summaryrefslogtreecommitdiff
path: root/protocols
diff options
context:
space:
mode:
Diffstat (limited to 'protocols')
-rw-r--r--protocols/Telegram/Telegram.vcxproj2
-rw-r--r--protocols/Telegram/Telegram.vcxproj.filters33
-rw-r--r--protocols/Telegram/src/main.cpp17
-rw-r--r--protocols/Telegram/src/stdafx.h6
-rw-r--r--protocols/Telegram/tdlib/build/tdactor.vcxproj164
-rw-r--r--protocols/Telegram/tdlib/build/tdcore.vcxproj398
-rw-r--r--protocols/Telegram/tdlib/build/tddb.vcxproj171
-rw-r--r--protocols/Telegram/tdlib/build/tdnet.vcxproj166
-rw-r--r--protocols/Telegram/tdlib/build/tdsqlite.vcxproj132
-rw-r--r--protocols/Telegram/tdlib/build/tdsqlite.vcxproj.filters27
-rw-r--r--protocols/Telegram/tdlib/build/tdutils.vcxproj278
-rw-r--r--protocols/Telegram/tdlib/td/.clang-format74
-rw-r--r--protocols/Telegram/tdlib/td/.gitattributes14
-rw-r--r--protocols/Telegram/tdlib/td/.gitignore1
-rw-r--r--protocols/Telegram/tdlib/td/.ycm_extra_conf.py159
-rw-r--r--protocols/Telegram/tdlib/td/CHANGELOG.md1447
-rw-r--r--protocols/Telegram/tdlib/td/CMake/AddCXXCompilerFlag.cmake16
-rw-r--r--protocols/Telegram/tdlib/td/CMake/FindReadline.cmake8
-rw-r--r--protocols/Telegram/tdlib/td/CMake/GeneratePkgConfig.cmake92
-rw-r--r--protocols/Telegram/tdlib/td/CMake/GetGitRevisionDescription.cmake127
-rw-r--r--protocols/Telegram/tdlib/td/CMake/GetGitRevisionDescription.cmake.in43
-rw-r--r--protocols/Telegram/tdlib/td/CMake/PreventInSourceBuild.cmake14
-rw-r--r--protocols/Telegram/tdlib/td/CMake/TdSetUpCompiler.cmake174
-rw-r--r--protocols/Telegram/tdlib/td/CMake/iOS.cmake69
-rw-r--r--protocols/Telegram/tdlib/td/CMake/illumos.cmake10
-rw-r--r--protocols/Telegram/tdlib/td/CMakeLists.txt733
-rw-r--r--protocols/Telegram/tdlib/td/README.md93
-rw-r--r--protocols/Telegram/tdlib/td/SplitSource.php368
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/CMakeLists.txt38
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_actor.cpp156
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_crypto.cpp392
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_db.cpp89
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_empty.cpp3
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_handshake.cpp48
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_http.cpp59
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_http_reader.cpp40
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_http_server.cpp57
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_http_server_cheat.cpp91
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_http_server_fast.cpp85
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_log.cpp63
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_misc.cpp590
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_queue.cpp400
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/bench_tddb.cpp87
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/check_proxy.cpp193
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/check_tls.cpp336
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/hashmap_build.cpp545
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/hashset_memory.cpp193
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/rmdir.cpp13
-rw-r--r--protocols/Telegram/tdlib/td/benchmark/wget.cpp32
-rw-r--r--protocols/Telegram/tdlib/td/bitbucket-pipelines.yml2
-rw-r--r--protocols/Telegram/tdlib/td/build.html1141
-rw-r--r--protocols/Telegram/tdlib/td/example/README.md315
-rw-r--r--protocols/Telegram/tdlib/td/example/android/.gitignore3
-rw-r--r--protocols/Telegram/tdlib/td/example/android/AddIntDef.php51
-rw-r--r--protocols/Telegram/tdlib/td/example/android/CMakeLists.txt48
-rw-r--r--protocols/Telegram/tdlib/td/example/android/Dockerfile26
-rw-r--r--protocols/Telegram/tdlib/td/example/android/README.md24
-rw-r--r--protocols/Telegram/tdlib/td/example/android/build-openssl.sh78
-rw-r--r--protocols/Telegram/tdlib/td/example/android/build-tdlib.sh90
-rw-r--r--protocols/Telegram/tdlib/td/example/android/check-environment.sh50
-rw-r--r--protocols/Telegram/tdlib/td/example/android/fetch-sdk.sh30
-rw-r--r--protocols/Telegram/tdlib/td/example/cpp/CMakeLists.txt4
-rw-r--r--protocols/Telegram/tdlib/td/example/cpp/README.md4
-rw-r--r--protocols/Telegram/tdlib/td/example/cpp/td_example.cpp250
-rw-r--r--protocols/Telegram/tdlib/td/example/cpp/tdjson_example.cpp36
-rw-r--r--protocols/Telegram/tdlib/td/example/csharp/README.md18
-rw-r--r--protocols/Telegram/tdlib/td/example/csharp/TdExample.cs98
-rw-r--r--protocols/Telegram/tdlib/td/example/csharp/TdExample.csproj64
-rw-r--r--protocols/Telegram/tdlib/td/example/go/main.go26
-rw-r--r--protocols/Telegram/tdlib/td/example/ios/Python-Apple-support.patch104
-rw-r--r--protocols/Telegram/tdlib/td/example/ios/README.md31
-rw-r--r--protocols/Telegram/tdlib/td/example/ios/build-openssl.sh39
-rw-r--r--protocols/Telegram/tdlib/td/example/ios/build.sh116
-rw-r--r--protocols/Telegram/tdlib/td/example/ios/openssl-1.0.2n-darwin-arm64.patch12
-rw-r--r--protocols/Telegram/tdlib/td/example/java/CMakeLists.txt68
-rw-r--r--protocols/Telegram/tdlib/td/example/java/README.md16
-rw-r--r--protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java277
-rw-r--r--protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Log.java75
-rw-r--r--protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java372
-rw-r--r--protocols/Telegram/tdlib/td/example/java/td_jni.cpp128
-rw-r--r--protocols/Telegram/tdlib/td/example/python/README.md4
-rw-r--r--protocols/Telegram/tdlib/td/example/python/tdjson_example.py164
-rw-r--r--protocols/Telegram/tdlib/td/example/ruby/Gemfile3
-rw-r--r--protocols/Telegram/tdlib/td/example/ruby/Gemfile.lock17
-rw-r--r--protocols/Telegram/tdlib/td/example/ruby/example.rb61
-rw-r--r--protocols/Telegram/tdlib/td/example/swift/README.md6
-rw-r--r--protocols/Telegram/tdlib/td/example/swift/src/main.swift88
-rw-r--r--protocols/Telegram/tdlib/td/example/swift/src/td-Bridging-Header.h8
-rw-r--r--protocols/Telegram/tdlib/td/example/swift/td.xcodeproj/project.pbxproj16
-rw-r--r--protocols/Telegram/tdlib/td/example/uwp/README.md16
-rw-r--r--protocols/Telegram/tdlib/td/example/uwp/app/App.xaml.cs2
-rw-r--r--protocols/Telegram/tdlib/td/example/uwp/app/MainPage.xaml.cs76
-rw-r--r--protocols/Telegram/tdlib/td/example/uwp/app/Properties/AssemblyInfo.cs4
-rw-r--r--protocols/Telegram/tdlib/td/example/uwp/app/TdApp.csproj2
-rw-r--r--protocols/Telegram/tdlib/td/example/uwp/build.ps145
-rw-r--r--protocols/Telegram/tdlib/td/example/uwp/extension.vsixmanifest2
-rw-r--r--protocols/Telegram/tdlib/td/example/web/.gitignore5
-rw-r--r--protocols/Telegram/tdlib/td/example/web/README.md29
-rw-r--r--protocols/Telegram/tdlib/td/example/web/build-openssl.sh28
-rw-r--r--protocols/Telegram/tdlib/td/example/web/build-tdlib.sh44
-rw-r--r--protocols/Telegram/tdlib/td/example/web/build-tdweb.sh7
-rw-r--r--protocols/Telegram/tdlib/td/example/web/copy-tdlib.sh7
-rw-r--r--protocols/Telegram/tdlib/td/example/web/tdweb/README.md29
-rw-r--r--protocols/Telegram/tdlib/td/example/web/tdweb/package-lock.json7681
-rw-r--r--protocols/Telegram/tdlib/td/example/web/tdweb/package.json102
-rw-r--r--protocols/Telegram/tdlib/td/example/web/tdweb/src/index.js680
-rw-r--r--protocols/Telegram/tdlib/td/example/web/tdweb/src/logger.js47
-rw-r--r--protocols/Telegram/tdlib/td/example/web/tdweb/src/wasm-utils.js136
-rw-r--r--protocols/Telegram/tdlib/td/example/web/tdweb/src/worker.js1034
-rw-r--r--protocols/Telegram/tdlib/td/example/web/tdweb/webpack.config.js86
-rw-r--r--protocols/Telegram/tdlib/td/format.ps12
-rw-r--r--protocols/Telegram/tdlib/td/format.sh3
-rw-r--r--protocols/Telegram/tdlib/td/gen_git_commit_h.ps16
-rw-r--r--protocols/Telegram/tdlib/td/gen_git_commit_h.sh13
-rw-r--r--protocols/Telegram/tdlib/td/memprof/memprof.cpp61
-rw-r--r--protocols/Telegram/tdlib/td/memprof/memprof.h2
-rw-r--r--protocols/Telegram/tdlib/td/memprof/memprof_stat.cpp166
-rw-r--r--protocols/Telegram/tdlib/td/memprof/memprof_stat.h (renamed from protocols/Telegram/tdlib/td/test/tests_runner.h)13
-rw-r--r--protocols/Telegram/tdlib/td/post.js1
-rw-r--r--protocols/Telegram/tdlib/td/sqlite/CMakeLists.txt44
-rw-r--r--protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.c109762
-rw-r--r--protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.h6597
-rw-r--r--protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3ext.h930
-rw-r--r--protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3session.h757
-rw-r--r--protocols/Telegram/tdlib/td/src.ps12
-rw-r--r--protocols/Telegram/tdlib/td/src.sh2
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/CMakeLists.txt121
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/DotnetTlDocumentationGenerator.php37
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/DoxygenTlDocumentationGenerator.php111
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/JavadocTlDocumentationGenerator.php66
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php69
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/generate_c.cpp4
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/generate_common.cpp7
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/generate_dotnet.cpp5
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/generate_java.cpp2
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/generate_json.cpp5
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/remove_documentation.cpp2
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/scheme/mtproto_api.tl25
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/scheme/mtproto_api.tlobin8276 -> 0 bytes
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/scheme/secret_api.tl28
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/scheme/secret_api.tlobin14120 -> 0 bytes
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/scheme/td_api.tl5991
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/scheme/td_api.tlobin112840 -> 0 bytes
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/scheme/telegram_api.tl1561
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/scheme/telegram_api.tlobin149276 -> 0 bytes
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/scheme/update-tlo.sh6
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/CMakeLists.txt25
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/LICENSE339
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/crc32.c345
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/crc32.h37
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/portable_endian.h88
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser-tree.h178
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser.c3078
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser.h223
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-tl.h55
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/tlc.c165
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/wgetopt.c1274
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl-parser/wgetopt.h193
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_json_converter.cpp153
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_json_converter.h5
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_c.h244
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.cpp97
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.h15
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_dotnet.h190
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_h.cpp102
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_h.h19
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.cpp15
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.h98
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_java.cpp53
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_java.h117
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.cpp59
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.h80
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.cpp20
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.h41
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_td.cpp52
-rw-r--r--protocols/Telegram/tdlib/td/td/generate/tl_writer_td.h5
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/AuthData.cpp54
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/AuthData.h69
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/AuthKey.h72
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/ConnectionManager.cpp37
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/ConnectionManager.h70
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/CryptoStorer.h127
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/DhCallback.h29
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/DhHandshake.cpp232
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/DhHandshake.h (renamed from protocols/Telegram/tdlib/td/td/mtproto/crypto.h)72
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/Handshake.cpp269
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/Handshake.h99
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.cpp52
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.h64
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/HandshakeConnection.h36
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.cpp53
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.h37
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.cpp16
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.h14
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/KDF.cpp107
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/KDF.h22
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/MtprotoQuery.h25
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/NoCryptoStorer.h33
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/PacketInfo.h35
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/PacketStorer.h15
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/Ping.cpp104
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/Ping.h25
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/PingConnection.cpp201
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/PingConnection.h69
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/ProxySecret.cpp53
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/ProxySecret.h69
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/RSA.cpp158
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/RSA.h49
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/RawConnection.cpp494
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/RawConnection.h134
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.cpp458
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.h151
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.cpp138
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.h140
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/TlsInit.cpp517
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/TlsInit.h50
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/TlsReaderByteFlow.cpp40
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/TlsReaderByteFlow.h20
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp304
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/Transport.h197
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/TransportType.h27
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/crypto.cpp441
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/utils.cpp16
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/utils.h71
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AccessRights.h6
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Account.cpp605
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Account.h49
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AffectedHistory.h33
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.cpp602
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.h103
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.hpp51
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Application.cpp135
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Application.h28
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AttachMenuManager.cpp1099
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AttachMenuManager.h166
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AudiosManager.cpp206
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AudiosManager.h53
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AudiosManager.hpp133
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp1222
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AuthManager.h344
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AuthManager.hpp168
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AutoDownloadSettings.cpp144
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/AutoDownloadSettings.h38
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BackgroundId.h70
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BackgroundManager.cpp1273
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BackgroundManager.h200
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BackgroundType.cpp468
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BackgroundType.h134
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BackgroundType.hpp84
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BotCommand.cpp226
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BotCommand.h100
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BotCommandScope.cpp125
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BotCommandScope.h43
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BotMenuButton.cpp164
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/BotMenuButton.h83
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CallActor.cpp696
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CallActor.h127
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.cpp7
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.h13
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CallId.h16
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CallManager.cpp93
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CallManager.h39
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.cpp171
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.h39
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ChainId.h57
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ChannelId.h31
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ChannelParticipantFilter.cpp116
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ChannelParticipantFilter.h62
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ChannelType.h (renamed from protocols/Telegram/tdlib/td/test/TestsRunner.h)8
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ChatId.h30
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ChatReactions.cpp118
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ChatReactions.h83
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Client.cpp760
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Client.h171
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ClientActor.cpp32
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ClientActor.h49
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ClientDotNet.cpp150
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ClientJson.cpp170
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ClientJson.h27
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ConfigManager.cpp1836
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ConfigManager.h134
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ConfigShared.cpp126
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ConfigShared.h56
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ConnectionState.cpp38
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ConnectionState.h19
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Contact.cpp66
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Contact.h90
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ContactsManager.cpp15290
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ContactsManager.h1547
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CountryInfoManager.cpp543
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CountryInfoManager.h87
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/CustomEmojiId.h65
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.cpp24
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.h13
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Dependencies.cpp130
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Dependencies.h54
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.cpp270
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.h69
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DhCache.cpp21
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DhCache.h14
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DhConfig.h8
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogAction.cpp530
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogAction.h101
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogActionBar.cpp296
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogActionBar.h118
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogAdministrator.cpp26
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogAdministrator.h89
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogDate.h73
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogDb.cpp381
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogDb.h68
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogEventLog.cpp592
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogEventLog.h24
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogFilter.cpp480
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogFilter.h105
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogFilter.hpp84
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogFilterId.h73
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogId.cpp87
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogId.h29
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogInviteLink.cpp132
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogInviteLink.h161
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogListId.h138
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogLocation.cpp63
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogLocation.h63
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.cpp102
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.h74
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.hpp89
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.cpp855
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.h443
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogParticipantFilter.cpp142
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogParticipantFilter.h39
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogSource.cpp98
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DialogSource.h48
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Dimensions.cpp51
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Dimensions.h28
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Dimensions.hpp28
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Document.cpp113
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Document.h46
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Document.hpp107
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.cpp498
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.h66
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.hpp33
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DownloadManager.cpp840
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DownloadManager.h112
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DownloadManagerCallback.cpp125
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DownloadManagerCallback.h69
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DraftMessage.cpp95
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DraftMessage.h38
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/DraftMessage.hpp31
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/EmailVerification.cpp53
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/EmailVerification.h37
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/EmojiStatus.cpp330
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/EmojiStatus.h110
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/EncryptedFile.h91
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.cpp475
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.h196
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.hpp139
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/FolderId.h68
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopic.cpp52
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopic.h50
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.cpp42
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.h64
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.hpp54
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.cpp43
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.h45
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.hpp40
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicInfo.cpp70
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicInfo.h77
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicManager.cpp358
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ForumTopicManager.h81
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/FullMessageId.h68
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Game.cpp100
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Game.h30
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Game.hpp5
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GameManager.cpp319
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GameManager.h56
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GitCommitHash.cpp.in15
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GitCommitHash.h (renamed from protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/Scheduler.cpp)8
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Global.cpp278
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Global.h378
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GroupCallId.h53
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GroupCallManager.cpp4861
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GroupCallManager.h427
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipant.cpp321
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipant.h112
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipantOrder.cpp73
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipantOrder.h64
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GroupCallVideoPayload.cpp61
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/GroupCallVideoPayload.h40
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/HashtagHints.cpp27
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/HashtagHints.h10
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.cpp1811
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.h89
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputDialogId.cpp157
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputDialogId.h82
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputGroupCallId.cpp23
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputGroupCallId.h72
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputInvoice.cpp413
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputInvoice.h133
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputInvoice.hpp195
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputMessageText.cpp43
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputMessageText.h39
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/InputMessageText.hpp35
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/JsonValue.cpp247
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/JsonValue.h39
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/LabeledPricePart.h47
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/LanguagePackManager.cpp1920
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/LanguagePackManager.h201
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/LinkManager.cpp1958
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/LinkManager.h166
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Location.cpp138
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Location.h124
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Log.cpp49
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Log.h12
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/LogDotNet.cpp14
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Logging.cpp155
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Logging.h36
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageContent.cpp6244
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageContent.h276
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageContentType.cpp391
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageContentType.h91
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageCopyOptions.h57
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageDb.cpp1260
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageDb.h207
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageEntity.cpp3641
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageEntity.h178
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageEntity.hpp68
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.cpp328
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.h118
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.hpp115
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageId.cpp165
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageId.h217
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageLinkInfo.h33
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageReaction.cpp1017
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageReaction.h240
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageReaction.hpp124
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageReplyHeader.cpp62
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageReplyHeader.h29
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.cpp232
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.h74
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.hpp104
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageSearchFilter.cpp148
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageSearchFilter.h68
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageSender.cpp169
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageSender.h44
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageThreadDb.cpp343
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageThreadDb.h98
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageThreadInfo.h22
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageTtl.cpp27
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessageTtl.h56
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessagesDb.cpp1006
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessagesDb.h169
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessagesManager.cpp41493
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MessagesManager.h3942
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MinChannel.h21
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/MinChannel.hpp51
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NewPasswordState.cpp58
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NewPasswordState.h27
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Notification.h47
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationGroupId.h67
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationGroupKey.h43
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationGroupType.h69
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationId.h72
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationManager.cpp4227
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationManager.h417
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsManager.cpp1504
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsManager.h217
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsScope.cpp69
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsScope.h29
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationSound.cpp313
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationSound.h67
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationSoundType.h15
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationType.cpp406
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/NotificationType.h67
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/OptionManager.cpp863
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/OptionManager.h94
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/OrderInfo.cpp190
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/OrderInfo.h82
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/OrderInfo.hpp87
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PasswordManager.cpp790
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PasswordManager.h143
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Payments.cpp660
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Payments.h152
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Payments.hpp134
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PhoneNumberManager.cpp263
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PhoneNumberManager.h76
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Photo.cpp672
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Photo.h119
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Photo.hpp99
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PhotoFormat.h15
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PhotoSize.cpp447
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PhotoSize.h78
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PhotoSize.hpp60
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.cpp261
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.h272
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.hpp191
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PollId.h55
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PollId.hpp27
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PollManager.cpp1862
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PollManager.h254
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PollManager.hpp224
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Premium.cpp545
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Premium.h42
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.cpp109
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.h58
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.hpp77
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.cpp415
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.h91
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PtsManager.h9
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/PublicDialogType.h22
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/QueryCombiner.cpp109
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/QueryCombiner.h51
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/RecentDialogList.cpp275
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/RecentDialogList.h59
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.cpp395
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.h48
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.hpp100
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ReportReason.cpp109
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ReportReason.h61
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/RequestActor.h160
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/RestrictionReason.cpp101
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/RestrictionReason.h70
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ScheduledServerMessageId.h65
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.cpp74
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.h55
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.hpp71
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.cpp993
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.h241
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.cpp2
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.h7
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretChatId.h9
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretChatLayer.h21
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.cpp346
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.h70
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretInputMedia.cpp40
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecretInputMedia.h13
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecureManager.cpp1332
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecureManager.h99
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecureStorage.cpp410
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecureStorage.h195
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecureValue.cpp1470
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecureValue.h234
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SecureValue.hpp159
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.cpp189
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.h98
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.hpp55
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SentEmailCode.cpp29
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SentEmailCode.h50
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.cpp243
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.h48
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ServerMessageId.h43
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SetWithPosition.h228
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SpecialStickerSetType.cpp111
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SpecialStickerSetType.h66
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SponsoredMessageManager.cpp370
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SponsoredMessageManager.h70
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StateManager.cpp86
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StateManager.h94
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StickerFormat.cpp181
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StickerFormat.h44
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StickerSetId.h55
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StickerSetId.hpp27
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StickerType.cpp66
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StickerType.h29
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StickersManager.cpp8361
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StickersManager.h1043
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StickersManager.hpp530
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StorageManager.cpp265
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/StorageManager.h77
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SuggestedAction.cpp197
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/SuggestedAction.h79
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Support.cpp113
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Support.h24
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Td.cpp8834
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Td.h1002
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TdCallback.h8
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TdDb.cpp419
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TdDb.h104
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TdParameters.h2
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TermsOfService.cpp121
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TermsOfService.h74
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ThemeManager.cpp441
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/ThemeManager.h120
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TopDialogCategory.cpp109
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TopDialogCategory.h37
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.cpp592
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.h109
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.cpp207
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.h62
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.hpp31
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/UniqueId.h2
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.cpp3400
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.h562
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/UserId.h48
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Usernames.cpp222
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Usernames.h142
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Venue.cpp126
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Venue.h92
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/Version.h56
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.cpp301
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.h82
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.hpp92
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/VideosManager.cpp179
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/VideosManager.h54
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/VideosManager.hpp38
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.cpp272
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.h78
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.hpp81
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/WebPageBlock.cpp2424
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/WebPageBlock.h107
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/WebPageId.h16
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.cpp2542
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.h190
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/cli.cpp6017
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileBitmask.cpp172
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileBitmask.h41
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileData.h60
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileData.hpp137
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileDb.cpp192
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileDb.h54
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileDbId.h58
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.cpp241
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.h51
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileEncryptionKey.cpp101
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileEncryptionKey.h104
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.cpp36
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.h21
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.cpp42
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.h23
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.cpp167
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.h28
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.cpp358
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.h33
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.cpp50
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.h26
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileId.h9
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileId.hpp3
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.cpp142
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.h113
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.cpp148
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.h74
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderActor.h12
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.cpp287
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.h48
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileLocation.h997
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileLocation.hpp433
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp2941
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileManager.h398
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileManager.hpp118
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileSourceId.h59
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileSourceId.hpp28
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileStats.cpp165
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileStats.h72
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.cpp117
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.h14
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileType.cpp306
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileType.h68
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.cpp143
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.h50
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.cpp360
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.h71
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.cpp25
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.h27
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/files/ResourceState.h16
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/logevent/LogEvent.h222
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/logevent/LogEventHelper.cpp56
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/logevent/LogEventHelper.h57
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/logevent/SecretChatEvent.h334
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/misc.cpp110
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/misc.h21
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.cpp49
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.h37
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.cpp1255
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.h275
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.cpp134
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.h35
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/DcId.h28
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/DcOptions.h155
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.cpp89
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.h29
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.cpp78
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.h82
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetActor.cpp8
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetActor.h15
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.cpp93
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.h210
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCounter.h47
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.cpp76
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.h48
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.cpp57
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.h11
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.cpp227
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.h34
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQueryStats.cpp56
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetQueryStats.h49
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.cpp78
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.h56
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/NetType.h15
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/Proxy.cpp70
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/Proxy.h163
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.cpp126
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.h28
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.cpp58
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.h17
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/Session.cpp972
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/Session.h172
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.cpp74
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.h28
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.cpp172
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.h37
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/net/TempAuthKeyWatchdog.h45
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/td_c_client.cpp43
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/td_c_client.h22
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/td_emscripten.cpp37
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/td_json_client.cpp43
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/td_json_client.h128
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/td_log.cpp2
-rw-r--r--protocols/Telegram/tdlib/td/td/telegram/td_log.h20
-rw-r--r--protocols/Telegram/tdlib/td/td/tl/TlObject.h103
-rw-r--r--protocols/Telegram/tdlib/td/td/tl/tl_dotnet_object.h44
-rw-r--r--protocols/Telegram/tdlib/td/td/tl/tl_jni_object.cpp48
-rw-r--r--protocols/Telegram/tdlib/td/td/tl/tl_jni_object.h38
-rw-r--r--protocols/Telegram/tdlib/td/td/tl/tl_json.h139
-rw-r--r--protocols/Telegram/tdlib/td/td/tl/tl_object_parse.h70
-rw-r--r--protocols/Telegram/tdlib/td/td/tl/tl_object_store.h45
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/CMakeLists.txt39
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/example/example.cpp28
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/ConcurrentScheduler.cpp205
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/ConcurrentScheduler.h (renamed from protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ConcurrentScheduler.h)71
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/Condition.h47
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.cpp25
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.h72
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/MultiTimeout.cpp (renamed from protocols/Telegram/tdlib/td/tdactor/td/actor/Timeout.cpp)42
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/MultiTimeout.h81
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/PromiseFuture.h360
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/SchedulerLocalStorage.h15
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/SignalSlot.h18
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/SleepActor.h18
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/Timeout.h83
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/actor.h3
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor-decl.h20
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor.h46
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId-decl.h72
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId.h69
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo-decl.h35
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo.h65
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ConcurrentScheduler.cpp102
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Event.h70
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull-decl.h6
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull.h4
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler-decl.h149
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.cpp250
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.h213
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorLocker.h117
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorSignals.h84
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorState.h166
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/Scheduler.h1508
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/test/actors_bugs.cpp93
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/test/actors_impl2.cpp535
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/test/actors_main.cpp322
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/test/actors_simple.cpp428
-rw-r--r--protocols/Telegram/tdlib/td/tdactor/test/actors_workers.cpp87
-rw-r--r--protocols/Telegram/tdlib/td/tdclientjson_export_list5
-rw-r--r--protocols/Telegram/tdlib/td/tddb/CMakeLists.txt23
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/BinlogKeyValue.h110
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/DbKey.h28
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/KeyValueSyncInterface.h17
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/Pmc.h27
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SeqKeyValue.h26
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteConnectionSafe.cpp51
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteConnectionSafe.h48
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.cpp215
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.h47
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValue.cpp124
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValue.h184
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.cpp75
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.h22
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueSafe.h4
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.cpp84
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.h32
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/TQueue.cpp558
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/TQueue.h155
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/TsSeqKeyValue.h25
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.cpp304
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.h54
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.cpp56
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.h74
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogHelper.h47
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogInterface.h42
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.cpp53
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.h36
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/binlog_dump.cpp138
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.cpp6
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.h8
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.cpp23
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.h15
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.cpp39
-rw-r--r--protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.h45
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/CMakeLists.txt54
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/DarwinHttp.h21
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/DarwinHttp.mm68
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp220
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.h68
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.cpp63
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.h17
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.cpp127
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.h159
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.cpp14
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.h4
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.cpp2
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.h4
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpHeaderCreator.h34
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.cpp18
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.h19
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.cpp8
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.h25
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpProxy.cpp110
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpProxy.h28
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.cpp38
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.h31
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.cpp327
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.h52
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/NetStats.h15
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.cpp133
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.h44
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/SslCtx.cpp312
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/SslCtx.h45
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/SslFd.cpp280
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/SslFd.h109
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/SslStream.cpp420
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/SslStream.h46
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.cpp26
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.h14
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/TransparentProxy.cpp84
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/TransparentProxy.h57
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/Wget.cpp96
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/Wget.h35
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/CMakeLists.txt4
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.cpp9
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.h8
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.cpp4
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.cpp4
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.h4
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.cpp17
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.cpp163
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.h9
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.cpp4
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple.h72
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple_parser.h4
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.cpp17
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.h6
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.cpp16
-rw-r--r--protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.h21
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/CMakeLists.txt214
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/generate/CMakeLists.txt20
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/generate/generate_mime_types_gperf.cpp22
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/generate/mime_types.txt28
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/AesCtrByteFlow.h26
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.cpp160
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.h51
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/AtomicRead.h89
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.cpp104
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.h30
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedFd.h62
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedReader.h16
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.cpp17
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.h177
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ByteFlow.h139
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/CancellationToken.h71
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ChainScheduler.h376
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ChangesProcessor.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Closure.h84
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/CombinedLog.h86
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ConcurrentHashTable.h322
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Container.h7
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Context.h44
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/DecTree.h216
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Destructor.h52
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Enumerator.h20
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/EpochBasedMemoryReclamation.h201
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.cpp20
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.h30
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.cpp124
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.h33
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMap.h24
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMapChunks.h575
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashSet.h24
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.cpp24
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.h551
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlFast.h81
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.cpp32
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.h35
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlStrict.h38
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.cpp59
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.h20
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.cpp83
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.h7
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Hash.h70
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/HashMap.h27
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/HashSet.h27
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/HashTableUtils.h72
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/HazardPointers.h54
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Heap.h67
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.cpp125
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.h21
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.cpp134
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.h43
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.cpp100
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.h230
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/List.h63
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MapNode.h166
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MemoryLog.h60
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.cpp2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MovableValue.h16
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.cpp (renamed from protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCounter.cpp)8
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.h56
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcWaiter.h281
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MpscLinkQueue.h13
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/MpscPollableQueue.h65
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Named.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/NullLog.h19
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ObjectPool.h14
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Observer.h6
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.cpp263
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.h81
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/OptionsParser.h150
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/OrderedEventsProcessor.h16
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Parser.h104
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.cpp70
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.h57
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Promise.h373
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Random.cpp119
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Random.h45
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ScopeGuard.h15
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/SetNode.h129
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/SharedObjectPool.h52
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.cpp (renamed from protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.cpp)13
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.h382
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Slice-decl.h49
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.cpp34
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.h90
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/SliceBuilder.h57
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Span.h158
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/SpinLock.h8
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.cpp72
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.h79
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Status.cpp40
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Status.h253
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/StealingQueue.h125
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Storer.h22
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/StorerBase.h4
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.cpp151
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.h75
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadLocalStorage.h55
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadSafeCounter.h132
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Time.cpp36
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Time.h50
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TimedStat.h36
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.cpp41
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.h19
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TlDowncastHelper.h29
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TlStorerToString.h180
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.cpp64
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.h33
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.cpp106
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.h (renamed from protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/SchedulerId.h)27
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TsList.h214
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.cpp21
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.h55
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/UInt.h91
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/Variant.h28
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/VectorQueue.h94
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashMap.h190
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashSet.h141
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeVector.h69
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/algorithm.h217
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/as.h81
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/base64.cpp289
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/base64.h11
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/benchmark.h18
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/bits.h310
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.cpp118
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.h206
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/check.cpp23
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/check.h32
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/common.h45
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/config.h5
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/config.h.in5
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.cpp1115
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.h143
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.cpp302
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.h32
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.cpp96
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.h22
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.cpp8
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/fixed_vector.h79
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/format.h68
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/int_types.h22
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/invoke.h72
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/logging.cpp271
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/logging.h244
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/misc.cpp200
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/misc.h179
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/optional.h71
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/overloaded.h4
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.cpp81
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.h19
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/CxCli.h65
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFd.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFdBase.h8
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.cpp1104
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.h226
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.cpp481
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.h50
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/FromApp.h100
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.cpp473
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.h52
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/IoSlice.h42
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.cpp109
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.h52
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/Mutex.h30
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/Poll.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollBase.h11
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.cpp71
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.h122
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/RwMutex.h10
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.cpp384
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.h35
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.cpp683
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.h53
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.cpp142
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.h26
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.cpp251
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.h49
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.cpp873
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.h93
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/config.h21
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.cpp82
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.h30
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.cpp45
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.h26
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.cpp94
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.h29
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.cpp41
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.h24
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.cpp110
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.h71
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.cpp115
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.h38
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.cpp261
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.h68
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.cpp44
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.h24
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/PollableFd.h230
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.cpp49
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.h27
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.cpp34
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.cpp217
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.h83
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadStl.h99
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.cpp70
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.h28
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/skip_eintr.h63
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.cpp304
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.h223
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.cpp25
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.h29
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.cpp97
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.h20
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.cpp88
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.h6
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.cpp2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.h2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.cpp139
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.h23
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread.h5
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.cpp12
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.h18
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.cpp289
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.h (renamed from protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.h)8
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.cpp57
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.h16
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.cpp111
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.h4
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/queue.h40
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/tests.cpp271
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/tests.h258
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/tl_helpers.h148
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.cpp38
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.h98
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/tl_storers.h211
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/translit.cpp115
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/translit.h16
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/type_traits.h25
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/uint128.h293
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.cpp1484
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.h9
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/unique_ptr.h106
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.cpp117
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.h48
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/ChainScheduler.cpp244
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/ConcurrentHashMap.cpp252
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/Enumerator.cpp2
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/EpochBasedMemoryReclamation.cpp68
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/HashSet.cpp438
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/HazardPointers.cpp16
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/HttpUrl.cpp54
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/List.cpp169
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/MpmcQueue.cpp24
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/MpmcWaiter.cpp60
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/MpscLinkQueue.cpp17
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/OptionParser.cpp82
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/OrderedEventsProcessor.cpp6
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/SharedObjectPool.cpp25
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/SharedSlice.cpp91
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/StealingQueue.cpp180
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashMap.cpp95
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashSet.cpp73
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/WaitFreeVector.cpp69
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/bitmask.cpp249
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/buffer.cpp56
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/crypto.cpp333
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/emoji.cpp128
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/filesystem.cpp68
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/gzip.cpp188
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/hashset_benchmark.cpp647
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/heap.cpp47
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/json.cpp34
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/log.cpp187
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/misc.cpp1218
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/port.cpp316
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/pq.cpp138
-rw-r--r--protocols/Telegram/tdlib/td/tdutils/test/variant.cpp24
-rw-r--r--protocols/Telegram/tdlib/td/test/CMakeLists.txt51
-rw-r--r--protocols/Telegram/tdlib/td/test/TestsRunner.cpp63
-rw-r--r--protocols/Telegram/tdlib/td/test/country_info.cpp87
-rw-r--r--protocols/Telegram/tdlib/td/test/crypto.cpp279
-rw-r--r--protocols/Telegram/tdlib/td/test/data.cpp442
-rw-r--r--protocols/Telegram/tdlib/td/test/data.h15
-rw-r--r--protocols/Telegram/tdlib/td/test/db.cpp512
-rw-r--r--protocols/Telegram/tdlib/td/test/fuzz_url.cpp4
-rw-r--r--protocols/Telegram/tdlib/td/test/http.cpp358
-rw-r--r--protocols/Telegram/tdlib/td/test/link.cpp983
-rw-r--r--protocols/Telegram/tdlib/td/test/main.cpp56
-rw-r--r--protocols/Telegram/tdlib/td/test/message_entities.cpp1652
-rw-r--r--protocols/Telegram/tdlib/td/test/mtproto.cpp663
-rw-r--r--protocols/Telegram/tdlib/td/test/online.cpp632
-rw-r--r--protocols/Telegram/tdlib/td/test/poll.cpp58
-rw-r--r--protocols/Telegram/tdlib/td/test/secret.cpp376
-rw-r--r--protocols/Telegram/tdlib/td/test/secure_storage.cpp70
-rw-r--r--protocols/Telegram/tdlib/td/test/set_with_position.cpp263
-rw-r--r--protocols/Telegram/tdlib/td/test/string_cleaning.cpp103
-rw-r--r--protocols/Telegram/tdlib/td/test/tdclient.cpp1031
-rw-r--r--protocols/Telegram/tdlib/td/test/tests_runner.cpp18
-rw-r--r--protocols/Telegram/tdlib/td/test/tqueue.cpp250
-rw-r--r--protocols/Telegram/tdlib/tdactor.vcxproj67
-rw-r--r--protocols/Telegram/tdlib/tdactor.vcxproj.filters (renamed from protocols/Telegram/tdlib/build/tdactor.vcxproj.filters)0
-rw-r--r--protocols/Telegram/tdlib/tdcore.vcxproj295
-rw-r--r--protocols/Telegram/tdlib/tdcore.vcxproj.filters (renamed from protocols/Telegram/tdlib/build/tdcore.vcxproj.filters)0
-rw-r--r--protocols/Telegram/tdlib/tddb.vcxproj79
-rw-r--r--protocols/Telegram/tdlib/tddb.vcxproj.filters (renamed from protocols/Telegram/tdlib/build/tddb.vcxproj.filters)0
-rw-r--r--protocols/Telegram/tdlib/tdlib.vcxproj23
-rw-r--r--protocols/Telegram/tdlib/tdlib.vcxproj.filters26
-rw-r--r--protocols/Telegram/tdlib/tdnet.vcxproj77
-rw-r--r--protocols/Telegram/tdlib/tdnet.vcxproj.filters (renamed from protocols/Telegram/tdlib/build/tdnet.vcxproj.filters)0
-rw-r--r--protocols/Telegram/tdlib/tdutils.vcxproj189
-rw-r--r--protocols/Telegram/tdlib/tdutils.vcxproj.filters (renamed from protocols/Telegram/tdlib/build/tdutils.vcxproj.filters)0
1172 files changed, 313595 insertions, 105656 deletions
diff --git a/protocols/Telegram/Telegram.vcxproj b/protocols/Telegram/Telegram.vcxproj
index 47af72f50e..79d6b0d73f 100644
--- a/protocols/Telegram/Telegram.vcxproj
+++ b/protocols/Telegram/Telegram.vcxproj
@@ -26,6 +26,8 @@
<Import Project="$(ProjectDir)..\..\build\vc.common\plugin.props" />
</ImportGroup>
<ItemGroup>
+ <ClCompile Include="src\main.cpp" />
+ <ClCompile Include="src\mt_proto.cpp" />
<ClCompile Include="src\stdafx.cxx">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
diff --git a/protocols/Telegram/Telegram.vcxproj.filters b/protocols/Telegram/Telegram.vcxproj.filters
index 83cffdb711..556fdc07cc 100644
--- a/protocols/Telegram/Telegram.vcxproj.filters
+++ b/protocols/Telegram/Telegram.vcxproj.filters
@@ -2,17 +2,42 @@
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(ProjectDir)..\..\build\vc.common\common.filters" />
<ItemGroup>
- <ClInclude Include="src\*.h">
+ <ClInclude Include="src\resource.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClCompile Include="src\*.cpp;src\*.cxx">
+ <ClInclude Include="src\stdafx.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClCompile Include="src\mt_proto.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ResourceCompile Include="res\*.rc">
+ <ClCompile Include="src\stdafx.cxx">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ResourceCompile Include="res\version.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
- <None Include="res\*.ico;res\*.bmp;res\*.cur">
+ <None Include="res\telegram.ico">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="src\stdafx.cxx">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\mt_proto.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <Image Include="telegram.ico">
+ <Filter>Resource Files</Filter>
+ </Image>
+ </ItemGroup>
</Project> \ No newline at end of file
diff --git a/protocols/Telegram/src/main.cpp b/protocols/Telegram/src/main.cpp
index 3e25624f7f..0315936c3f 100644
--- a/protocols/Telegram/src/main.cpp
+++ b/protocols/Telegram/src/main.cpp
@@ -1,8 +1,11 @@
#include "stdafx.h"
int hLangpack;
+CMPlugin g_plugin;
+
+/////////////////////////////////////////////////////////////////////////////////////////
-PLUGININFOEX pluginInfo =
+static PLUGININFOEX pluginInfo =
{
sizeof(PLUGININFOEX),
__PLUGIN_NAME,
@@ -14,14 +17,13 @@ PLUGININFOEX pluginInfo =
UNICODE_AWARE,
// {AE708252-0DF8-42BA-9EF9-9ACC038EEDA7}
{0xae708252, 0xdf8, 0x42ba, {0x9e, 0xf9, 0x9a, 0xcc, 0x3, 0x8e, 0xed, 0xa7}}
-
};
-/////////////////////////////////////////////////////////////////////////////////////////
-
-CMPlugin g_plugin;
-
-extern "C" _pfnCrtInit _pRawDllMain = &CMPlugin::RawDllMain;
+CMPlugin::CMPlugin() :
+ ACCPROTOPLUGIN<CMTProto>("Telegram", pluginInfo)
+{
+ SetUniqueId("Phone");
+}
/////////////////////////////////////////////////////////////////////////////////////////
@@ -31,7 +33,6 @@ extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOC
extern "C" int __declspec(dllexport) Load(void)
{
- mir_getLP(&pluginInfo);
return 0;
}
diff --git a/protocols/Telegram/src/stdafx.h b/protocols/Telegram/src/stdafx.h
index 06f32c205d..f039dc6474 100644
--- a/protocols/Telegram/src/stdafx.h
+++ b/protocols/Telegram/src/stdafx.h
@@ -21,11 +21,7 @@ struct CMTProto;
struct CMPlugin : public ACCPROTOPLUGIN<CMTProto>
{
- CMPlugin() :
- ACCPROTOPLUGIN<CMTProto>("TELEGRAM")
- {
- SetUniqueId("Phone");
- }
+ CMPlugin();
};
#endif //_COMMON_H_ \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tdactor.vcxproj b/protocols/Telegram/tdlib/build/tdactor.vcxproj
deleted file mode 100644
index b25d56e2c8..0000000000
--- a/protocols/Telegram/tdlib/build/tdactor.vcxproj
+++ /dev/null
@@ -1,164 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{85F63934-02FE-332A-8703-059040B65512}</ProjectGuid>
- <ProjectName>tdactor</ProjectName>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <PlatformToolset>v141</PlatformToolset>
- <ConfigurationType>StaticLibrary</ConfigurationType>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <PlatformToolset>v141</PlatformToolset>
- <ConfigurationType>StaticLibrary</ConfigurationType>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <ImportGroup Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <PropertyGroup Label="UserMacros" />
- <PropertyGroup>
- <_ProjectFileVersion>10.0.20506.1</_ProjectFileVersion>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdactor;..\td\tdutils;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <WarningLevel>Level4</WarningLevel>
- <Optimization>Disabled</Optimization>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdactor;..\td\tdutils;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <WarningLevel>Level4</WarningLevel>
- <Optimization>Disabled</Optimization>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdactor;..\td\tdutils;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdactor;..\td\tdutils;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemGroup>
- <ClCompile Include="..\td\tdactor\td\actor\impl\ConcurrentScheduler.cpp" />
- <ClCompile Include="..\td\tdactor\td\actor\impl\Scheduler.cpp" />
- <ClCompile Include="..\td\tdactor\td\actor\MultiPromise.cpp" />
- <ClCompile Include="..\td\tdactor\td\actor\Timeout.cpp" />
- <ClCompile Include="..\td\tdactor\td\actor\impl2\Scheduler.cpp" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\Actor-decl.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\Actor.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\ActorId-decl.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\ActorId.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\ActorInfo-decl.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\ActorInfo.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\EventFull-decl.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\EventFull.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\ConcurrentScheduler.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\Event.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\Scheduler-decl.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl\Scheduler.h" />
- <ClInclude Include="..\td\tdactor\td\actor\Condition.h" />
- <ClInclude Include="..\td\tdactor\td\actor\MultiPromise.h" />
- <ClInclude Include="..\td\tdactor\td\actor\PromiseFuture.h" />
- <ClInclude Include="..\td\tdactor\td\actor\SchedulerLocalStorage.h" />
- <ClInclude Include="..\td\tdactor\td\actor\SignalSlot.h" />
- <ClInclude Include="..\td\tdactor\td\actor\SleepActor.h" />
- <ClInclude Include="..\td\tdactor\td\actor\Timeout.h" />
- <ClInclude Include="..\td\tdactor\td\actor\actor.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl2\ActorLocker.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl2\ActorSignals.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl2\ActorState.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl2\Scheduler.h" />
- <ClInclude Include="..\td\tdactor\td\actor\impl2\SchedulerId.h" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="tdutils.vcxproj">
- <Project>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</Project>
- <Name>tdutils</Name>
- </ProjectReference>
- </ItemGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tdcore.vcxproj b/protocols/Telegram/tdlib/build/tdcore.vcxproj
deleted file mode 100644
index 0500310881..0000000000
--- a/protocols/Telegram/tdlib/build/tdcore.vcxproj
+++ /dev/null
@@ -1,398 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{FC88FB5A-AAED-3F3E-9959-236444D8F644}</ProjectGuid>
- <ProjectName>tdcore</ProjectName>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <ImportGroup Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <PropertyGroup Label="UserMacros" />
- <PropertyGroup>
- <_ProjectFileVersion>10.0.20506.1</_ProjectFileVersion>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td;..\td\td\generate\auto;..\..\..\..\include;..\td\tdactor;..\td\tdutils;..\td\tdnet;..\td\tddb;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <Optimization>Disabled</Optimization>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td;..\td\td\generate\auto;..\..\..\..\include;..\td\tdactor;..\td\tdutils;..\td\tdnet;..\td\tddb;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <Optimization>Disabled</Optimization>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td;..\td\td\generate\auto;..\..\..\..\include;..\td\tdactor;..\td\tdutils;..\td\tdnet;..\td\tddb;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td;..\td\td\generate\auto;..\..\..\..\include;..\td\tdactor;..\td\tdutils;..\td\tdnet;..\td\tddb;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemGroup>
- <ClCompile Include="..\td\td\mtproto\AuthData.cpp" />
- <ClCompile Include="..\td\td\mtproto\crypto.cpp" />
- <ClCompile Include="..\td\td\mtproto\Handshake.cpp" />
- <ClCompile Include="..\td\td\mtproto\HandshakeActor.cpp" />
- <ClCompile Include="..\td\td\mtproto\HttpTransport.cpp" />
- <ClCompile Include="..\td\td\mtproto\IStreamTransport.cpp" />
- <ClCompile Include="..\td\td\mtproto\RawConnection.cpp" />
- <ClCompile Include="..\td\td\mtproto\SessionConnection.cpp" />
- <ClCompile Include="..\td\td\mtproto\TcpTransport.cpp" />
- <ClCompile Include="..\td\td\mtproto\Transport.cpp" />
- <ClCompile Include="..\td\td\mtproto\utils.cpp" />
- <ClCompile Include="..\td\td\telegram\AnimationsManager.cpp" />
- <ClCompile Include="..\td\td\telegram\AudiosManager.cpp" />
- <ClCompile Include="..\td\td\telegram\AuthManager.cpp" />
- <ClCompile Include="..\td\td\telegram\CallActor.cpp" />
- <ClCompile Include="..\td\td\telegram\CallDiscardReason.cpp" />
- <ClCompile Include="..\td\td\telegram\CallManager.cpp" />
- <ClCompile Include="..\td\td\telegram\CallbackQueriesManager.cpp" />
- <ClCompile Include="..\td\td\telegram\ClientActor.cpp" />
- <ClCompile Include="..\td\td\telegram\ConfigManager.cpp" />
- <ClCompile Include="..\td\td\telegram\ConfigShared.cpp" />
- <ClCompile Include="..\td\td\telegram\Contact.cpp" />
- <ClCompile Include="..\td\td\telegram\ContactsManager.cpp" />
- <ClCompile Include="..\td\td\telegram\DelayDispatcher.cpp" />
- <ClCompile Include="..\td\td\telegram\DeviceTokenManager.cpp" />
- <ClCompile Include="..\td\td\telegram\DhCache.cpp" />
- <ClCompile Include="..\td\td\telegram\DialogDb.cpp" />
- <ClCompile Include="..\td\td\telegram\DialogId.cpp" />
- <ClCompile Include="..\td\td\telegram\DialogParticipant.cpp" />
- <ClCompile Include="..\td\td\telegram\DocumentsManager.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileDb.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileDownloader.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileFromBytes.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileGcParameters.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileGcWorker.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileGenerateManager.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileHashUploader.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileLoader.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileLoaderUtils.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileLoadManager.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileManager.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileStats.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileStatsWorker.cpp" />
- <ClCompile Include="..\td\td\telegram\files\FileUploader.cpp" />
- <ClCompile Include="..\td\td\telegram\files\PartsManager.cpp" />
- <ClCompile Include="..\td\td\telegram\files\ResourceManager.cpp" />
- <ClCompile Include="..\td\td\telegram\Game.cpp" />
- <ClCompile Include="..\td\td\telegram\Global.cpp" />
- <ClCompile Include="..\td\td\telegram\HashtagHints.cpp" />
- <ClCompile Include="..\td\td\telegram\InlineQueriesManager.cpp" />
- <ClCompile Include="..\td\td\telegram\Location.cpp" />
- <ClCompile Include="..\td\td\telegram\MessageEntity.cpp" />
- <ClCompile Include="..\td\td\telegram\MessagesDb.cpp" />
- <ClCompile Include="..\td\td\telegram\MessagesManager.cpp" />
- <ClCompile Include="..\td\td\telegram\misc.cpp" />
- <ClCompile Include="..\td\td\telegram\net\AuthDataShared.cpp" />
- <ClCompile Include="..\td\td\telegram\net\ConnectionCreator.cpp" />
- <ClCompile Include="..\td\td\telegram\net\DcAuthManager.cpp" />
- <ClCompile Include="..\td\td\telegram\net\DcOptionsSet.cpp" />
- <ClCompile Include="..\td\td\telegram\net\MtprotoHeader.cpp" />
- <ClCompile Include="..\td\td\telegram\net\NetActor.cpp" />
- <ClCompile Include="..\td\td\telegram\net\NetQuery.cpp" />
- <ClCompile Include="..\td\td\telegram\net\NetQueryCounter.cpp" />
- <ClCompile Include="..\td\td\telegram\net\NetQueryCreator.cpp" />
- <ClCompile Include="..\td\td\telegram\net\NetQueryDelayer.cpp" />
- <ClCompile Include="..\td\td\telegram\net\NetQueryDispatcher.cpp" />
- <ClCompile Include="..\td\td\telegram\net\NetStatsManager.cpp" />
- <ClCompile Include="..\td\td\telegram\net\PublicRsaKeyShared.cpp" />
- <ClCompile Include="..\td\td\telegram\net\PublicRsaKeyWatchdog.cpp" />
- <ClCompile Include="..\td\td\telegram\net\Session.cpp" />
- <ClCompile Include="..\td\td\telegram\net\SessionProxy.cpp" />
- <ClCompile Include="..\td\td\telegram\net\SessionMultiProxy.cpp" />
- <ClCompile Include="..\td\td\telegram\Payments.cpp" />
- <ClCompile Include="..\td\td\telegram\PasswordManager.cpp" />
- <ClCompile Include="..\td\td\telegram\PrivacyManager.cpp" />
- <ClCompile Include="..\td\td\telegram\Photo.cpp" />
- <ClCompile Include="..\td\td\telegram\ReplyMarkup.cpp" />
- <ClCompile Include="..\td\td\telegram\SecretChatActor.cpp" />
- <ClCompile Include="..\td\td\telegram\SecretChatDb.cpp" />
- <ClCompile Include="..\td\td\telegram\SecretChatsManager.cpp" />
- <ClCompile Include="..\td\td\telegram\SequenceDispatcher.cpp" />
- <ClCompile Include="..\td\td\telegram\StateManager.cpp" />
- <ClCompile Include="..\td\td\telegram\StickersManager.cpp" />
- <ClCompile Include="..\td\td\telegram\StorageManager.cpp" />
- <ClCompile Include="..\td\td\telegram\Td.cpp" />
- <ClCompile Include="..\td\td\telegram\TdDb.cpp" />
- <ClCompile Include="..\td\td\telegram\TopDialogManager.cpp" />
- <ClCompile Include="..\td\td\telegram\UpdatesManager.cpp" />
- <ClCompile Include="..\td\td\telegram\VideoNotesManager.cpp" />
- <ClCompile Include="..\td\td\telegram\VideosManager.cpp" />
- <ClCompile Include="..\td\td\telegram\VoiceNotesManager.cpp" />
- <ClCompile Include="..\td\td\telegram\WebPagesManager.cpp" />
- <ClInclude Include="..\td\td\mtproto\AuthData.h" />
- <ClInclude Include="..\td\td\mtproto\AuthKey.h" />
- <ClInclude Include="..\td\td\mtproto\crypto.h" />
- <ClInclude Include="..\td\td\mtproto\CryptoStorer.h" />
- <ClInclude Include="..\td\td\mtproto\Handshake.h" />
- <ClInclude Include="..\td\td\mtproto\HandshakeActor.h" />
- <ClInclude Include="..\td\td\mtproto\HandshakeConnection.h" />
- <ClInclude Include="..\td\td\mtproto\HttpTransport.h" />
- <ClInclude Include="..\td\td\mtproto\IStreamTransport.h" />
- <ClInclude Include="..\td\td\mtproto\NoCryptoStorer.h" />
- <ClInclude Include="..\td\td\mtproto\PacketStorer.h" />
- <ClInclude Include="..\td\td\mtproto\PingConnection.h" />
- <ClInclude Include="..\td\td\mtproto\RawConnection.h" />
- <ClInclude Include="..\td\td\mtproto\SessionConnection.h" />
- <ClInclude Include="..\td\td\mtproto\TcpTransport.h" />
- <ClInclude Include="..\td\td\mtproto\Transport.h" />
- <ClInclude Include="..\td\td\mtproto\utils.h" />
- <ClInclude Include="..\td\td\telegram\AccessRights.h" />
- <ClInclude Include="..\td\td\telegram\AnimationsManager.h" />
- <ClInclude Include="..\td\td\telegram\AudiosManager.h" />
- <ClInclude Include="..\td\td\telegram\AuthManager.h" />
- <ClInclude Include="..\td\td\telegram\CallActor.h" />
- <ClInclude Include="..\td\td\telegram\CallDiscardReason.h" />
- <ClInclude Include="..\td\td\telegram\CallId.h" />
- <ClInclude Include="..\td\td\telegram\CallManager.h" />
- <ClInclude Include="..\td\td\telegram\CallbackQueriesManager.h" />
- <ClInclude Include="..\td\td\telegram\ChannelId.h" />
- <ClInclude Include="..\td\td\telegram\ChatId.h" />
- <ClInclude Include="..\td\td\telegram\ClientActor.h" />
- <ClInclude Include="..\td\td\telegram\ConfigManager.h" />
- <ClInclude Include="..\td\td\telegram\ConfigShared.h" />
- <ClInclude Include="..\td\td\telegram\Contact.h" />
- <ClInclude Include="..\td\td\telegram\ContactsManager.h" />
- <ClInclude Include="..\td\td\telegram\DelayDispatcher.h" />
- <ClInclude Include="..\td\td\telegram\DeviceTokenManager.h" />
- <ClInclude Include="..\td\td\telegram\DhCache.h" />
- <ClInclude Include="..\td\td\telegram\DhConfig.h" />
- <ClInclude Include="..\td\td\telegram\DialogDb.h" />
- <ClInclude Include="..\td\td\telegram\DialogId.h" />
- <ClInclude Include="..\td\td\telegram\DialogParticipant.h" />
- <ClInclude Include="..\td\td\telegram\DocumentsManager.h" />
- <ClInclude Include="..\td\td\telegram\files\FileDb.h" />
- <ClInclude Include="..\td\td\telegram\files\FileDownloader.h" />
- <ClInclude Include="..\td\td\telegram\files\FileFromBytes.h" />
- <ClInclude Include="..\td\td\telegram\files\FileGcParameters.h" />
- <ClInclude Include="..\td\td\telegram\files\FileGcWorker.h" />
- <ClInclude Include="..\td\td\telegram\files\FileGenerateManager.h" />
- <ClInclude Include="..\td\td\telegram\files\FileHashUploader.h" />
- <ClInclude Include="..\td\td\telegram\files\FileId.h" />
- <ClInclude Include="..\td\td\telegram\files\FileLoaderActor.h" />
- <ClInclude Include="..\td\td\telegram\files\FileLoader.h" />
- <ClInclude Include="..\td\td\telegram\files\FileLoaderUtils.h" />
- <ClInclude Include="..\td\td\telegram\files\FileLoadManager.h" />
- <ClInclude Include="..\td\td\telegram\files\FileLocation.h" />
- <ClInclude Include="..\td\td\telegram\files\FileManager.h" />
- <ClInclude Include="..\td\td\telegram\files\FileStats.h" />
- <ClInclude Include="..\td\td\telegram\files\FileStatsWorker.h" />
- <ClInclude Include="..\td\td\telegram\files\FileUploader.h" />
- <ClInclude Include="..\td\td\telegram\files\PartsManager.h" />
- <ClInclude Include="..\td\td\telegram\files\ResourceManager.h" />
- <ClInclude Include="..\td\td\telegram\files\ResourceState.h" />
- <ClInclude Include="..\td\td\telegram\Game.h" />
- <ClInclude Include="..\td\td\telegram\Global.h" />
- <ClInclude Include="..\td\td\telegram\HashtagHints.h" />
- <ClInclude Include="..\td\td\telegram\InlineQueriesManager.h" />
- <ClInclude Include="..\td\td\telegram\Location.h" />
- <ClInclude Include="..\td\td\telegram\logevent\LogEvent.h" />
- <ClInclude Include="..\td\td\telegram\logevent\SecretChatEvent.h" />
- <ClInclude Include="..\td\td\telegram\MessageEntity.h" />
- <ClInclude Include="..\td\td\telegram\MessageId.h" />
- <ClInclude Include="..\td\td\telegram\MessagesDb.h" />
- <ClInclude Include="..\td\td\telegram\MessagesManager.h" />
- <ClInclude Include="..\td\td\telegram\misc.h" />
- <ClInclude Include="..\td\td\telegram\net\AuthDataShared.h" />
- <ClInclude Include="..\td\td\telegram\net\ConnectionCreator.h" />
- <ClInclude Include="..\td\td\telegram\net\DcAuthManager.h" />
- <ClInclude Include="..\td\td\telegram\net\DcId.h" />
- <ClInclude Include="..\td\td\telegram\net\DcOptions.h" />
- <ClInclude Include="..\td\td\telegram\net\DcOptionsSet.h" />
- <ClInclude Include="..\td\td\telegram\net\MtprotoHeader.h" />
- <ClInclude Include="..\td\td\telegram\net\NetActor.h" />
- <ClInclude Include="..\td\td\telegram\net\NetQuery.h" />
- <ClInclude Include="..\td\td\telegram\net\NetQueryCounter.h" />
- <ClInclude Include="..\td\td\telegram\net\NetQueryCreator.h" />
- <ClInclude Include="..\td\td\telegram\net\NetQueryDelayer.h" />
- <ClInclude Include="..\td\td\telegram\net\NetQueryDispatcher.h" />
- <ClInclude Include="..\td\td\telegram\net\NetStatsManager.h" />
- <ClInclude Include="..\td\td\telegram\net\NetType.h" />
- <ClInclude Include="..\td\td\telegram\net\PublicRsaKeyShared.h" />
- <ClInclude Include="..\td\td\telegram\net\PublicRsaKeyWatchdog.h" />
- <ClInclude Include="..\td\td\telegram\net\Session.h" />
- <ClInclude Include="..\td\td\telegram\net\SessionProxy.h" />
- <ClInclude Include="..\td\td\telegram\net\SessionMultiProxy.h" />
- <ClInclude Include="..\td\td\telegram\net\TempAuthKeyWatchdog.h" />
- <ClInclude Include="..\td\td\telegram\PasswordManager.h" />
- <ClInclude Include="..\td\td\telegram\Payments.h" />
- <ClInclude Include="..\td\td\telegram\Photo.h" />
- <ClInclude Include="..\td\td\telegram\PrivacyManager.h" />
- <ClInclude Include="..\td\td\telegram\PtsManager.h" />
- <ClInclude Include="..\td\td\telegram\ReplyMarkup.h" />
- <ClInclude Include="..\td\td\telegram\SecretChatActor.h" />
- <ClInclude Include="..\td\td\telegram\SecretChatId.h" />
- <ClInclude Include="..\td\td\telegram\SecretChatDb.h" />
- <ClInclude Include="..\td\td\telegram\SecretChatsManager.h" />
- <ClInclude Include="..\td\td\telegram\SecretInputMedia.h" />
- <ClInclude Include="..\td\td\telegram\SequenceDispatcher.h" />
- <ClInclude Include="..\td\td\telegram\StateManager.h" />
- <ClInclude Include="..\td\td\telegram\StickersManager.h" />
- <ClInclude Include="..\td\td\telegram\StorageManager.h" />
- <ClInclude Include="..\td\td\telegram\Td.h" />
- <ClInclude Include="..\td\td\telegram\TdCallback.h" />
- <ClInclude Include="..\td\td\telegram\TdDb.h" />
- <ClInclude Include="..\td\td\telegram\TdParameters.h" />
- <ClInclude Include="..\td\td\telegram\TopDialogManager.h" />
- <ClInclude Include="..\td\td\telegram\UniqueId.h" />
- <ClInclude Include="..\td\td\telegram\UpdatesManager.h" />
- <ClInclude Include="..\td\td\telegram\UserId.h" />
- <ClInclude Include="..\td\td\telegram\Version.h" />
- <ClInclude Include="..\td\td\telegram\VideoNotesManager.h" />
- <ClInclude Include="..\td\td\telegram\VideosManager.h" />
- <ClInclude Include="..\td\td\telegram\VoiceNotesManager.h" />
- <ClInclude Include="..\td\td\telegram\WebPageId.h" />
- <ClInclude Include="..\td\td\telegram\WebPagesManager.h" />
- <ClInclude Include="..\td\td\telegram\AnimationsManager.hpp" />
- <ClInclude Include="..\td\td\telegram\AudiosManager.hpp" />
- <ClInclude Include="..\td\td\telegram\AuthManager.hpp" />
- <ClInclude Include="..\td\td\telegram\DocumentsManager.hpp" />
- <ClInclude Include="..\td\td\telegram\files\FileId.hpp" />
- <ClInclude Include="..\td\td\telegram\files\FileManager.hpp" />
- <ClInclude Include="..\td\td\telegram\Game.hpp" />
- <ClInclude Include="..\td\td\telegram\Payments.hpp" />
- <ClInclude Include="..\td\td\telegram\Photo.hpp" />
- <ClInclude Include="..\td\td\telegram\ReplyMarkup.hpp" />
- <ClInclude Include="..\td\td\telegram\StickersManager.hpp" />
- <ClInclude Include="..\td\td\telegram\VideoNotesManager.hpp" />
- <ClInclude Include="..\td\td\telegram\VideosManager.hpp" />
- <ClInclude Include="..\td\td\telegram\VoiceNotesManager.hpp" />
- <ClCompile Include="..\td\td\generate\auto\td\mtproto\mtproto_api.cpp" />
- <ClInclude Include="..\td\td\generate\auto\td\mtproto\mtproto_api.h" />
- <ClInclude Include="..\td\td\generate\auto\td\mtproto\mtproto_api.hpp" />
- <ClCompile Include="..\td\td\generate\auto\td\telegram\td_api.cpp" />
- <ClInclude Include="..\td\td\generate\auto\td\telegram\td_api.h" />
- <ClInclude Include="..\td\td\generate\auto\td\telegram\td_api.hpp" />
- <ClCompile Include="..\td\td\generate\auto\td\telegram\telegram_api.cpp" />
- <ClInclude Include="..\td\td\generate\auto\td\telegram\telegram_api.h" />
- <ClInclude Include="..\td\td\generate\auto\td\telegram\telegram_api.hpp" />
- <ClCompile Include="..\td\td\generate\auto\td\telegram\secret_api.cpp" />
- <ClInclude Include="..\td\td\generate\auto\td\telegram\secret_api.h" />
- <ClInclude Include="..\td\td\generate\auto\td\telegram\secret_api.hpp" />
- <ClInclude Include="..\td\td\tl\TlObject.h" />
- <ClInclude Include="..\td\td\tl\tl_object_parse.h" />
- <ClInclude Include="..\td\td\tl\tl_object_store.h" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="tdactor.vcxproj">
- <Project>{85F63934-02FE-332A-8703-059040B65512}</Project>
- <Name>tdactor</Name>
- </ProjectReference>
- <ProjectReference Include="tddb.vcxproj">
- <Project>{F525EE11-8820-3D8A-87A5-465D50A98A64}</Project>
- <Name>tddb</Name>
- </ProjectReference>
- <ProjectReference Include="tdnet.vcxproj">
- <Project>{2246C3CF-7888-3102-984A-80214ADF418C}</Project>
- <Name>tdnet</Name>
- </ProjectReference>
- <ProjectReference Include="tdsqlite.vcxproj">
- <Project>{4FA94C32-60A9-33CC-B822-9BB1BDDD34FD}</Project>
- <Name>tdsqlite</Name>
- </ProjectReference>
- <ProjectReference Include="tdutils.vcxproj">
- <Project>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</Project>
- <Name>tdutils</Name>
- </ProjectReference>
- </ItemGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tddb.vcxproj b/protocols/Telegram/tdlib/build/tddb.vcxproj
deleted file mode 100644
index f7d21a06b5..0000000000
--- a/protocols/Telegram/tdlib/build/tddb.vcxproj
+++ /dev/null
@@ -1,171 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{F525EE11-8820-3D8A-87A5-465D50A98A64}</ProjectGuid>
- <ProjectName>tddb</ProjectName>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <ImportGroup Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <PropertyGroup Label="UserMacros" />
- <PropertyGroup>
- <_ProjectFileVersion>10.0.20506.1</_ProjectFileVersion>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tddb;..\td\tdactor;..\td\tdutils;..\td\build\tdutils;..\td\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <Optimization>Disabled</Optimization>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tddb;..\td\tdactor;..\td\tdutils;..\td\build\tdutils;..\td\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <Optimization>Disabled</Optimization>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tddb;..\td\tdactor;..\td\tdutils;..\td\build\tdutils;..\td\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tddb;..\td\tdactor;..\td\tdutils;..\td\build\tdutils;..\td\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemGroup>
- <ClCompile Include="..\td\tddb\td\db\binlog\Binlog.cpp" />
- <ClCompile Include="..\td\tddb\td\db\binlog\BinlogEvent.cpp" />
- <ClCompile Include="..\td\tddb\td\db\binlog\ConcurrentBinlog.cpp" />
- <ClCompile Include="..\td\tddb\td\db\binlog\detail\BinlogEventsBuffer.cpp" />
- <ClCompile Include="..\td\tddb\td\db\binlog\detail\BinlogEventsProcessor.cpp" />
- <ClCompile Include="..\td\tddb\td\db\SqliteDb.cpp" />
- <ClCompile Include="..\td\tddb\td\db\SqliteStatement.cpp" />
- <ClCompile Include="..\td\tddb\td\db\SqliteKeyValueAsync.cpp" />
- <ClCompile Include="..\td\tddb\td\db\detail\RawSqliteDb.cpp" />
- <ClInclude Include="..\td\tddb\td\db\binlog\Binlog.h" />
- <ClInclude Include="..\td\tddb\td\db\binlog\BinlogInterface.h" />
- <ClInclude Include="..\td\tddb\td\db\binlog\BinlogEvent.h" />
- <ClInclude Include="..\td\tddb\td\db\binlog\BinlogHelper.h" />
- <ClInclude Include="..\td\tddb\td\db\binlog\ConcurrentBinlog.h" />
- <ClInclude Include="..\td\tddb\td\db\binlog\detail\BinlogEventsBuffer.h" />
- <ClInclude Include="..\td\tddb\td\db\binlog\detail\BinlogEventsProcessor.h" />
- <ClInclude Include="..\td\tddb\td\db\BinlogKeyValue.h" />
- <ClInclude Include="..\td\tddb\td\db\DbKey.h" />
- <ClInclude Include="..\td\tddb\td\db\KeyValueSyncInterface.h" />
- <ClInclude Include="..\td\tddb\td\db\Pmc.h" />
- <ClInclude Include="..\td\tddb\td\db\SeqKeyValue.h" />
- <ClInclude Include="..\td\tddb\td\db\SqliteConnectionSafe.h" />
- <ClInclude Include="..\td\tddb\td\db\SqliteDb.h" />
- <ClInclude Include="..\td\tddb\td\db\SqliteKeyValue.h" />
- <ClInclude Include="..\td\tddb\td\db\SqliteKeyValueAsync.h" />
- <ClInclude Include="..\td\tddb\td\db\SqliteKeyValueSafe.h" />
- <ClInclude Include="..\td\tddb\td\db\SqliteStatement.h" />
- <ClInclude Include="..\td\tddb\td\db\TsSeqKeyValue.h" />
- <ClInclude Include="..\td\tddb\td\db\detail\RawSqliteDb.h" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="tdactor.vcxproj">
- <Project>{85F63934-02FE-332A-8703-059040B65512}</Project>
- <Name>tdactor</Name>
- </ProjectReference>
- <ProjectReference Include="tdsqlite.vcxproj">
- <Project>{4FA94C32-60A9-33CC-B822-9BB1BDDD34FD}</Project>
- <Name>tdsqlite</Name>
- </ProjectReference>
- <ProjectReference Include="tdutils.vcxproj">
- <Project>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</Project>
- <Name>tdutils</Name>
- </ProjectReference>
- </ItemGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tdnet.vcxproj b/protocols/Telegram/tdlib/build/tdnet.vcxproj
deleted file mode 100644
index 43626ae814..0000000000
--- a/protocols/Telegram/tdlib/build/tdnet.vcxproj
+++ /dev/null
@@ -1,166 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{2246C3CF-7888-3102-984A-80214ADF418C}</ProjectGuid>
- <ProjectName>tdnet</ProjectName>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <ImportGroup Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <PropertyGroup Label="UserMacros" />
- <PropertyGroup>
- <_ProjectFileVersion>10.0.20506.1</_ProjectFileVersion>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdnet;..\..\..\..\include;..\td\tdutils;..\td\tdactor;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <Optimization>Disabled</Optimization>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdnet;..\..\..\..\include;..\td\tdutils;..\td\tdactor;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <Optimization>Disabled</Optimization>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdnet;..\..\..\..\include;..\td\tdutils;..\td\tdactor;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdnet;..\..\..\..\include;..\td\tdutils;..\td\tdactor;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemGroup>
- <ClCompile Include="..\td\tdnet\td\net\GetHostByNameActor.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\HttpChunkedByteFlow.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\HttpConnectionBase.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\HttpContentLengthByteFlow.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\HttpFile.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\HttpInboundConnection.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\HttpOutboundConnection.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\HttpQuery.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\HttpReader.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\Socks5.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\SslFd.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\TcpListener.cpp" />
- <ClCompile Include="..\td\tdnet\td\net\Wget.cpp" />
- <ClInclude Include="..\td\tdnet\td\net\GetHostByNameActor.h" />
- <ClInclude Include="..\td\tdnet\td\net\HttpChunkedByteFlow.h" />
- <ClInclude Include="..\td\tdnet\td\net\HttpConnectionBase.h" />
- <ClInclude Include="..\td\tdnet\td\net\HttpContentLengthByteFlow.h" />
- <ClInclude Include="..\td\tdnet\td\net\HttpFile.h" />
- <ClInclude Include="..\td\tdnet\td\net\HttpHeaderCreator.h" />
- <ClInclude Include="..\td\tdnet\td\net\HttpInboundConnection.h" />
- <ClInclude Include="..\td\tdnet\td\net\HttpOutboundConnection.h" />
- <ClInclude Include="..\td\tdnet\td\net\HttpQuery.h" />
- <ClInclude Include="..\td\tdnet\td\net\HttpReader.h" />
- <ClInclude Include="..\td\tdnet\td\net\NetStats.h" />
- <ClInclude Include="..\td\tdnet\td\net\Socks5.h" />
- <ClInclude Include="..\td\tdnet\td\net\SslFd.h" />
- <ClInclude Include="..\td\tdnet\td\net\TcpListener.h" />
- <ClInclude Include="..\td\tdnet\td\net\Wget.h" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="tdactor.vcxproj">
- <Project>{85F63934-02FE-332A-8703-059040B65512}</Project>
- <Name>tdactor</Name>
- </ProjectReference>
- <ProjectReference Include="tdutils.vcxproj">
- <Project>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</Project>
- <Name>tdutils</Name>
- </ProjectReference>
- </ItemGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tdsqlite.vcxproj b/protocols/Telegram/tdlib/build/tdsqlite.vcxproj
deleted file mode 100644
index 2a3c25f0a7..0000000000
--- a/protocols/Telegram/tdlib/build/tdsqlite.vcxproj
+++ /dev/null
@@ -1,132 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{4FA94C32-60A9-33CC-B822-9BB1BDDD34FD}</ProjectGuid>
- <ProjectName>tdsqlite</ProjectName>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <ImportGroup Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <PropertyGroup Label="UserMacros" />
- <PropertyGroup>
- <_ProjectFileVersion>10.0.20506.1</_ProjectFileVersion>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\sqlite;..\..\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4996</DisableSpecificWarnings>
- <ExceptionHandling>
- </ExceptionHandling>
- <Optimization>Disabled</Optimization>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;SQLITE_DEFAULT_MEMSTATUS=0;SQLITE_OMIT_LOAD_EXTENSION;SQLITE_OMIT_DECLTYPE;SQLITE_OMIT_PROGRESS_CALLBACK;SQLITE_HAS_CODEC;SQLITE_TEMP_STORE=2;SQLITE_ENABLE_FTS5;SQLITE_DISABLE_LFS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\sqlite;..\..\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4996</DisableSpecificWarnings>
- <ExceptionHandling>
- </ExceptionHandling>
- <Optimization>Disabled</Optimization>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;SQLITE_DEFAULT_MEMSTATUS=0;SQLITE_OMIT_LOAD_EXTENSION;SQLITE_OMIT_DECLTYPE;SQLITE_OMIT_PROGRESS_CALLBACK;SQLITE_HAS_CODEC;SQLITE_TEMP_STORE=2;SQLITE_ENABLE_FTS5;SQLITE_DISABLE_LFS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\sqlite;..\..\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4996</DisableSpecificWarnings>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;SQLITE_DEFAULT_MEMSTATUS=0;SQLITE_OMIT_LOAD_EXTENSION;SQLITE_OMIT_DECLTYPE;SQLITE_OMIT_PROGRESS_CALLBACK;SQLITE_HAS_CODEC;SQLITE_TEMP_STORE=2;SQLITE_ENABLE_FTS5;SQLITE_DISABLE_LFS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\sqlite;..\..\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4996</DisableSpecificWarnings>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;SQLITE_DEFAULT_MEMSTATUS=0;SQLITE_OMIT_LOAD_EXTENSION;SQLITE_OMIT_DECLTYPE;SQLITE_OMIT_PROGRESS_CALLBACK;SQLITE_HAS_CODEC;SQLITE_TEMP_STORE=2;SQLITE_ENABLE_FTS5;SQLITE_DISABLE_LFS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemGroup>
- <ClCompile Include="..\td\sqlite\sqlite\sqlite3.c" />
- <ClInclude Include="..\td\sqlite\sqlite\sqlite3.h" />
- <ClInclude Include="..\td\sqlite\sqlite\sqlite3ext.h" />
- <ClInclude Include="..\td\sqlite\sqlite\sqlite3session.h" />
- </ItemGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tdsqlite.vcxproj.filters b/protocols/Telegram/tdlib/build/tdsqlite.vcxproj.filters
deleted file mode 100644
index 1128706fb0..0000000000
--- a/protocols/Telegram/tdlib/build/tdsqlite.vcxproj.filters
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup>
- <ClCompile Include="..\td\sqlite\sqlite\sqlite3.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- </ItemGroup>
- <ItemGroup>
- <ClInclude Include="..\td\sqlite\sqlite\sqlite3.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\td\sqlite\sqlite\sqlite3ext.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\td\sqlite\sqlite\sqlite3session.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- </ItemGroup>
- <ItemGroup>
- <Filter Include="Header Files">
- <UniqueIdentifier>{0FD26E20-5E51-396B-B4E5-98068F96B37E}</UniqueIdentifier>
- </Filter>
- <Filter Include="Source Files">
- <UniqueIdentifier>{CC4593AA-1CC3-37C8-BDF9-C5986B1808BD}</UniqueIdentifier>
- </Filter>
- </ItemGroup>
-</Project>
diff --git a/protocols/Telegram/tdlib/build/tdutils.vcxproj b/protocols/Telegram/tdlib/build/tdutils.vcxproj
deleted file mode 100644
index d893543680..0000000000
--- a/protocols/Telegram/tdlib/build/tdutils.vcxproj
+++ /dev/null
@@ -1,278 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</ProjectGuid>
- <ProjectName>tdutils</ProjectName>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v141</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <ImportGroup Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
- <PropertyGroup Label="UserMacros" />
- <PropertyGroup>
- <_ProjectFileVersion>10.0.20506.1</_ProjectFileVersion>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <OutDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <OutDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
- <IntDir>$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</IntDir>
- </PropertyGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdutils;..\..\..\..\include;..\..\..\..\libs\zlib\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <Optimization>Disabled</Optimization>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdutils;..\..\..\..\include;..\..\..\..\libs\zlib\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <Optimization>Disabled</Optimization>
- <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdutils;..\..\..\..\include;..\..\..\..\libs\zlib\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
- <ClCompile>
- <AdditionalIncludeDirectories>..\td\tdutils;..\..\..\..\include;..\..\..\..\libs\zlib\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <WarningLevel>Level4</WarningLevel>
- <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- </ClCompile>
- </ItemDefinitionGroup>
- <ItemGroup>
- <ClCompile Include="..\td\tdutils\td\utils\port\Clocks.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\Fd.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\FileFd.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\IPAddress.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\path.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\ServerSocketFd.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\signals.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\sleep.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\SocketFd.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\Stat.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\thread_local.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\wstring_convert.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\detail\Epoll.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\detail\EventFdBsd.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\detail\EventFdLinux.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\detail\EventFdWindows.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\detail\KQueue.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\detail\Poll.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\detail\Select.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\detail\ThreadIdGuard.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\port\detail\WineventPoll.cpp" />
- <ClCompile Include="..\td\tdutils\generate\auto\mime_type_to_extension.cpp" />
- <ClCompile Include="..\td\tdutils\generate\auto\extension_to_mime_type.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\base64.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\BigNum.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\buffer.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\crypto.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\FileLog.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\filesystem.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\find_boundary.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\Gzip.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\GzipByteFlow.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\Hints.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\HttpUrl.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\JsonBuilder.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\logging.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\misc.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\MimeType.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\Random.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\StackAllocator.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\Status.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\StringBuilder.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\Time.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\Timer.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\tl_parsers.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\unicode.cpp" />
- <ClCompile Include="..\td\tdutils\td\utils\utf8.cpp" />
- <ClInclude Include="..\td\tdutils\td\utils\port\Clocks.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\config.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\CxCli.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\EventFd.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\EventFdBase.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\Fd.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\FileFd.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\IPAddress.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\path.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\platform.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\Poll.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\PollBase.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\RwMutex.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\ServerSocketFd.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\signals.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\sleep.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\SocketFd.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\Stat.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\thread.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\thread_local.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\wstring_convert.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\Epoll.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\EventFdBsd.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\EventFdLinux.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\EventFdWindows.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\KQueue.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\Poll.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\Select.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\ThreadIdGuard.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\ThreadPthread.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\ThreadStl.h" />
- <ClInclude Include="..\td\tdutils\td\utils\port\detail\WineventPoll.h" />
- <ClInclude Include="..\td\tdutils\td\utils\AesCtrByteFlow.h" />
- <ClInclude Include="..\td\tdutils\td\utils\base64.h" />
- <ClInclude Include="..\td\tdutils\td\utils\benchmark.h" />
- <ClInclude Include="..\td\tdutils\td\utils\BigNum.h" />
- <ClInclude Include="..\td\tdutils\td\utils\buffer.h" />
- <ClInclude Include="..\td\tdutils\td\utils\BufferedFd.h" />
- <ClInclude Include="..\td\tdutils\td\utils\BufferedReader.h" />
- <ClInclude Include="..\td\tdutils\td\utils\ByteFlow.h" />
- <ClInclude Include="..\td\tdutils\td\utils\ChangesProcessor.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Closure.h" />
- <ClInclude Include="..\td\tdutils\td\utils\common.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Container.h" />
- <ClInclude Include="..\td\tdutils\td\utils\crypto.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Enumerator.h" />
- <ClInclude Include="..\td\tdutils\td\utils\FileLog.h" />
- <ClInclude Include="..\td\tdutils\td\utils\filesystem.h" />
- <ClInclude Include="..\td\tdutils\td\utils\find_boundary.h" />
- <ClInclude Include="..\td\tdutils\td\utils\FloodControlFast.h" />
- <ClInclude Include="..\td\tdutils\td\utils\FloodControlStrict.h" />
- <ClInclude Include="..\td\tdutils\td\utils\format.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Gzip.h" />
- <ClInclude Include="..\td\tdutils\td\utils\GzipByteFlow.h" />
- <ClInclude Include="..\td\tdutils\td\utils\HazardPointers.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Heap.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Hints.h" />
- <ClInclude Include="..\td\tdutils\td\utils\HttpUrl.h" />
- <ClInclude Include="..\td\tdutils\td\utils\int_types.h" />
- <ClInclude Include="..\td\tdutils\td\utils\invoke.h" />
- <ClInclude Include="..\td\tdutils\td\utils\JsonBuilder.h" />
- <ClInclude Include="..\td\tdutils\td\utils\List.h" />
- <ClInclude Include="..\td\tdutils\td\utils\logging.h" />
- <ClInclude Include="..\td\tdutils\td\utils\MemoryLog.h" />
- <ClInclude Include="..\td\tdutils\td\utils\MimeType.h" />
- <ClInclude Include="..\td\tdutils\td\utils\misc.h" />
- <ClInclude Include="..\td\tdutils\td\utils\MovableValue.h" />
- <ClInclude Include="..\td\tdutils\td\utils\MpmcQueue.h" />
- <ClInclude Include="..\td\tdutils\td\utils\MpmcWaiter.h" />
- <ClInclude Include="..\td\tdutils\td\utils\MpscPollableQueue.h" />
- <ClInclude Include="..\td\tdutils\td\utils\MpscLinkQueue.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Named.h" />
- <ClInclude Include="..\td\tdutils\td\utils\ObjectPool.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Observer.h" />
- <ClInclude Include="..\td\tdutils\td\utils\optional.h" />
- <ClInclude Include="..\td\tdutils\td\utils\OptionsParser.h" />
- <ClInclude Include="..\td\tdutils\td\utils\OrderedEventsProcessor.h" />
- <ClInclude Include="..\td\tdutils\td\utils\overloaded.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Parser.h" />
- <ClInclude Include="..\td\tdutils\td\utils\PathView.h" />
- <ClInclude Include="..\td\tdutils\td\utils\queue.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Random.h" />
- <ClInclude Include="..\td\tdutils\td\utils\ScopeGuard.h" />
- <ClInclude Include="..\td\tdutils\td\utils\SharedObjectPool.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Slice-decl.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Slice.h" />
- <ClInclude Include="..\td\tdutils\td\utils\SpinLock.h" />
- <ClInclude Include="..\td\tdutils\td\utils\StackAllocator.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Status.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Storer.h" />
- <ClInclude Include="..\td\tdutils\td\utils\StorerBase.h" />
- <ClInclude Include="..\td\tdutils\td\utils\StringBuilder.h" />
- <ClInclude Include="..\td\tdutils\td\utils\tests.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Time.h" />
- <ClInclude Include="..\td\tdutils\td\utils\TimedStat.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Timer.h" />
- <ClInclude Include="..\td\tdutils\td\utils\tl_helpers.h" />
- <ClInclude Include="..\td\tdutils\td\utils\tl_parsers.h" />
- <ClInclude Include="..\td\tdutils\td\utils\tl_storers.h" />
- <ClInclude Include="..\td\tdutils\td\utils\type_traits.h" />
- <ClInclude Include="..\td\tdutils\td\utils\unicode.h" />
- <ClInclude Include="..\td\tdutils\td\utils\utf8.h" />
- <ClInclude Include="..\td\tdutils\td\utils\Variant.h" />
- </ItemGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/td/.clang-format b/protocols/Telegram/tdlib/td/.clang-format
index e169061bab..4e23f1d3a6 100644
--- a/protocols/Telegram/tdlib/td/.clang-format
+++ b/protocols/Telegram/tdlib/td/.clang-format
@@ -3,26 +3,35 @@ Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
-AlignConsecutiveAssignments: false
-AlignConsecutiveDeclarations: false
+AlignArrayOfStructures: None
+AlignConsecutiveMacros: None
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Left
-AlignOperands: true
+AlignOperands: Align
AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
-AllowShortBlocksOnASingleLine: false
+AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
+AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: None # All
-AllowShortIfStatementsOnASingleLine: false # true
+AllowShortIfStatementsOnASingleLine: Never # WithoutElse
+AllowShortLambdasOnASingleLine: Inline # All
AllowShortLoopsOnASingleLine: false # true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
-AlwaysBreakTemplateDeclarations: true
+AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
+BitFieldColonSpacing: Both
BraceWrapping:
+ AfterCaseLabel: false
AfterClass: false
- AfterControlStatement: false
+ AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
@@ -32,13 +41,17 @@ BraceWrapping:
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
+BreakBeforeConceptDeclarations: true
BreakBeforeInheritanceComma: true # false
+BreakInheritanceList: BeforeComma # BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true # false
BreakConstructorInitializers: BeforeComma # BeforeColon
@@ -51,48 +64,85 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
+DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
-# ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+ForEachMacros:
+ - Q_FOREACH_THIS_LIST_MUST_BE_NON_EMPTY
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '.*'
+ Priority: 0
+IndentAccessModifiers: false
+IndentCaseBlocks: false
IndentCaseLabels: true
+IndentExternBlock: AfterExternBlock
+IndentGotoLabels: true
IndentPPDirectives: None
+IndentRequires: false
IndentWidth: 2
IndentWrappedFunctionNames: false
+# InsertTrailingCommas: None
# JavaScriptQuotes: Leave
# JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
+# ObjCBinPackProtocolList: Never
# ObjCBlockIndentWidth: 2
+# ObjCBreakBeforeNestedBlockParam: true
# ObjCSpaceAfterProperty: false
-# ObjCSpaceBeforeProtocolList: false
+# ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
+PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 200
-PointerAlignment: Left
+PointerAlignment: Right
+PPIndentWidth: -1
+ReferenceAlignment: Pointer
ReflowComments: false # true
-SortIncludes: false # disabled, because we need case insensitive sort
+ShortNamespaceLines: 0 # 1
+SortIncludes: CaseInsensitive # CaseSensitive
+# SortJavaStaticImport: Before
SortUsingDeclarations: false # true
SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
+SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
-SpacesInAngles: false
+SpacesInAngles: Never
+SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+ Minimum: 1
+ Maximum: 1 # -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 100 # 8
+UseCRLF: false
UseTab: Never
...
diff --git a/protocols/Telegram/tdlib/td/.gitattributes b/protocols/Telegram/tdlib/td/.gitattributes
index ecb5de5494..06e5328a4f 100644
--- a/protocols/Telegram/tdlib/td/.gitattributes
+++ b/protocols/Telegram/tdlib/td/.gitattributes
@@ -5,32 +5,34 @@
*.h text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.c text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.tl text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
+*.mm text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.txt text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.sh text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent eol=lf
*.php text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.ps1 text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent eol=crlf
*.yml text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
-*.py text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.cmake text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.md text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
+*.in text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
+*.html text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.java text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.py text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
+*.js text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
+*.patch text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.swift text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
+*.pbxproj text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.cs text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.xaml text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.appxmanifest text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
+*.vsixmanifest text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.json text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.csproj text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.sln text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.xml text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
-*.rb text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
-*.lock text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
-*.go text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
+*.config text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
sqlite/sqlite/* linguist-vendored
-*.tlo binary
-
*.pfx binary
*.png binary
diff --git a/protocols/Telegram/tdlib/td/.gitignore b/protocols/Telegram/tdlib/td/.gitignore
index 4b29dd985e..2572f22305 100644
--- a/protocols/Telegram/tdlib/td/.gitignore
+++ b/protocols/Telegram/tdlib/td/.gitignore
@@ -6,3 +6,4 @@ auto/
db_backup
*.pyc
docs/
+vcpkg/
diff --git a/protocols/Telegram/tdlib/td/.ycm_extra_conf.py b/protocols/Telegram/tdlib/td/.ycm_extra_conf.py
deleted file mode 100644
index dd6050fea6..0000000000
--- a/protocols/Telegram/tdlib/td/.ycm_extra_conf.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# This file is NOT licensed under the GPLv3, which is the license for the rest
-# of YouCompleteMe.
-#
-# Here's the license text for this file:
-#
-# This is free and unencumbered software released into the public domain.
-#
-# Anyone is free to copy, modify, publish, use, compile, sell, or
-# distribute this software, either in source code form or as a compiled
-# binary, for any purpose, commercial or non-commercial, and by any
-# means.
-#
-# In jurisdictions that recognize copyright laws, the author or authors
-# of this software dedicate any and all copyright interest in the
-# software to the public domain. We make this dedication for the benefit
-# of the public at large and to the detriment of our heirs and
-# successors. We intend this dedication to be an overt act of
-# relinquishment in perpetuity of all present and future rights to this
-# software under copyright law.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-# OTHER DEALINGS IN THE SOFTWARE.
-#
-# For more information, please refer to <http://unlicense.org/>
-
-import os
-import ycm_core
-
-# These are the compilation flags that will be used in case there's no
-# compilation database set (by default, one is not set).
-# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR.
-flags = [
-"-stdlib=libc++",
-"-Wall",
-"-Wextra",
-"-Wno-unused-parameter",
-"-Wno-deprecated-declarations",
-"-std=c++14",
-"-x",
-"c++",
-"-I",
-".",
-"-I", "tdutils",
-"-I", "tdutils/generate",
-"-I", "tdactor",
-"-I", "tddb",
-"-I", "tdnet",
-"-I", "tdtl",
-"-I", "td/generate",
-"-I", "td/generate/auto",
-"-I", "td",
-"-isystem",
-"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1",
-"-isystem",
-"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/7.3.0/include",
-"-isystem",
-"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include"
-]
-
-
-# Set this to the absolute path to the folder (NOT the file!) containing the
-# compile_commands.json file to use that instead of 'flags'. See here for
-# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html
-#
-# You can get CMake to generate this file for you by adding:
-# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 )
-# to your CMakeLists.txt file.
-#
-# Most projects will NOT need to set this to anything; you can just change the
-# 'flags' list of compilation flags. Notice that YCM itself uses that approach.
-compilation_database_folder = 'build'
-
-if os.path.exists( compilation_database_folder ):
- database = ycm_core.CompilationDatabase( compilation_database_folder )
-else:
- database = None
-
-SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ]
-
-def DirectoryOfThisScript():
- return os.path.dirname( os.path.abspath( __file__ ) )
-
-
-def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
- if not working_directory:
- return list( flags )
- new_flags = []
- make_next_absolute = False
- path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
- for flag in flags:
- new_flag = flag
-
- if make_next_absolute:
- make_next_absolute = False
- if not flag.startswith( '/' ):
- new_flag = os.path.join( working_directory, flag )
-
- for path_flag in path_flags:
- if flag == path_flag:
- make_next_absolute = True
- break
-
- if flag.startswith( path_flag ):
- path = flag[ len( path_flag ): ]
- new_flag = path_flag + os.path.join( working_directory, path )
- break
-
- if new_flag:
- new_flags.append( new_flag )
- return new_flags
-
-
-def IsHeaderFile( filename ):
- extension = os.path.splitext( filename )[ 1 ]
- return extension in [ '.h', '.hxx', '.hpp', '.hh' ]
-
-
-def GetCompilationInfoForFile( filename ):
- # The compilation_commands.json file generated by CMake does not have entries
- # for header files. So we do our best by asking the db for flags for a
- # corresponding source file, if any. If one exists, the flags for that file
- # should be good enough.
- if IsHeaderFile( filename ):
- basename = os.path.splitext( filename )[ 0 ]
- for extension in SOURCE_EXTENSIONS:
- replacement_file = basename + extension
- if os.path.exists( replacement_file ):
- compilation_info = database.GetCompilationInfoForFile(
- replacement_file )
- if compilation_info.compiler_flags_:
- return compilation_info
- return None
- return database.GetCompilationInfoForFile( filename )
-
-
-def FlagsForFile( filename, **kwargs ):
- if database:
- # Bear in mind that compilation_info.compiler_flags_ does NOT return a
- # python list, but a "list-like" StringVec object
- compilation_info = GetCompilationInfoForFile( filename )
- if not compilation_info:
- return None
-
- final_flags = MakeRelativePathsInFlagsAbsolute(
- compilation_info.compiler_flags_,
- compilation_info.compiler_working_dir_ )
- else:
- relative_to = DirectoryOfThisScript()
- final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )
-
- return {
- 'flags': final_flags,
- 'do_cache': True
- }
diff --git a/protocols/Telegram/tdlib/td/CHANGELOG.md b/protocols/Telegram/tdlib/td/CHANGELOG.md
index 77a14246e2..05c5269684 100644
--- a/protocols/Telegram/tdlib/td/CHANGELOG.md
+++ b/protocols/Telegram/tdlib/td/CHANGELOG.md
@@ -1,4 +1,1435 @@
-Changes in 1.2.0:
+Changes in 1.8.0 (29 Dec 2021):
+
+* Changed the type of user, basic group and supergroup identifiers from `int32` to `int53`.
+* Simplified chat list loading and removed the ability to misuse the method `getChats`:
+ - Renamed the method `getChats` to `loadChats`.
+ - Removed the parameters `offset_order` and `offset_chat_id` from the method `loadChats`. Chats are now always loaded
+ from the last known chat in the list.
+ - Changed the type of the result in the method `loadChats` to `ok`. If no chats were loaded, a 404 error is returned.
+ The order of chats in the list must be maintained using the updates `updateChatPosition`, `updateChatLastMessage`,
+ and `updateChatDraftMessage`.
+ - Added the convenience method `getChats`, which returns the requested number of chats from the beginning of a chat
+ list and can be used if the list of chats doesn't need to be maintained in a consistent state.
+* Added the ability to hook TDLib log messages:
+ - Added the function `td_set_log_message_callback` to JSON interface.
+ - Added the function `set_log_message_callback` to the class `ClientManager`.
+ - Added the function `SetLogMessageCallback` to the UWP native wrapper through C++/CX.
+ - Deprecated the function `td_set_log_fatal_error_callback` in JSON interface in favor of
+ the function `td_set_log_message_callback`.
+ - Deprecated the function `Log::set_fatal_error_callback` in favor of
+ the function `ClientManager::set_log_message_callback`.
+* Added support for sending messages on behalf of public channels owned by the user:
+ - Added the field `message_sender_id` to the class `chat`, containing the identifier of the currently selected
+ message sender.
+ - Added the update `updateChatMessageSender`.
+ - Added the method `getChatAvailableMessageSenders`, returning the list of available message senders for the chat.
+ - Added the method `setChatMessageSender`, changing the currently selected message sender.
+ - Added the field `need_another_sender` to the class `messageSendingStateFailed`. If it is true, an alert needs
+ to be shown to the user that the message will be re-sent on behalf of another sender.
+ - Replaced the method `deleteChatMessagesFromUser` with the method `deleteChatMessagesBySender`, expecting
+ a `MessageSender` instead of a user identifier.
+ - Replaced the update `updateUserChatAction` with the update `updateChatAction`, containing a `MessageSender`
+ instead of a user identifier as a source of the chat action.
+* Added the ability to ban supergroups and channels in other supergroups and channels:
+ - Replaced the fields `user_id` with the fields `member_id` in the classes `chatMember` and
+ `chatEventMemberRestricted`.
+ - Replaced the parameters `user_id` with the parameters `member_id` in the methods `setChatMemberStatus` and
+ `getChatMember`.
+* Improved support for animated emoji:
+ - Added the class `animatedEmoji`, containing information about an animated emoji.
+ - Added the class `messageAnimatedEmoji` to the types of message content.
+ - Added the method `clickAnimatedEmojiMessage` to be called when an animated emoji is clicked.
+ - Added the update `updateAnimatedEmojiMessageClicked`, received when a big animated sticker must be played.
+ - Added the class `chatActionWatchingAnimations` to the types of chat action.
+ - Added the method `getAnimatedEmoji`, returning an animated emoji corresponding to a given emoji.
+ - Added the writable option "disable_animated_emoji".
+ - Removed the option "animated_emoji_sticker_set_name".
+* Added support for automatic message deletion in all chat types:
+ - Added the field `message_ttl` to the class `chat`.
+ - Added the update `updateChatMessageTtl`.
+ - Added the method `setChatMessageTtl`.
+ - Added the class `chatEventMessageTtlChanged`, representing change of the field `message_ttl` in the chat event log.
+ - Removed the field `ttl` from the class `secretChat` in favor of the field `message_ttl` in the class `chat`.
+ - Removed the method `sendChatSetTtlMessage` in favor of the method `setChatMessageTtl`.
+* Improved names of the fields of the type `MessageSender`:
+ - Renamed the fields `sender` to `sender_id` in the classes `message` and `notificationTypeNewPushMessage`.
+ - Renamed the parameter `sender` to `sender_id` in the methods `searchChatMessages`, `addLocalMessage`, and
+ `toggleMessageSenderIsBlocked`.
+ - Renamed the field `recent_repliers` to `recent_replier_ids` in the class `messageReplyInfo`.
+ - Renamed the field `traveler` to `traveler_id` in the class `messageProximityAlertTriggered`.
+ - Renamed the field `watcher` to `watcher_id` in the class `messageProximityAlertTriggered`.
+* The field `formatted_phone_number` in the class `phoneNumberInfo` now contains the character '-' at the places of
+ expected digits.
+* Added the synchronous method `getPhoneNumberInfoSync` that can be used instead of the method `getPhoneNumberInfo`
+ to synchronously receive information about a phone number by its prefix.
+* Replaced the field `user_id` in the class `chatEvent` with the field `member_id` of the type `MessageSender`.
+* Improved support for bot payments:
+ - Allowed sending invoices as results of inline queries by allowing bots to use the class `inputMessageInvoice` as
+ the value of the field `input_message_content` in the classes `inputInlineQueryResultAnimation`,
+ `inputInlineQueryResultArticle`, `inputInlineQueryResultAudio`, `inputInlineQueryResultContact`,
+ `inputInlineQueryResultDocument`, `inputInlineQueryResultLocation`, `inputInlineQueryResultPhoto`,
+ `inputInlineQueryResultSticker`, `inputInlineQueryResultVenue`, `inputInlineQueryResultVideo`, and
+ `inputInlineQueryResultVoiceNote`.
+ - Allowed sending invoice messages to basic group, supergroup and channel chats.
+ - Allowed bots to send forwardable invoices by specifying an empty field `start_parameter` in
+ the class `inputMessageInvoice`.
+ - Added the field `invoice_chat_id` to the class `messagePaymentSuccessful`.
+ - Added the field `id` to the class `paymentForm`, containing a unique payment form identifier.
+ - Added the parameter `payment_form_id` to the method `sendPaymentForm`.
+ - Added the field `seller_bot_user_id` to the class `paymentForm`, containing the user identifier of the seller bot.
+ - Added the field `payments_provider_user_id` to the class `paymentForm`, containing the user identifier of
+ the payment provider bot.
+ - Added the fields `title`, `description`, `photo`, and `seller_bot_user_id` to the class `paymentReceipt`.
+ - Added the fields `max_tip_amount` and `suggested_tip_amounts` to the class `invoice`.
+ - Added the parameter `tip_amount` to the method `sendPaymentForm`.
+ - Added the field `tip_amount` to the class `paymentReceipt`.
+ - Renamed the class `inputCredentialsAndroidPay` to `inputCredentialsGooglePay`.
+ - Added the class `paymentFormTheme`, containing the desired colors for a payment form.
+ - Added the parameter `theme` to the method `getPaymentForm`.
+ - Removed the field `invoice_message_id` from the class `messagePaymentSuccessfulBot`.
+* Added the method `deleteChat`, which can be used to completely delete a chat along with all messages.
+* Removed the method `deleteSupergroup` in favor of the method `deleteChat`.
+* Changed the type of the result in the method `getProxyLink` to the class `httpUrl` instead of the class `text`.
+* Removed support for secret chat layers before 73.
+* Added support for sponsored messages:
+ - Added the class `sponsoredMessage`.
+ - Added the method `getChatSponsoredMessage`.
+ - Added the ability to pass identifiers of sponsored messages to `viewMessages`. The method must be called when
+ the entire text of the sponsored message is shown on the screen (excluding the button).
+* Added support for video chats:
+ - Added the class `groupCall`, representing a group call.
+ - Added the method `getGroupCall` for fetching information about a group call.
+ - Added the update `updateGroupCall`.
+ - Added the class `videoChat`, representing a video chat, i.e. group call bound to a chat.
+ - Added the field `video_chat` to the class `chat`.
+ - Added the update `updateChatVideoChat`.
+ - Added the classes `messageVideoChatScheduled`, `messageVideoChatStarted`, and `messageVideoChatEnded` to
+ the types of message content.
+ - Added the class `messageInviteVideoChatParticipants` to the types of message content.
+ - Added the field `can_manage_video_chats` to the class `chatMemberStatusAdministrator`.
+ - Added the class `groupCallId`.
+ - Added the method `createVideoChat` for video chat creation.
+ - Added the method `startScheduledGroupCall`.
+ - Added the method `toggleGroupCallEnabledStartNotification`.
+ - Added the method `joinGroupCall`.
+ - Added the method `leaveGroupCall`.
+ - Added the method `endGroupCall`.
+ - Added the method `toggleGroupCallIsMyVideoEnabled`.
+ - Added the method `toggleGroupCallIsMyVideoPaused`.
+ - Added the methods `startGroupCallScreenSharing`, `toggleGroupCallScreenSharingIsPaused`,
+ `endGroupCallScreenSharing` for managing screen sharing during group calls.
+ - Added the method `setGroupCallTitle`.
+ - Added the method `toggleGroupCallMuteNewParticipants`.
+ - Added the classes `groupCallVideoSourceGroup` and `groupCallParticipantVideoInfo`, describing available
+ video streams.
+ - Added the class `groupCallParticipant`.
+ - Added the update `updateGroupCallParticipant`.
+ - Added the method `loadGroupCallParticipants`.
+ - Added the method `toggleGroupCallParticipantIsHandRaised`.
+ - Added the method `setGroupCallParticipantIsSpeaking`.
+ - Added the methods `toggleGroupCallParticipantIsMuted` and `setGroupCallParticipantVolumeLevel` for managing
+ the volume level of group call participants.
+ - Added the method `inviteGroupCallParticipants`.
+ - Added the method `getGroupCallInviteLink` and `revokeGroupCallInviteLink` for managing group call invite links.
+ - Added the methods `startGroupCallRecording` and `endGroupCallRecording` for managing group call recordings.
+ - Added the method `getVideoChatAvailableParticipants`, returning the list of participants, on whose behalf
+ a video chat in the chat can be joined.
+ - Added the method `setVideoChatDefaultParticipant` for changing the default participant on whose behalf a video chat
+ will be joined.
+ - Added the class `GroupCallVideoQuality` and the method `getGroupCallStreamSegment` for downloading segments of
+ live streams.
+ - Added the class `groupCallRecentSpeaker`, representing a group call participant that was recently speaking.
+ - Added the field `video_chat_changes` to the class `chatEventLogFilters`.
+ - Added the class `chatEventVideoChatCreated`, representing a video chat being created in the chat event log.
+ - Added the class `chatEventVideoChatEnded`, representing a video chat being ended in the chat event log.
+ - Added the class `chatEventVideoChatMuteNewParticipantsToggled`, representing changes of
+ the setting `mute_new_participants` of a video chat in the chat event log.
+ - Added the class `chatEventVideoChatParticipantIsMutedToggled`, representing a video chat participant being muted or
+ unmuted in the chat event log.
+ - Added the class `chatEventVideoChatParticipantVolumeLevelChanged`, representing a video chat participant's
+ volume level being changed in the chat event log.
+* Added "; pass null" documentation for all TDLib method parameters, for which null is an expected value.
+* Added support for link processing:
+ - Added the method `getInternalLinkType`, which can parse internal links and return the exact link type and actions
+ to be done when the link is clicked.
+ - Added the classes `internalLinkTypeActiveSessions`, `internalLinkTypeAuthenticationCode`,
+ `internalLinkTypeBackground`, `internalLinkTypeBotStart`, `internalLinkTypeBotStartInGroup`,
+ `internalLinkTypeChangePhoneNumber`, `internalLinkTypeChatInvite`, `internalLinkTypeFilterSettings`,
+ `internalLinkTypeGame`, `internalLinkTypeLanguagePack`, `internalLinkTypeMessage`, `internalLinkTypeMessageDraft`,
+ `internalLinkTypePassportDataRequest`, `internalLinkTypePhoneNumberConfirmation`, `internalLinkTypeProxy`,
+ `internalLinkTypePublicChat`, `internalLinkTypeQrCodeAuthentication`, `internalLinkTypeSettings`,
+ `internalLinkTypeStickerSet`, `internalLinkTypeTheme`, `internalLinkTypeThemeSettings`,
+ `internalLinkTypeUnknownDeepLink`, `internalLinkTypeUnsupportedProxy`, and `internalLinkTypeVideoChat` to represent
+ different types of internal links.
+ - Added the method `getExternalLinkInfo`, which needs to be called if the clicked link wasn't recognized as
+ an internal link.
+ - Added the method `getExternalLink`, which needs to be called after the method `getExternalLinkInfo` if the user
+ confirms automatic authorization on the website.
+* Added support for expiring chat invite links:
+ - Added the field `is_primary` to the class `chatInviteLink`. The primary invite link can't have a name,
+ expiration date, or usage limit. There is exactly one primary invite link for each administrator with
+ the can_invite_users right at any given time.
+ - Added the field `name` to the class `chatInviteLink`.
+ - Added the field `creator_user_id` to the class `chatInviteLink`.
+ - Added the field `date` to the class `chatInviteLink`, containing the link creation date.
+ - Added the field `edit_date` to the class `chatInviteLink`, containing the date the link was last edited.
+ - Added the fields `expiration_date` and `member_limit` to the class `chatInviteLink`, limiting link usage.
+ - Added the field `member_count` to the class `chatInviteLink`.
+ - Added the field `is_revoked` to the class `chatInviteLink`.
+ - Changed the type of the fields `invite_link` in the classes `basicGroupFullInfo` and `supergroupFullInfo` to
+ `chatInviteLink`.
+ - Added the field `description` to the class `chatInviteLinkInfo`, containing the description of the chat.
+ - Replaced the method `generateChatInviteLink` with the method `replacePrimaryChatInviteLink`.
+ - Added the method `createChatInviteLink` for creating new invite links.
+ - Added the method `editChatInviteLink` for editing non-primary invite links.
+ - Added the method `revokeChatInviteLink`.
+ - Added the method `deleteRevokedChatInviteLink`.
+ - Added the method `deleteAllRevokedChatInviteLink`.
+ - Added the method `getChatInviteLink`.
+ - Added the class `chatInviteLinks`, containing a list of chat invite links.
+ - Added the method `getChatInviteLinks`.
+ - Added the classes `chatInviteLinkCount` and `chatInviteLinkCounts`.
+ - Added the method `getChatInviteLinkCounts`, returning the number of invite links created by chat administrators.
+ - Added the classes `chatInviteLinkMember` and `chatInviteLinkMembers`.
+ - Added the method `getChatInviteLinkMembers`.
+ - Added the field `invite_link_changes` to the class `chatEventLogFilters`.
+ - Added the class `chatEventMemberJoinedByInviteLink`, representing a user joining the chat via invite link in
+ the chat event log.
+ - Added the class `chatEventInviteLinkEdited` to the types of chat event.
+ - Added the class `chatEventInviteLinkRevoked` to the types of chat event.
+ - Added the class `chatEventInviteLinkDeleted` to the types of chat event.
+ - Added the class `chatActionBarInviteMembers` to the types of chat action bar.
+* Added support for chat invite links that create join requests:
+ - Added the class `messageChatJoinByRequest` to the types of message content.
+ - Added the class `pushMessageContentChatJoinByRequest` to the types of push message content.
+ - Added the field `pending_join_requests` to the class `chat`.
+ - Added the class `chatJoinRequestsInfo`, containing basic information about pending join requests for the chat.
+ - Added the update `updateChatPendingJoinRequests`.
+ - Added the fields `creates_join_request` to the classes `chatInviteLink` and `chatInviteLinkInfo`.
+ - Added the field `pending_join_request_count` to the class `chatInviteLink`.
+ - Added the class `chatJoinRequest`, describing a user that sent a join request.
+ - Added the class `chatJoinRequests`, containing a list of requests to join the chat.
+ - Added the method `getChatJoinRequests`.
+ - Added the method `processChatJoinRequest` for processing a request to join the chat.
+ - Added the method `processChatJoinRequests` for processing all requests to join the chat.
+ - Added the class `chatActionBarJoinRequest` to the types of chat action bar.
+ - Added the class `chatEventMemberJoinedByRequest`, representing a user approved to join the chat after
+ a join request in the chat event log.
+ - Added the update `updateNewChatJoinRequest` for bots.
+* Added the ability to see viewers of outgoing messages in small group chats:
+ - Added the method `getMessageViewers`.
+ - Added the field `can_get_viewers` to the class `message`.
+* Added the parameter `only_preview` to the method `forwardMessages`, which can be used to receive a preview of
+ forwarded messages.
+* Added the method `getRecentlyOpenedChats`, returning the list of recently opened chats.
+* Increased number of recently found chats that are stored to 50.
+* Added the ability to get information about chat messages, which are split by days:
+ - Added the class `messageCalendarDay`, representing found messages, sent on a specific day.
+ - Added the class `messageCalendar`, representing found messages, split by days.
+ - Added the writable option "utc_time_offset", which contains a UTC time offset used for splitting messages by days.
+ The option is reset automatically on each TDLib instance launch, so it needs to be set manually only if
+ the time offset is changed during execution.
+ - Added the method `getChatMessageCalendar`, returning information about chat messages, which are split by days.
+* Added the ability to get messages of the specified type in sparse positions for hyper-speed scrolling implementation:
+ - Added the class `messagePosition`, containing an identifier and send date of a message in
+ a specific chat history position.
+ - Added the class `messagePositions`, containing a list of message positions.
+ - Added the method `getChatSparseMessagePositions`.
+* Added the field `can_manage_chat` to the class `chatMemberStatusAdministrator` to allow promoting chat administrators
+ without additional rights.
+* Improved support for bot commands:
+ - Bot command entities in chats without bots are now automatically hidden.
+ - Added the class `botCommands`, representing a list of bot commands.
+ - Added the field `commands` to the class `userFullInfo`, containing the list of commands to be used in
+ the private chat with the bot.
+ - Added the fields `bot_commands` to the classes `basicGroupFullInfo` and `supergroupFullInfo`.
+ - Removed the class `botInfo`.
+ - Removed the fields `bot_info` from the classes `userFullInfo` and `chatMember`.
+ - Added the class `BotCommandScope`.
+ - Added the methods `setCommands`, `deleteCommands`, `getCommands` for bots.
+* Added the read-only options "suggested_video_note_length", "suggested_video_note_video_bitrate", and
+ "suggested_video_note_audio_bitrate", containing suggested video note encoding parameters.
+* Added the read-only option "channel_bot_user_id", containing the identifier of the bot which is shown in
+ outdated clients as the sender of messages sent on behalf of channels.
+* Added the ability to fetch the actual value of the option "is_location_visible" using the method `getOption` in case
+ the value of the option was changed from another device.
+* Added the field `minithumbnail` to the class `profilePhoto`, representing a profile photo minithumbnail.
+* Added the field `minithumbnail` to the class `chatPhotoInfo`, representing a chat photo minithumbnail.
+* Added support for sticker outlines:
+ - Added the field `outline` to the class `sticker`.
+ - Added the fields `thumbnail_outline` to the classes `stickerSet` and `stickerSetInfo`.
+ - Added the class `closedVectorPath`, representing a closed vector path.
+ - Added the class `VectorPathCommand`, representing one edge of a closed vector path.
+ - Added the class `point`, representing a point on a Cartesian plane.
+* Added support for chats and messages with protected content:
+ - Added the field `has_protected_content` to the class `chat`.
+ - Added the update `updateChatHasProtectedContent`.
+ - Added the method `toggleChatHasProtectedContent`.
+ - Added the class `chatEventHasProtectedContentToggled`, representing a change of the setting `has_protected_content`
+ in the chat event log.
+ - Added the field `can_be_saved` to the class `message`.
+* Added support for broadcast groups, i.e. supergroups without a limit on the number of members in which
+ only administrators can send messages:
+ - Added the field `is_broadcast_group` to the class `supergroup`.
+ - Added the method `toggleSupergroupIsBroadcastGroup`. Conversion of a supergroup to a broadcast group
+ can't be undone.
+ - Added the class `suggestedActionConvertToBroadcastGroup`, representing a suggestion to convert a supergroup to
+ a broadcast group.
+* Improved chat reports:
+ - Added the parameter `text` to the method `reportChat`, allowing to add additional details to all
+ chat reporting reasons.
+ - Removed the field `text` from the class `chatReportReasonCustom`.
+ - Added the method `reportChatPhoto` for reporting chat photos.
+* Added support for users and chats reported as fake:
+ - Added the field `is_fake` to the class `user`.
+ - Added the field `is_fake` to the class `supergroup`.
+ - Added the class `chatReportReasonFake` to the types of chat reporting reasons.
+* Added the class `inlineKeyboardButtonTypeUser` to the types of inline keyboard buttons.
+* Added the field `input_field_placeholder` to the classes `replyMarkupForceReply` and `replyMarkupShowKeyboard`.
+* Added support for media timestamp entities in messages:
+ - Added the class `textEntityTypeMediaTimestamp` to the types of text entities.
+ - Added the field `has_timestamped_media` to the class `message`, describing whether media timestamp entities refer
+ to the message itself or to the replied message.
+ - Added the parameter `media_timestamp` to the method `getMessageLink` to support creating message links with
+ a given media timestamp.
+ - Added the field `can_get_media_timestamp_links` to the class `message`.
+ - Added the field `media_timestamp` to the class `messageLinkInfo` for handling of message links with
+ a specified media timestamp.
+* Added the ability to change properties of active sessions:
+ - Added the field `can_accept_secret_chats` to the class `session`.
+ - Added the method `toggleSessionCanAcceptSecretChats`.
+ - Added the field `can_accept_calls` to the class `session`.
+ - Added the method `toggleSessionCanAcceptCalls`.
+ - Added the field `inactive_session_ttl_days` to the class `sessions`.
+ - Added the method `setInactiveSessionTtl`.
+* Added new ways for phone number verification:
+ - Added the class `authenticationCodeTypeMissedCall`, describing an authentication code delivered by
+ a canceled phone call to the given number. The last digits of the phone number that calls are the code that
+ must be entered manually by the user.
+ - Added the field `allow_missed_call` to the class `phoneNumberAuthenticationSettings`.
+ - Added the read-only option "authentication_token", which can be received when logging out and contains
+ an authentication token to be used on subsequent authorizations.
+ - Added the field `authentication_tokens` to the class `phoneNumberAuthenticationSettings`.
+* Added support for resetting the password from an active session:
+ - Added the class `ResetPasswordResult` and the method `resetPassword`.
+ - Added the method `cancelPasswordReset`, which can be used to cancel a pending password reset.
+ - Added the field `pending_reset_date` to the class `passwordState`.
+* Added the ability to set a new 2-step verification password after recovering a lost password using
+ a recovery email address:
+ - Added the parameters `new_password` and `new_hint` to the methods `recoverAuthenticationPassword` and
+ `recoverPassword`.
+ - Added the method `checkAuthenticationPasswordRecoveryCode`.
+ - Added the method `checkPasswordRecoveryCode`.
+* Added the class `chatActionChoosingSticker` to the types of chat action.
+* Added the class `backgroundFillFreeformGradient` to the types of background fill.
+* Added the field `is_inverted` to the class `backgroundTypePattern` for inverted patterns for dark themes.
+* Added support for chat themes:
+ - Added the field `theme_name` to the class `chat`.
+ - Added the method `setChatTheme`.
+ - Added the update `updateChatTheme`, received when a theme was changed in a chat.
+ - Added the class `messageChatSetTheme` to the types of message content.
+ - Added the class `pushMessageContentChatSetTheme` to the types of push message content.
+ - Added the class `themeSettings`, representing basic theme settings.
+ - Added the class `chatTheme`, representing a chat theme.
+ - Added the update `updateChatThemes`, received when the list of available chat themes changes.
+* Added the ability for regular users to create sticker sets:
+ - Allowed to use the methods `uploadStickerFile` and `createNewStickerSet` as regular users.
+ - Replaced the parameter `png_sticker` in the method `uploadStickerFile` with the parameter `sticker` of
+ the type `InputSticker`.
+ - Added the parameter `source` to the method `createNewStickerSet`.
+ - Added the method `getSuggestedStickerSetName`.
+ - Added the class `CheckStickerSetNameResult` and the method `checkStickerSetName` for checking a sticker set name
+ before creating a sticker set.
+* Added support for importing chat history from an external source:
+ - Added the method `importMessages`.
+ - Added the method `getMessageImportConfirmationText`.
+ - Added the class `MessageFileType` and the method `getMessageFileType`, which can be used to check whether
+ the format of a file with exported message history is supported.
+ - Added the class `messageForwardOriginMessageImport` to the types of forwarded message origins for
+ imported messages.
+ - Added the parameter `for_import` to the method `createNewSupergroupChat`, which needs to be set to true whenever
+ the chat is created for a subsequent message history import.
+* Added new types of suggested actions:
+ - Added the class `suggestedActionSetPassword`, suggesting the user to set a 2-step verification password to be able
+ to log in again before the specified number of days pass.
+ - Added the class `suggestedActionCheckPassword`, suggesting the user to check whether they still remember
+ their 2-step verification password.
+ - Added the class `suggestedActionViewChecksHint`, suggesting the user to see a hint about the meaning of
+ one and two check marks on sent messages.
+* Added the method `getSuggestedFileName`, which returns a suggested name for saving a file in a given directory.
+* Added the method `deleteAllCallMessages`.
+* Added the method `deleteChatMessagesByDate`, which can be used to delete all messages between the specified dates in
+ a chat.
+* Added the field `unread_message_count` to the class `messageThreadInfo`.
+* Added the field `has_private_forwards` to the class `userFullInfo`.
+* Added the field `description` to the class `userFullInfo`, containing description of the bot.
+* Added the field `emoji` to the class `inputMessageSticker`, allowing to specify the emoji that was used to choose
+ the sent sticker.
+* Added the method `banChatMember` that can be used to ban chat members instead of the method `setChatMemberStatus` and
+ allows to revoke messages from the banned user in basic groups.
+* Allowed to use the method `setChatMemberStatus` for adding chat members.
+* Removed the parameter `user_id` from the method `reportSupergroupSpam`. Messages from different senders can now
+ be reported simultaneously.
+* Added the field `feedback_link` to the class `webPageInstantView`.
+* Added the method `getApplicationDownloadLink`, returning the link for downloading official Telegram applications.
+* Removed unusable search message filters `searchMessagesFilterCall` and `searchMessagesFilterMissedCall`.
+* Removed the method `getChatStatisticsUrl`.
+* Removed the method `getInviteText` in favor of the method `getApplicationDownloadLink`.
+* Added the field `chat_type` to the update `updateNewInlineQuery` for bots.
+* Added the update `updateChatMember` for bots.
+* Improved the appearance of the [TDLib build instructions generator](https://tdlib.github.io/td/build.html).
+* Added support for the illumos operating system.
+* Added support for network access on real watchOS devices.
+* Added support for OpenSSL 3.0.
+* Improved the iOS/watchOS/tvOS build example to generate a universal XCFramework.
+* Added support for ARM64 simulators in the iOS/watchOS/tvOS build example.
+* Added the option `-release_only` to the build script for Universal Windows Platform, allowing to build
+ TDLib SDK for Universal Windows Platform in release-only mode.
+* Rewritten the native .NET binding using the new `ClientManager` interface:
+ - Replaced the method `Client.send` with a static method that must be called exactly once in a dedicated thread.
+ The callbacks `ClientResultHandler` will be called in this thread for all clients.
+ - Removed the function `Client.Dispose()` from the C++/CLI native binding. The objects of the type `Client`
+ don't need to be explicitly disposed anymore.
+* Rewritten the C binding using the new `ClientManager` interface:
+ - Renamed the fields `id` in the structs `TdRequest` and `TdResponse` to `request_id`.
+ - Added the field `client_id` to the struct `TdResponse`.
+ - Replaced the method `TdCClientCreate` with the method `TdCClientCreateId`.
+ - Replaced the parameter `instance` with the parameter `client_id` in the function `TdCClientSend`.
+ - Added the methods `TdCClientReceive` and `TdCClientExecute`.
+ - Removed the methods `TdCClientSendCommandSync`, `TdCClientDestroy`, and `TdCClientSetVerbosity`.
+
+-----------------------------------------------------------------------------------------------------------------------
+
+Changes in 1.7.0 (28 Nov 2020):
+
+* Added a new simplified JSON interface in which updates and responses to requests from all TDLib instances
+ are received in the same thread:
+ - The TDLib instance is identified by the unique `client_id` identifier, which is returned by the method
+ `td_create_client_id`.
+ - Use the method `td_send` to send a request to a specified client. The TDLib instance is created on the first
+ request sent to it.
+ - Use the method `td_receive` to receive updates and request responses from TDLib. The response will contain
+ the identifier of the client from which the event was received in the field "@client_id".
+ - Use the method `td_execute` to synchronously execute suitable TDLib methods.
+* Added support for adding chats to more than one chat list:
+ - Added the class `chatPosition`, describing the position of the chat within a chat list.
+ - Replaced the fields `chat_list`, `order`, `is_sponsored` and `is_pinned` in the class `chat` with
+ the field `positions`, containing a list of the chat positions in various chat list.
+ - Replaced the field `order` with the field `positions` in the updates `updateChatLastMessage` and
+ `updateChatDraftMessage`.
+ - Added the update `updateChatPosition`.
+ - Removed the superfluous updates `updateChatChatList`, `updateChatIsSponsored`, `updateChatOrder` and
+ `updateChatIsPinned`.
+ - Added the parameter `chat_list` to the method `toggleChatIsPinned`.
+ - Added the class `chatLists`, containing a list of chat lists.
+ - Added the method `getChatListsToAddChat`, returning all chat lists to which a chat can be added.
+ - Added the method `addChatToList`, which can be used to add a chat to a chat list.
+ - Remove the method `setChatChatList`.
+* Added support for chat filters:
+ - Added the new chat list type `chatListFilter`.
+ - Added the classes `chatFilterInfo` and `chatFilter`, describing a filter of user chats.
+ - Added the update `updateChatFilters`, which is sent when the list of chat filters is changed.
+ - Added the methods `createChatFilter`, `editChatFilter` and `deleteChatFilter` for managing chat filters.
+ - Added the method `reorderChatFilters` for changing the order of chat filters.
+ - Added the method `getChatFilter`, returning full information about a chat filter.
+ - Added the synchronous method `getChatFilterDefaultIconName`.
+ - Added the classes `recommendedChatFilter` and `recommendedChatFilters`.
+ - Added the method `getRecommendedChatFilters`, returning a list of recommended chat filters.
+* Added support for messages sent on behalf of chats instead of users:
+ - Added the class `MessageSender`, representing a user or a chat which sent a message.
+ - Added the class `MessageSenders`, representing a list of message senders.
+ - Replaced the field `sender_user_id` with the field `sender` of the type `MessageSender` in the classes `message`
+ and `notificationTypeNewPushMessage`.
+ - Added the class `messageForwardOriginChat`, which describe a chat as the original sender of a message.
+ - Added the ability to search messages sent by a chat by replacing the parameter `sender_user_id` with
+ the parameter `sender` of the type `MessageSender` in the method `searchChatMessages`.
+ - Added the ability to specify a chat as a local message sender by replacing the parameter `sender_user_id` with
+ the parameter `sender` of the type `MessageSender` in the method `addLocalMessage`.
+* Added support for video calls:
+ - Added the class `callServer`, describing a server for relaying call data.
+ - Added the classes `callServerTypeTelegramReflector` and `callServerTypeWebrtc`, representing different types of
+ supported call servers.
+ - Replaced the field `connections` with the field `servers` in the class `callStateReady`.
+ - Removed the class `callConnection`.
+ - Added the update `updateNewCallSignalingData`.
+ - Added the method `sendCallSignalingData`.
+ - Added the field `supports_video_calls` to the class `userFullInfo`.
+ - Added the field `is_video` to the class `messageCall`.
+ - Added the field `is_video` to the class `call`.
+ - Added the parameter `is_video` to the method `createCall`.
+ - Added the parameter `is_video` to the method `discardCall`.
+ - Added two new types of call problems `callProblemDistortedVideo` and `callProblemPixelatedVideo`.
+ - Added the field `library_versions` to the class `callProtocol`, which must be used to specify all supported
+ call library versions.
+* Added support for multiple pinned messages and the ability to pin messages in private chats:
+ - Added the ability to pin messages in all private chats.
+ - Added the ability to pin mutiple messages in all chats.
+ - Added the field `is_pinned` to the class `message`.
+ - Added the update `updateMessageIsPinned`.
+ - Added the parameter `only_for_self` to the method `pinChatMessage`, allowing to pin messages in private chats for
+ one side only.
+ - Added the ability to find pinned messages in a chat using the filter `searchMessagesFilterPinned`.
+ - Added the parameter `message_id` to the method `unpinChatMessage`.
+ - Added the field `message` to the class `chatEventMessageUnpinned`.
+ - Added the method `unpinAllChatMessages`, which can be used to simultaneously unpin all pinned messages in a chat.
+ - Documented that notifications about new pinned messages are always silent in channels and private chats.
+ - The method `getChatPinnedMessage` now returns the newest pinned message in the chat.
+ - Removed the field `pinned_message_id` from the class `chat`.
+ - Removed the update `updateChatPinnedMessage`.
+* Improved thumbnail representation and added support for animated MPEG4 thumbnails:
+ - Added the class `ThumbnailFormat`, representing the various supported thumbnail formats.
+ - Added the class `thumbnail`, containing information about a thumbnail.
+ - Changed the type of all thumbnail fields from `photoSize` to `thumbnail`.
+ - Added support for thumbnails in the format `thumbnailFormatMpeg4` for some animations and videos.
+ - Replaced the classes `inputInlineQueryResultAnimatedGif` and `inputInlineQueryResultAnimatedMpeg4` with
+ the generic class `inputInlineQueryResultAnimation`.
+ - Added support for animated thumbnails in the class `inputInlineQueryResultAnimation`.
+ - The class `photoSize` is now only used for JPEG images.
+* Improved support for user profile photos and chat photos:
+ - Added the field `photo` to the class `userFullInfo`, containing full information about the user photo.
+ - Added the field `photo` to the class `basicGroupFullInfo`, containing full information about the group photo.
+ - Added the field `photo` to the class `supergroupFullInfo`, containing full information about the group photo.
+ - Renamed the class `chatPhoto` to `chatPhotoInfo`.
+ - Added the field `has_animation` to the classes `profilePhoto` and `chatPhotoInfo`, which is set to true for
+ animated chat photos.
+ - Added the classes `chatPhoto` and `chatPhotos`.
+ - Added minithumbnail support via the field `minithumbnail` in the class `chatPhoto`.
+ - Added the class `animatedChatPhoto`.
+ - Added animated chat photo support via the field `animation` in the class `chatPhoto`.
+ - Removed the classes `userProfilePhoto` and `userProfilePhotos`.
+ - Changed the type of the field `photo` in the class `messageChatChangePhoto` to `chatPhoto`.
+ - Changed the type of the fields `old_photo` and `new_photo` in the class `chatEventPhotoChanged` to `chatPhoto`.
+ - Changed the return type of the method `getUserProfilePhotos` to `chatPhotos`.
+ - Added the class `InputChatPhoto`, representing a chat or a profile photo to set.
+ - Changed the type of the parameter `photo` in the methods `setProfilePhoto` and `setChatPhoto` to
+ the `InputChatPhoto`.
+ - Added the ability to explicitly re-use previously set profile photos using the class `inputChatPhotoPrevious`.
+ - Added the ability to set animated chat photos using the class `inputChatPhotoAnimated`.
+* Added support for message threads in supergroups and channel comments:
+ - Added the field `message_thread_id` to the class `message`.
+ - Added the class `messageThreadInfo`, containing information about a message thread.
+ - Added the class `messageReplyInfo`, containing information about replies to a message.
+ - Added the field `reply_info` to the class `messageInteractionInfo`, containing information about message replies.
+ - Added the field `can_get_message_thread` to the class `message`.
+ - Added the method `getMessageThread`, returning information about the message thread to which a message belongs.
+ - Added the method `getMessageThreadHistory`, returning messages belonging to a message thread.
+ - Added the parameter `message_thread_id` to the methods `sendMessage`, `sendMessageAlbum` and
+ `sendInlineQueryResultMessage` for sending messages within a thread.
+ - Added the parameter `message_thread_id` to the method `searchChatMessages` to search messages within a thread.
+ - Added the parameter `message_thread_id` to the method `viewMessages`.
+ - Added the parameter `message_thread_id` to the method `setChatDraftMessage`.
+ - Added the parameter `message_thread_id` to the method `sendChatAction` to send chat actions to a thread.
+ - Added the field `message_thread_id` to the update `updateUserChatAction`.
+* Improved support for message albums:
+ - Added support for sending and receiving messages of the types `messageAudio` and `messageDocument` as albums.
+ - Added automatic grouping into audio or document albums in the method `forwardMessages` if all forwarded or
+ copied messages are of the same type.
+ - Removed the parameter `as_album` from the method `forwardMessages`. Forwarded message albums are now determined
+ automatically.
+* Simplified usage of methods generating an HTTP link to a message:
+ - Added the class `messageLink`, representing an HTTP link to a message.
+ - Combined the methods `getPublicMessageLink` and `getMessageLink` into the method `getMessageLink`, which
+ now returns a public link to the message if possible and a private link otherwise. The combined method is
+ an offline method now.
+ - Added the parameter `for_comment` to the method `getMessageLink`, which allows to get a message link to the message
+ that opens it in a thread.
+ - Removed the class `publicMessageLink`.
+ - Added the field `for_comment` to the class `messageLinkInfo`.
+ - Added the separate method `getMessageEmbeddingCode`, returning an HTML code for embedding a message.
+* Added the ability to block private messages sent via the @replies bot from chats:
+ - Added the field `is_blocked` to the class `chat`.
+ - Added the update `updateChatIsBlocked`.
+ - Added the method `blockMessageSenderFromReplies`.
+ - Replaced the methods `blockUser` and `unblockUser` with the method `toggleMessageSenderIsBlocked`.
+ - Replaced the method `getBlockedUsers` with the method `getBlockedMessageSenders`.
+* Added support for incoming messages which are replies to messages in different chats:
+ - Added the field `reply_in_chat_id` to the class `message`.
+ - The method `getRepliedMessage` can now return the replied message in a different chat.
+* Renamed the class `sendMessageOptions` to `messageSendOptions`.
+* Added the new `tdapi` static library, which needs to be additionally linked in when static linking is used.
+* Changed the type of the field `value` in the class `optionValueInteger` from `int32` to `int64`.
+* Changed the type of the field `description` in the class `webPage` from `string` to `formattedText`.
+* Improved Instant View support:
+ - Added the field `view_count` to the class `webPageInstantView`.
+ - Added the class `richTextAnchorLink`, containing a link to an anchor on the same page.
+ - Added the class `richTextReference`, containing a reference to a text on the same page.
+ - Removed the field `text` from the class `richTextAnchor`.
+ - Removed the field `url` which is no longer needed from the class `webPageInstantView`.
+* Allowed the update `updateServiceNotification` to be sent before authorization is completed.
+* Disallowed to pass messages in non-strictly increasing order to the method `forwardMessages`.
+* Improved sending copies of messages:
+ - Added the class `messageCopyOptions` and the field `copy_options` to the class `inputMessageForwarded`.
+ - Removed the fields `send_copy` and `remove_caption` from the class `inputMessageForwarded`.
+ - Allowed to replace captions in copied messages using the fields `replace_caption` and `new_caption` in
+ the class `messageCopyOptions`.
+ - Allowed to specify `reply_to_message_id` when sending a copy of a message.
+ - Allowed to specify `reply_markup` when sending a copy of a message.
+* Allowed passing multiple input language codes to `searchEmojis` by replacing the parameter `input_language_code` with
+ the parameter `input_language_codes`.
+* Added support for public service announcements:
+ - Added the class `ChatSource` and the field `source` to the class `chatPosition`.
+ - Added the new type of chat source `chatSourcePublicServiceAnnouncement`.
+ - Added the field `public_service_announcement_type` to the class `messageForwardInfo`.
+* Added support for previewing of private supergroups and channels by their invite link.
+ - The field `chat_id` in the class `chatInviteLinkInfo` is now non-zero for private supergroups and channels to which
+ the temporary read access is granted.
+ - Added the field `accessible_for` to the class `chatInviteLinkInfo`, containing the amount of time for which
+ read access to the chat will remain available.
+* Improved methods for message search:
+ - Replaced the field `next_from_search_id` with a string field `next_offset` in the class `foundMessages`.
+ - Added the field `total_count` to the class `foundMessages`; can be -1 if the total count of matching messages is
+ unknown.
+ - Replaced the parameter `from_search_id` with the parameter `offset` in the method `searchSecretMessages`.
+ - Added the parameter `filter` to the method `searchMessages`.
+ - Added the parameters `min_date` and `max_date` to the method `searchMessages` to search messages sent only within
+ a particular timeframe.
+* Added pkg-config file generation for all installed libraries.
+* Added automatic operating system version detection. Use an empty field `system_version` in
+ the class `tdlibParameters` for the automatic detection.
+* Increased maximum file size from 1500 MB to 2000 MB.
+* Added support for human-friendly Markdown formatting:
+ - Added the synchronous method `parseMarkdown` for human-friendly parsing of text entities.
+ - Added the synchronous method `getMarkdownText` for replacing text entities with a human-friendly
+ Markdown formatting.
+ - Added the writable option "always_parse_markdown" which enables automatic parsing of text entities in
+ all `inputMessageText` objects.
+* Added support for dice with random values in messages:
+ - Added the class `messageDice` to the types of message content; contains a dice.
+ - Added the class `DiceStickers`, containing animated stickers needed to show the dice.
+ - Added the class `inputMessageDice` to the types of new input message content; can be used to send a dice.
+ - Added the update `updateDiceEmojis`, containing information about supported dice emojis.
+* Added support for chat statistics in channels and supergroups:
+ - Added the field `can_get_statistics` to the class `supergroupFullInfo`.
+ - Added the class `ChatStatistics`, which represents a supergroup or a channel statistics.
+ - Added the method `getChatStatistics` returning detailed statistics about a chat.
+ - Added the classes `chatStatisticsMessageInteractionInfo`, `chatStatisticsAdministratorActionsInfo`,
+ `chatStatisticsMessageSenderInfo` and `chatStatisticsInviterInfo` representing various parts of chat statistics.
+ - Added the class `statisticalValue` describing recent changes of a statistical value.
+ - Added the class `StatisticalGraph` describing a statistical graph.
+ - Added the method `getStatisticalGraph`, which can be used for loading asynchronous or zoomed in statistical graphs.
+ - Added the class `dateRange` representing a date range for which statistics are available.
+ - Removed the field `can_view_statistics` from the class `supergroupFullInfo` and marked
+ the method `getChatStatisticsUrl` as disabled and not working.
+* Added support for detailed statistics about interactions with messages:
+ - Added the class `messageInteractionInfo`, containing information about message views, forwards and replies.
+ - Added the field `interaction_info` to the class `message`.
+ - Added the update `updateMessageInteractionInfo`.
+ - Added the field `can_get_statistics` to the class `message`.
+ - Added the class `messageStatistics`.
+ - Added the method `getMessageStatistics`.
+ - Added the method `getMessagePublicForwards`, returning all forwards of a message to public channels.
+ - Removed the now superfluous field `views` from the class `message`.
+ - Removed the now superfluous update `updateMessageViews`.
+* Improved support for native polls:
+ - Added the field `explanation` to the class `pollTypeQuiz`.
+ - Added the fields `close_date` and `open_period` to the class `poll`.
+ - Added the fields `close_date` and `open_period` to the class `inputMessagePoll`; for bots only.
+ - Increased maximum poll question length to 300 characters for bots.
+* Added support for anonymous administrators in supergroups:
+ - Added the field `is_anonymous` to the classes `chatMemberStatusCreator` and `chatMemberStatusAdministrator`.
+ - The field `author_signature` in the class `message` can now contain a custom title of the anonymous administrator
+ that sent the message.
+* Added support for a new type of inline keyboard buttons, requiring user password entry:
+ - Added the class `inlineKeyboardButtonTypeCallbackWithPassword`, representing a button requiring password entry from
+ a user.
+ - Added the class `callbackQueryPayloadDataWithPassword`, representing new type of callback button payload,
+ which must be used for the buttons of the type `inlineKeyboardButtonTypeCallbackWithPassword`.
+* Added support for making the location of the user public:
+ - Added the writable option "is_location_visible" to allow other users see location of the current user.
+ - Added the method `setLocation`, which should be called if `getOption("is_location_visible")` is true and location
+ changes by more than 1 kilometer.
+* Improved Notification API:
+ - Added the field `sender_name` to the class `notificationTypeNewPushMessage`.
+ - Added the writable option "disable_sent_scheduled_message_notifications" for disabling notifications about
+ outgoing scheduled messages that were sent.
+ - Added the field `is_outgoing` to the class `notificationTypeNewPushMessage` for recognizing
+ outgoing scheduled messages that were sent.
+ - Added the fields `has_audios` and `has_documents` to the class `pushMessageContentMediaAlbum`.
+* Added the field `date` to the class `draftMessage`.
+* Added the update `updateStickerSet`, which is sent after a sticker set is changed.
+* Added support for pagination in trending sticker sets:
+ - Added the parameters `offset` and `limit` to the method `getTrendingStickerSets`.
+ - Changed the field `sticker_sets` in the update `updateTrendingStickerSets` to contain only the prefix of
+ trending sticker sets.
+* Messages that failed to send can now be found using the filter `searchMessagesFilterFailedToSend`.
+* Added the ability to disable automatic server-side file type detection using the new field
+ `disable_content_type_detection` of the class `inputMessageDocument`.
+* Improved chat action bar:
+ - Added the field `can_unarchive` to the classes `chatActionBarReportSpam` and `chatActionBarReportAddBlock`,
+ which is true whenever the chat was automatically archived.
+ - Added the field `distance` to the class `chatActionBarReportAddBlock`,
+ which denotes the distance between the users.
+* Added support for actions suggested to the user by the server:
+ - Added the class `SuggestedAction`, representing possible actions suggested by the server.
+ - Added the update `updateSuggestedActions`.
+ - Added the method `hideSuggestedAction`, which can be used to dismiss a suggested action.
+* Supported attaching stickers to animations:
+ - Added the field `has_stickers` to the class `animation`.
+ - Added the field `added_sticker_file_ids` to the class `inputMessageAnimation`.
+* Added methods for phone number formatting:
+ - Added the class `countryInfo`, describing a country.
+ - Added the class `countries`, containing a list of countries.
+ - Added the method `getCountries`, returning a list of all existing countries.
+ - Added the class `phonenumberinfo` and the method `getPhoneNumberInfo`, which can be used to format a phone number
+ according to local rules.
+* Improved location support:
+ - Added the field `horizontal_accuracy` to the class `location`.
+ - Added the field `heading` to the classes `messageLocation` and `inputMessageLocation` for live locations.
+ - Added the parameter `heading` to the methods `editMessageLiveLocation` and `editInlineMessageLiveLocation`.
+* Added support for proximity alerts in live locations:
+ - Added the field `proximity_alert_radius` to the classes `messageLocation` and `inputMessageLocation`.
+ - Added the parameter `proximity_alert_radius` to the methods `editMessageLiveLocation` and
+ `editInlineMessageLiveLocation`.
+ - Added the new message content `messageProximityAlertTriggered`, received whenever a proximity alert is triggered.
+* Added `CentOS 7` and `CentOS 8` operating systems to the
+ [TDLib build instructions generator](https://tdlib.github.io/td/build.html).
+* Added the CMake configuration option TD_ENABLE_MULTI_PROCESSOR_COMPILATION, which can be used to enable parallel
+ build with MSVC.
+* Added support for sending and receiving messages in secret chats with silent notifications.
+* Added the field `progressive_sizes` to the class `photo` to allow partial progressive JPEG photo download.
+* Added the field `redirect_stderr` to the class `logStreamFile` to allow explicit control over stderr redirection to
+ the log file.
+* Added the read-only option "can_archive_and_mute_new_chats_from_unknown_users", which can be used to check, whether
+ the option "archive_and_mute_new_chats_from_unknown_users" can be changed.
+* Added the writable option "archive_and_mute_new_chats_from_unknown_users", which can be used to automatically archive
+ and mute new chats from non-contacts. The option can be set only if the option
+ "can_archive_and_mute_new_chats_from_unknown_users" is true.
+* Added the writable option "message_unload_delay", which can be used to change the minimum delay before messages are
+ unloaded from the memory.
+* Added the writable option "disable_persistent_network_statistics", which can be used to disable persistent
+ network usage statistics, significantly reducing disk usage.
+* Added the writable option "disable_time_adjustment_protection", which can be used to disable protection from
+ external time adjustment, significantly reducing disk usage.
+* Added the writable option "ignore_default_disable_notification" to allow the application to manually specify the
+ `disable_notification` option each time when sending messages instead of following the default per-chat settings.
+* Added the read-only option "telegram_service_notifications_chat_id", containing the identifier of
+ the Telegram service notifications chat.
+* Added the read-only option "replies_bot_chat_id", containing the identifier of the @replies bot.
+* Added the read-only option "group_anonymous_bot_user_id", containing the identifier of the bot which is shown as
+ the sender of anonymous group messages when viewed from an outdated client.
+* Added the new venue provider value "gplaces" for Google Places.
+* Added the parameter `return_deleted_file_statistics` to the method `optimizeStorage` to return information about
+ the files that were deleted instead of the ones that were not.
+* Added the ability to search for supergroup members to mention by their name and username:
+ - Added the new filter `supergroupMembersFilterMention` for the method `getSupergroupMembers`.
+ - Added the new filter `chatMembersFilterMention` for the method `searchChatMembers`.
+* Added support for highlighting bank card numbers:
+ - Added the new text entity `textEntityTypeBankCardNumber`.
+ - Added the classes `bankCardInfo` and `bankCardActionOpenUrl`, containing information about a bank card.
+ - Added the method `getBankCardInfo`, returning information about a bank card.
+* Improved methods for managing sticker sets by bots:
+ - Added the method `setStickerSetThumbnail`.
+ - Added the ability to create new animated sticker sets and add new stickers to them by adding
+ the class `inputStickerAnimated`.
+ - Renamed the class `inputSticker` to `inputStickerStatic`.
+ - Renamed the field `png_sticker` to `sticker` in the class `inputStickerStatic`.
+* Added the method `setCommands` for bots.
+* Added the method `getCallbackQueryMessage` for bots.
+* Added support for starting bots in private chats through `sendBotStartMessage`.
+* Added the field `total_count` to the class `chats`. The field should have a precise value for the responses of
+ the methods `getChats`, `searchChats` and `getGroupsInCommon`.
+* Added the update `updateAnimationSearchParameters`, containing information about animation search parameters.
+* Documented that `getRepliedMessage` can be used to get a pinned message, a game message, or an invoice message for
+ messages of the types `messagePinMessage`, `messageGameScore`, and `messagePaymentSuccessful` respectively.
+* Added guarantees that the field `member_count` in the class `supergroup` is known if the supergroup was received from
+ the methods `searchChatsNearby`, `getInactiveSupergroupChats`, `getSuitableDiscussionChats`, `getGroupsInCommon`, or
+ `getUserPrivacySettingRules`.
+* Updated SQLCipher to 4.4.0.
+* Updated dependencies in the prebuilt TDLib for Android:
+ - Updated SDK to SDK 30.
+ - Updated NDK to r21d, which dropped support for 32-bit ARM devices without Neon support.
+* Updated recommended `emsdk` version for `tdweb` building to the 2.0.6.
+* Removed the ability to change the update handler after client creation in native .NET binding, Java example and
+ prebuilt library for Android.
+* Removed the ability to change the default exception handler after client creation in Java example and
+ prebuilt library for Android.
+* Removed the ability to close Client using close() method in Java example and prebuilt library for Android.
+ Use the method TdApi.close() instead.
+* Changed license of source code in prebuilt library for Android to Boost Software License, Version 1.0.
+
+-----------------------------------------------------------------------------------------------------------------------
+
+Changes in 1.6.0 (31 Jan 2020):
+
+* Added support for multiple chat lists. Currently, only two chat lists Main and Archive are supported:
+ - Added the class `ChatList`, which represents a chat list and could be `chatListMain` or `chatListArchive`.
+ - Added the field `chat_list` to the class `chat`, denoting the chat list to which the chat belongs.
+ - Added the parameter `chat_list` to the methods `getChats`, `searchMessages` and `setPinnedChats`.
+ - Added the field `chat_list` to the updates `updateUnreadMessageCount` and `updateUnreadChatCount`.
+ - Added the field `total_count` to the update `updateUnreadChatCount`, containing the total number of chats in
+ the list.
+ - Added the update `updateChatChatList`, which is sent after a chat is moved to or from a chat list.
+ - Added the method `setChatChatList`, which can be used to move a chat between chat lists.
+ - Added the option `pinned_archived_chat_count_max` for the maximum number of pinned chats in the Archive chat list.
+* Added support for scheduled messages:
+ - Added the classes `messageSchedulingStateSendAtDate` and `messageSchedulingStateSendWhenOnline`,
+ representing the scheduling state of a message.
+ - Added the field `scheduling_state` to the class `message`, which allows to distinguish between scheduled and
+ ordinary messages.
+ - The update `updateNewMessage` can now contain a scheduled message and must be handled appropriately.
+ - The updates `updateMessageContent`, `updateDeleteMessages`, `updateMessageViews`, `updateMessageSendSucceeded`,
+ `updateMessageSendFailed`, and `updateMessageSendAcknowledged` can now contain identifiers of scheduled messages.
+ - Added the class `sendMessageOptions`, which contains options for sending messages,
+ including the scheduling state of the messages.
+ - Replaced the parameters `disable_notification` and `from_background` in the methods `sendMessage`,
+ `sendMessageAlbum`, `sendInlineQueryResultMessage`, and `forwardMessages` with the new field `options` of
+ the type `sendMessageOptions`.
+ - Added the method `editMessageSchedulingState`, which can be used to reschedule a message or send it immediately.
+ - Added the method `getChatScheduledMessages`, which returns all scheduled messages in a chat.
+ - Added the field `has_scheduled_messages` to the class `chat`.
+ - Added the update `updateChatHasScheduledMessages`, which is sent whenever the field `has_scheduled_messages`
+ changes in a chat.
+ - Added support for reminders in Saved Messages and notifications about other sent scheduled messages in
+ the [Notification API](https://core.telegram.org/tdlib/notification-api/).
+* Added support for adding users without a known phone number to the list of contacts:
+ - Added the method `addContact` for adding or renaming contacts without a known phone number.
+ - Added the field `need_phone_number_privacy_exception` to the class `userFullInfo`, containing the default value for
+ the second parameter of the method `addContact`.
+ - Added the fields `is_contact` and `is_mutual_contact` to the class `user`.
+ - Removed the class `LinkState` and the fields `outgoing_link` and `incoming_link` from the class `user`.
+* Improved support for the top chat action bar:
+ - Added the class `ChatActionBar`, representing all possible types of the action bar.
+ - Added the field `action_bar` to the class `chat`.
+ - Removed the legacy class `chatReportSpamState`.
+ - Removed the legacy methods `getChatReportSpamState` and `changeChatReportSpamState`.
+ - Added the update `updateChatActionBar`.
+ - Added the method `removeChatActionBar`, which allows to dismiss the action bar.
+ - Added the method `sharePhoneNumber`, allowing to share the phone number of the current user with a mutual contact.
+ - Added the new reason `chatReportReasonUnrelatedLocation` for reporting location-based groups unrelated to
+ their stated location.
+* Improved support for text entities:
+ - Added the new types of text entities `textEntityTypeUnderline` and `textEntityTypeStrikethrough`.
+ - Added support for nested entities. Entities can be nested, but must not mutually intersect with each other.
+ Pre, Code and PreCode entities can't contain other entities. Bold, Italic, Underline and Strikethrough entities can
+ contain and be contained in all other entities. All other entities can't contain each other.
+ - Added the field `version` to the method `textParseModeMarkdown`. Versions 0 and 1 correspond to Bot API Markdown
+ parse mode, version 2 to Bot API MarkdownV2 parse mode with underline, strikethrough and nested entities support.
+ - The new entity types and nested entities are supported in secret chats also if its layer is at least 101.
+* Added support for native non-anonymous, multiple answer, and quiz-style polls:
+ - Added support for quiz-style polls, which has exactly one correct answer option and can be answered only once.
+ - Added support for regular polls, which allows multiple answers.
+ - Added the classes `pollTypeRegular` and `pollTypeQuiz`, representing the possible types of a poll.
+ - Added the field `type` to the classes `poll` and `inputMessagePoll`.
+ - Added support for non-anonymous polls with visible votes by adding the field `is_anonymous` to the classes `poll`
+ and `inputMessagePoll`.
+ - Added the method `getPollVoters` returning users that voted for the specified option in a non-anonymous poll.
+ - Added the new reply markup keyboard button `keyboardButtonTypeRequestPoll`.
+ - Added the field `is_regular` to the class `pushMessageContentPoll`.
+ - Added the update `updatePollAnswer` for bots only.
+ - Added the field `is_closed` to the class `inputMessagePoll`, which can be used by bots to send a closed poll.
+* Clarified in the documentation that file remote ID is guaranteed to be usable only if the corresponding file is
+ still accessible to the user and is known to TDLib. For example, if the file is from a message, then the message
+ must be not deleted and accessible to the user. If the file database is disabled, then the corresponding object with
+ the file must be preloaded by the client.
+* Added support for administrator custom titles:
+ - Added the field `custom_title` to `chatMemberStatusCreator` and `chatMemberStatusAdministrator` classes.
+ - Added the classes `chatAdministrator` and `chatAdministrators`, containing user identifiers along with
+ their custom administrator title and owner status.
+ - Replaced the result type of the method `getChatAdministrators` with `chatAdministrators`.
+* Improved Instant View support:
+ - Added the new web page block `pageBlockVoiceNote`.
+ - Changed value of invisible cells in `pageBlockTableCell` to null.
+ - Added the field `is_cached` to the class `richTextUrl`.
+* Improved support for chat backgrounds:
+ - Added the classes `backgroundFillSolid` for solid color backgrounds and `backgroundFillGradient` for
+ gradient backgrounds.
+ - Added support for TGV (gzipped subset of SVG with MIME type "application/x-tgwallpattern") background patterns
+ in addition to PNG patterns. Background pattern thumbnails are still always in PNG format.
+ - Replaced the field `color` in the class `backgroundTypePattern` with the field `fill` of type `BackgroundFill`.
+ - Replaced the class `backgroundTypeSolid` with the class `backgroundTypeFill`.
+* Added support for discussion groups for channel chats:
+ - Added the field `linked_chat_id` to the class `supergroupFullInfo` containing the identifier of a discussion
+ supergroup for the channel, or a channel, for which the supergroup is the designated discussion supergroup.
+ - Added the field `has_linked_chat` to the class `supergroup`.
+ - Added the method `getSuitableDiscussionChats`, which returns a list of chats which can be assigned as
+ a discussion group for a channel by the current user.
+ - Added the method `setChatDiscussionGroup`, which can be used to add or remove a discussion group from a channel.
+ - Added the class `chatEventLinkedChatChanged` representing a change of the linked chat in the chat event log.
+* Added support for slow mode in supergroups:
+ - Added the field `is_slow_mode_enabled` to the class `supergroup`.
+ - Added the field `slow_mode_delay` to the class `supergroupFullInfo`.
+ - Added the method `setChatSlowModeDelay`, which can be used to change the slow mode delay setting in a supergroup.
+ - Added the class `chatEventSlowModeDelayChanged` representing a change of the slow mode delay setting in
+ the chat event log.
+* Improved privacy settings support:
+ - Added the classes `userPrivacySettingRuleAllowChatMembers` and `userPrivacySettingRuleRestrictChatMembers`
+ to include or exclude all group members in a privacy setting rule.
+ - Added the class `userPrivacySettingShowPhoneNumber` for managing the visibility of the user's phone number.
+ - Added the class `userPrivacySettingAllowFindingByPhoneNumber` for managing whether the user can be found by
+ their phone number.
+* Added the method `checkCreatedPublicChatsLimit` for checking whether the maximum number of owned public chats
+ has been reached.
+* Added support for transferring ownership of supergroup and channel chats:
+ - Added the method `transferChatOwnership`.
+ - Added the class `CanTransferOwnershipResult` and the method `canTransferOwnership` for checking
+ whether chat ownership can be transferred from the current session.
+* Added support for location-based supergroups:
+ - Added the class `chatLocation`, which contains the location to which the supergroup is connected.
+ - Added the field `has_location` to the class `supergroup`.
+ - Added the field `location` to the class `supergroupFullInfo`.
+ - Added the ability to create location-based supergroups via the new field `location` in
+ the method `createNewSupergroupChat`.
+ - Added the method `setChatLocation`, which allows to change location of location-based supergroups.
+ - Added the field `can_set_location` to the class `supergroupFullInfo`.
+ - Added the class `PublicChatType`, which can be one of `publicChatTypeHasUsername` or
+ `publicChatTypeIsLocationBased`.
+ - Added the parameter `type` to the method `getCreatedPublicChats`, which allows to get location-based supergroups
+ owned by the user.
+ - Supported location-based supergroups as public chats where appropriate.
+ - Added the class `chatEventLocationChanged` representing a change of the location of a chat in the chat event log.
+* Added support for searching chats and users nearby:
+ - Added the classes `chatNearby` and `chatsNearby`, containing information about chats along with
+ the distance to them.
+ - Added the method `searchChatsNearby`, which returns chats and users nearby.
+ - Added the update `updateUsersNearby`, which is sent 60 seconds after a successful `searchChatsNearby` request.
+* Improved support for inline keyboard buttons of the type `inlineKeyboardButtonTypeLoginUrl`:
+ - Added the class `LoginUrlInfo` and the method `getLoginUrlInfo`, which allows to get information about
+ an inline button of the type `inlineKeyboardButtonTypeLoginUrl`.
+ - Added the method `getLoginUrl` for automatic authorization on the target website.
+* Improved support for content restrictions:
+ - The field `restriction_reason` in the classes `user` and `channel` now contains only a human-readable description
+ why access must be restricted. It is non-empty if and only if access to the chat needs to be restricted.
+ - Added the field `restriction_reason` to the class `message`. It is non-empty if and only if access to the message
+ needs to be restricted.
+ - Added the writable option `ignore_platform_restrictions`, which can be set in non-store apps to ignore restrictions
+ specific to the currently used operating system.
+ - Added the writable option `ignore_sensitive_content_restrictions`, which can be set to show sensitive content on
+ all user devices. `getOption("ignore_sensitive_content_restrictions")` can be used to fetch the actual value of
+ the option, the option will not be immediately updated after a change from another device.
+ - Added the read-only option `can_ignore_sensitive_content_restrictions`, which can be used to check, whether
+ the option `ignore_sensitive_content_restrictions` can be changed.
+* Added support for QR code authentication for already registered users:
+ - Added the authorization state `authorizationStateWaitOtherDeviceConfirmation`.
+ - Added the method `requestQrCodeAuthentication`, which can be used in the `authorizationStateWaitPhoneNumber` state
+ instead of the method `setAuthenticationPhoneNumber` to request QR code authentication.
+ - Added the method `confirmQrCodeAuthentication` for authentication confirmation from another device.
+* Added the update `updateMessageLiveLocationViewed`, which is supposed to trigger an edit of the corresponding
+ live location.
+* Added the parameter `input_language_code` to the method `searchEmojis`.
+* Added the method `getInactiveSupergroupChats`, to be used when the user receives a CHANNELS_TOO_MUCH error after
+ reaching the limit on the number of joined supergroup and channel chats.
+* Added the field `unique_id` to the class `remoteFile`, which can be used to identify the same file for
+ different users.
+* Added the new category of top chat list `topChatCategoryForwardChats`.
+* Added the read-only option `animated_emoji_sticker_set_name`, containing name of a sticker set with animated emojis.
+* Added the read-only option `unix_time`, containing an estimation of the current Unix timestamp.
+ The option will not be updated automatically unless the difference between the previous estimation and
+ the locally available monotonic clocks changes significantly.
+* Added the field `is_silent` to the class `notification`, so silent notifications can be shown with
+ the appropriate mark.
+* Added the field `video_upload_bitrate` to the class `autoDownloadSettings`.
+* Disallowed to call `setChatNotificationSettings` method on the chat with self, which never worked.
+* Added support for `ton://` URLs in messages and inline keyboard buttons.
+
+-----------------------------------------------------------------------------------------------------------------------
+
+Changes in 1.5.0 (9 Sep 2019):
+
+* Changed authorization workflow:
+ - Added the state `authorizationStateWaitRegistration`, which will be received after `authorizationStateWaitCode` for
+ users who are not registered yet.
+ - Added the method `registerUser`, which must be used in the `authorizationStateWaitRegistration` state to finish
+ registration of the user.
+ - Removed the fields `is_registered` and `terms_of_service` from the class `authorizationStateWaitCode`.
+ - Removed the parameters `first_name` and `last_name` from the method `checkAuthenticationCode`.
+* Added support for messages with an unknown sender (zero `sender_user_id`) in private chats, basic groups and
+ supergroups. Currently, the sender is unknown for posts in channels and for channel posts automatically forwarded to
+ the discussion group.
+* Added support for the new permission system for non-administrator users in groups:
+ - Added the class `chatPermissions` containing all supported permissions, including new permissions `can_send_polls`,
+ `can_change_info`, `can_invite_users` and `can_pin_messages`.
+ - Added the field `permissions` to the class `chat`, describing actions that non-administrator chat members are
+ allowed to take in the chat.
+ - Added the update `updateChatPermissions`.
+ - Added the method `setChatPermissions` for changing chat permissions.
+ - Added the class `chatEventPermissionsChanged` representing a change of chat permissions in the chat event log.
+ - Replaced the fields `can_send_messages`, `can_send_media_messages`, `can_send_other_messages`,
+ `can_add_web_page_previews` in the class `chatMemberStatusRestricted` with the field `permissions` of
+ the type `chatPermissions`.
+ - Removed the field `everyone_is_administrator` from the `basicGroup` class in favor of the field `permissions` of
+ the class `chat`.
+ - Removed the field `anyone_can_invite` from the `supergroup` class in favor of the field `permissions` of
+ the class `chat`.
+ - Removed the method `toggleBasicGroupAdministrators` in favor of `setChatPermissions`.
+ - Removed the method `toggleSupergroupInvites` in favor of `setChatPermissions`.
+ - Renamed the field `anyone_can_invite` to `can_invite_users` in the class `chatEventInvitesToggled`.
+ - The permissions `can_send_other_messages` and `can_add_web_page_previews` now imply only `can_send_messages`
+ instead of `can_send_media_messages`.
+ - Allowed administrators in basic groups to use the method `generateChatInviteLink`.
+* Added out of the box `OpenBSD` and `NetBSD` operating systems support.
+* Added possibility to use `LibreSSL` >= 2.7.0 instead of `OpenSSL` to build TDLib.
+* Added instructions for building TDLib on `Debian 10`, `OpenBSD` and `NetBSD` to
+ the [TDLib build instructions generator](https://tdlib.github.io/td/build.html).
+* Added support for Backgrounds 2.0:
+ - Added the classes `BackgroundType`, `background`, `backgrounds` and `InputBackground`.
+ - Added the method `getBackground` returning the list of backgrounds installed by the user.
+ - Added the method `setBackground` for changing the background selected by the user.
+ - Added the update `updateSelectedBackground`, which is sent right after a successful initialization and whenever
+ the selected background changes.
+ - Added the method `removeBackground` for removing a background from the list of installed backgrounds.
+ - Added the method `resetBackgrounds` for restoring the default list of installed backgrounds.
+ - Added the method `searchBackground` returning a background by its name.
+ - Added the method `getBackgroundUrl` returning a persistent URL for a background.
+ - Removed the `getWallpapers` method.
+ - Removed the `wallpaper` and the `wallpapers` classes.
+ - The class `fileTypeWallpaper` can be used for remote file identifiers of both old wallpapers and new backgrounds.
+* Added support for descriptions in basic groups:
+ - Added the field `description` to the class `basicGroupFullInfo`.
+ - Replaced the method `setSupergroupDescription` with `setChatDescription` which can be used for any chat type.
+* Added support for emoji suggestions:
+ - Added the method `searchEmojis` for searching emojis by keywords.
+ - Added the method `getEmojiSuggestionsUrl`, which can be used to automatically log in to the translation platform
+ and suggest new emoji replacements.
+ - Renamed the class `stickerEmojis` to `emojis`.
+* Changed type of the fields `old_photo` and `new_photo` in the class `chatEventPhotoChanged` from `chatPhoto` to
+ `photo`.
+* Changed recommended size for `inputThumbnail` from 90x90 to 320x320.
+* Combined all supported settings for phone number authentication:
+ - Added the class `phoneNumberAuthenticationSettings` which contains all the settings.
+ - Replaced the parameters `is_current_phone_number` and `allow_flash_call` in the methods
+ `setAuthenticationPhoneNumber`, `sendPhoneNumberConfirmationCode`, `sendPhoneNumberVerificationCode` and
+ `changePhoneNumber` with the parameter `settings` of the type `phoneNumberAuthenticationSettings`.
+ - Added support for automatic SMS code verification for official applications via the new field `allow_app_hash` in
+ the class `phoneNumberAuthenticationSettings`.
+* Added support for auto-download settings presets.
+ - Added the classes `autoDownloadSettings` and `autoDownloadSettingsPresets`.
+ - Added the method `getAutoDownloadSettingsPresets` for getting the settings.
+ - Added the method `setAutoDownloadSettings`, which needs to be called whenever the user changes the settings.
+* Added support for minithumbnails - thumbnail images of a very poor quality and low resolution:
+ - Added the class `minithumbnail`.
+ - Added the field `minithumbnail` to `animation`, `document`, `photo`, `video` and `videoNote` classes.
+ - Added the field `audio_cover_minithumbnail` to the class `audio`.
+* Added support for resending messages which failed to send:
+ - Added the fields `error_code`, `error_message`, `can_retry` and `retry_after` to
+ the class `messageSendingStateFailed`.
+ - Added the method `resendMessages`.
+* Added the field `is_animated` to the `sticker`, `stickerSet` and `stickerSetInfo` classes.
+ Animated stickers can be received anywhere where non-animated stickers can appear.
+* Added the parameters `send_copy` and `remove_caption` to the `forwardMessages` method to allow forwarding of
+ messages without links to the originals.
+* Added the fields `send_copy` and `remove_caption` to `inputMessageForwarded` method to allow forwarding of
+ a message without link to the original message.
+* Added the method `getMessageLinkInfo` for getting information about a link to a message in a chat.
+* Added the class `userPrivacySettingShowProfilePhoto` for managing visibility of the user's profile photo.
+* Added the class `userPrivacySettingShowLinkInForwardedMessages` for managing whether a link to the user's account is
+ included with forwarded messages.
+* Added the field `thumbnail` to the classes `stickerSet` and `stickerSetInfo`, containing a thumbnail for
+ the sticker set.
+* Added the field `is_scam` to the classes `user` and `supergroup`.
+* Added a new kind of inline keyboard button `inlineKeyboardButtonTypeLoginUrl`, which for the moment must be processed
+ in the same way as an `inlineKeyboardButtonTypeUrl`.
+* Added the new class `supergroupMembersFilterContacts`, allowing to only search for contacts
+ in `getSupergroupMembers`.
+* Added the new class `chatMembersFilterContacts`, allowing to only search for contacts in `searchChatMembers`.
+* Added the class `chatEventPollStopped` representing the closing of a poll in a message in the chat event log.
+* Added ability to specify the exact types of problems with a call in the method `sendCallRating` and
+ the new class `CallProblem`.
+* Changes in [tdweb](https://github.com/tdlib/td/blob/master/example/web/):
+ - Supported non-zero `offset` and `limit` in `readFilePart`.
+
+-----------------------------------------------------------------------------------------------------------------------
+
+Changes in 1.4.0 (1 May 2019):
+
+* Added a [TDLib build instructions generator](https://tdlib.github.io/td/build.html), covering in details
+ TDLib building on the most popular operating systems.
+* Added an example of TDLib building and usage from a browser.
+ See https://github.com/tdlib/td/blob/master/example/web/ for more details.
+* Allowed to pass NULL pointer to `td_json_client_execute` instead of a previously created JSON client.
+ Now you can use synchronous TDLib methods through a JSON interface before creating a TDLib JSON client.
+* Added support for media streaming by allowing to download any part of a file:
+ - Added the `offset` parameter to `downloadFile` which specifies the starting position
+ from which the file should be downloaded.
+ - Added the `limit` parameter to `downloadFile` which specifies how many bytes should be downloaded starting from
+ the `offset` position.
+ - Added the field `download_offset` to the class `localFile` which contains the current download offset.
+ - The field `downloaded_prefix_size` of the `localFile` class now contains the number of available bytes
+ from the position `download_offset` instead of from the beginning of the file.
+ - Added the method `getFileDownloadedPrefixSize` which can be used to get the number of locally available file bytes
+ from a given offset without actually changing the download offset.
+* Added the parameter `synchronous` to `downloadFile` which causes the request to return the result only after
+ the download is completed.
+* Added support for native polls in messages:
+ - Added `messagePoll` to the types of message content; contains a poll.
+ - Added the classes `poll` and `pollOption` describing a poll and a poll answer option respectively.
+ - Added `inputMessagePoll` to the types of new input message content; can be used to send a poll.
+ - Added the method `setPollAnswer` which can be used for voting in polls.
+ - Added the method `stopPoll` which can be used to stop polls. Use the `Message.can_be_edited` field to check whether
+ this method can be called on a message.
+ - Added the update `updatePoll` for bots only. Ordinary users receive poll updates through `updateMessageContent`.
+* Added a Notification API. See article https://core.telegram.org/tdlib/notification-api for a detailed description.
+ - Added the class `pushReceiverId` which contains a globally unique identifier of the push notification subscription.
+ - Changed the return type of the method `registerDevice` to `pushReceiverId` to allow matching of push notifications
+ with TDLib instances.
+ - Removed the fields `disable_notification` and `contains_mention` from `updateNewMessage`.
+ - Renamed the class `deviceTokenGoogleCloudMessaging` to `deviceTokenFirebaseCloudMessaging`.
+ - Added the field `encrypt` to classes `deviceTokenApplePushVoIP` and `deviceTokenFirebaseCloudMessaging`
+ which allows to subscribe for end-to-end encrypted push notifications.
+ - Added the option `notification_group_count_max` which can be used to enable the Notification API and set
+ the maximum number of notification groups to be shown simultaneously.
+ - Added the option `notification_group_size_max` which can be used to set the maximum number of simultaneously shown
+ notifications in a group.
+ - Added the synchronous method `getPushReceiverId` for matching a push notification with a TDLib instance.
+ - Added the method `processPushNotification` for handling of push notifications.
+ - Removed the method `processDcUpdate` in favor of the general `processPushNotification` method.
+ - Added the update `updateNotificationGroup`, sent whenever a notification group changes.
+ - Added the update `updateNotification`, sent whenever a notification changes.
+ - Added the update `updateActiveNotifications` for syncing the list of active notifications on startup.
+ - Added the update `updateHavePendingNotifications` which can be used to improve lifetime handling of
+ the TDLib instance.
+ - Added the possibility to disable special handling of notifications about pinned messages via the new settings
+ `use_default_disable_pinned_message_notifications`, `disable_pinned_message_notifications` in
+ the class `chatNotificationSettings` and the new setting `disable_pinned_message_notifications` in
+ the class `scopeNotificationSettings`.
+ - Added the possibility to disable special handling of notifications about mentions and replies via the new settings
+ `use_default_disable_mention_notifications`, `disable_mention_notifications` in
+ the class `chatNotificationSettings` and the new setting `disable_mention_notifications` in
+ the class `scopeNotificationSettings`.
+ - Added the class `PushMessageContent` describing the content of a notification, received through
+ a push notification.
+ - Added the class `NotificationType` describing a type of a notification.
+ - Added the class `notification` containing information about a notification.
+ - Added the class `NotificationGroupType` describing a type of a notification group.
+ - Added the class `notificationGroup` describing a state of a notification group.
+ - Added the methods `removeNotification` and `removeNotificationGroup` for handling notifications removal
+ by the user.
+ - Added the separate notification scope `notificationSettingsScopeChannelChats` for channel chats.
+* Added support for pinned notifications in basic groups and Saved Messages:
+ - Added the field `pinned_message_id` to the class `chat`.
+ - Removed the field `pinned_message_id` from the class `supergroupFullInfo` in favor of `chat.pinned_message_id`.
+ - Added the update `updateChatPinnedMessage`.
+ - The right `can_pin_messages` is now applicable to both basic groups and supergroups.
+ - Replaced the method `pinSupergroupMessage` with `pinChatMessage` which can be used for any chat type.
+ - Replaced the method `unpinSupergroupMessage` with `unpinChatMessage` which can be used for any chat type.
+* Added new synchronous methods for managing TDLib internal logging. The old functions are deprecated and
+ will be removed in TDLib 2.0.0.
+ - Added the synchronous method `setLogStream` for changing the stream to which the TDLib internal log is written.
+ - Added the synchronous method `getLogStream` for getting information about the currently used log stream.
+ - Added the classes `logStreamDefault`, `logStreamFile` and `logStreamEmpty` describing different supported kinds of
+ log streams.
+ - Added the class `logVerbosityLevel` containing the verbosity level of the TDLib internal log.
+ - Added the class `logTags` containing a list of available TDLib internal log tags.
+ - Added the synchronous method `setLogVerbosityLevel` for changing verbosity level of logging.
+ - Added the synchronous method `getLogVerbosityLevel` for getting the current verbosity level of logging.
+ - Added the synchronous method `getLogTags` returning all currently supported log tags.
+ - Added the synchronous method `setLogTagVerbosityLevel` for changing the verbosity level of logging for
+ some specific part of the code.
+ - Added the synchronous method `getLogTagVerbosityLevel` for getting the current verbosity level for a specific part
+ of the code.
+ - Added the synchronous method `addLogMessage` for using the TDLib internal log by the application.
+* Added support for Instant View 2.0:
+ - Replaced the field `has_instant_view` in class `webPage` with the `instant_view_version` field.
+ - Added the field `version` to the class `webPageInstantView`.
+ - Added the class `pageBlockCaption`.
+ - Changed the type of `caption` fields in `pageBlockAnimation`, `pageBlockAudio`, `pageBlockPhoto`, `pageBlockVideo`,
+ `pageBlockEmbedded`, `pageBlockEmbeddedPost`, `pageBlockCollage` and `pageBlockSlideshow` from
+ `RichText` to `pageBlockCaption`.
+ - Added the class `pageBlockListItem` and replaced the content of the `pageBlockList` class with a list of
+ `pageBlockListItem`.
+ - Added 6 new kinds of `RichText`: `richTextSubscript`, `richTextSuperscript`, `richTextMarked`,
+ `richTextPhoneNumber`, `richTextIcon` and `richTextAnchor`.
+ - Added new classes `pageBlockRelatedArticle`, `PageBlockHorizontalAlignment`, `PageBlockVerticalAlignment` and
+ `pageBlockTableCell`.
+ - Added new block types `pageBlockKicker`, `pageBlockRelatedArticles`, `pageBlockTable`, `pageBlockDetails` and
+ `pageBlockMap`.
+ - Added the flag `is_rtl` to the class `webPageInstantView`.
+ - Renamed the field `caption` in classes `pageBlockBlockQuote` and `pageBlockPullQuote` to `credit`.
+ - Dimensions in `pageBlockEmbedded` can now be unknown.
+ - Added the field `url` to `pageBlockPhoto` which contains a URL that needs to be opened when the photo is clicked.
+ - Added the field `url` to `webPageInstantView` which must be used for the correct handling of anchors.
+* Added methods for confirmation of the 2-step verification recovery email address:
+ - Added the method `checkRecoveryEmailAddressCode` for checking the verification code.
+ - Added the method `resendRecoveryEmailAddressCode` for resending the verification code.
+ - Replaced the field `unconfirmed_recovery_email_address_pattern` in the class `passwordState` with
+ the `recovery_email_address_code_info` field containing full information about the code.
+ - The necessity of recovery email address confirmation in `setPassword` and `setRecoveryEmailAddress` methods
+ is now returned by the corresponding `passwordState` and not by the error `EMAIL_UNCONFIRMED`.
+* Improved the `MessageForwardInfo` class and added support for hidden original senders:
+ - Removed the old `messageForwardedPost` and `messageForwardedFromUser` classes.
+ - Added the class `messageForwardInfo` which contains information about the origin of the message, original sending
+ date and identifies the place from which the message was forwarded the last time for messages forwarded to
+ Saved Messages.
+ - Added the classes `messageForwardOriginUser`, `messageForwardOriginHiddenUser` and `messageForwardOriginChannel`
+ which describe the exact origins of a message.
+* Improved getting the list of user profile photos:
+ - Added the class `userProfilePhoto`, containing `id`, `added_date` and `sizes` of a profile photo.
+ - Changed the type of the field `photos` in `userProfilePhotos` to a list of `userProfilePhoto` instead of
+ a list of `photo`. `getUserProfilePhotos` now returns a date for each profile photo.
+ - Removed the field `id` from the class `photo` (this field was only needed in the result of `getUserProfilePhotos`).
+* Added the possibility to get a Telegram Passport authorization form before asking the user for a password:
+ - Removed the parameter `password` from the method `getPassportAuthorizationForm`.
+ - Moved the fields `elements` and `errors` from the class `passportAuthorizationForm` to
+ the new class `passportElementsWithErrors`.
+ - Added the method `getPassportAuthorizationFormAvailableElements` that takes the user's password and
+ returns previously uploaded Telegram Passport elements and errors in them.
+* Added the field `file_index` to the classes `passportElementErrorSourceFile` and
+ `passportElementErrorSourceTranslationFile`.
+* Added the method `getCurrentState` returning all updates describing the current `TDLib` state. It can be used to
+ restore the correct state after connecting to a running TDLib instance.
+* Added the class `updates` which contains a list of updates and is returned by the `getCurrentState` method.
+* Added the update `updateChatOnlineMemberCount` which is automatically sent for open group chats if the number of
+ online members in a group changes.
+* Added support for custom language packs downloaded from the server:
+ - Added the fields `base_language_pack_id`` to the class `languagePackInfo`. Strings from the base language pack
+ must be used for untranslated keys from the chosen language pack.
+ - Added the fields `plural_code`, `is_official`, `is_rtl`, `is_beta`, `is_installed`, `total_string_count`,
+ `translated_string_count`, `translation_url` to the class `languagePackInfo`.
+ - Added the method `addCustomServerLanguagePack` which adds a custom server language pack to the list of
+ installed language packs.
+ - Added the method `getLanguagePackInfo` which can be used for handling `https://t.me/setlanguage/...` links.
+ - Added the method `synchronizeLanguagePack` which can be used to fetch the latest versions of all strings from
+ a language pack.
+ The method doesn't need to be called explicitly for the current used/base language packs.
+ - The method `deleteLanguagePack` now also removes the language pack from the list of installed language packs.
+* Added the method `getChatNotificationSettingsExceptions` which can be used to get chats with
+ non-default notification settings.
+* Added the parameter `hide_via_bot` to `sendInlineQueryResultMessage` which can be used for
+ `getOption("animation_search_bot_username")`, `getOption("photo_search_bot_username")` and
+ `getOption("venue_search_bot_username")` bots to hide that the message was sent via the bot.
+* Added the class `chatReportReasonChildAbuse` which can be used to report a chat for child abuse.
+* Added the method `getMessageLocally` which returns a message only if it is available locally without
+ a network request.
+* Added the method `writeGeneratedFilePart` which can be used to write a generated file if there is no direct access to
+ TDLib's file system.
+* Added the method `readFilePart` which can be used to read a file from the TDLib file cache.
+* Added the class `filePart` to represent the result of the new `readFilePart` method.
+* Added the field `log_size` to the `storageStatisticsFast` class which contains the size of the TDLib internal log.
+ Previously the size was included into the value of the `database_size` field.
+* Added the field `language_pack_database_size` to the `storageStatisticsFast` class which contains the size of the
+ language pack database.
+* Added the field `is_support` to the class `user` which can be used to identify Telegram Support accounts.
+* Added the class `HttpUrl` encapsulating an HTTP URL.
+* Added the method `getMessageLink` which can be used to create a private link (which works only for members) to
+ a message in a supergroup or channel.
+* Added support for channel statistics (coming soon):
+ - Added the field `can_view_statistics` to the `supergroupFullInfo` class.
+ - Added the method `getChatStatisticsUrl` which returns a URL with the chat statistics.
+* Added support for server-side peer-to-peer calls privacy:
+ - Added the class `userPrivacySettingAllowPeerToPeerCalls` for managing privacy.
+ - Added the field `allow_p2p` to `callStateReady` class which must be used to determine whether
+ a peer-to-peer connection can be used.
+* Added the option `ignore_background_updates` which allows to skip all updates received while the TDLib instance was
+ not running. The option does nothing if the database or secret chats are used.
+* Added the read-only option `expect_blocking`, suggesting whether Telegram is blocked for the user.
+* Added the read-only option `enabled_proxy_id`, containing the ID of the enabled proxy.
+* Added the ability to identify password pending sessions (where the code was entered but not
+ the two-step verification password) via the flag `is_password_pending` in the `session` class.
+ TDLib guarantees that the sessions will be returned by the `getActiveSessions` method in the correct order.
+* Added the classes `JsonValue` and `jsonObjectMember` which represent a JSON value and
+ a member of a JSON object respectively as TDLib API objects.
+* Added the synchronous methods `getJsonValue` and `getJsonString` for simple conversion between
+ a JSON-encoded string and `JsonValue` TDLib API class.
+* Added the methods `getApplicationConfig` and `saveApplicationLogEvent` to be used for testing purposes.
+* Added the temporarily class `databaseStatistics` and the method `getDatabaseStatistics` for rough estimations of
+ database tables size in a human-readable format.
+* Made the method `Client.Execute` static in .NET interface.
+* Removed the `on_closed` callback virtual method from low-level C++ ClientActor interface.
+ Callback destructor can be used instead.
+* Updated dependencies in the prebuilt TDLib for Android:
+ - Updated SDK to SDK 28 in which helper classes was moved from `android.support.` to `androidx.` package.
+ - Updated NDK to r19c, which dropped support for Android versions up to 4.0.4, so the minimum supported version is
+ Android 4.1.
+ - Updated OpenSSL to version 1.1.1.
+ - Added x86_64 libraries.
+* Added out of the box `FreeBSD` support.
+* Significantly improved TDLib compilation time and decreased compiler RAM usage:
+ - In native C++ interface `td_api::object_ptr` is now a simple homebrew const-propagating class instead of
+ `std::unique_ptr`.
+ - Added the script `SplitSource.php`, which can be used to split some source code files before building
+ the library to reduce maximum RAM usage per file at the expense of increased build time.
+* The update `updateOption` with the `version` option is now guaranteed to come before all other updates.
+ It can now be used to dynamically discover available methods.
+* Added the ability to delete incoming messages in private chats and revoke messages without a time limit:
+ - Added the parameter `revoke` to the method `deleteChatHistory`; use it to delete chat history for all chat members.
+ - Added the fields `can_be_deleted_only_for_self` and `can_be_deleted_for_all_users` to the class `chat`
+ which can be used to determine for whom the chat can be deleted through the `deleteChatHistory` method.
+ - The fields `Message.can_be_deleted_only_for_self` and `Message.can_be_deleted_for_all_users` can still be used
+ to determine for whom the message can be deleted through the `deleteMessages` method.
+* Added support for server-generated notifications about newly registered contacts:
+ - Setting the option `disable_contact_registered_notifications` now affects all user sessions.
+ When the option is enabled, the client will still receive `messageContactRegistered` message in the private chat,
+ but there will be no notification about the message.
+ - `getOption("disable_contact_registered_notifications")` can be used to fetch the actual value of the option,
+ the option will not be updated automatically after a change from another device.
+* Decreased the maximum allowed first name and last name length to 64, chat title length to 128,
+ matching the new server-side limits.
+* Decreased the maximum allowed value of the `forward_limit` parameter of the `addChatMember` method from 300 to 100,
+ matching the new server-side limit.
+* Added protection from opening two TDLib instances with the same database directory from one process.
+* Added copying of notification settings of new secret chats from notification settings of
+ the corresponding private chat.
+* Excluded the sponsored chat (when using sponsored proxies) from unread counters.
+* Allowed to pass decreased local_size in `setFileGenerationProgress` to restart the generation from the beginning.
+* Added a check for modification time of original file in `inputFileGenerated` whenever possible.
+ If the original file was changed, then TDLib will restart the generation.
+* Added the destruction of MTProto keys on the server during log out.
+* Added support for hexadecimal-encoded and decimal-encoded IPv4 proxy server addresses.
+* Improved the behavior of `changeImportedContacts` which now also deletes contacts of users without Telegram accounts
+ from the server.
+* Added the ability to call `getStorageStatistics` before authorization.
+* Allowed to pass `limit` = -`offset` for negative offset in the `getChatHistory` method.
+* Changed the recommended `inputThumbnail` size to be at most 320x320 instead of the previous 90x90.
+* Disabled building by default of the native C interface. Use `cmake --build . --target tdc` to build it.
+* Numerous optimizations and bug fixes:
+ - Network implementation for Windows was completely rewritten to allow a literally unlimited number of
+ simultaneously used TDLib instances.
+ - TDLib instances can now share working threads with each other. Only a limited number of threads will be created
+ even if there are thousands of TDLib instances in a single process.
+ - Removed the restriction on the size of update or response result in JSON interface.
+ - Fixed pinning of the 5th chat when there is a sponsored chat.
+ - Fixed IPv6 on Windows.
+ - Improved network connections balancing, aliveness checks and overall stability.
+ - Various autogenerated documentation fixes and improvements.
+
+-----------------------------------------------------------------------------------------------------------------------
+
+Changes in 1.3.0 (5 Sep 2018):
+
+* Added a review of existing TDLib based [frameworks](https://github.com/tdlib/td/blob/master/example/README.md)
+ in different programming languages.
+* Added a [Getting started](https://core.telegram.org/tdlib/getting-started) guide describing the main TDLib concepts
+ and basic principles required for library usage.
+* When a chat is opened, only those messages that have been viewed are marked as read.
+* Improved the proxy settings API:
+ - A list of proxies is stored instead of just one proxy.
+ - New methods `addProxy`, `editProxy`, `enableProxy`, `disableProxy`, `removeProxy` and `getProxies` were added
+ instead of `setProxy` and `getProxy`.
+ - Added the method `pingProxy` which can be used to compute time needed to receive a response from a Telegram server
+ through a proxy or directly.
+ - Added support for MTProto proxy via class `proxyTypeMtproto`.
+ - Added support for HTTP proxy via class `proxyTypeHttp`.
+ - For each proxy last time it was used is remembered.
+ - Added the method `getProxyLink` which returns an HTTPS link that can be used to share a proxy with others.
+* Improved the notification settings API. Scope notification settings are now properly synchronized between all devices
+ and chat notification settings can be reset to their default values:
+ - The `notificationSettings` class was split into `chatNotificationSettings` and `scopeNotificationSettings`.
+ - Only two notification settings scopes are left: `notificationSettingsScopePrivateChats` which is responsible for
+ default notification settings for private and secret chats and `notificationSettingsScopeGroupChats` for all other
+ chats.
+ - `updateNotificationSettings` was split into `updateChatNotificationSettings` and `updateScopeNotificationSettings`.
+ - `setNotificationSettings` was split into `setChatNotificationSettings` and `setScopeNotificationSettings`.
+ - `getNotificationSettings` was replaced with `getScopeNotificationSettings`.
+* Added the field `filter` to the `searchChatMembers` method to support searching among administrators, bots,
+ restricted and banned members.
+* Added the ability to use synchronous requests and `setAlarm` before the library is initialized.
+* Added the ability to send requests that don't need authentication before the library is initialized. These requests
+ will be postponed and executed at the earliest opportunity. For example, `setNetworkType` can be used to disable the
+ network for TDLib before the library tries to use it; `addProxy` can be used to add a proxy before any network
+ activity; or `setOption("use_pfs")` can be used to guarantee that PFS is used for all requests.
+* Added support for tg:// links in `inlineKeyboardButtonTypeUrl` and `textEntityTypeTextUrl`.
+* Added the ability to call `deleteAccount` in the `authorizationStateWaitPassword` authorization state.
+* Added the ability to call `checkAuthenticationCode` with an empty `first_name` for unregistered users to check the
+ code validity.
+* Added the methods `editMessageMedia` and `editInlineMessageMedia` for editing media messages content.
+* Renamed the class `shippingAddress` to `address`.
+* Changed the return value of the `requestPasswordRecovery` method from `passwordRecoveryInfo` to
+ `emailAddressAuthenticationCodeInfo`.
+* Added support for sponsored channels promoted by MTProto-proxies:
+ - Added the field `is_sponsored` to the `chat` class.
+ - Added `updateChatIsSponsored`, sent when this field changes.
+* Added support for marking chats as unread:
+ - Added the field `is_marked_as_unread` to `chat`.
+ - Added the update `updateChatIsMarkedAsUnread`.
+ - Added the method `toggleChatIsMarkedAsUnread`.
+* Added support for a default value of `disable_notification`, used when a message is sent to the chat:
+ - Added the field `default_disable_notification` to `chat` class.
+ - Added the update `updateChatDefaultDisableNotification`.
+ - Added the method `toggleChatDefaultDisableNotification`.
+* Added the field `vcard` to the `contact` class.
+* Added the field `type` to `venue`, which contains a provider-specific type of the venue,
+* Added the update `updateUnreadChatCount`, enabled when the message database is used and sent when
+ the number of unread chats has changed.
+* Added the method `addLocalMessage` for adding a local message to a chat.
+* Added the method `getDeepLinkInfo`, which can return information about `tg://` links that are not supported by
+ the client.
+* Added support for language packs:
+ - Added the writable option `language_pack_database_path` which can be used to specify a path to a database
+ for storing language pack strings, so that this database can be shared between different accounts.
+ If not specified, language pack strings will be stored only in memory.
+ Changes to the option are applied only on the next TDLib launch.
+ - Added the writable option `localization_target` for setting up a name for the current localization target
+ (currently supported: "android", "android_x", "ios", "macos" and "tdesktop").
+ - Added the writable option `language_pack_id` for setting up an identifier of the currently used language pack from
+ the current localization target (a "language pack" represents the collection of strings that can be used to display
+ the interface of a particular application in a particular language).
+ - Added the class `LanguagePackStringValue` describing the possible values of a string from a language pack.
+ - Added the class `languagePackString` describing a string from a language pack.
+ - Added the class `languagePackStrings` containing a list of language pack strings.
+ - Added the class `languagePackInfo` containing information about a language pack from a localization target.
+ - Added the class `localizationTargetInfo` containing information about a localization target.
+ - Added the update `updateLanguagePackStrings` which is sent when some strings in a language pack have changed.
+ - Added the synchronous method `getLanguagePackString` which can be used to get a language pack string from
+ the local database.
+ - Added the method `getLocalizationTargetInfo` which returns information about the current localization target.
+ - Added the method `getLanguagePackStrings` which returns some or all strings from a language pack, possibly fetching
+ them from the server.
+ - Added the method `setCustomLanguagePack` for adding or editing a custom language pack.
+ - Added the method `editCustomLanguagePackInfo` for editing information about a custom language pack.
+ - Added the method `setCustomLanguagePackString` for adding, editing or deleting a string in a custom language pack.
+ - Added the method `deleteLanguagePack` for deleting a language pack from the database.
+ - Added the read-only option `suggested_language_pack_id` containing the identifier of the language pack,
+ suggested for the user by the server.
+* Added support for Telegram Passport:
+ - Added two new message contents `messagePassportDataSent` for ordinary users and `messagePassportDataReceived`
+ for bots containing information about Telegram Passport data shared with a bot.
+ - Added the new file type `fileTypeSecure`.
+ - Added the class `datedFile` containing information about a file along with the date it was uploaded.
+ - Added the helper classes `date`, `personalDetails`, `identityDocument`, `inputIdentityDocument`,
+ `personalDocument`, `inputPersonalDocument`, `passportElements`.
+ - Added the class `PassportElementType` describing all supported types of Telegram Passport elements.
+ - Added the class `PassportElement` containing information about a Telegram Passport element.
+ - Added the class `InputPassportElement` containing information about a Telegram Passport element to save.
+ - Added the classes `passportElementError` and `PassportElementErrorSource` describing an error in
+ a Telegram Passport element.
+ - Added the field `has_passport_data` to the `passwordState` class.
+ - Added the methods `getPassportElement`, `getAllPassportElements`, `setPassportElement`, `deletePassportElement`
+ for managing Telegram Passport elements.
+ - Added the methods `getPassportAuthorizationForm` and `sendPassportAuthorizationForm` used for sharing
+ Telegram Passport data with a service via a bot.
+ - Added the methods `sendPhoneNumberVerificationCode`, `resendPhoneNumberVerificationCode` and
+ `checkPhoneNumberVerificationCode` for verification of a phone number used for Telegram Passport.
+ - Added the methods `sendEmailAddressVerificationCode`, `resendEmailAddressVerificationCode` and
+ `checkEmailAddressVerificationCode` for verification of an email address used for Telegram Passport.
+ - Added the method `getPreferredCountryLanguage` returning a most popular language in a country.
+ - Added the classes `inputPassportElementError` and `InputPassportElementErrorSource` for bots describing an error in
+ a Telegram Passport element.
+ - Added the method `setPassportElementErrors` for bots.
+ - Added the class `encryptedPassportElement` and `encryptedCredentials` for bots describing
+ an encrypted Telegram Passport element.
+* Improved support for Telegram terms of service:
+ - Added the class `termsOfService`, containing information about the Telegram terms of service.
+ - Added the field `terms_of_service` to `authorizationStateWaitCode`.
+ - Added the update `updateTermsOfService` coming when new terms of service need to be accepted by the user.
+ - Added the method `acceptTermsOfService` for accepting terms of service.
+ - Removed the method `getTermsOfService`.
+* Added the method `getMapThumbnailFile` which can be used to register and download a map thumbnail file.
+* Added the methods `sendPhoneNumberConfirmationCode`, `resendPhoneNumberConfirmationCode` and
+ `checkPhoneNumberConfirmationCode` which can be used to prevent an account from being deleted.
+* Added the convenience methods `joinChat` and `leaveChat` which can be used instead of `setChatMemberStatus` to manage
+ the current user's membership in a chat.
+* Added the convenience method `getContacts` which can be used instead of `searchContacts` to get all contacts.
+* Added the synchronous method `cleanFileName` which removes potentially dangerous characters from a file name.
+* Added the method `getChatMessageCount` which can be used to get the number of shared media.
+* Added the writable option `ignore_inline_thumbnails` which can be used to prevent file thumbnails sent
+ by the server along with messages from being saved on the disk.
+* Added the writable option `prefer_ipv6` which can be used to prefer IPv6 connections over IPv4.
+* Added the writable option `disable_top_chats` which can be used to disable support for top chats.
+* Added the class `chatReportReasonCopyright` for reporting chats containing infringing content.
+* Added the method `clearAllDraftMessages` which can be used to delete all cloud drafts.
+* Added the read-only options `message_text_length_max` and `message_caption_length_max`.
+* Added the read-only options `animation_search_bot_username`, `photo_search_bot_username` and
+ `venue_search_bot_username` containing usernames of bots which can be used in inline mode for animations, photos and
+ venues search respectively.
+* Numerous optimizations and bug fixes:
+ - Fixed string encoding for .NET binding.
+ - Fixed building TDLib SDK for Universal Windows Platform for ARM with MSVC 2017.
+ - Fixed the Swift example project.
+ - Fixed the syntax error in the Python example.
+ - Sticker thumbnails can now have `webp` extensions if they are more likely to be in WEBP format.
+
+-----------------------------------------------------------------------------------------------------------------------
+
+Changes in 1.2.0 (20 Mar 2018):
* Added support for native .NET bindings through `C++/CLI` and `C++/CX`.
See [using in .NET projects](README.md#using-dotnet) for more details.
@@ -19,7 +1450,7 @@ Changes in 1.2.0:
* Added new message content type `messageWebsiteConnected`.
* Added new text entity types `textEntityTypeCashtag` and `textEntityTypePhoneNumber`.
* Added new update `updateUnreadMessageCount`, enabled when message database is used.
-* Method `joinChatByInviteLink` now returns the joined `Chat`.
+* Method `joinChatByInviteLink` now returns the joined `chat`.
* Method `getWebPagePreview` now accepts `formattedText` instead of plain `string`.
* Added field `phone_number` to `authenticationCodeInfo`, which contains a phone number that is being authenticated.
* Added field `is_secret` to `messageAnimation`, `messagePhoto`, `messageVideo` and `messageVideoNote` classes,
@@ -36,7 +1467,7 @@ Changes in 1.2.0:
* Added method `searchInstalledStickerSets` to search by title and name for installed sticker sets.
* Added methods for handling connected websites: `getConnectedWebsites`, `disconnectWebsite` and
`disconnectAllWebsites`.
-* Added method `getCountryCode`, which uses current user IP to identify their country.
+* Added method `getCountryCode`, which uses current user IP address to identify their country.
* Added option `t_me_url`.
* Fixed `BlackBerry` spelling in `deviceTokenBlackBerryPush`.
* Fixed return type of `getChatMessageByDate` method, which is `Message` and not `Messages`.
@@ -45,13 +1476,13 @@ Changes in 1.2.0:
-----------------------------------------------------------------------------------------------------------------------
-Changes in 1.1.1:
+Changes in 1.1.1 (4 Feb 2018):
* Fixed C JSON bindings compilation error.
* Fixed locale-dependent JSON generation.
-----------------------------------------------------------------------------------------------------------------------
-Changes in 1.1.0:
+Changes in 1.1.0 (31 Jan 2018):
* Methods `td::Log::set_file_path` and `td_set_log_file_path` now return whether they succeeded.
* Added methods `td::Log::set_max_file_size` and `td_set_log_max_file_size` for restricting maximum TDLib log size.
@@ -80,9 +1511,9 @@ Changes in 1.1.0:
* Added parameter `as_album` to method `getPublicMessageLink` to enable getting public links for media albums.
* Added field `html` to class `publicMessageLink`, containing HTML-code for message/message album embedding.
* Added parameter `only_if_pending` to method `cancelDownloadFile` to allow keeping already started downloads.
-* Methods `createPrivateChat`, `createBasciGroupChat`, `createSupergroupChat` and `createSecretChat`
- can now be called without a prior call to `getUser`/`getBasicGroup`/`getSupergorup`/`getSecretChat`.
-* Added parameter `force` to methods `createPrivateChat`, `createBasciGroupChat` and `createSupergroupChat` to allow
+* Methods `createPrivateChat`, `createBasicGroupChat`, `createSupergroupChat` and `createSecretChat`
+ can now be called without a prior call to `getUser`/`getBasicGroup`/`getSupergroup`/`getSecretChat`.
+* Added parameter `force` to methods `createPrivateChat`, `createBasicGroupChat` and `createSupergroupChat` to allow
creating a chat without network requests.
* Numerous optimizations and bug fixes.
diff --git a/protocols/Telegram/tdlib/td/CMake/AddCXXCompilerFlag.cmake b/protocols/Telegram/tdlib/td/CMake/AddCXXCompilerFlag.cmake
index b57fa4dd98..6fb615a1f7 100644
--- a/protocols/Telegram/tdlib/td/CMake/AddCXXCompilerFlag.cmake
+++ b/protocols/Telegram/tdlib/td/CMake/AddCXXCompilerFlag.cmake
@@ -12,7 +12,7 @@
# add_cxx_compiler_flag(-no-strict-aliasing RELEASE)
# Requires CMake 2.6+
-if(__add_cxx_compiler_flag)
+if (__add_cxx_compiler_flag)
return()
endif()
set(__add_cxx_compiler_flag INCLUDED)
@@ -29,22 +29,22 @@ endfunction(mangle_compiler_flag)
function(add_cxx_compiler_flag FLAG)
string(REPLACE "-Wno-" "-W" MAIN_FLAG ${FLAG})
- mangle_compiler_flag("${MAIN_FLAG}" MANGLED_FLAG)
+ mangle_compiler_flag("${MAIN_FLAG}" MANGLED_FLAG_NAME)
if (DEFINED CMAKE_REQUIRED_FLAGS)
set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}")
else()
set(CMAKE_REQUIRED_FLAGS "${FLAG}")
endif()
- check_cxx_compiler_flag("${MAIN_FLAG}" ${MANGLED_FLAG})
+ check_cxx_compiler_flag("${MAIN_FLAG}" ${MANGLED_FLAG_NAME})
if (DEFINED OLD_CMAKE_REQUIRED_FLAGS)
set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}")
else()
unset(CMAKE_REQUIRED_FLAGS)
endif()
- if(${MANGLED_FLAG})
+ if (${MANGLED_FLAG_NAME})
set(VARIANT ${ARGV1})
- if(ARGV1)
+ if (ARGV1)
string(TOUPPER "_${VARIANT}" VARIANT)
endif()
set(CMAKE_CXX_FLAGS${VARIANT} "${CMAKE_CXX_FLAGS${VARIANT}} ${FLAG}" PARENT_SCOPE)
@@ -53,12 +53,12 @@ endfunction()
function(add_required_cxx_compiler_flag FLAG)
string(REPLACE "-Wno-" "-W" MAIN_FLAG ${FLAG})
- mangle_compiler_flag("${MAIN_FLAG}" MANGLED_FLAG)
+ mangle_compiler_flag("${MAIN_FLAG}" MANGLED_FLAG_NAME)
set(OLD_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${FLAG}")
- check_cxx_compiler_flag("${MAIN_FLAG}" ${MANGLED_FLAG})
+ check_cxx_compiler_flag("${MAIN_FLAG}" ${MANGLED_FLAG_NAME})
set(CMAKE_REQUIRED_FLAGS "${OLD_CMAKE_REQUIRED_FLAGS}")
- if (${MANGLED_FLAG})
+ if (${MANGLED_FLAG_NAME})
set(VARIANT ${ARGV1})
if (ARGV1)
string(TOUPPER "_${VARIANT}" VARIANT)
diff --git a/protocols/Telegram/tdlib/td/CMake/FindReadline.cmake b/protocols/Telegram/tdlib/td/CMake/FindReadline.cmake
index dab24bb19f..94b92ef558 100644
--- a/protocols/Telegram/tdlib/td/CMake/FindReadline.cmake
+++ b/protocols/Telegram/tdlib/td/CMake/FindReadline.cmake
@@ -1,10 +1,10 @@
if (APPLE)
- find_path(READLINE_INCLUDE_DIR readline/readline.h /usr/local/opt/readline/include /opt/local/include /opt/include /usr/local/include /usr/include NO_DEFAULT_PATH)
+ find_path(READLINE_INCLUDE_DIR readline/readline.h /opt/homebrew/opt/readline/include /usr/local/opt/readline/include /opt/local/include /opt/include /usr/local/include /usr/include NO_DEFAULT_PATH)
endif()
find_path(READLINE_INCLUDE_DIR readline/readline.h)
if (APPLE)
- find_library(READLINE_LIBRARY readline /usr/local/opt/readline/lib /opt/local/lib /opt/lib /usr/local/lib /usr/lib NO_DEFAULT_PATH)
+ find_library(READLINE_LIBRARY readline /opt/homebrew/opt/readline/lib /usr/local/opt/readline/lib /opt/local/lib /opt/lib /usr/local/lib /usr/lib NO_DEFAULT_PATH)
endif()
find_library(READLINE_LIBRARY readline)
@@ -13,7 +13,7 @@ if (READLINE_INCLUDE_DIR AND READLINE_LIBRARY AND NOT GNU_READLINE_FOUND)
set(CMAKE_REQUIRED_LIBRARIES "${READLINE_LIBRARY}")
include(CheckCXXSourceCompiles)
unset(GNU_READLINE_FOUND CACHE)
- CHECK_CXX_SOURCE_COMPILES("#include <stdio.h>\n#include <readline/readline.h>\nint main() { rl_replace_line(\"\", 0); }" GNU_READLINE_FOUND)
+ check_cxx_source_compiles("#include <stdio.h>\n#include <readline/readline.h>\nint main() { rl_replace_line(\"\", 0); }" GNU_READLINE_FOUND)
if (NOT GNU_READLINE_FOUND)
unset(READLINE_INCLUDE_DIR CACHE)
unset(READLINE_LIBRARY CACHE)
@@ -21,5 +21,5 @@ if (READLINE_INCLUDE_DIR AND READLINE_LIBRARY AND NOT GNU_READLINE_FOUND)
endif()
include(FindPackageHandleStandardArgs)
-FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG READLINE_INCLUDE_DIR READLINE_LIBRARY)
+find_package_handle_standard_args(Readline DEFAULT_MSG READLINE_INCLUDE_DIR READLINE_LIBRARY)
mark_as_advanced(READLINE_INCLUDE_DIR READLINE_LIBRARY)
diff --git a/protocols/Telegram/tdlib/td/CMake/GeneratePkgConfig.cmake b/protocols/Telegram/tdlib/td/CMake/GeneratePkgConfig.cmake
new file mode 100644
index 0000000000..afbe06ac60
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/CMake/GeneratePkgConfig.cmake
@@ -0,0 +1,92 @@
+function(get_relative_link OUTPUT PATH)
+ if (PATH MATCHES "^[$]<[$]<CONFIG:DEBUG>:")
+ set(${OUTPUT} "" PARENT_SCOPE)
+ return()
+ endif()
+ string(REGEX REPLACE "^[$]<[$]<NOT:[$]<CONFIG:DEBUG>>:(.*)>$" "\\1" PATH "${PATH}")
+
+ get_filename_component(NAME "${PATH}" NAME_WE)
+ if (IS_ABSOLUTE ${PATH})
+ get_filename_component(DIRECTORY_NAME "${PATH}" DIRECTORY)
+ if (WIN32)
+ set(${OUTPUT} "-l\"${DIRECTORY_NAME}/${NAME}\"" PARENT_SCOPE)
+ else()
+ get_filename_component(FULL_NAME "${PATH}" NAME)
+ set(${OUTPUT} "-L\"${DIRECTORY_NAME}\" -l:${FULL_NAME}" PARENT_SCOPE)
+ endif()
+ return()
+ endif()
+
+ if (NOT WIN32 AND NAME MATCHES "^lib")
+ string(REGEX REPLACE "^lib" "-l" LINK "${NAME}")
+ elseif (NAME MATCHES "^-")
+ set(LINK "${NAME}")
+ else()
+ string(CONCAT LINK "-l" "${NAME}")
+ endif()
+ set(${OUTPUT} "${LINK}" PARENT_SCOPE)
+endfunction()
+
+function(generate_pkgconfig TARGET DESCRIPTION)
+ # message("Generating pkg-config for ${TARGET}")
+ get_filename_component(PREFIX "${CMAKE_INSTALL_PREFIX}" REALPATH)
+
+ get_target_property(LIST "${TARGET}" LINK_LIBRARIES)
+ set(REQS "")
+ set(LIBS "")
+ foreach (LIB ${LIST})
+ if (TARGET "${LIB}")
+ set(HAS_REQS 1)
+ list(APPEND REQS "${LIB}")
+ else()
+ set(HAS_LIBS 1)
+ get_relative_link(LINK "${LIB}")
+ if (NOT LINK EQUAL "")
+ list(APPEND LIBS "${LINK}")
+ endif()
+ endif()
+ endforeach()
+
+ if (HAS_REQS)
+ set(REQUIRES "")
+ foreach (REQ ${REQS})
+ set(REQUIRES "${REQUIRES} ${REQ}")
+ endforeach()
+ set(REQUIRES "Requires.private:${REQUIRES}\n")
+ endif()
+ if (HAS_LIBS)
+ set(LIBRARIES "")
+ list(REVERSE LIBS)
+ list(REMOVE_DUPLICATES LIBS)
+ foreach (LIB ${LIBS})
+ set(LIBRARIES " ${LIB}${LIBRARIES}")
+ endforeach()
+ set(LIBRARIES "Libs.private:${LIBRARIES}\n")
+ endif()
+
+ if (IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
+ set(PKGCONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
+ else()
+ set(PKGCONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
+ endif()
+
+ if (IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
+ set(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
+ else()
+ set(PKGCONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}")
+ endif()
+
+ file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig")
+ file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/${TARGET}.pc" CONTENT
+"prefix=${PREFIX}
+
+Name: ${TARGET}
+Description: ${DESCRIPTION}
+Version: ${PROJECT_VERSION}
+
+CFlags: -I\"${PKGCONFIG_INCLUDEDIR}\"
+Libs: -L\"${PKGCONFIG_LIBDIR}\" -l${TARGET}
+${REQUIRES}${LIBRARIES}")
+
+ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/${TARGET}.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
+endfunction()
diff --git a/protocols/Telegram/tdlib/td/CMake/GetGitRevisionDescription.cmake b/protocols/Telegram/tdlib/td/CMake/GetGitRevisionDescription.cmake
new file mode 100644
index 0000000000..b565582098
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/CMake/GetGitRevisionDescription.cmake
@@ -0,0 +1,127 @@
+# - Returns a version string from Git
+#
+# These functions force a re-configure on each git commit so that you can
+# trust the values of the variables in your build system.
+#
+# get_git_head_revision(<refspecvar> <hashvar>)
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2020 Ryan Pavlik <ryan.pavlik@gmail.com> <abiryan@ryand.net>
+# http://academic.cleardefinition.com
+#
+# Copyright 2009-2013, Iowa State University.
+# Copyright 2013-2020, Ryan Pavlik
+# Copyright 2013-2020, Contributors
+# SPDX-License-Identifier: BSL-1.0
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+if (__get_git_revision_description)
+ return()
+endif()
+set(__get_git_revision_description YES)
+
+# We must run the following at "include" time, not at function call time,
+# to find the path to this module rather than the path to a calling list file
+get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
+
+# Function _git_find_closest_git_dir finds the next closest .git directory
+# that is part of any directory in the path defined by _start_dir.
+# The result is returned in the parent scope variable whose name is passed
+# as variable _git_dir_var. If no .git directory can be found, the
+# function returns an empty string via _git_dir_var.
+#
+# Example: Given a path C:/bla/foo/bar and assuming C:/bla/.git exists and
+# neither foo nor bar contain a file/directory .git. This wil return
+# C:/bla/.git
+#
+function(_git_find_closest_git_dir _start_dir _git_dir_var)
+ set(cur_dir "${_start_dir}")
+ set(git_dir "${_start_dir}/.git")
+ while (NOT EXISTS "${git_dir}")
+ # .git dir not found, search parent directories
+ set(git_previous_parent "${cur_dir}")
+ get_filename_component(cur_dir "${cur_dir}" DIRECTORY)
+ if (cur_dir STREQUAL git_previous_parent)
+ # We have reached the root directory, we are not in git
+ set(${_git_dir_var} "" PARENT_SCOPE)
+ return()
+ endif()
+ set(git_dir "${cur_dir}/.git")
+ endwhile()
+ set(${_git_dir_var} "${git_dir}" PARENT_SCOPE)
+endfunction()
+
+function(get_git_head_revision _refspecvar _hashvar)
+ _git_find_closest_git_dir("${CMAKE_CURRENT_SOURCE_DIR}" GIT_DIR)
+
+ if (NOT GIT_DIR STREQUAL "")
+ file(RELATIVE_PATH _relative_to_source_dir "${CMAKE_CURRENT_SOURCE_DIR}" "${GIT_DIR}")
+ if (_relative_to_source_dir MATCHES "^[.][.]")
+ # We've gone above the CMake root dir.
+ set(GIT_DIR "")
+ endif()
+ endif()
+ if (GIT_DIR STREQUAL "")
+ set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+ set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+ return()
+ endif()
+
+ find_package(Git)
+
+ # Check if the current source dir is a git submodule or a worktree.
+ # In both cases .git is a file instead of a directory.
+ #
+ if ((NOT IS_DIRECTORY ${GIT_DIR}) AND Git_FOUND)
+ # The following git command will return a non empty string that
+ # points to the super project working tree if the current
+ # source dir is inside a git submodule.
+ # Otherwise the command will return an empty string.
+ #
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" rev-parse --show-superproject-working-tree
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ OUTPUT_VARIABLE out
+ ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if (NOT out STREQUAL "")
+ # If out is non-empty, GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a submodule
+ file(READ ${GIT_DIR} submodule)
+ string(REGEX REPLACE "gitdir: (.*)$" "\\1" GIT_DIR_RELATIVE ${submodule})
+ string(STRIP ${GIT_DIR_RELATIVE} GIT_DIR_RELATIVE)
+ get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
+ get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
+ set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
+ else()
+ # GIT_DIR/CMAKE_CURRENT_SOURCE_DIR is in a worktree
+ file(READ ${GIT_DIR} worktree_ref)
+ # The .git directory contains a path to the worktree information directory
+ # inside the parent git repo of the worktree.
+ string(REGEX REPLACE "gitdir: (.*)$" "\\1" git_worktree_dir ${worktree_ref})
+ string(STRIP ${git_worktree_dir} git_worktree_dir)
+ _git_find_closest_git_dir("${git_worktree_dir}" GIT_DIR)
+ set(HEAD_SOURCE_FILE "${git_worktree_dir}/HEAD")
+ endif()
+ else()
+ set(HEAD_SOURCE_FILE "${GIT_DIR}/HEAD")
+ endif()
+ if (NOT EXISTS "${HEAD_SOURCE_FILE}")
+ return()
+ endif()
+
+ set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
+ if (NOT EXISTS "${GIT_DATA}")
+ file(MAKE_DIRECTORY "${GIT_DATA}")
+ endif()
+ set(HEAD_FILE "${GIT_DATA}/HEAD")
+ configure_file("${HEAD_SOURCE_FILE}" "${HEAD_FILE}" COPYONLY)
+
+ configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY)
+ include("${GIT_DATA}/grabRef.cmake")
+
+ set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
+ set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
+endfunction()
diff --git a/protocols/Telegram/tdlib/td/CMake/GetGitRevisionDescription.cmake.in b/protocols/Telegram/tdlib/td/CMake/GetGitRevisionDescription.cmake.in
new file mode 100644
index 0000000000..bfc1e54c15
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/CMake/GetGitRevisionDescription.cmake.in
@@ -0,0 +1,43 @@
+#
+# Internal file for GetGitRevisionDescription.cmake
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright 2009-2012, Iowa State University
+# Copyright 2011-2015, Contributors
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+# SPDX-License-Identifier: BSL-1.0
+
+set(HEAD_HASH)
+
+file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
+
+string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
+if (HEAD_CONTENTS MATCHES "ref")
+ # named branch
+ string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
+ if (EXISTS "@GIT_DIR@/${HEAD_REF}")
+ configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+ else()
+ configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY)
+ file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
+ if (PACKED_REFS MATCHES "([0-9a-z]*) ${HEAD_REF}")
+ set(HEAD_HASH "${CMAKE_MATCH_1}")
+ endif()
+ endif()
+else()
+ # detached HEAD
+ configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
+endif()
+
+if (NOT HEAD_HASH)
+ file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
+ string(STRIP "${HEAD_HASH}" HEAD_HASH)
+endif()
diff --git a/protocols/Telegram/tdlib/td/CMake/PreventInSourceBuild.cmake b/protocols/Telegram/tdlib/td/CMake/PreventInSourceBuild.cmake
new file mode 100644
index 0000000000..1815e82a25
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/CMake/PreventInSourceBuild.cmake
@@ -0,0 +1,14 @@
+function(prevent_in_source_build)
+ get_filename_component(REAL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH)
+ get_filename_component(REAL_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" REALPATH)
+
+ if (REAL_BINARY_DIR STREQUAL REAL_SOURCE_DIR)
+ message(" Out-of-source build must be used. Remove the files already")
+ message(" created by CMake and rerun CMake from a new directory:")
+ message(" rm -rf CMakeFiles CMakeCache.txt")
+ message(" mkdir build")
+ message(" cd build")
+ message(" cmake ..")
+ message(FATAL_ERROR "In-source build failed.")
+ endif()
+endfunction()
diff --git a/protocols/Telegram/tdlib/td/CMake/TdSetUpCompiler.cmake b/protocols/Telegram/tdlib/td/CMake/TdSetUpCompiler.cmake
new file mode 100644
index 0000000000..536c5efb29
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/CMake/TdSetUpCompiler.cmake
@@ -0,0 +1,174 @@
+# - Configures C++14 compiler, setting TDLib-specific compilation options.
+
+function(td_set_up_compiler)
+ set(CMAKE_EXPORT_COMPILE_COMMANDS 1 PARENT_SCOPE)
+
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON PARENT_SCOPE)
+
+ include(illumos)
+
+ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ set(GCC 1)
+ set(GCC 1 PARENT_SCOPE)
+ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ set(CLANG 1)
+ set(CLANG 1 PARENT_SCOPE)
+ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
+ set(INTEL 1)
+ set(INTEL 1 PARENT_SCOPE)
+ elseif (NOT MSVC)
+ message(FATAL_ERROR "Compiler isn't supported")
+ endif()
+
+ include(CheckCXXCompilerFlag)
+
+ if (GCC OR CLANG OR INTEL)
+ if (WIN32 AND INTEL)
+ set(STD14_FLAG /Qstd=c++14)
+ else()
+ set(STD14_FLAG -std=c++14)
+ endif()
+ check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD14)
+ if (NOT HAVE_STD14)
+ string(REPLACE "c++14" "c++1y" STD14_FLAG "${STD14_FLAG}")
+ check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD1Y)
+ set(HAVE_STD14 ${HAVE_STD1Y})
+ endif()
+ elseif (MSVC)
+ set(HAVE_STD14 MSVC_VERSION>=1900)
+ endif()
+
+ if (NOT HAVE_STD14)
+ message(FATAL_ERROR "No C++14 support in the compiler. Please upgrade the compiler.")
+ endif()
+
+ if (MSVC)
+ if (CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1")
+ string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
+ endif()
+ add_definitions(-D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8 /GR- /W4 /wd4100 /wd4127 /wd4324 /wd4505 /wd4814 /wd4702 /bigobj")
+ elseif (CLANG OR GCC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD14_FLAG} -fno-omit-frame-pointer -fno-exceptions -fno-rtti")
+ if (APPLE)
+ set(TD_LINKER_FLAGS "-Wl,-dead_strip")
+ if (NOT CMAKE_BUILD_TYPE MATCHES "Deb")
+ set(TD_LINKER_FLAGS "${TD_LINKER_FLAGS},-x,-S")
+ endif()
+ else()
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections")
+ if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
+ set(TD_LINKER_FLAGS "-Wl,-z,ignore")
+ elseif (EMSCRIPTEN)
+ set(TD_LINKER_FLAGS "-Wl,--gc-sections")
+ else()
+ set(TD_LINKER_FLAGS "-Wl,--gc-sections -Wl,--exclude-libs,ALL")
+ endif()
+ endif()
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${TD_LINKER_FLAGS}")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${TD_LINKER_FLAGS}")
+
+ if (WIN32 OR CYGWIN)
+ if (GCC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj")
+ endif()
+ endif()
+ elseif (INTEL)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD14_FLAG}")
+ endif()
+
+ if (WIN32)
+ add_definitions(-DNTDDI_VERSION=0x06020000 -DWINVER=0x0602 -D_WIN32_WINNT=0x0602 -DPSAPI_VERSION=1 -DNOMINMAX -DUNICODE -D_UNICODE -DWIN32_LEAN_AND_MEAN)
+ endif()
+ if (CYGWIN)
+ add_definitions(-D_DEFAULT_SOURCE=1 -DFD_SETSIZE=4096)
+ endif()
+
+ # _FILE_OFFSET_BITS is broken in Android NDK r15, r15b and r17 and doesn't work prior to Android 7.0
+ add_definitions(-D_FILE_OFFSET_BITS=64)
+
+ # _GNU_SOURCE might not be defined by g++
+ add_definitions(-D_GNU_SOURCE)
+
+ if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lsocket -lnsl")
+ if (ILLUMOS)
+ add_definitions(-DTD_ILLUMOS=1)
+ endif()
+ endif()
+
+ include(AddCXXCompilerFlag)
+ if (NOT MSVC)
+ add_cxx_compiler_flag("-Wall")
+ add_cxx_compiler_flag("-Wextra")
+ add_cxx_compiler_flag("-Wimplicit-fallthrough=2")
+ add_cxx_compiler_flag("-Wpointer-arith")
+ add_cxx_compiler_flag("-Wcast-qual")
+ add_cxx_compiler_flag("-Wsign-compare")
+ add_cxx_compiler_flag("-Wduplicated-branches")
+ add_cxx_compiler_flag("-Wduplicated-cond")
+ add_cxx_compiler_flag("-Walloc-zero")
+ add_cxx_compiler_flag("-Wlogical-op")
+ add_cxx_compiler_flag("-Wno-tautological-compare")
+ add_cxx_compiler_flag("-Wpointer-arith")
+ add_cxx_compiler_flag("-Wvla")
+ add_cxx_compiler_flag("-Wnon-virtual-dtor")
+ add_cxx_compiler_flag("-Wno-unused-parameter")
+ add_cxx_compiler_flag("-Wconversion")
+ add_cxx_compiler_flag("-Wno-sign-conversion")
+ add_cxx_compiler_flag("-Wc++14-compat-pedantic")
+ add_cxx_compiler_flag("-Wdeprecated")
+ add_cxx_compiler_flag("-Wno-unused-command-line-argument")
+ add_cxx_compiler_flag("-Qunused-arguments")
+ add_cxx_compiler_flag("-Wodr")
+ add_cxx_compiler_flag("-flto-odr-type-merging")
+
+ # add_cxx_compiler_flag("-Werror")
+
+ # add_cxx_compiler_flag("-Wcast-align")
+
+ #std::int32_t <-> int and off_t <-> std::size_t/std::int64_t
+ # add_cxx_compiler_flag("-Wuseless-cast")
+
+ #external headers like openssl
+ # add_cxx_compiler_flag("-Wzero-as-null-pointer-constant")
+ endif()
+
+ if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0))
+ add_cxx_compiler_flag("-Wno-maybe-uninitialized") # too many false positives
+ endif()
+ if (WIN32 AND GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8.0))
+ # warns about casts of function pointers returned by GetProcAddress
+ add_cxx_compiler_flag("-Wno-cast-function-type")
+ endif()
+ if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0))
+ # warns about a lot of "return std::move", which are not redundant for compilers without fix for DR 1579, i.e. GCC 4.9 or clang 3.8
+ # see http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1579
+ add_cxx_compiler_flag("-Wno-redundant-move")
+ endif()
+ if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0))
+ add_cxx_compiler_flag("-Wno-stringop-overflow") # some false positives
+ endif()
+ if (CLANG AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.5))
+ # https://stackoverflow.com/questions/26744556/warning-returning-a-captured-reference-from-a-lambda
+ add_cxx_compiler_flag("-Wno-return-stack-address")
+ endif()
+
+ if (MINGW)
+ add_cxx_compiler_flag("-ftrack-macro-expansion=0")
+ endif()
+
+ #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem /usr/include/c++/v1")
+ #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
+ #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
+ #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
+ #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
+ #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=leak")
+
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" PARENT_SCOPE)
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}" PARENT_SCOPE)
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}" PARENT_SCOPE)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" PARENT_SCOPE)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" PARENT_SCOPE)
+endfunction()
diff --git a/protocols/Telegram/tdlib/td/CMake/iOS.cmake b/protocols/Telegram/tdlib/td/CMake/iOS.cmake
index 9b395a0764..9351395422 100644
--- a/protocols/Telegram/tdlib/td/CMake/iOS.cmake
+++ b/protocols/Telegram/tdlib/td/CMake/iOS.cmake
@@ -9,12 +9,16 @@
# OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch.
# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch.
#
+# IOS_ARCH = automatic(default) or "arch1;arch2" (e.q. "x86_64;arm64")
+# By default this value will be automatically chosen based on the IOS_PLATFORM value above.
+# If set manually, it will override the default and force to build those architectures only.
+#
# CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder
-# By default this location is automatcially chosen based on the IOS_PLATFORM value above.
+# By default this location is automatically chosen based on the IOS_PLATFORM value above.
# If set manually, it will override the default location and force the user of a particular Developer Platform
#
# CMAKE_IOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder
-# By default this location is automatcially chosen based on the CMAKE_IOS_DEVELOPER_ROOT value.
+# By default this location is automatically chosen based on the CMAKE_IOS_DEVELOPER_ROOT value.
# In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path.
# If set manually, this will force the use of a specific SDK version
@@ -48,9 +52,9 @@ endif (CMAKE_UNAME)
# Force the compilers to gcc for iOS
set (CMAKE_C_COMPILER /usr/bin/gcc)
set (CMAKE_CXX_COMPILER /usr/bin/g++)
-set(CMAKE_AR ar CACHE FILEPATH "" FORCE)
-set(CMAKE_RANLIB ranlib CACHE FILEPATH "" FORCE)
-set(PKG_CONFIG_EXECUTABLE pkg-config CACHE FILEPATH "" FORCE)
+set (CMAKE_AR ar CACHE FILEPATH "" FORCE)
+set (CMAKE_RANLIB ranlib CACHE FILEPATH "" FORCE)
+set (PKG_CONFIG_EXECUTABLE pkg-config CACHE FILEPATH "" FORCE)
# Setup iOS platform unless specified manually with IOS_PLATFORM
if (NOT DEFINED IOS_PLATFORM)
@@ -59,7 +63,7 @@ endif (NOT DEFINED IOS_PLATFORM)
set (IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform")
# Check the platform selection and setup for developer root
-if (${IOS_PLATFORM} STREQUAL "OS")
+if (IOS_PLATFORM STREQUAL "OS")
set (IOS_PLATFORM_LOCATION "iPhoneOS.platform")
set (XCODE_IOS_PLATFORM iphoneos)
@@ -67,7 +71,7 @@ if (${IOS_PLATFORM} STREQUAL "OS")
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos")
set (APPLE_IOS True)
-elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR")
+elseif (IOS_PLATFORM STREQUAL "SIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform")
set (XCODE_IOS_PLATFORM iphonesimulator)
@@ -76,7 +80,7 @@ elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR")
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator")
set (APPLE_IOS True)
-elseif (${IOS_PLATFORM} STREQUAL "WATCHOS")
+elseif (IOS_PLATFORM STREQUAL "WATCHOS")
set (IOS_PLATFORM_LOCATION "WatchOS.platform")
set (XCODE_IOS_PLATFORM watchos)
@@ -84,7 +88,7 @@ elseif (${IOS_PLATFORM} STREQUAL "WATCHOS")
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-watchos")
set (APPLE_WATCH True)
-elseif (${IOS_PLATFORM} STREQUAL "WATCHSIMULATOR")
+elseif (IOS_PLATFORM STREQUAL "WATCHSIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "WatchSimulator.platform")
set (XCODE_IOS_PLATFORM watchsimulator)
@@ -93,7 +97,7 @@ elseif (${IOS_PLATFORM} STREQUAL "WATCHSIMULATOR")
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-watchsimulator")
set (APPLE_WATCH True)
-elseif (${IOS_PLATFORM} STREQUAL "TVOS")
+elseif (IOS_PLATFORM STREQUAL "TVOS")
set (IOS_PLATFORM_LOCATION "AppleTvOS.platform")
set (XCODE_IOS_PLATFORM tvos)
@@ -101,7 +105,7 @@ elseif (${IOS_PLATFORM} STREQUAL "TVOS")
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-appletvos")
set (APPLE_TV True)
-elseif (${IOS_PLATFORM} STREQUAL "TVSIMULATOR")
+elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR")
set (SIMULATOR_FLAG true)
set (IOS_PLATFORM_LOCATION "AppleTvSimulator.platform")
set (XCODE_IOS_PLATFORM tvsimulator)
@@ -110,7 +114,7 @@ elseif (${IOS_PLATFORM} STREQUAL "TVSIMULATOR")
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-tvsimulator")
set (APPLE_TV True)
-else (${IOS_PLATFORM} STREQUAL "OS")
+else (IOS_PLATFORM STREQUAL "OS")
message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS, SIMULATOR, or WATCHOS.")
endif ()
@@ -132,12 +136,9 @@ if (IOS_DEPLOYMENT_TARGET)
endif()
set (CMAKE_SHARED_LINKER_FLAGS_INIT "-fapplication-extension")
-if (NOT SIMULATOR_FLAG)
- set (BITCODE "-fembed-bitcode")
-endif()
-set (CMAKE_C_FLAGS_INIT "${XCODE_IOS_PLATFORM_VERSION_FLAGS} ${BITCODE}")
+set (CMAKE_C_FLAGS_INIT "${XCODE_IOS_PLATFORM_VERSION_FLAGS}")
# Hidden visibilty is required for cxx on iOS
-set (CMAKE_CXX_FLAGS_INIT "${XCODE_IOS_PLATFORM_VERSION_FLAGS} ${BITCODE} -fvisibility-inlines-hidden")
+set (CMAKE_CXX_FLAGS_INIT "${XCODE_IOS_PLATFORM_VERSION_FLAGS} -fvisibility-inlines-hidden")
set (CMAKE_C_LINK_FLAGS "${XCODE_IOS_PLATFORM_VERSION_FLAGS} -fapplication-extension -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}")
set (CMAKE_CXX_LINK_FLAGS "${XCODE_IOS_PLATFORM_VERSION_FLAGS} -fapplication-extension -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}")
@@ -168,7 +169,7 @@ set (XCODE_PRE_43_ROOT "/Developer/Platforms/${IOS_PLATFORM_LOCATION}/Developer"
if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
if (EXISTS ${XCODE_POST_43_ROOT})
set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_POST_43_ROOT})
- elseif(EXISTS ${XCODE_PRE_43_ROOT})
+ elseif (EXISTS ${XCODE_PRE_43_ROOT})
set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_PRE_43_ROOT})
endif (EXISTS ${XCODE_POST_43_ROOT})
endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
@@ -191,23 +192,23 @@ set (CMAKE_IOS_SDK_ROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Location of the select
# Set the sysroot default to the most recent SDK
set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS support")
-# set the architecture for iOS
-if (IOS_PLATFORM STREQUAL "OS")
- set (IOS_ARCH "armv7;armv7s;arm64")
-elseif (IOS_PLATFORM STREQUAL "SIMULATOR")
- set (IOS_ARCH "i386;x86_64")
-elseif (IOS_PLATFORM STREQUAL "WATCHOS")
- set (IOS_ARCH "armv7k")
-elseif (IOS_PLATFORM STREQUAL "WATCHSIMULATOR")
- set (IOS_ARCH "i386")
-elseif (IOS_PLATFORM STREQUAL "TVOS")
- set (IOS_ARCH "arm64")
-elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR")
- set (IOS_ARCH "x86_64")
-else()
- message (WARNING "Unknown IOS_PLATFORM=<${IOS_PLATFORM}>")
+# Set the architectures unless specified manually with IOS_ARCH
+if (NOT DEFINED IOS_ARCH)
+ if (IOS_PLATFORM STREQUAL "OS")
+ set (IOS_ARCH "armv7;armv7s;arm64")
+ elseif (IOS_PLATFORM STREQUAL "SIMULATOR")
+ set (IOS_ARCH "i386;x86_64;arm64")
+ elseif (IOS_PLATFORM STREQUAL "WATCHOS")
+ set (IOS_ARCH "armv7k;arm64_32")
+ elseif (IOS_PLATFORM STREQUAL "WATCHSIMULATOR")
+ set (IOS_ARCH "i386;x86_64;arm64")
+ elseif (IOS_PLATFORM STREQUAL "TVOS")
+ set (IOS_ARCH "arm64")
+ elseif (IOS_PLATFORM STREQUAL "TVSIMULATOR")
+ set (IOS_ARCH "x86_64;arm64")
+ endif()
endif()
-message (STATUS ${IOS_ARCH})
+message (STATUS "The iOS architectures: ${IOS_ARCH}")
set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE STRING "Build architecture for iOS")
diff --git a/protocols/Telegram/tdlib/td/CMake/illumos.cmake b/protocols/Telegram/tdlib/td/CMake/illumos.cmake
new file mode 100644
index 0000000000..70583d1a82
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/CMake/illumos.cmake
@@ -0,0 +1,10 @@
+if (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
+ #
+ # Determine if the host is running an illumos distribution:
+ #
+ execute_process(COMMAND /usr/bin/uname -o OUTPUT_VARIABLE UNAME_O OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+ if (UNAME_O STREQUAL "illumos")
+ set(ILLUMOS 1)
+ endif()
+endif()
diff --git a/protocols/Telegram/tdlib/td/CMakeLists.txt b/protocols/Telegram/tdlib/td/CMakeLists.txt
index 5e2a626021..8b683c20b7 100644
--- a/protocols/Telegram/tdlib/td/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/CMakeLists.txt
@@ -1,21 +1,43 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
-project(TDLib VERSION 1.2.0 LANGUAGES CXX C)
+if (POLICY CMP0065)
+ # do not export symbols from executables
+ # affects compiler checks in project(), so must be set before it
+ cmake_policy(SET CMP0065 NEW)
+endif()
+
+project(TDLib VERSION 1.8.8 LANGUAGES CXX C)
-# Prevent in-source build
-get_filename_component(TD_REAL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH)
-get_filename_component(TD_REAL_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" REALPATH)
+if (NOT DEFINED CMAKE_MODULE_PATH)
+ set(CMAKE_MODULE_PATH "")
+endif()
+set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" "${CMAKE_MODULE_PATH}")
-if (TD_REAL_BINARY_DIR STREQUAL TD_REAL_SOURCE_DIR)
- message(" Out-of-source build should be used to build TDLib.")
- message(" You need to remove the files already created by CMake and")
- message(" rerun CMake from a new directory:")
- message(" rm -rf CMakeFiles CMakeCache.txt")
- message(" mkdir build")
- message(" cd build")
- message(" cmake ..")
- message(FATAL_ERROR "In-source build failed.")
+if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
+ set(CMAKE_INSTALL_LIBDIR "lib")
+endif()
+if (NOT DEFINED CMAKE_INSTALL_BINDIR)
+ set(CMAKE_INSTALL_BINDIR "bin")
+endif()
+if (NOT DEFINED CMAKE_INSTALL_INCLUDEDIR)
+ set(CMAKE_INSTALL_INCLUDEDIR "include")
+endif()
+
+if (POLICY CMP0054)
+ # do not expand quoted arguments
+ cmake_policy(SET CMP0054 NEW)
+endif()
+if (POLICY CMP0060)
+ # link libraries by full path
+ cmake_policy(SET CMP0060 NEW)
endif()
+if (POLICY CMP0074)
+ # use environment variables to find libraries
+ cmake_policy(SET CMP0074 NEW)
+endif()
+
+include(PreventInSourceBuild)
+prevent_in_source_build()
option(TD_ENABLE_JNI "Use \"ON\" to enable JNI-compatible TDLib API.")
option(TD_ENABLE_DOTNET "Use \"ON\" to enable generation of C++/CLI or C++/CX TDLib API bindings.")
@@ -24,15 +46,6 @@ if (TD_ENABLE_DOTNET AND (CMAKE_VERSION VERSION_LESS "3.1.0"))
message(FATAL_ERROR "CMake 3.1.0 or higher is required. You are running version ${CMAKE_VERSION}.")
endif()
-if (NOT DEFINED CMAKE_MODULE_PATH)
- set(CMAKE_MODULE_PATH "")
-endif()
-set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" "${CMAKE_MODULE_PATH}")
-
-set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
-
-set(CMAKE_POSITION_INDEPENDENT_CODE ON)
-
enable_testing()
if (POLICY CMP0069)
@@ -46,15 +59,15 @@ if (POLICY CMP0069)
# set_property(DIRECTORY PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) do not work?
string(REPLACE ";" " " CXX_FLAGS_IPO "${CMAKE_CXX_COMPILE_OPTIONS_IPO}")
message(STATUS "Use link time optimization CXX options: ${CXX_FLAGS_IPO}")
- set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${CXX_FLAGS_IPO}")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_FLAGS_IPO}")
string(REPLACE ";" " " C_FLAGS_IPO "${CMAKE_C_COMPILE_OPTIONS_IPO}")
message(STATUS "Use link time optimization C options: ${C_FLAGS_IPO}")
- set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${C_FLAGS_IPO}")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_FLAGS_IPO}")
string(REPLACE ";" " " LINK_FLAGS_IPO "${CMAKE_CXX_LINK_OPTIONS_IPO}")
message(STATUS "Use link time optimization linker options: ${LINK_FLAGS_IPO}")
- set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${LINK_FLAGS_IPO}")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINK_FLAGS_IPO}")
endif()
endif()
endif()
@@ -67,14 +80,14 @@ if (CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
else()
- message(STATUS "Could NOT find ccache")
+ message(STATUS "Could NOT find ccache (this is NOT an error)")
endif()
set(MEMPROF "" CACHE STRING "Use one of \"ON\", \"FAST\" or \"SAFE\" to enable memory profiling. \
-Works under Mac OS and Linux when compiled using glibc. \
+Works under macOS and Linux when compiled using glibc. \
In FAST mode stack is unwinded only using frame pointers, which may fail. \
In SAFE mode stack is unwinded using backtrace function from execinfo.h, which may be very slow. \
-By default both methods are used to achieve maximum speed and accuracy")
+By default both methods are used to achieve the maximum speed and accuracy")
if (EMSCRIPTEN)
# use prebuilt zlib
@@ -82,18 +95,23 @@ if (EMSCRIPTEN)
set(ZLIB_LIBRARIES)
set(ZLIB_INCLUDE_DIR)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s ALLOW_MEMORY_GROWTH=1 -s USE_ZLIB=1 -s MODULARIZE=1 -s EXTRA_EXPORTED_RUNTIME_METHODS=\"['FS']\"")
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Os -s ALLOW_MEMORY_GROWTH=1 -s USE_ZLIB=1 -s MODULARIZE=1 -s EXTRA_EXPORTED_RUNTIME_METHODS=\"['FS']\"")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s MEMFS_APPEND_TO_TYPED_ARRAYS=1 -s USE_ZLIB=1 -s MODULARIZE=1 \
+ -s EXPORT_NAME=\"'createTdwebModule'\" -s WEBSOCKET_URL=\"'wss:#'\" -s EXTRA_EXPORTED_RUNTIME_METHODS=\"['FS','cwrap']\" -lidbfs.js -lworkerfs.js")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s MEMFS_APPEND_TO_TYPED_ARRAYS=1 -s USE_ZLIB=1 -s MODULARIZE=1 \
+ -s EXPORT_NAME=\"'createTdwebModule'\" -s WEBSOCKET_URL=\"'wss:#'\" -s EXTRA_EXPORTED_RUNTIME_METHODS=\"['FS','cwrap']\" -lidbfs.js -lworkerfs.js")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1")
if (ASMJS)
set(TD_EMSCRIPTEN td_asmjs)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s WASM=0 -Wno-almost-asm")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s WASM=0 -Wno-almost-asm")
else()
set(TD_EMSCRIPTEN td_wasm)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s WASM=1")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s WASM=1")
endif()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --post-js ${CMAKE_CURRENT_SOURCE_DIR}/post.js")
endif()
if (NOT OPENSSL_FOUND)
@@ -103,38 +121,6 @@ if (OPENSSL_FOUND)
message(STATUS "Found OpenSSL: ${OPENSSL_INCLUDE_DIR} ${OPENSSL_LIBRARIES}")
endif()
-if (${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
- set(GCC 1)
-elseif (${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
- set(CLANG 1)
-elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL Intel)
- set(INTEL 1)
-elseif (NOT MSVC)
- message(FATAL_ERROR "Compiler isn't supported")
-endif()
-
-include(CheckCXXCompilerFlag)
-
-if (GCC OR CLANG OR INTEL)
- if (WIN32 AND INTEL)
- SET(STD14_FLAG /Qstd=c++14)
- else()
- SET(STD14_FLAG -std=c++14)
- endif()
- CHECK_CXX_COMPILER_FLAG(${STD14_FLAG} HAVE_STD14)
- if (NOT HAVE_STD14)
- string(REPLACE "c++14" "c++1y" STD14_FLAG "${STD14_FLAG}")
- CHECK_CXX_COMPILER_FLAG(${STD14_FLAG} HAVE_STD1Y)
- set(HAVE_STD14 ${HAVE_STD1Y})
- endif()
-elseif (MSVC)
- set(HAVE_STD14 MSVC_VERSION>=1900)
-endif()
-
-if (NOT HAVE_STD14)
- message(FATAL_ERROR "No C++14 support in the compiler. Please upgrade the compiler.")
-endif()
-
set(CMAKE_THREAD_PREFER_PTHREAD ON)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
@@ -143,86 +129,34 @@ if (THREADS_HAVE_PTHREAD_ARG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
+include(TdSetUpCompiler)
+td_set_up_compiler()
+
if (MSVC)
- if (CMAKE_CXX_FLAGS_DEBUG MATCHES "/RTC1")
- string(REPLACE "/RTC1" " " CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
- endif()
- add_definitions(-D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR- /W4 /wd4100 /wd4127 /wd4324 /wd4505 /wd4702")
-elseif (CLANG OR GCC)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD14_FLAG} -fno-omit-frame-pointer -fno-exceptions -fno-rtti")
- if (APPLE)
- set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-dead_strip,-x,-S")
- else()
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections")
- set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections -Wl,--exclude-libs,ALL")
+ option(TD_ENABLE_MULTI_PROCESSOR_COMPILATION "Use \"ON\" to enable multi-processor compilation.")
+
+ if (TD_ENABLE_MULTI_PROCESSOR_COMPILATION)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
endif()
+endif()
+if (CLANG OR GCC)
if (MEMPROF)
- CHECK_CXX_COMPILER_FLAG(-no-pie CXX_NO_PIE_FLAG)
+ include(CheckCXXCompilerFlag)
+ check_cxx_compiler_flag(-no-pie CXX_NO_PIE_FLAG)
if (CXX_NO_PIE_FLAG)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie")
elseif (APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-no_pie")
endif()
endif()
-elseif (INTEL)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${STD14_FLAG}")
endif()
-if (WIN32)
- add_definitions(-DNTDDI_VERSION=0x06020000 -DWINVER=0x0602 -D_WIN32_WINNT=0x0602 -DNOMINMAX -DUNICODE -D_UNICODE)
-endif()
-if (CYGWIN)
- add_definitions(-D_DEFAULT_SOURCE=1 -DFD_SETSIZE=4096)
-endif()
-
-if (NOT ANDROID) # _FILE_OFFSET_BITS is broken in ndk r15 and r15b and doesn't work prior to Android 7.0
- add_definitions(-D_FILE_OFFSET_BITS=64)
-endif()
-
-include(AddCXXCompilerFlag)
-if (NOT MSVC)
- add_cxx_compiler_flag("-Wall")
- add_cxx_compiler_flag("-Wextra")
- add_cxx_compiler_flag("-Wimplicit-fallthrough=2")
- add_cxx_compiler_flag("-Wpointer-arith")
- add_cxx_compiler_flag("-Wcast-qual")
- add_cxx_compiler_flag("-Wsign-compare")
- add_cxx_compiler_flag("-Wduplicated-branches")
- add_cxx_compiler_flag("-Wduplicated-cond")
- add_cxx_compiler_flag("-Walloc-zero")
- add_cxx_compiler_flag("-Wlogical-op")
- add_cxx_compiler_flag("-Wno-tautological-compare")
- add_cxx_compiler_flag("-Wpointer-arith")
- add_cxx_compiler_flag("-Wvla")
- add_cxx_compiler_flag("-Wnon-virtual-dtor")
- add_cxx_compiler_flag("-Wno-unused-parameter")
- add_cxx_compiler_flag("-Wconversion")
- add_cxx_compiler_flag("-Wno-sign-conversion")
- add_cxx_compiler_flag("-Wc++14-compat-pedantic")
- add_cxx_compiler_flag("-Qunused-arguments")
- add_cxx_compiler_flag("-Wodr")
- add_cxx_compiler_flag("-flto-odr-type-merging")
-
-# add_cxx_compiler_flag("-Werror")
-
-# add_cxx_compiler_flag("-Wcast-align")
-
-#std::int32_t <-> int and off_t <-> std::size_t/std::int64_t
-# add_cxx_compiler_flag("-Wuseless-cast")
-
-#external headers like openssl
-# add_cxx_compiler_flag("-Wzero-as-null-pointer-constant")
-endif()
-
-#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem /usr/include/c++/v1")
-#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
-#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
-#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
-#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
-#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=leak")
+include(GetGitRevisionDescription)
+get_git_head_revision(TD_GIT_REFSPEC TD_GIT_COMMIT_HASH)
+message(STATUS "Git state: ${TD_GIT_COMMIT_HASH}")
+
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/td/telegram/GitCommitHash.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/td/telegram/GitCommitHash.cpp" @ONLY)
add_subdirectory(tdtl)
@@ -234,10 +168,10 @@ if (NOT CMAKE_CROSSCOMPILING)
add_custom_target(prepare_cross_compiling DEPENDS tl_generate_common tdmime_auto tl_generate_json)
if (TD_ENABLE_DOTNET)
add_custom_target(remove_cpp_documentation
- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
- COMMAND remove_documentation ${TL_TD_AUTO} td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ COMMAND remove_documentation ${TL_TD_API_AUTO_SOURCE} td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h
COMMENT "Remove C++ documentation from sources"
- DEPENDS remove_documentation tl_generate_common generate_dotnet_api ${TL_TD_AUTO} td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h
+ DEPENDS remove_documentation tl_generate_common generate_dotnet_api ${TL_TD_API_AUTO_SOURCE} td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h
)
add_dependencies(prepare_cross_compiling generate_dotnet_api remove_cpp_documentation)
@@ -245,7 +179,7 @@ if (NOT CMAKE_CROSSCOMPILING)
endif()
if (NOT OPENSSL_FOUND)
- message(WARNING "Not found OpenSSL: skip TDLib, tdactor, tdnet, tddb")
+ message(WARNING "Can't find OpenSSL: stop TDLib building")
return()
endif()
@@ -253,7 +187,12 @@ if (NOT ZLIB_FOUND)
find_package(ZLIB)
endif()
if (NOT ZLIB_FOUND)
- message(WARNING "Not found zlib: skip TDLib, tdactor, tdnet, tddb")
+ message(WARNING "Can't find zlib: stop TDLib building")
+ return()
+endif()
+
+if (NOT TDUTILS_MIME_TYPE)
+ message(WARNING "Option TDUTILS_MIME_TYPE must not be disabled: stop TDLib building")
return()
endif()
@@ -274,84 +213,130 @@ endif()
get_directory_property(HAS_PARENT PARENT_DIRECTORY)
if (HAS_PARENT)
- set(TL_TD_AUTO_INCLUDES ${TL_TD_AUTO_INCLUDES} PARENT_SCOPE)
- set(TL_TD_API_TLO ${TL_TD_API_TLO} PARENT_SCOPE)
- set(TL_TD_JSON_AUTO ${TL_TD_JSON_AUTO} PARENT_SCOPE)
- set(TD_TEST_SOURCE ${TD_TEST_SOURCE} PARENT_SCOPE)
+ set(TL_TD_JSON_AUTO ${TL_TD_JSON_AUTO_SOURCE} PARENT_SCOPE) # used in tdbot
+ set(TD_TEST_SOURCE ${TD_TEST_SOURCE} PARENT_SCOPE) # used to build tests
endif()
#SOURCE SETS
-set_source_files_properties(${TL_TD_AUTO} PROPERTIES GENERATED TRUE)
-if (TD_ENABLE_JNI)
- set(TL_JNI_OBJECT
+set_source_files_properties(${TL_TD_API_AUTO_SOURCE} PROPERTIES GENERATED TRUE)
+if (TD_ENABLE_JNI OR ANDROID)
+ set(TL_JNI_OBJECT_SOURCE
td/tl/tl_jni_object.cpp
td/tl/tl_jni_object.h
)
else()
- set(TL_JNI_OBJECT)
+ set(TL_JNI_OBJECT_SOURCE)
endif()
+set(TL_TD_API_SOURCE
+ ${TL_TD_API_AUTO_SOURCE}
+ ${TL_JNI_OBJECT_SOURCE}
+ td/tl/TlObject.h
+)
+
+set_source_files_properties(${TL_TD_AUTO_SOURCE} PROPERTIES GENERATED TRUE)
set(TL_TD_SCHEME_SOURCE
- ${TL_TD_AUTO}
- ${TL_JNI_OBJECT}
+ ${TL_TD_AUTO_SOURCE}
td/tl/TlObject.h
td/tl/tl_object_parse.h
td/tl/tl_object_store.h
)
-set_source_files_properties(${TL_TD_JSON_AUTO} PROPERTIES GENERATED TRUE)
-set(TL_TD_JSON
- ${TL_TD_JSON_AUTO}
+set_source_files_properties(${TL_TD_JSON_AUTO_SOURCE} PROPERTIES GENERATED TRUE)
+set(TL_TD_JSON_SOURCE
+ ${TL_TD_JSON_AUTO_SOURCE}
td/tl/tl_json.h
)
-set_source_files_properties(${TL_C_AUTO} PROPERTIES GENERATED TRUE)
+set_source_files_properties(${TL_C_AUTO_SOURCE} PROPERTIES GENERATED TRUE)
set(TL_C_SCHEME_SOURCE
- ${TL_C_AUTO}
+ ${TL_C_AUTO_SOURCE}
)
-set_source_files_properties(${TL_DOTNET_AUTO} PROPERTIES GENERATED TRUE)
+set_source_files_properties(${TL_DOTNET_AUTO_SOURCE} PROPERTIES GENERATED TRUE)
set(TL_DOTNET_SCHEME_SOURCE
- ${TL_DOTNET_AUTO}
+ ${TL_DOTNET_AUTO_SOURCE}
td/tl/tl_dotnet_object.h
)
set(TDLIB_SOURCE
td/mtproto/AuthData.cpp
- td/mtproto/crypto.cpp
+ td/mtproto/ConnectionManager.cpp
+ td/mtproto/DhHandshake.cpp
td/mtproto/Handshake.cpp
td/mtproto/HandshakeActor.cpp
td/mtproto/HttpTransport.cpp
td/mtproto/IStreamTransport.cpp
+ td/mtproto/KDF.cpp
+ td/mtproto/Ping.cpp
+ td/mtproto/PingConnection.cpp
+ td/mtproto/ProxySecret.cpp
td/mtproto/RawConnection.cpp
+ td/mtproto/RSA.cpp
td/mtproto/SessionConnection.cpp
td/mtproto/TcpTransport.cpp
+ td/mtproto/TlsInit.cpp
+ td/mtproto/TlsReaderByteFlow.cpp
td/mtproto/Transport.cpp
td/mtproto/utils.cpp
+ td/telegram/Account.cpp
td/telegram/AnimationsManager.cpp
+ td/telegram/Application.cpp
+ td/telegram/AttachMenuManager.cpp
td/telegram/AudiosManager.cpp
td/telegram/AuthManager.cpp
+ td/telegram/AutoDownloadSettings.cpp
+ td/telegram/BackgroundManager.cpp
+ td/telegram/BackgroundType.cpp
+ td/telegram/BotCommand.cpp
+ td/telegram/BotCommandScope.cpp
+ td/telegram/BotMenuButton.cpp
+ td/telegram/BotMenuButton.h
td/telegram/CallActor.cpp
td/telegram/CallDiscardReason.cpp
td/telegram/CallManager.cpp
td/telegram/CallbackQueriesManager.cpp
+ td/telegram/ChannelParticipantFilter.cpp
+ td/telegram/ChatReactions.cpp
td/telegram/ClientActor.cpp
td/telegram/ConfigManager.cpp
- td/telegram/ConfigShared.cpp
+ td/telegram/ConnectionState.cpp
td/telegram/Contact.cpp
td/telegram/ContactsManager.cpp
+ td/telegram/CountryInfoManager.cpp
td/telegram/DelayDispatcher.cpp
+ td/telegram/Dependencies.cpp
td/telegram/DeviceTokenManager.cpp
td/telegram/DhCache.cpp
+ td/telegram/DialogAction.cpp
+ td/telegram/DialogActionBar.cpp
+ td/telegram/DialogAdministrator.cpp
td/telegram/DialogDb.cpp
+ td/telegram/DialogEventLog.cpp
+ td/telegram/DialogFilter.cpp
td/telegram/DialogId.cpp
+ td/telegram/DialogInviteLink.cpp
+ td/telegram/DialogLocation.cpp
+ td/telegram/DialogNotificationSettings.cpp
td/telegram/DialogParticipant.cpp
+ td/telegram/DialogParticipantFilter.cpp
+ td/telegram/DialogSource.cpp
+ td/telegram/Dimensions.cpp
+ td/telegram/Document.cpp
td/telegram/DocumentsManager.cpp
+ td/telegram/DownloadManager.cpp
+ td/telegram/DownloadManagerCallback.cpp
+ td/telegram/DraftMessage.cpp
+ td/telegram/EmailVerification.cpp
+ td/telegram/EmojiStatus.cpp
+ td/telegram/FileReferenceManager.cpp
+ td/telegram/files/FileBitmask.cpp
td/telegram/files/FileDb.cpp
td/telegram/files/FileDownloader.cpp
+ td/telegram/files/FileEncryptionKey.cpp
td/telegram/files/FileFromBytes.cpp
td/telegram/files/FileGcParameters.cpp
td/telegram/files/FileGcWorker.cpp
@@ -363,17 +348,48 @@ set(TDLIB_SOURCE
td/telegram/files/FileManager.cpp
td/telegram/files/FileStats.cpp
td/telegram/files/FileStatsWorker.cpp
+ td/telegram/files/FileType.cpp
td/telegram/files/FileUploader.cpp
td/telegram/files/PartsManager.cpp
td/telegram/files/ResourceManager.cpp
+ td/telegram/ForumTopic.cpp
+ td/telegram/ForumTopicEditedData.cpp
+ td/telegram/ForumTopicIcon.cpp
+ td/telegram/ForumTopicInfo.cpp
+ td/telegram/ForumTopicManager.cpp
td/telegram/Game.cpp
+ td/telegram/GameManager.cpp
td/telegram/Global.cpp
+ td/telegram/GroupCallManager.cpp
+ td/telegram/GroupCallParticipant.cpp
+ td/telegram/GroupCallParticipantOrder.cpp
+ td/telegram/GroupCallVideoPayload.cpp
td/telegram/HashtagHints.cpp
td/telegram/InlineQueriesManager.cpp
+ td/telegram/InputDialogId.cpp
+ td/telegram/InputGroupCallId.cpp
+ td/telegram/InputInvoice.cpp
+ td/telegram/InputMessageText.cpp
+ td/telegram/JsonValue.cpp
+ td/telegram/LanguagePackManager.cpp
+ td/telegram/LinkManager.cpp
td/telegram/Location.cpp
+ td/telegram/logevent/LogEventHelper.cpp
+ td/telegram/Logging.cpp
+ td/telegram/MessageContent.cpp
+ td/telegram/MessageContentType.cpp
+ td/telegram/MessageDb.cpp
td/telegram/MessageEntity.cpp
- td/telegram/MessagesDb.cpp
+ td/telegram/MessageExtendedMedia.cpp
+ td/telegram/MessageId.cpp
+ td/telegram/MessageReaction.cpp
+ td/telegram/MessageReplyHeader.cpp
+ td/telegram/MessageReplyInfo.cpp
+ td/telegram/MessageSearchFilter.cpp
+ td/telegram/MessageSender.cpp
td/telegram/MessagesManager.cpp
+ td/telegram/MessageThreadDb.cpp
+ td/telegram/MessageTtl.cpp
td/telegram/misc.cpp
td/telegram/net/AuthDataShared.cpp
td/telegram/net/ConnectionCreator.cpp
@@ -382,81 +398,174 @@ set(TDLIB_SOURCE
td/telegram/net/MtprotoHeader.cpp
td/telegram/net/NetActor.cpp
td/telegram/net/NetQuery.cpp
- td/telegram/net/NetQueryCounter.cpp
td/telegram/net/NetQueryCreator.cpp
td/telegram/net/NetQueryDelayer.cpp
td/telegram/net/NetQueryDispatcher.cpp
+ td/telegram/net/NetQueryStats.cpp
td/telegram/net/NetStatsManager.cpp
+ td/telegram/net/Proxy.cpp
td/telegram/net/PublicRsaKeyShared.cpp
td/telegram/net/PublicRsaKeyWatchdog.cpp
td/telegram/net/Session.cpp
td/telegram/net/SessionProxy.cpp
td/telegram/net/SessionMultiProxy.cpp
+ td/telegram/NewPasswordState.cpp
+ td/telegram/NotificationManager.cpp
+ td/telegram/NotificationSettingsScope.cpp
+ td/telegram/NotificationSettingsManager.cpp
+ td/telegram/NotificationSound.cpp
+ td/telegram/NotificationType.cpp
+ td/telegram/OptionManager.cpp
+ td/telegram/OrderInfo.cpp
td/telegram/Payments.cpp
td/telegram/PasswordManager.cpp
+ td/telegram/PhoneNumberManager.cpp
td/telegram/PrivacyManager.cpp
td/telegram/Photo.cpp
+ td/telegram/PhotoSize.cpp
+ td/telegram/PhotoSizeSource.cpp
+ td/telegram/PollManager.cpp
+ td/telegram/Premium.cpp
+ td/telegram/PremiumGiftOption.cpp
+ td/telegram/QueryCombiner.cpp
+ td/telegram/RecentDialogList.cpp
td/telegram/ReplyMarkup.cpp
+ td/telegram/ReportReason.cpp
+ td/telegram/RestrictionReason.cpp
+ td/telegram/ScopeNotificationSettings.cpp
td/telegram/SecretChatActor.cpp
td/telegram/SecretChatDb.cpp
td/telegram/SecretChatsManager.cpp
+ td/telegram/SecretInputMedia.cpp
+ td/telegram/SecureManager.cpp
+ td/telegram/SecureStorage.cpp
+ td/telegram/SecureValue.cpp
+ td/telegram/SendCodeHelper.cpp
+ td/telegram/SentEmailCode.cpp
td/telegram/SequenceDispatcher.cpp
+ td/telegram/SpecialStickerSetType.cpp
+ td/telegram/SponsoredMessageManager.cpp
td/telegram/StateManager.cpp
+ td/telegram/StickerFormat.cpp
td/telegram/StickersManager.cpp
+ td/telegram/StickerType.cpp
td/telegram/StorageManager.cpp
+ td/telegram/SuggestedAction.cpp
+ td/telegram/Support.cpp
td/telegram/Td.cpp
td/telegram/TdDb.cpp
+ td/telegram/TermsOfService.cpp
+ td/telegram/ThemeManager.cpp
+ td/telegram/TopDialogCategory.cpp
td/telegram/TopDialogManager.cpp
+ td/telegram/TranscriptionInfo.cpp
td/telegram/UpdatesManager.cpp
+ td/telegram/Usernames.cpp
+ td/telegram/Venue.cpp
td/telegram/VideoNotesManager.cpp
td/telegram/VideosManager.cpp
td/telegram/VoiceNotesManager.cpp
+ td/telegram/WebPageBlock.cpp
td/telegram/WebPagesManager.cpp
td/mtproto/AuthData.h
td/mtproto/AuthKey.h
- td/mtproto/crypto.h
+ td/mtproto/ConnectionManager.h
td/mtproto/CryptoStorer.h
+ td/mtproto/DhCallback.h
+ td/mtproto/DhHandshake.h
td/mtproto/Handshake.h
td/mtproto/HandshakeActor.h
td/mtproto/HandshakeConnection.h
td/mtproto/HttpTransport.h
td/mtproto/IStreamTransport.h
+ td/mtproto/KDF.h
+ td/mtproto/MtprotoQuery.h
td/mtproto/NoCryptoStorer.h
+ td/mtproto/PacketInfo.h
td/mtproto/PacketStorer.h
+ td/mtproto/Ping.h
td/mtproto/PingConnection.h
+ td/mtproto/ProxySecret.h
td/mtproto/RawConnection.h
+ td/mtproto/RSA.h
td/mtproto/SessionConnection.h
td/mtproto/TcpTransport.h
+ td/mtproto/TlsInit.h
+ td/mtproto/TlsReaderByteFlow.h
td/mtproto/Transport.h
+ td/mtproto/TransportType.h
td/mtproto/utils.h
td/telegram/AccessRights.h
+ td/telegram/Account.h
+ td/telegram/AffectedHistory.h
td/telegram/AnimationsManager.h
+ td/telegram/Application.h
+ td/telegram/AttachMenuManager.h
td/telegram/AudiosManager.h
td/telegram/AuthManager.h
+ td/telegram/AutoDownloadSettings.h
+ td/telegram/BackgroundId.h
+ td/telegram/BackgroundManager.h
+ td/telegram/BackgroundType.h
+ td/telegram/BotCommand.h
+ td/telegram/BotCommandScope.h
td/telegram/CallActor.h
td/telegram/CallDiscardReason.h
td/telegram/CallId.h
td/telegram/CallManager.h
td/telegram/CallbackQueriesManager.h
+ td/telegram/ChainId.h
td/telegram/ChannelId.h
+ td/telegram/ChannelParticipantFilter.h
+ td/telegram/ChannelType.h
td/telegram/ChatId.h
+ td/telegram/ChatReactions.h
td/telegram/ClientActor.h
td/telegram/ConfigManager.h
- td/telegram/ConfigShared.h
+ td/telegram/ConnectionState.h
td/telegram/Contact.h
td/telegram/ContactsManager.h
+ td/telegram/CountryInfoManager.h
+ td/telegram/CustomEmojiId.h
td/telegram/DelayDispatcher.h
+ td/telegram/Dependencies.h
td/telegram/DeviceTokenManager.h
td/telegram/DhCache.h
td/telegram/DhConfig.h
+ td/telegram/DialogAction.h
+ td/telegram/DialogActionBar.h
+ td/telegram/DialogAdministrator.h
+ td/telegram/DialogDate.h
td/telegram/DialogDb.h
+ td/telegram/DialogEventLog.h
+ td/telegram/DialogFilter.h
+ td/telegram/DialogFilterId.h
td/telegram/DialogId.h
+ td/telegram/DialogInviteLink.h
+ td/telegram/DialogListId.h
+ td/telegram/DialogLocation.h
+ td/telegram/DialogNotificationSettings.h
td/telegram/DialogParticipant.h
+ td/telegram/DialogParticipantFilter.h
+ td/telegram/DialogSource.h
+ td/telegram/Dimensions.h
+ td/telegram/Document.h
td/telegram/DocumentsManager.h
+ td/telegram/DownloadManager.h
+ td/telegram/DownloadManagerCallback.h
+ td/telegram/DraftMessage.h
+ td/telegram/EmailVerification.h
+ td/telegram/EmojiStatus.h
+ td/telegram/EncryptedFile.h
+ td/telegram/FileReferenceManager.h
+ td/telegram/files/FileBitmask.h
+ td/telegram/files/FileData.h
td/telegram/files/FileDb.h
+ td/telegram/files/FileDbId.h
td/telegram/files/FileDownloader.h
+ td/telegram/files/FileEncryptionKey.h
td/telegram/files/FileFromBytes.h
td/telegram/files/FileGcParameters.h
td/telegram/files/FileGcWorker.h
@@ -469,23 +578,63 @@ set(TDLIB_SOURCE
td/telegram/files/FileLoadManager.h
td/telegram/files/FileLocation.h
td/telegram/files/FileManager.h
+ td/telegram/files/FileSourceId.h
td/telegram/files/FileStats.h
td/telegram/files/FileStatsWorker.h
+ td/telegram/files/FileType.h
td/telegram/files/FileUploader.h
td/telegram/files/PartsManager.h
td/telegram/files/ResourceManager.h
td/telegram/files/ResourceState.h
+ td/telegram/FolderId.h
+ td/telegram/ForumTopic.h
+ td/telegram/ForumTopicEditedData.h
+ td/telegram/ForumTopicIcon.h
+ td/telegram/ForumTopicInfo.h
+ td/telegram/ForumTopicManager.h
+ td/telegram/FullMessageId.h
td/telegram/Game.h
+ td/telegram/GameManager.h
+ td/telegram/GitCommitHash.h
td/telegram/Global.h
+ td/telegram/GroupCallId.h
+ td/telegram/GroupCallManager.h
+ td/telegram/GroupCallParticipant.h
+ td/telegram/GroupCallParticipantOrder.h
+ td/telegram/GroupCallVideoPayload.h
td/telegram/HashtagHints.h
td/telegram/InlineQueriesManager.h
+ td/telegram/InputDialogId.h
+ td/telegram/InputGroupCallId.h
+ td/telegram/InputInvoice.h
+ td/telegram/InputMessageText.h
+ td/telegram/JsonValue.h
+ td/telegram/LabeledPricePart.h
+ td/telegram/LanguagePackManager.h
+ td/telegram/LinkManager.h
td/telegram/Location.h
td/telegram/logevent/LogEvent.h
+ td/telegram/logevent/LogEventHelper.h
td/telegram/logevent/SecretChatEvent.h
+ td/telegram/Logging.h
+ td/telegram/MessageContent.h
+ td/telegram/MessageContentType.h
+ td/telegram/MessageCopyOptions.h
+ td/telegram/MessageDb.h
td/telegram/MessageEntity.h
+ td/telegram/MessageExtendedMedia.h
td/telegram/MessageId.h
- td/telegram/MessagesDb.h
+ td/telegram/MessageLinkInfo.h
+ td/telegram/MessageReaction.h
+ td/telegram/MessageReplyHeader.h
+ td/telegram/MessageReplyInfo.h
+ td/telegram/MessageSearchFilter.h
+ td/telegram/MessageSender.h
td/telegram/MessagesManager.h
+ td/telegram/MessageThreadDb.h
+ td/telegram/MessageThreadInfo.h
+ td/telegram/MessageTtl.h
+ td/telegram/MinChannel.h
td/telegram/misc.h
td/telegram/net/AuthDataShared.h
td/telegram/net/ConnectionCreator.h
@@ -500,60 +649,145 @@ set(TDLIB_SOURCE
td/telegram/net/NetQueryCreator.h
td/telegram/net/NetQueryDelayer.h
td/telegram/net/NetQueryDispatcher.h
+ td/telegram/net/NetQueryStats.h
td/telegram/net/NetStatsManager.h
td/telegram/net/NetType.h
+ td/telegram/net/Proxy.h
td/telegram/net/PublicRsaKeyShared.h
td/telegram/net/PublicRsaKeyWatchdog.h
td/telegram/net/Session.h
td/telegram/net/SessionProxy.h
td/telegram/net/SessionMultiProxy.h
td/telegram/net/TempAuthKeyWatchdog.h
+ td/telegram/NewPasswordState.h
+ td/telegram/Notification.h
+ td/telegram/NotificationGroupId.h
+ td/telegram/NotificationGroupKey.h
+ td/telegram/NotificationGroupType.h
+ td/telegram/NotificationId.h
+ td/telegram/NotificationManager.h
+ td/telegram/NotificationSettingsScope.h
+ td/telegram/NotificationSettingsManager.h
+ td/telegram/NotificationSound.h
+ td/telegram/NotificationSoundType.h
+ td/telegram/NotificationType.h
+ td/telegram/OptionManager.h
+ td/telegram/OrderInfo.h
td/telegram/PasswordManager.h
td/telegram/Payments.h
+ td/telegram/PhoneNumberManager.h
td/telegram/Photo.h
+ td/telegram/PhotoFormat.h
+ td/telegram/PhotoSize.h
+ td/telegram/PhotoSizeSource.h
+ td/telegram/PollId.h
+ td/telegram/PollManager.h
+ td/telegram/Premium.h
+ td/telegram/PremiumGiftOption.h
td/telegram/PrivacyManager.h
td/telegram/PtsManager.h
+ td/telegram/PublicDialogType.h
+ td/telegram/QueryCombiner.h
+ td/telegram/RecentDialogList.h
td/telegram/ReplyMarkup.h
+ td/telegram/ReportReason.h
+ td/telegram/RequestActor.h
+ td/telegram/RestrictionReason.h
+ td/telegram/ScheduledServerMessageId.h
+ td/telegram/ScopeNotificationSettings.h
td/telegram/SecretChatActor.h
td/telegram/SecretChatId.h
td/telegram/SecretChatDb.h
+ td/telegram/SecretChatLayer.h
td/telegram/SecretChatsManager.h
td/telegram/SecretInputMedia.h
+ td/telegram/SecureManager.h
+ td/telegram/SecureStorage.h
+ td/telegram/SecureValue.h
+ td/telegram/SendCodeHelper.h
+ td/telegram/SentEmailCode.h
td/telegram/SequenceDispatcher.h
+ td/telegram/ServerMessageId.h
+ td/telegram/SetWithPosition.h
+ td/telegram/SpecialStickerSetType.h
+ td/telegram/SponsoredMessageManager.h
td/telegram/StateManager.h
+ td/telegram/StickerFormat.h
+ td/telegram/StickerSetId.h
td/telegram/StickersManager.h
+ td/telegram/StickerType.h
td/telegram/StorageManager.h
+ td/telegram/SuggestedAction.h
+ td/telegram/Support.h
td/telegram/Td.h
td/telegram/TdCallback.h
td/telegram/TdDb.h
td/telegram/TdParameters.h
+ td/telegram/TermsOfService.h
+ td/telegram/ThemeManager.h
+ td/telegram/TopDialogCategory.h
td/telegram/TopDialogManager.h
+ td/telegram/TranscriptionInfo.h
td/telegram/UniqueId.h
td/telegram/UpdatesManager.h
td/telegram/UserId.h
+ td/telegram/Usernames.h
+ td/telegram/Venue.h
td/telegram/Version.h
td/telegram/VideoNotesManager.h
td/telegram/VideosManager.h
td/telegram/VoiceNotesManager.h
+ td/telegram/WebPageBlock.h
td/telegram/WebPageId.h
td/telegram/WebPagesManager.h
td/telegram/AnimationsManager.hpp
td/telegram/AudiosManager.hpp
td/telegram/AuthManager.hpp
+ td/telegram/BackgroundType.hpp
+ td/telegram/DialogNotificationSettings.hpp
+ td/telegram/DialogFilter.hpp
+ td/telegram/Dimensions.hpp
+ td/telegram/Document.hpp
td/telegram/DocumentsManager.hpp
+ td/telegram/DraftMessage.hpp
+ td/telegram/FileReferenceManager.hpp
+ td/telegram/files/FileData.hpp
td/telegram/files/FileId.hpp
+ td/telegram/files/FileLocation.hpp
td/telegram/files/FileManager.hpp
+ td/telegram/files/FileSourceId.hpp
+ td/telegram/ForumTopicEditedData.hpp
+ td/telegram/ForumTopicIcon.hpp
td/telegram/Game.hpp
- td/telegram/Payments.hpp
+ td/telegram/InputInvoice.hpp
+ td/telegram/InputMessageText.hpp
+ td/telegram/MessageEntity.hpp
+ td/telegram/MessageExtendedMedia.hpp
+ td/telegram/MessageReaction.hpp
+ td/telegram/MessageReplyInfo.hpp
+ td/telegram/MinChannel.hpp
+ td/telegram/OrderInfo.hpp
td/telegram/Photo.hpp
+ td/telegram/PhotoSize.hpp
+ td/telegram/PhotoSizeSource.hpp
+ td/telegram/PollId.hpp
+ td/telegram/PollManager.hpp
+ td/telegram/PremiumGiftOption.hpp
td/telegram/ReplyMarkup.hpp
+ td/telegram/ScopeNotificationSettings.hpp
+ td/telegram/SecureValue.hpp
+ td/telegram/SendCodeHelper.hpp
+ td/telegram/StickerSetId.hpp
td/telegram/StickersManager.hpp
+ td/telegram/TranscriptionInfo.hpp
td/telegram/VideoNotesManager.hpp
td/telegram/VideosManager.hpp
td/telegram/VoiceNotesManager.hpp
${TL_TD_SCHEME_SOURCE}
+
+ ${CMAKE_CURRENT_BINARY_DIR}/td/telegram/GitCommitHash.cpp
)
set(MEMPROF_SOURCE
@@ -561,20 +795,9 @@ set(MEMPROF_SOURCE
memprof/memprof.h
)
-#RULES
-
-file(MAKE_DIRECTORY auto)
-
-if (WIN32)
- set(GIT_COMMIT_CMD powershell -ExecutionPolicy ByPass ./gen_git_commit_h.ps1)
-else()
- set(GIT_COMMIT_CMD ./gen_git_commit_h.sh)
-endif()
-
-add_custom_target(git_commit ALL
- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
- COMMAND ${GIT_COMMIT_CMD}
- COMMENT "Generate git_commit.h"
+set(MEMPROF_STAT_SOURCE
+ memprof/memprof_stat.cpp
+ memprof/memprof_stat.h
)
#LIBRARIES
@@ -589,25 +812,44 @@ if (MEMPROF)
target_compile_definitions(memprof PRIVATE -DUSE_MEMPROF_SAFE=1)
elseif (MEMPROF STREQUAL "FAST")
target_compile_definitions(memprof PRIVATE -DUSE_MEMPROF_FAST=1)
- elseif (NOT ${MEMPROF})
+ elseif (NOT MEMPROF)
message(FATAL_ERROR "Unsupported MEMPROF value \"${MEMPROF}\"")
endif()
endif()
+add_library(memprof_stat EXCLUDE_FROM_ALL STATIC ${MEMPROF_STAT_SOURCE})
+target_include_directories(memprof_stat PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+target_link_libraries(memprof_stat PRIVATE tdutils)
-# tdcore - mostly internal TDLib interface. One should use tdactor for interactions with it.
-add_library(tdcore STATIC ${TDLIB_SOURCE})
-target_include_directories(tdcore PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDES}>)
-target_include_directories(tdcore SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
-target_link_libraries(tdcore PUBLIC tdactor tdutils tdnet tddb PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
+
+add_library(tdapi ${TL_TD_API_SOURCE})
+target_include_directories(tdapi PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> INTERFACE $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDE_DIR}>)
+target_link_libraries(tdapi PRIVATE tdutils)
if (TD_ENABLE_JNI AND NOT ANDROID) # jni is available by default on Android
if (NOT JNI_FOUND)
find_package(JNI REQUIRED)
endif()
message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}")
- target_include_directories(tdcore PUBLIC ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2})
- target_link_libraries(tdcore PUBLIC ${JAVA_JVM_LIBRARY})
+ target_include_directories(tdapi PUBLIC ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2})
+ target_link_libraries(tdapi PUBLIC ${JAVA_JVM_LIBRARY})
+endif()
+
+if (NOT CMAKE_CROSSCOMPILING)
+ add_dependencies(tdapi tl_generate_common)
+endif()
+
+# tdcore - mostly internal TDLib interface. One should use tdactor for interactions with it.
+add_library(tdcore STATIC ${TDLIB_SOURCE})
+target_include_directories(tdcore PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDE_DIR}>)
+target_include_directories(tdcore SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
+target_link_libraries(tdcore PUBLIC tdapi tdactor tdutils tdnet tddb PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
+if (WIN32)
+ if (MINGW)
+ target_link_libraries(tdcore PRIVATE ws2_32 mswsock crypt32)
+ else()
+ target_link_libraries(tdcore PRIVATE ws2_32 Mswsock Crypt32)
+ endif()
endif()
if (NOT CMAKE_CROSSCOMPILING)
@@ -623,14 +865,8 @@ endif()
add_library(tdclient td/telegram/Client.cpp td/telegram/Client.h td/telegram/Log.cpp td/telegram/Log.h)
target_include_directories(tdclient PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
- $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDES}>
)
-target_link_libraries(tdclient PRIVATE tdcore)
-
-if (TD_ENABLE_JNI AND NOT ANDROID) # jni is available by default on Android
- target_include_directories(tdclient PUBLIC ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2})
- target_link_libraries(tdclient PUBLIC ${JAVA_JVM_LIBRARY})
-endif()
+target_link_libraries(tdclient PUBLIC tdapi PRIVATE tdcore)
if (TD_ENABLE_DOTNET)
add_library(tddotnet SHARED
@@ -638,18 +874,17 @@ if (TD_ENABLE_DOTNET)
td/telegram/LogDotNet.cpp
${TL_DOTNET_SCHEME_SOURCE}
)
- set(VCPKG_APPLOCAL_LIBRARY_DEPS ON)
set_target_properties(tddotnet PROPERTIES OUTPUT_NAME Telegram.Td)
target_link_libraries(tddotnet PRIVATE tdclient tdutils)
target_include_directories(tddotnet PUBLIC
- $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDES}>
+ $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDE_DIR}>
)
if (NOT CMAKE_CROSSCOMPILING)
add_dependencies(tddotnet generate_dotnet_api)
endif()
target_compile_options(tddotnet PRIVATE "/doc")
- if ("${CMAKE_SYSTEM_NAME}" STREQUAL "WindowsStore")
+ if (CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
set_target_properties(tddotnet PROPERTIES VS_WINRT_COMPONENT "true")
target_compile_options(tddotnet PUBLIC "/ZW")
else()
@@ -659,19 +894,19 @@ if (TD_ENABLE_DOTNET)
endif()
# tdc - TDLib interface in pure c.
-add_library(tdc STATIC ${TL_C_SCHEME_SOURCE} td/telegram/td_c_client.cpp td/telegram/td_c_client.h)
+add_library(tdc STATIC EXCLUDE_FROM_ALL ${TL_C_SCHEME_SOURCE} td/telegram/td_c_client.cpp td/telegram/td_c_client.h)
target_include_directories(tdc PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
- $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDES}>)
+ $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDE_DIR}>)
target_link_libraries(tdc PRIVATE tdclient tdutils)
if (NOT CMAKE_CROSSCOMPILING)
add_dependencies(tdc tl_generate_c)
endif()
-add_library(tdjson_private STATIC ${TL_TD_JSON} td/telegram/ClientJson.cpp td/telegram/ClientJson.h)
+add_library(tdjson_private STATIC ${TL_TD_JSON_SOURCE} td/telegram/ClientJson.cpp td/telegram/ClientJson.h)
target_include_directories(tdjson_private PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
- $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDES}>)
+ $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDE_DIR}>)
target_link_libraries(tdjson_private PUBLIC tdclient tdutils)
if (NOT CMAKE_CROSSCOMPILING)
add_dependencies(tdjson_private tl_generate_common tl_generate_json)
@@ -702,48 +937,43 @@ target_include_directories(tdjson_static PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
-set(BIGOBJ)
-if (WIN32 OR CYGWIN)
- if (MSVC)
- set(BIGOBJ "/bigobj")
- elseif (GCC)
- set(BIGOBJ "-Wa,-mbig-obj")
- endif()
-endif()
-if (BIGOBJ)
- target_compile_options(tdc PUBLIC ${BIGOBJ})
- target_compile_options(tdcore PUBLIC ${BIGOBJ})
- target_compile_options(tdclient PUBLIC ${BIGOBJ})
- target_compile_options(tdjson PUBLIC ${BIGOBJ})
- target_compile_options(tdjson_static PUBLIC ${BIGOBJ})
- if (TD_ENABLE_DOTNET)
- target_compile_options(tddotnet PUBLIC "/bigobj")
- endif()
-endif()
-
if (EMSCRIPTEN)
set(TD_EMSCRIPTEN_SRC td/telegram/td_emscripten.cpp)
add_executable(${TD_EMSCRIPTEN} ${TD_EMSCRIPTEN_SRC})
target_include_directories(${TD_EMSCRIPTEN} PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
- target_link_libraries(${TD_EMSCRIPTEN} PRIVATE tdjson_static)
+ target_link_libraries(${TD_EMSCRIPTEN} PRIVATE tdjson_static tdactor)
endif()
#EXECUTABLES
if (NOT CMAKE_CROSSCOMPILING)
- add_executable(tg_cli td/telegram/cli.cpp ${TL_TD_JSON})
+ add_executable(tg_cli td/telegram/cli.cpp ${TL_TD_JSON_SOURCE})
if (NOT READLINE_FOUND)
find_package(Readline)
endif()
if (NOT READLINE_FOUND)
- message(STATUS "Could NOT find Readline")
+ message(STATUS "Could NOT find Readline (this is NOT an error)")
else()
message(STATUS "Found Readline: ${READLINE_INCLUDE_DIR} ${READLINE_LIBRARY}")
- target_link_libraries(tg_cli PRIVATE ${READLINE_LIBRARY})
- target_include_directories(tg_cli SYSTEM PRIVATE ${READLINE_INCLUDE_DIR})
- target_compile_definitions(tg_cli PRIVATE -DUSE_READLINE=1)
+ if (NOT USABLE_READLINE_FOUND)
+ set(CMAKE_REQUIRED_INCLUDES "${READLINE_INCLUDE_DIR}")
+ set(CMAKE_REQUIRED_LIBRARIES "${READLINE_LIBRARY}")
+ include(CheckCXXSourceCompiles)
+ unset(USABLE_READLINE_FOUND CACHE)
+ check_cxx_source_compiles("#include <stdio.h>\n#include <readline/readline.h>\nint main() { rl_free(0); }" USABLE_READLINE_FOUND)
+ if (NOT USABLE_READLINE_FOUND)
+ message(STATUS "Found Readline is too old, ignore it (this is NOT an error)")
+ unset(READLINE_INCLUDE_DIR CACHE)
+ unset(READLINE_LIBRARY CACHE)
+ endif()
+ endif()
+ if (USABLE_READLINE_FOUND)
+ target_link_libraries(tg_cli PRIVATE ${READLINE_LIBRARY})
+ target_include_directories(tg_cli SYSTEM PRIVATE ${READLINE_INCLUDE_DIR})
+ target_compile_definitions(tg_cli PRIVATE -DUSE_READLINE=1)
+ endif()
endif()
- target_link_libraries(tg_cli PRIVATE memprof tdclient tdcore tdtl)
+ target_link_libraries(tg_cli PRIVATE memprof tdclient tdcore)
add_dependencies(tg_cli tl_generate_json)
endif()
@@ -761,32 +991,65 @@ add_library(Td::TdStatic ALIAS TdStatic)
add_library(Td::TdJson ALIAS TdJson)
add_library(Td::TdJsonStatic ALIAS TdJsonStatic)
-install(TARGETS tdjson TdJson tdjson_static TdJsonStatic tdjson_private tdclient tdcore TdStatic EXPORT TdTargets
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- RUNTIME DESTINATION bin
- INCLUDES DESTINATION include
+install(TARGETS tdjson TdJson tdjson_static TdJsonStatic tdjson_private tdclient tdcore tdapi TdStatic EXPORT TdTargets
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
+ INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
+# generate pkg-config files
+include(GeneratePkgConfig)
+
+generate_pkgconfig(tdutils "Telegram Library - Utils")
+generate_pkgconfig(tdactor "Telegram Library - Actor")
+generate_pkgconfig(tdnet "Telegram Library - Net")
+generate_pkgconfig(tdsqlite "Telegram Library - SQLite")
+generate_pkgconfig(tddb "Telegram Library - Database")
+if (MEMPROF)
+ # generate_pkgconfig(memprof "memprof - simple library for memory usage profiling")
+endif()
+generate_pkgconfig(tdcore "Telegram Library - Core")
+generate_pkgconfig(tdclient "Telegram Library - C++ Interface")
+if (TD_ENABLE_DOTNET)
+ # generate_pkgconfig(tddotnet "Telegram Library - C# Interface")
+endif()
+# generate_pkgconfig(tdc "Telegram Library - C interface")
+generate_pkgconfig(tdapi "Telegram Library - API")
+generate_pkgconfig(tdjson_private "Telegram Library - JSON interface (private)")
+generate_pkgconfig(tdjson "Telegram Library - JSON interface (shared)")
+generate_pkgconfig(tdjson_static "Telegram Library - JSON interface (static)")
+
install(EXPORT TdTargets
FILE TdTargets.cmake
NAMESPACE Td::
- DESTINATION lib/cmake/Td
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Td"
)
-install(FILES ${TD_JSON_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/td/telegram/tdjson_export.h DESTINATION include/td/telegram)
-install(FILES td/telegram/Client.h td/telegram/Log.h DESTINATION include/td/telegram)
-install(FILES td/tl/TlObject.h DESTINATION include/td/tl)
-install(FILES ${TL_TD_AUTO_INCLUDES}/td/telegram/td_api.h ${TL_TD_AUTO_INCLUDES}/td/telegram/td_api.hpp DESTINATION include/td/telegram)
+# Install tdjson/tdjson_static:
+install(FILES ${TD_JSON_HEADERS} "${CMAKE_CURRENT_BINARY_DIR}/td/telegram/tdjson_export.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/td/telegram")
+# Install tdclient:
+install(FILES td/telegram/Client.h td/telegram/Log.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/td/telegram")
+# Install tdapi:
+install(FILES td/tl/TlObject.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/td/tl")
+install(FILES "${TL_TD_AUTO_INCLUDE_DIR}/td/telegram/td_api.h" "${TL_TD_AUTO_INCLUDE_DIR}/td/telegram/td_api.hpp" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/td/telegram")
if (TD_ENABLE_JNI)
- install(FILES td/tl/tl_jni_object.h DESTINATION include/td/tl)
+ install(FILES td/tl/tl_jni_object.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/td/tl")
+endif()
+if (MSVC AND VCPKG_TOOLCHAIN)
+ install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/" DESTINATION "${CMAKE_INSTALL_BINDIR}" FILES_MATCHING PATTERN "*.dll")
endif()
include(CMakePackageConfigHelpers)
write_basic_package_version_file("TdConfigVersion.cmake"
- VERSION ${TDLib_VERSION}
+ VERSION "${TDLib_VERSION}"
COMPATIBILITY ExactVersion
)
install(FILES "TdConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/TdConfigVersion.cmake"
- DESTINATION lib/cmake/Td
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Td"
)
+
+# Add SOVERSION to shared libraries
+set_property(TARGET tdclient PROPERTY SOVERSION "${TDLib_VERSION}")
+set_property(TARGET tdapi PROPERTY SOVERSION "${TDLib_VERSION}")
+set_property(TARGET tdjson PROPERTY SOVERSION "${TDLib_VERSION}")
diff --git a/protocols/Telegram/tdlib/td/README.md b/protocols/Telegram/tdlib/td/README.md
index 00e94cabff..ef82fb2e48 100644
--- a/protocols/Telegram/tdlib/td/README.md
+++ b/protocols/Telegram/tdlib/td/README.md
@@ -7,7 +7,6 @@ TDLib (Telegram Database library) is a cross-platform library for building [Tele
- [Examples and documentation](#usage)
- [Dependencies](#dependencies)
- [Building](#building)
-- [Installing dependencies](#installing-dependencies)
- [Using in CMake C++ projects](#using-cxx)
- [Using in Java projects](#using-java)
- [Using in .NET projects](#using-dotnet)
@@ -19,10 +18,10 @@ TDLib (Telegram Database library) is a cross-platform library for building [Tele
`TDLib` has many advantages. Notably `TDLib` is:
-* **Cross-platform**: `TDLib` can be used on Android, iOS, Windows, macOS, Linux, Windows Phone, WebAssembly, watchOS, tvOS, Tizen, Cygwin. It should also work on other *nix systems with or without minimal effort.
+* **Cross-platform**: `TDLib` can be used on Android, iOS, Windows, macOS, Linux, FreeBSD, OpenBSD, NetBSD, illumos, Windows Phone, WebAssembly, watchOS, tvOS, Tizen, Cygwin. It should also work on other *nix systems with or without minimal effort.
* **Multilanguage**: `TDLib` can be easily used with any programming language that is able to execute C functions. Additionally it already has native Java (using `JNI`) bindings and .NET (using `C++/CLI` and `C++/CX`) bindings.
* **Easy to use**: `TDLib` takes care of all network implementation details, encryption and local data storage.
-* **High-performance**: in the [Telegram Bot API](https://core.telegram.org/bots/api), each `TDLib` instance handles more than 19000 active bots simultaneously.
+* **High-performance**: in the [Telegram Bot API](https://core.telegram.org/bots/api), each `TDLib` instance handles more than 24000 active bots simultaneously.
* **Well-documented**: all `TDLib` API methods and public interfaces are fully documented.
* **Consistent**: `TDLib` guarantees that all updates are delivered in the right order.
* **Reliable**: `TDLib` remains stable on slow and unreliable Internet connections.
@@ -31,7 +30,16 @@ TDLib (Telegram Database library) is a cross-platform library for building [Tele
<a name="usage"></a>
## Examples and documentation
-Take a look at our [examples](https://github.com/tdlib/td/tree/master/example) and [documentation](https://core.telegram.org/tdlib/docs/).
+See our [Getting Started](https://core.telegram.org/tdlib/getting-started) tutorial for a description of basic TDLib concepts.
+
+Take a look at our [examples](https://github.com/tdlib/td/blob/master/example/README.md#tdlib-usage-and-build-examples).
+
+See a [TDLib build instructions generator](https://tdlib.github.io/td/build.html) for detailed instructions on how to build TDLib.
+
+See description of our [JSON](#using-json), [C++](#using-cxx), [Java](#using-java) and [.NET](#using-dotnet) interfaces.
+
+See the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html)
+for a list of all available `TDLib` [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
<a name="dependencies"></a>
## Dependencies
@@ -43,13 +51,14 @@ Take a look at our [examples](https://github.com/tdlib/td/tree/master/example) a
* gperf (build only)
* CMake (3.0.2+, build only)
* PHP (optional, for documentation generation)
-* Doxygen (optional, for documentation generation)
<a name="building"></a>
## Building
-Install all `TDLib` dependencies as described in [Installing dependencies](#installing-dependencies).
-Then enter directory containing `TDLib` sources and compile them using CMake:
+The simplest way to build `TDLib` is to use our [TDLib build instructions generator](https://tdlib.github.io/td/build.html).
+You need only to choose your programming language and target operating system to receive complete build instructions.
+
+In general, you need to install all `TDLib` [dependencies](#dependencies), enter directory containing `TDLib` sources and compile them using CMake:
```
mkdir build
@@ -58,53 +67,33 @@ cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
```
-<a name="installing-dependencies"></a>
-### Installing dependencies
-
-<a name="macos"></a>
-#### macOS
-* Install the latest Xcode command line tools.
-* Install other dependencies, for example, using [Homebrew](https://brew.sh):
-```
-brew install gperf cmake openssl
-```
-* Build `TDLib` with CMake as explained in [building](#building). You may need to manually specify path to the installed OpenSSL to CMake, e.g.,
-```
-cmake -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/ ..
-```
-
-<a name="windows"></a>
-#### Windows
-* Download and install [gperf](https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/). Add the path to gperf.exe to the PATH environment variable.
-* Install [vcpkg](https://github.com/Microsoft/vcpkg#quick-start).
-* Run the following commands to install `TDLib` dependencies using vcpkg:
-```
-C:\src\vcpkg> .\vcpkg.exe install openssl:x64-windows openssl:x86-windows zlib:x64-windows zlib:x86-windows
-```
-* Build `TDLib` with CMake as explained in [building](#building), but instead of `cmake -DCMAKE_BUILD_TYPE=Release ..` use
+To build `TDLib` on low memory devices you can run [SplitSource.php](https://github.com/tdlib/td/blob/master/SplitSource.php) script
+before compiling main `TDLib` source code and compile only needed targets:
```
-cmake -DCMAKE_TOOLCHAIN_FILE=C:\src\vcpkg\scripts\buildsystems\vcpkg.cmake ..
+mkdir build
+cd build
+cmake -DCMAKE_BUILD_TYPE=Release ..
+cmake --build . --target prepare_cross_compiling
+cd ..
+php SplitSource.php
+cd build
+cmake --build . --target tdjson
+cmake --build . --target tdjson_static
+cd ..
+php SplitSource.php --undo
```
-
-To build 64-bit `TDLib` using MSVC, you will need to additionally specify parameter `-A x64` to CMake.
-To build `TDLib` in Release mode using MSVC, you will need to additionally specify parameter `--config Release` to the `cmake --build .` command.
-
-<a name="linux"></a>
-#### Linux
-* Install all dependencies using your package manager.
+In our tests clang 6.0 with libc++ required less than 500 MB of RAM per file and GCC 4.9/6.3 used less than 1 GB of RAM per file.
<a name="using-cxx"></a>
## Using in CMake C++ projects
-For C++ projects that use CMake, the best approach is to build `TDLib` as part of your project or to use a prebuilt installation.
+For C++ projects that use CMake, the best approach is to build `TDLib` as part of your project or to install it system-wide.
There are several libraries that you could use in your CMake project:
* Td::TdJson, Td::TdJsonStatic — dynamic and static version of a JSON interface. This has a simple C interface, so it can be easily used with any programming language that is able to execute C functions.
- See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) and [td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) documentation for more information.
+ See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for more information.
* Td::TdStatic — static library with C++ interface for general usage.
- See [Client](https://core.telegram.org/tdlib/docs/classtd_1_1_client.html) and [Log](https://core.telegram.org/tdlib/docs/classtd_1_1_log.html) documentation for more information.
-* Td::TdCoreStatic — static library with low-level C++ interface intended mostly for internal usage.
- See [ClientActor](https://core.telegram.org/tdlib/docs/classtd_1_1_client_actor.html) and [Log](https://core.telegram.org/tdlib/docs/classtd_1_1_log.html) documentation for more information.
+ See [ClientManager](https://core.telegram.org/tdlib/docs/classtd_1_1_client_manager.html) and [Client](https://core.telegram.org/tdlib/docs/classtd_1_1_client.html) documentation for more information.
For example, part of your CMakeLists.txt may look like this:
```
@@ -114,10 +103,10 @@ target_link_libraries(YourTarget PRIVATE Td::TdStatic)
Or you could install `TDLib` and then reference it in your CMakeLists.txt like this:
```
-find_package(Td 1.2.0 REQUIRED)
+find_package(Td 1.8.8 REQUIRED)
target_link_libraries(YourTarget PRIVATE Td::TdStatic)
```
-See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/tree/master/example/cpp/CMakeLists.txt).
+See [example/cpp/CMakeLists.txt](https://github.com/tdlib/td/blob/master/example/cpp/CMakeLists.txt).
<a name="using-java"></a>
## Using in Java projects
@@ -128,7 +117,7 @@ See [example/java](https://github.com/tdlib/td/tree/master/example/java) for exa
<a name="using-dotnet"></a>
## Using in .NET projects
`TDLib` provides native .NET interface through `C++/CLI` and `C++/CX`. To enable it, specify option `-DTD_ENABLE_DOTNET=ON` to CMake.
-.NET Core doesn't support `C++/CLI`, so if .NET Core is used, then `TDLib` JSON interface should be used through P/Invoke instead.
+.NET Core supports `C++/CLI` only since version 3.1 and only on Windows, so if older .NET Core is used or portability is needed, then `TDLib` JSON interface should be used through P/Invoke instead.
See [example/csharp](https://github.com/tdlib/td/tree/master/example/csharp) for example of using `TDLib` from C# and detailed build and usage instructions.
See [example/uwp](https://github.com/tdlib/td/tree/master/example/uwp) for example of using `TDLib` from C# UWP application and detailed build and usage instructions for Visual Studio Extension "TDLib for Universal Windows Platform".
@@ -142,10 +131,14 @@ git checkout td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h
## Using from other programming languages
`TDLib` provides efficient native C++, Java, and .NET interfaces.
But for most use cases we suggest to use the JSON interface, which can be easily used with any programming language that is able to execute C functions.
-See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) and [td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) documentation for detailed JSON interface description,
-scheme [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) or autogenerated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for the list of all available `TDLib` methods and classes.
+See [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html) documentation for detailed JSON interface description,
+the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of
+all available `TDLib` [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
+
+`TDLib` JSON interface adheres to semantic versioning and versions with the same major version number are binary and backward compatible, but the underlying `TDLib` API can be different for different minor and even patch versions.
+If you need to support different `TDLib` versions, then you can use a value of the `version` option to find exact `TDLib` version to use appropriate API methods.
-See [example/python/tdjson_example.py](https://github.com/tdlib/td/tree/master/example/python/tdjson_example.py) and [example/ruby/example.rb](https://github.com/tdlib/td/tree/master/example/ruby/example.rb) for an example of such usage.
+See [example/python/tdjson_example.py](https://github.com/tdlib/td/blob/master/example/python/tdjson_example.py) for an example of such usage.
<a name="license"></a>
## License
diff --git a/protocols/Telegram/tdlib/td/SplitSource.php b/protocols/Telegram/tdlib/td/SplitSource.php
new file mode 100644
index 0000000000..49854cb716
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/SplitSource.php
@@ -0,0 +1,368 @@
+<?php
+
+function disjoint_set_find(&$parents, $x) {
+ if ($parents[$x] !== $x) {
+ return $parents[$x] = disjoint_set_find($parents, $parents[$x]);
+ }
+ return $x;
+}
+
+function disjoint_set_union(&$parents, $x, $y) {
+ $x = disjoint_set_find($parents, $x);
+ $y = disjoint_set_find($parents, $y);
+
+ if ($x !== $y) {
+ if (rand(0, 1) == 0) {
+ $parents[$x] = $y;
+ } else {
+ $parents[$y] = $x;
+ }
+ }
+}
+
+function split_file($file, $chunks, $undo) {
+ $cpp_name = "$file.cpp";
+
+ echo "Processing file $cpp_name".PHP_EOL;
+
+ $new_files = array();
+ foreach (range(0, $chunks - 1) as $n) {
+ $new_files[] = "$file$n.cpp";
+ }
+
+ $is_generated = (strpos($file, 'td/generate/') === 0);
+
+ $cmake_file = $is_generated ? 'td/generate/CMakeLists.txt' : 'CMakeLists.txt';
+ $cmake = file_get_contents($cmake_file);
+
+ $cmake_cpp_name = $cpp_name;
+ $cmake_new_files = $new_files;
+ if ($is_generated) {
+ foreach ($cmake_new_files as &$file_ref) {
+ $file_ref = str_replace('td/generate/auto/td', '${TD_AUTO_INCLUDE_DIR}', $file_ref);
+ }
+ $cmake_cpp_name = str_replace('td/generate/auto/td', '${TD_AUTO_INCLUDE_DIR}', $cmake_cpp_name);
+ }
+
+ if ($undo) {
+ foreach ($new_files as $file) {
+ if (file_exists($file)) {
+ echo "Unlinking ".$file.PHP_EOL;
+ unlink($file);
+ }
+ }
+
+ if (strpos($cmake, $cmake_cpp_name) === false) {
+ $cmake = str_replace(implode(PHP_EOL.' ', $cmake_new_files), $cmake_cpp_name, $cmake);
+ file_put_contents($cmake_file, $cmake);
+ }
+
+ return;
+ }
+
+ if (strpos($cmake, $cmake_cpp_name) !== false) {
+ $cmake = str_replace($cmake_cpp_name, implode(PHP_EOL.' ', $cmake_new_files), $cmake);
+ file_put_contents($cmake_file, $cmake);
+ }
+
+ if (!file_exists($cpp_name)) {
+ echo "ERROR: skip nonexistent file $cpp_name".PHP_EOL;
+ return;
+ }
+
+ $lines = file($cpp_name);
+ $depth = 0;
+ $target_depth = 1 + $is_generated;
+ $is_static = false;
+ $in_define = false;
+ $in_comment = false;
+ $current = '';
+ $common = '';
+ $functions = array();
+ $namespace_begin = '';
+ $namespace_end = '';
+ foreach ($lines as $line) {
+ $add_depth = strpos($line, 'namespace ') === 0 ? 1 : (strpos($line, '} // namespace') === 0 ? -1 : 0);
+ if ($add_depth) {
+ # namespace begin/end
+ if ($add_depth > 0) {
+ $depth += $add_depth;
+ }
+ if ($depth <= $target_depth) {
+ if ($add_depth > 0) {
+ $namespace_begin .= $line;
+ } else {
+ $namespace_end .= $line;
+ }
+ }
+ if ($add_depth < 0) {
+ $depth += $add_depth;
+ }
+ if ($is_static) {
+ $common .= $current;
+ } else {
+ $functions[] = $current;
+ }
+ $common .= $line;
+ $current = '';
+ $is_static = false;
+ $in_define = false;
+ continue;
+ }
+
+ if (strpos($line, '#undef') === 0 && !trim($current)) {
+ continue;
+ }
+
+ if ($in_comment && strpos($line, '*/') === 0) {
+ $in_comment = false;
+ continue;
+ }
+ if (strpos($line, '/*') === 0) {
+ $in_comment = true;
+ }
+ if ($in_comment) {
+ continue;
+ }
+
+ if ($depth !== $target_depth) {
+ $common .= $line;
+ continue;
+ }
+
+ if (strpos($line, 'static ') === 0 && $depth === $target_depth) {
+ $is_static = true;
+ }
+ if (!trim($current) && strpos($line, '#define ') === 0) {
+ $is_static = true;
+ $in_define = true;
+ }
+
+ $current .= $line;
+ if ((strpos($line, '}') === 0 || ($in_define && !trim($line)) || preg_match('/^[a-z].*;\s*$/i', $line)) && $depth === $target_depth) {
+ # block end
+ if ($is_static) {
+ $common .= $current;
+ } else {
+ $functions[] = $current;
+ }
+ $current = '';
+ $is_static = false;
+ $in_define = false;
+ }
+ }
+ $current = trim($current);
+ if (!empty($current)) {
+ fwrite(STDERR, "ERROR: $current".PHP_EOL);
+ exit();
+ }
+
+ if (count($functions) < $chunks) {
+ fwrite(STDERR, "ERROR: file is too small to be split more".PHP_EOL);
+ return;
+ }
+
+ $deps = array(); // all functions from the same subarray must be in the same file
+ $parents = array();
+ foreach ($functions as $i => $f) {
+ if (preg_match_all('/(?J)create_handler<(?<name>[A-Z][A-Za-z]*)>|'.
+ '(?<name>[A-Z][A-Za-z]*) (final )?: public (Td::ResultHandler|Request)|'.
+ '(CREATE_REQUEST|CREATE_NO_ARGS_REQUEST)[(](?<name>[A-Z][A-Za-z]*)|'.
+ '(?<name>complete_pending_preauthentication_requests)|'.
+ '(?<name>get_message_history_slice)|'.
+ '(Up|Down)load[a-zA-Z]*C(?<name>allback)|(up|down)load_[a-z_]*_c(?<name>allback)_|'.
+ '(?<name>lazy_to_json)|'.
+ '(?<name>LogEvent)[^sA]|'.
+ '(?<name>parse)[(]|'.
+ '(?<name>store)[(]/', $f, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $name = $match['name'];
+ if ($name === 'parse' || $name === 'store') {
+ if ($is_generated) {
+ continue;
+ }
+ $name = 'LogEvent';
+ }
+ $deps[$name][] = $i;
+ }
+ }
+ $parents[$i] = $i;
+ }
+
+ foreach ($deps as $func_ids) {
+ foreach ($func_ids as $func_id) {
+ disjoint_set_union($parents, $func_ids[0], $func_id);
+ }
+ }
+ $sets = array();
+ $set_sizes = array();
+ foreach ($functions as $i => $f) {
+ $parent = disjoint_set_find($parents, $i);
+ if (!isset($sets[$parent])) {
+ $sets[$parent] = '';
+ $set_sizes[$parent] = 0;
+ }
+ $sets[$parent] .= $f;
+ $set_sizes[$parent] += preg_match('/Td::~?Td/', $f) ? 1000000 : strlen($f);
+ }
+ arsort($set_sizes);
+
+ $files = array_fill(0, $chunks, '');
+ $file_sizes = array_fill(0, $chunks, 0);
+ foreach ($set_sizes as $parent => $size) {
+ $file_id = array_search(min($file_sizes), $file_sizes);
+ $files[$file_id] .= $sets[$parent];
+ $file_sizes[$file_id] += $size;
+ }
+
+ foreach ($files as $n => $f) {
+ $new_content = $common.$namespace_begin.$f.$namespace_end;
+
+ $std_methods = array();
+ preg_match_all('/std::[a-z_0-9]*|td::unique(?!_)/', $new_content, $std_methods);
+ $std_methods = array_unique($std_methods[0]);
+
+ $needed_std_headers = array();
+ $type_headers = array(
+ 'std::move' => '',
+ 'std::vector' => '',
+ 'std::string' => '',
+ 'std::uint32_t' => '',
+ 'std::int32_t' => '',
+ 'std::int64_t' => '',
+ 'td::unique' => 'algorithm',
+ 'std::fill' => 'algorithm',
+ 'std::find' => 'algorithm',
+ 'std::max' => 'algorithm',
+ 'std::min' => 'algorithm',
+ 'std::remove' => 'algorithm',
+ 'std::reverse' => 'algorithm',
+ 'std::rotate' => 'algorithm',
+ 'std::sort' => 'algorithm',
+ 'std::abs' => 'cmath',
+ 'std::numeric_limits' => 'limits',
+ 'std::make_shared' => 'memory',
+ 'std::shared_ptr' => 'memory',
+ 'std::tie' => 'tuple',
+ 'std::tuple' => 'tuple',
+ 'std::decay_t' => 'type_traits',
+ 'std::is_same' => 'type_traits',
+ 'std::make_pair' => 'utility',
+ 'std::pair' => 'utility',
+ 'std::swap' => 'utility',
+ 'std::unordered_map' => 'unordered_map',
+ 'std::unordered_set' => 'unordered_set');
+ foreach ($type_headers as $type => $header) {
+ if (in_array($type, $std_methods)) {
+ $std_methods = array_diff($std_methods, array($type));
+ if ($header && !in_array($header, $needed_std_headers)) {
+ $needed_std_headers[] = $header;
+ }
+ }
+ }
+
+ if (!$std_methods) { // know all needed std headers
+ $new_content = preg_replace_callback(
+ '/#include <([a-z_]*)>/',
+ function ($matches) use ($needed_std_headers) {
+ if (in_array($matches[1], $needed_std_headers)) {
+ return $matches[0];
+ }
+ return '';
+ },
+ $new_content
+ );
+ }
+
+ if (!preg_match('/Td::~?Td/', $new_content)) { // destructor Td::~Td needs to see definitions of all forward-declared classes
+ $td_methods = array(
+ 'animations_manager[_(-][^.]|AnimationsManager[^;>]' => "AnimationsManager",
+ 'attach_menu_manager[_(-][^.]|AttachMenuManager[^;>]' => "AttachMenuManager",
+ 'audios_manager[_(-][^.]|AudiosManager' => "AudiosManager",
+ 'auth_manager[_(-][^.]|AuthManager' => 'AuthManager',
+ 'background_manager[_(-][^.]|BackgroundManager' => "BackgroundManager",
+ 'contacts_manager[_(-][^.]|ContactsManager([^ ;.]| [^*])' => 'ContactsManager',
+ 'country_info_manager[_(-][^.]|CountryInfoManager' => 'CountryInfoManager',
+ 'documents_manager[_(-][^.]|DocumentsManager' => "DocumentsManager",
+ 'file_reference_manager[_(-][^.]|FileReferenceManager|file_references[)]' => 'FileReferenceManager',
+ 'file_manager[_(-][^.]|FileManager([^ ;.]| [^*])|update_file[)]' => 'files/FileManager',
+ 'forum_topic_manager[_(-][^.]|ForumTopicManager' => 'ForumTopicManager',
+ 'G[(][)]|Global[^A-Za-z]' => 'Global',
+ 'game_manager[_(-][^.]|GameManager' => 'GameManager',
+ 'group_call_manager[_(-][^.]|GroupCallManager' => 'GroupCallManager',
+ 'HashtagHints' => 'HashtagHints',
+ 'inline_queries_manager[_(-][^.]|InlineQueriesManager' => 'InlineQueriesManager',
+ 'language_pack_manager[_(-]|LanguagePackManager' => 'LanguagePackManager',
+ 'link_manager[_(-][^.]|LinkManager' => 'LinkManager',
+ 'LogeventIdWithGeneration|add_log_event|delete_log_event|get_erase_log_event_promise|parse_time|store_time' => 'logevent/LogEventHelper',
+ 'MessageCopyOptions' => 'MessageCopyOptions',
+ 'messages_manager[_(-][^.]|MessagesManager' => 'MessagesManager',
+ 'notification_manager[_(-][^.]|NotificationManager|notifications[)]' => 'NotificationManager',
+ 'notification_settings_manager[_(-][^.]|NotificationSettingsManager' => 'NotificationSettingsManager',
+ 'option_manager[_(-][^.]|OptionManager' => "OptionManager",
+ 'phone_number_manager[_(-][^.]|PhoneNumberManager' => "PhoneNumberManager",
+ 'poll_manager[_(-][^.]|PollManager' => "PollManager",
+ 'PublicDialogType|get_public_dialog_type' => 'PublicDialogType',
+ 'SecretChatActor' => 'SecretChatActor',
+ 'secret_chats_manager[_(-]|SecretChatsManager' => 'SecretChatsManager',
+ 'sponsored_message_manager[_(-][^.]|SponsoredMessageManager' => 'SponsoredMessageManager',
+ 'stickers_manager[_(-][^.]|StickersManager' => 'StickersManager',
+ '[>](td_db[(][)]|get_td_db_impl[(])|TdDb[^A-Za-z]' => 'TdDb',
+ 'theme_manager[_(-][^.]|ThemeManager' => "ThemeManager",
+ 'TopDialogCategory|get_top_dialog_category' => 'TopDialogCategory',
+ 'top_dialog_manager[_(-][^.]|TopDialogManager' => 'TopDialogManager',
+ 'updates_manager[_(-][^.]|UpdatesManager|get_difference[)]|updateSentMessage|dummyUpdate' => 'UpdatesManager',
+ 'WebPageId(Hash)?' => 'WebPageId',
+ 'web_pages_manager[_(-][^.]|WebPagesManager' => 'WebPagesManager');
+
+ foreach ($td_methods as $pattern => $header) {
+ if (strpos($cpp_name, $header) !== false) {
+ continue;
+ }
+
+ $include_name = '#include "td/telegram/'.$header.'.h"';
+ if (strpos($new_content, $include_name) !== false && preg_match('/'.$pattern.'/', str_replace($include_name, '', $new_content)) === 0) {
+ $new_content = str_replace($include_name, '', $new_content);
+ }
+ }
+ } else {
+ $new_content = preg_replace_callback(
+ '|#include "[a-z_A-Z/0-9.]*"|',
+ function ($matches) {
+ if (strpos($matches[0], "Manager") !== false || strpos($matches[0], "HashtagHints") !== false || strpos($matches[0], "Td.h") !== false) {
+ return $matches[0];
+ }
+ return '';
+ },
+ $new_content
+ );
+ }
+
+ if (!file_exists($new_files[$n]) || file_get_contents($new_files[$n]) !== $new_content) {
+ echo "Writing file ".$new_files[$n].PHP_EOL;
+ file_put_contents($new_files[$n], $new_content);
+ }
+ }
+}
+
+if (in_array('--help', $argv) || in_array('-h', $argv)) {
+ echo "Usage: php SplitSource.php [OPTION]...\n".
+ "Splits some source files to reduce a maximum amount of RAM needed for compiling a single file.\n".
+ " -u, --undo Undo all source code changes.\n".
+ " -h, --help Show this help.\n";
+ exit(2);
+}
+
+$undo = in_array('--undo', $argv) || in_array('-u', $argv);
+$files = array('td/telegram/ContactsManager' => 20,
+ 'td/telegram/MessagesManager' => 50,
+ 'td/telegram/NotificationManager' => 10,
+ 'td/telegram/StickersManager' => 10,
+ 'td/telegram/Td' => 50,
+ 'td/generate/auto/td/telegram/td_api' => 10,
+ 'td/generate/auto/td/telegram/td_api_json' => 10,
+ 'td/generate/auto/td/telegram/telegram_api' => 10);
+
+foreach ($files as $file => $chunks) {
+ split_file($file, $chunks, $undo);
+}
diff --git a/protocols/Telegram/tdlib/td/benchmark/CMakeLists.txt b/protocols/Telegram/tdlib/td/benchmark/CMakeLists.txt
index 90f294fbc9..53fbdede52 100644
--- a/protocols/Telegram/tdlib/td/benchmark/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/benchmark/CMakeLists.txt
@@ -1,4 +1,6 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
@@ -7,7 +9,14 @@ endif()
#TODO: all benchmarks in one file
add_executable(bench_crypto bench_crypto.cpp)
-target_link_libraries(bench_crypto PRIVATE tdcore tdutils ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
+target_link_libraries(bench_crypto PRIVATE tdutils ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
+if (WIN32)
+ if (MINGW)
+ target_link_libraries(bench_crypto PRIVATE ws2_32 mswsock crypt32)
+ else()
+ target_link_libraries(bench_crypto PRIVATE ws2_32 Mswsock Crypt32)
+ endif()
+endif()
target_include_directories(bench_crypto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
add_executable(bench_actor bench_actor.cpp)
@@ -40,6 +49,12 @@ target_link_libraries(bench_tddb PRIVATE tdcore tddb tdutils)
add_executable(bench_misc bench_misc.cpp)
target_link_libraries(bench_misc PRIVATE tdcore tdutils)
+add_executable(check_proxy check_proxy.cpp)
+target_link_libraries(check_proxy PRIVATE tdclient tdutils)
+
+add_executable(check_tls check_tls.cpp)
+target_link_libraries(check_tls PRIVATE tdutils)
+
add_executable(rmdir rmdir.cpp)
target_link_libraries(rmdir PRIVATE tdutils)
@@ -57,3 +72,22 @@ if (NOT WIN32 AND NOT CYGWIN)
add_executable(bench_queue bench_queue.cpp)
target_link_libraries(bench_queue PRIVATE tdutils)
endif()
+
+if (TD_TEST_FOLLY AND TD_WITH_ABSEIL)
+ find_package(ABSL QUIET)
+ find_package(folly QUIET)
+ find_package(gflags QUIET)
+
+ if (ABSL_FOUND AND folly_FOUND)
+ add_executable(memory-hashset-memprof EXCLUDE_FROM_ALL hashset_memory.cpp)
+ target_compile_definitions(memory-hashset-memprof PRIVATE USE_MEMPROF=1)
+ target_link_libraries(memory-hashset-memprof PRIVATE tdutils memprof_stat Folly::folly absl::flat_hash_map absl::hash)
+
+ add_executable(memory-hashset-os hashset_memory.cpp)
+ target_compile_definitions(memory-hashset-os PRIVATE USE_MEMPROF=0)
+ target_link_libraries(memory-hashset-os PRIVATE tdutils Folly::folly absl::flat_hash_map absl::hash)
+
+ add_executable(hashmap-build hashmap_build.cpp)
+ target_link_libraries(hashmap-build PRIVATE tdutils Folly::folly absl::flat_hash_map absl::hash)
+ endif()
+endif()
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_actor.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_actor.cpp
index a966d601cf..afe94ca3c1 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_actor.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_actor.cpp
@@ -1,47 +1,101 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/benchmark.h"
-
#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
#include "td/actor/PromiseFuture.h"
+#include "td/utils/benchmark.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
#include "td/utils/logging.h"
-
-#include <algorithm>
+#include "td/utils/Promise.h"
+#include "td/utils/SliceBuilder.h"
#if TD_MSVC
#pragma comment(linker, "/STACK:16777216")
#endif
+struct TestActor final : public td::Actor {
+ static td::int32 actor_count_;
+
+ void start_up() final {
+ actor_count_++;
+ stop();
+ }
+
+ void tear_down() final {
+ if (--actor_count_ == 0) {
+ td::Scheduler::instance()->finish();
+ }
+ }
+};
+
+td::int32 TestActor::actor_count_;
+
+namespace td {
+template <>
+class ActorTraits<TestActor> {
+ public:
+ static constexpr bool need_context = false;
+ static constexpr bool need_start_up = true;
+};
+} // namespace td
+
+class CreateActorBench final : public td::Benchmark {
+ td::ConcurrentScheduler scheduler_{0, 0};
+
+ void start_up() final {
+ scheduler_.start();
+ }
+
+ void tear_down() final {
+ scheduler_.finish();
+ }
+
+ public:
+ td::string get_description() const final {
+ return "CreateActor";
+ }
+
+ void run(int n) final {
+ for (int i = 0; i < n; i++) {
+ scheduler_.create_actor_unsafe<TestActor>(0, "TestActor").release();
+ }
+ while (scheduler_.run_main(10)) {
+ // empty
+ }
+ }
+};
+
template <int type>
-class RingBench : public td::Benchmark {
+class RingBench final : public td::Benchmark {
public:
struct PassActor;
private:
- int actor_n_;
- int thread_n_;
- std::vector<td::ActorId<PassActor>> actor_array_;
- td::ConcurrentScheduler *scheduler_;
+ int actor_n_ = -1;
+ int thread_n_ = -1;
+ td::vector<td::ActorId<PassActor>> actor_array_;
+ td::ConcurrentScheduler *scheduler_ = nullptr;
public:
- std::string get_description() const override {
+ td::string get_description() const final {
static const char *types[] = {"later", "immediate", "raw", "tail", "lambda"};
static_assert(0 <= type && type < 5, "");
return PSTRING() << "Ring (send_" << types[type] << ") (threads_n = " << thread_n_ << ")";
}
- struct PassActor : public td::Actor {
- int id;
+ struct PassActor final : public td::Actor {
+ int id = -1;
td::ActorId<PassActor> next_actor;
int start_n = 0;
void pass(int n) {
- // LOG(INFO) << "pass: " << n;
+ // LOG(INFO) << "Pass: " << n;
if (n == 0) {
td::Scheduler::instance()->finish();
} else {
@@ -56,24 +110,23 @@ class RingBench : public td::Benchmark {
send_closure_later(next_actor, &PassActor::pass, n - 1);
} else {
// TODO: it is three times faster than send_event
- // may be send event could be further optimized?
- ::td::Scheduler::instance()->hack(static_cast<td::ActorId<Actor>>(next_actor),
- td::Event::raw(static_cast<td::uint32>(n - 1)));
+ // maybe send event could be further optimized?
+ next_actor.get_actor_unsafe()->raw_event(td::Event::raw(static_cast<td::uint32>(n - 1)).data);
}
} else if (type == 4) {
- send_lambda(next_actor, [=, ptr = next_actor.get_actor_unsafe()] { ptr->pass(n - 1); });
+ send_lambda(next_actor, [n, ptr = next_actor.get_actor_unsafe()] { ptr->pass(n - 1); });
}
}
}
- void raw_event(const td::Event::Raw &raw) override {
+ void raw_event(const td::Event::Raw &raw) final {
pass(static_cast<int>(raw.u32));
}
- void start_up() override {
+ void start_up() final {
yield();
}
- void wakeup() override {
+ void wakeup() final {
if (start_n != 0) {
int n = start_n;
start_n = 0;
@@ -85,11 +138,10 @@ class RingBench : public td::Benchmark {
RingBench(int actor_n, int thread_n) : actor_n_(actor_n), thread_n_(thread_n) {
}
- void start_up() override {
- scheduler_ = new td::ConcurrentScheduler();
- scheduler_->init(thread_n_);
+ void start_up() final {
+ scheduler_ = new td::ConcurrentScheduler(thread_n_, 0);
- actor_array_ = std::vector<td::ActorId<PassActor>>(actor_n_);
+ actor_array_ = td::vector<td::ActorId<PassActor>>(actor_n_);
for (int i = 0; i < actor_n_; i++) {
actor_array_[i] =
scheduler_->create_actor_unsafe<PassActor>(thread_n_ ? i % thread_n_ : 0, "PassActor").release();
@@ -101,30 +153,30 @@ class RingBench : public td::Benchmark {
scheduler_->start();
}
- void run(int n) override {
+ void run(int n) final {
// first actor is on main_thread
- actor_array_[0].get_actor_unsafe()->start_n = std::max(n, 100);
+ actor_array_[0].get_actor_unsafe()->start_n = td::max(n, 100);
while (scheduler_->run_main(10)) {
// empty
}
}
- void tear_down() override {
+ void tear_down() final {
scheduler_->finish();
delete scheduler_;
}
};
template <int type>
-class QueryBench : public td::Benchmark {
+class QueryBench final : public td::Benchmark {
public:
- std::string get_description() const override {
+ td::string get_description() const final {
static const char *types[] = {"callback", "immediate future", "delayed future", "dummy", "lambda", "lambda_future"};
static_assert(0 <= type && type < 6, "");
return PSTRING() << "QueryBench: " << types[type];
}
- class ClientActor : public td::Actor {
+ class ClientActor final : public td::Actor {
public:
class Callback {
public:
@@ -136,7 +188,7 @@ class QueryBench : public td::Benchmark {
virtual ~Callback() = default;
virtual void on_result(int x) = 0;
};
- explicit ClientActor(std::unique_ptr<Callback> callback) : callback_(std::move(callback)) {
+ explicit ClientActor(td::unique_ptr<Callback> callback) : callback_(std::move(callback)) {
}
void f(int x) {
callback_->on_result(x * x);
@@ -152,23 +204,23 @@ class QueryBench : public td::Benchmark {
}
private:
- std::unique_ptr<Callback> callback_;
+ td::unique_ptr<Callback> callback_;
};
- class ServerActor : public td::Actor {
+ class ServerActor final : public td::Actor {
public:
- class ClientCallback : public ClientActor::Callback {
+ class ClientCallback final : public ClientActor::Callback {
public:
explicit ClientCallback(td::ActorId<ServerActor> server) : server_(server) {
}
- void on_result(int x) override {
+ void on_result(int x) final {
send_closure(server_, &ServerActor::on_result, x);
}
private:
td::ActorId<ServerActor> server_;
};
- void start_up() override {
+ void start_up() final {
client_ = td::create_actor<ClientActor>("Client", td::make_unique<ClientCallback>(actor_id(this))).release();
}
@@ -177,7 +229,7 @@ class QueryBench : public td::Benchmark {
wakeup();
}
- void wakeup() override {
+ void wakeup() final {
while (true) {
if (n_ < 0) {
td::Scheduler::instance()->finish();
@@ -206,10 +258,12 @@ class QueryBench : public td::Benchmark {
} else if (type == 4) {
int val = 0;
send_lambda(client_, [&] { val = n_ * n_; });
+ CHECK(val == n_ * n_);
} else if (type == 5) {
send_closure(client_, &ClientActor::f_promise,
- td::PromiseCreator::lambda(
- [id = actor_id(this), n = n_](td::Unit) { send_closure(id, &ServerActor::result, n * n); }));
+ td::PromiseCreator::lambda([actor_id = actor_id(this), n = n_](td::Unit) {
+ send_closure(actor_id, &ServerActor::result, n * n);
+ }));
return;
}
}
@@ -220,7 +274,7 @@ class QueryBench : public td::Benchmark {
wakeup();
}
- void raw_event(const td::Event::Raw &event) override {
+ void raw_event(const td::Event::Raw &event) final {
int val = future_.move_as_ok();
CHECK(val == n_ * n_);
wakeup();
@@ -232,22 +286,21 @@ class QueryBench : public td::Benchmark {
private:
td::ActorId<ClientActor> client_;
- int n_;
+ int n_ = 0;
td::FutureActor<int> future_;
};
- void start_up() override {
- scheduler_ = new td::ConcurrentScheduler();
- scheduler_->init(0);
+ void start_up() final {
+ scheduler_ = new td::ConcurrentScheduler(0, 0);
server_ = scheduler_->create_actor_unsafe<ServerActor>(0, "Server");
scheduler_->start();
}
- void run(int n) override {
+ void run(int n) final {
// first actor is on main_thread
{
- auto guard = scheduler_->get_current_guard();
+ auto guard = scheduler_->get_main_guard();
send_closure(server_, &ServerActor::run, n);
}
while (scheduler_->run_main(10)) {
@@ -255,19 +308,21 @@ class QueryBench : public td::Benchmark {
}
}
- void tear_down() override {
+ void tear_down() final {
server_.release();
scheduler_->finish();
delete scheduler_;
}
private:
- td::ConcurrentScheduler *scheduler_;
+ td::ConcurrentScheduler *scheduler_ = nullptr;
td::ActorOwn<ServerActor> server_;
};
int main() {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
+ td::init_openssl_threads();
+
+ bench(CreateActorBench());
bench(RingBench<4>(504, 0));
bench(RingBench<3>(504, 0));
bench(RingBench<0>(504, 0));
@@ -286,5 +341,4 @@ int main() {
bench(RingBench<0>(504, 2));
bench(RingBench<1>(504, 2));
bench(RingBench<2>(504, 2));
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_crypto.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_crypto.cpp
index 44d309ef11..40d7602d2e 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_crypto.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_crypto.cpp
@@ -1,75 +1,374 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/benchmark.h"
+#include "td/utils/common.h"
#include "td/utils/crypto.h"
-#include "td/utils/int_types.h"
-#include "td/utils/logging.h"
#include "td/utils/port/thread.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/UInt.h"
+#include <openssl/evp.h>
#include <openssl/sha.h>
+#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <cstdlib>
+#include <iterator>
#include <random>
#include <string>
#include <vector>
static constexpr int DATA_SIZE = 8 << 10;
+static constexpr int SHORT_DATA_SIZE = 64;
-class SHA1Bench : public td::Benchmark {
+#if OPENSSL_VERSION_NUMBER <= 0x10100000L
+class SHA1Bench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
- std::string get_description() const override {
+ std::string get_description() const final {
return PSTRING() << "SHA1 OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
}
- void start_up() override {
- for (int i = 0; i < DATA_SIZE; i++) {
- data[i] = 123;
- data[i] = 0;
- }
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
unsigned char md[20];
SHA1(data, DATA_SIZE, md);
}
}
};
+#endif
+
+class SHA1ShortBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[SHORT_DATA_SIZE];
+
+ std::string get_description() const final {
+ return PSTRING() << "SHA1 [" << SHORT_DATA_SIZE << "B]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ }
+
+ void run(int n) final {
+ unsigned char md[20];
+ for (int i = 0; i < n; i++) {
+ td::sha1(td::Slice(data, SHORT_DATA_SIZE), md);
+ }
+ }
+};
+
+class SHA256ShortBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[SHORT_DATA_SIZE];
+
+ std::string get_description() const final {
+ return PSTRING() << "SHA256 [" << SHORT_DATA_SIZE << "B]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ }
+
+ void run(int n) final {
+ unsigned char md[32];
+ for (int i = 0; i < n; i++) {
+ td::sha256(td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32));
+ }
+ }
+};
+
+class SHA512ShortBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[SHORT_DATA_SIZE];
+
+ std::string get_description() const final {
+ return PSTRING() << "SHA512 [" << SHORT_DATA_SIZE << "B]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ }
+
+ void run(int n) final {
+ unsigned char md[64];
+ for (int i = 0; i < n; i++) {
+ td::sha512(td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 64));
+ }
+ }
+};
-class AESBench : public td::Benchmark {
+class HmacSha256ShortBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[SHORT_DATA_SIZE];
+
+ std::string get_description() const final {
+ return PSTRING() << "HMAC-SHA256 [" << SHORT_DATA_SIZE << "B]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ }
+
+ void run(int n) final {
+ unsigned char md[32];
+ for (int i = 0; i < n; i++) {
+ td::hmac_sha256(td::Slice(data, SHORT_DATA_SIZE), td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32));
+ }
+ }
+};
+
+class HmacSha512ShortBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[SHORT_DATA_SIZE];
+
+ std::string get_description() const final {
+ return PSTRING() << "HMAC-SHA512 [" << SHORT_DATA_SIZE << "B]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ }
+
+ void run(int n) final {
+ unsigned char md[32];
+ for (int i = 0; i < n; i++) {
+ td::hmac_sha256(td::Slice(data, SHORT_DATA_SIZE), td::Slice(data, SHORT_DATA_SIZE), td::MutableSlice(md, 32));
+ }
+ }
+};
+
+class AesEcbBench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
td::UInt256 key;
td::UInt256 iv;
- std::string get_description() const override {
- return PSTRING() << "AES OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
+ std::string get_description() const final {
+ return PSTRING() << "AES ECB OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ td::Random::secure_bytes(key.raw, sizeof(key));
+ td::Random::secure_bytes(iv.raw, sizeof(iv));
}
- void start_up() override {
- for (int i = 0; i < DATA_SIZE; i++) {
- data[i] = 123;
+ void run(int n) final {
+ td::AesState state;
+ state.init(td::as_slice(key), true);
+ td::MutableSlice data_slice(data, DATA_SIZE);
+ for (int i = 0; i <= n; i++) {
+ size_t step = 16;
+ for (size_t offset = 0; offset + step <= data_slice.size(); offset += step) {
+ state.encrypt(data_slice.ubegin() + offset, data_slice.ubegin() + offset, static_cast<int>(step));
+ }
}
+ }
+};
+
+class AesIgeEncryptBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[DATA_SIZE];
+ td::UInt256 key;
+ td::UInt256 iv;
+
+ std::string get_description() const final {
+ return PSTRING() << "AES IGE OpenSSL encrypt [" << (DATA_SIZE >> 10) << "KB]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
td::Random::secure_bytes(key.raw, sizeof(key));
td::Random::secure_bytes(iv.raw, sizeof(iv));
}
- void run(int n) override {
+ void run(int n) final {
td::MutableSlice data_slice(data, DATA_SIZE);
+ td::AesIgeState state;
+ state.init(as_slice(key), as_slice(iv), true);
for (int i = 0; i < n; i++) {
- td::aes_ige_encrypt(key, &iv, data_slice, data_slice);
+ state.encrypt(data_slice, data_slice);
+ }
+ }
+};
+
+class AesIgeDecryptBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[DATA_SIZE];
+ td::UInt256 key;
+ td::UInt256 iv;
+
+ std::string get_description() const final {
+ return PSTRING() << "AES IGE OpenSSL decrypt [" << (DATA_SIZE >> 10) << "KB]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ td::Random::secure_bytes(key.raw, sizeof(key));
+ td::Random::secure_bytes(iv.raw, sizeof(iv));
+ }
+
+ void run(int n) final {
+ td::MutableSlice data_slice(data, DATA_SIZE);
+ td::AesIgeState state;
+ state.init(as_slice(key), as_slice(iv), false);
+ for (int i = 0; i < n; i++) {
+ state.decrypt(data_slice, data_slice);
+ }
+ }
+};
+
+class AesCtrBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[DATA_SIZE];
+ td::UInt256 key;
+ td::UInt128 iv;
+
+ std::string get_description() const final {
+ return PSTRING() << "AES CTR OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ td::Random::secure_bytes(key.raw, sizeof(key));
+ td::Random::secure_bytes(iv.raw, sizeof(iv));
+ }
+
+ void run(int n) final {
+ td::MutableSlice data_slice(data, DATA_SIZE);
+ td::AesCtrState state;
+ state.init(as_slice(key), as_slice(iv));
+ for (int i = 0; i < n; i++) {
+ state.encrypt(data_slice, data_slice);
+ }
+ }
+};
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+class AesCtrOpenSSLBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[DATA_SIZE];
+ td::UInt256 key;
+ td::UInt128 iv;
+
+ std::string get_description() const final {
+ return PSTRING() << "AES CTR RAW OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ td::Random::secure_bytes(key.raw, sizeof(key));
+ td::Random::secure_bytes(iv.raw, sizeof(iv));
+ }
+
+ void run(int n) final {
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ EVP_EncryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, key.raw, iv.raw);
+
+ td::MutableSlice data_slice(data, DATA_SIZE);
+ td::AesCtrState state;
+ state.init(as_slice(key), as_slice(iv));
+ for (int i = 0; i < n; i++) {
+ int len = 0;
+ EVP_EncryptUpdate(ctx, data_slice.ubegin(), &len, data_slice.ubegin(), DATA_SIZE);
+ CHECK(len == DATA_SIZE);
+ }
+
+ EVP_CIPHER_CTX_free(ctx);
+ }
+};
+#endif
+
+class AesCbcDecryptBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[DATA_SIZE];
+ td::UInt256 key;
+ td::UInt128 iv;
+
+ std::string get_description() const final {
+ return PSTRING() << "AES CBC Decrypt OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ td::Random::secure_bytes(as_slice(key));
+ td::Random::secure_bytes(as_slice(iv));
+ }
+
+ void run(int n) final {
+ td::MutableSlice data_slice(data, DATA_SIZE);
+ for (int i = 0; i < n; i++) {
+ td::aes_cbc_decrypt(as_slice(key), as_slice(iv), data_slice, data_slice);
+ }
+ }
+};
+
+class AesCbcEncryptBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[DATA_SIZE];
+ td::UInt256 key;
+ td::UInt128 iv;
+
+ std::string get_description() const final {
+ return PSTRING() << "AES CBC Encrypt OpenSSL [" << (DATA_SIZE >> 10) << "KB]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ td::Random::secure_bytes(as_slice(key));
+ td::Random::secure_bytes(as_slice(iv));
+ }
+
+ void run(int n) final {
+ td::MutableSlice data_slice(data, DATA_SIZE);
+ for (int i = 0; i < n; i++) {
+ td::aes_cbc_encrypt(as_slice(key), as_slice(iv), data_slice, data_slice);
+ }
+ }
+};
+
+template <bool use_state>
+class AesIgeShortBench final : public td::Benchmark {
+ public:
+ alignas(64) unsigned char data[SHORT_DATA_SIZE];
+ td::UInt256 key;
+ td::UInt256 iv;
+
+ std::string get_description() const final {
+ return PSTRING() << "AES IGE OpenSSL " << (use_state ? "EVP" : "C ") << "[" << SHORT_DATA_SIZE << "B]";
+ }
+
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
+ td::Random::secure_bytes(as_slice(key));
+ td::Random::secure_bytes(as_slice(iv));
+ }
+
+ void run(int n) final {
+ td::MutableSlice data_slice(data, SHORT_DATA_SIZE);
+ for (int i = 0; i < n; i++) {
+ if (use_state) {
+ td::AesIgeState ige;
+ ige.init(as_slice(key), as_slice(iv), false);
+ ige.decrypt(data_slice, data_slice);
+ } else {
+ td::aes_ige_decrypt(as_slice(key), as_slice(iv), data_slice, data_slice);
+ }
}
}
};
@@ -110,15 +409,15 @@ BENCH(TdRandFast, "td_rand_fast") {
#if !TD_THREAD_UNSUPPORTED
BENCH(SslRand, "ssl_rand_int32") {
std::vector<td::thread> v;
- std::atomic<td::uint32> sum;
+ std::atomic<td::uint32> sum{0};
for (int i = 0; i < 3; i++) {
- v.push_back(td::thread([&] {
+ v.emplace_back([&sum, n] {
td::int32 res = 0;
for (int j = 0; j < n; j++) {
res ^= td::Random::secure_int32();
}
sum += res;
- }));
+ });
}
for (auto &x : v) {
x.join();
@@ -147,22 +446,19 @@ BENCH(Pbkdf2, "pbkdf2") {
td::pbkdf2_sha256(password, salt, n, key);
}
-class Crc32Bench : public td::Benchmark {
+class Crc32Bench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
- std::string get_description() const override {
+ std::string get_description() const final {
return PSTRING() << "Crc32 zlib [" << (DATA_SIZE >> 10) << "KB]";
}
- void start_up() override {
- for (int i = 0; i < DATA_SIZE; i++) {
- data[i] = 123;
- data[i] = 0;
- }
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
- void run(int n) override {
+ void run(int n) final {
td::uint64 res = 0;
for (int i = 0; i < n; i++) {
res += td::crc32(td::Slice(data, DATA_SIZE));
@@ -171,22 +467,19 @@ class Crc32Bench : public td::Benchmark {
}
};
-class Crc64Bench : public td::Benchmark {
+class Crc64Bench final : public td::Benchmark {
public:
alignas(64) unsigned char data[DATA_SIZE];
- std::string get_description() const override {
+ std::string get_description() const final {
return PSTRING() << "Crc64 Anton [" << (DATA_SIZE >> 10) << "KB]";
}
- void start_up() override {
- for (int i = 0; i < DATA_SIZE; i++) {
- data[i] = 123;
- data[i] = 0;
- }
+ void start_up() final {
+ std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(123));
}
- void run(int n) override {
+ void run(int n) final {
td::uint64 res = 0;
for (int i = 0; i < n; i++) {
res += td::crc64(td::Slice(data, DATA_SIZE));
@@ -196,6 +489,20 @@ class Crc64Bench : public td::Benchmark {
};
int main() {
+ td::init_openssl_threads();
+ td::bench(AesCtrBench());
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ td::bench(AesCtrOpenSSLBench());
+#endif
+
+ td::bench(AesCbcDecryptBench());
+ td::bench(AesCbcEncryptBench());
+ td::bench(AesIgeShortBench<true>());
+ td::bench(AesIgeShortBench<false>());
+ td::bench(AesIgeEncryptBench());
+ td::bench(AesIgeDecryptBench());
+ td::bench(AesEcbBench());
+
td::bench(Pbkdf2Bench());
td::bench(RandBench());
td::bench(CppRandBench());
@@ -205,9 +512,14 @@ int main() {
td::bench(SslRandBench());
#endif
td::bench(SslRandBufBench());
+#if OPENSSL_VERSION_NUMBER <= 0x10100000L
td::bench(SHA1Bench());
- td::bench(AESBench());
+#endif
+ td::bench(SHA1ShortBench());
+ td::bench(SHA256ShortBench());
+ td::bench(SHA512ShortBench());
+ td::bench(HmacSha256ShortBench());
+ td::bench(HmacSha512ShortBench());
td::bench(Crc32Bench());
td::bench(Crc64Bench());
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_db.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_db.cpp
index dc768e9d9d..822a915938 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_db.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_db.cpp
@@ -1,28 +1,35 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/actor/actor.h"
-
#include "td/db/binlog/Binlog.h"
+#include "td/db/binlog/ConcurrentBinlog.h"
#include "td/db/BinlogKeyValue.h"
+#include "td/db/DbKey.h"
#include "td/db/SeqKeyValue.h"
+#include "td/db/SqliteConnectionSafe.h"
#include "td/db/SqliteDb.h"
#include "td/db/SqliteKeyValueAsync.h"
+#include "td/db/SqliteKeyValueSafe.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/benchmark.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include <memory>
template <class KeyValueT>
-class TdKvBench : public td::Benchmark {
- td::ConcurrentScheduler sched;
+class TdKvBench final : public td::Benchmark {
+ td::ConcurrentScheduler sched{1, 0};
td::string name_;
public:
@@ -30,27 +37,27 @@ class TdKvBench : public td::Benchmark {
name_ = std::move(name);
}
- td::string get_description() const override {
+ td::string get_description() const final {
return name_;
}
- class Main : public td::Actor {
+ class Main final : public td::Actor {
public:
explicit Main(int n) : n_(n) {
}
private:
- void loop() override {
+ void loop() final {
KeyValueT::destroy("test_tddb").ignore();
- class Worker : public Actor {
+ class Worker final : public Actor {
public:
Worker(int n, td::string db_name) : n_(n) {
kv_.init(db_name).ensure();
}
private:
- void loop() override {
+ void loop() final {
for (int i = 0; i < n_; i++) {
kv_.set(td::to_string(i % 10), td::to_string(i));
}
@@ -64,12 +71,11 @@ class TdKvBench : public td::Benchmark {
int n_;
};
- void start_up_n(int n) override {
- sched.init(1);
+ void start_up_n(int n) final {
sched.create_actor_unsafe<Main>(1, "Main", n).release();
}
- void run(int n) override {
+ void run(int n) final {
sched.start();
while (sched.run_main(10)) {
// empty
@@ -77,24 +83,23 @@ class TdKvBench : public td::Benchmark {
sched.finish();
}
- void tear_down() override {
+ void tear_down() final {
}
};
template <bool is_encrypted = false>
-class SqliteKVBench : public td::Benchmark {
+class SqliteKVBench final : public td::Benchmark {
td::SqliteDb db;
- td::string get_description() const override {
+ td::string get_description() const final {
return PSTRING() << "SqliteKV " << td::tag("is_encrypted", is_encrypted);
}
- void start_up() override {
+ void start_up() final {
td::string path = "testdb.sqlite";
td::SqliteDb::destroy(path).ignore();
if (is_encrypted) {
- td::SqliteDb::change_key(path, td::DbKey::password("cucumber"), td::DbKey::empty());
- db = td::SqliteDb::open_with_key(path, td::DbKey::password("cucumber")).move_as_ok();
+ db = td::SqliteDb::change_key(path, true, td::DbKey::password("cucumber"), td::DbKey::empty()).move_as_ok();
} else {
- db = td::SqliteDb::open_with_key(path, td::DbKey::empty()).move_as_ok();
+ db = td::SqliteDb::open_with_key(path, true, td::DbKey::empty()).move_as_ok();
}
db.exec("PRAGMA encoding=\"UTF-8\"").ensure();
db.exec("PRAGMA synchronous=NORMAL").ensure();
@@ -103,7 +108,7 @@ class SqliteKVBench : public td::Benchmark {
db.exec("DROP TABLE IF EXISTS KV").ensure();
db.exec("CREATE TABLE IF NOT EXISTS KV (k BLOB PRIMARY KEY, v BLOB)").ensure();
}
- void run(int n) override {
+ void run(int n) final {
auto stmt = db.get_statement("REPLACE INTO KV (k, v) VALUES(?1, ?2)").move_as_ok();
db.exec("BEGIN TRANSACTION").ensure();
for (int i = 0; i < n; i++) {
@@ -135,17 +140,17 @@ static td::Status init_db(td::SqliteDb &db) {
return td::Status::OK();
}
-class SqliteKeyValueAsyncBench : public td::Benchmark {
+class SqliteKeyValueAsyncBench final : public td::Benchmark {
public:
- td::string get_description() const override {
+ td::string get_description() const final {
return "SqliteKeyValueAsync";
}
- void start_up() override {
+ void start_up() final {
do_start_up().ensure();
scheduler_->start();
}
- void run(int n) override {
- auto guard = scheduler_->get_current_guard();
+ void run(int n) final {
+ auto guard = scheduler_->get_main_guard();
for (int i = 0; i < n; i++) {
auto key = td::to_string(i % 10);
@@ -153,10 +158,10 @@ class SqliteKeyValueAsyncBench : public td::Benchmark {
sqlite_kv_async_->set(key, value, td::Auto());
}
}
- void tear_down() override {
+ void tear_down() final {
scheduler_->run_main(0.1);
{
- auto guard = scheduler_->get_current_guard();
+ auto guard = scheduler_->get_main_guard();
sqlite_kv_async_.reset();
sqlite_kv_safe_.reset();
sql_connection_->close_and_destroy();
@@ -167,21 +172,20 @@ class SqliteKeyValueAsyncBench : public td::Benchmark {
}
private:
- std::unique_ptr<td::ConcurrentScheduler> scheduler_;
+ td::unique_ptr<td::ConcurrentScheduler> scheduler_;
std::shared_ptr<td::SqliteConnectionSafe> sql_connection_;
std::shared_ptr<td::SqliteKeyValueSafe> sqlite_kv_safe_;
- std::unique_ptr<td::SqliteKeyValueAsyncInterface> sqlite_kv_async_;
+ td::unique_ptr<td::SqliteKeyValueAsyncInterface> sqlite_kv_async_;
td::Status do_start_up() {
- scheduler_ = std::make_unique<td::ConcurrentScheduler>();
- scheduler_->init(1);
+ scheduler_ = td::make_unique<td::ConcurrentScheduler>(1, 0);
- auto guard = scheduler_->get_current_guard();
+ auto guard = scheduler_->get_main_guard();
td::string sql_db_name = "testdb.sqlite";
td::SqliteDb::destroy(sql_db_name).ignore();
- sql_connection_ = std::make_shared<td::SqliteConnectionSafe>(sql_db_name);
+ sql_connection_ = std::make_shared<td::SqliteConnectionSafe>(sql_db_name, td::DbKey::empty());
auto &db = sql_connection_->get();
TRY_STATUS(init_db(db));
@@ -192,31 +196,31 @@ class SqliteKeyValueAsyncBench : public td::Benchmark {
}
};
-class SeqKvBench : public td::Benchmark {
- td::string get_description() const override {
+class SeqKvBench final : public td::Benchmark {
+ td::string get_description() const final {
return "SeqKvBench";
}
td::SeqKeyValue kv;
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
- kv.set(td::to_string(i % 10), td::to_string(i));
+ kv.set(PSLICE() << i % 10, PSLICE() << i);
}
}
};
template <bool is_encrypted = false>
-class BinlogKeyValueBench : public td::Benchmark {
- td::string get_description() const override {
+class BinlogKeyValueBench final : public td::Benchmark {
+ td::string get_description() const final {
return PSTRING() << "BinlogKeyValue " << td::tag("is_encrypted", is_encrypted);
}
td::BinlogKeyValue<td::Binlog> kv;
- void start_up() override {
+ void start_up() final {
td::SqliteDb::destroy("test_binlog").ignore();
kv.init("test_binlog", is_encrypted ? td::DbKey::password("cucumber") : td::DbKey::empty()).ensure();
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
kv.set(td::to_string(i % 10), td::to_string(i));
}
@@ -233,5 +237,4 @@ int main() {
bench(TdKvBench<td::BinlogKeyValue<td::Binlog>>("BinlogKeyValue<Binlog>"));
bench(TdKvBench<td::BinlogKeyValue<td::ConcurrentBinlog>>("BinlogKeyValue<ConcurrentBinlog>"));
bench(SeqKvBench());
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_empty.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_empty.cpp
index f6718152b4..ae2068c050 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_empty.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_empty.cpp
@@ -1,9 +1,8 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
int main() {
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_handshake.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_handshake.cpp
index 08d04f009c..f518fe03b0 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_handshake.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_handshake.cpp
@@ -1,15 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/benchmark.h" // for bench, do_not_optimize_away, etc
-
-#include "td/mtproto/crypto.h"
+#include "td/mtproto/DhCallback.h"
+#include "td/mtproto/DhHandshake.h"
#include "td/utils/base64.h"
-#include "td/utils/logging.h"
+#include "td/utils/benchmark.h"
+#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include <map>
@@ -18,59 +18,55 @@
#include <semaphore.h>
#endif
-namespace td {
-
-static int32 g = 3;
-static string prime_base64 =
+static td::int32 g = 3;
+static td::string prime_base64 =
"xxyuucaxyQSObFIvcPE_c5gNQCOOPiHBSTTQN1Y9kw9IGYoKp8FAWCKUk9IlMPTb-jNvbgrJJROVQ67UTM58NyD9UfaUWHBaxozU_mtrE6vcl0ZRKW"
"kyhFTxj6-MWV9kJHf-lrsqlB1bzR1KyMxJiAcI-ps3jjxPOpBgvuZ8-aSkppWBEFGQfhYnU7VrD2tBDbp02KhLKhSzFE4O8ShHVP0X7ZUNWWW0ud1G"
"WC2xF40WnGvEZbDW_5yjko_vW5rk5Bj8Feg-vqD4f6n_Xu1wBQ3tKEn0e_lZ2VaFDOkphR8NgRX2NbEF7i5OFdBLJFS_b0-t8DSxBAMRnNjjuS_MW"
"w";
-class HandshakeBench : public Benchmark {
- std::string get_description() const override {
+class HandshakeBench final : public td::Benchmark {
+ td::string get_description() const final {
return "Handshake";
}
- class FakeDhCallback : public DhCallback {
+ class FakeDhCallback final : public td::mtproto::DhCallback {
public:
- int is_good_prime(Slice prime_str) const override {
+ int is_good_prime(td::Slice prime_str) const final {
auto it = cache.find(prime_str.str());
if (it == cache.end()) {
return -1;
}
return it->second;
}
- void add_good_prime(Slice prime_str) const override {
+ void add_good_prime(td::Slice prime_str) const final {
cache[prime_str.str()] = 1;
}
- void add_bad_prime(Slice prime_str) const override {
+ void add_bad_prime(td::Slice prime_str) const final {
cache[prime_str.str()] = 0;
}
- mutable std::map<string, int> cache;
+ mutable std::map<td::string, int> cache;
} dh_callback;
- void run(int n) override {
- DhHandshake a;
- DhHandshake b;
- auto prime = base64url_decode(prime_base64).move_as_ok();
+ void run(int n) final {
+ td::mtproto::DhHandshake a;
+ td::mtproto::DhHandshake b;
+ auto prime = td::base64url_decode(prime_base64).move_as_ok();
+ td::mtproto::DhHandshake::check_config(g, prime, &dh_callback).ensure();
for (int i = 0; i < n; i += 2) {
a.set_config(g, prime);
b.set_config(g, prime);
b.set_g_a(a.get_g_b());
a.set_g_a(b.get_g_b());
- a.run_checks(&dh_callback).ensure();
- b.run_checks(&dh_callback).ensure();
+ a.run_checks(true, &dh_callback).ensure();
+ b.run_checks(true, &dh_callback).ensure();
auto a_key = a.gen_key();
auto b_key = b.gen_key();
CHECK(a_key.first == b_key.first);
}
}
};
-} // namespace td
int main() {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
- td::bench(td::HandshakeBench());
- return 0;
+ td::bench(HandshakeBench());
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_http.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_http.cpp
index 6958a5b313..70fdb909c2 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_http.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_http.cpp
@@ -1,15 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/actor/actor.h"
-
#include "td/net/HttpOutboundConnection.h"
#include "td/net/HttpQuery.h"
+#include "td/net/SslStream.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/buffer.h"
+#include "td/utils/BufferedFd.h"
#include "td/utils/logging.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/SocketFd.h"
@@ -18,50 +21,52 @@
#include <atomic>
#include <limits>
-namespace td {
-
std::atomic<int> counter;
-class HttpClient : public HttpOutboundConnection::Callback {
- void start_up() override {
- IPAddress addr;
+
+class HttpClient final : public td::HttpOutboundConnection::Callback {
+ void start_up() final {
+ td::IPAddress addr;
addr.init_ipv4_port("127.0.0.1", 8082).ensure();
- auto fd = SocketFd::open(addr);
- CHECK(fd.is_ok()) << fd.error();
- connection_ =
- create_actor<HttpOutboundConnection>("Connect", fd.move_as_ok(), std::numeric_limits<size_t>::max(), 0, 0,
- ActorOwn<HttpOutboundConnection::Callback>(actor_id(this)));
+ auto fd = td::SocketFd::open(addr);
+ LOG_CHECK(fd.is_ok()) << fd.error();
+ connection_ = td::create_actor<td::HttpOutboundConnection>(
+ "Connect", td::BufferedFd<td::SocketFd>(fd.move_as_ok()), td::SslStream{}, std::numeric_limits<size_t>::max(),
+ 0, 0, td::ActorOwn<td::HttpOutboundConnection::Callback>(actor_id(this)));
yield();
cnt_ = 100000;
counter++;
}
- void tear_down() override {
+
+ void tear_down() final {
if (--counter == 0) {
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
}
- void loop() override {
+
+ void loop() final {
if (cnt_-- < 0) {
return stop();
}
- send_closure(connection_, &HttpOutboundConnection::write_next, BufferSlice("GET / HTTP/1.1\r\n\r\n"));
- send_closure(connection_, &HttpOutboundConnection::write_ok);
+ send_closure(connection_, &td::HttpOutboundConnection::write_next, td::BufferSlice("GET / HTTP/1.1\r\n\r\n"));
+ send_closure(connection_, &td::HttpOutboundConnection::write_ok);
LOG(INFO) << "SEND";
}
- void handle(HttpQueryPtr result) override {
+
+ void handle(td::unique_ptr<td::HttpQuery> result) final {
loop();
}
- void on_connection_error(Status error) override {
+
+ void on_connection_error(td::Status error) final {
LOG(ERROR) << "ERROR: " << error;
}
- ActorOwn<HttpOutboundConnection> connection_;
- int cnt_;
+ td::ActorOwn<td::HttpOutboundConnection> connection_;
+ int cnt_ = 0;
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- auto scheduler = make_unique<ConcurrentScheduler>();
- scheduler->init(0);
+ auto scheduler = td::make_unique<td::ConcurrentScheduler>(0, 0);
scheduler->create_actor_unsafe<HttpClient>(0, "Client1").release();
scheduler->create_actor_unsafe<HttpClient>(0, "Client2").release();
scheduler->start();
@@ -69,10 +74,4 @@ int main() {
// empty
}
scheduler->finish();
- return 0;
-}
-} // namespace td
-
-int main() {
- return td::main();
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_http_reader.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_http_reader.cpp
index 2afe2d73ff..403af06bec 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_http_reader.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_http_reader.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,19 +9,20 @@
#include "td/utils/benchmark.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/find_boundary.h"
#include "td/utils/logging.h"
static std::string http_query = "GET / HTTP/1.1\r\nConnection:keep-alive\r\nhost:127.0.0.1:8080\r\n\r\n";
static const size_t block_size = 2500;
-class HttpReaderBench : public td::Benchmark {
- std::string get_description() const override {
+class HttpReaderBench final : public td::Benchmark {
+ std::string get_description() const final {
return "HttpReaderBench";
}
- void run(int n) override {
- int cnt = static_cast<int>(block_size / http_query.size());
+ void run(int n) final {
+ auto cnt = static_cast<int>(block_size / http_query.size());
td::HttpQuery q;
int parsed = 0;
int sent = 0;
@@ -45,27 +46,26 @@ class HttpReaderBench : public td::Benchmark {
td::ChainBufferReader reader_;
td::HttpReader http_reader_;
- void start_up() override {
- writer_ = td::ChainBufferWriter::create_empty();
+ void start_up() final {
reader_ = writer_.extract_reader();
http_reader_.init(&reader_, 10000, 0);
}
};
-class BufferBench : public td::Benchmark {
- std::string get_description() const override {
+class BufferBench final : public td::Benchmark {
+ std::string get_description() const final {
return "BufferBench";
}
- void run(int n) override {
- int cnt = static_cast<int>(block_size / http_query.size());
+ void run(int n) final {
+ auto cnt = static_cast<int>(block_size / http_query.size());
for (int i = 0; i < n; i += cnt) {
for (int j = 0; j < cnt; j++) {
writer_.append(http_query);
}
reader_.sync_with_writer();
for (int j = 0; j < cnt; j++) {
- reader_.cut_head(http_query.size());
+ auto result = reader_.cut_head(http_query.size());
}
}
}
@@ -73,19 +73,18 @@ class BufferBench : public td::Benchmark {
td::ChainBufferReader reader_;
td::HttpReader http_reader_;
- void start_up() override {
- writer_ = td::ChainBufferWriter::create_empty();
+ void start_up() final {
reader_ = writer_.extract_reader();
}
};
-class FindBoundaryBench : public td::Benchmark {
- std::string get_description() const override {
+class FindBoundaryBench final : public td::Benchmark {
+ std::string get_description() const final {
return "FindBoundaryBench";
}
- void run(int n) override {
- int cnt = static_cast<int>(block_size / http_query.size());
+ void run(int n) final {
+ auto cnt = static_cast<int>(block_size / http_query.size());
for (int i = 0; i < n; i += cnt) {
for (int j = 0; j < cnt; j++) {
writer_.append(http_query);
@@ -95,7 +94,7 @@ class FindBoundaryBench : public td::Benchmark {
size_t len = 0;
find_boundary(reader_.clone(), "\r\n\r\n", len);
CHECK(size_t(len) + 4 == http_query.size());
- reader_.cut_head(len + 2);
+ auto result = reader_.cut_head(len + 2);
reader_.advance(2);
}
}
@@ -104,8 +103,7 @@ class FindBoundaryBench : public td::Benchmark {
td::ChainBufferReader reader_;
td::HttpReader http_reader_;
- void start_up() override {
- writer_ = td::ChainBufferWriter::create_empty();
+ void start_up() final {
reader_ = writer_.extract_reader();
}
};
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_http_server.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_http_server.cpp
index c48e8b4a67..33cde9ef8c 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_http_server.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_http_server.cpp
@@ -1,31 +1,32 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/actor/actor.h"
-
#include "td/net/HttpHeaderCreator.h"
#include "td/net/HttpInboundConnection.h"
#include "td/net/HttpQuery.h"
#include "td/net/TcpListener.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+
#include "td/utils/buffer.h"
+#include "td/utils/BufferedFd.h"
#include "td/utils/logging.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
-namespace td {
-
static int cnt = 0;
-class HelloWorld : public HttpInboundConnection::Callback {
+
+class HelloWorld final : public td::HttpInboundConnection::Callback {
public:
- void handle(HttpQueryPtr query, ActorOwn<HttpInboundConnection> connection) override {
+ void handle(td::unique_ptr<td::HttpQuery> query, td::ActorOwn<td::HttpInboundConnection> connection) final {
// LOG(ERROR) << *query;
- HttpHeaderCreator hc;
- Slice content = "hello world";
- //auto content = BufferSlice("hello world");
+ td::HttpHeaderCreator hc;
+ td::Slice content = "hello world";
+ //auto content = td::BufferSlice("hello world");
hc.init_ok();
hc.set_keep_alive();
hc.set_content_size(content.size());
@@ -35,55 +36,49 @@ class HelloWorld : public HttpInboundConnection::Callback {
auto res = hc.finish(content);
LOG_IF(FATAL, res.is_error()) << res.error();
- send_closure(connection, &HttpInboundConnection::write_next, BufferSlice(res.ok()));
- send_closure(connection.release(), &HttpInboundConnection::write_ok);
+ send_closure(connection, &td::HttpInboundConnection::write_next, td::BufferSlice(res.ok()));
+ send_closure(connection.release(), &td::HttpInboundConnection::write_ok);
}
- void hangup() override {
+ void hangup() final {
LOG(ERROR) << "CLOSE " << cnt--;
stop();
}
};
const int N = 0;
-class Server : public TcpListener::Callback {
+class Server final : public td::TcpListener::Callback {
public:
- void start_up() override {
- listener_ = create_actor<TcpListener>("Listener", 8082, ActorOwn<TcpListener::Callback>(actor_id(this)));
+ void start_up() final {
+ listener_ =
+ td::create_actor<td::TcpListener>("Listener", 8082, td::ActorOwn<td::TcpListener::Callback>(actor_id(this)));
}
- void accept(SocketFd fd) override {
+ void accept(td::SocketFd fd) final {
LOG(ERROR) << "ACCEPT " << cnt++;
pos_++;
auto scheduler_id = pos_ % (N != 0 ? N : 1) + (N != 0);
- create_actor_on_scheduler<HttpInboundConnection>("HttpInboundConnection", scheduler_id, std::move(fd), 1024 * 1024,
- 0, 0,
- create_actor_on_scheduler<HelloWorld>("HelloWorld", scheduler_id))
+ td::create_actor_on_scheduler<td::HttpInboundConnection>(
+ "HttpInboundConnection", scheduler_id, td::BufferedFd<td::SocketFd>(std::move(fd)), 1024 * 1024, 0, 0,
+ td::create_actor_on_scheduler<HelloWorld>("HelloWorld", scheduler_id))
.release();
}
- void hangup() override {
+ void hangup() final {
// may be it should be default?..
- LOG(ERROR) << "hangup..";
+ LOG(ERROR) << "Hanging up..";
stop();
}
private:
- ActorOwn<TcpListener> listener_;
+ td::ActorOwn<td::TcpListener> listener_;
int pos_{0};
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- auto scheduler = make_unique<ConcurrentScheduler>();
- scheduler->init(N);
+ auto scheduler = td::make_unique<td::ConcurrentScheduler>(N, 0);
scheduler->create_actor_unsafe<Server>(0, "Server").release();
scheduler->start();
while (scheduler->run_main(10)) {
// empty
}
scheduler->finish();
- return 0;
-}
-} // namespace td
-
-int main() {
- return td::main();
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_http_server_cheat.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_http_server_cheat.cpp
index da6fbbd713..8bbd768b40 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_http_server_cheat.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_http_server_cheat.cpp
@@ -1,49 +1,46 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/actor/actor.h"
-
#include "td/net/HttpHeaderCreator.h"
-#include "td/net/HttpInboundConnection.h"
#include "td/net/TcpListener.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+
#include "td/utils/buffer.h"
#include "td/utils/logging.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include <array>
-namespace td {
-
-// HttpInboundConnection header
static int cnt = 0;
-class HelloWorld : public Actor {
+
+class HelloWorld final : public td::Actor {
public:
- explicit HelloWorld(SocketFd socket_fd) : socket_fd_(std::move(socket_fd)) {
+ explicit HelloWorld(td::SocketFd socket_fd) : socket_fd_(std::move(socket_fd)) {
}
private:
- SocketFd socket_fd_;
+ td::SocketFd socket_fd_;
std::array<char, 1024> read_buf;
size_t read_new_lines{0};
- std::string hello_;
- std::string write_buf_;
+ td::string hello_;
+ td::string write_buf_;
size_t write_pos_{0};
- void start_up() override {
- socket_fd_.get_fd().set_observer(this);
- subscribe(socket_fd_.get_fd());
- HttpHeaderCreator hc;
- Slice content = "hello world";
- //auto content = BufferSlice("hello world");
+ void start_up() final {
+ td::Scheduler::subscribe(socket_fd_.get_poll_info().extract_pollable_fd(this));
+ td::HttpHeaderCreator hc;
+ td::Slice content = "hello world";
+ //auto content = td::BufferSlice("hello world");
hc.init_ok();
hc.set_keep_alive();
hc.set_content_size(content.size());
@@ -53,36 +50,37 @@ class HelloWorld : public Actor {
hello_ = hc.finish(content).ok().str();
}
- void loop() override {
+ void loop() final {
auto status = do_loop();
if (status.is_error()) {
- unsubscribe(socket_fd_.get_fd());
+ td::Scheduler::unsubscribe(socket_fd_.get_poll_info().get_pollable_fd_ref());
stop();
LOG(ERROR) << "CLOSE: " << status;
}
}
- Status do_loop() {
+ td::Status do_loop() {
+ sync_with_poll(socket_fd_);
TRY_STATUS(read_loop());
TRY_STATUS(write_loop());
- if (can_close(socket_fd_)) {
- return Status::Error("CLOSE");
+ if (can_close_local(socket_fd_)) {
+ return td::Status::Error("CLOSE");
}
- return Status::OK();
+ return td::Status::OK();
}
- Status write_loop() {
- while (can_write(socket_fd_) && write_pos_ < write_buf_.size()) {
- TRY_RESULT(written, socket_fd_.write(Slice(write_buf_).substr(write_pos_)));
+ td::Status write_loop() {
+ while (can_write_local(socket_fd_) && write_pos_ < write_buf_.size()) {
+ TRY_RESULT(written, socket_fd_.write(td::Slice(write_buf_).substr(write_pos_)));
write_pos_ += written;
if (write_pos_ == write_buf_.size()) {
write_pos_ = 0;
write_buf_.clear();
}
}
- return Status::OK();
+ return td::Status::OK();
}
- Status read_loop() {
- while (can_read(socket_fd_)) {
- TRY_RESULT(read_size, socket_fd_.read(MutableSlice(read_buf.data(), read_buf.size())));
+ td::Status read_loop() {
+ while (can_read_local(socket_fd_)) {
+ TRY_RESULT(read_size, socket_fd_.read(td::MutableSlice(read_buf.data(), read_buf.size())));
for (size_t i = 0; i < read_size; i++) {
if (read_buf[i] == '\n') {
read_new_lines++;
@@ -93,46 +91,41 @@ class HelloWorld : public Actor {
}
}
}
- return Status::OK();
+ return td::Status::OK();
}
};
+
const int N = 0;
-class Server : public TcpListener::Callback {
+class Server final : public td::TcpListener::Callback {
public:
- void start_up() override {
- listener_ = create_actor<TcpListener>("Listener", 8082, ActorOwn<TcpListener::Callback>(actor_id(this)));
+ void start_up() final {
+ listener_ =
+ td::create_actor<td::TcpListener>("Listener", 8082, td::ActorOwn<td::TcpListener::Callback>(actor_id(this)));
}
- void accept(SocketFd fd) override {
+ void accept(td::SocketFd fd) final {
LOG(ERROR) << "ACCEPT " << cnt++;
pos_++;
auto scheduler_id = pos_ % (N != 0 ? N : 1) + (N != 0);
- create_actor_on_scheduler<HelloWorld>("HttpInboundConnection", scheduler_id, std::move(fd)).release();
+ td::create_actor_on_scheduler<HelloWorld>("HelloWorld", scheduler_id, std::move(fd)).release();
}
- void hangup() override {
+ void hangup() final {
// may be it should be default?..
- LOG(ERROR) << "hangup..";
+ LOG(ERROR) << "Hanging up..";
stop();
}
private:
- ActorOwn<TcpListener> listener_;
+ td::ActorOwn<td::TcpListener> listener_;
int pos_{0};
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- auto scheduler = make_unique<ConcurrentScheduler>();
- scheduler->init(N);
+ auto scheduler = td::make_unique<td::ConcurrentScheduler>(N, 0);
scheduler->create_actor_unsafe<Server>(0, "Server").release();
scheduler->start();
while (scheduler->run_main(10)) {
// empty
}
scheduler->finish();
- return 0;
-}
-} // namespace td
-
-int main() {
- return td::main();
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_http_server_fast.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_http_server_fast.cpp
index fbda47590b..4b140422f1 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_http_server_fast.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_http_server_fast.cpp
@@ -1,46 +1,48 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/actor/actor.h"
-
#include "td/net/HttpHeaderCreator.h"
#include "td/net/HttpQuery.h"
#include "td/net/HttpReader.h"
#include "td/net/TcpListener.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+
#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
#include "td/utils/logging.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
-namespace td {
-
-class HttpEchoConnection : public Actor {
+class HttpEchoConnection final : public td::Actor {
public:
- explicit HttpEchoConnection(SocketFd fd) : fd_(std::move(fd)) {
+ explicit HttpEchoConnection(td::SocketFd fd) : fd_(std::move(fd)) {
}
private:
- BufferedFd<SocketFd> fd_;
- HttpReader reader_;
- HttpQuery query_;
- void start_up() override {
- fd_.get_fd().set_observer(this);
- subscribe(fd_.get_fd());
+ td::BufferedFd<td::SocketFd> fd_;
+ td::HttpReader reader_;
+ td::HttpQuery query_;
+ void start_up() final {
+ td::Scheduler::subscribe(fd_.get_poll_info().extract_pollable_fd(this));
reader_.init(&fd_.input_buffer(), 1024 * 1024, 0);
}
+ void tear_down() final {
+ td::Scheduler::unsubscribe_before_close(fd_.get_poll_info().get_pollable_fd_ref());
+ fd_.close();
+ }
void handle_query() {
- query_ = HttpQuery();
- HttpHeaderCreator hc;
- Slice content = "hello world";
- //auto content = BufferSlice("hello world");
+ query_ = td::HttpQuery();
+ td::HttpHeaderCreator hc;
+ td::Slice content = "hello world";
+ //auto content = td::BufferSlice("hello world");
hc.init_ok();
hc.set_keep_alive();
hc.set_content_size(content.size());
@@ -51,20 +53,19 @@ class HttpEchoConnection : public Actor {
fd_.output_buffer().append(res.ok());
}
- void loop() override {
+ void loop() final {
+ sync_with_poll(fd_);
auto status = [&] {
TRY_STATUS(loop_read());
TRY_STATUS(loop_write());
- return Status::OK();
+ return td::Status::OK();
}();
- if (status.is_error() || can_close(fd_)) {
+ if (status.is_error() || can_close_local(fd_)) {
stop();
}
}
- Status loop_read() {
- if (can_read(fd_)) {
- TRY_STATUS(fd_.flush_read());
- }
+ td::Status loop_read() {
+ TRY_STATUS(fd_.flush_read());
while (true) {
TRY_RESULT(need, reader_.read_next(&query_));
if (need == 0) {
@@ -73,49 +74,43 @@ class HttpEchoConnection : public Actor {
break;
}
}
- return Status::OK();
+ return td::Status::OK();
}
- Status loop_write() {
+ td::Status loop_write() {
TRY_STATUS(fd_.flush_write());
- return Status::OK();
+ return td::Status::OK();
}
};
-const int N = 4;
-class Server : public TcpListener::Callback {
+const int N = 8;
+class Server final : public td::TcpListener::Callback {
public:
- void start_up() override {
- listener_ = create_actor<TcpListener>("Listener", 8082, ActorOwn<TcpListener::Callback>(actor_id(this)));
+ void start_up() final {
+ listener_ =
+ td::create_actor<td::TcpListener>("Listener", 8082, td::ActorOwn<td::TcpListener::Callback>(actor_id(this)));
}
- void accept(SocketFd fd) override {
+ void accept(td::SocketFd fd) final {
pos_++;
auto scheduler_id = pos_ % (N != 0 ? N : 1) + (N != 0);
- create_actor_on_scheduler<HttpEchoConnection>("HttpInboundConnection", scheduler_id, std::move(fd)).release();
+ td::create_actor_on_scheduler<HttpEchoConnection>("HttpEchoConnection", scheduler_id, std::move(fd)).release();
}
- void hangup() override {
- LOG(ERROR) << "hangup..";
+ void hangup() final {
+ LOG(ERROR) << "Hanging up..";
stop();
}
private:
- ActorOwn<TcpListener> listener_;
+ td::ActorOwn<td::TcpListener> listener_;
int pos_{0};
};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- auto scheduler = make_unique<ConcurrentScheduler>();
- scheduler->init(N);
+ auto scheduler = td::make_unique<td::ConcurrentScheduler>(N, 0);
scheduler->create_actor_unsafe<Server>(0, "Server").release();
scheduler->start();
while (scheduler->run_main(10)) {
// empty
}
scheduler->finish();
- return 0;
-}
-} // namespace td
-
-int main() {
- return td::main();
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_log.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_log.cpp
index a57b1b9b42..8c2628b8a9 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_log.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_log.cpp
@@ -1,16 +1,16 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/benchmark.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
#include <cstdio>
#include <fstream>
#include <iostream>
-#include <mutex>
#include <ostream>
#include <streambuf>
#include <string>
@@ -35,63 +35,63 @@ std::string create_tmp_file() {
#endif
}
-class IostreamWriteBench : public td::Benchmark {
+class IostreamWriteBench final : public td::Benchmark {
protected:
std::string file_name_;
std::ofstream stream;
- enum { buffer_size = 1 << 20 };
- char buffer[buffer_size];
+ static constexpr std::size_t BUFFER_SIZE = 1 << 20;
+ char buffer[BUFFER_SIZE];
public:
- std::string get_description() const override {
+ std::string get_description() const final {
return "ostream (to file, no buf, no flush)";
}
- void start_up() override {
+ void start_up() final {
file_name_ = create_tmp_file();
stream.open(file_name_.c_str());
CHECK(stream.is_open());
- // stream.rdbuf()->pubsetbuf(buffer, buffer_size);
+ // stream.rdbuf()->pubsetbuf(buffer, BUFFER_SIZE);
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
stream << "This is just for test" << 987654321 << '\n';
}
}
- void tear_down() override {
+ void tear_down() final {
stream.close();
unlink(file_name_.c_str());
}
};
-class FILEWriteBench : public td::Benchmark {
+class FILEWriteBench final : public td::Benchmark {
protected:
std::string file_name_;
FILE *file;
- enum { buffer_size = 1 << 20 };
- char buffer[buffer_size];
+ static constexpr std::size_t BUFFER_SIZE = 1 << 20;
+ char buffer[BUFFER_SIZE];
public:
- std::string get_description() const override {
+ std::string get_description() const final {
return "std::fprintf (to file, no buf, no flush)";
}
- void start_up() override {
+ void start_up() final {
file_name_ = create_tmp_file();
file = fopen(file_name_.c_str(), "w");
- // setvbuf(file, buffer, _IOFBF, buffer_size);
+ // setvbuf(file, buffer, _IOFBF, BUFFER_SIZE);
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
std::fprintf(file, "This is just for test%d\n", 987654321);
// std::fflush(file);
}
}
- void tear_down() override {
+ void tear_down() final {
std::fclose(file);
unlink(file_name_.c_str());
}
@@ -100,58 +100,56 @@ class FILEWriteBench : public td::Benchmark {
#if TD_ANDROID
#include <android/log.h>
#define ALOG(...) __android_log_print(ANDROID_LOG_VERBOSE, "XXX", __VA_ARGS__)
-class ALogWriteBench : public td::Benchmark {
+class ALogWriteBench final : public td::Benchmark {
public:
- std::string get_description() const override {
+ std::string get_description() const final {
return "android_log";
}
- void start_up() override {
+ void start_up() final {
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
ALOG("This is just for test%d\n", 987654321);
}
}
- void tear_down() override {
+ void tear_down() final {
}
};
#endif
-class LogWriteBench : public td::Benchmark {
+class LogWriteBench final : public td::Benchmark {
protected:
std::string file_name_;
std::ofstream stream;
std::streambuf *old_buf;
- enum { buffer_size = 1 << 20 };
- char buffer[buffer_size];
+ static constexpr std::size_t BUFFER_SIZE = 1 << 20;
+ char buffer[BUFFER_SIZE];
public:
- std::string get_description() const override {
+ std::string get_description() const final {
return "td_log (slow in debug mode)";
}
- void start_up() override {
+ void start_up() final {
file_name_ = create_tmp_file();
stream.open(file_name_.c_str());
CHECK(stream.is_open());
old_buf = std::cerr.rdbuf(stream.rdbuf());
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
LOG(DEBUG) << "This is just for test" << 987654321;
}
}
- void tear_down() override {
+ void tear_down() final {
stream.close();
unlink(file_name_.c_str());
std::cerr.rdbuf(old_buf);
}
};
-std::mutex mutex;
-
int main() {
td::bench(LogWriteBench());
#if TD_ANDROID
@@ -159,5 +157,4 @@ int main() {
#endif
td::bench(IostreamWriteBench());
td::bench(FILEWriteBench());
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_misc.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_misc.cpp
index bfbcea438b..56a6117bb4 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_misc.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_misc.cpp
@@ -1,11 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/telegram_api.hpp"
+
#include "td/utils/benchmark.h"
#include "td/utils/common.h"
+#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/port/Clocks.h"
#include "td/utils/port/EventFd.h"
@@ -15,9 +19,9 @@
#include "td/utils/port/Stat.h"
#include "td/utils/port/thread.h"
#include "td/utils/Slice.h"
-
-#include "td/telegram/telegram_api.h"
-#include "td/telegram/telegram_api.hpp"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+#include "td/utils/ThreadSafeCounter.h"
#if !TD_WINDOWS
#include <unistd.h>
@@ -28,37 +32,38 @@
#include <semaphore.h>
#endif
+#include <algorithm>
+#include <array>
#include <atomic>
#include <cstdint>
-
-namespace td {
+#include <set>
class F {
- uint32 &sum;
+ td::uint32 &sum;
public:
- explicit F(uint32 &sum) : sum(sum) {
+ explicit F(td::uint32 &sum) : sum(sum) {
}
template <class T>
void operator()(const T &x) const {
- sum += static_cast<uint32>(x.get_id());
+ sum += static_cast<td::uint32>(x.get_id());
}
};
BENCH(Call, "TL Call") {
- tl_object_ptr<telegram_api::Function> x = make_tl_object<telegram_api::account_getWallPapers>();
- uint32 res = 0;
+ td::tl_object_ptr<td::telegram_api::Function> x = td::make_tl_object<td::telegram_api::account_getWallPapers>(0);
+ td::uint32 res = 0;
F f(res);
for (int i = 0; i < n; i++) {
downcast_call(*x, f);
}
- do_not_optimize_away(res);
+ td::do_not_optimize_away(res);
}
#if !TD_EVENTFD_UNSUPPORTED
BENCH(EventFd, "EventFd") {
- EventFd fd;
+ td::EventFd fd;
fd.init();
for (int i = 0; i < n; i++) {
fd.release();
@@ -75,15 +80,15 @@ BENCH(NewInt, "new int + delete") {
res += reinterpret_cast<std::uintptr_t>(x);
delete x;
}
- do_not_optimize_away(res);
+ td::do_not_optimize_away(res);
}
-BENCH(NewObj, "new struct then delete") {
+BENCH(NewObj, "new struct, then delete") {
struct A {
- int32 a = 0;
- int32 b = 0;
- int32 c = 0;
- int32 d = 0;
+ td::int32 a = 0;
+ td::int32 b = 0;
+ td::int32 c = 0;
+ td::int32 d = 0;
};
std::uintptr_t res = 0;
A **ptr = new A *[n];
@@ -95,57 +100,57 @@ BENCH(NewObj, "new struct then delete") {
delete ptr[i];
}
delete[] ptr;
- do_not_optimize_away(res);
+ td::do_not_optimize_away(res);
}
#if !TD_THREAD_UNSUPPORTED
-BENCH(ThreadNew, "new struct then delete in several threads") {
- td::NewObjBench a, b;
- thread ta([&] { a.run(n / 2); });
- thread tb([&] { b.run(n - n / 2); });
+BENCH(ThreadNew, "new struct, then delete in 2 threads") {
+ NewObjBench a;
+ NewObjBench b;
+ td::thread ta([&] { a.run(n / 2); });
+ td::thread tb([&] { b.run(n - n / 2); });
ta.join();
tb.join();
}
#endif
-
-// Too hard for android clang (?)
+/*
+// Too hard for clang (?)
BENCH(Time, "Clocks::monotonic") {
double res = 0;
for (int i = 0; i < n; i++) {
- res += Clocks::monotonic();
+ res += td::Clocks::monotonic();
}
- do_not_optimize_away(res);
+ td::do_not_optimize_away(res);
}
-
+*/
#if !TD_WINDOWS
-class PipeBench : public Benchmark {
+class PipeBench final : public td::Benchmark {
public:
int p[2];
- PipeBench() {
- pipe(p);
- }
-
- string get_description() const override {
+ td::string get_description() const final {
return "pipe write + read int32";
}
- void start_up() override {
- pipe(p);
+ void start_up() final {
+ int res = pipe(p);
+ CHECK(res == 0);
}
- void run(int n) override {
+ void run(int n) final {
int res = 0;
for (int i = 0; i < n; i++) {
int val = 1;
- write(p[1], &val, sizeof(val));
- read(p[0], &val, sizeof(val));
+ auto write_len = write(p[1], &val, sizeof(val));
+ CHECK(write_len == sizeof(val));
+ auto read_len = read(p[0], &val, sizeof(val));
+ CHECK(read_len == sizeof(val));
res += val;
}
- do_not_optimize_away(res);
+ td::do_not_optimize_away(res);
}
- void tear_down() override {
+ void tear_down() final {
close(p[0]);
close(p[1]);
}
@@ -153,42 +158,42 @@ class PipeBench : public Benchmark {
#endif
#if TD_LINUX || TD_ANDROID || TD_TIZEN
-class SemBench : public Benchmark {
+class SemBench final : public td::Benchmark {
sem_t sem;
public:
- string get_description() const override {
+ td::string get_description() const final {
return "sem post + wait";
}
- void start_up() override {
+ void start_up() final {
int err = sem_init(&sem, 0, 0);
CHECK(err != -1);
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
sem_post(&sem);
sem_wait(&sem);
}
}
- void tear_down() override {
+ void tear_down() final {
sem_destroy(&sem);
}
};
#endif
#if !TD_WINDOWS
-class UtimeBench : public Benchmark {
+class UtimeBench final : public td::Benchmark {
public:
- void start_up() override {
- FileFd::open("test", FileFd::Flags::Create | FileFd::Flags::Write).move_as_ok().close();
+ void start_up() final {
+ td::FileFd::open("test", td::FileFd::Create | td::FileFd::Write).move_as_ok().close();
}
- string get_description() const override {
+ td::string get_description() const final {
return "utime";
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
int err = utime("test", nullptr);
CHECK(err >= 0);
@@ -203,73 +208,77 @@ class UtimeBench : public Benchmark {
#endif
BENCH(Pwrite, "pwrite") {
- auto fd = FileFd::open("test", FileFd::Flags::Create | FileFd::Flags::Write).move_as_ok();
+ auto fd = td::FileFd::open("test", td::FileFd::Create | td::FileFd::Write).move_as_ok();
for (int i = 0; i < n; i++) {
fd.pwrite("a", 0).ok();
}
fd.close();
}
-class CreateFileBench : public Benchmark {
- string get_description() const override {
+class CreateFileBench final : public td::Benchmark {
+ td::string get_description() const final {
return "create_file";
}
- void start_up() override {
- mkdir("A").ensure();
+ void start_up() final {
+ td::mkdir("A").ensure();
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < n; i++) {
- FileFd::open("A/" + to_string(i), FileFd::Flags::Write | FileFd::Flags::Create).move_as_ok().close();
+ td::FileFd::open(PSLICE() << "A/" << i, td::FileFd::Write | td::FileFd::Create).move_as_ok().close();
}
}
- void tear_down() override {
- auto status = td::walk_path("A/", [&](CSlice path, bool is_dir) {
- if (is_dir) {
- rmdir(path).ignore();
- } else {
- unlink(path).ignore();
+ void tear_down() final {
+ td::walk_path("A/", [&](td::CSlice path, auto type) {
+ if (type == td::WalkPath::Type::ExitDir) {
+ td::rmdir(path).ignore();
+ } else if (type == td::WalkPath::Type::NotDir) {
+ td::unlink(path).ignore();
}
- });
+ }).ignore();
}
};
-class WalkPathBench : public Benchmark {
- string get_description() const override {
+
+class WalkPathBench final : public td::Benchmark {
+ td::string get_description() const final {
return "walk_path";
}
- void start_up_n(int n) override {
- mkdir("A").ensure();
+ void start_up_n(int n) final {
+ td::mkdir("A").ensure();
for (int i = 0; i < n; i++) {
- FileFd::open("A/" + to_string(i), FileFd::Flags::Write | FileFd::Flags::Create).move_as_ok().close();
+ td::FileFd::open(PSLICE() << "A/" << i, td::FileFd::Write | td::FileFd::Create).move_as_ok().close();
}
}
- void run(int n) override {
+ void run(int n) final {
int cnt = 0;
- auto status = td::walk_path("A/", [&](CSlice path, bool is_dir) {
- stat(path).ok();
+ td::walk_path("A/", [&](td::CSlice path, auto type) {
+ if (type == td::WalkPath::Type::EnterDir) {
+ return;
+ }
+ td::stat(path).ok();
cnt++;
- });
- }
- void tear_down() override {
- auto status = td::walk_path("A/", [&](CSlice path, bool is_dir) {
- if (is_dir) {
- rmdir(path).ignore();
- } else {
- unlink(path).ignore();
+ }).ignore();
+ }
+ void tear_down() final {
+ td::walk_path("A/", [&](td::CSlice path, auto type) {
+ if (type == td::WalkPath::Type::ExitDir) {
+ td::rmdir(path).ignore();
+ } else if (type == td::WalkPath::Type::NotDir) {
+ td::unlink(path).ignore();
}
- });
+ }).ignore();
}
};
#if !TD_THREAD_UNSUPPORTED
template <int ThreadN = 2>
-class AtomicReleaseIncBench : public Benchmark {
- string get_description() const override {
+class AtomicReleaseIncBench final : public td::Benchmark {
+ td::string get_description() const final {
return PSTRING() << "AtomicReleaseInc" << ThreadN;
}
- static std::atomic<uint64> a_;
- void run(int n) override {
- std::vector<thread> threads;
+ static std::atomic<td::uint64> a_;
+ void run(int n) final {
+ td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
@@ -283,17 +292,17 @@ class AtomicReleaseIncBench : public Benchmark {
}
};
template <int ThreadN>
-std::atomic<uint64> AtomicReleaseIncBench<ThreadN>::a_;
+std::atomic<td::uint64> AtomicReleaseIncBench<ThreadN>::a_;
template <int ThreadN = 2>
-class AtomicReleaseCasIncBench : public Benchmark {
- string get_description() const override {
+class AtomicReleaseCasIncBench final : public td::Benchmark {
+ td::string get_description() const final {
return PSTRING() << "AtomicReleaseCasInc" << ThreadN;
}
- static std::atomic<uint64> a_;
- void run(int n) override {
- std::vector<thread> threads;
+ static std::atomic<td::uint64> a_;
+ void run(int n) final {
+ td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
@@ -309,16 +318,16 @@ class AtomicReleaseCasIncBench : public Benchmark {
}
};
template <int ThreadN>
-std::atomic<uint64> AtomicReleaseCasIncBench<ThreadN>::a_;
+std::atomic<td::uint64> AtomicReleaseCasIncBench<ThreadN>::a_;
-template <int ThreadN = 2>
-class RwMutexReadBench : public Benchmark {
- string get_description() const override {
+template <int ThreadN>
+class RwMutexReadBench final : public td::Benchmark {
+ td::string get_description() const final {
return PSTRING() << "RwMutexRead" << ThreadN;
}
- RwMutex mutex_;
- void run(int n) override {
- std::vector<thread> threads;
+ td::RwMutex mutex_;
+ void run(int n) final {
+ td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
@@ -331,14 +340,15 @@ class RwMutexReadBench : public Benchmark {
}
}
};
-template <int ThreadN = 2>
-class RwMutexWriteBench : public Benchmark {
- string get_description() const override {
+
+template <int ThreadN>
+class RwMutexWriteBench final : public td::Benchmark {
+ td::string get_description() const final {
return PSTRING() << "RwMutexWrite" << ThreadN;
}
- RwMutex mutex_;
- void run(int n) override {
- std::vector<thread> threads;
+ td::RwMutex mutex_;
+ void run(int n) final {
+ td::vector<td::thread> threads;
for (int i = 0; i < ThreadN; i++) {
threads.emplace_back([&] {
for (int i = 0; i < n / ThreadN; i++) {
@@ -351,42 +361,366 @@ class RwMutexWriteBench : public Benchmark {
}
}
};
+
+class ThreadSafeCounterBench final : public td::Benchmark {
+ static td::ThreadSafeCounter counter_;
+ int thread_count_;
+
+ td::string get_description() const final {
+ return PSTRING() << "ThreadSafeCounter" << thread_count_;
+ }
+ void run(int n) final {
+ counter_.clear();
+ td::vector<td::thread> threads;
+ for (int i = 0; i < thread_count_; i++) {
+ threads.emplace_back([n] {
+ for (int i = 0; i < n; i++) {
+ counter_.add(1);
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+ CHECK(counter_.sum() == n * thread_count_);
+ }
+
+ public:
+ explicit ThreadSafeCounterBench(int thread_count) : thread_count_(thread_count) {
+ }
+};
+td::ThreadSafeCounter ThreadSafeCounterBench::counter_;
+
+template <bool StrictOrder>
+class AtomicCounterBench final : public td::Benchmark {
+ static std::atomic<td::int64> counter_;
+ int thread_count_;
+
+ td::string get_description() const final {
+ return PSTRING() << "AtomicCounter" << thread_count_;
+ }
+ void run(int n) final {
+ counter_.store(0);
+ td::vector<td::thread> threads;
+ for (int i = 0; i < thread_count_; i++) {
+ threads.emplace_back([n] {
+ for (int i = 0; i < n; i++) {
+ counter_.fetch_add(1, StrictOrder ? std::memory_order_seq_cst : std::memory_order_relaxed);
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+ CHECK(counter_.load() == n * thread_count_);
+ }
+
+ public:
+ explicit AtomicCounterBench(int thread_count) : thread_count_(thread_count) {
+ }
+};
+template <bool StrictOrder>
+std::atomic<td::int64> AtomicCounterBench<StrictOrder>::counter_;
+
#endif
-} // namespace td
+
+class IdDuplicateCheckerOld {
+ public:
+ static td::string get_description() {
+ return "Old";
+ }
+ td::Status check(td::int64 message_id) {
+ if (saved_message_ids_.size() == MAX_SAVED_MESSAGE_IDS) {
+ auto oldest_message_id = *saved_message_ids_.begin();
+ if (message_id < oldest_message_id) {
+ return td::Status::Error(2, PSLICE() << "Ignore very old message_id "
+ << td::tag("oldest message_id", oldest_message_id)
+ << td::tag("got message_id", message_id));
+ }
+ }
+ if (saved_message_ids_.count(message_id) != 0) {
+ return td::Status::Error(1, PSLICE() << "Ignore duplicated message_id " << td::tag("message_id", message_id));
+ }
+
+ saved_message_ids_.insert(message_id);
+ if (saved_message_ids_.size() > MAX_SAVED_MESSAGE_IDS) {
+ saved_message_ids_.erase(saved_message_ids_.begin());
+ }
+ return td::Status::OK();
+ }
+
+ private:
+ static constexpr size_t MAX_SAVED_MESSAGE_IDS = 1000;
+ std::set<td::int64> saved_message_ids_;
+};
+
+template <size_t MAX_SAVED_MESSAGE_IDS>
+class IdDuplicateCheckerNew {
+ public:
+ static td::string get_description() {
+ return PSTRING() << "New" << MAX_SAVED_MESSAGE_IDS;
+ }
+ td::Status check(td::int64 message_id) {
+ auto insert_result = saved_message_ids_.insert(message_id);
+ if (!insert_result.second) {
+ return td::Status::Error(1, PSLICE() << "Ignore duplicated message_id " << td::tag("message_id", message_id));
+ }
+ if (saved_message_ids_.size() == MAX_SAVED_MESSAGE_IDS + 1) {
+ auto begin_it = saved_message_ids_.begin();
+ bool is_very_old = begin_it == insert_result.first;
+ saved_message_ids_.erase(begin_it);
+ if (is_very_old) {
+ return td::Status::Error(2, PSLICE() << "Ignore very old message_id "
+ << td::tag("oldest message_id", *saved_message_ids_.begin())
+ << td::tag("got message_id", message_id));
+ }
+ }
+ return td::Status::OK();
+ }
+
+ private:
+ std::set<td::int64> saved_message_ids_;
+};
+
+class IdDuplicateCheckerNewOther {
+ public:
+ static td::string get_description() {
+ return "NewOther";
+ }
+ td::Status check(td::int64 message_id) {
+ if (!saved_message_ids_.insert(message_id).second) {
+ return td::Status::Error(1, PSLICE() << "Ignore duplicated message_id " << td::tag("message_id", message_id));
+ }
+ if (saved_message_ids_.size() == MAX_SAVED_MESSAGE_IDS + 1) {
+ auto begin_it = saved_message_ids_.begin();
+ bool is_very_old = *begin_it == message_id;
+ saved_message_ids_.erase(begin_it);
+ if (is_very_old) {
+ return td::Status::Error(2, PSLICE() << "Ignore very old message_id "
+ << td::tag("oldest message_id", *saved_message_ids_.begin())
+ << td::tag("got message_id", message_id));
+ }
+ }
+ return td::Status::OK();
+ }
+
+ private:
+ static constexpr size_t MAX_SAVED_MESSAGE_IDS = 1000;
+ std::set<td::int64> saved_message_ids_;
+};
+
+class IdDuplicateCheckerNewSimple {
+ public:
+ static td::string get_description() {
+ return "NewSimple";
+ }
+ td::Status check(td::int64 message_id) {
+ auto insert_result = saved_message_ids_.insert(message_id);
+ if (!insert_result.second) {
+ return td::Status::Error(1, "Ignore duplicated message_id");
+ }
+ if (saved_message_ids_.size() == MAX_SAVED_MESSAGE_IDS + 1) {
+ auto begin_it = saved_message_ids_.begin();
+ bool is_very_old = begin_it == insert_result.first;
+ saved_message_ids_.erase(begin_it);
+ if (is_very_old) {
+ return td::Status::Error(2, "Ignore very old message_id");
+ }
+ }
+ return td::Status::OK();
+ }
+
+ private:
+ static constexpr size_t MAX_SAVED_MESSAGE_IDS = 1000;
+ std::set<td::int64> saved_message_ids_;
+};
+
+template <size_t max_size>
+class IdDuplicateCheckerArray {
+ public:
+ static td::string get_description() {
+ return PSTRING() << "Array" << max_size;
+ }
+ td::Status check(td::int64 message_id) {
+ if (end_pos_ == 2 * max_size) {
+ std::copy_n(&saved_message_ids_[max_size], max_size, &saved_message_ids_[0]);
+ end_pos_ = max_size;
+ }
+ if (end_pos_ == 0 || message_id > saved_message_ids_[end_pos_ - 1]) {
+ // fast path
+ saved_message_ids_[end_pos_++] = message_id;
+ return td::Status::OK();
+ }
+ if (end_pos_ >= max_size && message_id < saved_message_ids_[0]) {
+ return td::Status::Error(2, PSLICE() << "Ignore very old message_id "
+ << td::tag("oldest message_id", saved_message_ids_[0])
+ << td::tag("got message_id", message_id));
+ }
+ auto it = std::lower_bound(&saved_message_ids_[0], &saved_message_ids_[end_pos_], message_id);
+ if (*it == message_id) {
+ return td::Status::Error(1, PSLICE() << "Ignore duplicated message_id " << td::tag("message_id", message_id));
+ }
+ std::copy_backward(it, &saved_message_ids_[end_pos_], &saved_message_ids_[end_pos_ + 1]);
+ *it = message_id;
+ ++end_pos_;
+ return td::Status::OK();
+ }
+
+ private:
+ std::array<td::int64, 2 * max_size> saved_message_ids_;
+ std::size_t end_pos_ = 0;
+};
+
+template <class T>
+class DuplicateCheckerBench final : public td::Benchmark {
+ td::string get_description() const final {
+ return PSTRING() << "DuplicateCheckerBench" << T::get_description();
+ }
+ void run(int n) final {
+ T checker_;
+ for (int i = 0; i < n; i++) {
+ checker_.check(i).ensure();
+ }
+ }
+};
+
+template <class T>
+class DuplicateCheckerBenchRepeat final : public td::Benchmark {
+ td::string get_description() const final {
+ return PSTRING() << "DuplicateCheckerBenchRepeat" << T::get_description();
+ }
+ void run(int n) final {
+ T checker_;
+ for (int i = 0; i < n; i++) {
+ auto iter = i >> 10;
+ auto pos = i - (iter << 10);
+ if (pos < 768) {
+ if (iter >= 3 && pos == 0) {
+ auto error = checker_.check((iter - 3) * 768 + pos);
+ CHECK(error.error().code() == 2);
+ }
+ checker_.check(iter * 768 + pos).ensure();
+ } else {
+ checker_.check(iter * 768 + pos - 256).ensure_error();
+ }
+ }
+ }
+};
+
+template <class T>
+class DuplicateCheckerBenchRepeatOnly final : public td::Benchmark {
+ td::string get_description() const final {
+ return PSTRING() << "DuplicateCheckerBenchRepeatOnly" << T::get_description();
+ }
+ void run(int n) final {
+ T checker_;
+ for (int i = 0; i < n; i++) {
+ auto result = checker_.check(i & 255);
+ CHECK(result.is_error() == (i >= 256));
+ }
+ }
+};
+
+template <class T>
+class DuplicateCheckerBenchReverse final : public td::Benchmark {
+ td::string get_description() const final {
+ return PSTRING() << "DuplicateCheckerBenchReverseAdd" << T::get_description();
+ }
+ void run(int n) final {
+ T checker_;
+ for (int i = 0; i < n; i++) {
+ auto pos = i & 255;
+ checker_.check(i - pos + (255 - pos)).ensure();
+ }
+ }
+};
+
+template <class T>
+class DuplicateCheckerBenchEvenOdd final : public td::Benchmark {
+ td::string get_description() const final {
+ return PSTRING() << "DuplicateCheckerBenchEvenOdd" << T::get_description();
+ }
+ void run(int n) final {
+ T checker_;
+ for (int i = 0; i < n; i++) {
+ auto pos = i & 255;
+ checker_.check(i - pos + (pos * 2) % 256 + (pos * 2) / 256).ensure();
+ }
+ }
+};
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
+
+ td::bench(DuplicateCheckerBenchEvenOdd<IdDuplicateCheckerNew<1000>>());
+ td::bench(DuplicateCheckerBenchEvenOdd<IdDuplicateCheckerNew<300>>());
+ td::bench(DuplicateCheckerBenchEvenOdd<IdDuplicateCheckerArray<1000>>());
+ td::bench(DuplicateCheckerBenchEvenOdd<IdDuplicateCheckerArray<300>>());
+
+ td::bench(DuplicateCheckerBenchReverse<IdDuplicateCheckerNew<1000>>());
+ td::bench(DuplicateCheckerBenchReverse<IdDuplicateCheckerNew<300>>());
+ td::bench(DuplicateCheckerBenchReverse<IdDuplicateCheckerArray<1000>>());
+ td::bench(DuplicateCheckerBenchReverse<IdDuplicateCheckerArray<300>>());
+
+ td::bench(DuplicateCheckerBenchRepeatOnly<IdDuplicateCheckerNew<1000>>());
+ td::bench(DuplicateCheckerBenchRepeatOnly<IdDuplicateCheckerNew<300>>());
+ td::bench(DuplicateCheckerBenchRepeatOnly<IdDuplicateCheckerArray<1000>>());
+ td::bench(DuplicateCheckerBenchRepeatOnly<IdDuplicateCheckerArray<300>>());
+
+ td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerOld>());
+ td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerNew<1000>>());
+ td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerNewOther>());
+ td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerNewSimple>());
+ td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerNew<300>>());
+ td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerArray<1000>>());
+ td::bench(DuplicateCheckerBenchRepeat<IdDuplicateCheckerArray<300>>());
+
+ td::bench(DuplicateCheckerBench<IdDuplicateCheckerOld>());
+ td::bench(DuplicateCheckerBench<IdDuplicateCheckerNew<1000>>());
+ td::bench(DuplicateCheckerBench<IdDuplicateCheckerNewOther>());
+ td::bench(DuplicateCheckerBench<IdDuplicateCheckerNewSimple>());
+ td::bench(DuplicateCheckerBench<IdDuplicateCheckerNew<300>>());
+ td::bench(DuplicateCheckerBench<IdDuplicateCheckerNew<100>>());
+ td::bench(DuplicateCheckerBench<IdDuplicateCheckerNew<10>>());
+ td::bench(DuplicateCheckerBench<IdDuplicateCheckerArray<1000>>());
+ td::bench(DuplicateCheckerBench<IdDuplicateCheckerArray<300>>());
+
#if !TD_THREAD_UNSUPPORTED
- td::bench(td::AtomicReleaseIncBench<1>());
- td::bench(td::AtomicReleaseIncBench<2>());
- td::bench(td::AtomicReleaseCasIncBench<1>());
- td::bench(td::AtomicReleaseCasIncBench<2>());
- td::bench(td::RwMutexWriteBench<1>());
- td::bench(td::RwMutexReadBench<1>());
- td::bench(td::RwMutexWriteBench<>());
- td::bench(td::RwMutexReadBench<>());
+ for (int i = 1; i <= 16; i *= 2) {
+ td::bench(ThreadSafeCounterBench(i));
+ td::bench(AtomicCounterBench<false>(i));
+ td::bench(AtomicCounterBench<true>(i));
+ }
+
+ td::bench(AtomicReleaseIncBench<1>());
+ td::bench(AtomicReleaseIncBench<2>());
+ td::bench(AtomicReleaseCasIncBench<1>());
+ td::bench(AtomicReleaseCasIncBench<2>());
+ td::bench(RwMutexWriteBench<1>());
+ td::bench(RwMutexReadBench<1>());
+ td::bench(RwMutexWriteBench<2>());
+ td::bench(RwMutexReadBench<2>());
#endif
#if !TD_WINDOWS
- td::bench(td::UtimeBench());
+ td::bench(UtimeBench());
#endif
- td::bench(td::WalkPathBench());
- td::bench(td::CreateFileBench());
- td::bench(td::PwriteBench());
+ td::bench(WalkPathBench());
+ td::bench(CreateFileBench());
+ td::bench(PwriteBench());
- td::bench(td::CallBench());
+ td::bench(CallBench());
#if !TD_THREAD_UNSUPPORTED
- td::bench(td::ThreadNewBench());
+ td::bench(ThreadNewBench());
#endif
#if !TD_EVENTFD_UNSUPPORTED
- td::bench(td::EventFdBench());
+ td::bench(EventFdBench());
#endif
- td::bench(td::NewObjBench());
- td::bench(td::NewIntBench());
+ td::bench(NewObjBench());
+ td::bench(NewIntBench());
#if !TD_WINDOWS
- td::bench(td::PipeBench());
+ td::bench(PipeBench());
#endif
#if TD_LINUX || TD_ANDROID || TD_TIZEN
- td::bench(td::SemBench());
+ td::bench(SemBench());
#endif
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_queue.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_queue.cpp
index 13288e6cd7..6f7cf20bc9 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_queue.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_queue.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,47 +8,56 @@
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/MpscPollableQueue.h"
+#include "td/utils/port/sleep.h"
+#include "td/utils/port/thread.h"
#include "td/utils/queue.h"
+#include "td/utils/Random.h"
// TODO: check system calls
// TODO: all return values must be checked
#include <atomic>
-#include <cstdio>
-#include <cstdlib>
-#include <vector>
+#if TD_PORT_POSIX
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <sys/syscall.h>
#include <unistd.h>
+#endif
#if TD_LINUX
#include <sys/eventfd.h>
#endif
-using std::atomic;
-using std::vector;
-
-using td::int32;
-using td::uint32;
-
#define MODE std::memory_order_relaxed
// void set_affinity(int mask) {
-// int err, syscallres;
-// pid_t pid = gettid();
-// syscallres = syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);
-// if (syscallres) {
-// err = errno;
-// perror("oppa");
-//}
-//}
-
-// TODO: warnings and asserts. There should be no warnings or debug output in production.
+// pid_t pid = gettid();
+// int syscallres = syscall(__NR_sched_setaffinity, pid, sizeof(mask), &mask);
+// if (syscallres) {
+// perror("Failed to set affinity");
+// }
+// }
+
using qvalue_t = int;
+class Backoff {
+ int cnt = 0;
+
+ public:
+ bool next() {
+ cnt++;
+ if (cnt < 50) {
+ return true;
+ } else {
+ td::usleep_for(1);
+ return cnt < 500;
+ }
+ }
+};
+
+#if TD_PORT_POSIX
// Just for testing, not production
class PipeQueue {
int input;
@@ -57,18 +66,21 @@ class PipeQueue {
public:
void init() {
int new_pipe[2];
- pipe(new_pipe);
+ int res = pipe(new_pipe);
+ CHECK(res == 0);
output = new_pipe[0];
input = new_pipe[1];
}
void put(qvalue_t value) {
- write(input, &value, sizeof(value));
+ auto len = write(input, &value, sizeof(value));
+ CHECK(len == sizeof(value));
}
qvalue_t get() {
qvalue_t res;
- read(output, &res, sizeof(res));
+ auto len = read(output, &res, sizeof(res));
+ CHECK(len == sizeof(res));
return res;
}
@@ -78,26 +90,8 @@ class PipeQueue {
}
};
-class Backoff {
- int cnt;
-
- public:
- Backoff() : cnt(0) {
- }
-
- bool next() {
- cnt++;
- if (cnt < 50) {
- return true;
- } else {
- sched_yield();
- return cnt < 500;
- }
- }
-};
-
class VarQueue {
- atomic<qvalue_t> data;
+ std::atomic<qvalue_t> data{0};
public:
void init() {
@@ -179,6 +173,7 @@ class SemQueue {
return get();
}
};
+#endif
#if TD_LINUX
class EventfdQueue {
@@ -193,11 +188,14 @@ class EventfdQueue {
void put(qvalue_t value) {
q.put(value);
td::int64 x = 1;
- write(fd, &x, sizeof(x));
+ auto len = write(fd, &x, sizeof(x));
+ CHECK(len == sizeof(x));
}
qvalue_t get() {
td::int64 x;
- read(fd, &x, sizeof(x));
+ auto len = read(fd, &x, sizeof(x));
+ CHECK(len == sizeof(x));
+ CHECK(x == 1);
return q.get();
}
void destroy() {
@@ -212,17 +210,17 @@ const int queue_buf_size = 1 << 10;
class BufferQueue {
struct node {
qvalue_t val;
- char pad[64 - sizeof(atomic<qvalue_t>)];
+ char pad[64 - sizeof(std::atomic<qvalue_t>)];
};
node q[queue_buf_size];
struct Position {
- atomic<uint32> i;
- char pad[64 - sizeof(atomic<uint32>)];
+ std::atomic<td::uint32> i{0};
+ char pad[64 - sizeof(std::atomic<td::uint32>)];
- uint32 local_read_i;
- uint32 local_write_i;
- char pad2[64 - sizeof(uint32) * 2];
+ td::uint32 local_read_i;
+ td::uint32 local_write_i;
+ char pad2[64 - sizeof(td::uint32) * 2];
void init() {
i = 0;
@@ -318,8 +316,7 @@ class BufferQueue {
return;
}
if (!update_writer()) {
- std::fprintf(stderr, "put strong failed\n");
- std::exit(0);
+ LOG(FATAL) << "Put strong failed";
}
put_unsafe(val);
}
@@ -336,7 +333,7 @@ class BufferQueue {
#if TD_LINUX
class BufferedFdQueue {
int fd;
- atomic<int> wait_flag;
+ std::atomic<int> wait_flag{0};
BufferQueue q;
char pad[64];
@@ -351,7 +348,8 @@ class BufferedFdQueue {
td::int64 x = 1;
__sync_synchronize();
if (wait_flag.load(MODE)) {
- write(fd, &x, sizeof(x));
+ auto len = write(fd, &x, sizeof(x));
+ CHECK(len == sizeof(x));
}
}
void put_noflush(qvalue_t value) {
@@ -362,7 +360,8 @@ class BufferedFdQueue {
td::int64 x = 1;
__sync_synchronize();
if (wait_flag.load(MODE)) {
- write(fd, &x, sizeof(x));
+ auto len = write(fd, &x, sizeof(x));
+ CHECK(len == sizeof(x));
}
}
void flush_reader() {
@@ -393,7 +392,8 @@ class BufferedFdQueue {
wait_flag.store(1, MODE);
__sync_synchronize();
while (!(res = q.update_reader())) {
- read(fd, &x, sizeof(x));
+ auto len = read(fd, &x, sizeof(x));
+ CHECK(len == sizeof(x));
__sync_synchronize();
}
wait_flag.store(0, MODE);
@@ -416,7 +416,8 @@ class BufferedFdQueue {
wait_flag.store(1, MODE);
__sync_synchronize();
while (!q.update_reader()) {
- read(fd, &x, sizeof(x));
+ auto len = read(fd, &x, sizeof(x));
+ CHECK(len == sizeof(x));
__sync_synchronize();
}
wait_flag.store(0, MODE);
@@ -430,7 +431,7 @@ class BufferedFdQueue {
class FdQueue {
int fd;
- atomic<int> wait_flag;
+ std::atomic<int> wait_flag{0};
VarQueue q;
char pad[64];
@@ -445,12 +446,14 @@ class FdQueue {
td::int64 x = 1;
__sync_synchronize();
if (wait_flag.load(MODE)) {
- write(fd, &x, sizeof(x));
+ auto len = write(fd, &x, sizeof(x));
+ CHECK(len == sizeof(x));
}
}
qvalue_t get() {
// td::int64 x;
- // read(fd, &x, sizeof(x));
+ // auto len = read(fd, &x, sizeof(x));
+ // CHECK(len == sizeof(x));
// return q.get();
Backoff backoff;
@@ -466,14 +469,13 @@ class FdQueue {
td::int64 x;
wait_flag.store(1, MODE);
__sync_synchronize();
- // std::fprintf(stderr, "!\n");
- // while (res == -1 && read(fd, &x, sizeof(x))) {
- // res = q.try_get();
- //}
+ // while (res == -1 && read(fd, &x, sizeof(x)) == sizeof(x)) {
+ // res = q.try_get();
+ // }
do {
__sync_synchronize();
res = q.try_get();
- } while (res == -1 && read(fd, &x, sizeof(x)));
+ } while (res == -1 && read(fd, &x, sizeof(x)) == sizeof(x));
q.acquire();
wait_flag.store(0, MODE);
return res;
@@ -485,6 +487,7 @@ class FdQueue {
};
#endif
+#if TD_PORT_POSIX
class SemBackoffQueue {
sem_t sem;
VarQueue q;
@@ -554,46 +557,40 @@ class SemCheatQueue {
};
template <class QueueT>
-class QueueBenchmark2 : public td::Benchmark {
+class QueueBenchmark2 final : public td::Benchmark {
QueueT client, server;
int connections_n, queries_n;
int server_active_connections;
int client_active_connections;
- vector<td::int64> server_conn;
- vector<td::int64> client_conn;
+ td::vector<td::int64> server_conn;
+ td::vector<td::int64> client_conn;
+
+ td::string name;
public:
- explicit QueueBenchmark2(int connections_n = 1) : connections_n(connections_n) {
+ QueueBenchmark2(int connections_n, td::string name) : connections_n(connections_n), name(std::move(name)) {
}
- std::string get_description() const override {
- return "QueueBenchmark2";
+ td::string get_description() const final {
+ return name;
}
- void start_up() override {
+ void start_up() final {
client.init();
server.init();
}
- void tear_down() override {
+ void tear_down() final {
client.destroy();
server.destroy();
}
void server_process(qvalue_t value) {
int no = value & 0x00FFFFFF;
- int co = static_cast<int>(static_cast<unsigned int>(value) >> 24);
- // std::fprintf(stderr, "-->%d %d\n", co, no);
- if (co < 0 || co >= connections_n || no != server_conn[co]++) {
- std::fprintf(stderr, "%d %d\n", co, no);
- std::fprintf(stderr, "expected %d %lld\n", co, static_cast<long long>(server_conn[co] - 1));
- std::fprintf(stderr, "Server BUG\n");
- while (true) {
- }
- }
- // std::fprintf(stderr, "no = %d/%d\n", no, queries_n);
- // std::fprintf(stderr, "answer: %d %d\n", no, co);
+ auto co = static_cast<int>(static_cast<td::uint32>(value) >> 24);
+ CHECK(co >= 0 && co < connections_n);
+ CHECK(no == server_conn[co]++);
client.writer_put(value);
client.writer_flush();
@@ -603,15 +600,12 @@ class QueueBenchmark2 : public td::Benchmark {
}
void *server_run(void *) {
- server_conn = vector<td::int64>(connections_n);
+ server_conn = td::vector<td::int64>(connections_n);
server_active_connections = connections_n;
while (server_active_connections > 0) {
int cnt = server.reader_wait();
- if (cnt == 0) {
- std::fprintf(stderr, "ERROR!\n");
- std::exit(0);
- }
+ CHECK(cnt != 0);
while (cnt-- > 0) {
server_process(server.reader_get_unsafe());
server.reader_flush();
@@ -624,18 +618,10 @@ class QueueBenchmark2 : public td::Benchmark {
void client_process(qvalue_t value) {
int no = value & 0x00FFFFFF;
- int co = static_cast<int>(static_cast<unsigned int>(value) >> 24);
- // std::fprintf(stderr, "<--%d %d\n", co, no);
- if (co < 0 || co >= connections_n || no != client_conn[co]++) {
- std::fprintf(stderr, "%d %d\n", co, no);
- std::fprintf(stderr, "expected %d %lld\n", co, static_cast<long long>(client_conn[co] - 1));
- std::fprintf(stderr, "BUG\n");
- while (true) {
- }
- std::exit(0);
- }
+ auto co = static_cast<int>(static_cast<td::uint32>(value) >> 24);
+ CHECK(co >= 0 && co < connections_n);
+ CHECK(no == client_conn[co]++);
if (no + 1 < queries_n) {
- // std::fprintf(stderr, "query: %d %d\n", no + 1, co);
server.writer_put(value + 1);
server.writer_flush();
} else {
@@ -644,12 +630,9 @@ class QueueBenchmark2 : public td::Benchmark {
}
void *client_run(void *) {
- client_conn = vector<td::int64>(connections_n);
+ client_conn = td::vector<td::int64>(connections_n);
client_active_connections = connections_n;
- if (queries_n >= (1 << 24)) {
- std::fprintf(stderr, "Too big queries_n\n");
- std::exit(0);
- }
+ CHECK(queries_n < (1 << 24));
for (int i = 0; i < connections_n; i++) {
server.writer_put(static_cast<qvalue_t>(i) << 24);
@@ -658,10 +641,7 @@ class QueueBenchmark2 : public td::Benchmark {
while (client_active_connections > 0) {
int cnt = client.reader_wait();
- if (cnt == 0) {
- std::fprintf(stderr, "ERROR!\n");
- std::exit(0);
- }
+ CHECK(cnt != 0);
while (cnt-- > 0) {
client_process(client.reader_get_unsafe());
client.reader_flush();
@@ -681,7 +661,7 @@ class QueueBenchmark2 : public td::Benchmark {
return static_cast<QueueBenchmark2 *>(arg)->server_run(nullptr);
}
- void run(int n) override {
+ void run(int n) final {
pthread_t client_thread_id;
pthread_t server_thread_id;
@@ -696,45 +676,40 @@ class QueueBenchmark2 : public td::Benchmark {
};
template <class QueueT>
-class QueueBenchmark : public td::Benchmark {
+class QueueBenchmark final : public td::Benchmark {
QueueT client, server;
const int connections_n;
int queries_n;
+ td::string name;
+
public:
- explicit QueueBenchmark(int connections_n = 1) : connections_n(connections_n) {
+ QueueBenchmark(int connections_n, td::string name) : connections_n(connections_n), name(std::move(name)) {
}
- std::string get_description() const override {
- return "QueueBenchmark";
+ td::string get_description() const final {
+ return name;
}
- void start_up() override {
+ void start_up() final {
client.init();
server.init();
}
- void tear_down() override {
+ void tear_down() final {
client.destroy();
server.destroy();
}
void *server_run(void *) {
- vector<td::int64> conn(connections_n);
+ td::vector<td::int64> conn(connections_n);
int active_connections = connections_n;
while (active_connections > 0) {
qvalue_t value = server.get();
int no = value & 0x00FFFFFF;
- int co = static_cast<int>(value >> 24);
- // std::fprintf(stderr, "-->%d %d\n", co, no);
- if (co < 0 || co >= connections_n || no != conn[co]++) {
- std::fprintf(stderr, "%d %d\n", co, no);
- std::fprintf(stderr, "expected %d %lld\n", co, static_cast<long long>(conn[co] - 1));
- std::fprintf(stderr, "Server BUG\n");
- while (true) {
- }
- }
- // std::fprintf(stderr, "no = %d/%d\n", no, queries_n);
+ auto co = static_cast<int>(value >> 24);
+ CHECK(co >= 0 && co < connections_n);
+ CHECK(no == conn[co]++);
client.put(value);
if (no + 1 >= queries_n) {
active_connections--;
@@ -744,11 +719,8 @@ class QueueBenchmark : public td::Benchmark {
}
void *client_run(void *) {
- vector<td::int64> conn(connections_n);
- if (queries_n >= (1 << 24)) {
- std::fprintf(stderr, "Too big queries_n\n");
- std::exit(0);
- }
+ td::vector<td::int64> conn(connections_n);
+ CHECK(queries_n < (1 << 24));
for (int i = 0; i < connections_n; i++) {
server.put(static_cast<qvalue_t>(i) << 24);
}
@@ -756,16 +728,9 @@ class QueueBenchmark : public td::Benchmark {
while (active_connections > 0) {
qvalue_t value = client.get();
int no = value & 0x00FFFFFF;
- int co = static_cast<int>(value >> 24);
- // std::fprintf(stderr, "<--%d %d\n", co, no);
- if (co < 0 || co >= connections_n || no != conn[co]++) {
- std::fprintf(stderr, "%d %d\n", co, no);
- std::fprintf(stderr, "expected %d %lld\n", co, static_cast<long long>(conn[co] - 1));
- std::fprintf(stderr, "BUG\n");
- while (true) {
- }
- std::exit(0);
- }
+ auto co = static_cast<int>(value >> 24);
+ CHECK(co >= 0 && co < connections_n);
+ CHECK(no == conn[co]++);
if (no + 1 < queries_n) {
server.put(value + 1);
} else {
@@ -777,28 +742,18 @@ class QueueBenchmark : public td::Benchmark {
}
void *client_run2(void *) {
- vector<td::int64> conn(connections_n);
- if (queries_n >= (1 << 24)) {
- std::fprintf(stderr, "Too big queries_n\n");
- std::exit(0);
- }
- for (int it = 0; it < queries_n; it++) {
+ td::vector<td::int64> conn(connections_n);
+ CHECK(queries_n < (1 << 24));
+ for (int query = 0; query < queries_n; query++) {
for (int i = 0; i < connections_n; i++) {
- server.put((static_cast<td::int64>(i) << 24) + it);
+ server.put((static_cast<td::int64>(i) << 24) + query);
}
for (int i = 0; i < connections_n; i++) {
qvalue_t value = client.get();
int no = value & 0x00FFFFFF;
- int co = static_cast<int>(value >> 24);
- // std::fprintf(stderr, "<--%d %d\n", co, no);
- if (co < 0 || co >= connections_n || no != conn[co]++) {
- std::fprintf(stderr, "%d %d\n", co, no);
- std::fprintf(stderr, "expected %d %lld\n", co, static_cast<long long>(conn[co] - 1));
- std::fprintf(stderr, "BUG\n");
- while (true) {
- }
- std::exit(0);
- }
+ auto co = static_cast<int>(value >> 24);
+ CHECK(co >= 0 && co < connections_n);
+ CHECK(no == conn[co]++);
}
}
// system("cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");
@@ -813,7 +768,7 @@ class QueueBenchmark : public td::Benchmark {
return static_cast<QueueBenchmark *>(arg)->server_run(nullptr);
}
- void run(int n) override {
+ void run(int n) final {
pthread_t client_thread_id;
pthread_t server_thread_id;
@@ -828,8 +783,8 @@ class QueueBenchmark : public td::Benchmark {
};
template <class QueueT>
-class RingBenchmark : public td::Benchmark {
- enum { QN = 504 };
+class RingBenchmark final : public td::Benchmark {
+ static constexpr int QN = 504;
struct Thread {
int int_id;
@@ -840,7 +795,6 @@ class RingBenchmark : public td::Benchmark {
void *run() {
qvalue_t value;
- // std::fprintf(stderr, "start %d\n", int_id);
do {
int cnt = queue.reader_wait();
CHECK(cnt == 1);
@@ -861,7 +815,7 @@ class RingBenchmark : public td::Benchmark {
return static_cast<Thread *>(arg)->run();
}
- void start_up() override {
+ void start_up() final {
for (int i = 0; i < QN; i++) {
q[i].int_id = i;
q[i].queue.init();
@@ -869,18 +823,17 @@ class RingBenchmark : public td::Benchmark {
}
}
- void tear_down() override {
+ void tear_down() final {
for (int i = 0; i < QN; i++) {
q[i].queue.destroy();
}
}
- void run(int n) override {
+ void run(int n) final {
for (int i = 0; i < QN; i++) {
pthread_create(&q[i].id, nullptr, run_gateway, &q[i]);
}
- std::fprintf(stderr, "run %d\n", n);
if (n < 1000) {
n = 1000;
}
@@ -892,52 +845,89 @@ class RingBenchmark : public td::Benchmark {
}
}
};
+#endif
+
+/*
+#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
+static void test_queue() {
+ td::vector<td::thread> threads;
+ static constexpr size_t THREAD_COUNT = 100;
+ td::vector<td::MpscPollableQueue<int>> queues(THREAD_COUNT);
+ for (auto &q : queues) {
+ q.init();
+ }
+ for (size_t i = 0; i < THREAD_COUNT; i++) {
+ threads.emplace_back([&q = queues[i]] {
+ while (true) {
+ auto got = q.reader_wait_nonblock();
+ while (got-- > 0) {
+ q.reader_get_unsafe();
+ }
+ q.reader_get_event_fd().wait(1000);
+ }
+ });
+ }
+
+ for (size_t iter = 0; iter < THREAD_COUNT; iter++) {
+ td::usleep_for(100);
+ for (int i = 0; i < 5; i++) {
+ queues[td::Random::fast(0, THREAD_COUNT - 1)].writer_put(1);
+ }
+ }
+
+ for (size_t i = 0; i < THREAD_COUNT; i++) {
+ threads[i].join();
+ }
+}
+#endif
+*/
int main() {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
-#define BENCH_Q2(Q, N) \
- std::fprintf(stderr, "!%s %d:\t", #Q, N); \
- td::bench(QueueBenchmark2<Q>(N));
-#define BENCH_Q(Q, N) \
- std::fprintf(stderr, "%s %d:\t", #Q, N); \
- td::bench(QueueBenchmark<Q>(N));
-
-#define BENCH_R(Q) \
- std::fprintf(stderr, "%s:\t", #Q); \
- td::bench(RingBenchmark<Q>());
+#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
+ // test_queue();
+#endif
+
+#if TD_PORT_POSIX
// TODO: yield makes it extremely slow. Yet some backoff may be necessary.
- // BENCH_R(SemQueue);
- // BENCH_R(td::PollQueue<qvalue_t>);
+ // td::bench(RingBenchmark<SemQueue>());
+ // td::bench(RingBenchmark<td::PollQueue<qvalue_t>>());
- BENCH_Q2(td::PollQueue<qvalue_t>, 1);
- BENCH_Q2(td::MpscPollableQueue<qvalue_t>, 1);
- BENCH_Q2(td::PollQueue<qvalue_t>, 100);
- BENCH_Q2(td::MpscPollableQueue<qvalue_t>, 100);
- BENCH_Q2(td::PollQueue<qvalue_t>, 10);
- BENCH_Q2(td::MpscPollableQueue<qvalue_t>, 10);
+#define BENCH_Q2(Q, N) td::bench(QueueBenchmark2<Q<qvalue_t>>(N, #Q "(" #N ")"))
- BENCH_Q(VarQueue, 1);
- // BENCH_Q(FdQueue, 1);
- // BENCH_Q(BufferedFdQueue, 1);
- BENCH_Q(PipeQueue, 1);
- BENCH_Q(SemCheatQueue, 1);
- BENCH_Q(SemQueue, 1);
+#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
+ BENCH_Q2(td::InfBackoffQueue, 1);
+ BENCH_Q2(td::MpscPollableQueue, 1);
+ BENCH_Q2(td::PollQueue, 1);
+
+ BENCH_Q2(td::InfBackoffQueue, 10);
+ BENCH_Q2(td::MpscPollableQueue, 10);
+ BENCH_Q2(td::PollQueue, 10);
- // BENCH_Q2(td::PollQueue<qvalue_t>, 100);
- // BENCH_Q2(td::PollQueue<qvalue_t>, 10);
- // BENCH_Q2(td::PollQueue<qvalue_t>, 4);
- // BENCH_Q2(td::InfBackoffQueue<qvalue_t>, 100);
+ BENCH_Q2(td::InfBackoffQueue, 100);
+ BENCH_Q2(td::MpscPollableQueue, 100);
+ BENCH_Q2(td::PollQueue, 100);
- // BENCH_Q2(td::InfBackoffQueue<qvalue_t>, 1);
- // BENCH_Q(SemCheatQueue, 1);
+ BENCH_Q2(td::PollQueue, 4);
+ BENCH_Q2(td::PollQueue, 10);
+ BENCH_Q2(td::PollQueue, 100);
+#endif
- // BENCH_Q(BufferedFdQueue, 100);
- // BENCH_Q(BufferedFdQueue, 10);
+#define BENCH_Q(Q, N) td::bench(QueueBenchmark<Q>(N, #Q "(" #N ")"))
- // BENCH_Q(BufferQueue, 4);
- // BENCH_Q(BufferQueue, 100);
- // BENCH_Q(BufferQueue, 10);
- // BENCH_Q(BufferQueue, 1);
+#if TD_LINUX
+ BENCH_Q(BufferQueue, 1);
+ BENCH_Q(BufferedFdQueue, 1);
+ BENCH_Q(FdQueue, 1);
+#endif
+ BENCH_Q(PipeQueue, 1);
+ BENCH_Q(SemCheatQueue, 1);
+ BENCH_Q(SemQueue, 1);
+ BENCH_Q(VarQueue, 1);
- return 0;
+#if TD_LINUX
+ BENCH_Q(BufferQueue, 4);
+ BENCH_Q(BufferQueue, 10);
+ BENCH_Q(BufferQueue, 100);
+#endif
+#endif
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/bench_tddb.cpp b/protocols/Telegram/tdlib/td/benchmark/bench_tddb.cpp
index 91e957a501..c3298327e5 100644
--- a/protocols/Telegram/tdlib/td/benchmark/bench_tddb.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/bench_tddb.cpp
@@ -1,70 +1,78 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageDb.h"
#include "td/telegram/MessageId.h"
-#include "td/telegram/MessagesDb.h"
+#include "td/telegram/NotificationId.h"
+#include "td/telegram/ServerMessageId.h"
#include "td/telegram/UserId.h"
+#include "td/db/DbKey.h"
+#include "td/db/SqliteConnectionSafe.h"
+#include "td/db/SqliteDb.h"
+
+#include "td/actor/ConcurrentScheduler.h"
+
#include "td/utils/benchmark.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
#include "td/utils/Random.h"
#include "td/utils/Status.h"
#include <memory>
-namespace td {
-
-static Status init_db(SqliteDb &db) {
+static td::Status init_db(td::SqliteDb &db) {
TRY_STATUS(db.exec("PRAGMA encoding=\"UTF-8\""));
TRY_STATUS(db.exec("PRAGMA synchronous=NORMAL"));
TRY_STATUS(db.exec("PRAGMA journal_mode=WAL"));
TRY_STATUS(db.exec("PRAGMA temp_store=MEMORY"));
TRY_STATUS(db.exec("PRAGMA secure_delete=1"));
- return Status::OK();
+ return td::Status::OK();
}
-class MessagesDbBench : public Benchmark {
+class MessageDbBench final : public td::Benchmark {
public:
- string get_description() const override {
- return "MessagesDb";
+ td::string get_description() const final {
+ return "MessageDb";
}
- void start_up() override {
+ void start_up() final {
LOG(ERROR) << "START UP";
do_start_up().ensure();
scheduler_->start();
}
- void run(int n) override {
- auto guard = scheduler_->get_current_guard();
+ void run(int n) final {
+ auto guard = scheduler_->get_main_guard();
for (int i = 0; i < n; i += 20) {
- auto dialog_id = DialogId{UserId{Random::fast(1, 100)}};
- auto message_id_raw = Random::fast(1, 100000);
+ auto dialog_id = td::DialogId(td::UserId(static_cast<td::int64>(td::Random::fast(1, 100))));
+ auto message_id_raw = td::Random::fast(1, 100000);
for (int j = 0; j < 20; j++) {
- auto message_id = MessageId{ServerMessageId{message_id_raw + j}};
- auto unique_message_id = ServerMessageId{i + 1};
- auto sender_user_id = UserId{Random::fast(1, 1000)};
+ auto message_id = td::MessageId{td::ServerMessageId{message_id_raw + j}};
+ auto unique_message_id = td::ServerMessageId{i + 1};
+ auto sender_dialog_id = td::DialogId(td::UserId(static_cast<td::int64>(td::Random::fast(1, 1000))));
auto random_id = i + 1;
auto ttl_expires_at = 0;
- auto data = BufferSlice(Random::fast(100, 299));
+ auto data = td::BufferSlice(td::Random::fast(100, 299));
// use async on same thread.
- messages_db_async_->add_message({dialog_id, message_id}, unique_message_id, sender_user_id, random_id,
- ttl_expires_at, 0, 0, "", std::move(data), Promise<>());
+ message_db_async_->add_message({dialog_id, message_id}, unique_message_id, sender_dialog_id, random_id,
+ ttl_expires_at, 0, 0, "", td::NotificationId(), td::MessageId(), std::move(data),
+ td::Promise<>());
}
}
}
- void tear_down() override {
+ void tear_down() final {
scheduler_->run_main(0.1);
{
- auto guard = scheduler_->get_current_guard();
+ auto guard = scheduler_->get_main_guard();
sql_connection_.reset();
- messages_db_sync_safe_.reset();
- messages_db_async_.reset();
+ message_db_sync_safe_.reset();
+ message_db_async_.reset();
}
scheduler_->finish();
@@ -73,36 +81,33 @@ class MessagesDbBench : public Benchmark {
}
private:
- std::unique_ptr<td::ConcurrentScheduler> scheduler_;
- std::shared_ptr<SqliteConnectionSafe> sql_connection_;
- std::shared_ptr<MessagesDbSyncSafeInterface> messages_db_sync_safe_;
- std::shared_ptr<MessagesDbAsyncInterface> messages_db_async_;
+ td::unique_ptr<td::ConcurrentScheduler> scheduler_;
+ std::shared_ptr<td::SqliteConnectionSafe> sql_connection_;
+ std::shared_ptr<td::MessageDbSyncSafeInterface> message_db_sync_safe_;
+ std::shared_ptr<td::MessageDbAsyncInterface> message_db_async_;
- Status do_start_up() {
- scheduler_ = std::make_unique<ConcurrentScheduler>();
- scheduler_->init(1);
+ td::Status do_start_up() {
+ scheduler_ = td::make_unique<td::ConcurrentScheduler>(1, 0);
- auto guard = scheduler_->get_current_guard();
+ auto guard = scheduler_->get_main_guard();
- string sql_db_name = "testdb.sqlite";
- sql_connection_ = std::make_shared<SqliteConnectionSafe>(sql_db_name);
+ td::string sql_db_name = "testdb.sqlite";
+ sql_connection_ = std::make_shared<td::SqliteConnectionSafe>(sql_db_name, td::DbKey::empty());
auto &db = sql_connection_->get();
TRY_STATUS(init_db(db));
db.exec("BEGIN TRANSACTION").ensure();
// version == 0 ==> db will be destroyed
- TRY_STATUS(init_messages_db(db, 0));
+ TRY_STATUS(init_message_db(db, 0));
db.exec("COMMIT TRANSACTION").ensure();
- messages_db_sync_safe_ = create_messages_db_sync(sql_connection_);
- messages_db_async_ = create_messages_db_async(messages_db_sync_safe_, 0);
- return Status::OK();
+ message_db_sync_safe_ = td::create_message_db_sync(sql_connection_);
+ message_db_async_ = td::create_message_db_async(message_db_sync_safe_, 0);
+ return td::Status::OK();
}
};
-} // namespace td
int main() {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
- bench(td::MessagesDbBench());
- return 0;
+ td::bench(MessageDbBench());
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/check_proxy.cpp b/protocols/Telegram/tdlib/td/benchmark/check_proxy.cpp
new file mode 100644
index 0000000000..0a54c598c0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/benchmark/check_proxy.cpp
@@ -0,0 +1,193 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Client.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/common.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/TsCerr.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <iostream>
+#include <utility>
+
+static void usage() {
+ td::TsCerr() << "Tests specified MTProto-proxies, outputs working proxies to stdout; exits with code 0 if a working "
+ "proxy was found.\n";
+ td::TsCerr() << "Usage: check_proxy [options] server:port:secret [server2:port2:secret2 ...]\n";
+ td::TsCerr() << "Options:\n";
+ td::TsCerr() << " -v<N>\tSet verbosity level to N\n";
+ td::TsCerr() << " -h/--help\tDisplay this information\n";
+ td::TsCerr() << " -d/--dc-id\tIdentifier of a datacenter to which try to connect (default is 2)\n";
+ td::TsCerr() << " -l/--proxy-list\tName of a file with proxies to check; one proxy per line\n";
+ td::TsCerr() << " -t/--timeout\tMaximum overall timeout for the request (default is 10 seconds)\n";
+ std::exit(2);
+}
+
+int main(int argc, char **argv) {
+ int new_verbosity_level = VERBOSITY_NAME(FATAL);
+
+ td::vector<std::pair<td::string, td::td_api::object_ptr<td::td_api::testProxy>>> requests;
+
+ auto add_proxy = [&requests](td::string arg) {
+ if (arg.empty()) {
+ return;
+ }
+
+ std::size_t offset = 0;
+ if (arg[0] == '[') {
+ auto end_ipv6_pos = arg.find(']');
+ if (end_ipv6_pos == td::string::npos) {
+ td::TsCerr() << "Error: failed to find end of IPv6 address in \"" << arg << "\"\n";
+ usage();
+ }
+ offset = end_ipv6_pos;
+ }
+ if (std::count(arg.begin() + offset, arg.end(), ':') == 3) {
+ auto secret_domain_pos = arg.find(':', arg.find(':', offset) + 1) + 1;
+ auto domain_pos = arg.find(':', secret_domain_pos);
+ auto secret = arg.substr(secret_domain_pos, domain_pos - secret_domain_pos);
+ auto domain = arg.substr(domain_pos + 1);
+ auto r_decoded_secret = td::hex_decode(secret);
+ if (r_decoded_secret.is_error()) {
+ r_decoded_secret = td::base64url_decode(secret);
+ if (r_decoded_secret.is_error()) {
+ td::TsCerr() << "Error: failed to find proxy port and secret in \"" << arg << "\"\n";
+ usage();
+ }
+ }
+ arg = arg.substr(0, secret_domain_pos) + td::base64url_encode(r_decoded_secret.ok() + domain);
+ }
+
+ auto secret_pos = arg.rfind(':');
+ if (secret_pos == td::string::npos) {
+ td::TsCerr() << "Error: failed to find proxy port and secret in \"" << arg << "\"\n";
+ usage();
+ }
+ auto secret = arg.substr(secret_pos + 1);
+ auto port_pos = arg.substr(0, secret_pos).rfind(':');
+ if (port_pos == td::string::npos) {
+ td::TsCerr() << "Error: failed to find proxy secret in \"" << arg << "\"\n";
+ usage();
+ }
+ auto r_port = td::to_integer_safe<td::int32>(arg.substr(port_pos + 1, secret_pos - port_pos - 1));
+ if (r_port.is_error()) {
+ td::TsCerr() << "Error: failed to parse proxy port in \"" << arg << "\"\n";
+ usage();
+ }
+ auto port = r_port.move_as_ok();
+ auto server = arg.substr(0, port_pos);
+ if (server[0] == '[' && server.back() == ']') {
+ server = server.substr(1, server.size() - 2);
+ }
+
+ if (server.empty() || port <= 0 || port > 65536 || secret.empty()) {
+ td::TsCerr() << "Error: proxy address to check is in wrong format: \"" << arg << "\"\n";
+ usage();
+ }
+
+ requests.emplace_back(arg,
+ td::td_api::make_object<td::td_api::testProxy>(
+ server, port, td::td_api::make_object<td::td_api::proxyTypeMtproto>(secret), -1, -1));
+ };
+
+ td::int32 dc_id = 2;
+ double timeout = 10.0;
+
+ for (int i = 1; i < argc; i++) {
+ td::string arg(argv[i]);
+
+ auto get_next_arg = [&i, &arg, argc, argv](bool is_optional = false) {
+ CHECK(arg.size() >= 2);
+ if (arg.size() == 2 || arg[1] == '-') {
+ if (i + 1 < argc && argv[i + 1][0] != '-') {
+ return td::string(argv[++i]);
+ }
+ } else {
+ if (arg.size() > 2) {
+ return arg.substr(2);
+ }
+ }
+ if (!is_optional) {
+ td::TsCerr() << "Error: value is required after " << arg << "\n";
+ usage();
+ }
+ return td::string();
+ };
+
+ if (td::begins_with(arg, "-v")) {
+ arg = get_next_arg(true);
+ int new_verbosity = 1;
+ while (arg[0] == 'v') {
+ new_verbosity++;
+ arg = arg.substr(1);
+ }
+ if (!arg.empty()) {
+ new_verbosity += td::to_integer<int>(arg) - (new_verbosity == 1);
+ }
+ new_verbosity_level = VERBOSITY_NAME(FATAL) + new_verbosity;
+ } else if (td::begins_with(arg, "-t") || arg == "--timeout") {
+ timeout = td::to_double(get_next_arg());
+ } else if (td::begins_with(arg, "-d") || arg == "--dc-id") {
+ dc_id = td::to_integer<td::int32>(get_next_arg());
+ } else if (td::begins_with(arg, "-l") || arg == "--proxy-list") {
+ auto r_proxies = td::read_file_str(get_next_arg());
+ if (r_proxies.is_error()) {
+ td::TsCerr() << "Error: wrong file name specified\n";
+ usage();
+ }
+ for (auto &proxy : td::full_split(r_proxies.ok(), '\n')) {
+ add_proxy(td::trim(proxy));
+ }
+ } else if (arg[0] == '-') {
+ usage();
+ } else {
+ add_proxy(arg);
+ }
+ }
+
+ if (requests.empty()) {
+ td::TsCerr() << "Error: proxy address to check is not specified\n";
+ usage();
+ }
+
+ SET_VERBOSITY_LEVEL(new_verbosity_level);
+
+ td::ClientManager client_manager;
+ auto client_id = client_manager.create_client_id();
+ for (size_t i = 0; i < requests.size(); i++) {
+ auto &request = requests[i].second;
+ request->dc_id_ = dc_id;
+ request->timeout_ = timeout;
+ client_manager.send(client_id, i + 1, std::move(request));
+ }
+ size_t successful_requests = 0;
+ size_t failed_requests = 0;
+
+ while (successful_requests + failed_requests != requests.size()) {
+ auto response = client_manager.receive(100.0);
+ CHECK(client_id == response.client_id);
+ if (1 <= response.request_id && response.request_id <= requests.size()) {
+ auto &proxy = requests[static_cast<size_t>(response.request_id - 1)].first;
+ if (response.object->get_id() == td::td_api::error::ID) {
+ LOG(ERROR) << proxy << ": " << to_string(response.object);
+ failed_requests++;
+ } else {
+ std::cout << proxy << std::endl;
+ successful_requests++;
+ }
+ }
+ }
+
+ if (successful_requests == 0) {
+ return 1;
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/benchmark/check_tls.cpp b/protocols/Telegram/tdlib/td/benchmark/check_tls.cpp
new file mode 100644
index 0000000000..f7fb1fecb7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/benchmark/check_tls.cpp
@@ -0,0 +1,336 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/BigNum.h"
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/detail/Iocp.h"
+#include "td/utils/port/IPAddress.h"
+#include "td/utils/port/sleep.h"
+#include "td/utils/port/SocketFd.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+#include "td/utils/Time.h"
+
+#include <map>
+
+static td::BigNumContext context;
+
+static bool is_quadratic_residue(const td::BigNum &a) {
+ // 2^255 - 19
+ td::BigNum mod =
+ td::BigNum::from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed").move_as_ok();
+ // (mod - 1) / 2 = 2^254 - 10
+ td::BigNum pow =
+ td::BigNum::from_hex("3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6").move_as_ok();
+
+ td::BigNum r;
+ td::BigNum::mod_exp(r, a, pow, mod, context);
+ td::BigNum one = td::BigNum::from_decimal("1").move_as_ok();
+ td::BigNum::mod_add(r, r, one, mod, context);
+
+ td::string result = r.to_decimal();
+ CHECK(result == "0" || result == "1" || result == "2");
+ return result == "2";
+}
+
+struct TlsInfo {
+ td::vector<size_t> extension_list;
+ td::vector<size_t> encrypted_application_data_length;
+};
+
+td::Result<TlsInfo> test_tls(const td::string &url) {
+ td::IPAddress address;
+ TRY_STATUS(address.init_host_port(url, 443));
+ TRY_RESULT(socket, td::SocketFd::open(address));
+
+ td::string request;
+
+ auto add_string = [&](td::Slice data) {
+ request.append(data.data(), data.size());
+ };
+ auto add_random = [&](size_t length) {
+ while (length-- > 0) {
+ request += static_cast<char>(td::Random::secure_int32());
+ }
+ };
+ auto add_length = [&](size_t length) {
+ request += static_cast<char>(length / 256);
+ request += static_cast<char>(length % 256);
+ };
+ auto add_key = [&] {
+ td::string key(32, '\0');
+ td::BigNum mod =
+ td::BigNum::from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed").move_as_ok();
+ while (true) {
+ td::Random::secure_bytes(key);
+ key[31] = static_cast<char>(key[31] & 127);
+ td::BigNum x = td::BigNum::from_le_binary(key);
+ if (!is_quadratic_residue(x)) {
+ continue;
+ }
+
+ td::BigNum y = x.clone();
+ td::BigNum coef = td::BigNum::from_decimal("486662").move_as_ok();
+ td::BigNum::mod_add(y, y, coef, mod, context);
+ td::BigNum::mod_mul(y, y, x, mod, context);
+ td::BigNum one = td::BigNum::from_decimal("1").move_as_ok();
+ td::BigNum::mod_add(y, y, one, mod, context);
+ td::BigNum::mod_mul(y, y, x, mod, context);
+ // y = x^3 + 486662 * x^2 + x
+ if (is_quadratic_residue(y)) {
+ break;
+ }
+ }
+ request += key;
+ };
+
+ const size_t MAX_GREASE = 7;
+ char greases[MAX_GREASE];
+ td::Random::secure_bytes(td::MutableSlice{greases, MAX_GREASE});
+ for (auto &grease : greases) {
+ grease = static_cast<char>((grease & 0xF0) + 0x0A);
+ }
+ for (size_t i = 1; i < MAX_GREASE; i += 2) {
+ if (greases[i] == greases[i - 1]) {
+ greases[i] = static_cast<char>(0x10 ^ greases[i]);
+ }
+ }
+ auto add_grease = [&](size_t num) {
+ auto c = greases[num];
+ request += c;
+ request += c;
+ };
+
+ add_string("\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03");
+ add_random(32);
+ add_string("\x20");
+ add_random(32);
+ add_string("\x00\x20");
+ add_grease(0);
+ add_string(
+ "\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00"
+ "\x2f\x00\x35\x01\x00\x01\x93");
+ add_grease(2);
+ add_string("\x00\x00\x00\x00");
+ add_length(url.size() + 5);
+ add_length(url.size() + 3);
+ add_string("\x00");
+ add_length(url.size());
+ add_string(url);
+ add_string("\x00\x17\x00\x00\xff\x01\x00\x01\x00\x00\x0a\x00\x0a\x00\x08");
+ add_grease(4);
+ add_string(
+ "\x00\x1d\x00\x17\x00\x18\x00\x0b\x00\x02\x01\x00\x00\x23\x00\x00\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68"
+ "\x74\x74\x70\x2f\x31\x2e\x31\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x0d\x00\x12\x00\x10\x04\x03\x08\x04\x04"
+ "\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x00\x12\x00\x00\x00\x33\x00\x2b\x00\x29");
+ add_grease(4);
+ add_string("\x00\x01\x00\x00\x1d\x00\x20");
+ add_key();
+ add_string("\x00\x2d\x00\x02\x01\x01\x00\x2b\x00\x0b\x0a");
+ add_grease(6);
+ add_string("\x03\x04\x03\x03\x03\x02\x03\x01\x00\x1b\x00\x03\x02\x00\x02");
+ add_grease(3);
+ add_string("\x00\x01\x00\x00\x15");
+ auto padding = 515 - static_cast<int>(request.size());
+ CHECK(padding >= 0);
+ add_length(padding);
+ request.resize(517);
+
+ // LOG(ERROR) << td::format::as_hex_dump<0>(td::Slice(request));
+
+ TRY_STATUS(socket.write(request));
+
+ TlsInfo info;
+ auto end_time = td::Time::now() + 3;
+ td::string result;
+ size_t pos = 0;
+ size_t server_hello_length = 0;
+ size_t encrypted_application_data_length_sum = 0;
+ while (td::Time::now() < end_time) {
+ static char buf[20000];
+ TRY_RESULT(res, socket.read(td::MutableSlice{buf, sizeof(buf)}));
+ if (res > 0) {
+ auto read_length = [&]() -> size_t {
+ CHECK(result.size() >= 2 + pos);
+ pos += 2;
+ return static_cast<unsigned char>(result[pos - 2]) * 256 + static_cast<unsigned char>(result[pos - 1]);
+ };
+
+ result += td::Slice(buf, res).str();
+ while (true) {
+#define CHECK_LENGTH(length) \
+ if (pos + (length) > result.size()) { \
+ break; \
+ }
+#define EXPECT_STR(pos, str, error) \
+ if (!begins_with(td::Slice(result).substr(pos), str)) { \
+ return td::Status::Error(error); \
+ }
+
+ if (pos == 0) {
+ CHECK_LENGTH(3);
+ EXPECT_STR(0, "\x16\x03\x03", "Non-TLS response or TLS <= 1.1");
+ pos += 3;
+ }
+ if (pos == 3) {
+ CHECK_LENGTH(2);
+ server_hello_length = read_length();
+ if (server_hello_length <= 39) {
+ return td::Status::Error("Receive too short server hello");
+ }
+ }
+ if (server_hello_length > 0) {
+ if (pos == 5) {
+ CHECK_LENGTH(server_hello_length);
+
+ EXPECT_STR(5, "\x02\x00", "Non-TLS response 2");
+ EXPECT_STR(9, "\x03\x03", "Non-TLS response 3");
+
+ auto random_id = td::Slice(result.c_str() + 11, 32);
+ if (random_id ==
+ "\xcf\x21\xad\x74\xe5\x9a\x61\x11\xbe\x1d\x8c\x02\x1e\x65\xb8\x91\xc2\xa2\x11\x16\x7a\xbb\x8c\x5e\x07"
+ "\x9e\x09\xe2\xc8\xa8\x33\x9c") {
+ return td::Status::Error("TLS 1.3 servers returning HelloRetryRequest are not supprted");
+ }
+ if (result[43] == '\x00') {
+ return td::Status::Error("TLS <= 1.2: empty session_id");
+ }
+ EXPECT_STR(43, "\x20", "Non-TLS response 4");
+ if (server_hello_length <= 75) {
+ return td::Status::Error("Receive too short server hello 2");
+ }
+ EXPECT_STR(44, request.substr(44, 32), "TLS <= 1.2: expected mirrored session_id");
+ EXPECT_STR(76, "\x13\x01\x00", "TLS <= 1.2: expected 0x1301 as a chosen cipher");
+ pos += 74;
+ size_t extensions_length = read_length();
+ if (extensions_length + 76 != server_hello_length) {
+ return td::Status::Error("Receive wrong extensions length");
+ }
+ while (pos < 5 + server_hello_length - 4) {
+ info.extension_list.push_back(read_length());
+ size_t extension_length = read_length();
+ if (pos + extension_length > 5 + server_hello_length) {
+ return td::Status::Error("Receive wrong extension length");
+ }
+ pos += extension_length;
+ }
+ if (pos != 5 + server_hello_length) {
+ return td::Status::Error("Receive wrong extensions list");
+ }
+ }
+ if (pos == 5 + server_hello_length) {
+ CHECK_LENGTH(6);
+ EXPECT_STR(pos, "\x14\x03\x03\x00\x01\x01", "Expected dummy ChangeCipherSpec");
+ pos += 6;
+ }
+ if (pos == 11 + server_hello_length + encrypted_application_data_length_sum) {
+ if (pos == result.size()) {
+ return info;
+ }
+
+ CHECK_LENGTH(3);
+ EXPECT_STR(pos, "\x17\x03\x03", "Expected encrypted application data");
+ pos += 3;
+ }
+ if (pos == 14 + server_hello_length + encrypted_application_data_length_sum) {
+ CHECK_LENGTH(2);
+ size_t encrypted_application_data_length = read_length();
+ info.encrypted_application_data_length.push_back(encrypted_application_data_length);
+ if (encrypted_application_data_length == 0) {
+ return td::Status::Error("Receive empty encrypted application data");
+ }
+ }
+ if (pos == 16 + server_hello_length + encrypted_application_data_length_sum) {
+ CHECK_LENGTH(info.encrypted_application_data_length.back());
+ pos += info.encrypted_application_data_length.back();
+ encrypted_application_data_length_sum += info.encrypted_application_data_length.back() + 5;
+ }
+ }
+ }
+ } else {
+ td::usleep_for(10000);
+ }
+ }
+
+ // LOG(ERROR) << url << ":" << td::format::as_hex_dump<0>(td::Slice(result));
+ return td::Status::Error("Failed to get response in 3 seconds");
+}
+
+int main(int argc, char *argv[]) {
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
+
+#if TD_PORT_WINDOWS
+ auto iocp = td::make_unique<td::detail::Iocp>();
+ iocp->init();
+ auto iocp_thread = td::thread([&iocp] { iocp->loop(); });
+ td::detail::Iocp::Guard iocp_guard(iocp.get());
+#endif
+
+ td::vector<td::string> urls;
+ for (int i = 1; i < argc; i++) {
+ urls.emplace_back(argv[i]);
+ }
+ for (auto &url : urls) {
+ const int MAX_TRIES = 100;
+ td::vector<std::map<size_t, int>> length_count;
+ td::vector<size_t> extension_list;
+ for (int i = 0; i < MAX_TRIES; i++) {
+ auto r_tls_info = test_tls(url);
+ if (r_tls_info.is_error()) {
+ LOG(ERROR) << url << ": " << r_tls_info.error();
+ break;
+ } else {
+ auto tls_info = r_tls_info.move_as_ok();
+ if (length_count.size() < tls_info.encrypted_application_data_length.size()) {
+ length_count.resize(tls_info.encrypted_application_data_length.size());
+ }
+ for (size_t t = 0; t < tls_info.encrypted_application_data_length.size(); t++) {
+ length_count[t][tls_info.encrypted_application_data_length[t]]++;
+ }
+ if (i == 0) {
+ extension_list = tls_info.extension_list;
+ } else {
+ if (extension_list != tls_info.extension_list) {
+ LOG(ERROR) << url << ": TLS 1.3.0 extension list has changed from " << extension_list << " to "
+ << tls_info.extension_list;
+ break;
+ }
+ }
+ }
+
+ if (i == MAX_TRIES - 1) {
+ if (extension_list != td::vector<size_t>{51, 43} && extension_list != td::vector<size_t>{43, 51}) {
+ LOG(ERROR) << url << ": TLS 1.3.0 unsupported extension list " << extension_list;
+ } else {
+ td::string length_distribution = "|";
+ for (size_t t = 0; t < length_count.size(); t++) {
+ for (auto it : length_count[t]) {
+ length_distribution += PSTRING()
+ << it.first << " : " << static_cast<int>(it.second * 100.0 / MAX_TRIES) << "%|";
+ }
+ if (t + 1 != length_count.size()) {
+ length_distribution += " + |";
+ }
+ }
+ LOG(ERROR) << url << ": TLS 1.3.0 with extensions " << extension_list << " and "
+ << (length_count.size() != 1 ? "unsupported " : "")
+ << "encrypted application data length distribution " << length_distribution;
+ }
+ }
+ }
+ }
+
+#if TD_PORT_WINDOWS
+ iocp->interrupt_loop();
+ iocp_thread.join();
+#endif
+}
diff --git a/protocols/Telegram/tdlib/td/benchmark/hashmap_build.cpp b/protocols/Telegram/tdlib/td/benchmark/hashmap_build.cpp
new file mode 100644
index 0000000000..f6e71b748a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/benchmark/hashmap_build.cpp
@@ -0,0 +1,545 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/FlatHashMap.h"
+
+#ifdef SCOPE_EXIT
+#undef SCOPE_EXIT
+#endif
+
+#include <absl/container/flat_hash_map.h>
+#include <array>
+#include <folly/container/F14Map.h>
+#include <map>
+#include <unordered_map>
+
+#define test_map td::FlatHashMap
+//#define test_map folly::F14FastMap
+//#define test_map absl::flat_hash_map
+//#define test_map std::map
+//#define test_map std::unordered_map
+
+//#define CREATE_MAP(num) CREATE_MAP_IMPL(num)
+#define CREATE_MAP(num)
+
+#define CREATE_MAP_IMPL(num) \
+ int f_##num() { \
+ test_map<td::int32, std::array<char, num>> m; \
+ m.emplace(1, std::array<char, num>{}); \
+ int sum = 0; \
+ for (auto &it : m) { \
+ sum += it.first; \
+ } \
+ auto it = m.find(1); \
+ sum += it->first; \
+ m.erase(it); \
+ return sum; \
+ } \
+ int x_##num = f_##num()
+
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+CREATE_MAP(__LINE__);
+
+int main() {
+}
diff --git a/protocols/Telegram/tdlib/td/benchmark/hashset_memory.cpp b/protocols/Telegram/tdlib/td/benchmark/hashset_memory.cpp
new file mode 100644
index 0000000000..8f31b52114
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/benchmark/hashset_memory.cpp
@@ -0,0 +1,193 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#if USE_MEMPROF
+#include "memprof/memprof_stat.h"
+#endif
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashMapChunks.h"
+#include "td/utils/FlatHashTable.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/logging.h"
+#include "td/utils/MapNode.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/Stat.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StringBuilder.h"
+
+#ifdef SCOPE_EXIT
+#undef SCOPE_EXIT
+#endif
+
+#include <absl/container/flat_hash_map.h>
+#include <array>
+#include <folly/container/F14Map.h>
+#include <functional>
+#include <map>
+#include <unordered_map>
+
+static int mem_stat_i = -1;
+static int mem_stat_cur = 0;
+
+static bool use_memprof() {
+#if USE_MEMPROF
+ return mem_stat_i < 0 && is_memprof_on();
+#else
+ return mem_stat_i < 0;
+#endif
+}
+
+static td::uint64 get_memory() {
+#if USE_MEMPROF
+ if (use_memprof()) {
+ return get_used_memory_size();
+ }
+#endif
+ CHECK(!use_memprof());
+ return td::mem_stat().ok().resident_size_;
+}
+
+template <class T>
+class Generator {
+ public:
+ T next() {
+ UNREACHABLE();
+ }
+ static size_t dyn_size() {
+ UNREACHABLE();
+ }
+};
+
+template <class T>
+class IntGenerator {
+ public:
+ T next() {
+ return ++value;
+ }
+ static size_t dyn_size() {
+ return 0;
+ }
+
+ private:
+ T value{};
+};
+
+template <>
+class Generator<td::int32> final : public IntGenerator<td::int32> {};
+template <>
+class Generator<td::int64> final : public IntGenerator<td::int64> {};
+
+template <class T>
+class Generator<td::unique_ptr<T>> {
+ public:
+ td::unique_ptr<T> next() {
+ return td::make_unique<T>();
+ }
+ static std::size_t dyn_size() {
+ return sizeof(T);
+ }
+};
+
+template <class T, class KeyT, class ValueT>
+static void measure(td::StringBuilder &sb, td::Slice name, td::Slice key_name, td::Slice value_name) {
+ mem_stat_cur++;
+ if (mem_stat_i >= 0 && mem_stat_cur != mem_stat_i) {
+ return;
+ }
+ sb << name << "<" << key_name << "," << value_name << "> " << (use_memprof() ? "memprof" : "os") << "\n";
+ std::size_t ideal_size = sizeof(KeyT) + sizeof(ValueT) + Generator<ValueT>::dyn_size();
+
+ sb << "\tempty:" << sizeof(T);
+ struct Stat {
+ int pi;
+ double min_ratio;
+ double max_ratio;
+ };
+ td::vector<Stat> stat;
+ stat.reserve(1024);
+ for (std::size_t size : {1000000u}) {
+ Generator<KeyT> key_generator;
+ Generator<ValueT> value_generator;
+ auto start_mem = get_memory();
+ T ht;
+ auto ratio = [&] {
+ auto end_mem = get_memory();
+ auto used_mem = end_mem - start_mem;
+ return static_cast<double>(used_mem) / (static_cast<double>(ideal_size) * static_cast<double>(ht.size()));
+ };
+ double min_ratio;
+ double max_ratio;
+ auto reset = [&] {
+ min_ratio = 1e100;
+ max_ratio = 0;
+ };
+ auto update = [&] {
+ auto x = ratio();
+ min_ratio = td::min(min_ratio, x);
+ max_ratio = td::max(max_ratio, x);
+ };
+ reset();
+
+ int p = 10;
+ int pi = 1;
+ for (std::size_t i = 0; i < size; i++) {
+ ht.emplace(key_generator.next(), value_generator.next());
+ update();
+ if ((i + 1) % p == 0) {
+ stat.push_back(Stat{pi, min_ratio, max_ratio});
+ reset();
+ pi++;
+ p *= 10;
+ }
+ }
+ }
+ for (auto &s : stat) {
+ sb << " 10^" << s.pi << ":" << s.min_ratio << "->" << s.max_ratio;
+ }
+ sb << '\n';
+}
+
+template <std::size_t size>
+using Bytes = std::array<char, size>;
+
+template <template <typename... Args> class T>
+void print_memory_stats(td::Slice name) {
+ td::string big_buff(1 << 16, '\0');
+ td::StringBuilder sb(big_buff, false);
+#define MEASURE(KeyT, ValueT) measure<T<KeyT, ValueT>, KeyT, ValueT>(sb, name, #KeyT, #ValueT);
+ MEASURE(td::int32, td::int32);
+ MEASURE(td::int64, td::unique_ptr<Bytes<360>>);
+ if (!sb.as_cslice().empty()) {
+ LOG(PLAIN) << '\n' << sb.as_cslice() << '\n';
+ }
+}
+
+template <class KeyT, class ValueT, class HashT = td::Hash<KeyT>, class EqT = std::equal_to<KeyT>>
+using FlatHashMapImpl = td::FlatHashTable<td::MapNode<KeyT, ValueT>, HashT, EqT>;
+
+#define FOR_EACH_TABLE(F) \
+ F(FlatHashMapImpl) \
+ F(folly::F14FastMap) \
+ F(absl::flat_hash_map) \
+ F(std::unordered_map) \
+ F(std::map)
+#define BENCHMARK_MEMORY(T) print_memory_stats<T>(#T);
+
+int main(int argc, const char *argv[]) {
+ // Usage:
+ // % benchmark/memory-hashset-os 0
+ // Number of benchmarks = 10
+ // % for i in {1..10}; do ./benchmark/memory-hashset-os $i; done
+ if (argc > 1) {
+ mem_stat_i = td::to_integer<td::int32>(td::Slice(argv[1]));
+ }
+ FOR_EACH_TABLE(BENCHMARK_MEMORY);
+ if (mem_stat_i <= 0) {
+ LOG(PLAIN) << "Number of benchmarks = " << mem_stat_cur << "\n";
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/benchmark/rmdir.cpp b/protocols/Telegram/tdlib/td/benchmark/rmdir.cpp
index f1676baa63..d61baa0a02 100644
--- a/protocols/Telegram/tdlib/td/benchmark/rmdir.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/rmdir.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,15 +14,16 @@ int main(int argc, char *argv[]) {
}
td::CSlice dir(argv[1]);
int cnt = 0;
- auto status = td::walk_path(dir, [&](td::CSlice path, bool is_dir) {
- cnt++;
- LOG(INFO) << path << " " << is_dir;
- // if (is_dir) {
+ auto status = td::walk_path(dir, [&](td::CSlice path, auto type) {
+ if (type != td::WalkPath::Type::EnterDir) {
+ cnt++;
+ LOG(INFO) << path << " " << (type == td::WalkPath::Type::ExitDir);
+ }
+ //if (is_dir) {
// td::rmdir(path);
//} else {
// td::unlink(path);
//}
});
LOG(INFO) << status << ": " << cnt;
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/benchmark/wget.cpp b/protocols/Telegram/tdlib/td/benchmark/wget.cpp
index dba997e61f..5145f317f1 100644
--- a/protocols/Telegram/tdlib/td/benchmark/wget.cpp
+++ b/protocols/Telegram/tdlib/td/benchmark/wget.cpp
@@ -1,39 +1,43 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/net/HttpQuery.h"
#include "td/net/Wget.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+
+#include "td/utils/common.h"
#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
-#include <memory>
-#include <string>
-
int main(int argc, char *argv[]) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO));
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
td::VERBOSITY_NAME(fd) = VERBOSITY_NAME(INFO);
- std::string url = (argc > 1 ? argv[1] : "https://telegram.org");
- auto scheduler = std::make_unique<td::ConcurrentScheduler>();
- scheduler->init(0);
+ td::string url = (argc > 1 ? argv[1] : "https://telegram.org");
+ auto timeout = 10;
+ auto ttl = 3;
+ auto prefer_ipv6 = (argc > 2 && td::string(argv[2]) == "-6");
+ auto scheduler = td::make_unique<td::ConcurrentScheduler>(0, 0);
scheduler
- ->create_actor_unsafe<td::Wget>(0, "Client", td::PromiseCreator::lambda([](td::Result<td::HttpQueryPtr> res) {
+ ->create_actor_unsafe<td::Wget>(0, "Client",
+ td::PromiseCreator::lambda([](td::Result<td::unique_ptr<td::HttpQuery>> res) {
+ if (res.is_error()) {
+ LOG(FATAL) << res.error();
+ }
LOG(ERROR) << *res.ok();
td::Scheduler::instance()->finish();
}),
- url)
+ url, td::Auto(), timeout, ttl, prefer_ipv6)
.release();
scheduler->start();
while (scheduler->run_main(10)) {
// empty
}
scheduler->finish();
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/bitbucket-pipelines.yml b/protocols/Telegram/tdlib/td/bitbucket-pipelines.yml
index 4dbdf6c914..5f6b364ae8 100644
--- a/protocols/Telegram/tdlib/td/bitbucket-pipelines.yml
+++ b/protocols/Telegram/tdlib/td/bitbucket-pipelines.yml
@@ -1,4 +1,4 @@
-image: gcc:5.4
+image: gcc:latest
pipelines:
default:
diff --git a/protocols/Telegram/tdlib/td/build.html b/protocols/Telegram/tdlib/td/build.html
new file mode 100644
index 0000000000..2c24bbb30c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/build.html
@@ -0,0 +1,1141 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>TDLib build instructions</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <style>
+ :root {
+ --background: #fafafa;
+ --color: black;
+ --color-primary: #0088ff;
+ --color-primary-light: #42a7ff;
+ --color-code-block: #ebf9ff;
+ --color-select-border: rgb(211, 211, 211);
+ --color-checkbox-background: rgb(211, 211, 211);
+ --color-checkbox-tick: #ffffff;
+ --color-copy-success-background: #c1ffc6;
+ --color-copy-success-border: rgb(0, 255, 0);
+ --color-copy-fail-background: #ffcbcb;
+ --color-copy-fail-border: rgb(255, 0, 0);
+
+ color: var(--color);
+ background: var(--background);
+ }
+ @media (prefers-color-scheme: dark) {
+ :root {
+ --background: #0e0e0e;
+ --color: rgb(190, 190, 190);
+ --color-primary: #0088ff;
+ --color-code-block: #101315;
+ --color-select-border: rgb(54, 54, 54);
+ --color-checkbox-background: rgb(51, 51, 51);
+ --color-checkbox-tick: #ffffff;
+ --color-copy-success-background: #001f00;
+ --color-copy-success-border: rgb(0, 255, 0);
+ --color-copy-fail-background: #1f0000;
+ --color-copy-fail-border: rgb(255, 0, 0);
+ }
+ }
+ body {
+ font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
+ }
+ :focus:not(:focus-visible) {
+ outline: none;
+ }
+ input:focus-visible, select:focus-visible, button:focus-visible {
+ box-shadow: 0 0 0 3px var(--color-primary);
+ outline: none;
+ }
+
+ .hide {
+ display: none;
+ }
+ div.main {
+ max-width: 1250px;
+ padding: 25px;
+ margin: auto;
+ font-size: 16px;
+ }
+
+ p {
+ margin: 0;
+ }
+ .main > div {
+ margin-bottom: 20px;
+ }
+
+ #buildCommands {
+ font-family: Consolas, monospace;
+ margin-left: 40px;
+ background: var(--color-code-block);
+ padding: 5px;
+ margin-bottom: 0;
+ display: block;
+ overflow-x: auto;
+ }
+ #buildCommands ul {
+ list-style: '$ ';
+ }
+
+ a {
+ color: var(--color-primary);
+ text-decoration-color: transparent;
+ transition: text-decoration 200ms, color 200ms;
+ }
+ a:hover {
+ text-decoration: underline;
+ }
+ a:focus-visible {
+ text-decoration: underline 2px;
+ color: var(--color-primary-light);
+ outline: none;
+ }
+ select, button {
+ border: 1px solid var(--color-select-border);
+ background-color: var(--background);
+ color: var(--color);
+ padding: 5px;
+ margin-top: 5px;
+ box-shadow: 0 0 0 0 var(--color-primary);
+ transition: border 200ms, padding 200ms, box-shadow 200ms;
+ border-radius: 999em;
+ font-size: 16px;
+ cursor: pointer;
+ }
+
+ label * {
+ vertical-align: middle;
+ }
+
+ input[type=checkbox] {
+ margin-right: 5px;
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background-color: var(--color-checkbox-background);
+ height: 20px;
+ width: 20px;
+ border-radius: 3px;
+ position: relative;
+ top: -1px; // Fix alignment
+ transition: background-color 200ms, box-shadow 200ms;
+ }
+ input[type=checkbox]::after {
+ content: "";
+ transition: border-color 200ms;
+ position: absolute;
+ left: 6px;
+ top: 2px;
+ width: 5px;
+ height: 10px;
+ border: solid transparent;
+ border-width: 0 3px 3px 0;
+ -webkit-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ transform: rotate(45deg);
+ }
+ input[type=checkbox]:checked {
+ background-color: var(--color-primary);
+ }
+ input[type=checkbox]:checked::after {
+ border-color: var(--color-checkbox-tick);
+ }
+
+ input[type=radio] {
+ margin-right: 5px;
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background-color: var(--color-checkbox-background);
+ height: 20px;
+ width: 20px;
+ border-radius: 100%;
+ position: relative;
+ top: -2px; // Fix alignment
+ transition: background-color 200ms, box-shadow 200ms;
+ }
+ input[type=radio]::after {
+ content: "";
+ transition: border-color 200ms;
+ position: absolute;
+ left: 10px;
+ top: 10px;
+ width: 0;
+ height: 0;
+ border-radius: 100%;
+ background-color: transparent;
+ transition: width 200ms, height 200ms, left 200ms, top 200ms, background-color 100ms;
+ }
+ input[type=radio]:checked::after {
+ background-color: var(--color-primary);
+ left: 2px;
+ top: 2px;
+ width: 16px;
+ height: 16px;
+ }
+
+ #copyBuildCommandsButton {
+ margin-left: 40px;
+ }
+ #copyBuildCommandsButton.success {
+ background: var(--color-copy-success-background);
+ border-color: var(--color-copy-success-border);
+ }
+ #copyBuildCommandsButton.fail {
+ background: var(--color-copy-fail-background);
+ border-color: var(--color-copy-fail-border);
+ }
+ </style>
+</head>
+
+<body onload="onLoad(true)" onpopstate="onLoad(false)">
+
+<div class="main">
+ <div id="languageSelectDiv">
+ <p>Choose a programming language from which you want to use TDLib:</p>
+ <select id="languageSelect" onchange="onLanguageChanged(false)" autofocus class="large">
+ <option>Choose a programming language:</option>
+ <option>Python</option>
+ <option>JavaScript</option>
+ <option>Go</option>
+ <option>Java</option>
+ <option>Kotlin</option>
+ <option>C#</option>
+ <option>C++</option>
+ <option>Swift</option>
+ <option>Objective-C</option>
+ <option>Object Pascal</option>
+ <option>Dart</option>
+ <option>Rust</option>
+ <option>Erlang</option>
+ <option>PHP</option>
+ <option>Lua</option>
+ <option>Ruby</option>
+ <option>Crystal</option>
+ <option>Haskell</option>
+ <option>Nim</option>
+ <option>Clojure</option>
+ <option>D</option>
+ <option>Elixir</option>
+ <option>C</option>
+ <option>G</option>
+ <option>Other</option>
+ </select>
+ </div>
+
+ <div id="osSelectDiv" class="hide">
+ <p>Choose an operating system on which you want to use TDLib:</p>
+ <select id="osSelect" onchange="onOsChanged()" class="large">
+ <option>Choose an operating system:</option>
+ </select>
+ <p></p>
+ </div>
+
+ <div id="linuxSelectDiv" class="hide">
+ <p>Choose a Linux distro on which you want to use TDLib:</p>
+ <select id="linuxSelect" onchange="onOsChanged()" class="large">
+ <option>Choose a Linux distro:</option>
+ <option>Alpine</option>
+ <option>CentOS 7</option>
+ <option>CentOS 8</option>
+ <option>Debian 8</option>
+ <option>Debian 9</option>
+ <option>Debian 10+</option>
+ <option>Ubuntu 14</option>
+ <option>Ubuntu 16</option>
+ <option>Ubuntu 18</option>
+ <option>Ubuntu 20</option>
+ <option>Ubuntu 22</option>
+ <option>Other</option>
+ </select>
+ <p></p>
+ </div>
+
+ <div id="buildOptionsDiv" class="hide">
+ <div id="buildLtoDiv" class="hide">
+ <label><input type="checkbox" id="buildLtoCheckbox" onchange="onOptionsChanged()"/>Enable Link Time Optimization (requires CMake >= 3.9.0). It can significantly reduce binary size and increase performance, but sometimes it can lead to build failures.</label>
+ </div>
+
+ <div id="buildDebugDiv" class="hide">
+ <label><input type="checkbox" id="buildDebugCheckbox" onchange="onOptionsChanged()"/>Build the debug binary. Debug binaries are much larger and slower than the release one.</label>
+ </div>
+
+ <div id="buildInstallLocalDiv" class="hide">
+ <label><input type="checkbox" id="buildInstallLocalCheckbox" onchange="onOptionsChanged()"/>Install built TDLib to /usr/local instead of placing the files to td/tdlib.</label>
+ </div>
+
+ <p></p>
+
+ <div id="buildCompilerDiv" class="hide">
+ <span>Choose which compiler you want to use to build TDLib:</span><br>
+ <label><input type="radio" id="buildCompilerRadioGcc" name="buildCompilerRadio" onchange="onOptionsChanged()" checked/>g++</label>
+ <label><input type="radio" id="buildCompilerRadioClang" name="buildCompilerRadio" onchange="onOptionsChanged()"/>clang (recommended)</label>
+ <p></p>
+ </div>
+
+ <div id="buildArchiverDiv" class="hide">
+ <span>Choose which archiver application you want to use for VSIX creation:</span><br>
+ <label><input type="radio" id="buildArchiverRadio7Zip" name="buildArchiverRadio" onchange="onOptionsChanged()" checked/>7-Zip</label>
+ <label><input type="radio" id="buildArchiverRadioZip" name="buildArchiverRadio" onchange="onOptionsChanged()"/>zip</label>
+ <label><input type="radio" id="buildArchiverRadioWinRar" name="buildArchiverRadio" onchange="onOptionsChanged()"/>WinRAR</label>
+ <p></p>
+ </div>
+
+ <div id="buildShellDiv" class="hide">
+ <span>Choose which shell application you want to use for building:</span><br>
+ <label><input type="radio" id="buildShellRadioPowerShell" name="buildShellRadio" onchange="onOptionsChanged()" checked/>PowerShell</label>
+ <label><input type="radio" id="buildShellRadioBash" name="buildShellRadio" onchange="onOptionsChanged()"/>mintty/Bash</label>
+ <p></p>
+ </div>
+
+ <div id="buildShellBsdDiv" class="hide">
+ <span>Choose which shell application you want to use for building:</span><br>
+ <label><input type="radio" id="buildShellBsdRadioCsh" name="buildShellRadioBsd" onchange="onOptionsChanged()" checked/>tcsh/csh</label>
+ <label><input type="radio" id="buildShellBsdRadioBash" name="buildShellRadioBsd" onchange="onOptionsChanged()"/>Bash</label>
+ <p></p>
+ </div>
+
+ <div id="buildMacOsHostDiv" class="hide">
+ <span>Choose host architecture:</span><br>
+ <label><input type="radio" id="buildMacOsHostRadioAppleSilicon" name="buildMacOsHostRadio" onchange="onOptionsChanged()" checked/>Apple silicon</label>
+ <label><input type="radio" id="buildMacOsHostRadioIntel" name="buildMacOsHostRadio" onchange="onOptionsChanged()"/>Intel</label>
+ <p></p>
+ </div>
+
+ <div id="buildBitnessDiv" class="hide">
+ <span>Choose for which bitness you want to build TDLib:</span><br>
+ <label><input type="radio" id="buildBitnessRadio32" name="buildBitnessRadio" onchange="onOptionsChanged()" checked/>32</label>
+ <label><input type="radio" id="buildBitnessRadio64" name="buildBitnessRadio" onchange="onOptionsChanged()"/>64</label>
+ <p></p>
+ </div>
+
+ <div id="buildLowMemoryDiv" class="hide">
+ <label><input type="checkbox" id="buildLowMemoryCheckbox" onchange="onOptionsChanged()"/><span id="buildLowMemoryText">Check this if you have less than 4 GB of RAM</span></label>
+ </div>
+
+ <div id="buildRootDiv" class="hide">
+ <label><input type="checkbox" id="buildRootCheckbox" onchange="onOptionsChanged()"/>Build from root user (unrecommended)</label>
+ </div>
+
+ <p></p>
+ </div>
+
+ <div id="buildTextDiv" class="hide">
+ <p id="buildText">Hidden text</p>
+ </div>
+
+ <div id="buildCommandsDiv" class="hide">
+ <p id="buildCommandsHeader">Here is complete instruction for TDLib binaries building:</p>
+ <p id="buildPre">Hidden text</p>
+ <code id="buildCommands">Empty commands</code>
+ <button id="copyBuildCommandsButton" onclick="copyBuildInstructions()">
+ <span id="copyBuildCommandsText">Copy</span>
+ </button>
+ </div>
+</div>
+
+<script>
+function onLoad(initial) {
+ var url = new URL(window.location.href);
+ var language = url.searchParams.get('language');
+ if (language === 'csharp') {
+ language = 'c#';
+ } else if (language === 'cxx') {
+ language = 'c++';
+ } else if (!language) {
+ language = '';
+ }
+
+ var language_options = document.getElementById('languageSelect').options;
+ for (var i = 0; i < language_options.length; i++) {
+ language_options[i].selected = language_options[i].text.toLowerCase() === language.toLowerCase();
+ }
+
+ onLanguageChanged(initial || !history.state);
+}
+
+function getSupportedOs(language) {
+ switch (language) {
+ case 'Choose a programming language:':
+ return [];
+ case 'JavaScript':
+ return ['Windows (Node.js)', 'Linux (Node.js)', 'macOS (Node.js)', 'FreeBSD (Node.js)', 'OpenBSD (Node.js)', 'NetBSD (Node.js)', 'Browser'];
+ case 'Java':
+ case 'Kotlin':
+ return ['Android', 'Windows', 'Linux', 'macOS', 'FreeBSD', 'OpenBSD'];
+ case 'C#':
+ return ['Windows (through C++/CLI)', 'Universal Windows Platform (through C++/CX)', 'Windows (.NET Core)', 'Linux (.NET Core)', 'macOS (.NET Core)', 'FreeBSD (.NET Core)'];
+ case 'Dart':
+ return ['Android', 'iOS', 'Windows', 'Linux', 'macOS', 'tvOS', 'watchOS'];
+ case 'Swift':
+ case 'Objective-C':
+ return ['Windows', 'Linux', 'macOS', 'iOS', 'tvOS', 'watchOS'];
+ default:
+ return ['Windows', 'Linux', 'macOS', 'FreeBSD', 'OpenBSD', 'NetBSD'];
+ }
+}
+
+function getExampleAnchor(language) {
+ switch (language) {
+ case 'Python':
+ case 'JavaScript':
+ case 'Go':
+ case 'Java':
+ case 'Kotlin':
+ case 'Swift':
+ case 'Objective-C':
+ case 'Dart':
+ case 'Rust':
+ case 'Erlang':
+ case 'PHP':
+ case 'Lua':
+ case 'Ruby':
+ case 'Crystal':
+ case 'Haskell':
+ case 'Nim':
+ case 'Clojure':
+ case 'D':
+ case 'Elixir':
+ case 'C':
+ case 'G':
+ return language.toLowerCase();
+ case 'Object Pascal':
+ return 'object-pascal';
+ case 'C#':
+ return 'csharp';
+ case 'C++':
+ return 'cxx';
+ default:
+ return 'other';
+ }
+}
+
+function onLanguageChanged(initial) {
+ var language = document.getElementById('languageSelect').value;
+
+ var supported_os = getSupportedOs(language);
+
+ var os_select = document.getElementById('osSelect');
+
+ if (supported_os.length) {
+ document.getElementById('osSelectDiv').style.display = 'block';
+ } else {
+ if (history.state !== '' && history.state !== null) {
+ history.pushState('', '', 'build.html');
+ }
+ document.getElementById('osSelectDiv').style.display = 'none';
+ onOsChanged();
+ return;
+ }
+
+ if (!initial && history.state !== language) {
+ history.pushState(language, '', 'build.html?language=' + encodeURIComponent(language));
+ }
+
+ var existing_options = [];
+ for (var index = 1; index < os_select.length; index++) {
+ if (!supported_os.includes(os_select.options[index].value)) {
+ os_select.remove(index);
+ index--;
+ } else {
+ existing_options.push(os_select.options[index].value);
+ }
+ }
+
+ for (os of supported_os) {
+ if (!existing_options.includes(os)) {
+ var option = document.createElement('option');
+ option.text = os;
+ os_select.add(option);
+ }
+ }
+
+ onOsChanged();
+}
+
+function getTarget(language, os) {
+ var supported_os = getSupportedOs(language);
+ if (!supported_os.includes(os)) {
+ return null;
+ }
+
+ if (os.includes('C++/CLI')) {
+ return 'C++/CLI';
+ }
+ if (os.includes('C++/CX')) {
+ return 'C++/CX';
+ }
+ if (os.includes('Browser')) {
+ return 'WebAssembly';
+ }
+ if (os.includes('Android')) {
+ return 'Android';
+ }
+ if (os.includes('iOS') || os.includes('tvOS') || os.includes('watchOS')) {
+ return 'iOS';
+ }
+
+ switch (language) {
+ case 'C++':
+ return 'tdclient';
+ case 'Java':
+ case 'Kotlin':
+ return 'JNI';
+ default:
+ return 'tdjson';
+ }
+}
+
+function getTargetName(target) {
+ switch (target) {
+ case 'tdjson':
+ case 'WebAssembly':
+ case 'iOS':
+ return '<a href="https://github.com/tdlib/td#using-json">JSON</a>';
+ case 'tdclient':
+ return '<a href="https://github.com/tdlib/td#using-cxx">a simple and convenient C++11-interface</a>';
+ case 'JNI':
+ case 'Android':
+ return '<a href="https://github.com/tdlib/td#using-java">native ' + target + '</a>';
+ default:
+ return '<a href="https://github.com/tdlib/td#using-dotnet">native ' + target + '</a>';
+ }
+}
+
+function onOsChanged() {
+ var language = document.getElementById('languageSelect').value;
+ var os = document.getElementById('osSelect').value;
+ var target = getTarget(language, os);
+
+ document.getElementById('buildTextDiv').style.display = target ? 'block' : 'none';
+
+ if (language === 'Other') {
+ language = 'any other programming language';
+ }
+ var text = 'TDLib can be used from ' + language + ' on ' + os + ' through the ' + getTargetName(target) + ' interface.<br>' +
+ 'See <a href="https://github.com/tdlib/td/blob/master/example/README.md#' + getExampleAnchor(language) + '">examples</a> of such usage and already available third-party frameworks.<br>';
+
+ if (target === 'WebAssembly') {
+ text = 'TDLib is available in a prebuilt form as an <a href="https://www.npmjs.com/">NPM</a> package <a href="https://www.npmjs.com/package/tdweb">tdweb</a>.<br>' +
+ 'If you want to build it manually, take a look at our <a href="https://github.com/tdlib/td/tree/master/example/web">example</a>.';
+ target = '';
+ }
+ if (target === 'Android') {
+ text = 'TDLib for Android is available in a prebuilt form and can be downloaded from <a href="https://core.telegram.org/tdlib/tdlib.zip">there</a>.<br>' +
+ 'See <a href="https://github.com/tdlib/td/issues/77#issuecomment-640719893">build instructions</a> if you want to build the latest TDLib version or want to build TDLib with different interface.';
+ target = '';
+ }
+ if (target === 'iOS') {
+ target = '';
+ }
+ document.getElementById('buildText').innerHTML = text;
+
+ if (!target) {
+ document.getElementById('linuxSelectDiv').style.display = 'none';
+ document.getElementById('buildOptionsDiv').style.display = 'none';
+ document.getElementById('buildCommandsDiv').style.display = 'none';
+ return;
+ }
+
+ var os_linux = os.includes('Linux');
+ if (os_linux) {
+ document.getElementById('linuxSelectDiv').style.display = 'block';
+
+ var linux_distro = document.getElementById('linuxSelect').value;
+ if (linux_distro.includes('Choose ')) {
+ document.getElementById('buildTextDiv').style.display = 'none';
+ document.getElementById('buildOptionsDiv').style.display = 'none';
+ document.getElementById('buildCommandsDiv').style.display = 'none';
+ return;
+ }
+ } else {
+ document.getElementById('linuxSelectDiv').style.display = 'none';
+ }
+
+ document.getElementById('buildOptionsDiv').style.display = 'block';
+
+ onOptionsChanged();
+}
+
+function onOptionsChanged() {
+ var language = document.getElementById('languageSelect').value;
+ var os = document.getElementById('osSelect').value;
+ var target = getTarget(language, os);
+
+ var os_windows = os.includes('Windows');
+ var os_linux = os.includes('Linux');
+ var os_mac = os.includes('macOS');
+ var os_freebsd = os.includes('FreeBSD');
+ var os_openbsd = os.includes('OpenBSD');
+ var os_netbsd = os.includes('NetBSD');
+
+ var linux_distro = 'none';
+ if (os_linux) {
+ linux_distro = document.getElementById('linuxSelect').value;
+ }
+ document.getElementById('buildCommandsDiv').style.display = 'block';
+
+ var use_clang = os_freebsd || os_openbsd;
+ if (os_linux && linux_distro !== 'Alpine' && !linux_distro.includes('CentOS')) {
+ document.getElementById('buildCompilerDiv').style.display = 'block';
+ use_clang = document.getElementById('buildCompilerRadioClang').checked;
+ } else {
+ document.getElementById('buildCompilerDiv').style.display = 'none';
+ }
+
+ var low_memory = false;
+ if (os_linux || os_freebsd || os_netbsd) {
+ low_memory = document.getElementById('buildLowMemoryCheckbox').checked;
+ document.getElementById('buildLowMemoryText').innerHTML = 'I have less than ' + (use_clang ? '1.5' : '3.5') +' GB of RAM.' +
+ (low_memory ? ' Now you will need only ' + (use_clang ? '0.5' : '1') +' GB of RAM to build TDLib.' : '');
+ document.getElementById('buildLowMemoryDiv').style.display = 'block';
+ } else {
+ if (os_openbsd) {
+ low_memory = true;
+ }
+ document.getElementById('buildLowMemoryDiv').style.display = 'none';
+ }
+
+ var use_root = false;
+ if ((os_linux && linux_distro !== 'Other') || os_openbsd || os_netbsd) {
+ use_root = document.getElementById('buildRootCheckbox').checked;
+ document.getElementById('buildRootDiv').style.display = 'block';
+ } else {
+ document.getElementById('buildRootDiv').style.display = 'none';
+ }
+
+ var use_powershell = false;
+ var use_cmd = false;
+ var use_csh = false;
+ var homebrew_install_dir = '';
+ var os_mac_host_name = '';
+ if (os_windows) {
+ document.getElementById('buildShellDiv').style.display = 'block';
+ use_powershell = document.getElementById('buildShellRadioPowerShell').checked;
+ } else {
+ document.getElementById('buildShellDiv').style.display = 'none';
+ }
+ if (os_freebsd) {
+ document.getElementById('buildShellBsdDiv').style.display = 'block';
+ use_csh = document.getElementById('buildShellBsdRadioCsh').checked;
+ } else {
+ document.getElementById('buildShellBsdDiv').style.display = 'none';
+ }
+ if (os_mac) {
+ document.getElementById('buildMacOsHostDiv').style.display = 'block';
+ if (document.getElementById('buildMacOsHostRadioAppleSilicon').checked) {
+ homebrew_install_dir = '/opt/homebrew';
+ os_mac_host_name = 'Apple silicon';
+ } else {
+ homebrew_install_dir = '/usr/local';
+ os_mac_host_name = 'Intel';
+ }
+ } else {
+ document.getElementById('buildMacOsHostDiv').style.display = 'none';
+ }
+
+ var use_msvc = os_windows;
+ var use_vcpkg = os_windows;
+
+ var use_lto = false;
+ if (!use_msvc && language !== 'Java' && language !== 'Kotlin' && (os_mac || (os_linux && (linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20' || linux_distro === 'Ubuntu 22' || linux_distro === 'Other')))) {
+ document.getElementById('buildLtoDiv').style.display = 'block';
+ use_lto = document.getElementById('buildLtoCheckbox').checked;
+ } else {
+ document.getElementById('buildLtoDiv').style.display = 'none';
+ }
+
+ var archiver = 'none';
+ if (target === 'C++/CX') {
+ document.getElementById('buildArchiverDiv').style.display = 'block';
+ if (document.getElementById('buildArchiverRadio7Zip').checked) {
+ archiver = '7zip';
+ } else if (document.getElementById('buildArchiverRadioZip').checked) {
+ archiver = 'zip';
+ } else if (document.getElementById('buildArchiverRadioWinRar').checked) {
+ archiver = 'WinRAR';
+ }
+ } else {
+ document.getElementById('buildArchiverDiv').style.display = 'none';
+ }
+
+ var is_debug_build = false;
+ if (target !== 'C++/CX' && target !== 'C++/CLI') {
+ document.getElementById('buildDebugDiv').style.display = 'block';
+ is_debug_build = document.getElementById('buildDebugCheckbox').checked;
+ } else {
+ document.getElementById('buildDebugDiv').style.display = 'none';
+ }
+
+ var sudo = 'sudo ';
+ if (use_root || linux_distro.includes('Debian') || os_freebsd || os_openbsd || os_netbsd) {
+ sudo = '';
+ }
+
+ var build_32bit = false;
+ var build_64bit = false;
+ if (use_msvc && target !== 'C++/CX') {
+ document.getElementById('buildBitnessDiv').style.display = 'block';
+ build_32bit = document.getElementById('buildBitnessRadio32').checked;
+ build_64bit = document.getElementById('buildBitnessRadio64').checked;
+ } else {
+ document.getElementById('buildBitnessDiv').style.display = 'none';
+ }
+
+ var local = './';
+ if (use_cmd) {
+ local = '.\\';
+ }
+
+ var install_dir = 'td/tdlib';
+ if (!os_windows) {
+ document.getElementById('buildInstallLocalDiv').style.display = 'block';
+ if (document.getElementById('buildInstallLocalCheckbox').checked) {
+ install_dir = '/usr/local';
+ }
+ } else {
+ document.getElementById('buildInstallLocalDiv').style.display = 'none';
+ }
+
+ var pre_text = [];
+ if (os_windows) {
+ let win10_sdk = (target === 'C++/CX' ? ' and Windows 10 SDK' : '');
+ if (target !== 'C++/CLI' && target !== 'C++/CX') {
+ pre_text.push('Note that Windows Subsystem for Linux (WSL) and Cygwin are not Windows environments, so you need to use instructions for Linux for them instead.');
+ }
+ pre_text.push('Download and install <a href="https://visualstudio.microsoft.com/vs/community/">Microsoft Visual Studio</a>. Enable C++' + win10_sdk + ' support while installing.');
+ pre_text.push('Download and install <a href="https://cmake.org/download/">CMake</a>; choose "Add CMake to the system PATH" option while installing.');
+ pre_text.push('Download and install <a href="https://git-scm.com/download/win">Git</a>.');
+ pre_text.push('Download and unpack <a href="https://windows.php.net/download#php-7.2">PHP</a>. Add the path to php.exe to the PATH environment variable.');
+ if (target === 'C++/CX') {
+ switch (archiver) {
+ case '7zip':
+ pre_text.push('Download and install <a href="http://www.7-zip.org/download.html">7-Zip</a> archiver. Add the path to 7z.exe to the PATH environment variable.');
+ break;
+ case 'zip':
+ pre_text.push('Download and install <a href="http://gnuwin32.sourceforge.net/packages/zip.htm">zip</a> archiver. Add the path to zip.exe to the PATH environment variable.');
+ break;
+ case 'WinRAR':
+ pre_text.push('You need bought and installed <a href="https://www.win-rar.com/start.html?&L=4">WinRAR</a> archiver. Add the path to WinRAR.exe to the PATH environment variable.');
+ break;
+ }
+ }
+ }
+ if (target === 'JNI') {
+ if (os_windows) {
+ pre_text.push('Download and install <a href="https://www.oracle.com/technetwork/java/javase/downloads/index.html">JDK</a>.');
+ } else if (os_linux && linux_distro === 'Alpine') {
+ pre_text.push('Add community repository for your Alpine version (not edge) in /etc/apk/repositories. For example, you can do it through vim, preliminary installing it via "apk add --update vim".');
+ }
+ }
+ if (os_linux && linux_distro === 'Other') {
+ var jdk = target === 'JNI' ? ', JDK ' : '';
+ var compiler = use_clang ? 'clang >= 3.4, libc++' : 'g++ >= 4.9.2';
+ pre_text.push('Install Git, ' + compiler + ', make, CMake >= 3.0.2, OpenSSL-dev, zlib-dev, gperf, PHP' + jdk + ' using your package manager.');
+ }
+ if (os_linux && os.includes('Node.js')) {
+ pre_text.push('Note that for Node.js &ge; 17 you must build TDLib with OpenSSL 3.0.*, for Node.js &ge; 10 with OpenSSL 1.1.*, and for Node.js &lt; 10 with OpenSSL 1.0.*, so you may need to modify the following commands to install a proper OpenSSL version.');
+ }
+ if (os_freebsd) {
+ pre_text.push('Note that the following instruction is for FreeBSD 11.');
+ pre_text.push('Note that the following calls to <code>pkg</code> needs to be run as <code>root</code>.');
+ }
+ if (os_openbsd) {
+ pre_text.push('Note that the following instruction is for OpenBSD 6.7 and default KSH shell.');
+ pre_text.push('Note that building requires a lot of memory, so you may need to increase allowed per-process memory usage in /etc/login.conf or build from root.');
+ }
+ if (os_netbsd) {
+ pre_text.push('Note that the following instruction is for NetBSD 8+ and default SH shell.');
+ }
+ if (os_mac) {
+ pre_text.push('Note that the following instruction will build TDLib only for ' + os_mac_host_name + '.');
+ pre_text.push('If you want to create a universal XCFramework, take a look at our <a href="https://github.com/tdlib/td/tree/master/example/ios">example</a> instead.');
+ }
+
+ var terminal_name = (function () {
+ if (os_windows) {
+ return use_powershell ? 'PowerShell' : 'mintty/Bash';
+ }
+ if (os_mac) {
+ return 'Terminal';
+ }
+ if (os_openbsd) {
+ return 'ksh';
+ }
+ if (os_netbsd) {
+ return 'sh';
+ }
+ if (use_csh) {
+ return 'tcsh/csh';
+ }
+ return 'Bash';
+ })();
+ if (os_windows) {
+ pre_text.push('Close and re-open ' + terminal_name + ' if the PATH environment variable was changed.');
+ }
+ pre_text.push('Run these commands in ' + terminal_name + ' to build TDLib and to install it to ' + install_dir + ':');
+ document.getElementById('buildPre').innerHTML = '<ul><li>' + pre_text.join('</li><li>') + '</li></ul>';
+ document.getElementById('buildPre').style.display = 'block';
+
+ if (install_dir && install_dir !== '/usr/local') {
+ install_dir = '../tdlib';
+ if (target === 'JNI' || target === 'C++/CX') {
+ install_dir = '../../' + install_dir;
+ }
+ }
+ var jni_install_dir = '';
+ if (target === 'JNI') {
+ jni_install_dir = install_dir;
+ install_dir = '../example/java/td';
+ }
+
+ function getClangVersionSuffix() {
+ switch (linux_distro) {
+ case 'Ubuntu 14':
+ return '-3.9';
+ case 'Ubuntu 18':
+ return '-6.0';
+ case 'Ubuntu 20':
+ return '-10';
+ case 'Ubuntu 22':
+ return '-14';
+ default:
+ return ''; // use default version
+ }
+ }
+
+ var commands = [];
+
+ var php = 'php';
+ var cmake = 'cmake';
+ if (os_mac) {
+ commands.push('xcode-select --install');
+ commands.push('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"');
+ commands.push('brew install gperf cmake openssl' + (target === 'JNI' ? ' coreutils' : ''));
+ if (target === 'JNI') {
+ commands.push('brew install openjdk');
+ }
+ } else if (os_linux && linux_distro !== 'Other') {
+ switch (linux_distro) {
+ case 'Alpine':
+ commands.push(sudo + 'apk update');
+ commands.push(sudo + 'apk upgrade');
+ var packages = 'alpine-sdk linux-headers git zlib-dev openssl-dev gperf php cmake';
+ if (target === 'JNI') {
+ packages += ' openjdk8';
+ }
+ commands.push(sudo + 'apk add --update ' + packages);
+ break;
+ case 'CentOS 7':
+ case 'CentOS 8':
+ commands.push(sudo + 'yum update -y');
+ var packages = 'gcc-c++ make git zlib-devel openssl-devel php';
+ if (linux_distro === 'CentOS 7') {
+ commands.push(sudo + 'yum install -y centos-release-scl-rh epel-release');
+ commands.push(sudo + 'yum install -y devtoolset-9-gcc devtoolset-9-gcc-c++');
+ cmake = 'cmake3';
+ packages += ' gperf';
+ } else {
+ commands.push(sudo + 'dnf --enablerepo=powertools install gperf');
+ }
+ packages += ' ' + cmake;
+ if (target === 'JNI') {
+ packages += ' java-11-openjdk-devel';
+ }
+ commands.push(sudo + 'yum install -y ' + packages);
+ break;
+ case 'Debian 8':
+ case 'Debian 9':
+ case 'Debian 10+':
+ case 'Ubuntu 14':
+ case 'Ubuntu 16':
+ case 'Ubuntu 18':
+ case 'Ubuntu 20':
+ case 'Ubuntu 22':
+ if (linux_distro.includes('Debian') && !use_root) {
+ commands.push('su -');
+ }
+ if (linux_distro === 'Ubuntu 14' && !use_clang) {
+ commands.push(sudo + 'add-apt-repository ppa:ubuntu-toolchain-r/test');
+ }
+ commands.push(sudo + 'apt-get update');
+ commands.push(sudo + 'apt-get upgrade');
+ var packages = 'make git zlib1g-dev libssl-dev gperf';
+ if (linux_distro === 'Ubuntu 14' || linux_distro === 'Debian 8') {
+ packages += ' php5-cli';
+ } else {
+ packages += ' php-cli';
+ }
+ if (linux_distro === 'Ubuntu 14') {
+ packages += ' cmake3';
+ } else {
+ packages += ' cmake';
+ }
+ if (target === 'JNI') {
+ packages += ' default-jdk';
+ }
+ if (use_clang) {
+ packages += ' clang' + getClangVersionSuffix() + ' libc++-dev';
+ if (linux_distro === 'Debian 10+' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20' || linux_distro === 'Ubuntu 22') {
+ packages += ' libc++abi-dev';
+ }
+ } else {
+ packages += ' g++';
+ if (linux_distro === 'Ubuntu 14') {
+ packages += '-4.9';
+ }
+ }
+ commands.push(sudo + 'apt-get install ' + packages);
+ if (linux_distro.includes('Debian') && !use_root) {
+ commands.push('exit');
+ }
+ break;
+ }
+ } else if (os_freebsd) {
+ commands.push(sudo + 'pkg upgrade');
+ var packages = 'git gperf php72 cmake';
+ if (target === 'JNI') {
+ packages += ' openjdk';
+ }
+ commands.push(sudo + 'pkg install ' + packages);
+ } else if (os_openbsd) {
+ if (!use_root) {
+ commands.push('su -');
+ }
+ var packages = 'git gperf php-7.2.10 cmake';
+ if (target === 'JNI') {
+ packages += ' jdk';
+ }
+ commands.push('pkg_add -z ' + packages);
+ if (!use_root) {
+ commands.push('exit');
+ }
+
+ php = "php-7.2";
+ } else if (os_netbsd) {
+ if (!use_root) {
+ commands.push('su -');
+ }
+ commands.push('export PKG_PATH=http://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r)/All');
+ var packages = 'git gperf pcre2 php-8* cmake openssl gcc12-libs mozilla-rootcerts-openssl';
+ commands.push('pkg_add ' + packages);
+ if (!use_root) {
+ commands.push('exit');
+ }
+ }
+ commands.push('git clone https://github.com/tdlib/td.git');
+
+ commands.push('cd td');
+ // commands.push('git checkout v1.8.0');
+
+ if (use_vcpkg) {
+ commands.push('git clone https://github.com/Microsoft/vcpkg.git');
+ commands.push('cd vcpkg');
+ commands.push('git checkout 1b1ae50e1a69f7c659bd7d731e80b358d21c86ad');
+ commands.push(local + 'bootstrap-vcpkg.bat');
+ if (target === 'C++/CX') {
+ commands.push(local + 'vcpkg.exe install gperf:x86-windows openssl:arm-uwp openssl:arm64-uwp openssl:x64-uwp openssl:x86-uwp zlib:arm-uwp zlib:arm64-uwp zlib:x64-uwp zlib:x86-uwp');
+ } else {
+ if (build_64bit) {
+ commands.push(local + 'vcpkg.exe install gperf:x64-windows openssl:x64-windows zlib:x64-windows');
+ } else {
+ commands.push(local + 'vcpkg.exe install gperf:x86-windows openssl:x86-windows zlib:x86-windows');
+ }
+ }
+ commands.push('cd ..');
+ }
+
+ function getBacicCmakeInitOptions() {
+ var options = [];
+ if (!use_msvc) {
+ options.push('-DCMAKE_BUILD_TYPE=' + (is_debug_build ? 'Debug' : 'Release'));
+ }
+ if (use_msvc) {
+ if (build_64bit) {
+ options.push('-A x64');
+ } else {
+ options.push('-A Win32');
+ }
+ }
+ if (target === 'JNI') {
+ if (os_linux && linux_distro === 'Alpine') {
+ options.push('-DJAVA_HOME=/usr/lib/jvm/java-1.8-openjdk/');
+ }
+ if (os_freebsd) {
+ options.push('-DJAVA_HOME=/usr/local/openjdk7/');
+ }
+ if (os_mac) {
+ options.push('-DJAVA_HOME=' + homebrew_install_dir + '/opt/openjdk/libexec/openjdk.jdk/Contents/Home/');
+ }
+ }
+ return options;
+ }
+
+ if (target === 'C++/CX') {
+ commands.push('cd example/uwp');
+ var archiver_option = '';
+ switch (archiver) {
+ case 'zip':
+ archiver_option = ' -compress zip';
+ break;
+ case 'WinRAR':
+ archiver_option = ' -compress winrar';
+ break;
+ }
+
+ commands.push('powershell -ExecutionPolicy ByPass ' + local + 'build.ps1 -vcpkg_root ../../vcpkg -mode clean');
+ commands.push('powershell -ExecutionPolicy ByPass ' + local + 'build.ps1 -vcpkg_root ../../vcpkg' + archiver_option);
+ if (install_dir) {
+ commands.push('cp build-uwp/vsix/tdlib.vsix ' + install_dir);
+ }
+ commands.push('cd ../..');
+ } else {
+ commands.push(use_powershell ? 'Remove-Item build -Force -Recurse -ErrorAction SilentlyContinue' : 'rm -rf build');
+ commands.push('mkdir build');
+ commands.push('cd build');
+
+ cmake_init_options = getBacicCmakeInitOptions();
+ if (os_mac) {
+ cmake_init_options.push('-DOPENSSL_ROOT_DIR=' + homebrew_install_dir + '/opt/openssl/');
+ }
+ if (install_dir) {
+ cmake_init_options.push('-DCMAKE_INSTALL_PREFIX:PATH=' + install_dir);
+ }
+ if (target === 'JNI') {
+ cmake_init_options.push('-DTD_ENABLE_JNI=ON');
+ }
+ if (target === 'C++/CX' || target === 'C++/CLI') {
+ cmake_init_options.push('-DTD_ENABLE_DOTNET=ON');
+ }
+ if (use_lto) {
+ cmake_init_options.push('-DTD_ENABLE_LTO=ON');
+ }
+ if (use_vcpkg) {
+ cmake_init_options.push('-DCMAKE_TOOLCHAIN_FILE:FILEPATH=../vcpkg/scripts/buildsystems/vcpkg.cmake');
+ }
+
+ function getCmakeInitCommand(options) {
+ var prefix = '';
+ if (os_linux) {
+ if (use_clang) {
+ var clang_version_suffix = getClangVersionSuffix();
+ prefix = 'CXXFLAGS="-stdlib=libc++" CC=/usr/bin/clang' + clang_version_suffix + ' CXX=/usr/bin/clang++' + clang_version_suffix + ' ';
+ if (use_lto) {
+ options.push('-DCMAKE_AR=/usr/bin/llvm-ar' + clang_version_suffix);
+ options.push('-DCMAKE_NM=/usr/bin/llvm-nm' + clang_version_suffix);
+ options.push('-DCMAKE_OBJDUMP=/usr/bin/llvm-objdump' + clang_version_suffix);
+ options.push('-DCMAKE_RANLIB=/usr/bin/llvm-ranlib' + clang_version_suffix);
+ }
+ } else if (linux_distro === 'Ubuntu 14') {
+ prefix = 'CC=/usr/bin/gcc-4.9 CXX=/usr/bin/g++-4.9 ';
+ } else if (linux_distro === 'CentOS 7') {
+ prefix = 'CC=/opt/rh/devtoolset-9/root/usr/bin/gcc CXX=/opt/rh/devtoolset-9/root/usr/bin/g++ ';
+ }
+ }
+ return prefix + cmake + ' ' + options.join(' ') + ' ..';
+ }
+ commands.push(getCmakeInitCommand(cmake_init_options));
+
+ if (low_memory) {
+ commands.push(cmake + ' --build . --target prepare_cross_compiling');
+ commands.push('cd ..');
+ commands.push(php + ' SplitSource.php');
+ commands.push('cd build');
+ }
+
+ let build_command = cmake + ' --build .';
+ if (install_dir) {
+ build_command += ' --target install';
+ }
+ if (use_msvc) {
+ if (target === 'C++/CLI' || !is_debug_build) {
+ commands.push(build_command + ' --config Release');
+ }
+ if (target === 'C++/CLI' || is_debug_build) {
+ commands.push(build_command + ' --config Debug');
+ }
+ } else {
+ commands.push(build_command);
+ }
+ if (install_dir && os_linux && linux_distro === 'Debian 8') {
+ commands.push('sed -i "s/LINK_ONLY:td/LINK_ONLY:Td::td/g" ' + install_dir + '/lib/cmake/Td/TdTargets.cmake');
+ }
+ commands.push('cd ..');
+ if (target === 'JNI') {
+ commands.push('cd example/java');
+ commands.push(use_powershell ? 'Remove-Item build -Force -Recurse -ErrorAction SilentlyContinue' : 'rm -rf build');
+ commands.push('mkdir build');
+ commands.push('cd build');
+
+ cmake_init_options = getBacicCmakeInitOptions();
+ if (jni_install_dir) {
+ cmake_init_options.push('-DCMAKE_INSTALL_PREFIX:PATH=' + jni_install_dir);
+ }
+ if (use_vcpkg) {
+ cmake_init_options.push('-DCMAKE_TOOLCHAIN_FILE:FILEPATH=../../../vcpkg/scripts/buildsystems/vcpkg.cmake');
+ }
+ var is_alpine = os_linux && linux_distro === 'Alpine';
+ var resolve_path = use_powershell ? 'Resolve-Path' : (os_mac ? 'greadlink -e' : (is_alpine || os_freebsd || os_openbsd || os_netbsd ? 'readlink -f' : 'readlink -e'));
+ var resolved_path = resolve_path + ' ../td/lib/cmake/Td';
+ if (use_csh) {
+ cmake_init_options.push('-DTd_DIR:PATH=$td_dir');
+ commands.push('set td_dir=`' + resolved_path + '` ; ' + getCmakeInitCommand(cmake_init_options));
+ } else {
+ cmake_init_options.push('-DTd_DIR:PATH=$(' + resolved_path + ')');
+ commands.push(getCmakeInitCommand(cmake_init_options));
+ }
+
+ build_command = cmake + ' --build .';
+ if (jni_install_dir) {
+ build_command += ' --target install';
+ }
+ if (use_msvc) {
+ build_command += (is_debug_build ? ' --config Debug' : ' --config Release');
+ }
+ commands.push(build_command);
+ commands.push('cd ../../..');
+ } else if (target === 'C++/CX' || target === 'C++/CLI') {
+ commands.push('git checkout td/telegram/Client.h td/telegram/Log.h td/tl/TlObject.h');
+ }
+ if (low_memory) {
+ commands.push(php + ' SplitSource.php --undo');
+ }
+ commands.push('cd ..');
+ }
+ if (jni_install_dir) {
+ install_dir = jni_install_dir;
+ }
+ if (install_dir) {
+ if (install_dir !== '/usr/local') {
+ install_dir = 'td/tdlib';
+ }
+ commands.push((use_powershell ? 'dir ' : 'ls -l ') + install_dir);
+ }
+ document.getElementById('buildCommands').innerHTML = '<ul><li>' + commands.join('</li><li>') + '</li></ul>';
+ document.getElementById('copyBuildCommandsButton').style.display = commands.includes('exit') ? 'none' : 'block';
+}
+
+function copyBuildInstructions() {
+ var text = document.getElementById('buildCommands').innerText;
+
+ function resetButtonState (state) {
+ document.getElementById('copyBuildCommandsButton').classList.remove(state);
+ document.getElementById('copyBuildCommandsText').innerText = "Copy";
+ }
+
+ navigator.clipboard.writeText(text).then(result => {
+ document.getElementById('copyBuildCommandsButton').classList.add('success');
+ document.getElementById('copyBuildCommandsText').innerText = "Copied!";
+ setTimeout(() => resetButtonState('success'), 5000);
+ }, error => {
+ document.getElementById('copyBuildCommandsButton').classList.add('fail');
+ document.getElementById('copyBuildCommandsText').innerText = "Couldn't copy :(";
+ setTimeout(() => resetButtonState('fail'), 5000);
+ })
+}
+
+</script>
+
+</body>
+</html>
diff --git a/protocols/Telegram/tdlib/td/example/README.md b/protocols/Telegram/tdlib/td/example/README.md
new file mode 100644
index 0000000000..65bd53d411
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/README.md
@@ -0,0 +1,315 @@
+# TDLib usage and build examples
+
+This directory contains basic examples of TDLib usage from different programming languages and examples of library building for different platforms.
+If you are looking for documentation of all available TDLib methods, see the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the
+automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of all available TDLib
+[methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
+Also take a look at our [Getting Started](https://core.telegram.org/tdlib/getting-started) tutorial for a description of basic TDLib concepts.
+
+TDLib can be easily used from almost any programming language on any platform. See a [TDLib build instructions generator](https://tdlib.github.io/td/build.html) for detailed instructions on how to build TDLib.
+Choose your preferred programming language to see examples of usage and a detailed description:
+
+- [Python](#python)
+- [JavaScript](#javascript)
+- [Go](#go)
+- [Java](#java)
+- [Kotlin](#kotlin)
+- [C#](#csharp)
+- [C++](#cxx)
+- [Swift](#swift)
+- [Objective-C](#objective-c)
+- [Object Pascal](#object-pascal)
+- [Dart](#dart)
+- [Rust](#rust)
+- [Erlang](#erlang)
+- [PHP](#php)
+- [Lua](#lua)
+- [Ruby](#ruby)
+- [Crystal](#crystal)
+- [Haskell](#haskell)
+- [Nim](#nim)
+- [Clojure](#clojure)
+- [Emacs Lisp](#emacslisp)
+- [D](#d)
+- [Elixir](#elixir)
+- [1С](#1s)
+- [C](#c)
+- [G](#g)
+- [Other](#other)
+
+<a name="python"></a>
+## Using TDLib in Python projects
+
+TDLib can be used from Python through the [JSON](https://github.com/tdlib/td#using-json) interface.
+
+Convenient Python wrappers already exist for our JSON interface.
+
+If you use Python >= 3.6, take a look at [python-telegram](https://github.com/alexander-akhmetov/python-telegram).
+The wrapper uses the full power of asyncio, has a good documentation and has several examples. It can be installed through pip or used in a Docker container.
+You can also try a fork [python-telegram](https://github.com/iTeam-co/pytglib) of this library.
+
+If you want to use TDLib with asyncio and Python >= 3.9, take a look at [aiotdlib](https://github.com/pylakey/aiotdlib) or [Pytdbot](https://github.com/pytdbot/client).
+
+For older Python versions you can use [pytdlib](https://github.com/pytdlib/pytdlib).
+This wrapper contains generator for TDLib API classes and basic interface for interaction with TDLib.
+
+You can also check out [example/python/tdjson_example.py](https://github.com/tdlib/td/blob/master/example/python/tdjson_example.py),
+[tdlib-python](https://github.com/JunaidBabu/tdlib-python), or [Python Wrapper TDLib](https://github.com/alvhix/pywtdlib) for some basic examples of TDLib JSON interface integration with Python.
+
+<a name="javascript"></a>
+## Using TDLib in JavaScript projects
+
+TDLib can be compiled to WebAssembly or asm.js and used in a browser from JavaScript. See [tdweb](https://github.com/tdlib/td/tree/master/example/web) as a convenient wrapper for TDLib in a browser
+and [telegram-react](https://github.com/evgeny-nadymov/telegram-react) as an example of a TDLib-based Telegram client.
+
+See also [Svelte-tdweb-starter](https://github.com/gennadypolakov/svelte-tdweb-starter) - Svelte wrapper for tdweb, and [Telegram-Photoframe](https://github.com/lukefx/telegram-photoframe) - a web application that displays your prefered group or channel as Photoframe.
+
+TDLib can be used from Node.js through the [JSON](https://github.com/tdlib/td#using-json) interface.
+
+Convenient Node.js wrappers already exist for our JSON interface.
+For example, take a look at [Airgram](https://github.com/airgram/airgram) – modern TDLib framework for TypeScript/JavaScript, or
+at [tdl](https://github.com/Bannerets/tdl), which provides a convenient, fully-asynchronous interface for interaction with TDLib and contains a bunch of examples.
+
+You can also see [TdNode](https://github.com/puppy0cam/TdNode), [tglib](https://github.com/nodegin/tglib), [node-tdlib](https://github.com/wfjsw/node-tdlib), [tdlnode](https://github.com/fonbah/tdlnode),
+[Paper Plane](https://github.com/par6n/paper-plane), or [node-tlg](https://github.com/dilongfa/node-tlg) for other examples of TDLib JSON interface integration with Node.js.
+
+See also the source code of [DIBgram](https://github.com/DIBgram/DIBgram) - an unofficial Telegram web application which looks like Telegram Desktop.
+
+TDLib can be used also from NativeScript through the [JSON](https://github.com/tdlib/td#using-json) interface.
+See [nativescript-tglib](https://github.com/arpit2438735/nativescript-tglib) as an example of a NativeScript library for building Telegram clients.
+
+<a name="go"></a>
+## Using TDLib in Go projects
+
+TDLib can be used from the Go programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and Cgo, and can be linked either statically or dynamically.
+
+Convenient Go wrappers already exist for our JSON interface.
+For example, take a look at [go-tdlib](https://github.com/zelenin/go-tdlib) or [go-tdlib](https://github.com/Arman92/go-tdlib), which provide a convenient TDLib client, a generator for TDLib API classes and contain many examples.
+
+You can also see [go-tdjson](https://github.com/L11R/go-tdjson) for another example of TDLib JSON interface integration with Go.
+
+<a name="java"></a>
+## Using TDLib in Java projects
+
+TDLib can be used from the Java programming language through native [JNI](https://github.com/tdlib/td#using-java) binding.
+
+We provide a generator for JNI bridge methods and Java classes for all TDLib API methods and objects.
+See [example/java](https://github.com/tdlib/td/tree/master/example/java) for an example of using TDLib from desktop Java along with detailed building and usage instructions.
+To use TDLib to create Android Java applications, use our [prebuilt library for Android](https://core.telegram.org/tdlib/tdlib.zip).
+
+<a name="kotlin"></a>
+## Using TDLib in Kotlin projects
+
+TDLib can be used from the Kotlin/JVM programming language through same way as in [Java](#java).
+
+You can also use [ktd](https://github.com/whyoleg/ktd) library with Kotlin-specific bindings.
+
+See also [td-ktx](https://github.com/tdlibx/td-ktx) - Kotlin coroutines wrapper for TDLib.
+
+<a name="csharp"></a>
+## Using TDLib in C# projects
+
+TDLib provides a native [.NET](https://github.com/tdlib/td#using-dotnet) interface through `C++/CLI` and `C++/CX`.
+See [tdlib-netcore](https://github.com/dantmnf/tdlib-netcore) for a SWIG-like binding with automatically generated classes for TDLib API.
+See [example/uwp](https://github.com/tdlib/td/tree/master/example/uwp) for an example of building TDLib SDK for the Universal Windows Platform and an example of its usage from C#.
+See [example/csharp](https://github.com/tdlib/td/tree/master/example/csharp) for an example of building TDLib with `C++/CLI` support and an example of TDLib usage from C# on Windows.
+
+If you want to write a cross-platform C# application using .NET Core, see [tdsharp](https://github.com/egramtel/tdsharp). It uses our [JSON](https://github.com/tdlib/td#using-json) interface,
+provides an asynchronous interface for interaction with TDLib, automatically generated classes for TDLib API and has some examples.
+
+You can also use [TDLibCore](https://github.com/ph09nix/TDLibCore) library.
+
+Also see [Unigram](https://github.com/UnigramDev/Unigram), which is a full-featured client rewritten from scratch in C# using TDLib SDK for Universal Windows Platform in less than 2 months,
+[egram.tel](https://github.com/egramtel/egram.tel) – a cross-platform Telegram client written in C#, .NET Core, ReactiveUI and Avalonia, or
+[telewear](https://github.com/telewear/telewear) - a Telegram client for Samsung watches.
+
+<a name="cxx"></a>
+## Using TDLib in C++ projects
+
+TDLib has a simple and convenient C++11-interface for sending and receiving requests and can be statically linked to your application.
+
+See [example/cpp](https://github.com/tdlib/td/tree/master/example/cpp) for an example of TDLib usage from C++.
+[td_example.cpp](https://github.com/tdlib/td/blob/master/example/cpp/td_example.cpp) contains an example of authorization, processing new incoming messages, getting a list of chats and sending a text message.
+
+See also the source code of [Fernschreiber](https://github.com/Wunderfitz/harbour-fernschreiber) and [Depecher](https://github.com/blacksailer/depecher) – Telegram apps for Sailfish OS,
+[TELEports](https://gitlab.com/ubports/development/apps/teleports) – a Qt-client for Ubuntu Touch, [tdlib-purple](https://github.com/ars3niy/tdlib-purple) - Telegram plugin for Pidgin,
+or [MeeGram](https://github.com/qtinsider/meegram2) - a Telegram client for Nokia N9,
+[TDLib Native Sciter Extension](https://github.com/EricKotato/TDLibNSE) - a Sciter native extension for TDLib's JSON interface, all of which are based on TDLib.
+
+<a name="swift"></a>
+## Using TDLib in Swift projects
+
+TDLib can be used from the Swift programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
+
+See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, and macOS.
+
+See [TDLibKit](https://github.com/Swiftgram/TDLibKit), [tdlib-swift](https://github.com/modestman/tdlib-swift), or [TDLib-iOS](https://github.com/leoMehlig/TDLib-iOS), which provide convenient TDLib clients with automatically generated and fully-documented classes for all TDLib API methods and objects.
+
+See also the source code of [Moc](https://github.com/mock-foundation/moc) - a native and powerful macOS and iPadOS Telegram client, optimized for moderating large communities and personal use.
+
+See [example/swift](https://github.com/tdlib/td/tree/master/example/swift) for an example of a macOS Swift application.
+
+<a name="objective-c"></a>
+## Using TDLib in Objective-C projects
+
+TDLib can be used from the Objective-C programming language through [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
+
+See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, and macOS.
+
+<a name="object-pascal"></a>
+## Using TDLib in Object Pascal projects with Delphi and Lazarus
+
+TDLib can be used from the Object Pascal programming language through the [JSON](https://github.com/tdlib/td#using-json).
+
+See [tdlib-delphi](https://github.com/dieletro/tdlib-delphi) for an example of TDLib usage from Delphi.
+
+See [tdlib-lazarus](https://github.com/dieletro/tdlib-lazarus) for an example of TDLib usage from Lazarus.
+
+<a name="dart"></a>
+## Using TDLib in Dart projects
+
+TDLib can be used from the Dart programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and a Dart Native Extension or Dart FFI.
+
+See [tdlib-dart](https://github.com/ivk1800/tdlib-dart), which provide convenient TDLib client with automatically generated and fully-documented classes for all TDLib API methods and objects.
+
+See also [dart_tdlib](https://github.com/periodicaidan/dart_tdlib), [flutter_libtdjson](https://github.com/up9cloud/flutter_libtdjson), [Dart wrapper for TDLib](https://github.com/tdlib/td/pull/708/commits/237060abd4c205768153180e9f814298d1aa9d49), or [tdlib_bindings](https://github.com/lesnitsky/tdlib_bindings) for an example of a TDLib Dart bindings through FFI.
+
+See [Telegram Client library](https://github.com/azkadev/telegram_client), [project.scarlet](https://github.com/aaugmentum/project.scarlet), [tdlib](https://github.com/i-Naji/tdlib),
+[tdlib-dart](https://github.com/drewpayment/tdlib-dart), [FluGram](https://github.com/triedcatched/tdlib-dart), or [telegram-service](https://github.com/igorder-dev/telegram-service) for examples of using TDLib from Dart.
+
+See also [telegram-flutter](https://github.com/ivk1800/telegram-flutter) - Telegram client written in Dart, and [f-Telegram](https://github.com/evgfilim1/ftg) - Flutter Telegram client.
+
+<a name="rust"></a>
+## Using TDLib in Rust projects
+
+TDLib can be used from the Rust programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
+
+See [rust-tdlib](https://github.com/aCLr/rust-tdlib), or [tdlib](https://github.com/melix99/tdlib-rs), which provide convenient TDLib clients with automatically generated and fully-documented classes for all TDLib API methods and objects.
+
+See [rtdlib](https://github.com/fewensa/rtdlib), [tdlib-rs](https://github.com/d653/tdlib-rs), [tdlib-futures](https://github.com/yuri91/tdlib-futures),
+[tdlib-sys](https://github.com/nuxeh/tdlib-sys), [tdjson-rs](https://github.com/mersinvald/tdjson-rs), [rust-tdlib](https://github.com/vhaoran/rust-tdlib), or [tdlib-json-sys](https://github.com/aykxt/tdlib-json-sys) for examples of TDLib Rust bindings.
+
+Also see [Telegrand](https://github.com/melix99/telegrand) – a Telegram client optimized for the GNOME desktop.
+
+<a name="erlang"></a>
+## Using TDLib in Erlang projects
+
+TDLib can be used from the Erlang programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
+
+See [erl-tdlib](https://github.com/lattenwald/erl-tdlib) for an example of TDLib Erlang bindings.
+
+<a name="php"></a>
+## Using TDLib in PHP projects
+
+If you use modern PHP >= 7.4, you can use TDLib via a PHP FFI extension. For example, take a look at [ffi-tdlib](https://github.com/aurimasniekis/php-ffi-tdlib), or [tdlib-php-ffi](https://github.com/thisismzm/tdlib-php-ffi) - FFI-based TDLib wrappers.
+
+See also [tdlib-schema](https://github.com/aurimasniekis/php-tdlib-schema) - a generator for TDLib API classes.
+
+For older PHP versions you can use TDLib by wrapping its functionality in a PHP extension.
+
+See [phptdlib](https://github.com/yaroslavche/phptdlib), [tdlib](https://github.com/aurimasniekis/php-ext-tdlib), or [PIF-TDPony](https://github.com/danog/pif-tdpony)
+for examples of such extensions which provide access to TDLib from PHP.
+
+See [tdlib-bundle](https://github.com/yaroslavche/tdlib-bundle) – a Symfony bundle based on [phptdlib](https://github.com/yaroslavche/phptdlib).
+
+<a name="lua"></a>
+## Using TDLib in Lua projects
+
+TDLib can be used from the Lua programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
+
+See [luajit-tdlib](https://github.com/Rami-Sabbagh/luajit-tdlib), [tdlua](https://github.com/giuseppeM99/tdlua), or
+[luajit-tdlib](https://github.com/Playermet/luajit-tdlib) for examples of TDLib Lua bindings and basic usage examples.
+
+See also [tdbot](https://github.com/vysheng/tdbot), which makes all TDLib features available from Lua scripts.
+
+<a name="d"></a>
+## Using TDLib in D projects
+
+TDLib can be used from the D programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
+
+See [d-tdlib-service](https://github.com/Lord-Evil/d-tdlib-service) for an example of TDLib D bindings.
+
+<a name="ruby"></a>
+## Using TDLib in Ruby projects
+
+TDLib can be used from the Ruby programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
+
+See [tdlib-ruby](https://github.com/southbridgeio/tdlib-ruby) for examples of Ruby bindings and a client for TDLib.
+
+<a name="Crystal"></a>
+## Using TDLib in Crystal projects
+
+TDLib can be used from the Crystal programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
+
+See [Proton](https://github.com/protoncr/proton) for examples of Crystal bindings with automatically generated types for all TDLib API methods and objects.
+
+<a name="haskell"></a>
+## Using TDLib in Haskell projects
+
+TDLib can be used from the Haskell programming language.
+
+See [haskell-tdlib](https://github.com/mejgun/haskell-tdlib) or [tdlib](https://github.com/poscat0x04/tdlib) for examples of such usage and Haskell wrappers for TDLib.
+This library contains automatically generated Haskell types for all TDLib API methods and objects.
+
+<a name="nim"></a>
+## Using TDLib in Nim projects
+
+TDLib can be used from the Nim programming language.
+
+See [telenim](https://github.com/Yardanico/telenim) for example of such usage and a Nim wrapper for TDLib.
+
+<a name="clojure"></a>
+## Using TDLib in Clojure projects
+
+TDLib can be used from the Clojure programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
+
+See [clojure-tdlib-json-wrapper](https://github.com/MityaSaray/clojure-tdlib-json) for an example of TDLib Clojure bindings.
+
+<a name="emacslisp"></a>
+## Using TDLib in Emacs Lisp projects
+
+TDLib can be used from the Emacs Lisp programming language.
+
+See [telega.el](https://github.com/zevlg/telega.el) for an example of a GNU Emacs Telegram client.
+
+<a name="elixir"></a>
+## Using TDLib in Elixir projects
+
+TDLib can be used from the Elixir programming language.
+
+See [Elixir TDLib](https://github.com/QuantLayer/elixir-tdlib) for an example of such usage and an Elixir client for TDLib.
+The library contains automatically generated and fully-documented classes for all TDLib API methods and objects.
+
+<a name="1s"></a>
+## Using TDLib from 1С:Enterprise
+
+TDLib can be used from the 1С programming language.
+
+See [TDLib bindings for 1С:Enterprise](https://github.com/Infactum/telegram-native) and [e1c.tAgents](https://github.com/fedbka/e1c.tAgents) for examples of such usage.
+
+<a name="c"></a>
+## Using TDLib in C projects
+
+TDLib can be used from the C programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
+
+See [easy-tg](https://github.com/Trumeet/easy-tg) for an example of such usage.
+
+You can also try to use our [C](https://github.com/tdlib/td/blob/master/td/telegram/td_c_client.h) client, which was used by the private TDLib-based version of [telegram-cli](https://github.com/vysheng/tg).
+
+<a name="g"></a>
+## Using TDLib from G projects
+
+TDLib can be used from the G graphical programming language in LabVIEW development environment.
+
+See [TDLib bindings for LabVIEW](https://github.com/IvanLisRus/Telegram-Client_TDLib) for examples of such usage.
+
+<a name="other"></a>
+## Using TDLib from other programming languages
+
+You can use TDLib from any other programming language using [tdbot](https://github.com/vysheng/tdbot) or [TDLib JSON CLI](https://github.com/oott123/tdlib-json-cli),
+which provide a command line tool for interaction with TDLIb using the [JSON](https://github.com/tdlib/td#using-json) interface through stdin and stdout.
+You can use this method to use TDLib, for example, from Brainfuck (unfortunately, we haven't seen examples of sending a Telegram message through TDLib on Brainfuck yet).
+
+Alternatively, you can use the TDLib [JSON](https://github.com/tdlib/td#using-json) interface directly from your programming language.
+
+Feel free to create an issue, if you have created a valuable TDLib binding or a TDLib client in some programming language and want it to be added to this list of examples.
diff --git a/protocols/Telegram/tdlib/td/example/android/.gitignore b/protocols/Telegram/tdlib/td/example/android/.gitignore
new file mode 100644
index 0000000000..7547413bf5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/android/.gitignore
@@ -0,0 +1,3 @@
+SDK/
+tdlib/
+third-party/
diff --git a/protocols/Telegram/tdlib/td/example/android/AddIntDef.php b/protocols/Telegram/tdlib/td/example/android/AddIntDef.php
new file mode 100644
index 0000000000..d4ae4a65bd
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/android/AddIntDef.php
@@ -0,0 +1,51 @@
+<?php
+ if ($argc !== 2) {
+ exit();
+ }
+ $file = file_get_contents($argv[1]);
+
+ if (strpos($file, 'androidx.annotation.IntDef') !== false) {
+ exit();
+ }
+
+ $file = str_replace('import androidx.annotation.Nullable;', 'import androidx.annotation.IntDef;'.PHP_EOL.
+ 'import androidx.annotation.Nullable;'.PHP_EOL.
+ PHP_EOL.
+ 'import java.lang.annotation.Retention;'.PHP_EOL.
+ 'import java.lang.annotation.RetentionPolicy;'.PHP_EOL, $file);
+
+ preg_match_all('/public static class ([A-Za-z0-9]+) extends ([A-Za-z0-9]+)/', $file, $matches, PREG_SET_ORDER);
+ $children = [];
+ foreach ($matches as $val) {
+ if ($val[2] === 'Object') {
+ continue;
+ }
+
+ $children[$val[2]][] = ' '.$val[1].'.CONSTRUCTOR';
+ }
+
+ $file = preg_replace_callback('/public abstract static class ([A-Za-z0-9]+)(<R extends Object>)? extends Object [{]/',
+ function ($val) use ($children) {
+ $values = implode(','.PHP_EOL, $children[$val[1]]);
+ return $val[0].<<<EOL
+
+ /**
+ * Describes possible values returned by getConstructor().
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+$values
+ })
+ public @interface Constructors {}
+
+ /**
+ * @return identifier uniquely determining type of the object.
+ */
+ @Constructors
+ @Override
+ public abstract int getConstructor();
+EOL;
+ },
+ $file);
+
+ file_put_contents($argv[1], $file);
diff --git a/protocols/Telegram/tdlib/td/example/android/CMakeLists.txt b/protocols/Telegram/tdlib/td/example/android/CMakeLists.txt
new file mode 100644
index 0000000000..a31b1fc951
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/android/CMakeLists.txt
@@ -0,0 +1,48 @@
+cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
+
+project(TdAndroid VERSION 1.0 LANGUAGES CXX)
+
+option(TD_ENABLE_JNI "Enable JNI-compatible TDLib API" ON)
+
+set(TD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)
+
+if (CMAKE_CROSSCOMPILING)
+ set(CMAKE_MODULE_PATH "${TD_DIR}/CMake")
+
+ include(TdSetUpCompiler)
+ td_set_up_compiler()
+ string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -flto=thin -Oz")
+
+ list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}")
+ add_subdirectory(${TD_DIR} td)
+
+ add_library(tdjni SHARED "${TD_DIR}/example/java/td_jni.cpp")
+
+ target_link_libraries(tdjni PRIVATE Td::TdStatic)
+ target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="org/drinkless/tdlib")
+
+ add_custom_command(TARGET tdjni POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E rename $<TARGET_FILE:tdjni> $<TARGET_FILE:tdjni>.debug
+ COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded $<TARGET_FILE:tdjni>.debug -o $<TARGET_FILE:tdjni>)
+else()
+ add_subdirectory(${TD_DIR} td)
+
+ set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib")
+ set(TD_API_JAVA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${TD_API_JAVA_PACKAGE}/TdApi.java")
+ set(TD_API_TLO_PATH "${TD_DIR}/td/generate/auto/tlo/td_api.tlo")
+ set(TD_API_TL_PATH "${TD_DIR}/td/generate/scheme/td_api.tl")
+ set(JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH "${TD_DIR}/td/generate/JavadocTlDocumentationGenerator.php")
+ set(GENERATE_JAVA_CMD td_generate_java_api TdApi ${TD_API_TLO_PATH} ${CMAKE_CURRENT_SOURCE_DIR} ${TD_API_JAVA_PACKAGE})
+ if (PHP_EXECUTABLE)
+ set(GENERATE_JAVA_CMD ${GENERATE_JAVA_CMD} &&
+ ${PHP_EXECUTABLE} ${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH} ${TD_API_TL_PATH} ${TD_API_JAVA_PATH} androidx.annotation.Nullable @Nullable &&
+ ${PHP_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/AddIntDef.php ${TD_API_JAVA_PATH})
+ endif()
+
+ file(MAKE_DIRECTORY ${TD_API_JAVA_PACKAGE})
+ add_custom_target(tl_generate_java
+ COMMAND ${GENERATE_JAVA_CMD}
+ COMMENT "Generate Java TL source files"
+ DEPENDS td_generate_java_api tl_generate_tlo ${TD_API_TLO_PATH} ${TD_API_TL_PATH}
+ )
+endif()
diff --git a/protocols/Telegram/tdlib/td/example/android/Dockerfile b/protocols/Telegram/tdlib/td/example/android/Dockerfile
new file mode 100644
index 0000000000..7f738fa869
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/android/Dockerfile
@@ -0,0 +1,26 @@
+FROM --platform=linux/amd64 ubuntu:22.04 as build
+
+RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq default-jdk g++ git gperf make perl php-cli unzip wget && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /home
+
+ARG ANDROID_NDK_VERSION=23.2.8568313
+COPY ./check-environment.sh ./fetch-sdk.sh ./
+RUN ./fetch-sdk.sh SDK "$ANDROID_NDK_VERSION"
+
+ARG OPENSSL_VERSION=OpenSSL_1_1_1q
+COPY ./build-openssl.sh ./
+RUN ./build-openssl.sh SDK "$ANDROID_NDK_VERSION" openssl "$OPENSSL_VERSION"
+
+ADD "https://api.github.com/repos/tdlib/td/git/refs/heads/master" version.json
+ARG COMMIT_HASH=master
+RUN git clone https://github.com/tdlib/td.git && cd td && git checkout "$COMMIT_HASH"
+RUN cd td && git merge-base --is-ancestor bcd89728c3b93b67448b93b4863dc5bd4e122a4c "$COMMIT_HASH"
+
+ARG ANDROID_STL=c++_static
+RUN td/example/android/build-tdlib.sh SDK "$ANDROID_NDK_VERSION" openssl "$ANDROID_STL" && rm -rf td/example/android/build-*
+
+
+FROM scratch
+
+COPY --from=build /home/td/example/android/tdlib/tdlib* /
diff --git a/protocols/Telegram/tdlib/td/example/android/README.md b/protocols/Telegram/tdlib/td/example/android/README.md
new file mode 100644
index 0000000000..6fa34fb6d2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/android/README.md
@@ -0,0 +1,24 @@
+# TDLib Android example
+
+This is an example of building `TDLib` for Android.
+You need a Bash shell on Linux, macOS, or Windows with some common tools, a C++ compiler, JDK, PHP, perl, and gperf pre-installed.
+
+## Building TDLib for Android
+
+* Run the script `./check-environment.sh` to check that you have all required Unix tools and Java utilities. If the script exits with an error message, install the missing tool.
+* Run the script `./fetch-sdk.sh` to download Android SDK to a local directory.
+* Run the script `./build-openssl.sh` to download and build OpenSSL for Android.
+* Run the script `./build-tdlib.sh` to build TDLib for Android.
+* The built libraries are now located in the `tdlib/libs` directory, corresponding Java code is located in the `tdlib/java` directory, and standalone Java documentation can be found in the `tdlib/javadoc` directory. You can also use archives `tdlib/tdlib.zip` and `tdlib/tdlib-debug.zip`, which contain all aforementioned data.
+
+If you already have installed Android SDK and NDK, you can skip the second step and specify existing Android SDK root path and Android NDK version as the first and the second parameters to the subsequent scripts. Make sure that the SDK includes android-33 platform and CMake 3.22.1.
+
+If you already have prebuilt OpenSSL, you can skip the third step and specify path to the prebuild OpenSSL as the third parameter to the script `./build-tdlib.sh`.
+
+If you want to update TDLib to a newer version, you need to run only the script `./build-tdlib.sh`.
+
+You can specify different OpenSSL version as the fourth parameter to the script `./build-openssl.sh`. By default OpenSSL 1.1.1 is used because of much smaller binary footprint and better performance than newer OpenSSL versions.
+
+You can build TDLib against shared standard C++ library by specifying "c++_shared" as the fourth parameter to the script `./build-tdlib.sh`. This can reduce total application size if you have a lot of other C++ code and want it to use the same shared library.
+
+Alternatively, you can use Docker to build TDLib for Android. Use `docker build --output tdlib .` to build the latest TDLib commit from Github, or `docker build --build-arg COMMIT_HASH=<commit-hash> --output tdlib .` to build specific commit. The output archives will be placed in the tdlib directory as specified.
diff --git a/protocols/Telegram/tdlib/td/example/android/build-openssl.sh b/protocols/Telegram/tdlib/td/example/android/build-openssl.sh
new file mode 100644
index 0000000000..80f7b0fa5e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/android/build-openssl.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+ANDROID_SDK_ROOT=${1:-SDK}
+ANDROID_NDK_VERSION=${2:-23.2.8568313}
+OPENSSL_INSTALL_DIR=${3:-third-party/openssl}
+OPENSSL_VERSION=${4:-OpenSSL_1_1_1q} # openssl-3.0.5
+
+if [ ! -d "$ANDROID_SDK_ROOT" ] ; then
+ echo "Error: directory \"$ANDROID_SDK_ROOT\" doesn't exist. Run ./fetch-sdk.sh first, or provide a valid path to Android SDK."
+ exit 1
+fi
+
+if [ -e "$OPENSSL_INSTALL_DIR" ] ; then
+ echo "Error: file or directory \"$OPENSSL_INSTALL_DIR\" already exists. Delete it manually to proceed."
+ exit 1
+fi
+
+source ./check-environment.sh || exit 1
+
+mkdir -p $OPENSSL_INSTALL_DIR || exit 1
+
+ANDROID_SDK_ROOT="$(cd "$(dirname -- "$ANDROID_SDK_ROOT")" >/dev/null; pwd -P)/$(basename -- "$ANDROID_SDK_ROOT")"
+OPENSSL_INSTALL_DIR="$(cd "$(dirname -- "$OPENSSL_INSTALL_DIR")" >/dev/null; pwd -P)/$(basename -- "$OPENSSL_INSTALL_DIR")"
+
+cd $(dirname $0)
+
+echo "Downloading OpenSSL sources..."
+rm -f $OPENSSL_VERSION.tar.gz || exit 1
+$WGET https://github.com/openssl/openssl/archive/refs/tags/$OPENSSL_VERSION.tar.gz || exit 1
+rm -rf ./openssl-$OPENSSL_VERSION || exit 1
+tar xzf $OPENSSL_VERSION.tar.gz || exit 1
+rm $OPENSSL_VERSION.tar.gz || exit 1
+cd openssl-$OPENSSL_VERSION
+
+export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION # for OpenSSL 3.0
+export ANDROID_NDK_HOME=$ANDROID_NDK_ROOT # for OpenSSL 1.1.1
+PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/bin:$PATH
+
+if ! clang --help >/dev/null 2>&1 ; then
+ echo "Error: failed to run clang from Android NDK."
+ if [[ "$OS_NAME" == "linux" ]] ; then
+ echo "Prebuilt Android NDK binaries are linked against glibc, so glibc must be installed."
+ fi
+ exit 1
+fi
+
+ANDROID_API32=16
+ANDROID_API64=21
+if [[ ${ANDROID_NDK_VERSION%%.*} -ge 24 ]] ; then
+ ANDROID_API32=19
+fi
+
+for ABI in arm64-v8a armeabi-v7a x86_64 x86 ; do
+ if [[ $ABI == "x86" ]] ; then
+ ./Configure android-x86 no-shared -U__ANDROID_API__ -D__ANDROID_API__=$ANDROID_API32 || exit 1
+ elif [[ $ABI == "x86_64" ]] ; then
+ ./Configure android-x86_64 no-shared -U__ANDROID_API__ -D__ANDROID_API__=$ANDROID_API64 || exit 1
+ elif [[ $ABI == "armeabi-v7a" ]] ; then
+ ./Configure android-arm no-shared -U__ANDROID_API__ -D__ANDROID_API__=$ANDROID_API32 -D__ARM_MAX_ARCH__=8 || exit 1
+ elif [[ $ABI == "arm64-v8a" ]] ; then
+ ./Configure android-arm64 no-shared -U__ANDROID_API__ -D__ANDROID_API__=$ANDROID_API64 || exit 1
+ fi
+
+ sed -i.bak 's/-O3/-O3 -ffunction-sections -fdata-sections/g' Makefile || exit 1
+
+ make depend -s || exit 1
+ make -j4 -s || exit 1
+
+ mkdir -p $OPENSSL_INSTALL_DIR/$ABI/lib/ || exit 1
+ cp libcrypto.a libssl.a $OPENSSL_INSTALL_DIR/$ABI/lib/ || exit 1
+ cp -r include $OPENSSL_INSTALL_DIR/$ABI/ || exit 1
+
+ make distclean || exit 1
+done
+
+cd ..
+
+rm -rf ./openssl-$OPENSSL_VERSION || exit 1
diff --git a/protocols/Telegram/tdlib/td/example/android/build-tdlib.sh b/protocols/Telegram/tdlib/td/example/android/build-tdlib.sh
new file mode 100644
index 0000000000..1ff9ae7af2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/android/build-tdlib.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+
+ANDROID_SDK_ROOT=${1:-SDK}
+ANDROID_NDK_VERSION=${2:-23.2.8568313}
+OPENSSL_INSTALL_DIR=${3:-third-party/openssl}
+ANDROID_STL=${4:-c++_static}
+
+if [ "$ANDROID_STL" != "c++_static" ] && [ "$ANDROID_STL" != "c++_shared" ] ; then
+ echo 'Error: ANDROID_STL must be either "c++_static" or "c++_shared".'
+ exit 1
+fi
+
+source ./check-environment.sh || exit 1
+
+if [ ! -d "$ANDROID_SDK_ROOT" ] ; then
+ echo "Error: directory \"$ANDROID_SDK_ROOT\" doesn't exist. Run ./fetch-sdk.sh first, or provide a valid path to Android SDK."
+ exit 1
+fi
+
+if [ ! -d "$OPENSSL_INSTALL_DIR" ] ; then
+ echo "Error: directory \"$OPENSSL_INSTALL_DIR\" doesn't exists. Run ./build-openssl.sh first."
+ exit 1
+fi
+
+ANDROID_SDK_ROOT="$(cd "$(dirname -- "$ANDROID_SDK_ROOT")" >/dev/null; pwd -P)/$(basename -- "$ANDROID_SDK_ROOT")"
+ANDROID_NDK_ROOT="$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION"
+OPENSSL_INSTALL_DIR="$(cd "$(dirname -- "$OPENSSL_INSTALL_DIR")" >/dev/null; pwd -P)/$(basename -- "$OPENSSL_INSTALL_DIR")"
+PATH=$ANDROID_SDK_ROOT/cmake/3.22.1/bin:$PATH
+
+cd $(dirname $0)
+
+echo "Downloading annotation Java package..."
+rm -f android.jar annotation-1.4.0.jar || exit 1
+$WGET https://maven.google.com/androidx/annotation/annotation/1.4.0/annotation-1.4.0.jar || exit 1
+
+echo "Generating TDLib source files..."
+mkdir -p build-native || exit 1
+cd build-native
+cmake .. || exit 1
+cmake --build . --target prepare_cross_compiling || exit 1
+cmake --build . --target tl_generate_java || exit 1
+cd ..
+php AddIntDef.php org/drinkless/tdlib/TdApi.java || exit 1
+
+echo "Copying Java source files..."
+rm -rf tdlib || exit 1
+mkdir -p tdlib/java/org/drinkless/tdlib || exit 1
+cp -p {../../example,tdlib}/java/org/drinkless/tdlib/Client.java || exit 1
+mv {,tdlib/java/}org/drinkless/tdlib/TdApi.java || exit 1
+rm -rf org || exit 1
+
+echo "Generating Javadoc documentation..."
+cp "$ANDROID_SDK_ROOT/platforms/android-33/android.jar" . || exit 1
+JAVADOC_SEPARATOR=$([ "$OS_NAME" == "win" ] && echo ";" || echo ":")
+javadoc -d tdlib/javadoc -encoding UTF-8 -charset UTF-8 -classpath "android.jar${JAVADOC_SEPARATOR}annotation-1.4.0.jar" -quiet -sourcepath tdlib/java org.drinkless.tdlib || exit 1
+rm android.jar annotation-1.4.0.jar || exit 1
+
+echo "Building TDLib..."
+for ABI in arm64-v8a armeabi-v7a x86_64 x86 ; do
+ mkdir -p build-$ABI || exit 1
+ cd build-$ABI
+ cmake -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake" -DOPENSSL_ROOT_DIR="$OPENSSL_INSTALL_DIR/$ABI" -DCMAKE_BUILD_TYPE=RelWithDebInfo -GNinja -DANDROID_ABI=$ABI -DANDROID_STL=$ANDROID_STL -DANDROID_PLATFORM=android-16 .. || exit 1
+ cmake --build . || exit 1
+ cd ..
+
+ mkdir -p tdlib/libs/$ABI/ || exit 1
+ cp -p build-$ABI/libtd*.so* tdlib/libs/$ABI/ || exit 1
+ if [[ "$ANDROID_STL" == "c++_shared" ]] ; then
+ if [[ "$ABI" == "arm64-v8a" ]] ; then
+ FULL_ABI="aarch64-linux-android"
+ elif [[ "$ABI" == "armeabi-v7a" ]] ; then
+ FULL_ABI="arm-linux-androideabi"
+ elif [[ "$ABI" == "x86_64" ]] ; then
+ FULL_ABI="x86_64-linux-android"
+ elif [[ "$ABI" == "x86" ]] ; then
+ FULL_ABI="i686-linux-android"
+ fi
+ cp "$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/sysroot/usr/lib/$FULL_ABI/libc++_shared.so" tdlib/libs/$ABI/ || exit 1
+ "$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/bin/llvm-strip" tdlib/libs/$ABI/libc++_shared.so || exit 1
+ fi
+done
+
+echo "Compressing..."
+rm -f tdlib.zip tdlib-debug.zip || exit 1
+jar -cMf tdlib-debug.zip tdlib || exit 1
+rm tdlib/libs/*/*.debug || exit 1
+jar -cMf tdlib.zip tdlib || exit 1
+mv tdlib.zip tdlib-debug.zip tdlib || exit 1
+
+echo "Done."
diff --git a/protocols/Telegram/tdlib/td/example/android/check-environment.sh b/protocols/Telegram/tdlib/td/example/android/check-environment.sh
new file mode 100644
index 0000000000..1308647b60
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/android/check-environment.sh
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+
+# The script checks that all needed tools are installed and sets OS_NAME, HOST_ARCH, and WGET variables
+
+if [[ "$OSTYPE" == "linux"* ]] ; then
+ OS_NAME="linux"
+ HOST_ARCH="linux-x86_64"
+elif [[ "$OSTYPE" == "darwin"* ]] ; then
+ OS_NAME="mac"
+ HOST_ARCH="darwin-x86_64"
+elif [[ "$OSTYPE" == "msys" ]] ; then
+ OS_NAME="win"
+ HOST_ARCH="windows-x86_64"
+else
+ echo "Error: this script supports only Bash shell on Linux, macOS, or Windows."
+ exit 1
+fi
+
+if which wget >/dev/null 2>&1 ; then
+ WGET="wget -q"
+elif which curl >/dev/null 2>&1 ; then
+ WGET="curl -sfLO"
+else
+ echo "Error: this script requires either curl or wget tool installed."
+ exit 1
+fi
+
+for TOOL_NAME in gperf jar java javadoc make perl php sed tar yes unzip ; do
+ if ! which "$TOOL_NAME" >/dev/null 2>&1 ; then
+ echo "Error: this script requires $TOOL_NAME tool installed."
+ exit 1
+ fi
+done
+
+if [[ $(which make) = *" "* ]] ; then
+ echo "Error: OpenSSL expects that full path to make tool doesn't contain spaces. Move it to some other place."
+fi
+
+if ! perl -MExtUtils::MakeMaker -MLocale::Maketext::Simple -MPod::Usage -e '' >/dev/null 2>&1 ; then
+ echo "Error: Perl installation is broken."
+ if [[ "$OSTYPE" == "msys" ]] ; then
+ echo "For Git Bash you need to manually copy ExtUtils, Locale and Pod modules to /usr/share/perl5/core_perl from any compatible Perl installation."
+ fi
+ exit 1
+fi
+
+if ! java --help >/dev/null 2>&1 ; then
+ echo "Error: Java installation is broken. Install JDK from https://www.oracle.com/java/technologies/downloads/."
+ exit 1
+fi
diff --git a/protocols/Telegram/tdlib/td/example/android/fetch-sdk.sh b/protocols/Telegram/tdlib/td/example/android/fetch-sdk.sh
new file mode 100644
index 0000000000..61d93170a5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/android/fetch-sdk.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+ANDROID_SDK_ROOT=${1:-SDK}
+ANDROID_NDK_VERSION=${2:-23.2.8568313}
+
+if [ -e "$ANDROID_SDK_ROOT" ] ; then
+ echo "Error: file or directory \"$ANDROID_SDK_ROOT\" already exists. Delete it manually to proceed."
+ exit 1
+fi
+
+source ./check-environment.sh || exit 1
+
+SDKMANAGER="./sdkmanager"
+if [[ "$OS_NAME" == "win" ]] ; then
+ SDKMANAGER="./sdkmanager.bat"
+fi
+
+echo "Downloading SDK Manager..."
+mkdir -p "$ANDROID_SDK_ROOT" || exit 1
+cd "$ANDROID_SDK_ROOT" || exit 1
+$WGET "https://dl.google.com/android/repository/commandlinetools-$OS_NAME-8512546_latest.zip" || exit 1
+mkdir -p cmdline-tools || exit 1
+unzip -qq "commandlinetools-$OS_NAME-8512546_latest.zip" -d cmdline-tools || exit 1
+rm "commandlinetools-$OS_NAME-8512546_latest.zip" || exit 1
+mv cmdline-tools/* cmdline-tools/latest/ || exit 1
+
+echo "Installing required SDK tools..."
+cd cmdline-tools/latest/bin/ || exit 1
+yes | $SDKMANAGER --licenses >/dev/null || exit 1
+$SDKMANAGER --install "ndk;$ANDROID_NDK_VERSION" "cmake;3.22.1" "build-tools;33.0.0" "platforms;android-33" > /dev/null || exit 1
diff --git a/protocols/Telegram/tdlib/td/example/cpp/CMakeLists.txt b/protocols/Telegram/tdlib/td/example/cpp/CMakeLists.txt
index 3e7794d4fa..6ad8be44b7 100644
--- a/protocols/Telegram/tdlib/td/example/cpp/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/example/cpp/CMakeLists.txt
@@ -1,8 +1,8 @@
-cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.4 FATAL_ERROR)
project(TdExample VERSION 1.0 LANGUAGES CXX)
-find_package(Td 1.2.0 REQUIRED)
+find_package(Td 1.8.8 REQUIRED)
add_executable(tdjson_example tdjson_example.cpp)
target_link_libraries(tdjson_example PRIVATE Td::TdJson)
diff --git a/protocols/Telegram/tdlib/td/example/cpp/README.md b/protocols/Telegram/tdlib/td/example/cpp/README.md
index 9e5de531ba..1658ade6fc 100644
--- a/protocols/Telegram/tdlib/td/example/cpp/README.md
+++ b/protocols/Telegram/tdlib/td/example/cpp/README.md
@@ -1,4 +1,4 @@
-# TDLib cpp basic usage examples
+# TDLib C++ basic usage examples
TDLib should be prebuilt and installed to local subdirectory `td/`:
```
@@ -21,4 +21,4 @@ cmake --build .
Documentation for all available classes and methods can be found at https://core.telegram.org/tdlib/docs.
-To run `tdjson_example` you may need to manually copy a `tdjson` shared library from `td/bin` to a directory containing built binaries.
+To run the examples you may need to manually copy needed shared libraries from `td/bin` to a directory containing built binaries.
diff --git a/protocols/Telegram/tdlib/td/example/cpp/td_example.cpp b/protocols/Telegram/tdlib/td/example/cpp/td_example.cpp
index 1efbf465b9..3b6cf42edc 100644
--- a/protocols/Telegram/tdlib/td/example/cpp/td_example.cpp
+++ b/protocols/Telegram/tdlib/td/example/cpp/td_example.cpp
@@ -1,19 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <td/telegram/Client.h>
-#include <td/telegram/Log.h>
#include <td/telegram/td_api.h>
#include <td/telegram/td_api.hpp>
#include <cstdint>
#include <functional>
#include <iostream>
-#include <limits>
#include <map>
+#include <memory>
#include <sstream>
#include <string>
#include <vector>
@@ -35,7 +34,7 @@ struct overload<F> : public F {
template <class F, class... Fs>
struct overload<F, Fs...>
: public overload<F>
- , overload<Fs...> {
+ , public overload<Fs...> {
overload(F f, Fs... fs) : overload<F>(f), overload<Fs...>(fs...) {
}
using overload<F>::operator();
@@ -53,8 +52,10 @@ namespace td_api = td::td_api;
class TdExample {
public:
TdExample() {
- td::Log::set_verbosity_level(1);
- client_ = std::make_unique<td::Client>();
+ td::ClientManager::execute(td_api::make_object<td_api::setLogVerbosityLevel>(1));
+ client_manager_ = std::make_unique<td::ClientManager>();
+ client_id_ = client_manager_->create_client_id();
+ send_query(td_api::make_object<td_api::getOption>("version"), {});
}
void loop() {
@@ -62,10 +63,10 @@ class TdExample {
if (need_restart_) {
restart();
} else if (!are_authorized_) {
- process_response(client_->receive(10));
+ process_response(client_manager_->receive(10));
} else {
- std::cerr << "Enter action [q] quit [u] check for updates and request results [c] show chats [m <id> <text>] "
- "send message [l] logout: "
+ std::cout << "Enter action [q] quit [u] check for updates and request results [c] show chats [m <chat_id> "
+ "<text>] send message [me] show self [l] logout: "
<< std::endl;
std::string line;
std::getline(std::cin, line);
@@ -78,17 +79,23 @@ class TdExample {
return;
}
if (action == "u") {
- std::cerr << "Checking for updates..." << std::endl;
+ std::cout << "Checking for updates..." << std::endl;
while (true) {
- auto response = client_->receive(0);
+ auto response = client_manager_->receive(0);
if (response.object) {
process_response(std::move(response));
} else {
break;
}
}
+ } else if (action == "close") {
+ std::cout << "Closing..." << std::endl;
+ send_query(td_api::make_object<td_api::close>(), {});
+ } else if (action == "me") {
+ send_query(td_api::make_object<td_api::getMe>(),
+ [this](Object object) { std::cout << to_string(object) << std::endl; });
} else if (action == "l") {
- std::cerr << "Logging out..." << std::endl;
+ std::cout << "Logging out..." << std::endl;
send_query(td_api::make_object<td_api::logOut>(), {});
} else if (action == "m") {
std::int64_t chat_id;
@@ -97,7 +104,7 @@ class TdExample {
std::string text;
std::getline(ss, text);
- std::cerr << "Sending message to chat " << chat_id << "..." << std::endl;
+ std::cout << "Sending message to chat " << chat_id << "..." << std::endl;
auto send_message = td_api::make_object<td_api::sendMessage>();
send_message->chat_id_ = chat_id;
auto message_content = td_api::make_object<td_api::inputMessageText>();
@@ -107,17 +114,16 @@ class TdExample {
send_query(std::move(send_message), {});
} else if (action == "c") {
- std::cerr << "Loading chat list..." << std::endl;
- send_query(td_api::make_object<td_api::getChats>(std::numeric_limits<std::int64_t>::max(), 0, 20),
- [this](Object object) {
- if (object->get_id() == td_api::error::ID) {
- return;
- }
- auto chats = td::move_tl_object_as<td_api::chats>(object);
- for (auto chat_id : chats->chat_ids_) {
- std::cerr << "[id:" << chat_id << "] [title:" << chat_title_[chat_id] << "]" << std::endl;
- }
- });
+ std::cout << "Loading chat list..." << std::endl;
+ send_query(td_api::make_object<td_api::getChats>(nullptr, 20), [this](Object object) {
+ if (object->get_id() == td_api::error::ID) {
+ return;
+ }
+ auto chats = td::move_tl_object_as<td_api::chats>(object);
+ for (auto chat_id : chats->chat_ids_) {
+ std::cout << "[chat_id:" << chat_id << "] [title:" << chat_title_[chat_id] << "]" << std::endl;
+ }
+ });
}
}
}
@@ -125,7 +131,8 @@ class TdExample {
private:
using Object = td_api::object_ptr<td_api::Object>;
- std::unique_ptr<td::Client> client_;
+ std::unique_ptr<td::ClientManager> client_manager_;
+ std::int32_t client_id_{0};
td_api::object_ptr<td_api::AuthorizationState> authorization_state_;
bool are_authorized_{false};
@@ -135,12 +142,12 @@ class TdExample {
std::map<std::uint64_t, std::function<void(Object)>> handlers_;
- std::map<std::int32_t, td_api::object_ptr<td_api::user>> users_;
+ std::map<std::int64_t, td_api::object_ptr<td_api::user>> users_;
std::map<std::int64_t, std::string> chat_title_;
void restart() {
- client_.reset();
+ client_manager_.reset();
*this = TdExample();
}
@@ -149,24 +156,25 @@ class TdExample {
if (handler) {
handlers_.emplace(query_id, std::move(handler));
}
- client_->send({query_id, std::move(f)});
+ client_manager_->send(client_id_, query_id, std::move(f));
}
- void process_response(td::Client::Response response) {
+ void process_response(td::ClientManager::Response response) {
if (!response.object) {
return;
}
- //std::cerr << response.id << " " << to_string(response.object) << std::endl;
- if (response.id == 0) {
+ //std::cout << response.request_id << " " << to_string(response.object) << std::endl;
+ if (response.request_id == 0) {
return process_update(std::move(response.object));
}
- auto it = handlers_.find(response.id);
+ auto it = handlers_.find(response.request_id);
if (it != handlers_.end()) {
it->second(std::move(response.object));
+ handlers_.erase(it);
}
}
- std::string get_user_name(std::int32_t user_id) {
+ std::string get_user_name(std::int64_t user_id) const {
auto it = users_.find(user_id);
if (it == users_.end()) {
return "unknown user";
@@ -174,6 +182,14 @@ class TdExample {
return it->second->first_name_ + " " + it->second->last_name_;
}
+ std::string get_chat_title(std::int64_t chat_id) const {
+ auto it = chat_title_.find(chat_id);
+ if (it == chat_title_.end()) {
+ return "unknown chat";
+ }
+ return it->second;
+ }
+
void process_update(td_api::object_ptr<td_api::Object> update) {
td_api::downcast_call(
*update, overloaded(
@@ -193,13 +209,21 @@ class TdExample {
},
[this](td_api::updateNewMessage &update_new_message) {
auto chat_id = update_new_message.message_->chat_id_;
- auto sender_user_name = get_user_name(update_new_message.message_->sender_user_id_);
+ std::string sender_name;
+ td_api::downcast_call(*update_new_message.message_->sender_id_,
+ overloaded(
+ [this, &sender_name](td_api::messageSenderUser &user) {
+ sender_name = get_user_name(user.user_id_);
+ },
+ [this, &sender_name](td_api::messageSenderChat &chat) {
+ sender_name = get_chat_title(chat.chat_id_);
+ }));
std::string text;
if (update_new_message.message_->content_->get_id() == td_api::messageText::ID) {
text = static_cast<td_api::messageText &>(*update_new_message.message_->content_).text_->text_;
}
- std::cerr << "Got message: [chat_id:" << chat_id << "] [from:" << sender_user_name << "] ["
- << text << "]" << std::endl;
+ std::cout << "Got message: [chat_id:" << chat_id << "] [from:" << sender_name << "] [" << text
+ << "]" << std::endl;
},
[](auto &update) {}));
}
@@ -214,85 +238,91 @@ class TdExample {
void on_authorization_state_update() {
authentication_query_id_++;
- td_api::downcast_call(
- *authorization_state_,
- overloaded(
- [this](td_api::authorizationStateReady &) {
- are_authorized_ = true;
- std::cerr << "Got authorization" << std::endl;
- },
- [this](td_api::authorizationStateLoggingOut &) {
- are_authorized_ = false;
- std::cerr << "Logging out" << std::endl;
- },
- [this](td_api::authorizationStateClosing &) { std::cerr << "Closing" << std::endl; },
- [this](td_api::authorizationStateClosed &) {
- are_authorized_ = false;
- need_restart_ = true;
- std::cerr << "Terminated" << std::endl;
- },
- [this](td_api::authorizationStateWaitCode &wait_code) {
- std::string first_name;
- std::string last_name;
- if (!wait_code.is_registered_) {
- std::cerr << "Enter your first name: ";
- std::cin >> first_name;
- std::cerr << "Enter your last name: ";
- std::cin >> last_name;
- }
- std::cerr << "Enter authentication code: ";
- std::string code;
- std::cin >> code;
- send_query(td_api::make_object<td_api::checkAuthenticationCode>(code, first_name, last_name),
- create_authentication_query_handler());
- },
- [this](td_api::authorizationStateWaitPassword &) {
- std::cerr << "Enter authentication password: ";
- std::string password;
- std::cin >> password;
- send_query(td_api::make_object<td_api::checkAuthenticationPassword>(password),
- create_authentication_query_handler());
- },
- [this](td_api::authorizationStateWaitPhoneNumber &) {
- std::cerr << "Enter phone number: ";
- std::string phone_number;
- std::cin >> phone_number;
- send_query(td_api::make_object<td_api::setAuthenticationPhoneNumber>(
- phone_number, false /*allow_flash_calls*/, false /*is_current_phone_number*/),
- create_authentication_query_handler());
- },
- [this](td_api::authorizationStateWaitEncryptionKey &) {
- std::cerr << "Enter encryption key or DESTROY: ";
- std::string key;
- std::getline(std::cin, key);
- if (key == "DESTROY") {
- send_query(td_api::make_object<td_api::destroy>(), create_authentication_query_handler());
- } else {
- send_query(td_api::make_object<td_api::checkDatabaseEncryptionKey>(std::move(key)),
- create_authentication_query_handler());
- }
- },
- [this](td_api::authorizationStateWaitTdlibParameters &) {
- auto parameters = td_api::make_object<td_api::tdlibParameters>();
- parameters->database_directory_ = "tdlib";
- parameters->use_message_database_ = true;
- parameters->use_secret_chats_ = true;
- parameters->api_id_ = 94575;
- parameters->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2";
- parameters->system_language_code_ = "en";
- parameters->device_model_ = "Desktop";
- parameters->system_version_ = "Unknown";
- parameters->application_version_ = "1.0";
- parameters->enable_storage_optimizer_ = true;
- send_query(td_api::make_object<td_api::setTdlibParameters>(std::move(parameters)),
- create_authentication_query_handler());
- }));
+ td_api::downcast_call(*authorization_state_,
+ overloaded(
+ [this](td_api::authorizationStateReady &) {
+ are_authorized_ = true;
+ std::cout << "Got authorization" << std::endl;
+ },
+ [this](td_api::authorizationStateLoggingOut &) {
+ are_authorized_ = false;
+ std::cout << "Logging out" << std::endl;
+ },
+ [this](td_api::authorizationStateClosing &) { std::cout << "Closing" << std::endl; },
+ [this](td_api::authorizationStateClosed &) {
+ are_authorized_ = false;
+ need_restart_ = true;
+ std::cout << "Terminated" << std::endl;
+ },
+ [this](td_api::authorizationStateWaitPhoneNumber &) {
+ std::cout << "Enter phone number: " << std::flush;
+ std::string phone_number;
+ std::cin >> phone_number;
+ send_query(
+ td_api::make_object<td_api::setAuthenticationPhoneNumber>(phone_number, nullptr),
+ create_authentication_query_handler());
+ },
+ [this](td_api::authorizationStateWaitEmailAddress &) {
+ std::cout << "Enter email address: " << std::flush;
+ std::string email_address;
+ std::cin >> email_address;
+ send_query(td_api::make_object<td_api::setAuthenticationEmailAddress>(email_address),
+ create_authentication_query_handler());
+ },
+ [this](td_api::authorizationStateWaitEmailCode &) {
+ std::cout << "Enter email authentication code: " << std::flush;
+ std::string code;
+ std::cin >> code;
+ send_query(td_api::make_object<td_api::checkAuthenticationEmailCode>(
+ td_api::make_object<td_api::emailAddressAuthenticationCode>(code)),
+ create_authentication_query_handler());
+ },
+ [this](td_api::authorizationStateWaitCode &) {
+ std::cout << "Enter authentication code: " << std::flush;
+ std::string code;
+ std::cin >> code;
+ send_query(td_api::make_object<td_api::checkAuthenticationCode>(code),
+ create_authentication_query_handler());
+ },
+ [this](td_api::authorizationStateWaitRegistration &) {
+ std::string first_name;
+ std::string last_name;
+ std::cout << "Enter your first name: " << std::flush;
+ std::cin >> first_name;
+ std::cout << "Enter your last name: " << std::flush;
+ std::cin >> last_name;
+ send_query(td_api::make_object<td_api::registerUser>(first_name, last_name),
+ create_authentication_query_handler());
+ },
+ [this](td_api::authorizationStateWaitPassword &) {
+ std::cout << "Enter authentication password: " << std::flush;
+ std::string password;
+ std::getline(std::cin, password);
+ send_query(td_api::make_object<td_api::checkAuthenticationPassword>(password),
+ create_authentication_query_handler());
+ },
+ [this](td_api::authorizationStateWaitOtherDeviceConfirmation &state) {
+ std::cout << "Confirm this login link on another device: " << state.link_ << std::endl;
+ },
+ [this](td_api::authorizationStateWaitTdlibParameters &) {
+ auto request = td_api::make_object<td_api::setTdlibParameters>();
+ request->database_directory_ = "tdlib";
+ request->use_message_database_ = true;
+ request->use_secret_chats_ = true;
+ request->api_id_ = 94575;
+ request->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2";
+ request->system_language_code_ = "en";
+ request->device_model_ = "Desktop";
+ request->application_version_ = "1.0";
+ request->enable_storage_optimizer_ = true;
+ send_query(std::move(request), create_authentication_query_handler());
+ }));
}
void check_authentication_error(Object object) {
if (object->get_id() == td_api::error::ID) {
auto error = td::move_tl_object_as<td_api::error>(object);
- std::cerr << "Error: " << to_string(error);
+ std::cout << "Error: " << to_string(error) << std::flush;
on_authorization_state_update();
}
}
diff --git a/protocols/Telegram/tdlib/td/example/cpp/tdjson_example.cpp b/protocols/Telegram/tdlib/td/example/cpp/tdjson_example.cpp
index 6787b37f86..dcf203f2e9 100644
--- a/protocols/Telegram/tdlib/td/example/cpp/tdjson_example.cpp
+++ b/protocols/Telegram/tdlib/td/example/cpp/tdjson_example.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -12,14 +12,38 @@
// the main event cycle, which should be essentially the same for all languages.
int main() {
- void *client = td_json_client_create();
- // somehow share the client with other threads, which will be able to send requests via td_json_client_send
+ // disable TDLib logging
+ td_execute("{\"@type\":\"setLogVerbosityLevel\", \"new_verbosity_level\":0}");
+
+ int client_id = td_create_client_id();
+ // somehow share the client_id with other threads, which will be able to send requests via td_send
+
+ // start the client by sending request to it
+ td_send(client_id, "{\"@type\":\"getOption\", \"name\":\"version\"}");
+
+ const bool test_incorrect_queries = false;
+ if (test_incorrect_queries) {
+ td_execute("{\"@type\":\"setLogVerbosityLevel\", \"new_verbosity_level\":1}");
+ td_execute("");
+ td_execute("test");
+ td_execute("\"test\"");
+ td_execute("{\"@type\":\"test\", \"@extra\":1}");
+
+ td_send(client_id, "{\"@type\":\"getFileMimeType\"}");
+ td_send(client_id, "{\"@type\":\"getFileMimeType\", \"@extra\":1}");
+ td_send(client_id, "{\"@type\":\"getFileMimeType\", \"@extra\":null}");
+ td_send(client_id, "{\"@type\":\"test\"}");
+ td_send(client_id, "[]");
+ td_send(client_id, "{\"@type\":\"test\", \"@extra\":1}");
+ td_send(client_id, "{\"@type\":\"sendMessage\", \"chat_id\":true, \"@extra\":1}");
+ td_send(client_id, "test");
+ }
const double WAIT_TIMEOUT = 10.0; // seconds
while (true) {
- const char *result = td_json_client_receive(client, WAIT_TIMEOUT);
+ const char *result = td_receive(WAIT_TIMEOUT);
if (result != nullptr) {
- // parse the result as JSON object and process it as an incoming update or an answer to a previously sent request
+ // parse the result as a JSON object and process it as an incoming update or an answer to a previously sent request
// if (result is UpdateAuthorizationState with authorizationStateClosed) {
// break;
@@ -28,6 +52,4 @@ int main() {
std::cout << result << std::endl;
}
}
-
- td_json_client_destroy(client);
}
diff --git a/protocols/Telegram/tdlib/td/example/csharp/README.md b/protocols/Telegram/tdlib/td/example/csharp/README.md
index 2faa15e12e..b20d65947e 100644
--- a/protocols/Telegram/tdlib/td/example/csharp/README.md
+++ b/protocols/Telegram/tdlib/td/example/csharp/README.md
@@ -5,20 +5,26 @@ This is an example of building TDLib with `C++/CLI` support and an example of TD
## Building TDLib
* Download and install Microsoft Visual Studio 2015 or later.
-* Download and install [CMake](https://cmake.org/download/).
+* Download and install [CMake](https://cmake.org/download/); choose "Add CMake to the system PATH" option while installing.
* Install [vcpkg](https://github.com/Microsoft/vcpkg#quick-start) or update it to the latest version using `vcpkg update` and following received instructions.
-* Install `zlib` and `openssl` for using `vcpkg`:
+* Install `gperf`, `zlib`, and `openssl` using `vcpkg`:
```
-C:\src\vcpkg> .\vcpkg.exe install openssl:x64-windows openssl:x86-windows zlib:x64-windows zlib:x86-windows
+cd <path to vcpkg>
+.\vcpkg.exe install gperf:x64-windows gperf:x86-windows openssl:x64-windows openssl:x86-windows zlib:x64-windows zlib:x86-windows
```
* (Optional. For XML documentation generation.) Download [PHP](https://windows.php.net/download#php-7.2). Add the path to php.exe to the PATH environment variable.
-* Download and install [gperf](https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/). Add the path to gperf.exe to the PATH environment variable.
* Build `TDLib` with CMake enabling `.NET` support and specifying correct path to `vcpkg` toolchain file:
```
cd <path to TDLib sources>/example/csharp
mkdir build
cd build
-cmake -DTD_ENABLE_DOTNET=ON -DCMAKE_TOOLCHAIN_FILE=C:\src\vcpkg\scripts\buildsystems\vcpkg.cmake ../../..
+cmake -A Win32 -DTD_ENABLE_DOTNET=ON -DCMAKE_TOOLCHAIN_FILE=<path to vcpkg>/scripts/buildsystems/vcpkg.cmake ../../..
+cmake --build . --config Release
+cmake --build . --config Debug
+cd ..
+mkdir build64
+cd build64
+cmake -A x64 -DTD_ENABLE_DOTNET=ON -DCMAKE_TOOLCHAIN_FILE=<path to vcpkg>/scripts/buildsystems/vcpkg.cmake ../../..
cmake --build . --config Release
cmake --build . --config Debug
```
@@ -27,6 +33,6 @@ cmake --build . --config Debug
After `TDLib` is built you can open and run TdExample project.
It contains a simple console C# application with implementation of authorization and message sending.
-Just open it with Visual Studio 2015 or 2017 and run.
+Just open it with Visual Studio 2015 or later and run.
Also see TdExample.csproj for example of including TDLib in C# project with all native shared library dependencies.
diff --git a/protocols/Telegram/tdlib/td/example/csharp/TdExample.cs b/protocols/Telegram/tdlib/td/example/csharp/TdExample.cs
index be98e1a758..0c07ee7c7b 100644
--- a/protocols/Telegram/tdlib/td/example/csharp/TdExample.cs
+++ b/protocols/Telegram/tdlib/td/example/csharp/TdExample.cs
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -23,23 +23,18 @@ namespace TdExample
private static TdApi.AuthorizationState _authorizationState = null;
private static volatile bool _haveAuthorization = false;
- private static volatile bool _quiting = false;
+ private static volatile bool _needQuit = false;
+ private static volatile bool _canQuit = false;
private static volatile AutoResetEvent _gotAuthorization = new AutoResetEvent(false);
private static readonly string _newLine = Environment.NewLine;
- private static readonly string _commandsLine = "Enter command (gc <chatId> - GetChat, me - GetMe, sm <chatId> <message> - SendMessage, lo - LogOut, q - Quit): ";
+ private static readonly string _commandsLine = "Enter command (gc <chatId> - GetChat, me - GetMe, sm <chatId> <message> - SendMessage, lo - LogOut, r - Restart, q - Quit): ";
private static volatile string _currentPrompt = null;
private static Td.Client CreateTdClient()
{
- Td.Client result = Td.Client.Create(new UpdatesHandler());
- new Thread(() =>
- {
- Thread.CurrentThread.IsBackground = true;
- result.Run();
- }).Start();
- return result;
+ return Td.Client.Create(new UpdateHandler());
}
private static void Print(string str)
@@ -72,33 +67,48 @@ namespace TdExample
}
if (_authorizationState is TdApi.AuthorizationStateWaitTdlibParameters)
{
- TdApi.TdlibParameters parameters = new TdApi.TdlibParameters();
- parameters.DatabaseDirectory = "tdlib";
- parameters.UseMessageDatabase = true;
- parameters.UseSecretChats = true;
- parameters.ApiId = 94575;
- parameters.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
- parameters.SystemLanguageCode = "en";
- parameters.DeviceModel = "Desktop";
- parameters.SystemVersion = "Unknown";
- parameters.ApplicationVersion = "1.0";
- parameters.EnableStorageOptimizer = true;
+ TdApi.SetTdlibParameters request = new TdApi.SetTdlibParameters();
+ request.DatabaseDirectory = "tdlib";
+ request.UseMessageDatabase = true;
+ request.UseSecretChats = true;
+ request.ApiId = 94575;
+ request.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
+ request.SystemLanguageCode = "en";
+ request.DeviceModel = "Desktop";
+ request.ApplicationVersion = "1.0";
+ request.EnableStorageOptimizer = true;
- _client.Send(new TdApi.SetTdlibParameters(parameters), new AuthorizationRequestHandler());
- }
- else if (_authorizationState is TdApi.AuthorizationStateWaitEncryptionKey)
- {
- _client.Send(new TdApi.CheckDatabaseEncryptionKey(), new AuthorizationRequestHandler());
+ _client.Send(request, new AuthorizationRequestHandler());
}
else if (_authorizationState is TdApi.AuthorizationStateWaitPhoneNumber)
{
string phoneNumber = ReadLine("Please enter phone number: ");
- _client.Send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, false, false), new AuthorizationRequestHandler());
+ _client.Send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, null), new AuthorizationRequestHandler());
+ }
+ else if (_authorizationState is TdApi.AuthorizationStateWaitEmailAddress)
+ {
+ string emailAddress = ReadLine("Please enter email address: ");
+ _client.Send(new TdApi.SetAuthenticationEmailAddress(emailAddress), new AuthorizationRequestHandler());
+ }
+ else if (_authorizationState is TdApi.AuthorizationStateWaitEmailCode)
+ {
+ string code = ReadLine("Please enter email authentication code: ");
+ _client.Send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(code)), new AuthorizationRequestHandler());
+ }
+ else if (_authorizationState is TdApi.AuthorizationStateWaitOtherDeviceConfirmation state)
+ {
+ Console.WriteLine("Please confirm this login link on another device: " + state.Link);
}
else if (_authorizationState is TdApi.AuthorizationStateWaitCode)
{
string code = ReadLine("Please enter authentication code: ");
- _client.Send(new TdApi.CheckAuthenticationCode(code, "", ""), new AuthorizationRequestHandler());
+ _client.Send(new TdApi.CheckAuthenticationCode(code), new AuthorizationRequestHandler());
+ }
+ else if (_authorizationState is TdApi.AuthorizationStateWaitRegistration)
+ {
+ string firstName = ReadLine("Please enter your first name: ");
+ string lastName = ReadLine("Please enter your last name: ");
+ _client.Send(new TdApi.RegisterUser(firstName, lastName), new AuthorizationRequestHandler());
}
else if (_authorizationState is TdApi.AuthorizationStateWaitPassword)
{
@@ -123,9 +133,11 @@ namespace TdExample
else if (_authorizationState is TdApi.AuthorizationStateClosed)
{
Print("Closed");
- if (!_quiting)
+ if (!_needQuit)
{
_client = CreateTdClient(); // recreate _client after previous has closed
+ } else {
+ _canQuit = true;
}
}
else
@@ -172,8 +184,12 @@ namespace TdExample
_haveAuthorization = false;
_client.Send(new TdApi.LogOut(), _defaultHandler);
break;
+ case "r":
+ _haveAuthorization = false;
+ _client.Send(new TdApi.Close(), _defaultHandler);
+ break;
case "q":
- _quiting = true;
+ _needQuit = true;
_haveAuthorization = false;
_client.Send(new TdApi.Close(), _defaultHandler);
break;
@@ -195,37 +211,45 @@ namespace TdExample
TdApi.ReplyMarkup replyMarkup = new TdApi.ReplyMarkupInlineKeyboard(new TdApi.InlineKeyboardButton[][] { row, row, row });
TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), false, true);
- _client.Send(new TdApi.SendMessage(chatId, 0, false, false, replyMarkup, content), _defaultHandler);
+ _client.Send(new TdApi.SendMessage(chatId, 0, 0, null, replyMarkup, content), _defaultHandler);
}
static void Main()
{
// disable TDLib log
- Td.Log.SetVerbosityLevel(0);
- if (!Td.Log.SetFilePath("tdlib.log"))
+ Td.Client.Execute(new TdApi.SetLogVerbosityLevel(0));
+ if (Td.Client.Execute(new TdApi.SetLogStream(new TdApi.LogStreamFile("tdlib.log", 1 << 27, false))) is TdApi.Error)
{
throw new System.IO.IOException("Write access to the current directory is required");
}
+ new Thread(() =>
+ {
+ Thread.CurrentThread.IsBackground = true;
+ Td.Client.Run();
+ }).Start();
// create Td.Client
_client = CreateTdClient();
// test Client.Execute
- _defaultHandler.OnResult(_client.Execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")));
+ _defaultHandler.OnResult(Td.Client.Execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")));
// main loop
- while (!_quiting)
+ while (!_needQuit)
{
// await authorization
_gotAuthorization.Reset();
_gotAuthorization.WaitOne();
- _client.Send(new TdApi.GetChats(Int64.MaxValue, 0, 100), _defaultHandler); // preload chat list
+ _client.Send(new TdApi.LoadChats(null, 100), _defaultHandler); // preload main chat list
while (_haveAuthorization)
{
GetCommand();
}
}
+ while (!_canQuit) {
+ Thread.Sleep(1);
+ }
}
private class DefaultHandler : Td.ClientResultHandler
@@ -236,7 +260,7 @@ namespace TdExample
}
}
- private class UpdatesHandler : Td.ClientResultHandler
+ private class UpdateHandler : Td.ClientResultHandler
{
void Td.ClientResultHandler.OnResult(TdApi.BaseObject @object)
{
diff --git a/protocols/Telegram/tdlib/td/example/csharp/TdExample.csproj b/protocols/Telegram/tdlib/td/example/csharp/TdExample.csproj
index ea2ad56532..6a511d8f74 100644
--- a/protocols/Telegram/tdlib/td/example/csharp/TdExample.csproj
+++ b/protocols/Telegram/tdlib/td/example/csharp/TdExample.csproj
@@ -33,6 +33,24 @@
<PropertyGroup>
<RootNamespace>TdExample</RootNamespace>
</PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\x64\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <DebugType>full</DebugType>
+ <PlatformTarget>x64</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
+ <OutputPath>bin\x64\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <Optimize>true</Optimize>
+ <DebugType>pdbonly</DebugType>
+ <PlatformTarget>x64</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
@@ -43,38 +61,54 @@
<Reference Include="System.Xml.Linq" />
<Reference Include="Telegram.Td, Version=0.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
- <HintPath Condition=" '$(Configuration)' == 'Debug' ">build\Debug\Telegram.Td.dll</HintPath>
- <HintPath Condition=" '$(Configuration)' == 'Release' ">build\Release\Telegram.Td.dll</HintPath>
+ <HintPath Condition=" '$(Platform)' == 'x86' ">build\$(Configuration)\Telegram.Td.dll</HintPath>
+ <HintPath Condition=" '$(Platform)' == 'x64' ">build64\$(Configuration)\Telegram.Td.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="TdExample.cs" />
</ItemGroup>
- <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <Content Include="build\Debug\LIBEAY32.dll">
- <Link>LIBEAY32.dll</Link>
+ <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ <Content Include="build\Debug\zlibd1.dll">
+ <Link>zlibd1.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="build\Debug\SSLEAY32.dll">
- <Link>SSLEAY32.dll</Link>
+ </ItemGroup>
+ <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ <Content Include="build\Release\zlib1.dll">
+ <Link>zlib1.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="build\Debug\zlibd1.dll">
+ </ItemGroup>
+ <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
+ <Content Include="build64\Debug\zlibd1.dll">
<Link>zlibd1.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
- <ItemGroup Condition=" '$(Configuration)' == 'Release' ">
- <Content Include="build\Release\LIBEAY32.dll">
- <Link>LIBEAY32.dll</Link>
+ <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
+ <Content Include="build64\Release\zlib1.dll">
+ <Link>zlib1.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="build\Release\SSLEAY32.dll">
- <Link>SSLEAY32.dll</Link>
+ </ItemGroup>
+ <ItemGroup Condition=" '$(Platform)' == 'x86' ">
+ <Content Include="build\$(Configuration)\libcrypto-1_1.dll">
+ <Link>libcrypto-1_1.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="build\Release\zlib1.dll">
- <Link>zlib1.dll</Link>
+ <Content Include="build\$(Configuration)\libssl-1_1.dll">
+ <Link>libssl-1_1.dll</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
+ <ItemGroup Condition=" '$(Platform)' == 'x64' ">
+ <Content Include="build64\$(Configuration)\libcrypto-1_1-x64.dll">
+ <Link>libcrypto-1_1-x64.dll</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="build64\$(Configuration)\libssl-1_1-x64.dll">
+ <Link>libssl-1_1-x64.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
diff --git a/protocols/Telegram/tdlib/td/example/go/main.go b/protocols/Telegram/tdlib/td/example/go/main.go
deleted file mode 100644
index 430e067019..0000000000
--- a/protocols/Telegram/tdlib/td/example/go/main.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package main
-
-// #cgo LDFLAGS: -ltdjson
-// #include <td/telegram/td_json_client.h>
-import "C"
-import (
- "log"
- "unsafe"
-)
-
-func td_send(client unsafe.Pointer, query *C.char) {
- C.td_json_client_send(client, query)
-}
-
-func td_receive(client unsafe.Pointer) string {
- return C.GoString(C.td_json_client_receive(client, 1.0))
-}
-
-func main() {
- var client unsafe.Pointer
- client = C.td_json_client_create()
-
- query := C.CString(`{"@type": "getAuthorizationState"}`)
- td_send(client, query)
- log.Println(td_receive(client))
-}
diff --git a/protocols/Telegram/tdlib/td/example/ios/Python-Apple-support.patch b/protocols/Telegram/tdlib/td/example/ios/Python-Apple-support.patch
new file mode 100644
index 0000000000..cb551bf70a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/ios/Python-Apple-support.patch
@@ -0,0 +1,104 @@
+diff --git a/Makefile b/Makefile
+index 695be54..eda7b0d 100644
+--- a/Makefile
++++ b/Makefile
+@@ -7,8 +7,11 @@
+ # - watchOS - build everything for watchOS
+ # - OpenSSL-macOS - build OpenSSL for macOS
+ # - OpenSSL-iOS - build OpenSSL for iOS
++# - OpenSSL-iOS-simulator - build OpenSSL for iOS-simulator
+ # - OpenSSL-tvOS - build OpenSSL for tvOS
++# - OpenSSL-tvOS-simulator - build OpenSSL for tvOS-simulator
+ # - OpenSSL-watchOS - build OpenSSL for watchOS
++# - OpenSSL-watchOS-simulator - build OpenSSL for watchOS-simulator
+ # - BZip2-macOS - build BZip2 for macOS
+ # - BZip2-iOS - build BZip2 for iOS
+ # - BZip2-tvOS - build BZip2 for tvOS
+@@ -30,37 +33,51 @@ PYTHON_VERSION=2.7.14
+ PYTHON_VER=$(basename $(PYTHON_VERSION))
+
+ OPENSSL_VERSION_NUMBER=1.0.2
+-OPENSSL_REVISION=n
++OPENSSL_REVISION=u
+ OPENSSL_VERSION=$(OPENSSL_VERSION_NUMBER)$(OPENSSL_REVISION)
+
+ BZIP2_VERSION=1.0.6
+
+ # Supported OS
+-OS=macOS iOS tvOS watchOS
++OS=macOS iOS iOS-simulator tvOS tvOS-simulator watchOS watchOS-simulator
+
+ # macOS targets
+-TARGETS-macOS=macosx.x86_64
++TARGETS-macOS=macosx.arm64 macosx.x86_64
++PYTHON_TARGETS-macOS=macOS
+ CFLAGS-macOS=-mmacosx-version-min=$(MACOSX_DEPLOYMENT_TARGET)
+
+ # iOS targets
+-TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.i386 iphoneos.armv7 iphoneos.armv7s iphoneos.arm64
++TARGETS-iOS=iphoneos.armv7 iphoneos.armv7s iphoneos.arm64
+ CFLAGS-iOS=-mios-version-min=7.0
+-CFLAGS-iphoneos.armv7=-fembed-bitcode
+-CFLAGS-iphoneos.armv7s=-fembed-bitcode
+-CFLAGS-iphoneos.arm64=-fembed-bitcode
++CFLAGS-iphoneos.armv7=
++CFLAGS-iphoneos.armv7s=
++CFLAGS-iphoneos.arm64=
++
++# iOS-simulator targets
++TARGETS-iOS-simulator=iphonesimulator.x86_64 iphonesimulator.i386 iphonesimulator.arm64
++CFLAGS-iOS-simulator=-mios-simulator-version-min=7.0
+
+ # tvOS targets
+-TARGETS-tvOS=appletvsimulator.x86_64 appletvos.arm64
++TARGETS-tvOS=appletvos.arm64
+ CFLAGS-tvOS=-mtvos-version-min=9.0
+-CFLAGS-appletvos.arm64=-fembed-bitcode
++CFLAGS-appletvos.arm64=
+ PYTHON_CONFIGURE-tvOS=ac_cv_func_sigaltstack=no
+
++# tvOS-simulator targets
++TARGETS-tvOS-simulator=appletvsimulator.x86_64 appletvsimulator.arm64
++CFLAGS-tvOS-simulator=-mtvos-simulator-version-min=9.0
++
+ # watchOS targets
+-TARGETS-watchOS=watchsimulator.i386 watchos.armv7k
++TARGETS-watchOS=watchos.armv7k watchos.arm64_32
+ CFLAGS-watchOS=-mwatchos-version-min=4.0
+-CFLAGS-watchos.armv7k=-fembed-bitcode
++CFLAGS-watchos.armv7k=
++CFLAGS-watchos.arm64_32=
+ PYTHON_CONFIGURE-watchOS=ac_cv_func_sigaltstack=no
+
++# watchOS-simulator targets
++TARGETS-watchOS-simulator=watchsimulator.i386 watchsimulator.x86_64 watchsimulator.arm64
++CFLAGS-watchOS-simulator=-mwatchos-simulator-version-min=4.0
++
+ # override machine types for arm64
+ MACHINE_DETAILED-arm64=aarch64
+ MACHINE_SIMPLE-arm64=arm
+@@ -194,9 +211,11 @@ endif
+
+ # Configure the build
+ ifeq ($2,macOS)
++ # Patch openssl-darwin-arm64
++ cd $$(OPENSSL_DIR-$1) && git apply ../../../../openssl-1.0.2n-darwin-arm64.patch
+ cd $$(OPENSSL_DIR-$1) && \
+ CC="$$(CC-$1)" MACOSX_DEPLOYMENT_TARGET=$$(MACOSX_DEPLOYMENT_TARGET) \
+- ./Configure darwin64-x86_64-cc --openssldir=$(PROJECT_DIR)/build/$2/openssl
++ ./Configure darwin64-$$(ARCH-$1)-cc --openssldir=$(PROJECT_DIR)/build/$2/openssl
+ else
+ cd $$(OPENSSL_DIR-$1) && \
+ CC="$$(CC-$1)" \
+@@ -216,7 +235,10 @@ $$(OPENSSL_DIR-$1)/libssl.a $$(OPENSSL_DIR-$1)/libcrypto.a: $$(OPENSSL_DIR-$1)/M
+ CC="$$(CC-$1)" \
+ CROSS_TOP="$$(dir $$(SDK_ROOT-$1)).." \
+ CROSS_SDK="$$(notdir $$(SDK_ROOT-$1))" \
+- make all && make install
++ make build_libs && \
++ mkdir -p "$(PROJECT_DIR)/build/$2/openssl/lib" && \
++ cp libcrypto.a libssl.a "$(PROJECT_DIR)/build/$2/openssl/lib"
++ -cd $$(OPENSSL_DIR-$1) && make install_sw 2> /dev/null
+
+ # Unpack BZip2
+ $$(BZIP2_DIR-$1)/Makefile: downloads/bzip2-$(BZIP2_VERSION).tgz
diff --git a/protocols/Telegram/tdlib/td/example/ios/README.md b/protocols/Telegram/tdlib/td/example/ios/README.md
index 25a1e68fcc..00d4312be4 100644
--- a/protocols/Telegram/tdlib/td/example/ios/README.md
+++ b/protocols/Telegram/tdlib/td/example/ios/README.md
@@ -1,18 +1,19 @@
-# Build for iOS
+# Universal XCFramework build example
Below are instructions for building TDLib for iOS, watchOS, tvOS, and also macOS.
-If you need only a macOS build, take a look at our build instructions for [macOS](https://github.com/tdlib/td#macos).
+If you need only a macOS build for the current architecture, take a look at [TDLib build instructions generator](https://tdlib.github.io/td/build.html).
For example of usage take a look at our [Swift example](https://github.com/tdlib/td/tree/master/example/swift).
To compile `TDLib` you will need to:
-* Install the latest Xcode command line tools.
-* Install other build dependencies, for example, using [Homebrew](https://brew.sh):
+* Install the latest Xcode via `xcode-select --install` or downloading it from [Xcode website](https://developer.apple.com/xcode/).
+ It is not enough to install only command line developer tools to build `TDLib` for iOS.
+* Install other build dependencies using [Homebrew](https://brew.sh):
```
-brew install gperf cmake
+brew install gperf cmake coreutils
```
-* If you don't want to build `TDLib` for macOS, you can pregenerate required source code files using CMake prepare_cross_compiling target:
+* If you don't want to build `TDLib` for macOS first, you **must** pregenerate required source code files using `CMake` prepare_cross_compiling target:
```
cd <path to TDLib sources>
mkdir native-build
@@ -20,22 +21,26 @@ cd native-build
cmake ..
cmake --build . --target prepare_cross_compiling
```
-* Build OpenSSL for iOS, watchOS, tvOS and macOS:
+* Build OpenSSL for iOS, watchOS, tvOS, and macOS:
```
cd <path to TDLib sources>/example/ios
./build-openssl.sh
```
-Here we use scripts from [Python Apple support](https://github.com/pybee/Python-Apple-support), but any other OpenSSL builds should work too.
-Built libraries should be stored in `third_party/openssl/<platform>`, because the next script will rely on this location.
-* Build TDLib for iOS, watchOS, tvOS and macOS:
+Here we use scripts from [Python Apple support](https://github.com/beeware/Python-Apple-support), but any other OpenSSL build should work too.
+[Python Apple support](https://github.com/beeware/Python-Apple-support) has known problems with spaces in the path to the current directory, so
+you need to ensure that there are no spaces in the path.
+Built OpenSSL libraries should be stored in the directory `third_party/openssl/<platform>`, because the next script will rely on this location.
+* Build TDLib for iOS, watchOS, tvOS, and macOS:
```
cd <path to TDLib sources>/example/ios
./build.sh
```
-This may take a while, because TDLib will be built about 10 times.
-Resulting library for iOS will work on any architecture (arv7, armv7s, arm64) and even on a simulator.
+This may take a while, because TDLib will be built about 16 times.
+Resulting XCFramework will work on any architecture and even on a simulator.
We use [CMake/iOS.cmake](https://github.com/tdlib/td/blob/master/CMake/iOS.cmake) toolchain, other toolchains may work too.
-Built libraries will be store in `tdjson` directory.
+Built libraries and XCFramework will be stored in `tdjson` directory.
Documentation for all available classes and methods can be found at https://core.telegram.org/tdlib/docs.
+
+If you receive an "error: SDK "appletvsimulator" cannot be located", you need to run the command "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" before running ./build.sh.
diff --git a/protocols/Telegram/tdlib/td/example/ios/build-openssl.sh b/protocols/Telegram/tdlib/td/example/ios/build-openssl.sh
index 2ad9dbcfee..eac937caa8 100644
--- a/protocols/Telegram/tdlib/td/example/ios/build-openssl.sh
+++ b/protocols/Telegram/tdlib/td/example/ios/build-openssl.sh
@@ -1,22 +1,37 @@
#!/bin/sh
+cd $(dirname $0)
-git clone https://github.com/pybee/Python-Apple-support
+git clone https://github.com/beeware/Python-Apple-support
cd Python-Apple-support
-git checkout 2.7
+git checkout 60b990128d5f1f04c336ff66594574515ab56604
+git apply ../Python-Apple-support.patch
cd ..
#TODO: change openssl version
platforms="macOS iOS watchOS tvOS"
+
for platform in $platforms;
do
- echo $platform
- cd Python-Apple-support
- #NB: -j will fail
- make OpenSSL-$platform
- cd ..
- rm -rf third_party/openssl/$platform
- mkdir -p third_party/openssl/$platform/lib
- cp ./Python-Apple-support/build/$platform/libcrypto.a third_party/openssl/$platform/lib/
- cp ./Python-Apple-support/build/$platform/libssl.a third_party/openssl/$platform/lib/
- cp -r ./Python-Apple-support/build/$platform/Support/OpenSSL/Headers/ third_party/openssl/$platform/include
+ if [[ $platform = "macOS" ]]; then
+ simulators="0"
+ else
+ simulators="0 1"
+ fi
+
+ for simulator in $simulators;
+ do
+ if [[ $simulator = "1" ]]; then
+ platform="${platform}-simulator"
+ fi
+ echo $platform
+ cd Python-Apple-support
+ #NB: -j will fail
+ make OpenSSL-$platform || exit 1
+ cd ..
+ rm -rf third_party/openssl/$platform || exit 1
+ mkdir -p third_party/openssl/$platform/lib || exit 1
+ cp ./Python-Apple-support/build/$platform/libcrypto.a third_party/openssl/$platform/lib/ || exit 1
+ cp ./Python-Apple-support/build/$platform/libssl.a third_party/openssl/$platform/lib/ || exit 1
+ cp -r ./Python-Apple-support/build/$platform/openssl/include/ third_party/openssl/$platform/include || exit 1
+ done
done
diff --git a/protocols/Telegram/tdlib/td/example/ios/build.sh b/protocols/Telegram/tdlib/td/example/ios/build.sh
index 9970008a1c..cd11907b2d 100644
--- a/protocols/Telegram/tdlib/td/example/ios/build.sh
+++ b/protocols/Telegram/tdlib/td/example/ios/build.sh
@@ -1,74 +1,90 @@
-#/bin/sh
-td_path=$(realpath ../..)
+#!/bin/sh
+cd $(dirname $0)
+td_path=$(grealpath ../..)
rm -rf build
mkdir -p build
cd build
-platforms="macOS iOS watchOS tvOS"
-for platform in $platforms;
-do
- echo "Platform = ${platform} Simulator = ${simulator}"
- openssl_path=$(realpath ../third_party/openssl/${platform})
+set_cmake_options () {
+ # Set CMAKE options depending on platform passed $1
+ openssl_path=$(grealpath ../third_party/openssl/$1)
echo "OpenSSL path = ${openssl_path}"
openssl_crypto_library="${openssl_path}/lib/libcrypto.a"
openssl_ssl_library="${openssl_path}/lib/libssl.a"
+ options=""
options="$options -DOPENSSL_FOUND=1"
options="$options -DOPENSSL_CRYPTO_LIBRARY=${openssl_crypto_library}"
- #options="$options -DOPENSSL_SSL_LIBRARY=${openssl_ssl_library}"
+ options="$options -DOPENSSL_SSL_LIBRARY=${openssl_ssl_library}"
options="$options -DOPENSSL_INCLUDE_DIR=${openssl_path}/include"
options="$options -DOPENSSL_LIBRARIES=${openssl_crypto_library};${openssl_ssl_library}"
options="$options -DCMAKE_BUILD_TYPE=Release"
+}
+
+platforms="macOS iOS watchOS tvOS"
+#platforms="watchOS"
+for platform in $platforms;
+do
+ echo "Platform = ${platform}"
if [[ $platform = "macOS" ]]; then
+ simulators="0"
+ else
+ simulators="0 1"
+ fi
+
+ for simulator in $simulators;
+ do
+ if [[ $platform = "macOS" ]]; then
+ other_options="-DCMAKE_OSX_ARCHITECTURES='x86_64;arm64'"
+ else
+ if [[ $platform = "watchOS" ]]; then
+ ios_platform="WATCH"
+ elif [[ $platform = "tvOS" ]]; then
+ ios_platform="TV"
+ else
+ ios_platform=""
+ fi
+
+ if [[ $simulator = "1" ]]; then
+ platform="${platform}-simulator"
+ ios_platform="${ios_platform}SIMULATOR"
+ else
+ ios_platform="${ios_platform}OS"
+ fi
+
+ echo "iOS platform = ${ios_platform}"
+ other_options="-DIOS_PLATFORM=${ios_platform} -DCMAKE_TOOLCHAIN_FILE=${td_path}/CMake/iOS.cmake"
+ fi
+
+ set_cmake_options $platform
build="build-${platform}"
install="install-${platform}"
rm -rf $build
mkdir -p $build
mkdir -p $install
cd $build
- cmake $td_path $options -DCMAKE_INSTALL_PREFIX=../${install}
+ cmake $td_path $options $other_options -DCMAKE_INSTALL_PREFIX=../${install}
make -j3 install || exit
cd ..
- mkdir -p $platform
- cp $build/libtdjson.dylib $platform/libtdjson.dylib
- install_name_tool -id @rpath/libtdjson.dylib $platform/libtdjson.dylib
- else
- simulators="0 1"
- for simulator in $simulators;
- do
- build="build-${platform}"
- install="install-${platform}"
- if [[ $simulator = "1" ]]; then
- build="${build}-simulator"
- install="${install}-simulator"
- ios_platform="SIMULATOR"
- else
- ios_platform="OS"
- fi
- if [[ $platform = "watchOS" ]]; then
- ios_platform="WATCH${ios_platform}"
- fi
- if [[ $platform = "tvOS" ]]; then
- ios_platform="TV${ios_platform}"
- fi
- echo $ios_platform
- rm -rf $build
- mkdir -p $build
- mkdir -p $install
- cd $build
- cmake $td_path $options -DIOS_PLATFORM=${ios_platform} -DCMAKE_TOOLCHAIN_FILE=${td_path}/CMake/iOS.cmake -DCMAKE_INSTALL_PREFIX=../${install}
- make -j3 install || exit
- cd ..
- done
- lib="install-${platform}/lib/libtdjson.dylib"
- lib_simulator="install-${platform}-simulator/lib/libtdjson.dylib"
- mkdir -p $platform
- lipo -create $lib $lib_simulator -o $platform/libtdjson.dylib
- install_name_tool -id @rpath/libtdjson.dylib $platform/libtdjson.dylib
- fi
+ install_name_tool -id @rpath/libtdjson.dylib ${install}/lib/libtdjson.dylib
+ mkdir -p ../tdjson/${platform}/include
+ rsync --recursive ${install}/include/ ../tdjson/${platform}/include/
+ mkdir -p ../tdjson/${platform}/lib
+ cp ${install}/lib/libtdjson.dylib ../tdjson/${platform}/lib/
+ done
+done
- mkdir -p ../tdjson/$platform/include
- rsync --recursive ${install}/include/ ../tdjson/${platform}/include/
- mkdir -p ../tdjson/$platform/lib
- cp $platform/libtdjson.dylib ../tdjson/$platform/lib/
+produced_dylibs=(install-*/lib/libtdjson.dylib)
+xcodebuild_frameworks=()
+
+for dylib in "${produced_dylibs[@]}";
+do
+ xcodebuild_frameworks+=(-library $(grealpath "${dylib}"))
done
+
+# Make xcframework
+xcodebuild -create-xcframework \
+ "${xcodebuild_frameworks[@]}" \
+ -output "libtdjson.xcframework"
+
+rsync --recursive libtdjson.xcframework ../tdjson/
diff --git a/protocols/Telegram/tdlib/td/example/ios/openssl-1.0.2n-darwin-arm64.patch b/protocols/Telegram/tdlib/td/example/ios/openssl-1.0.2n-darwin-arm64.patch
new file mode 100644
index 0000000000..5239d94c32
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/ios/openssl-1.0.2n-darwin-arm64.patch
@@ -0,0 +1,12 @@
+--- Configure 2019-12-20 14:02:41.000000000 +0100
++++ Configure 2020-11-22 16:23:13.000000000 +0100
+@@ -650,7 +650,9 @@
+ "darwin-i386-cc","cc:-arch i386 -O3 -fomit-frame-pointer -DL_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_INT RC4_CHUNK DES_UNROLL BF_PTR:".eval{my $asm=$x86_asm;$asm=~s/cast\-586\.o//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch i386 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib",
+ "debug-darwin-i386-cc","cc:-arch i386 -g3 -DL_ENDIAN::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:BN_LLONG RC4_INT RC4_CHUNK DES_UNROLL BF_PTR:${x86_asm}:macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch i386 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib",
+ "darwin64-x86_64-cc","cc:-arch x86_64 -O3 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHUNK DES_INT DES_UNROLL:".eval{my $asm=$x86_64_asm;$asm=~s/rc4\-[^:]+//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch x86_64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib",
++"darwin64-arm64-cc","cc:-arch arm64 -O3 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHUNK DES_INT DES_UNROLL:${no_asm}:dlfcn:darwin-shared:-fPIC -fno-common:-arch arm64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib",
+ "debug-darwin64-x86_64-cc","cc:-arch x86_64 -ggdb -g2 -O0 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHUNK DES_INT DES_UNROLL:".eval{my $asm=$x86_64_asm;$asm=~s/rc4\-[^:]+//;$asm}.":macosx:dlfcn:darwin-shared:-fPIC -fno-common:-arch x86_64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib",
++"debug-darwin64-arm64-cc","cc:-arch arm64 -ggdb -g2 -O0 -DL_ENDIAN -Wall::-D_REENTRANT:MACOSX:-Wl,-search_paths_first%:SIXTY_FOUR_BIT_LONG RC4_CHUNK DES_INT DES_UNROLL:${no_asm}:dlfcn:darwin-shared:-fPIC -fno-common:-arch arm64 -dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib",
+ "debug-darwin-ppc-cc","cc:-DBN_DEBUG -DREF_CHECK -DCONF_DEBUG -DCRYPTO_MDEBUG -DB_ENDIAN -g -Wall -O::-D_REENTRANT:MACOSX::BN_LLONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${ppc32_asm}:osx32:dlfcn:darwin-shared:-fPIC:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib",
+ # iPhoneOS/iOS
+ "iphoneos-cross","llvm-gcc:-O3 -isysroot \$(CROSS_TOP)/SDKs/\$(CROSS_SDK) -fomit-frame-pointer -fno-common::-D_REENTRANT:macOS:-Wl,-search_paths_first%:BN_LLONG RC4_CHAR RC4_CHUNK DES_UNROLL BF_PTR:${no_asm}:dlfcn:darwin-shared:-fPIC -fno-common:-dynamiclib:.\$(SHLIB_MAJOR).\$(SHLIB_MINOR).dylib",
diff --git a/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt b/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt
index e8313a68b7..576eee2cfc 100644
--- a/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/example/java/CMakeLists.txt
@@ -1,7 +1,26 @@
-cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+
+if (POLICY CMP0065)
+ # do not export symbols from executables
+ # affects compiler checks in project(), so must be set before it
+ cmake_policy(SET CMP0065 NEW)
+endif()
project(TdJavaExample VERSION 1.0 LANGUAGES CXX)
+if (POLICY CMP0054)
+ # do not expand quoted arguments
+ cmake_policy(SET CMP0054 NEW)
+endif()
+if (POLICY CMP0060)
+ # link libraries by full path
+ cmake_policy(SET CMP0060 NEW)
+endif()
+if (POLICY CMP0074)
+ # use environment variables to find libraries
+ cmake_policy(SET CMP0074 NEW)
+endif()
+
find_package(Td REQUIRED)
if (NOT JNI_FOUND)
@@ -10,12 +29,15 @@ endif()
message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}")
if (NOT Java_FOUND)
- find_package(Java 1.6 REQUIRED)
+ find_package(Java REQUIRED)
endif()
message(STATUS "Found Java: ${Java_JAVAC_EXECUTABLE} ${Java_JAVADOC_EXECUTABLE}")
# Generating TdApi.java
find_program(PHP_EXECUTABLE php)
+if ((CMAKE_SYSTEM_NAME MATCHES "FreeBSD") AND (CMAKE_SYSTEM_VERSION MATCHES "HBSD"))
+ set(PHP_EXECUTABLE "PHP_EXECUTABLE-NOTFOUND")
+endif()
set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib")
set(TD_API_JAVA_PATH ${CMAKE_CURRENT_SOURCE_DIR})
@@ -37,14 +59,13 @@ set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}")
get_filename_component(JAVA_OUTPUT_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin REALPATH BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
file(MAKE_DIRECTORY ${JAVA_OUTPUT_DIRECTORY})
add_custom_target(build_java
- COMMAND ${Java_JAVAC_EXECUTABLE} -d ${JAVA_OUTPUT_DIRECTORY} ${JAVA_SOURCE_PATH}/example/Example.java ${JAVA_SOURCE_PATH}/Client.java ${JAVA_SOURCE_PATH}/Log.java ${JAVA_SOURCE_PATH}/TdApi.java
+ COMMAND ${Java_JAVAC_EXECUTABLE} -encoding UTF-8 -d ${JAVA_OUTPUT_DIRECTORY} ${JAVA_SOURCE_PATH}/example/Example.java ${JAVA_SOURCE_PATH}/Client.java ${JAVA_SOURCE_PATH}/TdApi.java
COMMENT "Building Java code"
DEPENDS td_generate_java_api
)
-set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}")
add_custom_target(generate_javadoc
- COMMAND ${Java_JAVADOC_EXECUTABLE} -d ${JAVA_OUTPUT_DIRECTORY}/../docs org.drinkless.tdlib
+ COMMAND ${Java_JAVADOC_EXECUTABLE} -encoding UTF-8 -charset UTF-8 -d ${JAVA_OUTPUT_DIRECTORY}/../docs org.drinkless.tdlib
WORKING_DIRECTORY ${TD_API_JAVA_PATH}
COMMENT "Generating Javadoc documentation"
DEPENDS td_generate_java_api
@@ -58,7 +79,39 @@ target_include_directories(tdjni PRIVATE ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PAT
target_link_libraries(tdjni PRIVATE Td::TdStatic ${JAVA_JVM_LIBRARY})
target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="${TD_API_JAVA_PACKAGE}")
-set_property(TARGET tdjni PROPERTY CXX_STANDARD 14)
+if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ set(GCC 1)
+elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ set(CLANG 1)
+elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
+ set(INTEL 1)
+elseif (NOT MSVC)
+ message(FATAL_ERROR "Compiler isn't supported")
+endif()
+
+include(CheckCXXCompilerFlag)
+
+if (GCC OR CLANG OR INTEL)
+ if (WIN32 AND INTEL)
+ set(STD14_FLAG /Qstd=c++14)
+ else()
+ set(STD14_FLAG -std=c++14)
+ endif()
+ check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD14)
+ if (NOT HAVE_STD14)
+ string(REPLACE "c++14" "c++1y" STD14_FLAG "${STD14_FLAG}")
+ check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD1Y)
+ set(HAVE_STD14 ${HAVE_STD1Y})
+ endif()
+
+ target_compile_options(tdjni PRIVATE "${STD14_FLAG}")
+elseif (MSVC)
+ set(HAVE_STD14 MSVC_VERSION>=1900)
+endif()
+
+if (NOT HAVE_STD14)
+ message(FATAL_ERROR "No C++14 support in the compiler. Please upgrade the compiler.")
+endif()
add_dependencies(tdjni td_generate_java_api build_java generate_javadoc)
@@ -66,3 +119,6 @@ install(TARGETS tdjni
LIBRARY DESTINATION bin
RUNTIME DESTINATION bin
)
+if (MSVC AND VCPKG_TOOLCHAIN)
+ install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/" DESTINATION bin FILES_MATCHING PATTERN "*.dll" PATTERN "*.pdb")
+endif()
diff --git a/protocols/Telegram/tdlib/td/example/java/README.md b/protocols/Telegram/tdlib/td/example/java/README.md
index c2b5c43171..4c7df1916d 100644
--- a/protocols/Telegram/tdlib/td/example/java/README.md
+++ b/protocols/Telegram/tdlib/td/example/java/README.md
@@ -3,7 +3,11 @@
To run this example, you will need installed JDK >= 1.6.
For Javadoc documentation generation PHP is needed.
-TDLib should be prebuilt for using with Java and installed to local subdirectory `td/` as follows:
+You can find complete build instructions for your operating system at https://tdlib.github.io/td/build.html?language=Java.
+
+In general, the build process looks as follows.
+
+TDLib should be prebuilt with JNI bindings and installed to local subdirectory `td/` as follows:
```
cd <path to TDLib sources>
mkdir jnibuild
@@ -11,9 +15,9 @@ cd jnibuild
cmake -DCMAKE_BUILD_TYPE=Release -DTD_ENABLE_JNI=ON -DCMAKE_INSTALL_PREFIX:PATH=../example/java/td ..
cmake --build . --target install
```
-If you want to compile TDLib for 64-bit Java on Windows using MSVC, you will also need to add `-A x64` option to CMake.
+If you want to compile TDLib for 32-bit/64-bit Java on Windows using MSVC, you will also need to add `-A Win32`/`-A x64` option to CMake.
-In Windows, use Vcpkg toolchain file by adding parameter -DCMAKE_TOOLCHAIN_FILE=<VCPKG_DIR>/scripts/buildsystems/vcpkg.cmake
+In Windows, use vcpkg toolchain file by adding parameter -DCMAKE_TOOLCHAIN_FILE=<VCPKG_DIR>/scripts/buildsystems/vcpkg.cmake
Then you can build this example:
```
@@ -32,8 +36,8 @@ cd <path to TDLib sources>/example/java/bin
java '-Djava.library.path=.' org/drinkless/tdlib/example/Example
```
-If you get "Could NOT find JNI ..." error from CMake, you need to specify to CMake path to the installed JDK, for example, "-DJAVA_HOME=/usr/lib/jvm/java-8-oracle/".
+If you receive "Could NOT find JNI ..." error from CMake, you need to specify to CMake path to the installed JDK, for example, "-DJAVA_HOME=/usr/lib/jvm/java-8-oracle/".
-If you get java.lang.UnsatisfiedLinkError with "Can't find dependent libraries", you may also need to copy some dependent shared libraries to `bin/`.
+If you receive java.lang.UnsatisfiedLinkError with "Can't find dependent libraries", you may also need to copy some dependent shared OpenSSL and zlib libraries to `bin/`.
-In case you compiled the example as 32-bit version, you may need to give -d32 parameter to Java.
+Make sure that you compiled the example for the same architecture as your JVM.
diff --git a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java
index efb38e9c5a..0a58bb45a7 100644
--- a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java
+++ b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Client.java
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,14 +8,19 @@ package org.drinkless.tdlib;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Main class for interaction with the TDLib.
*/
-public final class Client implements Runnable {
+public final class Client {
+ static {
+ try {
+ System.loadLibrary("tdjni");
+ } catch (UnsatisfiedLinkError e) {
+ e.printStackTrace();
+ }
+ }
+
/**
* Interface for handler for results of queries to TDLib and incoming updates from TDLib.
*/
@@ -43,6 +48,21 @@ public final class Client implements Runnable {
}
/**
+ * Interface for handler of messages that are added to the internal TDLib log.
+ */
+ public interface LogMessageHandler {
+ /**
+ * Callback called on messages that are added to the internal TDLib log.
+ *
+ * @param verbosityLevel Log verbosity level with which the message was added from -1 up to 1024.
+ * If 0, then TDLib will crash as soon as the callback returns.
+ * None of the TDLib methods can be called from the callback.
+ * @param message The message added to the internal TDLib log.
+ */
+ void onLogMessage(int verbosityLevel, String message);
+ }
+
+ /**
* Sends a request to the TDLib.
*
* @param query Object representing a query to the TDLib.
@@ -55,25 +75,11 @@ public final class Client implements Runnable {
* @throws NullPointerException if query is null.
*/
public void send(TdApi.Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
- if (query == null) {
- throw new NullPointerException("query is null");
- }
-
- readLock.lock();
- try {
- if (isClientDestroyed) {
- if (resultHandler != null) {
- handleResult(new TdApi.Error(500, "Client is closed"), resultHandler, exceptionHandler);
- }
- return;
- }
-
- long queryId = currentQueryId.incrementAndGet();
+ long queryId = currentQueryId.incrementAndGet();
+ if (resultHandler != null) {
handlers.put(queryId, new Handler(resultHandler, exceptionHandler));
- nativeClientSend(nativeClientId, queryId, query);
- } finally {
- readLock.unlock();
}
+ nativeClientSend(nativeClientId, queryId, query);
}
/**
@@ -97,108 +103,105 @@ public final class Client implements Runnable {
* @throws NullPointerException if query is null.
*/
public static TdApi.Object execute(TdApi.Function query) {
- if (query == null) {
- throw new NullPointerException("query is null");
- }
return nativeClientExecute(query);
}
/**
- * Replaces handler for incoming updates from the TDLib.
+ * Creates new Client.
*
- * @param updatesHandler Handler with onResult method which will be called for every incoming
- * update from the TDLib.
- * @param exceptionHandler Exception handler with onException method which will be called on
- * exception thrown from updatesHandler, if it is null, defaultExceptionHandler will be invoked.
+ * @param updateHandler Handler for incoming updates.
+ * @param updateExceptionHandler Handler for exceptions thrown from updateHandler. If it is null, exceptions will be iggnored.
+ * @param defaultExceptionHandler Default handler for exceptions thrown from all ResultHandler. If it is null, exceptions will be iggnored.
+ * @return created Client
*/
- public void setUpdatesHandler(ResultHandler updatesHandler, ExceptionHandler exceptionHandler) {
- handlers.put(0L, new Handler(updatesHandler, exceptionHandler));
+ public static Client create(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
+ Client client = new Client(updateHandler, updateExceptionHandler, defaultExceptionHandler);
+ synchronized (responseReceiver) {
+ if (!responseReceiver.isRun) {
+ responseReceiver.isRun = true;
+
+ Thread receiverThread = new Thread(responseReceiver, "TDLib thread");
+ receiverThread.setDaemon(true);
+ receiverThread.start();
+ }
+ }
+ return client;
}
/**
- * Replaces handler for incoming updates from the TDLib. Sets empty ExceptionHandler.
+ * Sets the handler for messages that are added to the internal TDLib log.
+ * None of the TDLib methods can be called from the callback.
*
- * @param updatesHandler Handler with onResult method which will be called for every incoming
- * update from the TDLib.
+ * @param maxVerbosityLevel The maximum verbosity level of messages for which the callback will be called.
+ * @param logMessageHandler Handler for messages that are added to the internal TDLib log. Pass null to remove the handler.
*/
- public void setUpdatesHandler(ResultHandler updatesHandler) {
- setUpdatesHandler(updatesHandler, null);
+ public static void setLogMessageHandler(int maxVerbosityLevel, Client.LogMessageHandler logMessageHandler) {
+ nativeClientSetLogMessageHandler(maxVerbosityLevel, logMessageHandler);
}
- /**
- * Replaces default exception handler to be invoked on exceptions thrown from updatesHandler and all other ResultHandler.
- *
- * @param defaultExceptionHandler Default exception handler. If null Exceptions are ignored.
- */
- public void setDefaultExceptionHandler(Client.ExceptionHandler defaultExceptionHandler) {
- this.defaultExceptionHandler = defaultExceptionHandler;
- }
+ private static class ResponseReceiver implements Runnable {
+ public boolean isRun = false;
- /**
- * Overridden method from Runnable, do not call it directly.
- */
- @Override
- public void run() {
- while (!stopFlag) {
- receiveQueries(300.0 /*seconds*/);
+ @Override
+ public void run() {
+ while (true) {
+ int resultN = nativeClientReceive(clientIds, eventIds, events, 100000.0 /*seconds*/);
+ for (int i = 0; i < resultN; i++) {
+ processResult(clientIds[i], eventIds[i], events[i]);
+ events[i] = null;
+ }
+ }
}
- }
-
- /**
- * Creates new Client.
- *
- * @param updatesHandler Handler for incoming updates.
- * @param updatesExceptionHandler Handler for exceptions thrown from updatesHandler. If it is null, exceptions will be iggnored.
- * @param defaultExceptionHandler Default handler for exceptions thrown from all ResultHandler. If it is null, exceptions will be iggnored.
- * @return created Client
- */
- public static Client create(ResultHandler updatesHandler, ExceptionHandler updatesExceptionHandler, ExceptionHandler defaultExceptionHandler) {
- Client client = new Client(updatesHandler, updatesExceptionHandler, defaultExceptionHandler);
- new Thread(client, "TDLib thread").start();
- return client;
- }
- /**
- * Closes Client.
- */
- public void close() {
- writeLock.lock();
- try {
- if (isClientDestroyed) {
- return;
- }
- if (!stopFlag) {
- send(new TdApi.Close(), null);
+ private void processResult(int clientId, long id, TdApi.Object object) {
+ boolean isClosed = false;
+ if (id == 0 && object instanceof TdApi.UpdateAuthorizationState) {
+ TdApi.AuthorizationState authorizationState = ((TdApi.UpdateAuthorizationState) object).authorizationState;
+ if (authorizationState instanceof TdApi.AuthorizationStateClosed) {
+ isClosed = true;
+ }
}
- isClientDestroyed = true;
- while (!stopFlag) {
- Thread.yield();
+
+ Handler handler = id == 0 ? updateHandlers.get(clientId) : handlers.remove(id);
+ if (handler != null) {
+ try {
+ handler.resultHandler.onResult(object);
+ } catch (Throwable cause) {
+ ExceptionHandler exceptionHandler = handler.exceptionHandler;
+ if (exceptionHandler == null) {
+ exceptionHandler = defaultExceptionHandlers.get(clientId);
+ }
+ if (exceptionHandler != null) {
+ try {
+ exceptionHandler.onException(cause);
+ } catch (Throwable ignored) {
+ }
+ }
+ }
}
- while (handlers.size() != 1) {
- receiveQueries(300.0);
+
+ if (isClosed) {
+ updateHandlers.remove(clientId); // there will be no more updates
+ defaultExceptionHandlers.remove(clientId); // ignore further exceptions
+ clientCount.decrementAndGet();
}
- destroyNativeClient(nativeClientId);
- } finally {
- writeLock.unlock();
}
- }
-
- private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
- private final Lock readLock = readWriteLock.readLock();
- private final Lock writeLock = readWriteLock.writeLock();
- private volatile boolean stopFlag = false;
- private volatile boolean isClientDestroyed = false;
- private final long nativeClientId;
+ private static final int MAX_EVENTS = 1000;
+ private final int[] clientIds = new int[MAX_EVENTS];
+ private final long[] eventIds = new long[MAX_EVENTS];
+ private final TdApi.Object[] events = new TdApi.Object[MAX_EVENTS];
+ }
- private final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();
- private final AtomicLong currentQueryId = new AtomicLong();
+ private final int nativeClientId;
- private volatile ExceptionHandler defaultExceptionHandler = null;
+ private static final ConcurrentHashMap<Integer, ExceptionHandler> defaultExceptionHandlers = new ConcurrentHashMap<Integer, ExceptionHandler>();
+ private static final ConcurrentHashMap<Integer, Handler> updateHandlers = new ConcurrentHashMap<Integer, Handler>();
+ private static final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();
+ private static final AtomicLong currentQueryId = new AtomicLong();
+ private static final AtomicLong clientCount = new AtomicLong();
- private static final int MAX_EVENTS = 1000;
- private final long[] eventIds = new long[MAX_EVENTS];
- private final TdApi.Object[] events = new TdApi.Object[MAX_EVENTS];
+ private static final ResponseReceiver responseReceiver = new ResponseReceiver();
private static class Handler {
final ResultHandler resultHandler;
@@ -210,76 +213,30 @@ public final class Client implements Runnable {
}
}
- private Client(ResultHandler updatesHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
+ private Client(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
+ clientCount.incrementAndGet();
nativeClientId = createNativeClient();
- handlers.put(0L, new Handler(updatesHandler, updateExceptionHandler));
- this.defaultExceptionHandler = defaultExceptionHandler;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- close();
- } finally {
- super.finalize();
- }
- }
-
- private void processResult(long id, TdApi.Object object) {
- if (object instanceof TdApi.UpdateAuthorizationState) {
- if (((TdApi.UpdateAuthorizationState) object).authorizationState instanceof TdApi.AuthorizationStateClosed) {
- stopFlag = true;
- }
- }
- Handler handler;
- if (id == 0) {
- // update handler stays forever
- handler = handlers.get(id);
- } else {
- handler = handlers.remove(id);
- }
- if (handler == null) {
- return;
- }
-
- handleResult(object, handler.resultHandler, handler.exceptionHandler);
- }
-
- private void handleResult(TdApi.Object object, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
- if (resultHandler == null) {
- return;
+ if (updateHandler != null) {
+ updateHandlers.put(nativeClientId, new Handler(updateHandler, updateExceptionHandler));
}
-
- try {
- resultHandler.onResult(object);
- } catch (Throwable cause) {
- if (exceptionHandler == null) {
- exceptionHandler = defaultExceptionHandler;
- }
- if (exceptionHandler != null) {
- try {
- exceptionHandler.onException(cause);
- } catch (Throwable ignored) {
- }
- }
+ if (defaultExceptionHandler != null) {
+ defaultExceptionHandlers.put(nativeClientId, defaultExceptionHandler);
}
+ send(new TdApi.GetOption("version"), null, null);
}
- private void receiveQueries(double timeout) {
- int resultN = nativeClientReceive(nativeClientId, eventIds, events, timeout);
- for (int i = 0; i < resultN; i++) {
- processResult(eventIds[i], events[i]);
- events[i] = null;
- }
+ @Override
+ protected void finalize() throws Throwable {
+ send(new TdApi.Close(), null, null);
}
- private static native long createNativeClient();
+ private static native int createNativeClient();
- private static native void nativeClientSend(long nativeClientId, long eventId, TdApi.Function function);
+ private static native void nativeClientSend(int nativeClientId, long eventId, TdApi.Function function);
- private static native int nativeClientReceive(long nativeClientId, long[] eventIds, TdApi.Object[] events, double timeout);
+ private static native int nativeClientReceive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout);
private static native TdApi.Object nativeClientExecute(TdApi.Function function);
- private static native void destroyNativeClient(long nativeClientId);
+ private static native void nativeClientSetLogMessageHandler(int maxVerbosityLevel, LogMessageHandler logMessageHandler);
}
diff --git a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Log.java b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Log.java
deleted file mode 100644
index c81ffbeeb7..0000000000
--- a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/Log.java
+++ /dev/null
@@ -1,75 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-package org.drinkless.tdlib;
-
-/**
- * Class for managing internal TDLib logging.
- */
-public final class Log {
- /**
- * Changes TDLib log verbosity.
- *
- * @param verbosityLevel New value of log verbosity level. Must be non-negative.
- * Value 0 corresponds to fatal errors,
- * value 1 corresponds to java.util.logging.Level.SEVERE,
- * value 2 corresponds to java.util.logging.Level.WARNING,
- * value 3 corresponds to java.util.logging.Level.INFO,
- * value 4 corresponds to java.util.logging.Level.FINE,
- * value 5 corresponds to java.util.logging.Level.FINER,
- * value greater than 5 can be used to enable even more logging.
- * Default value of the log verbosity level is 5.
- */
- public static native void setVerbosityLevel(int verbosityLevel);
-
- /**
- * Sets file path for writing TDLib internal log. By default TDLib writes logs to the System.err.
- * Use this method to write the log to a file instead.
- *
- * @param filePath Path to a file for writing TDLib internal log. Use an empty path to
- * switch back to logging to the System.err.
- * @return whether opening the log file succeeded.
- */
- public static native boolean setFilePath(String filePath);
-
- /**
- * Changes maximum size of TDLib log file.
- *
- * @param maxFileSize Maximum size of the file to where the internal TDLib log is written
- * before the file will be auto-rotated. Must be positive. Defaults to 10 MB.
- */
- public static native void setMaxFileSize(long maxFileSize);
-
- /**
- * This function is called from the JNI when a fatal error happens to provide a better error message.
- * The function does not return.
- *
- * @param errorMessage Error message.
- */
- private static void onFatalError(String errorMessage) {
- class ThrowError implements Runnable {
- private ThrowError(String errorMessage) {
- this.errorMessage = errorMessage;
- }
-
- @Override
- public void run() {
- throw new RuntimeException("TDLib fatal error: " + errorMessage);
- }
-
- private final String errorMessage;
- }
-
- new Thread(new ThrowError(errorMessage), "TDLib fatal error thread").start();
- while (true) {
- try {
- Thread.sleep(1000); // milliseconds
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- }
- }
- }
-}
diff --git a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java
index 831de88f1d..7e76b42276 100644
--- a/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java
+++ b/protocols/Telegram/tdlib/td/example/java/org/drinkless/tdlib/example/Example.java
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,17 +7,17 @@
package org.drinkless.tdlib.example;
import org.drinkless.tdlib.Client;
-import org.drinkless.tdlib.Log;
import org.drinkless.tdlib.TdApi;
+import java.io.BufferedReader;
import java.io.IOError;
import java.io.IOException;
-import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -30,34 +30,31 @@ public final class Example {
private static TdApi.AuthorizationState authorizationState = null;
private static volatile boolean haveAuthorization = false;
- private static volatile boolean quiting = false;
+ private static volatile boolean needQuit = false;
+ private static volatile boolean canQuit = false;
private static final Client.ResultHandler defaultHandler = new DefaultHandler();
private static final Lock authorizationLock = new ReentrantLock();
private static final Condition gotAuthorization = authorizationLock.newCondition();
- private static final ConcurrentMap<Integer, TdApi.User> users = new ConcurrentHashMap<Integer, TdApi.User>();
- private static final ConcurrentMap<Integer, TdApi.BasicGroup> basicGroups = new ConcurrentHashMap<Integer, TdApi.BasicGroup>();
- private static final ConcurrentMap<Integer, TdApi.Supergroup> supergroups = new ConcurrentHashMap<Integer, TdApi.Supergroup>();
+ private static final ConcurrentMap<Long, TdApi.User> users = new ConcurrentHashMap<Long, TdApi.User>();
+ private static final ConcurrentMap<Long, TdApi.BasicGroup> basicGroups = new ConcurrentHashMap<Long, TdApi.BasicGroup>();
+ private static final ConcurrentMap<Long, TdApi.Supergroup> supergroups = new ConcurrentHashMap<Long, TdApi.Supergroup>();
private static final ConcurrentMap<Integer, TdApi.SecretChat> secretChats = new ConcurrentHashMap<Integer, TdApi.SecretChat>();
private static final ConcurrentMap<Long, TdApi.Chat> chats = new ConcurrentHashMap<Long, TdApi.Chat>();
- private static final NavigableSet<OrderedChat> chatList = new TreeSet<OrderedChat>();
- private static boolean haveFullChatList = false;
+ private static final NavigableSet<OrderedChat> mainChatList = new TreeSet<OrderedChat>();
+ private static boolean haveFullMainChatList = false;
- private static final ConcurrentMap<Integer, TdApi.UserFullInfo> usersFullInfo = new ConcurrentHashMap<Integer, TdApi.UserFullInfo>();
- private static final ConcurrentMap<Integer, TdApi.BasicGroupFullInfo> basicGroupsFullInfo = new ConcurrentHashMap<Integer, TdApi.BasicGroupFullInfo>();
- private static final ConcurrentMap<Integer, TdApi.SupergroupFullInfo> supergroupsFullInfo = new ConcurrentHashMap<Integer, TdApi.SupergroupFullInfo>();
+ private static final ConcurrentMap<Long, TdApi.UserFullInfo> usersFullInfo = new ConcurrentHashMap<Long, TdApi.UserFullInfo>();
+ private static final ConcurrentMap<Long, TdApi.BasicGroupFullInfo> basicGroupsFullInfo = new ConcurrentHashMap<Long, TdApi.BasicGroupFullInfo>();
+ private static final ConcurrentMap<Long, TdApi.SupergroupFullInfo> supergroupsFullInfo = new ConcurrentHashMap<Long, TdApi.SupergroupFullInfo>();
private static final String newLine = System.getProperty("line.separator");
private static final String commandsLine = "Enter command (gcs - GetChats, gc <chatId> - GetChat, me - GetMe, sm <chatId> <message> - SendMessage, lo - LogOut, q - Quit): ";
private static volatile String currentPrompt = null;
- static {
- System.loadLibrary("tdjni");
- }
-
private static void print(String str) {
if (currentPrompt != null) {
System.out.println("");
@@ -68,18 +65,24 @@ public final class Example {
}
}
- private static void setChatOrder(TdApi.Chat chat, long order) {
- synchronized (chatList) {
- if (chat.order != 0) {
- boolean isRemoved = chatList.remove(new OrderedChat(chat.order, chat.id));
- assert isRemoved;
- }
+ private static void setChatPositions(TdApi.Chat chat, TdApi.ChatPosition[] positions) {
+ synchronized (mainChatList) {
+ synchronized (chat) {
+ for (TdApi.ChatPosition position : chat.positions) {
+ if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
+ boolean isRemoved = mainChatList.remove(new OrderedChat(chat.id, position));
+ assert isRemoved;
+ }
+ }
- chat.order = order;
+ chat.positions = positions;
- if (chat.order != 0) {
- boolean isAdded = chatList.add(new OrderedChat(chat.order, chat.id));
- assert isAdded;
+ for (TdApi.ChatPosition position : chat.positions) {
+ if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
+ boolean isAdded = mainChatList.add(new OrderedChat(chat.id, position));
+ assert isAdded;
+ }
+ }
}
}
}
@@ -90,31 +93,48 @@ public final class Example {
}
switch (Example.authorizationState.getConstructor()) {
case TdApi.AuthorizationStateWaitTdlibParameters.CONSTRUCTOR:
- TdApi.TdlibParameters parameters = new TdApi.TdlibParameters();
- parameters.databaseDirectory = "tdlib";
- parameters.useMessageDatabase = true;
- parameters.useSecretChats = true;
- parameters.apiId = 94575;
- parameters.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
- parameters.systemLanguageCode = "en";
- parameters.deviceModel = "Desktop";
- parameters.systemVersion = "Unknown";
- parameters.applicationVersion = "1.0";
- parameters.enableStorageOptimizer = true;
-
- client.send(new TdApi.SetTdlibParameters(parameters), new AuthorizationRequestHandler());
- break;
- case TdApi.AuthorizationStateWaitEncryptionKey.CONSTRUCTOR:
- client.send(new TdApi.CheckDatabaseEncryptionKey(), new AuthorizationRequestHandler());
+ TdApi.SetTdlibParameters request = new TdApi.SetTdlibParameters();
+ request.databaseDirectory = "tdlib";
+ request.useMessageDatabase = true;
+ request.useSecretChats = true;
+ request.apiId = 94575;
+ request.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
+ request.systemLanguageCode = "en";
+ request.deviceModel = "Desktop";
+ request.applicationVersion = "1.0";
+ request.enableStorageOptimizer = true;
+
+ client.send(request, new AuthorizationRequestHandler());
break;
case TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR: {
String phoneNumber = promptString("Please enter phone number: ");
- client.send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, false, false), new AuthorizationRequestHandler());
+ client.send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, null), new AuthorizationRequestHandler());
+ break;
+ }
+ case TdApi.AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR: {
+ String link = ((TdApi.AuthorizationStateWaitOtherDeviceConfirmation) Example.authorizationState).link;
+ System.out.println("Please confirm this login link on another device: " + link);
+ break;
+ }
+ case TdApi.AuthorizationStateWaitEmailAddress.CONSTRUCTOR: {
+ String emailAddress = promptString("Please enter email address: ");
+ client.send(new TdApi.SetAuthenticationEmailAddress(emailAddress), new AuthorizationRequestHandler());
+ break;
+ }
+ case TdApi.AuthorizationStateWaitEmailCode.CONSTRUCTOR: {
+ String code = promptString("Please enter email authentication code: ");
+ client.send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(code)), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitCode.CONSTRUCTOR: {
String code = promptString("Please enter authentication code: ");
- client.send(new TdApi.CheckAuthenticationCode(code, "", ""), new AuthorizationRequestHandler());
+ client.send(new TdApi.CheckAuthenticationCode(code), new AuthorizationRequestHandler());
+ break;
+ }
+ case TdApi.AuthorizationStateWaitRegistration.CONSTRUCTOR: {
+ String firstName = promptString("Please enter your first name: ");
+ String lastName = promptString("Please enter your last name: ");
+ client.send(new TdApi.RegisterUser(firstName, lastName), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitPassword.CONSTRUCTOR: {
@@ -141,8 +161,10 @@ public final class Example {
break;
case TdApi.AuthorizationStateClosed.CONSTRUCTOR:
print("Closed");
- if (!quiting) {
- client = Client.create(new UpdatesHandler(), null, null); // recreate client after previous has closed
+ if (!needQuit) {
+ client = Client.create(new UpdateHandler(), null, null); // recreate client after previous has closed
+ } else {
+ canQuit = true;
}
break;
default:
@@ -192,7 +214,7 @@ public final class Example {
if (commands.length > 1) {
limit = toInt(commands[1]);
}
- getChatList(limit);
+ getMainChatList(limit);
break;
}
case "gc":
@@ -211,7 +233,7 @@ public final class Example {
client.send(new TdApi.LogOut(), defaultHandler);
break;
case "q":
- quiting = true;
+ needQuit = true;
haveAuthorization = false;
client.send(new TdApi.Close(), defaultHandler);
break;
@@ -223,33 +245,26 @@ public final class Example {
}
}
- private static void getChatList(final int limit) {
- synchronized (chatList) {
- if (!haveFullChatList && limit > chatList.size()) {
- // have enough chats in the chat list or chat list is too small
- long offsetOrder = Long.MAX_VALUE;
- long offsetChatId = 0;
- if (!chatList.isEmpty()) {
- OrderedChat last = chatList.last();
- offsetOrder = last.order;
- offsetChatId = last.chatId;
- }
- client.send(new TdApi.GetChats(offsetOrder, offsetChatId, limit - chatList.size()), new Client.ResultHandler() {
+ private static void getMainChatList(final int limit) {
+ synchronized (mainChatList) {
+ if (!haveFullMainChatList && limit > mainChatList.size()) {
+ // send LoadChats request if there are some unknown chats and have not enough known chats
+ client.send(new TdApi.LoadChats(new TdApi.ChatListMain(), limit - mainChatList.size()), new Client.ResultHandler() {
@Override
public void onResult(TdApi.Object object) {
switch (object.getConstructor()) {
case TdApi.Error.CONSTRUCTOR:
- System.err.println("Receive an error for GetChats:" + newLine + object);
- break;
- case TdApi.Chats.CONSTRUCTOR:
- long[] chatIds = ((TdApi.Chats) object).chatIds;
- if (chatIds.length == 0) {
- synchronized (chatList) {
- haveFullChatList = true;
+ if (((TdApi.Error) object).code == 404) {
+ synchronized (mainChatList) {
+ haveFullMainChatList = true;
}
+ } else {
+ System.err.println("Receive an error for LoadChats:" + newLine + object);
}
+ break;
+ case TdApi.Ok.CONSTRUCTOR:
// chats had already been received through updates, let's retry request
- getChatList(limit);
+ getMainChatList(limit);
break;
default:
System.err.println("Receive wrong response from TDLib:" + newLine + object);
@@ -259,11 +274,10 @@ public final class Example {
return;
}
- // have enough chats in the chat list to answer request
- java.util.Iterator<OrderedChat> iter = chatList.iterator();
+ java.util.Iterator<OrderedChat> iter = mainChatList.iterator();
System.out.println();
- System.out.println("First " + limit + " chat(s) out of " + chatList.size() + " known chat(s):");
- for (int i = 0; i < limit; i++) {
+ System.out.println("First " + limit + " chat(s) out of " + mainChatList.size() + " known chat(s):");
+ for (int i = 0; i < limit && i < mainChatList.size(); i++) {
long chatId = iter.next().chatId;
TdApi.Chat chat = chats.get(chatId);
synchronized (chat) {
@@ -280,24 +294,27 @@ public final class Example {
TdApi.ReplyMarkup replyMarkup = new TdApi.ReplyMarkupInlineKeyboard(new TdApi.InlineKeyboardButton[][]{row, row, row});
TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), false, true);
- client.send(new TdApi.SendMessage(chatId, 0, false, false, replyMarkup, content), defaultHandler);
+ client.send(new TdApi.SendMessage(chatId, 0, 0, null, replyMarkup, content), defaultHandler);
}
public static void main(String[] args) throws InterruptedException {
- // disable TDLib log
- Log.setVerbosityLevel(0);
- if (!Log.setFilePath("tdlib.log")) {
+ // set log message handler to handle only fatal errors (0) and plain log messages (-1)
+ Client.setLogMessageHandler(0, new LogMessageHandler());
+
+ // disable TDLib log and redirect fatal errors and plain log messages to a file
+ Client.execute(new TdApi.SetLogVerbosityLevel(0));
+ if (Client.execute(new TdApi.SetLogStream(new TdApi.LogStreamFile("tdlib.log", 1 << 27, false))) instanceof TdApi.Error) {
throw new IOError(new IOException("Write access to the current directory is required"));
}
// create client
- client = Client.create(new UpdatesHandler(), null, null);
+ client = Client.create(new UpdateHandler(), null, null);
// test Client.execute
defaultHandler.onResult(Client.execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")));
// main loop
- while (!quiting) {
+ while (!needQuit) {
// await authorization
authorizationLock.lock();
try {
@@ -312,21 +329,24 @@ public final class Example {
getCommand();
}
}
+ while (!canQuit) {
+ Thread.sleep(1);
+ }
}
private static class OrderedChat implements Comparable<OrderedChat> {
- final long order;
final long chatId;
+ final TdApi.ChatPosition position;
- OrderedChat(long order, long chatId) {
- this.order = order;
+ OrderedChat(long chatId, TdApi.ChatPosition position) {
this.chatId = chatId;
+ this.position = position;
}
@Override
public int compareTo(OrderedChat o) {
- if (this.order != o.order) {
- return o.order < this.order ? -1 : 1;
+ if (this.position.order != o.position.order) {
+ return o.position.order < this.position.order ? -1 : 1;
}
if (this.chatId != o.chatId) {
return o.chatId < this.chatId ? -1 : 1;
@@ -337,7 +357,7 @@ public final class Example {
@Override
public boolean equals(Object obj) {
OrderedChat o = (OrderedChat) obj;
- return this.order == o.order && this.chatId == o.chatId;
+ return this.chatId == o.chatId && this.position.order == o.position.order;
}
}
@@ -348,7 +368,7 @@ public final class Example {
}
}
- private static class UpdatesHandler implements Client.ResultHandler {
+ private static class UpdateHandler implements Client.ResultHandler {
@Override
public void onResult(TdApi.Object object) {
switch (object.getConstructor()) {
@@ -360,7 +380,7 @@ public final class Example {
TdApi.UpdateUser updateUser = (TdApi.UpdateUser) object;
users.put(updateUser.user.id, updateUser.user);
break;
- case TdApi.UpdateUserStatus.CONSTRUCTOR: {
+ case TdApi.UpdateUserStatus.CONSTRUCTOR: {
TdApi.UpdateUserStatus updateUserStatus = (TdApi.UpdateUserStatus) object;
TdApi.User user = users.get(updateUserStatus.userId);
synchronized (user) {
@@ -387,9 +407,9 @@ public final class Example {
synchronized (chat) {
chats.put(chat.id, chat);
- long order = chat.order;
- chat.order = 0;
- setChatOrder(chat, order);
+ TdApi.ChatPosition[] positions = chat.positions;
+ chat.positions = new TdApi.ChatPosition[0];
+ setChatPositions(chat, positions);
}
break;
}
@@ -414,24 +434,37 @@ public final class Example {
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.lastMessage = updateChat.lastMessage;
- setChatOrder(chat, updateChat.order);
+ setChatPositions(chat, updateChat.positions);
}
break;
}
- case TdApi.UpdateChatOrder.CONSTRUCTOR: {
- TdApi.UpdateChatOrder updateChat = (TdApi.UpdateChatOrder) object;
- TdApi.Chat chat = chats.get(updateChat.chatId);
- synchronized (chat) {
- setChatOrder(chat, updateChat.order);
+ case TdApi.UpdateChatPosition.CONSTRUCTOR: {
+ TdApi.UpdateChatPosition updateChat = (TdApi.UpdateChatPosition) object;
+ if (updateChat.position.list.getConstructor() != TdApi.ChatListMain.CONSTRUCTOR) {
+ break;
}
- break;
- }
- case TdApi.UpdateChatIsPinned.CONSTRUCTOR: {
- TdApi.UpdateChatIsPinned updateChat = (TdApi.UpdateChatIsPinned) object;
+
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
- chat.isPinned = updateChat.isPinned;
- setChatOrder(chat, updateChat.order);
+ int i;
+ for (i = 0; i < chat.positions.length; i++) {
+ if (chat.positions[i].list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
+ break;
+ }
+ }
+ TdApi.ChatPosition[] new_positions = new TdApi.ChatPosition[chat.positions.length + (updateChat.position.order == 0 ? 0 : 1) - (i < chat.positions.length ? 1 : 0)];
+ int pos = 0;
+ if (updateChat.position.order != 0) {
+ new_positions[pos++] = updateChat.position;
+ }
+ for (int j = 0; j < chat.positions.length; j++) {
+ if (j != i) {
+ new_positions[pos++] = chat.positions[j];
+ }
+ }
+ assert pos == new_positions.length;
+
+ setChatPositions(chat, new_positions);
}
break;
}
@@ -481,17 +514,55 @@ public final class Example {
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.draftMessage = updateChat.draftMessage;
- setChatOrder(chat, updateChat.order);
+ setChatPositions(chat, updateChat.positions);
}
break;
}
- case TdApi.UpdateNotificationSettings.CONSTRUCTOR: {
- TdApi.UpdateNotificationSettings update = (TdApi.UpdateNotificationSettings) object;
- if (update.scope instanceof TdApi.NotificationSettingsScopeChat) {
- TdApi.Chat chat = chats.get(((TdApi.NotificationSettingsScopeChat) update.scope).chatId);
- synchronized (chat) {
- chat.notificationSettings = update.notificationSettings;
- }
+ case TdApi.UpdateChatPermissions.CONSTRUCTOR: {
+ TdApi.UpdateChatPermissions update = (TdApi.UpdateChatPermissions) object;
+ TdApi.Chat chat = chats.get(update.chatId);
+ synchronized (chat) {
+ chat.permissions = update.permissions;
+ }
+ break;
+ }
+ case TdApi.UpdateChatNotificationSettings.CONSTRUCTOR: {
+ TdApi.UpdateChatNotificationSettings update = (TdApi.UpdateChatNotificationSettings) object;
+ TdApi.Chat chat = chats.get(update.chatId);
+ synchronized (chat) {
+ chat.notificationSettings = update.notificationSettings;
+ }
+ break;
+ }
+ case TdApi.UpdateChatDefaultDisableNotification.CONSTRUCTOR: {
+ TdApi.UpdateChatDefaultDisableNotification update = (TdApi.UpdateChatDefaultDisableNotification) object;
+ TdApi.Chat chat = chats.get(update.chatId);
+ synchronized (chat) {
+ chat.defaultDisableNotification = update.defaultDisableNotification;
+ }
+ break;
+ }
+ case TdApi.UpdateChatIsMarkedAsUnread.CONSTRUCTOR: {
+ TdApi.UpdateChatIsMarkedAsUnread update = (TdApi.UpdateChatIsMarkedAsUnread) object;
+ TdApi.Chat chat = chats.get(update.chatId);
+ synchronized (chat) {
+ chat.isMarkedAsUnread = update.isMarkedAsUnread;
+ }
+ break;
+ }
+ case TdApi.UpdateChatIsBlocked.CONSTRUCTOR: {
+ TdApi.UpdateChatIsBlocked update = (TdApi.UpdateChatIsBlocked) object;
+ TdApi.Chat chat = chats.get(update.chatId);
+ synchronized (chat) {
+ chat.isBlocked = update.isBlocked;
+ }
+ break;
+ }
+ case TdApi.UpdateChatHasScheduledMessages.CONSTRUCTOR: {
+ TdApi.UpdateChatHasScheduledMessages update = (TdApi.UpdateChatHasScheduledMessages) object;
+ TdApi.Chat chat = chats.get(update.chatId);
+ synchronized (chat) {
+ chat.hasScheduledMessages = update.hasScheduledMessages;
}
break;
}
@@ -530,4 +601,85 @@ public final class Example {
}
}
}
-} \ No newline at end of file
+
+ private static class LogMessageHandler implements Client.LogMessageHandler {
+ @Override
+ public void onLogMessage(int verbosityLevel, String message) {
+ if (verbosityLevel == 0) {
+ onFatalError(message);
+ return;
+ }
+ System.err.println(message);
+ }
+ }
+
+ private static void onFatalError(String errorMessage) {
+ final class ThrowError implements Runnable {
+ private final String errorMessage;
+ private final AtomicLong errorThrowTime;
+
+ private ThrowError(String errorMessage, AtomicLong errorThrowTime) {
+ this.errorMessage = errorMessage;
+ this.errorThrowTime = errorThrowTime;
+ }
+
+ @Override
+ public void run() {
+ if (isDatabaseBrokenError(errorMessage) || isDiskFullError(errorMessage) || isDiskError(errorMessage)) {
+ processExternalError();
+ return;
+ }
+
+ errorThrowTime.set(System.currentTimeMillis());
+ throw new ClientError("TDLib fatal error: " + errorMessage);
+ }
+
+ private void processExternalError() {
+ errorThrowTime.set(System.currentTimeMillis());
+ throw new ExternalClientError("Fatal error: " + errorMessage);
+ }
+
+ final class ClientError extends Error {
+ private ClientError(String message) {
+ super(message);
+ }
+ }
+
+ final class ExternalClientError extends Error {
+ public ExternalClientError(String message) {
+ super(message);
+ }
+ }
+
+ private boolean isDatabaseBrokenError(String message) {
+ return message.contains("Wrong key or database is corrupted") ||
+ message.contains("SQL logic error or missing database") ||
+ message.contains("database disk image is malformed") ||
+ message.contains("file is encrypted or is not a database") ||
+ message.contains("unsupported file format") ||
+ message.contains("Database was corrupted and deleted during execution and can't be recreated");
+ }
+
+ private boolean isDiskFullError(String message) {
+ return message.contains("PosixError : No space left on device") ||
+ message.contains("database or disk is full");
+ }
+
+ private boolean isDiskError(String message) {
+ return message.contains("I/O error") || message.contains("Structure needs cleaning");
+ }
+ }
+
+ final AtomicLong errorThrowTime = new AtomicLong(Long.MAX_VALUE);
+ new Thread(new ThrowError(errorMessage, errorThrowTime), "TDLib fatal error thread").start();
+
+ // wait at least 10 seconds after the error is thrown
+ while (errorThrowTime.get() >= System.currentTimeMillis() - 10000) {
+ try {
+ Thread.sleep(1000 /* milliseconds */);
+ } catch (InterruptedException ignore) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/example/java/td_jni.cpp b/protocols/Telegram/tdlib/td/example/java/td_jni.cpp
index b9ba74a402..15c60a9545 100644
--- a/protocols/Telegram/tdlib/td/example/java/td_jni.cpp
+++ b/protocols/Telegram/tdlib/td/example/java/td_jni.cpp
@@ -1,11 +1,10 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <td/telegram/Client.h>
-#include <td/telegram/Log.h>
#include <td/telegram/td_api.h>
#include <td/tl/tl_jni_object.h>
@@ -26,28 +25,35 @@ static td::td_api::object_ptr<td::td_api::Function> fetch_function(JNIEnv *env,
return result;
}
-static td::Client *get_client(jlong client_id) {
- return reinterpret_cast<td::Client *>(static_cast<std::uintptr_t>(client_id));
+static td::ClientManager *get_manager() {
+ return td::ClientManager::get_manager_singleton();
}
-static jlong Client_createNativeClient(JNIEnv *env, jclass clazz) {
- return static_cast<jlong>(reinterpret_cast<std::uintptr_t>(new td::Client()));
+static jint Client_createNativeClient(JNIEnv *env, jclass clazz) {
+ return static_cast<jint>(get_manager()->create_client_id());
}
-static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jlong client_id, jlong id, jobject function) {
- get_client(client_id)->send({static_cast<std::uint64_t>(id), fetch_function(env, function)});
+static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jint client_id, jlong id, jobject function) {
+ get_manager()->send(static_cast<std::int32_t>(client_id), static_cast<std::uint64_t>(id),
+ fetch_function(env, function));
}
-static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jlong client_id, jlongArray ids, jobjectArray events,
- jdouble timeout) {
- auto client = get_client(client_id);
- jsize events_size = env->GetArrayLength(ids); // ids and events size must be of equal size
+static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jintArray client_ids, jlongArray ids,
+ jobjectArray events, jdouble timeout) {
+ jsize events_size = env->GetArrayLength(ids); // client_ids, ids and events must be of equal size
+ if (events_size == 0) {
+ return 0;
+ }
jsize result_size = 0;
- auto response = client->receive(timeout);
- while (response.object && result_size < events_size) {
- jlong result_id = static_cast<jlong>(response.id);
- env->SetLongArrayRegion(ids, result_size, 1, &result_id);
+ auto *manager = get_manager();
+ auto response = manager->receive(timeout);
+ while (response.object) {
+ auto client_id = static_cast<jint>(response.client_id);
+ env->SetIntArrayRegion(client_ids, result_size, 1, &client_id);
+
+ auto request_id = static_cast<jlong>(response.request_id);
+ env->SetLongArrayRegion(ids, result_size, 1, &request_id);
jobject object;
response.object->store(env, object);
@@ -55,33 +61,21 @@ static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jlong client_i
env->DeleteLocalRef(object);
result_size++;
- response = client->receive(0);
+ if (result_size == events_size) {
+ break;
+ }
+
+ response = manager->receive(0);
}
return result_size;
}
static jobject Client_nativeClientExecute(JNIEnv *env, jclass clazz, jobject function) {
jobject result;
- td::Client::execute({0, fetch_function(env, function)}).object->store(env, result);
+ td::ClientManager::execute(fetch_function(env, function))->store(env, result);
return result;
}
-static void Client_destroyNativeClient(JNIEnv *env, jclass clazz, jlong client_id) {
- delete get_client(client_id);
-}
-
-static void Log_setVerbosityLevel(JNIEnv *env, jclass clazz, jint new_log_verbosity_level) {
- td::Log::set_verbosity_level(static_cast<int>(new_log_verbosity_level));
-}
-
-static jboolean Log_setFilePath(JNIEnv *env, jclass clazz, jstring file_path) {
- return td::Log::set_file_path(td::jni::from_jstring(env, file_path)) ? JNI_TRUE : JNI_FALSE;
-}
-
-static void Log_setMaxFileSize(JNIEnv *env, jclass clazz, jlong max_file_size) {
- td::Log::set_max_file_size(max_file_size);
-}
-
static jstring Object_toString(JNIEnv *env, jobject object) {
return td::jni::to_jstring(env, to_string(td::td_api::Object::fetch(env, object)));
}
@@ -92,17 +86,52 @@ static jstring Function_toString(JNIEnv *env, jobject object) {
static constexpr jint JAVA_VERSION = JNI_VERSION_1_6;
static JavaVM *java_vm;
-static jclass log_class;
+static jobject log_message_handler;
-static void on_fatal_error(const char *error_message) {
+static void on_log_message(int verbosity_level, const char *log_message) {
auto env = td::jni::get_jni_env(java_vm, JAVA_VERSION);
- jmethodID on_fatal_error_method = env->GetStaticMethodID(log_class, "onFatalError", "(Ljava/lang/String;)V");
- if (env && on_fatal_error_method) {
- jstring error_str = td::jni::to_jstring(env.get(), error_message);
- env->CallStaticVoidMethod(log_class, on_fatal_error_method, error_str);
- if (error_str) {
- env->DeleteLocalRef(error_str);
+ if (env == nullptr) {
+ return;
+ }
+
+ jobject handler = env->NewLocalRef(log_message_handler);
+ if (!handler) {
+ return;
+ }
+
+ jclass handler_class = env->GetObjectClass(handler);
+ if (handler_class) {
+ jmethodID on_log_message_method = env->GetMethodID(handler_class, "onLogMessage", "(ILjava/lang/String;)V");
+ if (on_log_message_method) {
+ jstring log_message_str = td::jni::to_jstring(env.get(), log_message);
+ if (log_message_str) {
+ env->CallVoidMethod(handler, on_log_message_method, static_cast<jint>(verbosity_level), log_message_str);
+ env->DeleteLocalRef((jobject)log_message_str);
+ }
+ }
+ env->DeleteLocalRef((jobject)handler_class);
+ }
+
+ env->DeleteLocalRef(handler);
+}
+
+static void Client_nativeClientSetLogMessageHandler(JNIEnv *env, jclass clazz, jint max_verbosity_level,
+ jobject new_log_message_handler) {
+ if (log_message_handler) {
+ td::ClientManager::set_log_message_callback(0, nullptr);
+ jobject old_log_message_handler = log_message_handler;
+ log_message_handler = jobject();
+ env->DeleteGlobalRef(old_log_message_handler);
+ }
+
+ if (new_log_message_handler) {
+ log_message_handler = env->NewGlobalRef(new_log_message_handler);
+ if (!log_message_handler) {
+ // out of memory
+ return;
}
+
+ td::ClientManager::set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message);
}
}
@@ -120,21 +149,17 @@ static jint register_native(JavaVM *vm) {
};
auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/Client");
- log_class = td::jni::get_jclass(env, PACKAGE_NAME "/Log");
auto object_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Object");
auto function_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Function");
#define TD_OBJECT "L" PACKAGE_NAME "/TdApi$Object;"
#define TD_FUNCTION "L" PACKAGE_NAME "/TdApi$Function;"
- register_method(client_class, "createNativeClient", "()J", Client_createNativeClient);
- register_method(client_class, "nativeClientSend", "(JJ" TD_FUNCTION ")V", Client_nativeClientSend);
- register_method(client_class, "nativeClientReceive", "(J[J[" TD_OBJECT "D)I", Client_nativeClientReceive);
+ register_method(client_class, "createNativeClient", "()I", Client_createNativeClient);
+ register_method(client_class, "nativeClientSend", "(IJ" TD_FUNCTION ")V", Client_nativeClientSend);
+ register_method(client_class, "nativeClientReceive", "([I[J[" TD_OBJECT "D)I", Client_nativeClientReceive);
register_method(client_class, "nativeClientExecute", "(" TD_FUNCTION ")" TD_OBJECT, Client_nativeClientExecute);
- register_method(client_class, "destroyNativeClient", "(J)V", Client_destroyNativeClient);
-
- register_method(log_class, "setVerbosityLevel", "(I)V", Log_setVerbosityLevel);
- register_method(log_class, "setFilePath", "(Ljava/lang/String;)Z", Log_setFilePath);
- register_method(log_class, "setMaxFileSize", "(J)V", Log_setMaxFileSize);
+ register_method(client_class, "nativeClientSetLogMessageHandler", "(IL" PACKAGE_NAME "/Client$LogMessageHandler;)V",
+ Client_nativeClientSetLogMessageHandler);
register_method(object_class, "toString", "()Ljava/lang/String;", Object_toString);
@@ -145,7 +170,6 @@ static jint register_native(JavaVM *vm) {
td::jni::init_vars(env, PACKAGE_NAME);
td::td_api::Object::init_jni_vars(env, PACKAGE_NAME);
td::td_api::Function::init_jni_vars(env, PACKAGE_NAME);
- td::Log::set_fatal_error_callback(on_fatal_error);
return JAVA_VERSION;
}
diff --git a/protocols/Telegram/tdlib/td/example/python/README.md b/protocols/Telegram/tdlib/td/example/python/README.md
index c4a4ad3768..c3c171d906 100644
--- a/protocols/Telegram/tdlib/td/example/python/README.md
+++ b/protocols/Telegram/tdlib/td/example/python/README.md
@@ -7,5 +7,5 @@ Then you can run the example:
python tdjson_example.py
```
-Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html),
-[td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.
+Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html)
+and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.
diff --git a/protocols/Telegram/tdlib/td/example/python/tdjson_example.py b/protocols/Telegram/tdlib/td/example/python/tdjson_example.py
index d7b2e86fbb..ee8a92a158 100644
--- a/protocols/Telegram/tdlib/td/example/python/tdjson_example.py
+++ b/protocols/Telegram/tdlib/td/example/python/tdjson_example.py
@@ -1,106 +1,142 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
+#
+# Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com),
+# Pellegrino Prevete (pellegrinoprevete@gmail.com) 2014-2022
+#
+# Distributed under the Boost Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#
from ctypes.util import find_library
from ctypes import *
import json
import sys
# load shared library
-tdjson_path = find_library("tdjson") or "tdjson.dll"
+tdjson_path = find_library('tdjson') or 'tdjson.dll'
if tdjson_path is None:
- print('can\'t find tdjson library')
- quit()
+ sys.exit("Can't find 'tdjson' library")
tdjson = CDLL(tdjson_path)
# load TDLib functions from shared library
-td_json_client_create = tdjson.td_json_client_create
-td_json_client_create.restype = c_void_p
-td_json_client_create.argtypes = []
+_td_create_client_id = tdjson.td_create_client_id
+_td_create_client_id.restype = c_int
+_td_create_client_id.argtypes = []
-td_json_client_receive = tdjson.td_json_client_receive
-td_json_client_receive.restype = c_char_p
-td_json_client_receive.argtypes = [c_void_p, c_double]
+_td_receive = tdjson.td_receive
+_td_receive.restype = c_char_p
+_td_receive.argtypes = [c_double]
-td_json_client_send = tdjson.td_json_client_send
-td_json_client_send.restype = None
-td_json_client_send.argtypes = [c_void_p, c_char_p]
+_td_send = tdjson.td_send
+_td_send.restype = None
+_td_send.argtypes = [c_int, c_char_p]
-td_json_client_execute = tdjson.td_json_client_execute
-td_json_client_execute.restype = c_char_p
-td_json_client_execute.argtypes = [c_void_p, c_char_p]
+_td_execute = tdjson.td_execute
+_td_execute.restype = c_char_p
+_td_execute.argtypes = [c_char_p]
-td_json_client_destroy = tdjson.td_json_client_destroy
-td_json_client_destroy.restype = None
-td_json_client_destroy.argtypes = [c_void_p]
+log_message_callback_type = CFUNCTYPE(None, c_int, c_char_p)
-td_set_log_file_path = tdjson.td_set_log_file_path
-td_set_log_file_path.restype = c_int
-td_set_log_file_path.argtypes = [c_char_p]
+_td_set_log_message_callback = tdjson.td_set_log_message_callback
+_td_set_log_message_callback.restype = None
+_td_set_log_message_callback.argtypes = [c_int, log_message_callback_type]
-td_set_log_max_file_size = tdjson.td_set_log_max_file_size
-td_set_log_max_file_size.restype = None
-td_set_log_max_file_size.argtypes = [c_longlong]
-
-td_set_log_verbosity_level = tdjson.td_set_log_verbosity_level
-td_set_log_verbosity_level.restype = None
-td_set_log_verbosity_level.argtypes = [c_int]
+# initialize TDLib log with desired parameters
+@log_message_callback_type
+def on_log_message_callback(verbosity_level, message):
+ if verbosity_level == 0:
+ sys.exit('TDLib fatal error: %r' % message)
-fatal_error_callback_type = CFUNCTYPE(None, c_char_p)
+def td_execute(query):
+ query = json.dumps(query).encode('utf-8')
+ result = _td_execute(query)
+ if result:
+ result = json.loads(result.decode('utf-8'))
+ return result
-td_set_log_fatal_error_callback = tdjson.td_set_log_fatal_error_callback
-td_set_log_fatal_error_callback.restype = None
-td_set_log_fatal_error_callback.argtypes = [fatal_error_callback_type]
+_td_set_log_message_callback(2, on_log_message_callback)
-# initialize TDLib log with desired parameters
-def on_fatal_error_callback(error_message):
- print('TDLib fatal error: ', error_message)
+# setting TDLib log verbosity level to 1 (errors)
+print(str(td_execute({'@type': 'setLogVerbosityLevel', 'new_verbosity_level': 1, '@extra': 1.01234})).encode('utf-8'))
-td_set_log_verbosity_level(2)
-c_on_fatal_error_callback = fatal_error_callback_type(on_fatal_error_callback)
-td_set_log_fatal_error_callback(c_on_fatal_error_callback)
# create client
-client = td_json_client_create()
+client_id = _td_create_client_id()
# simple wrappers for client usage
def td_send(query):
query = json.dumps(query).encode('utf-8')
- td_json_client_send(client, query)
+ _td_send(client_id, query)
def td_receive():
- result = td_json_client_receive(client, 1.0)
+ result = _td_receive(1.0)
if result:
result = json.loads(result.decode('utf-8'))
return result
-def td_execute(query):
- query = json.dumps(query).encode('utf-8')
- result = td_json_client_execute(client, query)
- if result:
- result = json.loads(result.decode('utf-8'))
- return result
-
-# testing TDLib execute method
-print(td_execute({'@type': 'getTextEntities', 'text': '@telegram /test_command https://telegram.org telegram.me', '@extra': ['5', 7.0]}))
+# another test for TDLib execute method
+print(str(td_execute({'@type': 'getTextEntities', 'text': '@telegram /test_command https://telegram.org telegram.me', '@extra': ['5', 7.0, 'a']})).encode('utf-8'))
-# testing TDLib send method
+# start the client by sending request to it
td_send({'@type': 'getAuthorizationState', '@extra': 1.01234})
# main events cycle
while True:
event = td_receive()
if event:
- # if client is closed, we need to destroy it and create new client
- if event['@type'] is 'updateAuthorizationState' and event['authorization_state']['@type'] is 'authorizationStateClosed':
- break
+ # process authorization states
+ if event['@type'] == 'updateAuthorizationState':
+ auth_state = event['authorization_state']
+
+ # if client is closed, we need to destroy it and create new client
+ if auth_state['@type'] == 'authorizationStateClosed':
+ break
+
+ # set TDLib parameters
+ # you MUST obtain your own api_id and api_hash at https://my.telegram.org
+ # and use them in the setTdlibParameters call
+ if auth_state['@type'] == 'authorizationStateWaitTdlibParameters':
+ td_send({'@type': 'setTdlibParameters',
+ 'database_directory': 'tdlib',
+ 'use_message_database': True,
+ 'use_secret_chats': True,
+ 'api_id': 94575,
+ 'api_hash': 'a3406de8d171bb422bb6ddf3bbd800e2',
+ 'system_language_code': 'en',
+ 'device_model': 'Desktop',
+ 'application_version': '1.0',
+ 'enable_storage_optimizer': True})
+
+ # enter phone number to log in
+ if auth_state['@type'] == 'authorizationStateWaitPhoneNumber':
+ phone_number = input('Please enter your phone number: ')
+ td_send({'@type': 'setAuthenticationPhoneNumber', 'phone_number': phone_number})
+
+ # enter email address to log in
+ if auth_state['@type'] == 'authorizationStateWaitEmailAddress':
+ email_address = input('Please enter your email address: ')
+ td_send({'@type': 'setAuthenticationEmailAddress', 'email_address': email_address})
+
+ # wait for email authorization code
+ if auth_state['@type'] == 'authorizationStateWaitEmailCode':
+ code = input('Please enter the email authentication code you received: ')
+ td_send({'@type': 'checkAuthenticationEmailCode',
+ 'code': {'@type': 'emailAddressAuthenticationCode', 'code' : 'code'}})
+
+ # wait for authorization code
+ if auth_state['@type'] == 'authorizationStateWaitCode':
+ code = input('Please enter the authentication code you received: ')
+ td_send({'@type': 'checkAuthenticationCode', 'code': code})
+
+ # wait for first and last name for new users
+ if auth_state['@type'] == 'authorizationStateWaitRegistration':
+ first_name = input('Please enter your first name: ')
+ last_name = input('Please enter your last name: ')
+ td_send({'@type': 'registerUser', 'first_name': first_name, 'last_name': last_name})
+
+ # wait for password if present
+ if auth_state['@type'] == 'authorizationStateWaitPassword':
+ password = input('Please enter your password: ')
+ td_send({'@type': 'checkAuthenticationPassword', 'password': password})
# handle an incoming update or an answer to a previously sent request
- print(event)
+ print(str(event).encode('utf-8'))
sys.stdout.flush()
-
-# destroy client when it is closed and isn't needed anymore
-td_json_client_destroy(client)
diff --git a/protocols/Telegram/tdlib/td/example/ruby/Gemfile b/protocols/Telegram/tdlib/td/example/ruby/Gemfile
deleted file mode 100644
index 3a38ffc0a3..0000000000
--- a/protocols/Telegram/tdlib/td/example/ruby/Gemfile
+++ /dev/null
@@ -1,3 +0,0 @@
-source 'https://rubygems.org'
-
-gem 'tdlib-ruby'
diff --git a/protocols/Telegram/tdlib/td/example/ruby/Gemfile.lock b/protocols/Telegram/tdlib/td/example/ruby/Gemfile.lock
deleted file mode 100644
index 22954eddfb..0000000000
--- a/protocols/Telegram/tdlib/td/example/ruby/Gemfile.lock
+++ /dev/null
@@ -1,17 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- concurrent-ruby (1.0.5)
- dry-configurable (0.7.0)
- concurrent-ruby (~> 1.0)
- tdlib-ruby (0.2.0)
- dry-configurable (~> 0.7)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- tdlib-ruby
-
-BUNDLED WITH
- 1.16.1
diff --git a/protocols/Telegram/tdlib/td/example/ruby/example.rb b/protocols/Telegram/tdlib/td/example/ruby/example.rb
deleted file mode 100644
index 4b29dfd53a..0000000000
--- a/protocols/Telegram/tdlib/td/example/ruby/example.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'tdlib-ruby'
-
-TD.configure do |config|
- config.lib_path = 'path/to/dir_containing_lobtdjson'
-
- # You should obtain your own api_id and api_hash from https://my.telegram.org/apps
- config.client.api_id = 12345
- config.client.api_hash = '1234567890abcdefghigklmnopqrstuv'
-end
-
-TD::Api.set_log_verbosity_level(1)
-
-client = TD::Client.new
-
-begin
- state = nil
-
- client.on('updateAuthorizationState') do |update|
- next unless update.dig('authorization_state', '@type') == 'authorizationStateWaitPhoneNumber'
- state = :wait_phone
- end
-
- client.on('updateAuthorizationState') do |update|
- next unless update.dig('authorization_state', '@type') == 'authorizationStateWaitCode'
- state = :wait_code
- end
-
- client.on('updateAuthorizationState') do |update|
- next unless update.dig('authorization_state', '@type') == 'authorizationStateReady'
- state = :ready
- end
-
- loop do
- case state
- when :wait_phone
- p 'Please, enter your phone number:'
- phone = STDIN.gets.strip
- params = {
- '@type' => 'setAuthenticationPhoneNumber',
- 'phone_number' => phone
- }
- client.broadcast_and_receive(params)
- when :wait_code
- p 'Please, enter code from SMS:'
- code = STDIN.gets.strip
- params = {
- '@type' => 'checkAuthenticationCode',
- 'code' => code
- }
- client.broadcast_and_receive(params)
- when :ready
- @me = client.broadcast_and_receive('@type' => 'getMe')
- break
- end
- end
-
-ensure
- client.close
-end
-
-p @me
diff --git a/protocols/Telegram/tdlib/td/example/swift/README.md b/protocols/Telegram/tdlib/td/example/swift/README.md
index 6a333df766..7c57dcf64d 100644
--- a/protocols/Telegram/tdlib/td/example/swift/README.md
+++ b/protocols/Telegram/tdlib/td/example/swift/README.md
@@ -1,4 +1,4 @@
-# TDLib swift MacOS example
+# TDLib swift macOS example
TDLib should be prebuilt and installed to local subdirectory `td/`:
```
@@ -11,5 +11,5 @@ cmake --build . --target install
Then you can open and build the example with the latest Xcode.
-Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html),
-[td_log](https://core.telegram.org/tdlib/docs/td__log_8h.html) and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.
+Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html)
+and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.
diff --git a/protocols/Telegram/tdlib/td/example/swift/src/main.swift b/protocols/Telegram/tdlib/td/example/swift/src/main.swift
index ac81c632b8..9e35133958 100644
--- a/protocols/Telegram/tdlib/td/example/swift/src/main.swift
+++ b/protocols/Telegram/tdlib/td/example/swift/src/main.swift
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,8 +9,7 @@ import Foundation
// TDLib Client Swift binding
class TdClient {
- typealias Client = UnsafeMutableRawPointer
- var client = td_json_client_create()!
+ var client_id = td_create_client_id()
let tdlibMainLoop = DispatchQueue(label: "TDLib")
let tdlibQueryQueue = DispatchQueue(label: "TDLibQuery")
var queryF = Dictionary<Int64, (Dictionary<String,Any>)->()>()
@@ -27,7 +26,7 @@ class TdClient {
self.queryF[nextQueryId] = f
self.queryId = nextQueryId
}
- td_json_client_send(self.client, to_json(newQuery))
+ td_send(self.client_id, to_json(newQuery))
}
}
@@ -46,7 +45,6 @@ class TdClient {
}
deinit {
- td_json_client_destroy(client)
}
func run(updateHandler: @escaping (Dictionary<String,Any>)->()) {
@@ -54,7 +52,7 @@ class TdClient {
tdlibMainLoop.async { [weak self] in
while (true) {
if let s = self {
- if let res = td_json_client_receive(s.client, 10) {
+ if let res = td_receive(10) {
let event = String(cString: res)
s.queryResultAsync(event)
}
@@ -91,11 +89,11 @@ func to_json(_ obj: Any) -> String {
}
-// An example of usage
-td_set_log_verbosity_level(1);
-
var client = TdClient()
+// start the client by sending request to it
+client.queryAsync(query: ["@type":"getOption", "name":"version"])
+
func myReadLine() -> String {
while (true) {
if let line = readLine() {
@@ -109,42 +107,47 @@ func updateAuthorizationState(authorizationState: Dictionary<String, Any>) {
case "authorizationStateWaitTdlibParameters":
client.queryAsync(query:[
"@type":"setTdlibParameters",
- "parameters":[
- "database_directory":"tdlib",
- "use_message_database":true,
- "use_secret_chats":true,
- "api_id":94575,
- "api_hash":"a3406de8d171bb422bb6ddf3bbd800e2",
- "system_language_code":"en",
- "device_model":"Desktop",
- "system_version":"Unknown",
- "application_version":"1.0",
- "enable_storage_optimizer":true
- ]
+ "database_directory":"tdlib",
+ "use_message_database":true,
+ "use_secret_chats":true,
+ "api_id":94575,
+ "api_hash":"a3406de8d171bb422bb6ddf3bbd800e2",
+ "system_language_code":"en",
+ "device_model":"Desktop",
+ "application_version":"1.0",
+ "enable_storage_optimizer":true
]);
- case "authorizationStateWaitEncryptionKey":
- client.queryAsync(query: ["@type":"checkDatabaseEncryptionKey", "key":"cucumber"])
-
case "authorizationStateWaitPhoneNumber":
- print("Enter your phone: ")
- let phone = myReadLine()
- client.queryAsync(query:["@type":"setAuthenticationPhoneNumber", "phone_number":phone], f:checkAuthenticationError)
+ print("Enter your phone number: ")
+ let phone_number = myReadLine()
+ client.queryAsync(query:["@type":"setAuthenticationPhoneNumber", "phone_number":phone_number], f:checkAuthenticationError)
+
+ case "authorizationStateWaitEmailAddress":
+ print("Enter your email address: ")
+ let email_address = myReadLine()
+ client.queryAsync(query:["@type":"setAuthenticationEmailAddress", "email_address":email_address], f:checkAuthenticationError)
+
+ case "authorizationStateWaitEmailCode":
+ var code: String = ""
+ print("Enter email code: ")
+ code = myReadLine()
+ client.queryAsync(query:["@type":"checkAuthenticationEmailCode", "code":["@type":"emailAddressAuthenticationCode", "code":code]], f:checkAuthenticationError)
case "authorizationStateWaitCode":
- var first_name: String = ""
- var last_name: String = ""
var code: String = ""
- if let is_registered = authorizationState["is_registered"] as? Bool, is_registered {
- } else {
- print("Enter your first name: ")
- first_name = myReadLine()
- print("Enter your last name: ")
- last_name = myReadLine()
- }
print("Enter (SMS) code: ")
code = myReadLine()
- client.queryAsync(query:["@type":"checkAuthenticationCode", "code":code, "first_name":first_name, "last_name":last_name], f:checkAuthenticationError)
+ client.queryAsync(query:["@type":"checkAuthenticationCode", "code":code], f:checkAuthenticationError)
+
+ case "authorizationStateWaitRegistration":
+ var first_name: String = ""
+ var last_name: String = ""
+ print("Enter your first name: ")
+ first_name = myReadLine()
+ print("Enter your last name: ")
+ last_name = myReadLine()
+ client.queryAsync(query:["@type":"registerUser", "first_name":first_name, "last_name":last_name], f:checkAuthenticationError)
case "authorizationStateWaitPassword":
print("Enter password: ")
@@ -154,8 +157,17 @@ func updateAuthorizationState(authorizationState: Dictionary<String, Any>) {
case "authorizationStateReady":
()
+ case "authorizationStateLoggingOut":
+ print("Logging out...")
+
+ case "authorizationStateClosing":
+ print("Closing...")
+
+ case "authorizationStateClosed":
+ print("Closed.")
+
default:
- assert(false, "TODO: Unknown authorization state");
+ assert(false, "TODO: Unexpected authorization state");
}
}
diff --git a/protocols/Telegram/tdlib/td/example/swift/src/td-Bridging-Header.h b/protocols/Telegram/tdlib/td/example/swift/src/td-Bridging-Header.h
index 434ab2e4a4..407a876644 100644
--- a/protocols/Telegram/tdlib/td/example/swift/src/td-Bridging-Header.h
+++ b/protocols/Telegram/tdlib/td/example/swift/src/td-Bridging-Header.h
@@ -1,11 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/example/swift/td.xcodeproj/project.pbxproj b/protocols/Telegram/tdlib/td/example/swift/td.xcodeproj/project.pbxproj
index 7b774c421c..e7a4bb9085 100644
--- a/protocols/Telegram/tdlib/td/example/swift/td.xcodeproj/project.pbxproj
+++ b/protocols/Telegram/tdlib/td/example/swift/td.xcodeproj/project.pbxproj
@@ -7,17 +7,19 @@
objects = {
/* Begin PBXBuildFile section */
- 1F65E3A42031BF6A00F79763 /* libtdjson.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F65E3A32031BF6A00F79763 /* libtdjson.dylib */; };
1F65E3A92031C0F000F79763 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F65E3A82031C0F000F79763 /* main.swift */; };
+ 425922F020C8353500F06B38 /* libtdjson.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F65E3A32031BF6A00F79763 /* libtdjson.dylib */; };
+ 425922F120C844E700F06B38 /* libtdjson.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1F65E3A32031BF6A00F79763 /* libtdjson.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
1FCE2CEF1EC5E1B50061661A /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = /usr/share/man/man1/;
- dstSubfolderSpec = 0;
+ buildActionMask = 8;
+ dstPath = "";
+ dstSubfolderSpec = 11;
files = (
+ 425922F120C844E700F06B38 /* libtdjson.dylib in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 1;
};
@@ -35,7 +37,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 1F65E3A42031BF6A00F79763 /* libtdjson.dylib in Frameworks */,
+ 425922F020C8353500F06B38 /* libtdjson.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -252,7 +254,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/lib";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/td/lib";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/td/lib",
@@ -270,7 +272,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/lib";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/td/lib";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/td/lib",
diff --git a/protocols/Telegram/tdlib/td/example/uwp/README.md b/protocols/Telegram/tdlib/td/example/uwp/README.md
index e7bb9b821b..c0b4de723f 100644
--- a/protocols/Telegram/tdlib/td/example/uwp/README.md
+++ b/protocols/Telegram/tdlib/td/example/uwp/README.md
@@ -7,22 +7,22 @@ This is an example of building TDLib SDK for Universal Windows Platform and an e
* Download and install Microsoft Visual Studio 2015+ with Windows 10 SDK. We recommend to use the latest available versions of Microsoft Visual Studio and Windows 10 SDK.
* Download and install [CMake](https://cmake.org/download/).
* Install [vcpkg](https://github.com/Microsoft/vcpkg#quick-start) or update it to the latest version using `vcpkg update` and following received instructions.
-* Install `zlib` and `openssl` for all UWP architectures using `vcpkg`:
+* Install `zlib` and `openssl` for all UWP architectures and `gperf` for x86 using `vcpkg`:
```
-C:\src\vcpkg> .\vcpkg.exe install openssl:arm-uwp openssl:x64-uwp openssl:x86-uwp zlib:arm-uwp zlib:x64-uwp zlib:x86-uwp
+cd <path to vcpkg>
+.\vcpkg.exe install gperf:x86-windows openssl:arm-uwp openssl:arm64-uwp openssl:x64-uwp openssl:x86-uwp zlib:arm-uwp zlib:arm64-uwp zlib:x64-uwp zlib:x86-uwp
```
* (Optional. For XML documentation generation.) Download [PHP](https://windows.php.net/download#php-7.2). Add the path to php.exe to the PATH environment variable.
-* Download and install [gperf](https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/). Add the path to gperf.exe to the PATH environment variable.
* Download and install [7-Zip](http://www.7-zip.org/download.html) archiver, which is used by the `build.ps1` script to create a Telegram.Td.UWP Visual Studio Extension. Add the path to 7z.exe to the PATH environment variable.
Alternatively `build.ps1` supports compressing using [WinRAR](https://en.wikipedia.org/wiki/WinRAR) with option `-compress winrar` and compressing using [zip](http://gnuwin32.sourceforge.net/packages/zip.htm) with `-compress zip`.
-* Build `TDLib` using provided `build.ps1` script (TDLib should be built 6 times for multiple platforms in Debug and Release configurations, so it make take few hours). Pass path to vcpkg.exe as `-vcpkg-root` argument:
+* Build `TDLib` using provided `build.ps1` script (TDLib should be built 6 times for multiple platforms in Debug and Release configurations, so it make take few hours). Pass path to vcpkg.exe as `-vcpkg-root` argument, for example:
```
-powershell -ExecutionPolicy ByPass .\build.ps1 -vcpkg_root C:\src\vcpkg
+powershell -ExecutionPolicy ByPass .\build.ps1 -vcpkg_root C:\vcpkg
```
-If you need to restart the build from scratch, call `.\build.ps1 -mode clean` first.
+If you need to restart the build from scratch, call `.\build.ps1 -vcpkg_root C:\vcpkg -mode clean` first.
* Install Visual Studio Extension "TDLib for Universal Windows Platform" located at `build-uwp\vsix\tdlib.vsix`, which was created on the previous step by `build.ps1` script.
-Now `TDLib` can be freely used from any UWP project, built in Visual Studio.
+Now `TDLib` can be used from any UWP project, built in Visual Studio.
## Example of usage
-The `app/` directory contains a simple example of a C# application for Universal Windows Platform. Just open it with Visual Studio 2015 or 2017 and run.
+The `app/` directory contains a simple example of a C# application for Universal Windows Platform. Just open it with Visual Studio 2015 or later and run.
diff --git a/protocols/Telegram/tdlib/td/example/uwp/app/App.xaml.cs b/protocols/Telegram/tdlib/td/example/uwp/app/App.xaml.cs
index 0ed0f96812..5bfc66a30f 100644
--- a/protocols/Telegram/tdlib/td/example/uwp/app/App.xaml.cs
+++ b/protocols/Telegram/tdlib/td/example/uwp/app/App.xaml.cs
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/example/uwp/app/MainPage.xaml.cs b/protocols/Telegram/tdlib/td/example/uwp/app/MainPage.xaml.cs
index 9bdf5070ee..4c42e27676 100644
--- a/protocols/Telegram/tdlib/td/example/uwp/app/MainPage.xaml.cs
+++ b/protocols/Telegram/tdlib/td/example/uwp/app/MainPage.xaml.cs
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -18,7 +18,7 @@ namespace TdApp
{
public System.Collections.ObjectModel.ObservableCollection<string> Items { get; set; }
- private static MyClientResultHandler _handler;
+ private MyClientResultHandler _handler;
public MainPage()
{
@@ -27,31 +27,25 @@ namespace TdApp
Items = new System.Collections.ObjectModel.ObservableCollection<string>();
_handler = new MyClientResultHandler(this);
+ Td.Client.Execute(new TdApi.SetLogVerbosityLevel(0));
+ Td.Client.Execute(new TdApi.SetLogStream(new TdApi.LogStreamFile(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "log"), 1 << 27, false)));
+ Td.Client.SetLogMessageCallback(100, LogMessageCallback);
System.Threading.Tasks.Task.Run(() =>
{
- try
- {
- Td.Log.SetFilePath(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "log"));
- _client = Td.Client.Create(_handler);
- var parameters = new TdApi.TdlibParameters();
- parameters.DatabaseDirectory = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
- parameters.UseSecretChats = true;
- parameters.UseMessageDatabase = true;
- parameters.ApiId = 94575;
- parameters.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
- parameters.SystemLanguageCode = "en";
- parameters.DeviceModel = "en";
- parameters.SystemVersion = "en";
- parameters.ApplicationVersion = "1.0.0";
- _client.Send(new TdApi.SetTdlibParameters(parameters), null);
- _client.Send(new TdApi.CheckDatabaseEncryptionKey(), null);
- _client.Run();
- }
- catch (Exception ex)
- {
- Print(ex.ToString());
- }
+ Td.Client.Run();
});
+
+ _client = Td.Client.Create(_handler);
+ var request = new TdApi.SetTdlibParameters();
+ request.DatabaseDirectory = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
+ request.UseSecretChats = true;
+ request.UseMessageDatabase = true;
+ request.ApiId = 94575;
+ request.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
+ request.SystemLanguageCode = "en";
+ request.DeviceModel = "Desktop";
+ request.ApplicationVersion = "1.0.0";
+ _client.Send(request, null);
}
public void Print(String str)
@@ -62,7 +56,15 @@ namespace TdApp
});
}
- private static Td.Client _client;
+ private void LogMessageCallback(int verbosity_level, String str)
+ {
+ if (verbosity_level < 0) {
+ return;
+ }
+ Print(verbosity_level + ": " + str);
+ }
+
+ private Td.Client _client;
private void AcceptCommand(String command)
{
@@ -92,13 +94,25 @@ namespace TdApp
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
- _client.Send(new TdApi.SetAuthenticationPhoneNumber(args[1], false, false), _handler);
+ _client.Send(new TdApi.SetAuthenticationPhoneNumber(args[1], null), _handler);
+ }
+ else if (command.StartsWith("sae"))
+ {
+ var args = command.Split(" ".ToCharArray(), 2);
+ AcceptCommand(command);
+ _client.Send(new TdApi.SetAuthenticationEmailAddress(args[1]), _handler);
+ }
+ else if (command.StartsWith("caec"))
+ {
+ var args = command.Split(" ".ToCharArray(), 2);
+ AcceptCommand(command);
+ _client.Send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(args[1])), _handler);
}
else if (command.StartsWith("cac"))
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
- _client.Send(new TdApi.CheckAuthenticationCode(args[1], String.Empty, String.Empty), _handler);
+ _client.Send(new TdApi.CheckAuthenticationCode(args[1]), _handler);
}
else if (command.StartsWith("cap"))
{
@@ -106,6 +120,12 @@ namespace TdApp
AcceptCommand(command);
_client.Send(new TdApi.CheckAuthenticationPassword(args[1]), _handler);
}
+ else if (command.StartsWith("alm"))
+ {
+ var args = command.Split(" ".ToCharArray(), 3);
+ AcceptCommand(command);
+ _client.Send(new TdApi.AddLogMessage(Int32.Parse(args[1]), args[2]), _handler);
+ }
else if (command.StartsWith("gco"))
{
var args = command.Split(" ".ToCharArray(), 2);
@@ -116,7 +136,7 @@ namespace TdApp
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
- _client.Send(new TdApi.DownloadFile(Int32.Parse(args[1]), 1), _handler);
+ _client.Send(new TdApi.DownloadFile(Int32.Parse(args[1]), 1, 0, 0, false), _handler);
}
else if (command.StartsWith("bench"))
{
diff --git a/protocols/Telegram/tdlib/td/example/uwp/app/Properties/AssemblyInfo.cs b/protocols/Telegram/tdlib/td/example/uwp/app/Properties/AssemblyInfo.cs
index 99e2b83bea..2e008d1a29 100644
--- a/protocols/Telegram/tdlib/td/example/uwp/app/Properties/AssemblyInfo.cs
+++ b/protocols/Telegram/tdlib/td/example/uwp/app/Properties/AssemblyInfo.cs
@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("App2")]
-[assembly: AssemblyCopyright("Copyright © 2015-2018")]
+[assembly: AssemblyCopyright("Copyright © 2015-2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -26,4 +26,4 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
-[assembly: ComVisible(false)] \ No newline at end of file
+[assembly: ComVisible(false)]
diff --git a/protocols/Telegram/tdlib/td/example/uwp/app/TdApp.csproj b/protocols/Telegram/tdlib/td/example/uwp/app/TdApp.csproj
index b3d1d944d1..2b0de8df3d 100644
--- a/protocols/Telegram/tdlib/td/example/uwp/app/TdApp.csproj
+++ b/protocols/Telegram/tdlib/td/example/uwp/app/TdApp.csproj
@@ -88,7 +88,7 @@
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<ItemGroup>
- <!-- A reference to the entire .Net Framework and Windows SDK are automatically included -->
+ <!-- A reference to the entire .NET Framework and Windows SDK are automatically included -->
<Content Include="ApplicationInsights.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/protocols/Telegram/tdlib/td/example/uwp/build.ps1 b/protocols/Telegram/tdlib/td/example/uwp/build.ps1
index 71156b0a2d..587945a4b2 100644
--- a/protocols/Telegram/tdlib/td/example/uwp/build.ps1
+++ b/protocols/Telegram/tdlib/td/example/uwp/build.ps1
@@ -2,17 +2,23 @@ param (
[string]$vcpkg_root = $(throw "-vcpkg_root=<path to vcpkg> is required"),
[string]$arch = "",
[string]$mode = "all",
- [string]$compress = "7z"
+ [string]$compress = "7z",
+ [switch]$release_only = $false
)
$ErrorActionPreference = "Stop"
$vcpkg_root = Resolve-Path $vcpkg_root
$vcpkg_cmake="${vcpkg_root}\scripts\buildsystems\vcpkg.cmake"
-$arch_list = @( "x86", "x64", "arm" )
+$arch_list = @( "x86", "x64", "ARM" )
if ($arch) {
$arch_list = @(, $arch)
}
+$config_list = @( "Debug", "Release" )
+if ($release_only) {
+ $config_list = @(, "RelWithDebInfo")
+}
+$targets = @{ Debug = "Debug"; Release = "Retail"; RelWithDebInfo = "CommonConfiguration"}
$td_root = Resolve-Path "../.."
@@ -35,7 +41,7 @@ function prepare {
cd build-native
- cmake $td_root -DCMAKE_TOOLCHAIN_FILE="$vcpkg_cmake" -DTD_ENABLE_DOTNET=1
+ cmake -A Win32 -DCMAKE_TOOLCHAIN_FILE="$vcpkg_cmake" -DTD_ENABLE_DOTNET=ON "$td_root"
CheckLastExitCode
cmake --build . --target prepare_cross_compiling
CheckLastExitCode
@@ -47,7 +53,7 @@ function config {
New-Item -ItemType Directory -Force -Path build-uwp
cd build-uwp
- ForEach($arch in $arch_list) {
+ ForEach ($arch in $arch_list) {
echo "Config Arch = [$arch]"
New-Item -ItemType Directory -Force -Path $arch
cd $arch
@@ -56,7 +62,7 @@ function config {
if ($arch -eq "x86") {
$fixed_arch = "win32"
}
- cmake "$td_root" -A $fixed_arch -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_SYSTEM_NAME="WindowsStore" -DCMAKE_TOOLCHAIN_FILE="$vcpkg_cmake" -DTD_ENABLE_DOTNET=1
+ cmake -A $fixed_arch -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_SYSTEM_NAME="WindowsStore" -DCMAKE_TOOLCHAIN_FILE="$vcpkg_cmake" -DTD_ENABLE_DOTNET=ON "$td_root"
CheckLastExitCode
cd ..
}
@@ -66,11 +72,12 @@ function config {
function build {
cd build-uwp
- ForEach($arch in $arch_list) {
+ ForEach ($arch in $arch_list) {
echo "Build Arch = [$arch]"
cd $arch
- cmake --build . --config Release --target tddotnet
- cmake --build . --config Debug --target tddotnet
+ ForEach ($config in $config_list) {
+ cmake --build . --config $config --target tddotnet
+ }
cd ..
}
cd ..
@@ -85,23 +92,19 @@ function export {
cp '../`[Content_Types`].xml' vsix
cp ../LICENSE_1_0.txt vsix
- ForEach($arch in $arch_list) {
- New-Item -ItemType Directory -Force -Path vsix/DesignTime/Debug/${arch}
- New-Item -ItemType Directory -Force -Path vsix/DesignTime/Retail/${arch}
- New-Item -ItemType Directory -Force -Path vsix/Redist/Debug/${arch}
- New-Item -ItemType Directory -Force -Path vsix/Redist/Retail/${arch}
+ ForEach ($arch in $arch_list) {
New-Item -ItemType Directory -Force -Path vsix/References/CommonConfiguration/${arch}
+ ForEach ($config in $config_list) {
+ $target = $targets[$config]
- cp ${arch}/Debug/* -include "LIBEAY*","SSLEAY*","zlib*" vsix/Redist/Debug/${arch}/
- cp ${arch}/Release/* -include "LIBEAY*","SSLEAY*","zlib*" vsix/Redist/Retail/${arch}/
+ New-Item -ItemType Directory -Force -Path vsix/DesignTime/${target}/${arch}
+ cp ${arch}/${config}/Telegram.Td.lib vsix/DesignTime/${target}/${arch}/
- cp ${arch}/Debug/* -filter "Telegram.Td.*" -include "*.lib" vsix/DesignTime/Debug/${arch}/
- cp ${arch}/Release/* -filter "Telegram.Td.*" -include "*.lib" vsix/DesignTime/Retail/${arch}/
+ New-Item -ItemType Directory -Force -Path vsix/Redist/${target}/${arch}
+ cp ${arch}/${config}/* -include "SSLEAY*","LIBEAY*","libcrypto*","libssl*","zlib*","Telegram.Td.pdb","Telegram.Td.dll" vsix/Redist/${target}/${arch}/
- cp ${arch}/Debug/* -filter "Telegram.Td.*" -include "*.pdb","*.dll" vsix/Redist/Debug/${arch}/
- cp ${arch}/Release/* -filter "Telegram.Td.*" -include "*.pdb","*.dll" vsix/Redist/Retail/${arch}/
-
- cp ${arch}/Release/* -filter "Telegram.Td.*" -include "*.pri","*.winmd","*.xml" vsix/References/CommonConfiguration/${arch}/
+ cp ${arch}/${config}/* -include "Telegram.Td.pri","Telegram.Td.winmd","Telegram.Td.xml" vsix/References/CommonConfiguration/${arch}/
+ }
}
cd vsix
diff --git a/protocols/Telegram/tdlib/td/example/uwp/extension.vsixmanifest b/protocols/Telegram/tdlib/td/example/uwp/extension.vsixmanifest
index cfcfdb57af..28cacff65f 100644
--- a/protocols/Telegram/tdlib/td/example/uwp/extension.vsixmanifest
+++ b/protocols/Telegram/tdlib/td/example/uwp/extension.vsixmanifest
@@ -1,6 +1,6 @@
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
<Metadata>
- <Identity Id="Telegram.Td.UWP" Version="1.2.0" Language="en-US" Publisher="Telegram LLC" />
+ <Identity Id="Telegram.Td.UWP" Version="1.8.8" Language="en-US" Publisher="Telegram LLC" />
<DisplayName>TDLib for Universal Windows Platform</DisplayName>
<Description>TDLib is a library for building Telegram clients</Description>
<MoreInfo>https://core.telegram.org/tdlib</MoreInfo>
diff --git a/protocols/Telegram/tdlib/td/example/web/.gitignore b/protocols/Telegram/tdlib/td/example/web/.gitignore
new file mode 100644
index 0000000000..0a213668d8
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/.gitignore
@@ -0,0 +1,5 @@
+OpenSSL_*.tar.gz
+openssl-OpenSSL_*/
+tdweb/dist/
+tdweb/node_modules/
+tdweb/src/prebuilt/
diff --git a/protocols/Telegram/tdlib/td/example/web/README.md b/protocols/Telegram/tdlib/td/example/web/README.md
new file mode 100644
index 0000000000..e903d263a9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/README.md
@@ -0,0 +1,29 @@
+# TDLib Web example
+
+This is an example of building `TDLib` for browsers using [Emscripten](https://github.com/kripken/emscripten).
+These scripts build `TDLib` and create an [NPM](https://www.npmjs.com/) package [tdweb](https://www.npmjs.com/package/tdweb).
+You need a Unix shell with `sed`, `tar` and `wget` utilities to run the provided scripts.
+
+## Building tdweb NPM package
+
+* Install the 3.1.1 [emsdk](https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html), which is known to work. Do not use the system-provided `emscripten` package, because it contains a too old emsdk version.
+* Install all `TDLib` build dependencies described in [Building](https://github.com/tdlib/td#building) and `sed`, `tar` and `wget` utilities.
+* Run `source ./emsdk_env.sh` from `emsdk` directory to set up the correct build environment.
+* On `macOS`, install the `coreutils` [Homebrew](https://brew.sh) package and replace `realpath` in scripts with `grealpath`:
+```
+brew install coreutils
+sed -i.bak 's/[(]realpath/(grealpath/g' build-tdlib.sh
+```
+* Run build scripts in the following order:
+```
+cd <path to TDLib sources>/example/web
+./build-openssl.sh
+./build-tdlib.sh
+./copy-tdlib.sh
+./build-tdweb.sh
+```
+* The built package is now located in the `tdweb` directory.
+
+## Using tdweb NPM package
+
+See [tdweb](https://www.npmjs.com/package/tdweb) or [README.md](https://github.com/tdlib/td/tree/master/example/web/tdweb/README.md) for package documentation.
diff --git a/protocols/Telegram/tdlib/td/example/web/build-openssl.sh b/protocols/Telegram/tdlib/td/example/web/build-openssl.sh
new file mode 100644
index 0000000000..39e1d2d0d6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/build-openssl.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+cd $(dirname $0)
+
+emconfigure true 2> /dev/null || { echo 'emconfigure not found. Install emsdk and add emconfigure and emmake to PATH environment variable. See instruction at https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html. Do not forget to add `emconfigure` and `emmake` to the PATH environment variable via `emsdk/emsdk_env.sh` script.'; exit 1; }
+
+OPENSSL=OpenSSL_1_1_0j
+if [ ! -f $OPENSSL.tar.gz ]; then
+ echo "Downloading OpenSSL sources..."
+ wget https://github.com/openssl/openssl/archive/$OPENSSL.tar.gz
+fi
+rm -rf ./openssl-$OPENSSL
+echo "Unpacking OpenSSL sources..."
+tar xzf $OPENSSL.tar.gz || exit 1
+cd openssl-$OPENSSL
+
+emconfigure ./Configure linux-generic32 no-shared no-threads no-dso no-engine no-unit-test no-ui || exit 1
+sed -i.bak 's/CROSS_COMPILE=.*/CROSS_COMPILE=/g' Makefile || exit 1
+sed -i.bak 's/-ldl //g' Makefile || exit 1
+sed -i.bak 's/-O3/-Os/g' Makefile || exit 1
+echo "Building OpenSSL..."
+emmake make depend || exit 1
+emmake make -j 4 || exit 1
+
+rm -rf ../build/crypto || exit 1
+mkdir -p ../build/crypto/lib || exit 1
+cp libcrypto.a libssl.a ../build/crypto/lib/ || exit 1
+cp -r include ../build/crypto/ || exit 1
+cd ..
diff --git a/protocols/Telegram/tdlib/td/example/web/build-tdlib.sh b/protocols/Telegram/tdlib/td/example/web/build-tdlib.sh
new file mode 100644
index 0000000000..e013ccc29e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/build-tdlib.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+cd $(dirname $0)
+
+emcmake true 2> /dev/null || { echo 'emcmake not found. Install emsdk and add emcmake and emmake to PATH environment variable. See instruction at https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html. Do not forget to add `emcmake` and `emmake` to the PATH environment variable via `emsdk/emsdk_env.sh` script.'; exit 1; }
+
+rm -rf build/generate
+rm -rf build/asmjs
+rm -rf build/wasm
+
+mkdir -p build/generate
+mkdir -p build/asmjs
+mkdir -p build/wasm
+
+TD_ROOT=$(realpath ../../)
+OPENSSL_ROOT=$(realpath ./build/crypto/)
+OPENSSL_CRYPTO_LIBRARY=$OPENSSL_ROOT/lib/libcrypto.a
+OPENSSL_SSL_LIBRARY=$OPENSSL_ROOT/lib/libssl.a
+
+OPENSSL_OPTIONS="-DOPENSSL_FOUND=1 \
+ -DOPENSSL_ROOT_DIR=\"$OPENSSL_ROOT\" \
+ -DOPENSSL_INCLUDE_DIR=\"$OPENSSL_ROOT/include\" \
+ -DOPENSSL_CRYPTO_LIBRARY=\"$OPENSSL_CRYPTO_LIBRARY\" \
+ -DOPENSSL_SSL_LIBRARY=\"$OPENSSL_SSL_LIBRARY\" \
+ -DOPENSSL_LIBRARIES=\"$OPENSSL_SSL_LIBRARY;$OPENSSL_CRYPTO_LIBRARY\" \
+ -DOPENSSL_VERSION=\"1.1.0j\""
+
+cd build/generate
+cmake $TD_ROOT || exit 1
+cd ../..
+
+cd build/wasm
+eval emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel $OPENSSL_OPTIONS $TD_ROOT || exit 1
+cd ../..
+
+cd build/asmjs
+eval emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel $OPENSSL_OPTIONS -DASMJS=1 $TD_ROOT || exit 1
+cd ../..
+
+echo "Generating TDLib autogenerated source files..."
+cmake --build build/generate --target prepare_cross_compiling || exit 1
+echo "Building TDLib to WebAssembly..."
+cmake --build build/wasm --target td_wasm || exit 1
+echo "Building TDLib to asm.js..."
+cmake --build build/asmjs --target td_asmjs || exit 1
diff --git a/protocols/Telegram/tdlib/td/example/web/build-tdweb.sh b/protocols/Telegram/tdlib/td/example/web/build-tdweb.sh
new file mode 100644
index 0000000000..730f79a4b6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/build-tdweb.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+cd $(dirname $0)
+
+cd tdweb || exit 1
+npm install --no-save || exit 1
+npm run build || exit 1
+cd ..
diff --git a/protocols/Telegram/tdlib/td/example/web/copy-tdlib.sh b/protocols/Telegram/tdlib/td/example/web/copy-tdlib.sh
new file mode 100644
index 0000000000..f7167d5701
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/copy-tdlib.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+cd $(dirname $0)
+
+DEST=tdweb/src/prebuilt/release/
+mkdir -p $DEST || exit 1
+cp build/wasm/td_wasm.js build/wasm/td_wasm.wasm $DEST || exit 1
+cp build/asmjs/td_asmjs.js build/asmjs/td_asmjs.js.mem $DEST || exit 1
diff --git a/protocols/Telegram/tdlib/td/example/web/tdweb/README.md b/protocols/Telegram/tdlib/td/example/web/tdweb/README.md
new file mode 100644
index 0000000000..591bc941fb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/tdweb/README.md
@@ -0,0 +1,29 @@
+## tdweb - TDLib in a browser
+
+[TDLib](https://github.com/tdlib/td) is a library for building Telegram clients. tdweb is a convenient wrapper for TDLib in a browser which controls TDLib instance creation,
+handles interaction with TDLib and manages a filesystem for persistent TDLib data.
+
+For interaction with TDLib, you need to create an instance of the class `TdClient`, providing a handler for incoming updates and other options if needed.
+Once this is done, you can send queries to the TDLib instance using the method `TdClient.send` which returns a Promise object representing the result of the query.
+
+See [Getting Started](https://core.telegram.org/tdlib/getting-started) for a description of basic TDLib concepts and a short introduction to TDLib usage.
+
+See the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or
+the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of all available
+TDLib [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
+The JSON representation of TDLib API objects is straightforward: all API objects are represented as JSON objects with the same keys as the API object field names in the
+[td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme. Note that in the automatically generated C++ documentation all fields have an additional terminating underscore
+which shouldn't be used in the JSON interface. The object type name is stored in the special field '@type' which is optional in places where type is uniquely determined by the context.
+Fields of Bool type are stored as Boolean, fields of int32, int53, and double types are stored as Number, fields of int64 and string types are stored as String,
+fields of bytes type are base64 encoded and then stored as String, fields of array type are stored as Array.
+You can also add the field '@extra' to any query to TDLib and the response will contain the field '@extra' with exactly the same value.
+
+## Installation
+As usual, add npm tdweb package into your project:
+```
+npm install tdweb
+```
+
+All files will be installed into `node_modules/tdweb/dist/` folder. For now, it is your responsibility to make
+those files loadable from your server. For example, [telegram-react](https://github.com/evgeny-nadymov/telegram-react)
+manually copies these files into the `public` folder. If you know how to avoid this problem, please tell us.
diff --git a/protocols/Telegram/tdlib/td/example/web/tdweb/package-lock.json b/protocols/Telegram/tdlib/td/example/web/tdweb/package-lock.json
new file mode 100644
index 0000000000..04093e3971
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/tdweb/package-lock.json
@@ -0,0 +1,7681 @@
+{
+ "name": "tdweb",
+ "version": "1.8.1",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
+ "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.0.0"
+ }
+ },
+ "@babel/core": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.3.tgz",
+ "integrity": "sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/generator": "^7.4.0",
+ "@babel/helpers": "^7.4.3",
+ "@babel/parser": "^7.4.3",
+ "@babel/template": "^7.4.0",
+ "@babel/traverse": "^7.4.3",
+ "@babel/types": "^7.4.0",
+ "convert-source-map": "^1.1.0",
+ "debug": "^4.1.0",
+ "json5": "^2.1.0",
+ "lodash": "^4.17.11",
+ "resolve": "^1.3.2",
+ "semver": "^5.4.1",
+ "source-map": "^0.5.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/generator": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz",
+ "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.4.0",
+ "jsesc": "^2.5.1",
+ "lodash": "^4.17.11",
+ "source-map": "^0.5.0",
+ "trim-right": "^1.0.1"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/helper-annotate-as-pure": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz",
+ "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-builder-binary-assignment-operator-visitor": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz",
+ "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-explode-assignable-expression": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-call-delegate": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.0.tgz",
+ "integrity": "sha512-SdqDfbVdNQCBp3WhK2mNdDvHd3BD6qbmIc43CAyjnsfCmgHMeqgDcM3BzY2lchi7HBJGJ2CVdynLWbezaE4mmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-hoist-variables": "^7.4.0",
+ "@babel/traverse": "^7.4.0",
+ "@babel/types": "^7.4.0"
+ }
+ },
+ "@babel/helper-define-map": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.0.tgz",
+ "integrity": "sha512-wAhQ9HdnLIywERVcSvX40CEJwKdAa1ID4neI9NXQPDOHwwA+57DqwLiPEVy2AIyWzAk0CQ8qx4awO0VUURwLtA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.1.0",
+ "@babel/types": "^7.4.0",
+ "lodash": "^4.17.11"
+ }
+ },
+ "@babel/helper-explode-assignable-expression": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz",
+ "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==",
+ "dev": true,
+ "requires": {
+ "@babel/traverse": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
+ "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "^7.0.0",
+ "@babel/template": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
+ "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-hoist-variables": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.0.tgz",
+ "integrity": "sha512-/NErCuoe/et17IlAQFKWM24qtyYYie7sFIrW/tIQXpck6vAu2hhtYYsKLBWQV+BQZMbcIYPU/QMYuTufrY4aQw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.4.0"
+ }
+ },
+ "@babel/helper-member-expression-to-functions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz",
+ "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
+ "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.3.tgz",
+ "integrity": "sha512-H88T9IySZW25anu5uqyaC1DaQre7ofM+joZtAaO2F8NBdFfupH0SZ4gKjgSFVcvtx/aAirqA9L9Clio2heYbZA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/helper-simple-access": "^7.1.0",
+ "@babel/helper-split-export-declaration": "^7.0.0",
+ "@babel/template": "^7.2.2",
+ "@babel/types": "^7.2.2",
+ "lodash": "^4.17.11"
+ }
+ },
+ "@babel/helper-optimise-call-expression": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz",
+ "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
+ "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==",
+ "dev": true
+ },
+ "@babel/helper-regex": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.3.tgz",
+ "integrity": "sha512-hnoq5u96pLCfgjXuj8ZLX3QQ+6nAulS+zSgi6HulUwFbEruRAKwbGLU5OvXkE14L8XW6XsQEKsIDfgthKLRAyA==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.11"
+ }
+ },
+ "@babel/helper-remap-async-to-generator": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz",
+ "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.0.0",
+ "@babel/helper-wrap-function": "^7.1.0",
+ "@babel/template": "^7.1.0",
+ "@babel/traverse": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-replace-supers": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.0.tgz",
+ "integrity": "sha512-PVwCVnWWAgnal+kJ+ZSAphzyl58XrFeSKSAJRiqg5QToTsjL+Xu1f9+RJ+d+Q0aPhPfBGaYfkox66k86thxNSg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-member-expression-to-functions": "^7.0.0",
+ "@babel/helper-optimise-call-expression": "^7.0.0",
+ "@babel/traverse": "^7.4.0",
+ "@babel/types": "^7.4.0"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz",
+ "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz",
+ "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.4.0"
+ }
+ },
+ "@babel/helper-wrap-function": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz",
+ "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.1.0",
+ "@babel/template": "^7.1.0",
+ "@babel/traverse": "^7.1.0",
+ "@babel/types": "^7.2.0"
+ }
+ },
+ "@babel/helpers": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.3.tgz",
+ "integrity": "sha512-BMh7X0oZqb36CfyhvtbSmcWc3GXocfxv3yNsAEuM0l+fAqSO22rQrUpijr3oE/10jCTrB6/0b9kzmG4VetCj8Q==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.4.0",
+ "@babel/traverse": "^7.4.3",
+ "@babel/types": "^7.4.0"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
+ "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.0",
+ "esutils": "^2.0.2",
+ "js-tokens": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "@babel/parser": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz",
+ "integrity": "sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==",
+ "dev": true
+ },
+ "@babel/plugin-proposal-async-generator-functions": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz",
+ "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-remap-async-to-generator": "^7.1.0",
+ "@babel/plugin-syntax-async-generators": "^7.2.0"
+ }
+ },
+ "@babel/plugin-proposal-json-strings": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz",
+ "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/plugin-syntax-json-strings": "^7.2.0"
+ }
+ },
+ "@babel/plugin-proposal-object-rest-spread": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.3.tgz",
+ "integrity": "sha512-xC//6DNSSHVjq8O2ge0dyYlhshsH4T7XdCVoxbi5HzLYWfsC5ooFlJjrXk8RcAT+hjHAK9UjBXdylzSoDK3t4g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/plugin-syntax-object-rest-spread": "^7.2.0"
+ }
+ },
+ "@babel/plugin-proposal-optional-catch-binding": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz",
+ "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.2.0"
+ }
+ },
+ "@babel/plugin-proposal-unicode-property-regex": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.0.tgz",
+ "integrity": "sha512-h/KjEZ3nK9wv1P1FSNb9G079jXrNYR0Ko+7XkOx85+gM24iZbPn0rh4vCftk+5QKY7y1uByFataBTmX7irEF1w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-regex": "^7.0.0",
+ "regexpu-core": "^4.5.4"
+ }
+ },
+ "@babel/plugin-syntax-async-generators": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz",
+ "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-syntax-dynamic-import": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz",
+ "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-syntax-json-strings": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz",
+ "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz",
+ "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz",
+ "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-arrow-functions": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz",
+ "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-async-to-generator": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.0.tgz",
+ "integrity": "sha512-EeaFdCeUULM+GPFEsf7pFcNSxM7hYjoj5fiYbyuiXobW4JhFnjAv9OWzNwHyHcKoPNpAfeRDuW6VyaXEDUBa7g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-remap-async-to-generator": "^7.1.0"
+ }
+ },
+ "@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz",
+ "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-block-scoping": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.0.tgz",
+ "integrity": "sha512-AWyt3k+fBXQqt2qb9r97tn3iBwFpiv9xdAiG+Gr2HpAZpuayvbL55yWrsV3MyHvXk/4vmSiedhDRl1YI2Iy5nQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "lodash": "^4.17.11"
+ }
+ },
+ "@babel/plugin-transform-classes": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.3.tgz",
+ "integrity": "sha512-PUaIKyFUDtG6jF5DUJOfkBdwAS/kFFV3XFk7Nn0a6vR7ZT8jYw5cGtIlat77wcnd0C6ViGqo/wyNf4ZHytF/nQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.0.0",
+ "@babel/helper-define-map": "^7.4.0",
+ "@babel/helper-function-name": "^7.1.0",
+ "@babel/helper-optimise-call-expression": "^7.0.0",
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-replace-supers": "^7.4.0",
+ "@babel/helper-split-export-declaration": "^7.4.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/plugin-transform-computed-properties": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz",
+ "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-destructuring": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.3.tgz",
+ "integrity": "sha512-rVTLLZpydDFDyN4qnXdzwoVpk1oaXHIvPEOkOLyr88o7oHxVc/LyrnDx+amuBWGOwUb7D1s/uLsKBNTx08htZg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-dotall-regex": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.3.tgz",
+ "integrity": "sha512-9Arc2I0AGynzXRR/oPdSALv3k0rM38IMFyto7kOCwb5F9sLUt2Ykdo3V9yUPR+Bgr4kb6bVEyLkPEiBhzcTeoA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-regex": "^7.4.3",
+ "regexpu-core": "^4.5.4"
+ }
+ },
+ "@babel/plugin-transform-duplicate-keys": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz",
+ "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz",
+ "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0",
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-for-of": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.3.tgz",
+ "integrity": "sha512-UselcZPwVWNSURnqcfpnxtMehrb8wjXYOimlYQPBnup/Zld426YzIhNEvuRsEWVHfESIECGrxoI6L5QqzuLH5Q==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-function-name": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.3.tgz",
+ "integrity": "sha512-uT5J/3qI/8vACBR9I1GlAuU/JqBtWdfCrynuOkrWG6nCDieZd5przB1vfP59FRHBZQ9DC2IUfqr/xKqzOD5x0A==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.1.0",
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-literals": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz",
+ "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-member-expression-literals": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz",
+ "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-modules-amd": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz",
+ "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.1.0",
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-modules-commonjs": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.3.tgz",
+ "integrity": "sha512-sMP4JqOTbMJMimqsSZwYWsMjppD+KRyDIUVW91pd7td0dZKAvPmhCaxhOzkzLParKwgQc7bdL9UNv+rpJB0HfA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.4.3",
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-simple-access": "^7.1.0"
+ }
+ },
+ "@babel/plugin-transform-modules-systemjs": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.0.tgz",
+ "integrity": "sha512-gjPdHmqiNhVoBqus5qK60mWPp1CmYWp/tkh11mvb0rrys01HycEGD7NvvSoKXlWEfSM9TcL36CpsK8ElsADptQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-hoist-variables": "^7.4.0",
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-modules-umd": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz",
+ "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-transforms": "^7.1.0",
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.2.tgz",
+ "integrity": "sha512-NsAuliSwkL3WO2dzWTOL1oZJHm0TM8ZY8ZSxk2ANyKkt5SQlToGA4pzctmq1BEjoacurdwZ3xp2dCQWJkME0gQ==",
+ "dev": true,
+ "requires": {
+ "regexp-tree": "^0.1.0"
+ }
+ },
+ "@babel/plugin-transform-new-target": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.0.tgz",
+ "integrity": "sha512-6ZKNgMQmQmrEX/ncuCwnnw1yVGoaOW5KpxNhoWI7pCQdA0uZ0HqHGqenCUIENAnxRjy2WwNQ30gfGdIgqJXXqw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-object-super": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz",
+ "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-replace-supers": "^7.1.0"
+ }
+ },
+ "@babel/plugin-transform-parameters": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.3.tgz",
+ "integrity": "sha512-ULJYC2Vnw96/zdotCZkMGr2QVfKpIT/4/K+xWWY0MbOJyMZuk660BGkr3bEKWQrrciwz6xpmft39nA4BF7hJuA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-call-delegate": "^7.4.0",
+ "@babel/helper-get-function-arity": "^7.0.0",
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-property-literals": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz",
+ "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-regenerator": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.3.tgz",
+ "integrity": "sha512-kEzotPuOpv6/iSlHroCDydPkKYw7tiJGKlmYp6iJn4a6C/+b2FdttlJsLKYxolYHgotTJ5G5UY5h0qey5ka3+A==",
+ "dev": true,
+ "requires": {
+ "regenerator-transform": "^0.13.4"
+ }
+ },
+ "@babel/plugin-transform-reserved-words": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz",
+ "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-runtime": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.3.tgz",
+ "integrity": "sha512-7Q61bU+uEI7bCUFReT1NKn7/X6sDQsZ7wL1sJ9IYMAO7cI+eg6x9re1cEw2fCRMbbTVyoeUKWSV1M6azEfKCfg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "resolve": "^1.8.1",
+ "semver": "^5.5.1"
+ }
+ },
+ "@babel/plugin-transform-shorthand-properties": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz",
+ "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-spread": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz",
+ "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-sticky-regex": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz",
+ "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-regex": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-template-literals": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz",
+ "integrity": "sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.0.0",
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-typeof-symbol": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz",
+ "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
+ "@babel/plugin-transform-unicode-regex": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.3.tgz",
+ "integrity": "sha512-lnSNgkVjL8EMtnE8eSS7t2ku8qvKH3eqNf/IwIfnSPUqzgqYmRwzdsQWv4mNQAN9Nuo6Gz1Y0a4CSmdpu1Pp6g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-regex": "^7.4.3",
+ "regexpu-core": "^4.5.4"
+ }
+ },
+ "@babel/preset-env": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.4.3.tgz",
+ "integrity": "sha512-FYbZdV12yHdJU5Z70cEg0f6lvtpZ8jFSDakTm7WXeJbLXh4R0ztGEu/SW7G1nJ2ZvKwDhz8YrbA84eYyprmGqw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/plugin-proposal-async-generator-functions": "^7.2.0",
+ "@babel/plugin-proposal-json-strings": "^7.2.0",
+ "@babel/plugin-proposal-object-rest-spread": "^7.4.3",
+ "@babel/plugin-proposal-optional-catch-binding": "^7.2.0",
+ "@babel/plugin-proposal-unicode-property-regex": "^7.4.0",
+ "@babel/plugin-syntax-async-generators": "^7.2.0",
+ "@babel/plugin-syntax-json-strings": "^7.2.0",
+ "@babel/plugin-syntax-object-rest-spread": "^7.2.0",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.2.0",
+ "@babel/plugin-transform-arrow-functions": "^7.2.0",
+ "@babel/plugin-transform-async-to-generator": "^7.4.0",
+ "@babel/plugin-transform-block-scoped-functions": "^7.2.0",
+ "@babel/plugin-transform-block-scoping": "^7.4.0",
+ "@babel/plugin-transform-classes": "^7.4.3",
+ "@babel/plugin-transform-computed-properties": "^7.2.0",
+ "@babel/plugin-transform-destructuring": "^7.4.3",
+ "@babel/plugin-transform-dotall-regex": "^7.4.3",
+ "@babel/plugin-transform-duplicate-keys": "^7.2.0",
+ "@babel/plugin-transform-exponentiation-operator": "^7.2.0",
+ "@babel/plugin-transform-for-of": "^7.4.3",
+ "@babel/plugin-transform-function-name": "^7.4.3",
+ "@babel/plugin-transform-literals": "^7.2.0",
+ "@babel/plugin-transform-member-expression-literals": "^7.2.0",
+ "@babel/plugin-transform-modules-amd": "^7.2.0",
+ "@babel/plugin-transform-modules-commonjs": "^7.4.3",
+ "@babel/plugin-transform-modules-systemjs": "^7.4.0",
+ "@babel/plugin-transform-modules-umd": "^7.2.0",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.2",
+ "@babel/plugin-transform-new-target": "^7.4.0",
+ "@babel/plugin-transform-object-super": "^7.2.0",
+ "@babel/plugin-transform-parameters": "^7.4.3",
+ "@babel/plugin-transform-property-literals": "^7.2.0",
+ "@babel/plugin-transform-regenerator": "^7.4.3",
+ "@babel/plugin-transform-reserved-words": "^7.2.0",
+ "@babel/plugin-transform-shorthand-properties": "^7.2.0",
+ "@babel/plugin-transform-spread": "^7.2.0",
+ "@babel/plugin-transform-sticky-regex": "^7.2.0",
+ "@babel/plugin-transform-template-literals": "^7.2.0",
+ "@babel/plugin-transform-typeof-symbol": "^7.2.0",
+ "@babel/plugin-transform-unicode-regex": "^7.4.3",
+ "@babel/types": "^7.4.0",
+ "browserslist": "^4.5.2",
+ "core-js-compat": "^3.0.0",
+ "invariant": "^2.2.2",
+ "js-levenshtein": "^1.1.3",
+ "semver": "^5.5.0"
+ }
+ },
+ "@babel/runtime": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.3.tgz",
+ "integrity": "sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ },
+ "dependencies": {
+ "regenerator-runtime": {
+ "version": "0.13.2",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
+ "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
+ }
+ }
+ },
+ "@babel/template": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz",
+ "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/parser": "^7.4.0",
+ "@babel/types": "^7.4.0"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz",
+ "integrity": "sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/generator": "^7.4.0",
+ "@babel/helper-function-name": "^7.1.0",
+ "@babel/helper-split-export-declaration": "^7.4.0",
+ "@babel/parser": "^7.4.3",
+ "@babel/types": "^7.4.0",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0",
+ "lodash": "^4.17.11"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/types": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz",
+ "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.11",
+ "to-fast-properties": "^2.0.0"
+ },
+ "dependencies": {
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+ "dev": true
+ }
+ }
+ },
+ "@samverschueren/stream-to-observable": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz",
+ "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==",
+ "dev": true,
+ "requires": {
+ "any-observable": "^0.3.0"
+ }
+ },
+ "@typescript-eslint/eslint-plugin": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.7.0.tgz",
+ "integrity": "sha512-NUSz1aTlIzzTjFFVFyzrbo8oFjHg3K/M9MzYByqbMCxeFdErhLAcGITVfXzSz+Yvp5OOpMu3HkIttB0NyKl54Q==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/parser": "1.7.0",
+ "@typescript-eslint/typescript-estree": "1.7.0",
+ "eslint-utils": "^1.3.1",
+ "regexpp": "^2.0.1",
+ "requireindex": "^1.2.0",
+ "tsutils": "^3.7.0"
+ }
+ },
+ "@typescript-eslint/parser": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.7.0.tgz",
+ "integrity": "sha512-1QFKxs2V940372srm12ovSE683afqc1jB6zF/f8iKhgLz1yoSjYeGHipasao33VXKI+0a/ob9okeogGdKGvvlg==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/typescript-estree": "1.7.0",
+ "eslint-scope": "^4.0.0",
+ "eslint-visitor-keys": "^1.0.0"
+ }
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.7.0.tgz",
+ "integrity": "sha512-K5uedUxVmlYrVkFbyV3htDipvLqTE3QMOUQEHYJaKtgzxj6r7c5Ca/DG1tGgFxX+fsbi9nDIrf4arq7Ib7H/Yw==",
+ "dev": true,
+ "requires": {
+ "lodash.unescape": "4.0.1",
+ "semver": "5.5.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
+ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
+ "dev": true
+ }
+ }
+ },
+ "@webassemblyjs/ast": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
+ "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/helper-module-context": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/wast-parser": "1.9.0"
+ }
+ },
+ "@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz",
+ "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-api-error": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz",
+ "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-buffer": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz",
+ "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-code-frame": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz",
+ "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/wast-printer": "1.9.0"
+ }
+ },
+ "@webassemblyjs/helper-fsm": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz",
+ "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-module-context": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz",
+ "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0"
+ }
+ },
+ "@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz",
+ "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-wasm-section": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz",
+ "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-buffer": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/wasm-gen": "1.9.0"
+ }
+ },
+ "@webassemblyjs/ieee754": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz",
+ "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==",
+ "dev": true,
+ "requires": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "@webassemblyjs/leb128": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz",
+ "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==",
+ "dev": true,
+ "requires": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/utf8": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz",
+ "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==",
+ "dev": true
+ },
+ "@webassemblyjs/wasm-edit": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz",
+ "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-buffer": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/helper-wasm-section": "1.9.0",
+ "@webassemblyjs/wasm-gen": "1.9.0",
+ "@webassemblyjs/wasm-opt": "1.9.0",
+ "@webassemblyjs/wasm-parser": "1.9.0",
+ "@webassemblyjs/wast-printer": "1.9.0"
+ }
+ },
+ "@webassemblyjs/wasm-gen": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz",
+ "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/ieee754": "1.9.0",
+ "@webassemblyjs/leb128": "1.9.0",
+ "@webassemblyjs/utf8": "1.9.0"
+ }
+ },
+ "@webassemblyjs/wasm-opt": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz",
+ "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-buffer": "1.9.0",
+ "@webassemblyjs/wasm-gen": "1.9.0",
+ "@webassemblyjs/wasm-parser": "1.9.0"
+ }
+ },
+ "@webassemblyjs/wasm-parser": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz",
+ "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-api-error": "1.9.0",
+ "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+ "@webassemblyjs/ieee754": "1.9.0",
+ "@webassemblyjs/leb128": "1.9.0",
+ "@webassemblyjs/utf8": "1.9.0"
+ }
+ },
+ "@webassemblyjs/wast-parser": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz",
+ "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/floating-point-hex-parser": "1.9.0",
+ "@webassemblyjs/helper-api-error": "1.9.0",
+ "@webassemblyjs/helper-code-frame": "1.9.0",
+ "@webassemblyjs/helper-fsm": "1.9.0",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@webassemblyjs/wast-printer": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz",
+ "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/wast-parser": "1.9.0",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true
+ },
+ "@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true
+ },
+ "acorn": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz",
+ "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==",
+ "dev": true
+ },
+ "ajv": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
+ "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-errors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
+ "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
+ "dev": true
+ },
+ "ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true
+ },
+ "ansi-escapes": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "any-observable": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
+ "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==",
+ "dev": true
+ },
+ "anymatch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+ "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "aria-query": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz",
+ "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=",
+ "dev": true,
+ "requires": {
+ "ast-types-flow": "0.0.7",
+ "commander": "^2.11.0"
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+ "dev": true
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+ "dev": true
+ },
+ "array-includes": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
+ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.7.0"
+ }
+ },
+ "array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+ "dev": true,
+ "requires": {
+ "array-uniq": "^1.0.1"
+ }
+ },
+ "array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+ "dev": true
+ },
+ "asn1.js": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
+ "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "safer-buffer": "^2.1.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "assert": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+ "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+ "dev": true,
+ "requires": {
+ "object-assign": "^4.1.1",
+ "util": "0.10.3"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+ "dev": true
+ },
+ "util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.1"
+ }
+ }
+ }
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+ "dev": true
+ },
+ "ast-types-flow": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
+ "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=",
+ "dev": true
+ },
+ "astral-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+ "dev": true
+ },
+ "async-each": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
+ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
+ "dev": true,
+ "optional": true
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+ "dev": true
+ },
+ "axobject-query": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
+ "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==",
+ "dev": true,
+ "requires": {
+ "ast-types-flow": "0.0.7"
+ }
+ },
+ "babel-eslint": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz",
+ "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "@babel/parser": "^7.0.0",
+ "@babel/traverse": "^7.0.0",
+ "@babel/types": "^7.0.0",
+ "eslint-scope": "3.7.1",
+ "eslint-visitor-keys": "^1.0.0"
+ },
+ "dependencies": {
+ "eslint-scope": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz",
+ "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ }
+ }
+ },
+ "babel-loader": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.5.tgz",
+ "integrity": "sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==",
+ "dev": true,
+ "requires": {
+ "find-cache-dir": "^2.0.0",
+ "loader-utils": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "util.promisify": "^1.0.0"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "dev": true,
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "base64-js": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+ "dev": true
+ },
+ "big-integer": {
+ "version": "1.6.43",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.43.tgz",
+ "integrity": "sha512-9dULc9jsKmXl0Aeunug8wbF+58n+hQoFjqClN7WeZwGLh0XJUWyJJ9Ee+Ep+Ql/J9fRsTVaeThp8MhiCCrY0Jg=="
+ },
+ "big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
+ "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
+ "dev": true,
+ "optional": true
+ },
+ "bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "dev": true
+ },
+ "bn.js": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
+ "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "broadcast-channel": {
+ "version": "2.1.12",
+ "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-2.1.12.tgz",
+ "integrity": "sha512-U0b7c3Nwru3a8nDRt9R2OYXhX8GfmcEeaCwBZyIly7CIS3de4eXcl+DO6jgN6ux4Ly2eeBoBGKVneS60Cpfnjw==",
+ "requires": {
+ "@babel/runtime": "7.4.3",
+ "detect-node": "2.0.4",
+ "js-sha3": "0.8.0",
+ "microseconds": "0.1.0",
+ "nano-time": "1.0.0",
+ "rimraf": "2.6.3",
+ "unload": "2.1.0"
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+ "dev": true
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+ "dev": true,
+ "requires": {
+ "buffer-xor": "^1.0.3",
+ "cipher-base": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.3",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+ "dev": true,
+ "requires": {
+ "browserify-aes": "^1.0.4",
+ "browserify-des": "^1.0.0",
+ "evp_bytestokey": "^1.0.0"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "des.js": "^1.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "randombytes": "^2.0.1"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "browserify-sign": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
+ "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^5.1.1",
+ "browserify-rsa": "^4.0.1",
+ "create-hash": "^1.2.0",
+ "create-hmac": "^1.1.7",
+ "elliptic": "^6.5.3",
+ "inherits": "^2.0.4",
+ "parse-asn1": "^5.1.5",
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ }
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "requires": {
+ "pako": "~1.0.5"
+ }
+ },
+ "browserslist": {
+ "version": "4.5.5",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.5.5.tgz",
+ "integrity": "sha512-0QFO1r/2c792Ohkit5XI8Cm8pDtZxgNl2H6HU4mHrpYz7314pEYcsAVVatM0l/YmxPnEzh9VygXouj4gkFUTKA==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30000960",
+ "electron-to-chromium": "^1.3.124",
+ "node-releases": "^1.1.14"
+ },
+ "dependencies": {
+ "caniuse-lite": {
+ "version": "1.0.30000962",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000962.tgz",
+ "integrity": "sha512-WXYsW38HK+6eaj5IZR16Rn91TGhU3OhbwjKZvJ4HN/XBIABLKfbij9Mnd3pM0VEwZSlltWjoWg3I8FQ0DGgNOA==",
+ "dev": true
+ },
+ "electron-to-chromium": {
+ "version": "1.3.125",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.125.tgz",
+ "integrity": "sha512-XxowpqQxJ4nDwUXHtVtmEhRqBpm2OnjBomZmZtHD0d2Eo0244+Ojezhk3sD/MBSSe2nxCdGQFRXHIsf/LUTL9A==",
+ "dev": true
+ }
+ }
+ },
+ "buffer": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
+ "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
+ "dev": true,
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4",
+ "isarray": "^1.0.0"
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+ "dev": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+ "dev": true
+ },
+ "cacache": {
+ "version": "12.0.4",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+ "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
+ "dev": true,
+ "requires": {
+ "bluebird": "^3.5.5",
+ "chownr": "^1.1.1",
+ "figgy-pudding": "^3.5.1",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.1.15",
+ "infer-owner": "^1.0.3",
+ "lru-cache": "^5.1.1",
+ "mississippi": "^3.0.0",
+ "mkdirp": "^0.5.1",
+ "move-concurrently": "^1.0.1",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^2.6.3",
+ "ssri": "^6.0.1",
+ "unique-filename": "^1.1.1",
+ "y18n": "^4.0.0"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
+ }
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "dev": true,
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ }
+ },
+ "caller-callsite": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+ "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+ "dev": true,
+ "requires": {
+ "callsites": "^2.0.0"
+ },
+ "dependencies": {
+ "callsites": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+ "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
+ "dev": true
+ }
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "chardet": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+ "dev": true
+ },
+ "chokidar": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
+ "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "anymatch": "~3.1.1",
+ "braces": "~3.0.2",
+ "fsevents": "~2.1.2",
+ "glob-parent": "~5.1.0",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.4.0"
+ },
+ "dependencies": {
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "optional": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ }
+ }
+ },
+ "chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true
+ },
+ "chrome-trace-event": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
+ "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "ci-info": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+ "dev": true
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "clean-webpack-plugin": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-2.0.1.tgz",
+ "integrity": "sha512-vway5pXGVd91bicwjaf8j188Al6VMf9R9Ekl6q0qeiaWStRsOOXuh4qtjX1UrUvmz5XevQVCdjBuzr4Tzsnpog==",
+ "dev": true,
+ "requires": {
+ "del": "^4.0.0"
+ }
+ },
+ "cli-cursor": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+ "dev": true,
+ "requires": {
+ "restore-cursor": "^2.0.0"
+ }
+ },
+ "cli-truncate": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
+ "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=",
+ "dev": true,
+ "requires": {
+ "slice-ansi": "0.0.4",
+ "string-width": "^1.0.1"
+ },
+ "dependencies": {
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "slice-ansi": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ }
+ }
+ },
+ "cli-width": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+ "dev": true
+ },
+ "cliui": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+ "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+ "dev": true,
+ "requires": {
+ "string-width": "^3.1.0",
+ "strip-ansi": "^5.2.0",
+ "wrap-ansi": "^5.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "dev": true,
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
+ "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
+ "dev": true
+ },
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "component-emitter": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "confusing-browser-globals": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.7.tgz",
+ "integrity": "sha512-cgHI1azax5ATrZ8rJ+ODDML9Fvu67PimB6aNxBrc/QwSaDaM9eTfIEUHx3bBLJJ82ioSb+/5zfsMCCEJax3ByQ==",
+ "dev": true
+ },
+ "console-browserify": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
+ "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
+ "dev": true
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+ "dev": true
+ },
+ "contains-path": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
+ "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "copy-concurrently": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "fs-write-stream-atomic": "^1.0.8",
+ "iferr": "^0.1.5",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.0"
+ }
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+ "dev": true
+ },
+ "core-js-compat": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.0.1.tgz",
+ "integrity": "sha512-2pC3e+Ht/1/gD7Sim/sqzvRplMiRnFQVlPpDVaHtY9l7zZP7knamr3VRD6NyGfHd84MrDC0tAM9ulNxYMW0T3g==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^4.5.4",
+ "core-js": "3.0.1",
+ "core-js-pure": "3.0.1",
+ "semver": "^6.0.0"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.1.tgz",
+ "integrity": "sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz",
+ "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==",
+ "dev": true
+ }
+ }
+ },
+ "core-js-pure": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.0.1.tgz",
+ "integrity": "sha512-mSxeQ6IghKW3MoyF4cz19GJ1cMm7761ON+WObSyLfTu/Jn3x7w4NwNFnrZxgl4MTSvYYepVLNuRtlB4loMwJ5g==",
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "cosmiconfig": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.0.tgz",
+ "integrity": "sha512-nxt+Nfc3JAqf4WIWd0jXLjTJZmsPLrA9DDc4nRw2KFJQJK7DNooqSXrNI7tzLG50CF8axczly5UV929tBmh/7g==",
+ "dev": true,
+ "requires": {
+ "import-fresh": "^2.0.0",
+ "is-directory": "^0.3.1",
+ "js-yaml": "^3.13.0",
+ "parse-json": "^4.0.0"
+ },
+ "dependencies": {
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ }
+ }
+ }
+ },
+ "create-ecdh": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz",
+ "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "elliptic": "^6.5.3"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "inherits": "^2.0.1",
+ "md5.js": "^1.3.4",
+ "ripemd160": "^2.0.1",
+ "sha.js": "^2.4.0"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.3",
+ "create-hash": "^1.1.0",
+ "inherits": "^2.0.1",
+ "ripemd160": "^2.0.0",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "dev": true,
+ "requires": {
+ "browserify-cipher": "^1.0.0",
+ "browserify-sign": "^4.0.0",
+ "create-ecdh": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.0",
+ "diffie-hellman": "^5.0.0",
+ "inherits": "^2.0.1",
+ "pbkdf2": "^3.0.3",
+ "public-encrypt": "^4.0.0",
+ "randombytes": "^2.0.0",
+ "randomfill": "^1.0.3"
+ }
+ },
+ "cyclist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
+ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
+ "dev": true
+ },
+ "damerau-levenshtein": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
+ "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=",
+ "dev": true
+ },
+ "date-fns": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+ "dev": true
+ },
+ "dedent": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
+ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
+ "dev": true
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "dev": true,
+ "requires": {
+ "object-keys": "^1.0.12"
+ }
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "del": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/del/-/del-4.1.0.tgz",
+ "integrity": "sha512-C4kvKNlYrwXhKxz97BuohF8YoGgQ23Xm9lvoHmgT7JaPGprSEjk3+XFled74Yt/x0ZABUHg2D67covzAPUKx5Q==",
+ "dev": true,
+ "requires": {
+ "globby": "^6.1.0",
+ "is-path-cwd": "^2.0.0",
+ "is-path-in-cwd": "^2.0.0",
+ "p-map": "^2.0.0",
+ "pify": "^4.0.1",
+ "rimraf": "^2.6.3"
+ },
+ "dependencies": {
+ "p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "dev": true
+ }
+ }
+ },
+ "des.js": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
+ "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "detect-file": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
+ "dev": true
+ },
+ "detect-node": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
+ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw=="
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "miller-rabin": "^4.0.0",
+ "randombytes": "^2.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+ "dev": true
+ },
+ "duplexify": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "elegant-spinner": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
+ "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=",
+ "dev": true
+ },
+ "elliptic": {
+ "version": "6.5.3",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
+ "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.4.0",
+ "brorand": "^1.0.1",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "emoji-regex": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+ "dev": true
+ },
+ "emojis-list": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+ "dev": true
+ },
+ "end-of-stream": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
+ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "enhanced-resolve": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz",
+ "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "memory-fs": "^0.5.0",
+ "tapable": "^1.0.0"
+ },
+ "dependencies": {
+ "memory-fs": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
+ "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==",
+ "dev": true,
+ "requires": {
+ "errno": "^0.1.3",
+ "readable-stream": "^2.0.1"
+ }
+ }
+ }
+ },
+ "errno": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+ "dev": true,
+ "requires": {
+ "prr": "~1.0.1"
+ }
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
+ "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.1.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.1",
+ "is-callable": "^1.1.3",
+ "is-regex": "^1.0.4"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+ "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "eslint": {
+ "version": "5.16.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz",
+ "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0",
+ "ajv": "^6.9.1",
+ "chalk": "^2.1.0",
+ "cross-spawn": "^6.0.5",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "eslint-scope": "^4.0.3",
+ "eslint-utils": "^1.3.1",
+ "eslint-visitor-keys": "^1.0.0",
+ "espree": "^5.0.1",
+ "esquery": "^1.0.1",
+ "esutils": "^2.0.2",
+ "file-entry-cache": "^5.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob": "^7.1.2",
+ "globals": "^11.7.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "inquirer": "^6.2.2",
+ "js-yaml": "^3.13.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.3.0",
+ "lodash": "^4.17.11",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.8.2",
+ "path-is-inside": "^1.0.2",
+ "progress": "^2.0.0",
+ "regexpp": "^2.0.1",
+ "semver": "^5.5.1",
+ "strip-ansi": "^4.0.0",
+ "strip-json-comments": "^2.0.1",
+ "table": "^5.2.3",
+ "text-table": "^0.2.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "import-fresh": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz",
+ "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "eslint-config-react-app": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-4.0.0.tgz",
+ "integrity": "sha512-SeFxaI+0NAzWPFAI9AT+Vp9Xe2u5RCnn0JVEXkE338HgoPujc38Bc0upCJw4BWmavvIN/ODmE6EuzHoAEn3ozw==",
+ "dev": true,
+ "requires": {
+ "confusing-browser-globals": "^1.0.7"
+ }
+ },
+ "eslint-import-resolver-node": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz",
+ "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.9",
+ "resolve": "^1.5.0"
+ }
+ },
+ "eslint-loader": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.1.2.tgz",
+ "integrity": "sha512-rA9XiXEOilLYPOIInvVH5S/hYfyTPyxag6DZhoQOduM+3TkghAEQ3VcFO8VnX4J4qg/UIBzp72aOf/xvYmpmsg==",
+ "dev": true,
+ "requires": {
+ "loader-fs-cache": "^1.0.0",
+ "loader-utils": "^1.0.2",
+ "object-assign": "^4.0.1",
+ "object-hash": "^1.1.4",
+ "rimraf": "^2.6.1"
+ }
+ },
+ "eslint-module-utils": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz",
+ "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.8",
+ "pkg-dir": "^2.0.0"
+ }
+ },
+ "eslint-plugin-flowtype": {
+ "version": "2.50.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.50.3.tgz",
+ "integrity": "sha512-X+AoKVOr7Re0ko/yEXyM5SSZ0tazc6ffdIOocp2fFUlWoDt7DV0Bz99mngOkAFLOAWjqRA5jPwqUCbrx13XoxQ==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.10"
+ }
+ },
+ "eslint-plugin-import": {
+ "version": "2.17.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.17.2.tgz",
+ "integrity": "sha512-m+cSVxM7oLsIpmwNn2WXTJoReOF9f/CtLMo7qOVmKd1KntBy0hEcuNZ3erTmWjx+DxRO0Zcrm5KwAvI9wHcV5g==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.0.3",
+ "contains-path": "^0.1.0",
+ "debug": "^2.6.9",
+ "doctrine": "1.5.0",
+ "eslint-import-resolver-node": "^0.3.2",
+ "eslint-module-utils": "^2.4.0",
+ "has": "^1.0.3",
+ "lodash": "^4.17.11",
+ "minimatch": "^3.0.4",
+ "read-pkg-up": "^2.0.0",
+ "resolve": "^1.10.0"
+ },
+ "dependencies": {
+ "doctrine": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2",
+ "isarray": "^1.0.0"
+ }
+ }
+ }
+ },
+ "eslint-plugin-jsx-a11y": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz",
+ "integrity": "sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==",
+ "dev": true,
+ "requires": {
+ "aria-query": "^3.0.0",
+ "array-includes": "^3.0.3",
+ "ast-types-flow": "^0.0.7",
+ "axobject-query": "^2.0.2",
+ "damerau-levenshtein": "^1.0.4",
+ "emoji-regex": "^7.0.2",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^2.0.1"
+ }
+ },
+ "eslint-plugin-react": {
+ "version": "7.12.4",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz",
+ "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.0.3",
+ "doctrine": "^2.1.0",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^2.0.1",
+ "object.fromentries": "^2.0.0",
+ "prop-types": "^15.6.2",
+ "resolve": "^1.9.0"
+ },
+ "dependencies": {
+ "doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ }
+ }
+ },
+ "eslint-plugin-react-hooks": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.6.0.tgz",
+ "integrity": "sha512-lHBVRIaz5ibnIgNG07JNiAuBUeKhEf8l4etNx5vfAEwqQ5tcuK3jV9yjmopPgQDagQb7HwIuQVsE3IVcGrRnag==",
+ "dev": true
+ },
+ "eslint-scope": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+ "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
+ "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+ "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
+ "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==",
+ "dev": true
+ },
+ "espree": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz",
+ "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==",
+ "dev": true,
+ "requires": {
+ "acorn": "^6.0.7",
+ "acorn-jsx": "^5.0.0",
+ "eslint-visitor-keys": "^1.0.0"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+ "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+ "dev": true
+ }
+ }
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ },
+ "esquery": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
+ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.0.0"
+ }
+ },
+ "esrecurse": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.1.0"
+ }
+ },
+ "estraverse": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
+ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "dev": true
+ },
+ "events": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz",
+ "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==",
+ "dev": true
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "dev": true,
+ "requires": {
+ "md5.js": "^1.3.4",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "execa": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+ "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^4.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ }
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "expand-tilde": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+ "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
+ "dev": true,
+ "requires": {
+ "homedir-polyfill": "^1.0.1"
+ }
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "dev": true,
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "external-editor": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz",
+ "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==",
+ "dev": true,
+ "requires": {
+ "chardet": "^0.7.0",
+ "iconv-lite": "^0.4.24",
+ "tmp": "^0.0.33"
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "fast-deep-equal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+ "dev": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "figgy-pudding": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
+ "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
+ "dev": true
+ },
+ "figures": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.5"
+ }
+ },
+ "file-entry-cache": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+ "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^2.0.1"
+ }
+ },
+ "file-loader": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz",
+ "integrity": "sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.0.2",
+ "schema-utils": "^1.0.0"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
+ "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-keywords": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz",
+ "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==",
+ "dev": true
+ },
+ "fast-deep-equal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
+ "file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "dev": true,
+ "optional": true
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "find-cache-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+ "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^2.0.0",
+ "pkg-dir": "^3.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
+ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ }
+ }
+ },
+ "find-parent-dir": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz",
+ "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=",
+ "dev": true
+ },
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "findup-sync": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
+ "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
+ "dev": true,
+ "requires": {
+ "detect-file": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "micromatch": "^3.0.4",
+ "resolve-dir": "^1.0.1"
+ }
+ },
+ "flat-cache": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+ "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+ "dev": true,
+ "requires": {
+ "flatted": "^2.0.0",
+ "rimraf": "2.6.3",
+ "write": "1.0.3"
+ }
+ },
+ "flatted": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz",
+ "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==",
+ "dev": true
+ },
+ "flush-write-stream": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
+ "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.3.6"
+ }
+ },
+ "fn-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz",
+ "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=",
+ "dev": true
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+ "dev": true
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "dev": true,
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
+ "from2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0"
+ }
+ },
+ "fs-write-stream-atomic": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "iferr": "^0.1.5",
+ "imurmurhash": "^0.1.4",
+ "readable-stream": "1 || 2"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "fsevents": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
+ "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+ "dev": true
+ },
+ "g-status": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/g-status/-/g-status-2.0.2.tgz",
+ "integrity": "sha512-kQoE9qH+T1AHKgSSD0Hkv98bobE90ILQcXAF4wvGgsr7uFqNvwmh8j+Lq3l0RVt3E3HjSbv2B9biEGcEtpHLCA==",
+ "dev": true,
+ "requires": {
+ "arrify": "^1.0.1",
+ "matcher": "^1.0.0",
+ "simple-git": "^1.85.0"
+ }
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "get-own-enumerable-property-symbols": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz",
+ "integrity": "sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==",
+ "dev": true
+ },
+ "get-stdin": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
+ "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
+ "dev": true
+ },
+ "get-stream": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+ "dev": true,
+ "requires": {
+ "pump": "^3.0.0"
+ }
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+ "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ }
+ }
+ },
+ "global-modules": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+ "dev": true,
+ "requires": {
+ "global-prefix": "^3.0.0"
+ },
+ "dependencies": {
+ "global-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+ "dev": true,
+ "requires": {
+ "ini": "^1.3.5",
+ "kind-of": "^6.0.2",
+ "which": "^1.3.1"
+ }
+ }
+ }
+ },
+ "global-prefix": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+ "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
+ "dev": true,
+ "requires": {
+ "expand-tilde": "^2.0.2",
+ "homedir-polyfill": "^1.0.1",
+ "ini": "^1.3.4",
+ "is-windows": "^1.0.1",
+ "which": "^1.2.14"
+ }
+ },
+ "globals": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz",
+ "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==",
+ "dev": true
+ },
+ "globby": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+ "dev": true,
+ "requires": {
+ "array-union": "^1.0.1",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.15",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+ "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
+ "dev": true
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "hash-base": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
+ "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.6.0",
+ "safe-buffer": "^5.2.0"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ }
+ }
+ },
+ "hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+ "dev": true,
+ "requires": {
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "homedir-polyfill": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+ "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+ "dev": true,
+ "requires": {
+ "parse-passwd": "^1.0.0"
+ }
+ },
+ "hosted-git-info": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
+ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
+ "dev": true
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+ "dev": true
+ },
+ "husky": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz",
+ "integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==",
+ "dev": true,
+ "requires": {
+ "cosmiconfig": "^5.0.7",
+ "execa": "^1.0.0",
+ "find-up": "^3.0.0",
+ "get-stdin": "^6.0.0",
+ "is-ci": "^2.0.0",
+ "pkg-dir": "^3.0.0",
+ "please-upgrade-node": "^3.1.1",
+ "read-pkg": "^4.0.1",
+ "run-node": "^1.0.0",
+ "slash": "^2.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
+ "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ }
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ },
+ "read-pkg": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz",
+ "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=",
+ "dev": true,
+ "requires": {
+ "normalize-package-data": "^2.3.2",
+ "parse-json": "^4.0.0",
+ "pify": "^3.0.0"
+ }
+ }
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "ieee754": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
+ "dev": true
+ },
+ "iferr": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+ "dev": true
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "dev": true
+ },
+ "immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+ },
+ "import-fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+ "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+ "dev": true,
+ "requires": {
+ "caller-path": "^2.0.0",
+ "resolve-from": "^3.0.0"
+ },
+ "dependencies": {
+ "caller-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+ "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+ "dev": true,
+ "requires": {
+ "caller-callsite": "^2.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+ "dev": true
+ }
+ }
+ },
+ "import-local": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+ "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
+ "dev": true,
+ "requires": {
+ "pkg-dir": "^3.0.0",
+ "resolve-cwd": "^2.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "pkg-dir": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+ "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "dev": true,
+ "requires": {
+ "find-up": "^3.0.0"
+ }
+ }
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "indent-string": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+ "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=",
+ "dev": true
+ },
+ "infer-owner": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "ini": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+ "dev": true
+ },
+ "inquirer": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz",
+ "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^3.2.0",
+ "chalk": "^2.4.2",
+ "cli-cursor": "^2.1.0",
+ "cli-width": "^2.0.0",
+ "external-editor": "^3.0.3",
+ "figures": "^2.0.0",
+ "lodash": "^4.17.11",
+ "mute-stream": "0.0.7",
+ "run-async": "^2.2.0",
+ "rxjs": "^6.4.0",
+ "string-width": "^2.1.0",
+ "strip-ansi": "^5.1.0",
+ "through": "^2.3.6"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "interpret": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
+ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==",
+ "dev": true
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dev": true,
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "is-builtin-module": {
+ "version": "1.0.0",
+ "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+ "dev": true,
+ "requires": {
+ "builtin-modules": "^1.0.0"
+ }
+ },
+ "is-callable": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+ "dev": true
+ },
+ "is-ci": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
+ "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
+ "dev": true,
+ "requires": {
+ "ci-info": "^2.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-date-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+ "dev": true
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "is-directory": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+ "dev": true
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
+ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+ "dev": true
+ },
+ "is-observable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz",
+ "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==",
+ "dev": true,
+ "requires": {
+ "symbol-observable": "^1.1.0"
+ }
+ },
+ "is-path-cwd": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.1.0.tgz",
+ "integrity": "sha512-Sc5j3/YnM8tDeyCsVeKlm/0p95075DyLmDEIkSgQ7mXkrOX+uTCtmQFm0CYzVyJwcCCmO3k8qfJt17SxQwB5Zw==",
+ "dev": true
+ },
+ "is-path-in-cwd": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz",
+ "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==",
+ "dev": true,
+ "requires": {
+ "is-path-inside": "^2.1.0"
+ }
+ },
+ "is-path-inside": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz",
+ "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==",
+ "dev": true,
+ "requires": {
+ "path-is-inside": "^1.0.2"
+ }
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "is-promise": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.1"
+ }
+ },
+ "is-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+ "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=",
+ "dev": true
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "dev": true
+ },
+ "is-symbol": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+ "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.0"
+ }
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true
+ },
+ "is-wsl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "js-levenshtein": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+ "dev": true
+ },
+ "js-sha3": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
+ },
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+ "dev": true
+ },
+ "json-parse-better-errors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+ "dev": true
+ },
+ "json5": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz",
+ "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ }
+ }
+ },
+ "jsx-ast-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.1.0.tgz",
+ "integrity": "sha512-yDGDG2DS4JcqhA6blsuYbtsT09xL8AoLuUR2Gb5exrw7UEM19sBcOTq+YBBhrNbl0PUC4R4LnFu+dHg2HKeVvA==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.0.3"
+ }
+ },
+ "kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true
+ },
+ "levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ }
+ },
+ "lie": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+ "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
+ "requires": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "lint-staged": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.5.tgz",
+ "integrity": "sha512-e5ZavfnSLcBJE1BTzRTqw6ly8OkqVyO3GL2M6teSmTBYQ/2BuueD5GIt2RPsP31u/vjKdexUyDCxSyK75q4BDA==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.3.1",
+ "commander": "^2.14.1",
+ "cosmiconfig": "^5.0.2",
+ "debug": "^3.1.0",
+ "dedent": "^0.7.0",
+ "del": "^3.0.0",
+ "execa": "^1.0.0",
+ "find-parent-dir": "^0.3.0",
+ "g-status": "^2.0.2",
+ "is-glob": "^4.0.0",
+ "is-windows": "^1.0.2",
+ "listr": "^0.14.2",
+ "listr-update-renderer": "^0.5.0",
+ "lodash": "^4.17.11",
+ "log-symbols": "^2.2.0",
+ "micromatch": "^3.1.8",
+ "npm-which": "^3.0.1",
+ "p-map": "^1.1.1",
+ "path-is-inside": "^1.0.2",
+ "pify": "^3.0.0",
+ "please-upgrade-node": "^3.0.2",
+ "staged-git-files": "1.1.2",
+ "string-argv": "^0.0.2",
+ "stringify-object": "^3.2.2",
+ "yup": "^0.26.10"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "del": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz",
+ "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=",
+ "dev": true,
+ "requires": {
+ "globby": "^6.1.0",
+ "is-path-cwd": "^1.0.0",
+ "is-path-in-cwd": "^1.0.0",
+ "p-map": "^1.1.1",
+ "pify": "^3.0.0",
+ "rimraf": "^2.2.8"
+ }
+ },
+ "is-path-cwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
+ "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+ "dev": true
+ },
+ "is-path-in-cwd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
+ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
+ "dev": true,
+ "requires": {
+ "is-path-inside": "^1.0.0"
+ }
+ },
+ "is-path-inside": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
+ "dev": true,
+ "requires": {
+ "path-is-inside": "^1.0.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "listr": {
+ "version": "0.14.3",
+ "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz",
+ "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==",
+ "dev": true,
+ "requires": {
+ "@samverschueren/stream-to-observable": "^0.3.0",
+ "is-observable": "^1.1.0",
+ "is-promise": "^2.1.0",
+ "is-stream": "^1.1.0",
+ "listr-silent-renderer": "^1.1.1",
+ "listr-update-renderer": "^0.5.0",
+ "listr-verbose-renderer": "^0.5.0",
+ "p-map": "^2.0.0",
+ "rxjs": "^6.3.3"
+ },
+ "dependencies": {
+ "p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "dev": true
+ }
+ }
+ },
+ "listr-silent-renderer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
+ "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=",
+ "dev": true
+ },
+ "listr-update-renderer": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz",
+ "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==",
+ "dev": true,
+ "requires": {
+ "chalk": "^1.1.3",
+ "cli-truncate": "^0.2.1",
+ "elegant-spinner": "^1.0.1",
+ "figures": "^1.7.0",
+ "indent-string": "^3.0.0",
+ "log-symbols": "^1.0.2",
+ "log-update": "^2.3.0",
+ "strip-ansi": "^3.0.1"
+ },
+ "dependencies": {
+ "figures": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.5",
+ "object-assign": "^4.1.0"
+ }
+ },
+ "log-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
+ "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
+ "dev": true,
+ "requires": {
+ "chalk": "^1.0.0"
+ }
+ }
+ }
+ },
+ "listr-verbose-renderer": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz",
+ "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.1",
+ "cli-cursor": "^2.1.0",
+ "date-fns": "^1.27.2",
+ "figures": "^2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "load-json-file": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "strip-bom": "^3.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "loader-fs-cache": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz",
+ "integrity": "sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==",
+ "dev": true,
+ "requires": {
+ "find-cache-dir": "^0.1.1",
+ "mkdirp": "^0.5.1"
+ },
+ "dependencies": {
+ "find-cache-dir": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz",
+ "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "mkdirp": "^0.5.1",
+ "pkg-dir": "^1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "dev": true,
+ "requires": {
+ "path-exists": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "dev": true,
+ "requires": {
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
+ "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
+ "dev": true,
+ "requires": {
+ "find-up": "^1.0.0"
+ }
+ }
+ }
+ },
+ "loader-runner": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
+ "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+ "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^2.0.0",
+ "json5": "^1.0.1"
+ },
+ "dependencies": {
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ }
+ }
+ },
+ "localforage": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz",
+ "integrity": "sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==",
+ "requires": {
+ "lie": "3.1.1"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "dev": true,
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+ "dev": true
+ },
+ "lodash.unescape": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
+ "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
+ "dev": true
+ },
+ "log-symbols": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
+ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "log-update": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz",
+ "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=",
+ "dev": true,
+ "requires": {
+ "ansi-escapes": "^3.0.0",
+ "cli-cursor": "^2.0.0",
+ "wrap-ansi": "^3.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ },
+ "wrap-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz",
+ "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=",
+ "dev": true,
+ "requires": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0"
+ }
+ }
+ }
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "requires": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dev": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+ "dev": true
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "dev": true,
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "matcher": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz",
+ "integrity": "sha512-+BmqxWIubKTRKNWx/ahnCkk3mG8m7OturVlqq6HiojGJTd5hVYbgZm6WzcYPCoB+KBT4Vd6R7WSRG2OADNaCjg==",
+ "dev": true,
+ "requires": {
+ "escape-string-regexp": "^1.0.4"
+ }
+ },
+ "md5.js": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+ "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "memory-fs": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+ "dev": true,
+ "requires": {
+ "errno": "^0.1.3",
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "microseconds": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.1.0.tgz",
+ "integrity": "sha1-R9x7z2IXG4Aw4hUv2C8SpolKcRk="
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "brorand": "^1.0.1"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "dev": true
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+ "dev": true
+ },
+ "mississippi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+ "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+ "dev": true,
+ "requires": {
+ "concat-stream": "^1.5.0",
+ "duplexify": "^3.4.2",
+ "end-of-stream": "^1.1.0",
+ "flush-write-stream": "^1.0.0",
+ "from2": "^2.1.0",
+ "parallel-transform": "^1.1.0",
+ "pump": "^3.0.0",
+ "pumpify": "^1.3.3",
+ "stream-each": "^1.1.0",
+ "through2": "^2.0.0"
+ }
+ },
+ "mixin-deep": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "move-concurrently": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "copy-concurrently": "^1.0.0",
+ "fs-write-stream-atomic": "^1.0.8",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.3"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "mute-stream": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+ "dev": true
+ },
+ "nan": {
+ "version": "2.14.1",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
+ "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
+ "dev": true,
+ "optional": true
+ },
+ "nano-time": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
+ "integrity": "sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=",
+ "requires": {
+ "big-integer": "^1.6.16"
+ }
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ }
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+ "dev": true
+ },
+ "neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
+ "node-libs-browser": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
+ "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
+ "dev": true,
+ "requires": {
+ "assert": "^1.1.1",
+ "browserify-zlib": "^0.2.0",
+ "buffer": "^4.3.0",
+ "console-browserify": "^1.1.0",
+ "constants-browserify": "^1.0.0",
+ "crypto-browserify": "^3.11.0",
+ "domain-browser": "^1.1.1",
+ "events": "^3.0.0",
+ "https-browserify": "^1.0.0",
+ "os-browserify": "^0.3.0",
+ "path-browserify": "0.0.1",
+ "process": "^0.11.10",
+ "punycode": "^1.2.4",
+ "querystring-es3": "^0.2.0",
+ "readable-stream": "^2.3.3",
+ "stream-browserify": "^2.0.1",
+ "stream-http": "^2.7.2",
+ "string_decoder": "^1.0.0",
+ "timers-browserify": "^2.0.4",
+ "tty-browserify": "0.0.0",
+ "url": "^0.11.0",
+ "util": "^0.11.0",
+ "vm-browserify": "^1.0.1"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ }
+ }
+ },
+ "node-releases": {
+ "version": "1.1.16",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.16.tgz",
+ "integrity": "sha512-BOMWCW9CaT4sffMa5S9Mj4vYObvVShyo6JoM9WzzQOKVyNUn1+OVMUaQT3fo2tJKCMwHjqaDW/Pf3/JsYmPD2g==",
+ "dev": true,
+ "requires": {
+ "semver": "^5.3.0"
+ }
+ },
+ "normalize-package-data": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "is-builtin-module": "^1.0.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "optional": true
+ },
+ "npm-path": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz",
+ "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==",
+ "dev": true,
+ "requires": {
+ "which": "^1.2.10"
+ }
+ },
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "dev": true,
+ "requires": {
+ "path-key": "^2.0.0"
+ }
+ },
+ "npm-which": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz",
+ "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=",
+ "dev": true,
+ "requires": {
+ "commander": "^2.9.0",
+ "npm-path": "^2.0.2",
+ "which": "^1.2.10"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "dev": true,
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "object-hash": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz",
+ "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==",
+ "dev": true
+ },
+ "object-keys": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
+ "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==",
+ "dev": true
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.0"
+ }
+ },
+ "object.fromentries": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz",
+ "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.11.0",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.1"
+ }
+ },
+ "object.getownpropertydescriptors": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.5.1"
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^1.0.0"
+ }
+ },
+ "optionator": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+ "dev": true,
+ "requires": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.4",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "wordwrap": "~1.0.0"
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+ "dev": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true
+ },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+ "dev": true
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "dev": true,
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-map": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
+ "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
+ "dev": true
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "dev": true
+ },
+ "pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "dev": true
+ },
+ "parallel-transform": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
+ "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
+ "dev": true,
+ "requires": {
+ "cyclist": "^1.0.1",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.1.5"
+ }
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "parse-asn1": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz",
+ "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==",
+ "dev": true,
+ "requires": {
+ "asn1.js": "^5.2.0",
+ "browserify-aes": "^1.0.0",
+ "evp_bytestokey": "^1.0.0",
+ "pbkdf2": "^3.0.3",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.2.0"
+ }
+ },
+ "parse-passwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+ "dev": true
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+ "dev": true
+ },
+ "path-browserify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+ "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
+ "dev": true
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+ "dev": true,
+ "optional": true
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+ },
+ "path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "path-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+ "dev": true,
+ "requires": {
+ "pify": "^2.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "pbkdf2": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
+ "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
+ "dev": true,
+ "requires": {
+ "create-hash": "^1.1.2",
+ "create-hmac": "^1.1.4",
+ "ripemd160": "^2.0.1",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "picomatch": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
+ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+ "dev": true,
+ "optional": true
+ },
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+ "dev": true
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "dev": true,
+ "requires": {
+ "pinkie": "^2.0.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.1.0"
+ }
+ },
+ "please-upgrade-node": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz",
+ "integrity": "sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==",
+ "dev": true,
+ "requires": {
+ "semver-compare": "^1.0.0"
+ }
+ },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+ "dev": true
+ },
+ "prettier": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz",
+ "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==",
+ "dev": true
+ },
+ "private": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
+ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
+ "dev": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true
+ },
+ "progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true
+ },
+ "promise-inflight": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
+ "dev": true
+ },
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "dev": true,
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
+ "property-expr": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-1.5.1.tgz",
+ "integrity": "sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==",
+ "dev": true
+ },
+ "prr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+ "dev": true
+ },
+ "public-encrypt": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+ "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "parse-asn1": "^5.0.0",
+ "randombytes": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.9",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
+ "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
+ "dev": true
+ }
+ }
+ },
+ "pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
+ "requires": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ },
+ "dependencies": {
+ "pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ }
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "dev": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+ "dev": true
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.0.5",
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "react-is": {
+ "version": "16.8.6",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
+ "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==",
+ "dev": true
+ },
+ "read-pkg": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "^2.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^2.0.0"
+ }
+ },
+ "read-pkg-up": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.0.0",
+ "read-pkg": "^2.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
+ "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "regenerate": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+ "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
+ "dev": true
+ },
+ "regenerate-unicode-properties": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.2.tgz",
+ "integrity": "sha512-SbA/iNrBUf6Pv2zU8Ekv1Qbhv92yxL4hiDa2siuxs4KKn4oOoMDHXjAf7+Nz9qinUQ46B1LcWEi/PhJfPWpZWQ==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.0"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
+ "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
+ },
+ "regenerator-transform": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.4.tgz",
+ "integrity": "sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A==",
+ "dev": true,
+ "requires": {
+ "private": "^0.1.6"
+ }
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "regexp-tree": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.5.tgz",
+ "integrity": "sha512-nUmxvfJyAODw+0B13hj8CFVAxhe7fDEAgJgaotBu3nnR+IgGgZq59YedJP5VYTlkEfqjuK6TuRpnymKdatLZfQ==",
+ "dev": true
+ },
+ "regexpp": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+ "dev": true
+ },
+ "regexpu-core": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz",
+ "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.4.0",
+ "regenerate-unicode-properties": "^8.0.2",
+ "regjsgen": "^0.5.0",
+ "regjsparser": "^0.6.0",
+ "unicode-match-property-ecmascript": "^1.0.4",
+ "unicode-match-property-value-ecmascript": "^1.1.0"
+ }
+ },
+ "regjsgen": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz",
+ "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==",
+ "dev": true
+ },
+ "regjsparser": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz",
+ "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==",
+ "dev": true,
+ "requires": {
+ "jsesc": "~0.5.0"
+ }
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+ "dev": true,
+ "optional": true
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+ "dev": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+ "dev": true
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true
+ },
+ "requireindex": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
+ "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
+ "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ },
+ "resolve-cwd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+ "dev": true,
+ "requires": {
+ "resolve-from": "^3.0.0"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+ "dev": true
+ }
+ }
+ },
+ "resolve-dir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+ "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
+ "dev": true,
+ "requires": {
+ "expand-tilde": "^2.0.0",
+ "global-modules": "^1.0.0"
+ },
+ "dependencies": {
+ "global-modules": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+ "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
+ "dev": true,
+ "requires": {
+ "global-prefix": "^1.0.1",
+ "is-windows": "^1.0.1",
+ "resolve-dir": "^1.0.0"
+ }
+ }
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+ "dev": true
+ },
+ "restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+ "dev": true,
+ "requires": {
+ "onetime": "^2.0.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
+ "run-async": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+ "dev": true,
+ "requires": {
+ "is-promise": "^2.1.0"
+ }
+ },
+ "run-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz",
+ "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==",
+ "dev": true
+ },
+ "run-queue": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1"
+ }
+ },
+ "rxjs": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.1.tgz",
+ "integrity": "sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "dev": true,
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
+ "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-keywords": "^3.1.0"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
+ "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-keywords": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz",
+ "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==",
+ "dev": true
+ },
+ "fast-deep-equal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ }
+ }
+ },
+ "semver": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
+ "dev": true
+ },
+ "semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
+ "dev": true
+ },
+ "serialize-javascript": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
+ "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "set-value": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+ "dev": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true
+ },
+ "simple-git": {
+ "version": "1.110.0",
+ "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.110.0.tgz",
+ "integrity": "sha512-UYY0rQkknk0P5eb+KW+03F4TevZ9ou0H+LoGaj7iiVgpnZH4wdj/HTViy/1tNNkmIPcmtxuBqXWiYt2YwlRKOQ==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.0.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+ "dev": true
+ }
+ }
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+ "dev": true
+ },
+ "slice-ansi": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+ "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "astral-regex": "^1.0.0",
+ "is-fullwidth-code-point": "^2.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ }
+ }
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "dev": true,
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.2.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "source-list-map": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
+ "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ },
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+ "dev": true,
+ "requires": {
+ "atob": "^2.1.1",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-support": {
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+ "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+ "dev": true
+ },
+ "spdx-correct": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+ "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+ "dev": true,
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+ "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+ "dev": true,
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz",
+ "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==",
+ "dev": true
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "ssri": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+ "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+ "dev": true,
+ "requires": {
+ "figgy-pudding": "^3.5.1"
+ }
+ },
+ "staged-git-files": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-1.1.2.tgz",
+ "integrity": "sha512-0Eyrk6uXW6tg9PYkhi/V/J4zHp33aNyi2hOCmhFLqLTIhbgqWn5jlSzI+IU0VqrZq6+DbHcabQl/WP6P3BG0QA==",
+ "dev": true
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "dev": true,
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "stream-browserify": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+ "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+ "dev": true,
+ "requires": {
+ "inherits": "~2.0.1",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "stream-each": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+ "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "stream-http": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+ "dev": true,
+ "requires": {
+ "builtin-status-codes": "^3.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.3.6",
+ "to-arraybuffer": "^1.0.0",
+ "xtend": "^4.0.0"
+ }
+ },
+ "stream-shift": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+ "dev": true
+ },
+ "string-argv": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz",
+ "integrity": "sha1-2sMECGkMIfPDYwo/86BYd73L1zY=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ }
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "stringify-object": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
+ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+ "dev": true,
+ "requires": {
+ "get-own-enumerable-property-symbols": "^3.0.0",
+ "is-obj": "^1.0.1",
+ "is-regexp": "^1.0.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true
+ },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "http://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ },
+ "symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
+ "dev": true
+ },
+ "synchronous-promise": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.7.tgz",
+ "integrity": "sha512-16GbgwTmFMYFyQMLvtQjvNWh30dsFe1cAW5Fg1wm5+dg84L9Pe36mftsIRU95/W2YsISxsz/xq4VB23sqpgb/A==",
+ "dev": true
+ },
+ "table": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz",
+ "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.9.1",
+ "lodash": "^4.17.11",
+ "slice-ansi": "^2.1.0",
+ "string-width": "^3.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "tapable": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==",
+ "dev": true
+ },
+ "terser": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
+ "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.20.0",
+ "source-map": "~0.6.1",
+ "source-map-support": "~0.5.12"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "terser-webpack-plugin": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
+ "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
+ "dev": true,
+ "requires": {
+ "cacache": "^12.0.2",
+ "find-cache-dir": "^2.1.0",
+ "is-wsl": "^1.1.0",
+ "schema-utils": "^1.0.0",
+ "serialize-javascript": "^4.0.0",
+ "source-map": "^0.6.1",
+ "terser": "^4.1.2",
+ "webpack-sources": "^1.4.0",
+ "worker-farm": "^1.7.0"
+ },
+ "dependencies": {
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+ "dev": true
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
+ },
+ "through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
+ "timers-browserify": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
+ "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
+ "dev": true,
+ "requires": {
+ "setimmediate": "^1.0.4"
+ }
+ },
+ "tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dev": true,
+ "requires": {
+ "os-tmpdir": "~1.0.2"
+ }
+ },
+ "to-arraybuffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+ "dev": true
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ }
+ },
+ "toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=",
+ "dev": true
+ },
+ "trim-right": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
+ "dev": true
+ },
+ "tslib": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
+ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
+ "dev": true
+ },
+ "tsutils": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.10.0.tgz",
+ "integrity": "sha512-q20XSMq7jutbGB8luhKKsQldRKWvyBO2BGqni3p4yq8Ys9bEP/xQw3KepKmMRt9gJ4lvQSScrihJrcKdKoSU7Q==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.8.1"
+ }
+ },
+ "tty-browserify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+ "dev": true
+ },
+ "type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2"
+ }
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+ "dev": true
+ },
+ "typescript": {
+ "version": "3.4.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz",
+ "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==",
+ "dev": true
+ },
+ "unicode-canonical-property-names-ecmascript": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
+ "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
+ "dev": true
+ },
+ "unicode-match-property-ecmascript": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
+ "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
+ "dev": true,
+ "requires": {
+ "unicode-canonical-property-names-ecmascript": "^1.0.4",
+ "unicode-property-aliases-ecmascript": "^1.0.4"
+ }
+ },
+ "unicode-match-property-value-ecmascript": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz",
+ "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==",
+ "dev": true
+ },
+ "unicode-property-aliases-ecmascript": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz",
+ "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==",
+ "dev": true
+ },
+ "union-value": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^2.0.1"
+ }
+ },
+ "unique-filename": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+ "dev": true,
+ "requires": {
+ "unique-slug": "^2.0.0"
+ }
+ },
+ "unique-slug": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+ "dev": true,
+ "requires": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
+ "unload": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/unload/-/unload-2.1.0.tgz",
+ "integrity": "sha512-vOg/orTFrHv60iWLZbBpgrgoFaSovkcgQJUmBHNGFWlSFdwtoANZaT3uSePVhggkWSsPxs2rpBl5LHpmcSGjRw==",
+ "requires": {
+ "@babel/runtime": "7.1.5",
+ "detect-node": "2.0.4"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.5.tgz",
+ "integrity": "sha512-xKnPpXG/pvK1B90JkwwxSGii90rQGKtzcMt2gI5G6+M0REXaq6rOHsGC2ay6/d0Uje7zzvSzjEzfR3ENhFlrfA==",
+ "requires": {
+ "regenerator-runtime": "^0.12.0"
+ }
+ }
+ }
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "dev": true,
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+ "dev": true
+ }
+ }
+ },
+ "upath": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+ "dev": true,
+ "optional": true
+ },
+ "uri-js": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+ "dev": true
+ },
+ "url": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dev": true,
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+ "dev": true
+ }
+ }
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+ "dev": true
+ },
+ "util": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+ "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "util.promisify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
+ "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "object.getownpropertydescriptors": "^2.0.3"
+ }
+ },
+ "uuid": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+ },
+ "v8-compile-cache": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
+ "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
+ "dev": true
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "requires": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "vm-browserify": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
+ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
+ "dev": true
+ },
+ "watchpack": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz",
+ "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^3.4.1",
+ "graceful-fs": "^4.1.2",
+ "neo-async": "^2.5.0",
+ "watchpack-chokidar2": "^2.0.0"
+ }
+ },
+ "watchpack-chokidar2": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz",
+ "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chokidar": "^2.1.8"
+ },
+ "dependencies": {
+ "anymatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "micromatch": "^3.1.4",
+ "normalize-path": "^2.1.1"
+ },
+ "dependencies": {
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ }
+ }
+ },
+ "binary-extensions": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
+ "dev": true,
+ "optional": true
+ },
+ "chokidar": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+ "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.1",
+ "braces": "^2.3.2",
+ "fsevents": "^1.2.7",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.3",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "normalize-path": "^3.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.2.1",
+ "upath": "^1.1.1"
+ }
+ },
+ "fsevents": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
+ "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "bindings": "^1.5.0",
+ "nan": "^2.12.1"
+ }
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "readdirp": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+ "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "graceful-fs": "^4.1.11",
+ "micromatch": "^3.1.10",
+ "readable-stream": "^2.0.2"
+ }
+ }
+ }
+ },
+ "webpack": {
+ "version": "4.44.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz",
+ "integrity": "sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.9.0",
+ "@webassemblyjs/helper-module-context": "1.9.0",
+ "@webassemblyjs/wasm-edit": "1.9.0",
+ "@webassemblyjs/wasm-parser": "1.9.0",
+ "acorn": "^6.4.1",
+ "ajv": "^6.10.2",
+ "ajv-keywords": "^3.4.1",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^4.3.0",
+ "eslint-scope": "^4.0.3",
+ "json-parse-better-errors": "^1.0.2",
+ "loader-runner": "^2.4.0",
+ "loader-utils": "^1.2.3",
+ "memory-fs": "^0.4.1",
+ "micromatch": "^3.1.10",
+ "mkdirp": "^0.5.3",
+ "neo-async": "^2.6.1",
+ "node-libs-browser": "^2.2.1",
+ "schema-utils": "^1.0.0",
+ "tapable": "^1.1.3",
+ "terser-webpack-plugin": "^1.4.3",
+ "watchpack": "^1.7.4",
+ "webpack-sources": "^1.4.1"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "6.12.4",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
+ "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
+ "webpack-cli": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz",
+ "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.2",
+ "cross-spawn": "^6.0.5",
+ "enhanced-resolve": "^4.1.1",
+ "findup-sync": "^3.0.0",
+ "global-modules": "^2.0.0",
+ "import-local": "^2.0.0",
+ "interpret": "^1.4.0",
+ "loader-utils": "^1.4.0",
+ "supports-color": "^6.1.0",
+ "v8-compile-cache": "^2.1.1",
+ "yargs": "^13.3.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "dependencies": {
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true
+ },
+ "json5": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "loader-utils": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+ "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "dev": true,
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^1.0.1"
+ }
+ },
+ "supports-color": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+ "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
+ "webpack-sources": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
+ "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
+ "dev": true,
+ "requires": {
+ "source-list-map": "^2.0.0",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+ "dev": true
+ },
+ "worker-farm": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
+ "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
+ "dev": true,
+ "requires": {
+ "errno": "~0.1.7"
+ }
+ },
+ "worker-loader": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz",
+ "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.0.0",
+ "schema-utils": "^0.4.0"
+ }
+ },
+ "wrap-ansi": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+ "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "string-width": "^3.0.0",
+ "strip-ansi": "^5.0.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "write": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+ "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+ "dev": true,
+ "requires": {
+ "mkdirp": "^0.5.1"
+ }
+ },
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "yargs": {
+ "version": "13.3.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
+ "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^5.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^3.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^13.1.2"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+ "dev": true
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true
+ },
+ "string-width": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+ "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+ "dev": true,
+ "requires": {
+ "emoji-regex": "^7.0.1",
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^4.1.0"
+ }
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "13.1.2",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+ "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ }
+ },
+ "yup": {
+ "version": "0.26.10",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-0.26.10.tgz",
+ "integrity": "sha512-keuNEbNSnsOTOuGCt3UJW69jDE3O4P+UHAakO7vSeFMnjaitcmlbij/a3oNb9g1Y1KvSKH/7O1R2PQ4m4TRylw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "7.0.0",
+ "fn-name": "~2.0.1",
+ "lodash": "^4.17.10",
+ "property-expr": "^1.5.0",
+ "synchronous-promise": "^2.0.5",
+ "toposort": "^2.0.2"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
+ "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
+ "dev": true,
+ "requires": {
+ "regenerator-runtime": "^0.12.0"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/example/web/tdweb/package.json b/protocols/Telegram/tdlib/td/example/web/tdweb/package.json
new file mode 100644
index 0000000000..54ed3f2f96
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/tdweb/package.json
@@ -0,0 +1,102 @@
+{
+ "name": "tdweb",
+ "version": "1.8.1",
+ "description": "JavaScript interface for TDLib (Telegram library)",
+ "main": "dist/tdweb.js",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/tdlib/td.git",
+ "directory": "example/web/tdweb"
+ },
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "webpack --mode production",
+ "start": "webpack-dev-server --open"
+ },
+ "keywords": [
+ "telegram"
+ ],
+ "author": "Arseny Smirnov",
+ "license": "MIT",
+ "devDependencies": {
+ "@babel/core": "^7.4.3",
+ "@babel/plugin-syntax-dynamic-import": "^7.2.0",
+ "@babel/plugin-transform-runtime": "^7.4.3",
+ "@babel/preset-env": "^7.4.3",
+ "@typescript-eslint/eslint-plugin": "^1.7.0",
+ "acorn": "^6.4.1",
+ "babel-eslint": "^10.0.1",
+ "babel-loader": "^8.0.5",
+ "clean-webpack-plugin": "^2.0.1",
+ "eslint": "^5.16.0",
+ "eslint-config-react-app": "^4.0.0",
+ "eslint-loader": "^2.1.2",
+ "eslint-plugin-flowtype": "^2.0.0",
+ "eslint-plugin-import": "^2.17.2",
+ "eslint-plugin-jsx-a11y": "^6.2.1",
+ "eslint-plugin-react": "^7.12.4",
+ "eslint-plugin-react-hooks": "^1.6.0",
+ "file-loader": "^3.0.1",
+ "husky": "^1.3.1",
+ "lint-staged": "^8.1.5",
+ "prettier": "^1.17.0",
+ "typescript": "^3.4.5",
+ "webpack": "^4.44.1",
+ "webpack-cli": "^3.3.12",
+ "worker-loader": "^2.0.0"
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "lint-staged": {
+ "linters": {
+ "webpack.config.json": [
+ "prettier --single-quote --write",
+ "git add"
+ ],
+ "package.json": [
+ "prettier --single-quote --write",
+ "git add"
+ ],
+ "src/*.{js,jsx,json,css}": [
+ "prettier --single-quote --write",
+ "git add"
+ ]
+ }
+ },
+ "dependencies": {
+ "@babel/runtime": "^7.4.3",
+ "broadcast-channel": "^2.1.12",
+ "localforage": "^1.7.3",
+ "uuid": "^3.3.2"
+ },
+ "babel": {
+ "presets": [
+ "@babel/env"
+ ],
+ "plugins": [
+ "@babel/syntax-dynamic-import",
+ "@babel/transform-runtime"
+ ]
+ },
+ "eslintConfig": {
+ "extends": "eslint-config-react-app",
+ "env": {
+ "worker": true,
+ "node": true,
+ "browser": true
+ },
+ "globals": {
+ "WebAssembly": true
+ },
+ "settings": {
+ "react": {
+ "version": "999.999.999"
+ }
+ }
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/example/web/tdweb/src/index.js b/protocols/Telegram/tdlib/td/example/web/tdweb/src/index.js
new file mode 100644
index 0000000000..2f159b9424
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/tdweb/src/index.js
@@ -0,0 +1,680 @@
+import MyWorker from './worker.js';
+//import localforage from 'localforage';
+import BroadcastChannel from 'broadcast-channel';
+import uuid4 from 'uuid/v4';
+import log from './logger.js';
+
+const sleep = ms => new Promise(res => setTimeout(res, ms));
+
+/**
+ * TDLib in a browser
+ *
+ * TDLib can be compiled to WebAssembly or asm.js using Emscripten compiler and used in a browser from JavaScript.
+ * This is a convenient wrapper for TDLib in a browser which controls TDLib instance creation, handles interaction
+ * with TDLib and manages a filesystem for persistent TDLib data.
+ * TDLib instance is created in a Web Worker to run it in a separate thread.
+ * TdClient just sends queries to the Web Worker and receives updates and results from it.
+ * <br>
+ * <br>
+ * Differences from the TDLib JSON API:<br>
+ * 1. Added the update <code>updateFatalError error:string = Update;</code> which is sent whenever TDLib encounters a fatal error.<br>
+ * 2. Added the method <code>setJsLogVerbosityLevel new_verbosity_level:string = Ok;</code>, which allows to change the verbosity level of tdweb logging.<br>
+ * 3. Added the possibility to use blobs as input files via the constructor <code>inputFileBlob data:<JavaScript blob> = InputFile;</code>.<br>
+ * 4. The class <code>filePart</code> contains data as a JavaScript blob instead of a base64-encoded string.<br>
+ * 5. The methods <code>getStorageStatistics</code>, <code>getStorageStatisticsFast</code>, <code>optimizeStorage</code>, <code>addProxy</code> and <code>getFileDownloadedPrefixSize</code> are not supported.<br>
+ * <br>
+ */
+class TdClient {
+ /**
+ * @callback TdClient~updateCallback
+ * @param {Object} update The update.
+ */
+
+ /**
+ * Create TdClient.
+ * @param {Object} options - Options for TDLib instance creation.
+ * @param {TdClient~updateCallback} options.onUpdate - Callback for all incoming updates.
+ * @param {string} [options.instanceName=tdlib] - Name of the TDLib instance. Currently only one instance of TdClient with a given name is allowed. All but one instances with the same name will be automatically closed. Usually, the newest non-background instance is kept alive. Files will be stored in an IndexedDb table with the same name.
+ * @param {boolean} [options.isBackground=false] - Pass true if the instance is opened from the background.
+ * @param {string} [options.jsLogVerbosityLevel=info] - The initial verbosity level of the JavaScript part of the code (one of 'error', 'warning', 'info', 'log', 'debug').
+ * @param {number} [options.logVerbosityLevel=2] - The initial verbosity level for the TDLib internal logging (0-1023).
+ * @param {boolean} [options.useDatabase=true] - Pass false to use TDLib without database and secret chats. It will significantly improve loading time, but some functionality will be unavailable.
+ * @param {boolean} [options.readOnly=false] - For debug only. Pass true to open TDLib database in read-only mode
+ * @param {string} [options.mode=auto] - For debug only. The type of the TDLib build to use. 'asmjs' for asm.js and 'wasm' for WebAssembly. If mode == 'auto' WebAbassembly will be used if supported by browser, asm.js otherwise.
+ */
+ constructor(options) {
+ log.setVerbosity(options.jsLogVerbosityLevel);
+ this.worker = new MyWorker();
+ this.worker.onmessage = e => {
+ this.onResponse(e.data);
+ };
+ this.query_id = 0;
+ this.query_callbacks = new Map();
+ if ('onUpdate' in options) {
+ this.onUpdate = options.onUpdate;
+ delete options.onUpdate;
+ }
+ options.instanceName = options.instanceName || 'tdlib';
+ this.fileManager = new FileManager(options.instanceName, this);
+ this.worker.postMessage({ '@type': 'init', options: options });
+ this.closeOtherClients(options);
+ }
+
+ /**
+ * Send a query to TDLib.
+ *
+ * If the query contains the field '@extra', the same field will be added into the result.
+ *
+ * @param {Object} query - The query for TDLib. See the [td_api.tl]{@link https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl} scheme or
+ * the automatically generated [HTML documentation]{@link https://core.telegram.org/tdlib/docs/td__api_8h.html}
+ * for a list of all available TDLib [methods]{@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html} and
+ * [classes]{@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html}.
+ * @returns {Promise} Promise object represents the result of the query.
+ */
+ send(query) {
+ return this.doSend(query, true);
+ }
+
+ /** @private */
+ sendInternal(query) {
+ return this.doSend(query, false);
+ }
+ /** @private */
+ doSend(query, isExternal) {
+ this.query_id++;
+ if (query['@extra']) {
+ query['@extra'] = {
+ '@old_extra': JSON.parse(JSON.stringify(query['@extra'])),
+ query_id: this.query_id
+ };
+ } else {
+ query['@extra'] = {
+ query_id: this.query_id
+ };
+ }
+ if (query['@type'] === 'setJsLogVerbosityLevel') {
+ log.setVerbosity(query.new_verbosity_level);
+ }
+
+ log.debug('send to worker: ', query);
+ const res = new Promise((resolve, reject) => {
+ this.query_callbacks.set(this.query_id, [resolve, reject]);
+ });
+ if (isExternal) {
+ this.externalPostMessage(query);
+ } else {
+ this.worker.postMessage(query);
+ }
+ return res;
+ }
+
+ /** @private */
+ externalPostMessage(query) {
+ const unsupportedMethods = [
+ 'getStorageStatistics',
+ 'getStorageStatisticsFast',
+ 'optimizeStorage',
+ 'addProxy',
+ 'init',
+ 'start'
+ ];
+ if (unsupportedMethods.includes(query['@type'])) {
+ this.onResponse({
+ '@type': 'error',
+ '@extra': query['@extra'],
+ code: 400,
+ message: "Method '" + query['@type'] + "' is not supported"
+ });
+ return;
+ }
+ if (query['@type'] === 'readFile' || query['@type'] === 'readFilePart') {
+ this.readFile(query);
+ return;
+ }
+ if (query['@type'] === 'deleteFile') {
+ this.deleteFile(query);
+ return;
+ }
+ this.worker.postMessage(query);
+ }
+
+ /** @private */
+ async readFile(query) {
+ const response = await this.fileManager.readFile(query);
+ this.onResponse(response);
+ }
+
+ /** @private */
+ async deleteFile(query) {
+ const response = this.fileManager.deleteFile(query);
+ try {
+ if (response.idb_key) {
+ await this.sendInternal({
+ '@type': 'deleteIdbKey',
+ idb_key: response.idb_key
+ });
+ delete response.idb_key;
+ }
+ await this.sendInternal({
+ '@type': 'deleteFile',
+ file_id: query.file_id
+ });
+ } catch (e) {}
+ this.onResponse(response);
+ }
+
+ /** @private */
+ onResponse(response) {
+ log.debug(
+ 'receive from worker: ',
+ JSON.parse(
+ JSON.stringify(response, (key, value) => {
+ if (key === 'arr' || key === 'data') {
+ return undefined;
+ }
+ return value;
+ })
+ )
+ );
+
+ // for FileManager
+ response = this.prepareResponse(response);
+
+ if ('@extra' in response) {
+ const query_id = response['@extra'].query_id;
+ const [resolve, reject] = this.query_callbacks.get(query_id);
+ this.query_callbacks.delete(query_id);
+ if ('@old_extra' in response['@extra']) {
+ response['@extra'] = response['@extra']['@old_extra'];
+ }
+ if (resolve) {
+ if (response['@type'] === 'error') {
+ reject(response);
+ } else {
+ resolve(response);
+ }
+ }
+ } else {
+ if (response['@type'] === 'inited') {
+ this.onInited();
+ return;
+ }
+ if (response['@type'] === 'fsInited') {
+ this.onFsInited();
+ return;
+ }
+ if (
+ response['@type'] === 'updateAuthorizationState' &&
+ response.authorization_state['@type'] === 'authorizationStateClosed'
+ ) {
+ this.onClosed();
+ }
+ this.onUpdate(response);
+ }
+ }
+
+ /** @private */
+ prepareFile(file) {
+ return this.fileManager.registerFile(file);
+ }
+
+ /** @private */
+ prepareResponse(response) {
+ if (response['@type'] === 'file') {
+ if (false && Math.random() < 0.1) {
+ (async () => {
+ log.warn('DELETE FILE', response.id);
+ try {
+ await this.send({ '@type': 'deleteFile', file_id: response.id });
+ } catch (e) {}
+ })();
+ }
+ return this.prepareFile(response);
+ }
+ for (const key in response) {
+ const field = response[key];
+ if (
+ field &&
+ typeof field === 'object' &&
+ key !== 'data' &&
+ key !== 'arr'
+ ) {
+ response[key] = this.prepareResponse(field);
+ }
+ }
+ return response;
+ }
+
+ /** @private */
+ onBroadcastMessage(e) {
+ //const message = e.data;
+ const message = e;
+ if (message.uid === this.uid) {
+ log.info('ignore self broadcast message: ', message);
+ return;
+ }
+ log.info('got broadcast message: ', message);
+ if (message.isBackground && !this.isBackground) {
+ // continue
+ } else if (
+ (!message.isBackground && this.isBackground) ||
+ message.timestamp > this.timestamp
+ ) {
+ this.close();
+ return;
+ }
+ if (message.state === 'closed') {
+ this.waitSet.delete(message.uid);
+ if (this.waitSet.size === 0) {
+ log.info('onWaitSetEmpty');
+ this.onWaitSetEmpty();
+ this.onWaitSetEmpty = () => {};
+ }
+ } else {
+ this.waitSet.add(message.uid);
+ if (message.state !== 'closing') {
+ this.postState();
+ }
+ }
+ }
+
+ /** @private */
+ postState() {
+ const state = {
+ uid: this.uid,
+ state: this.state,
+ timestamp: this.timestamp,
+ isBackground: this.isBackground
+ };
+ log.info('Post state: ', state);
+ this.channel.postMessage(state);
+ }
+
+ /** @private */
+ onWaitSetEmpty() {
+ // nop
+ }
+
+ /** @private */
+ onFsInited() {
+ this.fileManager.init();
+ }
+
+ /** @private */
+ onInited() {
+ this.isInited = true;
+ this.doSendStart();
+ }
+
+ /** @private */
+ sendStart() {
+ this.wantSendStart = true;
+ this.doSendStart();
+ }
+
+ /** @private */
+ doSendStart() {
+ if (!this.isInited || !this.wantSendStart || this.state !== 'start') {
+ return;
+ }
+ this.wantSendStart = false;
+ this.state = 'active';
+ const query = { '@type': 'start' };
+ log.info('send to worker: ', query);
+ this.worker.postMessage(query);
+ }
+
+ /** @private */
+ onClosed() {
+ this.isClosing = true;
+ this.worker.terminate();
+ log.info('worker is terminated');
+ this.state = 'closed';
+ this.postState();
+ }
+
+ /** @private */
+ close() {
+ if (this.isClosing) {
+ return;
+ }
+ this.isClosing = true;
+
+ log.info('close state: ', this.state);
+
+ if (this.state === 'start') {
+ this.onClosed();
+ this.onUpdate({
+ '@type': 'updateAuthorizationState',
+ authorization_state: {
+ '@type': 'authorizationStateClosed'
+ }
+ });
+ return;
+ }
+
+ const query = { '@type': 'close' };
+ log.info('send to worker: ', query);
+ this.worker.postMessage(query);
+
+ this.state = 'closing';
+ this.postState();
+ }
+
+ /** @private */
+ async closeOtherClients(options) {
+ this.uid = uuid4();
+ this.state = 'start';
+ this.isBackground = !!options.isBackground;
+ this.timestamp = Date.now();
+ this.waitSet = new Set();
+
+ log.info('close other clients');
+ this.channel = new BroadcastChannel(options.instanceName, {
+ webWorkerSupport: false
+ });
+
+ this.postState();
+
+ this.channel.onmessage = message => {
+ this.onBroadcastMessage(message);
+ };
+
+ await sleep(300);
+ if (this.waitSet.size !== 0) {
+ await new Promise(resolve => {
+ this.onWaitSetEmpty = resolve;
+ });
+ }
+ this.sendStart();
+ }
+
+ /** @private */
+ onUpdate(update) {
+ log.info('ignore onUpdate');
+ //nop
+ }
+}
+
+/** @private */
+class ListNode {
+ constructor(value) {
+ this.value = value;
+ this.clear();
+ }
+
+ erase() {
+ this.prev.connect(this.next);
+ this.clear();
+ }
+ clear() {
+ this.prev = this;
+ this.next = this;
+ }
+
+ connect(other) {
+ this.next = other;
+ other.prev = this;
+ }
+
+ onUsed(other) {
+ other.usedAt = Date.now();
+ other.erase();
+ other.connect(this.next);
+ log.debug('LRU: used file_id: ', other.value);
+ this.connect(other);
+ }
+
+ getLru() {
+ if (this === this.next) {
+ throw new Error('popLru from empty list');
+ }
+ return this.prev;
+ }
+}
+
+/** @private */
+class FileManager {
+ constructor(instanceName, client) {
+ this.instanceName = instanceName;
+ this.cache = new Map();
+ this.pending = [];
+ this.transaction_id = 0;
+ this.totalSize = 0;
+ this.lru = new ListNode(-1);
+ this.client = client;
+ }
+
+ init() {
+ this.idb = new Promise((resolve, reject) => {
+ const request = indexedDB.open(this.instanceName);
+ request.onsuccess = () => resolve(request.result);
+ request.onerror = () => reject(request.error);
+ });
+ //this.store = localforage.createInstance({
+ //name: instanceName
+ //});
+ this.isInited = true;
+ }
+
+ unload(info) {
+ if (info.arr) {
+ log.debug(
+ 'LRU: delete file_id: ',
+ info.node.value,
+ ' with arr.length: ',
+ info.arr.length
+ );
+ this.totalSize -= info.arr.length;
+ delete info.arr;
+ }
+ if (info.node) {
+ info.node.erase();
+ delete info.node;
+ }
+ }
+
+ registerFile(file) {
+ if (file.idb_key || file.arr) {
+ file.local.is_downloading_completed = true;
+ } else {
+ file.local.is_downloading_completed = false;
+ }
+ let info = {};
+ const cached_info = this.cache.get(file.id);
+ if (cached_info) {
+ info = cached_info;
+ } else {
+ this.cache.set(file.id, info);
+ }
+ if (file.idb_key) {
+ info.idb_key = file.idb_key;
+ delete file.idb_key;
+ } else {
+ delete info.idb_key;
+ }
+ if (file.arr) {
+ const now = Date.now();
+ while (this.totalSize > 100000000) {
+ const node = this.lru.getLru();
+ // immunity for 60 seconds
+ if (node.usedAt + 60 * 1000 > now) {
+ break;
+ }
+ const lru_info = this.cache.get(node.value);
+ this.unload(lru_info);
+ }
+
+ if (info.arr) {
+ log.warn('Got file.arr at least twice for the same file');
+ this.totalSize -= info.arr.length;
+ }
+ info.arr = file.arr;
+ delete file.arr;
+ this.totalSize += info.arr.length;
+ if (!info.node) {
+ log.debug(
+ 'LRU: create file_id: ',
+ file.id,
+ ' with arr.length: ',
+ info.arr.length
+ );
+ info.node = new ListNode(file.id);
+ }
+ this.lru.onUsed(info.node);
+ log.info('Total file.arr size: ', this.totalSize);
+ }
+ info.file = file;
+ return file;
+ }
+
+ async flushLoad() {
+ const pending = this.pending;
+ this.pending = [];
+ const idb = await this.idb;
+ const transaction_id = this.transaction_id++;
+ const read = idb
+ .transaction(['keyvaluepairs'], 'readonly')
+ .objectStore('keyvaluepairs');
+ log.debug('Load group of files from idb', pending.length);
+ for (const query of pending) {
+ const request = read.get(query.key);
+ request.onsuccess = event => {
+ const blob = event.target.result;
+ if (blob) {
+ if (blob.size === 0) {
+ log.error('Got empty blob from db ', query.key);
+ }
+ query.resolve({ data: blob, transaction_id: transaction_id });
+ } else {
+ query.reject();
+ }
+ };
+ request.onerror = () => query.reject(request.error);
+ }
+ }
+
+ load(key, resolve, reject) {
+ if (this.pending.length === 0) {
+ setTimeout(() => {
+ this.flushLoad();
+ }, 1);
+ }
+ this.pending.push({ key: key, resolve: resolve, reject: reject });
+ }
+
+ async doLoadFull(info) {
+ if (info.arr) {
+ return { data: new Blob([info.arr]), transaction_id: -1 };
+ }
+ if (info.idb_key) {
+ const idb_key = info.idb_key;
+ //return this.store.getItem(idb_key);
+ return await new Promise((resolve, reject) => {
+ this.load(idb_key, resolve, reject);
+ });
+ }
+ throw new Error('File is not loaded');
+ }
+ async doLoad(info, offset, size) {
+ if (!info.arr && !info.idb_key && info.file.local.path) {
+ try {
+ const count = await this.client.sendInternal({
+ '@type': 'getFileDownloadedPrefixSize',
+ file_id: info.file.id,
+ offset: offset
+ });
+ //log.error(count, size);
+ if (!size) {
+ size = count.count;
+ } else if (size > count.count) {
+ throw new Error('File not loaded yet');
+ }
+ const res = await this.client.sendInternal({
+ '@type': 'readFilePart',
+ path: info.file.local.path,
+ offset: offset,
+ count: size
+ });
+ res.data = new Blob([res.data]);
+ res.transaction_id = -2;
+ //log.error(res);
+ return res;
+ } catch (e) {
+ log.info('readFilePart failed', info, offset, size, e);
+ }
+ }
+
+ const res = await this.doLoadFull(info);
+
+ // return slice(size, offset + size)
+ const data_size = res.data.size;
+ if (!size) {
+ size = data_size;
+ }
+ if (offset > data_size) {
+ offset = data_size;
+ }
+ res.data = res.data.slice(offset, offset + size);
+ return res;
+ }
+
+ doDelete(info) {
+ this.unload(info);
+ return info.idb_key;
+ }
+
+ async readFile(query) {
+ try {
+ if (!this.isInited) {
+ throw new Error('FileManager is not inited');
+ }
+ const info = this.cache.get(query.file_id);
+ if (!info) {
+ throw new Error('File is not loaded');
+ }
+ if (info.node) {
+ this.lru.onUsed(info.node);
+ }
+ query.offset = query.offset || 0;
+ query.size = query.count || query.size || 0;
+ const response = await this.doLoad(info, query.offset, query.size);
+ return {
+ '@type': 'filePart',
+ '@extra': query['@extra'],
+ data: response.data,
+ transaction_id: response.transaction_id
+ };
+ } catch (e) {
+ return {
+ '@type': 'error',
+ '@extra': query['@extra'],
+ code: 400,
+ message: e
+ };
+ }
+ }
+
+ deleteFile(query) {
+ const res = {
+ '@type': 'ok',
+ '@extra': query['@extra']
+ };
+ try {
+ if (!this.isInited) {
+ throw new Error('FileManager is not inited');
+ }
+ const info = this.cache.get(query.file_id);
+ if (!info) {
+ throw new Error('File is not loaded');
+ }
+ const idb_key = this.doDelete(info);
+ if (idb_key) {
+ res.idb_key = idb_key;
+ }
+ } catch (e) {}
+ return res;
+ }
+}
+
+export default TdClient;
diff --git a/protocols/Telegram/tdlib/td/example/web/tdweb/src/logger.js b/protocols/Telegram/tdlib/td/example/web/tdweb/src/logger.js
new file mode 100644
index 0000000000..95baed0318
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/tdweb/src/logger.js
@@ -0,0 +1,47 @@
+class Logger {
+ constructor() {
+ this.setVerbosity('WARNING');
+ }
+ debug(...str) {
+ if (this.checkVerbosity(4)) {
+ console.log(...str);
+ }
+ }
+ log(...str) {
+ if (this.checkVerbosity(4)) {
+ console.log(...str);
+ }
+ }
+ info(...str) {
+ if (this.checkVerbosity(3)) {
+ console.info(...str);
+ }
+ }
+ warn(...str) {
+ if (this.checkVerbosity(2)) {
+ console.warn(...str);
+ }
+ }
+ error(...str) {
+ if (this.checkVerbosity(1)) {
+ console.error(...str);
+ }
+ }
+ setVerbosity(level, default_level = 'info') {
+ if (level === undefined) {
+ level = default_level;
+ }
+ if (typeof level === 'string') {
+ level =
+ { ERROR: 1, WARNING: 2, INFO: 3, LOG: 4, DEBUG: 4 }[
+ level.toUpperCase()
+ ] || 2;
+ }
+ this.level = level;
+ }
+ checkVerbosity(level) {
+ return this.level >= level;
+ }
+}
+let log = new Logger();
+export default log;
diff --git a/protocols/Telegram/tdlib/td/example/web/tdweb/src/wasm-utils.js b/protocols/Telegram/tdlib/td/example/web/tdweb/src/wasm-utils.js
new file mode 100644
index 0000000000..50447d65b3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/tdweb/src/wasm-utils.js
@@ -0,0 +1,136 @@
+// 1. +++ fetchAndInstantiate() +++ //
+
+// This library function fetches the wasm module at 'url', instantiates it with
+// the given 'importObject', and returns the instantiated object instance
+
+export async function instantiateStreaming(url, importObject) {
+ let result = await WebAssembly.instantiateStreaming(fetch(url), importObject);
+ return result.instance;
+}
+export function fetchAndInstantiate(url, importObject) {
+ return fetch(url)
+ .then(response => response.arrayBuffer())
+ .then(bytes => WebAssembly.instantiate(bytes, importObject))
+ .then(results => results.instance);
+}
+
+// 2. +++ instantiateCachedURL() +++ //
+
+// This library function fetches the wasm Module at 'url', instantiates it with
+// the given 'importObject', and returns a Promise resolving to the finished
+// wasm Instance. Additionally, the function attempts to cache the compiled wasm
+// Module in IndexedDB using 'url' as the key. The entire site's wasm cache (not
+// just the given URL) is versioned by dbVersion and any change in dbVersion on
+// any call to instantiateCachedURL() will conservatively clear out the entire
+// cache to avoid stale modules.
+export function instantiateCachedURL(dbVersion, url, importObject) {
+ const dbName = 'wasm-cache';
+ const storeName = 'wasm-cache';
+
+ // This helper function Promise-ifies the operation of opening an IndexedDB
+ // database and clearing out the cache when the version changes.
+ function openDatabase() {
+ return new Promise((resolve, reject) => {
+ var request = indexedDB.open(dbName, dbVersion);
+ request.onerror = reject.bind(null, 'Error opening wasm cache database');
+ request.onsuccess = () => {
+ resolve(request.result);
+ };
+ request.onupgradeneeded = event => {
+ var db = request.result;
+ if (db.objectStoreNames.contains(storeName)) {
+ console.log(`Clearing out version ${event.oldVersion} wasm cache`);
+ db.deleteObjectStore(storeName);
+ }
+ console.log(`Creating version ${event.newVersion} wasm cache`);
+ db.createObjectStore(storeName);
+ };
+ });
+ }
+
+ // This helper function Promise-ifies the operation of looking up 'url' in the
+ // given IDBDatabase.
+ function lookupInDatabase(db) {
+ return new Promise((resolve, reject) => {
+ var store = db.transaction([storeName]).objectStore(storeName);
+ var request = store.get(url);
+ request.onerror = reject.bind(null, `Error getting wasm module ${url}`);
+ request.onsuccess = event => {
+ if (request.result) resolve(request.result);
+ else reject(`Module ${url} was not found in wasm cache`);
+ };
+ });
+ }
+
+ // This helper function fires off an async operation to store the given wasm
+ // Module in the given IDBDatabase.
+ function storeInDatabase(db, module) {
+ var store = db.transaction([storeName], 'readwrite').objectStore(storeName);
+ var request = store.put(module, url);
+ request.onerror = err => {
+ console.log(`Failed to store in wasm cache: ${err}`);
+ };
+ request.onsuccess = err => {
+ console.log(`Successfully stored ${url} in wasm cache`);
+ };
+ }
+
+ // This helper function fetches 'url', compiles it into a Module,
+ // instantiates the Module with the given import object.
+ function fetchAndInstantiate() {
+ return fetch(url)
+ .then(response => response.arrayBuffer())
+ .then(buffer => WebAssembly.instantiate(buffer, importObject));
+ }
+
+ // With all the Promise helper functions defined, we can now express the core
+ // logic of an IndexedDB cache lookup. We start by trying to open a database.
+ return openDatabase().then(
+ db => {
+ // Now see if we already have a compiled Module with key 'url' in 'db':
+ return lookupInDatabase(db).then(
+ module => {
+ // We do! Instantiate it with the given import object.
+ console.log(`Found ${url} in wasm cache`);
+ return WebAssembly.instantiate(module, importObject);
+ },
+ errMsg => {
+ // Nope! Compile from scratch and then store the compiled Module in 'db'
+ // with key 'url' for next time.
+ console.log(errMsg);
+ return fetchAndInstantiate().then(results => {
+ try {
+ storeInDatabase(db, results.module);
+ } catch (e) {
+ console.log('Failed to store module into db');
+ }
+ return results.instance;
+ });
+ }
+ );
+ },
+ errMsg => {
+ // If opening the database failed (due to permissions or quota), fall back
+ // to simply fetching and compiling the module and don't try to store the
+ // results.
+ console.log(errMsg);
+ return fetchAndInstantiate().then(results => results.instance);
+ }
+ );
+}
+
+export async function instantiateAny(version, url, importObject) {
+ console.log("instantiate");
+ try {
+ return await instantiateStreaming(url, importObject);
+ } catch (e) {
+ console.log("instantiateStreaming failed", e);
+ }
+ try {
+ return await instantiateCachedURL(version, url, importObject);
+ } catch (e) {
+ console.log("instantiateCachedURL failed", e);
+ }
+ throw new Error("can't instantiate wasm");
+}
+
diff --git a/protocols/Telegram/tdlib/td/example/web/tdweb/src/worker.js b/protocols/Telegram/tdlib/td/example/web/tdweb/src/worker.js
new file mode 100644
index 0000000000..dff1845126
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/tdweb/src/worker.js
@@ -0,0 +1,1034 @@
+import localforage from 'localforage';
+import log from './logger.js';
+import { instantiateAny } from './wasm-utils.js';
+
+import td_wasm_release from './prebuilt/release/td_wasm.wasm';
+import td_asmjs_mem_release from './prebuilt/release/td_asmjs.js.mem';
+
+const tdlibVersion = 6;
+const localForageDrivers = [
+ localforage.INDEXEDDB,
+ localforage.LOCALSTORAGE,
+ 'memoryDriver'
+];
+
+async function initLocalForage() {
+ // Implement the driver here.
+ const memoryDriver = {
+ _driver: 'memoryDriver',
+ _initStorage: function(options) {
+ const dbInfo = {};
+ if (options) {
+ for (const i in options) {
+ dbInfo[i] = options[i];
+ }
+ }
+ this._dbInfo = dbInfo;
+ this._map = new Map();
+ },
+ clear: async function() {
+ this._map.clear();
+ },
+ getItem: async function(key) {
+ const value = this._map.get(key);
+ console.log('getItem', this._map, key, value);
+ return value;
+ },
+ iterate: async function(iteratorCallback) {
+ log.error('iterate is not supported');
+ },
+ key: async function(n) {
+ log.error('key n is not supported');
+ },
+ keys: async function() {
+ return this._map.keys();
+ },
+ length: async function() {
+ return this._map.size();
+ },
+ removeItem: async function(key) {
+ this._map.delete(key);
+ },
+ setItem: async function(key, value) {
+ const originalValue = this._map.get(key);
+ console.log('setItem', this._map, key, value);
+ this._map.set(key, value);
+ return originalValue;
+ }
+ };
+
+ // Add the driver to localForage.
+ localforage.defineDriver(memoryDriver);
+}
+
+async function loadTdlibWasm(onFS, wasmUrl) {
+ console.log('loadTdlibWasm');
+ const td_module = await import('./prebuilt/release/td_wasm.js');
+ const createTdwebModule = td_module.default;
+ log.info('got td_wasm.js', td_module, createTdwebModule);
+ let td_wasm = td_wasm_release;
+ if (wasmUrl) {
+ td_wasm = wasmUrl;
+ }
+ let module = createTdwebModule({
+ onRuntimeInitialized: () => {
+ log.info('runtime intialized');
+ onFS(module.FS);
+ },
+ instantiateWasm: (imports, successCallback) => {
+ log.info('start instantiateWasm', td_wasm, imports);
+ const next = instance => {
+ log.info('finish instantiateWasm');
+ successCallback(instance);
+ };
+ instantiateAny(tdlibVersion, td_wasm, imports).then(next);
+ return {};
+ },
+ ENVIROMENT: 'WORKER'
+ });
+ log.info('Wait module');
+ module = await module;
+ log.info('Got module', module);
+ //onFS(module.FS);
+ return module;
+}
+
+async function loadTdlibAsmjs(onFS) {
+ console.log('loadTdlibAsmjs');
+ const createTdwebModule = (await import('./prebuilt/release/td_asmjs.js'))
+ .default;
+ console.log('got td_asm.js', createTdwebModule);
+ const fromFile = 'td_asmjs.js.mem';
+ const toFile = td_asmjs_mem_release;
+ let module = createTdwebModule({
+ onRuntimeInitialized: () => {
+ console.log('runtime intialized');
+ onFS(module.FS);
+ },
+ locateFile: name => {
+ if (name === fromFile) {
+ return toFile;
+ }
+ return name;
+ },
+ ENVIROMENT: 'WORKER'
+ });
+ log.info('Wait module');
+ module = await module;
+ log.info('Got module', module);
+ //onFS(module.FS);
+ return module;
+}
+
+async function loadTdlib(mode, onFS, wasmUrl) {
+ const wasmSupported = (() => {
+ try {
+ if (
+ typeof WebAssembly === 'object' &&
+ typeof WebAssembly.instantiate === 'function'
+ ) {
+ const module = new WebAssembly.Module(
+ Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)
+ );
+ if (module instanceof WebAssembly.Module)
+ return (
+ new WebAssembly.Instance(module) instanceof WebAssembly.Instance
+ );
+ }
+ } catch (e) {}
+ return false;
+ })();
+ if (!wasmSupported) {
+ if (mode === 'wasm') {
+ log.error('WebAssembly is not supported, trying to use it anyway');
+ } else {
+ log.warn('WebAssembly is not supported, trying to use asm.js');
+ mode = 'asmjs';
+ }
+ }
+
+ if (mode === 'asmjs') {
+ return loadTdlibAsmjs(onFS);
+ }
+ return loadTdlibWasm(onFS, wasmUrl);
+}
+
+class OutboundFileSystem {
+ constructor(root, FS) {
+ this.root = root;
+ this.nextFileId = 0;
+ this.FS = FS;
+ this.files = new Set();
+ FS.mkdir(root);
+ }
+ blobToPath(blob, name) {
+ const dir = this.root + '/' + this.nextFileId;
+ if (!name) {
+ name = 'blob';
+ }
+ this.nextFileId++;
+ this.FS.mkdir(dir);
+ this.FS.mount(
+ this.FS.filesystems.WORKERFS,
+ {
+ blobs: [{ name: name, data: blob }]
+ },
+ dir
+ );
+ const path = dir + '/' + name;
+ this.files.add(path);
+ return path;
+ }
+
+ forgetPath(path) {
+ if (this.files.has(path)) {
+ this.FS.unmount(path);
+ this.files.delete(path);
+ }
+ }
+}
+
+class InboundFileSystem {
+ static async create(dbName, root, FS_promise) {
+ const start = performance.now();
+ try {
+ const ifs = new InboundFileSystem();
+ ifs.pending = [];
+ ifs.pendingHasTimeout = false;
+ ifs.persistCount = 0;
+ ifs.persistSize = 0;
+ ifs.pendingI = 0;
+ ifs.inPersist = false;
+ ifs.totalCount = 0;
+
+ ifs.root = root;
+
+ //ifs.store = localforage.createInstance({
+ //name: dbName,
+ //driver: localForageDrivers
+ //});
+ log.debug('IDB name: ' + dbName);
+ ifs.idb = new Promise((resolve, reject) => {
+ const request = indexedDB.open(dbName);
+ request.onsuccess = () => resolve(request.result);
+ request.onerror = () => reject(request.error);
+ request.onupgradeneeded = () => {
+ request.result.createObjectStore('keyvaluepairs');
+ };
+ });
+
+ ifs.load_pids();
+
+ const FS = await FS_promise;
+ await ifs.idb;
+ ifs.FS = FS;
+ ifs.FS.mkdir(root);
+ const create_time = (performance.now() - start) / 1000;
+ log.debug('InboundFileSystem::create ' + create_time);
+ return ifs;
+ } catch (e) {
+ log.error('Failed to init Inbound FileSystem: ', e);
+ }
+ }
+
+ async load_pids() {
+ const keys_start = performance.now();
+ log.debug('InboundFileSystem::create::keys start');
+ //const keys = await this.store.keys();
+
+ let idb = await this.idb;
+ let read = idb
+ .transaction(['keyvaluepairs'], 'readonly')
+ .objectStore('keyvaluepairs');
+ const keys = await new Promise((resolve, reject) => {
+ const request = read.getAllKeys();
+ request.onsuccess = () => resolve(request.result);
+ request.onerror = () => reject(request.error);
+ });
+
+ const keys_time = (performance.now() - keys_start) / 1000;
+ log.debug(
+ 'InboundFileSystem::create::keys ' + keys_time + ' ' + keys.length
+ );
+ this.pids = new Set(keys);
+ }
+
+ has(pid) {
+ if (!this.pids) {
+ return true;
+ }
+
+ return this.pids.has(pid);
+ }
+
+ forget(pid) {
+ if (this.pids) {
+ this.pids.delete(pid);
+ }
+ }
+
+ async doPersist(pid, path, arr, resolve, reject, write) {
+ this.persistCount++;
+ let size = arr.length;
+ this.persistSize += size;
+ try {
+ //log.debug('persist.do start', pid, path, arr.length);
+ //await this.store.setItem(pid, new Blob([arr]));
+ await new Promise((resolve, reject) => {
+ const request = write.put(new Blob([arr]), pid);
+ request.onsuccess = () => resolve(request.result);
+ request.onerror = () => reject(request.error);
+ });
+ if (this.pids) {
+ this.pids.add(pid);
+ }
+ this.FS.unlink(path);
+ } catch (e) {
+ log.error('Failed persist ' + path + ' ', e);
+ }
+ //log.debug('persist.do finish', pid, path, arr.length);
+ this.persistCount--;
+ this.persistSize -= size;
+ resolve();
+
+ this.tryFinishPersist();
+ }
+
+ async flushPersist() {
+ if (this.inPersist) {
+ return;
+ }
+ log.debug('persist.flush');
+ this.inPersist = true;
+ let idb = await this.idb;
+ this.writeBegin = performance.now();
+ let write = idb
+ .transaction(['keyvaluepairs'], 'readwrite')
+ .objectStore('keyvaluepairs');
+ while (
+ this.pendingI < this.pending.length &&
+ this.persistCount < 20 &&
+ this.persistSize < 50 << 20
+ ) {
+ var q = this.pending[this.pendingI];
+ this.pending[this.pendingI] = null;
+ // TODO: add to transaction
+ this.doPersist(q.pid, q.path, q.arr, q.resolve, q.reject, write);
+ this.pendingI++;
+ this.totalCount++;
+ }
+ log.debug(
+ 'persist.flush transaction cnt=' +
+ this.persistCount +
+ ', size=' +
+ this.persistSize
+ );
+ this.inPersist = false;
+ this.tryFinishPersist();
+ }
+
+ async tryFinishPersist() {
+ if (this.inPersist) {
+ return;
+ }
+ if (this.persistCount !== 0) {
+ return;
+ }
+ log.debug('persist.finish ' + (performance.now() - this.writeBegin) / 1000);
+ if (this.pendingI === this.pending.length) {
+ this.pending = [];
+ this.pendingHasTimeout = false;
+ this.pendingI = 0;
+ log.debug('persist.finish done');
+ return;
+ }
+ log.debug('persist.finish continue');
+ this.flushPersist();
+ }
+
+ async persist(pid, path, arr) {
+ if (!this.pendingHasTimeout) {
+ this.pendingHasTimeout = true;
+ log.debug('persist set timeout');
+ setTimeout(() => {
+ this.flushPersist();
+ }, 1);
+ }
+ await new Promise((resolve, reject) => {
+ this.pending.push({
+ pid: pid,
+ path: path,
+ arr: arr,
+ resolve: resolve,
+ reject: reject
+ });
+ });
+ }
+
+ async unlink(pid) {
+ log.debug('Unlink ' + pid);
+ try {
+ this.forget(pid);
+ //await this.store.removeItem(pid);
+ let idb = await this.idb;
+ await new Promise((resolve, reject) => {
+ let write = idb
+ .transaction(['keyvaluepairs'], 'readwrite')
+ .objectStore('keyvaluepairs');
+ const request = write.delete(pid);
+ request.onsuccess = () => resolve(request.result);
+ request.onerror = () => reject(request.error);
+ });
+ } catch (e) {
+ log.error('Failed unlink ' + pid + ' ', e);
+ }
+ }
+}
+
+class DbFileSystem {
+ static async create(root, FS_promise, readOnly = false) {
+ const start = performance.now();
+ try {
+ const dbfs = new DbFileSystem();
+ dbfs.root = root;
+ const FS = await FS_promise;
+ dbfs.FS = FS;
+ dbfs.syncfs_total_time = 0;
+ dbfs.readOnly = readOnly;
+ dbfs.syncActive = 0;
+ FS.mkdir(root);
+ FS.mount(FS.filesystems.IDBFS, {}, root);
+
+ await new Promise((resolve, reject) => {
+ FS.syncfs(true, err => {
+ resolve();
+ });
+ });
+
+ const rmrf = path => {
+ log.debug('rmrf ', path);
+ let info;
+ try {
+ info = FS.lookupPath(path);
+ } catch (e) {
+ return;
+ }
+ log.debug('rmrf ', path, info);
+ if (info.node.isFolder) {
+ for (const key in info.node.contents) {
+ rmrf(info.path + '/' + info.node.contents[key].name);
+ }
+ log.debug('rmdir ', path);
+ FS.rmdir(path);
+ } else {
+ log.debug('unlink ', path);
+ FS.unlink(path);
+ }
+ };
+ //const dirs = ['thumbnails', 'profile_photos', 'secret', 'stickers', 'temp', 'wallpapers', 'secret_thumbnails', 'passport'];
+ const dirs = [];
+ const root_dir = FS.lookupPath(root);
+ for (const key in root_dir.node.contents) {
+ const value = root_dir.node.contents[key];
+ log.debug('node ', key, value);
+ if (!value.isFolder) {
+ continue;
+ }
+ dirs.push(root_dir.path + '/' + value.name);
+ }
+ for (const i in dirs) {
+ const dir = dirs[i];
+ rmrf(dir);
+ //FS.mkdir(dir);
+ //FS.mount(FS.filesystems.MEMFS, {}, dir);
+ }
+ dbfs.syncfsInterval = setInterval(() => {
+ dbfs.sync();
+ }, 5000);
+ const create_time = (performance.now() - start) / 1000;
+ log.debug('DbFileSystem::create ' + create_time);
+ return dbfs;
+ } catch (e) {
+ log.error('Failed to init DbFileSystem: ', e);
+ }
+ }
+ async sync(force) {
+ if (this.readOnly) {
+ return;
+ }
+ if (this.syncActive > 0 && !force) {
+ log.debug('SYNC: skip');
+ return;
+ }
+ this.syncActive++;
+ const start = performance.now();
+ await new Promise((resolve, reject) => {
+ this.FS.syncfs(false, () => {
+ const syncfs_time = (performance.now() - start) / 1000;
+ this.syncfs_total_time += syncfs_time;
+ log.debug('SYNC: ' + syncfs_time);
+ log.debug('SYNC total: ' + this.syncfs_total_time);
+ resolve();
+ });
+ });
+ this.syncActive--;
+ }
+ async close() {
+ clearInterval(this.syncfsInterval);
+ await this.sync(true);
+ }
+ async destroy() {
+ clearInterval(this.syncfsInterval);
+ if (this.readOnly) {
+ return;
+ }
+ this.FS.unmount(this.root);
+ const req = indexedDB.deleteDatabase(this.root);
+ await new Promise((resolve, reject) => {
+ req.onsuccess = function(e) {
+ log.info('SUCCESS');
+ resolve(e.result);
+ };
+ req.onerror = function(e) {
+ log.info('ONERROR');
+ reject(e.error);
+ };
+ req.onblocked = function(e) {
+ log.info('ONBLOCKED');
+ reject('blocked');
+ };
+ });
+ }
+}
+
+class TdFileSystem {
+ static async init_fs(prefix, FS_promise) {
+ const FS = await FS_promise;
+ FS.mkdir(prefix);
+ return FS;
+ }
+ static async create(instanceName, FS_promise, readOnly = false) {
+ try {
+ const tdfs = new TdFileSystem();
+ const prefix = '/' + instanceName;
+ tdfs.prefix = prefix;
+ FS_promise = TdFileSystem.init_fs(prefix, FS_promise);
+
+ //MEMFS. Store to IDB and delete files as soon as possible
+ const inboundFileSystem = InboundFileSystem.create(
+ instanceName,
+ prefix + '/inboundfs',
+ FS_promise
+ );
+
+ //IDBFS. MEMFS which is flushed to IDB from time to time
+ const dbFileSystem = DbFileSystem.create(
+ prefix + '/dbfs',
+ FS_promise,
+ readOnly
+ );
+
+ const FS = await FS_promise;
+ tdfs.FS = FS;
+
+ //WORKERFS. Temporary stores Blobs for outbound files
+ tdfs.outboundFileSystem = new OutboundFileSystem(
+ prefix + '/outboundfs',
+ tdfs.FS
+ );
+
+ tdfs.inboundFileSystem = await inboundFileSystem;
+ tdfs.dbFileSystem = await dbFileSystem;
+ return tdfs;
+ } catch (e) {
+ log.error('Failed to init TdFileSystem: ', e);
+ }
+ }
+ async destroy() {
+ await this.dbFileSystem.destroy();
+ }
+}
+
+class TdClient {
+ constructor(callback) {
+ log.info('Start worker');
+ this.pendingQueries = [];
+ this.isPending = true;
+ this.callback = callback;
+ this.wasInit = false;
+ }
+
+ async testLocalForage() {
+ await initLocalForage();
+ const DRIVERS = [
+ localforage.INDEXEDDB,
+ 'memoryDriver',
+ localforage.LOCALSTORAGE,
+ localforage.WEBSQL,
+ localForageDrivers
+ ];
+ for (const driverName of DRIVERS) {
+ console.log('Test ', driverName);
+ try {
+ await localforage.setDriver(driverName);
+ console.log('A');
+ await localforage.setItem('hello', 'world');
+ console.log('B');
+ const x = await localforage.getItem('hello');
+ console.log('got ', x);
+ await localforage.clear();
+ console.log('C');
+ } catch (error) {
+ console.log('Error', error);
+ }
+ }
+ }
+
+ async init(options) {
+ if (this.wasInit) {
+ return;
+ }
+ //await this.testLocalForage();
+ log.setVerbosity(options.jsLogVerbosityLevel);
+ this.wasInit = true;
+
+ options = options || {};
+ const mode = options.mode || 'wasm';
+
+ const FS_promise = new Promise(resolve => {
+ this.onFS = resolve;
+ });
+
+ const tdfs_promise = TdFileSystem.create(
+ options.instanceName,
+ FS_promise,
+ options.readOnly
+ );
+
+ this.useDatabase = true;
+ if ('useDatabase' in options) {
+ this.useDatabase = options.useDatabase;
+ }
+
+ log.info('load TdModule');
+ this.TdModule = await loadTdlib(mode, this.onFS, options.wasmUrl);
+ log.info('got TdModule');
+ this.td_functions = {
+ td_create: this.TdModule.cwrap(
+ 'td_emscripten_create_client_id',
+ 'number',
+ []
+ ),
+ td_send: this.TdModule.cwrap('td_emscripten_send', null, [
+ 'number',
+ 'string'
+ ]),
+ td_execute: this.TdModule.cwrap('td_emscripten_execute', 'string', [
+ 'string'
+ ]),
+ td_receive: this.TdModule.cwrap('td_emscripten_receive', 'string', []),
+ td_set_verbosity: verbosity => {
+ this.td_functions.td_execute(
+ JSON.stringify({
+ '@type': 'setLogVerbosityLevel',
+ new_verbosity_level: verbosity
+ })
+ );
+ },
+ td_get_timeout: this.TdModule.cwrap(
+ 'td_emscripten_get_timeout',
+ 'number',
+ []
+ )
+ };
+ //this.onFS(this.TdModule.FS);
+ this.FS = this.TdModule.FS;
+ this.TdModule['websocket']['on']('error', error => {
+ this.scheduleReceiveSoon();
+ });
+ this.TdModule['websocket']['on']('open', fd => {
+ this.scheduleReceiveSoon();
+ });
+ this.TdModule['websocket']['on']('listen', fd => {
+ this.scheduleReceiveSoon();
+ });
+ this.TdModule['websocket']['on']('connection', fd => {
+ this.scheduleReceiveSoon();
+ });
+ this.TdModule['websocket']['on']('message', fd => {
+ this.scheduleReceiveSoon();
+ });
+ this.TdModule['websocket']['on']('close', fd => {
+ this.scheduleReceiveSoon();
+ });
+
+ // wait till it is allowed to start
+ this.callback({ '@type': 'inited' });
+ await new Promise(resolve => {
+ this.onStart = resolve;
+ });
+ this.isStarted = true;
+
+ log.info('may start now');
+ if (this.isClosing) {
+ return;
+ }
+ log.info('FS start init');
+ this.tdfs = await tdfs_promise;
+ log.info('FS inited');
+ this.callback({ '@type': 'fsInited' });
+
+ // no async initialization after this point
+ if (options.logVerbosityLevel === undefined) {
+ options.logVerbosityLevel = 2;
+ }
+ this.td_functions.td_set_verbosity(options.logVerbosityLevel);
+ this.client_id = this.td_functions.td_create();
+
+ this.savingFiles = new Map();
+ this.send({
+ '@type': 'setOption',
+ name: 'store_all_files_in_files_directory',
+ value: {
+ '@type': 'optionValueBoolean',
+ value: true
+ }
+ });
+ this.send({
+ '@type': 'setOption',
+ name: 'language_pack_database_path',
+ value: {
+ '@type': 'optionValueString',
+ value: this.tdfs.dbFileSystem.root + '/language'
+ }
+ });
+ this.send({
+ '@type': 'setOption',
+ name: 'ignore_background_updates',
+ value: {
+ '@type': 'optionValueBoolean',
+ value: !this.useDatabase
+ }
+ });
+
+ this.flushPendingQueries();
+
+ this.receive();
+ }
+
+ prepareQueryRecursive(query) {
+ if (query['@type'] === 'inputFileBlob') {
+ return {
+ '@type': 'inputFileLocal',
+ path: this.tdfs.outboundFileSystem.blobToPath(query.data, query.name)
+ };
+ }
+ for (const key in query) {
+ const field = query[key];
+ if (field && typeof field === 'object') {
+ query[key] = this.prepareQueryRecursive(field);
+ }
+ }
+ return query;
+ }
+
+ prepareQuery(query) {
+ if (query['@type'] === 'setTdlibParameters') {
+ query.database_directory = this.tdfs.dbFileSystem.root;
+ query.files_directory = this.tdfs.inboundFileSystem.root;
+
+ const useDb = this.useDatabase;
+ query.use_file_database = useDb;
+ query.use_chat_info_database = useDb;
+ query.use_message_database = useDb;
+ query.use_secret_chats = useDb;
+ }
+ if (query['@type'] === 'getLanguagePackString') {
+ query.language_pack_database_path =
+ this.tdfs.dbFileSystem.root + '/language';
+ }
+ return this.prepareQueryRecursive(query);
+ }
+
+ onStart() {
+ //nop
+ log.info('ignore on_start');
+ }
+
+ deleteIdbKey(query) {
+ try {
+ } catch (e) {
+ this.callback({
+ '@type': 'error',
+ '@extra': query['@extra'],
+ code: 400,
+ message: e
+ });
+ return;
+ }
+ this.callback({
+ '@type': 'ok',
+ '@extra': query['@extra']
+ });
+ }
+
+ readFilePart(query) {
+ let res;
+ try {
+ //const file_size = this.FS.stat(query.path).size;
+ const stream = this.FS.open(query.path, 'r');
+ const buf = new Uint8Array(query.count);
+ this.FS.read(stream, buf, 0, query.count, query.offset);
+ this.FS.close(stream);
+ res = buf;
+ } catch (e) {
+ this.callback({
+ '@type': 'error',
+ '@extra': query['@extra'],
+ code: 400,
+ message: e.toString()
+ });
+ return;
+ }
+ this.callback(
+ {
+ '@type': 'filePart',
+ '@extra': query['@extra'],
+ data: res
+ },
+ [res.buffer]
+ );
+ }
+
+ send(query) {
+ if (this.isClosing) {
+ return;
+ }
+ if (this.wasFatalError) {
+ if (query['@type'] === 'destroy') {
+ this.destroy({ '@type': 'ok', '@extra': query['@extra'] });
+ }
+ return;
+ }
+ if (query['@type'] === 'init') {
+ this.init(query.options);
+ return;
+ }
+ if (query['@type'] === 'start') {
+ log.info('on_start');
+ this.onStart();
+ return;
+ }
+ if (query['@type'] === 'setJsLogVerbosityLevel') {
+ log.setVerbosity(query.new_verbosity_level);
+ return;
+ }
+ if (this.isPending) {
+ this.pendingQueries.push(query);
+ return;
+ }
+ if (
+ query['@type'] === 'setLogVerbosityLevel' ||
+ query['@type'] === 'getLogVerbosityLevel' ||
+ query['@type'] === 'setLogTagVerbosityLevel' ||
+ query['@type'] === 'getLogTagVerbosityLevel' ||
+ query['@type'] === 'getLogTags'
+ ) {
+ this.execute(query);
+ return;
+ }
+ if (query['@type'] === 'readFilePart') {
+ this.readFilePart(query);
+ return;
+ }
+ if (query['@type'] === 'deleteIdbKey') {
+ this.deleteIdbKey(query);
+ return;
+ }
+ query = this.prepareQuery(query);
+ this.td_functions.td_send(this.client_id, JSON.stringify(query));
+ this.scheduleReceiveSoon();
+ }
+
+ execute(query) {
+ try {
+ const res = this.td_functions.td_execute(JSON.stringify(query));
+ const response = JSON.parse(res);
+ this.callback(response);
+ } catch (error) {
+ this.onFatalError(error);
+ }
+ }
+ receive() {
+ this.cancelReceive();
+ if (this.wasFatalError) {
+ return;
+ }
+ try {
+ while (true) {
+ const msg = this.td_functions.td_receive();
+ if (!msg) {
+ break;
+ }
+ const response = this.prepareResponse(JSON.parse(msg));
+ if (
+ response['@type'] === 'updateAuthorizationState' &&
+ response.authorization_state['@type'] === 'authorizationStateClosed'
+ ) {
+ this.close(response);
+ break;
+ }
+ this.callback(response);
+ }
+
+ this.scheduleReceive();
+ } catch (error) {
+ this.onFatalError(error);
+ }
+ }
+
+ cancelReceive() {
+ if (this.receiveTimeout) {
+ clearTimeout(this.receiveTimeout);
+ delete this.receiveTimeout;
+ }
+ delete this.receiveSoon;
+ }
+ scheduleReceiveSoon() {
+ if (this.receiveSoon) {
+ return;
+ }
+ this.cancelReceive();
+ this.receiveSoon = true;
+ this.scheduleReceiveIn(0.001);
+ }
+ scheduleReceive() {
+ if (this.receiveSoon) {
+ return;
+ }
+ this.cancelReceive();
+ const timeout = this.td_functions.td_get_timeout();
+ this.scheduleReceiveIn(timeout);
+ }
+ scheduleReceiveIn(timeout) {
+ //return;
+ log.debug('Scheduler receive in ' + timeout + 's');
+ this.receiveTimeout = setTimeout(() => this.receive(), timeout * 1000);
+ }
+
+ onFatalError(error) {
+ this.wasFatalError = true;
+ this.asyncOnFatalError(error);
+ }
+
+ async close(last_update) {
+ // close db and cancell all timers
+ this.isClosing = true;
+ if (this.isStarted) {
+ log.debug('close worker: start');
+ await this.tdfs.dbFileSystem.close();
+ this.cancelReceive();
+ log.debug('close worker: finish');
+ }
+ this.callback(last_update);
+ }
+
+ async destroy(result) {
+ try {
+ log.info('destroy tdfs ...');
+ await this.tdfs.destroy();
+ log.info('destroy tdfs ok');
+ } catch (e) {
+ log.error('Failed destroy', e);
+ }
+ this.callback(result);
+ this.callback({
+ '@type': 'updateAuthorizationState',
+ authorization_state: {
+ '@type': 'authorizationStateClosed'
+ }
+ });
+ }
+
+ async asyncOnFatalError(error) {
+ await this.tdfs.dbFileSystem.sync();
+ this.callback({ '@type': 'updateFatalError', error: error });
+ }
+
+ saveFile(pid, file) {
+ const isSaving = this.savingFiles.has(pid);
+ this.savingFiles.set(pid, file);
+ if (isSaving) {
+ return file;
+ }
+ try {
+ const arr = this.FS.readFile(file.local.path);
+ if (arr) {
+ file = Object.assign({}, file);
+ file.arr = arr;
+ this.doSaveFile(pid, file, arr);
+ }
+ } catch (e) {
+ log.error('Failed to readFile: ', e);
+ }
+ return file;
+ }
+
+ async doSaveFile(pid, file, arr) {
+ await this.tdfs.inboundFileSystem.persist(pid, file.local.path, arr);
+ file = this.savingFiles.get(pid);
+ file.idb_key = pid;
+ this.callback({ '@type': 'updateFile', file: file });
+
+ this.savingFiles.delete(pid);
+ }
+
+ prepareFile(file) {
+ const pid = file.remote.unique_id ? file.remote.unique_id : file.remote.id;
+ if (!pid) {
+ return file;
+ }
+
+ if (file.local.is_downloading_active) {
+ this.tdfs.inboundFileSystem.forget(pid);
+ } else if (this.tdfs.inboundFileSystem.has(pid)) {
+ file.idb_key = pid;
+ return file;
+ }
+
+ if (file.local.is_downloading_completed) {
+ file = this.saveFile(pid, file);
+ }
+ return file;
+ }
+
+ prepareResponse(response) {
+ if (response['@type'] === 'file') {
+ return this.prepareFile(response);
+ }
+ for (const key in response) {
+ const field = response[key];
+ if (field && typeof field === 'object') {
+ response[key] = this.prepareResponse(field);
+ }
+ }
+ return response;
+ }
+
+ flushPendingQueries() {
+ this.isPending = false;
+ for (const query of this.pendingQueries) {
+ this.send(query);
+ }
+ }
+}
+
+const client = new TdClient((e, t = []) => postMessage(e, t));
+
+onmessage = function(e) {
+ try {
+ client.send(e.data);
+ } catch (error) {
+ client.onFatalError(error);
+ }
+};
diff --git a/protocols/Telegram/tdlib/td/example/web/tdweb/webpack.config.js b/protocols/Telegram/tdlib/td/example/web/tdweb/webpack.config.js
new file mode 100644
index 0000000000..67c7a3eebe
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/example/web/tdweb/webpack.config.js
@@ -0,0 +1,86 @@
+const path = require('path');
+const CleanWebpackPlugin = require('clean-webpack-plugin');
+
+module.exports = {
+ entry: ['./src/index.js'],
+ output: {
+ filename: 'tdweb.js',
+ path: path.resolve(__dirname, 'dist'),
+ library: 'tdweb',
+ libraryTarget: 'umd',
+ umdNamedDefine: true,
+ globalObject: 'this'
+ },
+ devServer: {
+ contentBase: './dist'
+ },
+ plugins: [
+ // new HtmlWebpackPlugin(),
+ new CleanWebpackPlugin({})
+ //, new UglifyJSPlugin()
+ ],
+ optimization:{
+ minimize: false, // <---- disables uglify.
+ },
+ module: {
+ noParse: /td_asmjs\.js$/,
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /prebuilt/,
+ enforce: 'pre',
+ include: [path.resolve(__dirname, 'src')],
+ use: [
+ {
+ loader: require.resolve('eslint-loader')
+ }
+ ]
+ },
+ {
+ test: /worker\.(js|jsx)$/,
+ include: [path.resolve(__dirname, 'src')],
+ use: [
+ {
+ loader: require.resolve('worker-loader')
+ }
+ ]
+ },
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /prebuilt/,
+ include: [path.resolve(__dirname, 'src')],
+ use: [
+ {
+ loader: require.resolve('babel-loader')
+ }
+ ]
+ },
+ {
+ test: /\.(wasm|mem)$/,
+ include: [path.resolve(__dirname, 'src')],
+ type: "javascript/auto",
+ use: [
+ {
+ loader: require.resolve('file-loader')
+ }
+ ]
+ }
+ ]
+ },
+ node: {
+ dgram: 'empty',
+ fs: 'empty',
+ net: 'empty',
+ tls: 'empty',
+ crypto: 'empty',
+ child_process: 'empty'
+ },
+ performance: {
+ maxAssetSize: 30000000
+ },
+ resolve: {
+ alias: {
+ ws$: 'fs'
+ }
+ }
+};
diff --git a/protocols/Telegram/tdlib/td/format.ps1 b/protocols/Telegram/tdlib/td/format.ps1
index f64296cc36..ef4f53155f 100644
--- a/protocols/Telegram/tdlib/td/format.ps1
+++ b/protocols/Telegram/tdlib/td/format.ps1
@@ -1,3 +1,3 @@
-./src.ps1 | Select-String -NotMatch "CxCli.h" | Select-String -NotMatch "dotnet" | ForEach-Object {
+./src.ps1 | Select-String -NotMatch "CxCli.h" | Select-String -CaseSensitive -NotMatch "DotNet" | Select-String -NotMatch "tl/tl_dotnet_object.h" | Select-String -NotMatch "/tl-parser/" | ForEach-Object {
clang-format -verbose -style=file -i $_
}
diff --git a/protocols/Telegram/tdlib/td/format.sh b/protocols/Telegram/tdlib/td/format.sh
index 64280e3384..47d21ee527 100644
--- a/protocols/Telegram/tdlib/td/format.sh
+++ b/protocols/Telegram/tdlib/td/format.sh
@@ -1,2 +1,3 @@
#!/bin/sh
-./src.sh | grep -v CxCli.h | grep -iv dotnet | xargs -n 1 clang-format -verbose -style=file -i
+cd $(dirname $0)
+./src.sh | grep -v CxCli.h | grep -v DotNet | grep -v tl/tl_dotnet_object.h | grep -v /tl-parser/ | xargs -n 1 clang-format -verbose -style=file -i
diff --git a/protocols/Telegram/tdlib/td/gen_git_commit_h.ps1 b/protocols/Telegram/tdlib/td/gen_git_commit_h.ps1
index 2893f89c2a..dadc225cdc 100644
--- a/protocols/Telegram/tdlib/td/gen_git_commit_h.ps1
+++ b/protocols/Telegram/tdlib/td/gen_git_commit_h.ps1
@@ -1,6 +1,6 @@
-$commit = git rev-parse HEAD
-git diff-index --quiet HEAD
-$dirty = $LASTEXITCODE
+$commit = try { git rev-parse HEAD } catch { "unknown" }
+try { git diff-index --quiet HEAD } catch {}
+$dirty = if ($LASTEXITCODE) { "true" } else { "false" }
echo "#pragma once`r`n#define GIT_COMMIT `"$commit`"`r`n#define GIT_DIRTY $dirty" | out-file -encoding ASCII auto/git_info.h.new
if (-not (Test-Path .\auto\git_info.h) -or (Compare-Object $(Get-Content .\auto\git_info.h.new) $(Get-Content .\auto\git_info.h))) {
mv -Force auto/git_info.h.new auto/git_info.h
diff --git a/protocols/Telegram/tdlib/td/gen_git_commit_h.sh b/protocols/Telegram/tdlib/td/gen_git_commit_h.sh
index b3416b2983..bc6bc2d352 100644
--- a/protocols/Telegram/tdlib/td/gen_git_commit_h.sh
+++ b/protocols/Telegram/tdlib/td/gen_git_commit_h.sh
@@ -1,7 +1,14 @@
#!/bin/sh
-commit=$(git rev-parse HEAD)
-git diff-index --quiet HEAD
-dirty=$?
+cd $(dirname $0)
+commit="$(git rev-parse HEAD 2> /dev/null)"
+commit="${commit:-unknown}"
+git diff-index --quiet HEAD 2> /dev/null
+if [ $? -ne 0 ]
+then
+ dirty="true"
+else
+ dirty="false"
+fi
printf "#pragma once\n#define GIT_COMMIT \"$commit\"\n#define GIT_DIRTY $dirty\n" > auto/git_info.h.new
if cmp -s auto/git_info.h.new auto/git_info.h 2>&1 > /dev/null
then
diff --git a/protocols/Telegram/tdlib/td/memprof/memprof.cpp b/protocols/Telegram/tdlib/td/memprof/memprof.cpp
index 11822ee00a..c1ef1d3d1b 100644
--- a/protocols/Telegram/tdlib/td/memprof/memprof.cpp
+++ b/protocols/Telegram/tdlib/td/memprof/memprof.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,7 +11,6 @@
#if (TD_DARWIN || TD_LINUX) && defined(USE_MEMPROF)
#include <algorithm>
#include <atomic>
-#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
@@ -28,6 +27,11 @@ bool is_memprof_on() {
return true;
}
+#define my_assert(f) \
+ if (!(f)) { \
+ std::abort(); \
+ }
+
#if USE_MEMPROF_SAFE
double get_fast_backtrace_success_rate() {
return 0;
@@ -54,7 +58,7 @@ static int fast_backtrace(void **buffer, int size) {
void *ip;
};
- stack_frame *bp = reinterpret_cast<stack_frame *>(get_bp());
+ auto *bp = reinterpret_cast<stack_frame *>(get_bp());
int i = 0;
while (i < size &&
#if TD_LINUX
@@ -72,7 +76,8 @@ static int fast_backtrace(void **buffer, int size) {
return i;
}
-static std::atomic<std::size_t> fast_backtrace_failed_cnt, backtrace_total_cnt;
+static std::atomic<std::size_t> fast_backtrace_failed_cnt;
+static std::atomic<std::size_t> backtrace_total_cnt;
double get_fast_backtrace_success_rate() {
return 1 - static_cast<double>(fast_backtrace_failed_cnt.load(std::memory_order_relaxed)) /
static_cast<double>(std::max(std::size_t(1), backtrace_total_cnt.load(std::memory_order_relaxed)));
@@ -116,8 +121,8 @@ static Backtrace get_backtrace() {
return res;
}
-static constexpr std::size_t reserved = 16;
-static constexpr std::int32_t malloc_info_magic = 0x27138373;
+static constexpr std::size_t RESERVED_SIZE = 16;
+static constexpr std::int32_t MALLOC_INFO_MAGIC = 0x27138373;
struct malloc_info {
std::int32_t magic;
std::int32_t size;
@@ -138,9 +143,9 @@ struct HashtableNode {
std::atomic<std::size_t> size;
};
-static constexpr std::size_t ht_max_size = 1000000;
+static constexpr std::size_t HT_MAX_SIZE = 1000000;
static std::atomic<std::size_t> ht_size{0};
-static std::array<HashtableNode, ht_max_size> ht;
+static std::array<HashtableNode, HT_MAX_SIZE> ht;
std::size_t get_ht_size() {
return ht_size.load();
@@ -148,14 +153,14 @@ std::size_t get_ht_size() {
std::int32_t get_ht_pos(const Backtrace &bt, bool force = false) {
auto hash = get_hash(bt);
- std::int32_t pos = static_cast<std::int32_t>(hash % ht.size());
+ auto pos = static_cast<std::int32_t>(hash % ht.size());
bool was_overflow = false;
while (true) {
auto pos_hash = ht[pos].hash.load();
if (pos_hash == 0) {
- if (ht_size > ht_max_size / 2) {
+ if (ht_size > HT_MAX_SIZE / 2) {
if (force) {
- assert(ht_size * 10 < ht_max_size * 7);
+ my_assert(ht_size * 10 < HT_MAX_SIZE * 7);
} else {
Backtrace unknown_bt{{nullptr}};
unknown_bt[0] = reinterpret_cast<void *>(1);
@@ -187,26 +192,29 @@ std::int32_t get_ht_pos(const Backtrace &bt, bool force = false) {
void dump_alloc(const std::function<void(const AllocInfo &)> &func) {
for (auto &node : ht) {
- if (node.size == 0) {
+ auto size = node.size.load(std::memory_order_relaxed);
+ if (size == 0) {
continue;
}
- func(AllocInfo{node.backtrace, node.size.load()});
+ func(AllocInfo{node.backtrace, size});
}
}
void register_xalloc(malloc_info *info, std::int32_t diff) {
+ my_assert(info->size >= 0);
if (diff > 0) {
- ht[info->ht_pos].size += info->size;
+ ht[info->ht_pos].size.fetch_add(info->size, std::memory_order_relaxed);
} else {
- ht[info->ht_pos].size -= info->size;
+ auto old_value = ht[info->ht_pos].size.fetch_sub(info->size, std::memory_order_relaxed);
+ my_assert(old_value >= static_cast<std::size_t>(info->size));
}
}
extern "C" {
static void *malloc_with_frame(std::size_t size, const Backtrace &frame) {
- static_assert(reserved % alignof(std::max_align_t) == 0, "fail");
- static_assert(reserved >= sizeof(malloc_info), "fail");
+ static_assert(RESERVED_SIZE % alignof(std::max_align_t) == 0, "fail");
+ static_assert(RESERVED_SIZE >= sizeof(malloc_info), "fail");
#if TD_DARWIN
static void *malloc_void = dlsym(RTLD_NEXT, "malloc");
static auto malloc_old = *reinterpret_cast<decltype(malloc) **>(&malloc_void);
@@ -214,26 +222,26 @@ static void *malloc_with_frame(std::size_t size, const Backtrace &frame) {
extern decltype(malloc) __libc_malloc;
static auto malloc_old = __libc_malloc;
#endif
- auto *info = static_cast<malloc_info *>(malloc_old(size + reserved));
+ auto *info = static_cast<malloc_info *>(malloc_old(size + RESERVED_SIZE));
auto *buf = reinterpret_cast<char *>(info);
- info->magic = malloc_info_magic;
+ info->magic = MALLOC_INFO_MAGIC;
info->size = static_cast<std::int32_t>(size);
info->ht_pos = get_ht_pos(frame);
register_xalloc(info, +1);
- void *data = buf + reserved;
+ void *data = buf + RESERVED_SIZE;
return data;
}
static malloc_info *get_info(void *data_void) {
- char *data = static_cast<char *>(data_void);
- auto *buf = data - reserved;
+ auto *data = static_cast<char *>(data_void);
+ auto *buf = data - RESERVED_SIZE;
auto *info = reinterpret_cast<malloc_info *>(buf);
- assert(info->magic == malloc_info_magic);
+ my_assert(info->magic == MALLOC_INFO_MAGIC);
return info;
}
@@ -257,12 +265,14 @@ void free(void *data_void) {
#endif
return free_old(info);
}
+
void *calloc(std::size_t size_a, std::size_t size_b) {
auto size = size_a * size_b;
void *res = malloc_with_frame(size, get_backtrace());
std::memset(res, 0, size);
return res;
}
+
void *realloc(void *ptr, std::size_t size) {
if (ptr == nullptr) {
return malloc_with_frame(size, get_backtrace());
@@ -274,13 +284,14 @@ void *realloc(void *ptr, std::size_t size) {
free(ptr);
return new_ptr;
}
+
void *memalign(std::size_t aligment, std::size_t size) {
- assert(false && "Memalign is unsupported");
+ my_assert(false && "Memalign is unsupported");
return nullptr;
}
}
-// c++14 guarantees than it is enough to override this two operators.
+// c++14 guarantees that it is enough to override these two operators.
void *operator new(std::size_t count) {
return malloc_with_frame(count, get_backtrace());
}
diff --git a/protocols/Telegram/tdlib/td/memprof/memprof.h b/protocols/Telegram/tdlib/td/memprof/memprof.h
index 00e33f7380..be3823f82e 100644
--- a/protocols/Telegram/tdlib/td/memprof/memprof.h
+++ b/protocols/Telegram/tdlib/td/memprof/memprof.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/memprof/memprof_stat.cpp b/protocols/Telegram/tdlib/td/memprof/memprof_stat.cpp
new file mode 100644
index 0000000000..00d371431e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/memprof/memprof_stat.cpp
@@ -0,0 +1,166 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "memprof/memprof_stat.h"
+
+#include "td/utils/port/platform.h"
+
+#if (TD_DARWIN || TD_LINUX)
+#include <algorithm>
+#include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <new>
+#include <utility>
+#include <vector>
+
+#include <dlfcn.h>
+#include <execinfo.h>
+
+bool is_memprof_on() {
+ return true;
+}
+
+#define my_assert(f) \
+ if (!(f)) { \
+ std::abort(); \
+ }
+
+struct malloc_info {
+ std::int32_t magic;
+ std::int32_t size;
+};
+
+static std::atomic<std::size_t> total_memory_used;
+
+void register_xalloc(malloc_info *info, std::int32_t diff) {
+ my_assert(info->size >= 0);
+ // TODO: this is very slow in case of several threads.
+ // Currently this statistics is intended only for memory benchmarks.
+ total_memory_used.fetch_add(diff * info->size, std::memory_order_relaxed);
+}
+
+std::size_t get_used_memory_size() {
+ return total_memory_used.load();
+}
+
+extern "C" {
+
+static constexpr std::size_t RESERVED_SIZE = 16;
+static constexpr std::int32_t MALLOC_INFO_MAGIC = 0x27138373;
+
+static void *do_malloc(std::size_t size) {
+ static_assert(RESERVED_SIZE % alignof(std::max_align_t) == 0, "fail");
+ static_assert(RESERVED_SIZE >= sizeof(malloc_info), "fail");
+#if TD_DARWIN
+ static void *malloc_void = dlsym(RTLD_NEXT, "malloc");
+ static auto malloc_old = *reinterpret_cast<decltype(malloc) **>(&malloc_void);
+#else
+ extern decltype(malloc) __libc_malloc;
+ static auto malloc_old = __libc_malloc;
+#endif
+ auto *info = static_cast<malloc_info *>(malloc_old(size + RESERVED_SIZE));
+ auto *buf = reinterpret_cast<char *>(info);
+
+ info->magic = MALLOC_INFO_MAGIC;
+ info->size = static_cast<std::int32_t>(size);
+
+ register_xalloc(info, +1);
+
+ void *data = buf + RESERVED_SIZE;
+
+ return data;
+}
+
+static malloc_info *get_info(void *data_void) {
+ auto *data = static_cast<char *>(data_void);
+ auto *buf = data - RESERVED_SIZE;
+
+ auto *info = reinterpret_cast<malloc_info *>(buf);
+ my_assert(info->magic == MALLOC_INFO_MAGIC);
+ return info;
+}
+
+void *malloc(std::size_t size) {
+ return do_malloc(size);
+}
+
+void free(void *data_void) {
+ if (data_void == nullptr) {
+ return;
+ }
+ auto *info = get_info(data_void);
+ register_xalloc(info, -1);
+
+#if TD_DARWIN
+ static void *free_void = dlsym(RTLD_NEXT, "free");
+ static auto free_old = *reinterpret_cast<decltype(free) **>(&free_void);
+#else
+ extern decltype(free) __libc_free;
+ static auto free_old = __libc_free;
+#endif
+ return free_old(info);
+}
+
+void *calloc(std::size_t size_a, std::size_t size_b) {
+ auto size = size_a * size_b;
+ void *res = do_malloc(size);
+ std::memset(res, 0, size);
+ return res;
+}
+
+void *realloc(void *ptr, std::size_t size) {
+ if (ptr == nullptr) {
+ return do_malloc(size);
+ }
+ auto *info = get_info(ptr);
+ auto *new_ptr = do_malloc(size);
+ auto to_copy = std::min(static_cast<std::int32_t>(size), info->size);
+ std::memcpy(new_ptr, ptr, to_copy);
+ free(ptr);
+ return new_ptr;
+}
+
+void *memalign(std::size_t alignment, std::size_t size) {
+ auto res = malloc(size);
+ my_assert(reinterpret_cast<std::uintptr_t>(res) % alignment == 0);
+ return res;
+}
+
+int posix_memalign(void **memptr, size_t alignment, size_t size) {
+ auto res = malloc(size);
+ my_assert(reinterpret_cast<std::uintptr_t>(res) % alignment == 0);
+ *memptr = res;
+ return 0;
+}
+}
+
+// c++14 guarantees that it is enough to override these two operators.
+void *operator new(std::size_t count) {
+ return do_malloc(count);
+}
+void operator delete(void *ptr) noexcept(true) {
+ free(ptr);
+}
+// because of gcc warning: the program should also define 'void operator delete(void*, std::size_t)'
+void operator delete(void *ptr, std::size_t) noexcept(true) {
+ free(ptr);
+}
+
+// c++17
+// void *operator new(std::size_t count, std::align_val_t al);
+// void operator delete(void *ptr, std::align_val_t al);
+
+#else
+bool is_memprof_on() {
+ return false;
+}
+std::size_t get_used_memory_size() {
+ return 0;
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/test/tests_runner.h b/protocols/Telegram/tdlib/td/memprof/memprof_stat.h
index 3de566858b..5bc045e25c 100644
--- a/protocols/Telegram/tdlib/td/test/tests_runner.h
+++ b/protocols/Telegram/tdlib/td/memprof/memprof_stat.h
@@ -1,18 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#ifdef __cplusplus
-extern "C" {
-#endif
+#include <cstddef>
-void tests_runner_init(const char *dir);
-void run_all_tests();
+bool is_memprof_on();
-#ifdef __cplusplus
-}
-#endif
+std::size_t get_used_memory_size();
diff --git a/protocols/Telegram/tdlib/td/post.js b/protocols/Telegram/tdlib/td/post.js
new file mode 100644
index 0000000000..531af4df13
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/post.js
@@ -0,0 +1 @@
+createTdwebModule.ready.FS = Module.FS;
diff --git a/protocols/Telegram/tdlib/td/sqlite/CMakeLists.txt b/protocols/Telegram/tdlib/td/sqlite/CMakeLists.txt
index c94efe4f57..518e3f0aa1 100644
--- a/protocols/Telegram/tdlib/td/sqlite/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/sqlite/CMakeLists.txt
@@ -1,4 +1,10 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
+
+if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
+ set(CMAKE_INSTALL_LIBDIR "lib")
+endif()
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
@@ -13,40 +19,58 @@ set(SQLITE_SOURCE
sqlite/sqlite3session.h
)
+# all SQLite functions are moved to namespace tdsqlite3 by `sed -Ebi 's/sqlite3([^.]|$)/td&/g' *`
+
add_library(tdsqlite STATIC ${SQLITE_SOURCE})
target_include_directories(tdsqlite PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_include_directories(tdsqlite SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
target_link_libraries(tdsqlite PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
+if (WIN32)
+ if (MINGW)
+ target_link_libraries(tdsqlite PRIVATE ws2_32 mswsock crypt32)
+ else()
+ target_link_libraries(tdsqlite PRIVATE ws2_32 Mswsock Crypt32)
+ endif()
+endif()
+
target_compile_definitions(tdsqlite PRIVATE
+ -DOMIT_MEMLOCK
-DSQLITE_DEFAULT_MEMSTATUS=0
- -DSQLITE_OMIT_LOAD_EXTENSION
+ -DSQLITE_DEFAULT_RECURSIVE_TRIGGERS=1
+ -DSQLITE_DEFAULT_SYNCHRONOUS=1
+ -DSQLITE_DISABLE_LFS
+ -DSQLITE_ENABLE_FTS5
+ -DSQLITE_HAS_CODEC
-DSQLITE_OMIT_DECLTYPE
+ -DSQLITE_OMIT_DEPRECATED
+ -DSQLITE_OMIT_DESERIALIZE
+ -DSQLITE_OMIT_LOAD_EXTENSION
-DSQLITE_OMIT_PROGRESS_CALLBACK
- #-DSQLITE_OMIT_DEPRECATED # SQLCipher uses deprecated sqlite3_profile
#-DSQLITE_OMIT_SHARED_CACHE
+ -DSQLITE_TEMP_STORE=2
)
-target_compile_definitions(tdsqlite PRIVATE -DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2 -DSQLITE_ENABLE_FTS5 -DSQLITE_DISABLE_LFS)
if (NOT WIN32)
target_compile_definitions(tdsqlite PRIVATE -DHAVE_USLEEP -DNDEBUG=1)
endif()
-if ("${CMAKE_SYSTEM_NAME}" STREQUAL "WindowsStore")
+if (CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
target_compile_definitions(tdsqlite PRIVATE -DSQLITE_OS_WINRT=1)
endif()
if (GCC OR CLANG)
target_compile_options(tdsqlite PRIVATE -Wno-deprecated-declarations -Wno-unused-variable -Wno-unused-const-variable -Wno-unused-function)
if (CLANG)
- target_compile_options(tdsqlite PRIVATE -Wno-parentheses-equality -Wno-unused-value)
+ target_compile_options(tdsqlite PRIVATE -Wno-parentheses-equality -Wno-unused-value -Wno-unused-command-line-argument -Qunused-arguments)
+ endif()
+ if (GCC AND NOT (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10.0))
+ target_compile_options(tdsqlite PRIVATE -Wno-return-local-addr -Wno-stringop-overflow)
endif()
elseif (MSVC)
target_compile_options(tdsqlite PRIVATE /wd4996)
endif()
install(TARGETS tdsqlite EXPORT TdTargets
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- RUNTIME DESTINATION bin
- INCLUDES DESTINATION include
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
diff --git a/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.c b/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.c
index a7bb6243ca..4fb5bc5e5d 100644
--- a/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.c
+++ b/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.c
@@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
-** version 3.15.2. By combining all the individual C code files into this
+** version 3.31.0. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@@ -14,7 +14,7 @@
** the text of this file. Search for "Begin file sqlite3.h" to find the start
** of the embedded sqlite3.h header file.) Additional code files may be needed
** if you want a wrapper to interface SQLite with your choice of programming
-** language. The code for the "sqlite3" command-line shell is also in a
+** language. The code for the "tdsqlite3" command-line shell is also in a
** separate file. This file contains only code for the core SQLite library.
*/
#define SQLITE_CORE 1
@@ -22,6 +22,774 @@
#ifndef SQLITE_PRIVATE
# define SQLITE_PRIVATE static
#endif
+/************** Begin file ctime.c *******************************************/
+/*
+** 2010 February 23
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file implements routines used to report what compile-time options
+** SQLite was built with.
+*/
+
+#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS /* IMP: R-16824-07538 */
+
+/*
+** Include the configuration header output by 'configure' if we're using the
+** autoconf-based build
+*/
+#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H)
+#include "config.h"
+#define SQLITECONFIG_H 1
+#endif
+
+/* These macros are provided to "stringify" the value of the define
+** for those options in which the value is meaningful. */
+#define CTIMEOPT_VAL_(opt) #opt
+#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
+
+/* Like CTIMEOPT_VAL, but especially for SQLITE_DEFAULT_LOOKASIDE. This
+** option requires a separate macro because legal values contain a single
+** comma. e.g. (-DSQLITE_DEFAULT_LOOKASIDE="100,100") */
+#define CTIMEOPT_VAL2_(opt1,opt2) #opt1 "," #opt2
+#define CTIMEOPT_VAL2(opt) CTIMEOPT_VAL2_(opt)
+
+/*
+** An array of names of all compile-time options. This array should
+** be sorted A-Z.
+**
+** This array looks large, but in a typical installation actually uses
+** only a handful of compile-time options, so most times this array is usually
+** rather short and uses little memory space.
+*/
+static const char * const tdsqlite3azCompileOpt[] = {
+
+/*
+** BEGIN CODE GENERATED BY tool/mkctime.tcl
+*/
+#if SQLITE_32BIT_ROWID
+ "32BIT_ROWID",
+#endif
+#if SQLITE_4_BYTE_ALIGNED_MALLOC
+ "4_BYTE_ALIGNED_MALLOC",
+#endif
+#if SQLITE_64BIT_STATS
+ "64BIT_STATS",
+#endif
+#if SQLITE_ALLOW_COVERING_INDEX_SCAN
+ "ALLOW_COVERING_INDEX_SCAN",
+#endif
+#if SQLITE_ALLOW_URI_AUTHORITY
+ "ALLOW_URI_AUTHORITY",
+#endif
+#ifdef SQLITE_BITMASK_TYPE
+ "BITMASK_TYPE=" CTIMEOPT_VAL(SQLITE_BITMASK_TYPE),
+#endif
+#if SQLITE_BUG_COMPATIBLE_20160819
+ "BUG_COMPATIBLE_20160819",
+#endif
+#if SQLITE_CASE_SENSITIVE_LIKE
+ "CASE_SENSITIVE_LIKE",
+#endif
+#if SQLITE_CHECK_PAGES
+ "CHECK_PAGES",
+#endif
+#if defined(__clang__) && defined(__clang_major__)
+ "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "."
+ CTIMEOPT_VAL(__clang_minor__) "."
+ CTIMEOPT_VAL(__clang_patchlevel__),
+#elif defined(_MSC_VER)
+ "COMPILER=msvc-" CTIMEOPT_VAL(_MSC_VER),
+#elif defined(__GNUC__) && defined(__VERSION__)
+ "COMPILER=gcc-" __VERSION__,
+#endif
+#if SQLITE_COVERAGE_TEST
+ "COVERAGE_TEST",
+#endif
+#if SQLITE_DEBUG
+ "DEBUG",
+#endif
+#if SQLITE_DEFAULT_AUTOMATIC_INDEX
+ "DEFAULT_AUTOMATIC_INDEX",
+#endif
+#if SQLITE_DEFAULT_AUTOVACUUM
+ "DEFAULT_AUTOVACUUM",
+#endif
+#ifdef SQLITE_DEFAULT_CACHE_SIZE
+ "DEFAULT_CACHE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_CACHE_SIZE),
+#endif
+#if SQLITE_DEFAULT_CKPTFULLFSYNC
+ "DEFAULT_CKPTFULLFSYNC",
+#endif
+#ifdef SQLITE_DEFAULT_FILE_FORMAT
+ "DEFAULT_FILE_FORMAT=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_FORMAT),
+#endif
+#ifdef SQLITE_DEFAULT_FILE_PERMISSIONS
+ "DEFAULT_FILE_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_PERMISSIONS),
+#endif
+#if SQLITE_DEFAULT_FOREIGN_KEYS
+ "DEFAULT_FOREIGN_KEYS",
+#endif
+#ifdef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT
+ "DEFAULT_JOURNAL_SIZE_LIMIT=" CTIMEOPT_VAL(SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT),
+#endif
+#ifdef SQLITE_DEFAULT_LOCKING_MODE
+ "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE),
+#endif
+#ifdef SQLITE_DEFAULT_LOOKASIDE
+ "DEFAULT_LOOKASIDE=" CTIMEOPT_VAL2(SQLITE_DEFAULT_LOOKASIDE),
+#endif
+#if SQLITE_DEFAULT_MEMSTATUS
+ "DEFAULT_MEMSTATUS",
+#endif
+#ifdef SQLITE_DEFAULT_MMAP_SIZE
+ "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE),
+#endif
+#ifdef SQLITE_DEFAULT_PAGE_SIZE
+ "DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_PAGE_SIZE),
+#endif
+#ifdef SQLITE_DEFAULT_PCACHE_INITSZ
+ "DEFAULT_PCACHE_INITSZ=" CTIMEOPT_VAL(SQLITE_DEFAULT_PCACHE_INITSZ),
+#endif
+#ifdef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS
+ "DEFAULT_PROXYDIR_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_PROXYDIR_PERMISSIONS),
+#endif
+#if SQLITE_DEFAULT_RECURSIVE_TRIGGERS
+ "DEFAULT_RECURSIVE_TRIGGERS",
+#endif
+#ifdef SQLITE_DEFAULT_ROWEST
+ "DEFAULT_ROWEST=" CTIMEOPT_VAL(SQLITE_DEFAULT_ROWEST),
+#endif
+#ifdef SQLITE_DEFAULT_SECTOR_SIZE
+ "DEFAULT_SECTOR_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_SECTOR_SIZE),
+#endif
+#ifdef SQLITE_DEFAULT_SYNCHRONOUS
+ "DEFAULT_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_SYNCHRONOUS),
+#endif
+#ifdef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT
+ "DEFAULT_WAL_AUTOCHECKPOINT=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_AUTOCHECKPOINT),
+#endif
+#ifdef SQLITE_DEFAULT_WAL_SYNCHRONOUS
+ "DEFAULT_WAL_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_SYNCHRONOUS),
+#endif
+#ifdef SQLITE_DEFAULT_WORKER_THREADS
+ "DEFAULT_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WORKER_THREADS),
+#endif
+#if SQLITE_DIRECT_OVERFLOW_READ
+ "DIRECT_OVERFLOW_READ",
+#endif
+#if SQLITE_DISABLE_DIRSYNC
+ "DISABLE_DIRSYNC",
+#endif
+#if SQLITE_DISABLE_FTS3_UNICODE
+ "DISABLE_FTS3_UNICODE",
+#endif
+#if SQLITE_DISABLE_FTS4_DEFERRED
+ "DISABLE_FTS4_DEFERRED",
+#endif
+#if SQLITE_DISABLE_INTRINSIC
+ "DISABLE_INTRINSIC",
+#endif
+#if SQLITE_DISABLE_LFS
+ "DISABLE_LFS",
+#endif
+#if SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS
+ "DISABLE_PAGECACHE_OVERFLOW_STATS",
+#endif
+#if SQLITE_DISABLE_SKIPAHEAD_DISTINCT
+ "DISABLE_SKIPAHEAD_DISTINCT",
+#endif
+#ifdef SQLITE_ENABLE_8_3_NAMES
+ "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES),
+#endif
+#if SQLITE_ENABLE_API_ARMOR
+ "ENABLE_API_ARMOR",
+#endif
+#if SQLITE_ENABLE_ATOMIC_WRITE
+ "ENABLE_ATOMIC_WRITE",
+#endif
+#if SQLITE_ENABLE_BATCH_ATOMIC_WRITE
+ "ENABLE_BATCH_ATOMIC_WRITE",
+#endif
+#if SQLITE_ENABLE_CEROD
+ "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD),
+#endif
+#if SQLITE_ENABLE_COLUMN_METADATA
+ "ENABLE_COLUMN_METADATA",
+#endif
+#if SQLITE_ENABLE_COLUMN_USED_MASK
+ "ENABLE_COLUMN_USED_MASK",
+#endif
+#if SQLITE_ENABLE_COSTMULT
+ "ENABLE_COSTMULT",
+#endif
+#if SQLITE_ENABLE_CURSOR_HINTS
+ "ENABLE_CURSOR_HINTS",
+#endif
+#if SQLITE_ENABLE_DBSTAT_VTAB
+ "ENABLE_DBSTAT_VTAB",
+#endif
+#if SQLITE_ENABLE_EXPENSIVE_ASSERT
+ "ENABLE_EXPENSIVE_ASSERT",
+#endif
+#if SQLITE_ENABLE_FTS1
+ "ENABLE_FTS1",
+#endif
+#if SQLITE_ENABLE_FTS2
+ "ENABLE_FTS2",
+#endif
+#if SQLITE_ENABLE_FTS3
+ "ENABLE_FTS3",
+#endif
+#if SQLITE_ENABLE_FTS3_PARENTHESIS
+ "ENABLE_FTS3_PARENTHESIS",
+#endif
+#if SQLITE_ENABLE_FTS3_TOKENIZER
+ "ENABLE_FTS3_TOKENIZER",
+#endif
+#if SQLITE_ENABLE_FTS4
+ "ENABLE_FTS4",
+#endif
+#if SQLITE_ENABLE_FTS5
+ "ENABLE_FTS5",
+#endif
+#if SQLITE_ENABLE_GEOPOLY
+ "ENABLE_GEOPOLY",
+#endif
+#if SQLITE_ENABLE_HIDDEN_COLUMNS
+ "ENABLE_HIDDEN_COLUMNS",
+#endif
+#if SQLITE_ENABLE_ICU
+ "ENABLE_ICU",
+#endif
+#if SQLITE_ENABLE_IOTRACE
+ "ENABLE_IOTRACE",
+#endif
+#if SQLITE_ENABLE_JSON1
+ "ENABLE_JSON1",
+#endif
+#if SQLITE_ENABLE_LOAD_EXTENSION
+ "ENABLE_LOAD_EXTENSION",
+#endif
+#ifdef SQLITE_ENABLE_LOCKING_STYLE
+ "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE),
+#endif
+#if SQLITE_ENABLE_MEMORY_MANAGEMENT
+ "ENABLE_MEMORY_MANAGEMENT",
+#endif
+#if SQLITE_ENABLE_MEMSYS3
+ "ENABLE_MEMSYS3",
+#endif
+#if SQLITE_ENABLE_MEMSYS5
+ "ENABLE_MEMSYS5",
+#endif
+#if SQLITE_ENABLE_MULTIPLEX
+ "ENABLE_MULTIPLEX",
+#endif
+#if SQLITE_ENABLE_NORMALIZE
+ "ENABLE_NORMALIZE",
+#endif
+#if SQLITE_ENABLE_NULL_TRIM
+ "ENABLE_NULL_TRIM",
+#endif
+#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK
+ "ENABLE_OVERSIZE_CELL_CHECK",
+#endif
+#if SQLITE_ENABLE_PREUPDATE_HOOK
+ "ENABLE_PREUPDATE_HOOK",
+#endif
+#if SQLITE_ENABLE_QPSG
+ "ENABLE_QPSG",
+#endif
+#if SQLITE_ENABLE_RBU
+ "ENABLE_RBU",
+#endif
+#if SQLITE_ENABLE_RTREE
+ "ENABLE_RTREE",
+#endif
+#if SQLITE_ENABLE_SELECTTRACE
+ "ENABLE_SELECTTRACE",
+#endif
+#if SQLITE_ENABLE_SESSION
+ "ENABLE_SESSION",
+#endif
+#if SQLITE_ENABLE_SNAPSHOT
+ "ENABLE_SNAPSHOT",
+#endif
+#if SQLITE_ENABLE_SORTER_REFERENCES
+ "ENABLE_SORTER_REFERENCES",
+#endif
+#if SQLITE_ENABLE_SQLLOG
+ "ENABLE_SQLLOG",
+#endif
+#if defined(SQLITE_ENABLE_STAT4)
+ "ENABLE_STAT4",
+#endif
+#if SQLITE_ENABLE_STMTVTAB
+ "ENABLE_STMTVTAB",
+#endif
+#if SQLITE_ENABLE_STMT_SCANSTATUS
+ "ENABLE_STMT_SCANSTATUS",
+#endif
+#if SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
+ "ENABLE_UNKNOWN_SQL_FUNCTION",
+#endif
+#if SQLITE_ENABLE_UNLOCK_NOTIFY
+ "ENABLE_UNLOCK_NOTIFY",
+#endif
+#if SQLITE_ENABLE_UPDATE_DELETE_LIMIT
+ "ENABLE_UPDATE_DELETE_LIMIT",
+#endif
+#if SQLITE_ENABLE_URI_00_ERROR
+ "ENABLE_URI_00_ERROR",
+#endif
+#if SQLITE_ENABLE_VFSTRACE
+ "ENABLE_VFSTRACE",
+#endif
+#if SQLITE_ENABLE_WHERETRACE
+ "ENABLE_WHERETRACE",
+#endif
+#if SQLITE_ENABLE_ZIPVFS
+ "ENABLE_ZIPVFS",
+#endif
+#if SQLITE_EXPLAIN_ESTIMATED_ROWS
+ "EXPLAIN_ESTIMATED_ROWS",
+#endif
+#if SQLITE_EXTRA_IFNULLROW
+ "EXTRA_IFNULLROW",
+#endif
+#ifdef SQLITE_EXTRA_INIT
+ "EXTRA_INIT=" CTIMEOPT_VAL(SQLITE_EXTRA_INIT),
+#endif
+#ifdef SQLITE_EXTRA_SHUTDOWN
+ "EXTRA_SHUTDOWN=" CTIMEOPT_VAL(SQLITE_EXTRA_SHUTDOWN),
+#endif
+#ifdef SQLITE_FTS3_MAX_EXPR_DEPTH
+ "FTS3_MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_FTS3_MAX_EXPR_DEPTH),
+#endif
+#if SQLITE_FTS5_ENABLE_TEST_MI
+ "FTS5_ENABLE_TEST_MI",
+#endif
+#if SQLITE_FTS5_NO_WITHOUT_ROWID
+ "FTS5_NO_WITHOUT_ROWID",
+#endif
+#if SQLITE_HAS_CODEC
+ "HAS_CODEC",
+#endif
+#if HAVE_ISNAN || SQLITE_HAVE_ISNAN
+ "HAVE_ISNAN",
+#endif
+#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX
+ "HOMEGROWN_RECURSIVE_MUTEX",
+#endif
+#if SQLITE_IGNORE_AFP_LOCK_ERRORS
+ "IGNORE_AFP_LOCK_ERRORS",
+#endif
+#if SQLITE_IGNORE_FLOCK_LOCK_ERRORS
+ "IGNORE_FLOCK_LOCK_ERRORS",
+#endif
+#if SQLITE_INLINE_MEMCPY
+ "INLINE_MEMCPY",
+#endif
+#if SQLITE_INT64_TYPE
+ "INT64_TYPE",
+#endif
+#ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX
+ "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX),
+#endif
+#if SQLITE_LIKE_DOESNT_MATCH_BLOBS
+ "LIKE_DOESNT_MATCH_BLOBS",
+#endif
+#if SQLITE_LOCK_TRACE
+ "LOCK_TRACE",
+#endif
+#if SQLITE_LOG_CACHE_SPILL
+ "LOG_CACHE_SPILL",
+#endif
+#ifdef SQLITE_MALLOC_SOFT_LIMIT
+ "MALLOC_SOFT_LIMIT=" CTIMEOPT_VAL(SQLITE_MALLOC_SOFT_LIMIT),
+#endif
+#ifdef SQLITE_MAX_ATTACHED
+ "MAX_ATTACHED=" CTIMEOPT_VAL(SQLITE_MAX_ATTACHED),
+#endif
+#ifdef SQLITE_MAX_COLUMN
+ "MAX_COLUMN=" CTIMEOPT_VAL(SQLITE_MAX_COLUMN),
+#endif
+#ifdef SQLITE_MAX_COMPOUND_SELECT
+ "MAX_COMPOUND_SELECT=" CTIMEOPT_VAL(SQLITE_MAX_COMPOUND_SELECT),
+#endif
+#ifdef SQLITE_MAX_DEFAULT_PAGE_SIZE
+ "MAX_DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_DEFAULT_PAGE_SIZE),
+#endif
+#ifdef SQLITE_MAX_EXPR_DEPTH
+ "MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_EXPR_DEPTH),
+#endif
+#ifdef SQLITE_MAX_FUNCTION_ARG
+ "MAX_FUNCTION_ARG=" CTIMEOPT_VAL(SQLITE_MAX_FUNCTION_ARG),
+#endif
+#ifdef SQLITE_MAX_LENGTH
+ "MAX_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LENGTH),
+#endif
+#ifdef SQLITE_MAX_LIKE_PATTERN_LENGTH
+ "MAX_LIKE_PATTERN_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LIKE_PATTERN_LENGTH),
+#endif
+#ifdef SQLITE_MAX_MEMORY
+ "MAX_MEMORY=" CTIMEOPT_VAL(SQLITE_MAX_MEMORY),
+#endif
+#ifdef SQLITE_MAX_MMAP_SIZE
+ "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE),
+#endif
+#ifdef SQLITE_MAX_MMAP_SIZE_
+ "MAX_MMAP_SIZE_=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE_),
+#endif
+#ifdef SQLITE_MAX_PAGE_COUNT
+ "MAX_PAGE_COUNT=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_COUNT),
+#endif
+#ifdef SQLITE_MAX_PAGE_SIZE
+ "MAX_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_SIZE),
+#endif
+#ifdef SQLITE_MAX_SCHEMA_RETRY
+ "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY),
+#endif
+#ifdef SQLITE_MAX_SQL_LENGTH
+ "MAX_SQL_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_SQL_LENGTH),
+#endif
+#ifdef SQLITE_MAX_TRIGGER_DEPTH
+ "MAX_TRIGGER_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_TRIGGER_DEPTH),
+#endif
+#ifdef SQLITE_MAX_VARIABLE_NUMBER
+ "MAX_VARIABLE_NUMBER=" CTIMEOPT_VAL(SQLITE_MAX_VARIABLE_NUMBER),
+#endif
+#ifdef SQLITE_MAX_VDBE_OP
+ "MAX_VDBE_OP=" CTIMEOPT_VAL(SQLITE_MAX_VDBE_OP),
+#endif
+#ifdef SQLITE_MAX_WORKER_THREADS
+ "MAX_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_MAX_WORKER_THREADS),
+#endif
+#if SQLITE_MEMDEBUG
+ "MEMDEBUG",
+#endif
+#if SQLITE_MIXED_ENDIAN_64BIT_FLOAT
+ "MIXED_ENDIAN_64BIT_FLOAT",
+#endif
+#if SQLITE_MMAP_READWRITE
+ "MMAP_READWRITE",
+#endif
+#if SQLITE_MUTEX_NOOP
+ "MUTEX_NOOP",
+#endif
+#if SQLITE_MUTEX_NREF
+ "MUTEX_NREF",
+#endif
+#if SQLITE_MUTEX_OMIT
+ "MUTEX_OMIT",
+#endif
+#if SQLITE_MUTEX_PTHREADS
+ "MUTEX_PTHREADS",
+#endif
+#if SQLITE_MUTEX_W32
+ "MUTEX_W32",
+#endif
+#if SQLITE_NEED_ERR_NAME
+ "NEED_ERR_NAME",
+#endif
+#if SQLITE_NOINLINE
+ "NOINLINE",
+#endif
+#if SQLITE_NO_SYNC
+ "NO_SYNC",
+#endif
+#if SQLITE_OMIT_ALTERTABLE
+ "OMIT_ALTERTABLE",
+#endif
+#if SQLITE_OMIT_ANALYZE
+ "OMIT_ANALYZE",
+#endif
+#if SQLITE_OMIT_ATTACH
+ "OMIT_ATTACH",
+#endif
+#if SQLITE_OMIT_AUTHORIZATION
+ "OMIT_AUTHORIZATION",
+#endif
+#if SQLITE_OMIT_AUTOINCREMENT
+ "OMIT_AUTOINCREMENT",
+#endif
+#if SQLITE_OMIT_AUTOINIT
+ "OMIT_AUTOINIT",
+#endif
+#if SQLITE_OMIT_AUTOMATIC_INDEX
+ "OMIT_AUTOMATIC_INDEX",
+#endif
+#if SQLITE_OMIT_AUTORESET
+ "OMIT_AUTORESET",
+#endif
+#if SQLITE_OMIT_AUTOVACUUM
+ "OMIT_AUTOVACUUM",
+#endif
+#if SQLITE_OMIT_BETWEEN_OPTIMIZATION
+ "OMIT_BETWEEN_OPTIMIZATION",
+#endif
+#if SQLITE_OMIT_BLOB_LITERAL
+ "OMIT_BLOB_LITERAL",
+#endif
+#if SQLITE_OMIT_BTREECOUNT
+ "OMIT_BTREECOUNT",
+#endif
+#if SQLITE_OMIT_CAST
+ "OMIT_CAST",
+#endif
+#if SQLITE_OMIT_CHECK
+ "OMIT_CHECK",
+#endif
+#if SQLITE_OMIT_COMPLETE
+ "OMIT_COMPLETE",
+#endif
+#if SQLITE_OMIT_COMPOUND_SELECT
+ "OMIT_COMPOUND_SELECT",
+#endif
+#if SQLITE_OMIT_CONFLICT_CLAUSE
+ "OMIT_CONFLICT_CLAUSE",
+#endif
+#if SQLITE_OMIT_CTE
+ "OMIT_CTE",
+#endif
+#if SQLITE_OMIT_DATETIME_FUNCS
+ "OMIT_DATETIME_FUNCS",
+#endif
+#if SQLITE_OMIT_DECLTYPE
+ "OMIT_DECLTYPE",
+#endif
+#if SQLITE_OMIT_DEPRECATED
+ "OMIT_DEPRECATED",
+#endif
+#if SQLITE_OMIT_DISKIO
+ "OMIT_DISKIO",
+#endif
+#if SQLITE_OMIT_EXPLAIN
+ "OMIT_EXPLAIN",
+#endif
+#if SQLITE_OMIT_FLAG_PRAGMAS
+ "OMIT_FLAG_PRAGMAS",
+#endif
+#if SQLITE_OMIT_FLOATING_POINT
+ "OMIT_FLOATING_POINT",
+#endif
+#if SQLITE_OMIT_FOREIGN_KEY
+ "OMIT_FOREIGN_KEY",
+#endif
+#if SQLITE_OMIT_GET_TABLE
+ "OMIT_GET_TABLE",
+#endif
+#if SQLITE_OMIT_HEX_INTEGER
+ "OMIT_HEX_INTEGER",
+#endif
+#if SQLITE_OMIT_INCRBLOB
+ "OMIT_INCRBLOB",
+#endif
+#if SQLITE_OMIT_INTEGRITY_CHECK
+ "OMIT_INTEGRITY_CHECK",
+#endif
+#if SQLITE_OMIT_LIKE_OPTIMIZATION
+ "OMIT_LIKE_OPTIMIZATION",
+#endif
+#if SQLITE_OMIT_LOAD_EXTENSION
+ "OMIT_LOAD_EXTENSION",
+#endif
+#if SQLITE_OMIT_LOCALTIME
+ "OMIT_LOCALTIME",
+#endif
+#if SQLITE_OMIT_LOOKASIDE
+ "OMIT_LOOKASIDE",
+#endif
+#if SQLITE_OMIT_MEMORYDB
+ "OMIT_MEMORYDB",
+#endif
+#if SQLITE_OMIT_OR_OPTIMIZATION
+ "OMIT_OR_OPTIMIZATION",
+#endif
+#if SQLITE_OMIT_PAGER_PRAGMAS
+ "OMIT_PAGER_PRAGMAS",
+#endif
+#if SQLITE_OMIT_PARSER_TRACE
+ "OMIT_PARSER_TRACE",
+#endif
+#if SQLITE_OMIT_POPEN
+ "OMIT_POPEN",
+#endif
+#if SQLITE_OMIT_PRAGMA
+ "OMIT_PRAGMA",
+#endif
+#if SQLITE_OMIT_PROGRESS_CALLBACK
+ "OMIT_PROGRESS_CALLBACK",
+#endif
+#if SQLITE_OMIT_QUICKBALANCE
+ "OMIT_QUICKBALANCE",
+#endif
+#if SQLITE_OMIT_REINDEX
+ "OMIT_REINDEX",
+#endif
+#if SQLITE_OMIT_SCHEMA_PRAGMAS
+ "OMIT_SCHEMA_PRAGMAS",
+#endif
+#if SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
+ "OMIT_SCHEMA_VERSION_PRAGMAS",
+#endif
+#if SQLITE_OMIT_SHARED_CACHE
+ "OMIT_SHARED_CACHE",
+#endif
+#if SQLITE_OMIT_SHUTDOWN_DIRECTORIES
+ "OMIT_SHUTDOWN_DIRECTORIES",
+#endif
+#if SQLITE_OMIT_SUBQUERY
+ "OMIT_SUBQUERY",
+#endif
+#if SQLITE_OMIT_TCL_VARIABLE
+ "OMIT_TCL_VARIABLE",
+#endif
+#if SQLITE_OMIT_TEMPDB
+ "OMIT_TEMPDB",
+#endif
+#if SQLITE_OMIT_TEST_CONTROL
+ "OMIT_TEST_CONTROL",
+#endif
+#if SQLITE_OMIT_TRACE
+ "OMIT_TRACE",
+#endif
+#if SQLITE_OMIT_TRIGGER
+ "OMIT_TRIGGER",
+#endif
+#if SQLITE_OMIT_TRUNCATE_OPTIMIZATION
+ "OMIT_TRUNCATE_OPTIMIZATION",
+#endif
+#if SQLITE_OMIT_UTF16
+ "OMIT_UTF16",
+#endif
+#if SQLITE_OMIT_VACUUM
+ "OMIT_VACUUM",
+#endif
+#if SQLITE_OMIT_VIEW
+ "OMIT_VIEW",
+#endif
+#if SQLITE_OMIT_VIRTUALTABLE
+ "OMIT_VIRTUALTABLE",
+#endif
+#if SQLITE_OMIT_WAL
+ "OMIT_WAL",
+#endif
+#if SQLITE_OMIT_WSD
+ "OMIT_WSD",
+#endif
+#if SQLITE_OMIT_XFER_OPT
+ "OMIT_XFER_OPT",
+#endif
+#if SQLITE_PCACHE_SEPARATE_HEADER
+ "PCACHE_SEPARATE_HEADER",
+#endif
+#if SQLITE_PERFORMANCE_TRACE
+ "PERFORMANCE_TRACE",
+#endif
+#if SQLITE_POWERSAFE_OVERWRITE
+ "POWERSAFE_OVERWRITE",
+#endif
+#if SQLITE_PREFER_PROXY_LOCKING
+ "PREFER_PROXY_LOCKING",
+#endif
+#if SQLITE_PROXY_DEBUG
+ "PROXY_DEBUG",
+#endif
+#if SQLITE_REVERSE_UNORDERED_SELECTS
+ "REVERSE_UNORDERED_SELECTS",
+#endif
+#if SQLITE_RTREE_INT_ONLY
+ "RTREE_INT_ONLY",
+#endif
+#if SQLITE_SECURE_DELETE
+ "SECURE_DELETE",
+#endif
+#if SQLITE_SMALL_STACK
+ "SMALL_STACK",
+#endif
+#ifdef SQLITE_SORTER_PMASZ
+ "SORTER_PMASZ=" CTIMEOPT_VAL(SQLITE_SORTER_PMASZ),
+#endif
+#if SQLITE_SOUNDEX
+ "SOUNDEX",
+#endif
+#ifdef SQLITE_STAT4_SAMPLES
+ "STAT4_SAMPLES=" CTIMEOPT_VAL(SQLITE_STAT4_SAMPLES),
+#endif
+#ifdef SQLITE_STMTJRNL_SPILL
+ "STMTJRNL_SPILL=" CTIMEOPT_VAL(SQLITE_STMTJRNL_SPILL),
+#endif
+#if SQLITE_SUBSTR_COMPATIBILITY
+ "SUBSTR_COMPATIBILITY",
+#endif
+#if SQLITE_SYSTEM_MALLOC
+ "SYSTEM_MALLOC",
+#endif
+#if SQLITE_TCL
+ "TCL",
+#endif
+#ifdef SQLITE_TEMP_STORE
+ "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE),
+#endif
+#if SQLITE_TEST
+ "TEST",
+#endif
+#if defined(SQLITE_THREADSAFE)
+ "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE),
+#elif defined(THREADSAFE)
+ "THREADSAFE=" CTIMEOPT_VAL(THREADSAFE),
+#else
+ "THREADSAFE=1",
+#endif
+#if SQLITE_UNLINK_AFTER_CLOSE
+ "UNLINK_AFTER_CLOSE",
+#endif
+#if SQLITE_UNTESTABLE
+ "UNTESTABLE",
+#endif
+#if SQLITE_USER_AUTHENTICATION
+ "USER_AUTHENTICATION",
+#endif
+#if SQLITE_USE_ALLOCA
+ "USE_ALLOCA",
+#endif
+#if SQLITE_USE_FCNTL_TRACE
+ "USE_FCNTL_TRACE",
+#endif
+#if SQLITE_USE_URI
+ "USE_URI",
+#endif
+#if SQLITE_VDBE_COVERAGE
+ "VDBE_COVERAGE",
+#endif
+#if SQLITE_WIN32_MALLOC
+ "WIN32_MALLOC",
+#endif
+#if SQLITE_ZERO_MALLOC
+ "ZERO_MALLOC",
+#endif
+/*
+** END CODE GENERATED BY tool/mkctime.tcl
+*/
+};
+
+SQLITE_PRIVATE const char **tdsqlite3CompileOptions(int *pnOpt){
+ *pnOpt = sizeof(tdsqlite3azCompileOpt) / sizeof(tdsqlite3azCompileOpt[0]);
+ return (const char**)tdsqlite3azCompileOpt;
+}
+
+#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
+
+/************** End of ctime.c ***********************************************/
/************** Begin file sqliteInt.h ***************************************/
/*
** 2001 September 15
@@ -76,14 +844,6 @@
#endif
/*
-** Make sure that rand_s() is available on Windows systems with MSVC 2005
-** or higher.
-*/
-#if defined(_MSC_VER) && _MSC_VER>=1400
-# define _CRT_RAND_S
-#endif
-
-/*
** Include the header file used to customize the compiler options for MSVC.
** This should be done first so that it can successfully prevent spurious
** compiler warnings due to subsequent content in this file and other files
@@ -126,6 +886,11 @@
#pragma warning(disable : 4706)
#endif /* defined(_MSC_VER) */
+#if defined(_MSC_VER) && !defined(_WIN64)
+#undef SQLITE_4_BYTE_ALIGNED_MALLOC
+#define SQLITE_4_BYTE_ALIGNED_MALLOC
+#endif /* defined(_MSC_VER) && !defined(_WIN64) */
+
#endif /* SQLITE_MSVC_H */
/************** End of msvc.h ************************************************/
@@ -204,12 +969,29 @@
# define _LARGEFILE_SOURCE 1
#endif
-/* What version of GCC is being used. 0 means GCC is not being used */
-#ifdef __GNUC__
+/* The GCC_VERSION and MSVC_VERSION macros are used to
+** conditionally include optimizations for each of these compilers. A
+** value of 0 means that compiler is not being used. The
+** SQLITE_DISABLE_INTRINSIC macro means do not use any compiler-specific
+** optimizations, and hence set all compiler macros to 0
+**
+** There was once also a CLANG_VERSION macro. However, we learn that the
+** version numbers in clang are for "marketing" only and are inconsistent
+** and unreliable. Fortunately, all versions of clang also recognize the
+** gcc version numbers and have reasonable settings for gcc version numbers,
+** so the GCC_VERSION macro will be set to a correct non-zero value even
+** when compiling with clang.
+*/
+#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC)
# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__)
#else
# define GCC_VERSION 0
#endif
+#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC)
+# define MSVC_VERSION _MSC_VER
+#else
+# define MSVC_VERSION 0
+#endif
/* Needed for various definitions... */
#if defined(__GNUC__) && !defined(_GNU_SOURCE)
@@ -259,7 +1041,7 @@
/************** Include sqlite3.h in the middle of sqliteInt.h ***************/
/************** Begin file sqlite3.h *****************************************/
/*
-** 2001 September 15
+** 2001-09-15
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
@@ -374,20 +1156,22 @@ extern "C" {
** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to
** a string which identifies a particular check-in of SQLite
** within its configuration management system. ^The SQLITE_SOURCE_ID
-** string contains the date and time of the check-in (UTC) and an SHA1
-** hash of the entire source tree.
+** string contains the date and time of the check-in (UTC) and a SHA1
+** or SHA3-256 hash of the entire source tree. If the source code has
+** been edited in any way since it was last checked in, then the last
+** four hexadecimal digits of the hash may be modified.
**
-** See also: [sqlite3_libversion()],
-** [sqlite3_libversion_number()], [sqlite3_sourceid()],
+** See also: [tdsqlite3_libversion()],
+** [tdsqlite3_libversion_number()], [tdsqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.15.2"
-#define SQLITE_VERSION_NUMBER 3015002
-#define SQLITE_SOURCE_ID "2016-11-28 19:13:37 bbd85d235f7037c6a033a9690534391ffeacecc8"
+#define SQLITE_VERSION "3.31.0"
+#define SQLITE_VERSION_NUMBER 3031000
+#define SQLITE_SOURCE_ID "2020-01-22 18:38:59 f6affdd41608946fcfcea914ece149038a8b25a62bbe719ed2561c649b86alt1"
/*
** CAPI3REF: Run-Time Library Version Numbers
-** KEYWORDS: sqlite3_version, sqlite3_sourceid
+** KEYWORDS: tdsqlite3_version tdsqlite3_sourceid
**
** These interfaces provide the same information as the [SQLITE_VERSION],
** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros
@@ -398,59 +1182,64 @@ extern "C" {
** compiled with matching library and header files.
**
** <blockquote><pre>
-** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
-** assert( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)==0 );
-** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
+** assert( tdsqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
+** assert( strncmp(tdsqlite3_sourceid(),SQLITE_SOURCE_ID,80)==0 );
+** assert( strcmp(tdsqlite3_libversion(),SQLITE_VERSION)==0 );
** </pre></blockquote>)^
**
-** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION]
-** macro. ^The sqlite3_libversion() function returns a pointer to the
-** to the sqlite3_version[] string constant. The sqlite3_libversion()
+** ^The tdsqlite3_version[] string constant contains the text of [SQLITE_VERSION]
+** macro. ^The tdsqlite3_libversion() function returns a pointer to the
+** to the tdsqlite3_version[] string constant. The tdsqlite3_libversion()
** function is provided for use in DLLs since DLL users usually do not have
** direct access to string constants within the DLL. ^The
-** sqlite3_libversion_number() function returns an integer equal to
-** [SQLITE_VERSION_NUMBER]. ^The sqlite3_sourceid() function returns
+** tdsqlite3_libversion_number() function returns an integer equal to
+** [SQLITE_VERSION_NUMBER]. ^(The tdsqlite3_sourceid() function returns
** a pointer to a string constant whose value is the same as the
-** [SQLITE_SOURCE_ID] C preprocessor macro.
+** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built
+** using an edited copy of [the amalgamation], then the last four characters
+** of the hash might be different from [SQLITE_SOURCE_ID].)^
**
** See also: [sqlite_version()] and [sqlite_source_id()].
*/
-SQLITE_API const char sqlite3_version[] = SQLITE_VERSION;
-SQLITE_API const char *sqlite3_libversion(void);
-SQLITE_API const char *sqlite3_sourceid(void);
-SQLITE_API int sqlite3_libversion_number(void);
+SQLITE_API const char tdsqlite3_version[] = SQLITE_VERSION;
+SQLITE_API const char *tdsqlite3_libversion(void);
+SQLITE_API const char *tdsqlite3_sourceid(void);
+SQLITE_API int tdsqlite3_libversion_number(void);
/*
** CAPI3REF: Run-Time Library Compilation Options Diagnostics
**
-** ^The sqlite3_compileoption_used() function returns 0 or 1
+** ^The tdsqlite3_compileoption_used() function returns 0 or 1
** indicating whether the specified option was defined at
** compile time. ^The SQLITE_ prefix may be omitted from the
-** option name passed to sqlite3_compileoption_used().
+** option name passed to tdsqlite3_compileoption_used().
**
-** ^The sqlite3_compileoption_get() function allows iterating
+** ^The tdsqlite3_compileoption_get() function allows iterating
** over the list of options that were defined at compile time by
** returning the N-th compile time option string. ^If N is out of range,
-** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_
+** tdsqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_
** prefix is omitted from any strings returned by
-** sqlite3_compileoption_get().
+** tdsqlite3_compileoption_get().
**
-** ^Support for the diagnostic functions sqlite3_compileoption_used()
-** and sqlite3_compileoption_get() may be omitted by specifying the
+** ^Support for the diagnostic functions tdsqlite3_compileoption_used()
+** and tdsqlite3_compileoption_get() may be omitted by specifying the
** [SQLITE_OMIT_COMPILEOPTION_DIAGS] option at compile time.
**
** See also: SQL functions [sqlite_compileoption_used()] and
** [sqlite_compileoption_get()] and the [compile_options pragma].
*/
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
-SQLITE_API int sqlite3_compileoption_used(const char *zOptName);
-SQLITE_API const char *sqlite3_compileoption_get(int N);
+SQLITE_API int tdsqlite3_compileoption_used(const char *zOptName);
+SQLITE_API const char *tdsqlite3_compileoption_get(int N);
+#else
+# define tdsqlite3_compileoption_used(X) 0
+# define tdsqlite3_compileoption_get(X) ((void*)0)
#endif
/*
** CAPI3REF: Test To See If The Library Is Threadsafe
**
-** ^The sqlite3_threadsafe() function returns zero if and only if
+** ^The tdsqlite3_threadsafe() function returns zero if and only if
** SQLite was compiled with mutexing code omitted due to the
** [SQLITE_THREADSAFE] compile-time option being set to 0.
**
@@ -473,33 +1262,33 @@ SQLITE_API const char *sqlite3_compileoption_get(int N);
** This interface only reports on the compile-time mutex setting
** of the [SQLITE_THREADSAFE] flag. If SQLite is compiled with
** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but
-** can be fully or partially disabled using a call to [sqlite3_config()]
+** can be fully or partially disabled using a call to [tdsqlite3_config()]
** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD],
** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the
-** sqlite3_threadsafe() function shows only the compile-time setting of
+** tdsqlite3_threadsafe() function shows only the compile-time setting of
** thread safety, not any run-time changes to that setting made by
-** sqlite3_config(). In other words, the return value from sqlite3_threadsafe()
-** is unchanged by calls to sqlite3_config().)^
+** tdsqlite3_config(). In other words, the return value from tdsqlite3_threadsafe()
+** is unchanged by calls to tdsqlite3_config().)^
**
** See the [threading mode] documentation for additional information.
*/
-SQLITE_API int sqlite3_threadsafe(void);
+SQLITE_API int tdsqlite3_threadsafe(void);
/*
** CAPI3REF: Database Connection Handle
** KEYWORDS: {database connection} {database connections}
**
** Each open SQLite database is represented by a pointer to an instance of
-** the opaque structure named "sqlite3". It is useful to think of an sqlite3
-** pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and
-** [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()]
-** and [sqlite3_close_v2()] are its destructors. There are many other
+** the opaque structure named "tdsqlite3". It is useful to think of an tdsqlite3
+** pointer as an object. The [tdsqlite3_open()], [tdsqlite3_open16()], and
+** [tdsqlite3_open_v2()] interfaces are its constructors, and [tdsqlite3_close()]
+** and [tdsqlite3_close_v2()] are its destructors. There are many other
** interfaces (such as
-** [sqlite3_prepare_v2()], [sqlite3_create_function()], and
-** [sqlite3_busy_timeout()] to name but three) that are methods on an
-** sqlite3 object.
+** [tdsqlite3_prepare_v2()], [tdsqlite3_create_function()], and
+** [tdsqlite3_busy_timeout()] to name but three) that are methods on an
+** tdsqlite3 object.
*/
-typedef struct sqlite3 sqlite3;
+typedef struct tdsqlite3 tdsqlite3;
/*
** CAPI3REF: 64-Bit Integer Types
@@ -508,18 +1297,22 @@ typedef struct sqlite3 sqlite3;
** Because there is no cross-platform way to specify 64-bit integer types
** SQLite includes typedefs for 64-bit signed and unsigned integers.
**
-** The sqlite3_int64 and sqlite3_uint64 are the preferred type definitions.
+** The tdsqlite3_int64 and tdsqlite3_uint64 are the preferred type definitions.
** The sqlite_int64 and sqlite_uint64 types are supported for backwards
** compatibility only.
**
-** ^The sqlite3_int64 and sqlite_int64 types can store integer values
+** ^The tdsqlite3_int64 and sqlite_int64 types can store integer values
** between -9223372036854775808 and +9223372036854775807 inclusive. ^The
-** sqlite3_uint64 and sqlite_uint64 types can store integer values
+** tdsqlite3_uint64 and sqlite_uint64 types can store integer values
** between 0 and +18446744073709551615 inclusive.
*/
#ifdef SQLITE_INT64_TYPE
typedef SQLITE_INT64_TYPE sqlite_int64;
- typedef unsigned SQLITE_INT64_TYPE sqlite_uint64;
+# ifdef SQLITE_UINT64_TYPE
+ typedef SQLITE_UINT64_TYPE sqlite_uint64;
+# else
+ typedef unsigned SQLITE_INT64_TYPE sqlite_uint64;
+# endif
#elif defined(_MSC_VER) || defined(__BORLANDC__)
typedef __int64 sqlite_int64;
typedef unsigned __int64 sqlite_uint64;
@@ -527,116 +1320,116 @@ typedef struct sqlite3 sqlite3;
typedef long long int sqlite_int64;
typedef unsigned long long int sqlite_uint64;
#endif
-typedef sqlite_int64 sqlite3_int64;
-typedef sqlite_uint64 sqlite3_uint64;
+typedef sqlite_int64 tdsqlite3_int64;
+typedef sqlite_uint64 tdsqlite3_uint64;
/*
** If compiling for a processor that lacks floating point support,
** substitute integer for floating-point.
*/
#ifdef SQLITE_OMIT_FLOATING_POINT
-# define double sqlite3_int64
+# define double tdsqlite3_int64
#endif
/*
** CAPI3REF: Closing A Database Connection
-** DESTRUCTOR: sqlite3
+** DESTRUCTOR: tdsqlite3
**
-** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors
-** for the [sqlite3] object.
-** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if
-** the [sqlite3] object is successfully destroyed and all associated
+** ^The tdsqlite3_close() and tdsqlite3_close_v2() routines are destructors
+** for the [tdsqlite3] object.
+** ^Calls to tdsqlite3_close() and tdsqlite3_close_v2() return [SQLITE_OK] if
+** the [tdsqlite3] object is successfully destroyed and all associated
** resources are deallocated.
**
** ^If the database connection is associated with unfinalized prepared
-** statements or unfinished sqlite3_backup objects then sqlite3_close()
+** statements or unfinished tdsqlite3_backup objects then tdsqlite3_close()
** will leave the database connection open and return [SQLITE_BUSY].
-** ^If sqlite3_close_v2() is called with unfinalized prepared statements
-** and/or unfinished sqlite3_backups, then the database connection becomes
+** ^If tdsqlite3_close_v2() is called with unfinalized prepared statements
+** and/or unfinished tdsqlite3_backups, then the database connection becomes
** an unusable "zombie" which will automatically be deallocated when the
-** last prepared statement is finalized or the last sqlite3_backup is
-** finished. The sqlite3_close_v2() interface is intended for use with
+** last prepared statement is finalized or the last tdsqlite3_backup is
+** finished. The tdsqlite3_close_v2() interface is intended for use with
** host languages that are garbage collected, and where the order in which
** destructors are called is arbitrary.
**
-** Applications should [sqlite3_finalize | finalize] all [prepared statements],
-** [sqlite3_blob_close | close] all [BLOB handles], and
-** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated
-** with the [sqlite3] object prior to attempting to close the object. ^If
-** sqlite3_close_v2() is called on a [database connection] that still has
+** Applications should [tdsqlite3_finalize | finalize] all [prepared statements],
+** [tdsqlite3_blob_close | close] all [BLOB handles], and
+** [tdsqlite3_backup_finish | finish] all [tdsqlite3_backup] objects associated
+** with the [tdsqlite3] object prior to attempting to close the object. ^If
+** tdsqlite3_close_v2() is called on a [database connection] that still has
** outstanding [prepared statements], [BLOB handles], and/or
-** [sqlite3_backup] objects then it returns [SQLITE_OK] and the deallocation
+** [tdsqlite3_backup] objects then it returns [SQLITE_OK] and the deallocation
** of resources is deferred until all [prepared statements], [BLOB handles],
-** and [sqlite3_backup] objects are also destroyed.
+** and [tdsqlite3_backup] objects are also destroyed.
**
-** ^If an [sqlite3] object is destroyed while a transaction is open,
+** ^If an [tdsqlite3] object is destroyed while a transaction is open,
** the transaction is automatically rolled back.
**
-** The C parameter to [sqlite3_close(C)] and [sqlite3_close_v2(C)]
+** The C parameter to [tdsqlite3_close(C)] and [tdsqlite3_close_v2(C)]
** must be either a NULL
-** pointer or an [sqlite3] object pointer obtained
-** from [sqlite3_open()], [sqlite3_open16()], or
-** [sqlite3_open_v2()], and not previously closed.
-** ^Calling sqlite3_close() or sqlite3_close_v2() with a NULL pointer
+** pointer or an [tdsqlite3] object pointer obtained
+** from [tdsqlite3_open()], [tdsqlite3_open16()], or
+** [tdsqlite3_open_v2()], and not previously closed.
+** ^Calling tdsqlite3_close() or tdsqlite3_close_v2() with a NULL pointer
** argument is a harmless no-op.
*/
-SQLITE_API int sqlite3_close(sqlite3*);
-SQLITE_API int sqlite3_close_v2(sqlite3*);
+SQLITE_API int tdsqlite3_close(tdsqlite3*);
+SQLITE_API int tdsqlite3_close_v2(tdsqlite3*);
/*
** The type for a callback function.
** This is legacy and deprecated. It is included for historical
** compatibility and is not documented.
*/
-typedef int (*sqlite3_callback)(void*,int,char**, char**);
+typedef int (*tdsqlite3_callback)(void*,int,char**, char**);
/*
** CAPI3REF: One-Step Query Execution Interface
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** The sqlite3_exec() interface is a convenience wrapper around
-** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()],
+** The tdsqlite3_exec() interface is a convenience wrapper around
+** [tdsqlite3_prepare_v2()], [tdsqlite3_step()], and [tdsqlite3_finalize()],
** that allows an application to run multiple statements of SQL
** without having to use a lot of C code.
**
-** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded,
+** ^The tdsqlite3_exec() interface runs zero or more UTF-8 encoded,
** semicolon-separate SQL statements passed into its 2nd argument,
** in the context of the [database connection] passed in as its 1st
** argument. ^If the callback function of the 3rd argument to
-** sqlite3_exec() is not NULL, then it is invoked for each result row
+** tdsqlite3_exec() is not NULL, then it is invoked for each result row
** coming out of the evaluated SQL statements. ^The 4th argument to
-** sqlite3_exec() is relayed through to the 1st argument of each
-** callback invocation. ^If the callback pointer to sqlite3_exec()
+** tdsqlite3_exec() is relayed through to the 1st argument of each
+** callback invocation. ^If the callback pointer to tdsqlite3_exec()
** is NULL, then no callback is ever invoked and result rows are
** ignored.
**
** ^If an error occurs while evaluating the SQL statements passed into
-** sqlite3_exec(), then execution of the current statement stops and
-** subsequent statements are skipped. ^If the 5th parameter to sqlite3_exec()
+** tdsqlite3_exec(), then execution of the current statement stops and
+** subsequent statements are skipped. ^If the 5th parameter to tdsqlite3_exec()
** is not NULL then any error message is written into memory obtained
-** from [sqlite3_malloc()] and passed back through the 5th parameter.
-** To avoid memory leaks, the application should invoke [sqlite3_free()]
+** from [tdsqlite3_malloc()] and passed back through the 5th parameter.
+** To avoid memory leaks, the application should invoke [tdsqlite3_free()]
** on error message strings returned through the 5th parameter of
-** sqlite3_exec() after the error message string is no longer needed.
-** ^If the 5th parameter to sqlite3_exec() is not NULL and no errors
-** occur, then sqlite3_exec() sets the pointer in its 5th parameter to
+** tdsqlite3_exec() after the error message string is no longer needed.
+** ^If the 5th parameter to tdsqlite3_exec() is not NULL and no errors
+** occur, then tdsqlite3_exec() sets the pointer in its 5th parameter to
** NULL before returning.
**
-** ^If an sqlite3_exec() callback returns non-zero, the sqlite3_exec()
+** ^If an tdsqlite3_exec() callback returns non-zero, the tdsqlite3_exec()
** routine returns SQLITE_ABORT without invoking the callback again and
** without running any subsequent SQL statements.
**
-** ^The 2nd argument to the sqlite3_exec() callback function is the
-** number of columns in the result. ^The 3rd argument to the sqlite3_exec()
+** ^The 2nd argument to the tdsqlite3_exec() callback function is the
+** number of columns in the result. ^The 3rd argument to the tdsqlite3_exec()
** callback is an array of pointers to strings obtained as if from
-** [sqlite3_column_text()], one for each column. ^If an element of a
+** [tdsqlite3_column_text()], one for each column. ^If an element of a
** result row is NULL then the corresponding string pointer for the
-** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the
-** sqlite3_exec() callback is an array of pointers to strings where each
+** tdsqlite3_exec() callback is a NULL pointer. ^The 4th argument to the
+** tdsqlite3_exec() callback is an array of pointers to strings where each
** entry represents the name of corresponding result column as obtained
-** from [sqlite3_column_name()].
+** from [tdsqlite3_column_name()].
**
-** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer
+** ^If the 2nd parameter to tdsqlite3_exec() is a NULL pointer, a pointer
** to an empty string, or a pointer that contains only whitespace and/or
** SQL comments, then no SQL statements are evaluated and the database
** is not changed.
@@ -644,16 +1437,16 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** Restrictions:
**
** <ul>
-** <li> The application must ensure that the 1st parameter to sqlite3_exec()
+** <li> The application must ensure that the 1st parameter to tdsqlite3_exec()
** is a valid and open [database connection].
** <li> The application must not close the [database connection] specified by
-** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running.
+** the 1st parameter to tdsqlite3_exec() while tdsqlite3_exec() is running.
** <li> The application must not modify the SQL statement text passed into
-** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running.
+** the 2nd parameter of tdsqlite3_exec() while tdsqlite3_exec() is running.
** </ul>
*/
-SQLITE_API int sqlite3_exec(
- sqlite3*, /* An open database */
+SQLITE_API int tdsqlite3_exec(
+ tdsqlite3*, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
@@ -673,7 +1466,7 @@ SQLITE_API int sqlite3_exec(
*/
#define SQLITE_OK 0 /* Successful result */
/* beginning-of-error-codes */
-#define SQLITE_ERROR 1 /* SQL error or missing database */
+#define SQLITE_ERROR 1 /* Generic error */
#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */
#define SQLITE_PERM 3 /* Access permission denied */
#define SQLITE_ABORT 4 /* Callback routine requested an abort */
@@ -681,14 +1474,14 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_LOCKED 6 /* A table in the database is locked */
#define SQLITE_NOMEM 7 /* A malloc() failed */
#define SQLITE_READONLY 8 /* Attempt to write a readonly database */
-#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/
+#define SQLITE_INTERRUPT 9 /* Operation terminated by tdsqlite3_interrupt()*/
#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */
#define SQLITE_CORRUPT 11 /* The database disk image is malformed */
-#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */
+#define SQLITE_NOTFOUND 12 /* Unknown opcode in tdsqlite3_file_control() */
#define SQLITE_FULL 13 /* Insertion failed because database is full */
#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
#define SQLITE_PROTOCOL 15 /* Database lock protocol error */
-#define SQLITE_EMPTY 16 /* Database is empty */
+#define SQLITE_EMPTY 16 /* Internal use only */
#define SQLITE_SCHEMA 17 /* The database schema changed */
#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */
#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
@@ -696,13 +1489,13 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_MISUSE 21 /* Library used incorrectly */
#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */
#define SQLITE_AUTH 23 /* Authorization denied */
-#define SQLITE_FORMAT 24 /* Auxiliary database format error */
-#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */
+#define SQLITE_FORMAT 24 /* Not used */
+#define SQLITE_RANGE 25 /* 2nd parameter to tdsqlite3_bind out of range */
#define SQLITE_NOTADB 26 /* File opened that is not a database file */
-#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */
-#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */
-#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
-#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
+#define SQLITE_NOTICE 27 /* Notifications from tdsqlite3_log() */
+#define SQLITE_WARNING 28 /* Warnings from tdsqlite3_log() */
+#define SQLITE_ROW 100 /* tdsqlite3_step() has another row ready */
+#define SQLITE_DONE 101 /* tdsqlite3_step() has finished executing */
/* end-of-error-codes */
/*
@@ -718,10 +1511,13 @@ SQLITE_API int sqlite3_exec(
** support for additional result codes that provide more detailed information
** about errors. These [extended result codes] are enabled or disabled
** on a per database connection basis using the
-** [sqlite3_extended_result_codes()] API. Or, the extended code for
+** [tdsqlite3_extended_result_codes()] API. Or, the extended code for
** the most recent error can be obtained using
-** [sqlite3_extended_errcode()].
+** [tdsqlite3_extended_errcode()].
*/
+#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8))
+#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8))
+#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8))
#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
@@ -750,18 +1546,27 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8))
#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8))
#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8))
+#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8))
+#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8))
+#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
+#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8))
#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8))
#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8))
#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8))
#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8))
+#define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5<<8)) /* Not Used */
+#define SQLITE_CANTOPEN_SYMLINK (SQLITE_CANTOPEN | (6<<8))
#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8))
+#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8))
#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8))
#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8))
#define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8))
#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4<<8))
+#define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5<<8))
+#define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6<<8))
#define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8))
#define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1<<8))
#define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2<<8))
@@ -773,27 +1578,29 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8))
#define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8))
#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8))
+#define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8))
#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8))
#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8))
#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8))
#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8))
#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8))
+#define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8))
/*
** CAPI3REF: Flags For File Open Operations
**
** These bit values are intended for use in the
-** 3rd parameter to the [sqlite3_open_v2()] interface and
-** in the 4th parameter to the [sqlite3_vfs.xOpen] method.
+** 3rd parameter to the [tdsqlite3_open_v2()] interface and
+** in the 4th parameter to the [tdsqlite3_vfs.xOpen] method.
*/
-#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */
+#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for tdsqlite3_open_v2() */
#define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */
#define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */
#define SQLITE_OPEN_AUTOPROXY 0x00000020 /* VFS only */
-#define SQLITE_OPEN_URI 0x00000040 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for sqlite3_open_v2() */
+#define SQLITE_OPEN_URI 0x00000040 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for tdsqlite3_open_v2() */
#define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */
#define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */
#define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */
@@ -801,21 +1608,22 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */
#define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */
#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */
-#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */
+#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for tdsqlite3_open_v2() */
#define SQLITE_OPEN_WAL 0x00080000 /* VFS only */
+#define SQLITE_OPEN_NOFOLLOW 0x01000000 /* Ok for tdsqlite3_open_v2() */
/* Reserved: 0x00F00000 */
/*
** CAPI3REF: Device Characteristics
**
-** The xDeviceCharacteristics method of the [sqlite3_io_methods]
+** The xDeviceCharacteristics method of the [tdsqlite3_io_methods]
** object returns an integer which is a vector of these
** bit values expressing I/O characteristics of the mass storage
-** device that holds the file that the [sqlite3_io_methods]
+** device that holds the file that the [tdsqlite3_io_methods]
** refers to.
**
** The SQLITE_IOCAP_ATOMIC property means that all writes of
@@ -832,10 +1640,15 @@ SQLITE_API int sqlite3_exec(
** file that were written at the application level might have changed
** and that adjacent bytes, even bytes within the same sector are
** guaranteed to be unchanged. The SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
-** flag indicate that a file cannot be deleted when open. The
+** flag indicates that a file cannot be deleted when open. The
** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on
** read-only media and cannot be changed even by processes with
** elevated privileges.
+**
+** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
+** filesystem supports doing multiple write operations atomically when those
+** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
+** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
*/
#define SQLITE_IOCAP_ATOMIC 0x00000001
#define SQLITE_IOCAP_ATOMIC512 0x00000002
@@ -851,13 +1664,14 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
#define SQLITE_IOCAP_IMMUTABLE 0x00002000
+#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
/*
** CAPI3REF: File Locking Levels
**
** SQLite uses one of these integer values as the second
** argument to calls it makes to the xLock() and xUnlock() methods
-** of an [sqlite3_io_methods] object.
+** of an [tdsqlite3_io_methods] object.
*/
#define SQLITE_LOCK_NONE 0
#define SQLITE_LOCK_SHARED 1
@@ -869,7 +1683,7 @@ SQLITE_API int sqlite3_exec(
** CAPI3REF: Synchronization Type Flags
**
** When SQLite invokes the xSync() method of an
-** [sqlite3_io_methods] object it uses a combination of
+** [tdsqlite3_io_methods] object it uses a combination of
** these integer values as the second argument.
**
** When the SQLITE_SYNC_DATAONLY flag is used, it means that the
@@ -898,33 +1712,33 @@ SQLITE_API int sqlite3_exec(
/*
** CAPI3REF: OS Interface Open File Handle
**
-** An [sqlite3_file] object represents an open file in the
-** [sqlite3_vfs | OS interface layer]. Individual OS interface
+** An [tdsqlite3_file] object represents an open file in the
+** [tdsqlite3_vfs | OS interface layer]. Individual OS interface
** implementations will
** want to subclass this object by appending additional fields
** for their own use. The pMethods entry is a pointer to an
-** [sqlite3_io_methods] object that defines methods for performing
+** [tdsqlite3_io_methods] object that defines methods for performing
** I/O operations on the open file.
*/
-typedef struct sqlite3_file sqlite3_file;
-struct sqlite3_file {
- const struct sqlite3_io_methods *pMethods; /* Methods for an open file */
+typedef struct tdsqlite3_file tdsqlite3_file;
+struct tdsqlite3_file {
+ const struct tdsqlite3_io_methods *pMethods; /* Methods for an open file */
};
/*
** CAPI3REF: OS Interface File Virtual Methods Object
**
-** Every file opened by the [sqlite3_vfs.xOpen] method populates an
-** [sqlite3_file] object (or, more commonly, a subclass of the
-** [sqlite3_file] object) with a pointer to an instance of this object.
+** Every file opened by the [tdsqlite3_vfs.xOpen] method populates an
+** [tdsqlite3_file] object (or, more commonly, a subclass of the
+** [tdsqlite3_file] object) with a pointer to an instance of this object.
** This object defines the methods used to perform various operations
-** against the open file represented by the [sqlite3_file] object.
+** against the open file represented by the [tdsqlite3_file] object.
**
-** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element
-** to a non-NULL pointer, then the sqlite3_io_methods.xClose method
-** may be invoked even if the [sqlite3_vfs.xOpen] reported that it failed. The
-** only way to prevent a call to xClose following a failed [sqlite3_vfs.xOpen]
-** is for the [sqlite3_vfs.xOpen] to set the sqlite3_file.pMethods element
+** If the [tdsqlite3_vfs.xOpen] method sets the tdsqlite3_file.pMethods element
+** to a non-NULL pointer, then the tdsqlite3_io_methods.xClose method
+** may be invoked even if the [tdsqlite3_vfs.xOpen] reported that it failed. The
+** only way to prevent a call to xClose following a failed [tdsqlite3_vfs.xOpen]
+** is for the [tdsqlite3_vfs.xOpen] to set the tdsqlite3_file.pMethods element
** to NULL.
**
** The flags argument to xSync may be one of [SQLITE_SYNC_NORMAL] or
@@ -949,7 +1763,7 @@ struct sqlite3_file {
**
** The xFileControl() method is a generic interface that allows custom
** VFS implementations to directly control an open file using the
-** [sqlite3_file_control()] interface. The second "op" argument is an
+** [tdsqlite3_file_control()] interface. The second "op" argument is an
** integer opcode. The third argument is a generic pointer intended to
** point to a structure that may contain arguments or space in which to
** write return values. Potential uses for xFileControl() might be
@@ -982,6 +1796,10 @@ struct sqlite3_file {
** <li> [SQLITE_IOCAP_ATOMIC64K]
** <li> [SQLITE_IOCAP_SAFE_APPEND]
** <li> [SQLITE_IOCAP_SEQUENTIAL]
+** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
+** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
+** <li> [SQLITE_IOCAP_IMMUTABLE]
+** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
** </ul>
**
** The SQLITE_IOCAP_ATOMIC property means that all writes of
@@ -1001,29 +1819,29 @@ struct sqlite3_file {
** failure to zero-fill short reads will eventually lead to
** database corruption.
*/
-typedef struct sqlite3_io_methods sqlite3_io_methods;
-struct sqlite3_io_methods {
+typedef struct tdsqlite3_io_methods tdsqlite3_io_methods;
+struct tdsqlite3_io_methods {
int iVersion;
- int (*xClose)(sqlite3_file*);
- int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
- int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
- int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
- int (*xSync)(sqlite3_file*, int flags);
- int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
- int (*xLock)(sqlite3_file*, int);
- int (*xUnlock)(sqlite3_file*, int);
- int (*xCheckReservedLock)(sqlite3_file*, int *pResOut);
- int (*xFileControl)(sqlite3_file*, int op, void *pArg);
- int (*xSectorSize)(sqlite3_file*);
- int (*xDeviceCharacteristics)(sqlite3_file*);
+ int (*xClose)(tdsqlite3_file*);
+ int (*xRead)(tdsqlite3_file*, void*, int iAmt, tdsqlite3_int64 iOfst);
+ int (*xWrite)(tdsqlite3_file*, const void*, int iAmt, tdsqlite3_int64 iOfst);
+ int (*xTruncate)(tdsqlite3_file*, tdsqlite3_int64 size);
+ int (*xSync)(tdsqlite3_file*, int flags);
+ int (*xFileSize)(tdsqlite3_file*, tdsqlite3_int64 *pSize);
+ int (*xLock)(tdsqlite3_file*, int);
+ int (*xUnlock)(tdsqlite3_file*, int);
+ int (*xCheckReservedLock)(tdsqlite3_file*, int *pResOut);
+ int (*xFileControl)(tdsqlite3_file*, int op, void *pArg);
+ int (*xSectorSize)(tdsqlite3_file*);
+ int (*xDeviceCharacteristics)(tdsqlite3_file*);
/* Methods above are valid for version 1 */
- int (*xShmMap)(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
- int (*xShmLock)(sqlite3_file*, int offset, int n, int flags);
- void (*xShmBarrier)(sqlite3_file*);
- int (*xShmUnmap)(sqlite3_file*, int deleteFlag);
+ int (*xShmMap)(tdsqlite3_file*, int iPg, int pgsz, int, void volatile**);
+ int (*xShmLock)(tdsqlite3_file*, int offset, int n, int flags);
+ void (*xShmBarrier)(tdsqlite3_file*);
+ int (*xShmUnmap)(tdsqlite3_file*, int deleteFlag);
/* Methods above are valid for version 2 */
- int (*xFetch)(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
- int (*xUnfetch)(sqlite3_file*, sqlite3_int64 iOfst, void *p);
+ int (*xFetch)(tdsqlite3_file*, tdsqlite3_int64 iOfst, int iAmt, void **pp);
+ int (*xUnfetch)(tdsqlite3_file*, tdsqlite3_int64 iOfst, void *p);
/* Methods above are valid for version 3 */
/* Additional methods may be added in future releases */
};
@@ -1033,7 +1851,7 @@ struct sqlite3_io_methods {
** KEYWORDS: {file control opcodes} {file control opcode}
**
** These integer constants are opcodes for the xFileControl method
-** of the [sqlite3_io_methods] object and for the [sqlite3_file_control()]
+** of the [tdsqlite3_io_methods] object and for the [tdsqlite3_file_control()]
** interface.
**
** <ul>
@@ -1054,10 +1872,19 @@ struct sqlite3_io_methods {
** file space based on this hint in order to help writes to the database
** file run faster.
**
+** <li>[[SQLITE_FCNTL_SIZE_LIMIT]]
+** The [SQLITE_FCNTL_SIZE_LIMIT] opcode is used by in-memory VFS that
+** implements [tdsqlite3_deserialize()] to set an upper bound on the size
+** of the in-memory database. The argument is a pointer to a [tdsqlite3_int64].
+** If the integer pointed to is negative, then it is filled in with the
+** current limit. Otherwise the limit is set to the larger of the value
+** of the integer pointed to and the current database size. The integer
+** pointed to is set to the new limit.
+**
** <li>[[SQLITE_FCNTL_CHUNK_SIZE]]
** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS
** extends and truncates the database file in chunks of a size specified
-** by the user. The fourth argument to [sqlite3_file_control()] should
+** by the user. The fourth argument to [tdsqlite3_file_control()] should
** point to an integer (type int) containing the new chunk-size to use
** for the nominated database. Allocating database file space in large
** chunks (say 1MB at a time), may reduce file-system fragmentation and
@@ -1065,12 +1892,12 @@ struct sqlite3_io_methods {
**
** <li>[[SQLITE_FCNTL_FILE_POINTER]]
** The [SQLITE_FCNTL_FILE_POINTER] opcode is used to obtain a pointer
-** to the [sqlite3_file] object associated with a particular database
+** to the [tdsqlite3_file] object associated with a particular database
** connection. See also [SQLITE_FCNTL_JOURNAL_POINTER].
**
** <li>[[SQLITE_FCNTL_JOURNAL_POINTER]]
** The [SQLITE_FCNTL_JOURNAL_POINTER] opcode is used to obtain a pointer
-** to the [sqlite3_file] object associated with the journal file (either
+** to the [tdsqlite3_file] object associated with the journal file (either
** the [rollback journal] or the [write-ahead log]) for a particular database
** connection. See also [SQLITE_FCNTL_FILE_POINTER].
**
@@ -1088,7 +1915,7 @@ struct sqlite3_io_methods {
** as part of a multi-database commit, the argument points to a nul-terminated
** string containing the transactions master-journal file name. VFSes that
** do not need this signal should silently ignore this opcode. Applications
-** should not call [sqlite3_file_control()] with this opcode as doing so may
+** should not call [tdsqlite3_file_control()] with this opcode as doing so may
** disrupt the operation of the specialized VFSes that do require it.
**
** <li>[[SQLITE_FCNTL_COMMIT_PHASETWO]]
@@ -1096,7 +1923,7 @@ struct sqlite3_io_methods {
** and sent to the VFS after a transaction has been committed immediately
** but before the database is unlocked. VFSes that do not need this signal
** should silently ignore this opcode. Applications should not call
-** [sqlite3_file_control()] with this opcode as doing so may disrupt the
+** [tdsqlite3_file_control()] with this opcode as doing so may disrupt the
** operation of the specialized VFSes that do require it.
**
** <li>[[SQLITE_FCNTL_WIN32_AV_RETRY]]
@@ -1110,7 +1937,7 @@ struct sqlite3_io_methods {
** opcode allows these two values (10 retries and 25 milliseconds of delay)
** to be adjusted. The values are changed for all database connections
** within the same process. The argument is a pointer to an array of two
-** integers where the first integer i the new retry count and the second
+** integers where the first integer is the new retry count and the second
** integer is the delay. If either integer is negative, then the setting
** is not changed but instead the prior value of that setting is written
** into the array entry, allowing the current retry settings to be
@@ -1119,14 +1946,15 @@ struct sqlite3_io_methods {
** <li>[[SQLITE_FCNTL_PERSIST_WAL]]
** ^The [SQLITE_FCNTL_PERSIST_WAL] opcode is used to set or query the
** persistent [WAL | Write Ahead Log] setting. By default, the auxiliary
-** write ahead log and shared memory files used for transaction control
+** write ahead log ([WAL file]) and shared memory
+** files used for transaction control
** are automatically deleted when the latest connection to the database
** closes. Setting persistent WAL mode causes those files to persist after
** close. Persisting the files is useful when other processes that do not
** have write permission on the directory containing the database file want
** to read the database file, as the WAL and shared memory files must exist
** in order for the database to be readable. The fourth parameter to
-** [sqlite3_file_control()] for this opcode should be a pointer to an integer.
+** [tdsqlite3_file_control()] for this opcode should be a pointer to an integer.
** That integer is 0 to disable persistent WAL mode or 1 to enable persistent
** WAL mode. If the integer is -1, then it is overwritten with the current
** WAL persistence setting.
@@ -1136,7 +1964,7 @@ struct sqlite3_io_methods {
** persistent "powersafe-overwrite" or "PSOW" setting. The PSOW setting
** determines the [SQLITE_IOCAP_POWERSAFE_OVERWRITE] bit of the
** xDeviceCharacteristics methods. The fourth parameter to
-** [sqlite3_file_control()] for this opcode should be a pointer to an integer.
+** [tdsqlite3_file_control()] for this opcode should be a pointer to an integer.
** That integer is 0 to disable zero-damage mode or 1 to enable zero-damage
** mode. If the integer is -1, then it is overwritten with the current
** zero-damage mode setting.
@@ -1151,8 +1979,8 @@ struct sqlite3_io_methods {
** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of
** all [VFSes] in the VFS stack. The names are of all VFS shims and the
** final bottom-level VFS are written into memory obtained from
-** [sqlite3_malloc()] and the result is stored in the char* variable
-** that the fourth parameter of [sqlite3_file_control()] points to.
+** [tdsqlite3_malloc()] and the result is stored in the char* variable
+** that the fourth parameter of [tdsqlite3_file_control()] points to.
** The caller is responsible for freeing the memory when done. As with
** all file-control actions, there is no guarantee that this will actually
** do anything. Callers should initialize the char* variable to a NULL
@@ -1162,22 +1990,22 @@ struct sqlite3_io_methods {
** <li>[[SQLITE_FCNTL_VFS_POINTER]]
** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level
** [VFSes] currently in use. ^(The argument X in
-** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be
-** of type "[sqlite3_vfs] **". This opcodes will set *X
+** tdsqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be
+** of type "[tdsqlite3_vfs] **". This opcodes will set *X
** to a pointer to the top-level VFS.)^
** ^When there are multiple VFS shims in the stack, this opcode finds the
** upper-most shim only.
**
** <li>[[SQLITE_FCNTL_PRAGMA]]
** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA]
-** file control is sent to the open [sqlite3_file] object corresponding
+** file control is sent to the open [tdsqlite3_file] object corresponding
** to the database file to which the pragma statement refers. ^The argument
** to the [SQLITE_FCNTL_PRAGMA] file control is an array of
** pointers to strings (char**) in which the second element of the array
** is the name of the pragma and the third element is the argument to the
** pragma or NULL if the pragma has no argument. ^The handler for an
** [SQLITE_FCNTL_PRAGMA] file control can optionally make the first element
-** of the char** argument point to a string obtained from [sqlite3_mprintf()]
+** of the char** argument point to a string obtained from [tdsqlite3_mprintf()]
** or the equivalent and that string will become the result of the pragma or
** the error message if the pragma fails. ^If the
** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal
@@ -1197,27 +2025,27 @@ struct sqlite3_io_methods {
** ^The [SQLITE_FCNTL_BUSYHANDLER]
** file-control may be invoked by SQLite on the database file handle
** shortly after it is opened in order to provide a custom VFS with access
-** to the connections busy-handler callback. The argument is of type (void **)
+** to the connection's busy-handler callback. The argument is of type (void**)
** - an array of two (void *) values. The first (void *) actually points
-** to a function of type (int (*)(void *)). In order to invoke the connections
+** to a function of type (int (*)(void *)). In order to invoke the connection's
** busy-handler, this function should be invoked with the second (void *) in
** the array as the only argument. If it returns non-zero, then the operation
** should be retried. If it returns zero, the custom VFS should abandon the
** current operation.
**
** <li>[[SQLITE_FCNTL_TEMPFILENAME]]
-** ^Application can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control
+** ^Applications can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control
** to have SQLite generate a
** temporary filename using the same algorithm that is followed to generate
** temporary filenames for TEMP tables and other internal uses. The
** argument should be a char** which will be filled with the filename
-** written into memory obtained from [sqlite3_malloc()]. The caller should
-** invoke [sqlite3_free()] on the result to avoid a memory leak.
+** written into memory obtained from [tdsqlite3_malloc()]. The caller should
+** invoke [tdsqlite3_free()] on the result to avoid a memory leak.
**
** <li>[[SQLITE_FCNTL_MMAP_SIZE]]
** The [SQLITE_FCNTL_MMAP_SIZE] file control is used to query or set the
** maximum number of bytes that will be used for memory-mapped I/O.
-** The argument is a pointer to a value of type sqlite3_int64 that
+** The argument is a pointer to a value of type tdsqlite3_int64 that
** is an advisory maximum number of bytes in the file to memory map. The
** pointer is overwritten with the old value. The limit is not changed if
** the value originally pointed to is negative, and so the current limit
@@ -1265,6 +2093,72 @@ struct sqlite3_io_methods {
** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by
** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for
** this opcode.
+**
+** <li>[[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]]
+** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then
+** the file descriptor is placed in "batch write mode", which
+** means all subsequent write operations will be deferred and done
+** atomically at the next [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. Systems
+** that do not support batch atomic writes will return SQLITE_NOTFOUND.
+** ^Following a successful SQLITE_FCNTL_BEGIN_ATOMIC_WRITE and prior to
+** the closing [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] or
+** [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE], SQLite will make
+** no VFS interface calls on the same [tdsqlite3_file] file descriptor
+** except for calls to the xWrite method and the xFileControl method
+** with [SQLITE_FCNTL_SIZE_HINT].
+**
+** <li>[[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]]
+** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write
+** operations since the previous successful call to
+** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically.
+** This file control returns [SQLITE_OK] if and only if the writes were
+** all performed successfully and have been committed to persistent storage.
+** ^Regardless of whether or not it is successful, this file control takes
+** the file descriptor out of batch write mode so that all subsequent
+** write operations are independent.
+** ^SQLite will never invoke SQLITE_FCNTL_COMMIT_ATOMIC_WRITE without
+** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE].
+**
+** <li>[[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]]
+** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write
+** operations since the previous successful call to
+** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back.
+** ^This file control takes the file descriptor out of batch write mode
+** so that all subsequent write operations are independent.
+** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without
+** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE].
+**
+** <li>[[SQLITE_FCNTL_LOCK_TIMEOUT]]
+** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode causes attempts to obtain
+** a file lock using the xLock or xShmLock methods of the VFS to wait
+** for up to M milliseconds before failing, where M is the single
+** unsigned integer parameter.
+**
+** <li>[[SQLITE_FCNTL_DATA_VERSION]]
+** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
+** a database file. The argument is a pointer to a 32-bit unsigned integer.
+** The "data version" for the pager is written into the pointer. The
+** "data version" changes whenever any change occurs to the corresponding
+** database file, either through SQL statements on the same database
+** connection or through transactions committed by separate database
+** connections possibly in other processes. The [tdsqlite3_total_changes()]
+** interface can be used to find if any database on the connection has changed,
+** but that interface responds to changes on TEMP as well as MAIN and does
+** not provide a mechanism to detect changes to MAIN only. Also, the
+** [tdsqlite3_total_changes()] interface responds to internal changes only and
+** omits changes made by other database connections. The
+** [PRAGMA data_version] command provides a mechanism to detect changes to
+** a single attached database that occur due to other database connections,
+** but omits changes implemented by the database connection on which it is
+** called. This file control is the only mechanism to detect changes that
+** happen either internally or externally and that are associated with
+** a particular attached database.
+**
+** <li>[[SQLITE_FCNTL_CKPT_DONE]]
+** The [SQLITE_FCNTL_CKPT_DONE] opcode is invoked from within a checkpoint
+** in wal mode after the client has finished copying pages from the wal
+** file to the database file, but before the *-shm file is updated to
+** record the fact that the pages have been checkpointed.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
@@ -1295,6 +2189,14 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_VFS_POINTER 27
#define SQLITE_FCNTL_JOURNAL_POINTER 28
#define SQLITE_FCNTL_WIN32_GET_HANDLE 29
+#define SQLITE_FCNTL_PDB 30
+#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31
+#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32
+#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33
+#define SQLITE_FCNTL_LOCK_TIMEOUT 34
+#define SQLITE_FCNTL_DATA_VERSION 35
+#define SQLITE_FCNTL_SIZE_LIMIT 36
+#define SQLITE_FCNTL_CKPT_DONE 37
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -1305,61 +2207,67 @@ struct sqlite3_io_methods {
/*
** CAPI3REF: Mutex Handle
**
-** The mutex module within SQLite defines [sqlite3_mutex] to be an
+** The mutex module within SQLite defines [tdsqlite3_mutex] to be an
** abstract type for a mutex object. The SQLite core never looks
-** at the internal representation of an [sqlite3_mutex]. It only
-** deals with pointers to the [sqlite3_mutex] object.
+** at the internal representation of an [tdsqlite3_mutex]. It only
+** deals with pointers to the [tdsqlite3_mutex] object.
**
-** Mutexes are created using [sqlite3_mutex_alloc()].
+** Mutexes are created using [tdsqlite3_mutex_alloc()].
*/
-typedef struct sqlite3_mutex sqlite3_mutex;
+typedef struct tdsqlite3_mutex tdsqlite3_mutex;
/*
** CAPI3REF: Loadable Extension Thunk
**
-** A pointer to the opaque sqlite3_api_routines structure is passed as
+** A pointer to the opaque tdsqlite3_api_routines structure is passed as
** the third parameter to entry points of [loadable extensions]. This
** structure must be typedefed in order to work around compiler warnings
** on some platforms.
*/
-typedef struct sqlite3_api_routines sqlite3_api_routines;
+typedef struct tdsqlite3_api_routines tdsqlite3_api_routines;
/*
** CAPI3REF: OS Interface Object
**
-** An instance of the sqlite3_vfs object defines the interface between
+** An instance of the tdsqlite3_vfs object defines the interface between
** the SQLite core and the underlying operating system. The "vfs"
** in the name of the object stands for "virtual file system". See
** the [VFS | VFS documentation] for further information.
**
-** The value of the iVersion field is initially 1 but may be larger in
-** future versions of SQLite. Additional fields may be appended to this
-** object when the iVersion value is increased. Note that the structure
-** of the sqlite3_vfs object changes in the transaction between
-** SQLite version 3.5.9 and 3.6.0 and yet the iVersion field was not
-** modified.
-**
-** The szOsFile field is the size of the subclassed [sqlite3_file]
+** The VFS interface is sometimes extended by adding new methods onto
+** the end. Each time such an extension occurs, the iVersion field
+** is incremented. The iVersion value started out as 1 in
+** SQLite [version 3.5.0] on [dateof:3.5.0], then increased to 2
+** with SQLite [version 3.7.0] on [dateof:3.7.0], and then increased
+** to 3 with SQLite [version 3.7.6] on [dateof:3.7.6]. Additional fields
+** may be appended to the tdsqlite3_vfs object and the iVersion value
+** may increase again in future versions of SQLite.
+** Note that due to an oversight, the structure
+** of the tdsqlite3_vfs object changed in the transition from
+** SQLite [version 3.5.9] to [version 3.6.0] on [dateof:3.6.0]
+** and yet the iVersion field was not increased.
+**
+** The szOsFile field is the size of the subclassed [tdsqlite3_file]
** structure used by this VFS. mxPathname is the maximum length of
** a pathname in this VFS.
**
-** Registered sqlite3_vfs objects are kept on a linked list formed by
-** the pNext pointer. The [sqlite3_vfs_register()]
-** and [sqlite3_vfs_unregister()] interfaces manage this list
-** in a thread-safe way. The [sqlite3_vfs_find()] interface
+** Registered tdsqlite3_vfs objects are kept on a linked list formed by
+** the pNext pointer. The [tdsqlite3_vfs_register()]
+** and [tdsqlite3_vfs_unregister()] interfaces manage this list
+** in a thread-safe way. The [tdsqlite3_vfs_find()] interface
** searches the list. Neither the application code nor the VFS
** implementation should use the pNext pointer.
**
-** The pNext field is the only field in the sqlite3_vfs
+** The pNext field is the only field in the tdsqlite3_vfs
** structure that SQLite will ever modify. SQLite will only access
** or modify this field while holding a particular static mutex.
-** The application should never modify anything within the sqlite3_vfs
+** The application should never modify anything within the tdsqlite3_vfs
** object once the object has been registered.
**
** The zName field holds the name of the VFS module. The name must
** be unique across all VFS modules.
**
-** [[sqlite3_vfs.xOpen]]
+** [[tdsqlite3_vfs.xOpen]]
** ^SQLite guarantees that the zFilename parameter to xOpen
** is either a NULL pointer or string obtained
** from xFullPathname() with an optional suffix added.
@@ -1369,7 +2277,7 @@ typedef struct sqlite3_api_routines sqlite3_api_routines;
** ^SQLite further guarantees that
** the string will be valid and unchanged until xClose() is
** called. Because of the previous sentence,
-** the [sqlite3_file] can safely store a pointer to the
+** the [tdsqlite3_file] can safely store a pointer to the
** filename if it needs to remember the filename for some reason.
** If the zFilename parameter to xOpen is a NULL pointer then xOpen
** must invent its own temporary name for the file. ^Whenever the
@@ -1377,8 +2285,8 @@ typedef struct sqlite3_api_routines sqlite3_api_routines;
** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE].
**
** The flags argument to xOpen() includes all bits set in
-** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()]
-** or [sqlite3_open16()] is used, then flags includes at least
+** the flags argument to [tdsqlite3_open_v2()]. Or if [tdsqlite3_open()]
+** or [tdsqlite3_open16()] is used, then flags includes at least
** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE].
** If xOpen() opens a file read-only then it sets *pOutFlags to
** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set.
@@ -1428,21 +2336,27 @@ typedef struct sqlite3_api_routines sqlite3_api_routines;
** for exclusive access.
**
** ^At least szOsFile bytes of memory are allocated by SQLite
-** to hold the [sqlite3_file] structure passed as the third
+** to hold the [tdsqlite3_file] structure passed as the third
** argument to xOpen. The xOpen method does not have to
** allocate the structure; it should just fill it in. Note that
-** the xOpen method must set the sqlite3_file.pMethods to either
-** a valid [sqlite3_io_methods] object or to NULL. xOpen must do
-** this even if the open fails. SQLite expects that the sqlite3_file.pMethods
+** the xOpen method must set the tdsqlite3_file.pMethods to either
+** a valid [tdsqlite3_io_methods] object or to NULL. xOpen must do
+** this even if the open fails. SQLite expects that the tdsqlite3_file.pMethods
** element will be valid after xOpen returns regardless of the success
** or failure of the xOpen call.
**
-** [[sqlite3_vfs.xAccess]]
+** [[tdsqlite3_vfs.xAccess]]
** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS]
** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to
** test whether a file is readable and writable, or [SQLITE_ACCESS_READ]
-** to test whether a file is at least readable. The file can be a
-** directory.
+** to test whether a file is at least readable. The SQLITE_ACCESS_READ
+** flag is never actually used and is not implemented in the built-in
+** VFSes of SQLite. The file is named by the second argument and can be a
+** directory. The xAccess method returns [SQLITE_OK] on success or some
+** non-zero error code if there is an I/O error or if the name of
+** the file given in the second argument is illegal. If SQLITE_OK
+** is returned, then non-zero or zero is written into *pResOut to indicate
+** whether or not the file is accessible.
**
** ^SQLite will always allocate at least mxPathname+1 bytes for the
** output buffer xFullPathname. The exact size of the output buffer
@@ -1481,40 +2395,40 @@ typedef struct sqlite3_api_routines sqlite3_api_routines;
** from one release to the next. Applications must not attempt to access
** any of these methods if the iVersion of the VFS is less than 3.
*/
-typedef struct sqlite3_vfs sqlite3_vfs;
-typedef void (*sqlite3_syscall_ptr)(void);
-struct sqlite3_vfs {
+typedef struct tdsqlite3_vfs tdsqlite3_vfs;
+typedef void (*tdsqlite3_syscall_ptr)(void);
+struct tdsqlite3_vfs {
int iVersion; /* Structure version number (currently 3) */
- int szOsFile; /* Size of subclassed sqlite3_file */
+ int szOsFile; /* Size of subclassed tdsqlite3_file */
int mxPathname; /* Maximum file pathname length */
- sqlite3_vfs *pNext; /* Next registered VFS */
+ tdsqlite3_vfs *pNext; /* Next registered VFS */
const char *zName; /* Name of this virtual file system */
void *pAppData; /* Pointer to application-specific data */
- int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
+ int (*xOpen)(tdsqlite3_vfs*, const char *zName, tdsqlite3_file*,
int flags, int *pOutFlags);
- int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
- int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
- int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
- void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
- void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
- void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void);
- void (*xDlClose)(sqlite3_vfs*, void*);
- int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
- int (*xSleep)(sqlite3_vfs*, int microseconds);
- int (*xCurrentTime)(sqlite3_vfs*, double*);
- int (*xGetLastError)(sqlite3_vfs*, int, char *);
+ int (*xDelete)(tdsqlite3_vfs*, const char *zName, int syncDir);
+ int (*xAccess)(tdsqlite3_vfs*, const char *zName, int flags, int *pResOut);
+ int (*xFullPathname)(tdsqlite3_vfs*, const char *zName, int nOut, char *zOut);
+ void *(*xDlOpen)(tdsqlite3_vfs*, const char *zFilename);
+ void (*xDlError)(tdsqlite3_vfs*, int nByte, char *zErrMsg);
+ void (*(*xDlSym)(tdsqlite3_vfs*,void*, const char *zSymbol))(void);
+ void (*xDlClose)(tdsqlite3_vfs*, void*);
+ int (*xRandomness)(tdsqlite3_vfs*, int nByte, char *zOut);
+ int (*xSleep)(tdsqlite3_vfs*, int microseconds);
+ int (*xCurrentTime)(tdsqlite3_vfs*, double*);
+ int (*xGetLastError)(tdsqlite3_vfs*, int, char *);
/*
** The methods above are in version 1 of the sqlite_vfs object
** definition. Those that follow are added in version 2 or later
*/
- int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
+ int (*xCurrentTimeInt64)(tdsqlite3_vfs*, tdsqlite3_int64*);
/*
** The methods above are in versions 1 and 2 of the sqlite_vfs object.
** Those below are for version 3 and greater.
*/
- int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr);
- sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName);
- const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName);
+ int (*xSetSystemCall)(tdsqlite3_vfs*, const char *zName, tdsqlite3_syscall_ptr);
+ tdsqlite3_syscall_ptr (*xGetSystemCall)(tdsqlite3_vfs*, const char *zName);
+ const char *(*xNextSystemCall)(tdsqlite3_vfs*, const char *zName);
/*
** The methods above are in versions 1 through 3 of the sqlite_vfs object.
** New fields may be appended in future versions. The iVersion
@@ -1526,7 +2440,7 @@ struct sqlite3_vfs {
** CAPI3REF: Flags for the xAccess VFS method
**
** These integer constants can be used as the third parameter to
-** the xAccess method of an [sqlite3_vfs] object. They determine
+** the xAccess method of an [tdsqlite3_vfs] object. They determine
** what kind of permissions the xAccess method is looking for.
** With SQLITE_ACCESS_EXISTS, the xAccess method
** simply checks whether the file exists.
@@ -1550,7 +2464,7 @@ struct sqlite3_vfs {
** CAPI3REF: Flags for the xShmLock VFS method
**
** These integer constants define the various locking operations
-** allowed by the xShmLock method of [sqlite3_io_methods]. The
+** allowed by the xShmLock method of [tdsqlite3_io_methods]. The
** following are the only legal combinations of flags to the
** xShmLock method:
**
@@ -1576,7 +2490,7 @@ struct sqlite3_vfs {
/*
** CAPI3REF: Maximum xShmLock index
**
-** The xShmLock method on [sqlite3_io_methods] may use values
+** The xShmLock method on [tdsqlite3_io_methods] may use values
** between 0 and this upper bound as its "offset" argument.
** The SQLite core will never attempt to acquire or release a
** lock outside of this range
@@ -1587,134 +2501,134 @@ struct sqlite3_vfs {
/*
** CAPI3REF: Initialize The SQLite Library
**
-** ^The sqlite3_initialize() routine initializes the
-** SQLite library. ^The sqlite3_shutdown() routine
-** deallocates any resources that were allocated by sqlite3_initialize().
+** ^The tdsqlite3_initialize() routine initializes the
+** SQLite library. ^The tdsqlite3_shutdown() routine
+** deallocates any resources that were allocated by tdsqlite3_initialize().
** These routines are designed to aid in process initialization and
** shutdown on embedded systems. Workstation applications using
** SQLite normally do not need to invoke either of these routines.
**
-** A call to sqlite3_initialize() is an "effective" call if it is
-** the first time sqlite3_initialize() is invoked during the lifetime of
-** the process, or if it is the first time sqlite3_initialize() is invoked
-** following a call to sqlite3_shutdown(). ^(Only an effective call
-** of sqlite3_initialize() does any initialization. All other calls
+** A call to tdsqlite3_initialize() is an "effective" call if it is
+** the first time tdsqlite3_initialize() is invoked during the lifetime of
+** the process, or if it is the first time tdsqlite3_initialize() is invoked
+** following a call to tdsqlite3_shutdown(). ^(Only an effective call
+** of tdsqlite3_initialize() does any initialization. All other calls
** are harmless no-ops.)^
**
-** A call to sqlite3_shutdown() is an "effective" call if it is the first
-** call to sqlite3_shutdown() since the last sqlite3_initialize(). ^(Only
-** an effective call to sqlite3_shutdown() does any deinitialization.
-** All other valid calls to sqlite3_shutdown() are harmless no-ops.)^
+** A call to tdsqlite3_shutdown() is an "effective" call if it is the first
+** call to tdsqlite3_shutdown() since the last tdsqlite3_initialize(). ^(Only
+** an effective call to tdsqlite3_shutdown() does any deinitialization.
+** All other valid calls to tdsqlite3_shutdown() are harmless no-ops.)^
**
-** The sqlite3_initialize() interface is threadsafe, but sqlite3_shutdown()
-** is not. The sqlite3_shutdown() interface must only be called from a
+** The tdsqlite3_initialize() interface is threadsafe, but tdsqlite3_shutdown()
+** is not. The tdsqlite3_shutdown() interface must only be called from a
** single thread. All open [database connections] must be closed and all
** other SQLite resources must be deallocated prior to invoking
-** sqlite3_shutdown().
+** tdsqlite3_shutdown().
**
-** Among other things, ^sqlite3_initialize() will invoke
-** sqlite3_os_init(). Similarly, ^sqlite3_shutdown()
-** will invoke sqlite3_os_end().
+** Among other things, ^tdsqlite3_initialize() will invoke
+** tdsqlite3_os_init(). Similarly, ^tdsqlite3_shutdown()
+** will invoke tdsqlite3_os_end().
**
-** ^The sqlite3_initialize() routine returns [SQLITE_OK] on success.
-** ^If for some reason, sqlite3_initialize() is unable to initialize
+** ^The tdsqlite3_initialize() routine returns [SQLITE_OK] on success.
+** ^If for some reason, tdsqlite3_initialize() is unable to initialize
** the library (perhaps it is unable to allocate a needed resource such
** as a mutex) it returns an [error code] other than [SQLITE_OK].
**
-** ^The sqlite3_initialize() routine is called internally by many other
+** ^The tdsqlite3_initialize() routine is called internally by many other
** SQLite interfaces so that an application usually does not need to
-** invoke sqlite3_initialize() directly. For example, [sqlite3_open()]
-** calls sqlite3_initialize() so the SQLite library will be automatically
-** initialized when [sqlite3_open()] is called if it has not be initialized
+** invoke tdsqlite3_initialize() directly. For example, [tdsqlite3_open()]
+** calls tdsqlite3_initialize() so the SQLite library will be automatically
+** initialized when [tdsqlite3_open()] is called if it has not be initialized
** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT]
-** compile-time option, then the automatic calls to sqlite3_initialize()
-** are omitted and the application must call sqlite3_initialize() directly
+** compile-time option, then the automatic calls to tdsqlite3_initialize()
+** are omitted and the application must call tdsqlite3_initialize() directly
** prior to using any other SQLite interface. For maximum portability,
-** it is recommended that applications always invoke sqlite3_initialize()
+** it is recommended that applications always invoke tdsqlite3_initialize()
** directly prior to using any other SQLite interface. Future releases
** of SQLite may require this. In other words, the behavior exhibited
** when SQLite is compiled with [SQLITE_OMIT_AUTOINIT] might become the
** default behavior in some future release of SQLite.
**
-** The sqlite3_os_init() routine does operating-system specific
-** initialization of the SQLite library. The sqlite3_os_end()
-** routine undoes the effect of sqlite3_os_init(). Typical tasks
+** The tdsqlite3_os_init() routine does operating-system specific
+** initialization of the SQLite library. The tdsqlite3_os_end()
+** routine undoes the effect of tdsqlite3_os_init(). Typical tasks
** performed by these routines include allocation or deallocation
** of static resources, initialization of global variables,
-** setting up a default [sqlite3_vfs] module, or setting up
-** a default configuration using [sqlite3_config()].
-**
-** The application should never invoke either sqlite3_os_init()
-** or sqlite3_os_end() directly. The application should only invoke
-** sqlite3_initialize() and sqlite3_shutdown(). The sqlite3_os_init()
-** interface is called automatically by sqlite3_initialize() and
-** sqlite3_os_end() is called by sqlite3_shutdown(). Appropriate
-** implementations for sqlite3_os_init() and sqlite3_os_end()
+** setting up a default [tdsqlite3_vfs] module, or setting up
+** a default configuration using [tdsqlite3_config()].
+**
+** The application should never invoke either tdsqlite3_os_init()
+** or tdsqlite3_os_end() directly. The application should only invoke
+** tdsqlite3_initialize() and tdsqlite3_shutdown(). The tdsqlite3_os_init()
+** interface is called automatically by tdsqlite3_initialize() and
+** tdsqlite3_os_end() is called by tdsqlite3_shutdown(). Appropriate
+** implementations for tdsqlite3_os_init() and tdsqlite3_os_end()
** are built into SQLite when it is compiled for Unix, Windows, or OS/2.
** When [custom builds | built for other platforms]
** (using the [SQLITE_OS_OTHER=1] compile-time
** option) the application must supply a suitable implementation for
-** sqlite3_os_init() and sqlite3_os_end(). An application-supplied
-** implementation of sqlite3_os_init() or sqlite3_os_end()
+** tdsqlite3_os_init() and tdsqlite3_os_end(). An application-supplied
+** implementation of tdsqlite3_os_init() or tdsqlite3_os_end()
** must return [SQLITE_OK] on success and some other [error code] upon
** failure.
*/
-SQLITE_API int sqlite3_initialize(void);
-SQLITE_API int sqlite3_shutdown(void);
-SQLITE_API int sqlite3_os_init(void);
-SQLITE_API int sqlite3_os_end(void);
+SQLITE_API int tdsqlite3_initialize(void);
+SQLITE_API int tdsqlite3_shutdown(void);
+SQLITE_API int tdsqlite3_os_init(void);
+SQLITE_API int tdsqlite3_os_end(void);
/*
** CAPI3REF: Configuring The SQLite Library
**
-** The sqlite3_config() interface is used to make global configuration
+** The tdsqlite3_config() interface is used to make global configuration
** changes to SQLite in order to tune SQLite to the specific needs of
** the application. The default configuration is recommended for most
** applications and so this routine is usually not necessary. It is
** provided to support rare applications with unusual needs.
**
-** <b>The sqlite3_config() interface is not threadsafe. The application
+** <b>The tdsqlite3_config() interface is not threadsafe. The application
** must ensure that no other SQLite interfaces are invoked by other
-** threads while sqlite3_config() is running.</b>
+** threads while tdsqlite3_config() is running.</b>
**
-** The sqlite3_config() interface
+** The tdsqlite3_config() interface
** may only be invoked prior to library initialization using
-** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()].
-** ^If sqlite3_config() is called after [sqlite3_initialize()] and before
-** [sqlite3_shutdown()] then it will return SQLITE_MISUSE.
-** Note, however, that ^sqlite3_config() can be called as part of the
-** implementation of an application-defined [sqlite3_os_init()].
+** [tdsqlite3_initialize()] or after shutdown by [tdsqlite3_shutdown()].
+** ^If tdsqlite3_config() is called after [tdsqlite3_initialize()] and before
+** [tdsqlite3_shutdown()] then it will return SQLITE_MISUSE.
+** Note, however, that ^tdsqlite3_config() can be called as part of the
+** implementation of an application-defined [tdsqlite3_os_init()].
**
-** The first argument to sqlite3_config() is an integer
+** The first argument to tdsqlite3_config() is an integer
** [configuration option] that determines
** what property of SQLite is to be configured. Subsequent arguments
** vary depending on the [configuration option]
** in the first argument.
**
-** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK].
+** ^When a configuration option is set, tdsqlite3_config() returns [SQLITE_OK].
** ^If the option is unknown or SQLite is unable to set the option
** then this routine returns a non-zero [error code].
*/
-SQLITE_API int sqlite3_config(int, ...);
+SQLITE_API int tdsqlite3_config(int, ...);
/*
** CAPI3REF: Configure database connections
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** The sqlite3_db_config() interface is used to make configuration
+** The tdsqlite3_db_config() interface is used to make configuration
** changes to a [database connection]. The interface is similar to
-** [sqlite3_config()] except that the changes apply to a single
+** [tdsqlite3_config()] except that the changes apply to a single
** [database connection] (specified in the first argument).
**
-** The second argument to sqlite3_db_config(D,V,...) is the
+** The second argument to tdsqlite3_db_config(D,V,...) is the
** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code
** that indicates what aspect of the [database connection] is being configured.
** Subsequent arguments vary depending on the configuration verb.
**
-** ^Calls to sqlite3_db_config() return SQLITE_OK if and only if
+** ^Calls to tdsqlite3_db_config() return SQLITE_OK if and only if
** the call is considered successful.
*/
-SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...);
+SQLITE_API int tdsqlite3_db_config(tdsqlite3*, int op, ...);
/*
** CAPI3REF: Memory Allocation Routines
@@ -1724,10 +2638,10 @@ SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...);
**
** This object is used in only one place in the SQLite interface.
** A pointer to an instance of this object is the argument to
-** [sqlite3_config()] when the configuration option is
+** [tdsqlite3_config()] when the configuration option is
** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC].
** By creating an instance of this object
-** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC])
+** and passing it to [tdsqlite3_config]([SQLITE_CONFIG_MALLOC])
** during configuration, an application can specify an alternative
** memory allocation subsystem for SQLite to use for all of its
** dynamic memory needs.
@@ -1754,20 +2668,20 @@ SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...);
** a memory allocation given a particular requested size. Most memory
** allocators round up memory allocations at least to the next multiple
** of 8. Some allocators round up to a larger multiple or to a power of 2.
-** Every memory allocation request coming in through [sqlite3_malloc()]
-** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0,
+** Every memory allocation request coming in through [tdsqlite3_malloc()]
+** or [tdsqlite3_realloc()] first calls xRoundup. If xRoundup returns 0,
** that causes the corresponding memory allocation to fail.
**
** The xInit method initializes the memory allocator. For example,
-** it might allocate any require mutexes or initialize internal data
+** it might allocate any required mutexes or initialize internal data
** structures. The xShutdown method is invoked (indirectly) by
-** [sqlite3_shutdown()] and should deallocate any resources acquired
+** [tdsqlite3_shutdown()] and should deallocate any resources acquired
** by xInit. The pAppData pointer is used as the only parameter to
** xInit and xShutdown.
**
** SQLite holds the [SQLITE_MUTEX_STATIC_MASTER] mutex when it invokes
** the xInit method, so the xInit method need not be threadsafe. The
-** xShutdown method is only called from [sqlite3_shutdown()] so it does
+** xShutdown method is only called from [tdsqlite3_shutdown()] so it does
** not need to be threadsafe either. For all other methods, SQLite
** holds the [SQLITE_MUTEX_STATIC_MEM] mutex as long as the
** [SQLITE_CONFIG_MEMSTATUS] configuration option is turned on (which
@@ -1779,8 +2693,8 @@ SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...);
** SQLite will never invoke xInit() more than once without an intervening
** call to xShutdown().
*/
-typedef struct sqlite3_mem_methods sqlite3_mem_methods;
-struct sqlite3_mem_methods {
+typedef struct tdsqlite3_mem_methods tdsqlite3_mem_methods;
+struct tdsqlite3_mem_methods {
void *(*xMalloc)(int); /* Memory allocation function */
void (*xFree)(void*); /* Free a prior allocation */
void *(*xRealloc)(void*,int); /* Resize an allocation */
@@ -1796,12 +2710,12 @@ struct sqlite3_mem_methods {
** KEYWORDS: {configuration option}
**
** These constants are the available integer configuration options that
-** can be passed as the first argument to the [sqlite3_config()] interface.
+** can be passed as the first argument to the [tdsqlite3_config()] interface.
**
** New configuration options may be added in future releases of SQLite.
** Existing configuration options might be discontinued. Applications
-** should check the return code from [sqlite3_config()] to make sure that
-** the call worked. The [sqlite3_config()] interface will return a
+** should check the return code from [tdsqlite3_config()] to make sure that
+** the call worked. The [tdsqlite3_config()] interface will return a
** non-zero [error code] if a discontinued or unsupported configuration option
** is invoked.
**
@@ -1813,7 +2727,7 @@ struct sqlite3_mem_methods {
** by a single thread. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** it is not possible to change the [threading mode] from its default
-** value of Single-thread and so [sqlite3_config()] will return
+** value of Single-thread and so [tdsqlite3_config()] will return
** [SQLITE_ERROR] if called with the SQLITE_CONFIG_SINGLETHREAD
** configuration option.</dd>
**
@@ -1828,7 +2742,7 @@ struct sqlite3_mem_methods {
** [database connection] at the same time. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** it is not possible to set the Multi-thread [threading mode] and
-** [sqlite3_config()] will return [SQLITE_ERROR] if called with the
+** [tdsqlite3_config()] will return [SQLITE_ERROR] if called with the
** SQLITE_CONFIG_MULTITHREAD configuration option.</dd>
**
** [[SQLITE_CONFIG_SERIALIZED]] <dt>SQLITE_CONFIG_SERIALIZED</dt>
@@ -1844,37 +2758,48 @@ struct sqlite3_mem_methods {
** ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** it is not possible to set the Serialized [threading mode] and
-** [sqlite3_config()] will return [SQLITE_ERROR] if called with the
+** [tdsqlite3_config()] will return [SQLITE_ERROR] if called with the
** SQLITE_CONFIG_SERIALIZED configuration option.</dd>
**
** [[SQLITE_CONFIG_MALLOC]] <dt>SQLITE_CONFIG_MALLOC</dt>
** <dd> ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is
-** a pointer to an instance of the [sqlite3_mem_methods] structure.
+** a pointer to an instance of the [tdsqlite3_mem_methods] structure.
** The argument specifies
** alternative low-level memory allocation routines to be used in place of
** the memory allocation routines built into SQLite.)^ ^SQLite makes
-** its own private copy of the content of the [sqlite3_mem_methods] structure
-** before the [sqlite3_config()] call returns.</dd>
+** its own private copy of the content of the [tdsqlite3_mem_methods] structure
+** before the [tdsqlite3_config()] call returns.</dd>
**
** [[SQLITE_CONFIG_GETMALLOC]] <dt>SQLITE_CONFIG_GETMALLOC</dt>
** <dd> ^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which
-** is a pointer to an instance of the [sqlite3_mem_methods] structure.
-** The [sqlite3_mem_methods]
+** is a pointer to an instance of the [tdsqlite3_mem_methods] structure.
+** The [tdsqlite3_mem_methods]
** structure is filled with the currently defined memory allocation routines.)^
** This option can be used to overload the default memory allocation
** routines with a wrapper that simulations memory allocation failure or
** tracks memory usage, for example. </dd>
**
+** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt>
+** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of
+** type int, interpreted as a boolean, which if true provides a hint to
+** SQLite that it should avoid large memory allocations if possible.
+** SQLite will run faster if it is free to make large memory allocations,
+** but some application might prefer to run slower in exchange for
+** guarantees about memory fragmentation that are possible if large
+** allocations are avoided. This hint is normally off.
+** </dd>
+**
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
** interpreted as a boolean, which enables or disables the collection of
** memory allocation statistics. ^(When memory allocation statistics are
** disabled, the following SQLite interfaces become non-operational:
** <ul>
-** <li> [sqlite3_memory_used()]
-** <li> [sqlite3_memory_highwater()]
-** <li> [sqlite3_soft_heap_limit64()]
-** <li> [sqlite3_status64()]
+** <li> [tdsqlite3_hard_heap_limit64()]
+** <li> [tdsqlite3_memory_used()]
+** <li> [tdsqlite3_memory_highwater()]
+** <li> [tdsqlite3_soft_heap_limit64()]
+** <li> [tdsqlite3_status64()]
** </ul>)^
** ^Memory allocation statistics are enabled by default unless SQLite is
** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory
@@ -1882,32 +2807,14 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt>
-** <dd> ^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer
-** that SQLite can use for scratch memory. ^(There are three arguments
-** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte
-** aligned memory buffer from which the scratch allocations will be
-** drawn, the size of each scratch allocation (sz),
-** and the maximum number of scratch allocations (N).)^
-** The first argument must be a pointer to an 8-byte aligned buffer
-** of at least sz*N bytes of memory.
-** ^SQLite will not use more than one scratch buffers per thread.
-** ^SQLite will never request a scratch buffer that is more than 6
-** times the database page size.
-** ^If SQLite needs needs additional
-** scratch memory beyond what is provided by this configuration option, then
-** [sqlite3_malloc()] will be used to obtain the memory needed.<p>
-** ^When the application provides any amount of scratch memory using
-** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large
-** [sqlite3_malloc|heap allocations].
-** This can help [Robson proof|prevent memory allocation failures] due to heap
-** fragmentation in low-memory embedded systems.
+** <dd> The SQLITE_CONFIG_SCRATCH option is no longer used.
** </dd>
**
** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt>
** <dd> ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool
** that SQLite can use for the database page cache with the default page
** cache implementation.
-** This configuration option is a no-op if an application-define page
+** This configuration option is a no-op if an application-defined page
** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2].
** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to
** 8-byte aligned memory (pMem), the size of each page cache line (sz),
@@ -1922,22 +2829,21 @@ struct sqlite3_mem_methods {
** aligned block of memory of at least sz*N bytes, otherwise
** subsequent behavior is undefined.
** ^When pMem is not NULL, SQLite will strive to use the memory provided
-** to satisfy page cache needs, falling back to [sqlite3_malloc()] if
+** to satisfy page cache needs, falling back to [tdsqlite3_malloc()] if
** a page cache line is larger than sz bytes or if all of the pMem buffer
** is exhausted.
** ^If pMem is NULL and N is non-zero, then each database connection
** does an initial bulk allocation for page cache memory
-** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or
+** from [tdsqlite3_malloc()] sufficient for N cache lines if N is positive or
** of -1024*N bytes if N is negative, . ^If additional
** page cache memory is needed beyond what is provided by the initial
-** allocation, then SQLite goes to [sqlite3_malloc()] separately for each
+** allocation, then SQLite goes to [tdsqlite3_malloc()] separately for each
** additional cache line. </dd>
**
** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt>
** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer
** that SQLite will use for all of its dynamic memory allocation needs
-** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and
-** [SQLITE_CONFIG_PAGECACHE].
+** beyond those provided for by [SQLITE_CONFIG_PAGECACHE].
** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled
** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns
** [SQLITE_ERROR] if invoked otherwise.
@@ -1956,27 +2862,27 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt>
** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a
-** pointer to an instance of the [sqlite3_mutex_methods] structure.
+** pointer to an instance of the [tdsqlite3_mutex_methods] structure.
** The argument specifies alternative low-level mutex routines to be used
** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of
-** the content of the [sqlite3_mutex_methods] structure before the call to
-** [sqlite3_config()] returns. ^If SQLite is compiled with
+** the content of the [tdsqlite3_mutex_methods] structure before the call to
+** [tdsqlite3_config()] returns. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** the entire mutexing subsystem is omitted from the build and hence calls to
-** [sqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will
+** [tdsqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will
** return [SQLITE_ERROR].</dd>
**
** [[SQLITE_CONFIG_GETMUTEX]] <dt>SQLITE_CONFIG_GETMUTEX</dt>
** <dd> ^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which
-** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The
-** [sqlite3_mutex_methods]
+** is a pointer to an instance of the [tdsqlite3_mutex_methods] structure. The
+** [tdsqlite3_mutex_methods]
** structure is filled with the currently defined mutex routines.)^
** This option can be used to overload the default mutex allocation
** routines with a wrapper used to track mutex usage for performance
** profiling or testing, for example. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** the entire mutexing subsystem is omitted from the build and hence calls to
-** [sqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will
+** [tdsqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will
** return [SQLITE_ERROR].</dd>
**
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
@@ -1986,18 +2892,18 @@ struct sqlite3_mem_methods {
** size of each lookaside buffer slot and the second is the number of
** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE
** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
-** option to [sqlite3_db_config()] can be used to change the lookaside
+** option to [tdsqlite3_db_config()] can be used to change the lookaside
** configuration on individual connections.)^ </dd>
**
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
-** a pointer to an [sqlite3_pcache_methods2] object. This object specifies
+** a pointer to an [tdsqlite3_pcache_methods2] object. This object specifies
** the interface to a custom page cache implementation.)^
-** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.</dd>
+** ^SQLite makes a copy of the [tdsqlite3_pcache_methods2] object.</dd>
**
** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which
-** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of
+** is a pointer to an [tdsqlite3_pcache_methods2] object. SQLite copies of
** the current page cache implementation into that object.)^ </dd>
**
** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt>
@@ -2006,15 +2912,15 @@ struct sqlite3_mem_methods {
** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a
** function with a call signature of void(*)(void*,int,const char*),
** and a pointer to void. ^If the function pointer is not NULL, it is
-** invoked by [sqlite3_log()] to process each logging event. ^If the
-** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op.
+** invoked by [tdsqlite3_log()] to process each logging event. ^If the
+** function pointer is NULL, the [tdsqlite3_log()] interface becomes a no-op.
** ^The void pointer that is the second argument to SQLITE_CONFIG_LOG is
** passed through as the first parameter to the application-defined logger
** function whenever that function is invoked. ^The second parameter to
** the logger function is a copy of the first parameter to the corresponding
-** [sqlite3_log()] call and is intended to be a [result code] or an
+** [tdsqlite3_log()] call and is intended to be a [result code] or an
** [extended result code]. ^The third parameter passed to the logger is
-** log message after formatting via [sqlite3_snprintf()].
+** log message after formatting via [tdsqlite3_snprintf()].
** The SQLite logging interface is not reentrant; the logger function
** supplied by the application must not invoke any SQLite interface.
** In a multi-threaded application, the application-defined logger
@@ -2024,8 +2930,8 @@ struct sqlite3_mem_methods {
** <dd>^(The SQLITE_CONFIG_URI option takes a single argument of type int.
** If non-zero, then URI handling is globally enabled. If the parameter is zero,
** then URI handling is globally disabled.)^ ^If URI handling is globally
-** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()],
-** [sqlite3_open16()] or
+** enabled, all filenames passed to [tdsqlite3_open()], [tdsqlite3_open_v2()],
+** [tdsqlite3_open16()] or
** specified as part of [ATTACH] commands are interpreted as URIs, regardless
** of whether or not the [SQLITE_OPEN_URI] flag is set when the database
** connection is opened. ^If it is globally disabled, filenames are
@@ -2057,7 +2963,7 @@ struct sqlite3_mem_methods {
** <dt>SQLITE_CONFIG_SQLLOG
** <dd>This option is only available if sqlite is compiled with the
** [SQLITE_ENABLE_SQLLOG] pre-processor macro defined. The first argument should
-** be a pointer to a function of type void(*)(void*,sqlite3*,const char*, int).
+** be a pointer to a function of type void(*)(void*,tdsqlite3*,const char*, int).
** The second should be of type (void*). The callback is invoked by the library
** in three separate circumstances, identified by the value passed as the
** fourth parameter. If the fourth parameter is 0, then the database connection
@@ -2072,7 +2978,7 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_CONFIG_MMAP_SIZE]]
** <dt>SQLITE_CONFIG_MMAP_SIZE
-** <dd>^SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values
+** <dd>^SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (tdsqlite3_int64) values
** that are the default mmap size limit (the default setting for
** [PRAGMA mmap_size]) and the maximum allowed mmap size limit.
** ^The default setting can be overridden by each database connection using
@@ -2123,57 +3029,88 @@ struct sqlite3_mem_methods {
** I/O required to support statement rollback.
** The default value for this setting is controlled by the
** [SQLITE_STMTJRNL_SPILL] compile-time option.
+**
+** [[SQLITE_CONFIG_SORTERREF_SIZE]]
+** <dt>SQLITE_CONFIG_SORTERREF_SIZE
+** <dd>The SQLITE_CONFIG_SORTERREF_SIZE option accepts a single parameter
+** of type (int) - the new value of the sorter-reference size threshold.
+** Usually, when SQLite uses an external sort to order records according
+** to an ORDER BY clause, all fields required by the caller are present in the
+** sorted records. However, if SQLite determines based on the declared type
+** of a table column that its values are likely to be very large - larger
+** than the configured sorter-reference size threshold - then a reference
+** is stored in each sorted record and the required column values loaded
+** from the database as records are returned in sorted order. The default
+** value for this option is to never use this optimization. Specifying a
+** negative value for this option restores the default behaviour.
+** This option is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option.
+**
+** [[SQLITE_CONFIG_MEMDB_MAXSIZE]]
+** <dt>SQLITE_CONFIG_MEMDB_MAXSIZE
+** <dd>The SQLITE_CONFIG_MEMDB_MAXSIZE option accepts a single parameter
+** [tdsqlite3_int64] parameter which is the default maximum size for an in-memory
+** database created using [tdsqlite3_deserialize()]. This default maximum
+** size can be adjusted up or down for individual databases using the
+** [SQLITE_FCNTL_SIZE_LIMIT] [tdsqlite3_file_control|file-control]. If this
+** configuration setting is never used, then the default maximum is determined
+** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that
+** compile-time option is not set, then the default maximum is 1073741824.
** </dl>
*/
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
-#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
-#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
-#define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */
+#define SQLITE_CONFIG_MALLOC 4 /* tdsqlite3_mem_methods* */
+#define SQLITE_CONFIG_GETMALLOC 5 /* tdsqlite3_mem_methods* */
+#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
-#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */
-#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
+#define SQLITE_CONFIG_MUTEX 10 /* tdsqlite3_mutex_methods* */
+#define SQLITE_CONFIG_GETMUTEX 11 /* tdsqlite3_mutex_methods* */
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
#define SQLITE_CONFIG_PCACHE 14 /* no-op */
#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */
#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */
#define SQLITE_CONFIG_URI 17 /* int */
-#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */
-#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */
+#define SQLITE_CONFIG_PCACHE2 18 /* tdsqlite3_pcache_methods2* */
+#define SQLITE_CONFIG_GETPCACHE2 19 /* tdsqlite3_pcache_methods2* */
#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
-#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
+#define SQLITE_CONFIG_MMAP_SIZE 22 /* tdsqlite3_int64, tdsqlite3_int64 */
#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
+#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
+#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */
+#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* tdsqlite3_int64 */
/*
** CAPI3REF: Database Connection Configuration Options
**
** These constants are the available integer configuration options that
-** can be passed as the second argument to the [sqlite3_db_config()] interface.
+** can be passed as the second argument to the [tdsqlite3_db_config()] interface.
**
** New configuration options may be added in future releases of SQLite.
** Existing configuration options might be discontinued. Applications
-** should check the return code from [sqlite3_db_config()] to make sure that
-** the call worked. ^The [sqlite3_db_config()] interface will return a
+** should check the return code from [tdsqlite3_db_config()] to make sure that
+** the call worked. ^The [tdsqlite3_db_config()] interface will return a
** non-zero [error code] if a discontinued or unsupported configuration option
** is invoked.
**
** <dl>
+** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> ^This option takes three additional arguments that determine the
** [lookaside memory allocator] configuration for the [database connection].
-** ^The first argument (the third parameter to [sqlite3_db_config()] is a
+** ^The first argument (the third parameter to [tdsqlite3_db_config()] is a
** pointer to a memory buffer to use for lookaside memory.
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
** may be NULL in which case SQLite will allocate the
-** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the
+** lookaside buffer itself using [tdsqlite3_malloc()]. ^The second argument is the
** size of each lookaside buffer slot. ^The third argument is the number of
** slots. The size of the buffer in the first argument must be greater than
** or equal to the product of the second and third arguments. The buffer
@@ -2183,11 +3120,12 @@ struct sqlite3_mem_methods {
** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words
** when the "current value" returned by
-** [sqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero.
+** [tdsqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero.
** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].)^</dd>
**
+** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
** <dd> ^This option is used to enable or disable the enforcement of
** [foreign key constraints]. There should be two additional arguments.
@@ -2198,6 +3136,7 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in
** which case the FK enforcement setting is not reported back. </dd>
**
+** [[SQLITE_DBCONFIG_ENABLE_TRIGGER]]
** <dt>SQLITE_DBCONFIG_ENABLE_TRIGGER</dt>
** <dd> ^This option is used to enable or disable [CREATE TRIGGER | triggers].
** There should be two additional arguments.
@@ -2208,9 +3147,21 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in
** which case the trigger setting is not reported back. </dd>
**
+** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
+** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
+** <dd> ^This option is used to enable or disable [CREATE VIEW | views].
+** There should be two additional arguments.
+** The first argument is an integer which is 0 to disable views,
+** positive to enable views or negative to leave the setting unchanged.
+** The second parameter is a pointer to an integer into which
+** is written 0 or 1 to indicate whether views are disabled or enabled
+** following this call. The second parameter may be a NULL pointer, in
+** which case the view setting is not reported back. </dd>
+**
+** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
-** <dd> ^This option is used to enable or disable the two-argument
-** version of the [fts3_tokenizer()] function which is part of the
+** <dd> ^This option is used to enable or disable the
+** [fts3_tokenizer()] function which is part of the
** [FTS3] full-text search engine extension.
** There should be two additional arguments.
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
@@ -2221,11 +3172,12 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in
** which case the new setting is not reported back. </dd>
**
+** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]]
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
-** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()]
+** <dd> ^This option is used to enable or disable the [tdsqlite3_load_extension()]
** interface independently of the [load_extension()] SQL function.
-** The [sqlite3_enable_load_extension()] API enables or disables both the
-** C-API [sqlite3_load_extension()] and the SQL function [load_extension()].
+** The [tdsqlite3_enable_load_extension()] API enables or disables both the
+** C-API [tdsqlite3_load_extension()] and the SQL function [load_extension()].
** There should be two additional arguments.
** When the first argument to this interface is 1, then only the C-API is
** enabled and the SQL function remains disabled. If the first argument to
@@ -2233,12 +3185,12 @@ struct sqlite3_mem_methods {
** If the first argument is -1, then no changes are made to state of either the
** C-API or the SQL function.
** The second parameter is a pointer to an integer into which
-** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface
+** is written 0 or 1 to indicate whether [tdsqlite3_load_extension()] interface
** is disabled or enabled following this call. The second parameter may
** be a NULL pointer, in which case the new setting is not reported back.
** </dd>
**
-** <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
+** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
** <dd> ^This option is used to change the name of the "main" database
** schema. ^The sole argument is a pointer to a constant UTF8 string
** which will become the new schema name in place of "main". ^SQLite
@@ -2247,6 +3199,163 @@ struct sqlite3_mem_methods {
** until after the database connection closes.
** </dd>
**
+** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]]
+** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt>
+** <dd> Usually, when a database in wal mode is closed or detached from a
+** database handle, SQLite checks if this will mean that there are now no
+** connections at all to the database. If so, it performs a checkpoint
+** operation before closing the connection. This option may be used to
+** override this behaviour. The first parameter passed to this operation
+** is an integer - positive to disable checkpoints-on-close, or zero (the
+** default) to enable them, and negative to leave the setting unchanged.
+** The second parameter is a pointer to an integer
+** into which is written 0 or 1 to indicate whether checkpoints-on-close
+** have been disabled - 0 if they are not disabled, 1 if they are.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_ENABLE_QPSG]] <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt>
+** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates
+** the [query planner stability guarantee] (QPSG). When the QPSG is active,
+** a single SQL query statement will always use the same algorithm regardless
+** of values of [bound parameters].)^ The QPSG disables some query optimizations
+** that look at the values of bound parameters, which can make some queries
+** slower. But the QPSG has the advantage of more predictable behavior. With
+** the QPSG active, SQLite will always use the same query plan in the field as
+** was used during testing in the lab.
+** The first argument to this setting is an integer which is 0 to disable
+** the QPSG, positive to enable QPSG, or negative to leave the setting
+** unchanged. The second parameter is a pointer to an integer into which
+** is written 0 or 1 to indicate whether the QPSG is disabled or enabled
+** following this call.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_TRIGGER_EQP]] <dt>SQLITE_DBCONFIG_TRIGGER_EQP</dt>
+** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not
+** include output for any operations performed by trigger programs. This
+** option is used to set or clear (the default) a flag that governs this
+** behavior. The first parameter passed to this operation is an integer -
+** positive to enable output for trigger programs, or zero to disable it,
+** or negative to leave the setting unchanged.
+** The second parameter is a pointer to an integer into which is written
+** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if
+** it is not disabled, 1 if it is.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_RESET_DATABASE]] <dt>SQLITE_DBCONFIG_RESET_DATABASE</dt>
+** <dd> Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run
+** [VACUUM] in order to reset a database back to an empty database
+** with no schema and no content. The following process works even for
+** a badly corrupted database file:
+** <ol>
+** <li> If the database connection is newly opened, make sure it has read the
+** database schema by preparing then discarding some query against the
+** database, or calling tdsqlite3_table_column_metadata(), ignoring any
+** errors. This step is only necessary if the application desires to keep
+** the database in WAL mode after the reset if it was in WAL mode before
+** the reset.
+** <li> tdsqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
+** <li> [tdsqlite3_exec](db, "[VACUUM]", 0, 0, 0);
+** <li> tdsqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
+** </ol>
+** Because resetting a database is destructive and irreversible, the
+** process requires the use of this obscure API and multiple steps to help
+** ensure that it does not happen by accident.
+**
+** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt>
+** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the
+** "defensive" flag for a database connection. When the defensive
+** flag is enabled, language features that allow ordinary SQL to
+** deliberately corrupt the database file are disabled. The disabled
+** features include but are not limited to the following:
+** <ul>
+** <li> The [PRAGMA writable_schema=ON] statement.
+** <li> The [PRAGMA journal_mode=OFF] statement.
+** <li> Writes to the [sqlite_dbpage] virtual table.
+** <li> Direct writes to [shadow tables].
+** </ul>
+** </dd>
+**
+** [[SQLITE_DBCONFIG_WRITABLE_SCHEMA]] <dt>SQLITE_DBCONFIG_WRITABLE_SCHEMA</dt>
+** <dd>The SQLITE_DBCONFIG_WRITABLE_SCHEMA option activates or deactivates the
+** "writable_schema" flag. This has the same effect and is logically equivalent
+** to setting [PRAGMA writable_schema=ON] or [PRAGMA writable_schema=OFF].
+** The first argument to this setting is an integer which is 0 to disable
+** the writable_schema, positive to enable writable_schema, or negative to
+** leave the setting unchanged. The second parameter is a pointer to an
+** integer into which is written 0 or 1 to indicate whether the writable_schema
+** is enabled or disabled following this call.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]]
+** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt>
+** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates
+** the legacy behavior of the [ALTER TABLE RENAME] command such it
+** behaves as it did prior to [version 3.24.0] (2018-06-04). See the
+** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for
+** additional information. This feature can also be turned on and off
+** using the [PRAGMA legacy_alter_table] statement.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_DQS_DML]]
+** <dt>SQLITE_DBCONFIG_DQS_DML</td>
+** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates
+** the legacy [double-quoted string literal] misfeature for DML statements
+** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The
+** default value of this setting is determined by the [-DSQLITE_DQS]
+** compile-time option.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_DQS_DDL]]
+** <dt>SQLITE_DBCONFIG_DQS_DDL</td>
+** <dd>The SQLITE_DBCONFIG_DQS option activates or deactivates
+** the legacy [double-quoted string literal] misfeature for DDL statements,
+** such as CREATE TABLE and CREATE INDEX. The
+** default value of this setting is determined by the [-DSQLITE_DQS]
+** compile-time option.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]]
+** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</td>
+** <dd>The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to
+** assume that database schemas (the contents of the [sqlite_master] tables)
+** are untainted by malicious content.
+** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite
+** takes additional defensive steps to protect the application from harm
+** including:
+** <ul>
+** <li> Prohibit the use of SQL functions inside triggers, views,
+** CHECK constraints, DEFAULT clauses, expression indexes,
+** partial indexes, or generated columns
+** unless those functions are tagged with [SQLITE_INNOCUOUS].
+** <li> Prohibit the use of virtual tables inside of triggers or views
+** unless those virtual tables are tagged with [SQLITE_VTAB_INNOCUOUS].
+** </ul>
+** This setting defaults to "on" for legacy compatibility, however
+** all applications are advised to turn it off if possible. This setting
+** can also be controlled using the [PRAGMA trusted_schema] statement.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]]
+** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</td>
+** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
+** the legacy file format flag. When activated, this flag causes all newly
+** created database file to have a schema format version number (the 4-byte
+** integer found at offset 44 into the database header) of 1. This in turn
+** means that the resulting database file will be readable and writable by
+** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting,
+** newly created databases are generally not understandable by SQLite versions
+** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there
+** is now scarcely any need to generated database files that are compatible
+** all the way back to version 3.0.0, and so this setting is of little
+** practical use, but is provided so that SQLite can continue to claim the
+** ability to generate new database files that are compatible with version
+** 3.0.0.
+** <p>Note that when the SQLITE_DBCONFIG_LEGACY_FILE_FORMAT setting is on,
+** the [VACUUM] command will fail with an obscure error when attempting to
+** process a table with generated columns and a descending index. This is
+** not considered a bug since SQLite versions 3.3.0 and earlier do not support
+** either generated columns or decending indexes.
+** </dd>
** </dl>
*/
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
@@ -2255,21 +3364,33 @@ struct sqlite3_mem_methods {
#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */
-
+#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */
+#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */
+#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */
+#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */
+#define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */
+#define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011 /* int int* */
+#define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */
+#define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */
+#define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */
+#define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */
+#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */
+#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */
+#define SQLITE_DBCONFIG_MAX 1017 /* Largest DBCONFIG */
/*
** CAPI3REF: Enable Or Disable Extended Result Codes
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_extended_result_codes() routine enables or disables the
+** ^The tdsqlite3_extended_result_codes() routine enables or disables the
** [extended result codes] feature of SQLite. ^The extended result
** codes are disabled by default for historical compatibility.
*/
-SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff);
+SQLITE_API int tdsqlite3_extended_result_codes(tdsqlite3*, int onoff);
/*
** CAPI3REF: Last Insert Rowid
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^Each entry in most SQLite tables (except for [WITHOUT ROWID] tables)
** has a unique 64-bit signed
@@ -2279,20 +3400,30 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff);
** the table has a column of type [INTEGER PRIMARY KEY] then that column
** is another alias for the rowid.
**
-** ^The sqlite3_last_insert_rowid(D) interface returns the [rowid] of the
-** most recent successful [INSERT] into a rowid table or [virtual table]
-** on database connection D.
-** ^Inserts into [WITHOUT ROWID] tables are not recorded.
-** ^If no successful [INSERT]s into rowid tables
-** have ever occurred on the database connection D,
-** then sqlite3_last_insert_rowid(D) returns zero.
-**
-** ^(If an [INSERT] occurs within a trigger or within a [virtual table]
-** method, then this routine will return the [rowid] of the inserted
-** row as long as the trigger or virtual table method is running.
-** But once the trigger or virtual table method ends, the value returned
-** by this routine reverts to what it was before the trigger or virtual
-** table method began.)^
+** ^The tdsqlite3_last_insert_rowid(D) interface usually returns the [rowid] of
+** the most recent successful [INSERT] into a rowid table or [virtual table]
+** on database connection D. ^Inserts into [WITHOUT ROWID] tables are not
+** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred
+** on the database connection D, then tdsqlite3_last_insert_rowid(D) returns
+** zero.
+**
+** As well as being set automatically as rows are inserted into database
+** tables, the value returned by this function may be set explicitly by
+** [tdsqlite3_set_last_insert_rowid()]
+**
+** Some virtual table implementations may INSERT rows into rowid tables as
+** part of committing a transaction (e.g. to flush data accumulated in memory
+** to disk). In this case subsequent calls to this function return the rowid
+** associated with these internal INSERT operations, which leads to
+** unintuitive results. Virtual table implementations that do write to rowid
+** tables in this way can avoid this problem by restoring the original
+** rowid value using [tdsqlite3_set_last_insert_rowid()] before returning
+** control to the user.
+**
+** ^(If an [INSERT] occurs within a trigger then this routine will
+** return the [rowid] of the inserted row as long as the trigger is
+** running. Once the trigger program ends, the value returned
+** by this routine reverts to what it was before the trigger was fired.)^
**
** ^An [INSERT] that fails due to a constraint violation is not a
** successful [INSERT] and does not change the value returned by this
@@ -2311,17 +3442,27 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff);
** [last_insert_rowid() SQL function].
**
** If a separate thread performs a new [INSERT] on the same
-** database connection while the [sqlite3_last_insert_rowid()]
+** database connection while the [tdsqlite3_last_insert_rowid()]
** function is running and thus changes the last insert [rowid],
-** then the value returned by [sqlite3_last_insert_rowid()] is
+** then the value returned by [tdsqlite3_last_insert_rowid()] is
** unpredictable and might not equal either the old or the new
** last insert [rowid].
*/
-SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
+SQLITE_API tdsqlite3_int64 tdsqlite3_last_insert_rowid(tdsqlite3*);
+
+/*
+** CAPI3REF: Set the Last Insert Rowid value.
+** METHOD: tdsqlite3
+**
+** The tdsqlite3_set_last_insert_rowid(D, R) method allows the application to
+** set the value returned by calling tdsqlite3_last_insert_rowid(D) to R
+** without inserting a row into the database.
+*/
+SQLITE_API void tdsqlite3_set_last_insert_rowid(tdsqlite3*,tdsqlite3_int64);
/*
** CAPI3REF: Count The Number Of Rows Modified
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This function returns the number of rows modified, inserted or
** deleted by the most recently completed INSERT, UPDATE or DELETE
@@ -2335,24 +3476,24 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
**
** Changes to a view that are intercepted by
** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value
-** returned by sqlite3_changes() immediately after an INSERT, UPDATE or
+** returned by tdsqlite3_changes() immediately after an INSERT, UPDATE or
** DELETE statement run on a view is always zero. Only changes made to real
** tables are counted.
**
-** Things are more complicated if the sqlite3_changes() function is
+** Things are more complicated if the tdsqlite3_changes() function is
** executed while a trigger program is running. This may happen if the
** program uses the [changes() SQL function], or if some other callback
-** function invokes sqlite3_changes() directly. Essentially:
+** function invokes tdsqlite3_changes() directly. Essentially:
**
** <ul>
** <li> ^(Before entering a trigger program the value returned by
-** sqlite3_changes() function is saved. After the trigger program
+** tdsqlite3_changes() function is saved. After the trigger program
** has finished, the original value is restored.)^
**
** <li> ^(Within a trigger program each INSERT, UPDATE and DELETE
-** statement sets the value returned by sqlite3_changes()
+** statement sets the value returned by tdsqlite3_changes()
** upon completion as normal. Of course, this value will not include
-** any changes performed by sub-triggers, as the sqlite3_changes()
+** any changes performed by sub-triggers, as the tdsqlite3_changes()
** value will be saved and restored after each sub-trigger has run.)^
** </ul>
**
@@ -2363,42 +3504,60 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
** program, the value returned reflects the number of rows modified by the
** previous INSERT, UPDATE or DELETE statement within the same trigger.
**
-** See also the [sqlite3_total_changes()] interface, the
-** [count_changes pragma], and the [changes() SQL function].
-**
** If a separate thread makes changes on the same database connection
-** while [sqlite3_changes()] is running then the value returned
+** while [tdsqlite3_changes()] is running then the value returned
** is unpredictable and not meaningful.
+**
+** See also:
+** <ul>
+** <li> the [tdsqlite3_total_changes()] interface
+** <li> the [count_changes pragma]
+** <li> the [changes() SQL function]
+** <li> the [data_version pragma]
+** </ul>
*/
-SQLITE_API int sqlite3_changes(sqlite3*);
+SQLITE_API int tdsqlite3_changes(tdsqlite3*);
/*
** CAPI3REF: Total Number Of Rows Modified
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This function returns the total number of rows inserted, modified or
** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed
** since the database connection was opened, including those executed as
** part of trigger programs. ^Executing any other type of SQL statement
-** does not affect the value returned by sqlite3_total_changes().
+** does not affect the value returned by tdsqlite3_total_changes().
**
** ^Changes made as part of [foreign key actions] are included in the
** count, but those made as part of REPLACE constraint resolution are
** not. ^Changes to a view that are intercepted by INSTEAD OF triggers
** are not counted.
-**
-** See also the [sqlite3_changes()] interface, the
-** [count_changes pragma], and the [total_changes() SQL function].
**
+** The [tdsqlite3_total_changes(D)] interface only reports the number
+** of rows that changed due to SQL statement run against database
+** connection D. Any changes by other database connections are ignored.
+** To detect changes against a database file from other database
+** connections use the [PRAGMA data_version] command or the
+** [SQLITE_FCNTL_DATA_VERSION] [file control].
+**
** If a separate thread makes changes on the same database connection
-** while [sqlite3_total_changes()] is running then the value
+** while [tdsqlite3_total_changes()] is running then the value
** returned is unpredictable and not meaningful.
+**
+** See also:
+** <ul>
+** <li> the [tdsqlite3_changes()] interface
+** <li> the [count_changes pragma]
+** <li> the [changes() SQL function]
+** <li> the [data_version pragma]
+** <li> the [SQLITE_FCNTL_DATA_VERSION] [file control]
+** </ul>
*/
-SQLITE_API int sqlite3_total_changes(sqlite3*);
+SQLITE_API int tdsqlite3_total_changes(tdsqlite3*);
/*
** CAPI3REF: Interrupt A Long-Running Query
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This function causes any pending database operation to abort and
** return at its earliest opportunity. This routine is typically
@@ -2409,10 +3568,10 @@ SQLITE_API int sqlite3_total_changes(sqlite3*);
** ^It is safe to call this routine from a thread different from the
** thread that is currently running the database operation. But it
** is not safe to call this routine with a [database connection] that
-** is closed or might close before sqlite3_interrupt() returns.
+** is closed or might close before tdsqlite3_interrupt() returns.
**
** ^If an SQL operation is very nearly finished at the time when
-** sqlite3_interrupt() is called, then it might not have an opportunity
+** tdsqlite3_interrupt() is called, then it might not have an opportunity
** to be interrupted and might continue to completion.
**
** ^An SQL operation that is interrupted will return [SQLITE_INTERRUPT].
@@ -2420,21 +3579,18 @@ SQLITE_API int sqlite3_total_changes(sqlite3*);
** that is inside an explicit transaction, then the entire transaction
** will be rolled back automatically.
**
-** ^The sqlite3_interrupt(D) call is in effect until all currently running
+** ^The tdsqlite3_interrupt(D) call is in effect until all currently running
** SQL statements on [database connection] D complete. ^Any new SQL statements
-** that are started after the sqlite3_interrupt() call and before the
-** running statements reaches zero are interrupted as if they had been
-** running prior to the sqlite3_interrupt() call. ^New SQL statements
+** that are started after the tdsqlite3_interrupt() call and before the
+** running statement count reaches zero are interrupted as if they had been
+** running prior to the tdsqlite3_interrupt() call. ^New SQL statements
** that are started after the running statement count reaches zero are
-** not effected by the sqlite3_interrupt().
-** ^A call to sqlite3_interrupt(D) that occurs when there are no running
+** not effected by the tdsqlite3_interrupt().
+** ^A call to tdsqlite3_interrupt(D) that occurs when there are no running
** SQL statements is a no-op and has no effect on SQL statements
-** that are started after the sqlite3_interrupt() call returns.
-**
-** If the database connection closes while [sqlite3_interrupt()]
-** is running then bad things will likely happen.
+** that are started after the tdsqlite3_interrupt() call returns.
*/
-SQLITE_API void sqlite3_interrupt(sqlite3*);
+SQLITE_API void tdsqlite3_interrupt(tdsqlite3*);
/*
** CAPI3REF: Determine If An SQL Statement Is Complete
@@ -2457,40 +3613,40 @@ SQLITE_API void sqlite3_interrupt(sqlite3*);
** ^These routines do not parse the SQL statements thus
** will not detect syntactically incorrect SQL.
**
-** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior
-** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked
-** automatically by sqlite3_complete16(). If that initialization fails,
-** then the return value from sqlite3_complete16() will be non-zero
+** ^(If SQLite has not been initialized using [tdsqlite3_initialize()] prior
+** to invoking tdsqlite3_complete16() then tdsqlite3_initialize() is invoked
+** automatically by tdsqlite3_complete16(). If that initialization fails,
+** then the return value from tdsqlite3_complete16() will be non-zero
** regardless of whether or not the input SQL is complete.)^
**
-** The input to [sqlite3_complete()] must be a zero-terminated
+** The input to [tdsqlite3_complete()] must be a zero-terminated
** UTF-8 string.
**
-** The input to [sqlite3_complete16()] must be a zero-terminated
+** The input to [tdsqlite3_complete16()] must be a zero-terminated
** UTF-16 string in native byte order.
*/
-SQLITE_API int sqlite3_complete(const char *sql);
-SQLITE_API int sqlite3_complete16(const void *sql);
+SQLITE_API int tdsqlite3_complete(const char *sql);
+SQLITE_API int tdsqlite3_complete16(const void *sql);
/*
** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors
** KEYWORDS: {busy-handler callback} {busy handler}
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X
+** ^The tdsqlite3_busy_handler(D,X,P) routine sets a callback function X
** that might be invoked with argument P whenever
** an attempt is made to access a database table associated with
** [database connection] D when another thread
** or process has the table locked.
-** The sqlite3_busy_handler() interface is used to implement
-** [sqlite3_busy_timeout()] and [PRAGMA busy_timeout].
+** The tdsqlite3_busy_handler() interface is used to implement
+** [tdsqlite3_busy_timeout()] and [PRAGMA busy_timeout].
**
** ^If the busy callback is NULL, then [SQLITE_BUSY]
** is returned immediately upon encountering the lock. ^If the busy callback
** is not NULL, then the callback might be invoked with two arguments.
**
** ^The first argument to the busy handler is a copy of the void* pointer which
-** is the third argument to sqlite3_busy_handler(). ^The second argument to
+** is the third argument to tdsqlite3_busy_handler(). ^The second argument to
** the busy handler callback is the number of times that the busy handler has
** been invoked previously for the same locking event. ^If the
** busy callback returns 0, then no additional attempts are made to
@@ -2519,7 +3675,7 @@ SQLITE_API int sqlite3_complete16(const void *sql);
**
** ^(There can only be a single busy handler defined for each
** [database connection]. Setting a new busy handler clears any
-** previously set handler.)^ ^Note that calling [sqlite3_busy_timeout()]
+** previously set handler.)^ ^Note that calling [tdsqlite3_busy_timeout()]
** or evaluating [PRAGMA busy_timeout=N] will change the
** busy handler and thus clear any previously set busy handler.
**
@@ -2531,17 +3687,17 @@ SQLITE_API int sqlite3_complete16(const void *sql);
** A busy handler must not close the database connection
** or [prepared statement] that invoked the busy handler.
*/
-SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
+SQLITE_API int tdsqlite3_busy_handler(tdsqlite3*,int(*)(void*,int),void*);
/*
** CAPI3REF: Set A Busy Timeout
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^This routine sets a [sqlite3_busy_handler | busy handler] that sleeps
+** ^This routine sets a [tdsqlite3_busy_handler | busy handler] that sleeps
** for a specified amount of time when a table is locked. ^The handler
** will sleep multiple times until at least "ms" milliseconds of sleeping
** have accumulated. ^After at least "ms" milliseconds of sleeping,
-** the handler returns 0 which causes [sqlite3_step()] to return
+** the handler returns 0 which causes [tdsqlite3_step()] to return
** [SQLITE_BUSY].
**
** ^Calling this routine with an argument less than or equal to zero
@@ -2549,22 +3705,22 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
**
** ^(There can only be a single busy handler for a particular
** [database connection] at any given moment. If another busy handler
-** was defined (using [sqlite3_busy_handler()]) prior to calling
+** was defined (using [tdsqlite3_busy_handler()]) prior to calling
** this routine, that other busy handler is cleared.)^
**
** See also: [PRAGMA busy_timeout]
*/
-SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
+SQLITE_API int tdsqlite3_busy_timeout(tdsqlite3*, int ms);
/*
** CAPI3REF: Convenience Routines For Running Queries
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** This is a legacy interface that is preserved for backwards compatibility.
** Use of this interface is not recommended.
**
** Definition: A <b>result table</b> is memory data structure created by the
-** [sqlite3_get_table()] interface. A result table records the
+** [tdsqlite3_get_table()] interface. A result table records the
** complete query results from one or more queries.
**
** The table conceptually has a number of rows and columns. But
@@ -2577,11 +3733,11 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
** to zero-terminated strings that contain the names of the columns.
** The remaining entries all point to query results. NULL values result
** in NULL pointers. All other values are in their UTF-8 zero-terminated
-** string representation as returned by [sqlite3_column_text()].
+** string representation as returned by [tdsqlite3_column_text()].
**
** A result table might consist of one or more memory allocations.
-** It is not safe to pass a result table directly to [sqlite3_free()].
-** A result table should be deallocated using [sqlite3_free_table()].
+** It is not safe to pass a result table directly to [tdsqlite3_free()].
+** A result table should be deallocated using [tdsqlite3_free_table()].
**
** ^(As an example of the result table format, suppose a query result
** is as follows:
@@ -2594,9 +3750,9 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
** Cindy | 21
** </pre></blockquote>
**
-** There are two column (M==2) and three rows (N==3). Thus the
+** There are two columns (M==2) and three rows (N==3). Thus the
** result table has 8 entries. Suppose the result table is stored
-** in an array names azResult. Then azResult holds this content:
+** in an array named azResult. Then azResult holds this content:
**
** <blockquote><pre>
** azResult&#91;0] = "Name";
@@ -2609,265 +3765,188 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
** azResult&#91;7] = "21";
** </pre></blockquote>)^
**
-** ^The sqlite3_get_table() function evaluates one or more
+** ^The tdsqlite3_get_table() function evaluates one or more
** semicolon-separated SQL statements in the zero-terminated UTF-8
** string of its 2nd parameter and returns a result table to the
** pointer given in its 3rd parameter.
**
-** After the application has finished with the result from sqlite3_get_table(),
-** it must pass the result table pointer to sqlite3_free_table() in order to
+** After the application has finished with the result from tdsqlite3_get_table(),
+** it must pass the result table pointer to tdsqlite3_free_table() in order to
** release the memory that was malloced. Because of the way the
-** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling
-** function must not try to call [sqlite3_free()] directly. Only
-** [sqlite3_free_table()] is able to release the memory properly and safely.
+** [tdsqlite3_malloc()] happens within tdsqlite3_get_table(), the calling
+** function must not try to call [tdsqlite3_free()] directly. Only
+** [tdsqlite3_free_table()] is able to release the memory properly and safely.
**
-** The sqlite3_get_table() interface is implemented as a wrapper around
-** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access
+** The tdsqlite3_get_table() interface is implemented as a wrapper around
+** [tdsqlite3_exec()]. The tdsqlite3_get_table() routine does not have access
** to any internal data structures of SQLite. It uses only the public
** interface defined here. As a consequence, errors that occur in the
-** wrapper layer outside of the internal [sqlite3_exec()] call are not
-** reflected in subsequent calls to [sqlite3_errcode()] or
-** [sqlite3_errmsg()].
+** wrapper layer outside of the internal [tdsqlite3_exec()] call are not
+** reflected in subsequent calls to [tdsqlite3_errcode()] or
+** [tdsqlite3_errmsg()].
*/
-SQLITE_API int sqlite3_get_table(
- sqlite3 *db, /* An open database */
+SQLITE_API int tdsqlite3_get_table(
+ tdsqlite3 *db, /* An open database */
const char *zSql, /* SQL to be evaluated */
char ***pazResult, /* Results of the query */
int *pnRow, /* Number of result rows written here */
int *pnColumn, /* Number of result columns written here */
char **pzErrmsg /* Error msg written here */
);
-SQLITE_API void sqlite3_free_table(char **result);
+SQLITE_API void tdsqlite3_free_table(char **result);
/*
** CAPI3REF: Formatted String Printing Functions
**
** These routines are work-alikes of the "printf()" family of functions
** from the standard C library.
-** These routines understand most of the common K&R formatting options,
-** plus some additional non-standard formats, detailed below.
-** Note that some of the more obscure formatting options from recent
-** C-library standards are omitted from this implementation.
+** These routines understand most of the common formatting options from
+** the standard library printf()
+** plus some additional non-standard formats ([%q], [%Q], [%w], and [%z]).
+** See the [built-in printf()] documentation for details.
**
-** ^The sqlite3_mprintf() and sqlite3_vmprintf() routines write their
-** results into memory obtained from [sqlite3_malloc()].
+** ^The tdsqlite3_mprintf() and tdsqlite3_vmprintf() routines write their
+** results into memory obtained from [tdsqlite3_malloc64()].
** The strings returned by these two routines should be
-** released by [sqlite3_free()]. ^Both routines return a
-** NULL pointer if [sqlite3_malloc()] is unable to allocate enough
+** released by [tdsqlite3_free()]. ^Both routines return a
+** NULL pointer if [tdsqlite3_malloc64()] is unable to allocate enough
** memory to hold the resulting string.
**
-** ^(The sqlite3_snprintf() routine is similar to "snprintf()" from
+** ^(The tdsqlite3_snprintf() routine is similar to "snprintf()" from
** the standard C library. The result is written into the
** buffer supplied as the second parameter whose size is given by
** the first parameter. Note that the order of the
** first two parameters is reversed from snprintf().)^ This is an
** historical accident that cannot be fixed without breaking
-** backwards compatibility. ^(Note also that sqlite3_snprintf()
+** backwards compatibility. ^(Note also that tdsqlite3_snprintf()
** returns a pointer to its buffer instead of the number of
** characters actually written into the buffer.)^ We admit that
** the number of characters written would be a more useful return
-** value but we cannot change the implementation of sqlite3_snprintf()
+** value but we cannot change the implementation of tdsqlite3_snprintf()
** now without breaking compatibility.
**
-** ^As long as the buffer size is greater than zero, sqlite3_snprintf()
+** ^As long as the buffer size is greater than zero, tdsqlite3_snprintf()
** guarantees that the buffer is always zero-terminated. ^The first
** parameter "n" is the total size of the buffer, including space for
** the zero terminator. So the longest string that can be completely
** written will be n-1 characters.
**
-** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf().
-**
-** These routines all implement some additional formatting
-** options that are useful for constructing SQL statements.
-** All of the usual printf() formatting options apply. In addition, there
-** is are "%q", "%Q", "%w" and "%z" options.
-**
-** ^(The %q option works like %s in that it substitutes a nul-terminated
-** string from the argument list. But %q also doubles every '\'' character.
-** %q is designed for use inside a string literal.)^ By doubling each '\''
-** character it escapes that character and allows it to be inserted into
-** the string.
-**
-** For example, assume the string variable zText contains text as follows:
-**
-** <blockquote><pre>
-** char *zText = "It's a happy day!";
-** </pre></blockquote>
-**
-** One can use this text in an SQL statement as follows:
-**
-** <blockquote><pre>
-** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES('%q')", zText);
-** sqlite3_exec(db, zSQL, 0, 0, 0);
-** sqlite3_free(zSQL);
-** </pre></blockquote>
-**
-** Because the %q format string is used, the '\'' character in zText
-** is escaped and the SQL generated is as follows:
+** ^The tdsqlite3_vsnprintf() routine is a varargs version of tdsqlite3_snprintf().
**
-** <blockquote><pre>
-** INSERT INTO table1 VALUES('It''s a happy day!')
-** </pre></blockquote>
-**
-** This is correct. Had we used %s instead of %q, the generated SQL
-** would have looked like this:
-**
-** <blockquote><pre>
-** INSERT INTO table1 VALUES('It's a happy day!');
-** </pre></blockquote>
-**
-** This second example is an SQL syntax error. As a general rule you should
-** always use %q instead of %s when inserting text into a string literal.
-**
-** ^(The %Q option works like %q except it also adds single quotes around
-** the outside of the total string. Additionally, if the parameter in the
-** argument list is a NULL pointer, %Q substitutes the text "NULL" (without
-** single quotes).)^ So, for example, one could say:
-**
-** <blockquote><pre>
-** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES(%Q)", zText);
-** sqlite3_exec(db, zSQL, 0, 0, 0);
-** sqlite3_free(zSQL);
-** </pre></blockquote>
-**
-** The code above will render a correct SQL statement in the zSQL
-** variable even if the zText variable is a NULL pointer.
-**
-** ^(The "%w" formatting option is like "%q" except that it expects to
-** be contained within double-quotes instead of single quotes, and it
-** escapes the double-quote character instead of the single-quote
-** character.)^ The "%w" formatting option is intended for safely inserting
-** table and column names into a constructed SQL statement.
-**
-** ^(The "%z" formatting option works like "%s" but with the
-** addition that after the string has been read and copied into
-** the result, [sqlite3_free()] is called on the input string.)^
+** See also: [built-in printf()], [printf() SQL function]
*/
-SQLITE_API char *sqlite3_mprintf(const char*,...);
-SQLITE_API char *sqlite3_vmprintf(const char*, va_list);
-SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...);
-SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
+SQLITE_API char *tdsqlite3_mprintf(const char*,...);
+SQLITE_API char *tdsqlite3_vmprintf(const char*, va_list);
+SQLITE_API char *tdsqlite3_snprintf(int,char*,const char*, ...);
+SQLITE_API char *tdsqlite3_vsnprintf(int,char*,const char*, va_list);
/*
** CAPI3REF: Memory Allocation Subsystem
**
** The SQLite core uses these three routines for all of its own
** internal memory allocation needs. "Core" in the previous sentence
-** does not include operating-system specific VFS implementation. The
+** does not include operating-system specific [VFS] implementation. The
** Windows VFS uses native malloc() and free() for some operations.
**
-** ^The sqlite3_malloc() routine returns a pointer to a block
+** ^The tdsqlite3_malloc() routine returns a pointer to a block
** of memory at least N bytes in length, where N is the parameter.
-** ^If sqlite3_malloc() is unable to obtain sufficient free
+** ^If tdsqlite3_malloc() is unable to obtain sufficient free
** memory, it returns a NULL pointer. ^If the parameter N to
-** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns
+** tdsqlite3_malloc() is zero or negative then tdsqlite3_malloc() returns
** a NULL pointer.
**
-** ^The sqlite3_malloc64(N) routine works just like
-** sqlite3_malloc(N) except that N is an unsigned 64-bit integer instead
+** ^The tdsqlite3_malloc64(N) routine works just like
+** tdsqlite3_malloc(N) except that N is an unsigned 64-bit integer instead
** of a signed 32-bit integer.
**
-** ^Calling sqlite3_free() with a pointer previously returned
-** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
-** that it might be reused. ^The sqlite3_free() routine is
+** ^Calling tdsqlite3_free() with a pointer previously returned
+** by tdsqlite3_malloc() or tdsqlite3_realloc() releases that memory so
+** that it might be reused. ^The tdsqlite3_free() routine is
** a no-op if is called with a NULL pointer. Passing a NULL pointer
-** to sqlite3_free() is harmless. After being freed, memory
+** to tdsqlite3_free() is harmless. After being freed, memory
** should neither be read nor written. Even reading previously freed
** memory might result in a segmentation fault or other severe error.
** Memory corruption, a segmentation fault, or other severe error
-** might result if sqlite3_free() is called with a non-NULL pointer that
-** was not obtained from sqlite3_malloc() or sqlite3_realloc().
+** might result if tdsqlite3_free() is called with a non-NULL pointer that
+** was not obtained from tdsqlite3_malloc() or tdsqlite3_realloc().
**
-** ^The sqlite3_realloc(X,N) interface attempts to resize a
+** ^The tdsqlite3_realloc(X,N) interface attempts to resize a
** prior memory allocation X to be at least N bytes.
-** ^If the X parameter to sqlite3_realloc(X,N)
+** ^If the X parameter to tdsqlite3_realloc(X,N)
** is a NULL pointer then its behavior is identical to calling
-** sqlite3_malloc(N).
-** ^If the N parameter to sqlite3_realloc(X,N) is zero or
+** tdsqlite3_malloc(N).
+** ^If the N parameter to tdsqlite3_realloc(X,N) is zero or
** negative then the behavior is exactly the same as calling
-** sqlite3_free(X).
-** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation
+** tdsqlite3_free(X).
+** ^tdsqlite3_realloc(X,N) returns a pointer to a memory allocation
** of at least N bytes in size or NULL if insufficient memory is available.
** ^If M is the size of the prior allocation, then min(N,M) bytes
** of the prior allocation are copied into the beginning of buffer returned
-** by sqlite3_realloc(X,N) and the prior allocation is freed.
-** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the
+** by tdsqlite3_realloc(X,N) and the prior allocation is freed.
+** ^If tdsqlite3_realloc(X,N) returns NULL and N is positive, then the
** prior allocation is not freed.
**
-** ^The sqlite3_realloc64(X,N) interfaces works the same as
-** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
+** ^The tdsqlite3_realloc64(X,N) interfaces works the same as
+** tdsqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
** of a 32-bit signed integer.
**
-** ^If X is a memory allocation previously obtained from sqlite3_malloc(),
-** sqlite3_malloc64(), sqlite3_realloc(), or sqlite3_realloc64(), then
-** sqlite3_msize(X) returns the size of that memory allocation in bytes.
-** ^The value returned by sqlite3_msize(X) might be larger than the number
+** ^If X is a memory allocation previously obtained from tdsqlite3_malloc(),
+** tdsqlite3_malloc64(), tdsqlite3_realloc(), or tdsqlite3_realloc64(), then
+** tdsqlite3_msize(X) returns the size of that memory allocation in bytes.
+** ^The value returned by tdsqlite3_msize(X) might be larger than the number
** of bytes requested when X was allocated. ^If X is a NULL pointer then
-** sqlite3_msize(X) returns zero. If X points to something that is not
+** tdsqlite3_msize(X) returns zero. If X points to something that is not
** the beginning of memory allocation, or if it points to a formerly
** valid memory allocation that has now been freed, then the behavior
-** of sqlite3_msize(X) is undefined and possibly harmful.
+** of tdsqlite3_msize(X) is undefined and possibly harmful.
**
-** ^The memory returned by sqlite3_malloc(), sqlite3_realloc(),
-** sqlite3_malloc64(), and sqlite3_realloc64()
+** ^The memory returned by tdsqlite3_malloc(), tdsqlite3_realloc(),
+** tdsqlite3_malloc64(), and tdsqlite3_realloc64()
** is always aligned to at least an 8 byte boundary, or to a
** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time
** option is used.
**
-** In SQLite version 3.5.0 and 3.5.1, it was possible to define
-** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in
-** implementation of these routines to be omitted. That capability
-** is no longer provided. Only built-in memory allocators can be used.
-**
-** Prior to SQLite version 3.7.10, the Windows OS interface layer called
-** the system malloc() and free() directly when converting
-** filenames between the UTF-8 encoding used by SQLite
-** and whatever filename encoding is used by the particular Windows
-** installation. Memory allocation errors were detected, but
-** they were reported back as [SQLITE_CANTOPEN] or
-** [SQLITE_IOERR] rather than [SQLITE_NOMEM].
-**
-** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()]
+** The pointer arguments to [tdsqlite3_free()] and [tdsqlite3_realloc()]
** must be either NULL or else pointers obtained from a prior
-** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have
+** invocation of [tdsqlite3_malloc()] or [tdsqlite3_realloc()] that have
** not yet been released.
**
** The application must not read or write any part of
** a block of memory after it has been released using
-** [sqlite3_free()] or [sqlite3_realloc()].
+** [tdsqlite3_free()] or [tdsqlite3_realloc()].
*/
-SQLITE_API void *sqlite3_malloc(int);
-SQLITE_API void *sqlite3_malloc64(sqlite3_uint64);
-SQLITE_API void *sqlite3_realloc(void*, int);
-SQLITE_API void *sqlite3_realloc64(void*, sqlite3_uint64);
-SQLITE_API void sqlite3_free(void*);
-SQLITE_API sqlite3_uint64 sqlite3_msize(void*);
+SQLITE_API void *tdsqlite3_malloc(int);
+SQLITE_API void *tdsqlite3_malloc64(tdsqlite3_uint64);
+SQLITE_API void *tdsqlite3_realloc(void*, int);
+SQLITE_API void *tdsqlite3_realloc64(void*, tdsqlite3_uint64);
+SQLITE_API void tdsqlite3_free(void*);
+SQLITE_API tdsqlite3_uint64 tdsqlite3_msize(void*);
/*
** CAPI3REF: Memory Allocator Statistics
**
** SQLite provides these two interfaces for reporting on the status
-** of the [sqlite3_malloc()], [sqlite3_free()], and [sqlite3_realloc()]
+** of the [tdsqlite3_malloc()], [tdsqlite3_free()], and [tdsqlite3_realloc()]
** routines, which form the built-in memory allocation subsystem.
**
-** ^The [sqlite3_memory_used()] routine returns the number of bytes
+** ^The [tdsqlite3_memory_used()] routine returns the number of bytes
** of memory currently outstanding (malloced but not freed).
-** ^The [sqlite3_memory_highwater()] routine returns the maximum
-** value of [sqlite3_memory_used()] since the high-water mark
-** was last reset. ^The values returned by [sqlite3_memory_used()] and
-** [sqlite3_memory_highwater()] include any overhead
-** added by SQLite in its implementation of [sqlite3_malloc()],
+** ^The [tdsqlite3_memory_highwater()] routine returns the maximum
+** value of [tdsqlite3_memory_used()] since the high-water mark
+** was last reset. ^The values returned by [tdsqlite3_memory_used()] and
+** [tdsqlite3_memory_highwater()] include any overhead
+** added by SQLite in its implementation of [tdsqlite3_malloc()],
** but not overhead added by the any underlying system library
-** routines that [sqlite3_malloc()] may call.
+** routines that [tdsqlite3_malloc()] may call.
**
** ^The memory high-water mark is reset to the current value of
-** [sqlite3_memory_used()] if and only if the parameter to
-** [sqlite3_memory_highwater()] is true. ^The value returned
-** by [sqlite3_memory_highwater(1)] is the high-water mark
+** [tdsqlite3_memory_used()] if and only if the parameter to
+** [tdsqlite3_memory_highwater()] is true. ^The value returned
+** by [tdsqlite3_memory_highwater(1)] is the high-water mark
** prior to the reset.
*/
-SQLITE_API sqlite3_int64 sqlite3_memory_used(void);
-SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
+SQLITE_API tdsqlite3_int64 tdsqlite3_memory_used(void);
+SQLITE_API tdsqlite3_int64 tdsqlite3_memory_highwater(int resetFlag);
/*
** CAPI3REF: Pseudo-Random Number Generator
@@ -2875,7 +3954,7 @@ SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
** SQLite contains a high-quality pseudo-random number generator (PRNG) used to
** select random [ROWID | ROWIDs] when inserting new records into a table that
** already uses the largest possible [ROWID]. The PRNG is also used for
-** the build-in random() and randomblob() SQL functions. This interface allows
+** the built-in random() and randomblob() SQL functions. This interface allows
** applications to access the same PRNG for other purposes.
**
** ^A call to this routine stores N bytes of randomness into buffer P.
@@ -2884,23 +3963,25 @@ SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
** ^If this routine has not been previously called or if the previous
** call had N less than one or a NULL pointer for P, then the PRNG is
** seeded using randomness obtained from the xRandomness method of
-** the default [sqlite3_vfs] object.
+** the default [tdsqlite3_vfs] object.
** ^If the previous call to this routine had an N of 1 or more and a
** non-NULL P then the pseudo-randomness is generated
-** internally and without recourse to the [sqlite3_vfs] xRandomness
+** internally and without recourse to the [tdsqlite3_vfs] xRandomness
** method.
*/
-SQLITE_API void sqlite3_randomness(int N, void *P);
+SQLITE_API void tdsqlite3_randomness(int N, void *P);
/*
** CAPI3REF: Compile-Time Authorization Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
+** KEYWORDS: {authorizer callback}
**
** ^This routine registers an authorizer callback with a particular
** [database connection], supplied in the first argument.
** ^The authorizer callback is invoked as SQL statements are being compiled
-** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()],
-** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()]. ^At various
+** by [tdsqlite3_prepare()] or its variants [tdsqlite3_prepare_v2()],
+** [tdsqlite3_prepare_v3()], [tdsqlite3_prepare16()], [tdsqlite3_prepare16_v2()],
+** and [tdsqlite3_prepare16_v3()]. ^At various
** points during the compilation process, as logic is being created
** to perform various actions, the authorizer callback is invoked to
** see if those actions are allowed. ^The authorizer callback should
@@ -2909,21 +3990,23 @@ SQLITE_API void sqlite3_randomness(int N, void *P);
** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be
** rejected with an error. ^If the authorizer callback returns
** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY]
-** then the [sqlite3_prepare_v2()] or equivalent call that triggered
+** then the [tdsqlite3_prepare_v2()] or equivalent call that triggered
** the authorizer will fail with an error message.
**
** When the callback returns [SQLITE_OK], that means the operation
** requested is ok. ^When the callback returns [SQLITE_DENY], the
-** [sqlite3_prepare_v2()] or equivalent call that triggered the
+** [tdsqlite3_prepare_v2()] or equivalent call that triggered the
** authorizer will fail with an error message explaining that
** access is denied.
**
** ^The first parameter to the authorizer callback is a copy of the third
-** parameter to the sqlite3_set_authorizer() interface. ^The second parameter
+** parameter to the tdsqlite3_set_authorizer() interface. ^The second parameter
** to the callback is an integer [SQLITE_COPY | action code] that specifies
** the particular action to be authorized. ^The third through sixth parameters
-** to the callback are zero-terminated strings that contain additional
-** details about the action to be authorized.
+** to the callback are either NULL pointers or zero-terminated strings
+** that contain additional details about the action to be authorized.
+** Applications must always be prepared to encounter a NULL pointer in any
+** of the third through the sixth parameters of the authorization callback.
**
** ^If the action code is [SQLITE_READ]
** and the callback returns [SQLITE_IGNORE] then the
@@ -2932,11 +4015,15 @@ SQLITE_API void sqlite3_randomness(int N, void *P);
** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE]
** return can be used to deny an untrusted user access to individual
** columns of a table.
+** ^When a table is referenced by a [SELECT] but no column values are
+** extracted from that table (for example in a query like
+** "SELECT count(*) FROM tab") then the [SQLITE_READ] authorizer callback
+** is invoked once for that table with a column name that is an empty string.
** ^If the action code is [SQLITE_DELETE] and the callback returns
** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the
** [truncate optimization] is disabled and all rows are deleted individually.
**
-** An authorizer is used when [sqlite3_prepare | preparing]
+** An authorizer is used when [tdsqlite3_prepare | preparing]
** SQL statements from an untrusted source, to ensure that the SQL statements
** do not try to access data they are not allowed to see, or that they do not
** try to execute malicious statements that damage the database. For
@@ -2944,37 +4031,37 @@ SQLITE_API void sqlite3_randomness(int N, void *P);
** SQL queries for evaluation by a database. But the application does
** not want the user to be able to make arbitrary changes to the
** database. An authorizer could then be put in place while the
-** user-entered SQL is being [sqlite3_prepare | prepared] that
+** user-entered SQL is being [tdsqlite3_prepare | prepared] that
** disallows everything except [SELECT] statements.
**
** Applications that need to process SQL from untrusted sources
-** might also consider lowering resource limits using [sqlite3_limit()]
+** might also consider lowering resource limits using [tdsqlite3_limit()]
** and limiting database size using the [max_page_count] [PRAGMA]
** in addition to using an authorizer.
**
** ^(Only a single authorizer can be in place on a database connection
-** at a time. Each call to sqlite3_set_authorizer overrides the
+** at a time. Each call to tdsqlite3_set_authorizer overrides the
** previous call.)^ ^Disable the authorizer by installing a NULL callback.
** The authorizer is disabled by default.
**
** The authorizer callback must not do anything that will modify
** the database connection that invoked the authorizer callback.
-** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their
+** Note that [tdsqlite3_prepare_v2()] and [tdsqlite3_step()] both modify their
** database connections for the meaning of "modify" in this paragraph.
**
-** ^When [sqlite3_prepare_v2()] is used to prepare a statement, the
-** statement might be re-prepared during [sqlite3_step()] due to a
+** ^When [tdsqlite3_prepare_v2()] is used to prepare a statement, the
+** statement might be re-prepared during [tdsqlite3_step()] due to a
** schema change. Hence, the application should ensure that the
-** correct authorizer callback remains in place during the [sqlite3_step()].
+** correct authorizer callback remains in place during the [tdsqlite3_step()].
**
** ^Note that the authorizer callback is invoked only during
-** [sqlite3_prepare()] or its variants. Authorization is not
-** performed during statement evaluation in [sqlite3_step()], unless
-** as stated in the previous paragraph, sqlite3_step() invokes
-** sqlite3_prepare_v2() to reprepare a statement after a schema change.
+** [tdsqlite3_prepare()] or its variants. Authorization is not
+** performed during statement evaluation in [tdsqlite3_step()], unless
+** as stated in the previous paragraph, tdsqlite3_step() invokes
+** tdsqlite3_prepare_v2() to reprepare a statement after a schema change.
*/
-SQLITE_API int sqlite3_set_authorizer(
- sqlite3*,
+SQLITE_API int tdsqlite3_set_authorizer(
+ tdsqlite3*,
int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
void *pUserData
);
@@ -2982,14 +4069,14 @@ SQLITE_API int sqlite3_set_authorizer(
/*
** CAPI3REF: Authorizer Return Codes
**
-** The [sqlite3_set_authorizer | authorizer callback function] must
+** The [tdsqlite3_set_authorizer | authorizer callback function] must
** return either [SQLITE_OK] or one of these two constants in order
** to signal SQLite whether or not the action is permitted. See the
-** [sqlite3_set_authorizer | authorizer documentation] for additional
+** [tdsqlite3_set_authorizer | authorizer documentation] for additional
** information.
**
** Note that SQLITE_IGNORE is also used as a [conflict resolution mode]
-** returned from the [sqlite3_vtab_on_conflict()] interface.
+** returned from the [tdsqlite3_vtab_on_conflict()] interface.
*/
#define SQLITE_DENY 1 /* Abort the SQL statement with an error */
#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */
@@ -2997,7 +4084,7 @@ SQLITE_API int sqlite3_set_authorizer(
/*
** CAPI3REF: Authorizer Action Codes
**
-** The [sqlite3_set_authorizer()] interface registers a callback function
+** The [tdsqlite3_set_authorizer()] interface registers a callback function
** that is invoked to authorize certain SQL statement actions. The
** second parameter to the callback is an integer code that specifies
** what action is being authorized. These are the integer action codes that
@@ -3051,48 +4138,48 @@ SQLITE_API int sqlite3_set_authorizer(
/*
** CAPI3REF: Tracing And Profiling Functions
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** These routines are deprecated. Use the [sqlite3_trace_v2()] interface
+** These routines are deprecated. Use the [tdsqlite3_trace_v2()] interface
** instead of the routines described here.
**
** These routines register callback functions that can be used for
** tracing and profiling the execution of SQL statements.
**
-** ^The callback function registered by sqlite3_trace() is invoked at
-** various times when an SQL statement is being run by [sqlite3_step()].
-** ^The sqlite3_trace() callback is invoked with a UTF-8 rendering of the
+** ^The callback function registered by tdsqlite3_trace() is invoked at
+** various times when an SQL statement is being run by [tdsqlite3_step()].
+** ^The tdsqlite3_trace() callback is invoked with a UTF-8 rendering of the
** SQL statement text as the statement first begins executing.
-** ^(Additional sqlite3_trace() callbacks might occur
+** ^(Additional tdsqlite3_trace() callbacks might occur
** as each triggered subprogram is entered. The callbacks for triggers
** contain a UTF-8 SQL comment that identifies the trigger.)^
**
** The [SQLITE_TRACE_SIZE_LIMIT] compile-time option can be used to limit
-** the length of [bound parameter] expansion in the output of sqlite3_trace().
+** the length of [bound parameter] expansion in the output of tdsqlite3_trace().
**
-** ^The callback function registered by sqlite3_profile() is invoked
+** ^The callback function registered by tdsqlite3_profile() is invoked
** as each SQL statement finishes. ^The profile callback contains
** the original statement text and an estimate of wall-clock time
** of how long that statement took to run. ^The profile callback
** time is in units of nanoseconds, however the current implementation
** is only capable of millisecond resolution so the six least significant
** digits in the time are meaningless. Future versions of SQLite
-** might provide greater resolution on the profiler callback. The
-** sqlite3_profile() function is considered experimental and is
-** subject to change in future versions of SQLite.
+** might provide greater resolution on the profiler callback. Invoking
+** either [tdsqlite3_trace()] or [tdsqlite3_trace_v2()] will cancel the
+** profile callback.
*/
-SQLITE_API SQLITE_DEPRECATED void *sqlite3_trace(sqlite3*,
+SQLITE_API SQLITE_DEPRECATED void *tdsqlite3_trace(tdsqlite3*,
void(*xTrace)(void*,const char*), void*);
-SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
- void(*xProfile)(void*,const char*,sqlite3_uint64), void*);
+SQLITE_API SQLITE_DEPRECATED void *tdsqlite3_profile(tdsqlite3*,
+ void(*xProfile)(void*,const char*,tdsqlite3_uint64), void*);
/*
** CAPI3REF: SQL Trace Event Codes
** KEYWORDS: SQLITE_TRACE
**
** These constants identify classes of events that can be monitored
-** using the [sqlite3_trace_v2()] tracing logic. The third argument
-** to [sqlite3_trace_v2()] is an OR-ed combination of one or more of
+** using the [tdsqlite3_trace_v2()] tracing logic. The M argument
+** to [tdsqlite3_trace_v2(D,M,X,P)] is an OR-ed combination of one or more of
** the following constants. ^The first argument to the trace callback
** is one of the following constants.
**
@@ -3101,7 +4188,7 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
** ^A trace callback has four arguments: xCallback(T,C,P,X).
** ^The T argument is one of the integer type codes above.
** ^The C argument is a copy of the context pointer passed in as the
-** fourth argument to [sqlite3_trace_v2()].
+** fourth argument to [tdsqlite3_trace_v2()].
** The P and X arguments are pointers whose meanings depend on T.
**
** <dl>
@@ -3113,13 +4200,13 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
** [prepared statement]. ^The X argument is a pointer to a string which
** is the unexpanded SQL text of the prepared statement or an SQL comment
** that indicates the invocation of a trigger. ^The callback can compute
-** the same text that would have been returned by the legacy [sqlite3_trace()]
+** the same text that would have been returned by the legacy [tdsqlite3_trace()]
** interface by using the X argument when X begins with "--" and invoking
-** [sqlite3_expanded_sql(P)] otherwise.
+** [tdsqlite3_expanded_sql(P)] otherwise.
**
** [[SQLITE_TRACE_PROFILE]] <dt>SQLITE_TRACE_PROFILE</dt>
** <dd>^An SQLITE_TRACE_PROFILE callback provides approximately the same
-** information as is provided by the [sqlite3_profile()] callback.
+** information as is provided by the [tdsqlite3_profile()] callback.
** ^The P argument is a pointer to the [prepared statement] and the
** X argument points to a 64-bit integer which is the estimated of
** the number of nanosecond that the prepared statement took to run.
@@ -3145,17 +4232,17 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
/*
** CAPI3REF: SQL Trace Hook
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_trace_v2(D,M,X,P) interface registers a trace callback
+** ^The tdsqlite3_trace_v2(D,M,X,P) interface registers a trace callback
** function X against [database connection] D, using property mask M
** and context pointer P. ^If the X callback is
** NULL or if the M mask is zero, then tracing is disabled. The
** M argument should be the bitwise OR-ed combination of
** zero or more [SQLITE_TRACE] constants.
**
-** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides
-** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2().
+** ^Each call to either tdsqlite3_trace() or tdsqlite3_trace_v2() overrides
+** (cancels) any prior calls to tdsqlite3_trace() or tdsqlite3_trace_v2().
**
** ^The X callback is invoked whenever any of the events identified by
** mask M occur. ^The integer return value from the callback is currently
@@ -3168,12 +4255,12 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
** ^The C argument is a copy of the context pointer.
** The P and X arguments are pointers whose meanings depend on T.
**
-** The sqlite3_trace_v2() interface is intended to replace the legacy
-** interfaces [sqlite3_trace()] and [sqlite3_profile()], both of which
+** The tdsqlite3_trace_v2() interface is intended to replace the legacy
+** interfaces [tdsqlite3_trace()] and [tdsqlite3_profile()], both of which
** are deprecated.
*/
-SQLITE_API int sqlite3_trace_v2(
- sqlite3*,
+SQLITE_API int tdsqlite3_trace_v2(
+ tdsqlite3*,
unsigned uMask,
int(*xCallback)(unsigned,void*,void*,void*),
void *pCtx
@@ -3181,11 +4268,11 @@ SQLITE_API int sqlite3_trace_v2(
/*
** CAPI3REF: Query Progress Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback
+** ^The tdsqlite3_progress_handler(D,N,X,P) interface causes the callback
** function X to be invoked periodically during long running calls to
-** [sqlite3_exec()], [sqlite3_step()] and [sqlite3_get_table()] for
+** [tdsqlite3_exec()], [tdsqlite3_step()] and [tdsqlite3_get_table()] for
** database connection D. An example use for this
** interface is to keep a GUI updated during a large query.
**
@@ -3207,44 +4294,42 @@ SQLITE_API int sqlite3_trace_v2(
**
** The progress handler callback must not do anything that will modify
** the database connection that invoked the progress handler.
-** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their
+** Note that [tdsqlite3_prepare_v2()] and [tdsqlite3_step()] both modify their
** database connections for the meaning of "modify" in this paragraph.
**
*/
-SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
+SQLITE_API void tdsqlite3_progress_handler(tdsqlite3*, int, int(*)(void*), void*);
/*
** CAPI3REF: Opening A New Database Connection
-** CONSTRUCTOR: sqlite3
+** CONSTRUCTOR: tdsqlite3
**
** ^These routines open an SQLite database file as specified by the
** filename argument. ^The filename argument is interpreted as UTF-8 for
-** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte
-** order for sqlite3_open16(). ^(A [database connection] handle is usually
+** tdsqlite3_open() and tdsqlite3_open_v2() and as UTF-16 in the native byte
+** order for tdsqlite3_open16(). ^(A [database connection] handle is usually
** returned in *ppDb, even if an error occurs. The only exception is that
-** if SQLite is unable to allocate memory to hold the [sqlite3] object,
-** a NULL will be written into *ppDb instead of a pointer to the [sqlite3]
+** if SQLite is unable to allocate memory to hold the [tdsqlite3] object,
+** a NULL will be written into *ppDb instead of a pointer to the [tdsqlite3]
** object.)^ ^(If the database is opened (and/or created) successfully, then
** [SQLITE_OK] is returned. Otherwise an [error code] is returned.)^ ^The
-** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain
+** [tdsqlite3_errmsg()] or [tdsqlite3_errmsg16()] routines can be used to obtain
** an English language description of the error following a failure of any
-** of the sqlite3_open() routines.
+** of the tdsqlite3_open() routines.
**
** ^The default encoding will be UTF-8 for databases created using
-** sqlite3_open() or sqlite3_open_v2(). ^The default encoding for databases
-** created using sqlite3_open16() will be UTF-16 in the native byte order.
+** tdsqlite3_open() or tdsqlite3_open_v2(). ^The default encoding for databases
+** created using tdsqlite3_open16() will be UTF-16 in the native byte order.
**
** Whether or not an error occurs when it is opened, resources
** associated with the [database connection] handle should be released by
-** passing it to [sqlite3_close()] when it is no longer required.
+** passing it to [tdsqlite3_close()] when it is no longer required.
**
-** The sqlite3_open_v2() interface works like sqlite3_open()
+** The tdsqlite3_open_v2() interface works like tdsqlite3_open()
** except that it accepts two additional parameters for additional control
** over the new database connection. ^(The flags parameter to
-** sqlite3_open_v2() can take one of
-** the following three values, optionally combined with the
-** [SQLITE_OPEN_NOMUTEX], [SQLITE_OPEN_FULLMUTEX], [SQLITE_OPEN_SHAREDCACHE],
-** [SQLITE_OPEN_PRIVATECACHE], and/or [SQLITE_OPEN_URI] flags:)^
+** tdsqlite3_open_v2() must include, at a minimum, one of the following
+** three flag combinations:)^
**
** <dl>
** ^(<dt>[SQLITE_OPEN_READONLY]</dt>
@@ -3259,30 +4344,58 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** ^(<dt>[SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]</dt>
** <dd>The database is opened for reading and writing, and is created if
** it does not already exist. This is the behavior that is always used for
-** sqlite3_open() and sqlite3_open16().</dd>)^
+** tdsqlite3_open() and tdsqlite3_open16().</dd>)^
** </dl>
**
-** If the 3rd parameter to sqlite3_open_v2() is not one of the
-** combinations shown above optionally combined with other
+** In addition to the required flags, the following optional flags are
+** also supported:
+**
+** <dl>
+** ^(<dt>[SQLITE_OPEN_URI]</dt>
+** <dd>The filename can be interpreted as a URI if this flag is set.</dd>)^
+**
+** ^(<dt>[SQLITE_OPEN_MEMORY]</dt>
+** <dd>The database will be opened as an in-memory database. The database
+** is named by the "filename" argument for the purposes of cache-sharing,
+** if shared cache mode is enabled, but the "filename" is otherwise ignored.
+** </dd>)^
+**
+** ^(<dt>[SQLITE_OPEN_NOMUTEX]</dt>
+** <dd>The new database connection will use the "multi-thread"
+** [threading mode].)^ This means that separate threads are allowed
+** to use SQLite at the same time, as long as each thread is using
+** a different [database connection].
+**
+** ^(<dt>[SQLITE_OPEN_FULLMUTEX]</dt>
+** <dd>The new database connection will use the "serialized"
+** [threading mode].)^ This means the multiple threads can safely
+** attempt to use the same database connection at the same time.
+** (Mutexes will block any actual concurrency, but in this mode
+** there is no harm in trying.)
+**
+** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt>
+** <dd>The database is opened [shared cache] enabled, overriding
+** the default shared cache setting provided by
+** [tdsqlite3_enable_shared_cache()].)^
+**
+** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
+** <dd>The database is opened [shared cache] disabled, overriding
+** the default shared cache setting provided by
+** [tdsqlite3_enable_shared_cache()].)^
+**
+** [[OPEN_NOFOLLOW]] ^(<dt>[SQLITE_OPEN_NOFOLLOW]</dt>
+** <dd>The database filename is not allowed to be a symbolic link</dd>
+** </dl>)^
+**
+** If the 3rd parameter to tdsqlite3_open_v2() is not one of the
+** required combinations shown above optionally combined with other
** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits]
** then the behavior is undefined.
**
-** ^If the [SQLITE_OPEN_NOMUTEX] flag is set, then the database connection
-** opens in the multi-thread [threading mode] as long as the single-thread
-** mode has not been set at compile-time or start-time. ^If the
-** [SQLITE_OPEN_FULLMUTEX] flag is set then the database connection opens
-** in the serialized [threading mode] unless single-thread was
-** previously selected at compile-time or start-time.
-** ^The [SQLITE_OPEN_SHAREDCACHE] flag causes the database connection to be
-** eligible to use [shared cache mode], regardless of whether or not shared
-** cache is enabled using [sqlite3_enable_shared_cache()]. ^The
-** [SQLITE_OPEN_PRIVATECACHE] flag causes the database connection to not
-** participate in [shared cache mode] even if it is enabled.
-**
-** ^The fourth parameter to sqlite3_open_v2() is the name of the
-** [sqlite3_vfs] object that defines the operating system interface that
+** ^The fourth parameter to tdsqlite3_open_v2() is the name of the
+** [tdsqlite3_vfs] object that defines the operating system interface that
** the new database connection should use. ^If the fourth parameter is
-** a NULL pointer then the default [sqlite3_vfs] object is used.
+** a NULL pointer then the default [tdsqlite3_vfs] object is used.
**
** ^If the filename is ":memory:", then a private, temporary in-memory database
** is created for the connection. ^This in-memory database will vanish when
@@ -3296,15 +4409,15 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** on-disk database will be created. ^This private database will be
** automatically deleted as soon as the database connection is closed.
**
-** [[URI filenames in sqlite3_open()]] <h3>URI Filenames</h3>
+** [[URI filenames in tdsqlite3_open()]] <h3>URI Filenames</h3>
**
** ^If [URI filename] interpretation is enabled, and the filename argument
** begins with "file:", then the filename is interpreted as a URI. ^URI
** filename interpretation is enabled if the [SQLITE_OPEN_URI] flag is
-** set in the fourth argument to sqlite3_open_v2(), or if it has
+** set in the third argument to tdsqlite3_open_v2(), or if it has
** been enabled globally using the [SQLITE_CONFIG_URI] option with the
-** [sqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option.
-** As of SQLite version 3.7.7, URI filename interpretation is turned off
+** [tdsqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option.
+** URI filename interpretation is turned off
** by default, but future releases of SQLite might enable URI filename
** interpretation by default. See "[URI filenames]" for additional
** information.
@@ -3334,16 +4447,16 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** a VFS object that provides the operating system interface that should
** be used to access the database file on disk. ^If this option is set to
** an empty string the default VFS object is used. ^Specifying an unknown
-** VFS is an error. ^If sqlite3_open_v2() is used and the vfs option is
+** VFS is an error. ^If tdsqlite3_open_v2() is used and the vfs option is
** present, then the VFS specified by the option takes precedence over
-** the value passed as the fourth parameter to sqlite3_open_v2().
+** the value passed as the fourth parameter to tdsqlite3_open_v2().
**
** <li> <b>mode</b>: ^(The mode parameter may be set to either "ro", "rw",
** "rwc", or "memory". Attempting to set it to any other value is
** an error)^.
** ^If "ro" is specified, then the database is opened for read-only
** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the
-** third argument to sqlite3_open_v2(). ^If the mode option is set to
+** third argument to tdsqlite3_open_v2(). ^If the mode option is set to
** "rw", then the database is opened for read-write (but not create)
** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had
** been set. ^Value "rwc" is equivalent to setting both
@@ -3351,14 +4464,14 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** set to "memory" then a pure [in-memory database] that never reads
** or writes from disk is used. ^It is an error to specify a value for
** the mode parameter that is less restrictive than that specified by
-** the flags passed in the third parameter to sqlite3_open_v2().
+** the flags passed in the third parameter to tdsqlite3_open_v2().
**
** <li> <b>cache</b>: ^The cache parameter may be set to either "shared" or
** "private". ^Setting it to "shared" is equivalent to setting the
** SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed to
-** sqlite3_open_v2(). ^Setting the cache parameter to "private" is
+** tdsqlite3_open_v2(). ^Setting the cache parameter to "private" is
** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit.
-** ^If sqlite3_open_v2() is used and the "cache" parameter is present in
+** ^If tdsqlite3_open_v2() is used and the "cache" parameter is present in
** a URI filename, its value overrides any behavior requested by setting
** SQLITE_OPEN_PRIVATECACHE or SQLITE_OPEN_SHAREDCACHE flag.
**
@@ -3429,28 +4542,28 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** the results are undefined.
**
** <b>Note to Windows users:</b> The encoding used for the filename argument
-** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever
+** of tdsqlite3_open() and tdsqlite3_open_v2() must be UTF-8, not whatever
** codepage is currently defined. Filenames containing international
** characters must be converted to UTF-8 prior to passing them into
-** sqlite3_open() or sqlite3_open_v2().
+** tdsqlite3_open() or tdsqlite3_open_v2().
**
** <b>Note to Windows Runtime users:</b> The temporary directory must be set
-** prior to calling sqlite3_open() or sqlite3_open_v2(). Otherwise, various
+** prior to calling tdsqlite3_open() or tdsqlite3_open_v2(). Otherwise, various
** features that require the use of temporary files may fail.
**
-** See also: [sqlite3_temp_directory]
+** See also: [tdsqlite3_temp_directory]
*/
-SQLITE_API int sqlite3_open(
+SQLITE_API int tdsqlite3_open(
const char *filename, /* Database filename (UTF-8) */
- sqlite3 **ppDb /* OUT: SQLite db handle */
+ tdsqlite3 **ppDb /* OUT: SQLite db handle */
);
-SQLITE_API int sqlite3_open16(
+SQLITE_API int tdsqlite3_open16(
const void *filename, /* Database filename (UTF-16) */
- sqlite3 **ppDb /* OUT: SQLite db handle */
+ tdsqlite3 **ppDb /* OUT: SQLite db handle */
);
-SQLITE_API int sqlite3_open_v2(
+SQLITE_API int tdsqlite3_open_v2(
const char *filename, /* Database filename (UTF-8) */
- sqlite3 **ppDb, /* OUT: SQLite db handle */
+ tdsqlite3 **ppDb, /* OUT: SQLite db handle */
int flags, /* Flags */
const char *zVfs /* Name of VFS module to use */
);
@@ -3458,70 +4571,129 @@ SQLITE_API int sqlite3_open_v2(
/*
** CAPI3REF: Obtain Values For URI Parameters
**
-** These are utility routines, useful to VFS implementations, that check
-** to see if a database file was a URI that contained a specific query
+** These are utility routines, useful to [VFS|custom VFS implementations],
+** that check if a database file was a URI that contained a specific query
** parameter, and if so obtains the value of that query parameter.
**
** If F is the database filename pointer passed into the xOpen() method of
-** a VFS implementation when the flags parameter to xOpen() has one or
-** more of the [SQLITE_OPEN_URI] or [SQLITE_OPEN_MAIN_DB] bits set and
-** P is the name of the query parameter, then
-** sqlite3_uri_parameter(F,P) returns the value of the P
+** a VFS implementation or it is the return value of [tdsqlite3_db_filename()]
+** and if P is the name of the query parameter, then
+** tdsqlite3_uri_parameter(F,P) returns the value of the P
** parameter if it exists or a NULL pointer if P does not appear as a
-** query parameter on F. If P is a query parameter of F
-** has no explicit value, then sqlite3_uri_parameter(F,P) returns
+** query parameter on F. If P is a query parameter of F and it
+** has no explicit value, then tdsqlite3_uri_parameter(F,P) returns
** a pointer to an empty string.
**
-** The sqlite3_uri_boolean(F,P,B) routine assumes that P is a boolean
+** The tdsqlite3_uri_boolean(F,P,B) routine assumes that P is a boolean
** parameter and returns true (1) or false (0) according to the value
-** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the
+** of P. The tdsqlite3_uri_boolean(F,P,B) routine returns true (1) if the
** value of query parameter P is one of "yes", "true", or "on" in any
** case or if the value begins with a non-zero number. The
-** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of
+** tdsqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of
** query parameter P is one of "no", "false", or "off" in any case or
** if the value begins with a numeric zero. If P is not a query
-** parameter on F or if the value of P is does not match any of the
-** above, then sqlite3_uri_boolean(F,P,B) returns (B!=0).
+** parameter on F or if the value of P does not match any of the
+** above, then tdsqlite3_uri_boolean(F,P,B) returns (B!=0).
**
-** The sqlite3_uri_int64(F,P,D) routine converts the value of P into a
+** The tdsqlite3_uri_int64(F,P,D) routine converts the value of P into a
** 64-bit signed integer and returns that integer, or D if P does not
** exist. If the value of P is something other than an integer, then
** zero is returned.
+**
+** The tdsqlite3_uri_key(F,N) returns a pointer to the name (not
+** the value) of the N-th query parameter for filename F, or a NULL
+** pointer if N is less than zero or greater than the number of query
+** parameters minus 1. The N value is zero-based so N should be 0 to obtain
+** the name of the first query parameter, 1 for the second parameter, and
+** so forth.
**
-** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and
-** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and
-** is not a database file pathname pointer that SQLite passed into the xOpen
-** VFS method, then the behavior of this routine is undefined and probably
-** undesirable.
+** If F is a NULL pointer, then tdsqlite3_uri_parameter(F,P) returns NULL and
+** tdsqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and
+** is not a database file pathname pointer that the SQLite core passed
+** into the xOpen VFS method, then the behavior of this routine is undefined
+** and probably undesirable.
+**
+** Beginning with SQLite [version 3.31.0] ([dateof:3.31.0]) the input F
+** parameter can also be the name of a rollback journal file or WAL file
+** in addition to the main database file. Prior to version 3.31.0, these
+** routines would only work if F was the name of the main database file.
+** When the F parameter is the name of the rollback journal or WAL file,
+** it has access to all the same query parameters as were found on the
+** main database file.
+**
+** See the [URI filename] documentation for additional information.
*/
-SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam);
-SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault);
-SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64);
+SQLITE_API const char *tdsqlite3_uri_parameter(const char *zFilename, const char *zParam);
+SQLITE_API int tdsqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault);
+SQLITE_API tdsqlite3_int64 tdsqlite3_uri_int64(const char*, const char*, tdsqlite3_int64);
+SQLITE_API const char *tdsqlite3_uri_key(const char *zFilename, int N);
+
+/*
+** CAPI3REF: Translate filenames
+**
+** These routines are available to [VFS|custom VFS implementations] for
+** translating filenames between the main database file, the journal file,
+** and the WAL file.
+**
+** If F is the name of an sqlite database file, journal file, or WAL file
+** passed by the SQLite core into the VFS, then tdsqlite3_filename_database(F)
+** returns the name of the corresponding database file.
+**
+** If F is the name of an sqlite database file, journal file, or WAL file
+** passed by the SQLite core into the VFS, or if F is a database filename
+** obtained from [tdsqlite3_db_filename()], then tdsqlite3_filename_journal(F)
+** returns the name of the corresponding rollback journal file.
+**
+** If F is the name of an sqlite database file, journal file, or WAL file
+** that was passed by the SQLite core into the VFS, or if F is a database
+** filename obtained from [tdsqlite3_db_filename()], then
+** tdsqlite3_filename_wal(F) returns the name of the corresponding
+** WAL file.
+**
+** In all of the above, if F is not the name of a database, journal or WAL
+** filename passed into the VFS from the SQLite core and F is not the
+** return value from [tdsqlite3_db_filename()], then the result is
+** undefined and is likely a memory access violation.
+*/
+SQLITE_API const char *tdsqlite3_filename_database(const char*);
+SQLITE_API const char *tdsqlite3_filename_journal(const char*);
+SQLITE_API const char *tdsqlite3_filename_wal(const char*);
/*
** CAPI3REF: Error Codes And Messages
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^If the most recent sqlite3_* API call associated with
-** [database connection] D failed, then the sqlite3_errcode(D) interface
+** ^If the most recent tdsqlite3_* API call associated with
+** [database connection] D failed, then the tdsqlite3_errcode(D) interface
** returns the numeric [result code] or [extended result code] for that
** API call.
-** If the most recent API call was successful,
-** then the return value from sqlite3_errcode() is undefined.
-** ^The sqlite3_extended_errcode()
+** ^The tdsqlite3_extended_errcode()
** interface is the same except that it always returns the
** [extended result code] even when extended result codes are
** disabled.
**
-** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
+** The values returned by tdsqlite3_errcode() and/or
+** tdsqlite3_extended_errcode() might change with each API call.
+** Except, there are some interfaces that are guaranteed to never
+** change the value of the error code. The error-code preserving
+** interfaces are:
+**
+** <ul>
+** <li> tdsqlite3_errcode()
+** <li> tdsqlite3_extended_errcode()
+** <li> tdsqlite3_errmsg()
+** <li> tdsqlite3_errmsg16()
+** </ul>
+**
+** ^The tdsqlite3_errmsg() and tdsqlite3_errmsg16() return English-language
** text that describes the error, as either UTF-8 or UTF-16 respectively.
** ^(Memory to hold the error message string is managed internally.
** The application does not need to worry about freeing the result.
** However, the error string might be overwritten or deallocated by
** subsequent calls to other SQLite interface functions.)^
**
-** ^The sqlite3_errstr() interface returns the English-language text
+** ^The tdsqlite3_errstr() interface returns the English-language text
** that describes the [result code], as UTF-8.
** ^(Memory to hold the error message string is managed internally
** and must not be freed by the application)^.
@@ -3532,19 +4704,19 @@ SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int
** When that happens, the second error will be reported since these
** interfaces always report the most recent result. To avoid
** this, each thread can obtain exclusive use of the [database connection] D
-** by invoking [sqlite3_mutex_enter]([sqlite3_db_mutex](D)) before beginning
-** to use D and invoking [sqlite3_mutex_leave]([sqlite3_db_mutex](D)) after
+** by invoking [tdsqlite3_mutex_enter]([tdsqlite3_db_mutex](D)) before beginning
+** to use D and invoking [tdsqlite3_mutex_leave]([tdsqlite3_db_mutex](D)) after
** all calls to the interfaces listed here are completed.
**
** If an interface fails with SQLITE_MISUSE, that means the interface
** was invoked incorrectly by the application. In that case, the
** error code and message may or may not be set.
*/
-SQLITE_API int sqlite3_errcode(sqlite3 *db);
-SQLITE_API int sqlite3_extended_errcode(sqlite3 *db);
-SQLITE_API const char *sqlite3_errmsg(sqlite3*);
-SQLITE_API const void *sqlite3_errmsg16(sqlite3*);
-SQLITE_API const char *sqlite3_errstr(int);
+SQLITE_API int tdsqlite3_errcode(tdsqlite3 *db);
+SQLITE_API int tdsqlite3_extended_errcode(tdsqlite3 *db);
+SQLITE_API const char *tdsqlite3_errmsg(tdsqlite3*);
+SQLITE_API const void *tdsqlite3_errmsg16(tdsqlite3*);
+SQLITE_API const char *tdsqlite3_errstr(int);
/*
** CAPI3REF: Prepared Statement Object
@@ -3561,20 +4733,20 @@ SQLITE_API const char *sqlite3_errstr(int);
** The life-cycle of a prepared statement object usually goes like this:
**
** <ol>
-** <li> Create the prepared statement object using [sqlite3_prepare_v2()].
-** <li> Bind values to [parameters] using the sqlite3_bind_*()
+** <li> Create the prepared statement object using [tdsqlite3_prepare_v2()].
+** <li> Bind values to [parameters] using the tdsqlite3_bind_*()
** interfaces.
-** <li> Run the SQL by calling [sqlite3_step()] one or more times.
-** <li> Reset the prepared statement using [sqlite3_reset()] then go back
+** <li> Run the SQL by calling [tdsqlite3_step()] one or more times.
+** <li> Reset the prepared statement using [tdsqlite3_reset()] then go back
** to step 2. Do this zero or more times.
-** <li> Destroy the object using [sqlite3_finalize()].
+** <li> Destroy the object using [tdsqlite3_finalize()].
** </ol>
*/
-typedef struct sqlite3_stmt sqlite3_stmt;
+typedef struct tdsqlite3_stmt tdsqlite3_stmt;
/*
** CAPI3REF: Run-time Limits
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^(This interface allows the size of various constructs to be limited
** on a connection by connection basis. The first parameter is the
@@ -3593,7 +4765,7 @@ typedef struct sqlite3_stmt sqlite3_stmt;
** silently truncated to the hard upper bound.
**
** ^Regardless of whether or not the limit was changed, the
-** [sqlite3_limit()] interface returns the prior value of the limit.
+** [tdsqlite3_limit()] interface returns the prior value of the limit.
** ^Hence, to find the current value of a limit without changing it,
** simply invoke this interface with the third parameter set to -1.
**
@@ -3605,21 +4777,21 @@ typedef struct sqlite3_stmt sqlite3_stmt;
** off the Internet. The internal databases can be given the
** large, default limits. Databases managed by external sources can
** be given much smaller limits designed to prevent a denial of service
-** attack. Developers might also want to use the [sqlite3_set_authorizer()]
+** attack. Developers might also want to use the [tdsqlite3_set_authorizer()]
** interface to further control untrusted SQL. The size of the database
** created by an untrusted script can be contained using the
** [max_page_count] [PRAGMA].
**
** New run-time limit categories may be added in future releases.
*/
-SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
+SQLITE_API int tdsqlite3_limit(tdsqlite3*, int id, int newVal);
/*
** CAPI3REF: Run-Time Limit Categories
** KEYWORDS: {limit category} {*limit categories}
**
** These constants define various performance limits
-** that can be lowered at run-time using [sqlite3_limit()].
+** that can be lowered at run-time using [tdsqlite3_limit()].
** The synopsis of the meanings of the various limits is shown below.
** Additional information is available at [limits | Limits in SQLite].
**
@@ -3643,9 +4815,9 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
**
** [[SQLITE_LIMIT_VDBE_OP]] ^(<dt>SQLITE_LIMIT_VDBE_OP</dt>
** <dd>The maximum number of instructions in a virtual machine program
-** used to implement an SQL statement. This limit is not currently
-** enforced, though that might be added in some future release of
-** SQLite.</dd>)^
+** used to implement an SQL statement. If [tdsqlite3_prepare_v2()] or
+** the equivalent tries to allocate space for more than this many opcodes
+** in a single prepared statement, an SQLITE_NOMEM error is returned.</dd>)^
**
** [[SQLITE_LIMIT_FUNCTION_ARG]] ^(<dt>SQLITE_LIMIT_FUNCTION_ARG</dt>
** <dd>The maximum number of arguments on a function.</dd>)^
@@ -3684,22 +4856,73 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
#define SQLITE_LIMIT_WORKER_THREADS 11
/*
+** CAPI3REF: Prepare Flags
+**
+** These constants define various flags that can be passed into
+** "prepFlags" parameter of the [tdsqlite3_prepare_v3()] and
+** [tdsqlite3_prepare16_v3()] interfaces.
+**
+** New flags may be added in future releases of SQLite.
+**
+** <dl>
+** [[SQLITE_PREPARE_PERSISTENT]] ^(<dt>SQLITE_PREPARE_PERSISTENT</dt>
+** <dd>The SQLITE_PREPARE_PERSISTENT flag is a hint to the query planner
+** that the prepared statement will be retained for a long time and
+** probably reused many times.)^ ^Without this flag, [tdsqlite3_prepare_v3()]
+** and [tdsqlite3_prepare16_v3()] assume that the prepared statement will
+** be used just once or at most a few times and then destroyed using
+** [tdsqlite3_finalize()] relatively soon. The current implementation acts
+** on this hint by avoiding the use of [lookaside memory] so as not to
+** deplete the limited store of lookaside memory. Future versions of
+** SQLite may act on this hint differently.
+**
+** [[SQLITE_PREPARE_NORMALIZE]] <dt>SQLITE_PREPARE_NORMALIZE</dt>
+** <dd>The SQLITE_PREPARE_NORMALIZE flag is a no-op. This flag used
+** to be required for any prepared statement that wanted to use the
+** [tdsqlite3_normalized_sql()] interface. However, the
+** [tdsqlite3_normalized_sql()] interface is now available to all
+** prepared statements, regardless of whether or not they use this
+** flag.
+**
+** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
+** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
+** to return an error (error code SQLITE_ERROR) if the statement uses
+** any virtual tables.
+** </dl>
+*/
+#define SQLITE_PREPARE_PERSISTENT 0x01
+#define SQLITE_PREPARE_NORMALIZE 0x02
+#define SQLITE_PREPARE_NO_VTAB 0x04
+
+/*
** CAPI3REF: Compiling An SQL Statement
** KEYWORDS: {SQL statement compiler}
-** METHOD: sqlite3
-** CONSTRUCTOR: sqlite3_stmt
+** METHOD: tdsqlite3
+** CONSTRUCTOR: tdsqlite3_stmt
+**
+** To execute an SQL statement, it must first be compiled into a byte-code
+** program using one of these routines. Or, in other words, these routines
+** are constructors for the [prepared statement] object.
+**
+** The preferred routine to use is [tdsqlite3_prepare_v2()]. The
+** [tdsqlite3_prepare()] interface is legacy and should be avoided.
+** [tdsqlite3_prepare_v3()] has an extra "prepFlags" option that is used
+** for special purposes.
**
-** To execute an SQL query, it must first be compiled into a byte-code
-** program using one of these routines.
+** The use of the UTF-8 interfaces is preferred, as SQLite currently
+** does all parsing using UTF-8. The UTF-16 interfaces are provided
+** as a convenience. The UTF-16 interfaces work by converting the
+** input text into UTF-8, then invoking the corresponding UTF-8 interface.
**
** The first argument, "db", is a [database connection] obtained from a
-** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or
-** [sqlite3_open16()]. The database connection must not have been closed.
+** prior successful call to [tdsqlite3_open()], [tdsqlite3_open_v2()] or
+** [tdsqlite3_open16()]. The database connection must not have been closed.
**
** The second argument, "zSql", is the statement to be compiled, encoded
-** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2()
-** interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2()
-** use UTF-16.
+** as either UTF-8 or UTF-16. The tdsqlite3_prepare(), tdsqlite3_prepare_v2(),
+** and tdsqlite3_prepare_v3()
+** interfaces use UTF-8, and tdsqlite3_prepare16(), tdsqlite3_prepare16_v2(),
+** and tdsqlite3_prepare16_v3() use UTF-16.
**
** ^If the nByte argument is negative, then zSql is read up to the
** first zero terminator. ^If nByte is positive, then it is the
@@ -3716,129 +4939,160 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
** what remains uncompiled.
**
** ^*ppStmt is left pointing to a compiled [prepared statement] that can be
-** executed using [sqlite3_step()]. ^If there is an error, *ppStmt is set
+** executed using [tdsqlite3_step()]. ^If there is an error, *ppStmt is set
** to NULL. ^If the input text contains no SQL (if the input is an empty
** string or a comment) then *ppStmt is set to NULL.
** The calling procedure is responsible for deleting the compiled
-** SQL statement using [sqlite3_finalize()] after it has finished with it.
+** SQL statement using [tdsqlite3_finalize()] after it has finished with it.
** ppStmt may not be NULL.
**
-** ^On success, the sqlite3_prepare() family of routines return [SQLITE_OK];
+** ^On success, the tdsqlite3_prepare() family of routines return [SQLITE_OK];
** otherwise an [error code] is returned.
**
-** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are
-** recommended for all new programs. The two older interfaces are retained
-** for backwards compatibility, but their use is discouraged.
-** ^In the "v2" interfaces, the prepared statement
-** that is returned (the [sqlite3_stmt] object) contains a copy of the
-** original SQL text. This causes the [sqlite3_step()] interface to
+** The tdsqlite3_prepare_v2(), tdsqlite3_prepare_v3(), tdsqlite3_prepare16_v2(),
+** and tdsqlite3_prepare16_v3() interfaces are recommended for all new programs.
+** The older interfaces (tdsqlite3_prepare() and tdsqlite3_prepare16())
+** are retained for backwards compatibility, but their use is discouraged.
+** ^In the "vX" interfaces, the prepared statement
+** that is returned (the [tdsqlite3_stmt] object) contains a copy of the
+** original SQL text. This causes the [tdsqlite3_step()] interface to
** behave differently in three ways:
**
** <ol>
** <li>
** ^If the database schema changes, instead of returning [SQLITE_SCHEMA] as it
-** always used to do, [sqlite3_step()] will automatically recompile the SQL
+** always used to do, [tdsqlite3_step()] will automatically recompile the SQL
** statement and try to run it again. As many as [SQLITE_MAX_SCHEMA_RETRY]
-** retries will occur before sqlite3_step() gives up and returns an error.
+** retries will occur before tdsqlite3_step() gives up and returns an error.
** </li>
**
** <li>
-** ^When an error occurs, [sqlite3_step()] will return one of the detailed
+** ^When an error occurs, [tdsqlite3_step()] will return one of the detailed
** [error codes] or [extended error codes]. ^The legacy behavior was that
-** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code
-** and the application would have to make a second call to [sqlite3_reset()]
+** [tdsqlite3_step()] would only return a generic [SQLITE_ERROR] result code
+** and the application would have to make a second call to [tdsqlite3_reset()]
** in order to find the underlying cause of the problem. With the "v2" prepare
** interfaces, the underlying reason for the error is returned immediately.
** </li>
**
** <li>
-** ^If the specific value bound to [parameter | host parameter] in the
+** ^If the specific value bound to a [parameter | host parameter] in the
** WHERE clause might influence the choice of query plan for a statement,
** then the statement will be automatically recompiled, as if there had been
-** a schema change, on the first [sqlite3_step()] call following any change
-** to the [sqlite3_bind_text | bindings] of that [parameter].
-** ^The specific value of WHERE-clause [parameter] might influence the
+** a schema change, on the first [tdsqlite3_step()] call following any change
+** to the [tdsqlite3_bind_text | bindings] of that [parameter].
+** ^The specific value of a WHERE-clause [parameter] might influence the
** choice of query plan if the parameter is the left-hand side of a [LIKE]
** or [GLOB] operator or if the parameter is compared to an indexed column
-** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled.
+** and the [SQLITE_ENABLE_STAT4] compile-time option is enabled.
** </li>
** </ol>
+**
+** <p>^tdsqlite3_prepare_v3() differs from tdsqlite3_prepare_v2() only in having
+** the extra prepFlags parameter, which is a bit array consisting of zero or
+** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The
+** tdsqlite3_prepare_v2() interface works exactly the same as
+** tdsqlite3_prepare_v3() with a zero prepFlags parameter.
*/
-SQLITE_API int sqlite3_prepare(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_prepare(
+ tdsqlite3 *db, /* Database handle */
+ const char *zSql, /* SQL statement, UTF-8 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const char **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+SQLITE_API int tdsqlite3_prepare_v2(
+ tdsqlite3 *db, /* Database handle */
const char *zSql, /* SQL statement, UTF-8 encoded */
int nByte, /* Maximum length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
const char **pzTail /* OUT: Pointer to unused portion of zSql */
);
-SQLITE_API int sqlite3_prepare_v2(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_prepare_v3(
+ tdsqlite3 *db, /* Database handle */
const char *zSql, /* SQL statement, UTF-8 encoded */
int nByte, /* Maximum length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
const char **pzTail /* OUT: Pointer to unused portion of zSql */
);
-SQLITE_API int sqlite3_prepare16(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_prepare16(
+ tdsqlite3 *db, /* Database handle */
const void *zSql, /* SQL statement, UTF-16 encoded */
int nByte, /* Maximum length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
const void **pzTail /* OUT: Pointer to unused portion of zSql */
);
-SQLITE_API int sqlite3_prepare16_v2(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_prepare16_v2(
+ tdsqlite3 *db, /* Database handle */
const void *zSql, /* SQL statement, UTF-16 encoded */
int nByte, /* Maximum length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const void **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+SQLITE_API int tdsqlite3_prepare16_v3(
+ tdsqlite3 *db, /* Database handle */
+ const void *zSql, /* SQL statement, UTF-16 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
const void **pzTail /* OUT: Pointer to unused portion of zSql */
);
/*
** CAPI3REF: Retrieving Statement SQL
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_sql(P) interface returns a pointer to a copy of the UTF-8
+** ^The tdsqlite3_sql(P) interface returns a pointer to a copy of the UTF-8
** SQL text used to create [prepared statement] P if P was
-** created by either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()].
-** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8
+** created by [tdsqlite3_prepare_v2()], [tdsqlite3_prepare_v3()],
+** [tdsqlite3_prepare16_v2()], or [tdsqlite3_prepare16_v3()].
+** ^The tdsqlite3_expanded_sql(P) interface returns a pointer to a UTF-8
** string containing the SQL text of prepared statement P with
** [bound parameters] expanded.
+** ^The tdsqlite3_normalized_sql(P) interface returns a pointer to a UTF-8
+** string containing the normalized SQL text of prepared statement P. The
+** semantics used to normalize a SQL statement are unspecified and subject
+** to change. At a minimum, literal values will be replaced with suitable
+** placeholders.
**
** ^(For example, if a prepared statement is created using the SQL
** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345
-** and parameter :xyz is unbound, then sqlite3_sql() will return
-** the original string, "SELECT $abc,:xyz" but sqlite3_expanded_sql()
+** and parameter :xyz is unbound, then tdsqlite3_sql() will return
+** the original string, "SELECT $abc,:xyz" but tdsqlite3_expanded_sql()
** will return "SELECT 2345,NULL".)^
**
-** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory
+** ^The tdsqlite3_expanded_sql() interface returns NULL if insufficient memory
** is available to hold the result, or if the result would exceed the
** the maximum string length determined by the [SQLITE_LIMIT_LENGTH].
**
** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of
** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
-** option causes sqlite3_expanded_sql() to always return NULL.
+** option causes tdsqlite3_expanded_sql() to always return NULL.
**
-** ^The string returned by sqlite3_sql(P) is managed by SQLite and is
-** automatically freed when the prepared statement is finalized.
-** ^The string returned by sqlite3_expanded_sql(P), on the other hand,
-** is obtained from [sqlite3_malloc()] and must be free by the application
-** by passing it to [sqlite3_free()].
+** ^The strings returned by tdsqlite3_sql(P) and tdsqlite3_normalized_sql(P)
+** are managed by SQLite and are automatically freed when the prepared
+** statement is finalized.
+** ^The string returned by tdsqlite3_expanded_sql(P), on the other hand,
+** is obtained from [tdsqlite3_malloc()] and must be free by the application
+** by passing it to [tdsqlite3_free()].
*/
-SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt);
-SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt);
+SQLITE_API const char *tdsqlite3_sql(tdsqlite3_stmt *pStmt);
+SQLITE_API char *tdsqlite3_expanded_sql(tdsqlite3_stmt *pStmt);
+SQLITE_API const char *tdsqlite3_normalized_sql(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Determine If An SQL Statement Writes The Database
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if
+** ^The tdsqlite3_stmt_readonly(X) interface returns true (non-zero) if
** and only if the [prepared statement] X makes no direct changes to
** the content of the database file.
**
** Note that [application-defined SQL functions] or
** [virtual tables] might change the database indirectly as a side effect.
** ^(For example, if an application defines a function "eval()" that
-** calls [sqlite3_exec()], then the following SQL statement would
+** calls [tdsqlite3_exec()], then the following SQL statement would
** change the database file through side-effects:
**
** <blockquote><pre>
@@ -3846,102 +5100,119 @@ SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt);
** </pre></blockquote>
**
** But because the [SELECT] statement does not change the database file
-** directly, sqlite3_stmt_readonly() would still return true.)^
+** directly, tdsqlite3_stmt_readonly() would still return true.)^
**
** ^Transaction control statements such as [BEGIN], [COMMIT], [ROLLBACK],
-** [SAVEPOINT], and [RELEASE] cause sqlite3_stmt_readonly() to return true,
+** [SAVEPOINT], and [RELEASE] cause tdsqlite3_stmt_readonly() to return true,
** since the statements themselves do not actually modify the database but
** rather they control the timing of when other statements modify the
** database. ^The [ATTACH] and [DETACH] statements also cause
-** sqlite3_stmt_readonly() to return true since, while those statements
+** tdsqlite3_stmt_readonly() to return true since, while those statements
** change the configuration of a database connection, they do not make
** changes to the content of the database files on disk.
+** ^The tdsqlite3_stmt_readonly() interface returns true for [BEGIN] since
+** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and
+** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so
+** tdsqlite3_stmt_readonly() returns false for those commands.
+*/
+SQLITE_API int tdsqlite3_stmt_readonly(tdsqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Query The EXPLAIN Setting For A Prepared Statement
+** METHOD: tdsqlite3_stmt
+**
+** ^The tdsqlite3_stmt_isexplain(S) interface returns 1 if the
+** prepared statement S is an EXPLAIN statement, or 2 if the
+** statement S is an EXPLAIN QUERY PLAN.
+** ^The tdsqlite3_stmt_isexplain(S) interface returns 0 if S is
+** an ordinary statement or a NULL pointer.
*/
-SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_stmt_isexplain(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Determine If A Prepared Statement Has Been Reset
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the
+** ^The tdsqlite3_stmt_busy(S) interface returns true (non-zero) if the
** [prepared statement] S has been stepped at least once using
-** [sqlite3_step(S)] but has neither run to completion (returned
-** [SQLITE_DONE] from [sqlite3_step(S)]) nor
-** been reset using [sqlite3_reset(S)]. ^The sqlite3_stmt_busy(S)
+** [tdsqlite3_step(S)] but has neither run to completion (returned
+** [SQLITE_DONE] from [tdsqlite3_step(S)]) nor
+** been reset using [tdsqlite3_reset(S)]. ^The tdsqlite3_stmt_busy(S)
** interface returns false if S is a NULL pointer. If S is not a
** NULL pointer and is not a pointer to a valid [prepared statement]
** object, then the behavior is undefined and probably undesirable.
**
-** This interface can be used in combination [sqlite3_next_stmt()]
+** This interface can be used in combination [tdsqlite3_next_stmt()]
** to locate all prepared statements associated with a database
** connection that are in need of being reset. This can be used,
** for example, in diagnostic routines to search for prepared
** statements that are holding a transaction open.
*/
-SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*);
+SQLITE_API int tdsqlite3_stmt_busy(tdsqlite3_stmt*);
/*
** CAPI3REF: Dynamically Typed Value Object
-** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value}
+** KEYWORDS: {protected tdsqlite3_value} {unprotected tdsqlite3_value}
**
-** SQLite uses the sqlite3_value object to represent all values
+** SQLite uses the tdsqlite3_value object to represent all values
** that can be stored in a database table. SQLite uses dynamic typing
-** for the values it stores. ^Values stored in sqlite3_value objects
+** for the values it stores. ^Values stored in tdsqlite3_value objects
** can be integers, floating point values, strings, BLOBs, or NULL.
**
-** An sqlite3_value object may be either "protected" or "unprotected".
-** Some interfaces require a protected sqlite3_value. Other interfaces
-** will accept either a protected or an unprotected sqlite3_value.
-** Every interface that accepts sqlite3_value arguments specifies
-** whether or not it requires a protected sqlite3_value. The
-** [sqlite3_value_dup()] interface can be used to construct a new
-** protected sqlite3_value from an unprotected sqlite3_value.
+** An tdsqlite3_value object may be either "protected" or "unprotected".
+** Some interfaces require a protected tdsqlite3_value. Other interfaces
+** will accept either a protected or an unprotected tdsqlite3_value.
+** Every interface that accepts tdsqlite3_value arguments specifies
+** whether or not it requires a protected tdsqlite3_value. The
+** [tdsqlite3_value_dup()] interface can be used to construct a new
+** protected tdsqlite3_value from an unprotected tdsqlite3_value.
**
** The terms "protected" and "unprotected" refer to whether or not
** a mutex is held. An internal mutex is held for a protected
-** sqlite3_value object but no mutex is held for an unprotected
-** sqlite3_value object. If SQLite is compiled to be single-threaded
-** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0)
+** tdsqlite3_value object but no mutex is held for an unprotected
+** tdsqlite3_value object. If SQLite is compiled to be single-threaded
+** (with [SQLITE_THREADSAFE=0] and with [tdsqlite3_threadsafe()] returning 0)
** or if SQLite is run in one of reduced mutex modes
** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD]
** then there is no distinction between protected and unprotected
-** sqlite3_value objects and they can be used interchangeably. However,
+** tdsqlite3_value objects and they can be used interchangeably. However,
** for maximum code portability it is recommended that applications
** still make the distinction between protected and unprotected
-** sqlite3_value objects even when not strictly required.
+** tdsqlite3_value objects even when not strictly required.
**
-** ^The sqlite3_value objects that are passed as parameters into the
+** ^The tdsqlite3_value objects that are passed as parameters into the
** implementation of [application-defined SQL functions] are protected.
-** ^The sqlite3_value object returned by
-** [sqlite3_column_value()] is unprotected.
-** Unprotected sqlite3_value objects may only be used with
-** [sqlite3_result_value()] and [sqlite3_bind_value()].
-** The [sqlite3_value_blob | sqlite3_value_type()] family of
-** interfaces require protected sqlite3_value objects.
+** ^The tdsqlite3_value object returned by
+** [tdsqlite3_column_value()] is unprotected.
+** Unprotected tdsqlite3_value objects may only be used as arguments
+** to [tdsqlite3_result_value()], [tdsqlite3_bind_value()], and
+** [tdsqlite3_value_dup()].
+** The [tdsqlite3_value_blob | tdsqlite3_value_type()] family of
+** interfaces require protected tdsqlite3_value objects.
*/
-typedef struct Mem sqlite3_value;
+typedef struct tdsqlite3_value tdsqlite3_value;
/*
** CAPI3REF: SQL Function Context Object
**
** The context in which an SQL function executes is stored in an
-** sqlite3_context object. ^A pointer to an sqlite3_context object
+** tdsqlite3_context object. ^A pointer to an tdsqlite3_context object
** is always first parameter to [application-defined SQL functions].
** The application-defined SQL function implementation will pass this
-** pointer through into calls to [sqlite3_result_int | sqlite3_result()],
-** [sqlite3_aggregate_context()], [sqlite3_user_data()],
-** [sqlite3_context_db_handle()], [sqlite3_get_auxdata()],
-** and/or [sqlite3_set_auxdata()].
+** pointer through into calls to [tdsqlite3_result_int | tdsqlite3_result()],
+** [tdsqlite3_aggregate_context()], [tdsqlite3_user_data()],
+** [tdsqlite3_context_db_handle()], [tdsqlite3_get_auxdata()],
+** and/or [tdsqlite3_set_auxdata()].
*/
-typedef struct sqlite3_context sqlite3_context;
+typedef struct tdsqlite3_context tdsqlite3_context;
/*
** CAPI3REF: Binding Values To Prepared Statements
** KEYWORDS: {host parameter} {host parameters} {host parameter name}
** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding}
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
+** ^(In the SQL statement text input to [tdsqlite3_prepare_v2()] and its variants,
** literals may be replaced by a [parameter] that matches one of following
** templates:
**
@@ -3956,37 +5227,37 @@ typedef struct sqlite3_context sqlite3_context;
** In the templates above, NNN represents an integer literal,
** and VVV represents an alphanumeric identifier.)^ ^The values of these
** parameters (also called "host parameter names" or "SQL parameters")
-** can be set using the sqlite3_bind_*() routines defined here.
+** can be set using the tdsqlite3_bind_*() routines defined here.
**
-** ^The first argument to the sqlite3_bind_*() routines is always
-** a pointer to the [sqlite3_stmt] object returned from
-** [sqlite3_prepare_v2()] or its variants.
+** ^The first argument to the tdsqlite3_bind_*() routines is always
+** a pointer to the [tdsqlite3_stmt] object returned from
+** [tdsqlite3_prepare_v2()] or its variants.
**
** ^The second argument is the index of the SQL parameter to be set.
** ^The leftmost SQL parameter has an index of 1. ^When the same named
** SQL parameter is used more than once, second and subsequent
** occurrences have the same index as the first occurrence.
** ^The index for named parameters can be looked up using the
-** [sqlite3_bind_parameter_index()] API if desired. ^The index
+** [tdsqlite3_bind_parameter_index()] API if desired. ^The index
** for "?NNN" parameters is the value of NNN.
-** ^The NNN value must be between 1 and the [sqlite3_limit()]
+** ^The NNN value must be between 1 and the [tdsqlite3_limit()]
** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 999).
**
** ^The third argument is the value to bind to the parameter.
-** ^If the third parameter to sqlite3_bind_text() or sqlite3_bind_text16()
-** or sqlite3_bind_blob() is a NULL pointer then the fourth parameter
-** is ignored and the end result is the same as sqlite3_bind_null().
+** ^If the third parameter to tdsqlite3_bind_text() or tdsqlite3_bind_text16()
+** or tdsqlite3_bind_blob() is a NULL pointer then the fourth parameter
+** is ignored and the end result is the same as tdsqlite3_bind_null().
**
** ^(In those routines that have a fourth argument, its value is the
** number of bytes in the parameter. To be clear: the value is the
** number of <u>bytes</u> in the value, not the number of characters.)^
-** ^If the fourth parameter to sqlite3_bind_text() or sqlite3_bind_text16()
+** ^If the fourth parameter to tdsqlite3_bind_text() or tdsqlite3_bind_text16()
** is negative, then the length of the string is
** the number of bytes up to the first zero terminator.
-** If the fourth parameter to sqlite3_bind_blob() is negative, then
+** If the fourth parameter to tdsqlite3_bind_blob() is negative, then
** the behavior is undefined.
-** If a non-negative fourth parameter is provided to sqlite3_bind_text()
-** or sqlite3_bind_text16() or sqlite3_bind_text64() then
+** If a non-negative fourth parameter is provided to tdsqlite3_bind_text()
+** or tdsqlite3_bind_text16() or tdsqlite3_bind_text64() then
** that parameter must be the byte offset
** where the NUL terminator would occur assuming the string were NUL
** terminated. If any NUL characters occur at byte offsets less than
@@ -3997,74 +5268,86 @@ typedef struct sqlite3_context sqlite3_context;
** ^The fifth argument to the BLOB and string binding interfaces
** is a destructor used to dispose of the BLOB or
** string after SQLite has finished with it. ^The destructor is called
-** to dispose of the BLOB or string even if the call to bind API fails.
+** to dispose of the BLOB or string even if the call to the bind API fails,
+** except the destructor is not called if the third parameter is a NULL
+** pointer or the fourth parameter is negative.
** ^If the fifth argument is
** the special value [SQLITE_STATIC], then SQLite assumes that the
** information is in static, unmanaged space and does not need to be freed.
** ^If the fifth argument has the value [SQLITE_TRANSIENT], then
** SQLite makes its own private copy of the data immediately, before
-** the sqlite3_bind_*() routine returns.
+** the tdsqlite3_bind_*() routine returns.
**
-** ^The sixth argument to sqlite3_bind_text64() must be one of
+** ^The sixth argument to tdsqlite3_bind_text64() must be one of
** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]
** to specify the encoding of the text in the third parameter. If
-** the sixth argument to sqlite3_bind_text64() is not one of the
+** the sixth argument to tdsqlite3_bind_text64() is not one of the
** allowed values shown above, or if the text encoding is different
** from the encoding specified by the sixth parameter, then the behavior
** is undefined.
**
-** ^The sqlite3_bind_zeroblob() routine binds a BLOB of length N that
+** ^The tdsqlite3_bind_zeroblob() routine binds a BLOB of length N that
** is filled with zeroes. ^A zeroblob uses a fixed amount of memory
** (just an integer to hold its size) while it is being processed.
** Zeroblobs are intended to serve as placeholders for BLOBs whose
** content is later written using
-** [sqlite3_blob_open | incremental BLOB I/O] routines.
+** [tdsqlite3_blob_open | incremental BLOB I/O] routines.
** ^A negative value for the zeroblob results in a zero-length BLOB.
**
-** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer
+** ^The tdsqlite3_bind_pointer(S,I,P,T,D) routine causes the I-th parameter in
+** [prepared statement] S to have an SQL value of NULL, but to also be
+** associated with the pointer P of type T. ^D is either a NULL pointer or
+** a pointer to a destructor function for P. ^SQLite will invoke the
+** destructor D with a single argument of P when it is finished using
+** P. The T parameter should be a static string, preferably a string
+** literal. The tdsqlite3_bind_pointer() routine is part of the
+** [pointer passing interface] added for SQLite 3.20.0.
+**
+** ^If any of the tdsqlite3_bind_*() routines are called with a NULL pointer
** for the [prepared statement] or with a prepared statement for which
-** [sqlite3_step()] has been called more recently than [sqlite3_reset()],
-** then the call will return [SQLITE_MISUSE]. If any sqlite3_bind_()
+** [tdsqlite3_step()] has been called more recently than [tdsqlite3_reset()],
+** then the call will return [SQLITE_MISUSE]. If any tdsqlite3_bind_()
** routine is passed a [prepared statement] that has been finalized, the
** result is undefined and probably harmful.
**
-** ^Bindings are not cleared by the [sqlite3_reset()] routine.
+** ^Bindings are not cleared by the [tdsqlite3_reset()] routine.
** ^Unbound parameters are interpreted as NULL.
**
-** ^The sqlite3_bind_* routines return [SQLITE_OK] on success or an
+** ^The tdsqlite3_bind_* routines return [SQLITE_OK] on success or an
** [error code] if anything goes wrong.
** ^[SQLITE_TOOBIG] might be returned if the size of a string or BLOB
-** exceeds limits imposed by [sqlite3_limit]([SQLITE_LIMIT_LENGTH]) or
+** exceeds limits imposed by [tdsqlite3_limit]([SQLITE_LIMIT_LENGTH]) or
** [SQLITE_MAX_LENGTH].
** ^[SQLITE_RANGE] is returned if the parameter
** index is out of range. ^[SQLITE_NOMEM] is returned if malloc() fails.
**
-** See also: [sqlite3_bind_parameter_count()],
-** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()].
+** See also: [tdsqlite3_bind_parameter_count()],
+** [tdsqlite3_bind_parameter_name()], and [tdsqlite3_bind_parameter_index()].
*/
-SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
-SQLITE_API int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64,
+SQLITE_API int tdsqlite3_bind_blob(tdsqlite3_stmt*, int, const void*, int n, void(*)(void*));
+SQLITE_API int tdsqlite3_bind_blob64(tdsqlite3_stmt*, int, const void*, tdsqlite3_uint64,
void(*)(void*));
-SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double);
-SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int);
-SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
-SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int);
-SQLITE_API int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*));
-SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));
-SQLITE_API int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64,
+SQLITE_API int tdsqlite3_bind_double(tdsqlite3_stmt*, int, double);
+SQLITE_API int tdsqlite3_bind_int(tdsqlite3_stmt*, int, int);
+SQLITE_API int tdsqlite3_bind_int64(tdsqlite3_stmt*, int, tdsqlite3_int64);
+SQLITE_API int tdsqlite3_bind_null(tdsqlite3_stmt*, int);
+SQLITE_API int tdsqlite3_bind_text(tdsqlite3_stmt*,int,const char*,int,void(*)(void*));
+SQLITE_API int tdsqlite3_bind_text16(tdsqlite3_stmt*, int, const void*, int, void(*)(void*));
+SQLITE_API int tdsqlite3_bind_text64(tdsqlite3_stmt*, int, const char*, tdsqlite3_uint64,
void(*)(void*), unsigned char encoding);
-SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
-SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
-SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64);
+SQLITE_API int tdsqlite3_bind_value(tdsqlite3_stmt*, int, const tdsqlite3_value*);
+SQLITE_API int tdsqlite3_bind_pointer(tdsqlite3_stmt*, int, void*, const char*,void(*)(void*));
+SQLITE_API int tdsqlite3_bind_zeroblob(tdsqlite3_stmt*, int, int n);
+SQLITE_API int tdsqlite3_bind_zeroblob64(tdsqlite3_stmt*, int, tdsqlite3_uint64);
/*
** CAPI3REF: Number Of SQL Parameters
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^This routine can be used to find the number of [SQL parameters]
** in a [prepared statement]. SQL parameters are tokens of the
** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as
-** placeholders for values that are [sqlite3_bind_blob | bound]
+** placeholders for values that are [tdsqlite3_bind_blob | bound]
** to the parameters at a later time.
**
** ^(This routine actually returns the index of the largest (rightmost)
@@ -4072,17 +5355,17 @@ SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64);
** number of unique parameters. If parameters of the ?NNN form are used,
** there may be gaps in the list.)^
**
-** See also: [sqlite3_bind_blob|sqlite3_bind()],
-** [sqlite3_bind_parameter_name()], and
-** [sqlite3_bind_parameter_index()].
+** See also: [tdsqlite3_bind_blob|tdsqlite3_bind()],
+** [tdsqlite3_bind_parameter_name()], and
+** [tdsqlite3_bind_parameter_index()].
*/
-SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*);
+SQLITE_API int tdsqlite3_bind_parameter_count(tdsqlite3_stmt*);
/*
** CAPI3REF: Name Of A Host Parameter
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_bind_parameter_name(P,N) interface returns
+** ^The tdsqlite3_bind_parameter_name(P,N) interface returns
** the name of the N-th [SQL parameter] in the [prepared statement] P.
** ^(SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA"
** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA"
@@ -4097,73 +5380,78 @@ SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*);
** ^If the value N is out of range or if the N-th parameter is
** nameless, then NULL is returned. ^The returned string is
** always in UTF-8 encoding even if the named parameter was
-** originally specified as UTF-16 in [sqlite3_prepare16()] or
-** [sqlite3_prepare16_v2()].
+** originally specified as UTF-16 in [tdsqlite3_prepare16()],
+** [tdsqlite3_prepare16_v2()], or [tdsqlite3_prepare16_v3()].
**
-** See also: [sqlite3_bind_blob|sqlite3_bind()],
-** [sqlite3_bind_parameter_count()], and
-** [sqlite3_bind_parameter_index()].
+** See also: [tdsqlite3_bind_blob|tdsqlite3_bind()],
+** [tdsqlite3_bind_parameter_count()], and
+** [tdsqlite3_bind_parameter_index()].
*/
-SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int);
+SQLITE_API const char *tdsqlite3_bind_parameter_name(tdsqlite3_stmt*, int);
/*
** CAPI3REF: Index Of A Parameter With A Given Name
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^Return the index of an SQL parameter given its name. ^The
** index value returned is suitable for use as the second
-** parameter to [sqlite3_bind_blob|sqlite3_bind()]. ^A zero
+** parameter to [tdsqlite3_bind_blob|tdsqlite3_bind()]. ^A zero
** is returned if no matching parameter is found. ^The parameter
** name must be given in UTF-8 even if the original statement
-** was prepared from UTF-16 text using [sqlite3_prepare16_v2()].
+** was prepared from UTF-16 text using [tdsqlite3_prepare16_v2()] or
+** [tdsqlite3_prepare16_v3()].
**
-** See also: [sqlite3_bind_blob|sqlite3_bind()],
-** [sqlite3_bind_parameter_count()], and
-** [sqlite3_bind_parameter_name()].
+** See also: [tdsqlite3_bind_blob|tdsqlite3_bind()],
+** [tdsqlite3_bind_parameter_count()], and
+** [tdsqlite3_bind_parameter_name()].
*/
-SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
+SQLITE_API int tdsqlite3_bind_parameter_index(tdsqlite3_stmt*, const char *zName);
/*
** CAPI3REF: Reset All Bindings On A Prepared Statement
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^Contrary to the intuition of many, [sqlite3_reset()] does not reset
-** the [sqlite3_bind_blob | bindings] on a [prepared statement].
+** ^Contrary to the intuition of many, [tdsqlite3_reset()] does not reset
+** the [tdsqlite3_bind_blob | bindings] on a [prepared statement].
** ^Use this routine to reset all host parameters to NULL.
*/
-SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*);
+SQLITE_API int tdsqlite3_clear_bindings(tdsqlite3_stmt*);
/*
** CAPI3REF: Number Of Columns In A Result Set
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^Return the number of columns in the result set returned by the
-** [prepared statement]. ^This routine returns 0 if pStmt is an SQL
-** statement that does not return data (for example an [UPDATE]).
+** [prepared statement]. ^If this routine returns 0, that means the
+** [prepared statement] returns no data (for example an [UPDATE]).
+** ^However, just because this routine returns a positive number does not
+** mean that one or more rows of data will be returned. ^A SELECT statement
+** will always have a positive tdsqlite3_column_count() but depending on the
+** WHERE clause constraints and the table content, it might return no rows.
**
-** See also: [sqlite3_data_count()]
+** See also: [tdsqlite3_data_count()]
*/
-SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_column_count(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Column Names In A Result Set
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^These routines return the name assigned to a particular column
-** in the result set of a [SELECT] statement. ^The sqlite3_column_name()
+** in the result set of a [SELECT] statement. ^The tdsqlite3_column_name()
** interface returns a pointer to a zero-terminated UTF-8 string
-** and sqlite3_column_name16() returns a pointer to a zero-terminated
+** and tdsqlite3_column_name16() returns a pointer to a zero-terminated
** UTF-16 string. ^The first parameter is the [prepared statement]
** that implements the [SELECT] statement. ^The second parameter is the
** column number. ^The leftmost column is number 0.
**
** ^The returned string pointer is valid until either the [prepared statement]
-** is destroyed by [sqlite3_finalize()] or until the statement is automatically
-** reprepared by the first call to [sqlite3_step()] for a particular run
+** is destroyed by [tdsqlite3_finalize()] or until the statement is automatically
+** reprepared by the first call to [tdsqlite3_step()] for a particular run
** or until the next call to
-** sqlite3_column_name() or sqlite3_column_name16() on the same column.
+** tdsqlite3_column_name() or tdsqlite3_column_name16() on the same column.
**
-** ^If sqlite3_malloc() fails during the processing of either routine
+** ^If tdsqlite3_malloc() fails during the processing of either routine
** (for example during a conversion from UTF-8 to UTF-16) then a
** NULL pointer is returned.
**
@@ -4172,12 +5460,12 @@ SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt);
** then the name of the column is unspecified and may change from
** one release of SQLite to the next.
*/
-SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N);
-SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
+SQLITE_API const char *tdsqlite3_column_name(tdsqlite3_stmt*, int N);
+SQLITE_API const void *tdsqlite3_column_name16(tdsqlite3_stmt*, int N);
/*
** CAPI3REF: Source Of Data In A Query Result
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^These routines provide a means to determine the database, table, and
** table column that is the origin of a particular result column in
@@ -4187,8 +5475,8 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
** the database name, the _table_ routines return the table name, and
** the origin_ routines return the column name.
** ^The returned string is valid until the [prepared statement] is destroyed
-** using [sqlite3_finalize()] or until the statement is automatically
-** reprepared by the first call to [sqlite3_step()] for a particular run
+** using [tdsqlite3_finalize()] or until the statement is automatically
+** reprepared by the first call to [tdsqlite3_step()] for a particular run
** or until the same information is requested
** again in a different encoding.
**
@@ -4202,7 +5490,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
**
** ^If the Nth column returned by the statement is an expression or
** subquery and is not a column value, then all of these functions return
-** NULL. ^These routine might also return NULL if a memory allocation error
+** NULL. ^These routines might also return NULL if a memory allocation error
** occurs. ^Otherwise, they return the name of the attached database, table,
** or column that query result column was extracted from.
**
@@ -4212,25 +5500,21 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
** ^These APIs are only available if the library was compiled with the
** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol.
**
-** If two or more threads call one or more of these routines against the same
-** prepared statement and column at the same time then the results are
-** undefined.
-**
** If two or more threads call one or more
-** [sqlite3_column_database_name | column metadata interfaces]
+** [tdsqlite3_column_database_name | column metadata interfaces]
** for the same [prepared statement] and result column
** at the same time then the results are undefined.
*/
-SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int);
-SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int);
-SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int);
-SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int);
-SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int);
-SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int);
+SQLITE_API const char *tdsqlite3_column_database_name(tdsqlite3_stmt*,int);
+SQLITE_API const void *tdsqlite3_column_database_name16(tdsqlite3_stmt*,int);
+SQLITE_API const char *tdsqlite3_column_table_name(tdsqlite3_stmt*,int);
+SQLITE_API const void *tdsqlite3_column_table_name16(tdsqlite3_stmt*,int);
+SQLITE_API const char *tdsqlite3_column_origin_name(tdsqlite3_stmt*,int);
+SQLITE_API const void *tdsqlite3_column_origin_name16(tdsqlite3_stmt*,int);
/*
** CAPI3REF: Declared Datatype Of A Query Result
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^(The first parameter is a [prepared statement].
** If this statement is a [SELECT] statement and the Nth column of the
@@ -4258,23 +5542,25 @@ SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int);
** is associated with individual values, not with the containers
** used to hold those values.
*/
-SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int);
-SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
+SQLITE_API const char *tdsqlite3_column_decltype(tdsqlite3_stmt*,int);
+SQLITE_API const void *tdsqlite3_column_decltype16(tdsqlite3_stmt*,int);
/*
** CAPI3REF: Evaluate An SQL Statement
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** After a [prepared statement] has been prepared using either
-** [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or one of the legacy
-** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function
+** After a [prepared statement] has been prepared using any of
+** [tdsqlite3_prepare_v2()], [tdsqlite3_prepare_v3()], [tdsqlite3_prepare16_v2()],
+** or [tdsqlite3_prepare16_v3()] or one of the legacy
+** interfaces [tdsqlite3_prepare()] or [tdsqlite3_prepare16()], this function
** must be called one or more times to evaluate the statement.
**
-** The details of the behavior of the sqlite3_step() interface depend
-** on whether the statement was prepared using the newer "v2" interface
-** [sqlite3_prepare_v2()] and [sqlite3_prepare16_v2()] or the older legacy
-** interface [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the
-** new "v2" interface is recommended for new applications but the legacy
+** The details of the behavior of the tdsqlite3_step() interface depend
+** on whether the statement was prepared using the newer "vX" interfaces
+** [tdsqlite3_prepare_v3()], [tdsqlite3_prepare_v2()], [tdsqlite3_prepare16_v3()],
+** [tdsqlite3_prepare16_v2()] or the older legacy
+** interfaces [tdsqlite3_prepare()] and [tdsqlite3_prepare16()]. The use of the
+** new "vX" interface is recommended for new applications but the legacy
** interface will continue to be supported.
**
** ^In the legacy interface, the return value will be either [SQLITE_BUSY],
@@ -4290,78 +5576,79 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
** continuing.
**
** ^[SQLITE_DONE] means that the statement has finished executing
-** successfully. sqlite3_step() should not be called again on this virtual
-** machine without first calling [sqlite3_reset()] to reset the virtual
+** successfully. tdsqlite3_step() should not be called again on this virtual
+** machine without first calling [tdsqlite3_reset()] to reset the virtual
** machine back to its initial state.
**
** ^If the SQL statement being executed returns any data, then [SQLITE_ROW]
** is returned each time a new row of data is ready for processing by the
** caller. The values may be accessed using the [column access functions].
-** sqlite3_step() is called again to retrieve the next row of data.
+** tdsqlite3_step() is called again to retrieve the next row of data.
**
** ^[SQLITE_ERROR] means that a run-time error (such as a constraint
-** violation) has occurred. sqlite3_step() should not be called again on
-** the VM. More information may be found by calling [sqlite3_errmsg()].
+** violation) has occurred. tdsqlite3_step() should not be called again on
+** the VM. More information may be found by calling [tdsqlite3_errmsg()].
** ^With the legacy interface, a more specific error code (for example,
** [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth)
-** can be obtained by calling [sqlite3_reset()] on the
+** can be obtained by calling [tdsqlite3_reset()] on the
** [prepared statement]. ^In the "v2" interface,
-** the more specific error code is returned directly by sqlite3_step().
+** the more specific error code is returned directly by tdsqlite3_step().
**
** [SQLITE_MISUSE] means that the this routine was called inappropriately.
** Perhaps it was called on a [prepared statement] that has
-** already been [sqlite3_finalize | finalized] or on one that had
+** already been [tdsqlite3_finalize | finalized] or on one that had
** previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could
** be the case that the same database connection is being used by two or
** more threads at the same moment in time.
**
** For all versions of SQLite up to and including 3.6.23.1, a call to
-** [sqlite3_reset()] was required after sqlite3_step() returned anything
+** [tdsqlite3_reset()] was required after tdsqlite3_step() returned anything
** other than [SQLITE_ROW] before any subsequent invocation of
-** sqlite3_step(). Failure to reset the prepared statement using
-** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
-** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
-** sqlite3_step() began
-** calling [sqlite3_reset()] automatically in this circumstance rather
+** tdsqlite3_step(). Failure to reset the prepared statement using
+** [tdsqlite3_reset()] would result in an [SQLITE_MISUSE] return from
+** tdsqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
+** tdsqlite3_step() began
+** calling [tdsqlite3_reset()] automatically in this circumstance rather
** than returning [SQLITE_MISUSE]. This is not considered a compatibility
** break because any application that ever receives an SQLITE_MISUSE error
** is broken by definition. The [SQLITE_OMIT_AUTORESET] compile-time option
** can be used to restore the legacy behavior.
**
-** <b>Goofy Interface Alert:</b> In the legacy interface, the sqlite3_step()
+** <b>Goofy Interface Alert:</b> In the legacy interface, the tdsqlite3_step()
** API always returns a generic error code, [SQLITE_ERROR], following any
** error other than [SQLITE_BUSY] and [SQLITE_MISUSE]. You must call
-** [sqlite3_reset()] or [sqlite3_finalize()] in order to find one of the
+** [tdsqlite3_reset()] or [tdsqlite3_finalize()] in order to find one of the
** specific [error codes] that better describes the error.
** We admit that this is a goofy design. The problem has been fixed
** with the "v2" interface. If you prepare all of your SQL statements
-** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead
-** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces,
+** using [tdsqlite3_prepare_v3()] or [tdsqlite3_prepare_v2()]
+** or [tdsqlite3_prepare16_v2()] or [tdsqlite3_prepare16_v3()] instead
+** of the legacy [tdsqlite3_prepare()] and [tdsqlite3_prepare16()] interfaces,
** then the more specific [error codes] are returned directly
-** by sqlite3_step(). The use of the "v2" interface is recommended.
+** by tdsqlite3_step(). The use of the "vX" interfaces is recommended.
*/
-SQLITE_API int sqlite3_step(sqlite3_stmt*);
+SQLITE_API int tdsqlite3_step(tdsqlite3_stmt*);
/*
** CAPI3REF: Number of columns in a result set
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_data_count(P) interface returns the number of columns in the
+** ^The tdsqlite3_data_count(P) interface returns the number of columns in the
** current row of the result set of [prepared statement] P.
** ^If prepared statement P does not have results ready to return
-** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of
-** interfaces) then sqlite3_data_count(P) returns 0.
-** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer.
-** ^The sqlite3_data_count(P) routine returns 0 if the previous call to
-** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P)
-** will return non-zero if previous call to [sqlite3_step](P) returned
+** (via calls to the [tdsqlite3_column_int | tdsqlite3_column()] family of
+** interfaces) then tdsqlite3_data_count(P) returns 0.
+** ^The tdsqlite3_data_count(P) routine also returns 0 if P is a NULL pointer.
+** ^The tdsqlite3_data_count(P) routine returns 0 if the previous call to
+** [tdsqlite3_step](P) returned [SQLITE_DONE]. ^The tdsqlite3_data_count(P)
+** will return non-zero if previous call to [tdsqlite3_step](P) returned
** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum]
** where it always returns zero since each step of that multi-step
** pragma returns 0 columns of data.
**
-** See also: [sqlite3_column_count()]
+** See also: [tdsqlite3_column_count()]
*/
-SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_data_count(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Fundamental Datatypes
@@ -4398,79 +5685,118 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
/*
** CAPI3REF: Result Values From A Query
** KEYWORDS: {column access functions}
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
+**
+** <b>Summary:</b>
+** <blockquote><table border=0 cellpadding=0 cellspacing=0>
+** <tr><td><b>tdsqlite3_column_blob</b><td>&rarr;<td>BLOB result
+** <tr><td><b>tdsqlite3_column_double</b><td>&rarr;<td>REAL result
+** <tr><td><b>tdsqlite3_column_int</b><td>&rarr;<td>32-bit INTEGER result
+** <tr><td><b>tdsqlite3_column_int64</b><td>&rarr;<td>64-bit INTEGER result
+** <tr><td><b>tdsqlite3_column_text</b><td>&rarr;<td>UTF-8 TEXT result
+** <tr><td><b>tdsqlite3_column_text16</b><td>&rarr;<td>UTF-16 TEXT result
+** <tr><td><b>tdsqlite3_column_value</b><td>&rarr;<td>The result as an
+** [tdsqlite3_value|unprotected tdsqlite3_value] object.
+** <tr><td>&nbsp;<td>&nbsp;<td>&nbsp;
+** <tr><td><b>tdsqlite3_column_bytes</b><td>&rarr;<td>Size of a BLOB
+** or a UTF-8 TEXT result in bytes
+** <tr><td><b>tdsqlite3_column_bytes16&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>Size of UTF-16
+** TEXT in bytes
+** <tr><td><b>tdsqlite3_column_type</b><td>&rarr;<td>Default
+** datatype of the result
+** </table></blockquote>
+**
+** <b>Details:</b>
**
** ^These routines return information about a single column of the current
** result row of a query. ^In every case the first argument is a pointer
-** to the [prepared statement] that is being evaluated (the [sqlite3_stmt*]
-** that was returned from [sqlite3_prepare_v2()] or one of its variants)
+** to the [prepared statement] that is being evaluated (the [tdsqlite3_stmt*]
+** that was returned from [tdsqlite3_prepare_v2()] or one of its variants)
** and the second argument is the index of the column for which information
** should be returned. ^The leftmost column of the result set has the index 0.
** ^The number of columns in the result can be determined using
-** [sqlite3_column_count()].
+** [tdsqlite3_column_count()].
**
** If the SQL statement does not currently point to a valid row, or if the
** column index is out of range, the result is undefined.
** These routines may only be called when the most recent call to
-** [sqlite3_step()] has returned [SQLITE_ROW] and neither
-** [sqlite3_reset()] nor [sqlite3_finalize()] have been called subsequently.
-** If any of these routines are called after [sqlite3_reset()] or
-** [sqlite3_finalize()] or after [sqlite3_step()] has returned
+** [tdsqlite3_step()] has returned [SQLITE_ROW] and neither
+** [tdsqlite3_reset()] nor [tdsqlite3_finalize()] have been called subsequently.
+** If any of these routines are called after [tdsqlite3_reset()] or
+** [tdsqlite3_finalize()] or after [tdsqlite3_step()] has returned
** something other than [SQLITE_ROW], the results are undefined.
-** If [sqlite3_step()] or [sqlite3_reset()] or [sqlite3_finalize()]
+** If [tdsqlite3_step()] or [tdsqlite3_reset()] or [tdsqlite3_finalize()]
** are called from a different thread while any of these routines
** are pending, then the results are undefined.
**
-** ^The sqlite3_column_type() routine returns the
+** The first six interfaces (_blob, _double, _int, _int64, _text, and _text16)
+** each return the value of a result column in a specific data format. If
+** the result column is not initially in the requested format (for example,
+** if the query returns an integer but the tdsqlite3_column_text() interface
+** is used to extract the value) then an automatic type conversion is performed.
+**
+** ^The tdsqlite3_column_type() routine returns the
** [SQLITE_INTEGER | datatype code] for the initial data type
** of the result column. ^The returned value is one of [SQLITE_INTEGER],
-** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. The value
-** returned by sqlite3_column_type() is only meaningful if no type
-** conversions have occurred as described below. After a type conversion,
-** the value returned by sqlite3_column_type() is undefined. Future
-** versions of SQLite may change the behavior of sqlite3_column_type()
+** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL].
+** The return value of tdsqlite3_column_type() can be used to decide which
+** of the first six interface should be used to extract the column value.
+** The value returned by tdsqlite3_column_type() is only meaningful if no
+** automatic type conversions have occurred for the value in question.
+** After a type conversion, the result of calling tdsqlite3_column_type()
+** is undefined, though harmless. Future
+** versions of SQLite may change the behavior of tdsqlite3_column_type()
** following a type conversion.
**
-** ^If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes()
+** If the result is a BLOB or a TEXT string, then the tdsqlite3_column_bytes()
+** or tdsqlite3_column_bytes16() interfaces can be used to determine the size
+** of that BLOB or string.
+**
+** ^If the result is a BLOB or UTF-8 string then the tdsqlite3_column_bytes()
** routine returns the number of bytes in that BLOB or string.
-** ^If the result is a UTF-16 string, then sqlite3_column_bytes() converts
+** ^If the result is a UTF-16 string, then tdsqlite3_column_bytes() converts
** the string to UTF-8 and then returns the number of bytes.
-** ^If the result is a numeric value then sqlite3_column_bytes() uses
-** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns
+** ^If the result is a numeric value then tdsqlite3_column_bytes() uses
+** [tdsqlite3_snprintf()] to convert that value to a UTF-8 string and returns
** the number of bytes in that string.
-** ^If the result is NULL, then sqlite3_column_bytes() returns zero.
+** ^If the result is NULL, then tdsqlite3_column_bytes() returns zero.
**
-** ^If the result is a BLOB or UTF-16 string then the sqlite3_column_bytes16()
+** ^If the result is a BLOB or UTF-16 string then the tdsqlite3_column_bytes16()
** routine returns the number of bytes in that BLOB or string.
-** ^If the result is a UTF-8 string, then sqlite3_column_bytes16() converts
+** ^If the result is a UTF-8 string, then tdsqlite3_column_bytes16() converts
** the string to UTF-16 and then returns the number of bytes.
-** ^If the result is a numeric value then sqlite3_column_bytes16() uses
-** [sqlite3_snprintf()] to convert that value to a UTF-16 string and returns
+** ^If the result is a numeric value then tdsqlite3_column_bytes16() uses
+** [tdsqlite3_snprintf()] to convert that value to a UTF-16 string and returns
** the number of bytes in that string.
-** ^If the result is NULL, then sqlite3_column_bytes16() returns zero.
+** ^If the result is NULL, then tdsqlite3_column_bytes16() returns zero.
**
-** ^The values returned by [sqlite3_column_bytes()] and
-** [sqlite3_column_bytes16()] do not include the zero terminators at the end
+** ^The values returned by [tdsqlite3_column_bytes()] and
+** [tdsqlite3_column_bytes16()] do not include the zero terminators at the end
** of the string. ^For clarity: the values returned by
-** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of
+** [tdsqlite3_column_bytes()] and [tdsqlite3_column_bytes16()] are the number of
** bytes in the string, not the number of characters.
**
-** ^Strings returned by sqlite3_column_text() and sqlite3_column_text16(),
+** ^Strings returned by tdsqlite3_column_text() and tdsqlite3_column_text16(),
** even empty strings, are always zero-terminated. ^The return
-** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
-**
-** <b>Warning:</b> ^The object returned by [sqlite3_column_value()] is an
-** [unprotected sqlite3_value] object. In a multithreaded environment,
-** an unprotected sqlite3_value object may only be used safely with
-** [sqlite3_bind_value()] and [sqlite3_result_value()].
-** If the [unprotected sqlite3_value] object returned by
-** [sqlite3_column_value()] is used in any other way, including calls
-** to routines like [sqlite3_value_int()], [sqlite3_value_text()],
-** or [sqlite3_value_bytes()], the behavior is not threadsafe.
-**
-** These routines attempt to convert the value where appropriate. ^For
-** example, if the internal representation is FLOAT and a text result
-** is requested, [sqlite3_snprintf()] is used internally to perform the
+** value from tdsqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
+**
+** <b>Warning:</b> ^The object returned by [tdsqlite3_column_value()] is an
+** [unprotected tdsqlite3_value] object. In a multithreaded environment,
+** an unprotected tdsqlite3_value object may only be used safely with
+** [tdsqlite3_bind_value()] and [tdsqlite3_result_value()].
+** If the [unprotected tdsqlite3_value] object returned by
+** [tdsqlite3_column_value()] is used in any other way, including calls
+** to routines like [tdsqlite3_value_int()], [tdsqlite3_value_text()],
+** or [tdsqlite3_value_bytes()], the behavior is not threadsafe.
+** Hence, the tdsqlite3_column_value() interface
+** is normally only useful within the implementation of
+** [application-defined SQL functions] or [virtual tables], not within
+** top-level application code.
+**
+** The these routines may attempt to convert the datatype of the result.
+** ^For example, if the internal representation is FLOAT and a text result
+** is requested, [tdsqlite3_snprintf()] is used internally to perform the
** conversion automatically. ^(The following table details the conversions
** that are applied:
**
@@ -4498,20 +5824,20 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** </blockquote>)^
**
** Note that when type conversions occur, pointers returned by prior
-** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or
-** sqlite3_column_text16() may be invalidated.
+** calls to tdsqlite3_column_blob(), tdsqlite3_column_text(), and/or
+** tdsqlite3_column_text16() may be invalidated.
** Type conversions and pointer invalidations might occur
** in the following cases:
**
** <ul>
-** <li> The initial content is a BLOB and sqlite3_column_text() or
-** sqlite3_column_text16() is called. A zero-terminator might
+** <li> The initial content is a BLOB and tdsqlite3_column_text() or
+** tdsqlite3_column_text16() is called. A zero-terminator might
** need to be added to the string.</li>
-** <li> The initial content is UTF-8 text and sqlite3_column_bytes16() or
-** sqlite3_column_text16() is called. The content must be converted
+** <li> The initial content is UTF-8 text and tdsqlite3_column_bytes16() or
+** tdsqlite3_column_text16() is called. The content must be converted
** to UTF-16.</li>
-** <li> The initial content is UTF-16 text and sqlite3_column_bytes() or
-** sqlite3_column_text() is called. The content must be converted
+** <li> The initial content is UTF-16 text and tdsqlite3_column_bytes() or
+** tdsqlite3_column_text() is called. The content must be converted
** to UTF-8.</li>
** </ul>
**
@@ -4525,62 +5851,76 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** in one of the following ways:
**
** <ul>
-** <li>sqlite3_column_text() followed by sqlite3_column_bytes()</li>
-** <li>sqlite3_column_blob() followed by sqlite3_column_bytes()</li>
-** <li>sqlite3_column_text16() followed by sqlite3_column_bytes16()</li>
+** <li>tdsqlite3_column_text() followed by tdsqlite3_column_bytes()</li>
+** <li>tdsqlite3_column_blob() followed by tdsqlite3_column_bytes()</li>
+** <li>tdsqlite3_column_text16() followed by tdsqlite3_column_bytes16()</li>
** </ul>
**
-** In other words, you should call sqlite3_column_text(),
-** sqlite3_column_blob(), or sqlite3_column_text16() first to force the result
-** into the desired format, then invoke sqlite3_column_bytes() or
-** sqlite3_column_bytes16() to find the size of the result. Do not mix calls
-** to sqlite3_column_text() or sqlite3_column_blob() with calls to
-** sqlite3_column_bytes16(), and do not mix calls to sqlite3_column_text16()
-** with calls to sqlite3_column_bytes().
+** In other words, you should call tdsqlite3_column_text(),
+** tdsqlite3_column_blob(), or tdsqlite3_column_text16() first to force the result
+** into the desired format, then invoke tdsqlite3_column_bytes() or
+** tdsqlite3_column_bytes16() to find the size of the result. Do not mix calls
+** to tdsqlite3_column_text() or tdsqlite3_column_blob() with calls to
+** tdsqlite3_column_bytes16(), and do not mix calls to tdsqlite3_column_text16()
+** with calls to tdsqlite3_column_bytes().
**
** ^The pointers returned are valid until a type conversion occurs as
-** described above, or until [sqlite3_step()] or [sqlite3_reset()] or
-** [sqlite3_finalize()] is called. ^The memory space used to hold strings
-** and BLOBs is freed automatically. Do <em>not</em> pass the pointers returned
-** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into
-** [sqlite3_free()].
-**
-** ^(If a memory allocation error occurs during the evaluation of any
-** of these routines, a default value is returned. The default value
-** is either the integer 0, the floating point number 0.0, or a NULL
-** pointer. Subsequent calls to [sqlite3_errcode()] will return
-** [SQLITE_NOMEM].)^
-*/
-SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
-SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
-SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
-SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol);
-SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol);
-SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
-SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
-SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
-SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol);
-SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
+** described above, or until [tdsqlite3_step()] or [tdsqlite3_reset()] or
+** [tdsqlite3_finalize()] is called. ^The memory space used to hold strings
+** and BLOBs is freed automatically. Do not pass the pointers returned
+** from [tdsqlite3_column_blob()], [tdsqlite3_column_text()], etc. into
+** [tdsqlite3_free()].
+**
+** As long as the input parameters are correct, these routines will only
+** fail if an out-of-memory error occurs during a format conversion.
+** Only the following subset of interfaces are subject to out-of-memory
+** errors:
+**
+** <ul>
+** <li> tdsqlite3_column_blob()
+** <li> tdsqlite3_column_text()
+** <li> tdsqlite3_column_text16()
+** <li> tdsqlite3_column_bytes()
+** <li> tdsqlite3_column_bytes16()
+** </ul>
+**
+** If an out-of-memory error occurs, then the return value from these
+** routines is the same as if the column had contained an SQL NULL value.
+** Valid SQL NULL returns can be distinguished from out-of-memory errors
+** by invoking the [tdsqlite3_errcode()] immediately after the suspect
+** return value is obtained and before any
+** other SQLite interface is called on the same [database connection].
+*/
+SQLITE_API const void *tdsqlite3_column_blob(tdsqlite3_stmt*, int iCol);
+SQLITE_API double tdsqlite3_column_double(tdsqlite3_stmt*, int iCol);
+SQLITE_API int tdsqlite3_column_int(tdsqlite3_stmt*, int iCol);
+SQLITE_API tdsqlite3_int64 tdsqlite3_column_int64(tdsqlite3_stmt*, int iCol);
+SQLITE_API const unsigned char *tdsqlite3_column_text(tdsqlite3_stmt*, int iCol);
+SQLITE_API const void *tdsqlite3_column_text16(tdsqlite3_stmt*, int iCol);
+SQLITE_API tdsqlite3_value *tdsqlite3_column_value(tdsqlite3_stmt*, int iCol);
+SQLITE_API int tdsqlite3_column_bytes(tdsqlite3_stmt*, int iCol);
+SQLITE_API int tdsqlite3_column_bytes16(tdsqlite3_stmt*, int iCol);
+SQLITE_API int tdsqlite3_column_type(tdsqlite3_stmt*, int iCol);
/*
** CAPI3REF: Destroy A Prepared Statement Object
-** DESTRUCTOR: sqlite3_stmt
+** DESTRUCTOR: tdsqlite3_stmt
**
-** ^The sqlite3_finalize() function is called to delete a [prepared statement].
+** ^The tdsqlite3_finalize() function is called to delete a [prepared statement].
** ^If the most recent evaluation of the statement encountered no errors
-** or if the statement is never been evaluated, then sqlite3_finalize() returns
+** or if the statement is never been evaluated, then tdsqlite3_finalize() returns
** SQLITE_OK. ^If the most recent evaluation of statement S failed, then
-** sqlite3_finalize(S) returns the appropriate [error code] or
+** tdsqlite3_finalize(S) returns the appropriate [error code] or
** [extended error code].
**
-** ^The sqlite3_finalize(S) routine can be called at any point during
+** ^The tdsqlite3_finalize(S) routine can be called at any point during
** the life cycle of [prepared statement] S:
** before statement S is ever evaluated, after
-** one or more calls to [sqlite3_reset()], or after any call
-** to [sqlite3_step()] regardless of whether or not the statement has
+** one or more calls to [tdsqlite3_reset()], or after any call
+** to [tdsqlite3_step()] regardless of whether or not the statement has
** completed execution.
**
-** ^Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op.
+** ^Invoking tdsqlite3_finalize() on a NULL pointer is a harmless no-op.
**
** The application must finalize every [prepared statement] in order to avoid
** resource leaks. It is a grievous error for the application to try to use
@@ -4588,49 +5928,49 @@ SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
** statement after it has been finalized can result in undefined and
** undesirable behavior such as segfaults and heap corruption.
*/
-SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_finalize(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Reset A Prepared Statement Object
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** The sqlite3_reset() function is called to reset a [prepared statement]
+** The tdsqlite3_reset() function is called to reset a [prepared statement]
** object back to its initial state, ready to be re-executed.
** ^Any SQL statement variables that had values bound to them using
-** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values.
-** Use [sqlite3_clear_bindings()] to reset the bindings.
+** the [tdsqlite3_bind_blob | tdsqlite3_bind_*() API] retain their values.
+** Use [tdsqlite3_clear_bindings()] to reset the bindings.
**
-** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S
+** ^The [tdsqlite3_reset(S)] interface resets the [prepared statement] S
** back to the beginning of its program.
**
-** ^If the most recent call to [sqlite3_step(S)] for the
+** ^If the most recent call to [tdsqlite3_step(S)] for the
** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE],
-** or if [sqlite3_step(S)] has never before been called on S,
-** then [sqlite3_reset(S)] returns [SQLITE_OK].
+** or if [tdsqlite3_step(S)] has never before been called on S,
+** then [tdsqlite3_reset(S)] returns [SQLITE_OK].
**
-** ^If the most recent call to [sqlite3_step(S)] for the
+** ^If the most recent call to [tdsqlite3_step(S)] for the
** [prepared statement] S indicated an error, then
-** [sqlite3_reset(S)] returns an appropriate [error code].
+** [tdsqlite3_reset(S)] returns an appropriate [error code].
**
-** ^The [sqlite3_reset(S)] interface does not change the values
-** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S.
+** ^The [tdsqlite3_reset(S)] interface does not change the values
+** of any [tdsqlite3_bind_blob|bindings] on the [prepared statement] S.
*/
-SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_reset(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Create Or Redefine SQL Functions
** KEYWORDS: {function creation routines}
-** KEYWORDS: {application-defined SQL function}
-** KEYWORDS: {application-defined SQL functions}
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^These functions (collectively known as "function creation routines")
** are used to add SQL functions or aggregates or to redefine the behavior
-** of existing SQL functions or aggregates. The only differences between
-** these routines are the text encoding expected for
-** the second parameter (the name of the function being created)
-** and the presence or absence of a destructor callback for
-** the application data pointer.
+** of existing SQL functions or aggregates. The only differences between
+** the three "tdsqlite3_create_function*" routines are the text encoding
+** expected for the second parameter (the name of the function being
+** created) and the presence or absence of a destructor callback for
+** the application data pointer. Function tdsqlite3_create_window_function()
+** is similar, but allows the user to supply the extra callback functions
+** needed by [aggregate window functions].
**
** ^The first parameter is the [database connection] to which the SQL
** function is to be added. ^If an application uses more than one database
@@ -4648,7 +5988,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** is the number of arguments that the SQL function or
** aggregate takes. ^If this parameter is -1, then the SQL function or
** aggregate may take any number of arguments between 0 and the limit
-** set by [sqlite3_limit]([SQLITE_LIMIT_FUNCTION_ARG]). If the third
+** set by [tdsqlite3_limit]([SQLITE_LIMIT_FUNCTION_ARG]). If the third
** parameter is less than -1 or greater than 127 then the behavior is
** undefined.
**
@@ -4656,9 +5996,9 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** [SQLITE_UTF8 | text encoding] this SQL function prefers for
** its parameters. The application should set this parameter to
** [SQLITE_UTF16LE] if the function implementation invokes
-** [sqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the
-** implementation invokes [sqlite3_value_text16be()] on an input, or
-** [SQLITE_UTF16] if [sqlite3_value_text16()] is used, or [SQLITE_UTF8]
+** [tdsqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the
+** implementation invokes [tdsqlite3_value_text16be()] on an input, or
+** [SQLITE_UTF16] if [tdsqlite3_value_text16()] is used, or [SQLITE_UTF8]
** otherwise. ^The same SQL function may be registered multiple times using
** different preferred text encodings, with different implementations for
** each encoding.
@@ -4673,10 +6013,28 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** perform additional optimizations on deterministic functions, so use
** of the [SQLITE_DETERMINISTIC] flag is recommended where possible.
**
+** ^The fourth parameter may also optionally include the [SQLITE_DIRECTONLY]
+** flag, which if present prevents the function from being invoked from
+** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions,
+** index expressions, or the WHERE clause of partial indexes.
+**
+** <span style="background-color:#ffff90;">
+** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
+** all application-defined SQL functions that do not need to be
+** used inside of triggers, view, CHECK constraints, or other elements of
+** the database schema. This flags is especially recommended for SQL
+** functions that have side effects or reveal internal application state.
+** Without this flag, an attacker might be able to modify the schema of
+** a database file to include invocations of the function with parameters
+** chosen by the attacker, which the application will then execute when
+** the database file is opened and read.
+** </span>
+**
** ^(The fifth parameter is an arbitrary pointer. The implementation of the
-** function can gain access to this pointer using [sqlite3_user_data()].)^
+** function can gain access to this pointer using [tdsqlite3_user_data()].)^
**
-** ^The sixth, seventh and eighth parameters, xFunc, xStep and xFinal, are
+** ^The sixth, seventh and eighth parameters passed to the three
+** "tdsqlite3_create_function*" functions, xFunc, xStep and xFinal, are
** pointers to C-language functions that implement the SQL function or
** aggregate. ^A scalar SQL function requires an implementation of the xFunc
** callback only; NULL pointers must be passed as the xStep and xFinal
@@ -4685,15 +6043,24 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** SQL function or aggregate, pass NULL pointers for all three function
** callbacks.
**
-** ^(If the ninth parameter to sqlite3_create_function_v2() is not NULL,
-** then it is destructor for the application data pointer.
-** The destructor is invoked when the function is deleted, either by being
-** overloaded or when the database connection closes.)^
-** ^The destructor is also invoked if the call to
-** sqlite3_create_function_v2() fails.
-** ^When the destructor callback of the tenth parameter is invoked, it
-** is passed a single argument which is a copy of the application data
-** pointer which was the fifth parameter to sqlite3_create_function_v2().
+** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue
+** and xInverse) passed to tdsqlite3_create_window_function are pointers to
+** C-language callbacks that implement the new function. xStep and xFinal
+** must both be non-NULL. xValue and xInverse may either both be NULL, in
+** which case a regular aggregate function is created, or must both be
+** non-NULL, in which case the new function may be used as either an aggregate
+** or aggregate window function. More details regarding the implementation
+** of aggregate window functions are
+** [user-defined window functions|available here].
+**
+** ^(If the final parameter to tdsqlite3_create_function_v2() or
+** tdsqlite3_create_window_function() is not NULL, then it is destructor for
+** the application data pointer. The destructor is invoked when the function
+** is deleted, either by being overloaded or when the database connection
+** closes.)^ ^The destructor is also invoked if the call to
+** tdsqlite3_create_function_v2() fails. ^When the destructor callback is
+** invoked, it is passed a single argument which is a copy of the application
+** data pointer which was the fifth parameter to tdsqlite3_create_function_v2().
**
** ^It is permitted to register multiple implementations of the same
** functions with the same name but with either differing numbers of
@@ -4715,35 +6082,47 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** close the database connection nor finalize or reset the prepared
** statement in which the function is running.
*/
-SQLITE_API int sqlite3_create_function(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_create_function(
+ tdsqlite3 *db,
const char *zFunctionName,
int nArg,
int eTextRep,
void *pApp,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*)
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*)
);
-SQLITE_API int sqlite3_create_function16(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_create_function16(
+ tdsqlite3 *db,
const void *zFunctionName,
int nArg,
int eTextRep,
void *pApp,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*)
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*)
+);
+SQLITE_API int tdsqlite3_create_function_v2(
+ tdsqlite3 *db,
+ const char *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *pApp,
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*),
+ void(*xDestroy)(void*)
);
-SQLITE_API int sqlite3_create_function_v2(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_create_window_function(
+ tdsqlite3 *db,
const char *zFunctionName,
int nArg,
int eTextRep,
void *pApp,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*),
+ void (*xValue)(tdsqlite3_context*),
+ void (*xInverse)(tdsqlite3_context*,int,tdsqlite3_value**),
void(*xDestroy)(void*)
);
@@ -4758,17 +6137,77 @@ SQLITE_API int sqlite3_create_function_v2(
#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */
#define SQLITE_UTF16 4 /* Use native byte order */
#define SQLITE_ANY 5 /* Deprecated */
-#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */
+#define SQLITE_UTF16_ALIGNED 8 /* tdsqlite3_create_collation only */
/*
** CAPI3REF: Function Flags
**
** These constants may be ORed together with the
** [SQLITE_UTF8 | preferred text encoding] as the fourth argument
-** to [sqlite3_create_function()], [sqlite3_create_function16()], or
-** [sqlite3_create_function_v2()].
+** to [tdsqlite3_create_function()], [tdsqlite3_create_function16()], or
+** [tdsqlite3_create_function_v2()].
+**
+** <dl>
+** [[SQLITE_DETERMINISTIC]] <dt>SQLITE_DETERMINISTIC</dt><dd>
+** The SQLITE_DETERMINISTIC flag means that the new function always gives
+** the same output when the input parameters are the same.
+** The [abs|abs() function] is deterministic, for example, but
+** [randomblob|randomblob()] is not. Functions must
+** be deterministic in order to be used in certain contexts such as
+** with the WHERE clause of [partial indexes] or in [generated columns].
+** SQLite might also optimize deterministic functions by factoring them
+** out of inner loops.
+** </dd>
+**
+** [[SQLITE_DIRECTONLY]] <dt>SQLITE_DIRECTONLY</dt><dd>
+** The SQLITE_DIRECTONLY flag means that the function may only be invoked
+** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in
+** schema structures such as [CHECK constraints], [DEFAULT clauses],
+** [expression indexes], [partial indexes], or [generated columns].
+** The SQLITE_DIRECTONLY flags is a security feature which is recommended
+** for all [application-defined SQL functions], and especially for functions
+** that have side-effects or that could potentially leak sensitive
+** information.
+** </dd>
+**
+** [[SQLITE_INNOCUOUS]] <dt>SQLITE_INNOCUOUS</dt><dd>
+** The SQLITE_INNOCUOUS flag means that the function is unlikely
+** to cause problems even if misused. An innocuous function should have
+** no side effects and should not depend on any values other than its
+** input parameters. The [abs|abs() function] is an example of an
+** innocuous function.
+** The [load_extension() SQL function] is not innocuous because of its
+** side effects.
+** <p> SQLITE_INNOCUOUS is similar to SQLITE_DETERMINISTIC, but is not
+** exactly the same. The [random|random() function] is an example of a
+** function that is innocuous but not deterministic.
+** <p>Some heightened security settings
+** ([SQLITE_DBCONFIG_TRUSTED_SCHEMA] and [PRAGMA trusted_schema=OFF])
+** disable the use of SQL functions inside views and triggers and in
+** schema structures such as [CHECK constraints], [DEFAULT clauses],
+** [expression indexes], [partial indexes], and [generated columns] unless
+** the function is tagged with SQLITE_INNOCUOUS. Most built-in functions
+** are innocuous. Developers are advised to avoid using the
+** SQLITE_INNOCUOUS flag for application-defined functions unless the
+** function has been carefully audited and found to be free of potentially
+** security-adverse side-effects and information-leaks.
+** </dd>
+**
+** [[SQLITE_SUBTYPE]] <dt>SQLITE_SUBTYPE</dt><dd>
+** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call
+** [tdsqlite3_value_subtype()] to inspect the sub-types of its arguments.
+** Specifying this flag makes no difference for scalar or aggregate user
+** functions. However, if it is not specified for a user-defined window
+** function, then any sub-types belonging to arguments passed to the window
+** function may be discarded before the window function is called (i.e.
+** tdsqlite3_value_subtype() will always return 0).
+** </dd>
+** </dl>
*/
-#define SQLITE_DETERMINISTIC 0x800
+#define SQLITE_DETERMINISTIC 0x000000800
+#define SQLITE_DIRECTONLY 0x000080000
+#define SQLITE_SUBTYPE 0x000100000
+#define SQLITE_INNOCUOUS 0x000200000
/*
** CAPI3REF: Deprecated Functions
@@ -4781,45 +6220,87 @@ SQLITE_API int sqlite3_create_function_v2(
** these functions, we will not explain what they do.
*/
#ifndef SQLITE_OMIT_DEPRECATED
-SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*);
-SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*);
-SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*);
-SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void);
-SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void);
-SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),
- void*,sqlite3_int64);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_aggregate_count(tdsqlite3_context*);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_expired(tdsqlite3_stmt*);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_transfer_bindings(tdsqlite3_stmt*, tdsqlite3_stmt*);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_global_recover(void);
+SQLITE_API SQLITE_DEPRECATED void tdsqlite3_thread_cleanup(void);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_memory_alarm(void(*)(void*,tdsqlite3_int64,int),
+ void*,tdsqlite3_int64);
#endif
/*
** CAPI3REF: Obtaining SQL Values
-** METHOD: sqlite3_value
-**
-** The C-language implementation of SQL functions and aggregates uses
-** this set of interface routines to access the parameter values on
-** the function or aggregate.
-**
-** The xFunc (for scalar functions) or xStep (for aggregates) parameters
-** to [sqlite3_create_function()] and [sqlite3_create_function16()]
-** define callbacks that implement the SQL functions and aggregates.
-** The 3rd parameter to these callbacks is an array of pointers to
-** [protected sqlite3_value] objects. There is one [sqlite3_value] object for
-** each parameter to the SQL function. These routines are used to
-** extract values from the [sqlite3_value] objects.
-**
-** These routines work only with [protected sqlite3_value] objects.
-** Any attempt to use these routines on an [unprotected sqlite3_value]
-** object results in undefined behavior.
+** METHOD: tdsqlite3_value
+**
+** <b>Summary:</b>
+** <blockquote><table border=0 cellpadding=0 cellspacing=0>
+** <tr><td><b>tdsqlite3_value_blob</b><td>&rarr;<td>BLOB value
+** <tr><td><b>tdsqlite3_value_double</b><td>&rarr;<td>REAL value
+** <tr><td><b>tdsqlite3_value_int</b><td>&rarr;<td>32-bit INTEGER value
+** <tr><td><b>tdsqlite3_value_int64</b><td>&rarr;<td>64-bit INTEGER value
+** <tr><td><b>tdsqlite3_value_pointer</b><td>&rarr;<td>Pointer value
+** <tr><td><b>tdsqlite3_value_text</b><td>&rarr;<td>UTF-8 TEXT value
+** <tr><td><b>tdsqlite3_value_text16</b><td>&rarr;<td>UTF-16 TEXT value in
+** the native byteorder
+** <tr><td><b>tdsqlite3_value_text16be</b><td>&rarr;<td>UTF-16be TEXT value
+** <tr><td><b>tdsqlite3_value_text16le</b><td>&rarr;<td>UTF-16le TEXT value
+** <tr><td>&nbsp;<td>&nbsp;<td>&nbsp;
+** <tr><td><b>tdsqlite3_value_bytes</b><td>&rarr;<td>Size of a BLOB
+** or a UTF-8 TEXT in bytes
+** <tr><td><b>tdsqlite3_value_bytes16&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>Size of UTF-16
+** TEXT in bytes
+** <tr><td><b>tdsqlite3_value_type</b><td>&rarr;<td>Default
+** datatype of the value
+** <tr><td><b>tdsqlite3_value_numeric_type&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>Best numeric datatype of the value
+** <tr><td><b>tdsqlite3_value_nochange&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>True if the column is unchanged in an UPDATE
+** against a virtual table.
+** <tr><td><b>tdsqlite3_value_frombind&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>True if value originated from a [bound parameter]
+** </table></blockquote>
+**
+** <b>Details:</b>
+**
+** These routines extract type, size, and content information from
+** [protected tdsqlite3_value] objects. Protected tdsqlite3_value objects
+** are used to pass parameter information into the functions that
+** implement [application-defined SQL functions] and [virtual tables].
+**
+** These routines work only with [protected tdsqlite3_value] objects.
+** Any attempt to use these routines on an [unprotected tdsqlite3_value]
+** is not threadsafe.
**
** ^These routines work just like the corresponding [column access functions]
-** except that these routines take a single [protected sqlite3_value] object
-** pointer instead of a [sqlite3_stmt*] pointer and an integer column number.
+** except that these routines take a single [protected tdsqlite3_value] object
+** pointer instead of a [tdsqlite3_stmt*] pointer and an integer column number.
**
-** ^The sqlite3_value_text16() interface extracts a UTF-16 string
+** ^The tdsqlite3_value_text16() interface extracts a UTF-16 string
** in the native byte-order of the host machine. ^The
-** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces
+** tdsqlite3_value_text16be() and tdsqlite3_value_text16le() interfaces
** extract UTF-16 strings as big-endian and little-endian respectively.
**
-** ^(The sqlite3_value_numeric_type() interface attempts to apply
+** ^If [tdsqlite3_value] object V was initialized
+** using [tdsqlite3_bind_pointer(S,I,P,X,D)] or [tdsqlite3_result_pointer(C,P,X,D)]
+** and if X and Y are strings that compare equal according to strcmp(X,Y),
+** then tdsqlite3_value_pointer(V,Y) will return the pointer P. ^Otherwise,
+** tdsqlite3_value_pointer(V,Y) returns a NULL. The tdsqlite3_bind_pointer()
+** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
+**
+** ^(The tdsqlite3_value_type(V) interface returns the
+** [SQLITE_INTEGER | datatype code] for the initial datatype of the
+** [tdsqlite3_value] object V. The returned value is one of [SQLITE_INTEGER],
+** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL].)^
+** Other interfaces might change the datatype for an tdsqlite3_value object.
+** For example, if the datatype is initially SQLITE_INTEGER and
+** tdsqlite3_value_text(V) is called to extract a text value for that
+** integer, then subsequent calls to tdsqlite3_value_type(V) might return
+** SQLITE_TEXT. Whether or not a persistent internal datatype conversion
+** occurs is undefined and may change from one release of SQLite to the next.
+**
+** ^(The tdsqlite3_value_numeric_type() interface attempts to apply
** numeric affinity to the value. This means that an attempt is
** made to convert the value to an integer or floating point. If
** such a conversion is possible without loss of information (in other
@@ -4827,136 +6308,175 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
** then the conversion is performed. Otherwise no conversion occurs.
** The [SQLITE_INTEGER | datatype] after conversion is returned.)^
**
+** ^Within the [xUpdate] method of a [virtual table], the
+** tdsqlite3_value_nochange(X) interface returns true if and only if
+** the column corresponding to X is unchanged by the UPDATE operation
+** that the xUpdate method call was invoked to implement and if
+** and the prior [xColumn] method call that was invoked to extracted
+** the value for that column returned without setting a result (probably
+** because it queried [tdsqlite3_vtab_nochange()] and found that the column
+** was unchanging). ^Within an [xUpdate] method, any value for which
+** tdsqlite3_value_nochange(X) is true will in all other respects appear
+** to be a NULL value. If tdsqlite3_value_nochange(X) is invoked anywhere other
+** than within an [xUpdate] method call for an UPDATE statement, then
+** the return value is arbitrary and meaningless.
+**
+** ^The tdsqlite3_value_frombind(X) interface returns non-zero if the
+** value X originated from one of the [tdsqlite3_bind_int|tdsqlite3_bind()]
+** interfaces. ^If X comes from an SQL literal value, or a table column,
+** or an expression, then tdsqlite3_value_frombind(X) returns zero.
+**
** Please pay particular attention to the fact that the pointer returned
-** from [sqlite3_value_blob()], [sqlite3_value_text()], or
-** [sqlite3_value_text16()] can be invalidated by a subsequent call to
-** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()],
-** or [sqlite3_value_text16()].
+** from [tdsqlite3_value_blob()], [tdsqlite3_value_text()], or
+** [tdsqlite3_value_text16()] can be invalidated by a subsequent call to
+** [tdsqlite3_value_bytes()], [tdsqlite3_value_bytes16()], [tdsqlite3_value_text()],
+** or [tdsqlite3_value_text16()].
**
** These routines must be called from the same thread as
-** the SQL function that supplied the [sqlite3_value*] parameters.
-*/
-SQLITE_API const void *sqlite3_value_blob(sqlite3_value*);
-SQLITE_API int sqlite3_value_bytes(sqlite3_value*);
-SQLITE_API int sqlite3_value_bytes16(sqlite3_value*);
-SQLITE_API double sqlite3_value_double(sqlite3_value*);
-SQLITE_API int sqlite3_value_int(sqlite3_value*);
-SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*);
-SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*);
-SQLITE_API const void *sqlite3_value_text16(sqlite3_value*);
-SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*);
-SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*);
-SQLITE_API int sqlite3_value_type(sqlite3_value*);
-SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*);
+** the SQL function that supplied the [tdsqlite3_value*] parameters.
+**
+** As long as the input parameter is correct, these routines can only
+** fail if an out-of-memory error occurs during a format conversion.
+** Only the following subset of interfaces are subject to out-of-memory
+** errors:
+**
+** <ul>
+** <li> tdsqlite3_value_blob()
+** <li> tdsqlite3_value_text()
+** <li> tdsqlite3_value_text16()
+** <li> tdsqlite3_value_text16le()
+** <li> tdsqlite3_value_text16be()
+** <li> tdsqlite3_value_bytes()
+** <li> tdsqlite3_value_bytes16()
+** </ul>
+**
+** If an out-of-memory error occurs, then the return value from these
+** routines is the same as if the column had contained an SQL NULL value.
+** Valid SQL NULL returns can be distinguished from out-of-memory errors
+** by invoking the [tdsqlite3_errcode()] immediately after the suspect
+** return value is obtained and before any
+** other SQLite interface is called on the same [database connection].
+*/
+SQLITE_API const void *tdsqlite3_value_blob(tdsqlite3_value*);
+SQLITE_API double tdsqlite3_value_double(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_int(tdsqlite3_value*);
+SQLITE_API tdsqlite3_int64 tdsqlite3_value_int64(tdsqlite3_value*);
+SQLITE_API void *tdsqlite3_value_pointer(tdsqlite3_value*, const char*);
+SQLITE_API const unsigned char *tdsqlite3_value_text(tdsqlite3_value*);
+SQLITE_API const void *tdsqlite3_value_text16(tdsqlite3_value*);
+SQLITE_API const void *tdsqlite3_value_text16le(tdsqlite3_value*);
+SQLITE_API const void *tdsqlite3_value_text16be(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_bytes(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_bytes16(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_type(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_numeric_type(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_nochange(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_frombind(tdsqlite3_value*);
/*
** CAPI3REF: Finding The Subtype Of SQL Values
-** METHOD: sqlite3_value
+** METHOD: tdsqlite3_value
**
-** The sqlite3_value_subtype(V) function returns the subtype for
+** The tdsqlite3_value_subtype(V) function returns the subtype for
** an [application-defined SQL function] argument V. The subtype
** information can be used to pass a limited amount of context from
-** one SQL function to another. Use the [sqlite3_result_subtype()]
+** one SQL function to another. Use the [tdsqlite3_result_subtype()]
** routine to set the subtype for the return value of an SQL function.
-**
-** SQLite makes no use of subtype itself. It merely passes the subtype
-** from the result of one [application-defined SQL function] into the
-** input of another.
*/
-SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
+SQLITE_API unsigned int tdsqlite3_value_subtype(tdsqlite3_value*);
/*
** CAPI3REF: Copy And Free SQL Values
-** METHOD: sqlite3_value
+** METHOD: tdsqlite3_value
**
-** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
-** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
-** is a [protected sqlite3_value] object even if the input is not.
-** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
+** ^The tdsqlite3_value_dup(V) interface makes a copy of the [tdsqlite3_value]
+** object D and returns a pointer to that copy. ^The [tdsqlite3_value] returned
+** is a [protected tdsqlite3_value] object even if the input is not.
+** ^The tdsqlite3_value_dup(V) interface returns NULL if V is NULL or if a
** memory allocation fails.
**
-** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object
-** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer
-** then sqlite3_value_free(V) is a harmless no-op.
+** ^The tdsqlite3_value_free(V) interface frees an [tdsqlite3_value] object
+** previously obtained from [tdsqlite3_value_dup()]. ^If V is a NULL pointer
+** then tdsqlite3_value_free(V) is a harmless no-op.
*/
-SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value*);
-SQLITE_API void sqlite3_value_free(sqlite3_value*);
+SQLITE_API tdsqlite3_value *tdsqlite3_value_dup(const tdsqlite3_value*);
+SQLITE_API void tdsqlite3_value_free(tdsqlite3_value*);
/*
** CAPI3REF: Obtain Aggregate Function Context
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
** Implementations of aggregate SQL functions use this
** routine to allocate memory for storing their state.
**
-** ^The first time the sqlite3_aggregate_context(C,N) routine is called
-** for a particular aggregate function, SQLite
-** allocates N of memory, zeroes out that memory, and returns a pointer
+** ^The first time the tdsqlite3_aggregate_context(C,N) routine is called
+** for a particular aggregate function, SQLite allocates
+** N bytes of memory, zeroes out that memory, and returns a pointer
** to the new memory. ^On second and subsequent calls to
-** sqlite3_aggregate_context() for the same aggregate function instance,
+** tdsqlite3_aggregate_context() for the same aggregate function instance,
** the same buffer is returned. Sqlite3_aggregate_context() is normally
** called once for each invocation of the xStep callback and then one
** last time when the xFinal callback is invoked. ^(When no rows match
** an aggregate query, the xStep() callback of the aggregate function
** implementation is never called and xFinal() is called exactly once.
-** In those cases, sqlite3_aggregate_context() might be called for the
+** In those cases, tdsqlite3_aggregate_context() might be called for the
** first time from within xFinal().)^
**
-** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer
+** ^The tdsqlite3_aggregate_context(C,N) routine returns a NULL pointer
** when first called if N is less than or equal to zero or if a memory
** allocate error occurs.
**
-** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
+** ^(The amount of space allocated by tdsqlite3_aggregate_context(C,N) is
** determined by the N parameter on first successful call. Changing the
-** value of N in subsequent call to sqlite3_aggregate_context() within
+** value of N in any subsequents call to tdsqlite3_aggregate_context() within
** the same aggregate function instance will not resize the memory
** allocation.)^ Within the xFinal callback, it is customary to set
-** N=0 in calls to sqlite3_aggregate_context(C,N) so that no
+** N=0 in calls to tdsqlite3_aggregate_context(C,N) so that no
** pointless memory allocations occur.
**
** ^SQLite automatically frees the memory allocated by
-** sqlite3_aggregate_context() when the aggregate query concludes.
+** tdsqlite3_aggregate_context() when the aggregate query concludes.
**
** The first parameter must be a copy of the
-** [sqlite3_context | SQL function context] that is the first parameter
+** [tdsqlite3_context | SQL function context] that is the first parameter
** to the xStep or xFinal callback routine that implements the aggregate
** function.
**
** This routine must be called from the same thread in which
** the aggregate SQL function is running.
*/
-SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes);
+SQLITE_API void *tdsqlite3_aggregate_context(tdsqlite3_context*, int nBytes);
/*
** CAPI3REF: User Data For Functions
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
-** ^The sqlite3_user_data() interface returns a copy of
+** ^The tdsqlite3_user_data() interface returns a copy of
** the pointer that was the pUserData parameter (the 5th parameter)
-** of the [sqlite3_create_function()]
-** and [sqlite3_create_function16()] routines that originally
+** of the [tdsqlite3_create_function()]
+** and [tdsqlite3_create_function16()] routines that originally
** registered the application defined function.
**
** This routine must be called from the same thread in which
** the application-defined function is running.
*/
-SQLITE_API void *sqlite3_user_data(sqlite3_context*);
+SQLITE_API void *tdsqlite3_user_data(tdsqlite3_context*);
/*
** CAPI3REF: Database Connection For Functions
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
-** ^The sqlite3_context_db_handle() interface returns a copy of
+** ^The tdsqlite3_context_db_handle() interface returns a copy of
** the pointer to the [database connection] (the 1st parameter)
-** of the [sqlite3_create_function()]
-** and [sqlite3_create_function16()] routines that originally
+** of the [tdsqlite3_create_function()]
+** and [tdsqlite3_create_function16()] routines that originally
** registered the application defined function.
*/
-SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
+SQLITE_API tdsqlite3 *tdsqlite3_context_db_handle(tdsqlite3_context*);
/*
** CAPI3REF: Function Auxiliary Data
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
** These functions may be used by (non-aggregate) SQL functions to
** associate metadata with argument values. If the same value is passed to
@@ -4969,52 +6489,57 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
** the compiled regular expression can be reused on multiple
** invocations of the same function.
**
-** ^The sqlite3_get_auxdata() interface returns a pointer to the metadata
-** associated by the sqlite3_set_auxdata() function with the Nth argument
-** value to the application-defined function. ^If there is no metadata
-** associated with the function argument, this sqlite3_get_auxdata() interface
+** ^The tdsqlite3_get_auxdata(C,N) interface returns a pointer to the metadata
+** associated by the tdsqlite3_set_auxdata(C,N,P,X) function with the Nth argument
+** value to the application-defined function. ^N is zero for the left-most
+** function argument. ^If there is no metadata
+** associated with the function argument, the tdsqlite3_get_auxdata(C,N) interface
** returns a NULL pointer.
**
-** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th
+** ^The tdsqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th
** argument of the application-defined function. ^Subsequent
-** calls to sqlite3_get_auxdata(C,N) return P from the most recent
-** sqlite3_set_auxdata(C,N,P,X) call if the metadata is still valid or
+** calls to tdsqlite3_get_auxdata(C,N) return P from the most recent
+** tdsqlite3_set_auxdata(C,N,P,X) call if the metadata is still valid or
** NULL if the metadata has been discarded.
-** ^After each call to sqlite3_set_auxdata(C,N,P,X) where X is not NULL,
+** ^After each call to tdsqlite3_set_auxdata(C,N,P,X) where X is not NULL,
** SQLite will invoke the destructor function X with parameter P exactly
** once, when the metadata is discarded.
** SQLite is free to discard the metadata at any time, including: <ul>
** <li> ^(when the corresponding function parameter changes)^, or
-** <li> ^(when [sqlite3_reset()] or [sqlite3_finalize()] is called for the
+** <li> ^(when [tdsqlite3_reset()] or [tdsqlite3_finalize()] is called for the
** SQL statement)^, or
-** <li> ^(when sqlite3_set_auxdata() is invoked again on the same
+** <li> ^(when tdsqlite3_set_auxdata() is invoked again on the same
** parameter)^, or
-** <li> ^(during the original sqlite3_set_auxdata() call when a memory
+** <li> ^(during the original tdsqlite3_set_auxdata() call when a memory
** allocation error occurs.)^ </ul>
**
** Note the last bullet in particular. The destructor X in
-** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the
-** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata()
+** tdsqlite3_set_auxdata(C,N,P,X) might be called immediately, before the
+** tdsqlite3_set_auxdata() interface even returns. Hence tdsqlite3_set_auxdata()
** should be called near the end of the function implementation and the
** function implementation should not make any use of P after
-** sqlite3_set_auxdata() has been called.
+** tdsqlite3_set_auxdata() has been called.
**
** ^(In practice, metadata is preserved between function calls for
** function parameters that are compile-time constants, including literal
** values and [parameters] and expressions composed from the same.)^
**
+** The value of the N parameter to these interfaces should be non-negative.
+** Future enhancements may make use of negative N values to define new
+** kinds of function caching behavior.
+**
** These routines must be called from the same thread in which
** the SQL function is running.
*/
-SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N);
-SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*));
+SQLITE_API void *tdsqlite3_get_auxdata(tdsqlite3_context*, int N);
+SQLITE_API void tdsqlite3_set_auxdata(tdsqlite3_context*, int N, void*, void (*)(void*));
/*
** CAPI3REF: Constants Defining Special Destructor Behavior
**
** These are special values for the destructor that is passed in as the
-** final argument to routines like [sqlite3_result_blob()]. ^If the destructor
+** final argument to routines like [tdsqlite3_result_blob()]. ^If the destructor
** argument is SQLITE_STATIC, it means that the content pointer is constant
** and will never change. It does not need to be destroyed. ^The
** SQLITE_TRANSIENT value means that the content will likely change in
@@ -5024,89 +6549,89 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
** The typedef is necessary to work around problems in certain
** C++ compilers.
*/
-typedef void (*sqlite3_destructor_type)(void*);
-#define SQLITE_STATIC ((sqlite3_destructor_type)0)
-#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
+typedef void (*tdsqlite3_destructor_type)(void*);
+#define SQLITE_STATIC ((tdsqlite3_destructor_type)0)
+#define SQLITE_TRANSIENT ((tdsqlite3_destructor_type)-1)
/*
** CAPI3REF: Setting The Result Of An SQL Function
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
** These routines are used by the xFunc or xFinal callbacks that
** implement SQL functions and aggregates. See
-** [sqlite3_create_function()] and [sqlite3_create_function16()]
+** [tdsqlite3_create_function()] and [tdsqlite3_create_function16()]
** for additional information.
**
** These functions work very much like the [parameter binding] family of
** functions used to bind values to host parameters in prepared statements.
** Refer to the [SQL parameter] documentation for additional information.
**
-** ^The sqlite3_result_blob() interface sets the result from
+** ^The tdsqlite3_result_blob() interface sets the result from
** an application-defined function to be the BLOB whose content is pointed
** to by the second parameter and which is N bytes long where N is the
** third parameter.
**
-** ^The sqlite3_result_zeroblob(C,N) and sqlite3_result_zeroblob64(C,N)
+** ^The tdsqlite3_result_zeroblob(C,N) and tdsqlite3_result_zeroblob64(C,N)
** interfaces set the result of the application-defined function to be
** a BLOB containing all zero bytes and N bytes in size.
**
-** ^The sqlite3_result_double() interface sets the result from
+** ^The tdsqlite3_result_double() interface sets the result from
** an application-defined function to be a floating point value specified
** by its 2nd argument.
**
-** ^The sqlite3_result_error() and sqlite3_result_error16() functions
+** ^The tdsqlite3_result_error() and tdsqlite3_result_error16() functions
** cause the implemented SQL function to throw an exception.
** ^SQLite uses the string pointed to by the
-** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16()
+** 2nd parameter of tdsqlite3_result_error() or tdsqlite3_result_error16()
** as the text of an error message. ^SQLite interprets the error
-** message string from sqlite3_result_error() as UTF-8. ^SQLite
-** interprets the string from sqlite3_result_error16() as UTF-16 in native
-** byte order. ^If the third parameter to sqlite3_result_error()
-** or sqlite3_result_error16() is negative then SQLite takes as the error
+** message string from tdsqlite3_result_error() as UTF-8. ^SQLite
+** interprets the string from tdsqlite3_result_error16() as UTF-16 in native
+** byte order. ^If the third parameter to tdsqlite3_result_error()
+** or tdsqlite3_result_error16() is negative then SQLite takes as the error
** message all text up through the first zero character.
-** ^If the third parameter to sqlite3_result_error() or
-** sqlite3_result_error16() is non-negative then SQLite takes that many
+** ^If the third parameter to tdsqlite3_result_error() or
+** tdsqlite3_result_error16() is non-negative then SQLite takes that many
** bytes (not characters) from the 2nd parameter as the error message.
-** ^The sqlite3_result_error() and sqlite3_result_error16()
+** ^The tdsqlite3_result_error() and tdsqlite3_result_error16()
** routines make a private copy of the error message text before
** they return. Hence, the calling function can deallocate or
** modify the text after they return without harm.
-** ^The sqlite3_result_error_code() function changes the error code
+** ^The tdsqlite3_result_error_code() function changes the error code
** returned by SQLite as a result of an error in a function. ^By default,
-** the error code is SQLITE_ERROR. ^A subsequent call to sqlite3_result_error()
-** or sqlite3_result_error16() resets the error code to SQLITE_ERROR.
+** the error code is SQLITE_ERROR. ^A subsequent call to tdsqlite3_result_error()
+** or tdsqlite3_result_error16() resets the error code to SQLITE_ERROR.
**
-** ^The sqlite3_result_error_toobig() interface causes SQLite to throw an
+** ^The tdsqlite3_result_error_toobig() interface causes SQLite to throw an
** error indicating that a string or BLOB is too long to represent.
**
-** ^The sqlite3_result_error_nomem() interface causes SQLite to throw an
+** ^The tdsqlite3_result_error_nomem() interface causes SQLite to throw an
** error indicating that a memory allocation failed.
**
-** ^The sqlite3_result_int() interface sets the return value
+** ^The tdsqlite3_result_int() interface sets the return value
** of the application-defined function to be the 32-bit signed integer
** value given in the 2nd argument.
-** ^The sqlite3_result_int64() interface sets the return value
+** ^The tdsqlite3_result_int64() interface sets the return value
** of the application-defined function to be the 64-bit signed integer
** value given in the 2nd argument.
**
-** ^The sqlite3_result_null() interface sets the return value
+** ^The tdsqlite3_result_null() interface sets the return value
** of the application-defined function to be NULL.
**
-** ^The sqlite3_result_text(), sqlite3_result_text16(),
-** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces
+** ^The tdsqlite3_result_text(), tdsqlite3_result_text16(),
+** tdsqlite3_result_text16le(), and tdsqlite3_result_text16be() interfaces
** set the return value of the application-defined function to be
** a text string which is represented as UTF-8, UTF-16 native byte order,
** UTF-16 little endian, or UTF-16 big endian, respectively.
-** ^The sqlite3_result_text64() interface sets the return value of an
+** ^The tdsqlite3_result_text64() interface sets the return value of an
** application-defined function to be a text string in an encoding
** specified by the fifth (and last) parameter, which must be one
** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE].
** ^SQLite takes the text result from the application from
-** the 2nd parameter of the sqlite3_result_text* interfaces.
-** ^If the 3rd parameter to the sqlite3_result_text* interfaces
+** the 2nd parameter of the tdsqlite3_result_text* interfaces.
+** ^If the 3rd parameter to the tdsqlite3_result_text* interfaces
** is negative, then SQLite takes result text from the 2nd parameter
** through the first zero character.
-** ^If the 3rd parameter to the sqlite3_result_text* interfaces
+** ^If the 3rd parameter to the tdsqlite3_result_text* interfaces
** is non-negative, then as many bytes (not characters) of the text
** pointed to by the 2nd parameter are taken as the application-defined
** function result. If the 3rd parameter is non-negative, then it
@@ -5115,82 +6640,94 @@ typedef void (*sqlite3_destructor_type)(void*);
** in the string at a byte offset that is less than the value of the 3rd
** parameter, then the resulting string will contain embedded NULs and the
** result of expressions operating on strings with embedded NULs is undefined.
-** ^If the 4th parameter to the sqlite3_result_text* interfaces
-** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that
+** ^If the 4th parameter to the tdsqlite3_result_text* interfaces
+** or tdsqlite3_result_blob is a non-NULL pointer, then SQLite calls that
** function as the destructor on the text or BLOB result when it has
** finished using that result.
-** ^If the 4th parameter to the sqlite3_result_text* interfaces or to
-** sqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite
+** ^If the 4th parameter to the tdsqlite3_result_text* interfaces or to
+** tdsqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite
** assumes that the text or BLOB result is in constant space and does not
** copy the content of the parameter nor call a destructor on the content
** when it has finished using that result.
-** ^If the 4th parameter to the sqlite3_result_text* interfaces
-** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT
-** then SQLite makes a copy of the result into space obtained from
-** from [sqlite3_malloc()] before it returns.
+** ^If the 4th parameter to the tdsqlite3_result_text* interfaces
+** or tdsqlite3_result_blob is the special constant SQLITE_TRANSIENT
+** then SQLite makes a copy of the result into space obtained
+** from [tdsqlite3_malloc()] before it returns.
**
-** ^The sqlite3_result_value() interface sets the result of
+** ^The tdsqlite3_result_value() interface sets the result of
** the application-defined function to be a copy of the
-** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The
-** sqlite3_result_value() interface makes a copy of the [sqlite3_value]
-** so that the [sqlite3_value] specified in the parameter may change or
-** be deallocated after sqlite3_result_value() returns without harm.
-** ^A [protected sqlite3_value] object may always be used where an
-** [unprotected sqlite3_value] object is required, so either
-** kind of [sqlite3_value] object can be used with this interface.
+** [unprotected tdsqlite3_value] object specified by the 2nd parameter. ^The
+** tdsqlite3_result_value() interface makes a copy of the [tdsqlite3_value]
+** so that the [tdsqlite3_value] specified in the parameter may change or
+** be deallocated after tdsqlite3_result_value() returns without harm.
+** ^A [protected tdsqlite3_value] object may always be used where an
+** [unprotected tdsqlite3_value] object is required, so either
+** kind of [tdsqlite3_value] object can be used with this interface.
+**
+** ^The tdsqlite3_result_pointer(C,P,T,D) interface sets the result to an
+** SQL NULL value, just like [tdsqlite3_result_null(C)], except that it
+** also associates the host-language pointer P or type T with that
+** NULL value such that the pointer can be retrieved within an
+** [application-defined SQL function] using [tdsqlite3_value_pointer()].
+** ^If the D parameter is not NULL, then it is a pointer to a destructor
+** for the P parameter. ^SQLite invokes D with P as its only argument
+** when SQLite is finished with P. The T parameter should be a static
+** string and preferably a string literal. The tdsqlite3_result_pointer()
+** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
**
** If these routines are called from within the different thread
** than the one containing the application-defined function that received
-** the [sqlite3_context] pointer, the results are undefined.
-*/
-SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*));
-SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*,
- sqlite3_uint64,void(*)(void*));
-SQLITE_API void sqlite3_result_double(sqlite3_context*, double);
-SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int);
-SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int);
-SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*);
-SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*);
-SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int);
-SQLITE_API void sqlite3_result_int(sqlite3_context*, int);
-SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64);
-SQLITE_API void sqlite3_result_null(sqlite3_context*);
-SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*));
-SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64,
+** the [tdsqlite3_context] pointer, the results are undefined.
+*/
+SQLITE_API void tdsqlite3_result_blob(tdsqlite3_context*, const void*, int, void(*)(void*));
+SQLITE_API void tdsqlite3_result_blob64(tdsqlite3_context*,const void*,
+ tdsqlite3_uint64,void(*)(void*));
+SQLITE_API void tdsqlite3_result_double(tdsqlite3_context*, double);
+SQLITE_API void tdsqlite3_result_error(tdsqlite3_context*, const char*, int);
+SQLITE_API void tdsqlite3_result_error16(tdsqlite3_context*, const void*, int);
+SQLITE_API void tdsqlite3_result_error_toobig(tdsqlite3_context*);
+SQLITE_API void tdsqlite3_result_error_nomem(tdsqlite3_context*);
+SQLITE_API void tdsqlite3_result_error_code(tdsqlite3_context*, int);
+SQLITE_API void tdsqlite3_result_int(tdsqlite3_context*, int);
+SQLITE_API void tdsqlite3_result_int64(tdsqlite3_context*, tdsqlite3_int64);
+SQLITE_API void tdsqlite3_result_null(tdsqlite3_context*);
+SQLITE_API void tdsqlite3_result_text(tdsqlite3_context*, const char*, int, void(*)(void*));
+SQLITE_API void tdsqlite3_result_text64(tdsqlite3_context*, const char*,tdsqlite3_uint64,
void(*)(void*), unsigned char encoding);
-SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*));
-SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*));
-SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*));
-SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*);
-SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n);
-SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n);
+SQLITE_API void tdsqlite3_result_text16(tdsqlite3_context*, const void*, int, void(*)(void*));
+SQLITE_API void tdsqlite3_result_text16le(tdsqlite3_context*, const void*, int,void(*)(void*));
+SQLITE_API void tdsqlite3_result_text16be(tdsqlite3_context*, const void*, int,void(*)(void*));
+SQLITE_API void tdsqlite3_result_value(tdsqlite3_context*, tdsqlite3_value*);
+SQLITE_API void tdsqlite3_result_pointer(tdsqlite3_context*, void*,const char*,void(*)(void*));
+SQLITE_API void tdsqlite3_result_zeroblob(tdsqlite3_context*, int n);
+SQLITE_API int tdsqlite3_result_zeroblob64(tdsqlite3_context*, tdsqlite3_uint64 n);
/*
** CAPI3REF: Setting The Subtype Of An SQL Function
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
-** The sqlite3_result_subtype(C,T) function causes the subtype of
+** The tdsqlite3_result_subtype(C,T) function causes the subtype of
** the result from the [application-defined SQL function] with
-** [sqlite3_context] C to be the value T. Only the lower 8 bits
+** [tdsqlite3_context] C to be the value T. Only the lower 8 bits
** of the subtype T are preserved in current versions of SQLite;
** higher order bits are discarded.
** The number of subtype bytes preserved by SQLite might increase
** in future releases of SQLite.
*/
-SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
+SQLITE_API void tdsqlite3_result_subtype(tdsqlite3_context*,unsigned int);
/*
** CAPI3REF: Define New Collating Sequences
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^These functions add, remove, or modify a [collation] associated
** with the [database connection] specified as the first argument.
**
** ^The name of the collation is a UTF-8 string
-** for sqlite3_create_collation() and sqlite3_create_collation_v2()
-** and a UTF-16 string in native byte order for sqlite3_create_collation16().
-** ^Collation names that compare equal according to [sqlite3_strnicmp()] are
+** for tdsqlite3_create_collation() and tdsqlite3_create_collation_v2()
+** and a UTF-16 string in native byte order for tdsqlite3_create_collation16().
+** ^Collation names that compare equal according to [tdsqlite3_strnicmp()] are
** considered to be the same name.
**
** ^(The third argument (eTextRep) must be one of the constants:
@@ -5202,7 +6739,7 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
** <li> [SQLITE_UTF16_ALIGNED].
** </ul>)^
** ^The eTextRep argument determines the encoding of strings passed
-** to the collating function callback, xCallback.
+** to the collating function callback, xCompare.
** ^The [SQLITE_UTF16] and [SQLITE_UTF16_ALIGNED] values for eTextRep
** force strings to be UTF16 with native byte order.
** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin
@@ -5211,18 +6748,19 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
** ^The fourth argument, pArg, is an application data pointer that is passed
** through as the first argument to the collating function callback.
**
-** ^The fifth argument, xCallback, is a pointer to the collating function.
+** ^The fifth argument, xCompare, is a pointer to the collating function.
** ^Multiple collating functions can be registered using the same name but
** with different eTextRep parameters and SQLite will use whichever
** function requires the least amount of data transformation.
-** ^If the xCallback argument is NULL then the collating function is
+** ^If the xCompare argument is NULL then the collating function is
** deleted. ^When all collating functions having the same name are deleted,
** that collation is no longer usable.
**
** ^The collating function callback is invoked with a copy of the pArg
** application data pointer and with two strings in the encoding specified
-** by the eTextRep argument. The collating function must return an
-** integer that is negative, zero, or positive
+** by the eTextRep argument. The two integer parameters to the collating
+** function callback are the length of the two strings, in bytes. The collating
+** function must return an integer that is negative, zero, or positive
** if the first string is less than, equal to, or greater than the second,
** respectively. A collating function must always return the same answer
** given the same inputs. If two or more collating functions are registered
@@ -5239,44 +6777,44 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
** </ol>
**
** If a collating function fails any of the above constraints and that
-** collating function is registered and used, then the behavior of SQLite
+** collating function is registered and used, then the behavior of SQLite
** is undefined.
**
-** ^The sqlite3_create_collation_v2() works like sqlite3_create_collation()
+** ^The tdsqlite3_create_collation_v2() works like tdsqlite3_create_collation()
** with the addition that the xDestroy callback is invoked on pArg when
** the collating function is deleted.
** ^Collating functions are deleted when they are overridden by later
** calls to the collation creation functions or when the
-** [database connection] is closed using [sqlite3_close()].
+** [database connection] is closed using [tdsqlite3_close()].
**
** ^The xDestroy callback is <u>not</u> called if the
-** sqlite3_create_collation_v2() function fails. Applications that invoke
-** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should
+** tdsqlite3_create_collation_v2() function fails. Applications that invoke
+** tdsqlite3_create_collation_v2() with a non-NULL xDestroy argument should
** check the return code and dispose of the application data pointer
** themselves rather than expecting SQLite to deal with it for them.
** This is different from every other SQLite interface. The inconsistency
** is unfortunate but cannot be changed without breaking backwards
** compatibility.
**
-** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()].
+** See also: [tdsqlite3_collation_needed()] and [tdsqlite3_collation_needed16()].
*/
-SQLITE_API int sqlite3_create_collation(
- sqlite3*,
+SQLITE_API int tdsqlite3_create_collation(
+ tdsqlite3*,
const char *zName,
int eTextRep,
void *pArg,
int(*xCompare)(void*,int,const void*,int,const void*)
);
-SQLITE_API int sqlite3_create_collation_v2(
- sqlite3*,
+SQLITE_API int tdsqlite3_create_collation_v2(
+ tdsqlite3*,
const char *zName,
int eTextRep,
void *pArg,
int(*xCompare)(void*,int,const void*,int,const void*),
void(*xDestroy)(void*)
);
-SQLITE_API int sqlite3_create_collation16(
- sqlite3*,
+SQLITE_API int tdsqlite3_create_collation16(
+ tdsqlite3*,
const void *zName,
int eTextRep,
void *pArg,
@@ -5285,56 +6823,56 @@ SQLITE_API int sqlite3_create_collation16(
/*
** CAPI3REF: Collation Needed Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^To avoid having to register all collation sequences before a database
** can be used, a single callback function may be registered with the
** [database connection] to be invoked whenever an undefined collation
** sequence is required.
**
-** ^If the function is registered using the sqlite3_collation_needed() API,
+** ^If the function is registered using the tdsqlite3_collation_needed() API,
** then it is passed the names of undefined collation sequences as strings
-** encoded in UTF-8. ^If sqlite3_collation_needed16() is used,
+** encoded in UTF-8. ^If tdsqlite3_collation_needed16() is used,
** the names are passed as UTF-16 in machine native byte order.
** ^A call to either function replaces the existing collation-needed callback.
**
** ^(When the callback is invoked, the first argument passed is a copy
-** of the second argument to sqlite3_collation_needed() or
-** sqlite3_collation_needed16(). The second argument is the database
+** of the second argument to tdsqlite3_collation_needed() or
+** tdsqlite3_collation_needed16(). The second argument is the database
** connection. The third argument is one of [SQLITE_UTF8], [SQLITE_UTF16BE],
** or [SQLITE_UTF16LE], indicating the most desirable form of the collation
** sequence function required. The fourth parameter is the name of the
** required collation sequence.)^
**
** The callback function should register the desired collation using
-** [sqlite3_create_collation()], [sqlite3_create_collation16()], or
-** [sqlite3_create_collation_v2()].
+** [tdsqlite3_create_collation()], [tdsqlite3_create_collation16()], or
+** [tdsqlite3_create_collation_v2()].
*/
-SQLITE_API int sqlite3_collation_needed(
- sqlite3*,
+SQLITE_API int tdsqlite3_collation_needed(
+ tdsqlite3*,
void*,
- void(*)(void*,sqlite3*,int eTextRep,const char*)
+ void(*)(void*,tdsqlite3*,int eTextRep,const char*)
);
-SQLITE_API int sqlite3_collation_needed16(
- sqlite3*,
+SQLITE_API int tdsqlite3_collation_needed16(
+ tdsqlite3*,
void*,
- void(*)(void*,sqlite3*,int eTextRep,const void*)
+ void(*)(void*,tdsqlite3*,int eTextRep,const void*)
);
#ifdef SQLITE_HAS_CODEC
/*
** Specify the key for an encrypted database. This routine should be
-** called right after sqlite3_open().
+** called right after tdsqlite3_open().
**
** The code to implement this API is not available in the public release
** of SQLite.
*/
-SQLITE_API int sqlite3_key(
- sqlite3 *db, /* Database to be rekeyed */
+SQLITE_API int tdsqlite3_key(
+ tdsqlite3 *db, /* Database to be rekeyed */
const void *pKey, int nKey /* The key */
);
-SQLITE_API int sqlite3_key_v2(
- sqlite3 *db, /* Database to be rekeyed */
+SQLITE_API int tdsqlite3_key_v2(
+ tdsqlite3 *db, /* Database to be rekeyed */
const char *zDbName, /* Name of the database */
const void *pKey, int nKey /* The key */
);
@@ -5347,12 +6885,28 @@ SQLITE_API int sqlite3_key_v2(
** The code to implement this API is not available in the public release
** of SQLite.
*/
-SQLITE_API int sqlite3_rekey(
- sqlite3 *db, /* Database to be rekeyed */
+/* BEGIN SQLCIPHER
+ SQLCipher usage note:
+
+ If the current database is plaintext SQLCipher will NOT encrypt it.
+ If the current database is encrypted and pNew==0 or nNew==0, SQLCipher
+ will NOT decrypt it.
+
+ This routine will ONLY work on an already encrypted database in order
+ to change the key.
+
+ Conversion from plaintext-to-encrypted or encrypted-to-plaintext should
+ use an ATTACHed database and the sqlcipher_export() convenience function
+ as per the SQLCipher Documentation.
+
+ END SQLCIPHER
+*/
+SQLITE_API int tdsqlite3_rekey(
+ tdsqlite3 *db, /* Database to be rekeyed */
const void *pKey, int nKey /* The new key */
);
-SQLITE_API int sqlite3_rekey_v2(
- sqlite3 *db, /* Database to be rekeyed */
+SQLITE_API int tdsqlite3_rekey_v2(
+ tdsqlite3 *db, /* Database to be rekeyed */
const char *zDbName, /* Name of the database */
const void *pKey, int nKey /* The new key */
);
@@ -5361,7 +6915,7 @@ SQLITE_API int sqlite3_rekey_v2(
** Specify the activation key for a SEE database. Unless
** activated, none of the SEE routines will work.
*/
-SQLITE_API void sqlite3_activate_see(
+SQLITE_API void tdsqlite3_activate_see(
const char *zPassPhrase /* Activation phrase */
);
#endif
@@ -5371,7 +6925,7 @@ SQLITE_API void sqlite3_activate_see(
** Specify the activation key for a CEROD database. Unless
** activated, none of the CEROD routines will work.
*/
-SQLITE_API void sqlite3_activate_cerod(
+SQLITE_API void tdsqlite3_activate_cerod(
const char *zPassPhrase /* Activation phrase */
);
#endif
@@ -5379,7 +6933,7 @@ SQLITE_API void sqlite3_activate_cerod(
/*
** CAPI3REF: Suspend Execution For A Short Time
**
-** The sqlite3_sleep() function causes the current thread to suspend execution
+** The tdsqlite3_sleep() function causes the current thread to suspend execution
** for at least a number of milliseconds specified in its parameter.
**
** If the operating system does not support sleep requests with
@@ -5388,19 +6942,19 @@ SQLITE_API void sqlite3_activate_cerod(
** requested from the operating system is returned.
**
** ^SQLite implements this interface by calling the xSleep()
-** method of the default [sqlite3_vfs] object. If the xSleep() method
+** method of the default [tdsqlite3_vfs] object. If the xSleep() method
** of the default VFS is not implemented correctly, or not implemented at
-** all, then the behavior of sqlite3_sleep() may deviate from the description
+** all, then the behavior of tdsqlite3_sleep() may deviate from the description
** in the previous paragraphs.
*/
-SQLITE_API int sqlite3_sleep(int);
+SQLITE_API int tdsqlite3_sleep(int);
/*
** CAPI3REF: Name Of The Folder Holding Temporary Files
**
** ^(If this global variable is made to point to a string which is
** the name of a folder (a.k.a. directory), then all temporary files
-** created by SQLite when using a built-in [sqlite3_vfs | VFS]
+** created by SQLite when using a built-in [tdsqlite3_vfs | VFS]
** will be placed in that directory.)^ ^If this variable
** is a NULL pointer, then SQLite performs a search for an appropriate
** temporary file directory.
@@ -5422,22 +6976,22 @@ SQLITE_API int sqlite3_sleep(int);
** thereafter.
**
** ^The [temp_store_directory pragma] may modify this variable and cause
-** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore,
+** it to point to memory obtained from [tdsqlite3_malloc]. ^Furthermore,
** the [temp_store_directory pragma] always assumes that any string
** that this variable points to is held in memory obtained from
-** [sqlite3_malloc] and the pragma may attempt to free that memory
-** using [sqlite3_free].
+** [tdsqlite3_malloc] and the pragma may attempt to free that memory
+** using [tdsqlite3_free].
** Hence, if this variable is modified directly, either it should be
-** made NULL or made to point to memory obtained from [sqlite3_malloc]
+** made NULL or made to point to memory obtained from [tdsqlite3_malloc]
** or else the use of the [temp_store_directory pragma] should be avoided.
** Except when requested by the [temp_store_directory pragma], SQLite
-** does not free the memory that sqlite3_temp_directory points to. If
+** does not free the memory that tdsqlite3_temp_directory points to. If
** the application wants that memory to be freed, it must do
** so itself, taking care to only do so after all [database connection]
** objects have been destroyed.
**
** <b>Note to Windows Runtime users:</b> The temporary directory must be set
-** prior to calling [sqlite3_open] or [sqlite3_open_v2]. Otherwise, various
+** prior to calling [tdsqlite3_open] or [tdsqlite3_open_v2]. Otherwise, various
** features that require the use of temporary files may fail. Here is an
** example of how to do this using C++ with the Windows Runtime:
**
@@ -5448,10 +7002,10 @@ SQLITE_API int sqlite3_sleep(int);
** memset(zPathBuf, 0, sizeof(zPathBuf));
** WideCharToMultiByte(CP_UTF8, 0, zPath, -1, zPathBuf, sizeof(zPathBuf),
** &nbsp; NULL, NULL);
-** sqlite3_temp_directory = sqlite3_mprintf("%s", zPathBuf);
+** tdsqlite3_temp_directory = tdsqlite3_mprintf("%s", zPathBuf);
** </pre></blockquote>
*/
-SQLITE_API char *sqlite3_temp_directory;
+SQLITE_API char *tdsqlite3_temp_directory;
/*
** CAPI3REF: Name Of The Folder Holding Database Files
@@ -5459,7 +7013,7 @@ SQLITE_API char *sqlite3_temp_directory;
** ^(If this global variable is made to point to a string which is
** the name of a folder (a.k.a. directory), then all database files
** specified with a relative pathname and created or accessed by
-** SQLite when using a built-in windows [sqlite3_vfs | VFS] will be assumed
+** SQLite when using a built-in windows [tdsqlite3_vfs | VFS] will be assumed
** to be relative to that directory.)^ ^If this variable is a NULL
** pointer, then SQLite assumes that all database files specified
** with a relative pathname are relative to the current directory
@@ -5479,23 +7033,58 @@ SQLITE_API char *sqlite3_temp_directory;
** thereafter.
**
** ^The [data_store_directory pragma] may modify this variable and cause
-** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore,
+** it to point to memory obtained from [tdsqlite3_malloc]. ^Furthermore,
** the [data_store_directory pragma] always assumes that any string
** that this variable points to is held in memory obtained from
-** [sqlite3_malloc] and the pragma may attempt to free that memory
-** using [sqlite3_free].
+** [tdsqlite3_malloc] and the pragma may attempt to free that memory
+** using [tdsqlite3_free].
** Hence, if this variable is modified directly, either it should be
-** made NULL or made to point to memory obtained from [sqlite3_malloc]
+** made NULL or made to point to memory obtained from [tdsqlite3_malloc]
** or else the use of the [data_store_directory pragma] should be avoided.
*/
-SQLITE_API char *sqlite3_data_directory;
+SQLITE_API char *tdsqlite3_data_directory;
+
+/*
+** CAPI3REF: Win32 Specific Interface
+**
+** These interfaces are available only on Windows. The
+** [tdsqlite3_win32_set_directory] interface is used to set the value associated
+** with the [tdsqlite3_temp_directory] or [tdsqlite3_data_directory] variable, to
+** zValue, depending on the value of the type parameter. The zValue parameter
+** should be NULL to cause the previous value to be freed via [tdsqlite3_free];
+** a non-NULL value will be copied into memory obtained from [tdsqlite3_malloc]
+** prior to being used. The [tdsqlite3_win32_set_directory] interface returns
+** [SQLITE_OK] to indicate success, [SQLITE_ERROR] if the type is unsupported,
+** or [SQLITE_NOMEM] if memory could not be allocated. The value of the
+** [tdsqlite3_data_directory] variable is intended to act as a replacement for
+** the current directory on the sub-platforms of Win32 where that concept is
+** not present, e.g. WinRT and UWP. The [tdsqlite3_win32_set_directory8] and
+** [tdsqlite3_win32_set_directory16] interfaces behave exactly the same as the
+** tdsqlite3_win32_set_directory interface except the string parameter must be
+** UTF-8 or UTF-16, respectively.
+*/
+SQLITE_API int tdsqlite3_win32_set_directory(
+ unsigned long type, /* Identifier for directory being set or reset */
+ void *zValue /* New value for directory being set or reset */
+);
+SQLITE_API int tdsqlite3_win32_set_directory8(unsigned long type, const char *zValue);
+SQLITE_API int tdsqlite3_win32_set_directory16(unsigned long type, const void *zValue);
+
+/*
+** CAPI3REF: Win32 Directory Types
+**
+** These macros are only available on Windows. They define the allowed values
+** for the type argument to the [tdsqlite3_win32_set_directory] interface.
+*/
+#define SQLITE_WIN32_DATA_DIRECTORY_TYPE 1
+#define SQLITE_WIN32_TEMP_DIRECTORY_TYPE 2
/*
** CAPI3REF: Test For Auto-Commit Mode
** KEYWORDS: {autocommit mode}
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_get_autocommit() interface returns non-zero or
+** ^The tdsqlite3_get_autocommit() interface returns non-zero or
** zero if the given database connection is or is not in autocommit mode,
** respectively. ^Autocommit mode is on by default.
** ^Autocommit mode is disabled by a [BEGIN] statement.
@@ -5512,51 +7101,66 @@ SQLITE_API char *sqlite3_data_directory;
** connection while this routine is running, then the return value
** is undefined.
*/
-SQLITE_API int sqlite3_get_autocommit(sqlite3*);
+SQLITE_API int tdsqlite3_get_autocommit(tdsqlite3*);
/*
** CAPI3REF: Find The Database Handle Of A Prepared Statement
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_db_handle interface returns the [database connection] handle
+** ^The tdsqlite3_db_handle interface returns the [database connection] handle
** to which a [prepared statement] belongs. ^The [database connection]
-** returned by sqlite3_db_handle is the same [database connection]
+** returned by tdsqlite3_db_handle is the same [database connection]
** that was the first argument
-** to the [sqlite3_prepare_v2()] call (or its variants) that was used to
+** to the [tdsqlite3_prepare_v2()] call (or its variants) that was used to
** create the statement in the first place.
*/
-SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
+SQLITE_API tdsqlite3 *tdsqlite3_db_handle(tdsqlite3_stmt*);
/*
** CAPI3REF: Return The Filename For A Database Connection
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_db_filename(D,N) interface returns a pointer to a filename
-** associated with database N of connection D. ^The main database file
-** has the name "main". If there is no attached database N on the database
+** ^The tdsqlite3_db_filename(D,N) interface returns a pointer to the filename
+** associated with database N of connection D.
+** ^If there is no attached database N on the database
** connection D, or if database N is a temporary or in-memory database, then
-** a NULL pointer is returned.
+** this function will return either a NULL pointer or an empty string.
+**
+** ^The string value returned by this routine is owned and managed by
+** the database connection. ^The value will be valid until the database N
+** is [DETACH]-ed or until the database connection closes.
**
** ^The filename returned by this function is the output of the
** xFullPathname method of the [VFS]. ^In other words, the filename
** will be an absolute pathname, even if the filename used
** to open the database originally was a URI or relative pathname.
+**
+** If the filename pointer returned by this routine is not NULL, then it
+** can be used as the filename input parameter to these routines:
+** <ul>
+** <li> [tdsqlite3_uri_parameter()]
+** <li> [tdsqlite3_uri_boolean()]
+** <li> [tdsqlite3_uri_int64()]
+** <li> [tdsqlite3_filename_database()]
+** <li> [tdsqlite3_filename_journal()]
+** <li> [tdsqlite3_filename_wal()]
+** </ul>
*/
-SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName);
+SQLITE_API const char *tdsqlite3_db_filename(tdsqlite3 *db, const char *zDbName);
/*
** CAPI3REF: Determine if a database is read-only
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_db_readonly(D,N) interface returns 1 if the database N
+** ^The tdsqlite3_db_readonly(D,N) interface returns 1 if the database N
** of connection D is read-only, 0 if it is read/write, or -1 if N is not
** the name of a database on connection D.
*/
-SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName);
+SQLITE_API int tdsqlite3_db_readonly(tdsqlite3 *db, const char *zDbName);
/*
** CAPI3REF: Find the next prepared statement
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This interface returns a pointer to the next [prepared statement] after
** pStmt associated with the [database connection] pDb. ^If pStmt is NULL
@@ -5565,28 +7169,28 @@ SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName);
** satisfies the conditions of this routine, it returns NULL.
**
** The [database connection] pointer D in a call to
-** [sqlite3_next_stmt(D,S)] must refer to an open database
+** [tdsqlite3_next_stmt(D,S)] must refer to an open database
** connection and in particular must not be a NULL pointer.
*/
-SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt);
+SQLITE_API tdsqlite3_stmt *tdsqlite3_next_stmt(tdsqlite3 *pDb, tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Commit And Rollback Notification Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_commit_hook() interface registers a callback
+** ^The tdsqlite3_commit_hook() interface registers a callback
** function to be invoked whenever a transaction is [COMMIT | committed].
-** ^Any callback set by a previous call to sqlite3_commit_hook()
+** ^Any callback set by a previous call to tdsqlite3_commit_hook()
** for the same database connection is overridden.
-** ^The sqlite3_rollback_hook() interface registers a callback
+** ^The tdsqlite3_rollback_hook() interface registers a callback
** function to be invoked whenever a transaction is [ROLLBACK | rolled back].
-** ^Any callback set by a previous call to sqlite3_rollback_hook()
+** ^Any callback set by a previous call to tdsqlite3_rollback_hook()
** for the same database connection is overridden.
** ^The pArg argument is passed through to the callback.
** ^If the callback on a commit hook function returns non-zero,
** then the commit is converted into a rollback.
**
-** ^The sqlite3_commit_hook(D,C,P) and sqlite3_rollback_hook(D,C,P) functions
+** ^The tdsqlite3_commit_hook(D,C,P) and tdsqlite3_rollback_hook(D,C,P) functions
** return the P argument from the previous call of the same function
** on the same [database connection] D, or NULL for
** the first call for each function on D.
@@ -5595,10 +7199,10 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt);
** The callback implementation must not do anything that will modify
** the database connection that invoked the callback. Any actions
** to modify the database connection must be deferred until after the
-** completion of the [sqlite3_step()] call that triggered the commit
+** completion of the [tdsqlite3_step()] call that triggered the commit
** or rollback hook in the first place.
** Note that running any other SQL statements, including SELECT statements,
-** or merely calling [sqlite3_prepare_v2()] and [sqlite3_step()] will modify
+** or merely calling [tdsqlite3_prepare_v2()] and [tdsqlite3_step()] will modify
** the database connections for the meaning of "modify" in this paragraph.
**
** ^Registering a NULL function disables the callback.
@@ -5615,16 +7219,16 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt);
** ^The rollback callback is not invoked if a transaction is
** automatically rolled back because the database connection is closed.
**
-** See also the [sqlite3_update_hook()] interface.
+** See also the [tdsqlite3_update_hook()] interface.
*/
-SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*);
-SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
+SQLITE_API void *tdsqlite3_commit_hook(tdsqlite3*, int(*)(void*), void*);
+SQLITE_API void *tdsqlite3_rollback_hook(tdsqlite3*, void(*)(void *), void*);
/*
** CAPI3REF: Data Change Notification Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_update_hook() interface registers a callback function
+** ^The tdsqlite3_update_hook() interface registers a callback function
** with the [database connection] identified by the first argument
** to be invoked whenever a row is updated, inserted or deleted in
** a [rowid table].
@@ -5634,7 +7238,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** ^The second argument is a pointer to the function to invoke when a
** row is updated, inserted or deleted in a rowid table.
** ^The first argument to the callback is a copy of the third argument
-** to sqlite3_update_hook().
+** to tdsqlite3_update_hook().
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE],
** or [SQLITE_UPDATE], depending on the operation that caused the callback
** to be invoked.
@@ -5648,7 +7252,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified.
**
** ^In the current implementation, the update hook
-** is not invoked when duplication rows are deleted because of an
+** is not invoked when conflicting rows are deleted because of an
** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook
** invoked when rows are deleted using the [truncate optimization].
** The exceptions defined in this paragraph might change in a future
@@ -5657,21 +7261,21 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** The update hook implementation must not do anything that will modify
** the database connection that invoked the update hook. Any actions
** to modify the database connection must be deferred until after the
-** completion of the [sqlite3_step()] call that triggered the update hook.
-** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their
+** completion of the [tdsqlite3_step()] call that triggered the update hook.
+** Note that [tdsqlite3_prepare_v2()] and [tdsqlite3_step()] both modify their
** database connections for the meaning of "modify" in this paragraph.
**
-** ^The sqlite3_update_hook(D,C,P) function
+** ^The tdsqlite3_update_hook(D,C,P) function
** returns the P argument from the previous call
** on the same [database connection] D, or NULL for
** the first call on D.
**
-** See also the [sqlite3_commit_hook()], [sqlite3_rollback_hook()],
-** and [sqlite3_preupdate_hook()] interfaces.
+** See also the [tdsqlite3_commit_hook()], [tdsqlite3_rollback_hook()],
+** and [tdsqlite3_preupdate_hook()] interfaces.
*/
-SQLITE_API void *sqlite3_update_hook(
- sqlite3*,
- void(*)(void *,int ,char const *,char const *,sqlite3_int64),
+SQLITE_API void *tdsqlite3_update_hook(
+ tdsqlite3*,
+ void(*)(void *,int ,char const *,char const *,tdsqlite3_int64),
void*
);
@@ -5689,63 +7293,70 @@ SQLITE_API void *sqlite3_update_hook(
** sharing was enabled or disabled for each thread separately.
**
** ^(The cache sharing mode set by this interface effects all subsequent
-** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()].
-** Existing database connections continue use the sharing mode
+** calls to [tdsqlite3_open()], [tdsqlite3_open_v2()], and [tdsqlite3_open16()].
+** Existing database connections continue to use the sharing mode
** that was in effect at the time they were opened.)^
**
** ^(This routine returns [SQLITE_OK] if shared cache was enabled or disabled
** successfully. An [error code] is returned otherwise.)^
**
-** ^Shared cache is disabled by default. But this might change in
-** future releases of SQLite. Applications that care about shared
-** cache setting should set it explicitly.
+** ^Shared cache is disabled by default. It is recommended that it stay
+** that way. In other words, do not use this routine. This interface
+** continues to be provided for historical compatibility, but its use is
+** discouraged. Any use of shared cache is discouraged. If shared cache
+** must be used, it is recommended that shared cache only be enabled for
+** individual database connections using the [tdsqlite3_open_v2()] interface
+** with the [SQLITE_OPEN_SHAREDCACHE] flag.
**
** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0
** and will always return SQLITE_MISUSE. On those systems,
** shared cache mode should be enabled per-database connection via
-** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE].
+** [tdsqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE].
**
** This interface is threadsafe on processors where writing a
** 32-bit integer is atomic.
**
** See Also: [SQLite Shared-Cache Mode]
*/
-SQLITE_API int sqlite3_enable_shared_cache(int);
+SQLITE_API int tdsqlite3_enable_shared_cache(int);
/*
** CAPI3REF: Attempt To Free Heap Memory
**
-** ^The sqlite3_release_memory() interface attempts to free N bytes
+** ^The tdsqlite3_release_memory() interface attempts to free N bytes
** of heap memory by deallocating non-essential memory allocations
** held by the database library. Memory used to cache database
** pages to improve performance is an example of non-essential memory.
-** ^sqlite3_release_memory() returns the number of bytes actually freed,
+** ^tdsqlite3_release_memory() returns the number of bytes actually freed,
** which might be more or less than the amount requested.
-** ^The sqlite3_release_memory() routine is a no-op returning zero
+** ^The tdsqlite3_release_memory() routine is a no-op returning zero
** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT].
**
-** See also: [sqlite3_db_release_memory()]
+** See also: [tdsqlite3_db_release_memory()]
*/
-SQLITE_API int sqlite3_release_memory(int);
+SQLITE_API int tdsqlite3_release_memory(int);
/*
** CAPI3REF: Free Memory Used By A Database Connection
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_db_release_memory(D) interface attempts to free as much heap
+** ^The tdsqlite3_db_release_memory(D) interface attempts to free as much heap
** memory as possible from database connection D. Unlike the
-** [sqlite3_release_memory()] interface, this interface is in effect even
+** [tdsqlite3_release_memory()] interface, this interface is in effect even
** when the [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is
** omitted.
**
-** See also: [sqlite3_release_memory()]
+** See also: [tdsqlite3_release_memory()]
*/
-SQLITE_API int sqlite3_db_release_memory(sqlite3*);
+SQLITE_API int tdsqlite3_db_release_memory(tdsqlite3*);
/*
** CAPI3REF: Impose A Limit On Heap Size
**
-** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
+** These interfaces impose limits on the amount of heap memory that will be
+** by all database connections within a single process.
+**
+** ^The tdsqlite3_soft_heap_limit64() interface sets and/or queries the
** soft limit on the amount of heap memory that may be allocated by SQLite.
** ^SQLite strives to keep heap memory utilization below the soft heap
** limit by reducing the number of pages held in the page cache
@@ -5755,73 +7366,86 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
** an [SQLITE_NOMEM] error. In other words, the soft heap limit
** is advisory only.
**
-** ^The return value from sqlite3_soft_heap_limit64() is the size of
-** the soft heap limit prior to the call, or negative in the case of an
-** error. ^If the argument N is negative
-** then no change is made to the soft heap limit. Hence, the current
-** size of the soft heap limit can be determined by invoking
-** sqlite3_soft_heap_limit64() with a negative argument.
-**
-** ^If the argument N is zero then the soft heap limit is disabled.
+** ^The tdsqlite3_hard_heap_limit64(N) interface sets a hard upper bound of
+** N bytes on the amount of memory that will be allocated. ^The
+** tdsqlite3_hard_heap_limit64(N) interface is similar to
+** tdsqlite3_soft_heap_limit64(N) except that memory allocations will fail
+** when the hard heap limit is reached.
**
-** ^(The soft heap limit is not enforced in the current implementation
+** ^The return value from both tdsqlite3_soft_heap_limit64() and
+** tdsqlite3_hard_heap_limit64() is the size of
+** the heap limit prior to the call, or negative in the case of an
+** error. ^If the argument N is negative
+** then no change is made to the heap limit. Hence, the current
+** size of heap limits can be determined by invoking
+** tdsqlite3_soft_heap_limit64(-1) or tdsqlite3_hard_heap_limit(-1).
+**
+** ^Setting the heap limits to zero disables the heap limiter mechanism.
+**
+** ^The soft heap limit may not be greater than the hard heap limit.
+** ^If the hard heap limit is enabled and if tdsqlite3_soft_heap_limit(N)
+** is invoked with a value of N that is greater than the hard heap limit,
+** the the soft heap limit is set to the value of the hard heap limit.
+** ^The soft heap limit is automatically enabled whenever the hard heap
+** limit is enabled. ^When tdsqlite3_hard_heap_limit64(N) is invoked and
+** the soft heap limit is outside the range of 1..N, then the soft heap
+** limit is set to N. ^Invoking tdsqlite3_soft_heap_limit64(0) when the
+** hard heap limit is enabled makes the soft heap limit equal to the
+** hard heap limit.
+**
+** The memory allocation limits can also be adjusted using
+** [PRAGMA soft_heap_limit] and [PRAGMA hard_heap_limit].
+**
+** ^(The heap limits are not enforced in the current implementation
** if one or more of following conditions are true:
**
** <ul>
-** <li> The soft heap limit is set to zero.
+** <li> The limit value is set to zero.
** <li> Memory accounting is disabled using a combination of the
-** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and
+** [tdsqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and
** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option.
** <li> An alternative page cache implementation is specified using
-** [sqlite3_config]([SQLITE_CONFIG_PCACHE2],...).
+** [tdsqlite3_config]([SQLITE_CONFIG_PCACHE2],...).
** <li> The page cache allocates from its own memory pool supplied
-** by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than
+** by [tdsqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than
** from the heap.
** </ul>)^
**
-** Beginning with SQLite [version 3.7.3] ([dateof:3.7.3]),
-** the soft heap limit is enforced
-** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT]
-** compile-time option is invoked. With [SQLITE_ENABLE_MEMORY_MANAGEMENT],
-** the soft heap limit is enforced on every memory allocation. Without
-** [SQLITE_ENABLE_MEMORY_MANAGEMENT], the soft heap limit is only enforced
-** when memory is allocated by the page cache. Testing suggests that because
-** the page cache is the predominate memory user in SQLite, most
-** applications will achieve adequate soft heap limit enforcement without
-** the use of [SQLITE_ENABLE_MEMORY_MANAGEMENT].
-**
-** The circumstances under which SQLite will enforce the soft heap limit may
+** The circumstances under which SQLite will enforce the heap limits may
** changes in future releases of SQLite.
*/
-SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
+SQLITE_API tdsqlite3_int64 tdsqlite3_soft_heap_limit64(tdsqlite3_int64 N);
+SQLITE_API tdsqlite3_int64 tdsqlite3_hard_heap_limit64(tdsqlite3_int64 N);
/*
** CAPI3REF: Deprecated Soft Heap Limit Interface
** DEPRECATED
**
-** This is a deprecated version of the [sqlite3_soft_heap_limit64()]
+** This is a deprecated version of the [tdsqlite3_soft_heap_limit64()]
** interface. This routine is provided for historical compatibility
** only. All new applications should use the
-** [sqlite3_soft_heap_limit64()] interface rather than this one.
+** [tdsqlite3_soft_heap_limit64()] interface rather than this one.
*/
-SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
+SQLITE_API SQLITE_DEPRECATED void tdsqlite3_soft_heap_limit(int N);
/*
** CAPI3REF: Extract Metadata About A Column Of A Table
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns
+** ^(The tdsqlite3_table_column_metadata(X,D,T,C,....) routine returns
** information about column C of table T in database D
-** on [database connection] X.)^ ^The sqlite3_table_column_metadata()
+** on [database connection] X.)^ ^The tdsqlite3_table_column_metadata()
** interface returns SQLITE_OK and fills in the non-NULL pointers in
** the final five arguments with appropriate values if the specified
-** column exists. ^The sqlite3_table_column_metadata() interface returns
-** SQLITE_ERROR and if the specified column does not exist.
-** ^If the column-name parameter to sqlite3_table_column_metadata() is a
+** column exists. ^The tdsqlite3_table_column_metadata() interface returns
+** SQLITE_ERROR if the specified column does not exist.
+** ^If the column-name parameter to tdsqlite3_table_column_metadata() is a
** NULL pointer, then this routine simply checks for the existence of the
** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it
-** does not.
+** does not. If the table name parameter T in a call to
+** tdsqlite3_table_column_metadata(X,D,T,C,...) is NULL then the result is
+** undefined behavior.
**
** ^The column is identified by the second, third and fourth parameters to
** this function. ^(The second parameter is either the name of the database
@@ -5874,8 +7498,8 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
** parsed, if that has not already been done, and returns an error if
** any errors are encountered while loading the schema.
*/
-SQLITE_API int sqlite3_table_column_metadata(
- sqlite3 *db, /* Connection handle */
+SQLITE_API int tdsqlite3_table_column_metadata(
+ tdsqlite3 *db, /* Connection handle */
const char *zDbName, /* Database name or NULL */
const char *zTableName, /* Table name */
const char *zColumnName, /* Column name */
@@ -5888,11 +7512,11 @@ SQLITE_API int sqlite3_table_column_metadata(
/*
** CAPI3REF: Load An Extension
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This interface loads an SQLite extension library from the named file.
**
-** ^The sqlite3_load_extension() interface attempts to load an
+** ^The tdsqlite3_load_extension() interface attempts to load an
** [SQLite extension] library contained in the file zFile. If
** the file cannot be loaded directly, attempts are made to load
** with various operating-system specific extensions added.
@@ -5902,36 +7526,36 @@ SQLITE_API int sqlite3_table_column_metadata(
**
** ^The entry point is zProc.
** ^(zProc may be 0, in which case SQLite will try to come up with an
-** entry point name on its own. It first tries "sqlite3_extension_init".
-** If that does not work, it constructs a name "sqlite3_X_init" where the
+** entry point name on its own. It first tries "tdsqlite3_extension_init".
+** If that does not work, it constructs a name "tdsqlite3_X_init" where the
** X is consists of the lower-case equivalent of all ASCII alphabetic
** characters in the filename from the last "/" to the first following
** "." and omitting any initial "lib".)^
-** ^The sqlite3_load_extension() interface returns
+** ^The tdsqlite3_load_extension() interface returns
** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong.
** ^If an error occurs and pzErrMsg is not 0, then the
-** [sqlite3_load_extension()] interface shall attempt to
+** [tdsqlite3_load_extension()] interface shall attempt to
** fill *pzErrMsg with error message text stored in memory
-** obtained from [sqlite3_malloc()]. The calling function
-** should free this memory by calling [sqlite3_free()].
+** obtained from [tdsqlite3_malloc()]. The calling function
+** should free this memory by calling [tdsqlite3_free()].
**
** ^Extension loading must be enabled using
-** [sqlite3_enable_load_extension()] or
-** [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],1,NULL)
+** [tdsqlite3_enable_load_extension()] or
+** [tdsqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],1,NULL)
** prior to calling this API,
** otherwise an error will be returned.
**
** <b>Security warning:</b> It is recommended that the
** [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method be used to enable only this
-** interface. The use of the [sqlite3_enable_load_extension()] interface
+** interface. The use of the [tdsqlite3_enable_load_extension()] interface
** should be avoided. This will keep the SQL function [load_extension()]
** disabled and prevent SQL injections from giving attackers
** access to extension loading capabilities.
**
** See also the [load_extension() SQL function].
*/
-SQLITE_API int sqlite3_load_extension(
- sqlite3 *db, /* Load the extension into this database connection */
+SQLITE_API int tdsqlite3_load_extension(
+ tdsqlite3 *db, /* Load the extension into this database connection */
const char *zFile, /* Name of the shared library containing extension */
const char *zProc, /* Entry point. Derived from zFile if 0 */
char **pzErrMsg /* Put error message here if not 0 */
@@ -5939,30 +7563,30 @@ SQLITE_API int sqlite3_load_extension(
/*
** CAPI3REF: Enable Or Disable Extension Loading
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^So as not to open security holes in older applications that are
** unprepared to deal with [extension loading], and as a means of disabling
** [extension loading] while evaluating user-entered SQL, the following API
-** is provided to turn the [sqlite3_load_extension()] mechanism on and off.
+** is provided to turn the [tdsqlite3_load_extension()] mechanism on and off.
**
** ^Extension loading is off by default.
-** ^Call the sqlite3_enable_load_extension() routine with onoff==1
+** ^Call the tdsqlite3_enable_load_extension() routine with onoff==1
** to turn extension loading on and call it with onoff==0 to turn
** it back off again.
**
** ^This interface enables or disables both the C-API
-** [sqlite3_load_extension()] and the SQL function [load_extension()].
-** ^(Use [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],..)
+** [tdsqlite3_load_extension()] and the SQL function [load_extension()].
+** ^(Use [tdsqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],..)
** to enable or disable only the C-API.)^
**
** <b>Security warning:</b> It is recommended that extension loading
-** be disabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method
+** be enabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method
** rather than this interface, so the [load_extension()] SQL function
** remains disabled. This will prevent SQL injections from giving attackers
** access to extension loading capabilities.
*/
-SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
+SQLITE_API int tdsqlite3_enable_load_extension(tdsqlite3 *db, int onoff);
/*
** CAPI3REF: Automatically Load Statically Linked Extensions
@@ -5979,48 +7603,48 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
**
** <blockquote><pre>
** &nbsp; int xEntryPoint(
-** &nbsp; sqlite3 *db,
+** &nbsp; tdsqlite3 *db,
** &nbsp; const char **pzErrMsg,
-** &nbsp; const struct sqlite3_api_routines *pThunk
+** &nbsp; const struct tdsqlite3_api_routines *pThunk
** &nbsp; );
** </pre></blockquote>)^
**
** If the xEntryPoint routine encounters an error, it should make *pzErrMsg
-** point to an appropriate error message (obtained from [sqlite3_mprintf()])
+** point to an appropriate error message (obtained from [tdsqlite3_mprintf()])
** and return an appropriate [error code]. ^SQLite ensures that *pzErrMsg
** is NULL before calling the xEntryPoint(). ^SQLite will invoke
-** [sqlite3_free()] on *pzErrMsg after xEntryPoint() returns. ^If any
-** xEntryPoint() returns an error, the [sqlite3_open()], [sqlite3_open16()],
-** or [sqlite3_open_v2()] call that provoked the xEntryPoint() will fail.
+** [tdsqlite3_free()] on *pzErrMsg after xEntryPoint() returns. ^If any
+** xEntryPoint() returns an error, the [tdsqlite3_open()], [tdsqlite3_open16()],
+** or [tdsqlite3_open_v2()] call that provoked the xEntryPoint() will fail.
**
-** ^Calling sqlite3_auto_extension(X) with an entry point X that is already
+** ^Calling tdsqlite3_auto_extension(X) with an entry point X that is already
** on the list of automatic extensions is a harmless no-op. ^No entry point
** will be called more than once for each database connection that is opened.
**
-** See also: [sqlite3_reset_auto_extension()]
-** and [sqlite3_cancel_auto_extension()]
+** See also: [tdsqlite3_reset_auto_extension()]
+** and [tdsqlite3_cancel_auto_extension()]
*/
-SQLITE_API int sqlite3_auto_extension(void(*xEntryPoint)(void));
+SQLITE_API int tdsqlite3_auto_extension(void(*xEntryPoint)(void));
/*
** CAPI3REF: Cancel Automatic Extension Loading
**
-** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the
+** ^The [tdsqlite3_cancel_auto_extension(X)] interface unregisters the
** initialization routine X that was registered using a prior call to
-** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)]
+** [tdsqlite3_auto_extension(X)]. ^The [tdsqlite3_cancel_auto_extension(X)]
** routine returns 1 if initialization routine X was successfully
** unregistered and it returns 0 if X was not on the list of initialization
** routines.
*/
-SQLITE_API int sqlite3_cancel_auto_extension(void(*xEntryPoint)(void));
+SQLITE_API int tdsqlite3_cancel_auto_extension(void(*xEntryPoint)(void));
/*
** CAPI3REF: Reset Automatic Extension Loading
**
** ^This interface disables all automatic extensions previously
-** registered using [sqlite3_auto_extension()].
+** registered using [tdsqlite3_auto_extension()].
*/
-SQLITE_API void sqlite3_reset_auto_extension(void);
+SQLITE_API void tdsqlite3_reset_auto_extension(void);
/*
** The interface to the virtual-table mechanism is currently considered
@@ -6034,67 +7658,70 @@ SQLITE_API void sqlite3_reset_auto_extension(void);
/*
** Structures used by the virtual table interface
*/
-typedef struct sqlite3_vtab sqlite3_vtab;
-typedef struct sqlite3_index_info sqlite3_index_info;
-typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor;
-typedef struct sqlite3_module sqlite3_module;
+typedef struct tdsqlite3_vtab tdsqlite3_vtab;
+typedef struct tdsqlite3_index_info tdsqlite3_index_info;
+typedef struct tdsqlite3_vtab_cursor tdsqlite3_vtab_cursor;
+typedef struct tdsqlite3_module tdsqlite3_module;
/*
** CAPI3REF: Virtual Table Object
-** KEYWORDS: sqlite3_module {virtual table module}
+** KEYWORDS: tdsqlite3_module {virtual table module}
**
** This structure, sometimes called a "virtual table module",
-** defines the implementation of a [virtual tables].
+** defines the implementation of a [virtual table].
** This structure consists mostly of methods for the module.
**
** ^A virtual table module is created by filling in a persistent
** instance of this structure and passing a pointer to that instance
-** to [sqlite3_create_module()] or [sqlite3_create_module_v2()].
+** to [tdsqlite3_create_module()] or [tdsqlite3_create_module_v2()].
** ^The registration remains valid until it is replaced by a different
** module or until the [database connection] closes. The content
** of this structure must not change while it is registered with
** any database connection.
*/
-struct sqlite3_module {
+struct tdsqlite3_module {
int iVersion;
- int (*xCreate)(sqlite3*, void *pAux,
+ int (*xCreate)(tdsqlite3*, void *pAux,
int argc, const char *const*argv,
- sqlite3_vtab **ppVTab, char**);
- int (*xConnect)(sqlite3*, void *pAux,
+ tdsqlite3_vtab **ppVTab, char**);
+ int (*xConnect)(tdsqlite3*, void *pAux,
int argc, const char *const*argv,
- sqlite3_vtab **ppVTab, char**);
- int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
- int (*xDisconnect)(sqlite3_vtab *pVTab);
- int (*xDestroy)(sqlite3_vtab *pVTab);
- int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
- int (*xClose)(sqlite3_vtab_cursor*);
- int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
- int argc, sqlite3_value **argv);
- int (*xNext)(sqlite3_vtab_cursor*);
- int (*xEof)(sqlite3_vtab_cursor*);
- int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int);
- int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid);
- int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *);
- int (*xBegin)(sqlite3_vtab *pVTab);
- int (*xSync)(sqlite3_vtab *pVTab);
- int (*xCommit)(sqlite3_vtab *pVTab);
- int (*xRollback)(sqlite3_vtab *pVTab);
- int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
- void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
+ tdsqlite3_vtab **ppVTab, char**);
+ int (*xBestIndex)(tdsqlite3_vtab *pVTab, tdsqlite3_index_info*);
+ int (*xDisconnect)(tdsqlite3_vtab *pVTab);
+ int (*xDestroy)(tdsqlite3_vtab *pVTab);
+ int (*xOpen)(tdsqlite3_vtab *pVTab, tdsqlite3_vtab_cursor **ppCursor);
+ int (*xClose)(tdsqlite3_vtab_cursor*);
+ int (*xFilter)(tdsqlite3_vtab_cursor*, int idxNum, const char *idxStr,
+ int argc, tdsqlite3_value **argv);
+ int (*xNext)(tdsqlite3_vtab_cursor*);
+ int (*xEof)(tdsqlite3_vtab_cursor*);
+ int (*xColumn)(tdsqlite3_vtab_cursor*, tdsqlite3_context*, int);
+ int (*xRowid)(tdsqlite3_vtab_cursor*, tdsqlite3_int64 *pRowid);
+ int (*xUpdate)(tdsqlite3_vtab *, int, tdsqlite3_value **, tdsqlite3_int64 *);
+ int (*xBegin)(tdsqlite3_vtab *pVTab);
+ int (*xSync)(tdsqlite3_vtab *pVTab);
+ int (*xCommit)(tdsqlite3_vtab *pVTab);
+ int (*xRollback)(tdsqlite3_vtab *pVTab);
+ int (*xFindFunction)(tdsqlite3_vtab *pVtab, int nArg, const char *zName,
+ void (**pxFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
void **ppArg);
- int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
+ int (*xRename)(tdsqlite3_vtab *pVtab, const char *zNew);
/* The methods above are in version 1 of the sqlite_module object. Those
** below are for version 2 and greater. */
- int (*xSavepoint)(sqlite3_vtab *pVTab, int);
- int (*xRelease)(sqlite3_vtab *pVTab, int);
- int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
+ int (*xSavepoint)(tdsqlite3_vtab *pVTab, int);
+ int (*xRelease)(tdsqlite3_vtab *pVTab, int);
+ int (*xRollbackTo)(tdsqlite3_vtab *pVTab, int);
+ /* The methods above are in versions 1 and 2 of the sqlite_module object.
+ ** Those below are for version 3 and greater. */
+ int (*xShadowName)(const char*);
};
/*
** CAPI3REF: Virtual Table Indexing Information
-** KEYWORDS: sqlite3_index_info
+** KEYWORDS: tdsqlite3_index_info
**
-** The sqlite3_index_info structure and its substructures is used as part
+** The tdsqlite3_index_info structure and its substructures is used as part
** of the [virtual table] interface to
** pass information into and receive the reply from the [xBestIndex]
** method of a [virtual table module]. The fields under **Inputs** are the
@@ -6125,12 +7752,12 @@ struct sqlite3_module {
** The colUsed field indicates which columns of the virtual table may be
** required by the current scan. Virtual table columns are numbered from
** zero in the order in which they appear within the CREATE TABLE statement
-** passed to sqlite3_declare_vtab(). For the first 63 columns (columns 0-62),
+** passed to tdsqlite3_declare_vtab(). For the first 63 columns (columns 0-62),
** the corresponding bit is set within the colUsed mask if the column may be
** required by SQLite. If the table has at least 64 columns and any column
** to the right of the first 63 is required, then bit 63 of colUsed is also
** set. In other words, column iCol may be required if the expression
-** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to
+** (colUsed & ((tdsqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to
** non-zero.
**
** The [xBestIndex] method must fill aConstraintUsage[] with information
@@ -6138,11 +7765,17 @@ struct sqlite3_module {
** the right-hand side of the corresponding aConstraint[] is evaluated
** and becomes the argvIndex-th entry in argv. ^(If aConstraintUsage[].omit
** is true, then the constraint is assumed to be fully handled by the
-** virtual table and is not checked again by SQLite.)^
+** virtual table and might not be checked again by the byte code.)^ ^(The
+** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
+** is left in its default setting of false, the constraint will always be
+** checked separately in byte code. If the omit flag is change to true, then
+** the constraint may or may not be checked in byte code. In other words,
+** when the omit flag is true there is no guarantee that the constraint will
+** not be checked again using byte code.)^
**
** ^The idxNum and idxPtr values are recorded and passed into the
** [xFilter] method.
-** ^[sqlite3_free()] is used to free idxPtr if and only if
+** ^[tdsqlite3_free()] is used to free idxPtr if and only if
** needToFreeIdxPtr is true.
**
** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in
@@ -6173,77 +7806,87 @@ struct sqlite3_module {
** set and xUpdate returns SQLITE_CONSTRAINT, any database changes made by
** the xUpdate method are automatically rolled back by SQLite.
**
-** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info
+** IMPORTANT: The estimatedRows field was added to the tdsqlite3_index_info
** structure for SQLite [version 3.8.2] ([dateof:3.8.2]).
** If a virtual table extension is
** used with an SQLite version earlier than 3.8.2, the results of attempting
** to read or write the estimatedRows field are undefined (but are likely
-** to included crashing the application). The estimatedRows field should
-** therefore only be used if [sqlite3_libversion_number()] returns a
+** to include crashing the application). The estimatedRows field should
+** therefore only be used if [tdsqlite3_libversion_number()] returns a
** value greater than or equal to 3008002. Similarly, the idxFlags field
** was added for [version 3.9.0] ([dateof:3.9.0]).
** It may therefore only be used if
-** sqlite3_libversion_number() returns a value greater than or equal to
+** tdsqlite3_libversion_number() returns a value greater than or equal to
** 3009000.
*/
-struct sqlite3_index_info {
+struct tdsqlite3_index_info {
/* Inputs */
int nConstraint; /* Number of entries in aConstraint */
- struct sqlite3_index_constraint {
+ struct tdsqlite3_index_constraint {
int iColumn; /* Column constrained. -1 for ROWID */
unsigned char op; /* Constraint operator */
unsigned char usable; /* True if this constraint is usable */
int iTermOffset; /* Used internally - xBestIndex should ignore */
} *aConstraint; /* Table of WHERE clause constraints */
int nOrderBy; /* Number of terms in the ORDER BY clause */
- struct sqlite3_index_orderby {
+ struct tdsqlite3_index_orderby {
int iColumn; /* Column number */
unsigned char desc; /* True for DESC. False for ASC. */
} *aOrderBy; /* The ORDER BY clause */
/* Outputs */
- struct sqlite3_index_constraint_usage {
+ struct tdsqlite3_index_constraint_usage {
int argvIndex; /* if >0, constraint is part of argv to xFilter */
unsigned char omit; /* Do not code a test for this constraint */
} *aConstraintUsage;
int idxNum; /* Number used to identify the index */
- char *idxStr; /* String, possibly obtained from sqlite3_malloc */
- int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */
+ char *idxStr; /* String, possibly obtained from tdsqlite3_malloc */
+ int needToFreeIdxStr; /* Free idxStr using tdsqlite3_free() if true */
int orderByConsumed; /* True if output is already ordered */
double estimatedCost; /* Estimated cost of using this index */
/* Fields below are only available in SQLite 3.8.2 and later */
- sqlite3_int64 estimatedRows; /* Estimated number of rows returned */
+ tdsqlite3_int64 estimatedRows; /* Estimated number of rows returned */
/* Fields below are only available in SQLite 3.9.0 and later */
int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */
/* Fields below are only available in SQLite 3.10.0 and later */
- sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */
+ tdsqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */
};
/*
** CAPI3REF: Virtual Table Scan Flags
+**
+** Virtual table implementations are allowed to set the
+** [tdsqlite3_index_info].idxFlags field to some combination of
+** these bits.
*/
#define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */
/*
** CAPI3REF: Virtual Table Constraint Operator Codes
**
-** These macros defined the allowed values for the
-** [sqlite3_index_info].aConstraint[].op field. Each value represents
+** These macros define the allowed values for the
+** [tdsqlite3_index_info].aConstraint[].op field. Each value represents
** an operator that is part of a constraint term in the wHERE clause of
** a query that uses a [virtual table].
*/
-#define SQLITE_INDEX_CONSTRAINT_EQ 2
-#define SQLITE_INDEX_CONSTRAINT_GT 4
-#define SQLITE_INDEX_CONSTRAINT_LE 8
-#define SQLITE_INDEX_CONSTRAINT_LT 16
-#define SQLITE_INDEX_CONSTRAINT_GE 32
-#define SQLITE_INDEX_CONSTRAINT_MATCH 64
-#define SQLITE_INDEX_CONSTRAINT_LIKE 65
-#define SQLITE_INDEX_CONSTRAINT_GLOB 66
-#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
+#define SQLITE_INDEX_CONSTRAINT_EQ 2
+#define SQLITE_INDEX_CONSTRAINT_GT 4
+#define SQLITE_INDEX_CONSTRAINT_LE 8
+#define SQLITE_INDEX_CONSTRAINT_LT 16
+#define SQLITE_INDEX_CONSTRAINT_GE 32
+#define SQLITE_INDEX_CONSTRAINT_MATCH 64
+#define SQLITE_INDEX_CONSTRAINT_LIKE 65
+#define SQLITE_INDEX_CONSTRAINT_GLOB 66
+#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
+#define SQLITE_INDEX_CONSTRAINT_NE 68
+#define SQLITE_INDEX_CONSTRAINT_ISNOT 69
+#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70
+#define SQLITE_INDEX_CONSTRAINT_ISNULL 71
+#define SQLITE_INDEX_CONSTRAINT_IS 72
+#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150
/*
** CAPI3REF: Register A Virtual Table Implementation
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^These routines are used to register a new [virtual table module] name.
** ^Module names must be registered before
@@ -6258,32 +7901,55 @@ struct sqlite3_index_info {
** into the [xCreate] and [xConnect] methods of the virtual table module
** when a new virtual table is be being created or reinitialized.
**
-** ^The sqlite3_create_module_v2() interface has a fifth parameter which
+** ^The tdsqlite3_create_module_v2() interface has a fifth parameter which
** is a pointer to a destructor for the pClientData. ^SQLite will
** invoke the destructor function (if it is not NULL) when SQLite
** no longer needs the pClientData pointer. ^The destructor will also
-** be invoked if the call to sqlite3_create_module_v2() fails.
-** ^The sqlite3_create_module()
-** interface is equivalent to sqlite3_create_module_v2() with a NULL
+** be invoked if the call to tdsqlite3_create_module_v2() fails.
+** ^The tdsqlite3_create_module()
+** interface is equivalent to tdsqlite3_create_module_v2() with a NULL
** destructor.
+**
+** ^If the third parameter (the pointer to the tdsqlite3_module object) is
+** NULL then no new module is create and any existing modules with the
+** same name are dropped.
+**
+** See also: [tdsqlite3_drop_modules()]
*/
-SQLITE_API int sqlite3_create_module(
- sqlite3 *db, /* SQLite connection to register module with */
+SQLITE_API int tdsqlite3_create_module(
+ tdsqlite3 *db, /* SQLite connection to register module with */
const char *zName, /* Name of the module */
- const sqlite3_module *p, /* Methods for the module */
+ const tdsqlite3_module *p, /* Methods for the module */
void *pClientData /* Client data for xCreate/xConnect */
);
-SQLITE_API int sqlite3_create_module_v2(
- sqlite3 *db, /* SQLite connection to register module with */
+SQLITE_API int tdsqlite3_create_module_v2(
+ tdsqlite3 *db, /* SQLite connection to register module with */
const char *zName, /* Name of the module */
- const sqlite3_module *p, /* Methods for the module */
+ const tdsqlite3_module *p, /* Methods for the module */
void *pClientData, /* Client data for xCreate/xConnect */
void(*xDestroy)(void*) /* Module destructor function */
);
/*
+** CAPI3REF: Remove Unnecessary Virtual Table Implementations
+** METHOD: tdsqlite3
+**
+** ^The tdsqlite3_drop_modules(D,L) interface removes all virtual
+** table modules from database connection D except those named on list L.
+** The L parameter must be either NULL or a pointer to an array of pointers
+** to strings where the array is terminated by a single NULL pointer.
+** ^If the L parameter is NULL, then all virtual table modules are removed.
+**
+** See also: [tdsqlite3_create_module()]
+*/
+SQLITE_API int tdsqlite3_drop_modules(
+ tdsqlite3 *db, /* Remove modules from this connection */
+ const char **azKeep /* Except, do not remove the ones named here */
+);
+
+/*
** CAPI3REF: Virtual Table Instance Object
-** KEYWORDS: sqlite3_vtab
+** KEYWORDS: tdsqlite3_vtab
**
** Every [virtual table module] implementation uses a subclass
** of this object to describe a particular instance
@@ -6293,29 +7959,29 @@ SQLITE_API int sqlite3_create_module_v2(
** common to all module implementations.
**
** ^Virtual tables methods can set an error message by assigning a
-** string obtained from [sqlite3_mprintf()] to zErrMsg. The method should
-** take care that any prior string is freed by a call to [sqlite3_free()]
+** string obtained from [tdsqlite3_mprintf()] to zErrMsg. The method should
+** take care that any prior string is freed by a call to [tdsqlite3_free()]
** prior to assigning a new string to zErrMsg. ^After the error message
** is delivered up to the client application, the string will be automatically
-** freed by sqlite3_free() and the zErrMsg field will be zeroed.
+** freed by tdsqlite3_free() and the zErrMsg field will be zeroed.
*/
-struct sqlite3_vtab {
- const sqlite3_module *pModule; /* The module for this virtual table */
+struct tdsqlite3_vtab {
+ const tdsqlite3_module *pModule; /* The module for this virtual table */
int nRef; /* Number of open cursors */
- char *zErrMsg; /* Error message from sqlite3_mprintf() */
+ char *zErrMsg; /* Error message from tdsqlite3_mprintf() */
/* Virtual table implementations will typically add additional fields */
};
/*
** CAPI3REF: Virtual Table Cursor Object
-** KEYWORDS: sqlite3_vtab_cursor {virtual table cursor}
+** KEYWORDS: tdsqlite3_vtab_cursor {virtual table cursor}
**
** Every [virtual table module] implementation uses a subclass of the
** following structure to describe cursors that point into the
** [virtual table] and are used
** to loop through the virtual table. Cursors are created using the
-** [sqlite3_module.xOpen | xOpen] method of the module and are destroyed
-** by the [sqlite3_module.xClose | xClose] method. Cursors are used
+** [tdsqlite3_module.xOpen | xOpen] method of the module and are destroyed
+** by the [tdsqlite3_module.xClose | xClose] method. Cursors are used
** by the [xFilter], [xNext], [xEof], [xColumn], and [xRowid] methods
** of the module. Each module implementation will define
** the content of a cursor structure to suit its own needs.
@@ -6323,8 +7989,8 @@ struct sqlite3_vtab {
** This superclass exists in order to define fields of the cursor that
** are common to all implementations.
*/
-struct sqlite3_vtab_cursor {
- sqlite3_vtab *pVtab; /* Virtual table of this cursor */
+struct tdsqlite3_vtab_cursor {
+ tdsqlite3_vtab *pVtab; /* Virtual table of this cursor */
/* Virtual table implementations will typically add additional fields */
};
@@ -6336,11 +8002,11 @@ struct sqlite3_vtab_cursor {
** to declare the format (the names and datatypes of the columns) of
** the virtual tables they implement.
*/
-SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL);
+SQLITE_API int tdsqlite3_declare_vtab(tdsqlite3*, const char *zSQL);
/*
** CAPI3REF: Overload A Function For A Virtual Table
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^(Virtual tables can provide alternative implementations of functions
** using the [xFindFunction] method of the [virtual table module].
@@ -6355,7 +8021,7 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL);
** purpose is to be a placeholder function that can be overloaded
** by a [virtual table].
*/
-SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg);
+SQLITE_API int tdsqlite3_overload_function(tdsqlite3*, const char *zFuncName, int nArg);
/*
** The interface to the virtual-table mechanism defined above (back up
@@ -6372,19 +8038,19 @@ SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nA
** KEYWORDS: {BLOB handle} {BLOB handles}
**
** An instance of this object represents an open BLOB on which
-** [sqlite3_blob_open | incremental BLOB I/O] can be performed.
-** ^Objects of this type are created by [sqlite3_blob_open()]
-** and destroyed by [sqlite3_blob_close()].
-** ^The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces
+** [tdsqlite3_blob_open | incremental BLOB I/O] can be performed.
+** ^Objects of this type are created by [tdsqlite3_blob_open()]
+** and destroyed by [tdsqlite3_blob_close()].
+** ^The [tdsqlite3_blob_read()] and [tdsqlite3_blob_write()] interfaces
** can be used to read or write small subsections of the BLOB.
-** ^The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes.
+** ^The [tdsqlite3_blob_bytes()] interface returns the size of the BLOB in bytes.
*/
-typedef struct sqlite3_blob sqlite3_blob;
+typedef struct tdsqlite3_blob tdsqlite3_blob;
/*
** CAPI3REF: Open A BLOB For Incremental I/O
-** METHOD: sqlite3
-** CONSTRUCTOR: sqlite3_blob
+** METHOD: tdsqlite3
+** CONSTRUCTOR: tdsqlite3_blob
**
** ^(This interfaces opens a [BLOB handle | handle] to the BLOB located
** in row iRow, column zColumn, table zTable in database zDb;
@@ -6407,7 +8073,7 @@ typedef struct sqlite3_blob sqlite3_blob;
** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
-** the API is not misused, it is always safe to call [sqlite3_blob_close()]
+** the API is not misused, it is always safe to call [tdsqlite3_blob_close()]
** on *ppBlob after this function it returns.
**
** This function fails with SQLITE_ERROR if any of the following are true:
@@ -6428,70 +8094,80 @@ typedef struct sqlite3_blob sqlite3_blob;
**
** ^Unless it returns SQLITE_MISUSE, this function sets the
** [database connection] error code and message accessible via
-** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions.
+** [tdsqlite3_errcode()] and [tdsqlite3_errmsg()] and related functions.
**
+** A BLOB referenced by tdsqlite3_blob_open() may be read using the
+** [tdsqlite3_blob_read()] interface and modified by using
+** [tdsqlite3_blob_write()]. The [BLOB handle] can be moved to a
+** different row of the same table using the [tdsqlite3_blob_reopen()]
+** interface. However, the column, table, or database of a [BLOB handle]
+** cannot be changed after the [BLOB handle] is opened.
**
** ^(If the row that a BLOB handle points to is modified by an
** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects
** then the BLOB handle is marked as "expired".
** This is true if any column of the row is changed, even a column
** other than the one the BLOB handle is open on.)^
-** ^Calls to [sqlite3_blob_read()] and [sqlite3_blob_write()] for
+** ^Calls to [tdsqlite3_blob_read()] and [tdsqlite3_blob_write()] for
** an expired BLOB handle fail with a return code of [SQLITE_ABORT].
** ^(Changes written into a BLOB prior to the BLOB expiring are not
** rolled back by the expiration of the BLOB. Such changes will eventually
** commit if the transaction continues to completion.)^
**
-** ^Use the [sqlite3_blob_bytes()] interface to determine the size of
+** ^Use the [tdsqlite3_blob_bytes()] interface to determine the size of
** the opened blob. ^The size of a blob may not be changed by this
** interface. Use the [UPDATE] SQL command to change the size of a
** blob.
**
-** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces
+** ^The [tdsqlite3_bind_zeroblob()] and [tdsqlite3_result_zeroblob()] interfaces
** and the built-in [zeroblob] SQL function may be used to create a
** zero-filled blob to read or write using the incremental-blob interface.
**
** To avoid a resource leak, every open [BLOB handle] should eventually
-** be released by a call to [sqlite3_blob_close()].
+** be released by a call to [tdsqlite3_blob_close()].
+**
+** See also: [tdsqlite3_blob_close()],
+** [tdsqlite3_blob_reopen()], [tdsqlite3_blob_read()],
+** [tdsqlite3_blob_bytes()], [tdsqlite3_blob_write()].
*/
-SQLITE_API int sqlite3_blob_open(
- sqlite3*,
+SQLITE_API int tdsqlite3_blob_open(
+ tdsqlite3*,
const char *zDb,
const char *zTable,
const char *zColumn,
- sqlite3_int64 iRow,
+ tdsqlite3_int64 iRow,
int flags,
- sqlite3_blob **ppBlob
+ tdsqlite3_blob **ppBlob
);
/*
** CAPI3REF: Move a BLOB Handle to a New Row
-** METHOD: sqlite3_blob
+** METHOD: tdsqlite3_blob
**
-** ^This function is used to move an existing blob handle so that it points
+** ^This function is used to move an existing [BLOB handle] so that it points
** to a different row of the same database table. ^The new row is identified
** by the rowid value passed as the second argument. Only the row can be
** changed. ^The database, table and column on which the blob handle is open
-** remain the same. Moving an existing blob handle to a new row can be
+** remain the same. Moving an existing [BLOB handle] to a new row is
** faster than closing the existing handle and opening a new one.
**
-** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] -
+** ^(The new row must meet the same criteria as for [tdsqlite3_blob_open()] -
** it must exist and there must be either a blob or text value stored in
** the nominated column.)^ ^If the new row is not present in the table, or if
** it does not contain a blob or text value, or if another error occurs, an
** SQLite error code is returned and the blob handle is considered aborted.
-** ^All subsequent calls to [sqlite3_blob_read()], [sqlite3_blob_write()] or
-** [sqlite3_blob_reopen()] on an aborted blob handle immediately return
-** SQLITE_ABORT. ^Calling [sqlite3_blob_bytes()] on an aborted blob handle
+** ^All subsequent calls to [tdsqlite3_blob_read()], [tdsqlite3_blob_write()] or
+** [tdsqlite3_blob_reopen()] on an aborted blob handle immediately return
+** SQLITE_ABORT. ^Calling [tdsqlite3_blob_bytes()] on an aborted blob handle
** always returns zero.
**
** ^This function sets the database handle error code and message.
*/
-SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
+SQLITE_API int tdsqlite3_blob_reopen(tdsqlite3_blob *, tdsqlite3_int64);
/*
** CAPI3REF: Close A BLOB Handle
-** DESTRUCTOR: sqlite3_blob
+** DESTRUCTOR: tdsqlite3_blob
**
** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed
** unconditionally. Even if this routine returns an error code, the
@@ -6506,15 +8182,15 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
** Calling this function with an argument that is not a NULL pointer or an
** open blob handle results in undefined behaviour. ^Calling this routine
** with a null pointer (such as would be returned by a failed call to
-** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function
+** [tdsqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function
** is passed a valid open blob handle, the values returned by the
-** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning.
+** tdsqlite3_errcode() and tdsqlite3_errmsg() functions are set before returning.
*/
-SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
+SQLITE_API int tdsqlite3_blob_close(tdsqlite3_blob *);
/*
** CAPI3REF: Return The Size Of An Open BLOB
-** METHOD: sqlite3_blob
+** METHOD: tdsqlite3_blob
**
** ^Returns the size in bytes of the BLOB accessible via the
** successfully opened [BLOB handle] in its only argument. ^The
@@ -6522,15 +8198,15 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
** blob content; they cannot change the size of a blob.
**
** This routine only works on a [BLOB handle] which has been created
-** by a prior successful call to [sqlite3_blob_open()] and which has not
-** been closed by [sqlite3_blob_close()]. Passing any other pointer in
+** by a prior successful call to [tdsqlite3_blob_open()] and which has not
+** been closed by [tdsqlite3_blob_close()]. Passing any other pointer in
** to this routine results in undefined and probably undesirable behavior.
*/
-SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *);
+SQLITE_API int tdsqlite3_blob_bytes(tdsqlite3_blob *);
/*
** CAPI3REF: Read Data From A BLOB Incrementally
-** METHOD: sqlite3_blob
+** METHOD: tdsqlite3_blob
**
** ^(This function is used to read data from an open [BLOB handle] into a
** caller-supplied buffer. N bytes of data are copied into buffer Z
@@ -6540,39 +8216,39 @@ SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *);
** [SQLITE_ERROR] is returned and no data is read. ^If N or iOffset is
** less than zero, [SQLITE_ERROR] is returned and no data is read.
** ^The size of the blob (and hence the maximum value of N+iOffset)
-** can be determined using the [sqlite3_blob_bytes()] interface.
+** can be determined using the [tdsqlite3_blob_bytes()] interface.
**
** ^An attempt to read from an expired [BLOB handle] fails with an
** error code of [SQLITE_ABORT].
**
-** ^(On success, sqlite3_blob_read() returns SQLITE_OK.
+** ^(On success, tdsqlite3_blob_read() returns SQLITE_OK.
** Otherwise, an [error code] or an [extended error code] is returned.)^
**
** This routine only works on a [BLOB handle] which has been created
-** by a prior successful call to [sqlite3_blob_open()] and which has not
-** been closed by [sqlite3_blob_close()]. Passing any other pointer in
+** by a prior successful call to [tdsqlite3_blob_open()] and which has not
+** been closed by [tdsqlite3_blob_close()]. Passing any other pointer in
** to this routine results in undefined and probably undesirable behavior.
**
-** See also: [sqlite3_blob_write()].
+** See also: [tdsqlite3_blob_write()].
*/
-SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
+SQLITE_API int tdsqlite3_blob_read(tdsqlite3_blob *, void *Z, int N, int iOffset);
/*
** CAPI3REF: Write Data Into A BLOB Incrementally
-** METHOD: sqlite3_blob
+** METHOD: tdsqlite3_blob
**
** ^(This function is used to write data into an open [BLOB handle] from a
** caller-supplied buffer. N bytes of data are copied from the buffer Z
** into the open BLOB, starting at offset iOffset.)^
**
-** ^(On success, sqlite3_blob_write() returns SQLITE_OK.
+** ^(On success, tdsqlite3_blob_write() returns SQLITE_OK.
** Otherwise, an [error code] or an [extended error code] is returned.)^
** ^Unless SQLITE_MISUSE is returned, this function sets the
** [database connection] error code and message accessible via
-** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions.
+** [tdsqlite3_errcode()] and [tdsqlite3_errmsg()] and related functions.
**
** ^If the [BLOB handle] passed as the first argument was not opened for
-** writing (the flags parameter to [sqlite3_blob_open()] was zero),
+** writing (the flags parameter to [tdsqlite3_blob_open()] was zero),
** this function returns [SQLITE_READONLY].
**
** This function may only modify the contents of the BLOB; it is
@@ -6580,7 +8256,7 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
** ^If offset iOffset is less than N bytes from the end of the BLOB,
** [SQLITE_ERROR] is returned and no data is written. The size of the
** BLOB (and hence the maximum value of N+iOffset) can be determined
-** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less
+** using the [tdsqlite3_blob_bytes()] interface. ^If N or iOffset are less
** than zero [SQLITE_ERROR] is returned and no data is written.
**
** ^An attempt to write to an expired [BLOB handle] fails with an
@@ -6591,31 +8267,31 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
** or by other independent statements.
**
** This routine only works on a [BLOB handle] which has been created
-** by a prior successful call to [sqlite3_blob_open()] and which has not
-** been closed by [sqlite3_blob_close()]. Passing any other pointer in
+** by a prior successful call to [tdsqlite3_blob_open()] and which has not
+** been closed by [tdsqlite3_blob_close()]. Passing any other pointer in
** to this routine results in undefined and probably undesirable behavior.
**
-** See also: [sqlite3_blob_read()].
+** See also: [tdsqlite3_blob_read()].
*/
-SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset);
+SQLITE_API int tdsqlite3_blob_write(tdsqlite3_blob *, const void *z, int n, int iOffset);
/*
** CAPI3REF: Virtual File System Objects
**
-** A virtual filesystem (VFS) is an [sqlite3_vfs] object
+** A virtual filesystem (VFS) is an [tdsqlite3_vfs] object
** that SQLite uses to interact
** with the underlying operating system. Most SQLite builds come with a
** single default VFS that is appropriate for the host computer.
** New VFSes can be registered and existing VFSes can be unregistered.
** The following interfaces are provided.
**
-** ^The sqlite3_vfs_find() interface returns a pointer to a VFS given its name.
+** ^The tdsqlite3_vfs_find() interface returns a pointer to a VFS given its name.
** ^Names are case sensitive.
** ^Names are zero-terminated UTF-8 strings.
** ^If there is no match, a NULL pointer is returned.
** ^If zVfsName is NULL then the default VFS is returned.
**
-** ^New VFSes are registered with sqlite3_vfs_register().
+** ^New VFSes are registered with tdsqlite3_vfs_register().
** ^Each new VFS becomes the default VFS if the makeDflt flag is set.
** ^The same VFS can be registered multiple times without injury.
** ^To make an existing VFS into the default VFS, register it again
@@ -6624,13 +8300,13 @@ SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOff
** VFS is registered with a name that is NULL or an empty string,
** then the behavior is undefined.
**
-** ^Unregister a VFS with the sqlite3_vfs_unregister() interface.
+** ^Unregister a VFS with the tdsqlite3_vfs_unregister() interface.
** ^(If the default VFS is unregistered, another VFS is chosen as
** the default. The choice for the new VFS is arbitrary.)^
*/
-SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);
-SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);
-SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
+SQLITE_API tdsqlite3_vfs *tdsqlite3_vfs_find(const char *zVfsName);
+SQLITE_API int tdsqlite3_vfs_register(tdsqlite3_vfs*, int makeDflt);
+SQLITE_API int tdsqlite3_vfs_unregister(tdsqlite3_vfs*);
/*
** CAPI3REF: Mutexes
@@ -6661,14 +8337,14 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex
** implementation is included with the library. In this case the
** application must supply a custom mutex implementation using the
-** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function
-** before calling sqlite3_initialize() or any other public sqlite3_
-** function that calls sqlite3_initialize().
+** [SQLITE_CONFIG_MUTEX] option of the tdsqlite3_config() function
+** before calling tdsqlite3_initialize() or any other public tdsqlite3_
+** function that calls tdsqlite3_initialize().
**
-** ^The sqlite3_mutex_alloc() routine allocates a new
-** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
+** ^The tdsqlite3_mutex_alloc() routine allocates a new
+** mutex and returns a pointer to it. ^The tdsqlite3_mutex_alloc()
** routine returns NULL if it is unable to allocate the requested
-** mutex. The argument to sqlite3_mutex_alloc() must one of these
+** mutex. The argument to tdsqlite3_mutex_alloc() must one of these
** integer constants:
**
** <ul>
@@ -6689,7 +8365,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** </ul>
**
** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE)
-** cause sqlite3_mutex_alloc() to create
+** cause tdsqlite3_mutex_alloc() to create
** a new mutex. ^The new mutex is recursive when SQLITE_MUTEX_RECURSIVE
** is used but not necessarily so when SQLITE_MUTEX_FAST is used.
** The mutex implementation does not need to make a distinction
@@ -6699,7 +8375,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** implementation is available on the host platform, the mutex subsystem
** might return such a mutex in response to SQLITE_MUTEX_FAST.
**
-** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other
+** ^The other allowed parameters to tdsqlite3_mutex_alloc() (anything other
** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return
** a pointer to a static preexisting mutex. ^Nine static mutexes are
** used by the current version of SQLite. Future versions of SQLite
@@ -6709,19 +8385,19 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** SQLITE_MUTEX_RECURSIVE.
**
** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
-** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
+** or SQLITE_MUTEX_RECURSIVE) is used then tdsqlite3_mutex_alloc()
** returns a different mutex on every call. ^For the static
** mutex types, the same mutex is returned on every call that has
** the same type number.
**
-** ^The sqlite3_mutex_free() routine deallocates a previously
+** ^The tdsqlite3_mutex_free() routine deallocates a previously
** allocated dynamic mutex. Attempting to deallocate a static
** mutex results in undefined behavior.
**
-** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** ^The tdsqlite3_mutex_enter() and tdsqlite3_mutex_try() routines attempt
** to enter a mutex. ^If another thread is already within the mutex,
-** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
-** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK]
+** tdsqlite3_mutex_enter() will block and tdsqlite3_mutex_try() will return
+** SQLITE_BUSY. ^The tdsqlite3_mutex_try() interface returns [SQLITE_OK]
** upon successful entry. ^(Mutexes created using
** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread.
** In such cases, the
@@ -6730,27 +8406,27 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined.
**
** ^(Some systems (for example, Windows 95) do not support the operation
-** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try()
+** implemented by tdsqlite3_mutex_try(). On those systems, tdsqlite3_mutex_try()
** will always return SQLITE_BUSY. The SQLite core only ever uses
-** sqlite3_mutex_try() as an optimization so this is acceptable
+** tdsqlite3_mutex_try() as an optimization so this is acceptable
** behavior.)^
**
-** ^The sqlite3_mutex_leave() routine exits a mutex that was
+** ^The tdsqlite3_mutex_leave() routine exits a mutex that was
** previously entered by the same thread. The behavior
** is undefined if the mutex is not currently entered by the
** calling thread or is not currently allocated.
**
-** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or
-** sqlite3_mutex_leave() is a NULL pointer, then all three routines
+** ^If the argument to tdsqlite3_mutex_enter(), tdsqlite3_mutex_try(), or
+** tdsqlite3_mutex_leave() is a NULL pointer, then all three routines
** behave as no-ops.
**
-** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()].
+** See also: [tdsqlite3_mutex_held()] and [tdsqlite3_mutex_notheld()].
*/
-SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int);
-SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*);
-SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*);
-SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*);
-SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
+SQLITE_API tdsqlite3_mutex *tdsqlite3_mutex_alloc(int);
+SQLITE_API void tdsqlite3_mutex_free(tdsqlite3_mutex*);
+SQLITE_API void tdsqlite3_mutex_enter(tdsqlite3_mutex*);
+SQLITE_API int tdsqlite3_mutex_try(tdsqlite3_mutex*);
+SQLITE_API void tdsqlite3_mutex_leave(tdsqlite3_mutex*);
/*
** CAPI3REF: Mutex Methods Object
@@ -6763,41 +8439,41 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
** implementation for specialized deployments or systems for which SQLite
** does not provide a suitable implementation. In this case, the application
** creates and populates an instance of this structure to pass
-** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option.
+** to tdsqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option.
** Additionally, an instance of this structure can be used as an
** output variable when querying the system for the current mutex
** implementation, using the [SQLITE_CONFIG_GETMUTEX] option.
**
** ^The xMutexInit method defined by this structure is invoked as
-** part of system initialization by the sqlite3_initialize() function.
+** part of system initialization by the tdsqlite3_initialize() function.
** ^The xMutexInit routine is called by SQLite exactly once for each
-** effective call to [sqlite3_initialize()].
+** effective call to [tdsqlite3_initialize()].
**
** ^The xMutexEnd method defined by this structure is invoked as
-** part of system shutdown by the sqlite3_shutdown() function. The
+** part of system shutdown by the tdsqlite3_shutdown() function. The
** implementation of this method is expected to release all outstanding
** resources obtained by the mutex methods implementation, especially
** those obtained by the xMutexInit method. ^The xMutexEnd()
-** interface is invoked exactly once for each call to [sqlite3_shutdown()].
+** interface is invoked exactly once for each call to [tdsqlite3_shutdown()].
**
** ^(The remaining seven methods defined by this structure (xMutexAlloc,
** xMutexFree, xMutexEnter, xMutexTry, xMutexLeave, xMutexHeld and
** xMutexNotheld) implement the following interfaces (respectively):
**
** <ul>
-** <li> [sqlite3_mutex_alloc()] </li>
-** <li> [sqlite3_mutex_free()] </li>
-** <li> [sqlite3_mutex_enter()] </li>
-** <li> [sqlite3_mutex_try()] </li>
-** <li> [sqlite3_mutex_leave()] </li>
-** <li> [sqlite3_mutex_held()] </li>
-** <li> [sqlite3_mutex_notheld()] </li>
+** <li> [tdsqlite3_mutex_alloc()] </li>
+** <li> [tdsqlite3_mutex_free()] </li>
+** <li> [tdsqlite3_mutex_enter()] </li>
+** <li> [tdsqlite3_mutex_try()] </li>
+** <li> [tdsqlite3_mutex_leave()] </li>
+** <li> [tdsqlite3_mutex_held()] </li>
+** <li> [tdsqlite3_mutex_notheld()] </li>
** </ul>)^
**
-** The only difference is that the public sqlite3_XXX functions enumerated
+** The only difference is that the public tdsqlite3_XXX functions enumerated
** above silently ignore any invocations that pass a NULL pointer instead
** of a valid mutex handle. The implementations of the methods defined
-** by this structure are not required to handle this case, the results
+** by this structure are not required to handle this case. The results
** of passing a NULL pointer instead of a valid mutex handle are undefined
** (i.e. it is acceptable to provide an implementation that segfaults if
** it is passed a NULL pointer).
@@ -6807,33 +8483,33 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
** intervening calls to xMutexEnd(). Second and subsequent calls to
** xMutexInit() must be no-ops.
**
-** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()]
+** xMutexInit() must not use SQLite memory allocation ([tdsqlite3_malloc()]
** and its associates). Similarly, xMutexAlloc() must not use SQLite memory
** allocation for a static mutex. ^However xMutexAlloc() may use SQLite
** memory allocation for a fast or recursive mutex.
**
-** ^SQLite will invoke the xMutexEnd() method when [sqlite3_shutdown()] is
+** ^SQLite will invoke the xMutexEnd() method when [tdsqlite3_shutdown()] is
** called, but only if the prior call to xMutexInit returned SQLITE_OK.
** If xMutexInit fails in any way, it is expected to clean up after itself
** prior to returning.
*/
-typedef struct sqlite3_mutex_methods sqlite3_mutex_methods;
-struct sqlite3_mutex_methods {
+typedef struct tdsqlite3_mutex_methods tdsqlite3_mutex_methods;
+struct tdsqlite3_mutex_methods {
int (*xMutexInit)(void);
int (*xMutexEnd)(void);
- sqlite3_mutex *(*xMutexAlloc)(int);
- void (*xMutexFree)(sqlite3_mutex *);
- void (*xMutexEnter)(sqlite3_mutex *);
- int (*xMutexTry)(sqlite3_mutex *);
- void (*xMutexLeave)(sqlite3_mutex *);
- int (*xMutexHeld)(sqlite3_mutex *);
- int (*xMutexNotheld)(sqlite3_mutex *);
+ tdsqlite3_mutex *(*xMutexAlloc)(int);
+ void (*xMutexFree)(tdsqlite3_mutex *);
+ void (*xMutexEnter)(tdsqlite3_mutex *);
+ int (*xMutexTry)(tdsqlite3_mutex *);
+ void (*xMutexLeave)(tdsqlite3_mutex *);
+ int (*xMutexHeld)(tdsqlite3_mutex *);
+ int (*xMutexNotheld)(tdsqlite3_mutex *);
};
/*
** CAPI3REF: Mutex Verification Routines
**
-** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines
+** The tdsqlite3_mutex_held() and tdsqlite3_mutex_notheld() routines
** are intended for use inside assert() statements. The SQLite core
** never uses these routines except inside an assert() and applications
** are advised to follow the lead of the core. The SQLite core only
@@ -6850,24 +8526,24 @@ struct sqlite3_mutex_methods {
** versions of these routines, it should at least provide stubs that always
** return true so that one does not get spurious assertion failures.
**
-** If the argument to sqlite3_mutex_held() is a NULL pointer then
+** If the argument to tdsqlite3_mutex_held() is a NULL pointer then
** the routine should return 1. This seems counter-intuitive since
** clearly the mutex cannot be held if it does not exist. But
** the reason the mutex does not exist is because the build is not
** using mutexes. And we do not want the assert() containing the
-** call to sqlite3_mutex_held() to fail, so a non-zero return is
-** the appropriate thing to do. The sqlite3_mutex_notheld()
+** call to tdsqlite3_mutex_held() to fail, so a non-zero return is
+** the appropriate thing to do. The tdsqlite3_mutex_notheld()
** interface should also return 1 when given a NULL pointer.
*/
#ifndef NDEBUG
-SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*);
-SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
+SQLITE_API int tdsqlite3_mutex_held(tdsqlite3_mutex*);
+SQLITE_API int tdsqlite3_mutex_notheld(tdsqlite3_mutex*);
#endif
/*
** CAPI3REF: Mutex Types
**
-** The [sqlite3_mutex_alloc()] interface takes a single argument
+** The [tdsqlite3_mutex_alloc()] interface takes a single argument
** which is one of these integer constants.
**
** The set of static mutexes may change from one SQLite release to the
@@ -6877,13 +8553,13 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
#define SQLITE_MUTEX_FAST 0
#define SQLITE_MUTEX_RECURSIVE 1
#define SQLITE_MUTEX_STATIC_MASTER 2
-#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */
+#define SQLITE_MUTEX_STATIC_MEM 3 /* tdsqlite3_malloc() */
#define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */
-#define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */
-#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */
+#define SQLITE_MUTEX_STATIC_OPEN 4 /* tdsqlite3BtreeOpen() */
+#define SQLITE_MUTEX_STATIC_PRNG 5 /* tdsqlite3_randomness() */
#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */
#define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */
-#define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */
+#define SQLITE_MUTEX_STATIC_PMEM 7 /* tdsqlite3PageMalloc() */
#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */
#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */
#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */
@@ -6893,22 +8569,23 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
/*
** CAPI3REF: Retrieve the mutex for a database connection
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^This interface returns a pointer the [sqlite3_mutex] object that
+** ^This interface returns a pointer the [tdsqlite3_mutex] object that
** serializes access to the [database connection] given in the argument
** when the [threading mode] is Serialized.
** ^If the [threading mode] is Single-thread or Multi-thread then this
** routine returns a NULL pointer.
*/
-SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*);
+SQLITE_API tdsqlite3_mutex *tdsqlite3_db_mutex(tdsqlite3*);
/*
** CAPI3REF: Low-Level Control Of Database Files
-** METHOD: sqlite3
+** METHOD: tdsqlite3
+** KEYWORDS: {file control}
**
-** ^The [sqlite3_file_control()] interface makes a direct call to the
-** xFileControl method for the [sqlite3_io_methods] object associated
+** ^The [tdsqlite3_file_control()] interface makes a direct call to the
+** xFileControl method for the [tdsqlite3_io_methods] object associated
** with a particular database identified by the second argument. ^The
** name of the database is "main" for the main database or "temp" for the
** TEMP database, or the name that appears after the AS keyword for
@@ -6920,28 +8597,35 @@ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*);
** the xFileControl method. ^The return value of the xFileControl
** method becomes the return value of this routine.
**
-** ^The SQLITE_FCNTL_FILE_POINTER value for the op parameter causes
-** a pointer to the underlying [sqlite3_file] object to be written into
-** the space pointed to by the 4th parameter. ^The SQLITE_FCNTL_FILE_POINTER
-** case is a short-circuit path which does not actually invoke the
-** underlying sqlite3_io_methods.xFileControl method.
+** A few opcodes for [tdsqlite3_file_control()] are handled directly
+** by the SQLite core and never invoke the
+** tdsqlite3_io_methods.xFileControl method.
+** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes
+** a pointer to the underlying [tdsqlite3_file] object to be written into
+** the space pointed to by the 4th parameter. The
+** [SQLITE_FCNTL_JOURNAL_POINTER] works similarly except that it returns
+** the [tdsqlite3_file] object associated with the journal file instead of
+** the main database. The [SQLITE_FCNTL_VFS_POINTER] opcode returns
+** a pointer to the underlying [tdsqlite3_vfs] object for the file.
+** The [SQLITE_FCNTL_DATA_VERSION] returns the data version counter
+** from the pager.
**
** ^If the second parameter (zDbName) does not match the name of any
** open database file, then SQLITE_ERROR is returned. ^This error
-** code is not remembered and will not be recalled by [sqlite3_errcode()]
-** or [sqlite3_errmsg()]. The underlying xFileControl method might
+** code is not remembered and will not be recalled by [tdsqlite3_errcode()]
+** or [tdsqlite3_errmsg()]. The underlying xFileControl method might
** also return SQLITE_ERROR. There is no way to distinguish between
** an incorrect zDbName and an SQLITE_ERROR return from the underlying
** xFileControl method.
**
-** See also: [SQLITE_FCNTL_LOCKSTATE]
+** See also: [file control opcodes]
*/
-SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*);
+SQLITE_API int tdsqlite3_file_control(tdsqlite3*, const char *zDbName, int op, void*);
/*
** CAPI3REF: Testing Interface
**
-** ^The sqlite3_test_control() interface is used to read out internal
+** ^The tdsqlite3_test_control() interface is used to read out internal
** state of SQLite and to inject faults into SQLite for testing
** purposes. ^The first parameter is an operation code that determines
** the number, meaning, and operation of all subsequent parameters.
@@ -6955,23 +8639,23 @@ SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*
** Unlike most of the SQLite API, this function is not guaranteed to
** operate consistently from one release to the next.
*/
-SQLITE_API int sqlite3_test_control(int op, ...);
+SQLITE_API int tdsqlite3_test_control(int op, ...);
/*
** CAPI3REF: Testing Interface Operation Codes
**
** These constants are the valid operation code parameters used
-** as the first argument to [sqlite3_test_control()].
+** as the first argument to [tdsqlite3_test_control()].
**
** These parameters and their meanings are subject to change
** without notice. These values are for testing purposes only.
** Applications should not use any of these parameters or the
-** [sqlite3_test_control()] interface.
+** [tdsqlite3_test_control()] interface.
*/
#define SQLITE_TESTCTRL_FIRST 5
#define SQLITE_TESTCTRL_PRNG_SAVE 5
#define SQLITE_TESTCTRL_PRNG_RESTORE 6
-#define SQLITE_TESTCTRL_PRNG_RESET 7
+#define SQLITE_TESTCTRL_PRNG_RESET 7 /* NOT USED */
#define SQLITE_TESTCTRL_BITVEC_TEST 8
#define SQLITE_TESTCTRL_FAULT_INSTALL 9
#define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10
@@ -6980,8 +8664,9 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_ALWAYS 13
#define SQLITE_TESTCTRL_RESERVE 14
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
-#define SQLITE_TESTCTRL_ISKEYWORD 16
-#define SQLITE_TESTCTRL_SCRATCHMALLOC 17
+#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
+#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
+#define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19
@@ -6991,7 +8676,194 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_ISINIT 23
#define SQLITE_TESTCTRL_SORTER_MMAP 24
#define SQLITE_TESTCTRL_IMPOSTER 25
-#define SQLITE_TESTCTRL_LAST 25
+#define SQLITE_TESTCTRL_PARSER_COVERAGE 26
+#define SQLITE_TESTCTRL_RESULT_INTREAL 27
+#define SQLITE_TESTCTRL_PRNG_SEED 28
+#define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29
+#define SQLITE_TESTCTRL_LAST 29 /* Largest TESTCTRL */
+
+/*
+** CAPI3REF: SQL Keyword Checking
+**
+** These routines provide access to the set of SQL language keywords
+** recognized by SQLite. Applications can uses these routines to determine
+** whether or not a specific identifier needs to be escaped (for example,
+** by enclosing in double-quotes) so as not to confuse the parser.
+**
+** The tdsqlite3_keyword_count() interface returns the number of distinct
+** keywords understood by SQLite.
+**
+** The tdsqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and
+** makes *Z point to that keyword expressed as UTF8 and writes the number
+** of bytes in the keyword into *L. The string that *Z points to is not
+** zero-terminated. The tdsqlite3_keyword_name(N,Z,L) routine returns
+** SQLITE_OK if N is within bounds and SQLITE_ERROR if not. If either Z
+** or L are NULL or invalid pointers then calls to
+** tdsqlite3_keyword_name(N,Z,L) result in undefined behavior.
+**
+** The tdsqlite3_keyword_check(Z,L) interface checks to see whether or not
+** the L-byte UTF8 identifier that Z points to is a keyword, returning non-zero
+** if it is and zero if not.
+**
+** The parser used by SQLite is forgiving. It is often possible to use
+** a keyword as an identifier as long as such use does not result in a
+** parsing ambiguity. For example, the statement
+** "CREATE TABLE BEGIN(REPLACE,PRAGMA,END);" is accepted by SQLite, and
+** creates a new table named "BEGIN" with three columns named
+** "REPLACE", "PRAGMA", and "END". Nevertheless, best practice is to avoid
+** using keywords as identifiers. Common techniques used to avoid keyword
+** name collisions include:
+** <ul>
+** <li> Put all identifier names inside double-quotes. This is the official
+** SQL way to escape identifier names.
+** <li> Put identifier names inside &#91;...&#93;. This is not standard SQL,
+** but it is what SQL Server does and so lots of programmers use this
+** technique.
+** <li> Begin every identifier with the letter "Z" as no SQL keywords start
+** with "Z".
+** <li> Include a digit somewhere in every identifier name.
+** </ul>
+**
+** Note that the number of keywords understood by SQLite can depend on
+** compile-time options. For example, "VACUUM" is not a keyword if
+** SQLite is compiled with the [-DSQLITE_OMIT_VACUUM] option. Also,
+** new keywords may be added to future releases of SQLite.
+*/
+SQLITE_API int tdsqlite3_keyword_count(void);
+SQLITE_API int tdsqlite3_keyword_name(int,const char**,int*);
+SQLITE_API int tdsqlite3_keyword_check(const char*,int);
+
+/*
+** CAPI3REF: Dynamic String Object
+** KEYWORDS: {dynamic string}
+**
+** An instance of the tdsqlite3_str object contains a dynamically-sized
+** string under construction.
+**
+** The lifecycle of an tdsqlite3_str object is as follows:
+** <ol>
+** <li> ^The tdsqlite3_str object is created using [tdsqlite3_str_new()].
+** <li> ^Text is appended to the tdsqlite3_str object using various
+** methods, such as [tdsqlite3_str_appendf()].
+** <li> ^The tdsqlite3_str object is destroyed and the string it created
+** is returned using the [tdsqlite3_str_finish()] interface.
+** </ol>
+*/
+typedef struct tdsqlite3_str tdsqlite3_str;
+
+/*
+** CAPI3REF: Create A New Dynamic String Object
+** CONSTRUCTOR: tdsqlite3_str
+**
+** ^The [tdsqlite3_str_new(D)] interface allocates and initializes
+** a new [tdsqlite3_str] object. To avoid memory leaks, the object returned by
+** [tdsqlite3_str_new()] must be freed by a subsequent call to
+** [tdsqlite3_str_finish(X)].
+**
+** ^The [tdsqlite3_str_new(D)] interface always returns a pointer to a
+** valid [tdsqlite3_str] object, though in the event of an out-of-memory
+** error the returned object might be a special singleton that will
+** silently reject new text, always return SQLITE_NOMEM from
+** [tdsqlite3_str_errcode()], always return 0 for
+** [tdsqlite3_str_length()], and always return NULL from
+** [tdsqlite3_str_finish(X)]. It is always safe to use the value
+** returned by [tdsqlite3_str_new(D)] as the tdsqlite3_str parameter
+** to any of the other [tdsqlite3_str] methods.
+**
+** The D parameter to [tdsqlite3_str_new(D)] may be NULL. If the
+** D parameter in [tdsqlite3_str_new(D)] is not NULL, then the maximum
+** length of the string contained in the [tdsqlite3_str] object will be
+** the value set for [tdsqlite3_limit](D,[SQLITE_LIMIT_LENGTH]) instead
+** of [SQLITE_MAX_LENGTH].
+*/
+SQLITE_API tdsqlite3_str *tdsqlite3_str_new(tdsqlite3*);
+
+/*
+** CAPI3REF: Finalize A Dynamic String
+** DESTRUCTOR: tdsqlite3_str
+**
+** ^The [tdsqlite3_str_finish(X)] interface destroys the tdsqlite3_str object X
+** and returns a pointer to a memory buffer obtained from [tdsqlite3_malloc64()]
+** that contains the constructed string. The calling application should
+** pass the returned value to [tdsqlite3_free()] to avoid a memory leak.
+** ^The [tdsqlite3_str_finish(X)] interface may return a NULL pointer if any
+** errors were encountered during construction of the string. ^The
+** [tdsqlite3_str_finish(X)] interface will also return a NULL pointer if the
+** string in [tdsqlite3_str] object X is zero bytes long.
+*/
+SQLITE_API char *tdsqlite3_str_finish(tdsqlite3_str*);
+
+/*
+** CAPI3REF: Add Content To A Dynamic String
+** METHOD: tdsqlite3_str
+**
+** These interfaces add content to an tdsqlite3_str object previously obtained
+** from [tdsqlite3_str_new()].
+**
+** ^The [tdsqlite3_str_appendf(X,F,...)] and
+** [tdsqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf]
+** functionality of SQLite to append formatted text onto the end of
+** [tdsqlite3_str] object X.
+**
+** ^The [tdsqlite3_str_append(X,S,N)] method appends exactly N bytes from string S
+** onto the end of the [tdsqlite3_str] object X. N must be non-negative.
+** S must contain at least N non-zero bytes of content. To append a
+** zero-terminated string in its entirety, use the [tdsqlite3_str_appendall()]
+** method instead.
+**
+** ^The [tdsqlite3_str_appendall(X,S)] method appends the complete content of
+** zero-terminated string S onto the end of [tdsqlite3_str] object X.
+**
+** ^The [tdsqlite3_str_appendchar(X,N,C)] method appends N copies of the
+** single-byte character C onto the end of [tdsqlite3_str] object X.
+** ^This method can be used, for example, to add whitespace indentation.
+**
+** ^The [tdsqlite3_str_reset(X)] method resets the string under construction
+** inside [tdsqlite3_str] object X back to zero bytes in length.
+**
+** These methods do not return a result code. ^If an error occurs, that fact
+** is recorded in the [tdsqlite3_str] object and can be recovered by a
+** subsequent call to [tdsqlite3_str_errcode(X)].
+*/
+SQLITE_API void tdsqlite3_str_appendf(tdsqlite3_str*, const char *zFormat, ...);
+SQLITE_API void tdsqlite3_str_vappendf(tdsqlite3_str*, const char *zFormat, va_list);
+SQLITE_API void tdsqlite3_str_append(tdsqlite3_str*, const char *zIn, int N);
+SQLITE_API void tdsqlite3_str_appendall(tdsqlite3_str*, const char *zIn);
+SQLITE_API void tdsqlite3_str_appendchar(tdsqlite3_str*, int N, char C);
+SQLITE_API void tdsqlite3_str_reset(tdsqlite3_str*);
+
+/*
+** CAPI3REF: Status Of A Dynamic String
+** METHOD: tdsqlite3_str
+**
+** These interfaces return the current status of an [tdsqlite3_str] object.
+**
+** ^If any prior errors have occurred while constructing the dynamic string
+** in tdsqlite3_str X, then the [tdsqlite3_str_errcode(X)] method will return
+** an appropriate error code. ^The [tdsqlite3_str_errcode(X)] method returns
+** [SQLITE_NOMEM] following any out-of-memory error, or
+** [SQLITE_TOOBIG] if the size of the dynamic string exceeds
+** [SQLITE_MAX_LENGTH], or [SQLITE_OK] if there have been no errors.
+**
+** ^The [tdsqlite3_str_length(X)] method returns the current length, in bytes,
+** of the dynamic string under construction in [tdsqlite3_str] object X.
+** ^The length returned by [tdsqlite3_str_length(X)] does not include the
+** zero-termination byte.
+**
+** ^The [tdsqlite3_str_value(X)] method returns a pointer to the current
+** content of the dynamic string under construction in X. The value
+** returned by [tdsqlite3_str_value(X)] is managed by the tdsqlite3_str object X
+** and might be freed or altered by any subsequent method on the same
+** [tdsqlite3_str] object. Applications must not used the pointer returned
+** [tdsqlite3_str_value(X)] after any subsequent method call on the same
+** object. ^Applications may change the content of the string returned
+** by [tdsqlite3_str_value(X)] as long as they do not write into any bytes
+** outside the range of 0 to [tdsqlite3_str_length(X)] and do not read or
+** write any byte after any subsequent tdsqlite3_str method call.
+*/
+SQLITE_API int tdsqlite3_str_errcode(tdsqlite3_str*);
+SQLITE_API int tdsqlite3_str_length(tdsqlite3_str*);
+SQLITE_API char *tdsqlite3_str_value(tdsqlite3_str*);
/*
** CAPI3REF: SQLite Runtime Status
@@ -7010,20 +8882,20 @@ SQLITE_API int sqlite3_test_control(int op, ...);
** ^(Other parameters record only the highwater mark and not the current
** value. For these latter parameters nothing is written into *pCurrent.)^
**
-** ^The sqlite3_status() and sqlite3_status64() routines return
+** ^The tdsqlite3_status() and tdsqlite3_status64() routines return
** SQLITE_OK on success and a non-zero [error code] on failure.
**
** If either the current value or the highwater mark is too large to
** be represented by a 32-bit integer, then the values returned by
-** sqlite3_status() are undefined.
+** tdsqlite3_status() are undefined.
**
-** See also: [sqlite3_db_status()]
+** See also: [tdsqlite3_db_status()]
*/
-SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag);
-SQLITE_API int sqlite3_status64(
+SQLITE_API int tdsqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag);
+SQLITE_API int tdsqlite3_status64(
int op,
- sqlite3_int64 *pCurrent,
- sqlite3_int64 *pHighwater,
+ tdsqlite3_int64 *pCurrent,
+ tdsqlite3_int64 *pHighwater,
int resetFlag
);
@@ -7033,24 +8905,23 @@ SQLITE_API int sqlite3_status64(
** KEYWORDS: {status parameters}
**
** These integer constants designate various run-time status parameters
-** that can be returned by [sqlite3_status()].
+** that can be returned by [tdsqlite3_status()].
**
** <dl>
** [[SQLITE_STATUS_MEMORY_USED]] ^(<dt>SQLITE_STATUS_MEMORY_USED</dt>
** <dd>This parameter is the current amount of memory checked out
-** using [sqlite3_malloc()], either directly or indirectly. The
-** figure includes calls made to [sqlite3_malloc()] by the application
-** and internal memory usage by the SQLite library. Scratch memory
-** controlled by [SQLITE_CONFIG_SCRATCH] and auxiliary page-cache
+** using [tdsqlite3_malloc()], either directly or indirectly. The
+** figure includes calls made to [tdsqlite3_malloc()] by the application
+** and internal memory usage by the SQLite library. Auxiliary page-cache
** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in
** this parameter. The amount returned is the sum of the allocation
-** sizes as reported by the xSize method in [sqlite3_mem_methods].</dd>)^
+** sizes as reported by the xSize method in [tdsqlite3_mem_methods].</dd>)^
**
** [[SQLITE_STATUS_MALLOC_SIZE]] ^(<dt>SQLITE_STATUS_MALLOC_SIZE</dt>
** <dd>This parameter records the largest memory allocation request
-** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their
+** handed to [tdsqlite3_malloc()] or [tdsqlite3_realloc()] (or their
** internal equivalents). Only the value returned in the
-** *pHighwater parameter to [sqlite3_status()] is of interest.
+** *pHighwater parameter to [tdsqlite3_status()] is of interest.
** The value written into the *pCurrent parameter is undefined.</dd>)^
**
** [[SQLITE_STATUS_MALLOC_COUNT]] ^(<dt>SQLITE_STATUS_MALLOC_COUNT</dt>
@@ -7067,7 +8938,7 @@ SQLITE_API int sqlite3_status64(
** ^(<dt>SQLITE_STATUS_PAGECACHE_OVERFLOW</dt>
** <dd>This parameter returns the number of bytes of page cache
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
-** buffer and where forced to overflow to [sqlite3_malloc()]. The
+** buffer and where forced to overflow to [tdsqlite3_malloc()]. The
** returned value includes allocations that overflowed because they
** where too large (they were larger than the "sz" parameter to
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
@@ -7075,33 +8946,18 @@ SQLITE_API int sqlite3_status64(
**
** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(<dt>SQLITE_STATUS_PAGECACHE_SIZE</dt>
** <dd>This parameter records the largest memory allocation request
-** handed to [pagecache memory allocator]. Only the value returned in the
-** *pHighwater parameter to [sqlite3_status()] is of interest.
+** handed to the [pagecache memory allocator]. Only the value returned in the
+** *pHighwater parameter to [tdsqlite3_status()] is of interest.
** The value written into the *pCurrent parameter is undefined.</dd>)^
**
-** [[SQLITE_STATUS_SCRATCH_USED]] ^(<dt>SQLITE_STATUS_SCRATCH_USED</dt>
-** <dd>This parameter returns the number of allocations used out of the
-** [scratch memory allocator] configured using
-** [SQLITE_CONFIG_SCRATCH]. The value returned is in allocations, not
-** in bytes. Since a single thread may only have one scratch allocation
-** outstanding at time, this parameter also reports the number of threads
-** using scratch memory at the same time.</dd>)^
+** [[SQLITE_STATUS_SCRATCH_USED]] <dt>SQLITE_STATUS_SCRATCH_USED</dt>
+** <dd>No longer used.</dd>
**
** [[SQLITE_STATUS_SCRATCH_OVERFLOW]] ^(<dt>SQLITE_STATUS_SCRATCH_OVERFLOW</dt>
-** <dd>This parameter returns the number of bytes of scratch memory
-** allocation which could not be satisfied by the [SQLITE_CONFIG_SCRATCH]
-** buffer and where forced to overflow to [sqlite3_malloc()]. The values
-** returned include overflows because the requested allocation was too
-** larger (that is, because the requested allocation was larger than the
-** "sz" parameter to [SQLITE_CONFIG_SCRATCH]) and because no scratch buffer
-** slots were available.
-** </dd>)^
+** <dd>No longer used.</dd>
**
-** [[SQLITE_STATUS_SCRATCH_SIZE]] ^(<dt>SQLITE_STATUS_SCRATCH_SIZE</dt>
-** <dd>This parameter records the largest memory allocation request
-** handed to [scratch memory allocator]. Only the value returned in the
-** *pHighwater parameter to [sqlite3_status()] is of interest.
-** The value written into the *pCurrent parameter is undefined.</dd>)^
+** [[SQLITE_STATUS_SCRATCH_SIZE]] <dt>SQLITE_STATUS_SCRATCH_SIZE</dt>
+** <dd>No longer used.</dd>
**
** [[SQLITE_STATUS_PARSER_STACK]] ^(<dt>SQLITE_STATUS_PARSER_STACK</dt>
** <dd>The *pHighwater parameter records the deepest parser stack.
@@ -7114,17 +8970,17 @@ SQLITE_API int sqlite3_status64(
#define SQLITE_STATUS_MEMORY_USED 0
#define SQLITE_STATUS_PAGECACHE_USED 1
#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2
-#define SQLITE_STATUS_SCRATCH_USED 3
-#define SQLITE_STATUS_SCRATCH_OVERFLOW 4
+#define SQLITE_STATUS_SCRATCH_USED 3 /* NOT USED */
+#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 /* NOT USED */
#define SQLITE_STATUS_MALLOC_SIZE 5
#define SQLITE_STATUS_PARSER_STACK 6
#define SQLITE_STATUS_PAGECACHE_SIZE 7
-#define SQLITE_STATUS_SCRATCH_SIZE 8
+#define SQLITE_STATUS_SCRATCH_SIZE 8 /* NOT USED */
#define SQLITE_STATUS_MALLOC_COUNT 9
/*
** CAPI3REF: Database Connection Status
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This interface is used to retrieve runtime status information
** about a single [database connection]. ^The first argument is the
@@ -7140,24 +8996,24 @@ SQLITE_API int sqlite3_status64(
** the resetFlg is true, then the highest instantaneous value is
** reset back down to the current value.
**
-** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a
+** ^The tdsqlite3_db_status() routine returns SQLITE_OK on success and a
** non-zero [error code] on failure.
**
-** See also: [sqlite3_status()] and [sqlite3_stmt_status()].
+** See also: [tdsqlite3_status()] and [tdsqlite3_stmt_status()].
*/
-SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
+SQLITE_API int tdsqlite3_db_status(tdsqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
/*
** CAPI3REF: Status Parameters for database connections
** KEYWORDS: {SQLITE_DBSTATUS options}
**
** These constants are the available integer "verbs" that can be passed as
-** the second argument to the [sqlite3_db_status()] interface.
+** the second argument to the [tdsqlite3_db_status()] interface.
**
** New verbs may be added in future releases of SQLite. Existing verbs
** might be discontinued. Applications should check the return code from
-** [sqlite3_db_status()] to make sure that the call worked.
-** The [sqlite3_db_status()] interface will return a non-zero error code
+** [tdsqlite3_db_status()] to make sure that the call worked.
+** The [tdsqlite3_db_status()] interface will return a non-zero error code
** if a discontinued or unsupported verb is invoked.
**
** <dl>
@@ -7166,7 +9022,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** checked out.</dd>)^
**
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
-** <dd>This parameter returns the number malloc attempts that were
+** <dd>This parameter returns the number of malloc attempts that were
** satisfied using lookaside memory. Only the high-water value is meaningful;
** the current value is always zero.)^
**
@@ -7242,6 +9098,15 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
** </dd>
**
+** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt>
+** <dd>This parameter returns the number of dirty cache entries that have
+** been written to disk in the middle of a transaction due to the page
+** cache overflowing. Transactions are more efficient if they are written
+** to disk all at once. When pages spill mid-transaction, that introduces
+** additional overhead. This parameter can be used help identify
+** inefficiencies that can be resolved by increasing the cache size.
+** </dd>
+**
** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt>
** <dd>This parameter returns zero for the current value if and only if
** all foreign key constraints (deferred or immediate) have been
@@ -7261,12 +9126,13 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
#define SQLITE_DBSTATUS_CACHE_WRITE 9
#define SQLITE_DBSTATUS_DEFERRED_FKS 10
#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11
-#define SQLITE_DBSTATUS_MAX 11 /* Largest defined DBSTATUS */
+#define SQLITE_DBSTATUS_CACHE_SPILL 12
+#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */
/*
** CAPI3REF: Prepared Statement Status
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^(Each prepared statement maintains various
** [SQLITE_STMTSTATUS counters] that measure the number
@@ -7286,16 +9152,16 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** ^If the resetFlg is true, then the counter is reset to zero after this
** interface call returns.
**
-** See also: [sqlite3_status()] and [sqlite3_db_status()].
+** See also: [tdsqlite3_status()] and [tdsqlite3_db_status()].
*/
-SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
+SQLITE_API int tdsqlite3_stmt_status(tdsqlite3_stmt*, int op,int resetFlg);
/*
** CAPI3REF: Status Parameters for prepared statements
** KEYWORDS: {SQLITE_STMTSTATUS counter} {SQLITE_STMTSTATUS counters}
**
** These preprocessor macros define integer codes that name counter
-** values associated with the [sqlite3_stmt_status()] interface.
+** values associated with the [tdsqlite3_stmt_status()] interface.
** The meanings of the various counters are as follows:
**
** <dl>
@@ -7324,6 +9190,24 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
** used as a proxy for the total work done by the prepared statement.
** If the number of virtual machine operations exceeds 2147483647
** then the value returned by this statement status code is undefined.
+**
+** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
+** <dd>^This is the number of times that the prepare statement has been
+** automatically regenerated due to schema changes or changes to
+** [bound parameters] that might affect the query plan.
+**
+** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
+** <dd>^This is the number of times that the prepared statement has
+** been run. A single "run" for the purposes of this counter is one
+** or more calls to [tdsqlite3_step()] followed by a call to [tdsqlite3_reset()].
+** The counter is incremented on the first [tdsqlite3_step()] call of each
+** cycle.
+**
+** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
+** <dd>^This is the approximate number of bytes of heap memory
+** used to store the prepared statement. ^This value is not actually
+** a counter, and so the resetFlg parameter to tdsqlite3_stmt_status()
+** is ignored when the opcode is SQLITE_STMTSTATUS_MEMUSED.
** </dd>
** </dl>
*/
@@ -7331,32 +9215,35 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
#define SQLITE_STMTSTATUS_SORT 2
#define SQLITE_STMTSTATUS_AUTOINDEX 3
#define SQLITE_STMTSTATUS_VM_STEP 4
+#define SQLITE_STMTSTATUS_REPREPARE 5
+#define SQLITE_STMTSTATUS_RUN 6
+#define SQLITE_STMTSTATUS_MEMUSED 99
/*
** CAPI3REF: Custom Page Cache Object
**
-** The sqlite3_pcache type is opaque. It is implemented by
+** The tdsqlite3_pcache type is opaque. It is implemented by
** the pluggable module. The SQLite core has no knowledge of
** its size or internal structure and never deals with the
-** sqlite3_pcache object except by holding and passing pointers
+** tdsqlite3_pcache object except by holding and passing pointers
** to the object.
**
-** See [sqlite3_pcache_methods2] for additional information.
+** See [tdsqlite3_pcache_methods2] for additional information.
*/
-typedef struct sqlite3_pcache sqlite3_pcache;
+typedef struct tdsqlite3_pcache tdsqlite3_pcache;
/*
** CAPI3REF: Custom Page Cache Object
**
-** The sqlite3_pcache_page object represents a single page in the
+** The tdsqlite3_pcache_page object represents a single page in the
** page cache. The page cache will allocate instances of this
** object. Various methods of the page cache use pointers to instances
** of this object as parameters or as their return value.
**
-** See [sqlite3_pcache_methods2] for additional information.
+** See [tdsqlite3_pcache_methods2] for additional information.
*/
-typedef struct sqlite3_pcache_page sqlite3_pcache_page;
-struct sqlite3_pcache_page {
+typedef struct tdsqlite3_pcache_page tdsqlite3_pcache_page;
+struct tdsqlite3_pcache_page {
void *pBuf; /* The content of the page */
void *pExtra; /* Extra information associated with the page */
};
@@ -7365,9 +9252,9 @@ struct sqlite3_pcache_page {
** CAPI3REF: Application Defined Page Cache.
** KEYWORDS: {page cache}
**
-** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can
+** ^(The [tdsqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can
** register an alternative page cache implementation by passing in an
-** instance of the sqlite3_pcache_methods2 structure.)^
+** instance of the tdsqlite3_pcache_methods2 structure.)^
** In many applications, most of the heap memory allocated by
** SQLite is used for the page cache.
** By implementing a
@@ -7381,16 +9268,16 @@ struct sqlite3_pcache_page {
** extreme measure that is only needed by the most demanding applications.
** The built-in page cache is recommended for most uses.
**
-** ^(The contents of the sqlite3_pcache_methods2 structure are copied to an
-** internal buffer by SQLite within the call to [sqlite3_config]. Hence
+** ^(The contents of the tdsqlite3_pcache_methods2 structure are copied to an
+** internal buffer by SQLite within the call to [tdsqlite3_config]. Hence
** the application may discard the parameter after the call to
-** [sqlite3_config()] returns.)^
+** [tdsqlite3_config()] returns.)^
**
** [[the xInit() page cache method]]
** ^(The xInit() method is called once for each effective
-** call to [sqlite3_initialize()])^
+** call to [tdsqlite3_initialize()])^
** (usually only once during the lifetime of the process). ^(The xInit()
-** method is passed a copy of the sqlite3_pcache_methods2.pArg value.)^
+** method is passed a copy of the tdsqlite3_pcache_methods2.pArg value.)^
** The intent of the xInit() method is to set up global data structures
** required by the custom page cache implementation.
** ^(If the xInit() method is NULL, then the
@@ -7398,14 +9285,14 @@ struct sqlite3_pcache_page {
** page cache.)^
**
** [[the xShutdown() page cache method]]
-** ^The xShutdown() method is called by [sqlite3_shutdown()].
+** ^The xShutdown() method is called by [tdsqlite3_shutdown()].
** It can be used to clean up
** any outstanding resources before process shutdown, if required.
** ^The xShutdown() method may be NULL.
**
** ^SQLite automatically serializes calls to the xInit method,
** so the xInit method need not be threadsafe. ^The
-** xShutdown method is only called from [sqlite3_shutdown()] so it does
+** xShutdown method is only called from [tdsqlite3_shutdown()] so it does
** not need to be threadsafe either. All other methods must be threadsafe
** in multithreaded applications.
**
@@ -7449,10 +9336,10 @@ struct sqlite3_pcache_page {
**
** [[the xFetch() page cache methods]]
** The xFetch() method locates a page in the cache and returns a pointer to
-** an sqlite3_pcache_page object associated with that page, or a NULL pointer.
-** The pBuf element of the returned sqlite3_pcache_page object will be a
+** an tdsqlite3_pcache_page object associated with that page, or a NULL pointer.
+** The pBuf element of the returned tdsqlite3_pcache_page object will be a
** pointer to a buffer of szPage bytes used to store the content of a
-** single database page. The pExtra element of sqlite3_pcache_page will be
+** single database page. The pExtra element of tdsqlite3_pcache_page will be
** a pointer to the szExtra bytes of extra storage that SQLite has requested
** for each entry in the page cache.
**
@@ -7477,7 +9364,7 @@ struct sqlite3_pcache_page {
**
** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite
** will only use a createFlag of 2 after a prior call with a createFlag of 1
-** failed.)^ In between the to xFetch() calls, SQLite may
+** failed.)^ In between the xFetch() calls, SQLite may
** attempt to unpin one or more cache pages by spilling the content of
** pinned pages to disk and synching the operating system disk cache.
**
@@ -7510,8 +9397,8 @@ struct sqlite3_pcache_page {
** [[the xDestroy() page cache method]]
** ^The xDestroy() method is used to delete a cache allocated by xCreate().
** All resources associated with the specified cache should be freed. ^After
-** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*]
-** handle invalid, and will not use it with any other sqlite3_pcache_methods2
+** calling the xDestroy() method, SQLite considers the [tdsqlite3_pcache*]
+** handle invalid, and will not use it with any other tdsqlite3_pcache_methods2
** functions.
**
** [[the xShrink() page cache method]]
@@ -7520,56 +9407,56 @@ struct sqlite3_pcache_page {
** is not obligated to free any memory, but well-behaved implementations should
** do their best.
*/
-typedef struct sqlite3_pcache_methods2 sqlite3_pcache_methods2;
-struct sqlite3_pcache_methods2 {
+typedef struct tdsqlite3_pcache_methods2 tdsqlite3_pcache_methods2;
+struct tdsqlite3_pcache_methods2 {
int iVersion;
void *pArg;
int (*xInit)(void*);
void (*xShutdown)(void*);
- sqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable);
- void (*xCachesize)(sqlite3_pcache*, int nCachesize);
- int (*xPagecount)(sqlite3_pcache*);
- sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag);
- void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard);
- void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*,
+ tdsqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable);
+ void (*xCachesize)(tdsqlite3_pcache*, int nCachesize);
+ int (*xPagecount)(tdsqlite3_pcache*);
+ tdsqlite3_pcache_page *(*xFetch)(tdsqlite3_pcache*, unsigned key, int createFlag);
+ void (*xUnpin)(tdsqlite3_pcache*, tdsqlite3_pcache_page*, int discard);
+ void (*xRekey)(tdsqlite3_pcache*, tdsqlite3_pcache_page*,
unsigned oldKey, unsigned newKey);
- void (*xTruncate)(sqlite3_pcache*, unsigned iLimit);
- void (*xDestroy)(sqlite3_pcache*);
- void (*xShrink)(sqlite3_pcache*);
+ void (*xTruncate)(tdsqlite3_pcache*, unsigned iLimit);
+ void (*xDestroy)(tdsqlite3_pcache*);
+ void (*xShrink)(tdsqlite3_pcache*);
};
/*
** This is the obsolete pcache_methods object that has now been replaced
-** by sqlite3_pcache_methods2. This object is not used by SQLite. It is
+** by tdsqlite3_pcache_methods2. This object is not used by SQLite. It is
** retained in the header file for backwards compatibility only.
*/
-typedef struct sqlite3_pcache_methods sqlite3_pcache_methods;
-struct sqlite3_pcache_methods {
+typedef struct tdsqlite3_pcache_methods tdsqlite3_pcache_methods;
+struct tdsqlite3_pcache_methods {
void *pArg;
int (*xInit)(void*);
void (*xShutdown)(void*);
- sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable);
- void (*xCachesize)(sqlite3_pcache*, int nCachesize);
- int (*xPagecount)(sqlite3_pcache*);
- void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag);
- void (*xUnpin)(sqlite3_pcache*, void*, int discard);
- void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey);
- void (*xTruncate)(sqlite3_pcache*, unsigned iLimit);
- void (*xDestroy)(sqlite3_pcache*);
+ tdsqlite3_pcache *(*xCreate)(int szPage, int bPurgeable);
+ void (*xCachesize)(tdsqlite3_pcache*, int nCachesize);
+ int (*xPagecount)(tdsqlite3_pcache*);
+ void *(*xFetch)(tdsqlite3_pcache*, unsigned key, int createFlag);
+ void (*xUnpin)(tdsqlite3_pcache*, void*, int discard);
+ void (*xRekey)(tdsqlite3_pcache*, void*, unsigned oldKey, unsigned newKey);
+ void (*xTruncate)(tdsqlite3_pcache*, unsigned iLimit);
+ void (*xDestroy)(tdsqlite3_pcache*);
};
/*
** CAPI3REF: Online Backup Object
**
-** The sqlite3_backup object records state information about an ongoing
-** online backup operation. ^The sqlite3_backup object is created by
-** a call to [sqlite3_backup_init()] and is destroyed by a call to
-** [sqlite3_backup_finish()].
+** The tdsqlite3_backup object records state information about an ongoing
+** online backup operation. ^The tdsqlite3_backup object is created by
+** a call to [tdsqlite3_backup_init()] and is destroyed by a call to
+** [tdsqlite3_backup_finish()].
**
** See Also: [Using the SQLite Online Backup API]
*/
-typedef struct sqlite3_backup sqlite3_backup;
+typedef struct tdsqlite3_backup tdsqlite3_backup;
/*
** CAPI3REF: Online Backup API.
@@ -7590,63 +9477,63 @@ typedef struct sqlite3_backup sqlite3_backup;
**
** ^(To perform a backup operation:
** <ol>
-** <li><b>sqlite3_backup_init()</b> is called once to initialize the
+** <li><b>tdsqlite3_backup_init()</b> is called once to initialize the
** backup,
-** <li><b>sqlite3_backup_step()</b> is called one or more times to transfer
+** <li><b>tdsqlite3_backup_step()</b> is called one or more times to transfer
** the data between the two databases, and finally
-** <li><b>sqlite3_backup_finish()</b> is called to release all resources
+** <li><b>tdsqlite3_backup_finish()</b> is called to release all resources
** associated with the backup operation.
** </ol>)^
-** There should be exactly one call to sqlite3_backup_finish() for each
-** successful call to sqlite3_backup_init().
+** There should be exactly one call to tdsqlite3_backup_finish() for each
+** successful call to tdsqlite3_backup_init().
**
-** [[sqlite3_backup_init()]] <b>sqlite3_backup_init()</b>
+** [[tdsqlite3_backup_init()]] <b>tdsqlite3_backup_init()</b>
**
-** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the
+** ^The D and N arguments to tdsqlite3_backup_init(D,N,S,M) are the
** [database connection] associated with the destination database
** and the database name, respectively.
** ^The database name is "main" for the main database, "temp" for the
** temporary database, or the name specified after the AS keyword in
** an [ATTACH] statement for an attached database.
** ^The S and M arguments passed to
-** sqlite3_backup_init(D,N,S,M) identify the [database connection]
+** tdsqlite3_backup_init(D,N,S,M) identify the [database connection]
** and database name of the source database, respectively.
** ^The source and destination [database connections] (parameters S and D)
-** must be different or else sqlite3_backup_init(D,N,S,M) will fail with
+** must be different or else tdsqlite3_backup_init(D,N,S,M) will fail with
** an error.
**
-** ^A call to sqlite3_backup_init() will fail, returning NULL, if
+** ^A call to tdsqlite3_backup_init() will fail, returning NULL, if
** there is already a read or read-write transaction open on the
** destination database.
**
-** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is
+** ^If an error occurs within tdsqlite3_backup_init(D,N,S,M), then NULL is
** returned and an error code and error message are stored in the
** destination [database connection] D.
-** ^The error code and message for the failed call to sqlite3_backup_init()
-** can be retrieved using the [sqlite3_errcode()], [sqlite3_errmsg()], and/or
-** [sqlite3_errmsg16()] functions.
-** ^A successful call to sqlite3_backup_init() returns a pointer to an
-** [sqlite3_backup] object.
-** ^The [sqlite3_backup] object may be used with the sqlite3_backup_step() and
-** sqlite3_backup_finish() functions to perform the specified backup
+** ^The error code and message for the failed call to tdsqlite3_backup_init()
+** can be retrieved using the [tdsqlite3_errcode()], [tdsqlite3_errmsg()], and/or
+** [tdsqlite3_errmsg16()] functions.
+** ^A successful call to tdsqlite3_backup_init() returns a pointer to an
+** [tdsqlite3_backup] object.
+** ^The [tdsqlite3_backup] object may be used with the tdsqlite3_backup_step() and
+** tdsqlite3_backup_finish() functions to perform the specified backup
** operation.
**
-** [[sqlite3_backup_step()]] <b>sqlite3_backup_step()</b>
+** [[tdsqlite3_backup_step()]] <b>tdsqlite3_backup_step()</b>
**
-** ^Function sqlite3_backup_step(B,N) will copy up to N pages between
-** the source and destination databases specified by [sqlite3_backup] object B.
+** ^Function tdsqlite3_backup_step(B,N) will copy up to N pages between
+** the source and destination databases specified by [tdsqlite3_backup] object B.
** ^If N is negative, all remaining source pages are copied.
-** ^If sqlite3_backup_step(B,N) successfully copies N pages and there
+** ^If tdsqlite3_backup_step(B,N) successfully copies N pages and there
** are still more pages to be copied, then the function returns [SQLITE_OK].
-** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages
+** ^If tdsqlite3_backup_step(B,N) successfully finishes copying all pages
** from source to destination, then it returns [SQLITE_DONE].
-** ^If an error occurs while running sqlite3_backup_step(B,N),
+** ^If an error occurs while running tdsqlite3_backup_step(B,N),
** then an [error code] is returned. ^As well as [SQLITE_OK] and
-** [SQLITE_DONE], a call to sqlite3_backup_step() may return [SQLITE_READONLY],
+** [SQLITE_DONE], a call to tdsqlite3_backup_step() may return [SQLITE_READONLY],
** [SQLITE_NOMEM], [SQLITE_BUSY], [SQLITE_LOCKED], or an
** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX] extended error code.
**
-** ^(The sqlite3_backup_step() might return [SQLITE_READONLY] if
+** ^(The tdsqlite3_backup_step() might return [SQLITE_READONLY] if
** <ol>
** <li> the destination database was opened read-only, or
** <li> the destination database is using write-ahead-log journaling
@@ -7655,76 +9542,76 @@ typedef struct sqlite3_backup sqlite3_backup;
** destination and source page sizes differ.
** </ol>)^
**
-** ^If sqlite3_backup_step() cannot obtain a required file-system lock, then
-** the [sqlite3_busy_handler | busy-handler function]
+** ^If tdsqlite3_backup_step() cannot obtain a required file-system lock, then
+** the [tdsqlite3_busy_handler | busy-handler function]
** is invoked (if one is specified). ^If the
** busy-handler returns non-zero before the lock is available, then
** [SQLITE_BUSY] is returned to the caller. ^In this case the call to
-** sqlite3_backup_step() can be retried later. ^If the source
+** tdsqlite3_backup_step() can be retried later. ^If the source
** [database connection]
-** is being used to write to the source database when sqlite3_backup_step()
+** is being used to write to the source database when tdsqlite3_backup_step()
** is called, then [SQLITE_LOCKED] is returned immediately. ^Again, in this
-** case the call to sqlite3_backup_step() can be retried later on. ^(If
+** case the call to tdsqlite3_backup_step() can be retried later on. ^(If
** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or
** [SQLITE_READONLY] is returned, then
-** there is no point in retrying the call to sqlite3_backup_step(). These
+** there is no point in retrying the call to tdsqlite3_backup_step(). These
** errors are considered fatal.)^ The application must accept
** that the backup operation has failed and pass the backup operation handle
-** to the sqlite3_backup_finish() to release associated resources.
+** to the tdsqlite3_backup_finish() to release associated resources.
**
-** ^The first call to sqlite3_backup_step() obtains an exclusive lock
+** ^The first call to tdsqlite3_backup_step() obtains an exclusive lock
** on the destination file. ^The exclusive lock is not released until either
-** sqlite3_backup_finish() is called or the backup operation is complete
-** and sqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to
-** sqlite3_backup_step() obtains a [shared lock] on the source database that
-** lasts for the duration of the sqlite3_backup_step() call.
+** tdsqlite3_backup_finish() is called or the backup operation is complete
+** and tdsqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to
+** tdsqlite3_backup_step() obtains a [shared lock] on the source database that
+** lasts for the duration of the tdsqlite3_backup_step() call.
** ^Because the source database is not locked between calls to
-** sqlite3_backup_step(), the source database may be modified mid-way
+** tdsqlite3_backup_step(), the source database may be modified mid-way
** through the backup process. ^If the source database is modified by an
** external process or via a database connection other than the one being
** used by the backup operation, then the backup will be automatically
-** restarted by the next call to sqlite3_backup_step(). ^If the source
+** restarted by the next call to tdsqlite3_backup_step(). ^If the source
** database is modified by the using the same database connection as is used
** by the backup operation, then the backup database is automatically
** updated at the same time.
**
-** [[sqlite3_backup_finish()]] <b>sqlite3_backup_finish()</b>
+** [[tdsqlite3_backup_finish()]] <b>tdsqlite3_backup_finish()</b>
**
-** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the
+** When tdsqlite3_backup_step() has returned [SQLITE_DONE], or when the
** application wishes to abandon the backup operation, the application
-** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish().
-** ^The sqlite3_backup_finish() interfaces releases all
-** resources associated with the [sqlite3_backup] object.
-** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any
+** should destroy the [tdsqlite3_backup] by passing it to tdsqlite3_backup_finish().
+** ^The tdsqlite3_backup_finish() interfaces releases all
+** resources associated with the [tdsqlite3_backup] object.
+** ^If tdsqlite3_backup_step() has not yet returned [SQLITE_DONE], then any
** active write-transaction on the destination database is rolled back.
-** The [sqlite3_backup] object is invalid
-** and may not be used following a call to sqlite3_backup_finish().
+** The [tdsqlite3_backup] object is invalid
+** and may not be used following a call to tdsqlite3_backup_finish().
**
-** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
-** sqlite3_backup_step() errors occurred, regardless or whether or not
-** sqlite3_backup_step() completed.
+** ^The value returned by tdsqlite3_backup_finish is [SQLITE_OK] if no
+** tdsqlite3_backup_step() errors occurred, regardless or whether or not
+** tdsqlite3_backup_step() completed.
** ^If an out-of-memory condition or IO error occurred during any prior
-** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
-** sqlite3_backup_finish() returns the corresponding [error code].
+** tdsqlite3_backup_step() call on the same [tdsqlite3_backup] object, then
+** tdsqlite3_backup_finish() returns the corresponding [error code].
**
-** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step()
+** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from tdsqlite3_backup_step()
** is not a permanent error and does not affect the return value of
-** sqlite3_backup_finish().
+** tdsqlite3_backup_finish().
**
-** [[sqlite3_backup_remaining()]] [[sqlite3_backup_pagecount()]]
-** <b>sqlite3_backup_remaining() and sqlite3_backup_pagecount()</b>
+** [[tdsqlite3_backup_remaining()]] [[tdsqlite3_backup_pagecount()]]
+** <b>tdsqlite3_backup_remaining() and tdsqlite3_backup_pagecount()</b>
**
-** ^The sqlite3_backup_remaining() routine returns the number of pages still
-** to be backed up at the conclusion of the most recent sqlite3_backup_step().
-** ^The sqlite3_backup_pagecount() routine returns the total number of pages
+** ^The tdsqlite3_backup_remaining() routine returns the number of pages still
+** to be backed up at the conclusion of the most recent tdsqlite3_backup_step().
+** ^The tdsqlite3_backup_pagecount() routine returns the total number of pages
** in the source database at the conclusion of the most recent
-** sqlite3_backup_step().
+** tdsqlite3_backup_step().
** ^(The values returned by these functions are only updated by
-** sqlite3_backup_step(). If the source database is modified in a way that
+** tdsqlite3_backup_step(). If the source database is modified in a way that
** changes the size of the source database or the number of pages remaining,
-** those changes are not reflected in the output of sqlite3_backup_pagecount()
-** and sqlite3_backup_remaining() until after the next
-** sqlite3_backup_step().)^
+** those changes are not reflected in the output of tdsqlite3_backup_pagecount()
+** and tdsqlite3_backup_remaining() until after the next
+** tdsqlite3_backup_step().)^
**
** <b>Concurrent Usage of Database Handles</b>
**
@@ -7736,8 +9623,8 @@ typedef struct sqlite3_backup sqlite3_backup;
**
** However, the application must guarantee that the destination
** [database connection] is not passed to any other API (by any thread) after
-** sqlite3_backup_init() is called and before the corresponding call to
-** sqlite3_backup_finish(). SQLite does not currently check to see
+** tdsqlite3_backup_init() is called and before the corresponding call to
+** tdsqlite3_backup_finish(). SQLite does not currently check to see
** if the application incorrectly accesses the destination [database connection]
** and so no error code is reported, but the operations may malfunction
** nevertheless. Use of the destination database connection while a
@@ -7748,29 +9635,29 @@ typedef struct sqlite3_backup sqlite3_backup;
** is not accessed while the backup is running. In practice this means
** that the application must guarantee that the disk file being
** backed up to is not accessed by any connection within the process,
-** not just the specific connection that was passed to sqlite3_backup_init().
+** not just the specific connection that was passed to tdsqlite3_backup_init().
**
-** The [sqlite3_backup] object itself is partially threadsafe. Multiple
-** threads may safely make multiple concurrent calls to sqlite3_backup_step().
-** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount()
+** The [tdsqlite3_backup] object itself is partially threadsafe. Multiple
+** threads may safely make multiple concurrent calls to tdsqlite3_backup_step().
+** However, the tdsqlite3_backup_remaining() and tdsqlite3_backup_pagecount()
** APIs are not strictly speaking threadsafe. If they are invoked at the
-** same time as another thread is invoking sqlite3_backup_step() it is
+** same time as another thread is invoking tdsqlite3_backup_step() it is
** possible that they return invalid values.
*/
-SQLITE_API sqlite3_backup *sqlite3_backup_init(
- sqlite3 *pDest, /* Destination database handle */
+SQLITE_API tdsqlite3_backup *tdsqlite3_backup_init(
+ tdsqlite3 *pDest, /* Destination database handle */
const char *zDestName, /* Destination database name */
- sqlite3 *pSource, /* Source database handle */
+ tdsqlite3 *pSource, /* Source database handle */
const char *zSourceName /* Source database name */
);
-SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage);
-SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p);
-SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p);
-SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
+SQLITE_API int tdsqlite3_backup_step(tdsqlite3_backup *p, int nPage);
+SQLITE_API int tdsqlite3_backup_finish(tdsqlite3_backup *p);
+SQLITE_API int tdsqlite3_backup_remaining(tdsqlite3_backup *p);
+SQLITE_API int tdsqlite3_backup_pagecount(tdsqlite3_backup *p);
/*
** CAPI3REF: Unlock Notification
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^When running in shared-cache mode, a database operation may fail with
** an [SQLITE_LOCKED] error if the required locks on the shared-cache or
@@ -7791,17 +9678,17 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** identity of the database connection (the blocking connection) that
** has locked the required resource is stored internally. ^After an
** application receives an SQLITE_LOCKED error, it may call the
-** sqlite3_unlock_notify() method with the blocked connection handle as
+** tdsqlite3_unlock_notify() method with the blocked connection handle as
** the first argument to register for a callback that will be invoked
** when the blocking connections current transaction is concluded. ^The
-** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
-** call that concludes the blocking connections transaction.
+** callback is invoked from within the [tdsqlite3_step] or [tdsqlite3_close]
+** call that concludes the blocking connection's transaction.
**
-** ^(If sqlite3_unlock_notify() is called in a multi-threaded application,
+** ^(If tdsqlite3_unlock_notify() is called in a multi-threaded application,
** there is a chance that the blocking connection will have already
-** concluded its transaction by the time sqlite3_unlock_notify() is invoked.
+** concluded its transaction by the time tdsqlite3_unlock_notify() is invoked.
** If this happens, then the specified callback is invoked immediately,
-** from within the call to sqlite3_unlock_notify().)^
+** from within the call to tdsqlite3_unlock_notify().)^
**
** ^If the blocked connection is attempting to obtain a write-lock on a
** shared-cache table, and more than one other connection currently holds
@@ -7809,19 +9696,19 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** the other connections to use as the blocking connection.
**
** ^(There may be at most one unlock-notify callback registered by a
-** blocked connection. If sqlite3_unlock_notify() is called when the
+** blocked connection. If tdsqlite3_unlock_notify() is called when the
** blocked connection already has a registered unlock-notify callback,
-** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
+** then the new callback replaces the old.)^ ^If tdsqlite3_unlock_notify() is
** called with a NULL pointer as its second argument, then any existing
** unlock-notify callback is canceled. ^The blocked connections
** unlock-notify callback may also be canceled by closing the blocked
-** connection using [sqlite3_close()].
+** connection using [tdsqlite3_close()].
**
** The unlock-notify callback is not reentrant. If an application invokes
-** any sqlite3_xxx API functions from within an unlock-notify callback, a
+** any tdsqlite3_xxx API functions from within an unlock-notify callback, a
** crash or deadlock may be the result.
**
-** ^Unless deadlock is detected (see below), sqlite3_unlock_notify() always
+** ^Unless deadlock is detected (see below), tdsqlite3_unlock_notify() always
** returns SQLITE_OK.
**
** <b>Callback Invocation Details</b>
@@ -7833,7 +9720,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** an unlock-notify callback is a pointer to an array of void* pointers,
** and the second is the number of entries in the array.
**
-** When a blocking connections transaction is concluded, there may be
+** When a blocking connection's transaction is concluded, there may be
** more than one blocked connection that has registered for an unlock-notify
** callback. ^If two or more such blocked connections have specified the
** same callback function, then instead of invoking the callback function
@@ -7852,8 +9739,8 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** Y is waiting on connection X's transaction, then neither connection
** will proceed and the system may remain deadlocked indefinitely.
**
-** To avoid this scenario, the sqlite3_unlock_notify() performs deadlock
-** detection. ^If a given call to sqlite3_unlock_notify() would put the
+** To avoid this scenario, the tdsqlite3_unlock_notify() performs deadlock
+** detection. ^If a given call to tdsqlite3_unlock_notify() would put the
** system in a deadlocked state, then SQLITE_LOCKED is returned and no
** unlock-notify callback is registered. The system is said to be in
** a deadlocked state if connection A has registered for an unlock-notify
@@ -7867,24 +9754,24 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
**
** <b>The "DROP TABLE" Exception</b>
**
-** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost
-** always appropriate to call sqlite3_unlock_notify(). There is however,
+** When a call to [tdsqlite3_step()] returns SQLITE_LOCKED, it is almost
+** always appropriate to call tdsqlite3_unlock_notify(). There is however,
** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement,
** SQLite checks if there are any currently executing SELECT statements
** that belong to the same connection. If there are, SQLITE_LOCKED is
** returned. In this case there is no "blocking connection", so invoking
-** sqlite3_unlock_notify() results in the unlock-notify callback being
+** tdsqlite3_unlock_notify() results in the unlock-notify callback being
** invoked immediately. If the application then re-attempts the "DROP TABLE"
** or "DROP INDEX" query, an infinite loop might be the result.
**
** One way around this problem is to check the extended error code returned
-** by an sqlite3_step() call. ^(If there is a blocking connection, then the
+** by an tdsqlite3_step() call. ^(If there is a blocking connection, then the
** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in
** the special "DROP TABLE/INDEX" case, the extended error code is just
** SQLITE_LOCKED.)^
*/
-SQLITE_API int sqlite3_unlock_notify(
- sqlite3 *pBlocked, /* Waiting connection */
+SQLITE_API int tdsqlite3_unlock_notify(
+ tdsqlite3 *pBlocked, /* Waiting connection */
void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */
void *pNotifyArg /* Argument to pass to xNotify */
);
@@ -7893,82 +9780,82 @@ SQLITE_API int sqlite3_unlock_notify(
/*
** CAPI3REF: String Comparison
**
-** ^The [sqlite3_stricmp()] and [sqlite3_strnicmp()] APIs allow applications
+** ^The [tdsqlite3_stricmp()] and [tdsqlite3_strnicmp()] APIs allow applications
** and extensions to compare the contents of two buffers containing UTF-8
** strings in a case-independent fashion, using the same definition of "case
** independence" that SQLite uses internally when comparing identifiers.
*/
-SQLITE_API int sqlite3_stricmp(const char *, const char *);
-SQLITE_API int sqlite3_strnicmp(const char *, const char *, int);
+SQLITE_API int tdsqlite3_stricmp(const char *, const char *);
+SQLITE_API int tdsqlite3_strnicmp(const char *, const char *, int);
/*
** CAPI3REF: String Globbing
*
-** ^The [sqlite3_strglob(P,X)] interface returns zero if and only if
+** ^The [tdsqlite3_strglob(P,X)] interface returns zero if and only if
** string X matches the [GLOB] pattern P.
** ^The definition of [GLOB] pattern matching used in
-** [sqlite3_strglob(P,X)] is the same as for the "X GLOB P" operator in the
-** SQL dialect understood by SQLite. ^The [sqlite3_strglob(P,X)] function
+** [tdsqlite3_strglob(P,X)] is the same as for the "X GLOB P" operator in the
+** SQL dialect understood by SQLite. ^The [tdsqlite3_strglob(P,X)] function
** is case sensitive.
**
** Note that this routine returns zero on a match and non-zero if the strings
-** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()].
+** do not match, the same as [tdsqlite3_stricmp()] and [tdsqlite3_strnicmp()].
**
-** See also: [sqlite3_strlike()].
+** See also: [tdsqlite3_strlike()].
*/
-SQLITE_API int sqlite3_strglob(const char *zGlob, const char *zStr);
+SQLITE_API int tdsqlite3_strglob(const char *zGlob, const char *zStr);
/*
** CAPI3REF: String LIKE Matching
*
-** ^The [sqlite3_strlike(P,X,E)] interface returns zero if and only if
+** ^The [tdsqlite3_strlike(P,X,E)] interface returns zero if and only if
** string X matches the [LIKE] pattern P with escape character E.
** ^The definition of [LIKE] pattern matching used in
-** [sqlite3_strlike(P,X,E)] is the same as for the "X LIKE P ESCAPE E"
+** [tdsqlite3_strlike(P,X,E)] is the same as for the "X LIKE P ESCAPE E"
** operator in the SQL dialect understood by SQLite. ^For "X LIKE P" without
-** the ESCAPE clause, set the E parameter of [sqlite3_strlike(P,X,E)] to 0.
-** ^As with the LIKE operator, the [sqlite3_strlike(P,X,E)] function is case
+** the ESCAPE clause, set the E parameter of [tdsqlite3_strlike(P,X,E)] to 0.
+** ^As with the LIKE operator, the [tdsqlite3_strlike(P,X,E)] function is case
** insensitive - equivalent upper and lower case ASCII characters match
** one another.
**
-** ^The [sqlite3_strlike(P,X,E)] function matches Unicode characters, though
+** ^The [tdsqlite3_strlike(P,X,E)] function matches Unicode characters, though
** only ASCII characters are case folded.
**
** Note that this routine returns zero on a match and non-zero if the strings
-** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()].
+** do not match, the same as [tdsqlite3_stricmp()] and [tdsqlite3_strnicmp()].
**
-** See also: [sqlite3_strglob()].
+** See also: [tdsqlite3_strglob()].
*/
-SQLITE_API int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc);
+SQLITE_API int tdsqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc);
/*
** CAPI3REF: Error Logging Interface
**
-** ^The [sqlite3_log()] interface writes a message into the [error log]
-** established by the [SQLITE_CONFIG_LOG] option to [sqlite3_config()].
+** ^The [tdsqlite3_log()] interface writes a message into the [error log]
+** established by the [SQLITE_CONFIG_LOG] option to [tdsqlite3_config()].
** ^If logging is enabled, the zFormat string and subsequent arguments are
-** used with [sqlite3_snprintf()] to generate the final output string.
+** used with [tdsqlite3_snprintf()] to generate the final output string.
**
-** The sqlite3_log() interface is intended for use by extensions such as
+** The tdsqlite3_log() interface is intended for use by extensions such as
** virtual tables, collating functions, and SQL functions. While there is
-** nothing to prevent an application from calling sqlite3_log(), doing so
+** nothing to prevent an application from calling tdsqlite3_log(), doing so
** is considered bad form.
**
** The zFormat string must not be NULL.
**
-** To avoid deadlocks and other threading problems, the sqlite3_log() routine
+** To avoid deadlocks and other threading problems, the tdsqlite3_log() routine
** will not use dynamically allocated memory. The log message is stored in
** a fixed-length buffer on the stack. If the log message is longer than
** a few hundred characters, it will be truncated to the length of the
** buffer.
*/
-SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
+SQLITE_API void tdsqlite3_log(int iErrCode, const char *zFormat, ...);
/*
** CAPI3REF: Write-Ahead Log Commit Hook
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The [sqlite3_wal_hook()] function is used to register a callback that
+** ^The [tdsqlite3_wal_hook()] function is used to register a callback that
** is invoked each time data is committed to a database in wal mode.
**
** ^(The callback is invoked by SQLite after the commit has taken place and
@@ -7976,7 +9863,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** may read, write or [checkpoint] the database as required.
**
** ^The first parameter passed to the callback function when it is invoked
-** is a copy of the third parameter passed to sqlite3_wal_hook() when
+** is a copy of the third parameter passed to tdsqlite3_wal_hook() when
** registering the callback. ^The second is a copy of the database handle.
** ^The third parameter is the name of the database that was written to -
** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter
@@ -7992,24 +9879,24 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** are undefined.
**
** A single database handle may have at most a single write-ahead log callback
-** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any
+** registered at one time. ^Calling [tdsqlite3_wal_hook()] replaces any
** previously registered write-ahead log callback. ^Note that the
-** [sqlite3_wal_autocheckpoint()] interface and the
-** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will
-** overwrite any prior [sqlite3_wal_hook()] settings.
+** [tdsqlite3_wal_autocheckpoint()] interface and the
+** [wal_autocheckpoint pragma] both invoke [tdsqlite3_wal_hook()] and will
+** overwrite any prior [tdsqlite3_wal_hook()] settings.
*/
-SQLITE_API void *sqlite3_wal_hook(
- sqlite3*,
- int(*)(void *,sqlite3*,const char*,int),
+SQLITE_API void *tdsqlite3_wal_hook(
+ tdsqlite3*,
+ int(*)(void *,tdsqlite3*,const char*,int),
void*
);
/*
** CAPI3REF: Configure an auto-checkpoint
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around
-** [sqlite3_wal_hook()] that causes any database on [database connection] D
+** ^The [tdsqlite3_wal_autocheckpoint(D,N)] is a wrapper around
+** [tdsqlite3_wal_hook()] that causes any database on [database connection] D
** to automatically [checkpoint]
** after committing a transaction if there are N or
** more frames in the [write-ahead log] file. ^Passing zero or
@@ -8017,15 +9904,15 @@ SQLITE_API void *sqlite3_wal_hook(
** checkpoints entirely.
**
** ^The callback registered by this function replaces any existing callback
-** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback
-** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism
+** registered using [tdsqlite3_wal_hook()]. ^Likewise, registering a callback
+** using [tdsqlite3_wal_hook()] disables the automatic checkpoint mechanism
** configured by this function.
**
** ^The [wal_autocheckpoint pragma] can be used to invoke this interface
** from SQL.
**
** ^Checkpoints initiated by this mechanism are
-** [sqlite3_wal_checkpoint_v2|PASSIVE].
+** [tdsqlite3_wal_checkpoint_v2|PASSIVE].
**
** ^Every new [database connection] defaults to having the auto-checkpoint
** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT]
@@ -8033,35 +9920,35 @@ SQLITE_API void *sqlite3_wal_hook(
** is only necessary if the default setting is found to be suboptimal
** for a particular application.
*/
-SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
+SQLITE_API int tdsqlite3_wal_autocheckpoint(tdsqlite3 *db, int N);
/*
** CAPI3REF: Checkpoint a database
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to
-** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^
+** ^(The tdsqlite3_wal_checkpoint(D,X) is equivalent to
+** [tdsqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^
**
-** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the
+** In brief, tdsqlite3_wal_checkpoint(D,X) causes the content in the
** [write-ahead log] for database X on [database connection] D to be
** transferred into the database file and for the write-ahead log to
** be reset. See the [checkpointing] documentation for addition
** information.
**
** This interface used to be the only way to cause a checkpoint to
-** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()]
+** occur. But then the newer and more powerful [tdsqlite3_wal_checkpoint_v2()]
** interface was added. This interface is retained for backwards
** compatibility and as a convenience for applications that need to manually
** start a callback but which do not need the full power (and corresponding
-** complication) of [sqlite3_wal_checkpoint_v2()].
+** complication) of [tdsqlite3_wal_checkpoint_v2()].
*/
-SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
+SQLITE_API int tdsqlite3_wal_checkpoint(tdsqlite3 *db, const char *zDb);
/*
** CAPI3REF: Checkpoint a database
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint
+** ^(The tdsqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint
** operation on database X of [database connection] D in mode M. Status
** information is written back into integers pointed to by L and C.)^
** ^(The M parameter must be a valid [checkpoint mode]:)^
@@ -8077,7 +9964,7 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
**
** <dt>SQLITE_CHECKPOINT_FULL<dd>
** ^This mode blocks (it invokes the
-** [sqlite3_busy_handler|busy-handler callback]) until there is no
+** [tdsqlite3_busy_handler|busy-handler callback]) until there is no
** database writer and all readers are reading from the most recent database
** snapshot. ^It then checkpoints all frames in the log file and syncs the
** database file. ^This mode blocks new database writers while it is pending,
@@ -8142,15 +10029,15 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
** attached database, SQLITE_ERROR is returned to the caller.
**
** ^Unless it returns SQLITE_MISUSE,
-** the sqlite3_wal_checkpoint_v2() interface
+** the tdsqlite3_wal_checkpoint_v2() interface
** sets the error information that is queried by
-** [sqlite3_errcode()] and [sqlite3_errmsg()].
+** [tdsqlite3_errcode()] and [tdsqlite3_errmsg()].
**
** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface
** from SQL.
*/
-SQLITE_API int sqlite3_wal_checkpoint_v2(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_wal_checkpoint_v2(
+ tdsqlite3 *db, /* Database handle */
const char *zDb, /* Name of attached database (or NULL) */
int eMode, /* SQLITE_CHECKPOINT_* value */
int *pnLog, /* OUT: Size of WAL log in frames */
@@ -8162,8 +10049,8 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
** KEYWORDS: {checkpoint mode}
**
** These constants define all valid values for the "checkpoint mode" passed
-** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface.
-** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the
+** as the third parameter to the [tdsqlite3_wal_checkpoint_v2()] interface.
+** See the [tdsqlite3_wal_checkpoint_v2()] documentation for details on the
** meaning of each of these checkpoint modes.
*/
#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
@@ -8181,25 +10068,32 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
** If this interface is invoked outside the context of an xConnect or
** xCreate virtual table method then the behavior is undefined.
**
-** At present, there is only one option that may be configured using
-** this function. (See [SQLITE_VTAB_CONSTRAINT_SUPPORT].) Further options
-** may be added in the future.
+** In the call tdsqlite3_vtab_config(D,C,...) the D parameter is the
+** [database connection] in which the virtual table is being created and
+** which is passed in as the first argument to the [xConnect] or [xCreate]
+** method that is invoking tdsqlite3_vtab_config(). The C parameter is one
+** of the [virtual table configuration options]. The presence and meaning
+** of parameters after C depend on which [virtual table configuration option]
+** is used.
*/
-SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
+SQLITE_API int tdsqlite3_vtab_config(tdsqlite3*, int op, ...);
/*
** CAPI3REF: Virtual Table Configuration Options
+** KEYWORDS: {virtual table configuration options}
+** KEYWORDS: {virtual table configuration option}
**
** These macros define the various options to the
-** [sqlite3_vtab_config()] interface that [virtual table] implementations
+** [tdsqlite3_vtab_config()] interface that [virtual table] implementations
** can use to customize and optimize their behavior.
**
** <dl>
-** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT
+** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]]
+** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT</dt>
** <dd>Calls of the form
-** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported,
+** [tdsqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported,
** where X is an integer. If X is zero, then the [virtual table] whose
-** [xCreate] or [xConnect] method invoked [sqlite3_vtab_config()] does not
+** [xCreate] or [xConnect] method invoked [tdsqlite3_vtab_config()] does not
** support constraints. In this configuration (which is the default) if
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
@@ -8218,15 +10112,37 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
**
** Virtual table implementations that are required to handle OR REPLACE
** must do so within the [xUpdate] method. If a call to the
-** [sqlite3_vtab_on_conflict()] function indicates that the current ON
+** [tdsqlite3_vtab_on_conflict()] function indicates that the current ON
** CONFLICT policy is REPLACE, the virtual table implementation should
** silently replace the appropriate rows within the xUpdate callback and
** return SQLITE_OK. Or, if this is not possible, it may return
** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT
** constraint handling.
+** </dd>
+**
+** [[SQLITE_VTAB_DIRECTONLY]]<dt>SQLITE_VTAB_DIRECTONLY</dt>
+** <dd>Calls of the form
+** [tdsqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the
+** the [xConnect] or [xCreate] methods of a [virtual table] implmentation
+** prohibits that virtual table from being used from within triggers and
+** views.
+** </dd>
+**
+** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
+** <dd>Calls of the form
+** [tdsqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
+** the [xConnect] or [xCreate] methods of a [virtual table] implmentation
+** identify that virtual table as being safe to use from within triggers
+** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
+** virtual table can do no serious harm even if it is controlled by a
+** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS
+** flag unless absolutely necessary.
+** </dd>
** </dl>
*/
#define SQLITE_VTAB_CONSTRAINT_SUPPORT 1
+#define SQLITE_VTAB_INNOCUOUS 2
+#define SQLITE_VTAB_DIRECTONLY 3
/*
** CAPI3REF: Determine The Virtual Table Conflict Policy
@@ -8238,22 +10154,56 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
** of the SQL statement that triggered the call to the [xUpdate] method of the
** [virtual table].
*/
-SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
+SQLITE_API int tdsqlite3_vtab_on_conflict(tdsqlite3 *);
+
+/*
+** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE
+**
+** If the tdsqlite3_vtab_nochange(X) routine is called within the [xColumn]
+** method of a [virtual table], then it returns true if and only if the
+** column is being fetched as part of an UPDATE operation during which the
+** column value will not change. Applications might use this to substitute
+** a return value that is less expensive to compute and that the corresponding
+** [xUpdate] method understands as a "no-change" value.
+**
+** If the [xColumn] method calls tdsqlite3_vtab_nochange() and finds that
+** the column is not changed by the UPDATE statement, then the xColumn
+** method can optionally return without setting a result, without calling
+** any of the [tdsqlite3_result_int|tdsqlite3_result_xxxxx() interfaces].
+** In that case, [tdsqlite3_value_nochange(X)] will return true for the
+** same column in the [xUpdate] method.
+*/
+SQLITE_API int tdsqlite3_vtab_nochange(tdsqlite3_context*);
+
+/*
+** CAPI3REF: Determine The Collation For a Virtual Table Constraint
+**
+** This function may only be called from within a call to the [xBestIndex]
+** method of a [virtual table].
+**
+** The first argument must be the tdsqlite3_index_info object that is the
+** first parameter to the xBestIndex() method. The second argument must be
+** an index into the aConstraint[] array belonging to the tdsqlite3_index_info
+** structure passed to xBestIndex. This function returns a pointer to a buffer
+** containing the name of the collation sequence for the corresponding
+** constraint.
+*/
+SQLITE_API SQLITE_EXPERIMENTAL const char *tdsqlite3_vtab_collation(tdsqlite3_index_info*,int);
/*
** CAPI3REF: Conflict resolution modes
** KEYWORDS: {conflict resolution mode}
**
-** These constants are returned by [sqlite3_vtab_on_conflict()] to
+** These constants are returned by [tdsqlite3_vtab_on_conflict()] to
** inform a [virtual table] implementation what the [ON CONFLICT] mode
** is for the SQL statement being evaluated.
**
** Note that the [SQLITE_IGNORE] constant is also used as a potential
-** return value from the [sqlite3_set_authorizer()] callback and that
+** return value from the [tdsqlite3_set_authorizer()] callback and that
** [SQLITE_ABORT] is also a [result code].
*/
#define SQLITE_ROLLBACK 1
-/* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */
+/* #define SQLITE_IGNORE 2 // Also used by tdsqlite3_authorizer() callback */
#define SQLITE_FAIL 3
/* #define SQLITE_ABORT 4 // Also an error code */
#define SQLITE_REPLACE 5
@@ -8263,8 +10213,8 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** KEYWORDS: {scanstatus options}
**
** The following constants can be used for the T parameter to the
-** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a
-** different metric for sqlite3_stmt_scanstatus() to return.
+** [tdsqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a
+** different metric for tdsqlite3_stmt_scanstatus() to return.
**
** When the value returned to V is a string, space to hold that string is
** managed by the prepared statement S and will be automatically freed when
@@ -8272,15 +10222,15 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
**
** <dl>
** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt>
-** <dd>^The [sqlite3_int64] variable pointed to by the T parameter will be
+** <dd>^The [tdsqlite3_int64] variable pointed to by the V parameter will be
** set to the total number of times that the X-th loop has run.</dd>
**
** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt>
-** <dd>^The [sqlite3_int64] variable pointed to by the T parameter will be set
+** <dd>^The [tdsqlite3_int64] variable pointed to by the V parameter will be set
** to the total number of rows examined by all iterations of the X-th loop.</dd>
**
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
-** <dd>^The "double" variable pointed to by the T parameter will be set to the
+** <dd>^The "double" variable pointed to by the V parameter will be set to the
** query planner's estimate for the average number of rows output from each
** iteration of the X-th loop. If the query planner's estimates was accurate,
** then this value will approximate the quotient NVISIT/NLOOP and the
@@ -8288,17 +10238,17 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** be the NLOOP value for the current loop.
**
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
-** <dd>^The "const char *" variable pointed to by the T parameter will be set
+** <dd>^The "const char *" variable pointed to by the V parameter will be set
** to a zero-terminated UTF-8 string containing the name of the index or table
** used for the X-th loop.
**
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
-** <dd>^The "const char *" variable pointed to by the T parameter will be set
+** <dd>^The "const char *" variable pointed to by the V parameter will be set
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
** description for the X-th loop.
**
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECT</dt>
-** <dd>^The "int" variable pointed to by the T parameter will be set to the
+** <dd>^The "int" variable pointed to by the V parameter will be set to the
** "select-id" for the X-th loop. The select-id identifies which query or
** subquery the loop is part of. The main query has a select-id of zero.
** The select-id is the same value as is output in the first column
@@ -8314,7 +10264,7 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
/*
** CAPI3REF: Prepared Statement Scan Status
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** This interface returns information about the predicted and measured
** performance for pStmt. Advanced applications can use this
@@ -8341,10 +10291,10 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** as if the loop did not exist - it returns non-zero and leave the variable
** that pOut points to unchanged.
**
-** See also: [sqlite3_stmt_scanstatus_reset()]
+** See also: [tdsqlite3_stmt_scanstatus_reset()]
*/
-SQLITE_API int sqlite3_stmt_scanstatus(
- sqlite3_stmt *pStmt, /* Prepared statement for which info desired */
+SQLITE_API int tdsqlite3_stmt_scanstatus(
+ tdsqlite3_stmt *pStmt, /* Prepared statement for which info desired */
int idx, /* Index of loop to report on */
int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */
void *pOut /* Result written here */
@@ -8352,24 +10302,24 @@ SQLITE_API int sqlite3_stmt_scanstatus(
/*
** CAPI3REF: Zero Scan-Status Counters
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^Zero all [sqlite3_stmt_scanstatus()] related event counters.
+** ^Zero all [tdsqlite3_stmt_scanstatus()] related event counters.
**
** This API is only available if the library is built with pre-processor
** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined.
*/
-SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
+SQLITE_API void tdsqlite3_stmt_scanstatus_reset(tdsqlite3_stmt*);
/*
** CAPI3REF: Flush caches to disk mid-transaction
**
** ^If a write-transaction is open on [database connection] D when the
-** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
+** [tdsqlite3_db_cacheflush(D)] interface invoked, any dirty
** pages in the pager-cache that are not currently in use are written out
** to disk. A dirty page may be in use if a database cursor created by an
** active SQL statement is reading from it, or if it is page 1 of a database
-** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)]
+** file (page 1 is always "in use"). ^The [tdsqlite3_db_cacheflush(D)]
** interface flushes caches for all schemas - "main", "temp", and
** any [attached] databases.
**
@@ -8386,12 +10336,12 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
** example an IO error or out-of-memory condition), then processing is
** abandoned and an SQLite [error code] is returned to the caller immediately.
**
-** ^Otherwise, if no error occurs, [sqlite3_db_cacheflush()] returns SQLITE_OK.
+** ^Otherwise, if no error occurs, [tdsqlite3_db_cacheflush()] returns SQLITE_OK.
**
** ^This function does not set the database handle error code or message
-** returned by the [sqlite3_errcode()] and [sqlite3_errmsg()] functions.
+** returned by the [tdsqlite3_errcode()] and [tdsqlite3_errmsg()] functions.
*/
-SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
+SQLITE_API int tdsqlite3_db_cacheflush(tdsqlite3*);
/*
** CAPI3REF: The pre-update hook.
@@ -8399,20 +10349,20 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** ^These interfaces are only available if SQLite is compiled using the
** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option.
**
-** ^The [sqlite3_preupdate_hook()] interface registers a callback function
+** ^The [tdsqlite3_preupdate_hook()] interface registers a callback function
** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation
-** on a [rowid table].
+** on a database table.
** ^At most one preupdate hook may be registered at a time on a single
-** [database connection]; each call to [sqlite3_preupdate_hook()] overrides
+** [database connection]; each call to [tdsqlite3_preupdate_hook()] overrides
** the previous setting.
-** ^The preupdate hook is disabled by invoking [sqlite3_preupdate_hook()]
+** ^The preupdate hook is disabled by invoking [tdsqlite3_preupdate_hook()]
** with a NULL pointer as the second parameter.
-** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as
+** ^The third parameter to [tdsqlite3_preupdate_hook()] is passed through as
** the first parameter to callbacks.
**
-** ^The preupdate hook only fires for changes to [rowid tables]; the preupdate
-** hook is not invoked for changes to [virtual tables] or [WITHOUT ROWID]
-** tables.
+** ^The preupdate hook only fires for changes to real database tables; the
+** preupdate hook is not invoked for changes to [virtual tables] or to
+** system tables like sqlite_master or sqlite_stat1.
**
** ^The second parameter to the preupdate callback is a pointer to
** the [database connection] that registered the preupdate hook.
@@ -8426,15 +10376,19 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** databases.)^
** ^The fifth parameter to the preupdate callback is the name of the
** table that is being modified.
-** ^The sixth parameter to the preupdate callback is the initial [rowid] of the
-** row being changes for SQLITE_UPDATE and SQLITE_DELETE changes and is
-** undefined for SQLITE_INSERT changes.
-** ^The seventh parameter to the preupdate callback is the final [rowid] of
-** the row being changed for SQLITE_UPDATE and SQLITE_INSERT changes and is
-** undefined for SQLITE_DELETE changes.
-**
-** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()],
-** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces
+**
+** For an UPDATE or DELETE operation on a [rowid table], the sixth
+** parameter passed to the preupdate callback is the initial [rowid] of the
+** row being modified or deleted. For an INSERT operation on a rowid table,
+** or any operation on a WITHOUT ROWID table, the value of the sixth
+** parameter is undefined. For an INSERT or UPDATE on a rowid table the
+** seventh parameter is the final rowid value of the row being inserted
+** or updated. The value of the seventh parameter passed to the callback
+** function is not defined for operations on WITHOUT ROWID tables, or for
+** INSERT operations on rowid tables.
+**
+** The [tdsqlite3_preupdate_old()], [tdsqlite3_preupdate_new()],
+** [tdsqlite3_preupdate_count()], and [tdsqlite3_preupdate_depth()] interfaces
** provide additional information about a preupdate event. These routines
** may only be called from within a preupdate callback. Invoking any of
** these routines from outside of a preupdate callback or with a
@@ -8442,52 +10396,54 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** to the preupdate callback results in undefined and probably undesirable
** behavior.
**
-** ^The [sqlite3_preupdate_count(D)] interface returns the number of columns
+** ^The [tdsqlite3_preupdate_count(D)] interface returns the number of columns
** in the row that is being inserted, updated, or deleted.
**
-** ^The [sqlite3_preupdate_old(D,N,P)] interface writes into P a pointer to
-** a [protected sqlite3_value] that contains the value of the Nth column of
+** ^The [tdsqlite3_preupdate_old(D,N,P)] interface writes into P a pointer to
+** a [protected tdsqlite3_value] that contains the value of the Nth column of
** the table row before it is updated. The N parameter must be between 0
** and one less than the number of columns or the behavior will be
** undefined. This must only be used within SQLITE_UPDATE and SQLITE_DELETE
** preupdate callbacks; if it is used by an SQLITE_INSERT callback then the
-** behavior is undefined. The [sqlite3_value] that P points to
+** behavior is undefined. The [tdsqlite3_value] that P points to
** will be destroyed when the preupdate callback returns.
**
-** ^The [sqlite3_preupdate_new(D,N,P)] interface writes into P a pointer to
-** a [protected sqlite3_value] that contains the value of the Nth column of
+** ^The [tdsqlite3_preupdate_new(D,N,P)] interface writes into P a pointer to
+** a [protected tdsqlite3_value] that contains the value of the Nth column of
** the table row after it is updated. The N parameter must be between 0
** and one less than the number of columns or the behavior will be
** undefined. This must only be used within SQLITE_INSERT and SQLITE_UPDATE
** preupdate callbacks; if it is used by an SQLITE_DELETE callback then the
-** behavior is undefined. The [sqlite3_value] that P points to
+** behavior is undefined. The [tdsqlite3_value] that P points to
** will be destroyed when the preupdate callback returns.
**
-** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate
+** ^The [tdsqlite3_preupdate_depth(D)] interface returns 0 if the preupdate
** callback was invoked as a result of a direct insert, update, or delete
** operation; or 1 for inserts, updates, or deletes invoked by top-level
** triggers; or 2 for changes resulting from triggers called by top-level
** triggers; and so forth.
**
-** See also: [sqlite3_update_hook()]
+** See also: [tdsqlite3_update_hook()]
*/
-SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook(
- sqlite3 *db,
+#if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+SQLITE_API void *tdsqlite3_preupdate_hook(
+ tdsqlite3 *db,
void(*xPreUpdate)(
void *pCtx, /* Copy of third arg to preupdate_hook() */
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
int op, /* SQLITE_UPDATE, DELETE or INSERT */
char const *zDb, /* Database name */
char const *zName, /* Table name */
- sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */
- sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */
+ tdsqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */
+ tdsqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */
),
void*
);
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *);
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_depth(sqlite3 *);
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **);
+SQLITE_API int tdsqlite3_preupdate_old(tdsqlite3 *, int, tdsqlite3_value **);
+SQLITE_API int tdsqlite3_preupdate_count(tdsqlite3 *);
+SQLITE_API int tdsqlite3_preupdate_depth(tdsqlite3 *);
+SQLITE_API int tdsqlite3_preupdate_new(tdsqlite3 *, int, tdsqlite3_value **);
+#endif
/*
** CAPI3REF: Low-level system error code
@@ -8495,16 +10451,15 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3
** ^Attempt to return the underlying operating system error code or error
** number that caused the most recent I/O error or failure to open a file.
** The return value is OS-dependent. For example, on unix systems, after
-** [sqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be
+** [tdsqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be
** called to get back the underlying "errno" that caused the problem, such
** as ENOSPC, EAUTH, EISDIR, and so forth.
*/
-SQLITE_API int sqlite3_system_errno(sqlite3*);
+SQLITE_API int tdsqlite3_system_errno(tdsqlite3*);
/*
** CAPI3REF: Database Snapshot
-** KEYWORDS: {snapshot}
-** EXPERIMENTAL
+** KEYWORDS: {snapshot} {tdsqlite3_snapshot}
**
** An instance of the snapshot object records the state of a [WAL mode]
** database for some specific point in history.
@@ -8517,65 +10472,96 @@ SQLITE_API int sqlite3_system_errno(sqlite3*);
** Subsequent changes to the database from other connections are not seen
** by the reader until a new read transaction is started.
**
-** The sqlite3_snapshot object records state information about an historical
+** The tdsqlite3_snapshot object records state information about an historical
** version of the database file so that it is possible to later open a new read
** transaction that sees that historical version of the database rather than
** the most recent version.
-**
-** The constructor for this object is [sqlite3_snapshot_get()]. The
-** [sqlite3_snapshot_open()] method causes a fresh read transaction to refer
-** to an historical snapshot (if possible). The destructor for
-** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
*/
-typedef struct sqlite3_snapshot sqlite3_snapshot;
+typedef struct tdsqlite3_snapshot {
+ unsigned char hidden[48];
+} tdsqlite3_snapshot;
/*
** CAPI3REF: Record A Database Snapshot
-** EXPERIMENTAL
+** CONSTRUCTOR: tdsqlite3_snapshot
**
-** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
-** new [sqlite3_snapshot] object that records the current state of
+** ^The [tdsqlite3_snapshot_get(D,S,P)] interface attempts to make a
+** new [tdsqlite3_snapshot] object that records the current state of
** schema S in database connection D. ^On success, the
-** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
-** created [sqlite3_snapshot] object into *P and returns SQLITE_OK.
-** ^If schema S of [database connection] D is not a [WAL mode] database
-** that is in a read transaction, then [sqlite3_snapshot_get(D,S,P)]
-** leaves the *P value unchanged and returns an appropriate [error code].
-**
-** The [sqlite3_snapshot] object returned from a successful call to
-** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
+** [tdsqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
+** created [tdsqlite3_snapshot] object into *P and returns SQLITE_OK.
+** If there is not already a read-transaction open on schema S when
+** this function is called, one is opened automatically.
+**
+** The following must be true for this function to succeed. If any of
+** the following statements are false when tdsqlite3_snapshot_get() is
+** called, SQLITE_ERROR is returned. The final value of *P is undefined
+** in this case.
+**
+** <ul>
+** <li> The database handle must not be in [autocommit mode].
+**
+** <li> Schema S of [database connection] D must be a [WAL mode] database.
+**
+** <li> There must not be a write transaction open on schema S of database
+** connection D.
+**
+** <li> One or more transactions must have been written to the current wal
+** file since it was created on disk (by any connection). This means
+** that a snapshot cannot be taken on a wal mode database with no wal
+** file immediately after it is first opened. At least one transaction
+** must be written to it first.
+** </ul>
+**
+** This function may also return SQLITE_NOMEM. If it is called with the
+** database handle in autocommit mode but fails for some other reason,
+** whether or not a read transaction is opened on schema S is undefined.
+**
+** The [tdsqlite3_snapshot] object returned from a successful call to
+** [tdsqlite3_snapshot_get()] must be freed using [tdsqlite3_snapshot_free()]
** to avoid a memory leak.
**
-** The [sqlite3_snapshot_get()] interface is only available when the
-** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
+** The [tdsqlite3_snapshot_get()] interface is only available when the
+** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
- sqlite3 *db,
+SQLITE_API SQLITE_EXPERIMENTAL int tdsqlite3_snapshot_get(
+ tdsqlite3 *db,
const char *zSchema,
- sqlite3_snapshot **ppSnapshot
+ tdsqlite3_snapshot **ppSnapshot
);
/*
** CAPI3REF: Start a read transaction on an historical snapshot
-** EXPERIMENTAL
-**
-** ^The [sqlite3_snapshot_open(D,S,P)] interface starts a
-** read transaction for schema S of
-** [database connection] D such that the read transaction
-** refers to historical [snapshot] P, rather than the most
-** recent change to the database.
-** ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK on success
-** or an appropriate [error code] if it fails.
-**
-** ^In order to succeed, a call to [sqlite3_snapshot_open(D,S,P)] must be
-** the first operation following the [BEGIN] that takes the schema S
-** out of [autocommit mode].
-** ^In other words, schema S must not currently be in
-** a transaction for [sqlite3_snapshot_open(D,S,P)] to work, but the
-** database connection D must be out of [autocommit mode].
-** ^A [snapshot] will fail to open if it has been overwritten by a
-** [checkpoint].
-** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the
+** METHOD: tdsqlite3_snapshot
+**
+** ^The [tdsqlite3_snapshot_open(D,S,P)] interface either starts a new read
+** transaction or upgrades an existing one for schema S of
+** [database connection] D such that the read transaction refers to
+** historical [snapshot] P, rather than the most recent change to the
+** database. ^The [tdsqlite3_snapshot_open()] interface returns SQLITE_OK
+** on success or an appropriate [error code] if it fails.
+**
+** ^In order to succeed, the database connection must not be in
+** [autocommit mode] when [tdsqlite3_snapshot_open(D,S,P)] is called. If there
+** is already a read transaction open on schema S, then the database handle
+** must have no active statements (SELECT statements that have been passed
+** to tdsqlite3_step() but not tdsqlite3_reset() or tdsqlite3_finalize()).
+** SQLITE_ERROR is returned if either of these conditions is violated, or
+** if schema S does not exist, or if the snapshot object is invalid.
+**
+** ^A call to tdsqlite3_snapshot_open() will fail to open if the specified
+** snapshot has been overwritten by a [checkpoint]. In this case
+** SQLITE_ERROR_SNAPSHOT is returned.
+**
+** If there is already a read transaction open when this function is
+** invoked, then the same read transaction remains open (on the same
+** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT
+** is returned. If another error code - for example SQLITE_PROTOCOL or an
+** SQLITE_IOERR error code - is returned, then the final state of the
+** read transaction is undefined. If SQLITE_OK is returned, then the
+** read transaction is now open on database snapshot P.
+**
+** ^(A call to [tdsqlite3_snapshot_open(D,S,P)] will fail if the
** database connection D does not know that the database file for
** schema S is in [WAL mode]. A database connection might not know
** that the database file is in [WAL mode] if there has been no prior
@@ -8584,40 +10570,40 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
** (Hint: Run "[PRAGMA application_id]" against a newly opened
** database connection in order to make it ready to use snapshots.)
**
-** The [sqlite3_snapshot_open()] interface is only available when the
-** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
+** The [tdsqlite3_snapshot_open()] interface is only available when the
+** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
- sqlite3 *db,
+SQLITE_API SQLITE_EXPERIMENTAL int tdsqlite3_snapshot_open(
+ tdsqlite3 *db,
const char *zSchema,
- sqlite3_snapshot *pSnapshot
+ tdsqlite3_snapshot *pSnapshot
);
/*
** CAPI3REF: Destroy a snapshot
-** EXPERIMENTAL
+** DESTRUCTOR: tdsqlite3_snapshot
**
-** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P.
-** The application must eventually free every [sqlite3_snapshot] object
+** ^The [tdsqlite3_snapshot_free(P)] interface destroys [tdsqlite3_snapshot] P.
+** The application must eventually free every [tdsqlite3_snapshot] object
** using this routine to avoid a memory leak.
**
-** The [sqlite3_snapshot_free()] interface is only available when the
-** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
+** The [tdsqlite3_snapshot_free()] interface is only available when the
+** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
+SQLITE_API SQLITE_EXPERIMENTAL void tdsqlite3_snapshot_free(tdsqlite3_snapshot*);
/*
** CAPI3REF: Compare the ages of two snapshot handles.
-** EXPERIMENTAL
+** METHOD: tdsqlite3_snapshot
**
-** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages
+** The tdsqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages
** of two valid snapshot handles.
**
** If the two snapshot handles are not associated with the same database
** file, the result of the comparison is undefined.
**
** Additionally, the result of the comparison is only valid if both of the
-** snapshot handles were obtained by calling sqlite3_snapshot_get() since the
+** snapshot handles were obtained by calling tdsqlite3_snapshot_get() since the
** last time the wal file was deleted. The wal file is deleted when the
** database is changed back to rollback mode or when the number of database
** clients drops to zero. If either snapshot handle was obtained before the
@@ -8627,13 +10613,163 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
** Otherwise, this API returns a negative value if P1 refers to an older
** snapshot than P2, zero if the two handles refer to the same database
** snapshot, and a positive value if P1 is a newer snapshot than P2.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_SNAPSHOT] option.
+*/
+SQLITE_API SQLITE_EXPERIMENTAL int tdsqlite3_snapshot_cmp(
+ tdsqlite3_snapshot *p1,
+ tdsqlite3_snapshot *p2
+);
+
+/*
+** CAPI3REF: Recover snapshots from a wal file
+** METHOD: tdsqlite3_snapshot
+**
+** If a [WAL file] remains on disk after all database connections close
+** (either through the use of the [SQLITE_FCNTL_PERSIST_WAL] [file control]
+** or because the last process to have the database opened exited without
+** calling [tdsqlite3_close()]) and a new connection is subsequently opened
+** on that database and [WAL file], the [tdsqlite3_snapshot_open()] interface
+** will only be able to open the last transaction added to the WAL file
+** even though the WAL file contains other valid transactions.
+**
+** This function attempts to scan the WAL file associated with database zDb
+** of database handle db and make all valid snapshots available to
+** tdsqlite3_snapshot_open(). It is an error if there is already a read
+** transaction open on the database, or if the database is not a WAL mode
+** database.
+**
+** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_SNAPSHOT] option.
+*/
+SQLITE_API SQLITE_EXPERIMENTAL int tdsqlite3_snapshot_recover(tdsqlite3 *db, const char *zDb);
+
+/*
+** CAPI3REF: Serialize a database
+**
+** The tdsqlite3_serialize(D,S,P,F) interface returns a pointer to memory
+** that is a serialization of the S database on [database connection] D.
+** If P is not a NULL pointer, then the size of the database in bytes
+** is written into *P.
+**
+** For an ordinary on-disk database file, the serialization is just a
+** copy of the disk file. For an in-memory database or a "TEMP" database,
+** the serialization is the same sequence of bytes which would be written
+** to disk if that database where backed up to disk.
+**
+** The usual case is that tdsqlite3_serialize() copies the serialization of
+** the database into memory obtained from [tdsqlite3_malloc64()] and returns
+** a pointer to that memory. The caller is responsible for freeing the
+** returned value to avoid a memory leak. However, if the F argument
+** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
+** are made, and the tdsqlite3_serialize() function will return a pointer
+** to the contiguous memory representation of the database that SQLite
+** is currently using for that database, or NULL if the no such contiguous
+** memory representation of the database exists. A contiguous memory
+** representation of the database will usually only exist if there has
+** been a prior call to [tdsqlite3_deserialize(D,S,...)] with the same
+** values of D and S.
+** The size of the database is written into *P even if the
+** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy
+** of the database exists.
+**
+** A call to tdsqlite3_serialize(D,S,P,F) might return NULL even if the
+** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory
+** allocation error occurs.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_DESERIALIZE] option.
+*/
+SQLITE_API unsigned char *tdsqlite3_serialize(
+ tdsqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which DB to serialize. ex: "main", "temp", ... */
+ tdsqlite3_int64 *piSize, /* Write size of the DB here, if not NULL */
+ unsigned int mFlags /* Zero or more SQLITE_SERIALIZE_* flags */
+);
+
+/*
+** CAPI3REF: Flags for tdsqlite3_serialize
+**
+** Zero or more of the following constants can be OR-ed together for
+** the F argument to [tdsqlite3_serialize(D,S,P,F)].
+**
+** SQLITE_SERIALIZE_NOCOPY means that [tdsqlite3_serialize()] will return
+** a pointer to contiguous in-memory database that it is currently using,
+** without making a copy of the database. If SQLite is not currently using
+** a contiguous in-memory database, then this option causes
+** [tdsqlite3_serialize()] to return a NULL pointer. SQLite will only be
+** using a contiguous in-memory database if it has been initialized by a
+** prior call to [tdsqlite3_deserialize()].
+*/
+#define SQLITE_SERIALIZE_NOCOPY 0x001 /* Do no memory allocations */
+
+/*
+** CAPI3REF: Deserialize a database
+**
+** The tdsqlite3_deserialize(D,S,P,N,M,F) interface causes the
+** [database connection] D to disconnect from database S and then
+** reopen S as an in-memory database based on the serialization contained
+** in P. The serialized database P is N bytes in size. M is the size of
+** the buffer P, which might be larger than N. If M is larger than N, and
+** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is
+** permitted to add content to the in-memory database as long as the total
+** size does not exceed M bytes.
+**
+** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will
+** invoke tdsqlite3_free() on the serialization buffer when the database
+** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then
+** SQLite will try to increase the buffer size using tdsqlite3_realloc64()
+** if writes on the database cause it to grow larger than M bytes.
+**
+** The tdsqlite3_deserialize() interface will fail with SQLITE_BUSY if the
+** database is currently in a read transaction or is involved in a backup
+** operation.
+**
+** If tdsqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the
+** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then
+** [tdsqlite3_free()] is invoked on argument P prior to returning.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_DESERIALIZE] option.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
- sqlite3_snapshot *p1,
- sqlite3_snapshot *p2
+SQLITE_API int tdsqlite3_deserialize(
+ tdsqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which DB to reopen with the deserialization */
+ unsigned char *pData, /* The serialized database content */
+ tdsqlite3_int64 szDb, /* Number bytes in the deserialization */
+ tdsqlite3_int64 szBuf, /* Total size of buffer pData[] */
+ unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
);
/*
+** CAPI3REF: Flags for tdsqlite3_deserialize()
+**
+** The following are allowed values for 6th argument (the F argument) to
+** the [tdsqlite3_deserialize(D,S,P,N,M,F)] interface.
+**
+** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
+** in the P argument is held in memory obtained from [tdsqlite3_malloc64()]
+** and that SQLite should take ownership of this memory and automatically
+** free it when it has finished using it. Without this flag, the caller
+** is responsible for freeing any dynamically allocated memory.
+**
+** The SQLITE_DESERIALIZE_RESIZEABLE flag means that SQLite is allowed to
+** grow the size of the database using calls to [tdsqlite3_realloc64()]. This
+** flag should only be used if SQLITE_DESERIALIZE_FREEONCLOSE is also used.
+** Without this flag, the deserialized database cannot increase in size beyond
+** the number of bytes specified by the M parameter.
+**
+** The SQLITE_DESERIALIZE_READONLY flag means that the deserialized database
+** should be treated as read-only.
+*/
+#define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call tdsqlite3_free() on close */
+#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using tdsqlite3_realloc64() */
+#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
+
+/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
@@ -8646,7 +10782,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
#endif
#endif /* SQLITE3_H */
-/******** Begin file sqlite3rtree.h *********/
+/******** Begin file tdsqlite3rtree.h *********/
/*
** 2010 August 30
**
@@ -8668,16 +10804,16 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
extern "C" {
#endif
-typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry;
-typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info;
+typedef struct tdsqlite3_rtree_geometry tdsqlite3_rtree_geometry;
+typedef struct tdsqlite3_rtree_query_info tdsqlite3_rtree_query_info;
/* The double-precision datatype used by RTree depends on the
** SQLITE_RTREE_INT_ONLY compile-time option.
*/
#ifdef SQLITE_RTREE_INT_ONLY
- typedef sqlite3_int64 sqlite3_rtree_dbl;
+ typedef tdsqlite3_int64 tdsqlite3_rtree_dbl;
#else
- typedef double sqlite3_rtree_dbl;
+ typedef double tdsqlite3_rtree_dbl;
#endif
/*
@@ -8686,10 +10822,10 @@ typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info;
**
** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zGeom(... params ...)
*/
-SQLITE_API int sqlite3_rtree_geometry_callback(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_rtree_geometry_callback(
+ tdsqlite3 *db,
const char *zGeom,
- int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*,int*),
+ int (*xGeom)(tdsqlite3_rtree_geometry*, int, tdsqlite3_rtree_dbl*,int*),
void *pContext
);
@@ -8698,10 +10834,10 @@ SQLITE_API int sqlite3_rtree_geometry_callback(
** A pointer to a structure of the following type is passed as the first
** argument to callbacks registered using rtree_geometry_callback().
*/
-struct sqlite3_rtree_geometry {
+struct tdsqlite3_rtree_geometry {
void *pContext; /* Copy of pContext passed to s_r_g_c() */
int nParam; /* Size of array aParam[] */
- sqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */
+ tdsqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */
void *pUser; /* Callback implementation user data */
void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */
};
@@ -8712,10 +10848,10 @@ struct sqlite3_rtree_geometry {
**
** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zQueryFunc(... params ...)
*/
-SQLITE_API int sqlite3_rtree_query_callback(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_rtree_query_callback(
+ tdsqlite3 *db,
const char *zQueryFunc,
- int (*xQueryFunc)(sqlite3_rtree_query_info*),
+ int (*xQueryFunc)(tdsqlite3_rtree_query_info*),
void *pContext,
void (*xDestructor)(void*)
);
@@ -8724,34 +10860,34 @@ SQLITE_API int sqlite3_rtree_query_callback(
/*
** A pointer to a structure of the following type is passed as the
** argument to scored geometry callback registered using
-** sqlite3_rtree_query_callback().
+** tdsqlite3_rtree_query_callback().
**
** Note that the first 5 fields of this structure are identical to
-** sqlite3_rtree_geometry. This structure is a subclass of
-** sqlite3_rtree_geometry.
+** tdsqlite3_rtree_geometry. This structure is a subclass of
+** tdsqlite3_rtree_geometry.
*/
-struct sqlite3_rtree_query_info {
+struct tdsqlite3_rtree_query_info {
void *pContext; /* pContext from when function registered */
int nParam; /* Number of function parameters */
- sqlite3_rtree_dbl *aParam; /* value of function parameters */
+ tdsqlite3_rtree_dbl *aParam; /* value of function parameters */
void *pUser; /* callback can use this, if desired */
void (*xDelUser)(void*); /* function to free pUser */
- sqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */
+ tdsqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */
unsigned int *anQueue; /* Number of pending entries in the queue */
int nCoord; /* Number of coordinates */
int iLevel; /* Level of current node or entry */
int mxLevel; /* The largest iLevel value in the tree */
- sqlite3_int64 iRowid; /* Rowid for current entry */
- sqlite3_rtree_dbl rParentScore; /* Score of parent node */
+ tdsqlite3_int64 iRowid; /* Rowid for current entry */
+ tdsqlite3_rtree_dbl rParentScore; /* Score of parent node */
int eParentWithin; /* Visibility of parent node */
- int eWithin; /* OUT: Visiblity */
- sqlite3_rtree_dbl rScore; /* OUT: Write the score here */
+ int eWithin; /* OUT: Visibility */
+ tdsqlite3_rtree_dbl rScore; /* OUT: Write the score here */
/* The following fields are only available in 3.8.11 and later */
- sqlite3_value **apSqlParam; /* Original SQL values of parameters */
+ tdsqlite3_value **apSqlParam; /* Original SQL values of parameters */
};
/*
-** Allowed values for sqlite3_rtree_query.eWithin and .eParentWithin.
+** Allowed values for tdsqlite3_rtree_query.eWithin and .eParentWithin.
*/
#define NOT_WITHIN 0 /* Object completely outside of query region */
#define PARTLY_WITHIN 1 /* Object partially overlaps query region */
@@ -8764,8 +10900,8 @@ struct sqlite3_rtree_query_info {
#endif /* ifndef _SQLITE3RTREE_H_ */
-/******** End of sqlite3rtree.h *********/
-/******** Begin file sqlite3session.h *********/
+/******** End of tdsqlite3rtree.h *********/
+/******** Begin file tdsqlite3session.h *********/
#if !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION)
#define __SQLITESESSION_H_ 1
@@ -8780,16 +10916,23 @@ extern "C" {
/*
** CAPI3REF: Session Object Handle
+**
+** An instance of this object is a [session] that can be used to
+** record changes to a database.
*/
-typedef struct sqlite3_session sqlite3_session;
+typedef struct tdsqlite3_session tdsqlite3_session;
/*
** CAPI3REF: Changeset Iterator Handle
+**
+** An instance of this object acts as a cursor for iterating
+** over the elements of a [changeset] or [patchset].
*/
-typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
+typedef struct tdsqlite3_changeset_iter tdsqlite3_changeset_iter;
/*
** CAPI3REF: Create A New Session Object
+** CONSTRUCTOR: tdsqlite3_session
**
** Create a new session object attached to database handle db. If successful,
** a pointer to the new object is written to *ppSession and SQLITE_OK is
@@ -8800,13 +10943,13 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
** database handle.
**
** Session objects created using this function should be deleted using the
-** [sqlite3session_delete()] function before the database handle that they
+** [tdsqlite3session_delete()] function before the database handle that they
** are attached to is itself closed. If the database handle is closed before
** the session object is deleted, then the results of calling any session
-** module function, including [sqlite3session_delete()] on the session object
+** module function, including [tdsqlite3session_delete()] on the session object
** are undefined.
**
-** Because the session module uses the [sqlite3_preupdate_hook()] API, it
+** Because the session module uses the [tdsqlite3_preupdate_hook()] API, it
** is not possible for an application to register a pre-update hook on a
** database handle that has one or more session objects attached. Nor is
** it possible to create a session object attached to a database handle for
@@ -8818,34 +10961,36 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
** attached database. It is not an error if database zDb is not attached
** to the database when the session object is created.
*/
-int sqlite3session_create(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3session_create(
+ tdsqlite3 *db, /* Database handle */
const char *zDb, /* Name of db (e.g. "main") */
- sqlite3_session **ppSession /* OUT: New session object */
+ tdsqlite3_session **ppSession /* OUT: New session object */
);
/*
** CAPI3REF: Delete A Session Object
+** DESTRUCTOR: tdsqlite3_session
**
** Delete a session object previously allocated using
-** [sqlite3session_create()]. Once a session object has been deleted, the
+** [tdsqlite3session_create()]. Once a session object has been deleted, the
** results of attempting to use pSession with any other session module
** function are undefined.
**
** Session objects must be deleted before the database handle to which they
** are attached is closed. Refer to the documentation for
-** [sqlite3session_create()] for details.
+** [tdsqlite3session_create()] for details.
*/
-void sqlite3session_delete(sqlite3_session *pSession);
+SQLITE_API void tdsqlite3session_delete(tdsqlite3_session *pSession);
/*
** CAPI3REF: Enable Or Disable A Session Object
+** METHOD: tdsqlite3_session
**
** Enable or disable the recording of changes by a session object. When
** enabled, a session object records changes made to the database. When
** disabled - it does not. A newly created session object is enabled.
-** Refer to the documentation for [sqlite3session_changeset()] for further
+** Refer to the documentation for [tdsqlite3session_changeset()] for further
** details regarding how enabling and disabling a session object affects
** the eventual changesets.
**
@@ -8856,10 +11001,11 @@ void sqlite3session_delete(sqlite3_session *pSession);
** The return value indicates the final state of the session object: 0 if
** the session is disabled, or 1 if it is enabled.
*/
-int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
+SQLITE_API int tdsqlite3session_enable(tdsqlite3_session *pSession, int bEnable);
/*
** CAPI3REF: Set Or Clear the Indirect Change Flag
+** METHOD: tdsqlite3_session
**
** Each change recorded by a session object is marked as either direct or
** indirect. A change is marked as indirect if either:
@@ -8885,15 +11031,16 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
** The return value indicates the final state of the indirect flag: 0 if
** it is clear, or 1 if it is set.
*/
-int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
+SQLITE_API int tdsqlite3session_indirect(tdsqlite3_session *pSession, int bIndirect);
/*
** CAPI3REF: Attach A Table To A Session Object
+** METHOD: tdsqlite3_session
**
** If argument zTab is not NULL, then it is the name of a table to attach
** to the session object passed as the first argument. All subsequent changes
** made to the table while the session object is enabled will be recorded. See
-** documentation for [sqlite3session_changeset()] for further details.
+** documentation for [tdsqlite3session_changeset()] for further details.
**
** Or, if argument zTab is NULL, then changes are recorded for all tables
** in the database. If additional tables are added to the database (by
@@ -8914,23 +11061,53 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
**
** SQLITE_OK is returned if the call completes without error. Or, if an error
** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
-*/
-int sqlite3session_attach(
- sqlite3_session *pSession, /* Session object */
+**
+** <h3>Special sqlite_stat1 Handling</h3>
+**
+** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to
+** some of the rules above. In SQLite, the schema of sqlite_stat1 is:
+** <pre>
+** &nbsp; CREATE TABLE sqlite_stat1(tbl,idx,stat)
+** </pre>
+**
+** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are
+** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes
+** are recorded for rows for which (idx IS NULL) is true. However, for such
+** rows a zero-length blob (SQL value X'') is stored in the changeset or
+** patchset instead of a NULL value. This allows such changesets to be
+** manipulated by legacy implementations of tdsqlite3changeset_invert(),
+** concat() and similar.
+**
+** The tdsqlite3changeset_apply() function automatically converts the
+** zero-length blob back to a NULL value when updating the sqlite_stat1
+** table. However, if the application calls tdsqlite3changeset_new(),
+** tdsqlite3changeset_old() or tdsqlite3changeset_conflict on a changeset
+** iterator directly (including on a changeset iterator passed to a
+** conflict-handler callback) then the X'' value is returned. The application
+** must translate X'' to NULL itself if required.
+**
+** Legacy (older than 3.22.0) versions of the sessions module cannot capture
+** changes made to the sqlite_stat1 table. Legacy versions of the
+** tdsqlite3changeset_apply() function silently ignore any modifications to the
+** sqlite_stat1 table that are part of a changeset or patchset.
+*/
+SQLITE_API int tdsqlite3session_attach(
+ tdsqlite3_session *pSession, /* Session object */
const char *zTab /* Table name */
);
/*
** CAPI3REF: Set a table filter on a Session Object.
+** METHOD: tdsqlite3_session
**
** The second argument (xFilter) is the "filter callback". For changes to rows
** in tables that are not attached to the Session object, the filter is called
** to determine whether changes to the table's rows should be tracked or not.
-** If xFilter returns 0, changes is not tracked. Note that once a table is
+** If xFilter returns 0, changes are not tracked. Note that once a table is
** attached, xFilter will not be called again.
*/
-void sqlite3session_table_filter(
- sqlite3_session *pSession, /* Session object */
+SQLITE_API void tdsqlite3session_table_filter(
+ tdsqlite3_session *pSession, /* Session object */
int(*xFilter)(
void *pCtx, /* Copy of third arg to _filter_table() */
const char *zTab /* Table name */
@@ -8940,6 +11117,7 @@ void sqlite3session_table_filter(
/*
** CAPI3REF: Generate A Changeset From A Session Object
+** METHOD: tdsqlite3_session
**
** Obtain a changeset containing changes to the tables attached to the
** session object passed as the first argument. If successful,
@@ -8969,8 +11147,8 @@ void sqlite3session_table_filter(
** DELETE change only.
**
** The contents of a changeset may be traversed using an iterator created
-** using the [sqlite3changeset_start()] API. A changeset may be applied to
-** a database with a compatible schema using the [sqlite3changeset_apply()]
+** using the [tdsqlite3changeset_start()] API. A changeset may be applied to
+** a database with a compatible schema using the [tdsqlite3changeset_apply()]
** API.
**
** Within a changeset generated by this function, all changes related to a
@@ -8978,12 +11156,12 @@ void sqlite3session_table_filter(
** a changeset or when applying a changeset to a database, all changes related
** to a single table are processed before moving on to the next table. Tables
** are sorted in the same order in which they were attached (or auto-attached)
-** to the sqlite3_session object. The order in which the changes related to
+** to the tdsqlite3_session object. The order in which the changes related to
** a single table are stored is undefined.
**
** Following a successful call to this function, it is the responsibility of
** the caller to eventually free the buffer that *ppChangeset points to using
-** [sqlite3_free()].
+** [tdsqlite3_free()].
**
** <h3>Changeset Generation</h3>
**
@@ -9031,7 +11209,7 @@ void sqlite3session_table_filter(
** active, the resulting changeset will contain an UPDATE change instead of
** a DELETE and an INSERT.
**
-** When a session object is disabled (see the [sqlite3session_enable()] API),
+** When a session object is disabled (see the [tdsqlite3session_enable()] API),
** it does not accumulate records when rows are inserted, updated or deleted.
** This may appear to have some counter-intuitive effects if a single row
** is written to more than once during a session. For example, if a row
@@ -9042,18 +11220,19 @@ void sqlite3session_table_filter(
** another field of the same row is updated while the session is enabled, the
** resulting changeset will contain an UPDATE change that updates both fields.
*/
-int sqlite3session_changeset(
- sqlite3_session *pSession, /* Session object */
+SQLITE_API int tdsqlite3session_changeset(
+ tdsqlite3_session *pSession, /* Session object */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
);
/*
-** CAPI3REF: Load The Difference Between Tables Into A Session
+** CAPI3REF: Load The Difference Between Tables Into A Session
+** METHOD: tdsqlite3_session
**
** If it is not already attached to the session object passed as the first
** argument, this function attaches table zTbl in the same manner as the
-** [sqlite3session_attach()] function. If zTbl does not exist, or if it
+** [tdsqlite3session_attach()] function. If zTbl does not exist, or if it
** does not have a primary key, this function is a no-op (but does not return
** an error).
**
@@ -9086,25 +11265,26 @@ int sqlite3session_changeset(
** the from-table, a DELETE record is added to the session object.
**
** <li> For each row (primary key) that exists in both tables, but features
-** different in each, an UPDATE record is added to the session.
+** different non-PK values in each, an UPDATE record is added to the
+** session.
** </ul>
**
** To clarify, if this function is called and then a changeset constructed
-** using [sqlite3session_changeset()], then after applying that changeset to
+** using [tdsqlite3session_changeset()], then after applying that changeset to
** database zFrom the contents of the two compatible tables would be
** identical.
**
** It an error if database zFrom does not exist or does not contain the
** required compatible table.
**
-** If the operation successful, SQLITE_OK is returned. Otherwise, an SQLite
+** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
** may be set to point to a buffer containing an English language error
** message. It is the responsibility of the caller to free this buffer using
-** sqlite3_free().
+** tdsqlite3_free().
*/
-int sqlite3session_diff(
- sqlite3_session *pSession,
+SQLITE_API int tdsqlite3session_diff(
+ tdsqlite3_session *pSession,
const char *zFromDb,
const char *zTbl,
char **pzErrMsg
@@ -9113,6 +11293,7 @@ int sqlite3session_diff(
/*
** CAPI3REF: Generate A Patchset From A Session Object
+** METHOD: tdsqlite3_session
**
** The differences between a patchset and a changeset are that:
**
@@ -9124,25 +11305,25 @@ int sqlite3session_diff(
** </ul>
**
** A patchset blob may be used with up to date versions of all
-** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(),
+** tdsqlite3changeset_xxx API functions except for tdsqlite3changeset_invert(),
** which returns SQLITE_CORRUPT if it is passed a patchset. Similarly,
** attempting to use a patchset blob with old versions of the
-** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error.
+** tdsqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error.
**
** Because the non-primary key "old.*" fields are omitted, no
** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset
-** is passed to the sqlite3changeset_apply() API. Other conflict types work
+** is passed to the tdsqlite3changeset_apply() API. Other conflict types work
** in the same way as for changesets.
**
** Changes within a patchset are ordered in the same way as for changesets
-** generated by the sqlite3session_changeset() function (i.e. all changes for
+** generated by the tdsqlite3session_changeset() function (i.e. all changes for
** a single table are grouped together, tables appear in the order in which
** they were attached to the session object).
*/
-int sqlite3session_patchset(
- sqlite3_session *pSession, /* Session object */
- int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */
- void **ppPatchset /* OUT: Buffer containing changeset */
+SQLITE_API int tdsqlite3session_patchset(
+ tdsqlite3_session *pSession, /* Session object */
+ int *pnPatchset, /* OUT: Size of buffer at *ppPatchset */
+ void **ppPatchset /* OUT: Buffer containing patchset */
);
/*
@@ -9153,17 +11334,18 @@ int sqlite3session_patchset(
** more changes have been recorded, return zero.
**
** Even if this function returns zero, it is possible that calling
-** [sqlite3session_changeset()] on the session handle may still return a
+** [tdsqlite3session_changeset()] on the session handle may still return a
** changeset that contains no changes. This can happen when a row in
** an attached table is modified and then later on the original values
** are restored. However, if this function returns non-zero, then it is
-** guaranteed that a call to sqlite3session_changeset() will return a
+** guaranteed that a call to tdsqlite3session_changeset() will return a
** changeset containing zero changes.
*/
-int sqlite3session_isempty(sqlite3_session *pSession);
+SQLITE_API int tdsqlite3session_isempty(tdsqlite3_session *pSession);
/*
** CAPI3REF: Create An Iterator To Traverse A Changeset
+** CONSTRUCTOR: tdsqlite3_changeset_iter
**
** Create an iterator used to iterate through the contents of a changeset.
** If successful, *pp is set to point to the iterator handle and SQLITE_OK
@@ -9174,49 +11356,76 @@ int sqlite3session_isempty(sqlite3_session *pSession);
** iterator created by this function:
**
** <ul>
-** <li> [sqlite3changeset_next()]
-** <li> [sqlite3changeset_op()]
-** <li> [sqlite3changeset_new()]
-** <li> [sqlite3changeset_old()]
+** <li> [tdsqlite3changeset_next()]
+** <li> [tdsqlite3changeset_op()]
+** <li> [tdsqlite3changeset_new()]
+** <li> [tdsqlite3changeset_old()]
** </ul>
**
** It is the responsibility of the caller to eventually destroy the iterator
-** by passing it to [sqlite3changeset_finalize()]. The buffer containing the
+** by passing it to [tdsqlite3changeset_finalize()]. The buffer containing the
** changeset (pChangeset) must remain valid until after the iterator is
** destroyed.
**
** Assuming the changeset blob was created by one of the
-** [sqlite3session_changeset()], [sqlite3changeset_concat()] or
-** [sqlite3changeset_invert()] functions, all changes within the changeset
+** [tdsqlite3session_changeset()], [tdsqlite3changeset_concat()] or
+** [tdsqlite3changeset_invert()] functions, all changes within the changeset
** that apply to a single table are grouped together. This means that when
** an application iterates through a changeset using an iterator created by
** this function, all changes that relate to a single table are visited
** consecutively. There is no chance that the iterator will visit a change
** the applies to table X, then one for table Y, and then later on visit
** another change for table X.
+**
+** The behavior of tdsqlite3changeset_start_v2() and its streaming equivalent
+** may be modified by passing a combination of
+** [SQLITE_CHANGESETSTART_INVERT | supported flags] as the 4th parameter.
+**
+** Note that the tdsqlite3changeset_start_v2() API is still <b>experimental</b>
+** and therefore subject to change.
*/
-int sqlite3changeset_start(
- sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
+SQLITE_API int tdsqlite3changeset_start(
+ tdsqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
int nChangeset, /* Size of changeset blob in bytes */
void *pChangeset /* Pointer to blob containing changeset */
);
+SQLITE_API int tdsqlite3changeset_start_v2(
+ tdsqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
+ int nChangeset, /* Size of changeset blob in bytes */
+ void *pChangeset, /* Pointer to blob containing changeset */
+ int flags /* SESSION_CHANGESETSTART_* flags */
+);
+
+/*
+** CAPI3REF: Flags for tdsqlite3changeset_start_v2
+**
+** The following flags may passed via the 4th parameter to
+** [tdsqlite3changeset_start_v2] and [tdsqlite3changeset_start_v2_strm]:
+**
+** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
+** Invert the changeset while iterating through it. This is equivalent to
+** inverting a changeset using tdsqlite3changeset_invert() before applying it.
+** It is an error to specify this flag with a patchset.
+*/
+#define SQLITE_CHANGESETSTART_INVERT 0x0002
/*
** CAPI3REF: Advance A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
-** This function may only be used with iterators created by function
-** [sqlite3changeset_start()]. If it is called on an iterator passed to
-** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE
+** This function may only be used with iterators created by the function
+** [tdsqlite3changeset_start()]. If it is called on an iterator passed to
+** a conflict-handler callback by [tdsqlite3changeset_apply()], SQLITE_MISUSE
** is returned and the call has no effect.
**
-** Immediately after an iterator is created by sqlite3changeset_start(), it
+** Immediately after an iterator is created by tdsqlite3changeset_start(), it
** does not point to any change in the changeset. Assuming the changeset
** is not empty, the first call to this function advances the iterator to
** point to the first change in the changeset. Each subsequent call advances
** the iterator to point to the next change in the changeset (if any). If
** no error occurs and the iterator points to a valid change after a call
-** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned.
+** to tdsqlite3changeset_next() has advanced it, SQLITE_ROW is returned.
** Otherwise, if all changes in the changeset have already been visited,
** SQLITE_DONE is returned.
**
@@ -9224,26 +11433,27 @@ int sqlite3changeset_start(
** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or
** SQLITE_NOMEM.
*/
-int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
+SQLITE_API int tdsqlite3changeset_next(tdsqlite3_changeset_iter *pIter);
/*
** CAPI3REF: Obtain The Current Operation From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
-** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
-** created by [sqlite3changeset_start()]. In the latter case, the most recent
-** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
+** passed to a conflict-handler by [tdsqlite3changeset_apply()], or an iterator
+** created by [tdsqlite3changeset_start()]. In the latter case, the most recent
+** call to [tdsqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
** is not the case, this function returns [SQLITE_MISUSE].
**
** If argument pzTab is not NULL, then *pzTab is set to point to a
** nul-terminated utf-8 encoded string containing the name of the table
** affected by the current change. The buffer remains valid until either
-** sqlite3changeset_next() is called on the iterator or until the
+** tdsqlite3changeset_next() is called on the iterator or until the
** conflict-handler function returns. If pnCol is not NULL, then *pnCol is
** set to the number of columns in the table affected by the change. If
-** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change
+** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
** is an indirect change, or false (0) otherwise. See the documentation for
-** [sqlite3session_indirect()] for a description of direct and indirect
+** [tdsqlite3session_indirect()] for a description of direct and indirect
** changes. Finally, if pOp is not NULL, then *pOp is set to one of
** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the
** type of change that the iterator currently points to.
@@ -9252,8 +11462,8 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
** SQLite error code is returned. The values of the output variables may not
** be trusted in this case.
*/
-int sqlite3changeset_op(
- sqlite3_changeset_iter *pIter, /* Iterator object */
+SQLITE_API int tdsqlite3changeset_op(
+ tdsqlite3_changeset_iter *pIter, /* Iterator object */
const char **pzTab, /* OUT: Pointer to table name */
int *pnCol, /* OUT: Number of columns in table */
int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */
@@ -9262,6 +11472,7 @@ int sqlite3changeset_op(
/*
** CAPI3REF: Obtain The Primary Key Definition Of A Table
+** METHOD: tdsqlite3_changeset_iter
**
** For each modified table, a changeset includes the following:
**
@@ -9285,19 +11496,20 @@ int sqlite3changeset_op(
** SQLITE_OK is returned and the output variables populated as described
** above.
*/
-int sqlite3changeset_pk(
- sqlite3_changeset_iter *pIter, /* Iterator object */
+SQLITE_API int tdsqlite3changeset_pk(
+ tdsqlite3_changeset_iter *pIter, /* Iterator object */
unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */
int *pnCol /* OUT: Number of entries in output array */
);
/*
** CAPI3REF: Obtain old.* Values From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
-** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
-** created by [sqlite3changeset_start()]. In the latter case, the most recent
-** call to [sqlite3changeset_next()] must have returned SQLITE_ROW.
+** passed to a conflict-handler by [tdsqlite3changeset_apply()], or an iterator
+** created by [tdsqlite3changeset_start()]. In the latter case, the most recent
+** call to [tdsqlite3changeset_next()] must have returned SQLITE_ROW.
** Furthermore, it may only be called if the type of change that the iterator
** currently points to is either [SQLITE_DELETE] or [SQLITE_UPDATE]. Otherwise,
** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL.
@@ -9307,7 +11519,7 @@ int sqlite3changeset_pk(
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
**
** If successful, this function sets *ppValue to point to a protected
-** sqlite3_value object containing the iVal'th value from the vector of
+** tdsqlite3_value object containing the iVal'th value from the vector of
** original row values stored as part of the UPDATE or DELETE change and
** returns SQLITE_OK. The name of the function comes from the fact that this
** is similar to the "old.*" columns available to update or delete triggers.
@@ -9315,19 +11527,20 @@ int sqlite3changeset_pk(
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
-int sqlite3changeset_old(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_old(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
- sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
+ tdsqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
);
/*
** CAPI3REF: Obtain new.* Values From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
-** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
-** created by [sqlite3changeset_start()]. In the latter case, the most recent
-** call to [sqlite3changeset_next()] must have returned SQLITE_ROW.
+** passed to a conflict-handler by [tdsqlite3changeset_apply()], or an iterator
+** created by [tdsqlite3changeset_start()]. In the latter case, the most recent
+** call to [tdsqlite3changeset_next()] must have returned SQLITE_ROW.
** Furthermore, it may only be called if the type of change that the iterator
** currently points to is either [SQLITE_UPDATE] or [SQLITE_INSERT]. Otherwise,
** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL.
@@ -9337,7 +11550,7 @@ int sqlite3changeset_old(
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
**
** If successful, this function sets *ppValue to point to a protected
-** sqlite3_value object containing the iVal'th value from the vector of
+** tdsqlite3_value object containing the iVal'th value from the vector of
** new row values stored as part of the UPDATE or INSERT change and
** returns SQLITE_OK. If the change is an UPDATE and does not include
** a new value for the requested column, *ppValue is set to NULL and
@@ -9348,17 +11561,18 @@ int sqlite3changeset_old(
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
-int sqlite3changeset_new(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_new(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
- sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
+ tdsqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
);
/*
** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** This function should only be used with iterator objects passed to a
-** conflict-handler callback by [sqlite3changeset_apply()] with either
+** conflict-handler callback by [tdsqlite3changeset_apply()] with either
** [SQLITE_CHANGESET_DATA] or [SQLITE_CHANGESET_CONFLICT]. If this function
** is called on any other iterator, [SQLITE_MISUSE] is returned and *ppValue
** is set to NULL.
@@ -9368,21 +11582,22 @@ int sqlite3changeset_new(
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
**
** If successful, this function sets *ppValue to point to a protected
-** sqlite3_value object containing the iVal'th value from the
+** tdsqlite3_value object containing the iVal'th value from the
** "conflicting row" associated with the current conflict-handler callback
** and returns SQLITE_OK.
**
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
-int sqlite3changeset_conflict(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_conflict(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
- sqlite3_value **ppValue /* OUT: Value from conflicting row */
+ tdsqlite3_value **ppValue /* OUT: Value from conflicting row */
);
/*
** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations
+** METHOD: tdsqlite3_changeset_iter
**
** This function may only be called with an iterator passed to an
** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case
@@ -9391,40 +11606,43 @@ int sqlite3changeset_conflict(
**
** In all other cases this function returns SQLITE_MISUSE.
*/
-int sqlite3changeset_fk_conflicts(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_fk_conflicts(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int *pnOut /* OUT: Number of FK violations */
);
/*
** CAPI3REF: Finalize A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** This function is used to finalize an iterator allocated with
-** [sqlite3changeset_start()].
+** [tdsqlite3changeset_start()].
**
** This function should only be called on iterators created using the
-** [sqlite3changeset_start()] function. If an application calls this
+** [tdsqlite3changeset_start()] function. If an application calls this
** function with an iterator passed to a conflict-handler by
-** [sqlite3changeset_apply()], [SQLITE_MISUSE] is immediately returned and the
+** [tdsqlite3changeset_apply()], [SQLITE_MISUSE] is immediately returned and the
** call has no effect.
**
-** If an error was encountered within a call to an sqlite3changeset_xxx()
-** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an
-** [SQLITE_NOMEM] in [sqlite3changeset_new()]) then an error code corresponding
+** If an error was encountered within a call to an tdsqlite3changeset_xxx()
+** function (for example an [SQLITE_CORRUPT] in [tdsqlite3changeset_next()] or an
+** [SQLITE_NOMEM] in [tdsqlite3changeset_new()]) then an error code corresponding
** to that error is returned by this function. Otherwise, SQLITE_OK is
** returned. This is to allow the following pattern (pseudo-code):
**
-** sqlite3changeset_start();
-** while( SQLITE_ROW==sqlite3changeset_next() ){
+** <pre>
+** tdsqlite3changeset_start();
+** while( SQLITE_ROW==tdsqlite3changeset_next() ){
** // Do something with change.
** }
-** rc = sqlite3changeset_finalize();
+** rc = tdsqlite3changeset_finalize();
** if( rc!=SQLITE_OK ){
** // An error has occurred
** }
+** </pre>
*/
-int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
+SQLITE_API int tdsqlite3changeset_finalize(tdsqlite3_changeset_iter *pIter);
/*
** CAPI3REF: Invert A Changeset
@@ -9447,14 +11665,14 @@ int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
** SQLITE_OK is returned. If an error occurs, both *pnOut and *ppOut are
** zeroed and an SQLite error code returned.
**
-** It is the responsibility of the caller to eventually call sqlite3_free()
+** It is the responsibility of the caller to eventually call tdsqlite3_free()
** on the *ppOut pointer to free the buffer allocation following a successful
** call to this function.
**
** WARNING/TODO: This function currently assumes that the input is a valid
** changeset. If it is not, the results are undefined.
*/
-int sqlite3changeset_invert(
+SQLITE_API int tdsqlite3changeset_invert(
int nIn, const void *pIn, /* Input changeset */
int *pnOut, void **ppOut /* OUT: Inverse of input */
);
@@ -9467,23 +11685,25 @@ int sqlite3changeset_invert(
** changeset A followed by changeset B.
**
** This function combines the two input changesets using an
-** sqlite3_changegroup object. Calling it produces similar results as the
+** tdsqlite3_changegroup object. Calling it produces similar results as the
** following code fragment:
**
-** sqlite3_changegroup *pGrp;
-** rc = sqlite3_changegroup_new(&pGrp);
-** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nA, pA);
-** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nB, pB);
+** <pre>
+** tdsqlite3_changegroup *pGrp;
+** rc = tdsqlite3_changegroup_new(&pGrp);
+** if( rc==SQLITE_OK ) rc = tdsqlite3changegroup_add(pGrp, nA, pA);
+** if( rc==SQLITE_OK ) rc = tdsqlite3changegroup_add(pGrp, nB, pB);
** if( rc==SQLITE_OK ){
-** rc = sqlite3changegroup_output(pGrp, pnOut, ppOut);
+** rc = tdsqlite3changegroup_output(pGrp, pnOut, ppOut);
** }else{
** *ppOut = 0;
** *pnOut = 0;
** }
+** </pre>
**
-** Refer to the sqlite3_changegroup documentation below for details.
+** Refer to the tdsqlite3_changegroup documentation below for details.
*/
-int sqlite3changeset_concat(
+SQLITE_API int tdsqlite3changeset_concat(
int nA, /* Number of bytes in buffer pA */
void *pA, /* Pointer to buffer containing changeset A */
int nB, /* Number of bytes in buffer pB */
@@ -9495,48 +11715,53 @@ int sqlite3changeset_concat(
/*
** CAPI3REF: Changegroup Handle
+**
+** A changegroup is an object used to combine two or more
+** [changesets] or [patchsets]
*/
-typedef struct sqlite3_changegroup sqlite3_changegroup;
+typedef struct tdsqlite3_changegroup tdsqlite3_changegroup;
/*
** CAPI3REF: Create A New Changegroup Object
+** CONSTRUCTOR: tdsqlite3_changegroup
**
-** An sqlite3_changegroup object is used to combine two or more changesets
+** An tdsqlite3_changegroup object is used to combine two or more changesets
** (or patchsets) into a single changeset (or patchset). A single changegroup
** object may combine changesets or patchsets, but not both. The output is
** always in the same format as the input.
**
** If successful, this function returns SQLITE_OK and populates (*pp) with
-** a pointer to a new sqlite3_changegroup object before returning. The caller
+** a pointer to a new tdsqlite3_changegroup object before returning. The caller
** should eventually free the returned object using a call to
-** sqlite3changegroup_delete(). If an error occurs, an SQLite error code
+** tdsqlite3changegroup_delete(). If an error occurs, an SQLite error code
** (i.e. SQLITE_NOMEM) is returned and *pp is set to NULL.
**
-** The usual usage pattern for an sqlite3_changegroup object is as follows:
+** The usual usage pattern for an tdsqlite3_changegroup object is as follows:
**
** <ul>
-** <li> It is created using a call to sqlite3changegroup_new().
+** <li> It is created using a call to tdsqlite3changegroup_new().
**
** <li> Zero or more changesets (or patchsets) are added to the object
-** by calling sqlite3changegroup_add().
+** by calling tdsqlite3changegroup_add().
**
** <li> The result of combining all input changesets together is obtained
-** by the application via a call to sqlite3changegroup_output().
+** by the application via a call to tdsqlite3changegroup_output().
**
-** <li> The object is deleted using a call to sqlite3changegroup_delete().
+** <li> The object is deleted using a call to tdsqlite3changegroup_delete().
** </ul>
**
** Any number of calls to add() and output() may be made between the calls to
** new() and delete(), and in any order.
**
-** As well as the regular sqlite3changegroup_add() and
-** sqlite3changegroup_output() functions, also available are the streaming
-** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm().
+** As well as the regular tdsqlite3changegroup_add() and
+** tdsqlite3changegroup_output() functions, also available are the streaming
+** versions tdsqlite3changegroup_add_strm() and tdsqlite3changegroup_output_strm().
*/
-int sqlite3changegroup_new(sqlite3_changegroup **pp);
+SQLITE_API int tdsqlite3changegroup_new(tdsqlite3_changegroup **pp);
/*
** CAPI3REF: Add A Changeset To A Changegroup
+** METHOD: tdsqlite3_changegroup
**
** Add all changes within the changeset (or patchset) in buffer pData (size
** nData bytes) to the changegroup.
@@ -9605,23 +11830,24 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp);
** case, this function fails with SQLITE_SCHEMA. If the input changeset
** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is
** returned. Or, if an out-of-memory condition occurs during processing, this
-** function returns SQLITE_NOMEM. In all cases, if an error occurs the
-** final contents of the changegroup is undefined.
+** function returns SQLITE_NOMEM. In all cases, if an error occurs the state
+** of the final contents of the changegroup is undefined.
**
** If no error occurs, SQLITE_OK is returned.
*/
-int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
+SQLITE_API int tdsqlite3changegroup_add(tdsqlite3_changegroup*, int nData, void *pData);
/*
** CAPI3REF: Obtain A Composite Changeset From A Changegroup
+** METHOD: tdsqlite3_changegroup
**
** Obtain a buffer containing a changeset (or patchset) representing the
** current contents of the changegroup. If the inputs to the changegroup
** were themselves changesets, the output is a changeset. Or, if the
** inputs were patchsets, the output is also a patchset.
**
-** As with the output of the sqlite3session_changeset() and
-** sqlite3session_patchset() functions, all changes related to a single
+** As with the output of the tdsqlite3session_changeset() and
+** tdsqlite3session_patchset() functions, all changes related to a single
** table are grouped together in the output of this function. Tables appear
** in the same order as for the very first changeset added to the changegroup.
** If the second or subsequent changesets added to the changegroup contain
@@ -9634,35 +11860,35 @@ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
** is returned and the output variables are set to the size of and a
** pointer to the output buffer, respectively. In this case it is the
** responsibility of the caller to eventually free the buffer using a
-** call to sqlite3_free().
+** call to tdsqlite3_free().
*/
-int sqlite3changegroup_output(
- sqlite3_changegroup*,
+SQLITE_API int tdsqlite3changegroup_output(
+ tdsqlite3_changegroup*,
int *pnData, /* OUT: Size of output buffer in bytes */
void **ppData /* OUT: Pointer to output buffer */
);
/*
** CAPI3REF: Delete A Changegroup Object
+** DESTRUCTOR: tdsqlite3_changegroup
*/
-void sqlite3changegroup_delete(sqlite3_changegroup*);
+SQLITE_API void tdsqlite3changegroup_delete(tdsqlite3_changegroup*);
/*
** CAPI3REF: Apply A Changeset To A Database
**
-** Apply a changeset to a database. This function attempts to update the
-** "main" database attached to handle db with the changes found in the
-** changeset passed via the second and third arguments.
+** Apply a changeset or patchset to a database. These functions attempt to
+** update the "main" database attached to handle db with the changes found in
+** the changeset passed via the second and third arguments.
**
-** The fourth argument (xFilter) passed to this function is the "filter
+** The fourth argument (xFilter) passed to these functions is the "filter
** callback". If it is not NULL, then for each table affected by at least one
** change in the changeset, the filter callback is invoked with
** the table name as the second argument, and a copy of the context pointer
-** passed as the sixth argument to this function as the first. If the "filter
-** callback" returns zero, then no attempt is made to apply any changes to
-** the table. Otherwise, if the return value is non-zero or the xFilter
-** argument to this function is NULL, all changes related to the table are
-** attempted.
+** passed as the sixth argument as the first. If the "filter callback"
+** returns zero, then no attempt is made to apply any changes to the table.
+** Otherwise, if the return value is non-zero or the xFilter argument to
+** is NULL, all changes related to the table are attempted.
**
** For each table that is not excluded by the filter callback, this function
** tests that the target database contains a compatible table. A table is
@@ -9671,7 +11897,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** <ul>
** <li> The table has the same name as the name recorded in the
** changeset, and
-** <li> The table has the same number of columns as recorded in the
+** <li> The table has at least as many columns as recorded in the
** changeset, and
** <li> The table has primary key columns in the same position as
** recorded in the changeset.
@@ -9679,13 +11905,13 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** If there is no compatible table, it is not an error, but none of the
** changes associated with the table are applied. A warning message is issued
-** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
+** via the tdsqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
** one such warning is issued for each table in the changeset.
**
** For each change for which there is a compatible table, an attempt is made
** to modify the table contents according to the UPDATE, INSERT or DELETE
** change. If a change cannot be applied cleanly, the conflict handler
-** function passed as the fifth argument to sqlite3changeset_apply() may be
+** function passed as the fifth argument to tdsqlite3changeset_apply() may be
** invoked. A description of exactly when the conflict handler is invoked for
** each type of change is below.
**
@@ -9699,15 +11925,15 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** if the second argument passed to the conflict handler is either
** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler
** returns an illegal value, any changes already made are rolled back and
-** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different
-** actions are taken by sqlite3changeset_apply() depending on the value
+** the call to tdsqlite3changeset_apply() returns SQLITE_MISUSE. Different
+** actions are taken by tdsqlite3changeset_apply() depending on the value
** returned by each invocation of the conflict-handler function. Refer to
** the documentation for the three
** [SQLITE_CHANGESET_OMIT|available return values] for details.
**
** <dl>
** <dt>DELETE Changes<dd>
-** For each DELETE change, this function checks if the target database
+** For each DELETE change, the function checks if the target database
** contains a row with the same primary key value (or values) as the
** original row values stored in the changeset. If it does, and the values
** stored in all non-primary key columns also match the values stored in
@@ -9716,7 +11942,11 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** If a row with matching primary key values is found, but one or more of
** the non-primary key fields contains a value different from the original
** row value stored in the changeset, the conflict-handler function is
-** invoked with [SQLITE_CHANGESET_DATA] as the second argument.
+** invoked with [SQLITE_CHANGESET_DATA] as the second argument. If the
+** database table has more columns than are recorded in the changeset,
+** only the values of those non-primary key fields are compared against
+** the current database contents - any trailing database table columns
+** are ignored.
**
** If no row with matching primary key values is found in the database,
** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND]
@@ -9731,7 +11961,9 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** <dt>INSERT Changes<dd>
** For each INSERT change, an attempt is made to insert the new row into
-** the database.
+** the database. If the changeset row contains fewer fields than the
+** database table, the trailing fields are populated with their default
+** values.
**
** If the attempt to insert the row fails because the database already
** contains a row with the same primary key values, the conflict handler
@@ -9746,16 +11978,16 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** [SQLITE_CHANGESET_REPLACE].
**
** <dt>UPDATE Changes<dd>
-** For each UPDATE change, this function checks if the target database
+** For each UPDATE change, the function checks if the target database
** contains a row with the same primary key value (or values) as the
** original row values stored in the changeset. If it does, and the values
-** stored in all non-primary key columns also match the values stored in
-** the changeset the row is updated within the target database.
+** stored in all modified non-primary key columns also match the values
+** stored in the changeset the row is updated within the target database.
**
** If a row with matching primary key values is found, but one or more of
-** the non-primary key fields contains a value different from an original
-** row value stored in the changeset, the conflict-handler function is
-** invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since
+** the modified non-primary key fields contains a value different from an
+** original row value stored in the changeset, the conflict-handler function
+** is invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since
** UPDATE changes only contain values for non-primary key fields that are
** to be modified, only those fields need to match the original values to
** avoid the SQLITE_CHANGESET_DATA conflict-handler callback.
@@ -9774,17 +12006,34 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** It is safe to execute SQL statements, including those that write to the
** table that the callback related to, from within the xConflict callback.
-** This can be used to further customize the applications conflict
+** This can be used to further customize the application's conflict
** resolution strategy.
**
-** All changes made by this function are enclosed in a savepoint transaction.
+** All changes made by these functions are enclosed in a savepoint transaction.
** If any other error (aside from a constraint failure when attempting to
** write to the target database) occurs, then the savepoint transaction is
** rolled back, restoring the target database to its original state, and an
** SQLite error code returned.
-*/
-int sqlite3changeset_apply(
- sqlite3 *db, /* Apply change to "main" db of this handle */
+**
+** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
+** the input is a changeset (not a patchset), then tdsqlite3changeset_apply_v2()
+** may set (*ppRebase) to point to a "rebase" that may be used with the
+** tdsqlite3_rebaser APIs buffer before returning. In this case (*pnRebase)
+** is set to the size of the buffer in bytes. It is the responsibility of the
+** caller to eventually free any such buffer using tdsqlite3_free(). The buffer
+** is only allocated and populated if one or more conflicts were encountered
+** while applying the patchset. See comments surrounding the tdsqlite3_rebaser
+** APIs for further details.
+**
+** The behavior of tdsqlite3changeset_apply_v2() and its streaming equivalent
+** may be modified by passing a combination of
+** [SQLITE_CHANGESETAPPLY_NOSAVEPOINT | supported flags] as the 9th parameter.
+**
+** Note that the tdsqlite3changeset_apply_v2() API is still <b>experimental</b>
+** and therefore subject to change.
+*/
+SQLITE_API int tdsqlite3changeset_apply(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset, /* Changeset blob */
int(*xFilter)(
@@ -9794,10 +12043,51 @@ int sqlite3changeset_apply(
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
- sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx /* First argument passed to xConflict */
);
+SQLITE_API int tdsqlite3changeset_apply_v2(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ int nChangeset, /* Size of changeset in bytes */
+ void *pChangeset, /* Changeset blob */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase, /* OUT: Rebase data */
+ int flags /* SESSION_CHANGESETAPPLY_* flags */
+);
+
+/*
+** CAPI3REF: Flags for tdsqlite3changeset_apply_v2
+**
+** The following flags may passed via the 9th parameter to
+** [tdsqlite3changeset_apply_v2] and [tdsqlite3changeset_apply_v2_strm]:
+**
+** <dl>
+** <dt>SQLITE_CHANGESETAPPLY_NOSAVEPOINT <dd>
+** Usually, the sessions module encloses all operations performed by
+** a single call to apply_v2() or apply_v2_strm() in a [SAVEPOINT]. The
+** SAVEPOINT is committed if the changeset or patchset is successfully
+** applied, or rolled back if an error occurs. Specifying this flag
+** causes the sessions module to omit this savepoint. In this case, if the
+** caller has an open transaction or savepoint when apply_v2() is called,
+** it may revert the partially applied changeset by rolling it back.
+**
+** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
+** Invert the changeset before applying it. This is equivalent to inverting
+** a changeset using tdsqlite3changeset_invert() before applying it. It is
+** an error to specify this flag with a patchset.
+*/
+#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
+#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
/*
** CAPI3REF: Constants Passed To The Conflict Handler
@@ -9821,7 +12111,7 @@ int sqlite3changeset_apply(
** required PRIMARY KEY fields is not present in the database.
**
** There is no conflicting row in this case. The results of invoking the
-** sqlite3changeset_conflict() API are undefined.
+** tdsqlite3changeset_conflict() API are undefined.
**
** <dt>SQLITE_CHANGESET_CONFLICT<dd>
** CHANGESET_CONFLICT is passed as the second argument to the conflict
@@ -9841,8 +12131,8 @@ int sqlite3changeset_apply(
** CHANGESET_ABORT, the changeset is rolled back.
**
** No current or conflicting row information is provided. The only function
-** it is possible to call on the supplied sqlite3_changeset_iter handle
-** is sqlite3changeset_fk_conflicts().
+** it is possible to call on the supplied tdsqlite3_changeset_iter handle
+** is tdsqlite3changeset_fk_conflicts().
**
** <dt>SQLITE_CHANGESET_CONSTRAINT<dd>
** If any other constraint violation occurs while applying a change (i.e.
@@ -9850,7 +12140,7 @@ int sqlite3changeset_apply(
** invoked with CHANGESET_CONSTRAINT as the second argument.
**
** There is no conflicting row in this case. The results of invoking the
-** sqlite3changeset_conflict() API are undefined.
+** tdsqlite3changeset_conflict() API are undefined.
**
** </dl>
*/
@@ -9875,7 +12165,7 @@ int sqlite3changeset_apply(
** This value may only be returned if the second argument to the conflict
** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this
** is not the case, any changes applied so far are rolled back and the
-** call to sqlite3changeset_apply() returns SQLITE_MISUSE.
+** call to tdsqlite3changeset_apply() returns SQLITE_MISUSE.
**
** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict
** handler, then the conflicting row is either updated or deleted, depending
@@ -9888,13 +12178,168 @@ int sqlite3changeset_apply(
**
** <dt>SQLITE_CHANGESET_ABORT<dd>
** If this value is returned, any changes applied so far are rolled back
-** and the call to sqlite3changeset_apply() returns SQLITE_ABORT.
+** and the call to tdsqlite3changeset_apply() returns SQLITE_ABORT.
** </dl>
*/
#define SQLITE_CHANGESET_OMIT 0
#define SQLITE_CHANGESET_REPLACE 1
#define SQLITE_CHANGESET_ABORT 2
+/*
+** CAPI3REF: Rebasing changesets
+** EXPERIMENTAL
+**
+** Suppose there is a site hosting a database in state S0. And that
+** modifications are made that move that database to state S1 and a
+** changeset recorded (the "local" changeset). Then, a changeset based
+** on S0 is received from another site (the "remote" changeset) and
+** applied to the database. The database is then in state
+** (S1+"remote"), where the exact state depends on any conflict
+** resolution decisions (OMIT or REPLACE) made while applying "remote".
+** Rebasing a changeset is to update it to take those conflict
+** resolution decisions into account, so that the same conflicts
+** do not have to be resolved elsewhere in the network.
+**
+** For example, if both the local and remote changesets contain an
+** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)":
+**
+** local: INSERT INTO t1 VALUES(1, 'v1');
+** remote: INSERT INTO t1 VALUES(1, 'v2');
+**
+** and the conflict resolution is REPLACE, then the INSERT change is
+** removed from the local changeset (it was overridden). Or, if the
+** conflict resolution was "OMIT", then the local changeset is modified
+** to instead contain:
+**
+** UPDATE t1 SET b = 'v2' WHERE a=1;
+**
+** Changes within the local changeset are rebased as follows:
+**
+** <dl>
+** <dt>Local INSERT<dd>
+** This may only conflict with a remote INSERT. If the conflict
+** resolution was OMIT, then add an UPDATE change to the rebased
+** changeset. Or, if the conflict resolution was REPLACE, add
+** nothing to the rebased changeset.
+**
+** <dt>Local DELETE<dd>
+** This may conflict with a remote UPDATE or DELETE. In both cases the
+** only possible resolution is OMIT. If the remote operation was a
+** DELETE, then add no change to the rebased changeset. If the remote
+** operation was an UPDATE, then the old.* fields of change are updated
+** to reflect the new.* values in the UPDATE.
+**
+** <dt>Local UPDATE<dd>
+** This may conflict with a remote UPDATE or DELETE. If it conflicts
+** with a DELETE, and the conflict resolution was OMIT, then the update
+** is changed into an INSERT. Any undefined values in the new.* record
+** from the update change are filled in using the old.* values from
+** the conflicting DELETE. Or, if the conflict resolution was REPLACE,
+** the UPDATE change is simply omitted from the rebased changeset.
+**
+** If conflict is with a remote UPDATE and the resolution is OMIT, then
+** the old.* values are rebased using the new.* values in the remote
+** change. Or, if the resolution is REPLACE, then the change is copied
+** into the rebased changeset with updates to columns also updated by
+** the conflicting remote UPDATE removed. If this means no columns would
+** be updated, the change is omitted.
+** </dl>
+**
+** A local change may be rebased against multiple remote changes
+** simultaneously. If a single key is modified by multiple remote
+** changesets, they are combined as follows before the local changeset
+** is rebased:
+**
+** <ul>
+** <li> If there has been one or more REPLACE resolutions on a
+** key, it is rebased according to a REPLACE.
+**
+** <li> If there have been no REPLACE resolutions on a key, then
+** the local changeset is rebased according to the most recent
+** of the OMIT resolutions.
+** </ul>
+**
+** Note that conflict resolutions from multiple remote changesets are
+** combined on a per-field basis, not per-row. This means that in the
+** case of multiple remote UPDATE operations, some fields of a single
+** local change may be rebased for REPLACE while others are rebased for
+** OMIT.
+**
+** In order to rebase a local changeset, the remote changeset must first
+** be applied to the local database using tdsqlite3changeset_apply_v2() and
+** the buffer of rebase information captured. Then:
+**
+** <ol>
+** <li> An tdsqlite3_rebaser object is created by calling
+** tdsqlite3rebaser_create().
+** <li> The new object is configured with the rebase buffer obtained from
+** tdsqlite3changeset_apply_v2() by calling tdsqlite3rebaser_configure().
+** If the local changeset is to be rebased against multiple remote
+** changesets, then tdsqlite3rebaser_configure() should be called
+** multiple times, in the same order that the multiple
+** tdsqlite3changeset_apply_v2() calls were made.
+** <li> Each local changeset is rebased by calling tdsqlite3rebaser_rebase().
+** <li> The tdsqlite3_rebaser object is deleted by calling
+** tdsqlite3rebaser_delete().
+** </ol>
+*/
+typedef struct tdsqlite3_rebaser tdsqlite3_rebaser;
+
+/*
+** CAPI3REF: Create a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Allocate a new changeset rebaser object. If successful, set (*ppNew) to
+** point to the new object and return SQLITE_OK. Otherwise, if an error
+** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew)
+** to NULL.
+*/
+SQLITE_API int tdsqlite3rebaser_create(tdsqlite3_rebaser **ppNew);
+
+/*
+** CAPI3REF: Configure a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Configure the changeset rebaser object to rebase changesets according
+** to the conflict resolutions described by buffer pRebase (size nRebase
+** bytes), which must have been obtained from a previous call to
+** tdsqlite3changeset_apply_v2().
+*/
+SQLITE_API int tdsqlite3rebaser_configure(
+ tdsqlite3_rebaser*,
+ int nRebase, const void *pRebase
+);
+
+/*
+** CAPI3REF: Rebase a changeset
+** EXPERIMENTAL
+**
+** Argument pIn must point to a buffer containing a changeset nIn bytes
+** in size. This function allocates and populates a buffer with a copy
+** of the changeset rebased according to the configuration of the
+** rebaser object passed as the first argument. If successful, (*ppOut)
+** is set to point to the new buffer containing the rebased changeset and
+** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the
+** responsibility of the caller to eventually free the new buffer using
+** tdsqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut)
+** are set to zero and an SQLite error code returned.
+*/
+SQLITE_API int tdsqlite3rebaser_rebase(
+ tdsqlite3_rebaser*,
+ int nIn, const void *pIn,
+ int *pnOut, void **ppOut
+);
+
+/*
+** CAPI3REF: Delete a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Delete the changeset rebaser object and all associated resources. There
+** should be one call to this function for each successful invocation
+** of tdsqlite3rebaser_create().
+*/
+SQLITE_API void tdsqlite3rebaser_delete(tdsqlite3_rebaser *p);
+
/*
** CAPI3REF: Streaming Versions of API functions.
**
@@ -9903,18 +12348,19 @@ int sqlite3changeset_apply(
**
** <table border=1 style="margin-left:8ex;margin-right:8ex">
** <tr><th>Streaming function<th>Non-streaming equivalent</th>
-** <tr><td>sqlite3changeset_apply_str<td>[sqlite3changeset_apply]
-** <tr><td>sqlite3changeset_concat_str<td>[sqlite3changeset_concat]
-** <tr><td>sqlite3changeset_invert_str<td>[sqlite3changeset_invert]
-** <tr><td>sqlite3changeset_start_str<td>[sqlite3changeset_start]
-** <tr><td>sqlite3session_changeset_str<td>[sqlite3session_changeset]
-** <tr><td>sqlite3session_patchset_str<td>[sqlite3session_patchset]
+** <tr><td>tdsqlite3changeset_apply_strm<td>[tdsqlite3changeset_apply]
+** <tr><td>tdsqlite3changeset_apply_strm_v2<td>[tdsqlite3changeset_apply_v2]
+** <tr><td>tdsqlite3changeset_concat_strm<td>[tdsqlite3changeset_concat]
+** <tr><td>tdsqlite3changeset_invert_strm<td>[tdsqlite3changeset_invert]
+** <tr><td>tdsqlite3changeset_start_strm<td>[tdsqlite3changeset_start]
+** <tr><td>tdsqlite3session_changeset_strm<td>[tdsqlite3session_changeset]
+** <tr><td>tdsqlite3session_patchset_strm<td>[tdsqlite3session_patchset]
** </table>
**
** Non-streaming functions that accept changesets (or patchsets) as input
** require that the entire changeset be stored in a single buffer in memory.
** Similarly, those that return a changeset or patchset do so by returning
-** a pointer to a single large buffer allocated using sqlite3_malloc().
+** a pointer to a single large buffer allocated using tdsqlite3_malloc().
** Normally this is convenient. However, if an application running in a
** low-memory environment is required to handle very large changesets, the
** large contiguous memory allocations required can become onerous.
@@ -9947,7 +12393,7 @@ int sqlite3changeset_apply(
** an error, all processing is abandoned and the streaming API function
** returns a copy of the error code to the caller.
**
-** In the case of sqlite3changeset_start_strm(), the xInput callback may be
+** In the case of tdsqlite3changeset_start_strm(), the xInput callback may be
** invoked by the sessions module at any point during the lifetime of the
** iterator. If such an xInput callback returns an error, the iterator enters
** an error state, whereby all subsequent calls to iterator functions
@@ -9984,8 +12430,8 @@ int sqlite3changeset_apply(
** parameter set to a value less than or equal to zero. Other than this,
** no guarantees are made as to the size of the chunks of data returned.
*/
-int sqlite3changeset_apply_strm(
- sqlite3 *db, /* Apply change to "main" db of this handle */
+SQLITE_API int tdsqlite3changeset_apply_strm(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
int(*xFilter)(
@@ -9995,11 +12441,28 @@ int sqlite3changeset_apply_strm(
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
- sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx /* First argument passed to xConflict */
);
-int sqlite3changeset_concat_strm(
+SQLITE_API int tdsqlite3changeset_apply_v2_strm(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
+ void *pIn, /* First arg for xInput */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase,
+ int flags
+);
+SQLITE_API int tdsqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
int (*xInputB)(void *pIn, void *pData, int *pnData),
@@ -10007,36 +12470,88 @@ int sqlite3changeset_concat_strm(
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3changeset_invert_strm(
+SQLITE_API int tdsqlite3changeset_invert_strm(
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3changeset_start_strm(
- sqlite3_changeset_iter **pp,
+SQLITE_API int tdsqlite3changeset_start_strm(
+ tdsqlite3_changeset_iter **pp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
);
-int sqlite3session_changeset_strm(
- sqlite3_session *pSession,
+SQLITE_API int tdsqlite3changeset_start_v2_strm(
+ tdsqlite3_changeset_iter **pp,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int flags
+);
+SQLITE_API int tdsqlite3session_changeset_strm(
+ tdsqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3session_patchset_strm(
- sqlite3_session *pSession,
+SQLITE_API int tdsqlite3session_patchset_strm(
+ tdsqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3changegroup_add_strm(sqlite3_changegroup*,
+SQLITE_API int tdsqlite3changegroup_add_strm(tdsqlite3_changegroup*,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
);
-int sqlite3changegroup_output_strm(sqlite3_changegroup*,
+SQLITE_API int tdsqlite3changegroup_output_strm(tdsqlite3_changegroup*,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
+SQLITE_API int tdsqlite3rebaser_rebase_strm(
+ tdsqlite3_rebaser *pRebaser,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut
+);
+/*
+** CAPI3REF: Configure global parameters
+**
+** The tdsqlite3session_config() interface is used to make global configuration
+** changes to the sessions module in order to tune it to the specific needs
+** of the application.
+**
+** The tdsqlite3session_config() interface is not threadsafe. If it is invoked
+** while any other thread is inside any other sessions method then the
+** results are undefined. Furthermore, if it is invoked after any sessions
+** related objects have been created, the results are also undefined.
+**
+** The first argument to the tdsqlite3session_config() function must be one
+** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The
+** interpretation of the (void*) value passed as the second parameter and
+** the effect of calling this function depends on the value of the first
+** parameter.
+**
+** <dl>
+** <dt>SQLITE_SESSION_CONFIG_STRMSIZE<dd>
+** By default, the sessions module streaming interfaces attempt to input
+** and output data in approximately 1 KiB chunks. This operand may be used
+** to set and query the value of this configuration setting. The pointer
+** passed as the second argument must point to a value of type (int).
+** If this value is greater than 0, it is used as the new streaming data
+** chunk size for both input and output. Before returning, the (int) value
+** pointed to by pArg is set to the final value of the streaming interface
+** chunk size.
+** </dl>
+**
+** This function returns SQLITE_OK if successful, or an SQLite error code
+** otherwise.
+*/
+SQLITE_API int tdsqlite3session_config(int op, void *pArg);
+
+/*
+** CAPI3REF: Values for tdsqlite3session_config().
+*/
+#define SQLITE_SESSION_CONFIG_STRMSIZE 1
/*
** Make sure we can call this stuff from C++.
@@ -10047,7 +12562,7 @@ int sqlite3changegroup_output_strm(sqlite3_changegroup*,
#endif /* !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) */
-/******** End of sqlite3session.h *********/
+/******** End of tdsqlite3session.h *********/
/******** Begin file fts5.h *********/
/*
** 2014 May 31
@@ -10081,7 +12596,7 @@ extern "C" {
** CUSTOM AUXILIARY FUNCTIONS
**
** Virtual table implementations may overload SQL functions by implementing
-** the sqlite3_module.xFindFunction() method.
+** the tdsqlite3_module.xFindFunction() method.
*/
typedef struct Fts5ExtensionApi Fts5ExtensionApi;
@@ -10091,9 +12606,9 @@ typedef struct Fts5PhraseIter Fts5PhraseIter;
typedef void (*fts5_extension_function)(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
- sqlite3_context *pCtx, /* Context for returning result/error */
+ tdsqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
- sqlite3_value **apVal /* Array of trailing arguments */
+ tdsqlite3_value **apVal /* Array of trailing arguments */
);
struct Fts5PhraseIter {
@@ -10170,12 +12685,8 @@ struct Fts5PhraseIter {
**
** Usually, output parameter *piPhrase is set to the phrase number, *piCol
** to the column in which it occurs and *piOff the token offset of the
-** first token of the phrase. The exception is if the table was created
-** with the offsets=0 option specified. In this case *piOff is always
-** set to -1.
-**
-** Returns SQLITE_OK if successful, or an error code (i.e. SQLITE_NOMEM)
-** if an error occurs.
+** first token of the phrase. Returns SQLITE_OK if successful, or an error
+** code (i.e. SQLITE_NOMEM) if an error occurs.
**
** This API can be quite slow if used with an FTS5 table created with the
** "detail=none" or "detail=column" option.
@@ -10213,10 +12724,10 @@ struct Fts5PhraseIter {
**
** xSetAuxdata(pFts5, pAux, xDelete)
**
-** Save the pointer passed as the second argument as the extension functions
+** Save the pointer passed as the second argument as the extension function's
** "auxiliary data". The pointer may then be retrieved by the current or any
** future invocation of the same fts5 extension function made as part of
-** of the same MATCH query using the xGetAuxdata() API.
+** the same MATCH query using the xGetAuxdata() API.
**
** Each extension function is allocated a single auxiliary data slot for
** each FTS query (MATCH expression). If the extension function is invoked
@@ -10231,7 +12742,7 @@ struct Fts5PhraseIter {
** The xDelete callback, if one is specified, is also invoked on the
** auxiliary data pointer after the FTS5 query has finished.
**
-** If an error (e.g. an OOM condition) occurs within this function, an
+** If an error (e.g. an OOM condition) occurs within this function,
** the auxiliary data is set to NULL and an error code returned. If the
** xDelete parameter was not NULL, it is invoked on the auxiliary data
** pointer before returning.
@@ -10322,8 +12833,8 @@ struct Fts5ExtensionApi {
void *(*xUserData)(Fts5Context*);
int (*xColumnCount)(Fts5Context*);
- int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow);
- int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken);
+ int (*xRowCount)(Fts5Context*, tdsqlite3_int64 *pnRow);
+ int (*xColumnTotalSize)(Fts5Context*, int iCol, tdsqlite3_int64 *pnToken);
int (*xTokenize)(Fts5Context*,
const char *pText, int nText, /* Text to tokenize */
@@ -10337,7 +12848,7 @@ struct Fts5ExtensionApi {
int (*xInstCount)(Fts5Context*, int *pnInst);
int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff);
- sqlite3_int64 (*xRowid)(Fts5Context*);
+ tdsqlite3_int64 (*xRowid)(Fts5Context*);
int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn);
int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken);
@@ -10455,8 +12966,8 @@ struct Fts5ExtensionApi {
**
** There are several ways to approach this in FTS5:
**
-** <ol><li> By mapping all synonyms to a single token. In this case, the
-** In the above example, this means that the tokenizer returns the
+** <ol><li> By mapping all synonyms to a single token. In this case, using
+** the above example, this means that the tokenizer returns the
** same token for inputs "first" and "1st". Say that token is in
** fact "first", so that when the user inserts the document "I won
** 1st place" entries are added to the index for tokens "i", "won",
@@ -10464,11 +12975,11 @@ struct Fts5ExtensionApi {
** the tokenizer substitutes "first" for "1st" and the query works
** as expected.
**
-** <li> By adding multiple synonyms for a single term to the FTS index.
-** In this case, when tokenizing query text, the tokenizer may
-** provide multiple synonyms for a single term within the document.
-** FTS5 then queries the index for each synonym individually. For
-** example, faced with the query:
+** <li> By querying the index for all synonyms of each query term
+** separately. In this case, when tokenizing query text, the
+** tokenizer may provide multiple synonyms for a single term
+** within the document. FTS5 then queries the index for each
+** synonym individually. For example, faced with the query:
**
** <codeblock>
** ... MATCH 'first place'</codeblock>
@@ -10492,9 +13003,9 @@ struct Fts5ExtensionApi {
** "place".
**
** This way, even if the tokenizer does not provide synonyms
-** when tokenizing query text (it should not - to do would be
+** when tokenizing query text (it should not - to do so would be
** inefficient), it doesn't matter if the user queries for
-** 'first + place' or '1st + place', as there are entires in the
+** 'first + place' or '1st + place', as there are entries in the
** FTS index corresponding to both forms of the first token.
** </ol>
**
@@ -10522,7 +13033,7 @@ struct Fts5ExtensionApi {
** extra data to the FTS index or require FTS5 to query for multiple terms,
** so it is efficient in terms of disk space and query speed. However, it
** does not support prefix queries very well. If, as suggested above, the
-** token "first" is subsituted for "1st" by the tokenizer, then the query:
+** token "first" is substituted for "1st" by the tokenizer, then the query:
**
** <codeblock>
** ... MATCH '1s*'</codeblock>
@@ -10637,8 +13148,9 @@ struct fts5_api {
** Include the configuration header output by 'configure' if we're using the
** autoconf-based build
*/
-#ifdef _HAVE_SQLITE_CONFIG_H
-#include "config.h"
+#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H)
+/* #include "config.h" */
+#define SQLITECONFIG_H 1
#endif
/************** Include sqliteLimit.h in the middle of sqliteInt.h ***********/
@@ -10732,7 +13244,7 @@ struct fts5_api {
** Not currently enforced.
*/
#ifndef SQLITE_MAX_VDBE_OP
-# define SQLITE_MAX_VDBE_OP 25000
+# define SQLITE_MAX_VDBE_OP 250000000
#endif
/*
@@ -10893,15 +13405,15 @@ struct fts5_api {
** So we have to define the macros in different ways depending on the
** compiler.
*/
-#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */
+#if defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */
+# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X))
+# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X))
+#elif defined(__PTRDIFF_TYPE__) /* This case should work for GCC */
# define SQLITE_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X))
# define SQLITE_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X))
#elif !defined(__GNUC__) /* Works for compilers other than LLVM */
# define SQLITE_INT_TO_PTR(X) ((void*)&((char*)0)[X])
# define SQLITE_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0))
-#elif defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */
-# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X))
-# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X))
#else /* Generates a warning - but it always works */
# define SQLITE_INT_TO_PTR(X) ((void*)(X))
# define SQLITE_PTR_TO_INT(X) ((int)(X))
@@ -10930,6 +13442,7 @@ struct fts5_api {
# include <intrin.h>
# pragma intrinsic(_byteswap_ushort)
# pragma intrinsic(_byteswap_ulong)
+# pragma intrinsic(_byteswap_uint64)
# pragma intrinsic(_ReadWriteBarrier)
# else
# include <cmnintrin.h>
@@ -10947,6 +13460,11 @@ struct fts5_api {
**
** Older versions of SQLite used an optional THREADSAFE macro.
** We support that for legacy.
+**
+** To ensure that the correct value of "THREADSAFE" is reported when querying
+** for compile-time options at runtime (e.g. "PRAGMA compile_options"), this
+** logic is partially replicated in ctime.c. If it is updated here, it should
+** also be updated there.
*/
#if !defined(SQLITE_THREADSAFE)
# if defined(THREADSAFE)
@@ -11064,8 +13582,8 @@ struct fts5_api {
**
*/
#ifdef SQLITE_COVERAGE_TEST
-SQLITE_PRIVATE void sqlite3Coverage(int);
-# define testcase(X) if( X ){ sqlite3Coverage(__LINE__); }
+SQLITE_PRIVATE void tdsqlite3Coverage(int);
+# define testcase(X) if( X ){ tdsqlite3Coverage(__LINE__); }
#else
# define testcase(X)
#endif
@@ -11122,6 +13640,41 @@ SQLITE_PRIVATE void sqlite3Coverage(int);
#endif
/*
+** The harmless(X) macro indicates that expression X is usually false
+** but can be true without causing any problems, but we don't know of
+** any way to cause X to be true.
+**
+** In debugging and testing builds, this macro will abort if X is ever
+** true. In this way, developers are alerted to a possible test case
+** that causes X to be true. If a harmless macro ever fails, that is
+** an opportunity to change the macro into a testcase() and add a new
+** test case to the test suite.
+**
+** For normal production builds, harmless(X) is a no-op, since it does
+** not matter whether expression X is true or false.
+*/
+#ifdef SQLITE_DEBUG
+# define harmless(X) assert(!(X));
+#else
+# define harmless(X)
+#endif
+
+/*
+** Some conditionals are optimizations only. In other words, if the
+** conditionals are replaced with a constant 1 (true) or 0 (false) then
+** the correct answer is still obtained, though perhaps not as quickly.
+**
+** The following macros mark these optimizations conditionals.
+*/
+#if defined(SQLITE_MUTATION_TEST)
+# define OK_IF_ALWAYS_TRUE(X) (1)
+# define OK_IF_ALWAYS_FALSE(X) (0)
+#else
+# define OK_IF_ALWAYS_TRUE(X) (X)
+# define OK_IF_ALWAYS_FALSE(X) (X)
+#endif
+
+/*
** Some malloc failures are only possible if SQLITE_TEST_REALLOC_STRESS is
** defined. We need to defend against those failures when testing with
** SQLITE_TEST_REALLOC_STRESS, but we don't want the unreachable branches
@@ -11141,8 +13694,8 @@ SQLITE_PRIVATE void sqlite3Coverage(int);
*/
#if defined(SQLITE_FORCE_OS_TRACE) || defined(SQLITE_TEST) || \
(defined(SQLITE_DEBUG) && SQLITE_OS_WIN)
- extern int sqlite3OSTrace;
-# define OSTRACE(X) if( sqlite3OSTrace ) sqlite3DebugPrintf X
+ extern int tdsqlite3OSTrace;
+# define OSTRACE(X) if( tdsqlite3OSTrace ) tdsqlite3DebugPrintf X
# define SQLITE_HAVE_OS_TRACE
#else
# define OSTRACE(X)
@@ -11150,7 +13703,7 @@ SQLITE_PRIVATE void sqlite3Coverage(int);
#endif
/*
-** Is the sqlite3ErrName() function needed in the build? Currently,
+** Is the tdsqlite3ErrName() function needed in the build? Currently,
** it is needed by "mutex_w32.c" (when debugging), "os_win.c" (when
** OSTRACE is enabled), and by several "test*.c" files (which are
** compiled using SQLITE_TEST).
@@ -11235,7 +13788,7 @@ struct Hash {
unsigned int count; /* Number of entries in this table */
HashElem *first; /* The first element of the array */
struct _ht { /* the hash table */
- int count; /* Number of entries with this hash */
+ unsigned int count; /* Number of entries with this hash */
HashElem *chain; /* Pointer to first entry with this hash */
} *ht;
};
@@ -11255,10 +13808,10 @@ struct HashElem {
/*
** Access routines. To delete, insert a NULL pointer.
*/
-SQLITE_PRIVATE void sqlite3HashInit(Hash*);
-SQLITE_PRIVATE void *sqlite3HashInsert(Hash*, const char *pKey, void *pData);
-SQLITE_PRIVATE void *sqlite3HashFind(const Hash*, const char *pKey);
-SQLITE_PRIVATE void sqlite3HashClear(Hash*);
+SQLITE_PRIVATE void tdsqlite3HashInit(Hash*);
+SQLITE_PRIVATE void *tdsqlite3HashInsert(Hash*, const char *pKey, void *pData);
+SQLITE_PRIVATE void *tdsqlite3HashFind(const Hash*, const char *pKey);
+SQLITE_PRIVATE void tdsqlite3HashClear(Hash*);
/*
** Macros for looping over all elements of a hash table. The idiom is
@@ -11315,150 +13868,160 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*);
#define TK_AS 24
#define TK_WITHOUT 25
#define TK_COMMA 26
-#define TK_OR 27
-#define TK_AND 28
-#define TK_IS 29
-#define TK_MATCH 30
-#define TK_LIKE_KW 31
-#define TK_BETWEEN 32
-#define TK_IN 33
-#define TK_ISNULL 34
-#define TK_NOTNULL 35
-#define TK_NE 36
-#define TK_EQ 37
-#define TK_GT 38
-#define TK_LE 39
-#define TK_LT 40
-#define TK_GE 41
-#define TK_ESCAPE 42
-#define TK_BITAND 43
-#define TK_BITOR 44
-#define TK_LSHIFT 45
-#define TK_RSHIFT 46
-#define TK_PLUS 47
-#define TK_MINUS 48
-#define TK_STAR 49
-#define TK_SLASH 50
-#define TK_REM 51
-#define TK_CONCAT 52
-#define TK_COLLATE 53
-#define TK_BITNOT 54
-#define TK_ID 55
-#define TK_INDEXED 56
-#define TK_ABORT 57
-#define TK_ACTION 58
-#define TK_AFTER 59
-#define TK_ANALYZE 60
-#define TK_ASC 61
-#define TK_ATTACH 62
-#define TK_BEFORE 63
-#define TK_BY 64
-#define TK_CASCADE 65
-#define TK_CAST 66
-#define TK_COLUMNKW 67
-#define TK_CONFLICT 68
-#define TK_DATABASE 69
-#define TK_DESC 70
-#define TK_DETACH 71
-#define TK_EACH 72
-#define TK_FAIL 73
-#define TK_FOR 74
-#define TK_IGNORE 75
-#define TK_INITIALLY 76
-#define TK_INSTEAD 77
-#define TK_NO 78
-#define TK_KEY 79
-#define TK_OF 80
-#define TK_OFFSET 81
-#define TK_PRAGMA 82
-#define TK_RAISE 83
-#define TK_RECURSIVE 84
-#define TK_REPLACE 85
-#define TK_RESTRICT 86
-#define TK_ROW 87
-#define TK_TRIGGER 88
-#define TK_VACUUM 89
-#define TK_VIEW 90
-#define TK_VIRTUAL 91
-#define TK_WITH 92
-#define TK_REINDEX 93
-#define TK_RENAME 94
-#define TK_CTIME_KW 95
-#define TK_ANY 96
-#define TK_STRING 97
-#define TK_JOIN_KW 98
-#define TK_CONSTRAINT 99
-#define TK_DEFAULT 100
-#define TK_NULL 101
-#define TK_PRIMARY 102
-#define TK_UNIQUE 103
-#define TK_CHECK 104
-#define TK_REFERENCES 105
-#define TK_AUTOINCR 106
-#define TK_ON 107
-#define TK_INSERT 108
-#define TK_DELETE 109
-#define TK_UPDATE 110
-#define TK_SET 111
-#define TK_DEFERRABLE 112
-#define TK_FOREIGN 113
-#define TK_DROP 114
-#define TK_UNION 115
-#define TK_ALL 116
-#define TK_EXCEPT 117
-#define TK_INTERSECT 118
-#define TK_SELECT 119
-#define TK_VALUES 120
-#define TK_DISTINCT 121
-#define TK_DOT 122
-#define TK_FROM 123
-#define TK_JOIN 124
-#define TK_USING 125
-#define TK_ORDER 126
-#define TK_GROUP 127
-#define TK_HAVING 128
-#define TK_LIMIT 129
-#define TK_WHERE 130
-#define TK_INTO 131
-#define TK_FLOAT 132
-#define TK_BLOB 133
-#define TK_INTEGER 134
-#define TK_VARIABLE 135
-#define TK_CASE 136
-#define TK_WHEN 137
-#define TK_THEN 138
-#define TK_ELSE 139
-#define TK_INDEX 140
-#define TK_ALTER 141
-#define TK_ADD 142
-#define TK_TO_TEXT 143
-#define TK_TO_BLOB 144
-#define TK_TO_NUMERIC 145
-#define TK_TO_INT 146
-#define TK_TO_REAL 147
-#define TK_ISNOT 148
-#define TK_END_OF_FILE 149
-#define TK_UNCLOSED_STRING 150
-#define TK_FUNCTION 151
-#define TK_COLUMN 152
-#define TK_AGG_FUNCTION 153
-#define TK_AGG_COLUMN 154
-#define TK_UMINUS 155
-#define TK_UPLUS 156
-#define TK_REGISTER 157
-#define TK_VECTOR 158
-#define TK_SELECT_COLUMN 159
-#define TK_ASTERISK 160
-#define TK_SPAN 161
-#define TK_SPACE 162
-#define TK_ILLEGAL 163
-
-/* The token codes above must all fit in 8 bits */
-#define TKFLG_MASK 0xff
-
-/* Flags that can be added to a token code when it is not
-** being stored in a u8: */
-#define TKFLG_DONTFOLD 0x100 /* Omit constant folding optimizations */
+#define TK_ABORT 27
+#define TK_ACTION 28
+#define TK_AFTER 29
+#define TK_ANALYZE 30
+#define TK_ASC 31
+#define TK_ATTACH 32
+#define TK_BEFORE 33
+#define TK_BY 34
+#define TK_CASCADE 35
+#define TK_CAST 36
+#define TK_CONFLICT 37
+#define TK_DATABASE 38
+#define TK_DESC 39
+#define TK_DETACH 40
+#define TK_EACH 41
+#define TK_FAIL 42
+#define TK_OR 43
+#define TK_AND 44
+#define TK_IS 45
+#define TK_MATCH 46
+#define TK_LIKE_KW 47
+#define TK_BETWEEN 48
+#define TK_IN 49
+#define TK_ISNULL 50
+#define TK_NOTNULL 51
+#define TK_NE 52
+#define TK_EQ 53
+#define TK_GT 54
+#define TK_LE 55
+#define TK_LT 56
+#define TK_GE 57
+#define TK_ESCAPE 58
+#define TK_ID 59
+#define TK_COLUMNKW 60
+#define TK_DO 61
+#define TK_FOR 62
+#define TK_IGNORE 63
+#define TK_INITIALLY 64
+#define TK_INSTEAD 65
+#define TK_NO 66
+#define TK_KEY 67
+#define TK_OF 68
+#define TK_OFFSET 69
+#define TK_PRAGMA 70
+#define TK_RAISE 71
+#define TK_RECURSIVE 72
+#define TK_REPLACE 73
+#define TK_RESTRICT 74
+#define TK_ROW 75
+#define TK_ROWS 76
+#define TK_TRIGGER 77
+#define TK_VACUUM 78
+#define TK_VIEW 79
+#define TK_VIRTUAL 80
+#define TK_WITH 81
+#define TK_NULLS 82
+#define TK_FIRST 83
+#define TK_LAST 84
+#define TK_CURRENT 85
+#define TK_FOLLOWING 86
+#define TK_PARTITION 87
+#define TK_PRECEDING 88
+#define TK_RANGE 89
+#define TK_UNBOUNDED 90
+#define TK_EXCLUDE 91
+#define TK_GROUPS 92
+#define TK_OTHERS 93
+#define TK_TIES 94
+#define TK_GENERATED 95
+#define TK_ALWAYS 96
+#define TK_REINDEX 97
+#define TK_RENAME 98
+#define TK_CTIME_KW 99
+#define TK_ANY 100
+#define TK_BITAND 101
+#define TK_BITOR 102
+#define TK_LSHIFT 103
+#define TK_RSHIFT 104
+#define TK_PLUS 105
+#define TK_MINUS 106
+#define TK_STAR 107
+#define TK_SLASH 108
+#define TK_REM 109
+#define TK_CONCAT 110
+#define TK_COLLATE 111
+#define TK_BITNOT 112
+#define TK_ON 113
+#define TK_INDEXED 114
+#define TK_STRING 115
+#define TK_JOIN_KW 116
+#define TK_CONSTRAINT 117
+#define TK_DEFAULT 118
+#define TK_NULL 119
+#define TK_PRIMARY 120
+#define TK_UNIQUE 121
+#define TK_CHECK 122
+#define TK_REFERENCES 123
+#define TK_AUTOINCR 124
+#define TK_INSERT 125
+#define TK_DELETE 126
+#define TK_UPDATE 127
+#define TK_SET 128
+#define TK_DEFERRABLE 129
+#define TK_FOREIGN 130
+#define TK_DROP 131
+#define TK_UNION 132
+#define TK_ALL 133
+#define TK_EXCEPT 134
+#define TK_INTERSECT 135
+#define TK_SELECT 136
+#define TK_VALUES 137
+#define TK_DISTINCT 138
+#define TK_DOT 139
+#define TK_FROM 140
+#define TK_JOIN 141
+#define TK_USING 142
+#define TK_ORDER 143
+#define TK_GROUP 144
+#define TK_HAVING 145
+#define TK_LIMIT 146
+#define TK_WHERE 147
+#define TK_INTO 148
+#define TK_NOTHING 149
+#define TK_FLOAT 150
+#define TK_BLOB 151
+#define TK_INTEGER 152
+#define TK_VARIABLE 153
+#define TK_CASE 154
+#define TK_WHEN 155
+#define TK_THEN 156
+#define TK_ELSE 157
+#define TK_INDEX 158
+#define TK_ALTER 159
+#define TK_ADD 160
+#define TK_WINDOW 161
+#define TK_OVER 162
+#define TK_FILTER 163
+#define TK_COLUMN 164
+#define TK_AGG_FUNCTION 165
+#define TK_AGG_COLUMN 166
+#define TK_TRUEFALSE 167
+#define TK_ISNOT 168
+#define TK_FUNCTION 169
+#define TK_UMINUS 170
+#define TK_UPLUS 171
+#define TK_TRUTH 172
+#define TK_REGISTER 173
+#define TK_VECTOR 174
+#define TK_SELECT_COLUMN 175
+#define TK_IF_NULL_ROW 176
+#define TK_ASTERISK 177
+#define TK_SPAN 178
+#define TK_SPACE 179
+#define TK_ILLEGAL 180
/************** End of parse.h ***********************************************/
/************** Continuing where we left off in sqliteInt.h ******************/
@@ -11469,6 +14032,18 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*);
#include <stddef.h>
/*
+** Use a macro to replace memcpy() if compiled with SQLITE_INLINE_MEMCPY.
+** This allows better measurements of where memcpy() is used when running
+** cachegrind. But this macro version of memcpy() is very slow so it
+** should not be used in production. This is a performance measurement
+** hack only.
+*/
+#ifdef SQLITE_INLINE_MEMCPY
+# define memcpy(D,S,N) {char*xxd=(char*)(D);const char*xxs=(const char*)(S);\
+ int xxn=(N);while(xxn-->0)*(xxd++)=*(xxs++);}
+#endif
+
+/*
** If compiling for a processor that lacks floating point support,
** substitute integer for floating-point
*/
@@ -11477,7 +14052,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*);
# define float sqlite_int64
# define LONGDOUBLE_TYPE sqlite_int64
# ifndef SQLITE_BIG_DBL
-# define SQLITE_BIG_DBL (((sqlite3_int64)1)<<50)
+# define SQLITE_BIG_DBL (((tdsqlite3_int64)1)<<50)
# endif
# define SQLITE_OMIT_DATETIME_FUNCS 1
# define SQLITE_OMIT_TRACE 1
@@ -11524,7 +14099,6 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*);
*/
#ifndef SQLITE_TEMP_STORE
# define SQLITE_TEMP_STORE 1
-# define SQLITE_TEMP_STORE_xc 1 /* Exclude from ctime.c */
#endif
/*
@@ -11552,9 +14126,28 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*);
** pagecaches for each database connection. A positive number is the
** number of pages. A negative number N translations means that a buffer
** of -1024*N bytes is allocated and used for as many pages as it will hold.
+**
+** The default value of "20" was choosen to minimize the run-time of the
+** speedtest1 test program with options: --shrink-memory --reprepare
*/
#ifndef SQLITE_DEFAULT_PCACHE_INITSZ
-# define SQLITE_DEFAULT_PCACHE_INITSZ 100
+# define SQLITE_DEFAULT_PCACHE_INITSZ 20
+#endif
+
+/*
+** Default value for the SQLITE_CONFIG_SORTERREF_SIZE option.
+*/
+#ifndef SQLITE_DEFAULT_SORTERREF_SIZE
+# define SQLITE_DEFAULT_SORTERREF_SIZE 0x7fffffff
+#endif
+
+/*
+** The compile-time options SQLITE_MMAP_READWRITE and
+** SQLITE_ENABLE_BATCH_ATOMIC_WRITE are not compatible with one another.
+** You must choose one or the other (or neither) but not both.
+*/
+#if defined(SQLITE_MMAP_READWRITE) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE)
+#error Cannot use both SQLITE_MMAP_READWRITE and SQLITE_ENABLE_BATCH_ATOMIC_WRITE
#endif
/*
@@ -11695,7 +14288,8 @@ typedef INT16_TYPE LogEst;
# if defined(__SIZEOF_POINTER__)
# define SQLITE_PTRSIZE __SIZEOF_POINTER__
# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \
- defined(_M_ARM) || defined(__arm__) || defined(__x86)
+ defined(_M_ARM) || defined(__arm__) || defined(__x86) || \
+ (defined(__TOS_AIX__) && !defined(__64BIT__))
# define SQLITE_PTRSIZE 4
# else
# define SQLITE_PTRSIZE 8
@@ -11729,34 +14323,38 @@ typedef INT16_TYPE LogEst;
**
** For best performance, an attempt is made to guess at the byte-order
** using C-preprocessor macros. If that is unsuccessful, or if
-** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined
+** -DSQLITE_BYTEORDER=0 is set, then byte-order is determined
** at run-time.
*/
-#if (defined(i386) || defined(__i386__) || defined(_M_IX86) || \
- defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
- defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
- defined(__arm__)) && !defined(SQLITE_RUNTIME_BYTEORDER)
-# define SQLITE_BYTEORDER 1234
-# define SQLITE_BIGENDIAN 0
-# define SQLITE_LITTLEENDIAN 1
-# define SQLITE_UTF16NATIVE SQLITE_UTF16LE
+#ifndef SQLITE_BYTEORDER
+# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
+ defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
+ defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
+ defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64)
+# define SQLITE_BYTEORDER 1234
+# elif defined(sparc) || defined(__ppc__) || \
+ defined(__ARMEB__) || defined(__AARCH64EB__)
+# define SQLITE_BYTEORDER 4321
+# else
+# define SQLITE_BYTEORDER 0
+# endif
#endif
-#if (defined(sparc) || defined(__ppc__)) \
- && !defined(SQLITE_RUNTIME_BYTEORDER)
-# define SQLITE_BYTEORDER 4321
+#if SQLITE_BYTEORDER==4321
# define SQLITE_BIGENDIAN 1
# define SQLITE_LITTLEENDIAN 0
# define SQLITE_UTF16NATIVE SQLITE_UTF16BE
-#endif
-#if !defined(SQLITE_BYTEORDER)
+#elif SQLITE_BYTEORDER==1234
+# define SQLITE_BIGENDIAN 0
+# define SQLITE_LITTLEENDIAN 1
+# define SQLITE_UTF16NATIVE SQLITE_UTF16LE
+#else
# ifdef SQLITE_AMALGAMATION
- const int sqlite3one = 1;
+ const int tdsqlite3one = 1;
# else
- extern const int sqlite3one;
+ extern const int tdsqlite3one;
# endif
-# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */
-# define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0)
-# define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1)
+# define SQLITE_BIGENDIAN (*(char *)(&tdsqlite3one)==0)
+# define SQLITE_LITTLEENDIAN (*(char *)(&tdsqlite3one)==1)
# define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE)
#endif
@@ -11819,7 +14417,6 @@ typedef INT16_TYPE LogEst;
# else
# define SQLITE_MAX_MMAP_SIZE 0
# endif
-# define SQLITE_MAX_MMAP_SIZE_xc 1 /* exclude from ctime.c */
#endif
/*
@@ -11829,7 +14426,6 @@ typedef INT16_TYPE LogEst;
*/
#ifndef SQLITE_DEFAULT_MMAP_SIZE
# define SQLITE_DEFAULT_MMAP_SIZE 0
-# define SQLITE_DEFAULT_MMAP_SIZE_xc 1 /* Exclude from ctime.c */
#endif
#if SQLITE_DEFAULT_MMAP_SIZE>SQLITE_MAX_MMAP_SIZE
# undef SQLITE_DEFAULT_MMAP_SIZE
@@ -11837,24 +14433,10 @@ typedef INT16_TYPE LogEst;
#endif
/*
-** Only one of SQLITE_ENABLE_STAT3 or SQLITE_ENABLE_STAT4 can be defined.
-** Priority is given to SQLITE_ENABLE_STAT4. If either are defined, also
-** define SQLITE_ENABLE_STAT3_OR_STAT4
-*/
-#ifdef SQLITE_ENABLE_STAT4
-# undef SQLITE_ENABLE_STAT3
-# define SQLITE_ENABLE_STAT3_OR_STAT4 1
-#elif SQLITE_ENABLE_STAT3
-# define SQLITE_ENABLE_STAT3_OR_STAT4 1
-#elif SQLITE_ENABLE_STAT3_OR_STAT4
-# undef SQLITE_ENABLE_STAT3_OR_STAT4
-#endif
-
-/*
** SELECTTRACE_ENABLED will be either 1 or 0 depending on whether or not
** the Select query generator tracing logic is turned on.
*/
-#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_SELECTTRACE)
+#if defined(SQLITE_ENABLE_SELECTTRACE)
# define SELECTTRACE_ENABLED 1
#else
# define SELECTTRACE_ENABLED 0
@@ -11871,9 +14453,10 @@ typedef INT16_TYPE LogEst;
*/
typedef struct BusyHandler BusyHandler;
struct BusyHandler {
- int (*xFunc)(void *,int); /* The busy callback */
- void *pArg; /* First arg to busy callback */
- int nBusy; /* Incremented with each busy call */
+ int (*xBusyHandler)(void *,int); /* The busy callback */
+ void *pBusyArg; /* First arg to busy callback */
+ int nBusy; /* Incremented with each busy call */
+ u8 bExtraFileArg; /* Include tdsqlite3_file as callback arg */
};
/*
@@ -11906,14 +14489,14 @@ struct BusyHandler {
#define IsPowerOfTwo(X) (((X)&((X)-1))==0)
/*
-** The following value as a destructor means to use sqlite3DbFree().
-** The sqlite3DbFree() routine requires two parameters instead of the
+** The following value as a destructor means to use tdsqlite3DbFree().
+** The tdsqlite3DbFree() routine requires two parameters instead of the
** one parameter that destructors normally want. So we have to introduce
** this magic value that the code knows to handle differently. Any
** pointer will work here as long as it is distinct from SQLITE_STATIC
** and SQLITE_TRANSIENT.
*/
-#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3MallocSize)
+#define SQLITE_DYNAMIC ((tdsqlite3_destructor_type)tdsqlite3MallocSize)
/*
** When SQLITE_OMIT_WSD is defined, it means that the target platform does
@@ -11931,14 +14514,14 @@ struct BusyHandler {
*/
#ifdef SQLITE_OMIT_WSD
#define SQLITE_WSD const
- #define GLOBAL(t,v) (*(t*)sqlite3_wsd_find((void*)&(v), sizeof(v)))
- #define sqlite3GlobalConfig GLOBAL(struct Sqlite3Config, sqlite3Config)
-SQLITE_API int sqlite3_wsd_init(int N, int J);
-SQLITE_API void *sqlite3_wsd_find(void *K, int L);
+ #define GLOBAL(t,v) (*(t*)tdsqlite3_wsd_find((void*)&(v), sizeof(v)))
+ #define tdsqlite3GlobalConfig GLOBAL(struct Sqlite3Config, tdsqlite3Config)
+SQLITE_API int tdsqlite3_wsd_init(int N, int J);
+SQLITE_API void *tdsqlite3_wsd_find(void *K, int L);
#else
#define SQLITE_WSD
#define GLOBAL(t,v) v
- #define sqlite3GlobalConfig sqlite3Config
+ #define tdsqlite3GlobalConfig tdsqlite3Config
#endif
/*
@@ -11973,7 +14556,6 @@ typedef struct Db Db;
typedef struct Schema Schema;
typedef struct Expr Expr;
typedef struct ExprList ExprList;
-typedef struct ExprSpan ExprSpan;
typedef struct FKey FKey;
typedef struct FuncDestructor FuncDestructor;
typedef struct FuncDef FuncDef;
@@ -11990,13 +14572,14 @@ typedef struct NameContext NameContext;
typedef struct Parse Parse;
typedef struct PreUpdate PreUpdate;
typedef struct PrintfArguments PrintfArguments;
+typedef struct RenameToken RenameToken;
typedef struct RowSet RowSet;
typedef struct Savepoint Savepoint;
typedef struct Select Select;
typedef struct SQLiteThread SQLiteThread;
typedef struct SelectDest SelectDest;
typedef struct SrcList SrcList;
-typedef struct StrAccum StrAccum;
+typedef struct tdsqlite3_str StrAccum; /* Internal alias for tdsqlite3_str */
typedef struct Table Table;
typedef struct TableLock TableLock;
typedef struct Token Token;
@@ -12005,12 +14588,49 @@ typedef struct Trigger Trigger;
typedef struct TriggerPrg TriggerPrg;
typedef struct TriggerStep TriggerStep;
typedef struct UnpackedRecord UnpackedRecord;
+typedef struct Upsert Upsert;
typedef struct VTable VTable;
typedef struct VtabCtx VtabCtx;
typedef struct Walker Walker;
typedef struct WhereInfo WhereInfo;
+typedef struct Window Window;
typedef struct With With;
+
+/*
+** The bitmask datatype defined below is used for various optimizations.
+**
+** Changing this from a 64-bit to a 32-bit type limits the number of
+** tables in a join to 32 instead of 64. But it also reduces the size
+** of the library by 738 bytes on ix86.
+*/
+#ifdef SQLITE_BITMASK_TYPE
+ typedef SQLITE_BITMASK_TYPE Bitmask;
+#else
+ typedef u64 Bitmask;
+#endif
+
+/*
+** The number of bits in a Bitmask. "BMS" means "BitMask Size".
+*/
+#define BMS ((int)(sizeof(Bitmask)*8))
+
+/*
+** A bit in a Bitmask
+*/
+#define MASKBIT(n) (((Bitmask)1)<<(n))
+#define MASKBIT64(n) (((u64)1)<<(n))
+#define MASKBIT32(n) (((unsigned int)1)<<(n))
+#define ALLBITS ((Bitmask)-1)
+
+/* A VList object records a mapping between parameters/variables/wildcards
+** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer
+** variable number associated with that parameter. See the format description
+** on the tdsqlite3VListAdd() routine for more information. A VList is really
+** just an array of integers.
+*/
+typedef int VList;
+
/*
** Defer sourcing vdbe.h and btree.h until after the "u8" and
** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque
@@ -12062,16 +14682,16 @@ typedef struct BtShared BtShared;
typedef struct BtreePayload BtreePayload;
-SQLITE_PRIVATE int sqlite3BtreeOpen(
- sqlite3_vfs *pVfs, /* VFS to use with this b-tree */
+SQLITE_PRIVATE int tdsqlite3BtreeOpen(
+ tdsqlite3_vfs *pVfs, /* VFS to use with this b-tree */
const char *zFilename, /* Name of database file to open */
- sqlite3 *db, /* Associated database connection */
+ tdsqlite3 *db, /* Associated database connection */
Btree **ppBtree, /* Return open Btree* here */
int flags, /* Flags */
int vfsFlags /* Flags passed through to VFS open */
);
-/* The flags parameter to sqlite3BtreeOpen can be the bitwise or of the
+/* The flags parameter to tdsqlite3BtreeOpen can be the bitwise or of the
** following values.
**
** NOTE: These values must match the corresponding PAGER_ values in
@@ -12082,46 +14702,46 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
#define BTREE_SINGLE 4 /* The file contains at most 1 b-tree */
#define BTREE_UNORDERED 8 /* Use of a hash implementation is OK */
-SQLITE_PRIVATE int sqlite3BtreeClose(Btree*);
-SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree*,int);
-SQLITE_PRIVATE int sqlite3BtreeSetSpillSize(Btree*,int);
+SQLITE_PRIVATE int tdsqlite3BtreeClose(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeSetCacheSize(Btree*,int);
+SQLITE_PRIVATE int tdsqlite3BtreeSetSpillSize(Btree*,int);
#if SQLITE_MAX_MMAP_SIZE>0
-SQLITE_PRIVATE int sqlite3BtreeSetMmapLimit(Btree*,sqlite3_int64);
-#endif
-SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags(Btree*,unsigned);
-SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix);
-SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*);
-SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int);
-SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree*);
-SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree*,int);
-SQLITE_PRIVATE int sqlite3BtreeGetOptimalReserve(Btree*);
-SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p);
-SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int);
-SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *);
-SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int);
-SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster);
-SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*, int);
-SQLITE_PRIVATE int sqlite3BtreeCommit(Btree*);
-SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*,int,int);
-SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree*,int);
-SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, int*, int flags);
-SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree*);
-SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree*);
-SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree*);
-SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *, int, void(*)(void *));
-SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *pBtree);
+SQLITE_PRIVATE int tdsqlite3BtreeSetMmapLimit(Btree*,tdsqlite3_int64);
+#endif
+SQLITE_PRIVATE int tdsqlite3BtreeSetPagerFlags(Btree*,unsigned);
+SQLITE_PRIVATE int tdsqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix);
+SQLITE_PRIVATE int tdsqlite3BtreeGetPageSize(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeMaxPageCount(Btree*,int);
+SQLITE_PRIVATE u32 tdsqlite3BtreeLastPage(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeSecureDelete(Btree*,int);
+SQLITE_PRIVATE int tdsqlite3BtreeGetOptimalReserve(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeGetReserveNoMutex(Btree *p);
+SQLITE_PRIVATE int tdsqlite3BtreeSetAutoVacuum(Btree *, int);
+SQLITE_PRIVATE int tdsqlite3BtreeGetAutoVacuum(Btree *);
+SQLITE_PRIVATE int tdsqlite3BtreeBeginTrans(Btree*,int,int*);
+SQLITE_PRIVATE int tdsqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster);
+SQLITE_PRIVATE int tdsqlite3BtreeCommitPhaseTwo(Btree*, int);
+SQLITE_PRIVATE int tdsqlite3BtreeCommit(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeRollback(Btree*,int,int);
+SQLITE_PRIVATE int tdsqlite3BtreeBeginStmt(Btree*,int);
+SQLITE_PRIVATE int tdsqlite3BtreeCreateTable(Btree*, int*, int flags);
+SQLITE_PRIVATE int tdsqlite3BtreeIsInTrans(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeIsInReadTrans(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeIsInBackup(Btree*);
+SQLITE_PRIVATE void *tdsqlite3BtreeSchema(Btree *, int, void(*)(void *));
+SQLITE_PRIVATE int tdsqlite3BtreeSchemaLocked(Btree *pBtree);
#ifndef SQLITE_OMIT_SHARED_CACHE
-SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *pBtree, int iTab, u8 isWriteLock);
+SQLITE_PRIVATE int tdsqlite3BtreeLockTable(Btree *pBtree, int iTab, u8 isWriteLock);
#endif
-SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *, int, int);
+SQLITE_PRIVATE int tdsqlite3BtreeSavepoint(Btree *, int, int);
-SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *);
-SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *);
-SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *);
+SQLITE_PRIVATE const char *tdsqlite3BtreeGetFilename(Btree *);
+SQLITE_PRIVATE const char *tdsqlite3BtreeGetJournalname(Btree *);
+SQLITE_PRIVATE int tdsqlite3BtreeCopyFile(Btree *, Btree *);
-SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *);
+SQLITE_PRIVATE int tdsqlite3BtreeIncrVacuum(Btree *);
-/* The flags parameter to sqlite3BtreeCreateTable can be the bitwise OR
+/* The flags parameter to tdsqlite3BtreeCreateTable can be the bitwise OR
** of the flags shown below.
**
** Every SQLite table must have either BTREE_INTKEY or BTREE_BLOBKEY set.
@@ -12134,18 +14754,18 @@ SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *);
#define BTREE_INTKEY 1 /* Table has only 64-bit signed integer keys */
#define BTREE_BLOBKEY 2 /* Table has keys only - no data */
-SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree*, int, int*);
-SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int, int*);
-SQLITE_PRIVATE int sqlite3BtreeClearTableOfCursor(BtCursor*);
-SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree*, int, int);
+SQLITE_PRIVATE int tdsqlite3BtreeDropTable(Btree*, int, int*);
+SQLITE_PRIVATE int tdsqlite3BtreeClearTable(Btree*, int, int*);
+SQLITE_PRIVATE int tdsqlite3BtreeClearTableOfCursor(BtCursor*);
+SQLITE_PRIVATE int tdsqlite3BtreeTripAllCursors(Btree*, int, int);
-SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *pBtree, int idx, u32 *pValue);
-SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree*, int idx, u32 value);
+SQLITE_PRIVATE void tdsqlite3BtreeGetMeta(Btree *pBtree, int idx, u32 *pValue);
+SQLITE_PRIVATE int tdsqlite3BtreeUpdateMeta(Btree*, int idx, u32 value);
-SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p);
+SQLITE_PRIVATE int tdsqlite3BtreeNewDb(Btree *p);
/*
-** The second parameter to sqlite3BtreeGetMeta or sqlite3BtreeUpdateMeta
+** The second parameter to tdsqlite3BtreeGetMeta or tdsqlite3BtreeUpdateMeta
** should be one of the following values. The integer values are assigned
** to constants so that the offset of the corresponding field in an
** SQLite database header may be found using the following formula:
@@ -12173,7 +14793,7 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p);
#define BTREE_DATA_VERSION 15 /* A virtual meta-value */
/*
-** Kinds of hints that can be passed into the sqlite3BtreeCursorHint()
+** Kinds of hints that can be passed into the tdsqlite3BtreeCursorHint()
** interface.
**
** BTREE_HINT_RANGE (arguments: Expr*, Mem*)
@@ -12203,7 +14823,7 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p);
/*
** Values that may be OR'd together to form the argument to the
-** BTREE_HINT_FLAGS hint for sqlite3BtreeCursorHint():
+** BTREE_HINT_FLAGS hint for tdsqlite3BtreeCursorHint():
**
** The BTREE_BULKLOAD flag is set on index cursors when the index is going
** to be filled with content that is already in sorted order.
@@ -12218,7 +14838,7 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p);
#define BTREE_SEEK_EQ 0x00000002 /* EQ seeks only - no range seeks */
/*
-** Flags passed as the third argument to sqlite3BtreeCursor().
+** Flags passed as the third argument to tdsqlite3BtreeCursor().
**
** For read-only cursors the wrFlag argument is always zero. For read-write
** cursors it may be set to either (BTREE_WRCSR|BTREE_FORDELETE) or just
@@ -12243,49 +14863,66 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p);
#define BTREE_WRCSR 0x00000004 /* read-write cursor */
#define BTREE_FORDELETE 0x00000008 /* Cursor is for seek/delete only */
-SQLITE_PRIVATE int sqlite3BtreeCursor(
+SQLITE_PRIVATE int tdsqlite3BtreeCursor(
Btree*, /* BTree containing table to open */
int iTable, /* Index of root page */
int wrFlag, /* 1 for writing. 0 for read-only */
struct KeyInfo*, /* First argument to compare function */
BtCursor *pCursor /* Space to write cursor structure */
);
-SQLITE_PRIVATE int sqlite3BtreeCursorSize(void);
-SQLITE_PRIVATE void sqlite3BtreeCursorZero(BtCursor*);
-SQLITE_PRIVATE void sqlite3BtreeCursorHintFlags(BtCursor*, unsigned);
+SQLITE_PRIVATE BtCursor *tdsqlite3BtreeFakeValidCursor(void);
+SQLITE_PRIVATE int tdsqlite3BtreeCursorSize(void);
+SQLITE_PRIVATE void tdsqlite3BtreeCursorZero(BtCursor*);
+SQLITE_PRIVATE void tdsqlite3BtreeCursorHintFlags(BtCursor*, unsigned);
#ifdef SQLITE_ENABLE_CURSOR_HINTS
-SQLITE_PRIVATE void sqlite3BtreeCursorHint(BtCursor*, int, ...);
+SQLITE_PRIVATE void tdsqlite3BtreeCursorHint(BtCursor*, int, ...);
#endif
-SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor*);
-SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
+SQLITE_PRIVATE int tdsqlite3BtreeCloseCursor(BtCursor*);
+SQLITE_PRIVATE int tdsqlite3BtreeMovetoUnpacked(
BtCursor*,
UnpackedRecord *pUnKey,
i64 intKey,
int bias,
int *pRes
);
-SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*);
-SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor*, int*);
-SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags);
+SQLITE_PRIVATE int tdsqlite3BtreeCursorHasMoved(BtCursor*);
+SQLITE_PRIVATE int tdsqlite3BtreeCursorRestore(BtCursor*, int*);
+SQLITE_PRIVATE int tdsqlite3BtreeDelete(BtCursor*, u8 flags);
-/* Allowed flags for the 2nd argument to sqlite3BtreeDelete() */
+/* Allowed flags for tdsqlite3BtreeDelete() and tdsqlite3BtreeInsert() */
#define BTREE_SAVEPOSITION 0x02 /* Leave cursor pointing at NEXT or PREV */
#define BTREE_AUXDELETE 0x04 /* not the primary delete operation */
+#define BTREE_APPEND 0x08 /* Insert is likely an append */
/* An instance of the BtreePayload object describes the content of a single
** entry in either an index or table btree.
**
** Index btrees (used for indexes and also WITHOUT ROWID tables) contain
-** an arbitrary key and no data. These btrees have pKey,nKey set to their
-** key and pData,nData,nZero set to zero.
+** an arbitrary key and no data. These btrees have pKey,nKey set to the
+** key and the pData,nData,nZero fields are uninitialized. The aMem,nMem
+** fields give an array of Mem objects that are a decomposition of the key.
+** The nMem field might be zero, indicating that no decomposition is available.
**
** Table btrees (used for rowid tables) contain an integer rowid used as
** the key and passed in the nKey field. The pKey field is zero.
** pData,nData hold the content of the new entry. nZero extra zero bytes
** are appended to the end of the content when constructing the entry.
+** The aMem,nMem fields are uninitialized for table btrees.
+**
+** Field usage summary:
+**
+** Table BTrees Index Btrees
**
-** This object is used to pass information into sqlite3BtreeInsert(). The
+** pKey always NULL encoded key
+** nKey the ROWID length of pKey
+** pData data not used
+** aMem not used decomposed key value
+** nMem not used entries in aMem
+** nData length of pData not used
+** nZero extra zeros after pData not used
+**
+** This object is used to pass information into tdsqlite3BtreeInsert(). The
** same information used to be passed as five separate parameters. But placing
** the information into this object helps to keep the interface more
** organized and understandable, and it also helps the resulting code to
@@ -12293,53 +14930,63 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags);
*/
struct BtreePayload {
const void *pKey; /* Key content for indexes. NULL for tables */
- sqlite3_int64 nKey; /* Size of pKey for indexes. PRIMARY KEY for tabs */
- const void *pData; /* Data for tables. NULL for indexes */
+ tdsqlite3_int64 nKey; /* Size of pKey for indexes. PRIMARY KEY for tabs */
+ const void *pData; /* Data for tables. */
+ tdsqlite3_value *aMem; /* First of nMem value in the unpacked pKey */
+ u16 nMem; /* Number of aMem[] value. Might be zero */
int nData; /* Size of pData. 0 if none. */
int nZero; /* Extra zero data appended after pData,nData */
};
-SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload,
- int bias, int seekResult);
-SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes);
-SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes);
-SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int *pRes);
-SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*);
-SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int *pRes);
-SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor*);
-SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*);
-SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt);
-SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor*);
-SQLITE_PRIVATE int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*);
-
-SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*);
-SQLITE_PRIVATE struct Pager *sqlite3BtreePager(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload,
+ int flags, int seekResult);
+SQLITE_PRIVATE int tdsqlite3BtreeFirst(BtCursor*, int *pRes);
+SQLITE_PRIVATE int tdsqlite3BtreeLast(BtCursor*, int *pRes);
+SQLITE_PRIVATE int tdsqlite3BtreeNext(BtCursor*, int flags);
+SQLITE_PRIVATE int tdsqlite3BtreeEof(BtCursor*);
+SQLITE_PRIVATE int tdsqlite3BtreePrevious(BtCursor*, int flags);
+SQLITE_PRIVATE i64 tdsqlite3BtreeIntegerKey(BtCursor*);
+SQLITE_PRIVATE void tdsqlite3BtreeCursorPin(BtCursor*);
+SQLITE_PRIVATE void tdsqlite3BtreeCursorUnpin(BtCursor*);
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+SQLITE_PRIVATE i64 tdsqlite3BtreeOffset(BtCursor*);
+#endif
+SQLITE_PRIVATE int tdsqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*);
+SQLITE_PRIVATE const void *tdsqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt);
+SQLITE_PRIVATE u32 tdsqlite3BtreePayloadSize(BtCursor*);
+SQLITE_PRIVATE tdsqlite3_int64 tdsqlite3BtreeMaxRecordSize(BtCursor*);
+
+SQLITE_PRIVATE char *tdsqlite3BtreeIntegrityCheck(tdsqlite3*,Btree*,int*aRoot,int nRoot,int,int*);
+SQLITE_PRIVATE struct Pager *tdsqlite3BtreePager(Btree*);
+SQLITE_PRIVATE i64 tdsqlite3BtreeRowCountEst(BtCursor*);
#ifndef SQLITE_OMIT_INCRBLOB
-SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*);
-SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *);
+SQLITE_PRIVATE int tdsqlite3BtreePayloadChecked(BtCursor*, u32 offset, u32 amt, void*);
+SQLITE_PRIVATE int tdsqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*);
+SQLITE_PRIVATE void tdsqlite3BtreeIncrblobCursor(BtCursor *);
#endif
-SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *);
-SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBt, int iVersion);
-SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask);
-SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *pBt);
-SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void);
+SQLITE_PRIVATE void tdsqlite3BtreeClearCursor(BtCursor *);
+SQLITE_PRIVATE int tdsqlite3BtreeSetVersion(Btree *pBt, int iVersion);
+SQLITE_PRIVATE int tdsqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask);
+SQLITE_PRIVATE int tdsqlite3BtreeIsReadonly(Btree *pBt);
+SQLITE_PRIVATE int tdsqlite3HeaderSizeBtree(void);
#ifndef NDEBUG
-SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor*);
+SQLITE_PRIVATE int tdsqlite3BtreeCursorIsValid(BtCursor*);
#endif
+SQLITE_PRIVATE int tdsqlite3BtreeCursorIsValidNN(BtCursor*);
#ifndef SQLITE_OMIT_BTREECOUNT
-SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *, i64 *);
+SQLITE_PRIVATE int tdsqlite3BtreeCount(tdsqlite3*, BtCursor*, i64*);
#endif
#ifdef SQLITE_TEST
-SQLITE_PRIVATE int sqlite3BtreeCursorInfo(BtCursor*, int*, int);
-SQLITE_PRIVATE void sqlite3BtreeCursorList(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeCursorInfo(BtCursor*, int*, int);
+SQLITE_PRIVATE void tdsqlite3BtreeCursorList(Btree*);
#endif
#ifndef SQLITE_OMIT_WAL
-SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *);
+SQLITE_PRIVATE int tdsqlite3BtreeCheckpoint(Btree*, int, int *, int *);
#endif
/*
@@ -12348,38 +14995,38 @@ SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *);
** Enter and Leave procedures no-ops.
*/
#ifndef SQLITE_OMIT_SHARED_CACHE
-SQLITE_PRIVATE void sqlite3BtreeEnter(Btree*);
-SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3*);
-SQLITE_PRIVATE int sqlite3BtreeSharable(Btree*);
-SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor*);
-SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree*);
+SQLITE_PRIVATE void tdsqlite3BtreeEnter(Btree*);
+SQLITE_PRIVATE void tdsqlite3BtreeEnterAll(tdsqlite3*);
+SQLITE_PRIVATE int tdsqlite3BtreeSharable(Btree*);
+SQLITE_PRIVATE void tdsqlite3BtreeEnterCursor(BtCursor*);
+SQLITE_PRIVATE int tdsqlite3BtreeConnectionCount(Btree*);
#else
-# define sqlite3BtreeEnter(X)
-# define sqlite3BtreeEnterAll(X)
-# define sqlite3BtreeSharable(X) 0
-# define sqlite3BtreeEnterCursor(X)
-# define sqlite3BtreeConnectionCount(X) 1
+# define tdsqlite3BtreeEnter(X)
+# define tdsqlite3BtreeEnterAll(X)
+# define tdsqlite3BtreeSharable(X) 0
+# define tdsqlite3BtreeEnterCursor(X)
+# define tdsqlite3BtreeConnectionCount(X) 1
#endif
#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE
-SQLITE_PRIVATE void sqlite3BtreeLeave(Btree*);
-SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor*);
-SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3*);
+SQLITE_PRIVATE void tdsqlite3BtreeLeave(Btree*);
+SQLITE_PRIVATE void tdsqlite3BtreeLeaveCursor(BtCursor*);
+SQLITE_PRIVATE void tdsqlite3BtreeLeaveAll(tdsqlite3*);
#ifndef NDEBUG
/* These routines are used inside assert() statements only. */
-SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree*);
-SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3*);
-SQLITE_PRIVATE int sqlite3SchemaMutexHeld(sqlite3*,int,Schema*);
+SQLITE_PRIVATE int tdsqlite3BtreeHoldsMutex(Btree*);
+SQLITE_PRIVATE int tdsqlite3BtreeHoldsAllMutexes(tdsqlite3*);
+SQLITE_PRIVATE int tdsqlite3SchemaMutexHeld(tdsqlite3*,int,Schema*);
#endif
#else
-# define sqlite3BtreeLeave(X)
-# define sqlite3BtreeLeaveCursor(X)
-# define sqlite3BtreeLeaveAll(X)
+# define tdsqlite3BtreeLeave(X)
+# define tdsqlite3BtreeLeaveCursor(X)
+# define tdsqlite3BtreeLeaveAll(X)
-# define sqlite3BtreeHoldsMutex(X) 1
-# define sqlite3BtreeHoldsAllMutexes(X) 1
-# define sqlite3SchemaMutexHeld(X,Y,Z) 1
+# define tdsqlite3BtreeHoldsMutex(X) 1
+# define tdsqlite3BtreeHoldsAllMutexes(X) 1
+# define tdsqlite3SchemaMutexHeld(X,Y,Z) 1
#endif
@@ -12421,7 +15068,7 @@ typedef struct Vdbe Vdbe;
** The names of the following types declared in vdbeInt.h are required
** for the VdbeOp definition.
*/
-typedef struct Mem Mem;
+typedef struct tdsqlite3_value Mem;
typedef struct SubProgram SubProgram;
/*
@@ -12432,8 +15079,7 @@ typedef struct SubProgram SubProgram;
struct VdbeOp {
u8 opcode; /* What operation to perform */
signed char p4type; /* One of the P4_xxx constants for p4 */
- u8 notUsed1;
- u8 p5; /* Fifth parameter is an unsigned character */
+ u16 p5; /* Fifth parameter is an unsigned 16-bit integer */
int p1; /* First operand */
int p2; /* Second parameter (often the jump destination) */
int p3; /* The third parameter */
@@ -12444,7 +15090,7 @@ struct VdbeOp {
i64 *pI64; /* Used when p4type is P4_INT64 */
double *pReal; /* Used when p4type is P4_REAL */
FuncDef *pFunc; /* Used when p4type is P4_FUNCDEF */
- sqlite3_context *pCtx; /* Used when p4type is P4_FUNCCTX */
+ tdsqlite3_context *pCtx; /* Used when p4type is P4_FUNCCTX */
CollSeq *pColl; /* Used when p4type is P4_COLLSEQ */
Mem *pMem; /* Used when p4type is P4_MEM */
VTable *pVtab; /* Used when p4type is P4_VTAB */
@@ -12455,7 +15101,7 @@ struct VdbeOp {
#ifdef SQLITE_ENABLE_CURSOR_HINTS
Expr *pExpr; /* Used when p4type is P4_EXPR */
#endif
- int (*xAdvance)(BtCursor *, int *);
+ int (*xAdvance)(BtCursor *, int);
} p4;
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
char *zComment; /* Comment to improve readability */
@@ -12465,7 +15111,8 @@ struct VdbeOp {
u64 cycles; /* Total time spent executing this instruction */
#endif
#ifdef SQLITE_VDBE_COVERAGE
- int iSrcLine; /* Source-code line that generated this opcode */
+ u32 iSrcLine; /* Source-code line that generated this opcode
+ ** with flags in the upper 8 bits */
#endif
};
typedef struct VdbeOp VdbeOp;
@@ -12479,6 +15126,7 @@ struct SubProgram {
int nOp; /* Elements in aOp[] */
int nMem; /* Number of memory cells required */
int nCsr; /* Number of cursors required */
+ u8 *aOnce; /* Array of OP_Once flags */
void *token; /* id that may be used to recursive triggers */
SubProgram *pNext; /* Next sub-program already visited */
};
@@ -12498,25 +15146,27 @@ typedef struct VdbeOpList VdbeOpList;
/*
** Allowed values of VdbeOp.p4type
*/
-#define P4_NOTUSED 0 /* The P4 parameter is not used */
-#define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */
-#define P4_STATIC (-2) /* Pointer to a static string */
-#define P4_COLLSEQ (-4) /* P4 is a pointer to a CollSeq structure */
-#define P4_FUNCDEF (-5) /* P4 is a pointer to a FuncDef structure */
-#define P4_KEYINFO (-6) /* P4 is a pointer to a KeyInfo structure */
-#define P4_EXPR (-7) /* P4 is a pointer to an Expr tree */
-#define P4_MEM (-8) /* P4 is a pointer to a Mem* structure */
-#define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */
-#define P4_VTAB (-10) /* P4 is a pointer to an sqlite3_vtab structure */
-#define P4_MPRINTF (-11) /* P4 is a string obtained from sqlite3_mprintf() */
-#define P4_REAL (-12) /* P4 is a 64-bit floating point value */
-#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */
-#define P4_INT32 (-14) /* P4 is a 32-bit signed integer */
-#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */
-#define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */
-#define P4_ADVANCE (-19) /* P4 is a pointer to BtreeNext() or BtreePrev() */
-#define P4_TABLE (-20) /* P4 is a pointer to a Table structure */
-#define P4_FUNCCTX (-21) /* P4 is a pointer to an sqlite3_context object */
+#define P4_NOTUSED 0 /* The P4 parameter is not used */
+#define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */
+#define P4_STATIC (-1) /* Pointer to a static string */
+#define P4_COLLSEQ (-2) /* P4 is a pointer to a CollSeq structure */
+#define P4_INT32 (-3) /* P4 is a 32-bit signed integer */
+#define P4_SUBPROGRAM (-4) /* P4 is a pointer to a SubProgram structure */
+#define P4_ADVANCE (-5) /* P4 is a pointer to BtreeNext() or BtreePrev() */
+#define P4_TABLE (-6) /* P4 is a pointer to a Table structure */
+/* Above do not own any resources. Must free those below */
+#define P4_FREE_IF_LE (-7)
+#define P4_DYNAMIC (-7) /* Pointer to memory from sqliteMalloc() */
+#define P4_FUNCDEF (-8) /* P4 is a pointer to a FuncDef structure */
+#define P4_KEYINFO (-9) /* P4 is a pointer to a KeyInfo structure */
+#define P4_EXPR (-10) /* P4 is a pointer to an Expr tree */
+#define P4_MEM (-11) /* P4 is a pointer to a Mem* structure */
+#define P4_VTAB (-12) /* P4 is a pointer to an tdsqlite3_vtab structure */
+#define P4_REAL (-13) /* P4 is a 64-bit floating point value */
+#define P4_INT64 (-14) /* P4 is a 64-bit signed integer */
+#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */
+#define P4_FUNCCTX (-16) /* P4 is a pointer to an tdsqlite3_context object */
+#define P4_DYNBLOB (-17) /* Pointer to memory from sqliteMalloc() */
/* Error message codes for OP_Halt */
#define P5_ConstraintNotNull 1
@@ -12544,12 +15194,11 @@ typedef struct VdbeOpList VdbeOpList;
#endif
/*
-** The following macro converts a relative address in the p2 field
-** of a VdbeOp structure into a negative number so that
-** sqlite3VdbeAddOpList() knows that the address is relative. Calling
-** the macro again restores the address.
+** The following macro converts a label returned by tdsqlite3VdbeMakeLabel()
+** into an index into the Parse.aLabel[] array that contains the resolved
+** address of that label.
*/
-#define ADDR(X) (-1-(X))
+#define ADDR(X) (~(X))
/*
** The makefile scans the vdbe.c source file and creates the "opcodes.h"
@@ -12562,166 +15211,179 @@ typedef struct VdbeOpList VdbeOpList;
#define OP_Savepoint 0
#define OP_AutoCommit 1
#define OP_Transaction 2
-#define OP_SorterNext 3
-#define OP_PrevIfOpen 4
-#define OP_NextIfOpen 5
-#define OP_Prev 6
-#define OP_Next 7
-#define OP_Checkpoint 8
-#define OP_JournalMode 9
-#define OP_Vacuum 10
-#define OP_VFilter 11 /* synopsis: iplan=r[P3] zplan='P4' */
-#define OP_VUpdate 12 /* synopsis: data=r[P3@P2] */
-#define OP_Goto 13
-#define OP_Gosub 14
-#define OP_InitCoroutine 15
-#define OP_Yield 16
-#define OP_MustBeInt 17
-#define OP_Jump 18
+#define OP_SorterNext 3 /* jump */
+#define OP_Prev 4 /* jump */
+#define OP_Next 5 /* jump */
+#define OP_Checkpoint 6
+#define OP_JournalMode 7
+#define OP_Vacuum 8
+#define OP_VFilter 9 /* jump, synopsis: iplan=r[P3] zplan='P4' */
+#define OP_VUpdate 10 /* synopsis: data=r[P3@P2] */
+#define OP_Goto 11 /* jump */
+#define OP_Gosub 12 /* jump */
+#define OP_InitCoroutine 13 /* jump */
+#define OP_Yield 14 /* jump */
+#define OP_MustBeInt 15 /* jump */
+#define OP_Jump 16 /* jump */
+#define OP_Once 17 /* jump */
+#define OP_If 18 /* jump */
#define OP_Not 19 /* same as TK_NOT, synopsis: r[P2]= !r[P1] */
-#define OP_Once 20
-#define OP_If 21
-#define OP_IfNot 22
-#define OP_SeekLT 23 /* synopsis: key=r[P3@P4] */
-#define OP_SeekLE 24 /* synopsis: key=r[P3@P4] */
-#define OP_SeekGE 25 /* synopsis: key=r[P3@P4] */
-#define OP_SeekGT 26 /* synopsis: key=r[P3@P4] */
-#define OP_Or 27 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */
-#define OP_And 28 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */
-#define OP_NoConflict 29 /* synopsis: key=r[P3@P4] */
-#define OP_NotFound 30 /* synopsis: key=r[P3@P4] */
-#define OP_Found 31 /* synopsis: key=r[P3@P4] */
-#define OP_SeekRowid 32 /* synopsis: intkey=r[P3] */
-#define OP_NotExists 33 /* synopsis: intkey=r[P3] */
-#define OP_IsNull 34 /* same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */
-#define OP_NotNull 35 /* same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */
-#define OP_Ne 36 /* same as TK_NE, synopsis: IF r[P3]!=r[P1] */
-#define OP_Eq 37 /* same as TK_EQ, synopsis: IF r[P3]==r[P1] */
-#define OP_Gt 38 /* same as TK_GT, synopsis: IF r[P3]>r[P1] */
-#define OP_Le 39 /* same as TK_LE, synopsis: IF r[P3]<=r[P1] */
-#define OP_Lt 40 /* same as TK_LT, synopsis: IF r[P3]<r[P1] */
-#define OP_Ge 41 /* same as TK_GE, synopsis: IF r[P3]>=r[P1] */
-#define OP_ElseNotEq 42 /* same as TK_ESCAPE */
-#define OP_BitAnd 43 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */
-#define OP_BitOr 44 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */
-#define OP_ShiftLeft 45 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<<r[P1] */
-#define OP_ShiftRight 46 /* same as TK_RSHIFT, synopsis: r[P3]=r[P2]>>r[P1] */
-#define OP_Add 47 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */
-#define OP_Subtract 48 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */
-#define OP_Multiply 49 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */
-#define OP_Divide 50 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */
-#define OP_Remainder 51 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */
-#define OP_Concat 52 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */
-#define OP_Last 53
-#define OP_BitNot 54 /* same as TK_BITNOT, synopsis: r[P1]= ~r[P1] */
-#define OP_SorterSort 55
-#define OP_Sort 56
-#define OP_Rewind 57
-#define OP_IdxLE 58 /* synopsis: key=r[P3@P4] */
-#define OP_IdxGT 59 /* synopsis: key=r[P3@P4] */
-#define OP_IdxLT 60 /* synopsis: key=r[P3@P4] */
-#define OP_IdxGE 61 /* synopsis: key=r[P3@P4] */
-#define OP_RowSetRead 62 /* synopsis: r[P3]=rowset(P1) */
-#define OP_RowSetTest 63 /* synopsis: if r[P3] in rowset(P1) goto P2 */
-#define OP_Program 64
-#define OP_FkIfZero 65 /* synopsis: if fkctr[P1]==0 goto P2 */
-#define OP_IfPos 66 /* synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */
-#define OP_IfNotZero 67 /* synopsis: if r[P1]!=0 then r[P1]-=P3, goto P2 */
-#define OP_DecrJumpZero 68 /* synopsis: if (--r[P1])==0 goto P2 */
-#define OP_IncrVacuum 69
-#define OP_VNext 70
-#define OP_Init 71 /* synopsis: Start at P2 */
-#define OP_Return 72
-#define OP_EndCoroutine 73
-#define OP_HaltIfNull 74 /* synopsis: if r[P3]=null halt */
-#define OP_Halt 75
-#define OP_Integer 76 /* synopsis: r[P2]=P1 */
-#define OP_Int64 77 /* synopsis: r[P2]=P4 */
-#define OP_String 78 /* synopsis: r[P2]='P4' (len=P1) */
-#define OP_Null 79 /* synopsis: r[P2..P3]=NULL */
-#define OP_SoftNull 80 /* synopsis: r[P1]=NULL */
-#define OP_Blob 81 /* synopsis: r[P2]=P4 (len=P1) */
-#define OP_Variable 82 /* synopsis: r[P2]=parameter(P1,P4) */
-#define OP_Move 83 /* synopsis: r[P2@P3]=r[P1@P3] */
-#define OP_Copy 84 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */
-#define OP_SCopy 85 /* synopsis: r[P2]=r[P1] */
-#define OP_IntCopy 86 /* synopsis: r[P2]=r[P1] */
-#define OP_ResultRow 87 /* synopsis: output=r[P1@P2] */
-#define OP_CollSeq 88
-#define OP_Function0 89 /* synopsis: r[P3]=func(r[P2@P5]) */
-#define OP_Function 90 /* synopsis: r[P3]=func(r[P2@P5]) */
-#define OP_AddImm 91 /* synopsis: r[P1]=r[P1]+P2 */
-#define OP_RealAffinity 92
-#define OP_Cast 93 /* synopsis: affinity(r[P1]) */
-#define OP_Permutation 94
-#define OP_Compare 95 /* synopsis: r[P1@P3] <-> r[P2@P3] */
-#define OP_Column 96 /* synopsis: r[P3]=PX */
-#define OP_String8 97 /* same as TK_STRING, synopsis: r[P2]='P4' */
-#define OP_Affinity 98 /* synopsis: affinity(r[P1@P2]) */
-#define OP_MakeRecord 99 /* synopsis: r[P3]=mkrec(r[P1@P2]) */
-#define OP_Count 100 /* synopsis: r[P2]=count() */
-#define OP_ReadCookie 101
-#define OP_SetCookie 102
-#define OP_ReopenIdx 103 /* synopsis: root=P2 iDb=P3 */
-#define OP_OpenRead 104 /* synopsis: root=P2 iDb=P3 */
-#define OP_OpenWrite 105 /* synopsis: root=P2 iDb=P3 */
-#define OP_OpenAutoindex 106 /* synopsis: nColumn=P2 */
-#define OP_OpenEphemeral 107 /* synopsis: nColumn=P2 */
-#define OP_SorterOpen 108
-#define OP_SequenceTest 109 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */
-#define OP_OpenPseudo 110 /* synopsis: P3 columns in r[P2] */
-#define OP_Close 111
-#define OP_ColumnsUsed 112
-#define OP_Sequence 113 /* synopsis: r[P2]=cursor[P1].ctr++ */
-#define OP_NewRowid 114 /* synopsis: r[P2]=rowid */
-#define OP_Insert 115 /* synopsis: intkey=r[P3] data=r[P2] */
-#define OP_InsertInt 116 /* synopsis: intkey=P3 data=r[P2] */
-#define OP_Delete 117
-#define OP_ResetCount 118
-#define OP_SorterCompare 119 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
-#define OP_SorterData 120 /* synopsis: r[P2]=data */
-#define OP_RowKey 121 /* synopsis: r[P2]=key */
-#define OP_RowData 122 /* synopsis: r[P2]=data */
-#define OP_Rowid 123 /* synopsis: r[P2]=rowid */
-#define OP_NullRow 124
-#define OP_SorterInsert 125
-#define OP_IdxInsert 126 /* synopsis: key=r[P2] */
-#define OP_IdxDelete 127 /* synopsis: key=r[P2@P3] */
-#define OP_Seek 128 /* synopsis: Move P3 to P1.rowid */
-#define OP_IdxRowid 129 /* synopsis: r[P2]=rowid */
-#define OP_Destroy 130
-#define OP_Clear 131
-#define OP_Real 132 /* same as TK_FLOAT, synopsis: r[P2]=P4 */
-#define OP_ResetSorter 133
-#define OP_CreateIndex 134 /* synopsis: r[P2]=root iDb=P1 */
-#define OP_CreateTable 135 /* synopsis: r[P2]=root iDb=P1 */
-#define OP_ParseSchema 136
-#define OP_LoadAnalysis 137
-#define OP_DropTable 138
-#define OP_DropIndex 139
-#define OP_DropTrigger 140
-#define OP_IntegrityCk 141
-#define OP_RowSetAdd 142 /* synopsis: rowset(P1)=r[P2] */
-#define OP_Param 143
-#define OP_FkCounter 144 /* synopsis: fkctr[P1]+=P2 */
-#define OP_MemMax 145 /* synopsis: r[P1]=max(r[P1],r[P2]) */
-#define OP_OffsetLimit 146 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
-#define OP_AggStep0 147 /* synopsis: accum=r[P3] step(r[P2@P5]) */
-#define OP_AggStep 148 /* synopsis: accum=r[P3] step(r[P2@P5]) */
-#define OP_AggFinal 149 /* synopsis: accum=r[P1] N=P2 */
-#define OP_Expire 150
-#define OP_TableLock 151 /* synopsis: iDb=P1 root=P2 write=P3 */
-#define OP_VBegin 152
-#define OP_VCreate 153
-#define OP_VDestroy 154
-#define OP_VOpen 155
-#define OP_VColumn 156 /* synopsis: r[P3]=vcolumn(P2) */
-#define OP_VRename 157
-#define OP_Pagecount 158
-#define OP_MaxPgcnt 159
-#define OP_CursorHint 160
-#define OP_Noop 161
-#define OP_Explain 162
+#define OP_IfNot 20 /* jump */
+#define OP_IfNullRow 21 /* jump, synopsis: if P1.nullRow then r[P3]=NULL, goto P2 */
+#define OP_SeekLT 22 /* jump, synopsis: key=r[P3@P4] */
+#define OP_SeekLE 23 /* jump, synopsis: key=r[P3@P4] */
+#define OP_SeekGE 24 /* jump, synopsis: key=r[P3@P4] */
+#define OP_SeekGT 25 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IfNotOpen 26 /* jump, synopsis: if( !csr[P1] ) goto P2 */
+#define OP_IfNoHope 27 /* jump, synopsis: key=r[P3@P4] */
+#define OP_NoConflict 28 /* jump, synopsis: key=r[P3@P4] */
+#define OP_NotFound 29 /* jump, synopsis: key=r[P3@P4] */
+#define OP_Found 30 /* jump, synopsis: key=r[P3@P4] */
+#define OP_SeekRowid 31 /* jump, synopsis: intkey=r[P3] */
+#define OP_NotExists 32 /* jump, synopsis: intkey=r[P3] */
+#define OP_Last 33 /* jump */
+#define OP_IfSmaller 34 /* jump */
+#define OP_SorterSort 35 /* jump */
+#define OP_Sort 36 /* jump */
+#define OP_Rewind 37 /* jump */
+#define OP_IdxLE 38 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IdxGT 39 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IdxLT 40 /* jump, synopsis: key=r[P3@P4] */
+#define OP_IdxGE 41 /* jump, synopsis: key=r[P3@P4] */
+#define OP_RowSetRead 42 /* jump, synopsis: r[P3]=rowset(P1) */
+#define OP_Or 43 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */
+#define OP_And 44 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */
+#define OP_RowSetTest 45 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */
+#define OP_Program 46 /* jump */
+#define OP_FkIfZero 47 /* jump, synopsis: if fkctr[P1]==0 goto P2 */
+#define OP_IfPos 48 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */
+#define OP_IfNotZero 49 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */
+#define OP_IsNull 50 /* jump, same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */
+#define OP_NotNull 51 /* jump, same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */
+#define OP_Ne 52 /* jump, same as TK_NE, synopsis: IF r[P3]!=r[P1] */
+#define OP_Eq 53 /* jump, same as TK_EQ, synopsis: IF r[P3]==r[P1] */
+#define OP_Gt 54 /* jump, same as TK_GT, synopsis: IF r[P3]>r[P1] */
+#define OP_Le 55 /* jump, same as TK_LE, synopsis: IF r[P3]<=r[P1] */
+#define OP_Lt 56 /* jump, same as TK_LT, synopsis: IF r[P3]<r[P1] */
+#define OP_Ge 57 /* jump, same as TK_GE, synopsis: IF r[P3]>=r[P1] */
+#define OP_ElseNotEq 58 /* jump, same as TK_ESCAPE */
+#define OP_DecrJumpZero 59 /* jump, synopsis: if (--r[P1])==0 goto P2 */
+#define OP_IncrVacuum 60 /* jump */
+#define OP_VNext 61 /* jump */
+#define OP_Init 62 /* jump, synopsis: Start at P2 */
+#define OP_PureFunc 63 /* synopsis: r[P3]=func(r[P2@P5]) */
+#define OP_Function 64 /* synopsis: r[P3]=func(r[P2@P5]) */
+#define OP_Return 65
+#define OP_EndCoroutine 66
+#define OP_HaltIfNull 67 /* synopsis: if r[P3]=null halt */
+#define OP_Halt 68
+#define OP_Integer 69 /* synopsis: r[P2]=P1 */
+#define OP_Int64 70 /* synopsis: r[P2]=P4 */
+#define OP_String 71 /* synopsis: r[P2]='P4' (len=P1) */
+#define OP_Null 72 /* synopsis: r[P2..P3]=NULL */
+#define OP_SoftNull 73 /* synopsis: r[P1]=NULL */
+#define OP_Blob 74 /* synopsis: r[P2]=P4 (len=P1) */
+#define OP_Variable 75 /* synopsis: r[P2]=parameter(P1,P4) */
+#define OP_Move 76 /* synopsis: r[P2@P3]=r[P1@P3] */
+#define OP_Copy 77 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */
+#define OP_SCopy 78 /* synopsis: r[P2]=r[P1] */
+#define OP_IntCopy 79 /* synopsis: r[P2]=r[P1] */
+#define OP_ResultRow 80 /* synopsis: output=r[P1@P2] */
+#define OP_CollSeq 81
+#define OP_AddImm 82 /* synopsis: r[P1]=r[P1]+P2 */
+#define OP_RealAffinity 83
+#define OP_Cast 84 /* synopsis: affinity(r[P1]) */
+#define OP_Permutation 85
+#define OP_Compare 86 /* synopsis: r[P1@P3] <-> r[P2@P3] */
+#define OP_IsTrue 87 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */
+#define OP_Offset 88 /* synopsis: r[P3] = sqlite_offset(P1) */
+#define OP_Column 89 /* synopsis: r[P3]=PX */
+#define OP_Affinity 90 /* synopsis: affinity(r[P1@P2]) */
+#define OP_MakeRecord 91 /* synopsis: r[P3]=mkrec(r[P1@P2]) */
+#define OP_Count 92 /* synopsis: r[P2]=count() */
+#define OP_ReadCookie 93
+#define OP_SetCookie 94
+#define OP_ReopenIdx 95 /* synopsis: root=P2 iDb=P3 */
+#define OP_OpenRead 96 /* synopsis: root=P2 iDb=P3 */
+#define OP_OpenWrite 97 /* synopsis: root=P2 iDb=P3 */
+#define OP_OpenDup 98
+#define OP_OpenAutoindex 99 /* synopsis: nColumn=P2 */
+#define OP_OpenEphemeral 100 /* synopsis: nColumn=P2 */
+#define OP_BitAnd 101 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */
+#define OP_BitOr 102 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */
+#define OP_ShiftLeft 103 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<<r[P1] */
+#define OP_ShiftRight 104 /* same as TK_RSHIFT, synopsis: r[P3]=r[P2]>>r[P1] */
+#define OP_Add 105 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */
+#define OP_Subtract 106 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */
+#define OP_Multiply 107 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */
+#define OP_Divide 108 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */
+#define OP_Remainder 109 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */
+#define OP_Concat 110 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */
+#define OP_SorterOpen 111
+#define OP_BitNot 112 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */
+#define OP_SequenceTest 113 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */
+#define OP_OpenPseudo 114 /* synopsis: P3 columns in r[P2] */
+#define OP_String8 115 /* same as TK_STRING, synopsis: r[P2]='P4' */
+#define OP_Close 116
+#define OP_ColumnsUsed 117
+#define OP_SeekHit 118 /* synopsis: seekHit=P2 */
+#define OP_Sequence 119 /* synopsis: r[P2]=cursor[P1].ctr++ */
+#define OP_NewRowid 120 /* synopsis: r[P2]=rowid */
+#define OP_Insert 121 /* synopsis: intkey=r[P3] data=r[P2] */
+#define OP_Delete 122
+#define OP_ResetCount 123
+#define OP_SorterCompare 124 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */
+#define OP_SorterData 125 /* synopsis: r[P2]=data */
+#define OP_RowData 126 /* synopsis: r[P2]=data */
+#define OP_Rowid 127 /* synopsis: r[P2]=rowid */
+#define OP_NullRow 128
+#define OP_SeekEnd 129
+#define OP_SorterInsert 130 /* synopsis: key=r[P2] */
+#define OP_IdxInsert 131 /* synopsis: key=r[P2] */
+#define OP_IdxDelete 132 /* synopsis: key=r[P2@P3] */
+#define OP_DeferredSeek 133 /* synopsis: Move P3 to P1.rowid if needed */
+#define OP_IdxRowid 134 /* synopsis: r[P2]=rowid */
+#define OP_FinishSeek 135
+#define OP_Destroy 136
+#define OP_Clear 137
+#define OP_ResetSorter 138
+#define OP_CreateBtree 139 /* synopsis: r[P2]=root iDb=P1 flags=P3 */
+#define OP_SqlExec 140
+#define OP_ParseSchema 141
+#define OP_LoadAnalysis 142
+#define OP_DropTable 143
+#define OP_DropIndex 144
+#define OP_DropTrigger 145
+#define OP_IntegrityCk 146
+#define OP_RowSetAdd 147 /* synopsis: rowset(P1)=r[P2] */
+#define OP_Param 148
+#define OP_FkCounter 149 /* synopsis: fkctr[P1]+=P2 */
+#define OP_Real 150 /* same as TK_FLOAT, synopsis: r[P2]=P4 */
+#define OP_MemMax 151 /* synopsis: r[P1]=max(r[P1],r[P2]) */
+#define OP_OffsetLimit 152 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */
+#define OP_AggInverse 153 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */
+#define OP_AggStep 154 /* synopsis: accum=r[P3] step(r[P2@P5]) */
+#define OP_AggStep1 155 /* synopsis: accum=r[P3] step(r[P2@P5]) */
+#define OP_AggValue 156 /* synopsis: r[P3]=value N=P2 */
+#define OP_AggFinal 157 /* synopsis: accum=r[P1] N=P2 */
+#define OP_Expire 158
+#define OP_CursorLock 159
+#define OP_CursorUnlock 160
+#define OP_TableLock 161 /* synopsis: iDb=P1 root=P2 write=P3 */
+#define OP_VBegin 162
+#define OP_VCreate 163
+#define OP_VDestroy 164
+#define OP_VOpen 165
+#define OP_VColumn 166 /* synopsis: r[P3]=vcolumn(P2) */
+#define OP_VRename 167
+#define OP_Pagecount 168
+#define OP_MaxPgcnt 169
+#define OP_Trace 170
+#define OP_CursorHint 171
+#define OP_ReleaseReg 172 /* synopsis: release r[P1@P2] mask P3 */
+#define OP_Noop 173
+#define OP_Explain 174
+#define OP_Abortable 175
/* Properties such as "out2" or "jump" that are specified in
** comments following the "case" for each opcode in the vdbe.c
@@ -12734,114 +15396,162 @@ typedef struct VdbeOpList VdbeOpList;
#define OPFLG_OUT2 0x10 /* out2: P2 is an output */
#define OPFLG_OUT3 0x20 /* out3: P3 is an output */
#define OPFLG_INITIALIZER {\
-/* 0 */ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01,\
-/* 8 */ 0x00, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01,\
-/* 16 */ 0x03, 0x03, 0x01, 0x12, 0x01, 0x03, 0x03, 0x09,\
-/* 24 */ 0x09, 0x09, 0x09, 0x26, 0x26, 0x09, 0x09, 0x09,\
-/* 32 */ 0x09, 0x09, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\
-/* 40 */ 0x0b, 0x0b, 0x01, 0x26, 0x26, 0x26, 0x26, 0x26,\
-/* 48 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x01, 0x12, 0x01,\
-/* 56 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x23, 0x0b,\
-/* 64 */ 0x01, 0x01, 0x03, 0x03, 0x03, 0x01, 0x01, 0x01,\
-/* 72 */ 0x02, 0x02, 0x08, 0x00, 0x10, 0x10, 0x10, 0x10,\
-/* 80 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00,\
-/* 88 */ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,\
-/* 96 */ 0x00, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00,\
-/* 104 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
-/* 112 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,\
-/* 120 */ 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x04, 0x00,\
-/* 128 */ 0x00, 0x10, 0x10, 0x00, 0x10, 0x00, 0x10, 0x10,\
-/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x10,\
-/* 144 */ 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00,\
-/* 152 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10,\
-/* 160 */ 0x00, 0x00, 0x00,}
-
-/* The sqlite3P2Values() routine is able to run faster if it knows
+/* 0 */ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x10,\
+/* 8 */ 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x03, 0x03,\
+/* 16 */ 0x01, 0x01, 0x03, 0x12, 0x03, 0x01, 0x09, 0x09,\
+/* 24 */ 0x09, 0x09, 0x01, 0x09, 0x09, 0x09, 0x09, 0x09,\
+/* 32 */ 0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\
+/* 40 */ 0x01, 0x01, 0x23, 0x26, 0x26, 0x0b, 0x01, 0x01,\
+/* 48 */ 0x03, 0x03, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\
+/* 56 */ 0x0b, 0x0b, 0x01, 0x03, 0x01, 0x01, 0x01, 0x00,\
+/* 64 */ 0x00, 0x02, 0x02, 0x08, 0x00, 0x10, 0x10, 0x10,\
+/* 72 */ 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10,\
+/* 80 */ 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, 0x12,\
+/* 88 */ 0x20, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00,\
+/* 96 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x26, 0x26,\
+/* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x00,\
+/* 112 */ 0x12, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,\
+/* 120 */ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\
+/* 128 */ 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x10, 0x00,\
+/* 136 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\
+/* 144 */ 0x00, 0x00, 0x00, 0x06, 0x10, 0x00, 0x10, 0x04,\
+/* 152 */ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+/* 160 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+/* 168 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\
+}
+
+/* The tdsqlite3P2Values() routine is able to run faster if it knows
** the value of the largest JUMP opcode. The smaller the maximum
** JUMP opcode the better, so the mkopcodeh.tcl script that
** generated this include file strives to group all JUMP opcodes
** together near the beginning of the list.
*/
-#define SQLITE_MX_JUMP_OPCODE 71 /* Maximum JUMP opcode */
+#define SQLITE_MX_JUMP_OPCODE 62 /* Maximum JUMP opcode */
/************** End of opcodes.h *********************************************/
/************** Continuing where we left off in vdbe.h ***********************/
/*
+** Additional non-public SQLITE_PREPARE_* flags
+*/
+#define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */
+#define SQLITE_PREPARE_MASK 0x0f /* Mask of public flags */
+
+/*
** Prototypes for the VDBE interface. See comments on the implementation
** for a description of what each of these routines does.
*/
-SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse*);
-SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe*,int);
-SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe*,int,int);
-SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe*,int,int,int);
-SQLITE_PRIVATE int sqlite3VdbeGoto(Vdbe*,int);
-SQLITE_PRIVATE int sqlite3VdbeLoadString(Vdbe*,int,const char*);
-SQLITE_PRIVATE void sqlite3VdbeMultiLoad(Vdbe*,int,const char*,...);
-SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe*,int,int,int,int);
-SQLITE_PRIVATE int sqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int);
-SQLITE_PRIVATE int sqlite3VdbeAddOp4Dup8(Vdbe*,int,int,int,int,const u8*,int);
-SQLITE_PRIVATE int sqlite3VdbeAddOp4Int(Vdbe*,int,int,int,int,int);
-SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(Vdbe*,int);
+SQLITE_PRIVATE Vdbe *tdsqlite3VdbeCreate(Parse*);
+SQLITE_PRIVATE Parse *tdsqlite3VdbeParser(Vdbe*);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp0(Vdbe*,int);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp1(Vdbe*,int,int);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp2(Vdbe*,int,int,int);
+SQLITE_PRIVATE int tdsqlite3VdbeGoto(Vdbe*,int);
+SQLITE_PRIVATE int tdsqlite3VdbeLoadString(Vdbe*,int,const char*);
+SQLITE_PRIVATE void tdsqlite3VdbeMultiLoad(Vdbe*,int,const char*,...);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp3(Vdbe*,int,int,int,int);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp4Dup8(Vdbe*,int,int,int,int,const u8*,int);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp4Int(Vdbe*,int,int,int,int,int);
+SQLITE_PRIVATE int tdsqlite3VdbeAddFunctionCall(Parse*,int,int,int,int,const FuncDef*,int);
+SQLITE_PRIVATE void tdsqlite3VdbeEndCoroutine(Vdbe*,int);
#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS)
-SQLITE_PRIVATE void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N);
-#else
-# define sqlite3VdbeVerifyNoMallocRequired(A,B)
-#endif
-SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp, int iLineno);
-SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*);
-SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe*, u32 addr, u8);
-SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, u32 addr, int P1);
-SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, u32 addr, int P2);
-SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, u32 addr, int P3);
-SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u8 P5);
-SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr);
-SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe*, int addr);
-SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe*, u8 op);
-SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N);
-SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse*, Index*);
-SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int);
-SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int);
-SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3*,Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeMakeReady(Vdbe*,Parse*);
-SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int);
-SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N);
+SQLITE_PRIVATE void tdsqlite3VdbeVerifyNoResultRow(Vdbe *p);
+#else
+# define tdsqlite3VdbeVerifyNoMallocRequired(A,B)
+# define tdsqlite3VdbeVerifyNoResultRow(A)
+#endif
+#if defined(SQLITE_DEBUG)
+SQLITE_PRIVATE void tdsqlite3VdbeVerifyAbortable(Vdbe *p, int);
+#else
+# define tdsqlite3VdbeVerifyAbortable(A,B)
+#endif
+SQLITE_PRIVATE VdbeOp *tdsqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno);
+#ifndef SQLITE_OMIT_EXPLAIN
+SQLITE_PRIVATE void tdsqlite3VdbeExplain(Parse*,u8,const char*,...);
+SQLITE_PRIVATE void tdsqlite3VdbeExplainPop(Parse*);
+SQLITE_PRIVATE int tdsqlite3VdbeExplainParent(Parse*);
+# define ExplainQueryPlan(P) tdsqlite3VdbeExplain P
+# define ExplainQueryPlanPop(P) tdsqlite3VdbeExplainPop(P)
+# define ExplainQueryPlanParent(P) tdsqlite3VdbeExplainParent(P)
+#else
+# define ExplainQueryPlan(P)
+# define ExplainQueryPlanPop(P)
+# define ExplainQueryPlanParent(P) 0
+# define tdsqlite3ExplainBreakpoint(A,B) /*no-op*/
+#endif
+#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN)
+SQLITE_PRIVATE void tdsqlite3ExplainBreakpoint(const char*,const char*);
+#else
+# define tdsqlite3ExplainBreakpoint(A,B) /*no-op*/
+#endif
+SQLITE_PRIVATE void tdsqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*);
+SQLITE_PRIVATE void tdsqlite3VdbeChangeOpcode(Vdbe*, int addr, u8);
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP1(Vdbe*, int addr, int P1);
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP2(Vdbe*, int addr, int P2);
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP3(Vdbe*, int addr, int P3);
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP5(Vdbe*, u16 P5);
+SQLITE_PRIVATE void tdsqlite3VdbeJumpHere(Vdbe*, int addr);
+SQLITE_PRIVATE int tdsqlite3VdbeChangeToNoop(Vdbe*, int addr);
+SQLITE_PRIVATE int tdsqlite3VdbeDeletePriorOpcode(Vdbe*, u8 op);
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *, int);
-#endif
-SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe*);
-SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe*,int);
-SQLITE_PRIVATE int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, void(*)(void*));
-SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe*);
-SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, int);
-SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*);
-SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe*, int*, int*);
-SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe*, int, u8);
-SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe*, int);
+SQLITE_PRIVATE void tdsqlite3VdbeReleaseRegisters(Parse*,int addr, int n, u32 mask, int);
+#else
+# define tdsqlite3VdbeReleaseRegisters(P,A,N,M,F)
+#endif
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N);
+SQLITE_PRIVATE void tdsqlite3VdbeAppendP4(Vdbe*, void *pP4, int p4type);
+SQLITE_PRIVATE void tdsqlite3VdbeSetP4KeyInfo(Parse*, Index*);
+SQLITE_PRIVATE void tdsqlite3VdbeUsesBtree(Vdbe*, int);
+SQLITE_PRIVATE VdbeOp *tdsqlite3VdbeGetOp(Vdbe*, int);
+SQLITE_PRIVATE int tdsqlite3VdbeMakeLabel(Parse*);
+SQLITE_PRIVATE void tdsqlite3VdbeRunOnlyOnce(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeReusable(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeDelete(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeClearObject(tdsqlite3*,Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeMakeReady(Vdbe*,Parse*);
+SQLITE_PRIVATE int tdsqlite3VdbeFinalize(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeResolveLabel(Vdbe*, int);
+SQLITE_PRIVATE int tdsqlite3VdbeCurrentAddr(Vdbe*);
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int tdsqlite3VdbeAssertMayAbort(Vdbe *, int);
+#endif
+SQLITE_PRIVATE void tdsqlite3VdbeResetStepResult(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeRewind(Vdbe*);
+SQLITE_PRIVATE int tdsqlite3VdbeReset(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeSetNumCols(Vdbe*,int);
+SQLITE_PRIVATE int tdsqlite3VdbeSetColName(Vdbe*, int, int, const char *, void(*)(void*));
+SQLITE_PRIVATE void tdsqlite3VdbeCountChanges(Vdbe*);
+SQLITE_PRIVATE tdsqlite3 *tdsqlite3VdbeDb(Vdbe*);
+SQLITE_PRIVATE u8 tdsqlite3VdbePrepareFlags(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeSetSql(Vdbe*, const char *z, int n, u8);
+#ifdef SQLITE_ENABLE_NORMALIZE
+SQLITE_PRIVATE void tdsqlite3VdbeAddDblquoteStr(tdsqlite3*,Vdbe*,const char*);
+SQLITE_PRIVATE int tdsqlite3VdbeUsesDoubleQuotedString(Vdbe*,const char*);
+#endif
+SQLITE_PRIVATE void tdsqlite3VdbeSwap(Vdbe*,Vdbe*);
+SQLITE_PRIVATE VdbeOp *tdsqlite3VdbeTakeOpArray(Vdbe*, int*, int*);
+SQLITE_PRIVATE tdsqlite3_value *tdsqlite3VdbeGetBoundValue(Vdbe*, int, u8);
+SQLITE_PRIVATE void tdsqlite3VdbeSetVarmask(Vdbe*, int);
#ifndef SQLITE_OMIT_TRACE
-SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*);
+SQLITE_PRIVATE char *tdsqlite3VdbeExpandSql(Vdbe*, const char*);
#endif
-SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*);
+SQLITE_PRIVATE int tdsqlite3MemCompare(const Mem*, const Mem*, const CollSeq*);
+SQLITE_PRIVATE int tdsqlite3BlobCompare(const Mem*, const Mem*);
-SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*);
-SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*);
-SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int);
-SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *, char *, int, char **);
+SQLITE_PRIVATE void tdsqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*);
+SQLITE_PRIVATE int tdsqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*);
+SQLITE_PRIVATE int tdsqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int);
+SQLITE_PRIVATE UnpackedRecord *tdsqlite3VdbeAllocUnpackedRecord(KeyInfo*);
typedef int (*RecordCompare)(int,const void*,UnpackedRecord*);
-SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord*);
+SQLITE_PRIVATE RecordCompare tdsqlite3VdbeFindCompare(UnpackedRecord*);
-#ifndef SQLITE_OMIT_TRIGGER
-SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *);
-#endif
+SQLITE_PRIVATE void tdsqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *);
+SQLITE_PRIVATE int tdsqlite3VdbeHasSubProgram(Vdbe*);
+
+SQLITE_PRIVATE int tdsqlite3NotPureFunc(tdsqlite3_context*);
/* Use SQLITE_ENABLE_COMMENTS to enable generation of extra comments on
** each VDBE opcode.
@@ -12851,12 +15561,12 @@ SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *);
** generator.
*/
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
-SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe*, const char*, ...);
-# define VdbeComment(X) sqlite3VdbeComment X
-SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...);
-# define VdbeNoopComment(X) sqlite3VdbeNoopComment X
+SQLITE_PRIVATE void tdsqlite3VdbeComment(Vdbe*, const char*, ...);
+# define VdbeComment(X) tdsqlite3VdbeComment X
+SQLITE_PRIVATE void tdsqlite3VdbeNoopComment(Vdbe*, const char*, ...);
+# define VdbeNoopComment(X) tdsqlite3VdbeNoopComment X
# ifdef SQLITE_ENABLE_MODULE_COMMENTS
-# define VdbeModuleComment(X) sqlite3VdbeNoopComment X
+# define VdbeModuleComment(X) tdsqlite3VdbeNoopComment X
# else
# define VdbeModuleComment(X)
# endif
@@ -12883,30 +15593,63 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...);
**
** VdbeCoverageNeverTaken(v) // Previous branch is never taken
**
+** VdbeCoverageNeverNull(v) // Previous three-way branch is only
+** // taken on the first two ways. The
+** // NULL option is not possible
+**
+** VdbeCoverageEqNe(v) // Previous OP_Jump is only interested
+** // in distingishing equal and not-equal.
+**
** Every VDBE branch operation must be tagged with one of the macros above.
** If not, then when "make test" is run with -DSQLITE_VDBE_COVERAGE and
** -DSQLITE_DEBUG then an ALWAYS() will fail in the vdbeTakeBranch()
** routine in vdbe.c, alerting the developer to the missed tag.
+**
+** During testing, the test application will invoke
+** tdsqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE,...) to set a callback
+** routine that is invoked as each bytecode branch is taken. The callback
+** contains the sqlite3.c source line number ov the VdbeCoverage macro and
+** flags to indicate whether or not the branch was taken. The test application
+** is responsible for keeping track of this and reporting byte-code branches
+** that are never taken.
+**
+** See the VdbeBranchTaken() macro and vdbeTakeBranch() function in the
+** vdbe.c source file for additional information.
*/
#ifdef SQLITE_VDBE_COVERAGE
-SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe*,int);
-# define VdbeCoverage(v) sqlite3VdbeSetLineNumber(v,__LINE__)
-# define VdbeCoverageIf(v,x) if(x)sqlite3VdbeSetLineNumber(v,__LINE__)
-# define VdbeCoverageAlwaysTaken(v) sqlite3VdbeSetLineNumber(v,2);
-# define VdbeCoverageNeverTaken(v) sqlite3VdbeSetLineNumber(v,1);
+SQLITE_PRIVATE void tdsqlite3VdbeSetLineNumber(Vdbe*,int);
+# define VdbeCoverage(v) tdsqlite3VdbeSetLineNumber(v,__LINE__)
+# define VdbeCoverageIf(v,x) if(x)tdsqlite3VdbeSetLineNumber(v,__LINE__)
+# define VdbeCoverageAlwaysTaken(v) \
+ tdsqlite3VdbeSetLineNumber(v,__LINE__|0x5000000);
+# define VdbeCoverageNeverTaken(v) \
+ tdsqlite3VdbeSetLineNumber(v,__LINE__|0x6000000);
+# define VdbeCoverageNeverNull(v) \
+ tdsqlite3VdbeSetLineNumber(v,__LINE__|0x4000000);
+# define VdbeCoverageNeverNullIf(v,x) \
+ if(x)tdsqlite3VdbeSetLineNumber(v,__LINE__|0x4000000);
+# define VdbeCoverageEqNe(v) \
+ tdsqlite3VdbeSetLineNumber(v,__LINE__|0x8000000);
# define VDBE_OFFSET_LINENO(x) (__LINE__+x)
#else
# define VdbeCoverage(v)
# define VdbeCoverageIf(v,x)
# define VdbeCoverageAlwaysTaken(v)
# define VdbeCoverageNeverTaken(v)
+# define VdbeCoverageNeverNull(v)
+# define VdbeCoverageNeverNullIf(v,x)
+# define VdbeCoverageEqNe(v)
# define VDBE_OFFSET_LINENO(x) 0
#endif
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
-SQLITE_PRIVATE void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*);
+SQLITE_PRIVATE void tdsqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*);
#else
-# define sqlite3VdbeScanStatus(a,b,c,d,e)
+# define tdsqlite3VdbeScanStatus(a,b,c,d,e)
+#endif
+
+#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE)
+SQLITE_PRIVATE void tdsqlite3VdbePrintOp(FILE*, int, VdbeOp*);
#endif
#endif /* SQLITE_VDBE_H */
@@ -12937,7 +15680,7 @@ SQLITE_PRIVATE void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const ch
/*
** Default maximum size for persistent journal files. A negative
** value means no limit. This value may be overridden using the
-** sqlite3PagerJournalSizeLimit() API. See also "PRAGMA journal_size_limit".
+** tdsqlite3PagerJournalSizeLimit() API. See also "PRAGMA journal_size_limit".
*/
#ifndef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT
#define SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT -1
@@ -12970,7 +15713,7 @@ typedef struct PgHdr DbPage;
#define PAGER_MJ_PGNO(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1))
/*
-** Allowed values for the flags parameter to sqlite3PagerOpen().
+** Allowed values for the flags parameter to tdsqlite3PagerOpen().
**
** NOTE: These values must match the corresponding BTREE_ values in btree.h.
*/
@@ -12978,7 +15721,7 @@ typedef struct PgHdr DbPage;
#define PAGER_MEMORY 0x0002 /* In-memory database */
/*
-** Valid values for the second argument to sqlite3PagerLockingMode().
+** Valid values for the second argument to tdsqlite3PagerLockingMode().
*/
#define PAGER_LOCKINGMODE_QUERY -1
#define PAGER_LOCKINGMODE_NORMAL 0
@@ -13000,13 +15743,13 @@ typedef struct PgHdr DbPage;
#define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */
/*
-** Flags that make up the mask passed to sqlite3PagerGet().
+** Flags that make up the mask passed to tdsqlite3PagerGet().
*/
#define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */
#define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */
/*
-** Flags for sqlite3PagerSetFlags()
+** Flags for tdsqlite3PagerSetFlags()
**
** Value constraints (enforced via assert()):
** PAGER_FULLFSYNC == SQLITE_FullFSync
@@ -13030,8 +15773,8 @@ typedef struct PgHdr DbPage;
*/
/* Open and close a Pager connection. */
-SQLITE_PRIVATE int sqlite3PagerOpen(
- sqlite3_vfs*,
+SQLITE_PRIVATE int tdsqlite3PagerOpen(
+ tdsqlite3_vfs*,
Pager **ppPager,
const char*,
int,
@@ -13039,110 +15782,120 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
int,
void(*)(DbPage*)
);
-SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager);
-SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*);
+SQLITE_PRIVATE int tdsqlite3PagerClose(Pager *pPager, tdsqlite3*);
+SQLITE_PRIVATE int tdsqlite3PagerReadFileheader(Pager*, int, unsigned char*);
/* Functions used to configure a Pager object. */
-SQLITE_PRIVATE void sqlite3PagerSetBusyhandler(Pager*, int(*)(void *), void *);
-SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u32*, int);
+SQLITE_PRIVATE void tdsqlite3PagerSetBusyHandler(Pager*, int(*)(void *), void *);
+SQLITE_PRIVATE int tdsqlite3PagerSetPagesize(Pager*, u32*, int);
#ifdef SQLITE_HAS_CODEC
-SQLITE_PRIVATE void sqlite3PagerAlignReserve(Pager*,Pager*);
-#endif
-SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager*, int);
-SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int);
-SQLITE_PRIVATE int sqlite3PagerSetSpillsize(Pager*, int);
-SQLITE_PRIVATE void sqlite3PagerSetMmapLimit(Pager *, sqlite3_int64);
-SQLITE_PRIVATE void sqlite3PagerShrink(Pager*);
-SQLITE_PRIVATE void sqlite3PagerSetFlags(Pager*,unsigned);
-SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *, int);
-SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *, int);
-SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager*);
-SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager*);
-SQLITE_PRIVATE i64 sqlite3PagerJournalSizeLimit(Pager *, i64);
-SQLITE_PRIVATE sqlite3_backup **sqlite3PagerBackupPtr(Pager*);
-SQLITE_PRIVATE int sqlite3PagerFlush(Pager*);
+SQLITE_PRIVATE void tdsqlite3PagerAlignReserve(Pager*,Pager*);
+#endif
+SQLITE_PRIVATE int tdsqlite3PagerMaxPageCount(Pager*, int);
+SQLITE_PRIVATE void tdsqlite3PagerSetCachesize(Pager*, int);
+SQLITE_PRIVATE int tdsqlite3PagerSetSpillsize(Pager*, int);
+SQLITE_PRIVATE void tdsqlite3PagerSetMmapLimit(Pager *, tdsqlite3_int64);
+SQLITE_PRIVATE void tdsqlite3PagerShrink(Pager*);
+SQLITE_PRIVATE void tdsqlite3PagerSetFlags(Pager*,unsigned);
+SQLITE_PRIVATE int tdsqlite3PagerLockingMode(Pager *, int);
+SQLITE_PRIVATE int tdsqlite3PagerSetJournalMode(Pager *, int);
+SQLITE_PRIVATE int tdsqlite3PagerGetJournalMode(Pager*);
+SQLITE_PRIVATE int tdsqlite3PagerOkToChangeJournalMode(Pager*);
+SQLITE_PRIVATE i64 tdsqlite3PagerJournalSizeLimit(Pager *, i64);
+SQLITE_PRIVATE tdsqlite3_backup **tdsqlite3PagerBackupPtr(Pager*);
+SQLITE_PRIVATE int tdsqlite3PagerFlush(Pager*);
/* Functions used to obtain and release page references. */
-SQLITE_PRIVATE int sqlite3PagerGet(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag);
-SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno);
-SQLITE_PRIVATE void sqlite3PagerRef(DbPage*);
-SQLITE_PRIVATE void sqlite3PagerUnref(DbPage*);
-SQLITE_PRIVATE void sqlite3PagerUnrefNotNull(DbPage*);
+SQLITE_PRIVATE int tdsqlite3PagerGet(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag);
+SQLITE_PRIVATE DbPage *tdsqlite3PagerLookup(Pager *pPager, Pgno pgno);
+SQLITE_PRIVATE void tdsqlite3PagerRef(DbPage*);
+SQLITE_PRIVATE void tdsqlite3PagerUnref(DbPage*);
+SQLITE_PRIVATE void tdsqlite3PagerUnrefNotNull(DbPage*);
+SQLITE_PRIVATE void tdsqlite3PagerUnrefPageOne(DbPage*);
/* Operations on page references. */
-SQLITE_PRIVATE int sqlite3PagerWrite(DbPage*);
-SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage*);
-SQLITE_PRIVATE int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int);
-SQLITE_PRIVATE int sqlite3PagerPageRefcount(DbPage*);
-SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *);
-SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *);
+SQLITE_PRIVATE int tdsqlite3PagerWrite(DbPage*);
+SQLITE_PRIVATE void tdsqlite3PagerDontWrite(DbPage*);
+SQLITE_PRIVATE int tdsqlite3PagerMovepage(Pager*,DbPage*,Pgno,int);
+SQLITE_PRIVATE int tdsqlite3PagerPageRefcount(DbPage*);
+SQLITE_PRIVATE void *tdsqlite3PagerGetData(DbPage *);
+SQLITE_PRIVATE void *tdsqlite3PagerGetExtra(DbPage *);
/* Functions used to manage pager transactions and savepoints. */
-SQLITE_PRIVATE void sqlite3PagerPagecount(Pager*, int*);
-SQLITE_PRIVATE int sqlite3PagerBegin(Pager*, int exFlag, int);
-SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int);
-SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager*);
-SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zMaster);
-SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager*);
-SQLITE_PRIVATE int sqlite3PagerRollback(Pager*);
-SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
-SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
-SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager);
+SQLITE_PRIVATE void tdsqlite3PagerPagecount(Pager*, int*);
+SQLITE_PRIVATE int tdsqlite3PagerBegin(Pager*, int exFlag, int);
+SQLITE_PRIVATE int tdsqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int);
+SQLITE_PRIVATE int tdsqlite3PagerExclusiveLock(Pager*);
+SQLITE_PRIVATE int tdsqlite3PagerSync(Pager *pPager, const char *zMaster);
+SQLITE_PRIVATE int tdsqlite3PagerCommitPhaseTwo(Pager*);
+SQLITE_PRIVATE int tdsqlite3PagerRollback(Pager*);
+SQLITE_PRIVATE int tdsqlite3PagerOpenSavepoint(Pager *pPager, int n);
+SQLITE_PRIVATE int tdsqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
+SQLITE_PRIVATE int tdsqlite3PagerSharedLock(Pager *pPager);
#ifndef SQLITE_OMIT_WAL
-SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, int, int*, int*);
-SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager);
-SQLITE_PRIVATE int sqlite3PagerWalCallback(Pager *pPager);
-SQLITE_PRIVATE int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen);
-SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager);
-SQLITE_PRIVATE int sqlite3PagerUseWal(Pager *pPager);
+SQLITE_PRIVATE int tdsqlite3PagerCheckpoint(Pager *pPager, tdsqlite3*, int, int*, int*);
+SQLITE_PRIVATE int tdsqlite3PagerWalSupported(Pager *pPager);
+SQLITE_PRIVATE int tdsqlite3PagerWalCallback(Pager *pPager);
+SQLITE_PRIVATE int tdsqlite3PagerOpenWal(Pager *pPager, int *pisOpen);
+SQLITE_PRIVATE int tdsqlite3PagerCloseWal(Pager *pPager, tdsqlite3*);
# ifdef SQLITE_ENABLE_SNAPSHOT
-SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
-SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
+SQLITE_PRIVATE int tdsqlite3PagerSnapshotGet(Pager *pPager, tdsqlite3_snapshot **ppSnapshot);
+SQLITE_PRIVATE int tdsqlite3PagerSnapshotOpen(Pager *pPager, tdsqlite3_snapshot *pSnapshot);
+SQLITE_PRIVATE int tdsqlite3PagerSnapshotRecover(Pager *pPager);
+SQLITE_PRIVATE int tdsqlite3PagerSnapshotCheck(Pager *pPager, tdsqlite3_snapshot *pSnapshot);
+SQLITE_PRIVATE void tdsqlite3PagerSnapshotUnlock(Pager *pPager);
# endif
-#else
-# define sqlite3PagerUseWal(x) 0
+#endif
+
+#ifdef SQLITE_DIRECT_OVERFLOW_READ
+SQLITE_PRIVATE int tdsqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno);
#endif
#ifdef SQLITE_ENABLE_ZIPVFS
-SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager);
+SQLITE_PRIVATE int tdsqlite3PagerWalFramesize(Pager *pPager);
#endif
/* Functions used to query pager state and configuration. */
-SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager*);
-SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager*);
+SQLITE_PRIVATE u8 tdsqlite3PagerIsreadonly(Pager*);
+SQLITE_PRIVATE u32 tdsqlite3PagerDataVersion(Pager*);
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*);
-#endif
-SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager*);
-SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*, int);
-SQLITE_PRIVATE sqlite3_vfs *sqlite3PagerVfs(Pager*);
-SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*);
-SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager*);
-SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*);
-SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*);
-SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*);
-SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *);
-SQLITE_PRIVATE void sqlite3PagerClearCache(Pager*);
-SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *);
+SQLITE_PRIVATE int tdsqlite3PagerRefcount(Pager*);
+#endif
+SQLITE_PRIVATE int tdsqlite3PagerMemUsed(Pager*);
+SQLITE_PRIVATE const char *tdsqlite3PagerFilename(const Pager*, int);
+SQLITE_PRIVATE tdsqlite3_vfs *tdsqlite3PagerVfs(Pager*);
+SQLITE_PRIVATE tdsqlite3_file *tdsqlite3PagerFile(Pager*);
+SQLITE_PRIVATE tdsqlite3_file *tdsqlite3PagerJrnlFile(Pager*);
+SQLITE_PRIVATE const char *tdsqlite3PagerJournalname(Pager*);
+SQLITE_PRIVATE void *tdsqlite3PagerTempSpace(Pager*);
+SQLITE_PRIVATE int tdsqlite3PagerIsMemdb(Pager*);
+SQLITE_PRIVATE void tdsqlite3PagerCacheStat(Pager *, int, int, int *);
+SQLITE_PRIVATE void tdsqlite3PagerClearCache(Pager*);
+SQLITE_PRIVATE int tdsqlite3SectorSize(tdsqlite3_file *);
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+SQLITE_PRIVATE void tdsqlite3PagerResetLockTimeout(Pager *pPager);
+#else
+# define tdsqlite3PagerResetLockTimeout(X)
+#endif
/* Functions used to truncate the database file. */
-SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno);
+SQLITE_PRIVATE void tdsqlite3PagerTruncateImage(Pager*,Pgno);
-SQLITE_PRIVATE void sqlite3PagerRekey(DbPage*, Pgno, u16);
+SQLITE_PRIVATE void tdsqlite3PagerRekey(DbPage*, Pgno, u16);
#if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL)
-SQLITE_PRIVATE void *sqlite3PagerCodec(DbPage *);
+SQLITE_PRIVATE void *tdsqlite3PagerCodec(DbPage *);
#endif
/* Functions to support testing and debugging. */
#if !defined(NDEBUG) || defined(SQLITE_TEST)
-SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage*);
-SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage*);
+SQLITE_PRIVATE Pgno tdsqlite3PagerPagenumber(DbPage*);
+SQLITE_PRIVATE int tdsqlite3PagerIswriteable(DbPage*);
#endif
#ifdef SQLITE_TEST
-SQLITE_PRIVATE int *sqlite3PagerStats(Pager*);
-SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*);
+SQLITE_PRIVATE int *tdsqlite3PagerStats(Pager*);
+SQLITE_PRIVATE void tdsqlite3PagerRefdump(Pager*);
void disable_simulated_io_errors(void);
void enable_simulated_io_errors(void);
#else
@@ -13181,9 +15934,10 @@ typedef struct PCache PCache;
** structure.
*/
struct PgHdr {
- sqlite3_pcache_page *pPage; /* Pcache object page handle */
+ tdsqlite3_pcache_page *pPage; /* Pcache object page handle */
void *pData; /* Page data */
void *pExtra; /* Extra content */
+ PCache *pCache; /* PRIVATE: Cache that owns this page */
PgHdr *pDirty; /* Transient list of dirty sorted by pgno */
Pager *pPager; /* The pager this page is part of */
Pgno pgno; /* Page number for this page */
@@ -13193,14 +15947,15 @@ struct PgHdr {
u16 flags; /* PGHDR flags defined below */
/**********************************************************************
- ** Elements above are public. All that follows is private to pcache.c
- ** and should not be accessed by other modules.
+ ** Elements above, except pCache, are public. All that follow are
+ ** private to pcache.c and should not be accessed by other modules.
+ ** pCache is grouped with the public elements for efficiency.
*/
i16 nRef; /* Number of users of this page */
- PCache *pCache; /* Cache that owns this page */
-
PgHdr *pDirtyNext; /* Next element in list of dirty pages */
PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */
+ /* NB: pDirtyNext and pDirtyPrev are undefined if the
+ ** PgHdr object is not dirty */
};
/* Bit values for PgHdr.flags */
@@ -13215,19 +15970,19 @@ struct PgHdr {
#define PGHDR_WAL_APPEND 0x040 /* Appended to wal file */
/* Initialize and shutdown the page cache subsystem */
-SQLITE_PRIVATE int sqlite3PcacheInitialize(void);
-SQLITE_PRIVATE void sqlite3PcacheShutdown(void);
+SQLITE_PRIVATE int tdsqlite3PcacheInitialize(void);
+SQLITE_PRIVATE void tdsqlite3PcacheShutdown(void);
/* Page cache buffer management:
** These routines implement SQLITE_CONFIG_PAGECACHE.
*/
-SQLITE_PRIVATE void sqlite3PCacheBufferSetup(void *, int sz, int n);
+SQLITE_PRIVATE void tdsqlite3PCacheBufferSetup(void *, int sz, int n);
/* Create a new pager cache.
** Under memory stress, invoke xStress to try to make pages clean.
** Only clean and unpinned pages can be reclaimed.
*/
-SQLITE_PRIVATE int sqlite3PcacheOpen(
+SQLITE_PRIVATE int tdsqlite3PcacheOpen(
int szPage, /* Size of every page */
int szExtra, /* Extra space associated with each page */
int bPurgeable, /* True if pages are on backing store */
@@ -13237,67 +15992,67 @@ SQLITE_PRIVATE int sqlite3PcacheOpen(
);
/* Modify the page-size after the cache has been created. */
-SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *, int);
+SQLITE_PRIVATE int tdsqlite3PcacheSetPageSize(PCache *, int);
/* Return the size in bytes of a PCache object. Used to preallocate
** storage space.
*/
-SQLITE_PRIVATE int sqlite3PcacheSize(void);
+SQLITE_PRIVATE int tdsqlite3PcacheSize(void);
/* One release per successful fetch. Page is pinned until released.
** Reference counted.
*/
-SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch(PCache*, Pgno, int createFlag);
-SQLITE_PRIVATE int sqlite3PcacheFetchStress(PCache*, Pgno, sqlite3_pcache_page**);
-SQLITE_PRIVATE PgHdr *sqlite3PcacheFetchFinish(PCache*, Pgno, sqlite3_pcache_page *pPage);
-SQLITE_PRIVATE void sqlite3PcacheRelease(PgHdr*);
+SQLITE_PRIVATE tdsqlite3_pcache_page *tdsqlite3PcacheFetch(PCache*, Pgno, int createFlag);
+SQLITE_PRIVATE int tdsqlite3PcacheFetchStress(PCache*, Pgno, tdsqlite3_pcache_page**);
+SQLITE_PRIVATE PgHdr *tdsqlite3PcacheFetchFinish(PCache*, Pgno, tdsqlite3_pcache_page *pPage);
+SQLITE_PRIVATE void tdsqlite3PcacheRelease(PgHdr*);
-SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr*); /* Remove page from cache */
-SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr*); /* Make sure page is marked dirty */
-SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr*); /* Mark a single page as clean */
-SQLITE_PRIVATE void sqlite3PcacheCleanAll(PCache*); /* Mark all dirty list pages as clean */
-SQLITE_PRIVATE void sqlite3PcacheClearWritable(PCache*);
+SQLITE_PRIVATE void tdsqlite3PcacheDrop(PgHdr*); /* Remove page from cache */
+SQLITE_PRIVATE void tdsqlite3PcacheMakeDirty(PgHdr*); /* Make sure page is marked dirty */
+SQLITE_PRIVATE void tdsqlite3PcacheMakeClean(PgHdr*); /* Mark a single page as clean */
+SQLITE_PRIVATE void tdsqlite3PcacheCleanAll(PCache*); /* Mark all dirty list pages as clean */
+SQLITE_PRIVATE void tdsqlite3PcacheClearWritable(PCache*);
/* Change a page number. Used by incr-vacuum. */
-SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr*, Pgno);
+SQLITE_PRIVATE void tdsqlite3PcacheMove(PgHdr*, Pgno);
/* Remove all pages with pgno>x. Reset the cache if x==0 */
-SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache*, Pgno x);
+SQLITE_PRIVATE void tdsqlite3PcacheTruncate(PCache*, Pgno x);
/* Get a list of all dirty pages in the cache, sorted by page number */
-SQLITE_PRIVATE PgHdr *sqlite3PcacheDirtyList(PCache*);
+SQLITE_PRIVATE PgHdr *tdsqlite3PcacheDirtyList(PCache*);
/* Reset and close the cache object */
-SQLITE_PRIVATE void sqlite3PcacheClose(PCache*);
+SQLITE_PRIVATE void tdsqlite3PcacheClose(PCache*);
/* Clear flags from pages of the page cache */
-SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *);
+SQLITE_PRIVATE void tdsqlite3PcacheClearSyncFlags(PCache *);
/* Discard the contents of the cache */
-SQLITE_PRIVATE void sqlite3PcacheClear(PCache*);
+SQLITE_PRIVATE void tdsqlite3PcacheClear(PCache*);
/* Return the total number of outstanding page references */
-SQLITE_PRIVATE int sqlite3PcacheRefCount(PCache*);
+SQLITE_PRIVATE int tdsqlite3PcacheRefCount(PCache*);
/* Increment the reference count of an existing page */
-SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr*);
+SQLITE_PRIVATE void tdsqlite3PcacheRef(PgHdr*);
-SQLITE_PRIVATE int sqlite3PcachePageRefcount(PgHdr*);
+SQLITE_PRIVATE int tdsqlite3PcachePageRefcount(PgHdr*);
/* Return the total number of pages stored in the cache */
-SQLITE_PRIVATE int sqlite3PcachePagecount(PCache*);
+SQLITE_PRIVATE int tdsqlite3PcachePagecount(PCache*);
#if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG)
/* Iterate through all dirty pages currently stored in the cache. This
** interface is only available if SQLITE_CHECK_PAGES is defined when the
** library is built.
*/
-SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *));
+SQLITE_PRIVATE void tdsqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *));
#endif
#if defined(SQLITE_DEBUG)
/* Check invariants on a PgHdr object */
-SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr*);
+SQLITE_PRIVATE int tdsqlite3PcachePageSanity(PgHdr*);
#endif
/* Set and get the suggested cache-size for the specified pager-cache.
@@ -13306,9 +16061,9 @@ SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr*);
** the total number of pages cached by purgeable pager-caches to the sum
** of the suggested cache-sizes.
*/
-SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *, int);
+SQLITE_PRIVATE void tdsqlite3PcacheSetCachesize(PCache *, int);
#ifdef SQLITE_TEST
-SQLITE_PRIVATE int sqlite3PcacheGetCachesize(PCache *);
+SQLITE_PRIVATE int tdsqlite3PcacheGetCachesize(PCache *);
#endif
/* Set or get the suggested spill-size for the specified pager-cache.
@@ -13316,28 +16071,32 @@ SQLITE_PRIVATE int sqlite3PcacheGetCachesize(PCache *);
** The spill-size is the minimum number of pages in cache before the cache
** will attempt to spill dirty pages by calling xStress.
*/
-SQLITE_PRIVATE int sqlite3PcacheSetSpillsize(PCache *, int);
+SQLITE_PRIVATE int tdsqlite3PcacheSetSpillsize(PCache *, int);
/* Free up as much memory as possible from the page cache */
-SQLITE_PRIVATE void sqlite3PcacheShrink(PCache*);
+SQLITE_PRIVATE void tdsqlite3PcacheShrink(PCache*);
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
/* Try to return memory used by the pcache module to the main memory heap */
-SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int);
+SQLITE_PRIVATE int tdsqlite3PcacheReleaseMemory(int);
#endif
#ifdef SQLITE_TEST
-SQLITE_PRIVATE void sqlite3PcacheStats(int*,int*,int*,int*);
+SQLITE_PRIVATE void tdsqlite3PcacheStats(int*,int*,int*,int*);
#endif
-SQLITE_PRIVATE void sqlite3PCacheSetDefault(void);
+SQLITE_PRIVATE void tdsqlite3PCacheSetDefault(void);
/* Return the header size */
-SQLITE_PRIVATE int sqlite3HeaderSizePcache(void);
-SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void);
+SQLITE_PRIVATE int tdsqlite3HeaderSizePcache(void);
+SQLITE_PRIVATE int tdsqlite3HeaderSizePcache1(void);
/* Number of dirty pages as a percentage of the configured cache size */
-SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache*);
+SQLITE_PRIVATE int tdsqlite3PCachePercentDirty(PCache*);
+
+#ifdef SQLITE_DIRECT_OVERFLOW_READ
+SQLITE_PRIVATE int tdsqlite3PCacheIsDirty(PCache *pCache);
+#endif
#endif /* _PCACHE_H_ */
@@ -13475,7 +16234,7 @@ SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache*);
/*
** The following values may be passed as the second argument to
-** sqlite3OsLock(). The various locks exhibit the following semantics:
+** tdsqlite3OsLock(). The various locks exhibit the following semantics:
**
** SHARED: Any number of processes may hold a SHARED lock simultaneously.
** RESERVED: A single process may hold a RESERVED lock on a file at
@@ -13485,10 +16244,10 @@ SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache*);
** SHARED locks may be obtained by other processes.
** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks.
**
-** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a
+** PENDING_LOCK may not be passed directly to tdsqlite3OsLock(). Instead, a
** process that requests an EXCLUSIVE lock may actually obtain a PENDING
** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to
-** sqlite3OsLock().
+** tdsqlite3OsLock().
*/
#define NO_LOCK 0
#define SHARED_LOCK 1
@@ -13554,66 +16313,68 @@ SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache*);
#ifdef SQLITE_OMIT_WSD
# define PENDING_BYTE (0x40000000)
#else
-# define PENDING_BYTE sqlite3PendingByte
+# define PENDING_BYTE tdsqlite3PendingByte
#endif
#define RESERVED_BYTE (PENDING_BYTE+1)
#define SHARED_FIRST (PENDING_BYTE+2)
#define SHARED_SIZE 510
/*
-** Wrapper around OS specific sqlite3_os_init() function.
+** Wrapper around OS specific tdsqlite3_os_init() function.
*/
-SQLITE_PRIVATE int sqlite3OsInit(void);
+SQLITE_PRIVATE int tdsqlite3OsInit(void);
/*
-** Functions for accessing sqlite3_file methods
-*/
-SQLITE_PRIVATE void sqlite3OsClose(sqlite3_file*);
-SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset);
-SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset);
-SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size);
-SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int);
-SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize);
-SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int);
-SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int);
-SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut);
-SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*);
-SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file*,int,void*);
+** Functions for accessing tdsqlite3_file methods
+*/
+SQLITE_PRIVATE void tdsqlite3OsClose(tdsqlite3_file*);
+SQLITE_PRIVATE int tdsqlite3OsRead(tdsqlite3_file*, void*, int amt, i64 offset);
+SQLITE_PRIVATE int tdsqlite3OsWrite(tdsqlite3_file*, const void*, int amt, i64 offset);
+SQLITE_PRIVATE int tdsqlite3OsTruncate(tdsqlite3_file*, i64 size);
+SQLITE_PRIVATE int tdsqlite3OsSync(tdsqlite3_file*, int);
+SQLITE_PRIVATE int tdsqlite3OsFileSize(tdsqlite3_file*, i64 *pSize);
+SQLITE_PRIVATE int tdsqlite3OsLock(tdsqlite3_file*, int);
+SQLITE_PRIVATE int tdsqlite3OsUnlock(tdsqlite3_file*, int);
+SQLITE_PRIVATE int tdsqlite3OsCheckReservedLock(tdsqlite3_file *id, int *pResOut);
+SQLITE_PRIVATE int tdsqlite3OsFileControl(tdsqlite3_file*,int,void*);
+SQLITE_PRIVATE void tdsqlite3OsFileControlHint(tdsqlite3_file*,int,void*);
#define SQLITE_FCNTL_DB_UNCHANGED 0xca093fa0
-SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id);
-SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id);
-SQLITE_PRIVATE int sqlite3OsShmMap(sqlite3_file *,int,int,int,void volatile **);
-SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int, int, int);
-SQLITE_PRIVATE void sqlite3OsShmBarrier(sqlite3_file *id);
-SQLITE_PRIVATE int sqlite3OsShmUnmap(sqlite3_file *id, int);
-SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64, int, void **);
-SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *, i64, void *);
+SQLITE_PRIVATE int tdsqlite3OsSectorSize(tdsqlite3_file *id);
+SQLITE_PRIVATE int tdsqlite3OsDeviceCharacteristics(tdsqlite3_file *id);
+#ifndef SQLITE_OMIT_WAL
+SQLITE_PRIVATE int tdsqlite3OsShmMap(tdsqlite3_file *,int,int,int,void volatile **);
+SQLITE_PRIVATE int tdsqlite3OsShmLock(tdsqlite3_file *id, int, int, int);
+SQLITE_PRIVATE void tdsqlite3OsShmBarrier(tdsqlite3_file *id);
+SQLITE_PRIVATE int tdsqlite3OsShmUnmap(tdsqlite3_file *id, int);
+#endif /* SQLITE_OMIT_WAL */
+SQLITE_PRIVATE int tdsqlite3OsFetch(tdsqlite3_file *id, i64, int, void **);
+SQLITE_PRIVATE int tdsqlite3OsUnfetch(tdsqlite3_file *, i64, void *);
/*
-** Functions for accessing sqlite3_vfs methods
+** Functions for accessing tdsqlite3_vfs methods
*/
-SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *);
-SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int);
-SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int, int *pResOut);
-SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *);
+SQLITE_PRIVATE int tdsqlite3OsOpen(tdsqlite3_vfs *, const char *, tdsqlite3_file*, int, int *);
+SQLITE_PRIVATE int tdsqlite3OsDelete(tdsqlite3_vfs *, const char *, int);
+SQLITE_PRIVATE int tdsqlite3OsAccess(tdsqlite3_vfs *, const char *, int, int *pResOut);
+SQLITE_PRIVATE int tdsqlite3OsFullPathname(tdsqlite3_vfs *, const char *, int, char *);
#ifndef SQLITE_OMIT_LOAD_EXTENSION
-SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *);
-SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *);
-SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *, void *, const char *))(void);
-SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *);
+SQLITE_PRIVATE void *tdsqlite3OsDlOpen(tdsqlite3_vfs *, const char *);
+SQLITE_PRIVATE void tdsqlite3OsDlError(tdsqlite3_vfs *, int, char *);
+SQLITE_PRIVATE void (*tdsqlite3OsDlSym(tdsqlite3_vfs *, void *, const char *))(void);
+SQLITE_PRIVATE void tdsqlite3OsDlClose(tdsqlite3_vfs *, void *);
#endif /* SQLITE_OMIT_LOAD_EXTENSION */
-SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *);
-SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int);
-SQLITE_PRIVATE int sqlite3OsGetLastError(sqlite3_vfs*);
-SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64*);
+SQLITE_PRIVATE int tdsqlite3OsRandomness(tdsqlite3_vfs *, int, char *);
+SQLITE_PRIVATE int tdsqlite3OsSleep(tdsqlite3_vfs *, int);
+SQLITE_PRIVATE int tdsqlite3OsGetLastError(tdsqlite3_vfs*);
+SQLITE_PRIVATE int tdsqlite3OsCurrentTimeInt64(tdsqlite3_vfs *, tdsqlite3_int64*);
/*
** Convenience functions for opening and closing files using
-** sqlite3_malloc() to obtain space for the file-handle structure.
+** tdsqlite3_malloc() to obtain space for the file-handle structure.
*/
-SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*);
-SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *);
+SQLITE_PRIVATE int tdsqlite3OsOpenMalloc(tdsqlite3_vfs *, const char *, tdsqlite3_file **, int,int*);
+SQLITE_PRIVATE void tdsqlite3OsCloseFree(tdsqlite3_file *);
#endif /* _SQLITE_OS_H_ */
@@ -13677,19 +16438,20 @@ SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *);
/*
** If this is a no-op implementation, implement everything as macros.
*/
-#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8)
-#define sqlite3_mutex_free(X)
-#define sqlite3_mutex_enter(X)
-#define sqlite3_mutex_try(X) SQLITE_OK
-#define sqlite3_mutex_leave(X)
-#define sqlite3_mutex_held(X) ((void)(X),1)
-#define sqlite3_mutex_notheld(X) ((void)(X),1)
-#define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8)
-#define sqlite3MutexInit() SQLITE_OK
-#define sqlite3MutexEnd()
+#define tdsqlite3_mutex_alloc(X) ((tdsqlite3_mutex*)8)
+#define tdsqlite3_mutex_free(X)
+#define tdsqlite3_mutex_enter(X)
+#define tdsqlite3_mutex_try(X) SQLITE_OK
+#define tdsqlite3_mutex_leave(X)
+#define tdsqlite3_mutex_held(X) ((void)(X),1)
+#define tdsqlite3_mutex_notheld(X) ((void)(X),1)
+#define tdsqlite3MutexAlloc(X) ((tdsqlite3_mutex*)8)
+#define tdsqlite3MutexInit() SQLITE_OK
+#define tdsqlite3MutexEnd()
#define MUTEX_LOGIC(X)
#else
#define MUTEX_LOGIC(X) X
+SQLITE_API int tdsqlite3_mutex_held(tdsqlite3_mutex*);
#endif /* defined(SQLITE_MUTEX_OMIT) */
/************** End of mutex.h ***********************************************/
@@ -13720,7 +16482,7 @@ SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *);
** and the one-based values are used internally.
*/
#ifndef SQLITE_DEFAULT_SYNCHRONOUS
-# define SQLITE_DEFAULT_SYNCHRONOUS (PAGER_SYNCHRONOUS_FULL-1)
+# define SQLITE_DEFAULT_SYNCHRONOUS 2
#endif
#ifndef SQLITE_DEFAULT_WAL_SYNCHRONOUS
# define SQLITE_DEFAULT_WAL_SYNCHRONOUS SQLITE_DEFAULT_SYNCHRONOUS
@@ -13751,11 +16513,11 @@ struct Db {
**
** Schema objects are automatically deallocated when the last Btree that
** references them is destroyed. The TEMP Schema is manually freed by
-** sqlite3_close().
+** tdsqlite3_close().
*
** A thread must be holding a mutex on the corresponding Btree in order
** to access Schema content. This implies that the thread must also be
-** holding a mutex on the sqlite3 connection pointer that owns the Btree.
+** holding a mutex on the tdsqlite3 connection pointer that owns the Btree.
** For a TEMP Schema, only the connection mutex is required.
*/
struct Schema {
@@ -13794,10 +16556,11 @@ struct Schema {
#define DB_SchemaLoaded 0x0001 /* The schema has been loaded */
#define DB_UnresetViews 0x0002 /* Some views have defined column names */
#define DB_Empty 0x0004 /* The file is empty (length 0 bytes) */
+#define DB_ResetWanted 0x0008 /* Reset the schema when nSchemaLock==0 */
/*
** The number of different kinds of things that can be limited
-** using the sqlite3_limit() interface.
+** using the tdsqlite3_limit() interface.
*/
#define SQLITE_N_LIMIT (SQLITE_LIMIT_WORKER_THREADS+1)
@@ -13820,15 +16583,47 @@ struct Schema {
** is shared by multiple database connections. Therefore, while parsing
** schema information, the Lookaside.bEnabled flag is cleared so that
** lookaside allocations are not used to construct the schema objects.
+**
+** New lookaside allocations are only allowed if bDisable==0. When
+** bDisable is greater than zero, sz is set to zero which effectively
+** disables lookaside without adding a new test for the bDisable flag
+** in a performance-critical path. sz should be set by to szTrue whenever
+** bDisable changes back to zero.
+**
+** Lookaside buffers are initially held on the pInit list. As they are
+** used and freed, they are added back to the pFree list. New allocations
+** come off of pFree first, then pInit as a fallback. This dual-list
+** allows use to compute a high-water mark - the maximum number of allocations
+** outstanding at any point in the past - by subtracting the number of
+** allocations on the pInit list from the total number of allocations.
+**
+** Enhancement on 2019-12-12: Two-size-lookaside
+** The default lookaside configuration is 100 slots of 1200 bytes each.
+** The larger slot sizes are important for performance, but they waste
+** a lot of space, as most lookaside allocations are less than 128 bytes.
+** The two-size-lookaside enhancement breaks up the lookaside allocation
+** into two pools: One of 128-byte slots and the other of the default size
+** (1200-byte) slots. Allocations are filled from the small-pool first,
+** failing over to the full-size pool if that does not work. Thus more
+** lookaside slots are available while also using less memory.
+** This enhancement can be omitted by compiling with
+** SQLITE_OMIT_TWOSIZE_LOOKASIDE.
*/
struct Lookaside {
u32 bDisable; /* Only operate the lookaside when zero */
u16 sz; /* Size of each buffer in bytes */
- u8 bMalloced; /* True if pStart obtained from sqlite3_malloc() */
- int nOut; /* Number of buffers currently checked out */
- int mxOut; /* Highwater mark for nOut */
- int anStat[3]; /* 0: hits. 1: size misses. 2: full misses */
+ u16 szTrue; /* True value of sz, even if disabled */
+ u8 bMalloced; /* True if pStart obtained from tdsqlite3_malloc() */
+ u32 nSlot; /* Number of lookaside slots allocated */
+ u32 anStat[3]; /* 0: hits. 1: size misses. 2: full misses */
+ LookasideSlot *pInit; /* List of buffers not previously used */
LookasideSlot *pFree; /* List of available buffers */
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ LookasideSlot *pSmallInit; /* List of small buffers not prediously used */
+ LookasideSlot *pSmallFree; /* List of available small buffers */
+ void *pMiddle; /* First byte past end of full-size buffers and
+ ** the first byte of LOOKASIDE_SMALL buffers */
+#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
void *pStart; /* First byte of available memory space */
void *pEnd; /* First byte past end of available space */
};
@@ -13836,42 +16631,55 @@ struct LookasideSlot {
LookasideSlot *pNext; /* Next buffer in the list of free buffers */
};
+#define DisableLookaside db->lookaside.bDisable++;db->lookaside.sz=0
+#define EnableLookaside db->lookaside.bDisable--;\
+ db->lookaside.sz=db->lookaside.bDisable?0:db->lookaside.szTrue
+
+/* Size of the smaller allocations in two-size lookside */
+#ifdef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+# define LOOKASIDE_SMALL 0
+#else
+# define LOOKASIDE_SMALL 128
+#endif
+
/*
** A hash table for built-in function definitions. (Application-defined
** functions use a regular table table from hash.h.)
**
** Hash each FuncDef structure into one of the FuncDefHash.a[] slots.
-** Collisions are on the FuncDef.u.pHash chain.
+** Collisions are on the FuncDef.u.pHash chain. Use the SQLITE_FUNC_HASH()
+** macro to compute a hash on the function name.
*/
#define SQLITE_FUNC_HASH_SZ 23
struct FuncDefHash {
FuncDef *a[SQLITE_FUNC_HASH_SZ]; /* Hash table for functions */
};
+#define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ)
#ifdef SQLITE_USER_AUTHENTICATION
/*
-** Information held in the "sqlite3" database connection object and used
+** Information held in the "tdsqlite3" database connection object and used
** to manage user authentication.
*/
-typedef struct sqlite3_userauth sqlite3_userauth;
-struct sqlite3_userauth {
+typedef struct tdsqlite3_userauth tdsqlite3_userauth;
+struct tdsqlite3_userauth {
u8 authLevel; /* Current authentication level */
int nAuthPW; /* Size of the zAuthPW in bytes */
char *zAuthPW; /* Password used to authenticate */
char *zAuthUser; /* User name used to authenticate */
};
-/* Allowed values for sqlite3_userauth.authLevel */
+/* Allowed values for tdsqlite3_userauth.authLevel */
#define UAUTH_Unknown 0 /* Authentication not yet checked */
#define UAUTH_Fail 1 /* User authentication failed */
#define UAUTH_User 2 /* Authenticated as a normal user */
#define UAUTH_Admin 3 /* Authenticated as an administrator */
/* Functions used only by user authorization logic */
-SQLITE_PRIVATE int sqlite3UserAuthTable(const char*);
-SQLITE_PRIVATE int sqlite3UserAuthCheckLogin(sqlite3*,const char*,u8*);
-SQLITE_PRIVATE void sqlite3UserAuthInit(sqlite3*);
-SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**);
+SQLITE_PRIVATE int tdsqlite3UserAuthTable(const char*);
+SQLITE_PRIVATE int tdsqlite3UserAuthCheckLogin(tdsqlite3*,const char*,u8*);
+SQLITE_PRIVATE void tdsqlite3UserAuthInit(tdsqlite3*);
+SQLITE_PRIVATE void tdsqlite3CryptFunc(tdsqlite3_context*,int,tdsqlite3_value**);
#endif /* SQLITE_USER_AUTHENTICATION */
@@ -13879,37 +16687,42 @@ SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**);
** typedef for the authorization callback function.
*/
#ifdef SQLITE_USER_AUTHENTICATION
- typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
+ typedef int (*tdsqlite3_xauth)(void*,int,const char*,const char*,const char*,
const char*, const char*);
#else
- typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
+ typedef int (*tdsqlite3_xauth)(void*,int,const char*,const char*,const char*,
const char*);
#endif
#ifndef SQLITE_OMIT_DEPRECATED
/* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing
-** in the style of sqlite3_trace()
+** in the style of tdsqlite3_trace()
*/
-#define SQLITE_TRACE_LEGACY 0x80
+#define SQLITE_TRACE_LEGACY 0x40 /* Use the legacy xTrace */
+#define SQLITE_TRACE_XPROFILE 0x80 /* Use the legacy xProfile */
#else
-#define SQLITE_TRACE_LEGACY 0
+#define SQLITE_TRACE_LEGACY 0
+#define SQLITE_TRACE_XPROFILE 0
#endif /* SQLITE_OMIT_DEPRECATED */
+#define SQLITE_TRACE_NONLEGACY_MASK 0x0f /* Normal flags */
/*
** Each database connection is an instance of the following structure.
*/
-struct sqlite3 {
- sqlite3_vfs *pVfs; /* OS Interface */
+struct tdsqlite3 {
+ tdsqlite3_vfs *pVfs; /* OS Interface */
struct Vdbe *pVdbe; /* List of active virtual machines */
CollSeq *pDfltColl; /* The default collating sequence (BINARY) */
- sqlite3_mutex *mutex; /* Connection mutex */
+ tdsqlite3_mutex *mutex; /* Connection mutex */
Db *aDb; /* All backends */
int nDb; /* Number of backends currently in use */
- int flags; /* Miscellaneous flags. See below */
+ u32 mDbFlags; /* flags recording internal state */
+ u64 flags; /* flags settable by pragmas. See below */
i64 lastRowid; /* ROWID of most recent insert (see above) */
i64 szMmap; /* Default mmap_size setting */
- unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */
+ u32 nSchemaLock; /* Do not reset the schema when non-zero */
+ unsigned int openFlags; /* Flags passed to tdsqlite3_vfs.xOpen() */
int errCode; /* Most recent error code (SQLITE_*) */
int errMask; /* & result codes with this before returning */
int iSysErrno; /* Errno value from last system error */
@@ -13925,18 +16738,22 @@ struct sqlite3 {
u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */
u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */
u8 mTrace; /* zero or more SQLITE_TRACE flags */
+ u8 noSharedCache; /* True if no shared-cache backends */
+ u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */
int nextPagesize; /* Pagesize after VACUUM if >0 */
u32 magic; /* Magic number for detect library misuse */
- int nChange; /* Value returned by sqlite3_changes() */
- int nTotalChange; /* Value returned by sqlite3_total_changes() */
+ int nChange; /* Value returned by tdsqlite3_changes() */
+ int nTotalChange; /* Value returned by tdsqlite3_total_changes() */
int aLimit[SQLITE_N_LIMIT]; /* Limits */
int nMaxSorterMmap; /* Maximum size of regions mapped by sorter */
- struct sqlite3InitInfo { /* Information used during initialization */
+ struct tdsqlite3InitInfo { /* Information used during initialization */
int newTnum; /* Rootpage of table being initialized */
u8 iDb; /* Which db file is being initialized */
u8 busy; /* TRUE if currently initializing */
- u8 orphanTrigger; /* Last statement is orphaned TEMP trigger */
- u8 imposterTable; /* Building an imposter table */
+ unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */
+ unsigned imposterTable : 1; /* Building an imposter table */
+ unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */
+ char **azInit; /* "type", "name", and "tbl_name" columns */
} init;
int nVdbeActive; /* Number of VDBEs currently running */
int nVdbeRead; /* Number of active VDBEs that read or write */
@@ -13947,36 +16764,39 @@ struct sqlite3 {
void **aExtension; /* Array of shared library handles */
int (*xTrace)(u32,void*,void*,void*); /* Trace function */
void *pTraceArg; /* Argument to the trace function */
+#ifndef SQLITE_OMIT_DEPRECATED
void (*xProfile)(void*,const char*,u64); /* Profiling function */
void *pProfileArg; /* Argument to profile function */
+#endif
void *pCommitArg; /* Argument to xCommitCallback() */
int (*xCommitCallback)(void*); /* Invoked at every commit. */
void *pRollbackArg; /* Argument to xRollbackCallback() */
void (*xRollbackCallback)(void*); /* Invoked at every commit. */
void *pUpdateArg;
void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
+ Parse *pParse; /* Current parse */
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
void *pPreUpdateArg; /* First argument to xPreUpdateCallback */
- void (*xPreUpdateCallback)( /* Registered using sqlite3_preupdate_hook() */
- void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64
+ void (*xPreUpdateCallback)( /* Registered using tdsqlite3_preupdate_hook() */
+ void*,tdsqlite3*,int,char const*,char const*,tdsqlite3_int64,tdsqlite3_int64
);
PreUpdate *pPreUpdate; /* Context for active pre-update callback */
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
#ifndef SQLITE_OMIT_WAL
- int (*xWalCallback)(void *, sqlite3 *, const char *, int);
+ int (*xWalCallback)(void *, tdsqlite3 *, const char *, int);
void *pWalArg;
#endif
- void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
- void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*);
+ void(*xCollNeeded)(void*,tdsqlite3*,int eTextRep,const char*);
+ void(*xCollNeeded16)(void*,tdsqlite3*,int eTextRep,const void*);
void *pCollNeededArg;
- sqlite3_value *pErr; /* Most recent error message */
+ tdsqlite3_value *pErr; /* Most recent error message */
union {
- volatile int isInterrupted; /* True if sqlite3_interrupt has been called */
+ volatile int isInterrupted; /* True if tdsqlite3_interrupt has been called */
double notUsed1; /* Spacer */
} u1;
Lookaside lookaside; /* Lookaside malloc configuration */
#ifndef SQLITE_OMIT_AUTHORIZATION
- sqlite3_xauth xAuth; /* Access authorization function */
+ tdsqlite3_xauth xAuth; /* Access authorization function */
void *pAuthArg; /* 1st argument to the access auth function */
#endif
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
@@ -13986,10 +16806,10 @@ struct sqlite3 {
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
int nVTrans; /* Allocated size of aVTrans */
- Hash aModule; /* populated by sqlite3_create_module() */
+ Hash aModule; /* populated by tdsqlite3_create_module() */
VtabCtx *pVtabCtx; /* Context for active vtab connect/create */
VTable **aVTrans; /* Virtual tables with open transactions */
- VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */
+ VTable *pDisconnect; /* Disconnect these in next tdsqlite3_prepare() */
#endif
Hash aFunc; /* Hash table of connection functions */
Hash aCollSeq; /* All collating sequences */
@@ -14013,14 +16833,14 @@ struct sqlite3 {
** tried to do recently failed with an SQLITE_LOCKED error due to locks
** held by Y.
*/
- sqlite3 *pBlockingConnection; /* Connection that caused SQLITE_LOCKED */
- sqlite3 *pUnlockConnection; /* Connection to watch for unlock */
+ tdsqlite3 *pBlockingConnection; /* Connection that caused SQLITE_LOCKED */
+ tdsqlite3 *pUnlockConnection; /* Connection to watch for unlock */
void *pUnlockArg; /* Argument to xUnlockNotify */
void (*xUnlockNotify)(void **, int); /* Unlock notify callback */
- sqlite3 *pNextBlocked; /* Next in list of all blocked connections */
+ tdsqlite3 *pNextBlocked; /* Next in list of all blocked connections */
#endif
#ifdef SQLITE_USER_AUTHENTICATION
- sqlite3_userauth auth; /* User authentication information */
+ tdsqlite3_userauth auth; /* User authentication information */
#endif
};
@@ -14031,6 +16851,13 @@ struct sqlite3 {
#define ENC(db) ((db)->enc)
/*
+** A u64 constant where the lower 32 bits are all zeros. Only the
+** upper 32 bits are included in the argument. Necessary because some
+** C-compilers still do not accept LL integer literals.
+*/
+#define HI(X) ((u64)(X)<<32)
+
+/*
** Possible values for the sqlite3.flags.
**
** Value constraints (enforced via assert()):
@@ -14038,72 +16865,93 @@ struct sqlite3 {
** SQLITE_CkptFullFSync == PAGER_CKPT_FULLFSYNC
** SQLITE_CacheSpill == PAGER_CACHE_SPILL
*/
-#define SQLITE_VdbeTrace 0x00000001 /* True to trace VDBE execution */
-#define SQLITE_InternChanges 0x00000002 /* Uncommitted Hash table changes */
+#define SQLITE_WriteSchema 0x00000001 /* OK to update SQLITE_MASTER */
+#define SQLITE_LegacyFileFmt 0x00000002 /* Create new databases in format 1 */
#define SQLITE_FullColNames 0x00000004 /* Show full column names on SELECT */
#define SQLITE_FullFSync 0x00000008 /* Use full fsync on the backend */
#define SQLITE_CkptFullFSync 0x00000010 /* Use full fsync for checkpoint */
#define SQLITE_CacheSpill 0x00000020 /* OK to spill pager cache */
#define SQLITE_ShortColNames 0x00000040 /* Show short columns names */
-#define SQLITE_CountRows 0x00000080 /* Count rows changed by INSERT, */
- /* DELETE, or UPDATE and return */
- /* the count using a callback. */
+#define SQLITE_TrustedSchema 0x00000080 /* Allow unsafe functions and
+ ** vtabs in the schema definition */
#define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */
/* result set is empty */
-#define SQLITE_SqlTrace 0x00000200 /* Debug print SQL as it executes */
-#define SQLITE_VdbeListing 0x00000400 /* Debug listings of VDBE programs */
-#define SQLITE_WriteSchema 0x00000800 /* OK to update SQLITE_MASTER */
-#define SQLITE_VdbeAddopTrace 0x00001000 /* Trace sqlite3VdbeAddOp() calls */
-#define SQLITE_IgnoreChecks 0x00002000 /* Do not enforce check constraints */
-#define SQLITE_ReadUncommitted 0x0004000 /* For shared-cache mode */
-#define SQLITE_LegacyFileFmt 0x00008000 /* Create new databases in format 1 */
-#define SQLITE_RecoveryMode 0x00010000 /* Ignore schema errors */
-#define SQLITE_ReverseOrder 0x00020000 /* Reverse unordered SELECTs */
-#define SQLITE_RecTriggers 0x00040000 /* Enable recursive triggers */
-#define SQLITE_ForeignKeys 0x00080000 /* Enforce foreign key constraints */
-#define SQLITE_AutoIndex 0x00100000 /* Enable automatic indexes */
-#define SQLITE_PreferBuiltin 0x00200000 /* Preference to built-in funcs */
-#define SQLITE_LoadExtension 0x00400000 /* Enable load_extension */
-#define SQLITE_LoadExtFunc 0x00800000 /* Enable load_extension() SQL func */
-#define SQLITE_EnableTrigger 0x01000000 /* True to enable triggers */
-#define SQLITE_DeferFKs 0x02000000 /* Defer all FK constraints */
-#define SQLITE_QueryOnly 0x04000000 /* Disable database changes */
-#define SQLITE_VdbeEQP 0x08000000 /* Debug EXPLAIN QUERY PLAN */
-#define SQLITE_Vacuum 0x10000000 /* Currently in a VACUUM */
-#define SQLITE_CellSizeCk 0x20000000 /* Check btree cell sizes on load */
-#define SQLITE_Fts3Tokenizer 0x40000000 /* Enable fts3_tokenizer(2) */
+#define SQLITE_IgnoreChecks 0x00000200 /* Do not enforce check constraints */
+#define SQLITE_ReadUncommit 0x00000400 /* READ UNCOMMITTED in shared-cache */
+#define SQLITE_NoCkptOnClose 0x00000800 /* No checkpoint on close()/DETACH */
+#define SQLITE_ReverseOrder 0x00001000 /* Reverse unordered SELECTs */
+#define SQLITE_RecTriggers 0x00002000 /* Enable recursive triggers */
+#define SQLITE_ForeignKeys 0x00004000 /* Enforce foreign key constraints */
+#define SQLITE_AutoIndex 0x00008000 /* Enable automatic indexes */
+#define SQLITE_LoadExtension 0x00010000 /* Enable load_extension */
+#define SQLITE_LoadExtFunc 0x00020000 /* Enable load_extension() SQL func */
+#define SQLITE_EnableTrigger 0x00040000 /* True to enable triggers */
+#define SQLITE_DeferFKs 0x00080000 /* Defer all FK constraints */
+#define SQLITE_QueryOnly 0x00100000 /* Disable database changes */
+#define SQLITE_CellSizeCk 0x00200000 /* Check btree cell sizes on load */
+#define SQLITE_Fts3Tokenizer 0x00400000 /* Enable fts3_tokenizer(2) */
+#define SQLITE_EnableQPSG 0x00800000 /* Query Planner Stability Guarantee*/
+#define SQLITE_TriggerEQP 0x01000000 /* Show trigger EXPLAIN QUERY PLAN */
+#define SQLITE_ResetDatabase 0x02000000 /* Reset the database */
+#define SQLITE_LegacyAlter 0x04000000 /* Legacy ALTER TABLE behaviour */
+#define SQLITE_NoSchemaError 0x08000000 /* Do not report schema parse errors*/
+#define SQLITE_Defensive 0x10000000 /* Input SQL is likely hostile */
+#define SQLITE_DqsDDL 0x20000000 /* dbl-quoted strings allowed in DDL*/
+#define SQLITE_DqsDML 0x40000000 /* dbl-quoted strings allowed in DML*/
+#define SQLITE_EnableView 0x80000000 /* Enable the use of views */
+#define SQLITE_CountRows HI(0x00001) /* Count rows changed by INSERT, */
+ /* DELETE, or UPDATE and return */
+ /* the count using a callback. */
+/* Flags used only if debugging */
+#ifdef SQLITE_DEBUG
+#define SQLITE_SqlTrace HI(0x0100000) /* Debug print SQL as it executes */
+#define SQLITE_VdbeListing HI(0x0200000) /* Debug listings of VDBE progs */
+#define SQLITE_VdbeTrace HI(0x0400000) /* True to trace VDBE execution */
+#define SQLITE_VdbeAddopTrace HI(0x0800000) /* Trace tdsqlite3VdbeAddOp() calls */
+#define SQLITE_VdbeEQP HI(0x1000000) /* Debug EXPLAIN QUERY PLAN */
+#define SQLITE_ParserTrace HI(0x2000000) /* PRAGMA parser_trace=ON */
+#endif
+
+/*
+** Allowed values for sqlite3.mDbFlags
+*/
+#define DBFLAG_SchemaChange 0x0001 /* Uncommitted Hash table changes */
+#define DBFLAG_PreferBuiltin 0x0002 /* Preference to built-in funcs */
+#define DBFLAG_Vacuum 0x0004 /* Currently in a VACUUM */
+#define DBFLAG_VacuumInto 0x0008 /* Currently running VACUUM INTO */
+#define DBFLAG_SchemaKnownOk 0x0010 /* Schema is known to be valid */
+#define DBFLAG_InternalFunc 0x0020 /* Allow use of internal functions */
/*
** Bits of the sqlite3.dbOptFlags field that are used by the
-** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to
+** tdsqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to
** selectively disable various optimizations.
*/
#define SQLITE_QueryFlattener 0x0001 /* Query flattening */
-#define SQLITE_ColumnCache 0x0002 /* Column cache */
+#define SQLITE_WindowFunc 0x0002 /* Use xInverse for window functions */
#define SQLITE_GroupByOrder 0x0004 /* GROUPBY cover of ORDERBY */
#define SQLITE_FactorOutConst 0x0008 /* Constant factoring */
-/* not used 0x0010 // Was: SQLITE_IdxRealAsInt */
-#define SQLITE_DistinctOpt 0x0020 /* DISTINCT using indexes */
-#define SQLITE_CoverIdxScan 0x0040 /* Covering index scans */
-#define SQLITE_OrderByIdxJoin 0x0080 /* ORDER BY of joins via index */
-#define SQLITE_SubqCoroutine 0x0100 /* Evaluate subqueries as coroutines */
-#define SQLITE_Transitive 0x0200 /* Transitive constraints */
-#define SQLITE_OmitNoopJoin 0x0400 /* Omit unused tables in joins */
-#define SQLITE_Stat34 0x0800 /* Use STAT3 or STAT4 data */
-#define SQLITE_CursorHints 0x2000 /* Add OP_CursorHint opcodes */
+#define SQLITE_DistinctOpt 0x0010 /* DISTINCT using indexes */
+#define SQLITE_CoverIdxScan 0x0020 /* Covering index scans */
+#define SQLITE_OrderByIdxJoin 0x0040 /* ORDER BY of joins via index */
+#define SQLITE_Transitive 0x0080 /* Transitive constraints */
+#define SQLITE_OmitNoopJoin 0x0100 /* Omit unused tables in joins */
+#define SQLITE_CountOfView 0x0200 /* The count-of-view optimization */
+#define SQLITE_CursorHints 0x0400 /* Add OP_CursorHint opcodes */
+#define SQLITE_Stat4 0x0800 /* Use STAT4 data */
+ /* TH3 expects the Stat4 ^^^^^^ value to be 0x0800. Don't change it */
+#define SQLITE_PushDown 0x1000 /* The push-down optimization */
+#define SQLITE_SimplifyJoin 0x2000 /* Convert LEFT JOIN to JOIN */
+#define SQLITE_SkipScan 0x4000 /* Skip-scans */
+#define SQLITE_PropagateConst 0x8000 /* The constant propagation opt */
#define SQLITE_AllOpts 0xffff /* All optimizations */
/*
** Macros for testing whether or not optimizations are enabled or disabled.
*/
-#ifndef SQLITE_OMIT_BUILTIN_TEST
#define OptimizationDisabled(db, mask) (((db)->dbOptFlags&(mask))!=0)
#define OptimizationEnabled(db, mask) (((db)->dbOptFlags&(mask))==0)
-#else
-#define OptimizationDisabled(db, mask) 0
-#define OptimizationEnabled(db, mask) 1
-#endif
/*
** Return true if it OK to factor constant expressions into the initialization
@@ -14126,7 +16974,7 @@ struct sqlite3 {
/*
** Each SQL function is defined by an instance of the following
** structure. For global built-in functions (ex: substr(), max(), count())
-** a pointer to this structure is held in the sqlite3BuiltinFunctions object.
+** a pointer to this structure is held in the tdsqlite3BuiltinFunctions object.
** For per-connection application-defined functions, a pointer to this
** structure is held in the db->aHash hash table.
**
@@ -14135,11 +16983,13 @@ struct sqlite3 {
*/
struct FuncDef {
i8 nArg; /* Number of arguments. -1 means unlimited */
- u16 funcFlags; /* Some combination of SQLITE_FUNC_* */
+ u32 funcFlags; /* Some combination of SQLITE_FUNC_* */
void *pUserData; /* User data parameter */
FuncDef *pNext; /* Next function with same name */
- void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */
- void (*xFinalize)(sqlite3_context*); /* Agg finalizer */
+ void (*xSFunc)(tdsqlite3_context*,int,tdsqlite3_value**); /* func or agg-step */
+ void (*xFinalize)(tdsqlite3_context*); /* Agg finalizer */
+ void (*xValue)(tdsqlite3_context*); /* Current agg value */
+ void (*xInverse)(tdsqlite3_context*,int,tdsqlite3_value**); /* inverse agg-step */
const char *zName; /* SQL name of the function. */
union {
FuncDef *pHash; /* Next with a different name but the same hash */
@@ -14178,13 +17028,15 @@ struct FuncDestructor {
** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG
** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG
** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API
+** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API
+** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS
** SQLITE_FUNC_ENCMASK depends on SQLITE_UTF* macros in the API
*/
#define SQLITE_FUNC_ENCMASK 0x0003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */
#define SQLITE_FUNC_LIKE 0x0004 /* Candidate for the LIKE optimization */
#define SQLITE_FUNC_CASE 0x0008 /* Case-sensitive LIKE-type function */
#define SQLITE_FUNC_EPHEM 0x0010 /* Ephemeral. Delete with VDBE */
-#define SQLITE_FUNC_NEEDCOLL 0x0020 /* sqlite3GetFuncCollSeq() might be called*/
+#define SQLITE_FUNC_NEEDCOLL 0x0020 /* tdsqlite3GetFuncCollSeq() might be called*/
#define SQLITE_FUNC_LENGTH 0x0040 /* Built-in length() function */
#define SQLITE_FUNC_TYPEOF 0x0080 /* Built-in typeof() function */
#define SQLITE_FUNC_COUNT 0x0100 /* Built-in count(*) aggregate */
@@ -14194,6 +17046,22 @@ struct FuncDestructor {
#define SQLITE_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */
#define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a
** single query - might change over time */
+#define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */
+#define SQLITE_FUNC_OFFSET 0x8000 /* Built-in sqlite_offset() function */
+#define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */
+#define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */
+#define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */
+#define SQLITE_FUNC_SUBTYPE 0x00100000 /* Result likely to have sub-type */
+#define SQLITE_FUNC_UNSAFE 0x00200000 /* Function has side effects */
+#define SQLITE_FUNC_INLINE 0x00400000 /* Functions implemented in-line */
+
+/* Identifier numbers for each in-line function */
+#define INLINEFUNC_coalesce 0
+#define INLINEFUNC_implies_nonnull_row 1
+#define INLINEFUNC_expr_implies_expr 2
+#define INLINEFUNC_expr_compare 3
+#define INLINEFUNC_affinity 4
+#define INLINEFUNC_unlikely 99 /* Default case */
/*
** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are
@@ -14203,17 +17071,40 @@ struct FuncDestructor {
** Used to create a scalar function definition of a function zName
** implemented by C function xFunc that accepts nArg arguments. The
** value passed as iArg is cast to a (void*) and made available
-** as the user-data (sqlite3_user_data()) for the function. If
+** as the user-data (tdsqlite3_user_data()) for the function. If
** argument bNC is true, then the SQLITE_FUNC_NEEDCOLL flag is set.
**
** VFUNCTION(zName, nArg, iArg, bNC, xFunc)
** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag.
**
+** SFUNCTION(zName, nArg, iArg, bNC, xFunc)
+** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag and
+** adds the SQLITE_DIRECTONLY flag.
+**
+** INLINE_FUNC(zName, nArg, iFuncId, mFlags)
+** zName is the name of a function that is implemented by in-line
+** byte code rather than by the usual callbacks. The iFuncId
+** parameter determines the function id. The mFlags parameter is
+** optional SQLITE_FUNC_ flags for this function.
+**
+** TEST_FUNC(zName, nArg, iFuncId, mFlags)
+** zName is the name of a test-only function implemented by in-line
+** byte code rather than by the usual callbacks. The iFuncId
+** parameter determines the function id. The mFlags parameter is
+** optional SQLITE_FUNC_ flags for this function.
+**
** DFUNCTION(zName, nArg, iArg, bNC, xFunc)
** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag and
** adds the SQLITE_FUNC_SLOCHNG flag. Used for date & time functions
** and functions like sqlite_version() that can change, but not during
-** a single query.
+** a single query. The iArg is ignored. The user-data is always set
+** to a NULL pointer. The bNC parameter is not used.
+**
+** PURE_DATE(zName, nArg, iArg, bNC, xFunc)
+** Used for "pure" date/time functions, this macro is like DFUNCTION
+** except that it does set the SQLITE_FUNC_CONSTANT flags. iArg is
+** ignored and the user-data for these functions is set to an
+** arbitrary non-NULL pointer. The bNC parameter is not used.
**
** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal)
** Used to create an aggregate function definition implemented by
@@ -14221,38 +17112,58 @@ struct FuncDestructor {
** are interpreted in the same way as the first 4 parameters to
** FUNCTION().
**
+** WFUNCTION(zName, nArg, iArg, xStep, xFinal, xValue, xInverse)
+** Used to create an aggregate function definition implemented by
+** the C functions xStep and xFinal. The first four parameters
+** are interpreted in the same way as the first 4 parameters to
+** FUNCTION().
+**
** LIKEFUNC(zName, nArg, pArg, flags)
** Used to create a scalar function definition of a function zName
** that accepts nArg arguments and is implemented by a call to C
** function likeFunc. Argument pArg is cast to a (void *) and made
-** available as the function user-data (sqlite3_user_data()). The
+** available as the function user-data (tdsqlite3_user_data()). The
** FuncDef.flags variable is set to the value passed as the flags
** parameter.
*/
#define FUNCTION(zName, nArg, iArg, bNC, xFunc) \
{nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0} }
+ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} }
#define VFUNCTION(zName, nArg, iArg, bNC, xFunc) \
{nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0} }
+ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} }
+#define SFUNCTION(zName, nArg, iArg, bNC, xFunc) \
+ {nArg, SQLITE_UTF8|SQLITE_DIRECTONLY|SQLITE_FUNC_UNSAFE, \
+ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} }
+#define INLINE_FUNC(zName, nArg, iArg, mFlags) \
+ {nArg, SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \
+ SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} }
+#define TEST_FUNC(zName, nArg, iArg, mFlags) \
+ {nArg, SQLITE_UTF8|SQLITE_FUNC_INTERNAL|SQLITE_FUNC_TEST| \
+ SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \
+ SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} }
#define DFUNCTION(zName, nArg, iArg, bNC, xFunc) \
- {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0} }
+ {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8, \
+ 0, 0, xFunc, 0, 0, 0, #zName, {0} }
+#define PURE_DATE(zName, nArg, iArg, bNC, xFunc) \
+ {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \
+ (void*)&tdsqlite3Config, 0, xFunc, 0, 0, 0, #zName, {0} }
#define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \
{nArg,SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\
- SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, #zName, {0} }
+ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} }
#define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \
{nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \
- pArg, 0, xFunc, 0, #zName, }
+ pArg, 0, xFunc, 0, 0, 0, #zName, }
#define LIKEFUNC(zName, nArg, arg, flags) \
{nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \
- (void *)arg, 0, likeFunc, 0, #zName, {0} }
-#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal) \
- {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL), \
- SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}}
-#define AGGREGATE2(zName, nArg, arg, nc, xStep, xFinal, extraFlags) \
- {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|extraFlags, \
- SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,#zName, {0}}
+ (void *)arg, 0, likeFunc, 0, 0, 0, #zName, {0} }
+#define WAGGREGATE(zName, nArg, arg, nc, xStep, xFinal, xValue, xInverse, f) \
+ {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|f, \
+ SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,xValue,xInverse,#zName, {0}}
+#define INTERNAL_FUNCTION(zName, nArg, xFunc) \
+ {nArg, SQLITE_FUNC_INTERNAL|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \
+ 0, 0, xFunc, 0, 0, 0, #zName, {0} }
+
/*
** All current savepoints are stored in a linked list starting at
@@ -14268,7 +17179,7 @@ struct Savepoint {
};
/*
-** The following are used as the second parameter to sqlite3Savepoint(),
+** The following are used as the second parameter to tdsqlite3Savepoint(),
** and as the P1 argument to the OP_Savepoint instruction.
*/
#define SAVEPOINT_BEGIN 0
@@ -14282,32 +17193,54 @@ struct Savepoint {
** hash table.
*/
struct Module {
- const sqlite3_module *pModule; /* Callback pointers */
+ const tdsqlite3_module *pModule; /* Callback pointers */
const char *zName; /* Name passed to create_module() */
+ int nRefModule; /* Number of pointers to this object */
void *pAux; /* pAux passed to create_module() */
void (*xDestroy)(void *); /* Module destructor function */
Table *pEpoTab; /* Eponymous table for this module */
};
/*
-** information about each column of an SQL table is held in an instance
-** of this structure.
+** Information about each column of an SQL table is held in an instance
+** of the Column structure, in the Table.aCol[] array.
+**
+** Definitions:
+**
+** "table column index" This is the index of the column in the
+** Table.aCol[] array, and also the index of
+** the column in the original CREATE TABLE stmt.
+**
+** "storage column index" This is the index of the column in the
+** record BLOB generated by the OP_MakeRecord
+** opcode. The storage column index is less than
+** or equal to the table column index. It is
+** equal if and only if there are no VIRTUAL
+** columns to the left.
*/
struct Column {
char *zName; /* Name of this column, \000, then the type */
- Expr *pDflt; /* Default value of this column */
+ Expr *pDflt; /* Default value or GENERATED ALWAYS AS value */
char *zColl; /* Collating sequence. If NULL, use the default */
u8 notNull; /* An OE_ code for handling a NOT NULL constraint */
char affinity; /* One of the SQLITE_AFF_... values */
u8 szEst; /* Estimated size of value in this column. sizeof(INT)==1 */
- u8 colFlags; /* Boolean properties. See COLFLAG_ defines below */
+ u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */
};
/* Allowed values for Column.colFlags:
*/
-#define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */
-#define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */
-#define COLFLAG_HASTYPE 0x0004 /* Type name follows column name */
+#define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */
+#define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */
+#define COLFLAG_HASTYPE 0x0004 /* Type name follows column name */
+#define COLFLAG_UNIQUE 0x0008 /* Column def contains "UNIQUE" or "PK" */
+#define COLFLAG_SORTERREF 0x0010 /* Use sorter-refs with this column */
+#define COLFLAG_VIRTUAL 0x0020 /* GENERATED ALWAYS AS ... VIRTUAL */
+#define COLFLAG_STORED 0x0040 /* GENERATED ALWAYS AS ... STORED */
+#define COLFLAG_NOTAVAIL 0x0080 /* STORED column not yet calculated */
+#define COLFLAG_BUSY 0x0100 /* Blocks recursion on GENERATED columns */
+#define COLFLAG_GENERATED 0x0060 /* Combo: _STORED, _VIRTUAL */
+#define COLFLAG_NOINSERT 0x0062 /* Combo: _HIDDEN, _STORED, _VIRTUAL */
/*
** A "Collating Sequence" is defined by an instance of the following
@@ -14347,13 +17280,14 @@ struct CollSeq {
** Note also that the numeric types are grouped together so that testing
** for a numeric type is a single comparison. And the BLOB type is first.
*/
-#define SQLITE_AFF_BLOB 'A'
-#define SQLITE_AFF_TEXT 'B'
-#define SQLITE_AFF_NUMERIC 'C'
-#define SQLITE_AFF_INTEGER 'D'
-#define SQLITE_AFF_REAL 'E'
+#define SQLITE_AFF_NONE 0x40 /* '@' */
+#define SQLITE_AFF_BLOB 0x41 /* 'A' */
+#define SQLITE_AFF_TEXT 0x42 /* 'B' */
+#define SQLITE_AFF_NUMERIC 0x43 /* 'C' */
+#define SQLITE_AFF_INTEGER 0x44 /* 'D' */
+#define SQLITE_AFF_REAL 0x45 /* 'E' */
-#define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC)
+#define tdsqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC)
/*
** The SQLITE_AFF_MASK values masks off the significant bits of an
@@ -14381,10 +17315,10 @@ struct CollSeq {
** the database schema.
**
** If the database schema is shared, then there is one instance of this
-** structure for each database connection (sqlite3*) that uses the shared
+** structure for each database connection (tdsqlite3*) that uses the shared
** schema. This is because each database connection requires its own unique
-** instance of the sqlite3_vtab* handle used to access the virtual table
-** implementation. sqlite3_vtab* handles can not be shared between
+** instance of the tdsqlite3_vtab* handle used to access the virtual table
+** implementation. tdsqlite3_vtab* handles can not be shared between
** database connections, even when the rest of the in-memory database
** schema is shared, as the implementation often stores the database
** connection handle passed to it via the xConnect() or xCreate() method
@@ -14397,37 +17331,44 @@ struct CollSeq {
** All VTable objects that correspond to a single table in a shared
** database schema are initially stored in a linked-list pointed to by
** the Table.pVTable member variable of the corresponding Table object.
-** When an sqlite3_prepare() operation is required to access the virtual
+** When an tdsqlite3_prepare() operation is required to access the virtual
** table, it searches the list for the VTable that corresponds to the
** database connection doing the preparing so as to use the correct
-** sqlite3_vtab* handle in the compiled query.
+** tdsqlite3_vtab* handle in the compiled query.
**
** When an in-memory Table object is deleted (for example when the
** schema is being reloaded for some reason), the VTable objects are not
-** deleted and the sqlite3_vtab* handles are not xDisconnect()ed
+** deleted and the tdsqlite3_vtab* handles are not xDisconnect()ed
** immediately. Instead, they are moved from the Table.pVTable list to
** another linked list headed by the sqlite3.pDisconnect member of the
-** corresponding sqlite3 structure. They are then deleted/xDisconnected
-** next time a statement is prepared using said sqlite3*. This is done
+** corresponding tdsqlite3 structure. They are then deleted/xDisconnected
+** next time a statement is prepared using said tdsqlite3*. This is done
** to avoid deadlock issues involving multiple sqlite3.mutex mutexes.
-** Refer to comments above function sqlite3VtabUnlockList() for an
+** Refer to comments above function tdsqlite3VtabUnlockList() for an
** explanation as to why it is safe to add an entry to an sqlite3.pDisconnect
** list without holding the corresponding sqlite3.mutex mutex.
**
** The memory for objects of this type is always allocated by
-** sqlite3DbMalloc(), using the connection handle stored in VTable.db as
+** tdsqlite3DbMalloc(), using the connection handle stored in VTable.db as
** the first argument.
*/
struct VTable {
- sqlite3 *db; /* Database connection associated with this table */
+ tdsqlite3 *db; /* Database connection associated with this table */
Module *pMod; /* Pointer to module implementation */
- sqlite3_vtab *pVtab; /* Pointer to vtab instance */
+ tdsqlite3_vtab *pVtab; /* Pointer to vtab instance */
int nRef; /* Number of pointers to this structure */
u8 bConstraint; /* True if constraints are supported */
+ u8 eVtabRisk; /* Riskiness of allowing hacker access */
int iSavepoint; /* Depth of the SAVEPOINT stack */
VTable *pNext; /* Next in linked list (see above) */
};
+/* Allowed values for VTable.eVtabRisk
+*/
+#define SQLITE_VTABRISK_Low 0
+#define SQLITE_VTABRISK_Normal 1
+#define SQLITE_VTABRISK_High 2
+
/*
** The schema for each SQL table and view is represented in memory
** by an instance of the following structure.
@@ -14442,15 +17383,16 @@ struct Table {
ExprList *pCheck; /* All CHECK constraints */
/* ... also used as column name list in a VIEW */
int tnum; /* Root BTree page for this table */
+ u32 nTabRef; /* Number of pointers to this Table */
+ u32 tabFlags; /* Mask of TF_* values */
i16 iPKey; /* If not negative, use aCol[iPKey] as the rowid */
i16 nCol; /* Number of columns in this table */
- u16 nRef; /* Number of pointers to this Table */
+ i16 nNVCol; /* Number of columns that are not VIRTUAL */
LogEst nRowLogEst; /* Estimated rows in table - from sqlite_stat1 table */
LogEst szTabRow; /* Estimated size of each table row in bytes */
#ifdef SQLITE_ENABLE_COSTMULT
LogEst costMult; /* Cost multiplier for using this table */
#endif
- u8 tabFlags; /* Mask of TF_* values */
u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */
#ifndef SQLITE_OMIT_ALTERTABLE
int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */
@@ -14472,17 +17414,28 @@ struct Table {
** followed by non-hidden columns. Example: "CREATE VIRTUAL TABLE x USING
** vtab1(a HIDDEN, b);". Since "b" is a non-hidden column but "a" is hidden,
** the TF_OOOHidden attribute would apply in this case. Such tables require
-** special handling during INSERT processing.
-*/
-#define TF_Readonly 0x01 /* Read-only system table */
-#define TF_Ephemeral 0x02 /* An ephemeral table */
-#define TF_HasPrimaryKey 0x04 /* Table has a primary key */
-#define TF_Autoincrement 0x08 /* Integer primary key is autoincrement */
-#define TF_Virtual 0x10 /* Is a virtual table */
-#define TF_WithoutRowid 0x20 /* No rowid. PRIMARY KEY is the key */
-#define TF_NoVisibleRowid 0x40 /* No user-visible "rowid" column */
-#define TF_OOOHidden 0x80 /* Out-of-Order hidden columns */
-
+** special handling during INSERT processing. The "OOO" means "Out Of Order".
+**
+** Constraints:
+**
+** TF_HasVirtual == COLFLAG_Virtual
+** TF_HasStored == COLFLAG_Stored
+*/
+#define TF_Readonly 0x0001 /* Read-only system table */
+#define TF_Ephemeral 0x0002 /* An ephemeral table */
+#define TF_HasPrimaryKey 0x0004 /* Table has a primary key */
+#define TF_Autoincrement 0x0008 /* Integer primary key is autoincrement */
+#define TF_HasStat1 0x0010 /* nRowLogEst set from sqlite_stat1 */
+#define TF_HasVirtual 0x0020 /* Has one or more VIRTUAL columns */
+#define TF_HasStored 0x0040 /* Has one or more STORED columns */
+#define TF_HasGenerated 0x0060 /* Combo: HasVirtual + HasStored */
+#define TF_WithoutRowid 0x0080 /* No rowid. PRIMARY KEY is the key */
+#define TF_StatsUsed 0x0100 /* Query planner decisions affected by
+ ** Index.aiRowLogEst[] values */
+#define TF_NoVisibleRowid 0x0200 /* No user-visible "rowid" column */
+#define TF_OOOHidden 0x0400 /* Out-of-Order hidden columns */
+#define TF_HasNotNull 0x0800 /* Contains NOT NULL constraints */
+#define TF_Shadow 0x1000 /* True for a shadow table */
/*
** Test to see whether or not a table is a virtual table. This is
@@ -14490,7 +17443,7 @@ struct Table {
** table support is omitted from the build.
*/
#ifndef SQLITE_OMIT_VIRTUALTABLE
-# define IsVirtual(X) (((X)->tabFlags & TF_Virtual)!=0)
+# define IsVirtual(X) ((X)->nModuleArg)
#else
# define IsVirtual(X) 0
#endif
@@ -14593,18 +17546,17 @@ struct FKey {
#define OE_Fail 3 /* Stop the operation but leave all prior changes */
#define OE_Ignore 4 /* Ignore the error. Do not do the INSERT or UPDATE */
#define OE_Replace 5 /* Delete existing record, then do INSERT or UPDATE */
-
-#define OE_Restrict 6 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */
-#define OE_SetNull 7 /* Set the foreign key value to NULL */
-#define OE_SetDflt 8 /* Set the foreign key value to its default */
-#define OE_Cascade 9 /* Cascade the changes */
-
-#define OE_Default 10 /* Do whatever the default action is */
+#define OE_Update 6 /* Process as a DO UPDATE in an upsert */
+#define OE_Restrict 7 /* OE_Abort for IMMEDIATE, OE_Rollback for DEFERRED */
+#define OE_SetNull 8 /* Set the foreign key value to NULL */
+#define OE_SetDflt 9 /* Set the foreign key value to its default */
+#define OE_Cascade 10 /* Cascade the changes */
+#define OE_Default 11 /* Do whatever the default action is */
/*
** An instance of the following structure is passed as the first
-** argument to sqlite3VdbeKeyCompare and is used to control the
+** argument to tdsqlite3VdbeKeyCompare and is used to control the
** comparison of the two index keys.
**
** Note that aSortOrder[] and aColl[] have nField+1 slots. There
@@ -14614,14 +17566,20 @@ struct FKey {
struct KeyInfo {
u32 nRef; /* Number of references to this KeyInfo object */
u8 enc; /* Text encoding - one of the SQLITE_UTF* values */
- u16 nField; /* Number of key columns in the index */
- u16 nXField; /* Number of columns beyond the key columns */
- sqlite3 *db; /* The database connection */
- u8 *aSortOrder; /* Sort order for each column. */
+ u16 nKeyField; /* Number of key columns in the index */
+ u16 nAllField; /* Total columns, including key plus others */
+ tdsqlite3 *db; /* The database connection */
+ u8 *aSortFlags; /* Sort order for each column. */
CollSeq *aColl[1]; /* Collating sequence for each term of the key */
};
/*
+** Allowed bit values for entries in the KeyInfo.aSortFlags[] array.
+*/
+#define KEYINFO_ORDER_DESC 0x01 /* DESC sort order */
+#define KEYINFO_ORDER_BIGNULL 0x02 /* NULL is larger than any other value */
+
+/*
** This object holds a record which has been parsed out into individual
** fields, for the purposes of doing a comparison.
**
@@ -14662,8 +17620,8 @@ struct UnpackedRecord {
u16 nField; /* Number of entries in apMem[] */
i8 default_rc; /* Comparison result if keys are equal */
u8 errCode; /* Error detected by xRecordCompare (CORRUPT or NOMEM) */
- i8 r1; /* Value to return if (lhs > rhs) */
- i8 r2; /* Value to return if (rhs < lhs) */
+ i8 r1; /* Value to return if (lhs < rhs) */
+ i8 r2; /* Value to return if (lhs > rhs) */
u8 eqSeen; /* True if an equality comparison has been seen */
};
@@ -14719,13 +17677,17 @@ struct Index {
u16 nKeyCol; /* Number of columns forming the key */
u16 nColumn; /* Number of columns stored in the index */
u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
- unsigned idxType:2; /* 1==UNIQUE, 2==PRIMARY KEY, 0==CREATE INDEX */
+ unsigned idxType:2; /* 0:Normal 1:UNIQUE, 2:PRIMARY KEY, 3:IPK */
unsigned bUnordered:1; /* Use this index for == or IN queries only */
unsigned uniqNotNull:1; /* True if UNIQUE and NOT NULL for all columns */
unsigned isResized:1; /* True if resizeIndexObject() has been called */
unsigned isCovering:1; /* True if this is a covering index */
unsigned noSkipScan:1; /* Do not try to use skip-scan if true */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+ unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */
+ unsigned bNoQuery:1; /* Do not use this index to optimize queries */
+ unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */
+ unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */
+#ifdef SQLITE_ENABLE_STAT4
int nSample; /* Number of elements in aSample[] */
int nSampleCol; /* Size of IndexSample.anEq[] and so on */
tRowcnt *aAvgEq; /* Average nEq values for keys not in aSample */
@@ -14733,6 +17695,7 @@ struct Index {
tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this index */
tRowcnt nRowEst0; /* Non-logarithmic number of rows in the index */
#endif
+ Bitmask colNotIdxed; /* 0 for unindexed columns in pTab */
};
/*
@@ -14741,6 +17704,7 @@ struct Index {
#define SQLITE_IDXTYPE_APPDEF 0 /* Created using CREATE INDEX */
#define SQLITE_IDXTYPE_UNIQUE 1 /* Implements a UNIQUE constraint */
#define SQLITE_IDXTYPE_PRIMARYKEY 2 /* Is the PRIMARY KEY for the table */
+#define SQLITE_IDXTYPE_IPK 3 /* INTEGER PRIMARY KEY index */
/* Return true if index X is a PRIMARY KEY index */
#define IsPrimaryKeyIndex(X) ((X)->idxType==SQLITE_IDXTYPE_PRIMARYKEY)
@@ -14755,7 +17719,7 @@ struct Index {
#define XN_EXPR (-2) /* Indexed column is an expression */
/*
-** Each sample stored in the sqlite_stat3 table is represented in memory
+** Each sample stored in the sqlite_stat4 table is represented in memory
** using a structure of this type. See documentation at the top of the
** analyze.c source file for additional information.
*/
@@ -14768,12 +17732,20 @@ struct IndexSample {
};
/*
+** Possible values to use within the flags argument to tdsqlite3GetToken().
+*/
+#define SQLITE_TOKEN_QUOTED 0x1 /* Token is a quoted identifier. */
+#define SQLITE_TOKEN_KEYWORD 0x2 /* Token is a keyword. */
+
+/*
** Each token coming out of the lexer is an instance of
** this structure. Tokens are also used as part of an expression.
**
-** Note if Token.z==0 then Token.dyn and Token.n are undefined and
-** may contain random values. Do not make any assumptions about Token.dyn
-** and Token.n when Token.z==0.
+** The memory that "z" points to is owned by other objects. Take care
+** that the owner of the "z" string does not deallocate the string before
+** the Token goes out of scope! Very often, the "z" points to some place
+** in the middle of the Parse.zSql text. But it might also point to a
+** static string.
*/
struct Token {
const char *z; /* Text of the token. Not NULL-terminated! */
@@ -14905,7 +17877,11 @@ typedef int ynVar;
*/
struct Expr {
u8 op; /* Operation performed by this node */
- char affinity; /* The affinity of the column or 0 if not a column */
+ char affExpr; /* affinity, or RAISE type */
+ u8 op2; /* TK_REGISTER/TK_TRUTH: original value of Expr.op
+ ** TK_COLUMN: the value of p5 for OP_Column
+ ** TK_AGG_FUNCTION: nesting depth
+ ** TK_FUNCTION: NC_SelfRef flag if needs OP_PureFunc */
u32 flags; /* Various flags. EP_* See below */
union {
char *zToken; /* Token value. Zero terminated and dequoted */
@@ -14936,51 +17912,70 @@ struct Expr {
** TK_REGISTER: register number
** TK_TRIGGER: 1 -> new, 0 -> old
** EP_Unlikely: 134217728 times likelihood
+ ** TK_IN: ephemerial table holding RHS
+ ** TK_SELECT_COLUMN: Number of columns on the LHS
** TK_SELECT: 1st register of result vector */
ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid.
** TK_VARIABLE: variable number (always >= 1).
** TK_SELECT_COLUMN: column of the result vector */
i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */
i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */
- u8 op2; /* TK_REGISTER: original value of Expr.op
- ** TK_COLUMN: the value of p5 for OP_Column
- ** TK_AGG_FUNCTION: nesting depth */
AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */
- Table *pTab; /* Table for TK_COLUMN expressions. */
+ union {
+ Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL
+ ** for a column of an index on an expression */
+ Window *pWin; /* EP_WinFunc: Window/Filter defn for a function */
+ struct { /* TK_IN, TK_SELECT, and TK_EXISTS */
+ int iAddr; /* Subroutine entry address */
+ int regReturn; /* Register used to hold return address */
+ } sub;
+ } y;
};
/*
** The following are the meanings of bits in the Expr.flags field.
-*/
-#define EP_FromJoin 0x000001 /* Originates in ON/USING clause of outer join */
-#define EP_Agg 0x000002 /* Contains one or more aggregate functions */
-#define EP_Resolved 0x000004 /* IDs have been resolved to COLUMNs */
-#define EP_Error 0x000008 /* Expression contains one or more errors */
-#define EP_Distinct 0x000010 /* Aggregate function with DISTINCT keyword */
-#define EP_VarSelect 0x000020 /* pSelect is correlated, not constant */
-#define EP_DblQuoted 0x000040 /* token.z was originally in "..." */
-#define EP_InfixFunc 0x000080 /* True for an infix function: LIKE, GLOB, etc */
-#define EP_Collate 0x000100 /* Tree contains a TK_COLLATE operator */
-#define EP_Generic 0x000200 /* Ignore COLLATE or affinity on this tree */
-#define EP_IntValue 0x000400 /* Integer value contained in u.iValue */
-#define EP_xIsSelect 0x000800 /* x.pSelect is valid (otherwise x.pList is) */
-#define EP_Skip 0x001000 /* COLLATE, AS, or UNLIKELY */
-#define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */
-#define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
-#define EP_Static 0x008000 /* Held in memory not obtained from malloc() */
-#define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */
-#define EP_NoReduce 0x020000 /* Cannot EXPRDUP_REDUCE this Expr */
-#define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */
-#define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
-#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
-#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */
-#define EP_Alias 0x400000 /* Is an alias for a result set column */
-#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */
-
-/*
-** Combinations of two or more EP_* flags
-*/
-#define EP_Propagate (EP_Collate|EP_Subquery) /* Propagate these bits up tree */
+** Value restrictions:
+**
+** EP_Agg == NC_HasAgg == SF_HasAgg
+** EP_Win == NC_HasWin
+*/
+#define EP_FromJoin 0x000001 /* Originates in ON/USING clause of outer join */
+#define EP_Distinct 0x000002 /* Aggregate function with DISTINCT keyword */
+#define EP_HasFunc 0x000004 /* Contains one or more functions of any kind */
+#define EP_FixedCol 0x000008 /* TK_Column with a known fixed value */
+#define EP_Agg 0x000010 /* Contains one or more aggregate functions */
+#define EP_VarSelect 0x000020 /* pSelect is correlated, not constant */
+#define EP_DblQuoted 0x000040 /* token.z was originally in "..." */
+#define EP_InfixFunc 0x000080 /* True for an infix function: LIKE, GLOB, etc */
+#define EP_Collate 0x000100 /* Tree contains a TK_COLLATE operator */
+#define EP_Commuted 0x000200 /* Comparison operator has been commuted */
+#define EP_IntValue 0x000400 /* Integer value contained in u.iValue */
+#define EP_xIsSelect 0x000800 /* x.pSelect is valid (otherwise x.pList is) */
+#define EP_Skip 0x001000 /* Operator does not contribute to affinity */
+#define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */
+#define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */
+#define EP_Win 0x008000 /* Contains window functions */
+#define EP_MemToken 0x010000 /* Need to tdsqlite3DbFree() Expr.zToken */
+#define EP_NoReduce 0x020000 /* Cannot EXPRDUP_REDUCE this Expr */
+#define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */
+#define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */
+#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */
+#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */
+#define EP_Alias 0x400000 /* Is an alias for a result set column */
+#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */
+#define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */
+#define EP_Subrtn 0x2000000 /* Uses Expr.y.sub. TK_IN, _SELECT, or _EXISTS */
+#define EP_Quoted 0x4000000 /* TK_ID was originally quoted */
+#define EP_Static 0x8000000 /* Held in memory not obtained from malloc() */
+#define EP_IsTrue 0x10000000 /* Always has boolean value of TRUE */
+#define EP_IsFalse 0x20000000 /* Always has boolean value of FALSE */
+#define EP_FromDDL 0x40000000 /* Originates from sqlite_master */
+
+/*
+** The EP_Propagate mask is a set of properties that automatically propagate
+** upwards into parent nodes.
+*/
+#define EP_Propagate (EP_Collate|EP_Subquery|EP_HasFunc)
/*
** These macros can be used to test, set, or clear bits in the
@@ -14990,6 +17985,8 @@ struct Expr {
#define ExprHasAllProperty(E,P) (((E)->flags&(P))==(P))
#define ExprSetProperty(E,P) (E)->flags|=(P)
#define ExprClearProperty(E,P) (E)->flags&=~(P)
+#define ExprAlwaysTrue(E) (((E)->flags&(EP_FromJoin|EP_IsTrue))==EP_IsTrue)
+#define ExprAlwaysFalse(E) (((E)->flags&(EP_FromJoin|EP_IsFalse))==EP_IsFalse)
/* The ExprSetVVAProperty() macro is used for Verification, Validation,
** and Accreditation only. It works like ExprSetProperty() during VVA
@@ -15011,12 +18008,24 @@ struct Expr {
#define EXPR_TOKENONLYSIZE offsetof(Expr,pLeft) /* Fewer features */
/*
-** Flags passed to the sqlite3ExprDup() function. See the header comment
-** above sqlite3ExprDup() for details.
+** Flags passed to the tdsqlite3ExprDup() function. See the header comment
+** above tdsqlite3ExprDup() for details.
*/
#define EXPRDUP_REDUCE 0x0001 /* Used reduced-size Expr nodes */
/*
+** True if the expression passed as an argument was a function with
+** an OVER() clause (a window function).
+*/
+#ifdef SQLITE_OMIT_WINDOWFUNC
+# define IsWindowFunc(p) 0
+#else
+# define IsWindowFunc(p) ( \
+ ExprHasProperty((p), EP_WinFunc) && p->y.pWin->eFrmType!=TK_FILTER \
+ )
+#endif
+
+/*
** A list of expressions. Each expression may optionally have a
** name. An expr/name combination can be used in several ways, such
** as the list of "expr AS ID" fields following a "SELECT" or in the
@@ -15024,24 +18033,31 @@ struct Expr {
** also be used as the argument to a function, in which case the a.zName
** field is not used.
**
-** By default the Expr.zSpan field holds a human-readable description of
-** the expression that is used in the generation of error messages and
-** column labels. In this case, Expr.zSpan is typically the text of a
-** column expression as it exists in a SELECT statement. However, if
-** the bSpanIsTab flag is set, then zSpan is overloaded to mean the name
-** of the result column in the form: DATABASE.TABLE.COLUMN. This later
-** form is used for name resolution with nested FROM clauses.
+** In order to try to keep memory usage down, the Expr.a.zEName field
+** is used for multiple purposes:
+**
+** eEName Usage
+** ---------- -------------------------
+** ENAME_NAME (1) the AS of result set column
+** (2) COLUMN= of an UPDATE
+**
+** ENAME_TAB DB.TABLE.NAME used to resolve names
+** of subqueries
+**
+** ENAME_SPAN Text of the original result set
+** expression.
*/
struct ExprList {
int nExpr; /* Number of expressions on the list */
struct ExprList_item { /* For each expression in the list */
- Expr *pExpr; /* The list of expressions */
- char *zName; /* Token associated with this expression */
- char *zSpan; /* Original text of the expression */
- u8 sortOrder; /* 1 for DESC or 0 for ASC */
+ Expr *pExpr; /* The parse tree for this expression */
+ char *zEName; /* Token associated with this expression */
+ u8 sortFlags; /* Mask of KEYINFO_ORDER_* flags */
+ unsigned eEName :2; /* Meaning of zEName */
unsigned done :1; /* A flag to indicate when processing is finished */
- unsigned bSpanIsTab :1; /* zSpan holds DB.TABLE.COLUMN */
unsigned reusable :1; /* Constant expression is reusable */
+ unsigned bSorterRef :1; /* Defer evaluation until after sorting */
+ unsigned bNulls: 1; /* True if explicit "NULLS FIRST/LAST" */
union {
struct {
u16 iOrderByCol; /* For ORDER BY, column number in result set */
@@ -15049,19 +18065,15 @@ struct ExprList {
} x;
int iConstExprReg; /* Register in which Expr value is cached */
} u;
- } *a; /* Alloc a power of two greater or equal to nExpr */
+ } a[1]; /* One slot for each expression in the list */
};
/*
-** An instance of this structure is used by the parser to record both
-** the parse tree for an expression and the span of input text for an
-** expression.
+** Allowed values for Expr.a.eEName
*/
-struct ExprSpan {
- Expr *pExpr; /* The expression parse tree */
- const char *zStart; /* First character of input text */
- const char *zEnd; /* One character past the end of input text */
-};
+#define ENAME_NAME 0 /* The AS clause of a result set */
+#define ENAME_SPAN 1 /* Complete text of the result set expression */
+#define ENAME_TAB 2 /* "DB.TABLE.NAME" for the result set */
/*
** An instance of this structure can hold a simple list of identifiers,
@@ -15087,31 +18099,6 @@ struct IdList {
};
/*
-** The bitmask datatype defined below is used for various optimizations.
-**
-** Changing this from a 64-bit to a 32-bit type limits the number of
-** tables in a join to 32 instead of 64. But it also reduces the size
-** of the library by 738 bytes on ix86.
-*/
-#ifdef SQLITE_BITMASK_TYPE
- typedef SQLITE_BITMASK_TYPE Bitmask;
-#else
- typedef u64 Bitmask;
-#endif
-
-/*
-** The number of bits in a Bitmask. "BMS" means "BitMask Size".
-*/
-#define BMS ((int)(sizeof(Bitmask)*8))
-
-/*
-** A bit in a Bitmask
-*/
-#define MASKBIT(n) (((Bitmask)1)<<(n))
-#define MASKBIT32(n) (((unsigned int)1)<<(n))
-#define ALLBITS ((Bitmask)-1)
-
-/*
** The following structure describes the FROM clause of a SELECT statement.
** Each table or subquery in the FROM clause is a separate element of
** the SrcList.a[] array.
@@ -15124,7 +18111,7 @@ struct IdList {
**
** The jointype starts out showing the join type between the current table
** and the next table on the list. The parser builds the list this way.
-** But sqlite3SrcListShiftJoinType() later shifts the jointypes so that each
+** But tdsqlite3SrcListShiftJoinType() later shifts the jointypes so that each
** jointype expresses the join between the table and the previous table.
**
** In the colUsed field, the high-order bit (bit 63) is set if the table
@@ -15151,10 +18138,8 @@ struct SrcList {
unsigned isCorrelated :1; /* True if sub-query is correlated */
unsigned viaCoroutine :1; /* Implemented as a co-routine */
unsigned isRecursive :1; /* True for recursive reference in WITH */
+ unsigned fromDDL :1; /* Comes from sqlite_master */
} fg;
-#ifndef SQLITE_OMIT_EXPLAIN
- u8 iSelectId; /* If pSelect!=0, the id of the sub-select in EQP */
-#endif
int iCursor; /* The VDBE cursor number used to access this table */
Expr *pOn; /* The ON clause of a join */
IdList *pUsing; /* The USING clause of a join */
@@ -15180,7 +18165,7 @@ struct SrcList {
/*
-** Flags appropriate for the wctrlFlags parameter of sqlite3WhereBegin()
+** Flags appropriate for the wctrlFlags parameter of tdsqlite3WhereBegin()
** and the WhereInfo.wctrlFlags member.
**
** Value constraints (enforced via assert()):
@@ -15197,15 +18182,15 @@ struct SrcList {
#define WHERE_GROUPBY 0x0040 /* pOrderBy is really a GROUP BY */
#define WHERE_DISTINCTBY 0x0080 /* pOrderby is really a DISTINCT clause */
#define WHERE_WANT_DISTINCT 0x0100 /* All output needs to be distinct */
-#define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */
+#define WHERE_SORTBYGROUP 0x0200 /* Support tdsqlite3WhereIsSorted() */
#define WHERE_SEEK_TABLE 0x0400 /* Do not defer seeks on main table */
#define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */
- /* 0x1000 not currently used */
+#define WHERE_SEEK_UNIQ_TABLE 0x1000 /* Do not defer seeks if unique */
/* 0x2000 not currently used */
#define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */
/* 0x8000 not currently used */
-/* Allowed return values from sqlite3WhereIsDistinct()
+/* Allowed return values from tdsqlite3WhereIsDistinct()
*/
#define WHERE_DISTINCT_NOOP 0 /* DISTINCT keyword not used */
#define WHERE_DISTINCT_UNIQUE 1 /* No duplicates */
@@ -15236,40 +18221,82 @@ struct SrcList {
struct NameContext {
Parse *pParse; /* The parser */
SrcList *pSrcList; /* One or more tables used to resolve names */
- ExprList *pEList; /* Optional list of result-set columns */
- AggInfo *pAggInfo; /* Information about aggregates at this level */
+ union {
+ ExprList *pEList; /* Optional list of result-set columns */
+ AggInfo *pAggInfo; /* Information about aggregates at this level */
+ Upsert *pUpsert; /* ON CONFLICT clause information from an upsert */
+ } uNC;
NameContext *pNext; /* Next outer name context. NULL for outermost */
int nRef; /* Number of names resolved by this context */
int nErr; /* Number of errors encountered while resolving names */
- u16 ncFlags; /* Zero or more NC_* flags defined below */
+ int ncFlags; /* Zero or more NC_* flags defined below */
+ Select *pWinSelect; /* SELECT statement for any window functions */
};
/*
** Allowed values for the NameContext, ncFlags field.
**
** Value constraints (all checked via assert()):
-** NC_HasAgg == SF_HasAgg
+** NC_HasAgg == SF_HasAgg == EP_Agg
** NC_MinMaxAgg == SF_MinMaxAgg == SQLITE_FUNC_MINMAX
-**
-*/
-#define NC_AllowAgg 0x0001 /* Aggregate functions are allowed here */
-#define NC_PartIdx 0x0002 /* True if resolving a partial index WHERE */
-#define NC_IsCheck 0x0004 /* True if resolving names in a CHECK constraint */
-#define NC_InAggFunc 0x0008 /* True if analyzing arguments to an agg func */
-#define NC_HasAgg 0x0010 /* One or more aggregate functions seen */
-#define NC_IdxExpr 0x0020 /* True if resolving columns of CREATE INDEX */
-#define NC_VarSelect 0x0040 /* A correlated subquery has been seen */
-#define NC_MinMaxAgg 0x1000 /* min/max aggregates seen. See note above */
+** NC_HasWin == EP_Win
+**
+*/
+#define NC_AllowAgg 0x00001 /* Aggregate functions are allowed here */
+#define NC_PartIdx 0x00002 /* True if resolving a partial index WHERE */
+#define NC_IsCheck 0x00004 /* True if resolving a CHECK constraint */
+#define NC_GenCol 0x00008 /* True for a GENERATED ALWAYS AS clause */
+#define NC_HasAgg 0x00010 /* One or more aggregate functions seen */
+#define NC_IdxExpr 0x00020 /* True if resolving columns of CREATE INDEX */
+#define NC_SelfRef 0x0002e /* Combo: PartIdx, isCheck, GenCol, and IdxExpr */
+#define NC_VarSelect 0x00040 /* A correlated subquery has been seen */
+#define NC_UEList 0x00080 /* True if uNC.pEList is used */
+#define NC_UAggInfo 0x00100 /* True if uNC.pAggInfo is used */
+#define NC_UUpsert 0x00200 /* True if uNC.pUpsert is used */
+#define NC_MinMaxAgg 0x01000 /* min/max aggregates seen. See note above */
+#define NC_Complex 0x02000 /* True if a function or subquery seen */
+#define NC_AllowWin 0x04000 /* Window functions are allowed here */
+#define NC_HasWin 0x08000 /* One or more window functions seen */
+#define NC_IsDDL 0x10000 /* Resolving names in a CREATE statement */
+#define NC_InAggFunc 0x20000 /* True if analyzing arguments to an agg func */
+#define NC_FromDDL 0x40000 /* SQL text comes from sqlite_master */
+
+/*
+** An instance of the following object describes a single ON CONFLICT
+** clause in an upsert.
+**
+** The pUpsertTarget field is only set if the ON CONFLICT clause includes
+** conflict-target clause. (In "ON CONFLICT(a,b)" the "(a,b)" is the
+** conflict-target clause.) The pUpsertTargetWhere is the optional
+** WHERE clause used to identify partial unique indexes.
+**
+** pUpsertSet is the list of column=expr terms of the UPDATE statement.
+** The pUpsertSet field is NULL for a ON CONFLICT DO NOTHING. The
+** pUpsertWhere is the WHERE clause for the UPDATE and is NULL if the
+** WHERE clause is omitted.
+*/
+struct Upsert {
+ ExprList *pUpsertTarget; /* Optional description of conflicting index */
+ Expr *pUpsertTargetWhere; /* WHERE clause for partial index targets */
+ ExprList *pUpsertSet; /* The SET clause from an ON CONFLICT UPDATE */
+ Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */
+ /* The fields above comprise the parse tree for the upsert clause.
+ ** The fields below are used to transfer information from the INSERT
+ ** processing down into the UPDATE processing while generating code.
+ ** Upsert owns the memory allocated above, but not the memory below. */
+ Index *pUpsertIdx; /* Constraint that pUpsertTarget identifies */
+ SrcList *pUpsertSrc; /* Table to be updated */
+ int regData; /* First register holding array of VALUES */
+ int iDataCur; /* Index of the data cursor */
+ int iIdxCur; /* Index of the first index cursor */
+};
/*
** An instance of the following structure contains all information
** needed to generate code for a single SELECT statement.
**
-** nLimit is set to -1 if there is no LIMIT clause. nOffset is set to 0.
-** If there is a LIMIT clause, the parser sets nLimit to the value of the
-** limit and nOffset to the value of the offset (or 0 if there is not
-** offset). But later on, nLimit and nOffset become the memory locations
-** in the VDBE that record the limit and offset counters.
+** See the header comment on the computeLimitRegisters() routine for a
+** detailed description of the meaning of the iLimit and iOffset fields.
**
** addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes.
** These addresses must be stored so that we can go back and fill in
@@ -15282,15 +18309,13 @@ struct NameContext {
** sequences for the ORDER BY clause.
*/
struct Select {
- ExprList *pEList; /* The fields of the result */
u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */
LogEst nSelectRow; /* Estimated number of result rows */
u32 selFlags; /* Various SF_* values */
int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */
-#if SELECTTRACE_ENABLED
- char zSelName[12]; /* Symbolic name of this SELECT use for debugging */
-#endif
+ u32 selId; /* Unique identifier number for this SELECT */
int addrOpenEphm[2]; /* OP_OpenEphem opcodes related to this select */
+ ExprList *pEList; /* The fields of the result */
SrcList *pSrc; /* The FROM clause */
Expr *pWhere; /* The WHERE clause */
ExprList *pGroupBy; /* The GROUP BY clause */
@@ -15299,8 +18324,11 @@ struct Select {
Select *pPrior; /* Prior select in a compound select statement */
Select *pNext; /* Next select to the left in a compound */
Expr *pLimit; /* LIMIT expression. NULL means not used. */
- Expr *pOffset; /* OFFSET expression. NULL means not used. */
With *pWith; /* WITH clause attached to this select. Or NULL. */
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ Window *pWin; /* List of window functions */
+ Window *pWinDefn; /* List of named window definitions */
+#endif
};
/*
@@ -15312,25 +18340,28 @@ struct Select {
** SF_MinMaxAgg == NC_MinMaxAgg == SQLITE_FUNC_MINMAX
** SF_FixedLimit == WHERE_USE_LIMIT
*/
-#define SF_Distinct 0x00001 /* Output should be DISTINCT */
-#define SF_All 0x00002 /* Includes the ALL keyword */
-#define SF_Resolved 0x00004 /* Identifiers have been resolved */
-#define SF_Aggregate 0x00008 /* Contains agg functions or a GROUP BY */
-#define SF_HasAgg 0x00010 /* Contains aggregate functions */
-#define SF_UsesEphemeral 0x00020 /* Uses the OpenEphemeral opcode */
-#define SF_Expanded 0x00040 /* sqlite3SelectExpand() called on this */
-#define SF_HasTypeInfo 0x00080 /* FROM subqueries have Table metadata */
-#define SF_Compound 0x00100 /* Part of a compound query */
-#define SF_Values 0x00200 /* Synthesized from VALUES clause */
-#define SF_MultiValue 0x00400 /* Single VALUES term with multiple rows */
-#define SF_NestedFrom 0x00800 /* Part of a parenthesized FROM clause */
-#define SF_MinMaxAgg 0x01000 /* Aggregate containing min() or max() */
-#define SF_Recursive 0x02000 /* The recursive part of a recursive CTE */
-#define SF_FixedLimit 0x04000 /* nSelectRow set by a constant LIMIT */
-#define SF_MaybeConvert 0x08000 /* Need convertCompoundSelectToSubquery() */
-#define SF_Converted 0x10000 /* By convertCompoundSelectToSubquery() */
-#define SF_IncludeHidden 0x20000 /* Include hidden columns in output */
-
+#define SF_Distinct 0x0000001 /* Output should be DISTINCT */
+#define SF_All 0x0000002 /* Includes the ALL keyword */
+#define SF_Resolved 0x0000004 /* Identifiers have been resolved */
+#define SF_Aggregate 0x0000008 /* Contains agg functions or a GROUP BY */
+#define SF_HasAgg 0x0000010 /* Contains aggregate functions */
+#define SF_UsesEphemeral 0x0000020 /* Uses the OpenEphemeral opcode */
+#define SF_Expanded 0x0000040 /* tdsqlite3SelectExpand() called on this */
+#define SF_HasTypeInfo 0x0000080 /* FROM subqueries have Table metadata */
+#define SF_Compound 0x0000100 /* Part of a compound query */
+#define SF_Values 0x0000200 /* Synthesized from VALUES clause */
+#define SF_MultiValue 0x0000400 /* Single VALUES term with multiple rows */
+#define SF_NestedFrom 0x0000800 /* Part of a parenthesized FROM clause */
+#define SF_MinMaxAgg 0x0001000 /* Aggregate containing min() or max() */
+#define SF_Recursive 0x0002000 /* The recursive part of a recursive CTE */
+#define SF_FixedLimit 0x0004000 /* nSelectRow set by a constant LIMIT */
+#define SF_MaybeConvert 0x0008000 /* Need convertCompoundSelectToSubquery() */
+#define SF_Converted 0x0010000 /* By convertCompoundSelectToSubquery() */
+#define SF_IncludeHidden 0x0020000 /* Include hidden columns in output */
+#define SF_ComplexResult 0x0040000 /* Result contains subquery or function */
+#define SF_WhereBegin 0x0080000 /* Really a WhereBegin() call. Debug Only */
+#define SF_WinRewrite 0x0100000 /* Window function rewrite accomplished */
+#define SF_View 0x0200000 /* SELECT statement is a view */
/*
** The results of a SELECT can be distributed in several ways, as defined
@@ -15421,10 +18452,10 @@ struct Select {
*/
struct SelectDest {
u8 eDest; /* How to dispose of the results. On of SRT_* above. */
- char *zAffSdst; /* Affinity used when eDest==SRT_Set */
int iSDParm; /* A parameter used by the eDest disposal method */
int iSdst; /* Base register where results are written */
int nSdst; /* Number of registers allocated */
+ char *zAffSdst; /* Affinity used when eDest==SRT_Set */
ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */
};
@@ -15445,13 +18476,6 @@ struct AutoincInfo {
};
/*
-** Size of the column cache
-*/
-#ifndef SQLITE_N_COLCACHE
-# define SQLITE_N_COLCACHE 10
-#endif
-
-/*
** At least one instance of the following structure is created for each
** trigger that may be fired while parsing an INSERT, UPDATE or DELETE
** statement. All such objects are stored in the linked list headed at
@@ -15485,8 +18509,8 @@ struct TriggerPrg {
# define DbMaskTest(M,I) (((M)[(I)/8]&(1<<((I)&7)))!=0)
# define DbMaskZero(M) memset((M),0,sizeof(M))
# define DbMaskSet(M,I) (M)[(I)/8]|=(1<<((I)&7))
-# define DbMaskAllZero(M) sqlite3DbMaskAllZero(M)
-# define DbMaskNonZero(M) (sqlite3DbMaskAllZero(M)==0)
+# define DbMaskAllZero(M) tdsqlite3DbMaskAllZero(M)
+# define DbMaskNonZero(M) (tdsqlite3DbMaskAllZero(M)==0)
#else
typedef unsigned int yDbMask;
# define DbMaskTest(M,I) (((M)&(((yDbMask)1)<<(I)))!=0)
@@ -15507,13 +18531,13 @@ struct TriggerPrg {
** each recursion.
**
** The nTableLock and aTableLock variables are only used if the shared-cache
-** feature is enabled (if sqlite3Tsd()->useSharedData is true). They are
+** feature is enabled (if tdsqlite3Tsd()->useSharedData is true). They are
** used to store the set of table-locks required by the statement being
-** compiled. Function sqlite3TableLock() is used to add entries to the
+** compiled. Function tdsqlite3TableLock() is used to add entries to the
** list.
*/
struct Parse {
- sqlite3 *db; /* The main database structure */
+ tdsqlite3 *db; /* The main database structure */
char *zErrMsg; /* An error message */
Vdbe *pVdbe; /* An engine for executing database bytecode */
int rc; /* Return code from execution */
@@ -15526,19 +18550,17 @@ struct Parse {
u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */
u8 okConstFactor; /* OK to factor out constants */
u8 disableLookaside; /* Number of times lookaside has been disabled */
- u8 nColCache; /* Number of entries in aColCache[] */
+ u8 disableVtab; /* Disable all virtual tables for this parse */
int nRangeReg; /* Size of the temporary register block */
int iRangeReg; /* First register in temporary register block */
int nErr; /* Number of errors seen */
int nTab; /* Number of previously allocated VDBE cursors */
int nMem; /* Number of memory cells used so far */
- int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */
int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */
- int ckBase; /* Base register of data during check constraints */
- int iSelfTab; /* Table of an index whose exprs are being coded */
- int iCacheLevel; /* ColCache valid when aColCache[].iLevel<=iCacheLevel */
- int iCacheCnt; /* Counter used to generate aColCache[].lru values */
- int nLabel; /* Number of labels used */
+ int iSelfTab; /* Table associated with an index on expr, or negative
+ ** of the base register during check-constraint eval */
+ int nLabel; /* The *negative* of the number of labels used */
+ int nLabelAlloc; /* Number of slots in aLabel */
int *aLabel; /* Space to hold the labels */
ExprList *pConstExpr;/* Constant expressions */
Token constraintName;/* Name of the constraint currently being parsed */
@@ -15547,10 +18569,7 @@ struct Parse {
int regRowid; /* Register holding rowid of CREATE TABLE entry */
int regRoot; /* Register holding root page number for new objects */
int nMaxArg; /* Max args passed to user function by sub-program */
-#if SELECTTRACE_ENABLED
- int nSelect; /* Number of SELECT statements seen */
- int nSelectIndent; /* How far to indent SELECTTRACE() output */
-#endif
+ int nSelect; /* Number of SELECT stmts. Counter for Select.selId */
#ifndef SQLITE_OMIT_SHARED_CACHE
int nTableLock; /* Number of locks in aTableLock */
TableLock *aTableLock; /* Required table locks for shared-cache mode */
@@ -15558,7 +18577,8 @@ struct Parse {
AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */
Parse *pToplevel; /* Parse structure for main program (or NULL) */
Table *pTriggerTab; /* Table triggers are being coded for */
- int addrCrTab; /* Address of OP_CreateTable opcode on CREATE TABLE */
+ Parse *pParentParse; /* Parent parser if this parser is nested */
+ int addrCrTab; /* Address of OP_CreateBtree opcode on CREATE TABLE */
u32 nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */
u32 oldmask; /* Mask of old.* columns referenced */
u32 newmask; /* Mask of new.* columns referenced */
@@ -15570,17 +18590,9 @@ struct Parse {
** Fields above must be initialized to zero. The fields that follow,
** down to the beginning of the recursive section, do not need to be
** initialized as they will be set before being used. The boundary is
- ** determined by offsetof(Parse,aColCache).
+ ** determined by offsetof(Parse,aTempReg).
**************************************************************************/
- struct yColCache {
- int iTable; /* Table cursor number */
- i16 iColumn; /* Table column number */
- u8 tempReg; /* iReg is a temp register that needs to be freed */
- int iLevel; /* Nesting level */
- int iReg; /* Reg with value of this column. 0 means none. */
- int lru; /* Least recently used entry has the smallest value */
- } aColCache[SQLITE_N_COLCACHE]; /* One for each column cache entry */
int aTempReg[8]; /* Holding area for temporary registers */
Token sNameToken; /* Token with unqualified schema object name */
@@ -15593,22 +18605,25 @@ struct Parse {
Token sLastToken; /* The last token parsed */
ynVar nVar; /* Number of '?' variables seen in the SQL so far */
- int nzVar; /* Number of available slots in azVar[] */
u8 iPkSortOrder; /* ASC or DESC for INTEGER PRIMARY KEY */
u8 explain; /* True if the EXPLAIN flag is found on the query */
+#if !(defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_OMIT_ALTERTABLE))
+ u8 eParseMode; /* PARSE_MODE_XXX constant */
+#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
- u8 declareVtab; /* True if inside sqlite3_declare_vtab() */
int nVtabLock; /* Number of virtual tables to lock */
#endif
int nHeight; /* Expression tree height of current sub-select */
#ifndef SQLITE_OMIT_EXPLAIN
- int iSelectId; /* ID of current select for EXPLAIN output */
- int iNextSelectId; /* Next available select ID for EXPLAIN output */
+ int addrExplain; /* Address of current OP_Explain opcode */
#endif
- char **azVar; /* Pointers to names of parameters */
- Vdbe *pReprepare; /* VM being reprepared (sqlite3Reprepare()) */
+ VList *pVList; /* Mapping between variable names and numbers */
+ Vdbe *pReprepare; /* VM being reprepared (tdsqlite3Reprepare()) */
const char *zTail; /* All SQL text past the last semicolon parsed */
Table *pNewTable; /* A table being constructed by CREATE TABLE */
+ Index *pNewIndex; /* An index being constructed by CREATE INDEX.
+ ** Also used to hold redundant UNIQUE constraints
+ ** during a RENAME COLUMN */
Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */
const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */
#ifndef SQLITE_OMIT_VIRTUALTABLE
@@ -15619,23 +18634,43 @@ struct Parse {
TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */
With *pWith; /* Current WITH clause, or NULL */
With *pWithToFree; /* Free this WITH object at the end of the parse */
+#ifndef SQLITE_OMIT_ALTERTABLE
+ RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */
+#endif
};
+#define PARSE_MODE_NORMAL 0
+#define PARSE_MODE_DECLARE_VTAB 1
+#define PARSE_MODE_RENAME 2
+#define PARSE_MODE_UNMAP 3
+
/*
** Sizes and pointers of various parts of the Parse object.
*/
-#define PARSE_HDR_SZ offsetof(Parse,aColCache) /* Recursive part w/o aColCache*/
+#define PARSE_HDR_SZ offsetof(Parse,aTempReg) /* Recursive part w/o aColCache*/
#define PARSE_RECURSE_SZ offsetof(Parse,sLastToken) /* Recursive part */
#define PARSE_TAIL_SZ (sizeof(Parse)-PARSE_RECURSE_SZ) /* Non-recursive part */
#define PARSE_TAIL(X) (((char*)(X))+PARSE_RECURSE_SZ) /* Pointer to tail */
/*
-** Return true if currently inside an sqlite3_declare_vtab() call.
+** Return true if currently inside an tdsqlite3_declare_vtab() call.
*/
#ifdef SQLITE_OMIT_VIRTUALTABLE
#define IN_DECLARE_VTAB 0
#else
- #define IN_DECLARE_VTAB (pParse->declareVtab)
+ #define IN_DECLARE_VTAB (pParse->eParseMode==PARSE_MODE_DECLARE_VTAB)
+#endif
+
+#if defined(SQLITE_OMIT_ALTERTABLE)
+ #define IN_RENAME_OBJECT 0
+#else
+ #define IN_RENAME_OBJECT (pParse->eParseMode>=PARSE_MODE_RENAME)
+#endif
+
+#if defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_OMIT_ALTERTABLE)
+ #define IN_SPECIAL_PARSE 0
+#else
+ #define IN_SPECIAL_PARSE (pParse->eParseMode!=PARSE_MODE_NORMAL)
#endif
/*
@@ -15661,14 +18696,13 @@ struct AuthContext {
*/
#define OPFLAG_NCHANGE 0x01 /* OP_Insert: Set to update db->nChange */
/* Also used in P2 (not P5) of OP_Delete */
+#define OPFLAG_NOCHNG 0x01 /* OP_VColumn nochange for UPDATE */
#define OPFLAG_EPHEM 0x01 /* OP_Column: Ephemeral output is ok */
-#define OPFLAG_LASTROWID 0x02 /* Set to update db->lastRowid */
+#define OPFLAG_LASTROWID 0x20 /* Set to update db->lastRowid */
#define OPFLAG_ISUPDATE 0x04 /* This OP_Insert is an sql UPDATE */
#define OPFLAG_APPEND 0x08 /* This is likely to be an append */
#define OPFLAG_USESEEKRESULT 0x10 /* Try to avoid a seek in BtreeInsert() */
-#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
#define OPFLAG_ISNOOP 0x40 /* OP_Delete does pre-update-hook only */
-#endif
#define OPFLAG_LENGTHARG 0x40 /* OP_Column only used for length() */
#define OPFLAG_TYPEOFARG 0x80 /* OP_Column only used for typeof() */
#define OPFLAG_BULKCSR 0x01 /* OP_Open** used to open bulk cursor */
@@ -15676,15 +18710,16 @@ struct AuthContext {
#define OPFLAG_FORDELETE 0x08 /* OP_Open should use BTREE_FORDELETE */
#define OPFLAG_P2ISREG 0x10 /* P2 to OP_Open** is a register number */
#define OPFLAG_PERMUTE 0x01 /* OP_Compare: use the permutation */
-#define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete: keep cursor position */
+#define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete/Insert: save cursor pos */
#define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */
+#define OPFLAG_NOCHNG_MAGIC 0x6d /* OP_MakeRecord: serialtype 10 is ok */
/*
* Each trigger present in the database schema is stored as an instance of
* struct Trigger.
*
* Pointers to instances of struct Trigger are stored in two ways.
- * 1. In the "trigHash" hash table (part of the sqlite3* that represents the
+ * 1. In the "trigHash" hash table (part of the tdsqlite3* that represents the
* database). This allows Trigger structures to be retrieved by name.
* 2. All triggers associated with a single table form a linked list, using the
* pNext member of struct Trigger. A pointer to the first element of the
@@ -15752,7 +18787,7 @@ struct Trigger {
* pWhere -> The WHERE clause of the UPDATE statement if one is specified.
* Otherwise NULL.
* pExprList -> A list of the columns to update and the expressions to update
- * them to. See sqlite3Update() documentation of "pChanges"
+ * them to. See tdsqlite3Update() documentation of "pChanges"
* argument.
*
*/
@@ -15763,8 +18798,10 @@ struct TriggerStep {
Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */
char *zTarget; /* Target table for DELETE, UPDATE, INSERT */
Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */
- ExprList *pExprList; /* SET clause for UPDATE. */
+ ExprList *pExprList; /* SET clause for UPDATE */
IdList *pIdList; /* Column names for INSERT */
+ Upsert *pUpsert; /* Upsert clauses on an INSERT */
+ char *zSpan; /* Original SQL text of this command */
TriggerStep *pNext; /* Next in the link-list */
TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */
};
@@ -15778,7 +18815,7 @@ typedef struct DbFixer DbFixer;
struct DbFixer {
Parse *pParse; /* The parsing context. Error messages written here */
Schema *pSchema; /* Fix items to this schema */
- int bVarOnly; /* Check for variable references only */
+ u8 bTemp; /* True for TEMP schema entries */
const char *zDb; /* Make sure all objects are contained in this database */
const char *zType; /* Type of the container - used for error messages */
const Token *pName; /* Name of the container - used for error messages */
@@ -15788,18 +18825,15 @@ struct DbFixer {
** An objected used to accumulate the text of a string where we
** do not necessarily know how big the string will be in the end.
*/
-struct StrAccum {
- sqlite3 *db; /* Optional database for lookaside. Can be NULL */
- char *zBase; /* A base allocation. Not from malloc. */
+struct tdsqlite3_str {
+ tdsqlite3 *db; /* Optional database for lookaside. Can be NULL */
char *zText; /* The string collected so far */
- u32 nChar; /* Length of the string so far */
u32 nAlloc; /* Amount of space allocated in zText */
u32 mxAlloc; /* Maximum allowed allocation. 0 for no malloc usage */
- u8 accError; /* STRACCUM_NOMEM or STRACCUM_TOOBIG */
+ u32 nChar; /* Length of the string so far */
+ u8 accError; /* SQLITE_NOMEM or SQLITE_TOOBIG */
u8 printfFlags; /* SQLITE_PRINTF flags below */
};
-#define STRACCUM_NOMEM 1
-#define STRACCUM_TOOBIG 2
#define SQLITE_PRINTF_INTERNAL 0x01 /* Internal-use-only converters allowed */
#define SQLITE_PRINTF_SQLFUNC 0x02 /* SQL function arguments to VXPrintf */
#define SQLITE_PRINTF_MALLOCED 0x04 /* True if xText is allocated space */
@@ -15809,42 +18843,48 @@ struct StrAccum {
/*
** A pointer to this structure is used to communicate information
-** from sqlite3Init and OP_ParseSchema into the sqlite3InitCallback.
+** from tdsqlite3Init and OP_ParseSchema into the tdsqlite3InitCallback.
*/
typedef struct {
- sqlite3 *db; /* The database being initialized */
+ tdsqlite3 *db; /* The database being initialized */
char **pzErrMsg; /* Error message stored here */
int iDb; /* 0 for main database. 1 for TEMP, 2.. for ATTACHed */
int rc; /* Result code stored here */
+ u32 mInitFlags; /* Flags controlling error messages */
+ u32 nInitRow; /* Number of rows processed */
} InitData;
/*
+** Allowed values for mInitFlags
+*/
+#define INITFLAG_AlterTable 0x0001 /* This is a reparse after ALTER TABLE */
+
+/*
** Structure containing global configuration data for the SQLite library.
**
** This structure also contains some state information.
*/
struct Sqlite3Config {
int bMemstat; /* True to enable memory status */
- int bCoreMutex; /* True to enable core mutexing */
- int bFullMutex; /* True to enable full mutexing */
- int bOpenUri; /* True to interpret filenames as URIs */
- int bUseCis; /* Use covering indices for full-scans */
+ u8 bCoreMutex; /* True to enable core mutexing */
+ u8 bFullMutex; /* True to enable full mutexing */
+ u8 bOpenUri; /* True to interpret filenames as URIs */
+ u8 bUseCis; /* Use covering indices for full-scans */
+ u8 bSmallMalloc; /* Avoid large memory allocations if true */
+ u8 bExtraSchemaChecks; /* Verify type,name,tbl_name in schema */
int mxStrlen; /* Maximum string length */
int neverCorrupt; /* Database is always well-formed */
int szLookaside; /* Default lookaside buffer size */
int nLookaside; /* Default lookaside buffer count */
int nStmtSpill; /* Stmt-journal spill-to-disk threshold */
- sqlite3_mem_methods m; /* Low-level memory allocation interface */
- sqlite3_mutex_methods mutex; /* Low-level mutex interface */
- sqlite3_pcache_methods2 pcache2; /* Low-level page-cache interface */
+ tdsqlite3_mem_methods m; /* Low-level memory allocation interface */
+ tdsqlite3_mutex_methods mutex; /* Low-level mutex interface */
+ tdsqlite3_pcache_methods2 pcache2; /* Low-level page-cache interface */
void *pHeap; /* Heap storage space */
int nHeap; /* Size of pHeap[] */
int mnReq, mxReq; /* Min and max heap requests sizes */
- sqlite3_int64 szMmap; /* mmap() space per open file */
- sqlite3_int64 mxMmap; /* Maximum value for szMmap */
- void *pScratch; /* Scratch memory */
- int szScratch; /* Size of each scratch buffer */
- int nScratch; /* Number of scratch buffers */
+ tdsqlite3_int64 szMmap; /* mmap() space per open file */
+ tdsqlite3_int64 mxMmap; /* Maximum value for szMmap */
void *pPage; /* Page cache memory */
int szPage; /* Size of each page in pPage[] */
int nPage; /* Number of pages in pPage[] */
@@ -15859,25 +18899,30 @@ struct Sqlite3Config {
int isMallocInit; /* True after malloc is initialized */
int isPCacheInit; /* True after malloc is initialized */
int nRefInitMutex; /* Number of users of pInitMutex */
- sqlite3_mutex *pInitMutex; /* Mutex used by sqlite3_initialize() */
+ tdsqlite3_mutex *pInitMutex; /* Mutex used by tdsqlite3_initialize() */
void (*xLog)(void*,int,const char*); /* Function for logging */
void *pLogArg; /* First argument to xLog() */
#ifdef SQLITE_ENABLE_SQLLOG
- void(*xSqllog)(void*,sqlite3*,const char*, int);
+ void(*xSqllog)(void*,tdsqlite3*,const char*, int);
void *pSqllogArg;
#endif
#ifdef SQLITE_VDBE_COVERAGE
/* The following callback (if not NULL) is invoked on every VDBE branch
** operation. Set the callback using SQLITE_TESTCTRL_VDBE_COVERAGE.
*/
- void (*xVdbeBranch)(void*,int iSrcLine,u8 eThis,u8 eMx); /* Callback */
+ void (*xVdbeBranch)(void*,unsigned iSrcLine,u8 eThis,u8 eMx); /* Callback */
void *pVdbeBranchArg; /* 1st argument */
#endif
-#ifndef SQLITE_OMIT_BUILTIN_TEST
- int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */
+#ifdef SQLITE_ENABLE_DESERIALIZE
+ tdsqlite3_int64 mxMemdbSize; /* Default max memdb size */
+#endif
+#ifndef SQLITE_UNTESTABLE
+ int (*xTestCallback)(int); /* Invoked by tdsqlite3FaultSim() */
#endif
int bLocaltimeFault; /* True to fail localtime() calls */
int iOnceResetThreshold; /* When to reset OP_Once counters */
+ u32 szSorterRef; /* Min size in bytes to use sorter-refs */
+ unsigned int iPrngSeed; /* Alternative fixed seed for the PRNG */
};
/*
@@ -15893,10 +18938,10 @@ struct Sqlite3Config {
** CORRUPT_DB is true during normal operation. CORRUPT_DB does not indicate
** that the database is definitely corrupt, only that it might be corrupt.
** For most test cases, CORRUPT_DB is set to false using a special
-** sqlite3_test_control(). This enables assert() statements to prove
+** tdsqlite3_test_control(). This enables assert() statements to prove
** things that are always true for well-formed databases.
*/
-#define CORRUPT_DB (sqlite3Config.neverCorrupt==0)
+#define CORRUPT_DB (tdsqlite3Config.neverCorrupt==0)
/*
** Context pointer passed down through the tree-walk.
@@ -15907,26 +18952,38 @@ struct Walker {
int (*xSelectCallback)(Walker*,Select*); /* Callback for SELECTs */
void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */
int walkerDepth; /* Number of subqueries */
- u8 eCode; /* A small processing code */
+ u16 eCode; /* A small processing code */
union { /* Extra data for callback */
- NameContext *pNC; /* Naming context */
- int n; /* A counter */
- int iCur; /* A cursor number */
- SrcList *pSrcList; /* FROM clause */
- struct SrcCount *pSrcCount; /* Counting column references */
- struct CCurHint *pCCurHint; /* Used by codeCursorHint() */
- int *aiCol; /* array of column indexes */
- struct IdxCover *pIdxCover; /* Check for index coverage */
+ NameContext *pNC; /* Naming context */
+ int n; /* A counter */
+ int iCur; /* A cursor number */
+ SrcList *pSrcList; /* FROM clause */
+ struct SrcCount *pSrcCount; /* Counting column references */
+ struct CCurHint *pCCurHint; /* Used by codeCursorHint() */
+ int *aiCol; /* array of column indexes */
+ struct IdxCover *pIdxCover; /* Check for index coverage */
+ struct IdxExprTrans *pIdxTrans; /* Convert idxed expr to column */
+ ExprList *pGroupBy; /* GROUP BY clause */
+ Select *pSelect; /* HAVING to WHERE clause ctx */
+ struct WindowRewrite *pRewrite; /* Window rewrite context */
+ struct WhereConst *pConst; /* WHERE clause constants */
+ struct RenameCtx *pRename; /* RENAME COLUMN context */
+ struct Table *pTab; /* Table of generated column */
} u;
};
/* Forward declarations */
-SQLITE_PRIVATE int sqlite3WalkExpr(Walker*, Expr*);
-SQLITE_PRIVATE int sqlite3WalkExprList(Walker*, ExprList*);
-SQLITE_PRIVATE int sqlite3WalkSelect(Walker*, Select*);
-SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker*, Select*);
-SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker*, Select*);
-SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker*, Expr*);
+SQLITE_PRIVATE int tdsqlite3WalkExpr(Walker*, Expr*);
+SQLITE_PRIVATE int tdsqlite3WalkExprList(Walker*, ExprList*);
+SQLITE_PRIVATE int tdsqlite3WalkSelect(Walker*, Select*);
+SQLITE_PRIVATE int tdsqlite3WalkSelectExpr(Walker*, Select*);
+SQLITE_PRIVATE int tdsqlite3WalkSelectFrom(Walker*, Select*);
+SQLITE_PRIVATE int tdsqlite3ExprWalkNoop(Walker*, Expr*);
+SQLITE_PRIVATE int tdsqlite3SelectWalkNoop(Walker*, Select*);
+SQLITE_PRIVATE int tdsqlite3SelectWalkFail(Walker*, Select*);
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE void tdsqlite3SelectWalkAssert2(Walker*, Select*);
+#endif
/*
** Return code from the parse-tree walking primitives and their
@@ -15954,7 +19011,7 @@ struct With {
#ifdef SQLITE_DEBUG
/*
** An instance of the TreeView object is used for printing the content of
-** data structures on sqlite3DebugPrintf() using a tree-like view.
+** data structures on tdsqlite3DebugPrintf() using a tree-like view.
*/
struct TreeView {
int iLevel; /* Which level of the tree we are on */
@@ -15963,6 +19020,85 @@ struct TreeView {
#endif /* SQLITE_DEBUG */
/*
+** This object is used in various ways, most (but not all) related to window
+** functions.
+**
+** (1) A single instance of this structure is attached to the
+** the Expr.y.pWin field for each window function in an expression tree.
+** This object holds the information contained in the OVER clause,
+** plus additional fields used during code generation.
+**
+** (2) All window functions in a single SELECT form a linked-list
+** attached to Select.pWin. The Window.pFunc and Window.pExpr
+** fields point back to the expression that is the window function.
+**
+** (3) The terms of the WINDOW clause of a SELECT are instances of this
+** object on a linked list attached to Select.pWinDefn.
+**
+** (4) For an aggregate function with a FILTER clause, an instance
+** of this object is stored in Expr.y.pWin with eFrmType set to
+** TK_FILTER. In this case the only field used is Window.pFilter.
+**
+** The uses (1) and (2) are really the same Window object that just happens
+** to be accessible in two different ways. Use case (3) are separate objects.
+*/
+struct Window {
+ char *zName; /* Name of window (may be NULL) */
+ char *zBase; /* Name of base window for chaining (may be NULL) */
+ ExprList *pPartition; /* PARTITION BY clause */
+ ExprList *pOrderBy; /* ORDER BY clause */
+ u8 eFrmType; /* TK_RANGE, TK_GROUPS, TK_ROWS, or 0 */
+ u8 eStart; /* UNBOUNDED, CURRENT, PRECEDING or FOLLOWING */
+ u8 eEnd; /* UNBOUNDED, CURRENT, PRECEDING or FOLLOWING */
+ u8 bImplicitFrame; /* True if frame was implicitly specified */
+ u8 eExclude; /* TK_NO, TK_CURRENT, TK_TIES, TK_GROUP, or 0 */
+ Expr *pStart; /* Expression for "<expr> PRECEDING" */
+ Expr *pEnd; /* Expression for "<expr> FOLLOWING" */
+ Window **ppThis; /* Pointer to this object in Select.pWin list */
+ Window *pNextWin; /* Next window function belonging to this SELECT */
+ Expr *pFilter; /* The FILTER expression */
+ FuncDef *pFunc; /* The function */
+ int iEphCsr; /* Partition buffer or Peer buffer */
+ int regAccum; /* Accumulator */
+ int regResult; /* Interim result */
+ int csrApp; /* Function cursor (used by min/max) */
+ int regApp; /* Function register (also used by min/max) */
+ int regPart; /* Array of registers for PARTITION BY values */
+ Expr *pOwner; /* Expression object this window is attached to */
+ int nBufferCol; /* Number of columns in buffer table */
+ int iArgCol; /* Offset of first argument for this function */
+ int regOne; /* Register containing constant value 1 */
+ int regStartRowid;
+ int regEndRowid;
+ u8 bExprArgs; /* Defer evaluation of window function arguments
+ ** due to the SQLITE_SUBTYPE flag */
+};
+
+#ifndef SQLITE_OMIT_WINDOWFUNC
+SQLITE_PRIVATE void tdsqlite3WindowDelete(tdsqlite3*, Window*);
+SQLITE_PRIVATE void tdsqlite3WindowUnlinkFromSelect(Window*);
+SQLITE_PRIVATE void tdsqlite3WindowListDelete(tdsqlite3 *db, Window *p);
+SQLITE_PRIVATE Window *tdsqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*, u8);
+SQLITE_PRIVATE void tdsqlite3WindowAttach(Parse*, Expr*, Window*);
+SQLITE_PRIVATE void tdsqlite3WindowLink(Select *pSel, Window *pWin);
+SQLITE_PRIVATE int tdsqlite3WindowCompare(Parse*, Window*, Window*, int);
+SQLITE_PRIVATE void tdsqlite3WindowCodeInit(Parse*, Select*);
+SQLITE_PRIVATE void tdsqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int);
+SQLITE_PRIVATE int tdsqlite3WindowRewrite(Parse*, Select*);
+SQLITE_PRIVATE int tdsqlite3ExpandSubquery(Parse*, struct SrcList_item*);
+SQLITE_PRIVATE void tdsqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*);
+SQLITE_PRIVATE Window *tdsqlite3WindowDup(tdsqlite3 *db, Expr *pOwner, Window *p);
+SQLITE_PRIVATE Window *tdsqlite3WindowListDup(tdsqlite3 *db, Window *p);
+SQLITE_PRIVATE void tdsqlite3WindowFunctions(void);
+SQLITE_PRIVATE void tdsqlite3WindowChain(Parse*, Window*, Window*);
+SQLITE_PRIVATE Window *tdsqlite3WindowAssemble(Parse*, Window*, ExprList*, ExprList*, Token*);
+#else
+# define tdsqlite3WindowDelete(a,b)
+# define tdsqlite3WindowFunctions()
+# define tdsqlite3WindowAttach(a,b,c)
+#endif
+
+/*
** Assuming zIn points to the first byte of a UTF-8 character,
** advance zIn to point to the first byte of the next UTF-8 character.
*/
@@ -15976,23 +19112,27 @@ struct TreeView {
** The SQLITE_*_BKPT macros are substitutes for the error codes with
** the same name but without the _BKPT suffix. These macros invoke
** routines that report the line-number on which the error originated
-** using sqlite3_log(). The routines also provide a convenient place
+** using tdsqlite3_log(). The routines also provide a convenient place
** to set a debugger breakpoint.
*/
-SQLITE_PRIVATE int sqlite3CorruptError(int);
-SQLITE_PRIVATE int sqlite3MisuseError(int);
-SQLITE_PRIVATE int sqlite3CantopenError(int);
-#define SQLITE_CORRUPT_BKPT sqlite3CorruptError(__LINE__)
-#define SQLITE_MISUSE_BKPT sqlite3MisuseError(__LINE__)
-#define SQLITE_CANTOPEN_BKPT sqlite3CantopenError(__LINE__)
+SQLITE_PRIVATE int tdsqlite3ReportError(int iErr, int lineno, const char *zType);
+SQLITE_PRIVATE int tdsqlite3CorruptError(int);
+SQLITE_PRIVATE int tdsqlite3MisuseError(int);
+SQLITE_PRIVATE int tdsqlite3CantopenError(int);
+#define SQLITE_CORRUPT_BKPT tdsqlite3CorruptError(__LINE__)
+#define SQLITE_MISUSE_BKPT tdsqlite3MisuseError(__LINE__)
+#define SQLITE_CANTOPEN_BKPT tdsqlite3CantopenError(__LINE__)
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3NomemError(int);
-SQLITE_PRIVATE int sqlite3IoerrnomemError(int);
-# define SQLITE_NOMEM_BKPT sqlite3NomemError(__LINE__)
-# define SQLITE_IOERR_NOMEM_BKPT sqlite3IoerrnomemError(__LINE__)
+SQLITE_PRIVATE int tdsqlite3NomemError(int);
+SQLITE_PRIVATE int tdsqlite3IoerrnomemError(int);
+SQLITE_PRIVATE int tdsqlite3CorruptPgnoError(int,Pgno);
+# define SQLITE_NOMEM_BKPT tdsqlite3NomemError(__LINE__)
+# define SQLITE_IOERR_NOMEM_BKPT tdsqlite3IoerrnomemError(__LINE__)
+# define SQLITE_CORRUPT_PGNO(P) tdsqlite3CorruptPgnoError(__LINE__,(P))
#else
# define SQLITE_NOMEM_BKPT SQLITE_NOMEM
# define SQLITE_IOERR_NOMEM_BKPT SQLITE_IOERR_NOMEM
+# define SQLITE_CORRUPT_PGNO(P) tdsqlite3CorruptError(__LINE__)
#endif
/*
@@ -16027,60 +19167,59 @@ SQLITE_PRIVATE int sqlite3IoerrnomemError(int);
** sqlite versions only work for ASCII characters, regardless of locale.
*/
#ifdef SQLITE_ASCII
-# define sqlite3Toupper(x) ((x)&~(sqlite3CtypeMap[(unsigned char)(x)]&0x20))
-# define sqlite3Isspace(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x01)
-# define sqlite3Isalnum(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x06)
-# define sqlite3Isalpha(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x02)
-# define sqlite3Isdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x04)
-# define sqlite3Isxdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x08)
-# define sqlite3Tolower(x) (sqlite3UpperToLower[(unsigned char)(x)])
-# define sqlite3Isquote(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x80)
-#else
-# define sqlite3Toupper(x) toupper((unsigned char)(x))
-# define sqlite3Isspace(x) isspace((unsigned char)(x))
-# define sqlite3Isalnum(x) isalnum((unsigned char)(x))
-# define sqlite3Isalpha(x) isalpha((unsigned char)(x))
-# define sqlite3Isdigit(x) isdigit((unsigned char)(x))
-# define sqlite3Isxdigit(x) isxdigit((unsigned char)(x))
-# define sqlite3Tolower(x) tolower((unsigned char)(x))
-# define sqlite3Isquote(x) ((x)=='"'||(x)=='\''||(x)=='['||(x)=='`')
-#endif
-#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
-SQLITE_PRIVATE int sqlite3IsIdChar(u8);
+# define tdsqlite3Toupper(x) ((x)&~(tdsqlite3CtypeMap[(unsigned char)(x)]&0x20))
+# define tdsqlite3Isspace(x) (tdsqlite3CtypeMap[(unsigned char)(x)]&0x01)
+# define tdsqlite3Isalnum(x) (tdsqlite3CtypeMap[(unsigned char)(x)]&0x06)
+# define tdsqlite3Isalpha(x) (tdsqlite3CtypeMap[(unsigned char)(x)]&0x02)
+# define tdsqlite3Isdigit(x) (tdsqlite3CtypeMap[(unsigned char)(x)]&0x04)
+# define tdsqlite3Isxdigit(x) (tdsqlite3CtypeMap[(unsigned char)(x)]&0x08)
+# define tdsqlite3Tolower(x) (tdsqlite3UpperToLower[(unsigned char)(x)])
+# define tdsqlite3Isquote(x) (tdsqlite3CtypeMap[(unsigned char)(x)]&0x80)
+#else
+# define tdsqlite3Toupper(x) toupper((unsigned char)(x))
+# define tdsqlite3Isspace(x) isspace((unsigned char)(x))
+# define tdsqlite3Isalnum(x) isalnum((unsigned char)(x))
+# define tdsqlite3Isalpha(x) isalpha((unsigned char)(x))
+# define tdsqlite3Isdigit(x) isdigit((unsigned char)(x))
+# define tdsqlite3Isxdigit(x) isxdigit((unsigned char)(x))
+# define tdsqlite3Tolower(x) tolower((unsigned char)(x))
+# define tdsqlite3Isquote(x) ((x)=='"'||(x)=='\''||(x)=='['||(x)=='`')
#endif
+SQLITE_PRIVATE int tdsqlite3IsIdChar(u8);
/*
** Internal function prototypes
*/
-SQLITE_PRIVATE int sqlite3StrICmp(const char*,const char*);
-SQLITE_PRIVATE int sqlite3Strlen30(const char*);
-SQLITE_PRIVATE char *sqlite3ColumnType(Column*,char*);
-#define sqlite3StrNICmp sqlite3_strnicmp
-
-SQLITE_PRIVATE int sqlite3MallocInit(void);
-SQLITE_PRIVATE void sqlite3MallocEnd(void);
-SQLITE_PRIVATE void *sqlite3Malloc(u64);
-SQLITE_PRIVATE void *sqlite3MallocZero(u64);
-SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3*, u64);
-SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3*, u64);
-SQLITE_PRIVATE void *sqlite3DbMallocRawNN(sqlite3*, u64);
-SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3*,const char*);
-SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3*,const char*, u64);
-SQLITE_PRIVATE void *sqlite3Realloc(void*, u64);
-SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64);
-SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64);
-SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*);
-SQLITE_PRIVATE int sqlite3MallocSize(void*);
-SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, void*);
-SQLITE_PRIVATE void *sqlite3ScratchMalloc(int);
-SQLITE_PRIVATE void sqlite3ScratchFree(void*);
-SQLITE_PRIVATE void *sqlite3PageMalloc(int);
-SQLITE_PRIVATE void sqlite3PageFree(void*);
-SQLITE_PRIVATE void sqlite3MemSetDefault(void);
-#ifndef SQLITE_OMIT_BUILTIN_TEST
-SQLITE_PRIVATE void sqlite3BenignMallocHooks(void (*)(void), void (*)(void));
-#endif
-SQLITE_PRIVATE int sqlite3HeapNearlyFull(void);
+SQLITE_PRIVATE int tdsqlite3StrICmp(const char*,const char*);
+SQLITE_PRIVATE int tdsqlite3Strlen30(const char*);
+#define tdsqlite3Strlen30NN(C) (strlen(C)&0x3fffffff)
+SQLITE_PRIVATE char *tdsqlite3ColumnType(Column*,char*);
+#define tdsqlite3StrNICmp tdsqlite3_strnicmp
+
+SQLITE_PRIVATE int tdsqlite3MallocInit(void);
+SQLITE_PRIVATE void tdsqlite3MallocEnd(void);
+SQLITE_PRIVATE void *tdsqlite3Malloc(u64);
+SQLITE_PRIVATE void *tdsqlite3MallocZero(u64);
+SQLITE_PRIVATE void *tdsqlite3DbMallocZero(tdsqlite3*, u64);
+SQLITE_PRIVATE void *tdsqlite3DbMallocRaw(tdsqlite3*, u64);
+SQLITE_PRIVATE void *tdsqlite3DbMallocRawNN(tdsqlite3*, u64);
+SQLITE_PRIVATE char *tdsqlite3DbStrDup(tdsqlite3*,const char*);
+SQLITE_PRIVATE char *tdsqlite3DbStrNDup(tdsqlite3*,const char*, u64);
+SQLITE_PRIVATE char *tdsqlite3DbSpanDup(tdsqlite3*,const char*,const char*);
+SQLITE_PRIVATE void *tdsqlite3Realloc(void*, u64);
+SQLITE_PRIVATE void *tdsqlite3DbReallocOrFree(tdsqlite3 *, void *, u64);
+SQLITE_PRIVATE void *tdsqlite3DbRealloc(tdsqlite3 *, void *, u64);
+SQLITE_PRIVATE void tdsqlite3DbFree(tdsqlite3*, void*);
+SQLITE_PRIVATE void tdsqlite3DbFreeNN(tdsqlite3*, void*);
+SQLITE_PRIVATE int tdsqlite3MallocSize(void*);
+SQLITE_PRIVATE int tdsqlite3DbMallocSize(tdsqlite3*, void*);
+SQLITE_PRIVATE void *tdsqlite3PageMalloc(int);
+SQLITE_PRIVATE void tdsqlite3PageFree(void*);
+SQLITE_PRIVATE void tdsqlite3MemSetDefault(void);
+#ifndef SQLITE_UNTESTABLE
+SQLITE_PRIVATE void tdsqlite3BenignMallocHooks(void (*)(void), void (*)(void));
+#endif
+SQLITE_PRIVATE int tdsqlite3HeapNearlyFull(void);
/*
** On systems with ample stack space and that support alloca(), make
@@ -16088,56 +19227,67 @@ SQLITE_PRIVATE int sqlite3HeapNearlyFull(void);
** obtain space from malloc().
**
** The alloca() routine never returns NULL. This will cause code paths
-** that deal with sqlite3StackAlloc() failures to be unreachable.
+** that deal with tdsqlite3StackAlloc() failures to be unreachable.
*/
#ifdef SQLITE_USE_ALLOCA
-# define sqlite3StackAllocRaw(D,N) alloca(N)
-# define sqlite3StackAllocZero(D,N) memset(alloca(N), 0, N)
-# define sqlite3StackFree(D,P)
+# define tdsqlite3StackAllocRaw(D,N) alloca(N)
+# define tdsqlite3StackAllocZero(D,N) memset(alloca(N), 0, N)
+# define tdsqlite3StackFree(D,P)
#else
-# define sqlite3StackAllocRaw(D,N) sqlite3DbMallocRaw(D,N)
-# define sqlite3StackAllocZero(D,N) sqlite3DbMallocZero(D,N)
-# define sqlite3StackFree(D,P) sqlite3DbFree(D,P)
+# define tdsqlite3StackAllocRaw(D,N) tdsqlite3DbMallocRaw(D,N)
+# define tdsqlite3StackAllocZero(D,N) tdsqlite3DbMallocZero(D,N)
+# define tdsqlite3StackFree(D,P) tdsqlite3DbFree(D,P)
#endif
/* Do not allow both MEMSYS5 and MEMSYS3 to be defined together. If they
** are, disable MEMSYS3
*/
#ifdef SQLITE_ENABLE_MEMSYS5
-SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys5(void);
+SQLITE_PRIVATE const tdsqlite3_mem_methods *tdsqlite3MemGetMemsys5(void);
#undef SQLITE_ENABLE_MEMSYS3
#endif
#ifdef SQLITE_ENABLE_MEMSYS3
-SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys3(void);
+SQLITE_PRIVATE const tdsqlite3_mem_methods *tdsqlite3MemGetMemsys3(void);
#endif
#ifndef SQLITE_MUTEX_OMIT
-SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void);
-SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3NoopMutex(void);
-SQLITE_PRIVATE sqlite3_mutex *sqlite3MutexAlloc(int);
-SQLITE_PRIVATE int sqlite3MutexInit(void);
-SQLITE_PRIVATE int sqlite3MutexEnd(void);
+SQLITE_PRIVATE tdsqlite3_mutex_methods const *tdsqlite3DefaultMutex(void);
+SQLITE_PRIVATE tdsqlite3_mutex_methods const *tdsqlite3NoopMutex(void);
+SQLITE_PRIVATE tdsqlite3_mutex *tdsqlite3MutexAlloc(int);
+SQLITE_PRIVATE int tdsqlite3MutexInit(void);
+SQLITE_PRIVATE int tdsqlite3MutexEnd(void);
#endif
#if !defined(SQLITE_MUTEX_OMIT) && !defined(SQLITE_MUTEX_NOOP)
-SQLITE_PRIVATE void sqlite3MemoryBarrier(void);
+SQLITE_PRIVATE void tdsqlite3MemoryBarrier(void);
#else
-# define sqlite3MemoryBarrier()
+# define tdsqlite3MemoryBarrier()
#endif
-SQLITE_PRIVATE sqlite3_int64 sqlite3StatusValue(int);
-SQLITE_PRIVATE void sqlite3StatusUp(int, int);
-SQLITE_PRIVATE void sqlite3StatusDown(int, int);
-SQLITE_PRIVATE void sqlite3StatusHighwater(int, int);
+SQLITE_PRIVATE tdsqlite3_int64 tdsqlite3StatusValue(int);
+SQLITE_PRIVATE void tdsqlite3StatusUp(int, int);
+SQLITE_PRIVATE void tdsqlite3StatusDown(int, int);
+SQLITE_PRIVATE void tdsqlite3StatusHighwater(int, int);
+SQLITE_PRIVATE int tdsqlite3LookasideUsed(tdsqlite3*,int*);
-/* Access to mutexes used by sqlite3_status() */
-SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void);
-SQLITE_PRIVATE sqlite3_mutex *sqlite3MallocMutex(void);
+/* Access to mutexes used by tdsqlite3_status() */
+SQLITE_PRIVATE tdsqlite3_mutex *tdsqlite3Pcache1Mutex(void);
+SQLITE_PRIVATE tdsqlite3_mutex *tdsqlite3MallocMutex(void);
+
+#if defined(SQLITE_ENABLE_MULTITHREADED_CHECKS) && !defined(SQLITE_MUTEX_OMIT)
+SQLITE_PRIVATE void tdsqlite3MutexWarnOnContention(tdsqlite3_mutex*);
+#else
+# define tdsqlite3MutexWarnOnContention(x)
+#endif
#ifndef SQLITE_OMIT_FLOATING_POINT
-SQLITE_PRIVATE int sqlite3IsNaN(double);
+# define EXP754 (((u64)0x7ff)<<52)
+# define MAN754 ((((u64)1)<<52)-1)
+# define IsNaN(X) (((X)&EXP754)==EXP754 && ((X)&MAN754)!=0)
+SQLITE_PRIVATE int tdsqlite3IsNaN(double);
#else
-# define sqlite3IsNaN(X) 0
+# define IsNaN(X) 0
+# define tdsqlite3IsNaN(X) 0
#endif
/*
@@ -16147,370 +19297,418 @@ SQLITE_PRIVATE int sqlite3IsNaN(double);
struct PrintfArguments {
int nArg; /* Total number of arguments */
int nUsed; /* Number of arguments used so far */
- sqlite3_value **apArg; /* The argument values */
+ tdsqlite3_value **apArg; /* The argument values */
};
-SQLITE_PRIVATE void sqlite3VXPrintf(StrAccum*, const char*, va_list);
-SQLITE_PRIVATE void sqlite3XPrintf(StrAccum*, const char*, ...);
-SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3*,const char*, ...);
-SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3*,const char*, va_list);
+SQLITE_PRIVATE char *tdsqlite3MPrintf(tdsqlite3*,const char*, ...);
+SQLITE_PRIVATE char *tdsqlite3VMPrintf(tdsqlite3*,const char*, va_list);
#if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE)
-SQLITE_PRIVATE void sqlite3DebugPrintf(const char*, ...);
+SQLITE_PRIVATE void tdsqlite3DebugPrintf(const char*, ...);
#endif
#if defined(SQLITE_TEST)
-SQLITE_PRIVATE void *sqlite3TestTextToPtr(const char*);
+SQLITE_PRIVATE void *tdsqlite3TestTextToPtr(const char*);
#endif
#if defined(SQLITE_DEBUG)
-SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView*, const Expr*, u8);
-SQLITE_PRIVATE void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*);
-SQLITE_PRIVATE void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*);
-SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView*, const Select*, u8);
-SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView*, const With*, u8);
-#endif
-
-
-SQLITE_PRIVATE void sqlite3SetString(char **, sqlite3*, const char*);
-SQLITE_PRIVATE void sqlite3ErrorMsg(Parse*, const char*, ...);
-SQLITE_PRIVATE void sqlite3Dequote(char*);
-SQLITE_PRIVATE void sqlite3TokenInit(Token*,char*);
-SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char*, int);
-SQLITE_PRIVATE int sqlite3RunParser(Parse*, const char*, char **);
-SQLITE_PRIVATE void sqlite3FinishCoding(Parse*);
-SQLITE_PRIVATE int sqlite3GetTempReg(Parse*);
-SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse*,int);
-SQLITE_PRIVATE int sqlite3GetTempRange(Parse*,int);
-SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse*,int,int);
-SQLITE_PRIVATE void sqlite3ClearTempRegCache(Parse*);
+SQLITE_PRIVATE void tdsqlite3TreeViewExpr(TreeView*, const Expr*, u8);
+SQLITE_PRIVATE void tdsqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*);
+SQLITE_PRIVATE void tdsqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*);
+SQLITE_PRIVATE void tdsqlite3TreeViewSrcList(TreeView*, const SrcList*);
+SQLITE_PRIVATE void tdsqlite3TreeViewSelect(TreeView*, const Select*, u8);
+SQLITE_PRIVATE void tdsqlite3TreeViewWith(TreeView*, const With*, u8);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+SQLITE_PRIVATE void tdsqlite3TreeViewWindow(TreeView*, const Window*, u8);
+SQLITE_PRIVATE void tdsqlite3TreeViewWinFunc(TreeView*, const Window*, u8);
+#endif
+#endif
+
+
+SQLITE_PRIVATE void tdsqlite3SetString(char **, tdsqlite3*, const char*);
+SQLITE_PRIVATE void tdsqlite3ErrorMsg(Parse*, const char*, ...);
+SQLITE_PRIVATE int tdsqlite3ErrorToParser(tdsqlite3*,int);
+SQLITE_PRIVATE void tdsqlite3Dequote(char*);
+SQLITE_PRIVATE void tdsqlite3DequoteExpr(Expr*);
+SQLITE_PRIVATE void tdsqlite3TokenInit(Token*,char*);
+SQLITE_PRIVATE int tdsqlite3KeywordCode(const unsigned char*, int);
+SQLITE_PRIVATE int tdsqlite3RunParser(Parse*, const char*, char **);
+SQLITE_PRIVATE void tdsqlite3FinishCoding(Parse*);
+SQLITE_PRIVATE int tdsqlite3GetTempReg(Parse*);
+SQLITE_PRIVATE void tdsqlite3ReleaseTempReg(Parse*,int);
+SQLITE_PRIVATE int tdsqlite3GetTempRange(Parse*,int);
+SQLITE_PRIVATE void tdsqlite3ReleaseTempRange(Parse*,int,int);
+SQLITE_PRIVATE void tdsqlite3ClearTempRegCache(Parse*);
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse*,int,int);
-#endif
-SQLITE_PRIVATE Expr *sqlite3ExprAlloc(sqlite3*,int,const Token*,int);
-SQLITE_PRIVATE Expr *sqlite3Expr(sqlite3*,int,const char*);
-SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*);
-SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*, const Token*);
-SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse*, Expr*, Select*);
-SQLITE_PRIVATE Expr *sqlite3ExprAnd(sqlite3*,Expr*, Expr*);
-SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*);
-SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32);
-SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*);
-SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*);
-SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*);
-SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList*,int);
-SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int);
-SQLITE_PRIVATE void sqlite3ExprListSetSpan(Parse*,ExprList*,ExprSpan*);
-SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3*, ExprList*);
-SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList*);
-SQLITE_PRIVATE int sqlite3Init(sqlite3*, char**);
-SQLITE_PRIVATE int sqlite3InitCallback(void*, int, char**, char**);
-SQLITE_PRIVATE void sqlite3Pragma(Parse*,Token*,Token*,Token*,int);
-SQLITE_PRIVATE void sqlite3ResetAllSchemasOfConnection(sqlite3*);
-SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int);
-SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*);
-SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*);
-SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3*,Table*);
-SQLITE_PRIVATE int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**);
-SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation(Parse*,Table*,Select*);
-SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse*,Select*);
-SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *, int);
-SQLITE_PRIVATE Index *sqlite3PrimaryKeyIndex(Table*);
-SQLITE_PRIVATE i16 sqlite3ColumnOfIndex(Index*, i16);
-SQLITE_PRIVATE void sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int);
+SQLITE_PRIVATE int tdsqlite3NoTempsInRange(Parse*,int,int);
+#endif
+SQLITE_PRIVATE Expr *tdsqlite3ExprAlloc(tdsqlite3*,int,const Token*,int);
+SQLITE_PRIVATE Expr *tdsqlite3Expr(tdsqlite3*,int,const char*);
+SQLITE_PRIVATE void tdsqlite3ExprAttachSubtrees(tdsqlite3*,Expr*,Expr*,Expr*);
+SQLITE_PRIVATE Expr *tdsqlite3PExpr(Parse*, int, Expr*, Expr*);
+SQLITE_PRIVATE void tdsqlite3PExprAddSelect(Parse*, Expr*, Select*);
+SQLITE_PRIVATE Expr *tdsqlite3ExprAnd(Parse*,Expr*, Expr*);
+SQLITE_PRIVATE Expr *tdsqlite3ExprSimplifiedAndOr(Expr*);
+SQLITE_PRIVATE Expr *tdsqlite3ExprFunction(Parse*,ExprList*, Token*, int);
+SQLITE_PRIVATE void tdsqlite3ExprFunctionUsable(Parse*,Expr*,FuncDef*);
+SQLITE_PRIVATE void tdsqlite3ExprAssignVarNumber(Parse*, Expr*, u32);
+SQLITE_PRIVATE void tdsqlite3ExprDelete(tdsqlite3*, Expr*);
+SQLITE_PRIVATE void tdsqlite3ExprUnmapAndDelete(Parse*, Expr*);
+SQLITE_PRIVATE ExprList *tdsqlite3ExprListAppend(Parse*,ExprList*,Expr*);
+SQLITE_PRIVATE ExprList *tdsqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*);
+SQLITE_PRIVATE void tdsqlite3ExprListSetSortOrder(ExprList*,int,int);
+SQLITE_PRIVATE void tdsqlite3ExprListSetName(Parse*,ExprList*,Token*,int);
+SQLITE_PRIVATE void tdsqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*);
+SQLITE_PRIVATE void tdsqlite3ExprListDelete(tdsqlite3*, ExprList*);
+SQLITE_PRIVATE u32 tdsqlite3ExprListFlags(const ExprList*);
+SQLITE_PRIVATE int tdsqlite3IndexHasDuplicateRootPage(Index*);
+SQLITE_PRIVATE int tdsqlite3Init(tdsqlite3*, char**);
+SQLITE_PRIVATE int tdsqlite3InitCallback(void*, int, char**, char**);
+SQLITE_PRIVATE int tdsqlite3InitOne(tdsqlite3*, int, char**, u32);
+SQLITE_PRIVATE void tdsqlite3Pragma(Parse*,Token*,Token*,Token*,int);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+SQLITE_PRIVATE Module *tdsqlite3PragmaVtabRegister(tdsqlite3*,const char *zName);
+#endif
+SQLITE_PRIVATE void tdsqlite3ResetAllSchemasOfConnection(tdsqlite3*);
+SQLITE_PRIVATE void tdsqlite3ResetOneSchema(tdsqlite3*,int);
+SQLITE_PRIVATE void tdsqlite3CollapseDatabaseArray(tdsqlite3*);
+SQLITE_PRIVATE void tdsqlite3CommitInternalChanges(tdsqlite3*);
+SQLITE_PRIVATE void tdsqlite3DeleteColumnNames(tdsqlite3*,Table*);
+SQLITE_PRIVATE int tdsqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**);
+SQLITE_PRIVATE void tdsqlite3SelectAddColumnTypeAndCollation(Parse*,Table*,Select*,char);
+SQLITE_PRIVATE Table *tdsqlite3ResultSetOfSelect(Parse*,Select*,char);
+SQLITE_PRIVATE void tdsqlite3OpenMasterTable(Parse *, int);
+SQLITE_PRIVATE Index *tdsqlite3PrimaryKeyIndex(Table*);
+SQLITE_PRIVATE i16 tdsqlite3TableColumnToIndex(Index*, i16);
+#ifdef SQLITE_OMIT_GENERATED_COLUMNS
+# define tdsqlite3TableColumnToStorage(T,X) (X) /* No-op pass-through */
+# define tdsqlite3StorageColumnToTable(T,X) (X) /* No-op pass-through */
+#else
+SQLITE_PRIVATE i16 tdsqlite3TableColumnToStorage(Table*, i16);
+SQLITE_PRIVATE i16 tdsqlite3StorageColumnToTable(Table*, i16);
+#endif
+SQLITE_PRIVATE void tdsqlite3StartTable(Parse*,Token*,Token*,int,int,int,int);
#if SQLITE_ENABLE_HIDDEN_COLUMNS
-SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table*, Column*);
+SQLITE_PRIVATE void tdsqlite3ColumnPropertiesFromName(Table*, Column*);
+#else
+# define tdsqlite3ColumnPropertiesFromName(T,C) /* no-op */
+#endif
+SQLITE_PRIVATE void tdsqlite3AddColumn(Parse*,Token*,Token*);
+SQLITE_PRIVATE void tdsqlite3AddNotNull(Parse*, int);
+SQLITE_PRIVATE void tdsqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int);
+SQLITE_PRIVATE void tdsqlite3AddCheckConstraint(Parse*, Expr*);
+SQLITE_PRIVATE void tdsqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*);
+SQLITE_PRIVATE void tdsqlite3AddCollateType(Parse*, Token*);
+SQLITE_PRIVATE void tdsqlite3AddGenerated(Parse*,Expr*,Token*);
+SQLITE_PRIVATE void tdsqlite3EndTable(Parse*,Token*,Token*,u8,Select*);
+SQLITE_PRIVATE int tdsqlite3ParseUri(const char*,const char*,unsigned int*,
+ tdsqlite3_vfs**,char**,char **);
+#ifdef SQLITE_HAS_CODEC
+SQLITE_PRIVATE int tdsqlite3CodecQueryParameters(tdsqlite3*,const char*,const char*);
#else
-# define sqlite3ColumnPropertiesFromName(T,C) /* no-op */
+# define tdsqlite3CodecQueryParameters(A,B,C) 0
#endif
-SQLITE_PRIVATE void sqlite3AddColumn(Parse*,Token*,Token*);
-SQLITE_PRIVATE void sqlite3AddNotNull(Parse*, int);
-SQLITE_PRIVATE void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int);
-SQLITE_PRIVATE void sqlite3AddCheckConstraint(Parse*, Expr*);
-SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse*,ExprSpan*);
-SQLITE_PRIVATE void sqlite3AddCollateType(Parse*, Token*);
-SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*);
-SQLITE_PRIVATE int sqlite3ParseUri(const char*,const char*,unsigned int*,
- sqlite3_vfs**,char**,char **);
-SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3*,const char*);
+SQLITE_PRIVATE Btree *tdsqlite3DbNameToBtree(tdsqlite3*,const char*);
-#ifdef SQLITE_OMIT_BUILTIN_TEST
-# define sqlite3FaultSim(X) SQLITE_OK
+#ifdef SQLITE_UNTESTABLE
+# define tdsqlite3FaultSim(X) SQLITE_OK
#else
-SQLITE_PRIVATE int sqlite3FaultSim(int);
+SQLITE_PRIVATE int tdsqlite3FaultSim(int);
#endif
-SQLITE_PRIVATE Bitvec *sqlite3BitvecCreate(u32);
-SQLITE_PRIVATE int sqlite3BitvecTest(Bitvec*, u32);
-SQLITE_PRIVATE int sqlite3BitvecTestNotNull(Bitvec*, u32);
-SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec*, u32);
-SQLITE_PRIVATE void sqlite3BitvecClear(Bitvec*, u32, void*);
-SQLITE_PRIVATE void sqlite3BitvecDestroy(Bitvec*);
-SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec*);
-#ifndef SQLITE_OMIT_BUILTIN_TEST
-SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int,int*);
+SQLITE_PRIVATE Bitvec *tdsqlite3BitvecCreate(u32);
+SQLITE_PRIVATE int tdsqlite3BitvecTest(Bitvec*, u32);
+SQLITE_PRIVATE int tdsqlite3BitvecTestNotNull(Bitvec*, u32);
+SQLITE_PRIVATE int tdsqlite3BitvecSet(Bitvec*, u32);
+SQLITE_PRIVATE void tdsqlite3BitvecClear(Bitvec*, u32, void*);
+SQLITE_PRIVATE void tdsqlite3BitvecDestroy(Bitvec*);
+SQLITE_PRIVATE u32 tdsqlite3BitvecSize(Bitvec*);
+#ifndef SQLITE_UNTESTABLE
+SQLITE_PRIVATE int tdsqlite3BitvecBuiltinTest(int,int*);
#endif
-SQLITE_PRIVATE RowSet *sqlite3RowSetInit(sqlite3*, void*, unsigned int);
-SQLITE_PRIVATE void sqlite3RowSetClear(RowSet*);
-SQLITE_PRIVATE void sqlite3RowSetInsert(RowSet*, i64);
-SQLITE_PRIVATE int sqlite3RowSetTest(RowSet*, int iBatch, i64);
-SQLITE_PRIVATE int sqlite3RowSetNext(RowSet*, i64*);
+SQLITE_PRIVATE RowSet *tdsqlite3RowSetInit(tdsqlite3*);
+SQLITE_PRIVATE void tdsqlite3RowSetDelete(void*);
+SQLITE_PRIVATE void tdsqlite3RowSetClear(void*);
+SQLITE_PRIVATE void tdsqlite3RowSetInsert(RowSet*, i64);
+SQLITE_PRIVATE int tdsqlite3RowSetTest(RowSet*, int iBatch, i64);
+SQLITE_PRIVATE int tdsqlite3RowSetNext(RowSet*, i64*);
-SQLITE_PRIVATE void sqlite3CreateView(Parse*,Token*,Token*,Token*,ExprList*,Select*,int,int);
+SQLITE_PRIVATE void tdsqlite3CreateView(Parse*,Token*,Token*,Token*,ExprList*,Select*,int,int);
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE)
-SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse*,Table*);
+SQLITE_PRIVATE int tdsqlite3ViewGetColumnNames(Parse*,Table*);
#else
-# define sqlite3ViewGetColumnNames(A,B) 0
+# define tdsqlite3ViewGetColumnNames(A,B) 0
#endif
#if SQLITE_MAX_ATTACHED>30
-SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask);
+SQLITE_PRIVATE int tdsqlite3DbMaskAllZero(yDbMask);
#endif
-SQLITE_PRIVATE void sqlite3DropTable(Parse*, SrcList*, int, int);
-SQLITE_PRIVATE void sqlite3CodeDropTable(Parse*, Table*, int, int);
-SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3*, Table*);
+SQLITE_PRIVATE void tdsqlite3DropTable(Parse*, SrcList*, int, int);
+SQLITE_PRIVATE void tdsqlite3CodeDropTable(Parse*, Table*, int, int);
+SQLITE_PRIVATE void tdsqlite3DeleteTable(tdsqlite3*, Table*);
+SQLITE_PRIVATE void tdsqlite3FreeIndex(tdsqlite3*, Index*);
#ifndef SQLITE_OMIT_AUTOINCREMENT
-SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse);
-SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse);
-#else
-# define sqlite3AutoincrementBegin(X)
-# define sqlite3AutoincrementEnd(X)
-#endif
-SQLITE_PRIVATE void sqlite3Insert(Parse*, SrcList*, Select*, IdList*, int);
-SQLITE_PRIVATE void *sqlite3ArrayAllocate(sqlite3*,void*,int,int*,int*);
-SQLITE_PRIVATE IdList *sqlite3IdListAppend(sqlite3*, IdList*, Token*);
-SQLITE_PRIVATE int sqlite3IdListIndex(IdList*,const char*);
-SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(sqlite3*, SrcList*, int, int);
-SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(sqlite3*, SrcList*, Token*, Token*);
-SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*,
+SQLITE_PRIVATE void tdsqlite3AutoincrementBegin(Parse *pParse);
+SQLITE_PRIVATE void tdsqlite3AutoincrementEnd(Parse *pParse);
+#else
+# define tdsqlite3AutoincrementBegin(X)
+# define tdsqlite3AutoincrementEnd(X)
+#endif
+SQLITE_PRIVATE void tdsqlite3Insert(Parse*, SrcList*, Select*, IdList*, int, Upsert*);
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+SQLITE_PRIVATE void tdsqlite3ComputeGeneratedColumns(Parse*, int, Table*);
+#endif
+SQLITE_PRIVATE void *tdsqlite3ArrayAllocate(tdsqlite3*,void*,int,int*,int*);
+SQLITE_PRIVATE IdList *tdsqlite3IdListAppend(Parse*, IdList*, Token*);
+SQLITE_PRIVATE int tdsqlite3IdListIndex(IdList*,const char*);
+SQLITE_PRIVATE SrcList *tdsqlite3SrcListEnlarge(Parse*, SrcList*, int, int);
+SQLITE_PRIVATE SrcList *tdsqlite3SrcListAppend(Parse*, SrcList*, Token*, Token*);
+SQLITE_PRIVATE SrcList *tdsqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*,
Token*, Select*, Expr*, IdList*);
-SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *, SrcList *, Token *);
-SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse*, SrcList*, ExprList*);
-SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *, struct SrcList_item *);
-SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList*);
-SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse*, SrcList*);
-SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3*, IdList*);
-SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3*, SrcList*);
-SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(sqlite3*,i16,int,char**);
-SQLITE_PRIVATE void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
+SQLITE_PRIVATE void tdsqlite3SrcListIndexedBy(Parse *, SrcList *, Token *);
+SQLITE_PRIVATE void tdsqlite3SrcListFuncArgs(Parse*, SrcList*, ExprList*);
+SQLITE_PRIVATE int tdsqlite3IndexedByLookup(Parse *, struct SrcList_item *);
+SQLITE_PRIVATE void tdsqlite3SrcListShiftJoinType(SrcList*);
+SQLITE_PRIVATE void tdsqlite3SrcListAssignCursors(Parse*, SrcList*);
+SQLITE_PRIVATE void tdsqlite3IdListDelete(tdsqlite3*, IdList*);
+SQLITE_PRIVATE void tdsqlite3SrcListDelete(tdsqlite3*, SrcList*);
+SQLITE_PRIVATE Index *tdsqlite3AllocateIndexObject(tdsqlite3*,i16,int,char**);
+SQLITE_PRIVATE void tdsqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
Expr*, int, int, u8);
-SQLITE_PRIVATE void sqlite3DropIndex(Parse*, SrcList*, int);
-SQLITE_PRIVATE int sqlite3Select(Parse*, Select*, SelectDest*);
-SQLITE_PRIVATE Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*,
- Expr*,ExprList*,u32,Expr*,Expr*);
-SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3*, Select*);
-SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse*, SrcList*);
-SQLITE_PRIVATE int sqlite3IsReadOnly(Parse*, Table*, int);
-SQLITE_PRIVATE void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int);
+SQLITE_PRIVATE void tdsqlite3DropIndex(Parse*, SrcList*, int);
+SQLITE_PRIVATE int tdsqlite3Select(Parse*, Select*, SelectDest*);
+SQLITE_PRIVATE Select *tdsqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*,
+ Expr*,ExprList*,u32,Expr*);
+SQLITE_PRIVATE void tdsqlite3SelectDelete(tdsqlite3*, Select*);
+SQLITE_PRIVATE void tdsqlite3SelectReset(Parse*, Select*);
+SQLITE_PRIVATE Table *tdsqlite3SrcListLookup(Parse*, SrcList*);
+SQLITE_PRIVATE int tdsqlite3IsReadOnly(Parse*, Table*, int);
+SQLITE_PRIVATE void tdsqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int);
#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY)
-SQLITE_PRIVATE Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,Expr*,char*);
-#endif
-SQLITE_PRIVATE void sqlite3DeleteFrom(Parse*, SrcList*, Expr*);
-SQLITE_PRIVATE void sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int);
-SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int);
-SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo*);
-SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo*);
-SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo*);
-SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo*);
-SQLITE_PRIVATE int sqlite3WhereOrderedInnerLoop(WhereInfo*);
-SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo*);
-SQLITE_PRIVATE int sqlite3WhereContinueLabel(WhereInfo*);
-SQLITE_PRIVATE int sqlite3WhereBreakLabel(WhereInfo*);
-SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo*, int*);
+SQLITE_PRIVATE Expr *tdsqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*);
+#endif
+SQLITE_PRIVATE void tdsqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*);
+SQLITE_PRIVATE void tdsqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*,
+ Upsert*);
+SQLITE_PRIVATE WhereInfo *tdsqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int);
+SQLITE_PRIVATE void tdsqlite3WhereEnd(WhereInfo*);
+SQLITE_PRIVATE LogEst tdsqlite3WhereOutputRowCount(WhereInfo*);
+SQLITE_PRIVATE int tdsqlite3WhereIsDistinct(WhereInfo*);
+SQLITE_PRIVATE int tdsqlite3WhereIsOrdered(WhereInfo*);
+SQLITE_PRIVATE int tdsqlite3WhereOrderByLimitOptLabel(WhereInfo*);
+SQLITE_PRIVATE int tdsqlite3WhereIsSorted(WhereInfo*);
+SQLITE_PRIVATE int tdsqlite3WhereContinueLabel(WhereInfo*);
+SQLITE_PRIVATE int tdsqlite3WhereBreakLabel(WhereInfo*);
+SQLITE_PRIVATE int tdsqlite3WhereOkOnePass(WhereInfo*, int*);
#define ONEPASS_OFF 0 /* Use of ONEPASS not allowed */
#define ONEPASS_SINGLE 1 /* ONEPASS valid for a single row update */
#define ONEPASS_MULTI 2 /* ONEPASS is valid for multiple rows */
-SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn(Parse*, Index*, int, int, int);
-SQLITE_PRIVATE int sqlite3ExprCodeGetColumn(Parse*, Table*, int, int, int, u8);
-SQLITE_PRIVATE void sqlite3ExprCodeGetColumnToReg(Parse*, Table*, int, int, int);
-SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable(Vdbe*, Table*, int, int, int);
-SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse*, int, int, int);
-SQLITE_PRIVATE void sqlite3ExprCacheStore(Parse*, int, int, int);
-SQLITE_PRIVATE void sqlite3ExprCachePush(Parse*);
-SQLITE_PRIVATE void sqlite3ExprCachePop(Parse*);
-SQLITE_PRIVATE void sqlite3ExprCacheRemove(Parse*, int, int);
-SQLITE_PRIVATE void sqlite3ExprCacheClear(Parse*);
-SQLITE_PRIVATE void sqlite3ExprCacheAffinityChange(Parse*, int, int);
-SQLITE_PRIVATE void sqlite3ExprCode(Parse*, Expr*, int);
-SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int);
-SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int);
-SQLITE_PRIVATE void sqlite3ExprCodeAtInit(Parse*, Expr*, int, u8);
-SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*);
-SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int);
-SQLITE_PRIVATE void sqlite3ExprCodeAndCache(Parse*, Expr*, int);
-SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8);
+SQLITE_PRIVATE int tdsqlite3WhereUsesDeferredSeek(WhereInfo*);
+SQLITE_PRIVATE void tdsqlite3ExprCodeLoadIndexColumn(Parse*, Index*, int, int, int);
+SQLITE_PRIVATE int tdsqlite3ExprCodeGetColumn(Parse*, Table*, int, int, int, u8);
+SQLITE_PRIVATE void tdsqlite3ExprCodeGetColumnOfTable(Vdbe*, Table*, int, int, int);
+SQLITE_PRIVATE void tdsqlite3ExprCodeMove(Parse*, int, int, int);
+SQLITE_PRIVATE void tdsqlite3ExprCode(Parse*, Expr*, int);
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+SQLITE_PRIVATE void tdsqlite3ExprCodeGeneratedColumn(Parse*, Column*, int);
+#endif
+SQLITE_PRIVATE void tdsqlite3ExprCodeCopy(Parse*, Expr*, int);
+SQLITE_PRIVATE void tdsqlite3ExprCodeFactorable(Parse*, Expr*, int);
+SQLITE_PRIVATE int tdsqlite3ExprCodeAtInit(Parse*, Expr*, int);
+SQLITE_PRIVATE int tdsqlite3ExprCodeTemp(Parse*, Expr*, int*);
+SQLITE_PRIVATE int tdsqlite3ExprCodeTarget(Parse*, Expr*, int);
+SQLITE_PRIVATE int tdsqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8);
#define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */
#define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */
#define SQLITE_ECEL_REF 0x04 /* Use ExprList.u.x.iOrderByCol */
-SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse*, Expr*, int, int);
-SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse*, Expr*, int, int);
-SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse*, Expr*, int, int);
-SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3*,const char*, const char*);
+#define SQLITE_ECEL_OMITREF 0x08 /* Omit if ExprList.u.x.iOrderByCol */
+SQLITE_PRIVATE void tdsqlite3ExprIfTrue(Parse*, Expr*, int, int);
+SQLITE_PRIVATE void tdsqlite3ExprIfFalse(Parse*, Expr*, int, int);
+SQLITE_PRIVATE void tdsqlite3ExprIfFalseDup(Parse*, Expr*, int, int);
+SQLITE_PRIVATE Table *tdsqlite3FindTable(tdsqlite3*,const char*, const char*);
#define LOCATE_VIEW 0x01
#define LOCATE_NOERR 0x02
-SQLITE_PRIVATE Table *sqlite3LocateTable(Parse*,u32 flags,const char*, const char*);
-SQLITE_PRIVATE Table *sqlite3LocateTableItem(Parse*,u32 flags,struct SrcList_item *);
-SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*);
-SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*);
-SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*);
-SQLITE_PRIVATE void sqlite3Vacuum(Parse*,Token*);
-SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*, int);
-SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, Token*);
-SQLITE_PRIVATE int sqlite3ExprCompare(Expr*, Expr*, int);
-SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList*, ExprList*, int);
-SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Expr*, Expr*, int);
-SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
-SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
-SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx);
-SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr*, SrcList*);
-SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse*);
-#ifndef SQLITE_OMIT_BUILTIN_TEST
-SQLITE_PRIVATE void sqlite3PrngSaveState(void);
-SQLITE_PRIVATE void sqlite3PrngRestoreState(void);
-#endif
-SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3*,int);
-SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse*, int);
-SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse*, const char *zDb);
-SQLITE_PRIVATE void sqlite3BeginTransaction(Parse*, int);
-SQLITE_PRIVATE void sqlite3CommitTransaction(Parse*);
-SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse*);
-SQLITE_PRIVATE void sqlite3Savepoint(Parse*, int, Token*);
-SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *);
-SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3*);
-SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr*);
-SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*);
-SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8);
-SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int);
+SQLITE_PRIVATE Table *tdsqlite3LocateTable(Parse*,u32 flags,const char*, const char*);
+SQLITE_PRIVATE Table *tdsqlite3LocateTableItem(Parse*,u32 flags,struct SrcList_item *);
+SQLITE_PRIVATE Index *tdsqlite3FindIndex(tdsqlite3*,const char*, const char*);
+SQLITE_PRIVATE void tdsqlite3UnlinkAndDeleteTable(tdsqlite3*,int,const char*);
+SQLITE_PRIVATE void tdsqlite3UnlinkAndDeleteIndex(tdsqlite3*,int,const char*);
+SQLITE_PRIVATE void tdsqlite3Vacuum(Parse*,Token*,Expr*);
+SQLITE_PRIVATE int tdsqlite3RunVacuum(char**, tdsqlite3*, int, tdsqlite3_value*);
+SQLITE_PRIVATE char *tdsqlite3NameFromToken(tdsqlite3*, Token*);
+SQLITE_PRIVATE int tdsqlite3ExprCompare(Parse*,Expr*, Expr*, int);
+SQLITE_PRIVATE int tdsqlite3ExprCompareSkip(Expr*, Expr*, int);
+SQLITE_PRIVATE int tdsqlite3ExprListCompare(ExprList*, ExprList*, int);
+SQLITE_PRIVATE int tdsqlite3ExprImpliesExpr(Parse*,Expr*, Expr*, int);
+SQLITE_PRIVATE int tdsqlite3ExprImpliesNonNullRow(Expr*,int);
+SQLITE_PRIVATE void tdsqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
+SQLITE_PRIVATE void tdsqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
+SQLITE_PRIVATE int tdsqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx);
+SQLITE_PRIVATE int tdsqlite3FunctionUsesThisSrc(Expr*, SrcList*);
+SQLITE_PRIVATE Vdbe *tdsqlite3GetVdbe(Parse*);
+#ifndef SQLITE_UNTESTABLE
+SQLITE_PRIVATE void tdsqlite3PrngSaveState(void);
+SQLITE_PRIVATE void tdsqlite3PrngRestoreState(void);
+#endif
+SQLITE_PRIVATE void tdsqlite3RollbackAll(tdsqlite3*,int);
+SQLITE_PRIVATE void tdsqlite3CodeVerifySchema(Parse*, int);
+SQLITE_PRIVATE void tdsqlite3CodeVerifyNamedSchema(Parse*, const char *zDb);
+SQLITE_PRIVATE void tdsqlite3BeginTransaction(Parse*, int);
+SQLITE_PRIVATE void tdsqlite3EndTransaction(Parse*,int);
+SQLITE_PRIVATE void tdsqlite3Savepoint(Parse*, int, Token*);
+SQLITE_PRIVATE void tdsqlite3CloseSavepoints(tdsqlite3 *);
+SQLITE_PRIVATE void tdsqlite3LeaveMutexAndCloseZombie(tdsqlite3*);
+SQLITE_PRIVATE u32 tdsqlite3IsTrueOrFalse(const char*);
+SQLITE_PRIVATE int tdsqlite3ExprIdToTrueFalse(Expr*);
+SQLITE_PRIVATE int tdsqlite3ExprTruthValue(const Expr*);
+SQLITE_PRIVATE int tdsqlite3ExprIsConstant(Expr*);
+SQLITE_PRIVATE int tdsqlite3ExprIsConstantNotJoin(Expr*);
+SQLITE_PRIVATE int tdsqlite3ExprIsConstantOrFunction(Expr*, u8);
+SQLITE_PRIVATE int tdsqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*);
+SQLITE_PRIVATE int tdsqlite3ExprIsTableConstant(Expr*,int);
#ifdef SQLITE_ENABLE_CURSOR_HINTS
-SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*);
+SQLITE_PRIVATE int tdsqlite3ExprContainsSubquery(Expr*);
#endif
-SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*);
-SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*);
-SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
-SQLITE_PRIVATE int sqlite3IsRowid(const char*);
-SQLITE_PRIVATE void sqlite3GenerateRowDelete(
+SQLITE_PRIVATE int tdsqlite3ExprIsInteger(Expr*, int*);
+SQLITE_PRIVATE int tdsqlite3ExprCanBeNull(const Expr*);
+SQLITE_PRIVATE int tdsqlite3ExprNeedsNoAffinityChange(const Expr*, char);
+SQLITE_PRIVATE int tdsqlite3IsRowid(const char*);
+SQLITE_PRIVATE void tdsqlite3GenerateRowDelete(
Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int);
-SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int);
-SQLITE_PRIVATE int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,Index*,int);
-SQLITE_PRIVATE void sqlite3ResolvePartIdxLabel(Parse*,int);
-SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int,
- u8,u8,int,int*,int*);
-SQLITE_PRIVATE void sqlite3CompleteInsertion(Parse*,Table*,int,int,int,int*,int,int,int);
-SQLITE_PRIVATE int sqlite3OpenTableAndIndices(Parse*, Table*, int, u8, int, u8*, int*, int*);
-SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse*, int, int);
-SQLITE_PRIVATE void sqlite3MultiWrite(Parse*);
-SQLITE_PRIVATE void sqlite3MayAbort(Parse*);
-SQLITE_PRIVATE void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8);
-SQLITE_PRIVATE void sqlite3UniqueConstraint(Parse*, int, Index*);
-SQLITE_PRIVATE void sqlite3RowidConstraint(Parse*, int, Table*);
-SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,Expr*,int);
-SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int);
-SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int);
-SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,IdList*);
-SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,Select*,int);
-#if SELECTTRACE_ENABLED
-SQLITE_PRIVATE void sqlite3SelectSetName(Select*,const char*);
+SQLITE_PRIVATE void tdsqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int);
+SQLITE_PRIVATE int tdsqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,Index*,int);
+SQLITE_PRIVATE void tdsqlite3ResolvePartIdxLabel(Parse*,int);
+SQLITE_PRIVATE int tdsqlite3ExprReferencesUpdatedColumn(Expr*,int*,int);
+SQLITE_PRIVATE void tdsqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int,
+ u8,u8,int,int*,int*,Upsert*);
+#ifdef SQLITE_ENABLE_NULL_TRIM
+SQLITE_PRIVATE void tdsqlite3SetMakeRecordP5(Vdbe*,Table*);
#else
-# define sqlite3SelectSetName(A,B)
-#endif
-SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(FuncDef*,int);
-SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,u8,u8);
-SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void);
-SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void);
-SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*);
-SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*);
-SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*);
-SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int);
+# define tdsqlite3SetMakeRecordP5(A,B)
+#endif
+SQLITE_PRIVATE void tdsqlite3CompleteInsertion(Parse*,Table*,int,int,int,int*,int,int,int);
+SQLITE_PRIVATE int tdsqlite3OpenTableAndIndices(Parse*, Table*, int, u8, int, u8*, int*, int*);
+SQLITE_PRIVATE void tdsqlite3BeginWriteOperation(Parse*, int, int);
+SQLITE_PRIVATE void tdsqlite3MultiWrite(Parse*);
+SQLITE_PRIVATE void tdsqlite3MayAbort(Parse*);
+SQLITE_PRIVATE void tdsqlite3HaltConstraint(Parse*, int, int, char*, i8, u8);
+SQLITE_PRIVATE void tdsqlite3UniqueConstraint(Parse*, int, Index*);
+SQLITE_PRIVATE void tdsqlite3RowidConstraint(Parse*, int, Table*);
+SQLITE_PRIVATE Expr *tdsqlite3ExprDup(tdsqlite3*,Expr*,int);
+SQLITE_PRIVATE ExprList *tdsqlite3ExprListDup(tdsqlite3*,ExprList*,int);
+SQLITE_PRIVATE SrcList *tdsqlite3SrcListDup(tdsqlite3*,SrcList*,int);
+SQLITE_PRIVATE IdList *tdsqlite3IdListDup(tdsqlite3*,IdList*);
+SQLITE_PRIVATE Select *tdsqlite3SelectDup(tdsqlite3*,Select*,int);
+SQLITE_PRIVATE FuncDef *tdsqlite3FunctionSearch(int,const char*);
+SQLITE_PRIVATE void tdsqlite3InsertBuiltinFuncs(FuncDef*,int);
+SQLITE_PRIVATE FuncDef *tdsqlite3FindFunction(tdsqlite3*,const char*,int,u8,u8);
+SQLITE_PRIVATE void tdsqlite3RegisterBuiltinFunctions(void);
+SQLITE_PRIVATE void tdsqlite3RegisterDateTimeFunctions(void);
+SQLITE_PRIVATE void tdsqlite3RegisterPerConnectionBuiltinFunctions(tdsqlite3*);
+SQLITE_PRIVATE int tdsqlite3SafetyCheckOk(tdsqlite3*);
+SQLITE_PRIVATE int tdsqlite3SafetyCheckSickOrOk(tdsqlite3*);
+SQLITE_PRIVATE void tdsqlite3ChangeCookie(Parse*, int);
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, int);
+SQLITE_PRIVATE void tdsqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int);
#endif
#ifndef SQLITE_OMIT_TRIGGER
-SQLITE_PRIVATE void sqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*,
+SQLITE_PRIVATE void tdsqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*,
Expr*,int, int);
-SQLITE_PRIVATE void sqlite3FinishTrigger(Parse*, TriggerStep*, Token*);
-SQLITE_PRIVATE void sqlite3DropTrigger(Parse*, SrcList*, int);
-SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse*, Trigger*);
-SQLITE_PRIVATE Trigger *sqlite3TriggersExist(Parse *, Table*, int, ExprList*, int *pMask);
-SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *, Table *);
-SQLITE_PRIVATE void sqlite3CodeRowTrigger(Parse*, Trigger *, int, ExprList*, int, Table *,
+SQLITE_PRIVATE void tdsqlite3FinishTrigger(Parse*, TriggerStep*, Token*);
+SQLITE_PRIVATE void tdsqlite3DropTrigger(Parse*, SrcList*, int);
+SQLITE_PRIVATE void tdsqlite3DropTriggerPtr(Parse*, Trigger*);
+SQLITE_PRIVATE Trigger *tdsqlite3TriggersExist(Parse *, Table*, int, ExprList*, int *pMask);
+SQLITE_PRIVATE Trigger *tdsqlite3TriggerList(Parse *, Table *);
+SQLITE_PRIVATE void tdsqlite3CodeRowTrigger(Parse*, Trigger *, int, ExprList*, int, Table *,
int, int, int);
-SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int);
+SQLITE_PRIVATE void tdsqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, int, int, int);
void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*);
-SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*);
-SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*);
-SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(sqlite3*,Token*, IdList*,
- Select*,u8);
-SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(sqlite3*,Token*,ExprList*, Expr*, u8);
-SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*);
-SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3*, Trigger*);
-SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*);
-SQLITE_PRIVATE u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int);
-# define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p))
-# define sqlite3IsToplevel(p) ((p)->pToplevel==0)
-#else
-# define sqlite3TriggersExist(B,C,D,E,F) 0
-# define sqlite3DeleteTrigger(A,B)
-# define sqlite3DropTriggerPtr(A,B)
-# define sqlite3UnlinkAndDeleteTrigger(A,B,C)
-# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I)
-# define sqlite3CodeRowTriggerDirect(A,B,C,D,E,F)
-# define sqlite3TriggerList(X, Y) 0
-# define sqlite3ParseToplevel(p) p
-# define sqlite3IsToplevel(p) 1
-# define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0
-#endif
-
-SQLITE_PRIVATE int sqlite3JoinType(Parse*, Token*, Token*, Token*);
-SQLITE_PRIVATE void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int);
-SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse*, int);
+SQLITE_PRIVATE void tdsqlite3DeleteTriggerStep(tdsqlite3*, TriggerStep*);
+SQLITE_PRIVATE TriggerStep *tdsqlite3TriggerSelectStep(tdsqlite3*,Select*,
+ const char*,const char*);
+SQLITE_PRIVATE TriggerStep *tdsqlite3TriggerInsertStep(Parse*,Token*, IdList*,
+ Select*,u8,Upsert*,
+ const char*,const char*);
+SQLITE_PRIVATE TriggerStep *tdsqlite3TriggerUpdateStep(Parse*,Token*,ExprList*, Expr*, u8,
+ const char*,const char*);
+SQLITE_PRIVATE TriggerStep *tdsqlite3TriggerDeleteStep(Parse*,Token*, Expr*,
+ const char*,const char*);
+SQLITE_PRIVATE void tdsqlite3DeleteTrigger(tdsqlite3*, Trigger*);
+SQLITE_PRIVATE void tdsqlite3UnlinkAndDeleteTrigger(tdsqlite3*,int,const char*);
+SQLITE_PRIVATE u32 tdsqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int);
+# define tdsqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p))
+# define tdsqlite3IsToplevel(p) ((p)->pToplevel==0)
+#else
+# define tdsqlite3TriggersExist(B,C,D,E,F) 0
+# define tdsqlite3DeleteTrigger(A,B)
+# define tdsqlite3DropTriggerPtr(A,B)
+# define tdsqlite3UnlinkAndDeleteTrigger(A,B,C)
+# define tdsqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I)
+# define tdsqlite3CodeRowTriggerDirect(A,B,C,D,E,F)
+# define tdsqlite3TriggerList(X, Y) 0
+# define tdsqlite3ParseToplevel(p) p
+# define tdsqlite3IsToplevel(p) 1
+# define tdsqlite3TriggerColmask(A,B,C,D,E,F,G) 0
+#endif
+
+SQLITE_PRIVATE int tdsqlite3JoinType(Parse*, Token*, Token*, Token*);
+SQLITE_PRIVATE void tdsqlite3SetJoinExpr(Expr*,int);
+SQLITE_PRIVATE void tdsqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int);
+SQLITE_PRIVATE void tdsqlite3DeferForeignKey(Parse*, int);
#ifndef SQLITE_OMIT_AUTHORIZATION
-SQLITE_PRIVATE void sqlite3AuthRead(Parse*,Expr*,Schema*,SrcList*);
-SQLITE_PRIVATE int sqlite3AuthCheck(Parse*,int, const char*, const char*, const char*);
-SQLITE_PRIVATE void sqlite3AuthContextPush(Parse*, AuthContext*, const char*);
-SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext*);
-SQLITE_PRIVATE int sqlite3AuthReadCol(Parse*, const char *, const char *, int);
-#else
-# define sqlite3AuthRead(a,b,c,d)
-# define sqlite3AuthCheck(a,b,c,d,e) SQLITE_OK
-# define sqlite3AuthContextPush(a,b,c)
-# define sqlite3AuthContextPop(a) ((void)(a))
-#endif
-SQLITE_PRIVATE void sqlite3Attach(Parse*, Expr*, Expr*, Expr*);
-SQLITE_PRIVATE void sqlite3Detach(Parse*, Expr*);
-SQLITE_PRIVATE void sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*);
-SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*);
-SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*);
-SQLITE_PRIVATE int sqlite3FixExpr(DbFixer*, Expr*);
-SQLITE_PRIVATE int sqlite3FixExprList(DbFixer*, ExprList*);
-SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*);
-SQLITE_PRIVATE int sqlite3AtoF(const char *z, double*, int, u8);
-SQLITE_PRIVATE int sqlite3GetInt32(const char *, int*);
-SQLITE_PRIVATE int sqlite3Atoi(const char*);
-SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *pData, int nChar);
-SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *pData, int nByte);
-SQLITE_PRIVATE u32 sqlite3Utf8Read(const u8**);
-SQLITE_PRIVATE LogEst sqlite3LogEst(u64);
-SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst,LogEst);
+SQLITE_PRIVATE void tdsqlite3AuthRead(Parse*,Expr*,Schema*,SrcList*);
+SQLITE_PRIVATE int tdsqlite3AuthCheck(Parse*,int, const char*, const char*, const char*);
+SQLITE_PRIVATE void tdsqlite3AuthContextPush(Parse*, AuthContext*, const char*);
+SQLITE_PRIVATE void tdsqlite3AuthContextPop(AuthContext*);
+SQLITE_PRIVATE int tdsqlite3AuthReadCol(Parse*, const char *, const char *, int);
+#else
+# define tdsqlite3AuthRead(a,b,c,d)
+# define tdsqlite3AuthCheck(a,b,c,d,e) SQLITE_OK
+# define tdsqlite3AuthContextPush(a,b,c)
+# define tdsqlite3AuthContextPop(a) ((void)(a))
+#endif
+SQLITE_PRIVATE void tdsqlite3Attach(Parse*, Expr*, Expr*, Expr*);
+SQLITE_PRIVATE void tdsqlite3Detach(Parse*, Expr*);
+SQLITE_PRIVATE void tdsqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*);
+SQLITE_PRIVATE int tdsqlite3FixSrcList(DbFixer*, SrcList*);
+SQLITE_PRIVATE int tdsqlite3FixSelect(DbFixer*, Select*);
+SQLITE_PRIVATE int tdsqlite3FixExpr(DbFixer*, Expr*);
+SQLITE_PRIVATE int tdsqlite3FixExprList(DbFixer*, ExprList*);
+SQLITE_PRIVATE int tdsqlite3FixTriggerStep(DbFixer*, TriggerStep*);
+SQLITE_PRIVATE int tdsqlite3RealSameAsInt(double,tdsqlite3_int64);
+SQLITE_PRIVATE int tdsqlite3AtoF(const char *z, double*, int, u8);
+SQLITE_PRIVATE int tdsqlite3GetInt32(const char *, int*);
+SQLITE_PRIVATE int tdsqlite3Atoi(const char*);
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_PRIVATE int tdsqlite3Utf16ByteLen(const void *pData, int nChar);
+#endif
+SQLITE_PRIVATE int tdsqlite3Utf8CharLen(const char *pData, int nByte);
+SQLITE_PRIVATE u32 tdsqlite3Utf8Read(const u8**);
+SQLITE_PRIVATE LogEst tdsqlite3LogEst(u64);
+SQLITE_PRIVATE LogEst tdsqlite3LogEstAdd(LogEst,LogEst);
#ifndef SQLITE_OMIT_VIRTUALTABLE
-SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double);
+SQLITE_PRIVATE LogEst tdsqlite3LogEstFromDouble(double);
#endif
#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \
- defined(SQLITE_ENABLE_STAT3_OR_STAT4) || \
+ defined(SQLITE_ENABLE_STAT4) || \
defined(SQLITE_EXPLAIN_ESTIMATED_ROWS)
-SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst);
+SQLITE_PRIVATE u64 tdsqlite3LogEstToInt(LogEst);
#endif
+SQLITE_PRIVATE VList *tdsqlite3VListAdd(tdsqlite3*,VList*,const char*,int,int);
+SQLITE_PRIVATE const char *tdsqlite3VListNumToName(VList*,int);
+SQLITE_PRIVATE int tdsqlite3VListNameToNum(VList*,const char*,int);
/*
** Routines to read and write variable-length integers. These used to
** be defined locally, but now we use the varint routines in the util.c
** file.
*/
-SQLITE_PRIVATE int sqlite3PutVarint(unsigned char*, u64);
-SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *, u64 *);
-SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *, u32 *);
-SQLITE_PRIVATE int sqlite3VarintLen(u64 v);
+SQLITE_PRIVATE int tdsqlite3PutVarint(unsigned char*, u64);
+SQLITE_PRIVATE u8 tdsqlite3GetVarint(const unsigned char *, u64 *);
+SQLITE_PRIVATE u8 tdsqlite3GetVarint32(const unsigned char *, u32 *);
+SQLITE_PRIVATE int tdsqlite3VarintLen(u64 v);
/*
** The common case is for a varint to be a single byte. They following
@@ -16518,242 +19716,304 @@ SQLITE_PRIVATE int sqlite3VarintLen(u64 v);
** the procedure for larger varints.
*/
#define getVarint32(A,B) \
- (u8)((*(A)<(u8)0x80)?((B)=(u32)*(A)),1:sqlite3GetVarint32((A),(u32 *)&(B)))
+ (u8)((*(A)<(u8)0x80)?((B)=(u32)*(A)),1:tdsqlite3GetVarint32((A),(u32 *)&(B)))
#define putVarint32(A,B) \
(u8)(((u32)(B)<(u32)0x80)?(*(A)=(unsigned char)(B)),1:\
- sqlite3PutVarint((A),(B)))
-#define getVarint sqlite3GetVarint
-#define putVarint sqlite3PutVarint
-
-
-SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*);
-SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int);
-SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2);
-SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity);
-SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int);
-SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr);
-SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8);
-SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*);
-SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...);
-SQLITE_PRIVATE void sqlite3Error(sqlite3*,int);
-SQLITE_PRIVATE void sqlite3SystemError(sqlite3*,int);
-SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3*, const char *z, int n);
-SQLITE_PRIVATE u8 sqlite3HexToInt(int h);
-SQLITE_PRIVATE int sqlite3TwoPartName(Parse *, Token *, Token *, Token **);
+ tdsqlite3PutVarint((A),(B)))
+#define getVarint tdsqlite3GetVarint
+#define putVarint tdsqlite3PutVarint
+
+
+SQLITE_PRIVATE const char *tdsqlite3IndexAffinityStr(tdsqlite3*, Index*);
+SQLITE_PRIVATE void tdsqlite3TableAffinity(Vdbe*, Table*, int);
+SQLITE_PRIVATE char tdsqlite3CompareAffinity(Expr *pExpr, char aff2);
+SQLITE_PRIVATE int tdsqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity);
+SQLITE_PRIVATE char tdsqlite3TableColumnAffinity(Table*,int);
+SQLITE_PRIVATE char tdsqlite3ExprAffinity(Expr *pExpr);
+SQLITE_PRIVATE int tdsqlite3Atoi64(const char*, i64*, int, u8);
+SQLITE_PRIVATE int tdsqlite3DecOrHexToI64(const char*, i64*);
+SQLITE_PRIVATE void tdsqlite3ErrorWithMsg(tdsqlite3*, int, const char*,...);
+SQLITE_PRIVATE void tdsqlite3Error(tdsqlite3*,int);
+SQLITE_PRIVATE void tdsqlite3SystemError(tdsqlite3*,int);
+SQLITE_PRIVATE void *tdsqlite3HexToBlob(tdsqlite3*, const char *z, int n);
+SQLITE_PRIVATE u8 tdsqlite3HexToInt(int h);
+SQLITE_PRIVATE int tdsqlite3TwoPartName(Parse *, Token *, Token *, Token **);
#if defined(SQLITE_NEED_ERR_NAME)
-SQLITE_PRIVATE const char *sqlite3ErrName(int);
-#endif
-
-SQLITE_PRIVATE const char *sqlite3ErrStr(int);
-SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse);
-SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int);
-SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName);
-SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr);
-SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int);
-SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*);
-SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*);
-SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *);
-SQLITE_PRIVATE int sqlite3CheckObjectName(Parse *, const char *);
-SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *, int);
-SQLITE_PRIVATE int sqlite3AddInt64(i64*,i64);
-SQLITE_PRIVATE int sqlite3SubInt64(i64*,i64);
-SQLITE_PRIVATE int sqlite3MulInt64(i64*,i64);
-SQLITE_PRIVATE int sqlite3AbsInt32(int);
+SQLITE_PRIVATE const char *tdsqlite3ErrName(int);
+#endif
+
+#ifdef SQLITE_ENABLE_DESERIALIZE
+SQLITE_PRIVATE int tdsqlite3MemdbInit(void);
+#endif
+
+SQLITE_PRIVATE const char *tdsqlite3ErrStr(int);
+SQLITE_PRIVATE int tdsqlite3ReadSchema(Parse *pParse);
+SQLITE_PRIVATE CollSeq *tdsqlite3FindCollSeq(tdsqlite3*,u8 enc, const char*,int);
+SQLITE_PRIVATE int tdsqlite3IsBinary(const CollSeq*);
+SQLITE_PRIVATE CollSeq *tdsqlite3LocateCollSeq(Parse *pParse, const char*zName);
+SQLITE_PRIVATE CollSeq *tdsqlite3ExprCollSeq(Parse *pParse, Expr *pExpr);
+SQLITE_PRIVATE CollSeq *tdsqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr);
+SQLITE_PRIVATE int tdsqlite3ExprCollSeqMatch(Parse*,Expr*,Expr*);
+SQLITE_PRIVATE Expr *tdsqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int);
+SQLITE_PRIVATE Expr *tdsqlite3ExprAddCollateString(Parse*,Expr*,const char*);
+SQLITE_PRIVATE Expr *tdsqlite3ExprSkipCollate(Expr*);
+SQLITE_PRIVATE Expr *tdsqlite3ExprSkipCollateAndLikely(Expr*);
+SQLITE_PRIVATE int tdsqlite3CheckCollSeq(Parse *, CollSeq *);
+SQLITE_PRIVATE int tdsqlite3WritableSchema(tdsqlite3*);
+SQLITE_PRIVATE int tdsqlite3CheckObjectName(Parse*, const char*,const char*,const char*);
+SQLITE_PRIVATE void tdsqlite3VdbeSetChanges(tdsqlite3 *, int);
+SQLITE_PRIVATE int tdsqlite3AddInt64(i64*,i64);
+SQLITE_PRIVATE int tdsqlite3SubInt64(i64*,i64);
+SQLITE_PRIVATE int tdsqlite3MulInt64(i64*,i64);
+SQLITE_PRIVATE int tdsqlite3AbsInt32(int);
#ifdef SQLITE_ENABLE_8_3_NAMES
-SQLITE_PRIVATE void sqlite3FileSuffix3(const char*, char*);
+SQLITE_PRIVATE void tdsqlite3FileSuffix3(const char*, char*);
#else
-# define sqlite3FileSuffix3(X,Y)
+# define tdsqlite3FileSuffix3(X,Y)
#endif
-SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z,u8);
+SQLITE_PRIVATE u8 tdsqlite3GetBoolean(const char *z,u8);
-SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value*, u8);
-SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value*, u8);
-SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8,
+SQLITE_PRIVATE const void *tdsqlite3ValueText(tdsqlite3_value*, u8);
+SQLITE_PRIVATE int tdsqlite3ValueBytes(tdsqlite3_value*, u8);
+SQLITE_PRIVATE void tdsqlite3ValueSetStr(tdsqlite3_value*, int, const void *,u8,
void(*)(void*));
-SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value*);
-SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*);
-SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *);
-SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8);
-SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **);
-SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8);
+SQLITE_PRIVATE void tdsqlite3ValueSetNull(tdsqlite3_value*);
+SQLITE_PRIVATE void tdsqlite3ValueFree(tdsqlite3_value*);
+#ifndef SQLITE_UNTESTABLE
+SQLITE_PRIVATE void tdsqlite3ResultIntReal(tdsqlite3_context*);
+#endif
+SQLITE_PRIVATE tdsqlite3_value *tdsqlite3ValueNew(tdsqlite3 *);
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_PRIVATE char *tdsqlite3Utf16to8(tdsqlite3 *, const void*, int, u8);
+#endif
+SQLITE_PRIVATE int tdsqlite3ValueFromExpr(tdsqlite3 *, Expr *, u8, u8, tdsqlite3_value **);
+SQLITE_PRIVATE void tdsqlite3ValueApplyAffinity(tdsqlite3_value *, u8, u8);
#ifndef SQLITE_AMALGAMATION
-SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[];
-SQLITE_PRIVATE const char sqlite3StrBINARY[];
-SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[];
-SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[];
-SQLITE_PRIVATE const Token sqlite3IntTokens[];
-SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config;
-SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions;
+SQLITE_PRIVATE const unsigned char tdsqlite3OpcodeProperty[];
+SQLITE_PRIVATE const char tdsqlite3StrBINARY[];
+SQLITE_PRIVATE const unsigned char tdsqlite3UpperToLower[];
+SQLITE_PRIVATE const unsigned char tdsqlite3CtypeMap[];
+SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config tdsqlite3Config;
+SQLITE_PRIVATE FuncDefHash tdsqlite3BuiltinFunctions;
#ifndef SQLITE_OMIT_WSD
-SQLITE_PRIVATE int sqlite3PendingByte;
-#endif
-#endif
-SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3*, int, int, int);
-SQLITE_PRIVATE void sqlite3Reindex(Parse*, Token*, Token*);
-SQLITE_PRIVATE void sqlite3AlterFunctions(void);
-SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
-SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *, int *);
-SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...);
-SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*);
-SQLITE_PRIVATE int sqlite3CodeSubselect(Parse*, Expr *, int, int);
-SQLITE_PRIVATE void sqlite3SelectPrep(Parse*, Select*, NameContext*);
-SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p);
-SQLITE_PRIVATE int sqlite3MatchSpanName(const char*, const char*, const char*, const char*);
-SQLITE_PRIVATE int sqlite3ResolveExprNames(NameContext*, Expr*);
-SQLITE_PRIVATE int sqlite3ResolveExprListNames(NameContext*, ExprList*);
-SQLITE_PRIVATE void sqlite3ResolveSelectNames(Parse*, Select*, NameContext*);
-SQLITE_PRIVATE void sqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*);
-SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*);
-SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int, int);
-SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *);
-SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *);
-SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*);
-SQLITE_PRIVATE char sqlite3AffinityType(const char*, u8*);
-SQLITE_PRIVATE void sqlite3Analyze(Parse*, Token*, Token*);
-SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler*);
-SQLITE_PRIVATE int sqlite3FindDb(sqlite3*, Token*);
-SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *, const char *);
-SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3*,int iDB);
-SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3*,Index*);
-SQLITE_PRIVATE void sqlite3DefaultRowEst(Index*);
-SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3*, int);
-SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3*,Expr*,int*,char*);
-SQLITE_PRIVATE void sqlite3SchemaClear(void *);
-SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *, Btree *);
-SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *);
-SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3*,int,int);
-SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo*);
-SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoRef(KeyInfo*);
-SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse*, Index*);
-#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3KeyInfoIsWriteable(KeyInfo*);
+SQLITE_PRIVATE int tdsqlite3PendingByte;
+#endif
#endif
-SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *,
- void (*)(sqlite3_context*,int,sqlite3_value **),
- void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*),
+#ifdef VDBE_PROFILE
+SQLITE_PRIVATE tdsqlite3_uint64 tdsqlite3NProfileCnt;
+#endif
+SQLITE_PRIVATE void tdsqlite3RootPageMoved(tdsqlite3*, int, int, int);
+SQLITE_PRIVATE void tdsqlite3Reindex(Parse*, Token*, Token*);
+SQLITE_PRIVATE void tdsqlite3AlterFunctions(void);
+SQLITE_PRIVATE void tdsqlite3AlterRenameTable(Parse*, SrcList*, Token*);
+SQLITE_PRIVATE void tdsqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*);
+SQLITE_PRIVATE int tdsqlite3GetToken(const unsigned char *, int *);
+SQLITE_PRIVATE void tdsqlite3NestedParse(Parse*, const char*, ...);
+SQLITE_PRIVATE void tdsqlite3ExpirePreparedStatements(tdsqlite3*, int);
+SQLITE_PRIVATE void tdsqlite3CodeRhsOfIN(Parse*, Expr*, int);
+SQLITE_PRIVATE int tdsqlite3CodeSubselect(Parse*, Expr*);
+SQLITE_PRIVATE void tdsqlite3SelectPrep(Parse*, Select*, NameContext*);
+SQLITE_PRIVATE void tdsqlite3SelectWrongNumTermsError(Parse *pParse, Select *p);
+SQLITE_PRIVATE int tdsqlite3MatchEName(
+ const struct ExprList_item*,
+ const char*,
+ const char*,
+ const char*
+);
+SQLITE_PRIVATE int tdsqlite3ResolveExprNames(NameContext*, Expr*);
+SQLITE_PRIVATE int tdsqlite3ResolveExprListNames(NameContext*, ExprList*);
+SQLITE_PRIVATE void tdsqlite3ResolveSelectNames(Parse*, Select*, NameContext*);
+SQLITE_PRIVATE int tdsqlite3ResolveSelfReference(Parse*,Table*,int,Expr*,ExprList*);
+SQLITE_PRIVATE int tdsqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const char*);
+SQLITE_PRIVATE void tdsqlite3ColumnDefault(Vdbe *, Table *, int, int);
+SQLITE_PRIVATE void tdsqlite3AlterFinishAddColumn(Parse *, Token *);
+SQLITE_PRIVATE void tdsqlite3AlterBeginAddColumn(Parse *, SrcList *);
+SQLITE_PRIVATE void *tdsqlite3RenameTokenMap(Parse*, void*, Token*);
+SQLITE_PRIVATE void tdsqlite3RenameTokenRemap(Parse*, void *pTo, void *pFrom);
+SQLITE_PRIVATE void tdsqlite3RenameExprUnmap(Parse*, Expr*);
+SQLITE_PRIVATE void tdsqlite3RenameExprlistUnmap(Parse*, ExprList*);
+SQLITE_PRIVATE CollSeq *tdsqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*);
+SQLITE_PRIVATE char tdsqlite3AffinityType(const char*, Column*);
+SQLITE_PRIVATE void tdsqlite3Analyze(Parse*, Token*, Token*);
+SQLITE_PRIVATE int tdsqlite3InvokeBusyHandler(BusyHandler*, tdsqlite3_file*);
+SQLITE_PRIVATE int tdsqlite3FindDb(tdsqlite3*, Token*);
+SQLITE_PRIVATE int tdsqlite3FindDbName(tdsqlite3 *, const char *);
+SQLITE_PRIVATE int tdsqlite3AnalysisLoad(tdsqlite3*,int iDB);
+SQLITE_PRIVATE void tdsqlite3DeleteIndexSamples(tdsqlite3*,Index*);
+SQLITE_PRIVATE void tdsqlite3DefaultRowEst(Index*);
+SQLITE_PRIVATE void tdsqlite3RegisterLikeFunctions(tdsqlite3*, int);
+SQLITE_PRIVATE int tdsqlite3IsLikeFunction(tdsqlite3*,Expr*,int*,char*);
+SQLITE_PRIVATE void tdsqlite3SchemaClear(void *);
+SQLITE_PRIVATE Schema *tdsqlite3SchemaGet(tdsqlite3 *, Btree *);
+SQLITE_PRIVATE int tdsqlite3SchemaToIndex(tdsqlite3 *db, Schema *);
+SQLITE_PRIVATE KeyInfo *tdsqlite3KeyInfoAlloc(tdsqlite3*,int,int);
+SQLITE_PRIVATE void tdsqlite3KeyInfoUnref(KeyInfo*);
+SQLITE_PRIVATE KeyInfo *tdsqlite3KeyInfoRef(KeyInfo*);
+SQLITE_PRIVATE KeyInfo *tdsqlite3KeyInfoOfIndex(Parse*, Index*);
+SQLITE_PRIVATE KeyInfo *tdsqlite3KeyInfoFromExprList(Parse*, ExprList*, int, int);
+SQLITE_PRIVATE int tdsqlite3HasExplicitNulls(Parse*, ExprList*);
+
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int tdsqlite3KeyInfoIsWriteable(KeyInfo*);
+#endif
+SQLITE_PRIVATE int tdsqlite3CreateFunc(tdsqlite3 *, const char *, int, int, void *,
+ void (*)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*)(tdsqlite3_context*),
+ void (*)(tdsqlite3_context*),
+ void (*)(tdsqlite3_context*,int,tdsqlite3_value **),
FuncDestructor *pDestructor
);
-SQLITE_PRIVATE void sqlite3OomFault(sqlite3*);
-SQLITE_PRIVATE void sqlite3OomClear(sqlite3*);
-SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int);
-SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *);
-
-SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int);
-SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum*,const char*,int);
-SQLITE_PRIVATE void sqlite3StrAccumAppendAll(StrAccum*,const char*);
-SQLITE_PRIVATE void sqlite3AppendChar(StrAccum*,int,char);
-SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*);
-SQLITE_PRIVATE void sqlite3StrAccumReset(StrAccum*);
-SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int);
-SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int);
-
-SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *);
-SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *);
+SQLITE_PRIVATE void tdsqlite3NoopDestructor(void*);
+SQLITE_PRIVATE void tdsqlite3OomFault(tdsqlite3*);
+SQLITE_PRIVATE void tdsqlite3OomClear(tdsqlite3*);
+SQLITE_PRIVATE int tdsqlite3ApiExit(tdsqlite3 *db, int);
+SQLITE_PRIVATE int tdsqlite3OpenTempDatabase(Parse *);
+
+SQLITE_PRIVATE void tdsqlite3StrAccumInit(StrAccum*, tdsqlite3*, char*, int, int);
+SQLITE_PRIVATE char *tdsqlite3StrAccumFinish(StrAccum*);
+SQLITE_PRIVATE void tdsqlite3SelectDestInit(SelectDest*,int,int);
+SQLITE_PRIVATE Expr *tdsqlite3CreateColumnExpr(tdsqlite3 *, SrcList *, int, int);
+
+SQLITE_PRIVATE void tdsqlite3BackupRestart(tdsqlite3_backup *);
+SQLITE_PRIVATE void tdsqlite3BackupUpdate(tdsqlite3_backup *, Pgno, const u8 *);
#ifndef SQLITE_OMIT_SUBQUERY
-SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse*, Expr*);
+SQLITE_PRIVATE int tdsqlite3ExprCheckIN(Parse*, Expr*);
#else
-# define sqlite3ExprCheckIN(x,y) SQLITE_OK
+# define tdsqlite3ExprCheckIN(x,y) SQLITE_OK
#endif
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
-SQLITE_PRIVATE void sqlite3AnalyzeFunctions(void);
-SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue(
+#ifdef SQLITE_ENABLE_STAT4
+SQLITE_PRIVATE int tdsqlite3Stat4ProbeSetValue(
Parse*,Index*,UnpackedRecord**,Expr*,int,int,int*);
-SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr(Parse*, Expr*, u8, sqlite3_value**);
-SQLITE_PRIVATE void sqlite3Stat4ProbeFree(UnpackedRecord*);
-SQLITE_PRIVATE int sqlite3Stat4Column(sqlite3*, const void*, int, int, sqlite3_value**);
-SQLITE_PRIVATE char sqlite3IndexColumnAffinity(sqlite3*, Index*, int);
+SQLITE_PRIVATE int tdsqlite3Stat4ValueFromExpr(Parse*, Expr*, u8, tdsqlite3_value**);
+SQLITE_PRIVATE void tdsqlite3Stat4ProbeFree(UnpackedRecord*);
+SQLITE_PRIVATE int tdsqlite3Stat4Column(tdsqlite3*, const void*, int, int, tdsqlite3_value**);
+SQLITE_PRIVATE char tdsqlite3IndexColumnAffinity(tdsqlite3*, Index*, int);
#endif
/*
** The interface to the LEMON-generated parser
*/
-SQLITE_PRIVATE void *sqlite3ParserAlloc(void*(*)(u64));
-SQLITE_PRIVATE void sqlite3ParserFree(void*, void(*)(void*));
-SQLITE_PRIVATE void sqlite3Parser(void*, int, Token, Parse*);
+#ifndef SQLITE_AMALGAMATION
+SQLITE_PRIVATE void *tdsqlite3ParserAlloc(void*(*)(u64), Parse*);
+SQLITE_PRIVATE void tdsqlite3ParserFree(void*, void(*)(void*));
+#endif
+SQLITE_PRIVATE void tdsqlite3Parser(void*, int, Token);
+SQLITE_PRIVATE int tdsqlite3ParserFallback(int);
#ifdef YYTRACKMAXSTACKDEPTH
-SQLITE_PRIVATE int sqlite3ParserStackPeak(void*);
+SQLITE_PRIVATE int tdsqlite3ParserStackPeak(void*);
#endif
-SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3*);
+SQLITE_PRIVATE void tdsqlite3AutoLoadExtensions(tdsqlite3*);
#ifndef SQLITE_OMIT_LOAD_EXTENSION
-SQLITE_PRIVATE void sqlite3CloseExtensions(sqlite3*);
+SQLITE_PRIVATE void tdsqlite3CloseExtensions(tdsqlite3*);
#else
-# define sqlite3CloseExtensions(X)
+# define tdsqlite3CloseExtensions(X)
#endif
#ifndef SQLITE_OMIT_SHARED_CACHE
-SQLITE_PRIVATE void sqlite3TableLock(Parse *, int, int, u8, const char *);
+SQLITE_PRIVATE void tdsqlite3TableLock(Parse *, int, int, u8, const char *);
#else
- #define sqlite3TableLock(v,w,x,y,z)
+ #define tdsqlite3TableLock(v,w,x,y,z)
#endif
#ifdef SQLITE_TEST
-SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*);
+SQLITE_PRIVATE int tdsqlite3Utf8To8(unsigned char*);
#endif
#ifdef SQLITE_OMIT_VIRTUALTABLE
-# define sqlite3VtabClear(Y)
-# define sqlite3VtabSync(X,Y) SQLITE_OK
-# define sqlite3VtabRollback(X)
-# define sqlite3VtabCommit(X)
-# define sqlite3VtabInSync(db) 0
-# define sqlite3VtabLock(X)
-# define sqlite3VtabUnlock(X)
-# define sqlite3VtabUnlockList(X)
-# define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK
-# define sqlite3GetVTable(X,Y) ((VTable*)0)
-#else
-SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table*);
-SQLITE_PRIVATE void sqlite3VtabDisconnect(sqlite3 *db, Table *p);
-SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, Vdbe*);
-SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db);
-SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db);
-SQLITE_PRIVATE void sqlite3VtabLock(VTable *);
-SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *);
-SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3*);
-SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *, int, int);
-SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe*, sqlite3_vtab*);
-SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3*, Table*);
-# define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0)
-#endif
-SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse*,Module*);
-SQLITE_PRIVATE void sqlite3VtabEponymousTableClear(sqlite3*,Module*);
-SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse*,Table*);
-SQLITE_PRIVATE void sqlite3VtabBeginParse(Parse*, Token*, Token*, Token*, int);
-SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse*, Token*);
-SQLITE_PRIVATE void sqlite3VtabArgInit(Parse*);
-SQLITE_PRIVATE void sqlite3VtabArgExtend(Parse*, Token*);
-SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3*, int, const char *, char **);
-SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse*, Table*);
-SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3*, int, const char *);
-SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *, VTable *);
-SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*);
-SQLITE_PRIVATE void sqlite3InvalidFunction(sqlite3_context*,int,sqlite3_value**);
-SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*);
-SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe*, const char*, int);
-SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *);
-SQLITE_PRIVATE void sqlite3ParserReset(Parse*);
-SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*);
-SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*);
-SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *);
-SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*);
-SQLITE_PRIVATE const char *sqlite3JournalModename(int);
+# define tdsqlite3VtabClear(Y)
+# define tdsqlite3VtabSync(X,Y) SQLITE_OK
+# define tdsqlite3VtabRollback(X)
+# define tdsqlite3VtabCommit(X)
+# define tdsqlite3VtabInSync(db) 0
+# define tdsqlite3VtabLock(X)
+# define tdsqlite3VtabUnlock(X)
+# define tdsqlite3VtabModuleUnref(D,X)
+# define tdsqlite3VtabUnlockList(X)
+# define tdsqlite3VtabSavepoint(X, Y, Z) SQLITE_OK
+# define tdsqlite3GetVTable(X,Y) ((VTable*)0)
+#else
+SQLITE_PRIVATE void tdsqlite3VtabClear(tdsqlite3 *db, Table*);
+SQLITE_PRIVATE void tdsqlite3VtabDisconnect(tdsqlite3 *db, Table *p);
+SQLITE_PRIVATE int tdsqlite3VtabSync(tdsqlite3 *db, Vdbe*);
+SQLITE_PRIVATE int tdsqlite3VtabRollback(tdsqlite3 *db);
+SQLITE_PRIVATE int tdsqlite3VtabCommit(tdsqlite3 *db);
+SQLITE_PRIVATE void tdsqlite3VtabLock(VTable *);
+SQLITE_PRIVATE void tdsqlite3VtabUnlock(VTable *);
+SQLITE_PRIVATE void tdsqlite3VtabModuleUnref(tdsqlite3*,Module*);
+SQLITE_PRIVATE void tdsqlite3VtabUnlockList(tdsqlite3*);
+SQLITE_PRIVATE int tdsqlite3VtabSavepoint(tdsqlite3 *, int, int);
+SQLITE_PRIVATE void tdsqlite3VtabImportErrmsg(Vdbe*, tdsqlite3_vtab*);
+SQLITE_PRIVATE VTable *tdsqlite3GetVTable(tdsqlite3*, Table*);
+SQLITE_PRIVATE Module *tdsqlite3VtabCreateModule(
+ tdsqlite3*,
+ const char*,
+ const tdsqlite3_module*,
+ void*,
+ void(*)(void*)
+ );
+# define tdsqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0)
+#endif
+SQLITE_PRIVATE int tdsqlite3ReadOnlyShadowTables(tdsqlite3 *db);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+SQLITE_PRIVATE int tdsqlite3ShadowTableName(tdsqlite3 *db, const char *zName);
+#else
+# define tdsqlite3ShadowTableName(A,B) 0
+#endif
+SQLITE_PRIVATE int tdsqlite3VtabEponymousTableInit(Parse*,Module*);
+SQLITE_PRIVATE void tdsqlite3VtabEponymousTableClear(tdsqlite3*,Module*);
+SQLITE_PRIVATE void tdsqlite3VtabMakeWritable(Parse*,Table*);
+SQLITE_PRIVATE void tdsqlite3VtabBeginParse(Parse*, Token*, Token*, Token*, int);
+SQLITE_PRIVATE void tdsqlite3VtabFinishParse(Parse*, Token*);
+SQLITE_PRIVATE void tdsqlite3VtabArgInit(Parse*);
+SQLITE_PRIVATE void tdsqlite3VtabArgExtend(Parse*, Token*);
+SQLITE_PRIVATE int tdsqlite3VtabCallCreate(tdsqlite3*, int, const char *, char **);
+SQLITE_PRIVATE int tdsqlite3VtabCallConnect(Parse*, Table*);
+SQLITE_PRIVATE int tdsqlite3VtabCallDestroy(tdsqlite3*, int, const char *);
+SQLITE_PRIVATE int tdsqlite3VtabBegin(tdsqlite3 *, VTable *);
+SQLITE_PRIVATE FuncDef *tdsqlite3VtabOverloadFunction(tdsqlite3 *,FuncDef*, int nArg, Expr*);
+SQLITE_PRIVATE tdsqlite3_int64 tdsqlite3StmtCurrentTime(tdsqlite3_context*);
+SQLITE_PRIVATE int tdsqlite3VdbeParameterIndex(Vdbe*, const char*, int);
+SQLITE_PRIVATE int tdsqlite3TransferBindings(tdsqlite3_stmt *, tdsqlite3_stmt *);
+SQLITE_PRIVATE void tdsqlite3ParserReset(Parse*);
+#ifdef SQLITE_ENABLE_NORMALIZE
+SQLITE_PRIVATE char *tdsqlite3Normalize(Vdbe*, const char*);
+#endif
+SQLITE_PRIVATE int tdsqlite3Reprepare(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3ExprListCheckLength(Parse*, ExprList*, const char*);
+SQLITE_PRIVATE CollSeq *tdsqlite3ExprCompareCollSeq(Parse*,Expr*);
+SQLITE_PRIVATE CollSeq *tdsqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *);
+SQLITE_PRIVATE int tdsqlite3TempInMemory(const tdsqlite3*);
+SQLITE_PRIVATE const char *tdsqlite3JournalModename(int);
#ifndef SQLITE_OMIT_WAL
-SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*);
-SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int);
+SQLITE_PRIVATE int tdsqlite3Checkpoint(tdsqlite3*, int, int, int*, int*);
+SQLITE_PRIVATE int tdsqlite3WalDefaultHook(void*,tdsqlite3*,const char*,int);
#endif
#ifndef SQLITE_OMIT_CTE
-SQLITE_PRIVATE With *sqlite3WithAdd(Parse*,With*,Token*,ExprList*,Select*);
-SQLITE_PRIVATE void sqlite3WithDelete(sqlite3*,With*);
-SQLITE_PRIVATE void sqlite3WithPush(Parse*, With*, u8);
+SQLITE_PRIVATE With *tdsqlite3WithAdd(Parse*,With*,Token*,ExprList*,Select*);
+SQLITE_PRIVATE void tdsqlite3WithDelete(tdsqlite3*,With*);
+SQLITE_PRIVATE void tdsqlite3WithPush(Parse*, With*, u8);
+#else
+#define tdsqlite3WithPush(x,y,z)
+#define tdsqlite3WithDelete(x,y)
+#endif
+#ifndef SQLITE_OMIT_UPSERT
+SQLITE_PRIVATE Upsert *tdsqlite3UpsertNew(tdsqlite3*,ExprList*,Expr*,ExprList*,Expr*);
+SQLITE_PRIVATE void tdsqlite3UpsertDelete(tdsqlite3*,Upsert*);
+SQLITE_PRIVATE Upsert *tdsqlite3UpsertDup(tdsqlite3*,Upsert*);
+SQLITE_PRIVATE int tdsqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*);
+SQLITE_PRIVATE void tdsqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int);
#else
-#define sqlite3WithPush(x,y,z)
-#define sqlite3WithDelete(x,y)
+#define tdsqlite3UpsertNew(v,w,x,y,z) ((Upsert*)0)
+#define tdsqlite3UpsertDelete(x,y)
+#define tdsqlite3UpsertDup(x,y) ((Upsert*)0)
#endif
+
/* Declarations for functions in fkey.c. All of these are replaced by
** no-op macros if OMIT_FOREIGN_KEY is defined. In this case no foreign
** key functionality is available. If OMIT_TRIGGER is defined but
@@ -16762,25 +20022,26 @@ SQLITE_PRIVATE void sqlite3WithPush(Parse*, With*, u8);
** provided (enforcement of FK constraints requires the triggers sub-system).
*/
#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
-SQLITE_PRIVATE void sqlite3FkCheck(Parse*, Table*, int, int, int*, int);
-SQLITE_PRIVATE void sqlite3FkDropTable(Parse*, SrcList *, Table*);
-SQLITE_PRIVATE void sqlite3FkActions(Parse*, Table*, ExprList*, int, int*, int);
-SQLITE_PRIVATE int sqlite3FkRequired(Parse*, Table*, int*, int);
-SQLITE_PRIVATE u32 sqlite3FkOldmask(Parse*, Table*);
-SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *);
-#else
- #define sqlite3FkActions(a,b,c,d,e,f)
- #define sqlite3FkCheck(a,b,c,d,e,f)
- #define sqlite3FkDropTable(a,b,c)
- #define sqlite3FkOldmask(a,b) 0
- #define sqlite3FkRequired(a,b,c,d) 0
+SQLITE_PRIVATE void tdsqlite3FkCheck(Parse*, Table*, int, int, int*, int);
+SQLITE_PRIVATE void tdsqlite3FkDropTable(Parse*, SrcList *, Table*);
+SQLITE_PRIVATE void tdsqlite3FkActions(Parse*, Table*, ExprList*, int, int*, int);
+SQLITE_PRIVATE int tdsqlite3FkRequired(Parse*, Table*, int*, int);
+SQLITE_PRIVATE u32 tdsqlite3FkOldmask(Parse*, Table*);
+SQLITE_PRIVATE FKey *tdsqlite3FkReferences(Table *);
+#else
+ #define tdsqlite3FkActions(a,b,c,d,e,f)
+ #define tdsqlite3FkCheck(a,b,c,d,e,f)
+ #define tdsqlite3FkDropTable(a,b,c)
+ #define tdsqlite3FkOldmask(a,b) 0
+ #define tdsqlite3FkRequired(a,b,c,d) 0
+ #define tdsqlite3FkReferences(a) 0
#endif
#ifndef SQLITE_OMIT_FOREIGN_KEY
-SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *, Table*);
-SQLITE_PRIVATE int sqlite3FkLocateIndex(Parse*,Table*,FKey*,Index**,int**);
+SQLITE_PRIVATE void tdsqlite3FkDelete(tdsqlite3 *, Table*);
+SQLITE_PRIVATE int tdsqlite3FkLocateIndex(Parse*,Table*,FKey*,Index**,int**);
#else
- #define sqlite3FkDelete(a,b)
- #define sqlite3FkLocateIndex(a,b,c,d,e)
+ #define tdsqlite3FkDelete(a,b)
+ #define tdsqlite3FkLocateIndex(a,b,c,d,e)
#endif
@@ -16792,19 +20053,19 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex(Parse*,Table*,FKey*,Index**,int**);
/*
** The interface to the code in fault.c used for identifying "benign"
-** malloc failures. This is only present if SQLITE_OMIT_BUILTIN_TEST
+** malloc failures. This is only present if SQLITE_UNTESTABLE
** is not defined.
*/
-#ifndef SQLITE_OMIT_BUILTIN_TEST
-SQLITE_PRIVATE void sqlite3BeginBenignMalloc(void);
-SQLITE_PRIVATE void sqlite3EndBenignMalloc(void);
+#ifndef SQLITE_UNTESTABLE
+SQLITE_PRIVATE void tdsqlite3BeginBenignMalloc(void);
+SQLITE_PRIVATE void tdsqlite3EndBenignMalloc(void);
#else
- #define sqlite3BeginBenignMalloc()
- #define sqlite3EndBenignMalloc()
+ #define tdsqlite3BeginBenignMalloc()
+ #define tdsqlite3EndBenignMalloc()
#endif
/*
-** Allowed return values from sqlite3FindInIndex()
+** Allowed return values from tdsqlite3FindInIndex()
*/
#define IN_INDEX_ROWID 1 /* Search the rowid of the table */
#define IN_INDEX_EPH 2 /* Search an ephemeral b-tree */
@@ -16812,60 +20073,64 @@ SQLITE_PRIVATE void sqlite3EndBenignMalloc(void);
#define IN_INDEX_INDEX_DESC 4 /* Existing index DESCENDING */
#define IN_INDEX_NOOP 5 /* No table available. Use comparisons */
/*
-** Allowed flags for the 3rd parameter to sqlite3FindInIndex().
+** Allowed flags for the 3rd parameter to tdsqlite3FindInIndex().
*/
#define IN_INDEX_NOOP_OK 0x0001 /* OK to return IN_INDEX_NOOP */
#define IN_INDEX_MEMBERSHIP 0x0002 /* IN operator used for membership test */
#define IN_INDEX_LOOP 0x0004 /* IN operator used as a loop */
-SQLITE_PRIVATE int sqlite3FindInIndex(Parse *, Expr *, u32, int*, int*);
+SQLITE_PRIVATE int tdsqlite3FindInIndex(Parse *, Expr *, u32, int*, int*, int*);
-SQLITE_PRIVATE int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int);
-SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *);
-#ifdef SQLITE_ENABLE_ATOMIC_WRITE
-SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *);
+SQLITE_PRIVATE int tdsqlite3JournalOpen(tdsqlite3_vfs *, const char *, tdsqlite3_file *, int, int);
+SQLITE_PRIVATE int tdsqlite3JournalSize(tdsqlite3_vfs *);
+#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \
+ || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE)
+SQLITE_PRIVATE int tdsqlite3JournalCreate(tdsqlite3_file *);
#endif
-SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p);
-SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *);
+SQLITE_PRIVATE int tdsqlite3JournalIsInMemory(tdsqlite3_file *p);
+SQLITE_PRIVATE void tdsqlite3MemJournalOpen(tdsqlite3_file *);
-SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p);
+SQLITE_PRIVATE void tdsqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p);
#if SQLITE_MAX_EXPR_DEPTH>0
-SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *);
-SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse*, int);
+SQLITE_PRIVATE int tdsqlite3SelectExprHeight(Select *);
+SQLITE_PRIVATE int tdsqlite3ExprCheckHeight(Parse*, int);
#else
- #define sqlite3SelectExprHeight(x) 0
- #define sqlite3ExprCheckHeight(x,y)
+ #define tdsqlite3SelectExprHeight(x) 0
+ #define tdsqlite3ExprCheckHeight(x,y)
#endif
-SQLITE_PRIVATE u32 sqlite3Get4byte(const u8*);
-SQLITE_PRIVATE void sqlite3Put4byte(u8*, u32);
+SQLITE_PRIVATE u32 tdsqlite3Get4byte(const u8*);
+SQLITE_PRIVATE void tdsqlite3Put4byte(u8*, u32);
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
-SQLITE_PRIVATE void sqlite3ConnectionBlocked(sqlite3 *, sqlite3 *);
-SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db);
-SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db);
+SQLITE_PRIVATE void tdsqlite3ConnectionBlocked(tdsqlite3 *, tdsqlite3 *);
+SQLITE_PRIVATE void tdsqlite3ConnectionUnlocked(tdsqlite3 *db);
+SQLITE_PRIVATE void tdsqlite3ConnectionClosed(tdsqlite3 *db);
#else
- #define sqlite3ConnectionBlocked(x,y)
- #define sqlite3ConnectionUnlocked(x)
- #define sqlite3ConnectionClosed(x)
+ #define tdsqlite3ConnectionBlocked(x,y)
+ #define tdsqlite3ConnectionUnlocked(x)
+ #define tdsqlite3ConnectionClosed(x)
#endif
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE void sqlite3ParserTrace(FILE*, char *);
+SQLITE_PRIVATE void tdsqlite3ParserTrace(FILE*, char *);
+#endif
+#if defined(YYCOVERAGE)
+SQLITE_PRIVATE int tdsqlite3ParserCoverage(FILE*);
#endif
/*
** If the SQLITE_ENABLE IOTRACE exists then the global variable
-** sqlite3IoTrace is a pointer to a printf-like routine used to
+** tdsqlite3IoTrace is a pointer to a printf-like routine used to
** print I/O tracing messages.
*/
#ifdef SQLITE_ENABLE_IOTRACE
-# define IOTRACE(A) if( sqlite3IoTrace ){ sqlite3IoTrace A; }
-SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe*);
-SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *sqlite3IoTrace)(const char*,...);
+# define IOTRACE(A) if( tdsqlite3IoTrace ){ tdsqlite3IoTrace A; }
+SQLITE_PRIVATE void tdsqlite3VdbeIOTraceSql(Vdbe*);
+SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *tdsqlite3IoTrace)(const char*,...);
#else
# define IOTRACE(A)
-# define sqlite3VdbeIOTraceSql(X)
+# define tdsqlite3VdbeIOTraceSql(X)
#endif
/*
@@ -16873,16 +20138,16 @@ SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *sqlite3IoTrace)(const char*,...);
** only. They are used to verify that different "types" of memory
** allocations are properly tracked by the system.
**
-** sqlite3MemdebugSetType() sets the "type" of an allocation to one of
+** tdsqlite3MemdebugSetType() sets the "type" of an allocation to one of
** the MEMTYPE_* macros defined below. The type must be a bitmask with
** a single bit set.
**
-** sqlite3MemdebugHasType() returns true if any of the bits in its second
-** argument match the type set by the previous sqlite3MemdebugSetType().
-** sqlite3MemdebugHasType() is intended for use inside assert() statements.
+** tdsqlite3MemdebugHasType() returns true if any of the bits in its second
+** argument match the type set by the previous tdsqlite3MemdebugSetType().
+** tdsqlite3MemdebugHasType() is intended for use inside assert() statements.
**
-** sqlite3MemdebugNoType() returns true if none of the bits in its second
-** argument match the type set by the previous sqlite3MemdebugSetType().
+** tdsqlite3MemdebugNoType() returns true if none of the bits in its second
+** argument match the type set by the previous tdsqlite3MemdebugSetType().
**
** Perhaps the most important point is the difference between MEMTYPE_HEAP
** and MEMTYPE_LOOKASIDE. If an allocation is MEMTYPE_LOOKASIDE, that means
@@ -16897,35 +20162,42 @@ SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *sqlite3IoTrace)(const char*,...);
** play when the SQLITE_MEMDEBUG compile-time option is used.
*/
#ifdef SQLITE_MEMDEBUG
-SQLITE_PRIVATE void sqlite3MemdebugSetType(void*,u8);
-SQLITE_PRIVATE int sqlite3MemdebugHasType(void*,u8);
-SQLITE_PRIVATE int sqlite3MemdebugNoType(void*,u8);
+SQLITE_PRIVATE void tdsqlite3MemdebugSetType(void*,u8);
+SQLITE_PRIVATE int tdsqlite3MemdebugHasType(void*,u8);
+SQLITE_PRIVATE int tdsqlite3MemdebugNoType(void*,u8);
#else
-# define sqlite3MemdebugSetType(X,Y) /* no-op */
-# define sqlite3MemdebugHasType(X,Y) 1
-# define sqlite3MemdebugNoType(X,Y) 1
+# define tdsqlite3MemdebugSetType(X,Y) /* no-op */
+# define tdsqlite3MemdebugHasType(X,Y) 1
+# define tdsqlite3MemdebugNoType(X,Y) 1
#endif
#define MEMTYPE_HEAP 0x01 /* General heap allocations */
#define MEMTYPE_LOOKASIDE 0x02 /* Heap that might have been lookaside */
-#define MEMTYPE_SCRATCH 0x04 /* Scratch allocations */
-#define MEMTYPE_PCACHE 0x08 /* Page cache allocations */
+#define MEMTYPE_PCACHE 0x04 /* Page cache allocations */
/*
** Threading interface
*/
#if SQLITE_MAX_WORKER_THREADS>0
-SQLITE_PRIVATE int sqlite3ThreadCreate(SQLiteThread**,void*(*)(void*),void*);
-SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread*, void**);
+SQLITE_PRIVATE int tdsqlite3ThreadCreate(SQLiteThread**,void*(*)(void*),void*);
+SQLITE_PRIVATE int tdsqlite3ThreadJoin(SQLiteThread*, void**);
#endif
+#if defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)
+SQLITE_PRIVATE int tdsqlite3DbpageRegister(tdsqlite3*);
+#endif
#if defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)
-SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3*);
+SQLITE_PRIVATE int tdsqlite3DbstatRegister(tdsqlite3*);
#endif
-SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr);
-SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr);
-SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr*, int);
-SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int);
+SQLITE_PRIVATE int tdsqlite3ExprVectorSize(Expr *pExpr);
+SQLITE_PRIVATE int tdsqlite3ExprIsVector(Expr *pExpr);
+SQLITE_PRIVATE Expr *tdsqlite3VectorFieldSubexpr(Expr*, int);
+SQLITE_PRIVATE Expr *tdsqlite3ExprForVectorField(Parse*,Expr*,int);
+SQLITE_PRIVATE void tdsqlite3VectorErrorMsg(Parse*, Expr*);
+
+#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
+SQLITE_PRIVATE const char **tdsqlite3CompileOptions(int *pnOpt);
+#endif
#endif /* SQLITEINT_H */
@@ -16965,8 +20237,153 @@ SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int);
#ifdef SQLITE_HAS_CODEC
/* #include <assert.h> */
+/************** Include sqlcipher.h in the middle of crypto.c ****************/
+/************** Begin file sqlcipher.h ***************************************/
+/*
+** SQLCipher
+** sqlcipher.h developed by Stephen Lombardo (Zetetic LLC)
+** sjlombardo at zetetic dot net
+** http://zetetic.net
+**
+** Copyright (c) 2008, ZETETIC LLC
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of the ZETETIC LLC nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
+** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
+** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+*/
+/* BEGIN SQLCIPHER */
+#ifdef SQLITE_HAS_CODEC
+#ifndef SQLCIPHER_H
+#define SQLCIPHER_H
+
+#define SQLCIPHER_HMAC_SHA1 0
+#define SQLCIPHER_HMAC_SHA1_LABEL "HMAC_SHA1"
+#define SQLCIPHER_HMAC_SHA256 1
+#define SQLCIPHER_HMAC_SHA256_LABEL "HMAC_SHA256"
+#define SQLCIPHER_HMAC_SHA512 2
+#define SQLCIPHER_HMAC_SHA512_LABEL "HMAC_SHA512"
+
+
+#define SQLCIPHER_PBKDF2_HMAC_SHA1 0
+#define SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL "PBKDF2_HMAC_SHA1"
+#define SQLCIPHER_PBKDF2_HMAC_SHA256 1
+#define SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL "PBKDF2_HMAC_SHA256"
+#define SQLCIPHER_PBKDF2_HMAC_SHA512 2
+#define SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL "PBKDF2_HMAC_SHA512"
+
+
+typedef struct {
+ int (*activate)(void *ctx);
+ int (*deactivate)(void *ctx);
+ const char* (*get_provider_name)(void *ctx);
+ int (*add_random)(void *ctx, void *buffer, int length);
+ int (*random)(void *ctx, void *buffer, int length);
+ int (*hmac)(void *ctx, int algorithm, unsigned char *hmac_key, int key_sz, unsigned char *in, int in_sz, unsigned char *in2, int in2_sz, unsigned char *out);
+ int (*kdf)(void *ctx, int algorithm, const unsigned char *pass, int pass_sz, unsigned char* salt, int salt_sz, int workfactor, int key_sz, unsigned char *key);
+ int (*cipher)(void *ctx, int mode, unsigned char *key, int key_sz, unsigned char *iv, unsigned char *in, int in_sz, unsigned char *out);
+ const char* (*get_cipher)(void *ctx);
+ int (*get_key_sz)(void *ctx);
+ int (*get_iv_sz)(void *ctx);
+ int (*get_block_sz)(void *ctx);
+ int (*get_hmac_sz)(void *ctx, int algorithm);
+ int (*ctx_init)(void **ctx);
+ int (*ctx_free)(void **ctx);
+ int (*fips_status)(void *ctx);
+ const char* (*get_provider_version)(void *ctx);
+} sqlcipher_provider;
+
+/* utility functions */
+SQLITE_PRIVATE void* sqlcipher_malloc(u64);
+SQLITE_PRIVATE void sqlcipher_mlock(void *, u64);
+SQLITE_PRIVATE void sqlcipher_munlock(void *, u64);
+SQLITE_PRIVATE void* sqlcipher_memset(void *, unsigned char, u64);
+SQLITE_PRIVATE int sqlcipher_ismemset(const void *, unsigned char, u64);
+SQLITE_PRIVATE int sqlcipher_memcmp(const void *, const void *, int);
+SQLITE_PRIVATE void sqlcipher_free(void *, u64);
+SQLITE_PRIVATE char* sqlcipher_version();
+
+/* provider interfaces */
+SQLITE_PRIVATE int sqlcipher_register_provider(sqlcipher_provider *);
+SQLITE_PRIVATE sqlcipher_provider* sqlcipher_get_provider(void);
+
+#define SQLCIPHER_MUTEX_PROVIDER 0
+#define SQLCIPHER_MUTEX_PROVIDER_ACTIVATE 1
+#define SQLCIPHER_MUTEX_PROVIDER_RAND 2
+#define SQLCIPHER_MUTEX_RESERVED1 3
+#define SQLCIPHER_MUTEX_RESERVED2 4
+#define SQLCIPHER_MUTEX_RESERVED3 5
+#define SQLCIPHER_MUTEX_COUNT 6
+
+SQLITE_PRIVATE tdsqlite3_mutex* sqlcipher_mutex(int);
+
+#endif
+#endif
+/* END SQLCIPHER */
+
+
+/************** End of sqlcipher.h *******************************************/
+/************** Continuing where we left off in crypto.c *********************/
+/************** Include crypto.h in the middle of crypto.c *******************/
+/************** Begin file crypto.h ******************************************/
+/*
+** SQLCipher
+** crypto.h developed by Stephen Lombardo (Zetetic LLC)
+** sjlombardo at zetetic dot net
+** http://zetetic.net
+**
+** Copyright (c) 2008, ZETETIC LLC
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+** * Neither the name of the ZETETIC LLC nor the
+** names of its contributors may be used to endorse or promote products
+** derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
+** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
+** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+*/
+/* BEGIN SQLCIPHER */
+#ifdef SQLITE_HAS_CODEC
+#ifndef CRYPTO_H
+#define CRYPTO_H
+
/* #include "sqliteInt.h" */
-/************** Include btreeInt.h in the middle of crypto.c *****************/
+/************** Include btreeInt.h in the middle of crypto.h *****************/
/************** Begin file btreeInt.h ****************************************/
/*
** 2004 April 6
@@ -17229,37 +20646,39 @@ typedef struct CellInfo CellInfo;
#define PTF_LEAF 0x08
/*
-** As each page of the file is loaded into memory, an instance of the following
-** structure is appended and initialized to zero. This structure stores
-** information about the page that is decoded from the raw file page.
+** An instance of this object stores information about each a single database
+** page that has been loaded into memory. The information in this object
+** is derived from the raw on-disk page content.
**
-** The pParent field points back to the parent page. This allows us to
-** walk up the BTree from any leaf to the root. Care must be taken to
-** unref() the parent page pointer when this page is no longer referenced.
-** The pageDestructor() routine handles that chore.
+** As each database page is loaded into memory, the pager allocats an
+** instance of this object and zeros the first 8 bytes. (This is the
+** "extra" information associated with each page of the pager.)
**
** Access to all fields of this structure is controlled by the mutex
** stored in MemPage.pBt->mutex.
*/
struct MemPage {
u8 isInit; /* True if previously initialized. MUST BE FIRST! */
- u8 nOverflow; /* Number of overflow cell bodies in aCell[] */
+ u8 bBusy; /* Prevent endless loops on corrupt database files */
u8 intKey; /* True if table b-trees. False for index b-trees */
u8 intKeyLeaf; /* True if the leaf of an intKey table */
+ Pgno pgno; /* Page number for this page */
+ /* Only the first 8 bytes (above) are zeroed by pager.c when a new page
+ ** is allocated. All fields that follow must be initialized before use */
u8 leaf; /* True if a leaf page */
u8 hdrOffset; /* 100 for page 1. 0 otherwise */
u8 childPtrSize; /* 0 if leaf==1. 4 if leaf==0 */
u8 max1bytePayload; /* min(maxLocal,127) */
- u8 bBusy; /* Prevent endless loops on corrupt database files */
+ u8 nOverflow; /* Number of overflow cell bodies in aCell[] */
u16 maxLocal; /* Copy of BtShared.maxLocal or BtShared.maxLeaf */
u16 minLocal; /* Copy of BtShared.minLocal or BtShared.minLeaf */
u16 cellOffset; /* Index in aData of first cell pointer */
- u16 nFree; /* Number of free bytes on the page */
+ int nFree; /* Number of free bytes on the page. -1 for unknown */
u16 nCell; /* Number of cells on this page, local and ovfl */
u16 maskPage; /* Mask for page offset */
- u16 aiOvfl[5]; /* Insert the i-th overflow cell before the aiOvfl-th
+ u16 aiOvfl[4]; /* Insert the i-th overflow cell before the aiOvfl-th
** non-overflow cell */
- u8 *apOvfl[5]; /* Pointers to the body of overflow cells */
+ u8 *apOvfl[4]; /* Pointers to the body of overflow cells */
BtShared *pBt; /* Pointer to BtShared that this page is part of */
u8 *aData; /* Pointer to disk image of the page data */
u8 *aDataEnd; /* One byte past the end of usable data */
@@ -17268,17 +20687,9 @@ struct MemPage {
DbPage *pDbPage; /* Pager page handle */
u16 (*xCellSize)(MemPage*,u8*); /* cellSizePtr method */
void (*xParseCell)(MemPage*,u8*,CellInfo*); /* btreeParseCell method */
- Pgno pgno; /* Page number for this page */
};
/*
-** The in-memory image of a disk page has the auxiliary information appended
-** to the end. EXTRA_SIZE is the number of bytes of space needed to hold
-** that extra information.
-*/
-#define EXTRA_SIZE sizeof(MemPage)
-
-/*
** A linked list of the following structures is stored at BtShared.pLock.
** Locks are added (or upgraded from READ_LOCK to WRITE_LOCK) when a cursor
** is opened on the table with root page BtShared.iTable. Locks are removed
@@ -17318,13 +20729,13 @@ struct BtLock {
** they often do so without holding sqlite3.mutex.
*/
struct Btree {
- sqlite3 *db; /* The database connection holding this btree */
+ tdsqlite3 *db; /* The database connection holding this btree */
BtShared *pBt; /* Sharable content of this btree */
u8 inTrans; /* TRANS_NONE, TRANS_READ or TRANS_WRITE */
u8 sharable; /* True if we can share pBt with another db */
u8 locked; /* True if db currently has pBt locked */
u8 hasIncrblobCur; /* True if there are one or more Incrblob cursors */
- int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */
+ int wantToLock; /* Number of nested calls to tdsqlite3BtreeEnter() */
int nBackup; /* Number of backup operations reading this btree */
u32 iDataVersion; /* Combines with pBt->pPager->iDataVersion */
Btree *pNext; /* List of other sharable Btrees from the same db */
@@ -17382,10 +20793,10 @@ struct Btree {
*/
struct BtShared {
Pager *pPager; /* The page cache */
- sqlite3 *db; /* Database connection currently using this Btree */
+ tdsqlite3 *db; /* Database connection currently using this Btree */
BtCursor *pCursor; /* A list of all open cursors */
MemPage *pPage1; /* First page of the database */
- u8 openFlags; /* Flags to sqlite3BtreeOpen() */
+ u8 openFlags; /* Flags to tdsqlite3BtreeOpen() */
#ifndef SQLITE_OMIT_AUTOVACUUM
u8 autoVacuum; /* True if auto-vacuum is enabled */
u8 incrVacuum; /* True if incr-vacuum is enabled */
@@ -17405,9 +20816,9 @@ struct BtShared {
u32 usableSize; /* Number of usable bytes on each page */
int nTransaction; /* Number of open transactions (read + write) */
u32 nPage; /* Number of pages in the database */
- void *pSchema; /* Pointer to space allocated by sqlite3BtreeSchema() */
+ void *pSchema; /* Pointer to space allocated by tdsqlite3BtreeSchema() */
void (*xFreeSchema)(void*); /* Destructor for BtShared.pSchema */
- sqlite3_mutex *mutex; /* Non-recursive mutex required to access this object */
+ tdsqlite3_mutex *mutex; /* Non-recursive mutex required to access this object */
Bitvec *pHasContent; /* Set of pages moved to free-list this transaction */
#ifndef SQLITE_OMIT_SHARED_CACHE
int nRef; /* Number of references to this structure */
@@ -17424,10 +20835,12 @@ struct BtShared {
#define BTS_READ_ONLY 0x0001 /* Underlying file is readonly */
#define BTS_PAGESIZE_FIXED 0x0002 /* Page size can no longer be changed */
#define BTS_SECURE_DELETE 0x0004 /* PRAGMA secure_delete is enabled */
-#define BTS_INITIALLY_EMPTY 0x0008 /* Database was empty at trans start */
-#define BTS_NO_WAL 0x0010 /* Do not open write-ahead-log files */
-#define BTS_EXCLUSIVE 0x0020 /* pWriter has an exclusive lock */
-#define BTS_PENDING 0x0040 /* Waiting for read-locks to clear */
+#define BTS_OVERWRITE 0x0008 /* Overwrite deleted content with zeros */
+#define BTS_FAST_SECURE 0x000c /* Combination of the previous two */
+#define BTS_INITIALLY_EMPTY 0x0010 /* Database was empty at trans start */
+#define BTS_NO_WAL 0x0020 /* Do not open write-ahead-log files */
+#define BTS_EXCLUSIVE 0x0040 /* pWriter has an exclusive lock */
+#define BTS_PENDING 0x0080 /* Waiting for read-locks to clear */
/*
** An instance of the following structure is used to hold information
@@ -17468,35 +20881,43 @@ struct CellInfo {
** found at self->pBt->mutex.
**
** skipNext meaning:
-** eState==SKIPNEXT && skipNext>0: Next sqlite3BtreeNext() is no-op.
-** eState==SKIPNEXT && skipNext<0: Next sqlite3BtreePrevious() is no-op.
-** eState==FAULT: Cursor fault with skipNext as error code.
+** The meaning of skipNext depends on the value of eState:
+**
+** eState Meaning of skipNext
+** VALID skipNext is meaningless and is ignored
+** INVALID skipNext is meaningless and is ignored
+** SKIPNEXT tdsqlite3BtreeNext() is a no-op if skipNext>0 and
+** tdsqlite3BtreePrevious() is no-op if skipNext<0.
+** REQUIRESEEK restoreCursorPosition() restores the cursor to
+** eState=SKIPNEXT if skipNext!=0
+** FAULT skipNext holds the cursor fault error code.
*/
struct BtCursor {
+ u8 eState; /* One of the CURSOR_XXX constants (see below) */
+ u8 curFlags; /* zero or more BTCF_* flags defined below */
+ u8 curPagerFlags; /* Flags to send to tdsqlite3PagerGet() */
+ u8 hints; /* As configured by CursorSetHints() */
+ int skipNext; /* Prev() is noop if negative. Next() is noop if positive.
+ ** Error code if eState==CURSOR_FAULT */
Btree *pBtree; /* The Btree to which this cursor belongs */
+ Pgno *aOverflow; /* Cache of overflow page locations */
+ void *pKey; /* Saved key that was cursor last known position */
+ /* All fields above are zeroed when the cursor is allocated. See
+ ** tdsqlite3BtreeCursorZero(). Fields that follow must be manually
+ ** initialized. */
+#define BTCURSOR_FIRST_UNINIT pBt /* Name of first uninitialized field */
BtShared *pBt; /* The BtShared this cursor points to */
BtCursor *pNext; /* Forms a linked list of all cursors */
- Pgno *aOverflow; /* Cache of overflow page locations */
CellInfo info; /* A parse of the cell we are pointing at */
i64 nKey; /* Size of pKey, or last integer key */
- void *pKey; /* Saved key that was cursor last known position */
Pgno pgnoRoot; /* The root page of this tree */
- int nOvflAlloc; /* Allocated size of aOverflow[] array */
- int skipNext; /* Prev() is noop if negative. Next() is noop if positive.
- ** Error code if eState==CURSOR_FAULT */
- u8 curFlags; /* zero or more BTCF_* flags defined below */
- u8 curPagerFlags; /* Flags to send to sqlite3PagerGet() */
- u8 eState; /* One of the CURSOR_XXX constants (see below) */
- u8 hints; /* As configured by CursorSetHints() */
- /* All fields above are zeroed when the cursor is allocated. See
- ** sqlite3BtreeCursorZero(). Fields that follow must be manually
- ** initialized. */
i8 iPage; /* Index of current page in apPage */
u8 curIntKey; /* Value of apPage[0]->intKey */
- struct KeyInfo *pKeyInfo; /* Argument passed to comparison function */
- void *padding1; /* Make object size a multiple of 16 */
- u16 aiIdx[BTCURSOR_MAX_DEPTH]; /* Current index in apPage[i] */
- MemPage *apPage[BTCURSOR_MAX_DEPTH]; /* Pages from root to current page */
+ u16 ix; /* Current index for apPage[iPage] */
+ u16 aiIdx[BTCURSOR_MAX_DEPTH-1]; /* Current index in apPage[i] */
+ struct KeyInfo *pKeyInfo; /* Arg passed to comparison function */
+ MemPage *pPage; /* Current page */
+ MemPage *apPage[BTCURSOR_MAX_DEPTH-1]; /* Stack of parents of current page */
};
/*
@@ -17508,6 +20929,7 @@ struct BtCursor {
#define BTCF_AtLast 0x08 /* Cursor is pointing ot the last entry */
#define BTCF_Incrblob 0x10 /* True if an incremental I/O handle */
#define BTCF_Multiple 0x20 /* Maybe another cursor on the same btree */
+#define BTCF_Pinned 0x40 /* Cursor is busy and cannot be moved */
/*
** Potential values for BtCursor.eState.
@@ -17522,7 +20944,7 @@ struct BtCursor {
**
** CURSOR_SKIPNEXT:
** Cursor is valid except that the Cursor.skipNext field is non-zero
-** indicating that the next sqlite3BtreeNext() or sqlite3BtreePrevious()
+** indicating that the next tdsqlite3BtreeNext() or tdsqlite3BtreePrevious()
** operation should be a no-op.
**
** CURSOR_REQUIRESEEK:
@@ -17539,8 +20961,8 @@ struct BtCursor {
** Do nothing else with this cursor. Any attempt to use the cursor
** should return the error code stored in BtCursor.skipNext
*/
-#define CURSOR_INVALID 0
-#define CURSOR_VALID 1
+#define CURSOR_VALID 0
+#define CURSOR_INVALID 1
#define CURSOR_SKIPNEXT 2
#define CURSOR_REQUIRESEEK 3
#define CURSOR_FAULT 4
@@ -17651,6 +21073,7 @@ struct IntegrityCk {
int v1, v2; /* Values for up to two %d fields in zPfx */
StrAccum errMsg; /* Accumulate the error message text here */
u32 *heap; /* Min-heap used for analyzing cell coverage */
+ tdsqlite3 *db; /* Database connection running the check */
};
/*
@@ -17658,8 +21081,8 @@ struct IntegrityCk {
*/
#define get2byte(x) ((x)[0]<<8 | (x)[1])
#define put2byte(p,v) ((p)[0] = (u8)((v)>>8), (p)[1] = (u8)(v))
-#define get4byte sqlite3Get4byte
-#define put4byte sqlite3Put4byte
+#define get4byte tdsqlite3Get4byte
+#define put4byte tdsqlite3Put4byte
/*
** get2byteAligned(), unlike get2byte(), requires that its argument point to a
@@ -17668,75 +21091,36 @@ struct IntegrityCk {
*/
#if SQLITE_BYTEORDER==4321
# define get2byteAligned(x) (*(u16*)(x))
-#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \
- && GCC_VERSION>=4008000
+#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4008000
# define get2byteAligned(x) __builtin_bswap16(*(u16*)(x))
-#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \
- && defined(_MSC_VER) && _MSC_VER>=1300
+#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
# define get2byteAligned(x) _byteswap_ushort(*(u16*)(x))
#else
# define get2byteAligned(x) ((x)[0]<<8 | (x)[1])
#endif
/************** End of btreeInt.h ********************************************/
-/************** Continuing where we left off in crypto.c *********************/
-/************** Include crypto.h in the middle of crypto.c *******************/
-/************** Begin file crypto.h ******************************************/
-/*
-** SQLCipher
-** crypto.h developed by Stephen Lombardo (Zetetic LLC)
-** sjlombardo at zetetic dot net
-** http://zetetic.net
-**
-** Copyright (c) 2008, ZETETIC LLC
-** All rights reserved.
-**
-** Redistribution and use in source and binary forms, with or without
-** modification, are permitted provided that the following conditions are met:
-** * Redistributions of source code must retain the above copyright
-** notice, this list of conditions and the following disclaimer.
-** * Redistributions in binary form must reproduce the above copyright
-** notice, this list of conditions and the following disclaimer in the
-** documentation and/or other materials provided with the distribution.
-** * Neither the name of the ZETETIC LLC nor the
-** names of its contributors may be used to endorse or promote products
-** derived from this software without specific prior written permission.
-**
-** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
-** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
-** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-**
-*/
-/* BEGIN SQLCIPHER */
-#ifdef SQLITE_HAS_CODEC
-#ifndef CRYPTO_H
-#define CRYPTO_H
+/************** Continuing where we left off in crypto.h *********************/
+/* #include "pager.h" */
-#if !defined (SQLCIPHER_CRYPTO_CC) \
- && !defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT) \
- && !defined (SQLCIPHER_CRYPTO_OPENSSL)
-#define SQLCIPHER_CRYPTO_OPENSSL
-#endif
+/* extensions defined in pager.c */
+SQLITE_PRIVATE void *tdsqlite3PagerGetCodec(Pager*);
+SQLITE_PRIVATE void tdsqlite3PagerSetCodec(Pager*, void *(*)(void*,void*,Pgno,int), void (*)(void*,int,int), void (*)(void*), void *);
+SQLITE_API int tdsqlite3pager_is_mj_pgno(Pager*, Pgno);
+SQLITE_API void tdsqlite3pager_error(Pager*, int);
+SQLITE_API void tdsqlite3pager_reset(Pager *pPager);
#define FILE_HEADER_SZ 16
-#ifndef CIPHER_VERSION
-#ifdef SQLCIPHER_FIPS
-#define CIPHER_VERSION "3.4.1 FIPS"
-#else
-#define CIPHER_VERSION "3.4.1"
-#endif
+#define CIPHER_XSTR(s) CIPHER_STR(s)
+#define CIPHER_STR(s) #s
+
+#ifndef CIPHER_VERSION_NUMBER
+#define CIPHER_VERSION_NUMBER 4.4.0
#endif
-#ifndef CIPHER
-#define CIPHER "aes-256-cbc"
+#ifndef CIPHER_VERSION_BUILD
+#define CIPHER_VERSION_BUILD community
#endif
#define CIPHER_DECRYPT 0
@@ -17747,7 +21131,7 @@ struct IntegrityCk {
#define CIPHER_READWRITE_CTX 2
#ifndef PBKDF2_ITER
-#define PBKDF2_ITER 64000
+#define PBKDF2_ITER 256000
#endif
/* possible flags for cipher_ctx->flags */
@@ -17784,11 +21168,30 @@ struct IntegrityCk {
#define CIPHER_MAX_KEY_SZ 64
#endif
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
#ifdef CODEC_DEBUG
-#define CODEC_TRACE(X) {printf X;fflush(stdout);}
+#ifdef __ANDROID__
+#define CODEC_TRACE(...) {__android_log_print(ANDROID_LOG_DEBUG, "sqlcipher", __VA_ARGS__);}
+#else
+#define CODEC_TRACE(...) {fprintf(stderr, __VA_ARGS__);fflush(stderr);}
+#endif
#else
-#define CODEC_TRACE(X)
+#define CODEC_TRACE(...)
+#endif
+
+#ifdef CODEC_DEBUG_MUTEX
+#define CODEC_TRACE_MUTEX(...) CODEC_TRACE(__VA_ARGS__)
+#else
+#define CODEC_TRACE_MUTEX(...)
+#endif
+
+#ifdef CODEC_DEBUG_MEMORY
+#define CODEC_TRACE_MEMORY(...) CODEC_TRACE(__VA_ARGS__)
+#else
+#define CODEC_TRACE_MEMORY(...)
#endif
#ifdef CODEC_DEBUG_PAGEDATA
@@ -17807,18 +21210,6 @@ struct IntegrityCk {
#define CODEC_HEXDUMP(DESC,BUFFER,LEN)
#endif
-/* extensions defined in pager.c */
-SQLITE_API void sqlite3pager_get_codec(Pager *pPager, void **ctx);
-SQLITE_API int sqlite3pager_is_mj_pgno(Pager *pPager, Pgno pgno);
-SQLITE_PRIVATE sqlite3_file *sqlite3Pager_get_fd(Pager *pPager);
-SQLITE_API void sqlite3pager_sqlite3PagerSetCodec(
- Pager *pPager,
- void *(*xCodec)(void*,void*,Pgno,int),
- void (*xCodecSizeChng)(void*,int,int),
- void (*xCodecFree)(void*),
- void *pCodec
-);
-SQLITE_API void sqlite3pager_sqlite3PagerSetError(Pager *pPager, int error);
/* end extensions defined in pager.c */
/*
@@ -17840,7 +21231,7 @@ static void cipher_hex2bin(const unsigned char *hex, int sz, unsigned char *out)
static void cipher_bin2hex(const unsigned char* in, int sz, char *out) {
int i;
for(i=0; i < sz; i++) {
- sqlite3_snprintf(3, out + (i*2), "%02x ", in[i]);
+ tdsqlite3_snprintf(3, out + (i*2), "%02x ", in[i]);
}
}
@@ -17858,73 +21249,143 @@ static int cipher_isHex(const unsigned char *hex, int sz){
}
/* extensions defined in crypto_impl.c */
-typedef struct codec_ctx codec_ctx;
+/* the default implementation of SQLCipher uses a cipher_ctx
+ to keep track of read / write state separately. The following
+ struct and associated functions are defined here */
+typedef struct {
+ int derive_key;
+ int pass_sz;
+ unsigned char *key;
+ unsigned char *hmac_key;
+ unsigned char *pass;
+ char *keyspec;
+} cipher_ctx;
+
+
+typedef struct {
+ int store_pass;
+ int kdf_iter;
+ int fast_kdf_iter;
+ int kdf_salt_sz;
+ int key_sz;
+ int iv_sz;
+ int block_sz;
+ int page_sz;
+ int keyspec_sz;
+ int reserve_sz;
+ int hmac_sz;
+ int plaintext_header_sz;
+ int hmac_algorithm;
+ int kdf_algorithm;
+ unsigned int skip_read_hmac;
+ unsigned int need_kdf_salt;
+ unsigned int flags;
+ unsigned char *kdf_salt;
+ unsigned char *hmac_kdf_salt;
+ unsigned char *buffer;
+ Btree *pBt;
+ cipher_ctx *read_ctx;
+ cipher_ctx *write_ctx;
+ sqlcipher_provider *provider;
+ void *provider_ctx;
+} codec_ctx ;
+
+/* crypto.c functions */
+SQLITE_PRIVATE int sqlcipher_codec_pragma(tdsqlite3*, int, Parse*, const char *, const char*);
+SQLITE_PRIVATE int tdsqlite3CodecAttach(tdsqlite3*, int, const void *, int);
+SQLITE_PRIVATE void tdsqlite3CodecGetKey(tdsqlite3*, int, void**, int*);
+SQLITE_PRIVATE void sqlcipher_exportFunc(tdsqlite3_context *, int, tdsqlite3_value **);
+
+/* crypto_impl.c functions */
+
+SQLITE_PRIVATE void sqlcipher_init_memmethods(void);
/* activation and initialization */
-void sqlcipher_activate();
-void sqlcipher_deactivate();
-int sqlcipher_codec_ctx_init(codec_ctx **, Db *, Pager *, sqlite3_file *, const void *, int);
-void sqlcipher_codec_ctx_free(codec_ctx **);
-int sqlcipher_codec_key_derive(codec_ctx *);
-int sqlcipher_codec_key_copy(codec_ctx *, int);
+SQLITE_PRIVATE void sqlcipher_activate(void);
+SQLITE_PRIVATE void sqlcipher_deactivate(void);
+
+SQLITE_PRIVATE int sqlcipher_codec_ctx_init(codec_ctx **, Db *, Pager *, const void *, int);
+SQLITE_PRIVATE void sqlcipher_codec_ctx_free(codec_ctx **);
+SQLITE_PRIVATE int sqlcipher_codec_key_derive(codec_ctx *);
+SQLITE_PRIVATE int sqlcipher_codec_key_copy(codec_ctx *, int);
/* page cipher implementation */
-int sqlcipher_page_cipher(codec_ctx *, int, Pgno, int, int, unsigned char *, unsigned char *);
+SQLITE_PRIVATE int sqlcipher_page_cipher(codec_ctx *, int, Pgno, int, int, unsigned char *, unsigned char *);
/* context setters & getters */
-void sqlcipher_codec_ctx_set_error(codec_ctx *, int);
+SQLITE_PRIVATE void sqlcipher_codec_ctx_set_error(codec_ctx *, int);
+
+SQLITE_PRIVATE void sqlcipher_codec_get_pass(codec_ctx *, void **, int *);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_pass(codec_ctx *, const void *, int, int);
+SQLITE_PRIVATE void sqlcipher_codec_get_keyspec(codec_ctx *, void **zKey, int *nKey);
-int sqlcipher_codec_ctx_set_pass(codec_ctx *, const void *, int, int);
-void sqlcipher_codec_get_keyspec(codec_ctx *, void **zKey, int *nKey);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_pagesize(codec_ctx *, int);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_pagesize(codec_ctx *);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_reservesize(codec_ctx *);
-int sqlcipher_codec_ctx_set_pagesize(codec_ctx *, int);
-int sqlcipher_codec_ctx_get_pagesize(codec_ctx *);
-int sqlcipher_codec_ctx_get_reservesize(codec_ctx *);
+SQLITE_PRIVATE void sqlcipher_set_default_pagesize(int page_size);
+SQLITE_PRIVATE int sqlcipher_get_default_pagesize(void);
-void sqlcipher_set_default_pagesize(int page_size);
-int sqlcipher_get_default_pagesize();
+SQLITE_PRIVATE void sqlcipher_set_default_kdf_iter(int iter);
+SQLITE_PRIVATE int sqlcipher_get_default_kdf_iter(void);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *, int);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_kdf_iter(codec_ctx *ctx);
-void sqlcipher_set_default_kdf_iter(int iter);
-int sqlcipher_get_default_kdf_iter();
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int sz);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void **salt);
-int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *, int, int);
-int sqlcipher_codec_ctx_get_kdf_iter(codec_ctx *ctx, int);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_fast_kdf_iter(codec_ctx *, int);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_fast_kdf_iter(codec_ctx *);
-void* sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx);
+SQLITE_PRIVATE const char* sqlcipher_codec_ctx_get_cipher(codec_ctx *ctx);
-int sqlcipher_codec_ctx_set_fast_kdf_iter(codec_ctx *, int, int);
-int sqlcipher_codec_ctx_get_fast_kdf_iter(codec_ctx *, int);
+SQLITE_PRIVATE void* sqlcipher_codec_ctx_get_data(codec_ctx *);
-int sqlcipher_codec_ctx_set_cipher(codec_ctx *, const char *, int);
-const char* sqlcipher_codec_ctx_get_cipher(codec_ctx *ctx, int for_ctx);
+SQLITE_PRIVATE void sqlcipher_set_default_use_hmac(int use);
+SQLITE_PRIVATE int sqlcipher_get_default_use_hmac(void);
-void* sqlcipher_codec_ctx_get_data(codec_ctx *);
+SQLITE_PRIVATE void sqlcipher_set_hmac_salt_mask(unsigned char mask);
+SQLITE_PRIVATE unsigned char sqlcipher_get_hmac_salt_mask(void);
-void sqlcipher_exportFunc(sqlite3_context *, int, sqlite3_value **);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_use_hmac(codec_ctx *ctx);
-void sqlcipher_set_default_use_hmac(int use);
-int sqlcipher_get_default_use_hmac();
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_flag(codec_ctx *ctx, unsigned int flag);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_unset_flag(codec_ctx *ctx, unsigned int flag);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_flag(codec_ctx *ctx, unsigned int flag);
-void sqlcipher_set_hmac_salt_mask(unsigned char mask);
-unsigned char sqlcipher_get_hmac_salt_mask();
+SQLITE_PRIVATE const char* sqlcipher_codec_get_cipher_provider(codec_ctx *ctx);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_migrate(codec_ctx *ctx);
+SQLITE_PRIVATE int sqlcipher_codec_add_random(codec_ctx *ctx, const char *data, int random_sz);
+SQLITE_PRIVATE int sqlcipher_cipher_profile(tdsqlite3 *db, const char *destination);
+SQLITE_PRIVATE int sqlcipher_codec_get_store_pass(codec_ctx *ctx);
+SQLITE_PRIVATE void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey);
+SQLITE_PRIVATE void sqlcipher_codec_set_store_pass(codec_ctx *ctx, int value);
+SQLITE_PRIVATE int sqlcipher_codec_fips_status(codec_ctx *ctx);
+SQLITE_PRIVATE const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx);
-int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use);
-int sqlcipher_codec_ctx_get_use_hmac(codec_ctx *ctx, int for_ctx);
+SQLITE_PRIVATE int sqlcipher_set_default_plaintext_header_size(int size);
+SQLITE_PRIVATE int sqlcipher_get_default_plaintext_header_size(void);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_plaintext_header_size(codec_ctx *ctx);
-int sqlcipher_codec_ctx_set_flag(codec_ctx *ctx, unsigned int flag);
-int sqlcipher_codec_ctx_unset_flag(codec_ctx *ctx, unsigned int flag);
-int sqlcipher_codec_ctx_get_flag(codec_ctx *ctx, unsigned int flag, int for_ctx);
+SQLITE_PRIVATE int sqlcipher_set_default_hmac_algorithm(int algorithm);
+SQLITE_PRIVATE int sqlcipher_get_default_hmac_algorithm(void);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_hmac_algorithm(codec_ctx *ctx, int algorithm);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_hmac_algorithm(codec_ctx *ctx);
+
+SQLITE_PRIVATE int sqlcipher_set_default_kdf_algorithm(int algorithm);
+SQLITE_PRIVATE int sqlcipher_get_default_kdf_algorithm(void);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_set_kdf_algorithm(codec_ctx *ctx, int algorithm);
+SQLITE_PRIVATE int sqlcipher_codec_ctx_get_kdf_algorithm(codec_ctx *ctx);
+
+SQLITE_PRIVATE void sqlcipher_set_mem_security(int);
+SQLITE_PRIVATE int sqlcipher_get_mem_security(void);
+
+SQLITE_PRIVATE int sqlcipher_find_db_index(tdsqlite3 *db, const char *zDb);
+
+SQLITE_PRIVATE int sqlcipher_codec_ctx_integrity_check(codec_ctx *, Parse *, char *);
-const char* sqlcipher_codec_get_cipher_provider(codec_ctx *ctx);
-int sqlcipher_codec_ctx_migrate(codec_ctx *ctx);
-int sqlcipher_codec_add_random(codec_ctx *ctx, const char *data, int random_sz);
-int sqlcipher_cipher_profile(sqlite3 *db, const char *destination);
-static void sqlcipher_profile_callback(void *file, const char *sql, sqlite3_uint64 run_time);
-static int sqlcipher_codec_get_store_pass(codec_ctx *ctx);
-static void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey);
-static void sqlcipher_codec_set_store_pass(codec_ctx *ctx, int value);
-int sqlcipher_codec_fips_status(codec_ctx *ctx);
-const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx);
#endif
#endif
/* END SQLCIPHER */
@@ -17932,162 +21393,181 @@ const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx);
/************** End of crypto.h **********************************************/
/************** Continuing where we left off in crypto.c *********************/
-static const char* codec_get_cipher_version() {
- return CIPHER_VERSION;
-}
+#ifdef SQLCIPHER_EXT
+#include "sqlcipher_ext.h"
+#endif
/* Generate code to return a string value */
-static void codec_vdbe_return_static_string(Parse *pParse, const char *zLabel, const char *value){
- Vdbe *v = sqlite3GetVdbe(pParse);
- sqlite3VdbeSetNumCols(v, 1);
- sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLabel, SQLITE_STATIC);
- sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, value, 0);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+static void codec_vdbe_return_string(Parse *pParse, const char *zLabel, const char *value, int value_type){
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ tdsqlite3VdbeSetNumCols(v, 1);
+ tdsqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLabel, SQLITE_STATIC);
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, value, value_type);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
}
-static int codec_set_btree_to_codec_pagesize(sqlite3 *db, Db *pDb, codec_ctx *ctx) {
+static int codec_set_btree_to_codec_pagesize(tdsqlite3 *db, Db *pDb, codec_ctx *ctx) {
int rc, page_sz, reserve_sz;
page_sz = sqlcipher_codec_ctx_get_pagesize(ctx);
reserve_sz = sqlcipher_codec_ctx_get_reservesize(ctx);
- sqlite3_mutex_enter(db->mutex);
+ CODEC_TRACE("codec_set_btree_to_codec_pagesize: tdsqlite3BtreeSetPageSize() size=%d reserve=%d\n", page_sz, reserve_sz);
+
+ CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: entering database mutex %p\n", db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
+ CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: entered database mutex %p\n", db->mutex);
db->nextPagesize = page_sz;
/* before forcing the page size we need to unset the BTS_PAGESIZE_FIXED flag, else
sqliteBtreeSetPageSize will block the change */
pDb->pBt->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED;
- CODEC_TRACE(("codec_set_btree_to_codec_pagesize: sqlite3BtreeSetPageSize() size=%d reserve=%d\n", page_sz, reserve_sz));
- rc = sqlite3BtreeSetPageSize(pDb->pBt, page_sz, reserve_sz, 0);
- sqlite3_mutex_leave(db->mutex);
+ rc = tdsqlite3BtreeSetPageSize(pDb->pBt, page_sz, reserve_sz, 0);
+
+ CODEC_TRACE("codec_set_btree_to_codec_pagesize: tdsqlite3BtreeSetPageSize returned %d\n", rc);
+
+ CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: leaving database mutex %p\n", db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
+ CODEC_TRACE_MUTEX("codec_set_btree_to_codec_pagesize: left database mutex %p\n", db->mutex);
+
return rc;
}
-static int codec_set_pass_key(sqlite3* db, int nDb, const void *zKey, int nKey, int for_ctx) {
+static int codec_set_pass_key(tdsqlite3* db, int nDb, const void *zKey, int nKey, int for_ctx) {
struct Db *pDb = &db->aDb[nDb];
- CODEC_TRACE(("codec_set_pass_key: entered db=%p nDb=%d zKey=%s nKey=%d for_ctx=%d\n", db, nDb, (char *)zKey, nKey, for_ctx));
+ CODEC_TRACE("codec_set_pass_key: entered db=%p nDb=%d zKey=%s nKey=%d for_ctx=%d\n", db, nDb, (char *)zKey, nKey, for_ctx);
if(pDb->pBt) {
- codec_ctx *ctx;
- sqlite3pager_get_codec(pDb->pBt->pBt->pPager, (void **) &ctx);
+ codec_ctx *ctx = (codec_ctx*) tdsqlite3PagerGetCodec(pDb->pBt->pBt->pPager);
+
if(ctx) return sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, for_ctx);
}
return SQLITE_ERROR;
}
-int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLeft, const char *zRight) {
- char *pragma_cipher_deprecated_msg = "PRAGMA cipher command is deprecated, please remove from usage.";
+int sqlcipher_codec_pragma(tdsqlite3* db, int iDb, Parse *pParse, const char *zLeft, const char *zRight) {
struct Db *pDb = &db->aDb[iDb];
codec_ctx *ctx = NULL;
int rc;
if(pDb->pBt) {
- sqlite3pager_get_codec(pDb->pBt->pBt->pPager, (void **) &ctx);
+ ctx = (codec_ctx*) tdsqlite3PagerGetCodec(pDb->pBt->pBt->pPager);
}
- CODEC_TRACE(("sqlcipher_codec_pragma: entered db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p\n", db, iDb, pParse, zLeft, zRight, ctx));
+ CODEC_TRACE("sqlcipher_codec_pragma: entered db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p\n", db, iDb, pParse, zLeft, zRight, ctx);
- if( sqlite3StrICmp(zLeft, "cipher_fips_status")== 0 && !zRight ){
+#ifdef SQLCIPHER_EXT
+ if( tdsqlite3StrICmp(zLeft, "cipher_license")==0 && zRight ){
+ char *license_result = tdsqlite3_mprintf("%d", sqlcipher_license_key(zRight));
+ codec_vdbe_return_string(pParse, "cipher_license", license_result, P4_DYNAMIC);
+ } else
+ if( tdsqlite3StrICmp(zLeft, "cipher_license")==0 && !zRight ){
+ if(ctx) {
+ char *license_result = tdsqlite3_mprintf("%d", ctx
+ ? sqlcipher_license_key_status(ctx->provider)
+ : SQLITE_ERROR);
+ codec_vdbe_return_string(pParse, "cipher_license", license_result, P4_DYNAMIC);
+ }
+ } else
+#endif
+ if( tdsqlite3StrICmp(zLeft, "cipher_fips_status")== 0 && !zRight ){
if(ctx) {
- char *fips_mode_status = sqlite3_mprintf("%d", sqlcipher_codec_fips_status(ctx));
- codec_vdbe_return_static_string(pParse, "cipher_fips_status", fips_mode_status);
- sqlite3_free(fips_mode_status);
+ char *fips_mode_status = tdsqlite3_mprintf("%d", sqlcipher_codec_fips_status(ctx));
+ codec_vdbe_return_string(pParse, "cipher_fips_status", fips_mode_status, P4_DYNAMIC);
}
} else
- if( sqlite3StrICmp(zLeft, "cipher_store_pass")==0 && zRight ) {
+ if( tdsqlite3StrICmp(zLeft, "cipher_store_pass")==0 && zRight ) {
if(ctx) {
- sqlcipher_codec_set_store_pass(ctx, sqlite3GetBoolean(zRight, 1));
+ sqlcipher_codec_set_store_pass(ctx, tdsqlite3GetBoolean(zRight, 1));
}
} else
- if( sqlite3StrICmp(zLeft, "cipher_store_pass")==0 && !zRight ) {
+ if( tdsqlite3StrICmp(zLeft, "cipher_store_pass")==0 && !zRight ) {
if(ctx){
- char *store_pass_value = sqlite3_mprintf("%d", sqlcipher_codec_get_store_pass(ctx));
- codec_vdbe_return_static_string(pParse, "cipher_store_pass", store_pass_value);
- sqlite3_free(store_pass_value);
+ char *store_pass_value = tdsqlite3_mprintf("%d", sqlcipher_codec_get_store_pass(ctx));
+ codec_vdbe_return_string(pParse, "cipher_store_pass", store_pass_value, P4_DYNAMIC);
}
}
- if( sqlite3StrICmp(zLeft, "cipher_profile")== 0 && zRight ){
- char *profile_status = sqlite3_mprintf("%d", sqlcipher_cipher_profile(db, zRight));
- codec_vdbe_return_static_string(pParse, "cipher_profile", profile_status);
- sqlite3_free(profile_status);
+ if( tdsqlite3StrICmp(zLeft, "cipher_profile")== 0 && zRight ){
+ char *profile_status = tdsqlite3_mprintf("%d", sqlcipher_cipher_profile(db, zRight));
+ codec_vdbe_return_string(pParse, "cipher_profile", profile_status, P4_DYNAMIC);
} else
- if( sqlite3StrICmp(zLeft, "cipher_add_random")==0 && zRight ){
+ if( tdsqlite3StrICmp(zLeft, "cipher_add_random")==0 && zRight ){
if(ctx) {
- char *add_random_status = sqlite3_mprintf("%d", sqlcipher_codec_add_random(ctx, zRight, sqlite3Strlen30(zRight)));
- codec_vdbe_return_static_string(pParse, "cipher_add_random", add_random_status);
- sqlite3_free(add_random_status);
+ char *add_random_status = tdsqlite3_mprintf("%d", sqlcipher_codec_add_random(ctx, zRight, tdsqlite3Strlen30(zRight)));
+ codec_vdbe_return_string(pParse, "cipher_add_random", add_random_status, P4_DYNAMIC);
}
} else
- if( sqlite3StrICmp(zLeft, "cipher_migrate")==0 && !zRight ){
+ if( tdsqlite3StrICmp(zLeft, "cipher_migrate")==0 && !zRight ){
if(ctx){
- char *migrate_status = sqlite3_mprintf("%d", sqlcipher_codec_ctx_migrate(ctx));
- codec_vdbe_return_static_string(pParse, "cipher_migrate", migrate_status);
- sqlite3_free(migrate_status);
+ char *migrate_status = tdsqlite3_mprintf("%d", sqlcipher_codec_ctx_migrate(ctx));
+ codec_vdbe_return_string(pParse, "cipher_migrate", migrate_status, P4_DYNAMIC);
}
} else
- if( sqlite3StrICmp(zLeft, "cipher_provider")==0 && !zRight ){
- if(ctx) { codec_vdbe_return_static_string(pParse, "cipher_provider",
- sqlcipher_codec_get_cipher_provider(ctx));
+ if( tdsqlite3StrICmp(zLeft, "cipher_provider")==0 && !zRight ){
+ if(ctx) { codec_vdbe_return_string(pParse, "cipher_provider",
+ sqlcipher_codec_get_cipher_provider(ctx), P4_TRANSIENT);
}
} else
- if( sqlite3StrICmp(zLeft, "cipher_provider_version")==0 && !zRight){
- if(ctx) { codec_vdbe_return_static_string(pParse, "cipher_provider_version",
- sqlcipher_codec_get_provider_version(ctx));
+ if( tdsqlite3StrICmp(zLeft, "cipher_provider_version")==0 && !zRight){
+ if(ctx) { codec_vdbe_return_string(pParse, "cipher_provider_version",
+ sqlcipher_codec_get_provider_version(ctx), P4_TRANSIENT);
}
} else
- if( sqlite3StrICmp(zLeft, "cipher_version")==0 && !zRight ){
- codec_vdbe_return_static_string(pParse, "cipher_version", codec_get_cipher_version());
+ if( tdsqlite3StrICmp(zLeft, "cipher_version")==0 && !zRight ){
+ codec_vdbe_return_string(pParse, "cipher_version", sqlcipher_version(), P4_DYNAMIC);
}else
- if( sqlite3StrICmp(zLeft, "cipher")==0 ){
+ if( tdsqlite3StrICmp(zLeft, "cipher")==0 ){
if(ctx) {
if( zRight ) {
- rc = sqlcipher_codec_ctx_set_cipher(ctx, zRight, 2); // change cipher for both
- codec_vdbe_return_static_string(pParse, "cipher", pragma_cipher_deprecated_msg);
- sqlite3_log(SQLITE_WARNING, pragma_cipher_deprecated_msg);
- return rc;
+ const char* message = "PRAGMA cipher is no longer supported.";
+ codec_vdbe_return_string(pParse, "cipher", message, P4_TRANSIENT);
+ tdsqlite3_log(SQLITE_WARNING, message);
}else {
- codec_vdbe_return_static_string(pParse, "cipher",
- sqlcipher_codec_ctx_get_cipher(ctx, 2));
+ codec_vdbe_return_string(pParse, "cipher", sqlcipher_codec_ctx_get_cipher(ctx), P4_TRANSIENT);
}
}
}else
- if( sqlite3StrICmp(zLeft, "rekey_cipher")==0 && zRight ){
- if(ctx) sqlcipher_codec_ctx_set_cipher(ctx, zRight, 1); // change write cipher only
+ if( tdsqlite3StrICmp(zLeft, "rekey_cipher")==0 && zRight ){
+ const char* message = "PRAGMA rekey_cipher is no longer supported.";
+ codec_vdbe_return_string(pParse, "rekey_cipher", message, P4_TRANSIENT);
+ tdsqlite3_log(SQLITE_WARNING, message);
}else
- if( sqlite3StrICmp(zLeft,"cipher_default_kdf_iter")==0 ){
+ if( tdsqlite3StrICmp(zLeft,"cipher_default_kdf_iter")==0 ){
if( zRight ) {
- sqlcipher_set_default_kdf_iter(atoi(zRight)); // change default KDF iterations
+ sqlcipher_set_default_kdf_iter(atoi(zRight)); /* change default KDF iterations */
} else {
- char *kdf_iter = sqlite3_mprintf("%d", sqlcipher_get_default_kdf_iter());
- codec_vdbe_return_static_string(pParse, "cipher_default_kdf_iter", kdf_iter);
- sqlite3_free(kdf_iter);
+ char *kdf_iter = tdsqlite3_mprintf("%d", sqlcipher_get_default_kdf_iter());
+ codec_vdbe_return_string(pParse, "cipher_default_kdf_iter", kdf_iter, P4_DYNAMIC);
}
}else
- if( sqlite3StrICmp(zLeft, "kdf_iter")==0 ){
+ if( tdsqlite3StrICmp(zLeft, "kdf_iter")==0 ){
if(ctx) {
if( zRight ) {
- sqlcipher_codec_ctx_set_kdf_iter(ctx, atoi(zRight), 2); // change of RW PBKDF2 iteration
+ sqlcipher_codec_ctx_set_kdf_iter(ctx, atoi(zRight)); /* change of RW PBKDF2 iteration */
} else {
- char *kdf_iter = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_kdf_iter(ctx, 2));
- codec_vdbe_return_static_string(pParse, "kdf_iter", kdf_iter);
- sqlite3_free(kdf_iter);
+ char *kdf_iter = tdsqlite3_mprintf("%d", sqlcipher_codec_ctx_get_kdf_iter(ctx));
+ codec_vdbe_return_string(pParse, "kdf_iter", kdf_iter, P4_DYNAMIC);
}
}
}else
- if( sqlite3StrICmp(zLeft, "fast_kdf_iter")==0){
+ if( tdsqlite3StrICmp(zLeft, "fast_kdf_iter")==0){
if(ctx) {
if( zRight ) {
- sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, atoi(zRight), 2); // change of RW PBKDF2 iteration
+ char *deprecation = "PRAGMA fast_kdf_iter is deprecated, please remove from use";
+ sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, atoi(zRight)); /* change of RW PBKDF2 iteration */
+ codec_vdbe_return_string(pParse, "fast_kdf_iter", deprecation, P4_TRANSIENT);
+ tdsqlite3_log(SQLITE_WARNING, deprecation);
} else {
- char *fast_kdf_iter = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_fast_kdf_iter(ctx, 2));
- codec_vdbe_return_static_string(pParse, "fast_kdf_iter", fast_kdf_iter);
- sqlite3_free(fast_kdf_iter);
+ char *fast_kdf_iter = tdsqlite3_mprintf("%d", sqlcipher_codec_ctx_get_fast_kdf_iter(ctx));
+ codec_vdbe_return_string(pParse, "fast_kdf_iter", fast_kdf_iter, P4_DYNAMIC);
}
}
}else
- if( sqlite3StrICmp(zLeft, "rekey_kdf_iter")==0 && zRight ){
- if(ctx) sqlcipher_codec_ctx_set_kdf_iter(ctx, atoi(zRight), 1); // write iterations only
+ if( tdsqlite3StrICmp(zLeft, "rekey_kdf_iter")==0 && zRight ){
+ const char* message = "PRAGMA rekey_kdf_iter is no longer supported.";
+ codec_vdbe_return_string(pParse, "rekey_kdf_iter", message, P4_TRANSIENT);
+ tdsqlite3_log(SQLITE_WARNING, message);
}else
- if( sqlite3StrICmp(zLeft,"cipher_page_size")==0 ){
+ if( tdsqlite3StrICmp(zLeft,"cipher_page_size")==0 ){
if(ctx) {
if( zRight ) {
int size = atoi(zRight);
@@ -18096,84 +21576,416 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef
rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx);
if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc);
} else {
- char * page_size = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_pagesize(ctx));
- codec_vdbe_return_static_string(pParse, "cipher_page_size", page_size);
- sqlite3_free(page_size);
+ char * page_size = tdsqlite3_mprintf("%d", sqlcipher_codec_ctx_get_pagesize(ctx));
+ codec_vdbe_return_string(pParse, "cipher_page_size", page_size, P4_DYNAMIC);
}
}
}else
- if( sqlite3StrICmp(zLeft,"cipher_default_page_size")==0 ){
+ if( tdsqlite3StrICmp(zLeft,"cipher_default_page_size")==0 ){
if( zRight ) {
sqlcipher_set_default_pagesize(atoi(zRight));
} else {
- char *default_page_size = sqlite3_mprintf("%d", sqlcipher_get_default_pagesize());
- codec_vdbe_return_static_string(pParse, "cipher_default_page_size", default_page_size);
- sqlite3_free(default_page_size);
+ char *default_page_size = tdsqlite3_mprintf("%d", sqlcipher_get_default_pagesize());
+ codec_vdbe_return_string(pParse, "cipher_default_page_size", default_page_size, P4_DYNAMIC);
}
}else
- if( sqlite3StrICmp(zLeft,"cipher_default_use_hmac")==0 ){
+ if( tdsqlite3StrICmp(zLeft,"cipher_default_use_hmac")==0 ){
if( zRight ) {
- sqlcipher_set_default_use_hmac(sqlite3GetBoolean(zRight,1));
+ sqlcipher_set_default_use_hmac(tdsqlite3GetBoolean(zRight,1));
} else {
- char *default_use_hmac = sqlite3_mprintf("%d", sqlcipher_get_default_use_hmac());
- codec_vdbe_return_static_string(pParse, "cipher_default_use_hmac", default_use_hmac);
- sqlite3_free(default_use_hmac);
+ char *default_use_hmac = tdsqlite3_mprintf("%d", sqlcipher_get_default_use_hmac());
+ codec_vdbe_return_string(pParse, "cipher_default_use_hmac", default_use_hmac, P4_DYNAMIC);
}
}else
- if( sqlite3StrICmp(zLeft,"cipher_use_hmac")==0 ){
+ if( tdsqlite3StrICmp(zLeft,"cipher_use_hmac")==0 ){
if(ctx) {
if( zRight ) {
- rc = sqlcipher_codec_ctx_set_use_hmac(ctx, sqlite3GetBoolean(zRight,1));
+ rc = sqlcipher_codec_ctx_set_use_hmac(ctx, tdsqlite3GetBoolean(zRight,1));
if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc);
/* since the use of hmac has changed, the page size may also change */
rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx);
if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc);
} else {
- char *hmac_flag = sqlite3_mprintf("%d", sqlcipher_codec_ctx_get_use_hmac(ctx, 2));
- codec_vdbe_return_static_string(pParse, "cipher_use_hmac", hmac_flag);
- sqlite3_free(hmac_flag);
+ char *hmac_flag = tdsqlite3_mprintf("%d", sqlcipher_codec_ctx_get_use_hmac(ctx));
+ codec_vdbe_return_string(pParse, "cipher_use_hmac", hmac_flag, P4_DYNAMIC);
}
}
}else
- if( sqlite3StrICmp(zLeft,"cipher_hmac_pgno")==0 ){
+ if( tdsqlite3StrICmp(zLeft,"cipher_hmac_pgno")==0 ){
if(ctx) {
if(zRight) {
- // clear both pgno endian flags
- if(sqlite3StrICmp(zRight, "le") == 0) {
+ char *deprecation = "PRAGMA cipher_hmac_pgno is deprecated, please remove from use";
+ /* clear both pgno endian flags */
+ if(tdsqlite3StrICmp(zRight, "le") == 0) {
sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_BE_PGNO);
sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_LE_PGNO);
- } else if(sqlite3StrICmp(zRight, "be") == 0) {
+ } else if(tdsqlite3StrICmp(zRight, "be") == 0) {
sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_LE_PGNO);
sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_BE_PGNO);
- } else if(sqlite3StrICmp(zRight, "native") == 0) {
+ } else if(tdsqlite3StrICmp(zRight, "native") == 0) {
sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_LE_PGNO);
sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_BE_PGNO);
}
+ codec_vdbe_return_string(pParse, "cipher_hmac_pgno", deprecation, P4_TRANSIENT);
+ tdsqlite3_log(SQLITE_WARNING, deprecation);
+
} else {
- if(sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_LE_PGNO, 2)) {
- codec_vdbe_return_static_string(pParse, "cipher_hmac_pgno", "le");
- } else if(sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_BE_PGNO, 2)) {
- codec_vdbe_return_static_string(pParse, "cipher_hmac_pgno", "be");
+ if(sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_LE_PGNO)) {
+ codec_vdbe_return_string(pParse, "cipher_hmac_pgno", "le", P4_TRANSIENT);
+ } else if(sqlcipher_codec_ctx_get_flag(ctx, CIPHER_FLAG_BE_PGNO)) {
+ codec_vdbe_return_string(pParse, "cipher_hmac_pgno", "be", P4_TRANSIENT);
} else {
- codec_vdbe_return_static_string(pParse, "cipher_hmac_pgno", "native");
+ codec_vdbe_return_string(pParse, "cipher_hmac_pgno", "native", P4_TRANSIENT);
}
}
}
}else
- if( sqlite3StrICmp(zLeft,"cipher_hmac_salt_mask")==0 ){
+ if( tdsqlite3StrICmp(zLeft,"cipher_hmac_salt_mask")==0 ){
if(ctx) {
if(zRight) {
- if (sqlite3StrNICmp(zRight ,"x'", 2) == 0 && sqlite3Strlen30(zRight) == 5) {
+ char *deprecation = "PRAGMA cipher_hmac_salt_mask is deprecated, please remove from use";
+ if (tdsqlite3StrNICmp(zRight ,"x'", 2) == 0 && tdsqlite3Strlen30(zRight) == 5) {
unsigned char mask = 0;
const unsigned char *hex = (const unsigned char *)zRight+2;
cipher_hex2bin(hex,2,&mask);
sqlcipher_set_hmac_salt_mask(mask);
}
+ codec_vdbe_return_string(pParse, "cipher_hmac_salt_mask", deprecation, P4_TRANSIENT);
+ tdsqlite3_log(SQLITE_WARNING, deprecation);
+ } else {
+ char *hmac_salt_mask = tdsqlite3_mprintf("%02x", sqlcipher_get_hmac_salt_mask());
+ codec_vdbe_return_string(pParse, "cipher_hmac_salt_mask", hmac_salt_mask, P4_DYNAMIC);
+ }
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_plaintext_header_size")==0 ){
+ if(ctx) {
+ if( zRight ) {
+ int size = atoi(zRight);
+ if((rc = sqlcipher_codec_ctx_set_plaintext_header_size(ctx, size)) != SQLITE_OK)
+ sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ } else {
+ char *size = tdsqlite3_mprintf("%d", sqlcipher_codec_ctx_get_plaintext_header_size(ctx));
+ codec_vdbe_return_string(pParse, "cipher_plaintext_header_size", size, P4_DYNAMIC);
+ }
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_default_plaintext_header_size")==0 ){
+ if( zRight ) {
+ sqlcipher_set_default_plaintext_header_size(atoi(zRight));
+ } else {
+ char *size = tdsqlite3_mprintf("%d", sqlcipher_get_default_plaintext_header_size());
+ codec_vdbe_return_string(pParse, "cipher_default_plaintext_header_size", size, P4_DYNAMIC);
+ tdsqlite3_free(size);
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_salt")==0 ){
+ if(ctx) {
+ if(zRight) {
+ if (tdsqlite3StrNICmp(zRight ,"x'", 2) == 0 && tdsqlite3Strlen30(zRight) == (FILE_HEADER_SZ*2)+3) {
+ unsigned char *salt = (unsigned char*) tdsqlite3_malloc(FILE_HEADER_SZ);
+ const unsigned char *hex = (const unsigned char *)zRight+2;
+ cipher_hex2bin(hex,FILE_HEADER_SZ*2,salt);
+ sqlcipher_codec_ctx_set_kdf_salt(ctx, salt, FILE_HEADER_SZ);
+ tdsqlite3_free(salt);
+ }
+ } else {
+ void *salt;
+ char *hexsalt = (char*) tdsqlite3_malloc((FILE_HEADER_SZ*2)+1);
+ if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &salt)) == SQLITE_OK) {
+ cipher_bin2hex(salt, FILE_HEADER_SZ, hexsalt);
+ codec_vdbe_return_string(pParse, "cipher_salt", hexsalt, P4_DYNAMIC);
+ } else {
+ tdsqlite3_free(hexsalt);
+ sqlcipher_codec_ctx_set_error(ctx, rc);
+ }
+ }
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_hmac_algorithm")==0 ){
+ if(ctx) {
+ if(zRight) {
+ rc = SQLITE_ERROR;
+ if(tdsqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) {
+ rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1);
+ } else if(tdsqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) {
+ rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA256);
+ } else if(tdsqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) {
+ rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA512);
+ }
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ } else {
+ int algorithm = sqlcipher_codec_ctx_get_hmac_algorithm(ctx);
+ if(algorithm == SQLCIPHER_HMAC_SHA1) {
+ codec_vdbe_return_string(pParse, "cipher_hmac_algorithm", SQLCIPHER_HMAC_SHA1_LABEL, P4_TRANSIENT);
+ } else if(algorithm == SQLCIPHER_HMAC_SHA256) {
+ codec_vdbe_return_string(pParse, "cipher_hmac_algorithm", SQLCIPHER_HMAC_SHA256_LABEL, P4_TRANSIENT);
+ } else if(algorithm == SQLCIPHER_HMAC_SHA512) {
+ codec_vdbe_return_string(pParse, "cipher_hmac_algorithm", SQLCIPHER_HMAC_SHA512_LABEL, P4_TRANSIENT);
+ }
+ }
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_default_hmac_algorithm")==0 ){
+ if(zRight) {
+ rc = SQLITE_ERROR;
+ if(tdsqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA1_LABEL) == 0) {
+ rc = sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA1);
+ } else if(tdsqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA256_LABEL) == 0) {
+ rc = sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA256);
+ } else if(tdsqlite3StrICmp(zRight, SQLCIPHER_HMAC_SHA512_LABEL) == 0) {
+ rc = sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA512);
+ }
+ } else {
+ int algorithm = sqlcipher_get_default_hmac_algorithm();
+ if(algorithm == SQLCIPHER_HMAC_SHA1) {
+ codec_vdbe_return_string(pParse, "cipher_default_hmac_algorithm", SQLCIPHER_HMAC_SHA1_LABEL, P4_TRANSIENT);
+ } else if(algorithm == SQLCIPHER_HMAC_SHA256) {
+ codec_vdbe_return_string(pParse, "cipher_default_hmac_algorithm", SQLCIPHER_HMAC_SHA256_LABEL, P4_TRANSIENT);
+ } else if(algorithm == SQLCIPHER_HMAC_SHA512) {
+ codec_vdbe_return_string(pParse, "cipher_default_hmac_algorithm", SQLCIPHER_HMAC_SHA512_LABEL, P4_TRANSIENT);
+ }
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_kdf_algorithm")==0 ){
+ if(ctx) {
+ if(zRight) {
+ rc = SQLITE_ERROR;
+ if(tdsqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) {
+ rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1);
+ } else if(tdsqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) {
+ rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA256);
+ } else if(tdsqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) {
+ rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA512);
+ }
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
} else {
- char *hmac_salt_mask = sqlite3_mprintf("%02x", sqlcipher_get_hmac_salt_mask());
- codec_vdbe_return_static_string(pParse, "cipher_hmac_salt_mask", hmac_salt_mask);
- sqlite3_free(hmac_salt_mask);
+ int algorithm = sqlcipher_codec_ctx_get_kdf_algorithm(ctx);
+ if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) {
+ codec_vdbe_return_string(pParse, "cipher_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL, P4_TRANSIENT);
+ } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) {
+ codec_vdbe_return_string(pParse, "cipher_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL, P4_TRANSIENT);
+ } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) {
+ codec_vdbe_return_string(pParse, "cipher_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL, P4_TRANSIENT);
+ }
+ }
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_default_kdf_algorithm")==0 ){
+ if(zRight) {
+ rc = SQLITE_ERROR;
+ if(tdsqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL) == 0) {
+ rc = sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA1);
+ } else if(tdsqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL) == 0) {
+ rc = sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA256);
+ } else if(tdsqlite3StrICmp(zRight, SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL) == 0) {
+ rc = sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA512);
+ }
+ } else {
+ int algorithm = sqlcipher_get_default_kdf_algorithm();
+ if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) {
+ codec_vdbe_return_string(pParse, "cipher_default_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL, P4_TRANSIENT);
+ } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) {
+ codec_vdbe_return_string(pParse, "cipher_default_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL, P4_TRANSIENT);
+ } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) {
+ codec_vdbe_return_string(pParse, "cipher_default_kdf_algorithm", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL, P4_TRANSIENT);
+ }
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_compatibility")==0 ){
+ if(ctx) {
+ if(zRight) {
+ int version = atoi(zRight);
+
+ switch(version) {
+ case 1:
+ rc = sqlcipher_codec_ctx_set_pagesize(ctx, 1024);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 4000);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 0);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ break;
+
+ case 2:
+ rc = sqlcipher_codec_ctx_set_pagesize(ctx, 1024);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 4000);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 1);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ break;
+
+ case 3:
+ rc = sqlcipher_codec_ctx_set_pagesize(ctx, 1024);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA1);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA1);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 64000);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 1);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ break;
+
+ default:
+ rc = sqlcipher_codec_ctx_set_pagesize(ctx, 4096);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, SQLCIPHER_HMAC_SHA512);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, SQLCIPHER_PBKDF2_HMAC_SHA512);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, 256000);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ rc = sqlcipher_codec_ctx_set_use_hmac(ctx, 1);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ break;
+ }
+
+ rc = codec_set_btree_to_codec_pagesize(db, pDb, ctx);
+ if (rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR);
+ }
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_default_compatibility")==0 ){
+ if(zRight) {
+ int version = atoi(zRight);
+ switch(version) {
+ case 1:
+ sqlcipher_set_default_pagesize(1024);
+ sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA1);
+ sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA1);
+ sqlcipher_set_default_kdf_iter(4000);
+ sqlcipher_set_default_use_hmac(0);
+ break;
+
+ case 2:
+ sqlcipher_set_default_pagesize(1024);
+ sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA1);
+ sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA1);
+ sqlcipher_set_default_kdf_iter(4000);
+ sqlcipher_set_default_use_hmac(1);
+ break;
+
+ case 3:
+ sqlcipher_set_default_pagesize(1024);
+ sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA1);
+ sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA1);
+ sqlcipher_set_default_kdf_iter(64000);
+ sqlcipher_set_default_use_hmac(1);
+ break;
+
+ default:
+ sqlcipher_set_default_pagesize(4096);
+ sqlcipher_set_default_hmac_algorithm(SQLCIPHER_HMAC_SHA512);
+ sqlcipher_set_default_kdf_algorithm(SQLCIPHER_PBKDF2_HMAC_SHA512);
+ sqlcipher_set_default_kdf_iter(256000);
+ sqlcipher_set_default_use_hmac(1);
+ break;
+ }
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_memory_security")==0 ){
+ if( zRight ) {
+ sqlcipher_set_mem_security(tdsqlite3GetBoolean(zRight,1));
+ } else {
+ char *on = tdsqlite3_mprintf("%d", sqlcipher_get_mem_security());
+ codec_vdbe_return_string(pParse, "cipher_memory_security", on, P4_DYNAMIC);
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_settings")==0 ){
+ if(ctx) {
+ int algorithm;
+ char *pragma;
+
+ pragma = tdsqlite3_mprintf("PRAGMA kdf_iter = %d;", sqlcipher_codec_ctx_get_kdf_iter(ctx));
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_page_size = %d;", sqlcipher_codec_ctx_get_pagesize(ctx));
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_use_hmac = %d;", sqlcipher_codec_ctx_get_use_hmac(ctx));
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_plaintext_header_size = %d;", sqlcipher_codec_ctx_get_plaintext_header_size(ctx));
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ algorithm = sqlcipher_codec_ctx_get_hmac_algorithm(ctx);
+ pragma = NULL;
+ if(algorithm == SQLCIPHER_HMAC_SHA1) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA1_LABEL);
+ } else if(algorithm == SQLCIPHER_HMAC_SHA256) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA256_LABEL);
+ } else if(algorithm == SQLCIPHER_HMAC_SHA512) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA512_LABEL);
}
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ algorithm = sqlcipher_codec_ctx_get_kdf_algorithm(ctx);
+ pragma = NULL;
+ if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL);
+ } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL);
+ } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL);
+ }
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ }
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_default_settings")==0 ){
+ int algorithm;
+ char *pragma;
+
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_kdf_iter = %d;", sqlcipher_get_default_kdf_iter());
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_page_size = %d;", sqlcipher_get_default_pagesize());
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_use_hmac = %d;", sqlcipher_get_default_use_hmac());
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_plaintext_header_size = %d;", sqlcipher_get_default_plaintext_header_size());
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ algorithm = sqlcipher_get_default_hmac_algorithm();
+ pragma = NULL;
+ if(algorithm == SQLCIPHER_HMAC_SHA1) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA1_LABEL);
+ } else if(algorithm == SQLCIPHER_HMAC_SHA256) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA256_LABEL);
+ } else if(algorithm == SQLCIPHER_HMAC_SHA512) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_hmac_algorithm = %s;", SQLCIPHER_HMAC_SHA512_LABEL);
+ }
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+
+ algorithm = sqlcipher_get_default_kdf_algorithm();
+ pragma = NULL;
+ if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA1) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA1_LABEL);
+ } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA256) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA256_LABEL);
+ } else if(algorithm == SQLCIPHER_PBKDF2_HMAC_SHA512) {
+ pragma = tdsqlite3_mprintf("PRAGMA cipher_default_kdf_algorithm = %s;", SQLCIPHER_PBKDF2_HMAC_SHA512_LABEL);
+ }
+ codec_vdbe_return_string(pParse, "pragma", pragma, P4_DYNAMIC);
+ }else
+ if( tdsqlite3StrICmp(zLeft,"cipher_integrity_check")==0 ){
+ if(ctx) {
+ sqlcipher_codec_ctx_integrity_check(ctx, pParse, "cipher_integrity_check");
}
}else {
return 0;
@@ -18181,22 +21993,36 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef
return 1;
}
+/* these constants are used internally within SQLite's pager.c to differentiate between
+ operations on the main database or journal pages. This is important in the context
+ of a rekey operations, where the journal must be written using the original key
+ material (to allow a transactional rollback), while the new database pages are being
+ written with the new key material*/
+#define CODEC_READ_OP 3
+#define CODEC_WRITE_OP 6
+#define CODEC_JOURNAL_OP 7
/*
- * sqlite3Codec can be called in multiple modes.
+ * tdsqlite3Codec can be called in multiple modes.
* encrypt mode - expected to return a pointer to the
* encrypted data without altering pData.
* decrypt mode - expected to return a pointer to pData, with
* the data decrypted in the input buffer
*/
-void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) {
+static void* tdsqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) {
codec_ctx *ctx = (codec_ctx *) iCtx;
int offset = 0, rc = 0;
int page_sz = sqlcipher_codec_ctx_get_pagesize(ctx);
unsigned char *pData = (unsigned char *) data;
void *buffer = sqlcipher_codec_ctx_get_data(ctx);
- void *kdf_salt = sqlcipher_codec_ctx_get_kdf_salt(ctx);
- CODEC_TRACE(("sqlite3Codec: entered pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz));
+ int plaintext_header_sz = sqlcipher_codec_ctx_get_plaintext_header_size(ctx);
+ int cctx = CIPHER_READ_CTX;
+
+ CODEC_TRACE("tdsqlite3Codec: entered pgno=%d, mode=%d, page_sz=%d\n", pgno, mode, page_sz);
+
+#ifdef SQLCIPHER_EXT
+ if(sqlcipher_license_check(ctx) != SQLITE_OK) return NULL;
+#endif
/* call to derive keys if not present yet */
if((rc = sqlcipher_codec_key_derive(ctx)) != SQLITE_OK) {
@@ -18204,94 +22030,133 @@ void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) {
return NULL;
}
- if(pgno == 1) offset = FILE_HEADER_SZ; /* adjust starting pointers in data page for header offset on first page*/
+ if(pgno == 1) /* adjust starting pointers in data page for header offset on first page*/
+ offset = plaintext_header_sz ? plaintext_header_sz : FILE_HEADER_SZ;
+
- CODEC_TRACE(("sqlite3Codec: switch mode=%d offset=%d\n", mode, offset));
+ CODEC_TRACE("tdsqlite3Codec: switch mode=%d offset=%d\n", mode, offset);
switch(mode) {
- case 0: /* decrypt */
- case 2:
- case 3:
- if(pgno == 1) memcpy(buffer, SQLITE_FILE_HEADER, FILE_HEADER_SZ); /* copy file header to the first 16 bytes of the page */
- rc = sqlcipher_page_cipher(ctx, CIPHER_READ_CTX, pgno, CIPHER_DECRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset);
- if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc);
+ case CODEC_READ_OP: /* decrypt */
+ if(pgno == 1) /* copy initial part of file header or SQLite magic to buffer */
+ memcpy(buffer, plaintext_header_sz ? pData : (void *) SQLITE_FILE_HEADER, offset);
+
+ rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_DECRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset);
+ if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */
+ sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset);
+ sqlcipher_codec_ctx_set_error(ctx, rc);
+ }
memcpy(pData, buffer, page_sz); /* copy buffer data back to pData and return */
return pData;
break;
- case 6: /* encrypt */
- if(pgno == 1) memcpy(buffer, kdf_salt, FILE_HEADER_SZ); /* copy salt to output buffer */
- rc = sqlcipher_page_cipher(ctx, CIPHER_WRITE_CTX, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset);
- if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc);
- return buffer; /* return persistent buffer data, pData remains intact */
- break;
- case 7:
- if(pgno == 1) memcpy(buffer, kdf_salt, FILE_HEADER_SZ); /* copy salt to output buffer */
- rc = sqlcipher_page_cipher(ctx, CIPHER_READ_CTX, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset);
- if(rc != SQLITE_OK) sqlcipher_codec_ctx_set_error(ctx, rc);
+
+ case CODEC_WRITE_OP: /* encrypt database page, operate on write context and fall through to case 7, so the write context is used*/
+ cctx = CIPHER_WRITE_CTX;
+
+ case CODEC_JOURNAL_OP: /* encrypt journal page, operate on read context use to get the original page data from the database */
+ if(pgno == 1) { /* copy initial part of file header or salt to buffer */
+ void *kdf_salt = NULL;
+ /* retrieve the kdf salt */
+ if((rc = sqlcipher_codec_ctx_get_kdf_salt(ctx, &kdf_salt)) != SQLITE_OK) {
+ sqlcipher_codec_ctx_set_error(ctx, rc);
+ return NULL;
+ }
+ memcpy(buffer, plaintext_header_sz ? pData : kdf_salt, offset);
+ }
+ rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset);
+ if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */
+ sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset);
+ sqlcipher_codec_ctx_set_error(ctx, rc);
+ }
return buffer; /* return persistent buffer data, pData remains intact */
break;
+
default:
+ sqlcipher_codec_ctx_set_error(ctx, SQLITE_ERROR); /* unsupported mode, set error */
return pData;
break;
}
}
-SQLITE_PRIVATE void sqlite3FreeCodecArg(void *pCodecArg) {
+static void tdsqlite3FreeCodecArg(void *pCodecArg) {
codec_ctx *ctx = (codec_ctx *) pCodecArg;
if(pCodecArg == NULL) return;
- sqlcipher_codec_ctx_free(&ctx); // wipe and free allocated memory for the context
+ sqlcipher_codec_ctx_free(&ctx); /* wipe and free allocated memory for the context */
sqlcipher_deactivate(); /* cleanup related structures, OpenSSL etc, when codec is detatched */
}
-SQLITE_PRIVATE int sqlite3CodecAttach(sqlite3* db, int nDb, const void *zKey, int nKey) {
+SQLITE_PRIVATE int tdsqlite3CodecAttach(tdsqlite3* db, int nDb, const void *zKey, int nKey) {
struct Db *pDb = &db->aDb[nDb];
- CODEC_TRACE(("sqlite3CodecAttach: entered nDb=%d zKey=%s, nKey=%d\n", nDb, (char *)zKey, nKey));
+ CODEC_TRACE("tdsqlite3CodecAttach: entered db=%p, nDb=%d zKey=%s, nKey=%d\n", db, nDb, (char *)zKey, nKey);
if(nKey && zKey && pDb->pBt) {
int rc;
Pager *pPager = pDb->pBt->pBt->pPager;
- sqlite3_file *fd = sqlite3Pager_get_fd(pPager);
+ tdsqlite3_file *fd;
codec_ctx *ctx;
+ /* check if the tdsqlite3_file is open, and if not force handle to NULL */
+ if((fd = tdsqlite3PagerFile(pPager))->pMethods == 0) fd = NULL;
+
+ CODEC_TRACE("tdsqlite3CodecAttach: calling sqlcipher_activate()\n");
sqlcipher_activate(); /* perform internal initialization for sqlcipher */
- sqlite3_mutex_enter(db->mutex);
+ CODEC_TRACE_MUTEX("tdsqlite3CodecAttach: entering database mutex %p\n", db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
+ CODEC_TRACE_MUTEX("tdsqlite3CodecAttach: entered database mutex %p\n", db->mutex);
+
+#ifdef SQLCIPHER_EXT
+ if((rc = tdsqlite3_set_authorizer(db, sqlcipher_license_authorizer, db)) != SQLITE_OK) {
+ tdsqlite3_mutex_leave(db->mutex);
+ return rc;
+ }
+#endif
/* point the internal codec argument against the contet to be prepared */
- rc = sqlcipher_codec_ctx_init(&ctx, pDb, pDb->pBt->pBt->pPager, fd, zKey, nKey);
+ CODEC_TRACE("tdsqlite3CodecAttach: calling sqlcipher_codec_ctx_init()\n");
+ rc = sqlcipher_codec_ctx_init(&ctx, pDb, pDb->pBt->pBt->pPager, zKey, nKey);
if(rc != SQLITE_OK) {
/* initialization failed, do not attach potentially corrupted context */
- sqlite3_mutex_leave(db->mutex);
+ CODEC_TRACE("tdsqlite3CodecAttach: context initialization failed with rc=%d\n", rc);
+ /* force an error at the pager level, such that even the upstream caller ignores the return code
+ the pager will be in an error state and will process no further operations */
+ tdsqlite3pager_error(pPager, rc);
+ pDb->pBt->pBt->db->errCode = rc;
+ CODEC_TRACE_MUTEX("tdsqlite3CodecAttach: leaving database mutex %p (early return on rc=%d)\n", db->mutex, rc);
+ tdsqlite3_mutex_leave(db->mutex);
+ CODEC_TRACE_MUTEX("tdsqlite3CodecAttach: left database mutex %p (early return on rc=%d)\n", db->mutex, rc);
return rc;
}
- sqlite3pager_sqlite3PagerSetCodec(sqlite3BtreePager(pDb->pBt), sqlite3Codec, NULL, sqlite3FreeCodecArg, (void *) ctx);
+ CODEC_TRACE("tdsqlite3CodecAttach: calling tdsqlite3PagerSetCodec()\n");
+ tdsqlite3PagerSetCodec(tdsqlite3BtreePager(pDb->pBt), tdsqlite3Codec, NULL, tdsqlite3FreeCodecArg, (void *) ctx);
+ CODEC_TRACE("tdsqlite3CodecAttach: calling codec_set_btree_to_codec_pagesize()\n");
codec_set_btree_to_codec_pagesize(db, pDb, ctx);
/* force secure delete. This has the benefit of wiping internal data when deleted
and also ensures that all pages are written to disk (i.e. not skipped by
- sqlite3PagerDontWrite optimizations) */
- sqlite3BtreeSecureDelete(pDb->pBt, 1);
+ tdsqlite3PagerDontWrite optimizations) */
+ CODEC_TRACE("tdsqlite3CodecAttach: calling tdsqlite3BtreeSecureDelete()\n");
+ tdsqlite3BtreeSecureDelete(pDb->pBt, 1);
/* if fd is null, then this is an in-memory database and
we dont' want to overwrite the AutoVacuum settings
if not null, then set to the default */
if(fd != NULL) {
- sqlite3BtreeSetAutoVacuum(pDb->pBt, SQLITE_DEFAULT_AUTOVACUUM);
+ CODEC_TRACE("tdsqlite3CodecAttach: calling tdsqlite3BtreeSetAutoVacuum()\n");
+ tdsqlite3BtreeSetAutoVacuum(pDb->pBt, SQLITE_DEFAULT_AUTOVACUUM);
}
- sqlite3_mutex_leave(db->mutex);
+ CODEC_TRACE_MUTEX("tdsqlite3CodecAttach: leaving database mutex %p\n", db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
+ CODEC_TRACE_MUTEX("tdsqlite3CodecAttach: left database mutex %p\n", db->mutex);
}
return SQLITE_OK;
}
-SQLITE_API void sqlite3_activate_see(const char* in) {
- /* do nothing, security enhancements are always active */
-}
-
-static int sqlcipher_find_db_index(sqlite3 *db, const char *zDb) {
+int sqlcipher_find_db_index(tdsqlite3 *db, const char *zDb) {
int db_index;
if(zDb == NULL){
return 0;
@@ -18305,27 +22170,31 @@ static int sqlcipher_find_db_index(sqlite3 *db, const char *zDb) {
return 0;
}
-SQLITE_API int sqlite3_key(sqlite3 *db, const void *pKey, int nKey) {
- CODEC_TRACE(("sqlite3_key entered: db=%p pKey=%s nKey=%d\n", db, (char *)pKey, nKey));
- return sqlite3_key_v2(db, "main", pKey, nKey);
+SQLITE_API void tdsqlite3_activate_see(const char* in) {
+ /* do nothing, security enhancements are always active */
}
-SQLITE_API int sqlite3_key_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) {
- CODEC_TRACE(("sqlite3_key_v2: entered db=%p zDb=%s pKey=%s nKey=%d\n", db, zDb, (char *)pKey, nKey));
+SQLITE_API int tdsqlite3_key(tdsqlite3 *db, const void *pKey, int nKey) {
+ CODEC_TRACE("tdsqlite3_key entered: db=%p pKey=%s nKey=%d\n", db, (char *)pKey, nKey);
+ return tdsqlite3_key_v2(db, "main", pKey, nKey);
+}
+
+SQLITE_API int tdsqlite3_key_v2(tdsqlite3 *db, const char *zDb, const void *pKey, int nKey) {
+ CODEC_TRACE("tdsqlite3_key_v2: entered db=%p zDb=%s pKey=%s nKey=%d\n", db, zDb, (char *)pKey, nKey);
/* attach key if db and pKey are not null and nKey is > 0 */
if(db && pKey && nKey) {
int db_index = sqlcipher_find_db_index(db, zDb);
- return sqlite3CodecAttach(db, db_index, pKey, nKey);
+ return tdsqlite3CodecAttach(db, db_index, pKey, nKey);
}
return SQLITE_ERROR;
}
-SQLITE_API int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) {
- CODEC_TRACE(("sqlite3_rekey entered: db=%p pKey=%s nKey=%d\n", db, (char *)pKey, nKey));
- return sqlite3_rekey_v2(db, "main", pKey, nKey);
+SQLITE_API int tdsqlite3_rekey(tdsqlite3 *db, const void *pKey, int nKey) {
+ CODEC_TRACE("tdsqlite3_rekey entered: db=%p pKey=%s nKey=%d\n", db, (char *)pKey, nKey);
+ return tdsqlite3_rekey_v2(db, "main", pKey, nKey);
}
-/* sqlite3_rekey_v2
+/* tdsqlite3_rekey_v2
** Given a database, this will reencrypt the database using a new key.
** There is only one possible modes of operation - to encrypt a database
** that is already encrpyted. If the database is not already encrypted
@@ -18335,12 +22204,12 @@ SQLITE_API int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) {
** 2. If there is NOT already a key present do nothing
** 3. If there is a key present, re-encrypt the database with the new key
*/
-SQLITE_API int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey, int nKey) {
- CODEC_TRACE(("sqlite3_rekey_v2: entered db=%p zDb=%s pKey=%s, nKey=%d\n", db, zDb, (char *)pKey, nKey));
+SQLITE_API int tdsqlite3_rekey_v2(tdsqlite3 *db, const char *zDb, const void *pKey, int nKey) {
+ CODEC_TRACE("tdsqlite3_rekey_v2: entered db=%p zDb=%s pKey=%s, nKey=%d\n", db, zDb, (char *)pKey, nKey);
if(db && pKey && nKey) {
int db_index = sqlcipher_find_db_index(db, zDb);
struct Db *pDb = &db->aDb[db_index];
- CODEC_TRACE(("sqlite3_rekey_v2: database pDb=%p db_index:%d\n", pDb, db_index));
+ CODEC_TRACE("tdsqlite3_rekey_v2: database pDb=%p db_index:%d\n", pDb, db_index);
if(pDb->pBt) {
codec_ctx *ctx;
int rc, page_count;
@@ -18348,15 +22217,17 @@ SQLITE_API int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey,
PgHdr *page;
Pager *pPager = pDb->pBt->pBt->pPager;
- sqlite3pager_get_codec(pDb->pBt->pBt->pPager, (void **) &ctx);
+ ctx = (codec_ctx*) tdsqlite3PagerGetCodec(pDb->pBt->pBt->pPager);
if(ctx == NULL) {
/* there was no codec attached to this database, so this should do nothing! */
- CODEC_TRACE(("sqlite3_rekey_v2: no codec attached to db, exiting\n"));
+ CODEC_TRACE("tdsqlite3_rekey_v2: no codec attached to db, exiting\n");
return SQLITE_OK;
}
- sqlite3_mutex_enter(db->mutex);
+ CODEC_TRACE_MUTEX("tdsqlite3_rekey_v2: entering database mutex %p\n", db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
+ CODEC_TRACE_MUTEX("tdsqlite3_rekey_v2: entered database mutex %p\n", db->mutex);
codec_set_pass_key(db, db_index, pKey, nKey, CIPHER_WRITE_CTX);
@@ -18366,52 +22237,56 @@ SQLITE_API int sqlite3_rekey_v2(sqlite3 *db, const char *zDb, const void *pKey,
** 3. If that goes ok then commit and put ctx->rekey into ctx->key
** note: don't deallocate rekey since it may be used in a subsequent iteration
*/
- rc = sqlite3BtreeBeginTrans(pDb->pBt, 1); /* begin write transaction */
- sqlite3PagerPagecount(pPager, &page_count);
+ rc = tdsqlite3BtreeBeginTrans(pDb->pBt, 1, 0); /* begin write transaction */
+ tdsqlite3PagerPagecount(pPager, &page_count);
for(pgno = 1; rc == SQLITE_OK && pgno <= (unsigned int)page_count; pgno++) { /* pgno's start at 1 see pager.c:pagerAcquire */
- if(!sqlite3pager_is_mj_pgno(pPager, pgno)) { /* skip this page (see pager.c:pagerAcquire for reasoning) */
- rc = sqlite3PagerGet(pPager, pgno, &page, 0);
+ if(!tdsqlite3pager_is_mj_pgno(pPager, pgno)) { /* skip this page (see pager.c:pagerAcquire for reasoning) */
+ rc = tdsqlite3PagerGet(pPager, pgno, &page, 0);
if(rc == SQLITE_OK) { /* write page see pager_incr_changecounter for example */
- rc = sqlite3PagerWrite(page);
+ rc = tdsqlite3PagerWrite(page);
if(rc == SQLITE_OK) {
- sqlite3PagerUnref(page);
+ tdsqlite3PagerUnref(page);
} else {
- CODEC_TRACE(("sqlite3_rekey_v2: error %d occurred writing page %d\n", rc, pgno));
+ CODEC_TRACE("tdsqlite3_rekey_v2: error %d occurred writing page %d\n", rc, pgno);
}
} else {
- CODEC_TRACE(("sqlite3_rekey_v2: error %d occurred getting page %d\n", rc, pgno));
+ CODEC_TRACE("tdsqlite3_rekey_v2: error %d occurred getting page %d\n", rc, pgno);
}
}
}
/* if commit was successful commit and copy the rekey data to current key, else rollback to release locks */
if(rc == SQLITE_OK) {
- CODEC_TRACE(("sqlite3_rekey_v2: committing\n"));
- rc = sqlite3BtreeCommit(pDb->pBt);
+ CODEC_TRACE("tdsqlite3_rekey_v2: committing\n");
+ rc = tdsqlite3BtreeCommit(pDb->pBt);
sqlcipher_codec_key_copy(ctx, CIPHER_WRITE_CTX);
} else {
- CODEC_TRACE(("sqlite3_rekey_v2: rollback\n"));
- sqlite3BtreeRollback(pDb->pBt, SQLITE_ABORT_ROLLBACK, 0);
+ CODEC_TRACE("tdsqlite3_rekey_v2: rollback\n");
+ tdsqlite3BtreeRollback(pDb->pBt, SQLITE_ABORT_ROLLBACK, 0);
}
- sqlite3_mutex_leave(db->mutex);
+ CODEC_TRACE_MUTEX("tdsqlite3_rekey_v2: leaving database mutex %p\n", db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
+ CODEC_TRACE_MUTEX("tdsqlite3_rekey_v2: left database mutex %p\n", db->mutex);
}
return SQLITE_OK;
}
return SQLITE_ERROR;
}
-SQLITE_PRIVATE void sqlite3CodecGetKey(sqlite3* db, int nDb, void **zKey, int *nKey) {
+SQLITE_PRIVATE void tdsqlite3CodecGetKey(tdsqlite3* db, int nDb, void **zKey, int *nKey) {
struct Db *pDb = &db->aDb[nDb];
- CODEC_TRACE(("sqlite3CodecGetKey: entered db=%p, nDb=%d\n", db, nDb));
+ CODEC_TRACE("tdsqlite3CodecGetKey: entered db=%p, nDb=%d\n", db, nDb);
if( pDb->pBt ) {
- codec_ctx *ctx;
- sqlite3pager_get_codec(pDb->pBt->pBt->pPager, (void **) &ctx);
+ codec_ctx *ctx = (codec_ctx*) tdsqlite3PagerGetCodec(pDb->pBt->pBt->pPager);
+
if(ctx) {
- if(sqlcipher_codec_get_store_pass(ctx) == 1) {
+ /* pass back the keyspec from the codec, unless PRAGMA cipher_store_pass
+ is set or keyspec has not yet been derived, in which case pass
+ back the password key material */
+ sqlcipher_codec_get_keyspec(ctx, zKey, nKey);
+ if(sqlcipher_codec_get_store_pass(ctx) == 1 || *zKey == NULL) {
sqlcipher_codec_get_pass(ctx, zKey, nKey);
- } else {
- sqlcipher_codec_get_keyspec(ctx, zKey, nKey);
}
} else {
*zKey = NULL;
@@ -18442,11 +22317,11 @@ SQLITE_PRIVATE void sqlite3CodecGetKey(sqlite3* db, int nDb, void **zKey, int *n
**
** Based on vacuumFinalize from vacuum.c
*/
-static int sqlcipher_finalize(sqlite3 *db, sqlite3_stmt *pStmt, char **pzErrMsg){
+static int sqlcipher_finalize(tdsqlite3 *db, tdsqlite3_stmt *pStmt, char **pzErrMsg){
int rc;
- rc = sqlite3VdbeFinalize((Vdbe*)pStmt);
+ rc = tdsqlite3VdbeFinalize((Vdbe*)pStmt);
if( rc ){
- sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db));
+ tdsqlite3SetString(pzErrMsg, db, tdsqlite3_errmsg(db));
}
return rc;
}
@@ -18456,17 +22331,17 @@ static int sqlcipher_finalize(sqlite3 *db, sqlite3_stmt *pStmt, char **pzErrMsg)
**
** Based on execSql from vacuum.c
*/
-static int sqlcipher_execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){
- sqlite3_stmt *pStmt;
+static int sqlcipher_execSql(tdsqlite3 *db, char **pzErrMsg, const char *zSql){
+ tdsqlite3_stmt *pStmt;
VVA_ONLY( int rc; )
if( !zSql ){
return SQLITE_NOMEM;
}
- if( SQLITE_OK!=sqlite3_prepare(db, zSql, -1, &pStmt, 0) ){
- sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db));
- return sqlite3_errcode(db);
+ if( SQLITE_OK!=tdsqlite3_prepare(db, zSql, -1, &pStmt, 0) ){
+ tdsqlite3SetString(pzErrMsg, db, tdsqlite3_errmsg(db));
+ return tdsqlite3_errcode(db);
}
- VVA_ONLY( rc = ) sqlite3_step(pStmt);
+ VVA_ONLY( rc = ) tdsqlite3_step(pStmt);
assert( rc!=SQLITE_ROW );
return sqlcipher_finalize(db, pStmt, pzErrMsg);
}
@@ -18477,15 +22352,15 @@ static int sqlcipher_execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){
**
** Based on execExecSql from vacuum.c
*/
-static int sqlcipher_execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql){
- sqlite3_stmt *pStmt;
+static int sqlcipher_execExecSql(tdsqlite3 *db, char **pzErrMsg, const char *zSql){
+ tdsqlite3_stmt *pStmt;
int rc;
- rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
+ rc = tdsqlite3_prepare(db, zSql, -1, &pStmt, 0);
if( rc!=SQLITE_OK ) return rc;
- while( SQLITE_ROW==sqlite3_step(pStmt) ){
- rc = sqlcipher_execSql(db, pzErrMsg, (char*)sqlite3_column_text(pStmt, 0));
+ while( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ rc = sqlcipher_execSql(db, pzErrMsg, (char*)tdsqlite3_column_text(pStmt, 0));
if( rc!=SQLITE_OK ){
sqlcipher_finalize(db, pStmt, pzErrMsg);
return rc;
@@ -18498,120 +22373,135 @@ static int sqlcipher_execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql)
/*
* copy database and schema from the main database to an attached database
*
- * Based on sqlite3RunVacuum from vacuum.c
-*/
-void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **argv) {
- sqlite3 *db = sqlite3_context_db_handle(context);
- const char* attachedDb = (const char*) sqlite3_value_text(argv[0]);
- int saved_flags; /* Saved value of the db->flags */
- int saved_nChange; /* Saved value of db->nChange */
- int saved_nTotalChange; /* Saved value of db->nTotalChange */
- int (*saved_xTrace)(u32,void*,void*,void*); /* Saved db->xTrace */
+ * Based on tdsqlite3RunVacuum from vacuum.c
+*/
+void sqlcipher_exportFunc(tdsqlite3_context *context, int argc, tdsqlite3_value **argv) {
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
+ const char* targetDb, *sourceDb;
+ int targetDb_idx = 0;
+ u64 saved_flags = db->flags; /* Saved value of the db->flags */
+ u32 saved_mDbFlags = db->mDbFlags; /* Saved value of the db->mDbFlags */
+ int saved_nChange = db->nChange; /* Saved value of db->nChange */
+ int saved_nTotalChange = db->nTotalChange; /* Saved value of db->nTotalChange */
+ u8 saved_mTrace = db->mTrace; /* Saved value of db->mTrace */
+ int (*saved_xTrace)(u32,void*,void*,void*) = db->xTrace; /* Saved db->xTrace */
int rc = SQLITE_OK; /* Return code from service routines */
char *zSql = NULL; /* SQL statements */
char *pzErrMsg = NULL;
-
- saved_flags = db->flags;
- saved_nChange = db->nChange;
- saved_nTotalChange = db->nTotalChange;
- saved_xTrace = db->xTrace;
- db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_PreferBuiltin;
- db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder);
+
+ if(argc != 1 && argc != 2) {
+ rc = SQLITE_ERROR;
+ pzErrMsg = tdsqlite3_mprintf("invalid number of arguments (%d) passed to sqlcipher_export", argc);
+ goto end_of_export;
+ }
+
+ targetDb = (const char*) tdsqlite3_value_text(argv[0]);
+ sourceDb = (argc == 2) ? (char *) tdsqlite3_value_text(argv[1]) : "main";
+
+ /* if the name of the target is not main, but the index returned is zero
+ there is a mismatch and we should not proceed */
+ targetDb_idx = sqlcipher_find_db_index(db, targetDb);
+ if(targetDb_idx == 0 && tdsqlite3StrICmp("main", targetDb) != 0) {
+ rc = SQLITE_ERROR;
+ pzErrMsg = tdsqlite3_mprintf("unknown database %s", targetDb);
+ goto end_of_export;
+ }
+ db->init.iDb = targetDb_idx;
+
+ db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks;
+ db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum;
+ db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_Defensive | SQLITE_CountRows);
db->xTrace = 0;
+ db->mTrace = 0;
/* Query the schema of the main database. Create a mirror schema
** in the temporary database.
*/
- zSql = sqlite3_mprintf(
- "SELECT 'CREATE TABLE %s.' || substr(sql,14) "
- " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'"
+ zSql = tdsqlite3_mprintf(
+ "SELECT sql "
+ " FROM %s.sqlite_master WHERE type='table' AND name!='sqlite_sequence'"
" AND rootpage>0"
- , attachedDb);
+ , sourceDb);
rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
if( rc!=SQLITE_OK ) goto end_of_export;
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
- zSql = sqlite3_mprintf(
- "SELECT 'CREATE INDEX %s.' || substr(sql,14)"
- " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %%' "
- , attachedDb);
+ zSql = tdsqlite3_mprintf(
+ "SELECT sql "
+ " FROM %s.sqlite_master WHERE sql LIKE 'CREATE INDEX %%' "
+ , sourceDb);
rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
if( rc!=SQLITE_OK ) goto end_of_export;
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
- zSql = sqlite3_mprintf(
- "SELECT 'CREATE UNIQUE INDEX %s.' || substr(sql,21) "
- " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %%'"
- , attachedDb);
+ zSql = tdsqlite3_mprintf(
+ "SELECT sql "
+ " FROM %s.sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %%'"
+ , sourceDb);
rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
if( rc!=SQLITE_OK ) goto end_of_export;
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
/* Loop through the tables in the main database. For each, do
** an "INSERT INTO rekey_db.xxx SELECT * FROM main.xxx;" to copy
** the contents to the temporary database.
*/
- zSql = sqlite3_mprintf(
+ zSql = tdsqlite3_mprintf(
"SELECT 'INSERT INTO %s.' || quote(name) "
- "|| ' SELECT * FROM main.' || quote(name) || ';'"
- "FROM main.sqlite_master "
+ "|| ' SELECT * FROM %s.' || quote(name) || ';'"
+ "FROM %s.sqlite_master "
"WHERE type = 'table' AND name!='sqlite_sequence' "
" AND rootpage>0"
- , attachedDb);
+ , targetDb, sourceDb, sourceDb);
rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
if( rc!=SQLITE_OK ) goto end_of_export;
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
- /* Copy over the sequence table
+ /* Copy over the contents of the sequence table
*/
- zSql = sqlite3_mprintf(
- "SELECT 'DELETE FROM %s.' || quote(name) || ';' "
- "FROM %s.sqlite_master WHERE name='sqlite_sequence' "
- , attachedDb, attachedDb);
- rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
- if( rc!=SQLITE_OK ) goto end_of_export;
- sqlite3_free(zSql);
-
- zSql = sqlite3_mprintf(
+ zSql = tdsqlite3_mprintf(
"SELECT 'INSERT INTO %s.' || quote(name) "
- "|| ' SELECT * FROM main.' || quote(name) || ';' "
+ "|| ' SELECT * FROM %s.' || quote(name) || ';' "
"FROM %s.sqlite_master WHERE name=='sqlite_sequence';"
- , attachedDb, attachedDb);
+ , targetDb, sourceDb, targetDb);
rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql);
if( rc!=SQLITE_OK ) goto end_of_export;
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
/* Copy the triggers, views, and virtual tables from the main database
** over to the temporary database. None of these objects has any
** associated storage, so all we have to do is copy their entries
** from the SQLITE_MASTER table.
*/
- zSql = sqlite3_mprintf(
+ zSql = tdsqlite3_mprintf(
"INSERT INTO %s.sqlite_master "
" SELECT type, name, tbl_name, rootpage, sql"
- " FROM main.sqlite_master"
+ " FROM %s.sqlite_master"
" WHERE type='view' OR type='trigger'"
" OR (type='table' AND rootpage=0)"
- , attachedDb);
+ , targetDb, sourceDb);
rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execSql(db, &pzErrMsg, zSql);
if( rc!=SQLITE_OK ) goto end_of_export;
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
zSql = NULL;
end_of_export:
+ db->init.iDb = 0;
db->flags = saved_flags;
+ db->mDbFlags = saved_mDbFlags;
db->nChange = saved_nChange;
db->nTotalChange = saved_nTotalChange;
db->xTrace = saved_xTrace;
+ db->mTrace = saved_mTrace;
- sqlite3_free(zSql);
+ if(zSql) tdsqlite3_free(zSql);
if(rc) {
if(pzErrMsg != NULL) {
- sqlite3_result_error(context, pzErrMsg, -1);
- sqlite3DbFree(db, pzErrMsg);
+ tdsqlite3_result_error(context, pzErrMsg, -1);
+ tdsqlite3DbFree(db, pzErrMsg);
} else {
- sqlite3_result_error(context, sqlite3ErrStr(rc), -1);
+ tdsqlite3_result_error(context, tdsqlite3ErrStr(rc), -1);
}
}
}
@@ -18656,146 +22546,112 @@ end_of_export:
/* BEGIN SQLCIPHER */
#ifdef SQLITE_HAS_CODEC
-/* #include "sqliteInt.h" */
-/* #include "btreeInt.h" */
-/************** Include sqlcipher.h in the middle of crypto_impl.c ***********/
-/************** Begin file sqlcipher.h ***************************************/
-/*
-** SQLCipher
-** sqlcipher.h developed by Stephen Lombardo (Zetetic LLC)
-** sjlombardo at zetetic dot net
-** http://zetetic.net
-**
-** Copyright (c) 2008, ZETETIC LLC
-** All rights reserved.
-**
-** Redistribution and use in source and binary forms, with or without
-** modification, are permitted provided that the following conditions are met:
-** * Redistributions of source code must retain the above copyright
-** notice, this list of conditions and the following disclaimer.
-** * Redistributions in binary form must reproduce the above copyright
-** notice, this list of conditions and the following disclaimer in the
-** documentation and/or other materials provided with the distribution.
-** * Neither the name of the ZETETIC LLC nor the
-** names of its contributors may be used to endorse or promote products
-** derived from this software without specific prior written permission.
-**
-** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
-** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
-** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-**
-*/
-/* BEGIN SQLCIPHER */
-#ifdef SQLITE_HAS_CODEC
-#ifndef SQLCIPHER_H
-#define SQLCIPHER_H
-
-
-typedef struct {
- int (*activate)(void *ctx);
- int (*deactivate)(void *ctx);
- const char* (*get_provider_name)(void *ctx);
- int (*add_random)(void *ctx, void *buffer, int length);
- int (*random)(void *ctx, void *buffer, int length);
- int (*hmac)(void *ctx, unsigned char *hmac_key, int key_sz, unsigned char *in, int in_sz, unsigned char *in2, int in2_sz, unsigned char *out);
- int (*kdf)(void *ctx, const unsigned char *pass, int pass_sz, unsigned char* salt, int salt_sz, int workfactor, int key_sz, unsigned char *key);
- int (*cipher)(void *ctx, int mode, unsigned char *key, int key_sz, unsigned char *iv, unsigned char *in, int in_sz, unsigned char *out);
- int (*set_cipher)(void *ctx, const char *cipher_name);
- const char* (*get_cipher)(void *ctx);
- int (*get_key_sz)(void *ctx);
- int (*get_iv_sz)(void *ctx);
- int (*get_block_sz)(void *ctx);
- int (*get_hmac_sz)(void *ctx);
- int (*ctx_copy)(void *target_ctx, void *source_ctx);
- int (*ctx_cmp)(void *c1, void *c2);
- int (*ctx_init)(void **ctx);
- int (*ctx_free)(void **ctx);
- int (*fips_status)(void *ctx);
- const char* (*get_provider_version)(void *ctx);
-} sqlcipher_provider;
-
-/* utility functions */
-void sqlcipher_free(void *ptr, int sz);
-void* sqlcipher_malloc(int sz);
-void* sqlcipher_memset(void *v, unsigned char value, int len);
-int sqlcipher_ismemset(const void *v, unsigned char value, int len);
-int sqlcipher_memcmp(const void *v0, const void *v1, int len);
-void sqlcipher_free(void *, int);
-
-/* provider interfaces */
-int sqlcipher_register_provider(sqlcipher_provider *p);
-sqlcipher_provider* sqlcipher_get_provider();
-
-#endif
-#endif
-/* END SQLCIPHER */
-
-
-/************** End of sqlcipher.h *******************************************/
-/************** Continuing where we left off in crypto_impl.c ****************/
+/* #include "sqlcipher.h" */
/* #include "crypto.h" */
-#ifndef OMIT_MEMLOCK
+// #ifndef OMIT_MEMLOCK
#if defined(__unix__) || defined(__APPLE__) || defined(_AIX)
+#include <errno.h>
+#include <unistd.h>
+#include <sys/resource.h>
#include <sys/mman.h>
#elif defined(_WIN32)
-# include <windows.h>
-#endif
+#include <windows.h>
#endif
+// #endif
-/* the default implementation of SQLCipher uses a cipher_ctx
- to keep track of read / write state separately. The following
- struct and associated functions are defined here */
-typedef struct {
- int store_pass;
- int derive_key;
- int kdf_iter;
- int fast_kdf_iter;
- int key_sz;
- int iv_sz;
- int block_sz;
- int pass_sz;
- int reserve_sz;
- int hmac_sz;
- int keyspec_sz;
- unsigned int flags;
- unsigned char *key;
- unsigned char *hmac_key;
- unsigned char *pass;
- char *keyspec;
- sqlcipher_provider *provider;
- void *provider_ctx;
-} cipher_ctx;
+static volatile unsigned int default_flags = DEFAULT_CIPHER_FLAGS;
+static volatile unsigned char hmac_salt_mask = HMAC_SALT_MASK;
-static unsigned int default_flags = DEFAULT_CIPHER_FLAGS;
-static unsigned char hmac_salt_mask = HMAC_SALT_MASK;
-static int default_kdf_iter = PBKDF2_ITER;
-static int default_page_size = 1024;
-static unsigned int sqlcipher_activate_count = 0;
-static sqlite3_mutex* sqlcipher_provider_mutex = NULL;
+#include <openssl/opensslv.h>
+
+#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10000000L
+static volatile int default_kdf_iter = 64000;
+static volatile int default_page_size = 1024;
+static volatile int default_plaintext_header_sz = 0;
+static volatile int default_hmac_algorithm = SQLCIPHER_HMAC_SHA1;
+static volatile int default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA1;
+#else
+static volatile int default_kdf_iter = PBKDF2_ITER;
+static volatile int default_page_size = 4096;
+static volatile int default_plaintext_header_sz = 0;
+static volatile int default_hmac_algorithm = SQLCIPHER_HMAC_SHA512;
+static volatile int default_kdf_algorithm = SQLCIPHER_PBKDF2_HMAC_SHA512;
+#endif
+static volatile int mem_security_on = 1;
+static volatile int mem_security_initialized = 0;
+static volatile int mem_security_activated = 0;
+static volatile unsigned int sqlcipher_activate_count = 0;
+static volatile tdsqlite3_mem_methods default_mem_methods;
static sqlcipher_provider *default_provider = NULL;
-struct codec_ctx {
- int kdf_salt_sz;
- int page_sz;
- unsigned char *kdf_salt;
- unsigned char *hmac_kdf_salt;
- unsigned char *buffer;
- Btree *pBt;
- cipher_ctx *read_ctx;
- cipher_ctx *write_ctx;
- unsigned int skip_read_hmac;
- unsigned int need_kdf_salt;
+static tdsqlite3_mutex* sqlcipher_static_mutex[SQLCIPHER_MUTEX_COUNT];
+
+tdsqlite3_mutex* sqlcipher_mutex(int mutex) {
+ if(mutex < 0 || mutex >= SQLCIPHER_MUTEX_COUNT) return NULL;
+ return sqlcipher_static_mutex[mutex];
+}
+
+static int sqlcipher_mem_init(void *pAppData) {
+ return default_mem_methods.xInit(pAppData);
+}
+static void sqlcipher_mem_shutdown(void *pAppData) {
+ default_mem_methods.xShutdown(pAppData);
+}
+static void *sqlcipher_mem_malloc(int n) {
+ void *ptr = default_mem_methods.xMalloc(n);
+ if(mem_security_on) {
+ CODEC_TRACE_MEMORY("sqlcipher_mem_malloc: calling sqlcipher_mlock(%p,%d)\n", ptr, n);
+ sqlcipher_mlock(ptr, n);
+ if(!mem_security_activated) mem_security_activated = 1;
+ }
+ return ptr;
+}
+static int sqlcipher_mem_size(void *p) {
+ return default_mem_methods.xSize(p);
+}
+static void sqlcipher_mem_free(void *p) {
+ int sz;
+ if(mem_security_on) {
+ sz = sqlcipher_mem_size(p);
+ CODEC_TRACE_MEMORY("sqlcipher_mem_free: calling sqlcipher_memset(%p,0,%d) and sqlcipher_munlock(%p, %d) \n", p, sz, p, sz);
+ sqlcipher_memset(p, 0, sz);
+ sqlcipher_munlock(p, sz);
+ if(!mem_security_activated) mem_security_activated = 1;
+ }
+ default_mem_methods.xFree(p);
+}
+static void *sqlcipher_mem_realloc(void *p, int n) {
+ return default_mem_methods.xRealloc(p, n);
+}
+static int sqlcipher_mem_roundup(int n) {
+ return default_mem_methods.xRoundup(n);
+}
+
+static tdsqlite3_mem_methods sqlcipher_mem_methods = {
+ sqlcipher_mem_malloc,
+ sqlcipher_mem_free,
+ sqlcipher_mem_realloc,
+ sqlcipher_mem_size,
+ sqlcipher_mem_roundup,
+ sqlcipher_mem_init,
+ sqlcipher_mem_shutdown,
+ 0
};
+void sqlcipher_init_memmethods() {
+ if(mem_security_initialized) return;
+ if(tdsqlite3_config(SQLITE_CONFIG_GETMALLOC, &default_mem_methods) != SQLITE_OK ||
+ tdsqlite3_config(SQLITE_CONFIG_MALLOC, &sqlcipher_mem_methods) != SQLITE_OK) {
+ mem_security_on = mem_security_activated = 0;
+ }
+ mem_security_initialized = 1;
+}
+
int sqlcipher_register_provider(sqlcipher_provider *p) {
- sqlite3_mutex_enter(sqlcipher_provider_mutex);
+ CODEC_TRACE_MUTEX("sqlcipher_register_provider: entering SQLCIPHER_MUTEX_PROVIDER\n");
+ tdsqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER));
+ CODEC_TRACE_MUTEX("sqlcipher_register_provider: entered SQLCIPHER_MUTEX_PROVIDER\n");
+
if(default_provider != NULL && default_provider != p) {
/* only free the current registerd provider if it has been initialized
and it isn't a pointer to the same provider passed to the function
@@ -18803,7 +22659,10 @@ int sqlcipher_register_provider(sqlcipher_provider *p) {
sqlcipher_free(default_provider, sizeof(sqlcipher_provider));
}
default_provider = p;
- sqlite3_mutex_leave(sqlcipher_provider_mutex);
+ CODEC_TRACE_MUTEX("sqlcipher_register_provider: leaving SQLCIPHER_MUTEX_PROVIDER\n");
+ tdsqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER));
+ CODEC_TRACE_MUTEX("sqlcipher_register_provider: left SQLCIPHER_MUTEX_PROVIDER\n");
+
return SQLITE_OK;
}
@@ -18814,70 +22673,58 @@ sqlcipher_provider* sqlcipher_get_provider() {
return default_provider;
}
-void sqlcipher_activate() {
- sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
-
- if(sqlcipher_provider_mutex == NULL) {
- /* allocate a new mutex to guard access to the provider */
- sqlcipher_provider_mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
- }
-
- /* check to see if there is a provider registered at this point
- if there no provider registered at this point, register the
- default provider */
- if(sqlcipher_get_provider() == NULL) {
- sqlcipher_provider *p = sqlcipher_malloc(sizeof(sqlcipher_provider));
-#if defined (SQLCIPHER_CRYPTO_CC)
- extern int sqlcipher_cc_setup(sqlcipher_provider *p);
- sqlcipher_cc_setup(p);
-#elif defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT)
- extern int sqlcipher_ltc_setup(sqlcipher_provider *p);
- sqlcipher_ltc_setup(p);
-#elif defined (SQLCIPHER_CRYPTO_OPENSSL)
- extern int sqlcipher_openssl_setup(sqlcipher_provider *p);
- sqlcipher_openssl_setup(p);
-#else
-#error "NO DEFAULT SQLCIPHER CRYPTO PROVIDER DEFINED"
-#endif
- sqlcipher_register_provider(p);
- }
-
- sqlcipher_activate_count++; /* increment activation count */
-
- sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
-}
-
void sqlcipher_deactivate() {
- sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ CODEC_TRACE_MUTEX("sqlcipher_deactivate: entering static master mutex\n");
+ tdsqlite3_mutex_enter(tdsqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ CODEC_TRACE_MUTEX("sqlcipher_deactivate: entered static master mutex\n");
+
sqlcipher_activate_count--;
/* if no connections are using sqlcipher, cleanup globals */
if(sqlcipher_activate_count < 1) {
- sqlite3_mutex_enter(sqlcipher_provider_mutex);
+
+ CODEC_TRACE_MUTEX("sqlcipher_deactivate: entering SQLCIPHER_MUTEX_PROVIDER\n");
+ tdsqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER));
+ CODEC_TRACE_MUTEX("sqlcipher_deactivate: entered SQLCIPHER_MUTEX_PROVIDER\n");
+
if(default_provider != NULL) {
sqlcipher_free(default_provider, sizeof(sqlcipher_provider));
default_provider = NULL;
}
- sqlite3_mutex_leave(sqlcipher_provider_mutex);
-
- /* last connection closed, free provider mutex*/
- sqlite3_mutex_free(sqlcipher_provider_mutex);
- sqlcipher_provider_mutex = NULL;
+ CODEC_TRACE_MUTEX("sqlcipher_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER\n");
+ tdsqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER));
+ CODEC_TRACE_MUTEX("sqlcipher_deactivate: left SQLCIPHER_MUTEX_PROVIDER\n");
+
+#ifdef SQLCIPHER_EXT
+ sqlcipher_ext_provider_destroy();
+#endif
+
+ /* last connection closed, free mutexes */
+ if(sqlcipher_activate_count == 0) {
+ int i;
+ for(i = 0; i < SQLCIPHER_MUTEX_COUNT; i++) {
+ tdsqlite3_mutex_free(sqlcipher_static_mutex[i]);
+ }
+ }
sqlcipher_activate_count = 0; /* reset activation count */
}
- sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+
+ CODEC_TRACE_MUTEX("sqlcipher_deactivate: leaving static master mutex\n");
+ tdsqlite3_mutex_leave(tdsqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ CODEC_TRACE_MUTEX("sqlcipher_deactivate: left static master mutex\n");
}
/* constant time memset using volitile to avoid having the memset
optimized out by the compiler.
Note: As suggested by Joachim Schipper (joachim.schipper@fox-it.com)
*/
-void* sqlcipher_memset(void *v, unsigned char value, int len) {
- int i = 0;
+void* sqlcipher_memset(void *v, unsigned char value, u64 len) {
+ u64 i = 0;
volatile unsigned char *a = v;
if (v == NULL) return v;
+ CODEC_TRACE_MEMORY("sqlcipher_memset: setting %p[0-%llu]=%d)\n", a, len, value);
for(i = 0; i < len; i++) {
a[i] = value;
}
@@ -18888,9 +22735,9 @@ void* sqlcipher_memset(void *v, unsigned char value, int len) {
/* constant time memory check tests every position of a memory segement
matches a single value (i.e. the memory is all zeros)
returns 0 if match, 1 of no match */
-int sqlcipher_ismemset(const void *v, unsigned char value, int len) {
+int sqlcipher_ismemset(const void *v, unsigned char value, u64 len) {
const unsigned char *a = v;
- int i = 0, result = 0;
+ u64 i = 0, result = 0;
for(i = 0; i < len; i++) {
result |= a[i] ^ value;
@@ -18912,54 +22759,98 @@ int sqlcipher_memcmp(const void *v0, const void *v1, int len) {
return (result != 0);
}
-/**
- * Free and wipe memory. Uses SQLites internal sqlite3_free so that memory
- * can be countend and memory leak detection works in the test suite.
- * If ptr is not null memory will be freed.
- * If sz is greater than zero, the memory will be overwritten with zero before it is freed
- * If sz is > 0, and not compiled with OMIT_MEMLOCK, system will attempt to unlock the
- * memory segment so it can be paged
- */
-void sqlcipher_free(void *ptr, int sz) {
- if(ptr) {
- if(sz > 0) {
- sqlcipher_memset(ptr, 0, sz);
+void sqlcipher_mlock(void *ptr, u64 sz) {
#ifndef OMIT_MEMLOCK
#if defined(__unix__) || defined(__APPLE__)
- munlock(ptr, sz);
+ int rc;
+ unsigned long pagesize = sysconf(_SC_PAGESIZE);
+ unsigned long offset = (unsigned long) ptr % pagesize;
+
+ if(ptr == NULL || sz == 0) return;
+
+ CODEC_TRACE_MEMORY("sqlcipher_mem_lock: calling mlock(%p,%lu); _SC_PAGESIZE=%lu\n", ptr - offset, sz + offset, pagesize);
+ rc = mlock(ptr - offset, sz + offset);
+ if(rc!=0) {
+ CODEC_TRACE_MEMORY("sqlcipher_mem_lock: mlock(%p,%lu) returned %d errno=%d\n", ptr - offset, sz + offset, rc, errno);
+ }
#elif defined(_WIN32)
#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP))
-VirtualUnlock(ptr, sz);
+ int rc;
+ CODEC_TRACE("sqlcipher_mem_lock: calling VirtualLock(%p,%d)\n", ptr, sz);
+ rc = VirtualLock(ptr, sz);
+ if(rc==0) {
+ CODEC_TRACE("sqlcipher_mem_lock: VirtualLock(%p,%d) returned %d LastError=%d\n", ptr, sz, rc, GetLastError());
+ }
#endif
#endif
#endif
- }
- sqlite3_free(ptr);
- }
}
-/**
- * allocate memory. Uses sqlite's internall malloc wrapper so memory can be
- * reference counted and leak detection works. Unless compiled with OMIT_MEMLOCK
- * attempts to lock the memory pages so sensitive information won't be swapped
- */
-void* sqlcipher_malloc(int sz) {
- void *ptr = sqlite3Malloc(sz);
- sqlcipher_memset(ptr, 0, sz);
+void sqlcipher_munlock(void *ptr, u64 sz) {
#ifndef OMIT_MEMLOCK
- if(ptr) {
#if defined(__unix__) || defined(__APPLE__)
- mlock(ptr, sz);
+ int rc;
+ unsigned long pagesize = sysconf(_SC_PAGESIZE);
+ unsigned long offset = (unsigned long) ptr % pagesize;
+
+ if(ptr == NULL || sz == 0) return;
+
+ CODEC_TRACE_MEMORY("sqlcipher_mem_unlock: calling munlock(%p,%lu)\n", ptr - offset, sz + offset);
+ rc = munlock(ptr - offset, sz + offset);
+ if(rc!=0) {
+ CODEC_TRACE_MEMORY("sqlcipher_mem_unlock: munlock(%p,%lu) returned %d errno=%d\n", ptr - offset, sz + offset, rc, errno);
+ }
#elif defined(_WIN32)
#if !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP || WINAPI_FAMILY == WINAPI_FAMILY_APP))
- VirtualLock(ptr, sz);
+ int rc;
+ CODEC_TRACE("sqlcipher_mem_lock: calling VirtualUnlock(%p,%d)\n", ptr, sz);
+ rc = VirtualUnlock(ptr, sz);
+ if(!rc) {
+ CODEC_TRACE("sqlcipher_mem_unlock: VirtualUnlock(%p,%d) returned %d LastError=%d\n", ptr, sz, rc, GetLastError());
+ }
#endif
#endif
- }
#endif
+}
+
+/**
+ * Free and wipe memory. Uses SQLites internal tdsqlite3_free so that memory
+ * can be countend and memory leak detection works in the test suite.
+ * If ptr is not null memory will be freed.
+ * If sz is greater than zero, the memory will be overwritten with zero before it is freed
+ * If sz is > 0, and not compiled with OMIT_MEMLOCK, system will attempt to unlock the
+ * memory segment so it can be paged
+ */
+void sqlcipher_free(void *ptr, u64 sz) {
+ CODEC_TRACE_MEMORY("sqlcipher_free: calling sqlcipher_memset(%p,0,%llu)\n", ptr, sz);
+ sqlcipher_memset(ptr, 0, sz);
+ sqlcipher_munlock(ptr, sz);
+ tdsqlite3_free(ptr);
+}
+
+/**
+ * allocate memory. Uses sqlite's internall malloc wrapper so memory can be
+ * reference counted and leak detection works. Unless compiled with OMIT_MEMLOCK
+ * attempts to lock the memory pages so sensitive information won't be swapped
+ */
+void* sqlcipher_malloc(u64 sz) {
+ void *ptr;
+ CODEC_TRACE_MEMORY("sqlcipher_malloc: calling tdsqlite3Malloc(%llu)\n", sz);
+ ptr = tdsqlite3Malloc(sz);
+ CODEC_TRACE_MEMORY("sqlcipher_malloc: calling sqlcipher_memset(%p,0,%llu)\n", ptr, sz);
+ sqlcipher_memset(ptr, 0, sz);
+ sqlcipher_mlock(ptr, sz);
return ptr;
}
+char* sqlcipher_version() {
+#ifdef CIPHER_VERSION_QUALIFIER
+ char *version = tdsqlite3_mprintf("%s %s %s", CIPHER_XSTR(CIPHER_VERSION_NUMBER), CIPHER_XSTR(CIPHER_VERSION_QUALIFIER), CIPHER_XSTR(CIPHER_VERSION_BUILD));
+#else
+ char *version = tdsqlite3_mprintf("%s %s", CIPHER_XSTR(CIPHER_VERSION_NUMBER), CIPHER_XSTR(CIPHER_VERSION_BUILD));
+#endif
+ return version;
+}
/**
* Initialize new cipher_ctx struct. This function will allocate memory
@@ -18968,29 +22859,21 @@ void* sqlcipher_malloc(int sz) {
* returns SQLITE_OK if initialization was successful
* returns SQLITE_NOMEM if an error occured allocating memory
*/
-static int sqlcipher_cipher_ctx_init(cipher_ctx **iCtx) {
- int rc;
- cipher_ctx *ctx;
+static int sqlcipher_cipher_ctx_init(codec_ctx *ctx, cipher_ctx **iCtx) {
+ cipher_ctx *c_ctx;
+ CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating context\n");
*iCtx = (cipher_ctx *) sqlcipher_malloc(sizeof(cipher_ctx));
- ctx = *iCtx;
- if(ctx == NULL) return SQLITE_NOMEM;
+ c_ctx = *iCtx;
+ if(c_ctx == NULL) return SQLITE_NOMEM;
- ctx->provider = (sqlcipher_provider *) sqlcipher_malloc(sizeof(sqlcipher_provider));
- if(ctx->provider == NULL) return SQLITE_NOMEM;
+ CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating key\n");
+ c_ctx->key = (unsigned char *) sqlcipher_malloc(ctx->key_sz);
- /* make a copy of the provider to be used for the duration of the context */
- sqlite3_mutex_enter(sqlcipher_provider_mutex);
- memcpy(ctx->provider, default_provider, sizeof(sqlcipher_provider));
- sqlite3_mutex_leave(sqlcipher_provider_mutex);
-
- if((rc = ctx->provider->ctx_init(&ctx->provider_ctx)) != SQLITE_OK) return rc;
- ctx->key = (unsigned char *) sqlcipher_malloc(CIPHER_MAX_KEY_SZ);
- ctx->hmac_key = (unsigned char *) sqlcipher_malloc(CIPHER_MAX_KEY_SZ);
- if(ctx->key == NULL) return SQLITE_NOMEM;
- if(ctx->hmac_key == NULL) return SQLITE_NOMEM;
+ CODEC_TRACE("sqlcipher_cipher_ctx_init: allocating hmac_key\n");
+ c_ctx->hmac_key = (unsigned char *) sqlcipher_malloc(ctx->key_sz);
- /* setup default flags */
- ctx->flags = default_flags;
+ if(c_ctx->key == NULL) return SQLITE_NOMEM;
+ if(c_ctx->hmac_key == NULL) return SQLITE_NOMEM;
return SQLITE_OK;
}
@@ -18998,16 +22881,35 @@ static int sqlcipher_cipher_ctx_init(cipher_ctx **iCtx) {
/**
* Free and wipe memory associated with a cipher_ctx
*/
-static void sqlcipher_cipher_ctx_free(cipher_ctx **iCtx) {
- cipher_ctx *ctx = *iCtx;
- CODEC_TRACE(("cipher_ctx_free: entered iCtx=%p\n", iCtx));
- ctx->provider->ctx_free(&ctx->provider_ctx);
- sqlcipher_free(ctx->provider, sizeof(sqlcipher_provider));
- sqlcipher_free(ctx->key, ctx->key_sz);
- sqlcipher_free(ctx->hmac_key, ctx->key_sz);
- sqlcipher_free(ctx->pass, ctx->pass_sz);
- sqlcipher_free(ctx->keyspec, ctx->keyspec_sz);
- sqlcipher_free(ctx, sizeof(cipher_ctx));
+static void sqlcipher_cipher_ctx_free(codec_ctx* ctx, cipher_ctx **iCtx) {
+ cipher_ctx *c_ctx = *iCtx;
+ CODEC_TRACE("cipher_ctx_free: entered iCtx=%p\n", iCtx);
+ sqlcipher_free(c_ctx->key, ctx->key_sz);
+ sqlcipher_free(c_ctx->hmac_key, ctx->key_sz);
+ sqlcipher_free(c_ctx->pass, c_ctx->pass_sz);
+ sqlcipher_free(c_ctx->keyspec, ctx->keyspec_sz);
+ sqlcipher_free(c_ctx, sizeof(cipher_ctx));
+}
+
+static int sqlcipher_codec_ctx_reserve_setup(codec_ctx *ctx) {
+ int base_reserve = ctx->iv_sz; /* base reserve size will be IV only */
+ int reserve = base_reserve;
+
+ ctx->hmac_sz = ctx->provider->get_hmac_sz(ctx->provider_ctx, ctx->hmac_algorithm);
+
+ if(sqlcipher_codec_ctx_get_use_hmac(ctx))
+ reserve += ctx->hmac_sz; /* if reserve will include hmac, update that size */
+
+ /* calculate the amount of reserve needed in even increments of the cipher block size */
+ reserve = ((reserve % ctx->block_sz) == 0) ? reserve :
+ ((reserve / ctx->block_sz) + 1) * ctx->block_sz;
+
+ CODEC_TRACE("sqlcipher_codec_ctx_reserve_setup: base_reserve=%d block_sz=%d md_size=%d reserve=%d\n",
+ base_reserve, ctx->block_sz, ctx->hmac_sz, reserve);
+
+ ctx->reserve_sz = reserve;
+
+ return SQLITE_OK;
}
/**
@@ -19018,14 +22920,7 @@ static void sqlcipher_cipher_ctx_free(cipher_ctx **iCtx) {
*/
static int sqlcipher_cipher_ctx_cmp(cipher_ctx *c1, cipher_ctx *c2) {
int are_equal = (
- c1->iv_sz == c2->iv_sz
- && c1->kdf_iter == c2->kdf_iter
- && c1->fast_kdf_iter == c2->fast_kdf_iter
- && c1->key_sz == c2->key_sz
- && c1->pass_sz == c2->pass_sz
- && c1->flags == c2->flags
- && c1->hmac_sz == c2->hmac_sz
- && c1->provider->ctx_cmp(c1->provider_ctx, c2->provider_ctx)
+ c1->pass_sz == c2->pass_sz
&& (
c1->pass == c2->pass
|| !sqlcipher_memcmp((const unsigned char*)c1->pass,
@@ -19033,39 +22928,25 @@ static int sqlcipher_cipher_ctx_cmp(cipher_ctx *c1, cipher_ctx *c2) {
c1->pass_sz)
));
- CODEC_TRACE(("sqlcipher_cipher_ctx_cmp: entered \
+ CODEC_TRACE("sqlcipher_cipher_ctx_cmp: entered \
c1=%p c2=%p \
- c1->iv_sz=%d c2->iv_sz=%d \
- c1->kdf_iter=%d c2->kdf_iter=%d \
- c1->fast_kdf_iter=%d c2->fast_kdf_iter=%d \
- c1->key_sz=%d c2->key_sz=%d \
c1->pass_sz=%d c2->pass_sz=%d \
- c1->flags=%d c2->flags=%d \
- c1->hmac_sz=%d c2->hmac_sz=%d \
- c1->provider_ctx=%p c2->provider_ctx=%p \
c1->pass=%p c2->pass=%p \
c1->pass=%s c2->pass=%s \
- provider->ctx_cmp=%d \
sqlcipher_memcmp=%d \
are_equal=%d \
\n",
c1, c2,
- c1->iv_sz, c2->iv_sz,
- c1->kdf_iter, c2->kdf_iter,
- c1->fast_kdf_iter, c2->fast_kdf_iter,
- c1->key_sz, c2->key_sz,
c1->pass_sz, c2->pass_sz,
- c1->flags, c2->flags,
- c1->hmac_sz, c2->hmac_sz,
- c1->provider_ctx, c2->provider_ctx,
c1->pass, c2->pass,
c1->pass, c2->pass,
- c1->provider->ctx_cmp(c1->provider_ctx, c2->provider_ctx),
- sqlcipher_memcmp((const unsigned char*)c1->pass,
- (const unsigned char*)c2->pass,
- c1->pass_sz),
+ (c1->pass == NULL || c2->pass == NULL)
+ ? -1 : sqlcipher_memcmp(
+ (const unsigned char*)c1->pass,
+ (const unsigned char*)c2->pass,
+ c1->pass_sz),
are_equal
- ));
+ );
return !are_equal; /* return 0 if they are the same, 1 otherwise */
}
@@ -19078,38 +22959,30 @@ static int sqlcipher_cipher_ctx_cmp(cipher_ctx *c1, cipher_ctx *c2) {
* returns SQLITE_OK if initialization was successful
* returns SQLITE_NOMEM if an error occured allocating memory
*/
-static int sqlcipher_cipher_ctx_copy(cipher_ctx *target, cipher_ctx *source) {
+static int sqlcipher_cipher_ctx_copy(codec_ctx *ctx, cipher_ctx *target, cipher_ctx *source) {
void *key = target->key;
void *hmac_key = target->hmac_key;
- void *provider = target->provider;
- void *provider_ctx = target->provider_ctx;
- CODEC_TRACE(("sqlcipher_cipher_ctx_copy: entered target=%p, source=%p\n", target, source));
+ CODEC_TRACE("sqlcipher_cipher_ctx_copy: entered target=%p, source=%p\n", target, source);
sqlcipher_free(target->pass, target->pass_sz);
- sqlcipher_free(target->keyspec, target->keyspec_sz);
+ sqlcipher_free(target->keyspec, ctx->keyspec_sz);
memcpy(target, source, sizeof(cipher_ctx));
- target->key = key; //restore pointer to previously allocated key data
- memcpy(target->key, source->key, CIPHER_MAX_KEY_SZ);
-
- target->hmac_key = hmac_key; //restore pointer to previously allocated hmac key data
- memcpy(target->hmac_key, source->hmac_key, CIPHER_MAX_KEY_SZ);
-
- target->provider = provider; // restore pointer to previouly allocated provider;
- memcpy(target->provider, source->provider, sizeof(sqlcipher_provider));
+ target->key = key; /* restore pointer to previously allocated key data */
+ memcpy(target->key, source->key, ctx->key_sz);
- target->provider_ctx = provider_ctx; // restore pointer to previouly allocated provider context;
- target->provider->ctx_copy(target->provider_ctx, source->provider_ctx);
+ target->hmac_key = hmac_key; /* restore pointer to previously allocated hmac key data */
+ memcpy(target->hmac_key, source->hmac_key, ctx->key_sz);
if(source->pass && source->pass_sz) {
target->pass = sqlcipher_malloc(source->pass_sz);
if(target->pass == NULL) return SQLITE_NOMEM;
memcpy(target->pass, source->pass, source->pass_sz);
}
- if(source->keyspec && source->keyspec_sz) {
- target->keyspec = sqlcipher_malloc(source->keyspec_sz);
+ if(source->keyspec) {
+ target->keyspec = sqlcipher_malloc(ctx->keyspec_sz);
if(target->keyspec == NULL) return SQLITE_NOMEM;
- memcpy(target->keyspec, source->keyspec, source->keyspec_sz);
+ memcpy(target->keyspec, source->keyspec, ctx->keyspec_sz);
}
return SQLITE_OK;
}
@@ -19120,33 +22993,28 @@ static int sqlcipher_cipher_ctx_copy(cipher_ctx *target, cipher_ctx *source) {
* returns SQLITE_OK if assignment was successfull
* returns SQLITE_NOMEM if an error occured allocating memory
*/
-static int sqlcipher_cipher_ctx_set_keyspec(cipher_ctx *ctx, const unsigned char *key, int key_sz, const unsigned char *salt, int salt_sz) {
-
- /* free, zero existing pointers and size */
- sqlcipher_free(ctx->keyspec, ctx->keyspec_sz);
- ctx->keyspec = NULL;
- ctx->keyspec_sz = 0;
+static int sqlcipher_cipher_ctx_set_keyspec(codec_ctx *ctx, cipher_ctx *c_ctx, const unsigned char *key) {
+ /* free, zero existing pointers and size */
+ sqlcipher_free(c_ctx->keyspec, ctx->keyspec_sz);
+ c_ctx->keyspec = NULL;
- /* establic a hex-formated key specification, containing the raw encryption key and
- the salt used to generate it */
- ctx->keyspec_sz = ((key_sz + salt_sz) * 2) + 3;
- ctx->keyspec = sqlcipher_malloc(ctx->keyspec_sz);
- if(ctx->keyspec == NULL) return SQLITE_NOMEM;
+ c_ctx->keyspec = sqlcipher_malloc(ctx->keyspec_sz);
+ if(c_ctx->keyspec == NULL) return SQLITE_NOMEM;
- ctx->keyspec[0] = 'x';
- ctx->keyspec[1] = '\'';
- cipher_bin2hex(key, key_sz, ctx->keyspec + 2);
- cipher_bin2hex(salt, salt_sz, ctx->keyspec + (key_sz * 2) + 2);
- ctx->keyspec[ctx->keyspec_sz - 1] = '\'';
+ c_ctx->keyspec[0] = 'x';
+ c_ctx->keyspec[1] = '\'';
+ cipher_bin2hex(key, ctx->key_sz, c_ctx->keyspec + 2);
+ cipher_bin2hex(ctx->kdf_salt, ctx->kdf_salt_sz, c_ctx->keyspec + (ctx->key_sz * 2) + 2);
+ c_ctx->keyspec[ctx->keyspec_sz - 1] = '\'';
return SQLITE_OK;
}
int sqlcipher_codec_get_store_pass(codec_ctx *ctx) {
- return ctx->read_ctx->store_pass;
+ return ctx->store_pass;
}
void sqlcipher_codec_set_store_pass(codec_ctx *ctx, int value) {
- ctx->read_ctx->store_pass = value;
+ ctx->store_pass = value;
}
void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey) {
@@ -19154,6 +23022,11 @@ void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey) {
*nKey = ctx->read_ctx->pass_sz;
}
+static void sqlcipher_set_derive_key(codec_ctx *ctx, int derive) {
+ if(ctx->read_ctx != NULL) ctx->read_ctx->derive_key = 1;
+ if(ctx->write_ctx != NULL) ctx->write_ctx->derive_key = 1;
+}
+
/**
* Set the passphrase for the cipher_ctx
*
@@ -19161,7 +23034,6 @@ void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey) {
* returns SQLITE_NOMEM if an error occured allocating memory
*/
static int sqlcipher_cipher_ctx_set_pass(cipher_ctx *ctx, const void *zKey, int nKey) {
-
/* free, zero existing pointers and size */
sqlcipher_free(ctx->pass, ctx->pass_sz);
ctx->pass = NULL;
@@ -19184,37 +23056,14 @@ int sqlcipher_codec_ctx_set_pass(codec_ctx *ctx, const void *zKey, int nKey, int
c_ctx->derive_key = 1;
if(for_ctx == 2)
- if((rc = sqlcipher_cipher_ctx_copy( for_ctx ? ctx->read_ctx : ctx->write_ctx, c_ctx)) != SQLITE_OK)
+ if((rc = sqlcipher_cipher_ctx_copy(ctx, for_ctx ? ctx->read_ctx : ctx->write_ctx, c_ctx)) != SQLITE_OK)
return rc;
return SQLITE_OK;
}
-int sqlcipher_codec_ctx_set_cipher(codec_ctx *ctx, const char *cipher_name, int for_ctx) {
- cipher_ctx *c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx;
- int rc;
-
- rc = c_ctx->provider->set_cipher(c_ctx->provider_ctx, cipher_name);
- if(rc != SQLITE_OK){
- sqlcipher_codec_ctx_set_error(ctx, rc);
- return rc;
- }
- c_ctx->key_sz = c_ctx->provider->get_key_sz(c_ctx->provider_ctx);
- c_ctx->iv_sz = c_ctx->provider->get_iv_sz(c_ctx->provider_ctx);
- c_ctx->block_sz = c_ctx->provider->get_block_sz(c_ctx->provider_ctx);
- c_ctx->hmac_sz = c_ctx->provider->get_hmac_sz(c_ctx->provider_ctx);
- c_ctx->derive_key = 1;
-
- if(for_ctx == 2)
- if((rc = sqlcipher_cipher_ctx_copy( for_ctx ? ctx->read_ctx : ctx->write_ctx, c_ctx)) != SQLITE_OK)
- return rc;
-
- return SQLITE_OK;
-}
-
-const char* sqlcipher_codec_ctx_get_cipher(codec_ctx *ctx, int for_ctx) {
- cipher_ctx *c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx;
- return c_ctx->provider->get_cipher(c_ctx->provider_ctx);
+const char* sqlcipher_codec_ctx_get_cipher(codec_ctx *ctx) {
+ return ctx->provider->get_cipher(ctx->provider_ctx);
}
/* set the global default KDF iteration */
@@ -19226,42 +23075,24 @@ int sqlcipher_get_default_kdf_iter() {
return default_kdf_iter;
}
-int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *ctx, int kdf_iter, int for_ctx) {
- cipher_ctx *c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx;
- int rc;
-
- c_ctx->kdf_iter = kdf_iter;
- c_ctx->derive_key = 1;
-
- if(for_ctx == 2)
- if((rc = sqlcipher_cipher_ctx_copy( for_ctx ? ctx->read_ctx : ctx->write_ctx, c_ctx)) != SQLITE_OK)
- return rc;
-
+int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *ctx, int kdf_iter) {
+ ctx->kdf_iter = kdf_iter;
+ sqlcipher_set_derive_key(ctx, 1);
return SQLITE_OK;
}
-int sqlcipher_codec_ctx_get_kdf_iter(codec_ctx *ctx, int for_ctx) {
- cipher_ctx *c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx;
- return c_ctx->kdf_iter;
+int sqlcipher_codec_ctx_get_kdf_iter(codec_ctx *ctx) {
+ return ctx->kdf_iter;
}
-int sqlcipher_codec_ctx_set_fast_kdf_iter(codec_ctx *ctx, int fast_kdf_iter, int for_ctx) {
- cipher_ctx *c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx;
- int rc;
-
- c_ctx->fast_kdf_iter = fast_kdf_iter;
- c_ctx->derive_key = 1;
-
- if(for_ctx == 2)
- if((rc = sqlcipher_cipher_ctx_copy( for_ctx ? ctx->read_ctx : ctx->write_ctx, c_ctx)) != SQLITE_OK)
- return rc;
-
+int sqlcipher_codec_ctx_set_fast_kdf_iter(codec_ctx *ctx, int fast_kdf_iter) {
+ ctx->fast_kdf_iter = fast_kdf_iter;
+ sqlcipher_set_derive_key(ctx, 1);
return SQLITE_OK;
}
-int sqlcipher_codec_ctx_get_fast_kdf_iter(codec_ctx *ctx, int for_ctx) {
- cipher_ctx *c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx;
- return c_ctx->fast_kdf_iter;
+int sqlcipher_codec_ctx_get_fast_kdf_iter(codec_ctx *ctx) {
+ return ctx->fast_kdf_iter;
}
/* set the global default flag for HMAC */
@@ -19284,76 +23115,156 @@ unsigned char sqlcipher_get_hmac_salt_mask() {
/* set the codec flag for whether this individual database should be using hmac */
int sqlcipher_codec_ctx_set_use_hmac(codec_ctx *ctx, int use) {
- int reserve = CIPHER_MAX_IV_SZ; /* base reserve size will be IV only */
-
- if(use) reserve += ctx->read_ctx->hmac_sz; /* if reserve will include hmac, update that size */
-
- /* calculate the amount of reserve needed in even increments of the cipher block size */
-
- reserve = ((reserve % ctx->read_ctx->block_sz) == 0) ? reserve :
- ((reserve / ctx->read_ctx->block_sz) + 1) * ctx->read_ctx->block_sz;
-
- CODEC_TRACE(("sqlcipher_codec_ctx_set_use_hmac: use=%d block_sz=%d md_size=%d reserve=%d\n",
- use, ctx->read_ctx->block_sz, ctx->read_ctx->hmac_sz, reserve));
-
-
if(use) {
sqlcipher_codec_ctx_set_flag(ctx, CIPHER_FLAG_HMAC);
} else {
sqlcipher_codec_ctx_unset_flag(ctx, CIPHER_FLAG_HMAC);
}
-
- ctx->write_ctx->reserve_sz = ctx->read_ctx->reserve_sz = reserve;
+ return sqlcipher_codec_ctx_reserve_setup(ctx);
+}
+
+int sqlcipher_codec_ctx_get_use_hmac(codec_ctx *ctx) {
+ return (ctx->flags & CIPHER_FLAG_HMAC) != 0;
+}
+
+/* the length of plaintext header size must be:
+ * 1. greater than or equal to zero
+ * 2. a multiple of the cipher block size
+ * 3. less than the usable size of the first database page
+ */
+int sqlcipher_set_default_plaintext_header_size(int size) {
+ default_plaintext_header_sz = size;
+ return SQLITE_OK;
+}
+
+int sqlcipher_codec_ctx_set_plaintext_header_size(codec_ctx *ctx, int size) {
+ if(size >= 0 && (size % ctx->block_sz) == 0 && size < (ctx->page_sz - ctx->reserve_sz)) {
+ ctx->plaintext_header_sz = size;
+ return SQLITE_OK;
+ }
+ return SQLITE_ERROR;
+}
+
+int sqlcipher_get_default_plaintext_header_size() {
+ return default_plaintext_header_sz;
+}
+
+int sqlcipher_codec_ctx_get_plaintext_header_size(codec_ctx *ctx) {
+ return ctx->plaintext_header_sz;
+}
+
+/* manipulate HMAC algorithm */
+int sqlcipher_set_default_hmac_algorithm(int algorithm) {
+ default_hmac_algorithm = algorithm;
+ return SQLITE_OK;
+}
+
+int sqlcipher_codec_ctx_set_hmac_algorithm(codec_ctx *ctx, int algorithm) {
+ ctx->hmac_algorithm = algorithm;
+ return sqlcipher_codec_ctx_reserve_setup(ctx);
+}
+
+int sqlcipher_get_default_hmac_algorithm() {
+ return default_hmac_algorithm;
+}
+
+int sqlcipher_codec_ctx_get_hmac_algorithm(codec_ctx *ctx) {
+ return ctx->hmac_algorithm;
+}
+
+/* manipulate KDF algorithm */
+int sqlcipher_set_default_kdf_algorithm(int algorithm) {
+ default_kdf_algorithm = algorithm;
return SQLITE_OK;
}
-int sqlcipher_codec_ctx_get_use_hmac(codec_ctx *ctx, int for_ctx) {
- cipher_ctx * c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx;
- return (c_ctx->flags & CIPHER_FLAG_HMAC) != 0;
+int sqlcipher_codec_ctx_set_kdf_algorithm(codec_ctx *ctx, int algorithm) {
+ ctx->kdf_algorithm = algorithm;
+ return SQLITE_OK;
+}
+
+int sqlcipher_get_default_kdf_algorithm() {
+ return default_kdf_algorithm;
+}
+
+int sqlcipher_codec_ctx_get_kdf_algorithm(codec_ctx *ctx) {
+ return ctx->kdf_algorithm;
}
int sqlcipher_codec_ctx_set_flag(codec_ctx *ctx, unsigned int flag) {
- ctx->write_ctx->flags |= flag;
- ctx->read_ctx->flags |= flag;
+ ctx->flags |= flag;
return SQLITE_OK;
}
int sqlcipher_codec_ctx_unset_flag(codec_ctx *ctx, unsigned int flag) {
- ctx->write_ctx->flags &= ~flag;
- ctx->read_ctx->flags &= ~flag;
+ ctx->flags &= ~flag;
return SQLITE_OK;
}
-int sqlcipher_codec_ctx_get_flag(codec_ctx *ctx, unsigned int flag, int for_ctx) {
- cipher_ctx * c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx;
- return (c_ctx->flags & flag) != 0;
+int sqlcipher_codec_ctx_get_flag(codec_ctx *ctx, unsigned int flag) {
+ return (ctx->flags & flag) != 0;
}
void sqlcipher_codec_ctx_set_error(codec_ctx *ctx, int error) {
- CODEC_TRACE(("sqlcipher_codec_ctx_set_error: ctx=%p, error=%d\n", ctx, error));
- sqlite3pager_sqlite3PagerSetError(ctx->pBt->pBt->pPager, error);
+ CODEC_TRACE("sqlcipher_codec_ctx_set_error: ctx=%p, error=%d\n", ctx, error);
+ tdsqlite3pager_error(ctx->pBt->pBt->pPager, error);
ctx->pBt->pBt->db->errCode = error;
}
int sqlcipher_codec_ctx_get_reservesize(codec_ctx *ctx) {
- return ctx->read_ctx->reserve_sz;
+ return ctx->reserve_sz;
}
void* sqlcipher_codec_ctx_get_data(codec_ctx *ctx) {
return ctx->buffer;
}
-void* sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx) {
- return ctx->kdf_salt;
+static int sqlcipher_codec_ctx_init_kdf_salt(codec_ctx *ctx) {
+ tdsqlite3_file *fd = tdsqlite3PagerFile(ctx->pBt->pBt->pPager);
+
+ if(!ctx->need_kdf_salt) {
+ return SQLITE_OK; /* don't reload salt when not needed */
+ }
+
+ /* read salt from header, if present, otherwise generate a new random salt */
+ CODEC_TRACE("sqlcipher_codec_ctx_init_kdf_salt: obtaining salt\n");
+ if(fd == NULL || fd->pMethods == 0 || tdsqlite3OsRead(fd, ctx->kdf_salt, ctx->kdf_salt_sz, 0) != SQLITE_OK) {
+ CODEC_TRACE("sqlcipher_codec_ctx_init_kdf_salt: unable to read salt from file header, generating random\n");
+ if(ctx->provider->random(ctx->provider_ctx, ctx->kdf_salt, ctx->kdf_salt_sz) != SQLITE_OK) return SQLITE_ERROR;
+ }
+ ctx->need_kdf_salt = 0;
+ return SQLITE_OK;
+}
+
+int sqlcipher_codec_ctx_set_kdf_salt(codec_ctx *ctx, unsigned char *salt, int size) {
+ if(size >= ctx->kdf_salt_sz) {
+ memcpy(ctx->kdf_salt, salt, ctx->kdf_salt_sz);
+ ctx->need_kdf_salt = 0;
+ return SQLITE_OK;
+ }
+ return SQLITE_ERROR;
+}
+
+int sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx, void** salt) {
+ int rc = SQLITE_OK;
+ if(ctx->need_kdf_salt) {
+ rc = sqlcipher_codec_ctx_init_kdf_salt(ctx);
+ }
+ *salt = ctx->kdf_salt;
+ return rc;
}
void sqlcipher_codec_get_keyspec(codec_ctx *ctx, void **zKey, int *nKey) {
*zKey = ctx->read_ctx->keyspec;
- *nKey = ctx->read_ctx->keyspec_sz;
+ *nKey = ctx->keyspec_sz;
}
int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) {
+ if(!((size != 0) && ((size & (size - 1)) == 0)) || size < 512 || size > 65536) {
+ CODEC_TRACE(("cipher_page_size not a power of 2 and between 512 and 65536 inclusive\n"));
+ return SQLITE_ERROR;
+ }
/* attempt to free the existing page buffer */
sqlcipher_free(ctx->buffer,ctx->page_sz);
ctx->page_sz = size;
@@ -19379,9 +23290,22 @@ int sqlcipher_get_default_pagesize() {
return default_page_size;
}
-int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, sqlite3_file *fd, const void *zKey, int nKey) {
+void sqlcipher_set_mem_security(int on) {
+ mem_security_on = on;
+ mem_security_activated = 0;
+}
+
+int sqlcipher_get_mem_security() {
+ return mem_security_on && mem_security_activated;
+}
+
+
+int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, const void *zKey, int nKey) {
int rc;
codec_ctx *ctx;
+
+ CODEC_TRACE("sqlcipher_codec_ctx_init: allocating context\n");
+
*iCtx = sqlcipher_malloc(sizeof(codec_ctx));
ctx = *iCtx;
@@ -19393,6 +23317,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, sqlite3_f
directly off the database file. This is the salt for the
key derivation function. If we get a short read allocate
a new random salt value */
+ CODEC_TRACE("sqlcipher_codec_ctx_init: allocating kdf_salt\n");
ctx->kdf_salt_sz = FILE_HEADER_SZ;
ctx->kdf_salt = sqlcipher_malloc(ctx->kdf_salt_sz);
if(ctx->kdf_salt == NULL) return SQLITE_NOMEM;
@@ -19400,34 +23325,88 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, sqlite3_f
/* allocate space for separate hmac salt data. We want the
HMAC derivation salt to be different than the encryption
key derivation salt */
+ CODEC_TRACE("sqlcipher_codec_ctx_init: allocating hmac_kdf_salt\n");
ctx->hmac_kdf_salt = sqlcipher_malloc(ctx->kdf_salt_sz);
if(ctx->hmac_kdf_salt == NULL) return SQLITE_NOMEM;
+ /* setup default flags */
+ ctx->flags = default_flags;
+
+ /* defer attempt to read KDF salt until first use */
+ ctx->need_kdf_salt = 1;
+
+ /* setup the crypto provider */
+ CODEC_TRACE("sqlcipher_codec_ctx_init: allocating provider\n");
+ ctx->provider = (sqlcipher_provider *) sqlcipher_malloc(sizeof(sqlcipher_provider));
+ if(ctx->provider == NULL) return SQLITE_NOMEM;
+
+ /* make a copy of the provider to be used for the duration of the context */
+ CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: entering SQLCIPHER_MUTEX_PROVIDER\n");
+ tdsqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER));
+ CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: entered SQLCIPHER_MUTEX_PROVIDER\n");
+
+ memcpy(ctx->provider, default_provider, sizeof(sqlcipher_provider));
+
+ CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: leaving SQLCIPHER_MUTEX_PROVIDER\n");
+ tdsqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER));
+ CODEC_TRACE_MUTEX("sqlcipher_codec_ctx_init: left SQLCIPHER_MUTEX_PROVIDER\n");
+
+ CODEC_TRACE("sqlcipher_codec_ctx_init: calling provider ctx_init\n");
+ if((rc = ctx->provider->ctx_init(&ctx->provider_ctx)) != SQLITE_OK) return rc;
+
+ ctx->key_sz = ctx->provider->get_key_sz(ctx->provider_ctx);
+ ctx->iv_sz = ctx->provider->get_iv_sz(ctx->provider_ctx);
+ ctx->block_sz = ctx->provider->get_block_sz(ctx->provider_ctx);
+
+ /* establic the size for a hex-formated key specification, containing the
+ raw encryption key and the salt used to generate it format. will be x'hexkey...hexsalt'
+ so oversize by 3 bytes */
+ ctx->keyspec_sz = ((ctx->key_sz + ctx->kdf_salt_sz) * 2) + 3;
/*
Always overwrite page size and set to the default because the first page of the database
in encrypted and thus sqlite can't effectively determine the pagesize. this causes an issue in
cases where bytes 16 & 17 of the page header are a power of 2 as reported by John Lehman
*/
+ CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_pagesize with %d\n", default_page_size);
if((rc = sqlcipher_codec_ctx_set_pagesize(ctx, default_page_size)) != SQLITE_OK) return rc;
- if((rc = sqlcipher_cipher_ctx_init(&ctx->read_ctx)) != SQLITE_OK) return rc;
- if((rc = sqlcipher_cipher_ctx_init(&ctx->write_ctx)) != SQLITE_OK) return rc;
+ /* establish settings for the KDF iterations and fast (HMAC) KDF iterations */
+ CODEC_TRACE("sqlcipher_codec_ctx_init: setting default_kdf_iter\n");
+ if((rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, default_kdf_iter)) != SQLITE_OK) return rc;
- if(fd == NULL || sqlite3OsRead(fd, ctx->kdf_salt, FILE_HEADER_SZ, 0) != SQLITE_OK) {
- ctx->need_kdf_salt = 1;
- }
+ CODEC_TRACE("sqlcipher_codec_ctx_init: setting fast_kdf_iter\n");
+ if((rc = sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, FAST_PBKDF2_ITER)) != SQLITE_OK) return rc;
- if((rc = sqlcipher_codec_ctx_set_cipher(ctx, CIPHER, 0)) != SQLITE_OK) return rc;
- if((rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, default_kdf_iter, 0)) != SQLITE_OK) return rc;
- if((rc = sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, FAST_PBKDF2_ITER, 0)) != SQLITE_OK) return rc;
- if((rc = sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, 0)) != SQLITE_OK) return rc;
+ /* set the default HMAC and KDF algorithms which will determine the reserve size */
+ CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_hmac_algorithm with %d\n", default_hmac_algorithm);
+ if((rc = sqlcipher_codec_ctx_set_hmac_algorithm(ctx, default_hmac_algorithm)) != SQLITE_OK) return rc;
/* Note that use_hmac is a special case that requires recalculation of page size
so we call set_use_hmac to perform setup */
+ CODEC_TRACE("sqlcipher_codec_ctx_init: setting use_hmac\n");
if((rc = sqlcipher_codec_ctx_set_use_hmac(ctx, default_flags & CIPHER_FLAG_HMAC)) != SQLITE_OK) return rc;
- if((rc = sqlcipher_cipher_ctx_copy(ctx->write_ctx, ctx->read_ctx)) != SQLITE_OK) return rc;
+ CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_kdf_algorithm with %d\n", default_kdf_algorithm);
+ if((rc = sqlcipher_codec_ctx_set_kdf_algorithm(ctx, default_kdf_algorithm)) != SQLITE_OK) return rc;
+
+ /* setup the default plaintext header size */
+ CODEC_TRACE("sqlcipher_codec_ctx_init: calling sqlcipher_codec_ctx_set_plaintext_header_size with %d\n", default_plaintext_header_sz);
+ if((rc = sqlcipher_codec_ctx_set_plaintext_header_size(ctx, default_plaintext_header_sz)) != SQLITE_OK) return rc;
+
+ /* initialize the read and write sub-contexts. this must happen after key_sz is established */
+ CODEC_TRACE("sqlcipher_codec_ctx_init: initializing read_ctx\n");
+ if((rc = sqlcipher_cipher_ctx_init(ctx, &ctx->read_ctx)) != SQLITE_OK) return rc;
+
+ CODEC_TRACE("sqlcipher_codec_ctx_init: initializing write_ctx\n");
+ if((rc = sqlcipher_cipher_ctx_init(ctx, &ctx->write_ctx)) != SQLITE_OK) return rc;
+
+ /* set the key material on one of the sub cipher contexts and sync them up */
+ CODEC_TRACE("sqlcipher_codec_ctx_init: setting pass key\n");
+ if((rc = sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, 0)) != SQLITE_OK) return rc;
+
+ CODEC_TRACE("sqlcipher_codec_ctx_init: copying write_ctx to read_ctx\n");
+ if((rc = sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx)) != SQLITE_OK) return rc;
return SQLITE_OK;
}
@@ -19438,12 +23417,16 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, sqlite3_f
*/
void sqlcipher_codec_ctx_free(codec_ctx **iCtx) {
codec_ctx *ctx = *iCtx;
- CODEC_TRACE(("codec_ctx_free: entered iCtx=%p\n", iCtx));
+ CODEC_TRACE("codec_ctx_free: entered iCtx=%p\n", iCtx);
sqlcipher_free(ctx->kdf_salt, ctx->kdf_salt_sz);
sqlcipher_free(ctx->hmac_kdf_salt, ctx->kdf_salt_sz);
sqlcipher_free(ctx->buffer, 0);
- sqlcipher_cipher_ctx_free(&ctx->read_ctx);
- sqlcipher_cipher_ctx_free(&ctx->write_ctx);
+
+ ctx->provider->ctx_free(&ctx->provider_ctx);
+ sqlcipher_free(ctx->provider, sizeof(sqlcipher_provider));
+
+ sqlcipher_cipher_ctx_free(ctx, &ctx->read_ctx);
+ sqlcipher_cipher_ctx_free(ctx, &ctx->write_ctx);
sqlcipher_free(ctx, sizeof(codec_ctx));
}
@@ -19455,7 +23438,7 @@ static void sqlcipher_put4byte_le(unsigned char *p, u32 v) {
p[3] = (u8)(v>>24);
}
-static int sqlcipher_page_hmac(cipher_ctx *ctx, Pgno pgno, unsigned char *in, int in_sz, unsigned char *out) {
+static int sqlcipher_page_hmac(codec_ctx *ctx, cipher_ctx *c_ctx, Pgno pgno, unsigned char *in, int in_sz, unsigned char *out) {
unsigned char pgno_raw[sizeof(pgno)];
/* we may convert page number to consistent representation before calculating MAC for
compatibility across big-endian and little-endian platforms.
@@ -19468,7 +23451,7 @@ static int sqlcipher_page_hmac(cipher_ctx *ctx, Pgno pgno, unsigned char *in, in
if(ctx->flags & CIPHER_FLAG_LE_PGNO) { /* compute hmac using little endian pgno*/
sqlcipher_put4byte_le(pgno_raw, pgno);
} else if(ctx->flags & CIPHER_FLAG_BE_PGNO) { /* compute hmac using big endian pgno */
- sqlite3Put4byte(pgno_raw, pgno); /* sqlite3Put4byte converts 32bit uint to big endian */
+ tdsqlite3Put4byte(pgno_raw, pgno); /* tdsqlite3Put4byte converts 32bit uint to big endian */
} else { /* use native byte ordering */
memcpy(pgno_raw, &pgno, sizeof(pgno));
}
@@ -19476,12 +23459,11 @@ static int sqlcipher_page_hmac(cipher_ctx *ctx, Pgno pgno, unsigned char *in, in
/* include the encrypted page data, initialization vector, and page number in HMAC. This will
prevent both tampering with the ciphertext, manipulation of the IV, or resequencing otherwise
valid pages out of order in a database */
- ctx->provider->hmac(
- ctx->provider_ctx, ctx->hmac_key,
+ return ctx->provider->hmac(
+ ctx->provider_ctx, ctx->hmac_algorithm, c_ctx->hmac_key,
ctx->key_sz, in,
in_sz, (unsigned char*) &pgno_raw,
sizeof(pgno), out);
- return SQLITE_OK;
}
/*
@@ -19498,70 +23480,76 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int
int size;
/* calculate some required positions into various buffers */
- size = page_sz - c_ctx->reserve_sz; /* adjust size to useable size and memset reserve at end of page */
+ size = page_sz - ctx->reserve_sz; /* adjust size to useable size and memset reserve at end of page */
iv_out = out + size;
iv_in = in + size;
/* hmac will be written immediately after the initialization vector. the remainder of the page reserve will contain
random bytes. note, these pointers are only valid when using hmac */
- hmac_in = in + size + c_ctx->iv_sz;
- hmac_out = out + size + c_ctx->iv_sz;
+ hmac_in = in + size + ctx->iv_sz;
+ hmac_out = out + size + ctx->iv_sz;
out_start = out; /* note the original position of the output buffer pointer, as out will be rewritten during encryption */
- CODEC_TRACE(("codec_cipher:entered pgno=%d, mode=%d, size=%d\n", pgno, mode, size));
+ CODEC_TRACE("codec_cipher:entered pgno=%d, mode=%d, size=%d\n", pgno, mode, size);
CODEC_HEXDUMP("codec_cipher: input page data", in, page_sz);
/* the key size should never be zero. If it is, error out. */
- if(c_ctx->key_sz == 0) {
- CODEC_TRACE(("codec_cipher: error possible context corruption, key_sz is zero for pgno=%d\n", pgno));
- sqlcipher_memset(out, 0, page_sz);
- return SQLITE_ERROR;
+ if(ctx->key_sz == 0) {
+ CODEC_TRACE("codec_cipher: error possible context corruption, key_sz is zero for pgno=%d\n", pgno);
+ goto error;
}
if(mode == CIPHER_ENCRYPT) {
/* start at front of the reserve block, write random data to the end */
- if(c_ctx->provider->random(c_ctx->provider_ctx, iv_out, c_ctx->reserve_sz) != SQLITE_OK) return SQLITE_ERROR;
+ if(ctx->provider->random(ctx->provider_ctx, iv_out, ctx->reserve_sz) != SQLITE_OK) goto error;
} else { /* CIPHER_DECRYPT */
- memcpy(iv_out, iv_in, c_ctx->iv_sz); /* copy the iv from the input to output buffer */
+ memcpy(iv_out, iv_in, ctx->iv_sz); /* copy the iv from the input to output buffer */
}
- if((c_ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT) && !ctx->skip_read_hmac) {
- if(sqlcipher_page_hmac(c_ctx, pgno, in, size + c_ctx->iv_sz, hmac_out) != SQLITE_OK) {
- sqlcipher_memset(out, 0, page_sz);
- CODEC_TRACE(("codec_cipher: hmac operations failed for pgno=%d\n", pgno));
- return SQLITE_ERROR;
+ if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_DECRYPT) && !ctx->skip_read_hmac) {
+ if(sqlcipher_page_hmac(ctx, c_ctx, pgno, in, size + ctx->iv_sz, hmac_out) != SQLITE_OK) {
+ CODEC_TRACE("codec_cipher: hmac operation on decrypt failed for pgno=%d\n", pgno);
+ goto error;
}
- CODEC_TRACE(("codec_cipher: comparing hmac on in=%p out=%p hmac_sz=%d\n", hmac_in, hmac_out, c_ctx->hmac_sz));
- if(sqlcipher_memcmp(hmac_in, hmac_out, c_ctx->hmac_sz) != 0) { /* the hmac check failed */
+ CODEC_TRACE("codec_cipher: comparing hmac on in=%p out=%p hmac_sz=%d\n", hmac_in, hmac_out, ctx->hmac_sz);
+ if(sqlcipher_memcmp(hmac_in, hmac_out, ctx->hmac_sz) != 0) { /* the hmac check failed */
if(sqlcipher_ismemset(in, 0, page_sz) == 0) {
/* first check if the entire contents of the page is zeros. If so, this page
resulted from a short read (i.e. sqlite attempted to pull a page after the end of the file. these
short read failures must be ignored for autovaccum mode to work so wipe the output buffer
and return SQLITE_OK to skip the decryption step. */
- CODEC_TRACE(("codec_cipher: zeroed page (short read) for pgno %d, encryption but returning SQLITE_OK\n", pgno));
+ CODEC_TRACE("codec_cipher: zeroed page (short read) for pgno %d, encryption but returning SQLITE_OK\n", pgno);
sqlcipher_memset(out, 0, page_sz);
- return SQLITE_OK;
+ return SQLITE_OK;
} else {
- /* if the page memory is not all zeros, it means the there was data and a hmac on the page.
+ /* if the page memory is not all zeros, it means the there was data and a hmac on the page.
since the check failed, the page was either tampered with or corrupted. wipe the output buffer,
and return SQLITE_ERROR to the caller */
- CODEC_TRACE(("codec_cipher: hmac check failed for pgno=%d returning SQLITE_ERROR\n", pgno));
- sqlcipher_memset(out, 0, page_sz);
- return SQLITE_ERROR;
+ CODEC_TRACE("codec_cipher: hmac check failed for pgno=%d returning SQLITE_ERROR\n", pgno);
+ goto error;
}
}
}
- c_ctx->provider->cipher(c_ctx->provider_ctx, mode, c_ctx->key, c_ctx->key_sz, iv_out, in, size, out);
+ if(ctx->provider->cipher(ctx->provider_ctx, mode, c_ctx->key, ctx->key_sz, iv_out, in, size, out) != SQLITE_OK) {
+ CODEC_TRACE("codec_cipher: cipher operation mode=%d failed for pgno=%d returning SQLITE_ERROR\n", mode, pgno);
+ goto error;
+ };
- if((c_ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_ENCRYPT)) {
- sqlcipher_page_hmac(c_ctx, pgno, out_start, size + c_ctx->iv_sz, hmac_out);
+ if((ctx->flags & CIPHER_FLAG_HMAC) && (mode == CIPHER_ENCRYPT)) {
+ if(sqlcipher_page_hmac(ctx, c_ctx, pgno, out_start, size + ctx->iv_sz, hmac_out) != SQLITE_OK) {
+ CODEC_TRACE("codec_cipher: hmac operation on encrypt failed for pgno=%d\n", pgno);
+ goto error;
+ };
}
CODEC_HEXDUMP("codec_cipher: output page data", out_start, page_sz);
return SQLITE_OK;
+error:
+ sqlcipher_memset(out, 0, page_sz);
+ return SQLITE_ERROR;
}
/**
@@ -19581,43 +23569,44 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int
*/
static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) {
int rc;
- CODEC_TRACE(("cipher_ctx_key_derive: entered c_ctx->pass=%s, c_ctx->pass_sz=%d \
- ctx->kdf_salt=%p ctx->kdf_salt_sz=%d c_ctx->kdf_iter=%d \
- ctx->hmac_kdf_salt=%p, c_ctx->fast_kdf_iter=%d c_ctx->key_sz=%d\n",
- c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, c_ctx->kdf_iter,
- ctx->hmac_kdf_salt, c_ctx->fast_kdf_iter, c_ctx->key_sz));
+ CODEC_TRACE("cipher_ctx_key_derive: entered c_ctx->pass=%s, c_ctx->pass_sz=%d \
+ ctx->kdf_salt=%p ctx->kdf_salt_sz=%d ctx->kdf_iter=%d \
+ ctx->hmac_kdf_salt=%p, ctx->fast_kdf_iter=%d ctx->key_sz=%d\n",
+ c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter,
+ ctx->hmac_kdf_salt, ctx->fast_kdf_iter, ctx->key_sz);
- if(c_ctx->pass && c_ctx->pass_sz) { // if pass is not null
-
+ if(c_ctx->pass && c_ctx->pass_sz) { /* if key material is present on the context for derivation */
+
+ /* if necessary, initialize the salt from the header or random source */
if(ctx->need_kdf_salt) {
- if(ctx->read_ctx->provider->random(ctx->read_ctx->provider_ctx, ctx->kdf_salt, FILE_HEADER_SZ) != SQLITE_OK) return SQLITE_ERROR;
- ctx->need_kdf_salt = 0;
+ if((rc = sqlcipher_codec_ctx_init_kdf_salt(ctx)) != SQLITE_OK) return rc;
}
- if (c_ctx->pass_sz == ((c_ctx->key_sz * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, c_ctx->key_sz * 2)) {
+
+ if (c_ctx->pass_sz == ((ctx->key_sz * 2) + 3) && tdsqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, ctx->key_sz * 2)) {
int n = c_ctx->pass_sz - 3; /* adjust for leading x' and tailing ' */
const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */
- CODEC_TRACE(("cipher_ctx_key_derive: using raw key from hex\n"));
+ CODEC_TRACE("cipher_ctx_key_derive: using raw key from hex\n");
cipher_hex2bin(z, n, c_ctx->key);
- } else if (c_ctx->pass_sz == (((c_ctx->key_sz + ctx->kdf_salt_sz) * 2) + 3) && sqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, (c_ctx->key_sz + ctx->kdf_salt_sz) * 2)) {
+ } else if (c_ctx->pass_sz == (((ctx->key_sz + ctx->kdf_salt_sz) * 2) + 3) && tdsqlite3StrNICmp((const char *)c_ctx->pass ,"x'", 2) == 0 && cipher_isHex(c_ctx->pass + 2, (ctx->key_sz + ctx->kdf_salt_sz) * 2)) {
const unsigned char *z = c_ctx->pass + 2; /* adjust lead offset of x' */
- CODEC_TRACE(("cipher_ctx_key_derive: using raw key from hex\n"));
- cipher_hex2bin(z, (c_ctx->key_sz * 2), c_ctx->key);
- cipher_hex2bin(z + (c_ctx->key_sz * 2), (ctx->kdf_salt_sz * 2), ctx->kdf_salt);
+ CODEC_TRACE("cipher_ctx_key_derive: using raw key from hex\n");
+ cipher_hex2bin(z, (ctx->key_sz * 2), c_ctx->key);
+ cipher_hex2bin(z + (ctx->key_sz * 2), (ctx->kdf_salt_sz * 2), ctx->kdf_salt);
} else {
- CODEC_TRACE(("cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations\n", c_ctx->kdf_iter));
- c_ctx->provider->kdf(c_ctx->provider_ctx, c_ctx->pass, c_ctx->pass_sz,
- ctx->kdf_salt, ctx->kdf_salt_sz, c_ctx->kdf_iter,
- c_ctx->key_sz, c_ctx->key);
+ CODEC_TRACE("cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations\n", ctx->kdf_iter);
+ if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->pass, c_ctx->pass_sz,
+ ctx->kdf_salt, ctx->kdf_salt_sz, ctx->kdf_iter,
+ ctx->key_sz, c_ctx->key) != SQLITE_OK) return SQLITE_ERROR;
}
/* set the context "keyspec" containing the hex-formatted key and salt to be used when attaching databases */
- if((rc = sqlcipher_cipher_ctx_set_keyspec(c_ctx, c_ctx->key, c_ctx->key_sz, ctx->kdf_salt, ctx->kdf_salt_sz)) != SQLITE_OK) return rc;
+ if((rc = sqlcipher_cipher_ctx_set_keyspec(ctx, c_ctx, c_ctx->key)) != SQLITE_OK) return rc;
/* if this context is setup to use hmac checks, generate a seperate and different
key for HMAC. In this case, we use the output of the previous KDF as the input to
this KDF run. This ensures a distinct but predictable HMAC key. */
- if(c_ctx->flags & CIPHER_FLAG_HMAC) {
+ if(ctx->flags & CIPHER_FLAG_HMAC) {
int i;
/* start by copying the kdf key into the hmac salt slot
@@ -19630,13 +23619,13 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) {
ctx->hmac_kdf_salt[i] ^= hmac_salt_mask;
}
- CODEC_TRACE(("cipher_ctx_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations\n",
- c_ctx->fast_kdf_iter));
+ CODEC_TRACE("cipher_ctx_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations\n",
+ ctx->fast_kdf_iter);
- c_ctx->provider->kdf(c_ctx->provider_ctx, c_ctx->key, c_ctx->key_sz,
- ctx->hmac_kdf_salt, ctx->kdf_salt_sz, c_ctx->fast_kdf_iter,
- c_ctx->key_sz, c_ctx->hmac_key);
+ if(ctx->provider->kdf(ctx->provider_ctx, ctx->kdf_algorithm, c_ctx->key, ctx->key_sz,
+ ctx->hmac_kdf_salt, ctx->kdf_salt_sz, ctx->fast_kdf_iter,
+ ctx->key_sz, c_ctx->hmac_key) != SQLITE_OK) return SQLITE_ERROR;
}
c_ctx->derive_key = 0;
@@ -19654,14 +23643,14 @@ int sqlcipher_codec_key_derive(codec_ctx *ctx) {
if(ctx->write_ctx->derive_key) {
if(sqlcipher_cipher_ctx_cmp(ctx->write_ctx, ctx->read_ctx) == 0) {
/* the relevant parameters are the same, just copy read key */
- if(sqlcipher_cipher_ctx_copy(ctx->write_ctx, ctx->read_ctx) != SQLITE_OK) return SQLITE_ERROR;
+ if(sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx) != SQLITE_OK) return SQLITE_ERROR;
} else {
if(sqlcipher_cipher_ctx_key_derive(ctx, ctx->write_ctx) != SQLITE_OK) return SQLITE_ERROR;
}
}
/* TODO: wipe and free passphrase after key derivation */
- if(ctx->read_ctx->store_pass != 1) {
+ if(ctx->store_pass != 1) {
sqlcipher_cipher_ctx_set_pass(ctx->read_ctx, NULL, 0);
sqlcipher_cipher_ctx_set_pass(ctx->write_ctx, NULL, 0);
}
@@ -19671,553 +23660,396 @@ int sqlcipher_codec_key_derive(codec_ctx *ctx) {
int sqlcipher_codec_key_copy(codec_ctx *ctx, int source) {
if(source == CIPHER_READ_CTX) {
- return sqlcipher_cipher_ctx_copy(ctx->write_ctx, ctx->read_ctx);
+ return sqlcipher_cipher_ctx_copy(ctx, ctx->write_ctx, ctx->read_ctx);
} else {
- return sqlcipher_cipher_ctx_copy(ctx->read_ctx, ctx->write_ctx);
+ return sqlcipher_cipher_ctx_copy(ctx, ctx->read_ctx, ctx->write_ctx);
}
}
const char* sqlcipher_codec_get_cipher_provider(codec_ctx *ctx) {
- return ctx->read_ctx->provider->get_provider_name(ctx->read_ctx);
+ return ctx->provider->get_provider_name(ctx->provider_ctx);
}
-static int sqlcipher_check_connection(const char *filename, char *key, int key_sz, char *sql, int *user_version) {
+static int sqlcipher_check_connection(const char *filename, char *key, int key_sz, char *sql, int *user_version, char** journal_mode) {
int rc;
- sqlite3 *db = NULL;
- sqlite3_stmt *statement = NULL;
+ tdsqlite3 *db = NULL;
+ tdsqlite3_stmt *statement = NULL;
+ char *query_journal_mode = "PRAGMA journal_mode;";
char *query_user_version = "PRAGMA user_version;";
-
- rc = sqlite3_open(filename, &db);
- if(rc != SQLITE_OK){
- goto cleanup;
- }
- rc = sqlite3_key(db, key, key_sz);
- if(rc != SQLITE_OK){
- goto cleanup;
- }
- rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
- if(rc != SQLITE_OK){
- goto cleanup;
- }
- rc = sqlite3_prepare(db, query_user_version, -1, &statement, NULL);
- if(rc != SQLITE_OK){
+
+ rc = tdsqlite3_open(filename, &db);
+ if(rc != SQLITE_OK) goto cleanup;
+
+ rc = tdsqlite3_key(db, key, key_sz);
+ if(rc != SQLITE_OK) goto cleanup;
+
+ rc = tdsqlite3_exec(db, sql, NULL, NULL, NULL);
+ if(rc != SQLITE_OK) goto cleanup;
+
+ /* start by querying the user version.
+ this will fail if the key is incorrect */
+ rc = tdsqlite3_prepare(db, query_user_version, -1, &statement, NULL);
+ if(rc != SQLITE_OK) goto cleanup;
+
+ rc = tdsqlite3_step(statement);
+ if(rc == SQLITE_ROW) {
+ *user_version = tdsqlite3_column_int(statement, 0);
+ } else {
goto cleanup;
}
- rc = sqlite3_step(statement);
- if(rc == SQLITE_ROW){
- *user_version = sqlite3_column_int(statement, 0);
- rc = SQLITE_OK;
+ tdsqlite3_finalize(statement);
+
+ rc = tdsqlite3_prepare(db, query_journal_mode, -1, &statement, NULL);
+ if(rc != SQLITE_OK) goto cleanup;
+
+ rc = tdsqlite3_step(statement);
+ if(rc == SQLITE_ROW) {
+ *journal_mode = tdsqlite3_mprintf("%s", tdsqlite3_column_text(statement, 0));
+ } else {
+ goto cleanup;
}
+ rc = SQLITE_OK;
+ /* cleanup will finalize open statement */
cleanup:
- if(statement){
- sqlite3_finalize(statement);
- }
- if(db){
- sqlite3_close(db);
- }
+ if(statement) tdsqlite3_finalize(statement);
+ if(db) tdsqlite3_close(db);
return rc;
}
-int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) {
- u32 meta;
+int sqlcipher_codec_ctx_integrity_check(codec_ctx *ctx, Parse *pParse, char *column) {
+ Pgno page = 1;
int rc = 0;
- int command_idx = 0;
- int password_sz;
- int saved_flags;
- int saved_nChange;
- int saved_nTotalChange;
- int (*saved_xTrace)(u32,void*,void*,void*); /* Saved db->xTrace */
- Db *pDb = 0;
- sqlite3 *db = ctx->pBt->db;
- const char *db_filename = sqlite3_db_filename(db, "main");
- char *migrated_db_filename = sqlite3_mprintf("%s-migrated", db_filename);
- char *pragma_hmac_off = "PRAGMA cipher_use_hmac = OFF;";
- char *pragma_4k_kdf_iter = "PRAGMA kdf_iter = 4000;";
- char *pragma_1x_and_4k;
- char *set_user_version;
- char *key;
- int key_sz;
- int user_version = 0;
- int upgrade_1x_format = 0;
- int upgrade_4k_format = 0;
- static const unsigned char aCopy[] = {
- BTREE_SCHEMA_VERSION, 1, /* Add one to the old schema cookie */
- BTREE_DEFAULT_CACHE_SIZE, 0, /* Preserve the default page cache size */
- BTREE_TEXT_ENCODING, 0, /* Preserve the text encoding */
- BTREE_USER_VERSION, 0, /* Preserve the user version */
- BTREE_APPLICATION_ID, 0, /* Preserve the application id */
- };
-
+ char *result;
+ unsigned char *hmac_out = NULL;
+ tdsqlite3_file *fd = tdsqlite3PagerFile(ctx->pBt->pBt->pPager);
+ i64 file_sz;
+
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ tdsqlite3VdbeSetNumCols(v, 1);
+ tdsqlite3VdbeSetColName(v, 0, COLNAME_NAME, column, SQLITE_STATIC);
+
+ if(fd == NULL || fd->pMethods == 0) {
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, "database file is undefined", P4_TRANSIENT);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ goto cleanup;
+ }
- key_sz = ctx->read_ctx->pass_sz + 1;
- key = sqlcipher_malloc(key_sz);
- memset(key, 0, key_sz);
- memcpy(key, ctx->read_ctx->pass, ctx->read_ctx->pass_sz);
+ if(!(ctx->flags & CIPHER_FLAG_HMAC)) {
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, "HMAC is not enabled, unable to integrity check", P4_TRANSIENT);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ goto cleanup;
+ }
- if(db_filename){
- const char* commands[5];
- char *attach_command = sqlite3_mprintf("ATTACH DATABASE '%s-migrated' as migrate KEY '%q';",
- db_filename, key);
+ if((rc = sqlcipher_codec_key_derive(ctx)) != SQLITE_OK) {
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, "unable to derive keys", P4_TRANSIENT);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ goto cleanup;
+ }
- int rc = sqlcipher_check_connection(db_filename, key, ctx->read_ctx->pass_sz, "", &user_version);
- if(rc == SQLITE_OK){
- CODEC_TRACE(("No upgrade required - exiting\n"));
- goto exit;
- }
-
- // Version 2 - check for 4k with hmac format
- rc = sqlcipher_check_connection(db_filename, key, ctx->read_ctx->pass_sz, pragma_4k_kdf_iter, &user_version);
- if(rc == SQLITE_OK) {
- CODEC_TRACE(("Version 2 format found\n"));
- upgrade_4k_format = 1;
- }
+ tdsqlite3OsFileSize(fd, &file_sz);
+ hmac_out = sqlcipher_malloc(ctx->hmac_sz);
- // Version 1 - check both no hmac and 4k together
- pragma_1x_and_4k = sqlite3_mprintf("%s%s", pragma_hmac_off,
- pragma_4k_kdf_iter);
- rc = sqlcipher_check_connection(db_filename, key, ctx->read_ctx->pass_sz, pragma_1x_and_4k, &user_version);
- sqlite3_free(pragma_1x_and_4k);
- if(rc == SQLITE_OK) {
- CODEC_TRACE(("Version 1 format found\n"));
- upgrade_1x_format = 1;
- upgrade_4k_format = 1;
- }
+ for(page = 1; page <= file_sz / ctx->page_sz; page++) {
+ int offset = (page - 1) * ctx->page_sz;
+ int payload_sz = ctx->page_sz - ctx->reserve_sz + ctx->iv_sz;
+ int read_sz = ctx->page_sz;
- if(upgrade_1x_format == 0 && upgrade_4k_format == 0) {
- CODEC_TRACE(("Upgrade format not determined\n"));
- goto handle_error;
+ if(page==1) {
+ int page1_offset = ctx->plaintext_header_sz ? ctx->plaintext_header_sz : FILE_HEADER_SZ;
+ read_sz = read_sz - page1_offset;
+ payload_sz = payload_sz - page1_offset;
+ offset += page1_offset;
}
- set_user_version = sqlite3_mprintf("PRAGMA migrate.user_version = %d;", user_version);
- commands[0] = upgrade_4k_format == 1 ? pragma_4k_kdf_iter : "";
- commands[1] = upgrade_1x_format == 1 ? pragma_hmac_off : "";
- commands[2] = attach_command;
- commands[3] = "SELECT sqlcipher_export('migrate');";
- commands[4] = set_user_version;
-
- for(command_idx = 0; command_idx < ArraySize(commands); command_idx++){
- const char *command = commands[command_idx];
- if(strcmp(command, "") == 0){
- continue;
- }
- rc = sqlite3_exec(db, command, NULL, NULL, NULL);
- if(rc != SQLITE_OK){
- break;
- }
+ sqlcipher_memset(ctx->buffer, 0, ctx->page_sz);
+ sqlcipher_memset(hmac_out, 0, ctx->hmac_sz);
+ if(tdsqlite3OsRead(fd, ctx->buffer, read_sz, offset) != SQLITE_OK) {
+ result = tdsqlite3_mprintf("error reading %d bytes from file page %d at offset %d\n", read_sz, page, offset);
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, result, P4_DYNAMIC);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ } else if(sqlcipher_page_hmac(ctx, ctx->read_ctx, page, ctx->buffer, payload_sz, hmac_out) != SQLITE_OK) {
+ result = tdsqlite3_mprintf("HMAC operation failed for page %d", page);
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, result, P4_DYNAMIC);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ } else if(sqlcipher_memcmp(ctx->buffer + payload_sz, hmac_out, ctx->hmac_sz) != 0) {
+ result = tdsqlite3_mprintf("HMAC verification failed for page %d", page);
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, result, P4_DYNAMIC);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
}
- sqlite3_free(attach_command);
- sqlite3_free(set_user_version);
- sqlcipher_free(key, key_sz);
-
- if(rc == SQLITE_OK){
- Btree *pDest;
- Btree *pSrc;
- int i = 0;
-
- if( !db->autoCommit ){
- CODEC_TRACE(("cannot migrate from within a transaction"));
- goto handle_error;
- }
- if( db->nVdbeActive>1 ){
- CODEC_TRACE(("cannot migrate - SQL statements in progress"));
- goto handle_error;
- }
-
- /* Save the current value of the database flags so that it can be
- ** restored before returning. Then set the writable-schema flag, and
- ** disable CHECK and foreign key constraints. */
- saved_flags = db->flags;
- saved_nChange = db->nChange;
- saved_nTotalChange = db->nTotalChange;
- saved_xTrace = db->xTrace;
- db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_PreferBuiltin;
- db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder);
- db->xTrace = 0;
-
- pDest = db->aDb[0].pBt;
- pDb = &(db->aDb[db->nDb-1]);
- pSrc = pDb->pBt;
-
- rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
- rc = sqlite3BtreeBeginTrans(pSrc, 2);
- rc = sqlite3BtreeBeginTrans(pDest, 2);
-
- assert( 1==sqlite3BtreeIsInTrans(pDest) );
- assert( 1==sqlite3BtreeIsInTrans(pSrc) );
-
- sqlite3CodecGetKey(db, db->nDb - 1, (void**)&key, &password_sz);
- sqlite3CodecAttach(db, 0, key, password_sz);
- sqlite3pager_get_codec(pDest->pBt->pPager, (void**)&ctx);
-
- ctx->skip_read_hmac = 1;
- for(i=0; i<ArraySize(aCopy); i+=2){
- sqlite3BtreeGetMeta(pSrc, aCopy[i], &meta);
- rc = sqlite3BtreeUpdateMeta(pDest, aCopy[i], meta+aCopy[i+1]);
- if( NEVER(rc!=SQLITE_OK) ) goto handle_error;
- }
- rc = sqlite3BtreeCopyFile(pDest, pSrc);
- ctx->skip_read_hmac = 0;
- if( rc!=SQLITE_OK ) goto handle_error;
- rc = sqlite3BtreeCommit(pDest);
-
- db->flags = saved_flags;
- db->nChange = saved_nChange;
- db->nTotalChange = saved_nTotalChange;
- db->xTrace = saved_xTrace;
- db->autoCommit = 1;
- sqlite3BtreeClose(pDb->pBt);
- pDb->pBt = 0;
- pDb->pSchema = 0;
- sqlite3ResetAllSchemasOfConnection(db);
- remove(migrated_db_filename);
- sqlite3_free(migrated_db_filename);
- } else {
- CODEC_TRACE(("*** migration failure** \n\n"));
- }
-
}
- goto exit;
- handle_error:
- CODEC_TRACE(("An error occurred attempting to migrate the database\n"));
- rc = SQLITE_ERROR;
+ if(file_sz % ctx->page_sz != 0) {
+ result = tdsqlite3_mprintf("page %d has an invalid size of %lld bytes", page, file_sz - ((file_sz / ctx->page_sz) * ctx->page_sz));
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, result, P4_DYNAMIC);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ }
- exit:
- return rc;
+cleanup:
+ if(hmac_out != NULL) sqlcipher_free(hmac_out, ctx->hmac_sz);
+ return SQLITE_OK;
}
-int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz){
- const char *suffix = &zRight[random_sz-1];
- int n = random_sz - 3; /* adjust for leading x' and tailing ' */
- if (n > 0 &&
- sqlite3StrNICmp((const char *)zRight ,"x'", 2) == 0 &&
- sqlite3StrNICmp(suffix, "'", 1) == 0 &&
- n % 2 == 0) {
- int rc = 0;
- int buffer_sz = n / 2;
- unsigned char *random;
- const unsigned char *z = (const unsigned char *)zRight + 2; /* adjust lead offset of x' */
- CODEC_TRACE(("sqlcipher_codec_add_random: using raw random blob from hex\n"));
- random = sqlcipher_malloc(buffer_sz);
- memset(random, 0, buffer_sz);
- cipher_hex2bin(z, n, random);
- rc = ctx->read_ctx->provider->add_random(ctx->read_ctx->provider_ctx, random, buffer_sz);
- sqlcipher_free(random, buffer_sz);
- return rc;
+int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) {
+ int i, pass_sz, keyspec_sz, nRes, user_version, rc, oflags;
+ Db *pDb = 0;
+ tdsqlite3 *db = ctx->pBt->db;
+ const char *db_filename = tdsqlite3_db_filename(db, "main");
+ char *set_user_version = NULL, *pass = NULL, *attach_command = NULL, *migrated_db_filename = NULL, *keyspec = NULL, *temp = NULL, *journal_mode = NULL, *set_journal_mode = NULL, *pragma_compat = NULL;
+ Btree *pDest = NULL, *pSrc = NULL;
+ tdsqlite3_file *srcfile, *destfile;
+#if defined(_WIN32) || defined(SQLITE_OS_WINRT)
+ LPWSTR w_db_filename = NULL, w_migrated_db_filename = NULL;
+ int w_db_filename_sz = 0, w_migrated_db_filename_sz = 0;
+#endif
+ pass_sz = keyspec_sz = rc = user_version = 0;
+
+ if(!db_filename || tdsqlite3Strlen30(db_filename) < 1)
+ goto cleanup; /* exit immediately if this is an in memory database */
+
+ /* pull the provided password / key material off the current codec context */
+ pass_sz = ctx->read_ctx->pass_sz;
+ pass = sqlcipher_malloc(pass_sz+1);
+ memset(pass, 0, pass_sz+1);
+ memcpy(pass, ctx->read_ctx->pass, pass_sz);
+
+ /* Version 4 - current, no upgrade required, so exit immediately */
+ rc = sqlcipher_check_connection(db_filename, pass, pass_sz, "", &user_version, &journal_mode);
+ if(rc == SQLITE_OK){
+ CODEC_TRACE("No upgrade required - exiting\n");
+ goto cleanup;
}
- return SQLITE_ERROR;
-}
-
-int sqlcipher_cipher_profile(sqlite3 *db, const char *destination){
- FILE *f;
- if(sqlite3StrICmp(destination, "stdout") == 0){
- f = stdout;
- }else if(sqlite3StrICmp(destination, "stderr") == 0){
- f = stderr;
- }else if(sqlite3StrICmp(destination, "off") == 0){
- f = 0;
- }else{
-#if defined(_WIN32) && (__STDC_VERSION__ > 199901L) || defined(SQLITE_OS_WINRT)
- if(fopen_s(&f, destination, "a") != 0){
-#else
- f = fopen(destination, "a");
- if(f == 0){
-#endif
- return SQLITE_ERROR;
- }
+ for(i = 3; i > 0; i--) {
+ pragma_compat = tdsqlite3_mprintf("PRAGMA cipher_compatibility = %d;", i);
+ rc = sqlcipher_check_connection(db_filename, pass, pass_sz, pragma_compat, &user_version, &journal_mode);
+ if(rc == SQLITE_OK) {
+ CODEC_TRACE("Version %d format found\n", i);
+ goto migrate;
+ }
+ if(pragma_compat) sqlcipher_free(pragma_compat, tdsqlite3Strlen30(pragma_compat));
+ pragma_compat = NULL;
}
- sqlite3_profile(db, sqlcipher_profile_callback, f);
- return SQLITE_OK;
-}
-
-static void sqlcipher_profile_callback(void *file, const char *sql, sqlite3_uint64 run_time){
- FILE *f = (FILE*)file;
- double elapsed = run_time/1000000.0;
- if(f) fprintf(f, "Elapsed time:%.3f ms - %s\n", elapsed, sql);
-}
+ /* if we exit the loop normally we failed to determine the version, this is an error */
+ CODEC_TRACE("Upgrade format not determined\n");
+ goto handle_error;
-int sqlcipher_codec_fips_status(codec_ctx *ctx) {
- return ctx->read_ctx->provider->fips_status(ctx->read_ctx);
-}
+migrate:
-const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx) {
- return ctx->read_ctx->provider->get_provider_version(ctx->read_ctx);
-}
+ temp = tdsqlite3_mprintf("%s-migrated", db_filename);
+ /* overallocate migrated_db_filename, because tdsqlite3OsOpen will read past the null terminator
+ * to determine whether the filename was URI formatted */
+ migrated_db_filename = sqlcipher_malloc(tdsqlite3Strlen30(temp)+2);
+ memcpy(migrated_db_filename, temp, tdsqlite3Strlen30(temp));
+ sqlcipher_free(temp, tdsqlite3Strlen30(temp));
-#endif
-/* END SQLCIPHER */
+ attach_command = tdsqlite3_mprintf("ATTACH DATABASE '%s' as migrate;", migrated_db_filename, pass);
+ set_user_version = tdsqlite3_mprintf("PRAGMA migrate.user_version = %d;", user_version);
-/************** End of crypto_impl.c *****************************************/
-/************** Begin file crypto_libtomcrypt.c ******************************/
-/*
-** SQLCipher
-** http://sqlcipher.net
-**
-** Copyright (c) 2008 - 2013, ZETETIC LLC
-** All rights reserved.
-**
-** Redistribution and use in source and binary forms, with or without
-** modification, are permitted provided that the following conditions are met:
-** * Redistributions of source code must retain the above copyright
-** notice, this list of conditions and the following disclaimer.
-** * Redistributions in binary form must reproduce the above copyright
-** notice, this list of conditions and the following disclaimer in the
-** documentation and/or other materials provided with the distribution.
-** * Neither the name of the ZETETIC LLC nor the
-** names of its contributors may be used to endorse or promote products
-** derived from this software without specific prior written permission.
-**
-** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
-** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
-** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-**
-*/
-/* BEGIN SQLCIPHER */
-#ifdef SQLITE_HAS_CODEC
-#ifdef SQLCIPHER_CRYPTO_LIBTOMCRYPT
-/* #include "sqliteInt.h" */
-/* #include "sqlcipher.h" */
-#include <tomcrypt.h>
+ rc = tdsqlite3_exec(db, pragma_compat, NULL, NULL, NULL);
+ if(rc != SQLITE_OK){
+ CODEC_TRACE("set compatibility mode failed, error code %d\n", rc);
+ goto handle_error;
+ }
-#define FORTUNA_MAX_SZ 32
-static prng_state prng;
-static unsigned int ltc_init = 0;
-static unsigned int ltc_ref_count = 0;
-static sqlite3_mutex* ltc_rand_mutex = NULL;
+ /* force journal mode to DELETE, we will set it back later if different */
+ rc = tdsqlite3_exec(db, "PRAGMA journal_mode = delete;", NULL, NULL, NULL);
+ if(rc != SQLITE_OK){
+ CODEC_TRACE("force journal mode DELETE failed, error code %d\n", rc);
+ goto handle_error;
+ }
-static int sqlcipher_ltc_add_random(void *ctx, void *buffer, int length) {
- int rc = 0;
- int data_to_read = length;
- int block_sz = data_to_read < FORTUNA_MAX_SZ ? data_to_read : FORTUNA_MAX_SZ;
- const unsigned char * data = (const unsigned char *)buffer;
-#ifndef SQLCIPHER_LTC_NO_MUTEX_RAND
- sqlite3_mutex_enter(ltc_rand_mutex);
-#endif
- while(data_to_read > 0){
- rc = fortuna_add_entropy(data, block_sz, &prng);
- rc = rc != CRYPT_OK ? SQLITE_ERROR : SQLITE_OK;
- if(rc != SQLITE_OK){
- break;
- }
- data_to_read -= block_sz;
- data += block_sz;
- block_sz = data_to_read < FORTUNA_MAX_SZ ? data_to_read : FORTUNA_MAX_SZ;
- }
- fortuna_ready(&prng);
-#ifndef SQLCIPHER_LTC_NO_MUTEX_RAND
- sqlite3_mutex_leave(ltc_rand_mutex);
-#endif
- return rc;
-}
+ rc = tdsqlite3_exec(db, attach_command, NULL, NULL, NULL);
+ if(rc != SQLITE_OK){
+ CODEC_TRACE("attach failed, error code %d\n", rc);
+ goto handle_error;
+ }
-static int sqlcipher_ltc_activate(void *ctx) {
- unsigned char random_buffer[FORTUNA_MAX_SZ];
-#ifndef SQLCIPHER_LTC_NO_MUTEX_RAND
- if(ltc_rand_mutex == NULL){
- ltc_rand_mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ rc = tdsqlite3_key_v2(db, "migrate", pass, pass_sz);
+ if(rc != SQLITE_OK){
+ CODEC_TRACE("keying attached database failed, error code %d\n", rc);
+ goto handle_error;
}
- sqlite3_mutex_enter(ltc_rand_mutex);
-#endif
- sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ);
- if(ltc_init == 0) {
- if(register_prng(&fortuna_desc) != CRYPT_OK) return SQLITE_ERROR;
- if(register_cipher(&rijndael_desc) != CRYPT_OK) return SQLITE_ERROR;
- if(register_hash(&sha1_desc) != CRYPT_OK) return SQLITE_ERROR;
- if(fortuna_start(&prng) != CRYPT_OK) {
- return SQLITE_ERROR;
- }
- ltc_init = 1;
+
+ rc = tdsqlite3_exec(db, "SELECT sqlcipher_export('migrate');", NULL, NULL, NULL);
+ if(rc != SQLITE_OK){
+ CODEC_TRACE("sqlcipher_export failed, error code %d\n", rc);
+ goto handle_error;
}
- ltc_ref_count++;
-#ifndef SQLCIPHER_TEST
- sqlite3_randomness(FORTUNA_MAX_SZ, random_buffer);
-#endif
-#ifndef SQLCIPHER_LTC_NO_MUTEX_RAND
- sqlite3_mutex_leave(ltc_rand_mutex);
-#endif
- if(sqlcipher_ltc_add_random(ctx, random_buffer, FORTUNA_MAX_SZ) != SQLITE_OK) {
- return SQLITE_ERROR;
+
+ rc = tdsqlite3_exec(db, set_user_version, NULL, NULL, NULL);
+ if(rc != SQLITE_OK){
+ CODEC_TRACE("set user version failed, error code %d\n", rc);
+ goto handle_error;
}
- sqlcipher_memset(random_buffer, 0, FORTUNA_MAX_SZ);
- return SQLITE_OK;
-}
-static int sqlcipher_ltc_deactivate(void *ctx) {
-#ifndef SQLCIPHER_LTC_NO_MUTEX_RAND
- sqlite3_mutex_enter(ltc_rand_mutex);
-#endif
- ltc_ref_count--;
- if(ltc_ref_count == 0){
- fortuna_done(&prng);
- sqlcipher_memset((void *)&prng, 0, sizeof(prng));
-#ifndef SQLCIPHER_LTC_NO_MUTEX_RAND
- sqlite3_mutex_leave(ltc_rand_mutex);
- sqlite3_mutex_free(ltc_rand_mutex);
- ltc_rand_mutex = NULL;
-#endif
+ if( !db->autoCommit ){
+ CODEC_TRACE("cannot migrate from within a transaction");
+ goto handle_error;
}
-#ifndef SQLCIPHER_LTC_NO_MUTEX_RAND
- else {
- sqlite3_mutex_leave(ltc_rand_mutex);
+ if( db->nVdbeActive>1 ){
+ CODEC_TRACE("cannot migrate - SQL statements in progress");
+ goto handle_error;
}
-#endif
- return SQLITE_OK;
-}
-static const char* sqlcipher_ltc_get_provider_name(void *ctx) {
- return "libtomcrypt";
-}
+ pDest = db->aDb[0].pBt;
+ pDb = &(db->aDb[db->nDb-1]);
+ pSrc = pDb->pBt;
-static const char* sqlcipher_ltc_get_provider_version(void *ctx) {
- return SCRYPT;
-}
+ nRes = tdsqlite3BtreeGetOptimalReserve(pSrc);
+ /* unset the BTS_PAGESIZE_FIXED flag to avoid SQLITE_READONLY */
+ pDest->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED;
+ rc = tdsqlite3BtreeSetPageSize(pDest, default_page_size, nRes, 0);
+ CODEC_TRACE("set btree page size to %d res %d rc %d\n", default_page_size, nRes, rc);
+ if( rc!=SQLITE_OK ) goto handle_error;
-static int sqlcipher_ltc_random(void *ctx, void *buffer, int length) {
-#ifndef SQLCIPHER_LTC_NO_MUTEX_RAND
- sqlite3_mutex_enter(ltc_rand_mutex);
-#endif
- fortuna_read(buffer, length, &prng);
-#ifndef SQLCIPHER_LTC_NO_MUTEX_RAND
- sqlite3_mutex_leave(ltc_rand_mutex);
-#endif
- return SQLITE_OK;
-}
+ tdsqlite3CodecGetKey(db, db->nDb - 1, (void**)&keyspec, &keyspec_sz);
+ tdsqlite3CodecAttach(db, 0, keyspec, keyspec_sz);
+
+ srcfile = tdsqlite3PagerFile(pSrc->pBt->pPager);
+ destfile = tdsqlite3PagerFile(pDest->pBt->pPager);
-static int sqlcipher_ltc_hmac(void *ctx, unsigned char *hmac_key, int key_sz, unsigned char *in, int in_sz, unsigned char *in2, int in2_sz, unsigned char *out) {
- int rc, hash_idx;
- hmac_state hmac;
- unsigned long outlen = key_sz;
+ tdsqlite3OsClose(srcfile);
+ tdsqlite3OsClose(destfile);
- hash_idx = find_hash("sha1");
- if((rc = hmac_init(&hmac, hash_idx, hmac_key, key_sz)) != CRYPT_OK) return SQLITE_ERROR;
- if((rc = hmac_process(&hmac, in, in_sz)) != CRYPT_OK) return SQLITE_ERROR;
- if((rc = hmac_process(&hmac, in2, in2_sz)) != CRYPT_OK) return SQLITE_ERROR;
- if((rc = hmac_done(&hmac, out, &outlen)) != CRYPT_OK) return SQLITE_ERROR;
- return SQLITE_OK;
-}
+#if defined(_WIN32) || defined(SQLITE_OS_WINRT)
+ CODEC_TRACE("performing windows MoveFileExA\n");
-static int sqlcipher_ltc_kdf(void *ctx, const unsigned char *pass, int pass_sz, unsigned char* salt, int salt_sz, int workfactor, int key_sz, unsigned char *key) {
- int rc, hash_idx;
- unsigned long outlen = key_sz;
- unsigned long random_buffer_sz = sizeof(char) * 256;
- unsigned char *random_buffer = sqlcipher_malloc(random_buffer_sz);
- sqlcipher_memset(random_buffer, 0, random_buffer_sz);
+ w_db_filename_sz = MultiByteToWideChar(CP_UTF8, 0, (LPCCH) db_filename, -1, NULL, 0);
+ w_db_filename = sqlcipher_malloc(w_db_filename_sz * sizeof(wchar_t));
+ w_db_filename_sz = MultiByteToWideChar(CP_UTF8, 0, (LPCCH) db_filename, -1, (const LPWSTR) w_db_filename, w_db_filename_sz);
- hash_idx = find_hash("sha1");
- if((rc = pkcs_5_alg2(pass, pass_sz, salt, salt_sz,
- workfactor, hash_idx, key, &outlen)) != CRYPT_OK) {
- return SQLITE_ERROR;
+ w_migrated_db_filename_sz = MultiByteToWideChar(CP_UTF8, 0, (LPCCH) migrated_db_filename, -1, NULL, 0);
+ w_migrated_db_filename = sqlcipher_malloc(w_migrated_db_filename_sz * sizeof(wchar_t));
+ w_migrated_db_filename_sz = MultiByteToWideChar(CP_UTF8, 0, (LPCCH) migrated_db_filename, -1, (const LPWSTR) w_migrated_db_filename, w_migrated_db_filename_sz);
+
+ if(!MoveFileExW(w_migrated_db_filename, w_db_filename, MOVEFILE_REPLACE_EXISTING)) {
+ CODEC_TRACE("move error");
+ rc = SQLITE_ERROR;
+ CODEC_TRACE("error occurred while renaming %d\n", rc);
+ goto handle_error;
}
- if((rc = pkcs_5_alg2(key, key_sz, salt, salt_sz,
- 1, hash_idx, random_buffer, &random_buffer_sz)) != CRYPT_OK) {
- return SQLITE_ERROR;
+#else
+ CODEC_TRACE("performing POSIX rename\n");
+ if ((rc = rename(migrated_db_filename, db_filename)) != 0) {
+ CODEC_TRACE("error occurred while renaming %d\n", rc);
+ goto handle_error;
}
- sqlcipher_ltc_add_random(ctx, random_buffer, random_buffer_sz);
- sqlcipher_free(random_buffer, random_buffer_sz);
- return SQLITE_OK;
-}
+#endif
+ CODEC_TRACE("renamed migration database %s to main database %s: %d\n", migrated_db_filename, db_filename, rc);
-static const char* sqlcipher_ltc_get_cipher(void *ctx) {
- return "rijndael";
-}
+ rc = tdsqlite3OsOpen(db->pVfs, migrated_db_filename, srcfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags);
+ CODEC_TRACE("reopened migration database: %d\n", rc);
+ if( rc!=SQLITE_OK ) goto handle_error;
-static int sqlcipher_ltc_cipher(void *ctx, int mode, unsigned char *key, int key_sz, unsigned char *iv, unsigned char *in, int in_sz, unsigned char *out) {
- int rc, cipher_idx;
- symmetric_CBC cbc;
+ rc = tdsqlite3OsOpen(db->pVfs, db_filename, destfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_MAIN_DB, &oflags);
+ CODEC_TRACE("reopened main database: %d\n", rc);
+ if( rc!=SQLITE_OK ) goto handle_error;
- if((cipher_idx = find_cipher(sqlcipher_ltc_get_cipher(ctx))) == -1) return SQLITE_ERROR;
- if((rc = cbc_start(cipher_idx, iv, key, key_sz, 0, &cbc)) != CRYPT_OK) return SQLITE_ERROR;
- rc = mode == 1 ? cbc_encrypt(in, out, in_sz, &cbc) : cbc_decrypt(in, out, in_sz, &cbc);
- if(rc != CRYPT_OK) return SQLITE_ERROR;
- cbc_done(&cbc);
- return SQLITE_OK;
-}
+ tdsqlite3pager_reset(pDest->pBt->pPager);
+ CODEC_TRACE("reset pager\n");
-static int sqlcipher_ltc_set_cipher(void *ctx, const char *cipher_name) {
- return SQLITE_OK;
-}
+ rc = tdsqlite3_exec(db, "DETACH DATABASE migrate;", NULL, NULL, NULL);
+ CODEC_TRACE("DETACH DATABASE called %d\n", rc);
+ if(rc != SQLITE_OK) goto cleanup;
-static int sqlcipher_ltc_get_key_sz(void *ctx) {
- int cipher_idx = find_cipher(sqlcipher_ltc_get_cipher(ctx));
- return cipher_descriptor[cipher_idx].max_key_length;
-}
+ rc = tdsqlite3OsDelete(db->pVfs, migrated_db_filename, 0);
+ CODEC_TRACE("deleted migration database: %d\n", rc);
+ if( rc!=SQLITE_OK ) goto handle_error;
-static int sqlcipher_ltc_get_iv_sz(void *ctx) {
- int cipher_idx = find_cipher(sqlcipher_ltc_get_cipher(ctx));
- return cipher_descriptor[cipher_idx].block_length;
-}
+ tdsqlite3ResetAllSchemasOfConnection(db);
+ CODEC_TRACE("reset all schemas\n");
-static int sqlcipher_ltc_get_block_sz(void *ctx) {
- int cipher_idx = find_cipher(sqlcipher_ltc_get_cipher(ctx));
- return cipher_descriptor[cipher_idx].block_length;
-}
+ set_journal_mode = tdsqlite3_mprintf("PRAGMA journal_mode = %s;", journal_mode);
+ rc = tdsqlite3_exec(db, set_journal_mode, NULL, NULL, NULL);
+ CODEC_TRACE("%s: %d\n", set_journal_mode, rc);
+ if( rc!=SQLITE_OK ) goto handle_error;
-static int sqlcipher_ltc_get_hmac_sz(void *ctx) {
- int hash_idx = find_hash("sha1");
- return hash_descriptor[hash_idx].hashsize;
-}
+ goto cleanup;
-static int sqlcipher_ltc_ctx_copy(void *target_ctx, void *source_ctx) {
- return SQLITE_OK;
+handle_error:
+ CODEC_TRACE("An error occurred attempting to migrate the database - last error %d\n", rc);
+ rc = SQLITE_ERROR;
+
+cleanup:
+ if(pass) sqlcipher_free(pass, pass_sz);
+ if(attach_command) sqlcipher_free(attach_command, tdsqlite3Strlen30(attach_command));
+ if(migrated_db_filename) sqlcipher_free(migrated_db_filename, tdsqlite3Strlen30(migrated_db_filename));
+ if(set_user_version) sqlcipher_free(set_user_version, tdsqlite3Strlen30(set_user_version));
+ if(set_journal_mode) sqlcipher_free(set_journal_mode, tdsqlite3Strlen30(set_journal_mode));
+ if(journal_mode) sqlcipher_free(journal_mode, tdsqlite3Strlen30(journal_mode));
+ if(pragma_compat) sqlcipher_free(pragma_compat, tdsqlite3Strlen30(pragma_compat));
+#if defined(_WIN32) || defined(SQLITE_OS_WINRT)
+ if(w_db_filename) sqlcipher_free(w_db_filename, w_db_filename_sz);
+ if(w_migrated_db_filename) sqlcipher_free(w_migrated_db_filename, w_migrated_db_filename_sz);
+#endif
+ return rc;
}
-static int sqlcipher_ltc_ctx_cmp(void *c1, void *c2) {
- return 1;
+int sqlcipher_codec_add_random(codec_ctx *ctx, const char *zRight, int random_sz){
+ const char *suffix = &zRight[random_sz-1];
+ int n = random_sz - 3; /* adjust for leading x' and tailing ' */
+ if (n > 0 &&
+ tdsqlite3StrNICmp((const char *)zRight ,"x'", 2) == 0 &&
+ tdsqlite3StrNICmp(suffix, "'", 1) == 0 &&
+ n % 2 == 0) {
+ int rc = 0;
+ int buffer_sz = n / 2;
+ unsigned char *random;
+ const unsigned char *z = (const unsigned char *)zRight + 2; /* adjust lead offset of x' */
+ CODEC_TRACE("sqlcipher_codec_add_random: using raw random blob from hex\n");
+ random = sqlcipher_malloc(buffer_sz);
+ memset(random, 0, buffer_sz);
+ cipher_hex2bin(z, n, random);
+ rc = ctx->provider->add_random(ctx->provider_ctx, random, buffer_sz);
+ sqlcipher_free(random, buffer_sz);
+ return rc;
+ }
+ return SQLITE_ERROR;
}
-static int sqlcipher_ltc_ctx_init(void **ctx) {
- sqlcipher_ltc_activate(NULL);
- return SQLITE_OK;
+static void sqlcipher_profile_callback(void *file, const char *sql, tdsqlite3_uint64 run_time){
+ FILE *f = (FILE*)file;
+ double elapsed = run_time/1000000.0;
+ if(f) fprintf(f, "Elapsed time:%.3f ms - %s\n", elapsed, sql);
}
-static int sqlcipher_ltc_ctx_free(void **ctx) {
- sqlcipher_ltc_deactivate(&ctx);
+int sqlcipher_cipher_profile(tdsqlite3 *db, const char *destination){
+#if defined(SQLITE_OMIT_TRACE) || defined(SQLITE_OMIT_DEPRECATED)
+ return SQLITE_ERROR;
+#else
+ FILE *f;
+ if(tdsqlite3StrICmp(destination, "stdout") == 0){
+ f = stdout;
+ }else if(tdsqlite3StrICmp(destination, "stderr") == 0){
+ f = stderr;
+ }else if(tdsqlite3StrICmp(destination, "off") == 0){
+ f = 0;
+ }else{
+#if !defined(SQLCIPHER_PROFILE_USE_FOPEN) && (defined(_WIN32) && (__STDC_VERSION__ > 199901L) || defined(SQLITE_OS_WINRT))
+ if(fopen_s(&f, destination, "a") != 0) return SQLITE_ERROR;
+#else
+ if((f = fopen(destination, "a")) == 0) return SQLITE_ERROR;
+#endif
+ }
+ tdsqlite3_profile(db, sqlcipher_profile_callback, f);
return SQLITE_OK;
+#endif
}
-static int sqlcipher_ltc_fips_status(void *ctx) {
- return 0;
+int sqlcipher_codec_fips_status(codec_ctx *ctx) {
+ return ctx->provider->fips_status(ctx->provider_ctx);
}
-int sqlcipher_ltc_setup(sqlcipher_provider *p) {
- p->activate = sqlcipher_ltc_activate;
- p->deactivate = sqlcipher_ltc_deactivate;
- p->get_provider_name = sqlcipher_ltc_get_provider_name;
- p->random = sqlcipher_ltc_random;
- p->hmac = sqlcipher_ltc_hmac;
- p->kdf = sqlcipher_ltc_kdf;
- p->cipher = sqlcipher_ltc_cipher;
- p->set_cipher = sqlcipher_ltc_set_cipher;
- p->get_cipher = sqlcipher_ltc_get_cipher;
- p->get_key_sz = sqlcipher_ltc_get_key_sz;
- p->get_iv_sz = sqlcipher_ltc_get_iv_sz;
- p->get_block_sz = sqlcipher_ltc_get_block_sz;
- p->get_hmac_sz = sqlcipher_ltc_get_hmac_sz;
- p->ctx_copy = sqlcipher_ltc_ctx_copy;
- p->ctx_cmp = sqlcipher_ltc_ctx_cmp;
- p->ctx_init = sqlcipher_ltc_ctx_init;
- p->ctx_free = sqlcipher_ltc_ctx_free;
- p->add_random = sqlcipher_ltc_add_random;
- p->fips_status = sqlcipher_ltc_fips_status;
- p->get_provider_version = sqlcipher_ltc_get_provider_version;
- return SQLITE_OK;
+const char* sqlcipher_codec_get_provider_version(codec_ctx *ctx) {
+ return ctx->provider->get_provider_version(ctx->provider_ctx);
}
#endif
-#endif
/* END SQLCIPHER */
-/************** End of crypto_libtomcrypt.c **********************************/
+/************** End of crypto_impl.c *****************************************/
/************** Begin file crypto_openssl.c **********************************/
/*
** SQLCipher
@@ -20251,13 +24083,25 @@ int sqlcipher_ltc_setup(sqlcipher_provider *p) {
*/
/* BEGIN SQLCIPHER */
#ifdef SQLITE_HAS_CODEC
-#ifdef SQLCIPHER_CRYPTO_OPENSSL
/* #include "sqliteInt.h" */
/* #include "crypto.h" */
/* #include "sqlcipher.h" */
#include <openssl/rand.h>
#include <openssl/evp.h>
+#include <openssl/objects.h>
#include <openssl/hmac.h>
+#include <openssl/err.h>
+
+#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10000000L
+// HMAC_* functions returned void before 87d52468aa600e02326e13f01331e1f3b8602ed0
+#define HMAC_Init_ex_(...) (HMAC_Init_ex(__VA_ARGS__), 1)
+#define HMAC_Update_(...) (HMAC_Update(__VA_ARGS__), 1)
+#define HMAC_Final_(...) (HMAC_Final(__VA_ARGS__), 1)
+#else
+#define HMAC_Init_ex_(...) HMAC_Init_ex(__VA_ARGS__)
+#define HMAC_Update_(...) HMAC_Update(__VA_ARGS__)
+#define HMAC_Final_(...) HMAC_Final(__VA_ARGS__)
+#endif
typedef struct {
EVP_CIPHER *evp_cipher;
@@ -20265,9 +24109,8 @@ typedef struct {
static unsigned int openssl_external_init = 0;
static unsigned int openssl_init_count = 0;
-static sqlite3_mutex* openssl_rand_mutex = NULL;
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
static HMAC_CTX *HMAC_CTX_new(void)
{
HMAC_CTX *ctx = OPENSSL_malloc(sizeof(*ctx));
@@ -20277,10 +24120,10 @@ static HMAC_CTX *HMAC_CTX_new(void)
return ctx;
}
-// Per 1.1.0 (https://wiki.openssl.org/index.php/1.1_API_Changes)
-// HMAC_CTX_free should call HMAC_CTX_cleanup, then EVP_MD_CTX_Cleanup.
-// HMAC_CTX_cleanup internally calls EVP_MD_CTX_cleanup so these
-// calls are not needed.
+/* Per 1.1.0 (https://wiki.openssl.org/index.php/1.1_API_Changes)
+ HMAC_CTX_free should call HMAC_CTX_cleanup, then EVP_MD_CTX_Cleanup.
+ HMAC_CTX_cleanup internally calls EVP_MD_CTX_cleanup so these
+ calls are not needed. */
static void HMAC_CTX_free(HMAC_CTX *ctx)
{
if (ctx != NULL) {
@@ -20292,15 +24135,22 @@ static void HMAC_CTX_free(HMAC_CTX *ctx)
static int sqlcipher_openssl_add_random(void *ctx, void *buffer, int length) {
#ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND
- sqlite3_mutex_enter(openssl_rand_mutex);
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND\n");
+ tdsqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND\n");
#endif
RAND_add(buffer, length, 0);
#ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND
- sqlite3_mutex_leave(openssl_rand_mutex);
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND\n");
+ tdsqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_add_random: left SQLCIPHER_MUTEX_PROVIDER_RAND\n");
#endif
return SQLITE_OK;
}
+#define OPENSSL_CIPHER "aes-256-cbc"
+
+
/* activate and initialize sqlcipher. Most importantly, this will automatically
intialize OpenSSL's EVP system if it hasn't already be externally. Note that
this function may be called multiple times as new codecs are intiialized.
@@ -20311,9 +24161,12 @@ static int sqlcipher_openssl_activate(void *ctx) {
/* initialize openssl and increment the internal init counter
but only if it hasn't been initalized outside of SQLCipher by this program
e.g. on startup */
- sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n");
+ tdsqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n");
- if(openssl_init_count == 0 && EVP_get_cipherbyname(CIPHER) != NULL) {
+ if(openssl_init_count == 0 && EVP_get_cipherbyname(OPENSSL_CIPHER) != NULL) {
/* if openssl has not yet been initialized by this library, but
a call to get_cipherbyname works, then the openssl library
has been initialized externally already. */
@@ -20323,26 +24176,30 @@ static int sqlcipher_openssl_activate(void *ctx) {
#ifdef SQLCIPHER_FIPS
if(!FIPS_mode()){
if(!FIPS_mode_set(1)){
+ unsigned long err = 0;
ERR_load_crypto_strings();
+#ifdef __ANDROID__
+ while((err = ERR_get_error()) != 0) {
+ __android_log_print(ANDROID_LOG_ERROR, "sqlcipher","error: %lx. %s.", err, ERR_error_string(err, NULL));
+ }
+#else
ERR_print_errors_fp(stderr);
+#endif
}
}
#endif
if(openssl_init_count == 0 && openssl_external_init == 0) {
/* if the library was not externally initialized, then should be now */
+#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
OpenSSL_add_all_algorithms();
- }
-
-#ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND
- if(openssl_rand_mutex == NULL) {
- /* allocate a mutex to guard against concurrent calls to RAND_bytes() */
- openssl_rand_mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
- }
#endif
+ }
openssl_init_count++;
- sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n");
+ tdsqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_activate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n");
return SQLITE_OK;
}
@@ -20350,7 +24207,9 @@ static int sqlcipher_openssl_activate(void *ctx) {
freeing the EVP structures on the final deactivation to ensure that
OpenSSL memory is cleaned up */
static int sqlcipher_openssl_deactivate(void *ctx) {
- sqlite3_mutex_enter(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: entering SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n");
+ tdsqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: entered SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n");
openssl_init_count--;
if(openssl_init_count == 0) {
@@ -20360,16 +24219,17 @@ static int sqlcipher_openssl_deactivate(void *ctx) {
Note: this code will only be reached if OpensSSL_add_all_algorithms()
is called by SQLCipher internally. This should prevent SQLCipher from
"cleaning up" openssl when it was initialized externally by the program */
+#if (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10100000L) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
EVP_cleanup();
+#endif
} else {
openssl_external_init = 0;
}
-#ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND
- sqlite3_mutex_free(openssl_rand_mutex);
- openssl_rand_mutex = NULL;
-#endif
- }
- sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ }
+
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: leaving SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n");
+ tdsqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_ACTIVATE));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_deactivate: left SQLCIPHER_MUTEX_PROVIDER_ACTIVATE\n");
return SQLITE_OK;
}
@@ -20391,60 +24251,111 @@ static int sqlcipher_openssl_random (void *ctx, void *buffer, int length) {
but a more proper solution is that applications setup platform-appropriate
thread saftey in openssl externally */
#ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND
- sqlite3_mutex_enter(openssl_rand_mutex);
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_random: entering SQLCIPHER_MUTEX_PROVIDER_RAND\n");
+ tdsqlite3_mutex_enter(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_random: entered SQLCIPHER_MUTEX_PROVIDER_RAND\n");
#endif
rc = RAND_bytes((unsigned char *)buffer, length);
#ifndef SQLCIPHER_OPENSSL_NO_MUTEX_RAND
- sqlite3_mutex_leave(openssl_rand_mutex);
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_random: leaving SQLCIPHER_MUTEX_PROVIDER_RAND\n");
+ tdsqlite3_mutex_leave(sqlcipher_mutex(SQLCIPHER_MUTEX_PROVIDER_RAND));
+ CODEC_TRACE_MUTEX("sqlcipher_openssl_random: left SQLCIPHER_MUTEX_PROVIDER_RAND\n");
#endif
return (rc == 1) ? SQLITE_OK : SQLITE_ERROR;
}
-static int sqlcipher_openssl_hmac(void *ctx, unsigned char *hmac_key, int key_sz, unsigned char *in, int in_sz, unsigned char *in2, int in2_sz, unsigned char *out) {
+static int sqlcipher_openssl_hmac(void *ctx, int algorithm, unsigned char *hmac_key, int key_sz, unsigned char *in, int in_sz, unsigned char *in2, int in2_sz, unsigned char *out) {
unsigned int outlen;
- HMAC_CTX* hctx = HMAC_CTX_new();
- if(hctx == NULL) return SQLITE_ERROR;
- HMAC_Init_ex(hctx, hmac_key, key_sz, EVP_sha1(), NULL);
- HMAC_Update(hctx, in, in_sz);
- HMAC_Update(hctx, in2, in2_sz);
- HMAC_Final(hctx, out, &outlen);
- HMAC_CTX_free(hctx);
- return SQLITE_OK;
+ int rc = SQLITE_OK;
+ HMAC_CTX* hctx = NULL;
+
+ if(in == NULL) goto error;
+
+ hctx = HMAC_CTX_new();
+ if(hctx == NULL) goto error;
+
+ switch(algorithm) {
+ case SQLCIPHER_HMAC_SHA1:
+ if(!HMAC_Init_ex_(hctx, hmac_key, key_sz, EVP_sha1(), NULL)) goto error;
+ break;
+ case SQLCIPHER_HMAC_SHA256:
+ if(!HMAC_Init_ex_(hctx, hmac_key, key_sz, EVP_sha256(), NULL)) goto error;
+ break;
+ case SQLCIPHER_HMAC_SHA512:
+ if(!HMAC_Init_ex_(hctx, hmac_key, key_sz, EVP_sha512(), NULL)) goto error;
+ break;
+ default:
+ goto error;
+ }
+
+ if(!HMAC_Update_(hctx, in, in_sz)) goto error;
+ if(in2 != NULL) {
+ if(!HMAC_Update_(hctx, in2, in2_sz)) goto error;
+ }
+ if(!HMAC_Final_(hctx, out, &outlen)) goto error;
+
+ goto cleanup;
+error:
+ rc = SQLITE_ERROR;
+cleanup:
+ if(hctx) HMAC_CTX_free(hctx);
+ return rc;
}
-static int sqlcipher_openssl_kdf(void *ctx, const unsigned char *pass, int pass_sz, unsigned char* salt, int salt_sz, int workfactor, int key_sz, unsigned char *key) {
- PKCS5_PBKDF2_HMAC_SHA1((const char *)pass, pass_sz, salt, salt_sz, workfactor, key_sz, key);
- return SQLITE_OK;
+static int sqlcipher_openssl_kdf(void *ctx, int algorithm, const unsigned char *pass, int pass_sz, unsigned char* salt, int salt_sz, int workfactor, int key_sz, unsigned char *key) {
+ int rc = SQLITE_OK;
+
+#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x10000000L
+// PKCS5_PBKDF2_HMAC was added in 856640b54f3d22a34d6565e9c78c441a15222577 and added to the header only in 777c47acbeecf9602cc465864c9f5f2c609c989d,
+// so use PKCS5_PBKDF2_HMAC_SHA1 unconditionally if OpenSSL < 1.0.0
+ if(!PKCS5_PBKDF2_HMAC_SHA1((const char *)pass, pass_sz, salt, salt_sz, workfactor, key_sz, key)) goto error;
+#else
+ switch(algorithm) {
+ case SQLCIPHER_HMAC_SHA1:
+ if(!PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha1(), key_sz, key)) goto error;
+ break;
+ case SQLCIPHER_HMAC_SHA256:
+ if(!PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha256(), key_sz, key)) goto error;
+ break;
+ case SQLCIPHER_HMAC_SHA512:
+ if(!PKCS5_PBKDF2_HMAC((const char *)pass, pass_sz, salt, salt_sz, workfactor, EVP_sha512(), key_sz, key)) goto error;
+ break;
+ default:
+ return SQLITE_ERROR;
+ }
+#endif
+
+ goto cleanup;
+error:
+ rc = SQLITE_ERROR;
+cleanup:
+ return rc;
}
static int sqlcipher_openssl_cipher(void *ctx, int mode, unsigned char *key, int key_sz, unsigned char *iv, unsigned char *in, int in_sz, unsigned char *out) {
- int tmp_csz, csz;
+ int tmp_csz, csz, rc = SQLITE_OK;
EVP_CIPHER_CTX* ectx = EVP_CIPHER_CTX_new();
- if(ectx == NULL) return SQLITE_ERROR;
- EVP_CipherInit_ex(ectx, ((openssl_ctx *)ctx)->evp_cipher, NULL, NULL, NULL, mode);
- EVP_CIPHER_CTX_set_padding(ectx, 0); // no padding
- EVP_CipherInit_ex(ectx, NULL, NULL, key, iv, mode);
- EVP_CipherUpdate(ectx, out, &tmp_csz, in, in_sz);
+ if(ectx == NULL) goto error;
+ if(!EVP_CipherInit_ex(ectx, ((openssl_ctx *)ctx)->evp_cipher, NULL, NULL, NULL, mode)) goto error;
+ if(!EVP_CIPHER_CTX_set_padding(ectx, 0)) goto error; /* no padding */
+ if(!EVP_CipherInit_ex(ectx, NULL, NULL, key, iv, mode)) goto error;
+ if(!EVP_CipherUpdate(ectx, out, &tmp_csz, in, in_sz)) goto error;
csz = tmp_csz;
out += tmp_csz;
- EVP_CipherFinal_ex(ectx, out, &tmp_csz);
+ if(!EVP_CipherFinal_ex(ectx, out, &tmp_csz)) goto error;
csz += tmp_csz;
- EVP_CIPHER_CTX_free(ectx);
assert(in_sz == csz);
- return SQLITE_OK;
-}
-static int sqlcipher_openssl_set_cipher(void *ctx, const char *cipher_name) {
- openssl_ctx *o_ctx = (openssl_ctx *)ctx;
- EVP_CIPHER* cipher = (EVP_CIPHER *) EVP_get_cipherbyname(cipher_name);
- if(cipher != NULL) {
- o_ctx->evp_cipher = cipher;
- }
- return cipher != NULL ? SQLITE_OK : SQLITE_ERROR;
+ goto cleanup;
+error:
+ rc = SQLITE_ERROR;
+cleanup:
+ if(ectx) EVP_CIPHER_CTX_free(ectx);
+ return rc;
}
static const char* sqlcipher_openssl_get_cipher(void *ctx) {
- return EVP_CIPHER_name(((openssl_ctx *)ctx)->evp_cipher);
+ return OBJ_nid2sn(EVP_CIPHER_nid(((openssl_ctx *)ctx)->evp_cipher));
}
static int sqlcipher_openssl_get_key_sz(void *ctx) {
@@ -20459,24 +24370,32 @@ static int sqlcipher_openssl_get_block_sz(void *ctx) {
return EVP_CIPHER_block_size(((openssl_ctx *)ctx)->evp_cipher);
}
-static int sqlcipher_openssl_get_hmac_sz(void *ctx) {
- return EVP_MD_size(EVP_sha1());
-}
-
-static int sqlcipher_openssl_ctx_copy(void *target_ctx, void *source_ctx) {
- memcpy(target_ctx, source_ctx, sizeof(openssl_ctx));
- return SQLITE_OK;
-}
-
-static int sqlcipher_openssl_ctx_cmp(void *c1, void *c2) {
- return ((openssl_ctx *)c1)->evp_cipher == ((openssl_ctx *)c2)->evp_cipher;
+static int sqlcipher_openssl_get_hmac_sz(void *ctx, int algorithm) {
+ switch(algorithm) {
+ case SQLCIPHER_HMAC_SHA1:
+ return EVP_MD_size(EVP_sha1());
+ break;
+ case SQLCIPHER_HMAC_SHA256:
+ return EVP_MD_size(EVP_sha256());
+ break;
+ case SQLCIPHER_HMAC_SHA512:
+ return EVP_MD_size(EVP_sha512());
+ break;
+ default:
+ return 0;
+ }
}
static int sqlcipher_openssl_ctx_init(void **ctx) {
+ openssl_ctx *o_ctx;
+
*ctx = sqlcipher_malloc(sizeof(openssl_ctx));
if(*ctx == NULL) return SQLITE_NOMEM;
sqlcipher_openssl_activate(*ctx);
- return SQLITE_OK;
+
+ o_ctx = (openssl_ctx *)*ctx;
+ o_ctx->evp_cipher = (EVP_CIPHER *) EVP_get_cipherbyname(OPENSSL_CIPHER);
+ return o_ctx->evp_cipher != NULL ? SQLITE_OK : SQLITE_ERROR;
}
static int sqlcipher_openssl_ctx_free(void **ctx) {
@@ -20493,7 +24412,7 @@ static int sqlcipher_openssl_fips_status(void *ctx) {
#endif
}
-int sqlcipher_openssl_setup(sqlcipher_provider *p) {
+SQLITE_PRIVATE int sqlcipher_openssl_setup(sqlcipher_provider *p) {
p->activate = sqlcipher_openssl_activate;
p->deactivate = sqlcipher_openssl_deactivate;
p->get_provider_name = sqlcipher_openssl_get_provider_name;
@@ -20501,14 +24420,11 @@ int sqlcipher_openssl_setup(sqlcipher_provider *p) {
p->hmac = sqlcipher_openssl_hmac;
p->kdf = sqlcipher_openssl_kdf;
p->cipher = sqlcipher_openssl_cipher;
- p->set_cipher = sqlcipher_openssl_set_cipher;
p->get_cipher = sqlcipher_openssl_get_cipher;
p->get_key_sz = sqlcipher_openssl_get_key_sz;
p->get_iv_sz = sqlcipher_openssl_get_iv_sz;
p->get_block_sz = sqlcipher_openssl_get_block_sz;
p->get_hmac_sz = sqlcipher_openssl_get_hmac_sz;
- p->ctx_copy = sqlcipher_openssl_ctx_copy;
- p->ctx_cmp = sqlcipher_openssl_ctx_cmp;
p->ctx_init = sqlcipher_openssl_ctx_init;
p->ctx_free = sqlcipher_openssl_ctx_free;
p->add_random = sqlcipher_openssl_add_random;
@@ -20517,180 +24433,44 @@ int sqlcipher_openssl_setup(sqlcipher_provider *p) {
return SQLITE_OK;
}
-#endif
-#endif
-/* END SQLCIPHER */
-
-/************** End of crypto_openssl.c **************************************/
-/************** Begin file crypto_cc.c ***************************************/
-/*
-** SQLCipher
-** http://sqlcipher.net
-**
-** Copyright (c) 2008 - 2013, ZETETIC LLC
-** All rights reserved.
-**
-** Redistribution and use in source and binary forms, with or without
-** modification, are permitted provided that the following conditions are met:
-** * Redistributions of source code must retain the above copyright
-** notice, this list of conditions and the following disclaimer.
-** * Redistributions in binary form must reproduce the above copyright
-** notice, this list of conditions and the following disclaimer in the
-** documentation and/or other materials provided with the distribution.
-** * Neither the name of the ZETETIC LLC nor the
-** names of its contributors may be used to endorse or promote products
-** derived from this software without specific prior written permission.
-**
-** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
-** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
-** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-**
-*/
-/* BEGIN SQLCIPHER */
-#ifdef SQLITE_HAS_CODEC
-#ifdef SQLCIPHER_CRYPTO_CC
-/* #include "crypto.h" */
-/* #include "sqlcipher.h" */
-#include <CommonCrypto/CommonCrypto.h>
-#include <Security/SecRandom.h>
-#include <CoreFoundation/CoreFoundation.h>
-
-static int sqlcipher_cc_add_random(void *ctx, void *buffer, int length) {
- return SQLITE_OK;
-}
-
-/* generate a defined number of random bytes */
-static int sqlcipher_cc_random (void *ctx, void *buffer, int length) {
- return (SecRandomCopyBytes(kSecRandomDefault, length, (uint8_t *)buffer) == 0) ? SQLITE_OK : SQLITE_ERROR;
-}
-
-static const char* sqlcipher_cc_get_provider_name(void *ctx) {
- return "commoncrypto";
-}
+void sqlcipher_activate() {
+ CODEC_TRACE_MUTEX("sqlcipher_activate: entering static master mutex\n");
+ tdsqlite3_mutex_enter(tdsqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ CODEC_TRACE_MUTEX("sqlcipher_activate: entered static master mutex\n");
-static const char* sqlcipher_cc_get_provider_version(void *ctx) {
-#if TARGET_OS_MAC
- CFTypeRef version;
- CFBundleRef bundle = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"));
- if(bundle == NULL) {
- return "unknown";
+ /* allocate new mutexes */
+ if(sqlcipher_activate_count == 0) {
+ int i;
+ for(i = 0; i < SQLCIPHER_MUTEX_COUNT; i++) {
+ sqlcipher_static_mutex[i] = tdsqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ }
}
- version = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"));
- return CFStringGetCStringPtr(version, kCFStringEncodingUTF8);
-#else
- return "unknown";
-#endif
-}
-
-static int sqlcipher_cc_hmac(void *ctx, unsigned char *hmac_key, int key_sz, unsigned char *in, int in_sz, unsigned char *in2, int in2_sz, unsigned char *out) {
- CCHmacContext hmac_context;
- CCHmacInit(&hmac_context, kCCHmacAlgSHA1, hmac_key, key_sz);
- CCHmacUpdate(&hmac_context, in, in_sz);
- CCHmacUpdate(&hmac_context, in2, in2_sz);
- CCHmacFinal(&hmac_context, out);
- return SQLITE_OK;
-}
-
-static int sqlcipher_cc_kdf(void *ctx, const unsigned char *pass, int pass_sz, unsigned char* salt, int salt_sz, int workfactor, int key_sz, unsigned char *key) {
- CCKeyDerivationPBKDF(kCCPBKDF2, (const char *)pass, pass_sz, salt, salt_sz, kCCPRFHmacAlgSHA1, workfactor, key, key_sz);
- return SQLITE_OK;
-}
-
-static int sqlcipher_cc_cipher(void *ctx, int mode, unsigned char *key, int key_sz, unsigned char *iv, unsigned char *in, int in_sz, unsigned char *out) {
- CCCryptorRef cryptor;
- size_t tmp_csz, csz;
- CCOperation op = mode == CIPHER_ENCRYPT ? kCCEncrypt : kCCDecrypt;
-
- CCCryptorCreate(op, kCCAlgorithmAES128, 0, key, kCCKeySizeAES256, iv, &cryptor);
- CCCryptorUpdate(cryptor, in, in_sz, out, in_sz, &tmp_csz);
- csz = tmp_csz;
- out += tmp_csz;
- CCCryptorFinal(cryptor, out, in_sz - csz, &tmp_csz);
- csz += tmp_csz;
- CCCryptorRelease(cryptor);
- assert(in_sz == csz);
-
- return SQLITE_OK;
-}
-
-static int sqlcipher_cc_set_cipher(void *ctx, const char *cipher_name) {
- return SQLITE_OK;
-}
-
-static const char* sqlcipher_cc_get_cipher(void *ctx) {
- return "aes-256-cbc";
-}
-static int sqlcipher_cc_get_key_sz(void *ctx) {
- return kCCKeySizeAES256;
-}
-
-static int sqlcipher_cc_get_iv_sz(void *ctx) {
- return kCCBlockSizeAES128;
-}
-
-static int sqlcipher_cc_get_block_sz(void *ctx) {
- return kCCBlockSizeAES128;
-}
-
-static int sqlcipher_cc_get_hmac_sz(void *ctx) {
- return CC_SHA1_DIGEST_LENGTH;
-}
-
-static int sqlcipher_cc_ctx_copy(void *target_ctx, void *source_ctx) {
- return SQLITE_OK;
-}
-
-static int sqlcipher_cc_ctx_cmp(void *c1, void *c2) {
- return 1; /* always indicate contexts are the same */
-}
-
-static int sqlcipher_cc_ctx_init(void **ctx) {
- return SQLITE_OK;
-}
-
-static int sqlcipher_cc_ctx_free(void **ctx) {
- return SQLITE_OK;
-}
+ /* check to see if there is a provider registered at this point
+ if there no provider registered at this point, register the
+ default provider */
+ if(sqlcipher_get_provider() == NULL) {
+ sqlcipher_provider *p = sqlcipher_malloc(sizeof(sqlcipher_provider));
+ sqlcipher_openssl_setup(p);
+ CODEC_TRACE("sqlcipher_activate: calling sqlcipher_register_provider(%p)\n", p);
+#ifdef SQLCIPHER_EXT
+ sqlcipher_ext_provider_setup(p);
+#endif
+ sqlcipher_register_provider(p);
+ CODEC_TRACE("sqlcipher_activate: called sqlcipher_register_provider(%p)\n",p);
+ }
-static int sqlcipher_cc_fips_status(void *ctx) {
- return 0;
-}
+ sqlcipher_activate_count++; /* increment activation count */
-int sqlcipher_cc_setup(sqlcipher_provider *p) {
- p->random = sqlcipher_cc_random;
- p->get_provider_name = sqlcipher_cc_get_provider_name;
- p->hmac = sqlcipher_cc_hmac;
- p->kdf = sqlcipher_cc_kdf;
- p->cipher = sqlcipher_cc_cipher;
- p->set_cipher = sqlcipher_cc_set_cipher;
- p->get_cipher = sqlcipher_cc_get_cipher;
- p->get_key_sz = sqlcipher_cc_get_key_sz;
- p->get_iv_sz = sqlcipher_cc_get_iv_sz;
- p->get_block_sz = sqlcipher_cc_get_block_sz;
- p->get_hmac_sz = sqlcipher_cc_get_hmac_sz;
- p->ctx_copy = sqlcipher_cc_ctx_copy;
- p->ctx_cmp = sqlcipher_cc_ctx_cmp;
- p->ctx_init = sqlcipher_cc_ctx_init;
- p->ctx_free = sqlcipher_cc_ctx_free;
- p->add_random = sqlcipher_cc_add_random;
- p->fips_status = sqlcipher_cc_fips_status;
- p->get_provider_version = sqlcipher_cc_get_provider_version;
- return SQLITE_OK;
+ CODEC_TRACE_MUTEX("sqlcipher_activate: leaving static master mutex\n");
+ tdsqlite3_mutex_leave(tdsqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER));
+ CODEC_TRACE_MUTEX("sqlcipher_activate: left static master mutex\n");
}
#endif
-#endif
/* END SQLCIPHER */
-/************** End of crypto_cc.c *******************************************/
+/************** End of crypto_openssl.c **************************************/
/************** Begin file global.c ******************************************/
/*
** 2008 June 13
@@ -20715,7 +24495,7 @@ int sqlcipher_cc_setup(sqlcipher_provider *p) {
** handle case conversions for the UTF character set since the tables
** involved are nearly as big or bigger than SQLite itself.
*/
-SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = {
+SQLITE_PRIVATE const unsigned char tdsqlite3UpperToLower[] = {
#ifdef SQLITE_ASCII
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
@@ -20773,7 +24553,7 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = {
**
** (x & ~(map[x]&0x20))
**
-** The equivalent of tolower() is implemented using the sqlite3UpperToLower[]
+** The equivalent of tolower() is implemented using the tdsqlite3UpperToLower[]
** array. tolower() is used more often than toupper() by SQLite.
**
** Bit 0x40 is set if the character is non-alphanumeric and can be used in an
@@ -20781,8 +24561,7 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = {
** non-ASCII UTF character. Hence the test for whether or not a character is
** part of an identifier is 0x46.
*/
-#ifdef SQLITE_ASCII
-SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = {
+SQLITE_PRIVATE const unsigned char tdsqlite3CtypeMap[256] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00..07 ........ */
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, /* 08..0f ........ */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10..17 ........ */
@@ -20819,7 +24598,6 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = {
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* f0..f7 ........ */
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 /* f8..ff ........ */
};
-#endif
/* EVIDENCE-OF: R-02982-34736 In order to maintain full backwards
** compatibility for legacy applications, the URI filename capability is
@@ -20831,17 +24609,31 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = {
** EVIDENCE-OF: R-43642-56306 By default, URI handling is globally
** disabled. The default value may be changed by compiling with the
** SQLITE_USE_URI symbol defined.
+**
+** URI filenames are enabled by default if SQLITE_HAS_CODEC is
+** enabled.
*/
#ifndef SQLITE_USE_URI
-# define SQLITE_USE_URI 0
+# ifdef SQLITE_HAS_CODEC
+# define SQLITE_USE_URI 1
+# else
+# define SQLITE_USE_URI 0
+# endif
#endif
/* EVIDENCE-OF: R-38720-18127 The default setting is determined by the
** SQLITE_ALLOW_COVERING_INDEX_SCAN compile-time option, or is "on" if
** that compile-time option is omitted.
*/
-#ifndef SQLITE_ALLOW_COVERING_INDEX_SCAN
+#if !defined(SQLITE_ALLOW_COVERING_INDEX_SCAN)
# define SQLITE_ALLOW_COVERING_INDEX_SCAN 1
+#else
+# if !SQLITE_ALLOW_COVERING_INDEX_SCAN
+# error "Compile-time disabling of covering index scan using the\
+ -DSQLITE_ALLOW_COVERING_INDEX_SCAN=0 option is deprecated.\
+ Contact SQLite developers if this is a problem for you, and\
+ delete this #error macro to continue with your build."
+# endif
#endif
/* The minimum PMA size is set to this value multiplied by the database
@@ -20864,19 +24656,49 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = {
#endif
/*
+** The default lookaside-configuration, the format "SZ,N". SZ is the
+** number of bytes in each lookaside slot (should be a multiple of 8)
+** and N is the number of slots. The lookaside-configuration can be
+** changed as start-time using tdsqlite3_config(SQLITE_CONFIG_LOOKASIDE)
+** or at run-time for an individual database connection using
+** tdsqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE);
+**
+** With the two-size-lookaside enhancement, less lookaside is required.
+** The default configuration of 1200,40 actually provides 30 1200-byte slots
+** and 93 128-byte slots, which is more lookaside than is available
+** using the older 1200,100 configuration without two-size-lookaside.
+*/
+#ifndef SQLITE_DEFAULT_LOOKASIDE
+# ifdef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+# define SQLITE_DEFAULT_LOOKASIDE 1200,100 /* 120KB of memory */
+# else
+# define SQLITE_DEFAULT_LOOKASIDE 1200,40 /* 48KB of memory */
+# endif
+#endif
+
+
+/* The default maximum size of an in-memory database created using
+** tdsqlite3_deserialize()
+*/
+#ifndef SQLITE_MEMDB_DEFAULT_MAXSIZE
+# define SQLITE_MEMDB_DEFAULT_MAXSIZE 1073741824
+#endif
+
+/*
** The following singleton contains the global configuration for
** the SQLite library.
*/
-SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = {
+SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config tdsqlite3Config = {
SQLITE_DEFAULT_MEMSTATUS, /* bMemstat */
1, /* bCoreMutex */
SQLITE_THREADSAFE==1, /* bFullMutex */
SQLITE_USE_URI, /* bOpenUri */
SQLITE_ALLOW_COVERING_INDEX_SCAN, /* bUseCis */
+ 0, /* bSmallMalloc */
+ 1, /* bExtraSchemaChecks */
0x7ffffffe, /* mxStrlen */
0, /* neverCorrupt */
- 128, /* szLookaside */
- 500, /* nLookaside */
+ SQLITE_DEFAULT_LOOKASIDE, /* szLookaside, nLookaside */
SQLITE_STMTJRNL_SPILL, /* nStmtSpill */
{0,0,0,0,0,0,0,0}, /* m */
{0,0,0,0,0,0,0,0,0}, /* mutex */
@@ -20886,9 +24708,6 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = {
0, 0, /* mnHeap, mxHeap */
SQLITE_DEFAULT_MMAP_SIZE, /* szMmap */
SQLITE_MAX_MMAP_SIZE, /* mxMmap */
- (void*)0, /* pScratch */
- 0, /* szScratch */
- 0, /* nScratch */
(void*)0, /* pPage */
0, /* szPage */
SQLITE_DEFAULT_PCACHE_INITSZ, /* nPage */
@@ -20913,11 +24732,16 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = {
0, /* xVdbeBranch */
0, /* pVbeBranchArg */
#endif
-#ifndef SQLITE_OMIT_BUILTIN_TEST
+#ifdef SQLITE_ENABLE_DESERIALIZE
+ SQLITE_MEMDB_DEFAULT_MAXSIZE, /* mxMemdbSize */
+#endif
+#ifndef SQLITE_UNTESTABLE
0, /* xTestCallback */
#endif
0, /* bLocaltimeFault */
- 0x7ffffffe /* iOnceResetThreshold */
+ 0x7ffffffe, /* iOnceResetThreshold */
+ SQLITE_DEFAULT_SORTERREF_SIZE, /* szSorterRef */
+ 0, /* iPrngSeed */
};
/*
@@ -20925,16 +24749,15 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = {
** database connections. After initialization, this table is
** read-only.
*/
-SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions;
+SQLITE_PRIVATE FuncDefHash tdsqlite3BuiltinFunctions;
+#ifdef VDBE_PROFILE
/*
-** Constant tokens for values 0 and 1.
+** The following performance counter can be used in place of
+** tdsqlite3Hwtime() for profiling. This is a no-op on standard builds.
*/
-SQLITE_PRIVATE const Token sqlite3IntTokens[] = {
- { "0", 1 },
- { "1", 1 }
-};
-
+SQLITE_PRIVATE tdsqlite3_uint64 tdsqlite3NProfileCnt = 0;
+#endif
/*
** The value of the "pending" byte must be 0x40000000 (1 byte past the
@@ -20946,7 +24769,7 @@ SQLITE_PRIVATE const Token sqlite3IntTokens[] = {
** During testing, it is often desirable to move the pending byte to
** a different position in the file. This allows code that has to
** deal with the pending byte to run on files that are much smaller
-** than 1 GiB. The sqlite3_test_control() interface can be used to
+** than 1 GiB. The tdsqlite3_test_control() interface can be used to
** move the pending byte.
**
** IMPORTANT: Changing the pending byte to any value other than
@@ -20955,7 +24778,7 @@ SQLITE_PRIVATE const Token sqlite3IntTokens[] = {
** and incorrect behavior.
*/
#ifndef SQLITE_OMIT_WSD
-SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000;
+SQLITE_PRIVATE int tdsqlite3PendingByte = 0x40000000;
#endif
/* #include "opcodes.h" */
@@ -20965,468 +24788,14 @@ SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000;
** from the comments following the "case OP_xxxx:" statements in
** the vdbe.c file.
*/
-SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[] = OPFLG_INITIALIZER;
+SQLITE_PRIVATE const unsigned char tdsqlite3OpcodeProperty[] = OPFLG_INITIALIZER;
/*
** Name of the default collating sequence
*/
-SQLITE_PRIVATE const char sqlite3StrBINARY[] = "BINARY";
+SQLITE_PRIVATE const char tdsqlite3StrBINARY[] = "BINARY";
/************** End of global.c **********************************************/
-/************** Begin file ctime.c *******************************************/
-/*
-** 2010 February 23
-**
-** The author disclaims copyright to this source code. In place of
-** a legal notice, here is a blessing:
-**
-** May you do good and not evil.
-** May you find forgiveness for yourself and forgive others.
-** May you share freely, never taking more than you give.
-**
-*************************************************************************
-**
-** This file implements routines used to report what compile-time options
-** SQLite was built with.
-*/
-
-#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
-
-/* #include "sqliteInt.h" */
-
-/*
-** An array of names of all compile-time options. This array should
-** be sorted A-Z.
-**
-** This array looks large, but in a typical installation actually uses
-** only a handful of compile-time options, so most times this array is usually
-** rather short and uses little memory space.
-*/
-static const char * const azCompileOpt[] = {
-
-/* These macros are provided to "stringify" the value of the define
-** for those options in which the value is meaningful. */
-#define CTIMEOPT_VAL_(opt) #opt
-#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
-
-#if SQLITE_32BIT_ROWID
- "32BIT_ROWID",
-#endif
-#if SQLITE_4_BYTE_ALIGNED_MALLOC
- "4_BYTE_ALIGNED_MALLOC",
-#endif
-#if SQLITE_CASE_SENSITIVE_LIKE
- "CASE_SENSITIVE_LIKE",
-#endif
-#if SQLITE_CHECK_PAGES
- "CHECK_PAGES",
-#endif
-#if defined(__clang__) && defined(__clang_major__)
- "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "."
- CTIMEOPT_VAL(__clang_minor__) "."
- CTIMEOPT_VAL(__clang_patchlevel__),
-#elif defined(_MSC_VER)
- "COMPILER=msvc-" CTIMEOPT_VAL(_MSC_VER),
-#elif defined(__GNUC__) && defined(__VERSION__)
- "COMPILER=gcc-" __VERSION__,
-#endif
-#if SQLITE_COVERAGE_TEST
- "COVERAGE_TEST",
-#endif
-#if SQLITE_DEBUG
- "DEBUG",
-#endif
-#if SQLITE_DEFAULT_LOCKING_MODE
- "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE),
-#endif
-#if defined(SQLITE_DEFAULT_MMAP_SIZE) && !defined(SQLITE_DEFAULT_MMAP_SIZE_xc)
- "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE),
-#endif
-#if SQLITE_DISABLE_DIRSYNC
- "DISABLE_DIRSYNC",
-#endif
-#if SQLITE_DISABLE_LFS
- "DISABLE_LFS",
-#endif
-#if SQLITE_ENABLE_8_3_NAMES
- "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES),
-#endif
-#if SQLITE_ENABLE_API_ARMOR
- "ENABLE_API_ARMOR",
-#endif
-#if SQLITE_ENABLE_ATOMIC_WRITE
- "ENABLE_ATOMIC_WRITE",
-#endif
-#if SQLITE_ENABLE_CEROD
- "ENABLE_CEROD",
-#endif
-#if SQLITE_ENABLE_COLUMN_METADATA
- "ENABLE_COLUMN_METADATA",
-#endif
-#if SQLITE_ENABLE_DBSTAT_VTAB
- "ENABLE_DBSTAT_VTAB",
-#endif
-#if SQLITE_ENABLE_EXPENSIVE_ASSERT
- "ENABLE_EXPENSIVE_ASSERT",
-#endif
-#if SQLITE_ENABLE_FTS1
- "ENABLE_FTS1",
-#endif
-#if SQLITE_ENABLE_FTS2
- "ENABLE_FTS2",
-#endif
-#if SQLITE_ENABLE_FTS3
- "ENABLE_FTS3",
-#endif
-#if SQLITE_ENABLE_FTS3_PARENTHESIS
- "ENABLE_FTS3_PARENTHESIS",
-#endif
-#if SQLITE_ENABLE_FTS4
- "ENABLE_FTS4",
-#endif
-#if SQLITE_ENABLE_FTS5
- "ENABLE_FTS5",
-#endif
-#if SQLITE_ENABLE_ICU
- "ENABLE_ICU",
-#endif
-#if SQLITE_ENABLE_IOTRACE
- "ENABLE_IOTRACE",
-#endif
-#if SQLITE_ENABLE_JSON1
- "ENABLE_JSON1",
-#endif
-#if SQLITE_ENABLE_LOAD_EXTENSION
- "ENABLE_LOAD_EXTENSION",
-#endif
-#if SQLITE_ENABLE_LOCKING_STYLE
- "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE),
-#endif
-#if SQLITE_ENABLE_MEMORY_MANAGEMENT
- "ENABLE_MEMORY_MANAGEMENT",
-#endif
-#if SQLITE_ENABLE_MEMSYS3
- "ENABLE_MEMSYS3",
-#endif
-#if SQLITE_ENABLE_MEMSYS5
- "ENABLE_MEMSYS5",
-#endif
-#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK
- "ENABLE_OVERSIZE_CELL_CHECK",
-#endif
-#if SQLITE_ENABLE_RTREE
- "ENABLE_RTREE",
-#endif
-#if defined(SQLITE_ENABLE_STAT4)
- "ENABLE_STAT4",
-#elif defined(SQLITE_ENABLE_STAT3)
- "ENABLE_STAT3",
-#endif
-#if SQLITE_ENABLE_UNLOCK_NOTIFY
- "ENABLE_UNLOCK_NOTIFY",
-#endif
-#if SQLITE_ENABLE_UPDATE_DELETE_LIMIT
- "ENABLE_UPDATE_DELETE_LIMIT",
-#endif
-#if SQLITE_HAS_CODEC
- "HAS_CODEC",
-#endif
-#if HAVE_ISNAN || SQLITE_HAVE_ISNAN
- "HAVE_ISNAN",
-#endif
-#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX
- "HOMEGROWN_RECURSIVE_MUTEX",
-#endif
-#if SQLITE_IGNORE_AFP_LOCK_ERRORS
- "IGNORE_AFP_LOCK_ERRORS",
-#endif
-#if SQLITE_IGNORE_FLOCK_LOCK_ERRORS
- "IGNORE_FLOCK_LOCK_ERRORS",
-#endif
-#ifdef SQLITE_INT64_TYPE
- "INT64_TYPE",
-#endif
-#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS
- "LIKE_DOESNT_MATCH_BLOBS",
-#endif
-#if SQLITE_LOCK_TRACE
- "LOCK_TRACE",
-#endif
-#if defined(SQLITE_MAX_MMAP_SIZE) && !defined(SQLITE_MAX_MMAP_SIZE_xc)
- "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE),
-#endif
-#ifdef SQLITE_MAX_SCHEMA_RETRY
- "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY),
-#endif
-#if SQLITE_MEMDEBUG
- "MEMDEBUG",
-#endif
-#if SQLITE_MIXED_ENDIAN_64BIT_FLOAT
- "MIXED_ENDIAN_64BIT_FLOAT",
-#endif
-#if SQLITE_NO_SYNC
- "NO_SYNC",
-#endif
-#if SQLITE_OMIT_ALTERTABLE
- "OMIT_ALTERTABLE",
-#endif
-#if SQLITE_OMIT_ANALYZE
- "OMIT_ANALYZE",
-#endif
-#if SQLITE_OMIT_ATTACH
- "OMIT_ATTACH",
-#endif
-#if SQLITE_OMIT_AUTHORIZATION
- "OMIT_AUTHORIZATION",
-#endif
-#if SQLITE_OMIT_AUTOINCREMENT
- "OMIT_AUTOINCREMENT",
-#endif
-#if SQLITE_OMIT_AUTOINIT
- "OMIT_AUTOINIT",
-#endif
-#if SQLITE_OMIT_AUTOMATIC_INDEX
- "OMIT_AUTOMATIC_INDEX",
-#endif
-#if SQLITE_OMIT_AUTORESET
- "OMIT_AUTORESET",
-#endif
-#if SQLITE_OMIT_AUTOVACUUM
- "OMIT_AUTOVACUUM",
-#endif
-#if SQLITE_OMIT_BETWEEN_OPTIMIZATION
- "OMIT_BETWEEN_OPTIMIZATION",
-#endif
-#if SQLITE_OMIT_BLOB_LITERAL
- "OMIT_BLOB_LITERAL",
-#endif
-#if SQLITE_OMIT_BTREECOUNT
- "OMIT_BTREECOUNT",
-#endif
-#if SQLITE_OMIT_BUILTIN_TEST
- "OMIT_BUILTIN_TEST",
-#endif
-#if SQLITE_OMIT_CAST
- "OMIT_CAST",
-#endif
-#if SQLITE_OMIT_CHECK
- "OMIT_CHECK",
-#endif
-#if SQLITE_OMIT_COMPLETE
- "OMIT_COMPLETE",
-#endif
-#if SQLITE_OMIT_COMPOUND_SELECT
- "OMIT_COMPOUND_SELECT",
-#endif
-#if SQLITE_OMIT_CTE
- "OMIT_CTE",
-#endif
-#if SQLITE_OMIT_DATETIME_FUNCS
- "OMIT_DATETIME_FUNCS",
-#endif
-#if SQLITE_OMIT_DECLTYPE
- "OMIT_DECLTYPE",
-#endif
-#if SQLITE_OMIT_DEPRECATED
- "OMIT_DEPRECATED",
-#endif
-#if SQLITE_OMIT_DISKIO
- "OMIT_DISKIO",
-#endif
-#if SQLITE_OMIT_EXPLAIN
- "OMIT_EXPLAIN",
-#endif
-#if SQLITE_OMIT_FLAG_PRAGMAS
- "OMIT_FLAG_PRAGMAS",
-#endif
-#if SQLITE_OMIT_FLOATING_POINT
- "OMIT_FLOATING_POINT",
-#endif
-#if SQLITE_OMIT_FOREIGN_KEY
- "OMIT_FOREIGN_KEY",
-#endif
-#if SQLITE_OMIT_GET_TABLE
- "OMIT_GET_TABLE",
-#endif
-#if SQLITE_OMIT_INCRBLOB
- "OMIT_INCRBLOB",
-#endif
-#if SQLITE_OMIT_INTEGRITY_CHECK
- "OMIT_INTEGRITY_CHECK",
-#endif
-#if SQLITE_OMIT_LIKE_OPTIMIZATION
- "OMIT_LIKE_OPTIMIZATION",
-#endif
-#if SQLITE_OMIT_LOAD_EXTENSION
- "OMIT_LOAD_EXTENSION",
-#endif
-#if SQLITE_OMIT_LOCALTIME
- "OMIT_LOCALTIME",
-#endif
-#if SQLITE_OMIT_LOOKASIDE
- "OMIT_LOOKASIDE",
-#endif
-#if SQLITE_OMIT_MEMORYDB
- "OMIT_MEMORYDB",
-#endif
-#if SQLITE_OMIT_OR_OPTIMIZATION
- "OMIT_OR_OPTIMIZATION",
-#endif
-#if SQLITE_OMIT_PAGER_PRAGMAS
- "OMIT_PAGER_PRAGMAS",
-#endif
-#if SQLITE_OMIT_PRAGMA
- "OMIT_PRAGMA",
-#endif
-#if SQLITE_OMIT_PROGRESS_CALLBACK
- "OMIT_PROGRESS_CALLBACK",
-#endif
-#if SQLITE_OMIT_QUICKBALANCE
- "OMIT_QUICKBALANCE",
-#endif
-#if SQLITE_OMIT_REINDEX
- "OMIT_REINDEX",
-#endif
-#if SQLITE_OMIT_SCHEMA_PRAGMAS
- "OMIT_SCHEMA_PRAGMAS",
-#endif
-#if SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
- "OMIT_SCHEMA_VERSION_PRAGMAS",
-#endif
-#if SQLITE_OMIT_SHARED_CACHE
- "OMIT_SHARED_CACHE",
-#endif
-#if SQLITE_OMIT_SUBQUERY
- "OMIT_SUBQUERY",
-#endif
-#if SQLITE_OMIT_TCL_VARIABLE
- "OMIT_TCL_VARIABLE",
-#endif
-#if SQLITE_OMIT_TEMPDB
- "OMIT_TEMPDB",
-#endif
-#if SQLITE_OMIT_TRACE
- "OMIT_TRACE",
-#endif
-#if SQLITE_OMIT_TRIGGER
- "OMIT_TRIGGER",
-#endif
-#if SQLITE_OMIT_TRUNCATE_OPTIMIZATION
- "OMIT_TRUNCATE_OPTIMIZATION",
-#endif
-#if SQLITE_OMIT_UTF16
- "OMIT_UTF16",
-#endif
-#if SQLITE_OMIT_VACUUM
- "OMIT_VACUUM",
-#endif
-#if SQLITE_OMIT_VIEW
- "OMIT_VIEW",
-#endif
-#if SQLITE_OMIT_VIRTUALTABLE
- "OMIT_VIRTUALTABLE",
-#endif
-#if SQLITE_OMIT_WAL
- "OMIT_WAL",
-#endif
-#if SQLITE_OMIT_WSD
- "OMIT_WSD",
-#endif
-#if SQLITE_OMIT_XFER_OPT
- "OMIT_XFER_OPT",
-#endif
-#if SQLITE_PERFORMANCE_TRACE
- "PERFORMANCE_TRACE",
-#endif
-#if SQLITE_PROXY_DEBUG
- "PROXY_DEBUG",
-#endif
-#if SQLITE_RTREE_INT_ONLY
- "RTREE_INT_ONLY",
-#endif
-#if SQLITE_SECURE_DELETE
- "SECURE_DELETE",
-#endif
-#if SQLITE_SMALL_STACK
- "SMALL_STACK",
-#endif
-#if SQLITE_SOUNDEX
- "SOUNDEX",
-#endif
-#if SQLITE_SYSTEM_MALLOC
- "SYSTEM_MALLOC",
-#endif
-#if SQLITE_TCL
- "TCL",
-#endif
-#if defined(SQLITE_TEMP_STORE) && !defined(SQLITE_TEMP_STORE_xc)
- "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE),
-#endif
-#if SQLITE_TEST
- "TEST",
-#endif
-#if defined(SQLITE_THREADSAFE)
- "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE),
-#endif
-#if SQLITE_USE_ALLOCA
- "USE_ALLOCA",
-#endif
-#if SQLITE_USER_AUTHENTICATION
- "USER_AUTHENTICATION",
-#endif
-#if SQLITE_WIN32_MALLOC
- "WIN32_MALLOC",
-#endif
-#if SQLITE_ZERO_MALLOC
- "ZERO_MALLOC"
-#endif
-};
-
-/*
-** Given the name of a compile-time option, return true if that option
-** was used and false if not.
-**
-** The name can optionally begin with "SQLITE_" but the "SQLITE_" prefix
-** is not required for a match.
-*/
-SQLITE_API int sqlite3_compileoption_used(const char *zOptName){
- int i, n;
-
-#if SQLITE_ENABLE_API_ARMOR
- if( zOptName==0 ){
- (void)SQLITE_MISUSE_BKPT;
- return 0;
- }
-#endif
- if( sqlite3StrNICmp(zOptName, "SQLITE_", 7)==0 ) zOptName += 7;
- n = sqlite3Strlen30(zOptName);
-
- /* Since ArraySize(azCompileOpt) is normally in single digits, a
- ** linear search is adequate. No need for a binary search. */
- for(i=0; i<ArraySize(azCompileOpt); i++){
- if( sqlite3StrNICmp(zOptName, azCompileOpt[i], n)==0
- && sqlite3IsIdChar((unsigned char)azCompileOpt[i][n])==0
- ){
- return 1;
- }
- }
- return 0;
-}
-
-/*
-** Return the N-th compile-time option string. If N is out of range,
-** return a NULL pointer.
-*/
-SQLITE_API const char *sqlite3_compileoption_get(int N){
- if( N>=0 && N<ArraySize(azCompileOpt) ){
- return azCompileOpt[N];
- }
- return 0;
-}
-
-#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
-
-/************** End of ctime.c ***********************************************/
/************** Begin file status.c ******************************************/
/*
** 2008 June 18
@@ -21440,7 +24809,7 @@ SQLITE_API const char *sqlite3_compileoption_get(int N){
**
*************************************************************************
**
-** This module implements the sqlite3_status() interface and related
+** This module implements the tdsqlite3_status() interface and related
** functionality.
*/
/* #include "sqliteInt.h" */
@@ -21521,57 +24890,61 @@ typedef struct AuxData AuxData;
*/
typedef struct VdbeCursor VdbeCursor;
struct VdbeCursor {
- u8 eCurType; /* One of the CURTYPE_* values above */
- i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */
- u8 nullRow; /* True if pointing to a row with no data */
- u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */
- u8 isTable; /* True for rowid tables. False for indexes */
+ u8 eCurType; /* One of the CURTYPE_* values above */
+ i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */
+ u8 nullRow; /* True if pointing to a row with no data */
+ u8 deferredMoveto; /* A call to tdsqlite3BtreeMoveto() is needed */
+ u8 isTable; /* True for rowid tables. False for indexes */
#ifdef SQLITE_DEBUG
- u8 seekOp; /* Most recent seek operation on this cursor */
- u8 wrFlag; /* The wrFlag argument to sqlite3BtreeCursor() */
-#endif
- Bool isEphemeral:1; /* True for an ephemeral table */
- Bool useRandomRowid:1;/* Generate new record numbers semi-randomly */
- Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */
- Pgno pgnoRoot; /* Root page of the open btree cursor */
- i16 nField; /* Number of fields in the header */
- u16 nHdrParsed; /* Number of header fields parsed so far */
+ u8 seekOp; /* Most recent seek operation on this cursor */
+ u8 wrFlag; /* The wrFlag argument to tdsqlite3BtreeCursor() */
+#endif
+ Bool isEphemeral:1; /* True for an ephemeral table */
+ Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */
+ Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */
+ Bool seekHit:1; /* See the OP_SeekHit and OP_IfNoHope opcodes */
+ Btree *pBtx; /* Separate file holding temporary table */
+ i64 seqCount; /* Sequence counter */
+ int *aAltMap; /* Mapping from table to index column numbers */
+
+ /* Cached OP_Column parse information is only valid if cacheStatus matches
+ ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of
+ ** CACHE_STALE (0) and so setting cacheStatus=CACHE_STALE guarantees that
+ ** the cache is out of date. */
+ u32 cacheStatus; /* Cache is valid if this matches Vdbe.cacheCtr */
+ int seekResult; /* Result of previous tdsqlite3BtreeMoveto() or 0
+ ** if there have been no prior seeks on the cursor. */
+ /* seekResult does not distinguish between "no seeks have ever occurred
+ ** on this cursor" and "the most recent seek was an exact match".
+ ** For CURTYPE_PSEUDO, seekResult is the register holding the record */
+
+ /* When a new VdbeCursor is allocated, only the fields above are zeroed.
+ ** The fields that follow are uninitialized, and must be individually
+ ** initialized prior to first use. */
+ VdbeCursor *pAltCursor; /* Associated index cursor from which to read */
union {
- BtCursor *pCursor; /* CURTYPE_BTREE. Btree cursor */
- sqlite3_vtab_cursor *pVCur; /* CURTYPE_VTAB. Vtab cursor */
- int pseudoTableReg; /* CURTYPE_PSEUDO. Reg holding content. */
- VdbeSorter *pSorter; /* CURTYPE_SORTER. Sorter object */
+ BtCursor *pCursor; /* CURTYPE_BTREE or _PSEUDO. Btree cursor */
+ tdsqlite3_vtab_cursor *pVCur; /* CURTYPE_VTAB. Vtab cursor */
+ VdbeSorter *pSorter; /* CURTYPE_SORTER. Sorter object */
} uc;
- Btree *pBt; /* Separate file holding temporary table */
- KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */
- int seekResult; /* Result of previous sqlite3BtreeMoveto() */
- i64 seqCount; /* Sequence counter */
- i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */
- VdbeCursor *pAltCursor; /* Associated index cursor from which to read */
- int *aAltMap; /* Mapping from table to index column numbers */
+ KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */
+ u32 iHdrOffset; /* Offset to next unparsed byte of the header */
+ Pgno pgnoRoot; /* Root page of the open btree cursor */
+ i16 nField; /* Number of fields in the header */
+ u16 nHdrParsed; /* Number of header fields parsed so far */
+ i64 movetoTarget; /* Argument to the deferred tdsqlite3BtreeMoveto() */
+ u32 *aOffset; /* Pointer to aType[nField] */
+ const u8 *aRow; /* Data for the current row, if all on one page */
+ u32 payloadSize; /* Total number of bytes in the record */
+ u32 szRow; /* Byte available in aRow */
#ifdef SQLITE_ENABLE_COLUMN_USED_MASK
- u64 maskUsed; /* Mask of columns used by this cursor */
+ u64 maskUsed; /* Mask of columns used by this cursor */
#endif
- /* Cached information about the header for the data record that the
- ** cursor is currently pointing to. Only valid if cacheStatus matches
- ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of
- ** CACHE_STALE and so setting cacheStatus=CACHE_STALE guarantees that
- ** the cache is out of date.
- **
- ** aRow might point to (ephemeral) data for the current row, or it might
- ** be NULL.
- */
- u32 cacheStatus; /* Cache is valid if this matches Vdbe.cacheCtr */
- u32 payloadSize; /* Total number of bytes in the record */
- u32 szRow; /* Byte available in aRow */
- u32 iHdrOffset; /* Offset to next unparsed byte of the header */
- const u8 *aRow; /* Data for the current row, if all on one page */
- u32 *aOffset; /* Pointer to aType[nField] */
- u32 aType[1]; /* Type values for all entries in the record */
/* 2*nField extra array elements allocated for aType[], beyond the one
** static element declared in the structure. nField total array slots for
** aType[] and nField+1 array slots for aOffset[] */
+ u32 aType[1]; /* Type values record decode. MUST BE LAST */
};
@@ -21595,7 +24968,7 @@ struct VdbeCursor {
** is linked into the Vdbe.pDelFrame list. The contents of the Vdbe.pDelFrame
** list is deleted when the VM is reset in VdbeHalt(). The reason for doing
** this instead of deleting the VdbeFrame immediately is to avoid recursive
-** calls to sqlite3VdbeMemRelease() when the memory cells belonging to the
+** calls to tdsqlite3VdbeMemRelease() when the memory cells belonging to the
** child frame are released.
**
** The currently executing frame is stored in Vdbe.pFrame. Vdbe.pFrame is
@@ -21609,9 +24982,13 @@ struct VdbeFrame {
i64 *anExec; /* Event counters from parent frame */
Mem *aMem; /* Array of memory cells for parent frame */
VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */
+ u8 *aOnce; /* Bitmask used by OP_Once */
void *token; /* Copy of SubProgram.token */
i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */
AuxData *pAuxData; /* Linked list of auxdata allocations */
+#if SQLITE_DEBUG
+ u32 iFrameMagic; /* magic number for sanity checking */
+#endif
int nCursor; /* Number of entries in apCsr */
int pc; /* Program Counter in parent (calling) frame */
int nOp; /* Size of aOp array */
@@ -21622,6 +24999,13 @@ struct VdbeFrame {
int nDbChange; /* Value of db->nChange */
};
+/* Magic number for sanity checking on VdbeFrame objects */
+#define SQLITE_FRAME_MAGIC 0x879fb71e
+
+/*
+** Return a pointer to the array of registers allocated for use
+** by a VdbeFrame.
+*/
#define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))])
/*
@@ -21629,14 +25013,13 @@ struct VdbeFrame {
** structures. Each Mem struct may cache multiple representations (string,
** integer etc.) of the same value.
*/
-struct Mem {
+struct tdsqlite3_value {
union MemValue {
double r; /* Real value used when MEM_Real is set in flags */
i64 i; /* Integer value used when MEM_Int is set in flags */
- int nZero; /* Used when bit MEM_Zero is set in flags */
+ int nZero; /* Extra zero bytes when MEM_Zero and MEM_Blob set */
+ const char *zPType; /* Pointer type when MEM_Term|MEM_Subtype|MEM_Null */
FuncDef *pDef; /* Used only when flags==MEM_Agg */
- RowSet *pRowSet; /* Used only when flags==MEM_RowSet */
- VdbeFrame *pFrame; /* Used when flags==MEM_Frame */
} u;
u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */
u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */
@@ -21647,11 +25030,11 @@ struct Mem {
char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */
int szMalloc; /* Size of the zMalloc allocation */
u32 uTemp; /* Transient storage for serial_type in OP_MakeRecord */
- sqlite3 *db; /* The associated database connection */
+ tdsqlite3 *db; /* The associated database connection */
void (*xDel)(void*);/* Destructor for Mem.z - only valid if MEM_Dyn */
#ifdef SQLITE_DEBUG
Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */
- void *pFiller; /* So that sizeof(Mem) is a multiple of 8 */
+ u16 mScopyFlags; /* flags value immediately after the shallow copy */
#endif
};
@@ -21665,7 +25048,8 @@ struct Mem {
** representations of the value stored in the Mem struct.
**
** If the MEM_Null flag is set, then the value is an SQL NULL value.
-** No other flags may be set in this case.
+** For a pointer type created using tdsqlite3_bind_pointer() or
+** tdsqlite3_result_pointer() the MEM_Term and MEM_Subtype flags are also set.
**
** If the MEM_Str flag is set then Mem.z points at a string representation.
** Usually this is encoded in the same unicode encoding as the main
@@ -21673,17 +25057,17 @@ struct Mem {
** set, then the string is nul terminated. The MEM_Int and MEM_Real
** flags may coexist with the MEM_Str flag.
*/
-#define MEM_Null 0x0001 /* Value is NULL */
+#define MEM_Null 0x0001 /* Value is NULL (or a pointer) */
#define MEM_Str 0x0002 /* Value is a string */
#define MEM_Int 0x0004 /* Value is an integer */
#define MEM_Real 0x0008 /* Value is a real number */
#define MEM_Blob 0x0010 /* Value is a BLOB */
-#define MEM_AffMask 0x001f /* Mask of affinity bits */
-#define MEM_RowSet 0x0020 /* Value is a RowSet object */
-#define MEM_Frame 0x0040 /* Value is a VdbeFrame object */
+#define MEM_IntReal 0x0020 /* MEM_Int that stringifies like MEM_Real */
+#define MEM_AffMask 0x003f /* Mask of affinity bits */
+#define MEM_FromBind 0x0040 /* Value originates from tdsqlite3_bind() */
#define MEM_Undefined 0x0080 /* Value is undefined */
#define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */
-#define MEM_TypeMask 0x81ff /* Mask of type bits */
+#define MEM_TypeMask 0xc1bf /* Mask of type bits */
/* Whenever Mem contains a valid string or blob representation, one of
@@ -21691,7 +25075,7 @@ struct Mem {
** policy for Mem.z. The MEM_Term flag tells us whether or not the
** string is \000 or \u0000 terminated
*/
-#define MEM_Term 0x0200 /* String rep is nul terminated */
+#define MEM_Term 0x0200 /* String in Mem.z is zero terminated */
#define MEM_Dyn 0x0400 /* Need to call Mem.xDel() on Mem.z */
#define MEM_Static 0x0800 /* Mem.z points to a static string */
#define MEM_Ephem 0x1000 /* Mem.z points to an ephemeral string */
@@ -21707,7 +25091,7 @@ struct Mem {
** that needs to be deallocated to avoid a leak.
*/
#define VdbeMemDynamic(X) \
- (((X)->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame))!=0)
+ (((X)->flags&(MEM_Agg|MEM_Dyn))!=0)
/*
** Clear any existing type flags from a Mem and replace them with f
@@ -21716,6 +25100,13 @@ struct Mem {
((p)->flags = ((p)->flags&~(MEM_TypeMask|MEM_Zero))|f)
/*
+** True if Mem X is a NULL-nochng type.
+*/
+#define MemNullNochng(X) \
+ (((X)->flags&MEM_TypeMask)==(MEM_Null|MEM_Zero) \
+ && (X)->n==0 && (X)->u.nZero==0)
+
+/*
** Return true if a memory cell is not marked as invalid. This macro
** is for use inside assert() statements only.
*/
@@ -21725,17 +25116,17 @@ struct Mem {
/*
** Each auxiliary data pointer stored by a user defined function
-** implementation calling sqlite3_set_auxdata() is stored in an instance
+** implementation calling tdsqlite3_set_auxdata() is stored in an instance
** of this structure. All such structures associated with a single VM
** are stored in a linked list headed at Vdbe.pAuxData. All are destroyed
** when the VM is halted (if not before).
*/
struct AuxData {
- int iOp; /* Instruction number of OP_Function opcode */
- int iArg; /* Index of function argument. */
+ int iAuxOp; /* Instruction number of OP_Function opcode */
+ int iAuxArg; /* Index of function argument. */
void *pAux; /* Aux data pointer */
- void (*xDelete)(void *); /* Destructor for the aux data */
- AuxData *pNext; /* Next element in list */
+ void (*xDeleteAux)(void*); /* Destructor for the aux data */
+ AuxData *pNextAux; /* Next element in list */
};
/*
@@ -21751,7 +25142,7 @@ struct AuxData {
** This structure is defined inside of vdbeInt.h because it uses substructures
** (Mem) which are only defined there.
*/
-struct sqlite3_context {
+struct tdsqlite3_context {
Mem *pOut; /* The return value is stored here */
FuncDef *pFunc; /* Pointer to function information */
Mem *pMem; /* Memory cell used to store aggregate context */
@@ -21759,9 +25150,8 @@ struct sqlite3_context {
int iOp; /* Instruction number of OP_Function */
int isError; /* Error code returned by the function. */
u8 skipFlag; /* Skip accumulator loading if true */
- u8 fErrorOrAux; /* isError!=0 or pVdbe->pAuxData modified */
u8 argc; /* Number of arguments */
- sqlite3_value *argv[1]; /* Argument set */
+ tdsqlite3_value *argv[1]; /* Argument set */
};
/* A bitfield type for use inside of structures. Always follow with :N where
@@ -21769,6 +25159,9 @@ struct sqlite3_context {
*/
typedef unsigned bft; /* Bit Field Type */
+/* The ScanStatus object holds a single value for the
+** tdsqlite3_stmt_scanstatus() interface.
+*/
typedef struct ScanStatus ScanStatus;
struct ScanStatus {
int addrExplain; /* OP_Explain for loop */
@@ -21779,19 +25172,31 @@ struct ScanStatus {
char *zName; /* Name of table or index */
};
+/* The DblquoteStr object holds the text of a double-quoted
+** string for a prepared statement. A linked list of these objects
+** is constructed during statement parsing and is held on Vdbe.pDblStr.
+** When computing a normalized SQL statement for an SQL statement, that
+** list is consulted for each double-quoted identifier to see if the
+** identifier should really be a string literal.
+*/
+typedef struct DblquoteStr DblquoteStr;
+struct DblquoteStr {
+ DblquoteStr *pNextStr; /* Next string literal in the list */
+ char z[8]; /* Dequoted value for the string */
+};
+
/*
** An instance of the virtual machine. This structure contains the complete
** state of the virtual machine.
**
-** The "sqlite3_stmt" structure pointer that is returned by sqlite3_prepare()
+** The "tdsqlite3_stmt" structure pointer that is returned by tdsqlite3_prepare()
** is really a pointer to an instance of this structure.
*/
struct Vdbe {
- sqlite3 *db; /* The database connection that owns this statement */
+ tdsqlite3 *db; /* The database connection that owns this statement */
Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */
Parse *pParse; /* Parsing context used to create this Vdbe */
ynVar nVar; /* Number of entries in aVar[] */
- ynVar nzVar; /* Number of entries in azVar[] */
u32 magic; /* Magic number for sanity checking */
int nMem; /* Number of memory locations currently allocated */
int nCursor; /* Number of slots in apCsr[] */
@@ -21799,47 +25204,53 @@ struct Vdbe {
int pc; /* The program counter */
int rc; /* Value to return */
int nChange; /* Number of db changes made since last reset */
- int iStatement; /* Statement number (or 0 if has not opened stmt) */
+ int iStatement; /* Statement number (or 0 if has no opened stmt) */
i64 iCurrentTime; /* Value of julianday('now') for this statement */
i64 nFkConstraint; /* Number of imm. FK constraints this VM */
i64 nStmtDefCons; /* Number of def. constraints when stmt started */
i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */
+ Mem *aMem; /* The memory locations */
+ Mem **apArg; /* Arguments to currently executing user function */
+ VdbeCursor **apCsr; /* One element of this array for each open cursor */
+ Mem *aVar; /* Values for the OP_Variable opcode. */
/* When allocating a new Vdbe object, all of the fields below should be
** initialized to zero or NULL */
Op *aOp; /* Space to hold the virtual machine's program */
- Mem *aMem; /* The memory locations */
- Mem **apArg; /* Arguments to currently executing user function */
+ int nOp; /* Number of instructions in the program */
+ int nOpAlloc; /* Slots allocated for aOp[] */
Mem *aColName; /* Column names to return */
Mem *pResultSet; /* Pointer to an array of results */
char *zErrMsg; /* Error message written here */
- VdbeCursor **apCsr; /* One element of this array for each open cursor */
- Mem *aVar; /* Values for the OP_Variable opcode. */
- char **azVar; /* Name of variables */
+ VList *pVList; /* Name of variables */
#ifndef SQLITE_OMIT_TRACE
i64 startTime; /* Time when query started - used for profiling */
#endif
- int nOp; /* Number of instructions in the program */
#ifdef SQLITE_DEBUG
- int rcApp; /* errcode set by sqlite3_result_error_code() */
+ int rcApp; /* errcode set by tdsqlite3_result_error_code() */
+ u32 nWrite; /* Number of write operations that have occurred */
#endif
u16 nResColumn; /* Number of columns in one row of the result set */
u8 errorAction; /* Recovery action to do in case of an error */
u8 minWriteFileFormat; /* Minimum file format for writable database files */
- bft expired:1; /* True if the VM needs to be recompiled */
- bft doingRerun:1; /* True if rerunning after an auto-reprepare */
+ u8 prepFlags; /* SQLITE_PREPARE_* flags */
+ bft expired:2; /* 1: recompile VM immediately 2: when convenient */
bft explain:2; /* True if EXPLAIN present on SQL command */
+ bft doingRerun:1; /* True if rerunning after an auto-reprepare */
bft changeCntOn:1; /* True to update the change-counter */
bft runOnlyOnce:1; /* Automatically expire on reset */
bft usesStmtJournal:1; /* True if uses a statement journal */
bft readOnly:1; /* True for statements that do not write */
bft bIsReader:1; /* True for statements that read */
- bft isPrepareV2:1; /* True if prepared with prepare_v2() */
yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */
yDbMask lockMask; /* Subset of btreeMask that requires a lock */
- u32 aCounter[5]; /* Counters used by sqlite3_stmt_status() */
+ u32 aCounter[7]; /* Counters used by tdsqlite3_stmt_status() */
char *zSql; /* Text of the SQL statement that generated this */
+#ifdef SQLITE_ENABLE_NORMALIZE
+ char *zNormSql; /* Normalization of the associated SQL statement */
+ DblquoteStr *pDblStr; /* List of double-quoted string literals */
+#endif
void *pFree; /* Free this when deleting the vdbe */
VdbeFrame *pFrame; /* Parent frame */
VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */
@@ -21850,7 +25261,7 @@ struct Vdbe {
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
i64 *anExec; /* Number of times each op has been executed */
int nScan; /* Entries in aScan[] */
- ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */
+ ScanStatus *aScan; /* Scan definitions for tdsqlite3_stmt_scanstatus() */
#endif
};
@@ -21865,7 +25276,7 @@ struct Vdbe {
/*
** Structure used to store the context required by the
-** sqlite3_preupdate_*() API functions.
+** tdsqlite3_preupdate_*() API functions.
*/
struct PreUpdate {
Vdbe *v;
@@ -21880,116 +25291,140 @@ struct PreUpdate {
i64 iKey2; /* Second key value passed to hook */
Mem *aNew; /* Array of new.* values */
Table *pTab; /* Schema object being upated */
+ Index *pPk; /* PK index if pTab is WITHOUT ROWID */
};
/*
** Function prototypes
*/
-SQLITE_PRIVATE void sqlite3VdbeError(Vdbe*, const char *, ...);
-SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*);
+SQLITE_PRIVATE void tdsqlite3VdbeError(Vdbe*, const char *, ...);
+SQLITE_PRIVATE void tdsqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*);
void sqliteVdbePopStack(Vdbe*,int);
-SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor**, int*);
-SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor*);
-#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE)
-SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE*, int, Op*);
-#endif
-SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32);
-SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8);
-SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem*, int, u32*);
-SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(unsigned char*, Mem*, u32);
-SQLITE_PRIVATE u32 sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*);
-SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3*, AuxData**, int, int);
+SQLITE_PRIVATE int SQLITE_NOINLINE tdsqlite3VdbeFinishMoveto(VdbeCursor*);
+SQLITE_PRIVATE int tdsqlite3VdbeCursorMoveto(VdbeCursor**, int*);
+SQLITE_PRIVATE int tdsqlite3VdbeCursorRestore(VdbeCursor*);
+SQLITE_PRIVATE u32 tdsqlite3VdbeSerialTypeLen(u32);
+SQLITE_PRIVATE u8 tdsqlite3VdbeOneByteSerialTypeLen(u8);
+SQLITE_PRIVATE u32 tdsqlite3VdbeSerialPut(unsigned char*, Mem*, u32);
+SQLITE_PRIVATE u32 tdsqlite3VdbeSerialGet(const unsigned char*, u32, Mem*);
+SQLITE_PRIVATE void tdsqlite3VdbeDeleteAuxData(tdsqlite3*, AuxData**, int, int);
int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *);
-SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(sqlite3*,VdbeCursor*,UnpackedRecord*,int*);
-SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3*, BtCursor*, i64*);
-SQLITE_PRIVATE int sqlite3VdbeExec(Vdbe*);
-SQLITE_PRIVATE int sqlite3VdbeList(Vdbe*);
-SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe*);
-SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *, int);
-SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem*);
-SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem*, const Mem*);
-SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int);
-SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem*, Mem*);
-SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem*);
-SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, int, u8, void(*)(void*));
-SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem*, i64);
+SQLITE_PRIVATE int tdsqlite3VdbeIdxKeyCompare(tdsqlite3*,VdbeCursor*,UnpackedRecord*,int*);
+SQLITE_PRIVATE int tdsqlite3VdbeIdxRowid(tdsqlite3*, BtCursor*, i64*);
+SQLITE_PRIVATE int tdsqlite3VdbeExec(Vdbe*);
+#ifndef SQLITE_OMIT_EXPLAIN
+SQLITE_PRIVATE int tdsqlite3VdbeList(Vdbe*);
+#endif
+SQLITE_PRIVATE int tdsqlite3VdbeHalt(Vdbe*);
+SQLITE_PRIVATE int tdsqlite3VdbeChangeEncoding(Mem *, int);
+SQLITE_PRIVATE int tdsqlite3VdbeMemTooBig(Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeMemCopy(Mem*, const Mem*);
+SQLITE_PRIVATE void tdsqlite3VdbeMemShallowCopy(Mem*, const Mem*, int);
+SQLITE_PRIVATE void tdsqlite3VdbeMemMove(Mem*, Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeMemNulTerminate(Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeMemSetStr(Mem*, const char*, int, u8, void(*)(void*));
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetInt64(Mem*, i64);
#ifdef SQLITE_OMIT_FLOATING_POINT
-# define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64
-#else
-SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem*, double);
-#endif
-SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem*,sqlite3*,u16);
-SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem*);
-SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem*,int);
-SQLITE_PRIVATE void sqlite3VdbeMemSetRowSet(Mem*);
-SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem*);
-SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem*, u8, u8);
-SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem*);
-SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem*);
-SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem*);
-SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem*);
-SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem*);
-SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem*);
-SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem*,u8,u8);
-SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,int,Mem*);
-SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p);
-SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem*, FuncDef*);
-SQLITE_PRIVATE const char *sqlite3OpcodeName(int);
-SQLITE_PRIVATE int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve);
-SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int n);
-SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *, int);
-SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame*);
-SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *);
+# define tdsqlite3VdbeMemSetDouble tdsqlite3VdbeMemSetInt64
+#else
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetDouble(Mem*, double);
+#endif
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetPointer(Mem*, void*, const char*, void(*)(void*));
+SQLITE_PRIVATE void tdsqlite3VdbeMemInit(Mem*,tdsqlite3*,u16);
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetNull(Mem*);
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetZeroBlob(Mem*,int);
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int tdsqlite3VdbeMemIsRowSet(const Mem*);
+#endif
+SQLITE_PRIVATE int tdsqlite3VdbeMemSetRowSet(Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeMemMakeWriteable(Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeMemStringify(Mem*, u8, u8);
+SQLITE_PRIVATE i64 tdsqlite3VdbeIntValue(Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeMemIntegerify(Mem*);
+SQLITE_PRIVATE double tdsqlite3VdbeRealValue(Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeBooleanValue(Mem*, int ifNull);
+SQLITE_PRIVATE void tdsqlite3VdbeIntegerAffinity(Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeMemRealify(Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeMemNumerify(Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeMemCast(Mem*,u8,u8);
+SQLITE_PRIVATE int tdsqlite3VdbeMemFromBtree(BtCursor*,u32,u32,Mem*);
+SQLITE_PRIVATE void tdsqlite3VdbeMemRelease(Mem *p);
+SQLITE_PRIVATE int tdsqlite3VdbeMemFinalize(Mem*, FuncDef*);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+SQLITE_PRIVATE int tdsqlite3VdbeMemAggValue(Mem*, Mem*, FuncDef*);
+#endif
+#ifndef SQLITE_OMIT_EXPLAIN
+SQLITE_PRIVATE const char *tdsqlite3OpcodeName(int);
+#endif
+SQLITE_PRIVATE int tdsqlite3VdbeMemGrow(Mem *pMem, int n, int preserve);
+SQLITE_PRIVATE int tdsqlite3VdbeMemClearAndResize(Mem *pMem, int n);
+SQLITE_PRIVATE int tdsqlite3VdbeCloseStatement(Vdbe *, int);
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int tdsqlite3VdbeFrameIsValid(VdbeFrame*);
+#endif
+SQLITE_PRIVATE void tdsqlite3VdbeFrameMemDel(void*); /* Destructor on Mem */
+SQLITE_PRIVATE void tdsqlite3VdbeFrameDelete(VdbeFrame*); /* Actually deletes the Frame */
+SQLITE_PRIVATE int tdsqlite3VdbeFrameRestore(VdbeFrame *);
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
-SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int);
+SQLITE_PRIVATE void tdsqlite3VdbePreUpdateHook(Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int);
#endif
-SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p);
+SQLITE_PRIVATE int tdsqlite3VdbeTransferError(Vdbe *p);
-SQLITE_PRIVATE int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *);
-SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *);
-SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *, VdbeCursor *);
-SQLITE_PRIVATE int sqlite3VdbeSorterRowkey(const VdbeCursor *, Mem *);
-SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *, const VdbeCursor *, int *);
-SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *, int *);
-SQLITE_PRIVATE int sqlite3VdbeSorterWrite(const VdbeCursor *, Mem *);
-SQLITE_PRIVATE int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *);
+SQLITE_PRIVATE int tdsqlite3VdbeSorterInit(tdsqlite3 *, int, VdbeCursor *);
+SQLITE_PRIVATE void tdsqlite3VdbeSorterReset(tdsqlite3 *, VdbeSorter *);
+SQLITE_PRIVATE void tdsqlite3VdbeSorterClose(tdsqlite3 *, VdbeCursor *);
+SQLITE_PRIVATE int tdsqlite3VdbeSorterRowkey(const VdbeCursor *, Mem *);
+SQLITE_PRIVATE int tdsqlite3VdbeSorterNext(tdsqlite3 *, const VdbeCursor *);
+SQLITE_PRIVATE int tdsqlite3VdbeSorterRewind(const VdbeCursor *, int *);
+SQLITE_PRIVATE int tdsqlite3VdbeSorterWrite(const VdbeCursor *, Mem *);
+SQLITE_PRIVATE int tdsqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *);
+
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE void tdsqlite3VdbeIncrWriteCounter(Vdbe*, VdbeCursor*);
+SQLITE_PRIVATE void tdsqlite3VdbeAssertAbortable(Vdbe*);
+#else
+# define tdsqlite3VdbeIncrWriteCounter(V,C)
+# define tdsqlite3VdbeAssertAbortable(V)
+#endif
#if !defined(SQLITE_OMIT_SHARED_CACHE)
-SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeEnter(Vdbe*);
#else
-# define sqlite3VdbeEnter(X)
+# define tdsqlite3VdbeEnter(X)
#endif
#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0
-SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeLeave(Vdbe*);
#else
-# define sqlite3VdbeLeave(X)
+# define tdsqlite3VdbeLeave(X)
#endif
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe*,Mem*);
-SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem*);
+SQLITE_PRIVATE void tdsqlite3VdbeMemAboutToChange(Vdbe*,Mem*);
+SQLITE_PRIVATE int tdsqlite3VdbeCheckMemInvariants(Mem*);
#endif
#ifndef SQLITE_OMIT_FOREIGN_KEY
-SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *, int);
+SQLITE_PRIVATE int tdsqlite3VdbeCheckFk(Vdbe *, int);
#else
-# define sqlite3VdbeCheckFk(p,i) 0
+# define tdsqlite3VdbeCheckFk(p,i) 0
#endif
-SQLITE_PRIVATE int sqlite3VdbeMemTranslate(Mem*, u8);
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe*);
-SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf);
+SQLITE_PRIVATE void tdsqlite3VdbePrintSql(Vdbe*);
+SQLITE_PRIVATE void tdsqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr);
+#endif
+#ifndef SQLITE_OMIT_UTF16
+SQLITE_PRIVATE int tdsqlite3VdbeMemTranslate(Mem*, u8);
+SQLITE_PRIVATE int tdsqlite3VdbeMemHandleBom(Mem *pMem);
#endif
-SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem);
#ifndef SQLITE_OMIT_INCRBLOB
-SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *);
- #define ExpandBlob(P) (((P)->flags&MEM_Zero)?sqlite3VdbeMemExpandBlob(P):0)
+SQLITE_PRIVATE int tdsqlite3VdbeMemExpandBlob(Mem *);
+ #define ExpandBlob(P) (((P)->flags&MEM_Zero)?tdsqlite3VdbeMemExpandBlob(P):0)
#else
- #define sqlite3VdbeMemExpandBlob(x) SQLITE_OK
+ #define tdsqlite3VdbeMemExpandBlob(x) SQLITE_OK
#define ExpandBlob(P) SQLITE_OK
#endif
@@ -22002,18 +25437,18 @@ SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *);
** Variables in which to record status information.
*/
#if SQLITE_PTRSIZE>4
-typedef sqlite3_int64 sqlite3StatValueType;
+typedef tdsqlite3_int64 tdsqlite3StatValueType;
#else
-typedef u32 sqlite3StatValueType;
+typedef u32 tdsqlite3StatValueType;
#endif
-typedef struct sqlite3StatType sqlite3StatType;
-static SQLITE_WSD struct sqlite3StatType {
- sqlite3StatValueType nowValue[10]; /* Current value */
- sqlite3StatValueType mxValue[10]; /* Maximum value */
-} sqlite3Stat = { {0,}, {0,} };
+typedef struct tdsqlite3StatType tdsqlite3StatType;
+static SQLITE_WSD struct tdsqlite3StatType {
+ tdsqlite3StatValueType nowValue[10]; /* Current value */
+ tdsqlite3StatValueType mxValue[10]; /* Maximum value */
+} tdsqlite3Stat = { {0,}, {0,} };
/*
-** Elements of sqlite3Stat[] are protected by either the memory allocator
+** Elements of tdsqlite3Stat[] are protected by either the memory allocator
** mutex, or by the pcache1 mutex. The following array determines which.
*/
static const char statMutex[] = {
@@ -22034,26 +25469,26 @@ static const char statMutex[] = {
** state vector. If writable static data is unsupported on the target,
** we have to locate the state vector at run-time. In the more common
** case where writable static data is supported, wsdStat can refer directly
-** to the "sqlite3Stat" state vector declared above.
+** to the "tdsqlite3Stat" state vector declared above.
*/
#ifdef SQLITE_OMIT_WSD
-# define wsdStatInit sqlite3StatType *x = &GLOBAL(sqlite3StatType,sqlite3Stat)
+# define wsdStatInit tdsqlite3StatType *x = &GLOBAL(tdsqlite3StatType,tdsqlite3Stat)
# define wsdStat x[0]
#else
# define wsdStatInit
-# define wsdStat sqlite3Stat
+# define wsdStat tdsqlite3Stat
#endif
/*
** Return the current value of a status parameter. The caller must
** be holding the appropriate mutex.
*/
-SQLITE_PRIVATE sqlite3_int64 sqlite3StatusValue(int op){
+SQLITE_PRIVATE tdsqlite3_int64 tdsqlite3StatusValue(int op){
wsdStatInit;
assert( op>=0 && op<ArraySize(wsdStat.nowValue) );
assert( op>=0 && op<ArraySize(statMutex) );
- assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex()
- : sqlite3MallocMutex()) );
+ assert( tdsqlite3_mutex_held(statMutex[op] ? tdsqlite3Pcache1Mutex()
+ : tdsqlite3MallocMutex()) );
return wsdStat.nowValue[op];
}
@@ -22068,23 +25503,23 @@ SQLITE_PRIVATE sqlite3_int64 sqlite3StatusValue(int op){
** The StatusDown() routine lowers the current value by N. The highwater
** mark is unchanged. N must be non-negative for StatusDown().
*/
-SQLITE_PRIVATE void sqlite3StatusUp(int op, int N){
+SQLITE_PRIVATE void tdsqlite3StatusUp(int op, int N){
wsdStatInit;
assert( op>=0 && op<ArraySize(wsdStat.nowValue) );
assert( op>=0 && op<ArraySize(statMutex) );
- assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex()
- : sqlite3MallocMutex()) );
+ assert( tdsqlite3_mutex_held(statMutex[op] ? tdsqlite3Pcache1Mutex()
+ : tdsqlite3MallocMutex()) );
wsdStat.nowValue[op] += N;
if( wsdStat.nowValue[op]>wsdStat.mxValue[op] ){
wsdStat.mxValue[op] = wsdStat.nowValue[op];
}
}
-SQLITE_PRIVATE void sqlite3StatusDown(int op, int N){
+SQLITE_PRIVATE void tdsqlite3StatusDown(int op, int N){
wsdStatInit;
assert( N>=0 );
assert( op>=0 && op<ArraySize(statMutex) );
- assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex()
- : sqlite3MallocMutex()) );
+ assert( tdsqlite3_mutex_held(statMutex[op] ? tdsqlite3Pcache1Mutex()
+ : tdsqlite3MallocMutex()) );
assert( op>=0 && op<ArraySize(wsdStat.nowValue) );
wsdStat.nowValue[op] -= N;
}
@@ -22093,18 +25528,17 @@ SQLITE_PRIVATE void sqlite3StatusDown(int op, int N){
** Adjust the highwater mark if necessary.
** The caller must hold the appropriate mutex.
*/
-SQLITE_PRIVATE void sqlite3StatusHighwater(int op, int X){
- sqlite3StatValueType newValue;
+SQLITE_PRIVATE void tdsqlite3StatusHighwater(int op, int X){
+ tdsqlite3StatValueType newValue;
wsdStatInit;
assert( X>=0 );
- newValue = (sqlite3StatValueType)X;
+ newValue = (tdsqlite3StatValueType)X;
assert( op>=0 && op<ArraySize(wsdStat.nowValue) );
assert( op>=0 && op<ArraySize(statMutex) );
- assert( sqlite3_mutex_held(statMutex[op] ? sqlite3Pcache1Mutex()
- : sqlite3MallocMutex()) );
+ assert( tdsqlite3_mutex_held(statMutex[op] ? tdsqlite3Pcache1Mutex()
+ : tdsqlite3MallocMutex()) );
assert( op==SQLITE_STATUS_MALLOC_SIZE
|| op==SQLITE_STATUS_PAGECACHE_SIZE
- || op==SQLITE_STATUS_SCRATCH_SIZE
|| op==SQLITE_STATUS_PARSER_STACK );
if( newValue>wsdStat.mxValue[op] ){
wsdStat.mxValue[op] = newValue;
@@ -22114,13 +25548,13 @@ SQLITE_PRIVATE void sqlite3StatusHighwater(int op, int X){
/*
** Query status information.
*/
-SQLITE_API int sqlite3_status64(
+SQLITE_API int tdsqlite3_status64(
int op,
- sqlite3_int64 *pCurrent,
- sqlite3_int64 *pHighwater,
+ tdsqlite3_int64 *pCurrent,
+ tdsqlite3_int64 *pHighwater,
int resetFlag
){
- sqlite3_mutex *pMutex;
+ tdsqlite3_mutex *pMutex;
wsdStatInit;
if( op<0 || op>=ArraySize(wsdStat.nowValue) ){
return SQLITE_MISUSE_BKPT;
@@ -22128,24 +25562,24 @@ SQLITE_API int sqlite3_status64(
#ifdef SQLITE_ENABLE_API_ARMOR
if( pCurrent==0 || pHighwater==0 ) return SQLITE_MISUSE_BKPT;
#endif
- pMutex = statMutex[op] ? sqlite3Pcache1Mutex() : sqlite3MallocMutex();
- sqlite3_mutex_enter(pMutex);
+ pMutex = statMutex[op] ? tdsqlite3Pcache1Mutex() : tdsqlite3MallocMutex();
+ tdsqlite3_mutex_enter(pMutex);
*pCurrent = wsdStat.nowValue[op];
*pHighwater = wsdStat.mxValue[op];
if( resetFlag ){
wsdStat.mxValue[op] = wsdStat.nowValue[op];
}
- sqlite3_mutex_leave(pMutex);
+ tdsqlite3_mutex_leave(pMutex);
(void)pMutex; /* Prevent warning when SQLITE_THREADSAFE=0 */
return SQLITE_OK;
}
-SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag){
- sqlite3_int64 iCur = 0, iHwtr = 0;
+SQLITE_API int tdsqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag){
+ tdsqlite3_int64 iCur = 0, iHwtr = 0;
int rc;
#ifdef SQLITE_ENABLE_API_ARMOR
if( pCurrent==0 || pHighwater==0 ) return SQLITE_MISUSE_BKPT;
#endif
- rc = sqlite3_status64(op, &iCur, &iHwtr, resetFlag);
+ rc = tdsqlite3_status64(op, &iCur, &iHwtr, resetFlag);
if( rc==0 ){
*pCurrent = (int)iCur;
*pHighwater = (int)iHwtr;
@@ -22154,10 +25588,36 @@ SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetF
}
/*
+** Return the number of LookasideSlot elements on the linked list
+*/
+static u32 countLookasideSlots(LookasideSlot *p){
+ u32 cnt = 0;
+ while( p ){
+ p = p->pNext;
+ cnt++;
+ }
+ return cnt;
+}
+
+/*
+** Count the number of slots of lookaside memory that are outstanding
+*/
+SQLITE_PRIVATE int tdsqlite3LookasideUsed(tdsqlite3 *db, int *pHighwater){
+ u32 nInit = countLookasideSlots(db->lookaside.pInit);
+ u32 nFree = countLookasideSlots(db->lookaside.pFree);
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ nInit += countLookasideSlots(db->lookaside.pSmallInit);
+ nFree += countLookasideSlots(db->lookaside.pSmallFree);
+#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
+ if( pHighwater ) *pHighwater = db->lookaside.nSlot - nInit;
+ return db->lookaside.nSlot - (nInit+nFree);
+}
+
+/*
** Query status information for a single database connection
*/
-SQLITE_API int sqlite3_db_status(
- sqlite3 *db, /* The database connection whose status is desired */
+SQLITE_API int tdsqlite3_db_status(
+ tdsqlite3 *db, /* The database connection whose status is desired */
int op, /* Status verb */
int *pCurrent, /* Write current value here */
int *pHighwater, /* Write high-water mark here */
@@ -22165,17 +25625,31 @@ SQLITE_API int sqlite3_db_status(
){
int rc = SQLITE_OK; /* Return code */
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){
+ if( !tdsqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){
return SQLITE_MISUSE_BKPT;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
switch( op ){
case SQLITE_DBSTATUS_LOOKASIDE_USED: {
- *pCurrent = db->lookaside.nOut;
- *pHighwater = db->lookaside.mxOut;
+ *pCurrent = tdsqlite3LookasideUsed(db, pHighwater);
if( resetFlag ){
- db->lookaside.mxOut = db->lookaside.nOut;
+ LookasideSlot *p = db->lookaside.pFree;
+ if( p ){
+ while( p->pNext ) p = p->pNext;
+ p->pNext = db->lookaside.pInit;
+ db->lookaside.pInit = db->lookaside.pFree;
+ db->lookaside.pFree = 0;
+ }
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ p = db->lookaside.pSmallFree;
+ if( p ){
+ while( p->pNext ) p = p->pNext;
+ p->pNext = db->lookaside.pSmallInit;
+ db->lookaside.pSmallInit = db->lookaside.pSmallFree;
+ db->lookaside.pSmallFree = 0;
+ }
+#endif
}
break;
}
@@ -22205,19 +25679,19 @@ SQLITE_API int sqlite3_db_status(
case SQLITE_DBSTATUS_CACHE_USED: {
int totalUsed = 0;
int i;
- sqlite3BtreeEnterAll(db);
+ tdsqlite3BtreeEnterAll(db);
for(i=0; i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( pBt ){
- Pager *pPager = sqlite3BtreePager(pBt);
- int nByte = sqlite3PagerMemUsed(pPager);
+ Pager *pPager = tdsqlite3BtreePager(pBt);
+ int nByte = tdsqlite3PagerMemUsed(pPager);
if( op==SQLITE_DBSTATUS_CACHE_USED_SHARED ){
- nByte = nByte / sqlite3BtreeConnectionCount(pBt);
+ nByte = nByte / tdsqlite3BtreeConnectionCount(pBt);
}
totalUsed += nByte;
}
}
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3BtreeLeaveAll(db);
*pCurrent = totalUsed;
*pHighwater = 0;
break;
@@ -22232,34 +25706,34 @@ SQLITE_API int sqlite3_db_status(
int i; /* Used to iterate through schemas */
int nByte = 0; /* Used to accumulate return value */
- sqlite3BtreeEnterAll(db);
+ tdsqlite3BtreeEnterAll(db);
db->pnBytesFreed = &nByte;
for(i=0; i<db->nDb; i++){
Schema *pSchema = db->aDb[i].pSchema;
if( ALWAYS(pSchema!=0) ){
HashElem *p;
- nByte += sqlite3GlobalConfig.m.xRoundup(sizeof(HashElem)) * (
+ nByte += tdsqlite3GlobalConfig.m.xRoundup(sizeof(HashElem)) * (
pSchema->tblHash.count
+ pSchema->trigHash.count
+ pSchema->idxHash.count
+ pSchema->fkeyHash.count
);
- nByte += sqlite3_msize(pSchema->tblHash.ht);
- nByte += sqlite3_msize(pSchema->trigHash.ht);
- nByte += sqlite3_msize(pSchema->idxHash.ht);
- nByte += sqlite3_msize(pSchema->fkeyHash.ht);
+ nByte += tdsqlite3_msize(pSchema->tblHash.ht);
+ nByte += tdsqlite3_msize(pSchema->trigHash.ht);
+ nByte += tdsqlite3_msize(pSchema->idxHash.ht);
+ nByte += tdsqlite3_msize(pSchema->fkeyHash.ht);
for(p=sqliteHashFirst(&pSchema->trigHash); p; p=sqliteHashNext(p)){
- sqlite3DeleteTrigger(db, (Trigger*)sqliteHashData(p));
+ tdsqlite3DeleteTrigger(db, (Trigger*)sqliteHashData(p));
}
for(p=sqliteHashFirst(&pSchema->tblHash); p; p=sqliteHashNext(p)){
- sqlite3DeleteTable(db, (Table *)sqliteHashData(p));
+ tdsqlite3DeleteTable(db, (Table *)sqliteHashData(p));
}
}
}
db->pnBytesFreed = 0;
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3BtreeLeaveAll(db);
*pHighwater = 0;
*pCurrent = nByte;
@@ -22277,8 +25751,8 @@ SQLITE_API int sqlite3_db_status(
db->pnBytesFreed = &nByte;
for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pNext){
- sqlite3VdbeClearObject(db, pVdbe);
- sqlite3DbFree(db, pVdbe);
+ tdsqlite3VdbeClearObject(db, pVdbe);
+ tdsqlite3DbFree(db, pVdbe);
}
db->pnBytesFreed = 0;
@@ -22293,6 +25767,9 @@ SQLITE_API int sqlite3_db_status(
** pagers the database handle is connected to. *pHighwater is always set
** to zero.
*/
+ case SQLITE_DBSTATUS_CACHE_SPILL:
+ op = SQLITE_DBSTATUS_CACHE_WRITE+1;
+ /* Fall through into the next case */
case SQLITE_DBSTATUS_CACHE_HIT:
case SQLITE_DBSTATUS_CACHE_MISS:
case SQLITE_DBSTATUS_CACHE_WRITE:{
@@ -22303,8 +25780,8 @@ SQLITE_API int sqlite3_db_status(
for(i=0; i<db->nDb; i++){
if( db->aDb[i].pBt ){
- Pager *pPager = sqlite3BtreePager(db->aDb[i].pBt);
- sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet);
+ Pager *pPager = tdsqlite3BtreePager(db->aDb[i].pBt);
+ tdsqlite3PagerCacheStat(pPager, op, resetFlag, &nRet);
}
}
*pHighwater = 0; /* IMP: R-42420-56072 */
@@ -22328,7 +25805,7 @@ SQLITE_API int sqlite3_db_status(
rc = SQLITE_ERROR;
}
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -22349,7 +25826,7 @@ SQLITE_API int sqlite3_db_status(
** functions for SQLite.
**
** There is only one exported symbol in this file - the function
-** sqlite3RegisterDateTimeFunctions() found at the bottom of the file.
+** tdsqlite3RegisterDateTimeFunctions() found at the bottom of the file.
** All other code has file scope.
**
** SQLite processes all times and dates as julian day numbers. The
@@ -22375,7 +25852,7 @@ SQLITE_API int sqlite3_db_status(
**
** Jean Meeus
** Astronomical Algorithms, 2nd Edition, 1998
-** ISBM 0-943396-61-1
+** ISBN 0-943396-61-1
** Willmann-Bell, Inc
** Richmond, Virginia (USA)
*/
@@ -22401,16 +25878,18 @@ struct tm *__cdecl localtime(const time_t *);
*/
typedef struct DateTime DateTime;
struct DateTime {
- sqlite3_int64 iJD; /* The julian day number times 86400000 */
- int Y, M, D; /* Year, month, and day */
- int h, m; /* Hour and minutes */
- int tz; /* Timezone offset in minutes */
- double s; /* Seconds */
- char validYMD; /* True (1) if Y,M,D are valid */
- char validHMS; /* True (1) if h,m,s are valid */
- char validJD; /* True (1) if iJD is valid */
- char validTZ; /* True (1) if tz is valid */
- char tzSet; /* Timezone was set explicitly */
+ tdsqlite3_int64 iJD; /* The julian day number times 86400000 */
+ int Y, M, D; /* Year, month, and day */
+ int h, m; /* Hour and minutes */
+ int tz; /* Timezone offset in minutes */
+ double s; /* Seconds */
+ char validJD; /* True (1) if iJD is valid */
+ char rawS; /* Raw numeric value stored in s */
+ char validYMD; /* True (1) if Y,M,D are valid */
+ char validHMS; /* True (1) if h,m,s are valid */
+ char validTZ; /* True (1) if tz is valid */
+ char tzSet; /* Timezone was set explicitly */
+ char isError; /* An overflow has occurred */
};
@@ -22460,7 +25939,7 @@ static int getDigits(const char *zDate, const char *zFormat, ...){
nextC = zFormat[3];
val = 0;
while( N-- ){
- if( !sqlite3Isdigit(*zDate) ){
+ if( !tdsqlite3Isdigit(*zDate) ){
goto end_getDigits;
}
val = val*10 + *zDate - '0';
@@ -22499,7 +25978,7 @@ static int parseTimezone(const char *zDate, DateTime *p){
int sgn = 0;
int nHr, nMn;
int c;
- while( sqlite3Isspace(*zDate) ){ zDate++; }
+ while( tdsqlite3Isspace(*zDate) ){ zDate++; }
p->tz = 0;
c = *zDate;
if( c=='-' ){
@@ -22519,7 +25998,7 @@ static int parseTimezone(const char *zDate, DateTime *p){
zDate += 5;
p->tz = sgn*(nMn + nHr*60);
zulu_time:
- while( sqlite3Isspace(*zDate) ){ zDate++; }
+ while( tdsqlite3Isspace(*zDate) ){ zDate++; }
p->tzSet = 1;
return *zDate!=0;
}
@@ -22544,10 +26023,10 @@ static int parseHhMmSs(const char *zDate, DateTime *p){
return 1;
}
zDate += 2;
- if( *zDate=='.' && sqlite3Isdigit(zDate[1]) ){
+ if( *zDate=='.' && tdsqlite3Isdigit(zDate[1]) ){
double rScale = 1.0;
zDate++;
- while( sqlite3Isdigit(*zDate) ){
+ while( tdsqlite3Isdigit(*zDate) ){
ms = ms*10.0 + *zDate - '0';
rScale *= 10.0;
zDate++;
@@ -22558,6 +26037,7 @@ static int parseHhMmSs(const char *zDate, DateTime *p){
s = 0;
}
p->validJD = 0;
+ p->rawS = 0;
p->validHMS = 1;
p->h = h;
p->m = m;
@@ -22568,6 +26048,14 @@ static int parseHhMmSs(const char *zDate, DateTime *p){
}
/*
+** Put the DateTime object into its error state.
+*/
+static void datetimeError(DateTime *p){
+ memset(p, 0, sizeof(*p));
+ p->isError = 1;
+}
+
+/*
** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume
** that the YYYY-MM-DD is according to the Gregorian calendar.
**
@@ -22586,6 +26074,10 @@ static void computeJD(DateTime *p){
M = 1;
D = 1;
}
+ if( Y<-4713 || Y>9999 || p->rawS ){
+ datetimeError(p);
+ return;
+ }
if( M<=2 ){
Y--;
M += 12;
@@ -22594,10 +26086,10 @@ static void computeJD(DateTime *p){
B = 2 - A + (A/4);
X1 = 36525*(Y+4716)/100;
X2 = 306001*(M+1)/10000;
- p->iJD = (sqlite3_int64)((X1 + X2 + D + B - 1524.5 ) * 86400000);
+ p->iJD = (tdsqlite3_int64)((X1 + X2 + D + B - 1524.5 ) * 86400000);
p->validJD = 1;
if( p->validHMS ){
- p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000);
+ p->iJD += p->h*3600000 + p->m*60000 + (tdsqlite3_int64)(p->s*1000);
if( p->validTZ ){
p->iJD -= p->tz*60000;
p->validYMD = 0;
@@ -22632,7 +26124,7 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){
return 1;
}
zDate += 10;
- while( sqlite3Isspace(*zDate) || 'T'==*(u8*)zDate ){ zDate++; }
+ while( tdsqlite3Isspace(*zDate) || 'T'==*(u8*)zDate ){ zDate++; }
if( parseHhMmSs(zDate, p)==0 ){
/* We got the time */
}else if( *zDate==0 ){
@@ -22656,8 +26148,8 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){
**
** Return the number of errors.
*/
-static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
- p->iJD = sqlite3StmtCurrentTime(context);
+static int setDateTimeToCurrent(tdsqlite3_context *context, DateTime *p){
+ p->iJD = tdsqlite3StmtCurrentTime(context);
if( p->iJD>0 ){
p->validJD = 1;
return 0;
@@ -22667,6 +26159,21 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
}
/*
+** Input "r" is a numeric quantity which might be a julian day number,
+** or the number of seconds since 1970. If the value if r is within
+** range of a julian day number, install it as such and set validJD.
+** If the value is a valid unix timestamp, put it in p->s and set p->rawS.
+*/
+static void setRawDateNumber(DateTime *p, double r){
+ p->s = r;
+ p->rawS = 1;
+ if( r>=0.0 && r<5373484.5 ){
+ p->iJD = (tdsqlite3_int64)(r*86400000.0 + 0.5);
+ p->validJD = 1;
+ }
+}
+
+/*
** Attempt to parse the given string into a julian day number. Return
** the number of errors.
**
@@ -22683,7 +26190,7 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){
** as there is a year and date.
*/
static int parseDateOrTime(
- sqlite3_context *context,
+ tdsqlite3_context *context,
const char *zDate,
DateTime *p
){
@@ -22692,16 +26199,33 @@ static int parseDateOrTime(
return 0;
}else if( parseHhMmSs(zDate, p)==0 ){
return 0;
- }else if( sqlite3StrICmp(zDate,"now")==0){
+ }else if( tdsqlite3StrICmp(zDate,"now")==0 && tdsqlite3NotPureFunc(context) ){
return setDateTimeToCurrent(context, p);
- }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){
- p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5);
- p->validJD = 1;
+ }else if( tdsqlite3AtoF(zDate, &r, tdsqlite3Strlen30(zDate), SQLITE_UTF8)>0 ){
+ setRawDateNumber(p, r);
return 0;
}
return 1;
}
+/* The julian day number for 9999-12-31 23:59:59.999 is 5373484.4999999.
+** Multiplying this by 86400000 gives 464269060799999 as the maximum value
+** for DateTime.iJD.
+**
+** But some older compilers (ex: gcc 4.2.1 on older Macs) cannot deal with
+** such a large integer literal, so we have to encode it.
+*/
+#define INT_464269060799999 ((((i64)0x1a640)<<32)|0x1072fdff)
+
+/*
+** Return TRUE if the given julian day number is within range.
+**
+** The input is the JulianDay times 86400000.
+*/
+static int validJulianDay(tdsqlite3_int64 iJD){
+ return iJD>=0 && iJD<=INT_464269060799999;
+}
+
/*
** Compute the Year, Month, and Day from the julian day number.
*/
@@ -22712,6 +26236,9 @@ static void computeYMD(DateTime *p){
p->Y = 2000;
p->M = 1;
p->D = 1;
+ }else if( !validJulianDay(p->iJD) ){
+ datetimeError(p);
+ return;
}else{
Z = (int)((p->iJD + 43200000)/86400000);
A = (int)((Z - 1867216.25)/36524.25);
@@ -22743,6 +26270,7 @@ static void computeHMS(DateTime *p){
s -= p->h*3600;
p->m = s/60;
p->s += s - p->m*60;
+ p->rawS = 0;
p->validHMS = 1;
}
@@ -22788,7 +26316,7 @@ static void clearYMD_HMS_TZ(DateTime *p){
** is available. This routine returns 0 on success and
** non-zero on any kind of error.
**
-** If the sqlite3GlobalConfig.bLocaltimeFault variable is true then this
+** If the tdsqlite3GlobalConfig.bLocaltimeFault variable is true then this
** routine will always fail.
**
** EVIDENCE-OF: R-62172-00036 In this implementation, the standard C
@@ -22800,19 +26328,19 @@ static int osLocaltime(time_t *t, struct tm *pTm){
#if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S
struct tm *pX;
#if SQLITE_THREADSAFE>0
- sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
+ tdsqlite3_mutex *mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
#endif
- sqlite3_mutex_enter(mutex);
+ tdsqlite3_mutex_enter(mutex);
pX = localtime(t);
-#ifndef SQLITE_OMIT_BUILTIN_TEST
- if( sqlite3GlobalConfig.bLocaltimeFault ) pX = 0;
+#ifndef SQLITE_UNTESTABLE
+ if( tdsqlite3GlobalConfig.bLocaltimeFault ) pX = 0;
#endif
if( pX ) *pTm = *pX;
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
rc = pX==0;
#else
-#ifndef SQLITE_OMIT_BUILTIN_TEST
- if( sqlite3GlobalConfig.bLocaltimeFault ) return 1;
+#ifndef SQLITE_UNTESTABLE
+ if( tdsqlite3GlobalConfig.bLocaltimeFault ) return 1;
#endif
#if HAVE_LOCALTIME_R
rc = localtime_r(t, pTm)==0;
@@ -22834,9 +26362,9 @@ static int osLocaltime(time_t *t, struct tm *pTm){
** Or, if an error does occur, set *pRc to SQLITE_ERROR. The returned value
** is undefined in this case.
*/
-static sqlite3_int64 localtimeOffset(
+static tdsqlite3_int64 localtimeOffset(
DateTime *p, /* Date at which to calculate offset */
- sqlite3_context *pCtx, /* Write error here if one occurs */
+ tdsqlite3_context *pCtx, /* Write error here if one occurs */
int *pRc /* OUT: Error code. SQLITE_OK or ERROR */
){
DateTime x, y;
@@ -22869,7 +26397,7 @@ static sqlite3_int64 localtimeOffset(
computeJD(&x);
t = (time_t)(x.iJD/1000 - 21086676*(i64)10000);
if( osLocaltime(&t, &sLocal) ){
- sqlite3_result_error(pCtx, "local time unavailable", -1);
+ tdsqlite3_result_error(pCtx, "local time unavailable", -1);
*pRc = SQLITE_ERROR;
return 0;
}
@@ -22882,7 +26410,9 @@ static sqlite3_int64 localtimeOffset(
y.validYMD = 1;
y.validHMS = 1;
y.validJD = 0;
+ y.rawS = 0;
y.validTZ = 0;
+ y.isError = 0;
computeJD(&y);
*pRc = SQLITE_OK;
return y.iJD - x.iJD;
@@ -22890,6 +26420,29 @@ static sqlite3_int64 localtimeOffset(
#endif /* SQLITE_OMIT_LOCALTIME */
/*
+** The following table defines various date transformations of the form
+**
+** 'NNN days'
+**
+** Where NNN is an arbitrary floating-point number and "days" can be one
+** of several units of time.
+*/
+static const struct {
+ u8 eType; /* Transformation type code */
+ u8 nName; /* Length of th name */
+ char *zName; /* Name of the transformation */
+ double rLimit; /* Maximum NNN value for this transform */
+ double rXform; /* Constant used for this transform */
+} aXformType[] = {
+ { 0, 6, "second", 464269060800.0, 86400000.0/(24.0*60.0*60.0) },
+ { 0, 6, "minute", 7737817680.0, 86400000.0/(24.0*60.0) },
+ { 0, 4, "hour", 128963628.0, 86400000.0/24.0 },
+ { 0, 3, "day", 5373485.0, 86400000.0 },
+ { 1, 5, "month", 176546.0, 30.0*86400000.0 },
+ { 2, 4, "year", 14713.0, 365.0*86400000.0 },
+};
+
+/*
** Process a modifier to a date-time stamp. The modifiers are
** as follows:
**
@@ -22913,17 +26466,15 @@ static sqlite3_int64 localtimeOffset(
** to context pCtx. If the error is an unrecognized modifier, no error is
** written to pCtx.
*/
-static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
+static int parseModifier(
+ tdsqlite3_context *pCtx, /* Function context */
+ const char *z, /* The text of the modifier */
+ int n, /* Length of zMod in bytes */
+ DateTime *p /* The date/time value to be modified */
+){
int rc = 1;
- int n;
double r;
- char *z, zBuf[30];
- z = zBuf;
- for(n=0; n<ArraySize(zBuf)-1 && zMod[n]; n++){
- z[n] = (char)sqlite3UpperToLower[(u8)zMod[n]];
- }
- z[n] = 0;
- switch( z[0] ){
+ switch(tdsqlite3UpperToLower[(u8)z[0]] ){
#ifndef SQLITE_OMIT_LOCALTIME
case 'l': {
/* localtime
@@ -22931,7 +26482,7 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
** Assuming the current time value is UTC (a.k.a. GMT), shift it to
** show local time.
*/
- if( strcmp(z, "localtime")==0 ){
+ if( tdsqlite3_stricmp(z, "localtime")==0 && tdsqlite3NotPureFunc(pCtx) ){
computeJD(p);
p->iJD += localtimeOffset(p, pCtx, &rc);
clearYMD_HMS_TZ(p);
@@ -22943,18 +26494,23 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
/*
** unixepoch
**
- ** Treat the current value of p->iJD as the number of
+ ** Treat the current value of p->s as the number of
** seconds since 1970. Convert to a real julian day number.
*/
- if( strcmp(z, "unixepoch")==0 && p->validJD ){
- p->iJD = (p->iJD + 43200)/86400 + 21086676*(i64)10000000;
- clearYMD_HMS_TZ(p);
- rc = 0;
+ if( tdsqlite3_stricmp(z, "unixepoch")==0 && p->rawS ){
+ r = p->s*1000.0 + 210866760000000.0;
+ if( r>=0.0 && r<464269060800000.0 ){
+ clearYMD_HMS_TZ(p);
+ p->iJD = (tdsqlite3_int64)(r + 0.5);
+ p->validJD = 1;
+ p->rawS = 0;
+ rc = 0;
+ }
}
#ifndef SQLITE_OMIT_LOCALTIME
- else if( strcmp(z, "utc")==0 ){
+ else if( tdsqlite3_stricmp(z, "utc")==0 && tdsqlite3NotPureFunc(pCtx) ){
if( p->tzSet==0 ){
- sqlite3_int64 c1;
+ tdsqlite3_int64 c1;
computeJD(p);
c1 = localtimeOffset(p, pCtx, &rc);
if( rc==SQLITE_OK ){
@@ -22978,10 +26534,10 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
** weekday N where 0==Sunday, 1==Monday, and so forth. If the
** date is already on the appropriate weekday, this is a no-op.
*/
- if( strncmp(z, "weekday ", 8)==0
- && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8)
+ if( tdsqlite3_strnicmp(z, "weekday ", 8)==0
+ && tdsqlite3AtoF(&z[8], &r, tdsqlite3Strlen30(&z[8]), SQLITE_UTF8)>0
&& (n=(int)r)==r && n>=0 && r<7 ){
- sqlite3_int64 Z;
+ tdsqlite3_int64 Z;
computeYMD_HMS(p);
p->validTZ = 0;
p->validJD = 0;
@@ -23001,23 +26557,24 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
** Move the date backwards to the beginning of the current day,
** or month or year.
*/
- if( strncmp(z, "start of ", 9)!=0 ) break;
+ if( tdsqlite3_strnicmp(z, "start of ", 9)!=0 ) break;
+ if( !p->validJD && !p->validYMD && !p->validHMS ) break;
z += 9;
computeYMD(p);
p->validHMS = 1;
p->h = p->m = 0;
p->s = 0.0;
+ p->rawS = 0;
p->validTZ = 0;
p->validJD = 0;
- if( strcmp(z,"month")==0 ){
+ if( tdsqlite3_stricmp(z,"month")==0 ){
p->D = 1;
rc = 0;
- }else if( strcmp(z,"year")==0 ){
- computeYMD(p);
+ }else if( tdsqlite3_stricmp(z,"year")==0 ){
p->M = 1;
p->D = 1;
rc = 0;
- }else if( strcmp(z,"day")==0 ){
+ }else if( tdsqlite3_stricmp(z,"day")==0 ){
rc = 0;
}
break;
@@ -23035,8 +26592,9 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
case '8':
case '9': {
double rRounder;
- for(n=1; z[n] && z[n]!=':' && !sqlite3Isspace(z[n]); n++){}
- if( !sqlite3AtoF(z, &r, n, SQLITE_UTF8) ){
+ int i;
+ for(n=1; z[n] && z[n]!=':' && !tdsqlite3Isspace(z[n]); n++){}
+ if( tdsqlite3AtoF(z, &r, n, SQLITE_UTF8)<=0 ){
rc = 1;
break;
}
@@ -23048,8 +26606,8 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
*/
const char *z2 = z;
DateTime tx;
- sqlite3_int64 day;
- if( !sqlite3Isdigit(*z2) ) z2++;
+ tdsqlite3_int64 day;
+ if( !tdsqlite3Isdigit(*z2) ) z2++;
memset(&tx, 0, sizeof(tx));
if( parseHhMmSs(z2, &tx) ) break;
computeJD(&tx);
@@ -23063,46 +26621,48 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
rc = 0;
break;
}
+
+ /* If control reaches this point, it means the transformation is
+ ** one of the forms like "+NNN days". */
z += n;
- while( sqlite3Isspace(*z) ) z++;
- n = sqlite3Strlen30(z);
+ while( tdsqlite3Isspace(*z) ) z++;
+ n = tdsqlite3Strlen30(z);
if( n>10 || n<3 ) break;
- if( z[n-1]=='s' ){ z[n-1] = 0; n--; }
+ if( tdsqlite3UpperToLower[(u8)z[n-1]]=='s' ) n--;
computeJD(p);
- rc = 0;
+ rc = 1;
rRounder = r<0 ? -0.5 : +0.5;
- if( n==3 && strcmp(z,"day")==0 ){
- p->iJD += (sqlite3_int64)(r*86400000.0 + rRounder);
- }else if( n==4 && strcmp(z,"hour")==0 ){
- p->iJD += (sqlite3_int64)(r*(86400000.0/24.0) + rRounder);
- }else if( n==6 && strcmp(z,"minute")==0 ){
- p->iJD += (sqlite3_int64)(r*(86400000.0/(24.0*60.0)) + rRounder);
- }else if( n==6 && strcmp(z,"second")==0 ){
- p->iJD += (sqlite3_int64)(r*(86400000.0/(24.0*60.0*60.0)) + rRounder);
- }else if( n==5 && strcmp(z,"month")==0 ){
- int x, y;
- computeYMD_HMS(p);
- p->M += (int)r;
- x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12;
- p->Y += x;
- p->M -= x*12;
- p->validJD = 0;
- computeJD(p);
- y = (int)r;
- if( y!=r ){
- p->iJD += (sqlite3_int64)((r - y)*30.0*86400000.0 + rRounder);
- }
- }else if( n==4 && strcmp(z,"year")==0 ){
- int y = (int)r;
- computeYMD_HMS(p);
- p->Y += y;
- p->validJD = 0;
- computeJD(p);
- if( y!=r ){
- p->iJD += (sqlite3_int64)((r - y)*365.0*86400000.0 + rRounder);
+ for(i=0; i<ArraySize(aXformType); i++){
+ if( aXformType[i].nName==n
+ && tdsqlite3_strnicmp(aXformType[i].zName, z, n)==0
+ && r>-aXformType[i].rLimit && r<aXformType[i].rLimit
+ ){
+ switch( aXformType[i].eType ){
+ case 1: { /* Special processing to add months */
+ int x;
+ computeYMD_HMS(p);
+ p->M += (int)r;
+ x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12;
+ p->Y += x;
+ p->M -= x*12;
+ p->validJD = 0;
+ r -= (int)r;
+ break;
+ }
+ case 2: { /* Special processing to add years */
+ int y = (int)r;
+ computeYMD_HMS(p);
+ p->Y += y;
+ p->validJD = 0;
+ r -= (int)r;
+ break;
+ }
+ }
+ computeJD(p);
+ p->iJD += (tdsqlite3_int64)(r*aXformType[i].rXform + rRounder);
+ rc = 0;
+ break;
}
- }else{
- rc = 1;
}
clearYMD_HMS_TZ(p);
break;
@@ -23124,32 +26684,34 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){
** then assume a default value of "now" for argv[0].
*/
static int isDate(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv,
+ tdsqlite3_value **argv,
DateTime *p
){
- int i;
+ int i, n;
const unsigned char *z;
int eType;
memset(p, 0, sizeof(*p));
if( argc==0 ){
return setDateTimeToCurrent(context, p);
}
- if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT
+ if( (eType = tdsqlite3_value_type(argv[0]))==SQLITE_FLOAT
|| eType==SQLITE_INTEGER ){
- p->iJD = (sqlite3_int64)(sqlite3_value_double(argv[0])*86400000.0 + 0.5);
- p->validJD = 1;
+ setRawDateNumber(p, tdsqlite3_value_double(argv[0]));
}else{
- z = sqlite3_value_text(argv[0]);
+ z = tdsqlite3_value_text(argv[0]);
if( !z || parseDateOrTime(context, (char*)z, p) ){
return 1;
}
}
for(i=1; i<argc; i++){
- z = sqlite3_value_text(argv[i]);
- if( z==0 || parseModifier(context, (char*)z, p) ) return 1;
+ z = tdsqlite3_value_text(argv[i]);
+ n = tdsqlite3_value_bytes(argv[i]);
+ if( z==0 || parseModifier(context, (char*)z, n, p) ) return 1;
}
+ computeJD(p);
+ if( p->isError || !validJulianDay(p->iJD) ) return 1;
return 0;
}
@@ -23165,14 +26727,14 @@ static int isDate(
** Return the julian day number of the date specified in the arguments
*/
static void juliandayFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
DateTime x;
if( isDate(context, argc, argv, &x)==0 ){
computeJD(&x);
- sqlite3_result_double(context, x.iJD/86400000.0);
+ tdsqlite3_result_double(context, x.iJD/86400000.0);
}
}
@@ -23182,17 +26744,17 @@ static void juliandayFunc(
** Return YYYY-MM-DD HH:MM:SS
*/
static void datetimeFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
DateTime x;
if( isDate(context, argc, argv, &x)==0 ){
char zBuf[100];
computeYMD_HMS(&x);
- sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d %02d:%02d:%02d",
+ tdsqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d %02d:%02d:%02d",
x.Y, x.M, x.D, x.h, x.m, (int)(x.s));
- sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
}
}
@@ -23202,16 +26764,16 @@ static void datetimeFunc(
** Return HH:MM:SS
*/
static void timeFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
DateTime x;
if( isDate(context, argc, argv, &x)==0 ){
char zBuf[100];
computeHMS(&x);
- sqlite3_snprintf(sizeof(zBuf), zBuf, "%02d:%02d:%02d", x.h, x.m, (int)x.s);
- sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+ tdsqlite3_snprintf(sizeof(zBuf), zBuf, "%02d:%02d:%02d", x.h, x.m, (int)x.s);
+ tdsqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
}
}
@@ -23221,16 +26783,16 @@ static void timeFunc(
** Return YYYY-MM-DD
*/
static void dateFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
DateTime x;
if( isDate(context, argc, argv, &x)==0 ){
char zBuf[100];
computeYMD(&x);
- sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d", x.Y, x.M, x.D);
- sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+ tdsqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d", x.Y, x.M, x.D);
+ tdsqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
}
}
@@ -23254,21 +26816,21 @@ static void dateFunc(
** %% %
*/
static void strftimeFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
DateTime x;
u64 n;
size_t i,j;
char *z;
- sqlite3 *db;
+ tdsqlite3 *db;
const char *zFmt;
char zBuf[100];
if( argc==0 ) return;
- zFmt = (const char*)sqlite3_value_text(argv[0]);
+ zFmt = (const char*)tdsqlite3_value_text(argv[0]);
if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return;
- db = sqlite3_context_db_handle(context);
+ db = tdsqlite3_context_db_handle(context);
for(i=0, n=1; zFmt[i]; i++, n++){
if( zFmt[i]=='%' ){
switch( zFmt[i+1] ){
@@ -23309,12 +26871,12 @@ static void strftimeFunc(
if( n<sizeof(zBuf) ){
z = zBuf;
}else if( n>(u64)db->aLimit[SQLITE_LIMIT_LENGTH] ){
- sqlite3_result_error_toobig(context);
+ tdsqlite3_result_error_toobig(context);
return;
}else{
- z = sqlite3DbMallocRawNN(db, (int)n);
+ z = tdsqlite3DbMallocRawNN(db, (int)n);
if( z==0 ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
return;
}
}
@@ -23326,15 +26888,15 @@ static void strftimeFunc(
}else{
i++;
switch( zFmt[i] ){
- case 'd': sqlite3_snprintf(3, &z[j],"%02d",x.D); j+=2; break;
+ case 'd': tdsqlite3_snprintf(3, &z[j],"%02d",x.D); j+=2; break;
case 'f': {
double s = x.s;
if( s>59.999 ) s = 59.999;
- sqlite3_snprintf(7, &z[j],"%06.3f", s);
- j += sqlite3Strlen30(&z[j]);
+ tdsqlite3_snprintf(7, &z[j],"%06.3f", s);
+ j += tdsqlite3Strlen30(&z[j]);
break;
}
- case 'H': sqlite3_snprintf(3, &z[j],"%02d",x.h); j+=2; break;
+ case 'H': tdsqlite3_snprintf(3, &z[j],"%02d",x.h); j+=2; break;
case 'W': /* Fall thru */
case 'j': {
int nDay; /* Number of days since 1st day of year */
@@ -23347,34 +26909,34 @@ static void strftimeFunc(
if( zFmt[i]=='W' ){
int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */
wd = (int)(((x.iJD+43200000)/86400000)%7);
- sqlite3_snprintf(3, &z[j],"%02d",(nDay+7-wd)/7);
+ tdsqlite3_snprintf(3, &z[j],"%02d",(nDay+7-wd)/7);
j += 2;
}else{
- sqlite3_snprintf(4, &z[j],"%03d",nDay+1);
+ tdsqlite3_snprintf(4, &z[j],"%03d",nDay+1);
j += 3;
}
break;
}
case 'J': {
- sqlite3_snprintf(20, &z[j],"%.16g",x.iJD/86400000.0);
- j+=sqlite3Strlen30(&z[j]);
+ tdsqlite3_snprintf(20, &z[j],"%.16g",x.iJD/86400000.0);
+ j+=tdsqlite3Strlen30(&z[j]);
break;
}
- case 'm': sqlite3_snprintf(3, &z[j],"%02d",x.M); j+=2; break;
- case 'M': sqlite3_snprintf(3, &z[j],"%02d",x.m); j+=2; break;
+ case 'm': tdsqlite3_snprintf(3, &z[j],"%02d",x.M); j+=2; break;
+ case 'M': tdsqlite3_snprintf(3, &z[j],"%02d",x.m); j+=2; break;
case 's': {
- sqlite3_snprintf(30,&z[j],"%lld",
+ tdsqlite3_snprintf(30,&z[j],"%lld",
(i64)(x.iJD/1000 - 21086676*(i64)10000));
- j += sqlite3Strlen30(&z[j]);
+ j += tdsqlite3Strlen30(&z[j]);
break;
}
- case 'S': sqlite3_snprintf(3,&z[j],"%02d",(int)x.s); j+=2; break;
+ case 'S': tdsqlite3_snprintf(3,&z[j],"%02d",(int)x.s); j+=2; break;
case 'w': {
z[j++] = (char)(((x.iJD+129600000)/86400000) % 7) + '0';
break;
}
case 'Y': {
- sqlite3_snprintf(5,&z[j],"%04d",x.Y); j+=sqlite3Strlen30(&z[j]);
+ tdsqlite3_snprintf(5,&z[j],"%04d",x.Y); j+=tdsqlite3Strlen30(&z[j]);
break;
}
default: z[j++] = '%'; break;
@@ -23382,7 +26944,7 @@ static void strftimeFunc(
}
}
z[j] = 0;
- sqlite3_result_text(context, z, -1,
+ tdsqlite3_result_text(context, z, -1,
z==zBuf ? SQLITE_TRANSIENT : SQLITE_DYNAMIC);
}
@@ -23392,9 +26954,9 @@ static void strftimeFunc(
** This function returns the same value as time('now').
*/
static void ctimeFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **NotUsed2
+ tdsqlite3_value **NotUsed2
){
UNUSED_PARAMETER2(NotUsed, NotUsed2);
timeFunc(context, 0, 0);
@@ -23406,9 +26968,9 @@ static void ctimeFunc(
** This function returns the same value as date('now').
*/
static void cdateFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **NotUsed2
+ tdsqlite3_value **NotUsed2
){
UNUSED_PARAMETER2(NotUsed, NotUsed2);
dateFunc(context, 0, 0);
@@ -23420,9 +26982,9 @@ static void cdateFunc(
** This function returns the same value as datetime('now').
*/
static void ctimestampFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **NotUsed2
+ tdsqlite3_value **NotUsed2
){
UNUSED_PARAMETER2(NotUsed, NotUsed2);
datetimeFunc(context, 0, 0);
@@ -23442,13 +27004,13 @@ static void ctimestampFunc(
** as the user-data for the function.
*/
static void currentTimeFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
time_t t;
- char *zFormat = (char *)sqlite3_user_data(context);
- sqlite3_int64 iT;
+ char *zFormat = (char *)tdsqlite3_user_data(context);
+ tdsqlite3_int64 iT;
struct tm *pTm;
struct tm sNow;
char zBuf[20];
@@ -23456,20 +27018,20 @@ static void currentTimeFunc(
UNUSED_PARAMETER(argc);
UNUSED_PARAMETER(argv);
- iT = sqlite3StmtCurrentTime(context);
+ iT = tdsqlite3StmtCurrentTime(context);
if( iT<=0 ) return;
- t = iT/1000 - 10000*(sqlite3_int64)21086676;
+ t = iT/1000 - 10000*(tdsqlite3_int64)21086676;
#if HAVE_GMTIME_R
pTm = gmtime_r(&t, &sNow);
#else
- sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ tdsqlite3_mutex_enter(tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
pTm = gmtime(&t);
if( pTm ) memcpy(&sNow, pTm, sizeof(sNow));
- sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ tdsqlite3_mutex_leave(tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
#endif
if( pTm ){
strftime(zBuf, 20, zFormat, &sNow);
- sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
}
}
#endif
@@ -23479,14 +27041,14 @@ static void currentTimeFunc(
** functions. This should be the only routine in this file with
** external linkage.
*/
-SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void){
+SQLITE_PRIVATE void tdsqlite3RegisterDateTimeFunctions(void){
static FuncDef aDateTimeFuncs[] = {
#ifndef SQLITE_OMIT_DATETIME_FUNCS
- DFUNCTION(julianday, -1, 0, 0, juliandayFunc ),
- DFUNCTION(date, -1, 0, 0, dateFunc ),
- DFUNCTION(time, -1, 0, 0, timeFunc ),
- DFUNCTION(datetime, -1, 0, 0, datetimeFunc ),
- DFUNCTION(strftime, -1, 0, 0, strftimeFunc ),
+ PURE_DATE(julianday, -1, 0, 0, juliandayFunc ),
+ PURE_DATE(date, -1, 0, 0, dateFunc ),
+ PURE_DATE(time, -1, 0, 0, timeFunc ),
+ PURE_DATE(datetime, -1, 0, 0, datetimeFunc ),
+ PURE_DATE(strftime, -1, 0, 0, strftimeFunc ),
DFUNCTION(current_time, 0, 0, 0, ctimeFunc ),
DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc),
DFUNCTION(current_date, 0, 0, 0, cdateFunc ),
@@ -23496,7 +27058,7 @@ SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void){
STR_FUNCTION(current_timestamp, 0, "%Y-%m-%d %H:%M:%S", 0, currentTimeFunc),
#endif
};
- sqlite3InsertBuiltinFuncs(aDateTimeFuncs, ArraySize(aDateTimeFuncs));
+ tdsqlite3InsertBuiltinFuncs(aDateTimeFuncs, ArraySize(aDateTimeFuncs));
}
/************** End of date.c ************************************************/
@@ -23524,53 +27086,53 @@ SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void){
** is used for testing the I/O recovery logic.
*/
#if defined(SQLITE_TEST)
-SQLITE_API int sqlite3_io_error_hit = 0; /* Total number of I/O Errors */
-SQLITE_API int sqlite3_io_error_hardhit = 0; /* Number of non-benign errors */
-SQLITE_API int sqlite3_io_error_pending = 0; /* Count down to first I/O error */
-SQLITE_API int sqlite3_io_error_persist = 0; /* True if I/O errors persist */
-SQLITE_API int sqlite3_io_error_benign = 0; /* True if errors are benign */
-SQLITE_API int sqlite3_diskfull_pending = 0;
-SQLITE_API int sqlite3_diskfull = 0;
+SQLITE_API int tdsqlite3_io_error_hit = 0; /* Total number of I/O Errors */
+SQLITE_API int tdsqlite3_io_error_hardhit = 0; /* Number of non-benign errors */
+SQLITE_API int tdsqlite3_io_error_pending = 0; /* Count down to first I/O error */
+SQLITE_API int tdsqlite3_io_error_persist = 0; /* True if I/O errors persist */
+SQLITE_API int tdsqlite3_io_error_benign = 0; /* True if errors are benign */
+SQLITE_API int tdsqlite3_diskfull_pending = 0;
+SQLITE_API int tdsqlite3_diskfull = 0;
#endif /* defined(SQLITE_TEST) */
/*
** When testing, also keep a count of the number of open files.
*/
#if defined(SQLITE_TEST)
-SQLITE_API int sqlite3_open_file_count = 0;
+SQLITE_API int tdsqlite3_open_file_count = 0;
#endif /* defined(SQLITE_TEST) */
/*
-** The default SQLite sqlite3_vfs implementations do not allocate
+** The default SQLite tdsqlite3_vfs implementations do not allocate
** memory (actually, os_unix.c allocates a small amount of memory
** from within OsOpen()), but some third-party implementations may.
-** So we test the effects of a malloc() failing and the sqlite3OsXXX()
+** So we test the effects of a malloc() failing and the tdsqlite3OsXXX()
** function returning SQLITE_IOERR_NOMEM using the DO_OS_MALLOC_TEST macro.
**
** The following functions are instrumented for malloc() failure
** testing:
**
-** sqlite3OsRead()
-** sqlite3OsWrite()
-** sqlite3OsSync()
-** sqlite3OsFileSize()
-** sqlite3OsLock()
-** sqlite3OsCheckReservedLock()
-** sqlite3OsFileControl()
-** sqlite3OsShmMap()
-** sqlite3OsOpen()
-** sqlite3OsDelete()
-** sqlite3OsAccess()
-** sqlite3OsFullPathname()
+** tdsqlite3OsRead()
+** tdsqlite3OsWrite()
+** tdsqlite3OsSync()
+** tdsqlite3OsFileSize()
+** tdsqlite3OsLock()
+** tdsqlite3OsCheckReservedLock()
+** tdsqlite3OsFileControl()
+** tdsqlite3OsShmMap()
+** tdsqlite3OsOpen()
+** tdsqlite3OsDelete()
+** tdsqlite3OsAccess()
+** tdsqlite3OsFullPathname()
**
*/
#if defined(SQLITE_TEST)
-SQLITE_API int sqlite3_memdebug_vfs_oom_test = 1;
+SQLITE_API int tdsqlite3_memdebug_vfs_oom_test = 1;
#define DO_OS_MALLOC_TEST(x) \
- if (sqlite3_memdebug_vfs_oom_test && (!x || !sqlite3JournalIsInMemory(x))) { \
- void *pTstAlloc = sqlite3Malloc(10); \
+ if (tdsqlite3_memdebug_vfs_oom_test && (!x || !tdsqlite3JournalIsInMemory(x))) { \
+ void *pTstAlloc = tdsqlite3Malloc(10); \
if (!pTstAlloc) return SQLITE_IOERR_NOMEM_BKPT; \
- sqlite3_free(pTstAlloc); \
+ tdsqlite3_free(pTstAlloc); \
}
#else
#define DO_OS_MALLOC_TEST(x)
@@ -23578,58 +27140,61 @@ SQLITE_API int sqlite3_memdebug_vfs_oom_test = 1;
/*
** The following routines are convenience wrappers around methods
-** of the sqlite3_file object. This is mostly just syntactic sugar. All
+** of the tdsqlite3_file object. This is mostly just syntactic sugar. All
** of this would be completely automatic if SQLite were coded using
** C++ instead of plain old C.
*/
-SQLITE_PRIVATE void sqlite3OsClose(sqlite3_file *pId){
+SQLITE_PRIVATE void tdsqlite3OsClose(tdsqlite3_file *pId){
if( pId->pMethods ){
pId->pMethods->xClose(pId);
pId->pMethods = 0;
}
}
-SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file *id, void *pBuf, int amt, i64 offset){
+SQLITE_PRIVATE int tdsqlite3OsRead(tdsqlite3_file *id, void *pBuf, int amt, i64 offset){
DO_OS_MALLOC_TEST(id);
return id->pMethods->xRead(id, pBuf, amt, offset);
}
-SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file *id, const void *pBuf, int amt, i64 offset){
+SQLITE_PRIVATE int tdsqlite3OsWrite(tdsqlite3_file *id, const void *pBuf, int amt, i64 offset){
DO_OS_MALLOC_TEST(id);
return id->pMethods->xWrite(id, pBuf, amt, offset);
}
-SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file *id, i64 size){
+SQLITE_PRIVATE int tdsqlite3OsTruncate(tdsqlite3_file *id, i64 size){
return id->pMethods->xTruncate(id, size);
}
-SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file *id, int flags){
+SQLITE_PRIVATE int tdsqlite3OsSync(tdsqlite3_file *id, int flags){
DO_OS_MALLOC_TEST(id);
- return id->pMethods->xSync(id, flags);
+ return flags ? id->pMethods->xSync(id, flags) : SQLITE_OK;
}
-SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file *id, i64 *pSize){
+SQLITE_PRIVATE int tdsqlite3OsFileSize(tdsqlite3_file *id, i64 *pSize){
DO_OS_MALLOC_TEST(id);
return id->pMethods->xFileSize(id, pSize);
}
-SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file *id, int lockType){
+SQLITE_PRIVATE int tdsqlite3OsLock(tdsqlite3_file *id, int lockType){
DO_OS_MALLOC_TEST(id);
return id->pMethods->xLock(id, lockType);
}
-SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file *id, int lockType){
+SQLITE_PRIVATE int tdsqlite3OsUnlock(tdsqlite3_file *id, int lockType){
return id->pMethods->xUnlock(id, lockType);
}
-SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut){
+SQLITE_PRIVATE int tdsqlite3OsCheckReservedLock(tdsqlite3_file *id, int *pResOut){
DO_OS_MALLOC_TEST(id);
return id->pMethods->xCheckReservedLock(id, pResOut);
}
/*
-** Use sqlite3OsFileControl() when we are doing something that might fail
-** and we need to know about the failures. Use sqlite3OsFileControlHint()
+** Use tdsqlite3OsFileControl() when we are doing something that might fail
+** and we need to know about the failures. Use tdsqlite3OsFileControlHint()
** when simply tossing information over the wall to the VFS and we do not
** really care if the VFS receives and understands the information since it
-** is only a hint and can be safely ignored. The sqlite3OsFileControlHint()
+** is only a hint and can be safely ignored. The tdsqlite3OsFileControlHint()
** routine has no return value since the return value would be meaningless.
*/
-SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){
+SQLITE_PRIVATE int tdsqlite3OsFileControl(tdsqlite3_file *id, int op, void *pArg){
+ if( id->pMethods==0 ) return SQLITE_NOTFOUND;
#ifdef SQLITE_TEST
- if( op!=SQLITE_FCNTL_COMMIT_PHASETWO ){
+ if( op!=SQLITE_FCNTL_COMMIT_PHASETWO
+ && op!=SQLITE_FCNTL_LOCK_TIMEOUT
+ ){
/* Faults are not injected into COMMIT_PHASETWO because, assuming SQLite
** is using a regular VFS, it is called after the corresponding
** transaction has been committed. Injecting a fault at this point
@@ -23645,28 +27210,29 @@ SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){
#endif
return id->pMethods->xFileControl(id, op, pArg);
}
-SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file *id, int op, void *pArg){
- (void)id->pMethods->xFileControl(id, op, pArg);
+SQLITE_PRIVATE void tdsqlite3OsFileControlHint(tdsqlite3_file *id, int op, void *pArg){
+ if( id->pMethods ) (void)id->pMethods->xFileControl(id, op, pArg);
}
-SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id){
- int (*xSectorSize)(sqlite3_file*) = id->pMethods->xSectorSize;
+SQLITE_PRIVATE int tdsqlite3OsSectorSize(tdsqlite3_file *id){
+ int (*xSectorSize)(tdsqlite3_file*) = id->pMethods->xSectorSize;
return (xSectorSize ? xSectorSize(id) : SQLITE_DEFAULT_SECTOR_SIZE);
}
-SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id){
+SQLITE_PRIVATE int tdsqlite3OsDeviceCharacteristics(tdsqlite3_file *id){
return id->pMethods->xDeviceCharacteristics(id);
}
-SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int offset, int n, int flags){
+#ifndef SQLITE_OMIT_WAL
+SQLITE_PRIVATE int tdsqlite3OsShmLock(tdsqlite3_file *id, int offset, int n, int flags){
return id->pMethods->xShmLock(id, offset, n, flags);
}
-SQLITE_PRIVATE void sqlite3OsShmBarrier(sqlite3_file *id){
+SQLITE_PRIVATE void tdsqlite3OsShmBarrier(tdsqlite3_file *id){
id->pMethods->xShmBarrier(id);
}
-SQLITE_PRIVATE int sqlite3OsShmUnmap(sqlite3_file *id, int deleteFlag){
+SQLITE_PRIVATE int tdsqlite3OsShmUnmap(tdsqlite3_file *id, int deleteFlag){
return id->pMethods->xShmUnmap(id, deleteFlag);
}
-SQLITE_PRIVATE int sqlite3OsShmMap(
- sqlite3_file *id, /* Database file handle */
+SQLITE_PRIVATE int tdsqlite3OsShmMap(
+ tdsqlite3_file *id, /* Database file handle */
int iPage,
int pgsz,
int bExtend, /* True to extend file if necessary */
@@ -23675,23 +27241,24 @@ SQLITE_PRIVATE int sqlite3OsShmMap(
DO_OS_MALLOC_TEST(id);
return id->pMethods->xShmMap(id, iPage, pgsz, bExtend, pp);
}
+#endif /* SQLITE_OMIT_WAL */
#if SQLITE_MAX_MMAP_SIZE>0
/* The real implementation of xFetch and xUnfetch */
-SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64 iOff, int iAmt, void **pp){
+SQLITE_PRIVATE int tdsqlite3OsFetch(tdsqlite3_file *id, i64 iOff, int iAmt, void **pp){
DO_OS_MALLOC_TEST(id);
return id->pMethods->xFetch(id, iOff, iAmt, pp);
}
-SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *id, i64 iOff, void *p){
+SQLITE_PRIVATE int tdsqlite3OsUnfetch(tdsqlite3_file *id, i64 iOff, void *p){
return id->pMethods->xUnfetch(id, iOff, p);
}
#else
/* No-op stubs to use when memory-mapped I/O is disabled */
-SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64 iOff, int iAmt, void **pp){
+SQLITE_PRIVATE int tdsqlite3OsFetch(tdsqlite3_file *id, i64 iOff, int iAmt, void **pp){
*pp = 0;
return SQLITE_OK;
}
-SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *id, i64 iOff, void *p){
+SQLITE_PRIVATE int tdsqlite3OsUnfetch(tdsqlite3_file *id, i64 iOff, void *p){
return SQLITE_OK;
}
#endif
@@ -23700,10 +27267,10 @@ SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *id, i64 iOff, void *p){
** The next group of routines are convenience wrappers around the
** VFS methods.
*/
-SQLITE_PRIVATE int sqlite3OsOpen(
- sqlite3_vfs *pVfs,
+SQLITE_PRIVATE int tdsqlite3OsOpen(
+ tdsqlite3_vfs *pVfs,
const char *zPath,
- sqlite3_file *pFile,
+ tdsqlite3_file *pFile,
int flags,
int *pFlagsOut
){
@@ -23713,17 +27280,17 @@ SQLITE_PRIVATE int sqlite3OsOpen(
** down into the VFS layer. Some SQLITE_OPEN_ flags (for example,
** SQLITE_OPEN_FULLMUTEX or SQLITE_OPEN_SHAREDCACHE) are blocked before
** reaching the VFS. */
- rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x87f7f, pFlagsOut);
+ rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x1087f7f, pFlagsOut);
assert( rc==SQLITE_OK || pFile->pMethods==0 );
return rc;
}
-SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
+SQLITE_PRIVATE int tdsqlite3OsDelete(tdsqlite3_vfs *pVfs, const char *zPath, int dirSync){
DO_OS_MALLOC_TEST(0);
assert( dirSync==0 || dirSync==1 );
return pVfs->xDelete(pVfs, zPath, dirSync);
}
-SQLITE_PRIVATE int sqlite3OsAccess(
- sqlite3_vfs *pVfs,
+SQLITE_PRIVATE int tdsqlite3OsAccess(
+ tdsqlite3_vfs *pVfs,
const char *zPath,
int flags,
int *pResOut
@@ -23731,8 +27298,8 @@ SQLITE_PRIVATE int sqlite3OsAccess(
DO_OS_MALLOC_TEST(0);
return pVfs->xAccess(pVfs, zPath, flags, pResOut);
}
-SQLITE_PRIVATE int sqlite3OsFullPathname(
- sqlite3_vfs *pVfs,
+SQLITE_PRIVATE int tdsqlite3OsFullPathname(
+ tdsqlite3_vfs *pVfs,
const char *zPath,
int nPathOut,
char *zPathOut
@@ -23742,29 +27309,37 @@ SQLITE_PRIVATE int sqlite3OsFullPathname(
return pVfs->xFullPathname(pVfs, zPath, nPathOut, zPathOut);
}
#ifndef SQLITE_OMIT_LOAD_EXTENSION
-SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
+SQLITE_PRIVATE void *tdsqlite3OsDlOpen(tdsqlite3_vfs *pVfs, const char *zPath){
return pVfs->xDlOpen(pVfs, zPath);
}
-SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
+SQLITE_PRIVATE void tdsqlite3OsDlError(tdsqlite3_vfs *pVfs, int nByte, char *zBufOut){
pVfs->xDlError(pVfs, nByte, zBufOut);
}
-SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *pVfs, void *pHdle, const char *zSym))(void){
+SQLITE_PRIVATE void (*tdsqlite3OsDlSym(tdsqlite3_vfs *pVfs, void *pHdle, const char *zSym))(void){
return pVfs->xDlSym(pVfs, pHdle, zSym);
}
-SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *pVfs, void *pHandle){
+SQLITE_PRIVATE void tdsqlite3OsDlClose(tdsqlite3_vfs *pVfs, void *pHandle){
pVfs->xDlClose(pVfs, pHandle);
}
#endif /* SQLITE_OMIT_LOAD_EXTENSION */
-SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
- return pVfs->xRandomness(pVfs, nByte, zBufOut);
+SQLITE_PRIVATE int tdsqlite3OsRandomness(tdsqlite3_vfs *pVfs, int nByte, char *zBufOut){
+ if( tdsqlite3Config.iPrngSeed ){
+ memset(zBufOut, 0, nByte);
+ if( ALWAYS(nByte>(signed)sizeof(unsigned)) ) nByte = sizeof(unsigned int);
+ memcpy(zBufOut, &tdsqlite3Config.iPrngSeed, nByte);
+ return SQLITE_OK;
+ }else{
+ return pVfs->xRandomness(pVfs, nByte, zBufOut);
+ }
+
}
-SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *pVfs, int nMicro){
+SQLITE_PRIVATE int tdsqlite3OsSleep(tdsqlite3_vfs *pVfs, int nMicro){
return pVfs->xSleep(pVfs, nMicro);
}
-SQLITE_PRIVATE int sqlite3OsGetLastError(sqlite3_vfs *pVfs){
+SQLITE_PRIVATE int tdsqlite3OsGetLastError(tdsqlite3_vfs *pVfs){
return pVfs->xGetLastError ? pVfs->xGetLastError(pVfs, 0, 0) : 0;
}
-SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
+SQLITE_PRIVATE int tdsqlite3OsCurrentTimeInt64(tdsqlite3_vfs *pVfs, tdsqlite3_int64 *pTimeOut){
int rc;
/* IMPLEMENTATION-OF: R-49045-42493 SQLite will use the xCurrentTimeInt64()
** method to get the current date and time if that method is available
@@ -23777,25 +27352,25 @@ SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p
}else{
double r;
rc = pVfs->xCurrentTime(pVfs, &r);
- *pTimeOut = (sqlite3_int64)(r*86400000.0);
+ *pTimeOut = (tdsqlite3_int64)(r*86400000.0);
}
return rc;
}
-SQLITE_PRIVATE int sqlite3OsOpenMalloc(
- sqlite3_vfs *pVfs,
+SQLITE_PRIVATE int tdsqlite3OsOpenMalloc(
+ tdsqlite3_vfs *pVfs,
const char *zFile,
- sqlite3_file **ppFile,
+ tdsqlite3_file **ppFile,
int flags,
int *pOutFlags
){
int rc;
- sqlite3_file *pFile;
- pFile = (sqlite3_file *)sqlite3MallocZero(pVfs->szOsFile);
+ tdsqlite3_file *pFile;
+ pFile = (tdsqlite3_file *)tdsqlite3MallocZero(pVfs->szOsFile);
if( pFile ){
- rc = sqlite3OsOpen(pVfs, zFile, pFile, flags, pOutFlags);
+ rc = tdsqlite3OsOpen(pVfs, zFile, pFile, flags, pOutFlags);
if( rc!=SQLITE_OK ){
- sqlite3_free(pFile);
+ tdsqlite3_free(pFile);
}else{
*ppFile = pFile;
}
@@ -23804,67 +27379,67 @@ SQLITE_PRIVATE int sqlite3OsOpenMalloc(
}
return rc;
}
-SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *pFile){
+SQLITE_PRIVATE void tdsqlite3OsCloseFree(tdsqlite3_file *pFile){
assert( pFile );
- sqlite3OsClose(pFile);
- sqlite3_free(pFile);
+ tdsqlite3OsClose(pFile);
+ tdsqlite3_free(pFile);
}
/*
** This function is a wrapper around the OS specific implementation of
-** sqlite3_os_init(). The purpose of the wrapper is to provide the
+** tdsqlite3_os_init(). The purpose of the wrapper is to provide the
** ability to simulate a malloc failure, so that the handling of an
-** error in sqlite3_os_init() by the upper layers can be tested.
+** error in tdsqlite3_os_init() by the upper layers can be tested.
*/
-SQLITE_PRIVATE int sqlite3OsInit(void){
- void *p = sqlite3_malloc(10);
+SQLITE_PRIVATE int tdsqlite3OsInit(void){
+ void *p = tdsqlite3_malloc(10);
if( p==0 ) return SQLITE_NOMEM_BKPT;
- sqlite3_free(p);
- return sqlite3_os_init();
+ tdsqlite3_free(p);
+ return tdsqlite3_os_init();
}
/*
** The list of all registered VFS implementations.
*/
-static sqlite3_vfs * SQLITE_WSD vfsList = 0;
-#define vfsList GLOBAL(sqlite3_vfs *, vfsList)
+static tdsqlite3_vfs * SQLITE_WSD vfsList = 0;
+#define vfsList GLOBAL(tdsqlite3_vfs *, vfsList)
/*
** Locate a VFS by name. If no name is given, simply return the
** first VFS on the list.
*/
-SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfs){
- sqlite3_vfs *pVfs = 0;
+SQLITE_API tdsqlite3_vfs *tdsqlite3_vfs_find(const char *zVfs){
+ tdsqlite3_vfs *pVfs = 0;
#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex;
+ tdsqlite3_mutex *mutex;
#endif
#ifndef SQLITE_OMIT_AUTOINIT
- int rc = sqlite3_initialize();
+ int rc = tdsqlite3_initialize();
if( rc ) return 0;
#endif
#if SQLITE_THREADSAFE
- mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
+ mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
#endif
- sqlite3_mutex_enter(mutex);
+ tdsqlite3_mutex_enter(mutex);
for(pVfs = vfsList; pVfs; pVfs=pVfs->pNext){
if( zVfs==0 ) break;
if( strcmp(zVfs, pVfs->zName)==0 ) break;
}
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
return pVfs;
}
/*
** Unlink a VFS from the linked list
*/
-static void vfsUnlink(sqlite3_vfs *pVfs){
- assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)) );
+static void vfsUnlink(tdsqlite3_vfs *pVfs){
+ assert( tdsqlite3_mutex_held(tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)) );
if( pVfs==0 ){
/* No-op */
}else if( vfsList==pVfs ){
vfsList = pVfs->pNext;
}else if( vfsList ){
- sqlite3_vfs *p = vfsList;
+ tdsqlite3_vfs *p = vfsList;
while( p->pNext && p->pNext!=pVfs ){
p = p->pNext;
}
@@ -23879,18 +27454,18 @@ static void vfsUnlink(sqlite3_vfs *pVfs){
** VFS multiple times. The new VFS becomes the default if makeDflt is
** true.
*/
-SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){
- MUTEX_LOGIC(sqlite3_mutex *mutex;)
+SQLITE_API int tdsqlite3_vfs_register(tdsqlite3_vfs *pVfs, int makeDflt){
+ MUTEX_LOGIC(tdsqlite3_mutex *mutex;)
#ifndef SQLITE_OMIT_AUTOINIT
- int rc = sqlite3_initialize();
+ int rc = tdsqlite3_initialize();
if( rc ) return rc;
#endif
#ifdef SQLITE_ENABLE_API_ARMOR
if( pVfs==0 ) return SQLITE_MISUSE_BKPT;
#endif
- MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
- sqlite3_mutex_enter(mutex);
+ MUTEX_LOGIC( mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
+ tdsqlite3_mutex_enter(mutex);
vfsUnlink(pVfs);
if( makeDflt || vfsList==0 ){
pVfs->pNext = vfsList;
@@ -23900,20 +27475,23 @@ SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){
vfsList->pNext = pVfs;
}
assert(vfsList);
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
return SQLITE_OK;
}
/*
** Unregister a VFS so that it is no longer accessible.
*/
-SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs *pVfs){
-#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
+SQLITE_API int tdsqlite3_vfs_unregister(tdsqlite3_vfs *pVfs){
+ MUTEX_LOGIC(tdsqlite3_mutex *mutex;)
+#ifndef SQLITE_OMIT_AUTOINIT
+ int rc = tdsqlite3_initialize();
+ if( rc ) return rc;
#endif
- sqlite3_mutex_enter(mutex);
+ MUTEX_LOGIC( mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
+ tdsqlite3_mutex_enter(mutex);
vfsUnlink(pVfs);
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
return SQLITE_OK;
}
@@ -23933,7 +27511,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs *pVfs){
**
** This file contains code to support the concept of "benign"
** malloc failures (when the xMalloc() or xRealloc() method of the
-** sqlite3_mem_methods structure fails to allocate a block of memory
+** tdsqlite3_mem_methods structure fails to allocate a block of memory
** and returns 0).
**
** Most malloc failures are non-benign. After they occur, SQLite
@@ -23947,7 +27525,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs *pVfs){
/* #include "sqliteInt.h" */
-#ifndef SQLITE_OMIT_BUILTIN_TEST
+#ifndef SQLITE_UNTESTABLE
/*
** Global variables.
@@ -23956,29 +27534,29 @@ typedef struct BenignMallocHooks BenignMallocHooks;
static SQLITE_WSD struct BenignMallocHooks {
void (*xBenignBegin)(void);
void (*xBenignEnd)(void);
-} sqlite3Hooks = { 0, 0 };
+} tdsqlite3Hooks = { 0, 0 };
/* The "wsdHooks" macro will resolve to the appropriate BenignMallocHooks
** structure. If writable static data is unsupported on the target,
** we have to locate the state vector at run-time. In the more common
** case where writable static data is supported, wsdHooks can refer directly
-** to the "sqlite3Hooks" state vector declared above.
+** to the "tdsqlite3Hooks" state vector declared above.
*/
#ifdef SQLITE_OMIT_WSD
# define wsdHooksInit \
- BenignMallocHooks *x = &GLOBAL(BenignMallocHooks,sqlite3Hooks)
+ BenignMallocHooks *x = &GLOBAL(BenignMallocHooks,tdsqlite3Hooks)
# define wsdHooks x[0]
#else
# define wsdHooksInit
-# define wsdHooks sqlite3Hooks
+# define wsdHooks tdsqlite3Hooks
#endif
/*
-** Register hooks to call when sqlite3BeginBenignMalloc() and
-** sqlite3EndBenignMalloc() are called, respectively.
+** Register hooks to call when tdsqlite3BeginBenignMalloc() and
+** tdsqlite3EndBenignMalloc() are called, respectively.
*/
-SQLITE_PRIVATE void sqlite3BenignMallocHooks(
+SQLITE_PRIVATE void tdsqlite3BenignMallocHooks(
void (*xBenignBegin)(void),
void (*xBenignEnd)(void)
){
@@ -23988,24 +27566,24 @@ SQLITE_PRIVATE void sqlite3BenignMallocHooks(
}
/*
-** This (sqlite3EndBenignMalloc()) is called by SQLite code to indicate that
-** subsequent malloc failures are benign. A call to sqlite3EndBenignMalloc()
+** This (tdsqlite3EndBenignMalloc()) is called by SQLite code to indicate that
+** subsequent malloc failures are benign. A call to tdsqlite3EndBenignMalloc()
** indicates that subsequent malloc failures are non-benign.
*/
-SQLITE_PRIVATE void sqlite3BeginBenignMalloc(void){
+SQLITE_PRIVATE void tdsqlite3BeginBenignMalloc(void){
wsdHooksInit;
if( wsdHooks.xBenignBegin ){
wsdHooks.xBenignBegin();
}
}
-SQLITE_PRIVATE void sqlite3EndBenignMalloc(void){
+SQLITE_PRIVATE void tdsqlite3EndBenignMalloc(void){
wsdHooksInit;
if( wsdHooks.xBenignEnd ){
wsdHooks.xBenignEnd();
}
}
-#endif /* #ifndef SQLITE_OMIT_BUILTIN_TEST */
+#endif /* #ifndef SQLITE_UNTESTABLE */
/************** End of fault.c ***********************************************/
/************** Begin file mem0.c ********************************************/
@@ -24025,7 +27603,7 @@ SQLITE_PRIVATE void sqlite3EndBenignMalloc(void){
** SQLITE_ZERO_MALLOC is defined. The allocation drivers implemented
** here always fail. SQLite will not operate with these drivers. These
** are merely placeholders. Real drivers must be substituted using
-** sqlite3_config() before SQLite will operate.
+** tdsqlite3_config() before SQLite will operate.
*/
/* #include "sqliteInt.h" */
@@ -24039,32 +27617,32 @@ SQLITE_PRIVATE void sqlite3EndBenignMalloc(void){
/*
** No-op versions of all memory allocation routines
*/
-static void *sqlite3MemMalloc(int nByte){ return 0; }
-static void sqlite3MemFree(void *pPrior){ return; }
-static void *sqlite3MemRealloc(void *pPrior, int nByte){ return 0; }
-static int sqlite3MemSize(void *pPrior){ return 0; }
-static int sqlite3MemRoundup(int n){ return n; }
-static int sqlite3MemInit(void *NotUsed){ return SQLITE_OK; }
-static void sqlite3MemShutdown(void *NotUsed){ return; }
+static void *tdsqlite3MemMalloc(int nByte){ return 0; }
+static void tdsqlite3MemFree(void *pPrior){ return; }
+static void *tdsqlite3MemRealloc(void *pPrior, int nByte){ return 0; }
+static int tdsqlite3MemSize(void *pPrior){ return 0; }
+static int tdsqlite3MemRoundup(int n){ return n; }
+static int tdsqlite3MemInit(void *NotUsed){ return SQLITE_OK; }
+static void tdsqlite3MemShutdown(void *NotUsed){ return; }
/*
** This routine is the only routine in this file with external linkage.
**
** Populate the low-level memory allocation function pointers in
-** sqlite3GlobalConfig.m with pointers to the routines in this file.
-*/
-SQLITE_PRIVATE void sqlite3MemSetDefault(void){
- static const sqlite3_mem_methods defaultMethods = {
- sqlite3MemMalloc,
- sqlite3MemFree,
- sqlite3MemRealloc,
- sqlite3MemSize,
- sqlite3MemRoundup,
- sqlite3MemInit,
- sqlite3MemShutdown,
+** tdsqlite3GlobalConfig.m with pointers to the routines in this file.
+*/
+SQLITE_PRIVATE void tdsqlite3MemSetDefault(void){
+ static const tdsqlite3_mem_methods defaultMethods = {
+ tdsqlite3MemMalloc,
+ tdsqlite3MemFree,
+ tdsqlite3MemRealloc,
+ tdsqlite3MemSize,
+ tdsqlite3MemRoundup,
+ tdsqlite3MemInit,
+ tdsqlite3MemShutdown,
0
};
- sqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods);
+ tdsqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods);
}
#endif /* SQLITE_ZERO_MALLOC */
@@ -24088,7 +27666,7 @@ SQLITE_PRIVATE void sqlite3MemSetDefault(void){
** to obtain the memory it needs.
**
** This file contains implementations of the low-level memory allocation
-** routines specified in the sqlite3_mem_methods object. The content of
+** routines specified in the tdsqlite3_mem_methods object. The content of
** this file is only used if SQLITE_SYSTEM_MALLOC is defined. The
** SQLITE_SYSTEM_MALLOC macro is defined automatically if neither the
** SQLITE_MEMDEBUG nor the SQLITE_WIN32_MALLOC macros are defined. The
@@ -24130,7 +27708,9 @@ SQLITE_PRIVATE void sqlite3MemSetDefault(void){
*/
#include <sys/sysctl.h>
#include <malloc/malloc.h>
+#ifdef SQLITE_MIGHT_BE_SINGLE_CORE
#include <libkern/OSAtomic.h>
+#endif /* SQLITE_MIGHT_BE_SINGLE_CORE */
static malloc_zone_t* _sqliteZone_;
#define SQLITE_MALLOC(x) malloc_zone_malloc(_sqliteZone_, (x))
#define SQLITE_FREE(x) malloc_zone_free(_sqliteZone_, (x));
@@ -24190,49 +27770,51 @@ static malloc_zone_t* _sqliteZone_;
/*
** Like malloc(), but remember the size of the allocation
-** so that we can find it later using sqlite3MemSize().
+** so that we can find it later using tdsqlite3MemSize().
**
** For this low-level routine, we are guaranteed that nByte>0 because
** cases of nByte<=0 will be intercepted and dealt with by higher level
** routines.
*/
-static void *sqlite3MemMalloc(int nByte){
+static void *tdsqlite3MemMalloc(int nByte){
#ifdef SQLITE_MALLOCSIZE
- void *p = SQLITE_MALLOC( nByte );
+ void *p;
+ testcase( ROUND8(nByte)==nByte );
+ p = SQLITE_MALLOC( nByte );
if( p==0 ){
- testcase( sqlite3GlobalConfig.xLog!=0 );
- sqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes of memory", nByte);
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ tdsqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes of memory", nByte);
}
return p;
#else
- sqlite3_int64 *p;
+ tdsqlite3_int64 *p;
assert( nByte>0 );
- nByte = ROUND8(nByte);
+ testcase( ROUND8(nByte)!=nByte );
p = SQLITE_MALLOC( nByte+8 );
if( p ){
p[0] = nByte;
p++;
}else{
- testcase( sqlite3GlobalConfig.xLog!=0 );
- sqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes of memory", nByte);
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ tdsqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes of memory", nByte);
}
return (void *)p;
#endif
}
/*
-** Like free() but works for allocations obtained from sqlite3MemMalloc()
-** or sqlite3MemRealloc().
+** Like free() but works for allocations obtained from tdsqlite3MemMalloc()
+** or tdsqlite3MemRealloc().
**
** For this low-level routine, we already know that pPrior!=0 since
** cases where pPrior==0 will have been intecepted and dealt with
** by higher-level routines.
*/
-static void sqlite3MemFree(void *pPrior){
+static void tdsqlite3MemFree(void *pPrior){
#ifdef SQLITE_MALLOCSIZE
SQLITE_FREE(pPrior);
#else
- sqlite3_int64 *p = (sqlite3_int64*)pPrior;
+ tdsqlite3_int64 *p = (tdsqlite3_int64*)pPrior;
assert( pPrior!=0 );
p--;
SQLITE_FREE(p);
@@ -24243,14 +27825,14 @@ static void sqlite3MemFree(void *pPrior){
** Report the allocated size of a prior return from xMalloc()
** or xRealloc().
*/
-static int sqlite3MemSize(void *pPrior){
+static int tdsqlite3MemSize(void *pPrior){
#ifdef SQLITE_MALLOCSIZE
assert( pPrior!=0 );
return (int)SQLITE_MALLOCSIZE(pPrior);
#else
- sqlite3_int64 *p;
+ tdsqlite3_int64 *p;
assert( pPrior!=0 );
- p = (sqlite3_int64*)pPrior;
+ p = (tdsqlite3_int64*)pPrior;
p--;
return (int)p[0];
#endif
@@ -24258,7 +27840,7 @@ static int sqlite3MemSize(void *pPrior){
/*
** Like realloc(). Resize an allocation previously obtained from
-** sqlite3MemMalloc().
+** tdsqlite3MemMalloc().
**
** For this low-level interface, we know that pPrior!=0. Cases where
** pPrior==0 while have been intercepted by higher-level routine and
@@ -24266,18 +27848,18 @@ static int sqlite3MemSize(void *pPrior){
** cases where nByte<=0 will have been intercepted by higher-level
** routines and redirected to xFree.
*/
-static void *sqlite3MemRealloc(void *pPrior, int nByte){
+static void *tdsqlite3MemRealloc(void *pPrior, int nByte){
#ifdef SQLITE_MALLOCSIZE
void *p = SQLITE_REALLOC(pPrior, nByte);
if( p==0 ){
- testcase( sqlite3GlobalConfig.xLog!=0 );
- sqlite3_log(SQLITE_NOMEM,
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ tdsqlite3_log(SQLITE_NOMEM,
"failed memory resize %u to %u bytes",
SQLITE_MALLOCSIZE(pPrior), nByte);
}
return p;
#else
- sqlite3_int64 *p = (sqlite3_int64*)pPrior;
+ tdsqlite3_int64 *p = (tdsqlite3_int64*)pPrior;
assert( pPrior!=0 && nByte>0 );
assert( nByte==ROUND8(nByte) ); /* EV: R-46199-30249 */
p--;
@@ -24286,10 +27868,10 @@ static void *sqlite3MemRealloc(void *pPrior, int nByte){
p[0] = nByte;
p++;
}else{
- testcase( sqlite3GlobalConfig.xLog!=0 );
- sqlite3_log(SQLITE_NOMEM,
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ tdsqlite3_log(SQLITE_NOMEM,
"failed memory resize %u to %u bytes",
- sqlite3MemSize(pPrior), nByte);
+ tdsqlite3MemSize(pPrior), nByte);
}
return (void*)p;
#endif
@@ -24298,14 +27880,14 @@ static void *sqlite3MemRealloc(void *pPrior, int nByte){
/*
** Round up a request size to the next valid allocation size.
*/
-static int sqlite3MemRoundup(int n){
+static int tdsqlite3MemRoundup(int n){
return ROUND8(n);
}
/*
** Initialize this module.
*/
-static int sqlite3MemInit(void *NotUsed){
+static int tdsqlite3MemInit(void *NotUsed){
#if defined(__APPLE__) && !defined(SQLITE_WITHOUT_ZONEMALLOC)
int cpuCount;
size_t len;
@@ -24321,19 +27903,10 @@ static int sqlite3MemInit(void *NotUsed){
}else{
/* only 1 core, use our own zone to contention over global locks,
** e.g. we have our own dedicated locks */
- bool success;
- malloc_zone_t* newzone = malloc_create_zone(4096, 0);
- malloc_set_zone_name(newzone, "Sqlite_Heap");
- do{
- success = OSAtomicCompareAndSwapPtrBarrier(NULL, newzone,
- (void * volatile *)&_sqliteZone_);
- }while(!_sqliteZone_);
- if( !success ){
- /* somebody registered a zone first */
- malloc_destroy_zone(newzone);
- }
+ _sqliteZone_ = malloc_create_zone(4096, 0);
+ malloc_set_zone_name(_sqliteZone_, "Sqlite_Heap");
}
-#endif
+#endif /* defined(__APPLE__) && !defined(SQLITE_WITHOUT_ZONEMALLOC) */
UNUSED_PARAMETER(NotUsed);
return SQLITE_OK;
}
@@ -24341,7 +27914,7 @@ static int sqlite3MemInit(void *NotUsed){
/*
** Deinitialize this module.
*/
-static void sqlite3MemShutdown(void *NotUsed){
+static void tdsqlite3MemShutdown(void *NotUsed){
UNUSED_PARAMETER(NotUsed);
return;
}
@@ -24350,20 +27923,20 @@ static void sqlite3MemShutdown(void *NotUsed){
** This routine is the only routine in this file with external linkage.
**
** Populate the low-level memory allocation function pointers in
-** sqlite3GlobalConfig.m with pointers to the routines in this file.
-*/
-SQLITE_PRIVATE void sqlite3MemSetDefault(void){
- static const sqlite3_mem_methods defaultMethods = {
- sqlite3MemMalloc,
- sqlite3MemFree,
- sqlite3MemRealloc,
- sqlite3MemSize,
- sqlite3MemRoundup,
- sqlite3MemInit,
- sqlite3MemShutdown,
+** tdsqlite3GlobalConfig.m with pointers to the routines in this file.
+*/
+SQLITE_PRIVATE void tdsqlite3MemSetDefault(void){
+ static const tdsqlite3_mem_methods defaultMethods = {
+ tdsqlite3MemMalloc,
+ tdsqlite3MemFree,
+ tdsqlite3MemRealloc,
+ tdsqlite3MemSize,
+ tdsqlite3MemRoundup,
+ tdsqlite3MemInit,
+ tdsqlite3MemShutdown,
0
};
- sqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods);
+ tdsqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods);
}
#endif /* SQLITE_SYSTEM_MALLOC */
@@ -24389,7 +27962,7 @@ SQLITE_PRIVATE void sqlite3MemSetDefault(void){
** leaks and memory usage errors.
**
** This file contains implementations of the low-level memory allocation
-** routines specified in the sqlite3_mem_methods object.
+** routines specified in the tdsqlite3_mem_methods object.
*/
/* #include "sqliteInt.h" */
@@ -24456,7 +28029,7 @@ static struct {
/*
** Mutex to control access to the memory allocation subsystem.
*/
- sqlite3_mutex *mutex;
+ tdsqlite3_mutex *mutex;
/*
** Head and tail of a linked list of all outstanding allocations
@@ -24477,8 +28050,8 @@ static struct {
char zTitle[100]; /* The title text */
/*
- ** sqlite3MallocDisallow() increments the following counter.
- ** sqlite3MallocAllow() decrements it.
+ ** tdsqlite3MallocDisallow() increments the following counter.
+ ** tdsqlite3MallocAllow() decrements it.
*/
int disallow; /* Do not allow memory allocation */
@@ -24521,7 +28094,7 @@ static void adjustStats(int iSize, int increment){
** This routine checks the guards at either end of the allocation and
** if they are incorrect it asserts.
*/
-static struct MemBlockHdr *sqlite3MemsysGetHeader(void *pAllocation){
+static struct MemBlockHdr *tdsqlite3MemsysGetHeader(void *pAllocation){
struct MemBlockHdr *p;
int *pInt;
u8 *pU8;
@@ -24545,25 +28118,25 @@ static struct MemBlockHdr *sqlite3MemsysGetHeader(void *pAllocation){
/*
** Return the number of bytes currently allocated at address p.
*/
-static int sqlite3MemSize(void *p){
+static int tdsqlite3MemSize(void *p){
struct MemBlockHdr *pHdr;
if( !p ){
return 0;
}
- pHdr = sqlite3MemsysGetHeader(p);
+ pHdr = tdsqlite3MemsysGetHeader(p);
return (int)pHdr->iSize;
}
/*
** Initialize the memory allocation subsystem.
*/
-static int sqlite3MemInit(void *NotUsed){
+static int tdsqlite3MemInit(void *NotUsed){
UNUSED_PARAMETER(NotUsed);
assert( (sizeof(struct MemBlockHdr)&7) == 0 );
- if( !sqlite3GlobalConfig.bMemstat ){
+ if( !tdsqlite3GlobalConfig.bMemstat ){
/* If memory status is enabled, then the malloc.c wrapper will already
** hold the STATIC_MEM mutex when the routines here are invoked. */
- mem.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
+ mem.mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
}
return SQLITE_OK;
}
@@ -24571,7 +28144,7 @@ static int sqlite3MemInit(void *NotUsed){
/*
** Deinitialize the memory allocation subsystem.
*/
-static void sqlite3MemShutdown(void *NotUsed){
+static void tdsqlite3MemShutdown(void *NotUsed){
UNUSED_PARAMETER(NotUsed);
mem.mutex = 0;
}
@@ -24579,7 +28152,7 @@ static void sqlite3MemShutdown(void *NotUsed){
/*
** Round up a request size to the next valid allocation size.
*/
-static int sqlite3MemRoundup(int n){
+static int tdsqlite3MemRoundup(int n){
return ROUND8(n);
}
@@ -24611,7 +28184,7 @@ static void randomFill(char *pBuf, int nByte){
/*
** Allocate nByte bytes of memory.
*/
-static void *sqlite3MemMalloc(int nByte){
+static void *tdsqlite3MemMalloc(int nByte){
struct MemBlockHdr *pHdr;
void **pBt;
char *z;
@@ -24619,7 +28192,7 @@ static void *sqlite3MemMalloc(int nByte){
void *p = 0;
int totalSize;
int nReserve;
- sqlite3_mutex_enter(mem.mutex);
+ tdsqlite3_mutex_enter(mem.mutex);
assert( mem.disallow==0 );
nReserve = ROUND8(nByte);
totalSize = nReserve + sizeof(*pHdr) + sizeof(int) +
@@ -24663,23 +28236,23 @@ static void *sqlite3MemMalloc(int nByte){
memset(((char*)pInt)+nByte, 0x65, nReserve-nByte);
p = (void*)pInt;
}
- sqlite3_mutex_leave(mem.mutex);
+ tdsqlite3_mutex_leave(mem.mutex);
return p;
}
/*
** Free memory.
*/
-static void sqlite3MemFree(void *pPrior){
+static void tdsqlite3MemFree(void *pPrior){
struct MemBlockHdr *pHdr;
void **pBt;
char *z;
- assert( sqlite3GlobalConfig.bMemstat || sqlite3GlobalConfig.bCoreMutex==0
+ assert( tdsqlite3GlobalConfig.bMemstat || tdsqlite3GlobalConfig.bCoreMutex==0
|| mem.mutex!=0 );
- pHdr = sqlite3MemsysGetHeader(pPrior);
+ pHdr = tdsqlite3MemsysGetHeader(pPrior);
pBt = (void**)pHdr;
pBt -= pHdr->nBacktraceSlots;
- sqlite3_mutex_enter(mem.mutex);
+ tdsqlite3_mutex_enter(mem.mutex);
if( pHdr->pPrev ){
assert( pHdr->pPrev->pNext==pHdr );
pHdr->pPrev->pNext = pHdr->pNext;
@@ -24700,7 +28273,7 @@ static void sqlite3MemFree(void *pPrior){
randomFill(z, sizeof(void*)*pHdr->nBacktraceSlots + sizeof(*pHdr) +
(int)pHdr->iSize + sizeof(int) + pHdr->nTitle);
free(z);
- sqlite3_mutex_leave(mem.mutex);
+ tdsqlite3_mutex_leave(mem.mutex);
}
/*
@@ -24712,48 +28285,48 @@ static void sqlite3MemFree(void *pPrior){
** much more likely to break and we are much more liking to find
** the error.
*/
-static void *sqlite3MemRealloc(void *pPrior, int nByte){
+static void *tdsqlite3MemRealloc(void *pPrior, int nByte){
struct MemBlockHdr *pOldHdr;
void *pNew;
assert( mem.disallow==0 );
assert( (nByte & 7)==0 ); /* EV: R-46199-30249 */
- pOldHdr = sqlite3MemsysGetHeader(pPrior);
- pNew = sqlite3MemMalloc(nByte);
+ pOldHdr = tdsqlite3MemsysGetHeader(pPrior);
+ pNew = tdsqlite3MemMalloc(nByte);
if( pNew ){
memcpy(pNew, pPrior, (int)(nByte<pOldHdr->iSize ? nByte : pOldHdr->iSize));
if( nByte>pOldHdr->iSize ){
randomFill(&((char*)pNew)[pOldHdr->iSize], nByte - (int)pOldHdr->iSize);
}
- sqlite3MemFree(pPrior);
+ tdsqlite3MemFree(pPrior);
}
return pNew;
}
/*
** Populate the low-level memory allocation function pointers in
-** sqlite3GlobalConfig.m with pointers to the routines in this file.
-*/
-SQLITE_PRIVATE void sqlite3MemSetDefault(void){
- static const sqlite3_mem_methods defaultMethods = {
- sqlite3MemMalloc,
- sqlite3MemFree,
- sqlite3MemRealloc,
- sqlite3MemSize,
- sqlite3MemRoundup,
- sqlite3MemInit,
- sqlite3MemShutdown,
+** tdsqlite3GlobalConfig.m with pointers to the routines in this file.
+*/
+SQLITE_PRIVATE void tdsqlite3MemSetDefault(void){
+ static const tdsqlite3_mem_methods defaultMethods = {
+ tdsqlite3MemMalloc,
+ tdsqlite3MemFree,
+ tdsqlite3MemRealloc,
+ tdsqlite3MemSize,
+ tdsqlite3MemRoundup,
+ tdsqlite3MemInit,
+ tdsqlite3MemShutdown,
0
};
- sqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods);
+ tdsqlite3_config(SQLITE_CONFIG_MALLOC, &defaultMethods);
}
/*
** Set the "type" of an allocation.
*/
-SQLITE_PRIVATE void sqlite3MemdebugSetType(void *p, u8 eType){
- if( p && sqlite3GlobalConfig.m.xMalloc==sqlite3MemMalloc ){
+SQLITE_PRIVATE void tdsqlite3MemdebugSetType(void *p, u8 eType){
+ if( p && tdsqlite3GlobalConfig.m.xMalloc==tdsqlite3MemMalloc ){
struct MemBlockHdr *pHdr;
- pHdr = sqlite3MemsysGetHeader(p);
+ pHdr = tdsqlite3MemsysGetHeader(p);
assert( pHdr->iForeGuard==FOREGUARD );
pHdr->eType = eType;
}
@@ -24766,13 +28339,13 @@ SQLITE_PRIVATE void sqlite3MemdebugSetType(void *p, u8 eType){
** This routine is designed for use within an assert() statement, to
** verify the type of an allocation. For example:
**
-** assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
+** assert( tdsqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
*/
-SQLITE_PRIVATE int sqlite3MemdebugHasType(void *p, u8 eType){
+SQLITE_PRIVATE int tdsqlite3MemdebugHasType(void *p, u8 eType){
int rc = 1;
- if( p && sqlite3GlobalConfig.m.xMalloc==sqlite3MemMalloc ){
+ if( p && tdsqlite3GlobalConfig.m.xMalloc==tdsqlite3MemMalloc ){
struct MemBlockHdr *pHdr;
- pHdr = sqlite3MemsysGetHeader(p);
+ pHdr = tdsqlite3MemsysGetHeader(p);
assert( pHdr->iForeGuard==FOREGUARD ); /* Allocation is valid */
if( (pHdr->eType&eType)==0 ){
rc = 0;
@@ -24788,13 +28361,13 @@ SQLITE_PRIVATE int sqlite3MemdebugHasType(void *p, u8 eType){
** This routine is designed for use within an assert() statement, to
** verify the type of an allocation. For example:
**
-** assert( sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) );
+** assert( tdsqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) );
*/
-SQLITE_PRIVATE int sqlite3MemdebugNoType(void *p, u8 eType){
+SQLITE_PRIVATE int tdsqlite3MemdebugNoType(void *p, u8 eType){
int rc = 1;
- if( p && sqlite3GlobalConfig.m.xMalloc==sqlite3MemMalloc ){
+ if( p && tdsqlite3GlobalConfig.m.xMalloc==tdsqlite3MemMalloc ){
struct MemBlockHdr *pHdr;
- pHdr = sqlite3MemsysGetHeader(p);
+ pHdr = tdsqlite3MemsysGetHeader(p);
assert( pHdr->iForeGuard==FOREGUARD ); /* Allocation is valid */
if( (pHdr->eType&eType)!=0 ){
rc = 0;
@@ -24808,31 +28381,31 @@ SQLITE_PRIVATE int sqlite3MemdebugNoType(void *p, u8 eType){
** A value of zero turns off backtracing. The number is always rounded
** up to a multiple of 2.
*/
-SQLITE_PRIVATE void sqlite3MemdebugBacktrace(int depth){
+SQLITE_PRIVATE void tdsqlite3MemdebugBacktrace(int depth){
if( depth<0 ){ depth = 0; }
if( depth>20 ){ depth = 20; }
depth = (depth+1)&0xfe;
mem.nBacktrace = depth;
}
-SQLITE_PRIVATE void sqlite3MemdebugBacktraceCallback(void (*xBacktrace)(int, int, void **)){
+SQLITE_PRIVATE void tdsqlite3MemdebugBacktraceCallback(void (*xBacktrace)(int, int, void **)){
mem.xBacktrace = xBacktrace;
}
/*
** Set the title string for subsequent allocations.
*/
-SQLITE_PRIVATE void sqlite3MemdebugSettitle(const char *zTitle){
- unsigned int n = sqlite3Strlen30(zTitle) + 1;
- sqlite3_mutex_enter(mem.mutex);
+SQLITE_PRIVATE void tdsqlite3MemdebugSettitle(const char *zTitle){
+ unsigned int n = tdsqlite3Strlen30(zTitle) + 1;
+ tdsqlite3_mutex_enter(mem.mutex);
if( n>=sizeof(mem.zTitle) ) n = sizeof(mem.zTitle)-1;
memcpy(mem.zTitle, zTitle, n);
mem.zTitle[n] = 0;
mem.nTitle = ROUND8(n);
- sqlite3_mutex_leave(mem.mutex);
+ tdsqlite3_mutex_leave(mem.mutex);
}
-SQLITE_PRIVATE void sqlite3MemdebugSync(){
+SQLITE_PRIVATE void tdsqlite3MemdebugSync(){
struct MemBlockHdr *pHdr;
for(pHdr=mem.pFirst; pHdr; pHdr=pHdr->pNext){
void **pBt = (void**)pHdr;
@@ -24845,7 +28418,7 @@ SQLITE_PRIVATE void sqlite3MemdebugSync(){
** Open the file indicated and write a log of all unfreed memory
** allocations into that log.
*/
-SQLITE_PRIVATE void sqlite3MemdebugDump(const char *zFilename){
+SQLITE_PRIVATE void tdsqlite3MemdebugDump(const char *zFilename){
FILE *out;
struct MemBlockHdr *pHdr;
void **pBt;
@@ -24885,9 +28458,9 @@ SQLITE_PRIVATE void sqlite3MemdebugDump(const char *zFilename){
}
/*
-** Return the number of times sqlite3MemMalloc() has been called.
+** Return the number of times tdsqlite3MemMalloc() has been called.
*/
-SQLITE_PRIVATE int sqlite3MemdebugMallocCount(){
+SQLITE_PRIVATE int tdsqlite3MemdebugMallocCount(){
int i;
int nTotal = 0;
for(i=0; i<NCSIZE; i++){
@@ -24917,9 +28490,9 @@ SQLITE_PRIVATE int sqlite3MemdebugMallocCount(){
**
** This version of the memory allocation subsystem omits all
** use of malloc(). The SQLite user supplies a block of memory
-** before calling sqlite3_initialize() from which allocations
+** before calling tdsqlite3_initialize() from which allocations
** are made and returned by the xMalloc() and xRealloc()
-** implementations. Once sqlite3_initialize() has been called,
+** implementations. Once tdsqlite3_initialize() has been called,
** the amount of memory available to SQLite is fixed and cannot
** be changed.
**
@@ -24933,7 +28506,7 @@ SQLITE_PRIVATE int sqlite3MemdebugMallocCount(){
** SQLITE_ENABLE_MEMSYS3 is defined. Defining this symbol does not
** mean that the library will use a memory-pool by default, just that
** it is available. The mempool allocator is activated by calling
-** sqlite3_config().
+** tdsqlite3_config().
*/
#ifdef SQLITE_ENABLE_MEMSYS3
@@ -25016,7 +28589,7 @@ static SQLITE_WSD struct Mem3Global {
/*
** Mutex to control access to the memory allocation subsystem.
*/
- sqlite3_mutex *mutex;
+ tdsqlite3_mutex *mutex;
/*
** The minimum amount of free space that we have seen.
@@ -25050,7 +28623,7 @@ static SQLITE_WSD struct Mem3Global {
static void memsys3UnlinkFromList(u32 i, u32 *pRoot){
u32 next = mem3.aPool[i].u.list.next;
u32 prev = mem3.aPool[i].u.list.prev;
- assert( sqlite3_mutex_held(mem3.mutex) );
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
if( prev==0 ){
*pRoot = next;
}else{
@@ -25069,7 +28642,7 @@ static void memsys3UnlinkFromList(u32 i, u32 *pRoot){
*/
static void memsys3Unlink(u32 i){
u32 size, hash;
- assert( sqlite3_mutex_held(mem3.mutex) );
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
assert( (mem3.aPool[i-1].u.hdr.size4x & 1)==0 );
assert( i>=1 );
size = mem3.aPool[i-1].u.hdr.size4x/4;
@@ -25088,7 +28661,7 @@ static void memsys3Unlink(u32 i){
** at *pRoot.
*/
static void memsys3LinkIntoList(u32 i, u32 *pRoot){
- assert( sqlite3_mutex_held(mem3.mutex) );
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
mem3.aPool[i].u.list.next = *pRoot;
mem3.aPool[i].u.list.prev = 0;
if( *pRoot ){
@@ -25103,7 +28676,7 @@ static void memsys3LinkIntoList(u32 i, u32 *pRoot){
*/
static void memsys3Link(u32 i){
u32 size, hash;
- assert( sqlite3_mutex_held(mem3.mutex) );
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
assert( i>=1 );
assert( (mem3.aPool[i-1].u.hdr.size4x & 1)==0 );
size = mem3.aPool[i-1].u.hdr.size4x/4;
@@ -25120,16 +28693,16 @@ static void memsys3Link(u32 i){
/*
** If the STATIC_MEM mutex is not already held, obtain it now. The mutex
** will already be held (obtained by code in malloc.c) if
-** sqlite3GlobalConfig.bMemStat is true.
+** tdsqlite3GlobalConfig.bMemStat is true.
*/
static void memsys3Enter(void){
- if( sqlite3GlobalConfig.bMemstat==0 && mem3.mutex==0 ){
- mem3.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
+ if( tdsqlite3GlobalConfig.bMemstat==0 && mem3.mutex==0 ){
+ mem3.mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
}
- sqlite3_mutex_enter(mem3.mutex);
+ tdsqlite3_mutex_enter(mem3.mutex);
}
static void memsys3Leave(void){
- sqlite3_mutex_leave(mem3.mutex);
+ tdsqlite3_mutex_leave(mem3.mutex);
}
/*
@@ -25138,10 +28711,10 @@ static void memsys3Leave(void){
static void memsys3OutOfMemory(int nByte){
if( !mem3.alarmBusy ){
mem3.alarmBusy = 1;
- assert( sqlite3_mutex_held(mem3.mutex) );
- sqlite3_mutex_leave(mem3.mutex);
- sqlite3_release_memory(nByte);
- sqlite3_mutex_enter(mem3.mutex);
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
+ tdsqlite3_mutex_leave(mem3.mutex);
+ tdsqlite3_release_memory(nByte);
+ tdsqlite3_mutex_enter(mem3.mutex);
mem3.alarmBusy = 0;
}
}
@@ -25154,7 +28727,7 @@ static void memsys3OutOfMemory(int nByte){
*/
static void *memsys3Checkout(u32 i, u32 nBlock){
u32 x;
- assert( sqlite3_mutex_held(mem3.mutex) );
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
assert( i>=1 );
assert( mem3.aPool[i-1].u.hdr.size4x/4==nBlock );
assert( mem3.aPool[i+nBlock-1].u.hdr.prevSize==nBlock );
@@ -25171,7 +28744,7 @@ static void *memsys3Checkout(u32 i, u32 nBlock){
** is not large enough, return 0.
*/
static void *memsys3FromMaster(u32 nBlock){
- assert( sqlite3_mutex_held(mem3.mutex) );
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
assert( mem3.szMaster>=nBlock );
if( nBlock>=mem3.szMaster-1 ){
/* Use the entire master */
@@ -25218,7 +28791,7 @@ static void *memsys3FromMaster(u32 nBlock){
static void memsys3Merge(u32 *pRoot){
u32 iNext, prev, size, i, x;
- assert( sqlite3_mutex_held(mem3.mutex) );
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
for(i=*pRoot; i>0; i=iNext){
iNext = mem3.aPool[i].u.list.next;
size = mem3.aPool[i-1].u.hdr.size4x;
@@ -25259,7 +28832,7 @@ static void *memsys3MallocUnsafe(int nByte){
u32 nBlock;
u32 toFree;
- assert( sqlite3_mutex_held(mem3.mutex) );
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
assert( sizeof(Mem3Block)==8 );
if( nByte<=12 ){
nBlock = 2;
@@ -25340,7 +28913,7 @@ static void memsys3FreeUnsafe(void *pOld){
Mem3Block *p = (Mem3Block*)pOld;
int i;
u32 size, x;
- assert( sqlite3_mutex_held(mem3.mutex) );
+ assert( tdsqlite3_mutex_held(mem3.mutex) );
assert( p>mem3.aPool && p<&mem3.aPool[mem3.nPool] );
i = p - mem3.aPool;
assert( (mem3.aPool[i-1].u.hdr.size4x&1)==1 );
@@ -25400,7 +28973,7 @@ static int memsys3Roundup(int n){
** Allocate nBytes of memory.
*/
static void *memsys3Malloc(int nBytes){
- sqlite3_int64 *p;
+ tdsqlite3_int64 *p;
assert( nBytes>0 ); /* malloc.c filters out 0 byte requests */
memsys3Enter();
p = memsys3MallocUnsafe(nBytes);
@@ -25425,10 +28998,10 @@ static void *memsys3Realloc(void *pPrior, int nBytes){
int nOld;
void *p;
if( pPrior==0 ){
- return sqlite3_malloc(nBytes);
+ return tdsqlite3_malloc(nBytes);
}
if( nBytes<=0 ){
- sqlite3_free(pPrior);
+ tdsqlite3_free(pPrior);
return 0;
}
nOld = memsys3Size(pPrior);
@@ -25454,14 +29027,14 @@ static void *memsys3Realloc(void *pPrior, int nBytes){
*/
static int memsys3Init(void *NotUsed){
UNUSED_PARAMETER(NotUsed);
- if( !sqlite3GlobalConfig.pHeap ){
+ if( !tdsqlite3GlobalConfig.pHeap ){
return SQLITE_ERROR;
}
/* Store a pointer to the memory block in global structure mem3. */
assert( sizeof(Mem3Block)==8 );
- mem3.aPool = (Mem3Block *)sqlite3GlobalConfig.pHeap;
- mem3.nPool = (sqlite3GlobalConfig.nHeap / sizeof(Mem3Block)) - 2;
+ mem3.aPool = (Mem3Block *)tdsqlite3GlobalConfig.pHeap;
+ mem3.nPool = (tdsqlite3GlobalConfig.nHeap / sizeof(Mem3Block)) - 2;
/* Initialize the master block. */
mem3.szMaster = mem3.nPool;
@@ -25489,7 +29062,7 @@ static void memsys3Shutdown(void *NotUsed){
** Open the file indicated and write a log of all unfreed memory
** allocations into that log.
*/
-SQLITE_PRIVATE void sqlite3Memsys3Dump(const char *zFilename){
+SQLITE_PRIVATE void tdsqlite3Memsys3Dump(const char *zFilename){
#ifdef SQLITE_DEBUG
FILE *out;
u32 i, j;
@@ -25551,7 +29124,7 @@ SQLITE_PRIVATE void sqlite3Memsys3Dump(const char *zFilename){
fprintf(out, "master=%d\n", mem3.iMaster);
fprintf(out, "nowUsed=%d\n", mem3.nPool*8 - mem3.szMaster*8);
fprintf(out, "mxUsed=%d\n", mem3.nPool*8 - mem3.mnMaster*8);
- sqlite3_mutex_leave(mem3.mutex);
+ tdsqlite3_mutex_leave(mem3.mutex);
if( out==stdout ){
fflush(stdout);
}else{
@@ -25567,14 +29140,14 @@ SQLITE_PRIVATE void sqlite3Memsys3Dump(const char *zFilename){
** linkage.
**
** Populate the low-level memory allocation function pointers in
-** sqlite3GlobalConfig.m with pointers to the routines in this file. The
+** tdsqlite3GlobalConfig.m with pointers to the routines in this file. The
** arguments specify the block of memory to manage.
**
-** This routine is only called by sqlite3_config(), and therefore
+** This routine is only called by tdsqlite3_config(), and therefore
** is not required to be threadsafe (it is not).
*/
-SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys3(void){
- static const sqlite3_mem_methods mempoolMethods = {
+SQLITE_PRIVATE const tdsqlite3_mem_methods *tdsqlite3MemGetMemsys3(void){
+ static const tdsqlite3_mem_methods mempoolMethods = {
memsys3Malloc,
memsys3Free,
memsys3Realloc,
@@ -25607,9 +29180,9 @@ SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys3(void){
**
** This version of the memory allocation subsystem omits all
** use of malloc(). The application gives SQLite a block of memory
-** before calling sqlite3_initialize() from which allocations
+** before calling tdsqlite3_initialize() from which allocations
** are made and returned by the xMalloc() and xRealloc()
-** implementations. Once sqlite3_initialize() has been called,
+** implementations. Once tdsqlite3_initialize() has been called,
** the amount of memory available to SQLite is fixed and cannot
** be changed.
**
@@ -25638,7 +29211,7 @@ SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys3(void){
**
** N >= M*(1 + log2(n)/2) - n + 1
**
-** The sqlite3_status() logic tracks the maximum values of n and M so
+** The tdsqlite3_status() logic tracks the maximum values of n and M so
** that an application can, at any time, verify this constraint.
*/
/* #include "sqliteInt.h" */
@@ -25693,7 +29266,7 @@ static SQLITE_WSD struct Mem5Global {
/*
** Mutex to control access to the memory allocation subsystem.
*/
- sqlite3_mutex *mutex;
+ tdsqlite3_mutex *mutex;
#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
/*
@@ -25763,7 +29336,7 @@ static void memsys5Unlink(int i, int iLogsize){
*/
static void memsys5Link(int i, int iLogsize){
int x;
- assert( sqlite3_mutex_held(mem5.mutex) );
+ assert( tdsqlite3_mutex_held(mem5.mutex) );
assert( i>=0 && i<mem5.nBlock );
assert( iLogsize>=0 && iLogsize<=LOGMAX );
assert( (mem5.aCtrl[i] & CTRL_LOGSIZE)==iLogsize );
@@ -25781,10 +29354,10 @@ static void memsys5Link(int i, int iLogsize){
** Obtain or release the mutex needed to access global data structures.
*/
static void memsys5Enter(void){
- sqlite3_mutex_enter(mem5.mutex);
+ tdsqlite3_mutex_enter(mem5.mutex);
}
static void memsys5Leave(void){
- sqlite3_mutex_leave(mem5.mutex);
+ tdsqlite3_mutex_leave(mem5.mutex);
}
/*
@@ -25840,8 +29413,8 @@ static void *memsys5MallocUnsafe(int nByte){
*/
for(iBin=iLogsize; iBin<=LOGMAX && mem5.aiFreelist[iBin]<0; iBin++){}
if( iBin>LOGMAX ){
- testcase( sqlite3GlobalConfig.xLog!=0 );
- sqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes", nByte);
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ tdsqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes", nByte);
return 0;
}
i = mem5.aiFreelist[iBin];
@@ -25947,7 +29520,7 @@ static void memsys5FreeUnsafe(void *pOld){
** Allocate nBytes of memory.
*/
static void *memsys5Malloc(int nBytes){
- sqlite3_int64 *p = 0;
+ tdsqlite3_int64 *p = 0;
if( nBytes>0 ){
memsys5Enter();
p = memsys5MallocUnsafe(nBytes);
@@ -26057,12 +29630,12 @@ static int memsys5Init(void *NotUsed){
*/
assert( (sizeof(Mem5Link)&(sizeof(Mem5Link)-1))==0 );
- nByte = sqlite3GlobalConfig.nHeap;
- zByte = (u8*)sqlite3GlobalConfig.pHeap;
- assert( zByte!=0 ); /* sqlite3_config() does not allow otherwise */
+ nByte = tdsqlite3GlobalConfig.nHeap;
+ zByte = (u8*)tdsqlite3GlobalConfig.pHeap;
+ assert( zByte!=0 ); /* tdsqlite3_config() does not allow otherwise */
- /* boundaries on sqlite3GlobalConfig.mnReq are enforced in sqlite3_config() */
- nMinLog = memsys5Log(sqlite3GlobalConfig.mnReq);
+ /* boundaries on tdsqlite3GlobalConfig.mnReq are enforced in tdsqlite3_config() */
+ nMinLog = memsys5Log(tdsqlite3GlobalConfig.mnReq);
mem5.szAtom = (1<<nMinLog);
while( (int)sizeof(Mem5Link)>mem5.szAtom ){
mem5.szAtom = mem5.szAtom << 1;
@@ -26088,8 +29661,8 @@ static int memsys5Init(void *NotUsed){
}
/* If a mutex is required for normal operation, allocate one */
- if( sqlite3GlobalConfig.bMemstat==0 ){
- mem5.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
+ if( tdsqlite3GlobalConfig.bMemstat==0 ){
+ mem5.mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
}
return SQLITE_OK;
@@ -26109,7 +29682,7 @@ static void memsys5Shutdown(void *NotUsed){
** Open the file indicated and write a log of all unfreed memory
** allocations into that log.
*/
-SQLITE_PRIVATE void sqlite3Memsys5Dump(const char *zFilename){
+SQLITE_PRIVATE void tdsqlite3Memsys5Dump(const char *zFilename){
FILE *out;
int i, j, n;
int nMinLog;
@@ -26149,11 +29722,11 @@ SQLITE_PRIVATE void sqlite3Memsys5Dump(const char *zFilename){
/*
** This routine is the only routine in this file with external
-** linkage. It returns a pointer to a static sqlite3_mem_methods
+** linkage. It returns a pointer to a static tdsqlite3_mem_methods
** struct populated with the memsys5 methods.
*/
-SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys5(void){
- static const sqlite3_mem_methods memsys5Methods = {
+SQLITE_PRIVATE const tdsqlite3_mem_methods *tdsqlite3MemGetMemsys5(void){
+ static const tdsqlite3_mem_methods memsys5Methods = {
memsys5Malloc,
memsys5Free,
memsys5Realloc,
@@ -26198,24 +29771,215 @@ static SQLITE_WSD int mutexIsInit = 0;
#ifndef SQLITE_MUTEX_OMIT
+
+#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS
+/*
+** This block (enclosed by SQLITE_ENABLE_MULTITHREADED_CHECKS) contains
+** the implementation of a wrapper around the system default mutex
+** implementation (tdsqlite3DefaultMutex()).
+**
+** Most calls are passed directly through to the underlying default
+** mutex implementation. Except, if a mutex is configured by calling
+** tdsqlite3MutexWarnOnContention() on it, then if contention is ever
+** encountered within xMutexEnter() a warning is emitted via tdsqlite3_log().
+**
+** This type of mutex is used as the database handle mutex when testing
+** apps that usually use SQLITE_CONFIG_MULTITHREAD mode.
+*/
+
+/*
+** Type for all mutexes used when SQLITE_ENABLE_MULTITHREADED_CHECKS
+** is defined. Variable CheckMutex.mutex is a pointer to the real mutex
+** allocated by the system mutex implementation. Variable iType is usually set
+** to the type of mutex requested - SQLITE_MUTEX_RECURSIVE, SQLITE_MUTEX_FAST
+** or one of the static mutex identifiers. Or, if this is a recursive mutex
+** that has been configured using tdsqlite3MutexWarnOnContention(), it is
+** set to SQLITE_MUTEX_WARNONCONTENTION.
+*/
+typedef struct CheckMutex CheckMutex;
+struct CheckMutex {
+ int iType;
+ tdsqlite3_mutex *mutex;
+};
+
+#define SQLITE_MUTEX_WARNONCONTENTION (-1)
+
+/*
+** Pointer to real mutex methods object used by the CheckMutex
+** implementation. Set by checkMutexInit().
+*/
+static SQLITE_WSD const tdsqlite3_mutex_methods *pGlobalMutexMethods;
+
+#ifdef SQLITE_DEBUG
+static int checkMutexHeld(tdsqlite3_mutex *p){
+ return pGlobalMutexMethods->xMutexHeld(((CheckMutex*)p)->mutex);
+}
+static int checkMutexNotheld(tdsqlite3_mutex *p){
+ return pGlobalMutexMethods->xMutexNotheld(((CheckMutex*)p)->mutex);
+}
+#endif
+
+/*
+** Initialize and deinitialize the mutex subsystem.
+*/
+static int checkMutexInit(void){
+ pGlobalMutexMethods = tdsqlite3DefaultMutex();
+ return SQLITE_OK;
+}
+static int checkMutexEnd(void){
+ pGlobalMutexMethods = 0;
+ return SQLITE_OK;
+}
+
+/*
+** Allocate a mutex.
+*/
+static tdsqlite3_mutex *checkMutexAlloc(int iType){
+ static CheckMutex staticMutexes[] = {
+ {2, 0}, {3, 0}, {4, 0}, {5, 0},
+ {6, 0}, {7, 0}, {8, 0}, {9, 0},
+ {10, 0}, {11, 0}, {12, 0}, {13, 0}
+ };
+ CheckMutex *p = 0;
+
+ assert( SQLITE_MUTEX_RECURSIVE==1 && SQLITE_MUTEX_FAST==0 );
+ if( iType<2 ){
+ p = tdsqlite3MallocZero(sizeof(CheckMutex));
+ if( p==0 ) return 0;
+ p->iType = iType;
+ }else{
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( iType-2>=ArraySize(staticMutexes) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+ p = &staticMutexes[iType-2];
+ }
+
+ if( p->mutex==0 ){
+ p->mutex = pGlobalMutexMethods->xMutexAlloc(iType);
+ if( p->mutex==0 ){
+ if( iType<2 ){
+ tdsqlite3_free(p);
+ }
+ p = 0;
+ }
+ }
+
+ return (tdsqlite3_mutex*)p;
+}
+
+/*
+** Free a mutex.
+*/
+static void checkMutexFree(tdsqlite3_mutex *p){
+ assert( SQLITE_MUTEX_RECURSIVE<2 );
+ assert( SQLITE_MUTEX_FAST<2 );
+ assert( SQLITE_MUTEX_WARNONCONTENTION<2 );
+
+#if SQLITE_ENABLE_API_ARMOR
+ if( ((CheckMutex*)p)->iType<2 )
+#endif
+ {
+ CheckMutex *pCheck = (CheckMutex*)p;
+ pGlobalMutexMethods->xMutexFree(pCheck->mutex);
+ tdsqlite3_free(pCheck);
+ }
+#ifdef SQLITE_ENABLE_API_ARMOR
+ else{
+ (void)SQLITE_MISUSE_BKPT;
+ }
+#endif
+}
+
+/*
+** Enter the mutex.
+*/
+static void checkMutexEnter(tdsqlite3_mutex *p){
+ CheckMutex *pCheck = (CheckMutex*)p;
+ if( pCheck->iType==SQLITE_MUTEX_WARNONCONTENTION ){
+ if( SQLITE_OK==pGlobalMutexMethods->xMutexTry(pCheck->mutex) ){
+ return;
+ }
+ tdsqlite3_log(SQLITE_MISUSE,
+ "illegal multi-threaded access to database connection"
+ );
+ }
+ pGlobalMutexMethods->xMutexEnter(pCheck->mutex);
+}
+
+/*
+** Enter the mutex (do not block).
+*/
+static int checkMutexTry(tdsqlite3_mutex *p){
+ CheckMutex *pCheck = (CheckMutex*)p;
+ return pGlobalMutexMethods->xMutexTry(pCheck->mutex);
+}
+
+/*
+** Leave the mutex.
+*/
+static void checkMutexLeave(tdsqlite3_mutex *p){
+ CheckMutex *pCheck = (CheckMutex*)p;
+ pGlobalMutexMethods->xMutexLeave(pCheck->mutex);
+}
+
+tdsqlite3_mutex_methods const *multiThreadedCheckMutex(void){
+ static const tdsqlite3_mutex_methods sMutex = {
+ checkMutexInit,
+ checkMutexEnd,
+ checkMutexAlloc,
+ checkMutexFree,
+ checkMutexEnter,
+ checkMutexTry,
+ checkMutexLeave,
+#ifdef SQLITE_DEBUG
+ checkMutexHeld,
+ checkMutexNotheld
+#else
+ 0,
+ 0
+#endif
+ };
+ return &sMutex;
+}
+
+/*
+** Mark the SQLITE_MUTEX_RECURSIVE mutex passed as the only argument as
+** one on which there should be no contention.
+*/
+SQLITE_PRIVATE void tdsqlite3MutexWarnOnContention(tdsqlite3_mutex *p){
+ if( tdsqlite3GlobalConfig.mutex.xMutexAlloc==checkMutexAlloc ){
+ CheckMutex *pCheck = (CheckMutex*)p;
+ assert( pCheck->iType==SQLITE_MUTEX_RECURSIVE );
+ pCheck->iType = SQLITE_MUTEX_WARNONCONTENTION;
+ }
+}
+#endif /* ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS */
+
/*
** Initialize the mutex system.
*/
-SQLITE_PRIVATE int sqlite3MutexInit(void){
+SQLITE_PRIVATE int tdsqlite3MutexInit(void){
int rc = SQLITE_OK;
- if( !sqlite3GlobalConfig.mutex.xMutexAlloc ){
+ if( !tdsqlite3GlobalConfig.mutex.xMutexAlloc ){
/* If the xMutexAlloc method has not been set, then the user did not
- ** install a mutex implementation via sqlite3_config() prior to
- ** sqlite3_initialize() being called. This block copies pointers to
- ** the default implementation into the sqlite3GlobalConfig structure.
+ ** install a mutex implementation via tdsqlite3_config() prior to
+ ** tdsqlite3_initialize() being called. This block copies pointers to
+ ** the default implementation into the tdsqlite3GlobalConfig structure.
*/
- sqlite3_mutex_methods const *pFrom;
- sqlite3_mutex_methods *pTo = &sqlite3GlobalConfig.mutex;
+ tdsqlite3_mutex_methods const *pFrom;
+ tdsqlite3_mutex_methods *pTo = &tdsqlite3GlobalConfig.mutex;
- if( sqlite3GlobalConfig.bCoreMutex ){
- pFrom = sqlite3DefaultMutex();
+ if( tdsqlite3GlobalConfig.bCoreMutex ){
+#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS
+ pFrom = multiThreadedCheckMutex();
+#else
+ pFrom = tdsqlite3DefaultMutex();
+#endif
}else{
- pFrom = sqlite3NoopMutex();
+ pFrom = tdsqlite3NoopMutex();
}
pTo->xMutexInit = pFrom->xMutexInit;
pTo->xMutexEnd = pFrom->xMutexEnd;
@@ -26225,11 +29989,11 @@ SQLITE_PRIVATE int sqlite3MutexInit(void){
pTo->xMutexLeave = pFrom->xMutexLeave;
pTo->xMutexHeld = pFrom->xMutexHeld;
pTo->xMutexNotheld = pFrom->xMutexNotheld;
- sqlite3MemoryBarrier();
+ tdsqlite3MemoryBarrier();
pTo->xMutexAlloc = pFrom->xMutexAlloc;
}
- assert( sqlite3GlobalConfig.mutex.xMutexInit );
- rc = sqlite3GlobalConfig.mutex.xMutexInit();
+ assert( tdsqlite3GlobalConfig.mutex.xMutexInit );
+ rc = tdsqlite3GlobalConfig.mutex.xMutexInit();
#ifdef SQLITE_DEBUG
GLOBAL(int, mutexIsInit) = 1;
@@ -26240,12 +30004,12 @@ SQLITE_PRIVATE int sqlite3MutexInit(void){
/*
** Shutdown the mutex system. This call frees resources allocated by
-** sqlite3MutexInit().
+** tdsqlite3MutexInit().
*/
-SQLITE_PRIVATE int sqlite3MutexEnd(void){
+SQLITE_PRIVATE int tdsqlite3MutexEnd(void){
int rc = SQLITE_OK;
- if( sqlite3GlobalConfig.mutex.xMutexEnd ){
- rc = sqlite3GlobalConfig.mutex.xMutexEnd();
+ if( tdsqlite3GlobalConfig.mutex.xMutexEnd ){
+ rc = tdsqlite3GlobalConfig.mutex.xMutexEnd();
}
#ifdef SQLITE_DEBUG
@@ -26258,31 +30022,31 @@ SQLITE_PRIVATE int sqlite3MutexEnd(void){
/*
** Retrieve a pointer to a static mutex or allocate a new dynamic one.
*/
-SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int id){
+SQLITE_API tdsqlite3_mutex *tdsqlite3_mutex_alloc(int id){
#ifndef SQLITE_OMIT_AUTOINIT
- if( id<=SQLITE_MUTEX_RECURSIVE && sqlite3_initialize() ) return 0;
- if( id>SQLITE_MUTEX_RECURSIVE && sqlite3MutexInit() ) return 0;
+ if( id<=SQLITE_MUTEX_RECURSIVE && tdsqlite3_initialize() ) return 0;
+ if( id>SQLITE_MUTEX_RECURSIVE && tdsqlite3MutexInit() ) return 0;
#endif
- assert( sqlite3GlobalConfig.mutex.xMutexAlloc );
- return sqlite3GlobalConfig.mutex.xMutexAlloc(id);
+ assert( tdsqlite3GlobalConfig.mutex.xMutexAlloc );
+ return tdsqlite3GlobalConfig.mutex.xMutexAlloc(id);
}
-SQLITE_PRIVATE sqlite3_mutex *sqlite3MutexAlloc(int id){
- if( !sqlite3GlobalConfig.bCoreMutex ){
+SQLITE_PRIVATE tdsqlite3_mutex *tdsqlite3MutexAlloc(int id){
+ if( !tdsqlite3GlobalConfig.bCoreMutex ){
return 0;
}
assert( GLOBAL(int, mutexIsInit) );
- assert( sqlite3GlobalConfig.mutex.xMutexAlloc );
- return sqlite3GlobalConfig.mutex.xMutexAlloc(id);
+ assert( tdsqlite3GlobalConfig.mutex.xMutexAlloc );
+ return tdsqlite3GlobalConfig.mutex.xMutexAlloc(id);
}
/*
** Free a dynamic mutex.
*/
-SQLITE_API void sqlite3_mutex_free(sqlite3_mutex *p){
+SQLITE_API void tdsqlite3_mutex_free(tdsqlite3_mutex *p){
if( p ){
- assert( sqlite3GlobalConfig.mutex.xMutexFree );
- sqlite3GlobalConfig.mutex.xMutexFree(p);
+ assert( tdsqlite3GlobalConfig.mutex.xMutexFree );
+ tdsqlite3GlobalConfig.mutex.xMutexFree(p);
}
}
@@ -26290,10 +30054,10 @@ SQLITE_API void sqlite3_mutex_free(sqlite3_mutex *p){
** Obtain the mutex p. If some other thread already has the mutex, block
** until it can be obtained.
*/
-SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex *p){
+SQLITE_API void tdsqlite3_mutex_enter(tdsqlite3_mutex *p){
if( p ){
- assert( sqlite3GlobalConfig.mutex.xMutexEnter );
- sqlite3GlobalConfig.mutex.xMutexEnter(p);
+ assert( tdsqlite3GlobalConfig.mutex.xMutexEnter );
+ tdsqlite3GlobalConfig.mutex.xMutexEnter(p);
}
}
@@ -26301,40 +30065,40 @@ SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex *p){
** Obtain the mutex p. If successful, return SQLITE_OK. Otherwise, if another
** thread holds the mutex and it cannot be obtained, return SQLITE_BUSY.
*/
-SQLITE_API int sqlite3_mutex_try(sqlite3_mutex *p){
+SQLITE_API int tdsqlite3_mutex_try(tdsqlite3_mutex *p){
int rc = SQLITE_OK;
if( p ){
- assert( sqlite3GlobalConfig.mutex.xMutexTry );
- return sqlite3GlobalConfig.mutex.xMutexTry(p);
+ assert( tdsqlite3GlobalConfig.mutex.xMutexTry );
+ return tdsqlite3GlobalConfig.mutex.xMutexTry(p);
}
return rc;
}
/*
-** The sqlite3_mutex_leave() routine exits a mutex that was previously
+** The tdsqlite3_mutex_leave() routine exits a mutex that was previously
** entered by the same thread. The behavior is undefined if the mutex
** is not currently entered. If a NULL pointer is passed as an argument
** this function is a no-op.
*/
-SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex *p){
+SQLITE_API void tdsqlite3_mutex_leave(tdsqlite3_mutex *p){
if( p ){
- assert( sqlite3GlobalConfig.mutex.xMutexLeave );
- sqlite3GlobalConfig.mutex.xMutexLeave(p);
+ assert( tdsqlite3GlobalConfig.mutex.xMutexLeave );
+ tdsqlite3GlobalConfig.mutex.xMutexLeave(p);
}
}
#ifndef NDEBUG
/*
-** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are
+** The tdsqlite3_mutex_held() and tdsqlite3_mutex_notheld() routine are
** intended for use inside assert() statements.
*/
-SQLITE_API int sqlite3_mutex_held(sqlite3_mutex *p){
- assert( p==0 || sqlite3GlobalConfig.mutex.xMutexHeld );
- return p==0 || sqlite3GlobalConfig.mutex.xMutexHeld(p);
+SQLITE_API int tdsqlite3_mutex_held(tdsqlite3_mutex *p){
+ assert( p==0 || tdsqlite3GlobalConfig.mutex.xMutexHeld );
+ return p==0 || tdsqlite3GlobalConfig.mutex.xMutexHeld(p);
}
-SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){
- assert( p==0 || sqlite3GlobalConfig.mutex.xMutexNotheld );
- return p==0 || sqlite3GlobalConfig.mutex.xMutexNotheld(p);
+SQLITE_API int tdsqlite3_mutex_notheld(tdsqlite3_mutex *p){
+ assert( p==0 || tdsqlite3GlobalConfig.mutex.xMutexNotheld );
+ return p==0 || tdsqlite3GlobalConfig.mutex.xMutexNotheld(p);
}
#endif
@@ -26361,7 +30125,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){
** here are place-holders. Applications can substitute working
** mutex routines at start-time using the
**
-** sqlite3_config(SQLITE_CONFIG_MUTEX,...)
+** tdsqlite3_config(SQLITE_CONFIG_MUTEX,...)
**
** interface.
**
@@ -26381,20 +30145,20 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){
*/
static int noopMutexInit(void){ return SQLITE_OK; }
static int noopMutexEnd(void){ return SQLITE_OK; }
-static sqlite3_mutex *noopMutexAlloc(int id){
+static tdsqlite3_mutex *noopMutexAlloc(int id){
UNUSED_PARAMETER(id);
- return (sqlite3_mutex*)8;
+ return (tdsqlite3_mutex*)8;
}
-static void noopMutexFree(sqlite3_mutex *p){ UNUSED_PARAMETER(p); return; }
-static void noopMutexEnter(sqlite3_mutex *p){ UNUSED_PARAMETER(p); return; }
-static int noopMutexTry(sqlite3_mutex *p){
+static void noopMutexFree(tdsqlite3_mutex *p){ UNUSED_PARAMETER(p); return; }
+static void noopMutexEnter(tdsqlite3_mutex *p){ UNUSED_PARAMETER(p); return; }
+static int noopMutexTry(tdsqlite3_mutex *p){
UNUSED_PARAMETER(p);
return SQLITE_OK;
}
-static void noopMutexLeave(sqlite3_mutex *p){ UNUSED_PARAMETER(p); return; }
+static void noopMutexLeave(tdsqlite3_mutex *p){ UNUSED_PARAMETER(p); return; }
-SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3NoopMutex(void){
- static const sqlite3_mutex_methods sMutex = {
+SQLITE_PRIVATE tdsqlite3_mutex_methods const *tdsqlite3NoopMutex(void){
+ static const tdsqlite3_mutex_methods sMutex = {
noopMutexInit,
noopMutexEnd,
noopMutexAlloc,
@@ -26421,21 +30185,21 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3NoopMutex(void){
/*
** The mutex object
*/
-typedef struct sqlite3_debug_mutex {
+typedef struct tdsqlite3_debug_mutex {
int id; /* The mutex type */
int cnt; /* Number of entries without a matching leave */
-} sqlite3_debug_mutex;
+} tdsqlite3_debug_mutex;
/*
-** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are
+** The tdsqlite3_mutex_held() and tdsqlite3_mutex_notheld() routine are
** intended for use inside assert() statements.
*/
-static int debugMutexHeld(sqlite3_mutex *pX){
- sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX;
+static int debugMutexHeld(tdsqlite3_mutex *pX){
+ tdsqlite3_debug_mutex *p = (tdsqlite3_debug_mutex*)pX;
return p==0 || p->cnt>0;
}
-static int debugMutexNotheld(sqlite3_mutex *pX){
- sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX;
+static int debugMutexNotheld(tdsqlite3_mutex *pX){
+ tdsqlite3_debug_mutex *p = (tdsqlite3_debug_mutex*)pX;
return p==0 || p->cnt==0;
}
@@ -26446,17 +30210,17 @@ static int debugMutexInit(void){ return SQLITE_OK; }
static int debugMutexEnd(void){ return SQLITE_OK; }
/*
-** The sqlite3_mutex_alloc() routine allocates a new
+** The tdsqlite3_mutex_alloc() routine allocates a new
** mutex and returns a pointer to it. If it returns NULL
** that means that a mutex could not be allocated.
*/
-static sqlite3_mutex *debugMutexAlloc(int id){
- static sqlite3_debug_mutex aStatic[SQLITE_MUTEX_STATIC_VFS3 - 1];
- sqlite3_debug_mutex *pNew = 0;
+static tdsqlite3_mutex *debugMutexAlloc(int id){
+ static tdsqlite3_debug_mutex aStatic[SQLITE_MUTEX_STATIC_VFS3 - 1];
+ tdsqlite3_debug_mutex *pNew = 0;
switch( id ){
case SQLITE_MUTEX_FAST:
case SQLITE_MUTEX_RECURSIVE: {
- pNew = sqlite3Malloc(sizeof(*pNew));
+ pNew = tdsqlite3Malloc(sizeof(*pNew));
if( pNew ){
pNew->id = id;
pNew->cnt = 0;
@@ -26475,17 +30239,17 @@ static sqlite3_mutex *debugMutexAlloc(int id){
break;
}
}
- return (sqlite3_mutex*)pNew;
+ return (tdsqlite3_mutex*)pNew;
}
/*
** This routine deallocates a previously allocated mutex.
*/
-static void debugMutexFree(sqlite3_mutex *pX){
- sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX;
+static void debugMutexFree(tdsqlite3_mutex *pX){
+ tdsqlite3_debug_mutex *p = (tdsqlite3_debug_mutex*)pX;
assert( p->cnt==0 );
if( p->id==SQLITE_MUTEX_RECURSIVE || p->id==SQLITE_MUTEX_FAST ){
- sqlite3_free(p);
+ tdsqlite3_free(p);
}else{
#ifdef SQLITE_ENABLE_API_ARMOR
(void)SQLITE_MISUSE_BKPT;
@@ -26494,43 +30258,43 @@ static void debugMutexFree(sqlite3_mutex *pX){
}
/*
-** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** The tdsqlite3_mutex_enter() and tdsqlite3_mutex_try() routines attempt
** to enter a mutex. If another thread is already within the mutex,
-** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
-** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK
+** tdsqlite3_mutex_enter() will block and tdsqlite3_mutex_try() will return
+** SQLITE_BUSY. The tdsqlite3_mutex_try() interface returns SQLITE_OK
** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can
** be entered multiple times by the same thread. In such cases the,
** mutex must be exited an equal number of times before another thread
** can enter. If the same thread tries to enter any other kind of mutex
** more than once, the behavior is undefined.
*/
-static void debugMutexEnter(sqlite3_mutex *pX){
- sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX;
+static void debugMutexEnter(tdsqlite3_mutex *pX){
+ tdsqlite3_debug_mutex *p = (tdsqlite3_debug_mutex*)pX;
assert( p->id==SQLITE_MUTEX_RECURSIVE || debugMutexNotheld(pX) );
p->cnt++;
}
-static int debugMutexTry(sqlite3_mutex *pX){
- sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX;
+static int debugMutexTry(tdsqlite3_mutex *pX){
+ tdsqlite3_debug_mutex *p = (tdsqlite3_debug_mutex*)pX;
assert( p->id==SQLITE_MUTEX_RECURSIVE || debugMutexNotheld(pX) );
p->cnt++;
return SQLITE_OK;
}
/*
-** The sqlite3_mutex_leave() routine exits a mutex that was
+** The tdsqlite3_mutex_leave() routine exits a mutex that was
** previously entered by the same thread. The behavior
** is undefined if the mutex is not currently entered or
** is not currently allocated. SQLite will never do either.
*/
-static void debugMutexLeave(sqlite3_mutex *pX){
- sqlite3_debug_mutex *p = (sqlite3_debug_mutex*)pX;
+static void debugMutexLeave(tdsqlite3_mutex *pX){
+ tdsqlite3_debug_mutex *p = (tdsqlite3_debug_mutex*)pX;
assert( debugMutexHeld(pX) );
p->cnt--;
assert( p->id==SQLITE_MUTEX_RECURSIVE || debugMutexNotheld(pX) );
}
-SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3NoopMutex(void){
- static const sqlite3_mutex_methods sMutex = {
+SQLITE_PRIVATE tdsqlite3_mutex_methods const *tdsqlite3NoopMutex(void){
+ static const tdsqlite3_mutex_methods sMutex = {
debugMutexInit,
debugMutexEnd,
debugMutexAlloc,
@@ -26552,8 +30316,8 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3NoopMutex(void){
** is used regardless of the run-time threadsafety setting.
*/
#ifdef SQLITE_MUTEX_NOOP
-SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
- return sqlite3NoopMutex();
+SQLITE_PRIVATE tdsqlite3_mutex_methods const *tdsqlite3DefaultMutex(void){
+ return tdsqlite3NoopMutex();
}
#endif /* defined(SQLITE_MUTEX_NOOP) */
#endif /* !defined(SQLITE_MUTEX_OMIT) */
@@ -26587,7 +30351,7 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
#include <pthread.h>
/*
-** The sqlite3_mutex.id, sqlite3_mutex.nRef, and sqlite3_mutex.owner fields
+** The tdsqlite3_mutex.id, tdsqlite3_mutex.nRef, and tdsqlite3_mutex.owner fields
** are necessary under two condidtions: (1) Debug builds and (2) using
** home-grown mutexes. Encapsulate these conditions into a single #define.
*/
@@ -26600,7 +30364,7 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
/*
** Each recursive mutex is an instance of the following structure.
*/
-struct sqlite3_mutex {
+struct tdsqlite3_mutex {
pthread_mutex_t mutex; /* Mutex controlling the lock */
#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR)
int id; /* Mutex type */
@@ -26612,15 +30376,16 @@ struct sqlite3_mutex {
#endif
};
#if SQLITE_MUTEX_NREF
-#define SQLITE3_MUTEX_INITIALIZER {PTHREAD_MUTEX_INITIALIZER,0,0,(pthread_t)0,0}
+# define SQLITE3_MUTEX_INITIALIZER(id) \
+ {PTHREAD_MUTEX_INITIALIZER,id,0,(pthread_t)0,0}
#elif defined(SQLITE_ENABLE_API_ARMOR)
-#define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER, 0 }
+# define SQLITE3_MUTEX_INITIALIZER(id) { PTHREAD_MUTEX_INITIALIZER, id }
#else
-#define SQLITE3_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER }
+#define SQLITE3_MUTEX_INITIALIZER(id) { PTHREAD_MUTEX_INITIALIZER }
#endif
/*
-** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are
+** The tdsqlite3_mutex_held() and tdsqlite3_mutex_notheld() routine are
** intended for use only inside assert() statements. On some platforms,
** there might be race conditions that can cause these routines to
** deliver incorrect results. In particular, if pthread_equal() is
@@ -26636,10 +30401,10 @@ struct sqlite3_mutex {
** routines are never called.
*/
#if !defined(NDEBUG) || defined(SQLITE_DEBUG)
-static int pthreadMutexHeld(sqlite3_mutex *p){
+static int pthreadMutexHeld(tdsqlite3_mutex *p){
return (p->nRef!=0 && pthread_equal(p->owner, pthread_self()));
}
-static int pthreadMutexNotheld(sqlite3_mutex *p){
+static int pthreadMutexNotheld(tdsqlite3_mutex *p){
return p->nRef==0 || pthread_equal(p->owner, pthread_self())==0;
}
#endif
@@ -26649,7 +30414,7 @@ static int pthreadMutexNotheld(sqlite3_mutex *p){
** and also for the implementation of xShmBarrier in the VFS in cases
** where SQLite is compiled without mutexes.
*/
-SQLITE_PRIVATE void sqlite3MemoryBarrier(void){
+SQLITE_PRIVATE void tdsqlite3MemoryBarrier(void){
#if defined(SQLITE_MEMORY_BARRIER)
SQLITE_MEMORY_BARRIER;
#elif defined(__GNUC__) && GCC_VERSION>=4001000
@@ -26664,11 +30429,11 @@ static int pthreadMutexInit(void){ return SQLITE_OK; }
static int pthreadMutexEnd(void){ return SQLITE_OK; }
/*
-** The sqlite3_mutex_alloc() routine allocates a new
+** The tdsqlite3_mutex_alloc() routine allocates a new
** mutex and returns a pointer to it. If it returns NULL
** that means that a mutex could not be allocated. SQLite
** will unwind its stack and return an error. The argument
-** to sqlite3_mutex_alloc() is one of these integer constants:
+** to tdsqlite3_mutex_alloc() is one of these integer constants:
**
** <ul>
** <li> SQLITE_MUTEX_FAST
@@ -26687,7 +30452,7 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; }
** <li> SQLITE_MUTEX_STATIC_VFS3
** </ul>
**
-** The first two constants cause sqlite3_mutex_alloc() to create
+** The first two constants cause tdsqlite3_mutex_alloc() to create
** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE
** is used but not necessarily so when SQLITE_MUTEX_FAST is used.
** The mutex implementation does not need to make a distinction
@@ -26697,7 +30462,7 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; }
** implementation is available on the host platform, the mutex subsystem
** might return such a mutex in response to SQLITE_MUTEX_FAST.
**
-** The other allowed parameters to sqlite3_mutex_alloc() each return
+** The other allowed parameters to tdsqlite3_mutex_alloc() each return
** a pointer to a static preexisting mutex. Six static mutexes are
** used by the current version of SQLite. Future versions of SQLite
** may add additional static mutexes. Static mutexes are for internal
@@ -26706,30 +30471,30 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; }
** SQLITE_MUTEX_RECURSIVE.
**
** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
-** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
+** or SQLITE_MUTEX_RECURSIVE) is used then tdsqlite3_mutex_alloc()
** returns a different mutex on every call. But for the static
** mutex types, the same mutex is returned on every call that has
** the same type number.
*/
-static sqlite3_mutex *pthreadMutexAlloc(int iType){
- static sqlite3_mutex staticMutexes[] = {
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER
+static tdsqlite3_mutex *pthreadMutexAlloc(int iType){
+ static tdsqlite3_mutex staticMutexes[] = {
+ SQLITE3_MUTEX_INITIALIZER(2),
+ SQLITE3_MUTEX_INITIALIZER(3),
+ SQLITE3_MUTEX_INITIALIZER(4),
+ SQLITE3_MUTEX_INITIALIZER(5),
+ SQLITE3_MUTEX_INITIALIZER(6),
+ SQLITE3_MUTEX_INITIALIZER(7),
+ SQLITE3_MUTEX_INITIALIZER(8),
+ SQLITE3_MUTEX_INITIALIZER(9),
+ SQLITE3_MUTEX_INITIALIZER(10),
+ SQLITE3_MUTEX_INITIALIZER(11),
+ SQLITE3_MUTEX_INITIALIZER(12),
+ SQLITE3_MUTEX_INITIALIZER(13)
};
- sqlite3_mutex *p;
+ tdsqlite3_mutex *p;
switch( iType ){
case SQLITE_MUTEX_RECURSIVE: {
- p = sqlite3MallocZero( sizeof(*p) );
+ p = tdsqlite3MallocZero( sizeof(*p) );
if( p ){
#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX
/* If recursive mutexes are not available, we will have to
@@ -26743,13 +30508,19 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
pthread_mutex_init(&p->mutex, &recursiveAttr);
pthread_mutexattr_destroy(&recursiveAttr);
#endif
+#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR)
+ p->id = SQLITE_MUTEX_RECURSIVE;
+#endif
}
break;
}
case SQLITE_MUTEX_FAST: {
- p = sqlite3MallocZero( sizeof(*p) );
+ p = tdsqlite3MallocZero( sizeof(*p) );
if( p ){
pthread_mutex_init(&p->mutex, 0);
+#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR)
+ p->id = SQLITE_MUTEX_FAST;
+#endif
}
break;
}
@@ -26765,7 +30536,7 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
}
}
#if SQLITE_MUTEX_NREF || defined(SQLITE_ENABLE_API_ARMOR)
- if( p ) p->id = iType;
+ assert( p==0 || p->id==iType );
#endif
return p;
}
@@ -26776,14 +30547,14 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){
** allocated mutex. SQLite is careful to deallocate every
** mutex that it allocates.
*/
-static void pthreadMutexFree(sqlite3_mutex *p){
+static void pthreadMutexFree(tdsqlite3_mutex *p){
assert( p->nRef==0 );
#if SQLITE_ENABLE_API_ARMOR
if( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE )
#endif
{
pthread_mutex_destroy(&p->mutex);
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
#ifdef SQLITE_ENABLE_API_ARMOR
else{
@@ -26793,17 +30564,17 @@ static void pthreadMutexFree(sqlite3_mutex *p){
}
/*
-** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** The tdsqlite3_mutex_enter() and tdsqlite3_mutex_try() routines attempt
** to enter a mutex. If another thread is already within the mutex,
-** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
-** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK
+** tdsqlite3_mutex_enter() will block and tdsqlite3_mutex_try() will return
+** SQLITE_BUSY. The tdsqlite3_mutex_try() interface returns SQLITE_OK
** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can
** be entered multiple times by the same thread. In such cases the,
** mutex must be exited an equal number of times before another thread
** can enter. If the same thread tries to enter any other kind of mutex
** more than once, the behavior is undefined.
*/
-static void pthreadMutexEnter(sqlite3_mutex *p){
+static void pthreadMutexEnter(tdsqlite3_mutex *p){
assert( p->id==SQLITE_MUTEX_RECURSIVE || pthreadMutexNotheld(p) );
#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX
@@ -26845,7 +30616,7 @@ static void pthreadMutexEnter(sqlite3_mutex *p){
}
#endif
}
-static int pthreadMutexTry(sqlite3_mutex *p){
+static int pthreadMutexTry(tdsqlite3_mutex *p){
int rc;
assert( p->id==SQLITE_MUTEX_RECURSIVE || pthreadMutexNotheld(p) );
@@ -26897,12 +30668,12 @@ static int pthreadMutexTry(sqlite3_mutex *p){
}
/*
-** The sqlite3_mutex_leave() routine exits a mutex that was
+** The tdsqlite3_mutex_leave() routine exits a mutex that was
** previously entered by the same thread. The behavior
** is undefined if the mutex is not currently entered or
** is not currently allocated. SQLite will never do either.
*/
-static void pthreadMutexLeave(sqlite3_mutex *p){
+static void pthreadMutexLeave(tdsqlite3_mutex *p){
assert( pthreadMutexHeld(p) );
#if SQLITE_MUTEX_NREF
p->nRef--;
@@ -26925,8 +30696,8 @@ static void pthreadMutexLeave(sqlite3_mutex *p){
#endif
}
-SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
- static const sqlite3_mutex_methods sMutex = {
+SQLITE_PRIVATE tdsqlite3_mutex_methods const *tdsqlite3DefaultMutex(void){
+ static const tdsqlite3_mutex_methods sMutex = {
pthreadMutexInit,
pthreadMutexEnd,
pthreadMutexAlloc,
@@ -27027,7 +30798,7 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
******************************************************************************
**
** This file contains inline asm code for retrieving "high-performance"
-** counters for x86 class CPUs.
+** counters for x86 and x86_64 class CPUs.
*/
#ifndef SQLITE_HWTIME_H
#define SQLITE_HWTIME_H
@@ -27038,12 +30809,13 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
** processor and returns that value. This can be used for high-res
** profiling.
*/
-#if (defined(__GNUC__) || defined(_MSC_VER)) && \
- (defined(i386) || defined(__i386__) || defined(_M_IX86))
+#if !defined(__STRICT_ANSI__) && \
+ (defined(__GNUC__) || defined(_MSC_VER)) && \
+ (defined(i386) || defined(__i386__) || defined(_M_IX86))
#if defined(__GNUC__)
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned int lo, hi;
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return (sqlite_uint64)hi << 32 | lo;
@@ -27051,7 +30823,7 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
#elif defined(_MSC_VER)
- __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){
+ __declspec(naked) __inline sqlite_uint64 __cdecl tdsqlite3Hwtime(void){
__asm {
rdtsc
ret ; return value at EDX:EAX
@@ -27060,17 +30832,17 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
#endif
-#elif (defined(__GNUC__) && defined(__x86_64__))
+#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__))
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned long val;
__asm__ __volatile__ ("rdtsc" : "=A" (val));
return val;
}
-#elif (defined(__GNUC__) && defined(__ppc__))
+#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__))
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned long long retval;
unsigned long junk;
__asm__ __volatile__ ("\n\
@@ -27085,16 +30857,15 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
#else
- #error Need implementation of sqlite3Hwtime() for your platform.
-
/*
- ** To compile without implementing sqlite3Hwtime() for your platform,
- ** you can remove the above #error and use the following
- ** stub function. You will lose timing support for many
- ** of the debugging and testing utilities, but it should at
- ** least compile and run.
+ ** asm() is needed for hardware timing support. Without asm(),
+ ** disable the tdsqlite3Hwtime() routine.
+ **
+ ** tdsqlite3Hwtime() is only used for some obscure debugging
+ ** and analysis configurations, not in any deliverable, so this
+ ** should not be a great loss.
*/
-SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); }
+SQLITE_PRIVATE sqlite_uint64 tdsqlite3Hwtime(void){ return ((sqlite_uint64)0); }
#endif
@@ -27105,8 +30876,8 @@ SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); }
static sqlite_uint64 g_start;
static sqlite_uint64 g_elapsed;
-#define TIMER_START g_start=sqlite3Hwtime()
-#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start
+#define TIMER_START g_start=tdsqlite3Hwtime()
+#define TIMER_END g_elapsed=tdsqlite3Hwtime()-g_start
#define TIMER_ELAPSED g_elapsed
#else
#define TIMER_START
@@ -27120,32 +30891,32 @@ static sqlite_uint64 g_elapsed;
** is used for testing the I/O recovery logic.
*/
#if defined(SQLITE_TEST)
-SQLITE_API extern int sqlite3_io_error_hit;
-SQLITE_API extern int sqlite3_io_error_hardhit;
-SQLITE_API extern int sqlite3_io_error_pending;
-SQLITE_API extern int sqlite3_io_error_persist;
-SQLITE_API extern int sqlite3_io_error_benign;
-SQLITE_API extern int sqlite3_diskfull_pending;
-SQLITE_API extern int sqlite3_diskfull;
-#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X)
+SQLITE_API extern int tdsqlite3_io_error_hit;
+SQLITE_API extern int tdsqlite3_io_error_hardhit;
+SQLITE_API extern int tdsqlite3_io_error_pending;
+SQLITE_API extern int tdsqlite3_io_error_persist;
+SQLITE_API extern int tdsqlite3_io_error_benign;
+SQLITE_API extern int tdsqlite3_diskfull_pending;
+SQLITE_API extern int tdsqlite3_diskfull;
+#define SimulateIOErrorBenign(X) tdsqlite3_io_error_benign=(X)
#define SimulateIOError(CODE) \
- if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \
- || sqlite3_io_error_pending-- == 1 ) \
+ if( (tdsqlite3_io_error_persist && tdsqlite3_io_error_hit) \
+ || tdsqlite3_io_error_pending-- == 1 ) \
{ local_ioerr(); CODE; }
static void local_ioerr(){
IOTRACE(("IOERR\n"));
- sqlite3_io_error_hit++;
- if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++;
+ tdsqlite3_io_error_hit++;
+ if( !tdsqlite3_io_error_benign ) tdsqlite3_io_error_hardhit++;
}
#define SimulateDiskfullError(CODE) \
- if( sqlite3_diskfull_pending ){ \
- if( sqlite3_diskfull_pending == 1 ){ \
+ if( tdsqlite3_diskfull_pending ){ \
+ if( tdsqlite3_diskfull_pending == 1 ){ \
local_ioerr(); \
- sqlite3_diskfull = 1; \
- sqlite3_io_error_hit = 1; \
+ tdsqlite3_diskfull = 1; \
+ tdsqlite3_io_error_hit = 1; \
CODE; \
}else{ \
- sqlite3_diskfull_pending--; \
+ tdsqlite3_diskfull_pending--; \
} \
}
#else
@@ -27158,8 +30929,8 @@ static void local_ioerr(){
** When testing, keep a count of the number of open files.
*/
#if defined(SQLITE_TEST)
-SQLITE_API extern int sqlite3_open_file_count;
-#define OpenCounter(X) sqlite3_open_file_count+=(X)
+SQLITE_API extern int tdsqlite3_open_file_count;
+#define OpenCounter(X) tdsqlite3_open_file_count+=(X)
#else
#define OpenCounter(X)
#endif /* defined(SQLITE_TEST) */
@@ -27197,8 +30968,11 @@ SQLITE_API extern int sqlite3_open_file_count;
/* #include "windows.h" */
#ifdef __CYGWIN__
+# define NOCRYPT
+# include <windows.h>
+
# include <sys/cygwin.h>
-# include <errno.h> /* amalgamator: dontcache */
+/* # include <errno.h> ** amalgamator: dontcache ** */
#endif
/*
@@ -27276,13 +31050,13 @@ SQLITE_API extern int sqlite3_open_file_count;
/*
** Each recursive mutex is an instance of the following structure.
*/
-struct sqlite3_mutex {
+struct tdsqlite3_mutex {
CRITICAL_SECTION mutex; /* Mutex controlling the lock */
int id; /* Mutex type */
#ifdef SQLITE_DEBUG
volatile int nRef; /* Number of enterances */
volatile DWORD owner; /* Thread holding this mutex */
- volatile int trace; /* True to trace changes */
+ volatile LONG trace; /* True to trace changes */
#endif
};
@@ -27294,26 +31068,26 @@ struct sqlite3_mutex {
#define SQLITE_W32_MUTEX_INITIALIZER { 0 }
#ifdef SQLITE_DEBUG
-#define SQLITE3_MUTEX_INITIALIZER { SQLITE_W32_MUTEX_INITIALIZER, 0, \
+#define SQLITE3_MUTEX_INITIALIZER(id) { SQLITE_W32_MUTEX_INITIALIZER, id, \
0L, (DWORD)0, 0 }
#else
-#define SQLITE3_MUTEX_INITIALIZER { SQLITE_W32_MUTEX_INITIALIZER, 0 }
+#define SQLITE3_MUTEX_INITIALIZER(id) { SQLITE_W32_MUTEX_INITIALIZER, id }
#endif
#ifdef SQLITE_DEBUG
/*
-** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are
+** The tdsqlite3_mutex_held() and tdsqlite3_mutex_notheld() routine are
** intended for use only inside assert() statements.
*/
-static int winMutexHeld(sqlite3_mutex *p){
+static int winMutexHeld(tdsqlite3_mutex *p){
return p->nRef!=0 && p->owner==GetCurrentThreadId();
}
-static int winMutexNotheld2(sqlite3_mutex *p, DWORD tid){
+static int winMutexNotheld2(tdsqlite3_mutex *p, DWORD tid){
return p->nRef==0 || p->owner!=tid;
}
-static int winMutexNotheld(sqlite3_mutex *p){
+static int winMutexNotheld(tdsqlite3_mutex *p){
DWORD tid = GetCurrentThreadId();
return winMutexNotheld2(p, tid);
}
@@ -27324,13 +31098,12 @@ static int winMutexNotheld(sqlite3_mutex *p){
** and also for the xShmBarrier method of the VFS in cases when SQLite is
** compiled without mutexes (SQLITE_THREADSAFE=0).
*/
-SQLITE_PRIVATE void sqlite3MemoryBarrier(void){
+SQLITE_PRIVATE void tdsqlite3MemoryBarrier(void){
#if defined(SQLITE_MEMORY_BARRIER)
SQLITE_MEMORY_BARRIER;
#elif defined(__GNUC__)
__sync_synchronize();
-#elif !defined(SQLITE_DISABLE_INTRINSIC) && \
- defined(_MSC_VER) && _MSC_VER>=1300
+#elif MSVC_VERSION>=1300
_ReadWriteBarrier();
#elif defined(MemoryBarrier)
MemoryBarrier();
@@ -27340,32 +31113,32 @@ SQLITE_PRIVATE void sqlite3MemoryBarrier(void){
/*
** Initialize and deinitialize the mutex subsystem.
*/
-static sqlite3_mutex winMutex_staticMutexes[] = {
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER,
- SQLITE3_MUTEX_INITIALIZER
+static tdsqlite3_mutex winMutex_staticMutexes[] = {
+ SQLITE3_MUTEX_INITIALIZER(2),
+ SQLITE3_MUTEX_INITIALIZER(3),
+ SQLITE3_MUTEX_INITIALIZER(4),
+ SQLITE3_MUTEX_INITIALIZER(5),
+ SQLITE3_MUTEX_INITIALIZER(6),
+ SQLITE3_MUTEX_INITIALIZER(7),
+ SQLITE3_MUTEX_INITIALIZER(8),
+ SQLITE3_MUTEX_INITIALIZER(9),
+ SQLITE3_MUTEX_INITIALIZER(10),
+ SQLITE3_MUTEX_INITIALIZER(11),
+ SQLITE3_MUTEX_INITIALIZER(12),
+ SQLITE3_MUTEX_INITIALIZER(13)
};
static int winMutex_isInit = 0;
static int winMutex_isNt = -1; /* <0 means "need to query" */
/* As the winMutexInit() and winMutexEnd() functions are called as part
-** of the sqlite3_initialize() and sqlite3_shutdown() processing, the
+** of the tdsqlite3_initialize() and tdsqlite3_shutdown() processing, the
** "interlocked" magic used here is probably not strictly necessary.
*/
static LONG SQLITE_WIN32_VOLATILE winMutex_lock = 0;
-SQLITE_API int sqlite3_win32_is_nt(void); /* os_win.c */
-SQLITE_API void sqlite3_win32_sleep(DWORD milliseconds); /* os_win.c */
+SQLITE_API int tdsqlite3_win32_is_nt(void); /* os_win.c */
+SQLITE_API void tdsqlite3_win32_sleep(DWORD milliseconds); /* os_win.c */
static int winMutexInit(void){
/* The first to increment to 1 does actual initialization */
@@ -27383,7 +31156,7 @@ static int winMutexInit(void){
/* Another thread is (in the process of) initializing the static
** mutexes */
while( !winMutex_isInit ){
- sqlite3_win32_sleep(1);
+ tdsqlite3_win32_sleep(1);
}
}
return SQLITE_OK;
@@ -27405,11 +31178,11 @@ static int winMutexEnd(void){
}
/*
-** The sqlite3_mutex_alloc() routine allocates a new
+** The tdsqlite3_mutex_alloc() routine allocates a new
** mutex and returns a pointer to it. If it returns NULL
** that means that a mutex could not be allocated. SQLite
** will unwind its stack and return an error. The argument
-** to sqlite3_mutex_alloc() is one of these integer constants:
+** to tdsqlite3_mutex_alloc() is one of these integer constants:
**
** <ul>
** <li> SQLITE_MUTEX_FAST
@@ -27428,7 +31201,7 @@ static int winMutexEnd(void){
** <li> SQLITE_MUTEX_STATIC_VFS3
** </ul>
**
-** The first two constants cause sqlite3_mutex_alloc() to create
+** The first two constants cause tdsqlite3_mutex_alloc() to create
** a new mutex. The new mutex is recursive when SQLITE_MUTEX_RECURSIVE
** is used but not necessarily so when SQLITE_MUTEX_FAST is used.
** The mutex implementation does not need to make a distinction
@@ -27438,7 +31211,7 @@ static int winMutexEnd(void){
** implementation is available on the host platform, the mutex subsystem
** might return such a mutex in response to SQLITE_MUTEX_FAST.
**
-** The other allowed parameters to sqlite3_mutex_alloc() each return
+** The other allowed parameters to tdsqlite3_mutex_alloc() each return
** a pointer to a static preexisting mutex. Six static mutexes are
** used by the current version of SQLite. Future versions of SQLite
** may add additional static mutexes. Static mutexes are for internal
@@ -27447,18 +31220,18 @@ static int winMutexEnd(void){
** SQLITE_MUTEX_RECURSIVE.
**
** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
-** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
+** or SQLITE_MUTEX_RECURSIVE) is used then tdsqlite3_mutex_alloc()
** returns a different mutex on every call. But for the static
** mutex types, the same mutex is returned on every call that has
** the same type number.
*/
-static sqlite3_mutex *winMutexAlloc(int iType){
- sqlite3_mutex *p;
+static tdsqlite3_mutex *winMutexAlloc(int iType){
+ tdsqlite3_mutex *p;
switch( iType ){
case SQLITE_MUTEX_FAST:
case SQLITE_MUTEX_RECURSIVE: {
- p = sqlite3MallocZero( sizeof(*p) );
+ p = tdsqlite3MallocZero( sizeof(*p) );
if( p ){
p->id = iType;
#ifdef SQLITE_DEBUG
@@ -27482,15 +31255,15 @@ static sqlite3_mutex *winMutexAlloc(int iType){
}
#endif
p = &winMutex_staticMutexes[iType-2];
- p->id = iType;
#ifdef SQLITE_DEBUG
#ifdef SQLITE_WIN32_MUTEX_TRACE_STATIC
- p->trace = 1;
+ InterlockedCompareExchange(&p->trace, 1, 0);
#endif
#endif
break;
}
}
+ assert( p==0 || p->id==iType );
return p;
}
@@ -27500,12 +31273,12 @@ static sqlite3_mutex *winMutexAlloc(int iType){
** allocated mutex. SQLite is careful to deallocate every
** mutex that it allocates.
*/
-static void winMutexFree(sqlite3_mutex *p){
+static void winMutexFree(tdsqlite3_mutex *p){
assert( p );
assert( p->nRef==0 && p->owner==0 );
if( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ){
DeleteCriticalSection(&p->mutex);
- sqlite3_free(p);
+ tdsqlite3_free(p);
}else{
#ifdef SQLITE_ENABLE_API_ARMOR
(void)SQLITE_MISUSE_BKPT;
@@ -27514,17 +31287,17 @@ static void winMutexFree(sqlite3_mutex *p){
}
/*
-** The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** The tdsqlite3_mutex_enter() and tdsqlite3_mutex_try() routines attempt
** to enter a mutex. If another thread is already within the mutex,
-** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
-** SQLITE_BUSY. The sqlite3_mutex_try() interface returns SQLITE_OK
+** tdsqlite3_mutex_enter() will block and tdsqlite3_mutex_try() will return
+** SQLITE_BUSY. The tdsqlite3_mutex_try() interface returns SQLITE_OK
** upon successful entry. Mutexes created using SQLITE_MUTEX_RECURSIVE can
** be entered multiple times by the same thread. In such cases the,
** mutex must be exited an equal number of times before another thread
** can enter. If the same thread tries to enter any other kind of mutex
** more than once, the behavior is undefined.
*/
-static void winMutexEnter(sqlite3_mutex *p){
+static void winMutexEnter(tdsqlite3_mutex *p){
#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
DWORD tid = GetCurrentThreadId();
#endif
@@ -27541,13 +31314,13 @@ static void winMutexEnter(sqlite3_mutex *p){
p->owner = tid;
p->nRef++;
if( p->trace ){
- OSTRACE(("ENTER-MUTEX tid=%lu, mutex=%p (%d), nRef=%d\n",
- tid, p, p->trace, p->nRef));
+ OSTRACE(("ENTER-MUTEX tid=%lu, mutex(%d)=%p (%d), nRef=%d\n",
+ tid, p->id, p, p->trace, p->nRef));
}
#endif
}
-static int winMutexTry(sqlite3_mutex *p){
+static int winMutexTry(tdsqlite3_mutex *p){
#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
DWORD tid = GetCurrentThreadId();
#endif
@@ -27555,7 +31328,7 @@ static int winMutexTry(sqlite3_mutex *p){
assert( p );
assert( p->id==SQLITE_MUTEX_RECURSIVE || winMutexNotheld2(p, tid) );
/*
- ** The sqlite3_mutex_try() routine is very rarely used, and when it
+ ** The tdsqlite3_mutex_try() routine is very rarely used, and when it
** is used it is merely an optimization. So it is OK for it to always
** fail.
**
@@ -27569,7 +31342,7 @@ static int winMutexTry(sqlite3_mutex *p){
assert( winMutex_isInit==1 );
assert( winMutex_isNt>=-1 && winMutex_isNt<=1 );
if( winMutex_isNt<0 ){
- winMutex_isNt = sqlite3_win32_is_nt();
+ winMutex_isNt = tdsqlite3_win32_is_nt();
}
assert( winMutex_isNt==0 || winMutex_isNt==1 );
if( winMutex_isNt && TryEnterCriticalSection(&p->mutex) ){
@@ -27584,20 +31357,20 @@ static int winMutexTry(sqlite3_mutex *p){
#endif
#ifdef SQLITE_DEBUG
if( p->trace ){
- OSTRACE(("TRY-MUTEX tid=%lu, mutex=%p (%d), owner=%lu, nRef=%d, rc=%s\n",
- tid, p, p->trace, p->owner, p->nRef, sqlite3ErrName(rc)));
+ OSTRACE(("TRY-MUTEX tid=%lu, mutex(%d)=%p (%d), owner=%lu, nRef=%d, rc=%s\n",
+ tid, p->id, p, p->trace, p->owner, p->nRef, tdsqlite3ErrName(rc)));
}
#endif
return rc;
}
/*
-** The sqlite3_mutex_leave() routine exits a mutex that was
+** The tdsqlite3_mutex_leave() routine exits a mutex that was
** previously entered by the same thread. The behavior
** is undefined if the mutex is not currently entered or
** is not currently allocated. SQLite will never do either.
*/
-static void winMutexLeave(sqlite3_mutex *p){
+static void winMutexLeave(tdsqlite3_mutex *p){
#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
DWORD tid = GetCurrentThreadId();
#endif
@@ -27613,14 +31386,14 @@ static void winMutexLeave(sqlite3_mutex *p){
LeaveCriticalSection(&p->mutex);
#ifdef SQLITE_DEBUG
if( p->trace ){
- OSTRACE(("LEAVE-MUTEX tid=%lu, mutex=%p (%d), nRef=%d\n",
- tid, p, p->trace, p->nRef));
+ OSTRACE(("LEAVE-MUTEX tid=%lu, mutex(%d)=%p (%d), nRef=%d\n",
+ tid, p->id, p, p->trace, p->nRef));
}
#endif
}
-SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
- static const sqlite3_mutex_methods sMutex = {
+SQLITE_PRIVATE tdsqlite3_mutex_methods const *tdsqlite3DefaultMutex(void){
+ static const tdsqlite3_mutex_methods sMutex = {
winMutexInit,
winMutexEnd,
winMutexAlloc,
@@ -27665,11 +31438,11 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){
** held by SQLite. An example of non-essential memory is memory used to
** cache database pages that are not currently in use.
*/
-SQLITE_API int sqlite3_release_memory(int n){
+SQLITE_API int tdsqlite3_release_memory(int n){
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
- return sqlite3PcacheReleaseMemory(n);
+ return tdsqlite3PcacheReleaseMemory(n);
#else
- /* IMPLEMENTATION-OF: R-34391-24921 The sqlite3_release_memory() routine
+ /* IMPLEMENTATION-OF: R-34391-24921 The tdsqlite3_release_memory() routine
** is a no-op returning zero if SQLite is not compiled with
** SQLITE_ENABLE_MEMORY_MANAGEMENT. */
UNUSED_PARAMETER(n);
@@ -27678,43 +31451,33 @@ SQLITE_API int sqlite3_release_memory(int n){
}
/*
-** An instance of the following object records the location of
-** each unused scratch buffer.
+** Default value of the hard heap limit. 0 means "no limit".
*/
-typedef struct ScratchFreeslot {
- struct ScratchFreeslot *pNext; /* Next unused scratch buffer */
-} ScratchFreeslot;
+#ifndef SQLITE_MAX_MEMORY
+# define SQLITE_MAX_MEMORY 0
+#endif
/*
** State information local to the memory allocation subsystem.
*/
static SQLITE_WSD struct Mem0Global {
- sqlite3_mutex *mutex; /* Mutex to serialize access */
- sqlite3_int64 alarmThreshold; /* The soft heap limit */
-
- /*
- ** Pointers to the end of sqlite3GlobalConfig.pScratch memory
- ** (so that a range test can be used to determine if an allocation
- ** being freed came from pScratch) and a pointer to the list of
- ** unused scratch allocations.
- */
- void *pScratchEnd;
- ScratchFreeslot *pScratchFree;
- u32 nScratchFree;
+ tdsqlite3_mutex *mutex; /* Mutex to serialize access */
+ tdsqlite3_int64 alarmThreshold; /* The soft heap limit */
+ tdsqlite3_int64 hardLimit; /* The hard upper bound on memory */
/*
** True if heap is nearly "full" where "full" is defined by the
- ** sqlite3_soft_heap_limit() setting.
+ ** tdsqlite3_soft_heap_limit() setting.
*/
int nearlyFull;
-} mem0 = { 0, 0, 0, 0, 0, 0 };
+} mem0 = { 0, SQLITE_MAX_MEMORY, SQLITE_MAX_MEMORY, 0 };
#define mem0 GLOBAL(struct Mem0Global, mem0)
/*
-** Return the memory allocator mutex. sqlite3_status() needs it.
+** Return the memory allocator mutex. tdsqlite3_status() needs it.
*/
-SQLITE_PRIVATE sqlite3_mutex *sqlite3MallocMutex(void){
+SQLITE_PRIVATE tdsqlite3_mutex *tdsqlite3MallocMutex(void){
return mem0.mutex;
}
@@ -27724,10 +31487,10 @@ SQLITE_PRIVATE sqlite3_mutex *sqlite3MallocMutex(void){
** that was invoked when memory usage grew too large. Now it is a
** no-op.
*/
-SQLITE_API int sqlite3_memory_alarm(
- void(*xCallback)(void *pArg, sqlite3_int64 used,int N),
+SQLITE_API int tdsqlite3_memory_alarm(
+ void(*xCallback)(void *pArg, tdsqlite3_int64 used,int N),
void *pArg,
- sqlite3_int64 iThreshold
+ tdsqlite3_int64 iThreshold
){
(void)xCallback;
(void)pArg;
@@ -27737,93 +31500,122 @@ SQLITE_API int sqlite3_memory_alarm(
#endif
/*
-** Set the soft heap-size limit for the library. Passing a zero or
-** negative value indicates no limit.
+** Set the soft heap-size limit for the library. An argument of
+** zero disables the limit. A negative argument is a no-op used to
+** obtain the return value.
+**
+** The return value is the value of the heap limit just before this
+** interface was called.
+**
+** If the hard heap limit is enabled, then the soft heap limit cannot
+** be disabled nor raised above the hard heap limit.
*/
-SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 n){
- sqlite3_int64 priorLimit;
- sqlite3_int64 excess;
- sqlite3_int64 nUsed;
+SQLITE_API tdsqlite3_int64 tdsqlite3_soft_heap_limit64(tdsqlite3_int64 n){
+ tdsqlite3_int64 priorLimit;
+ tdsqlite3_int64 excess;
+ tdsqlite3_int64 nUsed;
#ifndef SQLITE_OMIT_AUTOINIT
- int rc = sqlite3_initialize();
+ int rc = tdsqlite3_initialize();
if( rc ) return -1;
#endif
- sqlite3_mutex_enter(mem0.mutex);
+ tdsqlite3_mutex_enter(mem0.mutex);
priorLimit = mem0.alarmThreshold;
if( n<0 ){
- sqlite3_mutex_leave(mem0.mutex);
+ tdsqlite3_mutex_leave(mem0.mutex);
return priorLimit;
}
+ if( mem0.hardLimit>0 && (n>mem0.hardLimit || n==0) ){
+ n = mem0.hardLimit;
+ }
mem0.alarmThreshold = n;
- nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
+ nUsed = tdsqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
mem0.nearlyFull = (n>0 && n<=nUsed);
- sqlite3_mutex_leave(mem0.mutex);
- excess = sqlite3_memory_used() - n;
- if( excess>0 ) sqlite3_release_memory((int)(excess & 0x7fffffff));
+ tdsqlite3_mutex_leave(mem0.mutex);
+ excess = tdsqlite3_memory_used() - n;
+ if( excess>0 ) tdsqlite3_release_memory((int)(excess & 0x7fffffff));
return priorLimit;
}
-SQLITE_API void sqlite3_soft_heap_limit(int n){
+SQLITE_API void tdsqlite3_soft_heap_limit(int n){
if( n<0 ) n = 0;
- sqlite3_soft_heap_limit64(n);
+ tdsqlite3_soft_heap_limit64(n);
}
/*
+** Set the hard heap-size limit for the library. An argument of zero
+** disables the hard heap limit. A negative argument is a no-op used
+** to obtain the return value without affecting the hard heap limit.
+**
+** The return value is the value of the hard heap limit just prior to
+** calling this interface.
+**
+** Setting the hard heap limit will also activate the soft heap limit
+** and constrain the soft heap limit to be no more than the hard heap
+** limit.
+*/
+SQLITE_API tdsqlite3_int64 tdsqlite3_hard_heap_limit64(tdsqlite3_int64 n){
+ tdsqlite3_int64 priorLimit;
+#ifndef SQLITE_OMIT_AUTOINIT
+ int rc = tdsqlite3_initialize();
+ if( rc ) return -1;
+#endif
+ tdsqlite3_mutex_enter(mem0.mutex);
+ priorLimit = mem0.hardLimit;
+ if( n>=0 ){
+ mem0.hardLimit = n;
+ if( n<mem0.alarmThreshold || mem0.alarmThreshold==0 ){
+ mem0.alarmThreshold = n;
+ }
+ }
+ tdsqlite3_mutex_leave(mem0.mutex);
+ return priorLimit;
+}
+
+
+/*
** Initialize the memory allocation subsystem.
*/
-SQLITE_PRIVATE int sqlite3MallocInit(void){
+SQLITE_PRIVATE int tdsqlite3MallocInit(void){
int rc;
- if( sqlite3GlobalConfig.m.xMalloc==0 ){
- sqlite3MemSetDefault();
+ if( tdsqlite3GlobalConfig.m.xMalloc==0 ){
+ tdsqlite3MemSetDefault();
}
memset(&mem0, 0, sizeof(mem0));
- mem0.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
- if( sqlite3GlobalConfig.pScratch && sqlite3GlobalConfig.szScratch>=100
- && sqlite3GlobalConfig.nScratch>0 ){
- int i, n, sz;
- ScratchFreeslot *pSlot;
- sz = ROUNDDOWN8(sqlite3GlobalConfig.szScratch);
- sqlite3GlobalConfig.szScratch = sz;
- pSlot = (ScratchFreeslot*)sqlite3GlobalConfig.pScratch;
- n = sqlite3GlobalConfig.nScratch;
- mem0.pScratchFree = pSlot;
- mem0.nScratchFree = n;
- for(i=0; i<n-1; i++){
- pSlot->pNext = (ScratchFreeslot*)(sz+(char*)pSlot);
- pSlot = pSlot->pNext;
- }
- pSlot->pNext = 0;
- mem0.pScratchEnd = (void*)&pSlot[1];
- }else{
- mem0.pScratchEnd = 0;
- sqlite3GlobalConfig.pScratch = 0;
- sqlite3GlobalConfig.szScratch = 0;
- sqlite3GlobalConfig.nScratch = 0;
- }
- if( sqlite3GlobalConfig.pPage==0 || sqlite3GlobalConfig.szPage<512
- || sqlite3GlobalConfig.nPage<=0 ){
- sqlite3GlobalConfig.pPage = 0;
- sqlite3GlobalConfig.szPage = 0;
- }
- rc = sqlite3GlobalConfig.m.xInit(sqlite3GlobalConfig.m.pAppData);
+ mem0.mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM);
+ if( tdsqlite3GlobalConfig.pPage==0 || tdsqlite3GlobalConfig.szPage<512
+ || tdsqlite3GlobalConfig.nPage<=0 ){
+ tdsqlite3GlobalConfig.pPage = 0;
+ tdsqlite3GlobalConfig.szPage = 0;
+ }
+ rc = tdsqlite3GlobalConfig.m.xInit(tdsqlite3GlobalConfig.m.pAppData);
if( rc!=SQLITE_OK ) memset(&mem0, 0, sizeof(mem0));
+/* BEGIN SQLCIPHER */
+#ifdef SQLITE_HAS_CODEC
+ /* install wrapping functions for memory management
+ that will wipe all memory allocated by SQLite
+ when freed */
+ if( rc==SQLITE_OK ) {
+ sqlcipher_init_memmethods();
+ }
+#endif
+/* END SQLCIPHER */
return rc;
}
/*
** Return true if the heap is currently under memory pressure - in other
** words if the amount of heap used is close to the limit set by
-** sqlite3_soft_heap_limit().
+** tdsqlite3_soft_heap_limit().
*/
-SQLITE_PRIVATE int sqlite3HeapNearlyFull(void){
+SQLITE_PRIVATE int tdsqlite3HeapNearlyFull(void){
return mem0.nearlyFull;
}
/*
** Deinitialize the memory allocation subsystem.
*/
-SQLITE_PRIVATE void sqlite3MallocEnd(void){
- if( sqlite3GlobalConfig.m.xShutdown ){
- sqlite3GlobalConfig.m.xShutdown(sqlite3GlobalConfig.m.pAppData);
+SQLITE_PRIVATE void tdsqlite3MallocEnd(void){
+ if( tdsqlite3GlobalConfig.m.xShutdown ){
+ tdsqlite3GlobalConfig.m.xShutdown(tdsqlite3GlobalConfig.m.pAppData);
}
memset(&mem0, 0, sizeof(mem0));
}
@@ -27831,9 +31623,9 @@ SQLITE_PRIVATE void sqlite3MallocEnd(void){
/*
** Return the amount of memory currently checked out.
*/
-SQLITE_API sqlite3_int64 sqlite3_memory_used(void){
- sqlite3_int64 res, mx;
- sqlite3_status64(SQLITE_STATUS_MEMORY_USED, &res, &mx, 0);
+SQLITE_API tdsqlite3_int64 tdsqlite3_memory_used(void){
+ tdsqlite3_int64 res, mx;
+ tdsqlite3_status64(SQLITE_STATUS_MEMORY_USED, &res, &mx, 0);
return res;
}
@@ -27842,76 +31634,90 @@ SQLITE_API sqlite3_int64 sqlite3_memory_used(void){
** checked out since either the beginning of this process
** or since the most recent reset.
*/
-SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag){
- sqlite3_int64 res, mx;
- sqlite3_status64(SQLITE_STATUS_MEMORY_USED, &res, &mx, resetFlag);
+SQLITE_API tdsqlite3_int64 tdsqlite3_memory_highwater(int resetFlag){
+ tdsqlite3_int64 res, mx;
+ tdsqlite3_status64(SQLITE_STATUS_MEMORY_USED, &res, &mx, resetFlag);
return mx;
}
/*
** Trigger the alarm
*/
-static void sqlite3MallocAlarm(int nByte){
+static void tdsqlite3MallocAlarm(int nByte){
if( mem0.alarmThreshold<=0 ) return;
- sqlite3_mutex_leave(mem0.mutex);
- sqlite3_release_memory(nByte);
- sqlite3_mutex_enter(mem0.mutex);
+ tdsqlite3_mutex_leave(mem0.mutex);
+ tdsqlite3_release_memory(nByte);
+ tdsqlite3_mutex_enter(mem0.mutex);
}
/*
** Do a memory allocation with statistics and alarms. Assume the
** lock is already held.
*/
-static int mallocWithAlarm(int n, void **pp){
- int nFull;
+static void mallocWithAlarm(int n, void **pp){
void *p;
- assert( sqlite3_mutex_held(mem0.mutex) );
- nFull = sqlite3GlobalConfig.m.xRoundup(n);
- sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, n);
+ int nFull;
+ assert( tdsqlite3_mutex_held(mem0.mutex) );
+ assert( n>0 );
+
+ /* In Firefox (circa 2017-02-08), xRoundup() is remapped to an internal
+ ** implementation of malloc_good_size(), which must be called in debug
+ ** mode and specifically when the DMD "Dark Matter Detector" is enabled
+ ** or else a crash results. Hence, do not attempt to optimize out the
+ ** following xRoundup() call. */
+ nFull = tdsqlite3GlobalConfig.m.xRoundup(n);
+
+ tdsqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, n);
if( mem0.alarmThreshold>0 ){
- sqlite3_int64 nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
+ tdsqlite3_int64 nUsed = tdsqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
if( nUsed >= mem0.alarmThreshold - nFull ){
mem0.nearlyFull = 1;
- sqlite3MallocAlarm(nFull);
+ tdsqlite3MallocAlarm(nFull);
+ if( mem0.hardLimit ){
+ nUsed = tdsqlite3StatusValue(SQLITE_STATUS_MEMORY_USED);
+ if( nUsed >= mem0.hardLimit - nFull ){
+ *pp = 0;
+ return;
+ }
+ }
}else{
mem0.nearlyFull = 0;
}
}
- p = sqlite3GlobalConfig.m.xMalloc(nFull);
+ p = tdsqlite3GlobalConfig.m.xMalloc(nFull);
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
if( p==0 && mem0.alarmThreshold>0 ){
- sqlite3MallocAlarm(nFull);
- p = sqlite3GlobalConfig.m.xMalloc(nFull);
+ tdsqlite3MallocAlarm(nFull);
+ p = tdsqlite3GlobalConfig.m.xMalloc(nFull);
}
#endif
if( p ){
- nFull = sqlite3MallocSize(p);
- sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nFull);
- sqlite3StatusUp(SQLITE_STATUS_MALLOC_COUNT, 1);
+ nFull = tdsqlite3MallocSize(p);
+ tdsqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nFull);
+ tdsqlite3StatusUp(SQLITE_STATUS_MALLOC_COUNT, 1);
}
*pp = p;
- return nFull;
}
/*
-** Allocate memory. This routine is like sqlite3_malloc() except that it
+** Allocate memory. This routine is like tdsqlite3_malloc() except that it
** assumes the memory subsystem has already been initialized.
*/
-SQLITE_PRIVATE void *sqlite3Malloc(u64 n){
+SQLITE_PRIVATE void *tdsqlite3Malloc(u64 n){
void *p;
if( n==0 || n>=0x7fffff00 ){
/* A memory allocation of a number of bytes which is near the maximum
** signed integer value might cause an integer overflow inside of the
** xMalloc(). Hence we limit the maximum size to 0x7fffff00, giving
** 255 bytes of overhead. SQLite itself will never use anything near
- ** this amount. The only way to reach the limit is with sqlite3_malloc() */
+ ** this amount. The only way to reach the limit is with tdsqlite3_malloc() */
p = 0;
- }else if( sqlite3GlobalConfig.bMemstat ){
- sqlite3_mutex_enter(mem0.mutex);
+ }else if( tdsqlite3GlobalConfig.bMemstat ){
+ tdsqlite3_mutex_enter(mem0.mutex);
mallocWithAlarm((int)n, &p);
- sqlite3_mutex_leave(mem0.mutex);
+ tdsqlite3_mutex_leave(mem0.mutex);
}else{
- p = sqlite3GlobalConfig.m.xMalloc((int)n);
+ p = tdsqlite3GlobalConfig.m.xMalloc((int)n);
}
assert( EIGHT_BYTE_ALIGNMENT(p) ); /* IMP: R-11148-40995 */
return p;
@@ -27922,123 +31728,24 @@ SQLITE_PRIVATE void *sqlite3Malloc(u64 n){
** First make sure the memory subsystem is initialized, then do the
** allocation.
*/
-SQLITE_API void *sqlite3_malloc(int n){
+SQLITE_API void *tdsqlite3_malloc(int n){
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
- return n<=0 ? 0 : sqlite3Malloc(n);
+ return n<=0 ? 0 : tdsqlite3Malloc(n);
}
-SQLITE_API void *sqlite3_malloc64(sqlite3_uint64 n){
+SQLITE_API void *tdsqlite3_malloc64(tdsqlite3_uint64 n){
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
-#endif
- return sqlite3Malloc(n);
-}
-
-/*
-** Each thread may only have a single outstanding allocation from
-** xScratchMalloc(). We verify this constraint in the single-threaded
-** case by setting scratchAllocOut to 1 when an allocation
-** is outstanding clearing it when the allocation is freed.
-*/
-#if SQLITE_THREADSAFE==0 && !defined(NDEBUG)
-static int scratchAllocOut = 0;
-#endif
-
-
-/*
-** Allocate memory that is to be used and released right away.
-** This routine is similar to alloca() in that it is not intended
-** for situations where the memory might be held long-term. This
-** routine is intended to get memory to old large transient data
-** structures that would not normally fit on the stack of an
-** embedded processor.
-*/
-SQLITE_PRIVATE void *sqlite3ScratchMalloc(int n){
- void *p;
- assert( n>0 );
-
- sqlite3_mutex_enter(mem0.mutex);
- sqlite3StatusHighwater(SQLITE_STATUS_SCRATCH_SIZE, n);
- if( mem0.nScratchFree && sqlite3GlobalConfig.szScratch>=n ){
- p = mem0.pScratchFree;
- mem0.pScratchFree = mem0.pScratchFree->pNext;
- mem0.nScratchFree--;
- sqlite3StatusUp(SQLITE_STATUS_SCRATCH_USED, 1);
- sqlite3_mutex_leave(mem0.mutex);
- }else{
- sqlite3_mutex_leave(mem0.mutex);
- p = sqlite3Malloc(n);
- if( sqlite3GlobalConfig.bMemstat && p ){
- sqlite3_mutex_enter(mem0.mutex);
- sqlite3StatusUp(SQLITE_STATUS_SCRATCH_OVERFLOW, sqlite3MallocSize(p));
- sqlite3_mutex_leave(mem0.mutex);
- }
- sqlite3MemdebugSetType(p, MEMTYPE_SCRATCH);
- }
- assert( sqlite3_mutex_notheld(mem0.mutex) );
-
-
-#if SQLITE_THREADSAFE==0 && !defined(NDEBUG)
- /* EVIDENCE-OF: R-12970-05880 SQLite will not use more than one scratch
- ** buffers per thread.
- **
- ** This can only be checked in single-threaded mode.
- */
- assert( scratchAllocOut==0 );
- if( p ) scratchAllocOut++;
+ if( tdsqlite3_initialize() ) return 0;
#endif
-
- return p;
-}
-SQLITE_PRIVATE void sqlite3ScratchFree(void *p){
- if( p ){
-
-#if SQLITE_THREADSAFE==0 && !defined(NDEBUG)
- /* Verify that no more than two scratch allocation per thread
- ** is outstanding at one time. (This is only checked in the
- ** single-threaded case since checking in the multi-threaded case
- ** would be much more complicated.) */
- assert( scratchAllocOut>=1 && scratchAllocOut<=2 );
- scratchAllocOut--;
-#endif
-
- if( SQLITE_WITHIN(p, sqlite3GlobalConfig.pScratch, mem0.pScratchEnd) ){
- /* Release memory from the SQLITE_CONFIG_SCRATCH allocation */
- ScratchFreeslot *pSlot;
- pSlot = (ScratchFreeslot*)p;
- sqlite3_mutex_enter(mem0.mutex);
- pSlot->pNext = mem0.pScratchFree;
- mem0.pScratchFree = pSlot;
- mem0.nScratchFree++;
- assert( mem0.nScratchFree <= (u32)sqlite3GlobalConfig.nScratch );
- sqlite3StatusDown(SQLITE_STATUS_SCRATCH_USED, 1);
- sqlite3_mutex_leave(mem0.mutex);
- }else{
- /* Release memory back to the heap */
- assert( sqlite3MemdebugHasType(p, MEMTYPE_SCRATCH) );
- assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_SCRATCH) );
- sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
- if( sqlite3GlobalConfig.bMemstat ){
- int iSize = sqlite3MallocSize(p);
- sqlite3_mutex_enter(mem0.mutex);
- sqlite3StatusDown(SQLITE_STATUS_SCRATCH_OVERFLOW, iSize);
- sqlite3StatusDown(SQLITE_STATUS_MEMORY_USED, iSize);
- sqlite3StatusDown(SQLITE_STATUS_MALLOC_COUNT, 1);
- sqlite3GlobalConfig.m.xFree(p);
- sqlite3_mutex_leave(mem0.mutex);
- }else{
- sqlite3GlobalConfig.m.xFree(p);
- }
- }
- }
+ return tdsqlite3Malloc(n);
}
/*
** TRUE if p is a lookaside memory allocation from db
*/
#ifndef SQLITE_OMIT_LOOKASIDE
-static int isLookaside(sqlite3 *db, void *p){
+static int isLookaside(tdsqlite3 *db, void *p){
return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd);
}
#else
@@ -28047,51 +31754,69 @@ static int isLookaside(sqlite3 *db, void *p){
/*
** Return the size of a memory allocation previously obtained from
-** sqlite3Malloc() or sqlite3_malloc().
+** tdsqlite3Malloc() or tdsqlite3_malloc().
*/
-SQLITE_PRIVATE int sqlite3MallocSize(void *p){
- assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
- return sqlite3GlobalConfig.m.xSize(p);
+SQLITE_PRIVATE int tdsqlite3MallocSize(void *p){
+ assert( tdsqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
+ return tdsqlite3GlobalConfig.m.xSize(p);
+}
+static int lookasideMallocSize(tdsqlite3 *db, void *p){
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ return p<db->lookaside.pMiddle ? db->lookaside.szTrue : LOOKASIDE_SMALL;
+#else
+ return db->lookaside.szTrue;
+#endif
}
-SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){
+SQLITE_PRIVATE int tdsqlite3DbMallocSize(tdsqlite3 *db, void *p){
assert( p!=0 );
+#ifdef SQLITE_DEBUG
if( db==0 || !isLookaside(db,p) ){
-#if SQLITE_DEBUG
if( db==0 ){
- assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
- assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
+ assert( tdsqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
+ assert( tdsqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
}else{
- assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
- assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
+ assert( tdsqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
+ assert( tdsqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
}
+ }
#endif
- return sqlite3GlobalConfig.m.xSize(p);
- }else{
- assert( sqlite3_mutex_held(db->mutex) );
- return db->lookaside.sz;
+ if( db ){
+ if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){
+ assert( tdsqlite3_mutex_held(db->mutex) );
+ return LOOKASIDE_SMALL;
+ }
+#endif
+ if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){
+ assert( tdsqlite3_mutex_held(db->mutex) );
+ return db->lookaside.szTrue;
+ }
+ }
}
+ return tdsqlite3GlobalConfig.m.xSize(p);
}
-SQLITE_API sqlite3_uint64 sqlite3_msize(void *p){
- assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
- assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
- return p ? sqlite3GlobalConfig.m.xSize(p) : 0;
+SQLITE_API tdsqlite3_uint64 tdsqlite3_msize(void *p){
+ assert( tdsqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
+ assert( tdsqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
+ return p ? tdsqlite3GlobalConfig.m.xSize(p) : 0;
}
/*
-** Free memory previously obtained from sqlite3Malloc().
+** Free memory previously obtained from tdsqlite3Malloc().
*/
-SQLITE_API void sqlite3_free(void *p){
+SQLITE_API void tdsqlite3_free(void *p){
if( p==0 ) return; /* IMP: R-49053-54554 */
- assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
- assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
- if( sqlite3GlobalConfig.bMemstat ){
- sqlite3_mutex_enter(mem0.mutex);
- sqlite3StatusDown(SQLITE_STATUS_MEMORY_USED, sqlite3MallocSize(p));
- sqlite3StatusDown(SQLITE_STATUS_MALLOC_COUNT, 1);
- sqlite3GlobalConfig.m.xFree(p);
- sqlite3_mutex_leave(mem0.mutex);
+ assert( tdsqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
+ assert( tdsqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
+ if( tdsqlite3GlobalConfig.bMemstat ){
+ tdsqlite3_mutex_enter(mem0.mutex);
+ tdsqlite3StatusDown(SQLITE_STATUS_MEMORY_USED, tdsqlite3MallocSize(p));
+ tdsqlite3StatusDown(SQLITE_STATUS_MALLOC_COUNT, 1);
+ tdsqlite3GlobalConfig.m.xFree(p);
+ tdsqlite3_mutex_leave(mem0.mutex);
}else{
- sqlite3GlobalConfig.m.xFree(p);
+ tdsqlite3GlobalConfig.m.xFree(p);
}
}
@@ -28099,116 +31824,132 @@ SQLITE_API void sqlite3_free(void *p){
** Add the size of memory allocation "p" to the count in
** *db->pnBytesFreed.
*/
-static SQLITE_NOINLINE void measureAllocationSize(sqlite3 *db, void *p){
- *db->pnBytesFreed += sqlite3DbMallocSize(db,p);
+static SQLITE_NOINLINE void measureAllocationSize(tdsqlite3 *db, void *p){
+ *db->pnBytesFreed += tdsqlite3DbMallocSize(db,p);
}
/*
** Free memory that might be associated with a particular database
-** connection.
+** connection. Calling tdsqlite3DbFree(D,X) for X==0 is a harmless no-op.
+** The tdsqlite3DbFreeNN(D,X) version requires that X be non-NULL.
*/
-SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){
- assert( db==0 || sqlite3_mutex_held(db->mutex) );
- if( p==0 ) return;
+SQLITE_PRIVATE void tdsqlite3DbFreeNN(tdsqlite3 *db, void *p){
+ assert( db==0 || tdsqlite3_mutex_held(db->mutex) );
+ assert( p!=0 );
if( db ){
if( db->pnBytesFreed ){
measureAllocationSize(db, p);
return;
}
- if( isLookaside(db, p) ){
- LookasideSlot *pBuf = (LookasideSlot*)p;
-#if SQLITE_DEBUG
- /* Trash all content in the buffer being freed */
- memset(p, 0xaa, db->lookaside.sz);
+ if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){
+ LookasideSlot *pBuf = (LookasideSlot*)p;
+#ifdef SQLITE_DEBUG
+ memset(p, 0xaa, LOOKASIDE_SMALL); /* Trash freed content */
#endif
- pBuf->pNext = db->lookaside.pFree;
- db->lookaside.pFree = pBuf;
- db->lookaside.nOut--;
- return;
+ pBuf->pNext = db->lookaside.pSmallFree;
+ db->lookaside.pSmallFree = pBuf;
+ return;
+ }
+#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
+ if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){
+ LookasideSlot *pBuf = (LookasideSlot*)p;
+#ifdef SQLITE_DEBUG
+ memset(p, 0xaa, db->lookaside.szTrue); /* Trash freed content */
+#endif
+ pBuf->pNext = db->lookaside.pFree;
+ db->lookaside.pFree = pBuf;
+ return;
+ }
}
}
- assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
- assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
- assert( db!=0 || sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) );
- sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
- sqlite3_free(p);
+ assert( tdsqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
+ assert( tdsqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
+ assert( db!=0 || tdsqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) );
+ tdsqlite3MemdebugSetType(p, MEMTYPE_HEAP);
+ tdsqlite3_free(p);
+}
+SQLITE_PRIVATE void tdsqlite3DbFree(tdsqlite3 *db, void *p){
+ assert( db==0 || tdsqlite3_mutex_held(db->mutex) );
+ if( p ) tdsqlite3DbFreeNN(db, p);
}
/*
** Change the size of an existing memory allocation
*/
-SQLITE_PRIVATE void *sqlite3Realloc(void *pOld, u64 nBytes){
+SQLITE_PRIVATE void *tdsqlite3Realloc(void *pOld, u64 nBytes){
int nOld, nNew, nDiff;
void *pNew;
- assert( sqlite3MemdebugHasType(pOld, MEMTYPE_HEAP) );
- assert( sqlite3MemdebugNoType(pOld, (u8)~MEMTYPE_HEAP) );
+ assert( tdsqlite3MemdebugHasType(pOld, MEMTYPE_HEAP) );
+ assert( tdsqlite3MemdebugNoType(pOld, (u8)~MEMTYPE_HEAP) );
if( pOld==0 ){
- return sqlite3Malloc(nBytes); /* IMP: R-04300-56712 */
+ return tdsqlite3Malloc(nBytes); /* IMP: R-04300-56712 */
}
if( nBytes==0 ){
- sqlite3_free(pOld); /* IMP: R-26507-47431 */
+ tdsqlite3_free(pOld); /* IMP: R-26507-47431 */
return 0;
}
if( nBytes>=0x7fffff00 ){
- /* The 0x7ffff00 limit term is explained in comments on sqlite3Malloc() */
+ /* The 0x7ffff00 limit term is explained in comments on tdsqlite3Malloc() */
return 0;
}
- nOld = sqlite3MallocSize(pOld);
+ nOld = tdsqlite3MallocSize(pOld);
/* IMPLEMENTATION-OF: R-46199-30249 SQLite guarantees that the second
** argument to xRealloc is always a value returned by a prior call to
** xRoundup. */
- nNew = sqlite3GlobalConfig.m.xRoundup((int)nBytes);
+ nNew = tdsqlite3GlobalConfig.m.xRoundup((int)nBytes);
if( nOld==nNew ){
pNew = pOld;
- }else if( sqlite3GlobalConfig.bMemstat ){
- sqlite3_mutex_enter(mem0.mutex);
- sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes);
+ }else if( tdsqlite3GlobalConfig.bMemstat ){
+ tdsqlite3_mutex_enter(mem0.mutex);
+ tdsqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes);
nDiff = nNew - nOld;
- if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >=
+ if( nDiff>0 && tdsqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >=
mem0.alarmThreshold-nDiff ){
- sqlite3MallocAlarm(nDiff);
+ tdsqlite3MallocAlarm(nDiff);
}
- pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew);
+ pNew = tdsqlite3GlobalConfig.m.xRealloc(pOld, nNew);
if( pNew==0 && mem0.alarmThreshold>0 ){
- sqlite3MallocAlarm((int)nBytes);
- pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew);
+ tdsqlite3MallocAlarm((int)nBytes);
+ pNew = tdsqlite3GlobalConfig.m.xRealloc(pOld, nNew);
}
if( pNew ){
- nNew = sqlite3MallocSize(pNew);
- sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nNew-nOld);
+ nNew = tdsqlite3MallocSize(pNew);
+ tdsqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nNew-nOld);
}
- sqlite3_mutex_leave(mem0.mutex);
+ tdsqlite3_mutex_leave(mem0.mutex);
}else{
- pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew);
+ pNew = tdsqlite3GlobalConfig.m.xRealloc(pOld, nNew);
}
assert( EIGHT_BYTE_ALIGNMENT(pNew) ); /* IMP: R-11148-40995 */
return pNew;
}
/*
-** The public interface to sqlite3Realloc. Make sure that the memory
+** The public interface to tdsqlite3Realloc. Make sure that the memory
** subsystem is initialized prior to invoking sqliteRealloc.
*/
-SQLITE_API void *sqlite3_realloc(void *pOld, int n){
+SQLITE_API void *tdsqlite3_realloc(void *pOld, int n){
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
if( n<0 ) n = 0; /* IMP: R-26507-47431 */
- return sqlite3Realloc(pOld, n);
+ return tdsqlite3Realloc(pOld, n);
}
-SQLITE_API void *sqlite3_realloc64(void *pOld, sqlite3_uint64 n){
+SQLITE_API void *tdsqlite3_realloc64(void *pOld, tdsqlite3_uint64 n){
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
- return sqlite3Realloc(pOld, n);
+ return tdsqlite3Realloc(pOld, n);
}
/*
** Allocate and zero memory.
*/
-SQLITE_PRIVATE void *sqlite3MallocZero(u64 n){
- void *p = sqlite3Malloc(n);
+SQLITE_PRIVATE void *tdsqlite3MallocZero(u64 n){
+ void *p = tdsqlite3Malloc(n);
if( p ){
memset(p, 0, (size_t)n);
}
@@ -28219,24 +31960,24 @@ SQLITE_PRIVATE void *sqlite3MallocZero(u64 n){
** Allocate and zero memory. If the allocation fails, make
** the mallocFailed flag in the connection pointer.
*/
-SQLITE_PRIVATE void *sqlite3DbMallocZero(sqlite3 *db, u64 n){
+SQLITE_PRIVATE void *tdsqlite3DbMallocZero(tdsqlite3 *db, u64 n){
void *p;
testcase( db==0 );
- p = sqlite3DbMallocRaw(db, n);
+ p = tdsqlite3DbMallocRaw(db, n);
if( p ) memset(p, 0, (size_t)n);
return p;
}
-/* Finish the work of sqlite3DbMallocRawNN for the unusual and
+/* Finish the work of tdsqlite3DbMallocRawNN for the unusual and
** slower case when the allocation cannot be fulfilled using lookaside.
*/
-static SQLITE_NOINLINE void *dbMallocRawFinish(sqlite3 *db, u64 n){
+static SQLITE_NOINLINE void *dbMallocRawFinish(tdsqlite3 *db, u64 n){
void *p;
assert( db!=0 );
- p = sqlite3Malloc(n);
- if( !p ) sqlite3OomFault(db);
- sqlite3MemdebugSetType(p,
+ p = tdsqlite3Malloc(n);
+ if( !p ) tdsqlite3OomFault(db);
+ tdsqlite3MemdebugSetType(p,
(db->lookaside.bDisable==0) ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP);
return p;
}
@@ -28253,50 +31994,64 @@ static SQLITE_NOINLINE void *dbMallocRawFinish(sqlite3 *db, u64 n){
** This is an important assumption. There are many places in the
** code that do things like this:
**
-** int *a = (int*)sqlite3DbMallocRaw(db, 100);
-** int *b = (int*)sqlite3DbMallocRaw(db, 200);
+** int *a = (int*)tdsqlite3DbMallocRaw(db, 100);
+** int *b = (int*)tdsqlite3DbMallocRaw(db, 200);
** if( b ) a[10] = 9;
**
** In other words, if a subsequent malloc (ex: "b") worked, it is assumed
** that all prior mallocs (ex: "a") worked too.
**
-** The sqlite3MallocRawNN() variant guarantees that the "db" parameter is
+** The tdsqlite3MallocRawNN() variant guarantees that the "db" parameter is
** not a NULL pointer.
*/
-SQLITE_PRIVATE void *sqlite3DbMallocRaw(sqlite3 *db, u64 n){
+SQLITE_PRIVATE void *tdsqlite3DbMallocRaw(tdsqlite3 *db, u64 n){
void *p;
- if( db ) return sqlite3DbMallocRawNN(db, n);
- p = sqlite3Malloc(n);
- sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
+ if( db ) return tdsqlite3DbMallocRawNN(db, n);
+ p = tdsqlite3Malloc(n);
+ tdsqlite3MemdebugSetType(p, MEMTYPE_HEAP);
return p;
}
-SQLITE_PRIVATE void *sqlite3DbMallocRawNN(sqlite3 *db, u64 n){
+SQLITE_PRIVATE void *tdsqlite3DbMallocRawNN(tdsqlite3 *db, u64 n){
#ifndef SQLITE_OMIT_LOOKASIDE
LookasideSlot *pBuf;
assert( db!=0 );
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
assert( db->pnBytesFreed==0 );
- if( db->lookaside.bDisable==0 ){
- assert( db->mallocFailed==0 );
- if( n>db->lookaside.sz ){
- db->lookaside.anStat[1]++;
- }else if( (pBuf = db->lookaside.pFree)==0 ){
- db->lookaside.anStat[2]++;
- }else{
- db->lookaside.pFree = pBuf->pNext;
- db->lookaside.nOut++;
+ if( n>db->lookaside.sz ){
+ if( !db->lookaside.bDisable ){
+ db->lookaside.anStat[1]++;
+ }else if( db->mallocFailed ){
+ return 0;
+ }
+ return dbMallocRawFinish(db, n);
+ }
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ if( n<=LOOKASIDE_SMALL ){
+ if( (pBuf = db->lookaside.pSmallFree)!=0 ){
+ db->lookaside.pSmallFree = pBuf->pNext;
+ db->lookaside.anStat[0]++;
+ return (void*)pBuf;
+ }else if( (pBuf = db->lookaside.pSmallInit)!=0 ){
+ db->lookaside.pSmallInit = pBuf->pNext;
db->lookaside.anStat[0]++;
- if( db->lookaside.nOut>db->lookaside.mxOut ){
- db->lookaside.mxOut = db->lookaside.nOut;
- }
return (void*)pBuf;
}
- }else if( db->mallocFailed ){
- return 0;
+ }
+#endif
+ if( (pBuf = db->lookaside.pFree)!=0 ){
+ db->lookaside.pFree = pBuf->pNext;
+ db->lookaside.anStat[0]++;
+ return (void*)pBuf;
+ }else if( (pBuf = db->lookaside.pInit)!=0 ){
+ db->lookaside.pInit = pBuf->pNext;
+ db->lookaside.anStat[0]++;
+ return (void*)pBuf;
+ }else{
+ db->lookaside.anStat[2]++;
}
#else
assert( db!=0 );
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
assert( db->pnBytesFreed==0 );
if( db->mallocFailed ){
return 0;
@@ -28306,39 +32061,48 @@ SQLITE_PRIVATE void *sqlite3DbMallocRawNN(sqlite3 *db, u64 n){
}
/* Forward declaration */
-static SQLITE_NOINLINE void *dbReallocFinish(sqlite3 *db, void *p, u64 n);
+static SQLITE_NOINLINE void *dbReallocFinish(tdsqlite3 *db, void *p, u64 n);
/*
** Resize the block of memory pointed to by p to n bytes. If the
** resize fails, set the mallocFailed flag in the connection object.
*/
-SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *db, void *p, u64 n){
+SQLITE_PRIVATE void *tdsqlite3DbRealloc(tdsqlite3 *db, void *p, u64 n){
assert( db!=0 );
- if( p==0 ) return sqlite3DbMallocRawNN(db, n);
- assert( sqlite3_mutex_held(db->mutex) );
- if( isLookaside(db,p) && n<=db->lookaside.sz ) return p;
+ if( p==0 ) return tdsqlite3DbMallocRawNN(db, n);
+ assert( tdsqlite3_mutex_held(db->mutex) );
+ if( ((uptr)p)<(uptr)db->lookaside.pEnd ){
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ if( ((uptr)p)>=(uptr)db->lookaside.pMiddle ){
+ if( n<=LOOKASIDE_SMALL ) return p;
+ }else
+#endif
+ if( ((uptr)p)>=(uptr)db->lookaside.pStart ){
+ if( n<=db->lookaside.szTrue ) return p;
+ }
+ }
return dbReallocFinish(db, p, n);
}
-static SQLITE_NOINLINE void *dbReallocFinish(sqlite3 *db, void *p, u64 n){
+static SQLITE_NOINLINE void *dbReallocFinish(tdsqlite3 *db, void *p, u64 n){
void *pNew = 0;
assert( db!=0 );
assert( p!=0 );
if( db->mallocFailed==0 ){
if( isLookaside(db, p) ){
- pNew = sqlite3DbMallocRawNN(db, n);
+ pNew = tdsqlite3DbMallocRawNN(db, n);
if( pNew ){
- memcpy(pNew, p, db->lookaside.sz);
- sqlite3DbFree(db, p);
+ memcpy(pNew, p, lookasideMallocSize(db, p));
+ tdsqlite3DbFree(db, p);
}
}else{
- assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
- assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
- sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
- pNew = sqlite3_realloc64(p, n);
+ assert( tdsqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
+ assert( tdsqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) );
+ tdsqlite3MemdebugSetType(p, MEMTYPE_HEAP);
+ pNew = tdsqlite3_realloc64(p, n);
if( !pNew ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
}
- sqlite3MemdebugSetType(pNew,
+ tdsqlite3MemdebugSetType(pNew,
(db->lookaside.bDisable==0 ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP));
}
}
@@ -28349,44 +32113,43 @@ static SQLITE_NOINLINE void *dbReallocFinish(sqlite3 *db, void *p, u64 n){
** Attempt to reallocate p. If the reallocation fails, then free p
** and set the mallocFailed flag in the database connection.
*/
-SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *db, void *p, u64 n){
+SQLITE_PRIVATE void *tdsqlite3DbReallocOrFree(tdsqlite3 *db, void *p, u64 n){
void *pNew;
- pNew = sqlite3DbRealloc(db, p, n);
+ pNew = tdsqlite3DbRealloc(db, p, n);
if( !pNew ){
- sqlite3DbFree(db, p);
+ tdsqlite3DbFree(db, p);
}
return pNew;
}
/*
** Make a copy of a string in memory obtained from sqliteMalloc(). These
-** functions call sqlite3MallocRaw() directly instead of sqliteMalloc(). This
+** functions call tdsqlite3MallocRaw() directly instead of sqliteMalloc(). This
** is because when memory debugging is turned on, these two functions are
** called via macros that record the current file and line number in the
** ThreadData structure.
*/
-SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3 *db, const char *z){
+SQLITE_PRIVATE char *tdsqlite3DbStrDup(tdsqlite3 *db, const char *z){
char *zNew;
size_t n;
if( z==0 ){
return 0;
}
- n = sqlite3Strlen30(z) + 1;
- assert( (n&0x7fffffff)==n );
- zNew = sqlite3DbMallocRaw(db, (int)n);
+ n = strlen(z) + 1;
+ zNew = tdsqlite3DbMallocRaw(db, n);
if( zNew ){
memcpy(zNew, z, n);
}
return zNew;
}
-SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){
+SQLITE_PRIVATE char *tdsqlite3DbStrNDup(tdsqlite3 *db, const char *z, u64 n){
char *zNew;
assert( db!=0 );
if( z==0 ){
return 0;
}
assert( (n&0x7fffffff)==n );
- zNew = sqlite3DbMallocRawNN(db, n+1);
+ zNew = tdsqlite3DbMallocRawNN(db, n+1);
if( zNew ){
memcpy(zNew, z, (size_t)n);
zNew[n] = 0;
@@ -28395,11 +32158,24 @@ SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){
}
/*
+** The text between zStart and zEnd represents a phrase within a larger
+** SQL statement. Make a copy of this phrase in space obtained form
+** tdsqlite3DbMalloc(). Omit leading and trailing whitespace.
+*/
+SQLITE_PRIVATE char *tdsqlite3DbSpanDup(tdsqlite3 *db, const char *zStart, const char *zEnd){
+ int n;
+ while( tdsqlite3Isspace(zStart[0]) ) zStart++;
+ n = (int)(zEnd - zStart);
+ while( ALWAYS(n>0) && tdsqlite3Isspace(zStart[n-1]) ) n--;
+ return tdsqlite3DbStrNDup(db, zStart, n);
+}
+
+/*
** Free any prior content in *pz and replace it with a copy of zNew.
*/
-SQLITE_PRIVATE void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){
- sqlite3DbFree(db, *pz);
- *pz = sqlite3DbStrDup(db, zNew);
+SQLITE_PRIVATE void tdsqlite3SetString(char **pz, tdsqlite3 *db, const char *zNew){
+ tdsqlite3DbFree(db, *pz);
+ *pz = tdsqlite3DbStrDup(db, zNew);
}
/*
@@ -28408,13 +32184,16 @@ SQLITE_PRIVATE void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){
** temporarily disable the lookaside memory allocator and interrupt
** any running VDBEs.
*/
-SQLITE_PRIVATE void sqlite3OomFault(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3OomFault(tdsqlite3 *db){
if( db->mallocFailed==0 && db->bBenignMalloc==0 ){
db->mallocFailed = 1;
if( db->nVdbeExec>0 ){
db->u1.isInterrupted = 1;
}
- db->lookaside.bDisable++;
+ DisableLookaside;
+ if( db->pParse ){
+ db->pParse->rc = SQLITE_NOMEM_BKPT;
+ }
}
}
@@ -28425,43 +32204,43 @@ SQLITE_PRIVATE void sqlite3OomFault(sqlite3 *db){
** The memory allocator is not restarted if there are running
** VDBEs.
*/
-SQLITE_PRIVATE void sqlite3OomClear(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3OomClear(tdsqlite3 *db){
if( db->mallocFailed && db->nVdbeExec==0 ){
db->mallocFailed = 0;
db->u1.isInterrupted = 0;
assert( db->lookaside.bDisable>0 );
- db->lookaside.bDisable--;
+ EnableLookaside;
}
}
/*
** Take actions at the end of an API call to indicate an OOM error
*/
-static SQLITE_NOINLINE int apiOomError(sqlite3 *db){
- sqlite3OomClear(db);
- sqlite3Error(db, SQLITE_NOMEM);
+static SQLITE_NOINLINE int apiOomError(tdsqlite3 *db){
+ tdsqlite3OomClear(db);
+ tdsqlite3Error(db, SQLITE_NOMEM);
return SQLITE_NOMEM_BKPT;
}
/*
** This function must be called before exiting any API function (i.e.
-** returning control to the user) that has called sqlite3_malloc or
-** sqlite3_realloc.
+** returning control to the user) that has called tdsqlite3_malloc or
+** tdsqlite3_realloc.
**
** The returned value is normally a copy of the second argument to this
** function. However, if a malloc() failure has occurred since the previous
** invocation SQLITE_NOMEM is returned instead.
**
** If an OOM as occurred, then the connection error-code (the value
-** returned by sqlite3_errcode()) is set to SQLITE_NOMEM.
+** returned by tdsqlite3_errcode()) is set to SQLITE_NOMEM.
*/
-SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){
+SQLITE_PRIVATE int tdsqlite3ApiExit(tdsqlite3* db, int rc){
/* If the db handle must hold the connection handle mutex here.
** Otherwise the read (and possible write) of db->mallocFailed
- ** is unsafe, as is the call to sqlite3Error().
+ ** is unsafe, as is the call to tdsqlite3Error().
*/
assert( db!=0 );
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
if( db->mallocFailed || rc==SQLITE_IOERR_NOMEM ){
return apiOomError(db);
}
@@ -28487,7 +32266,7 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){
** Conversion types fall into various categories as defined by the
** following enumeration.
*/
-#define etRADIX 0 /* Integer types. %d, %x, %o, and so forth */
+#define etRADIX 0 /* non-decimal integer types. %x %o */
#define etFLOAT 1 /* Floating point. %f */
#define etEXP 2 /* Exponentional notation. %e and %E */
#define etGENERIC 3 /* Floating or exponential, depending on exponent. %g */
@@ -28505,8 +32284,9 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){
#define etPOINTER 13 /* The %p conversion */
#define etSQLESCAPE3 14 /* %w -> Strings with '\"' doubled */
#define etORDINAL 15 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */
+#define etDECIMAL 16 /* %d or %u, but not %x, %o */
-#define etINVALID 16 /* Any unrecognized conversion type */
+#define etINVALID 17 /* Any unrecognized conversion type */
/*
@@ -28530,9 +32310,8 @@ typedef struct et_info { /* Information about each format field */
/*
** Allowed values for et_info.flags
*/
-#define FLAG_SIGNED 1 /* True if the value to convert is signed */
-#define FLAG_INTERN 2 /* True if for internal use only */
-#define FLAG_STRING 4 /* Allow infinity precision */
+#define FLAG_SIGNED 1 /* True if the value to convert is signed */
+#define FLAG_STRING 4 /* Allow infinite precision */
/*
@@ -28542,7 +32321,7 @@ typedef struct et_info { /* Information about each format field */
static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
static const char aPrefix[] = "-x0\000X0";
static const et_info fmtinfo[] = {
- { 'd', 10, 1, etRADIX, 0, 0 },
+ { 'd', 10, 1, etDECIMAL, 0, 0 },
{ 's', 0, 4, etSTRING, 0, 0 },
{ 'g', 0, 1, etGENERIC, 30, 0 },
{ 'z', 0, 4, etDYNSTRING, 0, 0 },
@@ -28551,7 +32330,7 @@ static const et_info fmtinfo[] = {
{ 'w', 0, 4, etSQLESCAPE3, 0, 0 },
{ 'c', 0, 0, etCHARX, 0, 0 },
{ 'o', 8, 0, etRADIX, 0, 2 },
- { 'u', 10, 0, etRADIX, 0, 0 },
+ { 'u', 10, 0, etDECIMAL, 0, 0 },
{ 'x', 16, 0, etRADIX, 16, 1 },
{ 'X', 16, 0, etRADIX, 0, 4 },
#ifndef SQLITE_OMIT_FLOATING_POINT
@@ -28560,16 +32339,21 @@ static const et_info fmtinfo[] = {
{ 'E', 0, 1, etEXP, 14, 0 },
{ 'G', 0, 1, etGENERIC, 14, 0 },
#endif
- { 'i', 10, 1, etRADIX, 0, 0 },
+ { 'i', 10, 1, etDECIMAL, 0, 0 },
{ 'n', 0, 0, etSIZE, 0, 0 },
{ '%', 0, 0, etPERCENT, 0, 0 },
{ 'p', 16, 0, etPOINTER, 0, 1 },
-/* All the rest have the FLAG_INTERN bit set and are thus for internal
-** use only */
- { 'T', 0, 2, etTOKEN, 0, 0 },
- { 'S', 0, 2, etSRCLIST, 0, 0 },
- { 'r', 10, 3, etORDINAL, 0, 0 },
+ /* All the rest are undocumented and are for internal use only */
+ { 'T', 0, 0, etTOKEN, 0, 0 },
+ { 'S', 0, 0, etSRCLIST, 0, 0 },
+ { 'r', 10, 1, etORDINAL, 0, 0 },
+};
+
+/* Floating point constants used for rounding */
+static const double arRound[] = {
+ 5.0e-01, 5.0e-02, 5.0e-03, 5.0e-04, 5.0e-05,
+ 5.0e-06, 5.0e-07, 5.0e-08, 5.0e-09, 5.0e-10,
};
/*
@@ -28607,27 +32391,50 @@ static char et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){
** Set the StrAccum object to an error mode.
*/
static void setStrAccumError(StrAccum *p, u8 eError){
- assert( eError==STRACCUM_NOMEM || eError==STRACCUM_TOOBIG );
+ assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG );
p->accError = eError;
- p->nAlloc = 0;
+ if( p->mxAlloc ) tdsqlite3_str_reset(p);
+ if( eError==SQLITE_TOOBIG ) tdsqlite3ErrorToParser(p->db, eError);
}
/*
** Extra argument values from a PrintfArguments object
*/
-static sqlite3_int64 getIntArg(PrintfArguments *p){
+static tdsqlite3_int64 getIntArg(PrintfArguments *p){
if( p->nArg<=p->nUsed ) return 0;
- return sqlite3_value_int64(p->apArg[p->nUsed++]);
+ return tdsqlite3_value_int64(p->apArg[p->nUsed++]);
}
static double getDoubleArg(PrintfArguments *p){
if( p->nArg<=p->nUsed ) return 0.0;
- return sqlite3_value_double(p->apArg[p->nUsed++]);
+ return tdsqlite3_value_double(p->apArg[p->nUsed++]);
}
static char *getTextArg(PrintfArguments *p){
if( p->nArg<=p->nUsed ) return 0;
- return (char*)sqlite3_value_text(p->apArg[p->nUsed++]);
+ return (char*)tdsqlite3_value_text(p->apArg[p->nUsed++]);
}
+/*
+** Allocate memory for a temporary buffer needed for printf rendering.
+**
+** If the requested size of the temp buffer is larger than the size
+** of the output buffer in pAccum, then cause an SQLITE_TOOBIG error.
+** Do the size check before the memory allocation to prevent rogue
+** SQL from requesting large allocations using the precision or width
+** field of the printf() function.
+*/
+static char *printfTempBuf(tdsqlite3_str *pAccum, tdsqlite3_int64 n){
+ char *z;
+ if( pAccum->accError ) return 0;
+ if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){
+ setStrAccumError(pAccum, SQLITE_TOOBIG);
+ return 0;
+ }
+ z = tdsqlite3DbMallocRaw(pAccum->db, n);
+ if( z==0 ){
+ setStrAccumError(pAccum, SQLITE_NOMEM);
+ }
+ return z;
+}
/*
** On machines with a small stack size, you can redefine the
@@ -28641,8 +32448,8 @@ static char *getTextArg(PrintfArguments *p){
/*
** Render a string given by "fmt" into the StrAccum object.
*/
-SQLITE_PRIVATE void sqlite3VXPrintf(
- StrAccum *pAccum, /* Accumulate results here */
+SQLITE_API void tdsqlite3_str_vappendf(
+ tdsqlite3_str *pAccum, /* Accumulate results here */
const char *fmt, /* Format string */
va_list ap /* arguments */
){
@@ -28653,17 +32460,15 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
int idx; /* A general purpose loop counter */
int width; /* Width of the current field */
etByte flag_leftjustify; /* True if "-" flag is present */
- etByte flag_plussign; /* True if "+" flag is present */
- etByte flag_blanksign; /* True if " " flag is present */
+ etByte flag_prefix; /* '+' or ' ' or 0 for prefix */
etByte flag_alternateform; /* True if "#" flag is present */
etByte flag_altform2; /* True if "!" flag is present */
etByte flag_zeropad; /* True if field width constant starts with zero */
- etByte flag_long; /* True if "l" flag is present */
- etByte flag_longlong; /* True if the "ll" flag is present */
+ etByte flag_long; /* 1 for the "l" flag, 2 for "ll", 0 by default */
etByte done; /* Loop termination flag */
+ etByte cThousand; /* Thousands separator for %d and %u */
etByte xtype = etINVALID; /* Conversion paradigm */
u8 bArgList; /* True for SQLITE_PRINTF_SQLFUNC */
- u8 useIntern; /* Ok to use internal conversions (ex: %T) */
char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */
sqlite_uint64 longvalue; /* Value for integer types */
LONGDOUBLE_TYPE realvalue; /* Value for real types */
@@ -28681,14 +32486,17 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */
char buf[etBUFSIZE]; /* Conversion buffer */
+ /* pAccum never starts out with an empty buffer that was obtained from
+ ** malloc(). This precondition is required by the mprintf("%z...")
+ ** optimization. */
+ assert( pAccum->nChar>0 || (pAccum->printfFlags&SQLITE_PRINTF_MALLOCED)==0 );
+
bufpt = 0;
- if( pAccum->printfFlags ){
- if( (bArgList = (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC))!=0 ){
- pArgList = va_arg(ap, PrintfArguments*);
- }
- useIntern = pAccum->printfFlags & SQLITE_PRINTF_INTERNAL;
+ if( (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC)!=0 ){
+ pArgList = va_arg(ap, PrintfArguments*);
+ bArgList = 1;
}else{
- bArgList = useIntern = 0;
+ bArgList = 0;
}
for(; (c=(*fmt))!=0; ++fmt){
if( c!='%' ){
@@ -28698,113 +32506,124 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
#else
do{ fmt++; }while( *fmt && *fmt != '%' );
#endif
- sqlite3StrAccumAppend(pAccum, bufpt, (int)(fmt - bufpt));
+ tdsqlite3_str_append(pAccum, bufpt, (int)(fmt - bufpt));
if( *fmt==0 ) break;
}
if( (c=(*++fmt))==0 ){
- sqlite3StrAccumAppend(pAccum, "%", 1);
+ tdsqlite3_str_append(pAccum, "%", 1);
break;
}
/* Find out what flags are present */
- flag_leftjustify = flag_plussign = flag_blanksign =
+ flag_leftjustify = flag_prefix = cThousand =
flag_alternateform = flag_altform2 = flag_zeropad = 0;
done = 0;
+ width = 0;
+ flag_long = 0;
+ precision = -1;
do{
switch( c ){
case '-': flag_leftjustify = 1; break;
- case '+': flag_plussign = 1; break;
- case ' ': flag_blanksign = 1; break;
+ case '+': flag_prefix = '+'; break;
+ case ' ': flag_prefix = ' '; break;
case '#': flag_alternateform = 1; break;
case '!': flag_altform2 = 1; break;
case '0': flag_zeropad = 1; break;
+ case ',': cThousand = ','; break;
default: done = 1; break;
- }
- }while( !done && (c=(*++fmt))!=0 );
- /* Get the field width */
- if( c=='*' ){
- if( bArgList ){
- width = (int)getIntArg(pArgList);
- }else{
- width = va_arg(ap,int);
- }
- if( width<0 ){
- flag_leftjustify = 1;
- width = width >= -2147483647 ? -width : 0;
- }
- c = *++fmt;
- }else{
- unsigned wx = 0;
- while( c>='0' && c<='9' ){
- wx = wx*10 + c - '0';
- c = *++fmt;
- }
- testcase( wx>0x7fffffff );
- width = wx & 0x7fffffff;
- }
- assert( width>=0 );
+ case 'l': {
+ flag_long = 1;
+ c = *++fmt;
+ if( c=='l' ){
+ c = *++fmt;
+ flag_long = 2;
+ }
+ done = 1;
+ break;
+ }
+ case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9': {
+ unsigned wx = c - '0';
+ while( (c = *++fmt)>='0' && c<='9' ){
+ wx = wx*10 + c - '0';
+ }
+ testcase( wx>0x7fffffff );
+ width = wx & 0x7fffffff;
#ifdef SQLITE_PRINTF_PRECISION_LIMIT
- if( width>SQLITE_PRINTF_PRECISION_LIMIT ){
- width = SQLITE_PRINTF_PRECISION_LIMIT;
- }
+ if( width>SQLITE_PRINTF_PRECISION_LIMIT ){
+ width = SQLITE_PRINTF_PRECISION_LIMIT;
+ }
#endif
-
- /* Get the precision */
- if( c=='.' ){
- c = *++fmt;
- if( c=='*' ){
- if( bArgList ){
- precision = (int)getIntArg(pArgList);
- }else{
- precision = va_arg(ap,int);
+ if( c!='.' && c!='l' ){
+ done = 1;
+ }else{
+ fmt--;
+ }
+ break;
}
- c = *++fmt;
- if( precision<0 ){
- precision = precision >= -2147483647 ? -precision : -1;
+ case '*': {
+ if( bArgList ){
+ width = (int)getIntArg(pArgList);
+ }else{
+ width = va_arg(ap,int);
+ }
+ if( width<0 ){
+ flag_leftjustify = 1;
+ width = width >= -2147483647 ? -width : 0;
+ }
+#ifdef SQLITE_PRINTF_PRECISION_LIMIT
+ if( width>SQLITE_PRINTF_PRECISION_LIMIT ){
+ width = SQLITE_PRINTF_PRECISION_LIMIT;
+ }
+#endif
+ if( (c = fmt[1])!='.' && c!='l' ){
+ c = *++fmt;
+ done = 1;
+ }
+ break;
}
- }else{
- unsigned px = 0;
- while( c>='0' && c<='9' ){
- px = px*10 + c - '0';
+ case '.': {
c = *++fmt;
- }
- testcase( px>0x7fffffff );
- precision = px & 0x7fffffff;
- }
- }else{
- precision = -1;
- }
- assert( precision>=(-1) );
+ if( c=='*' ){
+ if( bArgList ){
+ precision = (int)getIntArg(pArgList);
+ }else{
+ precision = va_arg(ap,int);
+ }
+ if( precision<0 ){
+ precision = precision >= -2147483647 ? -precision : -1;
+ }
+ c = *++fmt;
+ }else{
+ unsigned px = 0;
+ while( c>='0' && c<='9' ){
+ px = px*10 + c - '0';
+ c = *++fmt;
+ }
+ testcase( px>0x7fffffff );
+ precision = px & 0x7fffffff;
+ }
#ifdef SQLITE_PRINTF_PRECISION_LIMIT
- if( precision>SQLITE_PRINTF_PRECISION_LIMIT ){
- precision = SQLITE_PRINTF_PRECISION_LIMIT;
- }
+ if( precision>SQLITE_PRINTF_PRECISION_LIMIT ){
+ precision = SQLITE_PRINTF_PRECISION_LIMIT;
+ }
#endif
-
-
- /* Get the conversion type modifier */
- if( c=='l' ){
- flag_long = 1;
- c = *++fmt;
- if( c=='l' ){
- flag_longlong = 1;
- c = *++fmt;
- }else{
- flag_longlong = 0;
+ if( c=='l' ){
+ --fmt;
+ }else{
+ done = 1;
+ }
+ break;
+ }
}
- }else{
- flag_long = flag_longlong = 0;
- }
+ }while( !done && (c=(*++fmt))!=0 );
+
/* Fetch the info entry for the field */
infop = &fmtinfo[0];
xtype = etINVALID;
for(idx=0; idx<ArraySize(fmtinfo); idx++){
if( c==fmtinfo[idx].fmttype ){
infop = &fmtinfo[idx];
- if( useIntern || (infop->flags & FLAG_INTERN)==0 ){
- xtype = infop->type;
- }else{
- return;
- }
+ xtype = infop->type;
break;
}
}
@@ -28814,15 +32633,11 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
**
** flag_alternateform TRUE if a '#' is present.
** flag_altform2 TRUE if a '!' is present.
- ** flag_plussign TRUE if a '+' is present.
+ ** flag_prefix '+' or ' ' or zero
** flag_leftjustify TRUE if a '-' is present or if the
** field width was negative.
** flag_zeropad TRUE if the width began with 0.
- ** flag_long TRUE if the letter 'l' (ell) prefixed
- ** the conversion character.
- ** flag_longlong TRUE if the letter 'll' (ell ell) prefixed
- ** the conversion character.
- ** flag_blanksign TRUE if a ' ' is present.
+ ** flag_long 1 for "l", 2 for "ll"
** width The specified field width. This is
** always non-negative. Zero is the default.
** precision The specified precision. The default
@@ -28832,19 +32647,24 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
*/
switch( xtype ){
case etPOINTER:
- flag_longlong = sizeof(char*)==sizeof(i64);
- flag_long = sizeof(char*)==sizeof(long int);
+ flag_long = sizeof(char*)==sizeof(i64) ? 2 :
+ sizeof(char*)==sizeof(long int) ? 1 : 0;
/* Fall through into the next case */
case etORDINAL:
- case etRADIX:
+ case etRADIX:
+ cThousand = 0;
+ /* Fall through into the next case */
+ case etDECIMAL:
if( infop->flags & FLAG_SIGNED ){
i64 v;
if( bArgList ){
v = getIntArg(pArgList);
- }else if( flag_longlong ){
- v = va_arg(ap,i64);
}else if( flag_long ){
- v = va_arg(ap,long int);
+ if( flag_long==2 ){
+ v = va_arg(ap,i64) ;
+ }else{
+ v = va_arg(ap,long int);
+ }
}else{
v = va_arg(ap,int);
}
@@ -28857,17 +32677,17 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
prefix = '-';
}else{
longvalue = v;
- if( flag_plussign ) prefix = '+';
- else if( flag_blanksign ) prefix = ' ';
- else prefix = 0;
+ prefix = flag_prefix;
}
}else{
if( bArgList ){
longvalue = (u64)getIntArg(pArgList);
- }else if( flag_longlong ){
- longvalue = va_arg(ap,u64);
}else if( flag_long ){
- longvalue = va_arg(ap,unsigned long int);
+ if( flag_long==2 ){
+ longvalue = va_arg(ap,u64);
+ }else{
+ longvalue = va_arg(ap,unsigned long int);
+ }
}else{
longvalue = va_arg(ap,unsigned int);
}
@@ -28877,16 +32697,16 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
if( flag_zeropad && precision<width-(prefix!=0) ){
precision = width-(prefix!=0);
}
- if( precision<etBUFSIZE-10 ){
+ if( precision<etBUFSIZE-10-etBUFSIZE/3 ){
nOut = etBUFSIZE;
zOut = buf;
}else{
- nOut = precision + 10;
- zOut = zExtra = sqlite3Malloc( nOut );
- if( zOut==0 ){
- setStrAccumError(pAccum, STRACCUM_NOMEM);
- return;
- }
+ u64 n;
+ n = (u64)precision + 10;
+ if( cThousand ) n += precision/3;
+ zOut = zExtra = printfTempBuf(pAccum, n);
+ if( zOut==0 ) return;
+ nOut = (int)n;
}
bufpt = &zOut[nOut-1];
if( xtype==etORDINAL ){
@@ -28907,8 +32727,23 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
}while( longvalue>0 );
}
length = (int)(&zOut[nOut-1]-bufpt);
- for(idx=precision-length; idx>0; idx--){
+ while( precision>length ){
*(--bufpt) = '0'; /* Zero pad */
+ length++;
+ }
+ if( cThousand ){
+ int nn = (length - 1)/3; /* Number of "," to insert */
+ int ix = (length - 1)%3 + 1;
+ bufpt -= nn;
+ for(idx=0; nn>0; idx++){
+ bufpt[idx] = bufpt[idx+nn];
+ ix--;
+ if( ix==0 ){
+ bufpt[++idx] = cThousand;
+ nn--;
+ ix = 3;
+ }
+ }
}
if( prefix ) *(--bufpt) = prefix; /* Add sign */
if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */
@@ -28935,17 +32770,25 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
realvalue = -realvalue;
prefix = '-';
}else{
- if( flag_plussign ) prefix = '+';
- else if( flag_blanksign ) prefix = ' ';
- else prefix = 0;
+ prefix = flag_prefix;
}
if( xtype==etGENERIC && precision>0 ) precision--;
testcase( precision>0xfff );
- for(idx=precision&0xfff, rounder=0.5; idx>0; idx--, rounder*=0.1){}
- if( xtype==etFLOAT ) realvalue += rounder;
+ idx = precision & 0xfff;
+ rounder = arRound[idx%10];
+ while( idx>=10 ){ rounder *= 1.0e-10; idx -= 10; }
+ if( xtype==etFLOAT ){
+ double rx = (double)realvalue;
+ tdsqlite3_uint64 u;
+ int ex;
+ memcpy(&u, &rx, sizeof(u));
+ ex = -1023 + (int)((u>>52)&0x7ff);
+ if( precision+(ex/3) < 15 ) rounder += realvalue*3e-16;
+ realvalue += rounder;
+ }
/* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
exp = 0;
- if( sqlite3IsNaN((double)realvalue) ){
+ if( tdsqlite3IsNaN((double)realvalue) ){
bufpt = "NaN";
length = 3;
break;
@@ -28991,12 +32834,12 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
}else{
e2 = exp;
}
- if( MAX(e2,0)+(i64)precision+(i64)width > etBUFSIZE - 15 ){
- bufpt = zExtra
- = sqlite3Malloc( MAX(e2,0)+(i64)precision+(i64)width+15 );
- if( bufpt==0 ){
- setStrAccumError(pAccum, STRACCUM_NOMEM);
- return;
+ {
+ i64 szBufNeeded; /* Size of a temporary buffer needed */
+ szBufNeeded = MAX(e2,0)+(i64)precision+(i64)width+15;
+ if( szBufNeeded > etBUFSIZE ){
+ bufpt = zExtra = printfTempBuf(pAccum, szBufNeeded);
+ if( bufpt==0 ) return;
}
}
zOut = bufpt;
@@ -29091,22 +32934,52 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
case etCHARX:
if( bArgList ){
bufpt = getTextArg(pArgList);
- c = bufpt ? bufpt[0] : 0;
+ length = 1;
+ if( bufpt ){
+ buf[0] = c = *(bufpt++);
+ if( (c&0xc0)==0xc0 ){
+ while( length<4 && (bufpt[0]&0xc0)==0x80 ){
+ buf[length++] = *(bufpt++);
+ }
+ }
+ }else{
+ buf[0] = 0;
+ }
}else{
- c = va_arg(ap,int);
+ unsigned int ch = va_arg(ap,unsigned int);
+ if( ch<0x00080 ){
+ buf[0] = ch & 0xff;
+ length = 1;
+ }else if( ch<0x00800 ){
+ buf[0] = 0xc0 + (u8)((ch>>6)&0x1f);
+ buf[1] = 0x80 + (u8)(ch & 0x3f);
+ length = 2;
+ }else if( ch<0x10000 ){
+ buf[0] = 0xe0 + (u8)((ch>>12)&0x0f);
+ buf[1] = 0x80 + (u8)((ch>>6) & 0x3f);
+ buf[2] = 0x80 + (u8)(ch & 0x3f);
+ length = 3;
+ }else{
+ buf[0] = 0xf0 + (u8)((ch>>18) & 0x07);
+ buf[1] = 0x80 + (u8)((ch>>12) & 0x3f);
+ buf[2] = 0x80 + (u8)((ch>>6) & 0x3f);
+ buf[3] = 0x80 + (u8)(ch & 0x3f);
+ length = 4;
+ }
}
if( precision>1 ){
width -= precision-1;
if( width>1 && !flag_leftjustify ){
- sqlite3AppendChar(pAccum, width-1, ' ');
+ tdsqlite3_str_appendchar(pAccum, width-1, ' ');
width = 0;
}
- sqlite3AppendChar(pAccum, precision-1, c);
+ while( precision-- > 1 ){
+ tdsqlite3_str_append(pAccum, buf, length);
+ }
}
- length = 1;
- buf[0] = c;
bufpt = buf;
- break;
+ flag_altform2 = 1;
+ goto adjust_width_for_utf8;
case etSTRING:
case etDYNSTRING:
if( bArgList ){
@@ -29118,17 +32991,50 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
if( bufpt==0 ){
bufpt = "";
}else if( xtype==etDYNSTRING ){
+ if( pAccum->nChar==0
+ && pAccum->mxAlloc
+ && width==0
+ && precision<0
+ && pAccum->accError==0
+ ){
+ /* Special optimization for tdsqlite3_mprintf("%z..."):
+ ** Extend an existing memory allocation rather than creating
+ ** a new one. */
+ assert( (pAccum->printfFlags&SQLITE_PRINTF_MALLOCED)==0 );
+ pAccum->zText = bufpt;
+ pAccum->nAlloc = tdsqlite3DbMallocSize(pAccum->db, bufpt);
+ pAccum->nChar = 0x7fffffff & (int)strlen(bufpt);
+ pAccum->printfFlags |= SQLITE_PRINTF_MALLOCED;
+ length = 0;
+ break;
+ }
zExtra = bufpt;
}
if( precision>=0 ){
- for(length=0; length<precision && bufpt[length]; length++){}
+ if( flag_altform2 ){
+ /* Set length to the number of bytes needed in order to display
+ ** precision characters */
+ unsigned char *z = (unsigned char*)bufpt;
+ while( precision-- > 0 && z[0] ){
+ SQLITE_SKIP_UTF8(z);
+ }
+ length = (int)(z - (unsigned char*)bufpt);
+ }else{
+ for(length=0; length<precision && bufpt[length]; length++){}
+ }
}else{
- length = sqlite3Strlen30(bufpt);
+ length = 0x7fffffff & (int)strlen(bufpt);
+ }
+ adjust_width_for_utf8:
+ if( flag_altform2 && width>0 ){
+ /* Adjust width to account for extra bytes in UTF-8 characters */
+ int ii = length - 1;
+ while( ii>=0 ) if( (bufpt[ii--] & 0xc0)==0x80 ) width++;
}
break;
- case etSQLESCAPE: /* Escape ' characters */
- case etSQLESCAPE2: /* Escape ' and enclose in '...' */
- case etSQLESCAPE3: { /* Escape " characters */
+ case etSQLESCAPE: /* %q: Escape ' characters */
+ case etSQLESCAPE2: /* %Q: Escape ' and enclose in '...' */
+ case etSQLESCAPE3: { /* %w: Escape " characters */
int i, j, k, n, isnull;
int needQuote;
char ch;
@@ -29142,18 +33048,23 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
}
isnull = escarg==0;
if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)");
+ /* For %q, %Q, and %w, the precision is the number of byte (or
+ ** characters if the ! flags is present) to use from the input.
+ ** Because of the extra quoting characters inserted, the number
+ ** of output characters may be larger than the precision.
+ */
k = precision;
for(i=n=0; k!=0 && (ch=escarg[i])!=0; i++, k--){
if( ch==q ) n++;
+ if( flag_altform2 && (ch&0xc0)==0xc0 ){
+ while( (escarg[i+1]&0xc0)==0x80 ){ i++; }
+ }
}
needQuote = !isnull && xtype==etSQLESCAPE2;
n += i + 3;
if( n>etBUFSIZE ){
- bufpt = zExtra = sqlite3Malloc( n );
- if( bufpt==0 ){
- setStrAccumError(pAccum, STRACCUM_NOMEM);
- return;
- }
+ bufpt = zExtra = printfTempBuf(pAccum, n);
+ if( bufpt==0 ) return;
}else{
bufpt = buf;
}
@@ -29167,31 +33078,34 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
if( needQuote ) bufpt[j++] = q;
bufpt[j] = 0;
length = j;
- /* The precision in %q and %Q means how many input characters to
- ** consume, not the length of the output...
- ** if( precision>=0 && precision<length ) length = precision; */
- break;
+ goto adjust_width_for_utf8;
}
case etTOKEN: {
- Token *pToken = va_arg(ap, Token*);
+ Token *pToken;
+ if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return;
+ pToken = va_arg(ap, Token*);
assert( bArgList==0 );
if( pToken && pToken->n ){
- sqlite3StrAccumAppend(pAccum, (const char*)pToken->z, pToken->n);
+ tdsqlite3_str_append(pAccum, (const char*)pToken->z, pToken->n);
}
length = width = 0;
break;
}
case etSRCLIST: {
- SrcList *pSrc = va_arg(ap, SrcList*);
- int k = va_arg(ap, int);
- struct SrcList_item *pItem = &pSrc->a[k];
+ SrcList *pSrc;
+ int k;
+ struct SrcList_item *pItem;
+ if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return;
+ pSrc = va_arg(ap, SrcList*);
+ k = va_arg(ap, int);
+ pItem = &pSrc->a[k];
assert( bArgList==0 );
assert( k>=0 && k<pSrc->nSrc );
if( pItem->zDatabase ){
- sqlite3StrAccumAppendAll(pAccum, pItem->zDatabase);
- sqlite3StrAccumAppend(pAccum, ".", 1);
+ tdsqlite3_str_appendall(pAccum, pItem->zDatabase);
+ tdsqlite3_str_append(pAccum, ".", 1);
}
- sqlite3StrAccumAppendAll(pAccum, pItem->zName);
+ tdsqlite3_str_appendall(pAccum, pItem->zName);
length = width = 0;
break;
}
@@ -29203,15 +33117,22 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
/*
** The text of the conversion is pointed to by "bufpt" and is
** "length" characters long. The field width is "width". Do
- ** the output.
+ ** the output. Both length and width are in bytes, not characters,
+ ** at this point. If the "!" flag was present on string conversions
+ ** indicating that width and precision should be expressed in characters,
+ ** then the values have been translated prior to reaching this point.
*/
width -= length;
- if( width>0 && !flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' ');
- sqlite3StrAccumAppend(pAccum, bufpt, length);
- if( width>0 && flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' ');
+ if( width>0 ){
+ if( !flag_leftjustify ) tdsqlite3_str_appendchar(pAccum, width, ' ');
+ tdsqlite3_str_append(pAccum, bufpt, length);
+ if( flag_leftjustify ) tdsqlite3_str_appendchar(pAccum, width, ' ');
+ }else{
+ tdsqlite3_str_append(pAccum, bufpt, length);
+ }
if( zExtra ){
- sqlite3DbFree(pAccum->db, zExtra);
+ tdsqlite3DbFree(pAccum->db, zExtra);
zExtra = 0;
}
}/* End for loop over the format string */
@@ -29224,22 +33145,20 @@ SQLITE_PRIVATE void sqlite3VXPrintf(
** Return the number of bytes of text that StrAccum is able to accept
** after the attempted enlargement. The value returned might be zero.
*/
-static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
+static int tdsqlite3StrAccumEnlarge(StrAccum *p, int N){
char *zNew;
assert( p->nChar+(i64)N >= p->nAlloc ); /* Only called if really needed */
if( p->accError ){
- testcase(p->accError==STRACCUM_TOOBIG);
- testcase(p->accError==STRACCUM_NOMEM);
+ testcase(p->accError==SQLITE_TOOBIG);
+ testcase(p->accError==SQLITE_NOMEM);
return 0;
}
if( p->mxAlloc==0 ){
- N = p->nAlloc - p->nChar - 1;
- setStrAccumError(p, STRACCUM_TOOBIG);
- return N;
+ setStrAccumError(p, SQLITE_TOOBIG);
+ return p->nAlloc - p->nChar - 1;
}else{
char *zOld = isMalloced(p) ? p->zText : 0;
i64 szNew = p->nChar;
- assert( (p->zText==0 || p->zText==p->zBase)==!isMalloced(p) );
szNew += N + 1;
if( szNew+p->nChar<=p->mxAlloc ){
/* Force exponential buffer size growth as long as it does not overflow,
@@ -29247,26 +33166,26 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
szNew += p->nChar;
}
if( szNew > p->mxAlloc ){
- sqlite3StrAccumReset(p);
- setStrAccumError(p, STRACCUM_TOOBIG);
+ tdsqlite3_str_reset(p);
+ setStrAccumError(p, SQLITE_TOOBIG);
return 0;
}else{
p->nAlloc = (int)szNew;
}
if( p->db ){
- zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc);
+ zNew = tdsqlite3DbRealloc(p->db, zOld, p->nAlloc);
}else{
- zNew = sqlite3_realloc64(zOld, p->nAlloc);
+ zNew = tdsqlite3_realloc64(zOld, p->nAlloc);
}
if( zNew ){
assert( p->zText!=0 || p->nChar==0 );
if( !isMalloced(p) && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar);
p->zText = zNew;
- p->nAlloc = sqlite3DbMallocSize(p->db, zNew);
+ p->nAlloc = tdsqlite3DbMallocSize(p->db, zNew);
p->printfFlags |= SQLITE_PRINTF_MALLOCED;
}else{
- sqlite3StrAccumReset(p);
- setStrAccumError(p, STRACCUM_NOMEM);
+ tdsqlite3_str_reset(p);
+ setStrAccumError(p, SQLITE_NOMEM);
return 0;
}
}
@@ -29276,12 +33195,11 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){
/*
** Append N copies of character c to the given string buffer.
*/
-SQLITE_PRIVATE void sqlite3AppendChar(StrAccum *p, int N, char c){
+SQLITE_API void tdsqlite3_str_appendchar(tdsqlite3_str *p, int N, char c){
testcase( p->nChar + (i64)N > 0x7fffffff );
- if( p->nChar+(i64)N >= p->nAlloc && (N = sqlite3StrAccumEnlarge(p, N))<=0 ){
+ if( p->nChar+(i64)N >= p->nAlloc && (N = tdsqlite3StrAccumEnlarge(p, N))<=0 ){
return;
}
- assert( (p->zText==p->zBase)==!isMalloced(p) );
while( (N--)>0 ) p->zText[p->nChar++] = c;
}
@@ -29289,31 +33207,30 @@ SQLITE_PRIVATE void sqlite3AppendChar(StrAccum *p, int N, char c){
** The StrAccum "p" is not large enough to accept N new bytes of z[].
** So enlarge if first, then do the append.
**
-** This is a helper routine to sqlite3StrAccumAppend() that does special-case
+** This is a helper routine to tdsqlite3_str_append() that does special-case
** work (enlarging the buffer) using tail recursion, so that the
-** sqlite3StrAccumAppend() routine can use fast calling semantics.
+** tdsqlite3_str_append() routine can use fast calling semantics.
*/
static void SQLITE_NOINLINE enlargeAndAppend(StrAccum *p, const char *z, int N){
- N = sqlite3StrAccumEnlarge(p, N);
+ N = tdsqlite3StrAccumEnlarge(p, N);
if( N>0 ){
memcpy(&p->zText[p->nChar], z, N);
p->nChar += N;
}
- assert( (p->zText==0 || p->zText==p->zBase)==!isMalloced(p) );
}
/*
** Append N bytes of text from z to the StrAccum object. Increase the
** size of the memory allocation for StrAccum if necessary.
*/
-SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){
+SQLITE_API void tdsqlite3_str_append(tdsqlite3_str *p, const char *z, int N){
assert( z!=0 || N==0 );
assert( p->zText!=0 || p->nChar==0 || p->accError );
assert( N>=0 );
- assert( p->accError==0 || p->nAlloc==0 );
+ assert( p->accError==0 || p->nAlloc==0 || p->mxAlloc==0 );
if( p->nChar+N >= p->nAlloc ){
enlargeAndAppend(p,z,N);
- }else{
+ }else if( N ){
assert( p->zText );
p->nChar += N;
memcpy(&p->zText[p->nChar-N], z, N);
@@ -29323,8 +33240,8 @@ SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){
/*
** Append the complete text of zero-terminated string z[] to the p string.
*/
-SQLITE_PRIVATE void sqlite3StrAccumAppendAll(StrAccum *p, const char *z){
- sqlite3StrAccumAppend(p, z, sqlite3Strlen30(z));
+SQLITE_API void tdsqlite3_str_appendall(tdsqlite3_str *p, const char *z){
+ tdsqlite3_str_append(p, z, tdsqlite3Strlen30(z));
}
@@ -29333,32 +33250,79 @@ SQLITE_PRIVATE void sqlite3StrAccumAppendAll(StrAccum *p, const char *z){
** Return a pointer to the resulting string. Return a NULL
** pointer if any kind of error was encountered.
*/
-SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){
+static SQLITE_NOINLINE char *strAccumFinishRealloc(StrAccum *p){
+ char *zText;
+ assert( p->mxAlloc>0 && !isMalloced(p) );
+ zText = tdsqlite3DbMallocRaw(p->db, p->nChar+1 );
+ if( zText ){
+ memcpy(zText, p->zText, p->nChar+1);
+ p->printfFlags |= SQLITE_PRINTF_MALLOCED;
+ }else{
+ setStrAccumError(p, SQLITE_NOMEM);
+ }
+ p->zText = zText;
+ return zText;
+}
+SQLITE_PRIVATE char *tdsqlite3StrAccumFinish(StrAccum *p){
if( p->zText ){
- assert( (p->zText==p->zBase)==!isMalloced(p) );
p->zText[p->nChar] = 0;
if( p->mxAlloc>0 && !isMalloced(p) ){
- p->zText = sqlite3DbMallocRaw(p->db, p->nChar+1 );
- if( p->zText ){
- memcpy(p->zText, p->zBase, p->nChar+1);
- p->printfFlags |= SQLITE_PRINTF_MALLOCED;
- }else{
- setStrAccumError(p, STRACCUM_NOMEM);
- }
+ return strAccumFinishRealloc(p);
}
}
return p->zText;
}
/*
+** This singleton is an tdsqlite3_str object that is returned if
+** tdsqlite3_malloc() fails to provide space for a real one. This
+** tdsqlite3_str object accepts no new text and always returns
+** an SQLITE_NOMEM error.
+*/
+static tdsqlite3_str tdsqlite3OomStr = {
+ 0, 0, 0, 0, 0, SQLITE_NOMEM, 0
+};
+
+/* Finalize a string created using tdsqlite3_str_new().
+*/
+SQLITE_API char *tdsqlite3_str_finish(tdsqlite3_str *p){
+ char *z;
+ if( p!=0 && p!=&tdsqlite3OomStr ){
+ z = tdsqlite3StrAccumFinish(p);
+ tdsqlite3_free(p);
+ }else{
+ z = 0;
+ }
+ return z;
+}
+
+/* Return any error code associated with p */
+SQLITE_API int tdsqlite3_str_errcode(tdsqlite3_str *p){
+ return p ? p->accError : SQLITE_NOMEM;
+}
+
+/* Return the current length of p in bytes */
+SQLITE_API int tdsqlite3_str_length(tdsqlite3_str *p){
+ return p ? p->nChar : 0;
+}
+
+/* Return the current value for p */
+SQLITE_API char *tdsqlite3_str_value(tdsqlite3_str *p){
+ if( p==0 || p->nChar==0 ) return 0;
+ p->zText[p->nChar] = 0;
+ return p->zText;
+}
+
+/*
** Reset an StrAccum string. Reclaim all malloced memory.
*/
-SQLITE_PRIVATE void sqlite3StrAccumReset(StrAccum *p){
- assert( (p->zText==0 || p->zText==p->zBase)==!isMalloced(p) );
+SQLITE_API void tdsqlite3_str_reset(StrAccum *p){
if( isMalloced(p) ){
- sqlite3DbFree(p->db, p->zText);
+ tdsqlite3DbFree(p->db, p->zText);
p->printfFlags &= ~SQLITE_PRINTF_MALLOCED;
}
+ p->nAlloc = 0;
+ p->nChar = 0;
p->zText = 0;
}
@@ -29376,32 +33340,44 @@ SQLITE_PRIVATE void sqlite3StrAccumReset(StrAccum *p){
** mx: Maximum number of bytes to accumulate. If mx==0 then no memory
** allocations will ever occur.
*/
-SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum *p, sqlite3 *db, char *zBase, int n, int mx){
- p->zText = p->zBase = zBase;
+SQLITE_PRIVATE void tdsqlite3StrAccumInit(StrAccum *p, tdsqlite3 *db, char *zBase, int n, int mx){
+ p->zText = zBase;
p->db = db;
- p->nChar = 0;
p->nAlloc = n;
p->mxAlloc = mx;
+ p->nChar = 0;
p->accError = 0;
p->printfFlags = 0;
}
+/* Allocate and initialize a new dynamic string object */
+SQLITE_API tdsqlite3_str *tdsqlite3_str_new(tdsqlite3 *db){
+ tdsqlite3_str *p = tdsqlite3_malloc64(sizeof(*p));
+ if( p ){
+ tdsqlite3StrAccumInit(p, 0, 0, 0,
+ db ? db->aLimit[SQLITE_LIMIT_LENGTH] : SQLITE_MAX_LENGTH);
+ }else{
+ p = &tdsqlite3OomStr;
+ }
+ return p;
+}
+
/*
** Print into memory obtained from sqliteMalloc(). Use the internal
** %-conversion extensions.
*/
-SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3 *db, const char *zFormat, va_list ap){
+SQLITE_PRIVATE char *tdsqlite3VMPrintf(tdsqlite3 *db, const char *zFormat, va_list ap){
char *z;
char zBase[SQLITE_PRINT_BUF_SIZE];
StrAccum acc;
assert( db!=0 );
- sqlite3StrAccumInit(&acc, db, zBase, sizeof(zBase),
+ tdsqlite3StrAccumInit(&acc, db, zBase, sizeof(zBase),
db->aLimit[SQLITE_LIMIT_LENGTH]);
acc.printfFlags = SQLITE_PRINTF_INTERNAL;
- sqlite3VXPrintf(&acc, zFormat, ap);
- z = sqlite3StrAccumFinish(&acc);
- if( acc.accError==STRACCUM_NOMEM ){
- sqlite3OomFault(db);
+ tdsqlite3_str_vappendf(&acc, zFormat, ap);
+ z = tdsqlite3StrAccumFinish(&acc);
+ if( acc.accError==SQLITE_NOMEM ){
+ tdsqlite3OomFault(db);
}
return z;
}
@@ -29410,20 +33386,20 @@ SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3 *db, const char *zFormat, va_list a
** Print into memory obtained from sqliteMalloc(). Use the internal
** %-conversion extensions.
*/
-SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3 *db, const char *zFormat, ...){
+SQLITE_PRIVATE char *tdsqlite3MPrintf(tdsqlite3 *db, const char *zFormat, ...){
va_list ap;
char *z;
va_start(ap, zFormat);
- z = sqlite3VMPrintf(db, zFormat, ap);
+ z = tdsqlite3VMPrintf(db, zFormat, ap);
va_end(ap);
return z;
}
/*
-** Print into memory obtained from sqlite3_malloc(). Omit the internal
+** Print into memory obtained from tdsqlite3_malloc(). Omit the internal
** %-conversion extensions.
*/
-SQLITE_API char *sqlite3_vmprintf(const char *zFormat, va_list ap){
+SQLITE_API char *tdsqlite3_vmprintf(const char *zFormat, va_list ap){
char *z;
char zBase[SQLITE_PRINT_BUF_SIZE];
StrAccum acc;
@@ -29435,44 +33411,44 @@ SQLITE_API char *sqlite3_vmprintf(const char *zFormat, va_list ap){
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
- sqlite3StrAccumInit(&acc, 0, zBase, sizeof(zBase), SQLITE_MAX_LENGTH);
- sqlite3VXPrintf(&acc, zFormat, ap);
- z = sqlite3StrAccumFinish(&acc);
+ tdsqlite3StrAccumInit(&acc, 0, zBase, sizeof(zBase), SQLITE_MAX_LENGTH);
+ tdsqlite3_str_vappendf(&acc, zFormat, ap);
+ z = tdsqlite3StrAccumFinish(&acc);
return z;
}
/*
-** Print into memory obtained from sqlite3_malloc()(). Omit the internal
+** Print into memory obtained from tdsqlite3_malloc()(). Omit the internal
** %-conversion extensions.
*/
-SQLITE_API char *sqlite3_mprintf(const char *zFormat, ...){
+SQLITE_API char *tdsqlite3_mprintf(const char *zFormat, ...){
va_list ap;
char *z;
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
va_start(ap, zFormat);
- z = sqlite3_vmprintf(zFormat, ap);
+ z = tdsqlite3_vmprintf(zFormat, ap);
va_end(ap);
return z;
}
/*
-** sqlite3_snprintf() works like snprintf() except that it ignores the
+** tdsqlite3_snprintf() works like snprintf() except that it ignores the
** current locale settings. This is important for SQLite because we
** are not able to use a "," as the decimal point in place of "." as
** specified by some locales.
**
-** Oops: The first two arguments of sqlite3_snprintf() are backwards
+** Oops: The first two arguments of tdsqlite3_snprintf() are backwards
** from the snprintf() standard. Unfortunately, it is too late to change
** this without breaking compatibility, so we just have to live with the
** mistake.
**
-** sqlite3_vsnprintf() is the varargs version.
+** tdsqlite3_vsnprintf() is the varargs version.
*/
-SQLITE_API char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){
+SQLITE_API char *tdsqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){
StrAccum acc;
if( n<=0 ) return zBuf;
#ifdef SQLITE_ENABLE_API_ARMOR
@@ -29482,49 +33458,50 @@ SQLITE_API char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_li
return zBuf;
}
#endif
- sqlite3StrAccumInit(&acc, 0, zBuf, n, 0);
- sqlite3VXPrintf(&acc, zFormat, ap);
- return sqlite3StrAccumFinish(&acc);
+ tdsqlite3StrAccumInit(&acc, 0, zBuf, n, 0);
+ tdsqlite3_str_vappendf(&acc, zFormat, ap);
+ zBuf[acc.nChar] = 0;
+ return zBuf;
}
-SQLITE_API char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){
+SQLITE_API char *tdsqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){
char *z;
va_list ap;
va_start(ap,zFormat);
- z = sqlite3_vsnprintf(n, zBuf, zFormat, ap);
+ z = tdsqlite3_vsnprintf(n, zBuf, zFormat, ap);
va_end(ap);
return z;
}
/*
-** This is the routine that actually formats the sqlite3_log() message.
-** We house it in a separate routine from sqlite3_log() to avoid using
+** This is the routine that actually formats the tdsqlite3_log() message.
+** We house it in a separate routine from tdsqlite3_log() to avoid using
** stack space on small-stack systems when logging is disabled.
**
-** sqlite3_log() must render into a static buffer. It cannot dynamically
+** tdsqlite3_log() must render into a static buffer. It cannot dynamically
** allocate memory because it might be called while the memory allocator
** mutex is held.
**
-** sqlite3VXPrintf() might ask for *temporary* memory allocations for
+** tdsqlite3_str_vappendf() might ask for *temporary* memory allocations for
** certain format characters (%q) or for very large precisions or widths.
-** Care must be taken that any sqlite3_log() calls that occur while the
+** Care must be taken that any tdsqlite3_log() calls that occur while the
** memory mutex is held do not use these mechanisms.
*/
static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){
StrAccum acc; /* String accumulator */
char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */
- sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0);
- sqlite3VXPrintf(&acc, zFormat, ap);
- sqlite3GlobalConfig.xLog(sqlite3GlobalConfig.pLogArg, iErrCode,
- sqlite3StrAccumFinish(&acc));
+ tdsqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0);
+ tdsqlite3_str_vappendf(&acc, zFormat, ap);
+ tdsqlite3GlobalConfig.xLog(tdsqlite3GlobalConfig.pLogArg, iErrCode,
+ tdsqlite3StrAccumFinish(&acc));
}
/*
** Format and write a message to the log if logging is enabled.
*/
-SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...){
+SQLITE_API void tdsqlite3_log(int iErrCode, const char *zFormat, ...){
va_list ap; /* Vararg list */
- if( sqlite3GlobalConfig.xLog ){
+ if( tdsqlite3GlobalConfig.xLog ){
va_start(ap, zFormat);
renderLogMsg(iErrCode, zFormat, ap);
va_end(ap);
@@ -29537,29 +33514,36 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...){
** The printf() built into some versions of windows does not understand %lld
** and segfaults if you give it a long long int.
*/
-SQLITE_PRIVATE void sqlite3DebugPrintf(const char *zFormat, ...){
+SQLITE_PRIVATE void tdsqlite3DebugPrintf(const char *zFormat, ...){
va_list ap;
StrAccum acc;
char zBuf[500];
- sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
+ tdsqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
va_start(ap,zFormat);
- sqlite3VXPrintf(&acc, zFormat, ap);
+ tdsqlite3_str_vappendf(&acc, zFormat, ap);
va_end(ap);
- sqlite3StrAccumFinish(&acc);
+ tdsqlite3StrAccumFinish(&acc);
+#ifdef SQLITE_OS_TRACE_PROC
+ {
+ extern void SQLITE_OS_TRACE_PROC(const char *zBuf, int nBuf);
+ SQLITE_OS_TRACE_PROC(zBuf, sizeof(zBuf));
+ }
+#else
fprintf(stdout,"%s", zBuf);
fflush(stdout);
+#endif
}
#endif
/*
-** variable-argument wrapper around sqlite3VXPrintf(). The bFlags argument
+** variable-argument wrapper around tdsqlite3_str_vappendf(). The bFlags argument
** can contain the bit SQLITE_PRINTF_INTERNAL enable internal formats.
*/
-SQLITE_PRIVATE void sqlite3XPrintf(StrAccum *p, const char *zFormat, ...){
+SQLITE_API void tdsqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){
va_list ap;
va_start(ap,zFormat);
- sqlite3VXPrintf(p, zFormat, ap);
+ tdsqlite3_str_vappendf(p, zFormat, ap);
va_end(ap);
}
@@ -29591,9 +33575,9 @@ SQLITE_PRIVATE void sqlite3XPrintf(StrAccum *p, const char *zFormat, ...){
** Add a new subitem to the tree. The moreToFollow flag indicates that this
** is not the last item in the tree.
*/
-static TreeView *sqlite3TreeViewPush(TreeView *p, u8 moreToFollow){
+static TreeView *tdsqlite3TreeViewPush(TreeView *p, u8 moreToFollow){
if( p==0 ){
- p = sqlite3_malloc64( sizeof(*p) );
+ p = tdsqlite3_malloc64( sizeof(*p) );
if( p==0 ) return 0;
memset(p, 0, sizeof(*p));
}else{
@@ -29607,33 +33591,36 @@ static TreeView *sqlite3TreeViewPush(TreeView *p, u8 moreToFollow){
/*
** Finished with one layer of the tree
*/
-static void sqlite3TreeViewPop(TreeView *p){
+static void tdsqlite3TreeViewPop(TreeView *p){
if( p==0 ) return;
p->iLevel--;
- if( p->iLevel<0 ) sqlite3_free(p);
+ if( p->iLevel<0 ) tdsqlite3_free(p);
}
/*
** Generate a single line of output for the tree, with a prefix that contains
** all the appropriate tree lines
*/
-static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){
+static void tdsqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){
va_list ap;
int i;
StrAccum acc;
char zBuf[500];
- sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
+ tdsqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
if( p ){
for(i=0; i<p->iLevel && i<sizeof(p->bLine)-1; i++){
- sqlite3StrAccumAppend(&acc, p->bLine[i] ? "| " : " ", 4);
+ tdsqlite3_str_append(&acc, p->bLine[i] ? "| " : " ", 4);
}
- sqlite3StrAccumAppend(&acc, p->bLine[i] ? "|-- " : "'-- ", 4);
+ tdsqlite3_str_append(&acc, p->bLine[i] ? "|-- " : "'-- ", 4);
}
- va_start(ap, zFormat);
- sqlite3VXPrintf(&acc, zFormat, ap);
- va_end(ap);
- if( zBuf[acc.nChar-1]!='\n' ) sqlite3StrAccumAppend(&acc, "\n", 1);
- sqlite3StrAccumFinish(&acc);
+ if( zFormat!=0 ){
+ va_start(ap, zFormat);
+ tdsqlite3_str_vappendf(&acc, zFormat, ap);
+ va_end(ap);
+ assert( acc.nChar>0 || acc.accError );
+ tdsqlite3_str_append(&acc, "\n", 1);
+ }
+ tdsqlite3StrAccumFinish(&acc);
fprintf(stdout,"%s", zBuf);
fflush(stdout);
}
@@ -29641,70 +33628,120 @@ static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){
/*
** Shorthand for starting a new tree item that consists of a single label
*/
-static void sqlite3TreeViewItem(TreeView *p, const char *zLabel,u8 moreFollows){
- p = sqlite3TreeViewPush(p, moreFollows);
- sqlite3TreeViewLine(p, "%s", zLabel);
+static void tdsqlite3TreeViewItem(TreeView *p, const char *zLabel,u8 moreFollows){
+ p = tdsqlite3TreeViewPush(p, moreFollows);
+ tdsqlite3TreeViewLine(p, "%s", zLabel);
}
/*
** Generate a human-readable description of a WITH clause.
*/
-SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){
+SQLITE_PRIVATE void tdsqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 moreToFollow){
int i;
if( pWith==0 ) return;
if( pWith->nCte==0 ) return;
if( pWith->pOuter ){
- sqlite3TreeViewLine(pView, "WITH (0x%p, pOuter=0x%p)",pWith,pWith->pOuter);
+ tdsqlite3TreeViewLine(pView, "WITH (0x%p, pOuter=0x%p)",pWith,pWith->pOuter);
}else{
- sqlite3TreeViewLine(pView, "WITH (0x%p)", pWith);
+ tdsqlite3TreeViewLine(pView, "WITH (0x%p)", pWith);
}
if( pWith->nCte>0 ){
- pView = sqlite3TreeViewPush(pView, 1);
+ pView = tdsqlite3TreeViewPush(pView, 1);
for(i=0; i<pWith->nCte; i++){
StrAccum x;
char zLine[1000];
const struct Cte *pCte = &pWith->a[i];
- sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0);
- sqlite3XPrintf(&x, "%s", pCte->zName);
+ tdsqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0);
+ tdsqlite3_str_appendf(&x, "%s", pCte->zName);
if( pCte->pCols && pCte->pCols->nExpr>0 ){
char cSep = '(';
int j;
for(j=0; j<pCte->pCols->nExpr; j++){
- sqlite3XPrintf(&x, "%c%s", cSep, pCte->pCols->a[j].zName);
+ tdsqlite3_str_appendf(&x, "%c%s", cSep, pCte->pCols->a[j].zEName);
cSep = ',';
}
- sqlite3XPrintf(&x, ")");
+ tdsqlite3_str_appendf(&x, ")");
}
- sqlite3XPrintf(&x, " AS");
- sqlite3StrAccumFinish(&x);
- sqlite3TreeViewItem(pView, zLine, i<pWith->nCte-1);
- sqlite3TreeViewSelect(pView, pCte->pSelect, 0);
- sqlite3TreeViewPop(pView);
+ tdsqlite3_str_appendf(&x, " AS");
+ tdsqlite3StrAccumFinish(&x);
+ tdsqlite3TreeViewItem(pView, zLine, i<pWith->nCte-1);
+ tdsqlite3TreeViewSelect(pView, pCte->pSelect, 0);
+ tdsqlite3TreeViewPop(pView);
}
- sqlite3TreeViewPop(pView);
+ tdsqlite3TreeViewPop(pView);
}
}
+/*
+** Generate a human-readable description of a SrcList object.
+*/
+SQLITE_PRIVATE void tdsqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
+ int i;
+ for(i=0; i<pSrc->nSrc; i++){
+ const struct SrcList_item *pItem = &pSrc->a[i];
+ StrAccum x;
+ char zLine[100];
+ tdsqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0);
+ tdsqlite3_str_appendf(&x, "{%d:*}", pItem->iCursor);
+ if( pItem->zDatabase ){
+ tdsqlite3_str_appendf(&x, " %s.%s", pItem->zDatabase, pItem->zName);
+ }else if( pItem->zName ){
+ tdsqlite3_str_appendf(&x, " %s", pItem->zName);
+ }
+ if( pItem->pTab ){
+ tdsqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p",
+ pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab);
+ }
+ if( pItem->zAlias ){
+ tdsqlite3_str_appendf(&x, " (AS %s)", pItem->zAlias);
+ }
+ if( pItem->fg.jointype & JT_LEFT ){
+ tdsqlite3_str_appendf(&x, " LEFT-JOIN");
+ }
+ if( pItem->fg.fromDDL ){
+ tdsqlite3_str_appendf(&x, " DDL");
+ }
+ tdsqlite3StrAccumFinish(&x);
+ tdsqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1);
+ if( pItem->pSelect ){
+ tdsqlite3TreeViewSelect(pView, pItem->pSelect, 0);
+ }
+ if( pItem->fg.isTabFunc ){
+ tdsqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:");
+ }
+ tdsqlite3TreeViewPop(pView);
+ }
+}
/*
** Generate a human-readable description of a Select object.
*/
-SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){
+SQLITE_PRIVATE void tdsqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){
int n = 0;
int cnt = 0;
- pView = sqlite3TreeViewPush(pView, moreToFollow);
+ if( p==0 ){
+ tdsqlite3TreeViewLine(pView, "nil-SELECT");
+ return;
+ }
+ pView = tdsqlite3TreeViewPush(pView, moreToFollow);
if( p->pWith ){
- sqlite3TreeViewWith(pView, p->pWith, 1);
+ tdsqlite3TreeViewWith(pView, p->pWith, 1);
cnt = 1;
- sqlite3TreeViewPush(pView, 1);
+ tdsqlite3TreeViewPush(pView, 1);
}
do{
- sqlite3TreeViewLine(pView, "SELECT%s%s (0x%p) selFlags=0x%x nSelectRow=%d",
- ((p->selFlags & SF_Distinct) ? " DISTINCT" : ""),
- ((p->selFlags & SF_Aggregate) ? " agg_flag" : ""), p, p->selFlags,
- (int)p->nSelectRow
- );
- if( cnt++ ) sqlite3TreeViewPop(pView);
+ if( p->selFlags & SF_WhereBegin ){
+ tdsqlite3TreeViewLine(pView, "tdsqlite3WhereBegin()");
+ }else{
+ tdsqlite3TreeViewLine(pView,
+ "SELECT%s%s (%u/%p) selFlags=0x%x nSelectRow=%d",
+ ((p->selFlags & SF_Distinct) ? " DISTINCT" : ""),
+ ((p->selFlags & SF_Aggregate) ? " agg_flag" : ""),
+ p->selId, p, p->selFlags,
+ (int)p->nSelectRow
+ );
+ }
+ if( cnt++ ) tdsqlite3TreeViewPop(pView);
if( p->pPrior ){
n = 1000;
}else{
@@ -29715,70 +33752,67 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m
if( p->pHaving ) n++;
if( p->pOrderBy ) n++;
if( p->pLimit ) n++;
- if( p->pOffset ) n++;
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( p->pWin ) n++;
+ if( p->pWinDefn ) n++;
+#endif
}
- sqlite3TreeViewExprList(pView, p->pEList, (n--)>0, "result-set");
- if( p->pSrc && p->pSrc->nSrc ){
- int i;
- pView = sqlite3TreeViewPush(pView, (n--)>0);
- sqlite3TreeViewLine(pView, "FROM");
- for(i=0; i<p->pSrc->nSrc; i++){
- struct SrcList_item *pItem = &p->pSrc->a[i];
- StrAccum x;
- char zLine[100];
- sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0);
- sqlite3XPrintf(&x, "{%d,*}", pItem->iCursor);
- if( pItem->zDatabase ){
- sqlite3XPrintf(&x, " %s.%s", pItem->zDatabase, pItem->zName);
- }else if( pItem->zName ){
- sqlite3XPrintf(&x, " %s", pItem->zName);
- }
- if( pItem->pTab ){
- sqlite3XPrintf(&x, " tabname=%Q", pItem->pTab->zName);
- }
- if( pItem->zAlias ){
- sqlite3XPrintf(&x, " (AS %s)", pItem->zAlias);
- }
- if( pItem->fg.jointype & JT_LEFT ){
- sqlite3XPrintf(&x, " LEFT-JOIN");
- }
- sqlite3StrAccumFinish(&x);
- sqlite3TreeViewItem(pView, zLine, i<p->pSrc->nSrc-1);
- if( pItem->pSelect ){
- sqlite3TreeViewSelect(pView, pItem->pSelect, 0);
- }
- if( pItem->fg.isTabFunc ){
- sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:");
- }
- sqlite3TreeViewPop(pView);
+ if( p->pEList ){
+ tdsqlite3TreeViewExprList(pView, p->pEList, n>0, "result-set");
+ }
+ n--;
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( p->pWin ){
+ Window *pX;
+ pView = tdsqlite3TreeViewPush(pView, (n--)>0);
+ tdsqlite3TreeViewLine(pView, "window-functions");
+ for(pX=p->pWin; pX; pX=pX->pNextWin){
+ tdsqlite3TreeViewWinFunc(pView, pX, pX->pNextWin!=0);
}
- sqlite3TreeViewPop(pView);
+ tdsqlite3TreeViewPop(pView);
+ }
+#endif
+ if( p->pSrc && p->pSrc->nSrc ){
+ pView = tdsqlite3TreeViewPush(pView, (n--)>0);
+ tdsqlite3TreeViewLine(pView, "FROM");
+ tdsqlite3TreeViewSrcList(pView, p->pSrc);
+ tdsqlite3TreeViewPop(pView);
}
if( p->pWhere ){
- sqlite3TreeViewItem(pView, "WHERE", (n--)>0);
- sqlite3TreeViewExpr(pView, p->pWhere, 0);
- sqlite3TreeViewPop(pView);
+ tdsqlite3TreeViewItem(pView, "WHERE", (n--)>0);
+ tdsqlite3TreeViewExpr(pView, p->pWhere, 0);
+ tdsqlite3TreeViewPop(pView);
}
if( p->pGroupBy ){
- sqlite3TreeViewExprList(pView, p->pGroupBy, (n--)>0, "GROUPBY");
+ tdsqlite3TreeViewExprList(pView, p->pGroupBy, (n--)>0, "GROUPBY");
}
if( p->pHaving ){
- sqlite3TreeViewItem(pView, "HAVING", (n--)>0);
- sqlite3TreeViewExpr(pView, p->pHaving, 0);
- sqlite3TreeViewPop(pView);
+ tdsqlite3TreeViewItem(pView, "HAVING", (n--)>0);
+ tdsqlite3TreeViewExpr(pView, p->pHaving, 0);
+ tdsqlite3TreeViewPop(pView);
+ }
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( p->pWinDefn ){
+ Window *pX;
+ tdsqlite3TreeViewItem(pView, "WINDOW", (n--)>0);
+ for(pX=p->pWinDefn; pX; pX=pX->pNextWin){
+ tdsqlite3TreeViewWindow(pView, pX, pX->pNextWin!=0);
+ }
+ tdsqlite3TreeViewPop(pView);
}
+#endif
if( p->pOrderBy ){
- sqlite3TreeViewExprList(pView, p->pOrderBy, (n--)>0, "ORDERBY");
+ tdsqlite3TreeViewExprList(pView, p->pOrderBy, (n--)>0, "ORDERBY");
}
if( p->pLimit ){
- sqlite3TreeViewItem(pView, "LIMIT", (n--)>0);
- sqlite3TreeViewExpr(pView, p->pLimit, 0);
- sqlite3TreeViewPop(pView);
- }
- if( p->pOffset ){
- sqlite3TreeViewItem(pView, "OFFSET", (n--)>0);
- sqlite3TreeViewExpr(pView, p->pOffset, 0);
- sqlite3TreeViewPop(pView);
+ tdsqlite3TreeViewItem(pView, "LIMIT", (n--)>0);
+ tdsqlite3TreeViewExpr(pView, p->pLimit->pLeft, p->pLimit->pRight!=0);
+ if( p->pLimit->pRight ){
+ tdsqlite3TreeViewItem(pView, "OFFSET", (n--)>0);
+ tdsqlite3TreeViewExpr(pView, p->pLimit->pRight, 0);
+ tdsqlite3TreeViewPop(pView);
+ }
+ tdsqlite3TreeViewPop(pView);
}
if( p->pPrior ){
const char *zOp = "UNION";
@@ -29787,93 +33821,234 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m
case TK_INTERSECT: zOp = "INTERSECT"; break;
case TK_EXCEPT: zOp = "EXCEPT"; break;
}
- sqlite3TreeViewItem(pView, zOp, 1);
+ tdsqlite3TreeViewItem(pView, zOp, 1);
}
p = p->pPrior;
}while( p!=0 );
- sqlite3TreeViewPop(pView);
+ tdsqlite3TreeViewPop(pView);
+}
+
+#ifndef SQLITE_OMIT_WINDOWFUNC
+/*
+** Generate a description of starting or stopping bounds
+*/
+SQLITE_PRIVATE void tdsqlite3TreeViewBound(
+ TreeView *pView, /* View context */
+ u8 eBound, /* UNBOUNDED, CURRENT, PRECEDING, FOLLOWING */
+ Expr *pExpr, /* Value for PRECEDING or FOLLOWING */
+ u8 moreToFollow /* True if more to follow */
+){
+ switch( eBound ){
+ case TK_UNBOUNDED: {
+ tdsqlite3TreeViewItem(pView, "UNBOUNDED", moreToFollow);
+ tdsqlite3TreeViewPop(pView);
+ break;
+ }
+ case TK_CURRENT: {
+ tdsqlite3TreeViewItem(pView, "CURRENT", moreToFollow);
+ tdsqlite3TreeViewPop(pView);
+ break;
+ }
+ case TK_PRECEDING: {
+ tdsqlite3TreeViewItem(pView, "PRECEDING", moreToFollow);
+ tdsqlite3TreeViewExpr(pView, pExpr, 0);
+ tdsqlite3TreeViewPop(pView);
+ break;
+ }
+ case TK_FOLLOWING: {
+ tdsqlite3TreeViewItem(pView, "FOLLOWING", moreToFollow);
+ tdsqlite3TreeViewExpr(pView, pExpr, 0);
+ tdsqlite3TreeViewPop(pView);
+ break;
+ }
+ }
}
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+
+#ifndef SQLITE_OMIT_WINDOWFUNC
+/*
+** Generate a human-readable explanation for a Window object
+*/
+SQLITE_PRIVATE void tdsqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){
+ int nElement = 0;
+ if( pWin->pFilter ){
+ tdsqlite3TreeViewItem(pView, "FILTER", 1);
+ tdsqlite3TreeViewExpr(pView, pWin->pFilter, 0);
+ tdsqlite3TreeViewPop(pView);
+ }
+ pView = tdsqlite3TreeViewPush(pView, more);
+ if( pWin->zName ){
+ tdsqlite3TreeViewLine(pView, "OVER %s (%p)", pWin->zName, pWin);
+ }else{
+ tdsqlite3TreeViewLine(pView, "OVER (%p)", pWin);
+ }
+ if( pWin->zBase ) nElement++;
+ if( pWin->pOrderBy ) nElement++;
+ if( pWin->eFrmType ) nElement++;
+ if( pWin->eExclude ) nElement++;
+ if( pWin->zBase ){
+ tdsqlite3TreeViewPush(pView, (--nElement)>0);
+ tdsqlite3TreeViewLine(pView, "window: %s", pWin->zBase);
+ tdsqlite3TreeViewPop(pView);
+ }
+ if( pWin->pPartition ){
+ tdsqlite3TreeViewExprList(pView, pWin->pPartition, nElement>0,"PARTITION-BY");
+ }
+ if( pWin->pOrderBy ){
+ tdsqlite3TreeViewExprList(pView, pWin->pOrderBy, (--nElement)>0, "ORDER-BY");
+ }
+ if( pWin->eFrmType ){
+ char zBuf[30];
+ const char *zFrmType = "ROWS";
+ if( pWin->eFrmType==TK_RANGE ) zFrmType = "RANGE";
+ if( pWin->eFrmType==TK_GROUPS ) zFrmType = "GROUPS";
+ tdsqlite3_snprintf(sizeof(zBuf),zBuf,"%s%s",zFrmType,
+ pWin->bImplicitFrame ? " (implied)" : "");
+ tdsqlite3TreeViewItem(pView, zBuf, (--nElement)>0);
+ tdsqlite3TreeViewBound(pView, pWin->eStart, pWin->pStart, 1);
+ tdsqlite3TreeViewBound(pView, pWin->eEnd, pWin->pEnd, 0);
+ tdsqlite3TreeViewPop(pView);
+ }
+ if( pWin->eExclude ){
+ char zBuf[30];
+ const char *zExclude;
+ switch( pWin->eExclude ){
+ case TK_NO: zExclude = "NO OTHERS"; break;
+ case TK_CURRENT: zExclude = "CURRENT ROW"; break;
+ case TK_GROUP: zExclude = "GROUP"; break;
+ case TK_TIES: zExclude = "TIES"; break;
+ default:
+ tdsqlite3_snprintf(sizeof(zBuf),zBuf,"invalid(%d)", pWin->eExclude);
+ zExclude = zBuf;
+ break;
+ }
+ tdsqlite3TreeViewPush(pView, 0);
+ tdsqlite3TreeViewLine(pView, "EXCLUDE %s", zExclude);
+ tdsqlite3TreeViewPop(pView);
+ }
+ tdsqlite3TreeViewPop(pView);
+}
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+
+#ifndef SQLITE_OMIT_WINDOWFUNC
+/*
+** Generate a human-readable explanation for a Window Function object
+*/
+SQLITE_PRIVATE void tdsqlite3TreeViewWinFunc(TreeView *pView, const Window *pWin, u8 more){
+ pView = tdsqlite3TreeViewPush(pView, more);
+ tdsqlite3TreeViewLine(pView, "WINFUNC %s(%d)",
+ pWin->pFunc->zName, pWin->pFunc->nArg);
+ tdsqlite3TreeViewWindow(pView, pWin, 0);
+ tdsqlite3TreeViewPop(pView);
+}
+#endif /* SQLITE_OMIT_WINDOWFUNC */
/*
** Generate a human-readable explanation of an expression tree.
*/
-SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
+SQLITE_PRIVATE void tdsqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
const char *zBinOp = 0; /* Binary operator */
const char *zUniOp = 0; /* Unary operator */
- char zFlgs[30];
- pView = sqlite3TreeViewPush(pView, moreToFollow);
+ char zFlgs[60];
+ pView = tdsqlite3TreeViewPush(pView, moreToFollow);
if( pExpr==0 ){
- sqlite3TreeViewLine(pView, "nil");
- sqlite3TreeViewPop(pView);
+ tdsqlite3TreeViewLine(pView, "nil");
+ tdsqlite3TreeViewPop(pView);
return;
}
- if( pExpr->flags ){
- sqlite3_snprintf(sizeof(zFlgs),zFlgs," flags=0x%x",pExpr->flags);
+ if( pExpr->flags || pExpr->affExpr ){
+ StrAccum x;
+ tdsqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0);
+ tdsqlite3_str_appendf(&x, " fg.af=%x.%c",
+ pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n');
+ if( ExprHasProperty(pExpr, EP_FromJoin) ){
+ tdsqlite3_str_appendf(&x, " iRJT=%d", pExpr->iRightJoinTable);
+ }
+ if( ExprHasProperty(pExpr, EP_FromDDL) ){
+ tdsqlite3_str_appendf(&x, " DDL");
+ }
+ tdsqlite3StrAccumFinish(&x);
}else{
zFlgs[0] = 0;
}
switch( pExpr->op ){
case TK_AGG_COLUMN: {
- sqlite3TreeViewLine(pView, "AGG{%d:%d}%s",
+ tdsqlite3TreeViewLine(pView, "AGG{%d:%d}%s",
pExpr->iTable, pExpr->iColumn, zFlgs);
break;
}
case TK_COLUMN: {
if( pExpr->iTable<0 ){
/* This only happens when coding check constraints */
- sqlite3TreeViewLine(pView, "COLUMN(%d)%s", pExpr->iColumn, zFlgs);
+ char zOp2[16];
+ if( pExpr->op2 ){
+ tdsqlite3_snprintf(sizeof(zOp2),zOp2," op2=0x%02x",pExpr->op2);
+ }else{
+ zOp2[0] = 0;
+ }
+ tdsqlite3TreeViewLine(pView, "COLUMN(%d)%s%s",
+ pExpr->iColumn, zFlgs, zOp2);
}else{
- sqlite3TreeViewLine(pView, "{%d:%d}%s",
- pExpr->iTable, pExpr->iColumn, zFlgs);
+ tdsqlite3TreeViewLine(pView, "{%d:%d} pTab=%p%s",
+ pExpr->iTable, pExpr->iColumn,
+ pExpr->y.pTab, zFlgs);
+ }
+ if( ExprHasProperty(pExpr, EP_FixedCol) ){
+ tdsqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
}
break;
}
case TK_INTEGER: {
if( pExpr->flags & EP_IntValue ){
- sqlite3TreeViewLine(pView, "%d", pExpr->u.iValue);
+ tdsqlite3TreeViewLine(pView, "%d", pExpr->u.iValue);
}else{
- sqlite3TreeViewLine(pView, "%s", pExpr->u.zToken);
+ tdsqlite3TreeViewLine(pView, "%s", pExpr->u.zToken);
}
break;
}
#ifndef SQLITE_OMIT_FLOATING_POINT
case TK_FLOAT: {
- sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken);
+ tdsqlite3TreeViewLine(pView,"%s", pExpr->u.zToken);
break;
}
#endif
case TK_STRING: {
- sqlite3TreeViewLine(pView,"%Q", pExpr->u.zToken);
+ tdsqlite3TreeViewLine(pView,"%Q", pExpr->u.zToken);
break;
}
case TK_NULL: {
- sqlite3TreeViewLine(pView,"NULL");
+ tdsqlite3TreeViewLine(pView,"NULL");
+ break;
+ }
+ case TK_TRUEFALSE: {
+ tdsqlite3TreeViewLine(pView,
+ tdsqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE");
break;
}
#ifndef SQLITE_OMIT_BLOB_LITERAL
case TK_BLOB: {
- sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken);
+ tdsqlite3TreeViewLine(pView,"%s", pExpr->u.zToken);
break;
}
#endif
case TK_VARIABLE: {
- sqlite3TreeViewLine(pView,"VARIABLE(%s,%d)",
+ tdsqlite3TreeViewLine(pView,"VARIABLE(%s,%d)",
pExpr->u.zToken, pExpr->iColumn);
break;
}
case TK_REGISTER: {
- sqlite3TreeViewLine(pView,"REGISTER(%d)", pExpr->iTable);
+ tdsqlite3TreeViewLine(pView,"REGISTER(%d)", pExpr->iTable);
break;
}
case TK_ID: {
- sqlite3TreeViewLine(pView,"ID \"%w\"", pExpr->u.zToken);
+ tdsqlite3TreeViewLine(pView,"ID \"%w\"", pExpr->u.zToken);
break;
}
#ifndef SQLITE_OMIT_CAST
case TK_CAST: {
/* Expressions of the form: CAST(pLeft AS token) */
- sqlite3TreeViewLine(pView,"CAST %Q", pExpr->u.zToken);
- sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
+ tdsqlite3TreeViewLine(pView,"CAST %Q", pExpr->u.zToken);
+ tdsqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
break;
}
#endif /* SQLITE_OMIT_CAST */
@@ -29906,55 +34081,98 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m
case TK_ISNULL: zUniOp = "ISNULL"; break;
case TK_NOTNULL: zUniOp = "NOTNULL"; break;
+ case TK_TRUTH: {
+ int x;
+ const char *azOp[] = {
+ "IS-FALSE", "IS-TRUE", "IS-NOT-FALSE", "IS-NOT-TRUE"
+ };
+ assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT );
+ assert( pExpr->pRight );
+ assert( tdsqlite3ExprSkipCollate(pExpr->pRight)->op==TK_TRUEFALSE );
+ x = (pExpr->op2==TK_ISNOT)*2 + tdsqlite3ExprTruthValue(pExpr->pRight);
+ zUniOp = azOp[x];
+ break;
+ }
+
case TK_SPAN: {
- sqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken);
- sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
+ tdsqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken);
+ tdsqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
break;
}
case TK_COLLATE: {
- sqlite3TreeViewLine(pView, "COLLATE %Q", pExpr->u.zToken);
- sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
+ /* COLLATE operators without the EP_Collate flag are intended to
+ ** emulate collation associated with a table column. These show
+ ** up in the treeview output as "SOFT-COLLATE". Explicit COLLATE
+ ** operators that appear in the original SQL always have the
+ ** EP_Collate bit set and appear in treeview output as just "COLLATE" */
+ tdsqlite3TreeViewLine(pView, "%sCOLLATE %Q%s",
+ !ExprHasProperty(pExpr, EP_Collate) ? "SOFT-" : "",
+ pExpr->u.zToken, zFlgs);
+ tdsqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
break;
}
case TK_AGG_FUNCTION:
case TK_FUNCTION: {
ExprList *pFarg; /* List of function arguments */
+ Window *pWin;
if( ExprHasProperty(pExpr, EP_TokenOnly) ){
pFarg = 0;
+ pWin = 0;
}else{
pFarg = pExpr->x.pList;
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ pWin = ExprHasProperty(pExpr, EP_WinFunc) ? pExpr->y.pWin : 0;
+#else
+ pWin = 0;
+#endif
}
if( pExpr->op==TK_AGG_FUNCTION ){
- sqlite3TreeViewLine(pView, "AGG_FUNCTION%d %Q",
- pExpr->op2, pExpr->u.zToken);
+ tdsqlite3TreeViewLine(pView, "AGG_FUNCTION%d %Q%s",
+ pExpr->op2, pExpr->u.zToken, zFlgs);
+ }else if( pExpr->op2!=0 ){
+ const char *zOp2;
+ char zBuf[8];
+ tdsqlite3_snprintf(sizeof(zBuf),zBuf,"0x%02x",pExpr->op2);
+ zOp2 = zBuf;
+ if( pExpr->op2==NC_IsCheck ) zOp2 = "NC_IsCheck";
+ if( pExpr->op2==NC_IdxExpr ) zOp2 = "NC_IdxExpr";
+ if( pExpr->op2==NC_PartIdx ) zOp2 = "NC_PartIdx";
+ if( pExpr->op2==NC_GenCol ) zOp2 = "NC_GenCol";
+ tdsqlite3TreeViewLine(pView, "FUNCTION %Q%s op2=%s",
+ pExpr->u.zToken, zFlgs, zOp2);
}else{
- sqlite3TreeViewLine(pView, "FUNCTION %Q", pExpr->u.zToken);
+ tdsqlite3TreeViewLine(pView, "FUNCTION %Q%s", pExpr->u.zToken, zFlgs);
}
if( pFarg ){
- sqlite3TreeViewExprList(pView, pFarg, 0, 0);
+ tdsqlite3TreeViewExprList(pView, pFarg, pWin!=0, 0);
+ }
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( pWin ){
+ tdsqlite3TreeViewWindow(pView, pWin, 0);
}
+#endif
break;
}
#ifndef SQLITE_OMIT_SUBQUERY
case TK_EXISTS: {
- sqlite3TreeViewLine(pView, "EXISTS-expr");
- sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0);
+ tdsqlite3TreeViewLine(pView, "EXISTS-expr flags=0x%x", pExpr->flags);
+ tdsqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0);
break;
}
case TK_SELECT: {
- sqlite3TreeViewLine(pView, "SELECT-expr");
- sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0);
+ tdsqlite3TreeViewLine(pView, "SELECT-expr flags=0x%x", pExpr->flags);
+ tdsqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0);
break;
}
case TK_IN: {
- sqlite3TreeViewLine(pView, "IN");
- sqlite3TreeViewExpr(pView, pExpr->pLeft, 1);
+ tdsqlite3TreeViewLine(pView, "IN flags=0x%x", pExpr->flags);
+ tdsqlite3TreeViewExpr(pView, pExpr->pLeft, 1);
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
- sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0);
+ tdsqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0);
}else{
- sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0);
+ tdsqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0);
}
break;
}
@@ -29975,10 +34193,10 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m
Expr *pX = pExpr->pLeft;
Expr *pY = pExpr->x.pList->a[0].pExpr;
Expr *pZ = pExpr->x.pList->a[1].pExpr;
- sqlite3TreeViewLine(pView, "BETWEEN");
- sqlite3TreeViewExpr(pView, pX, 1);
- sqlite3TreeViewExpr(pView, pY, 1);
- sqlite3TreeViewExpr(pView, pZ, 0);
+ tdsqlite3TreeViewLine(pView, "BETWEEN");
+ tdsqlite3TreeViewExpr(pView, pX, 1);
+ tdsqlite3TreeViewExpr(pView, pY, 1);
+ tdsqlite3TreeViewExpr(pView, pZ, 0);
break;
}
case TK_TRIGGER: {
@@ -29989,95 +34207,116 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m
** is set to the column of the pseudo-table to read, or to -1 to
** read the rowid field.
*/
- sqlite3TreeViewLine(pView, "%s(%d)",
+ tdsqlite3TreeViewLine(pView, "%s(%d)",
pExpr->iTable ? "NEW" : "OLD", pExpr->iColumn);
break;
}
case TK_CASE: {
- sqlite3TreeViewLine(pView, "CASE");
- sqlite3TreeViewExpr(pView, pExpr->pLeft, 1);
- sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0);
+ tdsqlite3TreeViewLine(pView, "CASE");
+ tdsqlite3TreeViewExpr(pView, pExpr->pLeft, 1);
+ tdsqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0);
break;
}
#ifndef SQLITE_OMIT_TRIGGER
case TK_RAISE: {
const char *zType = "unk";
- switch( pExpr->affinity ){
+ switch( pExpr->affExpr ){
case OE_Rollback: zType = "rollback"; break;
case OE_Abort: zType = "abort"; break;
case OE_Fail: zType = "fail"; break;
case OE_Ignore: zType = "ignore"; break;
}
- sqlite3TreeViewLine(pView, "RAISE %s(%Q)", zType, pExpr->u.zToken);
+ tdsqlite3TreeViewLine(pView, "RAISE %s(%Q)", zType, pExpr->u.zToken);
break;
}
#endif
case TK_MATCH: {
- sqlite3TreeViewLine(pView, "MATCH {%d:%d}%s",
+ tdsqlite3TreeViewLine(pView, "MATCH {%d:%d}%s",
pExpr->iTable, pExpr->iColumn, zFlgs);
- sqlite3TreeViewExpr(pView, pExpr->pRight, 0);
+ tdsqlite3TreeViewExpr(pView, pExpr->pRight, 0);
break;
}
case TK_VECTOR: {
- sqlite3TreeViewBareExprList(pView, pExpr->x.pList, "VECTOR");
+ char *z = tdsqlite3_mprintf("VECTOR%s",zFlgs);
+ tdsqlite3TreeViewBareExprList(pView, pExpr->x.pList, z);
+ tdsqlite3_free(z);
break;
}
case TK_SELECT_COLUMN: {
- sqlite3TreeViewLine(pView, "SELECT-COLUMN %d", pExpr->iColumn);
- sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0);
+ tdsqlite3TreeViewLine(pView, "SELECT-COLUMN %d", pExpr->iColumn);
+ tdsqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0);
+ break;
+ }
+ case TK_IF_NULL_ROW: {
+ tdsqlite3TreeViewLine(pView, "IF-NULL-ROW %d", pExpr->iTable);
+ tdsqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
break;
}
default: {
- sqlite3TreeViewLine(pView, "op=%d", pExpr->op);
+ tdsqlite3TreeViewLine(pView, "op=%d", pExpr->op);
break;
}
}
if( zBinOp ){
- sqlite3TreeViewLine(pView, "%s%s", zBinOp, zFlgs);
- sqlite3TreeViewExpr(pView, pExpr->pLeft, 1);
- sqlite3TreeViewExpr(pView, pExpr->pRight, 0);
+ tdsqlite3TreeViewLine(pView, "%s%s", zBinOp, zFlgs);
+ tdsqlite3TreeViewExpr(pView, pExpr->pLeft, 1);
+ tdsqlite3TreeViewExpr(pView, pExpr->pRight, 0);
}else if( zUniOp ){
- sqlite3TreeViewLine(pView, "%s%s", zUniOp, zFlgs);
- sqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
+ tdsqlite3TreeViewLine(pView, "%s%s", zUniOp, zFlgs);
+ tdsqlite3TreeViewExpr(pView, pExpr->pLeft, 0);
}
- sqlite3TreeViewPop(pView);
+ tdsqlite3TreeViewPop(pView);
}
/*
** Generate a human-readable explanation of an expression list.
*/
-SQLITE_PRIVATE void sqlite3TreeViewBareExprList(
+SQLITE_PRIVATE void tdsqlite3TreeViewBareExprList(
TreeView *pView,
const ExprList *pList,
const char *zLabel
){
if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST";
if( pList==0 ){
- sqlite3TreeViewLine(pView, "%s (empty)", zLabel);
+ tdsqlite3TreeViewLine(pView, "%s (empty)", zLabel);
}else{
int i;
- sqlite3TreeViewLine(pView, "%s", zLabel);
+ tdsqlite3TreeViewLine(pView, "%s", zLabel);
for(i=0; i<pList->nExpr; i++){
int j = pList->a[i].u.x.iOrderByCol;
- if( j ){
- sqlite3TreeViewPush(pView, 0);
- sqlite3TreeViewLine(pView, "iOrderByCol=%d", j);
+ char *zName = pList->a[i].zEName;
+ int moreToFollow = i<pList->nExpr - 1;
+ if( pList->a[i].eEName!=ENAME_NAME ) zName = 0;
+ if( j || zName ){
+ tdsqlite3TreeViewPush(pView, moreToFollow);
+ moreToFollow = 0;
+ tdsqlite3TreeViewLine(pView, 0);
+ if( zName ){
+ fprintf(stdout, "AS %s ", zName);
+ }
+ if( j ){
+ fprintf(stdout, "iOrderByCol=%d", j);
+ }
+ fprintf(stdout, "\n");
+ fflush(stdout);
+ }
+ tdsqlite3TreeViewExpr(pView, pList->a[i].pExpr, moreToFollow);
+ if( j || zName ){
+ tdsqlite3TreeViewPop(pView);
}
- sqlite3TreeViewExpr(pView, pList->a[i].pExpr, i<pList->nExpr-1);
- if( j ) sqlite3TreeViewPop(pView);
}
}
}
-SQLITE_PRIVATE void sqlite3TreeViewExprList(
+SQLITE_PRIVATE void tdsqlite3TreeViewExprList(
TreeView *pView,
const ExprList *pList,
u8 moreToFollow,
const char *zLabel
){
- pView = sqlite3TreeViewPush(pView, moreToFollow);
- sqlite3TreeViewBareExprList(pView, pList, zLabel);
- sqlite3TreeViewPop(pView);
+ pView = tdsqlite3TreeViewPush(pView, moreToFollow);
+ tdsqlite3TreeViewBareExprList(pView, pList, zLabel);
+ tdsqlite3TreeViewPop(pView);
}
#endif /* SQLITE_DEBUG */
@@ -30107,16 +34346,16 @@ SQLITE_PRIVATE void sqlite3TreeViewExprList(
/* All threads share a single random number generator.
** This structure is the current state of the generator.
*/
-static SQLITE_WSD struct sqlite3PrngType {
+static SQLITE_WSD struct tdsqlite3PrngType {
unsigned char isInit; /* True if initialized */
unsigned char i, j; /* State variables */
unsigned char s[256]; /* State variables */
-} sqlite3Prng;
+} tdsqlite3Prng;
/*
** Return N random bytes.
*/
-SQLITE_API void sqlite3_randomness(int N, void *pBuf){
+SQLITE_API void tdsqlite3_randomness(int N, void *pBuf){
unsigned char t;
unsigned char *zBuf = pBuf;
@@ -30124,31 +34363,31 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){
** state vector. If writable static data is unsupported on the target,
** we have to locate the state vector at run-time. In the more common
** case where writable static data is supported, wsdPrng can refer directly
- ** to the "sqlite3Prng" state vector declared above.
+ ** to the "tdsqlite3Prng" state vector declared above.
*/
#ifdef SQLITE_OMIT_WSD
- struct sqlite3PrngType *p = &GLOBAL(struct sqlite3PrngType, sqlite3Prng);
+ struct tdsqlite3PrngType *p = &GLOBAL(struct tdsqlite3PrngType, tdsqlite3Prng);
# define wsdPrng p[0]
#else
-# define wsdPrng sqlite3Prng
+# define wsdPrng tdsqlite3Prng
#endif
#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex;
+ tdsqlite3_mutex *mutex;
#endif
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return;
+ if( tdsqlite3_initialize() ) return;
#endif
#if SQLITE_THREADSAFE
- mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PRNG);
+ mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PRNG);
#endif
- sqlite3_mutex_enter(mutex);
+ tdsqlite3_mutex_enter(mutex);
if( N<=0 || pBuf==0 ){
wsdPrng.isInit = 0;
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
return;
}
@@ -30166,7 +34405,7 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){
char k[256];
wsdPrng.j = 0;
wsdPrng.i = 0;
- sqlite3OsRandomness(sqlite3_vfs_find(0), 256, k);
+ tdsqlite3OsRandomness(tdsqlite3_vfs_find(0), 256, k);
for(i=0; i<256; i++){
wsdPrng.s[i] = (u8)i;
}
@@ -30189,35 +34428,35 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){
t += wsdPrng.s[wsdPrng.i];
*(zBuf++) = wsdPrng.s[t];
}while( --N );
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
}
-#ifndef SQLITE_OMIT_BUILTIN_TEST
+#ifndef SQLITE_UNTESTABLE
/*
** For testing purposes, we sometimes want to preserve the state of
** PRNG and restore the PRNG to its saved state at a later time, or
** to reset the PRNG to its initial state. These routines accomplish
** those tasks.
**
-** The sqlite3_test_control() interface calls these routines to
+** The tdsqlite3_test_control() interface calls these routines to
** control the PRNG.
*/
-static SQLITE_WSD struct sqlite3PrngType sqlite3SavedPrng;
-SQLITE_PRIVATE void sqlite3PrngSaveState(void){
+static SQLITE_WSD struct tdsqlite3PrngType tdsqlite3SavedPrng;
+SQLITE_PRIVATE void tdsqlite3PrngSaveState(void){
memcpy(
- &GLOBAL(struct sqlite3PrngType, sqlite3SavedPrng),
- &GLOBAL(struct sqlite3PrngType, sqlite3Prng),
- sizeof(sqlite3Prng)
+ &GLOBAL(struct tdsqlite3PrngType, tdsqlite3SavedPrng),
+ &GLOBAL(struct tdsqlite3PrngType, tdsqlite3Prng),
+ sizeof(tdsqlite3Prng)
);
}
-SQLITE_PRIVATE void sqlite3PrngRestoreState(void){
+SQLITE_PRIVATE void tdsqlite3PrngRestoreState(void){
memcpy(
- &GLOBAL(struct sqlite3PrngType, sqlite3Prng),
- &GLOBAL(struct sqlite3PrngType, sqlite3SavedPrng),
- sizeof(sqlite3Prng)
+ &GLOBAL(struct tdsqlite3PrngType, tdsqlite3Prng),
+ &GLOBAL(struct tdsqlite3PrngType, tdsqlite3SavedPrng),
+ sizeof(tdsqlite3Prng)
);
}
-#endif /* SQLITE_OMIT_BUILTIN_TEST */
+#endif /* SQLITE_UNTESTABLE */
/************** End of random.c **********************************************/
/************** Begin file threads.c *****************************************/
@@ -30236,13 +34475,13 @@ SQLITE_PRIVATE void sqlite3PrngRestoreState(void){
** This file presents a simple cross-platform threading interface for
** use internally by SQLite.
**
-** A "thread" can be created using sqlite3ThreadCreate(). This thread
+** A "thread" can be created using tdsqlite3ThreadCreate(). This thread
** runs independently of its creator until it is joined using
-** sqlite3ThreadJoin(), at which point it terminates.
+** tdsqlite3ThreadJoin(), at which point it terminates.
**
** Threads do not have to be real. It could be that the work of the
-** "thread" is done by the main thread at either the sqlite3ThreadCreate()
-** or sqlite3ThreadJoin() call. This is, in fact, what happens in
+** "thread" is done by the main thread at either the tdsqlite3ThreadCreate()
+** or tdsqlite3ThreadJoin() call. This is, in fact, what happens in
** single threaded systems. Nothing in SQLite requires multiple threads.
** This interface exists so that applications that want to take advantage
** of multiple cores can do so, while also allowing applications to stay
@@ -30271,7 +34510,7 @@ struct SQLiteThread {
};
/* Create a new thread */
-SQLITE_PRIVATE int sqlite3ThreadCreate(
+SQLITE_PRIVATE int tdsqlite3ThreadCreate(
SQLiteThread **ppThread, /* OUT: Write the thread object here */
void *(*xTask)(void*), /* Routine to run in a separate thread */
void *pIn /* Argument passed into xTask() */
@@ -30282,10 +34521,10 @@ SQLITE_PRIVATE int sqlite3ThreadCreate(
assert( ppThread!=0 );
assert( xTask!=0 );
/* This routine is never used in single-threaded mode */
- assert( sqlite3GlobalConfig.bCoreMutex!=0 );
+ assert( tdsqlite3GlobalConfig.bCoreMutex!=0 );
*ppThread = 0;
- p = sqlite3Malloc(sizeof(*p));
+ p = tdsqlite3Malloc(sizeof(*p));
if( p==0 ) return SQLITE_NOMEM_BKPT;
memset(p, 0, sizeof(*p));
p->xTask = xTask;
@@ -30294,7 +34533,7 @@ SQLITE_PRIVATE int sqlite3ThreadCreate(
** function that returns SQLITE_ERROR when passed the argument 200, that
** forces worker threads to run sequentially and deterministically
** for testing purposes. */
- if( sqlite3FaultSim(200) ){
+ if( tdsqlite3FaultSim(200) ){
rc = 1;
}else{
rc = pthread_create(&p->tid, 0, xTask, pIn);
@@ -30308,7 +34547,7 @@ SQLITE_PRIVATE int sqlite3ThreadCreate(
}
/* Get the results of the thread */
-SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
+SQLITE_PRIVATE int tdsqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
int rc;
assert( ppOut!=0 );
@@ -30319,7 +34558,7 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
}else{
rc = pthread_join(p->tid, ppOut) ? SQLITE_ERROR : SQLITE_OK;
}
- sqlite3_free(p);
+ tdsqlite3_free(p);
return rc;
}
@@ -30343,7 +34582,7 @@ struct SQLiteThread {
};
/* Thread procedure Win32 compatibility shim */
-static unsigned __stdcall sqlite3ThreadProc(
+static unsigned __stdcall tdsqlite3ThreadProc(
void *pArg /* IN: Pointer to the SQLiteThread structure */
){
SQLiteThread *p = (SQLiteThread *)pArg;
@@ -30366,7 +34605,7 @@ static unsigned __stdcall sqlite3ThreadProc(
}
/* Create a new thread */
-SQLITE_PRIVATE int sqlite3ThreadCreate(
+SQLITE_PRIVATE int tdsqlite3ThreadCreate(
SQLiteThread **ppThread, /* OUT: Write the thread object here */
void *(*xTask)(void*), /* Routine to run in a separate thread */
void *pIn /* Argument passed into xTask() */
@@ -30376,19 +34615,19 @@ SQLITE_PRIVATE int sqlite3ThreadCreate(
assert( ppThread!=0 );
assert( xTask!=0 );
*ppThread = 0;
- p = sqlite3Malloc(sizeof(*p));
+ p = tdsqlite3Malloc(sizeof(*p));
if( p==0 ) return SQLITE_NOMEM_BKPT;
/* If the SQLITE_TESTCTRL_FAULT_INSTALL callback is registered to a
** function that returns SQLITE_ERROR when passed the argument 200, that
** forces worker threads to run sequentially and deterministically
- ** (via the sqlite3FaultSim() term of the conditional) for testing
+ ** (via the tdsqlite3FaultSim() term of the conditional) for testing
** purposes. */
- if( sqlite3GlobalConfig.bCoreMutex==0 || sqlite3FaultSim(200) ){
+ if( tdsqlite3GlobalConfig.bCoreMutex==0 || tdsqlite3FaultSim(200) ){
memset(p, 0, sizeof(*p));
}else{
p->xTask = xTask;
p->pIn = pIn;
- p->tid = (void*)_beginthreadex(0, 0, sqlite3ThreadProc, p, 0, &p->id);
+ p->tid = (void*)_beginthreadex(0, 0, tdsqlite3ThreadProc, p, 0, &p->id);
if( p->tid==0 ){
memset(p, 0, sizeof(*p));
}
@@ -30401,10 +34640,10 @@ SQLITE_PRIVATE int sqlite3ThreadCreate(
return SQLITE_OK;
}
-SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject); /* os_win.c */
+SQLITE_PRIVATE DWORD tdsqlite3Win32Wait(HANDLE hObject); /* os_win.c */
/* Get the results of the thread */
-SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
+SQLITE_PRIVATE int tdsqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
DWORD rc;
BOOL bRc;
@@ -30416,13 +34655,13 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
assert( p->tid==0 );
}else{
assert( p->id!=0 && p->id!=GetCurrentThreadId() );
- rc = sqlite3Win32Wait((HANDLE)p->tid);
+ rc = tdsqlite3Win32Wait((HANDLE)p->tid);
assert( rc!=WAIT_IO_COMPLETION );
bRc = CloseHandle((HANDLE)p->tid);
assert( bRc );
}
if( rc==WAIT_OBJECT_0 ) *ppOut = p->pResult;
- sqlite3_free(p);
+ tdsqlite3_free(p);
return (rc==WAIT_OBJECT_0) ? SQLITE_OK : SQLITE_ERROR;
}
@@ -30446,7 +34685,7 @@ struct SQLiteThread {
};
/* Create a new thread */
-SQLITE_PRIVATE int sqlite3ThreadCreate(
+SQLITE_PRIVATE int tdsqlite3ThreadCreate(
SQLiteThread **ppThread, /* OUT: Write the thread object here */
void *(*xTask)(void*), /* Routine to run in a separate thread */
void *pIn /* Argument passed into xTask() */
@@ -30456,7 +34695,7 @@ SQLITE_PRIVATE int sqlite3ThreadCreate(
assert( ppThread!=0 );
assert( xTask!=0 );
*ppThread = 0;
- p = sqlite3Malloc(sizeof(*p));
+ p = tdsqlite3Malloc(sizeof(*p));
if( p==0 ) return SQLITE_NOMEM_BKPT;
if( (SQLITE_PTR_TO_INT(p)/17)&1 ){
p->xTask = xTask;
@@ -30470,7 +34709,7 @@ SQLITE_PRIVATE int sqlite3ThreadCreate(
}
/* Get the results of the thread */
-SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
+SQLITE_PRIVATE int tdsqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
assert( ppOut!=0 );
if( NEVER(p==0) ) return SQLITE_NOMEM_BKPT;
@@ -30479,13 +34718,13 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
}else{
*ppOut = p->pResult;
}
- sqlite3_free(p);
+ tdsqlite3_free(p);
#if defined(SQLITE_TEST)
{
- void *pTstAlloc = sqlite3Malloc(10);
+ void *pTstAlloc = tdsqlite3Malloc(10);
if (!pTstAlloc) return SQLITE_NOMEM_BKPT;
- sqlite3_free(pTstAlloc);
+ tdsqlite3_free(pTstAlloc);
}
#endif
@@ -30542,14 +34781,14 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){
** The following constant value is used by the SQLITE_BIGENDIAN and
** SQLITE_LITTLEENDIAN macros.
*/
-SQLITE_PRIVATE const int sqlite3one = 1;
+SQLITE_PRIVATE const int tdsqlite3one = 1;
#endif /* SQLITE_AMALGAMATION && SQLITE_BYTEORDER==0 */
/*
** This lookup table is used to help decode the first byte of
** a multi-byte UTF8 character.
*/
-static const unsigned char sqlite3Utf8Trans1[] = {
+static const unsigned char tdsqlite3Utf8Trans1[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
@@ -30655,7 +34894,7 @@ static const unsigned char sqlite3Utf8Trans1[] = {
#define READ_UTF8(zIn, zTerm, c) \
c = *(zIn++); \
if( c>=0xc0 ){ \
- c = sqlite3Utf8Trans1[c-0xc0]; \
+ c = tdsqlite3Utf8Trans1[c-0xc0]; \
while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \
c = (c<<6) + (0x3f & *(zIn++)); \
} \
@@ -30663,7 +34902,7 @@ static const unsigned char sqlite3Utf8Trans1[] = {
|| (c&0xFFFFF800)==0xD800 \
|| (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \
}
-SQLITE_PRIVATE u32 sqlite3Utf8Read(
+SQLITE_PRIVATE u32 tdsqlite3Utf8Read(
const unsigned char **pz /* Pointer to string from which to read char */
){
unsigned int c;
@@ -30673,7 +34912,7 @@ SQLITE_PRIVATE u32 sqlite3Utf8Read(
*/
c = *((*pz)++);
if( c>=0xc0 ){
- c = sqlite3Utf8Trans1[c-0xc0];
+ c = tdsqlite3Utf8Trans1[c-0xc0];
while( (*(*pz) & 0xc0)==0x80 ){
c = (c<<6) + (0x3f & *((*pz)++));
}
@@ -30689,7 +34928,7 @@ SQLITE_PRIVATE u32 sqlite3Utf8Read(
/*
** If the TRANSLATE_TRACE macro is defined, the value of each Mem is
-** printed on stderr on the way into and out of sqlite3VdbeMemTranslate().
+** printed on stderr on the way into and out of tdsqlite3VdbeMemTranslate().
*/
/* #define TRANSLATE_TRACE 1 */
@@ -30699,15 +34938,15 @@ SQLITE_PRIVATE u32 sqlite3Utf8Read(
** desiredEnc. It is an error if the string is already of the desired
** encoding, or if *pMem does not contain a string value.
*/
-SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desiredEnc){
- int len; /* Maximum length of output string in bytes */
- unsigned char *zOut; /* Output buffer */
- unsigned char *zIn; /* Input iterator */
- unsigned char *zTerm; /* End of input */
- unsigned char *z; /* Output iterator */
+SQLITE_PRIVATE SQLITE_NOINLINE int tdsqlite3VdbeMemTranslate(Mem *pMem, u8 desiredEnc){
+ tdsqlite3_int64 len; /* Maximum length of output string in bytes */
+ unsigned char *zOut; /* Output buffer */
+ unsigned char *zIn; /* Input iterator */
+ unsigned char *zTerm; /* End of input */
+ unsigned char *z; /* Output iterator */
unsigned int c;
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
assert( pMem->flags&MEM_Str );
assert( pMem->enc!=desiredEnc );
assert( pMem->enc!=0 );
@@ -30715,9 +34954,11 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desired
#if defined(TRANSLATE_TRACE) && defined(SQLITE_DEBUG)
{
- char zBuf[100];
- sqlite3VdbeMemPrettyPrint(pMem, zBuf);
- fprintf(stderr, "INPUT: %s\n", zBuf);
+ StrAccum acc;
+ char zBuf[1000];
+ tdsqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
+ tdsqlite3VdbeMemPrettyPrint(pMem, &acc);
+ fprintf(stderr, "INPUT: %s\n", tdsqlite3StrAccumFinish(&acc));
}
#endif
@@ -30728,7 +34969,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desired
if( pMem->enc!=SQLITE_UTF8 && desiredEnc!=SQLITE_UTF8 ){
u8 temp;
int rc;
- rc = sqlite3VdbeMemMakeWriteable(pMem);
+ rc = tdsqlite3VdbeMemMakeWriteable(pMem);
if( rc!=SQLITE_OK ){
assert( rc==SQLITE_NOMEM );
return SQLITE_NOMEM_BKPT;
@@ -30753,25 +34994,25 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desired
** nul-terminator.
*/
pMem->n &= ~1;
- len = pMem->n * 2 + 1;
+ len = 2 * (tdsqlite3_int64)pMem->n + 1;
}else{
/* When converting from UTF-8 to UTF-16 the maximum growth is caused
** when a 1-byte UTF-8 character is translated into a 2-byte UTF-16
** character. Two bytes are required in the output buffer for the
** nul-terminator.
*/
- len = pMem->n * 2 + 2;
+ len = 2 * (tdsqlite3_int64)pMem->n + 2;
}
/* Set zIn to point at the start of the input buffer and zTerm to point 1
** byte past the end.
**
** Variable zOut is set to point at the output buffer, space obtained
- ** from sqlite3_malloc().
+ ** from tdsqlite3_malloc().
*/
zIn = (u8*)pMem->z;
zTerm = &zIn[pMem->n];
- zOut = sqlite3DbMallocRaw(pMem->db, len);
+ zOut = tdsqlite3DbMallocRaw(pMem->db, len);
if( !zOut ){
return SQLITE_NOMEM_BKPT;
}
@@ -30815,24 +35056,28 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desired
assert( (pMem->n+(desiredEnc==SQLITE_UTF8?1:2))<=len );
c = pMem->flags;
- sqlite3VdbeMemRelease(pMem);
+ tdsqlite3VdbeMemRelease(pMem);
pMem->flags = MEM_Str|MEM_Term|(c&(MEM_AffMask|MEM_Subtype));
pMem->enc = desiredEnc;
pMem->z = (char*)zOut;
pMem->zMalloc = pMem->z;
- pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->z);
+ pMem->szMalloc = tdsqlite3DbMallocSize(pMem->db, pMem->z);
translate_out:
#if defined(TRANSLATE_TRACE) && defined(SQLITE_DEBUG)
{
- char zBuf[100];
- sqlite3VdbeMemPrettyPrint(pMem, zBuf);
- fprintf(stderr, "OUTPUT: %s\n", zBuf);
+ StrAccum acc;
+ char zBuf[1000];
+ tdsqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
+ tdsqlite3VdbeMemPrettyPrint(pMem, &acc);
+ fprintf(stderr, "OUTPUT: %s\n", tdsqlite3StrAccumFinish(&acc));
}
#endif
return SQLITE_OK;
}
+#endif /* SQLITE_OMIT_UTF16 */
+#ifndef SQLITE_OMIT_UTF16
/*
** This routine checks for a byte-order mark at the beginning of the
** UTF-16 string stored in *pMem. If one is present, it is removed and
@@ -30842,7 +35087,7 @@ translate_out:
** The allocation (static, dynamic etc.) and encoding of the Mem may be
** changed by this function.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem){
+SQLITE_PRIVATE int tdsqlite3VdbeMemHandleBom(Mem *pMem){
int rc = SQLITE_OK;
u8 bom = 0;
@@ -30859,7 +35104,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem){
}
if( bom ){
- rc = sqlite3VdbeMemMakeWriteable(pMem);
+ rc = tdsqlite3VdbeMemMakeWriteable(pMem);
if( rc==SQLITE_OK ){
pMem->n -= 2;
memmove(pMem->z, &pMem->z[2], pMem->n);
@@ -30880,7 +35125,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem){
** number of unicode characters in the first nByte of pZ (or up to
** the first 0x00, whichever comes first).
*/
-SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *zIn, int nByte){
+SQLITE_PRIVATE int tdsqlite3Utf8CharLen(const char *zIn, int nByte){
int r = 0;
const u8 *z = (const u8*)zIn;
const u8 *zTerm;
@@ -30910,13 +35155,13 @@ SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *zIn, int nByte){
** The translation is done in-place and aborted if the output
** overruns the input.
*/
-SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char *zIn){
+SQLITE_PRIVATE int tdsqlite3Utf8To8(unsigned char *zIn){
unsigned char *zOut = zIn;
unsigned char *zStart = zIn;
u32 c;
while( zIn[0] && zOut<=zIn ){
- c = sqlite3Utf8Read((const u8**)&zIn);
+ c = tdsqlite3Utf8Read((const u8**)&zIn);
if( c!=0xfffd ){
WRITE_UTF8(zOut, c);
}
@@ -30929,19 +35174,19 @@ SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char *zIn){
#ifndef SQLITE_OMIT_UTF16
/*
** Convert a UTF-16 string in the native encoding into a UTF-8 string.
-** Memory to hold the UTF-8 string is obtained from sqlite3_malloc and must
+** Memory to hold the UTF-8 string is obtained from tdsqlite3_malloc and must
** be freed by the calling function.
**
** NULL is returned if there is an allocation error.
*/
-SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *db, const void *z, int nByte, u8 enc){
+SQLITE_PRIVATE char *tdsqlite3Utf16to8(tdsqlite3 *db, const void *z, int nByte, u8 enc){
Mem m;
memset(&m, 0, sizeof(m));
m.db = db;
- sqlite3VdbeMemSetStr(&m, z, nByte, enc, SQLITE_STATIC);
- sqlite3VdbeChangeEncoding(&m, SQLITE_UTF8);
+ tdsqlite3VdbeMemSetStr(&m, z, nByte, enc, SQLITE_STATIC);
+ tdsqlite3VdbeChangeEncoding(&m, SQLITE_UTF8);
if( db->mallocFailed ){
- sqlite3VdbeMemRelease(&m);
+ tdsqlite3VdbeMemRelease(&m);
m.z = 0;
}
assert( (m.flags & MEM_Term)!=0 || db->mallocFailed );
@@ -30955,7 +35200,7 @@ SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *db, const void *z, int nByte, u8 e
** Return the number of bytes in the first nChar unicode characters
** in pZ. nChar must be non-negative.
*/
-SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *zIn, int nChar){
+SQLITE_PRIVATE int tdsqlite3Utf16ByteLen(const void *zIn, int nChar){
int c;
unsigned char const *z = zIn;
int n = 0;
@@ -30980,7 +35225,7 @@ SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *zIn, int nChar){
** It checks that the primitives for serializing and deserializing
** characters in each encoding are inverses of each other.
*/
-SQLITE_PRIVATE void sqlite3UtfSelfTest(void){
+SQLITE_PRIVATE void tdsqlite3UtfSelfTest(void){
unsigned int i, t;
unsigned char zBuf[20];
unsigned char *z;
@@ -30994,7 +35239,7 @@ SQLITE_PRIVATE void sqlite3UtfSelfTest(void){
assert( n>0 && n<=4 );
z[0] = 0;
z = zBuf;
- c = sqlite3Utf8Read((const u8**)&z);
+ c = tdsqlite3Utf8Read((const u8**)&z);
t = i;
if( i>=0xD800 && i<=0xDFFF ) t = 0xFFFD;
if( (i&0xFFFFFFFE)==0xFFFE ) t = 0xFFFD;
@@ -31050,34 +35295,42 @@ SQLITE_PRIVATE void sqlite3UtfSelfTest(void){
*/
/* #include "sqliteInt.h" */
/* #include <stdarg.h> */
-#if HAVE_ISNAN || SQLITE_HAVE_ISNAN
-# include <math.h>
+#ifndef SQLITE_OMIT_FLOATING_POINT
+#include <math.h>
#endif
/*
** Routine needed to support the testcase() macro.
*/
#ifdef SQLITE_COVERAGE_TEST
-SQLITE_PRIVATE void sqlite3Coverage(int x){
+SQLITE_PRIVATE void tdsqlite3Coverage(int x){
static unsigned dummy = 0;
dummy += (unsigned)x;
}
#endif
/*
-** Give a callback to the test harness that can be used to simulate faults
-** in places where it is difficult or expensive to do so purely by means
-** of inputs.
+** Calls to tdsqlite3FaultSim() are used to simulate a failure during testing,
+** or to bypass normal error detection during testing in order to let
+** execute proceed futher downstream.
+**
+** In deployment, tdsqlite3FaultSim() *always* return SQLITE_OK (0). The
+** tdsqlite3FaultSim() function only returns non-zero during testing.
**
-** The intent of the integer argument is to let the fault simulator know
-** which of multiple sqlite3FaultSim() calls has been hit.
+** During testing, if the test harness has set a fault-sim callback using
+** a call to tdsqlite3_test_control(SQLITE_TESTCTRL_FAULT_INSTALL), then
+** each call to tdsqlite3FaultSim() is relayed to that application-supplied
+** callback and the integer return value form the application-supplied
+** callback is returned by tdsqlite3FaultSim().
**
-** Return whatever integer value the test callback returns, or return
-** SQLITE_OK if no test callback is installed.
+** The integer argument to tdsqlite3FaultSim() is a code to identify which
+** tdsqlite3FaultSim() instance is being invoked. Each call to tdsqlite3FaultSim()
+** should have a unique code. To prevent legacy testing applications from
+** breaking, the codes should not be changed or reused.
*/
-#ifndef SQLITE_OMIT_BUILTIN_TEST
-SQLITE_PRIVATE int sqlite3FaultSim(int iTest){
- int (*xCallback)(int) = sqlite3GlobalConfig.xTestCallback;
+#ifndef SQLITE_UNTESTABLE
+SQLITE_PRIVATE int tdsqlite3FaultSim(int iTest){
+ int (*xCallback)(int) = tdsqlite3GlobalConfig.xTestCallback;
return xCallback ? xCallback(iTest) : SQLITE_OK;
}
#endif
@@ -31085,47 +35338,11 @@ SQLITE_PRIVATE int sqlite3FaultSim(int iTest){
#ifndef SQLITE_OMIT_FLOATING_POINT
/*
** Return true if the floating point value is Not a Number (NaN).
-**
-** Use the math library isnan() function if compiled with SQLITE_HAVE_ISNAN.
-** Otherwise, we have our own implementation that works on most systems.
*/
-SQLITE_PRIVATE int sqlite3IsNaN(double x){
- int rc; /* The value return */
-#if !SQLITE_HAVE_ISNAN && !HAVE_ISNAN
- /*
- ** Systems that support the isnan() library function should probably
- ** make use of it by compiling with -DSQLITE_HAVE_ISNAN. But we have
- ** found that many systems do not have a working isnan() function so
- ** this implementation is provided as an alternative.
- **
- ** This NaN test sometimes fails if compiled on GCC with -ffast-math.
- ** On the other hand, the use of -ffast-math comes with the following
- ** warning:
- **
- ** This option [-ffast-math] should never be turned on by any
- ** -O option since it can result in incorrect output for programs
- ** which depend on an exact implementation of IEEE or ISO
- ** rules/specifications for math functions.
- **
- ** Under MSVC, this NaN test may fail if compiled with a floating-
- ** point precision mode other than /fp:precise. From the MSDN
- ** documentation:
- **
- ** The compiler [with /fp:precise] will properly handle comparisons
- ** involving NaN. For example, x != x evaluates to true if x is NaN
- ** ...
- */
-#ifdef __FAST_MATH__
-# error SQLite will not work correctly with the -ffast-math option of GCC.
-#endif
- volatile double y = x;
- volatile double z = y;
- rc = (y!=z);
-#else /* if HAVE_ISNAN */
- rc = isnan(x);
-#endif /* HAVE_ISNAN */
- testcase( rc );
- return rc;
+SQLITE_PRIVATE int tdsqlite3IsNaN(double x){
+ u64 y;
+ memcpy(&y,&x,sizeof(y));
+ return IsNaN(y);
}
#endif /* SQLITE_OMIT_FLOATING_POINT */
@@ -31137,7 +35354,7 @@ SQLITE_PRIVATE int sqlite3IsNaN(double x){
** than the actual length of the string. For very long strings (greater
** than 1GiB) the value returned might be less than the true string length.
*/
-SQLITE_PRIVATE int sqlite3Strlen30(const char *z){
+SQLITE_PRIVATE int tdsqlite3Strlen30(const char *z){
if( z==0 ) return 0;
return 0x3fffffff & (int)strlen(z);
}
@@ -31149,41 +35366,41 @@ SQLITE_PRIVATE int sqlite3Strlen30(const char *z){
** The column type is an extra string stored after the zero-terminator on
** the column name if and only if the COLFLAG_HASTYPE flag is set.
*/
-SQLITE_PRIVATE char *sqlite3ColumnType(Column *pCol, char *zDflt){
+SQLITE_PRIVATE char *tdsqlite3ColumnType(Column *pCol, char *zDflt){
if( (pCol->colFlags & COLFLAG_HASTYPE)==0 ) return zDflt;
return pCol->zName + strlen(pCol->zName) + 1;
}
/*
-** Helper function for sqlite3Error() - called rarely. Broken out into
+** Helper function for tdsqlite3Error() - called rarely. Broken out into
** a separate routine to avoid unnecessary register saves on entry to
-** sqlite3Error().
+** tdsqlite3Error().
*/
-static SQLITE_NOINLINE void sqlite3ErrorFinish(sqlite3 *db, int err_code){
- if( db->pErr ) sqlite3ValueSetNull(db->pErr);
- sqlite3SystemError(db, err_code);
+static SQLITE_NOINLINE void tdsqlite3ErrorFinish(tdsqlite3 *db, int err_code){
+ if( db->pErr ) tdsqlite3ValueSetNull(db->pErr);
+ tdsqlite3SystemError(db, err_code);
}
/*
** Set the current error code to err_code and clear any prior error message.
-** Also set iSysErrno (by calling sqlite3System) if the err_code indicates
+** Also set iSysErrno (by calling tdsqlite3System) if the err_code indicates
** that would be appropriate.
*/
-SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){
+SQLITE_PRIVATE void tdsqlite3Error(tdsqlite3 *db, int err_code){
assert( db!=0 );
db->errCode = err_code;
- if( err_code || db->pErr ) sqlite3ErrorFinish(db, err_code);
+ if( err_code || db->pErr ) tdsqlite3ErrorFinish(db, err_code);
}
/*
** Load the sqlite3.iSysErrno field if that is an appropriate thing
** to do based on the SQLite error code in rc.
*/
-SQLITE_PRIVATE void sqlite3SystemError(sqlite3 *db, int rc){
+SQLITE_PRIVATE void tdsqlite3SystemError(tdsqlite3 *db, int rc){
if( rc==SQLITE_IOERR_NOMEM ) return;
rc &= 0xff;
if( rc==SQLITE_CANTOPEN || rc==SQLITE_IOERR ){
- db->iSysErrno = sqlite3OsGetLastError(db->pVfs);
+ db->iSysErrno = tdsqlite3OsGetLastError(db->pVfs);
}
}
@@ -31204,23 +35421,23 @@ SQLITE_PRIVATE void sqlite3SystemError(sqlite3 *db, int rc){
** zFormat and any string tokens that follow it are assumed to be
** encoded in UTF-8.
**
-** To clear the most recent error for sqlite handle "db", sqlite3Error
+** To clear the most recent error for sqlite handle "db", tdsqlite3Error
** should be called with err_code set to SQLITE_OK and zFormat set
** to NULL.
*/
-SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *zFormat, ...){
+SQLITE_PRIVATE void tdsqlite3ErrorWithMsg(tdsqlite3 *db, int err_code, const char *zFormat, ...){
assert( db!=0 );
db->errCode = err_code;
- sqlite3SystemError(db, err_code);
+ tdsqlite3SystemError(db, err_code);
if( zFormat==0 ){
- sqlite3Error(db, err_code);
- }else if( db->pErr || (db->pErr = sqlite3ValueNew(db))!=0 ){
+ tdsqlite3Error(db, err_code);
+ }else if( db->pErr || (db->pErr = tdsqlite3ValueNew(db))!=0 ){
char *z;
va_list ap;
va_start(ap, zFormat);
- z = sqlite3VMPrintf(db, zFormat, ap);
+ z = tdsqlite3VMPrintf(db, zFormat, ap);
va_end(ap);
- sqlite3ValueSetStr(db->pErr, -1, z, SQLITE_UTF8, SQLITE_DYNAMIC);
+ tdsqlite3ValueSetStr(db->pErr, -1, z, SQLITE_UTF8, SQLITE_DYNAMIC);
}
}
@@ -31235,30 +35452,44 @@ SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *z
** %S Insert the first element of a SrcList
**
** This function should be used to report any error that occurs while
-** compiling an SQL statement (i.e. within sqlite3_prepare()). The
-** last thing the sqlite3_prepare() function does is copy the error
-** stored by this function into the database handle using sqlite3Error().
-** Functions sqlite3Error() or sqlite3ErrorWithMsg() should be used
-** during statement execution (sqlite3_step() etc.).
+** compiling an SQL statement (i.e. within tdsqlite3_prepare()). The
+** last thing the tdsqlite3_prepare() function does is copy the error
+** stored by this function into the database handle using tdsqlite3Error().
+** Functions tdsqlite3Error() or tdsqlite3ErrorWithMsg() should be used
+** during statement execution (tdsqlite3_step() etc.).
*/
-SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){
+SQLITE_PRIVATE void tdsqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){
char *zMsg;
va_list ap;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
va_start(ap, zFormat);
- zMsg = sqlite3VMPrintf(db, zFormat, ap);
+ zMsg = tdsqlite3VMPrintf(db, zFormat, ap);
va_end(ap);
if( db->suppressErr ){
- sqlite3DbFree(db, zMsg);
+ tdsqlite3DbFree(db, zMsg);
}else{
pParse->nErr++;
- sqlite3DbFree(db, pParse->zErrMsg);
+ tdsqlite3DbFree(db, pParse->zErrMsg);
pParse->zErrMsg = zMsg;
pParse->rc = SQLITE_ERROR;
+ pParse->pWith = 0;
}
}
/*
+** If database connection db is currently parsing SQL, then transfer
+** error code errCode to that parser if the parser has not already
+** encountered some other kind of error.
+*/
+SQLITE_PRIVATE int tdsqlite3ErrorToParser(tdsqlite3 *db, int errCode){
+ Parse *pParse;
+ if( db==0 || (pParse = db->pParse)==0 ) return errCode;
+ pParse->rc = errCode;
+ pParse->nErr++;
+ return errCode;
+}
+
+/*
** Convert an SQL-style quoted string into a normal string by removing
** the quote characters. The conversion is done in-place. If the
** input does not begin with a quote character, then this routine
@@ -31271,16 +35502,16 @@ SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){
** dequoted string, exclusive of the zero terminator, if dequoting does
** occur.
**
-** 2002-Feb-14: This routine is extended to remove MS-Access style
+** 2002-02-14: This routine is extended to remove MS-Access style
** brackets from around identifiers. For example: "[a-b-c]" becomes
** "a-b-c".
*/
-SQLITE_PRIVATE void sqlite3Dequote(char *z){
+SQLITE_PRIVATE void tdsqlite3Dequote(char *z){
char quote;
int i, j;
if( z==0 ) return;
quote = z[0];
- if( !sqlite3Isquote(quote) ) return;
+ if( !tdsqlite3Isquote(quote) ) return;
if( quote=='[' ) quote = ']';
for(i=1, j=0;; i++){
assert( z[i] );
@@ -31297,50 +35528,61 @@ SQLITE_PRIVATE void sqlite3Dequote(char *z){
}
z[j] = 0;
}
+SQLITE_PRIVATE void tdsqlite3DequoteExpr(Expr *p){
+ assert( tdsqlite3Isquote(p->u.zToken[0]) );
+ p->flags |= p->u.zToken[0]=='"' ? EP_Quoted|EP_DblQuoted : EP_Quoted;
+ tdsqlite3Dequote(p->u.zToken);
+}
/*
** Generate a Token object from a string
*/
-SQLITE_PRIVATE void sqlite3TokenInit(Token *p, char *z){
+SQLITE_PRIVATE void tdsqlite3TokenInit(Token *p, char *z){
p->z = z;
- p->n = sqlite3Strlen30(z);
+ p->n = tdsqlite3Strlen30(z);
}
/* Convenient short-hand */
-#define UpperToLower sqlite3UpperToLower
+#define UpperToLower tdsqlite3UpperToLower
/*
** Some systems have stricmp(). Others have strcasecmp(). Because
** there is no consistency, we will define our own.
**
-** IMPLEMENTATION-OF: R-30243-02494 The sqlite3_stricmp() and
-** sqlite3_strnicmp() APIs allow applications and extensions to compare
+** IMPLEMENTATION-OF: R-30243-02494 The tdsqlite3_stricmp() and
+** tdsqlite3_strnicmp() APIs allow applications and extensions to compare
** the contents of two buffers containing UTF-8 strings in a
** case-independent fashion, using the same definition of "case
** independence" that SQLite uses internally when comparing identifiers.
*/
-SQLITE_API int sqlite3_stricmp(const char *zLeft, const char *zRight){
+SQLITE_API int tdsqlite3_stricmp(const char *zLeft, const char *zRight){
if( zLeft==0 ){
return zRight ? -1 : 0;
}else if( zRight==0 ){
return 1;
}
- return sqlite3StrICmp(zLeft, zRight);
+ return tdsqlite3StrICmp(zLeft, zRight);
}
-SQLITE_PRIVATE int sqlite3StrICmp(const char *zLeft, const char *zRight){
+SQLITE_PRIVATE int tdsqlite3StrICmp(const char *zLeft, const char *zRight){
unsigned char *a, *b;
- int c;
+ int c, x;
a = (unsigned char *)zLeft;
b = (unsigned char *)zRight;
for(;;){
- c = (int)UpperToLower[*a] - (int)UpperToLower[*b];
- if( c || *a==0 ) break;
+ c = *a;
+ x = *b;
+ if( c==x ){
+ if( c==0 ) break;
+ }else{
+ c = (int)UpperToLower[c] - (int)UpperToLower[x];
+ if( c ) break;
+ }
a++;
b++;
}
return c;
}
-SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){
+SQLITE_API int tdsqlite3_strnicmp(const char *zLeft, const char *zRight, int N){
register unsigned char *a, *b;
if( zLeft==0 ){
return zRight ? -1 : 0;
@@ -31354,6 +35596,45 @@ SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){
}
/*
+** Compute 10 to the E-th power. Examples: E==1 results in 10.
+** E==2 results in 100. E==50 results in 1.0e50.
+**
+** This routine only works for values of E between 1 and 341.
+*/
+static LONGDOUBLE_TYPE tdsqlite3Pow10(int E){
+#if defined(_MSC_VER)
+ static const LONGDOUBLE_TYPE x[] = {
+ 1.0e+001L,
+ 1.0e+002L,
+ 1.0e+004L,
+ 1.0e+008L,
+ 1.0e+016L,
+ 1.0e+032L,
+ 1.0e+064L,
+ 1.0e+128L,
+ 1.0e+256L
+ };
+ LONGDOUBLE_TYPE r = 1.0;
+ int i;
+ assert( E>=0 && E<=307 );
+ for(i=0; E!=0; i++, E >>=1){
+ if( E & 1 ) r *= x[i];
+ }
+ return r;
+#else
+ LONGDOUBLE_TYPE x = 10.0;
+ LONGDOUBLE_TYPE r = 1.0;
+ while(1){
+ if( E & 1 ) r *= x;
+ E >>= 1;
+ if( E==0 ) break;
+ x *= x;
+ }
+ return r;
+#endif
+}
+
+/*
** The string z[] is an text representation of a real number.
** Convert this string to a double and write it into *pResult.
**
@@ -31361,8 +35642,15 @@ SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){
** uses the encoding enc. The string is not necessarily zero-terminated.
**
** Return TRUE if the result is a valid real number (or integer) and FALSE
-** if the string is empty or contains extraneous text. Valid numbers
-** are in one of these formats:
+** if the string is empty or contains extraneous text. More specifically
+** return
+** 1 => The input string is a pure integer
+** 2 or more => The input has a decimal point or eNNN clause
+** 0 or less => The input string is not a valid number
+** -1 => Not a valid number, but has a valid prefix which
+** includes a decimal point and/or an eNNN clause
+**
+** Valid numbers are in one of these formats:
**
** [+-]digits[E[+-]digits]
** [+-]digits.[digits][E[+-]digits]
@@ -31375,10 +35663,13 @@ SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){
** returns FALSE but it still converts the prefix and writes the result
** into *pResult.
*/
-SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){
+#if defined(_MSC_VER)
+#pragma warning(disable : 4756)
+#endif
+SQLITE_PRIVATE int tdsqlite3AtoF(const char *z, double *pResult, int length, u8 enc){
#ifndef SQLITE_OMIT_FLOATING_POINT
int incr;
- const char *zEnd = z + length;
+ const char *zEnd;
/* sign * significand * (10 ^ (esign * exponent)) */
int sign = 1; /* sign of significand */
i64 s = 0; /* significand */
@@ -31387,26 +35678,31 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en
int e = 0; /* exponent */
int eValid = 1; /* True exponent is either not used or is well-formed */
double result;
- int nDigits = 0;
- int nonNum = 0; /* True if input contains UTF16 with high byte non-zero */
+ int nDigit = 0; /* Number of digits processed */
+ int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */
assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE );
*pResult = 0.0; /* Default return value, in case of an error */
+ if( length==0 ) return 0;
if( enc==SQLITE_UTF8 ){
incr = 1;
+ zEnd = z + length;
}else{
int i;
incr = 2;
+ length &= ~1;
assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 );
+ testcase( enc==SQLITE_UTF16LE );
+ testcase( enc==SQLITE_UTF16BE );
for(i=3-enc; i<length && z[i]==0; i+=2){}
- nonNum = i<length;
+ if( i<length ) eType = -100;
zEnd = &z[i^1];
z += (enc&1);
}
/* skip leading spaces */
- while( z<zEnd && sqlite3Isspace(*z) ) z+=incr;
+ while( z<zEnd && tdsqlite3Isspace(*z) ) z+=incr;
if( z>=zEnd ) return 0;
/* get sign of significand */
@@ -31418,27 +35714,30 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en
}
/* copy max significant digits to significand */
- while( z<zEnd && sqlite3Isdigit(*z) && s<((LARGEST_INT64-9)/10) ){
+ while( z<zEnd && tdsqlite3Isdigit(*z) ){
s = s*10 + (*z - '0');
- z+=incr, nDigits++;
+ z+=incr; nDigit++;
+ if( s>=((LARGEST_INT64-9)/10) ){
+ /* skip non-significant significand digits
+ ** (increase exponent by d to shift decimal left) */
+ while( z<zEnd && tdsqlite3Isdigit(*z) ){ z+=incr; d++; }
+ }
}
-
- /* skip non-significant significand digits
- ** (increase exponent by d to shift decimal left) */
- while( z<zEnd && sqlite3Isdigit(*z) ) z+=incr, nDigits++, d++;
if( z>=zEnd ) goto do_atof_calc;
/* if decimal point is present */
if( *z=='.' ){
z+=incr;
+ eType++;
/* copy digits from after decimal to significand
** (decrease exponent by d to shift decimal right) */
- while( z<zEnd && sqlite3Isdigit(*z) ){
+ while( z<zEnd && tdsqlite3Isdigit(*z) ){
if( s<((LARGEST_INT64-9)/10) ){
s = s*10 + (*z - '0');
d--;
+ nDigit++;
}
- z+=incr, nDigits++;
+ z+=incr;
}
}
if( z>=zEnd ) goto do_atof_calc;
@@ -31447,6 +35746,7 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en
if( *z=='e' || *z=='E' ){
z+=incr;
eValid = 0;
+ eType++;
/* This branch is needed to avoid a (harmless) buffer overread. The
** special comment alerts the mutation tester that the correct answer
@@ -31461,7 +35761,7 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en
z+=incr;
}
/* copy digits to exponent */
- while( z<zEnd && sqlite3Isdigit(*z) ){
+ while( z<zEnd && tdsqlite3Isdigit(*z) ){
e = e<10000 ? (e*10 + (*z - '0')) : 10000;
z+=incr;
eValid = 1;
@@ -31469,7 +35769,7 @@ SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 en
}
/* skip trailing spaces */
- while( z<zEnd && sqlite3Isspace(*z) ) z+=incr;
+ while( z<zEnd && tdsqlite3Isspace(*z) ) z+=incr;
do_atof_calc:
/* adjust exponent by d, and update sign */
@@ -31508,11 +35808,10 @@ do_atof_calc:
if( e==0 ){ /*OPTIMIZATION-IF-TRUE*/
result = (double)s;
}else{
- LONGDOUBLE_TYPE scale = 1.0;
/* attempt to handle extremely small/large numbers better */
if( e>307 ){ /*OPTIMIZATION-IF-TRUE*/
if( e<342 ){ /*OPTIMIZATION-IF-TRUE*/
- while( e%308 ) { scale *= 1.0e+1; e -= 1; }
+ LONGDOUBLE_TYPE scale = tdsqlite3Pow10(e-308);
if( esign<0 ){
result = s / scale;
result /= 1.0e+308;
@@ -31524,14 +35823,15 @@ do_atof_calc:
if( esign<0 ){
result = 0.0*s;
}else{
+#ifdef INFINITY
+ result = INFINITY*s;
+#else
result = 1e308*1e308*s; /* Infinity */
+#endif
}
}
}else{
- /* 1.0e+22 is the largest power of 10 than can be
- ** represented exactly. */
- while( e%22 ) { scale *= 1.0e+1; e -= 1; }
- while( e>0 ) { scale *= 1.0e+22; e -= 22; }
+ LONGDOUBLE_TYPE scale = tdsqlite3Pow10(e);
if( esign<0 ){
result = s / scale;
}else{
@@ -31545,11 +35845,20 @@ do_atof_calc:
*pResult = result;
/* return true if number and no extra non-whitespace chracters after */
- return z==zEnd && nDigits>0 && eValid && nonNum==0;
+ if( z==zEnd && nDigit>0 && eValid && eType>0 ){
+ return eType;
+ }else if( eType>=2 && (eType==3 || eValid) && nDigit>0 ){
+ return -1;
+ }else{
+ return 0;
+ }
#else
- return !sqlite3Atoi64(z, pResult, length, enc);
+ return !tdsqlite3Atoi64(z, pResult, length, enc);
#endif /* SQLITE_OMIT_FLOATING_POINT */
}
+#if defined(_MSC_VER)
+#pragma warning(default : 4756)
+#endif
/*
** Compare the 19-character string zNum against the text representation
@@ -31586,28 +35895,26 @@ static int compare2pow63(const char *zNum, int incr){
** Convert zNum to a 64-bit signed integer. zNum must be decimal. This
** routine does *not* accept hexadecimal notation.
**
-** If the zNum value is representable as a 64-bit twos-complement
-** integer, then write that value into *pNum and return 0.
-**
-** If zNum is exactly 9223372036854775808, return 2. This special
-** case is broken out because while 9223372036854775808 cannot be a
-** signed 64-bit integer, its negative -9223372036854775808 can be.
+** Returns:
**
-** If zNum is too big for a 64-bit integer and is not
-** 9223372036854775808 or if zNum contains any non-numeric text,
-** then return 1.
+** -1 Not even a prefix of the input text looks like an integer
+** 0 Successful transformation. Fits in a 64-bit signed integer.
+** 1 Excess non-space text after the integer value
+** 2 Integer too large for a 64-bit signed integer or is malformed
+** 3 Special case of 9223372036854775808
**
** length is the number of bytes in the string (bytes, not characters).
** The string is not necessarily zero-terminated. The encoding is
** given by enc.
*/
-SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){
+SQLITE_PRIVATE int tdsqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc){
int incr;
u64 u = 0;
int neg = 0; /* assume positive */
int i;
int c = 0;
int nonNum = 0; /* True if input contains UTF16 with high byte non-zero */
+ int rc; /* Baseline return code */
const char *zStart;
const char *zEnd = zNum + length;
assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE );
@@ -31621,7 +35928,7 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc
zEnd = &zNum[i^1];
zNum += (enc&1);
}
- while( zNum<zEnd && sqlite3Isspace(*zNum) ) zNum+=incr;
+ while( zNum<zEnd && tdsqlite3Isspace(*zNum) ) zNum+=incr;
if( zNum<zEnd ){
if( *zNum=='-' ){
neg = 1;
@@ -31635,43 +35942,57 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc
for(i=0; &zNum[i]<zEnd && (c=zNum[i])>='0' && c<='9'; i+=incr){
u = u*10 + c - '0';
}
+ testcase( i==18*incr );
+ testcase( i==19*incr );
+ testcase( i==20*incr );
if( u>LARGEST_INT64 ){
+ /* This test and assignment is needed only to suppress UB warnings
+ ** from clang and -fsanitize=undefined. This test and assignment make
+ ** the code a little larger and slower, and no harm comes from omitting
+ ** them, but we must appaise the undefined-behavior pharisees. */
*pNum = neg ? SMALLEST_INT64 : LARGEST_INT64;
}else if( neg ){
*pNum = -(i64)u;
}else{
*pNum = (i64)u;
}
- testcase( i==18 );
- testcase( i==19 );
- testcase( i==20 );
- if( &zNum[i]<zEnd /* Extra bytes at the end */
- || (i==0 && zStart==zNum) /* No digits */
- || i>19*incr /* Too many digits */
- || nonNum /* UTF16 with high-order bytes non-zero */
- ){
- /* zNum is empty or contains non-numeric text or is longer
- ** than 19 digits (thus guaranteeing that it is too large) */
- return 1;
- }else if( i<19*incr ){
+ rc = 0;
+ if( i==0 && zStart==zNum ){ /* No digits */
+ rc = -1;
+ }else if( nonNum ){ /* UTF16 with high-order bytes non-zero */
+ rc = 1;
+ }else if( &zNum[i]<zEnd ){ /* Extra bytes at the end */
+ int jj = i;
+ do{
+ if( !tdsqlite3Isspace(zNum[jj]) ){
+ rc = 1; /* Extra non-space text after the integer */
+ break;
+ }
+ jj += incr;
+ }while( &zNum[jj]<zEnd );
+ }
+ if( i<19*incr ){
/* Less than 19 digits, so we know that it fits in 64 bits */
assert( u<=LARGEST_INT64 );
- return 0;
+ return rc;
}else{
/* zNum is a 19-digit numbers. Compare it against 9223372036854775808. */
- c = compare2pow63(zNum, incr);
+ c = i>19*incr ? 1 : compare2pow63(zNum, incr);
if( c<0 ){
/* zNum is less than 9223372036854775808 so it fits */
assert( u<=LARGEST_INT64 );
- return 0;
- }else if( c>0 ){
- /* zNum is greater than 9223372036854775808 so it overflows */
- return 1;
+ return rc;
}else{
- /* zNum is exactly 9223372036854775808. Fits if negative. The
- ** special case 2 overflow if positive */
- assert( u-1==LARGEST_INT64 );
- return neg ? 0 : 2;
+ *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64;
+ if( c>0 ){
+ /* zNum is greater than 9223372036854775808 so it overflows */
+ return 2;
+ }else{
+ /* zNum is exactly 9223372036854775808. Fits if negative. The
+ ** special case 2 overflow if positive */
+ assert( u-1==LARGEST_INT64 );
+ return neg ? rc : 3;
+ }
}
}
}
@@ -31679,15 +36000,16 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc
/*
** Transform a UTF-8 integer literal, in either decimal or hexadecimal,
** into a 64-bit signed integer. This routine accepts hexadecimal literals,
-** whereas sqlite3Atoi64() does not.
+** whereas tdsqlite3Atoi64() does not.
**
** Returns:
**
** 0 Successful transformation. Fits in a 64-bit signed integer.
-** 1 Integer too large for a 64-bit signed integer or is malformed
-** 2 Special case of 9223372036854775808
+** 1 Excess text after the integer value
+** 2 Integer too large for a 64-bit signed integer or is malformed
+** 3 Special case of 9223372036854775808
*/
-SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char *z, i64 *pOut){
+SQLITE_PRIVATE int tdsqlite3DecOrHexToI64(const char *z, i64 *pOut){
#ifndef SQLITE_OMIT_HEX_INTEGER
if( z[0]=='0'
&& (z[1]=='x' || z[1]=='X')
@@ -31695,15 +36017,15 @@ SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char *z, i64 *pOut){
u64 u = 0;
int i, k;
for(i=2; z[i]=='0'; i++){}
- for(k=i; sqlite3Isxdigit(z[k]); k++){
- u = u*16 + sqlite3HexToInt(z[k]);
+ for(k=i; tdsqlite3Isxdigit(z[k]); k++){
+ u = u*16 + tdsqlite3HexToInt(z[k]);
}
memcpy(pOut, &u, 8);
- return (z[k]==0 && k-i<=16) ? 0 : 1;
+ return (z[k]==0 && k-i<=16) ? 0 : 2;
}else
#endif /* SQLITE_OMIT_HEX_INTEGER */
{
- return sqlite3Atoi64(z, pOut, sqlite3Strlen30(z), SQLITE_UTF8);
+ return tdsqlite3Atoi64(z, pOut, tdsqlite3Strlen30(z), SQLITE_UTF8);
}
}
@@ -31714,10 +36036,10 @@ SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char *z, i64 *pOut){
** This routine accepts both decimal and hexadecimal notation for integers.
**
** Any non-numeric characters that following zNum are ignored.
-** This is different from sqlite3Atoi64() which requires the
+** This is different from tdsqlite3Atoi64() which requires the
** input number to be zero-terminated.
*/
-SQLITE_PRIVATE int sqlite3GetInt32(const char *zNum, int *pValue){
+SQLITE_PRIVATE int tdsqlite3GetInt32(const char *zNum, int *pValue){
sqlite_int64 v = 0;
int i, c;
int neg = 0;
@@ -31730,15 +36052,15 @@ SQLITE_PRIVATE int sqlite3GetInt32(const char *zNum, int *pValue){
#ifndef SQLITE_OMIT_HEX_INTEGER
else if( zNum[0]=='0'
&& (zNum[1]=='x' || zNum[1]=='X')
- && sqlite3Isxdigit(zNum[2])
+ && tdsqlite3Isxdigit(zNum[2])
){
u32 u = 0;
zNum += 2;
while( zNum[0]=='0' ) zNum++;
- for(i=0; sqlite3Isxdigit(zNum[i]) && i<8; i++){
- u = u*16 + sqlite3HexToInt(zNum[i]);
+ for(i=0; tdsqlite3Isxdigit(zNum[i]) && i<8; i++){
+ u = u*16 + tdsqlite3HexToInt(zNum[i]);
}
- if( (u&0x80000000)==0 && sqlite3Isxdigit(zNum[i])==0 ){
+ if( (u&0x80000000)==0 && tdsqlite3Isxdigit(zNum[i])==0 ){
memcpy(pValue, &u, 4);
return 1;
}else{
@@ -31746,6 +36068,7 @@ SQLITE_PRIVATE int sqlite3GetInt32(const char *zNum, int *pValue){
}
}
#endif
+ if( !tdsqlite3Isdigit(zNum[0]) ) return 0;
while( zNum[0]=='0' ) zNum++;
for(i=0; i<11 && (c = zNum[i] - '0')>=0 && c<=9; i++){
v = v*10 + c;
@@ -31775,9 +36098,9 @@ SQLITE_PRIVATE int sqlite3GetInt32(const char *zNum, int *pValue){
** Return a 32-bit integer value extracted from a string. If the
** string is not an integer, just return 0.
*/
-SQLITE_PRIVATE int sqlite3Atoi(const char *z){
+SQLITE_PRIVATE int tdsqlite3Atoi(const char *z){
int x = 0;
- if( z ) sqlite3GetInt32(z, &x);
+ if( z ) tdsqlite3GetInt32(z, &x);
return x;
}
@@ -31834,7 +36157,7 @@ static int SQLITE_NOINLINE putVarint64(unsigned char *p, u64 v){
}
return n;
}
-SQLITE_PRIVATE int sqlite3PutVarint(unsigned char *p, u64 v){
+SQLITE_PRIVATE int tdsqlite3PutVarint(unsigned char *p, u64 v){
if( v<=0x7f ){
p[0] = v&0x7f;
return 1;
@@ -31848,7 +36171,7 @@ SQLITE_PRIVATE int sqlite3PutVarint(unsigned char *p, u64 v){
}
/*
-** Bitmasks used by sqlite3GetVarint(). These precomputed constants
+** Bitmasks used by tdsqlite3GetVarint(). These precomputed constants
** are defined here rather than simply putting the constant expressions
** inline in order to work around bugs in the RVT compiler.
**
@@ -31864,26 +36187,15 @@ SQLITE_PRIVATE int sqlite3PutVarint(unsigned char *p, u64 v){
** Read a 64-bit variable-length integer from memory starting at p[0].
** Return the number of bytes read. The value is stored in *v.
*/
-SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *p, u64 *v){
+SQLITE_PRIVATE u8 tdsqlite3GetVarint(const unsigned char *p, u64 *v){
u32 a,b,s;
- a = *p;
- /* a: p0 (unmasked) */
- if (!(a&0x80))
- {
- *v = a;
+ if( ((signed char*)p)[0]>=0 ){
+ *v = *p;
return 1;
}
-
- p++;
- b = *p;
- /* b: p1 (unmasked) */
- if (!(b&0x80))
- {
- a &= 0x7f;
- a = a<<7;
- a |= b;
- *v = a;
+ if( ((signed char*)p)[1]>=0 ){
+ *v = ((u32)(p[0]&0x7f)<<7) | p[1];
return 2;
}
@@ -31891,8 +36203,9 @@ SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *p, u64 *v){
assert( SLOT_2_0 == ((0x7f<<14) | (0x7f)) );
assert( SLOT_4_2_0 == ((0xfU<<28) | (0x7f<<14) | (0x7f)) );
- p++;
- a = a<<14;
+ a = ((u32)p[0])<<14;
+ b = p[1];
+ p += 2;
a |= *p;
/* a: p0<<14 | p2 (unmasked) */
if (!(a&0x80))
@@ -32035,7 +36348,7 @@ SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *p, u64 *v){
** single-byte case. All code should use the MACRO version as
** this function assumes the single-byte case has already been handled.
*/
-SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){
+SQLITE_PRIVATE u8 tdsqlite3GetVarint32(const unsigned char *p, u32 *v){
u32 a,b;
/* The 1-byte case. Overwhelmingly the most common. Handled inline
@@ -32094,7 +36407,7 @@ SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){
u8 n;
p -= 2;
- n = sqlite3GetVarint(p, &v64);
+ n = tdsqlite3GetVarint(p, &v64);
assert( n>3 && n<=9 );
if( (v64 & SQLITE_MAX_U32)!=v64 ){
*v = 0xffffffff;
@@ -32139,14 +36452,14 @@ SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){
/* We can only reach this point when reading a corrupt database
** file. In that case we are not in any hurry. Use the (relatively
- ** slow) general-purpose sqlite3GetVarint() routine to extract the
+ ** slow) general-purpose tdsqlite3GetVarint() routine to extract the
** value. */
{
u64 v64;
u8 n;
p -= 4;
- n = sqlite3GetVarint(p, &v64);
+ n = tdsqlite3GetVarint(p, &v64);
assert( n>5 && n<=9 );
*v = (u32)v64;
return n;
@@ -32158,7 +36471,7 @@ SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){
** Return the number of bytes that will be needed to store the given
** 64-bit integer.
*/
-SQLITE_PRIVATE int sqlite3VarintLen(u64 v){
+SQLITE_PRIVATE int tdsqlite3VarintLen(u64 v){
int i;
for(i=1; (v >>= 7)!=0; i++){ assert( i<10 ); }
return i;
@@ -32168,18 +36481,16 @@ SQLITE_PRIVATE int sqlite3VarintLen(u64 v){
/*
** Read or write a four-byte big-endian integer value.
*/
-SQLITE_PRIVATE u32 sqlite3Get4byte(const u8 *p){
+SQLITE_PRIVATE u32 tdsqlite3Get4byte(const u8 *p){
#if SQLITE_BYTEORDER==4321
u32 x;
memcpy(&x,p,4);
return x;
-#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \
- && defined(__GNUC__) && GCC_VERSION>=4003000
+#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
u32 x;
memcpy(&x,p,4);
return __builtin_bswap32(x);
-#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \
- && defined(_MSC_VER) && _MSC_VER>=1300
+#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
u32 x;
memcpy(&x,p,4);
return _byteswap_ulong(x);
@@ -32188,15 +36499,13 @@ SQLITE_PRIVATE u32 sqlite3Get4byte(const u8 *p){
return ((unsigned)p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
#endif
}
-SQLITE_PRIVATE void sqlite3Put4byte(unsigned char *p, u32 v){
+SQLITE_PRIVATE void tdsqlite3Put4byte(unsigned char *p, u32 v){
#if SQLITE_BYTEORDER==4321
memcpy(p,&v,4);
-#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \
- && defined(__GNUC__) && GCC_VERSION>=4003000
+#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
u32 x = __builtin_bswap32(v);
memcpy(p,&x,4);
-#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \
- && defined(_MSC_VER) && _MSC_VER>=1300
+#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
u32 x = _byteswap_ulong(v);
memcpy(p,&x,4);
#else
@@ -32214,7 +36523,7 @@ SQLITE_PRIVATE void sqlite3Put4byte(unsigned char *p, u32 v){
** This routine only works if h really is a valid hexadecimal
** character: 0..9a..fA..F
*/
-SQLITE_PRIVATE u8 sqlite3HexToInt(int h){
+SQLITE_PRIVATE u8 tdsqlite3HexToInt(int h){
assert( (h>='0' && h<='9') || (h>='a' && h<='f') || (h>='A' && h<='F') );
#ifdef SQLITE_ASCII
h += 9*(1&(h>>6));
@@ -32232,15 +36541,15 @@ SQLITE_PRIVATE u8 sqlite3HexToInt(int h){
** binary value has been obtained from malloc and must be freed by
** the calling routine.
*/
-SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3 *db, const char *z, int n){
+SQLITE_PRIVATE void *tdsqlite3HexToBlob(tdsqlite3 *db, const char *z, int n){
char *zBlob;
int i;
- zBlob = (char *)sqlite3DbMallocRawNN(db, n/2 + 1);
+ zBlob = (char *)tdsqlite3DbMallocRawNN(db, n/2 + 1);
n--;
if( zBlob ){
for(i=0; i<n; i+=2){
- zBlob[i/2] = (sqlite3HexToInt(z[i])<<4) | sqlite3HexToInt(z[i+1]);
+ zBlob[i/2] = (tdsqlite3HexToInt(z[i])<<4) | tdsqlite3HexToInt(z[i+1]);
}
zBlob[i/2] = 0;
}
@@ -32254,7 +36563,7 @@ SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3 *db, const char *z, int n){
** argument. The zType is a word like "NULL" or "closed" or "invalid".
*/
static void logBadConnection(const char *zType){
- sqlite3_log(SQLITE_MISUSE,
+ tdsqlite3_log(SQLITE_MISUSE,
"API call with %s database connection pointer",
zType
);
@@ -32269,12 +36578,12 @@ static void logBadConnection(const char *zType){
** dereferenced for any reason. The calling function should invoke
** SQLITE_MISUSE immediately.
**
-** sqlite3SafetyCheckOk() requires that the db pointer be valid for
-** use. sqlite3SafetyCheckSickOrOk() allows a db pointer that failed to
+** tdsqlite3SafetyCheckOk() requires that the db pointer be valid for
+** use. tdsqlite3SafetyCheckSickOrOk() allows a db pointer that failed to
** open properly and is not fit for general use but which can be
-** used as an argument to sqlite3_errmsg() or sqlite3_close().
+** used as an argument to tdsqlite3_errmsg() or tdsqlite3_close().
*/
-SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3 *db){
+SQLITE_PRIVATE int tdsqlite3SafetyCheckOk(tdsqlite3 *db){
u32 magic;
if( db==0 ){
logBadConnection("NULL");
@@ -32282,8 +36591,8 @@ SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3 *db){
}
magic = db->magic;
if( magic!=SQLITE_MAGIC_OPEN ){
- if( sqlite3SafetyCheckSickOrOk(db) ){
- testcase( sqlite3GlobalConfig.xLog!=0 );
+ if( tdsqlite3SafetyCheckSickOrOk(db) ){
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
logBadConnection("unopened");
}
return 0;
@@ -32291,13 +36600,13 @@ SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3 *db){
return 1;
}
}
-SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){
+SQLITE_PRIVATE int tdsqlite3SafetyCheckSickOrOk(tdsqlite3 *db){
u32 magic;
magic = db->magic;
if( magic!=SQLITE_MAGIC_SICK &&
magic!=SQLITE_MAGIC_OPEN &&
magic!=SQLITE_MAGIC_BUSY ){
- testcase( sqlite3GlobalConfig.xLog!=0 );
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
logBadConnection("invalid");
return 0;
}else{
@@ -32311,7 +36620,10 @@ SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){
** Return 0 on success. Or if the operation would have resulted in an
** overflow, leave *pA unchanged and return 1.
*/
-SQLITE_PRIVATE int sqlite3AddInt64(i64 *pA, i64 iB){
+SQLITE_PRIVATE int tdsqlite3AddInt64(i64 *pA, i64 iB){
+#if GCC_VERSION>=5004000 && !defined(__INTEL_COMPILER)
+ return __builtin_add_overflow(*pA, iB, pA);
+#else
i64 iA = *pA;
testcase( iA==0 ); testcase( iA==1 );
testcase( iB==-1 ); testcase( iB==0 );
@@ -32326,8 +36638,12 @@ SQLITE_PRIVATE int sqlite3AddInt64(i64 *pA, i64 iB){
}
*pA += iB;
return 0;
+#endif
}
-SQLITE_PRIVATE int sqlite3SubInt64(i64 *pA, i64 iB){
+SQLITE_PRIVATE int tdsqlite3SubInt64(i64 *pA, i64 iB){
+#if GCC_VERSION>=5004000 && !defined(__INTEL_COMPILER)
+ return __builtin_sub_overflow(*pA, iB, pA);
+#else
testcase( iB==SMALLEST_INT64+1 );
if( iB==SMALLEST_INT64 ){
testcase( (*pA)==(-1) ); testcase( (*pA)==0 );
@@ -32335,10 +36651,14 @@ SQLITE_PRIVATE int sqlite3SubInt64(i64 *pA, i64 iB){
*pA -= iB;
return 0;
}else{
- return sqlite3AddInt64(pA, -iB);
+ return tdsqlite3AddInt64(pA, -iB);
}
+#endif
}
-SQLITE_PRIVATE int sqlite3MulInt64(i64 *pA, i64 iB){
+SQLITE_PRIVATE int tdsqlite3MulInt64(i64 *pA, i64 iB){
+#if GCC_VERSION>=5004000 && !defined(__INTEL_COMPILER)
+ return __builtin_mul_overflow(*pA, iB, pA);
+#else
i64 iA = *pA;
if( iB>0 ){
if( iA>LARGEST_INT64/iB ) return 1;
@@ -32354,13 +36674,14 @@ SQLITE_PRIVATE int sqlite3MulInt64(i64 *pA, i64 iB){
}
*pA = iA*iB;
return 0;
+#endif
}
/*
** Compute the absolute value of a 32-bit signed integer, of possible. Or
** if the integer has a value of -2147483648, return +2147483647
*/
-SQLITE_PRIVATE int sqlite3AbsInt32(int x){
+SQLITE_PRIVATE int tdsqlite3AbsInt32(int x){
if( x>=0 ) return x;
if( x==(int)0x80000000 ) return 0x7fffffff;
return -x;
@@ -32384,13 +36705,13 @@ SQLITE_PRIVATE int sqlite3AbsInt32(int x){
** test.db-shm => test.shm
** test.db-mj7f3319fa => test.9fa
*/
-SQLITE_PRIVATE void sqlite3FileSuffix3(const char *zBaseFilename, char *z){
+SQLITE_PRIVATE void tdsqlite3FileSuffix3(const char *zBaseFilename, char *z){
#if SQLITE_ENABLE_8_3_NAMES<2
- if( sqlite3_uri_boolean(zBaseFilename, "8_3_names", 0) )
+ if( tdsqlite3_uri_boolean(zBaseFilename, "8_3_names", 0) )
#endif
{
int i, sz;
- sz = sqlite3Strlen30(z);
+ sz = tdsqlite3Strlen30(z);
for(i=sz-1; i>0 && z[i]!='/' && z[i]!='.'; i--){}
if( z[i]=='.' && ALWAYS(sz>i+4) ) memmove(&z[i+1], &z[sz-3], 4);
}
@@ -32403,7 +36724,7 @@ SQLITE_PRIVATE void sqlite3FileSuffix3(const char *zBaseFilename, char *z){
** value.
**
*/
-SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst a, LogEst b){
+SQLITE_PRIVATE LogEst tdsqlite3LogEstAdd(LogEst a, LogEst b){
static const unsigned char x[] = {
10, 10, /* 0,1 */
9, 9, /* 2,3 */
@@ -32430,15 +36751,21 @@ SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst a, LogEst b){
** Convert an integer into a LogEst. In other words, compute an
** approximation for 10*log2(x).
*/
-SQLITE_PRIVATE LogEst sqlite3LogEst(u64 x){
+SQLITE_PRIVATE LogEst tdsqlite3LogEst(u64 x){
static LogEst a[] = { 0, 2, 3, 5, 6, 7, 8, 9 };
LogEst y = 40;
if( x<8 ){
if( x<2 ) return 0;
while( x<8 ){ y -= 10; x <<= 1; }
}else{
+#if GCC_VERSION>=5004000
+ int i = 60 - __builtin_clzll(x);
+ y += i*10;
+ x >>= i;
+#else
while( x>255 ){ y += 40; x >>= 4; } /*OPTIMIZATION-IF-TRUE*/
while( x>15 ){ y += 10; x >>= 1; }
+#endif
}
return a[x&7] + y - 10;
}
@@ -32448,12 +36775,12 @@ SQLITE_PRIVATE LogEst sqlite3LogEst(u64 x){
** Convert a double into a LogEst
** In other words, compute an approximation for 10*log2(x).
*/
-SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double x){
+SQLITE_PRIVATE LogEst tdsqlite3LogEstFromDouble(double x){
u64 a;
LogEst e;
assert( sizeof(x)==8 && sizeof(a)==8 );
if( x<=1 ) return 0;
- if( x<=2000000000 ) return sqlite3LogEst((u64)x);
+ if( x<=2000000000 ) return tdsqlite3LogEst((u64)x);
memcpy(&a, &x, 8);
e = (a>>52) - 1022;
return e*10;
@@ -32461,7 +36788,7 @@ SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double x){
#endif /* SQLITE_OMIT_VIRTUALTABLE */
#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \
- defined(SQLITE_ENABLE_STAT3_OR_STAT4) || \
+ defined(SQLITE_ENABLE_STAT4) || \
defined(SQLITE_EXPLAIN_ESTIMATED_ROWS)
/*
** Convert a LogEst into an integer.
@@ -32469,7 +36796,7 @@ SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double x){
** Note that this routine is only used when one or more of various
** non-standard compile-time options is enabled.
*/
-SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){
+SQLITE_PRIVATE u64 tdsqlite3LogEstToInt(LogEst x){
u64 n;
n = x%10;
x /= 10;
@@ -32479,7 +36806,7 @@ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){
defined(SQLITE_EXPLAIN_ESTIMATED_ROWS)
if( x>60 ) return (u64)LARGEST_INT64;
#else
- /* If only SQLITE_ENABLE_STAT3_OR_STAT4 is on, then the largest input
+ /* If only SQLITE_ENABLE_STAT4 is on, then the largest input
** possible to this routine is 310, resulting in a maximum x of 31 */
assert( x<=60 );
#endif
@@ -32487,6 +36814,109 @@ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){
}
#endif /* defined SCANSTAT or STAT4 or ESTIMATED_ROWS */
+/*
+** Add a new name/number pair to a VList. This might require that the
+** VList object be reallocated, so return the new VList. If an OOM
+** error occurs, the original VList returned and the
+** db->mallocFailed flag is set.
+**
+** A VList is really just an array of integers. To destroy a VList,
+** simply pass it to tdsqlite3DbFree().
+**
+** The first integer is the number of integers allocated for the whole
+** VList. The second integer is the number of integers actually used.
+** Each name/number pair is encoded by subsequent groups of 3 or more
+** integers.
+**
+** Each name/number pair starts with two integers which are the numeric
+** value for the pair and the size of the name/number pair, respectively.
+** The text name overlays one or more following integers. The text name
+** is always zero-terminated.
+**
+** Conceptually:
+**
+** struct VList {
+** int nAlloc; // Number of allocated slots
+** int nUsed; // Number of used slots
+** struct VListEntry {
+** int iValue; // Value for this entry
+** int nSlot; // Slots used by this entry
+** // ... variable name goes here
+** } a[0];
+** }
+**
+** During code generation, pointers to the variable names within the
+** VList are taken. When that happens, nAlloc is set to zero as an
+** indication that the VList may never again be enlarged, since the
+** accompanying realloc() would invalidate the pointers.
+*/
+SQLITE_PRIVATE VList *tdsqlite3VListAdd(
+ tdsqlite3 *db, /* The database connection used for malloc() */
+ VList *pIn, /* The input VList. Might be NULL */
+ const char *zName, /* Name of symbol to add */
+ int nName, /* Bytes of text in zName */
+ int iVal /* Value to associate with zName */
+){
+ int nInt; /* number of sizeof(int) objects needed for zName */
+ char *z; /* Pointer to where zName will be stored */
+ int i; /* Index in pIn[] where zName is stored */
+
+ nInt = nName/4 + 3;
+ assert( pIn==0 || pIn[0]>=3 ); /* Verify ok to add new elements */
+ if( pIn==0 || pIn[1]+nInt > pIn[0] ){
+ /* Enlarge the allocation */
+ tdsqlite3_int64 nAlloc = (pIn ? 2*(tdsqlite3_int64)pIn[0] : 10) + nInt;
+ VList *pOut = tdsqlite3DbRealloc(db, pIn, nAlloc*sizeof(int));
+ if( pOut==0 ) return pIn;
+ if( pIn==0 ) pOut[1] = 2;
+ pIn = pOut;
+ pIn[0] = nAlloc;
+ }
+ i = pIn[1];
+ pIn[i] = iVal;
+ pIn[i+1] = nInt;
+ z = (char*)&pIn[i+2];
+ pIn[1] = i+nInt;
+ assert( pIn[1]<=pIn[0] );
+ memcpy(z, zName, nName);
+ z[nName] = 0;
+ return pIn;
+}
+
+/*
+** Return a pointer to the name of a variable in the given VList that
+** has the value iVal. Or return a NULL if there is no such variable in
+** the list
+*/
+SQLITE_PRIVATE const char *tdsqlite3VListNumToName(VList *pIn, int iVal){
+ int i, mx;
+ if( pIn==0 ) return 0;
+ mx = pIn[1];
+ i = 2;
+ do{
+ if( pIn[i]==iVal ) return (char*)&pIn[i+2];
+ i += pIn[i+1];
+ }while( i<mx );
+ return 0;
+}
+
+/*
+** Return the number of the variable named zName, if it is in VList.
+** or return 0 if there is no such variable.
+*/
+SQLITE_PRIVATE int tdsqlite3VListNameToNum(VList *pIn, const char *zName, int nName){
+ int i, mx;
+ if( pIn==0 ) return 0;
+ mx = pIn[1];
+ i = 2;
+ do{
+ const char *z = (const char*)&pIn[i+2];
+ if( strncmp(z,zName,nName)==0 && z[nName]==0 ) return pIn[i];
+ i += pIn[i+1];
+ }while( i<mx );
+ return 0;
+}
+
/************** End of util.c ************************************************/
/************** Begin file hash.c ********************************************/
/*
@@ -32511,7 +36941,7 @@ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){
**
** "pNew" is a pointer to the hash table that is to be initialized.
*/
-SQLITE_PRIVATE void sqlite3HashInit(Hash *pNew){
+SQLITE_PRIVATE void tdsqlite3HashInit(Hash *pNew){
assert( pNew!=0 );
pNew->first = 0;
pNew->count = 0;
@@ -32523,18 +36953,18 @@ SQLITE_PRIVATE void sqlite3HashInit(Hash *pNew){
** Call this routine to delete a hash table or to reset a hash table
** to the empty state.
*/
-SQLITE_PRIVATE void sqlite3HashClear(Hash *pH){
+SQLITE_PRIVATE void tdsqlite3HashClear(Hash *pH){
HashElem *elem; /* For looping over all elements of the table */
assert( pH!=0 );
elem = pH->first;
pH->first = 0;
- sqlite3_free(pH->ht);
+ tdsqlite3_free(pH->ht);
pH->ht = 0;
pH->htsize = 0;
while( elem ){
HashElem *next_elem = elem->next;
- sqlite3_free(elem);
+ tdsqlite3_free(elem);
elem = next_elem;
}
pH->count = 0;
@@ -32550,7 +36980,7 @@ static unsigned int strHash(const char *z){
/* Knuth multiplicative hashing. (Sorting & Searching, p. 510).
** 0x9e3779b1 is 2654435761 which is the closest prime number to
** (2**32)*golden_ratio, where golden_ratio = (sqrt(5) - 1)/2. */
- h += sqlite3UpperToLower[c];
+ h += tdsqlite3UpperToLower[c];
h *= 0x9e3779b1;
}
return h;
@@ -32590,7 +37020,7 @@ static void insertElement(
/* Resize the hash table so that it cantains "new_size" buckets.
**
-** The hash table might fail to resize if sqlite3_malloc() fails or
+** The hash table might fail to resize if tdsqlite3_malloc() fails or
** if the new size is the same as the prior size.
** Return TRUE if the resize occurs and false if not.
*/
@@ -32607,20 +37037,20 @@ static int rehash(Hash *pH, unsigned int new_size){
/* The inability to allocates space for a larger hash table is
** a performance hit but it is not a fatal error. So mark the
- ** allocation as a benign. Use sqlite3Malloc()/memset(0) instead of
- ** sqlite3MallocZero() to make the allocation, as sqlite3MallocZero()
+ ** allocation as a benign. Use tdsqlite3Malloc()/memset(0) instead of
+ ** tdsqlite3MallocZero() to make the allocation, as tdsqlite3MallocZero()
** only zeroes the requested number of bytes whereas this module will
** use the actual amount of space allocated for the hash table (which
** may be larger than the requested amount).
*/
- sqlite3BeginBenignMalloc();
- new_ht = (struct _ht *)sqlite3Malloc( new_size*sizeof(struct _ht) );
- sqlite3EndBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
+ new_ht = (struct _ht *)tdsqlite3Malloc( new_size*sizeof(struct _ht) );
+ tdsqlite3EndBenignMalloc();
if( new_ht==0 ) return 0;
- sqlite3_free(pH->ht);
+ tdsqlite3_free(pH->ht);
pH->ht = new_ht;
- pH->htsize = new_size = sqlite3MallocSize(new_ht)/sizeof(struct _ht);
+ pH->htsize = new_size = tdsqlite3MallocSize(new_ht)/sizeof(struct _ht);
memset(new_ht, 0, new_size*sizeof(struct _ht));
for(elem=pH->first, pH->first=0; elem; elem = next_elem){
unsigned int h = strHash(elem->pKey) % new_size;
@@ -32631,8 +37061,9 @@ static int rehash(Hash *pH, unsigned int new_size){
}
/* This function (for internal use only) locates an element in an
-** hash table that matches the given key. The hash for this key is
-** also computed and returned in the *pH parameter.
+** hash table that matches the given key. If no element is found,
+** a pointer to a static null element with HashElem.data==0 is returned.
+** If pH is not NULL, then the hash for this key is written to *pH.
*/
static HashElem *findElementWithHash(
const Hash *pH, /* The pH to be searched */
@@ -32640,8 +37071,9 @@ static HashElem *findElementWithHash(
unsigned int *pHash /* Write the hash value here */
){
HashElem *elem; /* Used to loop thru the element list */
- int count; /* Number of elements left to test */
+ unsigned int count; /* Number of elements left to test */
unsigned int h; /* The computed hash */
+ static HashElem nullElement = { 0, 0, 0, 0 };
if( pH->ht ){ /*OPTIMIZATION-IF-TRUE*/
struct _ht *pEntry;
@@ -32654,15 +37086,15 @@ static HashElem *findElementWithHash(
elem = pH->first;
count = pH->count;
}
- *pHash = h;
+ if( pHash ) *pHash = h;
while( count-- ){
assert( elem!=0 );
- if( sqlite3StrICmp(elem->pKey,pKey)==0 ){
+ if( tdsqlite3StrICmp(elem->pKey,pKey)==0 ){
return elem;
}
elem = elem->next;
}
- return 0;
+ return &nullElement;
}
/* Remove a single entry from the hash table given a pointer to that
@@ -32687,15 +37119,15 @@ static void removeElementGivenHash(
if( pEntry->chain==elem ){
pEntry->chain = elem->next;
}
+ assert( pEntry->count>0 );
pEntry->count--;
- assert( pEntry->count>=0 );
}
- sqlite3_free( elem );
+ tdsqlite3_free( elem );
pH->count--;
if( pH->count==0 ){
assert( pH->first==0 );
assert( pH->count==0 );
- sqlite3HashClear(pH);
+ tdsqlite3HashClear(pH);
}
}
@@ -32703,14 +37135,10 @@ static void removeElementGivenHash(
** that matches pKey. Return the data for this element if it is
** found, or NULL if there is no match.
*/
-SQLITE_PRIVATE void *sqlite3HashFind(const Hash *pH, const char *pKey){
- HashElem *elem; /* The element that matches key */
- unsigned int h; /* A hash on key */
-
+SQLITE_PRIVATE void *tdsqlite3HashFind(const Hash *pH, const char *pKey){
assert( pH!=0 );
assert( pKey!=0 );
- elem = findElementWithHash(pH, pKey, &h);
- return elem ? elem->data : 0;
+ return findElementWithHash(pH, pKey, 0)->data;
}
/* Insert an element into the hash table pH. The key is pKey
@@ -32727,7 +37155,7 @@ SQLITE_PRIVATE void *sqlite3HashFind(const Hash *pH, const char *pKey){
** If the "data" parameter to this function is NULL, then the
** element corresponding to "key" is removed from the hash table.
*/
-SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){
+SQLITE_PRIVATE void *tdsqlite3HashInsert(Hash *pH, const char *pKey, void *data){
unsigned int h; /* the hash of the key modulo hash table size */
HashElem *elem; /* Used to loop thru the element list */
HashElem *new_elem; /* New element added to the pH */
@@ -32735,7 +37163,7 @@ SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){
assert( pH!=0 );
assert( pKey!=0 );
elem = findElementWithHash(pH,pKey,&h);
- if( elem ){
+ if( elem->data ){
void *old_data = elem->data;
if( data==0 ){
removeElementGivenHash(pH,elem,h);
@@ -32746,7 +37174,7 @@ SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){
return old_data;
}
if( data==0 ) return 0;
- new_elem = (HashElem*)sqlite3Malloc( sizeof(HashElem) );
+ new_elem = (HashElem*)tdsqlite3Malloc( sizeof(HashElem) );
if( new_elem==0 ) return data;
new_elem->pKey = pKey;
new_elem->data = data;
@@ -32773,171 +37201,184 @@ SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){
#else
# define OpHelp(X)
#endif
-SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
+SQLITE_PRIVATE const char *tdsqlite3OpcodeName(int i){
static const char *const azName[] = {
/* 0 */ "Savepoint" OpHelp(""),
/* 1 */ "AutoCommit" OpHelp(""),
/* 2 */ "Transaction" OpHelp(""),
/* 3 */ "SorterNext" OpHelp(""),
- /* 4 */ "PrevIfOpen" OpHelp(""),
- /* 5 */ "NextIfOpen" OpHelp(""),
- /* 6 */ "Prev" OpHelp(""),
- /* 7 */ "Next" OpHelp(""),
- /* 8 */ "Checkpoint" OpHelp(""),
- /* 9 */ "JournalMode" OpHelp(""),
- /* 10 */ "Vacuum" OpHelp(""),
- /* 11 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"),
- /* 12 */ "VUpdate" OpHelp("data=r[P3@P2]"),
- /* 13 */ "Goto" OpHelp(""),
- /* 14 */ "Gosub" OpHelp(""),
- /* 15 */ "InitCoroutine" OpHelp(""),
- /* 16 */ "Yield" OpHelp(""),
- /* 17 */ "MustBeInt" OpHelp(""),
- /* 18 */ "Jump" OpHelp(""),
+ /* 4 */ "Prev" OpHelp(""),
+ /* 5 */ "Next" OpHelp(""),
+ /* 6 */ "Checkpoint" OpHelp(""),
+ /* 7 */ "JournalMode" OpHelp(""),
+ /* 8 */ "Vacuum" OpHelp(""),
+ /* 9 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"),
+ /* 10 */ "VUpdate" OpHelp("data=r[P3@P2]"),
+ /* 11 */ "Goto" OpHelp(""),
+ /* 12 */ "Gosub" OpHelp(""),
+ /* 13 */ "InitCoroutine" OpHelp(""),
+ /* 14 */ "Yield" OpHelp(""),
+ /* 15 */ "MustBeInt" OpHelp(""),
+ /* 16 */ "Jump" OpHelp(""),
+ /* 17 */ "Once" OpHelp(""),
+ /* 18 */ "If" OpHelp(""),
/* 19 */ "Not" OpHelp("r[P2]= !r[P1]"),
- /* 20 */ "Once" OpHelp(""),
- /* 21 */ "If" OpHelp(""),
- /* 22 */ "IfNot" OpHelp(""),
- /* 23 */ "SeekLT" OpHelp("key=r[P3@P4]"),
- /* 24 */ "SeekLE" OpHelp("key=r[P3@P4]"),
- /* 25 */ "SeekGE" OpHelp("key=r[P3@P4]"),
- /* 26 */ "SeekGT" OpHelp("key=r[P3@P4]"),
- /* 27 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"),
- /* 28 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"),
- /* 29 */ "NoConflict" OpHelp("key=r[P3@P4]"),
- /* 30 */ "NotFound" OpHelp("key=r[P3@P4]"),
- /* 31 */ "Found" OpHelp("key=r[P3@P4]"),
- /* 32 */ "SeekRowid" OpHelp("intkey=r[P3]"),
- /* 33 */ "NotExists" OpHelp("intkey=r[P3]"),
- /* 34 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"),
- /* 35 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"),
- /* 36 */ "Ne" OpHelp("IF r[P3]!=r[P1]"),
- /* 37 */ "Eq" OpHelp("IF r[P3]==r[P1]"),
- /* 38 */ "Gt" OpHelp("IF r[P3]>r[P1]"),
- /* 39 */ "Le" OpHelp("IF r[P3]<=r[P1]"),
- /* 40 */ "Lt" OpHelp("IF r[P3]<r[P1]"),
- /* 41 */ "Ge" OpHelp("IF r[P3]>=r[P1]"),
- /* 42 */ "ElseNotEq" OpHelp(""),
- /* 43 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"),
- /* 44 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"),
- /* 45 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<<r[P1]"),
- /* 46 */ "ShiftRight" OpHelp("r[P3]=r[P2]>>r[P1]"),
- /* 47 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"),
- /* 48 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"),
- /* 49 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"),
- /* 50 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"),
- /* 51 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"),
- /* 52 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"),
- /* 53 */ "Last" OpHelp(""),
- /* 54 */ "BitNot" OpHelp("r[P1]= ~r[P1]"),
- /* 55 */ "SorterSort" OpHelp(""),
- /* 56 */ "Sort" OpHelp(""),
- /* 57 */ "Rewind" OpHelp(""),
- /* 58 */ "IdxLE" OpHelp("key=r[P3@P4]"),
- /* 59 */ "IdxGT" OpHelp("key=r[P3@P4]"),
- /* 60 */ "IdxLT" OpHelp("key=r[P3@P4]"),
- /* 61 */ "IdxGE" OpHelp("key=r[P3@P4]"),
- /* 62 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"),
- /* 63 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"),
- /* 64 */ "Program" OpHelp(""),
- /* 65 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"),
- /* 66 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"),
- /* 67 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]-=P3, goto P2"),
- /* 68 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"),
- /* 69 */ "IncrVacuum" OpHelp(""),
- /* 70 */ "VNext" OpHelp(""),
- /* 71 */ "Init" OpHelp("Start at P2"),
- /* 72 */ "Return" OpHelp(""),
- /* 73 */ "EndCoroutine" OpHelp(""),
- /* 74 */ "HaltIfNull" OpHelp("if r[P3]=null halt"),
- /* 75 */ "Halt" OpHelp(""),
- /* 76 */ "Integer" OpHelp("r[P2]=P1"),
- /* 77 */ "Int64" OpHelp("r[P2]=P4"),
- /* 78 */ "String" OpHelp("r[P2]='P4' (len=P1)"),
- /* 79 */ "Null" OpHelp("r[P2..P3]=NULL"),
- /* 80 */ "SoftNull" OpHelp("r[P1]=NULL"),
- /* 81 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"),
- /* 82 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"),
- /* 83 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"),
- /* 84 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"),
- /* 85 */ "SCopy" OpHelp("r[P2]=r[P1]"),
- /* 86 */ "IntCopy" OpHelp("r[P2]=r[P1]"),
- /* 87 */ "ResultRow" OpHelp("output=r[P1@P2]"),
- /* 88 */ "CollSeq" OpHelp(""),
- /* 89 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"),
- /* 90 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"),
- /* 91 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"),
- /* 92 */ "RealAffinity" OpHelp(""),
- /* 93 */ "Cast" OpHelp("affinity(r[P1])"),
- /* 94 */ "Permutation" OpHelp(""),
- /* 95 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"),
- /* 96 */ "Column" OpHelp("r[P3]=PX"),
- /* 97 */ "String8" OpHelp("r[P2]='P4'"),
- /* 98 */ "Affinity" OpHelp("affinity(r[P1@P2])"),
- /* 99 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"),
- /* 100 */ "Count" OpHelp("r[P2]=count()"),
- /* 101 */ "ReadCookie" OpHelp(""),
- /* 102 */ "SetCookie" OpHelp(""),
- /* 103 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"),
- /* 104 */ "OpenRead" OpHelp("root=P2 iDb=P3"),
- /* 105 */ "OpenWrite" OpHelp("root=P2 iDb=P3"),
- /* 106 */ "OpenAutoindex" OpHelp("nColumn=P2"),
- /* 107 */ "OpenEphemeral" OpHelp("nColumn=P2"),
- /* 108 */ "SorterOpen" OpHelp(""),
- /* 109 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"),
- /* 110 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"),
- /* 111 */ "Close" OpHelp(""),
- /* 112 */ "ColumnsUsed" OpHelp(""),
- /* 113 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"),
- /* 114 */ "NewRowid" OpHelp("r[P2]=rowid"),
- /* 115 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"),
- /* 116 */ "InsertInt" OpHelp("intkey=P3 data=r[P2]"),
- /* 117 */ "Delete" OpHelp(""),
- /* 118 */ "ResetCount" OpHelp(""),
- /* 119 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
- /* 120 */ "SorterData" OpHelp("r[P2]=data"),
- /* 121 */ "RowKey" OpHelp("r[P2]=key"),
- /* 122 */ "RowData" OpHelp("r[P2]=data"),
- /* 123 */ "Rowid" OpHelp("r[P2]=rowid"),
- /* 124 */ "NullRow" OpHelp(""),
- /* 125 */ "SorterInsert" OpHelp(""),
- /* 126 */ "IdxInsert" OpHelp("key=r[P2]"),
- /* 127 */ "IdxDelete" OpHelp("key=r[P2@P3]"),
- /* 128 */ "Seek" OpHelp("Move P3 to P1.rowid"),
- /* 129 */ "IdxRowid" OpHelp("r[P2]=rowid"),
- /* 130 */ "Destroy" OpHelp(""),
- /* 131 */ "Clear" OpHelp(""),
- /* 132 */ "Real" OpHelp("r[P2]=P4"),
- /* 133 */ "ResetSorter" OpHelp(""),
- /* 134 */ "CreateIndex" OpHelp("r[P2]=root iDb=P1"),
- /* 135 */ "CreateTable" OpHelp("r[P2]=root iDb=P1"),
- /* 136 */ "ParseSchema" OpHelp(""),
- /* 137 */ "LoadAnalysis" OpHelp(""),
- /* 138 */ "DropTable" OpHelp(""),
- /* 139 */ "DropIndex" OpHelp(""),
- /* 140 */ "DropTrigger" OpHelp(""),
- /* 141 */ "IntegrityCk" OpHelp(""),
- /* 142 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"),
- /* 143 */ "Param" OpHelp(""),
- /* 144 */ "FkCounter" OpHelp("fkctr[P1]+=P2"),
- /* 145 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"),
- /* 146 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
- /* 147 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"),
- /* 148 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"),
- /* 149 */ "AggFinal" OpHelp("accum=r[P1] N=P2"),
- /* 150 */ "Expire" OpHelp(""),
- /* 151 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"),
- /* 152 */ "VBegin" OpHelp(""),
- /* 153 */ "VCreate" OpHelp(""),
- /* 154 */ "VDestroy" OpHelp(""),
- /* 155 */ "VOpen" OpHelp(""),
- /* 156 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"),
- /* 157 */ "VRename" OpHelp(""),
- /* 158 */ "Pagecount" OpHelp(""),
- /* 159 */ "MaxPgcnt" OpHelp(""),
- /* 160 */ "CursorHint" OpHelp(""),
- /* 161 */ "Noop" OpHelp(""),
- /* 162 */ "Explain" OpHelp(""),
+ /* 20 */ "IfNot" OpHelp(""),
+ /* 21 */ "IfNullRow" OpHelp("if P1.nullRow then r[P3]=NULL, goto P2"),
+ /* 22 */ "SeekLT" OpHelp("key=r[P3@P4]"),
+ /* 23 */ "SeekLE" OpHelp("key=r[P3@P4]"),
+ /* 24 */ "SeekGE" OpHelp("key=r[P3@P4]"),
+ /* 25 */ "SeekGT" OpHelp("key=r[P3@P4]"),
+ /* 26 */ "IfNotOpen" OpHelp("if( !csr[P1] ) goto P2"),
+ /* 27 */ "IfNoHope" OpHelp("key=r[P3@P4]"),
+ /* 28 */ "NoConflict" OpHelp("key=r[P3@P4]"),
+ /* 29 */ "NotFound" OpHelp("key=r[P3@P4]"),
+ /* 30 */ "Found" OpHelp("key=r[P3@P4]"),
+ /* 31 */ "SeekRowid" OpHelp("intkey=r[P3]"),
+ /* 32 */ "NotExists" OpHelp("intkey=r[P3]"),
+ /* 33 */ "Last" OpHelp(""),
+ /* 34 */ "IfSmaller" OpHelp(""),
+ /* 35 */ "SorterSort" OpHelp(""),
+ /* 36 */ "Sort" OpHelp(""),
+ /* 37 */ "Rewind" OpHelp(""),
+ /* 38 */ "IdxLE" OpHelp("key=r[P3@P4]"),
+ /* 39 */ "IdxGT" OpHelp("key=r[P3@P4]"),
+ /* 40 */ "IdxLT" OpHelp("key=r[P3@P4]"),
+ /* 41 */ "IdxGE" OpHelp("key=r[P3@P4]"),
+ /* 42 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"),
+ /* 43 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"),
+ /* 44 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"),
+ /* 45 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"),
+ /* 46 */ "Program" OpHelp(""),
+ /* 47 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"),
+ /* 48 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"),
+ /* 49 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"),
+ /* 50 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"),
+ /* 51 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"),
+ /* 52 */ "Ne" OpHelp("IF r[P3]!=r[P1]"),
+ /* 53 */ "Eq" OpHelp("IF r[P3]==r[P1]"),
+ /* 54 */ "Gt" OpHelp("IF r[P3]>r[P1]"),
+ /* 55 */ "Le" OpHelp("IF r[P3]<=r[P1]"),
+ /* 56 */ "Lt" OpHelp("IF r[P3]<r[P1]"),
+ /* 57 */ "Ge" OpHelp("IF r[P3]>=r[P1]"),
+ /* 58 */ "ElseNotEq" OpHelp(""),
+ /* 59 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"),
+ /* 60 */ "IncrVacuum" OpHelp(""),
+ /* 61 */ "VNext" OpHelp(""),
+ /* 62 */ "Init" OpHelp("Start at P2"),
+ /* 63 */ "PureFunc" OpHelp("r[P3]=func(r[P2@P5])"),
+ /* 64 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"),
+ /* 65 */ "Return" OpHelp(""),
+ /* 66 */ "EndCoroutine" OpHelp(""),
+ /* 67 */ "HaltIfNull" OpHelp("if r[P3]=null halt"),
+ /* 68 */ "Halt" OpHelp(""),
+ /* 69 */ "Integer" OpHelp("r[P2]=P1"),
+ /* 70 */ "Int64" OpHelp("r[P2]=P4"),
+ /* 71 */ "String" OpHelp("r[P2]='P4' (len=P1)"),
+ /* 72 */ "Null" OpHelp("r[P2..P3]=NULL"),
+ /* 73 */ "SoftNull" OpHelp("r[P1]=NULL"),
+ /* 74 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"),
+ /* 75 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"),
+ /* 76 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"),
+ /* 77 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"),
+ /* 78 */ "SCopy" OpHelp("r[P2]=r[P1]"),
+ /* 79 */ "IntCopy" OpHelp("r[P2]=r[P1]"),
+ /* 80 */ "ResultRow" OpHelp("output=r[P1@P2]"),
+ /* 81 */ "CollSeq" OpHelp(""),
+ /* 82 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"),
+ /* 83 */ "RealAffinity" OpHelp(""),
+ /* 84 */ "Cast" OpHelp("affinity(r[P1])"),
+ /* 85 */ "Permutation" OpHelp(""),
+ /* 86 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"),
+ /* 87 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"),
+ /* 88 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"),
+ /* 89 */ "Column" OpHelp("r[P3]=PX"),
+ /* 90 */ "Affinity" OpHelp("affinity(r[P1@P2])"),
+ /* 91 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"),
+ /* 92 */ "Count" OpHelp("r[P2]=count()"),
+ /* 93 */ "ReadCookie" OpHelp(""),
+ /* 94 */ "SetCookie" OpHelp(""),
+ /* 95 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"),
+ /* 96 */ "OpenRead" OpHelp("root=P2 iDb=P3"),
+ /* 97 */ "OpenWrite" OpHelp("root=P2 iDb=P3"),
+ /* 98 */ "OpenDup" OpHelp(""),
+ /* 99 */ "OpenAutoindex" OpHelp("nColumn=P2"),
+ /* 100 */ "OpenEphemeral" OpHelp("nColumn=P2"),
+ /* 101 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"),
+ /* 102 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"),
+ /* 103 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<<r[P1]"),
+ /* 104 */ "ShiftRight" OpHelp("r[P3]=r[P2]>>r[P1]"),
+ /* 105 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"),
+ /* 106 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"),
+ /* 107 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"),
+ /* 108 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"),
+ /* 109 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"),
+ /* 110 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"),
+ /* 111 */ "SorterOpen" OpHelp(""),
+ /* 112 */ "BitNot" OpHelp("r[P2]= ~r[P1]"),
+ /* 113 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"),
+ /* 114 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"),
+ /* 115 */ "String8" OpHelp("r[P2]='P4'"),
+ /* 116 */ "Close" OpHelp(""),
+ /* 117 */ "ColumnsUsed" OpHelp(""),
+ /* 118 */ "SeekHit" OpHelp("seekHit=P2"),
+ /* 119 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"),
+ /* 120 */ "NewRowid" OpHelp("r[P2]=rowid"),
+ /* 121 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"),
+ /* 122 */ "Delete" OpHelp(""),
+ /* 123 */ "ResetCount" OpHelp(""),
+ /* 124 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"),
+ /* 125 */ "SorterData" OpHelp("r[P2]=data"),
+ /* 126 */ "RowData" OpHelp("r[P2]=data"),
+ /* 127 */ "Rowid" OpHelp("r[P2]=rowid"),
+ /* 128 */ "NullRow" OpHelp(""),
+ /* 129 */ "SeekEnd" OpHelp(""),
+ /* 130 */ "SorterInsert" OpHelp("key=r[P2]"),
+ /* 131 */ "IdxInsert" OpHelp("key=r[P2]"),
+ /* 132 */ "IdxDelete" OpHelp("key=r[P2@P3]"),
+ /* 133 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"),
+ /* 134 */ "IdxRowid" OpHelp("r[P2]=rowid"),
+ /* 135 */ "FinishSeek" OpHelp(""),
+ /* 136 */ "Destroy" OpHelp(""),
+ /* 137 */ "Clear" OpHelp(""),
+ /* 138 */ "ResetSorter" OpHelp(""),
+ /* 139 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"),
+ /* 140 */ "SqlExec" OpHelp(""),
+ /* 141 */ "ParseSchema" OpHelp(""),
+ /* 142 */ "LoadAnalysis" OpHelp(""),
+ /* 143 */ "DropTable" OpHelp(""),
+ /* 144 */ "DropIndex" OpHelp(""),
+ /* 145 */ "DropTrigger" OpHelp(""),
+ /* 146 */ "IntegrityCk" OpHelp(""),
+ /* 147 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"),
+ /* 148 */ "Param" OpHelp(""),
+ /* 149 */ "FkCounter" OpHelp("fkctr[P1]+=P2"),
+ /* 150 */ "Real" OpHelp("r[P2]=P4"),
+ /* 151 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"),
+ /* 152 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"),
+ /* 153 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"),
+ /* 154 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"),
+ /* 155 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"),
+ /* 156 */ "AggValue" OpHelp("r[P3]=value N=P2"),
+ /* 157 */ "AggFinal" OpHelp("accum=r[P1] N=P2"),
+ /* 158 */ "Expire" OpHelp(""),
+ /* 159 */ "CursorLock" OpHelp(""),
+ /* 160 */ "CursorUnlock" OpHelp(""),
+ /* 161 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"),
+ /* 162 */ "VBegin" OpHelp(""),
+ /* 163 */ "VCreate" OpHelp(""),
+ /* 164 */ "VDestroy" OpHelp(""),
+ /* 165 */ "VOpen" OpHelp(""),
+ /* 166 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"),
+ /* 167 */ "VRename" OpHelp(""),
+ /* 168 */ "Pagecount" OpHelp(""),
+ /* 169 */ "MaxPgcnt" OpHelp(""),
+ /* 170 */ "Trace" OpHelp(""),
+ /* 171 */ "CursorHint" OpHelp(""),
+ /* 172 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"),
+ /* 173 */ "Noop" OpHelp(""),
+ /* 174 */ "Explain" OpHelp(""),
+ /* 175 */ "Abortable" OpHelp(""),
};
return azName[i];
}
@@ -32982,13 +37423,13 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
** + for flock() locking
** + for named semaphore locks (VxWorks only)
** + for AFP filesystem locks (MacOSX only)
-** * sqlite3_file methods not associated with locking.
-** * Definitions of sqlite3_io_methods objects for all locking
+** * tdsqlite3_file methods not associated with locking.
+** * Definitions of tdsqlite3_io_methods objects for all locking
** methods plus "finder" functions for each locking method.
-** * sqlite3_vfs method implementations.
+** * tdsqlite3_vfs method implementations.
** * Locking primitives for the proxy uber-locking-method. (MacOSX only)
-** * Definitions of sqlite3_vfs objects for all locking methods
-** plus implementations of sqlite3_os_init() and sqlite3_os_end().
+** * Definitions of tdsqlite3_vfs objects for all locking methods
+** plus implementations of tdsqlite3_os_init() and tdsqlite3_os_end().
*/
/* #include "sqliteInt.h" */
#if SQLITE_OS_UNIX /* This file is used on unix only */
@@ -33037,27 +37478,44 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
-#include <unistd.h>
+#include <sys/ioctl.h>
+/* #include <unistd.h> */
/* #include <time.h> */
#include <sys/time.h>
-#include <errno.h>
+/* #include <errno.h> */
#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0
/* # include <sys/mman.h> */
#endif
#if SQLITE_ENABLE_LOCKING_STYLE
-# include <sys/ioctl.h>
+/* # include <sys/ioctl.h> */
# include <sys/file.h>
# include <sys/param.h>
#endif /* SQLITE_ENABLE_LOCKING_STYLE */
-#if defined(__APPLE__) && ((__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) || \
- (__IPHONE_OS_VERSION_MIN_REQUIRED > 2000))
-# if (!defined(TARGET_OS_EMBEDDED) || (TARGET_OS_EMBEDDED==0)) \
- && (!defined(TARGET_IPHONE_SIMULATOR) || (TARGET_IPHONE_SIMULATOR==0))
-# define HAVE_GETHOSTUUID 1
-# else
-# warning "gethostuuid() is disabled."
+/*
+** Try to determine if gethostuuid() is available based on standard
+** macros. This might sometimes compute the wrong value for some
+** obscure platforms. For those cases, simply compile with one of
+** the following:
+**
+** -DHAVE_GETHOSTUUID=0
+** -DHAVE_GETHOSTUUID=1
+**
+** None if this matters except when building on Apple products with
+** -DSQLITE_ENABLE_LOCKING_STYLE.
+*/
+#ifndef HAVE_GETHOSTUUID
+# define HAVE_GETHOSTUUID 0
+# if defined(__APPLE__) && ((__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) || \
+ (__IPHONE_OS_VERSION_MIN_REQUIRED > 2000))
+# if (!defined(TARGET_OS_EMBEDDED) || (TARGET_OS_EMBEDDED==0)) \
+ && (!defined(TARGET_IPHONE_SIMULATOR) || (TARGET_IPHONE_SIMULATOR==0))
+# undef HAVE_GETHOSTUUID
+# define HAVE_GETHOSTUUID 1
+# else
+# warning "gethostuuid() is disabled."
+# endif
# endif
#endif
@@ -33082,12 +37540,10 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
#define SQLITE_FSFLAGS_IS_MSDOS 0x1
/*
-** If we are to be thread-safe, include the pthreads header and define
-** the SQLITE_UNIX_THREADS macro.
+** If we are to be thread-safe, include the pthreads header.
*/
#if SQLITE_THREADSAFE
/* # include <pthread.h> */
-# define SQLITE_UNIX_THREADS 1
#endif
/*
@@ -33143,40 +37599,41 @@ struct UnixUnusedFd {
};
/*
-** The unixFile structure is subclass of sqlite3_file specific to the unix
+** The unixFile structure is subclass of tdsqlite3_file specific to the unix
** VFS implementations.
*/
typedef struct unixFile unixFile;
struct unixFile {
- sqlite3_io_methods const *pMethod; /* Always the first entry */
- sqlite3_vfs *pVfs; /* The VFS that created this unixFile */
+ tdsqlite3_io_methods const *pMethod; /* Always the first entry */
+ tdsqlite3_vfs *pVfs; /* The VFS that created this unixFile */
unixInodeInfo *pInode; /* Info about locks on this inode */
int h; /* The file descriptor */
unsigned char eFileLock; /* The type of lock held on this fd */
unsigned short int ctrlFlags; /* Behavioral bits. UNIXFILE_* flags */
int lastErrno; /* The unix errno from last I/O error */
void *lockingContext; /* Locking style specific state */
- UnixUnusedFd *pUnused; /* Pre-allocated UnixUnusedFd */
+ UnixUnusedFd *pPreallocatedUnused; /* Pre-allocated UnixUnusedFd */
const char *zPath; /* Name of the file */
unixShm *pShm; /* Shared memory segment information */
int szChunk; /* Configured by FCNTL_CHUNK_SIZE */
#if SQLITE_MAX_MMAP_SIZE>0
int nFetchOut; /* Number of outstanding xFetch refs */
- sqlite3_int64 mmapSize; /* Usable size of mapping at pMapRegion */
- sqlite3_int64 mmapSizeActual; /* Actual size of mapping at pMapRegion */
- sqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */
+ tdsqlite3_int64 mmapSize; /* Usable size of mapping at pMapRegion */
+ tdsqlite3_int64 mmapSizeActual; /* Actual size of mapping at pMapRegion */
+ tdsqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */
void *pMapRegion; /* Memory mapped region */
#endif
-#ifdef __QNXNTO__
int sectorSize; /* Device sector size */
int deviceCharacteristics; /* Precomputed device characteristics */
-#endif
#if SQLITE_ENABLE_LOCKING_STYLE
int openFlags; /* The flags specified at open() */
#endif
#if SQLITE_ENABLE_LOCKING_STYLE || defined(__APPLE__)
unsigned fsFlags; /* cached details from statfs() */
#endif
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+ unsigned iBusyTimeout; /* Wait this many millisec on locks */
+#endif
#if OS_VXWORKS
struct vxworksFileId *pId; /* Unique file ID */
#endif
@@ -33285,7 +37742,7 @@ static pid_t randomnessPid = 0;
******************************************************************************
**
** This file contains inline asm code for retrieving "high-performance"
-** counters for x86 class CPUs.
+** counters for x86 and x86_64 class CPUs.
*/
#ifndef SQLITE_HWTIME_H
#define SQLITE_HWTIME_H
@@ -33296,12 +37753,13 @@ static pid_t randomnessPid = 0;
** processor and returns that value. This can be used for high-res
** profiling.
*/
-#if (defined(__GNUC__) || defined(_MSC_VER)) && \
- (defined(i386) || defined(__i386__) || defined(_M_IX86))
+#if !defined(__STRICT_ANSI__) && \
+ (defined(__GNUC__) || defined(_MSC_VER)) && \
+ (defined(i386) || defined(__i386__) || defined(_M_IX86))
#if defined(__GNUC__)
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned int lo, hi;
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return (sqlite_uint64)hi << 32 | lo;
@@ -33309,7 +37767,7 @@ static pid_t randomnessPid = 0;
#elif defined(_MSC_VER)
- __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){
+ __declspec(naked) __inline sqlite_uint64 __cdecl tdsqlite3Hwtime(void){
__asm {
rdtsc
ret ; return value at EDX:EAX
@@ -33318,17 +37776,17 @@ static pid_t randomnessPid = 0;
#endif
-#elif (defined(__GNUC__) && defined(__x86_64__))
+#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__))
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned long val;
__asm__ __volatile__ ("rdtsc" : "=A" (val));
return val;
}
-#elif (defined(__GNUC__) && defined(__ppc__))
+#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__))
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned long long retval;
unsigned long junk;
__asm__ __volatile__ ("\n\
@@ -33343,16 +37801,15 @@ static pid_t randomnessPid = 0;
#else
- #error Need implementation of sqlite3Hwtime() for your platform.
-
/*
- ** To compile without implementing sqlite3Hwtime() for your platform,
- ** you can remove the above #error and use the following
- ** stub function. You will lose timing support for many
- ** of the debugging and testing utilities, but it should at
- ** least compile and run.
+ ** asm() is needed for hardware timing support. Without asm(),
+ ** disable the tdsqlite3Hwtime() routine.
+ **
+ ** tdsqlite3Hwtime() is only used for some obscure debugging
+ ** and analysis configurations, not in any deliverable, so this
+ ** should not be a great loss.
*/
-SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); }
+SQLITE_PRIVATE sqlite_uint64 tdsqlite3Hwtime(void){ return ((sqlite_uint64)0); }
#endif
@@ -33363,8 +37820,8 @@ SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); }
static sqlite_uint64 g_start;
static sqlite_uint64 g_elapsed;
-#define TIMER_START g_start=sqlite3Hwtime()
-#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start
+#define TIMER_START g_start=tdsqlite3Hwtime()
+#define TIMER_END g_elapsed=tdsqlite3Hwtime()-g_start
#define TIMER_ELAPSED g_elapsed
#else
#define TIMER_START
@@ -33378,32 +37835,32 @@ static sqlite_uint64 g_elapsed;
** is used for testing the I/O recovery logic.
*/
#if defined(SQLITE_TEST)
-SQLITE_API extern int sqlite3_io_error_hit;
-SQLITE_API extern int sqlite3_io_error_hardhit;
-SQLITE_API extern int sqlite3_io_error_pending;
-SQLITE_API extern int sqlite3_io_error_persist;
-SQLITE_API extern int sqlite3_io_error_benign;
-SQLITE_API extern int sqlite3_diskfull_pending;
-SQLITE_API extern int sqlite3_diskfull;
-#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X)
+SQLITE_API extern int tdsqlite3_io_error_hit;
+SQLITE_API extern int tdsqlite3_io_error_hardhit;
+SQLITE_API extern int tdsqlite3_io_error_pending;
+SQLITE_API extern int tdsqlite3_io_error_persist;
+SQLITE_API extern int tdsqlite3_io_error_benign;
+SQLITE_API extern int tdsqlite3_diskfull_pending;
+SQLITE_API extern int tdsqlite3_diskfull;
+#define SimulateIOErrorBenign(X) tdsqlite3_io_error_benign=(X)
#define SimulateIOError(CODE) \
- if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \
- || sqlite3_io_error_pending-- == 1 ) \
+ if( (tdsqlite3_io_error_persist && tdsqlite3_io_error_hit) \
+ || tdsqlite3_io_error_pending-- == 1 ) \
{ local_ioerr(); CODE; }
static void local_ioerr(){
IOTRACE(("IOERR\n"));
- sqlite3_io_error_hit++;
- if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++;
+ tdsqlite3_io_error_hit++;
+ if( !tdsqlite3_io_error_benign ) tdsqlite3_io_error_hardhit++;
}
#define SimulateDiskfullError(CODE) \
- if( sqlite3_diskfull_pending ){ \
- if( sqlite3_diskfull_pending == 1 ){ \
+ if( tdsqlite3_diskfull_pending ){ \
+ if( tdsqlite3_diskfull_pending == 1 ){ \
local_ioerr(); \
- sqlite3_diskfull = 1; \
- sqlite3_io_error_hit = 1; \
+ tdsqlite3_diskfull = 1; \
+ tdsqlite3_io_error_hit = 1; \
CODE; \
}else{ \
- sqlite3_diskfull_pending--; \
+ tdsqlite3_diskfull_pending--; \
} \
}
#else
@@ -33416,8 +37873,8 @@ static void local_ioerr(){
** When testing, keep a count of the number of open files.
*/
#if defined(SQLITE_TEST)
-SQLITE_API extern int sqlite3_open_file_count;
-#define OpenCounter(X) sqlite3_open_file_count+=(X)
+SQLITE_API extern int tdsqlite3_open_file_count;
+#define OpenCounter(X) tdsqlite3_open_file_count+=(X)
#else
#define OpenCounter(X)
#endif /* defined(SQLITE_TEST) */
@@ -33473,6 +37930,20 @@ SQLITE_API extern int sqlite3_open_file_count;
# define lseek lseek64
#endif
+#ifdef __linux__
+/*
+** Linux-specific IOCTL magic numbers used for controlling F2FS
+*/
+#define F2FS_IOCTL_MAGIC 0xf5
+#define F2FS_IOC_START_ATOMIC_WRITE _IO(F2FS_IOCTL_MAGIC, 1)
+#define F2FS_IOC_COMMIT_ATOMIC_WRITE _IO(F2FS_IOCTL_MAGIC, 2)
+#define F2FS_IOC_START_VOLATILE_WRITE _IO(F2FS_IOCTL_MAGIC, 3)
+#define F2FS_IOC_ABORT_VOLATILE_WRITE _IO(F2FS_IOCTL_MAGIC, 5)
+#define F2FS_IOC_GET_FEATURES _IOR(F2FS_IOCTL_MAGIC, 12, u32)
+#define F2FS_FEATURE_ATOMIC_WRITE 0x0004
+#endif /* __linux__ */
+
+
/*
** Different Unix systems declare open() in different ways. Same use
** open(const char*,int,mode_t). Others use open(const char*,int,...).
@@ -33497,22 +37968,22 @@ static int unixGetpagesize(void);
*/
static struct unix_syscall {
const char *zName; /* Name of the system call */
- sqlite3_syscall_ptr pCurrent; /* Current value of the system call */
- sqlite3_syscall_ptr pDefault; /* Default value */
+ tdsqlite3_syscall_ptr pCurrent; /* Current value of the system call */
+ tdsqlite3_syscall_ptr pDefault; /* Default value */
} aSyscall[] = {
- { "open", (sqlite3_syscall_ptr)posixOpen, 0 },
+ { "open", (tdsqlite3_syscall_ptr)posixOpen, 0 },
#define osOpen ((int(*)(const char*,int,int))aSyscall[0].pCurrent)
- { "close", (sqlite3_syscall_ptr)close, 0 },
+ { "close", (tdsqlite3_syscall_ptr)close, 0 },
#define osClose ((int(*)(int))aSyscall[1].pCurrent)
- { "access", (sqlite3_syscall_ptr)access, 0 },
+ { "access", (tdsqlite3_syscall_ptr)access, 0 },
#define osAccess ((int(*)(const char*,int))aSyscall[2].pCurrent)
- { "getcwd", (sqlite3_syscall_ptr)getcwd, 0 },
+ { "getcwd", (tdsqlite3_syscall_ptr)getcwd, 0 },
#define osGetcwd ((char*(*)(char*,size_t))aSyscall[3].pCurrent)
- { "stat", (sqlite3_syscall_ptr)stat, 0 },
+ { "stat", (tdsqlite3_syscall_ptr)stat, 0 },
#define osStat ((int(*)(const char*,struct stat*))aSyscall[4].pCurrent)
/*
@@ -33525,126 +37996,142 @@ static struct unix_syscall {
{ "fstat", 0, 0 },
#define osFstat(a,b,c) 0
#else
- { "fstat", (sqlite3_syscall_ptr)fstat, 0 },
+ { "fstat", (tdsqlite3_syscall_ptr)fstat, 0 },
#define osFstat ((int(*)(int,struct stat*))aSyscall[5].pCurrent)
#endif
- { "ftruncate", (sqlite3_syscall_ptr)ftruncate, 0 },
+ { "ftruncate", (tdsqlite3_syscall_ptr)ftruncate, 0 },
#define osFtruncate ((int(*)(int,off_t))aSyscall[6].pCurrent)
- { "fcntl", (sqlite3_syscall_ptr)fcntl, 0 },
+ { "fcntl", (tdsqlite3_syscall_ptr)fcntl, 0 },
#define osFcntl ((int(*)(int,int,...))aSyscall[7].pCurrent)
- { "read", (sqlite3_syscall_ptr)read, 0 },
+ { "read", (tdsqlite3_syscall_ptr)read, 0 },
#define osRead ((ssize_t(*)(int,void*,size_t))aSyscall[8].pCurrent)
#if defined(USE_PREAD) || SQLITE_ENABLE_LOCKING_STYLE
- { "pread", (sqlite3_syscall_ptr)pread, 0 },
+ { "pread", (tdsqlite3_syscall_ptr)pread, 0 },
#else
- { "pread", (sqlite3_syscall_ptr)0, 0 },
+ { "pread", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osPread ((ssize_t(*)(int,void*,size_t,off_t))aSyscall[9].pCurrent)
#if defined(USE_PREAD64)
- { "pread64", (sqlite3_syscall_ptr)pread64, 0 },
+ { "pread64", (tdsqlite3_syscall_ptr)pread64, 0 },
#else
- { "pread64", (sqlite3_syscall_ptr)0, 0 },
+ { "pread64", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osPread64 ((ssize_t(*)(int,void*,size_t,off64_t))aSyscall[10].pCurrent)
- { "write", (sqlite3_syscall_ptr)write, 0 },
+ { "write", (tdsqlite3_syscall_ptr)write, 0 },
#define osWrite ((ssize_t(*)(int,const void*,size_t))aSyscall[11].pCurrent)
#if defined(USE_PREAD) || SQLITE_ENABLE_LOCKING_STYLE
- { "pwrite", (sqlite3_syscall_ptr)pwrite, 0 },
+ { "pwrite", (tdsqlite3_syscall_ptr)pwrite, 0 },
#else
- { "pwrite", (sqlite3_syscall_ptr)0, 0 },
+ { "pwrite", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osPwrite ((ssize_t(*)(int,const void*,size_t,off_t))\
aSyscall[12].pCurrent)
#if defined(USE_PREAD64)
- { "pwrite64", (sqlite3_syscall_ptr)pwrite64, 0 },
+ { "pwrite64", (tdsqlite3_syscall_ptr)pwrite64, 0 },
#else
- { "pwrite64", (sqlite3_syscall_ptr)0, 0 },
+ { "pwrite64", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osPwrite64 ((ssize_t(*)(int,const void*,size_t,off64_t))\
aSyscall[13].pCurrent)
- { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 },
+ { "fchmod", (tdsqlite3_syscall_ptr)fchmod, 0 },
#define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent)
#if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE
- { "fallocate", (sqlite3_syscall_ptr)posix_fallocate, 0 },
+ { "fallocate", (tdsqlite3_syscall_ptr)posix_fallocate, 0 },
#else
- { "fallocate", (sqlite3_syscall_ptr)0, 0 },
+ { "fallocate", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osFallocate ((int(*)(int,off_t,off_t))aSyscall[15].pCurrent)
- { "unlink", (sqlite3_syscall_ptr)unlink, 0 },
+ { "unlink", (tdsqlite3_syscall_ptr)unlink, 0 },
#define osUnlink ((int(*)(const char*))aSyscall[16].pCurrent)
- { "openDirectory", (sqlite3_syscall_ptr)openDirectory, 0 },
+ { "openDirectory", (tdsqlite3_syscall_ptr)openDirectory, 0 },
#define osOpenDirectory ((int(*)(const char*,int*))aSyscall[17].pCurrent)
- { "mkdir", (sqlite3_syscall_ptr)mkdir, 0 },
+ { "mkdir", (tdsqlite3_syscall_ptr)mkdir, 0 },
#define osMkdir ((int(*)(const char*,mode_t))aSyscall[18].pCurrent)
- { "rmdir", (sqlite3_syscall_ptr)rmdir, 0 },
+ { "rmdir", (tdsqlite3_syscall_ptr)rmdir, 0 },
#define osRmdir ((int(*)(const char*))aSyscall[19].pCurrent)
#if defined(HAVE_FCHOWN)
- { "fchown", (sqlite3_syscall_ptr)fchown, 0 },
+ { "fchown", (tdsqlite3_syscall_ptr)fchown, 0 },
#else
- { "fchown", (sqlite3_syscall_ptr)0, 0 },
+ { "fchown", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osFchown ((int(*)(int,uid_t,gid_t))aSyscall[20].pCurrent)
- { "geteuid", (sqlite3_syscall_ptr)geteuid, 0 },
+#if defined(HAVE_FCHOWN)
+ { "geteuid", (tdsqlite3_syscall_ptr)geteuid, 0 },
+#else
+ { "geteuid", (tdsqlite3_syscall_ptr)0, 0 },
+#endif
#define osGeteuid ((uid_t(*)(void))aSyscall[21].pCurrent)
#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0
- { "mmap", (sqlite3_syscall_ptr)mmap, 0 },
+ { "mmap", (tdsqlite3_syscall_ptr)mmap, 0 },
#else
- { "mmap", (sqlite3_syscall_ptr)0, 0 },
+ { "mmap", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osMmap ((void*(*)(void*,size_t,int,int,int,off_t))aSyscall[22].pCurrent)
#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0
- { "munmap", (sqlite3_syscall_ptr)munmap, 0 },
+ { "munmap", (tdsqlite3_syscall_ptr)munmap, 0 },
#else
- { "munmap", (sqlite3_syscall_ptr)0, 0 },
+ { "munmap", (tdsqlite3_syscall_ptr)0, 0 },
#endif
-#define osMunmap ((void*(*)(void*,size_t))aSyscall[23].pCurrent)
+#define osMunmap ((int(*)(void*,size_t))aSyscall[23].pCurrent)
#if HAVE_MREMAP && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)
- { "mremap", (sqlite3_syscall_ptr)mremap, 0 },
+ { "mremap", (tdsqlite3_syscall_ptr)mremap, 0 },
#else
- { "mremap", (sqlite3_syscall_ptr)0, 0 },
+ { "mremap", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osMremap ((void*(*)(void*,size_t,size_t,int,...))aSyscall[24].pCurrent)
#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0
- { "getpagesize", (sqlite3_syscall_ptr)unixGetpagesize, 0 },
+ { "getpagesize", (tdsqlite3_syscall_ptr)unixGetpagesize, 0 },
#else
- { "getpagesize", (sqlite3_syscall_ptr)0, 0 },
+ { "getpagesize", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osGetpagesize ((int(*)(void))aSyscall[25].pCurrent)
#if defined(HAVE_READLINK)
- { "readlink", (sqlite3_syscall_ptr)readlink, 0 },
+ { "readlink", (tdsqlite3_syscall_ptr)readlink, 0 },
#else
- { "readlink", (sqlite3_syscall_ptr)0, 0 },
+ { "readlink", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[26].pCurrent)
#if defined(HAVE_LSTAT)
- { "lstat", (sqlite3_syscall_ptr)lstat, 0 },
+ { "lstat", (tdsqlite3_syscall_ptr)lstat, 0 },
#else
- { "lstat", (sqlite3_syscall_ptr)0, 0 },
+ { "lstat", (tdsqlite3_syscall_ptr)0, 0 },
#endif
#define osLstat ((int(*)(const char*,struct stat*))aSyscall[27].pCurrent)
+#if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE)
+# ifdef __ANDROID__
+ { "ioctl", (tdsqlite3_syscall_ptr)(int(*)(int, int, ...))ioctl, 0 },
+#define osIoctl ((int(*)(int,int,...))aSyscall[28].pCurrent)
+# else
+ { "ioctl", (tdsqlite3_syscall_ptr)ioctl, 0 },
+#define osIoctl ((int(*)(int,unsigned long,...))aSyscall[28].pCurrent)
+# endif
+#else
+ { "ioctl", (tdsqlite3_syscall_ptr)0, 0 },
+#endif
+
}; /* End of the overrideable system calls */
@@ -33662,15 +38149,15 @@ static int robustFchown(int fd, uid_t uid, gid_t gid){
}
/*
-** This is the xSetSystemCall() method of sqlite3_vfs for all of the
+** This is the xSetSystemCall() method of tdsqlite3_vfs for all of the
** "unix" VFSes. Return SQLITE_OK opon successfully updating the
** system call pointer, or SQLITE_NOTFOUND if there is no configurable
** system call named zName.
*/
static int unixSetSystemCall(
- sqlite3_vfs *pNotUsed, /* The VFS pointer. Not used */
+ tdsqlite3_vfs *pNotUsed, /* The VFS pointer. Not used */
const char *zName, /* Name of system call to override */
- sqlite3_syscall_ptr pNewFunc /* Pointer to new system call value */
+ tdsqlite3_syscall_ptr pNewFunc /* Pointer to new system call value */
){
unsigned int i;
int rc = SQLITE_NOTFOUND;
@@ -33710,8 +38197,8 @@ static int unixSetSystemCall(
** recognized system call name. NULL is also returned if the system call
** is currently undefined.
*/
-static sqlite3_syscall_ptr unixGetSystemCall(
- sqlite3_vfs *pNotUsed,
+static tdsqlite3_syscall_ptr unixGetSystemCall(
+ tdsqlite3_vfs *pNotUsed,
const char *zName
){
unsigned int i;
@@ -33729,7 +38216,7 @@ static sqlite3_syscall_ptr unixGetSystemCall(
** is the last system call or if zName is not the name of a valid
** system call.
*/
-static const char *unixNextSystemCall(sqlite3_vfs *p, const char *zName){
+static const char *unixNextSystemCall(tdsqlite3_vfs *p, const char *zName){
int i = -1;
UNUSED_PARAMETER(p);
@@ -33785,7 +38272,7 @@ static int robust_open(const char *z, int f, mode_t m){
}
if( fd>=SQLITE_MINIMUM_FILE_DESCRIPTOR ) break;
osClose(fd);
- sqlite3_log(SQLITE_WARNING,
+ tdsqlite3_log(SQLITE_WARNING,
"attempt to open \"%s\" as file descriptor %d", z, fd);
fd = -1;
if( osOpen("/dev/null", f, m)<0 ) break;
@@ -33820,16 +38307,30 @@ static int robust_open(const char *z, int f, mode_t m){
** unixEnterMutex()
** assert( unixMutexHeld() );
** unixEnterLeave()
+**
+** To prevent deadlock, the global unixBigLock must must be acquired
+** before the unixInodeInfo.pLockMutex mutex, if both are held. It is
+** OK to get the pLockMutex without holding unixBigLock first, but if
+** that happens, the unixBigLock mutex must not be acquired until after
+** pLockMutex is released.
+**
+** OK: enter(unixBigLock), enter(pLockInfo)
+** OK: enter(unixBigLock)
+** OK: enter(pLockInfo)
+** ERROR: enter(pLockInfo), enter(unixBigLock)
*/
+static tdsqlite3_mutex *unixBigLock = 0;
static void unixEnterMutex(void){
- sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ assert( tdsqlite3_mutex_notheld(unixBigLock) ); /* Not a recursive mutex */
+ tdsqlite3_mutex_enter(unixBigLock);
}
static void unixLeaveMutex(void){
- sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ assert( tdsqlite3_mutex_held(unixBigLock) );
+ tdsqlite3_mutex_leave(unixBigLock);
}
#ifdef SQLITE_DEBUG
static int unixMutexHeld(void) {
- return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ return tdsqlite3_mutex_held(unixBigLock);
}
#endif
@@ -33871,7 +38372,7 @@ static int lockTrace(int fd, int op, struct flock *p){
zOpName = "SETLK";
}else{
s = osFcntl(fd, op, p);
- sqlite3DebugPrintf("fcntl unknown %d %d %d\n", fd, op, s);
+ tdsqlite3DebugPrintf("fcntl unknown %d %d %d\n", fd, op, s);
return s;
}
if( p->l_type==F_RDLCK ){
@@ -33886,7 +38387,7 @@ static int lockTrace(int fd, int op, struct flock *p){
assert( p->l_whence==SEEK_SET );
s = osFcntl(fd, op, p);
savedErrno = errno;
- sqlite3DebugPrintf("fcntl %d %d %s %s %d %d %d %d\n",
+ tdsqlite3DebugPrintf("fcntl %d %d %s %s %d %d %d %d\n",
threadid, fd, zOpName, zType, (int)p->l_start, (int)p->l_len,
(int)p->l_pid, s);
if( s==(-1) && op==F_SETLK && (p->l_type==F_RDLCK || p->l_type==F_WRLCK) ){
@@ -33902,7 +38403,7 @@ static int lockTrace(int fd, int op, struct flock *p){
}else{
assert( 0 );
}
- sqlite3DebugPrintf("fcntl-failure-reason: %s %d %d %d\n",
+ tdsqlite3DebugPrintf("fcntl-failure-reason: %s %d %d %d\n",
zType, (int)l2.l_start, (int)l2.l_len, (int)l2.l_pid);
}
errno = savedErrno;
@@ -33919,14 +38420,14 @@ static int lockTrace(int fd, int op, struct flock *p){
** this wrapper. On the Android platform, bypassing the logic below
** could lead to a corrupt database.
*/
-static int robust_ftruncate(int h, sqlite3_int64 sz){
+static int robust_ftruncate(int h, tdsqlite3_int64 sz){
int rc;
#ifdef __ANDROID__
/* On Android, ftruncate() always uses 32-bit offsets, even if
** _FILE_OFFSET_BITS=64 is defined. This means it is unsafe to attempt to
** truncate a file to any size larger than 2GiB. Silently ignore any
** such attempts. */
- if( sz>(sqlite3_int64)0x7FFFFFFF ){
+ if( sz>(tdsqlite3_int64)0x7FFFFFFF ){
rc = SQLITE_OK;
}else
#endif
@@ -33936,7 +38437,7 @@ static int robust_ftruncate(int h, sqlite3_int64 sz){
/*
** This routine translates a standard POSIX errno code into something
-** useful to the clients of the sqlite3 functions. Specifically, it is
+** useful to the clients of the tdsqlite3 functions. Specifically, it is
** intended to translate a variety of "try again" errors into SQLITE_BUSY
** and a variety of "please close the file descriptor NOW" errors into
** SQLITE_IOERR
@@ -34054,7 +38555,7 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){
assert( zAbsoluteName[0]=='/' );
n = (int)strlen(zAbsoluteName);
- pNew = sqlite3_malloc64( sizeof(*pNew) + (n+1) );
+ pNew = tdsqlite3_malloc64( sizeof(*pNew) + (n+1) );
if( pNew==0 ) return 0;
pNew->zCanonicalName = (char*)&pNew[1];
memcpy(pNew->zCanonicalName, zAbsoluteName, n+1);
@@ -34069,7 +38570,7 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){
if( pCandidate->nName==n
&& memcmp(pCandidate->zCanonicalName, pNew->zCanonicalName, n)==0
){
- sqlite3_free(pNew);
+ tdsqlite3_free(pNew);
pCandidate->nRef++;
unixLeaveMutex();
return pCandidate;
@@ -34098,7 +38599,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){
for(pp=&vxworksFileList; *pp && *pp!=pId; pp = &((*pp)->pNext)){}
assert( *pp==pId );
*pp = pId->pNext;
- sqlite3_free(pId);
+ tdsqlite3_free(pId);
}
unixLeaveMutex();
}
@@ -34147,7 +38648,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){
** For VxWorks, we have to use the alternative unique ID system based on
** canonical filename and implemented in the previous division.)
**
-** The sqlite3_file structure for POSIX is no longer just an integer file
+** The tdsqlite3_file structure for POSIX is no longer just an integer file
** descriptor. It is now a structure that holds the integer file
** descriptor and a pointer to a structure that describes the internal
** locks on the corresponding inode. There is one locking structure
@@ -34206,28 +38707,52 @@ struct unixFileId {
#if OS_VXWORKS
struct vxworksFileId *pId; /* Unique file ID for vxworks. */
#else
- ino_t ino; /* Inode number */
+ /* We are told that some versions of Android contain a bug that
+ ** sizes ino_t at only 32-bits instead of 64-bits. (See
+ ** https://android-review.googlesource.com/#/c/115351/3/dist/sqlite3.c)
+ ** To work around this, always allocate 64-bits for the inode number.
+ ** On small machines that only have 32-bit inodes, this wastes 4 bytes,
+ ** but that should not be a big deal. */
+ /* WAS: ino_t ino; */
+ u64 ino; /* Inode number */
#endif
};
/*
** An instance of the following structure is allocated for each open
-** inode. Or, on LinuxThreads, there is one of these structures for
-** each inode opened by each thread.
+** inode.
**
** A single inode can have multiple file descriptors, so each unixFile
** structure contains a pointer to an instance of this object and this
** object keeps a count of the number of unixFile pointing to it.
+**
+** Mutex rules:
+**
+** (1) Only the pLockMutex mutex must be held in order to read or write
+** any of the locking fields:
+** nShared, nLock, eFileLock, bProcessLock, pUnused
+**
+** (2) When nRef>0, then the following fields are unchanging and can
+** be read (but not written) without holding any mutex:
+** fileId, pLockMutex
+**
+** (3) With the exceptions above, all the fields may only be read
+** or written while holding the global unixBigLock mutex.
+**
+** Deadlock prevention: The global unixBigLock mutex may not
+** be acquired while holding the pLockMutex mutex. If both unixBigLock
+** and pLockMutex are needed, then unixBigLock must be acquired first.
*/
struct unixInodeInfo {
struct unixFileId fileId; /* The lookup key */
- int nShared; /* Number of SHARED locks held */
- unsigned char eFileLock; /* One of SHARED_LOCK, RESERVED_LOCK etc. */
- unsigned char bProcessLock; /* An exclusive process lock is held */
+ tdsqlite3_mutex *pLockMutex; /* Hold this mutex for... */
+ int nShared; /* Number of SHARED locks held */
+ int nLock; /* Number of outstanding file locks */
+ unsigned char eFileLock; /* One of SHARED_LOCK, RESERVED_LOCK etc. */
+ unsigned char bProcessLock; /* An exclusive process lock is held */
+ UnixUnusedFd *pUnused; /* Unused file descriptors to close */
int nRef; /* Number of pointers to this structure */
unixShmNode *pShmNode; /* Shared memory associated with this inode */
- int nLock; /* Number of outstanding file locks */
- UnixUnusedFd *pUnused; /* Unused file descriptors to close */
unixInodeInfo *pNext; /* List of all unixInodeInfo objects */
unixInodeInfo *pPrev; /* .... doubly linked */
#if SQLITE_ENABLE_LOCKING_STYLE
@@ -34241,8 +38766,26 @@ struct unixInodeInfo {
/*
** A lists of all unixInodeInfo objects.
+**
+** Must hold unixBigLock in order to read or write this variable.
*/
-static unixInodeInfo *inodeList = 0;
+static unixInodeInfo *inodeList = 0; /* All unixInodeInfo objects */
+
+#ifdef SQLITE_DEBUG
+/*
+** True if the inode mutex (on the unixFile.pFileMutex field) is held, or not.
+** This routine is used only within assert() to help verify correct mutex
+** usage.
+*/
+int unixFileMutexHeld(unixFile *pFile){
+ assert( pFile->pInode );
+ return tdsqlite3_mutex_held(pFile->pInode->pLockMutex);
+}
+int unixFileMutexNotheld(unixFile *pFile){
+ assert( pFile->pInode );
+ return tdsqlite3_mutex_notheld(pFile->pInode->pLockMutex);
+}
+#endif
/*
**
@@ -34250,7 +38793,7 @@ static unixInodeInfo *inodeList = 0;
** unixLogError().
**
** It is invoked after an error occurs in an OS function and errno has been
-** set. It logs a message using sqlite3_log() containing the current value of
+** set. It logs a message using tdsqlite3_log() containing the current value of
** errno and, if possible, the human-readable equivalent from strerror() or
** strerror_r().
**
@@ -34305,7 +38848,7 @@ static int unixLogErrorAtLine(
#endif
if( zPath==0 ) zPath = "";
- sqlite3_log(errcode,
+ tdsqlite3_log(errcode,
"os_unix.c:%d: (%d) %s(%s) - %s",
iLine, iErrno, zFunc, zPath, zErr
);
@@ -34348,10 +38891,11 @@ static void closePendingFds(unixFile *pFile){
unixInodeInfo *pInode = pFile->pInode;
UnixUnusedFd *p;
UnixUnusedFd *pNext;
+ assert( unixFileMutexHeld(pFile) );
for(p=pInode->pUnused; p; p=pNext){
pNext = p->pNext;
robust_close(pFile, p->fd, __LINE__);
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
pInode->pUnused = 0;
}
@@ -34359,17 +38903,20 @@ static void closePendingFds(unixFile *pFile){
/*
** Release a unixInodeInfo structure previously allocated by findInodeInfo().
**
-** The mutex entered using the unixEnterMutex() function must be held
-** when this function is called.
+** The global mutex must be held when this routine is called, but the mutex
+** on the inode being deleted must NOT be held.
*/
static void releaseInodeInfo(unixFile *pFile){
unixInodeInfo *pInode = pFile->pInode;
assert( unixMutexHeld() );
+ assert( unixFileMutexNotheld(pFile) );
if( ALWAYS(pInode) ){
pInode->nRef--;
if( pInode->nRef==0 ){
assert( pInode->pShmNode==0 );
+ tdsqlite3_mutex_enter(pInode->pLockMutex);
closePendingFds(pFile);
+ tdsqlite3_mutex_leave(pInode->pLockMutex);
if( pInode->pPrev ){
assert( pInode->pPrev->pNext==pInode );
pInode->pPrev->pNext = pInode->pNext;
@@ -34381,7 +38928,8 @@ static void releaseInodeInfo(unixFile *pFile){
assert( pInode->pNext->pPrev==pInode );
pInode->pNext->pPrev = pInode->pPrev;
}
- sqlite3_free(pInode);
+ tdsqlite3_mutex_free(pInode->pLockMutex);
+ tdsqlite3_free(pInode);
}
}
}
@@ -34391,8 +38939,7 @@ static void releaseInodeInfo(unixFile *pFile){
** describes that file descriptor. Create a new one if necessary. The
** return value might be uninitialized if an error occurs.
**
-** The mutex entered using the unixEnterMutex() function must be held
-** when this function is called.
+** The global mutex must held when calling this routine.
**
** Return an appropriate error code.
*/
@@ -34451,20 +38998,29 @@ static int findInodeInfo(
#if OS_VXWORKS
fileId.pId = pFile->pId;
#else
- fileId.ino = statbuf.st_ino;
+ fileId.ino = (u64)statbuf.st_ino;
#endif
+ assert( unixMutexHeld() );
pInode = inodeList;
while( pInode && memcmp(&fileId, &pInode->fileId, sizeof(fileId)) ){
pInode = pInode->pNext;
}
if( pInode==0 ){
- pInode = sqlite3_malloc64( sizeof(*pInode) );
+ pInode = tdsqlite3_malloc64( sizeof(*pInode) );
if( pInode==0 ){
return SQLITE_NOMEM_BKPT;
}
memset(pInode, 0, sizeof(*pInode));
memcpy(&pInode->fileId, &fileId, sizeof(fileId));
+ if( tdsqlite3GlobalConfig.bCoreMutex ){
+ pInode->pLockMutex = tdsqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ if( pInode->pLockMutex==0 ){
+ tdsqlite3_free(pInode);
+ return SQLITE_NOMEM_BKPT;
+ }
+ }
pInode->nRef = 1;
+ assert( unixMutexHeld() );
pInode->pNext = inodeList;
pInode->pPrev = 0;
if( inodeList ) inodeList->pPrev = pInode;
@@ -34485,7 +39041,8 @@ static int fileHasMoved(unixFile *pFile){
#else
struct stat buf;
return pFile->pInode!=0 &&
- (osStat(pFile->zPath, &buf)!=0 || buf.st_ino!=pFile->pInode->fileId.ino);
+ (osStat(pFile->zPath, &buf)!=0
+ || (u64)buf.st_ino!=pFile->pInode->fileId.ino);
#endif
}
@@ -34497,7 +39054,7 @@ static int fileHasMoved(unixFile *pFile){
** (2) The file is not a symbolic link
** (3) The file has not been renamed or unlinked
**
-** Issue sqlite3_log(SQLITE_WARNING,...) messages if anything is not right.
+** Issue tdsqlite3_log(SQLITE_WARNING,...) messages if anything is not right.
*/
static void verifyDbFile(unixFile *pFile){
struct stat buf;
@@ -34508,19 +39065,19 @@ static void verifyDbFile(unixFile *pFile){
rc = osFstat(pFile->h, &buf);
if( rc!=0 ){
- sqlite3_log(SQLITE_WARNING, "cannot fstat db file %s", pFile->zPath);
+ tdsqlite3_log(SQLITE_WARNING, "cannot fstat db file %s", pFile->zPath);
return;
}
if( buf.st_nlink==0 ){
- sqlite3_log(SQLITE_WARNING, "file unlinked while open: %s", pFile->zPath);
+ tdsqlite3_log(SQLITE_WARNING, "file unlinked while open: %s", pFile->zPath);
return;
}
if( buf.st_nlink>1 ){
- sqlite3_log(SQLITE_WARNING, "multiple links to file: %s", pFile->zPath);
+ tdsqlite3_log(SQLITE_WARNING, "multiple links to file: %s", pFile->zPath);
return;
}
if( fileHasMoved(pFile) ){
- sqlite3_log(SQLITE_WARNING, "file renamed while open: %s", pFile->zPath);
+ tdsqlite3_log(SQLITE_WARNING, "file renamed while open: %s", pFile->zPath);
return;
}
}
@@ -34532,7 +39089,7 @@ static void verifyDbFile(unixFile *pFile){
** to a non-zero value otherwise *pResOut is set to zero. The return value
** is set to SQLITE_OK unless an I/O error occurs during lock checking.
*/
-static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){
+static int unixCheckReservedLock(tdsqlite3_file *id, int *pResOut){
int rc = SQLITE_OK;
int reserved = 0;
unixFile *pFile = (unixFile*)id;
@@ -34541,7 +39098,7 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){
assert( pFile );
assert( pFile->eFileLock<=SHARED_LOCK );
- unixEnterMutex(); /* Because pFile->pInode is shared across threads */
+ tdsqlite3_mutex_enter(pFile->pInode->pLockMutex);
/* Check if a thread in this process holds such a lock */
if( pFile->pInode->eFileLock>SHARED_LOCK ){
@@ -34566,7 +39123,7 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){
}
#endif
- unixLeaveMutex();
+ tdsqlite3_mutex_leave(pFile->pInode->pLockMutex);
OSTRACE(("TEST WR-LOCK %d %d %d (unix)\n", pFile->h, rc, reserved));
*pResOut = reserved;
@@ -34574,6 +39131,43 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){
}
/*
+** Set a posix-advisory-lock.
+**
+** There are two versions of this routine. If compiled with
+** SQLITE_ENABLE_SETLK_TIMEOUT then the routine has an extra parameter
+** which is a pointer to a unixFile. If the unixFile->iBusyTimeout
+** value is set, then it is the number of milliseconds to wait before
+** failing the lock. The iBusyTimeout value is always reset back to
+** zero on each call.
+**
+** If SQLITE_ENABLE_SETLK_TIMEOUT is not defined, then do a non-blocking
+** attempt to set the lock.
+*/
+#ifndef SQLITE_ENABLE_SETLK_TIMEOUT
+# define osSetPosixAdvisoryLock(h,x,t) osFcntl(h,F_SETLK,x)
+#else
+static int osSetPosixAdvisoryLock(
+ int h, /* The file descriptor on which to take the lock */
+ struct flock *pLock, /* The description of the lock */
+ unixFile *pFile /* Structure holding timeout value */
+){
+ int rc = osFcntl(h,F_SETLK,pLock);
+ while( rc<0 && pFile->iBusyTimeout>0 ){
+ /* On systems that support some kind of blocking file lock with a timeout,
+ ** make appropriate changes here to invoke that blocking file lock. On
+ ** generic posix, however, there is no such API. So we simply try the
+ ** lock once every millisecond until either the timeout expires, or until
+ ** the lock is obtained. */
+ usleep(1000);
+ rc = osFcntl(h,F_SETLK,pLock);
+ pFile->iBusyTimeout--;
+ }
+ return rc;
+}
+#endif /* SQLITE_ENABLE_SETLK_TIMEOUT */
+
+
+/*
** Attempt to set a system-lock on the file pFile. The lock is
** described by pLock.
**
@@ -34595,8 +39189,8 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){
static int unixFileLock(unixFile *pFile, struct flock *pLock){
int rc;
unixInodeInfo *pInode = pFile->pInode;
- assert( unixMutexHeld() );
assert( pInode!=0 );
+ assert( tdsqlite3_mutex_held(pInode->pLockMutex) );
if( (pFile->ctrlFlags & (UNIXFILE_EXCL|UNIXFILE_RDONLY))==UNIXFILE_EXCL ){
if( pInode->bProcessLock==0 ){
struct flock lock;
@@ -34605,7 +39199,7 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){
lock.l_start = SHARED_FIRST;
lock.l_len = SHARED_SIZE;
lock.l_type = F_WRLCK;
- rc = osFcntl(pFile->h, F_SETLK, &lock);
+ rc = osSetPosixAdvisoryLock(pFile->h, &lock, pFile);
if( rc<0 ) return rc;
pInode->bProcessLock = 1;
pInode->nLock++;
@@ -34613,7 +39207,7 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){
rc = 0;
}
}else{
- rc = osFcntl(pFile->h, F_SETLK, pLock);
+ rc = osSetPosixAdvisoryLock(pFile->h, pLock, pFile);
}
return rc;
}
@@ -34639,10 +39233,10 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){
** RESERVED -> (PENDING) -> EXCLUSIVE
** PENDING -> EXCLUSIVE
**
-** This routine will only increase a lock. Use the sqlite3OsUnlock()
+** This routine will only increase a lock. Use the tdsqlite3OsUnlock()
** routine to lower a locking level.
*/
-static int unixLock(sqlite3_file *id, int eFileLock){
+static int unixLock(tdsqlite3_file *id, int eFileLock){
/* The following describes the implementation of the various locks and
** lock transitions in terms of the POSIX advisory shared and exclusive
** lock primitives (called read-locks and write-locks below, to avoid
@@ -34715,8 +39309,8 @@ static int unixLock(sqlite3_file *id, int eFileLock){
/* This mutex is needed because pFile->pInode is shared across threads
*/
- unixEnterMutex();
pInode = pFile->pInode;
+ tdsqlite3_mutex_enter(pInode->pLockMutex);
/* If some thread using this PID has a lock via a different unixFile*
** handle that precludes the requested lock, return BUSY.
@@ -34859,7 +39453,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){
}
end_lock:
- unixLeaveMutex();
+ tdsqlite3_mutex_leave(pInode->pLockMutex);
OSTRACE(("LOCK %d %s %s (unix)\n", pFile->h, azFileLock(eFileLock),
rc==SQLITE_OK ? "ok" : "failed"));
return rc;
@@ -34871,11 +39465,12 @@ end_lock:
*/
static void setPendingFd(unixFile *pFile){
unixInodeInfo *pInode = pFile->pInode;
- UnixUnusedFd *p = pFile->pUnused;
+ UnixUnusedFd *p = pFile->pPreallocatedUnused;
+ assert( unixFileMutexHeld(pFile) );
p->pNext = pInode->pUnused;
pInode->pUnused = p;
pFile->h = -1;
- pFile->pUnused = 0;
+ pFile->pPreallocatedUnused = 0;
}
/*
@@ -34891,7 +39486,7 @@ static void setPendingFd(unixFile *pFile){
** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to
** remove the write lock on a region when a read lock is set.
*/
-static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){
+static int posixUnlock(tdsqlite3_file *id, int eFileLock, int handleNFSUnlock){
unixFile *pFile = (unixFile*)id;
unixInodeInfo *pInode;
struct flock lock;
@@ -34906,8 +39501,8 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){
if( pFile->eFileLock<=eFileLock ){
return SQLITE_OK;
}
- unixEnterMutex();
pInode = pFile->pInode;
+ tdsqlite3_mutex_enter(pInode->pLockMutex);
assert( pInode->nShared!=0 );
if( pFile->eFileLock>SHARED_LOCK ){
assert( pInode->eFileLock==pFile->eFileLock );
@@ -35033,14 +39628,14 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){
*/
pInode->nLock--;
assert( pInode->nLock>=0 );
- if( pInode->nLock==0 ){
- closePendingFds(pFile);
- }
+ if( pInode->nLock==0 ) closePendingFds(pFile);
}
end_unlock:
- unixLeaveMutex();
- if( rc==SQLITE_OK ) pFile->eFileLock = eFileLock;
+ tdsqlite3_mutex_leave(pInode->pLockMutex);
+ if( rc==SQLITE_OK ){
+ pFile->eFileLock = eFileLock;
+ }
return rc;
}
@@ -35051,7 +39646,7 @@ end_unlock:
** If the locking level of the file descriptor is already at or below
** the requested locking level, this routine is a no-op.
*/
-static int unixUnlock(sqlite3_file *id, int eFileLock){
+static int unixUnlock(tdsqlite3_file *id, int eFileLock){
#if SQLITE_MAX_MMAP_SIZE>0
assert( eFileLock==SHARED_LOCK || ((unixFile *)id)->nFetchOut==0 );
#endif
@@ -35073,7 +39668,7 @@ static void unixUnmapfile(unixFile *pFd);
** even on VxWorks. A mutex will be acquired on VxWorks by the
** vxworksReleaseFileId() routine.
*/
-static int closeUnixFile(sqlite3_file *id){
+static int closeUnixFile(tdsqlite3_file *id){
unixFile *pFile = (unixFile*)id;
#if SQLITE_MAX_MMAP_SIZE>0
unixUnmapfile(pFile);
@@ -35094,13 +39689,13 @@ static int closeUnixFile(sqlite3_file *id){
#ifdef SQLITE_UNLINK_AFTER_CLOSE
if( pFile->ctrlFlags & UNIXFILE_DELETE ){
osUnlink(pFile->zPath);
- sqlite3_free(*(char**)&pFile->zPath);
+ tdsqlite3_free(*(char**)&pFile->zPath);
pFile->zPath = 0;
}
#endif
OSTRACE(("CLOSE %-3d\n", pFile->h));
OpenCounter(-1);
- sqlite3_free(pFile->pUnused);
+ tdsqlite3_free(pFile->pPreallocatedUnused);
memset(pFile, 0, sizeof(unixFile));
return SQLITE_OK;
}
@@ -35108,18 +39703,23 @@ static int closeUnixFile(sqlite3_file *id){
/*
** Close a file.
*/
-static int unixClose(sqlite3_file *id){
+static int unixClose(tdsqlite3_file *id){
int rc = SQLITE_OK;
unixFile *pFile = (unixFile *)id;
+ unixInodeInfo *pInode = pFile->pInode;
+
+ assert( pInode!=0 );
verifyDbFile(pFile);
unixUnlock(id, NO_LOCK);
+ assert( unixFileMutexNotheld(pFile) );
unixEnterMutex();
/* unixFile.pInode is always valid here. Otherwise, a different close
** routine (e.g. nolockClose()) would be called instead.
*/
assert( pFile->pInode->nLock>0 || pFile->pInode->bProcessLock==0 );
- if( ALWAYS(pFile->pInode) && pFile->pInode->nLock ){
+ tdsqlite3_mutex_enter(pInode->pLockMutex);
+ if( pInode->nLock ){
/* If there are outstanding locks, do not actually close the file just
** yet because that would clear those locks. Instead, add the file
** descriptor to pInode->pUnused list. It will be automatically closed
@@ -35127,6 +39727,7 @@ static int unixClose(sqlite3_file *id){
*/
setPendingFd(pFile);
}
+ tdsqlite3_mutex_leave(pInode->pLockMutex);
releaseInodeInfo(pFile);
rc = closeUnixFile(id);
unixLeaveMutex();
@@ -35153,16 +39754,16 @@ static int unixClose(sqlite3_file *id){
** time and one or more of those connections are writing.
*/
-static int nolockCheckReservedLock(sqlite3_file *NotUsed, int *pResOut){
+static int nolockCheckReservedLock(tdsqlite3_file *NotUsed, int *pResOut){
UNUSED_PARAMETER(NotUsed);
*pResOut = 0;
return SQLITE_OK;
}
-static int nolockLock(sqlite3_file *NotUsed, int NotUsed2){
+static int nolockLock(tdsqlite3_file *NotUsed, int NotUsed2){
UNUSED_PARAMETER2(NotUsed, NotUsed2);
return SQLITE_OK;
}
-static int nolockUnlock(sqlite3_file *NotUsed, int NotUsed2){
+static int nolockUnlock(tdsqlite3_file *NotUsed, int NotUsed2){
UNUSED_PARAMETER2(NotUsed, NotUsed2);
return SQLITE_OK;
}
@@ -35170,7 +39771,7 @@ static int nolockUnlock(sqlite3_file *NotUsed, int NotUsed2){
/*
** Close the file.
*/
-static int nolockClose(sqlite3_file *id) {
+static int nolockClose(tdsqlite3_file *id) {
return closeUnixFile(id);
}
@@ -35215,7 +39816,7 @@ static int nolockClose(sqlite3_file *id) {
** variation of CheckReservedLock(), *pResOut is set to true if any lock
** is held on the file and false if the file is unlocked.
*/
-static int dotlockCheckReservedLock(sqlite3_file *id, int *pResOut) {
+static int dotlockCheckReservedLock(tdsqlite3_file *id, int *pResOut) {
int rc = SQLITE_OK;
int reserved = 0;
unixFile *pFile = (unixFile*)id;
@@ -35250,13 +39851,13 @@ static int dotlockCheckReservedLock(sqlite3_file *id, int *pResOut) {
** RESERVED -> (PENDING) -> EXCLUSIVE
** PENDING -> EXCLUSIVE
**
-** This routine will only increase a lock. Use the sqlite3OsUnlock()
+** This routine will only increase a lock. Use the tdsqlite3OsUnlock()
** routine to lower a locking level.
**
** With dotfile locking, we really only support state (4): EXCLUSIVE.
** But we track the other locking levels internally.
*/
-static int dotlockLock(sqlite3_file *id, int eFileLock) {
+static int dotlockLock(tdsqlite3_file *id, int eFileLock) {
unixFile *pFile = (unixFile*)id;
char *zLockFile = (char *)pFile->lockingContext;
int rc = SQLITE_OK;
@@ -35306,7 +39907,7 @@ static int dotlockLock(sqlite3_file *id, int eFileLock) {
**
** When the locking level reaches NO_LOCK, delete the lock file.
*/
-static int dotlockUnlock(sqlite3_file *id, int eFileLock) {
+static int dotlockUnlock(tdsqlite3_file *id, int eFileLock) {
unixFile *pFile = (unixFile*)id;
char *zLockFile = (char *)pFile->lockingContext;
int rc;
@@ -35349,11 +39950,11 @@ static int dotlockUnlock(sqlite3_file *id, int eFileLock) {
/*
** Close a file. Make sure the lock has been released before closing.
*/
-static int dotlockClose(sqlite3_file *id) {
+static int dotlockClose(tdsqlite3_file *id) {
unixFile *pFile = (unixFile*)id;
assert( id!=0 );
dotlockUnlock(id, NO_LOCK);
- sqlite3_free(pFile->lockingContext);
+ tdsqlite3_free(pFile->lockingContext);
return closeUnixFile(id);
}
/****************** End of the dot-file lock implementation *******************
@@ -35395,7 +39996,7 @@ static int robust_flock(int fd, int op){
** to a non-zero value otherwise *pResOut is set to zero. The return value
** is set to SQLITE_OK unless an I/O error occurs during lock checking.
*/
-static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){
+static int flockCheckReservedLock(tdsqlite3_file *id, int *pResOut){
int rc = SQLITE_OK;
int reserved = 0;
unixFile *pFile = (unixFile*)id;
@@ -35437,7 +40038,7 @@ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){
OSTRACE(("TEST WR-LOCK %d %d %d (flock)\n", pFile->h, rc, reserved));
#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS
- if( (rc & SQLITE_IOERR) == SQLITE_IOERR ){
+ if( (rc & 0xff) == SQLITE_IOERR ){
rc = SQLITE_OK;
reserved=1;
}
@@ -35468,14 +40069,14 @@ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){
** PENDING -> EXCLUSIVE
**
** flock() only really support EXCLUSIVE locks. We track intermediate
-** lock states in the sqlite3_file structure, but all locks SHARED or
+** lock states in the tdsqlite3_file structure, but all locks SHARED or
** above are really EXCLUSIVE locks and exclude all other processes from
** access the file.
**
-** This routine will only increase a lock. Use the sqlite3OsUnlock()
+** This routine will only increase a lock. Use the tdsqlite3OsUnlock()
** routine to lower a locking level.
*/
-static int flockLock(sqlite3_file *id, int eFileLock) {
+static int flockLock(tdsqlite3_file *id, int eFileLock) {
int rc = SQLITE_OK;
unixFile *pFile = (unixFile*)id;
@@ -35504,7 +40105,7 @@ static int flockLock(sqlite3_file *id, int eFileLock) {
OSTRACE(("LOCK %d %s %s (flock)\n", pFile->h, azFileLock(eFileLock),
rc==SQLITE_OK ? "ok" : "failed"));
#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS
- if( (rc & SQLITE_IOERR) == SQLITE_IOERR ){
+ if( (rc & 0xff) == SQLITE_IOERR ){
rc = SQLITE_BUSY;
}
#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */
@@ -35519,7 +40120,7 @@ static int flockLock(sqlite3_file *id, int eFileLock) {
** If the locking level of the file descriptor is already at or below
** the requested locking level, this routine is a no-op.
*/
-static int flockUnlock(sqlite3_file *id, int eFileLock) {
+static int flockUnlock(tdsqlite3_file *id, int eFileLock) {
unixFile *pFile = (unixFile*)id;
assert( pFile );
@@ -35553,7 +40154,7 @@ static int flockUnlock(sqlite3_file *id, int eFileLock) {
/*
** Close a file.
*/
-static int flockClose(sqlite3_file *id) {
+static int flockClose(tdsqlite3_file *id) {
assert( id!=0 );
flockUnlock(id, NO_LOCK);
return closeUnixFile(id);
@@ -35582,7 +40183,7 @@ static int flockClose(sqlite3_file *id) {
** to a non-zero value otherwise *pResOut is set to zero. The return value
** is set to SQLITE_OK unless an I/O error occurs during lock checking.
*/
-static int semXCheckReservedLock(sqlite3_file *id, int *pResOut) {
+static int semXCheckReservedLock(tdsqlite3_file *id, int *pResOut) {
int rc = SQLITE_OK;
int reserved = 0;
unixFile *pFile = (unixFile*)id;
@@ -35642,14 +40243,14 @@ static int semXCheckReservedLock(sqlite3_file *id, int *pResOut) {
** PENDING -> EXCLUSIVE
**
** Semaphore locks only really support EXCLUSIVE locks. We track intermediate
-** lock states in the sqlite3_file structure, but all locks SHARED or
+** lock states in the tdsqlite3_file structure, but all locks SHARED or
** above are really EXCLUSIVE locks and exclude all other processes from
** access the file.
**
-** This routine will only increase a lock. Use the sqlite3OsUnlock()
+** This routine will only increase a lock. Use the tdsqlite3OsUnlock()
** routine to lower a locking level.
*/
-static int semXLock(sqlite3_file *id, int eFileLock) {
+static int semXLock(tdsqlite3_file *id, int eFileLock) {
unixFile *pFile = (unixFile*)id;
sem_t *pSem = pFile->pInode->pSem;
int rc = SQLITE_OK;
@@ -35682,7 +40283,7 @@ static int semXLock(sqlite3_file *id, int eFileLock) {
** If the locking level of the file descriptor is already at or below
** the requested locking level, this routine is a no-op.
*/
-static int semXUnlock(sqlite3_file *id, int eFileLock) {
+static int semXUnlock(tdsqlite3_file *id, int eFileLock) {
unixFile *pFile = (unixFile*)id;
sem_t *pSem = pFile->pInode->pSem;
@@ -35719,11 +40320,12 @@ static int semXUnlock(sqlite3_file *id, int eFileLock) {
/*
** Close a file.
*/
-static int semXClose(sqlite3_file *id) {
+static int semXClose(tdsqlite3_file *id) {
if( id ){
unixFile *pFile = (unixFile*)id;
semXUnlock(id, NO_LOCK);
assert( pFile );
+ assert( unixFileMutexNotheld(pFile) );
unixEnterMutex();
releaseInodeInfo(pFile);
unixLeaveMutex();
@@ -35824,7 +40426,7 @@ static int afpSetLock(
** to a non-zero value otherwise *pResOut is set to zero. The return value
** is set to SQLITE_OK unless an I/O error occurs during lock checking.
*/
-static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){
+static int afpCheckReservedLock(tdsqlite3_file *id, int *pResOut){
int rc = SQLITE_OK;
int reserved = 0;
unixFile *pFile = (unixFile*)id;
@@ -35838,8 +40440,7 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){
*pResOut = 1;
return SQLITE_OK;
}
- unixEnterMutex(); /* Because pFile->pInode is shared across threads */
-
+ tdsqlite3_mutex_enter(pFile->pInode->pLockMutex);
/* Check if a thread in this process holds such a lock */
if( pFile->pInode->eFileLock>SHARED_LOCK ){
reserved = 1;
@@ -35863,7 +40464,7 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){
}
}
- unixLeaveMutex();
+ tdsqlite3_mutex_leave(pFile->pInode->pLockMutex);
OSTRACE(("TEST WR-LOCK %d %d %d (afp)\n", pFile->h, rc, reserved));
*pResOut = reserved;
@@ -35891,10 +40492,10 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){
** RESERVED -> (PENDING) -> EXCLUSIVE
** PENDING -> EXCLUSIVE
**
-** This routine will only increase a lock. Use the sqlite3OsUnlock()
+** This routine will only increase a lock. Use the tdsqlite3OsUnlock()
** routine to lower a locking level.
*/
-static int afpLock(sqlite3_file *id, int eFileLock){
+static int afpLock(tdsqlite3_file *id, int eFileLock){
int rc = SQLITE_OK;
unixFile *pFile = (unixFile*)id;
unixInodeInfo *pInode = pFile->pInode;
@@ -35926,8 +40527,8 @@ static int afpLock(sqlite3_file *id, int eFileLock){
/* This mutex is needed because pFile->pInode is shared across threads
*/
- unixEnterMutex();
pInode = pFile->pInode;
+ tdsqlite3_mutex_enter(pInode->pLockMutex);
/* If some thread using this PID has a lock via a different unixFile*
** handle that precludes the requested lock, return BUSY.
@@ -36041,7 +40642,7 @@ static int afpLock(sqlite3_file *id, int eFileLock){
/* Can't reestablish the shared lock. Sqlite can't deal, this is
** a critical I/O error
*/
- rc = ((failed & SQLITE_IOERR) == SQLITE_IOERR) ? failed2 :
+ rc = ((failed & 0xff) == SQLITE_IOERR) ? failed2 :
SQLITE_IOERR_LOCK;
goto afp_end_lock;
}
@@ -36063,7 +40664,7 @@ static int afpLock(sqlite3_file *id, int eFileLock){
}
afp_end_lock:
- unixLeaveMutex();
+ tdsqlite3_mutex_leave(pInode->pLockMutex);
OSTRACE(("LOCK %d %s %s (afp)\n", pFile->h, azFileLock(eFileLock),
rc==SQLITE_OK ? "ok" : "failed"));
return rc;
@@ -36076,7 +40677,7 @@ afp_end_lock:
** If the locking level of the file descriptor is already at or below
** the requested locking level, this routine is a no-op.
*/
-static int afpUnlock(sqlite3_file *id, int eFileLock) {
+static int afpUnlock(tdsqlite3_file *id, int eFileLock) {
int rc = SQLITE_OK;
unixFile *pFile = (unixFile*)id;
unixInodeInfo *pInode;
@@ -36095,8 +40696,8 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) {
if( pFile->eFileLock<=eFileLock ){
return SQLITE_OK;
}
- unixEnterMutex();
pInode = pFile->pInode;
+ tdsqlite3_mutex_enter(pInode->pLockMutex);
assert( pInode->nShared!=0 );
if( pFile->eFileLock>SHARED_LOCK ){
assert( pInode->eFileLock==pFile->eFileLock );
@@ -36165,36 +40766,42 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) {
if( rc==SQLITE_OK ){
pInode->nLock--;
assert( pInode->nLock>=0 );
- if( pInode->nLock==0 ){
- closePendingFds(pFile);
- }
+ if( pInode->nLock==0 ) closePendingFds(pFile);
}
}
- unixLeaveMutex();
- if( rc==SQLITE_OK ) pFile->eFileLock = eFileLock;
+ tdsqlite3_mutex_leave(pInode->pLockMutex);
+ if( rc==SQLITE_OK ){
+ pFile->eFileLock = eFileLock;
+ }
return rc;
}
/*
** Close a file & cleanup AFP specific locking context
*/
-static int afpClose(sqlite3_file *id) {
+static int afpClose(tdsqlite3_file *id) {
int rc = SQLITE_OK;
unixFile *pFile = (unixFile*)id;
assert( id!=0 );
afpUnlock(id, NO_LOCK);
+ assert( unixFileMutexNotheld(pFile) );
unixEnterMutex();
- if( pFile->pInode && pFile->pInode->nLock ){
- /* If there are outstanding locks, do not actually close the file just
- ** yet because that would clear those locks. Instead, add the file
- ** descriptor to pInode->aPending. It will be automatically closed when
- ** the last lock is cleared.
- */
- setPendingFd(pFile);
+ if( pFile->pInode ){
+ unixInodeInfo *pInode = pFile->pInode;
+ tdsqlite3_mutex_enter(pInode->pLockMutex);
+ if( pInode->nLock ){
+ /* If there are outstanding locks, do not actually close the file just
+ ** yet because that would clear those locks. Instead, add the file
+ ** descriptor to pInode->aPending. It will be automatically closed when
+ ** the last lock is cleared.
+ */
+ setPendingFd(pFile);
+ }
+ tdsqlite3_mutex_leave(pInode->pLockMutex);
}
releaseInodeInfo(pFile);
- sqlite3_free(pFile->lockingContext);
+ tdsqlite3_free(pFile->lockingContext);
rc = closeUnixFile(id);
unixLeaveMutex();
return rc;
@@ -36221,7 +40828,7 @@ static int afpClose(sqlite3_file *id) {
** If the locking level of the file descriptor is already at or below
** the requested locking level, this routine is a no-op.
*/
-static int nfsUnlock(sqlite3_file *id, int eFileLock){
+static int nfsUnlock(tdsqlite3_file *id, int eFileLock){
return posixUnlock(id, eFileLock, 1);
}
@@ -36235,10 +40842,10 @@ static int nfsUnlock(sqlite3_file *id, int eFileLock){
******************************************************************************/
/******************************************************************************
-**************** Non-locking sqlite3_file methods *****************************
+**************** Non-locking tdsqlite3_file methods *****************************
**
** The next division contains implementations for all methods of the
-** sqlite3_file object other than the locking methods. The locking
+** tdsqlite3_file object other than the locking methods. The locking
** methods were defined in divisions above (one locking method per
** division). Those methods that are common to all locking modes
** are gather together into this division.
@@ -36257,7 +40864,7 @@ static int nfsUnlock(sqlite3_file *id, int eFileLock){
** To avoid stomping the errno value on a failed read the lastErrno value
** is set before returning.
*/
-static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){
+static int seekAndRead(unixFile *id, tdsqlite3_int64 offset, void *pBuf, int cnt){
int got;
int prior = 0;
#if (!defined(USE_PREAD) && !defined(USE_PREAD64))
@@ -36307,10 +40914,10 @@ static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){
** wrong.
*/
static int unixRead(
- sqlite3_file *id,
+ tdsqlite3_file *id,
void *pBuf,
int amt,
- sqlite3_int64 offset
+ tdsqlite3_int64 offset
){
unixFile *pFile = (unixFile *)id;
int got;
@@ -36321,7 +40928,7 @@ static int unixRead(
/* If this is a database file (not a journal, master-journal or temp
** file), the bytes in the locking range should never be read or written. */
#if 0
- assert( pFile->pUnused==0
+ assert( pFile->pPreallocatedUnused==0
|| offset>=PENDING_BYTE+512
|| offset+amt<=PENDING_BYTE
);
@@ -36421,10 +41028,10 @@ static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){
** or some other error code on failure.
*/
static int unixWrite(
- sqlite3_file *id,
+ tdsqlite3_file *id,
const void *pBuf,
int amt,
- sqlite3_int64 offset
+ tdsqlite3_int64 offset
){
unixFile *pFile = (unixFile*)id;
int wrote = 0;
@@ -36434,7 +41041,7 @@ static int unixWrite(
/* If this is a database file (not a journal, master-journal or temp
** file), the bytes in the locking range should never be read or written. */
#if 0
- assert( pFile->pUnused==0
+ assert( pFile->pPreallocatedUnused==0
|| offset>=PENDING_BYTE+512
|| offset+amt<=PENDING_BYTE
);
@@ -36505,8 +41112,8 @@ static int unixWrite(
** Count the number of fullsyncs and normal syncs. This is used to test
** that syncs and fullsyncs are occurring at the right times.
*/
-SQLITE_API int sqlite3_sync_count = 0;
-SQLITE_API int sqlite3_fullsync_count = 0;
+SQLITE_API int tdsqlite3_sync_count = 0;
+SQLITE_API int tdsqlite3_fullsync_count = 0;
#endif
/*
@@ -36578,8 +41185,8 @@ static int full_fsync(int fd, int fullSync, int dataOnly){
** gets called with the correct arguments.
*/
#ifdef SQLITE_TEST
- if( fullSync ) sqlite3_fullsync_count++;
- sqlite3_sync_count++;
+ if( fullSync ) tdsqlite3_fullsync_count++;
+ tdsqlite3_sync_count++;
#endif
/* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a
@@ -36656,7 +41263,7 @@ static int openDirectory(const char *zFilename, int *pFd){
int fd = -1;
char zDirname[MAX_PATHNAME+1];
- sqlite3_snprintf(MAX_PATHNAME, zDirname, "%s", zFilename);
+ tdsqlite3_snprintf(MAX_PATHNAME, zDirname, "%s", zFilename);
for(ii=(int)strlen(zDirname); ii>0 && zDirname[ii]!='/'; ii--);
if( ii>0 ){
zDirname[ii] = '\0';
@@ -36664,7 +41271,7 @@ static int openDirectory(const char *zFilename, int *pFd){
if( zDirname[0]!='/' ) zDirname[0] = '.';
zDirname[1] = 0;
}
- fd = robust_open(zDirname, O_RDONLY|O_BINARY, 0);
+ fd = robust_open(zDirname, O_RDONLY|O_BINARY|O_NOFOLLOW, 0);
if( fd>=0 ){
OSTRACE(("OPENDIR %-3d %s\n", fd, zDirname));
}
@@ -36688,7 +41295,7 @@ static int openDirectory(const char *zFilename, int *pFd){
** the directory entry for the journal was never created) and the transaction
** will not roll back - possibly leading to database corruption.
*/
-static int unixSync(sqlite3_file *id, int flags){
+static int unixSync(tdsqlite3_file *id, int flags){
int rc;
unixFile *pFile = (unixFile*)id;
@@ -36738,7 +41345,7 @@ static int unixSync(sqlite3_file *id, int flags){
/*
** Truncate an open file to a specified size
*/
-static int unixTruncate(sqlite3_file *id, i64 nByte){
+static int unixTruncate(tdsqlite3_file *id, i64 nByte){
unixFile *pFile = (unixFile *)id;
int rc;
assert( pFile );
@@ -36788,7 +41395,7 @@ static int unixTruncate(sqlite3_file *id, i64 nByte){
/*
** Determine the current size of a file in bytes
*/
-static int unixFileSize(sqlite3_file *id, i64 *pSize){
+static int unixFileSize(tdsqlite3_file *id, i64 *pSize){
int rc;
struct stat buf;
assert( id );
@@ -36817,7 +41424,7 @@ static int unixFileSize(sqlite3_file *id, i64 *pSize){
** Handler for proxy-locking file-control verbs. Defined below in the
** proxying locking division.
*/
-static int proxyFileControl(sqlite3_file*,int,void*);
+static int proxyFileControl(tdsqlite3_file*,int,void*);
#endif
/*
@@ -36846,7 +41453,7 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){
do{
err = osFallocate(pFile->h, buf.st_size, nSize-buf.st_size);
}while( err==EINTR );
- if( err ) return SQLITE_IOERR_WRITE;
+ if( err && err!=EINVAL ) return SQLITE_IOERR_WRITE;
#else
/* If the OS does not have posix_fallocate(), fake it. Write a
** single byte to the last byte in each block that falls entirely
@@ -36911,9 +41518,24 @@ static int unixGetTempname(int nBuf, char *zBuf);
/*
** Information and control of an open file handle.
*/
-static int unixFileControl(sqlite3_file *id, int op, void *pArg){
+static int unixFileControl(tdsqlite3_file *id, int op, void *pArg){
unixFile *pFile = (unixFile*)id;
switch( op ){
+#if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE)
+ case SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: {
+ int rc = osIoctl(pFile->h, F2FS_IOC_START_ATOMIC_WRITE);
+ return rc ? SQLITE_IOERR_BEGIN_ATOMIC : SQLITE_OK;
+ }
+ case SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: {
+ int rc = osIoctl(pFile->h, F2FS_IOC_COMMIT_ATOMIC_WRITE);
+ return rc ? SQLITE_IOERR_COMMIT_ATOMIC : SQLITE_OK;
+ }
+ case SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: {
+ int rc = osIoctl(pFile->h, F2FS_IOC_ABORT_VOLATILE_WRITE);
+ return rc ? SQLITE_IOERR_ROLLBACK_ATOMIC : SQLITE_OK;
+ }
+#endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */
+
case SQLITE_FCNTL_LOCKSTATE: {
*(int*)pArg = pFile->eFileLock;
return SQLITE_OK;
@@ -36942,11 +41564,11 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
return SQLITE_OK;
}
case SQLITE_FCNTL_VFSNAME: {
- *(char**)pArg = sqlite3_mprintf("%s", pFile->pVfs->zName);
+ *(char**)pArg = tdsqlite3_mprintf("%s", pFile->pVfs->zName);
return SQLITE_OK;
}
case SQLITE_FCNTL_TEMPFILENAME: {
- char *zTFile = sqlite3_malloc64( pFile->pVfs->mxPathname );
+ char *zTFile = tdsqlite3_malloc64( pFile->pVfs->mxPathname );
if( zTFile ){
unixGetTempname(pFile->pVfs->mxPathname, zTFile);
*(char**)pArg = zTFile;
@@ -36957,13 +41579,27 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
*(int*)pArg = fileHasMoved(pFile);
return SQLITE_OK;
}
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+ case SQLITE_FCNTL_LOCK_TIMEOUT: {
+ pFile->iBusyTimeout = *(int*)pArg;
+ return SQLITE_OK;
+ }
+#endif
#if SQLITE_MAX_MMAP_SIZE>0
case SQLITE_FCNTL_MMAP_SIZE: {
i64 newLimit = *(i64*)pArg;
int rc = SQLITE_OK;
- if( newLimit>sqlite3GlobalConfig.mxMmap ){
- newLimit = sqlite3GlobalConfig.mxMmap;
+ if( newLimit>tdsqlite3GlobalConfig.mxMmap ){
+ newLimit = tdsqlite3GlobalConfig.mxMmap;
+ }
+
+ /* The value of newLimit may be eventually cast to (size_t) and passed
+ ** to mmap(). Restrict its value to 2GB if (size_t) is not at least a
+ ** 64-bit type. */
+ if( newLimit>0 && sizeof(size_t)<8 ){
+ newLimit = (newLimit & 0x7FFFFFFF);
}
+
*(i64*)pArg = pFile->mmapSizeMax;
if( newLimit>=0 && newLimit!=pFile->mmapSizeMax && pFile->nFetchOut==0 ){
pFile->mmapSizeMax = newLimit;
@@ -36997,30 +41633,41 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){
}
/*
-** Return the sector size in bytes of the underlying block device for
-** the specified file. This is almost always 512 bytes, but may be
-** larger for some devices.
+** If pFd->sectorSize is non-zero when this function is called, it is a
+** no-op. Otherwise, the values of pFd->sectorSize and
+** pFd->deviceCharacteristics are set according to the file-system
+** characteristics.
**
-** SQLite code assumes this function cannot fail. It also assumes that
-** if two files are created in the same file-system directory (i.e.
-** a database and its journal file) that the sector size will be the
-** same for both.
+** There are two versions of this function. One for QNX and one for all
+** other systems.
*/
-#ifndef __QNXNTO__
-static int unixSectorSize(sqlite3_file *NotUsed){
- UNUSED_PARAMETER(NotUsed);
- return SQLITE_DEFAULT_SECTOR_SIZE;
-}
-#endif
+#ifndef __QNXNTO__
+static void setDeviceCharacteristics(unixFile *pFd){
+ assert( pFd->deviceCharacteristics==0 || pFd->sectorSize!=0 );
+ if( pFd->sectorSize==0 ){
+#if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE)
+ int res;
+ u32 f = 0;
-/*
-** The following version of unixSectorSize() is optimized for QNX.
-*/
-#ifdef __QNXNTO__
+ /* Check for support for F2FS atomic batch writes. */
+ res = osIoctl(pFd->h, F2FS_IOC_GET_FEATURES, &f);
+ if( res==0 && (f & F2FS_FEATURE_ATOMIC_WRITE) ){
+ pFd->deviceCharacteristics = SQLITE_IOCAP_BATCH_ATOMIC;
+ }
+#endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */
+
+ /* Set the POWERSAFE_OVERWRITE flag if requested. */
+ if( pFd->ctrlFlags & UNIXFILE_PSOW ){
+ pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE;
+ }
+
+ pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE;
+ }
+}
+#else
#include <sys/dcmd_blk.h>
#include <sys/statvfs.h>
-static int unixSectorSize(sqlite3_file *id){
- unixFile *pFile = (unixFile*)id;
+static void setDeviceCharacteristics(unixFile *pFile){
if( pFile->sectorSize == 0 ){
struct statvfs fsInfo;
@@ -37028,7 +41675,7 @@ static int unixSectorSize(sqlite3_file *id){
pFile->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE;
pFile->deviceCharacteristics = 0;
if( fstatvfs(pFile->h, &fsInfo) == -1 ) {
- return pFile->sectorSize;
+ return;
}
if( !strcmp(fsInfo.f_basetype, "tmp") ) {
@@ -37089,9 +41736,24 @@ static int unixSectorSize(sqlite3_file *id){
pFile->deviceCharacteristics = 0;
pFile->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE;
}
- return pFile->sectorSize;
}
-#endif /* __QNXNTO__ */
+#endif
+
+/*
+** Return the sector size in bytes of the underlying block device for
+** the specified file. This is almost always 512 bytes, but may be
+** larger for some devices.
+**
+** SQLite code assumes this function cannot fail. It also assumes that
+** if two files are created in the same file-system directory (i.e.
+** a database and its journal file) that the sector size will be the
+** same for both.
+*/
+static int unixSectorSize(tdsqlite3_file *id){
+ unixFile *pFd = (unixFile*)id;
+ setDeviceCharacteristics(pFd);
+ return pFd->sectorSize;
+}
/*
** Return the device characteristics for the file.
@@ -37106,17 +41768,10 @@ static int unixSectorSize(sqlite3_file *id){
** Hence, while POWERSAFE_OVERWRITE is on by default, there is a file-control
** available to turn it off and URI query parameter available to turn it off.
*/
-static int unixDeviceCharacteristics(sqlite3_file *id){
- unixFile *p = (unixFile*)id;
- int rc = 0;
-#ifdef __QNXNTO__
- if( p->sectorSize==0 ) unixSectorSize(id);
- rc = p->deviceCharacteristics;
-#endif
- if( p->ctrlFlags & UNIXFILE_PSOW ){
- rc |= SQLITE_IOCAP_POWERSAFE_OVERWRITE;
- }
- return rc;
+static int unixDeviceCharacteristics(tdsqlite3_file *id){
+ unixFile *pFd = (unixFile*)id;
+ setDeviceCharacteristics(pFd);
+ return pFd->deviceCharacteristics;
}
#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0
@@ -37163,21 +41818,22 @@ static int unixGetpagesize(void){
**
** The following fields are read-only after the object is created:
**
-** fid
+** hShm
** zFilename
**
-** Either unixShmNode.mutex must be held or unixShmNode.nRef==0 and
+** Either unixShmNode.pShmMutex must be held or unixShmNode.nRef==0 and
** unixMutexHeld() is true when reading or writing any other field
** in this structure.
*/
struct unixShmNode {
unixInodeInfo *pInode; /* unixInodeInfo that owns this SHM node */
- sqlite3_mutex *mutex; /* Mutex to access this object */
+ tdsqlite3_mutex *pShmMutex; /* Mutex to access this object */
char *zFilename; /* Name of the mmapped file */
- int h; /* Open file descriptor */
+ int hShm; /* Open file descriptor */
int szRegion; /* Size of shared-memory regions */
u16 nRegion; /* Size of array apRegion */
u8 isReadonly; /* True if read-only */
+ u8 isUnlocked; /* True if no DMS lock held */
char **apRegion; /* Array of mapped shared-memory regions */
int nRef; /* Number of unixShm objects pointing to this */
unixShm *pFirst; /* All unixShm objects pointing to this */
@@ -37195,16 +41851,16 @@ struct unixShmNode {
** The following fields are initialized when this object is created and
** are read-only thereafter:
**
-** unixShm.pFile
+** unixShm.pShmNode
** unixShm.id
**
-** All other fields are read/write. The unixShm.pFile->mutex must be held
-** while accessing any read/write fields.
+** All other fields are read/write. The unixShm.pShmNode->pShmMutex must
+** be held while accessing any read/write fields.
*/
struct unixShm {
unixShmNode *pShmNode; /* The underlying unixShmNode object */
unixShm *pNext; /* Next unixShm with the same unixShmNode */
- u8 hasMutex; /* True if holding the unixShmNode mutex */
+ u8 hasMutex; /* True if holding the unixShmNode->pShmMutex */
u8 id; /* Id of this connection within its unixShmNode */
u16 sharedMask; /* Mask of shared locks held */
u16 exclMask; /* Mask of exclusive locks held */
@@ -37234,7 +41890,8 @@ static int unixShmSystemLock(
/* Access to the unixShmNode object is serialized by the caller */
pShmNode = pFile->pInode->pShmNode;
- assert( sqlite3_mutex_held(pShmNode->mutex) || pShmNode->nRef==0 );
+ assert( pShmNode->nRef==0 || tdsqlite3_mutex_held(pShmNode->pShmMutex) );
+ assert( pShmNode->nRef>0 || unixMutexHeld() );
/* Shared locks never span more than one byte */
assert( n==1 || lockType!=F_RDLCK );
@@ -37242,15 +41899,13 @@ static int unixShmSystemLock(
/* Locks are within range */
assert( n>=1 && n<=SQLITE_SHM_NLOCK );
- if( pShmNode->h>=0 ){
+ if( pShmNode->hShm>=0 ){
/* Initialize the locking parameters */
- memset(&f, 0, sizeof(f));
f.l_type = lockType;
f.l_whence = SEEK_SET;
f.l_start = ofst;
f.l_len = n;
-
- rc = osFcntl(pShmNode->h, F_SETLK, &f);
+ rc = osSetPosixAdvisoryLock(pShmNode->hShm, &f, pFile);
rc = (rc!=(-1)) ? SQLITE_OK : SQLITE_BUSY;
}
@@ -37322,22 +41977,90 @@ static void unixShmPurge(unixFile *pFd){
int nShmPerMap = unixShmRegionPerMap();
int i;
assert( p->pInode==pFd->pInode );
- sqlite3_mutex_free(p->mutex);
+ tdsqlite3_mutex_free(p->pShmMutex);
for(i=0; i<p->nRegion; i+=nShmPerMap){
- if( p->h>=0 ){
+ if( p->hShm>=0 ){
osMunmap(p->apRegion[i], p->szRegion);
}else{
- sqlite3_free(p->apRegion[i]);
+ tdsqlite3_free(p->apRegion[i]);
}
}
- sqlite3_free(p->apRegion);
- if( p->h>=0 ){
- robust_close(pFd, p->h, __LINE__);
- p->h = -1;
+ tdsqlite3_free(p->apRegion);
+ if( p->hShm>=0 ){
+ robust_close(pFd, p->hShm, __LINE__);
+ p->hShm = -1;
}
p->pInode->pShmNode = 0;
- sqlite3_free(p);
+ tdsqlite3_free(p);
+ }
+}
+
+/*
+** The DMS lock has not yet been taken on shm file pShmNode. Attempt to
+** take it now. Return SQLITE_OK if successful, or an SQLite error
+** code otherwise.
+**
+** If the DMS cannot be locked because this is a readonly_shm=1
+** connection and no other process already holds a lock, return
+** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1.
+*/
+static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){
+ struct flock lock;
+ int rc = SQLITE_OK;
+
+ /* Use F_GETLK to determine the locks other processes are holding
+ ** on the DMS byte. If it indicates that another process is holding
+ ** a SHARED lock, then this process may also take a SHARED lock
+ ** and proceed with opening the *-shm file.
+ **
+ ** Or, if no other process is holding any lock, then this process
+ ** is the first to open it. In this case take an EXCLUSIVE lock on the
+ ** DMS byte and truncate the *-shm file to zero bytes in size. Then
+ ** downgrade to a SHARED lock on the DMS byte.
+ **
+ ** If another process is holding an EXCLUSIVE lock on the DMS byte,
+ ** return SQLITE_BUSY to the caller (it will try again). An earlier
+ ** version of this code attempted the SHARED lock at this point. But
+ ** this introduced a subtle race condition: if the process holding
+ ** EXCLUSIVE failed just before truncating the *-shm file, then this
+ ** process might open and use the *-shm file without truncating it.
+ ** And if the *-shm file has been corrupted by a power failure or
+ ** system crash, the database itself may also become corrupt. */
+ lock.l_whence = SEEK_SET;
+ lock.l_start = UNIX_SHM_DMS;
+ lock.l_len = 1;
+ lock.l_type = F_WRLCK;
+ if( osFcntl(pShmNode->hShm, F_GETLK, &lock)!=0 ) {
+ rc = SQLITE_IOERR_LOCK;
+ }else if( lock.l_type==F_UNLCK ){
+ if( pShmNode->isReadonly ){
+ pShmNode->isUnlocked = 1;
+ rc = SQLITE_READONLY_CANTINIT;
+ }else{
+ rc = unixShmSystemLock(pDbFd, F_WRLCK, UNIX_SHM_DMS, 1);
+ /* The first connection to attach must truncate the -shm file. We
+ ** truncate to 3 bytes (an arbitrary small number, less than the
+ ** -shm header size) rather than 0 as a system debugging aid, to
+ ** help detect if a -shm file truncation is legitimate or is the work
+ ** or a rogue process. */
+#ifdef __ANDROID__
+ /* Fix SQLite DB corruption on some Samsung devices */
+ if( rc==SQLITE_OK && robust_ftruncate(pShmNode->hShm, 0) ){
+#else
+ if( rc==SQLITE_OK && robust_ftruncate(pShmNode->hShm, 3) ){
+#endif
+ rc = unixLogError(SQLITE_IOERR_SHMOPEN,"ftruncate",pShmNode->zFilename);
+ }
+ }
+ }else if( lock.l_type==F_WRLCK ){
+ rc = SQLITE_BUSY;
+ }
+
+ if( rc==SQLITE_OK ){
+ assert( lock.l_type==F_UNLCK || lock.l_type==F_RDLCK );
+ rc = unixShmSystemLock(pDbFd, F_RDLCK, UNIX_SHM_DMS, 1);
}
+ return rc;
}
/*
@@ -37378,13 +42101,13 @@ static void unixShmPurge(unixFile *pFd){
static int unixOpenSharedMemory(unixFile *pDbFd){
struct unixShm *p = 0; /* The connection to be opened */
struct unixShmNode *pShmNode; /* The underlying mmapped file */
- int rc; /* Result code */
+ int rc = SQLITE_OK; /* Result code */
unixInodeInfo *pInode; /* The inode of fd */
- char *zShmFilename; /* Name of the file used for SHM */
+ char *zShm; /* Name of the file used for SHM */
int nShmFilename; /* Size of the SHM filename in bytes */
/* Allocate space for the new unixShm object. */
- p = sqlite3_malloc64( sizeof(*p) );
+ p = tdsqlite3_malloc64( sizeof(*p) );
if( p==0 ) return SQLITE_NOMEM_BKPT;
memset(p, 0, sizeof(*p));
assert( pDbFd->pShm==0 );
@@ -37392,6 +42115,7 @@ static int unixOpenSharedMemory(unixFile *pDbFd){
/* Check to see if a unixShmNode object already exists. Reuse an existing
** one if present. Create a new one if necessary.
*/
+ assert( unixFileMutexNotheld(pDbFd) );
unixEnterMutex();
pInode = pDbFd->pInode;
pShmNode = pInode->pShmNode;
@@ -37415,63 +42139,55 @@ static int unixOpenSharedMemory(unixFile *pDbFd){
#else
nShmFilename = 6 + (int)strlen(zBasePath);
#endif
- pShmNode = sqlite3_malloc64( sizeof(*pShmNode) + nShmFilename );
+ pShmNode = tdsqlite3_malloc64( sizeof(*pShmNode) + nShmFilename );
if( pShmNode==0 ){
rc = SQLITE_NOMEM_BKPT;
goto shm_open_err;
}
memset(pShmNode, 0, sizeof(*pShmNode)+nShmFilename);
- zShmFilename = pShmNode->zFilename = (char*)&pShmNode[1];
+ zShm = pShmNode->zFilename = (char*)&pShmNode[1];
#ifdef SQLITE_SHM_DIRECTORY
- sqlite3_snprintf(nShmFilename, zShmFilename,
+ tdsqlite3_snprintf(nShmFilename, zShm,
SQLITE_SHM_DIRECTORY "/sqlite-shm-%x-%x",
(u32)sStat.st_ino, (u32)sStat.st_dev);
#else
- sqlite3_snprintf(nShmFilename, zShmFilename, "%s-shm", zBasePath);
- sqlite3FileSuffix3(pDbFd->zPath, zShmFilename);
+ tdsqlite3_snprintf(nShmFilename, zShm, "%s-shm", zBasePath);
+ tdsqlite3FileSuffix3(pDbFd->zPath, zShm);
#endif
- pShmNode->h = -1;
+ pShmNode->hShm = -1;
pDbFd->pInode->pShmNode = pShmNode;
pShmNode->pInode = pDbFd->pInode;
- if( sqlite3GlobalConfig.bCoreMutex ){
- pShmNode->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
- if( pShmNode->mutex==0 ){
+ if( tdsqlite3GlobalConfig.bCoreMutex ){
+ pShmNode->pShmMutex = tdsqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ if( pShmNode->pShmMutex==0 ){
rc = SQLITE_NOMEM_BKPT;
goto shm_open_err;
}
}
if( pInode->bProcessLock==0 ){
- int openFlags = O_RDWR | O_CREAT;
- if( sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){
- openFlags = O_RDONLY;
- pShmNode->isReadonly = 1;
+ if( 0==tdsqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){
+ pShmNode->hShm = robust_open(zShm, O_RDWR|O_CREAT|O_NOFOLLOW,
+ (sStat.st_mode&0777));
}
- pShmNode->h = robust_open(zShmFilename, openFlags, (sStat.st_mode&0777));
- if( pShmNode->h<0 ){
- rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename);
- goto shm_open_err;
+ if( pShmNode->hShm<0 ){
+ pShmNode->hShm = robust_open(zShm, O_RDONLY|O_NOFOLLOW,
+ (sStat.st_mode&0777));
+ if( pShmNode->hShm<0 ){
+ rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShm);
+ goto shm_open_err;
+ }
+ pShmNode->isReadonly = 1;
}
/* If this process is running as root, make sure that the SHM file
** is owned by the same user that owns the original database. Otherwise,
** the original owner will not be able to connect.
*/
- robustFchown(pShmNode->h, sStat.st_uid, sStat.st_gid);
-
- /* Check to see if another process is holding the dead-man switch.
- ** If not, truncate the file to zero length.
- */
- rc = SQLITE_OK;
- if( unixShmSystemLock(pDbFd, F_WRLCK, UNIX_SHM_DMS, 1)==SQLITE_OK ){
- if( robust_ftruncate(pShmNode->h, 0) ){
- rc = unixLogError(SQLITE_IOERR_SHMOPEN, "ftruncate", zShmFilename);
- }
- }
- if( rc==SQLITE_OK ){
- rc = unixShmSystemLock(pDbFd, F_RDLCK, UNIX_SHM_DMS, 1);
- }
- if( rc ) goto shm_open_err;
+ robustFchown(pShmNode->hShm, sStat.st_uid, sStat.st_gid);
+
+ rc = unixLockSharedMemory(pDbFd, pShmNode);
+ if( rc!=SQLITE_OK && rc!=SQLITE_READONLY_CANTINIT ) goto shm_open_err;
}
}
@@ -37488,19 +42204,19 @@ static int unixOpenSharedMemory(unixFile *pDbFd){
** the cover of the unixEnterMutex() mutex and the pointer from the
** new (struct unixShm) object to the pShmNode has been set. All that is
** left to do is to link the new object into the linked list starting
- ** at pShmNode->pFirst. This must be done while holding the pShmNode->mutex
- ** mutex.
+ ** at pShmNode->pFirst. This must be done while holding the
+ ** pShmNode->pShmMutex.
*/
- sqlite3_mutex_enter(pShmNode->mutex);
+ tdsqlite3_mutex_enter(pShmNode->pShmMutex);
p->pNext = pShmNode->pFirst;
pShmNode->pFirst = p;
- sqlite3_mutex_leave(pShmNode->mutex);
- return SQLITE_OK;
+ tdsqlite3_mutex_leave(pShmNode->pShmMutex);
+ return rc;
/* Jump here on any error */
shm_open_err:
unixShmPurge(pDbFd); /* This call frees pShmNode if required */
- sqlite3_free(p);
+ tdsqlite3_free(p);
unixLeaveMutex();
return rc;
}
@@ -37525,7 +42241,7 @@ shm_open_err:
** memory and SQLITE_OK returned.
*/
static int unixShmMap(
- sqlite3_file *fd, /* Handle open on database file */
+ tdsqlite3_file *fd, /* Handle open on database file */
int iRegion, /* Region to retrieve */
int szRegion, /* Size of regions */
int bExtend, /* True to extend file if necessary */
@@ -37546,11 +42262,16 @@ static int unixShmMap(
p = pDbFd->pShm;
pShmNode = p->pShmNode;
- sqlite3_mutex_enter(pShmNode->mutex);
+ tdsqlite3_mutex_enter(pShmNode->pShmMutex);
+ if( pShmNode->isUnlocked ){
+ rc = unixLockSharedMemory(pDbFd, pShmNode);
+ if( rc!=SQLITE_OK ) goto shmpage_out;
+ pShmNode->isUnlocked = 0;
+ }
assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 );
assert( pShmNode->pInode==pDbFd->pInode );
- assert( pShmNode->h>=0 || pDbFd->pInode->bProcessLock==1 );
- assert( pShmNode->h<0 || pDbFd->pInode->bProcessLock==0 );
+ assert( pShmNode->hShm>=0 || pDbFd->pInode->bProcessLock==1 );
+ assert( pShmNode->hShm<0 || pDbFd->pInode->bProcessLock==0 );
/* Minimum number of regions required to be mapped. */
nReqRegion = ((iRegion+nShmPerMap) / nShmPerMap) * nShmPerMap;
@@ -37562,12 +42283,12 @@ static int unixShmMap(
pShmNode->szRegion = szRegion;
- if( pShmNode->h>=0 ){
+ if( pShmNode->hShm>=0 ){
/* The requested region is not mapped into this processes address space.
** Check to see if it has been allocated (i.e. if the wal-index file is
** large enough to contain the requested region).
*/
- if( osFstat(pShmNode->h, &sStat) ){
+ if( osFstat(pShmNode->hShm, &sStat) ){
rc = SQLITE_IOERR_SHMSIZE;
goto shmpage_out;
}
@@ -37595,7 +42316,7 @@ static int unixShmMap(
assert( (nByte % pgsz)==0 );
for(iPg=(sStat.st_size/pgsz); iPg<(nByte/pgsz); iPg++){
int x = 0;
- if( seekAndWriteFd(pShmNode->h, iPg*pgsz + pgsz-1, "", 1, &x)!=1 ){
+ if( seekAndWriteFd(pShmNode->hShm, iPg*pgsz + pgsz-1,"",1,&x)!=1 ){
const char *zFile = pShmNode->zFilename;
rc = unixLogError(SQLITE_IOERR_SHMSIZE, "write", zFile);
goto shmpage_out;
@@ -37606,7 +42327,7 @@ static int unixShmMap(
}
/* Map the requested memory region into this processes address space. */
- apNew = (char **)sqlite3_realloc(
+ apNew = (char **)tdsqlite3_realloc(
pShmNode->apRegion, nReqRegion*sizeof(char *)
);
if( !apNew ){
@@ -37618,22 +42339,22 @@ static int unixShmMap(
int nMap = szRegion*nShmPerMap;
int i;
void *pMem;
- if( pShmNode->h>=0 ){
+ if( pShmNode->hShm>=0 ){
pMem = osMmap(0, nMap,
pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE,
- MAP_SHARED, pShmNode->h, szRegion*(i64)pShmNode->nRegion
+ MAP_SHARED, pShmNode->hShm, szRegion*(i64)pShmNode->nRegion
);
if( pMem==MAP_FAILED ){
rc = unixLogError(SQLITE_IOERR_SHMMAP, "mmap", pShmNode->zFilename);
goto shmpage_out;
}
}else{
- pMem = sqlite3_malloc64(szRegion);
+ pMem = tdsqlite3_malloc64(nMap);
if( pMem==0 ){
rc = SQLITE_NOMEM_BKPT;
goto shmpage_out;
}
- memset(pMem, 0, szRegion);
+ memset(pMem, 0, nMap);
}
for(i=0; i<nShmPerMap; i++){
@@ -37650,7 +42371,7 @@ shmpage_out:
*pp = 0;
}
if( pShmNode->isReadonly && rc==SQLITE_OK ) rc = SQLITE_READONLY;
- sqlite3_mutex_leave(pShmNode->mutex);
+ tdsqlite3_mutex_leave(pShmNode->pShmMutex);
return rc;
}
@@ -37663,7 +42384,7 @@ shmpage_out:
** not go from shared to exclusive or from exclusive to shared.
*/
static int unixShmLock(
- sqlite3_file *fd, /* Database file holding the shared memory */
+ tdsqlite3_file *fd, /* Database file holding the shared memory */
int ofst, /* First lock to acquire or release */
int n, /* Number of locks to acquire or release */
int flags /* What to do with the lock */
@@ -37684,12 +42405,12 @@ static int unixShmLock(
|| flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED)
|| flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE) );
assert( n==1 || (flags & SQLITE_SHM_EXCLUSIVE)!=0 );
- assert( pShmNode->h>=0 || pDbFd->pInode->bProcessLock==1 );
- assert( pShmNode->h<0 || pDbFd->pInode->bProcessLock==0 );
+ assert( pShmNode->hShm>=0 || pDbFd->pInode->bProcessLock==1 );
+ assert( pShmNode->hShm<0 || pDbFd->pInode->bProcessLock==0 );
mask = (1<<(ofst+n)) - (1<<ofst);
assert( n>1 || mask==(1<<ofst) );
- sqlite3_mutex_enter(pShmNode->mutex);
+ tdsqlite3_mutex_enter(pShmNode->pShmMutex);
if( flags & SQLITE_SHM_UNLOCK ){
u16 allMask = 0; /* Mask of locks held by siblings */
@@ -37762,7 +42483,7 @@ static int unixShmLock(
}
}
}
- sqlite3_mutex_leave(pShmNode->mutex);
+ tdsqlite3_mutex_leave(pShmNode->pShmMutex);
OSTRACE(("SHM-LOCK shmid-%d, pid-%d got %03x,%03x\n",
p->id, osGetpid(0), p->sharedMask, p->exclMask));
return rc;
@@ -37775,10 +42496,13 @@ static int unixShmLock(
** any load or store begun after the barrier.
*/
static void unixShmBarrier(
- sqlite3_file *fd /* Database file holding the shared memory */
+ tdsqlite3_file *fd /* Database file holding the shared memory */
){
UNUSED_PARAMETER(fd);
- sqlite3MemoryBarrier(); /* compiler-defined memory barrier */
+ tdsqlite3MemoryBarrier(); /* compiler-defined memory barrier */
+ assert( fd->pMethods->xLock==nolockLock
+ || unixFileMutexNotheld((unixFile*)fd)
+ );
unixEnterMutex(); /* Also mutex, for redundancy */
unixLeaveMutex();
}
@@ -37791,7 +42515,7 @@ static void unixShmBarrier(
** routine is a harmless no-op.
*/
static int unixShmUnmap(
- sqlite3_file *fd, /* The underlying database file */
+ tdsqlite3_file *fd, /* The underlying database file */
int deleteFlag /* Delete shared-memory if true */
){
unixShm *p; /* The connection to be closed */
@@ -37809,22 +42533,23 @@ static int unixShmUnmap(
/* Remove connection p from the set of connections associated
** with pShmNode */
- sqlite3_mutex_enter(pShmNode->mutex);
+ tdsqlite3_mutex_enter(pShmNode->pShmMutex);
for(pp=&pShmNode->pFirst; (*pp)!=p; pp = &(*pp)->pNext){}
*pp = p->pNext;
/* Free the connection p */
- sqlite3_free(p);
+ tdsqlite3_free(p);
pDbFd->pShm = 0;
- sqlite3_mutex_leave(pShmNode->mutex);
+ tdsqlite3_mutex_leave(pShmNode->pShmMutex);
/* If pShmNode->nRef has reached 0, then close the underlying
** shared-memory file, too */
+ assert( unixFileMutexNotheld(pDbFd) );
unixEnterMutex();
assert( pShmNode->nRef>0 );
pShmNode->nRef--;
if( pShmNode->nRef==0 ){
- if( deleteFlag && pShmNode->h>=0 ){
+ if( deleteFlag && pShmNode->hShm>=0 ){
osUnlink(pShmNode->zFilename);
}
unixShmPurge(pDbFd);
@@ -37866,7 +42591,7 @@ static void unixUnmapfile(unixFile *pFd){
** unixFile.mmapSize
** unixFile.mmapSizeActual
**
-** If unsuccessful, an error message is logged via sqlite3_log() and
+** If unsuccessful, an error message is logged via tdsqlite3_log() and
** the three variables above are zeroed. In this case SQLite should
** continue accessing the database using the xRead() and xWrite()
** methods.
@@ -38000,7 +42725,7 @@ static int unixMapfile(unixFile *pFd, i64 nMap){
** If this function does return a pointer, the caller must eventually
** release the reference by calling unixUnfetch().
*/
-static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){
+static int unixFetch(tdsqlite3_file *fd, i64 iOff, int nAmt, void **pp){
#if SQLITE_MAX_MMAP_SIZE>0
unixFile *pFd = (unixFile *)fd; /* The underlying database file */
#endif
@@ -38031,7 +42756,7 @@ static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){
** to inform the VFS layer that, according to POSIX, any existing mapping
** may now be invalid and should be unmapped.
*/
-static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){
+static int unixUnfetch(tdsqlite3_file *fd, i64 iOff, void *p){
#if SQLITE_MAX_MMAP_SIZE>0
unixFile *pFd = (unixFile *)fd; /* The underlying database file */
UNUSED_PARAMETER(iOff);
@@ -38060,20 +42785,20 @@ static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){
}
/*
-** Here ends the implementation of all sqlite3_file methods.
+** Here ends the implementation of all tdsqlite3_file methods.
**
-********************** End sqlite3_file Methods *******************************
+********************** End tdsqlite3_file Methods *******************************
******************************************************************************/
/*
-** This division contains definitions of sqlite3_io_methods objects that
+** This division contains definitions of tdsqlite3_io_methods objects that
** implement various file locking strategies. It also contains definitions
** of "finder" functions. A finder-function is used to locate the appropriate
-** sqlite3_io_methods object for a particular database file. The pAppData
-** field of the sqlite3_vfs VFS objects are initialized to be pointers to
+** tdsqlite3_io_methods object for a particular database file. The pAppData
+** field of the tdsqlite3_vfs VFS objects are initialized to be pointers to
** the correct finder-function for that VFS.
**
-** Most finder functions return a pointer to a fixed sqlite3_io_methods
+** Most finder functions return a pointer to a fixed tdsqlite3_io_methods
** object. The only interesting finder-function is autolockIoFinder, which
** looks at the filesystem type and tries to guess the best locking
** strategy from that.
@@ -38093,14 +42818,14 @@ static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){
**
** Each instance of this macro generates two objects:
**
-** * A constant sqlite3_io_methods object call METHOD that has locking
+** * A constant tdsqlite3_io_methods object call METHOD that has locking
** methods CLOSE, LOCK, UNLOCK, CKRESLOCK.
**
** * An I/O method finder function called FINDER that returns a pointer
** to the METHOD object in the previous bullet.
*/
#define IOMETHODS(FINDER,METHOD,VERSION,CLOSE,LOCK,UNLOCK,CKLOCK,SHMMAP) \
-static const sqlite3_io_methods METHOD = { \
+static const tdsqlite3_io_methods METHOD = { \
VERSION, /* iVersion */ \
CLOSE, /* xClose */ \
unixRead, /* xRead */ \
@@ -38121,21 +42846,21 @@ static const sqlite3_io_methods METHOD = { \
unixFetch, /* xFetch */ \
unixUnfetch, /* xUnfetch */ \
}; \
-static const sqlite3_io_methods *FINDER##Impl(const char *z, unixFile *p){ \
+static const tdsqlite3_io_methods *FINDER##Impl(const char *z, unixFile *p){ \
UNUSED_PARAMETER(z); UNUSED_PARAMETER(p); \
return &METHOD; \
} \
-static const sqlite3_io_methods *(*const FINDER)(const char*,unixFile *p) \
+static const tdsqlite3_io_methods *(*const FINDER)(const char*,unixFile *p) \
= FINDER##Impl;
/*
-** Here are all of the sqlite3_io_methods objects for each of the
+** Here are all of the tdsqlite3_io_methods objects for each of the
** locking strategies. Functions that return pointers to these methods
** are also created.
*/
IOMETHODS(
posixIoFinder, /* Finder function name */
- posixIoMethods, /* sqlite3_io_methods object name */
+ posixIoMethods, /* tdsqlite3_io_methods object name */
3, /* shared memory and mmap are enabled */
unixClose, /* xClose method */
unixLock, /* xLock method */
@@ -38145,8 +42870,8 @@ IOMETHODS(
)
IOMETHODS(
nolockIoFinder, /* Finder function name */
- nolockIoMethods, /* sqlite3_io_methods object name */
- 3, /* shared memory is disabled */
+ nolockIoMethods, /* tdsqlite3_io_methods object name */
+ 3, /* shared memory and mmap are enabled */
nolockClose, /* xClose method */
nolockLock, /* xLock method */
nolockUnlock, /* xUnlock method */
@@ -38155,7 +42880,7 @@ IOMETHODS(
)
IOMETHODS(
dotlockIoFinder, /* Finder function name */
- dotlockIoMethods, /* sqlite3_io_methods object name */
+ dotlockIoMethods, /* tdsqlite3_io_methods object name */
1, /* shared memory is disabled */
dotlockClose, /* xClose method */
dotlockLock, /* xLock method */
@@ -38167,7 +42892,7 @@ IOMETHODS(
#if SQLITE_ENABLE_LOCKING_STYLE
IOMETHODS(
flockIoFinder, /* Finder function name */
- flockIoMethods, /* sqlite3_io_methods object name */
+ flockIoMethods, /* tdsqlite3_io_methods object name */
1, /* shared memory is disabled */
flockClose, /* xClose method */
flockLock, /* xLock method */
@@ -38180,7 +42905,7 @@ IOMETHODS(
#if OS_VXWORKS
IOMETHODS(
semIoFinder, /* Finder function name */
- semIoMethods, /* sqlite3_io_methods object name */
+ semIoMethods, /* tdsqlite3_io_methods object name */
1, /* shared memory is disabled */
semXClose, /* xClose method */
semXLock, /* xLock method */
@@ -38193,7 +42918,7 @@ IOMETHODS(
#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
IOMETHODS(
afpIoFinder, /* Finder function name */
- afpIoMethods, /* sqlite3_io_methods object name */
+ afpIoMethods, /* tdsqlite3_io_methods object name */
1, /* shared memory is disabled */
afpClose, /* xClose method */
afpLock, /* xLock method */
@@ -38209,17 +42934,17 @@ IOMETHODS(
** it uses proxy, dot-file, AFP, and flock() locking methods on those
** secondary files. For this reason, the division that implements
** proxy locking is located much further down in the file. But we need
-** to go ahead and define the sqlite3_io_methods and finder function
+** to go ahead and define the tdsqlite3_io_methods and finder function
** for proxy locking here. So we forward declare the I/O methods.
*/
#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
-static int proxyClose(sqlite3_file*);
-static int proxyLock(sqlite3_file*, int);
-static int proxyUnlock(sqlite3_file*, int);
-static int proxyCheckReservedLock(sqlite3_file*, int*);
+static int proxyClose(tdsqlite3_file*);
+static int proxyLock(tdsqlite3_file*, int);
+static int proxyUnlock(tdsqlite3_file*, int);
+static int proxyCheckReservedLock(tdsqlite3_file*, int*);
IOMETHODS(
proxyIoFinder, /* Finder function name */
- proxyIoMethods, /* sqlite3_io_methods object name */
+ proxyIoMethods, /* tdsqlite3_io_methods object name */
1, /* shared memory is disabled */
proxyClose, /* xClose method */
proxyLock, /* xLock method */
@@ -38233,7 +42958,7 @@ IOMETHODS(
#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
IOMETHODS(
nfsIoFinder, /* Finder function name */
- nfsIoMethods, /* sqlite3_io_methods object name */
+ nfsIoMethods, /* tdsqlite3_io_methods object name */
1, /* shared memory is disabled */
unixClose, /* xClose method */
unixLock, /* xLock method */
@@ -38246,18 +42971,18 @@ IOMETHODS(
#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
/*
** This "finder" function attempts to determine the best locking strategy
-** for the database file "filePath". It then returns the sqlite3_io_methods
+** for the database file "filePath". It then returns the tdsqlite3_io_methods
** object that implements that strategy.
**
** This is for MacOSX only.
*/
-static const sqlite3_io_methods *autolockIoFinderImpl(
+static const tdsqlite3_io_methods *autolockIoFinderImpl(
const char *filePath, /* name of the database file */
unixFile *pNew /* open file object for the database file */
){
static const struct Mapping {
const char *zFilesystem; /* Filesystem type name */
- const sqlite3_io_methods *pMethods; /* Appropriate locking method */
+ const tdsqlite3_io_methods *pMethods; /* Appropriate locking method */
} aMap[] = {
{ "hfs", &posixIoMethods },
{ "ufs", &posixIoMethods },
@@ -38304,7 +43029,7 @@ static const sqlite3_io_methods *autolockIoFinderImpl(
return &dotlockIoMethods;
}
}
-static const sqlite3_io_methods
+static const tdsqlite3_io_methods
*(*const autolockIoFinder)(const char*,unixFile*) = autolockIoFinderImpl;
#endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */
@@ -38315,7 +43040,7 @@ static const sqlite3_io_methods
** locking works. If it does, then that is what is used. If it does not
** work, then fallback to named semaphore locking.
*/
-static const sqlite3_io_methods *vxworksIoFinderImpl(
+static const tdsqlite3_io_methods *vxworksIoFinderImpl(
const char *filePath, /* name of the database file */
unixFile *pNew /* the open file object */
){
@@ -38340,7 +43065,7 @@ static const sqlite3_io_methods *vxworksIoFinderImpl(
return &semIoMethods;
}
}
-static const sqlite3_io_methods
+static const tdsqlite3_io_methods
*(*const vxworksIoFinder)(const char*,unixFile*) = vxworksIoFinderImpl;
#endif /* OS_VXWORKS */
@@ -38348,43 +43073,32 @@ static const sqlite3_io_methods
/*
** An abstract type for a pointer to an IO method finder function:
*/
-typedef const sqlite3_io_methods *(*finder_type)(const char*,unixFile*);
+typedef const tdsqlite3_io_methods *(*finder_type)(const char*,unixFile*);
/****************************************************************************
-**************************** sqlite3_vfs methods ****************************
+**************************** tdsqlite3_vfs methods ****************************
**
** This division contains the implementation of methods on the
-** sqlite3_vfs object.
+** tdsqlite3_vfs object.
*/
/*
** Initialize the contents of the unixFile structure pointed to by pId.
*/
static int fillInUnixFile(
- sqlite3_vfs *pVfs, /* Pointer to vfs object */
+ tdsqlite3_vfs *pVfs, /* Pointer to vfs object */
int h, /* Open file descriptor of file being opened */
- sqlite3_file *pId, /* Write to the unixFile structure here */
+ tdsqlite3_file *pId, /* Write to the unixFile structure here */
const char *zFilename, /* Name of the file being opened */
int ctrlFlags /* Zero or more UNIXFILE_* values */
){
- const sqlite3_io_methods *pLockingStyle;
+ const tdsqlite3_io_methods *pLockingStyle;
unixFile *pNew = (unixFile *)pId;
int rc = SQLITE_OK;
assert( pNew->pInode==NULL );
- /* Usually the path zFilename should not be a relative pathname. The
- ** exception is when opening the proxy "conch" file in builds that
- ** include the special Apple locking styles.
- */
-#if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE
- assert( zFilename==0 || zFilename[0]=='/'
- || pVfs->pAppData==(void*)&autolockIoFinder );
-#else
- assert( zFilename==0 || zFilename[0]=='/' );
-#endif
-
/* No locking occurs in temporary files */
assert( zFilename!=0 || (ctrlFlags & UNIXFILE_NOLOCK)!=0 );
@@ -38394,9 +43108,9 @@ static int fillInUnixFile(
pNew->zPath = zFilename;
pNew->ctrlFlags = (u8)ctrlFlags;
#if SQLITE_MAX_MMAP_SIZE>0
- pNew->mmapSizeMax = sqlite3GlobalConfig.szMmap;
+ pNew->mmapSizeMax = tdsqlite3GlobalConfig.szMmap;
#endif
- if( sqlite3_uri_boolean(((ctrlFlags & UNIXFILE_URI) ? zFilename : 0),
+ if( tdsqlite3_uri_boolean(((ctrlFlags & UNIXFILE_URI) ? zFilename : 0),
"psow", SQLITE_POWERSAFE_OVERWRITE) ){
pNew->ctrlFlags |= UNIXFILE_PSOW;
}
@@ -38462,7 +43176,7 @@ static int fillInUnixFile(
** the afpLockingContext.
*/
afpLockingContext *pCtx;
- pNew->lockingContext = pCtx = sqlite3_malloc64( sizeof(*pCtx) );
+ pNew->lockingContext = pCtx = tdsqlite3_malloc64( sizeof(*pCtx) );
if( pCtx==0 ){
rc = SQLITE_NOMEM_BKPT;
}else{
@@ -38475,7 +43189,7 @@ static int fillInUnixFile(
unixEnterMutex();
rc = findInodeInfo(pNew, &pNew->pInode);
if( rc!=SQLITE_OK ){
- sqlite3_free(pNew->lockingContext);
+ tdsqlite3_free(pNew->lockingContext);
robust_close(pNew, h, __LINE__);
h = -1;
}
@@ -38492,11 +43206,11 @@ static int fillInUnixFile(
int nFilename;
assert( zFilename!=0 );
nFilename = (int)strlen(zFilename) + 6;
- zLockFile = (char *)sqlite3_malloc64(nFilename);
+ zLockFile = (char *)tdsqlite3_malloc64(nFilename);
if( zLockFile==0 ){
rc = SQLITE_NOMEM_BKPT;
}else{
- sqlite3_snprintf(nFilename, zLockFile, "%s" DOTLOCK_SUFFIX, zFilename);
+ tdsqlite3_snprintf(nFilename, zLockFile, "%s" DOTLOCK_SUFFIX, zFilename);
}
pNew->lockingContext = zLockFile;
}
@@ -38511,7 +43225,7 @@ static int fillInUnixFile(
if( (rc==SQLITE_OK) && (pNew->pInode->pSem==NULL) ){
char *zSemName = pNew->pInode->aSemName;
int n;
- sqlite3_snprintf(MAX_PATHNAME, zSemName, "/%s.sem",
+ tdsqlite3_snprintf(MAX_PATHNAME, zSemName, "/%s.sem",
pNew->pId->zCanonicalName);
for( n=1; zSemName[n]; n++ )
if( zSemName[n]=='/' ) zSemName[n] = '_';
@@ -38559,7 +43273,7 @@ static const char *unixTempFileDir(void){
};
unsigned int i = 0;
struct stat buf;
- const char *zDir = sqlite3_temp_directory;
+ const char *zDir = tdsqlite3_temp_directory;
if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR");
if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR");
@@ -38597,10 +43311,10 @@ static int unixGetTempname(int nBuf, char *zBuf){
if( zDir==0 ) return SQLITE_IOERR_GETTEMPPATH;
do{
u64 r;
- sqlite3_randomness(sizeof(r), &r);
+ tdsqlite3_randomness(sizeof(r), &r);
assert( nBuf>2 );
zBuf[nBuf-2] = 0;
- sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c",
+ tdsqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c",
zDir, r, 0);
if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ) return SQLITE_ERROR;
}while( osAccess(zBuf,0)==0 );
@@ -38643,6 +43357,8 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){
#if !OS_VXWORKS
struct stat sStat; /* Results of stat() call */
+ unixEnterMutex();
+
/* A stat() call may fail for various reasons. If this happens, it is
** almost certain that an open() call on the same path will also fail.
** For this reason, if an error occurs in the stat() call here, it is
@@ -38651,25 +43367,28 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){
**
** Even if a subsequent open() call does succeed, the consequences of
** not searching for a reusable file descriptor are not dire. */
- if( 0==osStat(zPath, &sStat) ){
+ if( inodeList!=0 && 0==osStat(zPath, &sStat) ){
unixInodeInfo *pInode;
- unixEnterMutex();
pInode = inodeList;
while( pInode && (pInode->fileId.dev!=sStat.st_dev
- || pInode->fileId.ino!=sStat.st_ino) ){
+ || pInode->fileId.ino!=(u64)sStat.st_ino) ){
pInode = pInode->pNext;
}
if( pInode ){
UnixUnusedFd **pp;
+ assert( tdsqlite3_mutex_notheld(pInode->pLockMutex) );
+ tdsqlite3_mutex_enter(pInode->pLockMutex);
+ flags &= (SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE);
for(pp=&pInode->pUnused; *pp && (*pp)->flags!=flags; pp=&((*pp)->pNext));
pUnused = *pp;
if( pUnused ){
*pp = pUnused->pNext;
}
+ tdsqlite3_mutex_leave(pInode->pLockMutex);
}
- unixLeaveMutex();
}
+ unixLeaveMutex();
#endif /* if !OS_VXWORKS */
return pUnused;
}
@@ -38714,7 +43433,7 @@ static int getFileMode(
** If the SQLITE_ENABLE_8_3_NAMES option is enabled, then the
** original filename is unavailable. But 8_3_NAMES is only used for
** FAT filesystems and permissions do not matter there, so just use
-** the default permissions.
+** the default permissions. In 8_3_NAMES mode, leave *pMode set to zero.
*/
static int findCreateFileMode(
const char *zPath, /* Path of file (possibly) being created */
@@ -38743,18 +43462,13 @@ static int findCreateFileMode(
** where NN is a decimal number. The NN naming schemes are
** used by the test_multiplex.c module.
*/
- nDb = sqlite3Strlen30(zPath) - 1;
+ nDb = tdsqlite3Strlen30(zPath) - 1;
while( zPath[nDb]!='-' ){
-#ifndef SQLITE_ENABLE_8_3_NAMES
- /* In the normal case (8+3 filenames disabled) the journal filename
- ** is guaranteed to contain a '-' character. */
- assert( nDb>0 );
- assert( sqlite3Isalnum(zPath[nDb]) );
-#else
- /* If 8+3 names are possible, then the journal file might not contain
- ** a '-' character. So check for that case and return early. */
+ /* In normal operation, the journal file name will always contain
+ ** a '-' character. However in 8+3 filename mode, or if a corrupt
+ ** rollback journal specifies a master journal with a goofy name, then
+ ** the '-' might be missing. */
if( nDb==0 || zPath[nDb]=='.' ) return SQLITE_OK;
-#endif
nDb--;
}
memcpy(zDb, zPath, nDb);
@@ -38768,7 +43482,7 @@ static int findCreateFileMode(
** filename, check for the "modeof" parameter. If present, interpret
** its value as a filename and try to copy the mode, uid and gid from
** that file. */
- const char *z = sqlite3_uri_parameter(zPath, "modeof");
+ const char *z = tdsqlite3_uri_parameter(zPath, "modeof");
if( z ){
rc = getFileMode(z, pMode, pUid, pGid);
}
@@ -38782,9 +43496,9 @@ static int findCreateFileMode(
** Previously, the SQLite OS layer used three functions in place of this
** one:
**
-** sqlite3OsOpenReadWrite();
-** sqlite3OsOpenReadOnly();
-** sqlite3OsOpenExclusive();
+** tdsqlite3OsOpenReadWrite();
+** tdsqlite3OsOpenReadOnly();
+** tdsqlite3OsOpenExclusive();
**
** These calls correspond to the following combinations of flags:
**
@@ -38799,16 +43513,16 @@ static int findCreateFileMode(
** OpenExclusive().
*/
static int unixOpen(
- sqlite3_vfs *pVfs, /* The VFS for which this is the xOpen method */
+ tdsqlite3_vfs *pVfs, /* The VFS for which this is the xOpen method */
const char *zPath, /* Pathname of file to be opened */
- sqlite3_file *pFile, /* The file descriptor to be filled in */
+ tdsqlite3_file *pFile, /* The file descriptor to be filled in */
int flags, /* Input flags to control the opening */
int *pOutFlags /* Output flags returned to SQLite core */
){
unixFile *p = (unixFile *)pFile;
int fd = -1; /* File descriptor returned by open() */
int openFlags = 0; /* Flags to pass to open() */
- int eType = flags&0xFFFFFF00; /* Type of file to open */
+ int eType = flags&0x0FFF00; /* Type of file to open */
int noLock; /* True to omit locking primitives */
int rc = SQLITE_OK; /* Function Return Code */
int ctrlFlags = 0; /* UNIXFILE_* flags */
@@ -38829,7 +43543,7 @@ static int unixOpen(
** a file-descriptor on the directory too. The first time unixSync()
** is called the directory file descriptor will be fsync()ed and close()d.
*/
- int syncDir = (isCreate && (
+ int isNewJrnl = (isCreate && (
eType==SQLITE_OPEN_MASTER_JOURNAL
|| eType==SQLITE_OPEN_MAIN_JOURNAL
|| eType==SQLITE_OPEN_WAL
@@ -38874,9 +43588,8 @@ static int unixOpen(
*/
if( randomnessPid!=osGetpid(0) ){
randomnessPid = osGetpid(0);
- sqlite3_randomness(0,0);
+ tdsqlite3_randomness(0,0);
}
-
memset(p, 0, sizeof(unixFile));
if( eType==SQLITE_OPEN_MAIN_DB ){
@@ -38885,21 +43598,21 @@ static int unixOpen(
if( pUnused ){
fd = pUnused->fd;
}else{
- pUnused = sqlite3_malloc64(sizeof(*pUnused));
+ pUnused = tdsqlite3_malloc64(sizeof(*pUnused));
if( !pUnused ){
return SQLITE_NOMEM_BKPT;
}
}
- p->pUnused = pUnused;
+ p->pPreallocatedUnused = pUnused;
/* Database filenames are double-zero terminated if they are not
** URIs with parameters. Hence, they can always be passed into
- ** sqlite3_uri_parameter(). */
+ ** tdsqlite3_uri_parameter(). */
assert( (flags & SQLITE_OPEN_URI) || zName[strlen(zName)+1]==0 );
}else if( !zName ){
/* If zName is NULL, the upper layer is requesting a temp file. */
- assert(isDelete && !syncDir);
+ assert(isDelete && !isNewJrnl);
rc = unixGetTempname(pVfs->mxPathname, zTmpname);
if( rc!=SQLITE_OK ){
return rc;
@@ -38907,7 +43620,7 @@ static int unixOpen(
zName = zTmpname;
/* Generated temporary filenames are always double-zero terminated
- ** for use by sqlite3_uri_parameter(). */
+ ** for use by tdsqlite3_uri_parameter(). */
assert( zName[strlen(zName)+1]==0 );
}
@@ -38919,7 +43632,7 @@ static int unixOpen(
if( isReadWrite ) openFlags |= O_RDWR;
if( isCreate ) openFlags |= O_CREAT;
if( isExclusive ) openFlags |= (O_EXCL|O_NOFOLLOW);
- openFlags |= (O_LARGEFILE|O_BINARY);
+ openFlags |= (O_LARGEFILE|O_BINARY|O_NOFOLLOW);
if( fd<0 ){
mode_t openMode; /* Permissions to create file with */
@@ -38927,32 +43640,47 @@ static int unixOpen(
gid_t gid; /* Groupid for the file */
rc = findCreateFileMode(zName, flags, &openMode, &uid, &gid);
if( rc!=SQLITE_OK ){
- assert( !p->pUnused );
+ assert( !p->pPreallocatedUnused );
assert( eType==SQLITE_OPEN_WAL || eType==SQLITE_OPEN_MAIN_JOURNAL );
return rc;
}
fd = robust_open(zName, openFlags, openMode);
OSTRACE(("OPENX %-3d %s 0%o\n", fd, zName, openFlags));
assert( !isExclusive || (openFlags & O_CREAT)!=0 );
- if( fd<0 && errno!=EISDIR && isReadWrite ){
- /* Failed to open the file for read/write access. Try read-only. */
- flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
- openFlags &= ~(O_RDWR|O_CREAT);
- flags |= SQLITE_OPEN_READONLY;
- openFlags |= O_RDONLY;
- isReadonly = 1;
- fd = robust_open(zName, openFlags, openMode);
+ if( fd<0 ){
+ if( isNewJrnl && errno==EACCES && osAccess(zName, F_OK) ){
+ /* If unable to create a journal because the directory is not
+ ** writable, change the error code to indicate that. */
+ rc = SQLITE_READONLY_DIRECTORY;
+ }else if( errno!=EISDIR && isReadWrite ){
+ /* Failed to open the file for read/write access. Try read-only. */
+ flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
+ openFlags &= ~(O_RDWR|O_CREAT);
+ flags |= SQLITE_OPEN_READONLY;
+ openFlags |= O_RDONLY;
+ isReadonly = 1;
+ fd = robust_open(zName, openFlags, openMode);
+ }
}
if( fd<0 ){
- rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zName);
+ int rc2 = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zName);
+ if( rc==SQLITE_OK ) rc = rc2;
goto open_finished;
}
- /* If this process is running as root and if creating a new rollback
- ** journal or WAL file, set the ownership of the journal or WAL to be
- ** the same as the original database.
+ /* The owner of the rollback journal or WAL file should always be the
+ ** same as the owner of the database file. Try to ensure that this is
+ ** the case. The chown() system call will be a no-op if the current
+ ** process lacks root privileges, be we should at least try. Without
+ ** this step, if a root process opens a database file, it can leave
+ ** behinds a journal/WAL that is owned by root and hence make the
+ ** database inaccessible to unprivileged processes.
+ **
+ ** If openMode==0, then that means uid and gid are not set correctly
+ ** (probably because SQLite is configured to use 8+3 filename mode) and
+ ** in that case we do not want to attempt the chown().
*/
- if( flags & (SQLITE_OPEN_WAL|SQLITE_OPEN_MAIN_JOURNAL) ){
+ if( openMode && (flags & (SQLITE_OPEN_WAL|SQLITE_OPEN_MAIN_JOURNAL))!=0 ){
robustFchown(fd, uid, gid);
}
}
@@ -38961,16 +43689,17 @@ static int unixOpen(
*pOutFlags = flags;
}
- if( p->pUnused ){
- p->pUnused->fd = fd;
- p->pUnused->flags = flags;
+ if( p->pPreallocatedUnused ){
+ p->pPreallocatedUnused->fd = fd;
+ p->pPreallocatedUnused->flags =
+ flags & (SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE);
}
if( isDelete ){
#if OS_VXWORKS
zPath = zName;
#elif defined(SQLITE_UNLINK_AFTER_CLOSE)
- zPath = sqlite3_mprintf("%s", zName);
+ zPath = tdsqlite3_mprintf("%s", zName);
if( zPath==0 ){
robust_close(p, fd, __LINE__);
return SQLITE_NOMEM_BKPT;
@@ -39004,7 +43733,7 @@ static int unixOpen(
if( isReadonly ) ctrlFlags |= UNIXFILE_RDONLY;
noLock = eType!=SQLITE_OPEN_MAIN_DB;
if( noLock ) ctrlFlags |= UNIXFILE_NOLOCK;
- if( syncDir ) ctrlFlags |= UNIXFILE_DIRSYNC;
+ if( isNewJrnl ) ctrlFlags |= UNIXFILE_DIRSYNC;
if( flags & SQLITE_OPEN_URI ) ctrlFlags |= UNIXFILE_URI;
#if SQLITE_ENABLE_LOCKING_STYLE
@@ -39029,7 +43758,7 @@ static int unixOpen(
if( rc!=SQLITE_OK ){
/* Use unixClose to clean up the resources added in fillInUnixFile
** and clear all the structure's references. Specifically,
- ** pFile->pMethods will be NULL so sqlite3OsClose will be a no-op
+ ** pFile->pMethods will be NULL so tdsqlite3OsClose will be a no-op
*/
unixClose(pFile);
return rc;
@@ -39040,11 +43769,14 @@ static int unixOpen(
}
#endif
+ assert( zPath==0 || zPath[0]=='/'
+ || eType==SQLITE_OPEN_MASTER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL
+ );
rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags);
open_finished:
if( rc!=SQLITE_OK ){
- sqlite3_free(p->pUnused);
+ tdsqlite3_free(p->pPreallocatedUnused);
}
return rc;
}
@@ -39055,7 +43787,7 @@ open_finished:
** the directory after deleting the file.
*/
static int unixDelete(
- sqlite3_vfs *NotUsed, /* VFS containing this as the xDelete method */
+ tdsqlite3_vfs *NotUsed, /* VFS containing this as the xDelete method */
const char *zPath, /* Name of file to be deleted */
int dirSync /* If true, fsync() directory after deleting file */
){
@@ -39103,7 +43835,7 @@ static int unixDelete(
** Otherwise return 0.
*/
static int unixAccess(
- sqlite3_vfs *NotUsed, /* The VFS containing this xAccess method */
+ tdsqlite3_vfs *NotUsed, /* The VFS containing this xAccess method */
const char *zPath, /* Path of the file to examine */
int flags, /* What do we want to learn about the zPath file? */
int *pResOut /* Write result boolean here */
@@ -39118,7 +43850,8 @@ static int unixAccess(
if( flags==SQLITE_ACCESS_EXISTS ){
struct stat buf;
- *pResOut = (0==osStat(zPath, &buf) && buf.st_size>0);
+ *pResOut = 0==osStat(zPath, &buf) &&
+ (!S_ISREG(buf.st_mode) || buf.st_size>0);
}else{
*pResOut = osAccess(zPath, W_OK|R_OK)==0;
}
@@ -39133,13 +43866,13 @@ static int mkFullPathname(
char *zOut, /* Output buffer */
int nOut /* Allocated size of buffer zOut */
){
- int nPath = sqlite3Strlen30(zPath);
+ int nPath = tdsqlite3Strlen30(zPath);
int iOff = 0;
if( zPath[0]!='/' ){
if( osGetcwd(zOut, nOut-2)==0 ){
return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath);
}
- iOff = sqlite3Strlen30(zOut);
+ iOff = tdsqlite3Strlen30(zOut);
zOut[iOff++] = '/';
}
if( (iOff+nPath+1)>nOut ){
@@ -39148,7 +43881,7 @@ static int mkFullPathname(
zOut[iOff] = '\0';
return SQLITE_CANTOPEN_BKPT;
}
- sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath);
+ tdsqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath);
return SQLITE_OK;
}
@@ -39157,12 +43890,12 @@ static int mkFullPathname(
** is stored as a nul-terminated string in the buffer pointed to by
** zPath.
**
-** zOut points to a buffer of at least sqlite3_vfs.mxPathname bytes
+** zOut points to a buffer of at least tdsqlite3_vfs.mxPathname bytes
** (in this case, MAX_PATHNAME bytes). The full-path is written to
** this buffer before returning.
*/
static int unixFullPathname(
- sqlite3_vfs *pVfs, /* Pointer to vfs object */
+ tdsqlite3_vfs *pVfs, /* Pointer to vfs object */
const char *zPath, /* Possibly relative input path */
int nOut, /* Size of output buffer in bytes */
char *zOut /* Output buffer */
@@ -39172,7 +43905,7 @@ static int unixFullPathname(
#else
int rc = SQLITE_OK;
int nByte;
- int nLink = 1; /* Number of symbolic links followed so far */
+ int nLink = 0; /* Number of symbolic links followed so far */
const char *zIn = zPath; /* Input path for each iteration of loop */
char *zDel = 0;
@@ -39201,10 +43934,11 @@ static int unixFullPathname(
}
if( bLink ){
+ nLink++;
if( zDel==0 ){
- zDel = sqlite3_malloc(nOut);
+ zDel = tdsqlite3_malloc(nOut);
if( zDel==0 ) rc = SQLITE_NOMEM_BKPT;
- }else if( ++nLink>SQLITE_MAX_SYMLINKS ){
+ }else if( nLink>=SQLITE_MAX_SYMLINKS ){
rc = SQLITE_CANTOPEN_BKPT;
}
@@ -39215,7 +43949,7 @@ static int unixFullPathname(
}else{
if( zDel[0]!='/' ){
int n;
- for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--);
+ for(n = tdsqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--);
if( nByte+n+1>nOut ){
rc = SQLITE_CANTOPEN_BKPT;
}else{
@@ -39239,7 +43973,8 @@ static int unixFullPathname(
zIn = zOut;
}while( rc==SQLITE_OK );
- sqlite3_free(zDel);
+ tdsqlite3_free(zDel);
+ if( rc==SQLITE_OK && nLink ) rc = SQLITE_OK_SYMLINK;
return rc;
#endif /* HAVE_READLINK && HAVE_LSTAT */
}
@@ -39251,7 +43986,7 @@ static int unixFullPathname(
** within the shared library, and closing the shared library.
*/
#include <dlfcn.h>
-static void *unixDlOpen(sqlite3_vfs *NotUsed, const char *zFilename){
+static void *unixDlOpen(tdsqlite3_vfs *NotUsed, const char *zFilename){
UNUSED_PARAMETER(NotUsed);
return dlopen(zFilename, RTLD_NOW | RTLD_GLOBAL);
}
@@ -39263,17 +43998,17 @@ static void *unixDlOpen(sqlite3_vfs *NotUsed, const char *zFilename){
** is available, zBufOut is left unmodified and SQLite uses a default
** error message.
*/
-static void unixDlError(sqlite3_vfs *NotUsed, int nBuf, char *zBufOut){
+static void unixDlError(tdsqlite3_vfs *NotUsed, int nBuf, char *zBufOut){
const char *zErr;
UNUSED_PARAMETER(NotUsed);
unixEnterMutex();
zErr = dlerror();
if( zErr ){
- sqlite3_snprintf(nBuf, zBufOut, "%s", zErr);
+ tdsqlite3_snprintf(nBuf, zBufOut, "%s", zErr);
}
unixLeaveMutex();
}
-static void (*unixDlSym(sqlite3_vfs *NotUsed, void *p, const char*zSym))(void){
+static void (*unixDlSym(tdsqlite3_vfs *NotUsed, void *p, const char*zSym))(void){
/*
** GCC with -pedantic-errors says that C90 does not allow a void* to be
** cast into a pointer to a function. And yet the library dlsym() routine
@@ -39296,7 +44031,7 @@ static void (*unixDlSym(sqlite3_vfs *NotUsed, void *p, const char*zSym))(void){
x = (void(*(*)(void*,const char*))(void))dlsym;
return (*x)(p, zSym);
}
-static void unixDlClose(sqlite3_vfs *NotUsed, void *pHandle){
+static void unixDlClose(tdsqlite3_vfs *NotUsed, void *pHandle){
UNUSED_PARAMETER(NotUsed);
dlclose(pHandle);
}
@@ -39310,7 +44045,7 @@ static void unixDlClose(sqlite3_vfs *NotUsed, void *pHandle){
/*
** Write nBuf bytes of random data to the supplied buffer zBuf.
*/
-static int unixRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){
+static int unixRandomness(tdsqlite3_vfs *NotUsed, int nBuf, char *zBuf){
UNUSED_PARAMETER(NotUsed);
assert((size_t)nBuf>=(sizeof(time_t)+sizeof(int)));
@@ -39357,7 +44092,7 @@ static int unixRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){
** might be greater than or equal to the argument, but not less
** than the argument.
*/
-static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){
+static int unixSleep(tdsqlite3_vfs *NotUsed, int microseconds){
#if OS_VXWORKS
struct timespec sp;
@@ -39381,10 +44116,10 @@ static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){
/*
** The following variable, if set to a non-zero value, is interpreted as
** the number of seconds since 1970 and is used to set the result of
-** sqlite3OsCurrentTime() during testing.
+** tdsqlite3OsCurrentTime() during testing.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_current_time = 0; /* Fake system time in seconds since 1970. */
+SQLITE_API int tdsqlite3_current_time = 0; /* Fake system time in seconds since 1970. */
#endif
/*
@@ -39397,26 +44132,26 @@ SQLITE_API int sqlite3_current_time = 0; /* Fake system time in seconds since 1
** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date
** cannot be found.
*/
-static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){
- static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000;
+static int unixCurrentTimeInt64(tdsqlite3_vfs *NotUsed, tdsqlite3_int64 *piNow){
+ static const tdsqlite3_int64 unixEpoch = 24405875*(tdsqlite3_int64)8640000;
int rc = SQLITE_OK;
#if defined(NO_GETTOD)
time_t t;
time(&t);
- *piNow = ((sqlite3_int64)t)*1000 + unixEpoch;
+ *piNow = ((tdsqlite3_int64)t)*1000 + unixEpoch;
#elif OS_VXWORKS
struct timespec sNow;
clock_gettime(CLOCK_REALTIME, &sNow);
- *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_nsec/1000000;
+ *piNow = unixEpoch + 1000*(tdsqlite3_int64)sNow.tv_sec + sNow.tv_nsec/1000000;
#else
struct timeval sNow;
(void)gettimeofday(&sNow, 0); /* Cannot fail given valid arguments */
- *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000;
+ *piNow = unixEpoch + 1000*(tdsqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000;
#endif
#ifdef SQLITE_TEST
- if( sqlite3_current_time ){
- *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch;
+ if( tdsqlite3_current_time ){
+ *piNow = 1000*(tdsqlite3_int64)tdsqlite3_current_time + unixEpoch;
}
#endif
UNUSED_PARAMETER(NotUsed);
@@ -39429,8 +44164,8 @@ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){
** current time and date as a Julian Day number into *prNow and
** return 0. Return 1 if the time and date cannot be found.
*/
-static int unixCurrentTime(sqlite3_vfs *NotUsed, double *prNow){
- sqlite3_int64 i = 0;
+static int unixCurrentTime(tdsqlite3_vfs *NotUsed, double *prNow){
+ tdsqlite3_int64 i = 0;
int rc;
UNUSED_PARAMETER(NotUsed);
rc = unixCurrentTimeInt64(0, &i);
@@ -39447,7 +44182,7 @@ static int unixCurrentTime(sqlite3_vfs *NotUsed, double *prNow){
** during SQLite operation. Only the integer return code is currently
** used.
*/
-static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){
+static int unixGetLastError(tdsqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){
UNUSED_PARAMETER(NotUsed);
UNUSED_PARAMETER(NotUsed2);
UNUSED_PARAMETER(NotUsed3);
@@ -39456,7 +44191,7 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){
/*
-************************ End of sqlite3_vfs methods ***************************
+************************ End of tdsqlite3_vfs methods ***************************
******************************************************************************/
/******************************************************************************
@@ -39509,9 +44244,9 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){
**
** C APIs
**
-** sqlite3_file_control(db, dbname, SQLITE_FCNTL_SET_LOCKPROXYFILE,
+** tdsqlite3_file_control(db, dbname, SQLITE_FCNTL_SET_LOCKPROXYFILE,
** <proxy_path> | ":auto:");
-** sqlite3_file_control(db, dbname, SQLITE_FCNTL_GET_LOCKPROXYFILE,
+** tdsqlite3_file_control(db, dbname, SQLITE_FCNTL_GET_LOCKPROXYFILE,
** &<proxy_path>);
**
**
@@ -39628,7 +44363,7 @@ struct proxyLockingContext {
int conchHeld; /* 1 if the conch is held, -1 if lockless */
int nFails; /* Number of conch taking failures */
void *oldLockingContext; /* Original lockingcontext to restore on close */
- sqlite3_io_methods const *pOldMethod; /* Original I/O methods for close */
+ tdsqlite3_io_methods const *pOldMethod; /* Original I/O methods for close */
};
/*
@@ -39712,7 +44447,7 @@ static int proxyCreateLockPath(const char *lockPath){
/*
** Create a new VFS file descriptor (stored in memory obtained from
-** sqlite3_malloc) and open the file named "path" in the file descriptor.
+** tdsqlite3_malloc) and open the file named "path" in the file descriptor.
**
** The caller is responsible not only for closing the file descriptor
** but also for freeing the memory associated with the file descriptor.
@@ -39725,8 +44460,8 @@ static int proxyCreateUnixFile(
int fd = -1;
unixFile *pNew;
int rc = SQLITE_OK;
- int openFlags = O_RDWR | O_CREAT;
- sqlite3_vfs dummyVfs;
+ int openFlags = O_RDWR | O_CREAT | O_NOFOLLOW;
+ tdsqlite3_vfs dummyVfs;
int terrno = 0;
UnixUnusedFd *pUnused = NULL;
@@ -39740,7 +44475,7 @@ static int proxyCreateUnixFile(
if( pUnused ){
fd = pUnused->fd;
}else{
- pUnused = sqlite3_malloc64(sizeof(*pUnused));
+ pUnused = tdsqlite3_malloc64(sizeof(*pUnused));
if( !pUnused ){
return SQLITE_NOMEM_BKPT;
}
@@ -39755,7 +44490,7 @@ static int proxyCreateUnixFile(
}
}
if( fd<0 ){
- openFlags = O_RDONLY;
+ openFlags = O_RDONLY | O_NOFOLLOW;
fd = robust_open(path, openFlags, 0);
terrno = errno;
}
@@ -39773,7 +44508,7 @@ static int proxyCreateUnixFile(
}
}
- pNew = (unixFile *)sqlite3_malloc64(sizeof(*pNew));
+ pNew = (unixFile *)tdsqlite3_malloc64(sizeof(*pNew));
if( pNew==NULL ){
rc = SQLITE_NOMEM_BKPT;
goto end_create_proxy;
@@ -39785,28 +44520,28 @@ static int proxyCreateUnixFile(
dummyVfs.zName = "dummy";
pUnused->fd = fd;
pUnused->flags = openFlags;
- pNew->pUnused = pUnused;
+ pNew->pPreallocatedUnused = pUnused;
- rc = fillInUnixFile(&dummyVfs, fd, (sqlite3_file*)pNew, path, 0);
+ rc = fillInUnixFile(&dummyVfs, fd, (tdsqlite3_file*)pNew, path, 0);
if( rc==SQLITE_OK ){
*ppFile = pNew;
return SQLITE_OK;
}
end_create_proxy:
robust_close(pNew, fd, __LINE__);
- sqlite3_free(pNew);
- sqlite3_free(pUnused);
+ tdsqlite3_free(pNew);
+ tdsqlite3_free(pUnused);
return rc;
}
#ifdef SQLITE_TEST
/* simulate multiple hosts by creating unique hostid file paths */
-SQLITE_API int sqlite3_hostid_num = 0;
+SQLITE_API int tdsqlite3_hostid_num = 0;
#endif
#define PROXY_HOSTIDLEN 16 /* conch file host id length */
-#ifdef HAVE_GETHOSTUUID
+#if HAVE_GETHOSTUUID
/* Not always defined in the headers as it ought to be */
extern int gethostuuid(uuid_t id, const struct timespec *wait);
#endif
@@ -39817,7 +44552,7 @@ extern int gethostuuid(uuid_t id, const struct timespec *wait);
static int proxyGetHostID(unsigned char *pHostID, int *pError){
assert(PROXY_HOSTIDLEN == sizeof(uuid_t));
memset(pHostID, 0, PROXY_HOSTIDLEN);
-#ifdef HAVE_GETHOSTUUID
+#if HAVE_GETHOSTUUID
{
struct timespec timeout = {1, 0}; /* 1 sec timeout */
if( gethostuuid(pHostID, &timeout) ){
@@ -39833,8 +44568,8 @@ static int proxyGetHostID(unsigned char *pHostID, int *pError){
#endif
#ifdef SQLITE_TEST
/* simulate multiple hosts by creating unique hostid file paths */
- if( sqlite3_hostid_num != 0){
- pHostID[0] = (char)(pHostID[0] + (char)(sqlite3_hostid_num & 0xFF));
+ if( tdsqlite3_hostid_num != 0){
+ pHostID[0] = (char)(pHostID[0] + (char)(tdsqlite3_hostid_num & 0xFF));
}
#endif
@@ -39871,27 +44606,27 @@ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){
pathLen = strlcpy(tPath, cPath, MAXPATHLEN);
if( pathLen>MAXPATHLEN || pathLen<6 ||
(strlcpy(&tPath[pathLen-5], "break", 6) != 5) ){
- sqlite3_snprintf(sizeof(errmsg),errmsg,"path error (len %d)",(int)pathLen);
+ tdsqlite3_snprintf(sizeof(errmsg),errmsg,"path error (len %d)",(int)pathLen);
goto end_breaklock;
}
/* read the conch content */
readLen = osPread(conchFile->h, buf, PROXY_MAXCONCHLEN, 0);
if( readLen<PROXY_PATHINDEX ){
- sqlite3_snprintf(sizeof(errmsg),errmsg,"read error (len %d)",(int)readLen);
+ tdsqlite3_snprintf(sizeof(errmsg),errmsg,"read error (len %d)",(int)readLen);
goto end_breaklock;
}
/* write it out to the temporary break file */
- fd = robust_open(tPath, (O_RDWR|O_CREAT|O_EXCL), 0);
+ fd = robust_open(tPath, (O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW), 0);
if( fd<0 ){
- sqlite3_snprintf(sizeof(errmsg), errmsg, "create failed (%d)", errno);
+ tdsqlite3_snprintf(sizeof(errmsg), errmsg, "create failed (%d)", errno);
goto end_breaklock;
}
if( osPwrite(fd, buf, readLen, 0) != (ssize_t)readLen ){
- sqlite3_snprintf(sizeof(errmsg), errmsg, "write failed (%d)", errno);
+ tdsqlite3_snprintf(sizeof(errmsg), errmsg, "write failed (%d)", errno);
goto end_breaklock;
}
if( rename(tPath, cPath) ){
- sqlite3_snprintf(sizeof(errmsg), errmsg, "rename failed (%d)", errno);
+ tdsqlite3_snprintf(sizeof(errmsg), errmsg, "rename failed (%d)", errno);
goto end_breaklock;
}
rc = 0;
@@ -39923,7 +44658,7 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){
memset(&conchModTime, 0, sizeof(conchModTime));
do {
- rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, lockType);
+ rc = conchFile->pMethod->xLock((tdsqlite3_file*)conchFile, lockType);
nTries ++;
if( rc==SQLITE_BUSY ){
/* If the lock failed (busy):
@@ -39974,10 +44709,10 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){
if( 0==proxyBreakConchLock(pFile, myHostID) ){
rc = SQLITE_OK;
if( lockType==EXCLUSIVE_LOCK ){
- rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, SHARED_LOCK);
+ rc = conchFile->pMethod->xLock((tdsqlite3_file*)conchFile, SHARED_LOCK);
}
if( !rc ){
- rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, lockType);
+ rc = conchFile->pMethod->xLock((tdsqlite3_file*)conchFile, lockType);
}
}
}
@@ -40117,7 +44852,7 @@ static int proxyTakeConch(unixFile *pFile){
}
writeSize = PROXY_PATHINDEX + strlen(&writeBuffer[PROXY_PATHINDEX]);
robust_ftruncate(conchFile->h, writeSize);
- rc = unixWrite((sqlite3_file *)conchFile, writeBuffer, writeSize, 0);
+ rc = unixWrite((tdsqlite3_file *)conchFile, writeBuffer, writeSize, 0);
full_fsync(conchFile->h,0,0);
/* If we created a new conch file (not just updated the contents of a
** valid conch file), try to match the permissions of the database
@@ -40150,7 +44885,7 @@ static int proxyTakeConch(unixFile *pFile){
}
}
}
- conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, SHARED_LOCK);
+ conchFile->pMethod->xUnlock((tdsqlite3_file*)conchFile, SHARED_LOCK);
end_takeconch:
OSTRACE(("TRANSPROXY: CLOSE %d\n", pFile->h));
@@ -40186,7 +44921,7 @@ static int proxyTakeConch(unixFile *pFile){
** from the conch file or the path was allocated on the stack
*/
if( tempLockPath ){
- pCtx->lockProxyPath = sqlite3DbStrDup(0, tempLockPath);
+ pCtx->lockProxyPath = tdsqlite3DbStrDup(0, tempLockPath);
if( !pCtx->lockProxyPath ){
rc = SQLITE_NOMEM_BKPT;
}
@@ -40201,7 +44936,7 @@ static int proxyTakeConch(unixFile *pFile){
afpCtx->dbPath = pCtx->lockProxyPath;
}
} else {
- conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK);
+ conchFile->pMethod->xUnlock((tdsqlite3_file*)conchFile, NO_LOCK);
}
OSTRACE(("TAKECONCH %d %s\n", conchFile->h,
rc==SQLITE_OK?"ok":"failed"));
@@ -40225,7 +44960,7 @@ static int proxyReleaseConch(unixFile *pFile){
(pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"),
osGetpid(0)));
if( pCtx->conchHeld>0 ){
- rc = conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK);
+ rc = conchFile->pMethod->xUnlock((tdsqlite3_file*)conchFile, NO_LOCK);
}
pCtx->conchHeld = 0;
OSTRACE(("RELEASECONCH %d %s\n", conchFile->h,
@@ -40235,7 +44970,7 @@ static int proxyReleaseConch(unixFile *pFile){
/*
** Given the name of a database file, compute the name of its conch file.
-** Store the conch filename in memory obtained from sqlite3_malloc64().
+** Store the conch filename in memory obtained from tdsqlite3_malloc64().
** Make *pConchPath point to the new name. Return SQLITE_OK on success
** or SQLITE_NOMEM if unable to obtain memory.
**
@@ -40251,7 +44986,7 @@ static int proxyCreateConchPathname(char *dbPath, char **pConchPath){
/* Allocate space for the conch filename and initialize the name to
** the name of the original database file. */
- *pConchPath = conchPath = (char *)sqlite3_malloc64(len + 8);
+ *pConchPath = conchPath = (char *)tdsqlite3_malloc64(len + 8);
if( conchPath==0 ){
return SQLITE_NOMEM_BKPT;
}
@@ -40299,12 +45034,12 @@ static int switchLockProxyPath(unixFile *pFile, const char *path) {
pCtx->lockProxy=NULL;
pCtx->conchHeld = 0;
if( lockProxy!=NULL ){
- rc=lockProxy->pMethod->xClose((sqlite3_file *)lockProxy);
+ rc=lockProxy->pMethod->xClose((tdsqlite3_file *)lockProxy);
if( rc ) return rc;
- sqlite3_free(lockProxy);
+ tdsqlite3_free(lockProxy);
}
- sqlite3_free(oldPath);
- pCtx->lockProxyPath = sqlite3DbStrDup(0, path);
+ tdsqlite3_free(oldPath);
+ pCtx->lockProxyPath = tdsqlite3DbStrDup(0, path);
}
return rc;
@@ -40367,7 +45102,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) {
OSTRACE(("TRANSPROXY %d for %s pid=%d\n", pFile->h,
(lockPath ? lockPath : ":auto:"), osGetpid(0)));
- pCtx = sqlite3_malloc64( sizeof(*pCtx) );
+ pCtx = tdsqlite3_malloc64( sizeof(*pCtx) );
if( pCtx==0 ){
return SQLITE_NOMEM_BKPT;
}
@@ -40399,11 +45134,11 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) {
}
}
if( rc==SQLITE_OK && lockPath ){
- pCtx->lockProxyPath = sqlite3DbStrDup(0, lockPath);
+ pCtx->lockProxyPath = tdsqlite3DbStrDup(0, lockPath);
}
if( rc==SQLITE_OK ){
- pCtx->dbPath = sqlite3DbStrDup(0, dbPath);
+ pCtx->dbPath = tdsqlite3DbStrDup(0, dbPath);
if( pCtx->dbPath==NULL ){
rc = SQLITE_NOMEM_BKPT;
}
@@ -40418,12 +45153,12 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) {
pFile->pMethod = &proxyIoMethods;
}else{
if( pCtx->conchFile ){
- pCtx->conchFile->pMethod->xClose((sqlite3_file *)pCtx->conchFile);
- sqlite3_free(pCtx->conchFile);
+ pCtx->conchFile->pMethod->xClose((tdsqlite3_file *)pCtx->conchFile);
+ tdsqlite3_free(pCtx->conchFile);
}
- sqlite3DbFree(0, pCtx->lockProxyPath);
- sqlite3_free(pCtx->conchFilePath);
- sqlite3_free(pCtx);
+ tdsqlite3DbFree(0, pCtx->lockProxyPath);
+ tdsqlite3_free(pCtx->conchFilePath);
+ tdsqlite3_free(pCtx);
}
OSTRACE(("TRANSPROXY %d %s\n", pFile->h,
(rc==SQLITE_OK ? "ok" : "failed")));
@@ -40432,10 +45167,10 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) {
/*
-** This routine handles sqlite3_file_control() calls that are specific
+** This routine handles tdsqlite3_file_control() calls that are specific
** to proxy locking.
*/
-static int proxyFileControl(sqlite3_file *id, int op, void *pArg){
+static int proxyFileControl(tdsqlite3_file *id, int op, void *pArg){
switch( op ){
case SQLITE_FCNTL_GET_LOCKPROXYFILE: {
unixFile *pFile = (unixFile*)id;
@@ -40491,14 +45226,14 @@ static int proxyFileControl(sqlite3_file *id, int op, void *pArg){
assert( 0 ); /* The call assures that only valid opcodes are sent */
}
}
- /*NOTREACHED*/
+ /*NOTREACHED*/ assert(0);
return SQLITE_ERROR;
}
/*
** Within this division (the proxying locking implementation) the procedures
** above this point are all utilities. The lock-related methods of the
-** proxy-locking sqlite3_io_method object follow.
+** proxy-locking tdsqlite3_io_method object follow.
*/
@@ -40508,14 +45243,14 @@ static int proxyFileControl(sqlite3_file *id, int op, void *pArg){
** to a non-zero value otherwise *pResOut is set to zero. The return value
** is set to SQLITE_OK unless an I/O error occurs during lock checking.
*/
-static int proxyCheckReservedLock(sqlite3_file *id, int *pResOut) {
+static int proxyCheckReservedLock(tdsqlite3_file *id, int *pResOut) {
unixFile *pFile = (unixFile*)id;
int rc = proxyTakeConch(pFile);
if( rc==SQLITE_OK ){
proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext;
if( pCtx->conchHeld>0 ){
unixFile *proxy = pCtx->lockProxy;
- return proxy->pMethod->xCheckReservedLock((sqlite3_file*)proxy, pResOut);
+ return proxy->pMethod->xCheckReservedLock((tdsqlite3_file*)proxy, pResOut);
}else{ /* conchHeld < 0 is lockless */
pResOut=0;
}
@@ -40544,17 +45279,17 @@ static int proxyCheckReservedLock(sqlite3_file *id, int *pResOut) {
** RESERVED -> (PENDING) -> EXCLUSIVE
** PENDING -> EXCLUSIVE
**
-** This routine will only increase a lock. Use the sqlite3OsUnlock()
+** This routine will only increase a lock. Use the tdsqlite3OsUnlock()
** routine to lower a locking level.
*/
-static int proxyLock(sqlite3_file *id, int eFileLock) {
+static int proxyLock(tdsqlite3_file *id, int eFileLock) {
unixFile *pFile = (unixFile*)id;
int rc = proxyTakeConch(pFile);
if( rc==SQLITE_OK ){
proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext;
if( pCtx->conchHeld>0 ){
unixFile *proxy = pCtx->lockProxy;
- rc = proxy->pMethod->xLock((sqlite3_file*)proxy, eFileLock);
+ rc = proxy->pMethod->xLock((tdsqlite3_file*)proxy, eFileLock);
pFile->eFileLock = proxy->eFileLock;
}else{
/* conchHeld < 0 is lockless */
@@ -40571,14 +45306,14 @@ static int proxyLock(sqlite3_file *id, int eFileLock) {
** If the locking level of the file descriptor is already at or below
** the requested locking level, this routine is a no-op.
*/
-static int proxyUnlock(sqlite3_file *id, int eFileLock) {
+static int proxyUnlock(tdsqlite3_file *id, int eFileLock) {
unixFile *pFile = (unixFile*)id;
int rc = proxyTakeConch(pFile);
if( rc==SQLITE_OK ){
proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext;
if( pCtx->conchHeld>0 ){
unixFile *proxy = pCtx->lockProxy;
- rc = proxy->pMethod->xUnlock((sqlite3_file*)proxy, eFileLock);
+ rc = proxy->pMethod->xUnlock((tdsqlite3_file*)proxy, eFileLock);
pFile->eFileLock = proxy->eFileLock;
}else{
/* conchHeld < 0 is lockless */
@@ -40590,7 +45325,7 @@ static int proxyUnlock(sqlite3_file *id, int eFileLock) {
/*
** Close a file that uses proxy locks.
*/
-static int proxyClose(sqlite3_file *id) {
+static int proxyClose(tdsqlite3_file *id) {
if( ALWAYS(id) ){
unixFile *pFile = (unixFile*)id;
proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext;
@@ -40599,11 +45334,11 @@ static int proxyClose(sqlite3_file *id) {
int rc = SQLITE_OK;
if( lockProxy ){
- rc = lockProxy->pMethod->xUnlock((sqlite3_file*)lockProxy, NO_LOCK);
+ rc = lockProxy->pMethod->xUnlock((tdsqlite3_file*)lockProxy, NO_LOCK);
if( rc ) return rc;
- rc = lockProxy->pMethod->xClose((sqlite3_file*)lockProxy);
+ rc = lockProxy->pMethod->xClose((tdsqlite3_file*)lockProxy);
if( rc ) return rc;
- sqlite3_free(lockProxy);
+ tdsqlite3_free(lockProxy);
pCtx->lockProxy = 0;
}
if( conchFile ){
@@ -40611,17 +45346,17 @@ static int proxyClose(sqlite3_file *id) {
rc = proxyReleaseConch(pFile);
if( rc ) return rc;
}
- rc = conchFile->pMethod->xClose((sqlite3_file*)conchFile);
+ rc = conchFile->pMethod->xClose((tdsqlite3_file*)conchFile);
if( rc ) return rc;
- sqlite3_free(conchFile);
+ tdsqlite3_free(conchFile);
}
- sqlite3DbFree(0, pCtx->lockProxyPath);
- sqlite3_free(pCtx->conchFilePath);
- sqlite3DbFree(0, pCtx->dbPath);
+ tdsqlite3DbFree(0, pCtx->lockProxyPath);
+ tdsqlite3_free(pCtx->conchFilePath);
+ tdsqlite3DbFree(0, pCtx->dbPath);
/* restore the original locking context and pMethod then close it */
pFile->lockingContext = pCtx->oldLockingContext;
pFile->pMethod = pCtx->pOldMethod;
- sqlite3_free(pCtx);
+ tdsqlite3_free(pCtx);
return pFile->pMethod->xClose(id);
}
return SQLITE_OK;
@@ -40643,7 +45378,7 @@ static int proxyClose(sqlite3_file *id) {
** Initialize the operating system interface.
**
** This routine registers all VFS implementations for unix-like operating
-** systems. This routine, and the sqlite3_os_end() routine that follows,
+** systems. This routine, and the tdsqlite3_os_end() routine that follows,
** should be the only routines in this file that are visible from other
** files.
**
@@ -40652,9 +45387,9 @@ static int proxyClose(sqlite3_file *id) {
** necessarily been initialized when this routine is called, and so they
** should not be used.
*/
-SQLITE_API int sqlite3_os_init(void){
+SQLITE_API int tdsqlite3_os_init(void){
/*
- ** The following macro defines an initializer for an sqlite3_vfs object.
+ ** The following macro defines an initializer for an tdsqlite3_vfs object.
** The name of the VFS is NAME. The pAppData is a pointer to a pointer
** to the "finder" function. (pAppData is a pointer to a pointer because
** silly C90 rules prohibit a void* from being cast to a function pointer
@@ -40667,7 +45402,7 @@ SQLITE_API int sqlite3_os_init(void){
** behaviors. See the division above that contains the IOMETHODS
** macro for addition information on finder-functions.
**
- ** Most finders simply return a pointer to a fixed sqlite3_io_methods
+ ** Most finders simply return a pointer to a fixed tdsqlite3_io_methods
** object. But the "autolockIoFinder" available on MacOSX does a little
** more than that; it looks at the filesystem type that hosts the
** database file and tries to choose an locking method appropriate for
@@ -40701,11 +45436,11 @@ SQLITE_API int sqlite3_os_init(void){
/*
** All default VFSes for unix are contained in the following array.
**
- ** Note that the sqlite3_vfs.pNext field of the VFS object is modified
+ ** Note that the tdsqlite3_vfs.pNext field of the VFS object is modified
** by the SQLite core when the VFS is registered. So the following
** array cannot be const.
*/
- static sqlite3_vfs aVfs[] = {
+ static tdsqlite3_vfs aVfs[] = {
#if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__)
UNIXVFS("unix", autolockIoFinder ),
#elif OS_VXWORKS
@@ -40735,12 +45470,13 @@ SQLITE_API int sqlite3_os_init(void){
/* Double-check that the aSyscall[] array has been constructed
** correctly. See ticket [bb3a86e890c8e96ab] */
- assert( ArraySize(aSyscall)==28 );
+ assert( ArraySize(aSyscall)==29 );
/* Register all VFSes defined in the aVfs[] array */
- for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){
- sqlite3_vfs_register(&aVfs[i], i==0);
+ for(i=0; i<(sizeof(aVfs)/sizeof(tdsqlite3_vfs)); i++){
+ tdsqlite3_vfs_register(&aVfs[i], i==0);
}
+ unixBigLock = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
return SQLITE_OK;
}
@@ -40751,7 +45487,8 @@ SQLITE_API int sqlite3_os_init(void){
** to release dynamically allocated objects. But not on unix.
** This routine is a no-op for unix.
*/
-SQLITE_API int sqlite3_os_end(void){
+SQLITE_API int tdsqlite3_os_end(void){
+ unixBigLock = 0;
return SQLITE_OK;
}
@@ -40837,7 +45574,7 @@ SQLITE_API int sqlite3_os_end(void){
******************************************************************************
**
** This file contains inline asm code for retrieving "high-performance"
-** counters for x86 class CPUs.
+** counters for x86 and x86_64 class CPUs.
*/
#ifndef SQLITE_HWTIME_H
#define SQLITE_HWTIME_H
@@ -40848,12 +45585,13 @@ SQLITE_API int sqlite3_os_end(void){
** processor and returns that value. This can be used for high-res
** profiling.
*/
-#if (defined(__GNUC__) || defined(_MSC_VER)) && \
- (defined(i386) || defined(__i386__) || defined(_M_IX86))
+#if !defined(__STRICT_ANSI__) && \
+ (defined(__GNUC__) || defined(_MSC_VER)) && \
+ (defined(i386) || defined(__i386__) || defined(_M_IX86))
#if defined(__GNUC__)
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned int lo, hi;
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return (sqlite_uint64)hi << 32 | lo;
@@ -40861,7 +45599,7 @@ SQLITE_API int sqlite3_os_end(void){
#elif defined(_MSC_VER)
- __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){
+ __declspec(naked) __inline sqlite_uint64 __cdecl tdsqlite3Hwtime(void){
__asm {
rdtsc
ret ; return value at EDX:EAX
@@ -40870,17 +45608,17 @@ SQLITE_API int sqlite3_os_end(void){
#endif
-#elif (defined(__GNUC__) && defined(__x86_64__))
+#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__))
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned long val;
__asm__ __volatile__ ("rdtsc" : "=A" (val));
return val;
}
-#elif (defined(__GNUC__) && defined(__ppc__))
+#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__))
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned long long retval;
unsigned long junk;
__asm__ __volatile__ ("\n\
@@ -40895,16 +45633,15 @@ SQLITE_API int sqlite3_os_end(void){
#else
- #error Need implementation of sqlite3Hwtime() for your platform.
-
/*
- ** To compile without implementing sqlite3Hwtime() for your platform,
- ** you can remove the above #error and use the following
- ** stub function. You will lose timing support for many
- ** of the debugging and testing utilities, but it should at
- ** least compile and run.
+ ** asm() is needed for hardware timing support. Without asm(),
+ ** disable the tdsqlite3Hwtime() routine.
+ **
+ ** tdsqlite3Hwtime() is only used for some obscure debugging
+ ** and analysis configurations, not in any deliverable, so this
+ ** should not be a great loss.
*/
-SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); }
+SQLITE_PRIVATE sqlite_uint64 tdsqlite3Hwtime(void){ return ((sqlite_uint64)0); }
#endif
@@ -40915,8 +45652,8 @@ SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); }
static sqlite_uint64 g_start;
static sqlite_uint64 g_elapsed;
-#define TIMER_START g_start=sqlite3Hwtime()
-#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start
+#define TIMER_START g_start=tdsqlite3Hwtime()
+#define TIMER_END g_elapsed=tdsqlite3Hwtime()-g_start
#define TIMER_ELAPSED g_elapsed
#else
#define TIMER_START
@@ -40930,32 +45667,32 @@ static sqlite_uint64 g_elapsed;
** is used for testing the I/O recovery logic.
*/
#if defined(SQLITE_TEST)
-SQLITE_API extern int sqlite3_io_error_hit;
-SQLITE_API extern int sqlite3_io_error_hardhit;
-SQLITE_API extern int sqlite3_io_error_pending;
-SQLITE_API extern int sqlite3_io_error_persist;
-SQLITE_API extern int sqlite3_io_error_benign;
-SQLITE_API extern int sqlite3_diskfull_pending;
-SQLITE_API extern int sqlite3_diskfull;
-#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X)
+SQLITE_API extern int tdsqlite3_io_error_hit;
+SQLITE_API extern int tdsqlite3_io_error_hardhit;
+SQLITE_API extern int tdsqlite3_io_error_pending;
+SQLITE_API extern int tdsqlite3_io_error_persist;
+SQLITE_API extern int tdsqlite3_io_error_benign;
+SQLITE_API extern int tdsqlite3_diskfull_pending;
+SQLITE_API extern int tdsqlite3_diskfull;
+#define SimulateIOErrorBenign(X) tdsqlite3_io_error_benign=(X)
#define SimulateIOError(CODE) \
- if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \
- || sqlite3_io_error_pending-- == 1 ) \
+ if( (tdsqlite3_io_error_persist && tdsqlite3_io_error_hit) \
+ || tdsqlite3_io_error_pending-- == 1 ) \
{ local_ioerr(); CODE; }
static void local_ioerr(){
IOTRACE(("IOERR\n"));
- sqlite3_io_error_hit++;
- if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++;
+ tdsqlite3_io_error_hit++;
+ if( !tdsqlite3_io_error_benign ) tdsqlite3_io_error_hardhit++;
}
#define SimulateDiskfullError(CODE) \
- if( sqlite3_diskfull_pending ){ \
- if( sqlite3_diskfull_pending == 1 ){ \
+ if( tdsqlite3_diskfull_pending ){ \
+ if( tdsqlite3_diskfull_pending == 1 ){ \
local_ioerr(); \
- sqlite3_diskfull = 1; \
- sqlite3_io_error_hit = 1; \
+ tdsqlite3_diskfull = 1; \
+ tdsqlite3_io_error_hit = 1; \
CODE; \
}else{ \
- sqlite3_diskfull_pending--; \
+ tdsqlite3_diskfull_pending--; \
} \
}
#else
@@ -40968,8 +45705,8 @@ static void local_ioerr(){
** When testing, keep a count of the number of open files.
*/
#if defined(SQLITE_TEST)
-SQLITE_API extern int sqlite3_open_file_count;
-#define OpenCounter(X) sqlite3_open_file_count+=(X)
+SQLITE_API extern int tdsqlite3_open_file_count;
+#define OpenCounter(X) tdsqlite3_open_file_count+=(X)
#else
#define OpenCounter(X)
#endif /* defined(SQLITE_TEST) */
@@ -41215,13 +45952,13 @@ typedef struct winceLock {
#endif
/*
-** The winFile structure is a subclass of sqlite3_file* specific to the win32
+** The winFile structure is a subclass of tdsqlite3_file* specific to the win32
** portability layer.
*/
typedef struct winFile winFile;
struct winFile {
- const sqlite3_io_methods *pMethod; /*** Must be first ***/
- sqlite3_vfs *pVfs; /* The VFS used to open this file */
+ const tdsqlite3_io_methods *pMethod; /*** Must be first ***/
+ tdsqlite3_vfs *pVfs; /* The VFS used to open this file */
HANDLE h; /* Handle for accessing the file */
u8 locktype; /* Type of lock currently held on this file */
short sharedLockByte; /* Randomly chosen byte used as a shared lock */
@@ -41243,9 +45980,8 @@ struct winFile {
int nFetchOut; /* Number of outstanding xFetch references */
HANDLE hMap; /* Handle for accessing memory mapping */
void *pMapRegion; /* Area memory mapped */
- sqlite3_int64 mmapSize; /* Usable size of mapped region */
- sqlite3_int64 mmapSizeActual; /* Actual size of mapped region */
- sqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */
+ tdsqlite3_int64 mmapSize; /* Size of mapped region */
+ tdsqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */
#endif
};
@@ -41255,7 +45991,7 @@ struct winFile {
*/
typedef struct winVfsAppData winVfsAppData;
struct winVfsAppData {
- const sqlite3_io_methods *pMethod; /* The file I/O methods to use. */
+ const tdsqlite3_io_methods *pMethod; /* The file I/O methods to use. */
void *pAppData; /* The extra pAppData, if any. */
BOOL bNoLock; /* Non-zero if locking is disabled. */
};
@@ -41268,29 +46004,13 @@ struct winVfsAppData {
#define WINFILE_PSOW 0x10 /* SQLITE_IOCAP_POWERSAFE_OVERWRITE */
/*
- * The size of the buffer used by sqlite3_win32_write_debug().
+ * The size of the buffer used by tdsqlite3_win32_write_debug().
*/
#ifndef SQLITE_WIN32_DBG_BUF_SIZE
# define SQLITE_WIN32_DBG_BUF_SIZE ((int)(4096-sizeof(DWORD)))
#endif
/*
- * The value used with sqlite3_win32_set_directory() to specify that
- * the data directory should be changed.
- */
-#ifndef SQLITE_WIN32_DATA_DIRECTORY_TYPE
-# define SQLITE_WIN32_DATA_DIRECTORY_TYPE (1)
-#endif
-
-/*
- * The value used with sqlite3_win32_set_directory() to specify that
- * the temporary directory should be changed.
- */
-#ifndef SQLITE_WIN32_TEMP_DIRECTORY_TYPE
-# define SQLITE_WIN32_TEMP_DIRECTORY_TYPE (2)
-#endif
-
-/*
* If compiled with SQLITE_WIN32_MALLOC on Windows, we will use the
* various Win32 API heap functions instead of our own.
*/
@@ -41304,14 +46024,41 @@ struct winVfsAppData {
*
******************************************************************************
* WARNING: It is important to note that when this setting is non-zero and the
- * winMemShutdown function is called (e.g. by the sqlite3_shutdown
+ * winMemShutdown function is called (e.g. by the tdsqlite3_shutdown
* function), all data that was allocated using the isolated heap will
* be freed immediately and any attempt to access any of that freed
* data will almost certainly result in an immediate access violation.
******************************************************************************
*/
#ifndef SQLITE_WIN32_HEAP_CREATE
-# define SQLITE_WIN32_HEAP_CREATE (TRUE)
+# define SQLITE_WIN32_HEAP_CREATE (TRUE)
+#endif
+
+/*
+ * This is the maximum possible initial size of the Win32-specific heap, in
+ * bytes.
+ */
+#ifndef SQLITE_WIN32_HEAP_MAX_INIT_SIZE
+# define SQLITE_WIN32_HEAP_MAX_INIT_SIZE (4294967295U)
+#endif
+
+/*
+ * This is the extra space for the initial size of the Win32-specific heap,
+ * in bytes. This value may be zero.
+ */
+#ifndef SQLITE_WIN32_HEAP_INIT_EXTRA
+# define SQLITE_WIN32_HEAP_INIT_EXTRA (4194304)
+#endif
+
+/*
+ * Calculate the maximum legal cache size, in pages, based on the maximum
+ * possible initial heap size and the default page size, setting aside the
+ * needed extra space.
+ */
+#ifndef SQLITE_WIN32_MAX_CACHE_SIZE
+# define SQLITE_WIN32_MAX_CACHE_SIZE (((SQLITE_WIN32_HEAP_MAX_INIT_SIZE) - \
+ (SQLITE_WIN32_HEAP_INIT_EXTRA)) / \
+ (SQLITE_DEFAULT_PAGE_SIZE))
#endif
/*
@@ -41320,25 +46067,36 @@ struct winVfsAppData {
*/
#ifndef SQLITE_WIN32_CACHE_SIZE
# if SQLITE_DEFAULT_CACHE_SIZE>=0
-# define SQLITE_WIN32_CACHE_SIZE (SQLITE_DEFAULT_CACHE_SIZE)
+# define SQLITE_WIN32_CACHE_SIZE (SQLITE_DEFAULT_CACHE_SIZE)
# else
-# define SQLITE_WIN32_CACHE_SIZE (-(SQLITE_DEFAULT_CACHE_SIZE))
+# define SQLITE_WIN32_CACHE_SIZE (-(SQLITE_DEFAULT_CACHE_SIZE))
# endif
#endif
/*
+ * Make sure that the calculated cache size, in pages, cannot cause the
+ * initial size of the Win32-specific heap to exceed the maximum amount
+ * of memory that can be specified in the call to HeapCreate.
+ */
+#if SQLITE_WIN32_CACHE_SIZE>SQLITE_WIN32_MAX_CACHE_SIZE
+# undef SQLITE_WIN32_CACHE_SIZE
+# define SQLITE_WIN32_CACHE_SIZE (2000)
+#endif
+
+/*
* The initial size of the Win32-specific heap. This value may be zero.
*/
#ifndef SQLITE_WIN32_HEAP_INIT_SIZE
-# define SQLITE_WIN32_HEAP_INIT_SIZE ((SQLITE_WIN32_CACHE_SIZE) * \
- (SQLITE_DEFAULT_PAGE_SIZE) + 4194304)
+# define SQLITE_WIN32_HEAP_INIT_SIZE ((SQLITE_WIN32_CACHE_SIZE) * \
+ (SQLITE_DEFAULT_PAGE_SIZE) + \
+ (SQLITE_WIN32_HEAP_INIT_EXTRA))
#endif
/*
* The maximum size of the Win32-specific heap. This value may be zero.
*/
#ifndef SQLITE_WIN32_HEAP_MAX_SIZE
-# define SQLITE_WIN32_HEAP_MAX_SIZE (0)
+# define SQLITE_WIN32_HEAP_MAX_SIZE (0)
#endif
/*
@@ -41346,13 +46104,13 @@ struct winVfsAppData {
* zero for the default behavior.
*/
#ifndef SQLITE_WIN32_HEAP_FLAGS
-# define SQLITE_WIN32_HEAP_FLAGS (0)
+# define SQLITE_WIN32_HEAP_FLAGS (0)
#endif
/*
** The winMemData structure stores information required by the Win32-specific
-** sqlite3_mem_methods implementation.
+** tdsqlite3_mem_methods implementation.
*/
typedef struct winMemData winMemData;
struct winMemData {
@@ -41401,7 +46159,7 @@ static int winMemRoundup(int n);
static int winMemInit(void *pAppData);
static void winMemShutdown(void *pAppData);
-SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetWin32(void);
+SQLITE_PRIVATE const tdsqlite3_mem_methods *tdsqlite3MemGetWin32(void);
#endif /* SQLITE_WIN32_MALLOC */
/*
@@ -41417,13 +46175,13 @@ SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetWin32(void);
** can manually set this value to 1 to emulate Win98 behavior.
*/
#ifdef SQLITE_TEST
-SQLITE_API LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0;
+SQLITE_API LONG SQLITE_WIN32_VOLATILE tdsqlite3_os_type = 0;
#else
-static LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0;
+static LONG SQLITE_WIN32_VOLATILE tdsqlite3_os_type = 0;
#endif
#ifndef SYSCALL
-# define SYSCALL sqlite3_syscall_ptr
+# define SYSCALL tdsqlite3_syscall_ptr
#endif
/*
@@ -41442,8 +46200,8 @@ static LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0;
*/
static struct win_syscall {
const char *zName; /* Name of the system call */
- sqlite3_syscall_ptr pCurrent; /* Current value of the system call */
- sqlite3_syscall_ptr pDefault; /* Default value */
+ tdsqlite3_syscall_ptr pCurrent; /* Current value of the system call */
+ tdsqlite3_syscall_ptr pDefault; /* Default value */
} aSyscall[] = {
#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT
{ "AreFileApisANSI", (SYSCALL)AreFileApisANSI, 0 },
@@ -42096,15 +46854,15 @@ static struct win_syscall {
}; /* End of the overrideable system calls */
/*
-** This is the xSetSystemCall() method of sqlite3_vfs for all of the
+** This is the xSetSystemCall() method of tdsqlite3_vfs for all of the
** "win32" VFSes. Return SQLITE_OK opon successfully updating the
** system call pointer, or SQLITE_NOTFOUND if there is no configurable
** system call named zName.
*/
static int winSetSystemCall(
- sqlite3_vfs *pNotUsed, /* The VFS pointer. Not used */
+ tdsqlite3_vfs *pNotUsed, /* The VFS pointer. Not used */
const char *zName, /* Name of system call to override */
- sqlite3_syscall_ptr pNewFunc /* Pointer to new system call value */
+ tdsqlite3_syscall_ptr pNewFunc /* Pointer to new system call value */
){
unsigned int i;
int rc = SQLITE_NOTFOUND;
@@ -42144,8 +46902,8 @@ static int winSetSystemCall(
** recognized system call name. NULL is also returned if the system call
** is currently undefined.
*/
-static sqlite3_syscall_ptr winGetSystemCall(
- sqlite3_vfs *pNotUsed,
+static tdsqlite3_syscall_ptr winGetSystemCall(
+ tdsqlite3_vfs *pNotUsed,
const char *zName
){
unsigned int i;
@@ -42163,7 +46921,7 @@ static sqlite3_syscall_ptr winGetSystemCall(
** is the last system call or if zName is not the name of a valid
** system call.
*/
-static const char *winNextSystemCall(sqlite3_vfs *p, const char *zName){
+static const char *winNextSystemCall(tdsqlite3_vfs *p, const char *zName){
int i = -1;
UNUSED_PARAMETER(p);
@@ -42186,7 +46944,7 @@ static const char *winNextSystemCall(sqlite3_vfs *p, const char *zName){
** "pnLargest" argument, if non-zero, will be used to return the size of the
** largest committed free block in the heap, in bytes.
*/
-SQLITE_API int sqlite3_win32_compact_heap(LPUINT pnLargest){
+SQLITE_API int tdsqlite3_win32_compact_heap(LPUINT pnLargest){
int rc = SQLITE_OK;
UINT nLargest = 0;
HANDLE hHeap;
@@ -42202,17 +46960,17 @@ SQLITE_API int sqlite3_win32_compact_heap(LPUINT pnLargest){
if( (nLargest=osHeapCompact(hHeap, SQLITE_WIN32_HEAP_FLAGS))==0 ){
DWORD lastErrno = osGetLastError();
if( lastErrno==NO_ERROR ){
- sqlite3_log(SQLITE_NOMEM, "failed to HeapCompact (no space), heap=%p",
+ tdsqlite3_log(SQLITE_NOMEM, "failed to HeapCompact (no space), heap=%p",
(void*)hHeap);
rc = SQLITE_NOMEM_BKPT;
}else{
- sqlite3_log(SQLITE_ERROR, "failed to HeapCompact (%lu), heap=%p",
+ tdsqlite3_log(SQLITE_ERROR, "failed to HeapCompact (%lu), heap=%p",
osGetLastError(), (void*)hHeap);
rc = SQLITE_ERROR;
}
}
#else
- sqlite3_log(SQLITE_NOTFOUND, "failed to HeapCompact, heap=%p",
+ tdsqlite3_log(SQLITE_NOTFOUND, "failed to HeapCompact, heap=%p",
(void*)hHeap);
rc = SQLITE_NOTFOUND;
#endif
@@ -42223,19 +46981,19 @@ SQLITE_API int sqlite3_win32_compact_heap(LPUINT pnLargest){
/*
** If a Win32 native heap has been configured, this function will attempt to
** destroy and recreate it. If the Win32 native heap is not isolated and/or
-** the sqlite3_memory_used() function does not return zero, SQLITE_BUSY will
+** the tdsqlite3_memory_used() function does not return zero, SQLITE_BUSY will
** be returned and no changes will be made to the Win32 native heap.
*/
-SQLITE_API int sqlite3_win32_reset_heap(){
+SQLITE_API int tdsqlite3_win32_reset_heap(){
int rc;
- MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */
- MUTEX_LOGIC( sqlite3_mutex *pMem; ) /* The memsys static mutex */
- MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
- MUTEX_LOGIC( pMem = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); )
- sqlite3_mutex_enter(pMaster);
- sqlite3_mutex_enter(pMem);
+ MUTEX_LOGIC( tdsqlite3_mutex *pMaster; ) /* The main static mutex */
+ MUTEX_LOGIC( tdsqlite3_mutex *pMem; ) /* The memsys static mutex */
+ MUTEX_LOGIC( pMaster = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
+ MUTEX_LOGIC( pMem = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); )
+ tdsqlite3_mutex_enter(pMaster);
+ tdsqlite3_mutex_enter(pMem);
winMemAssertMagic();
- if( winMemGetHeap()!=NULL && winMemGetOwned() && sqlite3_memory_used()==0 ){
+ if( winMemGetHeap()!=NULL && winMemGetOwned() && tdsqlite3_memory_used()==0 ){
/*
** At this point, there should be no outstanding memory allocations on
** the heap. Also, since both the master and memsys locks are currently
@@ -42245,23 +47003,23 @@ SQLITE_API int sqlite3_win32_reset_heap(){
*/
assert( winMemGetHeap()!=NULL );
assert( winMemGetOwned() );
- assert( sqlite3_memory_used()==0 );
+ assert( tdsqlite3_memory_used()==0 );
winMemShutdown(winMemGetDataPtr());
assert( winMemGetHeap()==NULL );
assert( !winMemGetOwned() );
- assert( sqlite3_memory_used()==0 );
+ assert( tdsqlite3_memory_used()==0 );
rc = winMemInit(winMemGetDataPtr());
assert( rc!=SQLITE_OK || winMemGetHeap()!=NULL );
assert( rc!=SQLITE_OK || winMemGetOwned() );
- assert( rc!=SQLITE_OK || sqlite3_memory_used()==0 );
+ assert( rc!=SQLITE_OK || tdsqlite3_memory_used()==0 );
}else{
/*
** The Win32 native heap cannot be modified because it may be in use.
*/
rc = SQLITE_BUSY;
}
- sqlite3_mutex_leave(pMem);
- sqlite3_mutex_leave(pMaster);
+ tdsqlite3_mutex_leave(pMem);
+ tdsqlite3_mutex_leave(pMaster);
return rc;
}
#endif /* SQLITE_WIN32_MALLOC */
@@ -42271,7 +47029,7 @@ SQLITE_API int sqlite3_win32_reset_heap(){
** (if available).
*/
-SQLITE_API void sqlite3_win32_write_debug(const char *zBuf, int nBuf){
+SQLITE_API void tdsqlite3_win32_write_debug(const char *zBuf, int nBuf){
char zDbgBuf[SQLITE_WIN32_DBG_BUF_SIZE];
int nMin = MIN(nBuf, (SQLITE_WIN32_DBG_BUF_SIZE - 1)); /* may be negative. */
if( nMin<-1 ) nMin = -1; /* all negative values become -1. */
@@ -42317,7 +47075,7 @@ SQLITE_API void sqlite3_win32_write_debug(const char *zBuf, int nBuf){
static HANDLE sleepObj = NULL;
#endif
-SQLITE_API void sqlite3_win32_sleep(DWORD milliseconds){
+SQLITE_API void tdsqlite3_win32_sleep(DWORD milliseconds){
#if SQLITE_OS_WINRT
if ( sleepObj==NULL ){
sleepObj = osCreateEventExW(NULL, NULL, CREATE_EVENT_MANUAL_RESET,
@@ -42332,7 +47090,7 @@ SQLITE_API void sqlite3_win32_sleep(DWORD milliseconds){
#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \
SQLITE_THREADSAFE>0
-SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){
+SQLITE_PRIVATE DWORD tdsqlite3Win32Wait(HANDLE hObject){
DWORD rc;
while( (rc = osWaitForSingleObjectEx(hObject, INFINITE,
TRUE))==WAIT_IO_COMPLETION ){}
@@ -42359,14 +47117,14 @@ SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){
#elif !defined(SQLITE_WIN32_HAS_WIDE)
# define osIsNT() (0)
#else
-# define osIsNT() ((sqlite3_os_type==2) || sqlite3_win32_is_nt())
+# define osIsNT() ((tdsqlite3_os_type==2) || tdsqlite3_win32_is_nt())
#endif
/*
** This function determines if the machine is running a version of Windows
** based on the NT kernel.
*/
-SQLITE_API int sqlite3_win32_is_nt(void){
+SQLITE_API int tdsqlite3_win32_is_nt(void){
#if SQLITE_OS_WINRT
/*
** NOTE: The WinRT sub-platform is always assumed to be based on the NT
@@ -42374,24 +47132,24 @@ SQLITE_API int sqlite3_win32_is_nt(void){
*/
return 1;
#elif SQLITE_WIN32_GETVERSIONEX
- if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){
+ if( osInterlockedCompareExchange(&tdsqlite3_os_type, 0, 0)==0 ){
#if defined(SQLITE_WIN32_HAS_ANSI)
OSVERSIONINFOA sInfo;
sInfo.dwOSVersionInfoSize = sizeof(sInfo);
osGetVersionExA(&sInfo);
- osInterlockedCompareExchange(&sqlite3_os_type,
+ osInterlockedCompareExchange(&tdsqlite3_os_type,
(sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0);
#elif defined(SQLITE_WIN32_HAS_WIDE)
OSVERSIONINFOW sInfo;
sInfo.dwOSVersionInfoSize = sizeof(sInfo);
osGetVersionExW(&sInfo);
- osInterlockedCompareExchange(&sqlite3_os_type,
+ osInterlockedCompareExchange(&tdsqlite3_os_type,
(sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0);
#endif
}
- return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2;
+ return osInterlockedCompareExchange(&tdsqlite3_os_type, 2, 2)==2;
#elif SQLITE_TEST
- return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2;
+ return osInterlockedCompareExchange(&tdsqlite3_os_type, 2, 2)==2;
#else
/*
** NOTE: All sub-platforms where the GetVersionEx[AW] functions are
@@ -42419,7 +47177,7 @@ static void *winMemMalloc(int nBytes){
assert( nBytes>=0 );
p = osHeapAlloc(hHeap, SQLITE_WIN32_HEAP_FLAGS, (SIZE_T)nBytes);
if( !p ){
- sqlite3_log(SQLITE_NOMEM, "failed to HeapAlloc %u bytes (%lu), heap=%p",
+ tdsqlite3_log(SQLITE_NOMEM, "failed to HeapAlloc %u bytes (%lu), heap=%p",
nBytes, osGetLastError(), (void*)hHeap);
}
return p;
@@ -42440,7 +47198,7 @@ static void winMemFree(void *pPrior){
#endif
if( !pPrior ) return; /* Passing NULL to HeapFree is undefined. */
if( !osHeapFree(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ){
- sqlite3_log(SQLITE_NOMEM, "failed to HeapFree block %p (%lu), heap=%p",
+ tdsqlite3_log(SQLITE_NOMEM, "failed to HeapFree block %p (%lu), heap=%p",
pPrior, osGetLastError(), (void*)hHeap);
}
}
@@ -42466,7 +47224,7 @@ static void *winMemRealloc(void *pPrior, int nBytes){
p = osHeapReAlloc(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior, (SIZE_T)nBytes);
}
if( !p ){
- sqlite3_log(SQLITE_NOMEM, "failed to %s %u bytes (%lu), heap=%p",
+ tdsqlite3_log(SQLITE_NOMEM, "failed to %s %u bytes (%lu), heap=%p",
pPrior ? "HeapReAlloc" : "HeapAlloc", nBytes, osGetLastError(),
(void*)hHeap);
}
@@ -42490,7 +47248,7 @@ static int winMemSize(void *p){
if( !p ) return 0;
n = osHeapSize(hHeap, SQLITE_WIN32_HEAP_FLAGS, p);
if( n==(SIZE_T)-1 ){
- sqlite3_log(SQLITE_NOMEM, "failed to HeapSize block %p (%lu), heap=%p",
+ tdsqlite3_log(SQLITE_NOMEM, "failed to HeapSize block %p (%lu), heap=%p",
p, osGetLastError(), (void*)hHeap);
return 0;
}
@@ -42517,7 +47275,7 @@ static int winMemInit(void *pAppData){
#if !SQLITE_OS_WINRT && SQLITE_WIN32_HEAP_CREATE
if( !pWinMemData->hHeap ){
DWORD dwInitialSize = SQLITE_WIN32_HEAP_INIT_SIZE;
- DWORD dwMaximumSize = (DWORD)sqlite3GlobalConfig.nHeap;
+ DWORD dwMaximumSize = (DWORD)tdsqlite3GlobalConfig.nHeap;
if( dwMaximumSize==0 ){
dwMaximumSize = SQLITE_WIN32_HEAP_MAX_SIZE;
}else if( dwInitialSize>dwMaximumSize ){
@@ -42526,7 +47284,7 @@ static int winMemInit(void *pAppData){
pWinMemData->hHeap = osHeapCreate(SQLITE_WIN32_HEAP_FLAGS,
dwInitialSize, dwMaximumSize);
if( !pWinMemData->hHeap ){
- sqlite3_log(SQLITE_NOMEM,
+ tdsqlite3_log(SQLITE_NOMEM,
"failed to HeapCreate (%lu), flags=%u, initSize=%lu, maxSize=%lu",
osGetLastError(), SQLITE_WIN32_HEAP_FLAGS, dwInitialSize,
dwMaximumSize);
@@ -42538,7 +47296,7 @@ static int winMemInit(void *pAppData){
#else
pWinMemData->hHeap = osGetProcessHeap();
if( !pWinMemData->hHeap ){
- sqlite3_log(SQLITE_NOMEM,
+ tdsqlite3_log(SQLITE_NOMEM,
"failed to GetProcessHeap (%lu)", osGetLastError());
return SQLITE_NOMEM_BKPT;
}
@@ -42570,7 +47328,7 @@ static void winMemShutdown(void *pAppData){
#endif
if( pWinMemData->bOwned ){
if( !osHeapDestroy(pWinMemData->hHeap) ){
- sqlite3_log(SQLITE_NOMEM, "failed to HeapDestroy (%lu), heap=%p",
+ tdsqlite3_log(SQLITE_NOMEM, "failed to HeapDestroy (%lu), heap=%p",
osGetLastError(), (void*)pWinMemData->hHeap);
}
pWinMemData->bOwned = FALSE;
@@ -42581,14 +47339,14 @@ static void winMemShutdown(void *pAppData){
/*
** Populate the low-level memory allocation function pointers in
-** sqlite3GlobalConfig.m with pointers to the routines in this file. The
+** tdsqlite3GlobalConfig.m with pointers to the routines in this file. The
** arguments specify the block of memory to manage.
**
-** This routine is only called by sqlite3_config(), and therefore
+** This routine is only called by tdsqlite3_config(), and therefore
** is not required to be threadsafe (it is not).
*/
-SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetWin32(void){
- static const sqlite3_mem_methods winMemMethods = {
+SQLITE_PRIVATE const tdsqlite3_mem_methods *tdsqlite3MemGetWin32(void){
+ static const tdsqlite3_mem_methods winMemMethods = {
winMemMalloc,
winMemFree,
winMemRealloc,
@@ -42601,15 +47359,15 @@ SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetWin32(void){
return &winMemMethods;
}
-SQLITE_PRIVATE void sqlite3MemSetDefault(void){
- sqlite3_config(SQLITE_CONFIG_MALLOC, sqlite3MemGetWin32());
+SQLITE_PRIVATE void tdsqlite3MemSetDefault(void){
+ tdsqlite3_config(SQLITE_CONFIG_MALLOC, tdsqlite3MemGetWin32());
}
#endif /* SQLITE_WIN32_MALLOC */
/*
** Convert a UTF-8 string to Microsoft Unicode.
**
-** Space to hold the returned string is obtained from sqlite3_malloc().
+** Space to hold the returned string is obtained from tdsqlite3_malloc().
*/
static LPWSTR winUtf8ToUnicode(const char *zText){
int nChar;
@@ -42619,14 +47377,14 @@ static LPWSTR winUtf8ToUnicode(const char *zText){
if( nChar==0 ){
return 0;
}
- zWideText = sqlite3MallocZero( nChar*sizeof(WCHAR) );
+ zWideText = tdsqlite3MallocZero( nChar*sizeof(WCHAR) );
if( zWideText==0 ){
return 0;
}
nChar = osMultiByteToWideChar(CP_UTF8, 0, zText, -1, zWideText,
nChar);
if( nChar==0 ){
- sqlite3_free(zWideText);
+ tdsqlite3_free(zWideText);
zWideText = 0;
}
return zWideText;
@@ -42635,7 +47393,7 @@ static LPWSTR winUtf8ToUnicode(const char *zText){
/*
** Convert a Microsoft Unicode string to UTF-8.
**
-** Space to hold the returned string is obtained from sqlite3_malloc().
+** Space to hold the returned string is obtained from tdsqlite3_malloc().
*/
static char *winUnicodeToUtf8(LPCWSTR zWideText){
int nByte;
@@ -42645,14 +47403,14 @@ static char *winUnicodeToUtf8(LPCWSTR zWideText){
if( nByte == 0 ){
return 0;
}
- zText = sqlite3MallocZero( nByte );
+ zText = tdsqlite3MallocZero( nByte );
if( zText==0 ){
return 0;
}
nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideText, -1, zText, nByte,
0, 0);
if( nByte == 0 ){
- sqlite3_free(zText);
+ tdsqlite3_free(zText);
zText = 0;
}
return zText;
@@ -42662,7 +47420,7 @@ static char *winUnicodeToUtf8(LPCWSTR zWideText){
** Convert an ANSI string to Microsoft Unicode, using the ANSI or OEM
** code page.
**
-** Space to hold the returned string is obtained from sqlite3_malloc().
+** Space to hold the returned string is obtained from tdsqlite3_malloc().
*/
static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
int nByte;
@@ -42674,14 +47432,14 @@ static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
if( nByte==0 ){
return 0;
}
- zMbcsText = sqlite3MallocZero( nByte*sizeof(WCHAR) );
+ zMbcsText = tdsqlite3MallocZero( nByte*sizeof(WCHAR) );
if( zMbcsText==0 ){
return 0;
}
nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText,
nByte);
if( nByte==0 ){
- sqlite3_free(zMbcsText);
+ tdsqlite3_free(zMbcsText);
zMbcsText = 0;
}
return zMbcsText;
@@ -42691,7 +47449,7 @@ static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
** Convert a Microsoft Unicode string to a multi-byte character string,
** using the ANSI or OEM code page.
**
-** Space to hold the returned string is obtained from sqlite3_malloc().
+** Space to hold the returned string is obtained from tdsqlite3_malloc().
*/
static char *winUnicodeToMbcs(LPCWSTR zWideText, int useAnsi){
int nByte;
@@ -42702,14 +47460,14 @@ static char *winUnicodeToMbcs(LPCWSTR zWideText, int useAnsi){
if( nByte == 0 ){
return 0;
}
- zText = sqlite3MallocZero( nByte );
+ zText = tdsqlite3MallocZero( nByte );
if( zText==0 ){
return 0;
}
nByte = osWideCharToMultiByte(codepage, 0, zWideText, -1, zText,
nByte, 0, 0);
if( nByte == 0 ){
- sqlite3_free(zText);
+ tdsqlite3_free(zText);
zText = 0;
}
return zText;
@@ -42718,7 +47476,7 @@ static char *winUnicodeToMbcs(LPCWSTR zWideText, int useAnsi){
/*
** Convert a multi-byte character string to UTF-8.
**
-** Space to hold the returned string is obtained from sqlite3_malloc().
+** Space to hold the returned string is obtained from tdsqlite3_malloc().
*/
static char *winMbcsToUtf8(const char *zText, int useAnsi){
char *zTextUtf8;
@@ -42729,14 +47487,14 @@ static char *winMbcsToUtf8(const char *zText, int useAnsi){
return 0;
}
zTextUtf8 = winUnicodeToUtf8(zTmpWide);
- sqlite3_free(zTmpWide);
+ tdsqlite3_free(zTmpWide);
return zTextUtf8;
}
/*
** Convert a UTF-8 string to a multi-byte character string.
**
-** Space to hold the returned string is obtained from sqlite3_malloc().
+** Space to hold the returned string is obtained from tdsqlite3_malloc().
*/
static char *winUtf8ToMbcs(const char *zText, int useAnsi){
char *zTextMbcs;
@@ -42747,14 +47505,14 @@ static char *winUtf8ToMbcs(const char *zText, int useAnsi){
return 0;
}
zTextMbcs = winUnicodeToMbcs(zTmpWide, useAnsi);
- sqlite3_free(zTmpWide);
+ tdsqlite3_free(zTmpWide);
return zTextMbcs;
}
/*
** This is a public wrapper for the winUtf8ToUnicode() function.
*/
-SQLITE_API LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText){
+SQLITE_API LPWSTR tdsqlite3_win32_utf8_to_unicode(const char *zText){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
@@ -42762,7 +47520,7 @@ SQLITE_API LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText){
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
return winUtf8ToUnicode(zText);
}
@@ -42770,7 +47528,7 @@ SQLITE_API LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText){
/*
** This is a public wrapper for the winUnicodeToUtf8() function.
*/
-SQLITE_API char *sqlite3_win32_unicode_to_utf8(LPCWSTR zWideText){
+SQLITE_API char *tdsqlite3_win32_unicode_to_utf8(LPCWSTR zWideText){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zWideText ){
(void)SQLITE_MISUSE_BKPT;
@@ -42778,7 +47536,7 @@ SQLITE_API char *sqlite3_win32_unicode_to_utf8(LPCWSTR zWideText){
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
return winUnicodeToUtf8(zWideText);
}
@@ -42786,7 +47544,7 @@ SQLITE_API char *sqlite3_win32_unicode_to_utf8(LPCWSTR zWideText){
/*
** This is a public wrapper for the winMbcsToUtf8() function.
*/
-SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){
+SQLITE_API char *tdsqlite3_win32_mbcs_to_utf8(const char *zText){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
@@ -42794,7 +47552,7 @@ SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
return winMbcsToUtf8(zText, osAreFileApisANSI());
}
@@ -42802,7 +47560,7 @@ SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){
/*
** This is a public wrapper for the winMbcsToUtf8() function.
*/
-SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
+SQLITE_API char *tdsqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
@@ -42810,7 +47568,7 @@ SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
return winMbcsToUtf8(zText, useAnsi);
}
@@ -42818,7 +47576,7 @@ SQLITE_API char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
/*
** This is a public wrapper for the winUtf8ToMbcs() function.
*/
-SQLITE_API char *sqlite3_win32_utf8_to_mbcs(const char *zText){
+SQLITE_API char *tdsqlite3_win32_utf8_to_mbcs(const char *zText){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
@@ -42826,7 +47584,7 @@ SQLITE_API char *sqlite3_win32_utf8_to_mbcs(const char *zText){
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
return winUtf8ToMbcs(zText, osAreFileApisANSI());
}
@@ -42834,7 +47592,7 @@ SQLITE_API char *sqlite3_win32_utf8_to_mbcs(const char *zText){
/*
** This is a public wrapper for the winUtf8ToMbcs() function.
*/
-SQLITE_API char *sqlite3_win32_utf8_to_mbcs_v2(const char *zText, int useAnsi){
+SQLITE_API char *tdsqlite3_win32_utf8_to_mbcs_v2(const char *zText, int useAnsi){
#ifdef SQLITE_ENABLE_API_ARMOR
if( !zText ){
(void)SQLITE_MISUSE_BKPT;
@@ -42842,49 +47600,82 @@ SQLITE_API char *sqlite3_win32_utf8_to_mbcs_v2(const char *zText, int useAnsi){
}
#endif
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize() ) return 0;
+ if( tdsqlite3_initialize() ) return 0;
#endif
return winUtf8ToMbcs(zText, useAnsi);
}
/*
-** This function sets the data directory or the temporary directory based on
-** the provided arguments. The type argument must be 1 in order to set the
-** data directory or 2 in order to set the temporary directory. The zValue
-** argument is the name of the directory to use. The return value will be
-** SQLITE_OK if successful.
+** This function is the same as tdsqlite3_win32_set_directory (below); however,
+** it accepts a UTF-8 string.
*/
-SQLITE_API int sqlite3_win32_set_directory(DWORD type, LPCWSTR zValue){
+SQLITE_API int tdsqlite3_win32_set_directory8(
+ unsigned long type, /* Identifier for directory being set or reset */
+ const char *zValue /* New value for directory being set or reset */
+){
char **ppDirectory = 0;
#ifndef SQLITE_OMIT_AUTOINIT
- int rc = sqlite3_initialize();
+ int rc = tdsqlite3_initialize();
if( rc ) return rc;
#endif
if( type==SQLITE_WIN32_DATA_DIRECTORY_TYPE ){
- ppDirectory = &sqlite3_data_directory;
+ ppDirectory = &tdsqlite3_data_directory;
}else if( type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE ){
- ppDirectory = &sqlite3_temp_directory;
+ ppDirectory = &tdsqlite3_temp_directory;
}
assert( !ppDirectory || type==SQLITE_WIN32_DATA_DIRECTORY_TYPE
|| type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE
);
- assert( !ppDirectory || sqlite3MemdebugHasType(*ppDirectory, MEMTYPE_HEAP) );
+ assert( !ppDirectory || tdsqlite3MemdebugHasType(*ppDirectory, MEMTYPE_HEAP) );
if( ppDirectory ){
- char *zValueUtf8 = 0;
+ char *zCopy = 0;
if( zValue && zValue[0] ){
- zValueUtf8 = winUnicodeToUtf8(zValue);
- if ( zValueUtf8==0 ){
+ zCopy = tdsqlite3_mprintf("%s", zValue);
+ if ( zCopy==0 ){
return SQLITE_NOMEM_BKPT;
}
}
- sqlite3_free(*ppDirectory);
- *ppDirectory = zValueUtf8;
+ tdsqlite3_free(*ppDirectory);
+ *ppDirectory = zCopy;
return SQLITE_OK;
}
return SQLITE_ERROR;
}
/*
+** This function is the same as tdsqlite3_win32_set_directory (below); however,
+** it accepts a UTF-16 string.
+*/
+SQLITE_API int tdsqlite3_win32_set_directory16(
+ unsigned long type, /* Identifier for directory being set or reset */
+ const void *zValue /* New value for directory being set or reset */
+){
+ int rc;
+ char *zUtf8 = 0;
+ if( zValue ){
+ zUtf8 = tdsqlite3_win32_unicode_to_utf8(zValue);
+ if( zUtf8==0 ) return SQLITE_NOMEM_BKPT;
+ }
+ rc = tdsqlite3_win32_set_directory8(type, zUtf8);
+ if( zUtf8 ) tdsqlite3_free(zUtf8);
+ return rc;
+}
+
+/*
+** This function sets the data directory or the temporary directory based on
+** the provided arguments. The type argument must be 1 in order to set the
+** data directory or 2 in order to set the temporary directory. The zValue
+** argument is the name of the directory to use. The return value will be
+** SQLITE_OK if successful.
+*/
+SQLITE_API int tdsqlite3_win32_set_directory(
+ unsigned long type, /* Identifier for directory being set or reset */
+ void *zValue /* New value for directory being set or reset */
+){
+ return tdsqlite3_win32_set_directory16(type, zValue);
+}
+
+/*
** The return value of winGetLastErrorMsg
** is zero if the error message fits in the buffer, or non-zero
** otherwise (if the message was truncated).
@@ -42922,9 +47713,9 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){
#endif
if( dwLen > 0 ){
/* allocate a buffer and convert to UTF8 */
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
zOut = winUnicodeToUtf8(zTempWide);
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
#if !SQLITE_OS_WINRT
/* free the system buffer allocated by FormatMessage */
osLocalFree(zTempWide);
@@ -42945,21 +47736,21 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){
0);
if( dwLen > 0 ){
/* allocate a buffer and convert to UTF8 */
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI());
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
/* free the system buffer allocated by FormatMessage */
osLocalFree(zTemp);
}
}
#endif
if( 0 == dwLen ){
- sqlite3_snprintf(nBuf, zBuf, "OsError 0x%lx (%lu)", lastErrno, lastErrno);
+ tdsqlite3_snprintf(nBuf, zBuf, "OsError 0x%lx (%lu)", lastErrno, lastErrno);
}else{
/* copy a maximum of nBuf chars to output buffer */
- sqlite3_snprintf(nBuf, zBuf, "%s", zOut);
+ tdsqlite3_snprintf(nBuf, zBuf, "%s", zOut);
/* free the UTF8 buffer */
- sqlite3_free(zOut);
+ tdsqlite3_free(zOut);
}
return 0;
}
@@ -42970,7 +47761,7 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){
** winLogError().
**
** This routine is invoked after an error occurs in an OS function.
-** It logs a message using sqlite3_log() containing the current value of
+** It logs a message using tdsqlite3_log() containing the current value of
** error code and, if possible, the human-readable equivalent from
** FormatMessage.
**
@@ -42996,7 +47787,7 @@ static int winLogErrorAtLine(
if( zPath==0 ) zPath = "";
for(i=0; zMsg[i] && zMsg[i]!='\r' && zMsg[i]!='\n'; i++){}
zMsg[i] = 0;
- sqlite3_log(errcode,
+ tdsqlite3_log(errcode,
"os_win.c:%d: (%lu) %s(%s) - %s",
iLine, lastErrno, zFunc, zPath, zMsg
);
@@ -43059,13 +47850,13 @@ static int winRetryIoerr(int *pnRetry, DWORD *pError){
return 0;
}
if( winIoerrCanRetry1(e) ){
- sqlite3_win32_sleep(winIoerrRetryDelay*(1+*pnRetry));
+ tdsqlite3_win32_sleep(winIoerrRetryDelay*(1+*pnRetry));
++*pnRetry;
return 1;
}
#if defined(winIoerrCanRetry2)
else if( winIoerrCanRetry2(e) ){
- sqlite3_win32_sleep(winIoerrRetryDelay*(1+*pnRetry));
+ tdsqlite3_win32_sleep(winIoerrRetryDelay*(1+*pnRetry));
++*pnRetry;
return 1;
}
@@ -43081,7 +47872,7 @@ static int winRetryIoerr(int *pnRetry, DWORD *pError){
*/
static void winLogIoerr(int nRetry, int lineno){
if( nRetry ){
- sqlite3_log(SQLITE_NOTICE,
+ tdsqlite3_log(SQLITE_NOTICE,
"delayed %dms for lock/sharing conflict at line %d",
winIoerrRetryDelay*nRetry*(nRetry+1)/2, lineno
);
@@ -43104,7 +47895,7 @@ struct tm *__cdecl localtime(const time_t *t)
static struct tm y;
FILETIME uTm, lTm;
SYSTEMTIME pTm;
- sqlite3_int64 t64;
+ tdsqlite3_int64 t64;
t64 = *t;
t64 = (t64 + 11644473600)*10000000;
uTm.dwLowDateTime = (DWORD)(t64 & 0xFFFFFFFF);
@@ -43173,7 +47964,7 @@ static int winceCreateLock(const char *zFilename, winFile *pFile){
pFile->hMutex = osCreateMutexW(NULL, FALSE, zName);
if (!pFile->hMutex){
pFile->lastErrno = osGetLastError();
- sqlite3_free(zName);
+ tdsqlite3_free(zName);
return winLogError(SQLITE_IOERR, pFile->lastErrno,
"winceCreateLock1", zFilename);
}
@@ -43197,7 +47988,7 @@ static int winceCreateLock(const char *zFilename, winFile *pFile){
bInit = FALSE;
}
- sqlite3_free(zName);
+ tdsqlite3_free(zName);
/* If we succeeded in making the shared memory handle, map it. */
if( pFile->hShared ){
@@ -43471,7 +48262,7 @@ static BOOL winUnlockFile(
/*****************************************************************************
** The next group of routines implement the I/O methods specified
-** by the sqlite3_io_methods object.
+** by the tdsqlite3_io_methods object.
******************************************************************************/
/*
@@ -43486,7 +48277,7 @@ static BOOL winUnlockFile(
** argument to offset iOffset within the file. If successful, return 0.
** Otherwise, set pFile->lastErrno and return non-zero.
*/
-static int winSeekFile(winFile *pFile, sqlite3_int64 iOffset){
+static int winSeekFile(winFile *pFile, tdsqlite3_int64 iOffset){
#if !SQLITE_OS_WINRT
LONG upperBits; /* Most sig. 32 bits of new offset */
LONG lowerBits; /* Least sig. 32 bits of new offset */
@@ -43544,7 +48335,7 @@ static int winSeekFile(winFile *pFile, sqlite3_int64 iOffset){
#if SQLITE_MAX_MMAP_SIZE>0
/* Forward references to VFS helper methods used for memory mapped files */
-static int winMapfile(winFile*, sqlite3_int64);
+static int winMapfile(winFile*, tdsqlite3_int64);
static int winUnmapfile(winFile*);
#endif
@@ -43559,7 +48350,7 @@ static int winUnmapfile(winFile*);
** giving up and returning an error.
*/
#define MX_CLOSE_ATTEMPT 3
-static int winClose(sqlite3_file *id){
+static int winClose(tdsqlite3_file *id){
int rc, cnt = 0;
winFile *pFile = (winFile*)id;
@@ -43578,7 +48369,7 @@ static int winClose(sqlite3_file *id){
do{
rc = osCloseHandle(pFile->h);
/* SimulateIOError( rc=0; cnt=MX_CLOSE_ATTEMPT; ); */
- }while( rc==0 && ++cnt < MX_CLOSE_ATTEMPT && (sqlite3_win32_sleep(100), 1) );
+ }while( rc==0 && ++cnt < MX_CLOSE_ATTEMPT && (tdsqlite3_win32_sleep(100), 1) );
#if SQLITE_OS_WINCE
#define WINCE_DELETION_ATTEMPTS 3
{
@@ -43594,9 +48385,9 @@ static int winClose(sqlite3_file *id){
&& osGetFileAttributesW(pFile->zDeleteOnClose)!=0xffffffff
&& cnt++ < WINCE_DELETION_ATTEMPTS
){
- sqlite3_win32_sleep(100); /* Wait a little before trying again */
+ tdsqlite3_win32_sleep(100); /* Wait a little before trying again */
}
- sqlite3_free(pFile->zDeleteOnClose);
+ tdsqlite3_free(pFile->zDeleteOnClose);
}
#endif
if( rc ){
@@ -43616,10 +48407,10 @@ static int winClose(sqlite3_file *id){
** wrong.
*/
static int winRead(
- sqlite3_file *id, /* File to read from */
+ tdsqlite3_file *id, /* File to read from */
void *pBuf, /* Write content into this buffer */
int amt, /* Number of bytes to read */
- sqlite3_int64 offset /* Begin reading at this offset */
+ tdsqlite3_int64 offset /* Begin reading at this offset */
){
#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED)
OVERLAPPED overlapped; /* The offset for ReadFile. */
@@ -43696,10 +48487,10 @@ static int winRead(
** or some other error code on failure.
*/
static int winWrite(
- sqlite3_file *id, /* File to write into */
+ tdsqlite3_file *id, /* File to write into */
const void *pBuf, /* The bytes to be written */
int amt, /* Number of bytes to write */
- sqlite3_int64 offset /* Offset into the file to begin writing at */
+ tdsqlite3_int64 offset /* Offset into the file to begin writing at */
){
int rc = 0; /* True if error has occurred, else false */
winFile *pFile = (winFile*)id; /* File handle */
@@ -43804,10 +48595,33 @@ static int winWrite(
/*
** Truncate an open file to a specified size
*/
-static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){
+static int winTruncate(tdsqlite3_file *id, tdsqlite3_int64 nByte){
winFile *pFile = (winFile*)id; /* File handle object */
int rc = SQLITE_OK; /* Return code for this function */
DWORD lastErrno;
+#if SQLITE_MAX_MMAP_SIZE>0
+ tdsqlite3_int64 oldMmapSize;
+ if( pFile->nFetchOut>0 ){
+ /* File truncation is a no-op if there are outstanding memory mapped
+ ** pages. This is because truncating the file means temporarily unmapping
+ ** the file, and that might delete memory out from under existing cursors.
+ **
+ ** This can result in incremental vacuum not truncating the file,
+ ** if there is an active read cursor when the incremental vacuum occurs.
+ ** No real harm comes of this - the database file is not corrupted,
+ ** though some folks might complain that the file is bigger than it
+ ** needs to be.
+ **
+ ** The only feasible work-around is to defer the truncation until after
+ ** all references to memory-mapped content are closed. That is doable,
+ ** but involves adding a few branches in the common write code path which
+ ** could slow down normal operations slightly. Hence, we have decided for
+ ** now to simply make trancations a no-op if there are pending reads. We
+ ** can maybe revisit this decision in the future.
+ */
+ return SQLITE_OK;
+ }
+#endif
assert( pFile );
SimulateIOError(return SQLITE_IOERR_TRUNCATE);
@@ -43823,6 +48637,15 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){
nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk;
}
+#if SQLITE_MAX_MMAP_SIZE>0
+ if( pFile->pMapRegion ){
+ oldMmapSize = pFile->mmapSize;
+ }else{
+ oldMmapSize = 0;
+ }
+ winUnmapfile(pFile);
+#endif
+
/* SetEndOfFile() returns non-zero when successful, or zero when it fails. */
if( winSeekFile(pFile, nByte) ){
rc = winLogError(SQLITE_IOERR_TRUNCATE, pFile->lastErrno,
@@ -43835,17 +48658,17 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){
}
#if SQLITE_MAX_MMAP_SIZE>0
- /* If the file was truncated to a size smaller than the currently
- ** mapped region, reduce the effective mapping size as well. SQLite will
- ** use read() and write() to access data beyond this point from now on.
- */
- if( pFile->pMapRegion && nByte<pFile->mmapSize ){
- pFile->mmapSize = nByte;
+ if( rc==SQLITE_OK && oldMmapSize>0 ){
+ if( oldMmapSize>nByte ){
+ winMapfile(pFile, -1);
+ }else{
+ winMapfile(pFile, oldMmapSize);
+ }
}
#endif
OSTRACE(("TRUNCATE pid=%lu, pFile=%p, file=%p, rc=%s\n",
- osGetCurrentProcessId(), pFile, pFile->h, sqlite3ErrName(rc)));
+ osGetCurrentProcessId(), pFile, pFile->h, tdsqlite3ErrName(rc)));
return rc;
}
@@ -43854,14 +48677,14 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){
** Count the number of fullsyncs and normal syncs. This is used to test
** that syncs and fullsyncs are occuring at the right times.
*/
-SQLITE_API int sqlite3_sync_count = 0;
-SQLITE_API int sqlite3_fullsync_count = 0;
+SQLITE_API int tdsqlite3_sync_count = 0;
+SQLITE_API int tdsqlite3_fullsync_count = 0;
#endif
/*
** Make sure all writes to a particular file are committed to disk.
*/
-static int winSync(sqlite3_file *id, int flags){
+static int winSync(tdsqlite3_file *id, int flags){
#ifndef SQLITE_NO_SYNC
/*
** Used only when SQLITE_NO_SYNC is not defined.
@@ -43898,9 +48721,9 @@ static int winSync(sqlite3_file *id, int flags){
UNUSED_PARAMETER(flags);
#else
if( (flags&0x0F)==SQLITE_SYNC_FULL ){
- sqlite3_fullsync_count++;
+ tdsqlite3_fullsync_count++;
}
- sqlite3_sync_count++;
+ tdsqlite3_sync_count++;
#endif
/* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a
@@ -43946,7 +48769,7 @@ static int winSync(sqlite3_file *id, int flags){
/*
** Determine the current size of a file in bytes
*/
-static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){
+static int winFileSize(tdsqlite3_file *id, tdsqlite3_int64 *pSize){
winFile *pFile = (winFile*)id;
int rc = SQLITE_OK;
@@ -43974,7 +48797,7 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){
DWORD lastErrno;
lowerBits = osGetFileSize(pFile->h, &upperBits);
- *pSize = (((sqlite3_int64)upperBits)<<32) + lowerBits;
+ *pSize = (((tdsqlite3_int64)upperBits)<<32) + lowerBits;
if( (lowerBits == INVALID_FILE_SIZE)
&& ((lastErrno = osGetLastError())!=NO_ERROR) ){
pFile->lastErrno = lastErrno;
@@ -43984,7 +48807,7 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){
}
#endif
OSTRACE(("SIZE file=%p, pSize=%p, *pSize=%lld, rc=%s\n",
- pFile->h, pSize, *pSize, sqlite3ErrName(rc)));
+ pFile->h, pSize, *pSize, tdsqlite3ErrName(rc)));
return rc;
}
@@ -44042,7 +48865,7 @@ static int winGetReadLock(winFile *pFile){
#ifdef SQLITE_WIN32_HAS_ANSI
else{
int lk;
- sqlite3_randomness(sizeof(lk), &lk);
+ tdsqlite3_randomness(sizeof(lk), &lk);
pFile->sharedLockByte = (short)((lk & 0x7fffffff)%(SHARED_SIZE - 1));
res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS,
SHARED_FIRST+pFile->sharedLockByte, 0, 1, 0);
@@ -44106,7 +48929,7 @@ static int winUnlockReadLock(winFile *pFile){
** It is not possible to lower the locking level one step at a time. You
** must go straight to locking level 0.
*/
-static int winLock(sqlite3_file *id, int locktype){
+static int winLock(tdsqlite3_file *id, int locktype){
int rc = SQLITE_OK; /* Return code from subroutines */
int res = 1; /* Result of a Windows lock call */
int newLocktype; /* Set pFile->locktype to this value before exiting */
@@ -44120,7 +48943,7 @@ static int winLock(sqlite3_file *id, int locktype){
/* If there is already a lock of this type or more restrictive on the
** OsFile, do nothing. Don't use the end_lock: exit path, as
- ** sqlite3OsEnterMutex() hasn't been called yet.
+ ** tdsqlite3OsEnterMutex() hasn't been called yet.
*/
if( pFile->locktype>=locktype ){
OSTRACE(("LOCK-HELD file=%p, rc=SQLITE_OK\n", pFile->h));
@@ -44163,10 +48986,10 @@ static int winLock(sqlite3_file *id, int locktype){
pFile->lastErrno = lastErrno;
rc = SQLITE_IOERR_LOCK;
OSTRACE(("LOCK-FAIL file=%p, count=%d, rc=%s\n",
- pFile->h, cnt, sqlite3ErrName(rc)));
+ pFile->h, cnt, tdsqlite3ErrName(rc)));
return rc;
}
- if( cnt ) sqlite3_win32_sleep(1);
+ if( cnt ) tdsqlite3_win32_sleep(1);
}
gotPendingLock = res;
if( !res ){
@@ -44240,7 +49063,7 @@ static int winLock(sqlite3_file *id, int locktype){
}
pFile->locktype = (u8)newLocktype;
OSTRACE(("LOCK file=%p, lock=%d, rc=%s\n",
- pFile->h, pFile->locktype, sqlite3ErrName(rc)));
+ pFile->h, pFile->locktype, tdsqlite3ErrName(rc)));
return rc;
}
@@ -44249,7 +49072,7 @@ static int winLock(sqlite3_file *id, int locktype){
** file by this or any other process. If such a lock is held, return
** non-zero, otherwise zero.
*/
-static int winCheckReservedLock(sqlite3_file *id, int *pResOut){
+static int winCheckReservedLock(tdsqlite3_file *id, int *pResOut){
int res;
winFile *pFile = (winFile*)id;
@@ -44285,7 +49108,7 @@ static int winCheckReservedLock(sqlite3_file *id, int *pResOut){
** is NO_LOCK. If the second argument is SHARED_LOCK then this routine
** might return SQLITE_IOERR;
*/
-static int winUnlock(sqlite3_file *id, int locktype){
+static int winUnlock(tdsqlite3_file *id, int locktype){
int type;
winFile *pFile = (winFile*)id;
int rc = SQLITE_OK;
@@ -44314,7 +49137,7 @@ static int winUnlock(sqlite3_file *id, int locktype){
}
pFile->locktype = (u8)locktype;
OSTRACE(("UNLOCK file=%p, lock=%d, rc=%s\n",
- pFile->h, pFile->locktype, sqlite3ErrName(rc)));
+ pFile->h, pFile->locktype, tdsqlite3ErrName(rc)));
return rc;
}
@@ -44335,19 +49158,19 @@ static int winUnlock(sqlite3_file *id, int locktype){
** time and one or more of those connections are writing.
*/
-static int winNolockLock(sqlite3_file *id, int locktype){
+static int winNolockLock(tdsqlite3_file *id, int locktype){
UNUSED_PARAMETER(id);
UNUSED_PARAMETER(locktype);
return SQLITE_OK;
}
-static int winNolockCheckReservedLock(sqlite3_file *id, int *pResOut){
+static int winNolockCheckReservedLock(tdsqlite3_file *id, int *pResOut){
UNUSED_PARAMETER(id);
UNUSED_PARAMETER(pResOut);
return SQLITE_OK;
}
-static int winNolockUnlock(sqlite3_file *id, int locktype){
+static int winNolockUnlock(tdsqlite3_file *id, int locktype){
UNUSED_PARAMETER(id);
UNUSED_PARAMETER(locktype);
return SQLITE_OK;
@@ -44373,14 +49196,14 @@ static void winModeBit(winFile *pFile, unsigned char mask, int *pArg){
}
/* Forward references to VFS helper methods used for temporary files */
-static int winGetTempname(sqlite3_vfs *, char **);
+static int winGetTempname(tdsqlite3_vfs *, char **);
static int winIsDir(const void *);
static BOOL winIsDriveLetterAndColon(const char *);
/*
** Control and query of the open file handle.
*/
-static int winFileControl(sqlite3_file *id, int op, void *pArg){
+static int winFileControl(tdsqlite3_file *id, int op, void *pArg){
winFile *pFile = (winFile*)id;
OSTRACE(("FCNTL file=%p, op=%d, pArg=%p\n", pFile->h, op, pArg));
switch( op ){
@@ -44401,17 +49224,17 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
}
case SQLITE_FCNTL_SIZE_HINT: {
if( pFile->szChunk>0 ){
- sqlite3_int64 oldSz;
+ tdsqlite3_int64 oldSz;
int rc = winFileSize(id, &oldSz);
if( rc==SQLITE_OK ){
- sqlite3_int64 newSz = *(sqlite3_int64*)pArg;
+ tdsqlite3_int64 newSz = *(tdsqlite3_int64*)pArg;
if( newSz>oldSz ){
SimulateIOErrorBenign(1);
rc = winTruncate(id, newSz);
SimulateIOErrorBenign(0);
}
}
- OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, sqlite3ErrName(rc)));
+ OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, tdsqlite3ErrName(rc)));
return rc;
}
OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h));
@@ -44428,7 +49251,7 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
return SQLITE_OK;
}
case SQLITE_FCNTL_VFSNAME: {
- *(char**)pArg = sqlite3_mprintf("%s", pFile->pVfs->zName);
+ *(char**)pArg = tdsqlite3_mprintf("%s", pFile->pVfs->zName);
OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h));
return SQLITE_OK;
}
@@ -44470,16 +49293,24 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
if( rc==SQLITE_OK ){
*(char**)pArg = zTFile;
}
- OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, sqlite3ErrName(rc)));
+ OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, tdsqlite3ErrName(rc)));
return rc;
}
#if SQLITE_MAX_MMAP_SIZE>0
case SQLITE_FCNTL_MMAP_SIZE: {
i64 newLimit = *(i64*)pArg;
int rc = SQLITE_OK;
- if( newLimit>sqlite3GlobalConfig.mxMmap ){
- newLimit = sqlite3GlobalConfig.mxMmap;
+ if( newLimit>tdsqlite3GlobalConfig.mxMmap ){
+ newLimit = tdsqlite3GlobalConfig.mxMmap;
}
+
+ /* The value of newLimit may be eventually cast to (SIZE_T) and passed
+ ** to MapViewOfFile(). Restrict its value to 2GB if (SIZE_T) is not at
+ ** least a 64-bit type. */
+ if( newLimit>0 && sizeof(SIZE_T)<8 ){
+ newLimit = (newLimit & 0x7FFFFFFF);
+ }
+
*(i64*)pArg = pFile->mmapSizeMax;
if( newLimit>=0 && newLimit!=pFile->mmapSizeMax && pFile->nFetchOut==0 ){
pFile->mmapSizeMax = newLimit;
@@ -44488,7 +49319,7 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
rc = winMapfile(pFile, -1);
}
}
- OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, sqlite3ErrName(rc)));
+ OSTRACE(("FCNTL file=%p, rc=%s\n", pFile->h, tdsqlite3ErrName(rc)));
return rc;
}
#endif
@@ -44507,7 +49338,7 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
** a database and its journal file) that the sector size will be the
** same for both.
*/
-static int winSectorSize(sqlite3_file *id){
+static int winSectorSize(tdsqlite3_file *id){
(void)id;
return SQLITE_DEFAULT_SECTOR_SIZE;
}
@@ -44515,7 +49346,7 @@ static int winSectorSize(sqlite3_file *id){
/*
** Return a vector of device characteristics.
*/
-static int winDeviceCharacteristics(sqlite3_file *id){
+static int winDeviceCharacteristics(tdsqlite3_file *id){
winFile *p = (winFile*)id;
return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN |
((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0);
@@ -44524,7 +49355,7 @@ static int winDeviceCharacteristics(sqlite3_file *id){
/*
** Windows will only let you create file view mappings
** on allocation size granularity boundaries.
-** During sqlite3_os_init() we do a GetSystemInfo()
+** During tdsqlite3_os_init() we do a GetSystemInfo()
** to get the granularity size.
*/
static SYSTEM_INFO winSysInfo;
@@ -44544,15 +49375,16 @@ static SYSTEM_INFO winSysInfo;
** assert( winShmMutexHeld() );
** winShmLeaveMutex()
*/
+static tdsqlite3_mutex *winBigLock = 0;
static void winShmEnterMutex(void){
- sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ tdsqlite3_mutex_enter(winBigLock);
}
static void winShmLeaveMutex(void){
- sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ tdsqlite3_mutex_leave(winBigLock);
}
#ifndef NDEBUG
static int winShmMutexHeld(void) {
- return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1));
+ return tdsqlite3_mutex_held(winBigLock);
}
#endif
@@ -44580,12 +49412,15 @@ static int winShmMutexHeld(void) {
**
*/
struct winShmNode {
- sqlite3_mutex *mutex; /* Mutex to access this object */
+ tdsqlite3_mutex *mutex; /* Mutex to access this object */
char *zFilename; /* Name of the file */
winFile hFile; /* File handle from winOpen */
int szRegion; /* Size of shared-memory regions */
int nRegion; /* Size of array apRegion */
+ u8 isReadonly; /* True if read-only */
+ u8 isUnlocked; /* True if no DMS lock held */
+
struct ShmRegion {
HANDLE hMap; /* File handle from CreateFileMapping */
void *pMap;
@@ -44652,7 +49487,7 @@ static int winShmSystemLock(
int rc = 0; /* Result code form Lock/UnlockFileEx() */
/* Access to the winShmNode object is serialized by the caller */
- assert( sqlite3_mutex_held(pFile->mutex) || pFile->nRef==0 );
+ assert( pFile->nRef==0 || tdsqlite3_mutex_held(pFile->mutex) );
OSTRACE(("SHM-LOCK file=%p, lock=%d, offset=%d, size=%d\n",
pFile->hFile.h, lockType, ofst, nByte));
@@ -44676,14 +49511,14 @@ static int winShmSystemLock(
OSTRACE(("SHM-LOCK file=%p, func=%s, errno=%lu, rc=%s\n",
pFile->hFile.h, (lockType == WINSHM_UNLCK) ? "winUnlockFile" :
- "winLockFile", pFile->lastErrno, sqlite3ErrName(rc)));
+ "winLockFile", pFile->lastErrno, tdsqlite3ErrName(rc)));
return rc;
}
/* Forward references to VFS methods */
-static int winOpen(sqlite3_vfs*,const char*,sqlite3_file*,int,int*);
-static int winDelete(sqlite3_vfs *,const char*,int);
+static int winOpen(tdsqlite3_vfs*,const char*,tdsqlite3_file*,int,int*);
+static int winDelete(tdsqlite3_vfs *,const char*,int);
/*
** Purge the winShmNodeList list of all entries with winShmNode.nRef==0.
@@ -44691,7 +49526,7 @@ static int winDelete(sqlite3_vfs *,const char*,int);
** This is not a VFS shared-memory method; it is a utility function called
** by VFS shared-memory methods.
*/
-static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
+static void winShmPurge(tdsqlite3_vfs *pVfs, int deleteFlag){
winShmNode **pp;
winShmNode *p;
assert( winShmMutexHeld() );
@@ -44701,7 +49536,7 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
while( (p = *pp)!=0 ){
if( p->nRef==0 ){
int i;
- if( p->mutex ){ sqlite3_mutex_free(p->mutex); }
+ if( p->mutex ){ tdsqlite3_mutex_free(p->mutex); }
for(i=0; i<p->nRegion; i++){
BOOL bRc = osUnmapViewOfFile(p->aRegion[i].pMap);
OSTRACE(("SHM-PURGE-UNMAP pid=%lu, region=%d, rc=%s\n",
@@ -44714,19 +49549,19 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
}
if( p->hFile.h!=NULL && p->hFile.h!=INVALID_HANDLE_VALUE ){
SimulateIOErrorBenign(1);
- winClose((sqlite3_file *)&p->hFile);
+ winClose((tdsqlite3_file *)&p->hFile);
SimulateIOErrorBenign(0);
}
if( deleteFlag ){
SimulateIOErrorBenign(1);
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
winDelete(pVfs, p->zFilename, 0);
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
SimulateIOErrorBenign(0);
}
*pp = p->pNext;
- sqlite3_free(p->aRegion);
- sqlite3_free(p);
+ tdsqlite3_free(p->aRegion);
+ tdsqlite3_free(p);
}else{
pp = &p->pNext;
}
@@ -44734,6 +49569,37 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
}
/*
+** The DMS lock has not yet been taken on shm file pShmNode. Attempt to
+** take it now. Return SQLITE_OK if successful, or an SQLite error
+** code otherwise.
+**
+** If the DMS cannot be locked because this is a readonly_shm=1
+** connection and no other process already holds a lock, return
+** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1.
+*/
+static int winLockSharedMemory(winShmNode *pShmNode){
+ int rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, WIN_SHM_DMS, 1);
+
+ if( rc==SQLITE_OK ){
+ if( pShmNode->isReadonly ){
+ pShmNode->isUnlocked = 1;
+ winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
+ return SQLITE_READONLY_CANTINIT;
+ }else if( winTruncate((tdsqlite3_file*)&pShmNode->hFile, 0) ){
+ winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
+ return winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(),
+ "winLockSharedMemory", pShmNode->zFilename);
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
+ }
+
+ return winShmSystemLock(pShmNode, WINSHM_RDLCK, WIN_SHM_DMS, 1);
+}
+
+/*
** Open the shared-memory area associated with database file pDbFd.
**
** When opening a new shared-memory file, if no other instances of that
@@ -44742,27 +49608,27 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){
*/
static int winOpenSharedMemory(winFile *pDbFd){
struct winShm *p; /* The connection to be opened */
- struct winShmNode *pShmNode = 0; /* The underlying mmapped file */
- int rc; /* Result code */
- struct winShmNode *pNew; /* Newly allocated winShmNode */
+ winShmNode *pShmNode = 0; /* The underlying mmapped file */
+ int rc = SQLITE_OK; /* Result code */
+ winShmNode *pNew; /* Newly allocated winShmNode */
int nName; /* Size of zName in bytes */
assert( pDbFd->pShm==0 ); /* Not previously opened */
- /* Allocate space for the new sqlite3_shm object. Also speculatively
+ /* Allocate space for the new tdsqlite3_shm object. Also speculatively
** allocate space for a new winShmNode and filename.
*/
- p = sqlite3MallocZero( sizeof(*p) );
+ p = tdsqlite3MallocZero( sizeof(*p) );
if( p==0 ) return SQLITE_IOERR_NOMEM_BKPT;
- nName = sqlite3Strlen30(pDbFd->zPath);
- pNew = sqlite3MallocZero( sizeof(*pShmNode) + nName + 17 );
+ nName = tdsqlite3Strlen30(pDbFd->zPath);
+ pNew = tdsqlite3MallocZero( sizeof(*pShmNode) + nName + 17 );
if( pNew==0 ){
- sqlite3_free(p);
+ tdsqlite3_free(p);
return SQLITE_IOERR_NOMEM_BKPT;
}
pNew->zFilename = (char*)&pNew[1];
- sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath);
- sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename);
+ tdsqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath);
+ tdsqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename);
/* Look to see if there is an existing winShmNode that can be used.
** If no matching winShmNode currently exists, create a new one.
@@ -44772,49 +49638,45 @@ static int winOpenSharedMemory(winFile *pDbFd){
/* TBD need to come up with better match here. Perhaps
** use FILE_ID_BOTH_DIR_INFO Structure.
*/
- if( sqlite3StrICmp(pShmNode->zFilename, pNew->zFilename)==0 ) break;
+ if( tdsqlite3StrICmp(pShmNode->zFilename, pNew->zFilename)==0 ) break;
}
if( pShmNode ){
- sqlite3_free(pNew);
+ tdsqlite3_free(pNew);
}else{
+ int inFlags = SQLITE_OPEN_WAL;
+ int outFlags = 0;
+
pShmNode = pNew;
pNew = 0;
((winFile*)(&pShmNode->hFile))->h = INVALID_HANDLE_VALUE;
pShmNode->pNext = winShmNodeList;
winShmNodeList = pShmNode;
- if( sqlite3GlobalConfig.bCoreMutex ){
- pShmNode->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ if( tdsqlite3GlobalConfig.bCoreMutex ){
+ pShmNode->mutex = tdsqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
if( pShmNode->mutex==0 ){
rc = SQLITE_IOERR_NOMEM_BKPT;
goto shm_open_err;
}
}
- rc = winOpen(pDbFd->pVfs,
- pShmNode->zFilename, /* Name of the file (UTF-8) */
- (sqlite3_file*)&pShmNode->hFile, /* File handle here */
- SQLITE_OPEN_WAL | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
- 0);
- if( SQLITE_OK!=rc ){
+ if( 0==tdsqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){
+ inFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
+ }else{
+ inFlags |= SQLITE_OPEN_READONLY;
+ }
+ rc = winOpen(pDbFd->pVfs, pShmNode->zFilename,
+ (tdsqlite3_file*)&pShmNode->hFile,
+ inFlags, &outFlags);
+ if( rc!=SQLITE_OK ){
+ rc = winLogError(rc, osGetLastError(), "winOpenShm",
+ pShmNode->zFilename);
goto shm_open_err;
}
+ if( outFlags==SQLITE_OPEN_READONLY ) pShmNode->isReadonly = 1;
- /* Check to see if another process is holding the dead-man switch.
- ** If not, truncate the file to zero length.
- */
- if( winShmSystemLock(pShmNode, WINSHM_WRLCK, WIN_SHM_DMS, 1)==SQLITE_OK ){
- rc = winTruncate((sqlite3_file *)&pShmNode->hFile, 0);
- if( rc!=SQLITE_OK ){
- rc = winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(),
- "winOpenShm", pDbFd->zPath);
- }
- }
- if( rc==SQLITE_OK ){
- winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
- rc = winShmSystemLock(pShmNode, WINSHM_RDLCK, WIN_SHM_DMS, 1);
- }
- if( rc ) goto shm_open_err;
+ rc = winLockSharedMemory(pShmNode);
+ if( rc!=SQLITE_OK && rc!=SQLITE_READONLY_CANTINIT ) goto shm_open_err;
}
/* Make the new connection a child of the winShmNode */
@@ -44833,18 +49695,18 @@ static int winOpenSharedMemory(winFile *pDbFd){
** at pShmNode->pFirst. This must be done while holding the pShmNode->mutex
** mutex.
*/
- sqlite3_mutex_enter(pShmNode->mutex);
+ tdsqlite3_mutex_enter(pShmNode->mutex);
p->pNext = pShmNode->pFirst;
pShmNode->pFirst = p;
- sqlite3_mutex_leave(pShmNode->mutex);
- return SQLITE_OK;
+ tdsqlite3_mutex_leave(pShmNode->mutex);
+ return rc;
/* Jump here on any error */
shm_open_err:
winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
winShmPurge(pDbFd->pVfs, 0); /* This call frees pShmNode if required */
- sqlite3_free(p);
- sqlite3_free(pNew);
+ tdsqlite3_free(p);
+ tdsqlite3_free(pNew);
winShmLeaveMutex();
return rc;
}
@@ -44854,7 +49716,7 @@ shm_open_err:
** storage if deleteFlag is true.
*/
static int winShmUnmap(
- sqlite3_file *fd, /* Database holding shared memory */
+ tdsqlite3_file *fd, /* Database holding shared memory */
int deleteFlag /* Delete after closing if true */
){
winFile *pDbFd; /* Database holding shared-memory */
@@ -44869,14 +49731,14 @@ static int winShmUnmap(
/* Remove connection p from the set of connections associated
** with pShmNode */
- sqlite3_mutex_enter(pShmNode->mutex);
+ tdsqlite3_mutex_enter(pShmNode->mutex);
for(pp=&pShmNode->pFirst; (*pp)!=p; pp = &(*pp)->pNext){}
*pp = p->pNext;
/* Free the connection p */
- sqlite3_free(p);
+ tdsqlite3_free(p);
pDbFd->pShm = 0;
- sqlite3_mutex_leave(pShmNode->mutex);
+ tdsqlite3_mutex_leave(pShmNode->mutex);
/* If pShmNode->nRef has reached 0, then close the underlying
** shared-memory file, too */
@@ -44895,7 +49757,7 @@ static int winShmUnmap(
** Change the lock state for a shared-memory segment.
*/
static int winShmLock(
- sqlite3_file *fd, /* Database file holding the shared memory */
+ tdsqlite3_file *fd, /* Database file holding the shared memory */
int ofst, /* First lock to acquire or release */
int n, /* Number of locks to acquire or release */
int flags /* What to do with the lock */
@@ -44917,7 +49779,7 @@ static int winShmLock(
mask = (u16)((1U<<(ofst+n)) - (1U<<ofst));
assert( n>1 || mask==(1<<ofst) );
- sqlite3_mutex_enter(pShmNode->mutex);
+ tdsqlite3_mutex_enter(pShmNode->mutex);
if( flags & SQLITE_SHM_UNLOCK ){
u16 allMask = 0; /* Mask of locks held by siblings */
@@ -44990,10 +49852,10 @@ static int winShmLock(
}
}
}
- sqlite3_mutex_leave(pShmNode->mutex);
+ tdsqlite3_mutex_leave(pShmNode->mutex);
OSTRACE(("SHM-LOCK pid=%lu, id=%d, sharedMask=%03x, exclMask=%03x, rc=%s\n",
osGetCurrentProcessId(), p->id, p->sharedMask, p->exclMask,
- sqlite3ErrName(rc)));
+ tdsqlite3ErrName(rc)));
return rc;
}
@@ -45004,10 +49866,10 @@ static int winShmLock(
** any load or store begun after the barrier.
*/
static void winShmBarrier(
- sqlite3_file *fd /* Database holding the shared memory */
+ tdsqlite3_file *fd /* Database holding the shared memory */
){
UNUSED_PARAMETER(fd);
- sqlite3MemoryBarrier(); /* compiler-defined memory barrier */
+ tdsqlite3MemoryBarrier(); /* compiler-defined memory barrier */
winShmEnterMutex(); /* Also mutex, for redundancy */
winShmLeaveMutex();
}
@@ -45032,7 +49894,7 @@ static void winShmBarrier(
** memory and SQLITE_OK returned.
*/
static int winShmMap(
- sqlite3_file *fd, /* Handle open on database file */
+ tdsqlite3_file *fd, /* Handle open on database file */
int iRegion, /* Region to retrieve */
int szRegion, /* Size of regions */
int isWrite, /* True to extend file if necessary */
@@ -45041,22 +49903,30 @@ static int winShmMap(
winFile *pDbFd = (winFile*)fd;
winShm *pShm = pDbFd->pShm;
winShmNode *pShmNode;
+ DWORD protect = PAGE_READWRITE;
+ DWORD flags = FILE_MAP_WRITE | FILE_MAP_READ;
int rc = SQLITE_OK;
if( !pShm ){
rc = winOpenSharedMemory(pDbFd);
if( rc!=SQLITE_OK ) return rc;
pShm = pDbFd->pShm;
+ assert( pShm!=0 );
}
pShmNode = pShm->pShmNode;
- sqlite3_mutex_enter(pShmNode->mutex);
+ tdsqlite3_mutex_enter(pShmNode->mutex);
+ if( pShmNode->isUnlocked ){
+ rc = winLockSharedMemory(pShmNode);
+ if( rc!=SQLITE_OK ) goto shmpage_out;
+ pShmNode->isUnlocked = 0;
+ }
assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 );
if( pShmNode->nRegion<=iRegion ){
struct ShmRegion *apNew; /* New aRegion[] array */
int nByte = (iRegion+1)*szRegion; /* Minimum required file size */
- sqlite3_int64 sz; /* Current size of wal-index file */
+ tdsqlite3_int64 sz; /* Current size of wal-index file */
pShmNode->szRegion = szRegion;
@@ -45064,7 +49934,7 @@ static int winShmMap(
** Check to see if it has been allocated (i.e. if the wal-index file is
** large enough to contain the requested region).
*/
- rc = winFileSize((sqlite3_file *)&pShmNode->hFile, &sz);
+ rc = winFileSize((tdsqlite3_file *)&pShmNode->hFile, &sz);
if( rc!=SQLITE_OK ){
rc = winLogError(SQLITE_IOERR_SHMSIZE, osGetLastError(),
"winShmMap1", pDbFd->zPath);
@@ -45079,7 +49949,7 @@ static int winShmMap(
** the requested memory region.
*/
if( !isWrite ) goto shmpage_out;
- rc = winTruncate((sqlite3_file *)&pShmNode->hFile, nByte);
+ rc = winTruncate((tdsqlite3_file *)&pShmNode->hFile, nByte);
if( rc!=SQLITE_OK ){
rc = winLogError(SQLITE_IOERR_SHMSIZE, osGetLastError(),
"winShmMap2", pDbFd->zPath);
@@ -45088,7 +49958,7 @@ static int winShmMap(
}
/* Map the requested memory region into this processes address space. */
- apNew = (struct ShmRegion *)sqlite3_realloc64(
+ apNew = (struct ShmRegion *)tdsqlite3_realloc64(
pShmNode->aRegion, (iRegion+1)*sizeof(apNew[0])
);
if( !apNew ){
@@ -45097,21 +49967,26 @@ static int winShmMap(
}
pShmNode->aRegion = apNew;
+ if( pShmNode->isReadonly ){
+ protect = PAGE_READONLY;
+ flags = FILE_MAP_READ;
+ }
+
while( pShmNode->nRegion<=iRegion ){
HANDLE hMap = NULL; /* file-mapping handle */
void *pMap = 0; /* Mapped memory region */
#if SQLITE_OS_WINRT
hMap = osCreateFileMappingFromApp(pShmNode->hFile.h,
- NULL, PAGE_READWRITE, nByte, NULL
+ NULL, protect, nByte, NULL
);
#elif defined(SQLITE_WIN32_HAS_WIDE)
hMap = osCreateFileMappingW(pShmNode->hFile.h,
- NULL, PAGE_READWRITE, 0, nByte, NULL
+ NULL, protect, 0, nByte, NULL
);
#elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA
hMap = osCreateFileMappingA(pShmNode->hFile.h,
- NULL, PAGE_READWRITE, 0, nByte, NULL
+ NULL, protect, 0, nByte, NULL
);
#endif
OSTRACE(("SHM-MAP-CREATE pid=%lu, region=%d, size=%d, rc=%s\n",
@@ -45121,11 +49996,11 @@ static int winShmMap(
int iOffset = pShmNode->nRegion*szRegion;
int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity;
#if SQLITE_OS_WINRT
- pMap = osMapViewOfFileFromApp(hMap, FILE_MAP_WRITE | FILE_MAP_READ,
+ pMap = osMapViewOfFileFromApp(hMap, flags,
iOffset - iOffsetShift, szRegion + iOffsetShift
);
#else
- pMap = osMapViewOfFile(hMap, FILE_MAP_WRITE | FILE_MAP_READ,
+ pMap = osMapViewOfFile(hMap, flags,
0, iOffset - iOffsetShift, szRegion + iOffsetShift
);
#endif
@@ -45156,7 +50031,8 @@ shmpage_out:
}else{
*pp = 0;
}
- sqlite3_mutex_leave(pShmNode->mutex);
+ if( pShmNode->isReadonly && rc==SQLITE_OK ) rc = SQLITE_READONLY;
+ tdsqlite3_mutex_leave(pShmNode->mutex);
return rc;
}
@@ -45174,9 +50050,9 @@ shmpage_out:
static int winUnmapfile(winFile *pFile){
assert( pFile!=0 );
OSTRACE(("UNMAP-FILE pid=%lu, pFile=%p, hMap=%p, pMapRegion=%p, "
- "mmapSize=%lld, mmapSizeActual=%lld, mmapSizeMax=%lld\n",
+ "mmapSize=%lld, mmapSizeMax=%lld\n",
osGetCurrentProcessId(), pFile, pFile->hMap, pFile->pMapRegion,
- pFile->mmapSize, pFile->mmapSizeActual, pFile->mmapSizeMax));
+ pFile->mmapSize, pFile->mmapSizeMax));
if( pFile->pMapRegion ){
if( !osUnmapViewOfFile(pFile->pMapRegion) ){
pFile->lastErrno = osGetLastError();
@@ -45188,7 +50064,6 @@ static int winUnmapfile(winFile *pFile){
}
pFile->pMapRegion = 0;
pFile->mmapSize = 0;
- pFile->mmapSizeActual = 0;
}
if( pFile->hMap!=NULL ){
if( !osCloseHandle(pFile->hMap) ){
@@ -45221,8 +50096,8 @@ static int winUnmapfile(winFile *pFile){
** recreated as a result of outstanding references) or an SQLite error
** code otherwise.
*/
-static int winMapfile(winFile *pFd, sqlite3_int64 nByte){
- sqlite3_int64 nMap = nByte;
+static int winMapfile(winFile *pFd, tdsqlite3_int64 nByte){
+ tdsqlite3_int64 nMap = nByte;
int rc;
assert( nMap>=0 || pFd->nFetchOut==0 );
@@ -45232,7 +50107,7 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){
if( pFd->nFetchOut>0 ) return SQLITE_OK;
if( nMap<0 ){
- rc = winFileSize((sqlite3_file*)pFd, &nMap);
+ rc = winFileSize((tdsqlite3_file*)pFd, &nMap);
if( rc ){
OSTRACE(("MAP-FILE pid=%lu, pFile=%p, rc=SQLITE_IOERR_FSTAT\n",
osGetCurrentProcessId(), pFd));
@@ -45242,7 +50117,7 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){
if( nMap>pFd->mmapSizeMax ){
nMap = pFd->mmapSizeMax;
}
- nMap &= ~(sqlite3_int64)(winSysInfo.dwPageSize - 1);
+ nMap &= ~(tdsqlite3_int64)(winSysInfo.dwPageSize - 1);
if( nMap==0 && pFd->mmapSize>0 ){
winUnmapfile(pFd);
@@ -45276,11 +50151,11 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){
"winMapfile1", pFd->zPath);
/* Log the error, but continue normal operation using xRead/xWrite */
OSTRACE(("MAP-FILE-CREATE pid=%lu, pFile=%p, rc=%s\n",
- osGetCurrentProcessId(), pFd, sqlite3ErrName(rc)));
+ osGetCurrentProcessId(), pFd, tdsqlite3ErrName(rc)));
return SQLITE_OK;
}
assert( (nMap % winSysInfo.dwPageSize)==0 );
- assert( sizeof(SIZE_T)==sizeof(sqlite3_int64) || nMap<=0xffffffff );
+ assert( sizeof(SIZE_T)==sizeof(tdsqlite3_int64) || nMap<=0xffffffff );
#if SQLITE_OS_WINRT
pNew = osMapViewOfFileFromApp(pFd->hMap, flags, 0, (SIZE_T)nMap);
#else
@@ -45294,12 +50169,11 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){
"winMapfile2", pFd->zPath);
/* Log the error, but continue normal operation using xRead/xWrite */
OSTRACE(("MAP-FILE-MAP pid=%lu, pFile=%p, rc=%s\n",
- osGetCurrentProcessId(), pFd, sqlite3ErrName(rc)));
+ osGetCurrentProcessId(), pFd, tdsqlite3ErrName(rc)));
return SQLITE_OK;
}
pFd->pMapRegion = pNew;
pFd->mmapSize = nMap;
- pFd->mmapSizeActual = nMap;
}
OSTRACE(("MAP-FILE pid=%lu, pFile=%p, rc=SQLITE_OK\n",
@@ -45320,7 +50194,7 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){
** If this function does return a pointer, the caller must eventually
** release the reference by calling winUnfetch().
*/
-static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){
+static int winFetch(tdsqlite3_file *fd, i64 iOff, int nAmt, void **pp){
#if SQLITE_MAX_MMAP_SIZE>0
winFile *pFd = (winFile*)fd; /* The underlying database file */
#endif
@@ -45335,11 +50209,12 @@ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){
int rc = winMapfile(pFd, -1);
if( rc!=SQLITE_OK ){
OSTRACE(("FETCH pid=%lu, pFile=%p, rc=%s\n",
- osGetCurrentProcessId(), pFd, sqlite3ErrName(rc)));
+ osGetCurrentProcessId(), pFd, tdsqlite3ErrName(rc)));
return rc;
}
}
if( pFd->mmapSize >= iOff+nAmt ){
+ assert( pFd->pMapRegion!=0 );
*pp = &((u8 *)pFd->pMapRegion)[iOff];
pFd->nFetchOut++;
}
@@ -45361,7 +50236,7 @@ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){
** to inform the VFS layer that, according to POSIX, any existing mapping
** may now be invalid and should be unmapped.
*/
-static int winUnfetch(sqlite3_file *fd, i64 iOff, void *p){
+static int winUnfetch(tdsqlite3_file *fd, i64 iOff, void *p){
#if SQLITE_MAX_MMAP_SIZE>0
winFile *pFd = (winFile*)fd; /* The underlying database file */
@@ -45395,16 +50270,16 @@ static int winUnfetch(sqlite3_file *fd, i64 iOff, void *p){
}
/*
-** Here ends the implementation of all sqlite3_file methods.
+** Here ends the implementation of all tdsqlite3_file methods.
**
-********************** End sqlite3_file Methods *******************************
+********************** End tdsqlite3_file Methods *******************************
******************************************************************************/
/*
** This vector defines all the methods that can operate on an
-** sqlite3_file for win32.
+** tdsqlite3_file for win32.
*/
-static const sqlite3_io_methods winIoMethod = {
+static const tdsqlite3_io_methods winIoMethod = {
3, /* iVersion */
winClose, /* xClose */
winRead, /* xRead */
@@ -45428,9 +50303,9 @@ static const sqlite3_io_methods winIoMethod = {
/*
** This vector defines all the methods that can operate on an
-** sqlite3_file for win32 without performing any locking.
+** tdsqlite3_file for win32 without performing any locking.
*/
-static const sqlite3_io_methods winIoNolockMethod = {
+static const tdsqlite3_io_methods winIoNolockMethod = {
3, /* iVersion */
winClose, /* xClose */
winRead, /* xRead */
@@ -45465,10 +50340,10 @@ static winVfsAppData winNolockAppData = {
};
/****************************************************************************
-**************************** sqlite3_vfs methods ****************************
+**************************** tdsqlite3_vfs methods ****************************
**
** This division contains the implementation of methods on the
-** sqlite3_vfs object.
+** tdsqlite3_vfs object.
*/
#if defined(__CYGWIN__)
@@ -45519,7 +50394,7 @@ static void *winConvertFromUtf8Filename(const char *zFilename){
*/
static int winMakeEndInDirSep(int nBuf, char *zBuf){
if( zBuf ){
- int nLen = sqlite3Strlen30(zBuf);
+ int nLen = tdsqlite3Strlen30(zBuf);
if( nLen>0 ){
if( winIsDirSep(zBuf[nLen-1]) ){
return 1;
@@ -45535,15 +50410,15 @@ static int winMakeEndInDirSep(int nBuf, char *zBuf){
/*
** Create a temporary file name and store the resulting pointer into pzBuf.
-** The pointer returned in pzBuf must be freed via sqlite3_free().
+** The pointer returned in pzBuf must be freed via tdsqlite3_free().
*/
-static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
+static int winGetTempname(tdsqlite3_vfs *pVfs, char **pzBuf){
static char zChars[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
size_t i, j;
- int nPre = sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX);
+ int nPre = tdsqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX);
int nMax, nBuf, nDir, nLen;
char *zBuf;
@@ -45557,7 +50432,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
** name for the temporary file. If this fails, we cannot continue.
*/
nMax = pVfs->mxPathname; nBuf = nMax + 2;
- zBuf = sqlite3MallocZero( nBuf );
+ zBuf = tdsqlite3MallocZero( nBuf );
if( !zBuf ){
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
@@ -45569,18 +50444,18 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
*/
nDir = nMax - (nPre + 15);
assert( nDir>0 );
- if( sqlite3_temp_directory ){
- int nDirLen = sqlite3Strlen30(sqlite3_temp_directory);
+ if( tdsqlite3_temp_directory ){
+ int nDirLen = tdsqlite3Strlen30(tdsqlite3_temp_directory);
if( nDirLen>0 ){
- if( !winIsDirSep(sqlite3_temp_directory[nDirLen-1]) ){
+ if( !winIsDirSep(tdsqlite3_temp_directory[nDirLen-1]) ){
nDirLen++;
}
if( nDirLen>nDir ){
- sqlite3_free(zBuf);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n"));
return winLogError(SQLITE_ERROR, 0, "winGetTempname1", 0);
}
- sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory);
+ tdsqlite3_snprintf(nMax, zBuf, "%s", tdsqlite3_temp_directory);
}
}
#if defined(__CYGWIN__)
@@ -45616,28 +50491,28 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
if( winIsDriveLetterAndColon(zDir) ){
zConverted = winConvertFromUtf8Filename(zDir);
if( !zConverted ){
- sqlite3_free(zBuf);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
}
if( winIsDir(zConverted) ){
- sqlite3_snprintf(nMax, zBuf, "%s", zDir);
- sqlite3_free(zConverted);
+ tdsqlite3_snprintf(nMax, zBuf, "%s", zDir);
+ tdsqlite3_free(zConverted);
break;
}
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
}else{
- zConverted = sqlite3MallocZero( nMax+1 );
+ zConverted = tdsqlite3MallocZero( nMax+1 );
if( !zConverted ){
- sqlite3_free(zBuf);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
}
if( cygwin_conv_path(
osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A, zDir,
zConverted, nMax+1)<0 ){
- sqlite3_free(zConverted);
- sqlite3_free(zBuf);
+ tdsqlite3_free(zConverted);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_CONVPATH\n"));
return winLogError(SQLITE_IOERR_CONVPATH, (DWORD)errno,
"winGetTempname2", zDir);
@@ -45649,44 +50524,44 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
*/
char *zUtf8 = winConvertToUtf8Filename(zConverted);
if( !zUtf8 ){
- sqlite3_free(zConverted);
- sqlite3_free(zBuf);
+ tdsqlite3_free(zConverted);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
}
- sqlite3_snprintf(nMax, zBuf, "%s", zUtf8);
- sqlite3_free(zUtf8);
- sqlite3_free(zConverted);
+ tdsqlite3_snprintf(nMax, zBuf, "%s", zUtf8);
+ tdsqlite3_free(zUtf8);
+ tdsqlite3_free(zConverted);
break;
}
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
}
}
}
#elif !SQLITE_OS_WINRT && !defined(__CYGWIN__)
else if( osIsNT() ){
char *zMulti;
- LPWSTR zWidePath = sqlite3MallocZero( nMax*sizeof(WCHAR) );
+ LPWSTR zWidePath = tdsqlite3MallocZero( nMax*sizeof(WCHAR) );
if( !zWidePath ){
- sqlite3_free(zBuf);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
}
if( osGetTempPathW(nMax, zWidePath)==0 ){
- sqlite3_free(zWidePath);
- sqlite3_free(zBuf);
+ tdsqlite3_free(zWidePath);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_GETTEMPPATH\n"));
return winLogError(SQLITE_IOERR_GETTEMPPATH, osGetLastError(),
"winGetTempname2", 0);
}
zMulti = winUnicodeToUtf8(zWidePath);
if( zMulti ){
- sqlite3_snprintf(nMax, zBuf, "%s", zMulti);
- sqlite3_free(zMulti);
- sqlite3_free(zWidePath);
+ tdsqlite3_snprintf(nMax, zBuf, "%s", zMulti);
+ tdsqlite3_free(zMulti);
+ tdsqlite3_free(zWidePath);
}else{
- sqlite3_free(zWidePath);
- sqlite3_free(zBuf);
+ tdsqlite3_free(zWidePath);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
}
@@ -45694,24 +50569,24 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
#ifdef SQLITE_WIN32_HAS_ANSI
else{
char *zUtf8;
- char *zMbcsPath = sqlite3MallocZero( nMax );
+ char *zMbcsPath = tdsqlite3MallocZero( nMax );
if( !zMbcsPath ){
- sqlite3_free(zBuf);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
}
if( osGetTempPathA(nMax, zMbcsPath)==0 ){
- sqlite3_free(zBuf);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_GETTEMPPATH\n"));
return winLogError(SQLITE_IOERR_GETTEMPPATH, osGetLastError(),
"winGetTempname3", 0);
}
zUtf8 = winMbcsToUtf8(zMbcsPath, osAreFileApisANSI());
if( zUtf8 ){
- sqlite3_snprintf(nMax, zBuf, "%s", zUtf8);
- sqlite3_free(zUtf8);
+ tdsqlite3_snprintf(nMax, zBuf, "%s", zUtf8);
+ tdsqlite3_free(zUtf8);
}else{
- sqlite3_free(zBuf);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
return SQLITE_IOERR_NOMEM_BKPT;
}
@@ -45725,7 +50600,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
** one, fail.
*/
if( !winMakeEndInDirSep(nDir+1, zBuf) ){
- sqlite3_free(zBuf);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n"));
return winLogError(SQLITE_ERROR, 0, "winGetTempname4", 0);
}
@@ -45741,17 +50616,17 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
** two trailing NUL characters. The final directory separator character
** has already added if it was not already present.
*/
- nLen = sqlite3Strlen30(zBuf);
+ nLen = tdsqlite3Strlen30(zBuf);
if( (nLen + nPre + 17) > nBuf ){
- sqlite3_free(zBuf);
+ tdsqlite3_free(zBuf);
OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n"));
return winLogError(SQLITE_ERROR, 0, "winGetTempname5", 0);
}
- sqlite3_snprintf(nBuf-16-nLen, zBuf+nLen, SQLITE_TEMP_FILE_PREFIX);
+ tdsqlite3_snprintf(nBuf-16-nLen, zBuf+nLen, SQLITE_TEMP_FILE_PREFIX);
- j = sqlite3Strlen30(zBuf);
- sqlite3_randomness(15, &zBuf[j]);
+ j = tdsqlite3Strlen30(zBuf);
+ tdsqlite3_randomness(15, &zBuf[j]);
for(i=0; i<15; i++, j++){
zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ];
}
@@ -45792,13 +50667,21 @@ static int winIsDir(const void *zConverted){
return (attr!=INVALID_FILE_ATTRIBUTES) && (attr&FILE_ATTRIBUTE_DIRECTORY);
}
+/* forward reference */
+static int winAccess(
+ tdsqlite3_vfs *pVfs, /* Not used on win32 */
+ const char *zFilename, /* Name of file to check */
+ int flags, /* Type of test to make on this file */
+ int *pResOut /* OUT: Result */
+);
+
/*
** Open a file.
*/
static int winOpen(
- sqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */
+ tdsqlite3_vfs *pVfs, /* Used to get maximum path length and AppData */
const char *zName, /* Name of the file (UTF-8) */
- sqlite3_file *id, /* Write the SQLite file handle here */
+ tdsqlite3_file *id, /* Write the SQLite file handle here */
int flags, /* Open mode flags */
int *pOutFlags /* Status return flags */
){
@@ -45875,9 +50758,9 @@ static int winOpen(
pFile->h = INVALID_HANDLE_VALUE;
#if SQLITE_OS_WINRT
- if( !zUtf8Name && !sqlite3_temp_directory ){
- sqlite3_log(SQLITE_ERROR,
- "sqlite3_temp_directory variable should be set for WinRT");
+ if( !zUtf8Name && !tdsqlite3_temp_directory ){
+ tdsqlite3_log(SQLITE_ERROR,
+ "tdsqlite3_temp_directory variable should be set for WinRT");
}
#endif
@@ -45888,7 +50771,7 @@ static int winOpen(
assert( isDelete && !isOpenJournal );
rc = winGetTempname(pVfs, &zTmpname);
if( rc!=SQLITE_OK ){
- OSTRACE(("OPEN name=%s, rc=%s", zUtf8Name, sqlite3ErrName(rc)));
+ OSTRACE(("OPEN name=%s, rc=%s", zUtf8Name, tdsqlite3ErrName(rc)));
return rc;
}
zUtf8Name = zTmpname;
@@ -45896,22 +50779,22 @@ static int winOpen(
/* Database filenames are double-zero terminated if they are not
** URIs with parameters. Hence, they can always be passed into
- ** sqlite3_uri_parameter().
+ ** tdsqlite3_uri_parameter().
*/
assert( (eType!=SQLITE_OPEN_MAIN_DB) || (flags & SQLITE_OPEN_URI) ||
- zUtf8Name[sqlite3Strlen30(zUtf8Name)+1]==0 );
+ zUtf8Name[tdsqlite3Strlen30(zUtf8Name)+1]==0 );
/* Convert the filename to the system encoding. */
zConverted = winConvertFromUtf8Filename(zUtf8Name);
if( zConverted==0 ){
- sqlite3_free(zTmpname);
+ tdsqlite3_free(zTmpname);
OSTRACE(("OPEN name=%s, rc=SQLITE_IOERR_NOMEM", zUtf8Name));
return SQLITE_IOERR_NOMEM_BKPT;
}
if( winIsDir(zConverted) ){
- sqlite3_free(zConverted);
- sqlite3_free(zTmpname);
+ tdsqlite3_free(zConverted);
+ tdsqlite3_free(zTmpname);
OSTRACE(("OPEN name=%s, rc=SQLITE_CANTOPEN_ISDIR", zUtf8Name));
return SQLITE_CANTOPEN_ISDIR;
}
@@ -45971,37 +50854,58 @@ static int winOpen(
extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS;
extendedParameters.lpSecurityAttributes = NULL;
extendedParameters.hTemplateFile = NULL;
- while( (h = osCreateFile2((LPCWSTR)zConverted,
- dwDesiredAccess,
- dwShareMode,
- dwCreationDisposition,
- &extendedParameters))==INVALID_HANDLE_VALUE &&
- winRetryIoerr(&cnt, &lastErrno) ){
- /* Noop */
- }
+ do{
+ h = osCreateFile2((LPCWSTR)zConverted,
+ dwDesiredAccess,
+ dwShareMode,
+ dwCreationDisposition,
+ &extendedParameters);
+ if( h!=INVALID_HANDLE_VALUE ) break;
+ if( isReadWrite ){
+ int rc2, isRO = 0;
+ tdsqlite3BeginBenignMalloc();
+ rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO);
+ tdsqlite3EndBenignMalloc();
+ if( rc2==SQLITE_OK && isRO ) break;
+ }
+ }while( winRetryIoerr(&cnt, &lastErrno) );
#else
- while( (h = osCreateFileW((LPCWSTR)zConverted,
- dwDesiredAccess,
- dwShareMode, NULL,
- dwCreationDisposition,
- dwFlagsAndAttributes,
- NULL))==INVALID_HANDLE_VALUE &&
- winRetryIoerr(&cnt, &lastErrno) ){
- /* Noop */
- }
+ do{
+ h = osCreateFileW((LPCWSTR)zConverted,
+ dwDesiredAccess,
+ dwShareMode, NULL,
+ dwCreationDisposition,
+ dwFlagsAndAttributes,
+ NULL);
+ if( h!=INVALID_HANDLE_VALUE ) break;
+ if( isReadWrite ){
+ int rc2, isRO = 0;
+ tdsqlite3BeginBenignMalloc();
+ rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO);
+ tdsqlite3EndBenignMalloc();
+ if( rc2==SQLITE_OK && isRO ) break;
+ }
+ }while( winRetryIoerr(&cnt, &lastErrno) );
#endif
}
#ifdef SQLITE_WIN32_HAS_ANSI
else{
- while( (h = osCreateFileA((LPCSTR)zConverted,
- dwDesiredAccess,
- dwShareMode, NULL,
- dwCreationDisposition,
- dwFlagsAndAttributes,
- NULL))==INVALID_HANDLE_VALUE &&
- winRetryIoerr(&cnt, &lastErrno) ){
- /* Noop */
- }
+ do{
+ h = osCreateFileA((LPCSTR)zConverted,
+ dwDesiredAccess,
+ dwShareMode, NULL,
+ dwCreationDisposition,
+ dwFlagsAndAttributes,
+ NULL);
+ if( h!=INVALID_HANDLE_VALUE ) break;
+ if( isReadWrite ){
+ int rc2, isRO = 0;
+ tdsqlite3BeginBenignMalloc();
+ rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO);
+ tdsqlite3EndBenignMalloc();
+ if( rc2==SQLITE_OK && isRO ) break;
+ }
+ }while( winRetryIoerr(&cnt, &lastErrno) );
}
#endif
winLogIoerr(cnt, __LINE__);
@@ -46010,16 +50914,16 @@ static int winOpen(
dwDesiredAccess, (h==INVALID_HANDLE_VALUE) ? "failed" : "ok"));
if( h==INVALID_HANDLE_VALUE ){
- pFile->lastErrno = lastErrno;
- winLogError(SQLITE_CANTOPEN, pFile->lastErrno, "winOpen", zUtf8Name);
- sqlite3_free(zConverted);
- sqlite3_free(zTmpname);
+ tdsqlite3_free(zConverted);
+ tdsqlite3_free(zTmpname);
if( isReadWrite && !isExclusive ){
return winOpen(pVfs, zName, id,
((flags|SQLITE_OPEN_READONLY) &
~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)),
pOutFlags);
}else{
+ pFile->lastErrno = lastErrno;
+ winLogError(SQLITE_CANTOPEN, pFile->lastErrno, "winOpen", zUtf8Name);
return SQLITE_CANTOPEN_BKPT;
}
}
@@ -46045,9 +50949,9 @@ static int winOpen(
&& (rc = winceCreateLock(zName, pFile))!=SQLITE_OK
){
osCloseHandle(h);
- sqlite3_free(zConverted);
- sqlite3_free(zTmpname);
- OSTRACE(("OPEN-CE-LOCK name=%s, rc=%s\n", zName, sqlite3ErrName(rc)));
+ tdsqlite3_free(zConverted);
+ tdsqlite3_free(zTmpname);
+ OSTRACE(("OPEN-CE-LOCK name=%s, rc=%s\n", zName, tdsqlite3ErrName(rc)));
return rc;
}
}
@@ -46056,17 +50960,17 @@ static int winOpen(
}else
#endif
{
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
}
- sqlite3_free(zTmpname);
+ tdsqlite3_free(zTmpname);
pFile->pMethod = pAppData ? pAppData->pMethod : &winIoMethod;
pFile->pVfs = pVfs;
pFile->h = h;
if( isReadonly ){
pFile->ctrlFlags |= WINFILE_RDONLY;
}
- if( sqlite3_uri_boolean(zName, "psow", SQLITE_POWERSAFE_OVERWRITE) ){
+ if( tdsqlite3_uri_boolean(zName, "psow", SQLITE_POWERSAFE_OVERWRITE) ){
pFile->ctrlFlags |= WINFILE_PSOW;
}
pFile->lastErrno = NO_ERROR;
@@ -46075,8 +50979,7 @@ static int winOpen(
pFile->hMap = NULL;
pFile->pMapRegion = 0;
pFile->mmapSize = 0;
- pFile->mmapSizeActual = 0;
- pFile->mmapSizeMax = sqlite3GlobalConfig.szMmap;
+ pFile->mmapSizeMax = tdsqlite3GlobalConfig.szMmap;
#endif
OpenCounter(+1);
@@ -46096,7 +50999,7 @@ static int winOpen(
** up and returning an error.
*/
static int winDelete(
- sqlite3_vfs *pVfs, /* Not used on win32 */
+ tdsqlite3_vfs *pVfs, /* Not used on win32 */
const char *zFilename, /* Name of file to delete */
int syncDir /* Not used on win32 */
){
@@ -46195,8 +51098,8 @@ static int winDelete(
}else{
winLogIoerr(cnt, __LINE__);
}
- sqlite3_free(zConverted);
- OSTRACE(("DELETE name=%s, rc=%s\n", zFilename, sqlite3ErrName(rc)));
+ tdsqlite3_free(zConverted);
+ OSTRACE(("DELETE name=%s, rc=%s\n", zFilename, tdsqlite3ErrName(rc)));
return rc;
}
@@ -46204,7 +51107,7 @@ static int winDelete(
** Check the existence and status of a file.
*/
static int winAccess(
- sqlite3_vfs *pVfs, /* Not used on win32 */
+ tdsqlite3_vfs *pVfs, /* Not used on win32 */
const char *zFilename, /* Name of file to check */
int flags, /* Type of test to make on this file */
int *pResOut /* OUT: Result */
@@ -46245,7 +51148,7 @@ static int winAccess(
}else{
winLogIoerr(cnt, __LINE__);
if( lastErrno!=ERROR_FILE_NOT_FOUND && lastErrno!=ERROR_PATH_NOT_FOUND ){
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
return winLogError(SQLITE_IOERR_ACCESS, lastErrno, "winAccess",
zFilename);
}else{
@@ -46258,7 +51161,7 @@ static int winAccess(
attr = osGetFileAttributesA((char*)zConverted);
}
#endif
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
switch( flags ){
case SQLITE_ACCESS_READ:
case SQLITE_ACCESS_EXISTS:
@@ -46284,7 +51187,7 @@ static int winAccess(
static BOOL winIsDriveLetterAndColon(
const char *zPathname
){
- return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' );
+ return ( tdsqlite3Isalpha(zPathname[0]) && zPathname[1]==':' );
}
/*
@@ -46330,7 +51233,7 @@ static BOOL winIsVerbatimPathname(
** bytes in size.
*/
static int winFullPathname(
- sqlite3_vfs *pVfs, /* Pointer to vfs object */
+ tdsqlite3_vfs *pVfs, /* Pointer to vfs object */
const char *zRelative, /* Possibly relative input path */
int nFull, /* Size of output buffer in bytes */
char *zFull /* Output buffer */
@@ -46352,54 +51255,54 @@ static int winFullPathname(
SimulateIOError( return SQLITE_ERROR );
UNUSED_PARAMETER(nFull);
assert( nFull>=pVfs->mxPathname );
- if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
+ if ( tdsqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
/*
** NOTE: We are dealing with a relative path name and the data
** directory has been set. Therefore, use it as the basis
** for converting the relative path name to an absolute
** one by prepending the data directory and a slash.
*/
- char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 );
+ char *zOut = tdsqlite3MallocZero( pVfs->mxPathname+1 );
if( !zOut ){
return SQLITE_IOERR_NOMEM_BKPT;
}
if( cygwin_conv_path(
(osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A) |
CCP_RELATIVE, zRelative, zOut, pVfs->mxPathname+1)<0 ){
- sqlite3_free(zOut);
+ tdsqlite3_free(zOut);
return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno,
"winFullPathname1", zRelative);
}else{
char *zUtf8 = winConvertToUtf8Filename(zOut);
if( !zUtf8 ){
- sqlite3_free(zOut);
+ tdsqlite3_free(zOut);
return SQLITE_IOERR_NOMEM_BKPT;
}
- sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
- sqlite3_data_directory, winGetDirSep(), zUtf8);
- sqlite3_free(zUtf8);
- sqlite3_free(zOut);
+ tdsqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
+ tdsqlite3_data_directory, winGetDirSep(), zUtf8);
+ tdsqlite3_free(zUtf8);
+ tdsqlite3_free(zOut);
}
}else{
- char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 );
+ char *zOut = tdsqlite3MallocZero( pVfs->mxPathname+1 );
if( !zOut ){
return SQLITE_IOERR_NOMEM_BKPT;
}
if( cygwin_conv_path(
(osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A),
zRelative, zOut, pVfs->mxPathname+1)<0 ){
- sqlite3_free(zOut);
+ tdsqlite3_free(zOut);
return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno,
"winFullPathname2", zRelative);
}else{
char *zUtf8 = winConvertToUtf8Filename(zOut);
if( !zUtf8 ){
- sqlite3_free(zOut);
+ tdsqlite3_free(zOut);
return SQLITE_IOERR_NOMEM_BKPT;
}
- sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8);
- sqlite3_free(zUtf8);
- sqlite3_free(zOut);
+ tdsqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8);
+ tdsqlite3_free(zUtf8);
+ tdsqlite3_free(zOut);
}
}
return SQLITE_OK;
@@ -46409,17 +51312,17 @@ static int winFullPathname(
SimulateIOError( return SQLITE_ERROR );
/* WinCE has no concept of a relative pathname, or so I am told. */
/* WinRT has no way to convert a relative path to an absolute one. */
- if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
+ if ( tdsqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
/*
** NOTE: We are dealing with a relative path name and the data
** directory has been set. Therefore, use it as the basis
** for converting the relative path name to an absolute
** one by prepending the data directory and a backslash.
*/
- sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
- sqlite3_data_directory, winGetDirSep(), zRelative);
+ tdsqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
+ tdsqlite3_data_directory, winGetDirSep(), zRelative);
}else{
- sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative);
+ tdsqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zRelative);
}
return SQLITE_OK;
#endif
@@ -46431,15 +51334,15 @@ static int winFullPathname(
** current working directory has been unlinked.
*/
SimulateIOError( return SQLITE_ERROR );
- if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
+ if ( tdsqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
/*
** NOTE: We are dealing with a relative path name and the data
** directory has been set. Therefore, use it as the basis
** for converting the relative path name to an absolute
** one by prepending the data directory and a backslash.
*/
- sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
- sqlite3_data_directory, winGetDirSep(), zRelative);
+ tdsqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s",
+ tdsqlite3_data_directory, winGetDirSep(), zRelative);
return SQLITE_OK;
}
zConverted = winConvertFromUtf8Filename(zRelative);
@@ -46450,57 +51353,57 @@ static int winFullPathname(
LPWSTR zTemp;
nByte = osGetFullPathNameW((LPCWSTR)zConverted, 0, 0, 0);
if( nByte==0 ){
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
"winFullPathname1", zRelative);
}
nByte += 3;
- zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) );
+ zTemp = tdsqlite3MallocZero( nByte*sizeof(zTemp[0]) );
if( zTemp==0 ){
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
return SQLITE_IOERR_NOMEM_BKPT;
}
nByte = osGetFullPathNameW((LPCWSTR)zConverted, nByte, zTemp, 0);
if( nByte==0 ){
- sqlite3_free(zConverted);
- sqlite3_free(zTemp);
+ tdsqlite3_free(zConverted);
+ tdsqlite3_free(zTemp);
return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
"winFullPathname2", zRelative);
}
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
zOut = winUnicodeToUtf8(zTemp);
- sqlite3_free(zTemp);
+ tdsqlite3_free(zTemp);
}
#ifdef SQLITE_WIN32_HAS_ANSI
else{
char *zTemp;
nByte = osGetFullPathNameA((char*)zConverted, 0, 0, 0);
if( nByte==0 ){
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
"winFullPathname3", zRelative);
}
nByte += 3;
- zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) );
+ zTemp = tdsqlite3MallocZero( nByte*sizeof(zTemp[0]) );
if( zTemp==0 ){
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
return SQLITE_IOERR_NOMEM_BKPT;
}
nByte = osGetFullPathNameA((char*)zConverted, nByte, zTemp, 0);
if( nByte==0 ){
- sqlite3_free(zConverted);
- sqlite3_free(zTemp);
+ tdsqlite3_free(zConverted);
+ tdsqlite3_free(zTemp);
return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(),
"winFullPathname4", zRelative);
}
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI());
- sqlite3_free(zTemp);
+ tdsqlite3_free(zTemp);
}
#endif
if( zOut ){
- sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut);
- sqlite3_free(zOut);
+ tdsqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut);
+ tdsqlite3_free(zOut);
return SQLITE_OK;
}else{
return SQLITE_IOERR_NOMEM_BKPT;
@@ -46513,23 +51416,23 @@ static int winFullPathname(
** Interfaces for opening a shared library, finding entry points
** within the shared library, and closing the shared library.
*/
-static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){
+static void *winDlOpen(tdsqlite3_vfs *pVfs, const char *zFilename){
HANDLE h;
#if defined(__CYGWIN__)
int nFull = pVfs->mxPathname+1;
- char *zFull = sqlite3MallocZero( nFull );
+ char *zFull = tdsqlite3MallocZero( nFull );
void *zConverted = 0;
if( zFull==0 ){
OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0));
return 0;
}
if( winFullPathname(pVfs, zFilename, nFull, zFull)!=SQLITE_OK ){
- sqlite3_free(zFull);
+ tdsqlite3_free(zFull);
OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0));
return 0;
}
zConverted = winConvertFromUtf8Filename(zFull);
- sqlite3_free(zFull);
+ tdsqlite3_free(zFull);
#else
void *zConverted = winConvertFromUtf8Filename(zFilename);
UNUSED_PARAMETER(pVfs);
@@ -46551,14 +51454,14 @@ static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){
}
#endif
OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)h));
- sqlite3_free(zConverted);
+ tdsqlite3_free(zConverted);
return (void*)h;
}
-static void winDlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){
+static void winDlError(tdsqlite3_vfs *pVfs, int nBuf, char *zBufOut){
UNUSED_PARAMETER(pVfs);
winGetLastErrorMsg(osGetLastError(), nBuf, zBufOut);
}
-static void (*winDlSym(sqlite3_vfs *pVfs,void *pH,const char *zSym))(void){
+static void (*winDlSym(tdsqlite3_vfs *pVfs,void *pH,const char *zSym))(void){
FARPROC proc;
UNUSED_PARAMETER(pVfs);
proc = osGetProcAddressA((HANDLE)pH, zSym);
@@ -46566,7 +51469,7 @@ static void (*winDlSym(sqlite3_vfs *pVfs,void *pH,const char *zSym))(void){
(void*)pH, zSym, (void*)proc));
return (void(*)(void))proc;
}
-static void winDlClose(sqlite3_vfs *pVfs, void *pHandle){
+static void winDlClose(tdsqlite3_vfs *pVfs, void *pHandle){
UNUSED_PARAMETER(pVfs);
osFreeLibrary((HANDLE)pHandle);
OSTRACE(("DLCLOSE handle=%p\n", (void*)pHandle));
@@ -46603,7 +51506,7 @@ static void xorMemory(EntropyGatherer *p, unsigned char *x, int sz){
/*
** Write up to nBuf bytes of randomness into zBuf.
*/
-static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
+static int winRandomness(tdsqlite3_vfs *pVfs, int nBuf, char *zBuf){
#if defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS)
UNUSED_PARAMETER(pVfs);
memset(zBuf, 0, nBuf);
@@ -46612,9 +51515,6 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
EntropyGatherer e;
UNUSED_PARAMETER(pVfs);
memset(zBuf, 0, nBuf);
-#if defined(_MSC_VER) && _MSC_VER>=1400 && !SQLITE_OS_WINCE
- rand_s((unsigned int*)zBuf); /* rand_s() is not available with MinGW */
-#endif /* defined(_MSC_VER) && _MSC_VER>=1400 */
e.a = (unsigned char*)zBuf;
e.na = nBuf;
e.nXor = 0;
@@ -46663,8 +51563,8 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
/*
** Sleep for a little while. Return the amount of time slept.
*/
-static int winSleep(sqlite3_vfs *pVfs, int microsec){
- sqlite3_win32_sleep((microsec+999)/1000);
+static int winSleep(tdsqlite3_vfs *pVfs, int microsec){
+ tdsqlite3_win32_sleep((microsec+999)/1000);
UNUSED_PARAMETER(pVfs);
return ((microsec+999)/1000)*1000;
}
@@ -46672,10 +51572,10 @@ static int winSleep(sqlite3_vfs *pVfs, int microsec){
/*
** The following variable, if set to a non-zero value, is interpreted as
** the number of seconds since 1970 and is used to set the result of
-** sqlite3OsCurrentTime() during testing.
+** tdsqlite3OsCurrentTime() during testing.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_current_time = 0; /* Fake system time in seconds since 1970. */
+SQLITE_API int tdsqlite3_current_time = 0; /* Fake system time in seconds since 1970. */
#endif
/*
@@ -46688,19 +51588,19 @@ SQLITE_API int sqlite3_current_time = 0; /* Fake system time in seconds since 1
** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date
** cannot be found.
*/
-static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){
+static int winCurrentTimeInt64(tdsqlite3_vfs *pVfs, tdsqlite3_int64 *piNow){
/* FILETIME structure is a 64-bit value representing the number of
100-nanosecond intervals since January 1, 1601 (= JD 2305813.5).
*/
FILETIME ft;
- static const sqlite3_int64 winFiletimeEpoch = 23058135*(sqlite3_int64)8640000;
+ static const tdsqlite3_int64 winFiletimeEpoch = 23058135*(tdsqlite3_int64)8640000;
#ifdef SQLITE_TEST
- static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000;
+ static const tdsqlite3_int64 unixEpoch = 24405875*(tdsqlite3_int64)8640000;
#endif
/* 2^32 - to avoid use of LL and warnings in gcc */
- static const sqlite3_int64 max32BitValue =
- (sqlite3_int64)2000000000 + (sqlite3_int64)2000000000 +
- (sqlite3_int64)294967296;
+ static const tdsqlite3_int64 max32BitValue =
+ (tdsqlite3_int64)2000000000 + (tdsqlite3_int64)2000000000 +
+ (tdsqlite3_int64)294967296;
#if SQLITE_OS_WINCE
SYSTEMTIME time;
@@ -46714,12 +51614,12 @@ static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){
#endif
*piNow = winFiletimeEpoch +
- ((((sqlite3_int64)ft.dwHighDateTime)*max32BitValue) +
- (sqlite3_int64)ft.dwLowDateTime)/(sqlite3_int64)10000;
+ ((((tdsqlite3_int64)ft.dwHighDateTime)*max32BitValue) +
+ (tdsqlite3_int64)ft.dwLowDateTime)/(tdsqlite3_int64)10000;
#ifdef SQLITE_TEST
- if( sqlite3_current_time ){
- *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch;
+ if( tdsqlite3_current_time ){
+ *piNow = 1000*(tdsqlite3_int64)tdsqlite3_current_time + unixEpoch;
}
#endif
UNUSED_PARAMETER(pVfs);
@@ -46731,9 +51631,9 @@ static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){
** current time and date as a Julian Day number into *prNow and
** return 0. Return 1 if the time and date cannot be found.
*/
-static int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){
+static int winCurrentTime(tdsqlite3_vfs *pVfs, double *prNow){
int rc;
- sqlite3_int64 i;
+ tdsqlite3_int64 i;
rc = winCurrentTimeInt64(pVfs, &i);
if( !rc ){
*prNow = i/86400000.0;
@@ -46762,16 +51662,16 @@ static int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){
** on SQLite. It is fine to have an implementation that never
** returns an error message:
**
-** int xGetLastError(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
+** int xGetLastError(tdsqlite3_vfs *pVfs, int nBuf, char *zBuf){
** assert(zBuf[0]=='\0');
** return 0;
** }
**
** However if an error message is supplied, it will be incorporated
** by sqlite into the error message available to the user using
-** sqlite3_errmsg(), possibly making IO errors easier to debug.
+** tdsqlite3_errmsg(), possibly making IO errors easier to debug.
*/
-static int winGetLastError(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
+static int winGetLastError(tdsqlite3_vfs *pVfs, int nBuf, char *zBuf){
DWORD e = osGetLastError();
UNUSED_PARAMETER(pVfs);
if( nBuf>0 ) winGetLastErrorMsg(e, nBuf, zBuf);
@@ -46781,8 +51681,8 @@ static int winGetLastError(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
/*
** Initialize and deinitialize the operating system interface.
*/
-SQLITE_API int sqlite3_os_init(void){
- static sqlite3_vfs winVfs = {
+SQLITE_API int tdsqlite3_os_init(void){
+ static tdsqlite3_vfs winVfs = {
3, /* iVersion */
sizeof(winFile), /* szOsFile */
SQLITE_WIN32_MAX_PATH_BYTES, /* mxPathname */
@@ -46807,7 +51707,7 @@ SQLITE_API int sqlite3_os_init(void){
winNextSystemCall, /* xNextSystemCall */
};
#if defined(SQLITE_WIN32_HAS_WIDE)
- static sqlite3_vfs winLongPathVfs = {
+ static tdsqlite3_vfs winLongPathVfs = {
3, /* iVersion */
sizeof(winFile), /* szOsFile */
SQLITE_WINNT_MAX_PATH_BYTES, /* mxPathname */
@@ -46832,7 +51732,7 @@ SQLITE_API int sqlite3_os_init(void){
winNextSystemCall, /* xNextSystemCall */
};
#endif
- static sqlite3_vfs winNolockVfs = {
+ static tdsqlite3_vfs winNolockVfs = {
3, /* iVersion */
sizeof(winFile), /* szOsFile */
SQLITE_WIN32_MAX_PATH_BYTES, /* mxPathname */
@@ -46857,7 +51757,7 @@ SQLITE_API int sqlite3_os_init(void){
winNextSystemCall, /* xNextSystemCall */
};
#if defined(SQLITE_WIN32_HAS_WIDE)
- static sqlite3_vfs winLongPathNolockVfs = {
+ static tdsqlite3_vfs winLongPathNolockVfs = {
3, /* iVersion */
sizeof(winFile), /* szOsFile */
SQLITE_WINNT_MAX_PATH_BYTES, /* mxPathname */
@@ -46897,34 +51797,669 @@ SQLITE_API int sqlite3_os_init(void){
assert( winSysInfo.dwAllocationGranularity>0 );
assert( winSysInfo.dwPageSize>0 );
- sqlite3_vfs_register(&winVfs, 1);
+ tdsqlite3_vfs_register(&winVfs, 1);
#if defined(SQLITE_WIN32_HAS_WIDE)
- sqlite3_vfs_register(&winLongPathVfs, 0);
+ tdsqlite3_vfs_register(&winLongPathVfs, 0);
#endif
- sqlite3_vfs_register(&winNolockVfs, 0);
+ tdsqlite3_vfs_register(&winNolockVfs, 0);
#if defined(SQLITE_WIN32_HAS_WIDE)
- sqlite3_vfs_register(&winLongPathNolockVfs, 0);
+ tdsqlite3_vfs_register(&winLongPathNolockVfs, 0);
+#endif
+
+#ifndef SQLITE_OMIT_WAL
+ winBigLock = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1);
#endif
return SQLITE_OK;
}
-SQLITE_API int sqlite3_os_end(void){
+SQLITE_API int tdsqlite3_os_end(void){
#if SQLITE_OS_WINRT
if( sleepObj!=NULL ){
osCloseHandle(sleepObj);
sleepObj = NULL;
}
#endif
+
+#ifndef SQLITE_OMIT_WAL
+ winBigLock = 0;
+#endif
+
return SQLITE_OK;
}
#endif /* SQLITE_OS_WIN */
/************** End of os_win.c **********************************************/
+/************** Begin file memdb.c *******************************************/
+/*
+** 2016-09-07
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file implements an in-memory VFS. A database is held as a contiguous
+** block of memory.
+**
+** This file also implements interface tdsqlite3_serialize() and
+** tdsqlite3_deserialize().
+*/
+/* #include "sqliteInt.h" */
+#ifdef SQLITE_ENABLE_DESERIALIZE
+
+/*
+** Forward declaration of objects used by this utility
+*/
+typedef struct tdsqlite3_vfs MemVfs;
+typedef struct MemFile MemFile;
+
+/* Access to a lower-level VFS that (might) implement dynamic loading,
+** access to randomness, etc.
+*/
+#define ORIGVFS(p) ((tdsqlite3_vfs*)((p)->pAppData))
+
+/* An open file */
+struct MemFile {
+ tdsqlite3_file base; /* IO methods */
+ tdsqlite3_int64 sz; /* Size of the file */
+ tdsqlite3_int64 szAlloc; /* Space allocated to aData */
+ tdsqlite3_int64 szMax; /* Maximum allowed size of the file */
+ unsigned char *aData; /* content of the file */
+ int nMmap; /* Number of memory mapped pages */
+ unsigned mFlags; /* Flags */
+ int eLock; /* Most recent lock against this file */
+};
+
+/*
+** Methods for MemFile
+*/
+static int memdbClose(tdsqlite3_file*);
+static int memdbRead(tdsqlite3_file*, void*, int iAmt, tdsqlite3_int64 iOfst);
+static int memdbWrite(tdsqlite3_file*,const void*,int iAmt, tdsqlite3_int64 iOfst);
+static int memdbTruncate(tdsqlite3_file*, tdsqlite3_int64 size);
+static int memdbSync(tdsqlite3_file*, int flags);
+static int memdbFileSize(tdsqlite3_file*, tdsqlite3_int64 *pSize);
+static int memdbLock(tdsqlite3_file*, int);
+/* static int memdbCheckReservedLock(tdsqlite3_file*, int *pResOut);// not used */
+static int memdbFileControl(tdsqlite3_file*, int op, void *pArg);
+/* static int memdbSectorSize(tdsqlite3_file*); // not used */
+static int memdbDeviceCharacteristics(tdsqlite3_file*);
+static int memdbFetch(tdsqlite3_file*, tdsqlite3_int64 iOfst, int iAmt, void **pp);
+static int memdbUnfetch(tdsqlite3_file*, tdsqlite3_int64 iOfst, void *p);
+
+/*
+** Methods for MemVfs
+*/
+static int memdbOpen(tdsqlite3_vfs*, const char *, tdsqlite3_file*, int , int *);
+/* static int memdbDelete(tdsqlite3_vfs*, const char *zName, int syncDir); */
+static int memdbAccess(tdsqlite3_vfs*, const char *zName, int flags, int *);
+static int memdbFullPathname(tdsqlite3_vfs*, const char *zName, int, char *zOut);
+static void *memdbDlOpen(tdsqlite3_vfs*, const char *zFilename);
+static void memdbDlError(tdsqlite3_vfs*, int nByte, char *zErrMsg);
+static void (*memdbDlSym(tdsqlite3_vfs *pVfs, void *p, const char*zSym))(void);
+static void memdbDlClose(tdsqlite3_vfs*, void*);
+static int memdbRandomness(tdsqlite3_vfs*, int nByte, char *zOut);
+static int memdbSleep(tdsqlite3_vfs*, int microseconds);
+/* static int memdbCurrentTime(tdsqlite3_vfs*, double*); */
+static int memdbGetLastError(tdsqlite3_vfs*, int, char *);
+static int memdbCurrentTimeInt64(tdsqlite3_vfs*, tdsqlite3_int64*);
+
+static tdsqlite3_vfs memdb_vfs = {
+ 2, /* iVersion */
+ 0, /* szOsFile (set when registered) */
+ 1024, /* mxPathname */
+ 0, /* pNext */
+ "memdb", /* zName */
+ 0, /* pAppData (set when registered) */
+ memdbOpen, /* xOpen */
+ 0, /* memdbDelete, */ /* xDelete */
+ memdbAccess, /* xAccess */
+ memdbFullPathname, /* xFullPathname */
+ memdbDlOpen, /* xDlOpen */
+ memdbDlError, /* xDlError */
+ memdbDlSym, /* xDlSym */
+ memdbDlClose, /* xDlClose */
+ memdbRandomness, /* xRandomness */
+ memdbSleep, /* xSleep */
+ 0, /* memdbCurrentTime, */ /* xCurrentTime */
+ memdbGetLastError, /* xGetLastError */
+ memdbCurrentTimeInt64 /* xCurrentTimeInt64 */
+};
+
+static const tdsqlite3_io_methods memdb_io_methods = {
+ 3, /* iVersion */
+ memdbClose, /* xClose */
+ memdbRead, /* xRead */
+ memdbWrite, /* xWrite */
+ memdbTruncate, /* xTruncate */
+ memdbSync, /* xSync */
+ memdbFileSize, /* xFileSize */
+ memdbLock, /* xLock */
+ memdbLock, /* xUnlock - same as xLock in this case */
+ 0, /* memdbCheckReservedLock, */ /* xCheckReservedLock */
+ memdbFileControl, /* xFileControl */
+ 0, /* memdbSectorSize,*/ /* xSectorSize */
+ memdbDeviceCharacteristics, /* xDeviceCharacteristics */
+ 0, /* xShmMap */
+ 0, /* xShmLock */
+ 0, /* xShmBarrier */
+ 0, /* xShmUnmap */
+ memdbFetch, /* xFetch */
+ memdbUnfetch /* xUnfetch */
+};
+
+
+
+/*
+** Close an memdb-file.
+**
+** The pData pointer is owned by the application, so there is nothing
+** to free.
+*/
+static int memdbClose(tdsqlite3_file *pFile){
+ MemFile *p = (MemFile *)pFile;
+ if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ) tdsqlite3_free(p->aData);
+ return SQLITE_OK;
+}
+
+/*
+** Read data from an memdb-file.
+*/
+static int memdbRead(
+ tdsqlite3_file *pFile,
+ void *zBuf,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ MemFile *p = (MemFile *)pFile;
+ if( iOfst+iAmt>p->sz ){
+ memset(zBuf, 0, iAmt);
+ if( iOfst<p->sz ) memcpy(zBuf, p->aData+iOfst, p->sz - iOfst);
+ return SQLITE_IOERR_SHORT_READ;
+ }
+ memcpy(zBuf, p->aData+iOfst, iAmt);
+ return SQLITE_OK;
+}
+
+/*
+** Try to enlarge the memory allocation to hold at least sz bytes
+*/
+static int memdbEnlarge(MemFile *p, tdsqlite3_int64 newSz){
+ unsigned char *pNew;
+ if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || p->nMmap>0 ){
+ return SQLITE_FULL;
+ }
+ if( newSz>p->szMax ){
+ return SQLITE_FULL;
+ }
+ newSz *= 2;
+ if( newSz>p->szMax ) newSz = p->szMax;
+ pNew = tdsqlite3_realloc64(p->aData, newSz);
+ if( pNew==0 ) return SQLITE_NOMEM;
+ p->aData = pNew;
+ p->szAlloc = newSz;
+ return SQLITE_OK;
+}
+
+/*
+** Write data to an memdb-file.
+*/
+static int memdbWrite(
+ tdsqlite3_file *pFile,
+ const void *z,
+ int iAmt,
+ sqlite_int64 iOfst
+){
+ MemFile *p = (MemFile *)pFile;
+ if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ) return SQLITE_READONLY;
+ if( iOfst+iAmt>p->sz ){
+ int rc;
+ if( iOfst+iAmt>p->szAlloc
+ && (rc = memdbEnlarge(p, iOfst+iAmt))!=SQLITE_OK
+ ){
+ return rc;
+ }
+ if( iOfst>p->sz ) memset(p->aData+p->sz, 0, iOfst-p->sz);
+ p->sz = iOfst+iAmt;
+ }
+ memcpy(p->aData+iOfst, z, iAmt);
+ return SQLITE_OK;
+}
+
+/*
+** Truncate an memdb-file.
+**
+** In rollback mode (which is always the case for memdb, as it does not
+** support WAL mode) the truncate() method is only used to reduce
+** the size of a file, never to increase the size.
+*/
+static int memdbTruncate(tdsqlite3_file *pFile, sqlite_int64 size){
+ MemFile *p = (MemFile *)pFile;
+ if( NEVER(size>p->sz) ) return SQLITE_FULL;
+ p->sz = size;
+ return SQLITE_OK;
+}
+
+/*
+** Sync an memdb-file.
+*/
+static int memdbSync(tdsqlite3_file *pFile, int flags){
+ return SQLITE_OK;
+}
+
+/*
+** Return the current file-size of an memdb-file.
+*/
+static int memdbFileSize(tdsqlite3_file *pFile, sqlite_int64 *pSize){
+ MemFile *p = (MemFile *)pFile;
+ *pSize = p->sz;
+ return SQLITE_OK;
+}
+
+/*
+** Lock an memdb-file.
+*/
+static int memdbLock(tdsqlite3_file *pFile, int eLock){
+ MemFile *p = (MemFile *)pFile;
+ if( eLock>SQLITE_LOCK_SHARED
+ && (p->mFlags & SQLITE_DESERIALIZE_READONLY)!=0
+ ){
+ return SQLITE_READONLY;
+ }
+ p->eLock = eLock;
+ return SQLITE_OK;
+}
+
+#if 0 /* Never used because memdbAccess() always returns false */
+/*
+** Check if another file-handle holds a RESERVED lock on an memdb-file.
+*/
+static int memdbCheckReservedLock(tdsqlite3_file *pFile, int *pResOut){
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+#endif
+
+/*
+** File control method. For custom operations on an memdb-file.
+*/
+static int memdbFileControl(tdsqlite3_file *pFile, int op, void *pArg){
+ MemFile *p = (MemFile *)pFile;
+ int rc = SQLITE_NOTFOUND;
+ if( op==SQLITE_FCNTL_VFSNAME ){
+ *(char**)pArg = tdsqlite3_mprintf("memdb(%p,%lld)", p->aData, p->sz);
+ rc = SQLITE_OK;
+ }
+ if( op==SQLITE_FCNTL_SIZE_LIMIT ){
+ tdsqlite3_int64 iLimit = *(tdsqlite3_int64*)pArg;
+ if( iLimit<p->sz ){
+ if( iLimit<0 ){
+ iLimit = p->szMax;
+ }else{
+ iLimit = p->sz;
+ }
+ }
+ p->szMax = iLimit;
+ *(tdsqlite3_int64*)pArg = iLimit;
+ rc = SQLITE_OK;
+ }
+ return rc;
+}
+
+#if 0 /* Not used because of SQLITE_IOCAP_POWERSAFE_OVERWRITE */
+/*
+** Return the sector-size in bytes for an memdb-file.
+*/
+static int memdbSectorSize(tdsqlite3_file *pFile){
+ return 1024;
+}
+#endif
+
+/*
+** Return the device characteristic flags supported by an memdb-file.
+*/
+static int memdbDeviceCharacteristics(tdsqlite3_file *pFile){
+ return SQLITE_IOCAP_ATOMIC |
+ SQLITE_IOCAP_POWERSAFE_OVERWRITE |
+ SQLITE_IOCAP_SAFE_APPEND |
+ SQLITE_IOCAP_SEQUENTIAL;
+}
+
+/* Fetch a page of a memory-mapped file */
+static int memdbFetch(
+ tdsqlite3_file *pFile,
+ tdsqlite3_int64 iOfst,
+ int iAmt,
+ void **pp
+){
+ MemFile *p = (MemFile *)pFile;
+ if( iOfst+iAmt>p->sz ){
+ *pp = 0;
+ }else{
+ p->nMmap++;
+ *pp = (void*)(p->aData + iOfst);
+ }
+ return SQLITE_OK;
+}
+
+/* Release a memory-mapped page */
+static int memdbUnfetch(tdsqlite3_file *pFile, tdsqlite3_int64 iOfst, void *pPage){
+ MemFile *p = (MemFile *)pFile;
+ p->nMmap--;
+ return SQLITE_OK;
+}
+
+/*
+** Open an mem file handle.
+*/
+static int memdbOpen(
+ tdsqlite3_vfs *pVfs,
+ const char *zName,
+ tdsqlite3_file *pFile,
+ int flags,
+ int *pOutFlags
+){
+ MemFile *p = (MemFile*)pFile;
+ if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){
+ return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFile, flags, pOutFlags);
+ }
+ memset(p, 0, sizeof(*p));
+ p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE;
+ assert( pOutFlags!=0 ); /* True because flags==SQLITE_OPEN_MAIN_DB */
+ *pOutFlags = flags | SQLITE_OPEN_MEMORY;
+ p->base.pMethods = &memdb_io_methods;
+ p->szMax = tdsqlite3GlobalConfig.mxMemdbSize;
+ return SQLITE_OK;
+}
+
+#if 0 /* Only used to delete rollback journals, master journals, and WAL
+ ** files, none of which exist in memdb. So this routine is never used */
+/*
+** Delete the file located at zPath. If the dirSync argument is true,
+** ensure the file-system modifications are synced to disk before
+** returning.
+*/
+static int memdbDelete(tdsqlite3_vfs *pVfs, const char *zPath, int dirSync){
+ return SQLITE_IOERR_DELETE;
+}
+#endif
+
+/*
+** Test for access permissions. Return true if the requested permission
+** is available, or false otherwise.
+**
+** With memdb, no files ever exist on disk. So always return false.
+*/
+static int memdbAccess(
+ tdsqlite3_vfs *pVfs,
+ const char *zPath,
+ int flags,
+ int *pResOut
+){
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+
+/*
+** Populate buffer zOut with the full canonical pathname corresponding
+** to the pathname in zPath. zOut is guaranteed to point to a buffer
+** of at least (INST_MAX_PATHNAME+1) bytes.
+*/
+static int memdbFullPathname(
+ tdsqlite3_vfs *pVfs,
+ const char *zPath,
+ int nOut,
+ char *zOut
+){
+ tdsqlite3_snprintf(nOut, zOut, "%s", zPath);
+ return SQLITE_OK;
+}
+
+/*
+** Open the dynamic library located at zPath and return a handle.
+*/
+static void *memdbDlOpen(tdsqlite3_vfs *pVfs, const char *zPath){
+ return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath);
+}
+
+/*
+** Populate the buffer zErrMsg (size nByte bytes) with a human readable
+** utf-8 string describing the most recent error encountered associated
+** with dynamic libraries.
+*/
+static void memdbDlError(tdsqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+ ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg);
+}
+
+/*
+** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
+*/
+static void (*memdbDlSym(tdsqlite3_vfs *pVfs, void *p, const char *zSym))(void){
+ return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym);
+}
+
+/*
+** Close the dynamic library handle pHandle.
+*/
+static void memdbDlClose(tdsqlite3_vfs *pVfs, void *pHandle){
+ ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle);
+}
+
+/*
+** Populate the buffer pointed to by zBufOut with nByte bytes of
+** random data.
+*/
+static int memdbRandomness(tdsqlite3_vfs *pVfs, int nByte, char *zBufOut){
+ return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut);
+}
+
+/*
+** Sleep for nMicro microseconds. Return the number of microseconds
+** actually slept.
+*/
+static int memdbSleep(tdsqlite3_vfs *pVfs, int nMicro){
+ return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro);
+}
+
+#if 0 /* Never used. Modern cores only call xCurrentTimeInt64() */
+/*
+** Return the current time as a Julian Day number in *pTimeOut.
+*/
+static int memdbCurrentTime(tdsqlite3_vfs *pVfs, double *pTimeOut){
+ return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut);
+}
+#endif
+
+static int memdbGetLastError(tdsqlite3_vfs *pVfs, int a, char *b){
+ return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b);
+}
+static int memdbCurrentTimeInt64(tdsqlite3_vfs *pVfs, tdsqlite3_int64 *p){
+ return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p);
+}
+
+/*
+** Translate a database connection pointer and schema name into a
+** MemFile pointer.
+*/
+static MemFile *memdbFromDbSchema(tdsqlite3 *db, const char *zSchema){
+ MemFile *p = 0;
+ int rc = tdsqlite3_file_control(db, zSchema, SQLITE_FCNTL_FILE_POINTER, &p);
+ if( rc ) return 0;
+ if( p->base.pMethods!=&memdb_io_methods ) return 0;
+ return p;
+}
+
+/*
+** Return the serialization of a database
+*/
+SQLITE_API unsigned char *tdsqlite3_serialize(
+ tdsqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which database within the connection */
+ tdsqlite3_int64 *piSize, /* Write size here, if not NULL */
+ unsigned int mFlags /* Maybe SQLITE_SERIALIZE_NOCOPY */
+){
+ MemFile *p;
+ int iDb;
+ Btree *pBt;
+ tdsqlite3_int64 sz;
+ int szPage = 0;
+ tdsqlite3_stmt *pStmt = 0;
+ unsigned char *pOut;
+ char *zSql;
+ int rc;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !tdsqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+
+ if( zSchema==0 ) zSchema = db->aDb[0].zDbSName;
+ p = memdbFromDbSchema(db, zSchema);
+ iDb = tdsqlite3FindDbName(db, zSchema);
+ if( piSize ) *piSize = -1;
+ if( iDb<0 ) return 0;
+ if( p ){
+ if( piSize ) *piSize = p->sz;
+ if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
+ pOut = p->aData;
+ }else{
+ pOut = tdsqlite3_malloc64( p->sz );
+ if( pOut ) memcpy(pOut, p->aData, p->sz);
+ }
+ return pOut;
+ }
+ pBt = db->aDb[iDb].pBt;
+ if( pBt==0 ) return 0;
+ szPage = tdsqlite3BtreeGetPageSize(pBt);
+ zSql = tdsqlite3_mprintf("PRAGMA \"%w\".page_count", zSchema);
+ rc = zSql ? tdsqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) : SQLITE_NOMEM;
+ tdsqlite3_free(zSql);
+ if( rc ) return 0;
+ rc = tdsqlite3_step(pStmt);
+ if( rc!=SQLITE_ROW ){
+ pOut = 0;
+ }else{
+ sz = tdsqlite3_column_int64(pStmt, 0)*szPage;
+ if( piSize ) *piSize = sz;
+ if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
+ pOut = 0;
+ }else{
+ pOut = tdsqlite3_malloc64( sz );
+ if( pOut ){
+ int nPage = tdsqlite3_column_int(pStmt, 0);
+ Pager *pPager = tdsqlite3BtreePager(pBt);
+ int pgno;
+ for(pgno=1; pgno<=nPage; pgno++){
+ DbPage *pPage = 0;
+ unsigned char *pTo = pOut + szPage*(tdsqlite3_int64)(pgno-1);
+ rc = tdsqlite3PagerGet(pPager, pgno, (DbPage**)&pPage, 0);
+ if( rc==SQLITE_OK ){
+ memcpy(pTo, tdsqlite3PagerGetData(pPage), szPage);
+ }else{
+ memset(pTo, 0, szPage);
+ }
+ tdsqlite3PagerUnref(pPage);
+ }
+ }
+ }
+ }
+ tdsqlite3_finalize(pStmt);
+ return pOut;
+}
+
+/* Convert zSchema to a MemDB and initialize its content.
+*/
+SQLITE_API int tdsqlite3_deserialize(
+ tdsqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which DB to reopen with the deserialization */
+ unsigned char *pData, /* The serialized database content */
+ tdsqlite3_int64 szDb, /* Number bytes in the deserialization */
+ tdsqlite3_int64 szBuf, /* Total size of buffer pData[] */
+ unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
+){
+ MemFile *p;
+ char *zSql;
+ tdsqlite3_stmt *pStmt = 0;
+ int rc;
+ int iDb;
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !tdsqlite3SafetyCheckOk(db) ){
+ return SQLITE_MISUSE_BKPT;
+ }
+ if( szDb<0 ) return SQLITE_MISUSE_BKPT;
+ if( szBuf<0 ) return SQLITE_MISUSE_BKPT;
+#endif
+
+ tdsqlite3_mutex_enter(db->mutex);
+ if( zSchema==0 ) zSchema = db->aDb[0].zDbSName;
+ iDb = tdsqlite3FindDbName(db, zSchema);
+ if( iDb<0 ){
+ rc = SQLITE_ERROR;
+ goto end_deserialize;
+ }
+ zSql = tdsqlite3_mprintf("ATTACH x AS %Q", zSchema);
+ rc = tdsqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ tdsqlite3_free(zSql);
+ if( rc ) goto end_deserialize;
+ db->init.iDb = (u8)iDb;
+ db->init.reopenMemdb = 1;
+ rc = tdsqlite3_step(pStmt);
+ db->init.reopenMemdb = 0;
+ if( rc!=SQLITE_DONE ){
+ rc = SQLITE_ERROR;
+ goto end_deserialize;
+ }
+ p = memdbFromDbSchema(db, zSchema);
+ if( p==0 ){
+ rc = SQLITE_ERROR;
+ }else{
+ p->aData = pData;
+ p->sz = szDb;
+ p->szAlloc = szBuf;
+ p->szMax = szBuf;
+ if( p->szMax<tdsqlite3GlobalConfig.mxMemdbSize ){
+ p->szMax = tdsqlite3GlobalConfig.mxMemdbSize;
+ }
+ p->mFlags = mFlags;
+ rc = SQLITE_OK;
+ }
+
+end_deserialize:
+ tdsqlite3_finalize(pStmt);
+ tdsqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** This routine is called when the extension is loaded.
+** Register the new VFS.
+*/
+SQLITE_PRIVATE int tdsqlite3MemdbInit(void){
+ tdsqlite3_vfs *pLower = tdsqlite3_vfs_find(0);
+ int sz = pLower->szOsFile;
+ memdb_vfs.pAppData = pLower;
+ /* In all known configurations of SQLite, the size of a default
+ ** tdsqlite3_file is greater than the size of a memdb tdsqlite3_file.
+ ** Should that ever change, remove the following NEVER() */
+ if( NEVER(sz<sizeof(MemFile)) ) sz = sizeof(MemFile);
+ memdb_vfs.szOsFile = sz;
+ return tdsqlite3_vfs_register(&memdb_vfs, 0);
+}
+#endif /* SQLITE_ENABLE_DESERIALIZE */
+
+/************** End of memdb.c ***********************************************/
/************** Begin file bitvec.c ******************************************/
/*
** 2008 February 16
@@ -47040,10 +52575,10 @@ struct Bitvec {
** inclusive. Return a pointer to the new object. Return NULL if
** malloc fails.
*/
-SQLITE_PRIVATE Bitvec *sqlite3BitvecCreate(u32 iSize){
+SQLITE_PRIVATE Bitvec *tdsqlite3BitvecCreate(u32 iSize){
Bitvec *p;
assert( sizeof(*p)==BITVEC_SZ );
- p = sqlite3MallocZero( sizeof(*p) );
+ p = tdsqlite3MallocZero( sizeof(*p) );
if( p ){
p->iSize = iSize;
}
@@ -47055,7 +52590,7 @@ SQLITE_PRIVATE Bitvec *sqlite3BitvecCreate(u32 iSize){
** If p is NULL (if the bitmap has not been created) or if
** i is out of range, then return false.
*/
-SQLITE_PRIVATE int sqlite3BitvecTestNotNull(Bitvec *p, u32 i){
+SQLITE_PRIVATE int tdsqlite3BitvecTestNotNull(Bitvec *p, u32 i){
assert( p!=0 );
i--;
if( i>=p->iSize ) return 0;
@@ -47078,8 +52613,8 @@ SQLITE_PRIVATE int sqlite3BitvecTestNotNull(Bitvec *p, u32 i){
return 0;
}
}
-SQLITE_PRIVATE int sqlite3BitvecTest(Bitvec *p, u32 i){
- return p!=0 && sqlite3BitvecTestNotNull(p,i);
+SQLITE_PRIVATE int tdsqlite3BitvecTest(Bitvec *p, u32 i){
+ return p!=0 && tdsqlite3BitvecTestNotNull(p,i);
}
/*
@@ -47094,7 +52629,7 @@ SQLITE_PRIVATE int sqlite3BitvecTest(Bitvec *p, u32 i){
** and that the value for "i" is within range of the Bitvec object.
** Otherwise the behavior is undefined.
*/
-SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec *p, u32 i){
+SQLITE_PRIVATE int tdsqlite3BitvecSet(Bitvec *p, u32 i){
u32 h;
if( p==0 ) return SQLITE_OK;
assert( i>0 );
@@ -47104,7 +52639,7 @@ SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec *p, u32 i){
u32 bin = i/p->iDivisor;
i = i%p->iDivisor;
if( p->u.apSub[bin]==0 ){
- p->u.apSub[bin] = sqlite3BitvecCreate( p->iDivisor );
+ p->u.apSub[bin] = tdsqlite3BitvecCreate( p->iDivisor );
if( p->u.apSub[bin]==0 ) return SQLITE_NOMEM_BKPT;
}
p = p->u.apSub[bin];
@@ -47138,18 +52673,18 @@ bitvec_set_rehash:
if( p->nSet>=BITVEC_MXHASH ){
unsigned int j;
int rc;
- u32 *aiValues = sqlite3StackAllocRaw(0, sizeof(p->u.aHash));
+ u32 *aiValues = tdsqlite3StackAllocRaw(0, sizeof(p->u.aHash));
if( aiValues==0 ){
return SQLITE_NOMEM_BKPT;
}else{
memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash));
memset(p->u.apSub, 0, sizeof(p->u.apSub));
p->iDivisor = (p->iSize + BITVEC_NPTR - 1)/BITVEC_NPTR;
- rc = sqlite3BitvecSet(p, i);
+ rc = tdsqlite3BitvecSet(p, i);
for(j=0; j<BITVEC_NINT; j++){
- if( aiValues[j] ) rc |= sqlite3BitvecSet(p, aiValues[j]);
+ if( aiValues[j] ) rc |= tdsqlite3BitvecSet(p, aiValues[j]);
}
- sqlite3StackFree(0, aiValues);
+ tdsqlite3StackFree(0, aiValues);
return rc;
}
}
@@ -47165,7 +52700,7 @@ bitvec_set_end:
** pBuf must be a pointer to at least BITVEC_SZ bytes of temporary storage
** that BitvecClear can use to rebuilt its hash table.
*/
-SQLITE_PRIVATE void sqlite3BitvecClear(Bitvec *p, u32 i, void *pBuf){
+SQLITE_PRIVATE void tdsqlite3BitvecClear(Bitvec *p, u32 i, void *pBuf){
if( p==0 ) return;
assert( i>0 );
i--;
@@ -47202,26 +52737,26 @@ SQLITE_PRIVATE void sqlite3BitvecClear(Bitvec *p, u32 i, void *pBuf){
/*
** Destroy a bitmap object. Reclaim all memory used.
*/
-SQLITE_PRIVATE void sqlite3BitvecDestroy(Bitvec *p){
+SQLITE_PRIVATE void tdsqlite3BitvecDestroy(Bitvec *p){
if( p==0 ) return;
if( p->iDivisor ){
unsigned int i;
for(i=0; i<BITVEC_NPTR; i++){
- sqlite3BitvecDestroy(p->u.apSub[i]);
+ tdsqlite3BitvecDestroy(p->u.apSub[i]);
}
}
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
/*
** Return the value of the iSize parameter specified when Bitvec *p
** was created.
*/
-SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){
+SQLITE_PRIVATE u32 tdsqlite3BitvecSize(Bitvec *p){
return p->iSize;
}
-#ifndef SQLITE_OMIT_BUILTIN_TEST
+#ifndef SQLITE_UNTESTABLE
/*
** Let V[] be an array of unsigned characters sufficient to hold
** up to N bits. Let I be an integer between 0 and N. 0<=I<N.
@@ -47262,7 +52797,7 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){
**
** If a memory allocation error occurs, return -1.
*/
-SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
+SQLITE_PRIVATE int tdsqlite3BitvecBuiltinTest(int sz, int *aOp){
Bitvec *pBitvec = 0;
unsigned char *pV = 0;
int rc = -1;
@@ -47271,14 +52806,14 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
/* Allocate the Bitvec to be tested and a linear array of
** bits to act as the reference */
- pBitvec = sqlite3BitvecCreate( sz );
- pV = sqlite3MallocZero( (sz+7)/8 + 1 );
- pTmpSpace = sqlite3_malloc64(BITVEC_SZ);
+ pBitvec = tdsqlite3BitvecCreate( sz );
+ pV = tdsqlite3MallocZero( (sz+7)/8 + 1 );
+ pTmpSpace = tdsqlite3_malloc64(BITVEC_SZ);
if( pBitvec==0 || pV==0 || pTmpSpace==0 ) goto bitvec_end;
/* NULL pBitvec tests */
- sqlite3BitvecSet(0, 1);
- sqlite3BitvecClear(0, 1, pTmpSpace);
+ tdsqlite3BitvecSet(0, 1);
+ tdsqlite3BitvecClear(0, 1, pTmpSpace);
/* Run the program */
pc = 0;
@@ -47296,7 +52831,7 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
case 4:
default: {
nx = 2;
- sqlite3_randomness(sizeof(i), &i);
+ tdsqlite3_randomness(sizeof(i), &i);
break;
}
}
@@ -47306,11 +52841,11 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
if( (op & 1)!=0 ){
SETBIT(pV, (i+1));
if( op!=5 ){
- if( sqlite3BitvecSet(pBitvec, i+1) ) goto bitvec_end;
+ if( tdsqlite3BitvecSet(pBitvec, i+1) ) goto bitvec_end;
}
}else{
CLEARBIT(pV, (i+1));
- sqlite3BitvecClear(pBitvec, i+1, pTmpSpace);
+ tdsqlite3BitvecClear(pBitvec, i+1, pTmpSpace);
}
}
@@ -47319,11 +52854,11 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
** match (rc==0). Change rc to non-zero if a discrepancy
** is found.
*/
- rc = sqlite3BitvecTest(0,0) + sqlite3BitvecTest(pBitvec, sz+1)
- + sqlite3BitvecTest(pBitvec, 0)
- + (sqlite3BitvecSize(pBitvec) - sz);
+ rc = tdsqlite3BitvecTest(0,0) + tdsqlite3BitvecTest(pBitvec, sz+1)
+ + tdsqlite3BitvecTest(pBitvec, 0)
+ + (tdsqlite3BitvecSize(pBitvec) - sz);
for(i=1; i<=sz; i++){
- if( (TESTBIT(pV,i))!=sqlite3BitvecTest(pBitvec,i) ){
+ if( (TESTBIT(pV,i))!=tdsqlite3BitvecTest(pBitvec,i) ){
rc = i;
break;
}
@@ -47331,12 +52866,12 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){
/* Free allocated structure */
bitvec_end:
- sqlite3_free(pTmpSpace);
- sqlite3_free(pV);
- sqlite3BitvecDestroy(pBitvec);
+ tdsqlite3_free(pTmpSpace);
+ tdsqlite3_free(pV);
+ tdsqlite3BitvecDestroy(pBitvec);
return rc;
}
-#endif /* SQLITE_OMIT_BUILTIN_TEST */
+#endif /* SQLITE_UNTESTABLE */
/************** End of bitvec.c **********************************************/
/************** Begin file pcache.c ******************************************/
@@ -47374,7 +52909,7 @@ bitvec_end:
** The PCache.pSynced variable is used to optimize searching for a dirty
** page to eject from the cache mid-transaction. It is better to eject
** a page that does not require a journal sync than one that does.
-** Therefore, pSynced is maintained to that it *almost* always points
+** Therefore, pSynced is maintained so that it *almost* always points
** to either the oldest page in the pDirty/pDirtyTail list that has a
** clear PGHDR_NEED_SYNC flag or to a page that is older than this one
** (so that the right page to eject can be found by following pDirtyPrev
@@ -47392,7 +52927,7 @@ struct PCache {
u8 eCreate; /* eCreate value for for xFetch() */
int (*xStress)(void*,PgHdr*); /* Call to try make a page clean */
void *pStress; /* Argument to xStress */
- sqlite3_pcache *pCache; /* Pluggable cache module */
+ tdsqlite3_pcache *pCache; /* Pluggable cache module */
};
/********************************** Test and Debug Logic **********************/
@@ -47400,27 +52935,27 @@ struct PCache {
** Debug tracing macros. Enable by by changing the "0" to "1" and
** recompiling.
**
-** When sqlite3PcacheTrace is 1, single line trace messages are issued.
-** When sqlite3PcacheTrace is 2, a dump of the pcache showing all cache entries
+** When tdsqlite3PcacheTrace is 1, single line trace messages are issued.
+** When tdsqlite3PcacheTrace is 2, a dump of the pcache showing all cache entries
** is displayed for many operations, resulting in a lot of output.
*/
#if defined(SQLITE_DEBUG) && 0
- int sqlite3PcacheTrace = 2; /* 0: off 1: simple 2: cache dumps */
- int sqlite3PcacheMxDump = 9999; /* Max cache entries for pcacheDump() */
-# define pcacheTrace(X) if(sqlite3PcacheTrace){sqlite3DebugPrintf X;}
+ int tdsqlite3PcacheTrace = 2; /* 0: off 1: simple 2: cache dumps */
+ int tdsqlite3PcacheMxDump = 9999; /* Max cache entries for pcacheDump() */
+# define pcacheTrace(X) if(tdsqlite3PcacheTrace){tdsqlite3DebugPrintf X;}
void pcacheDump(PCache *pCache){
int N;
int i, j;
- sqlite3_pcache_page *pLower;
+ tdsqlite3_pcache_page *pLower;
PgHdr *pPg;
unsigned char *a;
- if( sqlite3PcacheTrace<2 ) return;
+ if( tdsqlite3PcacheTrace<2 ) return;
if( pCache->pCache==0 ) return;
- N = sqlite3PcachePagecount(pCache);
- if( N>sqlite3PcacheMxDump ) N = sqlite3PcacheMxDump;
+ N = tdsqlite3PcachePagecount(pCache);
+ if( N>tdsqlite3PcacheMxDump ) N = tdsqlite3PcacheMxDump;
for(i=1; i<=N; i++){
- pLower = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, i, 0);
+ pLower = tdsqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, i, 0);
if( pLower==0 ) continue;
pPg = (PgHdr*)pLower->pExtra;
printf("%3d: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags);
@@ -47428,7 +52963,7 @@ struct PCache {
for(j=0; j<12; j++) printf("%02x", a[j]);
printf("\n");
if( pPg->pPage==0 ){
- sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, pLower, 0);
+ tdsqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, pLower, 0);
}
}
}
@@ -47444,13 +52979,13 @@ struct PCache {
** This routine is for use inside of assert() statements only. For
** example:
**
-** assert( sqlite3PcachePageSanity(pPg) );
+** assert( tdsqlite3PcachePageSanity(pPg) );
*/
-#if SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr *pPg){
+#ifdef SQLITE_DEBUG
+SQLITE_PRIVATE int tdsqlite3PcachePageSanity(PgHdr *pPg){
PCache *pCache;
assert( pPg!=0 );
- assert( pPg->pgno>0 ); /* Page number is 1 or more */
+ assert( pPg->pgno>0 || pPg->pPager==0 ); /* Page number is 1 or more */
pCache = pPg->pCache;
assert( pCache!=0 ); /* Every page has an associated PCache */
if( pPg->flags & PGHDR_CLEAN ){
@@ -47463,7 +52998,7 @@ SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr *pPg){
assert( pPg->flags & PGHDR_DIRTY ); /* WRITEABLE implies DIRTY */
}
/* NEED_SYNC can be set independently of WRITEABLE. This can happen,
- ** for example, when using the sqlite3PagerDontWrite() optimization:
+ ** for example, when using the tdsqlite3PagerDontWrite() optimization:
** (1) Page X is journalled, and gets WRITEABLE and NEED_SEEK.
** (2) Page X moved to freelist, WRITEABLE is cleared
** (3) Page X reused, WRITEABLE is set again
@@ -47522,7 +53057,7 @@ static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){
pPage->pDirtyPrev->pDirtyNext = pPage->pDirtyNext;
}else{
/* If there are now no dirty pages in the cache, set eCreate to 2.
- ** This is an optimization that allows sqlite3PcacheFetch() to skip
+ ** This is an optimization that allows tdsqlite3PcacheFetch() to skip
** searching for a dirty page to eject from the cache when it might
** otherwise have to. */
assert( pPage==p->pDirty );
@@ -47533,12 +53068,9 @@ static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){
p->eCreate = 2;
}
}
- pPage->pDirtyNext = 0;
- pPage->pDirtyPrev = 0;
}
if( addRemove & PCACHE_DIRTYLIST_ADD ){
- assert( pPage->pDirtyNext==0 && pPage->pDirtyPrev==0 && p->pDirty!=pPage );
-
+ pPage->pDirtyPrev = 0;
pPage->pDirtyNext = p->pDirty;
if( pPage->pDirtyNext ){
assert( pPage->pDirtyNext->pDirtyPrev==0 );
@@ -47555,7 +53087,7 @@ static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){
/* If pSynced is NULL and this page has a clear NEED_SYNC flag, set
** pSynced to point to it. Checking the NEED_SYNC flag is an
** optimization, as if pSynced points to a page with the NEED_SYNC
- ** flag set sqlite3PcacheFetchStress() searches through all newer
+ ** flag set tdsqlite3PcacheFetchStress() searches through all newer
** entries of the dirty-list for a page with NEED_SYNC clear anyway. */
if( !p->pSynced
&& 0==(pPage->flags&PGHDR_NEED_SYNC) /*OPTIMIZATION-IF-FALSE*/
@@ -47573,7 +53105,7 @@ static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){
static void pcacheUnpin(PgHdr *p){
if( p->pCache->bPurgeable ){
pcacheTrace(("%p.UNPIN %d\n", p->pCache, p->pgno));
- sqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 0);
+ tdsqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 0);
pcacheDump(p->pCache);
}
}
@@ -47588,9 +53120,10 @@ static int numberOfCachePages(PCache *p){
** suggested cache size is set to N. */
return p->szCache;
}else{
- /* IMPLEMENTATION-OF: R-61436-13639 If the argument N is negative, then
- ** the number of cache pages is adjusted to use approximately abs(N*1024)
- ** bytes of memory. */
+ /* IMPLEMANTATION-OF: R-59858-46238 If the argument N is negative, then the
+ ** number of cache pages is adjusted to be a number of pages that would
+ ** use approximately abs(N*1024) bytes of memory based on the current
+ ** page size. */
return (int)((-1024*(i64)p->szCache)/(p->szPage+p->szExtra));
}
}
@@ -47600,34 +53133,41 @@ static int numberOfCachePages(PCache *p){
** Initialize and shutdown the page cache subsystem. Neither of these
** functions are threadsafe.
*/
-SQLITE_PRIVATE int sqlite3PcacheInitialize(void){
- if( sqlite3GlobalConfig.pcache2.xInit==0 ){
+SQLITE_PRIVATE int tdsqlite3PcacheInitialize(void){
+ if( tdsqlite3GlobalConfig.pcache2.xInit==0 ){
/* IMPLEMENTATION-OF: R-26801-64137 If the xInit() method is NULL, then the
** built-in default page cache is used instead of the application defined
** page cache. */
- sqlite3PCacheSetDefault();
+ tdsqlite3PCacheSetDefault();
+ assert( tdsqlite3GlobalConfig.pcache2.xInit!=0 );
}
- return sqlite3GlobalConfig.pcache2.xInit(sqlite3GlobalConfig.pcache2.pArg);
+ return tdsqlite3GlobalConfig.pcache2.xInit(tdsqlite3GlobalConfig.pcache2.pArg);
}
-SQLITE_PRIVATE void sqlite3PcacheShutdown(void){
- if( sqlite3GlobalConfig.pcache2.xShutdown ){
+SQLITE_PRIVATE void tdsqlite3PcacheShutdown(void){
+ if( tdsqlite3GlobalConfig.pcache2.xShutdown ){
/* IMPLEMENTATION-OF: R-26000-56589 The xShutdown() method may be NULL. */
- sqlite3GlobalConfig.pcache2.xShutdown(sqlite3GlobalConfig.pcache2.pArg);
+ tdsqlite3GlobalConfig.pcache2.xShutdown(tdsqlite3GlobalConfig.pcache2.pArg);
}
}
/*
** Return the size in bytes of a PCache object.
*/
-SQLITE_PRIVATE int sqlite3PcacheSize(void){ return sizeof(PCache); }
+SQLITE_PRIVATE int tdsqlite3PcacheSize(void){ return sizeof(PCache); }
/*
** Create a new PCache object. Storage space to hold the object
** has already been allocated and is passed in as the p pointer.
** The caller discovers how much space needs to be allocated by
-** calling sqlite3PcacheSize().
+** calling tdsqlite3PcacheSize().
+**
+** szExtra is some extra space allocated for each page. The first
+** 8 bytes of the extra space will be zeroed as the page is allocated,
+** but remaining content will be uninitialized. Though it is opaque
+** to this module, the extra space really ends up being the MemPage
+** structure in the pager.
*/
-SQLITE_PRIVATE int sqlite3PcacheOpen(
+SQLITE_PRIVATE int tdsqlite3PcacheOpen(
int szPage, /* Size of every page */
int szExtra, /* Extra space associated with each page */
int bPurgeable, /* True if pages are on backing store */
@@ -47638,6 +53178,7 @@ SQLITE_PRIVATE int sqlite3PcacheOpen(
memset(p, 0, sizeof(PCache));
p->szPage = 1;
p->szExtra = szExtra;
+ assert( szExtra>=8 ); /* First 8 bytes will be zeroed */
p->bPurgeable = bPurgeable;
p->eCreate = 2;
p->xStress = xStress;
@@ -47645,25 +53186,25 @@ SQLITE_PRIVATE int sqlite3PcacheOpen(
p->szCache = 100;
p->szSpill = 1;
pcacheTrace(("%p.OPEN szPage %d bPurgeable %d\n",p,szPage,bPurgeable));
- return sqlite3PcacheSetPageSize(p, szPage);
+ return tdsqlite3PcacheSetPageSize(p, szPage);
}
/*
** Change the page size for PCache object. The caller must ensure that there
** are no outstanding page references when this function is called.
*/
-SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){
+SQLITE_PRIVATE int tdsqlite3PcacheSetPageSize(PCache *pCache, int szPage){
assert( pCache->nRefSum==0 && pCache->pDirty==0 );
if( pCache->szPage ){
- sqlite3_pcache *pNew;
- pNew = sqlite3GlobalConfig.pcache2.xCreate(
+ tdsqlite3_pcache *pNew;
+ pNew = tdsqlite3GlobalConfig.pcache2.xCreate(
szPage, pCache->szExtra + ROUND8(sizeof(PgHdr)),
pCache->bPurgeable
);
if( pNew==0 ) return SQLITE_NOMEM_BKPT;
- sqlite3GlobalConfig.pcache2.xCachesize(pNew, numberOfCachePages(pCache));
+ tdsqlite3GlobalConfig.pcache2.xCachesize(pNew, numberOfCachePages(pCache));
if( pCache->pCache ){
- sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache);
+ tdsqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache);
}
pCache->pCache = pNew;
pCache->szPage = szPage;
@@ -47675,7 +53216,7 @@ SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){
/*
** Try to obtain a page from the cache.
**
-** This routine returns a pointer to an sqlite3_pcache_page object if
+** This routine returns a pointer to an tdsqlite3_pcache_page object if
** such an object is already in cache, or if a new one is created.
** This routine returns a NULL pointer if the object was not in cache
** and could not be created.
@@ -47688,26 +53229,25 @@ SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){
** is created only if that can be done without spilling dirty pages
** and without exceeding the cache size limit.
**
-** The caller needs to invoke sqlite3PcacheFetchFinish() to properly
-** initialize the sqlite3_pcache_page object and convert it into a
-** PgHdr object. The sqlite3PcacheFetch() and sqlite3PcacheFetchFinish()
+** The caller needs to invoke tdsqlite3PcacheFetchFinish() to properly
+** initialize the tdsqlite3_pcache_page object and convert it into a
+** PgHdr object. The tdsqlite3PcacheFetch() and tdsqlite3PcacheFetchFinish()
** routines are split this way for performance reasons. When separated
** they can both (usually) operate without having to push values to
** the stack on entry and pop them back off on exit, which saves a
** lot of pushing and popping.
*/
-SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch(
+SQLITE_PRIVATE tdsqlite3_pcache_page *tdsqlite3PcacheFetch(
PCache *pCache, /* Obtain the page from this cache */
Pgno pgno, /* Page number to obtain */
int createFlag /* If true, create page if it does not exist already */
){
int eCreate;
- sqlite3_pcache_page *pRes;
+ tdsqlite3_pcache_page *pRes;
assert( pCache!=0 );
assert( pCache->pCache!=0 );
assert( createFlag==3 || createFlag==0 );
- assert( pgno>0 );
assert( pCache->eCreate==((pCache->bPurgeable && pCache->pDirty) ? 1 : 2) );
/* eCreate defines what to do if the page does not exist.
@@ -47721,14 +53261,14 @@ SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch(
assert( eCreate==0 || eCreate==1 || eCreate==2 );
assert( createFlag==0 || pCache->eCreate==eCreate );
assert( createFlag==0 || eCreate==1+(!pCache->bPurgeable||!pCache->pDirty) );
- pRes = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate);
+ pRes = tdsqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate);
pcacheTrace(("%p.FETCH %d%s (result: %p)\n",pCache,pgno,
createFlag?" create":"",pRes));
return pRes;
}
/*
-** If the sqlite3PcacheFetch() routine is unable to allocate a new
+** If the tdsqlite3PcacheFetch() routine is unable to allocate a new
** page because no clean pages are available for reuse and the cache
** size limit has been reached, then this routine can be invoked to
** try harder to allocate a page. This routine might invoke the stress
@@ -47736,17 +53276,17 @@ SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch(
** allocate the new page and will only fail to allocate a new page on
** an OOM error.
**
-** This routine should be invoked only after sqlite3PcacheFetch() fails.
+** This routine should be invoked only after tdsqlite3PcacheFetch() fails.
*/
-SQLITE_PRIVATE int sqlite3PcacheFetchStress(
+SQLITE_PRIVATE int tdsqlite3PcacheFetchStress(
PCache *pCache, /* Obtain the page from this cache */
Pgno pgno, /* Page number to obtain */
- sqlite3_pcache_page **ppPage /* Write result here */
+ tdsqlite3_pcache_page **ppPage /* Write result here */
){
PgHdr *pPg;
if( pCache->eCreate==2 ) return 0;
- if( sqlite3PcachePagecount(pCache)>pCache->szSpill ){
+ if( tdsqlite3PcachePagecount(pCache)>pCache->szSpill ){
/* Find a dirty page to write-out and recycle. First try to find a
** page that does not require a journal-sync (one with PGHDR_NEED_SYNC
** cleared), but if that is not possible settle for any other
@@ -47767,10 +53307,10 @@ SQLITE_PRIVATE int sqlite3PcacheFetchStress(
if( pPg ){
int rc;
#ifdef SQLITE_LOG_CACHE_SPILL
- sqlite3_log(SQLITE_FULL,
+ tdsqlite3_log(SQLITE_FULL,
"spill page %d making room for %d - cache used: %d/%d",
pPg->pgno, pgno,
- sqlite3GlobalConfig.pcache.xPagecount(pCache->pCache),
+ tdsqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache),
numberOfCachePages(pCache));
#endif
pcacheTrace(("%p.SPILL %d\n",pCache,pPg->pgno));
@@ -47781,12 +53321,12 @@ SQLITE_PRIVATE int sqlite3PcacheFetchStress(
}
}
}
- *ppPage = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, 2);
+ *ppPage = tdsqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, 2);
return *ppPage==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK;
}
/*
-** This is a helper routine for sqlite3PcacheFetchFinish()
+** This is a helper routine for tdsqlite3PcacheFetchFinish()
**
** In the uncommon case where the page being fetched has not been
** initialized, this routine is invoked to do the initialization.
@@ -47797,7 +53337,7 @@ SQLITE_PRIVATE int sqlite3PcacheFetchStress(
static SQLITE_NOINLINE PgHdr *pcacheFetchFinishWithInit(
PCache *pCache, /* Obtain the page from this cache */
Pgno pgno, /* Page number obtained */
- sqlite3_pcache_page *pPage /* Page obtained by prior PcacheFetch() call */
+ tdsqlite3_pcache_page *pPage /* Page obtained by prior PcacheFetch() call */
){
PgHdr *pPgHdr;
assert( pPage!=0 );
@@ -47807,23 +53347,23 @@ static SQLITE_NOINLINE PgHdr *pcacheFetchFinishWithInit(
pPgHdr->pPage = pPage;
pPgHdr->pData = pPage->pBuf;
pPgHdr->pExtra = (void *)&pPgHdr[1];
- memset(pPgHdr->pExtra, 0, pCache->szExtra);
+ memset(pPgHdr->pExtra, 0, 8);
pPgHdr->pCache = pCache;
pPgHdr->pgno = pgno;
pPgHdr->flags = PGHDR_CLEAN;
- return sqlite3PcacheFetchFinish(pCache,pgno,pPage);
+ return tdsqlite3PcacheFetchFinish(pCache,pgno,pPage);
}
/*
-** This routine converts the sqlite3_pcache_page object returned by
-** sqlite3PcacheFetch() into an initialized PgHdr object. This routine
-** must be called after sqlite3PcacheFetch() in order to get a usable
+** This routine converts the tdsqlite3_pcache_page object returned by
+** tdsqlite3PcacheFetch() into an initialized PgHdr object. This routine
+** must be called after tdsqlite3PcacheFetch() in order to get a usable
** result.
*/
-SQLITE_PRIVATE PgHdr *sqlite3PcacheFetchFinish(
+SQLITE_PRIVATE PgHdr *tdsqlite3PcacheFetchFinish(
PCache *pCache, /* Obtain the page from this cache */
Pgno pgno, /* Page number obtained */
- sqlite3_pcache_page *pPage /* Page obtained by prior PcacheFetch() call */
+ tdsqlite3_pcache_page *pPage /* Page obtained by prior PcacheFetch() call */
){
PgHdr *pPgHdr;
@@ -47835,7 +53375,7 @@ SQLITE_PRIVATE PgHdr *sqlite3PcacheFetchFinish(
}
pCache->nRefSum++;
pPgHdr->nRef++;
- assert( sqlite3PcachePageSanity(pPgHdr) );
+ assert( tdsqlite3PcachePageSanity(pPgHdr) );
return pPgHdr;
}
@@ -47843,17 +53383,13 @@ SQLITE_PRIVATE PgHdr *sqlite3PcacheFetchFinish(
** Decrement the reference count on a page. If the page is clean and the
** reference count drops to 0, then it is made eligible for recycling.
*/
-SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){
+SQLITE_PRIVATE void SQLITE_NOINLINE tdsqlite3PcacheRelease(PgHdr *p){
assert( p->nRef>0 );
p->pCache->nRefSum--;
if( (--p->nRef)==0 ){
if( p->flags&PGHDR_CLEAN ){
pcacheUnpin(p);
- }else if( p->pDirtyPrev!=0 ){ /*OPTIMIZATION-IF-FALSE*/
- /* Move the page to the head of the dirty list. If p->pDirtyPrev==0,
- ** then page p is already at the head of the dirty list and the
- ** following call would be a no-op. Hence the OPTIMIZATION-IF-FALSE
- ** tag above. */
+ }else{
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT);
}
}
@@ -47862,9 +53398,9 @@ SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){
/*
** Increase the reference count of a supplied page by 1.
*/
-SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr *p){
+SQLITE_PRIVATE void tdsqlite3PcacheRef(PgHdr *p){
assert(p->nRef>0);
- assert( sqlite3PcachePageSanity(p) );
+ assert( tdsqlite3PcachePageSanity(p) );
p->nRef++;
p->pCache->nRefSum++;
}
@@ -47874,23 +53410,23 @@ SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr *p){
** page. This function deletes that reference, so after it returns the
** page pointed to by p is invalid.
*/
-SQLITE_PRIVATE void sqlite3PcacheDrop(PgHdr *p){
+SQLITE_PRIVATE void tdsqlite3PcacheDrop(PgHdr *p){
assert( p->nRef==1 );
- assert( sqlite3PcachePageSanity(p) );
+ assert( tdsqlite3PcachePageSanity(p) );
if( p->flags&PGHDR_DIRTY ){
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE);
}
p->pCache->nRefSum--;
- sqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 1);
+ tdsqlite3GlobalConfig.pcache2.xUnpin(p->pCache->pCache, p->pPage, 1);
}
/*
** Make sure the page is marked as dirty. If it isn't dirty already,
** make it so.
*/
-SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){
+SQLITE_PRIVATE void tdsqlite3PcacheMakeDirty(PgHdr *p){
assert( p->nRef>0 );
- assert( sqlite3PcachePageSanity(p) );
+ assert( tdsqlite3PcachePageSanity(p) );
if( p->flags & (PGHDR_CLEAN|PGHDR_DONT_WRITE) ){ /*OPTIMIZATION-IF-FALSE*/
p->flags &= ~PGHDR_DONT_WRITE;
if( p->flags & PGHDR_CLEAN ){
@@ -47899,7 +53435,7 @@ SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){
assert( (p->flags & (PGHDR_DIRTY|PGHDR_CLEAN))==PGHDR_DIRTY );
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_ADD);
}
- assert( sqlite3PcachePageSanity(p) );
+ assert( tdsqlite3PcachePageSanity(p) );
}
}
@@ -47907,36 +53443,35 @@ SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){
** Make sure the page is marked as clean. If it isn't clean already,
** make it so.
*/
-SQLITE_PRIVATE void sqlite3PcacheMakeClean(PgHdr *p){
- assert( sqlite3PcachePageSanity(p) );
- if( ALWAYS((p->flags & PGHDR_DIRTY)!=0) ){
- assert( (p->flags & PGHDR_CLEAN)==0 );
- pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE);
- p->flags &= ~(PGHDR_DIRTY|PGHDR_NEED_SYNC|PGHDR_WRITEABLE);
- p->flags |= PGHDR_CLEAN;
- pcacheTrace(("%p.CLEAN %d\n",p->pCache,p->pgno));
- assert( sqlite3PcachePageSanity(p) );
- if( p->nRef==0 ){
- pcacheUnpin(p);
- }
+SQLITE_PRIVATE void tdsqlite3PcacheMakeClean(PgHdr *p){
+ assert( tdsqlite3PcachePageSanity(p) );
+ assert( (p->flags & PGHDR_DIRTY)!=0 );
+ assert( (p->flags & PGHDR_CLEAN)==0 );
+ pcacheManageDirtyList(p, PCACHE_DIRTYLIST_REMOVE);
+ p->flags &= ~(PGHDR_DIRTY|PGHDR_NEED_SYNC|PGHDR_WRITEABLE);
+ p->flags |= PGHDR_CLEAN;
+ pcacheTrace(("%p.CLEAN %d\n",p->pCache,p->pgno));
+ assert( tdsqlite3PcachePageSanity(p) );
+ if( p->nRef==0 ){
+ pcacheUnpin(p);
}
}
/*
** Make every page in the cache clean.
*/
-SQLITE_PRIVATE void sqlite3PcacheCleanAll(PCache *pCache){
+SQLITE_PRIVATE void tdsqlite3PcacheCleanAll(PCache *pCache){
PgHdr *p;
pcacheTrace(("%p.CLEAN-ALL\n",pCache));
while( (p = pCache->pDirty)!=0 ){
- sqlite3PcacheMakeClean(p);
+ tdsqlite3PcacheMakeClean(p);
}
}
/*
** Clear the PGHDR_NEED_SYNC and PGHDR_WRITEABLE flag from all dirty pages.
*/
-SQLITE_PRIVATE void sqlite3PcacheClearWritable(PCache *pCache){
+SQLITE_PRIVATE void tdsqlite3PcacheClearWritable(PCache *pCache){
PgHdr *p;
pcacheTrace(("%p.CLEAR-WRITEABLE\n",pCache));
for(p=pCache->pDirty; p; p=p->pDirtyNext){
@@ -47948,7 +53483,7 @@ SQLITE_PRIVATE void sqlite3PcacheClearWritable(PCache *pCache){
/*
** Clear the PGHDR_NEED_SYNC flag from all dirty pages.
*/
-SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *pCache){
+SQLITE_PRIVATE void tdsqlite3PcacheClearSyncFlags(PCache *pCache){
PgHdr *p;
for(p=pCache->pDirty; p; p=p->pDirtyNext){
p->flags &= ~PGHDR_NEED_SYNC;
@@ -47959,13 +53494,13 @@ SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *pCache){
/*
** Change the page number of page p to newPgno.
*/
-SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){
+SQLITE_PRIVATE void tdsqlite3PcacheMove(PgHdr *p, Pgno newPgno){
PCache *pCache = p->pCache;
assert( p->nRef>0 );
assert( newPgno>0 );
- assert( sqlite3PcachePageSanity(p) );
+ assert( tdsqlite3PcachePageSanity(p) );
pcacheTrace(("%p.MOVE %d -> %d\n",pCache,p->pgno,newPgno));
- sqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno);
+ tdsqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno);
p->pgno = newPgno;
if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){
pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT);
@@ -47981,7 +53516,7 @@ SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){
** function is 0, then the data area associated with page 1 is zeroed, but
** the page object is not dropped.
*/
-SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){
+SQLITE_PRIVATE void tdsqlite3PcacheTruncate(PCache *pCache, Pgno pgno){
if( pCache->pCache ){
PgHdr *p;
PgHdr *pNext;
@@ -47989,42 +53524,42 @@ SQLITE_PRIVATE void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){
for(p=pCache->pDirty; p; p=pNext){
pNext = p->pDirtyNext;
/* This routine never gets call with a positive pgno except right
- ** after sqlite3PcacheCleanAll(). So if there are dirty pages,
+ ** after tdsqlite3PcacheCleanAll(). So if there are dirty pages,
** it must be that pgno==0.
*/
assert( p->pgno>0 );
if( p->pgno>pgno ){
assert( p->flags&PGHDR_DIRTY );
- sqlite3PcacheMakeClean(p);
+ tdsqlite3PcacheMakeClean(p);
}
}
if( pgno==0 && pCache->nRefSum ){
- sqlite3_pcache_page *pPage1;
- pPage1 = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache,1,0);
+ tdsqlite3_pcache_page *pPage1;
+ pPage1 = tdsqlite3GlobalConfig.pcache2.xFetch(pCache->pCache,1,0);
if( ALWAYS(pPage1) ){ /* Page 1 is always available in cache, because
** pCache->nRefSum>0 */
memset(pPage1->pBuf, 0, pCache->szPage);
pgno = 1;
}
}
- sqlite3GlobalConfig.pcache2.xTruncate(pCache->pCache, pgno+1);
+ tdsqlite3GlobalConfig.pcache2.xTruncate(pCache->pCache, pgno+1);
}
}
/*
** Close a cache.
*/
-SQLITE_PRIVATE void sqlite3PcacheClose(PCache *pCache){
+SQLITE_PRIVATE void tdsqlite3PcacheClose(PCache *pCache){
assert( pCache->pCache!=0 );
pcacheTrace(("%p.CLOSE\n",pCache));
- sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache);
+ tdsqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache);
}
/*
** Discard the contents of the cache.
*/
-SQLITE_PRIVATE void sqlite3PcacheClear(PCache *pCache){
- sqlite3PcacheTruncate(pCache, 0);
+SQLITE_PRIVATE void tdsqlite3PcacheClear(PCache *pCache){
+ tdsqlite3PcacheTruncate(pCache, 0);
}
/*
@@ -48103,7 +53638,7 @@ static PgHdr *pcacheSortDirtyList(PgHdr *pIn){
/*
** Return a list of all dirty pages in the cache, sorted by page number.
*/
-SQLITE_PRIVATE PgHdr *sqlite3PcacheDirtyList(PCache *pCache){
+SQLITE_PRIVATE PgHdr *tdsqlite3PcacheDirtyList(PCache *pCache){
PgHdr *p;
for(p=pCache->pDirty; p; p=p->pDirtyNext){
p->pDirty = p->pDirtyNext;
@@ -48117,30 +53652,30 @@ SQLITE_PRIVATE PgHdr *sqlite3PcacheDirtyList(PCache *pCache){
** This is not the total number of pages referenced, but the sum of the
** reference count for all pages.
*/
-SQLITE_PRIVATE int sqlite3PcacheRefCount(PCache *pCache){
+SQLITE_PRIVATE int tdsqlite3PcacheRefCount(PCache *pCache){
return pCache->nRefSum;
}
/*
** Return the number of references to the page supplied as an argument.
*/
-SQLITE_PRIVATE int sqlite3PcachePageRefcount(PgHdr *p){
+SQLITE_PRIVATE int tdsqlite3PcachePageRefcount(PgHdr *p){
return p->nRef;
}
/*
** Return the total number of pages in the cache.
*/
-SQLITE_PRIVATE int sqlite3PcachePagecount(PCache *pCache){
+SQLITE_PRIVATE int tdsqlite3PcachePagecount(PCache *pCache){
assert( pCache->pCache!=0 );
- return sqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache);
+ return tdsqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache);
}
#ifdef SQLITE_TEST
/*
** Get the suggested cache-size value.
*/
-SQLITE_PRIVATE int sqlite3PcacheGetCachesize(PCache *pCache){
+SQLITE_PRIVATE int tdsqlite3PcacheGetCachesize(PCache *pCache){
return numberOfCachePages(pCache);
}
#endif
@@ -48148,10 +53683,10 @@ SQLITE_PRIVATE int sqlite3PcacheGetCachesize(PCache *pCache){
/*
** Set the suggested cache-size value.
*/
-SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *pCache, int mxPage){
+SQLITE_PRIVATE void tdsqlite3PcacheSetCachesize(PCache *pCache, int mxPage){
assert( pCache->pCache!=0 );
pCache->szCache = mxPage;
- sqlite3GlobalConfig.pcache2.xCachesize(pCache->pCache,
+ tdsqlite3GlobalConfig.pcache2.xCachesize(pCache->pCache,
numberOfCachePages(pCache));
}
@@ -48160,7 +53695,7 @@ SQLITE_PRIVATE void sqlite3PcacheSetCachesize(PCache *pCache, int mxPage){
** argument is zero. Return the effective cache-spill size, which will
** be the larger of the szSpill and szCache.
*/
-SQLITE_PRIVATE int sqlite3PcacheSetSpillsize(PCache *p, int mxPage){
+SQLITE_PRIVATE int tdsqlite3PcacheSetSpillsize(PCache *p, int mxPage){
int res;
assert( p->pCache!=0 );
if( mxPage ){
@@ -48177,22 +53712,22 @@ SQLITE_PRIVATE int sqlite3PcacheSetSpillsize(PCache *p, int mxPage){
/*
** Free up as much memory as possible from the page cache.
*/
-SQLITE_PRIVATE void sqlite3PcacheShrink(PCache *pCache){
+SQLITE_PRIVATE void tdsqlite3PcacheShrink(PCache *pCache){
assert( pCache->pCache!=0 );
- sqlite3GlobalConfig.pcache2.xShrink(pCache->pCache);
+ tdsqlite3GlobalConfig.pcache2.xShrink(pCache->pCache);
}
/*
** Return the size of the header added by this middleware layer
** in the page-cache hierarchy.
*/
-SQLITE_PRIVATE int sqlite3HeaderSizePcache(void){ return ROUND8(sizeof(PgHdr)); }
+SQLITE_PRIVATE int tdsqlite3HeaderSizePcache(void){ return ROUND8(sizeof(PgHdr)); }
/*
** Return the number of dirty pages currently in the cache, as a percentage
** of the configured cache size.
*/
-SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache *pCache){
+SQLITE_PRIVATE int tdsqlite3PCachePercentDirty(PCache *pCache){
PgHdr *pDirty;
int nDirty = 0;
int nCache = numberOfCachePages(pCache);
@@ -48200,13 +53735,22 @@ SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache *pCache){
return nCache ? (int)(((i64)nDirty * 100) / nCache) : 0;
}
+#ifdef SQLITE_DIRECT_OVERFLOW_READ
+/*
+** Return true if there are one or more dirty pages in the cache. Else false.
+*/
+SQLITE_PRIVATE int tdsqlite3PCacheIsDirty(PCache *pCache){
+ return (pCache->pDirty!=0);
+}
+#endif
+
#if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG)
/*
** For all dirty pages currently in the cache, invoke the specified
** callback. This is only used if the SQLITE_CHECK_PAGES macro is
** defined.
*/
-SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)){
+SQLITE_PRIVATE void tdsqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)){
PgHdr *pDirty;
for(pDirty=pCache->pDirty; pDirty; pDirty=pDirty->pDirtyNext){
xIter(pDirty);
@@ -48229,8 +53773,8 @@ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHd
*************************************************************************
**
** This file implements the default page cache implementation (the
-** sqlite3_pcache interface). It also contains part of the implementation
-** of the SQLITE_CONFIG_PAGECACHE and sqlite3_release_memory() features.
+** tdsqlite3_pcache interface). It also contains part of the implementation
+** of the SQLITE_CONFIG_PAGECACHE and tdsqlite3_release_memory() features.
** If the default page cache implementation is overridden, then neither of
** these two features are available.
**
@@ -48246,13 +53790,13 @@ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHd
** as the database page number and how that database page is used. PgHdr
** is added by the pcache.c layer and contains information used to keep track
** of which pages are "dirty". PgHdr1 is an extension added by this
-** module (pcache1.c). The PgHdr1 header is a subclass of sqlite3_pcache_page.
+** module (pcache1.c). The PgHdr1 header is a subclass of tdsqlite3_pcache_page.
** PgHdr1 contains information needed to look up a page by its page number.
-** The superclass sqlite3_pcache_page.pBuf points to the start of the
-** database page content and sqlite3_pcache_page.pExtra points to PgHdr.
+** The superclass tdsqlite3_pcache_page.pBuf points to the start of the
+** database page content and tdsqlite3_pcache_page.pExtra points to PgHdr.
**
** The size of the extension (MemPage+PgHdr+PgHdr1) can be determined at
-** runtime using sqlite3_config(SQLITE_CONFIG_PCACHE_HDRSZ, &size). The
+** runtime using tdsqlite3_config(SQLITE_CONFIG_PCACHE_HDRSZ, &size). The
** sizes of the extensions sum to 272 bytes on x64 for 3.8.10, but this
** size can vary according to architecture, compile-time options, and
** SQLite library version number.
@@ -48274,8 +53818,8 @@ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHd
**
** Memory for a page might come from any of three sources:
**
-** (1) The general-purpose memory allocator - sqlite3Malloc()
-** (2) Global page-cache memory provided using sqlite3_config() with
+** (1) The general-purpose memory allocator - tdsqlite3Malloc()
+** (2) Global page-cache memory provided using tdsqlite3_config() with
** SQLITE_CONFIG_PAGECACHE.
** (3) PCache-local bulk allocation.
**
@@ -48283,10 +53827,10 @@ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHd
** that is allocated when the page cache is created. The size of the local
** bulk allocation can be adjusted using
**
-** sqlite3_config(SQLITE_CONFIG_PAGECACHE, (void*)0, 0, N).
+** tdsqlite3_config(SQLITE_CONFIG_PAGECACHE, (void*)0, 0, N).
**
** If N is positive, then N pages worth of memory are allocated using a single
-** sqlite3Malloc() call and that memory is used for the first N pages allocated.
+** tdsqlite3Malloc() call and that memory is used for the first N pages allocated.
** Or if N is negative, then -1024*N bytes of memory are allocated and used
** for as many pages as can be accomodated.
**
@@ -48310,19 +53854,36 @@ typedef struct PGroup PGroup;
** structure. Unless SQLITE_PCACHE_SEPARATE_HEADER is defined, a buffer of
** PgHdr1.pCache->szPage bytes is allocated directly before this structure
** in memory.
+**
+** Note: Variables isBulkLocal and isAnchor were once type "u8". That works,
+** but causes a 2-byte gap in the structure for most architectures (since
+** pointers must be either 4 or 8-byte aligned). As this structure is located
+** in memory directly after the associated page data, if the database is
+** corrupt, code at the b-tree layer may overread the page buffer and
+** read part of this structure before the corruption is detected. This
+** can cause a valgrind error if the unitialized gap is accessed. Using u16
+** ensures there is no such gap, and therefore no bytes of unitialized memory
+** in the structure.
*/
struct PgHdr1 {
- sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */
+ tdsqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */
unsigned int iKey; /* Key value (page number) */
- u8 isPinned; /* Page in use, not on the LRU list */
- u8 isBulkLocal; /* This page from bulk local storage */
- u8 isAnchor; /* This is the PGroup.lru element */
+ u16 isBulkLocal; /* This page from bulk local storage */
+ u16 isAnchor; /* This is the PGroup.lru element */
PgHdr1 *pNext; /* Next in hash table chain */
PCache1 *pCache; /* Cache that currently owns this page */
PgHdr1 *pLruNext; /* Next in LRU list of unpinned pages */
PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */
+ /* NB: pLruPrev is only valid if pLruNext!=0 */
};
+/*
+** A page is pinned if it is not on the LRU list. To be "pinned" means
+** that the page is in active use and must not be deallocated.
+*/
+#define PAGE_IS_PINNED(p) ((p)->pLruNext==0)
+#define PAGE_IS_UNPINNED(p) ((p)->pLruNext!=0)
+
/* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set
** of one or more PCaches that are able to recycle each other's unpinned
** pages when they are under memory pressure. A PGroup is an instance of
@@ -48346,11 +53907,11 @@ struct PgHdr1 {
** SQLITE_MUTEX_STATIC_LRU.
*/
struct PGroup {
- sqlite3_mutex *mutex; /* MUTEX_STATIC_LRU or NULL */
+ tdsqlite3_mutex *mutex; /* MUTEX_STATIC_LRU or NULL */
unsigned int nMaxPage; /* Sum of nMax for purgeable caches */
unsigned int nMinPage; /* Sum of nMin for purgeable caches */
unsigned int mxPinned; /* nMaxpage + 10 - nMinPage */
- unsigned int nCurrentPage; /* Number of purgeable pages allocated */
+ unsigned int nPurgeable; /* Number of purgeable pages allocated */
PgHdr1 lru; /* The beginning and end of the LRU list */
};
@@ -48360,15 +53921,17 @@ struct PGroup {
** is an instance of this object.
**
** Pointers to structures of this type are cast and returned as
-** opaque sqlite3_pcache* handles.
+** opaque tdsqlite3_pcache* handles.
*/
struct PCache1 {
/* Cache configuration parameters. Page size (szPage) and the purgeable
- ** flag (bPurgeable) are set when the cache is created. nMax may be
+ ** flag (bPurgeable) and the pnPurgeable pointer are all set when the
+ ** cache is created and are never changed thereafter. nMax may be
** modified at any time by a call to the pcache1Cachesize() method.
** The PGroup mutex must be held when accessing nMax.
*/
PGroup *pGroup; /* PGroup this cache belongs to */
+ unsigned int *pnPurgeable; /* Pointer to pGroup->nPurgeable */
int szPage; /* Size of database content section */
int szExtra; /* sizeof(MemPage)+sizeof(PgHdr) */
int szAlloc; /* Total size of one pcache line */
@@ -48377,6 +53940,7 @@ struct PCache1 {
unsigned int nMax; /* Configured "cache_size" value */
unsigned int n90pct; /* nMax*9/10 */
unsigned int iMaxKey; /* Largest key seen since xTruncate() */
+ unsigned int nPurgeableDummy; /* pnPurgeable points here when not used*/
/* Hash table of all pages. The following variables may only be accessed
** when the accessor is holding the PGroup mutex.
@@ -48405,7 +53969,7 @@ static SQLITE_WSD struct PCacheGlobal {
/* Variables related to SQLITE_CONFIG_PAGECACHE settings. The
** szSlot, nSlot, pStart, pEnd, nReserve, and isInit values are all
- ** fixed at sqlite3_initialize() time and do not require mutex protection.
+ ** fixed at tdsqlite3_initialize() time and do not require mutex protection.
** The nFreeSlot and pFree values do require mutex protection.
*/
int isInit; /* True if initialized */
@@ -48416,7 +53980,7 @@ static SQLITE_WSD struct PCacheGlobal {
int nReserve; /* Try to keep nFreeSlot above this */
void *pStart, *pEnd; /* Bounds of global page cache memory */
/* Above requires no mutex. Use mutex below for variable that follow. */
- sqlite3_mutex *mutex; /* Mutex for accessing the following: */
+ tdsqlite3_mutex *mutex; /* Mutex for accessing the following: */
PgFreeslot *pFree; /* Free page blocks */
int nFreeSlot; /* Number of unused pcache slots */
/* The following value requires a mutex to change. We skip the mutex on
@@ -48441,8 +54005,8 @@ static SQLITE_WSD struct PCacheGlobal {
# define pcache1LeaveMutex(X) assert((X)->mutex==0)
# define PCACHE1_MIGHT_USE_GROUP_MUTEX 0
#else
-# define pcache1EnterMutex(X) sqlite3_mutex_enter((X)->mutex)
-# define pcache1LeaveMutex(X) sqlite3_mutex_leave((X)->mutex)
+# define pcache1EnterMutex(X) tdsqlite3_mutex_enter((X)->mutex)
+# define pcache1LeaveMutex(X) tdsqlite3_mutex_leave((X)->mutex)
# define PCACHE1_MIGHT_USE_GROUP_MUTEX 1
#endif
@@ -48453,16 +54017,17 @@ static SQLITE_WSD struct PCacheGlobal {
/*
** This function is called during initialization if a static buffer is
** supplied to use for the page-cache by passing the SQLITE_CONFIG_PAGECACHE
-** verb to sqlite3_config(). Parameter pBuf points to an allocation large
+** verb to tdsqlite3_config(). Parameter pBuf points to an allocation large
** enough to contain 'n' buffers of 'sz' bytes each.
**
-** This routine is called from sqlite3_initialize() and so it is guaranteed
+** This routine is called from tdsqlite3_initialize() and so it is guaranteed
** to be serialized already. There is no need for further mutexing.
*/
-SQLITE_PRIVATE void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){
+SQLITE_PRIVATE void tdsqlite3PCacheBufferSetup(void *pBuf, int sz, int n){
if( pcache1.isInit ){
PgFreeslot *p;
if( pBuf==0 ) sz = n = 0;
+ if( n==0 ) sz = 0;
sz = ROUNDDOWN8(sz);
pcache1.szSlot = sz;
pcache1.nSlot = pcache1.nFreeSlot = n;
@@ -48490,7 +54055,7 @@ static int pcache1InitBulk(PCache1 *pCache){
if( pcache1.nInitPage==0 ) return 0;
/* Do not bother with a bulk allocation if the cache size very small */
if( pCache->nMax<3 ) return 0;
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
if( pcache1.nInitPage>0 ){
szBulk = pCache->szAlloc * (i64)pcache1.nInitPage;
}else{
@@ -48499,65 +54064,65 @@ static int pcache1InitBulk(PCache1 *pCache){
if( szBulk > pCache->szAlloc*(i64)pCache->nMax ){
szBulk = pCache->szAlloc*(i64)pCache->nMax;
}
- zBulk = pCache->pBulk = sqlite3Malloc( szBulk );
- sqlite3EndBenignMalloc();
+ zBulk = pCache->pBulk = tdsqlite3Malloc( szBulk );
+ tdsqlite3EndBenignMalloc();
if( zBulk ){
- int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc;
- int i;
- for(i=0; i<nBulk; i++){
+ int nBulk = tdsqlite3MallocSize(zBulk)/pCache->szAlloc;
+ do{
PgHdr1 *pX = (PgHdr1*)&zBulk[pCache->szPage];
pX->page.pBuf = zBulk;
pX->page.pExtra = &pX[1];
pX->isBulkLocal = 1;
pX->isAnchor = 0;
pX->pNext = pCache->pFree;
+ pX->pLruPrev = 0; /* Initializing this saves a valgrind error */
pCache->pFree = pX;
zBulk += pCache->szAlloc;
- }
+ }while( --nBulk );
}
return pCache->pFree!=0;
}
/*
** Malloc function used within this file to allocate space from the buffer
-** configured using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no
+** configured using tdsqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no
** such buffer exists or there is no space left in it, this function falls
-** back to sqlite3Malloc().
+** back to tdsqlite3Malloc().
**
** Multiple threads can run this routine at the same time. Global variables
** in pcache1 need to be protected via mutex.
*/
static void *pcache1Alloc(int nByte){
void *p = 0;
- assert( sqlite3_mutex_notheld(pcache1.grp.mutex) );
+ assert( tdsqlite3_mutex_notheld(pcache1.grp.mutex) );
if( nByte<=pcache1.szSlot ){
- sqlite3_mutex_enter(pcache1.mutex);
+ tdsqlite3_mutex_enter(pcache1.mutex);
p = (PgHdr1 *)pcache1.pFree;
if( p ){
pcache1.pFree = pcache1.pFree->pNext;
pcache1.nFreeSlot--;
pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve;
assert( pcache1.nFreeSlot>=0 );
- sqlite3StatusHighwater(SQLITE_STATUS_PAGECACHE_SIZE, nByte);
- sqlite3StatusUp(SQLITE_STATUS_PAGECACHE_USED, 1);
+ tdsqlite3StatusHighwater(SQLITE_STATUS_PAGECACHE_SIZE, nByte);
+ tdsqlite3StatusUp(SQLITE_STATUS_PAGECACHE_USED, 1);
}
- sqlite3_mutex_leave(pcache1.mutex);
+ tdsqlite3_mutex_leave(pcache1.mutex);
}
if( p==0 ){
/* Memory is not available in the SQLITE_CONFIG_PAGECACHE pool. Get
- ** it from sqlite3Malloc instead.
+ ** it from tdsqlite3Malloc instead.
*/
- p = sqlite3Malloc(nByte);
+ p = tdsqlite3Malloc(nByte);
#ifndef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS
if( p ){
- int sz = sqlite3MallocSize(p);
- sqlite3_mutex_enter(pcache1.mutex);
- sqlite3StatusHighwater(SQLITE_STATUS_PAGECACHE_SIZE, nByte);
- sqlite3StatusUp(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz);
- sqlite3_mutex_leave(pcache1.mutex);
+ int sz = tdsqlite3MallocSize(p);
+ tdsqlite3_mutex_enter(pcache1.mutex);
+ tdsqlite3StatusHighwater(SQLITE_STATUS_PAGECACHE_SIZE, nByte);
+ tdsqlite3StatusUp(SQLITE_STATUS_PAGECACHE_OVERFLOW, sz);
+ tdsqlite3_mutex_leave(pcache1.mutex);
}
#endif
- sqlite3MemdebugSetType(p, MEMTYPE_PCACHE);
+ tdsqlite3MemdebugSetType(p, MEMTYPE_PCACHE);
}
return p;
}
@@ -48569,28 +54134,28 @@ static void pcache1Free(void *p){
if( p==0 ) return;
if( SQLITE_WITHIN(p, pcache1.pStart, pcache1.pEnd) ){
PgFreeslot *pSlot;
- sqlite3_mutex_enter(pcache1.mutex);
- sqlite3StatusDown(SQLITE_STATUS_PAGECACHE_USED, 1);
+ tdsqlite3_mutex_enter(pcache1.mutex);
+ tdsqlite3StatusDown(SQLITE_STATUS_PAGECACHE_USED, 1);
pSlot = (PgFreeslot*)p;
pSlot->pNext = pcache1.pFree;
pcache1.pFree = pSlot;
pcache1.nFreeSlot++;
pcache1.bUnderPressure = pcache1.nFreeSlot<pcache1.nReserve;
assert( pcache1.nFreeSlot<=pcache1.nSlot );
- sqlite3_mutex_leave(pcache1.mutex);
+ tdsqlite3_mutex_leave(pcache1.mutex);
}else{
- assert( sqlite3MemdebugHasType(p, MEMTYPE_PCACHE) );
- sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
+ assert( tdsqlite3MemdebugHasType(p, MEMTYPE_PCACHE) );
+ tdsqlite3MemdebugSetType(p, MEMTYPE_HEAP);
#ifndef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS
{
int nFreed = 0;
- nFreed = sqlite3MallocSize(p);
- sqlite3_mutex_enter(pcache1.mutex);
- sqlite3StatusDown(SQLITE_STATUS_PAGECACHE_OVERFLOW, nFreed);
- sqlite3_mutex_leave(pcache1.mutex);
+ nFreed = tdsqlite3MallocSize(p);
+ tdsqlite3_mutex_enter(pcache1.mutex);
+ tdsqlite3StatusDown(SQLITE_STATUS_PAGECACHE_OVERFLOW, nFreed);
+ tdsqlite3_mutex_leave(pcache1.mutex);
}
#endif
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
}
@@ -48603,10 +54168,10 @@ static int pcache1MemSize(void *p){
return pcache1.szSlot;
}else{
int iSize;
- assert( sqlite3MemdebugHasType(p, MEMTYPE_PCACHE) );
- sqlite3MemdebugSetType(p, MEMTYPE_HEAP);
- iSize = sqlite3MallocSize(p);
- sqlite3MemdebugSetType(p, MEMTYPE_PCACHE);
+ assert( tdsqlite3MemdebugHasType(p, MEMTYPE_PCACHE) );
+ tdsqlite3MemdebugSetType(p, MEMTYPE_HEAP);
+ iSize = tdsqlite3MallocSize(p);
+ tdsqlite3MemdebugSetType(p, MEMTYPE_PCACHE);
return iSize;
}
}
@@ -48619,46 +54184,47 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache, int benignMalloc){
PgHdr1 *p = 0;
void *pPg;
- assert( sqlite3_mutex_held(pCache->pGroup->mutex) );
+ assert( tdsqlite3_mutex_held(pCache->pGroup->mutex) );
if( pCache->pFree || (pCache->nPage==0 && pcache1InitBulk(pCache)) ){
+ assert( pCache->pFree!=0 );
p = pCache->pFree;
pCache->pFree = p->pNext;
p->pNext = 0;
}else{
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
/* The group mutex must be released before pcache1Alloc() is called. This
- ** is because it might call sqlite3_release_memory(), which assumes that
+ ** is because it might call tdsqlite3_release_memory(), which assumes that
** this mutex is not held. */
assert( pcache1.separateCache==0 );
assert( pCache->pGroup==&pcache1.grp );
pcache1LeaveMutex(pCache->pGroup);
#endif
- if( benignMalloc ){ sqlite3BeginBenignMalloc(); }
+ if( benignMalloc ){ tdsqlite3BeginBenignMalloc(); }
#ifdef SQLITE_PCACHE_SEPARATE_HEADER
pPg = pcache1Alloc(pCache->szPage);
- p = sqlite3Malloc(sizeof(PgHdr1) + pCache->szExtra);
+ p = tdsqlite3Malloc(sizeof(PgHdr1) + pCache->szExtra);
if( !pPg || !p ){
pcache1Free(pPg);
- sqlite3_free(p);
+ tdsqlite3_free(p);
pPg = 0;
}
#else
pPg = pcache1Alloc(pCache->szAlloc);
- p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage];
#endif
- if( benignMalloc ){ sqlite3EndBenignMalloc(); }
+ if( benignMalloc ){ tdsqlite3EndBenignMalloc(); }
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
pcache1EnterMutex(pCache->pGroup);
#endif
if( pPg==0 ) return 0;
+#ifndef SQLITE_PCACHE_SEPARATE_HEADER
+ p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage];
+#endif
p->page.pBuf = pPg;
p->page.pExtra = &p[1];
p->isBulkLocal = 0;
p->isAnchor = 0;
}
- if( pCache->bPurgeable ){
- pCache->pGroup->nCurrentPage++;
- }
+ (*pCache->pnPurgeable)++;
return p;
}
@@ -48669,34 +54235,33 @@ static void pcache1FreePage(PgHdr1 *p){
PCache1 *pCache;
assert( p!=0 );
pCache = p->pCache;
- assert( sqlite3_mutex_held(p->pCache->pGroup->mutex) );
+ assert( tdsqlite3_mutex_held(p->pCache->pGroup->mutex) );
if( p->isBulkLocal ){
p->pNext = pCache->pFree;
pCache->pFree = p;
}else{
pcache1Free(p->page.pBuf);
#ifdef SQLITE_PCACHE_SEPARATE_HEADER
- sqlite3_free(p);
+ tdsqlite3_free(p);
#endif
}
- if( pCache->bPurgeable ){
- pCache->pGroup->nCurrentPage--;
- }
+ (*pCache->pnPurgeable)--;
}
/*
** Malloc function used by SQLite to obtain space from the buffer configured
-** using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no such buffer
-** exists, this function falls back to sqlite3Malloc().
+** using tdsqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no such buffer
+** exists, this function falls back to tdsqlite3Malloc().
*/
-SQLITE_PRIVATE void *sqlite3PageMalloc(int sz){
+SQLITE_PRIVATE void *tdsqlite3PageMalloc(int sz){
+ assert( sz<=65536+8 ); /* These allocations are never very large */
return pcache1Alloc(sz);
}
/*
-** Free an allocated buffer obtained from sqlite3PageMalloc().
+** Free an allocated buffer obtained from tdsqlite3PageMalloc().
*/
-SQLITE_PRIVATE void sqlite3PageFree(void *p){
+SQLITE_PRIVATE void tdsqlite3PageFree(void *p){
pcache1Free(p);
}
@@ -48721,7 +54286,7 @@ static int pcache1UnderMemoryPressure(PCache1 *pCache){
if( pcache1.nSlot && (pCache->szPage+pCache->szExtra)<=pcache1.szSlot ){
return pcache1.bUnderPressure;
}else{
- return sqlite3HeapNearlyFull();
+ return tdsqlite3HeapNearlyFull();
}
}
@@ -48739,7 +54304,7 @@ static void pcache1ResizeHash(PCache1 *p){
unsigned int nNew;
unsigned int i;
- assert( sqlite3_mutex_held(p->pGroup->mutex) );
+ assert( tdsqlite3_mutex_held(p->pGroup->mutex) );
nNew = p->nHash*2;
if( nNew<256 ){
@@ -48747,9 +54312,9 @@ static void pcache1ResizeHash(PCache1 *p){
}
pcache1LeaveMutex(p->pGroup);
- if( p->nHash ){ sqlite3BeginBenignMalloc(); }
- apNew = (PgHdr1 **)sqlite3MallocZero(sizeof(PgHdr1 *)*nNew);
- if( p->nHash ){ sqlite3EndBenignMalloc(); }
+ if( p->nHash ){ tdsqlite3BeginBenignMalloc(); }
+ apNew = (PgHdr1 **)tdsqlite3MallocZero(sizeof(PgHdr1 *)*nNew);
+ if( p->nHash ){ tdsqlite3EndBenignMalloc(); }
pcache1EnterMutex(p->pGroup);
if( apNew ){
for(i=0; i<p->nHash; i++){
@@ -48762,7 +54327,7 @@ static void pcache1ResizeHash(PCache1 *p){
apNew[h] = pPage;
}
}
- sqlite3_free(p->apHash);
+ tdsqlite3_free(p->apHash);
p->apHash = apNew;
p->nHash = nNew;
}
@@ -48776,22 +54341,19 @@ static void pcache1ResizeHash(PCache1 *p){
** The PGroup mutex must be held when this function is called.
*/
static PgHdr1 *pcache1PinPage(PgHdr1 *pPage){
- PCache1 *pCache;
-
assert( pPage!=0 );
- assert( pPage->isPinned==0 );
- pCache = pPage->pCache;
+ assert( PAGE_IS_UNPINNED(pPage) );
assert( pPage->pLruNext );
assert( pPage->pLruPrev );
- assert( sqlite3_mutex_held(pCache->pGroup->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pCache->pGroup->mutex) );
pPage->pLruPrev->pLruNext = pPage->pLruNext;
pPage->pLruNext->pLruPrev = pPage->pLruPrev;
pPage->pLruNext = 0;
- pPage->pLruPrev = 0;
- pPage->isPinned = 1;
+ /* pPage->pLruPrev = 0;
+ ** No need to clear pLruPrev as it is never accessed if pLruNext is 0 */
assert( pPage->isAnchor==0 );
- assert( pCache->pGroup->lru.isAnchor==1 );
- pCache->nRecyclable--;
+ assert( pPage->pCache->pGroup->lru.isAnchor==1 );
+ pPage->pCache->nRecyclable--;
return pPage;
}
@@ -48808,7 +54370,7 @@ static void pcache1RemoveFromHash(PgHdr1 *pPage, int freeFlag){
PCache1 *pCache = pPage->pCache;
PgHdr1 **pp;
- assert( sqlite3_mutex_held(pCache->pGroup->mutex) );
+ assert( tdsqlite3_mutex_held(pCache->pGroup->mutex) );
h = pPage->iKey % pCache->nHash;
for(pp=&pCache->apHash[h]; (*pp)!=pPage; pp=&(*pp)->pNext);
*pp = (*pp)->pNext;
@@ -48824,17 +54386,17 @@ static void pcache1RemoveFromHash(PgHdr1 *pPage, int freeFlag){
static void pcache1EnforceMaxPage(PCache1 *pCache){
PGroup *pGroup = pCache->pGroup;
PgHdr1 *p;
- assert( sqlite3_mutex_held(pGroup->mutex) );
- while( pGroup->nCurrentPage>pGroup->nMaxPage
+ assert( tdsqlite3_mutex_held(pGroup->mutex) );
+ while( pGroup->nPurgeable>pGroup->nMaxPage
&& (p=pGroup->lru.pLruPrev)->isAnchor==0
){
assert( p->pCache->pGroup==pGroup );
- assert( p->isPinned==0 );
+ assert( PAGE_IS_UNPINNED(p) );
pcache1PinPage(p);
pcache1RemoveFromHash(p, 1);
}
if( pCache->nPage==0 && pCache->pBulk ){
- sqlite3_free(pCache->pBulk);
+ tdsqlite3_free(pCache->pBulk);
pCache->pBulk = pCache->pFree = 0;
}
}
@@ -48852,7 +54414,7 @@ static void pcache1TruncateUnsafe(
){
TESTONLY( int nPage = 0; ) /* To assert pCache->nPage is correct */
unsigned int h, iStop;
- assert( sqlite3_mutex_held(pCache->pGroup->mutex) );
+ assert( tdsqlite3_mutex_held(pCache->pGroup->mutex) );
assert( pCache->iMaxKey >= iLimit );
assert( pCache->nHash > 0 );
if( pCache->iMaxKey - iLimit < pCache->nHash ){
@@ -48878,7 +54440,7 @@ static void pcache1TruncateUnsafe(
if( pPage->iKey>=iLimit ){
pCache->nPage--;
*pp = pPage->pNext;
- if( !pPage->isPinned ) pcache1PinPage(pPage);
+ if( PAGE_IS_UNPINNED(pPage) ) pcache1PinPage(pPage);
pcache1FreePage(pPage);
}else{
pp = &pPage->pNext;
@@ -48892,10 +54454,10 @@ static void pcache1TruncateUnsafe(
}
/******************************************************************************/
-/******** sqlite3_pcache Methods **********************************************/
+/******** tdsqlite3_pcache Methods **********************************************/
/*
-** Implementation of the sqlite3_pcache.xInit method.
+** Implementation of the tdsqlite3_pcache.xInit method.
*/
static int pcache1Init(void *NotUsed){
UNUSED_PARAMETER(NotUsed);
@@ -48912,7 +54474,7 @@ static int pcache1Init(void *NotUsed){
**
** * Use a unified cache in single-threaded applications that have
** configured a start-time buffer for use as page-cache memory using
- ** sqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N) with non-NULL
+ ** tdsqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N) with non-NULL
** pBuf argument.
**
** * Otherwise use separate caches (mode-1)
@@ -48920,23 +54482,23 @@ static int pcache1Init(void *NotUsed){
#if defined(SQLITE_ENABLE_MEMORY_MANAGEMENT)
pcache1.separateCache = 0;
#elif SQLITE_THREADSAFE
- pcache1.separateCache = sqlite3GlobalConfig.pPage==0
- || sqlite3GlobalConfig.bCoreMutex>0;
+ pcache1.separateCache = tdsqlite3GlobalConfig.pPage==0
+ || tdsqlite3GlobalConfig.bCoreMutex>0;
#else
- pcache1.separateCache = sqlite3GlobalConfig.pPage==0;
+ pcache1.separateCache = tdsqlite3GlobalConfig.pPage==0;
#endif
#if SQLITE_THREADSAFE
- if( sqlite3GlobalConfig.bCoreMutex ){
- pcache1.grp.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_LRU);
- pcache1.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PMEM);
+ if( tdsqlite3GlobalConfig.bCoreMutex ){
+ pcache1.grp.mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_LRU);
+ pcache1.mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PMEM);
}
#endif
if( pcache1.separateCache
- && sqlite3GlobalConfig.nPage!=0
- && sqlite3GlobalConfig.pPage==0
+ && tdsqlite3GlobalConfig.nPage!=0
+ && tdsqlite3GlobalConfig.pPage==0
){
- pcache1.nInitPage = sqlite3GlobalConfig.nPage;
+ pcache1.nInitPage = tdsqlite3GlobalConfig.nPage;
}else{
pcache1.nInitPage = 0;
}
@@ -48946,7 +54508,7 @@ static int pcache1Init(void *NotUsed){
}
/*
-** Implementation of the sqlite3_pcache.xShutdown method.
+** Implementation of the tdsqlite3_pcache.xShutdown method.
** Note that the static mutex allocated in xInit does
** not need to be freed.
*/
@@ -48957,14 +54519,14 @@ static void pcache1Shutdown(void *NotUsed){
}
/* forward declaration */
-static void pcache1Destroy(sqlite3_pcache *p);
+static void pcache1Destroy(tdsqlite3_pcache *p);
/*
-** Implementation of the sqlite3_pcache.xCreate method.
+** Implementation of the tdsqlite3_pcache.xCreate method.
**
** Allocate a new cache.
*/
-static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){
+static tdsqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){
PCache1 *pCache; /* The newly created page cache */
PGroup *pGroup; /* The group the new page cache will belong to */
int sz; /* Bytes of memory required to allocate the new cache */
@@ -48973,7 +54535,7 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){
assert( szExtra < 300 );
sz = sizeof(PCache1) + sizeof(PGroup)*pcache1.separateCache;
- pCache = (PCache1 *)sqlite3MallocZero(sz);
+ pCache = (PCache1 *)tdsqlite3MallocZero(sz);
if( pCache ){
if( pcache1.separateCache ){
pGroup = (PGroup*)&pCache[1];
@@ -48981,6 +54543,7 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){
}else{
pGroup = &pcache1.grp;
}
+ pcache1EnterMutex(pGroup);
if( pGroup->lru.isAnchor==0 ){
pGroup->lru.isAnchor = 1;
pGroup->lru.pLruPrev = pGroup->lru.pLruNext = &pGroup->lru;
@@ -48990,28 +54553,30 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){
pCache->szExtra = szExtra;
pCache->szAlloc = szPage + szExtra + ROUND8(sizeof(PgHdr1));
pCache->bPurgeable = (bPurgeable ? 1 : 0);
- pcache1EnterMutex(pGroup);
pcache1ResizeHash(pCache);
if( bPurgeable ){
pCache->nMin = 10;
pGroup->nMinPage += pCache->nMin;
pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage;
+ pCache->pnPurgeable = &pGroup->nPurgeable;
+ }else{
+ pCache->pnPurgeable = &pCache->nPurgeableDummy;
}
pcache1LeaveMutex(pGroup);
if( pCache->nHash==0 ){
- pcache1Destroy((sqlite3_pcache*)pCache);
+ pcache1Destroy((tdsqlite3_pcache*)pCache);
pCache = 0;
}
}
- return (sqlite3_pcache *)pCache;
+ return (tdsqlite3_pcache *)pCache;
}
/*
-** Implementation of the sqlite3_pcache.xCachesize method.
+** Implementation of the tdsqlite3_pcache.xCachesize method.
**
** Configure the cache_size limit for a cache.
*/
-static void pcache1Cachesize(sqlite3_pcache *p, int nMax){
+static void pcache1Cachesize(tdsqlite3_pcache *p, int nMax){
PCache1 *pCache = (PCache1 *)p;
if( pCache->bPurgeable ){
PGroup *pGroup = pCache->pGroup;
@@ -49026,11 +54591,11 @@ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){
}
/*
-** Implementation of the sqlite3_pcache.xShrink method.
+** Implementation of the tdsqlite3_pcache.xShrink method.
**
** Free up as much memory as possible.
*/
-static void pcache1Shrink(sqlite3_pcache *p){
+static void pcache1Shrink(tdsqlite3_pcache *p){
PCache1 *pCache = (PCache1*)p;
if( pCache->bPurgeable ){
PGroup *pGroup = pCache->pGroup;
@@ -49045,9 +54610,9 @@ static void pcache1Shrink(sqlite3_pcache *p){
}
/*
-** Implementation of the sqlite3_pcache.xPagecount method.
+** Implementation of the tdsqlite3_pcache.xPagecount method.
*/
-static int pcache1Pagecount(sqlite3_pcache *p){
+static int pcache1Pagecount(tdsqlite3_pcache *p){
int n;
PCache1 *pCache = (PCache1*)p;
pcache1EnterMutex(pCache->pGroup);
@@ -49097,7 +54662,7 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
){
PCache1 *pOther;
pPage = pGroup->lru.pLruPrev;
- assert( pPage->isPinned==0 );
+ assert( PAGE_IS_UNPINNED(pPage) );
pcache1RemoveFromHash(pPage, 0);
pcache1PinPage(pPage);
pOther = pPage->pCache;
@@ -49105,7 +54670,7 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
pcache1FreePage(pPage);
pPage = 0;
}else{
- pGroup->nCurrentPage -= (pOther->bPurgeable - pCache->bPurgeable);
+ pGroup->nPurgeable -= (pOther->bPurgeable - pCache->bPurgeable);
}
}
@@ -49122,9 +54687,9 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
pPage->iKey = iKey;
pPage->pNext = pCache->apHash[h];
pPage->pCache = pCache;
- pPage->pLruPrev = 0;
pPage->pLruNext = 0;
- pPage->isPinned = 1;
+ /* pPage->pLruPrev = 0;
+ ** No need to clear pLruPrev since it is not accessed when pLruNext==0 */
*(void **)pPage->page.pExtra = 0;
pCache->apHash[h] = pPage;
if( iKey>pCache->iMaxKey ){
@@ -49135,7 +54700,7 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
}
/*
-** Implementation of the sqlite3_pcache.xFetch method.
+** Implementation of the tdsqlite3_pcache.xFetch method.
**
** Fetch a page by key value.
**
@@ -49194,7 +54759,7 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2(
** invokes the appropriate routine.
*/
static PgHdr1 *pcache1FetchNoMutex(
- sqlite3_pcache *p,
+ tdsqlite3_pcache *p,
unsigned int iKey,
int createFlag
){
@@ -49210,7 +54775,7 @@ static PgHdr1 *pcache1FetchNoMutex(
** Otherwise (page not in hash and createFlag!=0) continue with
** subsequent steps to try to create the page. */
if( pPage ){
- if( !pPage->isPinned ){
+ if( PAGE_IS_UNPINNED(pPage) ){
return pcache1PinPage(pPage);
}else{
return pPage;
@@ -49224,7 +54789,7 @@ static PgHdr1 *pcache1FetchNoMutex(
}
#if PCACHE1_MIGHT_USE_GROUP_MUTEX
static PgHdr1 *pcache1FetchWithMutex(
- sqlite3_pcache *p,
+ tdsqlite3_pcache *p,
unsigned int iKey,
int createFlag
){
@@ -49238,8 +54803,8 @@ static PgHdr1 *pcache1FetchWithMutex(
return pPage;
}
#endif
-static sqlite3_pcache_page *pcache1Fetch(
- sqlite3_pcache *p,
+static tdsqlite3_pcache_page *pcache1Fetch(
+ tdsqlite3_pcache *p,
unsigned int iKey,
int createFlag
){
@@ -49255,23 +54820,23 @@ static sqlite3_pcache_page *pcache1Fetch(
assert( pCache->nHash>0 );
#if PCACHE1_MIGHT_USE_GROUP_MUTEX
if( pCache->pGroup->mutex ){
- return (sqlite3_pcache_page*)pcache1FetchWithMutex(p, iKey, createFlag);
+ return (tdsqlite3_pcache_page*)pcache1FetchWithMutex(p, iKey, createFlag);
}else
#endif
{
- return (sqlite3_pcache_page*)pcache1FetchNoMutex(p, iKey, createFlag);
+ return (tdsqlite3_pcache_page*)pcache1FetchNoMutex(p, iKey, createFlag);
}
}
/*
-** Implementation of the sqlite3_pcache.xUnpin method.
+** Implementation of the tdsqlite3_pcache.xUnpin method.
**
** Mark a page as unpinned (eligible for asynchronous recycling).
*/
static void pcache1Unpin(
- sqlite3_pcache *p,
- sqlite3_pcache_page *pPg,
+ tdsqlite3_pcache *p,
+ tdsqlite3_pcache_page *pPg,
int reuseUnlikely
){
PCache1 *pCache = (PCache1 *)p;
@@ -49284,10 +54849,10 @@ static void pcache1Unpin(
/* It is an error to call this function if the page is already
** part of the PGroup LRU list.
*/
- assert( pPage->pLruPrev==0 && pPage->pLruNext==0 );
- assert( pPage->isPinned==1 );
+ assert( pPage->pLruNext==0 );
+ assert( PAGE_IS_PINNED(pPage) );
- if( reuseUnlikely || pGroup->nCurrentPage>pGroup->nMaxPage ){
+ if( reuseUnlikely || pGroup->nPurgeable>pGroup->nMaxPage ){
pcache1RemoveFromHash(pPage, 1);
}else{
/* Add the page to the PGroup LRU list. */
@@ -49296,18 +54861,17 @@ static void pcache1Unpin(
(pPage->pLruNext = *ppFirst)->pLruPrev = pPage;
*ppFirst = pPage;
pCache->nRecyclable++;
- pPage->isPinned = 0;
}
pcache1LeaveMutex(pCache->pGroup);
}
/*
-** Implementation of the sqlite3_pcache.xRekey method.
+** Implementation of the tdsqlite3_pcache.xRekey method.
*/
static void pcache1Rekey(
- sqlite3_pcache *p,
- sqlite3_pcache_page *pPg,
+ tdsqlite3_pcache *p,
+ tdsqlite3_pcache_page *pPg,
unsigned int iOld,
unsigned int iNew
){
@@ -49339,13 +54903,13 @@ static void pcache1Rekey(
}
/*
-** Implementation of the sqlite3_pcache.xTruncate method.
+** Implementation of the tdsqlite3_pcache.xTruncate method.
**
** Discard all unpinned pages in the cache with a page number equal to
** or greater than parameter iLimit. Any pinned pages with a page number
** equal to or greater than iLimit are implicitly unpinned.
*/
-static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){
+static void pcache1Truncate(tdsqlite3_pcache *p, unsigned int iLimit){
PCache1 *pCache = (PCache1 *)p;
pcache1EnterMutex(pCache->pGroup);
if( iLimit<=pCache->iMaxKey ){
@@ -49356,11 +54920,11 @@ static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){
}
/*
-** Implementation of the sqlite3_pcache.xDestroy method.
+** Implementation of the tdsqlite3_pcache.xDestroy method.
**
** Destroy a cache allocated using pcache1Create().
*/
-static void pcache1Destroy(sqlite3_pcache *p){
+static void pcache1Destroy(tdsqlite3_pcache *p){
PCache1 *pCache = (PCache1 *)p;
PGroup *pGroup = pCache->pGroup;
assert( pCache->bPurgeable || (pCache->nMax==0 && pCache->nMin==0) );
@@ -49373,18 +54937,18 @@ static void pcache1Destroy(sqlite3_pcache *p){
pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage;
pcache1EnforceMaxPage(pCache);
pcache1LeaveMutex(pGroup);
- sqlite3_free(pCache->pBulk);
- sqlite3_free(pCache->apHash);
- sqlite3_free(pCache);
+ tdsqlite3_free(pCache->pBulk);
+ tdsqlite3_free(pCache->apHash);
+ tdsqlite3_free(pCache);
}
/*
-** This function is called during initialization (sqlite3_initialize()) to
+** This function is called during initialization (tdsqlite3_initialize()) to
** install the default pluggable cache module, assuming the user has not
** already provided an alternative.
*/
-SQLITE_PRIVATE void sqlite3PCacheSetDefault(void){
- static const sqlite3_pcache_methods2 defaultMethods = {
+SQLITE_PRIVATE void tdsqlite3PCacheSetDefault(void){
+ static const tdsqlite3_pcache_methods2 defaultMethods = {
1, /* iVersion */
0, /* pArg */
pcache1Init, /* xInit */
@@ -49399,19 +54963,19 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void){
pcache1Destroy, /* xDestroy */
pcache1Shrink /* xShrink */
};
- sqlite3_config(SQLITE_CONFIG_PCACHE2, &defaultMethods);
+ tdsqlite3_config(SQLITE_CONFIG_PCACHE2, &defaultMethods);
}
/*
** Return the size of the header on each page of this PCACHE implementation.
*/
-SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void){ return ROUND8(sizeof(PgHdr1)); }
+SQLITE_PRIVATE int tdsqlite3HeaderSizePcache1(void){ return ROUND8(sizeof(PgHdr1)); }
/*
** Return the global mutex used by this PCACHE implementation. The
-** sqlite3_status() routine needs access to this mutex.
+** tdsqlite3_status() routine needs access to this mutex.
*/
-SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void){
+SQLITE_PRIVATE tdsqlite3_mutex *tdsqlite3Pcache1Mutex(void){
return pcache1.mutex;
}
@@ -49419,17 +54983,17 @@ SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void){
/*
** This function is called to free superfluous dynamically allocated memory
** held by the pager system. Memory in use by any SQLite pager allocated
-** by the current thread may be sqlite3_free()ed.
+** by the current thread may be tdsqlite3_free()ed.
**
** nReq is the number of bytes of memory required. Once this much has
** been released, the function returns. The return value is the total number
** of bytes of memory released.
*/
-SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){
+SQLITE_PRIVATE int tdsqlite3PcacheReleaseMemory(int nReq){
int nFree = 0;
- assert( sqlite3_mutex_notheld(pcache1.grp.mutex) );
- assert( sqlite3_mutex_notheld(pcache1.mutex) );
- if( sqlite3GlobalConfig.nPage==0 ){
+ assert( tdsqlite3_mutex_notheld(pcache1.grp.mutex) );
+ assert( tdsqlite3_mutex_notheld(pcache1.mutex) );
+ if( tdsqlite3GlobalConfig.pPage==0 ){
PgHdr1 *p;
pcache1EnterMutex(&pcache1.grp);
while( (nReq<0 || nFree<nReq)
@@ -49438,9 +55002,9 @@ SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){
){
nFree += pcache1MemSize(p->page.pBuf);
#ifdef SQLITE_PCACHE_SEPARATE_HEADER
- nFree += sqlite3MemSize(p);
+ nFree += tdsqlite3MemSize(p);
#endif
- assert( p->isPinned==0 );
+ assert( PAGE_IS_UNPINNED(p) );
pcache1PinPage(p);
pcache1RemoveFromHash(p, 1);
}
@@ -49455,7 +55019,7 @@ SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){
** This function is used by test procedures to inspect the internal state
** of the global cache.
*/
-SQLITE_PRIVATE void sqlite3PcacheStats(
+SQLITE_PRIVATE void tdsqlite3PcacheStats(
int *pnCurrent, /* OUT: Total number of pages cached */
int *pnMax, /* OUT: Global maximum cache size */
int *pnMin, /* OUT: Sum of PCache1.nMin for purgeable caches */
@@ -49464,10 +55028,10 @@ SQLITE_PRIVATE void sqlite3PcacheStats(
PgHdr1 *p;
int nRecyclable = 0;
for(p=pcache1.grp.lru.pLruNext; p && !p->isAnchor; p=p->pLruNext){
- assert( p->isPinned==0 );
+ assert( PAGE_IS_UNPINNED(p) );
nRecyclable++;
}
- *pnCurrent = pcache1.grp.nCurrentPage;
+ *pnCurrent = pcache1.grp.nPurgeable;
*pnMax = (int)pcache1.grp.nMaxPage;
*pnMin = (int)pcache1.grp.nMinPage;
*pnRecyclable = nRecyclable;
@@ -49585,7 +55149,7 @@ struct RowSetChunk {
*/
struct RowSet {
struct RowSetChunk *pChunk; /* List of all chunk allocations */
- sqlite3 *db; /* The database connection */
+ tdsqlite3 *db; /* The database connection */
struct RowSetEntry *pEntry; /* List of entries using pRight */
struct RowSetEntry *pLast; /* Last entry on the pEntry list */
struct RowSetEntry *pFresh; /* Source of new entry objects */
@@ -49599,33 +55163,26 @@ struct RowSet {
** Allowed values for RowSet.rsFlags
*/
#define ROWSET_SORTED 0x01 /* True if RowSet.pEntry is sorted */
-#define ROWSET_NEXT 0x02 /* True if sqlite3RowSetNext() has been called */
+#define ROWSET_NEXT 0x02 /* True if tdsqlite3RowSetNext() has been called */
/*
-** Turn bulk memory into a RowSet object. N bytes of memory
-** are available at pSpace. The db pointer is used as a memory context
-** for any subsequent allocations that need to occur.
-** Return a pointer to the new RowSet object.
-**
-** It must be the case that N is sufficient to make a Rowset. If not
-** an assertion fault occurs.
-**
-** If N is larger than the minimum, use the surplus as an initial
-** allocation of entries available to be filled.
+** Allocate a RowSet object. Return NULL if a memory allocation
+** error occurs.
*/
-SQLITE_PRIVATE RowSet *sqlite3RowSetInit(sqlite3 *db, void *pSpace, unsigned int N){
- RowSet *p;
- assert( N >= ROUND8(sizeof(*p)) );
- p = pSpace;
- p->pChunk = 0;
- p->db = db;
- p->pEntry = 0;
- p->pLast = 0;
- p->pForest = 0;
- p->pFresh = (struct RowSetEntry*)(ROUND8(sizeof(*p)) + (char*)p);
- p->nFresh = (u16)((N - ROUND8(sizeof(*p)))/sizeof(struct RowSetEntry));
- p->rsFlags = ROWSET_SORTED;
- p->iBatch = 0;
+SQLITE_PRIVATE RowSet *tdsqlite3RowSetInit(tdsqlite3 *db){
+ RowSet *p = tdsqlite3DbMallocRawNN(db, sizeof(*p));
+ if( p ){
+ int N = tdsqlite3DbMallocSize(db, p);
+ p->pChunk = 0;
+ p->db = db;
+ p->pEntry = 0;
+ p->pLast = 0;
+ p->pForest = 0;
+ p->pFresh = (struct RowSetEntry*)(ROUND8(sizeof(*p)) + (char*)p);
+ p->nFresh = (u16)((N - ROUND8(sizeof(*p)))/sizeof(struct RowSetEntry));
+ p->rsFlags = ROWSET_SORTED;
+ p->iBatch = 0;
+ }
return p;
}
@@ -49634,11 +55191,12 @@ SQLITE_PRIVATE RowSet *sqlite3RowSetInit(sqlite3 *db, void *pSpace, unsigned int
** the RowSet has allocated over its lifetime. This routine is
** the destructor for the RowSet.
*/
-SQLITE_PRIVATE void sqlite3RowSetClear(RowSet *p){
+SQLITE_PRIVATE void tdsqlite3RowSetClear(void *pArg){
+ RowSet *p = (RowSet*)pArg;
struct RowSetChunk *pChunk, *pNextChunk;
for(pChunk=p->pChunk; pChunk; pChunk = pNextChunk){
pNextChunk = pChunk->pNextChunk;
- sqlite3DbFree(p->db, pChunk);
+ tdsqlite3DbFree(p->db, pChunk);
}
p->pChunk = 0;
p->nFresh = 0;
@@ -49649,6 +55207,16 @@ SQLITE_PRIVATE void sqlite3RowSetClear(RowSet *p){
}
/*
+** Deallocate all chunks from a RowSet. This frees all memory that
+** the RowSet has allocated over its lifetime. This routine is
+** the destructor for the RowSet.
+*/
+SQLITE_PRIVATE void tdsqlite3RowSetDelete(void *pArg){
+ tdsqlite3RowSetClear(pArg);
+ tdsqlite3DbFree(((RowSet*)pArg)->db, pArg);
+}
+
+/*
** Allocate a new RowSetEntry object that is associated with the
** given RowSet. Return a pointer to the new and completely uninitialized
** objected.
@@ -49662,7 +55230,7 @@ static struct RowSetEntry *rowSetEntryAlloc(RowSet *p){
/* We could allocate a fresh RowSetEntry each time one is needed, but it
** is more efficient to pull a preallocated entry from the pool */
struct RowSetChunk *pNew;
- pNew = sqlite3DbMallocRawNN(p->db, sizeof(*pNew));
+ pNew = tdsqlite3DbMallocRawNN(p->db, sizeof(*pNew));
if( pNew==0 ){
return 0;
}
@@ -49681,11 +55249,11 @@ static struct RowSetEntry *rowSetEntryAlloc(RowSet *p){
** The mallocFailed flag of the database connection is set if a
** memory allocation fails.
*/
-SQLITE_PRIVATE void sqlite3RowSetInsert(RowSet *p, i64 rowid){
+SQLITE_PRIVATE void tdsqlite3RowSetInsert(RowSet *p, i64 rowid){
struct RowSetEntry *pEntry; /* The new entry */
struct RowSetEntry *pLast; /* The last prior entry */
- /* This routine is never called after sqlite3RowSetNext() */
+ /* This routine is never called after tdsqlite3RowSetNext() */
assert( p!=0 && (p->rsFlags & ROWSET_NEXT)==0 );
pEntry = rowSetEntryAlloc(p);
@@ -49871,17 +55439,17 @@ static struct RowSetEntry *rowSetListToTree(struct RowSetEntry *pList){
** Write the element into *pRowid. Return 1 on success. Return
** 0 if the RowSet is already empty.
**
-** After this routine has been called, the sqlite3RowSetInsert()
+** After this routine has been called, the tdsqlite3RowSetInsert()
** routine may not be called again.
**
-** This routine may not be called after sqlite3RowSetTest() has
+** This routine may not be called after tdsqlite3RowSetTest() has
** been used. Older versions of RowSet allowed that, but as the
** capability was not used by the code generator, it was removed
** for code economy.
*/
-SQLITE_PRIVATE int sqlite3RowSetNext(RowSet *p, i64 *pRowid){
+SQLITE_PRIVATE int tdsqlite3RowSetNext(RowSet *p, i64 *pRowid){
assert( p!=0 );
- assert( p->pForest==0 ); /* Cannot be used with sqlite3RowSetText() */
+ assert( p->pForest==0 ); /* Cannot be used with tdsqlite3RowSetText() */
/* Merge the forest into a single sorted list on first call */
if( (p->rsFlags & ROWSET_NEXT)==0 ){ /*OPTIMIZATION-IF-FALSE*/
@@ -49896,8 +55464,8 @@ SQLITE_PRIVATE int sqlite3RowSetNext(RowSet *p, i64 *pRowid){
*pRowid = p->pEntry->v;
p->pEntry = p->pEntry->pRight;
if( p->pEntry==0 ){ /*OPTIMIZATION-IF-TRUE*/
- /* Free memory immediately, rather than waiting on sqlite3_finalize() */
- sqlite3RowSetClear(p);
+ /* Free memory immediately, rather than waiting on tdsqlite3_finalize() */
+ tdsqlite3RowSetClear(p);
}
return 1;
}else{
@@ -49913,10 +55481,10 @@ SQLITE_PRIVATE int sqlite3RowSetNext(RowSet *p, i64 *pRowid){
** on pRowSet->pEntry, then sort those entries into the forest at
** pRowSet->pForest so that they can be tested.
*/
-SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 iRowid){
+SQLITE_PRIVATE int tdsqlite3RowSetTest(RowSet *pRowSet, int iBatch, tdsqlite3_int64 iRowid){
struct RowSetEntry *p, *pTree;
- /* This routine is never called after sqlite3RowSetNext() */
+ /* This routine is never called after tdsqlite3RowSetNext() */
assert( pRowSet!=0 && (pRowSet->rsFlags & ROWSET_NEXT)==0 );
/* Sort entries into the forest on the first test of a new batch.
@@ -50022,32 +55590,32 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64
/* #include "sqliteInt.h" */
-/* Additional values that can be added to the sync_flags argument of
-** sqlite3WalFrames():
+/* Macros for extracting appropriate sync flags for either transaction
+** commits (WAL_SYNC_FLAGS(X)) or for checkpoint ops (CKPT_SYNC_FLAGS(X)):
*/
-#define WAL_SYNC_TRANSACTIONS 0x20 /* Sync at the end of each transaction */
-#define SQLITE_SYNC_MASK 0x13 /* Mask off the SQLITE_SYNC_* values */
+#define WAL_SYNC_FLAGS(X) ((X)&0x03)
+#define CKPT_SYNC_FLAGS(X) (((X)>>2)&0x03)
#ifdef SQLITE_OMIT_WAL
-# define sqlite3WalOpen(x,y,z) 0
-# define sqlite3WalLimit(x,y)
-# define sqlite3WalClose(w,x,y,z) 0
-# define sqlite3WalBeginReadTransaction(y,z) 0
-# define sqlite3WalEndReadTransaction(z)
-# define sqlite3WalDbsize(y) 0
-# define sqlite3WalBeginWriteTransaction(y) 0
-# define sqlite3WalEndWriteTransaction(x) 0
-# define sqlite3WalUndo(x,y,z) 0
-# define sqlite3WalSavepoint(y,z)
-# define sqlite3WalSavepointUndo(y,z) 0
-# define sqlite3WalFrames(u,v,w,x,y,z) 0
-# define sqlite3WalCheckpoint(r,s,t,u,v,w,x,y,z) 0
-# define sqlite3WalCallback(z) 0
-# define sqlite3WalExclusiveMode(y,z) 0
-# define sqlite3WalHeapMemory(z) 0
-# define sqlite3WalFramesize(z) 0
-# define sqlite3WalFindFrame(x,y,z) 0
-# define sqlite3WalFile(x) 0
+# define tdsqlite3WalOpen(x,y,z) 0
+# define tdsqlite3WalLimit(x,y)
+# define tdsqlite3WalClose(v,w,x,y,z) 0
+# define tdsqlite3WalBeginReadTransaction(y,z) 0
+# define tdsqlite3WalEndReadTransaction(z)
+# define tdsqlite3WalDbsize(y) 0
+# define tdsqlite3WalBeginWriteTransaction(y) 0
+# define tdsqlite3WalEndWriteTransaction(x) 0
+# define tdsqlite3WalUndo(x,y,z) 0
+# define tdsqlite3WalSavepoint(y,z)
+# define tdsqlite3WalSavepointUndo(y,z) 0
+# define tdsqlite3WalFrames(u,v,w,x,y,z) 0
+# define tdsqlite3WalCheckpoint(q,r,s,t,u,v,w,x,y,z) 0
+# define tdsqlite3WalCallback(z) 0
+# define tdsqlite3WalExclusiveMode(y,z) 0
+# define tdsqlite3WalHeapMemory(z) 0
+# define tdsqlite3WalFramesize(z) 0
+# define tdsqlite3WalFindFrame(x,y,z) 0
+# define tdsqlite3WalFile(x) 0
#else
#define WAL_SAVEPOINT_NDATA 4
@@ -50058,50 +55626,51 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64
typedef struct Wal Wal;
/* Open and close a connection to a write-ahead log. */
-SQLITE_PRIVATE int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *, int, i64, Wal**);
-SQLITE_PRIVATE int sqlite3WalClose(Wal *pWal, int sync_flags, int, u8 *);
+SQLITE_PRIVATE int tdsqlite3WalOpen(tdsqlite3_vfs*, tdsqlite3_file*, const char *, int, i64, Wal**);
+SQLITE_PRIVATE int tdsqlite3WalClose(Wal *pWal, tdsqlite3*, int sync_flags, int, u8 *);
/* Set the limiting size of a WAL file. */
-SQLITE_PRIVATE void sqlite3WalLimit(Wal*, i64);
+SQLITE_PRIVATE void tdsqlite3WalLimit(Wal*, i64);
/* Used by readers to open (lock) and close (unlock) a snapshot. A
** snapshot is like a read-transaction. It is the state of the database
-** at an instant in time. sqlite3WalOpenSnapshot gets a read lock and
+** at an instant in time. tdsqlite3WalOpenSnapshot gets a read lock and
** preserves the current state even if the other threads or processes
-** write to or checkpoint the WAL. sqlite3WalCloseSnapshot() closes the
+** write to or checkpoint the WAL. tdsqlite3WalCloseSnapshot() closes the
** transaction and releases the lock.
*/
-SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *);
-SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal);
+SQLITE_PRIVATE int tdsqlite3WalBeginReadTransaction(Wal *pWal, int *);
+SQLITE_PRIVATE void tdsqlite3WalEndReadTransaction(Wal *pWal);
/* Read a page from the write-ahead log, if it is present. */
-SQLITE_PRIVATE int sqlite3WalFindFrame(Wal *, Pgno, u32 *);
-SQLITE_PRIVATE int sqlite3WalReadFrame(Wal *, u32, int, u8 *);
+SQLITE_PRIVATE int tdsqlite3WalFindFrame(Wal *, Pgno, u32 *);
+SQLITE_PRIVATE int tdsqlite3WalReadFrame(Wal *, u32, int, u8 *);
/* If the WAL is not empty, return the size of the database. */
-SQLITE_PRIVATE Pgno sqlite3WalDbsize(Wal *pWal);
+SQLITE_PRIVATE Pgno tdsqlite3WalDbsize(Wal *pWal);
/* Obtain or release the WRITER lock. */
-SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal);
-SQLITE_PRIVATE int sqlite3WalEndWriteTransaction(Wal *pWal);
+SQLITE_PRIVATE int tdsqlite3WalBeginWriteTransaction(Wal *pWal);
+SQLITE_PRIVATE int tdsqlite3WalEndWriteTransaction(Wal *pWal);
/* Undo any frames written (but not committed) to the log */
-SQLITE_PRIVATE int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx);
+SQLITE_PRIVATE int tdsqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx);
/* Return an integer that records the current (uncommitted) write
** position in the WAL */
-SQLITE_PRIVATE void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData);
+SQLITE_PRIVATE void tdsqlite3WalSavepoint(Wal *pWal, u32 *aWalData);
/* Move the write position of the WAL back to iFrame. Called in
** response to a ROLLBACK TO command. */
-SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData);
+SQLITE_PRIVATE int tdsqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData);
/* Write a frame or frames to the log. */
-SQLITE_PRIVATE int sqlite3WalFrames(Wal *pWal, int, PgHdr *, Pgno, int, int);
+SQLITE_PRIVATE int tdsqlite3WalFrames(Wal *pWal, int, PgHdr *, Pgno, int, int);
/* Copy pages from the log to the database file */
-SQLITE_PRIVATE int sqlite3WalCheckpoint(
+SQLITE_PRIVATE int tdsqlite3WalCheckpoint(
Wal *pWal, /* Write-ahead log connection */
+ tdsqlite3 *db, /* Check this handle's interrupt flag */
int eMode, /* One of PASSIVE, FULL and RESTART */
int (*xBusy)(void*), /* Function to call when busy */
void *pBusyArg, /* Context argument for xBusyHandler */
@@ -50112,38 +55681,41 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
int *pnCkpt /* OUT: Number of backfilled frames in WAL */
);
-/* Return the value to pass to a sqlite3_wal_hook callback, the
+/* Return the value to pass to a tdsqlite3_wal_hook callback, the
** number of frames in the WAL at the point of the last commit since
-** sqlite3WalCallback() was called. If no commits have occurred since
+** tdsqlite3WalCallback() was called. If no commits have occurred since
** the last call, then return 0.
*/
-SQLITE_PRIVATE int sqlite3WalCallback(Wal *pWal);
+SQLITE_PRIVATE int tdsqlite3WalCallback(Wal *pWal);
/* Tell the wal layer that an EXCLUSIVE lock has been obtained (or released)
** by the pager layer on the database file.
*/
-SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op);
+SQLITE_PRIVATE int tdsqlite3WalExclusiveMode(Wal *pWal, int op);
/* Return true if the argument is non-NULL and the WAL module is using
** heap-memory for the wal-index. Otherwise, if the argument is NULL or the
** WAL module is using shared-memory, return false.
*/
-SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal);
+SQLITE_PRIVATE int tdsqlite3WalHeapMemory(Wal *pWal);
#ifdef SQLITE_ENABLE_SNAPSHOT
-SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
-SQLITE_PRIVATE void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
+SQLITE_PRIVATE int tdsqlite3WalSnapshotGet(Wal *pWal, tdsqlite3_snapshot **ppSnapshot);
+SQLITE_PRIVATE void tdsqlite3WalSnapshotOpen(Wal *pWal, tdsqlite3_snapshot *pSnapshot);
+SQLITE_PRIVATE int tdsqlite3WalSnapshotRecover(Wal *pWal);
+SQLITE_PRIVATE int tdsqlite3WalSnapshotCheck(Wal *pWal, tdsqlite3_snapshot *pSnapshot);
+SQLITE_PRIVATE void tdsqlite3WalSnapshotUnlock(Wal *pWal);
#endif
#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).
*/
-SQLITE_PRIVATE int sqlite3WalFramesize(Wal *pWal);
+SQLITE_PRIVATE int tdsqlite3WalFramesize(Wal *pWal);
#endif
-/* Return the sqlite3_file object for the WAL file */
-SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal);
+/* Return the tdsqlite3_file object for the WAL file */
+SQLITE_PRIVATE tdsqlite3_file *tdsqlite3WalFile(Wal *pWal);
#endif /* ifndef SQLITE_OMIT_WAL */
#endif /* SQLITE_WAL_H */
@@ -50242,9 +55814,9 @@ SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal);
** Macros for troubleshooting. Normally turned off
*/
#if 0
-int sqlite3PagerTrace=1; /* True to enable tracing */
-#define sqlite3DebugPrintf printf
-#define PAGERTRACE(X) if( sqlite3PagerTrace ){ sqlite3DebugPrintf X; }
+int tdsqlite3PagerTrace=1; /* True to enable tracing */
+#define tdsqlite3DebugPrintf printf
+#define PAGERTRACE(X) if( tdsqlite3PagerTrace ){ tdsqlite3DebugPrintf X; }
#else
#define PAGERTRACE(X)
#endif
@@ -50254,11 +55826,11 @@ int sqlite3PagerTrace=1; /* True to enable tracing */
** to print out file-descriptors.
**
** PAGERID() takes a pointer to a Pager struct as its argument. The
-** associated file-descriptor is returned. FILEHANDLEID() takes an sqlite3_file
+** associated file-descriptor is returned. FILEHANDLEID() takes an tdsqlite3_file
** struct as its argument.
*/
-#define PAGERID(p) ((int)(p->fd))
-#define FILEHANDLEID(fd) ((int)fd)
+#define PAGERID(p) (SQLITE_PTR_TO_INT(p->fd))
+#define FILEHANDLEID(fd) (SQLITE_PTR_TO_INT(fd))
/*
** The Pager.eState variable stores the current 'state' of a pager. A
@@ -50285,13 +55857,13 @@ int sqlite3PagerTrace=1; /* True to enable tracing */
**
** List of state transitions and the C [function] that performs each:
**
-** OPEN -> READER [sqlite3PagerSharedLock]
+** OPEN -> READER [tdsqlite3PagerSharedLock]
** READER -> OPEN [pager_unlock]
**
-** READER -> WRITER_LOCKED [sqlite3PagerBegin]
+** READER -> WRITER_LOCKED [tdsqlite3PagerBegin]
** WRITER_LOCKED -> WRITER_CACHEMOD [pager_open_journal]
** WRITER_CACHEMOD -> WRITER_DBMOD [syncJournal]
-** WRITER_DBMOD -> WRITER_FINISHED [sqlite3PagerCommitPhaseOne]
+** WRITER_DBMOD -> WRITER_FINISHED [tdsqlite3PagerCommitPhaseOne]
** WRITER_*** -> READER [pager_end_transaction]
**
** WRITER_*** -> ERROR [pager_error]
@@ -50354,7 +55926,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */
** * If the connection is open in rollback-mode, a RESERVED or greater
** lock is held on the database file.
** * If the connection is open in WAL-mode, a WAL write transaction
-** is open (i.e. sqlite3WalBeginWriteTransaction() has been successfully
+** is open (i.e. tdsqlite3WalBeginWriteTransaction() has been successfully
** called).
** * The dbSize, dbOrigSize and dbFileSize variables are all valid.
** * The contents of the pager cache have not been modified.
@@ -50438,10 +56010,10 @@ int sqlite3PagerTrace=1; /* True to enable tracing */
** Specifically, the pager jumps into the ERROR state if:
**
** 1. An error occurs while attempting a rollback. This happens in
-** function sqlite3PagerRollback().
+** function tdsqlite3PagerRollback().
**
** 2. An error occurs while attempting to finalize a journal file
-** following a commit in function sqlite3PagerCommitPhaseTwo().
+** following a commit in function tdsqlite3PagerCommitPhaseTwo().
**
** 3. An error occurs while attempting to write to the journal or
** database file in function pagerStress() in order to free up
@@ -50562,7 +56134,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */
** An instance of the following structure is allocated for each active
** savepoint and statement transaction in the system. All such structures
** are stored in the Pager.aSavepoint[] array, which is allocated and
-** resized using sqlite3Realloc().
+** resized using tdsqlite3Realloc().
**
** When a savepoint is created, the PagerSavepoint.iHdrOffset field is
** set to 0. If a journal-header is written into the main journal while
@@ -50672,7 +56244,7 @@ struct PagerSavepoint {
**
** If the SPILLFLAG_NOSYNC bit is set, writing to the database from
** pagerStress() is permitted, but syncing the journal file is not.
-** This flag is set by sqlite3PagerWrite() when the file-system sector-size
+** This flag is set by tdsqlite3PagerWrite() when the file-system sector-size
** is larger than the database page-size in order to prevent a journal sync
** from happening in between the journalling of two pages on the same sector.
**
@@ -50745,18 +56317,29 @@ struct PagerSavepoint {
** is set to zero in all other states. In PAGER_ERROR state, Pager.errCode
** is always set to SQLITE_FULL, SQLITE_IOERR or one of the SQLITE_IOERR_XXX
** sub-codes.
+**
+** syncFlags, walSyncFlags
+**
+** syncFlags is either SQLITE_SYNC_NORMAL (0x02) or SQLITE_SYNC_FULL (0x03).
+** syncFlags is used for rollback mode. walSyncFlags is used for WAL mode
+** and contains the flags used to sync the checkpoint operations in the
+** lower two bits, and sync flags used for transaction commits in the WAL
+** file in bits 0x04 and 0x08. In other words, to get the correct sync flags
+** for checkpoint operations, use (walSyncFlags&0x03) and to get the correct
+** sync flags for transaction commit, use ((walSyncFlags>>2)&0x03). Note
+** that with synchronous=NORMAL in WAL mode, transaction commit is not synced
+** meaning that the 0x04 and 0x08 bits are both zero.
*/
struct Pager {
- sqlite3_vfs *pVfs; /* OS functions to use for IO */
+ tdsqlite3_vfs *pVfs; /* OS functions to use for IO */
u8 exclusiveMode; /* Boolean. True if locking_mode==EXCLUSIVE */
u8 journalMode; /* One of the PAGER_JOURNALMODE_* values */
u8 useJournal; /* Use a rollback journal on this file */
u8 noSync; /* Do not sync the journal if true */
u8 fullSync; /* Do extra syncs of the journal for robustness */
u8 extraSync; /* sync directory after journal delete */
- u8 ckptSyncFlags; /* SYNC_NORMAL or SYNC_FULL for checkpoint */
- u8 walSyncFlags; /* SYNC_NORMAL or SYNC_FULL for wal writes */
u8 syncFlags; /* SYNC_NORMAL or SYNC_FULL otherwise */
+ u8 walSyncFlags; /* See description above */
u8 tempFile; /* zFilename is a temporary or immutable file */
u8 noLock; /* Do not lock (except in WAL mode) */
u8 readOnly; /* True for a read-only database */
@@ -50788,19 +56371,19 @@ struct Pager {
u32 cksumInit; /* Quasi-random value added to every checksum */
u32 nSubRec; /* Number of records written to sub-journal */
Bitvec *pInJournal; /* One bit for each page in the database file */
- sqlite3_file *fd; /* File descriptor for database */
- sqlite3_file *jfd; /* File descriptor for main journal */
- sqlite3_file *sjfd; /* File descriptor for sub-journal */
+ tdsqlite3_file *fd; /* File descriptor for database */
+ tdsqlite3_file *jfd; /* File descriptor for main journal */
+ tdsqlite3_file *sjfd; /* File descriptor for sub-journal */
i64 journalOff; /* Current write offset in the journal file */
i64 journalHdr; /* Byte offset to previous journal header */
- sqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */
+ tdsqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */
PagerSavepoint *aSavepoint; /* Array of active savepoints */
int nSavepoint; /* Number of elements in aSavepoint[] */
u32 iDataVersion; /* Changes whenever database content changes */
char dbFileVers[16]; /* Changes whenever database file changes */
int nMmapOut; /* Number of mmap pages currently outstanding */
- sqlite3_int64 szMmap; /* Desired maximum mmap size */
+ tdsqlite3_int64 szMmap; /* Desired maximum mmap size */
PgHdr *pMmapFreelist; /* List of free mmap page headers (pDirty) */
/*
** End of the routinely-changing class members
@@ -50808,7 +56391,7 @@ struct Pager {
u16 nExtra; /* Add this many bytes to each in-memory page */
i16 nReserve; /* Number of unused bytes at end of each page */
- u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */
+ u32 vfsFlags; /* Flags for tdsqlite3_vfs.xOpen() */
u32 sectorSize; /* Assumed sector size during rollback */
int pageSize; /* Number of bytes in a page */
Pgno mxPgno; /* Maximum allowed size of the database */
@@ -50817,11 +56400,12 @@ struct Pager {
char *zJournal; /* Name of the journal file */
int (*xBusyHandler)(void*); /* Function to call when busy */
void *pBusyHandlerArg; /* Context argument for xBusyHandler */
- int aStat[3]; /* Total cache hits, misses and writes */
+ int aStat[4]; /* Total cache hits, misses, writes, spills */
#ifdef SQLITE_TEST
int nRead; /* Database pages read */
#endif
void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */
+ int (*xGet)(Pager*,Pgno,DbPage**,int); /* Routine to fetch a patch */
#ifdef SQLITE_HAS_CODEC
void *(*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */
void (*xCodecSizeChng)(void*,int,int); /* Notify of page size changes */
@@ -50839,11 +56423,12 @@ struct Pager {
/*
** Indexes for use with Pager.aStat[]. The Pager.aStat[] array contains
** the values accessed by passing SQLITE_DBSTATUS_CACHE_HIT, CACHE_MISS
-** or CACHE_WRITE to sqlite3_db_status().
+** or CACHE_WRITE to tdsqlite3_db_status().
*/
#define PAGER_STAT_HIT 0
#define PAGER_STAT_MISS 1
#define PAGER_STAT_WRITE 2
+#define PAGER_STAT_SPILL 3
/*
** The following global variables hold counters used for
@@ -50851,9 +56436,9 @@ struct Pager {
** a non-testing build. These variables are not thread-safe.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_pager_readdb_count = 0; /* Number of full pages read from DB */
-SQLITE_API int sqlite3_pager_writedb_count = 0; /* Number of full pages written to DB */
-SQLITE_API int sqlite3_pager_writej_count = 0; /* Number of pages written to journal */
+SQLITE_API int tdsqlite3_pager_readdb_count = 0; /* Number of full pages read from DB */
+SQLITE_API int tdsqlite3_pager_writedb_count = 0; /* Number of full pages written to DB */
+SQLITE_API int tdsqlite3_pager_writej_count = 0; /* Number of pages written to journal */
# define PAGER_INCR(v) v++
#else
# define PAGER_INCR(v)
@@ -50928,7 +56513,7 @@ static const unsigned char aJournalMagic[] = {
#define PAGER_MAX_PGNO 2147483647
/*
-** The argument to this macro is a file descriptor (type sqlite3_file*).
+** The argument to this macro is a file descriptor (type tdsqlite3_file*).
** Return 0 if it is not open, or non-zero (but not 1) if it is.
**
** This is so that expressions can be written as:
@@ -50941,15 +56526,35 @@ static const unsigned char aJournalMagic[] = {
*/
#define isOpen(pFd) ((pFd)->pMethods!=0)
+#ifdef SQLITE_DIRECT_OVERFLOW_READ
/*
-** Return true if this pager uses a write-ahead log instead of the usual
-** rollback journal. Otherwise false.
+** Return true if page pgno can be read directly from the database file
+** by the b-tree layer. This is the case if:
+**
+** * the database file is open,
+** * there are no dirty pages in the cache, and
+** * the desired page is not currently in the wal file.
*/
+SQLITE_PRIVATE int tdsqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){
+ if( pPager->fd->pMethods==0 ) return 0;
+ if( tdsqlite3PCacheIsDirty(pPager->pPCache) ) return 0;
+#ifdef SQLITE_HAS_CODEC
+ if( pPager->xCodec!=0 ) return 0;
+#endif
#ifndef SQLITE_OMIT_WAL
-SQLITE_PRIVATE int sqlite3PagerUseWal(Pager *pPager){
- return (pPager->pWal!=0);
+ if( pPager->pWal ){
+ u32 iRead = 0;
+ int rc;
+ rc = tdsqlite3WalFindFrame(pPager->pWal, pgno, &iRead);
+ return (rc==SQLITE_OK && iRead==0);
+ }
+#endif
+ return 1;
}
-# define pagerUseWal(x) sqlite3PagerUseWal(x)
+#endif
+
+#ifndef SQLITE_OMIT_WAL
+# define pagerUseWal(x) ((x)->pWal!=0)
#else
# define pagerUseWal(x) 0
# define pagerRollbackWal(x) 0
@@ -51021,7 +56626,7 @@ static int assert_pager_state(Pager *p){
case PAGER_OPEN:
assert( !MEMDB );
assert( pPager->errCode==SQLITE_OK );
- assert( sqlite3PcacheRefCount(pPager->pPCache)==0 || pPager->tempFile );
+ assert( tdsqlite3PcacheRefCount(pPager->pPCache)==0 || pPager->tempFile );
break;
case PAGER_READER:
@@ -51069,6 +56674,7 @@ static int assert_pager_state(Pager *p){
assert( isOpen(p->jfd)
|| p->journalMode==PAGER_JOURNALMODE_OFF
|| p->journalMode==PAGER_JOURNALMODE_WAL
+ || (tdsqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC)
);
assert( pPager->dbOrigSize<=pPager->dbHintSize );
break;
@@ -51080,6 +56686,7 @@ static int assert_pager_state(Pager *p){
assert( isOpen(p->jfd)
|| p->journalMode==PAGER_JOURNALMODE_OFF
|| p->journalMode==PAGER_JOURNALMODE_WAL
+ || (tdsqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC)
);
break;
@@ -51089,7 +56696,7 @@ static int assert_pager_state(Pager *p){
** back to OPEN state.
*/
assert( pPager->errCode!=SQLITE_OK );
- assert( sqlite3PcacheRefCount(pPager->pPCache)>0 || pPager->tempFile );
+ assert( tdsqlite3PcacheRefCount(pPager->pPCache)>0 || pPager->tempFile );
break;
}
@@ -51105,11 +56712,15 @@ static int assert_pager_state(Pager *p){
** to "print *pPager" in gdb:
**
** (gdb) printf "%s", print_pager_state(pPager)
+**
+** This routine has external linkage in order to suppress compiler warnings
+** about an unused function. It is enclosed within SQLITE_DEBUG and so does
+** not appear in normal builds.
*/
-static char *print_pager_state(Pager *p){
+char *print_pager_state(Pager *p){
static char zRet[1024];
- sqlite3_snprintf(1024, zRet,
+ tdsqlite3_snprintf(1024, zRet,
"Filename: %s\n"
"State: %s errCode=%d\n"
"Lock: %s\n"
@@ -51148,6 +56759,33 @@ static char *print_pager_state(Pager *p){
}
#endif
+/* Forward references to the various page getters */
+static int getPageNormal(Pager*,Pgno,DbPage**,int);
+static int getPageError(Pager*,Pgno,DbPage**,int);
+#if SQLITE_MAX_MMAP_SIZE>0
+static int getPageMMap(Pager*,Pgno,DbPage**,int);
+#endif
+
+/*
+** Set the Pager.xGet method for the appropriate routine used to fetch
+** content from the pager.
+*/
+static void setGetterMethod(Pager *pPager){
+ if( pPager->errCode ){
+ pPager->xGet = getPageError;
+#if SQLITE_MAX_MMAP_SIZE>0
+ }else if( USEFETCH(pPager)
+#ifdef SQLITE_HAS_CODEC
+ && pPager->xCodec==0
+#endif
+ ){
+ pPager->xGet = getPageMMap;
+#endif /* SQLITE_MAX_MMAP_SIZE>0 */
+ }else{
+ pPager->xGet = getPageNormal;
+ }
+}
+
/*
** Return true if it is necessary to write page *pPg into the sub-journal.
** A page needs to be written into the sub-journal if there exists one
@@ -51164,7 +56802,7 @@ static int subjRequiresPage(PgHdr *pPg){
int i;
for(i=0; i<pPager->nSavepoint; i++){
p = &pPager->aSavepoint[i];
- if( p->nOrig>=pgno && 0==sqlite3BitvecTestNotNull(p->pInSavepoint, pgno) ){
+ if( p->nOrig>=pgno && 0==tdsqlite3BitvecTestNotNull(p->pInSavepoint, pgno) ){
return 1;
}
}
@@ -51176,7 +56814,7 @@ static int subjRequiresPage(PgHdr *pPg){
** Return true if the page is already in the journal file.
*/
static int pageInJournal(Pager *pPager, PgHdr *pPg){
- return sqlite3BitvecTest(pPager->pInJournal, pPg->pgno);
+ return tdsqlite3BitvecTest(pPager->pInJournal, pPg->pgno);
}
#endif
@@ -51187,11 +56825,11 @@ static int pageInJournal(Pager *pPager, PgHdr *pPg){
**
** All values are stored on disk as big-endian.
*/
-static int read32bits(sqlite3_file *fd, i64 offset, u32 *pRes){
+static int read32bits(tdsqlite3_file *fd, i64 offset, u32 *pRes){
unsigned char ac[4];
- int rc = sqlite3OsRead(fd, ac, sizeof(ac), offset);
+ int rc = tdsqlite3OsRead(fd, ac, sizeof(ac), offset);
if( rc==SQLITE_OK ){
- *pRes = sqlite3Get4byte(ac);
+ *pRes = tdsqlite3Get4byte(ac);
}
return rc;
}
@@ -51199,17 +56837,17 @@ static int read32bits(sqlite3_file *fd, i64 offset, u32 *pRes){
/*
** Write a 32-bit integer into a string buffer in big-endian byte order.
*/
-#define put32bits(A,B) sqlite3Put4byte((u8*)A,B)
+#define put32bits(A,B) tdsqlite3Put4byte((u8*)A,B)
/*
** Write a 32-bit integer into the given file descriptor. Return SQLITE_OK
** on success or an error code is something goes wrong.
*/
-static int write32bits(sqlite3_file *fd, i64 offset, u32 val){
+static int write32bits(tdsqlite3_file *fd, i64 offset, u32 val){
char ac[4];
put32bits(ac, val);
- return sqlite3OsWrite(fd, ac, 4, offset);
+ return tdsqlite3OsWrite(fd, ac, 4, offset);
}
/*
@@ -51229,12 +56867,13 @@ static int pagerUnlockDb(Pager *pPager, int eLock){
assert( eLock!=NO_LOCK || pagerUseWal(pPager)==0 );
if( isOpen(pPager->fd) ){
assert( pPager->eLock>=eLock );
- rc = pPager->noLock ? SQLITE_OK : sqlite3OsUnlock(pPager->fd, eLock);
+ rc = pPager->noLock ? SQLITE_OK : tdsqlite3OsUnlock(pPager->fd, eLock);
if( pPager->eLock!=UNKNOWN_LOCK ){
pPager->eLock = (u8)eLock;
}
IOTRACE(("UNLOCK %p %d\n", pPager, eLock))
}
+ pPager->changeCountDone = pPager->tempFile; /* ticket fb3b3024ea238d5c */
return rc;
}
@@ -51253,7 +56892,7 @@ static int pagerLockDb(Pager *pPager, int eLock){
assert( eLock==SHARED_LOCK || eLock==RESERVED_LOCK || eLock==EXCLUSIVE_LOCK );
if( pPager->eLock<eLock || pPager->eLock==UNKNOWN_LOCK ){
- rc = pPager->noLock ? SQLITE_OK : sqlite3OsLock(pPager->fd, eLock);
+ rc = pPager->noLock ? SQLITE_OK : tdsqlite3OsLock(pPager->fd, eLock);
if( rc==SQLITE_OK && (pPager->eLock!=UNKNOWN_LOCK||eLock==EXCLUSIVE_LOCK) ){
pPager->eLock = (u8)eLock;
IOTRACE(("LOCK %p %d\n", pPager, eLock))
@@ -51263,34 +56902,47 @@ static int pagerLockDb(Pager *pPager, int eLock){
}
/*
-** This function determines whether or not the atomic-write optimization
-** can be used with this pager. The optimization can be used if:
+** This function determines whether or not the atomic-write or
+** atomic-batch-write optimizations can be used with this pager. The
+** atomic-write optimization can be used if:
**
** (a) the value returned by OsDeviceCharacteristics() indicates that
** a database page may be written atomically, and
** (b) the value returned by OsSectorSize() is less than or equal
** to the page size.
**
-** The optimization is also always enabled for temporary files. It is
-** an error to call this function if pPager is opened on an in-memory
-** database.
+** If it can be used, then the value returned is the size of the journal
+** file when it contains rollback data for exactly one page.
**
-** If the optimization cannot be used, 0 is returned. If it can be used,
-** then the value returned is the size of the journal file when it
-** contains rollback data for exactly one page.
+** The atomic-batch-write optimization can be used if OsDeviceCharacteristics()
+** returns a value with the SQLITE_IOCAP_BATCH_ATOMIC bit set. -1 is
+** returned in this case.
+**
+** If neither optimization can be used, 0 is returned.
*/
-#ifdef SQLITE_ENABLE_ATOMIC_WRITE
static int jrnlBufferSize(Pager *pPager){
assert( !MEMDB );
- if( !pPager->tempFile ){
- int dc; /* Device characteristics */
- int nSector; /* Sector size */
- int szPage; /* Page size */
- assert( isOpen(pPager->fd) );
- dc = sqlite3OsDeviceCharacteristics(pPager->fd);
- nSector = pPager->sectorSize;
- szPage = pPager->pageSize;
+#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \
+ || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE)
+ int dc; /* Device characteristics */
+
+ assert( isOpen(pPager->fd) );
+ dc = tdsqlite3OsDeviceCharacteristics(pPager->fd);
+#else
+ UNUSED_PARAMETER(pPager);
+#endif
+
+#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE
+ if( pPager->dbSize>0 && (dc&SQLITE_IOCAP_BATCH_ATOMIC) ){
+ return -1;
+ }
+#endif
+
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+ {
+ int nSector = pPager->sectorSize;
+ int szPage = pPager->pageSize;
assert(SQLITE_IOCAP_ATOMIC512==(512>>8));
assert(SQLITE_IOCAP_ATOMIC64K==(65536>>8));
@@ -51300,11 +56952,11 @@ static int jrnlBufferSize(Pager *pPager){
}
return JOURNAL_HDR_SZ(pPager) + JOURNAL_PG_SZ(pPager);
-}
-#else
-# define jrnlBufferSize(x) 0
#endif
+ return 0;
+}
+
/*
** If SQLITE_CHECK_PAGES is defined then we do some sanity checking
** on the cache using a hash function. This is used for testing
@@ -51356,7 +57008,7 @@ static void checkPage(PgHdr *pPg){
** used to store a master journal file name at the end of a journal file.
**
** zMaster must point to a buffer of at least nMaster bytes allocated by
-** the caller. This should be sqlite3_vfs.mxPathname+1 (to ensure there is
+** the caller. This should be tdsqlite3_vfs.mxPathname+1 (to ensure there is
** enough space to write the master journal name). If the master journal
** name in the journal is longer than nMaster bytes (including a
** nul-terminator), then this is handled as if no master journal name
@@ -51373,7 +57025,7 @@ static void checkPage(PgHdr *pPg){
** If an error occurs while reading from the journal file, an SQLite
** error code is returned.
*/
-static int readMasterJournal(sqlite3_file *pJrnl, char *zMaster, u32 nMaster){
+static int readMasterJournal(tdsqlite3_file *pJrnl, char *zMaster, u32 nMaster){
int rc; /* Return code */
u32 len; /* Length in bytes of master journal name */
i64 szJ; /* Total size in bytes of journal file pJrnl */
@@ -51382,15 +57034,16 @@ static int readMasterJournal(sqlite3_file *pJrnl, char *zMaster, u32 nMaster){
unsigned char aMagic[8]; /* A buffer to hold the magic header */
zMaster[0] = '\0';
- if( SQLITE_OK!=(rc = sqlite3OsFileSize(pJrnl, &szJ))
+ if( SQLITE_OK!=(rc = tdsqlite3OsFileSize(pJrnl, &szJ))
|| szJ<16
|| SQLITE_OK!=(rc = read32bits(pJrnl, szJ-16, &len))
|| len>=nMaster
+ || len>szJ-16
|| len==0
|| SQLITE_OK!=(rc = read32bits(pJrnl, szJ-12, &cksum))
- || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, aMagic, 8, szJ-8))
+ || SQLITE_OK!=(rc = tdsqlite3OsRead(pJrnl, aMagic, 8, szJ-8))
|| memcmp(aMagic, aJournalMagic, 8)
- || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, zMaster, len, szJ-16-len))
+ || SQLITE_OK!=(rc = tdsqlite3OsRead(pJrnl, zMaster, len, szJ-16-len))
){
return rc;
}
@@ -51408,6 +57061,7 @@ static int readMasterJournal(sqlite3_file *pJrnl, char *zMaster, u32 nMaster){
len = 0;
}
zMaster[len] = '\0';
+ zMaster[len+1] = '\0';
return SQLITE_OK;
}
@@ -51463,19 +57117,19 @@ static i64 journalHdrOffset(Pager *pPager){
static int zeroJournalHdr(Pager *pPager, int doTruncate){
int rc = SQLITE_OK; /* Return code */
assert( isOpen(pPager->jfd) );
- assert( !sqlite3JournalIsInMemory(pPager->jfd) );
+ assert( !tdsqlite3JournalIsInMemory(pPager->jfd) );
if( pPager->journalOff ){
const i64 iLimit = pPager->journalSizeLimit; /* Local cache of jsl */
IOTRACE(("JZEROHDR %p\n", pPager))
if( doTruncate || iLimit==0 ){
- rc = sqlite3OsTruncate(pPager->jfd, 0);
+ rc = tdsqlite3OsTruncate(pPager->jfd, 0);
}else{
static const char zeroHdr[28] = {0};
- rc = sqlite3OsWrite(pPager->jfd, zeroHdr, sizeof(zeroHdr), 0);
+ rc = tdsqlite3OsWrite(pPager->jfd, zeroHdr, sizeof(zeroHdr), 0);
}
if( rc==SQLITE_OK && !pPager->noSync ){
- rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->syncFlags);
+ rc = tdsqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->syncFlags);
}
/* At this point the transaction is committed but the write lock
@@ -51486,9 +57140,9 @@ static int zeroJournalHdr(Pager *pPager, int doTruncate){
*/
if( rc==SQLITE_OK && iLimit>0 ){
i64 sz;
- rc = sqlite3OsFileSize(pPager->jfd, &sz);
+ rc = tdsqlite3OsFileSize(pPager->jfd, &sz);
if( rc==SQLITE_OK && sz>iLimit ){
- rc = sqlite3OsTruncate(pPager->jfd, iLimit);
+ rc = tdsqlite3OsTruncate(pPager->jfd, iLimit);
}
}
}
@@ -51557,7 +57211,7 @@ static int writeJournalHdr(Pager *pPager){
*/
assert( isOpen(pPager->fd) || pPager->noSync );
if( pPager->noSync || (pPager->journalMode==PAGER_JOURNALMODE_MEMORY)
- || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND)
+ || (tdsqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND)
){
memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic));
put32bits(&zHeader[sizeof(aJournalMagic)], 0xffffffff);
@@ -51566,7 +57220,7 @@ static int writeJournalHdr(Pager *pPager){
}
/* The random check-hash initializer */
- sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit);
+ tdsqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit);
put32bits(&zHeader[sizeof(aJournalMagic)+4], pPager->cksumInit);
/* The initial database size */
put32bits(&zHeader[sizeof(aJournalMagic)+8], pPager->dbOrigSize);
@@ -51598,12 +57252,12 @@ static int writeJournalHdr(Pager *pPager){
**
** The loop is required here in case the sector-size is larger than the
** database page size. Since the zHeader buffer is only Pager.pageSize
- ** bytes in size, more than one call to sqlite3OsWrite() may be required
+ ** bytes in size, more than one call to tdsqlite3OsWrite() may be required
** to populate the entire journal header sector.
*/
for(nWrite=0; rc==SQLITE_OK&&nWrite<JOURNAL_HDR_SZ(pPager); nWrite+=nHeader){
IOTRACE(("JHDR %p %lld %d\n", pPager, pPager->journalHdr, nHeader))
- rc = sqlite3OsWrite(pPager->jfd, zHeader, nHeader, pPager->journalOff);
+ rc = tdsqlite3OsWrite(pPager->jfd, zHeader, nHeader, pPager->journalOff);
assert( pPager->journalHdr <= pPager->journalOff );
pPager->journalOff += nHeader;
}
@@ -51657,7 +57311,7 @@ static int readJournalHdr(
** proceed.
*/
if( isHot || iHdrOff!=pPager->journalHdr ){
- rc = sqlite3OsRead(pPager->jfd, aMagic, sizeof(aMagic), iHdrOff);
+ rc = tdsqlite3OsRead(pPager->jfd, aMagic, sizeof(aMagic), iHdrOff);
if( rc ){
return rc;
}
@@ -51717,7 +57371,7 @@ static int readJournalHdr(
** Use a testcase() macro to make sure that malloc failure within
** PagerSetPagesize() is tested.
*/
- rc = sqlite3PagerSetPagesize(pPager, &iPageSize, -1);
+ rc = tdsqlite3PagerSetPagesize(pPager, &iPageSize, -1);
testcase( rc!=SQLITE_OK );
/* Update the assumed sector-size to match the value used by
@@ -51790,10 +57444,10 @@ static int writeMasterJournal(Pager *pPager, const char *zMaster){
** an error occurs, return the error code to the caller.
*/
if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_MJ_PGNO(pPager))))
- || (0 != (rc = sqlite3OsWrite(pPager->jfd, zMaster, nMaster, iHdrOff+4)))
+ || (0 != (rc = tdsqlite3OsWrite(pPager->jfd, zMaster, nMaster, iHdrOff+4)))
|| (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nMaster, nMaster)))
|| (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nMaster+4, cksum)))
- || (0 != (rc = sqlite3OsWrite(pPager->jfd, aJournalMagic, 8,
+ || (0 != (rc = tdsqlite3OsWrite(pPager->jfd, aJournalMagic, 8,
iHdrOff+4+nMaster+8)))
){
return rc;
@@ -51810,10 +57464,10 @@ static int writeMasterJournal(Pager *pPager, const char *zMaster){
** Easiest thing to do in this scenario is to truncate the journal
** file to the required size.
*/
- if( SQLITE_OK==(rc = sqlite3OsFileSize(pPager->jfd, &jrnlSize))
+ if( SQLITE_OK==(rc = tdsqlite3OsFileSize(pPager->jfd, &jrnlSize))
&& jrnlSize>pPager->journalOff
){
- rc = sqlite3OsTruncate(pPager->jfd, pPager->journalOff);
+ rc = tdsqlite3OsTruncate(pPager->jfd, pPager->journalOff);
}
return rc;
}
@@ -51823,15 +57477,14 @@ static int writeMasterJournal(Pager *pPager, const char *zMaster){
*/
static void pager_reset(Pager *pPager){
pPager->iDataVersion++;
- sqlite3BackupRestart(pPager->pBackup);
- sqlite3PcacheClear(pPager->pPCache);
+ tdsqlite3BackupRestart(pPager->pBackup);
+ tdsqlite3PcacheClear(pPager->pPCache);
}
/*
** Return the pPager->iDataVersion value
*/
-SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager *pPager){
- assert( pPager->eState>PAGER_OPEN );
+SQLITE_PRIVATE u32 tdsqlite3PagerDataVersion(Pager *pPager){
return pPager->iDataVersion;
}
@@ -51843,12 +57496,12 @@ SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager *pPager){
static void releaseAllSavepoints(Pager *pPager){
int ii; /* Iterator for looping through Pager.aSavepoint */
for(ii=0; ii<pPager->nSavepoint; ii++){
- sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint);
+ tdsqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint);
}
- if( !pPager->exclusiveMode || sqlite3JournalIsInMemory(pPager->sjfd) ){
- sqlite3OsClose(pPager->sjfd);
+ if( !pPager->exclusiveMode || tdsqlite3JournalIsInMemory(pPager->sjfd) ){
+ tdsqlite3OsClose(pPager->sjfd);
}
- sqlite3_free(pPager->aSavepoint);
+ tdsqlite3_free(pPager->aSavepoint);
pPager->aSavepoint = 0;
pPager->nSavepoint = 0;
pPager->nSubRec = 0;
@@ -51866,7 +57519,7 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){
for(ii=0; ii<pPager->nSavepoint; ii++){
PagerSavepoint *p = &pPager->aSavepoint[ii];
if( pgno<=p->nOrig ){
- rc |= sqlite3BitvecSet(p->pInSavepoint, pgno);
+ rc |= tdsqlite3BitvecSet(p->pInSavepoint, pgno);
testcase( rc==SQLITE_NOMEM );
assert( rc==SQLITE_OK || rc==SQLITE_NOMEM );
}
@@ -51898,17 +57551,17 @@ static void pager_unlock(Pager *pPager){
|| pPager->eState==PAGER_ERROR
);
- sqlite3BitvecDestroy(pPager->pInJournal);
+ tdsqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
releaseAllSavepoints(pPager);
if( pagerUseWal(pPager) ){
assert( !isOpen(pPager->jfd) );
- sqlite3WalEndReadTransaction(pPager->pWal);
+ tdsqlite3WalEndReadTransaction(pPager->pWal);
pPager->eState = PAGER_OPEN;
}else if( !pPager->exclusiveMode ){
int rc; /* Error code returned by pagerUnlockDb() */
- int iDc = isOpen(pPager->fd)?sqlite3OsDeviceCharacteristics(pPager->fd):0;
+ int iDc = isOpen(pPager->fd)?tdsqlite3OsDeviceCharacteristics(pPager->fd):0;
/* If the operating system support deletion of open files, then
** close the journal file when dropping the database lock. Otherwise
@@ -51924,7 +57577,7 @@ static void pager_unlock(Pager *pPager){
if( 0==(iDc & SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN)
|| 1!=(pPager->journalMode & 5)
){
- sqlite3OsClose(pPager->jfd);
+ tdsqlite3OsClose(pPager->jfd);
}
/* If the pager is in the ERROR state and the call to unlock the database
@@ -51942,7 +57595,6 @@ static void pager_unlock(Pager *pPager){
** code is cleared and the cache reset in the block below.
*/
assert( pPager->errCode || pPager->eState!=PAGER_ERROR );
- pPager->changeCountDone = 0;
pPager->eState = PAGER_OPEN;
}
@@ -51960,8 +57612,9 @@ static void pager_unlock(Pager *pPager){
}else{
pPager->eState = (isOpen(pPager->jfd) ? PAGER_OPEN : PAGER_READER);
}
- if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0);
+ if( USEFETCH(pPager) ) tdsqlite3OsUnfetch(pPager->fd, 0, 0);
pPager->errCode = SQLITE_OK;
+ setGetterMethod(pPager);
}
pPager->journalOff = 0;
@@ -51999,6 +57652,7 @@ static int pager_error(Pager *pPager, int rc){
if( rc2==SQLITE_FULL || rc2==SQLITE_IOERR ){
pPager->errCode = rc;
pPager->eState = PAGER_ERROR;
+ setGetterMethod(pPager);
}
return rc;
}
@@ -52025,7 +57679,7 @@ static int pagerFlushOnCommit(Pager *pPager, int bCommit){
if( pPager->tempFile==0 ) return 1;
if( !bCommit ) return 0;
if( !isOpen(pPager->fd) ) return 0;
- return (sqlite3PCachePercentDirty(pPager->pPCache)>=25);
+ return (tdsqlite3PCachePercentDirty(pPager->pPCache)>=25);
}
/*
@@ -52061,7 +57715,7 @@ static int pagerFlushOnCommit(Pager *pPager, int bCommit){
** file. An invalid journal file cannot be rolled back.
**
** journalMode==DELETE
-** The journal file is closed and deleted using sqlite3OsDelete().
+** The journal file is closed and deleted using tdsqlite3OsDelete().
**
** If the pager is running in exclusive mode, this method of finalizing
** the journal file is never used. Instead, if the journalMode is
@@ -52105,26 +57759,28 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
}
releaseAllSavepoints(pPager);
- assert( isOpen(pPager->jfd) || pPager->pInJournal==0 );
+ assert( isOpen(pPager->jfd) || pPager->pInJournal==0
+ || (tdsqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_BATCH_ATOMIC)
+ );
if( isOpen(pPager->jfd) ){
assert( !pagerUseWal(pPager) );
/* Finalize the journal file. */
- if( sqlite3JournalIsInMemory(pPager->jfd) ){
+ if( tdsqlite3JournalIsInMemory(pPager->jfd) ){
/* assert( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ); */
- sqlite3OsClose(pPager->jfd);
+ tdsqlite3OsClose(pPager->jfd);
}else if( pPager->journalMode==PAGER_JOURNALMODE_TRUNCATE ){
if( pPager->journalOff==0 ){
rc = SQLITE_OK;
}else{
- rc = sqlite3OsTruncate(pPager->jfd, 0);
+ rc = tdsqlite3OsTruncate(pPager->jfd, 0);
if( rc==SQLITE_OK && pPager->fullSync ){
/* Make sure the new file size is written into the inode right away.
** Otherwise the journal might resurrect following a power loss and
** cause the last transaction to roll back. See
** https://bugzilla.mozilla.org/show_bug.cgi?id=1072773
*/
- rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags);
+ rc = tdsqlite3OsSync(pPager->jfd, pPager->syncFlags);
}
}
pPager->journalOff = 0;
@@ -52140,39 +57796,39 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
** the database file, it will do so using an in-memory journal.
*/
int bDelete = !pPager->tempFile;
- assert( sqlite3JournalIsInMemory(pPager->jfd)==0 );
+ assert( tdsqlite3JournalIsInMemory(pPager->jfd)==0 );
assert( pPager->journalMode==PAGER_JOURNALMODE_DELETE
|| pPager->journalMode==PAGER_JOURNALMODE_MEMORY
|| pPager->journalMode==PAGER_JOURNALMODE_WAL
);
- sqlite3OsClose(pPager->jfd);
+ tdsqlite3OsClose(pPager->jfd);
if( bDelete ){
- rc = sqlite3OsDelete(pPager->pVfs, pPager->zJournal, pPager->extraSync);
+ rc = tdsqlite3OsDelete(pPager->pVfs, pPager->zJournal, pPager->extraSync);
}
}
}
#ifdef SQLITE_CHECK_PAGES
- sqlite3PcacheIterateDirty(pPager->pPCache, pager_set_pagehash);
- if( pPager->dbSize==0 && sqlite3PcacheRefCount(pPager->pPCache)>0 ){
- PgHdr *p = sqlite3PagerLookup(pPager, 1);
+ tdsqlite3PcacheIterateDirty(pPager->pPCache, pager_set_pagehash);
+ if( pPager->dbSize==0 && tdsqlite3PcacheRefCount(pPager->pPCache)>0 ){
+ PgHdr *p = tdsqlite3PagerLookup(pPager, 1);
if( p ){
p->pageHash = 0;
- sqlite3PagerUnrefNotNull(p);
+ tdsqlite3PagerUnrefNotNull(p);
}
}
#endif
- sqlite3BitvecDestroy(pPager->pInJournal);
+ tdsqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
pPager->nRec = 0;
if( rc==SQLITE_OK ){
- if( pagerFlushOnCommit(pPager, bCommit) ){
- sqlite3PcacheCleanAll(pPager->pPCache);
+ if( MEMDB || pagerFlushOnCommit(pPager, bCommit) ){
+ tdsqlite3PcacheCleanAll(pPager->pPCache);
}else{
- sqlite3PcacheClearWritable(pPager->pPCache);
+ tdsqlite3PcacheClearWritable(pPager->pPCache);
}
- sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize);
+ tdsqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize);
}
if( pagerUseWal(pPager) ){
@@ -52180,7 +57836,7 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE
** lock held on the database file.
*/
- rc2 = sqlite3WalEndWriteTransaction(pPager->pWal);
+ rc2 = tdsqlite3WalEndWriteTransaction(pPager->pWal);
assert( rc2==SQLITE_OK );
}else if( rc==SQLITE_OK && bCommit && pPager->dbFileSize>pPager->dbSize ){
/* This branch is taken when committing a transaction in rollback-journal
@@ -52193,16 +57849,15 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
rc = pager_truncate(pPager, pPager->dbSize);
}
- if( rc==SQLITE_OK && bCommit && isOpen(pPager->fd) ){
- rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_COMMIT_PHASETWO, 0);
+ if( rc==SQLITE_OK && bCommit ){
+ rc = tdsqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_COMMIT_PHASETWO, 0);
if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
}
if( !pPager->exclusiveMode
- && (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0))
+ && (!pagerUseWal(pPager) || tdsqlite3WalExclusiveMode(pPager->pWal, 0))
){
rc2 = pagerUnlockDb(pPager, SHARED_LOCK);
- pPager->changeCountDone = 0;
}
pPager->eState = PAGER_READER;
pPager->setMaster = 0;
@@ -52231,9 +57886,9 @@ static void pagerUnlockAndRollback(Pager *pPager){
if( pPager->eState!=PAGER_ERROR && pPager->eState!=PAGER_OPEN ){
assert( assert_pager_state(pPager) );
if( pPager->eState>=PAGER_WRITER_LOCKED ){
- sqlite3BeginBenignMalloc();
- sqlite3PagerRollback(pPager);
- sqlite3EndBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
+ tdsqlite3PagerRollback(pPager);
+ tdsqlite3EndBenignMalloc();
}else if( !pPager->exclusiveMode ){
assert( pPager->eState==PAGER_READER );
pager_end_transaction(pPager, 0, 0);
@@ -52292,7 +57947,7 @@ static void pagerReportSize(Pager *pPager){
** pager as it is in the source. This comes up when a VACUUM changes the
** number of reserved bits to the "optimal" amount.
*/
-SQLITE_PRIVATE void sqlite3PagerAlignReserve(Pager *pDest, Pager *pSrc){
+SQLITE_PRIVATE void tdsqlite3PagerAlignReserve(Pager *pDest, Pager *pSrc){
if( pDest->nReserve!=pSrc->nReserve ){
pDest->nReserve = pSrc->nReserve;
pagerReportSize(pDest);
@@ -52349,8 +58004,13 @@ static int pager_playback_one_page(
Pgno pgno; /* The page number of a page in journal */
u32 cksum; /* Checksum used for sanity checking */
char *aData; /* Temporary storage for the page */
- sqlite3_file *jfd; /* The file descriptor for the journal file */
+ tdsqlite3_file *jfd; /* The file descriptor for the journal file */
int isSynced; /* True if journal page is synced */
+#ifdef SQLITE_HAS_CODEC
+ /* The jrnlEnc flag is true if Journal pages should be passed through
+ ** the codec. It is false for pure in-memory journals. */
+ const int jrnlEnc = (isMainJrnl || pPager->subjInMemory==0);
+#endif
assert( (isMainJrnl&~1)==0 ); /* isMainJrnl is 0 or 1 */
assert( (isSavepnt&~1)==0 ); /* isSavepnt is 0 or 1 */
@@ -52378,7 +58038,7 @@ static int pager_playback_one_page(
jfd = isMainJrnl ? pPager->jfd : pPager->sjfd;
rc = read32bits(jfd, *pOffset, &pgno);
if( rc!=SQLITE_OK ) return rc;
- rc = sqlite3OsRead(jfd, (u8*)aData, pPager->pageSize, (*pOffset)+4);
+ rc = tdsqlite3OsRead(jfd, (u8*)aData, pPager->pageSize, (*pOffset)+4);
if( rc!=SQLITE_OK ) return rc;
*pOffset += pPager->pageSize + 4 + isMainJrnl*4;
@@ -52391,7 +58051,7 @@ static int pager_playback_one_page(
assert( !isSavepnt );
return SQLITE_DONE;
}
- if( pgno>(Pgno)pPager->dbSize || sqlite3BitvecTest(pDone, pgno) ){
+ if( pgno>(Pgno)pPager->dbSize || tdsqlite3BitvecTest(pDone, pgno) ){
return SQLITE_OK;
}
if( isMainJrnl ){
@@ -52405,7 +58065,7 @@ static int pager_playback_one_page(
/* If this page has already been played back before during the current
** rollback, then don't bother to play it back again.
*/
- if( pDone && (rc = sqlite3BitvecSet(pDone, pgno))!=SQLITE_OK ){
+ if( pDone && (rc = tdsqlite3BitvecSet(pDone, pgno))!=SQLITE_OK ){
return rc;
}
@@ -52454,7 +58114,7 @@ static int pager_playback_one_page(
if( pagerUseWal(pPager) ){
pPg = 0;
}else{
- pPg = sqlite3PagerLookup(pPager, pgno);
+ pPg = tdsqlite3PagerLookup(pPager, pgno);
}
assert( pPg || !MEMDB );
assert( pPager->eState!=PAGER_OPEN || pPg==0 || pPager->tempFile );
@@ -52474,14 +58134,34 @@ static int pager_playback_one_page(
i64 ofst = (pgno-1)*(i64)pPager->pageSize;
testcase( !isSavepnt && pPg!=0 && (pPg->flags&PGHDR_NEED_SYNC)!=0 );
assert( !pagerUseWal(pPager) );
- rc = sqlite3OsWrite(pPager->fd, (u8 *)aData, pPager->pageSize, ofst);
+
+ /* Write the data read from the journal back into the database file.
+ ** This is usually safe even for an encrypted database - as the data
+ ** was encrypted before it was written to the journal file. The exception
+ ** is if the data was just read from an in-memory sub-journal. In that
+ ** case it must be encrypted here before it is copied into the database
+ ** file. */
+#ifdef SQLITE_HAS_CODEC
+ if( !jrnlEnc ){
+ CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM_BKPT, aData);
+ rc = tdsqlite3OsWrite(pPager->fd, (u8 *)aData, pPager->pageSize, ofst);
+ CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM_BKPT);
+ }else
+#endif
+ rc = tdsqlite3OsWrite(pPager->fd, (u8 *)aData, pPager->pageSize, ofst);
+
if( pgno>pPager->dbFileSize ){
pPager->dbFileSize = pgno;
}
if( pPager->pBackup ){
- CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM_BKPT);
- sqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)aData);
- CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM_BKPT, aData);
+#ifdef SQLITE_HAS_CODEC
+ if( jrnlEnc ){
+ CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM_BKPT);
+ tdsqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)aData);
+ CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM_BKPT,aData);
+ }else
+#endif
+ tdsqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)aData);
}
}else if( !isMainJrnl && pPg==0 ){
/* If this is a rollback of a savepoint and data was not written to
@@ -52493,7 +58173,7 @@ static int pager_playback_one_page(
** There are a couple of different ways this can happen. All are quite
** obscure. When running in synchronous mode, this can only happen
** if the page is on the free-list at the start of the transaction, then
- ** populated, then moved using sqlite3PagerMovepage().
+ ** populated, then moved using tdsqlite3PagerMovepage().
**
** The solution is to add an in-memory page to the cache containing
** the data just read from the sub-journal. Mark the page as dirty
@@ -52503,26 +58183,26 @@ static int pager_playback_one_page(
assert( isSavepnt );
assert( (pPager->doNotSpill & SPILLFLAG_ROLLBACK)==0 );
pPager->doNotSpill |= SPILLFLAG_ROLLBACK;
- rc = sqlite3PagerGet(pPager, pgno, &pPg, 1);
+ rc = tdsqlite3PagerGet(pPager, pgno, &pPg, 1);
assert( (pPager->doNotSpill & SPILLFLAG_ROLLBACK)!=0 );
pPager->doNotSpill &= ~SPILLFLAG_ROLLBACK;
if( rc!=SQLITE_OK ) return rc;
- sqlite3PcacheMakeDirty(pPg);
+ tdsqlite3PcacheMakeDirty(pPg);
}
if( pPg ){
/* No page should ever be explicitly rolled back that is in use, except
** for page 1 which is held in use in order to keep the lock on the
** database active. However such a page may be rolled back as a result
** of an internal error resulting in an automatic call to
- ** sqlite3PagerRollback().
+ ** tdsqlite3PagerRollback().
*/
void *pData;
pData = pPg->pData;
memcpy(pData, (u8*)aData, pPager->pageSize);
pPager->xReiniter(pPg);
- /* It used to be that sqlite3PcacheMakeClean(pPg) was called here. But
+ /* It used to be that tdsqlite3PcacheMakeClean(pPg) was called here. But
** that call was dangerous and had no detectable benefit since the cache
- ** is normally cleaned by sqlite3PcacheCleanAll() after rollback and so
+ ** is normally cleaned by tdsqlite3PcacheCleanAll() after rollback and so
** has been removed. */
pager_set_pagehash(pPg);
@@ -52533,8 +58213,10 @@ static int pager_playback_one_page(
}
/* Decode the page just read from disk */
- CODEC1(pPager, pData, pPg->pgno, 3, rc=SQLITE_NOMEM_BKPT);
- sqlite3PcacheRelease(pPg);
+#if SQLITE_HAS_CODEC
+ if( jrnlEnc ){ CODEC1(pPager, pData, pPg->pgno, 3, rc=SQLITE_NOMEM_BKPT); }
+#endif
+ tdsqlite3PcacheRelease(pPg);
}
return rc;
}
@@ -52570,10 +58252,10 @@ static int pager_playback_one_page(
** If a child journal can be found that matches both of the criteria
** above, this function returns without doing anything. Otherwise, if
** no such child journal can be found, file zMaster is deleted from
-** the file-system using sqlite3OsDelete().
+** the file-system using tdsqlite3OsDelete().
**
** If an IO error within this function, an error code is returned. This
-** function allocates memory by calling sqlite3Malloc(). If an allocation
+** function allocates memory by calling tdsqlite3Malloc(). If an allocation
** fails, SQLITE_NOMEM is returned. Otherwise, if no IO or malloc errors
** occur, SQLITE_OK is returned.
**
@@ -52583,10 +58265,10 @@ static int pager_playback_one_page(
** size.
*/
static int pager_delmaster(Pager *pPager, const char *zMaster){
- sqlite3_vfs *pVfs = pPager->pVfs;
+ tdsqlite3_vfs *pVfs = pPager->pVfs;
int rc; /* Return code */
- sqlite3_file *pMaster; /* Malloc'd master-journal file descriptor */
- sqlite3_file *pJournal; /* Malloc'd child-journal file descriptor */
+ tdsqlite3_file *pMaster; /* Malloc'd master-journal file descriptor */
+ tdsqlite3_file *pJournal; /* Malloc'd child-journal file descriptor */
char *zMasterJournal = 0; /* Contents of master journal file */
i64 nMasterJournal; /* Size of master journal file */
char *zJournal; /* Pointer to one journal within MJ file */
@@ -52596,38 +58278,39 @@ static int pager_delmaster(Pager *pPager, const char *zMaster){
/* Allocate space for both the pJournal and pMaster file descriptors.
** If successful, open the master journal file for reading.
*/
- pMaster = (sqlite3_file *)sqlite3MallocZero(pVfs->szOsFile * 2);
- pJournal = (sqlite3_file *)(((u8 *)pMaster) + pVfs->szOsFile);
+ pMaster = (tdsqlite3_file *)tdsqlite3MallocZero(pVfs->szOsFile * 2);
+ pJournal = (tdsqlite3_file *)(((u8 *)pMaster) + pVfs->szOsFile);
if( !pMaster ){
rc = SQLITE_NOMEM_BKPT;
}else{
const int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_MASTER_JOURNAL);
- rc = sqlite3OsOpen(pVfs, zMaster, pMaster, flags, 0);
+ rc = tdsqlite3OsOpen(pVfs, zMaster, pMaster, flags, 0);
}
if( rc!=SQLITE_OK ) goto delmaster_out;
/* Load the entire master journal file into space obtained from
- ** sqlite3_malloc() and pointed to by zMasterJournal. Also obtain
+ ** tdsqlite3_malloc() and pointed to by zMasterJournal. Also obtain
** sufficient space (in zMasterPtr) to hold the names of master
** journal files extracted from regular rollback-journals.
*/
- rc = sqlite3OsFileSize(pMaster, &nMasterJournal);
+ rc = tdsqlite3OsFileSize(pMaster, &nMasterJournal);
if( rc!=SQLITE_OK ) goto delmaster_out;
nMasterPtr = pVfs->mxPathname+1;
- zMasterJournal = sqlite3Malloc(nMasterJournal + nMasterPtr + 1);
+ zMasterJournal = tdsqlite3Malloc(nMasterJournal + nMasterPtr + 2);
if( !zMasterJournal ){
rc = SQLITE_NOMEM_BKPT;
goto delmaster_out;
}
- zMasterPtr = &zMasterJournal[nMasterJournal+1];
- rc = sqlite3OsRead(pMaster, zMasterJournal, (int)nMasterJournal, 0);
+ zMasterPtr = &zMasterJournal[nMasterJournal+2];
+ rc = tdsqlite3OsRead(pMaster, zMasterJournal, (int)nMasterJournal, 0);
if( rc!=SQLITE_OK ) goto delmaster_out;
zMasterJournal[nMasterJournal] = 0;
+ zMasterJournal[nMasterJournal+1] = 0;
zJournal = zMasterJournal;
while( (zJournal-zMasterJournal)<nMasterJournal ){
int exists;
- rc = sqlite3OsAccess(pVfs, zJournal, SQLITE_ACCESS_EXISTS, &exists);
+ rc = tdsqlite3OsAccess(pVfs, zJournal, SQLITE_ACCESS_EXISTS, &exists);
if( rc!=SQLITE_OK ){
goto delmaster_out;
}
@@ -52638,13 +58321,13 @@ static int pager_delmaster(Pager *pPager, const char *zMaster){
*/
int c;
int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_MAIN_JOURNAL);
- rc = sqlite3OsOpen(pVfs, zJournal, pJournal, flags, 0);
+ rc = tdsqlite3OsOpen(pVfs, zJournal, pJournal, flags, 0);
if( rc!=SQLITE_OK ){
goto delmaster_out;
}
rc = readMasterJournal(pJournal, zMasterPtr, nMasterPtr);
- sqlite3OsClose(pJournal);
+ tdsqlite3OsClose(pJournal);
if( rc!=SQLITE_OK ){
goto delmaster_out;
}
@@ -52655,18 +58338,18 @@ static int pager_delmaster(Pager *pPager, const char *zMaster){
goto delmaster_out;
}
}
- zJournal += (sqlite3Strlen30(zJournal)+1);
+ zJournal += (tdsqlite3Strlen30(zJournal)+1);
}
- sqlite3OsClose(pMaster);
- rc = sqlite3OsDelete(pVfs, zMaster, 0);
+ tdsqlite3OsClose(pMaster);
+ rc = tdsqlite3OsDelete(pVfs, zMaster, 0);
delmaster_out:
- sqlite3_free(zMasterJournal);
+ tdsqlite3_free(zMasterJournal);
if( pMaster ){
- sqlite3OsClose(pMaster);
+ tdsqlite3OsClose(pMaster);
assert( !isOpen(pJournal) );
- sqlite3_free(pMaster);
+ tdsqlite3_free(pMaster);
}
return rc;
}
@@ -52704,17 +58387,17 @@ static int pager_truncate(Pager *pPager, Pgno nPage){
int szPage = pPager->pageSize;
assert( pPager->eLock==EXCLUSIVE_LOCK );
/* TODO: Is it safe to use Pager.dbFileSize here? */
- rc = sqlite3OsFileSize(pPager->fd, &currentSize);
+ rc = tdsqlite3OsFileSize(pPager->fd, &currentSize);
newSize = szPage*(i64)nPage;
if( rc==SQLITE_OK && currentSize!=newSize ){
if( currentSize>newSize ){
- rc = sqlite3OsTruncate(pPager->fd, newSize);
+ rc = tdsqlite3OsTruncate(pPager->fd, newSize);
}else if( (currentSize+szPage)<=newSize ){
char *pTmp = pPager->pTmpSpace;
memset(pTmp, 0, szPage);
testcase( (newSize-szPage) == currentSize );
testcase( (newSize-szPage) > currentSize );
- rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage);
+ rc = tdsqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage);
}
if( rc==SQLITE_OK ){
pPager->dbFileSize = nPage;
@@ -52728,8 +58411,8 @@ static int pager_truncate(Pager *pPager, Pgno nPage){
** Return a sanitized version of the sector-size of OS file pFile. The
** return value is guaranteed to lie between 32 and MAX_SECTOR_SIZE.
*/
-SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *pFile){
- int iRet = sqlite3OsSectorSize(pFile);
+SQLITE_PRIVATE int tdsqlite3SectorSize(tdsqlite3_file *pFile){
+ int iRet = tdsqlite3OsSectorSize(pFile);
if( iRet<32 ){
iRet = 512;
}else if( iRet>MAX_SECTOR_SIZE ){
@@ -52766,7 +58449,7 @@ static void setSectorSize(Pager *pPager){
assert( isOpen(pPager->fd) || pPager->tempFile );
if( pPager->tempFile
- || (sqlite3OsDeviceCharacteristics(pPager->fd) &
+ || (tdsqlite3OsDeviceCharacteristics(pPager->fd) &
SQLITE_IOCAP_POWERSAFE_OVERWRITE)!=0
){
/* Sector size doesn't matter for temporary files. Also, the file
@@ -52774,7 +58457,7 @@ static void setSectorSize(Pager *pPager){
** call will segfault. */
pPager->sectorSize = 512;
}else{
- pPager->sectorSize = sqlite3SectorSize(pPager->fd);
+ pPager->sectorSize = tdsqlite3SectorSize(pPager->fd);
}
}
@@ -52836,22 +58519,23 @@ static void setSectorSize(Pager *pPager){
** needed.
*/
static int pager_playback(Pager *pPager, int isHot){
- sqlite3_vfs *pVfs = pPager->pVfs;
+ tdsqlite3_vfs *pVfs = pPager->pVfs;
i64 szJ; /* Size of the journal file in bytes */
u32 nRec; /* Number of Records in the journal */
u32 u; /* Unsigned loop counter */
Pgno mxPg = 0; /* Size of the original file in pages */
int rc; /* Result code of a subroutine */
- int res = 1; /* Value returned by sqlite3OsAccess() */
+ int res = 1; /* Value returned by tdsqlite3OsAccess() */
char *zMaster = 0; /* Name of master journal file if any */
int needPagerReset; /* True to reset page prior to first page rollback */
int nPlayback = 0; /* Total number of pages restored from journal */
+ u32 savedPageSize = pPager->pageSize;
/* Figure out how many records are in the journal. Abort early if
** the journal is empty.
*/
assert( isOpen(pPager->jfd) );
- rc = sqlite3OsFileSize(pPager->jfd, &szJ);
+ rc = tdsqlite3OsFileSize(pPager->jfd, &szJ);
if( rc!=SQLITE_OK ){
goto end_playback;
}
@@ -52870,7 +58554,7 @@ static int pager_playback(Pager *pPager, int isHot){
zMaster = pPager->pTmpSpace;
rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1);
if( rc==SQLITE_OK && zMaster[0] ){
- rc = sqlite3OsAccess(pVfs, zMaster, SQLITE_ACCESS_EXISTS, &res);
+ rc = tdsqlite3OsAccess(pVfs, zMaster, SQLITE_ACCESS_EXISTS, &res);
}
zMaster = 0;
if( rc!=SQLITE_OK || !res ){
@@ -52975,15 +58659,16 @@ static int pager_playback(Pager *pPager, int isHot){
assert( 0 );
end_playback:
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3PagerSetPagesize(pPager, &savedPageSize, -1);
+ }
/* Following a rollback, the database file should be back in its original
** state prior to the start of the transaction, so invoke the
** SQLITE_FCNTL_DB_UNCHANGED file-control method to disable the
** assertion that the transaction counter was modified.
*/
#ifdef SQLITE_DEBUG
- if( pPager->fd->pMethods ){
- sqlite3OsFileControlHint(pPager->fd,SQLITE_FCNTL_DB_UNCHANGED,0);
- }
+ tdsqlite3OsFileControlHint(pPager->fd,SQLITE_FCNTL_DB_UNCHANGED,0);
#endif
/* If this playback is happening automatically as a result of an IO or
@@ -53005,7 +58690,7 @@ end_playback:
if( rc==SQLITE_OK
&& (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN)
){
- rc = sqlite3PagerSync(pPager, 0);
+ rc = tdsqlite3PagerSync(pPager, 0);
}
if( rc==SQLITE_OK ){
rc = pager_end_transaction(pPager, zMaster[0]!='\0', 0);
@@ -53019,7 +58704,7 @@ end_playback:
testcase( rc!=SQLITE_OK );
}
if( isHot && nPlayback ){
- sqlite3_log(SQLITE_NOTICE_RECOVER_ROLLBACK, "recovered %d pages from %s",
+ tdsqlite3_log(SQLITE_NOTICE_RECOVER_ROLLBACK, "recovered %d pages from %s",
nPlayback, pPager->zJournal);
}
@@ -53033,7 +58718,8 @@ end_playback:
/*
-** Read the content for page pPg out of the database file and into
+** Read the content for page pPg out of the database file (or out of
+** the WAL if that is where the most recent copy if found) into
** pPg->pData. A shared lock or greater must be held on the database
** file before this function is called.
**
@@ -53043,30 +58729,33 @@ end_playback:
** If an IO error occurs, then the IO error is returned to the caller.
** Otherwise, SQLITE_OK is returned.
*/
-static int readDbPage(PgHdr *pPg, u32 iFrame){
+static int readDbPage(PgHdr *pPg){
Pager *pPager = pPg->pPager; /* Pager object associated with page pPg */
- Pgno pgno = pPg->pgno; /* Page number to read */
int rc = SQLITE_OK; /* Return code */
- int pgsz = pPager->pageSize; /* Number of bytes to read */
+
+#ifndef SQLITE_OMIT_WAL
+ u32 iFrame = 0; /* Frame of WAL containing pgno */
assert( pPager->eState>=PAGER_READER && !MEMDB );
assert( isOpen(pPager->fd) );
-#ifndef SQLITE_OMIT_WAL
+ if( pagerUseWal(pPager) ){
+ rc = tdsqlite3WalFindFrame(pPager->pWal, pPg->pgno, &iFrame);
+ if( rc ) return rc;
+ }
if( iFrame ){
- /* Try to pull the page from the write-ahead log. */
- rc = sqlite3WalReadFrame(pPager->pWal, iFrame, pgsz, pPg->pData);
+ rc = tdsqlite3WalReadFrame(pPager->pWal, iFrame,pPager->pageSize,pPg->pData);
}else
#endif
{
- i64 iOffset = (pgno-1)*(i64)pPager->pageSize;
- rc = sqlite3OsRead(pPager->fd, pPg->pData, pgsz, iOffset);
+ i64 iOffset = (pPg->pgno-1)*(i64)pPager->pageSize;
+ rc = tdsqlite3OsRead(pPager->fd, pPg->pData, pPager->pageSize, iOffset);
if( rc==SQLITE_IOERR_SHORT_READ ){
rc = SQLITE_OK;
}
}
- if( pgno==1 ){
+ if( pPg->pgno==1 ){
if( rc ){
/* If the read is unsuccessful, set the dbFileVers[] to something
** that will never be a valid file version. dbFileVers[] is a copy
@@ -53086,13 +58775,13 @@ static int readDbPage(PgHdr *pPg, u32 iFrame){
memcpy(&pPager->dbFileVers, dbFileVers, sizeof(pPager->dbFileVers));
}
}
- CODEC1(pPager, pPg->pData, pgno, 3, rc = SQLITE_NOMEM_BKPT);
+ CODEC1(pPager, pPg->pData, pPg->pgno, 3, rc = SQLITE_NOMEM_BKPT);
- PAGER_INCR(sqlite3_pager_readdb_count);
+ PAGER_INCR(tdsqlite3_pager_readdb_count);
PAGER_INCR(pPager->nRead);
- IOTRACE(("PGIN %p %d\n", pPager, pgno));
+ IOTRACE(("PGIN %p %d\n", pPager, pPg->pgno));
PAGERTRACE(("FETCH %d page %d hash(%08x)\n",
- PAGERID(pPager), pgno, pager_pagehash(pPg)));
+ PAGERID(pPager), pPg->pgno, pager_pagehash(pPg)));
return rc;
}
@@ -53109,7 +58798,7 @@ static void pager_write_changecounter(PgHdr *pPg){
u32 change_counter;
/* Increment the value just read and write it back to byte 24. */
- change_counter = sqlite3Get4byte((u8*)pPg->pPager->dbFileVers)+1;
+ change_counter = tdsqlite3Get4byte((u8*)pPg->pPager->dbFileVers)+1;
put32bits(((char*)pPg->pData)+24, change_counter);
/* Also store the SQLite version number in bytes 96..99 and in
@@ -53138,20 +58827,16 @@ static int pagerUndoCallback(void *pCtx, Pgno iPg){
PgHdr *pPg;
assert( pagerUseWal(pPager) );
- pPg = sqlite3PagerLookup(pPager, iPg);
+ pPg = tdsqlite3PagerLookup(pPager, iPg);
if( pPg ){
- if( sqlite3PcachePageRefcount(pPg)==1 ){
- sqlite3PcacheDrop(pPg);
+ if( tdsqlite3PcachePageRefcount(pPg)==1 ){
+ tdsqlite3PcacheDrop(pPg);
}else{
- u32 iFrame = 0;
- rc = sqlite3WalFindFrame(pPager->pWal, pPg->pgno, &iFrame);
- if( rc==SQLITE_OK ){
- rc = readDbPage(pPg, iFrame);
- }
+ rc = readDbPage(pPg);
if( rc==SQLITE_OK ){
pPager->xReiniter(pPg);
}
- sqlite3PagerUnrefNotNull(pPg);
+ tdsqlite3PagerUnrefNotNull(pPg);
}
}
@@ -53163,7 +58848,7 @@ static int pagerUndoCallback(void *pCtx, Pgno iPg){
** also copied into the backup databases) as part of this transaction,
** the backups must be restarted.
*/
- sqlite3BackupRestart(pPager->pBackup);
+ tdsqlite3BackupRestart(pPager->pBackup);
return rc;
}
@@ -53183,8 +58868,8 @@ static int pagerRollbackWal(Pager *pPager){
** + Reload page content from the database (if refcount>0).
*/
pPager->dbSize = pPager->dbOrigSize;
- rc = sqlite3WalUndo(pPager->pWal, pagerUndoCallback, (void *)pPager);
- pList = sqlite3PcacheDirtyList(pPager->pPCache);
+ rc = tdsqlite3WalUndo(pPager->pWal, pagerUndoCallback, (void *)pPager);
+ pList = tdsqlite3PcacheDirtyList(pPager->pPCache);
while( pList && rc==SQLITE_OK ){
PgHdr *pNext = pList->pDirty;
rc = pagerUndoCallback((void *)pPager, pList->pgno);
@@ -53195,7 +58880,7 @@ static int pagerRollbackWal(Pager *pPager){
}
/*
-** This function is a wrapper around sqlite3WalFrames(). As well as logging
+** This function is a wrapper around tdsqlite3WalFrames(). As well as logging
** the contents of the list of pages headed by pList (connected by pDirty),
** this function notifies any active backup processes that the pages have
** changed.
@@ -53243,17 +58928,17 @@ static int pagerWalFrames(
pPager->aStat[PAGER_STAT_WRITE] += nList;
if( pList->pgno==1 ) pager_write_changecounter(pList);
- rc = sqlite3WalFrames(pPager->pWal,
+ rc = tdsqlite3WalFrames(pPager->pWal,
pPager->pageSize, pList, nTruncate, isCommit, pPager->walSyncFlags
);
if( rc==SQLITE_OK && pPager->pBackup ){
for(p=pList; p; p=p->pDirty){
- sqlite3BackupUpdate(pPager->pBackup, p->pgno, (u8 *)p->pData);
+ tdsqlite3BackupUpdate(pPager->pBackup, p->pgno, (u8 *)p->pData);
}
}
#ifdef SQLITE_CHECK_PAGES
- pList = sqlite3PcacheDirtyList(pPager->pPCache);
+ pList = tdsqlite3PcacheDirtyList(pPager->pPCache);
for(p=pList; p; p=p->pDirty){
pager_set_pagehash(p);
}
@@ -53277,17 +58962,17 @@ static int pagerBeginReadTransaction(Pager *pPager){
assert( pagerUseWal(pPager) );
assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER );
- /* sqlite3WalEndReadTransaction() was not called for the previous
+ /* tdsqlite3WalEndReadTransaction() was not called for the previous
** transaction in locking_mode=EXCLUSIVE. So call it now. If we
** are in locking_mode=NORMAL and EndRead() was previously called,
** the duplicate call is harmless.
*/
- sqlite3WalEndReadTransaction(pPager->pWal);
+ tdsqlite3WalEndReadTransaction(pPager->pWal);
- rc = sqlite3WalBeginReadTransaction(pPager->pWal, &changed);
+ rc = tdsqlite3WalBeginReadTransaction(pPager->pWal, &changed);
if( rc!=SQLITE_OK || changed ){
pager_reset(pPager);
- if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0);
+ if( USEFETCH(pPager) ) tdsqlite3OsUnfetch(pPager->fd, 0, 0);
}
return rc;
@@ -53316,16 +59001,16 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){
assert( pPager->eLock>=SHARED_LOCK );
assert( isOpen(pPager->fd) );
assert( pPager->tempFile==0 );
- nPage = sqlite3WalDbsize(pPager->pWal);
+ nPage = tdsqlite3WalDbsize(pPager->pWal);
/* If the number of pages in the database is not available from the
- ** WAL sub-system, determine the page counte based on the size of
+ ** WAL sub-system, determine the page count based on the size of
** the database file. If the size of the database file is not an
** integer multiple of the page-size, round up the result.
*/
if( nPage==0 && ALWAYS(isOpen(pPager->fd)) ){
i64 n = 0; /* Size of db file in bytes */
- int rc = sqlite3OsFileSize(pPager->fd, &n);
+ int rc = tdsqlite3OsFileSize(pPager->fd, &n);
if( rc!=SQLITE_OK ){
return rc;
}
@@ -53370,23 +59055,21 @@ static int pagerOpenWalIfPresent(Pager *pPager){
if( !pPager->tempFile ){
int isWal; /* True if WAL file exists */
- Pgno nPage; /* Size of the database file */
-
- rc = pagerPagecount(pPager, &nPage);
- if( rc ) return rc;
- if( nPage==0 ){
- rc = sqlite3OsDelete(pPager->pVfs, pPager->zWal, 0);
- if( rc==SQLITE_IOERR_DELETE_NOENT ) rc = SQLITE_OK;
- isWal = 0;
- }else{
- rc = sqlite3OsAccess(
- pPager->pVfs, pPager->zWal, SQLITE_ACCESS_EXISTS, &isWal
- );
- }
+ rc = tdsqlite3OsAccess(
+ pPager->pVfs, pPager->zWal, SQLITE_ACCESS_EXISTS, &isWal
+ );
if( rc==SQLITE_OK ){
if( isWal ){
- testcase( sqlite3PcachePagecount(pPager->pPCache)==0 );
- rc = sqlite3PagerOpenWal(pPager, 0);
+ Pgno nPage; /* Size of the database file */
+
+ rc = pagerPagecount(pPager, &nPage);
+ if( rc ) return rc;
+ if( nPage==0 ){
+ rc = tdsqlite3OsDelete(pPager->pVfs, pPager->zWal, 0);
+ }else{
+ testcase( tdsqlite3PcachePagecount(pPager->pPCache)==0 );
+ rc = tdsqlite3PagerOpenWal(pPager, 0);
+ }
}else if( pPager->journalMode==PAGER_JOURNALMODE_WAL ){
pPager->journalMode = PAGER_JOURNALMODE_DELETE;
}
@@ -53443,7 +59126,7 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){
/* Allocate a bitvec to use to store the set of pages rolled back */
if( pSavepoint ){
- pDone = sqlite3BitvecCreate(pSavepoint->nOrig);
+ pDone = tdsqlite3BitvecCreate(pSavepoint->nOrig);
if( !pDone ){
return SQLITE_NOMEM_BKPT;
}
@@ -53523,7 +59206,7 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){
i64 offset = (i64)pSavepoint->iSubRec*(4+pPager->pageSize);
if( pagerUseWal(pPager) ){
- rc = sqlite3WalSavepointUndo(pPager->pWal, pSavepoint->aWalData);
+ rc = tdsqlite3WalSavepointUndo(pPager->pWal, pSavepoint->aWalData);
}
for(ii=pSavepoint->iSubRec; rc==SQLITE_OK && ii<pPager->nSubRec; ii++){
assert( offset==(i64)ii*(4+pPager->pageSize) );
@@ -53532,7 +59215,7 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){
assert( rc!=SQLITE_DONE );
}
- sqlite3BitvecDestroy(pDone);
+ tdsqlite3BitvecDestroy(pDone);
if( rc==SQLITE_OK ){
pPager->journalOff = szJ;
}
@@ -53544,16 +59227,16 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){
** Change the maximum number of in-memory pages that are allowed
** before attempting to recycle clean and unused pages.
*/
-SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager *pPager, int mxPage){
- sqlite3PcacheSetCachesize(pPager->pPCache, mxPage);
+SQLITE_PRIVATE void tdsqlite3PagerSetCachesize(Pager *pPager, int mxPage){
+ tdsqlite3PcacheSetCachesize(pPager->pPCache, mxPage);
}
/*
** Change the maximum number of in-memory pages that are allowed
** before attempting to spill pages to journal.
*/
-SQLITE_PRIVATE int sqlite3PagerSetSpillsize(Pager *pPager, int mxPage){
- return sqlite3PcacheSetSpillsize(pPager->pPCache, mxPage);
+SQLITE_PRIVATE int tdsqlite3PagerSetSpillsize(Pager *pPager, int mxPage){
+ return tdsqlite3PcacheSetSpillsize(pPager->pPCache, mxPage);
}
/*
@@ -53561,12 +59244,13 @@ SQLITE_PRIVATE int sqlite3PagerSetSpillsize(Pager *pPager, int mxPage){
*/
static void pagerFixMaplimit(Pager *pPager){
#if SQLITE_MAX_MMAP_SIZE>0
- sqlite3_file *fd = pPager->fd;
+ tdsqlite3_file *fd = pPager->fd;
if( isOpen(fd) && fd->pMethods->iVersion>=3 ){
- sqlite3_int64 sz;
+ tdsqlite3_int64 sz;
sz = pPager->szMmap;
pPager->bUseFetch = (sz>0);
- sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_MMAP_SIZE, &sz);
+ setGetterMethod(pPager);
+ tdsqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_MMAP_SIZE, &sz);
}
#endif
}
@@ -53574,7 +59258,7 @@ static void pagerFixMaplimit(Pager *pPager){
/*
** Change the maximum size of any memory mapping made of the database file.
*/
-SQLITE_PRIVATE void sqlite3PagerSetMmapLimit(Pager *pPager, sqlite3_int64 szMmap){
+SQLITE_PRIVATE void tdsqlite3PagerSetMmapLimit(Pager *pPager, tdsqlite3_int64 szMmap){
pPager->szMmap = szMmap;
pagerFixMaplimit(pPager);
}
@@ -53582,8 +59266,8 @@ SQLITE_PRIVATE void sqlite3PagerSetMmapLimit(Pager *pPager, sqlite3_int64 szMmap
/*
** Free as much memory as possible from the pager.
*/
-SQLITE_PRIVATE void sqlite3PagerShrink(Pager *pPager){
- sqlite3PcacheShrink(pPager->pPCache);
+SQLITE_PRIVATE void tdsqlite3PagerShrink(Pager *pPager){
+ tdsqlite3PcacheShrink(pPager->pPCache);
}
/*
@@ -53594,7 +59278,7 @@ SQLITE_PRIVATE void sqlite3PagerShrink(Pager *pPager){
** changing the number of syncs()s when writing the journals.
** There are four levels:
**
-** OFF sqlite3OsSync() is never called. This is the default
+** OFF tdsqlite3OsSync() is never called. This is the default
** for temporary and transient files.
**
** NORMAL The journal is synced once before writes begin on the
@@ -53638,7 +59322,7 @@ SQLITE_PRIVATE void sqlite3PagerShrink(Pager *pPager){
** and FULL=3.
*/
#ifndef SQLITE_OMIT_PAGER_PRAGMAS
-SQLITE_PRIVATE void sqlite3PagerSetFlags(
+SQLITE_PRIVATE void tdsqlite3PagerSetFlags(
Pager *pPager, /* The pager to set safety level for */
unsigned pgFlags /* Various flags */
){
@@ -53654,20 +59338,17 @@ SQLITE_PRIVATE void sqlite3PagerSetFlags(
}
if( pPager->noSync ){
pPager->syncFlags = 0;
- pPager->ckptSyncFlags = 0;
}else if( pgFlags & PAGER_FULLFSYNC ){
pPager->syncFlags = SQLITE_SYNC_FULL;
- pPager->ckptSyncFlags = SQLITE_SYNC_FULL;
- }else if( pgFlags & PAGER_CKPT_FULLFSYNC ){
- pPager->syncFlags = SQLITE_SYNC_NORMAL;
- pPager->ckptSyncFlags = SQLITE_SYNC_FULL;
}else{
pPager->syncFlags = SQLITE_SYNC_NORMAL;
- pPager->ckptSyncFlags = SQLITE_SYNC_NORMAL;
}
- pPager->walSyncFlags = pPager->syncFlags;
+ pPager->walSyncFlags = (pPager->syncFlags<<2);
if( pPager->fullSync ){
- pPager->walSyncFlags |= WAL_SYNC_TRANSACTIONS;
+ pPager->walSyncFlags |= pPager->syncFlags;
+ }
+ if( (pgFlags & PAGER_CKPT_FULLFSYNC) && !pPager->noSync ){
+ pPager->walSyncFlags |= (SQLITE_SYNC_FULL<<2);
}
if( pgFlags & PAGER_CACHESPILL ){
pPager->doNotSpill &= ~SPILLFLAG_OFF;
@@ -53683,7 +59364,7 @@ SQLITE_PRIVATE void sqlite3PagerSetFlags(
** testing and analysis only.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_opentemp_count = 0;
+SQLITE_API int tdsqlite3_opentemp_count = 0;
#endif
/*
@@ -53703,18 +59384,18 @@ SQLITE_API int sqlite3_opentemp_count = 0;
*/
static int pagerOpentemp(
Pager *pPager, /* The pager object */
- sqlite3_file *pFile, /* Write the file descriptor here */
+ tdsqlite3_file *pFile, /* Write the file descriptor here */
int vfsFlags /* Flags passed through to the VFS */
){
int rc; /* Return code */
#ifdef SQLITE_TEST
- sqlite3_opentemp_count++; /* Used for testing and analysis only */
+ tdsqlite3_opentemp_count++; /* Used for testing and analysis only */
#endif
vfsFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE;
- rc = sqlite3OsOpen(pPager->pVfs, 0, pFile, vfsFlags, 0);
+ rc = tdsqlite3OsOpen(pPager->pVfs, 0, pFile, vfsFlags, 0);
assert( rc!=SQLITE_OK || isOpen(pFile) );
return rc;
}
@@ -53722,7 +59403,7 @@ static int pagerOpentemp(
/*
** Set the busy handler function.
**
-** The pager invokes the busy-handler if sqlite3OsLock() returns
+** The pager invokes the busy-handler if tdsqlite3OsLock() returns
** SQLITE_BUSY when trying to upgrade from no-lock to a SHARED lock,
** or when trying to upgrade from a RESERVED lock to an EXCLUSIVE
** lock. It does *not* invoke the busy handler when upgrading from
@@ -53740,20 +59421,18 @@ static int pagerOpentemp(
** retried. If it returns zero, then the SQLITE_BUSY error is
** returned to the caller of the pager API function.
*/
-SQLITE_PRIVATE void sqlite3PagerSetBusyhandler(
+SQLITE_PRIVATE void tdsqlite3PagerSetBusyHandler(
Pager *pPager, /* Pager object */
int (*xBusyHandler)(void *), /* Pointer to busy-handler function */
void *pBusyHandlerArg /* Argument to pass to xBusyHandler */
){
+ void **ap;
pPager->xBusyHandler = xBusyHandler;
pPager->pBusyHandlerArg = pBusyHandlerArg;
-
- if( isOpen(pPager->fd) ){
- void **ap = (void **)&pPager->xBusyHandler;
- assert( ((int(*)(void *))(ap[0]))==xBusyHandler );
- assert( ap[1]==pBusyHandlerArg );
- sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_BUSYHANDLER, (void *)ap);
- }
+ ap = (void **)&pPager->xBusyHandler;
+ assert( ((int(*)(void *))(ap[0]))==xBusyHandler );
+ assert( ap[1]==pBusyHandlerArg );
+ tdsqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_BUSYHANDLER, (void *)ap);
}
/*
@@ -53776,7 +59455,7 @@ SQLITE_PRIVATE void sqlite3PagerSetBusyhandler(
**
** then the pager object page size is set to *pPageSize.
**
-** If the page size is changed, then this function uses sqlite3PagerMalloc()
+** If the page size is changed, then this function uses tdsqlite3PagerMalloc()
** to obtain a new Pager.pTmpSpace buffer. If this allocation attempt
** fails, SQLITE_NOMEM is returned and the page size remains unchanged.
** In all other cases, SQLITE_OK is returned.
@@ -53786,7 +59465,7 @@ SQLITE_PRIVATE void sqlite3PagerSetBusyhandler(
** function was called, or because the memory allocation attempt failed,
** then *pPageSize is set to the old, retained page size before returning.
*/
-SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){
+SQLITE_PRIVATE int tdsqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){
int rc = SQLITE_OK;
/* It is not possible to do a full assert_pager_state() here, as this
@@ -53802,31 +59481,37 @@ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nR
u32 pageSize = *pPageSize;
assert( pageSize==0 || (pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE) );
if( (pPager->memDb==0 || pPager->dbSize==0)
- && sqlite3PcacheRefCount(pPager->pPCache)==0
+ && tdsqlite3PcacheRefCount(pPager->pPCache)==0
&& pageSize && pageSize!=(u32)pPager->pageSize
){
char *pNew = NULL; /* New temp space */
i64 nByte = 0;
if( pPager->eState>PAGER_OPEN && isOpen(pPager->fd) ){
- rc = sqlite3OsFileSize(pPager->fd, &nByte);
+ rc = tdsqlite3OsFileSize(pPager->fd, &nByte);
}
if( rc==SQLITE_OK ){
- pNew = (char *)sqlite3PageMalloc(pageSize);
- if( !pNew ) rc = SQLITE_NOMEM_BKPT;
+ /* 8 bytes of zeroed overrun space is sufficient so that the b-tree
+ * cell header parser will never run off the end of the allocation */
+ pNew = (char *)tdsqlite3PageMalloc(pageSize+8);
+ if( !pNew ){
+ rc = SQLITE_NOMEM_BKPT;
+ }else{
+ memset(pNew+pageSize, 0, 8);
+ }
}
if( rc==SQLITE_OK ){
pager_reset(pPager);
- rc = sqlite3PcacheSetPageSize(pPager->pPCache, pageSize);
+ rc = tdsqlite3PcacheSetPageSize(pPager->pPCache, pageSize);
}
if( rc==SQLITE_OK ){
- sqlite3PageFree(pPager->pTmpSpace);
+ tdsqlite3PageFree(pPager->pTmpSpace);
pPager->pTmpSpace = pNew;
pPager->dbSize = (Pgno)((nByte+pageSize-1)/pageSize);
pPager->pageSize = pageSize;
}else{
- sqlite3PageFree(pNew);
+ tdsqlite3PageFree(pNew);
}
}
@@ -53849,7 +59534,7 @@ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nR
** occurs. But other modules are free to use it too, as long as
** no rollbacks are happening.
*/
-SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager *pPager){
+SQLITE_PRIVATE void *tdsqlite3PagerTempSpace(Pager *pPager){
return pPager->pTmpSpace;
}
@@ -53860,12 +59545,15 @@ SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager *pPager){
**
** Regardless of mxPage, return the current maximum page count.
*/
-SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager *pPager, int mxPage){
+SQLITE_PRIVATE int tdsqlite3PagerMaxPageCount(Pager *pPager, int mxPage){
if( mxPage>0 ){
pPager->mxPgno = mxPage;
}
assert( pPager->eState!=PAGER_OPEN ); /* Called only by OP_MaxPgcnt */
- assert( pPager->mxPgno>=pPager->dbSize ); /* OP_MaxPgcnt enforces this */
+ /* assert( pPager->mxPgno>=pPager->dbSize ); */
+ /* OP_MaxPgcnt ensures that the parameter passed to this function is not
+ ** less than the total number of valid pages in the database. But this
+ ** may be less than Pager.dbSize, and so the assert() above is not valid */
return pPager->mxPgno;
}
@@ -53878,15 +59566,15 @@ SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager *pPager, int mxPage){
** and generate no code.
*/
#ifdef SQLITE_TEST
-SQLITE_API extern int sqlite3_io_error_pending;
-SQLITE_API extern int sqlite3_io_error_hit;
+SQLITE_API extern int tdsqlite3_io_error_pending;
+SQLITE_API extern int tdsqlite3_io_error_hit;
static int saved_cnt;
void disable_simulated_io_errors(void){
- saved_cnt = sqlite3_io_error_pending;
- sqlite3_io_error_pending = -1;
+ saved_cnt = tdsqlite3_io_error_pending;
+ tdsqlite3_io_error_pending = -1;
}
void enable_simulated_io_errors(void){
- sqlite3_io_error_pending = saved_cnt;
+ tdsqlite3_io_error_pending = saved_cnt;
}
#else
# define disable_simulated_io_errors()
@@ -53907,7 +59595,7 @@ void enable_simulated_io_errors(void){
** the error code is returned to the caller and the contents of the
** output buffer undefined.
*/
-SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager *pPager, int N, unsigned char *pDest){
+SQLITE_PRIVATE int tdsqlite3PagerReadFileheader(Pager *pPager, int N, unsigned char *pDest){
int rc = SQLITE_OK;
memset(pDest, 0, N);
assert( isOpen(pPager->fd) || pPager->tempFile );
@@ -53920,7 +59608,7 @@ SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager *pPager, int N, unsigned cha
if( isOpen(pPager->fd) ){
IOTRACE(("DBHDR %p 0 %d\n", pPager, N))
- rc = sqlite3OsRead(pPager->fd, pDest, N, 0);
+ rc = tdsqlite3OsRead(pPager->fd, pDest, N, 0);
if( rc==SQLITE_IOERR_SHORT_READ ){
rc = SQLITE_OK;
}
@@ -53935,7 +59623,7 @@ SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager *pPager, int N, unsigned cha
** However, if the file is between 1 and <page-size> bytes in size, then
** this is considered a 1 page file.
*/
-SQLITE_PRIVATE void sqlite3PagerPagecount(Pager *pPager, int *pnPage){
+SQLITE_PRIVATE void tdsqlite3PagerPagecount(Pager *pPager, int *pnPage){
assert( pPager->eState>=PAGER_READER );
assert( pPager->eState!=PAGER_WRITER_FINISHED );
*pnPage = (int)pPager->dbSize;
@@ -53947,7 +59635,7 @@ SQLITE_PRIVATE void sqlite3PagerPagecount(Pager *pPager, int *pnPage){
** a similar or greater lock is already held, this function is a no-op
** (returning SQLITE_OK immediately).
**
-** Otherwise, attempt to obtain the lock using sqlite3OsLock(). Invoke
+** Otherwise, attempt to obtain the lock using tdsqlite3OsLock(). Invoke
** the busy callback if the lock is currently not available. Repeat
** until the busy callback returns false or until the attempt to
** obtain the lock succeeds.
@@ -53962,7 +59650,7 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){
/* Check that this is either a no-op (because the requested lock is
** already held), or one of the transitions that the busy-handler
** may be invoked during, according to the comment above
- ** sqlite3PagerSetBusyhandler().
+ ** tdsqlite3PagerSetBusyhandler().
*/
assert( (pPager->eLock>=locktype)
|| (pPager->eLock==NO_LOCK && locktype==SHARED_LOCK)
@@ -54003,7 +59691,7 @@ static void assertTruncateConstraintCb(PgHdr *pPg){
assert( !subjRequiresPage(pPg) || pPg->pgno<=pPg->pPager->dbSize );
}
static void assertTruncateConstraint(Pager *pPager){
- sqlite3PcacheIterateDirty(pPager->pPCache, assertTruncateConstraintCb);
+ tdsqlite3PcacheIterateDirty(pPager->pPCache, assertTruncateConstraintCb);
}
#else
# define assertTruncateConstraint(pPager)
@@ -54020,7 +59708,7 @@ static void assertTruncateConstraint(Pager *pPager){
** rolled back or committed. It is not safe to call this function and
** then continue writing to the database.
*/
-SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){
+SQLITE_PRIVATE void tdsqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){
assert( pPager->dbSize>=nPage );
assert( pPager->eState>=PAGER_WRITER_CACHEMOD );
pPager->dbSize = nPage;
@@ -54054,14 +59742,15 @@ SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){
static int pagerSyncHotJournal(Pager *pPager){
int rc = SQLITE_OK;
if( !pPager->noSync ){
- rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_NORMAL);
+ rc = tdsqlite3OsSync(pPager->jfd, SQLITE_SYNC_NORMAL);
}
if( rc==SQLITE_OK ){
- rc = sqlite3OsFileSize(pPager->jfd, &pPager->journalHdr);
+ rc = tdsqlite3OsFileSize(pPager->jfd, &pPager->journalHdr);
}
return rc;
}
+#if SQLITE_MAX_MMAP_SIZE>0
/*
** Obtain a reference to a memory mapped page object for page number pgno.
** The new object will use the pointer pData, obtained from xFetch().
@@ -54084,11 +59773,12 @@ static int pagerAcquireMapPage(
*ppPage = p = pPager->pMmapFreelist;
pPager->pMmapFreelist = p->pDirty;
p->pDirty = 0;
- memset(p->pExtra, 0, pPager->nExtra);
+ assert( pPager->nExtra>=8 );
+ memset(p->pExtra, 0, 8);
}else{
- *ppPage = p = (PgHdr *)sqlite3MallocZero(sizeof(PgHdr) + pPager->nExtra);
+ *ppPage = p = (PgHdr *)tdsqlite3MallocZero(sizeof(PgHdr) + pPager->nExtra);
if( p==0 ){
- sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1) * pPager->pageSize, pData);
+ tdsqlite3OsUnfetch(pPager->fd, (i64)(pgno-1) * pPager->pageSize, pData);
return SQLITE_NOMEM_BKPT;
}
p->pExtra = (void *)&p[1];
@@ -54109,6 +59799,7 @@ static int pagerAcquireMapPage(
return SQLITE_OK;
}
+#endif
/*
** Release a reference to page pPg. pPg must have been returned by an
@@ -54121,7 +59812,7 @@ static void pagerReleaseMapPage(PgHdr *pPg){
pPager->pMmapFreelist = pPg;
assert( pPager->fd->pMethods->iVersion>=3 );
- sqlite3OsUnfetch(pPager->fd, (i64)(pPg->pgno-1)*pPager->pageSize, pPg->pData);
+ tdsqlite3OsUnfetch(pPager->fd, (i64)(pPg->pgno-1)*pPager->pageSize, pPg->pData);
}
/*
@@ -54132,8 +59823,32 @@ static void pagerFreeMapHdrs(Pager *pPager){
PgHdr *pNext;
for(p=pPager->pMmapFreelist; p; p=pNext){
pNext = p->pDirty;
- sqlite3_free(p);
+ tdsqlite3_free(p);
+ }
+}
+
+/* Verify that the database file has not be deleted or renamed out from
+** under the pager. Return SQLITE_OK if the database is still where it ought
+** to be on disk. Return non-zero (SQLITE_READONLY_DBMOVED or some other error
+** code from tdsqlite3OsAccess()) if the database has gone missing.
+*/
+static int databaseIsUnmoved(Pager *pPager){
+ int bHasMoved = 0;
+ int rc;
+
+ if( pPager->tempFile ) return SQLITE_OK;
+ if( pPager->dbSize==0 ) return SQLITE_OK;
+ assert( pPager->zFilename && pPager->zFilename[0] );
+ rc = tdsqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_HAS_MOVED, &bHasMoved);
+ if( rc==SQLITE_NOTFOUND ){
+ /* If the HAS_MOVED file-control is unimplemented, assume that the file
+ ** has not been moved. That is the historical behavior of SQLite: prior to
+ ** version 3.8.3, it never checked */
+ rc = SQLITE_OK;
+ }else if( rc==SQLITE_OK && bHasMoved ){
+ rc = SQLITE_READONLY_DBMOVED;
}
+ return rc;
}
@@ -54151,18 +59866,27 @@ static void pagerFreeMapHdrs(Pager *pPager){
** a hot journal may be left in the filesystem but no error is returned
** to the caller.
*/
-SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager){
- u8 *pTmp = (u8 *)pPager->pTmpSpace;
-
+SQLITE_PRIVATE int tdsqlite3PagerClose(Pager *pPager, tdsqlite3 *db){
+ u8 *pTmp = (u8*)pPager->pTmpSpace;
+ assert( db || pagerUseWal(pPager)==0 );
assert( assert_pager_state(pPager) );
disable_simulated_io_errors();
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
pagerFreeMapHdrs(pPager);
/* pPager->errCode = 0; */
pPager->exclusiveMode = 0;
#ifndef SQLITE_OMIT_WAL
- sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, pPager->pageSize, pTmp);
- pPager->pWal = 0;
+ {
+ u8 *a = 0;
+ assert( db || pPager->pWal==0 );
+ if( db && 0==(db->flags & SQLITE_NoCkptOnClose)
+ && SQLITE_OK==databaseIsUnmoved(pPager)
+ ){
+ a = pTmp;
+ }
+ tdsqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,a);
+ pPager->pWal = 0;
+ }
#endif
pager_reset(pPager);
if( MEMDB ){
@@ -54184,14 +59908,14 @@ SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager){
}
pagerUnlockAndRollback(pPager);
}
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
enable_simulated_io_errors();
PAGERTRACE(("CLOSE %d\n", PAGERID(pPager)));
IOTRACE(("CLOSE %p\n", pPager))
- sqlite3OsClose(pPager->jfd);
- sqlite3OsClose(pPager->fd);
- sqlite3PageFree(pTmp);
- sqlite3PcacheClose(pPager->pPCache);
+ tdsqlite3OsClose(pPager->jfd);
+ tdsqlite3OsClose(pPager->fd);
+ tdsqlite3PageFree(pTmp);
+ tdsqlite3PcacheClose(pPager->pPCache);
#ifdef SQLITE_HAS_CODEC
if( pPager->xCodecFree ) pPager->xCodecFree(pPager->pCodec);
@@ -54200,7 +59924,7 @@ SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager){
assert( !pPager->aSavepoint && !pPager->pInJournal );
assert( !isOpen(pPager->jfd) && !isOpen(pPager->sjfd) );
- sqlite3_free(pPager);
+ tdsqlite3_free(pPager);
return SQLITE_OK;
}
@@ -54208,7 +59932,7 @@ SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager){
/*
** Return the page number for page pPg.
*/
-SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage *pPg){
+SQLITE_PRIVATE Pgno tdsqlite3PagerPagenumber(DbPage *pPg){
return pPg->pgno;
}
#endif
@@ -54216,8 +59940,8 @@ SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage *pPg){
/*
** Increment the reference count for page pPg.
*/
-SQLITE_PRIVATE void sqlite3PagerRef(DbPage *pPg){
- sqlite3PcacheRef(pPg);
+SQLITE_PRIVATE void tdsqlite3PagerRef(DbPage *pPg){
+ tdsqlite3PcacheRef(pPg);
}
/*
@@ -54264,13 +59988,13 @@ static int syncJournal(Pager *pPager, int newHdr){
assert( assert_pager_state(pPager) );
assert( !pagerUseWal(pPager) );
- rc = sqlite3PagerExclusiveLock(pPager);
+ rc = tdsqlite3PagerExclusiveLock(pPager);
if( rc!=SQLITE_OK ) return rc;
if( !pPager->noSync ){
assert( !pPager->tempFile );
if( isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){
- const int iDc = sqlite3OsDeviceCharacteristics(pPager->fd);
+ const int iDc = tdsqlite3OsDeviceCharacteristics(pPager->fd);
assert( isOpen(pPager->jfd) );
if( 0==(iDc&SQLITE_IOCAP_SAFE_APPEND) ){
@@ -54304,10 +60028,10 @@ static int syncJournal(Pager *pPager, int newHdr){
put32bits(&zHeader[sizeof(aJournalMagic)], pPager->nRec);
iNextHdrOffset = journalHdrOffset(pPager);
- rc = sqlite3OsRead(pPager->jfd, aMagic, 8, iNextHdrOffset);
+ rc = tdsqlite3OsRead(pPager->jfd, aMagic, 8, iNextHdrOffset);
if( rc==SQLITE_OK && 0==memcmp(aMagic, aJournalMagic, 8) ){
static const u8 zerobyte = 0;
- rc = sqlite3OsWrite(pPager->jfd, &zerobyte, 1, iNextHdrOffset);
+ rc = tdsqlite3OsWrite(pPager->jfd, &zerobyte, 1, iNextHdrOffset);
}
if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){
return rc;
@@ -54327,11 +60051,11 @@ static int syncJournal(Pager *pPager, int newHdr){
if( pPager->fullSync && 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){
PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager)));
IOTRACE(("JSYNC %p\n", pPager))
- rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags);
+ rc = tdsqlite3OsSync(pPager->jfd, pPager->syncFlags);
if( rc!=SQLITE_OK ) return rc;
}
IOTRACE(("JHDR %p %lld\n", pPager, pPager->journalHdr));
- rc = sqlite3OsWrite(
+ rc = tdsqlite3OsWrite(
pPager->jfd, zHeader, sizeof(zHeader), pPager->journalHdr
);
if( rc!=SQLITE_OK ) return rc;
@@ -54339,7 +60063,7 @@ static int syncJournal(Pager *pPager, int newHdr){
if( 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){
PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager)));
IOTRACE(("JSYNC %p\n", pPager))
- rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags|
+ rc = tdsqlite3OsSync(pPager->jfd, pPager->syncFlags|
(pPager->syncFlags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0)
);
if( rc!=SQLITE_OK ) return rc;
@@ -54360,7 +60084,7 @@ static int syncJournal(Pager *pPager, int newHdr){
** successfully synced. Either way, clear the PGHDR_NEED_SYNC flag on
** all pages.
*/
- sqlite3PcacheClearSyncFlags(pPager->pPCache);
+ tdsqlite3PcacheClearSyncFlags(pPager->pPCache);
pPager->eState = PAGER_WRITER_DBMOD;
assert( assert_pager_state(pPager) );
return SQLITE_OK;
@@ -54424,8 +60148,8 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
&& pPager->dbHintSize<pPager->dbSize
&& (pList->pDirty || pList->pgno>pPager->dbHintSize)
){
- sqlite3_int64 szFile = pPager->pageSize * (sqlite3_int64)pPager->dbSize;
- sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &szFile);
+ tdsqlite3_int64 szFile = pPager->pageSize * (tdsqlite3_int64)pPager->dbSize;
+ tdsqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &szFile);
pPager->dbHintSize = pPager->dbSize;
}
@@ -54433,12 +60157,12 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
Pgno pgno = pList->pgno;
/* If there are dirty pages in the page cache with page numbers greater
- ** than Pager.dbSize, this means sqlite3PagerTruncateImage() was called to
+ ** than Pager.dbSize, this means tdsqlite3PagerTruncateImage() was called to
** make the file smaller (presumably by auto-vacuum code). Do not write
** any such pages to the file.
**
** Also, do not write out any page that has the PGHDR_DONT_WRITE flag
- ** set (set by sqlite3PagerDontWrite()).
+ ** set (set by tdsqlite3PagerDontWrite()).
*/
if( pgno<=pPager->dbSize && 0==(pList->flags&PGHDR_DONT_WRITE) ){
i64 offset = (pgno-1)*(i64)pPager->pageSize; /* Offset to write */
@@ -54451,7 +60175,7 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
CODEC2(pPager, pList->pData, pgno, 6, return SQLITE_NOMEM_BKPT, pData);
/* Write out the page data. */
- rc = sqlite3OsWrite(pPager->fd, pData, pPager->pageSize, offset);
+ rc = tdsqlite3OsWrite(pPager->fd, pData, pPager->pageSize, offset);
/* If page 1 was just written, update Pager.dbFileVers to match
** the value now stored in the database file. If writing this
@@ -54466,12 +60190,12 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
pPager->aStat[PAGER_STAT_WRITE]++;
/* Update any backup objects copying the contents of this pager. */
- sqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)pList->pData);
+ tdsqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)pList->pData);
PAGERTRACE(("STORE %d page %d hash(%08x)\n",
PAGERID(pPager), pgno, pager_pagehash(pList)));
IOTRACE(("PGOUT %p %d\n", pPager, pgno));
- PAGER_INCR(sqlite3_pager_writedb_count);
+ PAGER_INCR(tdsqlite3_pager_writedb_count);
}else{
PAGERTRACE(("NOSTORE %d page %d\n", PAGERID(pPager), pgno));
}
@@ -54487,7 +60211,7 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
** function is a no-op.
**
** SQLITE_OK is returned if everything goes according to plan. An
-** SQLITE_IOERR_XXX error code is returned if a call to sqlite3OsOpen()
+** SQLITE_IOERR_XXX error code is returned if a call to tdsqlite3OsOpen()
** fails.
*/
static int openSubJournal(Pager *pPager){
@@ -54496,11 +60220,11 @@ static int openSubJournal(Pager *pPager){
const int flags = SQLITE_OPEN_SUBJOURNAL | SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE
| SQLITE_OPEN_DELETEONCLOSE;
- int nStmtSpill = sqlite3Config.nStmtSpill;
+ int nStmtSpill = tdsqlite3Config.nStmtSpill;
if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY || pPager->subjInMemory ){
nStmtSpill = -1;
}
- rc = sqlite3JournalOpen(pPager->pVfs, 0, pPager->sjfd, flags, nStmtSpill);
+ rc = tdsqlite3JournalOpen(pPager->pVfs, 0, pPager->sjfd, flags, nStmtSpill);
}
return rc;
}
@@ -54537,12 +60261,17 @@ static int subjournalPage(PgHdr *pPg){
void *pData = pPg->pData;
i64 offset = (i64)pPager->nSubRec*(4+pPager->pageSize);
char *pData2;
-
- CODEC2(pPager, pData, pPg->pgno, 7, return SQLITE_NOMEM_BKPT, pData2);
+
+#if SQLITE_HAS_CODEC
+ if( !pPager->subjInMemory ){
+ CODEC2(pPager, pData, pPg->pgno, 7, return SQLITE_NOMEM_BKPT, pData2);
+ }else
+#endif
+ pData2 = pData;
PAGERTRACE(("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno));
rc = write32bits(pPager->sjfd, offset, pPg->pgno);
if( rc==SQLITE_OK ){
- rc = sqlite3OsWrite(pPager->sjfd, pData2, pPager->pageSize, offset+4);
+ rc = tdsqlite3OsWrite(pPager->sjfd, pData2, pPager->pageSize, offset+4);
}
}
}
@@ -54574,11 +60303,11 @@ static int subjournalPageIfRequired(PgHdr *pPg){
** out to the database file, if possible. This may involve syncing the
** journal file.
**
-** If successful, sqlite3PcacheMakeClean() is called on the page and
+** If successful, tdsqlite3PcacheMakeClean() is called on the page and
** SQLITE_OK returned. If an IO error occurs while trying to make the
** page clean, the IO error code is returned. If the page cannot be
** made clean for some other reason, but no error occurs, then SQLITE_OK
-** is returned by sqlite3PcacheMakeClean() is not called.
+** is returned by tdsqlite3PcacheMakeClean() is not called.
*/
static int pagerStress(void *p, PgHdr *pPg){
Pager *pPager = (Pager *)p;
@@ -54589,7 +60318,7 @@ static int pagerStress(void *p, PgHdr *pPg){
/* The doNotSpill NOSYNC bit is set during times when doing a sync of
** journal (and adding a new header) is not allowed. This occurs
- ** during calls to sqlite3PagerWrite() while trying to journal multiple
+ ** during calls to tdsqlite3PagerWrite() while trying to journal multiple
** pages belonging to the same sector.
**
** The doNotSpill ROLLBACK and OFF bits inhibits all cache spilling
@@ -54598,7 +60327,7 @@ static int pagerStress(void *p, PgHdr *pPg){
**
** Spilling is also prohibited when in an error state since that could
** lead to database corruption. In the current implementation it
- ** is impossible for sqlite3PcacheFetch() to be called with createFlag==3
+ ** is impossible for tdsqlite3PcacheFetch() to be called with createFlag==3
** while in the error state, hence it is impossible for this routine to
** be called in the error state. Nevertheless, we include a NEVER()
** test for the error state as a safeguard against future changes.
@@ -54614,6 +60343,7 @@ static int pagerStress(void *p, PgHdr *pPg){
return SQLITE_OK;
}
+ pPager->aStat[PAGER_STAT_SPILL]++;
pPg->pDirty = 0;
if( pagerUseWal(pPager) ){
/* Write a single frame for this page to the log. */
@@ -54622,6 +60352,13 @@ static int pagerStress(void *p, PgHdr *pPg){
rc = pagerWalFrames(pPager, pPg, 0, 0);
}
}else{
+
+#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE
+ if( pPager->tempFile==0 ){
+ rc = tdsqlite3JournalCreate(pPager->jfd);
+ if( rc!=SQLITE_OK ) return pager_error(pPager, rc);
+ }
+#endif
/* Sync the journal file if required. */
if( pPg->flags&PGHDR_NEED_SYNC
@@ -54640,7 +60377,7 @@ static int pagerStress(void *p, PgHdr *pPg){
/* Mark the page as clean. */
if( rc==SQLITE_OK ){
PAGERTRACE(("STRESS %d page %d\n", PAGERID(pPager), pPg->pgno));
- sqlite3PcacheMakeClean(pPg);
+ tdsqlite3PcacheMakeClean(pPg);
}
return pager_error(pPager, rc);
@@ -54649,10 +60386,10 @@ static int pagerStress(void *p, PgHdr *pPg){
/*
** Flush all unreferenced dirty pages to disk.
*/
-SQLITE_PRIVATE int sqlite3PagerFlush(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerFlush(Pager *pPager){
int rc = pPager->errCode;
if( !MEMDB ){
- PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
+ PgHdr *pList = tdsqlite3PcacheDirtyList(pPager->pPCache);
assert( assert_pager_state(pPager) );
while( rc==SQLITE_OK && pList ){
PgHdr *pNext = pList->pDirty;
@@ -54669,7 +60406,7 @@ SQLITE_PRIVATE int sqlite3PagerFlush(Pager *pPager){
/*
** Allocate and initialize a new Pager object and put a pointer to it
** in *ppPager. The pager should eventually be freed by passing it
-** to sqlite3PagerClose().
+** to tdsqlite3PagerClose().
**
** The zFilename argument is the path to the database file to open.
** If zFilename is NULL then a randomly-named temporary file is created
@@ -54680,7 +60417,9 @@ SQLITE_PRIVATE int sqlite3PagerFlush(Pager *pPager){
**
** The nExtra parameter specifies the number of bytes of space allocated
** along with each page reference. This space is available to the user
-** via the sqlite3PagerGetExtra() API.
+** via the tdsqlite3PagerGetExtra() API. When a new page is allocated, the
+** first 8 bytes of this space are zeroed but the remainder is uninitialized.
+** (The extra space is used by btree as the MemPage object.)
**
** The flags argument is used to specify properties that affect the
** operation of the pager. It should be passed some bitwise combination
@@ -54693,16 +60432,16 @@ SQLITE_PRIVATE int sqlite3PagerFlush(Pager *pPager){
** successfully, SQLITE_OK is returned and *ppPager set to point to
** the new pager object. If an error occurs, *ppPager is set to NULL
** and error code returned. This function may return SQLITE_NOMEM
-** (sqlite3Malloc() is used to allocate memory), SQLITE_CANTOPEN or
+** (tdsqlite3Malloc() is used to allocate memory), SQLITE_CANTOPEN or
** various SQLITE_IO_XXX errors.
*/
-SQLITE_PRIVATE int sqlite3PagerOpen(
- sqlite3_vfs *pVfs, /* The virtual file system to use */
+SQLITE_PRIVATE int tdsqlite3PagerOpen(
+ tdsqlite3_vfs *pVfs, /* The virtual file system to use */
Pager **ppPager, /* OUT: Return the Pager structure here */
const char *zFilename, /* Name of the database file to open */
int nExtra, /* Extra bytes append to each in-memory page */
int flags, /* flags controlling this file */
- int vfsFlags, /* flags passed through to sqlite3_vfs.xOpen() */
+ int vfsFlags, /* flags passed through to tdsqlite3_vfs.xOpen() */
void (*xReinit)(DbPage*) /* Function to reinitialize pages */
){
u8 *pPtr;
@@ -54710,19 +60449,25 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
int rc = SQLITE_OK; /* Return code */
int tempFile = 0; /* True for temp files (incl. in-memory files) */
int memDb = 0; /* True if this is an in-memory file */
+#ifdef SQLITE_ENABLE_DESERIALIZE
+ int memJM = 0; /* Memory journal mode */
+#else
+# define memJM 0
+#endif
int readOnly = 0; /* True if this is a read-only file */
int journalFileSize; /* Bytes to allocate for each journal fd */
char *zPathname = 0; /* Full path to database file */
int nPathname = 0; /* Number of bytes in zPathname */
int useJournal = (flags & PAGER_OMIT_JOURNAL)==0; /* False to omit journal */
- int pcacheSize = sqlite3PcacheSize(); /* Bytes to allocate for PCache */
+ int pcacheSize = tdsqlite3PcacheSize(); /* Bytes to allocate for PCache */
u32 szPageDflt = SQLITE_DEFAULT_PAGE_SIZE; /* Default page size */
const char *zUri = 0; /* URI args to copy */
- int nUri = 0; /* Number of bytes of URI args at *zUri */
+ int nUriByte = 1; /* Number of bytes of URI args at *zUri */
+ int nUri = 0; /* Number of URI parameters */
/* Figure out how much space is required for each journal file-handle
** (there are two of them, the main journal and the sub-journal). */
- journalFileSize = ROUND8(sqlite3JournalSize(pVfs));
+ journalFileSize = ROUND8(tdsqlite3JournalSize(pVfs));
/* Set the output variable to NULL in case an error occurs. */
*ppPager = 0;
@@ -54731,9 +60476,9 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
if( flags & PAGER_MEMORY ){
memDb = 1;
if( zFilename && zFilename[0] ){
- zPathname = sqlite3DbStrDup(0, zFilename);
+ zPathname = tdsqlite3DbStrDup(0, zFilename);
if( zPathname==0 ) return SQLITE_NOMEM_BKPT;
- nPathname = sqlite3Strlen30(zPathname);
+ nPathname = tdsqlite3Strlen30(zPathname);
zFilename = 0;
}
}
@@ -54746,20 +60491,30 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
if( zFilename && zFilename[0] ){
const char *z;
nPathname = pVfs->mxPathname+1;
- zPathname = sqlite3DbMallocRaw(0, nPathname*2);
+ zPathname = tdsqlite3DbMallocRaw(0, nPathname*2);
if( zPathname==0 ){
return SQLITE_NOMEM_BKPT;
}
zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */
- rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname);
- nPathname = sqlite3Strlen30(zPathname);
- z = zUri = &zFilename[sqlite3Strlen30(zFilename)+1];
+ rc = tdsqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_OK_SYMLINK ){
+ if( vfsFlags & SQLITE_OPEN_NOFOLLOW ){
+ rc = SQLITE_CANTOPEN_SYMLINK;
+ }else{
+ rc = SQLITE_OK;
+ }
+ }
+ }
+ nPathname = tdsqlite3Strlen30(zPathname);
+ z = zUri = &zFilename[tdsqlite3Strlen30(zFilename)+1];
while( *z ){
- z += sqlite3Strlen30(z)+1;
- z += sqlite3Strlen30(z)+1;
+ z += strlen(z)+1;
+ z += strlen(z)+1;
+ nUri++;
}
- nUri = (int)(&z[1] - zUri);
- assert( nUri>=0 );
+ nUriByte = (int)(&z[1] - zUri);
+ assert( nUriByte>=1 );
if( rc==SQLITE_OK && nPathname+8>pVfs->mxPathname ){
/* This branch is taken when the journal path required by
** the database being opened will be more than pVfs->mxPathname
@@ -54770,7 +60525,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
rc = SQLITE_CANTOPEN_BKPT;
}
if( rc!=SQLITE_OK ){
- sqlite3DbFree(0, zPathname);
+ tdsqlite3DbFree(0, zPathname);
return rc;
}
}
@@ -54780,54 +60535,92 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
** file name. The layout in memory is as follows:
**
** Pager object (sizeof(Pager) bytes)
- ** PCache object (sqlite3PcacheSize() bytes)
+ ** PCache object (tdsqlite3PcacheSize() bytes)
** Database file handle (pVfs->szOsFile bytes)
** Sub-journal file handle (journalFileSize bytes)
** Main journal file handle (journalFileSize bytes)
+ ** \0\1\0 journal prefix (3 bytes)
+ ** Journal filename (nPathname+8+1 bytes)
+ ** \2\0 WAL prefix (2 bytes)
+ ** WAL filename (nPathname+4+1 bytes)
+ ** \3\0 database prefix (2 bytes)
** Database file name (nPathname+1 bytes)
- ** Journal file name (nPathname+8+1 bytes)
- */
- pPtr = (u8 *)sqlite3MallocZero(
- ROUND8(sizeof(*pPager)) + /* Pager structure */
- ROUND8(pcacheSize) + /* PCache object */
- ROUND8(pVfs->szOsFile) + /* The main db file */
- journalFileSize * 2 + /* The two journal files */
- nPathname + 1 + nUri + /* zFilename */
- nPathname + 8 + 2 /* zJournal */
+ ** URI query parameters (nUriByte bytes)
+ ** \0\0 terminator (2 bytes)
+ */
+ pPtr = (u8 *)tdsqlite3MallocZero(
+ ROUND8(sizeof(*pPager)) + /* Pager structure */
+ ROUND8(pcacheSize) + /* PCache object */
+ ROUND8(pVfs->szOsFile) + /* The main db file */
+ journalFileSize * 2 + /* The two journal files */
+ 3 + /* Journal prefix */
+ nPathname + 8 + 1 + /* Journal filename */
#ifndef SQLITE_OMIT_WAL
- + nPathname + 4 + 2 /* zWal */
+ 2 + /* WAL prefix */
+ nPathname + 4 + 1 + /* WAL filename */
#endif
+ 2 + /* Database prefix */
+ nPathname + 1 + /* database filename */
+ nUriByte + /* query parameters */
+ 2 /* Terminator */
);
assert( EIGHT_BYTE_ALIGNMENT(SQLITE_INT_TO_PTR(journalFileSize)) );
if( !pPtr ){
- sqlite3DbFree(0, zPathname);
+ tdsqlite3DbFree(0, zPathname);
return SQLITE_NOMEM_BKPT;
}
- pPager = (Pager*)(pPtr);
- pPager->pPCache = (PCache*)(pPtr += ROUND8(sizeof(*pPager)));
- pPager->fd = (sqlite3_file*)(pPtr += ROUND8(pcacheSize));
- pPager->sjfd = (sqlite3_file*)(pPtr += ROUND8(pVfs->szOsFile));
- pPager->jfd = (sqlite3_file*)(pPtr += journalFileSize);
- pPager->zFilename = (char*)(pPtr += journalFileSize);
+ pPager = (Pager*)pPtr; pPtr += ROUND8(sizeof(*pPager));
+ pPager->pPCache = (PCache*)pPtr; pPtr += ROUND8(pcacheSize);
+ pPager->fd = (tdsqlite3_file*)pPtr; pPtr += ROUND8(pVfs->szOsFile);
+ pPager->sjfd = (tdsqlite3_file*)pPtr; pPtr += journalFileSize;
+ pPager->jfd = (tdsqlite3_file*)pPtr; pPtr += journalFileSize;
assert( EIGHT_BYTE_ALIGNMENT(pPager->jfd) );
- /* Fill in the Pager.zFilename and Pager.zJournal buffers, if required. */
- if( zPathname ){
- assert( nPathname>0 );
- pPager->zJournal = (char*)(pPtr += nPathname + 1 + nUri);
- memcpy(pPager->zFilename, zPathname, nPathname);
- if( nUri ) memcpy(&pPager->zFilename[nPathname+1], zUri, nUri);
- memcpy(pPager->zJournal, zPathname, nPathname);
- memcpy(&pPager->zJournal[nPathname], "-journal\000", 8+2);
- sqlite3FileSuffix3(pPager->zFilename, pPager->zJournal);
+
+ /* Fill in Pager.zJournal */
+ pPtr[1] = '\001'; pPtr += 3;
+ if( nPathname>0 ){
+ pPager->zJournal = (char*)pPtr;
+ memcpy(pPtr, zPathname, nPathname); pPtr += nPathname;
+ memcpy(pPtr, "-journal",8); pPtr += 8 + 1;
+#ifdef SQLITE_ENABLE_8_3_NAMES
+ tdsqlite3FileSuffix3(zFilename,pPager->zJournal);
+ pPtr = (u8*)(pPager->zJournal + tdsqlite3Strlen30(pPager->zJournal)+1);
+#endif
+ }else{
+ pPager->zJournal = 0;
+ pPtr++;
+ }
+
#ifndef SQLITE_OMIT_WAL
- pPager->zWal = &pPager->zJournal[nPathname+8+1];
- memcpy(pPager->zWal, zPathname, nPathname);
- memcpy(&pPager->zWal[nPathname], "-wal\000", 4+1);
- sqlite3FileSuffix3(pPager->zFilename, pPager->zWal);
+ /* Fill in Pager.zWal */
+ pPtr[0] = '\002'; pPtr[1] = 0; pPtr += 2;
+ if( nPathname>0 ){
+ pPager->zWal = (char*)pPtr;
+ memcpy(pPtr, zPathname, nPathname); pPtr += nPathname;
+ memcpy(pPtr, "-wal", 4); pPtr += 4 + 1;
+#ifdef SQLITE_ENABLE_8_3_NAMES
+ tdsqlite3FileSuffix3(zFilename, pPager->zWal);
+ pPtr = (u8*)(pPager->zWal + tdsqlite3Strlen30(pPager->zWal)+1);
#endif
- sqlite3DbFree(0, zPathname);
+ }else{
+ pPager->zWal = 0;
+ pPtr++;
}
+#endif
+
+ /* Fill in the Pager.zFilename and pPager.zQueryParam fields */
+ pPtr[0] = '\003'; pPtr[1] = 0; pPtr += 2;
+ pPager->zFilename = (char*)pPtr;
+ if( nPathname>0 ){
+ memcpy(pPtr, zPathname, nPathname); pPtr += nPathname + 1;
+ if( zUri ){
+ memcpy(pPtr, zUri, nUriByte); /* pPtr += nUriByte; // not needed */
+ }
+ /* Double-zero terminator implied by the tdsqlite3MallocZero */
+ }
+
+ if( nPathname ) tdsqlite3DbFree(0, zPathname);
pPager->pVfs = pVfs;
pPager->vfsFlags = vfsFlags;
@@ -54835,20 +60628,23 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
*/
if( zFilename && zFilename[0] ){
int fout = 0; /* VFS flags returned by xOpen() */
- rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout);
+ rc = tdsqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout);
assert( !memDb );
- readOnly = (fout&SQLITE_OPEN_READONLY);
+#ifdef SQLITE_ENABLE_DESERIALIZE
+ memJM = (fout&SQLITE_OPEN_MEMORY)!=0;
+#endif
+ readOnly = (fout&SQLITE_OPEN_READONLY)!=0;
/* If the file was successfully opened for read/write access,
** choose a default page size in case we have to create the
** database file. The default page size is the maximum of:
**
** + SQLITE_DEFAULT_PAGE_SIZE,
- ** + The value returned by sqlite3OsSectorSize()
+ ** + The value returned by tdsqlite3OsSectorSize()
** + The largest page size that can be written atomically.
*/
if( rc==SQLITE_OK ){
- int iDc = sqlite3OsDeviceCharacteristics(pPager->fd);
+ int iDc = tdsqlite3OsDeviceCharacteristics(pPager->fd);
if( !readOnly ){
setSectorSize(pPager);
assert(SQLITE_DEFAULT_PAGE_SIZE<=SQLITE_MAX_DEFAULT_PAGE_SIZE);
@@ -54873,9 +60669,9 @@ SQLITE_PRIVATE int sqlite3PagerOpen(
}
#endif
}
- pPager->noLock = sqlite3_uri_boolean(zFilename, "nolock", 0);
+ pPager->noLock = tdsqlite3_uri_boolean(pPager->zFilename, "nolock", 0);
if( (iDc & SQLITE_IOCAP_IMMUTABLE)!=0
- || sqlite3_uri_boolean(zFilename, "immutable", 0) ){
+ || tdsqlite3_uri_boolean(pPager->zFilename, "immutable", 0) ){
vfsFlags |= SQLITE_OPEN_READONLY;
goto act_like_temp_file;
}
@@ -54904,24 +60700,24 @@ act_like_temp_file:
*/
if( rc==SQLITE_OK ){
assert( pPager->memDb==0 );
- rc = sqlite3PagerSetPagesize(pPager, &szPageDflt, -1);
+ rc = tdsqlite3PagerSetPagesize(pPager, &szPageDflt, -1);
testcase( rc!=SQLITE_OK );
}
/* Initialize the PCache object. */
if( rc==SQLITE_OK ){
- assert( nExtra<1000 );
nExtra = ROUND8(nExtra);
- rc = sqlite3PcacheOpen(szPageDflt, nExtra, !memDb,
+ assert( nExtra>=8 && nExtra<1000 );
+ rc = tdsqlite3PcacheOpen(szPageDflt, nExtra, !memDb,
!memDb?pagerStress:0, (void *)pPager, pPager->pPCache);
}
/* If an error occurred above, free the Pager structure and close the file.
*/
if( rc!=SQLITE_OK ){
- sqlite3OsClose(pPager->fd);
- sqlite3PageFree(pPager->pTmpSpace);
- sqlite3_free(pPager);
+ tdsqlite3OsClose(pPager->fd);
+ tdsqlite3PageFree(pPager->pTmpSpace);
+ tdsqlite3_free(pPager);
return rc;
}
@@ -54947,19 +60743,24 @@ act_like_temp_file:
pPager->memDb = (u8)memDb;
pPager->readOnly = (u8)readOnly;
assert( useJournal || pPager->tempFile );
- pPager->noSync = pPager->tempFile;
+ int level = SQLITE_DEFAULT_SYNCHRONOUS + 1;
+ pPager->noSync = pPager->tempFile || level==PAGER_SYNCHRONOUS_OFF;
if( pPager->noSync ){
assert( pPager->fullSync==0 );
assert( pPager->extraSync==0 );
assert( pPager->syncFlags==0 );
assert( pPager->walSyncFlags==0 );
- assert( pPager->ckptSyncFlags==0 );
}else{
- pPager->fullSync = 1;
- pPager->extraSync = 0;
+ pPager->fullSync = level>=PAGER_SYNCHRONOUS_FULL ?1:0;
+ pPager->extraSync = level==PAGER_SYNCHRONOUS_EXTRA ?1:0;
pPager->syncFlags = SQLITE_SYNC_NORMAL;
- pPager->walSyncFlags = SQLITE_SYNC_NORMAL | WAL_SYNC_TRANSACTIONS;
- pPager->ckptSyncFlags = SQLITE_SYNC_NORMAL;
+ pPager->walSyncFlags = (pPager->syncFlags<<2);
+ if( pPager->fullSync ){
+ pPager->walSyncFlags |= pPager->syncFlags;
+ }
+#if SQLITE_DEFAULT_CKPTFULLFSYNC
+ pPager->walSyncFlags |= (SQLITE_SYNC_FULL<<2);
+#endif
}
/* pPager->pFirst = 0; */
/* pPager->pFirstSynced = 0; */
@@ -54970,12 +60771,13 @@ act_like_temp_file:
setSectorSize(pPager);
if( !useJournal ){
pPager->journalMode = PAGER_JOURNALMODE_OFF;
- }else if( memDb ){
+ }else if( memDb || memJM ){
pPager->journalMode = PAGER_JOURNALMODE_MEMORY;
}
/* pPager->xBusyHandler = 0; */
/* pPager->pBusyHandlerArg = 0; */
pPager->xReiniter = xReinit;
+ setGetterMethod(pPager);
/* memset(pPager->aHash, 0, sizeof(pPager->aHash)); */
/* pPager->szMmap = SQLITE_DEFAULT_MMAP_SIZE // will be set by btree.c */
@@ -54984,30 +60786,6 @@ act_like_temp_file:
}
-/* Verify that the database file has not be deleted or renamed out from
-** under the pager. Return SQLITE_OK if the database is still were it ought
-** to be on disk. Return non-zero (SQLITE_READONLY_DBMOVED or some other error
-** code from sqlite3OsAccess()) if the database has gone missing.
-*/
-static int databaseIsUnmoved(Pager *pPager){
- int bHasMoved = 0;
- int rc;
-
- if( pPager->tempFile ) return SQLITE_OK;
- if( pPager->dbSize==0 ) return SQLITE_OK;
- assert( pPager->zFilename && pPager->zFilename[0] );
- rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_HAS_MOVED, &bHasMoved);
- if( rc==SQLITE_NOTFOUND ){
- /* If the HAS_MOVED file-control is unimplemented, assume that the file
- ** has not been moved. That is the historical behavior of SQLite: prior to
- ** version 3.8.3, it never checked */
- rc = SQLITE_OK;
- }else if( rc==SQLITE_OK && bHasMoved ){
- rc = SQLITE_READONLY_DBMOVED;
- }
- return rc;
-}
-
/*
** This function is called after transitioning from PAGER_UNLOCK to
@@ -55041,7 +60819,7 @@ static int databaseIsUnmoved(Pager *pPager){
** code is returned and the value of *pExists is undefined.
*/
static int hasHotJournal(Pager *pPager, int *pExists){
- sqlite3_vfs * const pVfs = pPager->pVfs;
+ tdsqlite3_vfs * const pVfs = pPager->pVfs;
int rc = SQLITE_OK; /* Return code */
int exists = 1; /* True if a journal file is present */
int jrnlOpen = !!isOpen(pPager->jfd);
@@ -55050,26 +60828,26 @@ static int hasHotJournal(Pager *pPager, int *pExists){
assert( isOpen(pPager->fd) );
assert( pPager->eState==PAGER_OPEN );
- assert( jrnlOpen==0 || ( sqlite3OsDeviceCharacteristics(pPager->jfd) &
+ assert( jrnlOpen==0 || ( tdsqlite3OsDeviceCharacteristics(pPager->jfd) &
SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
));
*pExists = 0;
if( !jrnlOpen ){
- rc = sqlite3OsAccess(pVfs, pPager->zJournal, SQLITE_ACCESS_EXISTS, &exists);
+ rc = tdsqlite3OsAccess(pVfs, pPager->zJournal, SQLITE_ACCESS_EXISTS, &exists);
}
if( rc==SQLITE_OK && exists ){
int locked = 0; /* True if some process holds a RESERVED lock */
/* Race condition here: Another process might have been holding the
- ** the RESERVED lock and have a journal open at the sqlite3OsAccess()
+ ** the RESERVED lock and have a journal open at the tdsqlite3OsAccess()
** call above, but then delete the journal and drop the lock before
- ** we get to the following sqlite3OsCheckReservedLock() call. If that
+ ** we get to the following tdsqlite3OsCheckReservedLock() call. If that
** is the case, this routine might think there is a hot journal when
** in fact there is none. This results in a false-positive which will
** be dealt with by the playback routine. Ticket #3883.
*/
- rc = sqlite3OsCheckReservedLock(pPager->fd, &locked);
+ rc = tdsqlite3OsCheckReservedLock(pPager->fd, &locked);
if( rc==SQLITE_OK && !locked ){
Pgno nPage; /* Number of pages in database file */
@@ -55085,12 +60863,12 @@ static int hasHotJournal(Pager *pPager, int *pExists){
** journal_mode=PERSIST.
*/
if( nPage==0 && !jrnlOpen ){
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
if( pagerLockDb(pPager, RESERVED_LOCK)==SQLITE_OK ){
- sqlite3OsDelete(pVfs, pPager->zJournal, 0);
+ tdsqlite3OsDelete(pVfs, pPager->zJournal, 0);
if( !pPager->exclusiveMode ) pagerUnlockDb(pPager, SHARED_LOCK);
}
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
}else{
/* The journal file exists and no other connection has a reserved
** or greater lock on the database file. Now check that there is
@@ -55100,16 +60878,16 @@ static int hasHotJournal(Pager *pPager, int *pExists){
*/
if( !jrnlOpen ){
int f = SQLITE_OPEN_READONLY|SQLITE_OPEN_MAIN_JOURNAL;
- rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &f);
+ rc = tdsqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &f);
}
if( rc==SQLITE_OK ){
u8 first = 0;
- rc = sqlite3OsRead(pPager->jfd, (void *)&first, 1, 0);
+ rc = tdsqlite3OsRead(pPager->jfd, (void *)&first, 1, 0);
if( rc==SQLITE_IOERR_SHORT_READ ){
rc = SQLITE_OK;
}
if( !jrnlOpen ){
- sqlite3OsClose(pPager->jfd);
+ tdsqlite3OsClose(pPager->jfd);
}
*pExists = (first!=0);
}else if( rc==SQLITE_CANTOPEN ){
@@ -55135,7 +60913,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){
/*
** This function is called to obtain a shared lock on the database file.
-** It is illegal to call sqlite3PagerGet() until after this function
+** It is illegal to call tdsqlite3PagerGet() until after this function
** has been successfully called. If a shared-lock is already held when
** this function is called, it is a no-op.
**
@@ -55160,14 +60938,14 @@ static int hasHotJournal(Pager *pPager, int *pExists){
** occurs while locking the database, checking for a hot-journal file or
** rolling back a journal file, the IO error code is returned.
*/
-SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerSharedLock(Pager *pPager){
int rc = SQLITE_OK; /* Return code */
/* This routine is only called from b-tree and only when there are no
** outstanding pages. This implies that the pager state should either
** be OPEN or READER. READER is only possible if the pager is or was in
** exclusive access mode. */
- assert( sqlite3PcacheRefCount(pPager->pPCache)==0 );
+ assert( tdsqlite3PcacheRefCount(pPager->pPCache)==0 );
assert( assert_pager_state(pPager) );
assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER );
assert( pPager->errCode==SQLITE_OK );
@@ -55233,19 +61011,19 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){
** function was called and the journal file does not exist.
*/
if( !isOpen(pPager->jfd) ){
- sqlite3_vfs * const pVfs = pPager->pVfs;
+ tdsqlite3_vfs * const pVfs = pPager->pVfs;
int bExists; /* True if journal file exists */
- rc = sqlite3OsAccess(
+ rc = tdsqlite3OsAccess(
pVfs, pPager->zJournal, SQLITE_ACCESS_EXISTS, &bExists);
if( rc==SQLITE_OK && bExists ){
int fout = 0;
int f = SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_JOURNAL;
assert( !pPager->tempFile );
- rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &fout);
+ rc = tdsqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &fout);
assert( rc!=SQLITE_OK || isOpen(pPager->jfd) );
if( rc==SQLITE_OK && fout&SQLITE_OPEN_READONLY ){
rc = SQLITE_CANTOPEN_BKPT;
- sqlite3OsClose(pPager->jfd);
+ tdsqlite3OsClose(pPager->jfd);
}
}
}
@@ -55301,7 +61079,7 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){
** see if the database has been modified. If the database has changed,
** flush the cache. The hasHeldSharedLock flag prevents this from
** occurring on the very first access to a file, in order to save a
- ** single unnecessary sqlite3OsRead() call at the start-up.
+ ** single unnecessary tdsqlite3OsRead() call at the start-up.
**
** Database changes are detected by looking at 15 bytes beginning
** at offset 24 into the file. The first 4 of these 16 bytes are
@@ -55313,19 +61091,14 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){
** detected. The chance of an undetected change is so small that
** it can be neglected.
*/
- Pgno nPage = 0;
char dbFileVers[sizeof(pPager->dbFileVers)];
- rc = pagerPagecount(pPager, &nPage);
- if( rc ) goto failed;
-
- if( nPage>0 ){
- IOTRACE(("CKVERS %p %d\n", pPager, sizeof(dbFileVers)));
- rc = sqlite3OsRead(pPager->fd, &dbFileVers, sizeof(dbFileVers), 24);
- if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){
+ IOTRACE(("CKVERS %p %d\n", pPager, sizeof(dbFileVers)));
+ rc = tdsqlite3OsRead(pPager->fd, &dbFileVers, sizeof(dbFileVers), 24);
+ if( rc!=SQLITE_OK ){
+ if( rc!=SQLITE_IOERR_SHORT_READ ){
goto failed;
}
- }else{
memset(dbFileVers, 0, sizeof(dbFileVers));
}
@@ -55339,7 +61112,7 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){
** to be the right size but is not actually valid. Avoid this
** possibility by unmapping the db here. */
if( USEFETCH(pPager) ){
- sqlite3OsUnfetch(pPager->fd, 0, 0);
+ tdsqlite3OsUnfetch(pPager->fd, 0, 0);
}
}
}
@@ -55383,16 +61156,24 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){
** nothing to rollback, so this routine is a no-op.
*/
static void pagerUnlockIfUnused(Pager *pPager){
- if( pPager->nMmapOut==0 && (sqlite3PcacheRefCount(pPager->pPCache)==0) ){
+ if( tdsqlite3PcacheRefCount(pPager->pPCache)==0 ){
+ assert( pPager->nMmapOut==0 ); /* because page1 is never memory mapped */
pagerUnlockAndRollback(pPager);
}
}
/*
-** Acquire a reference to page number pgno in pager pPager (a page
-** reference has type DbPage*). If the requested reference is
+** The page getter methods each try to acquire a reference to a
+** page with page number pgno. If the requested reference is
** successfully obtained, it is copied to *ppPage and SQLITE_OK returned.
**
+** There are different implementations of the getter method depending
+** on the current state of the pager.
+**
+** getPageNormal() -- The normal getter
+** getPageError() -- Used if the pager is in an error state
+** getPageMmap() -- Used if memory-mapped I/O is enabled
+**
** If the requested page is already in the cache, it is returned.
** Otherwise, a new page object is allocated and populated with data
** read from the database file. In some cases, the pcache module may
@@ -55404,14 +61185,14 @@ static void pagerUnlockIfUnused(Pager *pPager){
** already in the cache when this function is called, then the extra
** data is left as it was when the page object was last used.
**
-** If the database image is smaller than the requested page or if a
-** non-zero value is passed as the noContent parameter and the
+** If the database image is smaller than the requested page or if
+** the flags parameter contains the PAGER_GET_NOCONTENT bit and the
** requested page is not already stored in the cache, then no
** actual disk read occurs. In this case the memory image of the
** page is initialized to all zeros.
**
-** If noContent is true, it means that we do not care about the contents
-** of the page. This occurs in two scenarios:
+** If PAGER_GET_NOCONTENT is true, it means that we do not care about
+** the contents of the page. This occurs in two scenarios:
**
** a) When reading a free-list leaf page from the database, and
**
@@ -55419,18 +61200,18 @@ static void pagerUnlockIfUnused(Pager *pPager){
** a new page into the cache to be filled with the data read
** from the savepoint journal.
**
-** If noContent is true, then the data returned is zeroed instead of
-** being read from the database. Additionally, the bits corresponding
+** If PAGER_GET_NOCONTENT is true, then the data returned is zeroed instead
+** of being read from the database. Additionally, the bits corresponding
** to pgno in Pager.pInJournal (bitvec of pages already written to the
** journal file) and the PagerSavepoint.pInSavepoint bitvecs of any open
** savepoints are set. This means if the page is made writable at any
-** point in the future, using a call to sqlite3PagerWrite(), its contents
+** point in the future, using a call to tdsqlite3PagerWrite(), its contents
** will not be journaled. This saves IO.
**
** The acquisition might fail for several reasons. In all cases,
** an appropriate error code is returned and *ppPage is set to NULL.
**
-** See also sqlite3PagerLookup(). Both this routine and Lookup() attempt
+** See also tdsqlite3PagerLookup(). Both this routine and Lookup() attempt
** to find a page in the in-memory cache first. If the page is not already
** in memory, this routine goes to disk to read it in whereas Lookup()
** just returns 0. This routine acquires a read-lock the first time it
@@ -55438,106 +61219,39 @@ static void pagerUnlockIfUnused(Pager *pPager){
** Since Lookup() never goes to disk, it never has to deal with locks
** or journal files.
*/
-SQLITE_PRIVATE int sqlite3PagerGet(
+static int getPageNormal(
Pager *pPager, /* The pager open on the database file */
Pgno pgno, /* Page number to fetch */
DbPage **ppPage, /* Write a pointer to the page here */
int flags /* PAGER_GET_XXX flags */
){
int rc = SQLITE_OK;
- PgHdr *pPg = 0;
- u32 iFrame = 0; /* Frame to read from WAL file */
- const int noContent = (flags & PAGER_GET_NOCONTENT);
-
- /* It is acceptable to use a read-only (mmap) page for any page except
- ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY
- ** flag was specified by the caller. And so long as the db is not a
- ** temporary or in-memory database. */
- const int bMmapOk = (pgno>1 && USEFETCH(pPager)
- && (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY))
-#ifdef SQLITE_HAS_CODEC
- && pPager->xCodec==0
-#endif
- );
+ PgHdr *pPg;
+ u8 noContent; /* True if PAGER_GET_NOCONTENT is set */
+ tdsqlite3_pcache_page *pBase;
- /* Optimization note: Adding the "pgno<=1" term before "pgno==0" here
- ** allows the compiler optimizer to reuse the results of the "pgno>1"
- ** test in the previous statement, and avoid testing pgno==0 in the
- ** common case where pgno is large. */
- if( pgno<=1 && pgno==0 ){
- return SQLITE_CORRUPT_BKPT;
- }
+ assert( pPager->errCode==SQLITE_OK );
assert( pPager->eState>=PAGER_READER );
assert( assert_pager_state(pPager) );
- assert( noContent==0 || bMmapOk==0 );
-
assert( pPager->hasHeldSharedLock==1 );
- /* If the pager is in the error state, return an error immediately.
- ** Otherwise, request the page from the PCache layer. */
- if( pPager->errCode!=SQLITE_OK ){
- rc = pPager->errCode;
- }else{
- if( bMmapOk && pagerUseWal(pPager) ){
- rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame);
- if( rc!=SQLITE_OK ) goto pager_acquire_err;
- }
-
- if( bMmapOk && iFrame==0 ){
- void *pData = 0;
-
- rc = sqlite3OsFetch(pPager->fd,
- (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData
- );
-
- if( rc==SQLITE_OK && pData ){
- if( pPager->eState>PAGER_READER || pPager->tempFile ){
- pPg = sqlite3PagerLookup(pPager, pgno);
- }
- if( pPg==0 ){
- rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg);
- }else{
- sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData);
- }
- if( pPg ){
- assert( rc==SQLITE_OK );
- *ppPage = pPg;
- return SQLITE_OK;
- }
- }
- if( rc!=SQLITE_OK ){
- goto pager_acquire_err;
- }
- }
-
- {
- sqlite3_pcache_page *pBase;
- pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3);
- if( pBase==0 ){
- rc = sqlite3PcacheFetchStress(pPager->pPCache, pgno, &pBase);
- if( rc!=SQLITE_OK ) goto pager_acquire_err;
- if( pBase==0 ){
- pPg = *ppPage = 0;
- rc = SQLITE_NOMEM_BKPT;
- goto pager_acquire_err;
- }
- }
- pPg = *ppPage = sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pBase);
- assert( pPg!=0 );
- }
- }
-
- if( rc!=SQLITE_OK ){
- /* Either the call to sqlite3PcacheFetch() returned an error or the
- ** pager was already in the error-state when this function was called.
- ** Set pPg to 0 and jump to the exception handler. */
+ if( pgno==0 ) return SQLITE_CORRUPT_BKPT;
+ pBase = tdsqlite3PcacheFetch(pPager->pPCache, pgno, 3);
+ if( pBase==0 ){
pPg = 0;
- goto pager_acquire_err;
+ rc = tdsqlite3PcacheFetchStress(pPager->pPCache, pgno, &pBase);
+ if( rc!=SQLITE_OK ) goto pager_acquire_err;
+ if( pBase==0 ){
+ rc = SQLITE_NOMEM_BKPT;
+ goto pager_acquire_err;
+ }
}
+ pPg = *ppPage = tdsqlite3PcacheFetchFinish(pPager->pPCache, pgno, pBase);
assert( pPg==(*ppPage) );
assert( pPg->pgno==pgno );
assert( pPg->pPager==pPager || pPg->pPager==0 );
+ noContent = (flags & PAGER_GET_NOCONTENT)!=0;
if( pPg->pPager && !noContent ){
/* In this case the pcache already contains an initialized copy of
** the page. Return without further ado. */
@@ -55547,17 +61261,18 @@ SQLITE_PRIVATE int sqlite3PagerGet(
}else{
/* The pager cache has created a new page. Its content needs to
- ** be initialized. */
-
- pPg->pPager = pPager;
-
- /* The maximum page number is 2^31. Return SQLITE_CORRUPT if a page
- ** number greater than this, or the unused locking-page, is requested. */
+ ** be initialized. But first some error checks:
+ **
+ ** (1) The maximum page number is 2^31
+ ** (2) Never try to fetch the locking page
+ */
if( pgno>PAGER_MAX_PGNO || pgno==PAGER_MJ_PGNO(pPager) ){
rc = SQLITE_CORRUPT_BKPT;
goto pager_acquire_err;
}
+ pPg->pPager = pPager;
+
assert( !isOpen(pPager->fd) || !MEMDB );
if( !isOpen(pPager->fd) || pPager->dbSize<pgno || noContent ){
if( pgno>pPager->mxPgno ){
@@ -55571,88 +61286,196 @@ SQLITE_PRIVATE int sqlite3PagerGet(
** to test the case where a malloc error occurs while trying to set
** a bit in a bit vector.
*/
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
if( pgno<=pPager->dbOrigSize ){
- TESTONLY( rc = ) sqlite3BitvecSet(pPager->pInJournal, pgno);
+ TESTONLY( rc = ) tdsqlite3BitvecSet(pPager->pInJournal, pgno);
testcase( rc==SQLITE_NOMEM );
}
TESTONLY( rc = ) addToSavepointBitvecs(pPager, pgno);
testcase( rc==SQLITE_NOMEM );
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
}
memset(pPg->pData, 0, pPager->pageSize);
IOTRACE(("ZERO %p %d\n", pPager, pgno));
}else{
- if( pagerUseWal(pPager) && bMmapOk==0 ){
- rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame);
- if( rc!=SQLITE_OK ) goto pager_acquire_err;
- }
assert( pPg->pPager==pPager );
pPager->aStat[PAGER_STAT_MISS]++;
- rc = readDbPage(pPg, iFrame);
+ rc = readDbPage(pPg);
if( rc!=SQLITE_OK ){
goto pager_acquire_err;
}
}
pager_set_pagehash(pPg);
}
-
return SQLITE_OK;
pager_acquire_err:
assert( rc!=SQLITE_OK );
if( pPg ){
- sqlite3PcacheDrop(pPg);
+ tdsqlite3PcacheDrop(pPg);
}
pagerUnlockIfUnused(pPager);
-
*ppPage = 0;
return rc;
}
+#if SQLITE_MAX_MMAP_SIZE>0
+/* The page getter for when memory-mapped I/O is enabled */
+static int getPageMMap(
+ Pager *pPager, /* The pager open on the database file */
+ Pgno pgno, /* Page number to fetch */
+ DbPage **ppPage, /* Write a pointer to the page here */
+ int flags /* PAGER_GET_XXX flags */
+){
+ int rc = SQLITE_OK;
+ PgHdr *pPg = 0;
+ u32 iFrame = 0; /* Frame to read from WAL file */
+
+ /* It is acceptable to use a read-only (mmap) page for any page except
+ ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY
+ ** flag was specified by the caller. And so long as the db is not a
+ ** temporary or in-memory database. */
+ const int bMmapOk = (pgno>1
+ && (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY))
+ );
+
+ assert( USEFETCH(pPager) );
+#ifdef SQLITE_HAS_CODEC
+ assert( pPager->xCodec==0 );
+#endif
+
+ /* Optimization note: Adding the "pgno<=1" term before "pgno==0" here
+ ** allows the compiler optimizer to reuse the results of the "pgno>1"
+ ** test in the previous statement, and avoid testing pgno==0 in the
+ ** common case where pgno is large. */
+ if( pgno<=1 && pgno==0 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ assert( pPager->eState>=PAGER_READER );
+ assert( assert_pager_state(pPager) );
+ assert( pPager->hasHeldSharedLock==1 );
+ assert( pPager->errCode==SQLITE_OK );
+
+ if( bMmapOk && pagerUseWal(pPager) ){
+ rc = tdsqlite3WalFindFrame(pPager->pWal, pgno, &iFrame);
+ if( rc!=SQLITE_OK ){
+ *ppPage = 0;
+ return rc;
+ }
+ }
+ if( bMmapOk && iFrame==0 ){
+ void *pData = 0;
+ rc = tdsqlite3OsFetch(pPager->fd,
+ (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData
+ );
+ if( rc==SQLITE_OK && pData ){
+ if( pPager->eState>PAGER_READER || pPager->tempFile ){
+ pPg = tdsqlite3PagerLookup(pPager, pgno);
+ }
+ if( pPg==0 ){
+ rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg);
+ }else{
+ tdsqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData);
+ }
+ if( pPg ){
+ assert( rc==SQLITE_OK );
+ *ppPage = pPg;
+ return SQLITE_OK;
+ }
+ }
+ if( rc!=SQLITE_OK ){
+ *ppPage = 0;
+ return rc;
+ }
+ }
+ return getPageNormal(pPager, pgno, ppPage, flags);
+}
+#endif /* SQLITE_MAX_MMAP_SIZE>0 */
+
+/* The page getter method for when the pager is an error state */
+static int getPageError(
+ Pager *pPager, /* The pager open on the database file */
+ Pgno pgno, /* Page number to fetch */
+ DbPage **ppPage, /* Write a pointer to the page here */
+ int flags /* PAGER_GET_XXX flags */
+){
+ UNUSED_PARAMETER(pgno);
+ UNUSED_PARAMETER(flags);
+ assert( pPager->errCode!=SQLITE_OK );
+ *ppPage = 0;
+ return pPager->errCode;
+}
+
+
+/* Dispatch all page fetch requests to the appropriate getter method.
+*/
+SQLITE_PRIVATE int tdsqlite3PagerGet(
+ Pager *pPager, /* The pager open on the database file */
+ Pgno pgno, /* Page number to fetch */
+ DbPage **ppPage, /* Write a pointer to the page here */
+ int flags /* PAGER_GET_XXX flags */
+){
+ return pPager->xGet(pPager, pgno, ppPage, flags);
+}
+
/*
** Acquire a page if it is already in the in-memory cache. Do
** not read the page from disk. Return a pointer to the page,
** or 0 if the page is not in cache.
**
-** See also sqlite3PagerGet(). The difference between this routine
-** and sqlite3PagerGet() is that _get() will go to the disk and read
+** See also tdsqlite3PagerGet(). The difference between this routine
+** and tdsqlite3PagerGet() is that _get() will go to the disk and read
** in the page if the page is not already in cache. This routine
** returns NULL if the page is not in cache or if a disk I/O error
** has ever happened.
*/
-SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){
- sqlite3_pcache_page *pPage;
+SQLITE_PRIVATE DbPage *tdsqlite3PagerLookup(Pager *pPager, Pgno pgno){
+ tdsqlite3_pcache_page *pPage;
assert( pPager!=0 );
assert( pgno!=0 );
assert( pPager->pPCache!=0 );
- pPage = sqlite3PcacheFetch(pPager->pPCache, pgno, 0);
+ pPage = tdsqlite3PcacheFetch(pPager->pPCache, pgno, 0);
assert( pPage==0 || pPager->hasHeldSharedLock );
if( pPage==0 ) return 0;
- return sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pPage);
+ return tdsqlite3PcacheFetchFinish(pPager->pPCache, pgno, pPage);
}
/*
** Release a page reference.
**
-** If the number of references to the page drop to zero, then the
-** page is added to the LRU list. When all references to all pages
-** are released, a rollback occurs and the lock on the database is
-** removed.
+** The tdsqlite3PagerUnref() and tdsqlite3PagerUnrefNotNull() may only be
+** used if we know that the page being released is not the last page.
+** The btree layer always holds page1 open until the end, so these first
+** to routines can be used to release any page other than BtShared.pPage1.
+**
+** Use tdsqlite3PagerUnrefPageOne() to release page1. This latter routine
+** checks the total number of outstanding pages and if the number of
+** pages reaches zero it drops the database lock.
*/
-SQLITE_PRIVATE void sqlite3PagerUnrefNotNull(DbPage *pPg){
- Pager *pPager;
+SQLITE_PRIVATE void tdsqlite3PagerUnrefNotNull(DbPage *pPg){
+ TESTONLY( Pager *pPager = pPg->pPager; )
assert( pPg!=0 );
- pPager = pPg->pPager;
if( pPg->flags & PGHDR_MMAP ){
+ assert( pPg->pgno!=1 ); /* Page1 is never memory mapped */
pagerReleaseMapPage(pPg);
}else{
- sqlite3PcacheRelease(pPg);
+ tdsqlite3PcacheRelease(pPg);
}
- pagerUnlockIfUnused(pPager);
+ /* Do not use this routine to release the last reference to page1 */
+ assert( tdsqlite3PcacheRefCount(pPager->pPCache)>0 );
}
-SQLITE_PRIVATE void sqlite3PagerUnref(DbPage *pPg){
- if( pPg ) sqlite3PagerUnrefNotNull(pPg);
+SQLITE_PRIVATE void tdsqlite3PagerUnref(DbPage *pPg){
+ if( pPg ) tdsqlite3PagerUnrefNotNull(pPg);
+}
+SQLITE_PRIVATE void tdsqlite3PagerUnrefPageOne(DbPage *pPg){
+ Pager *pPager;
+ assert( pPg!=0 );
+ assert( pPg->pgno==1 );
+ assert( (pPg->flags & PGHDR_MMAP)==0 ); /* Page1 is never memory mapped */
+ pPager = pPg->pPager;
+ tdsqlite3PagerResetLockTimeout(pPager);
+ tdsqlite3PcacheRelease(pPg);
+ pagerUnlockIfUnused(pPager);
}
/*
@@ -55679,7 +61502,7 @@ SQLITE_PRIVATE void sqlite3PagerUnref(DbPage *pPg){
*/
static int pager_open_journal(Pager *pPager){
int rc = SQLITE_OK; /* Return code */
- sqlite3_vfs * const pVfs = pPager->pVfs; /* Local cache of vfs pointer */
+ tdsqlite3_vfs * const pVfs = pPager->pVfs; /* Local cache of vfs pointer */
assert( pPager->eState==PAGER_WRITER_LOCKED );
assert( assert_pager_state(pPager) );
@@ -55691,7 +61514,7 @@ static int pager_open_journal(Pager *pPager){
if( NEVER(pPager->errCode) ) return pPager->errCode;
if( !pagerUseWal(pPager) && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){
- pPager->pInJournal = sqlite3BitvecCreate(pPager->dbSize);
+ pPager->pInJournal = tdsqlite3BitvecCreate(pPager->dbSize);
if( pPager->pInJournal==0 ){
return SQLITE_NOMEM_BKPT;
}
@@ -55699,14 +61522,14 @@ static int pager_open_journal(Pager *pPager){
/* Open the journal file if it is not already open. */
if( !isOpen(pPager->jfd) ){
if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){
- sqlite3MemJournalOpen(pPager->jfd);
+ tdsqlite3MemJournalOpen(pPager->jfd);
}else{
int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
int nSpill;
if( pPager->tempFile ){
flags |= (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL);
- nSpill = sqlite3Config.nStmtSpill;
+ nSpill = tdsqlite3Config.nStmtSpill;
}else{
flags |= SQLITE_OPEN_MAIN_JOURNAL;
nSpill = jrnlBufferSize(pPager);
@@ -55716,7 +61539,7 @@ static int pager_open_journal(Pager *pPager){
** it was originally opened. */
rc = databaseIsUnmoved(pPager);
if( rc==SQLITE_OK ){
- rc = sqlite3JournalOpen (
+ rc = tdsqlite3JournalOpen (
pVfs, pPager->zJournal, pPager->jfd, flags, nSpill
);
}
@@ -55739,7 +61562,7 @@ static int pager_open_journal(Pager *pPager){
}
if( rc!=SQLITE_OK ){
- sqlite3BitvecDestroy(pPager->pInJournal);
+ tdsqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
}else{
assert( pPager->eState==PAGER_WRITER_LOCKED );
@@ -55766,7 +61589,7 @@ static int pager_open_journal(Pager *pPager){
** sub-journal is implemented in-memory if pPager is an in-memory database,
** or using a temporary file otherwise.
*/
-SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
+SQLITE_PRIVATE int tdsqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){
int rc = SQLITE_OK;
if( pPager->errCode ) return pPager->errCode;
@@ -55780,12 +61603,12 @@ SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory
/* If the pager is configured to use locking_mode=exclusive, and an
** exclusive lock on the database is not already held, obtain it now.
*/
- if( pPager->exclusiveMode && sqlite3WalExclusiveMode(pPager->pWal, -1) ){
+ if( pPager->exclusiveMode && tdsqlite3WalExclusiveMode(pPager->pWal, -1) ){
rc = pagerLockDb(pPager, EXCLUSIVE_LOCK);
if( rc!=SQLITE_OK ){
return rc;
}
- (void)sqlite3WalExclusiveMode(pPager->pWal, 1);
+ (void)tdsqlite3WalExclusiveMode(pPager->pWal, 1);
}
/* Grab the write lock on the log file. If successful, upgrade to
@@ -55793,7 +61616,7 @@ SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory
** The busy-handler is not invoked if another connection already
** holds the write-lock. If possible, the upper layer will call it.
*/
- rc = sqlite3WalBeginWriteTransaction(pPager->pWal);
+ rc = tdsqlite3WalBeginWriteTransaction(pPager->pWal);
}else{
/* Obtain a RESERVED lock on the database file. If the exFlag parameter
** is true, then immediately upgrade this to an EXCLUSIVE lock. The
@@ -55862,14 +61685,14 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){
rc = write32bits(pPager->jfd, iOff, pPg->pgno);
if( rc!=SQLITE_OK ) return rc;
- rc = sqlite3OsWrite(pPager->jfd, pData2, pPager->pageSize, iOff+4);
+ rc = tdsqlite3OsWrite(pPager->jfd, pData2, pPager->pageSize, iOff+4);
if( rc!=SQLITE_OK ) return rc;
rc = write32bits(pPager->jfd, iOff+pPager->pageSize+4, cksum);
if( rc!=SQLITE_OK ) return rc;
IOTRACE(("JOUT %p %d %lld %d\n", pPager, pPg->pgno,
pPager->journalOff, pPager->pageSize));
- PAGER_INCR(sqlite3_pager_writej_count);
+ PAGER_INCR(tdsqlite3_pager_writej_count);
PAGERTRACE(("JOURNAL %d page %d needSync=%d hash(%08x)\n",
PAGERID(pPager), pPg->pgno,
((pPg->flags&PGHDR_NEED_SYNC)?1:0), pager_pagehash(pPg)));
@@ -55877,7 +61700,7 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){
pPager->journalOff += 8 + pPager->pageSize;
pPager->nRec++;
assert( pPager->pInJournal!=0 );
- rc = sqlite3BitvecSet(pPager->pInJournal, pPg->pgno);
+ rc = tdsqlite3BitvecSet(pPager->pInJournal, pPg->pgno);
testcase( rc==SQLITE_NOMEM );
assert( rc==SQLITE_OK || rc==SQLITE_NOMEM );
rc |= addToSavepointBitvecs(pPager, pPg->pgno);
@@ -55913,8 +61736,8 @@ static int pager_write(PgHdr *pPg){
** obtained the necessary locks to begin the write-transaction, but the
** rollback journal might not yet be open. Open it now if this is the case.
**
- ** This is done before calling sqlite3PcacheMakeDirty() on the page.
- ** Otherwise, if it were done after calling sqlite3PcacheMakeDirty(), then
+ ** This is done before calling tdsqlite3PcacheMakeDirty() on the page.
+ ** Otherwise, if it were done after calling tdsqlite3PcacheMakeDirty(), then
** an error might occur and the pager would end up in WRITER_LOCKED state
** with pages marked as dirty in the cache.
*/
@@ -55926,7 +61749,7 @@ static int pager_write(PgHdr *pPg){
assert( assert_pager_state(pPager) );
/* Mark the page that is about to be modified as dirty. */
- sqlite3PcacheMakeDirty(pPg);
+ tdsqlite3PcacheMakeDirty(pPg);
/* If a rollback journal is in use, them make sure the page that is about
** to change is in the rollback journal, or if the page is a new page off
@@ -55934,7 +61757,7 @@ static int pager_write(PgHdr *pPg){
*/
assert( (pPager->pInJournal!=0) == isOpen(pPager->jfd) );
if( pPager->pInJournal!=0
- && sqlite3BitvecTestNotNull(pPager->pInJournal, pPg->pgno)==0
+ && tdsqlite3BitvecTestNotNull(pPager->pInJournal, pPg->pgno)==0
){
assert( pagerUseWal(pPager)==0 );
if( pPg->pgno<=pPager->dbOrigSize ){
@@ -55974,7 +61797,7 @@ static int pager_write(PgHdr *pPg){
}
/*
-** This is a variant of sqlite3PagerWrite() that runs when the sector size
+** This is a variant of tdsqlite3PagerWrite() that runs when the sector size
** is larger than the page size. SQLite makes the (reasonable) assumption that
** all bytes of a sector are written together by hardware. Hence, all bytes of
** a sector need to be journalled in case of a power loss in the middle of
@@ -56023,22 +61846,22 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){
for(ii=0; ii<nPage && rc==SQLITE_OK; ii++){
Pgno pg = pg1+ii;
PgHdr *pPage;
- if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){
+ if( pg==pPg->pgno || !tdsqlite3BitvecTest(pPager->pInJournal, pg) ){
if( pg!=PAGER_MJ_PGNO(pPager) ){
- rc = sqlite3PagerGet(pPager, pg, &pPage, 0);
+ rc = tdsqlite3PagerGet(pPager, pg, &pPage, 0);
if( rc==SQLITE_OK ){
rc = pager_write(pPage);
if( pPage->flags&PGHDR_NEED_SYNC ){
needSync = 1;
}
- sqlite3PagerUnrefNotNull(pPage);
+ tdsqlite3PagerUnrefNotNull(pPage);
}
}
- }else if( (pPage = sqlite3PagerLookup(pPager, pg))!=0 ){
+ }else if( (pPage = tdsqlite3PagerLookup(pPager, pg))!=0 ){
if( pPage->flags&PGHDR_NEED_SYNC ){
needSync = 1;
}
- sqlite3PagerUnrefNotNull(pPage);
+ tdsqlite3PagerUnrefNotNull(pPage);
}
}
@@ -56051,10 +61874,10 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){
if( rc==SQLITE_OK && needSync ){
assert( !MEMDB );
for(ii=0; ii<nPage; ii++){
- PgHdr *pPage = sqlite3PagerLookup(pPager, pg1+ii);
+ PgHdr *pPage = tdsqlite3PagerLookup(pPager, pg1+ii);
if( pPage ){
pPage->flags |= PGHDR_NEED_SYNC;
- sqlite3PagerUnrefNotNull(pPage);
+ tdsqlite3PagerUnrefNotNull(pPage);
}
}
}
@@ -56078,16 +61901,16 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){
** If an error occurs, SQLITE_NOMEM or an IO error code is returned
** as appropriate. Otherwise, SQLITE_OK.
*/
-SQLITE_PRIVATE int sqlite3PagerWrite(PgHdr *pPg){
+SQLITE_PRIVATE int tdsqlite3PagerWrite(PgHdr *pPg){
Pager *pPager = pPg->pPager;
assert( (pPg->flags & PGHDR_MMAP)==0 );
assert( pPager->eState>=PAGER_WRITER_LOCKED );
assert( assert_pager_state(pPager) );
- if( pPager->errCode ){
- return pPager->errCode;
- }else if( (pPg->flags & PGHDR_WRITEABLE)!=0 && pPager->dbSize>=pPg->pgno ){
+ if( (pPg->flags & PGHDR_WRITEABLE)!=0 && pPager->dbSize>=pPg->pgno ){
if( pPager->nSavepoint ) return subjournalPageIfRequired(pPg);
return SQLITE_OK;
+ }else if( pPager->errCode ){
+ return pPager->errCode;
}else if( pPager->sectorSize > (u32)pPager->pageSize ){
assert( pPager->tempFile==0 );
return pagerWriteLargeSector(pPg);
@@ -56098,11 +61921,11 @@ SQLITE_PRIVATE int sqlite3PagerWrite(PgHdr *pPg){
/*
** Return TRUE if the page given in the argument was previously passed
-** to sqlite3PagerWrite(). In other words, return TRUE if it is ok
+** to tdsqlite3PagerWrite(). In other words, return TRUE if it is ok
** to change the content of the page.
*/
#ifndef NDEBUG
-SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage *pPg){
+SQLITE_PRIVATE int tdsqlite3PagerIswriteable(DbPage *pPg){
return pPg->flags & PGHDR_WRITEABLE;
}
#endif
@@ -56127,7 +61950,7 @@ SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage *pPg){
** to be written out to disk so that it may be read back in if the
** current transaction is rolled back.
*/
-SQLITE_PRIVATE void sqlite3PagerDontWrite(PgHdr *pPg){
+SQLITE_PRIVATE void tdsqlite3PagerDontWrite(PgHdr *pPg){
Pager *pPager = pPg->pPager;
if( !pPager->tempFile && (pPg->flags&PGHDR_DIRTY) && pPager->nSavepoint==0 ){
PAGERTRACE(("DONT_WRITE page %d of %d\n", pPg->pgno, PAGERID(pPager)));
@@ -56151,7 +61974,7 @@ SQLITE_PRIVATE void sqlite3PagerDontWrite(PgHdr *pPg){
** unconditional update of the change counters.
**
** If the isDirectMode flag is zero, then this is done by calling
-** sqlite3PagerWrite() on page 1, then modifying the contents of the
+** tdsqlite3PagerWrite() on page 1, then modifying the contents of the
** page data. In this case the file will be updated when the current
** transaction is committed.
**
@@ -56159,7 +61982,7 @@ SQLITE_PRIVATE void sqlite3PagerDontWrite(PgHdr *pPg){
** with the SQLITE_ENABLE_ATOMIC_WRITE macro defined. In this case,
** if isDirect is non-zero, then the database file is updated directly
** by writing an updated version of page 1 using a call to the
-** sqlite3OsWrite() function.
+** tdsqlite3OsWrite() function.
*/
static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
int rc = SQLITE_OK;
@@ -56193,7 +62016,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
assert( !pPager->tempFile && isOpen(pPager->fd) );
/* Open page 1 of the file for writing. */
- rc = sqlite3PagerGet(pPager, 1, &pPgHdr, 0);
+ rc = tdsqlite3PagerGet(pPager, 1, &pPgHdr, 0);
assert( pPgHdr==0 || rc==SQLITE_OK );
/* If page one was fetched successfully, and this function is not
@@ -56202,7 +62025,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
** above is always successful - hence the ALWAYS on rc==SQLITE_OK.
*/
if( !DIRECT_MODE && ALWAYS(rc==SQLITE_OK) ){
- rc = sqlite3PagerWrite(pPgHdr);
+ rc = tdsqlite3PagerWrite(pPgHdr);
}
if( rc==SQLITE_OK ){
@@ -56215,7 +62038,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
assert( pPager->dbFileSize>0 );
CODEC2(pPager, pPgHdr->pData, 1, 6, rc=SQLITE_NOMEM_BKPT, zBuf);
if( rc==SQLITE_OK ){
- rc = sqlite3OsWrite(pPager->fd, zBuf, pPager->pageSize, 0);
+ rc = tdsqlite3OsWrite(pPager->fd, zBuf, pPager->pageSize, 0);
pPager->aStat[PAGER_STAT_WRITE]++;
}
if( rc==SQLITE_OK ){
@@ -56232,7 +62055,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
}
/* Release the page reference. */
- sqlite3PagerUnref(pPgHdr);
+ tdsqlite3PagerUnref(pPgHdr);
}
return rc;
}
@@ -56244,17 +62067,14 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
** If successful, or if called on a pager for which it is a no-op, this
** function returns SQLITE_OK. Otherwise, an IO error code is returned.
*/
-SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zMaster){
+SQLITE_PRIVATE int tdsqlite3PagerSync(Pager *pPager, const char *zMaster){
int rc = SQLITE_OK;
-
- if( isOpen(pPager->fd) ){
- void *pArg = (void*)zMaster;
- rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC, pArg);
- if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
- }
+ void *pArg = (void*)zMaster;
+ rc = tdsqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC, pArg);
+ if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
if( rc==SQLITE_OK && !pPager->noSync ){
assert( !MEMDB );
- rc = sqlite3OsSync(pPager->fd, pPager->syncFlags);
+ rc = tdsqlite3OsSync(pPager->fd, pPager->syncFlags);
}
return rc;
}
@@ -56270,7 +62090,7 @@ SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zMaster){
** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is
** returned.
*/
-SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerExclusiveLock(Pager *pPager){
int rc = pPager->errCode;
assert( assert_pager_state(pPager) );
if( rc==SQLITE_OK ){
@@ -56305,14 +62125,14 @@ SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager *pPager){
** delete the master journal file if specified).
**
** Note that if zMaster==NULL, this does not overwrite a previous value
-** passed to an sqlite3PagerCommitPhaseOne() call.
+** passed to an tdsqlite3PagerCommitPhaseOne() call.
**
** If the final parameter - noSync - is true, then the database file itself
-** is not synced. The caller must call sqlite3PagerSync() directly to
+** is not synced. The caller must call tdsqlite3PagerSync() directly to
** sync the database file before calling CommitPhaseTwo() to delete the
** journal file in this case.
*/
-SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
+SQLITE_PRIVATE int tdsqlite3PagerCommitPhaseOne(
Pager *pPager, /* Pager object */
const char *zMaster, /* If not NULL, the master journal name */
int noSync /* True to omit the xSync on the db file */
@@ -56330,7 +62150,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
if( NEVER(pPager->errCode) ) return pPager->errCode;
/* Provide the ability to easily simulate an I/O error during testing */
- if( sqlite3FaultSim(400) ) return SQLITE_IOERR;
+ if( tdsqlite3FaultSim(400) ) return SQLITE_IOERR;
PAGERTRACE(("DATABASE SYNC: File=%s zMaster=%s nSize=%d\n",
pPager->zFilename, zMaster, pPager->dbSize));
@@ -56344,15 +62164,16 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
/* If this is an in-memory db, or no pages have been written to, or this
** function has already been called, it is mostly a no-op. However, any
** backup in progress needs to be restarted. */
- sqlite3BackupRestart(pPager->pBackup);
+ tdsqlite3BackupRestart(pPager->pBackup);
}else{
+ PgHdr *pList;
if( pagerUseWal(pPager) ){
- PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
PgHdr *pPageOne = 0;
+ pList = tdsqlite3PcacheDirtyList(pPager->pPCache);
if( pList==0 ){
/* Must have at least one page for the WAL commit flag.
** Ticket [2d1a5c67dfc2363e44f29d9bbd57f] 2011-05-18 */
- rc = sqlite3PagerGet(pPager, 1, &pPageOne, 0);
+ rc = tdsqlite3PagerGet(pPager, 1, &pPageOne, 0);
pList = pPageOne;
pList->pDirty = 0;
}
@@ -56360,11 +62181,26 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
if( ALWAYS(pList) ){
rc = pagerWalFrames(pPager, pList, pPager->dbSize, 1);
}
- sqlite3PagerUnref(pPageOne);
+ tdsqlite3PagerUnref(pPageOne);
if( rc==SQLITE_OK ){
- sqlite3PcacheCleanAll(pPager->pPCache);
+ tdsqlite3PcacheCleanAll(pPager->pPCache);
}
}else{
+ /* The bBatch boolean is true if the batch-atomic-write commit method
+ ** should be used. No rollback journal is created if batch-atomic-write
+ ** is enabled.
+ */
+#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE
+ tdsqlite3_file *fd = pPager->fd;
+ int bBatch = zMaster==0 /* An SQLITE_IOCAP_BATCH_ATOMIC commit */
+ && (tdsqlite3OsDeviceCharacteristics(fd) & SQLITE_IOCAP_BATCH_ATOMIC)
+ && !pPager->noSync
+ && tdsqlite3JournalIsInMemory(pPager->jfd);
+#else
+# define bBatch 0
+#endif
+
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
/* The following block updates the change-counter. Exactly how it
** does this depends on whether or not the atomic-update optimization
** was enabled at compile time, and if this transaction meets the
@@ -56378,7 +62214,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
** If the optimization was not enabled at compile time, then the
** pager_incr_changecounter() function is called to update the change
** counter in 'indirect-mode'. If the optimization is compiled in but
- ** is not applicable to this transaction, call sqlite3JournalCreate()
+ ** is not applicable to this transaction, call tdsqlite3JournalCreate()
** to make sure the journal file has actually been created, then call
** pager_incr_changecounter() to update the change-counter in indirect
** mode.
@@ -56388,33 +62224,41 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
** in 'direct' mode. In this case the journal file will never be
** created for this transaction.
*/
- #ifdef SQLITE_ENABLE_ATOMIC_WRITE
- PgHdr *pPg;
- assert( isOpen(pPager->jfd)
- || pPager->journalMode==PAGER_JOURNALMODE_OFF
- || pPager->journalMode==PAGER_JOURNALMODE_WAL
- );
- if( !zMaster && isOpen(pPager->jfd)
- && pPager->journalOff==jrnlBufferSize(pPager)
- && pPager->dbSize>=pPager->dbOrigSize
- && (0==(pPg = sqlite3PcacheDirtyList(pPager->pPCache)) || 0==pPg->pDirty)
- ){
- /* Update the db file change counter via the direct-write method. The
- ** following call will modify the in-memory representation of page 1
- ** to include the updated change counter and then write page 1
- ** directly to the database file. Because of the atomic-write
- ** property of the host file-system, this is safe.
- */
- rc = pager_incr_changecounter(pPager, 1);
- }else{
- rc = sqlite3JournalCreate(pPager->jfd);
- if( rc==SQLITE_OK ){
- rc = pager_incr_changecounter(pPager, 0);
+ if( bBatch==0 ){
+ PgHdr *pPg;
+ assert( isOpen(pPager->jfd)
+ || pPager->journalMode==PAGER_JOURNALMODE_OFF
+ || pPager->journalMode==PAGER_JOURNALMODE_WAL
+ );
+ if( !zMaster && isOpen(pPager->jfd)
+ && pPager->journalOff==jrnlBufferSize(pPager)
+ && pPager->dbSize>=pPager->dbOrigSize
+ && (!(pPg = tdsqlite3PcacheDirtyList(pPager->pPCache)) || 0==pPg->pDirty)
+ ){
+ /* Update the db file change counter via the direct-write method. The
+ ** following call will modify the in-memory representation of page 1
+ ** to include the updated change counter and then write page 1
+ ** directly to the database file. Because of the atomic-write
+ ** property of the host file-system, this is safe.
+ */
+ rc = pager_incr_changecounter(pPager, 1);
+ }else{
+ rc = tdsqlite3JournalCreate(pPager->jfd);
+ if( rc==SQLITE_OK ){
+ rc = pager_incr_changecounter(pPager, 0);
+ }
}
}
- #else
+#else /* SQLITE_ENABLE_ATOMIC_WRITE */
+#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE
+ if( zMaster ){
+ rc = tdsqlite3JournalCreate(pPager->jfd);
+ if( rc!=SQLITE_OK ) goto commit_phase_one_exit;
+ assert( bBatch==0 );
+ }
+#endif
rc = pager_incr_changecounter(pPager, 0);
- #endif
+#endif /* !SQLITE_ENABLE_ATOMIC_WRITE */
if( rc!=SQLITE_OK ) goto commit_phase_one_exit;
/* Write the master journal name into the journal file. If a master
@@ -56437,13 +62281,42 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
*/
rc = syncJournal(pPager, 0);
if( rc!=SQLITE_OK ) goto commit_phase_one_exit;
-
- rc = pager_write_pagelist(pPager,sqlite3PcacheDirtyList(pPager->pPCache));
+
+ pList = tdsqlite3PcacheDirtyList(pPager->pPCache);
+#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE
+ if( bBatch ){
+ rc = tdsqlite3OsFileControl(fd, SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, 0);
+ if( rc==SQLITE_OK ){
+ rc = pager_write_pagelist(pPager, pList);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3OsFileControl(fd, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE, 0);
+ }
+ if( rc!=SQLITE_OK ){
+ tdsqlite3OsFileControlHint(fd, SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE, 0);
+ }
+ }
+
+ if( (rc&0xFF)==SQLITE_IOERR && rc!=SQLITE_IOERR_NOMEM ){
+ rc = tdsqlite3JournalCreate(pPager->jfd);
+ if( rc!=SQLITE_OK ){
+ tdsqlite3OsClose(pPager->jfd);
+ goto commit_phase_one_exit;
+ }
+ bBatch = 0;
+ }else{
+ tdsqlite3OsClose(pPager->jfd);
+ }
+ }
+#endif /* SQLITE_ENABLE_BATCH_ATOMIC_WRITE */
+
+ if( bBatch==0 ){
+ rc = pager_write_pagelist(pPager, pList);
+ }
if( rc!=SQLITE_OK ){
assert( rc!=SQLITE_IOERR_BLOCKED );
goto commit_phase_one_exit;
}
- sqlite3PcacheCleanAll(pPager->pPCache);
+ tdsqlite3PcacheCleanAll(pPager->pPCache);
/* If the file on disk is smaller than the database image, use
** pager_truncate to grow the file here. This can happen if the database
@@ -56460,7 +62333,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(
/* Finally, sync the database file. */
if( !noSync ){
- rc = sqlite3PagerSync(pPager, zMaster);
+ rc = tdsqlite3PagerSync(pPager, zMaster);
}
IOTRACE(("DBSYNC %p\n", pPager))
}
@@ -56489,13 +62362,14 @@ commit_phase_one_exit:
** If an error occurs, an IO error code is returned and the pager
** moves into the error state. Otherwise, SQLITE_OK is returned.
*/
-SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerCommitPhaseTwo(Pager *pPager){
int rc = SQLITE_OK; /* Return code */
/* This routine should not be called if a prior error has occurred.
** But if (due to a coding error elsewhere in the system) it does get
** called, just return the same error code without doing anything. */
if( NEVER(pPager->errCode) ) return pPager->errCode;
+ pPager->iDataVersion++;
assert( pPager->eState==PAGER_WRITER_LOCKED
|| pPager->eState==PAGER_WRITER_FINISHED
@@ -56524,7 +62398,6 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){
}
PAGERTRACE(("COMMIT %d\n", PAGERID(pPager)));
- pPager->iDataVersion++;
rc = pager_end_transaction(pPager, pPager->setMaster, 1);
return pager_error(pPager, rc);
}
@@ -56555,7 +62428,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){
** their pre-transaction state by re-reading data from the database or
** WAL files. The WAL transaction is then closed.
*/
-SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerRollback(Pager *pPager){
int rc = SQLITE_OK; /* Return code */
PAGERTRACE(("ROLLBACK %d\n", PAGERID(pPager)));
@@ -56569,7 +62442,7 @@ SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){
if( pagerUseWal(pPager) ){
int rc2;
- rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_ROLLBACK, -1);
+ rc = tdsqlite3PagerSavepoint(pPager, SAVEPOINT_ROLLBACK, -1);
rc2 = pager_end_transaction(pPager, pPager->setMaster, 0);
if( rc==SQLITE_OK ) rc = rc2;
}else if( !isOpen(pPager->jfd) || pPager->eState==PAGER_WRITER_LOCKED ){
@@ -56582,6 +62455,7 @@ SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){
*/
pPager->errCode = SQLITE_ABORT;
pPager->eState = PAGER_ERROR;
+ setGetterMethod(pPager);
return rc;
}
}else{
@@ -56604,7 +62478,7 @@ SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){
** Return TRUE if the database file is opened read-only. Return FALSE
** if the database is (in theory) writable.
*/
-SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager *pPager){
+SQLITE_PRIVATE u8 tdsqlite3PagerIsreadonly(Pager *pPager){
return pPager->readOnly;
}
@@ -56612,8 +62486,8 @@ SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager *pPager){
/*
** Return the sum of the reference counts for all pages held by pPager.
*/
-SQLITE_PRIVATE int sqlite3PagerRefcount(Pager *pPager){
- return sqlite3PcacheRefCount(pPager->pPCache);
+SQLITE_PRIVATE int tdsqlite3PagerRefcount(Pager *pPager){
+ return tdsqlite3PcacheRefCount(pPager->pPCache);
}
#endif
@@ -56621,30 +62495,30 @@ SQLITE_PRIVATE int sqlite3PagerRefcount(Pager *pPager){
** Return the approximate number of bytes of memory currently
** used by the pager and its associated cache.
*/
-SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerMemUsed(Pager *pPager){
int perPageSize = pPager->pageSize + pPager->nExtra + sizeof(PgHdr)
+ 5*sizeof(void*);
- return perPageSize*sqlite3PcachePagecount(pPager->pPCache)
- + sqlite3MallocSize(pPager)
+ return perPageSize*tdsqlite3PcachePagecount(pPager->pPCache)
+ + tdsqlite3MallocSize(pPager)
+ pPager->pageSize;
}
/*
** Return the number of references to the specified page.
*/
-SQLITE_PRIVATE int sqlite3PagerPageRefcount(DbPage *pPage){
- return sqlite3PcachePageRefcount(pPage);
+SQLITE_PRIVATE int tdsqlite3PagerPageRefcount(DbPage *pPage){
+ return tdsqlite3PcachePageRefcount(pPage);
}
#ifdef SQLITE_TEST
/*
** This routine is used for testing and analysis only.
*/
-SQLITE_PRIVATE int *sqlite3PagerStats(Pager *pPager){
+SQLITE_PRIVATE int *tdsqlite3PagerStats(Pager *pPager){
static int a[11];
- a[0] = sqlite3PcacheRefCount(pPager->pPCache);
- a[1] = sqlite3PcachePagecount(pPager->pPCache);
- a[2] = sqlite3PcacheGetCachesize(pPager->pPCache);
+ a[0] = tdsqlite3PcacheRefCount(pPager->pPCache);
+ a[1] = tdsqlite3PcachePagecount(pPager->pPCache);
+ a[2] = tdsqlite3PcacheGetCachesize(pPager->pPCache);
a[3] = pPager->eState==PAGER_OPEN ? -1 : (int) pPager->dbSize;
a[4] = pPager->eState;
a[5] = pPager->errCode;
@@ -56658,33 +62532,40 @@ SQLITE_PRIVATE int *sqlite3PagerStats(Pager *pPager){
#endif
/*
-** Parameter eStat must be either SQLITE_DBSTATUS_CACHE_HIT or
-** SQLITE_DBSTATUS_CACHE_MISS. Before returning, *pnVal is incremented by the
+** Parameter eStat must be one of SQLITE_DBSTATUS_CACHE_HIT, _MISS, _WRITE,
+** or _WRITE+1. The SQLITE_DBSTATUS_CACHE_WRITE+1 case is a translation
+** of SQLITE_DBSTATUS_CACHE_SPILL. The _SPILL case is not contiguous because
+** it was added later.
+**
+** Before returning, *pnVal is incremented by the
** current cache hit or miss count, according to the value of eStat. If the
** reset parameter is non-zero, the cache hit or miss count is zeroed before
** returning.
*/
-SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){
+SQLITE_PRIVATE void tdsqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){
assert( eStat==SQLITE_DBSTATUS_CACHE_HIT
|| eStat==SQLITE_DBSTATUS_CACHE_MISS
|| eStat==SQLITE_DBSTATUS_CACHE_WRITE
+ || eStat==SQLITE_DBSTATUS_CACHE_WRITE+1
);
assert( SQLITE_DBSTATUS_CACHE_HIT+1==SQLITE_DBSTATUS_CACHE_MISS );
assert( SQLITE_DBSTATUS_CACHE_HIT+2==SQLITE_DBSTATUS_CACHE_WRITE );
- assert( PAGER_STAT_HIT==0 && PAGER_STAT_MISS==1 && PAGER_STAT_WRITE==2 );
+ assert( PAGER_STAT_HIT==0 && PAGER_STAT_MISS==1
+ && PAGER_STAT_WRITE==2 && PAGER_STAT_SPILL==3 );
- *pnVal += pPager->aStat[eStat - SQLITE_DBSTATUS_CACHE_HIT];
+ eStat -= SQLITE_DBSTATUS_CACHE_HIT;
+ *pnVal += pPager->aStat[eStat];
if( reset ){
- pPager->aStat[eStat - SQLITE_DBSTATUS_CACHE_HIT] = 0;
+ pPager->aStat[eStat] = 0;
}
}
/*
** Return true if this is an in-memory or temp-file backed pager.
*/
-SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerIsMemdb(Pager *pPager){
return pPager->tempFile;
}
@@ -56712,7 +62593,7 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){
** if the allocation fails. Otherwise, zero the new portion in case a
** malloc failure occurs while populating it in the for(...) loop below.
*/
- aNew = (PagerSavepoint *)sqlite3Realloc(
+ aNew = (PagerSavepoint *)tdsqlite3Realloc(
pPager->aSavepoint, sizeof(PagerSavepoint)*nSavepoint
);
if( !aNew ){
@@ -56730,12 +62611,12 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){
aNew[ii].iOffset = JOURNAL_HDR_SZ(pPager);
}
aNew[ii].iSubRec = pPager->nSubRec;
- aNew[ii].pInSavepoint = sqlite3BitvecCreate(pPager->dbSize);
+ aNew[ii].pInSavepoint = tdsqlite3BitvecCreate(pPager->dbSize);
if( !aNew[ii].pInSavepoint ){
return SQLITE_NOMEM_BKPT;
}
if( pagerUseWal(pPager) ){
- sqlite3WalSavepoint(pPager->pWal, aNew[ii].aWalData);
+ tdsqlite3WalSavepoint(pPager->pWal, aNew[ii].aWalData);
}
pPager->nSavepoint = ii+1;
}
@@ -56743,7 +62624,7 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){
assertTruncateConstraint(pPager);
return rc;
}
-SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){
+SQLITE_PRIVATE int tdsqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){
assert( pPager->eState>=PAGER_WRITER_LOCKED );
assert( assert_pager_state(pPager) );
@@ -56773,7 +62654,7 @@ SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){
**
** If a negative value is passed to this function, then the current
** transaction is rolled back. This is different to calling
-** sqlite3PagerRollback() because this function does not terminate
+** tdsqlite3PagerRollback() because this function does not terminate
** the transaction or unlock the database, it just restores the
** contents of the database to its original state.
**
@@ -56785,7 +62666,7 @@ SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){
** or an IO error code if an IO error occurs while rolling back a
** savepoint. If no errors occur, SQLITE_OK is returned.
*/
-SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){
+SQLITE_PRIVATE int tdsqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){
int rc = pPager->errCode;
#ifdef SQLITE_ENABLE_ZIPVFS
@@ -56805,7 +62686,7 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){
*/
nNew = iSavepoint + (( op==SAVEPOINT_RELEASE ) ? 0 : 1);
for(ii=nNew; ii<pPager->nSavepoint; ii++){
- sqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint);
+ tdsqlite3BitvecDestroy(pPager->aSavepoint[ii].pInSavepoint);
}
pPager->nSavepoint = nNew;
@@ -56814,8 +62695,8 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){
if( op==SAVEPOINT_RELEASE ){
if( nNew==0 && isOpen(pPager->sjfd) ){
/* Only truncate if it is an in-memory sub-journal. */
- if( sqlite3JournalIsInMemory(pPager->sjfd) ){
- rc = sqlite3OsTruncate(pPager->sjfd, 0);
+ if( tdsqlite3JournalIsInMemory(pPager->sjfd) ){
+ rc = tdsqlite3OsTruncate(pPager->sjfd, 0);
assert( rc==SQLITE_OK );
}
pPager->nSubRec = 0;
@@ -56843,6 +62724,7 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){
){
pPager->errCode = SQLITE_ABORT;
pPager->eState = PAGER_ERROR;
+ setGetterMethod(pPager);
}
#endif
}
@@ -56859,15 +62741,19 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){
** behavior. But when the Btree needs to know the filename for matching to
** shared cache, it uses nullIfMemDb==0 so that in-memory databases can
** participate in shared-cache.
+**
+** The return value to this routine is always safe to use with
+** tdsqlite3_uri_parameter() and tdsqlite3_filename_database() and friends.
*/
-SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager *pPager, int nullIfMemDb){
- return (nullIfMemDb && pPager->memDb) ? "" : pPager->zFilename;
+SQLITE_PRIVATE const char *tdsqlite3PagerFilename(const Pager *pPager, int nullIfMemDb){
+ static const char zFake[] = { 0x00, 0x01, 0x00, 0x00, 0x00 };
+ return (nullIfMemDb && pPager->memDb) ? &zFake[3] : pPager->zFilename;
}
/*
** Return the VFS structure for the pager.
*/
-SQLITE_PRIVATE sqlite3_vfs *sqlite3PagerVfs(Pager *pPager){
+SQLITE_PRIVATE tdsqlite3_vfs *tdsqlite3PagerVfs(Pager *pPager){
return pPager->pVfs;
}
@@ -56876,26 +62762,36 @@ SQLITE_PRIVATE sqlite3_vfs *sqlite3PagerVfs(Pager *pPager){
** with the pager. This might return NULL if the file has
** not yet been opened.
*/
-SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager *pPager){
+SQLITE_PRIVATE tdsqlite3_file *tdsqlite3PagerFile(Pager *pPager){
return pPager->fd;
}
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+/*
+** Reset the lock timeout for pager.
+*/
+SQLITE_PRIVATE void tdsqlite3PagerResetLockTimeout(Pager *pPager){
+ int x = 0;
+ tdsqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_LOCK_TIMEOUT, &x);
+}
+#endif
+
/*
** Return the file handle for the journal file (if it exists).
** This will be either the rollback journal or the WAL file.
*/
-SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager *pPager){
+SQLITE_PRIVATE tdsqlite3_file *tdsqlite3PagerJrnlFile(Pager *pPager){
#if SQLITE_OMIT_WAL
return pPager->jfd;
#else
- return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd;
+ return pPager->pWal ? tdsqlite3WalFile(pPager->pWal) : pPager->jfd;
#endif
}
/*
** Return the full pathname of the journal file.
*/
-SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager *pPager){
+SQLITE_PRIVATE const char *tdsqlite3PagerJournalname(Pager *pPager){
return pPager->zJournal;
}
@@ -56903,21 +62799,26 @@ SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager *pPager){
/*
** Set or retrieve the codec for this pager
*/
-SQLITE_PRIVATE void sqlite3PagerSetCodec(
+SQLITE_PRIVATE void tdsqlite3PagerSetCodec(
Pager *pPager,
void *(*xCodec)(void*,void*,Pgno,int),
void (*xCodecSizeChng)(void*,int,int),
void (*xCodecFree)(void*),
void *pCodec
){
- if( pPager->xCodecFree ) pPager->xCodecFree(pPager->pCodec);
+ if( pPager->xCodecFree ){
+ pPager->xCodecFree(pPager->pCodec);
+ }else{
+ pager_reset(pPager);
+ }
pPager->xCodec = pPager->memDb ? 0 : xCodec;
pPager->xCodecSizeChng = xCodecSizeChng;
pPager->xCodecFree = xCodecFree;
pPager->pCodec = pCodec;
+ setGetterMethod(pPager);
pagerReportSize(pPager);
}
-SQLITE_PRIVATE void *sqlite3PagerGetCodec(Pager *pPager){
+SQLITE_PRIVATE void *tdsqlite3PagerGetCodec(Pager *pPager){
return pPager->pCodec;
}
@@ -56928,7 +62829,7 @@ SQLITE_PRIVATE void *sqlite3PagerGetCodec(Pager *pPager){
** This function returns a pointer to a buffer containing the encrypted
** page content. If a malloc fails, this function may return NULL.
*/
-SQLITE_PRIVATE void *sqlite3PagerCodec(PgHdr *pPg){
+SQLITE_PRIVATE void *tdsqlite3PagerCodec(PgHdr *pPg){
void *aData = 0;
CODEC2(pPg->pPager, pPg->pData, pPg->pgno, 6, return 0, aData);
return aData;
@@ -56937,7 +62838,7 @@ SQLITE_PRIVATE void *sqlite3PagerCodec(PgHdr *pPg){
/*
** Return the current pager state
*/
-SQLITE_PRIVATE int sqlite3PagerState(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerState(Pager *pPager){
return pPager->eState;
}
#endif /* SQLITE_HAS_CODEC */
@@ -56968,7 +62869,7 @@ SQLITE_PRIVATE int sqlite3PagerState(Pager *pPager){
** This function may return SQLITE_NOMEM or an IO error code if an error
** occurs. Otherwise, it returns SQLITE_OK.
*/
-SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
+SQLITE_PRIVATE int tdsqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
PgHdr *pPgOld; /* The page being overwritten. */
Pgno needSyncPgno = 0; /* Old value of pPg->pgno, if sync is required */
int rc; /* Return code */
@@ -56985,7 +62886,7 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i
*/
assert( pPager->tempFile || !MEMDB );
if( pPager->tempFile ){
- rc = sqlite3PagerWrite(pPg);
+ rc = tdsqlite3PagerWrite(pPg);
if( rc ) return rc;
}
@@ -57037,30 +62938,34 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i
** for the page moved there.
*/
pPg->flags &= ~PGHDR_NEED_SYNC;
- pPgOld = sqlite3PagerLookup(pPager, pgno);
- assert( !pPgOld || pPgOld->nRef==1 );
+ pPgOld = tdsqlite3PagerLookup(pPager, pgno);
+ assert( !pPgOld || pPgOld->nRef==1 || CORRUPT_DB );
if( pPgOld ){
+ if( pPgOld->nRef>1 ){
+ tdsqlite3PagerUnrefNotNull(pPgOld);
+ return SQLITE_CORRUPT_BKPT;
+ }
pPg->flags |= (pPgOld->flags&PGHDR_NEED_SYNC);
if( pPager->tempFile ){
/* Do not discard pages from an in-memory database since we might
** need to rollback later. Just move the page out of the way. */
- sqlite3PcacheMove(pPgOld, pPager->dbSize+1);
+ tdsqlite3PcacheMove(pPgOld, pPager->dbSize+1);
}else{
- sqlite3PcacheDrop(pPgOld);
+ tdsqlite3PcacheDrop(pPgOld);
}
}
origPgno = pPg->pgno;
- sqlite3PcacheMove(pPg, pgno);
- sqlite3PcacheMakeDirty(pPg);
+ tdsqlite3PcacheMove(pPg, pgno);
+ tdsqlite3PcacheMakeDirty(pPg);
/* For an in-memory database, make sure the original page continues
** to exist, in case the transaction needs to roll back. Use pPgOld
** as the original page since it has already been allocated.
*/
if( pPager->tempFile && pPgOld ){
- sqlite3PcacheMove(pPgOld, origPgno);
- sqlite3PagerUnrefNotNull(pPgOld);
+ tdsqlite3PcacheMove(pPgOld, origPgno);
+ tdsqlite3PagerUnrefNotNull(pPgOld);
}
if( needSyncPgno ){
@@ -57079,17 +62984,17 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i
** the journal file twice, but that is not a problem.
*/
PgHdr *pPgHdr;
- rc = sqlite3PagerGet(pPager, needSyncPgno, &pPgHdr, 0);
+ rc = tdsqlite3PagerGet(pPager, needSyncPgno, &pPgHdr, 0);
if( rc!=SQLITE_OK ){
if( needSyncPgno<=pPager->dbOrigSize ){
assert( pPager->pTmpSpace!=0 );
- sqlite3BitvecClear(pPager->pInJournal, needSyncPgno, pPager->pTmpSpace);
+ tdsqlite3BitvecClear(pPager->pInJournal, needSyncPgno, pPager->pTmpSpace);
}
return rc;
}
pPgHdr->flags |= PGHDR_NEED_SYNC;
- sqlite3PcacheMakeDirty(pPgHdr);
- sqlite3PagerUnrefNotNull(pPgHdr);
+ tdsqlite3PcacheMakeDirty(pPgHdr);
+ tdsqlite3PagerUnrefNotNull(pPgHdr);
}
return SQLITE_OK;
@@ -57102,16 +63007,16 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i
** page number to iNew and sets the value of the PgHdr.flags field to
** the value passed as the third parameter.
*/
-SQLITE_PRIVATE void sqlite3PagerRekey(DbPage *pPg, Pgno iNew, u16 flags){
+SQLITE_PRIVATE void tdsqlite3PagerRekey(DbPage *pPg, Pgno iNew, u16 flags){
assert( pPg->pgno!=iNew );
pPg->flags = flags;
- sqlite3PcacheMove(pPg, iNew);
+ tdsqlite3PcacheMove(pPg, iNew);
}
/*
** Return a pointer to the data for the specified page.
*/
-SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *pPg){
+SQLITE_PRIVATE void *tdsqlite3PagerGetData(DbPage *pPg){
assert( pPg->nRef>0 || pPg->pPager->memDb );
return pPg->pData;
}
@@ -57120,7 +63025,7 @@ SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *pPg){
** Return a pointer to the Pager.nExtra bytes of "extra" space
** allocated along with the specified page.
*/
-SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *pPg){
+SQLITE_PRIVATE void *tdsqlite3PagerGetExtra(DbPage *pPg){
return pPg->pExtra;
}
@@ -57134,14 +63039,14 @@ SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *pPg){
** PAGER_LOCKINGMODE_EXCLUSIVE, indicating the current (possibly updated)
** locking-mode.
*/
-SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *pPager, int eMode){
+SQLITE_PRIVATE int tdsqlite3PagerLockingMode(Pager *pPager, int eMode){
assert( eMode==PAGER_LOCKINGMODE_QUERY
|| eMode==PAGER_LOCKINGMODE_NORMAL
|| eMode==PAGER_LOCKINGMODE_EXCLUSIVE );
assert( PAGER_LOCKINGMODE_QUERY<0 );
assert( PAGER_LOCKINGMODE_NORMAL>=0 && PAGER_LOCKINGMODE_EXCLUSIVE>=0 );
- assert( pPager->exclusiveMode || 0==sqlite3WalHeapMemory(pPager->pWal) );
- if( eMode>=0 && !pPager->tempFile && !sqlite3WalHeapMemory(pPager->pWal) ){
+ assert( pPager->exclusiveMode || 0==tdsqlite3WalHeapMemory(pPager->pWal) );
+ if( eMode>=0 && !pPager->tempFile && !tdsqlite3WalHeapMemory(pPager->pWal) ){
pPager->exclusiveMode = (u8)eMode;
}
return (int)pPager->exclusiveMode;
@@ -57167,16 +63072,9 @@ SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *pPager, int eMode){
**
** The returned indicate the current (possibly updated) journal-mode.
*/
-SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){
+SQLITE_PRIVATE int tdsqlite3PagerSetJournalMode(Pager *pPager, int eMode){
u8 eOld = pPager->journalMode; /* Prior journalmode */
-#ifdef SQLITE_DEBUG
- /* The print_pager_state() routine is intended to be used by the debugger
- ** only. We invoke it once here to suppress a compiler warning. */
- print_pager_state(pPager);
-#endif
-
-
/* The eMode parameter is always valid */
assert( eMode==PAGER_JOURNALMODE_DELETE
|| eMode==PAGER_JOURNALMODE_TRUNCATE
@@ -57229,22 +63127,22 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){
** database file. This ensures that the journal file is not deleted
** while it is in use by some other client.
*/
- sqlite3OsClose(pPager->jfd);
+ tdsqlite3OsClose(pPager->jfd);
if( pPager->eLock>=RESERVED_LOCK ){
- sqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0);
+ tdsqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0);
}else{
int rc = SQLITE_OK;
int state = pPager->eState;
assert( state==PAGER_OPEN || state==PAGER_READER );
if( state==PAGER_OPEN ){
- rc = sqlite3PagerSharedLock(pPager);
+ rc = tdsqlite3PagerSharedLock(pPager);
}
if( pPager->eState==PAGER_READER ){
assert( rc==SQLITE_OK );
rc = pagerLockDb(pPager, RESERVED_LOCK);
}
if( rc==SQLITE_OK ){
- sqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0);
+ tdsqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0);
}
if( rc==SQLITE_OK && state==PAGER_READER ){
pagerUnlockDb(pPager, SHARED_LOCK);
@@ -57254,7 +63152,7 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){
assert( state==pPager->eState );
}
}else if( eMode==PAGER_JOURNALMODE_OFF ){
- sqlite3OsClose(pPager->jfd);
+ tdsqlite3OsClose(pPager->jfd);
}
}
@@ -57265,7 +63163,7 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){
/*
** Return the current journal mode.
*/
-SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerGetJournalMode(Pager *pPager){
return (int)pPager->journalMode;
}
@@ -57274,7 +63172,7 @@ SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager *pPager){
** journalmode. Journalmode changes can only happen when the database
** is unmodified.
*/
-SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerOkToChangeJournalMode(Pager *pPager){
assert( assert_pager_state(pPager) );
if( pPager->eState>=PAGER_WRITER_CACHEMOD ) return 0;
if( NEVER(isOpen(pPager->jfd) && pPager->journalOff>0) ) return 0;
@@ -57287,10 +63185,10 @@ SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager *pPager){
** Setting the size limit to -1 means no limit is enforced.
** An attempt to set a limit smaller than -1 is a no-op.
*/
-SQLITE_PRIVATE i64 sqlite3PagerJournalSizeLimit(Pager *pPager, i64 iLimit){
+SQLITE_PRIVATE i64 tdsqlite3PagerJournalSizeLimit(Pager *pPager, i64 iLimit){
if( iLimit>=-1 ){
pPager->journalSizeLimit = iLimit;
- sqlite3WalLimit(pPager->pWal, iLimit);
+ tdsqlite3WalLimit(pPager->pWal, iLimit);
}
return pPager->journalSizeLimit;
}
@@ -57298,10 +63196,10 @@ SQLITE_PRIVATE i64 sqlite3PagerJournalSizeLimit(Pager *pPager, i64 iLimit){
/*
** Return a pointer to the pPager->pBackup variable. The backup module
** in backup.c maintains the content of this variable. This module
-** uses it opaquely as an argument to sqlite3BackupRestart() and
-** sqlite3BackupUpdate() only.
+** uses it opaquely as an argument to tdsqlite3BackupRestart() and
+** tdsqlite3BackupUpdate() only.
*/
-SQLITE_PRIVATE sqlite3_backup **sqlite3PagerBackupPtr(Pager *pPager){
+SQLITE_PRIVATE tdsqlite3_backup **tdsqlite3PagerBackupPtr(Pager *pPager){
return &pPager->pBackup;
}
@@ -57309,7 +63207,7 @@ SQLITE_PRIVATE sqlite3_backup **sqlite3PagerBackupPtr(Pager *pPager){
/*
** Unless this is an in-memory or temporary database, clear the pager cache.
*/
-SQLITE_PRIVATE void sqlite3PagerClearCache(Pager *pPager){
+SQLITE_PRIVATE void tdsqlite3PagerClearCache(Pager *pPager){
assert( MEMDB==0 || pPager->tempFile );
if( pPager->tempFile==0 ) pager_reset(pPager);
}
@@ -57319,34 +63217,41 @@ SQLITE_PRIVATE void sqlite3PagerClearCache(Pager *pPager){
#ifndef SQLITE_OMIT_WAL
/*
** This function is called when the user invokes "PRAGMA wal_checkpoint",
-** "PRAGMA wal_blocking_checkpoint" or calls the sqlite3_wal_checkpoint()
+** "PRAGMA wal_blocking_checkpoint" or calls the tdsqlite3_wal_checkpoint()
** or wal_blocking_checkpoint() API functions.
**
** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART.
*/
-SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, int eMode, int *pnLog, int *pnCkpt){
+SQLITE_PRIVATE int tdsqlite3PagerCheckpoint(
+ Pager *pPager, /* Checkpoint on this pager */
+ tdsqlite3 *db, /* Db handle used to check for interrupts */
+ int eMode, /* Type of checkpoint */
+ int *pnLog, /* OUT: Final number of frames in log */
+ int *pnCkpt /* OUT: Final number of checkpointed frames */
+){
int rc = SQLITE_OK;
if( pPager->pWal ){
- rc = sqlite3WalCheckpoint(pPager->pWal, eMode,
+ rc = tdsqlite3WalCheckpoint(pPager->pWal, db, eMode,
(eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler),
pPager->pBusyHandlerArg,
- pPager->ckptSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace,
+ pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace,
pnLog, pnCkpt
);
+ tdsqlite3PagerResetLockTimeout(pPager);
}
return rc;
}
-SQLITE_PRIVATE int sqlite3PagerWalCallback(Pager *pPager){
- return sqlite3WalCallback(pPager->pWal);
+SQLITE_PRIVATE int tdsqlite3PagerWalCallback(Pager *pPager){
+ return tdsqlite3WalCallback(pPager->pWal);
}
/*
** Return true if the underlying VFS for the given pager supports the
** primitives necessary for write-ahead logging.
*/
-SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager){
- const sqlite3_io_methods *pMethods = pPager->fd->pMethods;
+SQLITE_PRIVATE int tdsqlite3PagerWalSupported(Pager *pPager){
+ const tdsqlite3_io_methods *pMethods = pPager->fd->pMethods;
if( pPager->noLock ) return 0;
return pPager->exclusiveMode || (pMethods->iVersion>=2 && pMethods->xShmMap);
}
@@ -57370,7 +63275,7 @@ static int pagerExclusiveLock(Pager *pPager){
}
/*
-** Call sqlite3WalOpen() to open the WAL handle. If the pager is in
+** Call tdsqlite3WalOpen() to open the WAL handle. If the pager is in
** exclusive-locking mode when this function is called, take an EXCLUSIVE
** lock on the database file and use heap-memory to store the wal-index
** in. Otherwise, use the normal shared-memory.
@@ -57394,7 +63299,7 @@ static int pagerOpenWal(Pager *pPager){
** (e.g. due to malloc() failure), return an error code.
*/
if( rc==SQLITE_OK ){
- rc = sqlite3WalOpen(pPager->pVfs,
+ rc = tdsqlite3WalOpen(pPager->pVfs,
pPager->fd, pPager->zWal, pPager->exclusiveMode,
pPager->journalSizeLimit, &pPager->pWal
);
@@ -57420,7 +63325,7 @@ static int pagerOpenWal(Pager *pPager){
** the WAL file is already open, set *pbOpen to 1 and return SQLITE_OK
** without doing anything.
*/
-SQLITE_PRIVATE int sqlite3PagerOpenWal(
+SQLITE_PRIVATE int tdsqlite3PagerOpenWal(
Pager *pPager, /* Pager object */
int *pbOpen /* OUT: Set to true if call is a no-op */
){
@@ -57433,10 +63338,10 @@ SQLITE_PRIVATE int sqlite3PagerOpenWal(
assert( pbOpen!=0 || (!pPager->tempFile && !pPager->pWal) );
if( !pPager->tempFile && !pPager->pWal ){
- if( !sqlite3PagerWalSupported(pPager) ) return SQLITE_CANTOPEN;
+ if( !tdsqlite3PagerWalSupported(pPager) ) return SQLITE_CANTOPEN;
/* Close any rollback journal previously open */
- sqlite3OsClose(pPager->jfd);
+ tdsqlite3OsClose(pPager->jfd);
rc = pagerOpenWal(pPager);
if( rc==SQLITE_OK ){
@@ -57459,7 +63364,7 @@ SQLITE_PRIVATE int sqlite3PagerOpenWal(
** error (SQLITE_BUSY) is returned and the log connection is not closed.
** If successful, the EXCLUSIVE lock is not released before returning.
*/
-SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerCloseWal(Pager *pPager, tdsqlite3 *db){
int rc = SQLITE_OK;
assert( pPager->journalMode==PAGER_JOURNALMODE_WAL );
@@ -57472,7 +63377,7 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager){
int logexists = 0;
rc = pagerLockDb(pPager, SHARED_LOCK);
if( rc==SQLITE_OK ){
- rc = sqlite3OsAccess(
+ rc = tdsqlite3OsAccess(
pPager->pVfs, pPager->zWal, SQLITE_ACCESS_EXISTS, &logexists
);
}
@@ -57487,7 +63392,7 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager){
if( rc==SQLITE_OK && pPager->pWal ){
rc = pagerExclusiveLock(pPager);
if( rc==SQLITE_OK ){
- rc = sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags,
+ rc = tdsqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags,
pPager->pageSize, (u8*)pPager->pTmpSpace);
pPager->pWal = 0;
pagerFixMaplimit(pPager);
@@ -57497,15 +63402,17 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager){
return rc;
}
+
+
#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** If this is a WAL database, obtain a snapshot handle for the snapshot
** currently open. Otherwise, return an error.
*/
-SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot){
+SQLITE_PRIVATE int tdsqlite3PagerSnapshotGet(Pager *pPager, tdsqlite3_snapshot **ppSnapshot){
int rc = SQLITE_ERROR;
if( pPager->pWal ){
- rc = sqlite3WalSnapshotGet(pPager->pWal, ppSnapshot);
+ rc = tdsqlite3WalSnapshotGet(pPager->pWal, ppSnapshot);
}
return rc;
}
@@ -57515,15 +63422,61 @@ SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppS
** read transaction is opened, attempt to read from the snapshot it
** identifies. If this is not a WAL database, return an error.
*/
-SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot){
+SQLITE_PRIVATE int tdsqlite3PagerSnapshotOpen(Pager *pPager, tdsqlite3_snapshot *pSnapshot){
int rc = SQLITE_OK;
if( pPager->pWal ){
- sqlite3WalSnapshotOpen(pPager->pWal, pSnapshot);
+ tdsqlite3WalSnapshotOpen(pPager->pWal, pSnapshot);
+ }else{
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+
+/*
+** If this is a WAL database, call tdsqlite3WalSnapshotRecover(). If this
+** is not a WAL database, return an error.
+*/
+SQLITE_PRIVATE int tdsqlite3PagerSnapshotRecover(Pager *pPager){
+ int rc;
+ if( pPager->pWal ){
+ rc = tdsqlite3WalSnapshotRecover(pPager->pWal);
}else{
rc = SQLITE_ERROR;
}
return rc;
}
+
+/*
+** The caller currently has a read transaction open on the database.
+** If this is not a WAL database, SQLITE_ERROR is returned. Otherwise,
+** this function takes a SHARED lock on the CHECKPOINTER slot and then
+** checks if the snapshot passed as the second argument is still
+** available. If so, SQLITE_OK is returned.
+**
+** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
+** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
+** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
+** lock is released before returning.
+*/
+SQLITE_PRIVATE int tdsqlite3PagerSnapshotCheck(Pager *pPager, tdsqlite3_snapshot *pSnapshot){
+ int rc;
+ if( pPager->pWal ){
+ rc = tdsqlite3WalSnapshotCheck(pPager->pWal, pSnapshot);
+ }else{
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+
+/*
+** Release a lock obtained by an earlier successful call to
+** tdsqlite3PagerSnapshotCheck().
+*/
+SQLITE_PRIVATE void tdsqlite3PagerSnapshotUnlock(Pager *pPager){
+ assert( pPager->pWal );
+ tdsqlite3WalSnapshotUnlock(pPager->pWal);
+}
+
#endif /* SQLITE_ENABLE_SNAPSHOT */
#endif /* !SQLITE_OMIT_WAL */
@@ -57535,9 +63488,9 @@ SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSn
** WAL frames. Otherwise, if this is not a WAL database or the WAL file
** is empty, return 0.
*/
-SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){
+SQLITE_PRIVATE int tdsqlite3PagerWalFramesize(Pager *pPager){
assert( pPager->eState>=PAGER_READER );
- return sqlite3WalFramesize(pPager->pWal);
+ return tdsqlite3WalFramesize(pPager->pWal);
}
#endif
@@ -57545,30 +63498,19 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){
/* BEGIN SQLCIPHER */
#ifdef SQLITE_HAS_CODEC
-SQLITE_API void sqlite3pager_get_codec(Pager *pPager, void **ctx) {
- *ctx = pPager->pCodec;
-}
-SQLITE_API int sqlite3pager_is_mj_pgno(Pager *pPager, Pgno pgno) {
+SQLITE_API int tdsqlite3pager_is_mj_pgno(Pager *pPager, Pgno pgno) {
return (PAGER_MJ_PGNO(pPager) == pgno) ? 1 : 0;
}
-SQLITE_PRIVATE sqlite3_file *sqlite3Pager_get_fd(Pager *pPager) {
- return (isOpen(pPager->fd)) ? pPager->fd : NULL;
-}
-
-SQLITE_API void sqlite3pager_sqlite3PagerSetCodec(
- Pager *pPager,
- void *(*xCodec)(void*,void*,Pgno,int),
- void (*xCodecSizeChng)(void*,int,int),
- void (*xCodecFree)(void*),
- void *pCodec
-){
- sqlite3PagerSetCodec(pPager, xCodec, xCodecSizeChng, xCodecFree, pCodec);
+SQLITE_API void tdsqlite3pager_error(Pager *pPager, int error) {
+ pPager->errCode = error;
+ pPager->eState = PAGER_ERROR;
+ setGetterMethod(pPager);
}
-SQLITE_API void sqlite3pager_sqlite3PagerSetError( Pager *pPager, int error) {
- pPager->errCode = error;
+SQLITE_API void tdsqlite3pager_reset(Pager *pPager){
+ pager_reset(pPager);
}
#endif
@@ -57711,6 +63653,10 @@ SQLITE_API void sqlite3pager_sqlite3PagerSetError( Pager *pPager, int error) {
** on a network filesystem. All users of the database must be able to
** share memory.
**
+** In the default unix and windows implementation, the wal-index is a mmapped
+** file whose name is the database name with a "-shm" suffix added. For that
+** reason, the wal-index is sometimes called the "shm" file.
+**
** The wal-index is transient. After a crash, the wal-index can (and should
** be) reconstructed from the original WAL file. In fact, the VFS is required
** to either truncate or zero the header of the wal-index when the last
@@ -57827,13 +63773,25 @@ SQLITE_API void sqlite3pager_sqlite3PagerSetError( Pager *pPager, int error) {
** Trace output macros
*/
#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG)
-SQLITE_PRIVATE int sqlite3WalTrace = 0;
-# define WALTRACE(X) if(sqlite3WalTrace) sqlite3DebugPrintf X
+SQLITE_PRIVATE int tdsqlite3WalTrace = 0;
+# define WALTRACE(X) if(tdsqlite3WalTrace) tdsqlite3DebugPrintf X
#else
# define WALTRACE(X)
#endif
/*
+** WAL mode depends on atomic aligned 32-bit loads and stores in a few
+** places. The following macros try to make this explicit.
+*/
+#if GCC_VESRION>=5004000
+# define AtomicLoad(PTR) __atomic_load_n((PTR),__ATOMIC_RELAXED)
+# define AtomicStore(PTR,VAL) __atomic_store_n((PTR),(VAL),__ATOMIC_RELAXED)
+#else
+# define AtomicLoad(PTR) (*(PTR))
+# define AtomicStore(PTR,VAL) (*(PTR) = (VAL))
+#endif
+
+/*
** The maximum (and only) versions of the wal and wal-index formats
** that may be interpreted by this version of SQLite.
**
@@ -57850,9 +63808,18 @@ SQLITE_PRIVATE int sqlite3WalTrace = 0;
#define WALINDEX_MAX_VERSION 3007000
/*
-** Indices of various locking bytes. WAL_NREADER is the number
+** Index numbers for various locking bytes. WAL_NREADER is the number
** of available reader locks and should be at least 3. The default
** is SQLITE_SHM_NLOCK==8 and WAL_NREADER==5.
+**
+** Technically, the various VFSes are free to implement these locks however
+** they see fit. However, compatibility is encouraged so that VFSes can
+** interoperate. The standard implemention used on both unix and windows
+** is for the index number to indicate a byte offset into the
+** WalCkptInfo.aLock[] array in the wal-index header. In other words, all
+** locks are on the shm file. The WALINDEX_LOCK_OFFSET constant (which
+** should be 120) is the location in the shm file for the first locking
+** byte.
*/
#define WAL_WRITE_LOCK 0
#define WAL_ALL_BUT_WRITE 1
@@ -57976,7 +63943,6 @@ struct WalCkptInfo {
#define WAL_FRAME_HDRSIZE 24
/* Size of write ahead log header, including checksum. */
-/* #define WAL_HDRSIZE 24 */
#define WAL_HDRSIZE 32
/* WAL magic value. Either this value, or the same value with the least
@@ -58004,9 +63970,9 @@ struct WalCkptInfo {
** following object.
*/
struct Wal {
- sqlite3_vfs *pVfs; /* The VFS used to create pDbFd */
- sqlite3_file *pDbFd; /* File handle for the database file */
- sqlite3_file *pWalFd; /* File handle for WAL file */
+ tdsqlite3_vfs *pVfs; /* The VFS used to create pDbFd */
+ tdsqlite3_file *pDbFd; /* File handle for the database file */
+ tdsqlite3_file *pWalFd; /* File handle for WAL file */
u32 iCallback; /* Value to pass to log callback (or 0) */
i64 mxWalSize; /* Truncate WAL to this size upon reset */
int nWiData; /* Size of array apWiData */
@@ -58022,6 +63988,7 @@ struct Wal {
u8 truncateOnCommit; /* True to truncate WAL file on commit */
u8 syncHeader; /* Fsync the WAL header if true */
u8 padToSectorBoundary; /* Pad transactions out to the next sector */
+ u8 bShmUnreliable; /* SHM content is read-only and unreliable */
WalIndexHdr hdr; /* Wal-index header for current transaction */
u32 minFrame; /* Ignore wal frames before this one */
u32 iReCksum; /* On commit, recalculate checksums from here */
@@ -58111,18 +64078,27 @@ struct WalIterator {
** is broken into pages of WALINDEX_PGSZ bytes. Wal-index pages are
** numbered from zero.
**
+** If the wal-index is currently smaller the iPage pages then the size
+** of the wal-index might be increased, but only if it is safe to do
+** so. It is safe to enlarge the wal-index if pWal->writeLock is true
+** or pWal->exclusiveMode==WAL_HEAPMEMORY_MODE.
+**
** If this call is successful, *ppPage is set to point to the wal-index
** page and SQLITE_OK is returned. If an error (an OOM or VFS error) occurs,
** then an SQLite error code is returned and *ppPage is set to 0.
*/
-static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
+static SQLITE_NOINLINE int walIndexPageRealloc(
+ Wal *pWal, /* The WAL context */
+ int iPage, /* The page we seek */
+ volatile u32 **ppPage /* Write the page pointer here */
+){
int rc = SQLITE_OK;
/* Enlarge the pWal->apWiData[] array if required */
if( pWal->nWiData<=iPage ){
- int nByte = sizeof(u32*)*(iPage+1);
+ tdsqlite3_int64 nByte = sizeof(u32*)*(iPage+1);
volatile u32 **apNew;
- apNew = (volatile u32 **)sqlite3_realloc64((void *)pWal->apWiData, nByte);
+ apNew = (volatile u32 **)tdsqlite3_realloc64((void *)pWal->apWiData, nByte);
if( !apNew ){
*ppPage = 0;
return SQLITE_NOMEM_BKPT;
@@ -58134,16 +64110,19 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
}
/* Request a pointer to the required page from the VFS */
- if( pWal->apWiData[iPage]==0 ){
- if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
- pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ);
- if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT;
- }else{
- rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
- pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
- );
+ assert( pWal->apWiData[iPage]==0 );
+ if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
+ pWal->apWiData[iPage] = (u32 volatile *)tdsqlite3MallocZero(WALINDEX_PGSZ);
+ if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT;
+ }else{
+ rc = tdsqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ,
+ pWal->writeLock, (void volatile **)&pWal->apWiData[iPage]
+ );
+ assert( pWal->apWiData[iPage]!=0 || rc!=SQLITE_OK || pWal->writeLock==0 );
+ testcase( pWal->apWiData[iPage]==0 && rc==SQLITE_OK );
+ if( (rc&0xff)==SQLITE_READONLY ){
+ pWal->readOnly |= WAL_SHM_RDONLY;
if( rc==SQLITE_READONLY ){
- pWal->readOnly |= WAL_SHM_RDONLY;
rc = SQLITE_OK;
}
}
@@ -58153,6 +64132,16 @@ static int walIndexPage(Wal *pWal, int iPage, volatile u32 **ppPage){
assert( iPage==0 || *ppPage || rc!=SQLITE_OK );
return rc;
}
+static int walIndexPage(
+ Wal *pWal, /* The WAL context */
+ int iPage, /* The page we seek */
+ volatile u32 **ppPage /* Write the page pointer here */
+){
+ if( pWal->nWiData<=iPage || (*ppPage = pWal->apWiData[iPage])==0 ){
+ return walIndexPageRealloc(pWal, iPage, ppPage);
+ }
+ return SQLITE_OK;
+}
/*
** Return a pointer to the WalCkptInfo structure in the wal-index.
@@ -58211,6 +64200,7 @@ static void walChecksumBytes(
assert( nByte>=8 );
assert( (nByte&0x00000007)==0 );
+ assert( nByte<=65536 );
if( nativeCksum ){
do {
@@ -58231,7 +64221,7 @@ static void walChecksumBytes(
static void walShmBarrier(Wal *pWal){
if( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE ){
- sqlite3OsShmBarrier(pWal->pDbFd);
+ tdsqlite3OsShmBarrier(pWal->pDbFd);
}
}
@@ -58276,8 +64266,8 @@ static void walEncodeFrame(
int nativeCksum; /* True for native byte-order checksums */
u32 *aCksum = pWal->hdr.aFrameCksum;
assert( WAL_FRAME_HDRSIZE==24 );
- sqlite3Put4byte(&aFrame[0], iPage);
- sqlite3Put4byte(&aFrame[4], nTruncate);
+ tdsqlite3Put4byte(&aFrame[0], iPage);
+ tdsqlite3Put4byte(&aFrame[4], nTruncate);
if( pWal->iReCksum==0 ){
memcpy(&aFrame[8], pWal->hdr.aSalt, 8);
@@ -58285,8 +64275,8 @@ static void walEncodeFrame(
walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum);
walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum);
- sqlite3Put4byte(&aFrame[16], aCksum[0]);
- sqlite3Put4byte(&aFrame[20], aCksum[1]);
+ tdsqlite3Put4byte(&aFrame[16], aCksum[0]);
+ tdsqlite3Put4byte(&aFrame[20], aCksum[1]);
}else{
memset(&aFrame[8], 0, 16);
}
@@ -58318,7 +64308,7 @@ static int walDecodeFrame(
/* A frame is only valid if the page number is creater than zero.
*/
- pgno = sqlite3Get4byte(&aFrame[0]);
+ pgno = tdsqlite3Get4byte(&aFrame[0]);
if( pgno==0 ){
return 0;
}
@@ -58331,8 +64321,8 @@ static int walDecodeFrame(
nativeCksum = (pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN);
walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum);
walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum);
- if( aCksum[0]!=sqlite3Get4byte(&aFrame[16])
- || aCksum[1]!=sqlite3Get4byte(&aFrame[20])
+ if( aCksum[0]!=tdsqlite3Get4byte(&aFrame[16])
+ || aCksum[1]!=tdsqlite3Get4byte(&aFrame[20])
){
/* Checksum failed. */
return 0;
@@ -58342,7 +64332,7 @@ static int walDecodeFrame(
** and the new database size.
*/
*piPage = pgno;
- *pnTruncate = sqlite3Get4byte(&aFrame[4]);
+ *pnTruncate = tdsqlite3Get4byte(&aFrame[4]);
return 1;
}
@@ -58361,7 +64351,7 @@ static const char *walLockName(int lockIdx){
return "RECOVER-LOCK";
}else{
static char zName[15];
- sqlite3_snprintf(sizeof(zName), zName, "READ-LOCK[%d]",
+ tdsqlite3_snprintf(sizeof(zName), zName, "READ-LOCK[%d]",
lockIdx-WAL_READ_LOCK(0));
return zName;
}
@@ -58379,7 +64369,7 @@ static const char *walLockName(int lockIdx){
static int walLockShared(Wal *pWal, int lockIdx){
int rc;
if( pWal->exclusiveMode ) return SQLITE_OK;
- rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1,
+ rc = tdsqlite3OsShmLock(pWal->pDbFd, lockIdx, 1,
SQLITE_SHM_LOCK | SQLITE_SHM_SHARED);
WALTRACE(("WAL%p: acquire SHARED-%s %s\n", pWal,
walLockName(lockIdx), rc ? "failed" : "ok"));
@@ -58388,14 +64378,14 @@ static int walLockShared(Wal *pWal, int lockIdx){
}
static void walUnlockShared(Wal *pWal, int lockIdx){
if( pWal->exclusiveMode ) return;
- (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1,
+ (void)tdsqlite3OsShmLock(pWal->pDbFd, lockIdx, 1,
SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED);
WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx)));
}
static int walLockExclusive(Wal *pWal, int lockIdx, int n){
int rc;
if( pWal->exclusiveMode ) return SQLITE_OK;
- rc = sqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
+ rc = tdsqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE);
WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal,
walLockName(lockIdx), n, rc ? "failed" : "ok"));
@@ -58404,7 +64394,7 @@ static int walLockExclusive(Wal *pWal, int lockIdx, int n){
}
static void walUnlockExclusive(Wal *pWal, int lockIdx, int n){
if( pWal->exclusiveMode ) return;
- (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
+ (void)tdsqlite3OsShmLock(pWal->pDbFd, lockIdx, n,
SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE);
WALTRACE(("WAL%p: release EXCLUSIVE-%s cnt=%d\n", pWal,
walLockName(lockIdx), n));
@@ -58424,48 +64414,51 @@ static int walNextHash(int iPriorHash){
return (iPriorHash+1)&(HASHTABLE_NSLOT-1);
}
+/*
+** An instance of the WalHashLoc object is used to describe the location
+** of a page hash table in the wal-index. This becomes the return value
+** from walHashGet().
+*/
+typedef struct WalHashLoc WalHashLoc;
+struct WalHashLoc {
+ volatile ht_slot *aHash; /* Start of the wal-index hash table */
+ volatile u32 *aPgno; /* aPgno[1] is the page of first frame indexed */
+ u32 iZero; /* One less than the frame number of first indexed*/
+};
+
/*
** Return pointers to the hash table and page number array stored on
** page iHash of the wal-index. The wal-index is broken into 32KB pages
** numbered starting from 0.
**
-** Set output variable *paHash to point to the start of the hash table
-** in the wal-index file. Set *piZero to one less than the frame
+** Set output variable pLoc->aHash to point to the start of the hash table
+** in the wal-index file. Set pLoc->iZero to one less than the frame
** number of the first frame indexed by this hash table. If a
** slot in the hash table is set to N, it refers to frame number
-** (*piZero+N) in the log.
+** (pLoc->iZero+N) in the log.
**
-** Finally, set *paPgno so that *paPgno[1] is the page number of the
-** first frame indexed by the hash table, frame (*piZero+1).
+** Finally, set pLoc->aPgno so that pLoc->aPgno[1] is the page number of the
+** first frame indexed by the hash table, frame (pLoc->iZero+1).
*/
static int walHashGet(
Wal *pWal, /* WAL handle */
int iHash, /* Find the iHash'th table */
- volatile ht_slot **paHash, /* OUT: Pointer to hash index */
- volatile u32 **paPgno, /* OUT: Pointer to page number array */
- u32 *piZero /* OUT: Frame associated with *paPgno[0] */
+ WalHashLoc *pLoc /* OUT: Hash table location */
){
int rc; /* Return code */
- volatile u32 *aPgno;
- rc = walIndexPage(pWal, iHash, &aPgno);
+ rc = walIndexPage(pWal, iHash, &pLoc->aPgno);
assert( rc==SQLITE_OK || iHash>0 );
if( rc==SQLITE_OK ){
- u32 iZero;
- volatile ht_slot *aHash;
-
- aHash = (volatile ht_slot *)&aPgno[HASHTABLE_NPAGE];
+ pLoc->aHash = (volatile ht_slot *)&pLoc->aPgno[HASHTABLE_NPAGE];
if( iHash==0 ){
- aPgno = &aPgno[WALINDEX_HDR_SIZE/sizeof(u32)];
- iZero = 0;
+ pLoc->aPgno = &pLoc->aPgno[WALINDEX_HDR_SIZE/sizeof(u32)];
+ pLoc->iZero = 0;
}else{
- iZero = HASHTABLE_NPAGE_ONE + (iHash-1)*HASHTABLE_NPAGE;
+ pLoc->iZero = HASHTABLE_NPAGE_ONE + (iHash-1)*HASHTABLE_NPAGE;
}
-
- *paPgno = &aPgno[-1];
- *paHash = aHash;
- *piZero = iZero;
+ pLoc->aPgno = &pLoc->aPgno[-1];
}
return rc;
}
@@ -58511,12 +64504,11 @@ static u32 walFramePgno(Wal *pWal, u32 iFrame){
** actually needed.
*/
static void walCleanupHash(Wal *pWal){
- volatile ht_slot *aHash = 0; /* Pointer to hash table to clear */
- volatile u32 *aPgno = 0; /* Page number array for hash table */
- u32 iZero = 0; /* frame == (aHash[x]+iZero) */
+ WalHashLoc sLoc; /* Hash table location */
int iLimit = 0; /* Zero values greater than this */
int nByte; /* Number of bytes to zero in aPgno[] */
int i; /* Used to iterate through aHash[] */
+ int rc; /* Return code form walHashGet() */
assert( pWal->writeLock );
testcase( pWal->hdr.mxFrame==HASHTABLE_NPAGE_ONE-1 );
@@ -58527,28 +64519,29 @@ static void walCleanupHash(Wal *pWal){
/* Obtain pointers to the hash-table and page-number array containing
** the entry that corresponds to frame pWal->hdr.mxFrame. It is guaranteed
- ** that the page said hash-table and array reside on is already mapped.
+ ** that the page said hash-table and array reside on is already mapped.(1)
*/
assert( pWal->nWiData>walFramePage(pWal->hdr.mxFrame) );
assert( pWal->apWiData[walFramePage(pWal->hdr.mxFrame)] );
- walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &aHash, &aPgno, &iZero);
+ rc = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc);
+ if( NEVER(rc) ) return; /* Defense-in-depth, in case (1) above is wrong */
/* Zero all hash-table entries that correspond to frame numbers greater
** than pWal->hdr.mxFrame.
*/
- iLimit = pWal->hdr.mxFrame - iZero;
+ iLimit = pWal->hdr.mxFrame - sLoc.iZero;
assert( iLimit>0 );
for(i=0; i<HASHTABLE_NSLOT; i++){
- if( aHash[i]>iLimit ){
- aHash[i] = 0;
+ if( sLoc.aHash[i]>iLimit ){
+ sLoc.aHash[i] = 0;
}
}
/* Zero the entries in the aPgno array that correspond to frames with
** frame numbers greater than pWal->hdr.mxFrame.
*/
- nByte = (int)((char *)aHash - (char *)&aPgno[iLimit+1]);
- memset((void *)&aPgno[iLimit+1], 0, nByte);
+ nByte = (int)((char *)sLoc.aHash - (char *)&sLoc.aPgno[iLimit+1]);
+ memset((void *)&sLoc.aPgno[iLimit+1], 0, nByte);
#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT
/* Verify that the every entry in the mapping region is still reachable
@@ -58558,10 +64551,10 @@ static void walCleanupHash(Wal *pWal){
int j; /* Loop counter */
int iKey; /* Hash key */
for(j=1; j<=iLimit; j++){
- for(iKey=walHash(aPgno[j]); aHash[iKey]; iKey=walNextHash(iKey)){
- if( aHash[iKey]==j ) break;
+ for(iKey=walHash(sLoc.aPgno[j]);sLoc.aHash[iKey];iKey=walNextHash(iKey)){
+ if( sLoc.aHash[iKey]==j ) break;
}
- assert( aHash[iKey]==j );
+ assert( sLoc.aHash[iKey]==j );
}
}
#endif /* SQLITE_ENABLE_EXPENSIVE_ASSERT */
@@ -58574,11 +64567,9 @@ static void walCleanupHash(Wal *pWal){
*/
static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){
int rc; /* Return code */
- u32 iZero = 0; /* One less than frame number of aPgno[1] */
- volatile u32 *aPgno = 0; /* Page number array */
- volatile ht_slot *aHash = 0; /* Hash table */
+ WalHashLoc sLoc; /* Wal-index hash table location */
- rc = walHashGet(pWal, walFramePage(iFrame), &aHash, &aPgno, &iZero);
+ rc = walHashGet(pWal, walFramePage(iFrame), &sLoc);
/* Assuming the wal-index file was successfully mapped, populate the
** page number array and hash table entry.
@@ -58588,15 +64579,16 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){
int idx; /* Value to write to hash-table slot */
int nCollide; /* Number of hash collisions */
- idx = iFrame - iZero;
+ idx = iFrame - sLoc.iZero;
assert( idx <= HASHTABLE_NSLOT/2 + 1 );
/* If this is the first entry to be added to this hash-table, zero the
** entire hash table and aPgno[] array before proceeding.
*/
if( idx==1 ){
- int nByte = (int)((u8 *)&aHash[HASHTABLE_NSLOT] - (u8 *)&aPgno[1]);
- memset((void*)&aPgno[1], 0, nByte);
+ int nByte = (int)((u8 *)&sLoc.aHash[HASHTABLE_NSLOT]
+ - (u8 *)&sLoc.aPgno[1]);
+ memset((void*)&sLoc.aPgno[1], 0, nByte);
}
/* If the entry in aPgno[] is already set, then the previous writer
@@ -58605,18 +64597,18 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){
** Remove the remnants of that writers uncommitted transaction from
** the hash-table before writing any new entries.
*/
- if( aPgno[idx] ){
+ if( sLoc.aPgno[idx] ){
walCleanupHash(pWal);
- assert( !aPgno[idx] );
+ assert( !sLoc.aPgno[idx] );
}
/* Write the aPgno[] array entry and the hash-table slot. */
nCollide = idx;
- for(iKey=walHash(iPage); aHash[iKey]; iKey=walNextHash(iKey)){
+ for(iKey=walHash(iPage); sLoc.aHash[iKey]; iKey=walNextHash(iKey)){
if( (nCollide--)==0 ) return SQLITE_CORRUPT_BKPT;
}
- aPgno[idx] = iPage;
- aHash[iKey] = (ht_slot)idx;
+ sLoc.aPgno[idx] = iPage;
+ sLoc.aHash[iKey] = (ht_slot)idx;
#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT
/* Verify that the number of entries in the hash table exactly equals
@@ -58625,7 +64617,7 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){
{
int i; /* Loop counter */
int nEntry = 0; /* Number of entries in the hash table */
- for(i=0; i<HASHTABLE_NSLOT; i++){ if( aHash[i] ) nEntry++; }
+ for(i=0; i<HASHTABLE_NSLOT; i++){ if( sLoc.aHash[i] ) nEntry++; }
assert( nEntry==idx );
}
@@ -58637,10 +64629,12 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){
if( (idx&0x3ff)==0 ){
int i; /* Loop counter */
for(i=1; i<=idx; i++){
- for(iKey=walHash(aPgno[i]); aHash[iKey]; iKey=walNextHash(iKey)){
- if( aHash[iKey]==i ) break;
+ for(iKey=walHash(sLoc.aPgno[i]);
+ sLoc.aHash[iKey];
+ iKey=walNextHash(iKey)){
+ if( sLoc.aHash[iKey]==i ) break;
}
- assert( aHash[iKey]==i );
+ assert( sLoc.aHash[iKey]==i );
}
}
#endif /* SQLITE_ENABLE_EXPENSIVE_ASSERT */
@@ -58666,7 +64660,6 @@ static int walIndexRecover(Wal *pWal){
i64 nSize; /* Size of log file */
u32 aFrameCksum[2] = {0, 0};
int iLock; /* Lock offset to lock for checkpoint */
- int nLock; /* Number of locks to hold */
/* Obtain an exclusive lock on all byte in the locking range not already
** locked by the caller. The caller is guaranteed to have locked the
@@ -58679,16 +64672,22 @@ static int walIndexRecover(Wal *pWal){
assert( WAL_CKPT_LOCK==WAL_ALL_BUT_WRITE );
assert( pWal->writeLock );
iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock;
- nLock = SQLITE_SHM_NLOCK - iLock;
- rc = walLockExclusive(pWal, iLock, nLock);
+ rc = walLockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock);
+ if( rc==SQLITE_OK ){
+ rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
+ if( rc!=SQLITE_OK ){
+ walUnlockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock);
+ }
+ }
if( rc ){
return rc;
}
+
WALTRACE(("WAL%p: recovery begin...\n", pWal));
memset(&pWal->hdr, 0, sizeof(WalIndexHdr));
- rc = sqlite3OsFileSize(pWal->pWalFd, &nSize);
+ rc = tdsqlite3OsFileSize(pWal->pWalFd, &nSize);
if( rc!=SQLITE_OK ){
goto recovery_error;
}
@@ -58706,7 +64705,7 @@ static int walIndexRecover(Wal *pWal){
int isValid; /* True if this frame is valid */
/* Read in the WAL header. */
- rc = sqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0);
+ rc = tdsqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0);
if( rc!=SQLITE_OK ){
goto recovery_error;
}
@@ -58716,8 +64715,8 @@ static int walIndexRecover(Wal *pWal){
** data. Similarly, if the 'magic' value is invalid, ignore the whole
** WAL file.
*/
- magic = sqlite3Get4byte(&aBuf[0]);
- szPage = sqlite3Get4byte(&aBuf[8]);
+ magic = tdsqlite3Get4byte(&aBuf[0]);
+ szPage = tdsqlite3Get4byte(&aBuf[8]);
if( (magic&0xFFFFFFFE)!=WAL_MAGIC
|| szPage&(szPage-1)
|| szPage>SQLITE_MAX_PAGE_SIZE
@@ -58727,22 +64726,22 @@ static int walIndexRecover(Wal *pWal){
}
pWal->hdr.bigEndCksum = (u8)(magic&0x00000001);
pWal->szPage = szPage;
- pWal->nCkpt = sqlite3Get4byte(&aBuf[12]);
+ pWal->nCkpt = tdsqlite3Get4byte(&aBuf[12]);
memcpy(&pWal->hdr.aSalt, &aBuf[16], 8);
/* Verify that the WAL header checksum is correct */
walChecksumBytes(pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN,
aBuf, WAL_HDRSIZE-2*4, 0, pWal->hdr.aFrameCksum
);
- if( pWal->hdr.aFrameCksum[0]!=sqlite3Get4byte(&aBuf[24])
- || pWal->hdr.aFrameCksum[1]!=sqlite3Get4byte(&aBuf[28])
+ if( pWal->hdr.aFrameCksum[0]!=tdsqlite3Get4byte(&aBuf[24])
+ || pWal->hdr.aFrameCksum[1]!=tdsqlite3Get4byte(&aBuf[28])
){
goto finished;
}
/* Verify that the version number on the WAL format is one that
** are able to understand */
- version = sqlite3Get4byte(&aBuf[4]);
+ version = tdsqlite3Get4byte(&aBuf[4]);
if( version!=WAL_MAX_VERSION ){
rc = SQLITE_CANTOPEN_BKPT;
goto finished;
@@ -58750,7 +64749,7 @@ static int walIndexRecover(Wal *pWal){
/* Malloc a buffer to read frames into. */
szFrame = szPage + WAL_FRAME_HDRSIZE;
- aFrame = (u8 *)sqlite3_malloc64(szFrame);
+ aFrame = (u8 *)tdsqlite3_malloc64(szFrame);
if( !aFrame ){
rc = SQLITE_NOMEM_BKPT;
goto recovery_error;
@@ -58765,7 +64764,7 @@ static int walIndexRecover(Wal *pWal){
/* Read and decode the next log frame. */
iFrame++;
- rc = sqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOffset);
+ rc = tdsqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOffset);
if( rc!=SQLITE_OK ) break;
isValid = walDecodeFrame(pWal, &pgno, &nTruncate, aData, aFrame);
if( !isValid ) break;
@@ -58784,7 +64783,7 @@ static int walIndexRecover(Wal *pWal){
}
}
- sqlite3_free(aFrame);
+ tdsqlite3_free(aFrame);
}
finished:
@@ -58807,12 +64806,12 @@ finished:
if( pWal->hdr.mxFrame ) pInfo->aReadMark[1] = pWal->hdr.mxFrame;
/* If more than one frame was recovered from the log file, report an
- ** event via sqlite3_log(). This is to help with identifying performance
+ ** event via tdsqlite3_log(). This is to help with identifying performance
** problems caused by applications routinely shutting down without
** checkpointing the log file.
*/
if( pWal->hdr.nPage ){
- sqlite3_log(SQLITE_NOTICE_RECOVER_WAL,
+ tdsqlite3_log(SQLITE_NOTICE_RECOVER_WAL,
"recovered %d frames from WAL file %s",
pWal->hdr.mxFrame, pWal->zWalName
);
@@ -58821,7 +64820,8 @@ finished:
recovery_error:
WALTRACE(("WAL%p: recovery %s\n", pWal, rc ? "failed" : "ok"));
- walUnlockExclusive(pWal, iLock, nLock);
+ walUnlockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock);
+ walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
return rc;
}
@@ -58829,14 +64829,15 @@ recovery_error:
** Close an open wal-index.
*/
static void walIndexClose(Wal *pWal, int isDelete){
- if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ){
+ if( pWal->exclusiveMode==WAL_HEAPMEMORY_MODE || pWal->bShmUnreliable ){
int i;
for(i=0; i<pWal->nWiData; i++){
- sqlite3_free((void *)pWal->apWiData[i]);
+ tdsqlite3_free((void *)pWal->apWiData[i]);
pWal->apWiData[i] = 0;
}
- }else{
- sqlite3OsShmUnmap(pWal->pDbFd, isDelete);
+ }
+ if( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE ){
+ tdsqlite3OsShmUnmap(pWal->pDbFd, isDelete);
}
}
@@ -58855,9 +64856,9 @@ static void walIndexClose(Wal *pWal, int isDelete){
** *ppWal is set to point to a new WAL handle. If an error occurs,
** an SQLite error code is returned and *ppWal is left unmodified.
*/
-SQLITE_PRIVATE int sqlite3WalOpen(
- sqlite3_vfs *pVfs, /* vfs module to open wal and wal-index */
- sqlite3_file *pDbFd, /* The open database file */
+SQLITE_PRIVATE int tdsqlite3WalOpen(
+ tdsqlite3_vfs *pVfs, /* vfs module to open wal and wal-index */
+ tdsqlite3_file *pDbFd, /* The open database file */
const char *zWalName, /* Name of the WAL file */
int bNoShm, /* True to run in heap-memory mode */
i64 mxWalSize, /* Truncate WAL to this size on reset */
@@ -58888,13 +64889,13 @@ SQLITE_PRIVATE int sqlite3WalOpen(
/* Allocate an instance of struct Wal to return. */
*ppWal = 0;
- pRet = (Wal*)sqlite3MallocZero(sizeof(Wal) + pVfs->szOsFile);
+ pRet = (Wal*)tdsqlite3MallocZero(sizeof(Wal) + pVfs->szOsFile);
if( !pRet ){
return SQLITE_NOMEM_BKPT;
}
pRet->pVfs = pVfs;
- pRet->pWalFd = (sqlite3_file *)&pRet[1];
+ pRet->pWalFd = (tdsqlite3_file *)&pRet[1];
pRet->pDbFd = pDbFd;
pRet->readLock = -1;
pRet->mxWalSize = mxWalSize;
@@ -58905,17 +64906,17 @@ SQLITE_PRIVATE int sqlite3WalOpen(
/* Open file handle on the write-ahead log file. */
flags = (SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_WAL);
- rc = sqlite3OsOpen(pVfs, zWalName, pRet->pWalFd, flags, &flags);
+ rc = tdsqlite3OsOpen(pVfs, zWalName, pRet->pWalFd, flags, &flags);
if( rc==SQLITE_OK && flags&SQLITE_OPEN_READONLY ){
pRet->readOnly = WAL_RDONLY;
}
if( rc!=SQLITE_OK ){
walIndexClose(pRet, 0);
- sqlite3OsClose(pRet->pWalFd);
- sqlite3_free(pRet);
+ tdsqlite3OsClose(pRet->pWalFd);
+ tdsqlite3_free(pRet);
}else{
- int iDC = sqlite3OsDeviceCharacteristics(pDbFd);
+ int iDC = tdsqlite3OsDeviceCharacteristics(pDbFd);
if( iDC & SQLITE_IOCAP_SEQUENTIAL ){ pRet->syncHeader = 0; }
if( iDC & SQLITE_IOCAP_POWERSAFE_OVERWRITE ){
pRet->padToSectorBoundary = 0;
@@ -58929,7 +64930,7 @@ SQLITE_PRIVATE int sqlite3WalOpen(
/*
** Change the size to which the WAL file is trucated on each reset.
*/
-SQLITE_PRIVATE void sqlite3WalLimit(Wal *pWal, i64 iLimit){
+SQLITE_PRIVATE void tdsqlite3WalLimit(Wal *pWal, i64 iLimit){
if( pWal ) pWal->mxWalSize = iLimit;
}
@@ -59117,13 +65118,14 @@ static void walMergesort(
** Free an iterator allocated by walIteratorInit().
*/
static void walIteratorFree(WalIterator *p){
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
/*
** Construct a WalInterator object that can be used to loop over all
-** pages in the WAL in ascending order. The caller must hold the checkpoint
-** lock.
+** pages in the WAL following frame nBackfill in ascending order. Frames
+** nBackfill or earlier may be included - excluding them is an optimization
+** only. The caller must hold the checkpoint lock.
**
** On success, make *pp point to the newly allocated WalInterator object
** return SQLITE_OK. Otherwise, return an error code. If this routine
@@ -59132,11 +65134,11 @@ static void walIteratorFree(WalIterator *p){
** The calling routine should invoke walIteratorFree() to destroy the
** WalIterator object when it has finished with it.
*/
-static int walIteratorInit(Wal *pWal, WalIterator **pp){
+static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){
WalIterator *p; /* Return value */
int nSegment; /* Number of segments to merge */
u32 iLast; /* Last frame in log */
- int nByte; /* Number of bytes to allocate */
+ tdsqlite3_int64 nByte; /* Number of bytes to allocate */
int i; /* Iterator variable */
ht_slot *aTmp; /* Temp space used by merge-sort */
int rc = SQLITE_OK; /* Return Code */
@@ -59152,7 +65154,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){
nByte = sizeof(WalIterator)
+ (nSegment-1)*sizeof(struct WalSegment)
+ iLast*sizeof(ht_slot);
- p = (WalIterator *)sqlite3_malloc64(nByte);
+ p = (WalIterator *)tdsqlite3_malloc64(nByte);
if( !p ){
return SQLITE_NOMEM_BKPT;
}
@@ -59162,47 +65164,46 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){
/* Allocate temporary space used by the merge-sort routine. This block
** of memory will be freed before this function returns.
*/
- aTmp = (ht_slot *)sqlite3_malloc64(
+ aTmp = (ht_slot *)tdsqlite3_malloc64(
sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast)
);
if( !aTmp ){
rc = SQLITE_NOMEM_BKPT;
}
- for(i=0; rc==SQLITE_OK && i<nSegment; i++){
- volatile ht_slot *aHash;
- u32 iZero;
- volatile u32 *aPgno;
+ for(i=walFramePage(nBackfill+1); rc==SQLITE_OK && i<nSegment; i++){
+ WalHashLoc sLoc;
- rc = walHashGet(pWal, i, &aHash, &aPgno, &iZero);
+ rc = walHashGet(pWal, i, &sLoc);
if( rc==SQLITE_OK ){
int j; /* Counter variable */
int nEntry; /* Number of entries in this segment */
ht_slot *aIndex; /* Sorted index for this segment */
- aPgno++;
+ sLoc.aPgno++;
if( (i+1)==nSegment ){
- nEntry = (int)(iLast - iZero);
+ nEntry = (int)(iLast - sLoc.iZero);
}else{
- nEntry = (int)((u32*)aHash - (u32*)aPgno);
+ nEntry = (int)((u32*)sLoc.aHash - (u32*)sLoc.aPgno);
}
- aIndex = &((ht_slot *)&p->aSegment[p->nSegment])[iZero];
- iZero++;
+ aIndex = &((ht_slot *)&p->aSegment[p->nSegment])[sLoc.iZero];
+ sLoc.iZero++;
for(j=0; j<nEntry; j++){
aIndex[j] = (ht_slot)j;
}
- walMergesort((u32 *)aPgno, aTmp, aIndex, &nEntry);
- p->aSegment[i].iZero = iZero;
+ walMergesort((u32 *)sLoc.aPgno, aTmp, aIndex, &nEntry);
+ p->aSegment[i].iZero = sLoc.iZero;
p->aSegment[i].nEntry = nEntry;
p->aSegment[i].aIndex = aIndex;
- p->aSegment[i].aPgno = (u32 *)aPgno;
+ p->aSegment[i].aPgno = (u32 *)sLoc.aPgno;
}
}
- sqlite3_free(aTmp);
+ tdsqlite3_free(aTmp);
if( rc!=SQLITE_OK ){
walIteratorFree(p);
+ p = 0;
}
*pp = p;
return rc;
@@ -59251,7 +65252,7 @@ static int walPagesize(Wal *pWal){
**
** The value of parameter salt1 is used as the aSalt[1] value in the
** new wal-index header. It should be passed a pseudo-random value (i.e.
-** one obtained from sqlite3_randomness()).
+** one obtained from tdsqlite3_randomness()).
*/
static void walRestartHdr(Wal *pWal, u32 salt1){
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
@@ -59259,7 +65260,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){
u32 *aSalt = pWal->hdr.aSalt; /* Big-endian salt values */
pWal->nCkpt++;
pWal->hdr.mxFrame = 0;
- sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0]));
+ tdsqlite3Put4byte((u8*)&aSalt[0], 1 + tdsqlite3Get4byte((u8*)&aSalt[0]));
memcpy(&pWal->hdr.aSalt[1], &salt1, 4);
walIndexWriteHdr(pWal);
pInfo->nBackfill = 0;
@@ -59271,7 +65272,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){
/*
** Copy as much content as we can from the WAL back into the database file
-** in response to an sqlite3_wal_checkpoint() request or the equivalent.
+** in response to an tdsqlite3_wal_checkpoint() request or the equivalent.
**
** The amount of information copies from WAL to database might be limited
** by active readers. This routine will never overwrite a database page
@@ -59302,6 +65303,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){
*/
static int walCheckpoint(
Wal *pWal, /* Wal connection */
+ tdsqlite3 *db, /* Check for interrupts on this handle */
int eMode, /* One of PASSIVE, FULL or RESTART */
int (*xBusy)(void*), /* Function to call when busy */
void *pBusyArg, /* Context argument for xBusyHandler */
@@ -59324,13 +65326,6 @@ static int walCheckpoint(
pInfo = walCkptInfo(pWal);
if( pInfo->nBackfill<pWal->hdr.mxFrame ){
- /* Allocate the iterator */
- rc = walIteratorInit(pWal, &pIter);
- if( rc!=SQLITE_OK ){
- return rc;
- }
- assert( pIter );
-
/* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked
** in the SQLITE_CHECKPOINT_PASSIVE mode. */
assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 );
@@ -59350,7 +65345,19 @@ static int walCheckpoint(
** not decreasing it. So assuming either that either the "old" or
** "new" version of the value is read, and not some arbitrary value
** that would never be written by a real client, things are still
- ** safe. */
+ ** safe.
+ **
+ ** Astute readers have pointed out that the assumption stated in the
+ ** last sentence of the previous paragraph is not guaranteed to be
+ ** true for all conforming systems. However, the assumption is true
+ ** for all compilers and architectures in common use today (circa
+ ** 2019-11-27) and the alternatives are both slow and complex, and
+ ** so we will continue to go with the current design for now. If this
+ ** bothers you, or if you really are running on a system where aligned
+ ** 32-bit reads and writes are not atomic, then you can simply avoid
+ ** the use of WAL mode, or only use WAL mode together with
+ ** PRAGMA locking_mode=EXCLUSIVE and all will be well.
+ */
u32 y = pInfo->aReadMark[i];
if( mxSafeFrame>y ){
assert( y<=pWal->hdr.mxFrame );
@@ -59367,27 +65374,31 @@ static int walCheckpoint(
}
}
- if( pInfo->nBackfill<mxSafeFrame
+ /* Allocate the iterator */
+ if( pInfo->nBackfill<mxSafeFrame ){
+ rc = walIteratorInit(pWal, pInfo->nBackfill, &pIter);
+ assert( rc==SQLITE_OK || pIter==0 );
+ }
+
+ if( pIter
&& (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0),1))==SQLITE_OK
){
- i64 nSize; /* Current size of database file */
u32 nBackfill = pInfo->nBackfill;
pInfo->nBackfillAttempted = mxSafeFrame;
/* Sync the WAL to disk */
- if( sync_flags ){
- rc = sqlite3OsSync(pWal->pWalFd, sync_flags);
- }
+ rc = tdsqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags));
/* If the database may grow as a result of this checkpoint, hint
** about the eventual size of the db file to the VFS layer.
*/
if( rc==SQLITE_OK ){
i64 nReq = ((i64)mxPage * szPage);
- rc = sqlite3OsFileSize(pWal->pDbFd, &nSize);
+ i64 nSize; /* Current size of database file */
+ rc = tdsqlite3OsFileSize(pWal->pDbFd, &nSize);
if( rc==SQLITE_OK && nSize<nReq ){
- sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq);
+ tdsqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq);
}
}
@@ -59396,16 +65407,20 @@ static int walCheckpoint(
while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){
i64 iOffset;
assert( walFramePgno(pWal, iFrame)==iDbpage );
+ if( db->u1.isInterrupted ){
+ rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT;
+ break;
+ }
if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){
continue;
}
iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE;
/* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */
- rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset);
+ rc = tdsqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset);
if( rc!=SQLITE_OK ) break;
iOffset = (iDbpage-1)*(i64)szPage;
testcase( IS_BIG_INT(iOffset) );
- rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset);
+ rc = tdsqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset);
if( rc!=SQLITE_OK ) break;
}
@@ -59414,12 +65429,16 @@ static int walCheckpoint(
if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){
i64 szDb = pWal->hdr.nPage*(i64)szPage;
testcase( IS_BIG_INT(szDb) );
- rc = sqlite3OsTruncate(pWal->pDbFd, szDb);
- if( rc==SQLITE_OK && sync_flags ){
- rc = sqlite3OsSync(pWal->pDbFd, sync_flags);
+ rc = tdsqlite3OsTruncate(pWal->pDbFd, szDb);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3OsSync(pWal->pDbFd, CKPT_SYNC_FLAGS(sync_flags));
}
}
if( rc==SQLITE_OK ){
+ rc = tdsqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_DONE, 0);
+ if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
+ }
+ if( rc==SQLITE_OK ){
pInfo->nBackfill = mxSafeFrame;
}
}
@@ -59446,7 +65465,7 @@ static int walCheckpoint(
rc = SQLITE_BUSY;
}else if( eMode>=SQLITE_CHECKPOINT_RESTART ){
u32 salt1;
- sqlite3_randomness(4, &salt1);
+ tdsqlite3_randomness(4, &salt1);
assert( pInfo->nBackfill==pWal->hdr.mxFrame );
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1);
if( rc==SQLITE_OK ){
@@ -59465,7 +65484,7 @@ static int walCheckpoint(
** file-system. To avoid this, update the wal-index header to
** indicate that the log file contains zero valid frames. */
walRestartHdr(pWal, salt1);
- rc = sqlite3OsTruncate(pWal->pWalFd, 0);
+ rc = tdsqlite3OsTruncate(pWal->pWalFd, 0);
}
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
}
@@ -59484,22 +65503,23 @@ static int walCheckpoint(
static void walLimitSize(Wal *pWal, i64 nMax){
i64 sz;
int rx;
- sqlite3BeginBenignMalloc();
- rx = sqlite3OsFileSize(pWal->pWalFd, &sz);
+ tdsqlite3BeginBenignMalloc();
+ rx = tdsqlite3OsFileSize(pWal->pWalFd, &sz);
if( rx==SQLITE_OK && (sz > nMax ) ){
- rx = sqlite3OsTruncate(pWal->pWalFd, nMax);
+ rx = tdsqlite3OsTruncate(pWal->pWalFd, nMax);
}
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
if( rx ){
- sqlite3_log(rx, "cannot limit WAL size: %s", pWal->zWalName);
+ tdsqlite3_log(rx, "cannot limit WAL size: %s", pWal->zWalName);
}
}
/*
** Close a connection to a log file.
*/
-SQLITE_PRIVATE int sqlite3WalClose(
+SQLITE_PRIVATE int tdsqlite3WalClose(
Wal *pWal, /* Wal to close */
+ tdsqlite3 *db, /* For interrupt flag */
int sync_flags, /* Flags to pass to OsSync() (or 0) */
int nBuf,
u8 *zBuf /* Buffer of at least nBuf bytes */
@@ -59516,17 +65536,18 @@ SQLITE_PRIVATE int sqlite3WalClose(
**
** The EXCLUSIVE lock is not released before returning.
*/
- rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE);
- if( rc==SQLITE_OK ){
+ if( zBuf!=0
+ && SQLITE_OK==(rc = tdsqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE))
+ ){
if( pWal->exclusiveMode==WAL_NORMAL_MODE ){
pWal->exclusiveMode = WAL_EXCLUSIVE_MODE;
}
- rc = sqlite3WalCheckpoint(
- pWal, SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0
+ rc = tdsqlite3WalCheckpoint(pWal, db,
+ SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0
);
if( rc==SQLITE_OK ){
int bPersist = -1;
- sqlite3OsFileControlHint(
+ tdsqlite3OsFileControlHint(
pWal->pDbFd, SQLITE_FCNTL_PERSIST_WAL, &bPersist
);
if( bPersist!=1 ){
@@ -59547,15 +65568,15 @@ SQLITE_PRIVATE int sqlite3WalClose(
}
walIndexClose(pWal, isDelete);
- sqlite3OsClose(pWal->pWalFd);
+ tdsqlite3OsClose(pWal->pWalFd);
if( isDelete ){
- sqlite3BeginBenignMalloc();
- sqlite3OsDelete(pWal->pVfs, pWal->zWalName, 0);
- sqlite3EndBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
+ tdsqlite3OsDelete(pWal->pVfs, pWal->zWalName, 0);
+ tdsqlite3EndBenignMalloc();
}
WALTRACE(("WAL%p: closed\n", pWal));
- sqlite3_free((void *)pWal->apWiData);
- sqlite3_free(pWal);
+ tdsqlite3_free((void *)pWal->apWiData);
+ tdsqlite3_free(pWal);
}
return rc;
}
@@ -59624,6 +65645,12 @@ static int walIndexTryHdr(Wal *pWal, int *pChanged){
}
/*
+** This is the value that walTryBeginRead returns when it needs to
+** be retried.
+*/
+#define WAL_RETRY (-1)
+
+/*
** Read the wal-index header from the wal-index and into pWal->hdr.
** If the wal-header appears to be corrupt, try to reconstruct the
** wal-index from the WAL before returning.
@@ -59646,9 +65673,29 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
assert( pChanged );
rc = walIndexPage(pWal, 0, &page0);
if( rc!=SQLITE_OK ){
- return rc;
- };
- assert( page0 || pWal->writeLock==0 );
+ assert( rc!=SQLITE_READONLY ); /* READONLY changed to OK in walIndexPage */
+ if( rc==SQLITE_READONLY_CANTINIT ){
+ /* The SQLITE_READONLY_CANTINIT return means that the shared-memory
+ ** was openable but is not writable, and this thread is unable to
+ ** confirm that another write-capable connection has the shared-memory
+ ** open, and hence the content of the shared-memory is unreliable,
+ ** since the shared-memory might be inconsistent with the WAL file
+ ** and there is no writer on hand to fix it. */
+ assert( page0==0 );
+ assert( pWal->writeLock==0 );
+ assert( pWal->readOnly & WAL_SHM_RDONLY );
+ pWal->bShmUnreliable = 1;
+ pWal->exclusiveMode = WAL_HEAPMEMORY_MODE;
+ *pChanged = 1;
+ }else{
+ return rc; /* Any other non-OK return is just an error */
+ }
+ }else{
+ /* page0 can be NULL if the SHM is zero bytes in size and pWal->writeLock
+ ** is zero, which prevents the SHM from growing */
+ testcase( page0!=0 );
+ }
+ assert( page0!=0 || pWal->writeLock==0 );
/* If the first page of the wal-index has been mapped, try to read the
** wal-index header immediately, without holding any lock. This usually
@@ -59662,7 +65709,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
*/
assert( badHdr==0 || pWal->writeLock==0 );
if( badHdr ){
- if( pWal->readOnly & WAL_SHM_RDONLY ){
+ if( pWal->bShmUnreliable==0 && (pWal->readOnly & WAL_SHM_RDONLY) ){
if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){
walUnlockShared(pWal, WAL_WRITE_LOCK);
rc = SQLITE_READONLY_RECOVERY;
@@ -59692,15 +65739,193 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
if( badHdr==0 && pWal->hdr.iVersion!=WALINDEX_MAX_VERSION ){
rc = SQLITE_CANTOPEN_BKPT;
}
+ if( pWal->bShmUnreliable ){
+ if( rc!=SQLITE_OK ){
+ walIndexClose(pWal, 0);
+ pWal->bShmUnreliable = 0;
+ assert( pWal->nWiData>0 && pWal->apWiData[0]==0 );
+ /* walIndexRecover() might have returned SHORT_READ if a concurrent
+ ** writer truncated the WAL out from under it. If that happens, it
+ ** indicates that a writer has fixed the SHM file for us, so retry */
+ if( rc==SQLITE_IOERR_SHORT_READ ) rc = WAL_RETRY;
+ }
+ pWal->exclusiveMode = WAL_NORMAL_MODE;
+ }
return rc;
}
/*
-** This is the value that walTryBeginRead returns when it needs to
-** be retried.
+** Open a transaction in a connection where the shared-memory is read-only
+** and where we cannot verify that there is a separate write-capable connection
+** on hand to keep the shared-memory up-to-date with the WAL file.
+**
+** This can happen, for example, when the shared-memory is implemented by
+** memory-mapping a *-shm file, where a prior writer has shut down and
+** left the *-shm file on disk, and now the present connection is trying
+** to use that database but lacks write permission on the *-shm file.
+** Other scenarios are also possible, depending on the VFS implementation.
+**
+** Precondition:
+**
+** The *-wal file has been read and an appropriate wal-index has been
+** constructed in pWal->apWiData[] using heap memory instead of shared
+** memory.
+**
+** If this function returns SQLITE_OK, then the read transaction has
+** been successfully opened. In this case output variable (*pChanged)
+** is set to true before returning if the caller should discard the
+** contents of the page cache before proceeding. Or, if it returns
+** WAL_RETRY, then the heap memory wal-index has been discarded and
+** the caller should retry opening the read transaction from the
+** beginning (including attempting to map the *-shm file).
+**
+** If an error occurs, an SQLite error code is returned.
*/
-#define WAL_RETRY (-1)
+static int walBeginShmUnreliable(Wal *pWal, int *pChanged){
+ i64 szWal; /* Size of wal file on disk in bytes */
+ i64 iOffset; /* Current offset when reading wal file */
+ u8 aBuf[WAL_HDRSIZE]; /* Buffer to load WAL header into */
+ u8 *aFrame = 0; /* Malloc'd buffer to load entire frame */
+ int szFrame; /* Number of bytes in buffer aFrame[] */
+ u8 *aData; /* Pointer to data part of aFrame buffer */
+ volatile void *pDummy; /* Dummy argument for xShmMap */
+ int rc; /* Return code */
+ u32 aSaveCksum[2]; /* Saved copy of pWal->hdr.aFrameCksum */
+
+ assert( pWal->bShmUnreliable );
+ assert( pWal->readOnly & WAL_SHM_RDONLY );
+ assert( pWal->nWiData>0 && pWal->apWiData[0] );
+
+ /* Take WAL_READ_LOCK(0). This has the effect of preventing any
+ ** writers from running a checkpoint, but does not stop them
+ ** from running recovery. */
+ rc = walLockShared(pWal, WAL_READ_LOCK(0));
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_BUSY ) rc = WAL_RETRY;
+ goto begin_unreliable_shm_out;
+ }
+ pWal->readLock = 0;
+
+ /* Check to see if a separate writer has attached to the shared-memory area,
+ ** thus making the shared-memory "reliable" again. Do this by invoking
+ ** the xShmMap() routine of the VFS and looking to see if the return
+ ** is SQLITE_READONLY instead of SQLITE_READONLY_CANTINIT.
+ **
+ ** If the shared-memory is now "reliable" return WAL_RETRY, which will
+ ** cause the heap-memory WAL-index to be discarded and the actual
+ ** shared memory to be used in its place.
+ **
+ ** This step is important because, even though this connection is holding
+ ** the WAL_READ_LOCK(0) which prevents a checkpoint, a writer might
+ ** have already checkpointed the WAL file and, while the current
+ ** is active, wrap the WAL and start overwriting frames that this
+ ** process wants to use.
+ **
+ ** Once tdsqlite3OsShmMap() has been called for an tdsqlite3_file and has
+ ** returned any SQLITE_READONLY value, it must return only SQLITE_READONLY
+ ** or SQLITE_READONLY_CANTINIT or some error for all subsequent invocations,
+ ** even if some external agent does a "chmod" to make the shared-memory
+ ** writable by us, until tdsqlite3OsShmUnmap() has been called.
+ ** This is a requirement on the VFS implementation.
+ */
+ rc = tdsqlite3OsShmMap(pWal->pDbFd, 0, WALINDEX_PGSZ, 0, &pDummy);
+ assert( rc!=SQLITE_OK ); /* SQLITE_OK not possible for read-only connection */
+ if( rc!=SQLITE_READONLY_CANTINIT ){
+ rc = (rc==SQLITE_READONLY ? WAL_RETRY : rc);
+ goto begin_unreliable_shm_out;
+ }
+
+ /* We reach this point only if the real shared-memory is still unreliable.
+ ** Assume the in-memory WAL-index substitute is correct and load it
+ ** into pWal->hdr.
+ */
+ memcpy(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr));
+
+ /* Make sure some writer hasn't come in and changed the WAL file out
+ ** from under us, then disconnected, while we were not looking.
+ */
+ rc = tdsqlite3OsFileSize(pWal->pWalFd, &szWal);
+ if( rc!=SQLITE_OK ){
+ goto begin_unreliable_shm_out;
+ }
+ if( szWal<WAL_HDRSIZE ){
+ /* If the wal file is too small to contain a wal-header and the
+ ** wal-index header has mxFrame==0, then it must be safe to proceed
+ ** reading the database file only. However, the page cache cannot
+ ** be trusted, as a read/write connection may have connected, written
+ ** the db, run a checkpoint, truncated the wal file and disconnected
+ ** since this client's last read transaction. */
+ *pChanged = 1;
+ rc = (pWal->hdr.mxFrame==0 ? SQLITE_OK : WAL_RETRY);
+ goto begin_unreliable_shm_out;
+ }
+
+ /* Check the salt keys at the start of the wal file still match. */
+ rc = tdsqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0);
+ if( rc!=SQLITE_OK ){
+ goto begin_unreliable_shm_out;
+ }
+ if( memcmp(&pWal->hdr.aSalt, &aBuf[16], 8) ){
+ /* Some writer has wrapped the WAL file while we were not looking.
+ ** Return WAL_RETRY which will cause the in-memory WAL-index to be
+ ** rebuilt. */
+ rc = WAL_RETRY;
+ goto begin_unreliable_shm_out;
+ }
+
+ /* Allocate a buffer to read frames into */
+ szFrame = pWal->hdr.szPage + WAL_FRAME_HDRSIZE;
+ aFrame = (u8 *)tdsqlite3_malloc64(szFrame);
+ if( aFrame==0 ){
+ rc = SQLITE_NOMEM_BKPT;
+ goto begin_unreliable_shm_out;
+ }
+ aData = &aFrame[WAL_FRAME_HDRSIZE];
+
+ /* Check to see if a complete transaction has been appended to the
+ ** wal file since the heap-memory wal-index was created. If so, the
+ ** heap-memory wal-index is discarded and WAL_RETRY returned to
+ ** the caller. */
+ aSaveCksum[0] = pWal->hdr.aFrameCksum[0];
+ aSaveCksum[1] = pWal->hdr.aFrameCksum[1];
+ for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->hdr.szPage);
+ iOffset+szFrame<=szWal;
+ iOffset+=szFrame
+ ){
+ u32 pgno; /* Database page number for frame */
+ u32 nTruncate; /* dbsize field from frame header */
+
+ /* Read and decode the next log frame. */
+ rc = tdsqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOffset);
+ if( rc!=SQLITE_OK ) break;
+ if( !walDecodeFrame(pWal, &pgno, &nTruncate, aData, aFrame) ) break;
+
+ /* If nTruncate is non-zero, then a complete transaction has been
+ ** appended to this wal file. Set rc to WAL_RETRY and break out of
+ ** the loop. */
+ if( nTruncate ){
+ rc = WAL_RETRY;
+ break;
+ }
+ }
+ pWal->hdr.aFrameCksum[0] = aSaveCksum[0];
+ pWal->hdr.aFrameCksum[1] = aSaveCksum[1];
+
+ begin_unreliable_shm_out:
+ tdsqlite3_free(aFrame);
+ if( rc!=SQLITE_OK ){
+ int i;
+ for(i=0; i<pWal->nWiData; i++){
+ tdsqlite3_free((void*)pWal->apWiData[i]);
+ pWal->apWiData[i] = 0;
+ }
+ pWal->bShmUnreliable = 0;
+ tdsqlite3WalEndReadTransaction(pWal);
+ *pChanged = 1;
+ }
+ return rc;
+}
/*
** Attempt to start a read transaction. This might fail due to a race or
@@ -59716,7 +65941,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
** checkpointed. If useWal==0 then this routine calls walIndexReadHdr()
** to make a copy of the wal-index header into pWal->hdr. If the
** wal-index header has changed, *pChanged is set to 1 (as an indication
-** to the caller that the local paget cache is obsolete and needs to be
+** to the caller that the local page cache is obsolete and needs to be
** flushed.) When useWal==1, the wal-index header is assumed to already
** be loaded and the pChanged parameter is unused.
**
@@ -59762,6 +65987,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
assert( pWal->readLock<0 ); /* Not currently locked */
+ /* useWal may only be set for read/write connections */
+ assert( (pWal->readOnly & WAL_SHM_RDONLY)==0 || useWal==0 );
+
/* Take steps to avoid spinning forever if there is a protocol error.
**
** Circumstances that cause a RETRY should only last for the briefest
@@ -59772,8 +66000,8 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
** during the few nanoseconds that it is holding the lock. In that case,
** it might take longer than normal for the lock to free.
**
- ** After 5 RETRYs, we begin calling sqlite3OsSleep(). The first few
- ** calls to sqlite3OsSleep() have a delay of 1 microsecond. Really this
+ ** After 5 RETRYs, we begin calling tdsqlite3OsSleep(). The first few
+ ** calls to tdsqlite3OsSleep() have a delay of 1 microsecond. Really this
** is more of a scheduler yield than an actual delay. But on the 10th
** an subsequent retries, the delays start becoming longer and longer,
** so that on the 100th (and last) RETRY we delay for 323 milliseconds.
@@ -59786,11 +66014,14 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
return SQLITE_PROTOCOL;
}
if( cnt>=10 ) nDelay = (cnt-9)*(cnt-9)*39;
- sqlite3OsSleep(pWal->pVfs, nDelay);
+ tdsqlite3OsSleep(pWal->pVfs, nDelay);
}
if( !useWal ){
- rc = walIndexReadHdr(pWal, pChanged);
+ assert( rc==SQLITE_OK );
+ if( pWal->bShmUnreliable==0 ){
+ rc = walIndexReadHdr(pWal, pChanged);
+ }
if( rc==SQLITE_BUSY ){
/* If there is not a recovery running in another thread or process
** then convert BUSY errors to WAL_RETRY. If recovery is known to
@@ -59819,13 +66050,17 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
if( rc!=SQLITE_OK ){
return rc;
}
+ else if( pWal->bShmUnreliable ){
+ return walBeginShmUnreliable(pWal, pChanged);
+ }
}
+ assert( pWal->nWiData>0 );
+ assert( pWal->apWiData[0]!=0 );
pInfo = walCkptInfo(pWal);
- if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame
+ if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame
#ifdef SQLITE_ENABLE_SNAPSHOT
- && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0
- || 0==memcmp(&pWal->hdr, pWal->pSnapshot, sizeof(WalIndexHdr)))
+ && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0)
#endif
){
/* The WAL has been completely backfilled (or it is empty).
@@ -59872,7 +66107,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
#endif
for(i=1; i<WAL_NREADER; i++){
- u32 thisMark = pInfo->aReadMark[i];
+ u32 thisMark = AtomicLoad(pInfo->aReadMark+i);
if( mxReadMark<=thisMark && thisMark<=mxFrame ){
assert( thisMark!=READMARK_NOT_USED );
mxReadMark = thisMark;
@@ -59885,7 +66120,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
for(i=1; i<WAL_NREADER; i++){
rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
if( rc==SQLITE_OK ){
- mxReadMark = pInfo->aReadMark[i] = mxFrame;
+ mxReadMark = AtomicStore(pInfo->aReadMark+i,mxFrame);
mxI = i;
walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
break;
@@ -59896,7 +66131,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
if( mxI==0 ){
assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
- return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTLOCK;
+ return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT;
}
rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
@@ -59937,9 +66172,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
** we can guarantee that the checkpointer that set nBackfill could not
** see any pages past pWal->hdr.mxFrame, this problem does not come up.
*/
- pWal->minFrame = pInfo->nBackfill+1;
+ pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1;
walShmBarrier(pWal);
- if( pInfo->aReadMark[mxI]!=mxReadMark
+ if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark
|| memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
){
walUnlockShared(pWal, WAL_READ_LOCK(mxI));
@@ -59951,10 +66186,86 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
return rc;
}
+#ifdef SQLITE_ENABLE_SNAPSHOT
+/*
+** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted
+** variable so that older snapshots can be accessed. To do this, loop
+** through all wal frames from nBackfillAttempted to (nBackfill+1),
+** comparing their content to the corresponding page with the database
+** file, if any. Set nBackfillAttempted to the frame number of the
+** first frame for which the wal file content matches the db file.
+**
+** This is only really safe if the file-system is such that any page
+** writes made by earlier checkpointers were atomic operations, which
+** is not always true. It is also possible that nBackfillAttempted
+** may be left set to a value larger than expected, if a wal frame
+** contains content that duplicate of an earlier version of the same
+** page.
+**
+** SQLITE_OK is returned if successful, or an SQLite error code if an
+** error occurs. It is not an error if nBackfillAttempted cannot be
+** decreased at all.
+*/
+SQLITE_PRIVATE int tdsqlite3WalSnapshotRecover(Wal *pWal){
+ int rc;
+
+ assert( pWal->readLock>=0 );
+ rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
+ if( rc==SQLITE_OK ){
+ volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
+ int szPage = (int)pWal->szPage;
+ i64 szDb; /* Size of db file in bytes */
+
+ rc = tdsqlite3OsFileSize(pWal->pDbFd, &szDb);
+ if( rc==SQLITE_OK ){
+ void *pBuf1 = tdsqlite3_malloc(szPage);
+ void *pBuf2 = tdsqlite3_malloc(szPage);
+ if( pBuf1==0 || pBuf2==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ u32 i = pInfo->nBackfillAttempted;
+ for(i=pInfo->nBackfillAttempted; i>pInfo->nBackfill; i--){
+ WalHashLoc sLoc; /* Hash table location */
+ u32 pgno; /* Page number in db file */
+ i64 iDbOff; /* Offset of db file entry */
+ i64 iWalOff; /* Offset of wal file entry */
+
+ rc = walHashGet(pWal, walFramePage(i), &sLoc);
+ if( rc!=SQLITE_OK ) break;
+ pgno = sLoc.aPgno[i-sLoc.iZero];
+ iDbOff = (i64)(pgno-1) * szPage;
+
+ if( iDbOff+szPage<=szDb ){
+ iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE;
+ rc = tdsqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff);
+
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff);
+ }
+
+ if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){
+ break;
+ }
+ }
+
+ pInfo->nBackfillAttempted = i-1;
+ }
+ }
+
+ tdsqlite3_free(pBuf1);
+ tdsqlite3_free(pBuf2);
+ }
+ walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
+ }
+
+ return rc;
+}
+#endif /* SQLITE_ENABLE_SNAPSHOT */
+
/*
** Begin a read transaction on the database.
**
-** This routine used to be called sqlite3OpenSnapshot() and with good reason:
+** This routine used to be called tdsqlite3OpenSnapshot() and with good reason:
** it takes a snapshot of the state of the WAL and wal-index for the current
** instant in time. The current thread will continue to use this snapshot.
** Other threads might append new content to the WAL and wal-index but
@@ -59962,10 +66273,10 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
**
** If the database contents have changes since the previous read
** transaction, then *pChanged is set to 1 before returning. The
-** Pager layer will use this to know that is cache is stale and
+** Pager layer will use this to know that its cache is stale and
** needs to be flushed.
*/
-SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
+SQLITE_PRIVATE int tdsqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
int rc; /* Return code */
int cnt = 0; /* Number of TryBeginRead attempts */
@@ -60013,14 +66324,18 @@ SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
** has not yet set the pInfo->nBackfillAttempted variable to indicate
** its intent. To avoid the race condition this leads to, ensure that
** there is no checkpointer process by taking a shared CKPT lock
- ** before checking pInfo->nBackfillAttempted. */
+ ** before checking pInfo->nBackfillAttempted.
+ **
+ ** TODO: Does the aReadMark[] lock prevent a checkpointer from doing
+ ** this already?
+ */
rc = walLockShared(pWal, WAL_CKPT_LOCK);
if( rc==SQLITE_OK ){
/* Check that the wal file has not been wrapped. Assuming that it has
** not, also check that no checkpointer has attempted to checkpoint any
** frames beyond pSnapshot->mxFrame. If either of these conditions are
- ** true, return SQLITE_BUSY_SNAPSHOT. Otherwise, overwrite pWal->hdr
+ ** true, return SQLITE_ERROR_SNAPSHOT. Otherwise, overwrite pWal->hdr
** with *pSnapshot and set *pChanged as appropriate for opening the
** snapshot. */
if( !memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
@@ -60030,16 +66345,17 @@ SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr));
*pChanged = bChanged;
}else{
- rc = SQLITE_BUSY_SNAPSHOT;
+ rc = SQLITE_ERROR_SNAPSHOT;
}
/* Release the shared CKPT lock obtained above. */
walUnlockShared(pWal, WAL_CKPT_LOCK);
+ pWal->minFrame = 1;
}
if( rc!=SQLITE_OK ){
- sqlite3WalEndReadTransaction(pWal);
+ tdsqlite3WalEndReadTransaction(pWal);
}
}
}
@@ -60051,8 +66367,8 @@ SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){
** Finish with a read transaction. All this does is release the
** read-lock.
*/
-SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal){
- sqlite3WalEndWriteTransaction(pWal);
+SQLITE_PRIVATE void tdsqlite3WalEndReadTransaction(Wal *pWal){
+ tdsqlite3WalEndWriteTransaction(pWal);
if( pWal->readLock>=0 ){
walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock));
pWal->readLock = -1;
@@ -60067,7 +66383,7 @@ SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal){
** Return SQLITE_OK if successful, or an error code if an error occurs. If an
** error does occur, the final value of *piRead is undefined.
*/
-SQLITE_PRIVATE int sqlite3WalFindFrame(
+SQLITE_PRIVATE int tdsqlite3WalFindFrame(
Wal *pWal, /* WAL handle */
Pgno pgno, /* Database page number to read data for */
u32 *piRead /* OUT: Frame number (or zero) */
@@ -60086,7 +66402,7 @@ SQLITE_PRIVATE int sqlite3WalFindFrame(
** then the WAL is ignored by the reader so return early, as if the
** WAL were empty.
*/
- if( iLast==0 || pWal->readLock==0 ){
+ if( iLast==0 || (pWal->readLock==0 && pWal->bShmUnreliable==0) ){
*piRead = 0;
return SQLITE_OK;
}
@@ -60117,22 +66433,21 @@ SQLITE_PRIVATE int sqlite3WalFindFrame(
** table after the current read-transaction had started.
*/
iMinHash = walFramePage(pWal->minFrame);
- for(iHash=walFramePage(iLast); iHash>=iMinHash && iRead==0; iHash--){
- volatile ht_slot *aHash; /* Pointer to hash table */
- volatile u32 *aPgno; /* Pointer to array of page numbers */
- u32 iZero; /* Frame number corresponding to aPgno[0] */
+ for(iHash=walFramePage(iLast); iHash>=iMinHash; iHash--){
+ WalHashLoc sLoc; /* Hash table location */
int iKey; /* Hash slot index */
int nCollide; /* Number of hash collisions remaining */
int rc; /* Error code */
- rc = walHashGet(pWal, iHash, &aHash, &aPgno, &iZero);
+ rc = walHashGet(pWal, iHash, &sLoc);
if( rc!=SQLITE_OK ){
return rc;
}
nCollide = HASHTABLE_NSLOT;
- for(iKey=walHash(pgno); aHash[iKey]; iKey=walNextHash(iKey)){
- u32 iFrame = aHash[iKey] + iZero;
- if( iFrame<=iLast && iFrame>=pWal->minFrame && aPgno[aHash[iKey]]==pgno ){
+ for(iKey=walHash(pgno); sLoc.aHash[iKey]; iKey=walNextHash(iKey)){
+ u32 iH = sLoc.aHash[iKey];
+ u32 iFrame = iH + sLoc.iZero;
+ if( iFrame<=iLast && iFrame>=pWal->minFrame && sLoc.aPgno[iH]==pgno ){
assert( iFrame>iRead || CORRUPT_DB );
iRead = iFrame;
}
@@ -60140,6 +66455,7 @@ SQLITE_PRIVATE int sqlite3WalFindFrame(
return SQLITE_CORRUPT_BKPT;
}
}
+ if( iRead ) break;
}
#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT
@@ -60149,8 +66465,8 @@ SQLITE_PRIVATE int sqlite3WalFindFrame(
{
u32 iRead2 = 0;
u32 iTest;
- assert( pWal->minFrame>0 );
- for(iTest=iLast; iTest>=pWal->minFrame; iTest--){
+ assert( pWal->bShmUnreliable || pWal->minFrame>0 );
+ for(iTest=iLast; iTest>=pWal->minFrame && iTest>0; iTest--){
if( walFramePgno(pWal, iTest)==pgno ){
iRead2 = iTest;
break;
@@ -60169,7 +66485,7 @@ SQLITE_PRIVATE int sqlite3WalFindFrame(
** (which is nOut bytes in size). Return SQLITE_OK if successful, or an
** error code otherwise.
*/
-SQLITE_PRIVATE int sqlite3WalReadFrame(
+SQLITE_PRIVATE int tdsqlite3WalReadFrame(
Wal *pWal, /* WAL handle */
u32 iRead, /* Frame to read */
int nOut, /* Size of buffer pOut in bytes */
@@ -60183,13 +66499,13 @@ SQLITE_PRIVATE int sqlite3WalReadFrame(
testcase( sz>=65536 );
iOffset = walFrameOffset(iRead, sz) + WAL_FRAME_HDRSIZE;
/* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */
- return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset);
+ return tdsqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset);
}
/*
** Return the size of the database in pages (or zero, if unknown).
*/
-SQLITE_PRIVATE Pgno sqlite3WalDbsize(Wal *pWal){
+SQLITE_PRIVATE Pgno tdsqlite3WalDbsize(Wal *pWal){
if( pWal && ALWAYS(pWal->readLock>=0) ){
return pWal->hdr.nPage;
}
@@ -60201,7 +66517,7 @@ SQLITE_PRIVATE Pgno sqlite3WalDbsize(Wal *pWal){
** This function starts a write transaction on the WAL.
**
** A read transaction must have already been started by a prior call
-** to sqlite3WalBeginReadTransaction().
+** to tdsqlite3WalBeginReadTransaction().
**
** If another thread or process has written into the database since
** the read transaction was started, then it is not possible for this
@@ -60210,7 +66526,7 @@ SQLITE_PRIVATE Pgno sqlite3WalDbsize(Wal *pWal){
**
** There can only be a single writer active at a time.
*/
-SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal){
+SQLITE_PRIVATE int tdsqlite3WalBeginWriteTransaction(Wal *pWal){
int rc;
/* Cannot start a write transaction without first holding a read
@@ -60248,7 +66564,7 @@ SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal){
** End a write transaction. The commit has already been done. This
** routine merely releases the lock.
*/
-SQLITE_PRIVATE int sqlite3WalEndWriteTransaction(Wal *pWal){
+SQLITE_PRIVATE int tdsqlite3WalEndWriteTransaction(Wal *pWal){
if( pWal->writeLock ){
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
pWal->writeLock = 0;
@@ -60270,7 +66586,7 @@ SQLITE_PRIVATE int sqlite3WalEndWriteTransaction(Wal *pWal){
** Otherwise, if the callback function does not return an error, this
** function returns SQLITE_OK.
*/
-SQLITE_PRIVATE int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
+SQLITE_PRIVATE int tdsqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
int rc = SQLITE_OK;
if( ALWAYS(pWal->writeLock) ){
Pgno iMax = pWal->hdr.mxFrame;
@@ -60310,7 +66626,7 @@ SQLITE_PRIVATE int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *p
** "rollback" the write position of the WAL handle back to the current
** point in the event of a savepoint rollback (via WalSavepointUndo()).
*/
-SQLITE_PRIVATE void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){
+SQLITE_PRIVATE void tdsqlite3WalSavepoint(Wal *pWal, u32 *aWalData){
assert( pWal->writeLock );
aWalData[0] = pWal->hdr.mxFrame;
aWalData[1] = pWal->hdr.aFrameCksum[0];
@@ -60324,7 +66640,7 @@ SQLITE_PRIVATE void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){
** of WAL_SAVEPOINT_NDATA u32 values that has been previously populated
** by a call to WalSavepoint().
*/
-SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
+SQLITE_PRIVATE int tdsqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
int rc = SQLITE_OK;
assert( pWal->writeLock );
@@ -60351,7 +66667,7 @@ SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
/*
** This function is called just before writing a set of frames to the log
-** file (see sqlite3WalFrames()). It checks to see if, instead of appending
+** file (see tdsqlite3WalFrames()). It checks to see if, instead of appending
** to the current log file, it is possible to overwrite the start of the
** existing log file with the new frames (i.e. "reset" the log). If so,
** it sets pWal->hdr.mxFrame to 0. Otherwise, pWal->hdr.mxFrame is left
@@ -60370,7 +66686,7 @@ static int walRestartLog(Wal *pWal){
assert( pInfo->nBackfill==pWal->hdr.mxFrame );
if( pInfo->nBackfill>0 ){
u32 salt1;
- sqlite3_randomness(4, &salt1);
+ tdsqlite3_randomness(4, &salt1);
rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
if( rc==SQLITE_OK ){
/* If all readers are using WAL_READ_LOCK(0) (in other words if no
@@ -60380,7 +66696,7 @@ static int walRestartLog(Wal *pWal){
**
** In theory it would be Ok to update the cache of the header only
** at this point. But updating the actual wal-index header is also
- ** safe and means there is no special case for sqlite3WalUndo()
+ ** safe and means there is no special case for tdsqlite3WalUndo()
** to handle if this transaction is rolled back. */
walRestartHdr(pWal, salt1);
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
@@ -60405,13 +66721,13 @@ static int walRestartLog(Wal *pWal){
/*
** Information about the current state of the WAL file and where
-** the next fsync should occur - passed from sqlite3WalFrames() into
+** the next fsync should occur - passed from tdsqlite3WalFrames() into
** walWriteToLog().
*/
typedef struct WalWriter {
Wal *pWal; /* The complete WAL information */
- sqlite3_file *pFd; /* The WAL file to which we write */
- sqlite3_int64 iSyncPoint; /* Fsync at this offset */
+ tdsqlite3_file *pFd; /* The WAL file to which we write */
+ tdsqlite3_int64 iSyncPoint; /* Fsync at this offset */
int syncFlags; /* Flags for the fsync */
int szPage; /* Size of one page */
} WalWriter;
@@ -60428,21 +66744,21 @@ static int walWriteToLog(
WalWriter *p, /* WAL to write to */
void *pContent, /* Content to be written */
int iAmt, /* Number of bytes to write */
- sqlite3_int64 iOffset /* Start writing at this offset */
+ tdsqlite3_int64 iOffset /* Start writing at this offset */
){
int rc;
if( iOffset<p->iSyncPoint && iOffset+iAmt>=p->iSyncPoint ){
int iFirstAmt = (int)(p->iSyncPoint - iOffset);
- rc = sqlite3OsWrite(p->pFd, pContent, iFirstAmt, iOffset);
+ rc = tdsqlite3OsWrite(p->pFd, pContent, iFirstAmt, iOffset);
if( rc ) return rc;
iOffset += iFirstAmt;
iAmt -= iFirstAmt;
pContent = (void*)(iFirstAmt + (char*)pContent);
- assert( p->syncFlags & (SQLITE_SYNC_NORMAL|SQLITE_SYNC_FULL) );
- rc = sqlite3OsSync(p->pFd, p->syncFlags & SQLITE_SYNC_MASK);
+ assert( WAL_SYNC_FLAGS(p->syncFlags)!=0 );
+ rc = tdsqlite3OsSync(p->pFd, WAL_SYNC_FLAGS(p->syncFlags));
if( iAmt==0 || rc ) return rc;
}
- rc = sqlite3OsWrite(p->pFd, pContent, iAmt, iOffset);
+ rc = tdsqlite3OsWrite(p->pFd, pContent, iAmt, iOffset);
return rc;
}
@@ -60453,13 +66769,13 @@ static int walWriteOneFrame(
WalWriter *p, /* Where to write the frame */
PgHdr *pPage, /* The page of the frame to be written */
int nTruncate, /* The commit flag. Usually 0. >0 for commit */
- sqlite3_int64 iOffset /* Byte offset at which to write */
+ tdsqlite3_int64 iOffset /* Byte offset at which to write */
){
int rc; /* Result code from subfunctions */
void *pData; /* Data actually written */
u8 aFrame[WAL_FRAME_HDRSIZE]; /* Buffer to assemble frame-header in */
#if defined(SQLITE_HAS_CODEC)
- if( (pData = sqlite3PagerCodec(pPage))==0 ) return SQLITE_NOMEM_BKPT;
+ if( (pData = tdsqlite3PagerCodec(pPage))==0 ) return SQLITE_NOMEM_BKPT;
#else
pData = pPage->pData;
#endif
@@ -60487,7 +66803,7 @@ static int walRewriteChecksums(Wal *pWal, u32 iLast){
u32 iRead; /* Next frame to read from wal file */
i64 iCksumOff;
- aBuf = sqlite3_malloc(szPage + WAL_FRAME_HDRSIZE);
+ aBuf = tdsqlite3_malloc(szPage + WAL_FRAME_HDRSIZE);
if( aBuf==0 ) return SQLITE_NOMEM_BKPT;
/* Find the checksum values to use as input for the recalculating the
@@ -60501,34 +66817,34 @@ static int walRewriteChecksums(Wal *pWal, u32 iLast){
}else{
iCksumOff = walFrameOffset(pWal->iReCksum-1, szPage) + 16;
}
- rc = sqlite3OsRead(pWal->pWalFd, aBuf, sizeof(u32)*2, iCksumOff);
- pWal->hdr.aFrameCksum[0] = sqlite3Get4byte(aBuf);
- pWal->hdr.aFrameCksum[1] = sqlite3Get4byte(&aBuf[sizeof(u32)]);
+ rc = tdsqlite3OsRead(pWal->pWalFd, aBuf, sizeof(u32)*2, iCksumOff);
+ pWal->hdr.aFrameCksum[0] = tdsqlite3Get4byte(aBuf);
+ pWal->hdr.aFrameCksum[1] = tdsqlite3Get4byte(&aBuf[sizeof(u32)]);
iRead = pWal->iReCksum;
pWal->iReCksum = 0;
for(; rc==SQLITE_OK && iRead<=iLast; iRead++){
i64 iOff = walFrameOffset(iRead, szPage);
- rc = sqlite3OsRead(pWal->pWalFd, aBuf, szPage+WAL_FRAME_HDRSIZE, iOff);
+ rc = tdsqlite3OsRead(pWal->pWalFd, aBuf, szPage+WAL_FRAME_HDRSIZE, iOff);
if( rc==SQLITE_OK ){
u32 iPgno, nDbSize;
- iPgno = sqlite3Get4byte(aBuf);
- nDbSize = sqlite3Get4byte(&aBuf[4]);
+ iPgno = tdsqlite3Get4byte(aBuf);
+ nDbSize = tdsqlite3Get4byte(&aBuf[4]);
walEncodeFrame(pWal, iPgno, nDbSize, &aBuf[WAL_FRAME_HDRSIZE], aFrame);
- rc = sqlite3OsWrite(pWal->pWalFd, aFrame, sizeof(aFrame), iOff);
+ rc = tdsqlite3OsWrite(pWal->pWalFd, aFrame, sizeof(aFrame), iOff);
}
}
- sqlite3_free(aBuf);
+ tdsqlite3_free(aBuf);
return rc;
}
/*
** Write a set of frames to the log. The caller must hold the write-lock
-** on the log file (obtained using sqlite3WalBeginWriteTransaction()).
+** on the log file (obtained using tdsqlite3WalBeginWriteTransaction()).
*/
-SQLITE_PRIVATE int sqlite3WalFrames(
+SQLITE_PRIVATE int tdsqlite3WalFrames(
Wal *pWal, /* Wal handle to write to */
int szPage, /* Database page-size in bytes */
PgHdr *pList, /* List of dirty pages to write */
@@ -60582,15 +66898,15 @@ SQLITE_PRIVATE int sqlite3WalFrames(
u8 aWalHdr[WAL_HDRSIZE]; /* Buffer to assemble wal-header in */
u32 aCksum[2]; /* Checksum for wal-header */
- sqlite3Put4byte(&aWalHdr[0], (WAL_MAGIC | SQLITE_BIGENDIAN));
- sqlite3Put4byte(&aWalHdr[4], WAL_MAX_VERSION);
- sqlite3Put4byte(&aWalHdr[8], szPage);
- sqlite3Put4byte(&aWalHdr[12], pWal->nCkpt);
- if( pWal->nCkpt==0 ) sqlite3_randomness(8, pWal->hdr.aSalt);
+ tdsqlite3Put4byte(&aWalHdr[0], (WAL_MAGIC | SQLITE_BIGENDIAN));
+ tdsqlite3Put4byte(&aWalHdr[4], WAL_MAX_VERSION);
+ tdsqlite3Put4byte(&aWalHdr[8], szPage);
+ tdsqlite3Put4byte(&aWalHdr[12], pWal->nCkpt);
+ if( pWal->nCkpt==0 ) tdsqlite3_randomness(8, pWal->hdr.aSalt);
memcpy(&aWalHdr[16], pWal->hdr.aSalt, 8);
walChecksumBytes(1, aWalHdr, WAL_HDRSIZE-2*4, 0, aCksum);
- sqlite3Put4byte(&aWalHdr[24], aCksum[0]);
- sqlite3Put4byte(&aWalHdr[28], aCksum[1]);
+ tdsqlite3Put4byte(&aWalHdr[24], aCksum[0]);
+ tdsqlite3Put4byte(&aWalHdr[28], aCksum[1]);
pWal->szPage = szPage;
pWal->hdr.bigEndCksum = SQLITE_BIGENDIAN;
@@ -60598,7 +66914,7 @@ SQLITE_PRIVATE int sqlite3WalFrames(
pWal->hdr.aFrameCksum[1] = aCksum[1];
pWal->truncateOnCommit = 1;
- rc = sqlite3OsWrite(pWal->pWalFd, aWalHdr, sizeof(aWalHdr), 0);
+ rc = tdsqlite3OsWrite(pWal->pWalFd, aWalHdr, sizeof(aWalHdr), 0);
WALTRACE(("WAL%p: wal-header write %s\n", pWal, rc ? "failed" : "ok"));
if( rc!=SQLITE_OK ){
return rc;
@@ -60609,10 +66925,10 @@ SQLITE_PRIVATE int sqlite3WalFrames(
** an out-of-order write following a WAL restart could result in
** database corruption. See the ticket:
**
- ** http://localhost:591/sqlite/info/ff5be73dee
+ ** https://sqlite.org/src/info/ff5be73dee
*/
- if( pWal->syncHeader && sync_flags ){
- rc = sqlite3OsSync(pWal->pWalFd, sync_flags & SQLITE_SYNC_MASK);
+ if( pWal->syncHeader ){
+ rc = tdsqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags));
if( rc ) return rc;
}
}
@@ -60637,7 +66953,7 @@ SQLITE_PRIVATE int sqlite3WalFrames(
** checksums must be recomputed when the transaction is committed. */
if( iFirst && (p->pDirty || isCommit==0) ){
u32 iWrite = 0;
- VVA_ONLY(rc =) sqlite3WalFindFrame(pWal, p->pgno, &iWrite);
+ VVA_ONLY(rc =) tdsqlite3WalFindFrame(pWal, p->pgno, &iWrite);
assert( rc==SQLITE_OK || iWrite==0 );
if( iWrite>=iFirst ){
i64 iOff = walFrameOffset(iWrite, szPage) + WAL_FRAME_HDRSIZE;
@@ -60646,11 +66962,11 @@ SQLITE_PRIVATE int sqlite3WalFrames(
pWal->iReCksum = iWrite;
}
#if defined(SQLITE_HAS_CODEC)
- if( (pData = sqlite3PagerCodec(p))==0 ) return SQLITE_NOMEM;
+ if( (pData = tdsqlite3PagerCodec(p))==0 ) return SQLITE_NOMEM;
#else
pData = p->pData;
#endif
- rc = sqlite3OsWrite(pWal->pWalFd, pData, szPage, iOff);
+ rc = tdsqlite3OsWrite(pWal->pWalFd, pData, szPage, iOff);
if( rc ) return rc;
p->flags &= ~PGHDR_WAL_APPEND;
continue;
@@ -60687,10 +67003,10 @@ SQLITE_PRIVATE int sqlite3WalFrames(
** sector boundary is synced; the part of the last frame that extends
** past the sector boundary is written after the sync.
*/
- if( isCommit && (sync_flags & WAL_SYNC_TRANSACTIONS)!=0 ){
+ if( isCommit && WAL_SYNC_FLAGS(sync_flags)!=0 ){
int bSync = 1;
if( pWal->padToSectorBoundary ){
- int sectorSize = sqlite3SectorSize(pWal->pWalFd);
+ int sectorSize = tdsqlite3SectorSize(pWal->pWalFd);
w.iSyncPoint = ((iOffset+sectorSize-1)/sectorSize)*sectorSize;
bSync = (w.iSyncPoint==iOffset);
testcase( bSync );
@@ -60699,11 +67015,12 @@ SQLITE_PRIVATE int sqlite3WalFrames(
if( rc ) return rc;
iOffset += szFrame;
nExtra++;
+ assert( pLast!=0 );
}
}
if( bSync ){
assert( rc==SQLITE_OK );
- rc = sqlite3OsSync(w.pFd, sync_flags & SQLITE_SYNC_MASK);
+ rc = tdsqlite3OsSync(w.pFd, WAL_SYNC_FLAGS(sync_flags));
}
}
@@ -60731,6 +67048,7 @@ SQLITE_PRIVATE int sqlite3WalFrames(
iFrame++;
rc = walIndexAppend(pWal, iFrame, p->pgno);
}
+ assert( pLast!=0 || nExtra==0 );
while( rc==SQLITE_OK && nExtra>0 ){
iFrame++;
nExtra--;
@@ -60759,7 +67077,7 @@ SQLITE_PRIVATE int sqlite3WalFrames(
}
/*
-** This routine is called to implement sqlite3_wal_checkpoint() and
+** This routine is called to implement tdsqlite3_wal_checkpoint() and
** related interfaces.
**
** Obtain a CHECKPOINT lock and then backfill as much information as
@@ -60768,8 +67086,9 @@ SQLITE_PRIVATE int sqlite3WalFrames(
** If parameter xBusy is not NULL, it is a pointer to a busy-handler
** callback. In this case this function runs a blocking checkpoint.
*/
-SQLITE_PRIVATE int sqlite3WalCheckpoint(
+SQLITE_PRIVATE int tdsqlite3WalCheckpoint(
Wal *pWal, /* Wal connection */
+ tdsqlite3 *db, /* Check this handle's interrupt flag */
int eMode, /* PASSIVE, FULL, RESTART, or TRUNCATE */
int (*xBusy)(void*), /* Function to call when busy */
void *pBusyArg, /* Context argument for xBusyHandler */
@@ -60834,7 +67153,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
if( rc==SQLITE_OK ){
rc = walIndexReadHdr(pWal, &isChanged);
if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){
- sqlite3OsUnfetch(pWal->pDbFd, 0, 0);
+ tdsqlite3OsUnfetch(pWal->pDbFd, 0, 0);
}
}
@@ -60844,7 +67163,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){
rc = SQLITE_CORRUPT_BKPT;
}else{
- rc = walCheckpoint(pWal, eMode2, xBusy2, pBusyArg, sync_flags, zBuf);
+ rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags, zBuf);
}
/* If no error occurred, set the output variables. */
@@ -60865,19 +67184,19 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint(
}
/* Release the locks. */
- sqlite3WalEndWriteTransaction(pWal);
+ tdsqlite3WalEndWriteTransaction(pWal);
walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
pWal->ckptLock = 0;
WALTRACE(("WAL%p: checkpoint %s\n", pWal, rc ? "failed" : "ok"));
return (rc==SQLITE_OK && eMode!=eMode2 ? SQLITE_BUSY : rc);
}
-/* Return the value to pass to a sqlite3_wal_hook callback, the
+/* Return the value to pass to a tdsqlite3_wal_hook callback, the
** number of frames in the WAL at the point of the last commit since
-** sqlite3WalCallback() was called. If no commits have occurred since
+** tdsqlite3WalCallback() was called. If no commits have occurred since
** the last call, then return 0.
*/
-SQLITE_PRIVATE int sqlite3WalCallback(Wal *pWal){
+SQLITE_PRIVATE int tdsqlite3WalCallback(Wal *pWal){
u32 ret = 0;
if( pWal ){
ret = pWal->iCallback;
@@ -60910,7 +67229,7 @@ SQLITE_PRIVATE int sqlite3WalCallback(Wal *pWal){
** should acquire the database exclusive lock prior to invoking
** the op==1 case.
*/
-SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op){
+SQLITE_PRIVATE int tdsqlite3WalExclusiveMode(Wal *pWal, int op){
int rc;
assert( pWal->writeLock==0 );
assert( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE || op==-1 );
@@ -60925,24 +67244,24 @@ SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op){
assert( pWal->readLock>=0 || (op<=0 && pWal->exclusiveMode==0) );
if( op==0 ){
- if( pWal->exclusiveMode ){
- pWal->exclusiveMode = 0;
+ if( pWal->exclusiveMode!=WAL_NORMAL_MODE ){
+ pWal->exclusiveMode = WAL_NORMAL_MODE;
if( walLockShared(pWal, WAL_READ_LOCK(pWal->readLock))!=SQLITE_OK ){
- pWal->exclusiveMode = 1;
+ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE;
}
- rc = pWal->exclusiveMode==0;
+ rc = pWal->exclusiveMode==WAL_NORMAL_MODE;
}else{
/* Already in locking_mode=NORMAL */
rc = 0;
}
}else if( op>0 ){
- assert( pWal->exclusiveMode==0 );
+ assert( pWal->exclusiveMode==WAL_NORMAL_MODE );
assert( pWal->readLock>=0 );
walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock));
- pWal->exclusiveMode = 1;
+ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE;
rc = 1;
}else{
- rc = pWal->exclusiveMode==0;
+ rc = pWal->exclusiveMode==WAL_NORMAL_MODE;
}
return rc;
}
@@ -60952,7 +67271,7 @@ SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op){
** heap-memory for the wal-index. Otherwise, if the argument is NULL or the
** WAL module is using shared-memory, return false.
*/
-SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal){
+SQLITE_PRIVATE int tdsqlite3WalHeapMemory(Wal *pWal){
return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE );
}
@@ -60961,18 +67280,23 @@ SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal){
** every other subsystem, so the WAL module can put whatever it needs
** in the object.
*/
-SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){
+SQLITE_PRIVATE int tdsqlite3WalSnapshotGet(Wal *pWal, tdsqlite3_snapshot **ppSnapshot){
int rc = SQLITE_OK;
WalIndexHdr *pRet;
+ static const u32 aZero[4] = { 0, 0, 0, 0 };
assert( pWal->readLock>=0 && pWal->writeLock==0 );
- pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr));
+ if( memcmp(&pWal->hdr.aFrameCksum[0],aZero,16)==0 ){
+ *ppSnapshot = 0;
+ return SQLITE_ERROR;
+ }
+ pRet = (WalIndexHdr*)tdsqlite3_malloc(sizeof(WalIndexHdr));
if( pRet==0 ){
rc = SQLITE_NOMEM_BKPT;
}else{
memcpy(pRet, &pWal->hdr, sizeof(WalIndexHdr));
- *ppSnapshot = (sqlite3_snapshot*)pRet;
+ *ppSnapshot = (tdsqlite3_snapshot*)pRet;
}
return rc;
@@ -60980,7 +67304,7 @@ SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapsho
/* Try to open on pSnapshot when the next read-transaction starts
*/
-SQLITE_PRIVATE void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot){
+SQLITE_PRIVATE void tdsqlite3WalSnapshotOpen(Wal *pWal, tdsqlite3_snapshot *pSnapshot){
pWal->pSnapshot = (WalIndexHdr*)pSnapshot;
}
@@ -60988,7 +67312,7 @@ SQLITE_PRIVATE void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapsho
** Return a +ve value if snapshot p1 is newer than p2. A -ve value if
** p1 is older than p2 and zero if p1 and p2 are the same snapshot.
*/
-SQLITE_API int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){
+SQLITE_API int tdsqlite3_snapshot_cmp(tdsqlite3_snapshot *p1, tdsqlite3_snapshot *p2){
WalIndexHdr *pHdr1 = (WalIndexHdr*)p1;
WalIndexHdr *pHdr2 = (WalIndexHdr*)p2;
@@ -61000,6 +67324,43 @@ SQLITE_API int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){
if( pHdr1->mxFrame>pHdr2->mxFrame ) return +1;
return 0;
}
+
+/*
+** The caller currently has a read transaction open on the database.
+** This function takes a SHARED lock on the CHECKPOINTER slot and then
+** checks if the snapshot passed as the second argument is still
+** available. If so, SQLITE_OK is returned.
+**
+** If the snapshot is not available, SQLITE_ERROR is returned. Or, if
+** the CHECKPOINTER lock cannot be obtained, SQLITE_BUSY. If any error
+** occurs (any value other than SQLITE_OK is returned), the CHECKPOINTER
+** lock is released before returning.
+*/
+SQLITE_PRIVATE int tdsqlite3WalSnapshotCheck(Wal *pWal, tdsqlite3_snapshot *pSnapshot){
+ int rc;
+ rc = walLockShared(pWal, WAL_CKPT_LOCK);
+ if( rc==SQLITE_OK ){
+ WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot;
+ if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt))
+ || pNew->mxFrame<walCkptInfo(pWal)->nBackfillAttempted
+ ){
+ rc = SQLITE_ERROR_SNAPSHOT;
+ walUnlockShared(pWal, WAL_CKPT_LOCK);
+ }
+ }
+ return rc;
+}
+
+/*
+** Release a lock obtained by an earlier successful call to
+** tdsqlite3WalSnapshotCheck().
+*/
+SQLITE_PRIVATE void tdsqlite3WalSnapshotUnlock(Wal *pWal){
+ assert( pWal );
+ walUnlockShared(pWal, WAL_CKPT_LOCK);
+}
+
+
#endif /* SQLITE_ENABLE_SNAPSHOT */
#ifdef SQLITE_ENABLE_ZIPVFS
@@ -61008,15 +67369,15 @@ SQLITE_API int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){
** read-lock. This function returns the database page-size if it is known,
** or zero if it is not (or if pWal is NULL).
*/
-SQLITE_PRIVATE int sqlite3WalFramesize(Wal *pWal){
+SQLITE_PRIVATE int tdsqlite3WalFramesize(Wal *pWal){
assert( pWal==0 || pWal->readLock>=0 );
return (pWal ? pWal->szPage : 0);
}
#endif
-/* Return the sqlite3_file object for the WAL file
+/* Return the tdsqlite3_file object for the WAL file
*/
-SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal){
+SQLITE_PRIVATE tdsqlite3_file *tdsqlite3WalFile(Wal *pWal){
return pWal->pWalFd;
}
@@ -61052,10 +67413,10 @@ SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal){
*/
static void lockBtreeMutex(Btree *p){
assert( p->locked==0 );
- assert( sqlite3_mutex_notheld(p->pBt->mutex) );
- assert( sqlite3_mutex_held(p->db->mutex) );
+ assert( tdsqlite3_mutex_notheld(p->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
- sqlite3_mutex_enter(p->pBt->mutex);
+ tdsqlite3_mutex_enter(p->pBt->mutex);
p->pBt->db = p->db;
p->locked = 1;
}
@@ -61067,11 +67428,11 @@ static void lockBtreeMutex(Btree *p){
static void SQLITE_NOINLINE unlockBtreeMutex(Btree *p){
BtShared *pBt = p->pBt;
assert( p->locked==1 );
- assert( sqlite3_mutex_held(pBt->mutex) );
- assert( sqlite3_mutex_held(p->db->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
assert( p->db==pBt->db );
- sqlite3_mutex_leave(pBt->mutex);
+ tdsqlite3_mutex_leave(pBt->mutex);
p->locked = 0;
}
@@ -61094,7 +67455,7 @@ static void SQLITE_NOINLINE btreeLockCarefully(Btree *p);
** for the lock to become available on p, then relock all of the
** subsequent Btrees that desire a lock.
*/
-SQLITE_PRIVATE void sqlite3BtreeEnter(Btree *p){
+SQLITE_PRIVATE void tdsqlite3BtreeEnter(Btree *p){
/* Some basic sanity checking on the Btree. The list of Btrees
** connected by pNext and pPrev should be in sorted order by
** Btree.pBt value. All elements of the list should belong to
@@ -61110,7 +67471,7 @@ SQLITE_PRIVATE void sqlite3BtreeEnter(Btree *p){
assert( p->sharable || p->wantToLock==0 );
/* We should already hold a lock on the database connection */
- assert( sqlite3_mutex_held(p->db->mutex) );
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
/* Unless the database is sharable and unlocked, then BtShared.db
** should already be set correctly. */
@@ -61122,10 +67483,10 @@ SQLITE_PRIVATE void sqlite3BtreeEnter(Btree *p){
btreeLockCarefully(p);
}
-/* This is a helper function for sqlite3BtreeLock(). By moving
-** complex, but seldom used logic, out of sqlite3BtreeLock() and
+/* This is a helper function for tdsqlite3BtreeLock(). By moving
+** complex, but seldom used logic, out of tdsqlite3BtreeLock() and
** into this routine, we avoid unnecessary stack pointer changes
-** and thus help the sqlite3BtreeLock() routine to run much faster
+** and thus help the tdsqlite3BtreeLock() routine to run much faster
** in the common case.
*/
static void SQLITE_NOINLINE btreeLockCarefully(Btree *p){
@@ -61135,7 +67496,7 @@ static void SQLITE_NOINLINE btreeLockCarefully(Btree *p){
** want without having to go through the ascending lock
** procedure that follows. Just be sure not to block.
*/
- if( sqlite3_mutex_try(p->pBt->mutex)==SQLITE_OK ){
+ if( tdsqlite3_mutex_try(p->pBt->mutex)==SQLITE_OK ){
p->pBt->db = p->db;
p->locked = 1;
return;
@@ -61166,8 +67527,8 @@ static void SQLITE_NOINLINE btreeLockCarefully(Btree *p){
/*
** Exit the recursive mutex on a Btree.
*/
-SQLITE_PRIVATE void sqlite3BtreeLeave(Btree *p){
- assert( sqlite3_mutex_held(p->db->mutex) );
+SQLITE_PRIVATE void tdsqlite3BtreeLeave(Btree *p){
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
if( p->sharable ){
assert( p->wantToLock>0 );
p->wantToLock--;
@@ -61184,11 +67545,11 @@ SQLITE_PRIVATE void sqlite3BtreeLeave(Btree *p){
**
** This routine is used only from within assert() statements.
*/
-SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeHoldsMutex(Btree *p){
assert( p->sharable==0 || p->locked==0 || p->wantToLock>0 );
assert( p->sharable==0 || p->locked==0 || p->db==p->pBt->db );
- assert( p->sharable==0 || p->locked==0 || sqlite3_mutex_held(p->pBt->mutex) );
- assert( p->sharable==0 || p->locked==0 || sqlite3_mutex_held(p->db->mutex) );
+ assert( p->sharable==0 || p->locked==0 || tdsqlite3_mutex_held(p->pBt->mutex) );
+ assert( p->sharable==0 || p->locked==0 || tdsqlite3_mutex_held(p->db->mutex) );
return (p->sharable==0 || p->locked);
}
@@ -61209,24 +67570,35 @@ SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree *p){
** two or more btrees in common both try to lock all their btrees
** at the same instant.
*/
-SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){
+static void SQLITE_NOINLINE btreeEnterAll(tdsqlite3 *db){
int i;
+ int skipOk = 1;
Btree *p;
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
for(i=0; i<db->nDb; i++){
p = db->aDb[i].pBt;
- if( p ) sqlite3BtreeEnter(p);
+ if( p && p->sharable ){
+ tdsqlite3BtreeEnter(p);
+ skipOk = 0;
+ }
}
+ db->noSharedCache = skipOk;
}
-SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3BtreeEnterAll(tdsqlite3 *db){
+ if( db->noSharedCache==0 ) btreeEnterAll(db);
+}
+static void SQLITE_NOINLINE btreeLeaveAll(tdsqlite3 *db){
int i;
Btree *p;
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
for(i=0; i<db->nDb; i++){
p = db->aDb[i].pBt;
- if( p ) sqlite3BtreeLeave(p);
+ if( p ) tdsqlite3BtreeLeave(p);
}
}
+SQLITE_PRIVATE void tdsqlite3BtreeLeaveAll(tdsqlite3 *db){
+ if( db->noSharedCache==0 ) btreeLeaveAll(db);
+}
#ifndef NDEBUG
/*
@@ -61235,16 +67607,16 @@ SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3 *db){
**
** This routine is used inside assert() statements only.
*/
-SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3 *db){
+SQLITE_PRIVATE int tdsqlite3BtreeHoldsAllMutexes(tdsqlite3 *db){
int i;
- if( !sqlite3_mutex_held(db->mutex) ){
+ if( !tdsqlite3_mutex_held(db->mutex) ){
return 0;
}
for(i=0; i<db->nDb; i++){
Btree *p;
p = db->aDb[i].pBt;
if( p && p->sharable &&
- (p->wantToLock==0 || !sqlite3_mutex_held(p->pBt->mutex)) ){
+ (p->wantToLock==0 || !tdsqlite3_mutex_held(p->pBt->mutex)) ){
return 0;
}
}
@@ -61262,14 +67634,14 @@ SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3 *db){
** (2) if iDb!=1, then the mutex on db->aDb[iDb].pBt.
**
** If pSchema is not NULL, then iDb is computed from pSchema and
-** db using sqlite3SchemaToIndex().
+** db using tdsqlite3SchemaToIndex().
*/
-SQLITE_PRIVATE int sqlite3SchemaMutexHeld(sqlite3 *db, int iDb, Schema *pSchema){
+SQLITE_PRIVATE int tdsqlite3SchemaMutexHeld(tdsqlite3 *db, int iDb, Schema *pSchema){
Btree *p;
assert( db!=0 );
- if( pSchema ) iDb = sqlite3SchemaToIndex(db, pSchema);
+ if( pSchema ) iDb = tdsqlite3SchemaToIndex(db, pSchema);
assert( iDb>=0 && iDb<db->nDb );
- if( !sqlite3_mutex_held(db->mutex) ) return 0;
+ if( !tdsqlite3_mutex_held(db->mutex) ) return 0;
if( iDb==1 ) return 1;
p = db->aDb[iDb].pBt;
assert( p!=0 );
@@ -61288,10 +67660,10 @@ SQLITE_PRIVATE int sqlite3SchemaMutexHeld(sqlite3 *db, int iDb, Schema *pSchema)
** the ones below, are no-ops and are null #defines in btree.h.
*/
-SQLITE_PRIVATE void sqlite3BtreeEnter(Btree *p){
+SQLITE_PRIVATE void tdsqlite3BtreeEnter(Btree *p){
p->pBt->db = p->db;
}
-SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3BtreeEnterAll(tdsqlite3 *db){
int i;
for(i=0; i<db->nDb; i++){
Btree *p = db->aDb[i].pBt;
@@ -61310,12 +67682,12 @@ SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){
** any time OMIT_SHARED_CACHE is not defined, regardless of whether or not
** the build is threadsafe. Leave() is only required by threadsafe builds.
*/
-SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor *pCur){
- sqlite3BtreeEnter(pCur->pBtree);
+SQLITE_PRIVATE void tdsqlite3BtreeEnterCursor(BtCursor *pCur){
+ tdsqlite3BtreeEnter(pCur->pBtree);
}
# if SQLITE_THREADSAFE
-SQLITE_PRIVATE void sqlite3BtreeLeaveCursor(BtCursor *pCur){
- sqlite3BtreeLeave(pCur->pBtree);
+SQLITE_PRIVATE void tdsqlite3BtreeLeaveCursor(BtCursor *pCur){
+ tdsqlite3BtreeLeave(pCur->pBtree);
}
# endif
#endif /* ifndef SQLITE_OMIT_INCRBLOB */
@@ -61352,8 +67724,8 @@ static const char zMagicHeader[] = SQLITE_FILE_HEADER;
** macro.
*/
#if 0
-int sqlite3BtreeTrace=1; /* True to enable tracing */
-# define TRACE(X) if(sqlite3BtreeTrace){printf X;fflush(stdout);}
+int tdsqlite3BtreeTrace=1; /* True to enable tracing */
+# define TRACE(X) if(tdsqlite3BtreeTrace){printf X;fflush(stdout);}
#else
# define TRACE(X)
#endif
@@ -61398,9 +67770,9 @@ int sqlite3BtreeTrace=1; /* True to enable tracing */
** Access to this variable is protected by SQLITE_MUTEX_STATIC_MASTER.
*/
#ifdef SQLITE_TEST
-SQLITE_PRIVATE BtShared *SQLITE_WSD sqlite3SharedCacheList = 0;
+SQLITE_PRIVATE BtShared *SQLITE_WSD tdsqlite3SharedCacheList = 0;
#else
-static BtShared *SQLITE_WSD sqlite3SharedCacheList = 0;
+static BtShared *SQLITE_WSD tdsqlite3SharedCacheList = 0;
#endif
#endif /* SQLITE_OMIT_SHARED_CACHE */
@@ -61410,10 +67782,10 @@ static BtShared *SQLITE_WSD sqlite3SharedCacheList = 0;
**
** This routine has no effect on existing database connections.
** The shared cache setting effects only future calls to
-** sqlite3_open(), sqlite3_open16(), or sqlite3_open_v2().
+** tdsqlite3_open(), tdsqlite3_open16(), or tdsqlite3_open_v2().
*/
-SQLITE_API int sqlite3_enable_shared_cache(int enable){
- sqlite3GlobalConfig.sharedCacheEnabled = enable;
+SQLITE_API int tdsqlite3_enable_shared_cache(int enable){
+ tdsqlite3GlobalConfig.sharedCacheEnabled = enable;
return SQLITE_OK;
}
#endif
@@ -61438,6 +67810,34 @@ SQLITE_API int sqlite3_enable_shared_cache(int enable){
#define hasReadConflicts(a, b) 0
#endif
+/*
+** Implementation of the SQLITE_CORRUPT_PAGE() macro. Takes a single
+** (MemPage*) as an argument. The (MemPage*) must not be NULL.
+**
+** If SQLITE_DEBUG is not defined, then this macro is equivalent to
+** SQLITE_CORRUPT_BKPT. Or, if SQLITE_DEBUG is set, then the log message
+** normally produced as a side-effect of SQLITE_CORRUPT_BKPT is augmented
+** with the page number and filename associated with the (MemPage*).
+*/
+#ifdef SQLITE_DEBUG
+int corruptPageError(int lineno, MemPage *p){
+ char *zMsg;
+ tdsqlite3BeginBenignMalloc();
+ zMsg = tdsqlite3_mprintf("database corruption page %d of %s",
+ (int)p->pgno, tdsqlite3PagerFilename(p->pBt->pPager, 0)
+ );
+ tdsqlite3EndBenignMalloc();
+ if( zMsg ){
+ tdsqlite3ReportError(SQLITE_CORRUPT, lineno, zMsg);
+ }
+ tdsqlite3_free(zMsg);
+ return SQLITE_CORRUPT_BKPT;
+}
+# define SQLITE_CORRUPT_PAGE(pMemPage) corruptPageError(__LINE__, pMemPage)
+#else
+# define SQLITE_CORRUPT_PAGE(pMemPage) SQLITE_CORRUPT_PGNO(pMemPage->pgno)
+#endif
+
#ifndef SQLITE_OMIT_SHARED_CACHE
#ifdef SQLITE_DEBUG
@@ -61478,7 +67878,7 @@ static int hasSharedCacheTableLock(
** Return true immediately.
*/
if( (pBtree->sharable==0)
- || (eLockType==READ_LOCK && (pBtree->db->flags & SQLITE_ReadUncommitted))
+ || (eLockType==READ_LOCK && (pBtree->db->flags & SQLITE_ReadUncommit))
){
return 1;
}
@@ -61555,7 +67955,7 @@ static int hasReadConflicts(Btree *pBtree, Pgno iRoot){
for(p=pBtree->pBt->pCursor; p; p=p->pNext){
if( p->pgnoRoot==iRoot
&& p->pBtree!=pBtree
- && 0==(p->pBtree->db->flags & SQLITE_ReadUncommitted)
+ && 0==(p->pBtree->db->flags & SQLITE_ReadUncommit)
){
return 1;
}
@@ -61574,10 +67974,10 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){
BtShared *pBt = p->pBt;
BtLock *pIter;
- assert( sqlite3BtreeHoldsMutex(p) );
+ assert( tdsqlite3BtreeHoldsMutex(p) );
assert( eLock==READ_LOCK || eLock==WRITE_LOCK );
assert( p->db!=0 );
- assert( !(p->db->flags&SQLITE_ReadUncommitted)||eLock==WRITE_LOCK||iTab==1 );
+ assert( !(p->db->flags&SQLITE_ReadUncommit)||eLock==WRITE_LOCK||iTab==1 );
/* If requesting a write-lock, then the Btree must have an open write
** transaction on this file. And, obviously, for this to be so there
@@ -61595,7 +67995,7 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){
** requested lock may not be obtained.
*/
if( pBt->pWriter!=p && (pBt->btsFlags & BTS_EXCLUSIVE)!=0 ){
- sqlite3ConnectionBlocked(p->db, pBt->pWriter->db);
+ tdsqlite3ConnectionBlocked(p->db, pBt->pWriter->db);
return SQLITE_LOCKED_SHAREDCACHE;
}
@@ -61612,7 +68012,7 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){
assert( pIter->eLock==READ_LOCK || pIter->eLock==WRITE_LOCK );
assert( eLock==READ_LOCK || pIter->pBtree==p || pIter->eLock==READ_LOCK);
if( pIter->pBtree!=p && pIter->iTable==iTab && pIter->eLock!=eLock ){
- sqlite3ConnectionBlocked(p->db, pIter->pBtree->db);
+ tdsqlite3ConnectionBlocked(p->db, pIter->pBtree->db);
if( eLock==WRITE_LOCK ){
assert( p==pBt->pWriter );
pBt->btsFlags |= BTS_PENDING;
@@ -61647,7 +68047,7 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){
BtLock *pLock = 0;
BtLock *pIter;
- assert( sqlite3BtreeHoldsMutex(p) );
+ assert( tdsqlite3BtreeHoldsMutex(p) );
assert( eLock==READ_LOCK || eLock==WRITE_LOCK );
assert( p->db!=0 );
@@ -61655,7 +68055,7 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){
** obtain a read-lock using this function. The only read-lock obtained
** by a connection in read-uncommitted mode is on the sqlite_master
** table, and that lock is obtained in BtreeBeginTrans(). */
- assert( 0==(p->db->flags&SQLITE_ReadUncommitted) || eLock==WRITE_LOCK );
+ assert( 0==(p->db->flags&SQLITE_ReadUncommit) || eLock==WRITE_LOCK );
/* This function should only be called on a sharable b-tree after it
** has been determined that no other b-tree holds a conflicting lock. */
@@ -61674,7 +68074,7 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){
** with table iTable, allocate one and link it into the list.
*/
if( !pLock ){
- pLock = (BtLock *)sqlite3MallocZero(sizeof(BtLock));
+ pLock = (BtLock *)tdsqlite3MallocZero(sizeof(BtLock));
if( !pLock ){
return SQLITE_NOMEM_BKPT;
}
@@ -61710,7 +68110,7 @@ static void clearAllSharedCacheTableLocks(Btree *p){
BtShared *pBt = p->pBt;
BtLock **ppIter = &pBt->pLock;
- assert( sqlite3BtreeHoldsMutex(p) );
+ assert( tdsqlite3BtreeHoldsMutex(p) );
assert( p->sharable || 0==*ppIter );
assert( p->inTrans>0 );
@@ -61722,7 +68122,7 @@ static void clearAllSharedCacheTableLocks(Btree *p){
*ppIter = pLock->pNext;
assert( pLock->iTable!=1 || pLock==&p->lock );
if( pLock->iTable!=1 ){
- sqlite3_free(pLock);
+ tdsqlite3_free(pLock);
}
}else{
ppIter = &pLock->pNext;
@@ -61765,7 +68165,9 @@ static void downgradeAllSharedCacheTableLocks(Btree *p){
#endif /* SQLITE_OMIT_SHARED_CACHE */
-static void releasePage(MemPage *pPage); /* Forward reference */
+static void releasePage(MemPage *pPage); /* Forward reference */
+static void releasePageOne(MemPage *pPage); /* Forward reference */
+static void releasePageNotNull(MemPage *pPage); /* Forward reference */
/*
***** This routine is used inside of assert() only ****
@@ -61774,7 +68176,7 @@ static void releasePage(MemPage *pPage); /* Forward reference */
*/
#ifdef SQLITE_DEBUG
static int cursorHoldsMutex(BtCursor *p){
- return sqlite3_mutex_held(p->pBt->mutex);
+ return tdsqlite3_mutex_held(p->pBt->mutex);
}
/* Verify that the cursor and the BtShared agree about what is the current
@@ -61803,7 +68205,7 @@ static int cursorOwnsBtShared(BtCursor *p){
*/
static void invalidateAllOverflowCache(BtShared *pBt){
BtCursor *p;
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
for(p=pBt->pCursor; p; p=p->pNext){
invalidateOverflowCache(p);
}
@@ -61825,17 +68227,18 @@ static void invalidateAllOverflowCache(BtShared *pBt){
*/
static void invalidateIncrblobCursors(
Btree *pBtree, /* The database file to check */
+ Pgno pgnoRoot, /* The table that might be changing */
i64 iRow, /* The rowid that might be changing */
int isClearTable /* True if all rows are being deleted */
){
BtCursor *p;
if( pBtree->hasIncrblobCur==0 ) return;
- assert( sqlite3BtreeHoldsMutex(pBtree) );
+ assert( tdsqlite3BtreeHoldsMutex(pBtree) );
pBtree->hasIncrblobCur = 0;
for(p=pBtree->pBt->pCursor; p; p=p->pNext){
if( (p->curFlags & BTCF_Incrblob)!=0 ){
pBtree->hasIncrblobCur = 1;
- if( isClearTable || p->info.nKey==iRow ){
+ if( p->pgnoRoot==pgnoRoot && (isClearTable || p->info.nKey==iRow) ){
p->eState = CURSOR_INVALID;
}
}
@@ -61844,7 +68247,7 @@ static void invalidateIncrblobCursors(
#else
/* Stub function when INCRBLOB is omitted */
- #define invalidateIncrblobCursors(x,y,z)
+ #define invalidateIncrblobCursors(w,x,y,z)
#endif /* SQLITE_OMIT_INCRBLOB */
/*
@@ -61886,13 +68289,13 @@ static int btreeSetHasContent(BtShared *pBt, Pgno pgno){
int rc = SQLITE_OK;
if( !pBt->pHasContent ){
assert( pgno<=pBt->nPage );
- pBt->pHasContent = sqlite3BitvecCreate(pBt->nPage);
+ pBt->pHasContent = tdsqlite3BitvecCreate(pBt->nPage);
if( !pBt->pHasContent ){
rc = SQLITE_NOMEM_BKPT;
}
}
- if( rc==SQLITE_OK && pgno<=sqlite3BitvecSize(pBt->pHasContent) ){
- rc = sqlite3BitvecSet(pBt->pHasContent, pgno);
+ if( rc==SQLITE_OK && pgno<=tdsqlite3BitvecSize(pBt->pHasContent) ){
+ rc = tdsqlite3BitvecSet(pBt->pHasContent, pgno);
}
return rc;
}
@@ -61906,7 +68309,7 @@ static int btreeSetHasContent(BtShared *pBt, Pgno pgno){
*/
static int btreeGetHasContent(BtShared *pBt, Pgno pgno){
Bitvec *p = pBt->pHasContent;
- return (p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTest(p, pgno)));
+ return (p && (pgno>tdsqlite3BitvecSize(p) || tdsqlite3BitvecTest(p, pgno)));
}
/*
@@ -61914,7 +68317,7 @@ static int btreeGetHasContent(BtShared *pBt, Pgno pgno){
** invoked at the conclusion of each write-transaction.
*/
static void btreeClearHasContent(BtShared *pBt){
- sqlite3BitvecDestroy(pBt->pHasContent);
+ tdsqlite3BitvecDestroy(pBt->pHasContent);
pBt->pHasContent = 0;
}
@@ -61923,11 +68326,13 @@ static void btreeClearHasContent(BtShared *pBt){
*/
static void btreeReleaseAllCursorPages(BtCursor *pCur){
int i;
- for(i=0; i<=pCur->iPage; i++){
- releasePage(pCur->apPage[i]);
- pCur->apPage[i] = 0;
+ if( pCur->iPage>=0 ){
+ for(i=0; i<pCur->iPage; i++){
+ releasePageNotNull(pCur->apPage[i]);
+ }
+ releasePageNotNull(pCur->pPage);
+ pCur->iPage = -1;
}
- pCur->iPage = -1;
}
/*
@@ -61951,18 +68356,24 @@ static int saveCursorKey(BtCursor *pCur){
if( pCur->curIntKey ){
/* Only the rowid is required for a table btree */
- pCur->nKey = sqlite3BtreeIntegerKey(pCur);
- }else{
- /* For an index btree, save the complete key content */
+ pCur->nKey = tdsqlite3BtreeIntegerKey(pCur);
+ }else{
+ /* For an index btree, save the complete key content. It is possible
+ ** that the current key is corrupt. In that case, it is possible that
+ ** the tdsqlite3VdbeRecordUnpack() function may overread the buffer by
+ ** up to the size of 1 varint plus 1 8-byte value when the cursor
+ ** position is restored. Hence the 17 bytes of padding allocated
+ ** below. */
void *pKey;
- pCur->nKey = sqlite3BtreePayloadSize(pCur);
- pKey = sqlite3Malloc( pCur->nKey );
+ pCur->nKey = tdsqlite3BtreePayloadSize(pCur);
+ pKey = tdsqlite3Malloc( pCur->nKey + 9 + 8 );
if( pKey ){
- rc = sqlite3BtreeKey(pCur, 0, (int)pCur->nKey, pKey);
+ rc = tdsqlite3BtreePayload(pCur, 0, (int)pCur->nKey, pKey);
if( rc==SQLITE_OK ){
+ memset(((u8*)pKey)+pCur->nKey, 0, 9+8);
pCur->pKey = pKey;
}else{
- sqlite3_free(pKey);
+ tdsqlite3_free(pKey);
}
}else{
rc = SQLITE_NOMEM_BKPT;
@@ -61986,6 +68397,9 @@ static int saveCursorPosition(BtCursor *pCur){
assert( 0==pCur->pKey );
assert( cursorHoldsMutex(pCur) );
+ if( pCur->curFlags & BTCF_Pinned ){
+ return SQLITE_CONSTRAINT_PINNED;
+ }
if( pCur->eState==CURSOR_SKIPNEXT ){
pCur->eState = CURSOR_VALID;
}else{
@@ -62028,7 +68442,7 @@ static int SQLITE_NOINLINE saveCursorsOnList(BtCursor*,Pgno,BtCursor*);
*/
static int saveAllCursors(BtShared *pBt, Pgno iRoot, BtCursor *pExcept){
BtCursor *p;
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
assert( pExcept==0 || pExcept->pBt==pBt );
for(p=pBt->pCursor; p; p=p->pNext){
if( p!=pExcept && (0==iRoot || p->pgnoRoot==iRoot) ) break;
@@ -62056,7 +68470,7 @@ static int SQLITE_NOINLINE saveCursorsOnList(
return rc;
}
}else{
- testcase( p->iPage>0 );
+ testcase( p->iPage>=0 );
btreeReleaseAllCursorPages(p);
}
}
@@ -62068,9 +68482,9 @@ static int SQLITE_NOINLINE saveCursorsOnList(
/*
** Clear the current cursor position.
*/
-SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *pCur){
+SQLITE_PRIVATE void tdsqlite3BtreeClearCursor(BtCursor *pCur){
assert( cursorHoldsMutex(pCur) );
- sqlite3_free(pCur->pKey);
+ tdsqlite3_free(pCur->pKey);
pCur->pKey = 0;
pCur->eState = CURSOR_INVALID;
}
@@ -62089,26 +68503,24 @@ static int btreeMoveto(
){
int rc; /* Status code */
UnpackedRecord *pIdxKey; /* Unpacked index key */
- char aSpace[384]; /* Temp space for pIdxKey - to avoid a malloc */
- char *pFree = 0;
if( pKey ){
+ KeyInfo *pKeyInfo = pCur->pKeyInfo;
assert( nKey==(i64)(int)nKey );
- pIdxKey = sqlite3VdbeAllocUnpackedRecord(
- pCur->pKeyInfo, aSpace, sizeof(aSpace), &pFree
- );
+ pIdxKey = tdsqlite3VdbeAllocUnpackedRecord(pKeyInfo);
if( pIdxKey==0 ) return SQLITE_NOMEM_BKPT;
- sqlite3VdbeRecordUnpack(pCur->pKeyInfo, (int)nKey, pKey, pIdxKey);
- if( pIdxKey->nField==0 ){
- sqlite3DbFree(pCur->pKeyInfo->db, pFree);
- return SQLITE_CORRUPT_BKPT;
+ tdsqlite3VdbeRecordUnpack(pKeyInfo, (int)nKey, pKey, pIdxKey);
+ if( pIdxKey->nField==0 || pIdxKey->nField>pKeyInfo->nAllField ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto moveto_done;
}
}else{
pIdxKey = 0;
}
- rc = sqlite3BtreeMovetoUnpacked(pCur, pIdxKey, nKey, bias, pRes);
- if( pFree ){
- sqlite3DbFree(pCur->pKeyInfo->db, pFree);
+ rc = tdsqlite3BtreeMovetoUnpacked(pCur, pIdxKey, nKey, bias, pRes);
+moveto_done:
+ if( pIdxKey ){
+ tdsqlite3DbFree(pCur->pKeyInfo->db, pIdxKey);
}
return rc;
}
@@ -62122,19 +68534,23 @@ static int btreeMoveto(
*/
static int btreeRestoreCursorPosition(BtCursor *pCur){
int rc;
- int skipNext;
+ int skipNext = 0;
assert( cursorOwnsBtShared(pCur) );
assert( pCur->eState>=CURSOR_REQUIRESEEK );
if( pCur->eState==CURSOR_FAULT ){
return pCur->skipNext;
}
pCur->eState = CURSOR_INVALID;
- rc = btreeMoveto(pCur, pCur->pKey, pCur->nKey, 0, &skipNext);
+ if( tdsqlite3FaultSim(410) ){
+ rc = SQLITE_IOERR;
+ }else{
+ rc = btreeMoveto(pCur, pCur->pKey, pCur->nKey, 0, &skipNext);
+ }
if( rc==SQLITE_OK ){
- sqlite3_free(pCur->pKey);
+ tdsqlite3_free(pCur->pKey);
pCur->pKey = 0;
assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_INVALID );
- pCur->skipNext |= skipNext;
+ if( skipNext ) pCur->skipNext = skipNext;
if( pCur->skipNext && pCur->eState==CURSOR_VALID ){
pCur->eState = CURSOR_SKIPNEXT;
}
@@ -62156,11 +68572,26 @@ static int btreeRestoreCursorPosition(BtCursor *pCur){
**
** Calling this routine with a NULL cursor pointer returns false.
**
-** Use the separate sqlite3BtreeCursorRestore() routine to restore a cursor
+** Use the separate tdsqlite3BtreeCursorRestore() routine to restore a cursor
** back to where it ought to be if this routine returns true.
*/
-SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor *pCur){
- return pCur->eState!=CURSOR_VALID;
+SQLITE_PRIVATE int tdsqlite3BtreeCursorHasMoved(BtCursor *pCur){
+ assert( EIGHT_BYTE_ALIGNMENT(pCur)
+ || pCur==tdsqlite3BtreeFakeValidCursor() );
+ assert( offsetof(BtCursor, eState)==0 );
+ assert( sizeof(pCur->eState)==1 );
+ return CURSOR_VALID != *(u8*)pCur;
+}
+
+/*
+** Return a pointer to a fake BtCursor object that will always answer
+** false to the tdsqlite3BtreeCursorHasMoved() routine above. The fake
+** cursor returned must not be used with any other Btree interface.
+*/
+SQLITE_PRIVATE BtCursor *tdsqlite3BtreeFakeValidCursor(void){
+ static u8 fakeCursor = CURSOR_VALID;
+ assert( offsetof(BtCursor, eState)==0 );
+ return (BtCursor*)&fakeCursor;
}
/*
@@ -62174,9 +68605,9 @@ SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor *pCur){
** nearby row.
**
** This routine should only be called for a cursor that just returned
-** TRUE from sqlite3BtreeCursorHasMoved().
+** TRUE from tdsqlite3BtreeCursorHasMoved().
*/
-SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor *pCur, int *pDifferentRow){
+SQLITE_PRIVATE int tdsqlite3BtreeCursorRestore(BtCursor *pCur, int *pDifferentRow){
int rc;
assert( pCur!=0 );
@@ -62189,7 +68620,6 @@ SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor *pCur, int *pDifferentRow)
if( pCur->eState!=CURSOR_VALID ){
*pDifferentRow = 1;
}else{
- assert( pCur->skipNext==0 );
*pDifferentRow = 0;
}
return SQLITE_OK;
@@ -62201,7 +68631,7 @@ SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor *pCur, int *pDifferentRow)
** and number of the varargs parameters) is determined by the eHintType
** parameter. See the definitions of the BTREE_HINT_* macros for details.
*/
-SQLITE_PRIVATE void sqlite3BtreeCursorHint(BtCursor *pCur, int eHintType, ...){
+SQLITE_PRIVATE void tdsqlite3BtreeCursorHint(BtCursor *pCur, int eHintType, ...){
/* Used only by system that substitute their own storage engine */
}
#endif
@@ -62209,7 +68639,7 @@ SQLITE_PRIVATE void sqlite3BtreeCursorHint(BtCursor *pCur, int eHintType, ...){
/*
** Provide flag hints to the cursor.
*/
-SQLITE_PRIVATE void sqlite3BtreeCursorHintFlags(BtCursor *pCur, unsigned x){
+SQLITE_PRIVATE void tdsqlite3BtreeCursorHintFlags(BtCursor *pCur, unsigned x){
assert( x==BTREE_SEEK_EQ || x==BTREE_BULKLOAD || x==0 );
pCur->hints = x;
}
@@ -62228,7 +68658,7 @@ SQLITE_PRIVATE void sqlite3BtreeCursorHintFlags(BtCursor *pCur, unsigned x){
static Pgno ptrmapPageno(BtShared *pBt, Pgno pgno){
int nPagesPerMapPage;
Pgno iPtrMap, ret;
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
if( pgno<2 ) return 0;
nPagesPerMapPage = (pBt->usableSize/5)+1;
iPtrMap = (pgno-2)/nPagesPerMapPage;
@@ -62258,7 +68688,7 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){
if( *pRC ) return;
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
/* The master-journal page number must never be used as a pointer map page */
assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) );
@@ -62268,22 +68698,29 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){
return;
}
iPtrmap = PTRMAP_PAGENO(pBt, key);
- rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0);
+ rc = tdsqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0);
if( rc!=SQLITE_OK ){
*pRC = rc;
return;
}
+ if( ((char*)tdsqlite3PagerGetExtra(pDbPage))[0]!=0 ){
+ /* The first byte of the extra data is the MemPage.isInit byte.
+ ** If that byte is set, it means this page is also being used
+ ** as a btree page. */
+ *pRC = SQLITE_CORRUPT_BKPT;
+ goto ptrmap_exit;
+ }
offset = PTRMAP_PTROFFSET(iPtrmap, key);
if( offset<0 ){
*pRC = SQLITE_CORRUPT_BKPT;
goto ptrmap_exit;
}
assert( offset <= (int)pBt->usableSize-5 );
- pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);
+ pPtrmap = (u8 *)tdsqlite3PagerGetData(pDbPage);
if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){
TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent));
- *pRC= rc = sqlite3PagerWrite(pDbPage);
+ *pRC= rc = tdsqlite3PagerWrite(pDbPage);
if( rc==SQLITE_OK ){
pPtrmap[offset] = eType;
put4byte(&pPtrmap[offset+1], parent);
@@ -62291,7 +68728,7 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){
}
ptrmap_exit:
- sqlite3PagerUnref(pDbPage);
+ tdsqlite3PagerUnref(pDbPage);
}
/*
@@ -62308,18 +68745,18 @@ static int ptrmapGet(BtShared *pBt, Pgno key, u8 *pEType, Pgno *pPgno){
int offset; /* Offset of entry in pointer map */
int rc;
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
iPtrmap = PTRMAP_PAGENO(pBt, key);
- rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0);
+ rc = tdsqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage, 0);
if( rc!=0 ){
return rc;
}
- pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);
+ pPtrmap = (u8 *)tdsqlite3PagerGetData(pDbPage);
offset = PTRMAP_PTROFFSET(iPtrmap, key);
if( offset<0 ){
- sqlite3PagerUnref(pDbPage);
+ tdsqlite3PagerUnref(pDbPage);
return SQLITE_CORRUPT_BKPT;
}
assert( offset <= (int)pBt->usableSize-5 );
@@ -62327,15 +68764,15 @@ static int ptrmapGet(BtShared *pBt, Pgno key, u8 *pEType, Pgno *pPgno){
*pEType = pPtrmap[offset];
if( pPgno ) *pPgno = get4byte(&pPtrmap[offset+1]);
- sqlite3PagerUnref(pDbPage);
- if( *pEType<1 || *pEType>5 ) return SQLITE_CORRUPT_BKPT;
+ tdsqlite3PagerUnref(pDbPage);
+ if( *pEType<1 || *pEType>5 ) return SQLITE_CORRUPT_PGNO(iPtrmap);
return SQLITE_OK;
}
#else /* if defined SQLITE_OMIT_AUTOVACUUM */
#define ptrmapPut(w,x,y,z,rc)
#define ptrmapGet(w,x,y,z) SQLITE_OK
- #define ptrmapPutOvflPtr(x, y, rc)
+ #define ptrmapPutOvflPtr(x, y, z, rc)
#endif
/*
@@ -62410,7 +68847,7 @@ static void btreeParseCellPtrNoPayload(
u8 *pCell, /* Pointer to the cell text. */
CellInfo *pInfo /* Fill in this structure */
){
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
assert( pPage->leaf==0 );
assert( pPage->childPtrSize==4 );
#ifndef SQLITE_DEBUG
@@ -62431,7 +68868,7 @@ static void btreeParseCellPtr(
u32 nPayload; /* Number of bytes of cell payload */
u64 iKey; /* Extracted Key value */
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
assert( pPage->leaf==0 || pPage->leaf==1 );
assert( pPage->intKeyLeaf );
assert( pPage->childPtrSize==0 );
@@ -62498,7 +68935,7 @@ static void btreeParseCellPtrIndex(
u8 *pIter; /* For scanning through pCell */
u32 nPayload; /* Number of bytes of cell payload */
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
assert( pPage->leaf==0 || pPage->leaf==1 );
assert( pPage->intKeyLeaf==0 );
pIter = pCell + pPage->childPtrSize;
@@ -62628,17 +69065,24 @@ static u16 cellSize(MemPage *pPage, int iCell){
#ifndef SQLITE_OMIT_AUTOVACUUM
/*
-** If the cell pCell, part of page pPage contains a pointer
-** to an overflow page, insert an entry into the pointer-map
-** for the overflow page.
+** The cell pCell is currently part of page pSrc but will ultimately be part
+** of pPage. (pSrc and pPager are often the same.) If pCell contains a
+** pointer to an overflow page, insert an entry into the pointer-map for
+** the overflow page that will be valid after pCell has been moved to pPage.
*/
-static void ptrmapPutOvflPtr(MemPage *pPage, u8 *pCell, int *pRC){
+static void ptrmapPutOvflPtr(MemPage *pPage, MemPage *pSrc, u8 *pCell,int *pRC){
CellInfo info;
if( *pRC ) return;
assert( pCell!=0 );
pPage->xParseCell(pPage, pCell, &info);
if( info.nLocal<info.nPayload ){
- Pgno ovfl = get4byte(&pCell[info.nSize-4]);
+ Pgno ovfl;
+ if( SQLITE_WITHIN(pSrc->aDataEnd, pCell, pCell+info.nLocal) ){
+ testcase( pSrc!=pPage );
+ *pRC = SQLITE_CORRUPT_BKPT;
+ return;
+ }
+ ovfl = get4byte(&pCell[info.nSize-4]);
ptrmapPut(pPage->pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, pRC);
}
}
@@ -62646,17 +69090,18 @@ static void ptrmapPutOvflPtr(MemPage *pPage, u8 *pCell, int *pRC){
/*
-** Defragment the page given. All Cells are moved to the
-** end of the page and all free space is collected into one
-** big FreeBlk that occurs in between the header and cell
-** pointer array and the cell content area.
+** Defragment the page given. This routine reorganizes cells within the
+** page so that there are no free-blocks on the free-block list.
+**
+** Parameter nMaxFrag is the maximum amount of fragmented space that may be
+** present in the page after this routine returns.
**
** EVIDENCE-OF: R-44582-60138 SQLite may from time to time reorganize a
** b-tree page so that there are no freeblocks or fragment bytes, all
** unused bytes are contained in the unallocated space region, and all
** cells are packed tightly at the end of the page.
*/
-static int defragmentPage(MemPage *pPage){
+static int defragmentPage(MemPage *pPage, int nMaxFrag){
int i; /* Loop counter */
int pc; /* Address of the i-th cell */
int hdr; /* Offset to the page header */
@@ -62671,21 +69116,64 @@ static int defragmentPage(MemPage *pPage){
int iCellFirst; /* First allowable cell index */
int iCellLast; /* Last possible cell index */
-
- assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( tdsqlite3PagerIswriteable(pPage->pDbPage) );
assert( pPage->pBt!=0 );
assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE );
assert( pPage->nOverflow==0 );
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
temp = 0;
src = data = pPage->aData;
hdr = pPage->hdrOffset;
cellOffset = pPage->cellOffset;
nCell = pPage->nCell;
- assert( nCell==get2byte(&data[hdr+3]) );
+ assert( nCell==get2byte(&data[hdr+3]) || CORRUPT_DB );
+ iCellFirst = cellOffset + 2*nCell;
usableSize = pPage->pBt->usableSize;
+
+ /* This block handles pages with two or fewer free blocks and nMaxFrag
+ ** or fewer fragmented bytes. In this case it is faster to move the
+ ** two (or one) blocks of cells using memmove() and add the required
+ ** offsets to each pointer in the cell-pointer array than it is to
+ ** reconstruct the entire page. */
+ if( (int)data[hdr+7]<=nMaxFrag ){
+ int iFree = get2byte(&data[hdr+1]);
+ if( iFree>usableSize-4 ) return SQLITE_CORRUPT_PAGE(pPage);
+ if( iFree ){
+ int iFree2 = get2byte(&data[iFree]);
+ if( iFree2>usableSize-4 ) return SQLITE_CORRUPT_PAGE(pPage);
+ if( 0==iFree2 || (data[iFree2]==0 && data[iFree2+1]==0) ){
+ u8 *pEnd = &data[cellOffset + nCell*2];
+ u8 *pAddr;
+ int sz2 = 0;
+ int sz = get2byte(&data[iFree+2]);
+ int top = get2byte(&data[hdr+5]);
+ if( NEVER(top>=iFree) ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ if( iFree2 ){
+ if( iFree+sz>iFree2 ) return SQLITE_CORRUPT_PAGE(pPage);
+ sz2 = get2byte(&data[iFree2+2]);
+ if( iFree2+sz2 > usableSize ) return SQLITE_CORRUPT_PAGE(pPage);
+ memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz));
+ sz += sz2;
+ }else if( NEVER(iFree+sz>usableSize) ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+
+ cbrk = top+sz;
+ assert( cbrk+(iFree-top) <= usableSize );
+ memmove(&data[cbrk], &data[top], iFree-top);
+ for(pAddr=&data[cellOffset]; pAddr<pEnd; pAddr+=2){
+ pc = get2byte(pAddr);
+ if( pc<iFree ){ put2byte(pAddr, pc+sz); }
+ else if( pc<iFree2 ){ put2byte(pAddr, pc+sz2); }
+ }
+ goto defragment_out;
+ }
+ }
+ }
+
cbrk = usableSize;
- iCellFirst = cellOffset + 2*nCell;
iCellLast = usableSize - 4;
for(i=0; i<nCell; i++){
u8 *pAddr; /* The i-th cell pointer */
@@ -62697,13 +69185,13 @@ static int defragmentPage(MemPage *pPage){
** if PRAGMA cell_size_check=ON.
*/
if( pc<iCellFirst || pc>iCellLast ){
- return SQLITE_CORRUPT_BKPT;
+ return SQLITE_CORRUPT_PAGE(pPage);
}
assert( pc>=iCellFirst && pc<=iCellLast );
size = pPage->xCellSize(pPage, &src[pc]);
cbrk -= size;
if( cbrk<iCellFirst || pc+size>usableSize ){
- return SQLITE_CORRUPT_BKPT;
+ return SQLITE_CORRUPT_PAGE(pPage);
}
assert( cbrk+size<=usableSize && cbrk>=iCellFirst );
testcase( cbrk+size==usableSize );
@@ -62712,23 +69200,26 @@ static int defragmentPage(MemPage *pPage){
if( temp==0 ){
int x;
if( cbrk==pc ) continue;
- temp = sqlite3PagerTempSpace(pPage->pBt->pPager);
+ temp = tdsqlite3PagerTempSpace(pPage->pBt->pPager);
x = get2byte(&data[hdr+5]);
memcpy(&temp[x], &data[x], (cbrk+size) - x);
src = temp;
}
memcpy(&data[cbrk], &src[pc], size);
}
+ data[hdr+7] = 0;
+
+ defragment_out:
+ assert( pPage->nFree>=0 );
+ if( data[hdr+7]+cbrk-iCellFirst!=pPage->nFree ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
assert( cbrk>=iCellFirst );
put2byte(&data[hdr+5], cbrk);
data[hdr+1] = 0;
data[hdr+2] = 0;
- data[hdr+7] = 0;
memset(&data[iCellFirst], 0, cbrk-iCellFirst);
- assert( sqlite3PagerIswriteable(pPage->pDbPage) );
- if( cbrk-iCellFirst!=pPage->nFree ){
- return SQLITE_CORRUPT_BKPT;
- }
+ assert( tdsqlite3PagerIswriteable(pPage->pDbPage) );
return SQLITE_OK;
}
@@ -62747,22 +69238,16 @@ static int defragmentPage(MemPage *pPage){
** causes the fragmentation count to exceed 60.
*/
static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){
- const int hdr = pPg->hdrOffset;
- u8 * const aData = pPg->aData;
- int iAddr = hdr + 1;
- int pc = get2byte(&aData[iAddr]);
- int x;
- int usableSize = pPg->pBt->usableSize;
+ const int hdr = pPg->hdrOffset; /* Offset to page header */
+ u8 * const aData = pPg->aData; /* Page data */
+ int iAddr = hdr + 1; /* Address of ptr to pc */
+ int pc = get2byte(&aData[iAddr]); /* Address of a free slot */
+ int x; /* Excess size of the slot */
+ int maxPC = pPg->pBt->usableSize - nByte; /* Max address for a usable slot */
+ int size; /* Size of the free slot */
assert( pc>0 );
- do{
- int size; /* Size of the free slot */
- /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of
- ** increasing offset. */
- if( pc>usableSize-4 || pc<iAddr+4 ){
- *pRc = SQLITE_CORRUPT_BKPT;
- return 0;
- }
+ while( pc<=maxPC ){
/* EVIDENCE-OF: R-22710-53328 The third and fourth bytes of each
** freeblock form a big-endian integer which is the size of the freeblock
** in bytes, including the 4-byte header. */
@@ -62770,10 +69255,7 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){
if( (x = size - nByte)>=0 ){
testcase( x==4 );
testcase( x==3 );
- if( pc < pPg->cellOffset+2*pPg->nCell || size+pc > usableSize ){
- *pRc = SQLITE_CORRUPT_BKPT;
- return 0;
- }else if( x<4 ){
+ if( x<4 ){
/* EVIDENCE-OF: R-11498-58022 In a well-formed b-tree page, the total
** number of bytes in fragments may not exceed 60. */
if( aData[hdr+7]>57 ) return 0;
@@ -62782,17 +69264,31 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){
** fragmented bytes within the page. */
memcpy(&aData[iAddr], &aData[pc], 2);
aData[hdr+7] += (u8)x;
+ }else if( x+pc > maxPC ){
+ /* This slot extends off the end of the usable part of the page */
+ *pRc = SQLITE_CORRUPT_PAGE(pPg);
+ return 0;
}else{
/* The slot remains on the free-list. Reduce its size to account
- ** for the portion used by the new allocation. */
+ ** for the portion used by the new allocation. */
put2byte(&aData[pc+2], x);
}
return &aData[pc + x];
}
iAddr = pc;
pc = get2byte(&aData[pc]);
- }while( pc );
-
+ if( pc<=iAddr+size ){
+ if( pc ){
+ /* The next slot in the chain is not past the end of the current slot */
+ *pRc = SQLITE_CORRUPT_PAGE(pPg);
+ }
+ return 0;
+ }
+ }
+ if( pc>maxPC+nByte-4 ){
+ /* The free slot chain extends off the end of the page */
+ *pRc = SQLITE_CORRUPT_PAGE(pPg);
+ }
return 0;
}
@@ -62816,9 +69312,9 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
int rc = SQLITE_OK; /* Integer return code */
int gap; /* First byte of gap between cell pointers and cell content */
- assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( tdsqlite3PagerIswriteable(pPage->pDbPage) );
assert( pPage->pBt );
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
assert( nByte>=0 ); /* Minimum cell size is 4 */
assert( pPage->nFree>=nByte );
assert( pPage->nOverflow==0 );
@@ -62833,18 +69329,18 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
** However, that integer is too large to be stored in a 2-byte unsigned
** integer, so a value of 0 is used in its place. */
top = get2byte(&data[hdr+5]);
- assert( top<=(int)pPage->pBt->usableSize ); /* Prevent by getAndInitPage() */
+ assert( top<=(int)pPage->pBt->usableSize ); /* by btreeComputeFreeSpace() */
if( gap>top ){
if( top==0 && pPage->pBt->usableSize==65536 ){
top = 65536;
}else{
- return SQLITE_CORRUPT_BKPT;
+ return SQLITE_CORRUPT_PAGE(pPage);
}
}
- /* If there is enough space between gap and top for one more cell pointer
- ** array entry offset, and if the freelist is not empty, then search the
- ** freelist looking for a free slot big enough to satisfy the request.
+ /* If there is enough space between gap and top for one more cell pointer,
+ ** and if the freelist is not empty, then search the
+ ** freelist looking for a slot big enough to satisfy the request.
*/
testcase( gap+2==top );
testcase( gap+1==top );
@@ -62852,9 +69348,14 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
if( (data[hdr+2] || data[hdr+1]) && gap+2<=top ){
u8 *pSpace = pageFindSlot(pPage, nByte, &rc);
if( pSpace ){
- assert( pSpace>=data && (pSpace - data)<65536 );
- *pIdx = (int)(pSpace - data);
- return SQLITE_OK;
+ int g2;
+ assert( pSpace+nByte<=data+pPage->pBt->usableSize );
+ *pIdx = g2 = (int)(pSpace-data);
+ if( NEVER(g2<=gap) ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }else{
+ return SQLITE_OK;
+ }
}else if( rc ){
return rc;
}
@@ -62866,15 +69367,16 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
testcase( gap+2+nByte==top );
if( gap+2+nByte>top ){
assert( pPage->nCell>0 || CORRUPT_DB );
- rc = defragmentPage(pPage);
+ assert( pPage->nFree>=0 );
+ rc = defragmentPage(pPage, MIN(4, pPage->nFree - (2+nByte)));
if( rc ) return rc;
top = get2byteNotZero(&data[hdr+5]);
- assert( gap+nByte<=top );
+ assert( gap+2+nByte<=top );
}
/* Allocate memory from the gap in between the cell pointer array
- ** and the cell content area. The btreeInitPage() call has already
+ ** and the cell content area. The btreeComputeFreeSpace() call has already
** validated the freelist. Given that the freelist is valid, there
** is no way that the allocation can extend off the end of the page.
** The assert() below verifies the previous sentence.
@@ -62893,7 +69395,7 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){
**
** Adjacent freeblocks are coalesced.
**
-** Note that even though the freeblock list was checked by btreeInitPage(),
+** Even though the freeblock list was checked by btreeComputeFreeSpace(),
** that routine will not detect overlap between cells or freeblocks. Nor
** does it detect cells or freeblocks that encrouch into the reserved bytes
** at the end of the page. So do additional corruption checks inside this
@@ -62905,23 +69407,17 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){
u8 hdr; /* Page header size. 0 or 100 */
u8 nFrag = 0; /* Reduction in fragmentation */
u16 iOrigSize = iSize; /* Original value of iSize */
- u32 iLast = pPage->pBt->usableSize-4; /* Largest possible freeblock offset */
+ u16 x; /* Offset to cell content area */
u32 iEnd = iStart + iSize; /* First byte past the iStart buffer */
unsigned char *data = pPage->aData; /* Page content */
assert( pPage->pBt!=0 );
- assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( tdsqlite3PagerIswriteable(pPage->pDbPage) );
assert( CORRUPT_DB || iStart>=pPage->hdrOffset+6+pPage->childPtrSize );
assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize );
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
assert( iSize>=4 ); /* Minimum cell size is 4 */
- assert( iStart<=iLast );
-
- /* Overwrite deleted information with zeros when the secure_delete
- ** option is enabled */
- if( pPage->pBt->btsFlags & BTS_SECURE_DELETE ){
- memset(&data[iStart], 0, iSize);
- }
+ assert( iStart<=pPage->pBt->usableSize-4 );
/* The list of freeblocks must be in ascending order. Find the
** spot on the list where iStart should be inserted.
@@ -62933,12 +69429,14 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){
}else{
while( (iFreeBlk = get2byte(&data[iPtr]))<iStart ){
if( iFreeBlk<iPtr+4 ){
- if( iFreeBlk==0 ) break;
- return SQLITE_CORRUPT_BKPT;
+ if( iFreeBlk==0 ) break; /* TH3: corrupt082.100 */
+ return SQLITE_CORRUPT_PAGE(pPage);
}
iPtr = iFreeBlk;
}
- if( iFreeBlk>iLast ) return SQLITE_CORRUPT_BKPT;
+ if( iFreeBlk>pPage->pBt->usableSize-4 ){ /* TH3: corrupt081.100 */
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
assert( iFreeBlk>iPtr || iFreeBlk==0 );
/* At this point:
@@ -62949,9 +69447,11 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){
*/
if( iFreeBlk && iEnd+3>=iFreeBlk ){
nFrag = iFreeBlk - iEnd;
- if( iEnd>iFreeBlk ) return SQLITE_CORRUPT_BKPT;
+ if( iEnd>iFreeBlk ) return SQLITE_CORRUPT_PAGE(pPage);
iEnd = iFreeBlk + get2byte(&data[iFreeBlk+2]);
- if( iEnd > pPage->pBt->usableSize ) return SQLITE_CORRUPT_BKPT;
+ if( NEVER(iEnd > pPage->pBt->usableSize) ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
iSize = iEnd - iStart;
iFreeBlk = get2byte(&data[iFreeBlk]);
}
@@ -62963,28 +69463,35 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){
if( iPtr>hdr+1 ){
int iPtrEnd = iPtr + get2byte(&data[iPtr+2]);
if( iPtrEnd+3>=iStart ){
- if( iPtrEnd>iStart ) return SQLITE_CORRUPT_BKPT;
+ if( iPtrEnd>iStart ) return SQLITE_CORRUPT_PAGE(pPage);
nFrag += iStart - iPtrEnd;
iSize = iEnd - iPtr;
iStart = iPtr;
}
}
- if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_BKPT;
+ if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_PAGE(pPage);
data[hdr+7] -= nFrag;
}
- if( iStart==get2byte(&data[hdr+5]) ){
+ x = get2byte(&data[hdr+5]);
+ if( iStart<=x ){
/* The new freeblock is at the beginning of the cell content area,
** so just extend the cell content area rather than create another
** freelist entry */
- if( iPtr!=hdr+1 ) return SQLITE_CORRUPT_BKPT;
+ if( iStart<x ) return SQLITE_CORRUPT_PAGE(pPage);
+ if( NEVER(iPtr!=hdr+1) ) return SQLITE_CORRUPT_PAGE(pPage);
put2byte(&data[hdr+1], iFreeBlk);
put2byte(&data[hdr+5], iEnd);
}else{
/* Insert the new freeblock into the freelist */
put2byte(&data[iPtr], iStart);
- put2byte(&data[iStart], iFreeBlk);
- put2byte(&data[iStart+2], iSize);
}
+ if( pPage->pBt->btsFlags & BTS_FAST_SECURE ){
+ /* Overwrite deleted information with zeros when the secure_delete
+ ** option is enabled */
+ memset(&data[iStart], 0, iSize);
+ }
+ put2byte(&data[iStart], iFreeBlk);
+ put2byte(&data[iStart+2], iSize);
pPage->nFree += iOrigSize;
return SQLITE_OK;
}
@@ -63005,7 +69512,7 @@ static int decodeFlags(MemPage *pPage, int flagByte){
BtShared *pBt; /* A copy of pPage->pBt */
assert( pPage->hdrOffset==(pPage->pgno==1 ? 100 : 0) );
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
pPage->leaf = (u8)(flagByte>>3); assert( PTF_LEAF == 1<<3 );
flagByte &= ~PTF_LEAF;
pPage->childPtrSize = 4-4*pPage->leaf;
@@ -63044,144 +69551,184 @@ static int decodeFlags(MemPage *pPage, int flagByte){
}else{
/* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is
** an error. */
- return SQLITE_CORRUPT_BKPT;
+ return SQLITE_CORRUPT_PAGE(pPage);
}
pPage->max1bytePayload = pBt->max1bytePayload;
return SQLITE_OK;
}
/*
-** Initialize the auxiliary information for a disk block.
-**
-** Return SQLITE_OK on success. If we see that the page does
-** not contain a well-formed database page, then return
-** SQLITE_CORRUPT. Note that a return of SQLITE_OK does not
-** guarantee that the page is well-formed. It only shows that
-** we failed to detect any corruption.
+** Compute the amount of freespace on the page. In other words, fill
+** in the pPage->nFree field.
*/
-static int btreeInitPage(MemPage *pPage){
+static int btreeComputeFreeSpace(MemPage *pPage){
+ int pc; /* Address of a freeblock within pPage->aData[] */
+ u8 hdr; /* Offset to beginning of page header */
+ u8 *data; /* Equal to pPage->aData */
+ int usableSize; /* Amount of usable space on each page */
+ int nFree; /* Number of unused bytes on the page */
+ int top; /* First byte of the cell content area */
+ int iCellFirst; /* First allowable cell or freeblock offset */
+ int iCellLast; /* Last possible cell or freeblock offset */
assert( pPage->pBt!=0 );
assert( pPage->pBt->db!=0 );
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- assert( pPage->pgno==sqlite3PagerPagenumber(pPage->pDbPage) );
- assert( pPage == sqlite3PagerGetExtra(pPage->pDbPage) );
- assert( pPage->aData == sqlite3PagerGetData(pPage->pDbPage) );
-
- if( !pPage->isInit ){
- u16 pc; /* Address of a freeblock within pPage->aData[] */
- u8 hdr; /* Offset to beginning of page header */
- u8 *data; /* Equal to pPage->aData */
- BtShared *pBt; /* The main btree structure */
- int usableSize; /* Amount of usable space on each page */
- u16 cellOffset; /* Offset from start of page to first cell pointer */
- int nFree; /* Number of unused bytes on the page */
- int top; /* First byte of the cell content area */
- int iCellFirst; /* First allowable cell or freeblock offset */
- int iCellLast; /* Last possible cell or freeblock offset */
-
- pBt = pPage->pBt;
-
- hdr = pPage->hdrOffset;
- data = pPage->aData;
- /* EVIDENCE-OF: R-28594-02890 The one-byte flag at offset 0 indicating
- ** the b-tree page type. */
- if( decodeFlags(pPage, data[hdr]) ) return SQLITE_CORRUPT_BKPT;
- assert( pBt->pageSize>=512 && pBt->pageSize<=65536 );
- pPage->maskPage = (u16)(pBt->pageSize - 1);
- pPage->nOverflow = 0;
- usableSize = pBt->usableSize;
- pPage->cellOffset = cellOffset = hdr + 8 + pPage->childPtrSize;
- pPage->aDataEnd = &data[usableSize];
- pPage->aCellIdx = &data[cellOffset];
- pPage->aDataOfst = &data[pPage->childPtrSize];
- /* EVIDENCE-OF: R-58015-48175 The two-byte integer at offset 5 designates
- ** the start of the cell content area. A zero value for this integer is
- ** interpreted as 65536. */
- top = get2byteNotZero(&data[hdr+5]);
- /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
- ** number of cells on the page. */
- pPage->nCell = get2byte(&data[hdr+3]);
- if( pPage->nCell>MX_CELL(pBt) ){
- /* To many cells for a single page. The page must be corrupt */
- return SQLITE_CORRUPT_BKPT;
- }
- testcase( pPage->nCell==MX_CELL(pBt) );
- /* EVIDENCE-OF: R-24089-57979 If a page contains no cells (which is only
- ** possible for a root page of a table that contains no rows) then the
- ** offset to the cell content area will equal the page size minus the
- ** bytes of reserved space. */
- assert( pPage->nCell>0 || top==usableSize || CORRUPT_DB );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( pPage->pgno==tdsqlite3PagerPagenumber(pPage->pDbPage) );
+ assert( pPage == tdsqlite3PagerGetExtra(pPage->pDbPage) );
+ assert( pPage->aData == tdsqlite3PagerGetData(pPage->pDbPage) );
+ assert( pPage->isInit==1 );
+ assert( pPage->nFree<0 );
- /* A malformed database page might cause us to read past the end
- ** of page when parsing a cell.
- **
- ** The following block of code checks early to see if a cell extends
- ** past the end of a page boundary and causes SQLITE_CORRUPT to be
- ** returned if it does.
- */
- iCellFirst = cellOffset + 2*pPage->nCell;
- iCellLast = usableSize - 4;
- if( pBt->db->flags & SQLITE_CellSizeCk ){
- int i; /* Index into the cell pointer array */
- int sz; /* Size of a cell */
-
- if( !pPage->leaf ) iCellLast--;
- for(i=0; i<pPage->nCell; i++){
- pc = get2byteAligned(&data[cellOffset+i*2]);
- testcase( pc==iCellFirst );
- testcase( pc==iCellLast );
- if( pc<iCellFirst || pc>iCellLast ){
- return SQLITE_CORRUPT_BKPT;
- }
- sz = pPage->xCellSize(pPage, &data[pc]);
- testcase( pc+sz==usableSize );
- if( pc+sz>usableSize ){
- return SQLITE_CORRUPT_BKPT;
- }
- }
- if( !pPage->leaf ) iCellLast++;
- }
+ usableSize = pPage->pBt->usableSize;
+ hdr = pPage->hdrOffset;
+ data = pPage->aData;
+ /* EVIDENCE-OF: R-58015-48175 The two-byte integer at offset 5 designates
+ ** the start of the cell content area. A zero value for this integer is
+ ** interpreted as 65536. */
+ top = get2byteNotZero(&data[hdr+5]);
+ iCellFirst = hdr + 8 + pPage->childPtrSize + 2*pPage->nCell;
+ iCellLast = usableSize - 4;
- /* Compute the total free space on the page
- ** EVIDENCE-OF: R-23588-34450 The two-byte integer at offset 1 gives the
- ** start of the first freeblock on the page, or is zero if there are no
- ** freeblocks. */
- pc = get2byte(&data[hdr+1]);
- nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */
- while( pc>0 ){
- u16 next, size;
- if( pc<iCellFirst || pc>iCellLast ){
- /* EVIDENCE-OF: R-55530-52930 In a well-formed b-tree page, there will
- ** always be at least one cell before the first freeblock.
- **
- ** Or, the freeblock is off the end of the page
- */
- return SQLITE_CORRUPT_BKPT;
+ /* Compute the total free space on the page
+ ** EVIDENCE-OF: R-23588-34450 The two-byte integer at offset 1 gives the
+ ** start of the first freeblock on the page, or is zero if there are no
+ ** freeblocks. */
+ pc = get2byte(&data[hdr+1]);
+ nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */
+ if( pc>0 ){
+ u32 next, size;
+ if( pc<top ){
+ /* EVIDENCE-OF: R-55530-52930 In a well-formed b-tree page, there will
+ ** always be at least one cell before the first freeblock.
+ */
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ while( 1 ){
+ if( pc>iCellLast ){
+ /* Freeblock off the end of the page */
+ return SQLITE_CORRUPT_PAGE(pPage);
}
next = get2byte(&data[pc]);
size = get2byte(&data[pc+2]);
- if( (next>0 && next<=pc+size+3) || pc+size>usableSize ){
- /* Free blocks must be in ascending order. And the last byte of
- ** the free-block must lie on the database page. */
- return SQLITE_CORRUPT_BKPT;
- }
nFree = nFree + size;
+ if( next<=pc+size+3 ) break;
pc = next;
}
+ if( next>0 ){
+ /* Freeblock not in ascending order */
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ if( pc+size>(unsigned int)usableSize ){
+ /* Last freeblock extends past page end */
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ }
- /* At this point, nFree contains the sum of the offset to the start
- ** of the cell-content area plus the number of free bytes within
- ** the cell-content area. If this is greater than the usable-size
- ** of the page, then the page must be corrupted. This check also
- ** serves to verify that the offset to the start of the cell-content
- ** area, according to the page header, lies within the page.
- */
- if( nFree>usableSize ){
- return SQLITE_CORRUPT_BKPT;
+ /* At this point, nFree contains the sum of the offset to the start
+ ** of the cell-content area plus the number of free bytes within
+ ** the cell-content area. If this is greater than the usable-size
+ ** of the page, then the page must be corrupted. This check also
+ ** serves to verify that the offset to the start of the cell-content
+ ** area, according to the page header, lies within the page.
+ */
+ if( nFree>usableSize || nFree<iCellFirst ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ pPage->nFree = (u16)(nFree - iCellFirst);
+ return SQLITE_OK;
+}
+
+/*
+** Do additional sanity check after btreeInitPage() if
+** PRAGMA cell_size_check=ON
+*/
+static SQLITE_NOINLINE int btreeCellSizeCheck(MemPage *pPage){
+ int iCellFirst; /* First allowable cell or freeblock offset */
+ int iCellLast; /* Last possible cell or freeblock offset */
+ int i; /* Index into the cell pointer array */
+ int sz; /* Size of a cell */
+ int pc; /* Address of a freeblock within pPage->aData[] */
+ u8 *data; /* Equal to pPage->aData */
+ int usableSize; /* Maximum usable space on the page */
+ int cellOffset; /* Start of cell content area */
+
+ iCellFirst = pPage->cellOffset + 2*pPage->nCell;
+ usableSize = pPage->pBt->usableSize;
+ iCellLast = usableSize - 4;
+ data = pPage->aData;
+ cellOffset = pPage->cellOffset;
+ if( !pPage->leaf ) iCellLast--;
+ for(i=0; i<pPage->nCell; i++){
+ pc = get2byteAligned(&data[cellOffset+i*2]);
+ testcase( pc==iCellFirst );
+ testcase( pc==iCellLast );
+ if( pc<iCellFirst || pc>iCellLast ){
+ return SQLITE_CORRUPT_PAGE(pPage);
}
- pPage->nFree = (u16)(nFree - iCellFirst);
- pPage->isInit = 1;
+ sz = pPage->xCellSize(pPage, &data[pc]);
+ testcase( pc+sz==usableSize );
+ if( pc+sz>usableSize ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Initialize the auxiliary information for a disk block.
+**
+** Return SQLITE_OK on success. If we see that the page does
+** not contain a well-formed database page, then return
+** SQLITE_CORRUPT. Note that a return of SQLITE_OK does not
+** guarantee that the page is well-formed. It only shows that
+** we failed to detect any corruption.
+*/
+static int btreeInitPage(MemPage *pPage){
+ u8 *data; /* Equal to pPage->aData */
+ BtShared *pBt; /* The main btree structure */
+
+ assert( pPage->pBt!=0 );
+ assert( pPage->pBt->db!=0 );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( pPage->pgno==tdsqlite3PagerPagenumber(pPage->pDbPage) );
+ assert( pPage == tdsqlite3PagerGetExtra(pPage->pDbPage) );
+ assert( pPage->aData == tdsqlite3PagerGetData(pPage->pDbPage) );
+ assert( pPage->isInit==0 );
+
+ pBt = pPage->pBt;
+ data = pPage->aData + pPage->hdrOffset;
+ /* EVIDENCE-OF: R-28594-02890 The one-byte flag at offset 0 indicating
+ ** the b-tree page type. */
+ if( decodeFlags(pPage, data[0]) ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ assert( pBt->pageSize>=512 && pBt->pageSize<=65536 );
+ pPage->maskPage = (u16)(pBt->pageSize - 1);
+ pPage->nOverflow = 0;
+ pPage->cellOffset = pPage->hdrOffset + 8 + pPage->childPtrSize;
+ pPage->aCellIdx = data + pPage->childPtrSize + 8;
+ pPage->aDataEnd = pPage->aData + pBt->usableSize;
+ pPage->aDataOfst = pPage->aData + pPage->childPtrSize;
+ /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
+ ** number of cells on the page. */
+ pPage->nCell = get2byte(&data[3]);
+ if( pPage->nCell>MX_CELL(pBt) ){
+ /* To many cells for a single page. The page must be corrupt */
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ testcase( pPage->nCell==MX_CELL(pBt) );
+ /* EVIDENCE-OF: R-24089-57979 If a page contains no cells (which is only
+ ** possible for a root page of a table that contains no rows) then the
+ ** offset to the cell content area will equal the page size minus the
+ ** bytes of reserved space. */
+ assert( pPage->nCell>0
+ || get2byteNotZero(&data[5])==(int)pBt->usableSize
+ || CORRUPT_DB );
+ pPage->nFree = -1; /* Indicate that this value is yet uncomputed */
+ pPage->isInit = 1;
+ if( pBt->db->flags & SQLITE_CellSizeCk ){
+ return btreeCellSizeCheck(pPage);
}
return SQLITE_OK;
}
@@ -63196,12 +69743,12 @@ static void zeroPage(MemPage *pPage, int flags){
u8 hdr = pPage->hdrOffset;
u16 first;
- assert( sqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno );
- assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage );
- assert( sqlite3PagerGetData(pPage->pDbPage) == data );
- assert( sqlite3PagerIswriteable(pPage->pDbPage) );
- assert( sqlite3_mutex_held(pBt->mutex) );
- if( pBt->btsFlags & BTS_SECURE_DELETE ){
+ assert( tdsqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno );
+ assert( tdsqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage );
+ assert( tdsqlite3PagerGetData(pPage->pDbPage) == data );
+ assert( tdsqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
+ if( pBt->btsFlags & BTS_FAST_SECURE ){
memset(&data[hdr], 0, pBt->usableSize - hdr);
}
data[hdr] = (char)flags;
@@ -63228,15 +69775,15 @@ static void zeroPage(MemPage *pPage, int flags){
** the btree layer.
*/
static MemPage *btreePageFromDbPage(DbPage *pDbPage, Pgno pgno, BtShared *pBt){
- MemPage *pPage = (MemPage*)sqlite3PagerGetExtra(pDbPage);
+ MemPage *pPage = (MemPage*)tdsqlite3PagerGetExtra(pDbPage);
if( pgno!=pPage->pgno ){
- pPage->aData = sqlite3PagerGetData(pDbPage);
+ pPage->aData = tdsqlite3PagerGetData(pDbPage);
pPage->pDbPage = pDbPage;
pPage->pBt = pBt;
pPage->pgno = pgno;
pPage->hdrOffset = pgno==1 ? 100 : 0;
}
- assert( pPage->aData==sqlite3PagerGetData(pDbPage) );
+ assert( pPage->aData==tdsqlite3PagerGetData(pDbPage) );
return pPage;
}
@@ -63247,7 +69794,7 @@ static MemPage *btreePageFromDbPage(DbPage *pDbPage, Pgno pgno, BtShared *pBt){
** If the PAGER_GET_NOCONTENT flag is set, it means that we do not care
** about the content of the page at this time. So do not go to the disk
** to fetch the content. Just fill in the content with zeros for now.
-** If in the future we call sqlite3PagerWrite() on this page, that
+** If in the future we call tdsqlite3PagerWrite() on this page, that
** means we have started to be concerned about content and the disk
** read should occur at that point.
*/
@@ -63261,8 +69808,8 @@ static int btreeGetPage(
DbPage *pDbPage;
assert( flags==0 || flags==PAGER_GET_NOCONTENT || flags==PAGER_GET_READONLY );
- assert( sqlite3_mutex_held(pBt->mutex) );
- rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, flags);
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
+ rc = tdsqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, flags);
if( rc ) return rc;
*ppPage = btreePageFromDbPage(pDbPage, pgno, pBt);
return SQLITE_OK;
@@ -63275,8 +69822,8 @@ static int btreeGetPage(
*/
static MemPage *btreePageLookup(BtShared *pBt, Pgno pgno){
DbPage *pDbPage;
- assert( sqlite3_mutex_held(pBt->mutex) );
- pDbPage = sqlite3PagerLookup(pBt->pPager, pgno);
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
+ pDbPage = tdsqlite3PagerLookup(pBt->pPager, pgno);
if( pDbPage ){
return btreePageFromDbPage(pDbPage, pgno, pBt);
}
@@ -63288,12 +69835,12 @@ static MemPage *btreePageLookup(BtShared *pBt, Pgno pgno){
** error, return ((unsigned int)-1).
*/
static Pgno btreePagecount(BtShared *pBt){
+ assert( (pBt->nPage & 0x80000000)==0 || CORRUPT_DB );
return pBt->nPage;
}
-SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree *p){
- assert( sqlite3BtreeHoldsMutex(p) );
- assert( ((p->pBt->nPage)&0x8000000)==0 );
- return btreePagecount(p->pBt);
+SQLITE_PRIVATE u32 tdsqlite3BtreeLastPage(Btree *p){
+ assert( tdsqlite3BtreeHoldsMutex(p) );
+ return btreePagecount(p->pBt) & 0x7fffffff;
}
/*
@@ -63318,42 +69865,45 @@ static int getAndInitPage(
){
int rc;
DbPage *pDbPage;
- assert( sqlite3_mutex_held(pBt->mutex) );
- assert( pCur==0 || ppPage==&pCur->apPage[pCur->iPage] );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
+ assert( pCur==0 || ppPage==&pCur->pPage );
assert( pCur==0 || bReadOnly==pCur->curPagerFlags );
assert( pCur==0 || pCur->iPage>0 );
if( pgno>btreePagecount(pBt) ){
rc = SQLITE_CORRUPT_BKPT;
- goto getAndInitPage_error;
+ goto getAndInitPage_error1;
}
- rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly);
+ rc = tdsqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly);
if( rc ){
- goto getAndInitPage_error;
+ goto getAndInitPage_error1;
}
- *ppPage = (MemPage*)sqlite3PagerGetExtra(pDbPage);
+ *ppPage = (MemPage*)tdsqlite3PagerGetExtra(pDbPage);
if( (*ppPage)->isInit==0 ){
btreePageFromDbPage(pDbPage, pgno, pBt);
rc = btreeInitPage(*ppPage);
if( rc!=SQLITE_OK ){
- releasePage(*ppPage);
- goto getAndInitPage_error;
+ goto getAndInitPage_error2;
}
}
assert( (*ppPage)->pgno==pgno );
- assert( (*ppPage)->aData==sqlite3PagerGetData(pDbPage) );
+ assert( (*ppPage)->aData==tdsqlite3PagerGetData(pDbPage) );
/* If obtaining a child page for a cursor, we must verify that the page is
** compatible with the root page. */
if( pCur && ((*ppPage)->nCell<1 || (*ppPage)->intKey!=pCur->curIntKey) ){
- rc = SQLITE_CORRUPT_BKPT;
- releasePage(*ppPage);
- goto getAndInitPage_error;
+ rc = SQLITE_CORRUPT_PGNO(pgno);
+ goto getAndInitPage_error2;
}
return SQLITE_OK;
-getAndInitPage_error:
- if( pCur ) pCur->iPage--;
+getAndInitPage_error2:
+ releasePage(*ppPage);
+getAndInitPage_error1:
+ if( pCur ){
+ pCur->iPage--;
+ pCur->pPage = pCur->apPage[pCur->iPage];
+ }
testcase( pgno==0 );
assert( pgno!=0 || rc==SQLITE_CORRUPT );
return rc;
@@ -63362,19 +69912,31 @@ getAndInitPage_error:
/*
** Release a MemPage. This should be called once for each prior
** call to btreeGetPage.
+**
+** Page1 is a special case and must be released using releasePageOne().
*/
static void releasePageNotNull(MemPage *pPage){
assert( pPage->aData );
assert( pPage->pBt );
assert( pPage->pDbPage!=0 );
- assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage );
- assert( sqlite3PagerGetData(pPage->pDbPage)==pPage->aData );
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- sqlite3PagerUnrefNotNull(pPage->pDbPage);
+ assert( tdsqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage );
+ assert( tdsqlite3PagerGetData(pPage->pDbPage)==pPage->aData );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ tdsqlite3PagerUnrefNotNull(pPage->pDbPage);
}
static void releasePage(MemPage *pPage){
if( pPage ) releasePageNotNull(pPage);
}
+static void releasePageOne(MemPage *pPage){
+ assert( pPage!=0 );
+ assert( pPage->aData );
+ assert( pPage->pBt );
+ assert( pPage->pDbPage!=0 );
+ assert( tdsqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage );
+ assert( tdsqlite3PagerGetData(pPage->pDbPage)==pPage->aData );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ tdsqlite3PagerUnrefPageOne(pPage->pDbPage);
+}
/*
** Get an unused page.
@@ -63393,7 +69955,7 @@ static int btreeGetUnusedPage(
){
int rc = btreeGetPage(pBt, pgno, ppPage, flags);
if( rc==SQLITE_OK ){
- if( sqlite3PagerPageRefcount((*ppPage)->pDbPage)>1 ){
+ if( tdsqlite3PagerPageRefcount((*ppPage)->pDbPage)>1 ){
releasePage(*ppPage);
*ppPage = 0;
return SQLITE_CORRUPT_BKPT;
@@ -63416,12 +69978,12 @@ static int btreeGetUnusedPage(
*/
static void pageReinit(DbPage *pData){
MemPage *pPage;
- pPage = (MemPage *)sqlite3PagerGetExtra(pData);
- assert( sqlite3PagerPageRefcount(pData)>0 );
+ pPage = (MemPage *)tdsqlite3PagerGetExtra(pData);
+ assert( tdsqlite3PagerPageRefcount(pData)>0 );
if( pPage->isInit ){
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
pPage->isInit = 0;
- if( sqlite3PagerPageRefcount(pData)>1 ){
+ if( tdsqlite3PagerPageRefcount(pData)>1 ){
/* pPage might not be a btree page; it might be an overflow page
** or ptrmap page or a free page. In those cases, the following
** call to btreeInitPage() will likely return SQLITE_CORRUPT.
@@ -63439,8 +70001,9 @@ static void pageReinit(DbPage *pData){
static int btreeInvokeBusyHandler(void *pArg){
BtShared *pBt = (BtShared*)pArg;
assert( pBt->db );
- assert( sqlite3_mutex_held(pBt->db->mutex) );
- return sqlite3InvokeBusyHandler(&pBt->db->busyHandler);
+ assert( tdsqlite3_mutex_held(pBt->db->mutex) );
+ return tdsqlite3InvokeBusyHandler(&pBt->db->busyHandler,
+ tdsqlite3PagerFile(pBt->pPager));
}
/*
@@ -63450,7 +70013,7 @@ static int btreeInvokeBusyHandler(void *pArg){
** then an ephemeral database is created. The ephemeral database might
** be exclusively in memory, or it might use a disk-based memory cache.
** Either way, the ephemeral database will be automatically deleted
-** when sqlite3BtreeClose() is called.
+** when tdsqlite3BtreeClose() is called.
**
** If zFilename is ":memory:" then an in-memory database is created
** that is automatically destroyed when it is closed.
@@ -63464,17 +70027,17 @@ static int btreeInvokeBusyHandler(void *pArg){
** objects in the same database connection since doing so will lead
** to problems with locking.
*/
-SQLITE_PRIVATE int sqlite3BtreeOpen(
- sqlite3_vfs *pVfs, /* VFS to use for this b-tree */
+SQLITE_PRIVATE int tdsqlite3BtreeOpen(
+ tdsqlite3_vfs *pVfs, /* VFS to use for this b-tree */
const char *zFilename, /* Name of the file containing the BTree database */
- sqlite3 *db, /* Associated database handle */
+ tdsqlite3 *db, /* Associated database handle */
Btree **ppBtree, /* Pointer to new Btree object written here */
int flags, /* Options */
- int vfsFlags /* Flags passed through to sqlite3_vfs.xOpen() */
+ int vfsFlags /* Flags passed through to tdsqlite3_vfs.xOpen() */
){
BtShared *pBt = 0; /* Shared part of btree structure */
Btree *p; /* Handle to return */
- sqlite3_mutex *mutexOpen = 0; /* Prevents a race condition. Ticket #3537 */
+ tdsqlite3_mutex *mutexOpen = 0; /* Prevents a race condition. Ticket #3537 */
int rc = SQLITE_OK; /* Result code from this function */
u8 nReserve; /* Byte of unused space on each page */
unsigned char zDbHeader[100]; /* Database header content */
@@ -63489,13 +70052,13 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
const int isMemdb = 0;
#else
const int isMemdb = (zFilename && strcmp(zFilename, ":memory:")==0)
- || (isTempDb && sqlite3TempInMemory(db))
+ || (isTempDb && tdsqlite3TempInMemory(db))
|| (vfsFlags & SQLITE_OPEN_MEMORY)!=0;
#endif
assert( db!=0 );
assert( pVfs!=0 );
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
assert( (flags&0xff)==flags ); /* flags fit in 8 bits */
/* Only a BTREE_SINGLE database can be BTREE_UNORDERED */
@@ -63510,7 +70073,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
if( (vfsFlags & SQLITE_OPEN_MAIN_DB)!=0 && (isMemdb || isTempDb) ){
vfsFlags = (vfsFlags & ~SQLITE_OPEN_MAIN_DB) | SQLITE_OPEN_TEMP_DB;
}
- p = sqlite3MallocZero(sizeof(Btree));
+ p = tdsqlite3MallocZero(sizeof(Btree));
if( !p ){
return SQLITE_NOMEM_BKPT;
}
@@ -63528,45 +70091,49 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
*/
if( isTempDb==0 && (isMemdb==0 || (vfsFlags&SQLITE_OPEN_URI)!=0) ){
if( vfsFlags & SQLITE_OPEN_SHAREDCACHE ){
- int nFilename = sqlite3Strlen30(zFilename)+1;
+ int nFilename = tdsqlite3Strlen30(zFilename)+1;
int nFullPathname = pVfs->mxPathname+1;
- char *zFullPathname = sqlite3Malloc(MAX(nFullPathname,nFilename));
- MUTEX_LOGIC( sqlite3_mutex *mutexShared; )
+ char *zFullPathname = tdsqlite3Malloc(MAX(nFullPathname,nFilename));
+ MUTEX_LOGIC( tdsqlite3_mutex *mutexShared; )
p->sharable = 1;
if( !zFullPathname ){
- sqlite3_free(p);
+ tdsqlite3_free(p);
return SQLITE_NOMEM_BKPT;
}
if( isMemdb ){
memcpy(zFullPathname, zFilename, nFilename);
}else{
- rc = sqlite3OsFullPathname(pVfs, zFilename,
+ rc = tdsqlite3OsFullPathname(pVfs, zFilename,
nFullPathname, zFullPathname);
if( rc ){
- sqlite3_free(zFullPathname);
- sqlite3_free(p);
- return rc;
+ if( rc==SQLITE_OK_SYMLINK ){
+ rc = SQLITE_OK;
+ }else{
+ tdsqlite3_free(zFullPathname);
+ tdsqlite3_free(p);
+ return rc;
+ }
}
}
#if SQLITE_THREADSAFE
- mutexOpen = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_OPEN);
- sqlite3_mutex_enter(mutexOpen);
- mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
- sqlite3_mutex_enter(mutexShared);
+ mutexOpen = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_OPEN);
+ tdsqlite3_mutex_enter(mutexOpen);
+ mutexShared = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
+ tdsqlite3_mutex_enter(mutexShared);
#endif
- for(pBt=GLOBAL(BtShared*,sqlite3SharedCacheList); pBt; pBt=pBt->pNext){
+ for(pBt=GLOBAL(BtShared*,tdsqlite3SharedCacheList); pBt; pBt=pBt->pNext){
assert( pBt->nRef>0 );
- if( 0==strcmp(zFullPathname, sqlite3PagerFilename(pBt->pPager, 0))
- && sqlite3PagerVfs(pBt->pPager)==pVfs ){
+ if( 0==strcmp(zFullPathname, tdsqlite3PagerFilename(pBt->pPager, 0))
+ && tdsqlite3PagerVfs(pBt->pPager)==pVfs ){
int iDb;
for(iDb=db->nDb-1; iDb>=0; iDb--){
Btree *pExisting = db->aDb[iDb].pBt;
if( pExisting && pExisting->pBt==pBt ){
- sqlite3_mutex_leave(mutexShared);
- sqlite3_mutex_leave(mutexOpen);
- sqlite3_free(zFullPathname);
- sqlite3_free(p);
+ tdsqlite3_mutex_leave(mutexShared);
+ tdsqlite3_mutex_leave(mutexOpen);
+ tdsqlite3_free(zFullPathname);
+ tdsqlite3_free(p);
return SQLITE_CONSTRAINT;
}
}
@@ -63575,14 +70142,14 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
break;
}
}
- sqlite3_mutex_leave(mutexShared);
- sqlite3_free(zFullPathname);
+ tdsqlite3_mutex_leave(mutexShared);
+ tdsqlite3_free(zFullPathname);
}
#ifdef SQLITE_DEBUG
else{
/* In debug mode, we mark all persistent databases as sharable
** even when they are not. This exercises the locking code and
- ** gives more opportunity for asserts(sqlite3_mutex_held())
+ ** gives more opportunity for asserts(tdsqlite3_mutex_held())
** statements to find locking problems.
*/
p->sharable = 1;
@@ -63602,30 +70169,32 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
assert( sizeof(u16)==2 );
assert( sizeof(Pgno)==4 );
- pBt = sqlite3MallocZero( sizeof(*pBt) );
+ pBt = tdsqlite3MallocZero( sizeof(*pBt) );
if( pBt==0 ){
rc = SQLITE_NOMEM_BKPT;
goto btree_open_out;
}
- rc = sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename,
- EXTRA_SIZE, flags, vfsFlags, pageReinit);
+ rc = tdsqlite3PagerOpen(pVfs, &pBt->pPager, zFilename,
+ sizeof(MemPage), flags, vfsFlags, pageReinit);
if( rc==SQLITE_OK ){
- sqlite3PagerSetMmapLimit(pBt->pPager, db->szMmap);
- rc = sqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader);
+ tdsqlite3PagerSetMmapLimit(pBt->pPager, db->szMmap);
+ rc = tdsqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader);
}
if( rc!=SQLITE_OK ){
goto btree_open_out;
}
pBt->openFlags = (u8)flags;
pBt->db = db;
- sqlite3PagerSetBusyhandler(pBt->pPager, btreeInvokeBusyHandler, pBt);
+ tdsqlite3PagerSetBusyHandler(pBt->pPager, btreeInvokeBusyHandler, pBt);
p->pBt = pBt;
pBt->pCursor = 0;
pBt->pPage1 = 0;
- if( sqlite3PagerIsreadonly(pBt->pPager) ) pBt->btsFlags |= BTS_READ_ONLY;
-#ifdef SQLITE_SECURE_DELETE
+ if( tdsqlite3PagerIsreadonly(pBt->pPager) ) pBt->btsFlags |= BTS_READ_ONLY;
+#if defined(SQLITE_SECURE_DELETE)
pBt->btsFlags |= BTS_SECURE_DELETE;
+#elif defined(SQLITE_FAST_SECURE_DELETE)
+ pBt->btsFlags |= BTS_OVERWRITE;
#endif
/* EVIDENCE-OF: R-51873-39618 The page size for a database file is
** determined by the 2-byte integer located at an offset of 16 bytes from
@@ -63658,7 +70227,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
pBt->incrVacuum = (get4byte(&zDbHeader[36 + 7*4])?1:0);
#endif
}
- rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve);
+ rc = tdsqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve);
if( rc ) goto btree_open_out;
pBt->usableSize = pBt->pageSize - nReserve;
assert( (pBt->pageSize & 7)==0 ); /* 8-byte alignment of pageSize */
@@ -63668,19 +70237,19 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
*/
pBt->nRef = 1;
if( p->sharable ){
- MUTEX_LOGIC( sqlite3_mutex *mutexShared; )
- MUTEX_LOGIC( mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);)
- if( SQLITE_THREADSAFE && sqlite3GlobalConfig.bCoreMutex ){
- pBt->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_FAST);
+ MUTEX_LOGIC( tdsqlite3_mutex *mutexShared; )
+ MUTEX_LOGIC( mutexShared = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);)
+ if( SQLITE_THREADSAFE && tdsqlite3GlobalConfig.bCoreMutex ){
+ pBt->mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_FAST);
if( pBt->mutex==0 ){
rc = SQLITE_NOMEM_BKPT;
goto btree_open_out;
}
}
- sqlite3_mutex_enter(mutexShared);
- pBt->pNext = GLOBAL(BtShared*,sqlite3SharedCacheList);
- GLOBAL(BtShared*,sqlite3SharedCacheList) = pBt;
- sqlite3_mutex_leave(mutexShared);
+ tdsqlite3_mutex_enter(mutexShared);
+ pBt->pNext = GLOBAL(BtShared*,tdsqlite3SharedCacheList);
+ GLOBAL(BtShared*,tdsqlite3SharedCacheList) = pBt;
+ tdsqlite3_mutex_leave(mutexShared);
}
#endif
}
@@ -63721,25 +70290,32 @@ SQLITE_PRIVATE int sqlite3BtreeOpen(
btree_open_out:
if( rc!=SQLITE_OK ){
if( pBt && pBt->pPager ){
- sqlite3PagerClose(pBt->pPager);
+ tdsqlite3PagerClose(pBt->pPager, 0);
}
- sqlite3_free(pBt);
- sqlite3_free(p);
+ tdsqlite3_free(pBt);
+ tdsqlite3_free(p);
*ppBtree = 0;
}else{
+ tdsqlite3_file *pFile;
+
/* If the B-Tree was successfully opened, set the pager-cache size to the
** default value. Except, when opening on an existing shared pager-cache,
** do not change the pager-cache size.
*/
- if( sqlite3BtreeSchema(p, 0, 0)==0 ){
- sqlite3PagerSetCachesize(p->pBt->pPager, SQLITE_DEFAULT_CACHE_SIZE);
+ if( tdsqlite3BtreeSchema(p, 0, 0)==0 ){
+ tdsqlite3PagerSetCachesize(p->pBt->pPager, SQLITE_DEFAULT_CACHE_SIZE);
+ }
+
+ pFile = tdsqlite3PagerFile(pBt->pPager);
+ if( pFile->pMethods ){
+ tdsqlite3OsFileControlHint(pFile, SQLITE_FCNTL_PDB, (void*)&pBt->db);
}
}
if( mutexOpen ){
- assert( sqlite3_mutex_held(mutexOpen) );
- sqlite3_mutex_leave(mutexOpen);
+ assert( tdsqlite3_mutex_held(mutexOpen) );
+ tdsqlite3_mutex_leave(mutexOpen);
}
- assert( rc!=SQLITE_OK || sqlite3BtreeConnectionCount(*ppBtree)>0 );
+ assert( rc!=SQLITE_OK || tdsqlite3BtreeConnectionCount(*ppBtree)>0 );
return rc;
}
@@ -63751,19 +70327,19 @@ btree_open_out:
*/
static int removeFromSharingList(BtShared *pBt){
#ifndef SQLITE_OMIT_SHARED_CACHE
- MUTEX_LOGIC( sqlite3_mutex *pMaster; )
+ MUTEX_LOGIC( tdsqlite3_mutex *pMaster; )
BtShared *pList;
int removed = 0;
- assert( sqlite3_mutex_notheld(pBt->mutex) );
- MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
- sqlite3_mutex_enter(pMaster);
+ assert( tdsqlite3_mutex_notheld(pBt->mutex) );
+ MUTEX_LOGIC( pMaster = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
+ tdsqlite3_mutex_enter(pMaster);
pBt->nRef--;
if( pBt->nRef<=0 ){
- if( GLOBAL(BtShared*,sqlite3SharedCacheList)==pBt ){
- GLOBAL(BtShared*,sqlite3SharedCacheList) = pBt->pNext;
+ if( GLOBAL(BtShared*,tdsqlite3SharedCacheList)==pBt ){
+ GLOBAL(BtShared*,tdsqlite3SharedCacheList) = pBt->pNext;
}else{
- pList = GLOBAL(BtShared*,sqlite3SharedCacheList);
+ pList = GLOBAL(BtShared*,tdsqlite3SharedCacheList);
while( ALWAYS(pList) && pList->pNext!=pBt ){
pList=pList->pNext;
}
@@ -63772,11 +70348,11 @@ static int removeFromSharingList(BtShared *pBt){
}
}
if( SQLITE_THREADSAFE ){
- sqlite3_mutex_free(pBt->mutex);
+ tdsqlite3_mutex_free(pBt->mutex);
}
removed = 1;
}
- sqlite3_mutex_leave(pMaster);
+ tdsqlite3_mutex_leave(pMaster);
return removed;
#else
return 1;
@@ -63790,7 +70366,7 @@ static int removeFromSharingList(BtShared *pBt){
*/
static void allocateTempSpace(BtShared *pBt){
if( !pBt->pTmpSpace ){
- pBt->pTmpSpace = sqlite3PageMalloc( pBt->pageSize );
+ pBt->pTmpSpace = tdsqlite3PageMalloc( pBt->pageSize );
/* One of the uses of pBt->pTmpSpace is to format cells before
** inserting them into a leaf page (function fillInCell()). If
@@ -63820,7 +70396,7 @@ static void allocateTempSpace(BtShared *pBt){
static void freeTempSpace(BtShared *pBt){
if( pBt->pTmpSpace ){
pBt->pTmpSpace -= 4;
- sqlite3PageFree(pBt->pTmpSpace);
+ tdsqlite3PageFree(pBt->pTmpSpace);
pBt->pTmpSpace = 0;
}
}
@@ -63828,28 +70404,28 @@ static void freeTempSpace(BtShared *pBt){
/*
** Close an open database and invalidate all cursors.
*/
-SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeClose(Btree *p){
BtShared *pBt = p->pBt;
BtCursor *pCur;
/* Close all cursors opened via this handle. */
- assert( sqlite3_mutex_held(p->db->mutex) );
- sqlite3BtreeEnter(p);
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
+ tdsqlite3BtreeEnter(p);
pCur = pBt->pCursor;
while( pCur ){
BtCursor *pTmp = pCur;
pCur = pCur->pNext;
if( pTmp->pBtree==p ){
- sqlite3BtreeCloseCursor(pTmp);
+ tdsqlite3BtreeCloseCursor(pTmp);
}
}
/* Rollback any active transaction and free the handle structure.
- ** The call to sqlite3BtreeRollback() drops any table-locks held by
+ ** The call to tdsqlite3BtreeRollback() drops any table-locks held by
** this handle.
*/
- sqlite3BtreeRollback(p, SQLITE_OK, 0);
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeRollback(p, SQLITE_OK, 0);
+ tdsqlite3BtreeLeave(p);
/* If there are still other outstanding references to the shared-btree
** structure, return now. The remainder of this procedure cleans
@@ -63863,13 +70439,13 @@ SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){
** Clean out and delete the BtShared object.
*/
assert( !pBt->pCursor );
- sqlite3PagerClose(pBt->pPager);
+ tdsqlite3PagerClose(pBt->pPager, p->db);
if( pBt->xFreeSchema && pBt->pSchema ){
pBt->xFreeSchema(pBt->pSchema);
}
- sqlite3DbFree(0, pBt->pSchema);
+ tdsqlite3DbFree(0, pBt->pSchema);
freeTempSpace(pBt);
- sqlite3_free(pBt);
+ tdsqlite3_free(pBt);
}
#ifndef SQLITE_OMIT_SHARED_CACHE
@@ -63879,7 +70455,7 @@ SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){
if( p->pNext ) p->pNext->pPrev = p->pPrev;
#endif
- sqlite3_free(p);
+ tdsqlite3_free(p);
return SQLITE_OK;
}
@@ -63890,12 +70466,12 @@ SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){
** cache is allowed to grow larger than this limit if it contains
** dirty pages or pages still in active use.
*/
-SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree *p, int mxPage){
+SQLITE_PRIVATE int tdsqlite3BtreeSetCacheSize(Btree *p, int mxPage){
BtShared *pBt = p->pBt;
- assert( sqlite3_mutex_held(p->db->mutex) );
- sqlite3BtreeEnter(p);
- sqlite3PagerSetCachesize(pBt->pPager, mxPage);
- sqlite3BtreeLeave(p);
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
+ tdsqlite3BtreeEnter(p);
+ tdsqlite3PagerSetCachesize(pBt->pPager, mxPage);
+ tdsqlite3BtreeLeave(p);
return SQLITE_OK;
}
@@ -63909,13 +70485,13 @@ SQLITE_PRIVATE int sqlite3BtreeSetCacheSize(Btree *p, int mxPage){
** as an argument, no changes are made to the spill size setting, so
** using mxPage of 0 is a way to query the current spill size.
*/
-SQLITE_PRIVATE int sqlite3BtreeSetSpillSize(Btree *p, int mxPage){
+SQLITE_PRIVATE int tdsqlite3BtreeSetSpillSize(Btree *p, int mxPage){
BtShared *pBt = p->pBt;
int res;
- assert( sqlite3_mutex_held(p->db->mutex) );
- sqlite3BtreeEnter(p);
- res = sqlite3PagerSetSpillsize(pBt->pPager, mxPage);
- sqlite3BtreeLeave(p);
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
+ tdsqlite3BtreeEnter(p);
+ res = tdsqlite3PagerSetSpillsize(pBt->pPager, mxPage);
+ tdsqlite3BtreeLeave(p);
return res;
}
@@ -63924,12 +70500,12 @@ SQLITE_PRIVATE int sqlite3BtreeSetSpillSize(Btree *p, int mxPage){
** Change the limit on the amount of the database file that may be
** memory mapped.
*/
-SQLITE_PRIVATE int sqlite3BtreeSetMmapLimit(Btree *p, sqlite3_int64 szMmap){
+SQLITE_PRIVATE int tdsqlite3BtreeSetMmapLimit(Btree *p, tdsqlite3_int64 szMmap){
BtShared *pBt = p->pBt;
- assert( sqlite3_mutex_held(p->db->mutex) );
- sqlite3BtreeEnter(p);
- sqlite3PagerSetMmapLimit(pBt->pPager, szMmap);
- sqlite3BtreeLeave(p);
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
+ tdsqlite3BtreeEnter(p);
+ tdsqlite3PagerSetMmapLimit(pBt->pPager, szMmap);
+ tdsqlite3BtreeLeave(p);
return SQLITE_OK;
}
#endif /* SQLITE_MAX_MMAP_SIZE>0 */
@@ -63943,15 +70519,15 @@ SQLITE_PRIVATE int sqlite3BtreeSetMmapLimit(Btree *p, sqlite3_int64 szMmap){
** probability of damage to near zero but with a write performance reduction.
*/
#ifndef SQLITE_OMIT_PAGER_PRAGMAS
-SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags(
+SQLITE_PRIVATE int tdsqlite3BtreeSetPagerFlags(
Btree *p, /* The btree to set the safety level on */
unsigned pgFlags /* Various PAGER_* flags */
){
BtShared *pBt = p->pBt;
- assert( sqlite3_mutex_held(p->db->mutex) );
- sqlite3BtreeEnter(p);
- sqlite3PagerSetFlags(pBt->pPager, pgFlags);
- sqlite3BtreeLeave(p);
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
+ tdsqlite3BtreeEnter(p);
+ tdsqlite3PagerSetFlags(pBt->pPager, pgFlags);
+ tdsqlite3BtreeLeave(p);
return SQLITE_OK;
}
#endif
@@ -63976,16 +70552,16 @@ SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags(
** If the iFix!=0 then the BTS_PAGESIZE_FIXED flag is set so that the page size
** and autovacuum mode can no longer be changed.
*/
-SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve, int iFix){
+SQLITE_PRIVATE int tdsqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve, int iFix){
int rc = SQLITE_OK;
BtShared *pBt = p->pBt;
assert( nReserve>=-1 && nReserve<=255 );
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
#if SQLITE_HAS_CODEC
if( nReserve>pBt->optimalReserve ) pBt->optimalReserve = (u8)nReserve;
#endif
if( pBt->btsFlags & BTS_PAGESIZE_FIXED ){
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return SQLITE_READONLY;
}
if( nReserve<0 ){
@@ -63999,34 +70575,34 @@ SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve,
pBt->pageSize = (u32)pageSize;
freeTempSpace(pBt);
}
- rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve);
+ rc = tdsqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve);
pBt->usableSize = pBt->pageSize - (u16)nReserve;
if( iFix ) pBt->btsFlags |= BTS_PAGESIZE_FIXED;
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
/*
** Return the currently defined page size
*/
-SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeGetPageSize(Btree *p){
return p->pBt->pageSize;
}
/*
-** This function is similar to sqlite3BtreeGetReserve(), except that it
+** This function is similar to tdsqlite3BtreeGetReserve(), except that it
** may only be called if it is guaranteed that the b-tree mutex is already
** held.
**
** This is useful in one special case in the backup API code where it is
** known that the shared b-tree mutex is held, but the mutex on the
-** database handle that owns *p is not. In this case if sqlite3BtreeEnter()
+** database handle that owns *p is not. In this case if tdsqlite3BtreeEnter()
** were to be called, it might collide with some other operation on the
** database handle that owns *p, causing undefined behavior.
*/
-SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeGetReserveNoMutex(Btree *p){
int n;
- assert( sqlite3_mutex_held(p->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(p->pBt->mutex) );
n = p->pBt->pageSize - p->pBt->usableSize;
return n;
}
@@ -64040,14 +70616,14 @@ SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p){
** greater of the current reserved space and the maximum requested
** reserve space.
*/
-SQLITE_PRIVATE int sqlite3BtreeGetOptimalReserve(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeGetOptimalReserve(Btree *p){
int n;
- sqlite3BtreeEnter(p);
- n = sqlite3BtreeGetReserveNoMutex(p);
+ tdsqlite3BtreeEnter(p);
+ n = tdsqlite3BtreeGetReserveNoMutex(p);
#ifdef SQLITE_HAS_CODEC
if( n<p->pBt->optimalReserve ) n = p->pBt->optimalReserve;
#endif
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return n;
}
@@ -64057,29 +70633,44 @@ SQLITE_PRIVATE int sqlite3BtreeGetOptimalReserve(Btree *p){
** No changes are made if mxPage is 0 or negative.
** Regardless of the value of mxPage, return the maximum page count.
*/
-SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree *p, int mxPage){
+SQLITE_PRIVATE int tdsqlite3BtreeMaxPageCount(Btree *p, int mxPage){
int n;
- sqlite3BtreeEnter(p);
- n = sqlite3PagerMaxPageCount(p->pBt->pPager, mxPage);
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeEnter(p);
+ n = tdsqlite3PagerMaxPageCount(p->pBt->pPager, mxPage);
+ tdsqlite3BtreeLeave(p);
return n;
}
/*
-** Set the BTS_SECURE_DELETE flag if newFlag is 0 or 1. If newFlag is -1,
-** then make no changes. Always return the value of the BTS_SECURE_DELETE
-** setting after the change.
+** Change the values for the BTS_SECURE_DELETE and BTS_OVERWRITE flags:
+**
+** newFlag==0 Both BTS_SECURE_DELETE and BTS_OVERWRITE are cleared
+** newFlag==1 BTS_SECURE_DELETE set and BTS_OVERWRITE is cleared
+** newFlag==2 BTS_SECURE_DELETE cleared and BTS_OVERWRITE is set
+** newFlag==(-1) No changes
+**
+** This routine acts as a query if newFlag is less than zero
+**
+** With BTS_OVERWRITE set, deleted content is overwritten by zeros, but
+** freelist leaf pages are not written back to the database. Thus in-page
+** deleted content is cleared, but freelist deleted content is not.
+**
+** With BTS_SECURE_DELETE, operation is like BTS_OVERWRITE with the addition
+** that freelist leaf pages are written back into the database, increasing
+** the amount of disk I/O.
*/
-SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree *p, int newFlag){
+SQLITE_PRIVATE int tdsqlite3BtreeSecureDelete(Btree *p, int newFlag){
int b;
if( p==0 ) return 0;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
+ assert( BTS_OVERWRITE==BTS_SECURE_DELETE*2 );
+ assert( BTS_FAST_SECURE==(BTS_OVERWRITE|BTS_SECURE_DELETE) );
if( newFlag>=0 ){
- p->pBt->btsFlags &= ~BTS_SECURE_DELETE;
- if( newFlag ) p->pBt->btsFlags |= BTS_SECURE_DELETE;
- }
- b = (p->pBt->btsFlags & BTS_SECURE_DELETE)!=0;
- sqlite3BtreeLeave(p);
+ p->pBt->btsFlags &= ~BTS_FAST_SECURE;
+ p->pBt->btsFlags |= BTS_SECURE_DELETE*newFlag;
+ }
+ b = (p->pBt->btsFlags & BTS_FAST_SECURE)/BTS_SECURE_DELETE;
+ tdsqlite3BtreeLeave(p);
return b;
}
@@ -64089,7 +70680,7 @@ SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree *p, int newFlag){
** is disabled. The default value for the auto-vacuum property is
** determined by the SQLITE_DEFAULT_AUTOVACUUM macro.
*/
-SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){
+SQLITE_PRIVATE int tdsqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){
#ifdef SQLITE_OMIT_AUTOVACUUM
return SQLITE_READONLY;
#else
@@ -64097,14 +70688,14 @@ SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){
int rc = SQLITE_OK;
u8 av = (u8)autoVacuum;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
if( (pBt->btsFlags & BTS_PAGESIZE_FIXED)!=0 && (av ?1:0)!=pBt->autoVacuum ){
rc = SQLITE_READONLY;
}else{
pBt->autoVacuum = av ?1:0;
pBt->incrVacuum = av==2 ?1:0;
}
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
#endif
}
@@ -64113,22 +70704,52 @@ SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){
** Return the value of the 'auto-vacuum' property. If auto-vacuum is
** enabled 1 is returned. Otherwise 0.
*/
-SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeGetAutoVacuum(Btree *p){
#ifdef SQLITE_OMIT_AUTOVACUUM
return BTREE_AUTOVACUUM_NONE;
#else
int rc;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
rc = (
(!p->pBt->autoVacuum)?BTREE_AUTOVACUUM_NONE:
(!p->pBt->incrVacuum)?BTREE_AUTOVACUUM_FULL:
BTREE_AUTOVACUUM_INCR
);
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
#endif
}
+/*
+** If the user has not set the safety-level for this database connection
+** using "PRAGMA synchronous", and if the safety-level is not already
+** set to the value passed to this function as the second parameter,
+** set it so.
+*/
+#if SQLITE_DEFAULT_SYNCHRONOUS!=SQLITE_DEFAULT_WAL_SYNCHRONOUS \
+ && !defined(SQLITE_OMIT_WAL)
+static void setDefaultSyncFlag(BtShared *pBt, u8 safety_level){
+ tdsqlite3 *db;
+ Db *pDb;
+ if( (db=pBt->db)!=0 && (pDb=db->aDb)!=0 ){
+ while( pDb->pBt==0 || pDb->pBt->pBt!=pBt ){ pDb++; }
+ if( pDb->bSyncSet==0
+ && pDb->safety_level!=safety_level
+ && pDb!=&db->aDb[1]
+ ){
+ pDb->safety_level = safety_level;
+ tdsqlite3PagerSetFlags(pBt->pPager,
+ pDb->safety_level | (db->flags & PAGER_FLAGS_MASK));
+ }
+ }
+}
+#else
+# define setDefaultSyncFlag(pBt,safety_level)
+#endif
+
+/* Forward declaration */
+static int newDatabase(BtShared*);
+
/*
** Get a reference to pPage1 of the database file. This will
@@ -64142,13 +70763,13 @@ SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *p){
static int lockBtree(BtShared *pBt){
int rc; /* Result code from subfunctions */
MemPage *pPage1; /* Page 1 of the database file */
- int nPage; /* Number of pages in the database */
- int nPageFile = 0; /* Number of pages in the database file */
- int nPageHeader; /* Number of pages in the database according to hdr */
+ u32 nPage; /* Number of pages in the database */
+ u32 nPageFile = 0; /* Number of pages in the database file */
+ u32 nPageHeader; /* Number of pages in the database according to hdr */
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
assert( pBt->pPage1==0 );
- rc = sqlite3PagerSharedLock(pBt->pPager);
+ rc = tdsqlite3PagerSharedLock(pBt->pPager);
if( rc!=SQLITE_OK ) return rc;
rc = btreeGetPage(pBt, 1, &pPage1, 0);
if( rc!=SQLITE_OK ) return rc;
@@ -64157,10 +70778,13 @@ static int lockBtree(BtShared *pBt){
** a valid database file.
*/
nPage = nPageHeader = get4byte(28+(u8*)pPage1->aData);
- sqlite3PagerPagecount(pBt->pPager, &nPageFile);
+ tdsqlite3PagerPagecount(pBt->pPager, (int*)&nPageFile);
if( nPage==0 || memcmp(24+(u8*)pPage1->aData, 92+(u8*)pPage1->aData,4)!=0 ){
nPage = nPageFile;
}
+ if( (pBt->db->flags & SQLITE_ResetDatabase)!=0 ){
+ nPage = 0;
+ }
if( nPage>0 ){
u32 pageSize;
u32 usableSize;
@@ -64198,30 +70822,19 @@ static int lockBtree(BtShared *pBt){
*/
if( page1[19]==2 && (pBt->btsFlags & BTS_NO_WAL)==0 ){
int isOpen = 0;
- rc = sqlite3PagerOpenWal(pBt->pPager, &isOpen);
+ rc = tdsqlite3PagerOpenWal(pBt->pPager, &isOpen);
if( rc!=SQLITE_OK ){
goto page1_init_failed;
}else{
-#if SQLITE_DEFAULT_SYNCHRONOUS!=SQLITE_DEFAULT_WAL_SYNCHRONOUS
- sqlite3 *db;
- Db *pDb;
- if( (db=pBt->db)!=0 && (pDb=db->aDb)!=0 ){
- while( pDb->pBt==0 || pDb->pBt->pBt!=pBt ){ pDb++; }
- if( pDb->bSyncSet==0
- && pDb->safety_level==SQLITE_DEFAULT_SYNCHRONOUS+1
- ){
- pDb->safety_level = SQLITE_DEFAULT_WAL_SYNCHRONOUS+1;
- sqlite3PagerSetFlags(pBt->pPager,
- pDb->safety_level | (db->flags & PAGER_FLAGS_MASK));
- }
- }
-#endif
+ setDefaultSyncFlag(pBt, SQLITE_DEFAULT_WAL_SYNCHRONOUS+1);
if( isOpen==0 ){
- releasePage(pPage1);
+ releasePageOne(pPage1);
return SQLITE_OK;
}
}
rc = SQLITE_NOTADB;
+ }else{
+ setDefaultSyncFlag(pBt, SQLITE_DEFAULT_SYNCHRONOUS+1);
}
#endif
@@ -64246,6 +70859,7 @@ static int lockBtree(BtShared *pBt){
){
goto page1_init_failed;
}
+ pBt->btsFlags |= BTS_PAGESIZE_FIXED;
assert( (pageSize & 7)==0 );
/* EVIDENCE-OF: R-59310-51205 The "reserved space" size in the 1-byte
** integer at offset 20 is the number of bytes of space at the end of
@@ -64262,15 +70876,15 @@ static int lockBtree(BtShared *pBt){
** zero and return SQLITE_OK. The caller will call this function
** again with the correct page-size.
*/
- releasePage(pPage1);
+ releasePageOne(pPage1);
pBt->usableSize = usableSize;
pBt->pageSize = pageSize;
freeTempSpace(pBt);
- rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize,
+ rc = tdsqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize,
pageSize-usableSize);
return rc;
}
- if( (pBt->db->flags & SQLITE_RecoveryMode)==0 && nPage>nPageFile ){
+ if( tdsqlite3WritableSchema(pBt->db)==0 && nPage>nPageFile ){
rc = SQLITE_CORRUPT_BKPT;
goto page1_init_failed;
}
@@ -64316,7 +70930,7 @@ static int lockBtree(BtShared *pBt){
return SQLITE_OK;
page1_init_failed:
- releasePage(pPage1);
+ releasePageOne(pPage1);
pBt->pPage1 = 0;
return rc;
}
@@ -64354,14 +70968,14 @@ static int countValidCursors(BtShared *pBt, int wrOnly){
** If there is a transaction in progress, this routine is a no-op.
*/
static void unlockBtreeIfUnused(BtShared *pBt){
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
assert( countValidCursors(pBt,0)==0 || pBt->inTransaction>TRANS_NONE );
if( pBt->inTransaction==TRANS_NONE && pBt->pPage1!=0 ){
MemPage *pPage1 = pBt->pPage1;
assert( pPage1->aData );
- assert( sqlite3PagerRefcount(pBt->pPager)==1 );
+ assert( tdsqlite3PagerRefcount(pBt->pPager)==1 );
pBt->pPage1 = 0;
- releasePageNotNull(pPage1);
+ releasePageOne(pPage1);
}
}
@@ -64375,14 +70989,14 @@ static int newDatabase(BtShared *pBt){
unsigned char *data;
int rc;
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
if( pBt->nPage>0 ){
return SQLITE_OK;
}
pP1 = pBt->pPage1;
assert( pP1!=0 );
data = pP1->aData;
- rc = sqlite3PagerWrite(pP1->pDbPage);
+ rc = tdsqlite3PagerWrite(pP1->pDbPage);
if( rc ) return rc;
memcpy(data, zMagicHeader, sizeof(zMagicHeader));
assert( sizeof(zMagicHeader)==16 );
@@ -64414,12 +71028,12 @@ static int newDatabase(BtShared *pBt){
** consisting of a single page and no schema objects). Return SQLITE_OK
** if successful, or an SQLite error code otherwise.
*/
-SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeNewDb(Btree *p){
int rc;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
p->pBt->nPage = 0;
rc = newDatabase(p->pBt);
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -64436,13 +71050,13 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p){
** changes to the database. None of the following routines
** will work unless a transaction is started first:
**
-** sqlite3BtreeCreateTable()
-** sqlite3BtreeCreateIndex()
-** sqlite3BtreeClearTable()
-** sqlite3BtreeDropTable()
-** sqlite3BtreeInsert()
-** sqlite3BtreeDelete()
-** sqlite3BtreeUpdateMeta()
+** tdsqlite3BtreeCreateTable()
+** tdsqlite3BtreeCreateIndex()
+** tdsqlite3BtreeClearTable()
+** tdsqlite3BtreeDropTable()
+** tdsqlite3BtreeInsert()
+** tdsqlite3BtreeDelete()
+** tdsqlite3BtreeUpdateMeta()
**
** If an initial attempt to acquire the lock fails because of lock contention
** and the database was previously unlocked, then invoke the busy handler
@@ -64458,11 +71072,11 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p){
** when A already has a read lock, we encourage A to give up and let B
** proceed.
*/
-SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
+SQLITE_PRIVATE int tdsqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){
BtShared *pBt = p->pBt;
int rc = SQLITE_OK;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
btreeIntegrity(p);
/* If the btree is already in a write-transaction, or it
@@ -64474,6 +71088,12 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
}
assert( pBt->inTransaction==TRANS_WRITE || IfNotOmitAV(pBt->bDoTruncate)==0 );
+ if( (p->db->flags & SQLITE_ResetDatabase)
+ && tdsqlite3PagerIsreadonly(pBt->pPager)==0
+ ){
+ pBt->btsFlags &= ~BTS_READ_ONLY;
+ }
+
/* Write transactions are not possible on a read-only database */
if( (pBt->btsFlags & BTS_READ_ONLY)!=0 && wrflag ){
rc = SQLITE_READONLY;
@@ -64482,7 +71102,7 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
#ifndef SQLITE_OMIT_SHARED_CACHE
{
- sqlite3 *pBlock = 0;
+ tdsqlite3 *pBlock = 0;
/* If another database handle has already opened a write transaction
** on this shared-btree structure and a second write transaction is
** requested, return SQLITE_LOCKED.
@@ -64501,7 +71121,7 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
}
}
if( pBlock ){
- sqlite3ConnectionBlocked(p->db, pBlock);
+ tdsqlite3ConnectionBlocked(p->db, pBlock);
rc = SQLITE_LOCKED_SHAREDCACHE;
goto trans_begun;
}
@@ -64530,9 +71150,14 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
if( (pBt->btsFlags & BTS_READ_ONLY)!=0 ){
rc = SQLITE_READONLY;
}else{
- rc = sqlite3PagerBegin(pBt->pPager,wrflag>1,sqlite3TempInMemory(p->db));
+ rc = tdsqlite3PagerBegin(pBt->pPager,wrflag>1,tdsqlite3TempInMemory(p->db));
if( rc==SQLITE_OK ){
rc = newDatabase(pBt);
+ }else if( rc==SQLITE_BUSY_SNAPSHOT && pBt->inTransaction==TRANS_NONE ){
+ /* if there was no transaction opened when this function was
+ ** called and SQLITE_BUSY_SNAPSHOT is returned, change the error
+ ** code to SQLITE_BUSY. */
+ rc = SQLITE_BUSY;
}
}
}
@@ -64542,6 +71167,7 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
}
}while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE &&
btreeInvokeBusyHandler(pBt) );
+ tdsqlite3PagerResetLockTimeout(pBt->pPager);
if( rc==SQLITE_OK ){
if( p->inTrans==TRANS_NONE ){
@@ -64575,7 +71201,7 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
** rollback occurs within the transaction.
*/
if( pBt->nPage!=get4byte(&pPage1->aData[28]) ){
- rc = sqlite3PagerWrite(pPage1->pDbPage);
+ rc = tdsqlite3PagerWrite(pPage1->pDbPage);
if( rc==SQLITE_OK ){
put4byte(&pPage1->aData[28], pBt->nPage);
}
@@ -64583,18 +71209,22 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
}
}
-
trans_begun:
- if( rc==SQLITE_OK && wrflag ){
- /* This call makes sure that the pager has the correct number of
- ** open savepoints. If the second parameter is greater than 0 and
- ** the sub-journal is not already open, then it will be opened here.
- */
- rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint);
+ if( rc==SQLITE_OK ){
+ if( pSchemaVersion ){
+ *pSchemaVersion = get4byte(&pBt->pPage1->aData[40]);
+ }
+ if( wrflag ){
+ /* This call makes sure that the pager has the correct number of
+ ** open savepoints. If the second parameter is greater than 0 and
+ ** the sub-journal is not already open, then it will be opened here.
+ */
+ rc = tdsqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint);
+ }
}
btreeIntegrity(p);
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -64610,20 +71240,17 @@ static int setChildPtrmaps(MemPage *pPage){
int nCell; /* Number of cells in page pPage */
int rc; /* Return code */
BtShared *pBt = pPage->pBt;
- u8 isInitOrig = pPage->isInit;
Pgno pgno = pPage->pgno;
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- rc = btreeInitPage(pPage);
- if( rc!=SQLITE_OK ){
- goto set_child_ptrmaps_out;
- }
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ rc = pPage->isInit ? SQLITE_OK : btreeInitPage(pPage);
+ if( rc!=SQLITE_OK ) return rc;
nCell = pPage->nCell;
for(i=0; i<nCell; i++){
u8 *pCell = findCell(pPage, i);
- ptrmapPutOvflPtr(pPage, pCell, &rc);
+ ptrmapPutOvflPtr(pPage, pPage, pCell, &rc);
if( !pPage->leaf ){
Pgno childPgno = get4byte(pCell);
@@ -64636,8 +71263,6 @@ static int setChildPtrmaps(MemPage *pPage){
ptrmapPut(pBt, childPgno, PTRMAP_BTREE, pgno, &rc);
}
-set_child_ptrmaps_out:
- pPage->isInit = isInitOrig;
return rc;
}
@@ -64656,21 +71281,20 @@ set_child_ptrmaps_out:
** overflow page in the list.
*/
static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3PagerIswriteable(pPage->pDbPage) );
if( eType==PTRMAP_OVERFLOW2 ){
/* The pointer is always the first 4 bytes of the page in this case. */
if( get4byte(pPage->aData)!=iFrom ){
- return SQLITE_CORRUPT_BKPT;
+ return SQLITE_CORRUPT_PAGE(pPage);
}
put4byte(pPage->aData, iTo);
}else{
- u8 isInitOrig = pPage->isInit;
int i;
int nCell;
int rc;
- rc = btreeInitPage(pPage);
+ rc = pPage->isInit ? SQLITE_OK : btreeInitPage(pPage);
if( rc ) return rc;
nCell = pPage->nCell;
@@ -64679,12 +71303,14 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){
if( eType==PTRMAP_OVERFLOW1 ){
CellInfo info;
pPage->xParseCell(pPage, pCell, &info);
- if( info.nLocal<info.nPayload
- && pCell+info.nSize-1<=pPage->aData+pPage->maskPage
- && iFrom==get4byte(pCell+info.nSize-4)
- ){
- put4byte(pCell+info.nSize-4, iTo);
- break;
+ if( info.nLocal<info.nPayload ){
+ if( pCell+info.nSize > pPage->aData+pPage->pBt->usableSize ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
+ if( iFrom==get4byte(pCell+info.nSize-4) ){
+ put4byte(pCell+info.nSize-4, iTo);
+ break;
+ }
}
}else{
if( get4byte(pCell)==iFrom ){
@@ -64697,12 +71323,10 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){
if( i==nCell ){
if( eType!=PTRMAP_BTREE ||
get4byte(&pPage->aData[pPage->hdrOffset+8])!=iFrom ){
- return SQLITE_CORRUPT_BKPT;
+ return SQLITE_CORRUPT_PAGE(pPage);
}
put4byte(&pPage->aData[pPage->hdrOffset+8], iTo);
}
-
- pPage->isInit = isInitOrig;
}
return SQLITE_OK;
}
@@ -64723,7 +71347,7 @@ static int relocatePage(
u8 eType, /* Pointer map 'type' entry for pDbPage */
Pgno iPtrPage, /* Pointer map 'page-no' entry for pDbPage */
Pgno iFreePage, /* The location to move pDbPage to */
- int isCommit /* isCommit flag passed to sqlite3PagerMovepage */
+ int isCommit /* isCommit flag passed to tdsqlite3PagerMovepage */
){
MemPage *pPtrPage; /* The page that contains a pointer to pDbPage */
Pgno iDbPage = pDbPage->pgno;
@@ -64732,13 +71356,14 @@ static int relocatePage(
assert( eType==PTRMAP_OVERFLOW2 || eType==PTRMAP_OVERFLOW1 ||
eType==PTRMAP_BTREE || eType==PTRMAP_ROOTPAGE );
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
assert( pDbPage->pBt==pBt );
+ if( iDbPage<3 ) return SQLITE_CORRUPT_BKPT;
/* Move page iDbPage from its current location to page number iFreePage */
TRACE(("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n",
iDbPage, iFreePage, iPtrPage, eType));
- rc = sqlite3PagerMovepage(pPager, pDbPage->pDbPage, iFreePage, isCommit);
+ rc = tdsqlite3PagerMovepage(pPager, pDbPage->pDbPage, iFreePage, isCommit);
if( rc!=SQLITE_OK ){
return rc;
}
@@ -64776,7 +71401,7 @@ static int relocatePage(
if( rc!=SQLITE_OK ){
return rc;
}
- rc = sqlite3PagerWrite(pPtrPage->pDbPage);
+ rc = tdsqlite3PagerWrite(pPtrPage->pDbPage);
if( rc!=SQLITE_OK ){
releasePage(pPtrPage);
return rc;
@@ -64814,7 +71439,7 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){
Pgno nFreeList; /* Number of pages still on the free-list */
int rc;
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
assert( iLastPg>nFin );
if( !PTRMAP_ISPAGE(pBt, iLastPg) && iLastPg!=PENDING_BYTE_PAGE(pBt) ){
@@ -64932,11 +71557,11 @@ static Pgno finalDbSize(BtShared *pBt, Pgno nOrig, Pgno nFree){
** SQLITE_DONE is returned. If it is not finished, but no error occurred,
** SQLITE_OK is returned. Otherwise an SQLite error code.
*/
-SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeIncrVacuum(Btree *p){
int rc;
BtShared *pBt = p->pBt;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
assert( pBt->inTransaction==TRANS_WRITE && p->inTrans==TRANS_WRITE );
if( !pBt->autoVacuum ){
rc = SQLITE_DONE;
@@ -64954,19 +71579,19 @@ SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *p){
rc = incrVacuumStep(pBt, nFin, nOrig, 0);
}
if( rc==SQLITE_OK ){
- rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
+ rc = tdsqlite3PagerWrite(pBt->pPage1->pDbPage);
put4byte(&pBt->pPage1->aData[28], pBt->nPage);
}
}else{
rc = SQLITE_DONE;
}
}
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
/*
-** This routine is called prior to sqlite3PagerCommit when a transaction
+** This routine is called prior to tdsqlite3PagerCommit when a transaction
** is committed for an auto-vacuum database.
**
** If SQLITE_OK is returned, then *pnTrunc is set to the number of pages
@@ -64977,9 +71602,9 @@ SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *p){
static int autoVacuumCommit(BtShared *pBt){
int rc = SQLITE_OK;
Pager *pPager = pBt->pPager;
- VVA_ONLY( int nRef = sqlite3PagerRefcount(pPager); )
+ VVA_ONLY( int nRef = tdsqlite3PagerRefcount(pPager); )
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
invalidateAllOverflowCache(pBt);
assert(pBt->autoVacuum);
if( !pBt->incrVacuum ){
@@ -65007,7 +71632,7 @@ static int autoVacuumCommit(BtShared *pBt){
rc = incrVacuumStep(pBt, nFin, iFree, 1);
}
if( (rc==SQLITE_DONE || rc==SQLITE_OK) && nFree>0 ){
- rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
+ rc = tdsqlite3PagerWrite(pBt->pPage1->pDbPage);
put4byte(&pBt->pPage1->aData[32], 0);
put4byte(&pBt->pPage1->aData[36], 0);
put4byte(&pBt->pPage1->aData[28], nFin);
@@ -65015,11 +71640,11 @@ static int autoVacuumCommit(BtShared *pBt){
pBt->nPage = nFin;
}
if( rc!=SQLITE_OK ){
- sqlite3PagerRollback(pPager);
+ tdsqlite3PagerRollback(pPager);
}
}
- assert( nRef>=sqlite3PagerRefcount(pPager) );
+ assert( nRef>=tdsqlite3PagerRefcount(pPager) );
return rc;
}
@@ -65037,7 +71662,7 @@ static int autoVacuumCommit(BtShared *pBt){
** database are written into the database file and flushed to oxide.
** At the end of this call, the rollback journal still exists on the
** disk and we are still holding all locks, so the transaction has not
-** committed. See sqlite3BtreeCommitPhaseTwo() for the second phase of the
+** committed. See tdsqlite3BtreeCommitPhaseTwo() for the second phase of the
** commit process.
**
** This call is a no-op if no write-transaction is currently active on pBt.
@@ -65053,25 +71678,25 @@ static int autoVacuumCommit(BtShared *pBt){
** Once this is routine has returned, the only thing required to commit
** the write-transaction for this database file is to delete the journal.
*/
-SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
+SQLITE_PRIVATE int tdsqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
int rc = SQLITE_OK;
if( p->inTrans==TRANS_WRITE ){
BtShared *pBt = p->pBt;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pBt->autoVacuum ){
rc = autoVacuumCommit(pBt);
if( rc!=SQLITE_OK ){
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
}
if( pBt->bDoTruncate ){
- sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage);
+ tdsqlite3PagerTruncateImage(pBt->pPager, pBt->nPage);
}
#endif
- rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0);
- sqlite3BtreeLeave(p);
+ rc = tdsqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0);
+ tdsqlite3BtreeLeave(p);
}
return rc;
}
@@ -65082,8 +71707,8 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
*/
static void btreeEndTransaction(Btree *p){
BtShared *pBt = p->pBt;
- sqlite3 *db = p->db;
- assert( sqlite3BtreeHoldsMutex(p) );
+ tdsqlite3 *db = p->db;
+ assert( tdsqlite3BtreeHoldsMutex(p) );
#ifndef SQLITE_OMIT_AUTOVACUUM
pBt->bDoTruncate = 0;
@@ -65120,8 +71745,8 @@ static void btreeEndTransaction(Btree *p){
** Commit the transaction currently in progress.
**
** This routine implements the second phase of a 2-phase commit. The
-** sqlite3BtreeCommitPhaseOne() routine does the first phase and should
-** be invoked prior to calling this routine. The sqlite3BtreeCommitPhaseOne()
+** tdsqlite3BtreeCommitPhaseOne() routine does the first phase and should
+** be invoked prior to calling this routine. The tdsqlite3BtreeCommitPhaseOne()
** routine did all the work of writing information out to disk and flushing the
** contents so that they are written onto the disk platter. All this
** routine has to do is delete or truncate or zero the header in the
@@ -65142,10 +71767,10 @@ static void btreeEndTransaction(Btree *p){
** This will release the write lock on the database file. If there
** are no active cursors, it also releases the read lock.
*/
-SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){
+SQLITE_PRIVATE int tdsqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){
if( p->inTrans==TRANS_NONE ) return SQLITE_OK;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
btreeIntegrity(p);
/* If the handle has a write-transaction open, commit the shared-btrees
@@ -65156,9 +71781,9 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){
BtShared *pBt = p->pBt;
assert( pBt->inTransaction==TRANS_WRITE );
assert( pBt->nTransaction>0 );
- rc = sqlite3PagerCommitPhaseTwo(pBt->pPager);
+ rc = tdsqlite3PagerCommitPhaseTwo(pBt->pPager);
if( rc!=SQLITE_OK && bCleanup==0 ){
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
p->iDataVersion--; /* Compensate for pPager->iDataVersion++; */
@@ -65167,21 +71792,21 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){
}
btreeEndTransaction(p);
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return SQLITE_OK;
}
/*
** Do both phases of a commit.
*/
-SQLITE_PRIVATE int sqlite3BtreeCommit(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeCommit(Btree *p){
int rc;
- sqlite3BtreeEnter(p);
- rc = sqlite3BtreeCommitPhaseOne(p, 0);
+ tdsqlite3BtreeEnter(p);
+ rc = tdsqlite3BtreeCommitPhaseOne(p, 0);
if( rc==SQLITE_OK ){
- rc = sqlite3BtreeCommitPhaseTwo(p, 0);
+ rc = tdsqlite3BtreeCommitPhaseTwo(p, 0);
}
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -65211,39 +71836,47 @@ SQLITE_PRIVATE int sqlite3BtreeCommit(Btree *p){
** SQLITE_OK is returned if successful, or if an error occurs while
** saving a cursor position, an SQLite error code.
*/
-SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int writeOnly){
+SQLITE_PRIVATE int tdsqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int writeOnly){
BtCursor *p;
int rc = SQLITE_OK;
assert( (writeOnly==0 || writeOnly==1) && BTCF_WriteFlag==1 );
if( pBtree ){
- sqlite3BtreeEnter(pBtree);
+ tdsqlite3BtreeEnter(pBtree);
for(p=pBtree->pBt->pCursor; p; p=p->pNext){
- int i;
if( writeOnly && (p->curFlags & BTCF_WriteFlag)==0 ){
if( p->eState==CURSOR_VALID || p->eState==CURSOR_SKIPNEXT ){
rc = saveCursorPosition(p);
if( rc!=SQLITE_OK ){
- (void)sqlite3BtreeTripAllCursors(pBtree, rc, 0);
+ (void)tdsqlite3BtreeTripAllCursors(pBtree, rc, 0);
break;
}
}
}else{
- sqlite3BtreeClearCursor(p);
+ tdsqlite3BtreeClearCursor(p);
p->eState = CURSOR_FAULT;
p->skipNext = errCode;
}
- for(i=0; i<=p->iPage; i++){
- releasePage(p->apPage[i]);
- p->apPage[i] = 0;
- }
+ btreeReleaseAllCursorPages(p);
}
- sqlite3BtreeLeave(pBtree);
+ tdsqlite3BtreeLeave(pBtree);
}
return rc;
}
/*
+** Set the pBt->nPage field correctly, according to the current
+** state of the database. Assume pBt->pPage1 is valid.
+*/
+static void btreeSetNPage(BtShared *pBt, MemPage *pPage1){
+ int nPage = get4byte(&pPage1->aData[28]);
+ testcase( nPage==0 );
+ if( nPage==0 ) tdsqlite3PagerPagecount(pBt->pPager, &nPage);
+ testcase( pBt->nPage!=nPage );
+ pBt->nPage = nPage;
+}
+
+/*
** Rollback the transaction in progress.
**
** If tripCode is not SQLITE_OK then cursors will be invalidated (tripped).
@@ -65254,14 +71887,14 @@ SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int wr
** This will release the write lock on the database file. If there
** are no active cursors, it also releases the read lock.
*/
-SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){
+SQLITE_PRIVATE int tdsqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){
int rc;
BtShared *pBt = p->pBt;
MemPage *pPage1;
assert( writeOnly==1 || writeOnly==0 );
assert( tripCode==SQLITE_ABORT_ROLLBACK || tripCode==SQLITE_OK );
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
if( tripCode==SQLITE_OK ){
rc = tripCode = saveAllCursors(pBt, 0, 0);
if( rc ) writeOnly = 0;
@@ -65269,7 +71902,7 @@ SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){
rc = SQLITE_OK;
}
if( tripCode ){
- int rc2 = sqlite3BtreeTripAllCursors(p, tripCode, writeOnly);
+ int rc2 = tdsqlite3BtreeTripAllCursors(p, tripCode, writeOnly);
assert( rc==SQLITE_OK || (writeOnly==0 && rc2==SQLITE_OK) );
if( rc2!=SQLITE_OK ) rc = rc2;
}
@@ -65279,7 +71912,7 @@ SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){
int rc2;
assert( TRANS_WRITE==pBt->inTransaction );
- rc2 = sqlite3PagerRollback(pBt->pPager);
+ rc2 = tdsqlite3PagerRollback(pBt->pPager);
if( rc2!=SQLITE_OK ){
rc = rc2;
}
@@ -65288,12 +71921,8 @@ SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){
** call btreeGetPage() on page 1 again to make
** sure pPage1->aData is set correctly. */
if( btreeGetPage(pBt, 1, &pPage1, 0)==SQLITE_OK ){
- int nPage = get4byte(28+(u8*)pPage1->aData);
- testcase( nPage==0 );
- if( nPage==0 ) sqlite3PagerPagecount(pBt->pPager, &nPage);
- testcase( pBt->nPage!=nPage );
- pBt->nPage = nPage;
- releasePage(pPage1);
+ btreeSetNPage(pBt, pPage1);
+ releasePageOne(pPage1);
}
assert( countValidCursors(pBt, 1)==0 );
pBt->inTransaction = TRANS_READ;
@@ -65301,7 +71930,7 @@ SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){
}
btreeEndTransaction(p);
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -65321,12 +71950,12 @@ SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){
** including the new anonymous savepoint, open on the B-Tree. i.e. if there
** are no active savepoints and no other statement-transactions open,
** iStatement is 1. This anonymous savepoint can be released or rolled back
-** using the sqlite3BtreeSavepoint() function.
+** using the tdsqlite3BtreeSavepoint() function.
*/
-SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree *p, int iStatement){
+SQLITE_PRIVATE int tdsqlite3BtreeBeginStmt(Btree *p, int iStatement){
int rc;
BtShared *pBt = p->pBt;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
assert( p->inTrans==TRANS_WRITE );
assert( (pBt->btsFlags & BTS_READ_ONLY)==0 );
assert( iStatement>0 );
@@ -65337,8 +71966,8 @@ SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree *p, int iStatement){
** SQL statements. It is illegal to open, release or rollback any
** such savepoints while the statement transaction savepoint is active.
*/
- rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStatement);
- sqlite3BtreeLeave(p);
+ rc = tdsqlite3PagerOpenSavepoint(pBt->pPager, iStatement);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -65354,27 +71983,31 @@ SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree *p, int iStatement){
** from a normal transaction rollback, as no locks are released and the
** transaction remains open.
*/
-SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){
+SQLITE_PRIVATE int tdsqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){
int rc = SQLITE_OK;
if( p && p->inTrans==TRANS_WRITE ){
BtShared *pBt = p->pBt;
assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK );
assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) );
- sqlite3BtreeEnter(p);
- rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint);
+ tdsqlite3BtreeEnter(p);
+ if( op==SAVEPOINT_ROLLBACK ){
+ rc = saveAllCursors(pBt, 0, 0);
+ }
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3PagerSavepoint(pBt->pPager, op, iSavepoint);
+ }
if( rc==SQLITE_OK ){
if( iSavepoint<0 && (pBt->btsFlags & BTS_INITIALLY_EMPTY)!=0 ){
pBt->nPage = 0;
}
rc = newDatabase(pBt);
- pBt->nPage = get4byte(28 + pBt->pPage1->aData);
+ btreeSetNPage(pBt, pBt->pPage1);
- /* The database size was written into the offset 28 of the header
- ** when the transaction started, so we know that the value at offset
- ** 28 is nonzero. */
- assert( pBt->nPage>0 );
+ /* pBt->nPage might be zero if the database was corrupt when
+ ** the transaction was started. Otherwise, it must be at least 1. */
+ assert( CORRUPT_DB || pBt->nPage>0 );
}
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
}
return rc;
}
@@ -65418,7 +72051,7 @@ SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){
** root page of a b-tree. If it is not, then the cursor acquired
** will not work correctly.
**
-** It is assumed that the sqlite3BtreeCursorZero() has been called
+** It is assumed that the tdsqlite3BtreeCursorZero() has been called
** on pCur to initialize the memory space prior to invoking this routine.
*/
static int btreeCursor(
@@ -65431,7 +72064,7 @@ static int btreeCursor(
BtShared *pBt = p->pBt; /* Shared b-tree handle */
BtCursor *pX; /* Looping over other all cursors */
- assert( sqlite3BtreeHoldsMutex(p) );
+ assert( tdsqlite3BtreeHoldsMutex(p) );
assert( wrFlag==0
|| wrFlag==BTREE_WRCSR
|| wrFlag==(BTREE_WRCSR|BTREE_FORDELETE)
@@ -65440,8 +72073,9 @@ static int btreeCursor(
/* The following assert statements verify that if this is a sharable
** b-tree database, the connection is holding the required table locks,
** and that no other connection has any open cursor that conflicts with
- ** this lock. */
- assert( hasSharedCacheTableLock(p, iTable, pKeyInfo!=0, (wrFlag?2:1)) );
+ ** this lock. The iTable<1 term disables the check for corrupt schemas. */
+ assert( hasSharedCacheTableLock(p, iTable, pKeyInfo!=0, (wrFlag?2:1))
+ || iTable<1 );
assert( wrFlag==0 || !hasReadConflicts(p, iTable) );
/* Assert that the caller has opened the required transaction. */
@@ -65454,9 +72088,13 @@ static int btreeCursor(
allocateTempSpace(pBt);
if( pBt->pTmpSpace==0 ) return SQLITE_NOMEM_BKPT;
}
- if( iTable==1 && btreePagecount(pBt)==0 ){
- assert( wrFlag==0 );
- iTable = 0;
+ if( iTable<=1 ){
+ if( iTable<1 ){
+ return SQLITE_CORRUPT_BKPT;
+ }else if( btreePagecount(pBt)==0 ){
+ assert( wrFlag==0 );
+ iTable = 0;
+ }
}
/* Now that no other errors can occur, finish filling in the BtCursor
@@ -65481,22 +72119,31 @@ static int btreeCursor(
pCur->eState = CURSOR_INVALID;
return SQLITE_OK;
}
-SQLITE_PRIVATE int sqlite3BtreeCursor(
+static int btreeCursorWithLock(
+ Btree *p, /* The btree */
+ int iTable, /* Root page of table to open */
+ int wrFlag, /* 1 to write. 0 read-only */
+ struct KeyInfo *pKeyInfo, /* First arg to comparison function */
+ BtCursor *pCur /* Space for new cursor */
+){
+ int rc;
+ tdsqlite3BtreeEnter(p);
+ rc = btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur);
+ tdsqlite3BtreeLeave(p);
+ return rc;
+}
+SQLITE_PRIVATE int tdsqlite3BtreeCursor(
Btree *p, /* The btree */
int iTable, /* Root page of table to open */
int wrFlag, /* 1 to write. 0 read-only */
struct KeyInfo *pKeyInfo, /* First arg to xCompare() */
BtCursor *pCur /* Write new cursor here */
){
- int rc;
- if( iTable<1 ){
- rc = SQLITE_CORRUPT_BKPT;
+ if( p->sharable ){
+ return btreeCursorWithLock(p, iTable, wrFlag, pKeyInfo, pCur);
}else{
- sqlite3BtreeEnter(p);
- rc = btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur);
- sqlite3BtreeLeave(p);
+ return btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur);
}
- return rc;
}
/*
@@ -65507,7 +72154,7 @@ SQLITE_PRIVATE int sqlite3BtreeCursor(
** to users so they cannot do the sizeof() themselves - they must call
** this routine.
*/
-SQLITE_PRIVATE int sqlite3BtreeCursorSize(void){
+SQLITE_PRIVATE int tdsqlite3BtreeCursorSize(void){
return ROUND8(sizeof(BtCursor));
}
@@ -65519,21 +72166,19 @@ SQLITE_PRIVATE int sqlite3BtreeCursorSize(void){
** do not need to be zeroed and they are large, so we can save a lot
** of run-time by skipping the initialization of those elements.
*/
-SQLITE_PRIVATE void sqlite3BtreeCursorZero(BtCursor *p){
- memset(p, 0, offsetof(BtCursor, iPage));
+SQLITE_PRIVATE void tdsqlite3BtreeCursorZero(BtCursor *p){
+ memset(p, 0, offsetof(BtCursor, BTCURSOR_FIRST_UNINIT));
}
/*
** Close a cursor. The read lock on the database file is released
** when the last cursor is closed.
*/
-SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){
+SQLITE_PRIVATE int tdsqlite3BtreeCloseCursor(BtCursor *pCur){
Btree *pBtree = pCur->pBtree;
if( pBtree ){
- int i;
BtShared *pBt = pCur->pBt;
- sqlite3BtreeEnter(pBtree);
- sqlite3BtreeClearCursor(pCur);
+ tdsqlite3BtreeEnter(pBtree);
assert( pBt->pCursor!=0 );
if( pBt->pCursor==pCur ){
pBt->pCursor = pCur->pNext;
@@ -65547,13 +72192,12 @@ SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){
pPrev = pPrev->pNext;
}while( ALWAYS(pPrev) );
}
- for(i=0; i<=pCur->iPage; i++){
- releasePage(pCur->apPage[i]);
- }
+ btreeReleaseAllCursorPages(pCur);
unlockBtreeIfUnused(pBt);
- sqlite3_free(pCur->aOverflow);
- /* sqlite3_free(pCur); */
- sqlite3BtreeLeave(pBtree);
+ tdsqlite3_free(pCur->aOverflow);
+ tdsqlite3_free(pCur->pKey);
+ tdsqlite3BtreeLeave(pBtree);
+ pCur->pBtree = 0;
}
return SQLITE_OK;
}
@@ -65567,21 +72211,27 @@ SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){
** Using this cache reduces the number of calls to btreeParseCell().
*/
#ifndef NDEBUG
+ static int cellInfoEqual(CellInfo *a, CellInfo *b){
+ if( a->nKey!=b->nKey ) return 0;
+ if( a->pPayload!=b->pPayload ) return 0;
+ if( a->nPayload!=b->nPayload ) return 0;
+ if( a->nLocal!=b->nLocal ) return 0;
+ if( a->nSize!=b->nSize ) return 0;
+ return 1;
+ }
static void assertCellInfo(BtCursor *pCur){
CellInfo info;
- int iPage = pCur->iPage;
memset(&info, 0, sizeof(info));
- btreeParseCell(pCur->apPage[iPage], pCur->aiIdx[iPage], &info);
- assert( CORRUPT_DB || memcmp(&info, &pCur->info, sizeof(info))==0 );
+ btreeParseCell(pCur->pPage, pCur->ix, &info);
+ assert( CORRUPT_DB || cellInfoEqual(&info, &pCur->info) );
}
#else
#define assertCellInfo(x)
#endif
static SQLITE_NOINLINE void getCellInfo(BtCursor *pCur){
if( pCur->info.nSize==0 ){
- int iPage = pCur->iPage;
pCur->curFlags |= BTCF_ValidNKey;
- btreeParseCell(pCur->apPage[iPage],pCur->aiIdx[iPage],&pCur->info);
+ btreeParseCell(pCur->pPage,pCur->ix,&pCur->info);
}else{
assertCellInfo(pCur);
}
@@ -65593,10 +72243,14 @@ static SQLITE_NOINLINE void getCellInfo(BtCursor *pCur){
** that is currently pointing to a row in a (non-empty) table.
** This is a verification routine is used only within assert() statements.
*/
-SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor *pCur){
+SQLITE_PRIVATE int tdsqlite3BtreeCursorIsValid(BtCursor *pCur){
return pCur && pCur->eState==CURSOR_VALID;
}
#endif /* NDEBUG */
+SQLITE_PRIVATE int tdsqlite3BtreeCursorIsValidNN(BtCursor *pCur){
+ assert( pCur!=0 );
+ return pCur->eState==CURSOR_VALID;
+}
/*
** Return the value of the integer key or "rowid" for a table btree.
@@ -65604,7 +72258,7 @@ SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor *pCur){
** ordinary table btree. If the cursor points to an index btree or
** is invalid, the result of this routine is undefined.
*/
-SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor *pCur){
+SQLITE_PRIVATE i64 tdsqlite3BtreeIntegerKey(BtCursor *pCur){
assert( cursorHoldsMutex(pCur) );
assert( pCur->eState==CURSOR_VALID );
assert( pCur->curIntKey );
@@ -65613,6 +72267,32 @@ SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor *pCur){
}
/*
+** Pin or unpin a cursor.
+*/
+SQLITE_PRIVATE void tdsqlite3BtreeCursorPin(BtCursor *pCur){
+ assert( (pCur->curFlags & BTCF_Pinned)==0 );
+ pCur->curFlags |= BTCF_Pinned;
+}
+SQLITE_PRIVATE void tdsqlite3BtreeCursorUnpin(BtCursor *pCur){
+ assert( (pCur->curFlags & BTCF_Pinned)!=0 );
+ pCur->curFlags &= ~BTCF_Pinned;
+}
+
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+/*
+** Return the offset into the database file for the start of the
+** payload to which the cursor is pointing.
+*/
+SQLITE_PRIVATE i64 tdsqlite3BtreeOffset(BtCursor *pCur){
+ assert( cursorHoldsMutex(pCur) );
+ assert( pCur->eState==CURSOR_VALID );
+ getCellInfo(pCur);
+ return (i64)pCur->pBt->pageSize*((i64)pCur->pPage->pgno - 1) +
+ (i64)(pCur->info.pPayload - pCur->pPage->aData);
+}
+#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */
+
+/*
** Return the number of bytes of payload for the entry that pCur is
** currently pointing to. For table btrees, this will be the amount
** of data. For index btrees, this will be the size of the key.
@@ -65621,7 +72301,7 @@ SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor *pCur){
** valid entry. In other words, the calling procedure must guarantee
** that the cursor has Cursor.eState==CURSOR_VALID.
*/
-SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor *pCur){
+SQLITE_PRIVATE u32 tdsqlite3BtreePayloadSize(BtCursor *pCur){
assert( cursorHoldsMutex(pCur) );
assert( pCur->eState==CURSOR_VALID );
getCellInfo(pCur);
@@ -65629,6 +72309,25 @@ SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor *pCur){
}
/*
+** Return an upper bound on the size of any record for the table
+** that the cursor is pointing into.
+**
+** This is an optimization. Everything will still work if this
+** routine always returns 2147483647 (which is the largest record
+** that SQLite can handle) or more. But returning a smaller value might
+** prevent large memory allocations when trying to interpret a
+** corrupt datrabase.
+**
+** The current implementation merely returns the size of the underlying
+** database file.
+*/
+SQLITE_PRIVATE tdsqlite3_int64 tdsqlite3BtreeMaxRecordSize(BtCursor *pCur){
+ assert( cursorHoldsMutex(pCur) );
+ assert( pCur->eState==CURSOR_VALID );
+ return pCur->pBt->pageSize * (tdsqlite3_int64)pCur->pBt->nPage;
+}
+
+/*
** Given the page number of an overflow page in the database (parameter
** ovfl), this function finds the page number of the next page in the
** linked list of overflow pages. If possible, it uses the auto-vacuum
@@ -65657,7 +72356,7 @@ static int getOverflowPage(
MemPage *pPage = 0;
int rc = SQLITE_OK;
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
assert(pPgnoNext);
#ifndef SQLITE_OMIT_AUTOVACUUM
@@ -65710,7 +72409,7 @@ static int getOverflowPage(
** pPayload is a pointer to data stored on database page pDbPage.
** If argument eOp is false, then nByte bytes of data are copied
** from pPayload to the buffer pointed at by pBuf. If eOp is true,
-** then sqlite3PagerWrite() is called on pDbPage and nByte bytes
+** then tdsqlite3PagerWrite() is called on pDbPage and nByte bytes
** of data are copied from the buffer pBuf to pPayload.
**
** SQLITE_OK is returned on success, otherwise an error code.
@@ -65724,7 +72423,7 @@ static int copyPayload(
){
if( eOp ){
/* Copy data from buffer to page (a write operation) */
- int rc = sqlite3PagerWrite(pDbPage);
+ int rc = tdsqlite3PagerWrite(pDbPage);
if( rc!=SQLITE_OK ){
return rc;
}
@@ -65743,7 +72442,6 @@ static int copyPayload(
**
** 0: The operation is a read. Populate the overflow cache.
** 1: The operation is a write. Populate the overflow cache.
-** 2: The operation is a read. Do not populate the overflow cache.
**
** A total of "amt" bytes are read or written beginning at "offset".
** Data is read to or from the buffer pBuf.
@@ -65751,13 +72449,13 @@ static int copyPayload(
** The content being read or written might appear on the main page
** or be scattered out on multiple overflow pages.
**
-** If the current cursor entry uses one or more overflow pages and the
-** eOp argument is not 2, this function may allocate space for and lazily
-** populates the overflow page-list cache array (BtCursor.aOverflow).
+** If the current cursor entry uses one or more overflow pages
+** this function may allocate space for and lazily populate
+** the overflow page-list cache array (BtCursor.aOverflow).
** Subsequent calls use this cache to make seeking to the supplied offset
** more efficient.
**
-** Once an overflow page-list cache has been allocated, it may be
+** Once an overflow page-list cache has been allocated, it must be
** invalidated if some other cursor writes to the same table, or if
** the cursor is moved to a different row. Additionally, in auto-vacuum
** mode, the following events may invalidate an overflow page-list cache.
@@ -65776,24 +72474,20 @@ static int accessPayload(
unsigned char *aPayload;
int rc = SQLITE_OK;
int iIdx = 0;
- MemPage *pPage = pCur->apPage[pCur->iPage]; /* Btree page of current entry */
+ MemPage *pPage = pCur->pPage; /* Btree page of current entry */
BtShared *pBt = pCur->pBt; /* Btree this cursor belongs to */
#ifdef SQLITE_DIRECT_OVERFLOW_READ
- unsigned char * const pBufStart = pBuf;
- int bEnd; /* True if reading to end of data */
+ unsigned char * const pBufStart = pBuf; /* Start of original out buffer */
#endif
assert( pPage );
+ assert( eOp==0 || eOp==1 );
assert( pCur->eState==CURSOR_VALID );
- assert( pCur->aiIdx[pCur->iPage]<pPage->nCell );
+ assert( pCur->ix<pPage->nCell );
assert( cursorHoldsMutex(pCur) );
- assert( eOp!=2 || offset==0 ); /* Always start from beginning for eOp==2 */
getCellInfo(pCur);
aPayload = pCur->info.pPayload;
-#ifdef SQLITE_DIRECT_OVERFLOW_READ
- bEnd = offset+amt==pCur->info.nPayload;
-#endif
assert( offset+amt <= pCur->info.nPayload );
assert( aPayload > pPage->aData );
@@ -65803,7 +72497,7 @@ static int accessPayload(
** &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize]
** but is recast into its current form to avoid integer overflow problems
*/
- return SQLITE_CORRUPT_BKPT;
+ return SQLITE_CORRUPT_PAGE(pPage);
}
/* Check if data must be read/written to/from the btree page itself. */
@@ -65812,7 +72506,7 @@ static int accessPayload(
if( a+offset>pCur->info.nLocal ){
a = pCur->info.nLocal - offset;
}
- rc = copyPayload(&aPayload[offset], pBuf, a, (eOp & 0x01), pPage->pDbPage);
+ rc = copyPayload(&aPayload[offset], pBuf, a, eOp, pPage->pDbPage);
offset = 0;
pBuf += a;
amt -= a;
@@ -65828,53 +72522,47 @@ static int accessPayload(
nextPage = get4byte(&aPayload[pCur->info.nLocal]);
/* If the BtCursor.aOverflow[] has not been allocated, allocate it now.
- ** Except, do not allocate aOverflow[] for eOp==2.
**
** The aOverflow[] array is sized at one entry for each overflow page
** in the overflow chain. The page number of the first overflow page is
** stored in aOverflow[0], etc. A value of 0 in the aOverflow[] array
** means "not yet known" (the cache is lazily populated).
*/
- if( eOp!=2 && (pCur->curFlags & BTCF_ValidOvfl)==0 ){
+ if( (pCur->curFlags & BTCF_ValidOvfl)==0 ){
int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize;
- if( nOvfl>pCur->nOvflAlloc ){
- Pgno *aNew = (Pgno*)sqlite3Realloc(
+ if( pCur->aOverflow==0
+ || nOvfl*(int)sizeof(Pgno) > tdsqlite3MallocSize(pCur->aOverflow)
+ ){
+ Pgno *aNew = (Pgno*)tdsqlite3Realloc(
pCur->aOverflow, nOvfl*2*sizeof(Pgno)
);
if( aNew==0 ){
- rc = SQLITE_NOMEM_BKPT;
+ return SQLITE_NOMEM_BKPT;
}else{
- pCur->nOvflAlloc = nOvfl*2;
pCur->aOverflow = aNew;
}
}
- if( rc==SQLITE_OK ){
- memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno));
- pCur->curFlags |= BTCF_ValidOvfl;
+ memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno));
+ pCur->curFlags |= BTCF_ValidOvfl;
+ }else{
+ /* If the overflow page-list cache has been allocated and the
+ ** entry for the first required overflow page is valid, skip
+ ** directly to it.
+ */
+ if( pCur->aOverflow[offset/ovflSize] ){
+ iIdx = (offset/ovflSize);
+ nextPage = pCur->aOverflow[iIdx];
+ offset = (offset%ovflSize);
}
}
- /* If the overflow page-list cache has been allocated and the
- ** entry for the first required overflow page is valid, skip
- ** directly to it.
- */
- if( (pCur->curFlags & BTCF_ValidOvfl)!=0
- && pCur->aOverflow[offset/ovflSize]
- ){
- iIdx = (offset/ovflSize);
- nextPage = pCur->aOverflow[iIdx];
- offset = (offset%ovflSize);
- }
-
- for( ; rc==SQLITE_OK && amt>0 && nextPage; iIdx++){
-
+ assert( rc==SQLITE_OK && amt>0 );
+ while( nextPage ){
/* If required, populate the overflow page-list cache. */
- if( (pCur->curFlags & BTCF_ValidOvfl)!=0 ){
- assert( pCur->aOverflow[iIdx]==0
- || pCur->aOverflow[iIdx]==nextPage
- || CORRUPT_DB );
- pCur->aOverflow[iIdx] = nextPage;
- }
+ assert( pCur->aOverflow[iIdx]==0
+ || pCur->aOverflow[iIdx]==nextPage
+ || CORRUPT_DB );
+ pCur->aOverflow[iIdx] = nextPage;
if( offset>=ovflSize ){
/* The only reason to read this page is to obtain the page
@@ -65882,11 +72570,7 @@ static int accessPayload(
** data is not required. So first try to lookup the overflow
** page-list cache, if any, then fall back to the getOverflowPage()
** function.
- **
- ** Note that the aOverflow[] array must be allocated because eOp!=2
- ** here. If eOp==2, then offset==0 and this branch is never taken.
*/
- assert( eOp!=2 );
assert( pCur->curFlags & BTCF_ValidOvfl );
assert( pCur->pBtree->db==pBt->db );
if( pCur->aOverflow[iIdx+1] ){
@@ -65899,9 +72583,6 @@ static int accessPayload(
/* Need to read this page properly. It contains some of the
** range of data that is being read (eOp==0) or written (eOp!=0).
*/
-#ifdef SQLITE_DIRECT_OVERFLOW_READ
- sqlite3_file *fd;
-#endif
int a = amt;
if( a + offset > ovflSize ){
a = ovflSize - offset;
@@ -65912,29 +72593,27 @@ static int accessPayload(
**
** 1) this is a read operation, and
** 2) data is required from the start of this overflow page, and
- ** 3) the database is file-backed, and
- ** 4) there is no open write-transaction, and
- ** 5) the database is not a WAL database,
- ** 6) all data from the page is being read.
- ** 7) at least 4 bytes have already been read into the output buffer
+ ** 3) there are no dirty pages in the page-cache
+ ** 4) the database is file-backed, and
+ ** 5) the page is not in the WAL file
+ ** 6) at least 4 bytes have already been read into the output buffer
**
** then data can be read directly from the database file into the
** output buffer, bypassing the page-cache altogether. This speeds
** up loading large records that span many overflow pages.
*/
- if( (eOp&0x01)==0 /* (1) */
+ if( eOp==0 /* (1) */
&& offset==0 /* (2) */
- && (bEnd || a==ovflSize) /* (6) */
- && pBt->inTransaction==TRANS_READ /* (4) */
- && (fd = sqlite3PagerFile(pBt->pPager))->pMethods /* (3) */
- && 0==sqlite3PagerUseWal(pBt->pPager) /* (5) */
- && &pBuf[-4]>=pBufStart /* (7) */
+ && tdsqlite3PagerDirectReadOk(pBt->pPager, nextPage) /* (3,4,5) */
+ && &pBuf[-4]>=pBufStart /* (6) */
){
+ tdsqlite3_file *fd = tdsqlite3PagerFile(pBt->pPager);
u8 aSave[4];
u8 *aWrite = &pBuf[-4];
- assert( aWrite>=pBufStart ); /* hence (7) */
+ assert( aWrite>=pBufStart ); /* due to (6) */
memcpy(aSave, aWrite, 4);
- rc = sqlite3OsRead(fd, aWrite, a+4, (i64)pBt->pageSize*(nextPage-1));
+ rc = tdsqlite3OsRead(fd, aWrite, a+4, (i64)pBt->pageSize*(nextPage-1));
+ if( rc && nextPage>pBt->nPage ) rc = SQLITE_CORRUPT_BKPT;
nextPage = get4byte(aWrite);
memcpy(aWrite, aSave, 4);
}else
@@ -65942,77 +72621,87 @@ static int accessPayload(
{
DbPage *pDbPage;
- rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage,
- ((eOp&0x01)==0 ? PAGER_GET_READONLY : 0)
+ rc = tdsqlite3PagerGet(pBt->pPager, nextPage, &pDbPage,
+ (eOp==0 ? PAGER_GET_READONLY : 0)
);
if( rc==SQLITE_OK ){
- aPayload = sqlite3PagerGetData(pDbPage);
+ aPayload = tdsqlite3PagerGetData(pDbPage);
nextPage = get4byte(aPayload);
- rc = copyPayload(&aPayload[offset+4], pBuf, a, (eOp&0x01), pDbPage);
- sqlite3PagerUnref(pDbPage);
+ rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage);
+ tdsqlite3PagerUnref(pDbPage);
offset = 0;
}
}
amt -= a;
+ if( amt==0 ) return rc;
pBuf += a;
}
+ if( rc ) break;
+ iIdx++;
}
}
if( rc==SQLITE_OK && amt>0 ){
- return SQLITE_CORRUPT_BKPT;
+ /* Overflow chain ends prematurely */
+ return SQLITE_CORRUPT_PAGE(pPage);
}
return rc;
}
/*
-** Read part of the key associated with cursor pCur. Exactly
-** "amt" bytes will be transferred into pBuf[]. The transfer
+** Read part of the payload for the row at which that cursor pCur is currently
+** pointing. "amt" bytes will be transferred into pBuf[]. The transfer
** begins at "offset".
**
-** The caller must ensure that pCur is pointing to a valid row
-** in the table.
+** pCur can be pointing to either a table or an index b-tree.
+** If pointing to a table btree, then the content section is read. If
+** pCur is pointing to an index b-tree then the key section is read.
+**
+** For tdsqlite3BtreePayload(), the caller must ensure that pCur is pointing
+** to a valid row in the table. For tdsqlite3BtreePayloadChecked(), the
+** cursor might be invalid or might need to be restored before being read.
**
** Return SQLITE_OK on success or an error code if anything goes
** wrong. An error is returned if "offset+amt" is larger than
** the available payload.
*/
-SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
+SQLITE_PRIVATE int tdsqlite3BtreePayload(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
assert( cursorHoldsMutex(pCur) );
assert( pCur->eState==CURSOR_VALID );
- assert( pCur->iPage>=0 && pCur->apPage[pCur->iPage] );
- assert( pCur->aiIdx[pCur->iPage]<pCur->apPage[pCur->iPage]->nCell );
+ assert( pCur->iPage>=0 && pCur->pPage );
+ assert( pCur->ix<pCur->pPage->nCell );
return accessPayload(pCur, offset, amt, (unsigned char*)pBuf, 0);
}
/*
-** Read part of the data associated with cursor pCur. Exactly
-** "amt" bytes will be transfered into pBuf[]. The transfer
-** begins at "offset".
-**
-** Return SQLITE_OK on success or an error code if anything goes
-** wrong. An error is returned if "offset+amt" is larger than
-** the available payload.
+** This variant of tdsqlite3BtreePayload() works even if the cursor has not
+** in the CURSOR_VALID state. It is only used by the tdsqlite3_blob_read()
+** interface.
*/
-SQLITE_PRIVATE int sqlite3BtreeData(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
- int rc;
-
#ifndef SQLITE_OMIT_INCRBLOB
+static SQLITE_NOINLINE int accessPayloadChecked(
+ BtCursor *pCur,
+ u32 offset,
+ u32 amt,
+ void *pBuf
+){
+ int rc;
if ( pCur->eState==CURSOR_INVALID ){
return SQLITE_ABORT;
}
-#endif
-
assert( cursorOwnsBtShared(pCur) );
- rc = restoreCursorPosition(pCur);
- if( rc==SQLITE_OK ){
- assert( pCur->eState==CURSOR_VALID );
- assert( pCur->iPage>=0 && pCur->apPage[pCur->iPage] );
- assert( pCur->aiIdx[pCur->iPage]<pCur->apPage[pCur->iPage]->nCell );
- rc = accessPayload(pCur, offset, amt, pBuf, 0);
+ rc = btreeRestoreCursorPosition(pCur);
+ return rc ? rc : accessPayload(pCur, offset, amt, pBuf, 0);
+}
+SQLITE_PRIVATE int tdsqlite3BtreePayloadChecked(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
+ if( pCur->eState==CURSOR_VALID ){
+ assert( cursorOwnsBtShared(pCur) );
+ return accessPayload(pCur, offset, amt, pBuf, 0);
+ }else{
+ return accessPayloadChecked(pCur, offset, amt, pBuf);
}
- return rc;
}
+#endif /* SQLITE_OMIT_INCRBLOB */
/*
** Return a pointer to payload information from the entry that the
@@ -66037,18 +72726,23 @@ static const void *fetchPayload(
BtCursor *pCur, /* Cursor pointing to entry to read from */
u32 *pAmt /* Write the number of available bytes here */
){
- u32 amt;
- assert( pCur!=0 && pCur->iPage>=0 && pCur->apPage[pCur->iPage]);
+ int amt;
+ assert( pCur!=0 && pCur->iPage>=0 && pCur->pPage);
assert( pCur->eState==CURSOR_VALID );
- assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCur->pBtree->db->mutex) );
assert( cursorOwnsBtShared(pCur) );
- assert( pCur->aiIdx[pCur->iPage]<pCur->apPage[pCur->iPage]->nCell );
+ assert( pCur->ix<pCur->pPage->nCell );
assert( pCur->info.nSize>0 );
- assert( pCur->info.pPayload>pCur->apPage[pCur->iPage]->aData || CORRUPT_DB );
- assert( pCur->info.pPayload<pCur->apPage[pCur->iPage]->aDataEnd ||CORRUPT_DB);
- amt = (int)(pCur->apPage[pCur->iPage]->aDataEnd - pCur->info.pPayload);
- if( pCur->info.nLocal<amt ) amt = pCur->info.nLocal;
- *pAmt = amt;
+ assert( pCur->info.pPayload>pCur->pPage->aData || CORRUPT_DB );
+ assert( pCur->info.pPayload<pCur->pPage->aDataEnd ||CORRUPT_DB);
+ amt = pCur->info.nLocal;
+ if( amt>(int)(pCur->pPage->aDataEnd - pCur->info.pPayload) ){
+ /* There is too little space on the page for the expected amount
+ ** of local content. Database must be corrupt. */
+ assert( CORRUPT_DB );
+ amt = MAX(0, (int)(pCur->pPage->aDataEnd - pCur->info.pPayload));
+ }
+ *pAmt = (u32)amt;
return (void*)pCur->info.pPayload;
}
@@ -66067,7 +72761,7 @@ static const void *fetchPayload(
** These routines is used to get quick access to key and data
** in the common case where no overflow pages are used.
*/
-SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){
+SQLITE_PRIVATE const void *tdsqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){
return fetchPayload(pCur, pAmt);
}
@@ -66093,13 +72787,14 @@ static int moveToChild(BtCursor *pCur, u32 newPgno){
}
pCur->info.nSize = 0;
pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl);
+ pCur->aiIdx[pCur->iPage] = pCur->ix;
+ pCur->apPage[pCur->iPage] = pCur->pPage;
+ pCur->ix = 0;
pCur->iPage++;
- pCur->aiIdx[pCur->iPage] = 0;
- return getAndInitPage(pBt, newPgno, &pCur->apPage[pCur->iPage],
- pCur, pCur->curPagerFlags);
+ return getAndInitPage(pBt, newPgno, &pCur->pPage, pCur, pCur->curPagerFlags);
}
-#if SQLITE_DEBUG
+#ifdef SQLITE_DEBUG
/*
** Page pParent is an internal (non-leaf) tree page. This function
** asserts that page number iChild is the left-child if the iIdx'th
@@ -66130,19 +72825,23 @@ static void assertParentIndex(MemPage *pParent, int iIdx, Pgno iChild){
** the largest cell index.
*/
static void moveToParent(BtCursor *pCur){
+ MemPage *pLeaf;
assert( cursorOwnsBtShared(pCur) );
assert( pCur->eState==CURSOR_VALID );
assert( pCur->iPage>0 );
- assert( pCur->apPage[pCur->iPage] );
+ assert( pCur->pPage );
assertParentIndex(
pCur->apPage[pCur->iPage-1],
pCur->aiIdx[pCur->iPage-1],
- pCur->apPage[pCur->iPage]->pgno
+ pCur->pPage->pgno
);
testcase( pCur->aiIdx[pCur->iPage-1] > pCur->apPage[pCur->iPage-1]->nCell );
pCur->info.nSize = 0;
pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl);
- releasePageNotNull(pCur->apPage[pCur->iPage--]);
+ pCur->ix = pCur->aiIdx[pCur->iPage-1];
+ pLeaf = pCur->pPage;
+ pCur->pPage = pCur->apPage[--pCur->iPage];
+ releasePageNotNull(pLeaf);
}
/*
@@ -66154,9 +72853,9 @@ static void moveToParent(BtCursor *pCur){
** single child page. This can only happen with the table rooted at page 1.
**
** If the b-tree structure is empty, the cursor state is set to
-** CURSOR_INVALID. Otherwise, the cursor is set to point to the first
-** cell located on the root (or virtual root) page and the cursor state
-** is set to CURSOR_VALID.
+** CURSOR_INVALID and this routine returns SQLITE_EMPTY. Otherwise,
+** the cursor is set to point to the first cell located on the root
+** (or virtual root) page and the cursor state is set to CURSOR_VALID.
**
** If this function returns successfully, it may be assumed that the
** page-header flags indicate that the [virtual] root-page is the expected
@@ -66174,34 +72873,40 @@ static int moveToRoot(BtCursor *pCur){
assert( CURSOR_INVALID < CURSOR_REQUIRESEEK );
assert( CURSOR_VALID < CURSOR_REQUIRESEEK );
assert( CURSOR_FAULT > CURSOR_REQUIRESEEK );
- if( pCur->eState>=CURSOR_REQUIRESEEK ){
- if( pCur->eState==CURSOR_FAULT ){
- assert( pCur->skipNext!=SQLITE_OK );
- return pCur->skipNext;
- }
- sqlite3BtreeClearCursor(pCur);
- }
+ assert( pCur->eState < CURSOR_REQUIRESEEK || pCur->iPage<0 );
+ assert( pCur->pgnoRoot>0 || pCur->iPage<0 );
if( pCur->iPage>=0 ){
- while( pCur->iPage ){
- assert( pCur->apPage[pCur->iPage]!=0 );
- releasePageNotNull(pCur->apPage[pCur->iPage--]);
+ if( pCur->iPage ){
+ releasePageNotNull(pCur->pPage);
+ while( --pCur->iPage ){
+ releasePageNotNull(pCur->apPage[pCur->iPage]);
+ }
+ pCur->pPage = pCur->apPage[0];
+ goto skip_init;
}
}else if( pCur->pgnoRoot==0 ){
pCur->eState = CURSOR_INVALID;
- return SQLITE_OK;
+ return SQLITE_EMPTY;
}else{
assert( pCur->iPage==(-1) );
- rc = getAndInitPage(pCur->pBtree->pBt, pCur->pgnoRoot, &pCur->apPage[0],
+ if( pCur->eState>=CURSOR_REQUIRESEEK ){
+ if( pCur->eState==CURSOR_FAULT ){
+ assert( pCur->skipNext!=SQLITE_OK );
+ return pCur->skipNext;
+ }
+ tdsqlite3BtreeClearCursor(pCur);
+ }
+ rc = getAndInitPage(pCur->pBtree->pBt, pCur->pgnoRoot, &pCur->pPage,
0, pCur->curPagerFlags);
if( rc!=SQLITE_OK ){
pCur->eState = CURSOR_INVALID;
return rc;
}
pCur->iPage = 0;
- pCur->curIntKey = pCur->apPage[0]->intKey;
+ pCur->curIntKey = pCur->pPage->intKey;
}
- pRoot = pCur->apPage[0];
+ pRoot = pCur->pPage;
assert( pRoot->pgno==pCur->pgnoRoot );
/* If pCur->pKeyInfo is not NULL, then the caller that opened this cursor
@@ -66216,13 +72921,15 @@ static int moveToRoot(BtCursor *pCur){
** (or the freelist). */
assert( pRoot->intKey==1 || pRoot->intKey==0 );
if( pRoot->isInit==0 || (pCur->pKeyInfo==0)!=pRoot->intKey ){
- return SQLITE_CORRUPT_BKPT;
+ return SQLITE_CORRUPT_PAGE(pCur->pPage);
}
- pCur->aiIdx[0] = 0;
+skip_init:
+ pCur->ix = 0;
pCur->info.nSize = 0;
pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidNKey|BTCF_ValidOvfl);
+ pRoot = pCur->pPage;
if( pRoot->nCell>0 ){
pCur->eState = CURSOR_VALID;
}else if( !pRoot->leaf ){
@@ -66233,6 +72940,7 @@ static int moveToRoot(BtCursor *pCur){
rc = moveToChild(pCur, subpage);
}else{
pCur->eState = CURSOR_INVALID;
+ rc = SQLITE_EMPTY;
}
return rc;
}
@@ -66251,9 +72959,9 @@ static int moveToLeftmost(BtCursor *pCur){
assert( cursorOwnsBtShared(pCur) );
assert( pCur->eState==CURSOR_VALID );
- while( rc==SQLITE_OK && !(pPage = pCur->apPage[pCur->iPage])->leaf ){
- assert( pCur->aiIdx[pCur->iPage]<pPage->nCell );
- pgno = get4byte(findCell(pPage, pCur->aiIdx[pCur->iPage]));
+ while( rc==SQLITE_OK && !(pPage = pCur->pPage)->leaf ){
+ assert( pCur->ix<pPage->nCell );
+ pgno = get4byte(findCell(pPage, pCur->ix));
rc = moveToChild(pCur, pgno);
}
return rc;
@@ -66276,13 +72984,13 @@ static int moveToRightmost(BtCursor *pCur){
assert( cursorOwnsBtShared(pCur) );
assert( pCur->eState==CURSOR_VALID );
- while( !(pPage = pCur->apPage[pCur->iPage])->leaf ){
+ while( !(pPage = pCur->pPage)->leaf ){
pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]);
- pCur->aiIdx[pCur->iPage] = pPage->nCell;
+ pCur->ix = pPage->nCell;
rc = moveToChild(pCur, pgno);
if( rc ) return rc;
}
- pCur->aiIdx[pCur->iPage] = pPage->nCell-1;
+ pCur->ix = pPage->nCell-1;
assert( pCur->info.nSize==0 );
assert( (pCur->curFlags & BTCF_ValidNKey)==0 );
return SQLITE_OK;
@@ -66292,21 +73000,20 @@ static int moveToRightmost(BtCursor *pCur){
** on success. Set *pRes to 0 if the cursor actually points to something
** or set *pRes to 1 if the table is empty.
*/
-SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){
+SQLITE_PRIVATE int tdsqlite3BtreeFirst(BtCursor *pCur, int *pRes){
int rc;
assert( cursorOwnsBtShared(pCur) );
- assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCur->pBtree->db->mutex) );
rc = moveToRoot(pCur);
if( rc==SQLITE_OK ){
- if( pCur->eState==CURSOR_INVALID ){
- assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage]->nCell==0 );
- *pRes = 1;
- }else{
- assert( pCur->apPage[pCur->iPage]->nCell>0 );
- *pRes = 0;
- rc = moveToLeftmost(pCur);
- }
+ assert( pCur->pPage->nCell>0 );
+ *pRes = 0;
+ rc = moveToLeftmost(pCur);
+ }else if( rc==SQLITE_EMPTY ){
+ assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 );
+ *pRes = 1;
+ rc = SQLITE_OK;
}
return rc;
}
@@ -66315,11 +73022,11 @@ SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){
** on success. Set *pRes to 0 if the cursor actually points to something
** or set *pRes to 1 if the table is empty.
*/
-SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){
+SQLITE_PRIVATE int tdsqlite3BtreeLast(BtCursor *pCur, int *pRes){
int rc;
assert( cursorOwnsBtShared(pCur) );
- assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCur->pBtree->db->mutex) );
/* If the cursor already points to the last entry, this is a no-op. */
if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){
@@ -66330,28 +73037,27 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){
for(ii=0; ii<pCur->iPage; ii++){
assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell );
}
- assert( pCur->aiIdx[pCur->iPage]==pCur->apPage[pCur->iPage]->nCell-1 );
- assert( pCur->apPage[pCur->iPage]->leaf );
+ assert( pCur->ix==pCur->pPage->nCell-1 );
+ assert( pCur->pPage->leaf );
#endif
+ *pRes = 0;
return SQLITE_OK;
}
rc = moveToRoot(pCur);
if( rc==SQLITE_OK ){
- if( CURSOR_INVALID==pCur->eState ){
- assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage]->nCell==0 );
- *pRes = 1;
+ assert( pCur->eState==CURSOR_VALID );
+ *pRes = 0;
+ rc = moveToRightmost(pCur);
+ if( rc==SQLITE_OK ){
+ pCur->curFlags |= BTCF_AtLast;
}else{
- assert( pCur->eState==CURSOR_VALID );
- *pRes = 0;
- rc = moveToRightmost(pCur);
- if( rc==SQLITE_OK ){
- pCur->curFlags |= BTCF_AtLast;
- }else{
- pCur->curFlags &= ~BTCF_AtLast;
- }
-
+ pCur->curFlags &= ~BTCF_AtLast;
}
+ }else if( rc==SQLITE_EMPTY ){
+ assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 );
+ *pRes = 1;
+ rc = SQLITE_OK;
}
return rc;
}
@@ -66386,7 +73092,7 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){
** For index tables, the pIdxKey->eqSeen field is set to 1 if there
** exists an entry in the table that exactly matches pIdxKey.
*/
-SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
+SQLITE_PRIVATE int tdsqlite3BtreeMovetoUnpacked(
BtCursor *pCur, /* The cursor to be moved */
UnpackedRecord *pIdxKey, /* Unpacked index key */
i64 intKey, /* The table key */
@@ -66397,7 +73103,7 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
RecordCompare xRecordCompare;
assert( cursorOwnsBtShared(pCur) );
- assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCur->pBtree->db->mutex) );
assert( pRes );
assert( (pIdxKey==0)==(pCur->pKeyInfo==0) );
assert( pCur->eState!=CURSOR_VALID || (pIdxKey==0)==(pCur->curIntKey!=0) );
@@ -66411,14 +73117,34 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
*pRes = 0;
return SQLITE_OK;
}
- if( (pCur->curFlags & BTCF_AtLast)!=0 && pCur->info.nKey<intKey ){
- *pRes = -1;
- return SQLITE_OK;
+ if( pCur->info.nKey<intKey ){
+ if( (pCur->curFlags & BTCF_AtLast)!=0 ){
+ *pRes = -1;
+ return SQLITE_OK;
+ }
+ /* If the requested key is one more than the previous key, then
+ ** try to get there using tdsqlite3BtreeNext() rather than a full
+ ** binary search. This is an optimization only. The correct answer
+ ** is still obtained without this case, only a little more slowely */
+ if( pCur->info.nKey+1==intKey ){
+ *pRes = 0;
+ rc = tdsqlite3BtreeNext(pCur, 0);
+ if( rc==SQLITE_OK ){
+ getCellInfo(pCur);
+ if( pCur->info.nKey==intKey ){
+ return SQLITE_OK;
+ }
+ }else if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ }else{
+ return rc;
+ }
+ }
}
}
if( pIdxKey ){
- xRecordCompare = sqlite3VdbeFindCompare(pIdxKey);
+ xRecordCompare = tdsqlite3VdbeFindCompare(pIdxKey);
pIdxKey->errCode = 0;
assert( pIdxKey->default_rc==1
|| pIdxKey->default_rc==0
@@ -66430,22 +73156,23 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
rc = moveToRoot(pCur);
if( rc ){
+ if( rc==SQLITE_EMPTY ){
+ assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 );
+ *pRes = -1;
+ return SQLITE_OK;
+ }
return rc;
}
- assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage] );
- assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage]->isInit );
- assert( pCur->eState==CURSOR_INVALID || pCur->apPage[pCur->iPage]->nCell>0 );
- if( pCur->eState==CURSOR_INVALID ){
- *pRes = -1;
- assert( pCur->pgnoRoot==0 || pCur->apPage[pCur->iPage]->nCell==0 );
- return SQLITE_OK;
- }
- assert( pCur->apPage[0]->intKey==pCur->curIntKey );
+ assert( pCur->pPage );
+ assert( pCur->pPage->isInit );
+ assert( pCur->eState==CURSOR_VALID );
+ assert( pCur->pPage->nCell > 0 );
+ assert( pCur->iPage==0 || pCur->apPage[0]->intKey==pCur->curIntKey );
assert( pCur->curIntKey || pIdxKey );
for(;;){
int lwr, upr, idx, c;
Pgno chldPg;
- MemPage *pPage = pCur->apPage[pCur->iPage];
+ MemPage *pPage = pCur->pPage;
u8 *pCell; /* Pointer to current cell in pPage */
/* pPage->nCell must be greater than zero. If this is the root-page
@@ -66460,14 +73187,16 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
upr = pPage->nCell-1;
assert( biasRight==0 || biasRight==1 );
idx = upr>>(1-biasRight); /* idx = biasRight ? upr : (lwr+upr)/2; */
- pCur->aiIdx[pCur->iPage] = (u16)idx;
+ pCur->ix = (u16)idx;
if( xRecordCompare==0 ){
for(;;){
i64 nCellKey;
pCell = findCellPastPtr(pPage, idx);
if( pPage->intKeyLeaf ){
while( 0x80 <= *(pCell++) ){
- if( pCell>=pPage->aDataEnd ) return SQLITE_CORRUPT_BKPT;
+ if( pCell>=pPage->aDataEnd ){
+ return SQLITE_CORRUPT_PAGE(pPage);
+ }
}
}
getVarint(pCell, (u64*)&nCellKey);
@@ -66479,16 +73208,16 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
if( lwr>upr ){ c = +1; break; }
}else{
assert( nCellKey==intKey );
- pCur->curFlags |= BTCF_ValidNKey;
- pCur->info.nKey = nCellKey;
- pCur->aiIdx[pCur->iPage] = (u16)idx;
+ pCur->ix = (u16)idx;
if( !pPage->leaf ){
lwr = idx;
goto moveto_next_layer;
}else{
+ pCur->curFlags |= BTCF_ValidNKey;
+ pCur->info.nKey = nCellKey;
+ pCur->info.nSize = 0;
*pRes = 0;
- rc = SQLITE_OK;
- goto moveto_finish;
+ return SQLITE_OK;
}
}
assert( lwr+upr>=0 );
@@ -66533,29 +73262,32 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
** case this happens. */
void *pCellKey;
u8 * const pCellBody = pCell - pPage->childPtrSize;
+ const int nOverrun = 18; /* Size of the overrun padding */
pPage->xParseCell(pPage, pCellBody, &pCur->info);
nCell = (int)pCur->info.nKey;
testcase( nCell<0 ); /* True if key size is 2^32 or more */
testcase( nCell==0 ); /* Invalid key size: 0x80 0x80 0x00 */
testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */
testcase( nCell==2 ); /* Minimum legal index key size */
- if( nCell<2 ){
- rc = SQLITE_CORRUPT_BKPT;
+ if( nCell<2 || nCell/pCur->pBt->usableSize>pCur->pBt->nPage ){
+ rc = SQLITE_CORRUPT_PAGE(pPage);
goto moveto_finish;
}
- pCellKey = sqlite3Malloc( nCell+18 );
+ pCellKey = tdsqlite3Malloc( nCell+nOverrun );
if( pCellKey==0 ){
rc = SQLITE_NOMEM_BKPT;
goto moveto_finish;
}
- pCur->aiIdx[pCur->iPage] = (u16)idx;
- rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 2);
+ pCur->ix = (u16)idx;
+ rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0);
+ memset(((u8*)pCellKey)+nCell,0,nOverrun); /* Fix uninit warnings */
+ pCur->curFlags &= ~BTCF_ValidOvfl;
if( rc ){
- sqlite3_free(pCellKey);
+ tdsqlite3_free(pCellKey);
goto moveto_finish;
}
- c = xRecordCompare(nCell, pCellKey, pIdxKey);
- sqlite3_free(pCellKey);
+ c = tdsqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey);
+ tdsqlite3_free(pCellKey);
}
assert(
(pIdxKey->errCode!=SQLITE_CORRUPT || c==0)
@@ -66569,8 +73301,8 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
assert( c==0 );
*pRes = 0;
rc = SQLITE_OK;
- pCur->aiIdx[pCur->iPage] = (u16)idx;
- if( pIdxKey->errCode ) rc = SQLITE_CORRUPT;
+ pCur->ix = (u16)idx;
+ if( pIdxKey->errCode ) rc = SQLITE_CORRUPT_BKPT;
goto moveto_finish;
}
if( lwr>upr ) break;
@@ -66581,8 +73313,8 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked(
assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) );
assert( pPage->isInit );
if( pPage->leaf ){
- assert( pCur->aiIdx[pCur->iPage]<pCur->apPage[pCur->iPage]->nCell );
- pCur->aiIdx[pCur->iPage] = (u16)idx;
+ assert( pCur->ix<pCur->pPage->nCell );
+ pCur->ix = (u16)idx;
*pRes = c;
rc = SQLITE_OK;
goto moveto_finish;
@@ -66593,13 +73325,13 @@ moveto_next_layer:
}else{
chldPg = get4byte(findCell(pPage, lwr));
}
- pCur->aiIdx[pCur->iPage] = (u16)lwr;
+ pCur->ix = (u16)lwr;
rc = moveToChild(pCur, chldPg);
if( rc ) break;
}
moveto_finish:
pCur->info.nSize = 0;
- pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl);
+ assert( (pCur->curFlags & BTCF_ValidOvfl)==0 );
return rc;
}
@@ -66607,11 +73339,11 @@ moveto_finish:
/*
** Return TRUE if the cursor is not pointing at an entry of the table.
**
-** TRUE will be returned after a call to sqlite3BtreeNext() moves
-** past the last entry in the table or sqlite3BtreePrev() moves past
+** TRUE will be returned after a call to tdsqlite3BtreeNext() moves
+** past the last entry in the table or tdsqlite3BtreePrev() moves past
** the first entry. TRUE is also returned if the table is empty.
*/
-SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor *pCur){
+SQLITE_PRIVATE int tdsqlite3BtreeEof(BtCursor *pCur){
/* TODO: What if the cursor is in CURSOR_REQUIRESEEK but all table entries
** have been deleted? This API will need to change to return an error code
** as well as the boolean result value.
@@ -66620,34 +73352,56 @@ SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor *pCur){
}
/*
-** Advance the cursor to the next entry in the database. If
-** successful then set *pRes=0. If the cursor
-** was already pointing to the last entry in the database before
-** this routine was called, then set *pRes=1.
+** Return an estimate for the number of rows in the table that pCur is
+** pointing to. Return a negative number if no estimate is currently
+** available.
+*/
+SQLITE_PRIVATE i64 tdsqlite3BtreeRowCountEst(BtCursor *pCur){
+ i64 n;
+ u8 i;
+
+ assert( cursorOwnsBtShared(pCur) );
+ assert( tdsqlite3_mutex_held(pCur->pBtree->db->mutex) );
+
+ /* Currently this interface is only called by the OP_IfSmaller
+ ** opcode, and it that case the cursor will always be valid and
+ ** will always point to a leaf node. */
+ if( NEVER(pCur->eState!=CURSOR_VALID) ) return -1;
+ if( NEVER(pCur->pPage->leaf==0) ) return -1;
+
+ n = pCur->pPage->nCell;
+ for(i=0; i<pCur->iPage; i++){
+ n *= pCur->apPage[i]->nCell;
+ }
+ return n;
+}
+
+/*
+** Advance the cursor to the next entry in the database.
+** Return value:
**
-** The main entry point is sqlite3BtreeNext(). That routine is optimized
+** SQLITE_OK success
+** SQLITE_DONE cursor is already pointing at the last element
+** otherwise some kind of error occurred
+**
+** The main entry point is tdsqlite3BtreeNext(). That routine is optimized
** for the common case of merely incrementing the cell counter BtCursor.aiIdx
** to the next cell on the current page. The (slower) btreeNext() helper
** routine is called when it is necessary to move to a different page or
** to restore the cursor.
**
-** The calling function will set *pRes to 0 or 1. The initial *pRes value
-** will be 1 if the cursor being stepped corresponds to an SQL index and
-** if this routine could have been skipped if that SQL index had been
-** a unique index. Otherwise the caller will have set *pRes to zero.
-** Zero is the common case. The btree implementation is free to use the
-** initial *pRes value as a hint to improve performance, but the current
-** SQLite btree implementation does not. (Note that the comdb2 btree
-** implementation does use this hint, however.)
+** If bit 0x01 of the F argument in tdsqlite3BtreeNext(C,F) is 1, then the
+** cursor corresponds to an SQL index and this routine could have been
+** skipped if the SQL index had been a unique index. The F argument
+** is a hint to the implement. SQLite btree implementation does not use
+** this hint, but COMDB2 does.
*/
-static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){
+static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){
int rc;
int idx;
MemPage *pPage;
assert( cursorOwnsBtShared(pCur) );
- assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID );
- assert( *pRes==0 );
if( pCur->eState!=CURSOR_VALID ){
assert( (pCur->curFlags & BTCF_ValidOvfl)==0 );
rc = restoreCursorPosition(pCur);
@@ -66655,30 +73409,36 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){
return rc;
}
if( CURSOR_INVALID==pCur->eState ){
- *pRes = 1;
- return SQLITE_OK;
+ return SQLITE_DONE;
}
- if( pCur->skipNext ){
- assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_SKIPNEXT );
+ if( pCur->eState==CURSOR_SKIPNEXT ){
pCur->eState = CURSOR_VALID;
- if( pCur->skipNext>0 ){
- pCur->skipNext = 0;
- return SQLITE_OK;
- }
- pCur->skipNext = 0;
+ if( pCur->skipNext>0 ) return SQLITE_OK;
}
}
- pPage = pCur->apPage[pCur->iPage];
- idx = ++pCur->aiIdx[pCur->iPage];
- assert( pPage->isInit );
+ pPage = pCur->pPage;
+ idx = ++pCur->ix;
+ if( !pPage->isInit ){
+ /* The only known way for this to happen is for there to be a
+ ** recursive SQL function that does a DELETE operation as part of a
+ ** SELECT which deletes content out from under an active cursor
+ ** in a corrupt database file where the table being DELETE-ed from
+ ** has pages in common with the table being queried. See TH3
+ ** module cov1/btree78.test testcase 220 (2018-06-08) for an
+ ** example. */
+ return SQLITE_CORRUPT_BKPT;
+ }
/* If the database file is corrupt, it is possible for the value of idx
** to be invalid here. This can only occur if a second cursor modifies
** the page while cursor pCur is holding a reference to it. Which can
** only happen if the database is corrupt in such a way as to link the
- ** page into more than one b-tree structure. */
- testcase( idx>pPage->nCell );
+ ** page into more than one b-tree structure.
+ **
+ ** Update 2019-12-23: appears to long longer be possible after the
+ ** addition of anotherValidCursor() condition on balance_deeper(). */
+ harmless( idx>pPage->nCell );
if( idx>=pPage->nCell ){
if( !pPage->leaf ){
@@ -66688,15 +73448,14 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){
}
do{
if( pCur->iPage==0 ){
- *pRes = 1;
pCur->eState = CURSOR_INVALID;
- return SQLITE_OK;
+ return SQLITE_DONE;
}
moveToParent(pCur);
- pPage = pCur->apPage[pCur->iPage];
- }while( pCur->aiIdx[pCur->iPage]>=pPage->nCell );
+ pPage = pCur->pPage;
+ }while( pCur->ix>=pPage->nCell );
if( pPage->intKey ){
- return sqlite3BtreeNext(pCur, pRes);
+ return tdsqlite3BtreeNext(pCur, 0);
}else{
return SQLITE_OK;
}
@@ -66707,20 +73466,18 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur, int *pRes){
return moveToLeftmost(pCur);
}
}
-SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){
+SQLITE_PRIVATE int tdsqlite3BtreeNext(BtCursor *pCur, int flags){
MemPage *pPage;
+ UNUSED_PARAMETER( flags ); /* Used in COMDB2 but not native SQLite */
assert( cursorOwnsBtShared(pCur) );
- assert( pRes!=0 );
- assert( *pRes==0 || *pRes==1 );
- assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID );
+ assert( flags==0 || flags==1 );
pCur->info.nSize = 0;
pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl);
- *pRes = 0;
- if( pCur->eState!=CURSOR_VALID ) return btreeNext(pCur, pRes);
- pPage = pCur->apPage[pCur->iPage];
- if( (++pCur->aiIdx[pCur->iPage])>=pPage->nCell ){
- pCur->aiIdx[pCur->iPage]--;
- return btreeNext(pCur, pRes);
+ if( pCur->eState!=CURSOR_VALID ) return btreeNext(pCur);
+ pPage = pCur->pPage;
+ if( (++pCur->ix)>=pPage->nCell ){
+ pCur->ix--;
+ return btreeNext(pCur);
}
if( pPage->leaf ){
return SQLITE_OK;
@@ -66730,34 +73487,30 @@ SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){
}
/*
-** Step the cursor to the back to the previous entry in the database. If
-** successful then set *pRes=0. If the cursor
-** was already pointing to the first entry in the database before
-** this routine was called, then set *pRes=1.
+** Step the cursor to the back to the previous entry in the database.
+** Return values:
**
-** The main entry point is sqlite3BtreePrevious(). That routine is optimized
+** SQLITE_OK success
+** SQLITE_DONE the cursor is already on the first element of the table
+** otherwise some kind of error occurred
+**
+** The main entry point is tdsqlite3BtreePrevious(). That routine is optimized
** for the common case of merely decrementing the cell counter BtCursor.aiIdx
** to the previous cell on the current page. The (slower) btreePrevious()
** helper routine is called when it is necessary to move to a different page
** or to restore the cursor.
**
-** The calling function will set *pRes to 0 or 1. The initial *pRes value
-** will be 1 if the cursor being stepped corresponds to an SQL index and
-** if this routine could have been skipped if that SQL index had been
-** a unique index. Otherwise the caller will have set *pRes to zero.
-** Zero is the common case. The btree implementation is free to use the
-** initial *pRes value as a hint to improve performance, but the current
-** SQLite btree implementation does not. (Note that the comdb2 btree
-** implementation does use this hint, however.)
+** If bit 0x01 of the F argument to tdsqlite3BtreePrevious(C,F) is 1, then
+** the cursor corresponds to an SQL index and this routine could have been
+** skipped if the SQL index had been a unique index. The F argument is a
+** hint to the implement. The native SQLite btree implementation does not
+** use this hint, but COMDB2 does.
*/
-static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){
+static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur){
int rc;
MemPage *pPage;
assert( cursorOwnsBtShared(pCur) );
- assert( pRes!=0 );
- assert( *pRes==0 );
- assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID );
assert( (pCur->curFlags & (BTCF_AtLast|BTCF_ValidOvfl|BTCF_ValidNKey))==0 );
assert( pCur->info.nSize==0 );
if( pCur->eState!=CURSOR_VALID ){
@@ -66766,74 +73519,65 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){
return rc;
}
if( CURSOR_INVALID==pCur->eState ){
- *pRes = 1;
- return SQLITE_OK;
+ return SQLITE_DONE;
}
- if( pCur->skipNext ){
- assert( pCur->eState==CURSOR_VALID || pCur->eState==CURSOR_SKIPNEXT );
+ if( CURSOR_SKIPNEXT==pCur->eState ){
pCur->eState = CURSOR_VALID;
- if( pCur->skipNext<0 ){
- pCur->skipNext = 0;
- return SQLITE_OK;
- }
- pCur->skipNext = 0;
+ if( pCur->skipNext<0 ) return SQLITE_OK;
}
}
- pPage = pCur->apPage[pCur->iPage];
+ pPage = pCur->pPage;
assert( pPage->isInit );
if( !pPage->leaf ){
- int idx = pCur->aiIdx[pCur->iPage];
+ int idx = pCur->ix;
rc = moveToChild(pCur, get4byte(findCell(pPage, idx)));
if( rc ) return rc;
rc = moveToRightmost(pCur);
}else{
- while( pCur->aiIdx[pCur->iPage]==0 ){
+ while( pCur->ix==0 ){
if( pCur->iPage==0 ){
pCur->eState = CURSOR_INVALID;
- *pRes = 1;
- return SQLITE_OK;
+ return SQLITE_DONE;
}
moveToParent(pCur);
}
assert( pCur->info.nSize==0 );
- assert( (pCur->curFlags & (BTCF_ValidNKey|BTCF_ValidOvfl))==0 );
+ assert( (pCur->curFlags & (BTCF_ValidOvfl))==0 );
- pCur->aiIdx[pCur->iPage]--;
- pPage = pCur->apPage[pCur->iPage];
+ pCur->ix--;
+ pPage = pCur->pPage;
if( pPage->intKey && !pPage->leaf ){
- rc = sqlite3BtreePrevious(pCur, pRes);
+ rc = tdsqlite3BtreePrevious(pCur, 0);
}else{
rc = SQLITE_OK;
}
}
return rc;
}
-SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){
+SQLITE_PRIVATE int tdsqlite3BtreePrevious(BtCursor *pCur, int flags){
assert( cursorOwnsBtShared(pCur) );
- assert( pRes!=0 );
- assert( *pRes==0 || *pRes==1 );
- assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID );
- *pRes = 0;
+ assert( flags==0 || flags==1 );
+ UNUSED_PARAMETER( flags ); /* Used in COMDB2 but not native SQLite */
pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidOvfl|BTCF_ValidNKey);
pCur->info.nSize = 0;
if( pCur->eState!=CURSOR_VALID
- || pCur->aiIdx[pCur->iPage]==0
- || pCur->apPage[pCur->iPage]->leaf==0
+ || pCur->ix==0
+ || pCur->pPage->leaf==0
){
- return btreePrevious(pCur, pRes);
+ return btreePrevious(pCur);
}
- pCur->aiIdx[pCur->iPage]--;
+ pCur->ix--;
return SQLITE_OK;
}
/*
** Allocate a new page from the database file.
**
-** The new page is marked as dirty. (In other words, sqlite3PagerWrite()
+** The new page is marked as dirty. (In other words, tdsqlite3PagerWrite()
** has already been called on the new page.) The new page has also
** been referenced and the calling routine is responsible for calling
-** sqlite3PagerUnref() on the new page when it is done.
+** tdsqlite3PagerUnref() on the new page when it is done.
**
** SQLITE_OK is returned on success. Any other return value indicates
** an error. *ppPage is set to NULL in the event of an error.
@@ -66864,7 +73608,7 @@ static int allocateBtreePage(
MemPage *pPrevTrunk = 0;
Pgno mxPage; /* Total size of the database file */
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) );
pPage1 = pBt->pPage1;
mxPage = btreePagecount(pBt);
@@ -66905,7 +73649,7 @@ static int allocateBtreePage(
/* Decrement the free-list count by 1. Set iTrunk to the index of the
** first free-list trunk page. iPrevTrunk is initially 1.
*/
- rc = sqlite3PagerWrite(pPage1->pDbPage);
+ rc = tdsqlite3PagerWrite(pPage1->pDbPage);
if( rc ) return rc;
put4byte(&pPage1->aData[36], n-1);
@@ -66929,7 +73673,7 @@ static int allocateBtreePage(
}
testcase( iTrunk==mxPage );
if( iTrunk>mxPage || nSearch++ > n ){
- rc = SQLITE_CORRUPT_BKPT;
+ rc = SQLITE_CORRUPT_PGNO(pPrevTrunk ? pPrevTrunk->pgno : 1);
}else{
rc = btreeGetUnusedPage(pBt, iTrunk, &pTrunk, 0);
}
@@ -66947,7 +73691,7 @@ static int allocateBtreePage(
** So extract the trunk page itself and use it as the newly
** allocated page */
assert( pPrevTrunk==0 );
- rc = sqlite3PagerWrite(pTrunk->pDbPage);
+ rc = tdsqlite3PagerWrite(pTrunk->pDbPage);
if( rc ){
goto end_allocate_page;
}
@@ -66958,7 +73702,7 @@ static int allocateBtreePage(
TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1));
}else if( k>(u32)(pBt->usableSize/4 - 2) ){
/* Value of k is out of range. Database corruption */
- rc = SQLITE_CORRUPT_BKPT;
+ rc = SQLITE_CORRUPT_PGNO(iTrunk);
goto end_allocate_page;
#ifndef SQLITE_OMIT_AUTOVACUUM
}else if( searchList
@@ -66970,7 +73714,7 @@ static int allocateBtreePage(
*pPgno = iTrunk;
*ppPage = pTrunk;
searchList = 0;
- rc = sqlite3PagerWrite(pTrunk->pDbPage);
+ rc = tdsqlite3PagerWrite(pTrunk->pDbPage);
if( rc ){
goto end_allocate_page;
}
@@ -66978,7 +73722,7 @@ static int allocateBtreePage(
if( !pPrevTrunk ){
memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4);
}else{
- rc = sqlite3PagerWrite(pPrevTrunk->pDbPage);
+ rc = tdsqlite3PagerWrite(pPrevTrunk->pDbPage);
if( rc!=SQLITE_OK ){
goto end_allocate_page;
}
@@ -66992,7 +73736,7 @@ static int allocateBtreePage(
MemPage *pNewTrunk;
Pgno iNewTrunk = get4byte(&pTrunk->aData[8]);
if( iNewTrunk>mxPage ){
- rc = SQLITE_CORRUPT_BKPT;
+ rc = SQLITE_CORRUPT_PGNO(iTrunk);
goto end_allocate_page;
}
testcase( iNewTrunk==mxPage );
@@ -67000,7 +73744,7 @@ static int allocateBtreePage(
if( rc!=SQLITE_OK ){
goto end_allocate_page;
}
- rc = sqlite3PagerWrite(pNewTrunk->pDbPage);
+ rc = tdsqlite3PagerWrite(pNewTrunk->pDbPage);
if( rc!=SQLITE_OK ){
releasePage(pNewTrunk);
goto end_allocate_page;
@@ -67010,10 +73754,10 @@ static int allocateBtreePage(
memcpy(&pNewTrunk->aData[8], &pTrunk->aData[12], (k-1)*4);
releasePage(pNewTrunk);
if( !pPrevTrunk ){
- assert( sqlite3PagerIswriteable(pPage1->pDbPage) );
+ assert( tdsqlite3PagerIswriteable(pPage1->pDbPage) );
put4byte(&pPage1->aData[32], iNewTrunk);
}else{
- rc = sqlite3PagerWrite(pPrevTrunk->pDbPage);
+ rc = tdsqlite3PagerWrite(pPrevTrunk->pDbPage);
if( rc ){
goto end_allocate_page;
}
@@ -67041,9 +73785,9 @@ static int allocateBtreePage(
}
}else{
int dist;
- dist = sqlite3AbsInt32(get4byte(&aData[8]) - nearby);
+ dist = tdsqlite3AbsInt32(get4byte(&aData[8]) - nearby);
for(i=1; i<k; i++){
- int d2 = sqlite3AbsInt32(get4byte(&aData[8+i*4]) - nearby);
+ int d2 = tdsqlite3AbsInt32(get4byte(&aData[8+i*4]) - nearby);
if( d2<dist ){
closest = i;
dist = d2;
@@ -67057,7 +73801,7 @@ static int allocateBtreePage(
iPage = get4byte(&aData[8+closest*4]);
testcase( iPage==mxPage );
if( iPage>mxPage ){
- rc = SQLITE_CORRUPT_BKPT;
+ rc = SQLITE_CORRUPT_PGNO(iTrunk);
goto end_allocate_page;
}
testcase( iPage==mxPage );
@@ -67069,7 +73813,7 @@ static int allocateBtreePage(
TRACE(("ALLOCATE: %d was leaf %d of %d on trunk %d"
": %d more free pages\n",
*pPgno, closest+1, k, pTrunk->pgno, n-1));
- rc = sqlite3PagerWrite(pTrunk->pDbPage);
+ rc = tdsqlite3PagerWrite(pTrunk->pDbPage);
if( rc ) goto end_allocate_page;
if( closest<k-1 ){
memcpy(&aData[8+closest*4], &aData[4+k*4], 4);
@@ -67078,7 +73822,7 @@ static int allocateBtreePage(
noContent = !btreeGetHasContent(pBt, *pPgno)? PAGER_GET_NOCONTENT : 0;
rc = btreeGetUnusedPage(pBt, *pPgno, ppPage, noContent);
if( rc==SQLITE_OK ){
- rc = sqlite3PagerWrite((*ppPage)->pDbPage);
+ rc = tdsqlite3PagerWrite((*ppPage)->pDbPage);
if( rc!=SQLITE_OK ){
releasePage(*ppPage);
*ppPage = 0;
@@ -67111,7 +73855,7 @@ static int allocateBtreePage(
*/
int bNoContent = (0==IfNotOmitAV(pBt->bDoTruncate))? PAGER_GET_NOCONTENT:0;
- rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
+ rc = tdsqlite3PagerWrite(pBt->pPage1->pDbPage);
if( rc ) return rc;
pBt->nPage++;
if( pBt->nPage==PENDING_BYTE_PAGE(pBt) ) pBt->nPage++;
@@ -67127,7 +73871,7 @@ static int allocateBtreePage(
assert( pBt->nPage!=PENDING_BYTE_PAGE(pBt) );
rc = btreeGetUnusedPage(pBt, pBt->nPage, &pPg, bNoContent);
if( rc==SQLITE_OK ){
- rc = sqlite3PagerWrite(pPg->pDbPage);
+ rc = tdsqlite3PagerWrite(pPg->pDbPage);
releasePage(pPg);
}
if( rc ) return rc;
@@ -67141,7 +73885,7 @@ static int allocateBtreePage(
assert( *pPgno!=PENDING_BYTE_PAGE(pBt) );
rc = btreeGetUnusedPage(pBt, *pPgno, ppPage, bNoContent);
if( rc ) return rc;
- rc = sqlite3PagerWrite((*ppPage)->pDbPage);
+ rc = tdsqlite3PagerWrite((*ppPage)->pDbPage);
if( rc!=SQLITE_OK ){
releasePage(*ppPage);
*ppPage = 0;
@@ -67149,12 +73893,12 @@ static int allocateBtreePage(
TRACE(("ALLOCATE: %d from end of file\n", *pPgno));
}
- assert( *pPgno!=PENDING_BYTE_PAGE(pBt) );
+ assert( CORRUPT_DB || *pPgno!=PENDING_BYTE_PAGE(pBt) );
end_allocate_page:
releasePage(pTrunk);
releasePage(pPrevTrunk);
- assert( rc!=SQLITE_OK || sqlite3PagerPageRefcount((*ppPage)->pDbPage)<=1 );
+ assert( rc!=SQLITE_OK || tdsqlite3PagerPageRefcount((*ppPage)->pDbPage)<=1 );
assert( rc!=SQLITE_OK || (*ppPage)->isInit==0 );
return rc;
}
@@ -67177,22 +73921,24 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
MemPage *pPage1 = pBt->pPage1; /* Local reference to page 1 */
MemPage *pPage; /* Page being freed. May be NULL. */
int rc; /* Return Code */
- int nFree; /* Initial number of pages on free-list */
+ u32 nFree; /* Initial number of pages on free-list */
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
assert( CORRUPT_DB || iPage>1 );
assert( !pMemPage || pMemPage->pgno==iPage );
- if( iPage<2 ) return SQLITE_CORRUPT_BKPT;
+ if( iPage<2 || iPage>pBt->nPage ){
+ return SQLITE_CORRUPT_BKPT;
+ }
if( pMemPage ){
pPage = pMemPage;
- sqlite3PagerRef(pPage->pDbPage);
+ tdsqlite3PagerRef(pPage->pDbPage);
}else{
pPage = btreePageLookup(pBt, iPage);
}
/* Increment the free page count on pPage1 */
- rc = sqlite3PagerWrite(pPage1->pDbPage);
+ rc = tdsqlite3PagerWrite(pPage1->pDbPage);
if( rc ) goto freepage_out;
nFree = get4byte(&pPage1->aData[36]);
put4byte(&pPage1->aData[36], nFree+1);
@@ -67202,7 +73948,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
** always fully overwrite deleted information with zeros.
*/
if( (!pPage && ((rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0) )
- || ((rc = sqlite3PagerWrite(pPage->pDbPage))!=0)
+ || ((rc = tdsqlite3PagerWrite(pPage->pDbPage))!=0)
){
goto freepage_out;
}
@@ -67259,12 +74005,12 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
** order that database files created by newer versions of SQLite can be
** read by older versions of SQLite.
*/
- rc = sqlite3PagerWrite(pTrunk->pDbPage);
+ rc = tdsqlite3PagerWrite(pTrunk->pDbPage);
if( rc==SQLITE_OK ){
put4byte(&pTrunk->aData[4], nLeaf+1);
put4byte(&pTrunk->aData[8+nLeaf*4], iPage);
if( pPage && (pBt->btsFlags & BTS_SECURE_DELETE)==0 ){
- sqlite3PagerDontWrite(pPage->pDbPage);
+ tdsqlite3PagerDontWrite(pPage->pDbPage);
}
rc = btreeSetHasContent(pBt, iPage);
}
@@ -67282,7 +74028,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
if( pPage==0 && SQLITE_OK!=(rc = btreeGetPage(pBt, iPage, &pPage, 0)) ){
goto freepage_out;
}
- rc = sqlite3PagerWrite(pPage->pDbPage);
+ rc = tdsqlite3PagerWrite(pPage->pDbPage);
if( rc!=SQLITE_OK ){
goto freepage_out;
}
@@ -67306,37 +74052,38 @@ static void freePage(MemPage *pPage, int *pRC){
}
/*
-** Free any overflow pages associated with the given Cell. Write the
-** local Cell size (the number of bytes on the original page, omitting
-** overflow) into *pnSize.
+** Free any overflow pages associated with the given Cell. Store
+** size information about the cell in pInfo.
*/
static int clearCell(
MemPage *pPage, /* The page that contains the Cell */
unsigned char *pCell, /* First byte of the Cell */
- u16 *pnSize /* Write the size of the Cell here */
+ CellInfo *pInfo /* Size information about the cell */
){
- BtShared *pBt = pPage->pBt;
- CellInfo info;
+ BtShared *pBt;
Pgno ovflPgno;
int rc;
int nOvfl;
u32 ovflPageSize;
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- pPage->xParseCell(pPage, pCell, &info);
- *pnSize = info.nSize;
- if( info.nLocal==info.nPayload ){
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ pPage->xParseCell(pPage, pCell, pInfo);
+ if( pInfo->nLocal==pInfo->nPayload ){
return SQLITE_OK; /* No overflow pages. Return without doing anything */
}
- if( pCell+info.nSize-1 > pPage->aData+pPage->maskPage ){
- return SQLITE_CORRUPT_BKPT; /* Cell extends past end of page */
+ testcase( pCell + pInfo->nSize == pPage->aDataEnd );
+ testcase( pCell + (pInfo->nSize-1) == pPage->aDataEnd );
+ if( pCell + pInfo->nSize > pPage->aDataEnd ){
+ /* Cell extends past end of page */
+ return SQLITE_CORRUPT_PAGE(pPage);
}
- ovflPgno = get4byte(pCell + info.nSize - 4);
+ ovflPgno = get4byte(pCell + pInfo->nSize - 4);
+ pBt = pPage->pBt;
assert( pBt->usableSize > 4 );
ovflPageSize = pBt->usableSize - 4;
- nOvfl = (info.nPayload - info.nLocal + ovflPageSize - 1)/ovflPageSize;
+ nOvfl = (pInfo->nPayload - pInfo->nLocal + ovflPageSize - 1)/ovflPageSize;
assert( nOvfl>0 ||
- (CORRUPT_DB && (info.nPayload + ovflPageSize)<ovflPageSize)
+ (CORRUPT_DB && (pInfo->nPayload + ovflPageSize)<ovflPageSize)
);
while( nOvfl-- ){
Pgno iNext = 0;
@@ -67353,7 +74100,7 @@ static int clearCell(
}
if( ( pOvfl || ((pOvfl = btreePageLookup(pBt, ovflPgno))!=0) )
- && sqlite3PagerPageRefcount(pOvfl->pDbPage)!=1
+ && tdsqlite3PagerPageRefcount(pOvfl->pDbPage)!=1
){
/* There is no reason any cursor should have an outstanding reference
** to an overflow page belonging to a cell that is being deleted/updated.
@@ -67371,7 +74118,7 @@ static int clearCell(
}
if( pOvfl ){
- sqlite3PagerUnref(pOvfl->pDbPage);
+ tdsqlite3PagerUnref(pOvfl->pDbPage);
}
if( rc ) return rc;
ovflPgno = iNext;
@@ -67399,22 +74146,21 @@ static int fillInCell(
){
int nPayload;
const u8 *pSrc;
- int nSrc, n, rc;
+ int nSrc, n, rc, mn;
int spaceLeft;
- MemPage *pOvfl = 0;
- MemPage *pToRelease = 0;
+ MemPage *pToRelease;
unsigned char *pPrior;
unsigned char *pPayload;
- BtShared *pBt = pPage->pBt;
- Pgno pgnoOvfl = 0;
+ BtShared *pBt;
+ Pgno pgnoOvfl;
int nHeader;
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
/* pPage is not necessarily writeable since pCell might be auxiliary
** buffer space that is separate from the pPage buffer area */
- assert( pCell<pPage->aData || pCell>=&pPage->aData[pBt->pageSize]
- || sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( pCell<pPage->aData || pCell>=&pPage->aData[pPage->pBt->pageSize]
+ || tdsqlite3PagerIswriteable(pPage->pDbPage) );
/* Fill in the header. */
nHeader = pPage->childPtrSize;
@@ -67433,25 +74179,36 @@ static int fillInCell(
}
/* Fill in the payload */
+ pPayload = &pCell[nHeader];
if( nPayload<=pPage->maxLocal ){
+ /* This is the common case where everything fits on the btree page
+ ** and no overflow pages are required. */
n = nHeader + nPayload;
testcase( n==3 );
testcase( n==4 );
if( n<4 ) n = 4;
*pnSize = n;
- spaceLeft = nPayload;
- pPrior = pCell;
- }else{
- int mn = pPage->minLocal;
- n = mn + (nPayload - mn) % (pPage->pBt->usableSize - 4);
- testcase( n==pPage->maxLocal );
- testcase( n==pPage->maxLocal+1 );
- if( n > pPage->maxLocal ) n = mn;
- spaceLeft = n;
- *pnSize = n + nHeader + 4;
- pPrior = &pCell[nHeader+n];
+ assert( nSrc<=nPayload );
+ testcase( nSrc<nPayload );
+ memcpy(pPayload, pSrc, nSrc);
+ memset(pPayload+nSrc, 0, nPayload-nSrc);
+ return SQLITE_OK;
}
- pPayload = &pCell[nHeader];
+
+ /* If we reach this point, it means that some of the content will need
+ ** to spill onto overflow pages.
+ */
+ mn = pPage->minLocal;
+ n = mn + (nPayload - mn) % (pPage->pBt->usableSize - 4);
+ testcase( n==pPage->maxLocal );
+ testcase( n==pPage->maxLocal+1 );
+ if( n > pPage->maxLocal ) n = mn;
+ spaceLeft = n;
+ *pnSize = n + nHeader + 4;
+ pPrior = &pCell[nHeader+n];
+ pToRelease = 0;
+ pgnoOvfl = 0;
+ pBt = pPage->pBt;
/* At this point variables should be set as follows:
**
@@ -67465,7 +74222,7 @@ static int fillInCell(
** Use a call to btreeParseCellPtr() to verify that the values above
** were computed correctly.
*/
-#if SQLITE_DEBUG
+#ifdef SQLITE_DEBUG
{
CellInfo info;
pPage->xParseCell(pPage, pCell, &info);
@@ -67477,8 +74234,35 @@ static int fillInCell(
#endif
/* Write the payload into the local Cell and any extra into overflow pages */
- while( nPayload>0 ){
+ while( 1 ){
+ n = nPayload;
+ if( n>spaceLeft ) n = spaceLeft;
+
+ /* If pToRelease is not zero than pPayload points into the data area
+ ** of pToRelease. Make sure pToRelease is still writeable. */
+ assert( pToRelease==0 || tdsqlite3PagerIswriteable(pToRelease->pDbPage) );
+
+ /* If pPayload is part of the data area of pPage, then make sure pPage
+ ** is still writeable */
+ assert( pPayload<pPage->aData || pPayload>=&pPage->aData[pBt->pageSize]
+ || tdsqlite3PagerIswriteable(pPage->pDbPage) );
+
+ if( nSrc>=n ){
+ memcpy(pPayload, pSrc, n);
+ }else if( nSrc>0 ){
+ n = nSrc;
+ memcpy(pPayload, pSrc, n);
+ }else{
+ memset(pPayload, 0, n);
+ }
+ nPayload -= n;
+ if( nPayload<=0 ) break;
+ pPayload += n;
+ pSrc += n;
+ nSrc -= n;
+ spaceLeft -= n;
if( spaceLeft==0 ){
+ MemPage *pOvfl = 0;
#ifndef SQLITE_OMIT_AUTOVACUUM
Pgno pgnoPtrmap = pgnoOvfl; /* Overflow page pointer-map entry page */
if( pBt->autoVacuum ){
@@ -67516,12 +74300,12 @@ static int fillInCell(
/* If pToRelease is not zero than pPrior points into the data area
** of pToRelease. Make sure pToRelease is still writeable. */
- assert( pToRelease==0 || sqlite3PagerIswriteable(pToRelease->pDbPage) );
+ assert( pToRelease==0 || tdsqlite3PagerIswriteable(pToRelease->pDbPage) );
/* If pPrior is part of the data area of pPage, then make sure pPage
** is still writeable */
assert( pPrior<pPage->aData || pPrior>=&pPage->aData[pBt->pageSize]
- || sqlite3PagerIswriteable(pPage->pDbPage) );
+ || tdsqlite3PagerIswriteable(pPage->pDbPage) );
put4byte(pPrior, pgnoOvfl);
releasePage(pToRelease);
@@ -67531,30 +74315,6 @@ static int fillInCell(
pPayload = &pOvfl->aData[4];
spaceLeft = pBt->usableSize - 4;
}
- n = nPayload;
- if( n>spaceLeft ) n = spaceLeft;
-
- /* If pToRelease is not zero than pPayload points into the data area
- ** of pToRelease. Make sure pToRelease is still writeable. */
- assert( pToRelease==0 || sqlite3PagerIswriteable(pToRelease->pDbPage) );
-
- /* If pPayload is part of the data area of pPage, then make sure pPage
- ** is still writeable */
- assert( pPayload<pPage->aData || pPayload>=&pPage->aData[pBt->pageSize]
- || sqlite3PagerIswriteable(pPage->pDbPage) );
-
- if( nSrc>0 ){
- if( n>nSrc ) n = nSrc;
- assert( pSrc );
- memcpy(pPayload, pSrc, n);
- }else{
- memset(pPayload, 0, n);
- }
- nPayload -= n;
- pPayload += n;
- pSrc += n;
- nSrc -= n;
- spaceLeft -= n;
}
releasePage(pToRelease);
return SQLITE_OK;
@@ -67576,18 +74336,18 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){
int hdr; /* Beginning of the header. 0 most pages. 100 page 1 */
if( *pRC ) return;
-
assert( idx>=0 && idx<pPage->nCell );
assert( CORRUPT_DB || sz==cellSize(pPage, idx) );
- assert( sqlite3PagerIswriteable(pPage->pDbPage) );
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( pPage->nFree>=0 );
data = pPage->aData;
ptr = &pPage->aCellIdx[2*idx];
pc = get2byte(ptr);
hdr = pPage->hdrOffset;
testcase( pc==get2byte(&data[hdr+5]) );
testcase( pc+sz==pPage->pBt->usableSize );
- if( pc < (u32)get2byte(&data[hdr+5]) || pc+sz > pPage->pBt->usableSize ){
+ if( pc+sz > pPage->pBt->usableSize ){
*pRC = SQLITE_CORRUPT_BKPT;
return;
}
@@ -67644,13 +74404,9 @@ static void insertCell(
assert( pPage->nCell<=MX_CELL(pPage->pBt) || CORRUPT_DB );
assert( pPage->nOverflow<=ArraySize(pPage->apOvfl) );
assert( ArraySize(pPage->apOvfl)==ArraySize(pPage->aiOvfl) );
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- /* The cell should normally be sized correctly. However, when moving a
- ** malformed cell from a leaf page to an interior page, if the cell size
- ** wanted to be less than 4 but got rounded up to 4 on the leaf, then size
- ** might be less than 8 (leaf-size + pointer) on the interior node. Hence
- ** the term after the || in the following assert(). */
- assert( sz==pPage->xCellSize(pPage, pCell) || (sz==8 && iChild>0) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( sz==pPage->xCellSize(pPage, pCell) || CORRUPT_DB );
+ assert( pPage->nFree>=0 );
if( pPage->nOverflow || sz+2>pPage->nFree ){
if( pTemp ){
memcpy(pTemp, pCell, sz);
@@ -67660,7 +74416,10 @@ static void insertCell(
put4byte(pCell, iChild);
}
j = pPage->nOverflow++;
- assert( j<(int)(sizeof(pPage->apOvfl)/sizeof(pPage->apOvfl[0])) );
+ /* Comparison against ArraySize-1 since we hold back one extra slot
+ ** as a contingency. In other words, never need more than 3 overflow
+ ** slots but 4 are allocated, just to be safe. */
+ assert( j < ArraySize(pPage->apOvfl)-1 );
pPage->apOvfl[j] = pCell;
pPage->aiOvfl[j] = (u16)i;
@@ -67672,12 +74431,12 @@ static void insertCell(
assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */
assert( j==0 || i==pPage->aiOvfl[j-1]+1 ); /* Overflows are sequential */
}else{
- int rc = sqlite3PagerWrite(pPage->pDbPage);
+ int rc = tdsqlite3PagerWrite(pPage->pDbPage);
if( rc!=SQLITE_OK ){
*pRC = rc;
return;
}
- assert( sqlite3PagerIswriteable(pPage->pDbPage) );
+ assert( tdsqlite3PagerIswriteable(pPage->pDbPage) );
data = pPage->aData;
assert( &data[pPage->cellOffset]==pPage->aCellIdx );
rc = allocateSpace(pPage, sz, &idx);
@@ -67688,9 +74447,16 @@ static void insertCell(
assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB );
assert( idx+sz <= (int)pPage->pBt->usableSize );
pPage->nFree -= (u16)(2 + sz);
- memcpy(&data[idx], pCell, sz);
if( iChild ){
+ /* In a corrupt database where an entry in the cell index section of
+ ** a btree page has a value of 3 or less, the pCell value might point
+ ** as many as 4 bytes in front of the start of the aData buffer for
+ ** the source page. Make sure this does not cause problems by not
+ ** reading the first 4 bytes */
+ memcpy(&data[idx+4], pCell+4, sz-4);
put4byte(&data[idx], iChild);
+ }else{
+ memcpy(&data[idx], pCell, sz);
}
pIns = pPage->aCellIdx + i*2;
memmove(pIns+2, pIns, 2*(pPage->nCell - i));
@@ -67698,21 +74464,100 @@ static void insertCell(
pPage->nCell++;
/* increment the cell count */
if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++;
- assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell );
+ assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB );
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pPage->pBt->autoVacuum ){
/* The cell may contain a pointer to an overflow page. If so, write
** the entry for the overflow page into the pointer map.
*/
- ptrmapPutOvflPtr(pPage, pCell, pRC);
+ ptrmapPutOvflPtr(pPage, pPage, pCell, pRC);
}
#endif
}
}
/*
+** The following parameters determine how many adjacent pages get involved
+** in a balancing operation. NN is the number of neighbors on either side
+** of the page that participate in the balancing operation. NB is the
+** total number of pages that participate, including the target page and
+** NN neighbors on either side.
+**
+** The minimum value of NN is 1 (of course). Increasing NN above 1
+** (to 2 or 3) gives a modest improvement in SELECT and DELETE performance
+** in exchange for a larger degradation in INSERT and UPDATE performance.
+** The value of NN appears to give the best results overall.
+**
+** (Later:) The description above makes it seem as if these values are
+** tunable - as if you could change them and recompile and it would all work.
+** But that is unlikely. NB has been 3 since the inception of SQLite and
+** we have never tested any other value.
+*/
+#define NN 1 /* Number of neighbors on either side of pPage */
+#define NB 3 /* (NN*2+1): Total pages involved in the balance */
+
+/*
** A CellArray object contains a cache of pointers and sizes for a
** consecutive sequence of cells that might be held on multiple pages.
+**
+** The cells in this array are the divider cell or cells from the pParent
+** page plus up to three child pages. There are a total of nCell cells.
+**
+** pRef is a pointer to one of the pages that contributes cells. This is
+** used to access information such as MemPage.intKey and MemPage.pBt->pageSize
+** which should be common to all pages that contribute cells to this array.
+**
+** apCell[] and szCell[] hold, respectively, pointers to the start of each
+** cell and the size of each cell. Some of the apCell[] pointers might refer
+** to overflow cells. In other words, some apCel[] pointers might not point
+** to content area of the pages.
+**
+** A szCell[] of zero means the size of that cell has not yet been computed.
+**
+** The cells come from as many as four different pages:
+**
+** -----------
+** | Parent |
+** -----------
+** / | \
+** / | \
+** --------- --------- ---------
+** |Child-1| |Child-2| |Child-3|
+** --------- --------- ---------
+**
+** The order of cells is in the array is for an index btree is:
+**
+** 1. All cells from Child-1 in order
+** 2. The first divider cell from Parent
+** 3. All cells from Child-2 in order
+** 4. The second divider cell from Parent
+** 5. All cells from Child-3 in order
+**
+** For a table-btree (with rowids) the items 2 and 4 are empty because
+** content exists only in leaves and there are no divider cells.
+**
+** For an index btree, the apEnd[] array holds pointer to the end of page
+** for Child-1, the Parent, Child-2, the Parent (again), and Child-3,
+** respectively. The ixNx[] array holds the number of cells contained in
+** each of these 5 stages, and all stages to the left. Hence:
+**
+** ixNx[0] = Number of cells in Child-1.
+** ixNx[1] = Number of cells in Child-1 plus 1 for first divider.
+** ixNx[2] = Number of cells in Child-1 and Child-2 + 1 for 1st divider.
+** ixNx[3] = Number of cells in Child-1 and Child-2 + both divider cells
+** ixNx[4] = Total number of cells.
+**
+** For a table-btree, the concept is similar, except only apEnd[0]..apEnd[2]
+** are used and they point to the leaf pages only, and the ixNx value are:
+**
+** ixNx[0] = Number of cells in Child-1.
+** ixNx[1] = Number of cells in Child-1 and Child-2.
+** ixNx[2] = Total number of cells.
+**
+** Sometimes when deleting, a child page can have zero cells. In those
+** cases, ixNx[] entries with higher indexes, and the corresponding apEnd[]
+** entries, shift down. The end result is that each ixNx[] entry should
+** be larger than the previous
*/
typedef struct CellArray CellArray;
struct CellArray {
@@ -67720,6 +74565,8 @@ struct CellArray {
MemPage *pRef; /* Reference page */
u8 **apCell; /* All cells begin balanced */
u16 *szCell; /* Local size of all cells in apCell[] */
+ u8 *apEnd[NB*2]; /* MemPage.aDataEnd values */
+ int ixNx[NB*2]; /* Index of at which we move to the next apEnd[] */
};
/*
@@ -67770,36 +74617,59 @@ static u16 cachedCellSize(CellArray *p, int N){
** responsibility of the caller to set it correctly.
*/
static int rebuildPage(
- MemPage *pPg, /* Edit this page */
+ CellArray *pCArray, /* Content to be added to page pPg */
+ int iFirst, /* First cell in pCArray to use */
int nCell, /* Final number of cells on page */
- u8 **apCell, /* Array of cells */
- u16 *szCell /* Array of cell sizes */
+ MemPage *pPg /* The page to be reconstructed */
){
const int hdr = pPg->hdrOffset; /* Offset of header on pPg */
u8 * const aData = pPg->aData; /* Pointer to data for pPg */
const int usableSize = pPg->pBt->usableSize;
u8 * const pEnd = &aData[usableSize];
- int i;
+ int i = iFirst; /* Which cell to copy from pCArray*/
+ u32 j; /* Start of cell content area */
+ int iEnd = i+nCell; /* Loop terminator */
u8 *pCellptr = pPg->aCellIdx;
- u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager);
+ u8 *pTmp = tdsqlite3PagerTempSpace(pPg->pBt->pPager);
u8 *pData;
+ int k; /* Current slot in pCArray->apEnd[] */
+ u8 *pSrcEnd; /* Current pCArray->apEnd[k] value */
- i = get2byte(&aData[hdr+5]);
- memcpy(&pTmp[i], &aData[i], usableSize - i);
+ assert( i<iEnd );
+ j = get2byte(&aData[hdr+5]);
+ if( NEVER(j>(u32)usableSize) ){ j = 0; }
+ memcpy(&pTmp[j], &aData[j], usableSize - j);
+
+ for(k=0; pCArray->ixNx[k]<=i && ALWAYS(k<NB*2); k++){}
+ pSrcEnd = pCArray->apEnd[k];
pData = pEnd;
- for(i=0; i<nCell; i++){
- u8 *pCell = apCell[i];
+ while( 1/*exit by break*/ ){
+ u8 *pCell = pCArray->apCell[i];
+ u16 sz = pCArray->szCell[i];
+ assert( sz>0 );
if( SQLITE_WITHIN(pCell,aData,pEnd) ){
+ if( ((uptr)(pCell+sz))>(uptr)pEnd ) return SQLITE_CORRUPT_BKPT;
pCell = &pTmp[pCell - aData];
+ }else if( (uptr)(pCell+sz)>(uptr)pSrcEnd
+ && (uptr)(pCell)<(uptr)pSrcEnd
+ ){
+ return SQLITE_CORRUPT_BKPT;
}
- pData -= szCell[i];
+
+ pData -= sz;
put2byte(pCellptr, (pData - aData));
pCellptr += 2;
if( pData < pCellptr ) return SQLITE_CORRUPT_BKPT;
- memcpy(pData, pCell, szCell[i]);
- assert( szCell[i]==pPg->xCellSize(pPg, pCell) || CORRUPT_DB );
- testcase( szCell[i]!=pPg->xCellSize(pPg,pCell) );
+ memcpy(pData, pCell, sz);
+ assert( sz==pPg->xCellSize(pPg, pCell) || CORRUPT_DB );
+ testcase( sz!=pPg->xCellSize(pPg,pCell) )
+ i++;
+ if( i>=iEnd ) break;
+ if( pCArray->ixNx[k]<=i ){
+ k++;
+ pSrcEnd = pCArray->apEnd[k];
+ }
}
/* The pPg->nFree field is now set incorrectly. The caller will fix it. */
@@ -67814,12 +74684,11 @@ static int rebuildPage(
}
/*
-** Array apCell[] contains nCell pointers to b-tree cells. Array szCell
-** contains the size in bytes of each such cell. This function attempts to
-** add the cells stored in the array to page pPg. If it cannot (because
-** the page needs to be defragmented before the cells will fit), non-zero
-** is returned. Otherwise, if the cells are added successfully, zero is
-** returned.
+** The pCArray objects contains pointers to b-tree cells and the cell sizes.
+** This function attempts to add the cells stored in the array to page pPg.
+** If it cannot (because the page needs to be defragmented before the cells
+** will fit), non-zero is returned. Otherwise, if the cells are added
+** successfully, zero is returned.
**
** Argument pCellptr points to the first entry in the cell-pointer array
** (part of page pPg) to populate. After cell apCell[0] is written to the
@@ -67841,21 +74710,27 @@ static int rebuildPage(
static int pageInsertArray(
MemPage *pPg, /* Page to add cells to */
u8 *pBegin, /* End of cell-pointer array */
- u8 **ppData, /* IN/OUT: Page content -area pointer */
+ u8 **ppData, /* IN/OUT: Page content-area pointer */
u8 *pCellptr, /* Pointer to cell-pointer area */
int iFirst, /* Index of first cell to add */
int nCell, /* Number of cells to add to pPg */
CellArray *pCArray /* Array of cells */
){
- int i;
- u8 *aData = pPg->aData;
- u8 *pData = *ppData;
- int iEnd = iFirst + nCell;
+ int i = iFirst; /* Loop counter - cell index to insert */
+ u8 *aData = pPg->aData; /* Complete page */
+ u8 *pData = *ppData; /* Content area. A subset of aData[] */
+ int iEnd = iFirst + nCell; /* End of loop. One past last cell to ins */
+ int k; /* Current slot in pCArray->apEnd[] */
+ u8 *pEnd; /* Maximum extent of cell data */
assert( CORRUPT_DB || pPg->hdrOffset==0 ); /* Never called on page 1 */
- for(i=iFirst; i<iEnd; i++){
+ if( iEnd<=iFirst ) return 0;
+ for(k=0; pCArray->ixNx[k]<=i && ALWAYS(k<NB*2); k++){}
+ pEnd = pCArray->apEnd[k];
+ while( 1 /*Exit by break*/ ){
int sz, rc;
u8 *pSlot;
- sz = cachedCellSize(pCArray, i);
+ assert( pCArray->szCell[i]!=0 );
+ sz = pCArray->szCell[i];
if( (aData[1]==0 && aData[2]==0) || (pSlot = pageFindSlot(pPg,sz,&rc))==0 ){
if( (pData - pBegin)<sz ) return 1;
pData -= sz;
@@ -67867,20 +74742,33 @@ static int pageInsertArray(
assert( (pSlot+sz)<=pCArray->apCell[i]
|| pSlot>=(pCArray->apCell[i]+sz)
|| CORRUPT_DB );
+ if( (uptr)(pCArray->apCell[i]+sz)>(uptr)pEnd
+ && (uptr)(pCArray->apCell[i])<(uptr)pEnd
+ ){
+ assert( CORRUPT_DB );
+ (void)SQLITE_CORRUPT_BKPT;
+ return 1;
+ }
memmove(pSlot, pCArray->apCell[i], sz);
put2byte(pCellptr, (pSlot - aData));
pCellptr += 2;
+ i++;
+ if( i>=iEnd ) break;
+ if( pCArray->ixNx[k]<=i ){
+ k++;
+ pEnd = pCArray->apEnd[k];
+ }
}
*ppData = pData;
return 0;
}
/*
-** Array apCell[] contains nCell pointers to b-tree cells. Array szCell
-** contains the size in bytes of each such cell. This function adds the
-** space associated with each cell in the array that is currently stored
-** within the body of pPg to the pPg free-list. The cell-pointers and other
-** fields of the page are not updated.
+** The pCArray object contains pointers to b-tree cells and their sizes.
+**
+** This function adds the space associated with each cell in the array
+** that is currently stored within the body of pPg to the pPg free-list.
+** The cell-pointers and other fields of the page are not updated.
**
** This function returns the total number of cells added to the free-list.
*/
@@ -67930,9 +74818,9 @@ static int pageFreeArray(
}
/*
-** apCell[] and szCell[] contains pointers to and sizes of all cells in the
-** pages being balanced. The current page, pPg, has pPg->nCell cells starting
-** with apCell[iOld]. After balancing, this page should hold nNew cells
+** pCArray contains pointers to and sizes of all cells in the page being
+** balanced. The current page, pPg, has pPg->nCell cells starting with
+** pCArray->apCell[iOld]. After balancing, this page should hold nNew cells
** starting at apCell[iNew].
**
** This routine makes the necessary adjustments to pPg so that it contains
@@ -67959,18 +74847,22 @@ static int editPage(
int iNewEnd = iNew + nNew;
#ifdef SQLITE_DEBUG
- u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager);
+ u8 *pTmp = tdsqlite3PagerTempSpace(pPg->pBt->pPager);
memcpy(pTmp, aData, pPg->pBt->usableSize);
#endif
/* Remove cells from the start and end of the page */
+ assert( nCell>=0 );
if( iOld<iNew ){
int nShift = pageFreeArray(pPg, iOld, iNew-iOld, pCArray);
+ if( nShift>nCell ) return SQLITE_CORRUPT_BKPT;
memmove(pPg->aCellIdx, &pPg->aCellIdx[nShift*2], nCell*2);
nCell -= nShift;
}
if( iNewEnd < iOldEnd ){
- nCell -= pageFreeArray(pPg, iNewEnd, iOldEnd - iNewEnd, pCArray);
+ int nTail = pageFreeArray(pPg, iNewEnd, iOldEnd - iNewEnd, pCArray);
+ assert( nCell>=nTail );
+ nCell -= nTail;
}
pData = &aData[get2byteNotZero(&aData[hdr+5])];
@@ -67980,6 +74872,7 @@ static int editPage(
if( iNew<iOld ){
int nAdd = MIN(nNew,iOld-iNew);
assert( (iOld-iNew)<nNew || nCell==0 || CORRUPT_DB );
+ assert( nAdd>=0 );
pCellptr = pPg->aCellIdx;
memmove(&pCellptr[nAdd*2], pCellptr, nCell*2);
if( pageInsertArray(
@@ -67994,8 +74887,11 @@ static int editPage(
int iCell = (iOld + pPg->aiOvfl[i]) - iNew;
if( iCell>=0 && iCell<nNew ){
pCellptr = &pPg->aCellIdx[iCell * 2];
- memmove(&pCellptr[2], pCellptr, (nCell - iCell) * 2);
+ if( nCell>iCell ){
+ memmove(&pCellptr[2], pCellptr, (nCell - iCell) * 2);
+ }
nCell++;
+ cachedCellSize(pCArray, iCell+iNew);
if( pageInsertArray(
pPg, pBegin, &pData, pCellptr,
iCell+iNew, 1, pCArray
@@ -68004,6 +74900,7 @@ static int editPage(
}
/* Append cells to the end of the page */
+ assert( nCell>=0 );
pCellptr = &pPg->aCellIdx[nCell*2];
if( pageInsertArray(
pPg, pBegin, &pData, pCellptr,
@@ -68032,24 +74929,9 @@ static int editPage(
editpage_fail:
/* Unable to edit this page. Rebuild it from scratch instead. */
populateCellCache(pCArray, iNew, nNew);
- return rebuildPage(pPg, nNew, &pCArray->apCell[iNew], &pCArray->szCell[iNew]);
+ return rebuildPage(pCArray, iNew, nNew, pPg);
}
-/*
-** The following parameters determine how many adjacent pages get involved
-** in a balancing operation. NN is the number of neighbors on either side
-** of the page that participate in the balancing operation. NB is the
-** total number of pages that participate, including the target page and
-** NN neighbors on either side.
-**
-** The minimum value of NN is 1 (of course). Increasing NN above 1
-** (to 2 or 3) gives a modest improvement in SELECT and DELETE performance
-** in exchange for a larger degradation in INSERT and UPDATE performance.
-** The value of NN appears to give the best results overall.
-*/
-#define NN 1 /* Number of neighbors on either side of pPage */
-#define NB (NN*2+1) /* Total pages involved in the balance */
-
#ifndef SQLITE_OMIT_QUICKBALANCE
/*
@@ -68081,12 +74963,13 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){
int rc; /* Return Code */
Pgno pgnoNew; /* Page number of pNew */
- assert( sqlite3_mutex_held(pPage->pBt->mutex) );
- assert( sqlite3PagerIswriteable(pParent->pDbPage) );
+ assert( tdsqlite3_mutex_held(pPage->pBt->mutex) );
+ assert( tdsqlite3PagerIswriteable(pParent->pDbPage) );
assert( pPage->nOverflow==1 );
-
- /* This error condition is now caught prior to reaching this function */
- if( NEVER(pPage->nCell==0) ) return SQLITE_CORRUPT_BKPT;
+
+ if( pPage->nCell==0 ) return SQLITE_CORRUPT_BKPT; /* dbfuzz001.test */
+ assert( pPage->nFree>=0 );
+ assert( pParent->nFree>=0 );
/* Allocate a new page. This page will become the right-sibling of
** pPage. Make the parent page writable, so that the new divider cell
@@ -68100,12 +74983,22 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){
u8 *pCell = pPage->apOvfl[0];
u16 szCell = pPage->xCellSize(pPage, pCell);
u8 *pStop;
+ CellArray b;
- assert( sqlite3PagerIswriteable(pNew->pDbPage) );
- assert( pPage->aData[0]==(PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF) );
+ assert( tdsqlite3PagerIswriteable(pNew->pDbPage) );
+ assert( CORRUPT_DB || pPage->aData[0]==(PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF) );
zeroPage(pNew, PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF);
- rc = rebuildPage(pNew, 1, &pCell, &szCell);
- if( NEVER(rc) ) return rc;
+ b.nCell = 1;
+ b.pRef = pPage;
+ b.apCell = &pCell;
+ b.szCell = &szCell;
+ b.apEnd[0] = pPage->aDataEnd;
+ b.ixNx[0] = 2;
+ rc = rebuildPage(&b, 0, 1, pNew);
+ if( NEVER(rc) ){
+ releasePage(pNew);
+ return rc;
+ }
pNew->nFree = pBt->usableSize - pNew->cellOffset - 2 - szCell;
/* If this is an auto-vacuum database, update the pointer map
@@ -68120,7 +75013,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){
if( ISAUTOVACUUM ){
ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno, &rc);
if( szCell>pNew->minLocal ){
- ptrmapPutOvflPtr(pNew, pCell, &rc);
+ ptrmapPutOvflPtr(pNew, pNew, pCell, &rc);
}
}
@@ -68246,6 +75139,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){
*/
pTo->isInit = 0;
rc = btreeInitPage(pTo);
+ if( rc==SQLITE_OK ) rc = btreeComputeFreeSpace(pTo);
if( rc!=SQLITE_OK ){
*pRC = rc;
return;
@@ -68340,17 +75234,13 @@ static int balance_nonroot(
b.nCell = 0;
b.apCell = 0;
pBt = pParent->pBt;
- assert( sqlite3_mutex_held(pBt->mutex) );
- assert( sqlite3PagerIswriteable(pParent->pDbPage) );
-
-#if 0
- TRACE(("BALANCE: begin page %d child of %d\n", pPage->pgno, pParent->pgno));
-#endif
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3PagerIswriteable(pParent->pDbPage) );
/* At this point pParent may have at most one overflow cell. And if
** this overflow cell is present, it must be the cell with
** index iParentIdx. This scenario comes about when this function
- ** is called (indirectly) from sqlite3BtreeDelete().
+ ** is called (indirectly) from tdsqlite3BtreeDelete().
*/
assert( pParent->nOverflow==0 || pParent->nOverflow==1 );
assert( pParent->nOverflow==0 || pParent->aiOvfl[0]==iParentIdx );
@@ -68358,6 +75248,7 @@ static int balance_nonroot(
if( !aOvflSpace ){
return SQLITE_NOMEM_BKPT;
}
+ assert( pParent->nFree>=0 );
/* Find the sibling pages to balance. Also locate the cells in pParent
** that divide the siblings. An attempt is made to find NN siblings on
@@ -68397,10 +75288,16 @@ static int balance_nonroot(
memset(apOld, 0, (i+1)*sizeof(MemPage*));
goto balance_cleanup;
}
- nMaxCells += 1+apOld[i]->nCell+apOld[i]->nOverflow;
+ if( apOld[i]->nFree<0 ){
+ rc = btreeComputeFreeSpace(apOld[i]);
+ if( rc ){
+ memset(apOld, 0, (i)*sizeof(MemPage*));
+ goto balance_cleanup;
+ }
+ }
if( (i--)==0 ) break;
- if( i+nxDiv==pParent->aiOvfl[0] && pParent->nOverflow ){
+ if( pParent->nOverflow && i+nxDiv==pParent->aiOvfl[0] ){
apDiv[i] = pParent->apOvfl[0];
pgno = get4byte(apDiv[i]);
szNew[i] = pParent->xCellSize(pParent, apDiv[i]);
@@ -68422,7 +75319,7 @@ static int balance_nonroot(
** In this case, temporarily copy the cell into the aOvflSpace[]
** buffer. It will be copied out again as soon as the aSpace[] buffer
** is allocated. */
- if( pBt->btsFlags & BTS_SECURE_DELETE ){
+ if( pBt->btsFlags & BTS_FAST_SECURE ){
int iOff;
iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData);
@@ -68441,6 +75338,7 @@ static int balance_nonroot(
/* Make nMaxCells a multiple of 4 in order to preserve 8-byte
** alignment */
+ nMaxCells = nOld*(MX_CELL(pBt) + ArraySize(pParent->apOvfl));
nMaxCells = (nMaxCells + 3)&~3;
/*
@@ -68451,10 +75349,8 @@ static int balance_nonroot(
+ nMaxCells*sizeof(u16) /* b.szCell */
+ pBt->pageSize; /* aSpace1 */
- /* EVIDENCE-OF: R-28375-38319 SQLite will never request a scratch buffer
- ** that is more than 6 times the database page size. */
- assert( szScratch<=6*(int)pBt->pageSize );
- b.apCell = sqlite3ScratchMalloc( szScratch );
+ assert( szScratch<=7*(int)pBt->pageSize );
+ b.apCell = tdsqlite3StackAllocRaw(0, szScratch );
if( b.apCell==0 ){
rc = SQLITE_NOMEM_BKPT;
goto balance_cleanup;
@@ -68489,6 +75385,7 @@ static int balance_nonroot(
u16 maskPage = pOld->maskPage;
u8 *piCell = aData + pOld->cellOffset;
u8 *piEnd;
+ VVA_ONLY( int nCellAtStart = b.nCell; )
/* Verify that all sibling pages are of the same "type" (table-leaf,
** table-interior, index-leaf, or index-interior).
@@ -68499,7 +75396,7 @@ static int balance_nonroot(
}
/* Load b.apCell[] with pointers to all cells in pOld. If pOld
- ** constains overflow cells, include them in the b.apCell[] array
+ ** contains overflow cells, include them in the b.apCell[] array
** in the correct spot.
**
** Note that when there are multiple overflow cells, it is always the
@@ -68517,6 +75414,10 @@ static int balance_nonroot(
*/
memset(&b.szCell[b.nCell], 0, sizeof(b.szCell[0])*(limit+pOld->nOverflow));
if( pOld->nOverflow>0 ){
+ if( NEVER(limit<pOld->aiOvfl[0]) ){
+ rc = SQLITE_CORRUPT_BKPT;
+ goto balance_cleanup;
+ }
limit = pOld->aiOvfl[0];
for(j=0; j<limit; j++){
b.apCell[b.nCell] = aData + (maskPage & get2byteAligned(piCell));
@@ -68536,6 +75437,7 @@ static int balance_nonroot(
piCell += 2;
b.nCell++;
}
+ assert( (b.nCell-nCellAtStart)==(pOld->nCell+pOld->nOverflow) );
cntOld[i] = b.nCell;
if( i<nOld-1 && !leafData){
@@ -68589,10 +75491,20 @@ static int balance_nonroot(
**
*/
usableSpace = pBt->usableSize - 12 + leafCorrection;
- for(i=0; i<nOld; i++){
+ for(i=k=0; i<nOld; i++, k++){
MemPage *p = apOld[i];
+ b.apEnd[k] = p->aDataEnd;
+ b.ixNx[k] = cntOld[i];
+ if( k && b.ixNx[k]==b.ixNx[k-1] ){
+ k--; /* Omit b.ixNx[] entry for child pages with no cells */
+ }
+ if( !leafData ){
+ k++;
+ b.apEnd[k] = pParent->aDataEnd;
+ b.ixNx[k] = cntOld[i]+1;
+ }
+ assert( p->nFree>=0 );
szNew[i] = usableSpace - p->nFree;
- if( szNew[i]<0 ){ rc = SQLITE_CORRUPT_BKPT; goto balance_cleanup; }
for(j=0; j<p->nOverflow; j++){
szNew[i] += 2 + p->xCellSize(p, p->apOvfl[j]);
}
@@ -68707,7 +75619,7 @@ static int balance_nonroot(
if( i<nOld ){
pNew = apNew[i] = apOld[i];
apOld[i] = 0;
- rc = sqlite3PagerWrite(pNew->pDbPage);
+ rc = tdsqlite3PagerWrite(pNew->pDbPage);
nNew++;
if( rc ) goto balance_cleanup;
}else{
@@ -68767,9 +75679,9 @@ static int balance_nonroot(
aPgOrder[iBest] = 0xffffffff;
if( iBest!=i ){
if( iBest>i ){
- sqlite3PagerRekey(apNew[iBest]->pDbPage, pBt->nPage+iBest+1, 0);
+ tdsqlite3PagerRekey(apNew[iBest]->pDbPage, pBt->nPage+iBest+1, 0);
}
- sqlite3PagerRekey(apNew[i]->pDbPage, pgno, aPgFlags[iBest]);
+ tdsqlite3PagerRekey(apNew[i]->pDbPage, pgno, aPgFlags[iBest]);
apNew[i]->pgno = pgno;
}
}
@@ -68787,7 +75699,9 @@ static int balance_nonroot(
nNew>=5 ? cntNew[4] - cntNew[3] - !leafData : 0
));
- assert( sqlite3PagerIswriteable(pParent->pDbPage) );
+ assert( tdsqlite3PagerIswriteable(pParent->pDbPage) );
+ assert( nNew>=1 && nNew<=ArraySize(apNew) );
+ assert( apNew[nNew-1]!=0 );
put4byte(pRight, apNew[nNew-1]->pgno);
/* If the sibling pages are not leaves, ensure that the right-child pointer
@@ -68815,19 +75729,20 @@ static int balance_nonroot(
** populated, not here.
*/
if( ISAUTOVACUUM ){
- MemPage *pNew = apNew[0];
- u8 *aOld = pNew->aData;
+ MemPage *pOld;
+ MemPage *pNew = pOld = apNew[0];
int cntOldNext = pNew->nCell + pNew->nOverflow;
- int usableSize = pBt->usableSize;
int iNew = 0;
int iOld = 0;
for(i=0; i<b.nCell; i++){
u8 *pCell = b.apCell[i];
- if( i==cntOldNext ){
- MemPage *pOld = (++iOld)<nNew ? apNew[iOld] : apOld[iOld];
+ while( i==cntOldNext ){
+ iOld++;
+ assert( iOld<nNew || iOld<nOld );
+ assert( iOld>=0 && iOld<NB );
+ pOld = iOld<nNew ? apNew[iOld] : apOld[iOld];
cntOldNext += pOld->nCell + pOld->nOverflow + !leafData;
- aOld = pOld->aData;
}
if( i==cntNew[iNew] ){
pNew = apNew[++iNew];
@@ -68842,13 +75757,13 @@ static int balance_nonroot(
** overflow cell), we can skip updating the pointer map entries. */
if( iOld>=nNew
|| pNew->pgno!=aPgno[iOld]
- || !SQLITE_WITHIN(pCell,aOld,&aOld[usableSize])
+ || !SQLITE_WITHIN(pCell,pOld->aData,pOld->aDataEnd)
){
if( !leafCorrection ){
ptrmapPut(pBt, get4byte(pCell), PTRMAP_BTREE, pNew->pgno, &rc);
}
if( cachedCellSize(&b,i)>pNew->minLocal ){
- ptrmapPutOvflPtr(pNew, pCell, &rc);
+ ptrmapPutOvflPtr(pNew, pOld, pCell, &rc);
}
if( rc ) goto balance_cleanup;
}
@@ -68905,7 +75820,7 @@ static int balance_nonroot(
assert( iOvflSpace <= (int)pBt->pageSize );
insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno, &rc);
if( rc!=SQLITE_OK ) goto balance_cleanup;
- assert( sqlite3PagerIswriteable(pParent->pDbPage) );
+ assert( tdsqlite3PagerIswriteable(pParent->pDbPage) );
}
/* Now update the actual sibling pages. The order in which they are updated
@@ -68990,10 +75905,11 @@ static int balance_nonroot(
** free space needs to be up front.
*/
assert( nNew==1 || CORRUPT_DB );
- rc = defragmentPage(apNew[0]);
+ rc = defragmentPage(apNew[0], -1);
testcase( rc!=SQLITE_OK );
assert( apNew[0]->nFree ==
- (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2)
+ (get2byteNotZero(&apNew[0]->aData[5]) - apNew[0]->cellOffset
+ - apNew[0]->nCell*2)
|| rc!=SQLITE_OK
);
copyNodeContent(apNew[0], pParent, &rc);
@@ -69033,7 +75949,7 @@ static int balance_nonroot(
** Cleanup before returning.
*/
balance_cleanup:
- sqlite3ScratchFree(b.apCell);
+ tdsqlite3StackFree(0, b.apCell);
for(i=0; i<nOld; i++){
releasePage(apOld[i]);
}
@@ -69071,13 +75987,13 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){
BtShared *pBt = pRoot->pBt; /* The BTree */
assert( pRoot->nOverflow>0 );
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
/* Make pRoot, the root page of the b-tree, writable. Allocate a new
** page that will become the new right-child of pPage. Copy the contents
** of the node stored on pRoot into the new child page.
*/
- rc = sqlite3PagerWrite(pRoot->pDbPage);
+ rc = tdsqlite3PagerWrite(pRoot->pDbPage);
if( rc==SQLITE_OK ){
rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0);
copyNodeContent(pRoot, pChild, &rc);
@@ -69090,9 +76006,9 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){
releasePage(pChild);
return rc;
}
- assert( sqlite3PagerIswriteable(pChild->pDbPage) );
- assert( sqlite3PagerIswriteable(pRoot->pDbPage) );
- assert( pChild->nCell==pRoot->nCell );
+ assert( tdsqlite3PagerIswriteable(pChild->pDbPage) );
+ assert( tdsqlite3PagerIswriteable(pRoot->pDbPage) );
+ assert( pChild->nCell==pRoot->nCell || CORRUPT_DB );
TRACE(("BALANCE: copy root %d into %d\n", pRoot->pgno, pChild->pgno));
@@ -69112,6 +76028,30 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){
}
/*
+** Return SQLITE_CORRUPT if any cursor other than pCur is currently valid
+** on the same B-tree as pCur.
+**
+** This can if a database is corrupt with two or more SQL tables
+** pointing to the same b-tree. If an insert occurs on one SQL table
+** and causes a BEFORE TRIGGER to do a secondary insert on the other SQL
+** table linked to the same b-tree. If the secondary insert causes a
+** rebalance, that can change content out from under the cursor on the
+** first SQL table, violating invariants on the first insert.
+*/
+static int anotherValidCursor(BtCursor *pCur){
+ BtCursor *pOther;
+ for(pOther=pCur->pBt->pCursor; pOther; pOther=pOther->pNext){
+ if( pOther!=pCur
+ && pOther->eState==CURSOR_VALID
+ && pOther->pPage==pCur->pPage
+ ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
** The page that pCur currently points to has just been modified in
** some way. This function figures out if this modification means the
** tree needs to be balanced, and if so calls the appropriate balancing
@@ -69131,11 +76071,14 @@ static int balance(BtCursor *pCur){
VVA_ONLY( int balance_deeper_called = 0 );
do {
- int iPage = pCur->iPage;
- MemPage *pPage = pCur->apPage[iPage];
+ int iPage;
+ MemPage *pPage = pCur->pPage;
- if( iPage==0 ){
- if( pPage->nOverflow ){
+ if( NEVER(pPage->nFree<0) && btreeComputeFreeSpace(pPage) ) break;
+ if( pPage->nOverflow==0 && pPage->nFree<=nMin ){
+ break;
+ }else if( (iPage = pCur->iPage)==0 ){
+ if( pPage->nOverflow && (rc = anotherValidCursor(pCur))==SQLITE_OK ){
/* The root page of the b-tree is overfull. In this case call the
** balance_deeper() function to create a new child for the root-page
** and copy the current contents of the root-page to it. The
@@ -69146,20 +76089,23 @@ static int balance(BtCursor *pCur){
rc = balance_deeper(pPage, &pCur->apPage[1]);
if( rc==SQLITE_OK ){
pCur->iPage = 1;
+ pCur->ix = 0;
pCur->aiIdx[0] = 0;
- pCur->aiIdx[1] = 0;
- assert( pCur->apPage[1]->nOverflow );
+ pCur->apPage[0] = pPage;
+ pCur->pPage = pCur->apPage[1];
+ assert( pCur->pPage->nOverflow );
}
}else{
break;
}
- }else if( pPage->nOverflow==0 && pPage->nFree<=nMin ){
- break;
}else{
MemPage * const pParent = pCur->apPage[iPage-1];
int const iIdx = pCur->aiIdx[iPage-1];
- rc = sqlite3PagerWrite(pParent->pDbPage);
+ rc = tdsqlite3PagerWrite(pParent->pDbPage);
+ if( rc==SQLITE_OK && pParent->nFree<0 ){
+ rc = btreeComputeFreeSpace(pParent);
+ }
if( rc==SQLITE_OK ){
#ifndef SQLITE_OMIT_QUICKBALANCE
if( pPage->intKeyLeaf
@@ -69204,7 +76150,7 @@ static int balance(BtCursor *pCur){
** copied either into the body of a database page or into the new
** pSpace buffer passed to the latter call to balance_nonroot().
*/
- u8 *pSpace = sqlite3PageMalloc(pCur->pBt->pageSize);
+ u8 *pSpace = tdsqlite3PageMalloc(pCur->pBt->pageSize);
rc = balance_nonroot(pParent, iIdx, pSpace, iPage==1,
pCur->hints&BTREE_BULKLOAD);
if( pFree ){
@@ -69212,7 +76158,7 @@ static int balance(BtCursor *pCur){
** by a previous call to balance_nonroot(). Its contents are
** now stored either on real database pages or within the
** new pSpace buffer, so it may be safely freed here. */
- sqlite3PageFree(pFree);
+ tdsqlite3PageFree(pFree);
}
/* The pSpace buffer will be freed after the next call to
@@ -69228,15 +76174,110 @@ static int balance(BtCursor *pCur){
releasePage(pPage);
pCur->iPage--;
assert( pCur->iPage>=0 );
+ pCur->pPage = pCur->apPage[pCur->iPage];
}
}while( rc==SQLITE_OK );
if( pFree ){
- sqlite3PageFree(pFree);
+ tdsqlite3PageFree(pFree);
}
return rc;
}
+/* Overwrite content from pX into pDest. Only do the write if the
+** content is different from what is already there.
+*/
+static int btreeOverwriteContent(
+ MemPage *pPage, /* MemPage on which writing will occur */
+ u8 *pDest, /* Pointer to the place to start writing */
+ const BtreePayload *pX, /* Source of data to write */
+ int iOffset, /* Offset of first byte to write */
+ int iAmt /* Number of bytes to be written */
+){
+ int nData = pX->nData - iOffset;
+ if( nData<=0 ){
+ /* Overwritting with zeros */
+ int i;
+ for(i=0; i<iAmt && pDest[i]==0; i++){}
+ if( i<iAmt ){
+ int rc = tdsqlite3PagerWrite(pPage->pDbPage);
+ if( rc ) return rc;
+ memset(pDest + i, 0, iAmt - i);
+ }
+ }else{
+ if( nData<iAmt ){
+ /* Mixed read data and zeros at the end. Make a recursive call
+ ** to write the zeros then fall through to write the real data */
+ int rc = btreeOverwriteContent(pPage, pDest+nData, pX, iOffset+nData,
+ iAmt-nData);
+ if( rc ) return rc;
+ iAmt = nData;
+ }
+ if( memcmp(pDest, ((u8*)pX->pData) + iOffset, iAmt)!=0 ){
+ int rc = tdsqlite3PagerWrite(pPage->pDbPage);
+ if( rc ) return rc;
+ /* In a corrupt database, it is possible for the source and destination
+ ** buffers to overlap. This is harmless since the database is already
+ ** corrupt but it does cause valgrind and ASAN warnings. So use
+ ** memmove(). */
+ memmove(pDest, ((u8*)pX->pData) + iOffset, iAmt);
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Overwrite the cell that cursor pCur is pointing to with fresh content
+** contained in pX.
+*/
+static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){
+ int iOffset; /* Next byte of pX->pData to write */
+ int nTotal = pX->nData + pX->nZero; /* Total bytes of to write */
+ int rc; /* Return code */
+ MemPage *pPage = pCur->pPage; /* Page being written */
+ BtShared *pBt; /* Btree */
+ Pgno ovflPgno; /* Next overflow page to write */
+ u32 ovflPageSize; /* Size to write on overflow page */
+
+ if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd
+ || pCur->info.pPayload < pPage->aData + pPage->cellOffset
+ ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ /* Overwrite the local portion first */
+ rc = btreeOverwriteContent(pPage, pCur->info.pPayload, pX,
+ 0, pCur->info.nLocal);
+ if( rc ) return rc;
+ if( pCur->info.nLocal==nTotal ) return SQLITE_OK;
+
+ /* Now overwrite the overflow pages */
+ iOffset = pCur->info.nLocal;
+ assert( nTotal>=0 );
+ assert( iOffset>=0 );
+ ovflPgno = get4byte(pCur->info.pPayload + iOffset);
+ pBt = pPage->pBt;
+ ovflPageSize = pBt->usableSize - 4;
+ do{
+ rc = btreeGetPage(pBt, ovflPgno, &pPage, 0);
+ if( rc ) return rc;
+ if( tdsqlite3PagerPageRefcount(pPage->pDbPage)!=1 ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ if( iOffset+ovflPageSize<(u32)nTotal ){
+ ovflPgno = get4byte(pPage->aData);
+ }else{
+ ovflPageSize = nTotal - iOffset;
+ }
+ rc = btreeOverwriteContent(pPage, pPage->aData+4, pX,
+ iOffset, ovflPageSize);
+ }
+ tdsqlite3PagerUnref(pPage->pDbPage);
+ if( rc ) return rc;
+ iOffset += ovflPageSize;
+ }while( iOffset<nTotal );
+ return SQLITE_OK;
+}
+
/*
** Insert a new record into the BTree. The content of the new record
@@ -69254,22 +76295,24 @@ static int balance(BtCursor *pCur){
** pX.pData,nData,nZero fields must be zero.
**
** If the seekResult parameter is non-zero, then a successful call to
-** MovetoUnpacked() to seek cursor pCur to (pKey, nKey) has already
-** been performed. seekResult is the search result returned (a negative
-** number if pCur points at an entry that is smaller than (pKey, nKey), or
-** a positive value if pCur points at an entry that is larger than
-** (pKey, nKey)).
-**
-** If the seekResult parameter is non-zero, then the caller guarantees that
-** cursor pCur is pointing at the existing copy of a row that is to be
-** overwritten. If the seekResult parameter is 0, then cursor pCur may
-** point to any entry or to no entry at all and so this function has to seek
-** the cursor before the new key can be inserted.
-*/
-SQLITE_PRIVATE int sqlite3BtreeInsert(
+** MovetoUnpacked() to seek cursor pCur to (pKey,nKey) has already
+** been performed. In other words, if seekResult!=0 then the cursor
+** is currently pointing to a cell that will be adjacent to the cell
+** to be inserted. If seekResult<0 then pCur points to a cell that is
+** smaller then (pKey,nKey). If seekResult>0 then pCur points to a cell
+** that is larger than (pKey,nKey).
+**
+** If seekResult==0, that means pCur is pointing at some unknown location.
+** In that case, this routine must seek the cursor to the correct insertion
+** point for (pKey,nKey) before doing the insertion. For index btrees,
+** if pX->nMem is non-zero, then pX->aMem contains pointers to the unpacked
+** key values and pX->aMem can be used instead of pX->pKey to avoid having
+** to decode the key.
+*/
+SQLITE_PRIVATE int tdsqlite3BtreeInsert(
BtCursor *pCur, /* Insert data into the table of this cursor */
const BtreePayload *pX, /* Content of the row to be inserted */
- int appendBias, /* True if this is likely an append */
+ int flags, /* True if this is likely an append */
int seekResult /* Result of prior MovetoUnpacked() call */
){
int rc;
@@ -69282,6 +76325,8 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
unsigned char *oldCell;
unsigned char *newCell = 0;
+ assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND))==flags );
+
if( pCur->eState==CURSOR_FAULT ){
assert( pCur->skipNext!=SQLITE_OK );
return pCur->skipNext;
@@ -69304,7 +76349,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
**
** In some cases, the call to btreeMoveto() below is a no-op. For
** example, when inserting data into a table with auto-generated integer
- ** keys, the VDBE layer invokes sqlite3BtreeLast() to figure out the
+ ** keys, the VDBE layer invokes tdsqlite3BtreeLast() to figure out the
** integer key to use. It then calls this function to actually insert the
** data into the intkey B-Tree. In this case btreeMoveto() recognizes
** that the cursor is already where it needs to be and returns without
@@ -69320,27 +76365,100 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
assert( pX->pKey==0 );
/* If this is an insert into a table b-tree, invalidate any incrblob
** cursors open on the row being replaced */
- invalidateIncrblobCursors(p, pX->nKey, 0);
-
- /* If the cursor is currently on the last row and we are appending a
- ** new row onto the end, set the "loc" to avoid an unnecessary
- ** btreeMoveto() call */
- if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey>0
- && pCur->info.nKey==pX->nKey-1 ){
- loc = -1;
+ invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0);
+
+ /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing
+ ** to a row with the same key as the new entry being inserted.
+ */
+#ifdef SQLITE_DEBUG
+ if( flags & BTREE_SAVEPOSITION ){
+ assert( pCur->curFlags & BTCF_ValidNKey );
+ assert( pX->nKey==pCur->info.nKey );
+ assert( loc==0 );
+ }
+#endif
+
+ /* On the other hand, BTREE_SAVEPOSITION==0 does not imply
+ ** that the cursor is not pointing to a row to be overwritten.
+ ** So do a complete check.
+ */
+ if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey==pCur->info.nKey ){
+ /* The cursor is pointing to the entry that is to be
+ ** overwritten */
+ assert( pX->nData>=0 && pX->nZero>=0 );
+ if( pCur->info.nSize!=0
+ && pCur->info.nPayload==(u32)pX->nData+pX->nZero
+ ){
+ /* New entry is the same size as the old. Do an overwrite */
+ return btreeOverwriteCell(pCur, pX);
+ }
+ assert( loc==0 );
}else if( loc==0 ){
- rc = sqlite3BtreeMovetoUnpacked(pCur, 0, pX->nKey, appendBias, &loc);
+ /* The cursor is *not* pointing to the cell to be overwritten, nor
+ ** to an adjacent cell. Move the cursor so that it is pointing either
+ ** to the cell to be overwritten or an adjacent cell.
+ */
+ rc = tdsqlite3BtreeMovetoUnpacked(pCur, 0, pX->nKey, flags!=0, &loc);
if( rc ) return rc;
}
- }else if( loc==0 ){
- rc = btreeMoveto(pCur, pX->pKey, pX->nKey, appendBias, &loc);
- if( rc ) return rc;
+ }else{
+ /* This is an index or a WITHOUT ROWID table */
+
+ /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing
+ ** to a row with the same key as the new entry being inserted.
+ */
+ assert( (flags & BTREE_SAVEPOSITION)==0 || loc==0 );
+
+ /* If the cursor is not already pointing either to the cell to be
+ ** overwritten, or if a new cell is being inserted, if the cursor is
+ ** not pointing to an immediately adjacent cell, then move the cursor
+ ** so that it does.
+ */
+ if( loc==0 && (flags & BTREE_SAVEPOSITION)==0 ){
+ if( pX->nMem ){
+ UnpackedRecord r;
+ r.pKeyInfo = pCur->pKeyInfo;
+ r.aMem = pX->aMem;
+ r.nField = pX->nMem;
+ r.default_rc = 0;
+ r.errCode = 0;
+ r.r1 = 0;
+ r.r2 = 0;
+ r.eqSeen = 0;
+ rc = tdsqlite3BtreeMovetoUnpacked(pCur, &r, 0, flags!=0, &loc);
+ }else{
+ rc = btreeMoveto(pCur, pX->pKey, pX->nKey, flags!=0, &loc);
+ }
+ if( rc ) return rc;
+ }
+
+ /* If the cursor is currently pointing to an entry to be overwritten
+ ** and the new content is the same as as the old, then use the
+ ** overwrite optimization.
+ */
+ if( loc==0 ){
+ getCellInfo(pCur);
+ if( pCur->info.nKey==pX->nKey ){
+ BtreePayload x2;
+ x2.pData = pX->pKey;
+ x2.nData = pX->nKey;
+ x2.nZero = 0;
+ return btreeOverwriteCell(pCur, &x2);
+ }
+ }
+
}
- assert( pCur->eState==CURSOR_VALID || (pCur->eState==CURSOR_INVALID && loc) );
+ assert( pCur->eState==CURSOR_VALID
+ || (pCur->eState==CURSOR_INVALID && loc)
+ || CORRUPT_DB );
- pPage = pCur->apPage[pCur->iPage];
+ pPage = pCur->pPage;
assert( pPage->intKey || pX->nKey>=0 );
assert( pPage->leaf || !pPage->intKey );
+ if( pPage->nFree<0 ){
+ rc = btreeComputeFreeSpace(pPage);
+ if( rc ) return rc;
+ }
TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n",
pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno,
@@ -69352,11 +76470,11 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
if( rc ) goto end_insert;
assert( szNew==pPage->xCellSize(pPage, newCell) );
assert( szNew <= MX_CELL_SIZE(pBt) );
- idx = pCur->aiIdx[pCur->iPage];
+ idx = pCur->ix;
if( loc==0 ){
- u16 szOld;
+ CellInfo info;
assert( idx<pPage->nCell );
- rc = sqlite3PagerWrite(pPage->pDbPage);
+ rc = tdsqlite3PagerWrite(pPage->pDbPage);
if( rc ){
goto end_insert;
}
@@ -69364,12 +76482,37 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
if( !pPage->leaf ){
memcpy(newCell, oldCell, 4);
}
- rc = clearCell(pPage, oldCell, &szOld);
- dropCell(pPage, idx, szOld, &rc);
+ rc = clearCell(pPage, oldCell, &info);
+ testcase( pCur->curFlags & BTCF_ValidOvfl );
+ invalidateOverflowCache(pCur);
+ if( info.nSize==szNew && info.nLocal==info.nPayload
+ && (!ISAUTOVACUUM || szNew<pPage->minLocal)
+ ){
+ /* Overwrite the old cell with the new if they are the same size.
+ ** We could also try to do this if the old cell is smaller, then add
+ ** the leftover space to the free list. But experiments show that
+ ** doing that is no faster then skipping this optimization and just
+ ** calling dropCell() and insertCell().
+ **
+ ** This optimization cannot be used on an autovacuum database if the
+ ** new entry uses overflow pages, as the insertCell() call below is
+ ** necessary to add the PTRMAP_OVERFLOW1 pointer-map entry. */
+ assert( rc==SQLITE_OK ); /* clearCell never fails when nLocal==nPayload */
+ if( oldCell < pPage->aData+pPage->hdrOffset+10 ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ if( oldCell+szNew > pPage->aDataEnd ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ memcpy(oldCell, newCell, szNew);
+ return SQLITE_OK;
+ }
+ dropCell(pPage, idx, info.nSize, &rc);
if( rc ) goto end_insert;
}else if( loc<0 && pPage->nCell>0 ){
assert( pPage->leaf );
- idx = ++pCur->aiIdx[pCur->iPage];
+ idx = ++pCur->ix;
+ pCur->curFlags &= ~BTCF_ValidNKey;
}else{
assert( pPage->leaf );
}
@@ -69407,10 +76550,24 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
** fails. Internal data structure corruption will result otherwise.
** Also, set the cursor state to invalid. This stops saveCursorPosition()
** from trying to save the current position of the cursor. */
- pCur->apPage[pCur->iPage]->nOverflow = 0;
+ pCur->pPage->nOverflow = 0;
pCur->eState = CURSOR_INVALID;
+ if( (flags & BTREE_SAVEPOSITION) && rc==SQLITE_OK ){
+ btreeReleaseAllCursorPages(pCur);
+ if( pCur->pKeyInfo ){
+ assert( pCur->pKey==0 );
+ pCur->pKey = tdsqlite3Malloc( pX->nKey );
+ if( pCur->pKey==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memcpy(pCur->pKey, pX->pKey, pX->nKey);
+ }
+ }
+ pCur->eState = CURSOR_REQUIRESEEK;
+ pCur->nKey = pX->nKey;
+ }
}
- assert( pCur->apPage[pCur->iPage]->nOverflow==0 );
+ assert( pCur->iPage<0 || pCur->pPage->nOverflow==0 );
end_insert:
return rc;
@@ -69433,7 +76590,7 @@ end_insert:
** The BTREE_AUXDELETE bit is a hint that is not used by this implementation,
** but which might be used by alternative storage engines.
*/
-SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
+SQLITE_PRIVATE int tdsqlite3BtreeDelete(BtCursor *pCur, u8 flags){
Btree *p = pCur->pBtree;
BtShared *pBt = p->pBt;
int rc; /* Return code */
@@ -69441,7 +76598,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
unsigned char *pCell; /* Pointer to cell to delete */
int iCellIdx; /* Index of cell to delete */
int iCellDepth; /* Depth of node containing pCell */
- u16 szCell; /* Size of the cell being deleted */
+ CellInfo info; /* Size of the cell being deleted */
int bSkipnext = 0; /* Leaf cursor in SKIPNEXT state */
u8 bPreserve = flags & BTREE_SAVEPOSITION; /* Keep cursor valid */
@@ -69451,14 +76608,18 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
assert( pCur->curFlags & BTCF_WriteFlag );
assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) );
assert( !hasReadConflicts(p, pCur->pgnoRoot) );
- assert( pCur->aiIdx[pCur->iPage]<pCur->apPage[pCur->iPage]->nCell );
- assert( pCur->eState==CURSOR_VALID );
assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 );
+ if( pCur->eState==CURSOR_REQUIRESEEK ){
+ rc = btreeRestoreCursorPosition(pCur);
+ if( rc ) return rc;
+ }
+ assert( pCur->eState==CURSOR_VALID );
iCellDepth = pCur->iPage;
- iCellIdx = pCur->aiIdx[iCellDepth];
- pPage = pCur->apPage[iCellDepth];
+ iCellIdx = pCur->ix;
+ pPage = pCur->pPage;
pCell = findCell(pPage, iCellIdx);
+ if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ) return SQLITE_CORRUPT;
/* If the bPreserve flag is set to true, then the cursor position must
** be preserved following this delete operation. If the current delete
@@ -69472,6 +76633,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
if( bPreserve ){
if( !pPage->leaf
|| (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3)
+ || pPage->nCell==1 /* See dbfuzz001.test for a test case */
){
/* A b-tree rebalance will be required after deleting this entry.
** Save the cursor key. */
@@ -69490,8 +76652,8 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
** sub-tree headed by the child page of the cell being deleted. This makes
** balancing the tree following the delete operation easier. */
if( !pPage->leaf ){
- int notUsed = 0;
- rc = sqlite3BtreePrevious(pCur, &notUsed);
+ rc = tdsqlite3BtreePrevious(pCur, 0);
+ assert( rc!=SQLITE_DONE );
if( rc ) return rc;
}
@@ -69505,16 +76667,16 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
/* If this is a delete operation to remove a row from a table b-tree,
** invalidate any incrblob cursors open on the row being deleted. */
if( pCur->pKeyInfo==0 ){
- invalidateIncrblobCursors(p, pCur->info.nKey, 0);
+ invalidateIncrblobCursors(p, pCur->pgnoRoot, pCur->info.nKey, 0);
}
/* Make the page containing the entry to be deleted writable. Then free any
** overflow pages associated with the entry and finally remove the cell
** itself from within the page. */
- rc = sqlite3PagerWrite(pPage->pDbPage);
+ rc = tdsqlite3PagerWrite(pPage->pDbPage);
if( rc ) return rc;
- rc = clearCell(pPage, pCell, &szCell);
- dropCell(pPage, iCellIdx, szCell, &rc);
+ rc = clearCell(pPage, pCell, &info);
+ dropCell(pPage, iCellIdx, info.nSize, &rc);
if( rc ) return rc;
/* If the cell deleted was not located on a leaf page, then the cursor
@@ -69523,18 +76685,27 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
** node. The cell from the leaf node needs to be moved to the internal
** node to replace the deleted cell. */
if( !pPage->leaf ){
- MemPage *pLeaf = pCur->apPage[pCur->iPage];
+ MemPage *pLeaf = pCur->pPage;
int nCell;
- Pgno n = pCur->apPage[iCellDepth+1]->pgno;
+ Pgno n;
unsigned char *pTmp;
+ if( pLeaf->nFree<0 ){
+ rc = btreeComputeFreeSpace(pLeaf);
+ if( rc ) return rc;
+ }
+ if( iCellDepth<pCur->iPage-1 ){
+ n = pCur->apPage[iCellDepth+1]->pgno;
+ }else{
+ n = pCur->pPage->pgno;
+ }
pCell = findCell(pLeaf, pLeaf->nCell-1);
if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_BKPT;
nCell = pLeaf->xCellSize(pLeaf, pCell);
assert( MX_CELL_SIZE(pBt) >= nCell );
pTmp = pBt->pTmpSpace;
assert( pTmp!=0 );
- rc = sqlite3PagerWrite(pLeaf->pDbPage);
+ rc = tdsqlite3PagerWrite(pLeaf->pDbPage);
if( rc==SQLITE_OK ){
insertCell(pPage, iCellIdx, pCell-4, nCell+4, pTmp, n, &rc);
}
@@ -69559,29 +76730,34 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
** well. */
rc = balance(pCur);
if( rc==SQLITE_OK && pCur->iPage>iCellDepth ){
+ releasePageNotNull(pCur->pPage);
+ pCur->iPage--;
while( pCur->iPage>iCellDepth ){
releasePage(pCur->apPage[pCur->iPage--]);
}
+ pCur->pPage = pCur->apPage[pCur->iPage];
rc = balance(pCur);
}
if( rc==SQLITE_OK ){
if( bSkipnext ){
assert( bPreserve && (pCur->iPage==iCellDepth || CORRUPT_DB) );
- assert( pPage==pCur->apPage[pCur->iPage] || CORRUPT_DB );
+ assert( pPage==pCur->pPage || CORRUPT_DB );
assert( (pPage->nCell>0 || CORRUPT_DB) && iCellIdx<=pPage->nCell );
pCur->eState = CURSOR_SKIPNEXT;
if( iCellIdx>=pPage->nCell ){
pCur->skipNext = -1;
- pCur->aiIdx[iCellDepth] = pPage->nCell-1;
+ pCur->ix = pPage->nCell-1;
}else{
pCur->skipNext = 1;
}
}else{
rc = moveToRoot(pCur);
if( bPreserve ){
+ btreeReleaseAllCursorPages(pCur);
pCur->eState = CURSOR_REQUIRESEEK;
}
+ if( rc==SQLITE_EMPTY ) rc = SQLITE_OK;
}
}
return rc;
@@ -69605,7 +76781,7 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){
int rc;
int ptfFlags; /* Page-type flage for the root page of new table */
- assert( sqlite3BtreeHoldsMutex(p) );
+ assert( tdsqlite3BtreeHoldsMutex(p) );
assert( pBt->inTransaction==TRANS_WRITE );
assert( (pBt->btsFlags & BTS_READ_ONLY)==0 );
@@ -69630,7 +76806,7 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){
** root page of the new table should go. meta[3] is the largest root-page
** created so far, so the new root-page is (meta[3]+1).
*/
- sqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &pgnoRoot);
+ tdsqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &pgnoRoot);
pgnoRoot++;
/* The new root-page may not be allocated on a pointer-map page, or the
@@ -69697,7 +76873,7 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){
if( rc!=SQLITE_OK ){
return rc;
}
- rc = sqlite3PagerWrite(pRoot->pDbPage);
+ rc = tdsqlite3PagerWrite(pRoot->pDbPage);
if( rc!=SQLITE_OK ){
releasePage(pRoot);
return rc;
@@ -69715,10 +76891,10 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){
/* When the new root page was allocated, page 1 was made writable in
** order either to increase the database filesize, or to decrement the
- ** freelist count. Hence, the sqlite3BtreeUpdateMeta() call cannot fail.
+ ** freelist count. Hence, the tdsqlite3BtreeUpdateMeta() call cannot fail.
*/
- assert( sqlite3PagerIswriteable(pBt->pPage1->pDbPage) );
- rc = sqlite3BtreeUpdateMeta(p, 4, pgnoRoot);
+ assert( tdsqlite3PagerIswriteable(pBt->pPage1->pDbPage) );
+ rc = tdsqlite3BtreeUpdateMeta(p, 4, pgnoRoot);
if( NEVER(rc) ){
releasePage(pRoot);
return rc;
@@ -69729,23 +76905,23 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){
if( rc ) return rc;
}
#endif
- assert( sqlite3PagerIswriteable(pRoot->pDbPage) );
+ assert( tdsqlite3PagerIswriteable(pRoot->pDbPage) );
if( createTabFlags & BTREE_INTKEY ){
ptfFlags = PTF_INTKEY | PTF_LEAFDATA | PTF_LEAF;
}else{
ptfFlags = PTF_ZERODATA | PTF_LEAF;
}
zeroPage(pRoot, ptfFlags);
- sqlite3PagerUnref(pRoot->pDbPage);
+ tdsqlite3PagerUnref(pRoot->pDbPage);
assert( (pBt->openFlags & BTREE_SINGLE)==0 || pgnoRoot==2 );
*piTable = (int)pgnoRoot;
return SQLITE_OK;
}
-SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree *p, int *piTable, int flags){
+SQLITE_PRIVATE int tdsqlite3BtreeCreateTable(Btree *p, int *piTable, int flags){
int rc;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
rc = btreeCreateTable(p, piTable, flags);
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -69764,9 +76940,9 @@ static int clearDatabasePage(
unsigned char *pCell;
int i;
int hdr;
- u16 szCell;
+ CellInfo info;
- assert( sqlite3_mutex_held(pBt->mutex) );
+ assert( tdsqlite3_mutex_held(pBt->mutex) );
if( pgno>btreePagecount(pBt) ){
return SQLITE_CORRUPT_BKPT;
}
@@ -69784,7 +76960,7 @@ static int clearDatabasePage(
rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange);
if( rc ) goto cleardatabasepage_out;
}
- rc = clearCell(pPage, pCell, &szCell);
+ rc = clearCell(pPage, pCell, &info);
if( rc ) goto cleardatabasepage_out;
}
if( !pPage->leaf ){
@@ -69797,7 +76973,7 @@ static int clearDatabasePage(
}
if( freePageFlag ){
freePage(pPage, &rc);
- }else if( (rc = sqlite3PagerWrite(pPage->pDbPage))==0 ){
+ }else if( (rc = tdsqlite3PagerWrite(pPage->pDbPage))==0 ){
zeroPage(pPage, pPage->aData[hdr] | PTF_LEAF);
}
@@ -69820,10 +76996,10 @@ cleardatabasepage_out:
** integer value pointed to by pnChange is incremented by the number of
** entries in the table.
*/
-SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){
+SQLITE_PRIVATE int tdsqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){
int rc;
BtShared *pBt = p->pBt;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
assert( p->inTrans==TRANS_WRITE );
rc = saveAllCursors(pBt, (Pgno)iTable, 0);
@@ -69832,10 +77008,10 @@ SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){
/* Invalidate all incrblob cursors open on table iTable (assuming iTable
** is the root of a table b-tree - if it is not, the following call is
** a no-op). */
- invalidateIncrblobCursors(p, 0, 1);
+ invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1);
rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange);
}
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -69844,8 +77020,8 @@ SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){
**
** This routine only work for pCur on an ephemeral table.
*/
-SQLITE_PRIVATE int sqlite3BtreeClearTableOfCursor(BtCursor *pCur){
- return sqlite3BtreeClearTable(pCur->pBtree, pCur->pgnoRoot, 0);
+SQLITE_PRIVATE int tdsqlite3BtreeClearTableOfCursor(BtCursor *pCur){
+ return tdsqlite3BtreeClearTable(pCur->pBtree, pCur->pgnoRoot, 0);
}
/*
@@ -69873,33 +77049,16 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){
MemPage *pPage = 0;
BtShared *pBt = p->pBt;
- assert( sqlite3BtreeHoldsMutex(p) );
+ assert( tdsqlite3BtreeHoldsMutex(p) );
assert( p->inTrans==TRANS_WRITE );
-
- /* It is illegal to drop a table if any cursors are open on the
- ** database. This is because in auto-vacuum mode the backend may
- ** need to move another root-page to fill a gap left by the deleted
- ** root page. If an open cursor was using this page a problem would
- ** occur.
- **
- ** This error is caught long before control reaches this point.
- */
- if( NEVER(pBt->pCursor) ){
- sqlite3ConnectionBlocked(p->db, pBt->pCursor->pBtree->db);
- return SQLITE_LOCKED_SHAREDCACHE;
- }
-
- /*
- ** It is illegal to drop the sqlite_master table on page 1. But again,
- ** this error is caught long before reaching this point.
- */
- if( NEVER(iTable<2) ){
+ assert( iTable>=2 );
+ if( iTable>btreePagecount(pBt) ){
return SQLITE_CORRUPT_BKPT;
}
rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0);
if( rc ) return rc;
- rc = sqlite3BtreeClearTable(p, iTable, 0);
+ rc = tdsqlite3BtreeClearTable(p, iTable, 0);
if( rc ){
releasePage(pPage);
return rc;
@@ -69913,7 +77072,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){
#else
if( pBt->autoVacuum ){
Pgno maxRootPgno;
- sqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &maxRootPgno);
+ tdsqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &maxRootPgno);
if( iTable==maxRootPgno ){
/* If the table being dropped is the table with the largest root-page
@@ -69962,7 +77121,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){
}
assert( maxRootPgno!=PENDING_BYTE_PAGE(pBt) );
- rc = sqlite3BtreeUpdateMeta(p, 4, maxRootPgno);
+ rc = tdsqlite3BtreeUpdateMeta(p, 4, maxRootPgno);
}else{
freePage(pPage, &rc);
releasePage(pPage);
@@ -69970,11 +77129,11 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){
#endif
return rc;
}
-SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){
+SQLITE_PRIVATE int tdsqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){
int rc;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
rc = btreeDropTable(p, iTable, piMoved);
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -69999,17 +77158,17 @@ SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){
** pattern is the same as header meta values, and so it is convenient to
** read it from this routine.
*/
-SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){
+SQLITE_PRIVATE void tdsqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){
BtShared *pBt = p->pBt;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
assert( p->inTrans>TRANS_NONE );
assert( SQLITE_OK==querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK) );
assert( pBt->pPage1 );
assert( idx>=0 && idx<=15 );
if( idx==BTREE_DATA_VERSION ){
- *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iDataVersion;
+ *pMeta = tdsqlite3PagerDataVersion(pBt->pPager) + p->iDataVersion;
}else{
*pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]);
}
@@ -70022,23 +77181,23 @@ SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){
}
#endif
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
}
/*
** Write meta-information back into the database. Meta[0] is
** read-only and may not be written.
*/
-SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){
+SQLITE_PRIVATE int tdsqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){
BtShared *pBt = p->pBt;
unsigned char *pP1;
int rc;
assert( idx>=1 && idx<=15 );
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
assert( p->inTrans==TRANS_WRITE );
assert( pBt->pPage1!=0 );
pP1 = pBt->pPage1->aData;
- rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
+ rc = tdsqlite3PagerWrite(pBt->pPage1->pDbPage);
if( rc==SQLITE_OK ){
put4byte(&pP1[36 + idx*4], iMeta);
#ifndef SQLITE_OMIT_AUTOVACUUM
@@ -70049,7 +77208,7 @@ SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){
}
#endif
}
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -70062,20 +77221,20 @@ SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){
** Otherwise, if an error is encountered (i.e. an IO error or database
** corruption) an SQLite error code is returned.
*/
-SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){
+SQLITE_PRIVATE int tdsqlite3BtreeCount(tdsqlite3 *db, BtCursor *pCur, i64 *pnEntry){
i64 nEntry = 0; /* Value to return in *pnEntry */
int rc; /* Return code */
- if( pCur->pgnoRoot==0 ){
+ rc = moveToRoot(pCur);
+ if( rc==SQLITE_EMPTY ){
*pnEntry = 0;
return SQLITE_OK;
}
- rc = moveToRoot(pCur);
/* Unless an error occurs, the following loop runs one iteration for each
** page in the B-Tree structure (not including overflow pages).
*/
- while( rc==SQLITE_OK ){
+ while( rc==SQLITE_OK && !db->u1.isInterrupted ){
int iIdx; /* Index of child node in parent */
MemPage *pPage; /* Current page of the b-tree */
@@ -70083,7 +77242,7 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){
** this page contains countable entries. Increment the entry counter
** accordingly.
*/
- pPage = pCur->apPage[pCur->iPage];
+ pPage = pCur->pPage;
if( pPage->leaf || !pPage->intKey ){
nEntry += pPage->nCell;
}
@@ -70106,16 +77265,16 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){
return moveToRoot(pCur);
}
moveToParent(pCur);
- }while ( pCur->aiIdx[pCur->iPage]>=pCur->apPage[pCur->iPage]->nCell );
+ }while ( pCur->ix>=pCur->pPage->nCell );
- pCur->aiIdx[pCur->iPage]++;
- pPage = pCur->apPage[pCur->iPage];
+ pCur->ix++;
+ pPage = pCur->pPage;
}
/* Descend to the child node of the cell that the cursor currently
** points at. This is the right-child if (iIdx==pPage->nCell).
*/
- iIdx = pCur->aiIdx[pCur->iPage];
+ iIdx = pCur->ix;
if( iIdx==pPage->nCell ){
rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8]));
}else{
@@ -70132,7 +77291,7 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){
** Return the pager associated with a BTree. This routine is used for
** testing and debugging only.
*/
-SQLITE_PRIVATE Pager *sqlite3BtreePager(Btree *p){
+SQLITE_PRIVATE Pager *tdsqlite3BtreePager(Btree *p){
return p->pBt->pPager;
}
@@ -70151,14 +77310,14 @@ static void checkAppendMsg(
pCheck->nErr++;
va_start(ap, zFormat);
if( pCheck->errMsg.nChar ){
- sqlite3StrAccumAppend(&pCheck->errMsg, "\n", 1);
+ tdsqlite3_str_append(&pCheck->errMsg, "\n", 1);
}
if( pCheck->zPfx ){
- sqlite3XPrintf(&pCheck->errMsg, pCheck->zPfx, pCheck->v1, pCheck->v2);
+ tdsqlite3_str_appendf(&pCheck->errMsg, pCheck->zPfx, pCheck->v1, pCheck->v2);
}
- sqlite3VXPrintf(&pCheck->errMsg, zFormat, ap);
+ tdsqlite3_str_vappendf(&pCheck->errMsg, zFormat, ap);
va_end(ap);
- if( pCheck->errMsg.accError==STRACCUM_NOMEM ){
+ if( pCheck->errMsg.accError==SQLITE_NOMEM ){
pCheck->mallocFailed = 1;
}
}
@@ -70193,8 +77352,7 @@ static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){
** Also check that the page number is in bounds.
*/
static int checkRef(IntegrityCk *pCheck, Pgno iPage){
- if( iPage==0 ) return 1;
- if( iPage>pCheck->nPage ){
+ if( iPage>pCheck->nPage || iPage==0 ){
checkAppendMsg(pCheck, "invalid page number %d", iPage);
return 1;
}
@@ -70202,6 +77360,7 @@ static int checkRef(IntegrityCk *pCheck, Pgno iPage){
checkAppendMsg(pCheck, "2nd reference to page %d", iPage);
return 1;
}
+ if( pCheck->db->u1.isInterrupted ) return 1;
setPageReferenced(pCheck, iPage);
return 0;
}
@@ -70245,39 +77404,34 @@ static void checkList(
IntegrityCk *pCheck, /* Integrity checking context */
int isFreeList, /* True for a freelist. False for overflow page list */
int iPage, /* Page number for first page in the list */
- int N /* Expected number of pages in the list */
+ u32 N /* Expected number of pages in the list */
){
int i;
- int expected = N;
- int iFirst = iPage;
- while( N-- > 0 && pCheck->mxErr ){
+ u32 expected = N;
+ int nErrAtStart = pCheck->nErr;
+ while( iPage!=0 && pCheck->mxErr ){
DbPage *pOvflPage;
unsigned char *pOvflData;
- if( iPage<1 ){
- checkAppendMsg(pCheck,
- "%d of %d pages missing from overflow list starting at %d",
- N+1, expected, iFirst);
- break;
- }
if( checkRef(pCheck, iPage) ) break;
- if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage, 0) ){
+ N--;
+ if( tdsqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage, 0) ){
checkAppendMsg(pCheck, "failed to get page %d", iPage);
break;
}
- pOvflData = (unsigned char *)sqlite3PagerGetData(pOvflPage);
+ pOvflData = (unsigned char *)tdsqlite3PagerGetData(pOvflPage);
if( isFreeList ){
- int n = get4byte(&pOvflData[4]);
+ u32 n = (u32)get4byte(&pOvflData[4]);
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pCheck->pBt->autoVacuum ){
checkPtrmap(pCheck, iPage, PTRMAP_FREEPAGE, 0);
}
#endif
- if( n>(int)pCheck->pBt->usableSize/4-2 ){
+ if( n>pCheck->pBt->usableSize/4-2 ){
checkAppendMsg(pCheck,
"freelist leaf count too big on page %d", iPage);
N--;
}else{
- for(i=0; i<n; i++){
+ for(i=0; i<(int)n; i++){
Pgno iFreePage = get4byte(&pOvflData[8+i*4]);
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pCheck->pBt->autoVacuum ){
@@ -70302,11 +77456,13 @@ static void checkList(
}
#endif
iPage = get4byte(pOvflData);
- sqlite3PagerUnref(pOvflPage);
-
- if( isFreeList && N<(iPage!=0) ){
- checkAppendMsg(pCheck, "free-page count in header is too small");
- }
+ tdsqlite3PagerUnref(pOvflPage);
+ }
+ if( N && nErrAtStart==pCheck->nErr ){
+ checkAppendMsg(pCheck,
+ "%s is %d but should be %d",
+ isFreeList ? "size" : "overflow list length",
+ expected-N, expected);
}
}
#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
@@ -70433,6 +77589,11 @@ static int checkTreePage(
"btreeInitPage() returns error code %d", rc);
goto end_of_check;
}
+ if( (rc = btreeComputeFreeSpace(pPage))!=0 ){
+ assert( rc==SQLITE_CORRUPT );
+ checkAppendMsg(pCheck, "free space corruption", rc);
+ goto end_of_check;
+ }
data = pPage->aData;
hdr = pPage->hdrOffset;
@@ -70500,11 +77661,12 @@ static int checkTreePage(
checkAppendMsg(pCheck, "Rowid %lld out of order", info.nKey);
}
maxKey = info.nKey;
+ keyCanBeEqual = 0; /* Only the first key on the page may ==maxKey */
}
/* Check the content overflow list */
if( info.nPayload>info.nLocal ){
- int nPage; /* Number of pages on the overflow chain */
+ u32 nPage; /* Number of pages on the overflow chain */
Pgno pgnoOvfl; /* First page of the overflow chain */
assert( pc + info.nSize - 4 <= usableSize );
nPage = (info.nPayload - info.nLocal + usableSize - 5)/(usableSize - 4);
@@ -70564,9 +77726,9 @@ static int checkTreePage(
i = get2byte(&data[hdr+1]);
while( i>0 ){
int size, j;
- assert( (u32)i<=usableSize-4 ); /* Enforced by btreeInitPage() */
+ assert( (u32)i<=usableSize-4 ); /* Enforced by btreeComputeFreeSpace() */
size = get2byte(&data[i+2]);
- assert( (u32)(i+size)<=usableSize ); /* Enforced by btreeInitPage() */
+ assert( (u32)(i+size)<=usableSize ); /* due to btreeComputeFreeSpace() */
btreeHeapInsert(heap, (((u32)i)<<16)|(i+size-1));
/* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a
** big-endian integer which is the offset in the b-tree page of the next
@@ -70575,8 +77737,8 @@ static int checkTreePage(
j = get2byte(&data[i]);
/* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of
** increasing offset. */
- assert( j==0 || j>i+size ); /* Enforced by btreeInitPage() */
- assert( (u32)j<=usableSize-4 ); /* Enforced by btreeInitPage() */
+ assert( j==0 || j>i+size ); /* Enforced by btreeComputeFreeSpace() */
+ assert( (u32)j<=usableSize-4 ); /* Enforced by btreeComputeFreeSpace() */
i = j;
}
/* Analyze the min-heap looking for overlap between cells and/or
@@ -70641,7 +77803,8 @@ end_of_check:
** malloc is returned if *pnErr is non-zero. If *pnErr==0 then NULL is
** returned. If a memory allocation error occurs, NULL is returned.
*/
-SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(
+SQLITE_PRIVATE char *tdsqlite3BtreeIntegrityCheck(
+ tdsqlite3 *db, /* Database connection that is running the check */
Btree *p, /* The btree to be checked */
int *aRoot, /* An array of root pages numbers for individual trees */
int nRoot, /* Number of entries in aRoot[] */
@@ -70651,14 +77814,15 @@ SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(
Pgno i;
IntegrityCk sCheck;
BtShared *pBt = p->pBt;
- int savedDbFlags = pBt->db->flags;
+ u64 savedDbFlags = pBt->db->flags;
char zErr[100];
VVA_ONLY( int nRef );
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE );
- VVA_ONLY( nRef = sqlite3PagerRefcount(pBt->pPager) );
+ VVA_ONLY( nRef = tdsqlite3PagerRefcount(pBt->pPager) );
assert( nRef>=0 );
+ sCheck.db = db;
sCheck.pBt = pBt;
sCheck.pPager = pBt->pPager;
sCheck.nPage = btreePagecount(sCheck.pBt);
@@ -70670,18 +77834,18 @@ SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(
sCheck.v2 = 0;
sCheck.aPgRef = 0;
sCheck.heap = 0;
- sqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH);
+ tdsqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH);
sCheck.errMsg.printfFlags = SQLITE_PRINTF_INTERNAL;
if( sCheck.nPage==0 ){
goto integrity_ck_cleanup;
}
- sCheck.aPgRef = sqlite3MallocZero((sCheck.nPage / 8)+ 1);
+ sCheck.aPgRef = tdsqlite3MallocZero((sCheck.nPage / 8)+ 1);
if( !sCheck.aPgRef ){
sCheck.mallocFailed = 1;
goto integrity_ck_cleanup;
}
- sCheck.heap = (u32*)sqlite3PageMalloc( pBt->pageSize );
+ sCheck.heap = (u32*)tdsqlite3PageMalloc( pBt->pageSize );
if( sCheck.heap==0 ){
sCheck.mallocFailed = 1;
goto integrity_ck_cleanup;
@@ -70699,8 +77863,26 @@ SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(
/* Check all the tables.
*/
+#ifndef SQLITE_OMIT_AUTOVACUUM
+ if( pBt->autoVacuum ){
+ int mx = 0;
+ int mxInHdr;
+ for(i=0; (int)i<nRoot; i++) if( mx<aRoot[i] ) mx = aRoot[i];
+ mxInHdr = get4byte(&pBt->pPage1->aData[52]);
+ if( mx!=mxInHdr ){
+ checkAppendMsg(&sCheck,
+ "max rootpage (%d) disagrees with header (%d)",
+ mx, mxInHdr
+ );
+ }
+ }else if( get4byte(&pBt->pPage1->aData[64])!=0 ){
+ checkAppendMsg(&sCheck,
+ "incremental_vacuum enabled with a max rootpage of zero"
+ );
+ }
+#endif
testcase( pBt->db->flags & SQLITE_CellSizeCk );
- pBt->db->flags &= ~SQLITE_CellSizeCk;
+ pBt->db->flags &= ~(u64)SQLITE_CellSizeCk;
for(i=0; (int)i<nRoot && sCheck.mxErr; i++){
i64 notUsed;
if( aRoot[i]==0 ) continue;
@@ -70738,18 +77920,18 @@ SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(
/* Clean up and report errors.
*/
integrity_ck_cleanup:
- sqlite3PageFree(sCheck.heap);
- sqlite3_free(sCheck.aPgRef);
+ tdsqlite3PageFree(sCheck.heap);
+ tdsqlite3_free(sCheck.aPgRef);
if( sCheck.mallocFailed ){
- sqlite3StrAccumReset(&sCheck.errMsg);
+ tdsqlite3_str_reset(&sCheck.errMsg);
sCheck.nErr++;
}
*pnErr = sCheck.nErr;
- if( sCheck.nErr==0 ) sqlite3StrAccumReset(&sCheck.errMsg);
+ if( sCheck.nErr==0 ) tdsqlite3_str_reset(&sCheck.errMsg);
/* Make sure this analysis did not leave any unref() pages. */
- assert( nRef==sqlite3PagerRefcount(pBt->pPager) );
- sqlite3BtreeLeave(p);
- return sqlite3StrAccumFinish(&sCheck.errMsg);
+ assert( nRef==tdsqlite3PagerRefcount(pBt->pPager) );
+ tdsqlite3BtreeLeave(p);
+ return tdsqlite3StrAccumFinish(&sCheck.errMsg);
}
#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
@@ -70760,9 +77942,9 @@ integrity_ck_cleanup:
** The pager filename is invariant as long as the pager is
** open so it is safe to access without the BtShared mutex.
*/
-SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *p){
+SQLITE_PRIVATE const char *tdsqlite3BtreeGetFilename(Btree *p){
assert( p->pBt->pPager!=0 );
- return sqlite3PagerFilename(p->pBt->pPager, 1);
+ return tdsqlite3PagerFilename(p->pBt->pPager, 1);
}
/*
@@ -70773,16 +77955,16 @@ SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *p){
** The pager journal filename is invariant as long as the pager is
** open so it is safe to access without the BtShared mutex.
*/
-SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *p){
+SQLITE_PRIVATE const char *tdsqlite3BtreeGetJournalname(Btree *p){
assert( p->pBt->pPager!=0 );
- return sqlite3PagerJournalname(p->pBt->pPager);
+ return tdsqlite3PagerJournalname(p->pBt->pPager);
}
/*
** Return non-zero if a transaction is active.
*/
-SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree *p){
- assert( p==0 || sqlite3_mutex_held(p->db->mutex) );
+SQLITE_PRIVATE int tdsqlite3BtreeIsInTrans(Btree *p){
+ assert( p==0 || tdsqlite3_mutex_held(p->db->mutex) );
return (p && (p->inTrans==TRANS_WRITE));
}
@@ -70795,17 +77977,17 @@ SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree *p){
**
** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART.
*/
-SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree *p, int eMode, int *pnLog, int *pnCkpt){
+SQLITE_PRIVATE int tdsqlite3BtreeCheckpoint(Btree *p, int eMode, int *pnLog, int *pnCkpt){
int rc = SQLITE_OK;
if( p ){
BtShared *pBt = p->pBt;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
if( pBt->inTransaction!=TRANS_NONE ){
rc = SQLITE_LOCKED;
}else{
- rc = sqlite3PagerCheckpoint(pBt->pPager, eMode, pnLog, pnCkpt);
+ rc = tdsqlite3PagerCheckpoint(pBt->pPager, p->db, eMode, pnLog, pnCkpt);
}
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
}
return rc;
}
@@ -70814,15 +77996,15 @@ SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree *p, int eMode, int *pnLog, int *
/*
** Return non-zero if a read (or write) transaction is active.
*/
-SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeIsInReadTrans(Btree *p){
assert( p );
- assert( sqlite3_mutex_held(p->db->mutex) );
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
return p->inTrans!=TRANS_NONE;
}
-SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeIsInBackup(Btree *p){
assert( p );
- assert( sqlite3_mutex_held(p->db->mutex) );
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
return p->nBackup!=0;
}
@@ -70843,17 +78025,17 @@ SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree *p){
**
** Just before the shared-btree is closed, the function passed as the
** xFree argument when the memory allocation was made is invoked on the
-** blob of allocated memory. The xFree function should not call sqlite3_free()
+** blob of allocated memory. The xFree function should not call tdsqlite3_free()
** on the memory, the btree layer does that.
*/
-SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){
+SQLITE_PRIVATE void *tdsqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){
BtShared *pBt = p->pBt;
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
if( !pBt->pSchema && nBytes ){
- pBt->pSchema = sqlite3DbMallocZero(0, nBytes);
+ pBt->pSchema = tdsqlite3DbMallocZero(0, nBytes);
pBt->xFreeSchema = xFree;
}
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return pBt->pSchema;
}
@@ -70862,13 +78044,13 @@ SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void
** btree as the argument handle holds an exclusive lock on the
** sqlite_master table. Otherwise SQLITE_OK.
*/
-SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeSchemaLocked(Btree *p){
int rc;
- assert( sqlite3_mutex_held(p->db->mutex) );
- sqlite3BtreeEnter(p);
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
+ tdsqlite3BtreeEnter(p);
rc = querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK);
assert( rc==SQLITE_OK || rc==SQLITE_LOCKED_SHAREDCACHE );
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
return rc;
}
@@ -70879,7 +78061,7 @@ SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *p){
** lock is a write lock if isWritelock is true or a read lock
** if it is false.
*/
-SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){
+SQLITE_PRIVATE int tdsqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){
int rc = SQLITE_OK;
assert( p->inTrans!=TRANS_NONE );
if( p->sharable ){
@@ -70887,12 +78069,12 @@ SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){
assert( READ_LOCK+1==WRITE_LOCK );
assert( isWriteLock==0 || isWriteLock==1 );
- sqlite3BtreeEnter(p);
+ tdsqlite3BtreeEnter(p);
rc = querySharedCacheTableLock(p, iTab, lockType);
if( rc==SQLITE_OK ){
rc = setSharedCacheTableLock(p, iTab, lockType);
}
- sqlite3BtreeLeave(p);
+ tdsqlite3BtreeLeave(p);
}
return rc;
}
@@ -70909,10 +78091,10 @@ SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){
** parameters that attempt to write past the end of the existing data,
** no modifications are made and SQLITE_CORRUPT is returned.
*/
-SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){
+SQLITE_PRIVATE int tdsqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){
int rc;
assert( cursorOwnsBtShared(pCsr) );
- assert( sqlite3_mutex_held(pCsr->pBtree->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCsr->pBtree->db->mutex) );
assert( pCsr->curFlags & BTCF_Incrblob );
rc = restoreCursorPosition(pCsr);
@@ -70949,7 +78131,7 @@ SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void
&& pCsr->pBt->inTransaction==TRANS_WRITE );
assert( hasSharedCacheTableLock(pCsr->pBtree, pCsr->pgnoRoot, 0, 2) );
assert( !hasReadConflicts(pCsr->pBtree, pCsr->pgnoRoot) );
- assert( pCsr->apPage[pCsr->iPage]->intKey );
+ assert( pCsr->pPage->intKey );
return accessPayload(pCsr, offset, amt, (unsigned char *)z, 1);
}
@@ -70957,7 +78139,7 @@ SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void
/*
** Mark this cursor as an incremental blob cursor.
*/
-SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *pCur){
+SQLITE_PRIVATE void tdsqlite3BtreeIncrblobCursor(BtCursor *pCur){
pCur->curFlags |= BTCF_Incrblob;
pCur->pBtree->hasIncrblobCur = 1;
}
@@ -70968,7 +78150,7 @@ SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *pCur){
** "write version" (single byte at byte offset 19) fields in the database
** header to iVersion.
*/
-SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){
+SQLITE_PRIVATE int tdsqlite3BtreeSetVersion(Btree *pBtree, int iVersion){
BtShared *pBt = pBtree->pBt;
int rc; /* Return code */
@@ -70980,13 +78162,13 @@ SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){
pBt->btsFlags &= ~BTS_NO_WAL;
if( iVersion==1 ) pBt->btsFlags |= BTS_NO_WAL;
- rc = sqlite3BtreeBeginTrans(pBtree, 0);
+ rc = tdsqlite3BtreeBeginTrans(pBtree, 0, 0);
if( rc==SQLITE_OK ){
u8 *aData = pBt->pPage1->aData;
if( aData[18]!=(u8)iVersion || aData[19]!=(u8)iVersion ){
- rc = sqlite3BtreeBeginTrans(pBtree, 2);
+ rc = tdsqlite3BtreeBeginTrans(pBtree, 2, 0);
if( rc==SQLITE_OK ){
- rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
+ rc = tdsqlite3PagerWrite(pBt->pPage1->pDbPage);
if( rc==SQLITE_OK ){
aData[18] = (u8)iVersion;
aData[19] = (u8)iVersion;
@@ -71003,27 +78185,27 @@ SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){
** Return true if the cursor has a hint specified. This routine is
** only used from within assert() statements
*/
-SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor *pCsr, unsigned int mask){
+SQLITE_PRIVATE int tdsqlite3BtreeCursorHasHint(BtCursor *pCsr, unsigned int mask){
return (pCsr->hints & mask)!=0;
}
/*
** Return true if the given Btree is read-only.
*/
-SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeIsReadonly(Btree *p){
return (p->pBt->btsFlags & BTS_READ_ONLY)!=0;
}
/*
** Return the size of the header added to each page by this module.
*/
-SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }
+SQLITE_PRIVATE int tdsqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }
#if !defined(SQLITE_OMIT_SHARED_CACHE)
/*
** Return true if the Btree passed as the only argument is sharable.
*/
-SQLITE_PRIVATE int sqlite3BtreeSharable(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeSharable(Btree *p){
return p->sharable;
}
@@ -71032,7 +78214,7 @@ SQLITE_PRIVATE int sqlite3BtreeSharable(Btree *p){
** the Btree handle passed as the only argument. For private caches
** this is always 1. For shared caches it may be 1 or greater.
*/
-SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree *p){
+SQLITE_PRIVATE int tdsqlite3BtreeConnectionCount(Btree *p){
testcase( p->sharable );
return p->pBt->nRef;
}
@@ -71051,7 +78233,7 @@ SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree *p){
** May you share freely, never taking more than you give.
**
*************************************************************************
-** This file contains the implementation of the sqlite3_backup_XXX()
+** This file contains the implementation of the tdsqlite3_backup_XXX()
** API functions and the related features.
*/
/* #include "sqliteInt.h" */
@@ -71060,14 +78242,14 @@ SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree *p){
/*
** Structure allocated for each backup operation.
*/
-struct sqlite3_backup {
- sqlite3* pDestDb; /* Destination database handle */
+struct tdsqlite3_backup {
+ tdsqlite3* pDestDb; /* Destination database handle */
Btree *pDest; /* Destination b-tree file */
u32 iDestSchema; /* Original schema cookie in destination */
int bDestLocked; /* True once a write-transaction is open on pDest */
Pgno iNext; /* Page number of the next source page to copy */
- sqlite3* pSrcDb; /* Source database handle */
+ tdsqlite3* pSrcDb; /* Source database handle */
Btree *pSrc; /* Source b-tree file */
int rc; /* Backup process error code */
@@ -71079,16 +78261,16 @@ struct sqlite3_backup {
Pgno nPagecount; /* Total number of pages to copy */
int isAttached; /* True once backup has been registered with pager */
- sqlite3_backup *pNext; /* Next backup associated with source pager */
+ tdsqlite3_backup *pNext; /* Next backup associated with source pager */
};
/*
** THREAD SAFETY NOTES:
**
-** Once it has been created using backup_init(), a single sqlite3_backup
+** Once it has been created using backup_init(), a single tdsqlite3_backup
** structure may be accessed via two groups of thread-safe entry points:
**
-** * Via the sqlite3_backup_XXX() API function backup_step() and
+** * Via the tdsqlite3_backup_XXX() API function backup_step() and
** backup_finish(). Both these functions obtain the source database
** handle mutex and the mutex associated with the source BtShared
** structure, in that order.
@@ -71099,7 +78281,7 @@ struct sqlite3_backup {
** associated with the source database BtShared structure will always
** be held when either of these functions are invoked.
**
-** The other sqlite3_backup_XXX() API functions, backup_remaining() and
+** The other tdsqlite3_backup_XXX() API functions, backup_remaining() and
** backup_pagecount() are not thread-safe functions. If they are called
** while some other thread is calling backup_step() or backup_finish(),
** the values returned may be invalid. There is no way for a call to
@@ -71121,27 +78303,27 @@ struct sqlite3_backup {
** function. If an error occurs while doing so, return 0 and write an
** error message to pErrorDb.
*/
-static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){
- int i = sqlite3FindDbName(pDb, zDb);
+static Btree *findBtree(tdsqlite3 *pErrorDb, tdsqlite3 *pDb, const char *zDb){
+ int i = tdsqlite3FindDbName(pDb, zDb);
if( i==1 ){
Parse sParse;
int rc = 0;
memset(&sParse, 0, sizeof(sParse));
sParse.db = pDb;
- if( sqlite3OpenTempDatabase(&sParse) ){
- sqlite3ErrorWithMsg(pErrorDb, sParse.rc, "%s", sParse.zErrMsg);
+ if( tdsqlite3OpenTempDatabase(&sParse) ){
+ tdsqlite3ErrorWithMsg(pErrorDb, sParse.rc, "%s", sParse.zErrMsg);
rc = SQLITE_ERROR;
}
- sqlite3DbFree(pErrorDb, sParse.zErrMsg);
- sqlite3ParserReset(&sParse);
+ tdsqlite3DbFree(pErrorDb, sParse.zErrMsg);
+ tdsqlite3ParserReset(&sParse);
if( rc ){
return 0;
}
}
if( i<0 ){
- sqlite3ErrorWithMsg(pErrorDb, SQLITE_ERROR, "unknown database %s", zDb);
+ tdsqlite3ErrorWithMsg(pErrorDb, SQLITE_ERROR, "unknown database %s", zDb);
return 0;
}
@@ -71152,9 +78334,9 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){
** Attempt to set the page size of the destination to match the page size
** of the source.
*/
-static int setDestPgsz(sqlite3_backup *p){
+static int setDestPgsz(tdsqlite3_backup *p){
int rc;
- rc = sqlite3BtreeSetPageSize(p->pDest,sqlite3BtreeGetPageSize(p->pSrc),-1,0);
+ rc = tdsqlite3BtreeSetPageSize(p->pDest,tdsqlite3BtreeGetPageSize(p->pSrc),-1,0);
return rc;
}
@@ -71164,61 +78346,80 @@ static int setDestPgsz(sqlite3_backup *p){
** is an open read-transaction, return SQLITE_ERROR and leave an error
** message in database handle db.
*/
-static int checkReadTransaction(sqlite3 *db, Btree *p){
- if( sqlite3BtreeIsInReadTrans(p) ){
- sqlite3ErrorWithMsg(db, SQLITE_ERROR, "destination database is in use");
+static int checkReadTransaction(tdsqlite3 *db, Btree *p){
+ if( tdsqlite3BtreeIsInReadTrans(p) ){
+ tdsqlite3ErrorWithMsg(db, SQLITE_ERROR, "destination database is in use");
return SQLITE_ERROR;
}
return SQLITE_OK;
}
/*
-** Create an sqlite3_backup process to copy the contents of zSrcDb from
+** Create an tdsqlite3_backup process to copy the contents of zSrcDb from
** connection handle pSrcDb to zDestDb in pDestDb. If successful, return
-** a pointer to the new sqlite3_backup object.
+** a pointer to the new tdsqlite3_backup object.
**
** If an error occurs, NULL is returned and an error code and error message
** stored in database handle pDestDb.
*/
-SQLITE_API sqlite3_backup *sqlite3_backup_init(
- sqlite3* pDestDb, /* Database to write to */
+SQLITE_API tdsqlite3_backup *tdsqlite3_backup_init(
+ tdsqlite3* pDestDb, /* Database to write to */
const char *zDestDb, /* Name of database within pDestDb */
- sqlite3* pSrcDb, /* Database connection to read from */
+ tdsqlite3* pSrcDb, /* Database connection to read from */
const char *zSrcDb /* Name of database within pSrcDb */
){
- sqlite3_backup *p; /* Value to return */
+ tdsqlite3_backup *p; /* Value to return */
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(pSrcDb)||!sqlite3SafetyCheckOk(pDestDb) ){
+ if( !tdsqlite3SafetyCheckOk(pSrcDb)||!tdsqlite3SafetyCheckOk(pDestDb) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
+/* BEGIN SQLCIPHER */
+#ifdef SQLITE_HAS_CODEC
+ {
+ int srcNKey, destNKey;
+ void *zKey;
+
+ tdsqlite3CodecGetKey(pSrcDb, sqlcipher_find_db_index(pSrcDb, zSrcDb), &zKey, &srcNKey);
+ tdsqlite3CodecGetKey(pDestDb, sqlcipher_find_db_index(pDestDb, zDestDb), &zKey, &destNKey);
+ zKey = NULL;
+
+ /* either both databases must be plaintext, or both must be encrypted */
+ if((srcNKey == 0 && destNKey > 0) || (srcNKey > 0 && destNKey == 0)) {
+ tdsqlite3ErrorWithMsg(pDestDb, SQLITE_ERROR, "backup is not supported with encrypted databases");
+ return NULL;
+ }
+ }
+#endif
+/* END SQLCIPHER */
+
/* Lock the source database handle. The destination database
** handle is not locked in this routine, but it is locked in
- ** sqlite3_backup_step(). The user is required to ensure that no
+ ** tdsqlite3_backup_step(). The user is required to ensure that no
** other thread accesses the destination handle for the duration
** of the backup operation. Any attempt to use the destination
** database connection while a backup is in progress may cause
** a malfunction or a deadlock.
*/
- sqlite3_mutex_enter(pSrcDb->mutex);
- sqlite3_mutex_enter(pDestDb->mutex);
+ tdsqlite3_mutex_enter(pSrcDb->mutex);
+ tdsqlite3_mutex_enter(pDestDb->mutex);
if( pSrcDb==pDestDb ){
- sqlite3ErrorWithMsg(
+ tdsqlite3ErrorWithMsg(
pDestDb, SQLITE_ERROR, "source and destination must be distinct"
);
p = 0;
}else {
- /* Allocate space for a new sqlite3_backup object...
- ** EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a
- ** call to sqlite3_backup_init() and is destroyed by a call to
- ** sqlite3_backup_finish(). */
- p = (sqlite3_backup *)sqlite3MallocZero(sizeof(sqlite3_backup));
+ /* Allocate space for a new tdsqlite3_backup object...
+ ** EVIDENCE-OF: R-64852-21591 The tdsqlite3_backup object is created by a
+ ** call to tdsqlite3_backup_init() and is destroyed by a call to
+ ** tdsqlite3_backup_finish(). */
+ p = (tdsqlite3_backup *)tdsqlite3MallocZero(sizeof(tdsqlite3_backup));
if( !p ){
- sqlite3Error(pDestDb, SQLITE_NOMEM_BKPT);
+ tdsqlite3Error(pDestDb, SQLITE_NOMEM_BKPT);
}
}
@@ -71237,9 +78438,9 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init(
/* One (or both) of the named databases did not exist or an OOM
** error was hit. Or there is a transaction open on the destination
** database. The error has already been written into the pDestDb
- ** handle. All that is left to do here is free the sqlite3_backup
+ ** handle. All that is left to do here is free the tdsqlite3_backup
** structure. */
- sqlite3_free(p);
+ tdsqlite3_free(p);
p = 0;
}
}
@@ -71247,8 +78448,8 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init(
p->pSrc->nBackup++;
}
- sqlite3_mutex_leave(pDestDb->mutex);
- sqlite3_mutex_leave(pSrcDb->mutex);
+ tdsqlite3_mutex_leave(pDestDb->mutex);
+ tdsqlite3_mutex_leave(pSrcDb->mutex);
return p;
}
@@ -71267,27 +78468,27 @@ static int isFatalError(int rc){
** destination database.
*/
static int backupOnePage(
- sqlite3_backup *p, /* Backup handle */
+ tdsqlite3_backup *p, /* Backup handle */
Pgno iSrcPg, /* Source database page to backup */
const u8 *zSrcData, /* Source database page data */
int bUpdate /* True for an update, false otherwise */
){
- Pager * const pDestPager = sqlite3BtreePager(p->pDest);
- const int nSrcPgsz = sqlite3BtreeGetPageSize(p->pSrc);
- int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest);
+ Pager * const pDestPager = tdsqlite3BtreePager(p->pDest);
+ const int nSrcPgsz = tdsqlite3BtreeGetPageSize(p->pSrc);
+ int nDestPgsz = tdsqlite3BtreeGetPageSize(p->pDest);
const int nCopy = MIN(nSrcPgsz, nDestPgsz);
const i64 iEnd = (i64)iSrcPg*(i64)nSrcPgsz;
#ifdef SQLITE_HAS_CODEC
/* Use BtreeGetReserveNoMutex() for the source b-tree, as although it is
** guaranteed that the shared-mutex is held by this thread, handle
** p->pSrc may not actually be the owner. */
- int nSrcReserve = sqlite3BtreeGetReserveNoMutex(p->pSrc);
- int nDestReserve = sqlite3BtreeGetOptimalReserve(p->pDest);
+ int nSrcReserve = tdsqlite3BtreeGetReserveNoMutex(p->pSrc);
+ int nDestReserve = tdsqlite3BtreeGetOptimalReserve(p->pDest);
#endif
int rc = SQLITE_OK;
i64 iOff;
- assert( sqlite3BtreeGetReserveNoMutex(p->pSrc)>=0 );
+ assert( tdsqlite3BtreeGetReserveNoMutex(p->pSrc)>=0 );
assert( p->bDestLocked );
assert( !isFatalError(p->rc) );
assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) );
@@ -71296,7 +78497,7 @@ static int backupOnePage(
/* Catch the case where the destination is an in-memory database and the
** page sizes of the source and destination differ.
*/
- if( nSrcPgsz!=nDestPgsz && sqlite3PagerIsMemdb(pDestPager) ){
+ if( nSrcPgsz!=nDestPgsz && tdsqlite3PagerIsMemdb(pDestPager) ){
rc = SQLITE_READONLY;
}
@@ -71304,7 +78505,7 @@ static int backupOnePage(
/* Backup is not possible if the page size of the destination is changing
** and a codec is in use.
*/
- if( nSrcPgsz!=nDestPgsz && sqlite3PagerGetCodec(pDestPager)!=0 ){
+ if( nSrcPgsz!=nDestPgsz && tdsqlite3PagerGetCodec(pDestPager)!=0 ){
rc = SQLITE_READONLY;
}
@@ -71315,8 +78516,8 @@ static int backupOnePage(
*/
if( nSrcReserve!=nDestReserve ){
u32 newPgsz = nSrcPgsz;
- rc = sqlite3PagerSetPagesize(pDestPager, &newPgsz, nSrcReserve);
- if( rc==SQLITE_OK && newPgsz!=nSrcPgsz ) rc = SQLITE_READONLY;
+ rc = tdsqlite3PagerSetPagesize(pDestPager, &newPgsz, nSrcReserve);
+ if( rc==SQLITE_OK && newPgsz!=(u32)nSrcPgsz ) rc = SQLITE_READONLY;
}
#endif
@@ -71328,11 +78529,11 @@ static int backupOnePage(
DbPage *pDestPg = 0;
Pgno iDest = (Pgno)(iOff/nDestPgsz)+1;
if( iDest==PENDING_BYTE_PAGE(p->pDest->pBt) ) continue;
- if( SQLITE_OK==(rc = sqlite3PagerGet(pDestPager, iDest, &pDestPg, 0))
- && SQLITE_OK==(rc = sqlite3PagerWrite(pDestPg))
+ if( SQLITE_OK==(rc = tdsqlite3PagerGet(pDestPager, iDest, &pDestPg, 0))
+ && SQLITE_OK==(rc = tdsqlite3PagerWrite(pDestPg))
){
const u8 *zIn = &zSrcData[iOff%nSrcPgsz];
- u8 *zDestData = sqlite3PagerGetData(pDestPg);
+ u8 *zDestData = tdsqlite3PagerGetData(pDestPg);
u8 *zOut = &zDestData[iOff%nDestPgsz];
/* Copy the data from the source page into the destination page.
@@ -71343,12 +78544,12 @@ static int backupOnePage(
** "MUST BE FIRST" for this purpose.
*/
memcpy(zOut, zIn, nCopy);
- ((u8 *)sqlite3PagerGetExtra(pDestPg))[0] = 0;
+ ((u8 *)tdsqlite3PagerGetExtra(pDestPg))[0] = 0;
if( iOff==0 && bUpdate==0 ){
- sqlite3Put4byte(&zOut[28], sqlite3BtreeLastPage(p->pSrc));
+ tdsqlite3Put4byte(&zOut[28], tdsqlite3BtreeLastPage(p->pSrc));
}
}
- sqlite3PagerUnref(pDestPg);
+ tdsqlite3PagerUnref(pDestPg);
}
return rc;
@@ -71362,11 +78563,11 @@ static int backupOnePage(
** Return SQLITE_OK if everything is successful, or an SQLite error
** code if an error occurs.
*/
-static int backupTruncateFile(sqlite3_file *pFile, i64 iSize){
+static int backupTruncateFile(tdsqlite3_file *pFile, i64 iSize){
i64 iCurrent;
- int rc = sqlite3OsFileSize(pFile, &iCurrent);
+ int rc = tdsqlite3OsFileSize(pFile, &iCurrent);
if( rc==SQLITE_OK && iCurrent>iSize ){
- rc = sqlite3OsTruncate(pFile, iSize);
+ rc = tdsqlite3OsTruncate(pFile, iSize);
}
return rc;
}
@@ -71375,10 +78576,10 @@ static int backupTruncateFile(sqlite3_file *pFile, i64 iSize){
** Register this backup object with the associated source pager for
** callbacks when pages are changed or the cache invalidated.
*/
-static void attachBackupObject(sqlite3_backup *p){
- sqlite3_backup **pp;
- assert( sqlite3BtreeHoldsMutex(p->pSrc) );
- pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc));
+static void attachBackupObject(tdsqlite3_backup *p){
+ tdsqlite3_backup **pp;
+ assert( tdsqlite3BtreeHoldsMutex(p->pSrc) );
+ pp = tdsqlite3PagerBackupPtr(tdsqlite3BtreePager(p->pSrc));
p->pNext = *pp;
*pp = p;
p->isAttached = 1;
@@ -71387,7 +78588,7 @@ static void attachBackupObject(sqlite3_backup *p){
/*
** Copy nPage pages from the source b-tree to the destination.
*/
-SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
+SQLITE_API int tdsqlite3_backup_step(tdsqlite3_backup *p, int nPage){
int rc;
int destMode; /* Destination journal mode */
int pgszSrc = 0; /* Source page size */
@@ -71396,16 +78597,16 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
#ifdef SQLITE_ENABLE_API_ARMOR
if( p==0 ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(p->pSrcDb->mutex);
- sqlite3BtreeEnter(p->pSrc);
+ tdsqlite3_mutex_enter(p->pSrcDb->mutex);
+ tdsqlite3BtreeEnter(p->pSrc);
if( p->pDestDb ){
- sqlite3_mutex_enter(p->pDestDb->mutex);
+ tdsqlite3_mutex_enter(p->pDestDb->mutex);
}
rc = p->rc;
if( !isFatalError(rc) ){
- Pager * const pSrcPager = sqlite3BtreePager(p->pSrc); /* Source pager */
- Pager * const pDestPager = sqlite3BtreePager(p->pDest); /* Dest pager */
+ Pager * const pSrcPager = tdsqlite3BtreePager(p->pSrc); /* Source pager */
+ Pager * const pDestPager = tdsqlite3BtreePager(p->pDest); /* Dest pager */
int ii; /* Iterator variable */
int nSrcPage = -1; /* Size of source db in pages */
int bCloseTrans = 0; /* True if src db requires unlocking */
@@ -71423,8 +78624,8 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
** one now. If a transaction is opened here, then it will be closed
** before this function exits.
*/
- if( rc==SQLITE_OK && 0==sqlite3BtreeIsInReadTrans(p->pSrc) ){
- rc = sqlite3BtreeBeginTrans(p->pSrc, 0);
+ if( rc==SQLITE_OK && 0==tdsqlite3BtreeIsInReadTrans(p->pSrc) ){
+ rc = tdsqlite3BtreeBeginTrans(p->pSrc, 0, 0);
bCloseTrans = 1;
}
@@ -71440,17 +78641,17 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
/* Lock the destination database, if it is not locked already. */
if( SQLITE_OK==rc && p->bDestLocked==0
- && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2))
+ && SQLITE_OK==(rc = tdsqlite3BtreeBeginTrans(p->pDest, 2,
+ (int*)&p->iDestSchema))
){
p->bDestLocked = 1;
- sqlite3BtreeGetMeta(p->pDest, BTREE_SCHEMA_VERSION, &p->iDestSchema);
}
/* Do not allow backup if the destination database is in WAL mode
** and the page sizes are different between source and destination */
- pgszSrc = sqlite3BtreeGetPageSize(p->pSrc);
- pgszDest = sqlite3BtreeGetPageSize(p->pDest);
- destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest));
+ pgszSrc = tdsqlite3BtreeGetPageSize(p->pSrc);
+ pgszDest = tdsqlite3BtreeGetPageSize(p->pDest);
+ destMode = tdsqlite3PagerGetJournalMode(tdsqlite3BtreePager(p->pDest));
if( SQLITE_OK==rc && destMode==PAGER_JOURNALMODE_WAL && pgszSrc!=pgszDest ){
rc = SQLITE_READONLY;
}
@@ -71458,16 +78659,16 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
/* Now that there is a read-lock on the source database, query the
** source pager for the number of pages in the database.
*/
- nSrcPage = (int)sqlite3BtreeLastPage(p->pSrc);
+ nSrcPage = (int)tdsqlite3BtreeLastPage(p->pSrc);
assert( nSrcPage>=0 );
for(ii=0; (nPage<0 || ii<nPage) && p->iNext<=(Pgno)nSrcPage && !rc; ii++){
const Pgno iSrcPg = p->iNext; /* Source page number */
if( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ){
DbPage *pSrcPg; /* Source page object */
- rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg,PAGER_GET_READONLY);
+ rc = tdsqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg,PAGER_GET_READONLY);
if( rc==SQLITE_OK ){
- rc = backupOnePage(p, iSrcPg, sqlite3PagerGetData(pSrcPg), 0);
- sqlite3PagerUnref(pSrcPg);
+ rc = backupOnePage(p, iSrcPg, tdsqlite3PagerGetData(pSrcPg), 0);
+ tdsqlite3PagerUnref(pSrcPg);
}
}
p->iNext++;
@@ -71489,18 +78690,18 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
*/
if( rc==SQLITE_DONE ){
if( nSrcPage==0 ){
- rc = sqlite3BtreeNewDb(p->pDest);
+ rc = tdsqlite3BtreeNewDb(p->pDest);
nSrcPage = 1;
}
if( rc==SQLITE_OK || rc==SQLITE_DONE ){
- rc = sqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1);
+ rc = tdsqlite3BtreeUpdateMeta(p->pDest,1,p->iDestSchema+1);
}
if( rc==SQLITE_OK ){
if( p->pDestDb ){
- sqlite3ResetAllSchemasOfConnection(p->pDestDb);
+ tdsqlite3ResetAllSchemasOfConnection(p->pDestDb);
}
if( destMode==PAGER_JOURNALMODE_WAL ){
- rc = sqlite3BtreeSetVersion(p->pDest, 2);
+ rc = tdsqlite3BtreeSetVersion(p->pDest, 2);
}
}
if( rc==SQLITE_OK ){
@@ -71510,15 +78711,15 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
** size may be different to the source page size.
**
** If the source page size is smaller than the destination page size,
- ** round up. In this case the call to sqlite3OsTruncate() below will
+ ** round up. In this case the call to tdsqlite3OsTruncate() below will
** fix the size of the file. However it is important to call
- ** sqlite3PagerTruncateImage() here so that any pages in the
+ ** tdsqlite3PagerTruncateImage() here so that any pages in the
** destination file that lie beyond the nDestTruncate page mark are
** journalled by PagerCommitPhaseOne() before they are destroyed
** by the file truncation.
*/
- assert( pgszSrc==sqlite3BtreeGetPageSize(p->pSrc) );
- assert( pgszDest==sqlite3BtreeGetPageSize(p->pDest) );
+ assert( pgszSrc==tdsqlite3BtreeGetPageSize(p->pSrc) );
+ assert( pgszDest==tdsqlite3BtreeGetPageSize(p->pDest) );
if( pgszSrc<pgszDest ){
int ratio = pgszDest/pgszSrc;
nDestTruncate = (nSrcPage+ratio-1)/ratio;
@@ -71541,7 +78742,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
** copied into the destination database.
*/
const i64 iSize = (i64)pgszSrc * (i64)nSrcPage;
- sqlite3_file * const pFile = sqlite3PagerFile(pDestPager);
+ tdsqlite3_file * const pFile = tdsqlite3PagerFile(pDestPager);
Pgno iPg;
int nDstPage;
i64 iOff;
@@ -71560,19 +78761,19 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
** the database file in any way, knowing that if a power failure
** occurs, the original database will be reconstructed from the
** journal file. */
- sqlite3PagerPagecount(pDestPager, &nDstPage);
+ tdsqlite3PagerPagecount(pDestPager, &nDstPage);
for(iPg=nDestTruncate; rc==SQLITE_OK && iPg<=(Pgno)nDstPage; iPg++){
if( iPg!=PENDING_BYTE_PAGE(p->pDest->pBt) ){
DbPage *pPg;
- rc = sqlite3PagerGet(pDestPager, iPg, &pPg, 0);
+ rc = tdsqlite3PagerGet(pDestPager, iPg, &pPg, 0);
if( rc==SQLITE_OK ){
- rc = sqlite3PagerWrite(pPg);
- sqlite3PagerUnref(pPg);
+ rc = tdsqlite3PagerWrite(pPg);
+ tdsqlite3PagerUnref(pPg);
}
}
}
if( rc==SQLITE_OK ){
- rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 1);
+ rc = tdsqlite3PagerCommitPhaseOne(pDestPager, 0, 1);
}
/* Write the extra pages and truncate the database file as required */
@@ -71584,12 +78785,12 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
){
PgHdr *pSrcPg = 0;
const Pgno iSrcPg = (Pgno)((iOff/pgszSrc)+1);
- rc = sqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg, 0);
+ rc = tdsqlite3PagerGet(pSrcPager, iSrcPg, &pSrcPg, 0);
if( rc==SQLITE_OK ){
- u8 *zData = sqlite3PagerGetData(pSrcPg);
- rc = sqlite3OsWrite(pFile, zData, pgszSrc, iOff);
+ u8 *zData = tdsqlite3PagerGetData(pSrcPg);
+ rc = tdsqlite3OsWrite(pFile, zData, pgszSrc, iOff);
}
- sqlite3PagerUnref(pSrcPg);
+ tdsqlite3PagerUnref(pSrcPg);
}
if( rc==SQLITE_OK ){
rc = backupTruncateFile(pFile, iSize);
@@ -71597,16 +78798,16 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
/* Sync the database file to disk. */
if( rc==SQLITE_OK ){
- rc = sqlite3PagerSync(pDestPager, 0);
+ rc = tdsqlite3PagerSync(pDestPager, 0);
}
}else{
- sqlite3PagerTruncateImage(pDestPager, nDestTruncate);
- rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0);
+ tdsqlite3PagerTruncateImage(pDestPager, nDestTruncate);
+ rc = tdsqlite3PagerCommitPhaseOne(pDestPager, 0, 0);
}
/* Finish committing the transaction to the destination database. */
if( SQLITE_OK==rc
- && SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest, 0))
+ && SQLITE_OK==(rc = tdsqlite3BtreeCommitPhaseTwo(p->pDest, 0))
){
rc = SQLITE_DONE;
}
@@ -71620,8 +78821,8 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
*/
if( bCloseTrans ){
TESTONLY( int rc2 );
- TESTONLY( rc2 = ) sqlite3BtreeCommitPhaseOne(p->pSrc, 0);
- TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc, 0);
+ TESTONLY( rc2 = ) tdsqlite3BtreeCommitPhaseOne(p->pSrc, 0);
+ TESTONLY( rc2 |= ) tdsqlite3BtreeCommitPhaseTwo(p->pSrc, 0);
assert( rc2==SQLITE_OK );
}
@@ -71631,28 +78832,28 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
p->rc = rc;
}
if( p->pDestDb ){
- sqlite3_mutex_leave(p->pDestDb->mutex);
+ tdsqlite3_mutex_leave(p->pDestDb->mutex);
}
- sqlite3BtreeLeave(p->pSrc);
- sqlite3_mutex_leave(p->pSrcDb->mutex);
+ tdsqlite3BtreeLeave(p->pSrc);
+ tdsqlite3_mutex_leave(p->pSrcDb->mutex);
return rc;
}
/*
-** Release all resources associated with an sqlite3_backup* handle.
+** Release all resources associated with an tdsqlite3_backup* handle.
*/
-SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){
- sqlite3_backup **pp; /* Ptr to head of pagers backup list */
- sqlite3 *pSrcDb; /* Source database connection */
+SQLITE_API int tdsqlite3_backup_finish(tdsqlite3_backup *p){
+ tdsqlite3_backup **pp; /* Ptr to head of pagers backup list */
+ tdsqlite3 *pSrcDb; /* Source database connection */
int rc; /* Value to return */
/* Enter the mutexes */
if( p==0 ) return SQLITE_OK;
pSrcDb = p->pSrcDb;
- sqlite3_mutex_enter(pSrcDb->mutex);
- sqlite3BtreeEnter(p->pSrc);
+ tdsqlite3_mutex_enter(pSrcDb->mutex);
+ tdsqlite3BtreeEnter(p->pSrc);
if( p->pDestDb ){
- sqlite3_mutex_enter(p->pDestDb->mutex);
+ tdsqlite3_mutex_enter(p->pDestDb->mutex);
}
/* Detach this backup from the source pager. */
@@ -71660,40 +78861,42 @@ SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){
p->pSrc->nBackup--;
}
if( p->isAttached ){
- pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc));
+ pp = tdsqlite3PagerBackupPtr(tdsqlite3BtreePager(p->pSrc));
+ assert( pp!=0 );
while( *pp!=p ){
pp = &(*pp)->pNext;
+ assert( pp!=0 );
}
*pp = p->pNext;
}
/* If a transaction is still open on the Btree, roll it back. */
- sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0);
+ tdsqlite3BtreeRollback(p->pDest, SQLITE_OK, 0);
/* Set the error code of the destination database handle. */
rc = (p->rc==SQLITE_DONE) ? SQLITE_OK : p->rc;
if( p->pDestDb ){
- sqlite3Error(p->pDestDb, rc);
+ tdsqlite3Error(p->pDestDb, rc);
/* Exit the mutexes and free the backup context structure. */
- sqlite3LeaveMutexAndCloseZombie(p->pDestDb);
+ tdsqlite3LeaveMutexAndCloseZombie(p->pDestDb);
}
- sqlite3BtreeLeave(p->pSrc);
+ tdsqlite3BtreeLeave(p->pSrc);
if( p->pDestDb ){
- /* EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a
- ** call to sqlite3_backup_init() and is destroyed by a call to
- ** sqlite3_backup_finish(). */
- sqlite3_free(p);
+ /* EVIDENCE-OF: R-64852-21591 The tdsqlite3_backup object is created by a
+ ** call to tdsqlite3_backup_init() and is destroyed by a call to
+ ** tdsqlite3_backup_finish(). */
+ tdsqlite3_free(p);
}
- sqlite3LeaveMutexAndCloseZombie(pSrcDb);
+ tdsqlite3LeaveMutexAndCloseZombie(pSrcDb);
return rc;
}
/*
** Return the number of pages still to be backed up as of the most recent
-** call to sqlite3_backup_step().
+** call to tdsqlite3_backup_step().
*/
-SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p){
+SQLITE_API int tdsqlite3_backup_remaining(tdsqlite3_backup *p){
#ifdef SQLITE_ENABLE_API_ARMOR
if( p==0 ){
(void)SQLITE_MISUSE_BKPT;
@@ -71705,9 +78908,9 @@ SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p){
/*
** Return the total number of pages in the source database as of the most
-** recent call to sqlite3_backup_step().
+** recent call to tdsqlite3_backup_step().
*/
-SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p){
+SQLITE_API int tdsqlite3_backup_pagecount(tdsqlite3_backup *p){
#ifdef SQLITE_ENABLE_API_ARMOR
if( p==0 ){
(void)SQLITE_MISUSE_BKPT;
@@ -71730,13 +78933,13 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p){
** called.
*/
static SQLITE_NOINLINE void backupUpdate(
- sqlite3_backup *p,
+ tdsqlite3_backup *p,
Pgno iPage,
const u8 *aData
){
assert( p!=0 );
do{
- assert( sqlite3_mutex_held(p->pSrc->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(p->pSrc->pBt->mutex) );
if( !isFatalError(p->rc) && iPage<p->iNext ){
/* The backup process p has already copied page iPage. But now it
** has been modified by a transaction on the source pager. Copy
@@ -71744,9 +78947,9 @@ static SQLITE_NOINLINE void backupUpdate(
*/
int rc;
assert( p->pDestDb );
- sqlite3_mutex_enter(p->pDestDb->mutex);
+ tdsqlite3_mutex_enter(p->pDestDb->mutex);
rc = backupOnePage(p, iPage, aData, 1);
- sqlite3_mutex_leave(p->pDestDb->mutex);
+ tdsqlite3_mutex_leave(p->pDestDb->mutex);
assert( rc!=SQLITE_BUSY && rc!=SQLITE_LOCKED );
if( rc!=SQLITE_OK ){
p->rc = rc;
@@ -71754,7 +78957,7 @@ static SQLITE_NOINLINE void backupUpdate(
}
}while( (p = p->pNext)!=0 );
}
-SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *pBackup, Pgno iPage, const u8 *aData){
+SQLITE_PRIVATE void tdsqlite3BackupUpdate(tdsqlite3_backup *pBackup, Pgno iPage, const u8 *aData){
if( pBackup ) backupUpdate(pBackup, iPage, aData);
}
@@ -71769,10 +78972,10 @@ SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *pBackup, Pgno iPage, con
** corresponding to the source database is held when this function is
** called.
*/
-SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *pBackup){
- sqlite3_backup *p; /* Iterator variable */
+SQLITE_PRIVATE void tdsqlite3BackupRestart(tdsqlite3_backup *pBackup){
+ tdsqlite3_backup *p; /* Iterator variable */
for(p=pBackup; p; p=p->pNext){
- assert( sqlite3_mutex_held(p->pSrc->pBt->mutex) );
+ assert( tdsqlite3_mutex_held(p->pSrc->pBt->mutex) );
p->iNext = 1;
}
}
@@ -71786,25 +78989,25 @@ SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *pBackup){
** goes wrong, the transaction on pTo is rolled back. If successful, the
** transaction is committed before returning.
*/
-SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
+SQLITE_PRIVATE int tdsqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
int rc;
- sqlite3_file *pFd; /* File descriptor for database pTo */
- sqlite3_backup b;
- sqlite3BtreeEnter(pTo);
- sqlite3BtreeEnter(pFrom);
+ tdsqlite3_file *pFd; /* File descriptor for database pTo */
+ tdsqlite3_backup b;
+ tdsqlite3BtreeEnter(pTo);
+ tdsqlite3BtreeEnter(pFrom);
- assert( sqlite3BtreeIsInTrans(pTo) );
- pFd = sqlite3PagerFile(sqlite3BtreePager(pTo));
+ assert( tdsqlite3BtreeIsInTrans(pTo) );
+ pFd = tdsqlite3PagerFile(tdsqlite3BtreePager(pTo));
if( pFd->pMethods ){
- i64 nByte = sqlite3BtreeGetPageSize(pFrom)*(i64)sqlite3BtreeLastPage(pFrom);
- rc = sqlite3OsFileControl(pFd, SQLITE_FCNTL_OVERWRITE, &nByte);
+ i64 nByte = tdsqlite3BtreeGetPageSize(pFrom)*(i64)tdsqlite3BtreeLastPage(pFrom);
+ rc = tdsqlite3OsFileControl(pFd, SQLITE_FCNTL_OVERWRITE, &nByte);
if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
if( rc ) goto copy_finished;
}
- /* Set up an sqlite3_backup object. sqlite3_backup.pDestDb must be set
- ** to 0. This is used by the implementations of sqlite3_backup_step()
- ** and sqlite3_backup_finish() to detect that they are being called
+ /* Set up an tdsqlite3_backup object. tdsqlite3_backup.pDestDb must be set
+ ** to 0. This is used by the implementations of tdsqlite3_backup_step()
+ ** and tdsqlite3_backup_finish() to detect that they are being called
** from this function, not directly by the user.
*/
memset(&b, 0, sizeof(b));
@@ -71814,29 +79017,29 @@ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){
b.iNext = 1;
#ifdef SQLITE_HAS_CODEC
- sqlite3PagerAlignReserve(sqlite3BtreePager(pTo), sqlite3BtreePager(pFrom));
+ tdsqlite3PagerAlignReserve(tdsqlite3BtreePager(pTo), tdsqlite3BtreePager(pFrom));
#endif
/* 0x7FFFFFFF is the hard limit for the number of pages in a database
** file. By passing this as the number of pages to copy to
- ** sqlite3_backup_step(), we can guarantee that the copy finishes
+ ** tdsqlite3_backup_step(), we can guarantee that the copy finishes
** within a single call (unless an error occurs). The assert() statement
** checks this assumption - (p->rc) should be set to either SQLITE_DONE
** or an error code. */
- sqlite3_backup_step(&b, 0x7FFFFFFF);
+ tdsqlite3_backup_step(&b, 0x7FFFFFFF);
assert( b.rc!=SQLITE_OK );
- rc = sqlite3_backup_finish(&b);
+ rc = tdsqlite3_backup_finish(&b);
if( rc==SQLITE_OK ){
pTo->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED;
}else{
- sqlite3PagerClearCache(sqlite3BtreePager(b.pDest));
+ tdsqlite3PagerClearCache(tdsqlite3BtreePager(b.pDest));
}
- assert( sqlite3BtreeIsInTrans(pTo)==0 );
+ assert( tdsqlite3BtreeIsInTrans(pTo)==0 );
copy_finished:
- sqlite3BtreeLeave(pFrom);
- sqlite3BtreeLeave(pTo);
+ tdsqlite3BtreeLeave(pFrom);
+ tdsqlite3BtreeLeave(pTo);
return rc;
}
#endif /* SQLITE_OMIT_VACUUM */
@@ -71863,16 +79066,21 @@ copy_finished:
/* #include "sqliteInt.h" */
/* #include "vdbeInt.h" */
+/* True if X is a power of two. 0 is considered a power of two here.
+** In other words, return true if X has at most one bit set.
+*/
+#define ISPOWEROF2(X) (((X)&((X)-1))==0)
+
#ifdef SQLITE_DEBUG
/*
** Check invariants on a Mem object.
**
** This routine is intended for use inside of assert() statements, like
-** this: assert( sqlite3VdbeCheckMemInvariants(pMem) );
+** this: assert( tdsqlite3VdbeCheckMemInvariants(pMem) );
*/
-SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){
+SQLITE_PRIVATE int tdsqlite3VdbeCheckMemInvariants(Mem *p){
/* If MEM_Dyn is set then Mem.xDel!=0.
- ** Mem.xDel is might not be initialized if MEM_Dyn is clear.
+ ** Mem.xDel might not be initialized if MEM_Dyn is clear.
*/
assert( (p->flags & MEM_Dyn)==0 || p->xDel!=0 );
@@ -71882,12 +79090,40 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){
** That saves a few cycles in inner loops. */
assert( (p->flags & MEM_Dyn)==0 || p->szMalloc==0 );
- /* Cannot be both MEM_Int and MEM_Real at the same time */
- assert( (p->flags & (MEM_Int|MEM_Real))!=(MEM_Int|MEM_Real) );
+ /* Cannot have more than one of MEM_Int, MEM_Real, or MEM_IntReal */
+ assert( ISPOWEROF2(p->flags & (MEM_Int|MEM_Real|MEM_IntReal)) );
+
+ if( p->flags & MEM_Null ){
+ /* Cannot be both MEM_Null and some other type */
+ assert( (p->flags & (MEM_Int|MEM_Real|MEM_Str|MEM_Blob|MEM_Agg))==0 );
+
+ /* If MEM_Null is set, then either the value is a pure NULL (the usual
+ ** case) or it is a pointer set using tdsqlite3_bind_pointer() or
+ ** tdsqlite3_result_pointer(). If a pointer, then MEM_Term must also be
+ ** set.
+ */
+ if( (p->flags & (MEM_Term|MEM_Subtype))==(MEM_Term|MEM_Subtype) ){
+ /* This is a pointer type. There may be a flag to indicate what to
+ ** do with the pointer. */
+ assert( ((p->flags&MEM_Dyn)!=0 ? 1 : 0) +
+ ((p->flags&MEM_Ephem)!=0 ? 1 : 0) +
+ ((p->flags&MEM_Static)!=0 ? 1 : 0) <= 1 );
+
+ /* No other bits set */
+ assert( (p->flags & ~(MEM_Null|MEM_Term|MEM_Subtype|MEM_FromBind
+ |MEM_Dyn|MEM_Ephem|MEM_Static))==0 );
+ }else{
+ /* A pure NULL might have other flags, such as MEM_Static, MEM_Dyn,
+ ** MEM_Ephem, MEM_Cleared, or MEM_Subtype */
+ }
+ }else{
+ /* The MEM_Cleared bit is only allowed on NULLs */
+ assert( (p->flags & MEM_Cleared)==0 );
+ }
/* The szMalloc field holds the correct memory allocation size */
assert( p->szMalloc==0
- || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc) );
+ || p->szMalloc==tdsqlite3DbMallocSize(p->db,p->zMalloc) );
/* If p holds a string or blob, the Mem.z must point to exactly
** one of the following:
@@ -71909,6 +79145,80 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){
}
#endif
+/*
+** Render a Mem object which is one of MEM_Int, MEM_Real, or MEM_IntReal
+** into a buffer.
+*/
+static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){
+ StrAccum acc;
+ assert( p->flags & (MEM_Int|MEM_Real|MEM_IntReal) );
+ tdsqlite3StrAccumInit(&acc, 0, zBuf, sz, 0);
+ if( p->flags & MEM_Int ){
+ tdsqlite3_str_appendf(&acc, "%lld", p->u.i);
+ }else if( p->flags & MEM_IntReal ){
+ tdsqlite3_str_appendf(&acc, "%!.15g", (double)p->u.i);
+ }else{
+ tdsqlite3_str_appendf(&acc, "%!.15g", p->u.r);
+ }
+ assert( acc.zText==zBuf && acc.mxAlloc<=0 );
+ zBuf[acc.nChar] = 0; /* Fast version of tdsqlite3StrAccumFinish(&acc) */
+}
+
+#ifdef SQLITE_DEBUG
+/*
+** Validity checks on pMem. pMem holds a string.
+**
+** (1) Check that string value of pMem agrees with its integer or real value.
+** (2) Check that the string is correctly zero terminated
+**
+** A single int or real value always converts to the same strings. But
+** many different strings can be converted into the same int or real.
+** If a table contains a numeric value and an index is based on the
+** corresponding string value, then it is important that the string be
+** derived from the numeric value, not the other way around, to ensure
+** that the index and table are consistent. See ticket
+** https://www.sqlite.org/src/info/343634942dd54ab (2018-01-31) for
+** an example.
+**
+** This routine looks at pMem to verify that if it has both a numeric
+** representation and a string representation then the string rep has
+** been derived from the numeric and not the other way around. It returns
+** true if everything is ok and false if there is a problem.
+**
+** This routine is for use inside of assert() statements only.
+*/
+SQLITE_PRIVATE int tdsqlite3VdbeMemValidStrRep(Mem *p){
+ char zBuf[100];
+ char *z;
+ int i, j, incr;
+ if( (p->flags & MEM_Str)==0 ) return 1;
+ if( p->flags & MEM_Term ){
+ /* Insure that the string is properly zero-terminated. Pay particular
+ ** attention to the case where p->n is odd */
+ if( p->szMalloc>0 && p->z==p->zMalloc ){
+ assert( p->enc==SQLITE_UTF8 || p->szMalloc >= ((p->n+1)&~1)+2 );
+ assert( p->enc!=SQLITE_UTF8 || p->szMalloc >= p->n+1 );
+ }
+ assert( p->z[p->n]==0 );
+ assert( p->enc==SQLITE_UTF8 || p->z[(p->n+1)&~1]==0 );
+ assert( p->enc==SQLITE_UTF8 || p->z[((p->n+1)&~1)+1]==0 );
+ }
+ if( (p->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ) return 1;
+ vdbeMemRenderNum(sizeof(zBuf), zBuf, p);
+ z = p->z;
+ i = j = 0;
+ incr = 1;
+ if( p->enc!=SQLITE_UTF8 ){
+ incr = 2;
+ if( p->enc==SQLITE_UTF16BE ) z++;
+ }
+ while( zBuf[j] ){
+ if( zBuf[j++]!=z[i] ) return 0;
+ i += incr;
+ }
+ return 1;
+}
+#endif /* SQLITE_DEBUG */
/*
** If pMem is an object with a valid string representation, this routine
@@ -71923,17 +79233,17 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){
** SQLITE_NOMEM may be returned if a malloc() fails during conversion
** between formats.
*/
-SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){
+SQLITE_PRIVATE int tdsqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){
#ifndef SQLITE_OMIT_UTF16
int rc;
#endif
- assert( (pMem->flags&MEM_RowSet)==0 );
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
assert( desiredEnc==SQLITE_UTF8 || desiredEnc==SQLITE_UTF16LE
|| desiredEnc==SQLITE_UTF16BE );
if( !(pMem->flags&MEM_Str) || pMem->enc==desiredEnc ){
return SQLITE_OK;
}
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
#ifdef SQLITE_OMIT_UTF16
return SQLITE_ERROR;
#else
@@ -71941,7 +79251,7 @@ SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){
/* MemTranslate() may return SQLITE_OK or SQLITE_NOMEM. If NOMEM is returned,
** then the encoding of the value may not have changed.
*/
- rc = sqlite3VdbeMemTranslate(pMem, (u8)desiredEnc);
+ rc = tdsqlite3VdbeMemTranslate(pMem, (u8)desiredEnc);
assert(rc==SQLITE_OK || rc==SQLITE_NOMEM);
assert(rc==SQLITE_OK || pMem->enc!=desiredEnc);
assert(rc==SQLITE_NOMEM || pMem->enc==desiredEnc);
@@ -71950,17 +79260,16 @@ SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){
}
/*
-** Make sure pMem->z points to a writable allocation of at least
-** min(n,32) bytes.
+** Make sure pMem->z points to a writable allocation of at least n bytes.
**
** If the bPreserve argument is true, then copy of the content of
** pMem->z into the new allocation. pMem must be either a string or
** blob if bPreserve is true. If bPreserve is false, any prior content
** in pMem->z is discarded.
*/
-SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPreserve){
- assert( sqlite3VdbeCheckMemInvariants(pMem) );
- assert( (pMem->flags&MEM_RowSet)==0 );
+SQLITE_PRIVATE SQLITE_NOINLINE int tdsqlite3VdbeMemGrow(Mem *pMem, int n, int bPreserve){
+ assert( tdsqlite3VdbeCheckMemInvariants(pMem) );
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
testcase( pMem->db==0 );
/* If the bPreserve flag is set to true, then the memory cell must already
@@ -71969,27 +79278,31 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPre
testcase( bPreserve && pMem->z==0 );
assert( pMem->szMalloc==0
- || pMem->szMalloc==sqlite3DbMallocSize(pMem->db, pMem->zMalloc) );
- if( pMem->szMalloc<n ){
- if( n<32 ) n = 32;
- if( bPreserve && pMem->szMalloc>0 && pMem->z==pMem->zMalloc ){
- pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n);
- bPreserve = 0;
- }else{
- if( pMem->szMalloc>0 ) sqlite3DbFree(pMem->db, pMem->zMalloc);
- pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, n);
- }
- if( pMem->zMalloc==0 ){
- sqlite3VdbeMemSetNull(pMem);
- pMem->z = 0;
- pMem->szMalloc = 0;
- return SQLITE_NOMEM_BKPT;
+ || pMem->szMalloc==tdsqlite3DbMallocSize(pMem->db, pMem->zMalloc) );
+ if( pMem->szMalloc>0 && bPreserve && pMem->z==pMem->zMalloc ){
+ if( pMem->db ){
+ pMem->z = pMem->zMalloc = tdsqlite3DbReallocOrFree(pMem->db, pMem->z, n);
}else{
- pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc);
+ pMem->zMalloc = tdsqlite3Realloc(pMem->z, n);
+ if( pMem->zMalloc==0 ) tdsqlite3_free(pMem->z);
+ pMem->z = pMem->zMalloc;
}
+ bPreserve = 0;
+ }else{
+ if( pMem->szMalloc>0 ) tdsqlite3DbFreeNN(pMem->db, pMem->zMalloc);
+ pMem->zMalloc = tdsqlite3DbMallocRaw(pMem->db, n);
+ }
+ if( pMem->zMalloc==0 ){
+ tdsqlite3VdbeMemSetNull(pMem);
+ pMem->z = 0;
+ pMem->szMalloc = 0;
+ return SQLITE_NOMEM_BKPT;
+ }else{
+ pMem->szMalloc = tdsqlite3DbMallocSize(pMem->db, pMem->zMalloc);
}
- if( bPreserve && pMem->z && pMem->z!=pMem->zMalloc ){
+ if( bPreserve && pMem->z ){
+ assert( pMem->z!=pMem->zMalloc );
memcpy(pMem->zMalloc, pMem->z, pMem->n);
}
if( (pMem->flags&MEM_Dyn)!=0 ){
@@ -72009,21 +79322,41 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPre
**
** Any prior string or blob content in the pMem object may be discarded.
** The pMem->xDel destructor is called, if it exists. Though MEM_Str
-** and MEM_Blob values may be discarded, MEM_Int, MEM_Real, and MEM_Null
-** values are preserved.
+** and MEM_Blob values may be discarded, MEM_Int, MEM_Real, MEM_IntReal,
+** and MEM_Null values are preserved.
**
** Return SQLITE_OK on success or an error code (probably SQLITE_NOMEM)
** if unable to complete the resizing.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){
- assert( szNew>0 );
+SQLITE_PRIVATE int tdsqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){
+ assert( CORRUPT_DB || szNew>0 );
assert( (pMem->flags & MEM_Dyn)==0 || pMem->szMalloc==0 );
if( pMem->szMalloc<szNew ){
- return sqlite3VdbeMemGrow(pMem, szNew, 0);
+ return tdsqlite3VdbeMemGrow(pMem, szNew, 0);
}
assert( (pMem->flags & MEM_Dyn)==0 );
pMem->z = pMem->zMalloc;
- pMem->flags &= (MEM_Null|MEM_Int|MEM_Real);
+ pMem->flags &= (MEM_Null|MEM_Int|MEM_Real|MEM_IntReal);
+ return SQLITE_OK;
+}
+
+/*
+** It is already known that pMem contains an unterminated string.
+** Add the zero terminator.
+**
+** Three bytes of zero are added. In this way, there is guaranteed
+** to be a double-zero byte at an even byte boundary in order to
+** terminate a UTF16 string, even if the initial size of the buffer
+** is an odd number of bytes.
+*/
+static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){
+ if( tdsqlite3VdbeMemGrow(pMem, pMem->n+3, 1) ){
+ return SQLITE_NOMEM_BKPT;
+ }
+ pMem->z[pMem->n] = 0;
+ pMem->z[pMem->n+1] = 0;
+ pMem->z[pMem->n+2] = 0;
+ pMem->flags |= MEM_Term;
return SQLITE_OK;
}
@@ -72033,18 +79366,14 @@ SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){
**
** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
- assert( (pMem->flags&MEM_RowSet)==0 );
+SQLITE_PRIVATE int tdsqlite3VdbeMemMakeWriteable(Mem *pMem){
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
if( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ){
if( ExpandBlob(pMem) ) return SQLITE_NOMEM;
if( pMem->szMalloc==0 || pMem->z!=pMem->zMalloc ){
- if( sqlite3VdbeMemGrow(pMem, pMem->n + 2, 1) ){
- return SQLITE_NOMEM_BKPT;
- }
- pMem->z[pMem->n] = 0;
- pMem->z[pMem->n+1] = 0;
- pMem->flags |= MEM_Term;
+ int rc = vdbeMemAddTerminator(pMem);
+ if( rc ) return rc;
}
}
pMem->flags &= ~MEM_Ephem;
@@ -72060,19 +79389,21 @@ SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){
** blob stored in dynamically allocated space.
*/
#ifndef SQLITE_OMIT_INCRBLOB
-SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){
+SQLITE_PRIVATE int tdsqlite3VdbeMemExpandBlob(Mem *pMem){
int nByte;
assert( pMem->flags & MEM_Zero );
- assert( pMem->flags&MEM_Blob );
- assert( (pMem->flags&MEM_RowSet)==0 );
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ assert( (pMem->flags&MEM_Blob)!=0 || MemNullNochng(pMem) );
+ testcase( tdsqlite3_value_nochange(pMem) );
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
/* Set nByte to the number of bytes required to store the expanded blob. */
nByte = pMem->n + pMem->u.nZero;
if( nByte<=0 ){
+ if( (pMem->flags & MEM_Blob)==0 ) return SQLITE_OK;
nByte = 1;
}
- if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){
+ if( tdsqlite3VdbeMemGrow(pMem, nByte, 1) ){
return SQLITE_NOMEM_BKPT;
}
@@ -72084,24 +79415,10 @@ SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){
#endif
/*
-** It is already known that pMem contains an unterminated string.
-** Add the zero terminator.
-*/
-static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){
- if( sqlite3VdbeMemGrow(pMem, pMem->n+2, 1) ){
- return SQLITE_NOMEM_BKPT;
- }
- pMem->z[pMem->n] = 0;
- pMem->z[pMem->n+1] = 0;
- pMem->flags |= MEM_Term;
- return SQLITE_OK;
-}
-
-/*
** Make sure the given Mem is \u0000 terminated.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+SQLITE_PRIVATE int tdsqlite3VdbeMemNulTerminate(Mem *pMem){
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
testcase( (pMem->flags & (MEM_Term|MEM_Str))==(MEM_Term|MEM_Str) );
testcase( (pMem->flags & (MEM_Term|MEM_Str))==0 );
if( (pMem->flags & (MEM_Term|MEM_Str))!=MEM_Str ){
@@ -72112,53 +79429,42 @@ SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){
}
/*
-** Add MEM_Str to the set of representations for the given Mem. Numbers
-** are converted using sqlite3_snprintf(). Converting a BLOB to a string
-** is a no-op.
+** Add MEM_Str to the set of representations for the given Mem. This
+** routine is only called if pMem is a number of some kind, not a NULL
+** or a BLOB.
**
-** Existing representations MEM_Int and MEM_Real are invalidated if
-** bForce is true but are retained if bForce is false.
+** Existing representations MEM_Int, MEM_Real, or MEM_IntReal are invalidated
+** if bForce is true but are retained if bForce is false.
**
** A MEM_Null value will never be passed to this function. This function is
** used for converting values to text for returning to the user (i.e. via
-** sqlite3_value_text()), or for ensuring that values to be used as btree
+** tdsqlite3_value_text()), or for ensuring that values to be used as btree
** keys are strings. In the former case a NULL pointer is returned the
** user and the latter is an internal programming error.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){
- int fg = pMem->flags;
+SQLITE_PRIVATE int tdsqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){
const int nByte = 32;
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
- assert( !(fg&MEM_Zero) );
- assert( !(fg&(MEM_Str|MEM_Blob)) );
- assert( fg&(MEM_Int|MEM_Real) );
- assert( (pMem->flags&MEM_RowSet)==0 );
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
+ assert( !(pMem->flags&MEM_Zero) );
+ assert( !(pMem->flags&(MEM_Str|MEM_Blob)) );
+ assert( pMem->flags&(MEM_Int|MEM_Real|MEM_IntReal) );
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
assert( EIGHT_BYTE_ALIGNMENT(pMem) );
- if( sqlite3VdbeMemClearAndResize(pMem, nByte) ){
+ if( tdsqlite3VdbeMemClearAndResize(pMem, nByte) ){
pMem->enc = 0;
return SQLITE_NOMEM_BKPT;
}
- /* For a Real or Integer, use sqlite3_snprintf() to produce the UTF-8
- ** string representation of the value. Then, if the required encoding
- ** is UTF-16le or UTF-16be do a translation.
- **
- ** FIX ME: It would be better if sqlite3_snprintf() could do UTF-16.
- */
- if( fg & MEM_Int ){
- sqlite3_snprintf(nByte, pMem->z, "%lld", pMem->u.i);
- }else{
- assert( fg & MEM_Real );
- sqlite3_snprintf(nByte, pMem->z, "%!.15g", pMem->u.r);
- }
- pMem->n = sqlite3Strlen30(pMem->z);
+ vdbeMemRenderNum(nByte, pMem->z, pMem);
+ assert( pMem->z!=0 );
+ pMem->n = tdsqlite3Strlen30NN(pMem->z);
pMem->enc = SQLITE_UTF8;
pMem->flags |= MEM_Str|MEM_Term;
- if( bForce ) pMem->flags &= ~(MEM_Int|MEM_Real);
- sqlite3VdbeChangeEncoding(pMem, enc);
+ if( bForce ) pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal);
+ tdsqlite3VdbeChangeEncoding(pMem, enc);
return SQLITE_OK;
}
@@ -72170,56 +79476,72 @@ SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){
** Return SQLITE_ERROR if the finalizer reports an error. SQLITE_OK
** otherwise.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){
- int rc = SQLITE_OK;
- if( ALWAYS(pFunc && pFunc->xFinalize) ){
- sqlite3_context ctx;
- Mem t;
- assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef );
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
- memset(&ctx, 0, sizeof(ctx));
- memset(&t, 0, sizeof(t));
- t.flags = MEM_Null;
- t.db = pMem->db;
- ctx.pOut = &t;
- ctx.pMem = pMem;
- ctx.pFunc = pFunc;
- pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */
- assert( (pMem->flags & MEM_Dyn)==0 );
- if( pMem->szMalloc>0 ) sqlite3DbFree(pMem->db, pMem->zMalloc);
- memcpy(pMem, &t, sizeof(t));
- rc = ctx.isError;
- }
- return rc;
+SQLITE_PRIVATE int tdsqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){
+ tdsqlite3_context ctx;
+ Mem t;
+ assert( pFunc!=0 );
+ assert( pFunc->xFinalize!=0 );
+ assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef );
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
+ memset(&ctx, 0, sizeof(ctx));
+ memset(&t, 0, sizeof(t));
+ t.flags = MEM_Null;
+ t.db = pMem->db;
+ ctx.pOut = &t;
+ ctx.pMem = pMem;
+ ctx.pFunc = pFunc;
+ pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */
+ assert( (pMem->flags & MEM_Dyn)==0 );
+ if( pMem->szMalloc>0 ) tdsqlite3DbFreeNN(pMem->db, pMem->zMalloc);
+ memcpy(pMem, &t, sizeof(t));
+ return ctx.isError;
}
/*
+** Memory cell pAccum contains the context of an aggregate function.
+** This routine calls the xValue method for that function and stores
+** the results in memory cell pMem.
+**
+** SQLITE_ERROR is returned if xValue() reports an error. SQLITE_OK
+** otherwise.
+*/
+#ifndef SQLITE_OMIT_WINDOWFUNC
+SQLITE_PRIVATE int tdsqlite3VdbeMemAggValue(Mem *pAccum, Mem *pOut, FuncDef *pFunc){
+ tdsqlite3_context ctx;
+ assert( pFunc!=0 );
+ assert( pFunc->xValue!=0 );
+ assert( (pAccum->flags & MEM_Null)!=0 || pFunc==pAccum->u.pDef );
+ assert( pAccum->db==0 || tdsqlite3_mutex_held(pAccum->db->mutex) );
+ memset(&ctx, 0, sizeof(ctx));
+ tdsqlite3VdbeMemSetNull(pOut);
+ ctx.pOut = pOut;
+ ctx.pMem = pAccum;
+ ctx.pFunc = pFunc;
+ pFunc->xValue(&ctx);
+ return ctx.isError;
+}
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+
+/*
** If the memory cell contains a value that must be freed by
** invoking the external callback in Mem.xDel, then this routine
** will free that value. It also sets Mem.flags to MEM_Null.
**
-** This is a helper routine for sqlite3VdbeMemSetNull() and
-** for sqlite3VdbeMemRelease(). Use those other routines as the
+** This is a helper routine for tdsqlite3VdbeMemSetNull() and
+** for tdsqlite3VdbeMemRelease(). Use those other routines as the
** entry point for releasing Mem resources.
*/
static SQLITE_NOINLINE void vdbeMemClearExternAndSetNull(Mem *p){
- assert( p->db==0 || sqlite3_mutex_held(p->db->mutex) );
+ assert( p->db==0 || tdsqlite3_mutex_held(p->db->mutex) );
assert( VdbeMemDynamic(p) );
if( p->flags&MEM_Agg ){
- sqlite3VdbeMemFinalize(p, p->u.pDef);
+ tdsqlite3VdbeMemFinalize(p, p->u.pDef);
assert( (p->flags & MEM_Agg)==0 );
testcase( p->flags & MEM_Dyn );
}
if( p->flags&MEM_Dyn ){
- assert( (p->flags&MEM_RowSet)==0 );
assert( p->xDel!=SQLITE_DYNAMIC && p->xDel!=0 );
p->xDel((void *)p->z);
- }else if( p->flags&MEM_RowSet ){
- sqlite3RowSetClear(p->u.pRowSet);
- }else if( p->flags&MEM_Frame ){
- VdbeFrame *pFrame = p->u.pFrame;
- pFrame->pParent = pFrame->v->pDelFrame;
- pFrame->v->pDelFrame = pFrame;
}
p->flags = MEM_Null;
}
@@ -72228,7 +79550,7 @@ static SQLITE_NOINLINE void vdbeMemClearExternAndSetNull(Mem *p){
** Release memory held by the Mem p, both external memory cleared
** by p->xDel and memory in p->zMalloc.
**
-** This is a helper routine invoked by sqlite3VdbeMemRelease() in
+** This is a helper routine invoked by tdsqlite3VdbeMemRelease() in
** the unusual case where there really is memory in p that needs
** to be freed.
*/
@@ -72237,7 +79559,7 @@ static SQLITE_NOINLINE void vdbeMemClear(Mem *p){
vdbeMemClearExternAndSetNull(p);
}
if( p->szMalloc ){
- sqlite3DbFree(p->db, p->zMalloc);
+ tdsqlite3DbFreeNN(p->db, p->zMalloc);
p->szMalloc = 0;
}
p->z = 0;
@@ -72250,11 +79572,11 @@ static SQLITE_NOINLINE void vdbeMemClear(Mem *p){
** Use this routine prior to clean up prior to abandoning a Mem, or to
** reset a Mem back to its minimum memory utilization.
**
-** Use sqlite3VdbeMemSetNull() to release just the Mem.xDel space
+** Use tdsqlite3VdbeMemSetNull() to release just the Mem.xDel space
** prior to inserting new content into the Mem.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p){
- assert( sqlite3VdbeCheckMemInvariants(p) );
+SQLITE_PRIVATE void tdsqlite3VdbeMemRelease(Mem *p){
+ assert( tdsqlite3VdbeCheckMemInvariants(p) );
if( VdbeMemDynamic(p) || p->szMalloc ){
vdbeMemClear(p);
}
@@ -72265,7 +79587,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p){
** If the double is out of range of a 64-bit signed integer then
** return the closest available 64-bit signed integer.
*/
-static i64 doubleToInt64(double r){
+static SQLITE_NOINLINE i64 doubleToInt64(double r){
#ifdef SQLITE_OMIT_FLOATING_POINT
/* When floating-point is omitted, double and int64 are the same thing */
return r;
@@ -72301,20 +79623,23 @@ static i64 doubleToInt64(double r){
**
** If pMem represents a string value, its encoding might be changed.
*/
-SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem *pMem){
+static SQLITE_NOINLINE i64 memIntValue(Mem *pMem){
+ i64 value = 0;
+ tdsqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc);
+ return value;
+}
+SQLITE_PRIVATE i64 tdsqlite3VdbeIntValue(Mem *pMem){
int flags;
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
assert( EIGHT_BYTE_ALIGNMENT(pMem) );
flags = pMem->flags;
- if( flags & MEM_Int ){
+ if( flags & (MEM_Int|MEM_IntReal) ){
+ testcase( flags & MEM_IntReal );
return pMem->u.i;
}else if( flags & MEM_Real ){
return doubleToInt64(pMem->u.r);
- }else if( flags & (MEM_Str|MEM_Blob) ){
- i64 value = 0;
- assert( pMem->z || pMem->n==0 );
- sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc);
- return value;
+ }else if( (flags & (MEM_Str|MEM_Blob))!=0 && pMem->z!=0 ){
+ return memIntValue(pMem);
}else{
return 0;
}
@@ -72326,18 +79651,22 @@ SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem *pMem){
** value. If it is a string or blob, try to convert it to a double.
** If it is a NULL, return 0.0.
*/
-SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+static SQLITE_NOINLINE double memRealValue(Mem *pMem){
+ /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */
+ double val = (double)0;
+ tdsqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc);
+ return val;
+}
+SQLITE_PRIVATE double tdsqlite3VdbeRealValue(Mem *pMem){
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
assert( EIGHT_BYTE_ALIGNMENT(pMem) );
if( pMem->flags & MEM_Real ){
return pMem->u.r;
- }else if( pMem->flags & MEM_Int ){
+ }else if( pMem->flags & (MEM_Int|MEM_IntReal) ){
+ testcase( pMem->flags & MEM_IntReal );
return (double)pMem->u.i;
}else if( pMem->flags & (MEM_Str|MEM_Blob) ){
- /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */
- double val = (double)0;
- sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc);
- return val;
+ return memRealValue(pMem);
}else{
/* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */
return (double)0;
@@ -72345,14 +79674,25 @@ SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){
}
/*
+** Return 1 if pMem represents true, and return 0 if pMem represents false.
+** Return the value ifNull if pMem is NULL.
+*/
+SQLITE_PRIVATE int tdsqlite3VdbeBooleanValue(Mem *pMem, int ifNull){
+ testcase( pMem->flags & MEM_IntReal );
+ if( pMem->flags & (MEM_Int|MEM_IntReal) ) return pMem->u.i!=0;
+ if( pMem->flags & MEM_Null ) return ifNull;
+ return tdsqlite3VdbeRealValue(pMem)!=0.0;
+}
+
+/*
** The MEM structure is already a MEM_Real. Try to also make it a
** MEM_Int if we can.
*/
-SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){
+SQLITE_PRIVATE void tdsqlite3VdbeIntegerAffinity(Mem *pMem){
i64 ix;
assert( pMem->flags & MEM_Real );
- assert( (pMem->flags & MEM_RowSet)==0 );
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
assert( EIGHT_BYTE_ALIGNMENT(pMem) );
ix = doubleToInt64(pMem->u.r);
@@ -72376,12 +79716,12 @@ SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){
/*
** Convert pMem to type integer. Invalidate any prior representations.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem *pMem){
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
- assert( (pMem->flags & MEM_RowSet)==0 );
+SQLITE_PRIVATE int tdsqlite3VdbeMemIntegerify(Mem *pMem){
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
assert( EIGHT_BYTE_ALIGNMENT(pMem) );
- pMem->u.i = sqlite3VdbeIntValue(pMem);
+ pMem->u.i = tdsqlite3VdbeIntValue(pMem);
MemSetTypeFlag(pMem, MEM_Int);
return SQLITE_OK;
}
@@ -72390,36 +79730,60 @@ SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem *pMem){
** Convert pMem so that it is of type MEM_Real.
** Invalidate any prior representations.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem *pMem){
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
+SQLITE_PRIVATE int tdsqlite3VdbeMemRealify(Mem *pMem){
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
assert( EIGHT_BYTE_ALIGNMENT(pMem) );
- pMem->u.r = sqlite3VdbeRealValue(pMem);
+ pMem->u.r = tdsqlite3VdbeRealValue(pMem);
MemSetTypeFlag(pMem, MEM_Real);
return SQLITE_OK;
}
+/* Compare a floating point value to an integer. Return true if the two
+** values are the same within the precision of the floating point value.
+**
+** This function assumes that i was obtained by assignment from r1.
+**
+** For some versions of GCC on 32-bit machines, if you do the more obvious
+** comparison of "r1==(double)i" you sometimes get an answer of false even
+** though the r1 and (double)i values are bit-for-bit the same.
+*/
+SQLITE_PRIVATE int tdsqlite3RealSameAsInt(double r1, tdsqlite3_int64 i){
+ double r2 = (double)i;
+ return r1==0.0
+ || (memcmp(&r1, &r2, sizeof(r1))==0
+ && i >= -2251799813685248LL && i < 2251799813685248LL);
+}
+
/*
-** Convert pMem so that it has types MEM_Real or MEM_Int or both.
+** Convert pMem so that it has type MEM_Real or MEM_Int.
** Invalidate any prior representations.
**
** Every effort is made to force the conversion, even if the input
** is a string that does not look completely like a number. Convert
** as much of the string as we can and ignore the rest.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){
- if( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))==0 ){
+SQLITE_PRIVATE int tdsqlite3VdbeMemNumerify(Mem *pMem){
+ testcase( pMem->flags & MEM_Int );
+ testcase( pMem->flags & MEM_Real );
+ testcase( pMem->flags & MEM_IntReal );
+ testcase( pMem->flags & MEM_Null );
+ if( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null))==0 ){
+ int rc;
+ tdsqlite3_int64 ix;
assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 );
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
- if( 0==sqlite3Atoi64(pMem->z, &pMem->u.i, pMem->n, pMem->enc) ){
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
+ rc = tdsqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc);
+ if( ((rc==0 || rc==1) && tdsqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1)
+ || tdsqlite3RealSameAsInt(pMem->u.r, (ix = (i64)pMem->u.r))
+ ){
+ pMem->u.i = ix;
MemSetTypeFlag(pMem, MEM_Int);
}else{
- pMem->u.r = sqlite3VdbeRealValue(pMem);
MemSetTypeFlag(pMem, MEM_Real);
- sqlite3VdbeIntegerAffinity(pMem);
}
}
- assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))!=0 );
+ assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null))!=0 );
pMem->flags &= ~(MEM_Str|MEM_Blob|MEM_Zero);
return SQLITE_OK;
}
@@ -72431,12 +79795,12 @@ SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){
** affinity even if that results in loss of data. This routine is
** used (for example) to implement the SQL "cast()" operator.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){
- if( pMem->flags & MEM_Null ) return;
+SQLITE_PRIVATE int tdsqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){
+ if( pMem->flags & MEM_Null ) return SQLITE_OK;
switch( aff ){
case SQLITE_AFF_BLOB: { /* Really a cast to BLOB */
if( (pMem->flags & MEM_Blob)==0 ){
- sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding);
+ tdsqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding);
assert( pMem->flags & MEM_Str || pMem->db->mallocFailed );
if( pMem->flags & MEM_Str ) MemSetTypeFlag(pMem, MEM_Blob);
}else{
@@ -72445,27 +79809,28 @@ SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){
break;
}
case SQLITE_AFF_NUMERIC: {
- sqlite3VdbeMemNumerify(pMem);
+ tdsqlite3VdbeMemNumerify(pMem);
break;
}
case SQLITE_AFF_INTEGER: {
- sqlite3VdbeMemIntegerify(pMem);
+ tdsqlite3VdbeMemIntegerify(pMem);
break;
}
case SQLITE_AFF_REAL: {
- sqlite3VdbeMemRealify(pMem);
+ tdsqlite3VdbeMemRealify(pMem);
break;
}
default: {
assert( aff==SQLITE_AFF_TEXT );
assert( MEM_Str==(MEM_Blob>>3) );
pMem->flags |= (pMem->flags&MEM_Blob)>>3;
- sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding);
+ tdsqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding);
assert( pMem->flags & MEM_Str || pMem->db->mallocFailed );
- pMem->flags &= ~(MEM_Int|MEM_Real|MEM_Blob|MEM_Zero);
- break;
+ pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal|MEM_Blob|MEM_Zero);
+ return tdsqlite3VdbeChangeEncoding(pMem, encoding);
}
}
+ return SQLITE_OK;
}
/*
@@ -72473,7 +79838,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){
**
** The minimum amount of initialization feasible is performed.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem *pMem, sqlite3 *db, u16 flags){
+SQLITE_PRIVATE void tdsqlite3VdbeMemInit(Mem *pMem, tdsqlite3 *db, u16 flags){
assert( (flags & ~MEM_TypeMask)==0 );
pMem->flags = flags;
pMem->db = db;
@@ -72486,30 +79851,30 @@ SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem *pMem, sqlite3 *db, u16 flags){
**
** This routine calls the Mem.xDel destructor to dispose of values that
** require the destructor. But it preserves the Mem.zMalloc memory allocation.
-** To free all resources, use sqlite3VdbeMemRelease(), which both calls this
+** To free all resources, use tdsqlite3VdbeMemRelease(), which both calls this
** routine to invoke the destructor and deallocates Mem.zMalloc.
**
** Use this routine to reset the Mem prior to insert a new value.
**
-** Use sqlite3VdbeMemRelease() to complete erase the Mem prior to abandoning it.
+** Use tdsqlite3VdbeMemRelease() to complete erase the Mem prior to abandoning it.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem *pMem){
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetNull(Mem *pMem){
if( VdbeMemDynamic(pMem) ){
vdbeMemClearExternAndSetNull(pMem);
}else{
pMem->flags = MEM_Null;
}
}
-SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value *p){
- sqlite3VdbeMemSetNull((Mem*)p);
+SQLITE_PRIVATE void tdsqlite3ValueSetNull(tdsqlite3_value *p){
+ tdsqlite3VdbeMemSetNull((Mem*)p);
}
/*
** Delete any previous value and set the value to be a BLOB of length
** n containing all zeros.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){
- sqlite3VdbeMemRelease(pMem);
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){
+ tdsqlite3VdbeMemRelease(pMem);
pMem->flags = MEM_Blob|MEM_Zero;
pMem->n = 0;
if( n<0 ) n = 0;
@@ -72524,7 +79889,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){
** a 64-bit integer.
*/
static SQLITE_NOINLINE void vdbeReleaseAndSetInt64(Mem *pMem, i64 val){
- sqlite3VdbeMemSetNull(pMem);
+ tdsqlite3VdbeMemSetNull(pMem);
pMem->u.i = val;
pMem->flags = MEM_Int;
}
@@ -72533,7 +79898,7 @@ static SQLITE_NOINLINE void vdbeReleaseAndSetInt64(Mem *pMem, i64 val){
** Delete any previous value and set the value stored in *pMem to val,
** manifest type INTEGER.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetInt64(Mem *pMem, i64 val){
if( VdbeMemDynamic(pMem) ){
vdbeReleaseAndSetInt64(pMem, val);
}else{
@@ -72542,47 +79907,78 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){
}
}
+/* A no-op destructor */
+SQLITE_PRIVATE void tdsqlite3NoopDestructor(void *p){ UNUSED_PARAMETER(p); }
+
+/*
+** Set the value stored in *pMem should already be a NULL.
+** Also store a pointer to go with it.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetPointer(
+ Mem *pMem,
+ void *pPtr,
+ const char *zPType,
+ void (*xDestructor)(void*)
+){
+ assert( pMem->flags==MEM_Null );
+ pMem->u.zPType = zPType ? zPType : "";
+ pMem->z = pPtr;
+ pMem->flags = MEM_Null|MEM_Dyn|MEM_Subtype|MEM_Term;
+ pMem->eSubtype = 'p';
+ pMem->xDel = xDestructor ? xDestructor : tdsqlite3NoopDestructor;
+}
+
#ifndef SQLITE_OMIT_FLOATING_POINT
/*
** Delete any previous value and set the value stored in *pMem to val,
** manifest type REAL.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem *pMem, double val){
- sqlite3VdbeMemSetNull(pMem);
- if( !sqlite3IsNaN(val) ){
+SQLITE_PRIVATE void tdsqlite3VdbeMemSetDouble(Mem *pMem, double val){
+ tdsqlite3VdbeMemSetNull(pMem);
+ if( !tdsqlite3IsNaN(val) ){
pMem->u.r = val;
pMem->flags = MEM_Real;
}
}
#endif
+#ifdef SQLITE_DEBUG
+/*
+** Return true if the Mem holds a RowSet object. This routine is intended
+** for use inside of assert() statements.
+*/
+SQLITE_PRIVATE int tdsqlite3VdbeMemIsRowSet(const Mem *pMem){
+ return (pMem->flags&(MEM_Blob|MEM_Dyn))==(MEM_Blob|MEM_Dyn)
+ && pMem->xDel==tdsqlite3RowSetDelete;
+}
+#endif
+
/*
** Delete any previous value and set the value of pMem to be an
** empty boolean index.
+**
+** Return SQLITE_OK on success and SQLITE_NOMEM if a memory allocation
+** error occurs.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemSetRowSet(Mem *pMem){
- sqlite3 *db = pMem->db;
+SQLITE_PRIVATE int tdsqlite3VdbeMemSetRowSet(Mem *pMem){
+ tdsqlite3 *db = pMem->db;
+ RowSet *p;
assert( db!=0 );
- assert( (pMem->flags & MEM_RowSet)==0 );
- sqlite3VdbeMemRelease(pMem);
- pMem->zMalloc = sqlite3DbMallocRawNN(db, 64);
- if( db->mallocFailed ){
- pMem->flags = MEM_Null;
- pMem->szMalloc = 0;
- }else{
- assert( pMem->zMalloc );
- pMem->szMalloc = sqlite3DbMallocSize(db, pMem->zMalloc);
- pMem->u.pRowSet = sqlite3RowSetInit(db, pMem->zMalloc, pMem->szMalloc);
- assert( pMem->u.pRowSet!=0 );
- pMem->flags = MEM_RowSet;
- }
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
+ tdsqlite3VdbeMemRelease(pMem);
+ p = tdsqlite3RowSetInit(db);
+ if( p==0 ) return SQLITE_NOMEM;
+ pMem->z = (char*)p;
+ pMem->flags = MEM_Blob|MEM_Dyn;
+ pMem->xDel = tdsqlite3RowSetDelete;
+ return SQLITE_OK;
}
/*
** Return true if the Mem object contains a TEXT or BLOB that is
** too large - whose size exceeds SQLITE_MAX_LENGTH.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem *p){
+SQLITE_PRIVATE int tdsqlite3VdbeMemTooBig(Mem *p){
assert( p->db!=0 );
if( p->flags & (MEM_Str|MEM_Blob) ){
int n = p->n;
@@ -72600,15 +79996,36 @@ SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem *p){
** its link to a shallow copy and by marking any current shallow
** copies of this cell as invalid.
**
-** This is used for testing and debugging only - to make sure shallow
-** copies are not misused.
+** This is used for testing and debugging only - to help ensure that shallow
+** copies (created by OP_SCopy) are not misused.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe *pVdbe, Mem *pMem){
+SQLITE_PRIVATE void tdsqlite3VdbeMemAboutToChange(Vdbe *pVdbe, Mem *pMem){
int i;
Mem *pX;
- for(i=0, pX=pVdbe->aMem; i<pVdbe->nMem; i++, pX++){
+ for(i=1, pX=pVdbe->aMem+1; i<pVdbe->nMem; i++, pX++){
if( pX->pScopyFrom==pMem ){
- pX->flags |= MEM_Undefined;
+ u16 mFlags;
+ if( pVdbe->db->flags & SQLITE_VdbeTrace ){
+ tdsqlite3DebugPrintf("Invalidate R[%d] due to change in R[%d]\n",
+ (int)(pX - pVdbe->aMem), (int)(pMem - pVdbe->aMem));
+ }
+ /* If pX is marked as a shallow copy of pMem, then verify that
+ ** no significant changes have been made to pX since the OP_SCopy.
+ ** A significant change would indicated a missed call to this
+ ** function for pX. Minor changes, such as adding or removing a
+ ** dual type, are allowed, as long as the underlying value is the
+ ** same. */
+ mFlags = pMem->flags & pX->flags & pX->mScopyFlags;
+ assert( (mFlags&(MEM_Int|MEM_IntReal))==0 || pMem->u.i==pX->u.i );
+ /* assert( (mFlags&MEM_Real)==0 || pMem->u.r==pX->u.r ); */
+ /* ^^ */
+ /* Cannot reliably compare doubles for equality */
+ assert( (mFlags&MEM_Str)==0 || (pMem->n==pX->n && pMem->z==pX->z) );
+ assert( (mFlags&MEM_Blob)==0 || tdsqlite3BlobCompare(pMem,pX)==0 );
+
+ /* pMem is the register that is changing. But also mark pX as
+ ** undefined so that we can quickly detect the shallow-copy error */
+ pX->flags = MEM_Undefined;
pX->pScopyFrom = 0;
}
}
@@ -72616,7 +80033,6 @@ SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe *pVdbe, Mem *pMem){
}
#endif /* SQLITE_DEBUG */
-
/*
** Make an shallow copy of pFrom into pTo. Prior contents of
** pTo are freed. The pFrom->z field is not duplicated. If
@@ -72626,10 +80042,10 @@ SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe *pVdbe, Mem *pMem){
static SQLITE_NOINLINE void vdbeClrCopy(Mem *pTo, const Mem *pFrom, int eType){
vdbeMemClearExternAndSetNull(pTo);
assert( !VdbeMemDynamic(pTo) );
- sqlite3VdbeMemShallowCopy(pTo, pFrom, eType);
+ tdsqlite3VdbeMemShallowCopy(pTo, pFrom, eType);
}
-SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem *pTo, const Mem *pFrom, int srcType){
- assert( (pFrom->flags & MEM_RowSet)==0 );
+SQLITE_PRIVATE void tdsqlite3VdbeMemShallowCopy(Mem *pTo, const Mem *pFrom, int srcType){
+ assert( !tdsqlite3VdbeMemIsRowSet(pFrom) );
assert( pTo->db==pFrom->db );
if( VdbeMemDynamic(pTo) ){ vdbeClrCopy(pTo,pFrom,srcType); return; }
memcpy(pTo, pFrom, MEMCELLSIZE);
@@ -72644,17 +80060,17 @@ SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem *pTo, const Mem *pFrom, int sr
** Make a full copy of pFrom into pTo. Prior contents of pTo are
** freed before the copy is made.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem *pTo, const Mem *pFrom){
+SQLITE_PRIVATE int tdsqlite3VdbeMemCopy(Mem *pTo, const Mem *pFrom){
int rc = SQLITE_OK;
- assert( (pFrom->flags & MEM_RowSet)==0 );
+ assert( !tdsqlite3VdbeMemIsRowSet(pFrom) );
if( VdbeMemDynamic(pTo) ) vdbeMemClearExternAndSetNull(pTo);
memcpy(pTo, pFrom, MEMCELLSIZE);
pTo->flags &= ~MEM_Dyn;
if( pTo->flags&(MEM_Str|MEM_Blob) ){
if( 0==(pFrom->flags&MEM_Static) ){
pTo->flags |= MEM_Ephem;
- rc = sqlite3VdbeMemMakeWriteable(pTo);
+ rc = tdsqlite3VdbeMemMakeWriteable(pTo);
}
}
@@ -72667,12 +80083,12 @@ SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem *pTo, const Mem *pFrom){
**
** pFrom contains an SQL NULL when this routine returns.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){
- assert( pFrom->db==0 || sqlite3_mutex_held(pFrom->db->mutex) );
- assert( pTo->db==0 || sqlite3_mutex_held(pTo->db->mutex) );
+SQLITE_PRIVATE void tdsqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){
+ assert( pFrom->db==0 || tdsqlite3_mutex_held(pFrom->db->mutex) );
+ assert( pTo->db==0 || tdsqlite3_mutex_held(pTo->db->mutex) );
assert( pFrom->db==0 || pTo->db==0 || pFrom->db==pTo->db );
- sqlite3VdbeMemRelease(pTo);
+ tdsqlite3VdbeMemRelease(pTo);
memcpy(pTo, pFrom, sizeof(Mem));
pFrom->flags = MEM_Null;
pFrom->szMalloc = 0;
@@ -72693,7 +80109,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){
** is required to store the string, then value of pMem is unchanged. In
** either case, SQLITE_TOOBIG is returned.
*/
-SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
+SQLITE_PRIVATE int tdsqlite3VdbeMemSetStr(
Mem *pMem, /* Memory cell to set to string value */
const char *z, /* String pointer */
int n, /* Bytes in string, or negative */
@@ -72704,12 +80120,12 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
int iLimit; /* Maximum allowed string or blob size */
u16 flags = 0; /* New value for pMem->flags */
- assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) );
- assert( (pMem->flags & MEM_RowSet)==0 );
+ assert( pMem->db==0 || tdsqlite3_mutex_held(pMem->db->mutex) );
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
/* If z is a NULL pointer, set pMem to contain an SQL NULL. */
if( !z ){
- sqlite3VdbeMemSetNull(pMem);
+ tdsqlite3VdbeMemSetNull(pMem);
return SQLITE_OK;
}
@@ -72722,8 +80138,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
if( nByte<0 ){
assert( enc!=0 );
if( enc==SQLITE_UTF8 ){
- nByte = sqlite3Strlen30(z);
- if( nByte>iLimit ) nByte = iLimit+1;
+ nByte = 0x7fffffff & (int)strlen(z);
}else{
for(nByte=0; nByte<=iLimit && (z[nByte] | z[nByte+1]); nByte+=2){}
}
@@ -72735,37 +80150,47 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
** management (one of MEM_Dyn or MEM_Static).
*/
if( xDel==SQLITE_TRANSIENT ){
- int nAlloc = nByte;
+ u32 nAlloc = nByte;
if( flags&MEM_Term ){
nAlloc += (enc==SQLITE_UTF8?1:2);
}
if( nByte>iLimit ){
- return SQLITE_TOOBIG;
+ return tdsqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG);
}
testcase( nAlloc==0 );
testcase( nAlloc==31 );
testcase( nAlloc==32 );
- if( sqlite3VdbeMemClearAndResize(pMem, MAX(nAlloc,32)) ){
+ if( tdsqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){
return SQLITE_NOMEM_BKPT;
}
memcpy(pMem->z, z, nAlloc);
- }else if( xDel==SQLITE_DYNAMIC ){
- sqlite3VdbeMemRelease(pMem);
- pMem->zMalloc = pMem->z = (char *)z;
- pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc);
}else{
- sqlite3VdbeMemRelease(pMem);
+ tdsqlite3VdbeMemRelease(pMem);
pMem->z = (char *)z;
- pMem->xDel = xDel;
- flags |= ((xDel==SQLITE_STATIC)?MEM_Static:MEM_Dyn);
+ if( xDel==SQLITE_DYNAMIC ){
+ pMem->zMalloc = pMem->z;
+ pMem->szMalloc = tdsqlite3DbMallocSize(pMem->db, pMem->zMalloc);
+ }else{
+ pMem->xDel = xDel;
+ flags |= ((xDel==SQLITE_STATIC)?MEM_Static:MEM_Dyn);
+ }
}
pMem->n = nByte;
pMem->flags = flags;
- pMem->enc = (enc==0 ? SQLITE_UTF8 : enc);
+ if( enc ){
+ pMem->enc = enc;
+#ifdef SQLITE_ENABLE_SESSION
+ }else if( pMem->db==0 ){
+ pMem->enc = SQLITE_UTF8;
+#endif
+ }else{
+ assert( pMem->db!=0 );
+ pMem->enc = ENC(pMem->db);
+ }
#ifndef SQLITE_OMIT_UTF16
- if( pMem->enc!=SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){
+ if( enc>SQLITE_UTF8 && tdsqlite3VdbeMemHandleBom(pMem) ){
return SQLITE_NOMEM_BKPT;
}
#endif
@@ -72779,10 +80204,9 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr(
/*
** Move data out of a btree key or data field and into a Mem structure.
-** The data or key is taken from the entry that pCur is currently pointing
+** The data is payload from the entry that pCur is currently pointing
** to. offset and amt determine what portion of the data or key to retrieve.
-** key is true to get the key or false to get data. The result is written
-** into the pMem element.
+** The result is written into the pMem element.
**
** The pMem object must have been initialized. This routine will use
** pMem->zMalloc to hold the content from the btree, if possible. New
@@ -72797,46 +80221,42 @@ static SQLITE_NOINLINE int vdbeMemFromBtreeResize(
BtCursor *pCur, /* Cursor pointing at record to retrieve. */
u32 offset, /* Offset from the start of data to return bytes from. */
u32 amt, /* Number of bytes to return. */
- int key, /* If true, retrieve from the btree key, not data. */
Mem *pMem /* OUT: Return data in this Mem structure. */
){
int rc;
pMem->flags = MEM_Null;
- if( SQLITE_OK==(rc = sqlite3VdbeMemClearAndResize(pMem, amt+2)) ){
- if( key ){
- rc = sqlite3BtreeKey(pCur, offset, amt, pMem->z);
- }else{
- rc = sqlite3BtreeData(pCur, offset, amt, pMem->z);
- }
+ if( tdsqlite3BtreeMaxRecordSize(pCur)<offset+amt ){
+ return SQLITE_CORRUPT_BKPT;
+ }
+ if( SQLITE_OK==(rc = tdsqlite3VdbeMemClearAndResize(pMem, amt+1)) ){
+ rc = tdsqlite3BtreePayload(pCur, offset, amt, pMem->z);
if( rc==SQLITE_OK ){
- pMem->z[amt] = 0;
- pMem->z[amt+1] = 0;
- pMem->flags = MEM_Blob|MEM_Term;
+ pMem->z[amt] = 0; /* Overrun area used when reading malformed records */
+ pMem->flags = MEM_Blob;
pMem->n = (int)amt;
}else{
- sqlite3VdbeMemRelease(pMem);
+ tdsqlite3VdbeMemRelease(pMem);
}
}
return rc;
}
-SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(
+SQLITE_PRIVATE int tdsqlite3VdbeMemFromBtree(
BtCursor *pCur, /* Cursor pointing at record to retrieve. */
u32 offset, /* Offset from the start of data to return bytes from. */
u32 amt, /* Number of bytes to return. */
- int key, /* If true, retrieve from the btree key, not data. */
Mem *pMem /* OUT: Return data in this Mem structure. */
){
char *zData; /* Data from the btree layer */
u32 available = 0; /* Number of bytes available on the local btree page */
int rc = SQLITE_OK; /* Return code */
- assert( sqlite3BtreeCursorIsValid(pCur) );
+ assert( tdsqlite3BtreeCursorIsValid(pCur) );
assert( !VdbeMemDynamic(pMem) );
/* Note: the calls to BtreeKeyFetch() and DataFetch() below assert()
** that both the BtShared and database handle mutexes are held. */
- assert( (pMem->flags & MEM_RowSet)==0 );
- zData = (char *)sqlite3BtreePayloadFetch(pCur, &available);
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem) );
+ zData = (char *)tdsqlite3BtreePayloadFetch(pCur, &available);
assert( zData!=0 );
if( offset+amt<=available ){
@@ -72844,7 +80264,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(
pMem->flags = MEM_Blob|MEM_Ephem;
pMem->n = (int)amt;
}else{
- rc = vdbeMemFromBtreeResize(pCur, offset, amt, key, pMem);
+ rc = vdbeMemFromBtreeResize(pCur, offset, amt, pMem);
}
return rc;
@@ -72855,31 +80275,33 @@ SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(
** Convert it into a string with encoding enc and return a pointer
** to a zero-terminated version of that string.
*/
-static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){
+static SQLITE_NOINLINE const void *valueToText(tdsqlite3_value* pVal, u8 enc){
assert( pVal!=0 );
- assert( pVal->db==0 || sqlite3_mutex_held(pVal->db->mutex) );
+ assert( pVal->db==0 || tdsqlite3_mutex_held(pVal->db->mutex) );
assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) );
- assert( (pVal->flags & MEM_RowSet)==0 );
+ assert( !tdsqlite3VdbeMemIsRowSet(pVal) );
assert( (pVal->flags & (MEM_Null))==0 );
if( pVal->flags & (MEM_Blob|MEM_Str) ){
+ if( ExpandBlob(pVal) ) return 0;
pVal->flags |= MEM_Str;
if( pVal->enc != (enc & ~SQLITE_UTF16_ALIGNED) ){
- sqlite3VdbeChangeEncoding(pVal, enc & ~SQLITE_UTF16_ALIGNED);
+ tdsqlite3VdbeChangeEncoding(pVal, enc & ~SQLITE_UTF16_ALIGNED);
}
if( (enc & SQLITE_UTF16_ALIGNED)!=0 && 1==(1&SQLITE_PTR_TO_INT(pVal->z)) ){
assert( (pVal->flags & (MEM_Ephem|MEM_Static))!=0 );
- if( sqlite3VdbeMemMakeWriteable(pVal)!=SQLITE_OK ){
+ if( tdsqlite3VdbeMemMakeWriteable(pVal)!=SQLITE_OK ){
return 0;
}
}
- sqlite3VdbeMemNulTerminate(pVal); /* IMP: R-31275-44060 */
+ tdsqlite3VdbeMemNulTerminate(pVal); /* IMP: R-31275-44060 */
}else{
- sqlite3VdbeMemStringify(pVal, enc, 0);
+ tdsqlite3VdbeMemStringify(pVal, enc, 0);
assert( 0==(1&SQLITE_PTR_TO_INT(pVal->z)) );
}
assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0
|| pVal->db->mallocFailed );
if( pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) ){
+ assert( tdsqlite3VdbeMemValidStrRep(pVal) );
return pVal->z;
}else{
return 0;
@@ -72887,7 +80309,7 @@ static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){
}
/* This function is only available internally, it is not part of the
-** external API. It works in a similar way to sqlite3_value_text(),
+** external API. It works in a similar way to tdsqlite3_value_text(),
** except the data returned is in the encoding specified by the second
** parameter, which must be one of SQLITE_UTF16BE, SQLITE_UTF16LE or
** SQLITE_UTF8.
@@ -72896,12 +80318,13 @@ static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){
** If that is the case, then the result must be aligned on an even byte
** boundary.
*/
-SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){
+SQLITE_PRIVATE const void *tdsqlite3ValueText(tdsqlite3_value* pVal, u8 enc){
if( !pVal ) return 0;
- assert( pVal->db==0 || sqlite3_mutex_held(pVal->db->mutex) );
+ assert( pVal->db==0 || tdsqlite3_mutex_held(pVal->db->mutex) );
assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) );
- assert( (pVal->flags & MEM_RowSet)==0 );
+ assert( !tdsqlite3VdbeMemIsRowSet(pVal) );
if( (pVal->flags&(MEM_Str|MEM_Term))==(MEM_Str|MEM_Term) && pVal->enc==enc ){
+ assert( tdsqlite3VdbeMemValidStrRep(pVal) );
return pVal->z;
}
if( pVal->flags&MEM_Null ){
@@ -72911,10 +80334,10 @@ SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){
}
/*
-** Create a new sqlite3_value object.
+** Create a new tdsqlite3_value object.
*/
-SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *db){
- Mem *p = sqlite3DbMallocZero(db, sizeof(*p));
+SQLITE_PRIVATE tdsqlite3_value *tdsqlite3ValueNew(tdsqlite3 *db){
+ Mem *p = tdsqlite3DbMallocZero(db, sizeof(*p));
if( p ){
p->flags = MEM_Null;
p->db = db;
@@ -72923,7 +80346,7 @@ SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *db){
}
/*
-** Context object passed by sqlite3Stat4ProbeSetValue() through to
+** Context object passed by tdsqlite3Stat4ProbeSetValue() through to
** valueNew(). See comments above valueNew() for details.
*/
struct ValueNewStat4Ctx {
@@ -72934,18 +80357,18 @@ struct ValueNewStat4Ctx {
};
/*
-** Allocate and return a pointer to a new sqlite3_value object. If
+** Allocate and return a pointer to a new tdsqlite3_value object. If
** the second argument to this function is NULL, the object is allocated
-** by calling sqlite3ValueNew().
+** by calling tdsqlite3ValueNew().
**
** Otherwise, if the second argument is non-zero, then this function is
-** being called indirectly by sqlite3Stat4ProbeSetValue(). If it has not
+** being called indirectly by tdsqlite3Stat4ProbeSetValue(). If it has not
** already been allocated, allocate the UnpackedRecord structure that
** that function will return to its caller here. Then return a pointer to
-** an sqlite3_value within the UnpackedRecord.a[] array.
+** an tdsqlite3_value within the UnpackedRecord.a[] array.
*/
-static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+static tdsqlite3_value *valueNew(tdsqlite3 *db, struct ValueNewStat4Ctx *p){
+#ifdef SQLITE_ENABLE_STAT4
if( p ){
UnpackedRecord *pRec = p->ppRec[0];
@@ -72956,11 +80379,11 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){
int nCol = pIdx->nColumn; /* Number of index columns including rowid */
nByte = sizeof(Mem) * nCol + ROUND8(sizeof(UnpackedRecord));
- pRec = (UnpackedRecord*)sqlite3DbMallocZero(db, nByte);
+ pRec = (UnpackedRecord*)tdsqlite3DbMallocZero(db, nByte);
if( pRec ){
- pRec->pKeyInfo = sqlite3KeyInfoOfIndex(p->pParse, pIdx);
+ pRec->pKeyInfo = tdsqlite3KeyInfoOfIndex(p->pParse, pIdx);
if( pRec->pKeyInfo ){
- assert( pRec->pKeyInfo->nField+pRec->pKeyInfo->nXField==nCol );
+ assert( pRec->pKeyInfo->nAllField==nCol );
assert( pRec->pKeyInfo->enc==ENC(db) );
pRec->aMem = (Mem *)((u8*)pRec + ROUND8(sizeof(UnpackedRecord)));
for(i=0; i<nCol; i++){
@@ -72968,7 +80391,7 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){
pRec->aMem[i].db = db;
}
}else{
- sqlite3DbFree(db, pRec);
+ tdsqlite3DbFreeNN(db, pRec);
pRec = 0;
}
}
@@ -72981,8 +80404,8 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){
}
#else
UNUSED_PARAMETER(p);
-#endif /* defined(SQLITE_ENABLE_STAT3_OR_STAT4) */
- return sqlite3ValueNew(db);
+#endif /* defined(SQLITE_ENABLE_STAT4) */
+ return tdsqlite3ValueNew(db);
}
/*
@@ -72998,27 +80421,27 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){
** object containing the result before returning SQLITE_OK.
**
** Affinity aff is applied to the result of the function before returning.
-** If the result is a text value, the sqlite3_value object uses encoding
+** If the result is a text value, the tdsqlite3_value object uses encoding
** enc.
**
** If the conditions above are not met, this function returns SQLITE_OK
** and sets (*ppVal) to NULL. Or, if an error occurs, (*ppVal) is set to
** NULL and an SQLite error code returned.
*/
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
static int valueFromFunction(
- sqlite3 *db, /* The database connection */
+ tdsqlite3 *db, /* The database connection */
Expr *p, /* The expression to evaluate */
u8 enc, /* Encoding to use */
u8 aff, /* Affinity to use */
- sqlite3_value **ppVal, /* Write the new value here */
+ tdsqlite3_value **ppVal, /* Write the new value here */
struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */
){
- sqlite3_context ctx; /* Context object for function invocation */
- sqlite3_value **apVal = 0; /* Function arguments */
+ tdsqlite3_context ctx; /* Context object for function invocation */
+ tdsqlite3_value **apVal = 0; /* Function arguments */
int nVal = 0; /* Size of apVal[] array */
FuncDef *pFunc = 0; /* Function definition */
- sqlite3_value *pVal = 0; /* New value */
+ tdsqlite3_value *pVal = 0; /* New value */
int rc = SQLITE_OK; /* Return code */
ExprList *pList = 0; /* Function arguments */
int i; /* Iterator variable */
@@ -73027,7 +80450,7 @@ static int valueFromFunction(
assert( (p->flags & EP_TokenOnly)==0 );
pList = p->x.pList;
if( pList ) nVal = pList->nExpr;
- pFunc = sqlite3FindFunction(db, p->u.zToken, nVal, enc, 0);
+ pFunc = tdsqlite3FindFunction(db, p->u.zToken, nVal, enc, 0);
assert( pFunc );
if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0
|| (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL)
@@ -73036,13 +80459,13 @@ static int valueFromFunction(
}
if( pList ){
- apVal = (sqlite3_value**)sqlite3DbMallocZero(db, sizeof(apVal[0]) * nVal);
+ apVal = (tdsqlite3_value**)tdsqlite3DbMallocZero(db, sizeof(apVal[0]) * nVal);
if( apVal==0 ){
rc = SQLITE_NOMEM_BKPT;
goto value_from_function_out;
}
for(i=0; i<nVal; i++){
- rc = sqlite3ValueFromExpr(db, pList->a[i].pExpr, enc, aff, &apVal[i]);
+ rc = tdsqlite3ValueFromExpr(db, pList->a[i].pExpr, enc, aff, &apVal[i]);
if( apVal[i]==0 || rc!=SQLITE_OK ) goto value_from_function_out;
}
}
@@ -73060,12 +80483,12 @@ static int valueFromFunction(
pFunc->xSFunc(&ctx, nVal, apVal);
if( ctx.isError ){
rc = ctx.isError;
- sqlite3ErrorMsg(pCtx->pParse, "%s", sqlite3_value_text(pVal));
+ tdsqlite3ErrorMsg(pCtx->pParse, "%s", tdsqlite3_value_text(pVal));
}else{
- sqlite3ValueApplyAffinity(pVal, aff, SQLITE_UTF8);
+ tdsqlite3ValueApplyAffinity(pVal, aff, SQLITE_UTF8);
assert( rc==SQLITE_OK );
- rc = sqlite3VdbeChangeEncoding(pVal, enc);
- if( rc==SQLITE_OK && sqlite3VdbeMemTooBig(pVal) ){
+ rc = tdsqlite3VdbeChangeEncoding(pVal, enc);
+ if( rc==SQLITE_OK && tdsqlite3VdbeMemTooBig(pVal) ){
rc = SQLITE_TOOBIG;
pCtx->pParse->nErr++;
}
@@ -73078,9 +80501,9 @@ static int valueFromFunction(
}
if( apVal ){
for(i=0; i<nVal; i++){
- sqlite3ValueFree(apVal[i]);
+ tdsqlite3ValueFree(apVal[i]);
}
- sqlite3DbFree(db, apVal);
+ tdsqlite3DbFreeNN(db, apVal);
}
*ppVal = pVal;
@@ -73088,36 +80511,40 @@ static int valueFromFunction(
}
#else
# define valueFromFunction(a,b,c,d,e,f) SQLITE_OK
-#endif /* defined(SQLITE_ENABLE_STAT3_OR_STAT4) */
+#endif /* defined(SQLITE_ENABLE_STAT4) */
/*
** Extract a value from the supplied expression in the manner described
-** above sqlite3ValueFromExpr(). Allocate the sqlite3_value object
+** above tdsqlite3ValueFromExpr(). Allocate the tdsqlite3_value object
** using valueNew().
**
-** If pCtx is NULL and an error occurs after the sqlite3_value object
+** If pCtx is NULL and an error occurs after the tdsqlite3_value object
** has been allocated, it is freed before returning. Or, if pCtx is not
** NULL, it is assumed that the caller will free any allocated object
** in all cases.
*/
static int valueFromExpr(
- sqlite3 *db, /* The database connection */
+ tdsqlite3 *db, /* The database connection */
Expr *pExpr, /* The expression to evaluate */
u8 enc, /* Encoding to use */
u8 affinity, /* Affinity to use */
- sqlite3_value **ppVal, /* Write the new value here */
+ tdsqlite3_value **ppVal, /* Write the new value here */
struct ValueNewStat4Ctx *pCtx /* Second argument for valueNew() */
){
int op;
char *zVal = 0;
- sqlite3_value *pVal = 0;
+ tdsqlite3_value *pVal = 0;
int negInt = 1;
const char *zNeg = "";
int rc = SQLITE_OK;
assert( pExpr!=0 );
while( (op = pExpr->op)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft;
+#if defined(SQLITE_ENABLE_STAT4)
+ if( op==TK_REGISTER ) op = pExpr->op2;
+#else
if( NEVER(op==TK_REGISTER) ) op = pExpr->op2;
+#endif
/* Compressed expressions only appear when parsing the DEFAULT clause
** on a table column definition, and hence only when pCtx==0. This
@@ -73126,12 +80553,12 @@ static int valueFromExpr(
assert( (pExpr->flags & EP_TokenOnly)==0 || pCtx==0 );
if( op==TK_CAST ){
- u8 aff = sqlite3AffinityType(pExpr->u.zToken,0);
+ u8 aff = tdsqlite3AffinityType(pExpr->u.zToken,0);
rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx);
testcase( rc!=SQLITE_OK );
if( *ppVal ){
- sqlite3VdbeMemCast(*ppVal, aff, SQLITE_UTF8);
- sqlite3ValueApplyAffinity(*ppVal, affinity, SQLITE_UTF8);
+ tdsqlite3VdbeMemCast(*ppVal, aff, SQLITE_UTF8);
+ tdsqlite3ValueApplyAffinity(*ppVal, affinity, SQLITE_UTF8);
}
return rc;
}
@@ -73151,40 +80578,50 @@ static int valueFromExpr(
pVal = valueNew(db, pCtx);
if( pVal==0 ) goto no_mem;
if( ExprHasProperty(pExpr, EP_IntValue) ){
- sqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue*negInt);
+ tdsqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue*negInt);
}else{
- zVal = sqlite3MPrintf(db, "%s%s", zNeg, pExpr->u.zToken);
+ zVal = tdsqlite3MPrintf(db, "%s%s", zNeg, pExpr->u.zToken);
if( zVal==0 ) goto no_mem;
- sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, SQLITE_DYNAMIC);
+ tdsqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, SQLITE_DYNAMIC);
}
if( (op==TK_INTEGER || op==TK_FLOAT ) && affinity==SQLITE_AFF_BLOB ){
- sqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC, SQLITE_UTF8);
+ tdsqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC, SQLITE_UTF8);
}else{
- sqlite3ValueApplyAffinity(pVal, affinity, SQLITE_UTF8);
+ tdsqlite3ValueApplyAffinity(pVal, affinity, SQLITE_UTF8);
+ }
+ assert( (pVal->flags & MEM_IntReal)==0 );
+ if( pVal->flags & (MEM_Int|MEM_IntReal|MEM_Real) ){
+ testcase( pVal->flags & MEM_Int );
+ testcase( pVal->flags & MEM_Real );
+ pVal->flags &= ~MEM_Str;
}
- if( pVal->flags & (MEM_Int|MEM_Real) ) pVal->flags &= ~MEM_Str;
if( enc!=SQLITE_UTF8 ){
- rc = sqlite3VdbeChangeEncoding(pVal, enc);
+ rc = tdsqlite3VdbeChangeEncoding(pVal, enc);
}
}else if( op==TK_UMINUS ) {
/* This branch happens for multiple negative signs. Ex: -(-5) */
- if( SQLITE_OK==sqlite3ValueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal)
+ if( SQLITE_OK==valueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal,pCtx)
&& pVal!=0
){
- sqlite3VdbeMemNumerify(pVal);
+ tdsqlite3VdbeMemNumerify(pVal);
if( pVal->flags & MEM_Real ){
pVal->u.r = -pVal->u.r;
}else if( pVal->u.i==SMALLEST_INT64 ){
+#ifndef SQLITE_OMIT_FLOATING_POINT
pVal->u.r = -(double)SMALLEST_INT64;
+#else
+ pVal->u.r = LARGEST_INT64;
+#endif
MemSetTypeFlag(pVal, MEM_Real);
}else{
pVal->u.i = -pVal->u.i;
}
- sqlite3ValueApplyAffinity(pVal, affinity, enc);
+ tdsqlite3ValueApplyAffinity(pVal, affinity, enc);
}
}else if( op==TK_NULL ){
pVal = valueNew(db, pCtx);
if( pVal==0 ) goto no_mem;
+ tdsqlite3VdbeMemSetNull(pVal);
}
#ifndef SQLITE_OMIT_BLOB_LITERAL
else if( op==TK_BLOB ){
@@ -73194,104 +80631,64 @@ static int valueFromExpr(
pVal = valueNew(db, pCtx);
if( !pVal ) goto no_mem;
zVal = &pExpr->u.zToken[2];
- nVal = sqlite3Strlen30(zVal)-1;
+ nVal = tdsqlite3Strlen30(zVal)-1;
assert( zVal[nVal]=='\'' );
- sqlite3VdbeMemSetStr(pVal, sqlite3HexToBlob(db, zVal, nVal), nVal/2,
+ tdsqlite3VdbeMemSetStr(pVal, tdsqlite3HexToBlob(db, zVal, nVal), nVal/2,
0, SQLITE_DYNAMIC);
}
#endif
-
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
else if( op==TK_FUNCTION && pCtx!=0 ){
rc = valueFromFunction(db, pExpr, enc, affinity, &pVal, pCtx);
}
#endif
+ else if( op==TK_TRUEFALSE ){
+ pVal = valueNew(db, pCtx);
+ if( pVal ){
+ pVal->flags = MEM_Int;
+ pVal->u.i = pExpr->u.zToken[4]==0;
+ }
+ }
*ppVal = pVal;
return rc;
no_mem:
- sqlite3OomFault(db);
- sqlite3DbFree(db, zVal);
+#ifdef SQLITE_ENABLE_STAT4
+ if( pCtx==0 || pCtx->pParse->nErr==0 )
+#endif
+ tdsqlite3OomFault(db);
+ tdsqlite3DbFree(db, zVal);
assert( *ppVal==0 );
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- if( pCtx==0 ) sqlite3ValueFree(pVal);
+#ifdef SQLITE_ENABLE_STAT4
+ if( pCtx==0 ) tdsqlite3ValueFree(pVal);
#else
- assert( pCtx==0 ); sqlite3ValueFree(pVal);
+ assert( pCtx==0 ); tdsqlite3ValueFree(pVal);
#endif
return SQLITE_NOMEM_BKPT;
}
/*
-** Create a new sqlite3_value object, containing the value of pExpr.
+** Create a new tdsqlite3_value object, containing the value of pExpr.
**
** This only works for very simple expressions that consist of one constant
** token (i.e. "5", "5.1", "'a string'"). If the expression can
** be converted directly into a value, then the value is allocated and
** a pointer written to *ppVal. The caller is responsible for deallocating
-** the value by passing it to sqlite3ValueFree() later on. If the expression
+** the value by passing it to tdsqlite3ValueFree() later on. If the expression
** cannot be converted to a value, then *ppVal is set to NULL.
*/
-SQLITE_PRIVATE int sqlite3ValueFromExpr(
- sqlite3 *db, /* The database connection */
+SQLITE_PRIVATE int tdsqlite3ValueFromExpr(
+ tdsqlite3 *db, /* The database connection */
Expr *pExpr, /* The expression to evaluate */
u8 enc, /* Encoding to use */
u8 affinity, /* Affinity to use */
- sqlite3_value **ppVal /* Write the new value here */
+ tdsqlite3_value **ppVal /* Write the new value here */
){
return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0;
}
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
-/*
-** The implementation of the sqlite_record() function. This function accepts
-** a single argument of any type. The return value is a formatted database
-** record (a blob) containing the argument value.
-**
-** This is used to convert the value stored in the 'sample' column of the
-** sqlite_stat3 table to the record format SQLite uses internally.
-*/
-static void recordFunc(
- sqlite3_context *context,
- int argc,
- sqlite3_value **argv
-){
- const int file_format = 1;
- u32 iSerial; /* Serial type */
- int nSerial; /* Bytes of space for iSerial as varint */
- u32 nVal; /* Bytes of space required for argv[0] */
- int nRet;
- sqlite3 *db;
- u8 *aRet;
-
- UNUSED_PARAMETER( argc );
- iSerial = sqlite3VdbeSerialType(argv[0], file_format, &nVal);
- nSerial = sqlite3VarintLen(iSerial);
- db = sqlite3_context_db_handle(context);
-
- nRet = 1 + nSerial + nVal;
- aRet = sqlite3DbMallocRawNN(db, nRet);
- if( aRet==0 ){
- sqlite3_result_error_nomem(context);
- }else{
- aRet[0] = nSerial+1;
- putVarint32(&aRet[1], iSerial);
- sqlite3VdbeSerialPut(&aRet[1+nSerial], argv[0], iSerial);
- sqlite3_result_blob(context, aRet, nRet, SQLITE_TRANSIENT);
- sqlite3DbFree(db, aRet);
- }
-}
-
-/*
-** Register built-in functions used to help read ANALYZE data.
-*/
-SQLITE_PRIVATE void sqlite3AnalyzeFunctions(void){
- static FuncDef aAnalyzeTableFuncs[] = {
- FUNCTION(sqlite_record, 1, 0, 0, recordFunc),
- };
- sqlite3InsertBuiltinFuncs(aAnalyzeTableFuncs, ArraySize(aAnalyzeTableFuncs));
-}
-
+#ifdef SQLITE_ENABLE_STAT4
/*
** Attempt to extract a value from pExpr and use it to construct *ppVal.
**
@@ -73315,33 +80712,30 @@ static int stat4ValueFromExpr(
Expr *pExpr, /* The expression to extract a value from */
u8 affinity, /* Affinity to use */
struct ValueNewStat4Ctx *pAlloc,/* How to allocate space. Or NULL */
- sqlite3_value **ppVal /* OUT: New value object (or NULL) */
+ tdsqlite3_value **ppVal /* OUT: New value object (or NULL) */
){
int rc = SQLITE_OK;
- sqlite3_value *pVal = 0;
- sqlite3 *db = pParse->db;
+ tdsqlite3_value *pVal = 0;
+ tdsqlite3 *db = pParse->db;
/* Skip over any TK_COLLATE nodes */
- pExpr = sqlite3ExprSkipCollate(pExpr);
+ pExpr = tdsqlite3ExprSkipCollate(pExpr);
+ assert( pExpr==0 || pExpr->op!=TK_REGISTER || pExpr->op2!=TK_VARIABLE );
if( !pExpr ){
pVal = valueNew(db, pAlloc);
if( pVal ){
- sqlite3VdbeMemSetNull((Mem*)pVal);
+ tdsqlite3VdbeMemSetNull((Mem*)pVal);
}
- }else if( pExpr->op==TK_VARIABLE
- || NEVER(pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE)
- ){
+ }else if( pExpr->op==TK_VARIABLE && (db->flags & SQLITE_EnableQPSG)==0 ){
Vdbe *v;
int iBindVar = pExpr->iColumn;
- sqlite3VdbeSetVarmask(pParse->pVdbe, iBindVar);
+ tdsqlite3VdbeSetVarmask(pParse->pVdbe, iBindVar);
if( (v = pParse->pReprepare)!=0 ){
pVal = valueNew(db, pAlloc);
if( pVal ){
- rc = sqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]);
- if( rc==SQLITE_OK ){
- sqlite3ValueApplyAffinity(pVal, affinity, ENC(db));
- }
+ rc = tdsqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]);
+ tdsqlite3ValueApplyAffinity(pVal, affinity, ENC(db));
pVal->db = pParse->db;
}
}
@@ -73367,7 +80761,7 @@ static int stat4ValueFromExpr(
**
** * The expression is a bound variable, and this is a reprepare, or
**
-** * The sqlite3ValueFromExpr() function is able to extract a value
+** * The tdsqlite3ValueFromExpr() function is able to extract a value
** from the expression (i.e. the expression is a literal value).
**
** Or, if pExpr is a TK_VECTOR, one field is populated for each of the
@@ -73388,7 +80782,7 @@ static int stat4ValueFromExpr(
** error if a value cannot be extracted from pExpr. If an error does
** occur, an SQLite error code is returned.
*/
-SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue(
+SQLITE_PRIVATE int tdsqlite3Stat4ProbeSetValue(
Parse *pParse, /* Parse context */
Index *pIdx, /* Index being probed */
UnpackedRecord **ppRec, /* IN/OUT: Probe record */
@@ -73409,9 +80803,9 @@ SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue(
alloc.ppRec = ppRec;
for(i=0; i<nElem; i++){
- sqlite3_value *pVal = 0;
- Expr *pElem = (pExpr ? sqlite3VectorFieldSubexpr(pExpr, i) : 0);
- u8 aff = sqlite3IndexColumnAffinity(pParse->db, pIdx, iVal+i);
+ tdsqlite3_value *pVal = 0;
+ Expr *pElem = (pExpr ? tdsqlite3VectorFieldSubexpr(pExpr, i) : 0);
+ u8 aff = tdsqlite3IndexColumnAffinity(pParse->db, pIdx, iVal+i);
alloc.iVal = iVal+i;
rc = stat4ValueFromExpr(pParse, pElem, aff, &alloc, &pVal);
if( !pVal ) break;
@@ -73425,7 +80819,7 @@ SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue(
/*
** Attempt to extract a value from expression pExpr using the methods
-** as described for sqlite3Stat4ProbeSetValue() above.
+** as described for tdsqlite3Stat4ProbeSetValue() above.
**
** If successful, set *ppVal to point to a new value object and return
** SQLITE_OK. If no value can be extracted, but no other error occurs
@@ -73433,11 +80827,11 @@ SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue(
** does occur, return an SQLite error code. The final value of *ppVal
** is undefined in this case.
*/
-SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr(
+SQLITE_PRIVATE int tdsqlite3Stat4ValueFromExpr(
Parse *pParse, /* Parse context */
Expr *pExpr, /* The expression to extract a value from */
u8 affinity, /* Affinity to use */
- sqlite3_value **ppVal /* OUT: New value object (or NULL) */
+ tdsqlite3_value **ppVal /* OUT: New value object (or NULL) */
){
return stat4ValueFromExpr(pParse, pExpr, affinity, 0, ppVal);
}
@@ -73445,23 +80839,23 @@ SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr(
/*
** Extract the iCol-th column from the nRec-byte record in pRec. Write
** the column value into *ppVal. If *ppVal is initially NULL then a new
-** sqlite3_value object is allocated.
+** tdsqlite3_value object is allocated.
**
** If *ppVal is initially NULL then the caller is responsible for
** ensuring that the value written into *ppVal is eventually freed.
*/
-SQLITE_PRIVATE int sqlite3Stat4Column(
- sqlite3 *db, /* Database handle */
+SQLITE_PRIVATE int tdsqlite3Stat4Column(
+ tdsqlite3 *db, /* Database handle */
const void *pRec, /* Pointer to buffer containing record */
int nRec, /* Size of buffer pRec in bytes */
int iCol, /* Column to extract */
- sqlite3_value **ppVal /* OUT: Extracted value */
+ tdsqlite3_value **ppVal /* OUT: Extracted value */
){
- u32 t; /* a column type code */
+ u32 t = 0; /* a column type code */
int nHdr; /* Size of the header in the record */
int iHdr; /* Next unread header byte */
int iField; /* Next unread data byte */
- int szField; /* Size of the current data field */
+ int szField = 0; /* Size of the current data field */
int i; /* Column index */
u8 *a = (u8*)pRec; /* Typecast byte array */
Mem *pMem = *ppVal; /* Write result into this Mem object */
@@ -73475,72 +80869,72 @@ SQLITE_PRIVATE int sqlite3Stat4Column(
testcase( iHdr==nHdr );
testcase( iHdr==nHdr+1 );
if( iHdr>nHdr ) return SQLITE_CORRUPT_BKPT;
- szField = sqlite3VdbeSerialTypeLen(t);
+ szField = tdsqlite3VdbeSerialTypeLen(t);
iField += szField;
}
testcase( iField==nRec );
testcase( iField==nRec+1 );
if( iField>nRec ) return SQLITE_CORRUPT_BKPT;
if( pMem==0 ){
- pMem = *ppVal = sqlite3ValueNew(db);
+ pMem = *ppVal = tdsqlite3ValueNew(db);
if( pMem==0 ) return SQLITE_NOMEM_BKPT;
}
- sqlite3VdbeSerialGet(&a[iField-szField], t, pMem);
+ tdsqlite3VdbeSerialGet(&a[iField-szField], t, pMem);
pMem->enc = ENC(db);
return SQLITE_OK;
}
/*
** Unless it is NULL, the argument must be an UnpackedRecord object returned
-** by an earlier call to sqlite3Stat4ProbeSetValue(). This call deletes
+** by an earlier call to tdsqlite3Stat4ProbeSetValue(). This call deletes
** the object.
*/
-SQLITE_PRIVATE void sqlite3Stat4ProbeFree(UnpackedRecord *pRec){
+SQLITE_PRIVATE void tdsqlite3Stat4ProbeFree(UnpackedRecord *pRec){
if( pRec ){
int i;
- int nCol = pRec->pKeyInfo->nField+pRec->pKeyInfo->nXField;
+ int nCol = pRec->pKeyInfo->nAllField;
Mem *aMem = pRec->aMem;
- sqlite3 *db = aMem[0].db;
+ tdsqlite3 *db = aMem[0].db;
for(i=0; i<nCol; i++){
- sqlite3VdbeMemRelease(&aMem[i]);
+ tdsqlite3VdbeMemRelease(&aMem[i]);
}
- sqlite3KeyInfoUnref(pRec->pKeyInfo);
- sqlite3DbFree(db, pRec);
+ tdsqlite3KeyInfoUnref(pRec->pKeyInfo);
+ tdsqlite3DbFreeNN(db, pRec);
}
}
#endif /* ifdef SQLITE_ENABLE_STAT4 */
/*
-** Change the string value of an sqlite3_value object
+** Change the string value of an tdsqlite3_value object
*/
-SQLITE_PRIVATE void sqlite3ValueSetStr(
- sqlite3_value *v, /* Value to be set */
+SQLITE_PRIVATE void tdsqlite3ValueSetStr(
+ tdsqlite3_value *v, /* Value to be set */
int n, /* Length of string z */
const void *z, /* Text of the new string */
u8 enc, /* Encoding to use */
void (*xDel)(void*) /* Destructor for the string */
){
- if( v ) sqlite3VdbeMemSetStr((Mem *)v, z, n, enc, xDel);
+ if( v ) tdsqlite3VdbeMemSetStr((Mem *)v, z, n, enc, xDel);
}
/*
-** Free an sqlite3_value object
+** Free an tdsqlite3_value object
*/
-SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value *v){
+SQLITE_PRIVATE void tdsqlite3ValueFree(tdsqlite3_value *v){
if( !v ) return;
- sqlite3VdbeMemRelease((Mem *)v);
- sqlite3DbFree(((Mem*)v)->db, v);
+ tdsqlite3VdbeMemRelease((Mem *)v);
+ tdsqlite3DbFreeNN(((Mem*)v)->db, v);
}
/*
-** The sqlite3ValueBytes() routine returns the number of bytes in the
-** sqlite3_value object assuming that it uses the encoding "enc".
+** The tdsqlite3ValueBytes() routine returns the number of bytes in the
+** tdsqlite3_value object assuming that it uses the encoding "enc".
** The valueBytes() routine is a helper function.
*/
-static SQLITE_NOINLINE int valueBytes(sqlite3_value *pVal, u8 enc){
+static SQLITE_NOINLINE int valueBytes(tdsqlite3_value *pVal, u8 enc){
return valueToText(pVal, enc)!=0 ? pVal->n : 0;
}
-SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){
+SQLITE_PRIVATE int tdsqlite3ValueBytes(tdsqlite3_value *pVal, u8 enc){
Mem *p = (Mem*)pVal;
assert( (p->flags & MEM_Null)==0 || (p->flags & (MEM_Str|MEM_Blob))==0 );
if( (p->flags & MEM_Str)!=0 && pVal->enc==enc ){
@@ -73571,18 +80965,22 @@ SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){
**
*************************************************************************
** This file contains code used for creating, destroying, and populating
-** a VDBE (or an "sqlite3_stmt" as it is known to the outside world.)
+** a VDBE (or an "tdsqlite3_stmt" as it is known to the outside world.)
*/
/* #include "sqliteInt.h" */
/* #include "vdbeInt.h" */
+/* Forward references */
+static void freeEphemeralFunction(tdsqlite3 *db, FuncDef *pDef);
+static void vdbeFreeOpArray(tdsqlite3 *, Op *, int);
+
/*
** Create a new virtual database engine.
*/
-SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){
- sqlite3 *db = pParse->db;
+SQLITE_PRIVATE Vdbe *tdsqlite3VdbeCreate(Parse *pParse){
+ tdsqlite3 *db = pParse->db;
Vdbe *p;
- p = sqlite3DbMallocRawNN(db, sizeof(Vdbe) );
+ p = tdsqlite3DbMallocRawNN(db, sizeof(Vdbe) );
if( p==0 ) return 0;
memset(&p->aOp, 0, sizeof(Vdbe)-offsetof(Vdbe,aOp));
p->db = db;
@@ -73594,42 +80992,87 @@ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){
db->pVdbe = p;
p->magic = VDBE_MAGIC_INIT;
p->pParse = pParse;
+ pParse->pVdbe = p;
assert( pParse->aLabel==0 );
assert( pParse->nLabel==0 );
- assert( pParse->nOpAlloc==0 );
+ assert( p->nOpAlloc==0 );
assert( pParse->szOpAlloc==0 );
+ tdsqlite3VdbeAddOp2(p, OP_Init, 0, 1);
return p;
}
/*
+** Return the Parse object that owns a Vdbe object.
+*/
+SQLITE_PRIVATE Parse *tdsqlite3VdbeParser(Vdbe *p){
+ return p->pParse;
+}
+
+/*
** Change the error string stored in Vdbe.zErrMsg
*/
-SQLITE_PRIVATE void sqlite3VdbeError(Vdbe *p, const char *zFormat, ...){
+SQLITE_PRIVATE void tdsqlite3VdbeError(Vdbe *p, const char *zFormat, ...){
va_list ap;
- sqlite3DbFree(p->db, p->zErrMsg);
+ tdsqlite3DbFree(p->db, p->zErrMsg);
va_start(ap, zFormat);
- p->zErrMsg = sqlite3VMPrintf(p->db, zFormat, ap);
+ p->zErrMsg = tdsqlite3VMPrintf(p->db, zFormat, ap);
va_end(ap);
}
/*
** Remember the SQL string for a prepared statement.
*/
-SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe *p, const char *z, int n, int isPrepareV2){
- assert( isPrepareV2==1 || isPrepareV2==0 );
+SQLITE_PRIVATE void tdsqlite3VdbeSetSql(Vdbe *p, const char *z, int n, u8 prepFlags){
if( p==0 ) return;
-#if defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_ENABLE_SQLLOG)
- if( !isPrepareV2 ) return;
-#endif
+ p->prepFlags = prepFlags;
+ if( (prepFlags & SQLITE_PREPARE_SAVESQL)==0 ){
+ p->expmask = 0;
+ }
assert( p->zSql==0 );
- p->zSql = sqlite3DbStrNDup(p->db, z, n);
- p->isPrepareV2 = (u8)isPrepareV2;
+ p->zSql = tdsqlite3DbStrNDup(p->db, z, n);
+}
+
+#ifdef SQLITE_ENABLE_NORMALIZE
+/*
+** Add a new element to the Vdbe->pDblStr list.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeAddDblquoteStr(tdsqlite3 *db, Vdbe *p, const char *z){
+ if( p ){
+ int n = tdsqlite3Strlen30(z);
+ DblquoteStr *pStr = tdsqlite3DbMallocRawNN(db,
+ sizeof(*pStr)+n+1-sizeof(pStr->z));
+ if( pStr ){
+ pStr->pNextStr = p->pDblStr;
+ p->pDblStr = pStr;
+ memcpy(pStr->z, z, n+1);
+ }
+ }
+}
+#endif
+
+#ifdef SQLITE_ENABLE_NORMALIZE
+/*
+** zId of length nId is a double-quoted identifier. Check to see if
+** that identifier is really used as a string literal.
+*/
+SQLITE_PRIVATE int tdsqlite3VdbeUsesDoubleQuotedString(
+ Vdbe *pVdbe, /* The prepared statement */
+ const char *zId /* The double-quoted identifier, already dequoted */
+){
+ DblquoteStr *pStr;
+ assert( zId!=0 );
+ if( pVdbe->pDblStr==0 ) return 0;
+ for(pStr=pVdbe->pDblStr; pStr; pStr=pStr->pNextStr){
+ if( strcmp(zId, pStr->z)==0 ) return 1;
+ }
+ return 0;
}
+#endif
/*
** Swap all content between two VDBE structures.
*/
-SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){
+SQLITE_PRIVATE void tdsqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){
Vdbe tmp, *pTmp;
char *zTmp;
assert( pA->db==pB->db );
@@ -73645,7 +81088,15 @@ SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){
zTmp = pA->zSql;
pA->zSql = pB->zSql;
pB->zSql = zTmp;
- pB->isPrepareV2 = pA->isPrepareV2;
+#ifdef SQLITE_ENABLE_NORMALIZE
+ zTmp = pA->zNormSql;
+ pA->zNormSql = pB->zNormSql;
+ pB->zNormSql = zTmp;
+#endif
+ pB->expmask = pA->expmask;
+ pB->prepFlags = pA->prepFlags;
+ memcpy(pB->aCounter, pA->aCounter, sizeof(pB->aCounter));
+ pB->aCounter[SQLITE_STMTSTATUS_REPREPARE]++;
}
/*
@@ -73654,7 +81105,7 @@ SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){
** to 1024/sizeof(Op).
**
** If an out-of-memory error occurs while resizing the array, return
-** SQLITE_NOMEM. In this case Vdbe.aOp and Parse.nOpAlloc remain
+** SQLITE_NOMEM. In this case Vdbe.aOp and Vdbe.nOpAlloc remain
** unchanged (this is so that any opcodes already allocated can be
** correctly deallocated along with the rest of the Vdbe).
*/
@@ -73670,18 +81121,26 @@ static int growOpArray(Vdbe *v, int nOp){
** operation (without SQLITE_TEST_REALLOC_STRESS) is to double the current
** size of the op array or add 1KB of space, whichever is smaller. */
#ifdef SQLITE_TEST_REALLOC_STRESS
- int nNew = (p->nOpAlloc>=512 ? p->nOpAlloc*2 : p->nOpAlloc+nOp);
+ tdsqlite3_int64 nNew = (v->nOpAlloc>=512 ? 2*(tdsqlite3_int64)v->nOpAlloc
+ : (tdsqlite3_int64)v->nOpAlloc+nOp);
#else
- int nNew = (p->nOpAlloc ? p->nOpAlloc*2 : (int)(1024/sizeof(Op)));
+ tdsqlite3_int64 nNew = (v->nOpAlloc ? 2*(tdsqlite3_int64)v->nOpAlloc
+ : (tdsqlite3_int64)(1024/sizeof(Op)));
UNUSED_PARAMETER(nOp);
#endif
+ /* Ensure that the size of a VDBE does not grow too large */
+ if( nNew > p->db->aLimit[SQLITE_LIMIT_VDBE_OP] ){
+ tdsqlite3OomFault(p->db);
+ return SQLITE_NOMEM;
+ }
+
assert( nOp<=(1024/sizeof(Op)) );
- assert( nNew>=(p->nOpAlloc+nOp) );
- pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op));
+ assert( nNew>=(v->nOpAlloc+nOp) );
+ pNew = tdsqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op));
if( pNew ){
- p->szOpAlloc = sqlite3DbMallocSize(p->db, pNew);
- p->nOpAlloc = p->szOpAlloc/sizeof(Op);
+ p->szOpAlloc = tdsqlite3DbMallocSize(p->db, pNew);
+ v->nOpAlloc = p->szOpAlloc/sizeof(Op);
v->aOp = pNew;
}
return (pNew ? SQLITE_OK : SQLITE_NOMEM_BKPT);
@@ -73690,9 +81149,16 @@ static int growOpArray(Vdbe *v, int nOp){
#ifdef SQLITE_DEBUG
/* This routine is just a convenient place to set a breakpoint that will
** fire after each opcode is inserted and displayed using
-** "PRAGMA vdbe_addoptrace=on".
+** "PRAGMA vdbe_addoptrace=on". Parameters "pc" (program counter) and
+** pOp are available to make the breakpoint conditional.
+**
+** Other useful labels for breakpoints include:
+** test_trace_breakpoint(pc,pOp)
+** tdsqlite3CorruptError(lineno)
+** tdsqlite3MisuseError(lineno)
+** tdsqlite3CantopenError(lineno)
*/
-static void test_addop_breakpoint(void){
+static void test_addop_breakpoint(int pc, Op *pOp){
static int n = 0;
n++;
}
@@ -73710,24 +81176,24 @@ static void test_addop_breakpoint(void){
**
** p1, p2, p3 Operands
**
-** Use the sqlite3VdbeResolveLabel() function to fix an address and
-** the sqlite3VdbeChangeP4() function to change the value of the P4
+** Use the tdsqlite3VdbeResolveLabel() function to fix an address and
+** the tdsqlite3VdbeChangeP4() function to change the value of the P4
** operand.
*/
static SQLITE_NOINLINE int growOp3(Vdbe *p, int op, int p1, int p2, int p3){
- assert( p->pParse->nOpAlloc<=p->nOp );
+ assert( p->nOpAlloc<=p->nOp );
if( growOpArray(p, 1) ) return 1;
- assert( p->pParse->nOpAlloc>p->nOp );
- return sqlite3VdbeAddOp3(p, op, p1, p2, p3);
+ assert( p->nOpAlloc>p->nOp );
+ return tdsqlite3VdbeAddOp3(p, op, p1, p2, p3);
}
-SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){
int i;
VdbeOp *pOp;
i = p->nOp;
assert( p->magic==VDBE_MAGIC_INIT );
assert( op>=0 && op<0xff );
- if( p->pParse->nOpAlloc<=i ){
+ if( p->nOpAlloc<=i ){
return growOp3(p, op, p1, p2, p3);
}
p->nOp++;
@@ -73744,16 +81210,8 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){
#endif
#ifdef SQLITE_DEBUG
if( p->db->flags & SQLITE_VdbeAddopTrace ){
- int jj, kk;
- Parse *pParse = p->pParse;
- for(jj=kk=0; jj<pParse->nColCache; jj++){
- struct yColCache *x = pParse->aColCache + jj;
- printf(" r[%d]={%d:%d}", x->iReg, x->iTable, x->iColumn);
- kk++;
- }
- if( kk ) printf("\n");
- sqlite3VdbePrintOp(0, i, &p->aOp[i]);
- test_addop_breakpoint();
+ tdsqlite3VdbePrintOp(0, i, &p->aOp[i]);
+ test_addop_breakpoint(i, &p->aOp[i]);
}
#endif
#ifdef VDBE_PROFILE
@@ -73765,27 +81223,27 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){
#endif
return i;
}
-SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe *p, int op){
- return sqlite3VdbeAddOp3(p, op, 0, 0, 0);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp0(Vdbe *p, int op){
+ return tdsqlite3VdbeAddOp3(p, op, 0, 0, 0);
}
-SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe *p, int op, int p1){
- return sqlite3VdbeAddOp3(p, op, p1, 0, 0);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp1(Vdbe *p, int op, int p1){
+ return tdsqlite3VdbeAddOp3(p, op, p1, 0, 0);
}
-SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){
- return sqlite3VdbeAddOp3(p, op, p1, p2, 0);
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){
+ return tdsqlite3VdbeAddOp3(p, op, p1, p2, 0);
}
/* Generate code for an unconditional jump to instruction iDest
*/
-SQLITE_PRIVATE int sqlite3VdbeGoto(Vdbe *p, int iDest){
- return sqlite3VdbeAddOp3(p, OP_Goto, 0, iDest, 0);
+SQLITE_PRIVATE int tdsqlite3VdbeGoto(Vdbe *p, int iDest){
+ return tdsqlite3VdbeAddOp3(p, OP_Goto, 0, iDest, 0);
}
/* Generate code to cause the string zStr to be loaded into
** register iDest
*/
-SQLITE_PRIVATE int sqlite3VdbeLoadString(Vdbe *p, int iDest, const char *zStr){
- return sqlite3VdbeAddOp4(p, OP_String8, 0, iDest, 0, zStr, 0);
+SQLITE_PRIVATE int tdsqlite3VdbeLoadString(Vdbe *p, int iDest, const char *zStr){
+ return tdsqlite3VdbeAddOp4(p, OP_String8, 0, iDest, 0, zStr, 0);
}
/*
@@ -73795,8 +81253,11 @@ SQLITE_PRIVATE int sqlite3VdbeLoadString(Vdbe *p, int iDest, const char *zStr){
** "s" character in zTypes[], the register is a string if the argument is
** not NULL, or OP_Null if the value is a null pointer. For each "i" character
** in zTypes[], the register is initialized to an integer.
+**
+** If the input string does not end with "X" then an OP_ResultRow instruction
+** is generated for the values inserted.
*/
-SQLITE_PRIVATE void sqlite3VdbeMultiLoad(Vdbe *p, int iDest, const char *zTypes, ...){
+SQLITE_PRIVATE void tdsqlite3VdbeMultiLoad(Vdbe *p, int iDest, const char *zTypes, ...){
va_list ap;
int i;
char c;
@@ -73804,19 +81265,22 @@ SQLITE_PRIVATE void sqlite3VdbeMultiLoad(Vdbe *p, int iDest, const char *zTypes,
for(i=0; (c = zTypes[i])!=0; i++){
if( c=='s' ){
const char *z = va_arg(ap, const char*);
- sqlite3VdbeAddOp4(p, z==0 ? OP_Null : OP_String8, 0, iDest++, 0, z, 0);
+ tdsqlite3VdbeAddOp4(p, z==0 ? OP_Null : OP_String8, 0, iDest+i, 0, z, 0);
+ }else if( c=='i' ){
+ tdsqlite3VdbeAddOp2(p, OP_Integer, va_arg(ap, int), iDest+i);
}else{
- assert( c=='i' );
- sqlite3VdbeAddOp2(p, OP_Integer, va_arg(ap, int), iDest++);
+ goto skip_op_resultrow;
}
}
+ tdsqlite3VdbeAddOp2(p, OP_ResultRow, iDest, i);
+skip_op_resultrow:
va_end(ap);
}
/*
** Add an opcode that includes the p4 value as a pointer.
*/
-SQLITE_PRIVATE int sqlite3VdbeAddOp4(
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp4(
Vdbe *p, /* Add the opcode to this VM */
int op, /* The new opcode */
int p1, /* The P1 operand */
@@ -73825,8 +81289,51 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4(
const char *zP4, /* The P4 operand */
int p4type /* P4 operand type */
){
- int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3);
- sqlite3VdbeChangeP4(p, addr, zP4, p4type);
+ int addr = tdsqlite3VdbeAddOp3(p, op, p1, p2, p3);
+ tdsqlite3VdbeChangeP4(p, addr, zP4, p4type);
+ return addr;
+}
+
+/*
+** Add an OP_Function or OP_PureFunc opcode.
+**
+** The eCallCtx argument is information (typically taken from Expr.op2)
+** that describes the calling context of the function. 0 means a general
+** function call. NC_IsCheck means called by a check constraint,
+** NC_IdxExpr means called as part of an index expression. NC_PartIdx
+** means in the WHERE clause of a partial index. NC_GenCol means called
+** while computing a generated column value. 0 is the usual case.
+*/
+SQLITE_PRIVATE int tdsqlite3VdbeAddFunctionCall(
+ Parse *pParse, /* Parsing context */
+ int p1, /* Constant argument mask */
+ int p2, /* First argument register */
+ int p3, /* Register into which results are written */
+ int nArg, /* Number of argument */
+ const FuncDef *pFunc, /* The function to be invoked */
+ int eCallCtx /* Calling context */
+){
+ Vdbe *v = pParse->pVdbe;
+ int nByte;
+ int addr;
+ tdsqlite3_context *pCtx;
+ assert( v );
+ nByte = sizeof(*pCtx) + (nArg-1)*sizeof(tdsqlite3_value*);
+ pCtx = tdsqlite3DbMallocRawNN(pParse->db, nByte);
+ if( pCtx==0 ){
+ assert( pParse->db->mallocFailed );
+ freeEphemeralFunction(pParse->db, (FuncDef*)pFunc);
+ return 0;
+ }
+ pCtx->pOut = 0;
+ pCtx->pFunc = (FuncDef*)pFunc;
+ pCtx->pVdbe = 0;
+ pCtx->isError = 0;
+ pCtx->argc = nArg;
+ pCtx->iOp = tdsqlite3VdbeCurrentAddr(v);
+ addr = tdsqlite3VdbeAddOp4(v, eCallCtx ? OP_PureFunc : OP_Function,
+ p1, p2, p3, (char*)pCtx, P4_FUNCCTX);
+ tdsqlite3VdbeChangeP5(v, eCallCtx & NC_SelfRef);
return addr;
}
@@ -73834,7 +81341,7 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4(
** Add an opcode that includes the p4 value with a P4_INT64 or
** P4_REAL type.
*/
-SQLITE_PRIVATE int sqlite3VdbeAddOp4Dup8(
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp4Dup8(
Vdbe *p, /* Add the opcode to this VM */
int op, /* The new opcode */
int p1, /* The P1 operand */
@@ -73843,29 +81350,92 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4Dup8(
const u8 *zP4, /* The P4 operand */
int p4type /* P4 operand type */
){
- char *p4copy = sqlite3DbMallocRawNN(sqlite3VdbeDb(p), 8);
+ char *p4copy = tdsqlite3DbMallocRawNN(tdsqlite3VdbeDb(p), 8);
if( p4copy ) memcpy(p4copy, zP4, 8);
- return sqlite3VdbeAddOp4(p, op, p1, p2, p3, p4copy, p4type);
+ return tdsqlite3VdbeAddOp4(p, op, p1, p2, p3, p4copy, p4type);
+}
+
+#ifndef SQLITE_OMIT_EXPLAIN
+/*
+** Return the address of the current EXPLAIN QUERY PLAN baseline.
+** 0 means "none".
+*/
+SQLITE_PRIVATE int tdsqlite3VdbeExplainParent(Parse *pParse){
+ VdbeOp *pOp;
+ if( pParse->addrExplain==0 ) return 0;
+ pOp = tdsqlite3VdbeGetOp(pParse->pVdbe, pParse->addrExplain);
+ return pOp->p2;
+}
+
+/*
+** Set a debugger breakpoint on the following routine in order to
+** monitor the EXPLAIN QUERY PLAN code generation.
+*/
+#if defined(SQLITE_DEBUG)
+SQLITE_PRIVATE void tdsqlite3ExplainBreakpoint(const char *z1, const char *z2){
+ (void)z1;
+ (void)z2;
+}
+#endif
+
+/*
+** Add a new OP_ opcode.
+**
+** If the bPush flag is true, then make this opcode the parent for
+** subsequent Explains until tdsqlite3VdbeExplainPop() is called.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){
+#ifndef SQLITE_DEBUG
+ /* Always include the OP_Explain opcodes if SQLITE_DEBUG is defined.
+ ** But omit them (for performance) during production builds */
+ if( pParse->explain==2 )
+#endif
+ {
+ char *zMsg;
+ Vdbe *v;
+ va_list ap;
+ int iThis;
+ va_start(ap, zFmt);
+ zMsg = tdsqlite3VMPrintf(pParse->db, zFmt, ap);
+ va_end(ap);
+ v = pParse->pVdbe;
+ iThis = v->nOp;
+ tdsqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0,
+ zMsg, P4_DYNAMIC);
+ tdsqlite3ExplainBreakpoint(bPush?"PUSH":"", tdsqlite3VdbeGetOp(v,-1)->p4.z);
+ if( bPush){
+ pParse->addrExplain = iThis;
+ }
+ }
+}
+
+/*
+** Pop the EXPLAIN QUERY PLAN stack one level.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeExplainPop(Parse *pParse){
+ tdsqlite3ExplainBreakpoint("POP", 0);
+ pParse->addrExplain = tdsqlite3VdbeExplainParent(pParse);
}
+#endif /* SQLITE_OMIT_EXPLAIN */
/*
** Add an OP_ParseSchema opcode. This routine is broken out from
-** sqlite3VdbeAddOp4() since it needs to also needs to mark all btrees
+** tdsqlite3VdbeAddOp4() since it needs to also needs to mark all btrees
** as having been used.
**
-** The zWhere string must have been obtained from sqlite3_malloc().
+** The zWhere string must have been obtained from tdsqlite3_malloc().
** This routine will take ownership of the allocated memory.
*/
-SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere){
+SQLITE_PRIVATE void tdsqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere){
int j;
- sqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC);
- for(j=0; j<p->db->nDb; j++) sqlite3VdbeUsesBtree(p, j);
+ tdsqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC);
+ for(j=0; j<p->db->nDb; j++) tdsqlite3VdbeUsesBtree(p, j);
}
/*
** Add an opcode that includes the p4 value as an integer.
*/
-SQLITE_PRIVATE int sqlite3VdbeAddOp4Int(
+SQLITE_PRIVATE int tdsqlite3VdbeAddOp4Int(
Vdbe *p, /* Add the opcode to this VM */
int op, /* The new opcode */
int p1, /* The P1 operand */
@@ -73873,15 +81443,19 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4Int(
int p3, /* The P3 operand */
int p4 /* The P4 operand as an integer */
){
- int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3);
- sqlite3VdbeChangeP4(p, addr, SQLITE_INT_TO_PTR(p4), P4_INT32);
+ int addr = tdsqlite3VdbeAddOp3(p, op, p1, p2, p3);
+ if( p->db->mallocFailed==0 ){
+ VdbeOp *pOp = &p->aOp[addr];
+ pOp->p4type = P4_INT32;
+ pOp->p4.i = p4;
+ }
return addr;
}
/* Insert the end of a co-routine
*/
-SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(Vdbe *v, int regYield){
- sqlite3VdbeAddOp1(v, OP_EndCoroutine, regYield);
+SQLITE_PRIVATE void tdsqlite3VdbeEndCoroutine(Vdbe *v, int regYield){
+ tdsqlite3VdbeAddOp1(v, OP_EndCoroutine, regYield);
/* Clear the temporary register cache, thereby ensuring that each
** co-routine has its own independent set of registers, because co-routines
@@ -73904,35 +81478,59 @@ SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(Vdbe *v, int regYield){
** The VDBE knows that a P2 value is a label because labels are
** always negative and P2 values are suppose to be non-negative.
** Hence, a negative P2 value is a label that has yet to be resolved.
+** (Later:) This is only true for opcodes that have the OPFLG_JUMP
+** property.
+**
+** Variable usage notes:
**
-** Zero is returned if a malloc() fails.
+** Parse.aLabel[x] Stores the address that the x-th label resolves
+** into. For testing (SQLITE_DEBUG), unresolved
+** labels stores -1, but that is not required.
+** Parse.nLabelAlloc Number of slots allocated to Parse.aLabel[]
+** Parse.nLabel The *negative* of the number of labels that have
+** been issued. The negative is stored because
+** that gives a performance improvement over storing
+** the equivalent positive value.
*/
-SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Vdbe *v){
- Parse *p = v->pParse;
- int i = p->nLabel++;
- assert( v->magic==VDBE_MAGIC_INIT );
- if( (i & (i-1))==0 ){
- p->aLabel = sqlite3DbReallocOrFree(p->db, p->aLabel,
- (i*2+1)*sizeof(p->aLabel[0]));
- }
- if( p->aLabel ){
- p->aLabel[i] = -1;
- }
- return ADDR(i);
+SQLITE_PRIVATE int tdsqlite3VdbeMakeLabel(Parse *pParse){
+ return --pParse->nLabel;
}
/*
** Resolve label "x" to be the address of the next instruction to
** be inserted. The parameter "x" must have been obtained from
-** a prior call to sqlite3VdbeMakeLabel().
+** a prior call to tdsqlite3VdbeMakeLabel().
*/
-SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){
+static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){
+ int nNewSize = 10 - p->nLabel;
+ p->aLabel = tdsqlite3DbReallocOrFree(p->db, p->aLabel,
+ nNewSize*sizeof(p->aLabel[0]));
+ if( p->aLabel==0 ){
+ p->nLabelAlloc = 0;
+ }else{
+#ifdef SQLITE_DEBUG
+ int i;
+ for(i=p->nLabelAlloc; i<nNewSize; i++) p->aLabel[i] = -1;
+#endif
+ p->nLabelAlloc = nNewSize;
+ p->aLabel[j] = v->nOp;
+ }
+}
+SQLITE_PRIVATE void tdsqlite3VdbeResolveLabel(Vdbe *v, int x){
Parse *p = v->pParse;
int j = ADDR(x);
assert( v->magic==VDBE_MAGIC_INIT );
- assert( j<p->nLabel );
+ assert( j<-p->nLabel );
assert( j>=0 );
- if( p->aLabel ){
+#ifdef SQLITE_DEBUG
+ if( p->db->flags & SQLITE_VdbeAddopTrace ){
+ printf("RESOLVE LABEL %d to %d\n", x, v->nOp);
+ }
+#endif
+ if( p->nLabelAlloc + p->nLabel < 0 ){
+ resizeResolveLabel(p,v,j);
+ }else{
+ assert( p->aLabel[j]==(-1) ); /* Labels may only be resolved once */
p->aLabel[j] = v->nOp;
}
}
@@ -73940,18 +81538,18 @@ SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){
/*
** Mark the VDBE as one that can only be run one time.
*/
-SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe *p){
+SQLITE_PRIVATE void tdsqlite3VdbeRunOnlyOnce(Vdbe *p){
p->runOnlyOnce = 1;
}
/*
** Mark the VDBE as one that can only be run multiple times.
*/
-SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe *p){
+SQLITE_PRIVATE void tdsqlite3VdbeReusable(Vdbe *p){
p->runOnlyOnce = 0;
}
-#ifdef SQLITE_DEBUG /* sqlite3AssertMayAbort() logic */
+#ifdef SQLITE_DEBUG /* tdsqlite3AssertMayAbort() logic */
/*
** The following type and function are used to iterate through all opcodes
@@ -73966,7 +81564,7 @@ SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe *p){
** while( (pOp = opIterNext(&sIter)) ){
** // Do something with pOp
** }
-** sqlite3DbFree(v->db, sIter.apSub);
+** tdsqlite3DbFree(v->db, sIter.apSub);
**
*/
typedef struct VdbeOpIter VdbeOpIter;
@@ -74008,7 +81606,7 @@ static Op *opIterNext(VdbeOpIter *p){
if( p->apSub[j]==pRet->p4.pProgram ) break;
}
if( j==p->nSub ){
- p->apSub = sqlite3DbReallocOrFree(v->db, p->apSub, nByte);
+ p->apSub = tdsqlite3DbReallocOrFree(v->db, p->apSub, nByte);
if( !p->apSub ){
pRet = 0;
}else{
@@ -74031,21 +81629,24 @@ static Op *opIterNext(VdbeOpIter *p){
** * OP_HaltIfNull with P1=SQLITE_CONSTRAINT and P2=OE_Abort.
** * OP_Destroy
** * OP_VUpdate
+** * OP_VCreate
** * OP_VRename
** * OP_FkCounter with P2==0 (immediate foreign key constraint)
-** * OP_CreateTable and OP_InitCoroutine (for CREATE TABLE AS SELECT ...)
+** * OP_CreateBtree/BTREE_INTKEY and OP_InitCoroutine
+** (for CREATE TABLE AS SELECT ...)
**
** Then check that the value of Parse.mayAbort is true if an
** ABORT may be thrown, or false otherwise. Return true if it does
** match, or false otherwise. This function is intended to be used as
** part of an assert statement in the compiler. Similar to:
**
-** assert( sqlite3VdbeAssertMayAbort(pParse->pVdbe, pParse->mayAbort) );
+** assert( tdsqlite3VdbeAssertMayAbort(pParse->pVdbe, pParse->mayAbort) );
*/
-SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
+SQLITE_PRIVATE int tdsqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
int hasAbort = 0;
int hasFkCounter = 0;
int hasCreateTable = 0;
+ int hasCreateIndex = 0;
int hasInitCoroutine = 0;
Op *pOp;
VdbeOpIter sIter;
@@ -74055,13 +81656,24 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
while( (pOp = opIterNext(&sIter))!=0 ){
int opcode = pOp->opcode;
if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename
+ || opcode==OP_VDestroy
+ || opcode==OP_VCreate
+ || (opcode==OP_ParseSchema && pOp->p4.z==0)
|| ((opcode==OP_Halt || opcode==OP_HaltIfNull)
- && ((pOp->p1&0xff)==SQLITE_CONSTRAINT && pOp->p2==OE_Abort))
+ && ((pOp->p1)!=SQLITE_OK && pOp->p2==OE_Abort))
){
hasAbort = 1;
break;
}
- if( opcode==OP_CreateTable ) hasCreateTable = 1;
+ if( opcode==OP_CreateBtree && pOp->p3==BTREE_INTKEY ) hasCreateTable = 1;
+ if( mayAbort ){
+ /* hasCreateIndex may also be set for some DELETE statements that use
+ ** OP_Clear. So this routine may end up returning true in the case
+ ** where a "DELETE FROM tbl" has a statement-journal but does not
+ ** require one. This is not so bad - it is an inefficiency, not a bug. */
+ if( opcode==OP_CreateBtree && pOp->p3==BTREE_BLOBKEY ) hasCreateIndex = 1;
+ if( opcode==OP_Clear ) hasCreateIndex = 1;
+ }
if( opcode==OP_InitCoroutine ) hasInitCoroutine = 1;
#ifndef SQLITE_OMIT_FOREIGN_KEY
if( opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1 ){
@@ -74069,7 +81681,7 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
}
#endif
}
- sqlite3DbFree(v->db, sIter.apSub);
+ tdsqlite3DbFree(v->db, sIter.apSub);
/* Return true if hasAbort==mayAbort. Or if a malloc failure occurred.
** If malloc failed, then the while() loop above may not have iterated
@@ -74077,9 +81689,36 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
** true for this case to prevent the assert() in the callers frame
** from failing. */
return ( v->db->mallocFailed || hasAbort==mayAbort || hasFkCounter
- || (hasCreateTable && hasInitCoroutine) );
+ || (hasCreateTable && hasInitCoroutine) || hasCreateIndex
+ );
}
-#endif /* SQLITE_DEBUG - the sqlite3AssertMayAbort() function */
+#endif /* SQLITE_DEBUG - the tdsqlite3AssertMayAbort() function */
+
+#ifdef SQLITE_DEBUG
+/*
+** Increment the nWrite counter in the VDBE if the cursor is not an
+** ephemeral cursor, or if the cursor argument is NULL.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeIncrWriteCounter(Vdbe *p, VdbeCursor *pC){
+ if( pC==0
+ || (pC->eCurType!=CURTYPE_SORTER
+ && pC->eCurType!=CURTYPE_PSEUDO
+ && !pC->isEphemeral)
+ ){
+ p->nWrite++;
+ }
+}
+#endif
+
+#ifdef SQLITE_DEBUG
+/*
+** Assert if an Abort at this point in time might result in a corrupt
+** database.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeAssertAbortable(Vdbe *p){
+ assert( p->nWrite==0 || p->usesStmtJournal );
+}
+#endif
/*
** This routine is called after all opcodes have been inserted. It loops
@@ -74140,6 +81779,25 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
p->bIsReader = 1;
break;
}
+ case OP_Next:
+ case OP_SorterNext: {
+ pOp->p4.xAdvance = tdsqlite3BtreeNext;
+ pOp->p4type = P4_ADVANCE;
+ /* The code generator never codes any of these opcodes as a jump
+ ** to a label. They are always coded as a jump backwards to a
+ ** known address */
+ assert( pOp->p2>=0 );
+ break;
+ }
+ case OP_Prev: {
+ pOp->p4.xAdvance = tdsqlite3BtreePrevious;
+ pOp->p4type = P4_ADVANCE;
+ /* The code generator never codes any of these opcodes as a jump
+ ** to a label. They are always coded as a jump backwards to a
+ ** known address */
+ assert( pOp->p2>=0 );
+ break;
+ }
#ifndef SQLITE_OMIT_VIRTUALTABLE
case OP_VUpdate: {
if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2;
@@ -74151,32 +81809,30 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
assert( pOp[-1].opcode==OP_Integer );
n = pOp[-1].p1;
if( n>nMaxArgs ) nMaxArgs = n;
- break;
+ /* Fall through into the default case */
}
#endif
- case OP_Next:
- case OP_NextIfOpen:
- case OP_SorterNext: {
- pOp->p4.xAdvance = sqlite3BtreeNext;
- pOp->p4type = P4_ADVANCE;
- break;
- }
- case OP_Prev:
- case OP_PrevIfOpen: {
- pOp->p4.xAdvance = sqlite3BtreePrevious;
- pOp->p4type = P4_ADVANCE;
+ default: {
+ if( pOp->p2<0 ){
+ /* The mkopcodeh.tcl script has so arranged things that the only
+ ** non-jump opcodes less than SQLITE_MX_JUMP_CODE are guaranteed to
+ ** have non-negative values for P2. */
+ assert( (tdsqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 );
+ assert( ADDR(pOp->p2)<-pParse->nLabel );
+ pOp->p2 = aLabel[ADDR(pOp->p2)];
+ }
break;
}
}
- if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 && pOp->p2<0 ){
- assert( ADDR(pOp->p2)<pParse->nLabel );
- pOp->p2 = aLabel[ADDR(pOp->p2)];
- }
+ /* The mkopcodeh.tcl script has so arranged things that the only
+ ** non-jump opcodes less than SQLITE_MX_JUMP_CODE are guaranteed to
+ ** have non-negative values for P2. */
+ assert( (tdsqlite3OpcodeProperty[pOp->opcode]&OPFLG_JUMP)==0 || pOp->p2>=0);
}
if( pOp==p->aOp ) break;
pOp--;
}
- sqlite3DbFree(p->db, pParse->aLabel);
+ tdsqlite3DbFree(p->db, pParse->aLabel);
pParse->aLabel = 0;
pParse->nLabel = 0;
*pMaxFuncArgs = nMaxArgs;
@@ -74186,7 +81842,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
/*
** Return the address of the next instruction to be inserted.
*/
-SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe *p){
+SQLITE_PRIVATE int tdsqlite3VdbeCurrentAddr(Vdbe *p){
assert( p->magic==VDBE_MAGIC_INIT );
return p->nOp;
}
@@ -74195,13 +81851,40 @@ SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe *p){
** Verify that at least N opcode slots are available in p without
** having to malloc for more space (except when compiled using
** SQLITE_TEST_REALLOC_STRESS). This interface is used during testing
-** to verify that certain calls to sqlite3VdbeAddOpList() can never
+** to verify that certain calls to tdsqlite3VdbeAddOpList() can never
** fail due to a OOM fault and hence that the return value from
-** sqlite3VdbeAddOpList() will always be non-NULL.
+** tdsqlite3VdbeAddOpList() will always be non-NULL.
+*/
+#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS)
+SQLITE_PRIVATE void tdsqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N){
+ assert( p->nOp + N <= p->nOpAlloc );
+}
+#endif
+
+/*
+** Verify that the VM passed as the only argument does not contain
+** an OP_ResultRow opcode. Fail an assert() if it does. This is used
+** by code in pragma.c to ensure that the implementation of certain
+** pragmas comports with the flags specified in the mkpragmatab.tcl
+** script.
*/
#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS)
-SQLITE_PRIVATE void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N){
- assert( p->nOp + N <= p->pParse->nOpAlloc );
+SQLITE_PRIVATE void tdsqlite3VdbeVerifyNoResultRow(Vdbe *p){
+ int i;
+ for(i=0; i<p->nOp; i++){
+ assert( p->aOp[i].opcode!=OP_ResultRow );
+ }
+}
+#endif
+
+/*
+** Generate code (a single OP_Abortable opcode) that will
+** verify that the VDBE program can safely call Abort in the current
+** context.
+*/
+#if defined(SQLITE_DEBUG)
+SQLITE_PRIVATE void tdsqlite3VdbeVerifyAbortable(Vdbe *p, int onError){
+ if( onError==OE_Abort ) tdsqlite3VdbeAddOp0(p, OP_Abortable);
}
#endif
@@ -74216,11 +81899,11 @@ SQLITE_PRIVATE void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N){
** the number of entries in the Vdbe.apArg[] array required to execute the
** returned program.
*/
-SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){
+SQLITE_PRIVATE VdbeOp *tdsqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){
VdbeOp *aOp = p->aOp;
assert( aOp && !p->db->mallocFailed );
- /* Check that sqlite3VdbeUsesBtree() was not called on this VM */
+ /* Check that tdsqlite3VdbeUsesBtree() was not called on this VM */
assert( DbMaskAllZero(p->btreeMask) );
resolveP2Values(p, pnMaxArg);
@@ -74236,7 +81919,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg)
** Non-zero P2 arguments to jump instructions are automatically adjusted
** so that the jump target is relative to the first operation inserted.
*/
-SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(
+SQLITE_PRIVATE VdbeOp *tdsqlite3VdbeAddOpList(
Vdbe *p, /* Add opcodes to the prepared statement */
int nOp, /* Number of opcodes to add */
VdbeOpList const *aOp, /* The opcodes to be added */
@@ -74246,7 +81929,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(
VdbeOp *pOut, *pFirst;
assert( nOp>0 );
assert( p->magic==VDBE_MAGIC_INIT );
- if( p->nOp + nOp > p->pParse->nOpAlloc && growOpArray(p, nOp) ){
+ if( p->nOp + nOp > p->nOpAlloc && growOpArray(p, nOp) ){
return 0;
}
pFirst = pOut = &p->aOp[p->nOp];
@@ -74255,7 +81938,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(
pOut->p1 = aOp->p1;
pOut->p2 = aOp->p2;
assert( aOp->p2>=0 );
- if( (sqlite3OpcodeProperty[aOp->opcode] & OPFLG_JUMP)!=0 && aOp->p2>0 ){
+ if( (tdsqlite3OpcodeProperty[aOp->opcode] & OPFLG_JUMP)!=0 && aOp->p2>0 ){
pOut->p2 += p->nOp;
}
pOut->p3 = aOp->p3;
@@ -74272,7 +81955,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(
#endif
#ifdef SQLITE_DEBUG
if( p->db->flags & SQLITE_VdbeAddopTrace ){
- sqlite3VdbePrintOp(0, i+p->nOp, &p->aOp[i+p->nOp]);
+ tdsqlite3VdbePrintOp(0, i+p->nOp, &p->aOp[i+p->nOp]);
}
#endif
}
@@ -74282,9 +81965,9 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(
#if defined(SQLITE_ENABLE_STMT_SCANSTATUS)
/*
-** Add an entry to the array of counters managed by sqlite3_stmt_scanstatus().
+** Add an entry to the array of counters managed by tdsqlite3_stmt_scanstatus().
*/
-SQLITE_PRIVATE void sqlite3VdbeScanStatus(
+SQLITE_PRIVATE void tdsqlite3VdbeScanStatus(
Vdbe *p, /* VM to add scanstatus() to */
int addrExplain, /* Address of OP_Explain (or 0) */
int addrLoop, /* Address of loop counter */
@@ -74292,16 +81975,16 @@ SQLITE_PRIVATE void sqlite3VdbeScanStatus(
LogEst nEst, /* Estimated number of output rows */
const char *zName /* Name of table or index being scanned */
){
- int nByte = (p->nScan+1) * sizeof(ScanStatus);
+ tdsqlite3_int64 nByte = (p->nScan+1) * sizeof(ScanStatus);
ScanStatus *aNew;
- aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte);
+ aNew = (ScanStatus*)tdsqlite3DbRealloc(p->db, p->aScan, nByte);
if( aNew ){
ScanStatus *pNew = &aNew[p->nScan++];
pNew->addrExplain = addrExplain;
pNew->addrLoop = addrLoop;
pNew->addrVisit = addrVisit;
pNew->nEst = nEst;
- pNew->zName = sqlite3DbStrDup(p->db, zName);
+ pNew->zName = tdsqlite3DbStrDup(p->db, zName);
p->aScan = aNew;
}
}
@@ -74312,19 +81995,19 @@ SQLITE_PRIVATE void sqlite3VdbeScanStatus(
** Change the value of the opcode, or P1, P2, P3, or P5 operands
** for a specific instruction.
*/
-SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe *p, u32 addr, u8 iNewOpcode){
- sqlite3VdbeGetOp(p,addr)->opcode = iNewOpcode;
+SQLITE_PRIVATE void tdsqlite3VdbeChangeOpcode(Vdbe *p, int addr, u8 iNewOpcode){
+ tdsqlite3VdbeGetOp(p,addr)->opcode = iNewOpcode;
}
-SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe *p, u32 addr, int val){
- sqlite3VdbeGetOp(p,addr)->p1 = val;
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP1(Vdbe *p, int addr, int val){
+ tdsqlite3VdbeGetOp(p,addr)->p1 = val;
}
-SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe *p, u32 addr, int val){
- sqlite3VdbeGetOp(p,addr)->p2 = val;
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP2(Vdbe *p, int addr, int val){
+ tdsqlite3VdbeGetOp(p,addr)->p2 = val;
}
-SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe *p, u32 addr, int val){
- sqlite3VdbeGetOp(p,addr)->p3 = val;
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP3(Vdbe *p, int addr, int val){
+ tdsqlite3VdbeGetOp(p,addr)->p3 = val;
}
-SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u8 p5){
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP5(Vdbe *p, u16 p5){
assert( p->nOp>0 || p->db->mallocFailed );
if( p->nOp>0 ) p->aOp[p->nOp-1].p5 = p5;
}
@@ -74333,8 +82016,8 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u8 p5){
** Change the P2 operand of instruction addr so that it points to
** the address of the next instruction to be coded.
*/
-SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){
- sqlite3VdbeChangeP2(p, addr, p->nOp);
+SQLITE_PRIVATE void tdsqlite3VdbeJumpHere(Vdbe *p, int addr){
+ tdsqlite3VdbeChangeP2(p, addr, p->nOp);
}
@@ -74342,67 +82025,62 @@ SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){
** If the input FuncDef structure is ephemeral, then free it. If
** the FuncDef is not ephermal, then do nothing.
*/
-static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){
+static void freeEphemeralFunction(tdsqlite3 *db, FuncDef *pDef){
if( (pDef->funcFlags & SQLITE_FUNC_EPHEM)!=0 ){
- sqlite3DbFree(db, pDef);
+ tdsqlite3DbFreeNN(db, pDef);
}
}
-static void vdbeFreeOpArray(sqlite3 *, Op *, int);
-
/*
** Delete a P4 value if necessary.
*/
-static SQLITE_NOINLINE void freeP4Mem(sqlite3 *db, Mem *p){
- if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc);
- sqlite3DbFree(db, p);
+static SQLITE_NOINLINE void freeP4Mem(tdsqlite3 *db, Mem *p){
+ if( p->szMalloc ) tdsqlite3DbFree(db, p->zMalloc);
+ tdsqlite3DbFreeNN(db, p);
}
-static SQLITE_NOINLINE void freeP4FuncCtx(sqlite3 *db, sqlite3_context *p){
+static SQLITE_NOINLINE void freeP4FuncCtx(tdsqlite3 *db, tdsqlite3_context *p){
freeEphemeralFunction(db, p->pFunc);
- sqlite3DbFree(db, p);
+ tdsqlite3DbFreeNN(db, p);
}
-static void freeP4(sqlite3 *db, int p4type, void *p4){
+static void freeP4(tdsqlite3 *db, int p4type, void *p4){
assert( db );
switch( p4type ){
case P4_FUNCCTX: {
- freeP4FuncCtx(db, (sqlite3_context*)p4);
+ freeP4FuncCtx(db, (tdsqlite3_context*)p4);
break;
}
case P4_REAL:
case P4_INT64:
case P4_DYNAMIC:
+ case P4_DYNBLOB:
case P4_INTARRAY: {
- sqlite3DbFree(db, p4);
+ tdsqlite3DbFree(db, p4);
break;
}
case P4_KEYINFO: {
- if( db->pnBytesFreed==0 ) sqlite3KeyInfoUnref((KeyInfo*)p4);
+ if( db->pnBytesFreed==0 ) tdsqlite3KeyInfoUnref((KeyInfo*)p4);
break;
}
#ifdef SQLITE_ENABLE_CURSOR_HINTS
case P4_EXPR: {
- sqlite3ExprDelete(db, (Expr*)p4);
+ tdsqlite3ExprDelete(db, (Expr*)p4);
break;
}
#endif
- case P4_MPRINTF: {
- if( db->pnBytesFreed==0 ) sqlite3_free(p4);
- break;
- }
case P4_FUNCDEF: {
freeEphemeralFunction(db, (FuncDef*)p4);
break;
}
case P4_MEM: {
if( db->pnBytesFreed==0 ){
- sqlite3ValueFree((sqlite3_value*)p4);
+ tdsqlite3ValueFree((tdsqlite3_value*)p4);
}else{
freeP4Mem(db, (Mem*)p4);
}
break;
}
case P4_VTAB : {
- if( db->pnBytesFreed==0 ) sqlite3VtabUnlock((VTable *)p4);
+ if( db->pnBytesFreed==0 ) tdsqlite3VtabUnlock((VTable *)p4);
break;
}
}
@@ -74413,17 +82091,17 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){
** opcodes contained within. If aOp is not NULL it is assumed to contain
** nOp entries.
*/
-static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){
+static void vdbeFreeOpArray(tdsqlite3 *db, Op *aOp, int nOp){
if( aOp ){
Op *pOp;
- for(pOp=aOp; pOp<&aOp[nOp]; pOp++){
- if( pOp->p4type ) freeP4(db, pOp->p4type, pOp->p4.p);
+ for(pOp=&aOp[nOp-1]; pOp>=aOp; pOp--){
+ if( pOp->p4type <= P4_FREE_IF_LE ) freeP4(db, pOp->p4type, pOp->p4.p);
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
- sqlite3DbFree(db, pOp->zComment);
+ tdsqlite3DbFree(db, pOp->zComment);
#endif
}
+ tdsqlite3DbFreeNN(db, aOp);
}
- sqlite3DbFree(db, aOp);
}
/*
@@ -74431,15 +82109,22 @@ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){
** list at Vdbe.pSubProgram. This list is used to delete all sub-program
** objects when the VM is no longer required.
*/
-SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *pVdbe, SubProgram *p){
+SQLITE_PRIVATE void tdsqlite3VdbeLinkSubProgram(Vdbe *pVdbe, SubProgram *p){
p->pNext = pVdbe->pProgram;
pVdbe->pProgram = p;
}
/*
+** Return true if the given Vdbe has any SubPrograms.
+*/
+SQLITE_PRIVATE int tdsqlite3VdbeHasSubProgram(Vdbe *pVdbe){
+ return pVdbe->pProgram!=0;
+}
+
+/*
** Change the opcode at addr into OP_Noop
*/
-SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe *p, int addr){
+SQLITE_PRIVATE int tdsqlite3VdbeChangeToNoop(Vdbe *p, int addr){
VdbeOp *pOp;
if( p->db->mallocFailed ) return 0;
assert( addr>=0 && addr<p->nOp );
@@ -74455,22 +82140,57 @@ SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe *p, int addr){
** If the last opcode is "op" and it is not a jump destination,
** then remove it. Return true if and only if an opcode was removed.
*/
-SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){
+SQLITE_PRIVATE int tdsqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){
if( p->nOp>0 && p->aOp[p->nOp-1].opcode==op ){
- return sqlite3VdbeChangeToNoop(p, p->nOp-1);
+ return tdsqlite3VdbeChangeToNoop(p, p->nOp-1);
}else{
return 0;
}
}
+#ifdef SQLITE_DEBUG
+/*
+** Generate an OP_ReleaseReg opcode to indicate that a range of
+** registers, except any identified by mask, are no longer in use.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeReleaseRegisters(
+ Parse *pParse, /* Parsing context */
+ int iFirst, /* Index of first register to be released */
+ int N, /* Number of registers to release */
+ u32 mask, /* Mask of registers to NOT release */
+ int bUndefine /* If true, mark registers as undefined */
+){
+ if( N==0 ) return;
+ assert( pParse->pVdbe );
+ assert( iFirst>=1 );
+ assert( iFirst+N-1<=pParse->nMem );
+ if( N<=31 && mask!=0 ){
+ while( N>0 && (mask&1)!=0 ){
+ mask >>= 1;
+ iFirst++;
+ N--;
+ }
+ while( N>0 && N<=32 && (mask & MASKBIT32(N-1))!=0 ){
+ mask &= ~MASKBIT32(N-1);
+ N--;
+ }
+ }
+ if( N>0 ){
+ tdsqlite3VdbeAddOp3(pParse->pVdbe, OP_ReleaseReg, iFirst, N, *(int*)&mask);
+ if( bUndefine ) tdsqlite3VdbeChangeP5(pParse->pVdbe, 1);
+ }
+}
+#endif /* SQLITE_DEBUG */
+
+
/*
** Change the value of the P4 operand for a specific instruction.
** This routine is useful when a large program is loaded from a
-** static array using sqlite3VdbeAddOpList but we want to make a
+** static array using tdsqlite3VdbeAddOpList but we want to make a
** few minor changes to the program.
**
** If n>=0 then the P4 operand is dynamic, meaning that a copy of
-** the string is made into memory obtained from sqlite3_malloc().
+** the string is made into memory obtained from tdsqlite3_malloc().
** A value of n==0 means copy bytes of zP4 up to and including the
** first null byte. If n>0 then copy n+1 bytes of zP4.
**
@@ -74492,16 +82212,16 @@ static void SQLITE_NOINLINE vdbeChangeP4Full(
pOp->p4.p = 0;
}
if( n<0 ){
- sqlite3VdbeChangeP4(p, (int)(pOp - p->aOp), zP4, n);
+ tdsqlite3VdbeChangeP4(p, (int)(pOp - p->aOp), zP4, n);
}else{
- if( n==0 ) n = sqlite3Strlen30(zP4);
- pOp->p4.z = sqlite3DbStrNDup(p->db, zP4, n);
+ if( n==0 ) n = tdsqlite3Strlen30(zP4);
+ pOp->p4.z = tdsqlite3DbStrNDup(p->db, zP4, n);
pOp->p4type = P4_DYNAMIC;
}
}
-SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){
+SQLITE_PRIVATE void tdsqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){
Op *pOp;
- sqlite3 *db;
+ tdsqlite3 *db;
assert( p!=0 );
db = p->db;
assert( p->magic==VDBE_MAGIC_INIT );
@@ -74529,7 +82249,32 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int
assert( n<0 );
pOp->p4.p = (void*)zP4;
pOp->p4type = (signed char)n;
- if( n==P4_VTAB ) sqlite3VtabLock((VTable*)zP4);
+ if( n==P4_VTAB ) tdsqlite3VtabLock((VTable*)zP4);
+ }
+}
+
+/*
+** Change the P4 operand of the most recently coded instruction
+** to the value defined by the arguments. This is a high-speed
+** version of tdsqlite3VdbeChangeP4().
+**
+** The P4 operand must not have been previously defined. And the new
+** P4 must not be P4_INT32. Use tdsqlite3VdbeChangeP4() in either of
+** those cases.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeAppendP4(Vdbe *p, void *pP4, int n){
+ VdbeOp *pOp;
+ assert( n!=P4_INT32 && n!=P4_VTAB );
+ assert( n<=0 );
+ if( p->db->mallocFailed ){
+ freeP4(p->db, n, pP4);
+ }else{
+ assert( pP4!=0 );
+ assert( p->nOp>0 );
+ pOp = &p->aOp[p->nOp-1];
+ assert( pOp->p4type==P4_NOTUSED );
+ pOp->p4type = n;
+ pOp->p4.p = pP4;
}
}
@@ -74537,12 +82282,13 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int
** Set the P4 on the most recently added opcode to the KeyInfo for the
** index given.
*/
-SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){
+SQLITE_PRIVATE void tdsqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){
Vdbe *v = pParse->pVdbe;
+ KeyInfo *pKeyInfo;
assert( v!=0 );
assert( pIdx!=0 );
- sqlite3VdbeChangeP4(v, -1, (char*)sqlite3KeyInfoOfIndex(pParse, pIdx),
- P4_KEYINFO);
+ pKeyInfo = tdsqlite3KeyInfoOfIndex(pParse, pIdx);
+ if( pKeyInfo ) tdsqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO);
}
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
@@ -74554,14 +82300,15 @@ SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){
*/
static void vdbeVComment(Vdbe *p, const char *zFormat, va_list ap){
assert( p->nOp>0 || p->aOp==0 );
- assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed );
+ assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed
+ || p->pParse->nErr>0 );
if( p->nOp ){
assert( p->aOp );
- sqlite3DbFree(p->db, p->aOp[p->nOp-1].zComment);
- p->aOp[p->nOp-1].zComment = sqlite3VMPrintf(p->db, zFormat, ap);
+ tdsqlite3DbFree(p->db, p->aOp[p->nOp-1].zComment);
+ p->aOp[p->nOp-1].zComment = tdsqlite3VMPrintf(p->db, zFormat, ap);
}
}
-SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){
+SQLITE_PRIVATE void tdsqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){
va_list ap;
if( p ){
va_start(ap, zFormat);
@@ -74569,10 +82316,10 @@ SQLITE_PRIVATE void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){
va_end(ap);
}
}
-SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){
+SQLITE_PRIVATE void tdsqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){
va_list ap;
if( p ){
- sqlite3VdbeAddOp0(p, OP_Noop);
+ tdsqlite3VdbeAddOp0(p, OP_Noop);
va_start(ap, zFormat);
vdbeVComment(p, zFormat, ap);
va_end(ap);
@@ -74584,8 +82331,8 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){
/*
** Set the value if the iSrcLine field for the previously coded instruction.
*/
-SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe *v, int iLine){
- sqlite3VdbeGetOp(v,-1)->iSrcLine = iLine;
+SQLITE_PRIVATE void tdsqlite3VdbeSetLineNumber(Vdbe *v, int iLine){
+ tdsqlite3VdbeGetOp(v,-1)->iSrcLine = iLine;
}
#endif /* SQLITE_VDBE_COVERAGE */
@@ -74602,7 +82349,7 @@ SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe *v, int iLine){
** dummy will never be written to. This is verified by code inspection and
** by running with Valgrind.
*/
-SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){
+SQLITE_PRIVATE VdbeOp *tdsqlite3VdbeGetOp(Vdbe *p, int addr){
/* C89 specifies that the constant "dummy" will be initialized to all
** zeros, which is correct. MSVC generates a warning, nevertheless. */
static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */
@@ -74635,7 +82382,7 @@ static int translateP(char c, const Op *pOp){
** Compute a string for the "comment" field of a VDBE opcode listing.
**
** The Synopsis: field in comments in the vdbe.c source file gets converted
-** to an extra string that is appended to the sqlite3OpcodeName(). In the
+** to an extra string that is appended to the tdsqlite3OpcodeName(). In the
** absence of other comments, this synopsis becomes the comment on the opcode.
** Some translation occurs:
**
@@ -74655,17 +82402,17 @@ static int displayComment(
int nOpName;
int ii, jj;
char zAlt[50];
- zOpName = sqlite3OpcodeName(pOp->opcode);
- nOpName = sqlite3Strlen30(zOpName);
+ zOpName = tdsqlite3OpcodeName(pOp->opcode);
+ nOpName = tdsqlite3Strlen30(zOpName);
if( zOpName[nOpName+1] ){
int seenCom = 0;
char c;
zSynopsis = zOpName += nOpName + 1;
if( strncmp(zSynopsis,"IF ",3)==0 ){
if( pOp->p5 & SQLITE_STOREP2 ){
- sqlite3_snprintf(sizeof(zAlt), zAlt, "r[P2] = (%s)", zSynopsis+3);
+ tdsqlite3_snprintf(sizeof(zAlt), zAlt, "r[P2] = (%s)", zSynopsis+3);
}else{
- sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3);
+ tdsqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3);
}
zSynopsis = zAlt;
}
@@ -74673,42 +82420,42 @@ static int displayComment(
if( c=='P' ){
c = zSynopsis[++ii];
if( c=='4' ){
- sqlite3_snprintf(nTemp-jj, zTemp+jj, "%s", zP4);
+ tdsqlite3_snprintf(nTemp-jj, zTemp+jj, "%s", zP4);
}else if( c=='X' ){
- sqlite3_snprintf(nTemp-jj, zTemp+jj, "%s", pOp->zComment);
+ tdsqlite3_snprintf(nTemp-jj, zTemp+jj, "%s", pOp->zComment);
seenCom = 1;
}else{
int v1 = translateP(c, pOp);
int v2;
- sqlite3_snprintf(nTemp-jj, zTemp+jj, "%d", v1);
+ tdsqlite3_snprintf(nTemp-jj, zTemp+jj, "%d", v1);
if( strncmp(zSynopsis+ii+1, "@P", 2)==0 ){
ii += 3;
- jj += sqlite3Strlen30(zTemp+jj);
+ jj += tdsqlite3Strlen30(zTemp+jj);
v2 = translateP(zSynopsis[ii], pOp);
if( strncmp(zSynopsis+ii+1,"+1",2)==0 ){
ii += 2;
v2++;
}
if( v2>1 ){
- sqlite3_snprintf(nTemp-jj, zTemp+jj, "..%d", v1+v2-1);
+ tdsqlite3_snprintf(nTemp-jj, zTemp+jj, "..%d", v1+v2-1);
}
}else if( strncmp(zSynopsis+ii+1, "..P3", 4)==0 && pOp->p3==0 ){
ii += 4;
}
}
- jj += sqlite3Strlen30(zTemp+jj);
+ jj += tdsqlite3Strlen30(zTemp+jj);
}else{
zTemp[jj++] = c;
}
}
if( !seenCom && jj<nTemp-5 && pOp->zComment ){
- sqlite3_snprintf(nTemp-jj, zTemp+jj, "; %s", pOp->zComment);
- jj += sqlite3Strlen30(zTemp+jj);
+ tdsqlite3_snprintf(nTemp-jj, zTemp+jj, "; %s", pOp->zComment);
+ jj += tdsqlite3Strlen30(zTemp+jj);
}
if( jj<nTemp ) zTemp[jj] = 0;
}else if( pOp->zComment ){
- sqlite3_snprintf(nTemp, zTemp, "%s", pOp->zComment);
- jj = sqlite3Strlen30(zTemp);
+ tdsqlite3_snprintf(nTemp, zTemp, "%s", pOp->zComment);
+ jj = tdsqlite3Strlen30(zTemp);
}else{
zTemp[0] = 0;
jj = 0;
@@ -74726,23 +82473,23 @@ static void displayP4Expr(StrAccum *p, Expr *pExpr){
const char *zOp = 0;
switch( pExpr->op ){
case TK_STRING:
- sqlite3XPrintf(p, "%Q", pExpr->u.zToken);
+ tdsqlite3_str_appendf(p, "%Q", pExpr->u.zToken);
break;
case TK_INTEGER:
- sqlite3XPrintf(p, "%d", pExpr->u.iValue);
+ tdsqlite3_str_appendf(p, "%d", pExpr->u.iValue);
break;
case TK_NULL:
- sqlite3XPrintf(p, "NULL");
+ tdsqlite3_str_appendf(p, "NULL");
break;
case TK_REGISTER: {
- sqlite3XPrintf(p, "r[%d]", pExpr->iTable);
+ tdsqlite3_str_appendf(p, "r[%d]", pExpr->iTable);
break;
}
case TK_COLUMN: {
if( pExpr->iColumn<0 ){
- sqlite3XPrintf(p, "rowid");
+ tdsqlite3_str_appendf(p, "rowid");
}else{
- sqlite3XPrintf(p, "c%d", (int)pExpr->iColumn);
+ tdsqlite3_str_appendf(p, "c%d", (int)pExpr->iColumn);
}
break;
}
@@ -74774,18 +82521,18 @@ static void displayP4Expr(StrAccum *p, Expr *pExpr){
case TK_NOTNULL: zOp = "NOTNULL"; break;
default:
- sqlite3XPrintf(p, "%s", "expr");
+ tdsqlite3_str_appendf(p, "%s", "expr");
break;
}
if( zOp ){
- sqlite3XPrintf(p, "%s(", zOp);
+ tdsqlite3_str_appendf(p, "%s(", zOp);
displayP4Expr(p, pExpr->pLeft);
if( pExpr->pRight ){
- sqlite3StrAccumAppend(p, ",", 1);
+ tdsqlite3_str_append(p, ",", 1);
displayP4Expr(p, pExpr->pRight);
}
- sqlite3StrAccumAppend(p, ")", 1);
+ tdsqlite3_str_append(p, ")", 1);
}
}
#endif /* VDBE_DISPLAY_P4 && defined(SQLITE_ENABLE_CURSOR_HINTS) */
@@ -74800,20 +82547,23 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
char *zP4 = zTemp;
StrAccum x;
assert( nTemp>=20 );
- sqlite3StrAccumInit(&x, 0, zTemp, nTemp, 0);
+ tdsqlite3StrAccumInit(&x, 0, zTemp, nTemp, 0);
switch( pOp->p4type ){
case P4_KEYINFO: {
int j;
KeyInfo *pKeyInfo = pOp->p4.pKeyInfo;
- assert( pKeyInfo->aSortOrder!=0 );
- sqlite3XPrintf(&x, "k(%d", pKeyInfo->nField);
- for(j=0; j<pKeyInfo->nField; j++){
+ assert( pKeyInfo->aSortFlags!=0 );
+ tdsqlite3_str_appendf(&x, "k(%d", pKeyInfo->nKeyField);
+ for(j=0; j<pKeyInfo->nKeyField; j++){
CollSeq *pColl = pKeyInfo->aColl[j];
const char *zColl = pColl ? pColl->zName : "";
if( strcmp(zColl, "BINARY")==0 ) zColl = "B";
- sqlite3XPrintf(&x, ",%s%s", pKeyInfo->aSortOrder[j] ? "-" : "", zColl);
+ tdsqlite3_str_appendf(&x, ",%s%s%s",
+ (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_DESC) ? "-" : "",
+ (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_BIGNULL)? "N." : "",
+ zColl);
}
- sqlite3StrAccumAppend(&x, ")", 1);
+ tdsqlite3_str_append(&x, ")", 1);
break;
}
#ifdef SQLITE_ENABLE_CURSOR_HINTS
@@ -74824,41 +82574,39 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
#endif
case P4_COLLSEQ: {
CollSeq *pColl = pOp->p4.pColl;
- sqlite3XPrintf(&x, "(%.20s)", pColl->zName);
+ tdsqlite3_str_appendf(&x, "(%.20s)", pColl->zName);
break;
}
case P4_FUNCDEF: {
FuncDef *pDef = pOp->p4.pFunc;
- sqlite3XPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+ tdsqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg);
break;
}
-#ifdef SQLITE_DEBUG
case P4_FUNCCTX: {
FuncDef *pDef = pOp->p4.pCtx->pFunc;
- sqlite3XPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg);
+ tdsqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg);
break;
}
-#endif
case P4_INT64: {
- sqlite3XPrintf(&x, "%lld", *pOp->p4.pI64);
+ tdsqlite3_str_appendf(&x, "%lld", *pOp->p4.pI64);
break;
}
case P4_INT32: {
- sqlite3XPrintf(&x, "%d", pOp->p4.i);
+ tdsqlite3_str_appendf(&x, "%d", pOp->p4.i);
break;
}
case P4_REAL: {
- sqlite3XPrintf(&x, "%.16g", *pOp->p4.pReal);
+ tdsqlite3_str_appendf(&x, "%.16g", *pOp->p4.pReal);
break;
}
case P4_MEM: {
Mem *pMem = pOp->p4.pMem;
if( pMem->flags & MEM_Str ){
zP4 = pMem->z;
- }else if( pMem->flags & MEM_Int ){
- sqlite3XPrintf(&x, "%lld", pMem->u.i);
+ }else if( pMem->flags & (MEM_Int|MEM_IntReal) ){
+ tdsqlite3_str_appendf(&x, "%lld", pMem->u.i);
}else if( pMem->flags & MEM_Real ){
- sqlite3XPrintf(&x, "%.16g", pMem->u.r);
+ tdsqlite3_str_appendf(&x, "%.16g", pMem->u.r);
}else if( pMem->flags & MEM_Null ){
zP4 = "NULL";
}else{
@@ -74869,8 +82617,8 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
case P4_VTAB: {
- sqlite3_vtab *pVtab = pOp->p4.pVtab->pVtab;
- sqlite3XPrintf(&x, "vtab:%p", pVtab);
+ tdsqlite3_vtab *pVtab = pOp->p4.pVtab->pVtab;
+ tdsqlite3_str_appendf(&x, "vtab:%p", pVtab);
break;
}
#endif
@@ -74879,23 +82627,24 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
int *ai = pOp->p4.ai;
int n = ai[0]; /* The first element of an INTARRAY is always the
** count of the number of elements to follow */
- for(i=1; i<n; i++){
- sqlite3XPrintf(&x, ",%d", ai[i]);
+ for(i=1; i<=n; i++){
+ tdsqlite3_str_appendf(&x, ",%d", ai[i]);
}
zTemp[0] = '[';
- sqlite3StrAccumAppend(&x, "]", 1);
+ tdsqlite3_str_append(&x, "]", 1);
break;
}
case P4_SUBPROGRAM: {
- sqlite3XPrintf(&x, "program");
+ tdsqlite3_str_appendf(&x, "program");
break;
}
+ case P4_DYNBLOB:
case P4_ADVANCE: {
zTemp[0] = 0;
break;
}
case P4_TABLE: {
- sqlite3XPrintf(&x, "%s", pOp->p4.pTab->zName);
+ tdsqlite3_str_appendf(&x, "%s", pOp->p4.pTab->zName);
break;
}
default: {
@@ -74906,7 +82655,7 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
}
}
}
- sqlite3StrAccumFinish(&x);
+ tdsqlite3StrAccumFinish(&x);
assert( zP4!=0 );
return zP4;
}
@@ -74920,11 +82669,11 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
** is maintained in p->btreeMask. The p->lockMask value is the subset of
** p->btreeMask of databases that will require a lock.
*/
-SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe *p, int i){
+SQLITE_PRIVATE void tdsqlite3VdbeUsesBtree(Vdbe *p, int i){
assert( i>=0 && i<p->db->nDb && i<(int)sizeof(yDbMask)*8 );
assert( i<(int)sizeof(p->btreeMask)*8 );
DbMaskSet(p->btreeMask, i);
- if( i!=1 && sqlite3BtreeSharable(p->db->aDb[i].pBt) ){
+ if( i!=1 && tdsqlite3BtreeSharable(p->db->aDb[i].pBt) ){
DbMaskSet(p->lockMask, i);
}
}
@@ -74938,7 +82687,7 @@ SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe *p, int i){
** that the correct busy-handler callback is invoked if required.
**
** If SQLite is not threadsafe but does support shared-cache mode, then
-** sqlite3BtreeEnter() is invoked to set the BtShared.db variables
+** tdsqlite3BtreeEnter() is invoked to set the BtShared.db variables
** of all of BtShared structures accessible via the database handle
** associated with the VM.
**
@@ -74951,9 +82700,9 @@ SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe *p, int i){
** this routine is N*N. But as N is rarely more than 1, this should not
** be a problem.
*/
-SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe *p){
+SQLITE_PRIVATE void tdsqlite3VdbeEnter(Vdbe *p){
int i;
- sqlite3 *db;
+ tdsqlite3 *db;
Db *aDb;
int nDb;
if( DbMaskAllZero(p->lockMask) ) return; /* The common case */
@@ -74962,7 +82711,7 @@ SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe *p){
nDb = db->nDb;
for(i=0; i<nDb; i++){
if( i!=1 && DbMaskTest(p->lockMask,i) && ALWAYS(aDb[i].pBt!=0) ){
- sqlite3BtreeEnter(aDb[i].pBt);
+ tdsqlite3BtreeEnter(aDb[i].pBt);
}
}
}
@@ -74970,11 +82719,11 @@ SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe *p){
#if !defined(SQLITE_OMIT_SHARED_CACHE) && SQLITE_THREADSAFE>0
/*
-** Unlock all of the btrees previously locked by a call to sqlite3VdbeEnter().
+** Unlock all of the btrees previously locked by a call to tdsqlite3VdbeEnter().
*/
static SQLITE_NOINLINE void vdbeLeave(Vdbe *p){
int i;
- sqlite3 *db;
+ tdsqlite3 *db;
Db *aDb;
int nDb;
db = p->db;
@@ -74982,11 +82731,11 @@ static SQLITE_NOINLINE void vdbeLeave(Vdbe *p){
nDb = db->nDb;
for(i=0; i<nDb; i++){
if( i!=1 && DbMaskTest(p->lockMask,i) && ALWAYS(aDb[i].pBt!=0) ){
- sqlite3BtreeLeave(aDb[i].pBt);
+ tdsqlite3BtreeLeave(aDb[i].pBt);
}
}
}
-SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe *p){
+SQLITE_PRIVATE void tdsqlite3VdbeLeave(Vdbe *p){
if( DbMaskAllZero(p->lockMask) ) return; /* The common case */
vdbeLeave(p);
}
@@ -74996,7 +82745,7 @@ SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe *p){
/*
** Print a single opcode. This routine is used for debugging only.
*/
-SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, Op *pOp){
+SQLITE_PRIVATE void tdsqlite3VdbePrintOp(FILE *pOut, int pc, VdbeOp *pOp){
char *zP4;
char zPtr[50];
char zCom[100];
@@ -75008,11 +82757,11 @@ SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, Op *pOp){
#else
zCom[0] = 0;
#endif
- /* NB: The sqlite3OpcodeName() function is implemented by code created
+ /* NB: The tdsqlite3OpcodeName() function is implemented by code created
** by the mkopcodeh.awk and mkopcodec.awk scripts which extract the
** information from the vdbe.c source text */
fprintf(pOut, zFormat1, pc,
- sqlite3OpcodeName(pOp->opcode), pOp->p1, pOp->p2, pOp->p3, zP4, pOp->p5,
+ tdsqlite3OpcodeName(pOp->opcode), pOp->p1, pOp->p2, pOp->p3, zP4, pOp->p5,
zCom
);
fflush(pOut);
@@ -75022,7 +82771,7 @@ SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, Op *pOp){
/*
** Initialize an array of N Mem element.
*/
-static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){
+static void initMemArray(Mem *p, int N, tdsqlite3 *db, u16 flags){
while( (N--)>0 ){
p->db = db;
p->flags = flags;
@@ -75040,37 +82789,36 @@ static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){
static void releaseMemArray(Mem *p, int N){
if( p && N ){
Mem *pEnd = &p[N];
- sqlite3 *db = p->db;
+ tdsqlite3 *db = p->db;
if( db->pnBytesFreed ){
do{
- if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc);
+ if( p->szMalloc ) tdsqlite3DbFree(db, p->zMalloc);
}while( (++p)<pEnd );
return;
}
do{
assert( (&p[1])==pEnd || p[0].db==p[1].db );
- assert( sqlite3VdbeCheckMemInvariants(p) );
+ assert( tdsqlite3VdbeCheckMemInvariants(p) );
- /* This block is really an inlined version of sqlite3VdbeMemRelease()
+ /* This block is really an inlined version of tdsqlite3VdbeMemRelease()
** that takes advantage of the fact that the memory cell value is
** being set to NULL after releasing any dynamic resources.
**
** The justification for duplicating code is that according to
** callgrind, this causes a certain test case to hit the CPU 4.7
** percent less (x86 linux, gcc version 4.1.2, -O6) than if
- ** sqlite3MemRelease() were called from here. With -O2, this jumps
+ ** tdsqlite3MemRelease() were called from here. With -O2, this jumps
** to 6.6 percent. The test case is inserting 1000 rows into a table
** with no indexes using a single prepared INSERT statement, bind()
** and reset(). Inserts are grouped into a transaction.
*/
testcase( p->flags & MEM_Agg );
testcase( p->flags & MEM_Dyn );
- testcase( p->flags & MEM_Frame );
- testcase( p->flags & MEM_RowSet );
- if( p->flags&(MEM_Agg|MEM_Dyn|MEM_Frame|MEM_RowSet) ){
- sqlite3VdbeMemRelease(p);
+ testcase( p->xDel==tdsqlite3VdbeFrameMemDel );
+ if( p->flags&(MEM_Agg|MEM_Dyn) ){
+ tdsqlite3VdbeMemRelease(p);
}else if( p->szMalloc ){
- sqlite3DbFree(db, p->zMalloc);
+ tdsqlite3DbFreeNN(db, p->zMalloc);
p->szMalloc = 0;
}
@@ -75079,27 +82827,57 @@ static void releaseMemArray(Mem *p, int N){
}
}
+#ifdef SQLITE_DEBUG
+/*
+** Verify that pFrame is a valid VdbeFrame pointer. Return true if it is
+** and false if something is wrong.
+**
+** This routine is intended for use inside of assert() statements only.
+*/
+SQLITE_PRIVATE int tdsqlite3VdbeFrameIsValid(VdbeFrame *pFrame){
+ if( pFrame->iFrameMagic!=SQLITE_FRAME_MAGIC ) return 0;
+ return 1;
+}
+#endif
+
+
+/*
+** This is a destructor on a Mem object (which is really an tdsqlite3_value)
+** that deletes the Frame object that is attached to it as a blob.
+**
+** This routine does not delete the Frame right away. It merely adds the
+** frame to a list of frames to be deleted when the Vdbe halts.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeFrameMemDel(void *pArg){
+ VdbeFrame *pFrame = (VdbeFrame*)pArg;
+ assert( tdsqlite3VdbeFrameIsValid(pFrame) );
+ pFrame->pParent = pFrame->v->pDelFrame;
+ pFrame->v->pDelFrame = pFrame;
+}
+
+
/*
** Delete a VdbeFrame object and its contents. VdbeFrame objects are
-** allocated by the OP_Program opcode in sqlite3VdbeExec().
+** allocated by the OP_Program opcode in tdsqlite3VdbeExec().
*/
-SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame *p){
+SQLITE_PRIVATE void tdsqlite3VdbeFrameDelete(VdbeFrame *p){
int i;
Mem *aMem = VdbeFrameMem(p);
VdbeCursor **apCsr = (VdbeCursor **)&aMem[p->nChildMem];
+ assert( tdsqlite3VdbeFrameIsValid(p) );
for(i=0; i<p->nChildCsr; i++){
- sqlite3VdbeFreeCursor(p->v, apCsr[i]);
+ tdsqlite3VdbeFreeCursor(p->v, apCsr[i]);
}
releaseMemArray(aMem, p->nChildMem);
- sqlite3VdbeDeleteAuxData(p->v->db, &p->pAuxData, -1, 0);
- sqlite3DbFree(p->v->db, p);
+ tdsqlite3VdbeDeleteAuxData(p->v->db, &p->pAuxData, -1, 0);
+ tdsqlite3DbFree(p->v->db, p);
}
#ifndef SQLITE_OMIT_EXPLAIN
/*
** Give a listing of the program in the virtual machine.
**
-** The interface is the same as sqlite3VdbeExec(). But instead of
+** The interface is the same as tdsqlite3VdbeExec(). But instead of
** running the code, it invokes the callback once for each instruction.
** This feature is used to implement "EXPLAIN".
**
@@ -75107,21 +82885,26 @@ SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame *p){
** p->explain==2, only OP_Explain instructions are listed and these
** are shown in a different format. p->explain==2 is used to implement
** EXPLAIN QUERY PLAN.
+** 2018-04-24: In p->explain==2 mode, the OP_Init opcodes of triggers
+** are also shown, so that the boundaries between the main program and
+** each trigger are clear.
**
** When p->explain==1, first the main program is listed, then each of
** the trigger subprograms are listed one by one.
*/
-SQLITE_PRIVATE int sqlite3VdbeList(
+SQLITE_PRIVATE int tdsqlite3VdbeList(
Vdbe *p /* The VDBE */
){
int nRow; /* Stop when row count reaches this */
int nSub = 0; /* Number of sub-vdbes seen so far */
SubProgram **apSub = 0; /* Array of sub-vdbes */
Mem *pSub = 0; /* Memory cell hold array of subprogs */
- sqlite3 *db = p->db; /* The database connection */
+ tdsqlite3 *db = p->db; /* The database connection */
int i; /* Loop counter */
int rc = SQLITE_OK; /* Return code */
Mem *pMem = &p->aMem[1]; /* First Mem of result set */
+ int bListSubprogs = (p->explain==1 || (db->flags & SQLITE_TriggerEQP)!=0);
+ Op *pOp = 0;
assert( p->explain );
assert( p->magic==VDBE_MAGIC_RUN );
@@ -75129,27 +82912,27 @@ SQLITE_PRIVATE int sqlite3VdbeList(
/* Even though this opcode does not use dynamic strings for
** the result, result columns may become dynamic if the user calls
- ** sqlite3_column_text16(), causing a translation to UTF-16 encoding.
+ ** tdsqlite3_column_text16(), causing a translation to UTF-16 encoding.
*/
releaseMemArray(pMem, 8);
p->pResultSet = 0;
- if( p->rc==SQLITE_NOMEM_BKPT ){
- /* This happens if a malloc() inside a call to sqlite3_column_text() or
- ** sqlite3_column_text16() failed. */
- sqlite3OomFault(db);
+ if( p->rc==SQLITE_NOMEM ){
+ /* This happens if a malloc() inside a call to tdsqlite3_column_text() or
+ ** tdsqlite3_column_text16() failed. */
+ tdsqlite3OomFault(db);
return SQLITE_ERROR;
}
/* When the number of output rows reaches nRow, that means the
- ** listing has finished and sqlite3_step() should return SQLITE_DONE.
+ ** listing has finished and tdsqlite3_step() should return SQLITE_DONE.
** nRow is the sum of the number of rows in the main program, plus
** the sum of the number of rows in all trigger subprograms encountered
** so far. The nRow value will increase as new trigger subprograms are
** encountered, but p->pc will eventually catch up to nRow.
*/
nRow = p->nOp;
- if( p->explain==1 ){
+ if( bListSubprogs ){
/* The first 8 memory cells are used for the result set. So we will
** commandeer the 9th cell to use as storage for an array of pointers
** to trigger subprograms. The VDBE is guaranteed to have at least 9
@@ -75157,7 +82940,7 @@ SQLITE_PRIVATE int sqlite3VdbeList(
assert( p->nMem>9 );
pSub = &p->aMem[9];
if( pSub->flags&MEM_Blob ){
- /* On the first call to sqlite3_step(), pSub will hold a NULL. It is
+ /* On the first call to tdsqlite3_step(), pSub will hold a NULL. It is
** initialized to a BLOB by the P4_SUBPROGRAM processing logic below */
nSub = pSub->n/sizeof(Vdbe*);
apSub = (SubProgram **)pSub->z;
@@ -75167,19 +82950,13 @@ SQLITE_PRIVATE int sqlite3VdbeList(
}
}
- do{
+ while(1){ /* Loop exits via break */
i = p->pc++;
- }while( i<nRow && p->explain==2 && p->aOp[i].opcode!=OP_Explain );
- if( i>=nRow ){
- p->rc = SQLITE_OK;
- rc = SQLITE_DONE;
- }else if( db->u1.isInterrupted ){
- p->rc = SQLITE_INTERRUPT;
- rc = SQLITE_ERROR;
- sqlite3VdbeError(p, sqlite3ErrStr(p->rc));
- }else{
- char *zP4;
- Op *pOp;
+ if( i>=nRow ){
+ p->rc = SQLITE_OK;
+ rc = SQLITE_DONE;
+ break;
+ }
if( i<p->nOp ){
/* The output line number is small enough that we are still in the
** main program. */
@@ -75189,99 +82966,121 @@ SQLITE_PRIVATE int sqlite3VdbeList(
** pick up the appropriate opcode. */
int j;
i -= p->nOp;
+ assert( apSub!=0 );
+ assert( nSub>0 );
for(j=0; i>=apSub[j]->nOp; j++){
i -= apSub[j]->nOp;
+ assert( i<apSub[j]->nOp || j+1<nSub );
}
pOp = &apSub[j]->aOp[i];
}
- if( p->explain==1 ){
- pMem->flags = MEM_Int;
- pMem->u.i = i; /* Program counter */
- pMem++;
-
- pMem->flags = MEM_Static|MEM_Str|MEM_Term;
- pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */
- assert( pMem->z!=0 );
- pMem->n = sqlite3Strlen30(pMem->z);
- pMem->enc = SQLITE_UTF8;
- pMem++;
- /* When an OP_Program opcode is encounter (the only opcode that has
- ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms
- ** kept in p->aMem[9].z to hold the new program - assuming this subprogram
- ** has not already been seen.
- */
- if( pOp->p4type==P4_SUBPROGRAM ){
- int nByte = (nSub+1)*sizeof(SubProgram*);
- int j;
- for(j=0; j<nSub; j++){
- if( apSub[j]==pOp->p4.pProgram ) break;
- }
- if( j==nSub && SQLITE_OK==sqlite3VdbeMemGrow(pSub, nByte, nSub!=0) ){
- apSub = (SubProgram **)pSub->z;
- apSub[nSub++] = pOp->p4.pProgram;
- pSub->flags |= MEM_Blob;
- pSub->n = nSub*sizeof(SubProgram*);
+ /* When an OP_Program opcode is encounter (the only opcode that has
+ ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms
+ ** kept in p->aMem[9].z to hold the new program - assuming this subprogram
+ ** has not already been seen.
+ */
+ if( bListSubprogs && pOp->p4type==P4_SUBPROGRAM ){
+ int nByte = (nSub+1)*sizeof(SubProgram*);
+ int j;
+ for(j=0; j<nSub; j++){
+ if( apSub[j]==pOp->p4.pProgram ) break;
+ }
+ if( j==nSub ){
+ p->rc = tdsqlite3VdbeMemGrow(pSub, nByte, nSub!=0);
+ if( p->rc!=SQLITE_OK ){
+ rc = SQLITE_ERROR;
+ break;
}
+ apSub = (SubProgram **)pSub->z;
+ apSub[nSub++] = pOp->p4.pProgram;
+ pSub->flags |= MEM_Blob;
+ pSub->n = nSub*sizeof(SubProgram*);
+ nRow += pOp->p4.pProgram->nOp;
}
}
+ if( p->explain<2 ) break;
+ if( pOp->opcode==OP_Explain ) break;
+ if( pOp->opcode==OP_Init && p->pc>1 ) break;
+ }
- pMem->flags = MEM_Int;
- pMem->u.i = pOp->p1; /* P1 */
- pMem++;
+ if( rc==SQLITE_OK ){
+ if( db->u1.isInterrupted ){
+ p->rc = SQLITE_INTERRUPT;
+ rc = SQLITE_ERROR;
+ tdsqlite3VdbeError(p, tdsqlite3ErrStr(p->rc));
+ }else{
+ char *zP4;
+ if( p->explain==1 ){
+ pMem->flags = MEM_Int;
+ pMem->u.i = i; /* Program counter */
+ pMem++;
+
+ pMem->flags = MEM_Static|MEM_Str|MEM_Term;
+ pMem->z = (char*)tdsqlite3OpcodeName(pOp->opcode); /* Opcode */
+ assert( pMem->z!=0 );
+ pMem->n = tdsqlite3Strlen30(pMem->z);
+ pMem->enc = SQLITE_UTF8;
+ pMem++;
+ }
- pMem->flags = MEM_Int;
- pMem->u.i = pOp->p2; /* P2 */
- pMem++;
+ pMem->flags = MEM_Int;
+ pMem->u.i = pOp->p1; /* P1 */
+ pMem++;
- pMem->flags = MEM_Int;
- pMem->u.i = pOp->p3; /* P3 */
- pMem++;
+ pMem->flags = MEM_Int;
+ pMem->u.i = pOp->p2; /* P2 */
+ pMem++;
- if( sqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */
- assert( p->db->mallocFailed );
- return SQLITE_ERROR;
- }
- pMem->flags = MEM_Str|MEM_Term;
- zP4 = displayP4(pOp, pMem->z, pMem->szMalloc);
- if( zP4!=pMem->z ){
- pMem->n = 0;
- sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0);
- }else{
- assert( pMem->z!=0 );
- pMem->n = sqlite3Strlen30(pMem->z);
- pMem->enc = SQLITE_UTF8;
- }
- pMem++;
+ pMem->flags = MEM_Int;
+ pMem->u.i = pOp->p3; /* P3 */
+ pMem++;
- if( p->explain==1 ){
- if( sqlite3VdbeMemClearAndResize(pMem, 4) ){
+ if( tdsqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */
assert( p->db->mallocFailed );
return SQLITE_ERROR;
}
pMem->flags = MEM_Str|MEM_Term;
- pMem->n = 2;
- sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */
- pMem->enc = SQLITE_UTF8;
+ zP4 = displayP4(pOp, pMem->z, pMem->szMalloc);
+ if( zP4!=pMem->z ){
+ pMem->n = 0;
+ tdsqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0);
+ }else{
+ assert( pMem->z!=0 );
+ pMem->n = tdsqlite3Strlen30(pMem->z);
+ pMem->enc = SQLITE_UTF8;
+ }
pMem++;
-
+
+ if( p->explain==1 ){
+ if( tdsqlite3VdbeMemClearAndResize(pMem, 4) ){
+ assert( p->db->mallocFailed );
+ return SQLITE_ERROR;
+ }
+ pMem->flags = MEM_Str|MEM_Term;
+ pMem->n = 2;
+ tdsqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */
+ pMem->enc = SQLITE_UTF8;
+ pMem++;
+
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
- if( sqlite3VdbeMemClearAndResize(pMem, 500) ){
- assert( p->db->mallocFailed );
- return SQLITE_ERROR;
- }
- pMem->flags = MEM_Str|MEM_Term;
- pMem->n = displayComment(pOp, zP4, pMem->z, 500);
- pMem->enc = SQLITE_UTF8;
+ if( tdsqlite3VdbeMemClearAndResize(pMem, 500) ){
+ assert( p->db->mallocFailed );
+ return SQLITE_ERROR;
+ }
+ pMem->flags = MEM_Str|MEM_Term;
+ pMem->n = displayComment(pOp, zP4, pMem->z, 500);
+ pMem->enc = SQLITE_UTF8;
#else
- pMem->flags = MEM_Null; /* Comment */
+ pMem->flags = MEM_Null; /* Comment */
#endif
- }
+ }
- p->nResColumn = 8 - 4*(p->explain-1);
- p->pResultSet = &p->aMem[1];
- p->rc = SQLITE_OK;
- rc = SQLITE_ROW;
+ p->nResColumn = 8 - 4*(p->explain-1);
+ p->pResultSet = &p->aMem[1];
+ p->rc = SQLITE_OK;
+ rc = SQLITE_ROW;
+ }
}
return rc;
}
@@ -75291,7 +83090,7 @@ SQLITE_PRIVATE int sqlite3VdbeList(
/*
** Print the SQL that was used to generate a VDBE program.
*/
-SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe *p){
+SQLITE_PRIVATE void tdsqlite3VdbePrintSql(Vdbe *p){
const char *z = 0;
if( p->zSql ){
z = p->zSql;
@@ -75299,7 +83098,7 @@ SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe *p){
const VdbeOp *pOp = &p->aOp[0];
if( pOp->opcode==OP_Init && pOp->p4.z!=0 ){
z = pOp->p4.z;
- while( sqlite3Isspace(*z) ) z++;
+ while( tdsqlite3Isspace(*z) ) z++;
}
}
if( z ) printf("SQL: [%s]\n", z);
@@ -75310,19 +83109,19 @@ SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe *p){
/*
** Print an IOTRACE message showing SQL content.
*/
-SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe *p){
+SQLITE_PRIVATE void tdsqlite3VdbeIOTraceSql(Vdbe *p){
int nOp = p->nOp;
VdbeOp *pOp;
- if( sqlite3IoTrace==0 ) return;
+ if( tdsqlite3IoTrace==0 ) return;
if( nOp<1 ) return;
pOp = &p->aOp[0];
if( pOp->opcode==OP_Init && pOp->p4.z!=0 ){
int i, j;
char z[1000];
- sqlite3_snprintf(sizeof(z), z, "%s", pOp->p4.z);
- for(i=0; sqlite3Isspace(z[i]); i++){}
+ tdsqlite3_snprintf(sizeof(z), z, "%s", pOp->p4.z);
+ for(i=0; tdsqlite3Isspace(z[i]); i++){}
for(j=0; z[i]; i++){
- if( sqlite3Isspace(z[i]) ){
+ if( tdsqlite3Isspace(z[i]) ){
if( z[i-1]!=' ' ){
z[j++] = ' ';
}
@@ -75331,7 +83130,7 @@ SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe *p){
}
}
z[j] = 0;
- sqlite3IoTrace("SQL %s\n", z);
+ tdsqlite3IoTrace("SQL %s\n", z);
}
}
#endif /* !SQLITE_OMIT_TRACE && SQLITE_ENABLE_IOTRACE */
@@ -75341,9 +83140,9 @@ SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe *p){
** of a ReusableSpace object by the allocSpace() routine below.
*/
struct ReusableSpace {
- u8 *pSpace; /* Available memory */
- int nFree; /* Bytes of available memory */
- int nNeeded; /* Total bytes that could not be allocated */
+ u8 *pSpace; /* Available memory */
+ tdsqlite3_int64 nFree; /* Bytes of available memory */
+ tdsqlite3_int64 nNeeded; /* Total bytes that could not be allocated */
};
/* Try to allocate nByte bytes of 8-byte aligned bulk memory for pBuf
@@ -75363,7 +83162,7 @@ struct ReusableSpace {
static void *allocSpace(
struct ReusableSpace *p, /* Bulk memory available for allocation */
void *pBuf, /* Pointer to a prior allocation */
- int nByte /* Bytes of memory needed */
+ tdsqlite3_int64 nByte /* Bytes of memory needed */
){
assert( EIGHT_BYTE_ALIGNMENT(p->pSpace) );
if( pBuf==0 ){
@@ -75383,7 +83182,7 @@ static void *allocSpace(
** Rewind the VDBE back to the beginning in preparation for
** running it.
*/
-SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){
+SQLITE_PRIVATE void tdsqlite3VdbeRewind(Vdbe *p){
#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE)
int i;
#endif
@@ -75423,24 +83222,24 @@ SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){
** creating the virtual machine. This involves things such
** as allocating registers and initializing the program counter.
** After the VDBE has be prepped, it can be executed by one or more
-** calls to sqlite3VdbeExec().
+** calls to tdsqlite3VdbeExec().
**
** This function may be called exactly once on each virtual machine.
** After this routine is called the VM has been "packaged" and is ready
** to run. After this routine is called, further calls to
-** sqlite3VdbeAddOp() functions are prohibited. This routine disconnects
+** tdsqlite3VdbeAddOp() functions are prohibited. This routine disconnects
** the Vdbe from the Parse object that helped generate it so that the
** the Vdbe becomes an independent entity and the Parse object can be
** destroyed.
**
-** Use the sqlite3VdbeRewind() procedure to restore a virtual machine back
+** Use the tdsqlite3VdbeRewind() procedure to restore a virtual machine back
** to its initial state after it has been run.
*/
-SQLITE_PRIVATE void sqlite3VdbeMakeReady(
+SQLITE_PRIVATE void tdsqlite3VdbeMakeReady(
Vdbe *p, /* The VDBE */
Parse *pParse /* Parsing context */
){
- sqlite3 *db; /* The database connection */
+ tdsqlite3 *db; /* The database connection */
int nVar; /* Number of parameters */
int nMem; /* Number of VM memory registers */
int nCursor; /* Number of cursors required */
@@ -75481,8 +83280,26 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
resolveP2Values(p, &nArg);
p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort);
- if( pParse->explain && nMem<10 ){
- nMem = 10;
+ if( pParse->explain ){
+ static const char * const azColName[] = {
+ "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
+ "id", "parent", "notused", "detail"
+ };
+ int iFirst, mx, i;
+ if( nMem<10 ) nMem = 10;
+ if( pParse->explain==2 ){
+ tdsqlite3VdbeSetNumCols(p, 4);
+ iFirst = 8;
+ mx = 12;
+ }else{
+ tdsqlite3VdbeSetNumCols(p, 8);
+ iFirst = 0;
+ mx = 8;
+ }
+ for(i=iFirst; i<mx; i++){
+ tdsqlite3VdbeSetColName(p, i-iFirst, COLNAME_NAME,
+ azColName[i], SQLITE_STATIC);
+ }
}
p->expired = 0;
@@ -75496,24 +83313,30 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
** the leftover memory at the end of the opcode array. This can significantly
** reduce the amount of memory held by a prepared statement.
*/
- do {
- x.nNeeded = 0;
- p->aMem = allocSpace(&x, p->aMem, nMem*sizeof(Mem));
- p->aVar = allocSpace(&x, p->aVar, nVar*sizeof(Mem));
- p->apArg = allocSpace(&x, p->apArg, nArg*sizeof(Mem*));
- p->apCsr = allocSpace(&x, p->apCsr, nCursor*sizeof(VdbeCursor*));
+ x.nNeeded = 0;
+ p->aMem = allocSpace(&x, 0, nMem*sizeof(Mem));
+ p->aVar = allocSpace(&x, 0, nVar*sizeof(Mem));
+ p->apArg = allocSpace(&x, 0, nArg*sizeof(Mem*));
+ p->apCsr = allocSpace(&x, 0, nCursor*sizeof(VdbeCursor*));
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
- p->anExec = allocSpace(&x, p->anExec, p->nOp*sizeof(i64));
+ p->anExec = allocSpace(&x, 0, p->nOp*sizeof(i64));
#endif
- if( x.nNeeded==0 ) break;
- x.pSpace = p->pFree = sqlite3DbMallocRawNN(db, x.nNeeded);
+ if( x.nNeeded ){
+ x.pSpace = p->pFree = tdsqlite3DbMallocRawNN(db, x.nNeeded);
x.nFree = x.nNeeded;
- }while( !db->mallocFailed );
+ if( !db->mallocFailed ){
+ p->aMem = allocSpace(&x, p->aMem, nMem*sizeof(Mem));
+ p->aVar = allocSpace(&x, p->aVar, nVar*sizeof(Mem));
+ p->apArg = allocSpace(&x, p->apArg, nArg*sizeof(Mem*));
+ p->apCsr = allocSpace(&x, p->apCsr, nCursor*sizeof(VdbeCursor*));
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+ p->anExec = allocSpace(&x, p->anExec, p->nOp*sizeof(i64));
+#endif
+ }
+ }
- p->nzVar = pParse->nzVar;
- p->azVar = pParse->azVar;
- pParse->nzVar = 0;
- pParse->azVar = 0;
+ p->pVList = pParse->pVList;
+ pParse->pVList = 0;
p->explain = pParse->explain;
if( db->mallocFailed ){
p->nVar = 0;
@@ -75530,38 +83353,38 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
memset(p->anExec, 0, p->nOp*sizeof(i64));
#endif
}
- sqlite3VdbeRewind(p);
+ tdsqlite3VdbeRewind(p);
}
/*
** Close a VDBE cursor and release all the resources that cursor
** happens to hold.
*/
-SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){
+SQLITE_PRIVATE void tdsqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){
if( pCx==0 ){
return;
}
- assert( pCx->pBt==0 || pCx->eCurType==CURTYPE_BTREE );
+ assert( pCx->pBtx==0 || pCx->eCurType==CURTYPE_BTREE );
switch( pCx->eCurType ){
case CURTYPE_SORTER: {
- sqlite3VdbeSorterClose(p->db, pCx);
+ tdsqlite3VdbeSorterClose(p->db, pCx);
break;
}
case CURTYPE_BTREE: {
- if( pCx->pBt ){
- sqlite3BtreeClose(pCx->pBt);
+ if( pCx->isEphemeral ){
+ if( pCx->pBtx ) tdsqlite3BtreeClose(pCx->pBtx);
/* The pCx->pCursor will be close automatically, if it exists, by
** the call above. */
}else{
assert( pCx->uc.pCursor!=0 );
- sqlite3BtreeCloseCursor(pCx->uc.pCursor);
+ tdsqlite3BtreeCloseCursor(pCx->uc.pCursor);
}
break;
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
case CURTYPE_VTAB: {
- sqlite3_vtab_cursor *pVCur = pCx->uc.pVCur;
- const sqlite3_module *pModule = pVCur->pVtab->pModule;
+ tdsqlite3_vtab_cursor *pVCur = pCx->uc.pVCur;
+ const tdsqlite3_module *pModule = pVCur->pVtab->pModule;
assert( pVCur->pVtab->nRef>0 );
pVCur->pVtab->nRef--;
pModule->xClose(pVCur);
@@ -75580,7 +83403,7 @@ static void closeCursorsInFrame(Vdbe *p){
for(i=0; i<p->nCursor; i++){
VdbeCursor *pC = p->apCsr[i];
if( pC ){
- sqlite3VdbeFreeCursor(p, pC);
+ tdsqlite3VdbeFreeCursor(p, pC);
p->apCsr[i] = 0;
}
}
@@ -75592,7 +83415,7 @@ static void closeCursorsInFrame(Vdbe *p){
** is used, for example, when a trigger sub-program is halted to restore
** control to the main program.
*/
-SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){
+SQLITE_PRIVATE int tdsqlite3VdbeFrameRestore(VdbeFrame *pFrame){
Vdbe *v = pFrame->v;
closeCursorsInFrame(v);
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
@@ -75607,7 +83430,7 @@ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){
v->db->lastRowid = pFrame->lastRowid;
v->nChange = pFrame->nChange;
v->db->nChange = pFrame->nDbChange;
- sqlite3VdbeDeleteAuxData(v->db, &v->pAuxData, -1, 0);
+ tdsqlite3VdbeDeleteAuxData(v->db, &v->pAuxData, -1, 0);
v->pAuxData = pFrame->pAuxData;
pFrame->pAuxData = 0;
return pFrame->pc;
@@ -75625,7 +83448,7 @@ static void closeAllCursors(Vdbe *p){
if( p->pFrame ){
VdbeFrame *pFrame;
for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent);
- sqlite3VdbeFrameRestore(pFrame);
+ tdsqlite3VdbeFrameRestore(pFrame);
p->pFrame = 0;
p->nFrame = 0;
}
@@ -75637,66 +83460,46 @@ static void closeAllCursors(Vdbe *p){
while( p->pDelFrame ){
VdbeFrame *pDel = p->pDelFrame;
p->pDelFrame = pDel->pParent;
- sqlite3VdbeFrameDelete(pDel);
+ tdsqlite3VdbeFrameDelete(pDel);
}
/* Delete any auxdata allocations made by the VM */
- if( p->pAuxData ) sqlite3VdbeDeleteAuxData(p->db, &p->pAuxData, -1, 0);
+ if( p->pAuxData ) tdsqlite3VdbeDeleteAuxData(p->db, &p->pAuxData, -1, 0);
assert( p->pAuxData==0 );
}
/*
-** Clean up the VM after a single run.
-*/
-static void Cleanup(Vdbe *p){
- sqlite3 *db = p->db;
-
-#ifdef SQLITE_DEBUG
- /* Execute assert() statements to ensure that the Vdbe.apCsr[] and
- ** Vdbe.aMem[] arrays have already been cleaned up. */
- int i;
- if( p->apCsr ) for(i=0; i<p->nCursor; i++) assert( p->apCsr[i]==0 );
- if( p->aMem ){
- for(i=0; i<p->nMem; i++) assert( p->aMem[i].flags==MEM_Undefined );
- }
-#endif
-
- sqlite3DbFree(db, p->zErrMsg);
- p->zErrMsg = 0;
- p->pResultSet = 0;
-}
-
-/*
** Set the number of result columns that will be returned by this SQL
** statement. This is now set at compile time, rather than during
-** execution of the vdbe program so that sqlite3_column_count() can
-** be called on an SQL statement before sqlite3_step().
+** execution of the vdbe program so that tdsqlite3_column_count() can
+** be called on an SQL statement before tdsqlite3_step().
*/
-SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){
- Mem *pColName;
+SQLITE_PRIVATE void tdsqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){
int n;
- sqlite3 *db = p->db;
+ tdsqlite3 *db = p->db;
- releaseMemArray(p->aColName, p->nResColumn*COLNAME_N);
- sqlite3DbFree(db, p->aColName);
+ if( p->nResColumn ){
+ releaseMemArray(p->aColName, p->nResColumn*COLNAME_N);
+ tdsqlite3DbFree(db, p->aColName);
+ }
n = nResColumn*COLNAME_N;
p->nResColumn = (u16)nResColumn;
- p->aColName = pColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n );
+ p->aColName = (Mem*)tdsqlite3DbMallocRawNN(db, sizeof(Mem)*n );
if( p->aColName==0 ) return;
- initMemArray(p->aColName, n, p->db, MEM_Null);
+ initMemArray(p->aColName, n, db, MEM_Null);
}
/*
** Set the name of the idx'th column to be returned by the SQL statement.
** zName must be a pointer to a nul terminated string.
**
-** This call must be made after a call to sqlite3VdbeSetNumCols().
+** This call must be made after a call to tdsqlite3VdbeSetNumCols().
**
** The final parameter, xDel, must be one of SQLITE_DYNAMIC, SQLITE_STATIC
** or SQLITE_TRANSIENT. If it is SQLITE_DYNAMIC, then the buffer pointed
-** to by zName will be freed by sqlite3DbFree() when the vdbe is destroyed.
+** to by zName will be freed by tdsqlite3DbFree() when the vdbe is destroyed.
*/
-SQLITE_PRIVATE int sqlite3VdbeSetColName(
+SQLITE_PRIVATE int tdsqlite3VdbeSetColName(
Vdbe *p, /* Vdbe being configured */
int idx, /* Index of column zName applies to */
int var, /* One of the COLNAME_* constants */
@@ -75713,7 +83516,7 @@ SQLITE_PRIVATE int sqlite3VdbeSetColName(
}
assert( p->aColName!=0 );
pColName = &(p->aColName[idx+var*p->nResColumn]);
- rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel);
+ rc = tdsqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel);
assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 );
return rc;
}
@@ -75724,7 +83527,7 @@ SQLITE_PRIVATE int sqlite3VdbeSetColName(
** write-transaction spanning more than one database file, this routine
** takes care of the master journal trickery.
*/
-static int vdbeCommit(sqlite3 *db, Vdbe *p){
+static int vdbeCommit(tdsqlite3 *db, Vdbe *p){
int i;
int nTrans = 0; /* Number of databases with an active write-transaction
** that are candidates for a two-phase commit using a
@@ -75733,7 +83536,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
int needXcommit = 0;
#ifdef SQLITE_OMIT_VIRTUALTABLE
- /* With this option, sqlite3VtabSync() is defined to be simply
+ /* With this option, tdsqlite3VtabSync() is defined to be simply
** SQLITE_OK so p is not used.
*/
UNUSED_PARAMETER(p);
@@ -75745,7 +83548,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
** required, as an xSync() callback may add an attached database
** to the transaction.
*/
- rc = sqlite3VtabSync(db, p);
+ rc = tdsqlite3VtabSync(db, p);
/* This loop determines (a) if the commit hook should be invoked and
** (b) how many database files have open write transactions, not
@@ -75755,7 +83558,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
*/
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
- if( sqlite3BtreeIsInTrans(pBt) ){
+ if( tdsqlite3BtreeIsInTrans(pBt) ){
/* Whether or not a database might need a master journal depends upon
** its journal mode (among other things). This matrix determines which
** journal modes use a master journal and which do not */
@@ -75769,16 +83572,17 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
};
Pager *pPager; /* Pager associated with pBt */
needXcommit = 1;
- sqlite3BtreeEnter(pBt);
- pPager = sqlite3BtreePager(pBt);
+ tdsqlite3BtreeEnter(pBt);
+ pPager = tdsqlite3BtreePager(pBt);
if( db->aDb[i].safety_level!=PAGER_SYNCHRONOUS_OFF
- && aMJNeeded[sqlite3PagerGetJournalMode(pPager)]
+ && aMJNeeded[tdsqlite3PagerGetJournalMode(pPager)]
+ && tdsqlite3PagerIsMemdb(pPager)==0
){
assert( i!=1 );
nTrans++;
}
- rc = sqlite3PagerExclusiveLock(pPager);
- sqlite3BtreeLeave(pBt);
+ rc = tdsqlite3PagerExclusiveLock(pPager);
+ tdsqlite3BtreeLeave(pBt);
}
}
if( rc!=SQLITE_OK ){
@@ -75797,18 +83601,18 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
** TEMP database) has a transaction active. There is no need for the
** master-journal.
**
- ** If the return value of sqlite3BtreeGetFilename() is a zero length
+ ** If the return value of tdsqlite3BtreeGetFilename() is a zero length
** string, it means the main database is :memory: or a temp file. In
** that case we do not support atomic multi-file commits, so use the
** simple case then too.
*/
- if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt))
+ if( 0==tdsqlite3Strlen30(tdsqlite3BtreeGetFilename(db->aDb[0].pBt))
|| nTrans<=1
){
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( pBt ){
- rc = sqlite3BtreeCommitPhaseOne(pBt, 0);
+ rc = tdsqlite3BtreeCommitPhaseOne(pBt, 0);
}
}
@@ -75820,11 +83624,11 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( pBt ){
- rc = sqlite3BtreeCommitPhaseTwo(pBt, 0);
+ rc = tdsqlite3BtreeCommitPhaseTwo(pBt, 0);
}
}
if( rc==SQLITE_OK ){
- sqlite3VtabCommit(db);
+ tdsqlite3VtabCommit(db);
}
}
@@ -75834,49 +83638,49 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
*/
#ifndef SQLITE_OMIT_DISKIO
else{
- sqlite3_vfs *pVfs = db->pVfs;
+ tdsqlite3_vfs *pVfs = db->pVfs;
char *zMaster = 0; /* File-name for the master journal */
- char const *zMainFile = sqlite3BtreeGetFilename(db->aDb[0].pBt);
- sqlite3_file *pMaster = 0;
+ char const *zMainFile = tdsqlite3BtreeGetFilename(db->aDb[0].pBt);
+ tdsqlite3_file *pMaster = 0;
i64 offset = 0;
int res;
int retryCount = 0;
int nMainFile;
/* Select a master journal file name */
- nMainFile = sqlite3Strlen30(zMainFile);
- zMaster = sqlite3MPrintf(db, "%s-mjXXXXXX9XXz", zMainFile);
+ nMainFile = tdsqlite3Strlen30(zMainFile);
+ zMaster = tdsqlite3MPrintf(db, "%s-mjXXXXXX9XXz%c%c", zMainFile, 0, 0);
if( zMaster==0 ) return SQLITE_NOMEM_BKPT;
do {
u32 iRandom;
if( retryCount ){
if( retryCount>100 ){
- sqlite3_log(SQLITE_FULL, "MJ delete: %s", zMaster);
- sqlite3OsDelete(pVfs, zMaster, 0);
+ tdsqlite3_log(SQLITE_FULL, "MJ delete: %s", zMaster);
+ tdsqlite3OsDelete(pVfs, zMaster, 0);
break;
}else if( retryCount==1 ){
- sqlite3_log(SQLITE_FULL, "MJ collide: %s", zMaster);
+ tdsqlite3_log(SQLITE_FULL, "MJ collide: %s", zMaster);
}
}
retryCount++;
- sqlite3_randomness(sizeof(iRandom), &iRandom);
- sqlite3_snprintf(13, &zMaster[nMainFile], "-mj%06X9%02X",
+ tdsqlite3_randomness(sizeof(iRandom), &iRandom);
+ tdsqlite3_snprintf(13, &zMaster[nMainFile], "-mj%06X9%02X",
(iRandom>>8)&0xffffff, iRandom&0xff);
/* The antipenultimate character of the master journal name must
** be "9" to avoid name collisions when using 8+3 filenames. */
- assert( zMaster[sqlite3Strlen30(zMaster)-3]=='9' );
- sqlite3FileSuffix3(zMainFile, zMaster);
- rc = sqlite3OsAccess(pVfs, zMaster, SQLITE_ACCESS_EXISTS, &res);
+ assert( zMaster[tdsqlite3Strlen30(zMaster)-3]=='9' );
+ tdsqlite3FileSuffix3(zMainFile, zMaster);
+ rc = tdsqlite3OsAccess(pVfs, zMaster, SQLITE_ACCESS_EXISTS, &res);
}while( rc==SQLITE_OK && res );
if( rc==SQLITE_OK ){
/* Open the master journal. */
- rc = sqlite3OsOpenMalloc(pVfs, zMaster, &pMaster,
+ rc = tdsqlite3OsOpenMalloc(pVfs, zMaster, &pMaster,
SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|
SQLITE_OPEN_EXCLUSIVE|SQLITE_OPEN_MASTER_JOURNAL, 0
);
}
if( rc!=SQLITE_OK ){
- sqlite3DbFree(db, zMaster);
+ tdsqlite3DbFree(db, zMaster);
return rc;
}
@@ -75888,18 +83692,18 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
*/
for(i=0; i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
- if( sqlite3BtreeIsInTrans(pBt) ){
- char const *zFile = sqlite3BtreeGetJournalname(pBt);
+ if( tdsqlite3BtreeIsInTrans(pBt) ){
+ char const *zFile = tdsqlite3BtreeGetJournalname(pBt);
if( zFile==0 ){
continue; /* Ignore TEMP and :memory: databases */
}
assert( zFile[0]!=0 );
- rc = sqlite3OsWrite(pMaster, zFile, sqlite3Strlen30(zFile)+1, offset);
- offset += sqlite3Strlen30(zFile)+1;
+ rc = tdsqlite3OsWrite(pMaster, zFile, tdsqlite3Strlen30(zFile)+1, offset);
+ offset += tdsqlite3Strlen30(zFile)+1;
if( rc!=SQLITE_OK ){
- sqlite3OsCloseFree(pMaster);
- sqlite3OsDelete(pVfs, zMaster, 0);
- sqlite3DbFree(db, zMaster);
+ tdsqlite3OsCloseFree(pMaster);
+ tdsqlite3OsDelete(pVfs, zMaster, 0);
+ tdsqlite3DbFree(db, zMaster);
return rc;
}
}
@@ -75908,12 +83712,12 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
/* Sync the master journal file. If the IOCAP_SEQUENTIAL device
** flag is set this is not required.
*/
- if( 0==(sqlite3OsDeviceCharacteristics(pMaster)&SQLITE_IOCAP_SEQUENTIAL)
- && SQLITE_OK!=(rc = sqlite3OsSync(pMaster, SQLITE_SYNC_NORMAL))
+ if( 0==(tdsqlite3OsDeviceCharacteristics(pMaster)&SQLITE_IOCAP_SEQUENTIAL)
+ && SQLITE_OK!=(rc = tdsqlite3OsSync(pMaster, SQLITE_SYNC_NORMAL))
){
- sqlite3OsCloseFree(pMaster);
- sqlite3OsDelete(pVfs, zMaster, 0);
- sqlite3DbFree(db, zMaster);
+ tdsqlite3OsCloseFree(pMaster);
+ tdsqlite3OsDelete(pVfs, zMaster, 0);
+ tdsqlite3DbFree(db, zMaster);
return rc;
}
@@ -75922,7 +83726,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
** an error occurs here, do not delete the master journal file.
**
** If the error occurs during the first call to
- ** sqlite3BtreeCommitPhaseOne(), then there is a chance that the
+ ** tdsqlite3BtreeCommitPhaseOne(), then there is a chance that the
** master journal file will be orphaned. But we cannot delete it,
** in case the master journal file name was written into the journal
** file before the failure occurred.
@@ -75930,13 +83734,13 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( pBt ){
- rc = sqlite3BtreeCommitPhaseOne(pBt, zMaster);
+ rc = tdsqlite3BtreeCommitPhaseOne(pBt, zMaster);
}
}
- sqlite3OsCloseFree(pMaster);
+ tdsqlite3OsCloseFree(pMaster);
assert( rc!=SQLITE_BUSY );
if( rc!=SQLITE_OK ){
- sqlite3DbFree(db, zMaster);
+ tdsqlite3DbFree(db, zMaster);
return rc;
}
@@ -75944,32 +83748,32 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
** doing this the directory is synced again before any individual
** transaction files are deleted.
*/
- rc = sqlite3OsDelete(pVfs, zMaster, 1);
- sqlite3DbFree(db, zMaster);
+ rc = tdsqlite3OsDelete(pVfs, zMaster, 1);
+ tdsqlite3DbFree(db, zMaster);
zMaster = 0;
if( rc ){
return rc;
}
/* All files and directories have already been synced, so the following
- ** calls to sqlite3BtreeCommitPhaseTwo() are only closing files and
+ ** calls to tdsqlite3BtreeCommitPhaseTwo() are only closing files and
** deleting or truncating journals. If something goes wrong while
** this is happening we don't really care. The integrity of the
** transaction is already guaranteed, but some stray 'cold' journals
** may be lying around. Returning an error code won't help matters.
*/
disable_simulated_io_errors();
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
for(i=0; i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( pBt ){
- sqlite3BtreeCommitPhaseTwo(pBt, 1);
+ tdsqlite3BtreeCommitPhaseTwo(pBt, 1);
}
}
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
enable_simulated_io_errors();
- sqlite3VtabCommit(db);
+ tdsqlite3VtabCommit(db);
}
#endif
@@ -75986,14 +83790,14 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
** This is a no-op if NDEBUG is defined.
*/
#ifndef NDEBUG
-static void checkActiveVdbeCnt(sqlite3 *db){
+static void checkActiveVdbeCnt(tdsqlite3 *db){
Vdbe *p;
int cnt = 0;
int nWrite = 0;
int nRead = 0;
p = db->pVdbe;
while( p ){
- if( sqlite3_stmt_busy((sqlite3_stmt*)p) ){
+ if( tdsqlite3_stmt_busy((tdsqlite3_stmt*)p) ){
cnt++;
if( p->readOnly==0 ) nWrite++;
if( p->bIsReader ) nRead++;
@@ -76018,60 +83822,59 @@ static void checkActiveVdbeCnt(sqlite3 *db){
** If an IO error occurs, an SQLITE_IOERR_XXX error code is returned.
** Otherwise SQLITE_OK.
*/
-SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){
- sqlite3 *const db = p->db;
+static SQLITE_NOINLINE int vdbeCloseStatement(Vdbe *p, int eOp){
+ tdsqlite3 *const db = p->db;
int rc = SQLITE_OK;
+ int i;
+ const int iSavepoint = p->iStatement-1;
- /* If p->iStatement is greater than zero, then this Vdbe opened a
- ** statement transaction that should be closed here. The only exception
- ** is that an IO error may have occurred, causing an emergency rollback.
- ** In this case (db->nStatement==0), and there is nothing to do.
- */
- if( db->nStatement && p->iStatement ){
- int i;
- const int iSavepoint = p->iStatement-1;
-
- assert( eOp==SAVEPOINT_ROLLBACK || eOp==SAVEPOINT_RELEASE);
- assert( db->nStatement>0 );
- assert( p->iStatement==(db->nStatement+db->nSavepoint) );
+ assert( eOp==SAVEPOINT_ROLLBACK || eOp==SAVEPOINT_RELEASE);
+ assert( db->nStatement>0 );
+ assert( p->iStatement==(db->nStatement+db->nSavepoint) );
- for(i=0; i<db->nDb; i++){
- int rc2 = SQLITE_OK;
- Btree *pBt = db->aDb[i].pBt;
- if( pBt ){
- if( eOp==SAVEPOINT_ROLLBACK ){
- rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_ROLLBACK, iSavepoint);
- }
- if( rc2==SQLITE_OK ){
- rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_RELEASE, iSavepoint);
- }
- if( rc==SQLITE_OK ){
- rc = rc2;
- }
- }
- }
- db->nStatement--;
- p->iStatement = 0;
-
- if( rc==SQLITE_OK ){
+ for(i=0; i<db->nDb; i++){
+ int rc2 = SQLITE_OK;
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
if( eOp==SAVEPOINT_ROLLBACK ){
- rc = sqlite3VtabSavepoint(db, SAVEPOINT_ROLLBACK, iSavepoint);
+ rc2 = tdsqlite3BtreeSavepoint(pBt, SAVEPOINT_ROLLBACK, iSavepoint);
+ }
+ if( rc2==SQLITE_OK ){
+ rc2 = tdsqlite3BtreeSavepoint(pBt, SAVEPOINT_RELEASE, iSavepoint);
}
if( rc==SQLITE_OK ){
- rc = sqlite3VtabSavepoint(db, SAVEPOINT_RELEASE, iSavepoint);
+ rc = rc2;
}
}
+ }
+ db->nStatement--;
+ p->iStatement = 0;
- /* If the statement transaction is being rolled back, also restore the
- ** database handles deferred constraint counter to the value it had when
- ** the statement transaction was opened. */
+ if( rc==SQLITE_OK ){
if( eOp==SAVEPOINT_ROLLBACK ){
- db->nDeferredCons = p->nStmtDefCons;
- db->nDeferredImmCons = p->nStmtDefImmCons;
+ rc = tdsqlite3VtabSavepoint(db, SAVEPOINT_ROLLBACK, iSavepoint);
+ }
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3VtabSavepoint(db, SAVEPOINT_RELEASE, iSavepoint);
}
}
+
+ /* If the statement transaction is being rolled back, also restore the
+ ** database handles deferred constraint counter to the value it had when
+ ** the statement transaction was opened. */
+ if( eOp==SAVEPOINT_ROLLBACK ){
+ db->nDeferredCons = p->nStmtDefCons;
+ db->nDeferredImmCons = p->nStmtDefImmCons;
+ }
return rc;
}
+SQLITE_PRIVATE int tdsqlite3VdbeCloseStatement(Vdbe *p, int eOp){
+ if( p->db->nStatement && p->iStatement ){
+ return vdbeCloseStatement(p, eOp);
+ }
+ return SQLITE_OK;
+}
+
/*
** This function is called when a transaction opened by the database
@@ -76084,14 +83887,14 @@ SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){
** and write an error message to it. Then return SQLITE_ERROR.
*/
#ifndef SQLITE_OMIT_FOREIGN_KEY
-SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){
- sqlite3 *db = p->db;
+SQLITE_PRIVATE int tdsqlite3VdbeCheckFk(Vdbe *p, int deferred){
+ tdsqlite3 *db = p->db;
if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0)
|| (!deferred && p->nFkConstraint>0)
){
p->rc = SQLITE_CONSTRAINT_FOREIGNKEY;
p->errorAction = OE_Abort;
- sqlite3VdbeError(p, "FOREIGN KEY constraint failed");
+ tdsqlite3VdbeError(p, "FOREIGN KEY constraint failed");
return SQLITE_ERROR;
}
return SQLITE_OK;
@@ -76111,9 +83914,9 @@ SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){
** lock contention, return SQLITE_BUSY. If SQLITE_BUSY is returned, it
** means the close did not happen and needs to be repeated.
*/
-SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
+SQLITE_PRIVATE int tdsqlite3VdbeHalt(Vdbe *p){
int rc; /* Used to store transient return codes */
- sqlite3 *db = p->db;
+ tdsqlite3 *db = p->db;
/* This function contains the logic that determines if a statement or
** transaction will be committed or rolled back as a result of the
@@ -76131,13 +83934,13 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
** one, or the complete transaction if there is no statement transaction.
*/
+ if( p->magic!=VDBE_MAGIC_RUN ){
+ return SQLITE_OK;
+ }
if( db->mallocFailed ){
p->rc = SQLITE_NOMEM_BKPT;
}
closeAllCursors(p);
- if( p->magic!=VDBE_MAGIC_RUN ){
- return SQLITE_OK;
- }
checkActiveVdbeCnt(db);
/* No commit or rollback needed if the program never started or if the
@@ -76148,7 +83951,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
int isSpecialError; /* Set to true if a 'special' error */
/* Lock all btrees used by the statement */
- sqlite3VdbeEnter(p);
+ tdsqlite3VdbeEnter(p);
/* Check for one of the special errors */
mrc = p->rc & 0xff;
@@ -76174,8 +83977,8 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
/* We are forced to roll back the active transaction. Before doing
** so, abort any other statements this handle currently has active.
*/
- sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
- sqlite3CloseSavepoints(db);
+ tdsqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
+ tdsqlite3CloseSavepoints(db);
db->autoCommit = 1;
p->nChange = 0;
}
@@ -76183,8 +83986,8 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
}
/* Check for immediate foreign key violations. */
- if( p->rc==SQLITE_OK ){
- sqlite3VdbeCheckFk(p, 0);
+ if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){
+ tdsqlite3VdbeCheckFk(p, 0);
}
/* If the auto-commit flag is set and this is the only active writer
@@ -76193,15 +83996,15 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
** Note: This block also runs if one of the special errors handled
** above has occurred.
*/
- if( !sqlite3VtabInSync(db)
+ if( !tdsqlite3VtabInSync(db)
&& db->autoCommit
&& db->nVdbeWrite==(p->readOnly==0)
){
if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){
- rc = sqlite3VdbeCheckFk(p, 1);
+ rc = tdsqlite3VdbeCheckFk(p, 1);
if( rc!=SQLITE_OK ){
if( NEVER(p->readOnly) ){
- sqlite3VdbeLeave(p);
+ tdsqlite3VdbeLeave(p);
return SQLITE_ERROR;
}
rc = SQLITE_CONSTRAINT_FOREIGNKEY;
@@ -76213,20 +84016,20 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
rc = vdbeCommit(db, p);
}
if( rc==SQLITE_BUSY && p->readOnly ){
- sqlite3VdbeLeave(p);
+ tdsqlite3VdbeLeave(p);
return SQLITE_BUSY;
}else if( rc!=SQLITE_OK ){
p->rc = rc;
- sqlite3RollbackAll(db, SQLITE_OK);
+ tdsqlite3RollbackAll(db, SQLITE_OK);
p->nChange = 0;
}else{
db->nDeferredCons = 0;
db->nDeferredImmCons = 0;
- db->flags &= ~SQLITE_DeferFKs;
- sqlite3CommitInternalChanges(db);
+ db->flags &= ~(u64)SQLITE_DeferFKs;
+ tdsqlite3CommitInternalChanges(db);
}
}else{
- sqlite3RollbackAll(db, SQLITE_OK);
+ tdsqlite3RollbackAll(db, SQLITE_OK);
p->nChange = 0;
}
db->nStatement = 0;
@@ -76236,29 +84039,29 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
}else if( p->errorAction==OE_Abort ){
eStatementOp = SAVEPOINT_ROLLBACK;
}else{
- sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
- sqlite3CloseSavepoints(db);
+ tdsqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
+ tdsqlite3CloseSavepoints(db);
db->autoCommit = 1;
p->nChange = 0;
}
}
/* If eStatementOp is non-zero, then a statement transaction needs to
- ** be committed or rolled back. Call sqlite3VdbeCloseStatement() to
+ ** be committed or rolled back. Call tdsqlite3VdbeCloseStatement() to
** do so. If this operation returns an error, and the current statement
** error code is SQLITE_OK or SQLITE_CONSTRAINT, then promote the
** current statement error code.
*/
if( eStatementOp ){
- rc = sqlite3VdbeCloseStatement(p, eStatementOp);
+ rc = tdsqlite3VdbeCloseStatement(p, eStatementOp);
if( rc ){
if( p->rc==SQLITE_OK || (p->rc&0xff)==SQLITE_CONSTRAINT ){
p->rc = rc;
- sqlite3DbFree(db, p->zErrMsg);
+ tdsqlite3DbFree(db, p->zErrMsg);
p->zErrMsg = 0;
}
- sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
- sqlite3CloseSavepoints(db);
+ tdsqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
+ tdsqlite3CloseSavepoints(db);
db->autoCommit = 1;
p->nChange = 0;
}
@@ -76269,15 +84072,15 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
*/
if( p->changeCntOn ){
if( eStatementOp!=SAVEPOINT_ROLLBACK ){
- sqlite3VdbeSetChanges(db, p->nChange);
+ tdsqlite3VdbeSetChanges(db, p->nChange);
}else{
- sqlite3VdbeSetChanges(db, 0);
+ tdsqlite3VdbeSetChanges(db, 0);
}
p->nChange = 0;
}
/* Release the locks */
- sqlite3VdbeLeave(p);
+ tdsqlite3VdbeLeave(p);
}
/* We have successfully halted and closed the VM. Record this fact. */
@@ -76296,11 +84099,11 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
}
/* If the auto-commit flag is set to true, then any locks that were held
- ** by connection db have now been released. Call sqlite3ConnectionUnlocked()
+ ** by connection db have now been released. Call tdsqlite3ConnectionUnlocked()
** to invoke any required unlock-notify callbacks.
*/
if( db->autoCommit ){
- sqlite3ConnectionUnlocked(db);
+ tdsqlite3ConnectionUnlocked(db);
}
assert( db->nVdbeActive>0 || db->autoCommit==0 || db->nStatement==0 );
@@ -76309,35 +84112,35 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){
/*
-** Each VDBE holds the result of the most recent sqlite3_step() call
+** Each VDBE holds the result of the most recent tdsqlite3_step() call
** in p->rc. This routine sets that result back to SQLITE_OK.
*/
-SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe *p){
+SQLITE_PRIVATE void tdsqlite3VdbeResetStepResult(Vdbe *p){
p->rc = SQLITE_OK;
}
/*
** Copy the error code and error message belonging to the VDBE passed
** as the first argument to its database handle (so that they will be
-** returned by calls to sqlite3_errcode() and sqlite3_errmsg()).
+** returned by calls to tdsqlite3_errcode() and tdsqlite3_errmsg()).
**
** This function does not clear the VDBE error code or message, just
** copies them to the database handle.
*/
-SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p){
- sqlite3 *db = p->db;
+SQLITE_PRIVATE int tdsqlite3VdbeTransferError(Vdbe *p){
+ tdsqlite3 *db = p->db;
int rc = p->rc;
if( p->zErrMsg ){
db->bBenignMalloc++;
- sqlite3BeginBenignMalloc();
- if( db->pErr==0 ) db->pErr = sqlite3ValueNew(db);
- sqlite3ValueSetStr(db->pErr, -1, p->zErrMsg, SQLITE_UTF8, SQLITE_TRANSIENT);
- sqlite3EndBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
+ if( db->pErr==0 ) db->pErr = tdsqlite3ValueNew(db);
+ tdsqlite3ValueSetStr(db->pErr, -1, p->zErrMsg, SQLITE_UTF8, SQLITE_TRANSIENT);
+ tdsqlite3EndBenignMalloc();
db->bBenignMalloc--;
- db->errCode = rc;
- }else{
- sqlite3Error(db, rc);
+ }else if( db->pErr ){
+ tdsqlite3ValueSetNull(db->pErr);
}
+ db->errCode = rc;
return rc;
}
@@ -76347,14 +84150,14 @@ SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p){
** invoke it.
*/
static void vdbeInvokeSqllog(Vdbe *v){
- if( sqlite3GlobalConfig.xSqllog && v->rc==SQLITE_OK && v->zSql && v->pc>=0 ){
- char *zExpanded = sqlite3VdbeExpandSql(v, v->zSql);
+ if( tdsqlite3GlobalConfig.xSqllog && v->rc==SQLITE_OK && v->zSql && v->pc>=0 ){
+ char *zExpanded = tdsqlite3VdbeExpandSql(v, v->zSql);
assert( v->db->init.busy==0 );
if( zExpanded ){
- sqlite3GlobalConfig.xSqllog(
- sqlite3GlobalConfig.pSqllogArg, v->db, zExpanded, 1
+ tdsqlite3GlobalConfig.xSqllog(
+ tdsqlite3GlobalConfig.pSqllogArg, v->db, zExpanded, 1
);
- sqlite3DbFree(v->db, zExpanded);
+ tdsqlite3DbFree(v->db, zExpanded);
}
}
}
@@ -76373,40 +84176,53 @@ static void vdbeInvokeSqllog(Vdbe *v){
** virtual machine from VDBE_MAGIC_RUN or VDBE_MAGIC_HALT back to
** VDBE_MAGIC_INIT.
*/
-SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
- sqlite3 *db;
+SQLITE_PRIVATE int tdsqlite3VdbeReset(Vdbe *p){
+#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE)
+ int i;
+#endif
+
+ tdsqlite3 *db;
db = p->db;
/* If the VM did not run to completion or if it encountered an
** error, then it might not have been halted properly. So halt
** it now.
*/
- sqlite3VdbeHalt(p);
+ tdsqlite3VdbeHalt(p);
- /* If the VDBE has be run even partially, then transfer the error code
+ /* If the VDBE has been run even partially, then transfer the error code
** and error message from the VDBE into the main database structure. But
** if the VDBE has just been set to run but has not actually executed any
** instructions yet, leave the main database error information unchanged.
*/
if( p->pc>=0 ){
vdbeInvokeSqllog(p);
- sqlite3VdbeTransferError(p);
- sqlite3DbFree(db, p->zErrMsg);
- p->zErrMsg = 0;
+ tdsqlite3VdbeTransferError(p);
if( p->runOnlyOnce ) p->expired = 1;
}else if( p->rc && p->expired ){
/* The expired flag was set on the VDBE before the first call
- ** to sqlite3_step(). For consistency (since sqlite3_step() was
+ ** to tdsqlite3_step(). For consistency (since tdsqlite3_step() was
** called), set the database error in this case as well.
*/
- sqlite3ErrorWithMsg(db, p->rc, p->zErrMsg ? "%s" : 0, p->zErrMsg);
- sqlite3DbFree(db, p->zErrMsg);
- p->zErrMsg = 0;
+ tdsqlite3ErrorWithMsg(db, p->rc, p->zErrMsg ? "%s" : 0, p->zErrMsg);
}
- /* Reclaim all memory used by the VDBE
+ /* Reset register contents and reclaim error message memory.
*/
- Cleanup(p);
+#ifdef SQLITE_DEBUG
+ /* Execute assert() statements to ensure that the Vdbe.apCsr[] and
+ ** Vdbe.aMem[] arrays have already been cleaned up. */
+ if( p->apCsr ) for(i=0; i<p->nCursor; i++) assert( p->apCsr[i]==0 );
+ if( p->aMem ){
+ for(i=0; i<p->nMem; i++) assert( p->aMem[i].flags==MEM_Undefined );
+ }
+#endif
+ tdsqlite3DbFree(db, p->zErrMsg);
+ p->zErrMsg = 0;
+ p->pResultSet = 0;
+#ifdef SQLITE_DEBUG
+ p->nWrite = 0;
+#endif
/* Save profiling information from this VDBE run.
*/
@@ -76414,7 +84230,6 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
{
FILE *out = fopen("vdbe_profile.out", "a");
if( out ){
- int i;
fprintf(out, "---- ");
for(i=0; i<p->nOp; i++){
fprintf(out, "%02x", p->aOp[i].opcode);
@@ -76432,19 +84247,18 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
}
for(i=0; i<p->nOp; i++){
char zHdr[100];
- sqlite3_snprintf(sizeof(zHdr), zHdr, "%6u %12llu %8llu ",
+ tdsqlite3_snprintf(sizeof(zHdr), zHdr, "%6u %12llu %8llu ",
p->aOp[i].cnt,
p->aOp[i].cycles,
p->aOp[i].cnt>0 ? p->aOp[i].cycles/p->aOp[i].cnt : 0
);
fprintf(out, "%s", zHdr);
- sqlite3VdbePrintOp(out, i, &p->aOp[i]);
+ tdsqlite3VdbePrintOp(out, i, &p->aOp[i]);
}
fclose(out);
}
}
#endif
- p->iCurrentTime = 0;
p->magic = VDBE_MAGIC_RESET;
return p->rc & db->errMask;
}
@@ -76453,13 +84267,13 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){
** Clean up and delete a VDBE after execution. Return an integer which is
** the result code. Write any error message text into *pzErrMsg.
*/
-SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe *p){
+SQLITE_PRIVATE int tdsqlite3VdbeFinalize(Vdbe *p){
int rc = SQLITE_OK;
if( p->magic==VDBE_MAGIC_RUN || p->magic==VDBE_MAGIC_HALT ){
- rc = sqlite3VdbeReset(p);
+ rc = tdsqlite3VdbeReset(p);
assert( (rc & p->db->errMask)==rc );
}
- sqlite3VdbeDelete(p);
+ tdsqlite3VdbeDelete(p);
return rc;
}
@@ -76479,20 +84293,22 @@ SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe *p){
** * the corresponding bit in argument mask is clear (where the first
** function parameter corresponds to bit 0 etc.).
*/
-SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, int mask){
+SQLITE_PRIVATE void tdsqlite3VdbeDeleteAuxData(tdsqlite3 *db, AuxData **pp, int iOp, int mask){
while( *pp ){
AuxData *pAux = *pp;
if( (iOp<0)
- || (pAux->iOp==iOp && (pAux->iArg>31 || !(mask & MASKBIT32(pAux->iArg))))
+ || (pAux->iAuxOp==iOp
+ && pAux->iAuxArg>=0
+ && (pAux->iAuxArg>31 || !(mask & MASKBIT32(pAux->iAuxArg))))
){
- testcase( pAux->iArg==31 );
- if( pAux->xDelete ){
- pAux->xDelete(pAux->pAux);
+ testcase( pAux->iAuxArg==31 );
+ if( pAux->xDeleteAux ){
+ pAux->xDeleteAux(pAux->pAux);
}
- *pp = pAux->pNext;
- sqlite3DbFree(db, pAux);
+ *pp = pAux->pNextAux;
+ tdsqlite3DbFree(db, pAux);
}else{
- pp= &pAux->pNext;
+ pp= &pAux->pNextAux;
}
}
}
@@ -76501,47 +84317,58 @@ SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp,
** Free all memory associated with the Vdbe passed as the second argument,
** except for object itself, which is preserved.
**
-** The difference between this function and sqlite3VdbeDelete() is that
+** The difference between this function and tdsqlite3VdbeDelete() is that
** VdbeDelete() also unlinks the Vdbe from the list of VMs associated with
** the database connection and frees the object itself.
*/
-SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){
+SQLITE_PRIVATE void tdsqlite3VdbeClearObject(tdsqlite3 *db, Vdbe *p){
SubProgram *pSub, *pNext;
- int i;
assert( p->db==0 || p->db==db );
releaseMemArray(p->aColName, p->nResColumn*COLNAME_N);
for(pSub=p->pProgram; pSub; pSub=pNext){
pNext = pSub->pNext;
vdbeFreeOpArray(db, pSub->aOp, pSub->nOp);
- sqlite3DbFree(db, pSub);
+ tdsqlite3DbFree(db, pSub);
}
if( p->magic!=VDBE_MAGIC_INIT ){
releaseMemArray(p->aVar, p->nVar);
- for(i=p->nzVar-1; i>=0; i--) sqlite3DbFree(db, p->azVar[i]);
- sqlite3DbFree(db, p->azVar);
- sqlite3DbFree(db, p->pFree);
+ tdsqlite3DbFree(db, p->pVList);
+ tdsqlite3DbFree(db, p->pFree);
}
vdbeFreeOpArray(db, p->aOp, p->nOp);
- sqlite3DbFree(db, p->aColName);
- sqlite3DbFree(db, p->zSql);
+ tdsqlite3DbFree(db, p->aColName);
+ tdsqlite3DbFree(db, p->zSql);
+#ifdef SQLITE_ENABLE_NORMALIZE
+ tdsqlite3DbFree(db, p->zNormSql);
+ {
+ DblquoteStr *pThis, *pNext;
+ for(pThis=p->pDblStr; pThis; pThis=pNext){
+ pNext = pThis->pNextStr;
+ tdsqlite3DbFree(db, pThis);
+ }
+ }
+#endif
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
- for(i=0; i<p->nScan; i++){
- sqlite3DbFree(db, p->aScan[i].zName);
+ {
+ int i;
+ for(i=0; i<p->nScan; i++){
+ tdsqlite3DbFree(db, p->aScan[i].zName);
+ }
+ tdsqlite3DbFree(db, p->aScan);
}
- sqlite3DbFree(db, p->aScan);
#endif
}
/*
** Delete an entire VDBE.
*/
-SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){
- sqlite3 *db;
+SQLITE_PRIVATE void tdsqlite3VdbeDelete(Vdbe *p){
+ tdsqlite3 *db;
- if( NEVER(p==0) ) return;
+ assert( p!=0 );
db = p->db;
- assert( sqlite3_mutex_held(db->mutex) );
- sqlite3VdbeClearObject(db, p);
+ assert( tdsqlite3_mutex_held(db->mutex) );
+ tdsqlite3VdbeClearObject(db, p);
if( p->pPrev ){
p->pPrev->pNext = p->pNext;
}else{
@@ -76553,7 +84380,7 @@ SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){
}
p->magic = VDBE_MAGIC_DEAD;
p->db = 0;
- sqlite3DbFree(db, p);
+ tdsqlite3DbFreeNN(db, p);
}
/*
@@ -76561,19 +84388,19 @@ SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){
** carried out. Seek the cursor now. If an error occurs, return
** the appropriate error code.
*/
-static int SQLITE_NOINLINE handleDeferredMoveto(VdbeCursor *p){
+SQLITE_PRIVATE int SQLITE_NOINLINE tdsqlite3VdbeFinishMoveto(VdbeCursor *p){
int res, rc;
#ifdef SQLITE_TEST
- extern int sqlite3_search_count;
+ extern int tdsqlite3_search_count;
#endif
assert( p->deferredMoveto );
assert( p->isTable );
assert( p->eCurType==CURTYPE_BTREE );
- rc = sqlite3BtreeMovetoUnpacked(p->uc.pCursor, 0, p->movetoTarget, 0, &res);
+ rc = tdsqlite3BtreeMovetoUnpacked(p->uc.pCursor, 0, p->movetoTarget, 0, &res);
if( rc ) return rc;
if( res!=0 ) return SQLITE_CORRUPT_BKPT;
#ifdef SQLITE_TEST
- sqlite3_search_count++;
+ tdsqlite3_search_count++;
#endif
p->deferredMoveto = 0;
p->cacheStatus = CACHE_STALE;
@@ -76591,8 +84418,8 @@ static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){
int isDifferentRow, rc;
assert( p->eCurType==CURTYPE_BTREE );
assert( p->uc.pCursor!=0 );
- assert( sqlite3BtreeCursorHasMoved(p->uc.pCursor) );
- rc = sqlite3BtreeCursorRestore(p->uc.pCursor, &isDifferentRow);
+ assert( tdsqlite3BtreeCursorHasMoved(p->uc.pCursor) );
+ rc = tdsqlite3BtreeCursorRestore(p->uc.pCursor, &isDifferentRow);
p->cacheStatus = CACHE_STALE;
if( isDifferentRow ) p->nullRow = 1;
return rc;
@@ -76602,9 +84429,9 @@ static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){
** Check to ensure that the cursor is valid. Restore the cursor
** if need be. Return any I/O error from the restore operation.
*/
-SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor *p){
+SQLITE_PRIVATE int tdsqlite3VdbeCursorRestore(VdbeCursor *p){
assert( p->eCurType==CURTYPE_BTREE );
- if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){
+ if( tdsqlite3BtreeCursorHasMoved(p->uc.pCursor) ){
return handleMovedCursor(p);
}
return SQLITE_OK;
@@ -76623,21 +84450,20 @@ SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor *p){
** If the cursor is already pointing to the correct row and that row has
** not been deleted out from under the cursor, then this routine is a no-op.
*/
-SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, int *piCol){
+SQLITE_PRIVATE int tdsqlite3VdbeCursorMoveto(VdbeCursor **pp, int *piCol){
VdbeCursor *p = *pp;
- if( p->eCurType==CURTYPE_BTREE ){
- if( p->deferredMoveto ){
- int iMap;
- if( p->aAltMap && (iMap = p->aAltMap[1+*piCol])>0 ){
- *pp = p->pAltCursor;
- *piCol = iMap - 1;
- return SQLITE_OK;
- }
- return handleDeferredMoveto(p);
- }
- if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){
- return handleMovedCursor(p);
+ assert( p->eCurType==CURTYPE_BTREE || p->eCurType==CURTYPE_PSEUDO );
+ if( p->deferredMoveto ){
+ int iMap;
+ if( p->aAltMap && (iMap = p->aAltMap[1+*piCol])>0 ){
+ *pp = p->pAltCursor;
+ *piCol = iMap - 1;
+ return SQLITE_OK;
}
+ return tdsqlite3VdbeFinishMoveto(p);
+ }
+ if( tdsqlite3BtreeCursorHasMoved(p->uc.pCursor) ){
+ return handleMovedCursor(p);
}
return SQLITE_OK;
}
@@ -76645,11 +84471,11 @@ SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, int *piCol){
/*
** The following functions:
**
-** sqlite3VdbeSerialType()
-** sqlite3VdbeSerialTypeLen()
-** sqlite3VdbeSerialLen()
-** sqlite3VdbeSerialPut()
-** sqlite3VdbeSerialGet()
+** tdsqlite3VdbeSerialType()
+** tdsqlite3VdbeSerialTypeLen()
+** tdsqlite3VdbeSerialLen()
+** tdsqlite3VdbeSerialPut()
+** tdsqlite3VdbeSerialGet()
**
** encapsulate the code that serializes values for storage in SQLite
** data and index records. Each serialized value consists of a
@@ -76684,10 +84510,19 @@ SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, int *piCol){
** of SQLite will not understand those serial types.
*/
+#if 0 /* Inlined into the OP_MakeRecord opcode */
/*
** Return the serial-type for the value stored in pMem.
+**
+** This routine might convert a large MEM_IntReal value into MEM_Real.
+**
+** 2019-07-11: The primary user of this subroutine was the OP_MakeRecord
+** opcode in the byte-code engine. But by moving this routine in-line, we
+** can omit some redundant tests and make that opcode a lot faster. So
+** this routine is now only used by the STAT3 logic and STAT3 support has
+** ended. The code is kept here for historical reference only.
*/
-SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){
+SQLITE_PRIVATE u32 tdsqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){
int flags = pMem->flags;
u32 n;
@@ -76696,11 +84531,13 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){
*pLen = 0;
return 0;
}
- if( flags&MEM_Int ){
+ if( flags&(MEM_Int|MEM_IntReal) ){
/* Figure out whether to use 1, 2, 4, 6 or 8 bytes. */
# define MAX_6BYTE ((((i64)0x00008000)<<32)-1)
i64 i = pMem->u.i;
u64 u;
+ testcase( flags & MEM_Int );
+ testcase( flags & MEM_IntReal );
if( i<0 ){
u = ~i;
}else{
@@ -76720,6 +84557,15 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){
if( u<=2147483647 ){ *pLen = 4; return 4; }
if( u<=MAX_6BYTE ){ *pLen = 6; return 5; }
*pLen = 8;
+ if( flags&MEM_IntReal ){
+ /* If the value is IntReal and is going to take up 8 bytes to store
+ ** as an integer, then we might as well make it an 8-byte floating
+ ** point value */
+ pMem->u.r = (double)pMem->u.i;
+ pMem->flags &= ~MEM_IntReal;
+ pMem->flags |= MEM_Real;
+ return 7;
+ }
return 6;
}
if( flags&MEM_Real ){
@@ -76735,11 +84581,12 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){
*pLen = n;
return ((n*2) + 12 + ((flags&MEM_Str)!=0));
}
+#endif /* inlined into OP_MakeRecord */
/*
** The sizes for serial types less than 128
*/
-static const u8 sqlite3SmallTypeSizes[] = {
+static const u8 tdsqlite3SmallTypeSizes[] = {
/* 0 1 2 3 4 5 6 7 8 9 */
/* 0 */ 0, 1, 2, 3, 4, 6, 8, 8, 0, 0,
/* 10 */ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3,
@@ -76759,18 +84606,18 @@ static const u8 sqlite3SmallTypeSizes[] = {
/*
** Return the length of the data corresponding to the supplied serial-type.
*/
-SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32 serial_type){
+SQLITE_PRIVATE u32 tdsqlite3VdbeSerialTypeLen(u32 serial_type){
if( serial_type>=128 ){
return (serial_type-12)/2;
}else{
assert( serial_type<12
- || sqlite3SmallTypeSizes[serial_type]==(serial_type - 12)/2 );
- return sqlite3SmallTypeSizes[serial_type];
+ || tdsqlite3SmallTypeSizes[serial_type]==(serial_type - 12)/2 );
+ return tdsqlite3SmallTypeSizes[serial_type];
}
}
-SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){
+SQLITE_PRIVATE u8 tdsqlite3VdbeOneByteSerialTypeLen(u8 serial_type){
assert( serial_type<128 );
- return sqlite3SmallTypeSizes[serial_type];
+ return tdsqlite3SmallTypeSizes[serial_type];
}
/*
@@ -76839,7 +84686,7 @@ static u64 floatSwap(u64 in){
** of bytes in the zero-filled tail is included in the return value only
** if those bytes were zeroed in buf[].
*/
-SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){
+SQLITE_PRIVATE u32 tdsqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){
u32 len;
/* Integer and Real */
@@ -76853,7 +84700,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){
}else{
v = pMem->u.i;
}
- len = i = sqlite3SmallTypeSizes[serial_type];
+ len = i = tdsqlite3SmallTypeSizes[serial_type];
assert( i>0 );
do{
buf[--i] = (u8)(v&0xFF);
@@ -76865,7 +84712,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){
/* String or blob */
if( serial_type>=12 ){
assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.nZero:0)
- == (int)sqlite3VdbeSerialTypeLen(serial_type) );
+ == (int)tdsqlite3VdbeSerialTypeLen(serial_type) );
len = pMem->n;
if( len>0 ) memcpy(buf, pMem->z, len);
return len;
@@ -76893,7 +84740,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){
** routine so that in most cases the overhead of moving the stack pointer
** is avoided.
*/
-static u32 SQLITE_NOINLINE serialGet(
+static u32 serialGet(
const unsigned char *buf, /* Buffer to deserialize from */
u32 serial_type, /* Serial type to deserialize */
Mem *pMem /* Memory cell to write value into */
@@ -76925,17 +84772,23 @@ static u32 SQLITE_NOINLINE serialGet(
assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 );
swapMixedEndianFloat(x);
memcpy(&pMem->u.r, &x, sizeof(x));
- pMem->flags = sqlite3IsNaN(pMem->u.r) ? MEM_Null : MEM_Real;
+ pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real;
}
return 8;
}
-SQLITE_PRIVATE u32 sqlite3VdbeSerialGet(
+SQLITE_PRIVATE u32 tdsqlite3VdbeSerialGet(
const unsigned char *buf, /* Buffer to deserialize from */
u32 serial_type, /* Serial type to deserialize */
Mem *pMem /* Memory cell to write value into */
){
switch( serial_type ){
- case 10: /* Reserved for future use */
+ case 10: { /* Internal use only: NULL with virtual table
+ ** UPDATE no-change flag set */
+ pMem->flags = MEM_Null|MEM_Zero;
+ pMem->n = 0;
+ pMem->u.nZero = 0;
+ break;
+ }
case 11: /* Reserved for future use */
case 0: { /* Null */
/* EVIDENCE-OF: R-24078-09375 Value is a NULL. */
@@ -77016,47 +84869,30 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet(
}
/*
** This routine is used to allocate sufficient space for an UnpackedRecord
-** structure large enough to be used with sqlite3VdbeRecordUnpack() if
+** structure large enough to be used with tdsqlite3VdbeRecordUnpack() if
** the first argument is a pointer to KeyInfo structure pKeyInfo.
**
-** The space is either allocated using sqlite3DbMallocRaw() or from within
+** The space is either allocated using tdsqlite3DbMallocRaw() or from within
** the unaligned buffer passed via the second and third arguments (presumably
** stack space). If the former, then *ppFree is set to a pointer that should
-** be eventually freed by the caller using sqlite3DbFree(). Or, if the
+** be eventually freed by the caller using tdsqlite3DbFree(). Or, if the
** allocation comes from the pSpace/szSpace buffer, *ppFree is set to NULL
** before returning.
**
** If an OOM error occurs, NULL is returned.
*/
-SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(
- KeyInfo *pKeyInfo, /* Description of the record */
- char *pSpace, /* Unaligned space available */
- int szSpace, /* Size of pSpace[] in bytes */
- char **ppFree /* OUT: Caller should free this pointer */
+SQLITE_PRIVATE UnpackedRecord *tdsqlite3VdbeAllocUnpackedRecord(
+ KeyInfo *pKeyInfo /* Description of the record */
){
UnpackedRecord *p; /* Unpacked record to return */
- int nOff; /* Increment pSpace by nOff to align it */
int nByte; /* Number of bytes required for *p */
-
- /* We want to shift the pointer pSpace up such that it is 8-byte aligned.
- ** Thus, we need to calculate a value, nOff, between 0 and 7, to shift
- ** it by. If pSpace is already 8-byte aligned, nOff should be zero.
- */
- nOff = (8 - (SQLITE_PTR_TO_INT(pSpace) & 7)) & 7;
- nByte = ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nField+1);
- if( nByte>szSpace+nOff ){
- p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte);
- *ppFree = (char *)p;
- if( !p ) return 0;
- }else{
- p = (UnpackedRecord*)&pSpace[nOff];
- *ppFree = 0;
- }
-
+ nByte = ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1);
+ p = (UnpackedRecord *)tdsqlite3DbMallocRaw(pKeyInfo->db, nByte);
+ if( !p ) return 0;
p->aMem = (Mem*)&((char*)p)[ROUND8(sizeof(UnpackedRecord))];
- assert( pKeyInfo->aSortOrder!=0 );
+ assert( pKeyInfo->aSortFlags!=0 );
p->pKeyInfo = pKeyInfo;
- p->nField = pKeyInfo->nField + 1;
+ p->nField = pKeyInfo->nKeyField + 1;
return p;
}
@@ -77065,14 +84901,14 @@ SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(
** UnpackedRecord structure indicated by the fourth argument with the
** contents of the decoded record.
*/
-SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(
+SQLITE_PRIVATE void tdsqlite3VdbeRecordUnpack(
KeyInfo *pKeyInfo, /* Information about the record format */
int nKey, /* Size of the binary record */
const void *pKey, /* The binary record */
UnpackedRecord *p /* Populate this structure before returning. */
){
const unsigned char *aKey = (const unsigned char *)pKey;
- int d;
+ u32 d;
u32 idx; /* Offset in aKey[] to read from */
u16 u; /* Unsigned loop counter */
u32 szHdr;
@@ -77083,31 +84919,38 @@ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(
idx = getVarint32(aKey, szHdr);
d = szHdr;
u = 0;
- while( idx<szHdr && d<=nKey ){
+ while( idx<szHdr && d<=(u32)nKey ){
u32 serial_type;
idx += getVarint32(&aKey[idx], serial_type);
pMem->enc = pKeyInfo->enc;
pMem->db = pKeyInfo->db;
- /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */
+ /* pMem->flags = 0; // tdsqlite3VdbeSerialGet() will set this for us */
pMem->szMalloc = 0;
pMem->z = 0;
- d += sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem);
+ d += tdsqlite3VdbeSerialGet(&aKey[d], serial_type, pMem);
pMem++;
if( (++u)>=p->nField ) break;
}
- assert( u<=pKeyInfo->nField + 1 );
+ if( d>(u32)nKey && u ){
+ assert( CORRUPT_DB );
+ /* In a corrupt record entry, the last pMem might have been set up using
+ ** uninitialized memory. Overwrite its value with NULL, to prevent
+ ** warnings from MSAN. */
+ tdsqlite3VdbeMemSetNull(pMem-1);
+ }
+ assert( u<=pKeyInfo->nKeyField + 1 );
p->nField = u;
}
-#if SQLITE_DEBUG
+#ifdef SQLITE_DEBUG
/*
** This function compares two index or table record keys in the same way
-** as the sqlite3VdbeRecordCompare() routine. Unlike VdbeRecordCompare(),
+** as the tdsqlite3VdbeRecordCompare() routine. Unlike VdbeRecordCompare(),
** this function deserializes and compares values using the
-** sqlite3VdbeSerialGet() and sqlite3MemCompare() functions. It is used
+** tdsqlite3VdbeSerialGet() and tdsqlite3MemCompare() functions. It is used
** in assert() statements to ensure that the optimized code in
-** sqlite3VdbeRecordCompare() returns results with these two primitives.
+** tdsqlite3VdbeRecordCompare() returns results with these two primitives.
**
** Return true if the result of comparison is equivalent to desiredResult.
** Return false if there is a disagreement.
@@ -77130,7 +84973,7 @@ static int vdbeRecordCompareDebug(
if( pKeyInfo->db==0 ) return 1;
mem1.enc = pKeyInfo->enc;
mem1.db = pKeyInfo->db;
- /* mem1.flags = 0; // Will be initialized by sqlite3VdbeSerialGet() */
+ /* mem1.flags = 0; // Will be initialized by tdsqlite3VdbeSerialGet() */
VVA_ONLY( mem1.szMalloc = 0; ) /* Only needed by assert() statements */
/* Compilers may complain that mem1.u.i is potentially uninitialized.
@@ -77145,9 +84988,9 @@ static int vdbeRecordCompareDebug(
idx1 = getVarint32(aKey1, szHdr1);
if( szHdr1>98307 ) return SQLITE_CORRUPT;
d1 = szHdr1;
- assert( pKeyInfo->nField+pKeyInfo->nXField>=pPKey2->nField || CORRUPT_DB );
- assert( pKeyInfo->aSortOrder!=0 );
- assert( pKeyInfo->nField>0 );
+ assert( pKeyInfo->nAllField>=pPKey2->nField || CORRUPT_DB );
+ assert( pKeyInfo->aSortFlags!=0 );
+ assert( pKeyInfo->nKeyField>0 );
assert( idx1<=szHdr1 || CORRUPT_DB );
do{
u32 serial_type1;
@@ -77159,24 +85002,30 @@ static int vdbeRecordCompareDebug(
** a buffer overread. The "d1+serial_type1+2" subexpression will
** always be greater than or equal to the amount of required key space.
** Use that approximation to avoid the more expensive call to
- ** sqlite3VdbeSerialTypeLen() in the common case.
+ ** tdsqlite3VdbeSerialTypeLen() in the common case.
*/
- if( d1+serial_type1+2>(u32)nKey1
- && d1+sqlite3VdbeSerialTypeLen(serial_type1)>(u32)nKey1
+ if( d1+(u64)serial_type1+2>(u64)nKey1
+ && d1+(u64)tdsqlite3VdbeSerialTypeLen(serial_type1)>(u64)nKey1
){
break;
}
/* Extract the values to be compared.
*/
- d1 += sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1);
+ d1 += tdsqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1);
/* Do the comparison
*/
- rc = sqlite3MemCompare(&mem1, &pPKey2->aMem[i], pKeyInfo->aColl[i]);
+ rc = tdsqlite3MemCompare(&mem1, &pPKey2->aMem[i],
+ pKeyInfo->nAllField>i ? pKeyInfo->aColl[i] : 0);
if( rc!=0 ){
assert( mem1.szMalloc==0 ); /* See comment below */
- if( pKeyInfo->aSortOrder[i] ){
+ if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL)
+ && ((mem1.flags & MEM_Null) || (pPKey2->aMem[i].flags & MEM_Null))
+ ){
+ rc = -rc;
+ }
+ if( pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_DESC ){
rc = -rc; /* Invert the result for DESC sort order. */
}
goto debugCompareEnd;
@@ -77186,7 +85035,7 @@ static int vdbeRecordCompareDebug(
/* No memory allocation is ever used on mem1. Prove this using
** the following assert(). If the assert() fails, it indicates a
- ** memory leak and a need to call sqlite3VdbeMemRelease(&mem1).
+ ** memory leak and a need to call tdsqlite3VdbeMemRelease(&mem1).
*/
assert( mem1.szMalloc==0 );
@@ -77205,16 +85054,16 @@ debugCompareEnd:
}
#endif
-#if SQLITE_DEBUG
+#ifdef SQLITE_DEBUG
/*
** Count the number of fields (a.k.a. columns) in the record given by
** pKey,nKey. The verify that this count is less than or equal to the
-** limit given by pKeyInfo->nField + pKeyInfo->nXField.
+** limit given by pKeyInfo->nAllField.
**
** If this constraint is not satisfied, it means that the high-speed
** vdbeRecordCompareInt() and vdbeRecordCompareString() routines will
** not work correctly. If this assert() ever fires, it probably means
-** that the KeyInfo.nField or KeyInfo.nXField values were computed
+** that the KeyInfo.nKeyField or KeyInfo.nAllField values were computed
** incorrectly.
*/
static void vdbeAssertFieldCountWithinLimits(
@@ -77235,7 +85084,7 @@ static void vdbeAssertFieldCountWithinLimits(
idx += getVarint32(aKey+idx, notUsed);
nField++;
}
- assert( nField <= pKeyInfo->nField+pKeyInfo->nXField );
+ assert( nField <= pKeyInfo->nAllField );
}
#else
# define vdbeAssertFieldCountWithinLimits(A,B,C)
@@ -77260,21 +85109,22 @@ static int vdbeCompareMemString(
}else{
int rc;
const void *v1, *v2;
- int n1, n2;
Mem c1;
Mem c2;
- sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null);
- sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null);
- sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem);
- sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem);
- v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc);
- n1 = v1==0 ? 0 : c1.n;
- v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc);
- n2 = v2==0 ? 0 : c2.n;
- rc = pColl->xCmp(pColl->pUser, n1, v1, n2, v2);
- if( (v1==0 || v2==0) && prcErr ) *prcErr = SQLITE_NOMEM_BKPT;
- sqlite3VdbeMemRelease(&c1);
- sqlite3VdbeMemRelease(&c2);
+ tdsqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null);
+ tdsqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null);
+ tdsqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem);
+ tdsqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem);
+ v1 = tdsqlite3ValueText((tdsqlite3_value*)&c1, pColl->enc);
+ v2 = tdsqlite3ValueText((tdsqlite3_value*)&c2, pColl->enc);
+ if( (v1==0 || v2==0) ){
+ if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT;
+ rc = 0;
+ }else{
+ rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2);
+ }
+ tdsqlite3VdbeMemRelease(&c1);
+ tdsqlite3VdbeMemRelease(&c2);
return rc;
}
}
@@ -77296,7 +85146,7 @@ static int isAllZero(const char *z, int n){
** is less than, equal to, or greater than the second, respectively.
** If one blob is a prefix of the other, then the shorter is the lessor.
*/
-static SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem *pB2){
+SQLITE_PRIVATE SQLITE_NOINLINE int tdsqlite3BlobCompare(const Mem *pB1, const Mem *pB2){
int c;
int n1 = pB1->n;
int n2 = pB2->n;
@@ -77304,7 +85154,7 @@ static SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem *pB2){
/* It is possible to have a Blob value that has some non-zero content
** followed by zero content. But that only comes up for Blobs formed
** by the OP_MakeRecord opcode, and such Blobs never get passed into
- ** sqlite3MemCompare(). */
+ ** tdsqlite3MemCompare(). */
assert( (pB1->flags & MEM_Zero)==0 || n1==0 );
assert( (pB2->flags & MEM_Zero)==0 || n2==0 );
@@ -77329,7 +85179,7 @@ static SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem *pB2){
** number. Return negative, zero, or positive if the first (i64) is less than,
** equal to, or greater than the second (double).
*/
-static int sqlite3IntFloatCompare(i64 i, double r){
+static int tdsqlite3IntFloatCompare(i64 i, double r){
if( sizeof(LONGDOUBLE_TYPE)>8 ){
LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i;
if( x<r ) return -1;
@@ -77339,13 +85189,10 @@ static int sqlite3IntFloatCompare(i64 i, double r){
i64 y;
double s;
if( r<-9223372036854775808.0 ) return +1;
- if( r>9223372036854775807.0 ) return -1;
+ if( r>=9223372036854775808.0 ) return -1;
y = (i64)r;
if( i<y ) return -1;
- if( i>y ){
- if( y==SMALLEST_INT64 && r>0.0 ) return -1;
- return +1;
- }
+ if( i>y ) return +1;
s = (double)i;
if( s<r ) return -1;
if( s>r ) return +1;
@@ -77362,14 +85209,14 @@ static int sqlite3IntFloatCompare(i64 i, double r){
**
** Two NULL values are considered equal by this function.
*/
-SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){
+SQLITE_PRIVATE int tdsqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const CollSeq *pColl){
int f1, f2;
int combined_flags;
f1 = pMem1->flags;
f2 = pMem2->flags;
combined_flags = f1|f2;
- assert( (combined_flags & MEM_RowSet)==0 );
+ assert( !tdsqlite3VdbeMemIsRowSet(pMem1) && !tdsqlite3VdbeMemIsRowSet(pMem2) );
/* If one value is NULL, it is less than the other. If both values
** are NULL, return 0.
@@ -77380,8 +85227,13 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C
/* At least one of the two values is a number
*/
- if( combined_flags&(MEM_Int|MEM_Real) ){
- if( (f1 & f2 & MEM_Int)!=0 ){
+ if( combined_flags&(MEM_Int|MEM_Real|MEM_IntReal) ){
+ testcase( combined_flags & MEM_Int );
+ testcase( combined_flags & MEM_Real );
+ testcase( combined_flags & MEM_IntReal );
+ if( (f1 & f2 & (MEM_Int|MEM_IntReal))!=0 ){
+ testcase( f1 & f2 & MEM_Int );
+ testcase( f1 & f2 & MEM_IntReal );
if( pMem1->u.i < pMem2->u.i ) return -1;
if( pMem1->u.i > pMem2->u.i ) return +1;
return 0;
@@ -77391,16 +85243,24 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C
if( pMem1->u.r > pMem2->u.r ) return +1;
return 0;
}
- if( (f1&MEM_Int)!=0 ){
+ if( (f1&(MEM_Int|MEM_IntReal))!=0 ){
+ testcase( f1 & MEM_Int );
+ testcase( f1 & MEM_IntReal );
if( (f2&MEM_Real)!=0 ){
- return sqlite3IntFloatCompare(pMem1->u.i, pMem2->u.r);
+ return tdsqlite3IntFloatCompare(pMem1->u.i, pMem2->u.r);
+ }else if( (f2&(MEM_Int|MEM_IntReal))!=0 ){
+ if( pMem1->u.i < pMem2->u.i ) return -1;
+ if( pMem1->u.i > pMem2->u.i ) return +1;
+ return 0;
}else{
return -1;
}
}
if( (f1&MEM_Real)!=0 ){
- if( (f2&MEM_Int)!=0 ){
- return -sqlite3IntFloatCompare(pMem2->u.i, pMem1->u.r);
+ if( (f2&(MEM_Int|MEM_IntReal))!=0 ){
+ testcase( f2 & MEM_Int );
+ testcase( f2 & MEM_IntReal );
+ return -tdsqlite3IntFloatCompare(pMem2->u.i, pMem1->u.r);
}else{
return -1;
}
@@ -77437,7 +85297,7 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C
}
/* Both values must be blobs. Compare using memcmp(). */
- return sqlite3BlobCompare(pMem1, pMem2);
+ return tdsqlite3BlobCompare(pMem1, pMem2);
}
@@ -77489,7 +85349,7 @@ static i64 vdbeRecordDecodeInt(u32 serial_type, const u8 *aKey){
** greater than key2. The {nKey1, pKey1} key must be a blob
** created by the OP_MakeRecord opcode of the VDBE. The pPKey2
** key must be a parsed key such as obtained from
-** sqlite3VdbeParseRecord.
+** tdsqlite3VdbeParseRecord.
**
** If argument bSkip is non-zero, it is assumed that the caller has already
** determined that the first fields of the keys are equal.
@@ -77503,7 +85363,7 @@ static i64 vdbeRecordDecodeInt(u32 serial_type, const u8 *aKey){
** pPKey2->errCode is set to SQLITE_NOMEM and, if it is not NULL, the
** malloc-failed flag set on database handle (pPKey2->pKeyInfo->db).
*/
-SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
+SQLITE_PRIVATE int tdsqlite3VdbeRecordCompareWithSkip(
int nKey1, const void *pKey1, /* Left key */
UnpackedRecord *pPKey2, /* Right key */
int bSkip /* If true, skip the first field */
@@ -77514,7 +85374,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
u32 idx1; /* Offset of first type in header */
int rc = 0; /* Return value */
Mem *pRhs = pPKey2->aMem; /* Next field of pPKey2 to compare */
- KeyInfo *pKeyInfo = pPKey2->pKeyInfo;
+ KeyInfo *pKeyInfo;
const unsigned char *aKey1 = (const unsigned char *)pKey1;
Mem mem1;
@@ -77525,30 +85385,32 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
u32 s1;
idx1 = 1 + getVarint32(&aKey1[1], s1);
szHdr1 = aKey1[0];
- d1 = szHdr1 + sqlite3VdbeSerialTypeLen(s1);
+ d1 = szHdr1 + tdsqlite3VdbeSerialTypeLen(s1);
i = 1;
pRhs++;
}else{
idx1 = getVarint32(aKey1, szHdr1);
d1 = szHdr1;
- if( d1>(unsigned)nKey1 ){
- pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT;
- return 0; /* Corruption */
- }
i = 0;
}
+ if( d1>(unsigned)nKey1 ){
+ pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT;
+ return 0; /* Corruption */
+ }
VVA_ONLY( mem1.szMalloc = 0; ) /* Only needed by assert() statements */
- assert( pPKey2->pKeyInfo->nField+pPKey2->pKeyInfo->nXField>=pPKey2->nField
+ assert( pPKey2->pKeyInfo->nAllField>=pPKey2->nField
|| CORRUPT_DB );
- assert( pPKey2->pKeyInfo->aSortOrder!=0 );
- assert( pPKey2->pKeyInfo->nField>0 );
+ assert( pPKey2->pKeyInfo->aSortFlags!=0 );
+ assert( pPKey2->pKeyInfo->nKeyField>0 );
assert( idx1<=szHdr1 || CORRUPT_DB );
do{
u32 serial_type;
/* RHS is an integer */
- if( pRhs->flags & MEM_Int ){
+ if( pRhs->flags & (MEM_Int|MEM_IntReal) ){
+ testcase( pRhs->flags & MEM_Int );
+ testcase( pRhs->flags & MEM_IntReal );
serial_type = aKey1[idx1];
testcase( serial_type==12 );
if( serial_type>=10 ){
@@ -77556,8 +85418,8 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
}else if( serial_type==0 ){
rc = -1;
}else if( serial_type==7 ){
- sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
- rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r);
+ tdsqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
+ rc = -tdsqlite3IntFloatCompare(pRhs->u.i, mem1.u.r);
}else{
i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]);
i64 rhs = pRhs->u.i;
@@ -77581,7 +85443,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
}else if( serial_type==0 ){
rc = -1;
}else{
- sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
+ tdsqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
if( serial_type==7 ){
if( mem1.u.r<pRhs->u.r ){
rc = -1;
@@ -77589,7 +85451,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
rc = +1;
}
}else{
- rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r);
+ rc = tdsqlite3IntFloatCompare(mem1.u.i, pRhs->u.r);
}
}
}
@@ -77606,7 +85468,9 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
mem1.n = (serial_type - 12) / 2;
testcase( (d1+mem1.n)==(unsigned)nKey1 );
testcase( (d1+mem1.n+1)==(unsigned)nKey1 );
- if( (d1+mem1.n) > (unsigned)nKey1 ){
+ if( (d1+mem1.n) > (unsigned)nKey1
+ || (pKeyInfo = pPKey2->pKeyInfo)->nAllField<=i
+ ){
pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT;
return 0; /* Corruption */
}else if( pKeyInfo->aColl[i] ){
@@ -77660,8 +85524,14 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
}
if( rc!=0 ){
- if( pKeyInfo->aSortOrder[i] ){
- rc = -rc;
+ int sortFlags = pPKey2->pKeyInfo->aSortFlags[i];
+ if( sortFlags ){
+ if( (sortFlags & KEYINFO_ORDER_BIGNULL)==0
+ || ((sortFlags & KEYINFO_ORDER_DESC)
+ !=(serial_type==0 || (pRhs->flags&MEM_Null)))
+ ){
+ rc = -rc;
+ }
}
assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, rc) );
assert( mem1.szMalloc==0 ); /* See comment below */
@@ -77669,14 +85539,15 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
}
i++;
+ if( i==pPKey2->nField ) break;
pRhs++;
- d1 += sqlite3VdbeSerialTypeLen(serial_type);
- idx1 += sqlite3VarintLen(serial_type);
- }while( idx1<(unsigned)szHdr1 && i<pPKey2->nField && d1<=(unsigned)nKey1 );
+ d1 += tdsqlite3VdbeSerialTypeLen(serial_type);
+ idx1 += tdsqlite3VarintLen(serial_type);
+ }while( idx1<(unsigned)szHdr1 && d1<=(unsigned)nKey1 );
/* No memory allocation is ever used on mem1. Prove this using
** the following assert(). If the assert() fails, it indicates a
- ** memory leak and a need to call sqlite3VdbeMemRelease(&mem1). */
+ ** memory leak and a need to call tdsqlite3VdbeMemRelease(&mem1). */
assert( mem1.szMalloc==0 );
/* rc==0 here means that one or both of the keys ran out of fields and
@@ -77684,21 +85555,21 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
** value. */
assert( CORRUPT_DB
|| vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, pPKey2->default_rc)
- || pKeyInfo->db->mallocFailed
+ || pPKey2->pKeyInfo->db->mallocFailed
);
pPKey2->eqSeen = 1;
return pPKey2->default_rc;
}
-SQLITE_PRIVATE int sqlite3VdbeRecordCompare(
+SQLITE_PRIVATE int tdsqlite3VdbeRecordCompare(
int nKey1, const void *pKey1, /* Left key */
UnpackedRecord *pPKey2 /* Right key */
){
- return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 0);
+ return tdsqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 0);
}
/*
-** This function is an optimized version of sqlite3VdbeRecordCompare()
+** This function is an optimized version of tdsqlite3VdbeRecordCompare()
** that (a) the first field of pPKey2 is an integer, and (b) the
** size-of-header varint at the start of (pKey1/nKey1) fits in a single
** byte (i.e. is less than 128).
@@ -77768,10 +85639,10 @@ static int vdbeRecordCompareInt(
** (as gcc is clever enough to combine the two like cases). Other
** compilers might be similar. */
case 0: case 7:
- return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2);
+ return tdsqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2);
default:
- return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2);
+ return tdsqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2);
}
v = pPKey2->aMem[0].u.i;
@@ -77782,7 +85653,7 @@ static int vdbeRecordCompareInt(
}else if( pPKey2->nField>1 ){
/* The first fields of the two keys are equal. Compare the trailing
** fields. */
- res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1);
+ res = tdsqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1);
}else{
/* The first fields of the two keys are equal and there are no trailing
** fields. Return pPKey2->default_rc in this case. */
@@ -77795,7 +85666,7 @@ static int vdbeRecordCompareInt(
}
/*
-** This function is an optimized version of sqlite3VdbeRecordCompare()
+** This function is an optimized version of tdsqlite3VdbeRecordCompare()
** that (a) the first field of pPKey2 is a string, that (b) the first field
** uses the collation sequence BINARY and (c) that the size-of-header varint
** at the start of (pKey1/nKey1) fits in a single byte.
@@ -77828,11 +85699,15 @@ static int vdbeRecordCompareString(
nCmp = MIN( pPKey2->aMem[0].n, nStr );
res = memcmp(&aKey1[szHdr], pPKey2->aMem[0].z, nCmp);
- if( res==0 ){
+ if( res>0 ){
+ res = pPKey2->r2;
+ }else if( res<0 ){
+ res = pPKey2->r1;
+ }else{
res = nStr - pPKey2->aMem[0].n;
if( res==0 ){
if( pPKey2->nField>1 ){
- res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1);
+ res = tdsqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1);
}else{
res = pPKey2->default_rc;
pPKey2->eqSeen = 1;
@@ -77842,10 +85717,6 @@ static int vdbeRecordCompareString(
}else{
res = pPKey2->r1;
}
- }else if( res>0 ){
- res = pPKey2->r2;
- }else{
- res = pPKey2->r1;
}
}
@@ -77857,11 +85728,11 @@ static int vdbeRecordCompareString(
}
/*
-** Return a pointer to an sqlite3VdbeRecordCompare() compatible function
+** Return a pointer to an tdsqlite3VdbeRecordCompare() compatible function
** suitable for comparing serialized records to the unpacked record passed
** as the only argument.
*/
-SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){
+SQLITE_PRIVATE RecordCompare tdsqlite3VdbeFindCompare(UnpackedRecord *p){
/* varintRecordCompareInt() and varintRecordCompareString() both assume
** that the size-of-header varint that occurs at the start of each record
** fits in a single byte (i.e. is 127 or less). varintRecordCompareInt()
@@ -77875,9 +85746,12 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){
** The easiest way to enforce this limit is to consider only records with
** 13 fields or less. If the first field is an integer, the maximum legal
** header size is (12*5 + 1 + 1) bytes. */
- if( (p->pKeyInfo->nField + p->pKeyInfo->nXField)<=13 ){
+ if( p->pKeyInfo->nAllField<=13 ){
int flags = p->aMem[0].flags;
- if( p->pKeyInfo->aSortOrder[0] ){
+ if( p->pKeyInfo->aSortFlags[0] ){
+ if( p->pKeyInfo->aSortFlags[0] & KEYINFO_ORDER_BIGNULL ){
+ return tdsqlite3VdbeRecordCompare;
+ }
p->r1 = 1;
p->r2 = -1;
}else{
@@ -77890,13 +85764,15 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){
testcase( flags & MEM_Real );
testcase( flags & MEM_Null );
testcase( flags & MEM_Blob );
- if( (flags & (MEM_Real|MEM_Null|MEM_Blob))==0 && p->pKeyInfo->aColl[0]==0 ){
+ if( (flags & (MEM_Real|MEM_IntReal|MEM_Null|MEM_Blob))==0
+ && p->pKeyInfo->aColl[0]==0
+ ){
assert( flags & MEM_Str );
return vdbeRecordCompareString;
}
}
- return sqlite3VdbeRecordCompare;
+ return tdsqlite3VdbeRecordCompare;
}
/*
@@ -77907,7 +85783,7 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){
** pCur might be pointing to text obtained from a corrupt database file.
** So the content cannot be trusted. Do appropriate checks on the content.
*/
-SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){
+SQLITE_PRIVATE int tdsqlite3VdbeIdxRowid(tdsqlite3 *db, BtCursor *pCur, i64 *rowid){
i64 nCellKey = 0;
int rc;
u32 szHdr; /* Size of the header */
@@ -77917,16 +85793,16 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){
/* Get the size of the index entry. Only indices entries of less
** than 2GiB are support - anything large must be database corruption.
- ** Any corruption is detected in sqlite3BtreeParseCellPtr(), though, so
+ ** Any corruption is detected in tdsqlite3BtreeParseCellPtr(), though, so
** this code can safely assume that nCellKey is 32-bits
*/
- assert( sqlite3BtreeCursorIsValid(pCur) );
- nCellKey = sqlite3BtreePayloadSize(pCur);
+ assert( tdsqlite3BtreeCursorIsValid(pCur) );
+ nCellKey = tdsqlite3BtreePayloadSize(pCur);
assert( (nCellKey & SQLITE_MAX_U32)==(u64)nCellKey );
/* Read in the complete content of the index entry */
- sqlite3VdbeMemInit(&m, db, 0);
- rc = sqlite3VdbeMemFromBtree(pCur, 0, (u32)nCellKey, 1, &m);
+ tdsqlite3VdbeMemInit(&m, db, 0);
+ rc = tdsqlite3VdbeMemFromBtree(pCur, 0, (u32)nCellKey, &m);
if( rc ){
return rc;
}
@@ -77935,7 +85811,9 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){
(void)getVarint32((u8*)m.z, szHdr);
testcase( szHdr==3 );
testcase( szHdr==m.n );
- if( unlikely(szHdr<3 || (int)szHdr>m.n) ){
+ testcase( szHdr>0x7fffffff );
+ assert( m.n>=0 );
+ if( unlikely(szHdr<3 || szHdr>(unsigned)m.n) ){
goto idx_rowid_corruption;
}
@@ -77953,23 +85831,23 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){
if( unlikely(typeRowid<1 || typeRowid>9 || typeRowid==7) ){
goto idx_rowid_corruption;
}
- lenRowid = sqlite3SmallTypeSizes[typeRowid];
+ lenRowid = tdsqlite3SmallTypeSizes[typeRowid];
testcase( (u32)m.n==szHdr+lenRowid );
if( unlikely((u32)m.n<szHdr+lenRowid) ){
goto idx_rowid_corruption;
}
/* Fetch the integer off the end of the index record */
- sqlite3VdbeSerialGet((u8*)&m.z[m.n-lenRowid], typeRowid, &v);
+ tdsqlite3VdbeSerialGet((u8*)&m.z[m.n-lenRowid], typeRowid, &v);
*rowid = v.u.i;
- sqlite3VdbeMemRelease(&m);
+ tdsqlite3VdbeMemRelease(&m);
return SQLITE_OK;
/* Jump here if database corruption is detected after m has been
** allocated. Free the m object and return SQLITE_CORRUPT. */
idx_rowid_corruption:
testcase( m.szMalloc!=0 );
- sqlite3VdbeMemRelease(&m);
+ tdsqlite3VdbeMemRelease(&m);
return SQLITE_CORRUPT_BKPT;
}
@@ -77984,8 +85862,8 @@ idx_rowid_corruption:
** is ignored as well. Hence, this routine only compares the prefixes
** of the keys prior to the final rowid, not the entire key.
*/
-SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(
- sqlite3 *db, /* Database connection */
+SQLITE_PRIVATE int tdsqlite3VdbeIdxKeyCompare(
+ tdsqlite3 *db, /* Database connection */
VdbeCursor *pC, /* The cursor to compare against */
UnpackedRecord *pUnpacked, /* Unpacked version of key */
int *res /* Write the comparison result here */
@@ -77997,30 +85875,30 @@ SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(
assert( pC->eCurType==CURTYPE_BTREE );
pCur = pC->uc.pCursor;
- assert( sqlite3BtreeCursorIsValid(pCur) );
- nCellKey = sqlite3BtreePayloadSize(pCur);
+ assert( tdsqlite3BtreeCursorIsValid(pCur) );
+ nCellKey = tdsqlite3BtreePayloadSize(pCur);
/* nCellKey will always be between 0 and 0xffffffff because of the way
- ** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */
+ ** that btreeParseCellPtr() and tdsqlite3GetVarint32() are implemented */
if( nCellKey<=0 || nCellKey>0x7fffffff ){
*res = 0;
return SQLITE_CORRUPT_BKPT;
}
- sqlite3VdbeMemInit(&m, db, 0);
- rc = sqlite3VdbeMemFromBtree(pCur, 0, (u32)nCellKey, 1, &m);
+ tdsqlite3VdbeMemInit(&m, db, 0);
+ rc = tdsqlite3VdbeMemFromBtree(pCur, 0, (u32)nCellKey, &m);
if( rc ){
return rc;
}
- *res = sqlite3VdbeRecordCompare(m.n, m.z, pUnpacked);
- sqlite3VdbeMemRelease(&m);
+ *res = tdsqlite3VdbeRecordCompareWithSkip(m.n, m.z, pUnpacked, 0);
+ tdsqlite3VdbeMemRelease(&m);
return SQLITE_OK;
}
/*
** This routine sets the value to be returned by subsequent calls to
-** sqlite3_changes() on the database handle 'db'.
+** tdsqlite3_changes() on the database handle 'db'.
*/
-SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *db, int nChange){
- assert( sqlite3_mutex_held(db->mutex) );
+SQLITE_PRIVATE void tdsqlite3VdbeSetChanges(tdsqlite3 *db, int nChange){
+ assert( tdsqlite3_mutex_held(db->mutex) );
db->nChange = nChange;
db->nTotalChange += nChange;
}
@@ -78029,7 +85907,7 @@ SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *db, int nChange){
** Set a flag in the vdbe to update the change counter when it is finalised
** or reset.
*/
-SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe *v){
+SQLITE_PRIVATE void tdsqlite3VdbeCountChanges(Vdbe *v){
v->changeCntOn = 1;
}
@@ -78042,38 +85920,54 @@ SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe *v){
** programs obsolete. Removing user-defined functions or collating
** sequences, or changing an authorization function are the types of
** things that make prepared statements obsolete.
+**
+** If iCode is 1, then expiration is advisory. The statement should
+** be reprepared before being restarted, but if it is already running
+** it is allowed to run to completion.
+**
+** Internally, this function just sets the Vdbe.expired flag on all
+** prepared statements. The flag is set to 1 for an immediate expiration
+** and set to 2 for an advisory expiration.
*/
-SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3ExpirePreparedStatements(tdsqlite3 *db, int iCode){
Vdbe *p;
for(p = db->pVdbe; p; p=p->pNext){
- p->expired = 1;
+ p->expired = iCode+1;
}
}
/*
** Return the database associated with the Vdbe.
*/
-SQLITE_PRIVATE sqlite3 *sqlite3VdbeDb(Vdbe *v){
+SQLITE_PRIVATE tdsqlite3 *tdsqlite3VdbeDb(Vdbe *v){
return v->db;
}
/*
-** Return a pointer to an sqlite3_value structure containing the value bound
+** Return the SQLITE_PREPARE flags for a Vdbe.
+*/
+SQLITE_PRIVATE u8 tdsqlite3VdbePrepareFlags(Vdbe *v){
+ return v->prepFlags;
+}
+
+/*
+** Return a pointer to an tdsqlite3_value structure containing the value bound
** parameter iVar of VM v. Except, if the value is an SQL NULL, return
** 0 instead. Unless it is NULL, apply affinity aff (one of the SQLITE_AFF_*
** constants) to the value before returning it.
**
-** The returned value must be freed by the caller using sqlite3ValueFree().
+** The returned value must be freed by the caller using tdsqlite3ValueFree().
*/
-SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff){
+SQLITE_PRIVATE tdsqlite3_value *tdsqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff){
assert( iVar>0 );
if( v ){
Mem *pMem = &v->aVar[iVar-1];
+ assert( (v->db->flags & SQLITE_EnableQPSG)==0 );
if( 0==(pMem->flags & MEM_Null) ){
- sqlite3_value *pRet = sqlite3ValueNew(v->db);
+ tdsqlite3_value *pRet = tdsqlite3ValueNew(v->db);
if( pRet ){
- sqlite3VdbeMemCopy((Mem *)pRet, pMem);
- sqlite3ValueApplyAffinity(pRet, aff, SQLITE_UTF8);
+ tdsqlite3VdbeMemCopy((Mem *)pRet, pMem);
+ tdsqlite3ValueApplyAffinity(pRet, aff, SQLITE_UTF8);
}
return pRet;
}
@@ -78083,30 +85977,65 @@ SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff
/*
** Configure SQL variable iVar so that binding a new value to it signals
-** to sqlite3_reoptimize() that re-preparing the statement may result
+** to tdsqlite3_reoptimize() that re-preparing the statement may result
** in a better query plan.
*/
-SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
+SQLITE_PRIVATE void tdsqlite3VdbeSetVarmask(Vdbe *v, int iVar){
assert( iVar>0 );
- if( iVar>32 ){
- v->expmask = 0xffffffff;
+ assert( (v->db->flags & SQLITE_EnableQPSG)==0 );
+ if( iVar>=32 ){
+ v->expmask |= 0x80000000;
}else{
v->expmask |= ((u32)1 << (iVar-1));
}
}
+/*
+** Cause a function to throw an error if it was call from OP_PureFunc
+** rather than OP_Function.
+**
+** OP_PureFunc means that the function must be deterministic, and should
+** throw an error if it is given inputs that would make it non-deterministic.
+** This routine is invoked by date/time functions that use non-deterministic
+** features such as 'now'.
+*/
+SQLITE_PRIVATE int tdsqlite3NotPureFunc(tdsqlite3_context *pCtx){
+ const VdbeOp *pOp;
+#ifdef SQLITE_ENABLE_STAT4
+ if( pCtx->pVdbe==0 ) return 1;
+#endif
+ pOp = pCtx->pVdbe->aOp + pCtx->iOp;
+ if( pOp->opcode==OP_PureFunc ){
+ const char *zContext;
+ char *zMsg;
+ if( pOp->p5 & NC_IsCheck ){
+ zContext = "a CHECK constraint";
+ }else if( pOp->p5 & NC_GenCol ){
+ zContext = "a generated column";
+ }else{
+ zContext = "an index";
+ }
+ zMsg = tdsqlite3_mprintf("non-deterministic use of %s() in %s",
+ pCtx->pFunc->zName, zContext);
+ tdsqlite3_result_error(pCtx, zMsg, -1);
+ tdsqlite3_free(zMsg);
+ return 0;
+ }
+ return 1;
+}
+
#ifndef SQLITE_OMIT_VIRTUALTABLE
/*
-** Transfer error message text from an sqlite3_vtab.zErrMsg (text stored
-** in memory obtained from sqlite3_malloc) into a Vdbe.zErrMsg (text stored
-** in memory obtained from sqlite3DbMalloc).
+** Transfer error message text from an tdsqlite3_vtab.zErrMsg (text stored
+** in memory obtained from tdsqlite3_malloc) into a Vdbe.zErrMsg (text stored
+** in memory obtained from tdsqlite3DbMalloc).
*/
-SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){
+SQLITE_PRIVATE void tdsqlite3VtabImportErrmsg(Vdbe *p, tdsqlite3_vtab *pVtab){
if( pVtab->zErrMsg ){
- sqlite3 *db = p->db;
- sqlite3DbFree(db, p->zErrMsg);
- p->zErrMsg = sqlite3DbStrDup(db, pVtab->zErrMsg);
- sqlite3_free(pVtab->zErrMsg);
+ tdsqlite3 *db = p->db;
+ tdsqlite3DbFree(db, p->zErrMsg);
+ p->zErrMsg = tdsqlite3DbStrDup(db, pVtab->zErrMsg);
+ tdsqlite3_free(pVtab->zErrMsg);
pVtab->zErrMsg = 0;
}
}
@@ -78117,19 +86046,19 @@ SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){
/*
** If the second argument is not NULL, release any allocations associated
** with the memory cells in the p->aMem[] array. Also free the UnpackedRecord
-** structure itself, using sqlite3DbFree().
+** structure itself, using tdsqlite3DbFree().
**
** This function is used to free UnpackedRecord structures allocated by
** the vdbeUnpackRecord() function found in vdbeapi.c.
*/
-static void vdbeFreeUnpacked(sqlite3 *db, UnpackedRecord *p){
+static void vdbeFreeUnpacked(tdsqlite3 *db, int nField, UnpackedRecord *p){
if( p ){
int i;
- for(i=0; i<p->nField; i++){
+ for(i=0; i<nField; i++){
Mem *pMem = &p->aMem[i];
- if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem);
+ if( pMem->zMalloc ) tdsqlite3VdbeMemRelease(pMem);
}
- sqlite3DbFree(db, p);
+ tdsqlite3DbFreeNN(db, p);
}
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -78138,10 +86067,10 @@ static void vdbeFreeUnpacked(sqlite3 *db, UnpackedRecord *p){
/*
** Invoke the pre-update hook. If this is an UPDATE or DELETE pre-update call,
** then cursor passed as the second argument should point to the row about
-** to be update or deleted. If the application calls sqlite3_preupdate_old(),
+** to be update or deleted. If the application calls tdsqlite3_preupdate_old(),
** the required value will be read from the row the cursor points to.
*/
-SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
+SQLITE_PRIVATE void tdsqlite3VdbePreUpdateHook(
Vdbe *v, /* Vdbe pre-update hook is invoked by */
VdbeCursor *pCsr, /* Cursor to grab old.* values from */
int op, /* SQLITE_INSERT, UPDATE or DELETE */
@@ -78150,7 +86079,7 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
i64 iKey1, /* Initial key value */
int iReg /* Register for new.* record */
){
- sqlite3 *db = v->db;
+ tdsqlite3 *db = v->db;
i64 iKey2;
PreUpdate preupdate;
const char *zTbl = pTab->zName;
@@ -78158,10 +86087,15 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
assert( db->pPreUpdate==0 );
memset(&preupdate, 0, sizeof(PreUpdate));
- if( op==SQLITE_UPDATE ){
- iKey2 = v->aMem[iReg].u.i;
+ if( HasRowid(pTab)==0 ){
+ iKey1 = iKey2 = 0;
+ preupdate.pPk = tdsqlite3PrimaryKeyIndex(pTab);
}else{
- iKey2 = iKey1;
+ if( op==SQLITE_UPDATE ){
+ iKey2 = v->aMem[iReg].u.i;
+ }else{
+ iKey2 = iKey1;
+ }
}
assert( pCsr->nField==pTab->nCol
@@ -78174,8 +86108,8 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
preupdate.iNewReg = iReg;
preupdate.keyinfo.db = db;
preupdate.keyinfo.enc = ENC(db);
- preupdate.keyinfo.nField = pTab->nCol;
- preupdate.keyinfo.aSortOrder = (u8*)&fakeSortOrder;
+ preupdate.keyinfo.nKeyField = pTab->nCol;
+ preupdate.keyinfo.aSortFlags = (u8*)&fakeSortOrder;
preupdate.iKey1 = iKey1;
preupdate.iKey2 = iKey2;
preupdate.pTab = pTab;
@@ -78183,15 +86117,15 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
db->pPreUpdate = &preupdate;
db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2);
db->pPreUpdate = 0;
- sqlite3DbFree(db, preupdate.aRecord);
- vdbeFreeUnpacked(db, preupdate.pUnpacked);
- vdbeFreeUnpacked(db, preupdate.pNewUnpacked);
+ tdsqlite3DbFree(db, preupdate.aRecord);
+ vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pUnpacked);
+ vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pNewUnpacked);
if( preupdate.aNew ){
int i;
for(i=0; i<pCsr->nField; i++){
- sqlite3VdbeMemRelease(&preupdate.aNew[i]);
+ tdsqlite3VdbeMemRelease(&preupdate.aNew[i]);
}
- sqlite3DbFree(db, preupdate.aNew);
+ tdsqlite3DbFreeNN(db, preupdate.aNew);
}
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -78221,11 +86155,11 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(
** Return TRUE (non-zero) of the statement supplied as an argument needs
** to be recompiled. A statement needs to be recompiled whenever the
** execution environment changes in a way that would alter the program
-** that sqlite3_prepare() generates. For example, if new functions or
+** that tdsqlite3_prepare() generates. For example, if new functions or
** collating sequences are registered or if an authorizer function is
** added or changed.
*/
-SQLITE_API int sqlite3_expired(sqlite3_stmt *pStmt){
+SQLITE_API int tdsqlite3_expired(tdsqlite3_stmt *pStmt){
Vdbe *p = (Vdbe*)pStmt;
return p==0 || p->expired;
}
@@ -78238,7 +86172,7 @@ SQLITE_API int sqlite3_expired(sqlite3_stmt *pStmt){
*/
static int vdbeSafety(Vdbe *p){
if( p->db==0 ){
- sqlite3_log(SQLITE_MISUSE, "API called with finalized prepared statement");
+ tdsqlite3_log(SQLITE_MISUSE, "API called with finalized prepared statement");
return 1;
}else{
return 0;
@@ -78246,7 +86180,7 @@ static int vdbeSafety(Vdbe *p){
}
static int vdbeSafetyNotNull(Vdbe *p){
if( p==0 ){
- sqlite3_log(SQLITE_MISUSE, "API called with NULL prepared statement");
+ tdsqlite3_log(SQLITE_MISUSE, "API called with NULL prepared statement");
return 1;
}else{
return vdbeSafety(p);
@@ -78258,18 +86192,20 @@ static int vdbeSafetyNotNull(Vdbe *p){
** Invoke the profile callback. This routine is only called if we already
** know that the profile callback is defined and needs to be invoked.
*/
-static SQLITE_NOINLINE void invokeProfileCallback(sqlite3 *db, Vdbe *p){
- sqlite3_int64 iNow;
- sqlite3_int64 iElapse;
+static SQLITE_NOINLINE void invokeProfileCallback(tdsqlite3 *db, Vdbe *p){
+ tdsqlite3_int64 iNow;
+ tdsqlite3_int64 iElapse;
assert( p->startTime>0 );
- assert( db->xProfile!=0 || (db->mTrace & SQLITE_TRACE_PROFILE)!=0 );
+ assert( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 );
assert( db->init.busy==0 );
assert( p->zSql!=0 );
- sqlite3OsCurrentTimeInt64(db->pVfs, &iNow);
+ tdsqlite3OsCurrentTimeInt64(db->pVfs, &iNow);
iElapse = (iNow - p->startTime)*1000000;
+#ifndef SQLITE_OMIT_DEPRECATED
if( db->xProfile ){
db->xProfile(db->pProfileArg, p->zSql, iElapse);
}
+#endif
if( db->mTrace & SQLITE_TRACE_PROFILE ){
db->xTrace(SQLITE_TRACE_PROFILE, db->pTraceArg, p, (void*)&iElapse);
}
@@ -78287,28 +86223,28 @@ static SQLITE_NOINLINE void invokeProfileCallback(sqlite3 *db, Vdbe *p){
/*
** The following routine destroys a virtual machine that is created by
-** the sqlite3_compile() routine. The integer returned is an SQLITE_
+** the tdsqlite3_compile() routine. The integer returned is an SQLITE_
** success/failure code that describes the result of executing the virtual
** machine.
**
** This routine sets the error code and string returned by
-** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16().
+** tdsqlite3_errcode(), tdsqlite3_errmsg() and tdsqlite3_errmsg16().
*/
-SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt){
+SQLITE_API int tdsqlite3_finalize(tdsqlite3_stmt *pStmt){
int rc;
if( pStmt==0 ){
- /* IMPLEMENTATION-OF: R-57228-12904 Invoking sqlite3_finalize() on a NULL
+ /* IMPLEMENTATION-OF: R-57228-12904 Invoking tdsqlite3_finalize() on a NULL
** pointer is a harmless no-op. */
rc = SQLITE_OK;
}else{
Vdbe *v = (Vdbe*)pStmt;
- sqlite3 *db = v->db;
+ tdsqlite3 *db = v->db;
if( vdbeSafety(v) ) return SQLITE_MISUSE_BKPT;
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
checkProfileCallback(db, v);
- rc = sqlite3VdbeFinalize(v);
- rc = sqlite3ApiExit(db, rc);
- sqlite3LeaveMutexAndCloseZombie(db);
+ rc = tdsqlite3VdbeFinalize(v);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3LeaveMutexAndCloseZombie(db);
}
return rc;
}
@@ -78319,22 +86255,22 @@ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt){
** the prior execution is returned.
**
** This routine sets the error code and string returned by
-** sqlite3_errcode(), sqlite3_errmsg() and sqlite3_errmsg16().
+** tdsqlite3_errcode(), tdsqlite3_errmsg() and tdsqlite3_errmsg16().
*/
-SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt){
+SQLITE_API int tdsqlite3_reset(tdsqlite3_stmt *pStmt){
int rc;
if( pStmt==0 ){
rc = SQLITE_OK;
}else{
Vdbe *v = (Vdbe*)pStmt;
- sqlite3 *db = v->db;
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3 *db = v->db;
+ tdsqlite3_mutex_enter(db->mutex);
checkProfileCallback(db, v);
- rc = sqlite3VdbeReset(v);
- sqlite3VdbeRewind(v);
+ rc = tdsqlite3VdbeReset(v);
+ tdsqlite3VdbeRewind(v);
assert( (rc & (db->errMask))==rc );
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
}
return rc;
}
@@ -78342,31 +86278,32 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt){
/*
** Set all the parameters in the compiled SQL statement to NULL.
*/
-SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt *pStmt){
+SQLITE_API int tdsqlite3_clear_bindings(tdsqlite3_stmt *pStmt){
int i;
int rc = SQLITE_OK;
Vdbe *p = (Vdbe*)pStmt;
#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex = ((Vdbe*)pStmt)->db->mutex;
+ tdsqlite3_mutex *mutex = ((Vdbe*)pStmt)->db->mutex;
#endif
- sqlite3_mutex_enter(mutex);
+ tdsqlite3_mutex_enter(mutex);
for(i=0; i<p->nVar; i++){
- sqlite3VdbeMemRelease(&p->aVar[i]);
+ tdsqlite3VdbeMemRelease(&p->aVar[i]);
p->aVar[i].flags = MEM_Null;
}
- if( p->isPrepareV2 && p->expmask ){
+ assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 );
+ if( p->expmask ){
p->expired = 1;
}
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
return rc;
}
-/**************************** sqlite3_value_ *******************************
-** The following routines extract information from a Mem or sqlite3_value
+/**************************** tdsqlite3_value_ *******************************
+** The following routines extract information from a Mem or tdsqlite3_value
** structure.
*/
-SQLITE_API const void *sqlite3_value_blob(sqlite3_value *pVal){
+SQLITE_API const void *tdsqlite3_value_blob(tdsqlite3_value *pVal){
Mem *p = (Mem*)pVal;
if( p->flags & (MEM_Blob|MEM_Str) ){
if( ExpandBlob(p)!=SQLITE_OK ){
@@ -78376,90 +86313,160 @@ SQLITE_API const void *sqlite3_value_blob(sqlite3_value *pVal){
p->flags |= MEM_Blob;
return p->n ? p->z : 0;
}else{
- return sqlite3_value_text(pVal);
+ return tdsqlite3_value_text(pVal);
}
}
-SQLITE_API int sqlite3_value_bytes(sqlite3_value *pVal){
- return sqlite3ValueBytes(pVal, SQLITE_UTF8);
+SQLITE_API int tdsqlite3_value_bytes(tdsqlite3_value *pVal){
+ return tdsqlite3ValueBytes(pVal, SQLITE_UTF8);
}
-SQLITE_API int sqlite3_value_bytes16(sqlite3_value *pVal){
- return sqlite3ValueBytes(pVal, SQLITE_UTF16NATIVE);
+SQLITE_API int tdsqlite3_value_bytes16(tdsqlite3_value *pVal){
+ return tdsqlite3ValueBytes(pVal, SQLITE_UTF16NATIVE);
}
-SQLITE_API double sqlite3_value_double(sqlite3_value *pVal){
- return sqlite3VdbeRealValue((Mem*)pVal);
+SQLITE_API double tdsqlite3_value_double(tdsqlite3_value *pVal){
+ return tdsqlite3VdbeRealValue((Mem*)pVal);
}
-SQLITE_API int sqlite3_value_int(sqlite3_value *pVal){
- return (int)sqlite3VdbeIntValue((Mem*)pVal);
+SQLITE_API int tdsqlite3_value_int(tdsqlite3_value *pVal){
+ return (int)tdsqlite3VdbeIntValue((Mem*)pVal);
}
-SQLITE_API sqlite_int64 sqlite3_value_int64(sqlite3_value *pVal){
- return sqlite3VdbeIntValue((Mem*)pVal);
+SQLITE_API sqlite_int64 tdsqlite3_value_int64(tdsqlite3_value *pVal){
+ return tdsqlite3VdbeIntValue((Mem*)pVal);
}
-SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value *pVal){
+SQLITE_API unsigned int tdsqlite3_value_subtype(tdsqlite3_value *pVal){
Mem *pMem = (Mem*)pVal;
return ((pMem->flags & MEM_Subtype) ? pMem->eSubtype : 0);
}
-SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value *pVal){
- return (const unsigned char *)sqlite3ValueText(pVal, SQLITE_UTF8);
+SQLITE_API void *tdsqlite3_value_pointer(tdsqlite3_value *pVal, const char *zPType){
+ Mem *p = (Mem*)pVal;
+ if( (p->flags&(MEM_TypeMask|MEM_Term|MEM_Subtype)) ==
+ (MEM_Null|MEM_Term|MEM_Subtype)
+ && zPType!=0
+ && p->eSubtype=='p'
+ && strcmp(p->u.zPType, zPType)==0
+ ){
+ return (void*)p->z;
+ }else{
+ return 0;
+ }
+}
+SQLITE_API const unsigned char *tdsqlite3_value_text(tdsqlite3_value *pVal){
+ return (const unsigned char *)tdsqlite3ValueText(pVal, SQLITE_UTF8);
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API const void *sqlite3_value_text16(sqlite3_value* pVal){
- return sqlite3ValueText(pVal, SQLITE_UTF16NATIVE);
+SQLITE_API const void *tdsqlite3_value_text16(tdsqlite3_value* pVal){
+ return tdsqlite3ValueText(pVal, SQLITE_UTF16NATIVE);
}
-SQLITE_API const void *sqlite3_value_text16be(sqlite3_value *pVal){
- return sqlite3ValueText(pVal, SQLITE_UTF16BE);
+SQLITE_API const void *tdsqlite3_value_text16be(tdsqlite3_value *pVal){
+ return tdsqlite3ValueText(pVal, SQLITE_UTF16BE);
}
-SQLITE_API const void *sqlite3_value_text16le(sqlite3_value *pVal){
- return sqlite3ValueText(pVal, SQLITE_UTF16LE);
+SQLITE_API const void *tdsqlite3_value_text16le(tdsqlite3_value *pVal){
+ return tdsqlite3ValueText(pVal, SQLITE_UTF16LE);
}
#endif /* SQLITE_OMIT_UTF16 */
/* EVIDENCE-OF: R-12793-43283 Every value in SQLite has one of five
** fundamental datatypes: 64-bit signed integer 64-bit IEEE floating
** point number string BLOB NULL
*/
-SQLITE_API int sqlite3_value_type(sqlite3_value* pVal){
+SQLITE_API int tdsqlite3_value_type(tdsqlite3_value* pVal){
static const u8 aType[] = {
- SQLITE_BLOB, /* 0x00 */
- SQLITE_NULL, /* 0x01 */
- SQLITE_TEXT, /* 0x02 */
- SQLITE_NULL, /* 0x03 */
- SQLITE_INTEGER, /* 0x04 */
- SQLITE_NULL, /* 0x05 */
- SQLITE_INTEGER, /* 0x06 */
- SQLITE_NULL, /* 0x07 */
- SQLITE_FLOAT, /* 0x08 */
- SQLITE_NULL, /* 0x09 */
- SQLITE_FLOAT, /* 0x0a */
- SQLITE_NULL, /* 0x0b */
- SQLITE_INTEGER, /* 0x0c */
- SQLITE_NULL, /* 0x0d */
- SQLITE_INTEGER, /* 0x0e */
- SQLITE_NULL, /* 0x0f */
- SQLITE_BLOB, /* 0x10 */
- SQLITE_NULL, /* 0x11 */
- SQLITE_TEXT, /* 0x12 */
- SQLITE_NULL, /* 0x13 */
- SQLITE_INTEGER, /* 0x14 */
- SQLITE_NULL, /* 0x15 */
- SQLITE_INTEGER, /* 0x16 */
- SQLITE_NULL, /* 0x17 */
- SQLITE_FLOAT, /* 0x18 */
- SQLITE_NULL, /* 0x19 */
- SQLITE_FLOAT, /* 0x1a */
- SQLITE_NULL, /* 0x1b */
- SQLITE_INTEGER, /* 0x1c */
- SQLITE_NULL, /* 0x1d */
- SQLITE_INTEGER, /* 0x1e */
- SQLITE_NULL, /* 0x1f */
+ SQLITE_BLOB, /* 0x00 (not possible) */
+ SQLITE_NULL, /* 0x01 NULL */
+ SQLITE_TEXT, /* 0x02 TEXT */
+ SQLITE_NULL, /* 0x03 (not possible) */
+ SQLITE_INTEGER, /* 0x04 INTEGER */
+ SQLITE_NULL, /* 0x05 (not possible) */
+ SQLITE_INTEGER, /* 0x06 INTEGER + TEXT */
+ SQLITE_NULL, /* 0x07 (not possible) */
+ SQLITE_FLOAT, /* 0x08 FLOAT */
+ SQLITE_NULL, /* 0x09 (not possible) */
+ SQLITE_FLOAT, /* 0x0a FLOAT + TEXT */
+ SQLITE_NULL, /* 0x0b (not possible) */
+ SQLITE_INTEGER, /* 0x0c (not possible) */
+ SQLITE_NULL, /* 0x0d (not possible) */
+ SQLITE_INTEGER, /* 0x0e (not possible) */
+ SQLITE_NULL, /* 0x0f (not possible) */
+ SQLITE_BLOB, /* 0x10 BLOB */
+ SQLITE_NULL, /* 0x11 (not possible) */
+ SQLITE_TEXT, /* 0x12 (not possible) */
+ SQLITE_NULL, /* 0x13 (not possible) */
+ SQLITE_INTEGER, /* 0x14 INTEGER + BLOB */
+ SQLITE_NULL, /* 0x15 (not possible) */
+ SQLITE_INTEGER, /* 0x16 (not possible) */
+ SQLITE_NULL, /* 0x17 (not possible) */
+ SQLITE_FLOAT, /* 0x18 FLOAT + BLOB */
+ SQLITE_NULL, /* 0x19 (not possible) */
+ SQLITE_FLOAT, /* 0x1a (not possible) */
+ SQLITE_NULL, /* 0x1b (not possible) */
+ SQLITE_INTEGER, /* 0x1c (not possible) */
+ SQLITE_NULL, /* 0x1d (not possible) */
+ SQLITE_INTEGER, /* 0x1e (not possible) */
+ SQLITE_NULL, /* 0x1f (not possible) */
+ SQLITE_FLOAT, /* 0x20 INTREAL */
+ SQLITE_NULL, /* 0x21 (not possible) */
+ SQLITE_TEXT, /* 0x22 INTREAL + TEXT */
+ SQLITE_NULL, /* 0x23 (not possible) */
+ SQLITE_FLOAT, /* 0x24 (not possible) */
+ SQLITE_NULL, /* 0x25 (not possible) */
+ SQLITE_FLOAT, /* 0x26 (not possible) */
+ SQLITE_NULL, /* 0x27 (not possible) */
+ SQLITE_FLOAT, /* 0x28 (not possible) */
+ SQLITE_NULL, /* 0x29 (not possible) */
+ SQLITE_FLOAT, /* 0x2a (not possible) */
+ SQLITE_NULL, /* 0x2b (not possible) */
+ SQLITE_FLOAT, /* 0x2c (not possible) */
+ SQLITE_NULL, /* 0x2d (not possible) */
+ SQLITE_FLOAT, /* 0x2e (not possible) */
+ SQLITE_NULL, /* 0x2f (not possible) */
+ SQLITE_BLOB, /* 0x30 (not possible) */
+ SQLITE_NULL, /* 0x31 (not possible) */
+ SQLITE_TEXT, /* 0x32 (not possible) */
+ SQLITE_NULL, /* 0x33 (not possible) */
+ SQLITE_FLOAT, /* 0x34 (not possible) */
+ SQLITE_NULL, /* 0x35 (not possible) */
+ SQLITE_FLOAT, /* 0x36 (not possible) */
+ SQLITE_NULL, /* 0x37 (not possible) */
+ SQLITE_FLOAT, /* 0x38 (not possible) */
+ SQLITE_NULL, /* 0x39 (not possible) */
+ SQLITE_FLOAT, /* 0x3a (not possible) */
+ SQLITE_NULL, /* 0x3b (not possible) */
+ SQLITE_FLOAT, /* 0x3c (not possible) */
+ SQLITE_NULL, /* 0x3d (not possible) */
+ SQLITE_FLOAT, /* 0x3e (not possible) */
+ SQLITE_NULL, /* 0x3f (not possible) */
};
+#ifdef SQLITE_DEBUG
+ {
+ int eType = SQLITE_BLOB;
+ if( pVal->flags & MEM_Null ){
+ eType = SQLITE_NULL;
+ }else if( pVal->flags & (MEM_Real|MEM_IntReal) ){
+ eType = SQLITE_FLOAT;
+ }else if( pVal->flags & MEM_Int ){
+ eType = SQLITE_INTEGER;
+ }else if( pVal->flags & MEM_Str ){
+ eType = SQLITE_TEXT;
+ }
+ assert( eType == aType[pVal->flags&MEM_AffMask] );
+ }
+#endif
return aType[pVal->flags&MEM_AffMask];
}
-/* Make a copy of an sqlite3_value object
+/* Return true if a parameter to xUpdate represents an unchanged column */
+SQLITE_API int tdsqlite3_value_nochange(tdsqlite3_value *pVal){
+ return (pVal->flags&(MEM_Null|MEM_Zero))==(MEM_Null|MEM_Zero);
+}
+
+/* Return true if a parameter value originated from an tdsqlite3_bind() */
+SQLITE_API int tdsqlite3_value_frombind(tdsqlite3_value *pVal){
+ return (pVal->flags&MEM_FromBind)!=0;
+}
+
+/* Make a copy of an tdsqlite3_value object
*/
-SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){
- sqlite3_value *pNew;
+SQLITE_API tdsqlite3_value *tdsqlite3_value_dup(const tdsqlite3_value *pOrig){
+ tdsqlite3_value *pNew;
if( pOrig==0 ) return 0;
- pNew = sqlite3_malloc( sizeof(*pNew) );
+ pNew = tdsqlite3_malloc( sizeof(*pNew) );
if( pNew==0 ) return 0;
memset(pNew, 0, sizeof(*pNew));
memcpy(pNew, pOrig, MEMCELLSIZE);
@@ -78468,27 +86475,27 @@ SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){
if( pNew->flags&(MEM_Str|MEM_Blob) ){
pNew->flags &= ~(MEM_Static|MEM_Dyn);
pNew->flags |= MEM_Ephem;
- if( sqlite3VdbeMemMakeWriteable(pNew)!=SQLITE_OK ){
- sqlite3ValueFree(pNew);
+ if( tdsqlite3VdbeMemMakeWriteable(pNew)!=SQLITE_OK ){
+ tdsqlite3ValueFree(pNew);
pNew = 0;
}
}
return pNew;
}
-/* Destroy an sqlite3_value object previously obtained from
-** sqlite3_value_dup().
+/* Destroy an tdsqlite3_value object previously obtained from
+** tdsqlite3_value_dup().
*/
-SQLITE_API void sqlite3_value_free(sqlite3_value *pOld){
- sqlite3ValueFree(pOld);
+SQLITE_API void tdsqlite3_value_free(tdsqlite3_value *pOld){
+ tdsqlite3ValueFree(pOld);
}
-/**************************** sqlite3_result_ *******************************
+/**************************** tdsqlite3_result_ *******************************
** The following routines are used by user-defined functions to specify
** the function result.
**
-** The setStrOrError() function calls sqlite3VdbeMemSetStr() to store the
+** The setStrOrError() function calls tdsqlite3VdbeMemSetStr() to store the
** result as a string or blob but if the string or blob is too large, it
** then sets the error code to SQLITE_TOOBIG
**
@@ -78496,20 +86503,20 @@ SQLITE_API void sqlite3_value_free(sqlite3_value *pOld){
** on value P is not going to be used and need to be destroyed.
*/
static void setResultStrOrError(
- sqlite3_context *pCtx, /* Function context */
+ tdsqlite3_context *pCtx, /* Function context */
const char *z, /* String pointer */
int n, /* Bytes in string, or negative */
u8 enc, /* Encoding of z. 0 for BLOBs */
void (*xDel)(void*) /* Destructor function */
){
- if( sqlite3VdbeMemSetStr(pCtx->pOut, z, n, enc, xDel)==SQLITE_TOOBIG ){
- sqlite3_result_error_toobig(pCtx);
+ if( tdsqlite3VdbeMemSetStr(pCtx->pOut, z, n, enc, xDel)==SQLITE_TOOBIG ){
+ tdsqlite3_result_error_toobig(pCtx);
}
}
static int invokeValueDestructor(
const void *p, /* Value to destroy */
void (*xDel)(void*), /* The destructor */
- sqlite3_context *pCtx /* Set a SQLITE_TOOBIG error if no NULL */
+ tdsqlite3_context *pCtx /* Set a SQLITE_TOOBIG error if no NULL */
){
assert( xDel!=SQLITE_DYNAMIC );
if( xDel==0 ){
@@ -78519,26 +86526,26 @@ static int invokeValueDestructor(
}else{
xDel((void*)p);
}
- if( pCtx ) sqlite3_result_error_toobig(pCtx);
+ if( pCtx ) tdsqlite3_result_error_toobig(pCtx);
return SQLITE_TOOBIG;
}
-SQLITE_API void sqlite3_result_blob(
- sqlite3_context *pCtx,
+SQLITE_API void tdsqlite3_result_blob(
+ tdsqlite3_context *pCtx,
const void *z,
int n,
void (*xDel)(void *)
){
assert( n>=0 );
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
setResultStrOrError(pCtx, z, n, 0, xDel);
}
-SQLITE_API void sqlite3_result_blob64(
- sqlite3_context *pCtx,
+SQLITE_API void tdsqlite3_result_blob64(
+ tdsqlite3_context *pCtx,
const void *z,
- sqlite3_uint64 n,
+ tdsqlite3_uint64 n,
void (*xDel)(void *)
){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
assert( xDel!=SQLITE_DYNAMIC );
if( n>0x7fffffff ){
(void)invokeValueDestructor(z, xDel, pCtx);
@@ -78546,59 +86553,69 @@ SQLITE_API void sqlite3_result_blob64(
setResultStrOrError(pCtx, z, (int)n, 0, xDel);
}
}
-SQLITE_API void sqlite3_result_double(sqlite3_context *pCtx, double rVal){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- sqlite3VdbeMemSetDouble(pCtx->pOut, rVal);
+SQLITE_API void tdsqlite3_result_double(tdsqlite3_context *pCtx, double rVal){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ tdsqlite3VdbeMemSetDouble(pCtx->pOut, rVal);
}
-SQLITE_API void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+SQLITE_API void tdsqlite3_result_error(tdsqlite3_context *pCtx, const char *z, int n){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
pCtx->isError = SQLITE_ERROR;
- pCtx->fErrorOrAux = 1;
- sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT);
+ tdsqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT);
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+SQLITE_API void tdsqlite3_result_error16(tdsqlite3_context *pCtx, const void *z, int n){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
pCtx->isError = SQLITE_ERROR;
- pCtx->fErrorOrAux = 1;
- sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT);
+ tdsqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT);
}
#endif
-SQLITE_API void sqlite3_result_int(sqlite3_context *pCtx, int iVal){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- sqlite3VdbeMemSetInt64(pCtx->pOut, (i64)iVal);
+SQLITE_API void tdsqlite3_result_int(tdsqlite3_context *pCtx, int iVal){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ tdsqlite3VdbeMemSetInt64(pCtx->pOut, (i64)iVal);
}
-SQLITE_API void sqlite3_result_int64(sqlite3_context *pCtx, i64 iVal){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- sqlite3VdbeMemSetInt64(pCtx->pOut, iVal);
+SQLITE_API void tdsqlite3_result_int64(tdsqlite3_context *pCtx, i64 iVal){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ tdsqlite3VdbeMemSetInt64(pCtx->pOut, iVal);
}
-SQLITE_API void sqlite3_result_null(sqlite3_context *pCtx){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- sqlite3VdbeMemSetNull(pCtx->pOut);
+SQLITE_API void tdsqlite3_result_null(tdsqlite3_context *pCtx){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ tdsqlite3VdbeMemSetNull(pCtx->pOut);
+}
+SQLITE_API void tdsqlite3_result_pointer(
+ tdsqlite3_context *pCtx,
+ void *pPtr,
+ const char *zPType,
+ void (*xDestructor)(void*)
+){
+ Mem *pOut = pCtx->pOut;
+ assert( tdsqlite3_mutex_held(pOut->db->mutex) );
+ tdsqlite3VdbeMemRelease(pOut);
+ pOut->flags = MEM_Null;
+ tdsqlite3VdbeMemSetPointer(pOut, pPtr, zPType, xDestructor);
}
-SQLITE_API void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){
+SQLITE_API void tdsqlite3_result_subtype(tdsqlite3_context *pCtx, unsigned int eSubtype){
Mem *pOut = pCtx->pOut;
- assert( sqlite3_mutex_held(pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(pOut->db->mutex) );
pOut->eSubtype = eSubtype & 0xff;
pOut->flags |= MEM_Subtype;
}
-SQLITE_API void sqlite3_result_text(
- sqlite3_context *pCtx,
+SQLITE_API void tdsqlite3_result_text(
+ tdsqlite3_context *pCtx,
const char *z,
int n,
void (*xDel)(void *)
){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
setResultStrOrError(pCtx, z, n, SQLITE_UTF8, xDel);
}
-SQLITE_API void sqlite3_result_text64(
- sqlite3_context *pCtx,
+SQLITE_API void tdsqlite3_result_text64(
+ tdsqlite3_context *pCtx,
const char *z,
- sqlite3_uint64 n,
+ tdsqlite3_uint64 n,
void (*xDel)(void *),
unsigned char enc
){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
assert( xDel!=SQLITE_DYNAMIC );
if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE;
if( n>0x7fffffff ){
@@ -78608,86 +86625,98 @@ SQLITE_API void sqlite3_result_text64(
}
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API void sqlite3_result_text16(
- sqlite3_context *pCtx,
+SQLITE_API void tdsqlite3_result_text16(
+ tdsqlite3_context *pCtx,
const void *z,
int n,
void (*xDel)(void *)
){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
setResultStrOrError(pCtx, z, n, SQLITE_UTF16NATIVE, xDel);
}
-SQLITE_API void sqlite3_result_text16be(
- sqlite3_context *pCtx,
+SQLITE_API void tdsqlite3_result_text16be(
+ tdsqlite3_context *pCtx,
const void *z,
int n,
void (*xDel)(void *)
){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
setResultStrOrError(pCtx, z, n, SQLITE_UTF16BE, xDel);
}
-SQLITE_API void sqlite3_result_text16le(
- sqlite3_context *pCtx,
+SQLITE_API void tdsqlite3_result_text16le(
+ tdsqlite3_context *pCtx,
const void *z,
int n,
void (*xDel)(void *)
){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
setResultStrOrError(pCtx, z, n, SQLITE_UTF16LE, xDel);
}
#endif /* SQLITE_OMIT_UTF16 */
-SQLITE_API void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- sqlite3VdbeMemCopy(pCtx->pOut, pValue);
+SQLITE_API void tdsqlite3_result_value(tdsqlite3_context *pCtx, tdsqlite3_value *pValue){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ tdsqlite3VdbeMemCopy(pCtx->pOut, pValue);
}
-SQLITE_API void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- sqlite3VdbeMemSetZeroBlob(pCtx->pOut, n);
+SQLITE_API void tdsqlite3_result_zeroblob(tdsqlite3_context *pCtx, int n){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ tdsqlite3VdbeMemSetZeroBlob(pCtx->pOut, n);
}
-SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){
+SQLITE_API int tdsqlite3_result_zeroblob64(tdsqlite3_context *pCtx, u64 n){
Mem *pOut = pCtx->pOut;
- assert( sqlite3_mutex_held(pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(pOut->db->mutex) );
if( n>(u64)pOut->db->aLimit[SQLITE_LIMIT_LENGTH] ){
return SQLITE_TOOBIG;
}
- sqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n);
+ tdsqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n);
return SQLITE_OK;
}
-SQLITE_API void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){
- pCtx->isError = errCode;
- pCtx->fErrorOrAux = 1;
+SQLITE_API void tdsqlite3_result_error_code(tdsqlite3_context *pCtx, int errCode){
+ pCtx->isError = errCode ? errCode : -1;
#ifdef SQLITE_DEBUG
if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode;
#endif
if( pCtx->pOut->flags & MEM_Null ){
- sqlite3VdbeMemSetStr(pCtx->pOut, sqlite3ErrStr(errCode), -1,
+ tdsqlite3VdbeMemSetStr(pCtx->pOut, tdsqlite3ErrStr(errCode), -1,
SQLITE_UTF8, SQLITE_STATIC);
}
}
/* Force an SQLITE_TOOBIG error. */
-SQLITE_API void sqlite3_result_error_toobig(sqlite3_context *pCtx){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
+SQLITE_API void tdsqlite3_result_error_toobig(tdsqlite3_context *pCtx){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
pCtx->isError = SQLITE_TOOBIG;
- pCtx->fErrorOrAux = 1;
- sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1,
+ tdsqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1,
SQLITE_UTF8, SQLITE_STATIC);
}
/* An SQLITE_NOMEM error. */
-SQLITE_API void sqlite3_result_error_nomem(sqlite3_context *pCtx){
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- sqlite3VdbeMemSetNull(pCtx->pOut);
+SQLITE_API void tdsqlite3_result_error_nomem(tdsqlite3_context *pCtx){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ tdsqlite3VdbeMemSetNull(pCtx->pOut);
pCtx->isError = SQLITE_NOMEM_BKPT;
- pCtx->fErrorOrAux = 1;
- sqlite3OomFault(pCtx->pOut->db);
+ tdsqlite3OomFault(pCtx->pOut->db);
}
+#ifndef SQLITE_UNTESTABLE
+/* Force the INT64 value currently stored as the result to be
+** a MEM_IntReal value. See the SQLITE_TESTCTRL_RESULT_INTREAL
+** test-control.
+*/
+SQLITE_PRIVATE void tdsqlite3ResultIntReal(tdsqlite3_context *pCtx){
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+ if( pCtx->pOut->flags & MEM_Int ){
+ pCtx->pOut->flags &= ~MEM_Int;
+ pCtx->pOut->flags |= MEM_IntReal;
+ }
+}
+#endif
+
+
/*
** This function is called after a transaction has been committed. It
-** invokes callbacks registered with sqlite3_wal_hook() as required.
+** invokes callbacks registered with tdsqlite3_wal_hook() as required.
*/
-static int doWalCallbacks(sqlite3 *db){
+static int doWalCallbacks(tdsqlite3 *db){
int rc = SQLITE_OK;
#ifndef SQLITE_OMIT_WAL
int i;
@@ -78695,10 +86724,10 @@ static int doWalCallbacks(sqlite3 *db){
Btree *pBt = db->aDb[i].pBt;
if( pBt ){
int nEntry;
- sqlite3BtreeEnter(pBt);
- nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt));
- sqlite3BtreeLeave(pBt);
- if( db->xWalCallback && nEntry>0 && rc==SQLITE_OK ){
+ tdsqlite3BtreeEnter(pBt);
+ nEntry = tdsqlite3PagerWalCallback(tdsqlite3BtreePager(pBt));
+ tdsqlite3BtreeLeave(pBt);
+ if( nEntry>0 && db->xWalCallback && rc==SQLITE_OK ){
rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zDbSName, nEntry);
}
}
@@ -78715,17 +86744,17 @@ static int doWalCallbacks(sqlite3 *db){
** This routine implements the bulk of the logic behind the sqlite_step()
** API. The only thing omitted is the automatic recompile if a
** schema change has occurred. That detail is handled by the
-** outer sqlite3_step() wrapper procedure.
+** outer tdsqlite3_step() wrapper procedure.
*/
-static int sqlite3Step(Vdbe *p){
- sqlite3 *db;
+static int tdsqlite3Step(Vdbe *p){
+ tdsqlite3 *db;
int rc;
assert(p);
if( p->magic!=VDBE_MAGIC_RUN ){
- /* We used to require that sqlite3_reset() be called before retrying
- ** sqlite3_step() after any error or after SQLITE_DONE. But beginning
- ** with version 3.7.0, we changed this so that sqlite3_reset() would
+ /* We used to require that tdsqlite3_reset() be called before retrying
+ ** tdsqlite3_step() after any error or after SQLITE_DONE. But beginning
+ ** with version 3.7.0, we changed this so that tdsqlite3_reset() would
** be called automatically instead of throwing the SQLITE_MISUSE error.
** This "automatic-reset" change is not technically an incompatibility,
** since any application that receives an SQLITE_MISUSE is broken by
@@ -78736,17 +86765,17 @@ static int sqlite3Step(Vdbe *p){
** returns, and those were broken by the automatic-reset change. As a
** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the
** legacy behavior of returning SQLITE_MISUSE for cases where the
- ** previous sqlite3_step() returned something other than a SQLITE_LOCKED
+ ** previous tdsqlite3_step() returned something other than a SQLITE_LOCKED
** or SQLITE_BUSY error.
*/
#ifdef SQLITE_OMIT_AUTORESET
if( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){
- sqlite3_reset((sqlite3_stmt*)p);
+ tdsqlite3_reset((tdsqlite3_stmt*)p);
}else{
return SQLITE_MISUSE_BKPT;
}
#else
- sqlite3_reset((sqlite3_stmt*)p);
+ tdsqlite3_reset((tdsqlite3_stmt*)p);
#endif
}
@@ -78757,14 +86786,14 @@ static int sqlite3Step(Vdbe *p){
return SQLITE_NOMEM_BKPT;
}
- if( p->pc<=0 && p->expired ){
+ if( p->pc<0 && p->expired ){
p->rc = SQLITE_SCHEMA;
rc = SQLITE_ERROR;
goto end_of_step;
}
if( p->pc<0 ){
/* If there are no other statements currently running, then
- ** reset the interrupt flag. This prevents a call to sqlite3_interrupt
+ ** reset the interrupt flag. This prevents a call to tdsqlite3_interrupt
** from interrupting a statement that has not yet started.
*/
if( db->nVdbeActive==0 ){
@@ -78776,9 +86805,9 @@ static int sqlite3Step(Vdbe *p){
);
#ifndef SQLITE_OMIT_TRACE
- if( (db->xProfile || (db->mTrace & SQLITE_TRACE_PROFILE)!=0)
+ if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0
&& !db->init.busy && p->zSql ){
- sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime);
+ tdsqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime);
}else{
assert( p->startTime==0 );
}
@@ -78794,187 +86823,187 @@ static int sqlite3Step(Vdbe *p){
#endif
#ifndef SQLITE_OMIT_EXPLAIN
if( p->explain ){
- rc = sqlite3VdbeList(p);
+ rc = tdsqlite3VdbeList(p);
}else
#endif /* SQLITE_OMIT_EXPLAIN */
{
db->nVdbeExec++;
- rc = sqlite3VdbeExec(p);
+ rc = tdsqlite3VdbeExec(p);
db->nVdbeExec--;
}
+ if( rc!=SQLITE_ROW ){
#ifndef SQLITE_OMIT_TRACE
- /* If the statement completed successfully, invoke the profile callback */
- if( rc!=SQLITE_ROW ) checkProfileCallback(db, p);
+ /* If the statement completed successfully, invoke the profile callback */
+ checkProfileCallback(db, p);
#endif
- if( rc==SQLITE_DONE ){
- assert( p->rc==SQLITE_OK );
- p->rc = doWalCallbacks(db);
- if( p->rc!=SQLITE_OK ){
- rc = SQLITE_ERROR;
+ if( rc==SQLITE_DONE && db->autoCommit ){
+ assert( p->rc==SQLITE_OK );
+ p->rc = doWalCallbacks(db);
+ if( p->rc!=SQLITE_OK ){
+ rc = SQLITE_ERROR;
+ }
}
}
db->errCode = rc;
- if( SQLITE_NOMEM==sqlite3ApiExit(p->db, p->rc) ){
+ if( SQLITE_NOMEM==tdsqlite3ApiExit(p->db, p->rc) ){
p->rc = SQLITE_NOMEM_BKPT;
}
end_of_step:
/* At this point local variable rc holds the value that should be
** returned if this statement was compiled using the legacy
- ** sqlite3_prepare() interface. According to the docs, this can only
+ ** tdsqlite3_prepare() interface. According to the docs, this can only
** be one of the values in the first assert() below. Variable p->rc
- ** contains the value that would be returned if sqlite3_finalize()
+ ** contains the value that would be returned if tdsqlite3_finalize()
** were called on statement p.
*/
assert( rc==SQLITE_ROW || rc==SQLITE_DONE || rc==SQLITE_ERROR
|| (rc&0xff)==SQLITE_BUSY || rc==SQLITE_MISUSE
);
assert( (p->rc!=SQLITE_ROW && p->rc!=SQLITE_DONE) || p->rc==p->rcApp );
- if( p->isPrepareV2 && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){
- /* If this statement was prepared using sqlite3_prepare_v2(), and an
+ if( rc!=SQLITE_ROW
+ && rc!=SQLITE_DONE
+ && (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0
+ ){
+ /* If this statement was prepared using saved SQL and an
** error has occurred, then return the error code in p->rc to the
** caller. Set the error code in the database handle to the same value.
*/
- rc = sqlite3VdbeTransferError(p);
+ rc = tdsqlite3VdbeTransferError(p);
}
return (rc&db->errMask);
}
/*
-** This is the top-level implementation of sqlite3_step(). Call
-** sqlite3Step() to do most of the work. If a schema error occurs,
-** call sqlite3Reprepare() and try again.
+** This is the top-level implementation of tdsqlite3_step(). Call
+** tdsqlite3Step() to do most of the work. If a schema error occurs,
+** call tdsqlite3Reprepare() and try again.
*/
-SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){
- int rc = SQLITE_OK; /* Result from sqlite3Step() */
- int rc2 = SQLITE_OK; /* Result from sqlite3Reprepare() */
+SQLITE_API int tdsqlite3_step(tdsqlite3_stmt *pStmt){
+ int rc = SQLITE_OK; /* Result from tdsqlite3Step() */
Vdbe *v = (Vdbe*)pStmt; /* the prepared statement */
int cnt = 0; /* Counter to prevent infinite loop of reprepares */
- sqlite3 *db; /* The database connection */
+ tdsqlite3 *db; /* The database connection */
if( vdbeSafetyNotNull(v) ){
return SQLITE_MISUSE_BKPT;
}
db = v->db;
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
v->doingRerun = 0;
- while( (rc = sqlite3Step(v))==SQLITE_SCHEMA
+ while( (rc = tdsqlite3Step(v))==SQLITE_SCHEMA
&& cnt++ < SQLITE_MAX_SCHEMA_RETRY ){
int savedPc = v->pc;
- rc2 = rc = sqlite3Reprepare(v);
- if( rc!=SQLITE_OK) break;
- sqlite3_reset(pStmt);
+ rc = tdsqlite3Reprepare(v);
+ if( rc!=SQLITE_OK ){
+ /* This case occurs after failing to recompile an sql statement.
+ ** The error message from the SQL compiler has already been loaded
+ ** into the database handle. This block copies the error message
+ ** from the database handle into the statement and sets the statement
+ ** program counter to 0 to ensure that when the statement is
+ ** finalized or reset the parser error message is available via
+ ** tdsqlite3_errmsg() and tdsqlite3_errcode().
+ */
+ const char *zErr = (const char *)tdsqlite3_value_text(db->pErr);
+ tdsqlite3DbFree(db, v->zErrMsg);
+ if( !db->mallocFailed ){
+ v->zErrMsg = tdsqlite3DbStrDup(db, zErr);
+ v->rc = rc = tdsqlite3ApiExit(db, rc);
+ } else {
+ v->zErrMsg = 0;
+ v->rc = rc = SQLITE_NOMEM_BKPT;
+ }
+ break;
+ }
+ tdsqlite3_reset(pStmt);
if( savedPc>=0 ) v->doingRerun = 1;
assert( v->expired==0 );
}
- if( rc2!=SQLITE_OK ){
- /* This case occurs after failing to recompile an sql statement.
- ** The error message from the SQL compiler has already been loaded
- ** into the database handle. This block copies the error message
- ** from the database handle into the statement and sets the statement
- ** program counter to 0 to ensure that when the statement is
- ** finalized or reset the parser error message is available via
- ** sqlite3_errmsg() and sqlite3_errcode().
- */
- const char *zErr = (const char *)sqlite3_value_text(db->pErr);
- sqlite3DbFree(db, v->zErrMsg);
- if( !db->mallocFailed ){
- v->zErrMsg = sqlite3DbStrDup(db, zErr);
- v->rc = rc2;
- } else {
- v->zErrMsg = 0;
- v->rc = rc = SQLITE_NOMEM_BKPT;
- }
- }
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
/*
-** Extract the user data from a sqlite3_context structure and return a
+** Extract the user data from a tdsqlite3_context structure and return a
** pointer to it.
*/
-SQLITE_API void *sqlite3_user_data(sqlite3_context *p){
+SQLITE_API void *tdsqlite3_user_data(tdsqlite3_context *p){
assert( p && p->pFunc );
return p->pFunc->pUserData;
}
/*
-** Extract the user data from a sqlite3_context structure and return a
+** Extract the user data from a tdsqlite3_context structure and return a
** pointer to it.
**
-** IMPLEMENTATION-OF: R-46798-50301 The sqlite3_context_db_handle() interface
+** IMPLEMENTATION-OF: R-46798-50301 The tdsqlite3_context_db_handle() interface
** returns a copy of the pointer to the database connection (the 1st
-** parameter) of the sqlite3_create_function() and
-** sqlite3_create_function16() routines that originally registered the
+** parameter) of the tdsqlite3_create_function() and
+** tdsqlite3_create_function16() routines that originally registered the
** application defined function.
*/
-SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){
+SQLITE_API tdsqlite3 *tdsqlite3_context_db_handle(tdsqlite3_context *p){
assert( p && p->pOut );
return p->pOut->db;
}
/*
+** If this routine is invoked from within an xColumn method of a virtual
+** table, then it returns true if and only if the the call is during an
+** UPDATE operation and the value of the column will not be modified
+** by the UPDATE.
+**
+** If this routine is called from any context other than within the
+** xColumn method of a virtual table, then the return value is meaningless
+** and arbitrary.
+**
+** Virtual table implements might use this routine to optimize their
+** performance by substituting a NULL result, or some other light-weight
+** value, as a signal to the xUpdate routine that the column is unchanged.
+*/
+SQLITE_API int tdsqlite3_vtab_nochange(tdsqlite3_context *p){
+ assert( p );
+ return tdsqlite3_value_nochange(p->pOut);
+}
+
+/*
** Return the current time for a statement. If the current time
** is requested more than once within the same run of a single prepared
** statement, the exact same time is returned for each invocation regardless
** of the amount of time that elapses between invocations. In other words,
** the time returned is always the time of the first call.
*/
-SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context *p){
+SQLITE_PRIVATE tdsqlite3_int64 tdsqlite3StmtCurrentTime(tdsqlite3_context *p){
int rc;
-#ifndef SQLITE_ENABLE_STAT3_OR_STAT4
- sqlite3_int64 *piTime = &p->pVdbe->iCurrentTime;
+#ifndef SQLITE_ENABLE_STAT4
+ tdsqlite3_int64 *piTime = &p->pVdbe->iCurrentTime;
assert( p->pVdbe!=0 );
#else
- sqlite3_int64 iTime = 0;
- sqlite3_int64 *piTime = p->pVdbe!=0 ? &p->pVdbe->iCurrentTime : &iTime;
+ tdsqlite3_int64 iTime = 0;
+ tdsqlite3_int64 *piTime = p->pVdbe!=0 ? &p->pVdbe->iCurrentTime : &iTime;
#endif
if( *piTime==0 ){
- rc = sqlite3OsCurrentTimeInt64(p->pOut->db->pVfs, piTime);
+ rc = tdsqlite3OsCurrentTimeInt64(p->pOut->db->pVfs, piTime);
if( rc ) *piTime = 0;
}
return *piTime;
}
/*
-** The following is the implementation of an SQL function that always
-** fails with an error message stating that the function is used in the
-** wrong context. The sqlite3_overload_function() API might construct
-** SQL function that use this routine so that the functions will exist
-** for name resolution but are actually overloaded by the xFindFunction
-** method of virtual tables.
-*/
-SQLITE_PRIVATE void sqlite3InvalidFunction(
- sqlite3_context *context, /* The function calling context */
- int NotUsed, /* Number of arguments to the function */
- sqlite3_value **NotUsed2 /* Value of each argument */
-){
- const char *zName = context->pFunc->zName;
- char *zErr;
- UNUSED_PARAMETER2(NotUsed, NotUsed2);
- zErr = sqlite3_mprintf(
- "unable to use function %s in the requested context", zName);
- sqlite3_result_error(context, zErr, -1);
- sqlite3_free(zErr);
-}
-
-/*
** Create a new aggregate context for p and return a pointer to
** its pMem->z element.
*/
-static SQLITE_NOINLINE void *createAggContext(sqlite3_context *p, int nByte){
+static SQLITE_NOINLINE void *createAggContext(tdsqlite3_context *p, int nByte){
Mem *pMem = p->pMem;
assert( (pMem->flags & MEM_Agg)==0 );
if( nByte<=0 ){
- sqlite3VdbeMemSetNull(pMem);
+ tdsqlite3VdbeMemSetNull(pMem);
pMem->z = 0;
}else{
- sqlite3VdbeMemClearAndResize(pMem, nByte);
+ tdsqlite3VdbeMemClearAndResize(pMem, nByte);
pMem->flags = MEM_Agg;
pMem->u.pDef = p->pFunc;
if( pMem->z ){
@@ -78989,9 +87018,9 @@ static SQLITE_NOINLINE void *createAggContext(sqlite3_context *p, int nByte){
** context is allocated on the first call. Subsequent calls return the
** same context that was returned on prior calls.
*/
-SQLITE_API void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){
+SQLITE_API void *tdsqlite3_aggregate_context(tdsqlite3_context *p, int nByte){
assert( p && p->pFunc && p->pFunc->xFinalize );
- assert( sqlite3_mutex_held(p->pOut->db->mutex) );
+ assert( tdsqlite3_mutex_held(p->pOut->db->mutex) );
testcase( nByte<0 );
if( (p->pMem->flags & MEM_Agg)==0 ){
return createAggContext(p, nByte);
@@ -79003,30 +87032,43 @@ SQLITE_API void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){
/*
** Return the auxiliary data pointer, if any, for the iArg'th argument to
** the user-function defined by pCtx.
+**
+** The left-most argument is 0.
+**
+** Undocumented behavior: If iArg is negative then access a cache of
+** auxiliary data pointers that is available to all functions within a
+** single prepared statement. The iArg values must match.
*/
-SQLITE_API void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){
+SQLITE_API void *tdsqlite3_get_auxdata(tdsqlite3_context *pCtx, int iArg){
AuxData *pAuxData;
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
-#if SQLITE_ENABLE_STAT3_OR_STAT4
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+#if SQLITE_ENABLE_STAT4
if( pCtx->pVdbe==0 ) return 0;
#else
assert( pCtx->pVdbe!=0 );
#endif
- for(pAuxData=pCtx->pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNext){
- if( pAuxData->iOp==pCtx->iOp && pAuxData->iArg==iArg ) break;
+ for(pAuxData=pCtx->pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNextAux){
+ if( pAuxData->iAuxArg==iArg && (pAuxData->iAuxOp==pCtx->iOp || iArg<0) ){
+ return pAuxData->pAux;
+ }
}
-
- return (pAuxData ? pAuxData->pAux : 0);
+ return 0;
}
/*
** Set the auxiliary data pointer and delete function, for the iArg'th
** argument to the user-function defined by pCtx. Any previous value is
** deleted by calling the delete function specified when it was set.
+**
+** The left-most argument is 0.
+**
+** Undocumented behavior: If iArg is negative then make the data available
+** to all functions within the current prepared statement using iArg as an
+** access code.
*/
-SQLITE_API void sqlite3_set_auxdata(
- sqlite3_context *pCtx,
+SQLITE_API void tdsqlite3_set_auxdata(
+ tdsqlite3_context *pCtx,
int iArg,
void *pAux,
void (*xDelete)(void*)
@@ -79034,34 +87076,32 @@ SQLITE_API void sqlite3_set_auxdata(
AuxData *pAuxData;
Vdbe *pVdbe = pCtx->pVdbe;
- assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) );
- if( iArg<0 ) goto failed;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+ assert( tdsqlite3_mutex_held(pCtx->pOut->db->mutex) );
+#ifdef SQLITE_ENABLE_STAT4
if( pVdbe==0 ) goto failed;
#else
assert( pVdbe!=0 );
#endif
- for(pAuxData=pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNext){
- if( pAuxData->iOp==pCtx->iOp && pAuxData->iArg==iArg ) break;
+ for(pAuxData=pVdbe->pAuxData; pAuxData; pAuxData=pAuxData->pNextAux){
+ if( pAuxData->iAuxArg==iArg && (pAuxData->iAuxOp==pCtx->iOp || iArg<0) ){
+ break;
+ }
}
if( pAuxData==0 ){
- pAuxData = sqlite3DbMallocZero(pVdbe->db, sizeof(AuxData));
+ pAuxData = tdsqlite3DbMallocZero(pVdbe->db, sizeof(AuxData));
if( !pAuxData ) goto failed;
- pAuxData->iOp = pCtx->iOp;
- pAuxData->iArg = iArg;
- pAuxData->pNext = pVdbe->pAuxData;
+ pAuxData->iAuxOp = pCtx->iOp;
+ pAuxData->iAuxArg = iArg;
+ pAuxData->pNextAux = pVdbe->pAuxData;
pVdbe->pAuxData = pAuxData;
- if( pCtx->fErrorOrAux==0 ){
- pCtx->isError = 0;
- pCtx->fErrorOrAux = 1;
- }
- }else if( pAuxData->xDelete ){
- pAuxData->xDelete(pAuxData->pAux);
+ if( pCtx->isError==0 ) pCtx->isError = -1;
+ }else if( pAuxData->xDeleteAux ){
+ pAuxData->xDeleteAux(pAuxData->pAux);
}
pAuxData->pAux = pAux;
- pAuxData->xDelete = xDelete;
+ pAuxData->xDeleteAux = xDelete;
return;
failed:
@@ -79080,7 +87120,7 @@ failed:
** implementations should keep their own counts within their aggregate
** context.
*/
-SQLITE_API int sqlite3_aggregate_count(sqlite3_context *p){
+SQLITE_API int tdsqlite3_aggregate_count(tdsqlite3_context *p){
assert( p && p->pMem && p->pFunc && p->pFunc->xFinalize );
return p->pMem->n;
}
@@ -79089,7 +87129,7 @@ SQLITE_API int sqlite3_aggregate_count(sqlite3_context *p){
/*
** Return the number of columns in the result set for the statement pStmt.
*/
-SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt){
+SQLITE_API int tdsqlite3_column_count(tdsqlite3_stmt *pStmt){
Vdbe *pVm = (Vdbe *)pStmt;
return pVm ? pVm->nResColumn : 0;
}
@@ -79098,7 +87138,7 @@ SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt){
** Return the number of values available from the current row of the
** currently executing statement pStmt.
*/
-SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt){
+SQLITE_API int tdsqlite3_data_count(tdsqlite3_stmt *pStmt){
Vdbe *pVm = (Vdbe *)pStmt;
if( pVm==0 || pVm->pResultSet==0 ) return 0;
return pVm->nResColumn;
@@ -79131,11 +87171,11 @@ static const Mem *columnNullValue(void){
/* .zMalloc = */ (char*)0,
/* .szMalloc = */ (int)0,
/* .uTemp = */ (u32)0,
- /* .db = */ (sqlite3*)0,
+ /* .db = */ (tdsqlite3*)0,
/* .xDel = */ (void(*)(void*))0,
#ifdef SQLITE_DEBUG
/* .pScopyFrom = */ (Mem*)0,
- /* .pFiller = */ (void*)0,
+ /* .mScopyFlags= */ 0,
#endif
};
return &nullMem;
@@ -79147,25 +87187,25 @@ static const Mem *columnNullValue(void){
** If iCol is not valid, return a pointer to a Mem which has a value
** of NULL.
*/
-static Mem *columnMem(sqlite3_stmt *pStmt, int i){
+static Mem *columnMem(tdsqlite3_stmt *pStmt, int i){
Vdbe *pVm;
Mem *pOut;
pVm = (Vdbe *)pStmt;
if( pVm==0 ) return (Mem*)columnNullValue();
assert( pVm->db );
- sqlite3_mutex_enter(pVm->db->mutex);
+ tdsqlite3_mutex_enter(pVm->db->mutex);
if( pVm->pResultSet!=0 && i<pVm->nResColumn && i>=0 ){
pOut = &pVm->pResultSet[i];
}else{
- sqlite3Error(pVm->db, SQLITE_RANGE);
+ tdsqlite3Error(pVm->db, SQLITE_RANGE);
pOut = (Mem*)columnNullValue();
}
return pOut;
}
/*
-** This function is called after invoking an sqlite3_value_XXX function on a
+** This function is called after invoking an tdsqlite3_value_XXX function on a
** column value (i.e. a value returned by evaluating an SQL expression in the
** select list of a SELECT statement) that may cause a malloc() failure. If
** malloc() has failed, the threads mallocFailed flag is cleared and the result
@@ -79173,38 +87213,38 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){
**
** Specifically, this is called from within:
**
-** sqlite3_column_int()
-** sqlite3_column_int64()
-** sqlite3_column_text()
-** sqlite3_column_text16()
-** sqlite3_column_real()
-** sqlite3_column_bytes()
-** sqlite3_column_bytes16()
+** tdsqlite3_column_int()
+** tdsqlite3_column_int64()
+** tdsqlite3_column_text()
+** tdsqlite3_column_text16()
+** tdsqlite3_column_real()
+** tdsqlite3_column_bytes()
+** tdsqlite3_column_bytes16()
** sqiite3_column_blob()
*/
-static void columnMallocFailure(sqlite3_stmt *pStmt)
+static void columnMallocFailure(tdsqlite3_stmt *pStmt)
{
/* If malloc() failed during an encoding conversion within an
- ** sqlite3_column_XXX API, then set the return code of the statement to
+ ** tdsqlite3_column_XXX API, then set the return code of the statement to
** SQLITE_NOMEM. The next call to _step() (if any) will return SQLITE_ERROR
** and _finalize() will return NOMEM.
*/
Vdbe *p = (Vdbe *)pStmt;
if( p ){
assert( p->db!=0 );
- assert( sqlite3_mutex_held(p->db->mutex) );
- p->rc = sqlite3ApiExit(p->db, p->rc);
- sqlite3_mutex_leave(p->db->mutex);
+ assert( tdsqlite3_mutex_held(p->db->mutex) );
+ p->rc = tdsqlite3ApiExit(p->db, p->rc);
+ tdsqlite3_mutex_leave(p->db->mutex);
}
}
-/**************************** sqlite3_column_ *******************************
+/**************************** tdsqlite3_column_ *******************************
** The following routines are used to access elements of the current row
** in the result set.
*/
-SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int i){
+SQLITE_API const void *tdsqlite3_column_blob(tdsqlite3_stmt *pStmt, int i){
const void *val;
- val = sqlite3_value_blob( columnMem(pStmt,i) );
+ val = tdsqlite3_value_blob( columnMem(pStmt,i) );
/* Even though there is no encoding conversion, value_blob() might
** need to call malloc() to expand the result of a zeroblob()
** expression.
@@ -79212,54 +87252,54 @@ SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int i){
columnMallocFailure(pStmt);
return val;
}
-SQLITE_API int sqlite3_column_bytes(sqlite3_stmt *pStmt, int i){
- int val = sqlite3_value_bytes( columnMem(pStmt,i) );
+SQLITE_API int tdsqlite3_column_bytes(tdsqlite3_stmt *pStmt, int i){
+ int val = tdsqlite3_value_bytes( columnMem(pStmt,i) );
columnMallocFailure(pStmt);
return val;
}
-SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt *pStmt, int i){
- int val = sqlite3_value_bytes16( columnMem(pStmt,i) );
+SQLITE_API int tdsqlite3_column_bytes16(tdsqlite3_stmt *pStmt, int i){
+ int val = tdsqlite3_value_bytes16( columnMem(pStmt,i) );
columnMallocFailure(pStmt);
return val;
}
-SQLITE_API double sqlite3_column_double(sqlite3_stmt *pStmt, int i){
- double val = sqlite3_value_double( columnMem(pStmt,i) );
+SQLITE_API double tdsqlite3_column_double(tdsqlite3_stmt *pStmt, int i){
+ double val = tdsqlite3_value_double( columnMem(pStmt,i) );
columnMallocFailure(pStmt);
return val;
}
-SQLITE_API int sqlite3_column_int(sqlite3_stmt *pStmt, int i){
- int val = sqlite3_value_int( columnMem(pStmt,i) );
+SQLITE_API int tdsqlite3_column_int(tdsqlite3_stmt *pStmt, int i){
+ int val = tdsqlite3_value_int( columnMem(pStmt,i) );
columnMallocFailure(pStmt);
return val;
}
-SQLITE_API sqlite_int64 sqlite3_column_int64(sqlite3_stmt *pStmt, int i){
- sqlite_int64 val = sqlite3_value_int64( columnMem(pStmt,i) );
+SQLITE_API sqlite_int64 tdsqlite3_column_int64(tdsqlite3_stmt *pStmt, int i){
+ sqlite_int64 val = tdsqlite3_value_int64( columnMem(pStmt,i) );
columnMallocFailure(pStmt);
return val;
}
-SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt *pStmt, int i){
- const unsigned char *val = sqlite3_value_text( columnMem(pStmt,i) );
+SQLITE_API const unsigned char *tdsqlite3_column_text(tdsqlite3_stmt *pStmt, int i){
+ const unsigned char *val = tdsqlite3_value_text( columnMem(pStmt,i) );
columnMallocFailure(pStmt);
return val;
}
-SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt *pStmt, int i){
+SQLITE_API tdsqlite3_value *tdsqlite3_column_value(tdsqlite3_stmt *pStmt, int i){
Mem *pOut = columnMem(pStmt, i);
if( pOut->flags&MEM_Static ){
pOut->flags &= ~MEM_Static;
pOut->flags |= MEM_Ephem;
}
columnMallocFailure(pStmt);
- return (sqlite3_value *)pOut;
+ return (tdsqlite3_value *)pOut;
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt *pStmt, int i){
- const void *val = sqlite3_value_text16( columnMem(pStmt,i) );
+SQLITE_API const void *tdsqlite3_column_text16(tdsqlite3_stmt *pStmt, int i){
+ const void *val = tdsqlite3_value_text16( columnMem(pStmt,i) );
columnMallocFailure(pStmt);
return val;
}
#endif /* SQLITE_OMIT_UTF16 */
-SQLITE_API int sqlite3_column_type(sqlite3_stmt *pStmt, int i){
- int iType = sqlite3_value_type( columnMem(pStmt,i) );
+SQLITE_API int tdsqlite3_column_type(tdsqlite3_stmt *pStmt, int i){
+ int iType = tdsqlite3_value_type( columnMem(pStmt,i) );
columnMallocFailure(pStmt);
return iType;
}
@@ -79281,15 +87321,15 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt *pStmt, int i){
** or a constant) then useTypes 2, 3, and 4 return NULL.
*/
static const void *columnName(
- sqlite3_stmt *pStmt,
- int N,
- const void *(*xFunc)(Mem*),
- int useType
+ tdsqlite3_stmt *pStmt, /* The statement */
+ int N, /* Which column to get the name for */
+ int useUtf16, /* True to return the name as UTF16 */
+ int useType /* What type of name */
){
const void *ret;
Vdbe *p;
int n;
- sqlite3 *db;
+ tdsqlite3 *db;
#ifdef SQLITE_ENABLE_API_ARMOR
if( pStmt==0 ){
(void)SQLITE_MISUSE_BKPT;
@@ -79300,20 +87340,27 @@ static const void *columnName(
p = (Vdbe *)pStmt;
db = p->db;
assert( db!=0 );
- n = sqlite3_column_count(pStmt);
+ n = tdsqlite3_column_count(pStmt);
if( N<n && N>=0 ){
N += useType*n;
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
assert( db->mallocFailed==0 );
- ret = xFunc(&p->aColName[N]);
- /* A malloc may have failed inside of the xFunc() call. If this
+#ifndef SQLITE_OMIT_UTF16
+ if( useUtf16 ){
+ ret = tdsqlite3_value_text16((tdsqlite3_value*)&p->aColName[N]);
+ }else
+#endif
+ {
+ ret = tdsqlite3_value_text((tdsqlite3_value*)&p->aColName[N]);
+ }
+ /* A malloc may have failed inside of the _text() call. If this
** is the case, clear the mallocFailed flag and return NULL.
*/
if( db->mallocFailed ){
- sqlite3OomClear(db);
+ tdsqlite3OomClear(db);
ret = 0;
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
}
return ret;
}
@@ -79322,14 +87369,12 @@ static const void *columnName(
** Return the name of the Nth column of the result set returned by SQL
** statement pStmt.
*/
-SQLITE_API const char *sqlite3_column_name(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_NAME);
+SQLITE_API const char *tdsqlite3_column_name(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 0, COLNAME_NAME);
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_NAME);
+SQLITE_API const void *tdsqlite3_column_name16(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 1, COLNAME_NAME);
}
#endif
@@ -79347,14 +87392,12 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt *pStmt, int N){
** Return the column declaration type (if applicable) of the 'i'th column
** of the result set of SQL statement pStmt.
*/
-SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_DECLTYPE);
+SQLITE_API const char *tdsqlite3_column_decltype(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 0, COLNAME_DECLTYPE);
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_DECLTYPE);
+SQLITE_API const void *tdsqlite3_column_decltype16(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 1, COLNAME_DECLTYPE);
}
#endif /* SQLITE_OMIT_UTF16 */
#endif /* SQLITE_OMIT_DECLTYPE */
@@ -79365,14 +87408,12 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt *pStmt, int N){
** NULL is returned if the result column is an expression or constant or
** anything else which is not an unambiguous reference to a database column.
*/
-SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_DATABASE);
+SQLITE_API const char *tdsqlite3_column_database_name(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 0, COLNAME_DATABASE);
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_DATABASE);
+SQLITE_API const void *tdsqlite3_column_database_name16(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 1, COLNAME_DATABASE);
}
#endif /* SQLITE_OMIT_UTF16 */
@@ -79381,14 +87422,12 @@ SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt *pStmt, int N
** NULL is returned if the result column is an expression or constant or
** anything else which is not an unambiguous reference to a database column.
*/
-SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_TABLE);
+SQLITE_API const char *tdsqlite3_column_table_name(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 0, COLNAME_TABLE);
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_TABLE);
+SQLITE_API const void *tdsqlite3_column_table_name16(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 1, COLNAME_TABLE);
}
#endif /* SQLITE_OMIT_UTF16 */
@@ -79397,20 +87436,18 @@ SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt *pStmt, int N){
** NULL is returned if the result column is an expression or constant or
** anything else which is not an unambiguous reference to a database column.
*/
-SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text, COLNAME_COLUMN);
+SQLITE_API const char *tdsqlite3_column_origin_name(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 0, COLNAME_COLUMN);
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){
- return columnName(
- pStmt, N, (const void*(*)(Mem*))sqlite3_value_text16, COLNAME_COLUMN);
+SQLITE_API const void *tdsqlite3_column_origin_name16(tdsqlite3_stmt *pStmt, int N){
+ return columnName(pStmt, N, 1, COLNAME_COLUMN);
}
#endif /* SQLITE_OMIT_UTF16 */
#endif /* SQLITE_ENABLE_COLUMN_METADATA */
-/******************************* sqlite3_bind_ ***************************
+/******************************* tdsqlite3_bind_ ***************************
**
** Routines used to attach values to wildcards in a compiled SQL statement.
*/
@@ -79430,24 +87467,24 @@ static int vdbeUnbind(Vdbe *p, int i){
if( vdbeSafetyNotNull(p) ){
return SQLITE_MISUSE_BKPT;
}
- sqlite3_mutex_enter(p->db->mutex);
+ tdsqlite3_mutex_enter(p->db->mutex);
if( p->magic!=VDBE_MAGIC_RUN || p->pc>=0 ){
- sqlite3Error(p->db, SQLITE_MISUSE);
- sqlite3_mutex_leave(p->db->mutex);
- sqlite3_log(SQLITE_MISUSE,
+ tdsqlite3Error(p->db, SQLITE_MISUSE);
+ tdsqlite3_mutex_leave(p->db->mutex);
+ tdsqlite3_log(SQLITE_MISUSE,
"bind on a busy prepared statement: [%s]", p->zSql);
return SQLITE_MISUSE_BKPT;
}
if( i<1 || i>p->nVar ){
- sqlite3Error(p->db, SQLITE_RANGE);
- sqlite3_mutex_leave(p->db->mutex);
+ tdsqlite3Error(p->db, SQLITE_RANGE);
+ tdsqlite3_mutex_leave(p->db->mutex);
return SQLITE_RANGE;
}
i--;
pVar = &p->aVar[i];
- sqlite3VdbeMemRelease(pVar);
+ tdsqlite3VdbeMemRelease(pVar);
pVar->flags = MEM_Null;
- sqlite3Error(p->db, SQLITE_OK);
+ p->db->errCode = SQLITE_OK;
/* If the bit corresponding to this variable in Vdbe.expmask is set, then
** binding a new value to this variable invalidates the current query plan.
@@ -79455,12 +87492,11 @@ static int vdbeUnbind(Vdbe *p, int i){
** IMPLEMENTATION-OF: R-48440-37595 If the specific value bound to host
** parameter in the WHERE clause might influence the choice of query plan
** for a statement, then the statement will be automatically recompiled,
- ** as if there had been a schema change, on the first sqlite3_step() call
+ ** as if there had been a schema change, on the first tdsqlite3_step() call
** following any change to the bindings of that parameter.
*/
- if( p->isPrepareV2 &&
- ((i<32 && p->expmask & ((u32)1 << i)) || p->expmask==0xffffffff)
- ){
+ assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 );
+ if( p->expmask!=0 && (p->expmask & (i>=31 ? 0x80000000 : (u32)1<<i))!=0 ){
p->expired = 1;
}
return SQLITE_OK;
@@ -79470,7 +87506,7 @@ static int vdbeUnbind(Vdbe *p, int i){
** Bind a text or BLOB value.
*/
static int bindText(
- sqlite3_stmt *pStmt, /* The statement to bind against */
+ tdsqlite3_stmt *pStmt, /* The statement to bind against */
int i, /* Index of the parameter to bind */
const void *zData, /* Pointer to the data to be bound */
int nData, /* Number of bytes of data to be bound */
@@ -79485,14 +87521,16 @@ static int bindText(
if( rc==SQLITE_OK ){
if( zData!=0 ){
pVar = &p->aVar[i-1];
- rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel);
+ rc = tdsqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel);
if( rc==SQLITE_OK && encoding!=0 ){
- rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db));
+ rc = tdsqlite3VdbeChangeEncoding(pVar, ENC(p->db));
+ }
+ if( rc ){
+ tdsqlite3Error(p->db, rc);
+ rc = tdsqlite3ApiExit(p->db, rc);
}
- sqlite3Error(p->db, rc);
- rc = sqlite3ApiExit(p->db, rc);
}
- sqlite3_mutex_leave(p->db->mutex);
+ tdsqlite3_mutex_leave(p->db->mutex);
}else if( xDel!=SQLITE_STATIC && xDel!=SQLITE_TRANSIENT ){
xDel((void*)zData);
}
@@ -79503,8 +87541,8 @@ static int bindText(
/*
** Bind a blob value to an SQL statement variable.
*/
-SQLITE_API int sqlite3_bind_blob(
- sqlite3_stmt *pStmt,
+SQLITE_API int tdsqlite3_bind_blob(
+ tdsqlite3_stmt *pStmt,
int i,
const void *zData,
int nData,
@@ -79515,11 +87553,11 @@ SQLITE_API int sqlite3_bind_blob(
#endif
return bindText(pStmt, i, zData, nData, xDel, 0);
}
-SQLITE_API int sqlite3_bind_blob64(
- sqlite3_stmt *pStmt,
+SQLITE_API int tdsqlite3_bind_blob64(
+ tdsqlite3_stmt *pStmt,
int i,
const void *zData,
- sqlite3_uint64 nData,
+ tdsqlite3_uint64 nData,
void (*xDel)(void*)
){
assert( xDel!=SQLITE_DYNAMIC );
@@ -79529,40 +87567,58 @@ SQLITE_API int sqlite3_bind_blob64(
return bindText(pStmt, i, zData, (int)nData, xDel, 0);
}
}
-SQLITE_API int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){
+SQLITE_API int tdsqlite3_bind_double(tdsqlite3_stmt *pStmt, int i, double rValue){
int rc;
Vdbe *p = (Vdbe *)pStmt;
rc = vdbeUnbind(p, i);
if( rc==SQLITE_OK ){
- sqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue);
- sqlite3_mutex_leave(p->db->mutex);
+ tdsqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue);
+ tdsqlite3_mutex_leave(p->db->mutex);
}
return rc;
}
-SQLITE_API int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){
- return sqlite3_bind_int64(p, i, (i64)iValue);
+SQLITE_API int tdsqlite3_bind_int(tdsqlite3_stmt *p, int i, int iValue){
+ return tdsqlite3_bind_int64(p, i, (i64)iValue);
}
-SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){
+SQLITE_API int tdsqlite3_bind_int64(tdsqlite3_stmt *pStmt, int i, sqlite_int64 iValue){
int rc;
Vdbe *p = (Vdbe *)pStmt;
rc = vdbeUnbind(p, i);
if( rc==SQLITE_OK ){
- sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue);
- sqlite3_mutex_leave(p->db->mutex);
+ tdsqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue);
+ tdsqlite3_mutex_leave(p->db->mutex);
+ }
+ return rc;
+}
+SQLITE_API int tdsqlite3_bind_null(tdsqlite3_stmt *pStmt, int i){
+ int rc;
+ Vdbe *p = (Vdbe*)pStmt;
+ rc = vdbeUnbind(p, i);
+ if( rc==SQLITE_OK ){
+ tdsqlite3_mutex_leave(p->db->mutex);
}
return rc;
}
-SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){
+SQLITE_API int tdsqlite3_bind_pointer(
+ tdsqlite3_stmt *pStmt,
+ int i,
+ void *pPtr,
+ const char *zPTtype,
+ void (*xDestructor)(void*)
+){
int rc;
Vdbe *p = (Vdbe*)pStmt;
rc = vdbeUnbind(p, i);
if( rc==SQLITE_OK ){
- sqlite3_mutex_leave(p->db->mutex);
+ tdsqlite3VdbeMemSetPointer(&p->aVar[i-1], pPtr, zPTtype, xDestructor);
+ tdsqlite3_mutex_leave(p->db->mutex);
+ }else if( xDestructor ){
+ xDestructor(pPtr);
}
return rc;
}
-SQLITE_API int sqlite3_bind_text(
- sqlite3_stmt *pStmt,
+SQLITE_API int tdsqlite3_bind_text(
+ tdsqlite3_stmt *pStmt,
int i,
const char *zData,
int nData,
@@ -79570,11 +87626,11 @@ SQLITE_API int sqlite3_bind_text(
){
return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF8);
}
-SQLITE_API int sqlite3_bind_text64(
- sqlite3_stmt *pStmt,
+SQLITE_API int tdsqlite3_bind_text64(
+ tdsqlite3_stmt *pStmt,
int i,
const char *zData,
- sqlite3_uint64 nData,
+ tdsqlite3_uint64 nData,
void (*xDel)(void*),
unsigned char enc
){
@@ -79587,8 +87643,8 @@ SQLITE_API int sqlite3_bind_text64(
}
}
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API int sqlite3_bind_text16(
- sqlite3_stmt *pStmt,
+SQLITE_API int tdsqlite3_bind_text16(
+ tdsqlite3_stmt *pStmt,
int i,
const void *zData,
int nData,
@@ -79597,22 +87653,22 @@ SQLITE_API int sqlite3_bind_text16(
return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF16NATIVE);
}
#endif /* SQLITE_OMIT_UTF16 */
-SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){
+SQLITE_API int tdsqlite3_bind_value(tdsqlite3_stmt *pStmt, int i, const tdsqlite3_value *pValue){
int rc;
- switch( sqlite3_value_type((sqlite3_value*)pValue) ){
+ switch( tdsqlite3_value_type((tdsqlite3_value*)pValue) ){
case SQLITE_INTEGER: {
- rc = sqlite3_bind_int64(pStmt, i, pValue->u.i);
+ rc = tdsqlite3_bind_int64(pStmt, i, pValue->u.i);
break;
}
case SQLITE_FLOAT: {
- rc = sqlite3_bind_double(pStmt, i, pValue->u.r);
+ rc = tdsqlite3_bind_double(pStmt, i, pValue->u.r);
break;
}
case SQLITE_BLOB: {
if( pValue->flags & MEM_Zero ){
- rc = sqlite3_bind_zeroblob(pStmt, i, pValue->u.nZero);
+ rc = tdsqlite3_bind_zeroblob(pStmt, i, pValue->u.nZero);
}else{
- rc = sqlite3_bind_blob(pStmt, i, pValue->z, pValue->n,SQLITE_TRANSIENT);
+ rc = tdsqlite3_bind_blob(pStmt, i, pValue->z, pValue->n,SQLITE_TRANSIENT);
}
break;
}
@@ -79622,34 +87678,34 @@ SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_valu
break;
}
default: {
- rc = sqlite3_bind_null(pStmt, i);
+ rc = tdsqlite3_bind_null(pStmt, i);
break;
}
}
return rc;
}
-SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){
+SQLITE_API int tdsqlite3_bind_zeroblob(tdsqlite3_stmt *pStmt, int i, int n){
int rc;
Vdbe *p = (Vdbe *)pStmt;
rc = vdbeUnbind(p, i);
if( rc==SQLITE_OK ){
- sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n);
- sqlite3_mutex_leave(p->db->mutex);
+ tdsqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n);
+ tdsqlite3_mutex_leave(p->db->mutex);
}
return rc;
}
-SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt *pStmt, int i, sqlite3_uint64 n){
+SQLITE_API int tdsqlite3_bind_zeroblob64(tdsqlite3_stmt *pStmt, int i, tdsqlite3_uint64 n){
int rc;
Vdbe *p = (Vdbe *)pStmt;
- sqlite3_mutex_enter(p->db->mutex);
+ tdsqlite3_mutex_enter(p->db->mutex);
if( n>(u64)p->db->aLimit[SQLITE_LIMIT_LENGTH] ){
rc = SQLITE_TOOBIG;
}else{
assert( (n & 0x7FFFFFFF)==n );
- rc = sqlite3_bind_zeroblob(pStmt, i, n);
+ rc = tdsqlite3_bind_zeroblob(pStmt, i, n);
}
- rc = sqlite3ApiExit(p->db, rc);
- sqlite3_mutex_leave(p->db->mutex);
+ rc = tdsqlite3ApiExit(p->db, rc);
+ tdsqlite3_mutex_leave(p->db->mutex);
return rc;
}
@@ -79657,7 +87713,7 @@ SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt *pStmt, int i, sqlite3_uint6
** Return the number of wildcards that can be potentially bound to.
** This routine is added to support DBD::SQLite.
*/
-SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){
+SQLITE_API int tdsqlite3_bind_parameter_count(tdsqlite3_stmt *pStmt){
Vdbe *p = (Vdbe*)pStmt;
return p ? p->nVar : 0;
}
@@ -79668,12 +87724,10 @@ SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){
**
** The result is always UTF-8.
*/
-SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){
+SQLITE_API const char *tdsqlite3_bind_parameter_name(tdsqlite3_stmt *pStmt, int i){
Vdbe *p = (Vdbe*)pStmt;
- if( p==0 || i<1 || i>p->nzVar ){
- return 0;
- }
- return p->azVar[i-1];
+ if( p==0 ) return 0;
+ return tdsqlite3VListNumToName(p->pVList, i);
}
/*
@@ -79681,46 +87735,35 @@ SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){
** with that name. If there is no variable with the given name,
** return 0.
*/
-SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe *p, const char *zName, int nName){
- int i;
- if( p==0 ){
- return 0;
- }
- if( zName ){
- for(i=0; i<p->nzVar; i++){
- const char *z = p->azVar[i];
- if( z && strncmp(z,zName,nName)==0 && z[nName]==0 ){
- return i+1;
- }
- }
- }
- return 0;
+SQLITE_PRIVATE int tdsqlite3VdbeParameterIndex(Vdbe *p, const char *zName, int nName){
+ if( p==0 || zName==0 ) return 0;
+ return tdsqlite3VListNameToNum(p->pVList, zName, nName);
}
-SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){
- return sqlite3VdbeParameterIndex((Vdbe*)pStmt, zName, sqlite3Strlen30(zName));
+SQLITE_API int tdsqlite3_bind_parameter_index(tdsqlite3_stmt *pStmt, const char *zName){
+ return tdsqlite3VdbeParameterIndex((Vdbe*)pStmt, zName, tdsqlite3Strlen30(zName));
}
/*
** Transfer all bindings from the first statement over to the second.
*/
-SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *pFromStmt, sqlite3_stmt *pToStmt){
+SQLITE_PRIVATE int tdsqlite3TransferBindings(tdsqlite3_stmt *pFromStmt, tdsqlite3_stmt *pToStmt){
Vdbe *pFrom = (Vdbe*)pFromStmt;
Vdbe *pTo = (Vdbe*)pToStmt;
int i;
assert( pTo->db==pFrom->db );
assert( pTo->nVar==pFrom->nVar );
- sqlite3_mutex_enter(pTo->db->mutex);
+ tdsqlite3_mutex_enter(pTo->db->mutex);
for(i=0; i<pFrom->nVar; i++){
- sqlite3VdbeMemMove(&pTo->aVar[i], &pFrom->aVar[i]);
+ tdsqlite3VdbeMemMove(&pTo->aVar[i], &pFrom->aVar[i]);
}
- sqlite3_mutex_leave(pTo->db->mutex);
+ tdsqlite3_mutex_leave(pTo->db->mutex);
return SQLITE_OK;
}
#ifndef SQLITE_OMIT_DEPRECATED
/*
** Deprecated external interface. Internal/core SQLite code
-** should call sqlite3TransferBindings.
+** should call tdsqlite3TransferBindings.
**
** It is misuse to call this routine with statements from different
** database connections. But as this is a deprecated interface, we
@@ -79730,29 +87773,31 @@ SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *pFromStmt, sqlite3_stmt
** an SQLITE_ERROR is returned. Nothing else can go wrong, so otherwise
** SQLITE_OK is returned.
*/
-SQLITE_API int sqlite3_transfer_bindings(sqlite3_stmt *pFromStmt, sqlite3_stmt *pToStmt){
+SQLITE_API int tdsqlite3_transfer_bindings(tdsqlite3_stmt *pFromStmt, tdsqlite3_stmt *pToStmt){
Vdbe *pFrom = (Vdbe*)pFromStmt;
Vdbe *pTo = (Vdbe*)pToStmt;
if( pFrom->nVar!=pTo->nVar ){
return SQLITE_ERROR;
}
- if( pTo->isPrepareV2 && pTo->expmask ){
+ assert( (pTo->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || pTo->expmask==0 );
+ if( pTo->expmask ){
pTo->expired = 1;
}
- if( pFrom->isPrepareV2 && pFrom->expmask ){
+ assert( (pFrom->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || pFrom->expmask==0 );
+ if( pFrom->expmask ){
pFrom->expired = 1;
}
- return sqlite3TransferBindings(pFromStmt, pToStmt);
+ return tdsqlite3TransferBindings(pFromStmt, pToStmt);
}
#endif
/*
-** Return the sqlite3* database handle to which the prepared statement given
+** Return the tdsqlite3* database handle to which the prepared statement given
** in the argument belongs. This is the same database handle that was
-** the first argument to the sqlite3_prepare() that was used to create
+** the first argument to the tdsqlite3_prepare() that was used to create
** the statement in the first place.
*/
-SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt *pStmt){
+SQLITE_API tdsqlite3 *tdsqlite3_db_handle(tdsqlite3_stmt *pStmt){
return pStmt ? ((Vdbe*)pStmt)->db : 0;
}
@@ -79760,14 +87805,22 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt *pStmt){
** Return true if the prepared statement is guaranteed to not modify the
** database.
*/
-SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt){
+SQLITE_API int tdsqlite3_stmt_readonly(tdsqlite3_stmt *pStmt){
return pStmt ? ((Vdbe*)pStmt)->readOnly : 1;
}
/*
+** Return 1 if the statement is an EXPLAIN and return 2 if the
+** statement is an EXPLAIN QUERY PLAN
+*/
+SQLITE_API int tdsqlite3_stmt_isexplain(tdsqlite3_stmt *pStmt){
+ return pStmt ? ((Vdbe*)pStmt)->explain : 0;
+}
+
+/*
** Return true if the prepared statement is in need of being reset.
*/
-SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){
+SQLITE_API int tdsqlite3_stmt_busy(tdsqlite3_stmt *pStmt){
Vdbe *v = (Vdbe*)pStmt;
return v!=0 && v->magic==VDBE_MAGIC_RUN && v->pc>=0;
}
@@ -79778,45 +87831,58 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){
** prepared statement for the database connection. Return NULL if there
** are no more.
*/
-SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){
- sqlite3_stmt *pNext;
+SQLITE_API tdsqlite3_stmt *tdsqlite3_next_stmt(tdsqlite3 *pDb, tdsqlite3_stmt *pStmt){
+ tdsqlite3_stmt *pNext;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(pDb) ){
+ if( !tdsqlite3SafetyCheckOk(pDb) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
- sqlite3_mutex_enter(pDb->mutex);
+ tdsqlite3_mutex_enter(pDb->mutex);
if( pStmt==0 ){
- pNext = (sqlite3_stmt*)pDb->pVdbe;
+ pNext = (tdsqlite3_stmt*)pDb->pVdbe;
}else{
- pNext = (sqlite3_stmt*)((Vdbe*)pStmt)->pNext;
+ pNext = (tdsqlite3_stmt*)((Vdbe*)pStmt)->pNext;
}
- sqlite3_mutex_leave(pDb->mutex);
+ tdsqlite3_mutex_leave(pDb->mutex);
return pNext;
}
/*
** Return the value of a status counter for a prepared statement
*/
-SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){
+SQLITE_API int tdsqlite3_stmt_status(tdsqlite3_stmt *pStmt, int op, int resetFlag){
Vdbe *pVdbe = (Vdbe*)pStmt;
u32 v;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !pStmt ){
+ if( !pStmt
+ || (op!=SQLITE_STMTSTATUS_MEMUSED && (op<0||op>=ArraySize(pVdbe->aCounter)))
+ ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
- v = pVdbe->aCounter[op];
- if( resetFlag ) pVdbe->aCounter[op] = 0;
+ if( op==SQLITE_STMTSTATUS_MEMUSED ){
+ tdsqlite3 *db = pVdbe->db;
+ tdsqlite3_mutex_enter(db->mutex);
+ v = 0;
+ db->pnBytesFreed = (int*)&v;
+ tdsqlite3VdbeClearObject(db, pVdbe);
+ tdsqlite3DbFree(db, pVdbe);
+ db->pnBytesFreed = 0;
+ tdsqlite3_mutex_leave(db->mutex);
+ }else{
+ v = pVdbe->aCounter[op];
+ if( resetFlag ) pVdbe->aCounter[op] = 0;
+ }
return (int)v;
}
/*
** Return the SQL associated with a prepared statement
*/
-SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt){
+SQLITE_API const char *tdsqlite3_sql(tdsqlite3_stmt *pStmt){
Vdbe *p = (Vdbe *)pStmt;
return p ? p->zSql : 0;
}
@@ -79824,28 +87890,44 @@ SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt){
/*
** Return the SQL associated with a prepared statement with
** bound parameters expanded. Space to hold the returned string is
-** obtained from sqlite3_malloc(). The caller is responsible for
-** freeing the returned string by passing it to sqlite3_free().
+** obtained from tdsqlite3_malloc(). The caller is responsible for
+** freeing the returned string by passing it to tdsqlite3_free().
**
** The SQLITE_TRACE_SIZE_LIMIT puts an upper bound on the size of
** expanded bound parameters.
*/
-SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt){
+SQLITE_API char *tdsqlite3_expanded_sql(tdsqlite3_stmt *pStmt){
#ifdef SQLITE_OMIT_TRACE
return 0;
#else
char *z = 0;
- const char *zSql = sqlite3_sql(pStmt);
+ const char *zSql = tdsqlite3_sql(pStmt);
if( zSql ){
Vdbe *p = (Vdbe *)pStmt;
- sqlite3_mutex_enter(p->db->mutex);
- z = sqlite3VdbeExpandSql(p, zSql);
- sqlite3_mutex_leave(p->db->mutex);
+ tdsqlite3_mutex_enter(p->db->mutex);
+ z = tdsqlite3VdbeExpandSql(p, zSql);
+ tdsqlite3_mutex_leave(p->db->mutex);
}
return z;
#endif
}
+#ifdef SQLITE_ENABLE_NORMALIZE
+/*
+** Return the normalized SQL associated with a prepared statement.
+*/
+SQLITE_API const char *tdsqlite3_normalized_sql(tdsqlite3_stmt *pStmt){
+ Vdbe *p = (Vdbe *)pStmt;
+ if( p==0 ) return 0;
+ if( p->zNormSql==0 && ALWAYS(p->zSql!=0) ){
+ tdsqlite3_mutex_enter(p->db->mutex);
+ p->zNormSql = tdsqlite3Normalize(p, p->zSql);
+ tdsqlite3_mutex_leave(p->db->mutex);
+ }
+ return p->zNormSql;
+}
+#endif /* SQLITE_ENABLE_NORMALIZE */
+
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/*
** Allocate and populate an UnpackedRecord structure based on the serialized
@@ -79857,13 +87939,12 @@ static UnpackedRecord *vdbeUnpackRecord(
int nKey,
const void *pKey
){
- char *dummy; /* Dummy argument for AllocUnpackedRecord() */
UnpackedRecord *pRet; /* Return value */
- pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo, 0, 0, &dummy);
+ pRet = tdsqlite3VdbeAllocUnpackedRecord(pKeyInfo);
if( pRet ){
- memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nField+1));
- sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet);
+ memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nKeyField+1));
+ tdsqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet);
}
return pRet;
}
@@ -79872,8 +87953,9 @@ static UnpackedRecord *vdbeUnpackRecord(
** This function is called from within a pre-update callback to retrieve
** a field of the row currently being updated or deleted.
*/
-SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
+SQLITE_API int tdsqlite3_preupdate_old(tdsqlite3 *db, int iIdx, tdsqlite3_value **ppValue){
PreUpdate *p = db->pPreUpdate;
+ Mem *pMem;
int rc = SQLITE_OK;
/* Test that this call is being made from within an SQLITE_DELETE or
@@ -79882,6 +87964,9 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa
rc = SQLITE_MISUSE_BKPT;
goto preupdate_old_out;
}
+ if( p->pPk ){
+ iIdx = tdsqlite3TableColumnToIndex(p->pPk, iIdx);
+ }
if( iIdx>=p->pCsr->nField || iIdx<0 ){
rc = SQLITE_RANGE;
goto preupdate_old_out;
@@ -79892,38 +87977,37 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa
u32 nRec;
u8 *aRec;
- nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
- aRec = sqlite3DbMallocRaw(db, nRec);
+ nRec = tdsqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
+ aRec = tdsqlite3DbMallocRaw(db, nRec);
if( !aRec ) goto preupdate_old_out;
- rc = sqlite3BtreeData(p->pCsr->uc.pCursor, 0, nRec, aRec);
+ rc = tdsqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec);
if( rc==SQLITE_OK ){
p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec);
if( !p->pUnpacked ) rc = SQLITE_NOMEM;
}
if( rc!=SQLITE_OK ){
- sqlite3DbFree(db, aRec);
+ tdsqlite3DbFree(db, aRec);
goto preupdate_old_out;
}
p->aRecord = aRec;
}
- if( iIdx>=p->pUnpacked->nField ){
- *ppValue = (sqlite3_value *)columnNullValue();
- }else{
- Mem *pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
- *ppValue = &p->pUnpacked->aMem[iIdx];
- if( iIdx==p->pTab->iPKey ){
- sqlite3VdbeMemSetInt64(pMem, p->iKey1);
- }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){
- if( pMem->flags & MEM_Int ){
- sqlite3VdbeMemRealify(pMem);
- }
+ pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
+ if( iIdx==p->pTab->iPKey ){
+ tdsqlite3VdbeMemSetInt64(pMem, p->iKey1);
+ }else if( iIdx>=p->pUnpacked->nField ){
+ *ppValue = (tdsqlite3_value *)columnNullValue();
+ }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){
+ if( pMem->flags & (MEM_Int|MEM_IntReal) ){
+ testcase( pMem->flags & MEM_Int );
+ testcase( pMem->flags & MEM_IntReal );
+ tdsqlite3VdbeMemRealify(pMem);
}
}
preupdate_old_out:
- sqlite3Error(db, rc);
- return sqlite3ApiExit(db, rc);
+ tdsqlite3Error(db, rc);
+ return tdsqlite3ApiExit(db, rc);
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -79932,9 +88016,9 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa
** This function is called from within a pre-update callback to retrieve
** the number of columns in the row being updated, deleted or inserted.
*/
-SQLITE_API int sqlite3_preupdate_count(sqlite3 *db){
+SQLITE_API int tdsqlite3_preupdate_count(tdsqlite3 *db){
PreUpdate *p = db->pPreUpdate;
- return (p ? p->keyinfo.nField : 0);
+ return (p ? p->keyinfo.nKeyField : 0);
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -79950,7 +88034,7 @@ SQLITE_API int sqlite3_preupdate_count(sqlite3 *db){
** For the purposes of the previous paragraph, a foreign key CASCADE, SET NULL
** or SET DEFAULT action is considered a trigger.
*/
-SQLITE_API int sqlite3_preupdate_depth(sqlite3 *db){
+SQLITE_API int tdsqlite3_preupdate_depth(tdsqlite3 *db){
PreUpdate *p = db->pPreUpdate;
return (p ? p->v->nFrame : 0);
}
@@ -79961,7 +88045,7 @@ SQLITE_API int sqlite3_preupdate_depth(sqlite3 *db){
** This function is called from within a pre-update callback to retrieve
** a field of the row currently being updated or inserted.
*/
-SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
+SQLITE_API int tdsqlite3_preupdate_new(tdsqlite3 *db, int iIdx, tdsqlite3_value **ppValue){
PreUpdate *p = db->pPreUpdate;
int rc = SQLITE_OK;
Mem *pMem;
@@ -79970,6 +88054,9 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa
rc = SQLITE_MISUSE_BKPT;
goto preupdate_new_out;
}
+ if( p->pPk && p->op!=SQLITE_UPDATE ){
+ iIdx = tdsqlite3TableColumnToIndex(p->pPk, iIdx);
+ }
if( iIdx>=p->pCsr->nField || iIdx<0 ){
rc = SQLITE_RANGE;
goto preupdate_new_out;
@@ -79990,13 +88077,11 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa
}
p->pNewUnpacked = pUnpack;
}
- if( iIdx>=pUnpack->nField ){
- pMem = (sqlite3_value *)columnNullValue();
- }else{
- pMem = &pUnpack->aMem[iIdx];
- if( iIdx==p->pTab->iPKey ){
- sqlite3VdbeMemSetInt64(pMem, p->iKey2);
- }
+ pMem = &pUnpack->aMem[iIdx];
+ if( iIdx==p->pTab->iPKey ){
+ tdsqlite3VdbeMemSetInt64(pMem, p->iKey2);
+ }else if( iIdx>=pUnpack->nField ){
+ pMem = (tdsqlite3_value *)columnNullValue();
}
}else{
/* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required
@@ -80006,7 +88091,7 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa
*/
assert( p->op==SQLITE_UPDATE );
if( !p->aNew ){
- p->aNew = (Mem *)sqlite3DbMallocZero(db, sizeof(Mem) * p->pCsr->nField);
+ p->aNew = (Mem *)tdsqlite3DbMallocZero(db, sizeof(Mem) * p->pCsr->nField);
if( !p->aNew ){
rc = SQLITE_NOMEM;
goto preupdate_new_out;
@@ -80016,9 +88101,9 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa
pMem = &p->aNew[iIdx];
if( pMem->flags==0 ){
if( iIdx==p->pTab->iPKey ){
- sqlite3VdbeMemSetInt64(pMem, p->iKey2);
+ tdsqlite3VdbeMemSetInt64(pMem, p->iKey2);
}else{
- rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]);
+ rc = tdsqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]);
if( rc!=SQLITE_OK ) goto preupdate_new_out;
}
}
@@ -80026,8 +88111,8 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa
*ppValue = pMem;
preupdate_new_out:
- sqlite3Error(db, rc);
- return sqlite3ApiExit(db, rc);
+ tdsqlite3Error(db, rc);
+ return tdsqlite3ApiExit(db, rc);
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
@@ -80035,8 +88120,8 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa
/*
** Return status data for a single loop within query pStmt.
*/
-SQLITE_API int sqlite3_stmt_scanstatus(
- sqlite3_stmt *pStmt, /* Prepared statement being queried */
+SQLITE_API int tdsqlite3_stmt_scanstatus(
+ tdsqlite3_stmt *pStmt, /* Prepared statement being queried */
int idx, /* Index of loop to report on */
int iScanStatusOp, /* Which metric to return */
void *pOut /* OUT: Write the answer here */
@@ -80047,11 +88132,11 @@ SQLITE_API int sqlite3_stmt_scanstatus(
pScan = &p->aScan[idx];
switch( iScanStatusOp ){
case SQLITE_SCANSTAT_NLOOP: {
- *(sqlite3_int64*)pOut = p->anExec[pScan->addrLoop];
+ *(tdsqlite3_int64*)pOut = p->anExec[pScan->addrLoop];
break;
}
case SQLITE_SCANSTAT_NVISIT: {
- *(sqlite3_int64*)pOut = p->anExec[pScan->addrVisit];
+ *(tdsqlite3_int64*)pOut = p->anExec[pScan->addrVisit];
break;
}
case SQLITE_SCANSTAT_EST: {
@@ -80061,7 +88146,7 @@ SQLITE_API int sqlite3_stmt_scanstatus(
x += 10;
r *= 0.5;
}
- *(double*)pOut = r*sqlite3LogEstToInt(x);
+ *(double*)pOut = r*tdsqlite3LogEstToInt(x);
break;
}
case SQLITE_SCANSTAT_NAME: {
@@ -80092,9 +88177,9 @@ SQLITE_API int sqlite3_stmt_scanstatus(
}
/*
-** Zero all counters associated with the sqlite3_stmt_scanstatus() data.
+** Zero all counters associated with the tdsqlite3_stmt_scanstatus() data.
*/
-SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){
+SQLITE_API void tdsqlite3_stmt_scanstatus_reset(tdsqlite3_stmt *pStmt){
Vdbe *p = (Vdbe*)pStmt;
memset(p->anExec, 0, p->nOp * sizeof(i64));
}
@@ -80115,7 +88200,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){
*************************************************************************
**
** This file contains code used to insert the values of host parameters
-** (aka "wildcards") into the SQL text output by sqlite3_trace().
+** (aka "wildcards") into the SQL text output by tdsqlite3_trace().
**
** The Vdbe parse-tree explainer is also found here.
*/
@@ -80137,7 +88222,7 @@ static int findNextHostParameter(const char *zSql, int *pnToken){
*pnToken = 0;
while( zSql[0] ){
- n = sqlite3GetToken((u8*)zSql, &tokenType);
+ n = tdsqlite3GetToken((u8*)zSql, &tokenType);
assert( n>0 && tokenType!=TK_ILLEGAL );
if( tokenType==TK_VARIABLE ){
*pnToken = n;
@@ -80151,7 +88236,7 @@ static int findNextHostParameter(const char *zSql, int *pnToken){
/*
** This function returns a pointer to a nul-terminated string in memory
-** obtained from sqlite3DbMalloc(). If sqlite3.nVdbeExec is 1, then the
+** obtained from tdsqlite3DbMalloc(). If sqlite3.nVdbeExec is 1, then the
** string contains a copy of zRawSql but with host parameters expanded to
** their current bindings. Or, if sqlite3.nVdbeExec is greater than 1,
** then the returned string holds a copy of zRawSql with "-- " prepended
@@ -80173,11 +88258,11 @@ static int findNextHostParameter(const char *zSql, int *pnToken){
** parameter index is known, locate the value in p->aVar[]. Then render
** the value as a literal in place of the host parameter name.
*/
-SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
+SQLITE_PRIVATE char *tdsqlite3VdbeExpandSql(
Vdbe *p, /* The prepared statement being evaluated */
const char *zRawSql /* Raw text of the SQL statement */
){
- sqlite3 *db; /* The database connection */
+ tdsqlite3 *db; /* The database connection */
int idx = 0; /* Index of a host parameter */
int nextIndex = 1; /* Index of next ? host parameter */
int n; /* Length of a token prefix */
@@ -80186,35 +88271,35 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
Mem *pVar; /* Value of a host parameter */
StrAccum out; /* Accumulate the output here */
#ifndef SQLITE_OMIT_UTF16
- Mem utf8; /* Used to convert UTF16 parameters into UTF8 for display */
+ Mem utf8; /* Used to convert UTF16 into UTF8 for display */
#endif
char zBase[100]; /* Initial working space */
db = p->db;
- sqlite3StrAccumInit(&out, 0, zBase, sizeof(zBase),
+ tdsqlite3StrAccumInit(&out, 0, zBase, sizeof(zBase),
db->aLimit[SQLITE_LIMIT_LENGTH]);
if( db->nVdbeExec>1 ){
while( *zRawSql ){
const char *zStart = zRawSql;
while( *(zRawSql++)!='\n' && *zRawSql );
- sqlite3StrAccumAppend(&out, "-- ", 3);
+ tdsqlite3_str_append(&out, "-- ", 3);
assert( (zRawSql - zStart) > 0 );
- sqlite3StrAccumAppend(&out, zStart, (int)(zRawSql-zStart));
+ tdsqlite3_str_append(&out, zStart, (int)(zRawSql-zStart));
}
}else if( p->nVar==0 ){
- sqlite3StrAccumAppend(&out, zRawSql, sqlite3Strlen30(zRawSql));
+ tdsqlite3_str_append(&out, zRawSql, tdsqlite3Strlen30(zRawSql));
}else{
while( zRawSql[0] ){
n = findNextHostParameter(zRawSql, &nToken);
assert( n>0 );
- sqlite3StrAccumAppend(&out, zRawSql, n);
+ tdsqlite3_str_append(&out, zRawSql, n);
zRawSql += n;
assert( zRawSql[0] || nToken==0 );
if( nToken==0 ) break;
if( zRawSql[0]=='?' ){
if( nToken>1 ){
- assert( sqlite3Isdigit(zRawSql[1]) );
- sqlite3GetInt32(&zRawSql[1], &idx);
+ assert( tdsqlite3Isdigit(zRawSql[1]) );
+ tdsqlite3GetInt32(&zRawSql[1], &idx);
}else{
idx = nextIndex;
}
@@ -80225,7 +88310,7 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
testcase( zRawSql[0]=='$' );
testcase( zRawSql[0]=='@' );
testcase( zRawSql[0]=='#' );
- idx = sqlite3VdbeParameterIndex(p, zRawSql, nToken);
+ idx = tdsqlite3VdbeParameterIndex(p, zRawSql, nToken);
assert( idx>0 );
}
zRawSql += nToken;
@@ -80233,11 +88318,11 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
assert( idx>0 && idx<=p->nVar );
pVar = &p->aVar[idx-1];
if( pVar->flags & MEM_Null ){
- sqlite3StrAccumAppend(&out, "NULL", 4);
- }else if( pVar->flags & MEM_Int ){
- sqlite3XPrintf(&out, "%lld", pVar->u.i);
+ tdsqlite3_str_append(&out, "NULL", 4);
+ }else if( pVar->flags & (MEM_Int|MEM_IntReal) ){
+ tdsqlite3_str_appendf(&out, "%lld", pVar->u.i);
}else if( pVar->flags & MEM_Real ){
- sqlite3XPrintf(&out, "%!.15g", pVar->u.r);
+ tdsqlite3_str_appendf(&out, "%!.15g", pVar->u.r);
}else if( pVar->flags & MEM_Str ){
int nOut; /* Number of bytes of the string text to include in output */
#ifndef SQLITE_OMIT_UTF16
@@ -80245,9 +88330,9 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
if( enc!=SQLITE_UTF8 ){
memset(&utf8, 0, sizeof(utf8));
utf8.db = db;
- sqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC);
- if( SQLITE_NOMEM==sqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8) ){
- out.accError = STRACCUM_NOMEM;
+ tdsqlite3VdbeMemSetStr(&utf8, pVar->z, pVar->n, enc, SQLITE_STATIC);
+ if( SQLITE_NOMEM==tdsqlite3VdbeChangeEncoding(&utf8, SQLITE_UTF8) ){
+ out.accError = SQLITE_NOMEM;
out.nAlloc = 0;
}
pVar = &utf8;
@@ -80260,39 +88345,39 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
while( nOut<pVar->n && (pVar->z[nOut]&0xc0)==0x80 ){ nOut++; }
}
#endif
- sqlite3XPrintf(&out, "'%.*q'", nOut, pVar->z);
+ tdsqlite3_str_appendf(&out, "'%.*q'", nOut, pVar->z);
#ifdef SQLITE_TRACE_SIZE_LIMIT
if( nOut<pVar->n ){
- sqlite3XPrintf(&out, "/*+%d bytes*/", pVar->n-nOut);
+ tdsqlite3_str_appendf(&out, "/*+%d bytes*/", pVar->n-nOut);
}
#endif
#ifndef SQLITE_OMIT_UTF16
- if( enc!=SQLITE_UTF8 ) sqlite3VdbeMemRelease(&utf8);
+ if( enc!=SQLITE_UTF8 ) tdsqlite3VdbeMemRelease(&utf8);
#endif
}else if( pVar->flags & MEM_Zero ){
- sqlite3XPrintf(&out, "zeroblob(%d)", pVar->u.nZero);
+ tdsqlite3_str_appendf(&out, "zeroblob(%d)", pVar->u.nZero);
}else{
int nOut; /* Number of bytes of the blob to include in output */
assert( pVar->flags & MEM_Blob );
- sqlite3StrAccumAppend(&out, "x'", 2);
+ tdsqlite3_str_append(&out, "x'", 2);
nOut = pVar->n;
#ifdef SQLITE_TRACE_SIZE_LIMIT
if( nOut>SQLITE_TRACE_SIZE_LIMIT ) nOut = SQLITE_TRACE_SIZE_LIMIT;
#endif
for(i=0; i<nOut; i++){
- sqlite3XPrintf(&out, "%02x", pVar->z[i]&0xff);
+ tdsqlite3_str_appendf(&out, "%02x", pVar->z[i]&0xff);
}
- sqlite3StrAccumAppend(&out, "'", 1);
+ tdsqlite3_str_append(&out, "'", 1);
#ifdef SQLITE_TRACE_SIZE_LIMIT
if( nOut<pVar->n ){
- sqlite3XPrintf(&out, "/*+%d bytes*/", pVar->n-nOut);
+ tdsqlite3_str_appendf(&out, "/*+%d bytes*/", pVar->n-nOut);
}
#endif
}
}
}
- if( out.accError ) sqlite3StrAccumReset(&out);
- return sqlite3StrAccumFinish(&out);
+ if( out.accError ) tdsqlite3_str_reset(&out);
+ return tdsqlite3StrAccumFinish(&out);
}
#endif /* #ifndef SQLITE_OMIT_TRACE */
@@ -80332,7 +88417,7 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
** like that ever happens.
*/
#ifdef SQLITE_DEBUG
-# define memAboutToChange(P,M) sqlite3VdbeMemAboutToChange(P,M)
+# define memAboutToChange(P,M) tdsqlite3VdbeMemAboutToChange(P,M)
#else
# define memAboutToChange(P,M)
#endif
@@ -80345,19 +88430,19 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(
** help verify the correct operation of the library.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_search_count = 0;
+SQLITE_API int tdsqlite3_search_count = 0;
#endif
/*
** When this global variable is positive, it gets decremented once before
** each instruction in the VDBE. When it reaches zero, the u1.isInterrupted
-** field of the sqlite3 structure is set in order to simulate an interrupt.
+** field of the tdsqlite3 structure is set in order to simulate an interrupt.
**
** This facility is used for testing purposes only. It does not function
** in an ordinary build.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_interrupt_count = 0;
+SQLITE_API int tdsqlite3_interrupt_count = 0;
#endif
/*
@@ -80368,7 +88453,7 @@ SQLITE_API int sqlite3_interrupt_count = 0;
** library.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_sort_count = 0;
+SQLITE_API int tdsqlite3_sort_count = 0;
#endif
/*
@@ -80379,10 +88464,10 @@ SQLITE_API int sqlite3_sort_count = 0;
** help verify the correct operation of the library.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_max_blobsize = 0;
+SQLITE_API int tdsqlite3_max_blobsize = 0;
static void updateMaxBlobsize(Mem *p){
- if( (p->flags & (MEM_Str|MEM_Blob))!=0 && p->n>sqlite3_max_blobsize ){
- sqlite3_max_blobsize = p->n;
+ if( (p->flags & (MEM_Str|MEM_Blob))!=0 && p->n>tdsqlite3_max_blobsize ){
+ tdsqlite3_max_blobsize = p->n;
}
}
#endif
@@ -80405,62 +88490,118 @@ static void updateMaxBlobsize(Mem *p){
** library.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_found_count = 0;
+SQLITE_API int tdsqlite3_found_count = 0;
#endif
/*
** Test a register to see if it exceeds the current maximum blob size.
** If it does, record the new maximum blob size.
*/
-#if defined(SQLITE_TEST) && !defined(SQLITE_OMIT_BUILTIN_TEST)
+#if defined(SQLITE_TEST) && !defined(SQLITE_UNTESTABLE)
# define UPDATE_MAX_BLOBSIZE(P) updateMaxBlobsize(P)
#else
# define UPDATE_MAX_BLOBSIZE(P)
#endif
+#ifdef SQLITE_DEBUG
+/* This routine provides a convenient place to set a breakpoint during
+** tracing with PRAGMA vdbe_trace=on. The breakpoint fires right after
+** each opcode is printed. Variables "pc" (program counter) and pOp are
+** available to add conditionals to the breakpoint. GDB example:
+**
+** break test_trace_breakpoint if pc=22
+**
+** Other useful labels for breakpoints include:
+** test_addop_breakpoint(pc,pOp)
+** tdsqlite3CorruptError(lineno)
+** tdsqlite3MisuseError(lineno)
+** tdsqlite3CantopenError(lineno)
+*/
+static void test_trace_breakpoint(int pc, Op *pOp, Vdbe *v){
+ static int n = 0;
+ n++;
+}
+#endif
+
/*
** Invoke the VDBE coverage callback, if that callback is defined. This
** feature is used for test suite validation only and does not appear an
** production builds.
**
-** M is an integer, 2 or 3, that indices how many different ways the
-** branch can go. It is usually 2. "I" is the direction the branch
-** goes. 0 means falls through. 1 means branch is taken. 2 means the
-** second alternative branch is taken.
+** M is the type of branch. I is the direction taken for this instance of
+** the branch.
+**
+** M: 2 - two-way branch (I=0: fall-thru 1: jump )
+** 3 - two-way + NULL (I=0: fall-thru 1: jump 2: NULL )
+** 4 - OP_Jump (I=0: jump p1 1: jump p2 2: jump p3)
+**
+** In other words, if M is 2, then I is either 0 (for fall-through) or
+** 1 (for when the branch is taken). If M is 3, the I is 0 for an
+** ordinary fall-through, I is 1 if the branch was taken, and I is 2
+** if the result of comparison is NULL. For M=3, I=2 the jump may or
+** may not be taken, depending on the SQLITE_JUMPIFNULL flags in p5.
+** When M is 4, that means that an OP_Jump is being run. I is 0, 1, or 2
+** depending on if the operands are less than, equal, or greater than.
**
** iSrcLine is the source code line (from the __LINE__ macro) that
-** generated the VDBE instruction. This instrumentation assumes that all
-** source code is in a single file (the amalgamation). Special values 1
-** and 2 for the iSrcLine parameter mean that this particular branch is
-** always taken or never taken, respectively.
+** generated the VDBE instruction combined with flag bits. The source
+** code line number is in the lower 24 bits of iSrcLine and the upper
+** 8 bytes are flags. The lower three bits of the flags indicate
+** values for I that should never occur. For example, if the branch is
+** always taken, the flags should be 0x05 since the fall-through and
+** alternate branch are never taken. If a branch is never taken then
+** flags should be 0x06 since only the fall-through approach is allowed.
+**
+** Bit 0x08 of the flags indicates an OP_Jump opcode that is only
+** interested in equal or not-equal. In other words, I==0 and I==2
+** should be treated as equivalent
+**
+** Since only a line number is retained, not the filename, this macro
+** only works for amalgamation builds. But that is ok, since these macros
+** should be no-ops except for special builds used to measure test coverage.
*/
#if !defined(SQLITE_VDBE_COVERAGE)
# define VdbeBranchTaken(I,M)
#else
# define VdbeBranchTaken(I,M) vdbeTakeBranch(pOp->iSrcLine,I,M)
- static void vdbeTakeBranch(int iSrcLine, u8 I, u8 M){
- if( iSrcLine<=2 && ALWAYS(iSrcLine>0) ){
- M = iSrcLine;
- /* Assert the truth of VdbeCoverageAlwaysTaken() and
- ** VdbeCoverageNeverTaken() */
- assert( (M & I)==I );
- }else{
- if( sqlite3GlobalConfig.xVdbeBranch==0 ) return; /*NO_TEST*/
- sqlite3GlobalConfig.xVdbeBranch(sqlite3GlobalConfig.pVdbeBranchArg,
- iSrcLine,I,M);
+ static void vdbeTakeBranch(u32 iSrcLine, u8 I, u8 M){
+ u8 mNever;
+ assert( I<=2 ); /* 0: fall through, 1: taken, 2: alternate taken */
+ assert( M<=4 ); /* 2: two-way branch, 3: three-way branch, 4: OP_Jump */
+ assert( I<M ); /* I can only be 2 if M is 3 or 4 */
+ /* Transform I from a integer [0,1,2] into a bitmask of [1,2,4] */
+ I = 1<<I;
+ /* The upper 8 bits of iSrcLine are flags. The lower three bits of
+ ** the flags indicate directions that the branch can never go. If
+ ** a branch really does go in one of those directions, assert right
+ ** away. */
+ mNever = iSrcLine >> 24;
+ assert( (I & mNever)==0 );
+ if( tdsqlite3GlobalConfig.xVdbeBranch==0 ) return; /*NO_TEST*/
+ /* Invoke the branch coverage callback with three arguments:
+ ** iSrcLine - the line number of the VdbeCoverage() macro, with
+ ** flags removed.
+ ** I - Mask of bits 0x07 indicating which cases are are
+ ** fulfilled by this instance of the jump. 0x01 means
+ ** fall-thru, 0x02 means taken, 0x04 means NULL. Any
+ ** impossible cases (ex: if the comparison is never NULL)
+ ** are filled in automatically so that the coverage
+ ** measurement logic does not flag those impossible cases
+ ** as missed coverage.
+ ** M - Type of jump. Same as M argument above
+ */
+ I |= mNever;
+ if( M==2 ) I |= 0x04;
+ if( M==4 ){
+ I |= 0x08;
+ if( (mNever&0x08)!=0 && (I&0x05)!=0) I |= 0x05; /*NO_TEST*/
}
+ tdsqlite3GlobalConfig.xVdbeBranch(tdsqlite3GlobalConfig.pVdbeBranchArg,
+ iSrcLine&0xffffff, I, M);
}
#endif
/*
-** Convert the given register into a string if it isn't one
-** already. Return non-zero if a malloc() fails.
-*/
-#define Stringify(P, enc) \
- if(((P)->flags&(MEM_Str|MEM_Blob))==0 && sqlite3VdbeMemStringify(P,enc,0)) \
- { goto no_mem; }
-
-/*
** An ephemeral string value (signified by the MEM_Ephem flag) contains
** a pointer to a dynamically allocated string where some other entity
** is responsible for deallocating that string. Because the register
@@ -80473,7 +88614,7 @@ SQLITE_API int sqlite3_found_count = 0;
*/
#define Deephemeralize(P) \
if( ((P)->flags&MEM_Ephem)!=0 \
- && sqlite3VdbeMemMakeWriteable(P) ){ goto no_mem;}
+ && tdsqlite3VdbeMemMakeWriteable(P) ){ goto no_mem;}
/* Return true if the cursor was opened using the OP_OpenSorter opcode. */
#define isSorter(x) ((x)->eCurType==CURTYPE_SORTER)
@@ -80500,7 +88641,7 @@ static VdbeCursor *allocateCursor(
** allocations.
**
** * When using ENABLE_MEMORY_MANAGEMENT, memory cell buffers can
- ** be freed lazily via the sqlite3_release_memory() API. This
+ ** be freed lazily via the tdsqlite3_release_memory() API. This
** minimizes the number of malloc calls made by the system.
**
** The memory cell for cursor 0 is aMem[0]. The rest are allocated from
@@ -80513,16 +88654,21 @@ static VdbeCursor *allocateCursor(
VdbeCursor *pCx = 0;
nByte =
ROUND8(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField +
- (eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0);
+ (eCurType==CURTYPE_BTREE?tdsqlite3BtreeCursorSize():0);
assert( iCur>=0 && iCur<p->nCursor );
if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/
- sqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
+ /* Before calling tdsqlite3VdbeFreeCursor(), ensure the isEphemeral flag
+ ** is clear. Otherwise, if this is an ephemeral cursor created by
+ ** OP_OpenDup, the cursor will not be closed and will still be part
+ ** of a BtShared.pCursor list. */
+ if( p->apCsr[iCur]->pBtx==0 ) p->apCsr[iCur]->isEphemeral = 0;
+ tdsqlite3VdbeFreeCursor(p, p->apCsr[iCur]);
p->apCsr[iCur] = 0;
}
- if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){
+ if( SQLITE_OK==tdsqlite3VdbeMemClearAndResize(pMem, nByte) ){
p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z;
- memset(pCx, 0, sizeof(VdbeCursor));
+ memset(pCx, 0, offsetof(VdbeCursor,pAltCursor));
pCx->eCurType = eCurType;
pCx->iDb = iDb;
pCx->nField = nField;
@@ -80530,13 +88676,28 @@ static VdbeCursor *allocateCursor(
if( eCurType==CURTYPE_BTREE ){
pCx->uc.pCursor = (BtCursor*)
&pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField];
- sqlite3BtreeCursorZero(pCx->uc.pCursor);
+ tdsqlite3BtreeCursorZero(pCx->uc.pCursor);
}
}
return pCx;
}
/*
+** The string in pRec is known to look like an integer and to have a
+** floating point value of rValue. Return true and set *piValue to the
+** integer value if the string is in range to be an integer. Otherwise,
+** return false.
+*/
+static int alsoAnInt(Mem *pRec, double rValue, i64 *piValue){
+ i64 iValue = (double)rValue;
+ if( tdsqlite3RealSameAsInt(rValue,iValue) ){
+ *piValue = iValue;
+ return 1;
+ }
+ return 0==tdsqlite3Atoi64(pRec->z, piValue, pRec->n, pRec->enc);
+}
+
+/*
** Try to convert a value into a numeric representation if we can
** do so without loss of information. In other words, if the string
** looks like a number, convert it into a number. If it does not
@@ -80553,18 +88714,23 @@ static VdbeCursor *allocateCursor(
*/
static void applyNumericAffinity(Mem *pRec, int bTryForInt){
double rValue;
- i64 iValue;
u8 enc = pRec->enc;
- assert( (pRec->flags & (MEM_Str|MEM_Int|MEM_Real))==MEM_Str );
- if( sqlite3AtoF(pRec->z, &rValue, pRec->n, enc)==0 ) return;
- if( 0==sqlite3Atoi64(pRec->z, &iValue, pRec->n, enc) ){
- pRec->u.i = iValue;
+ int rc;
+ assert( (pRec->flags & (MEM_Str|MEM_Int|MEM_Real|MEM_IntReal))==MEM_Str );
+ rc = tdsqlite3AtoF(pRec->z, &rValue, pRec->n, enc);
+ if( rc<=0 ) return;
+ if( rc==1 && alsoAnInt(pRec, rValue, &pRec->u.i) ){
pRec->flags |= MEM_Int;
}else{
pRec->u.r = rValue;
pRec->flags |= MEM_Real;
- if( bTryForInt ) sqlite3VdbeIntegerAffinity(pRec);
+ if( bTryForInt ) tdsqlite3VdbeIntegerAffinity(pRec);
}
+ /* TEXT->NUMERIC is many->one. Hence, it is important to invalidate the
+ ** string representation after computing a numeric equivalent, because the
+ ** string representation might not be the canonical representation for the
+ ** numeric value. Ticket [343634942dd54ab57b7024] 2018-01-31. */
+ pRec->flags &= ~MEM_Str;
}
/*
@@ -80583,6 +88749,7 @@ static void applyNumericAffinity(Mem *pRec, int bTryForInt){
** Convert pRec to a text representation.
**
** SQLITE_AFF_BLOB:
+** SQLITE_AFF_NONE:
** No-op. pRec is unchanged.
*/
static void applyAffinity(
@@ -80597,7 +88764,7 @@ static void applyAffinity(
if( (pRec->flags & MEM_Real)==0 ){
if( pRec->flags & MEM_Str ) applyNumericAffinity(pRec,1);
}else{
- sqlite3VdbeIntegerAffinity(pRec);
+ tdsqlite3VdbeIntegerAffinity(pRec);
}
}
}else if( affinity==SQLITE_AFF_TEXT ){
@@ -80607,11 +88774,14 @@ static void applyAffinity(
** there is already a string rep, but it is pointless to waste those
** CPU cycles. */
if( 0==(pRec->flags&MEM_Str) ){ /*OPTIMIZATION-IF-FALSE*/
- if( (pRec->flags&(MEM_Real|MEM_Int)) ){
- sqlite3VdbeMemStringify(pRec, enc, 1);
+ if( (pRec->flags&(MEM_Real|MEM_Int|MEM_IntReal)) ){
+ testcase( pRec->flags & MEM_Int );
+ testcase( pRec->flags & MEM_Real );
+ testcase( pRec->flags & MEM_IntReal );
+ tdsqlite3VdbeMemStringify(pRec, enc, 1);
}
}
- pRec->flags &= ~(MEM_Real|MEM_Int);
+ pRec->flags &= ~(MEM_Real|MEM_Int|MEM_IntReal);
}
}
@@ -80621,22 +88791,22 @@ static void applyAffinity(
** is appropriate. But only do the conversion if it is possible without
** loss of information and return the revised type of the argument.
*/
-SQLITE_API int sqlite3_value_numeric_type(sqlite3_value *pVal){
- int eType = sqlite3_value_type(pVal);
+SQLITE_API int tdsqlite3_value_numeric_type(tdsqlite3_value *pVal){
+ int eType = tdsqlite3_value_type(pVal);
if( eType==SQLITE_TEXT ){
Mem *pMem = (Mem*)pVal;
applyNumericAffinity(pMem, 0);
- eType = sqlite3_value_type(pVal);
+ eType = tdsqlite3_value_type(pVal);
}
return eType;
}
/*
-** Exported version of applyAffinity(). This one works on sqlite3_value*,
+** Exported version of applyAffinity(). This one works on tdsqlite3_value*,
** not the internal Mem* type.
*/
-SQLITE_PRIVATE void sqlite3ValueApplyAffinity(
- sqlite3_value *pVal,
+SQLITE_PRIVATE void tdsqlite3ValueApplyAffinity(
+ tdsqlite3_value *pVal,
u8 affinity,
u8 enc
){
@@ -80650,12 +88820,21 @@ SQLITE_PRIVATE void sqlite3ValueApplyAffinity(
** accordingly.
*/
static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){
- assert( (pMem->flags & (MEM_Int|MEM_Real))==0 );
+ int rc;
+ tdsqlite3_int64 ix;
+ assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 );
assert( (pMem->flags & (MEM_Str|MEM_Blob))!=0 );
- if( sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc)==0 ){
- return 0;
- }
- if( sqlite3Atoi64(pMem->z, &pMem->u.i, pMem->n, pMem->enc)==SQLITE_OK ){
+ ExpandBlob(pMem);
+ rc = tdsqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc);
+ if( rc<=0 ){
+ if( rc==0 && tdsqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1 ){
+ pMem->u.i = ix;
+ return MEM_Int;
+ }else{
+ return MEM_Real;
+ }
+ }else if( rc==1 && tdsqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)==0 ){
+ pMem->u.i = ix;
return MEM_Int;
}
return MEM_Real;
@@ -80669,10 +88848,15 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){
** But it does set pMem->u.r and pMem->u.i appropriately.
*/
static u16 numericType(Mem *pMem){
- if( pMem->flags & (MEM_Int|MEM_Real) ){
- return pMem->flags & (MEM_Int|MEM_Real);
+ if( pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal) ){
+ testcase( pMem->flags & MEM_Int );
+ testcase( pMem->flags & MEM_Real );
+ testcase( pMem->flags & MEM_IntReal );
+ return pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal);
}
if( pMem->flags & (MEM_Str|MEM_Blob) ){
+ testcase( pMem->flags & MEM_Str );
+ testcase( pMem->flags & MEM_Blob );
return computeNumericType(pMem);
}
return 0;
@@ -80683,12 +88867,9 @@ static u16 numericType(Mem *pMem){
** Write a nice string representation of the contents of cell pMem
** into buffer zBuf, length nBuf.
*/
-SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf){
- char *zCsr = zBuf;
+SQLITE_PRIVATE void tdsqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr){
int f = pMem->flags;
-
static const char *const encnames[] = {"(X)", "(8)", "(16LE)", "(16BE)"};
-
if( f&MEM_Blob ){
int i;
char c;
@@ -80704,59 +88885,40 @@ SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf){
}else{
c = 's';
}
-
- sqlite3_snprintf(100, zCsr, "%c", c);
- zCsr += sqlite3Strlen30(zCsr);
- sqlite3_snprintf(100, zCsr, "%d[", pMem->n);
- zCsr += sqlite3Strlen30(zCsr);
- for(i=0; i<16 && i<pMem->n; i++){
- sqlite3_snprintf(100, zCsr, "%02X", ((int)pMem->z[i] & 0xFF));
- zCsr += sqlite3Strlen30(zCsr);
+ tdsqlite3_str_appendf(pStr, "%cx[", c);
+ for(i=0; i<25 && i<pMem->n; i++){
+ tdsqlite3_str_appendf(pStr, "%02X", ((int)pMem->z[i] & 0xFF));
}
- for(i=0; i<16 && i<pMem->n; i++){
+ tdsqlite3_str_appendf(pStr, "|");
+ for(i=0; i<25 && i<pMem->n; i++){
char z = pMem->z[i];
- if( z<32 || z>126 ) *zCsr++ = '.';
- else *zCsr++ = z;
+ tdsqlite3_str_appendchar(pStr, 1, (z<32||z>126)?'.':z);
}
-
- sqlite3_snprintf(100, zCsr, "]%s", encnames[pMem->enc]);
- zCsr += sqlite3Strlen30(zCsr);
+ tdsqlite3_str_appendf(pStr,"]");
if( f & MEM_Zero ){
- sqlite3_snprintf(100, zCsr,"+%dz",pMem->u.nZero);
- zCsr += sqlite3Strlen30(zCsr);
+ tdsqlite3_str_appendf(pStr, "+%dz",pMem->u.nZero);
}
- *zCsr = '\0';
}else if( f & MEM_Str ){
- int j, k;
- zBuf[0] = ' ';
+ int j;
+ u8 c;
if( f & MEM_Dyn ){
- zBuf[1] = 'z';
+ c = 'z';
assert( (f & (MEM_Static|MEM_Ephem))==0 );
}else if( f & MEM_Static ){
- zBuf[1] = 't';
+ c = 't';
assert( (f & (MEM_Dyn|MEM_Ephem))==0 );
}else if( f & MEM_Ephem ){
- zBuf[1] = 'e';
+ c = 'e';
assert( (f & (MEM_Static|MEM_Dyn))==0 );
}else{
- zBuf[1] = 's';
+ c = 's';
}
- k = 2;
- sqlite3_snprintf(100, &zBuf[k], "%d", pMem->n);
- k += sqlite3Strlen30(&zBuf[k]);
- zBuf[k++] = '[';
- for(j=0; j<15 && j<pMem->n; j++){
- u8 c = pMem->z[j];
- if( c>=0x20 && c<0x7f ){
- zBuf[k++] = c;
- }else{
- zBuf[k++] = '.';
- }
+ tdsqlite3_str_appendf(pStr, " %c%d[", c, pMem->n);
+ for(j=0; j<25 && j<pMem->n; j++){
+ c = pMem->z[j];
+ tdsqlite3_str_appendchar(pStr, 1, (c>=0x20&&c<=0x7f) ? c : '.');
}
- zBuf[k++] = ']';
- sqlite3_snprintf(100,&zBuf[k], encnames[pMem->enc]);
- k += sqlite3Strlen30(&zBuf[k]);
- zBuf[k++] = 0;
+ tdsqlite3_str_appendf(pStr, "]%s", encnames[pMem->enc]);
}
}
#endif
@@ -80769,32 +88931,52 @@ static void memTracePrint(Mem *p){
if( p->flags & MEM_Undefined ){
printf(" undefined");
}else if( p->flags & MEM_Null ){
- printf(" NULL");
+ printf(p->flags & MEM_Zero ? " NULL-nochng" : " NULL");
}else if( (p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str) ){
printf(" si:%lld", p->u.i);
+ }else if( (p->flags & (MEM_IntReal))!=0 ){
+ printf(" ir:%lld", p->u.i);
}else if( p->flags & MEM_Int ){
printf(" i:%lld", p->u.i);
#ifndef SQLITE_OMIT_FLOATING_POINT
}else if( p->flags & MEM_Real ){
- printf(" r:%g", p->u.r);
+ printf(" r:%.17g", p->u.r);
#endif
- }else if( p->flags & MEM_RowSet ){
+ }else if( tdsqlite3VdbeMemIsRowSet(p) ){
printf(" (rowset)");
}else{
- char zBuf[200];
- sqlite3VdbeMemPrettyPrint(p, zBuf);
- printf(" %s", zBuf);
+ StrAccum acc;
+ char zBuf[1000];
+ tdsqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
+ tdsqlite3VdbeMemPrettyPrint(p, &acc);
+ printf(" %s", tdsqlite3StrAccumFinish(&acc));
}
if( p->flags & MEM_Subtype ) printf(" subtype=0x%02x", p->eSubtype);
}
static void registerTrace(int iReg, Mem *p){
- printf("REG[%d] = ", iReg);
+ printf("R[%d] = ", iReg);
memTracePrint(p);
+ if( p->pScopyFrom ){
+ printf(" <== R[%d]", (int)(p->pScopyFrom - &p[-iReg]));
+ }
printf("\n");
+ tdsqlite3VdbeCheckMemInvariants(p);
}
#endif
#ifdef SQLITE_DEBUG
+/*
+** Show the values of all registers in the virtual machine. Used for
+** interactive debugging.
+*/
+SQLITE_PRIVATE void tdsqlite3VdbeRegisterDump(Vdbe *v){
+ int i;
+ for(i=1; i<v->nMem; i++) registerTrace(i, v->aMem+i);
+}
+#endif /* SQLITE_DEBUG */
+
+
+#ifdef SQLITE_DEBUG
# define REGISTER_TRACE(R,M) if(db->flags&SQLITE_VdbeTrace)registerTrace(R,M)
#else
# define REGISTER_TRACE(R,M)
@@ -80822,7 +89004,7 @@ static void registerTrace(int iReg, Mem *p){
******************************************************************************
**
** This file contains inline asm code for retrieving "high-performance"
-** counters for x86 class CPUs.
+** counters for x86 and x86_64 class CPUs.
*/
#ifndef SQLITE_HWTIME_H
#define SQLITE_HWTIME_H
@@ -80833,12 +89015,13 @@ static void registerTrace(int iReg, Mem *p){
** processor and returns that value. This can be used for high-res
** profiling.
*/
-#if (defined(__GNUC__) || defined(_MSC_VER)) && \
- (defined(i386) || defined(__i386__) || defined(_M_IX86))
+#if !defined(__STRICT_ANSI__) && \
+ (defined(__GNUC__) || defined(_MSC_VER)) && \
+ (defined(i386) || defined(__i386__) || defined(_M_IX86))
#if defined(__GNUC__)
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned int lo, hi;
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return (sqlite_uint64)hi << 32 | lo;
@@ -80846,7 +89029,7 @@ static void registerTrace(int iReg, Mem *p){
#elif defined(_MSC_VER)
- __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){
+ __declspec(naked) __inline sqlite_uint64 __cdecl tdsqlite3Hwtime(void){
__asm {
rdtsc
ret ; return value at EDX:EAX
@@ -80855,17 +89038,17 @@ static void registerTrace(int iReg, Mem *p){
#endif
-#elif (defined(__GNUC__) && defined(__x86_64__))
+#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__))
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned long val;
__asm__ __volatile__ ("rdtsc" : "=A" (val));
return val;
}
-#elif (defined(__GNUC__) && defined(__ppc__))
+#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__))
- __inline__ sqlite_uint64 sqlite3Hwtime(void){
+ __inline__ sqlite_uint64 tdsqlite3Hwtime(void){
unsigned long long retval;
unsigned long junk;
__asm__ __volatile__ ("\n\
@@ -80880,16 +89063,15 @@ static void registerTrace(int iReg, Mem *p){
#else
- #error Need implementation of sqlite3Hwtime() for your platform.
-
/*
- ** To compile without implementing sqlite3Hwtime() for your platform,
- ** you can remove the above #error and use the following
- ** stub function. You will lose timing support for many
- ** of the debugging and testing utilities, but it should at
- ** least compile and run.
+ ** asm() is needed for hardware timing support. Without asm(),
+ ** disable the tdsqlite3Hwtime() routine.
+ **
+ ** tdsqlite3Hwtime() is only used for some obscure debugging
+ ** and analysis configurations, not in any deliverable, so this
+ ** should not be a great loss.
*/
-SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); }
+SQLITE_PRIVATE sqlite_uint64 tdsqlite3Hwtime(void){ return ((sqlite_uint64)0); }
#endif
@@ -80911,7 +89093,7 @@ SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); }
**
** assert( checkSavepointCount(db) );
*/
-static int checkSavepointCount(sqlite3 *db){
+static int checkSavepointCount(tdsqlite3 *db){
int n = 0;
Savepoint *p;
for(p=db->pSavepoint; p; p=p->pNext) n++;
@@ -80925,7 +89107,7 @@ static int checkSavepointCount(sqlite3 *db){
** overwritten with an integer value.
*/
static SQLITE_NOINLINE Mem *out2PrereleaseWithClear(Mem *pOut){
- sqlite3VdbeMemSetNull(pOut);
+ tdsqlite3VdbeMemSetNull(pOut);
pOut->flags = MEM_Int;
return pOut;
}
@@ -80946,9 +89128,9 @@ static Mem *out2Prerelease(Vdbe *p, VdbeOp *pOp){
/*
** Execute as much of a VDBE program as we can.
-** This is the core of sqlite3_step().
+** This is the core of tdsqlite3_step().
*/
-SQLITE_PRIVATE int sqlite3VdbeExec(
+SQLITE_PRIVATE int tdsqlite3VdbeExec(
Vdbe *p /* The VDBE */
){
Op *aOp = p->aOp; /* Copy of p->aOp */
@@ -80960,61 +89142,60 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
int nExtraDelete = 0; /* Verifies FORDELETE and AUXDELETE flags */
#endif
int rc = SQLITE_OK; /* Value to return */
- sqlite3 *db = p->db; /* The database */
+ tdsqlite3 *db = p->db; /* The database */
u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */
u8 encoding = ENC(db); /* The database encoding */
int iCompare = 0; /* Result of last comparison */
unsigned nVmStep = 0; /* Number of virtual machine steps */
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
- unsigned nProgressLimit = 0;/* Invoke xProgress() when nVmStep reaches this */
+ unsigned nProgressLimit; /* Invoke xProgress() when nVmStep reaches this */
#endif
Mem *aMem = p->aMem; /* Copy of p->aMem */
Mem *pIn1 = 0; /* 1st input operand */
Mem *pIn2 = 0; /* 2nd input operand */
Mem *pIn3 = 0; /* 3rd input operand */
Mem *pOut = 0; /* Output operand */
- int *aPermute = 0; /* Permutation of columns for OP_Compare */
- i64 lastRowid = db->lastRowid; /* Saved value of the last insert ROWID */
#ifdef VDBE_PROFILE
u64 start; /* CPU clock count at start of opcode */
#endif
/*** INSERT STACK UNION HERE ***/
- assert( p->magic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */
- sqlite3VdbeEnter(p);
+ assert( p->magic==VDBE_MAGIC_RUN ); /* tdsqlite3_step() verifies this */
+ tdsqlite3VdbeEnter(p);
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ if( db->xProgress ){
+ u32 iPrior = p->aCounter[SQLITE_STMTSTATUS_VM_STEP];
+ assert( 0 < db->nProgressOps );
+ nProgressLimit = db->nProgressOps - (iPrior % db->nProgressOps);
+ }else{
+ nProgressLimit = 0xffffffff;
+ }
+#endif
if( p->rc==SQLITE_NOMEM ){
- /* This happens if a malloc() inside a call to sqlite3_column_text() or
- ** sqlite3_column_text16() failed. */
+ /* This happens if a malloc() inside a call to tdsqlite3_column_text() or
+ ** tdsqlite3_column_text16() failed. */
goto no_mem;
}
assert( p->rc==SQLITE_OK || (p->rc&0xff)==SQLITE_BUSY );
assert( p->bIsReader || p->readOnly!=0 );
- p->rc = SQLITE_OK;
p->iCurrentTime = 0;
assert( p->explain==0 );
p->pResultSet = 0;
db->busyHandler.nBusy = 0;
if( db->u1.isInterrupted ) goto abort_due_to_interrupt;
- sqlite3VdbeIOTraceSql(p);
-#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
- if( db->xProgress ){
- u32 iPrior = p->aCounter[SQLITE_STMTSTATUS_VM_STEP];
- assert( 0 < db->nProgressOps );
- nProgressLimit = db->nProgressOps - (iPrior % db->nProgressOps);
- }
-#endif
+ tdsqlite3VdbeIOTraceSql(p);
#ifdef SQLITE_DEBUG
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
if( p->pc==0
&& (p->db->flags & (SQLITE_VdbeListing|SQLITE_VdbeEQP|SQLITE_VdbeTrace))!=0
){
int i;
int once = 1;
- sqlite3VdbePrintSql(p);
+ tdsqlite3VdbePrintSql(p);
if( p->db->flags & SQLITE_VdbeListing ){
printf("VDBE Program Listing:\n");
for(i=0; i<p->nOp; i++){
- sqlite3VdbePrintOp(stdout, i, &aOp[i]);
+ tdsqlite3VdbePrintOp(stdout, i, &aOp[i]);
}
}
if( p->db->flags & SQLITE_VdbeEQP ){
@@ -81028,7 +89209,7 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
}
if( p->db->flags & SQLITE_VdbeTrace ) printf("VDBE Trace:\n");
}
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
#endif
for(pOp=&aOp[p->pc]; 1; pOp++){
/* Errors are detected by individual opcodes, with an immediate
@@ -81037,7 +89218,7 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
assert( pOp>=aOp && pOp<&aOp[p->nOp]);
#ifdef VDBE_PROFILE
- start = sqlite3Hwtime();
+ start = tdsqlite3NProfileCnt ? tdsqlite3NProfileCnt : tdsqlite3Hwtime();
#endif
nVmStep++;
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
@@ -81048,7 +89229,8 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
*/
#ifdef SQLITE_DEBUG
if( db->flags & SQLITE_VdbeTrace ){
- sqlite3VdbePrintOp(stdout, (int)(pOp - aOp), pOp);
+ tdsqlite3VdbePrintOp(stdout, (int)(pOp - aOp), pOp);
+ test_trace_breakpoint((int)(pOp - aOp),pOp,p);
}
#endif
@@ -81057,10 +89239,10 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
** if we have a special test build.
*/
#ifdef SQLITE_TEST
- if( sqlite3_interrupt_count>0 ){
- sqlite3_interrupt_count--;
- if( sqlite3_interrupt_count==0 ){
- sqlite3_interrupt(db);
+ if( tdsqlite3_interrupt_count>0 ){
+ tdsqlite3_interrupt_count--;
+ if( tdsqlite3_interrupt_count==0 ){
+ tdsqlite3_interrupt(db);
}
}
#endif
@@ -81068,26 +89250,26 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
/* Sanity checking on other operands */
#ifdef SQLITE_DEBUG
{
- u8 opProperty = sqlite3OpcodeProperty[pOp->opcode];
+ u8 opProperty = tdsqlite3OpcodeProperty[pOp->opcode];
if( (opProperty & OPFLG_IN1)!=0 ){
assert( pOp->p1>0 );
assert( pOp->p1<=(p->nMem+1 - p->nCursor) );
assert( memIsValid(&aMem[pOp->p1]) );
- assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p1]) );
+ assert( tdsqlite3VdbeCheckMemInvariants(&aMem[pOp->p1]) );
REGISTER_TRACE(pOp->p1, &aMem[pOp->p1]);
}
if( (opProperty & OPFLG_IN2)!=0 ){
assert( pOp->p2>0 );
assert( pOp->p2<=(p->nMem+1 - p->nCursor) );
assert( memIsValid(&aMem[pOp->p2]) );
- assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p2]) );
+ assert( tdsqlite3VdbeCheckMemInvariants(&aMem[pOp->p2]) );
REGISTER_TRACE(pOp->p2, &aMem[pOp->p2]);
}
if( (opProperty & OPFLG_IN3)!=0 ){
assert( pOp->p3>0 );
assert( pOp->p3<=(p->nMem+1 - p->nCursor) );
assert( memIsValid(&aMem[pOp->p3]) );
- assert( sqlite3VdbeCheckMemInvariants(&aMem[pOp->p3]) );
+ assert( tdsqlite3VdbeCheckMemInvariants(&aMem[pOp->p3]) );
REGISTER_TRACE(pOp->p3, &aMem[pOp->p3]);
}
if( (opProperty & OPFLG_OUT2)!=0 ){
@@ -81156,32 +89338,47 @@ SQLITE_PRIVATE int sqlite3VdbeExec(
** to the current line should be indented for EXPLAIN output.
*/
case OP_Goto: { /* jump */
+
+#ifdef SQLITE_DEBUG
+ /* In debuggging mode, when the p5 flags is set on an OP_Goto, that
+ ** means we should really jump back to the preceeding OP_ReleaseReg
+ ** instruction. */
+ if( pOp->p5 ){
+ assert( pOp->p2 < (int)(pOp - aOp) );
+ assert( pOp->p2 > 1 );
+ pOp = &aOp[pOp->p2 - 2];
+ assert( pOp[1].opcode==OP_ReleaseReg );
+ goto check_for_interrupt;
+ }
+#endif
+
jump_to_p2_and_check_for_interrupt:
pOp = &aOp[pOp->p2 - 1];
/* Opcodes that are used as the bottom of a loop (OP_Next, OP_Prev,
- ** OP_VNext, OP_RowSetNext, or OP_SorterNext) all jump here upon
- ** completion. Check to see if sqlite3_interrupt() has been called
+ ** OP_VNext, or OP_SorterNext) all jump here upon
+ ** completion. Check to see if tdsqlite3_interrupt() has been called
** or if the progress callback needs to be invoked.
**
** This code uses unstructured "goto" statements and does not look clean.
** But that is not due to sloppy coding habits. The code is written this
** way for performance, to avoid having to run the interrupt and progress
- ** checks on every opcode. This helps sqlite3_step() to run about 1.5%
+ ** checks on every opcode. This helps tdsqlite3_step() to run about 1.5%
** faster according to "valgrind --tool=cachegrind" */
check_for_interrupt:
if( db->u1.isInterrupted ) goto abort_due_to_interrupt;
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
/* Call the progress callback if it is configured and the required number
** of VDBE ops have been executed (either since this invocation of
- ** sqlite3VdbeExec() or since last time the progress callback was called).
+ ** tdsqlite3VdbeExec() or since last time the progress callback was called).
** If the progress callback returns non-zero, exit the virtual machine with
** a return code SQLITE_ABORT.
*/
- if( db->xProgress!=0 && nVmStep>=nProgressLimit ){
+ while( nVmStep>=nProgressLimit && db->xProgress!=0 ){
assert( db->nProgressOps!=0 );
- nProgressLimit = nVmStep + db->nProgressOps - (nVmStep%db->nProgressOps);
+ nProgressLimit += db->nProgressOps;
if( db->xProgress(db->pProgressArg) ){
+ nProgressLimit = 0xffffffff;
rc = SQLITE_INTERRUPT;
goto abort_due_to_error;
}
@@ -81304,6 +89501,9 @@ case OP_Yield: { /* in1, jump */
*/
case OP_HaltIfNull: { /* in3 */
pIn3 = &aMem[pOp->p3];
+#ifdef SQLITE_DEBUG
+ if( pOp->p2==OE_Abort ){ tdsqlite3VdbeAssertAbortable(p); }
+#endif
if( (pIn3->flags & MEM_Null)==0 ) break;
/* Fall through into OP_Halt */
}
@@ -81313,8 +89513,8 @@ case OP_HaltIfNull: { /* in3 */
** Exit immediately. All open cursors, etc are closed
** automatically.
**
-** P1 is the result code returned by sqlite3_exec(), sqlite3_reset(),
-** or sqlite3_finalize(). For a normal halt, this should be SQLITE_OK (0).
+** P1 is the result code returned by tdsqlite3_exec(), tdsqlite3_reset(),
+** or tdsqlite3_finalize(). For a normal halt, this should be SQLITE_OK (0).
** For errors, it can be some other value. If P1!=0 then P2 will determine
** whether or not to rollback the current transaction. Do not rollback
** if P2==OE_Fail. Do the rollback if P2==OE_Rollback. If P2==OE_Abort,
@@ -81343,14 +89543,16 @@ case OP_Halt: {
int pcx;
pcx = (int)(pOp - aOp);
+#ifdef SQLITE_DEBUG
+ if( pOp->p2==OE_Abort ){ tdsqlite3VdbeAssertAbortable(p); }
+#endif
if( pOp->p1==SQLITE_OK && p->pFrame ){
/* Halt the sub-program. Return control to the parent frame. */
pFrame = p->pFrame;
p->pFrame = pFrame->pParent;
p->nFrame--;
- sqlite3VdbeSetChanges(db, p->nChange);
- pcx = sqlite3VdbeFrameRestore(pFrame);
- lastRowid = db->lastRowid;
+ tdsqlite3VdbeSetChanges(db, p->nChange);
+ pcx = tdsqlite3VdbeFrameRestore(pFrame);
if( pOp->p2==OE_Ignore ){
/* Instruction pcx is the OP_Program that invoked the sub-program
** currently being halted. If the p2 instruction of this OP_Halt
@@ -81367,7 +89569,7 @@ case OP_Halt: {
p->rc = pOp->p1;
p->errorAction = (u8)pOp->p2;
p->pc = pcx;
- assert( pOp->p5>=0 && pOp->p5<=4 );
+ assert( pOp->p5<=4 );
if( p->rc ){
if( pOp->p5 ){
static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK",
@@ -81376,16 +89578,16 @@ case OP_Halt: {
testcase( pOp->p5==2 );
testcase( pOp->p5==3 );
testcase( pOp->p5==4 );
- sqlite3VdbeError(p, "%s constraint failed", azType[pOp->p5-1]);
+ tdsqlite3VdbeError(p, "%s constraint failed", azType[pOp->p5-1]);
if( pOp->p4.z ){
- p->zErrMsg = sqlite3MPrintf(db, "%z: %s", p->zErrMsg, pOp->p4.z);
+ p->zErrMsg = tdsqlite3MPrintf(db, "%z: %s", p->zErrMsg, pOp->p4.z);
}
}else{
- sqlite3VdbeError(p, "%s", pOp->p4.z);
+ tdsqlite3VdbeError(p, "%s", pOp->p4.z);
}
- sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg);
+ tdsqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg);
}
- rc = sqlite3VdbeHalt(p);
+ rc = tdsqlite3VdbeHalt(p);
assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR );
if( rc==SQLITE_BUSY ){
p->rc = SQLITE_BUSY;
@@ -81431,7 +89633,7 @@ case OP_Int64: { /* out2 */
case OP_Real: { /* same as TK_FLOAT, out2 */
pOut = out2Prerelease(p, pOp);
pOut->flags = MEM_Real;
- assert( !sqlite3IsNaN(*pOp->p4.pReal) );
+ assert( !tdsqlite3IsNaN(*pOp->p4.pReal) );
pOut->u.r = *pOp->p4.pReal;
break;
}
@@ -81448,30 +89650,30 @@ case OP_Real: { /* same as TK_FLOAT, out2 */
case OP_String8: { /* same as TK_STRING, out2 */
assert( pOp->p4.z!=0 );
pOut = out2Prerelease(p, pOp);
- pOp->opcode = OP_String;
- pOp->p1 = sqlite3Strlen30(pOp->p4.z);
+ pOp->p1 = tdsqlite3Strlen30(pOp->p4.z);
#ifndef SQLITE_OMIT_UTF16
if( encoding!=SQLITE_UTF8 ){
- rc = sqlite3VdbeMemSetStr(pOut, pOp->p4.z, -1, SQLITE_UTF8, SQLITE_STATIC);
+ rc = tdsqlite3VdbeMemSetStr(pOut, pOp->p4.z, -1, SQLITE_UTF8, SQLITE_STATIC);
assert( rc==SQLITE_OK || rc==SQLITE_TOOBIG );
- if( SQLITE_OK!=sqlite3VdbeChangeEncoding(pOut, encoding) ) goto no_mem;
+ if( rc ) goto too_big;
+ if( SQLITE_OK!=tdsqlite3VdbeChangeEncoding(pOut, encoding) ) goto no_mem;
assert( pOut->szMalloc>0 && pOut->zMalloc==pOut->z );
assert( VdbeMemDynamic(pOut)==0 );
pOut->szMalloc = 0;
pOut->flags |= MEM_Static;
if( pOp->p4type==P4_DYNAMIC ){
- sqlite3DbFree(db, pOp->p4.z);
+ tdsqlite3DbFree(db, pOp->p4.z);
}
pOp->p4type = P4_DYNAMIC;
pOp->p4.z = pOut->z;
pOp->p1 = pOut->n;
}
- testcase( rc==SQLITE_TOOBIG );
#endif
if( pOp->p1>db->aLimit[SQLITE_LIMIT_LENGTH] ){
goto too_big;
}
+ pOp->opcode = OP_String;
assert( rc==SQLITE_OK );
/* Fall through to the next case, OP_String */
}
@@ -81527,10 +89729,13 @@ case OP_Null: { /* out2 */
assert( pOp->p3<=(p->nMem+1 - p->nCursor) );
pOut->flags = nullFlag = pOp->p1 ? (MEM_Null|MEM_Cleared) : MEM_Null;
pOut->n = 0;
+#ifdef SQLITE_DEBUG
+ pOut->uTemp = 0;
+#endif
while( cnt>0 ){
pOut++;
memAboutToChange(p, pOut);
- sqlite3VdbeMemSetNull(pOut);
+ tdsqlite3VdbeMemSetNull(pOut);
pOut->flags = nullFlag;
pOut->n = 0;
cnt--;
@@ -81549,7 +89754,7 @@ case OP_Null: { /* out2 */
case OP_SoftNull: {
assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) );
pOut = &aMem[pOp->p1];
- pOut->flags = (pOut->flags|MEM_Null)&~MEM_Undefined;
+ pOut->flags = (pOut->flags&~(MEM_Undefined|MEM_AffMask))|MEM_Null;
break;
}
@@ -81562,7 +89767,7 @@ case OP_SoftNull: {
case OP_Blob: { /* out2 */
assert( pOp->p1 <= SQLITE_MAX_LENGTH );
pOut = out2Prerelease(p, pOp);
- sqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0);
+ tdsqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0);
pOut->enc = encoding;
UPDATE_MAX_BLOBSIZE(pOut);
break;
@@ -81574,19 +89779,22 @@ case OP_Blob: { /* out2 */
** Transfer the values of bound parameter P1 into register P2
**
** If the parameter is named, then its name appears in P4.
-** The P4 value is used by sqlite3_bind_parameter_name().
+** The P4 value is used by tdsqlite3_bind_parameter_name().
*/
case OP_Variable: { /* out2 */
Mem *pVar; /* Value being transferred */
assert( pOp->p1>0 && pOp->p1<=p->nVar );
- assert( pOp->p4.z==0 || pOp->p4.z==p->azVar[pOp->p1-1] );
+ assert( pOp->p4.z==0 || pOp->p4.z==tdsqlite3VListNumToName(p->pVList,pOp->p1) );
pVar = &p->aVar[pOp->p1 - 1];
- if( sqlite3VdbeMemTooBig(pVar) ){
+ if( tdsqlite3VdbeMemTooBig(pVar) ){
goto too_big;
}
- pOut = out2Prerelease(p, pOp);
- sqlite3VdbeMemShallowCopy(pOut, pVar, MEM_Static);
+ pOut = &aMem[pOp->p2];
+ if( VdbeMemDynamic(pOut) ) tdsqlite3VdbeMemSetNull(pOut);
+ memcpy(pOut, pVar, MEMCELLSIZE);
+ pOut->flags &= ~(MEM_Dyn|MEM_Ephem);
+ pOut->flags |= MEM_Static|MEM_FromBind;
UPDATE_MAX_BLOBSIZE(pOut);
break;
}
@@ -81618,10 +89826,15 @@ case OP_Move: {
assert( pIn1<=&aMem[(p->nMem+1 - p->nCursor)] );
assert( memIsValid(pIn1) );
memAboutToChange(p, pOut);
- sqlite3VdbeMemMove(pOut, pIn1);
+ tdsqlite3VdbeMemMove(pOut, pIn1);
#ifdef SQLITE_DEBUG
- if( pOut->pScopyFrom>=&aMem[p1] && pOut->pScopyFrom<pOut ){
- pOut->pScopyFrom += pOp->p2 - p1;
+ pIn1->pScopyFrom = 0;
+ { int i;
+ for(i=1; i<p->nMem; i++){
+ if( aMem[i].pScopyFrom==pIn1 ){
+ aMem[i].pScopyFrom = pOut;
+ }
+ }
}
#endif
Deephemeralize(pOut);
@@ -81648,7 +89861,8 @@ case OP_Copy: {
pOut = &aMem[pOp->p2];
assert( pOut!=pIn1 );
while( 1 ){
- sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem);
+ memAboutToChange(p, pOut);
+ tdsqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem);
Deephemeralize(pOut);
#ifdef SQLITE_DEBUG
pOut->pScopyFrom = 0;
@@ -81678,9 +89892,10 @@ case OP_SCopy: { /* out2 */
pIn1 = &aMem[pOp->p1];
pOut = &aMem[pOp->p2];
assert( pOut!=pIn1 );
- sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem);
+ tdsqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem);
#ifdef SQLITE_DEBUG
- if( pOut->pScopyFrom==0 ) pOut->pScopyFrom = pIn1;
+ pOut->pScopyFrom = pIn1;
+ pOut->mScopyFlags = pIn1->flags;
#endif
break;
}
@@ -81697,7 +89912,7 @@ case OP_IntCopy: { /* out2 */
pIn1 = &aMem[pOp->p1];
assert( (pIn1->flags & MEM_Int)!=0 );
pOut = &aMem[pOp->p2];
- sqlite3VdbeMemSetInt64(pOut, pIn1->u.i);
+ tdsqlite3VdbeMemSetInt64(pOut, pIn1->u.i);
break;
}
@@ -81705,8 +89920,8 @@ case OP_IntCopy: { /* out2 */
** Synopsis: output=r[P1@P2]
**
** The registers P1 through P1+P2-1 contain a single row of
-** results. This opcode causes the sqlite3_step() call to terminate
-** with an SQLITE_ROW return code and it sets up the sqlite3_stmt
+** results. This opcode causes the tdsqlite3_step() call to terminate
+** with an SQLITE_ROW return code and it sets up the tdsqlite3_stmt
** structure to provide access to the r(P1)..r(P1+P2-1) values as
** the result row.
*/
@@ -81717,22 +89932,10 @@ case OP_ResultRow: {
assert( pOp->p1>0 );
assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 );
-#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
- /* Run the progress counter just before returning.
- */
- if( db->xProgress!=0
- && nVmStep>=nProgressLimit
- && db->xProgress(db->pProgressArg)!=0
- ){
- rc = SQLITE_INTERRUPT;
- goto abort_due_to_error;
- }
-#endif
-
/* If this statement has violated immediate foreign key constraints, do
** not return the number of rows modified. And do not RELEASE the statement
** transaction. It needs to be rolled back. */
- if( SQLITE_OK!=(rc = sqlite3VdbeCheckFk(p, 0)) ){
+ if( SQLITE_OK!=(rc = tdsqlite3VdbeCheckFk(p, 0)) ){
assert( db->flags&SQLITE_CountRows );
assert( p->usesStmtJournal );
goto abort_due_to_error;
@@ -81754,7 +89957,7 @@ case OP_ResultRow: {
** the RELEASE call below can never fail.
*/
assert( p->iStatement==0 || db->flags&SQLITE_CountRows );
- rc = sqlite3VdbeCloseStatement(p, SAVEPOINT_RELEASE);
+ rc = tdsqlite3VdbeCloseStatement(p, SAVEPOINT_RELEASE);
assert( rc==SQLITE_OK );
/* Invalidate all ephemeral cursor row caches */
@@ -81770,8 +89973,16 @@ case OP_ResultRow: {
Deephemeralize(&pMem[i]);
assert( (pMem[i].flags & MEM_Ephem)==0
|| (pMem[i].flags & (MEM_Str|MEM_Blob))==0 );
- sqlite3VdbeMemNulTerminate(&pMem[i]);
+ tdsqlite3VdbeMemNulTerminate(&pMem[i]);
REGISTER_TRACE(pOp->p1+i, &pMem[i]);
+#ifdef SQLITE_DEBUG
+ /* The registers in the result will not be used again when the
+ ** prepared statement restarts. This is because tdsqlite3_column()
+ ** APIs might have caused type conversions of made other changes to
+ ** the register values. Therefore, we can go ahead and break any
+ ** OP_SCopy dependencies. */
+ pMem[i].pScopyFrom = 0;
+#endif
}
if( db->mallocFailed ) goto no_mem;
@@ -81779,6 +89990,7 @@ case OP_ResultRow: {
db->xTrace(SQLITE_TRACE_ROW, db->pTraceArg, p, 0);
}
+
/* Return SQLITE_ROW
*/
p->pc = (int)(pOp - aOp) + 1;
@@ -81800,33 +90012,57 @@ case OP_ResultRow: {
** to avoid a memcpy().
*/
case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */
- i64 nByte;
+ i64 nByte; /* Total size of the output string or blob */
+ u16 flags1; /* Initial flags for P1 */
+ u16 flags2; /* Initial flags for P2 */
pIn1 = &aMem[pOp->p1];
pIn2 = &aMem[pOp->p2];
pOut = &aMem[pOp->p3];
+ testcase( pIn1==pIn2 );
+ testcase( pOut==pIn2 );
assert( pIn1!=pOut );
- if( (pIn1->flags | pIn2->flags) & MEM_Null ){
- sqlite3VdbeMemSetNull(pOut);
+ flags1 = pIn1->flags;
+ testcase( flags1 & MEM_Null );
+ testcase( pIn2->flags & MEM_Null );
+ if( (flags1 | pIn2->flags) & MEM_Null ){
+ tdsqlite3VdbeMemSetNull(pOut);
break;
}
- if( ExpandBlob(pIn1) || ExpandBlob(pIn2) ) goto no_mem;
- Stringify(pIn1, encoding);
- Stringify(pIn2, encoding);
+ if( (flags1 & (MEM_Str|MEM_Blob))==0 ){
+ if( tdsqlite3VdbeMemStringify(pIn1,encoding,0) ) goto no_mem;
+ flags1 = pIn1->flags & ~MEM_Str;
+ }else if( (flags1 & MEM_Zero)!=0 ){
+ if( tdsqlite3VdbeMemExpandBlob(pIn1) ) goto no_mem;
+ flags1 = pIn1->flags & ~MEM_Str;
+ }
+ flags2 = pIn2->flags;
+ if( (flags2 & (MEM_Str|MEM_Blob))==0 ){
+ if( tdsqlite3VdbeMemStringify(pIn2,encoding,0) ) goto no_mem;
+ flags2 = pIn2->flags & ~MEM_Str;
+ }else if( (flags2 & MEM_Zero)!=0 ){
+ if( tdsqlite3VdbeMemExpandBlob(pIn2) ) goto no_mem;
+ flags2 = pIn2->flags & ~MEM_Str;
+ }
nByte = pIn1->n + pIn2->n;
if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){
goto too_big;
}
- if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){
+ if( tdsqlite3VdbeMemGrow(pOut, (int)nByte+3, pOut==pIn2) ){
goto no_mem;
}
MemSetTypeFlag(pOut, MEM_Str);
if( pOut!=pIn2 ){
memcpy(pOut->z, pIn2->z, pIn2->n);
+ assert( (pIn2->flags & MEM_Dyn) == (flags2 & MEM_Dyn) );
+ pIn2->flags = flags2;
}
memcpy(&pOut->z[pIn2->n], pIn1->z, pIn1->n);
+ assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) );
+ pIn1->flags = flags1;
pOut->z[nByte]=0;
pOut->z[nByte+1] = 0;
+ pOut->z[nByte+2] = 0;
pOut->flags |= MEM_Term;
pOut->n = (int)nByte;
pOut->enc = encoding;
@@ -81877,7 +90113,6 @@ case OP_Subtract: /* same as TK_MINUS, in1, in2, out3 */
case OP_Multiply: /* same as TK_STAR, in1, in2, out3 */
case OP_Divide: /* same as TK_SLASH, in1, in2, out3 */
case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */
- char bIntint; /* Started out as two integer operands */
u16 flags; /* Combined MEM_* flags from both inputs */
u16 type1; /* Numeric type of left operand */
u16 type2; /* Numeric type of right operand */
@@ -81892,15 +90127,13 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */
type2 = numericType(pIn2);
pOut = &aMem[pOp->p3];
flags = pIn1->flags | pIn2->flags;
- if( (flags & MEM_Null)!=0 ) goto arithmetic_result_is_null;
if( (type1 & type2 & MEM_Int)!=0 ){
iA = pIn1->u.i;
iB = pIn2->u.i;
- bIntint = 1;
switch( pOp->opcode ){
- case OP_Add: if( sqlite3AddInt64(&iB,iA) ) goto fp_math; break;
- case OP_Subtract: if( sqlite3SubInt64(&iB,iA) ) goto fp_math; break;
- case OP_Multiply: if( sqlite3MulInt64(&iB,iA) ) goto fp_math; break;
+ case OP_Add: if( tdsqlite3AddInt64(&iB,iA) ) goto fp_math; break;
+ case OP_Subtract: if( tdsqlite3SubInt64(&iB,iA) ) goto fp_math; break;
+ case OP_Multiply: if( tdsqlite3MulInt64(&iB,iA) ) goto fp_math; break;
case OP_Divide: {
if( iA==0 ) goto arithmetic_result_is_null;
if( iA==-1 && iB==SMALLEST_INT64 ) goto fp_math;
@@ -81916,11 +90149,12 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */
}
pOut->u.i = iB;
MemSetTypeFlag(pOut, MEM_Int);
+ }else if( (flags & MEM_Null)!=0 ){
+ goto arithmetic_result_is_null;
}else{
- bIntint = 0;
fp_math:
- rA = sqlite3VdbeRealValue(pIn1);
- rB = sqlite3VdbeRealValue(pIn2);
+ rA = tdsqlite3VdbeRealValue(pIn1);
+ rB = tdsqlite3VdbeRealValue(pIn2);
switch( pOp->opcode ){
case OP_Add: rB += rA; break;
case OP_Subtract: rB -= rA; break;
@@ -81932,8 +90166,8 @@ fp_math:
break;
}
default: {
- iA = (i64)rA;
- iB = (i64)rB;
+ iA = tdsqlite3VdbeIntValue(pIn1);
+ iB = tdsqlite3VdbeIntValue(pIn2);
if( iA==0 ) goto arithmetic_result_is_null;
if( iA==-1 ) iA = 1;
rB = (double)(iB % iA);
@@ -81944,27 +90178,24 @@ fp_math:
pOut->u.i = rB;
MemSetTypeFlag(pOut, MEM_Int);
#else
- if( sqlite3IsNaN(rB) ){
+ if( tdsqlite3IsNaN(rB) ){
goto arithmetic_result_is_null;
}
pOut->u.r = rB;
MemSetTypeFlag(pOut, MEM_Real);
- if( ((type1|type2)&MEM_Real)==0 && !bIntint ){
- sqlite3VdbeIntegerAffinity(pOut);
- }
#endif
}
break;
arithmetic_result_is_null:
- sqlite3VdbeMemSetNull(pOut);
+ tdsqlite3VdbeMemSetNull(pOut);
break;
}
/* Opcode: CollSeq P1 * * P4
**
-** P4 is a pointer to a CollSeq struct. If the next call to a user function
-** or aggregate calls sqlite3GetFuncCollSeq(), this collation sequence will
+** P4 is a pointer to a CollSeq object. If the next call to a user function
+** or aggregate calls tdsqlite3GetFuncCollSeq(), this collation sequence will
** be returned. This is used by the built-in min(), max() and nullif()
** functions.
**
@@ -81979,121 +90210,8 @@ arithmetic_result_is_null:
case OP_CollSeq: {
assert( pOp->p4type==P4_COLLSEQ );
if( pOp->p1 ){
- sqlite3VdbeMemSetInt64(&aMem[pOp->p1], 0);
- }
- break;
-}
-
-/* Opcode: Function0 P1 P2 P3 P4 P5
-** Synopsis: r[P3]=func(r[P2@P5])
-**
-** Invoke a user function (P4 is a pointer to a FuncDef object that
-** defines the function) with P5 arguments taken from register P2 and
-** successors. The result of the function is stored in register P3.
-** Register P3 must not be one of the function inputs.
-**
-** P1 is a 32-bit bitmask indicating whether or not each argument to the
-** function was determined to be constant at compile time. If the first
-** argument was constant then bit 0 of P1 is set. This is used to determine
-** whether meta data associated with a user function argument using the
-** sqlite3_set_auxdata() API may be safely retained until the next
-** invocation of this opcode.
-**
-** See also: Function, AggStep, AggFinal
-*/
-/* Opcode: Function P1 P2 P3 P4 P5
-** Synopsis: r[P3]=func(r[P2@P5])
-**
-** Invoke a user function (P4 is a pointer to an sqlite3_context object that
-** contains a pointer to the function to be run) with P5 arguments taken
-** from register P2 and successors. The result of the function is stored
-** in register P3. Register P3 must not be one of the function inputs.
-**
-** P1 is a 32-bit bitmask indicating whether or not each argument to the
-** function was determined to be constant at compile time. If the first
-** argument was constant then bit 0 of P1 is set. This is used to determine
-** whether meta data associated with a user function argument using the
-** sqlite3_set_auxdata() API may be safely retained until the next
-** invocation of this opcode.
-**
-** SQL functions are initially coded as OP_Function0 with P4 pointing
-** to a FuncDef object. But on first evaluation, the P4 operand is
-** automatically converted into an sqlite3_context object and the operation
-** changed to this OP_Function opcode. In this way, the initialization of
-** the sqlite3_context object occurs only once, rather than once for each
-** evaluation of the function.
-**
-** See also: Function0, AggStep, AggFinal
-*/
-case OP_Function0: {
- int n;
- sqlite3_context *pCtx;
-
- assert( pOp->p4type==P4_FUNCDEF );
- n = pOp->p5;
- assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
- assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) );
- assert( pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n );
- pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*));
- if( pCtx==0 ) goto no_mem;
- pCtx->pOut = 0;
- pCtx->pFunc = pOp->p4.pFunc;
- pCtx->iOp = (int)(pOp - aOp);
- pCtx->pVdbe = p;
- pCtx->argc = n;
- pOp->p4type = P4_FUNCCTX;
- pOp->p4.pCtx = pCtx;
- pOp->opcode = OP_Function;
- /* Fall through into OP_Function */
-}
-case OP_Function: {
- int i;
- sqlite3_context *pCtx;
-
- assert( pOp->p4type==P4_FUNCCTX );
- pCtx = pOp->p4.pCtx;
-
- /* If this function is inside of a trigger, the register array in aMem[]
- ** might change from one evaluation to the next. The next block of code
- ** checks to see if the register array has changed, and if so it
- ** reinitializes the relavant parts of the sqlite3_context object */
- pOut = &aMem[pOp->p3];
- if( pCtx->pOut != pOut ){
- pCtx->pOut = pOut;
- for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i];
- }
-
- memAboutToChange(p, pCtx->pOut);
-#ifdef SQLITE_DEBUG
- for(i=0; i<pCtx->argc; i++){
- assert( memIsValid(pCtx->argv[i]) );
- REGISTER_TRACE(pOp->p2+i, pCtx->argv[i]);
- }
-#endif
- MemSetTypeFlag(pCtx->pOut, MEM_Null);
- pCtx->fErrorOrAux = 0;
- db->lastRowid = lastRowid;
- (*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
- lastRowid = db->lastRowid; /* Remember rowid changes made by xSFunc */
-
- /* If the function returned an error, throw an exception */
- if( pCtx->fErrorOrAux ){
- if( pCtx->isError ){
- sqlite3VdbeError(p, "%s", sqlite3_value_text(pCtx->pOut));
- rc = pCtx->isError;
- }
- sqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1);
- if( rc ) goto abort_due_to_error;
- }
-
- /* Copy the result of the function into register P3 */
- if( pOut->flags & (MEM_Str|MEM_Blob) ){
- sqlite3VdbeChangeEncoding(pCtx->pOut, encoding);
- if( sqlite3VdbeMemTooBig(pCtx->pOut) ) goto too_big;
+ tdsqlite3VdbeMemSetInt64(&aMem[pOp->p1], 0);
}
-
- REGISTER_TRACE(pOp->p3, pCtx->pOut);
- UPDATE_MAX_BLOBSIZE(pCtx->pOut);
break;
}
@@ -82140,11 +90258,11 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */
pIn2 = &aMem[pOp->p2];
pOut = &aMem[pOp->p3];
if( (pIn1->flags | pIn2->flags) & MEM_Null ){
- sqlite3VdbeMemSetNull(pOut);
+ tdsqlite3VdbeMemSetNull(pOut);
break;
}
- iA = sqlite3VdbeIntValue(pIn2);
- iB = sqlite3VdbeIntValue(pIn1);
+ iA = tdsqlite3VdbeIntValue(pIn2);
+ iB = tdsqlite3VdbeIntValue(pIn1);
op = pOp->opcode;
if( op==OP_BitAnd ){
iA &= iB;
@@ -82190,7 +90308,7 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */
case OP_AddImm: { /* in1 */
pIn1 = &aMem[pOp->p1];
memAboutToChange(p, pIn1);
- sqlite3VdbeMemIntegerify(pIn1);
+ tdsqlite3VdbeMemIntegerify(pIn1);
pIn1->u.i += pOp->p2;
break;
}
@@ -82206,8 +90324,8 @@ case OP_MustBeInt: { /* jump, in1 */
pIn1 = &aMem[pOp->p1];
if( (pIn1->flags & MEM_Int)==0 ){
applyAffinity(pIn1, SQLITE_AFF_NUMERIC, encoding);
- VdbeBranchTaken((pIn1->flags&MEM_Int)==0, 2);
if( (pIn1->flags & MEM_Int)==0 ){
+ VdbeBranchTaken(1, 2);
if( pOp->p2==0 ){
rc = SQLITE_MISMATCH;
goto abort_due_to_error;
@@ -82216,6 +90334,7 @@ case OP_MustBeInt: { /* jump, in1 */
}
}
}
+ VdbeBranchTaken(0, 2);
MemSetTypeFlag(pIn1, MEM_Int);
break;
}
@@ -82232,8 +90351,11 @@ case OP_MustBeInt: { /* jump, in1 */
*/
case OP_RealAffinity: { /* in1 */
pIn1 = &aMem[pOp->p1];
- if( pIn1->flags & MEM_Int ){
- sqlite3VdbeMemRealify(pIn1);
+ if( pIn1->flags & (MEM_Int|MEM_IntReal) ){
+ testcase( pIn1->flags & MEM_Int );
+ testcase( pIn1->flags & MEM_IntReal );
+ tdsqlite3VdbeMemRealify(pIn1);
+ REGISTER_TRACE(pOp->p1, pIn1);
}
break;
}
@@ -82246,11 +90368,11 @@ case OP_RealAffinity: { /* in1 */
** Force the value in register P1 to be the type defined by P2.
**
** <ul>
-** <li value="97"> TEXT
-** <li value="98"> BLOB
-** <li value="99"> NUMERIC
-** <li value="100"> INTEGER
-** <li value="101"> REAL
+** <li> P2=='A' &rarr; BLOB
+** <li> P2=='B' &rarr; TEXT
+** <li> P2=='C' &rarr; NUMERIC
+** <li> P2=='D' &rarr; INTEGER
+** <li> P2=='E' &rarr; REAL
** </ul>
**
** A NULL value is not changed by this routine. It remains NULL.
@@ -82265,9 +90387,11 @@ case OP_Cast: { /* in1 */
pIn1 = &aMem[pOp->p1];
memAboutToChange(p, pIn1);
rc = ExpandBlob(pIn1);
- sqlite3VdbeMemCast(pIn1, pOp->p2, encoding);
- UPDATE_MAX_BLOBSIZE(pIn1);
if( rc ) goto abort_due_to_error;
+ rc = tdsqlite3VdbeMemCast(pIn1, pOp->p2, encoding);
+ if( rc ) goto abort_due_to_error;
+ UPDATE_MAX_BLOBSIZE(pIn1);
+ REGISTER_TRACE(pOp->p1, pIn1);
break;
}
#endif /* SQLITE_OMIT_CAST */
@@ -82390,16 +90514,15 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
** OP_Eq or OP_Ne) then take the jump or not depending on whether
** or not both operands are null.
*/
- assert( pOp->opcode==OP_Eq || pOp->opcode==OP_Ne );
assert( (flags1 & MEM_Cleared)==0 );
- assert( (pOp->p5 & SQLITE_JUMPIFNULL)==0 );
- if( (flags1&MEM_Null)!=0
- && (flags3&MEM_Null)!=0
+ assert( (pOp->p5 & SQLITE_JUMPIFNULL)==0 || CORRUPT_DB );
+ testcase( (pOp->p5 & SQLITE_JUMPIFNULL)!=0 );
+ if( (flags1&flags3&MEM_Null)!=0
&& (flags3&MEM_Cleared)==0
){
res = 0; /* Operands are equal */
}else{
- res = 1; /* Operands are not equal */
+ res = ((flags3 & MEM_Null) ? -1 : +1); /* Operands are not equal */
}
}else{
/* SQLITE_NULLEQ is clear and at least one operand is NULL,
@@ -82425,17 +90548,17 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
affinity = pOp->p5 & SQLITE_AFF_MASK;
if( affinity>=SQLITE_AFF_NUMERIC ){
if( (flags1 | flags3)&MEM_Str ){
- if( (flags1 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){
+ if( (flags1 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){
applyNumericAffinity(pIn1,0);
- testcase( flags3!=pIn3->flags ); /* Possible if pIn1==pIn3 */
+ testcase( flags3!=pIn3->flags );
flags3 = pIn3->flags;
}
- if( (flags3 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){
+ if( (flags3 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){
applyNumericAffinity(pIn3,0);
}
}
/* Handle the common case of integer comparison here, as an
- ** optimization, to avoid a call to sqlite3MemCompare() */
+ ** optimization, to avoid a call to tdsqlite3MemCompare() */
if( (pIn1->flags & pIn3->flags & MEM_Int)!=0 ){
if( pIn3->u.i > pIn1->u.i ){ res = +1; goto compare_op; }
if( pIn3->u.i < pIn1->u.i ){ res = -1; goto compare_op; }
@@ -82443,45 +90566,56 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
goto compare_op;
}
}else if( affinity==SQLITE_AFF_TEXT ){
- if( (flags1 & MEM_Str)==0 && (flags1 & (MEM_Int|MEM_Real))!=0 ){
+ if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
testcase( pIn1->flags & MEM_Int );
testcase( pIn1->flags & MEM_Real );
- sqlite3VdbeMemStringify(pIn1, encoding, 1);
+ testcase( pIn1->flags & MEM_IntReal );
+ tdsqlite3VdbeMemStringify(pIn1, encoding, 1);
testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) );
flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask);
- assert( pIn1!=pIn3 );
+ if( pIn1==pIn3 ) flags3 = flags1 | MEM_Str;
}
- if( (flags3 & MEM_Str)==0 && (flags3 & (MEM_Int|MEM_Real))!=0 ){
+ if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
testcase( pIn3->flags & MEM_Int );
testcase( pIn3->flags & MEM_Real );
- sqlite3VdbeMemStringify(pIn3, encoding, 1);
+ testcase( pIn3->flags & MEM_IntReal );
+ tdsqlite3VdbeMemStringify(pIn3, encoding, 1);
testcase( (flags3&MEM_Dyn) != (pIn3->flags&MEM_Dyn) );
flags3 = (pIn3->flags & ~MEM_TypeMask) | (flags3 & MEM_TypeMask);
}
}
assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 );
- res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl);
+ res = tdsqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl);
}
compare_op:
- switch( pOp->opcode ){
- case OP_Eq: res2 = res==0; break;
- case OP_Ne: res2 = res; break;
- case OP_Lt: res2 = res<0; break;
- case OP_Le: res2 = res<=0; break;
- case OP_Gt: res2 = res>0; break;
- default: res2 = res>=0; break;
+ /* At this point, res is negative, zero, or positive if reg[P1] is
+ ** less than, equal to, or greater than reg[P3], respectively. Compute
+ ** the answer to this operator in res2, depending on what the comparison
+ ** operator actually is. The next block of code depends on the fact
+ ** that the 6 comparison operators are consecutive integers in this
+ ** order: NE, EQ, GT, LE, LT, GE */
+ assert( OP_Eq==OP_Ne+1 ); assert( OP_Gt==OP_Ne+2 ); assert( OP_Le==OP_Ne+3 );
+ assert( OP_Lt==OP_Ne+4 ); assert( OP_Ge==OP_Ne+5 );
+ if( res<0 ){ /* ne, eq, gt, le, lt, ge */
+ static const unsigned char aLTb[] = { 1, 0, 0, 1, 1, 0 };
+ res2 = aLTb[pOp->opcode - OP_Ne];
+ }else if( res==0 ){
+ static const unsigned char aEQb[] = { 0, 1, 0, 1, 0, 1 };
+ res2 = aEQb[pOp->opcode - OP_Ne];
+ }else{
+ static const unsigned char aGTb[] = { 1, 0, 1, 0, 0, 1 };
+ res2 = aGTb[pOp->opcode - OP_Ne];
}
/* Undo any changes made by applyAffinity() to the input registers. */
- assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) );
- pIn1->flags = flags1;
assert( (pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn) );
pIn3->flags = flags3;
+ assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) );
+ pIn1->flags = flags1;
if( pOp->p5 & SQLITE_STOREP2 ){
pOut = &aMem[pOp->p2];
iCompare = res;
- res2 = res2!=0; /* For this path res2 must be exactly 0 or 1 */
if( (pOp->p5 & SQLITE_KEEPNULL)!=0 ){
/* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with 1
** and prevents OP_Ne from overwriting NULL with 0. This flag
@@ -82503,7 +90637,7 @@ compare_op:
pOut->u.i = res2;
REGISTER_TRACE(pOp->p2, pOut);
}else{
- VdbeBranchTaken(res!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3);
+ VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3);
if( res2 ){
goto jump_to_p2;
}
@@ -82513,16 +90647,31 @@ compare_op:
/* Opcode: ElseNotEq * P2 * * *
**
-** This opcode must immediately follow an OP_Lt or OP_Gt comparison operator.
-** If result of an OP_Eq comparison on the same two operands
-** would have be NULL or false (0), then then jump to P2.
-** If the result of an OP_Eq comparison on the two previous operands
-** would have been true (1), then fall through.
+** This opcode must follow an OP_Lt or OP_Gt comparison operator. There
+** can be zero or more OP_ReleaseReg opcodes intervening, but no other
+** opcodes are allowed to occur between this instruction and the previous
+** OP_Lt or OP_Gt. Furthermore, the prior OP_Lt or OP_Gt must have the
+** SQLITE_STOREP2 bit set in the P5 field.
+**
+** If result of an OP_Eq comparison on the same two operands as the
+** prior OP_Lt or OP_Gt would have been NULL or false (0), then then
+** jump to P2. If the result of an OP_Eq comparison on the two previous
+** operands would have been true (1), then fall through.
*/
case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */
- assert( pOp>aOp );
- assert( pOp[-1].opcode==OP_Lt || pOp[-1].opcode==OP_Gt );
- assert( pOp[-1].p5 & SQLITE_STOREP2 );
+
+#ifdef SQLITE_DEBUG
+ /* Verify the preconditions of this opcode - that it follows an OP_Lt or
+ ** OP_Gt with the SQLITE_STOREP2 flag set, with zero or more intervening
+ ** OP_ReleaseReg opcodes */
+ int iAddr;
+ for(iAddr = (int)(pOp - aOp) - 1; ALWAYS(iAddr>=0); iAddr--){
+ if( aOp[iAddr].opcode==OP_ReleaseReg ) continue;
+ assert( aOp[iAddr].opcode==OP_Lt || aOp[iAddr].opcode==OP_Gt );
+ assert( aOp[iAddr].p5 & SQLITE_STOREP2 );
+ break;
+ }
+#endif /* SQLITE_DEBUG */
VdbeBranchTaken(iCompare!=0, 2);
if( iCompare!=0 ) goto jump_to_p2;
break;
@@ -82531,8 +90680,8 @@ case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */
/* Opcode: Permutation * * * P4 *
**
-** Set the permutation used by the OP_Compare operator to be the array
-** of integers in P4.
+** Set the permutation used by the OP_Compare operator in the next
+** instruction. The permutation is stored in the P4 operand.
**
** The permutation is only valid until the next OP_Compare that has
** the OPFLAG_PERMUTE bit set in P5. Typically the OP_Permutation should
@@ -82544,7 +90693,8 @@ case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */
case OP_Permutation: {
assert( pOp->p4type==P4_INTARRAY );
assert( pOp->p4.ai );
- aPermute = pOp->p4.ai + 1;
+ assert( pOp[1].opcode==OP_Compare );
+ assert( pOp[1].p5 & OPFLAG_PERMUTE );
break;
}
@@ -82577,15 +90727,24 @@ case OP_Compare: {
int idx;
CollSeq *pColl; /* Collating sequence to use on this term */
int bRev; /* True for DESCENDING sort order */
+ int *aPermute; /* The permutation */
- if( (pOp->p5 & OPFLAG_PERMUTE)==0 ) aPermute = 0;
+ if( (pOp->p5 & OPFLAG_PERMUTE)==0 ){
+ aPermute = 0;
+ }else{
+ assert( pOp>aOp );
+ assert( pOp[-1].opcode==OP_Permutation );
+ assert( pOp[-1].p4type==P4_INTARRAY );
+ aPermute = pOp[-1].p4.ai + 1;
+ assert( aPermute!=0 );
+ }
n = pOp->p3;
pKeyInfo = pOp->p4.pKeyInfo;
assert( n>0 );
assert( pKeyInfo!=0 );
p1 = pOp->p1;
p2 = pOp->p2;
-#if SQLITE_DEBUG
+#ifdef SQLITE_DEBUG
if( aPermute ){
int k, mx = 0;
for(k=0; k<n; k++) if( aPermute[k]>mx ) mx = aPermute[k];
@@ -82602,16 +90761,20 @@ case OP_Compare: {
assert( memIsValid(&aMem[p2+idx]) );
REGISTER_TRACE(p1+idx, &aMem[p1+idx]);
REGISTER_TRACE(p2+idx, &aMem[p2+idx]);
- assert( i<pKeyInfo->nField );
+ assert( i<pKeyInfo->nKeyField );
pColl = pKeyInfo->aColl[i];
- bRev = pKeyInfo->aSortOrder[i];
- iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl);
+ bRev = (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_DESC);
+ iCompare = tdsqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl);
if( iCompare ){
+ if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL)
+ && ((aMem[p1+idx].flags & MEM_Null) || (aMem[p2+idx].flags & MEM_Null))
+ ){
+ iCompare = -iCompare;
+ }
if( bRev ) iCompare = -iCompare;
break;
}
}
- aPermute = 0;
break;
}
@@ -82623,11 +90786,11 @@ case OP_Compare: {
*/
case OP_Jump: { /* jump */
if( iCompare<0 ){
- VdbeBranchTaken(0,3); pOp = &aOp[pOp->p1 - 1];
+ VdbeBranchTaken(0,4); pOp = &aOp[pOp->p1 - 1];
}else if( iCompare==0 ){
- VdbeBranchTaken(1,3); pOp = &aOp[pOp->p2 - 1];
+ VdbeBranchTaken(1,4); pOp = &aOp[pOp->p2 - 1];
}else{
- VdbeBranchTaken(2,3); pOp = &aOp[pOp->p3 - 1];
+ VdbeBranchTaken(2,4); pOp = &aOp[pOp->p3 - 1];
}
break;
}
@@ -82657,18 +90820,8 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */
int v1; /* Left operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */
int v2; /* Right operand: 0==FALSE, 1==TRUE, 2==UNKNOWN or NULL */
- pIn1 = &aMem[pOp->p1];
- if( pIn1->flags & MEM_Null ){
- v1 = 2;
- }else{
- v1 = sqlite3VdbeIntValue(pIn1)!=0;
- }
- pIn2 = &aMem[pOp->p2];
- if( pIn2->flags & MEM_Null ){
- v2 = 2;
- }else{
- v2 = sqlite3VdbeIntValue(pIn2)!=0;
- }
+ v1 = tdsqlite3VdbeBooleanValue(&aMem[pOp->p1], 2);
+ v2 = tdsqlite3VdbeBooleanValue(&aMem[pOp->p2], 2);
if( pOp->opcode==OP_And ){
static const unsigned char and_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 };
v1 = and_logic[v1*3+v2];
@@ -82686,6 +90839,35 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */
break;
}
+/* Opcode: IsTrue P1 P2 P3 P4 *
+** Synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4
+**
+** This opcode implements the IS TRUE, IS FALSE, IS NOT TRUE, and
+** IS NOT FALSE operators.
+**
+** Interpret the value in register P1 as a boolean value. Store that
+** boolean (a 0 or 1) in register P2. Or if the value in register P1 is
+** NULL, then the P3 is stored in register P2. Invert the answer if P4
+** is 1.
+**
+** The logic is summarized like this:
+**
+** <ul>
+** <li> If P3==0 and P4==0 then r[P2] := r[P1] IS TRUE
+** <li> If P3==1 and P4==1 then r[P2] := r[P1] IS FALSE
+** <li> If P3==0 and P4==1 then r[P2] := r[P1] IS NOT TRUE
+** <li> If P3==1 and P4==0 then r[P2] := r[P1] IS NOT FALSE
+** </ul>
+*/
+case OP_IsTrue: { /* in1, out2 */
+ assert( pOp->p4type==P4_INT32 );
+ assert( pOp->p4.i==0 || pOp->p4.i==1 );
+ assert( pOp->p3==0 || pOp->p3==1 );
+ tdsqlite3VdbeMemSetInt64(&aMem[pOp->p2],
+ tdsqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3) ^ pOp->p4.i);
+ break;
+}
+
/* Opcode: Not P1 P2 * * *
** Synopsis: r[P2]= !r[P1]
**
@@ -82696,16 +90878,16 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */
case OP_Not: { /* same as TK_NOT, in1, out2 */
pIn1 = &aMem[pOp->p1];
pOut = &aMem[pOp->p2];
- sqlite3VdbeMemSetNull(pOut);
if( (pIn1->flags & MEM_Null)==0 ){
- pOut->flags = MEM_Int;
- pOut->u.i = !sqlite3VdbeIntValue(pIn1);
+ tdsqlite3VdbeMemSetInt64(pOut, !tdsqlite3VdbeBooleanValue(pIn1,0));
+ }else{
+ tdsqlite3VdbeMemSetNull(pOut);
}
break;
}
/* Opcode: BitNot P1 P2 * * *
-** Synopsis: r[P1]= ~r[P1]
+** Synopsis: r[P2]= ~r[P1]
**
** Interpret the content of register P1 as an integer. Store the
** ones-complement of the P1 value into register P2. If P1 holds
@@ -82714,29 +90896,49 @@ case OP_Not: { /* same as TK_NOT, in1, out2 */
case OP_BitNot: { /* same as TK_BITNOT, in1, out2 */
pIn1 = &aMem[pOp->p1];
pOut = &aMem[pOp->p2];
- sqlite3VdbeMemSetNull(pOut);
+ tdsqlite3VdbeMemSetNull(pOut);
if( (pIn1->flags & MEM_Null)==0 ){
pOut->flags = MEM_Int;
- pOut->u.i = ~sqlite3VdbeIntValue(pIn1);
+ pOut->u.i = ~tdsqlite3VdbeIntValue(pIn1);
}
break;
}
/* Opcode: Once P1 P2 * * *
**
-** If the P1 value is equal to the P1 value on the OP_Init opcode at
-** instruction 0, then jump to P2. If the two P1 values differ, then
-** set the P1 value on this opcode to equal the P1 value on the OP_Init
-** and fall through.
+** Fall through to the next instruction the first time this opcode is
+** encountered on each invocation of the byte-code program. Jump to P2
+** on the second and all subsequent encounters during the same invocation.
+**
+** Top-level programs determine first invocation by comparing the P1
+** operand against the P1 operand on the OP_Init opcode at the beginning
+** of the program. If the P1 values differ, then fall through and make
+** the P1 of this opcode equal to the P1 of OP_Init. If P1 values are
+** the same then take the jump.
+**
+** For subprograms, there is a bitmask in the VdbeFrame that determines
+** whether or not the jump should be taken. The bitmask is necessary
+** because the self-altering code trick does not work for recursive
+** triggers.
*/
case OP_Once: { /* jump */
+ u32 iAddr; /* Address of this instruction */
assert( p->aOp[0].opcode==OP_Init );
- VdbeBranchTaken(p->aOp[0].p1==pOp->p1, 2);
- if( p->aOp[0].p1==pOp->p1 ){
- goto jump_to_p2;
+ if( p->pFrame ){
+ iAddr = (int)(pOp - p->aOp);
+ if( (p->pFrame->aOnce[iAddr/8] & (1<<(iAddr & 7)))!=0 ){
+ VdbeBranchTaken(1, 2);
+ goto jump_to_p2;
+ }
+ p->pFrame->aOnce[iAddr/8] |= 1<<(iAddr & 7);
}else{
- pOp->p1 = p->aOp[0].p1;
+ if( p->aOp[0].p1==pOp->p1 ){
+ VdbeBranchTaken(1, 2);
+ goto jump_to_p2;
+ }
}
+ VdbeBranchTaken(0, 2);
+ pOp->p1 = p->aOp[0].p1;
break;
}
@@ -82746,30 +90948,25 @@ case OP_Once: { /* jump */
** is considered true if it is numeric and non-zero. If the value
** in P1 is NULL then take the jump if and only if P3 is non-zero.
*/
+case OP_If: { /* jump, in1 */
+ int c;
+ c = tdsqlite3VdbeBooleanValue(&aMem[pOp->p1], pOp->p3);
+ VdbeBranchTaken(c!=0, 2);
+ if( c ) goto jump_to_p2;
+ break;
+}
+
/* Opcode: IfNot P1 P2 P3 * *
**
** Jump to P2 if the value in register P1 is False. The value
** is considered false if it has a numeric value of zero. If the value
** in P1 is NULL then take the jump if and only if P3 is non-zero.
*/
-case OP_If: /* jump, in1 */
case OP_IfNot: { /* jump, in1 */
int c;
- pIn1 = &aMem[pOp->p1];
- if( pIn1->flags & MEM_Null ){
- c = pOp->p3;
- }else{
-#ifdef SQLITE_OMIT_FLOATING_POINT
- c = sqlite3VdbeIntValue(pIn1)!=0;
-#else
- c = sqlite3VdbeRealValue(pIn1)!=0.0;
-#endif
- if( pOp->opcode==OP_IfNot ) c = !c;
- }
+ c = !tdsqlite3VdbeBooleanValue(&aMem[pOp->p1], !pOp->p3);
VdbeBranchTaken(c!=0, 2);
- if( c ){
- goto jump_to_p2;
- }
+ if( c ) goto jump_to_p2;
break;
}
@@ -82801,6 +90998,54 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */
break;
}
+/* Opcode: IfNullRow P1 P2 P3 * *
+** Synopsis: if P1.nullRow then r[P3]=NULL, goto P2
+**
+** Check the cursor P1 to see if it is currently pointing at a NULL row.
+** If it is, then set register P3 to NULL and jump immediately to P2.
+** If P1 is not on a NULL row, then fall through without making any
+** changes.
+*/
+case OP_IfNullRow: { /* jump */
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ assert( p->apCsr[pOp->p1]!=0 );
+ if( p->apCsr[pOp->p1]->nullRow ){
+ tdsqlite3VdbeMemSetNull(aMem + pOp->p3);
+ goto jump_to_p2;
+ }
+ break;
+}
+
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+/* Opcode: Offset P1 P2 P3 * *
+** Synopsis: r[P3] = sqlite_offset(P1)
+**
+** Store in register r[P3] the byte offset into the database file that is the
+** start of the payload for the record at which that cursor P1 is currently
+** pointing.
+**
+** P2 is the column number for the argument to the sqlite_offset() function.
+** This opcode does not use P2 itself, but the P2 value is used by the
+** code generator. The P1, P2, and P3 operands to this opcode are the
+** same as for OP_Column.
+**
+** This opcode is only available if SQLite is compiled with the
+** -DSQLITE_ENABLE_OFFSET_SQL_FUNC option.
+*/
+case OP_Offset: { /* out3 */
+ VdbeCursor *pC; /* The VDBE cursor */
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = p->apCsr[pOp->p1];
+ pOut = &p->aMem[pOp->p3];
+ if( NEVER(pC==0) || pC->eCurType!=CURTYPE_BTREE ){
+ tdsqlite3VdbeMemSetNull(pOut);
+ }else{
+ tdsqlite3VdbeMemSetInt64(pOut, tdsqlite3BtreeOffset(pC->uc.pCursor));
+ }
+ break;
+}
+#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */
+
/* Opcode: Column P1 P2 P3 P4 P5
** Synopsis: r[P3]=PX
**
@@ -82812,16 +91057,11 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */
**
** The value extracted is stored in register P3.
**
-** If the column contains fewer than P2 fields, then extract a NULL. Or,
+** If the record contains fewer than P2 fields, then extract a NULL. Or,
** if the P4 argument is a P4_MEM use the value of the P4 argument as
** the result.
**
-** If the OPFLAG_CLEARCACHE bit is set on P5 and P1 is a pseudo-table cursor,
-** then the cache of the cursor is reset prior to extracting the column.
-** The first OP_Column against a pseudo-table after the value of the content
-** register has changed should have this bit set.
-**
-** If the OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG bits are set on P5 when
+** If the OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG bits are set on P5 then
** the result is guaranteed to only be used as the argument of a length()
** or typeof() function, respectively. The loading of large blobs can be
** skipped for length() and all content loading can be skipped for typeof().
@@ -82838,66 +91078,65 @@ case OP_Column: {
const u8 *zData; /* Part of the record being decoded */
const u8 *zHdr; /* Next unparsed byte of the header */
const u8 *zEndHdr; /* Pointer to first byte after the header */
- u32 offset; /* Offset into the data */
u64 offset64; /* 64-bit offset */
- u32 avail; /* Number of bytes of available data */
u32 t; /* A type code from the record header */
Mem *pReg; /* PseudoTable input register */
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
p2 = pOp->p2;
- /* If the cursor cache is stale, bring it up-to-date */
- rc = sqlite3VdbeCursorMoveto(&pC, &p2);
+ /* If the cursor cache is stale (meaning it is not currently point at
+ ** the correct row) then bring it up-to-date by doing the necessary
+ ** B-Tree seek. */
+ rc = tdsqlite3VdbeCursorMoveto(&pC, &p2);
if( rc ) goto abort_due_to_error;
assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
pDest = &aMem[pOp->p3];
memAboutToChange(p, pDest);
- assert( pOp->p1>=0 && pOp->p1<p->nCursor );
assert( pC!=0 );
assert( p2<pC->nField );
aOffset = pC->aOffset;
assert( pC->eCurType!=CURTYPE_VTAB );
assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow );
assert( pC->eCurType!=CURTYPE_SORTER );
- pCrsr = pC->uc.pCursor;
if( pC->cacheStatus!=p->cacheCtr ){ /*OPTIMIZATION-IF-FALSE*/
if( pC->nullRow ){
if( pC->eCurType==CURTYPE_PSEUDO ){
- assert( pC->uc.pseudoTableReg>0 );
- pReg = &aMem[pC->uc.pseudoTableReg];
+ /* For the special case of as pseudo-cursor, the seekResult field
+ ** identifies the register that holds the record */
+ assert( pC->seekResult>0 );
+ pReg = &aMem[pC->seekResult];
assert( pReg->flags & MEM_Blob );
assert( memIsValid(pReg) );
- pC->payloadSize = pC->szRow = avail = pReg->n;
+ pC->payloadSize = pC->szRow = pReg->n;
pC->aRow = (u8*)pReg->z;
}else{
- sqlite3VdbeMemSetNull(pDest);
+ tdsqlite3VdbeMemSetNull(pDest);
goto op_column_out;
}
}else{
+ pCrsr = pC->uc.pCursor;
assert( pC->eCurType==CURTYPE_BTREE );
assert( pCrsr );
- assert( sqlite3BtreeCursorIsValid(pCrsr) );
- pC->payloadSize = sqlite3BtreePayloadSize(pCrsr);
- pC->aRow = sqlite3BtreePayloadFetch(pCrsr, &avail);
- assert( avail<=65536 ); /* Maximum page size is 64KiB */
- if( pC->payloadSize <= (u32)avail ){
- pC->szRow = pC->payloadSize;
- }else if( pC->payloadSize > (u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ assert( tdsqlite3BtreeCursorIsValid(pCrsr) );
+ pC->payloadSize = tdsqlite3BtreePayloadSize(pCrsr);
+ pC->aRow = tdsqlite3BtreePayloadFetch(pCrsr, &pC->szRow);
+ assert( pC->szRow<=pC->payloadSize );
+ assert( pC->szRow<=65536 ); /* Maximum page size is 64KiB */
+ if( pC->payloadSize > (u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){
goto too_big;
- }else{
- pC->szRow = avail;
}
}
pC->cacheStatus = p->cacheCtr;
- pC->iHdrOffset = getVarint32(pC->aRow, offset);
+ pC->iHdrOffset = getVarint32(pC->aRow, aOffset[0]);
pC->nHdrParsed = 0;
- aOffset[0] = offset;
- if( avail<offset ){ /*OPTIMIZATION-IF-FALSE*/
+ if( pC->szRow<aOffset[0] ){ /*OPTIMIZATION-IF-FALSE*/
/* pC->aRow does not have to hold the entire row, but it does at least
** need to cover the header of the record. If pC->aRow does not contain
** the complete header, then set it to zero, forcing the header to be
@@ -82914,17 +91153,26 @@ case OP_Column: {
** 3-byte type for each of the maximum of 32768 columns plus three
** extra bytes for the header length itself. 32768*3 + 3 = 98307.
*/
- if( offset > 98307 || offset > pC->payloadSize ){
- rc = SQLITE_CORRUPT_BKPT;
- goto abort_due_to_error;
+ if( aOffset[0] > 98307 || aOffset[0] > pC->payloadSize ){
+ goto op_column_corrupt;
}
- }else if( offset>0 ){ /*OPTIMIZATION-IF-TRUE*/
- /* The following goto is an optimization. It can be omitted and
- ** everything will still work. But OP_Column is measurably faster
- ** by skipping the subsequent conditional, which is always true.
+ }else{
+ /* This is an optimization. By skipping over the first few tests
+ ** (ex: pC->nHdrParsed<=p2) in the next section, we achieve a
+ ** measurable performance gain.
+ **
+ ** This branch is taken even if aOffset[0]==0. Such a record is never
+ ** generated by SQLite, and could be considered corruption, but we
+ ** accept it for historical reasons. When aOffset[0]==0, the code this
+ ** branch jumps to reads past the end of the record, but never more
+ ** than a few bytes. Even if the record occurs at the end of the page
+ ** content area, the "page header" comes after the page content and so
+ ** this overread is harmless. Similar overreads can occur for a corrupt
+ ** database file.
*/
zData = pC->aRow;
assert( pC->nHdrParsed<=p2 ); /* Conditional skipped */
+ testcase( aOffset[0]==0 );
goto op_column_read_header;
}
}
@@ -82940,7 +91188,7 @@ case OP_Column: {
/* Make sure zData points to enough of the record to cover the header. */
if( pC->aRow==0 ){
memset(&sMem, 0, sizeof(sMem));
- rc = sqlite3VdbeMemFromBtree(pCrsr, 0, aOffset[0], !pC->isTable, &sMem);
+ rc = tdsqlite3VdbeMemFromBtree(pC->uc.pCursor, 0, aOffset[0], &sMem);
if( rc!=SQLITE_OK ) goto abort_due_to_error;
zData = (u8*)sMem.z;
}else{
@@ -82953,16 +91201,17 @@ case OP_Column: {
offset64 = aOffset[i];
zHdr = zData + pC->iHdrOffset;
zEndHdr = zData + aOffset[0];
+ testcase( zHdr>=zEndHdr );
do{
- if( (t = zHdr[0])<0x80 ){
+ if( (pC->aType[i] = t = zHdr[0])<0x80 ){
zHdr++;
- offset64 += sqlite3VdbeOneByteSerialTypeLen(t);
+ offset64 += tdsqlite3VdbeOneByteSerialTypeLen(t);
}else{
- zHdr += sqlite3GetVarint32(zHdr, &t);
- offset64 += sqlite3VdbeSerialTypeLen(t);
+ zHdr += tdsqlite3GetVarint32(zHdr, &t);
+ pC->aType[i] = t;
+ offset64 += tdsqlite3VdbeSerialTypeLen(t);
}
- pC->aType[i++] = t;
- aOffset[i] = (u32)(offset64 & 0xffffffff);
+ aOffset[++i] = (u32)(offset64 & 0xffffffff);
}while( i<=p2 && zHdr<zEndHdr );
/* The record is corrupt if any of the following are true:
@@ -82973,14 +91222,18 @@ case OP_Column: {
if( (zHdr>=zEndHdr && (zHdr>zEndHdr || offset64!=pC->payloadSize))
|| (offset64 > pC->payloadSize)
){
- if( pC->aRow==0 ) sqlite3VdbeMemRelease(&sMem);
- rc = SQLITE_CORRUPT_BKPT;
- goto abort_due_to_error;
+ if( aOffset[0]==0 ){
+ i = 0;
+ zHdr = zEndHdr;
+ }else{
+ if( pC->aRow==0 ) tdsqlite3VdbeMemRelease(&sMem);
+ goto op_column_corrupt;
+ }
}
pC->nHdrParsed = i;
pC->iHdrOffset = (u32)(zHdr - zData);
- if( pC->aRow==0 ) sqlite3VdbeMemRelease(&sMem);
+ if( pC->aRow==0 ) tdsqlite3VdbeMemRelease(&sMem);
}else{
t = 0;
}
@@ -82991,9 +91244,9 @@ case OP_Column: {
*/
if( pC->nHdrParsed<=p2 ){
if( pOp->p4type==P4_MEM ){
- sqlite3VdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static);
+ tdsqlite3VdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static);
}else{
- sqlite3VdbeMemSetNull(pDest);
+ tdsqlite3VdbeMemSetNull(pDest);
}
goto op_column_out;
}
@@ -83007,9 +91260,9 @@ case OP_Column: {
*/
assert( p2<pC->nHdrParsed );
assert( rc==SQLITE_OK );
- assert( sqlite3VdbeCheckMemInvariants(pDest) );
+ assert( tdsqlite3VdbeCheckMemInvariants(pDest) );
if( VdbeMemDynamic(pDest) ){
- sqlite3VdbeMemSetNull(pDest);
+ tdsqlite3VdbeMemSetNull(pDest);
}
assert( t==pC->aType[p2] );
if( pC->szRow>=aOffset[p2+1] ){
@@ -83017,18 +91270,18 @@ case OP_Column: {
** page - where the content is not on an overflow page */
zData = pC->aRow + aOffset[p2];
if( t<12 ){
- sqlite3VdbeSerialGet(zData, t, pDest);
+ tdsqlite3VdbeSerialGet(zData, t, pDest);
}else{
/* If the column value is a string, we need a persistent value, not
** a MEM_Ephem value. This branch is a fast short-cut that is equivalent
- ** to calling sqlite3VdbeSerialGet() and sqlite3VdbeDeephemeralize().
+ ** to calling tdsqlite3VdbeSerialGet() and tdsqlite3VdbeDeephemeralize().
*/
static const u16 aFlag[] = { MEM_Blob, MEM_Str|MEM_Term };
pDest->n = len = (t-12)/2;
pDest->enc = encoding;
if( pDest->szMalloc < len+2 ){
pDest->flags = MEM_Null;
- if( sqlite3VdbeMemGrow(pDest, len+2, 0) ) goto no_mem;
+ if( tdsqlite3VdbeMemGrow(pDest, len+2, 0) ) goto no_mem;
}else{
pDest->z = pDest->zMalloc;
}
@@ -83042,21 +91295,26 @@ case OP_Column: {
/* This branch happens only when content is on overflow pages */
if( ((pOp->p5 & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG))!=0
&& ((t>=12 && (t&1)==0) || (pOp->p5 & OPFLAG_TYPEOFARG)!=0))
- || (len = sqlite3VdbeSerialTypeLen(t))==0
+ || (len = tdsqlite3VdbeSerialTypeLen(t))==0
){
/* Content is irrelevant for
** 1. the typeof() function,
** 2. the length(X) function if X is a blob, and
** 3. if the content length is zero.
** So we might as well use bogus content rather than reading
- ** content from disk. */
- static u8 aZero[8]; /* This is the bogus content */
- sqlite3VdbeSerialGet(aZero, t, pDest);
+ ** content from disk.
+ **
+ ** Although tdsqlite3VdbeSerialGet() may read at most 8 bytes from the
+ ** buffer passed to it, debugging function VdbeMemPrettyPrint() may
+ ** read more. Use the global constant tdsqlite3CtypeMap[] as the array,
+ ** as that array is 256 bytes long (plenty for VdbeMemPrettyPrint())
+ ** and it begins with a bunch of zeros.
+ */
+ tdsqlite3VdbeSerialGet((u8*)tdsqlite3CtypeMap, t, pDest);
}else{
- rc = sqlite3VdbeMemFromBtree(pCrsr, aOffset[p2], len, !pC->isTable,
- pDest);
+ rc = tdsqlite3VdbeMemFromBtree(pC->uc.pCursor, aOffset[p2], len, pDest);
if( rc!=SQLITE_OK ) goto abort_due_to_error;
- sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest);
+ tdsqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest);
pDest->flags &= ~MEM_Ephem;
}
}
@@ -83065,6 +91323,15 @@ op_column_out:
UPDATE_MAX_BLOBSIZE(pDest);
REGISTER_TRACE(pOp->p3, pDest);
break;
+
+op_column_corrupt:
+ if( aOp[0].p3>0 ){
+ pOp = &aOp[aOp[0].p3-1];
+ break;
+ }else{
+ rc = SQLITE_CORRUPT_BKPT;
+ goto abort_due_to_error;
+ }
}
/* Opcode: Affinity P1 P2 * P4 *
@@ -83072,22 +91339,43 @@ op_column_out:
**
** Apply affinities to a range of P2 registers starting with P1.
**
-** P4 is a string that is P2 characters long. The nth character of the
-** string indicates the column affinity that should be used for the nth
+** P4 is a string that is P2 characters long. The N-th character of the
+** string indicates the column affinity that should be used for the N-th
** memory cell in the range.
*/
case OP_Affinity: {
const char *zAffinity; /* The affinity to be applied */
- char cAff; /* A single character of affinity */
zAffinity = pOp->p4.z;
assert( zAffinity!=0 );
+ assert( pOp->p2>0 );
assert( zAffinity[pOp->p2]==0 );
pIn1 = &aMem[pOp->p1];
- while( (cAff = *(zAffinity++))!=0 ){
+ while( 1 /*exit-by-break*/ ){
assert( pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)] );
- assert( memIsValid(pIn1) );
- applyAffinity(pIn1, cAff, encoding);
+ assert( zAffinity[0]==SQLITE_AFF_NONE || memIsValid(pIn1) );
+ applyAffinity(pIn1, zAffinity[0], encoding);
+ if( zAffinity[0]==SQLITE_AFF_REAL && (pIn1->flags & MEM_Int)!=0 ){
+ /* When applying REAL affinity, if the result is still an MEM_Int
+ ** that will fit in 6 bytes, then change the type to MEM_IntReal
+ ** so that we keep the high-resolution integer value but know that
+ ** the type really wants to be REAL. */
+ testcase( pIn1->u.i==140737488355328LL );
+ testcase( pIn1->u.i==140737488355327LL );
+ testcase( pIn1->u.i==-140737488355328LL );
+ testcase( pIn1->u.i==-140737488355329LL );
+ if( pIn1->u.i<=140737488355327LL && pIn1->u.i>=-140737488355328LL ){
+ pIn1->flags |= MEM_IntReal;
+ pIn1->flags &= ~MEM_Int;
+ }else{
+ pIn1->u.r = (double)pIn1->u.i;
+ pIn1->flags |= MEM_Real;
+ pIn1->flags &= ~MEM_Int;
+ }
+ }
+ REGISTER_TRACE((int)(pIn1-aMem), pIn1);
+ zAffinity++;
+ if( zAffinity[0]==0 ) break;
pIn1++;
}
break;
@@ -83100,8 +91388,8 @@ case OP_Affinity: {
** use as a data record in a database table or as a key
** in an index. The OP_Column opcode can decode the record later.
**
-** P4 may be a string that is P2 characters long. The nth character of the
-** string indicates the column affinity that should be used for the nth
+** P4 may be a string that is P2 characters long. The N-th character of the
+** string indicates the column affinity that should be used for the N-th
** field of the index key.
**
** The mapping from character to affinity is given by the SQLITE_AFF_
@@ -83110,7 +91398,6 @@ case OP_Affinity: {
** If P4 is NULL then all index fields have the affinity BLOB.
*/
case OP_MakeRecord: {
- u8 *zNewRecord; /* A buffer to hold the data for the new record */
Mem *pRec; /* The new record */
u64 nData; /* Number of bytes of data space */
int nHdr; /* Number of bytes of header space */
@@ -83123,9 +91410,9 @@ case OP_MakeRecord: {
int nField; /* Number of fields in the record */
char *zAffinity; /* The affinity string for the record */
int file_format; /* File format to use for encoding */
- int i; /* Space used in zNewRecord[] header */
- int j; /* Space used in zNewRecord[] content */
u32 len; /* Length of a field */
+ u8 *zHdr; /* Where to write next byte of the header */
+ u8 *zPayload; /* Where to write next byte of the payload */
/* Assuming the record contains N fields, the record format looks
** like this:
@@ -83138,7 +91425,7 @@ case OP_MakeRecord: {
** and so forth.
**
** Each type field is a varint representing the serial type of the
- ** corresponding data element (see sqlite3VdbeSerialType()). The
+ ** corresponding data element (see tdsqlite3VdbeSerialType()). The
** hdr-size field is also a varint which is the offset from the beginning
** of the record to data0.
*/
@@ -83164,30 +91451,147 @@ case OP_MakeRecord: {
if( zAffinity ){
pRec = pData0;
do{
- applyAffinity(pRec++, *(zAffinity++), encoding);
+ applyAffinity(pRec, zAffinity[0], encoding);
+ if( zAffinity[0]==SQLITE_AFF_REAL && (pRec->flags & MEM_Int) ){
+ pRec->flags |= MEM_IntReal;
+ pRec->flags &= ~(MEM_Int);
+ }
+ REGISTER_TRACE((int)(pRec-aMem), pRec);
+ zAffinity++;
+ pRec++;
assert( zAffinity[0]==0 || pRec<=pLast );
}while( zAffinity[0] );
}
+#ifdef SQLITE_ENABLE_NULL_TRIM
+ /* NULLs can be safely trimmed from the end of the record, as long as
+ ** as the schema format is 2 or more and none of the omitted columns
+ ** have a non-NULL default value. Also, the record must be left with
+ ** at least one field. If P5>0 then it will be one more than the
+ ** index of the right-most column with a non-NULL default value */
+ if( pOp->p5 ){
+ while( (pLast->flags & MEM_Null)!=0 && nField>pOp->p5 ){
+ pLast--;
+ nField--;
+ }
+ }
+#endif
+
/* Loop through the elements that will make up the record to figure
- ** out how much space is required for the new record.
+ ** out how much space is required for the new record. After this loop,
+ ** the Mem.uTemp field of each term should hold the serial-type that will
+ ** be used for that term in the generated record:
+ **
+ ** Mem.uTemp value type
+ ** --------------- ---------------
+ ** 0 NULL
+ ** 1 1-byte signed integer
+ ** 2 2-byte signed integer
+ ** 3 3-byte signed integer
+ ** 4 4-byte signed integer
+ ** 5 6-byte signed integer
+ ** 6 8-byte signed integer
+ ** 7 IEEE float
+ ** 8 Integer constant 0
+ ** 9 Integer constant 1
+ ** 10,11 reserved for expansion
+ ** N>=12 and even BLOB
+ ** N>=13 and odd text
+ **
+ ** The following additional values are computed:
+ ** nHdr Number of bytes needed for the record header
+ ** nData Number of bytes of data space needed for the record
+ ** nZero Zero bytes at the end of the record
*/
pRec = pLast;
do{
assert( memIsValid(pRec) );
- pRec->uTemp = serial_type = sqlite3VdbeSerialType(pRec, file_format, &len);
- if( pRec->flags & MEM_Zero ){
- if( nData ){
- if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem;
+ if( pRec->flags & MEM_Null ){
+ if( pRec->flags & MEM_Zero ){
+ /* Values with MEM_Null and MEM_Zero are created by xColumn virtual
+ ** table methods that never invoke tdsqlite3_result_xxxxx() while
+ ** computing an unchanging column value in an UPDATE statement.
+ ** Give such values a special internal-use-only serial-type of 10
+ ** so that they can be passed through to xUpdate and have
+ ** a true tdsqlite3_value_nochange(). */
+ assert( pOp->p5==OPFLAG_NOCHNG_MAGIC || CORRUPT_DB );
+ pRec->uTemp = 10;
+ }else{
+ pRec->uTemp = 0;
+ }
+ nHdr++;
+ }else if( pRec->flags & (MEM_Int|MEM_IntReal) ){
+ /* Figure out whether to use 1, 2, 4, 6 or 8 bytes. */
+ i64 i = pRec->u.i;
+ u64 uu;
+ testcase( pRec->flags & MEM_Int );
+ testcase( pRec->flags & MEM_IntReal );
+ if( i<0 ){
+ uu = ~i;
+ }else{
+ uu = i;
+ }
+ nHdr++;
+ testcase( uu==127 ); testcase( uu==128 );
+ testcase( uu==32767 ); testcase( uu==32768 );
+ testcase( uu==8388607 ); testcase( uu==8388608 );
+ testcase( uu==2147483647 ); testcase( uu==2147483648 );
+ testcase( uu==140737488355327LL ); testcase( uu==140737488355328LL );
+ if( uu<=127 ){
+ if( (i&1)==i && file_format>=4 ){
+ pRec->uTemp = 8+(u32)uu;
+ }else{
+ nData++;
+ pRec->uTemp = 1;
+ }
+ }else if( uu<=32767 ){
+ nData += 2;
+ pRec->uTemp = 2;
+ }else if( uu<=8388607 ){
+ nData += 3;
+ pRec->uTemp = 3;
+ }else if( uu<=2147483647 ){
+ nData += 4;
+ pRec->uTemp = 4;
+ }else if( uu<=140737488355327LL ){
+ nData += 6;
+ pRec->uTemp = 5;
}else{
- nZero += pRec->u.nZero;
- len -= pRec->u.nZero;
+ nData += 8;
+ if( pRec->flags & MEM_IntReal ){
+ /* If the value is IntReal and is going to take up 8 bytes to store
+ ** as an integer, then we might as well make it an 8-byte floating
+ ** point value */
+ pRec->u.r = (double)pRec->u.i;
+ pRec->flags &= ~MEM_IntReal;
+ pRec->flags |= MEM_Real;
+ pRec->uTemp = 7;
+ }else{
+ pRec->uTemp = 6;
+ }
+ }
+ }else if( pRec->flags & MEM_Real ){
+ nHdr++;
+ nData += 8;
+ pRec->uTemp = 7;
+ }else{
+ assert( db->mallocFailed || pRec->flags&(MEM_Str|MEM_Blob) );
+ assert( pRec->n>=0 );
+ len = (u32)pRec->n;
+ serial_type = (len*2) + 12 + ((pRec->flags & MEM_Str)!=0);
+ if( pRec->flags & MEM_Zero ){
+ serial_type += pRec->u.nZero*2;
+ if( nData ){
+ if( tdsqlite3VdbeMemExpandBlob(pRec) ) goto no_mem;
+ len += pRec->u.nZero;
+ }else{
+ nZero += pRec->u.nZero;
+ }
}
+ nData += len;
+ nHdr += tdsqlite3VarintLen(serial_type);
+ pRec->uTemp = serial_type;
}
- nData += len;
- testcase( serial_type==127 );
- testcase( serial_type==128 );
- nHdr += serial_type<=127 ? 1 : sqlite3VarintLen(serial_type);
if( pRec==pData0 ) break;
pRec--;
}while(1);
@@ -83203,52 +91607,59 @@ case OP_MakeRecord: {
nHdr += 1;
}else{
/* Rare case of a really large header */
- nVarint = sqlite3VarintLen(nHdr);
+ nVarint = tdsqlite3VarintLen(nHdr);
nHdr += nVarint;
- if( nVarint<sqlite3VarintLen(nHdr) ) nHdr++;
+ if( nVarint<tdsqlite3VarintLen(nHdr) ) nHdr++;
}
nByte = nHdr+nData;
- if( nByte+nZero>db->aLimit[SQLITE_LIMIT_LENGTH] ){
- goto too_big;
- }
/* Make sure the output register has a buffer large enough to store
** the new record. The output register (pOp->p3) is not allowed to
** be one of the input registers (because the following call to
- ** sqlite3VdbeMemClearAndResize() could clobber the value before it is used).
+ ** tdsqlite3VdbeMemClearAndResize() could clobber the value before it is used).
*/
- if( sqlite3VdbeMemClearAndResize(pOut, (int)nByte) ){
- goto no_mem;
+ if( nByte+nZero<=pOut->szMalloc ){
+ /* The output register is already large enough to hold the record.
+ ** No error checks or buffer enlargement is required */
+ pOut->z = pOut->zMalloc;
+ }else{
+ /* Need to make sure that the output is not too big and then enlarge
+ ** the output register to hold the full result */
+ if( nByte+nZero>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ goto too_big;
+ }
+ if( tdsqlite3VdbeMemClearAndResize(pOut, (int)nByte) ){
+ goto no_mem;
+ }
}
- zNewRecord = (u8 *)pOut->z;
+ pOut->n = (int)nByte;
+ pOut->flags = MEM_Blob;
+ if( nZero ){
+ pOut->u.nZero = nZero;
+ pOut->flags |= MEM_Zero;
+ }
+ UPDATE_MAX_BLOBSIZE(pOut);
+ zHdr = (u8 *)pOut->z;
+ zPayload = zHdr + nHdr;
/* Write the record */
- i = putVarint32(zNewRecord, nHdr);
- j = nHdr;
+ zHdr += putVarint32(zHdr, nHdr);
assert( pData0<=pLast );
pRec = pData0;
do{
serial_type = pRec->uTemp;
/* EVIDENCE-OF: R-06529-47362 Following the size varint are one or more
** additional varints, one per column. */
- i += putVarint32(&zNewRecord[i], serial_type); /* serial type */
+ zHdr += putVarint32(zHdr, serial_type); /* serial type */
/* EVIDENCE-OF: R-64536-51728 The values for each column in the record
** immediately follow the header. */
- j += sqlite3VdbeSerialPut(&zNewRecord[j], pRec, serial_type); /* content */
+ zPayload += tdsqlite3VdbeSerialPut(zPayload, pRec, serial_type); /* content */
}while( (++pRec)<=pLast );
- assert( i==nHdr );
- assert( j==nByte );
+ assert( nHdr==(int)(zHdr - (u8*)pOut->z) );
+ assert( nByte==(int)(zPayload - (u8*)pOut->z) );
assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
- pOut->n = (int)nByte;
- pOut->flags = MEM_Blob;
- if( nZero ){
- pOut->u.nZero = nZero;
- pOut->flags |= MEM_Zero;
- }
- pOut->enc = SQLITE_UTF8; /* In case the blob is ever converted to text */
REGISTER_TRACE(pOp->p3, pOut);
- UPDATE_MAX_BLOBSIZE(pOut);
break;
}
@@ -83267,19 +91678,20 @@ case OP_Count: { /* out2 */
pCrsr = p->apCsr[pOp->p1]->uc.pCursor;
assert( pCrsr );
nEntry = 0; /* Not needed. Only used to silence a warning. */
- rc = sqlite3BtreeCount(pCrsr, &nEntry);
+ rc = tdsqlite3BtreeCount(db, pCrsr, &nEntry);
if( rc ) goto abort_due_to_error;
pOut = out2Prerelease(p, pOp);
pOut->u.i = nEntry;
- break;
+ goto check_for_interrupt;
}
#endif
/* Opcode: Savepoint P1 * * P4 *
**
** Open, release or rollback the savepoint named by parameter P4, depending
-** on the value of P1. To open a new savepoint, P1==0. To release (commit) an
-** existing savepoint, P1==1, or to rollback an existing savepoint P1==2.
+** on the value of P1. To open a new savepoint set P1==0 (SAVEPOINT_BEGIN).
+** To release (commit) an existing savepoint set P1==1 (SAVEPOINT_RELEASE).
+** To rollback an existing savepoint set P1==2 (SAVEPOINT_ROLLBACK).
*/
case OP_Savepoint: {
int p1; /* Value of P1 operand */
@@ -83308,10 +91720,10 @@ case OP_Savepoint: {
/* A new savepoint cannot be created if there are active write
** statements (i.e. open read/write incremental blob handles).
*/
- sqlite3VdbeError(p, "cannot open savepoint - SQL statements in progress");
+ tdsqlite3VdbeError(p, "cannot open savepoint - SQL statements in progress");
rc = SQLITE_BUSY;
}else{
- nName = sqlite3Strlen30(zName);
+ nName = tdsqlite3Strlen30(zName);
#ifndef SQLITE_OMIT_VIRTUALTABLE
/* This call is Ok even if this savepoint is actually a transaction
@@ -83319,13 +91731,13 @@ case OP_Savepoint: {
** If this is a transaction savepoint being opened, it is guaranteed
** that the db->aVTrans[] array is empty. */
assert( db->autoCommit==0 || db->nVTrans==0 );
- rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN,
+ rc = tdsqlite3VtabSavepoint(db, SAVEPOINT_BEGIN,
db->nStatement+db->nSavepoint);
if( rc!=SQLITE_OK ) goto abort_due_to_error;
#endif
/* Create a new savepoint structure. */
- pNew = sqlite3DbMallocRawNN(db, sizeof(Savepoint)+nName+1);
+ pNew = tdsqlite3DbMallocRawNN(db, sizeof(Savepoint)+nName+1);
if( pNew ){
pNew->zName = (char *)&pNew[1];
memcpy(pNew->zName, zName, nName+1);
@@ -83347,25 +91759,26 @@ case OP_Savepoint: {
}
}
}else{
+ assert( p1==SAVEPOINT_RELEASE || p1==SAVEPOINT_ROLLBACK );
iSavepoint = 0;
/* Find the named savepoint. If there is no such savepoint, then an
** an error is returned to the user. */
for(
pSavepoint = db->pSavepoint;
- pSavepoint && sqlite3StrICmp(pSavepoint->zName, zName);
+ pSavepoint && tdsqlite3StrICmp(pSavepoint->zName, zName);
pSavepoint = pSavepoint->pNext
){
iSavepoint++;
}
if( !pSavepoint ){
- sqlite3VdbeError(p, "no such savepoint: %s", zName);
+ tdsqlite3VdbeError(p, "no such savepoint: %s", zName);
rc = SQLITE_ERROR;
}else if( db->nVdbeWrite>0 && p1==SAVEPOINT_RELEASE ){
/* It is not possible to release (commit) a savepoint if there are
** active write statements.
*/
- sqlite3VdbeError(p, "cannot release savepoint - "
+ tdsqlite3VdbeError(p, "cannot release savepoint - "
"SQL statements in progress");
rc = SQLITE_BUSY;
}else{
@@ -83376,51 +91789,57 @@ case OP_Savepoint: {
*/
int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint;
if( isTransaction && p1==SAVEPOINT_RELEASE ){
- if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
+ if( (rc = tdsqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
goto vdbe_return;
}
db->autoCommit = 1;
- if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
+ if( tdsqlite3VdbeHalt(p)==SQLITE_BUSY ){
p->pc = (int)(pOp - aOp);
db->autoCommit = 0;
p->rc = rc = SQLITE_BUSY;
goto vdbe_return;
}
- db->isTransactionSavepoint = 0;
rc = p->rc;
+ if( rc ){
+ db->autoCommit = 0;
+ }else{
+ db->isTransactionSavepoint = 0;
+ }
}else{
int isSchemaChange;
iSavepoint = db->nSavepoint - iSavepoint - 1;
if( p1==SAVEPOINT_ROLLBACK ){
- isSchemaChange = (db->flags & SQLITE_InternChanges)!=0;
+ isSchemaChange = (db->mDbFlags & DBFLAG_SchemaChange)!=0;
for(ii=0; ii<db->nDb; ii++){
- rc = sqlite3BtreeTripAllCursors(db->aDb[ii].pBt,
+ rc = tdsqlite3BtreeTripAllCursors(db->aDb[ii].pBt,
SQLITE_ABORT_ROLLBACK,
isSchemaChange==0);
if( rc!=SQLITE_OK ) goto abort_due_to_error;
}
}else{
+ assert( p1==SAVEPOINT_RELEASE );
isSchemaChange = 0;
}
for(ii=0; ii<db->nDb; ii++){
- rc = sqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint);
+ rc = tdsqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint);
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
}
}
if( isSchemaChange ){
- sqlite3ExpirePreparedStatements(db);
- sqlite3ResetAllSchemasOfConnection(db);
- db->flags = (db->flags | SQLITE_InternChanges);
+ tdsqlite3ExpirePreparedStatements(db, 0);
+ tdsqlite3ResetAllSchemasOfConnection(db);
+ db->mDbFlags |= DBFLAG_SchemaChange;
}
}
+ if( rc ) goto abort_due_to_error;
/* Regardless of whether this is a RELEASE or ROLLBACK, destroy all
** savepoints nested inside of the savepoint being operated on. */
while( db->pSavepoint!=pSavepoint ){
pTmp = db->pSavepoint;
db->pSavepoint = pTmp->pNext;
- sqlite3DbFree(db, pTmp);
+ tdsqlite3DbFree(db, pTmp);
db->nSavepoint--;
}
@@ -83431,17 +91850,18 @@ case OP_Savepoint: {
if( p1==SAVEPOINT_RELEASE ){
assert( pSavepoint==db->pSavepoint );
db->pSavepoint = pSavepoint->pNext;
- sqlite3DbFree(db, pSavepoint);
+ tdsqlite3DbFree(db, pSavepoint);
if( !isTransaction ){
db->nSavepoint--;
}
}else{
+ assert( p1==SAVEPOINT_ROLLBACK );
db->nDeferredCons = pSavepoint->nDeferredCons;
db->nDeferredImmCons = pSavepoint->nDeferredImmCons;
}
if( !isTransaction || p1==SAVEPOINT_ROLLBACK ){
- rc = sqlite3VtabSavepoint(db, p1, iSavepoint);
+ rc = tdsqlite3VtabSavepoint(db, p1, iSavepoint);
if( rc!=SQLITE_OK ) goto abort_due_to_error;
}
}
@@ -83474,29 +91894,28 @@ case OP_AutoCommit: {
if( desiredAutoCommit!=db->autoCommit ){
if( iRollback ){
assert( desiredAutoCommit==1 );
- sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
+ tdsqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK);
db->autoCommit = 1;
}else if( desiredAutoCommit && db->nVdbeWrite>0 ){
/* If this instruction implements a COMMIT and other VMs are writing
** return an error indicating that the other VMs must complete first.
*/
- sqlite3VdbeError(p, "cannot commit transaction - "
+ tdsqlite3VdbeError(p, "cannot commit transaction - "
"SQL statements in progress");
rc = SQLITE_BUSY;
goto abort_due_to_error;
- }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
+ }else if( (rc = tdsqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
goto vdbe_return;
}else{
db->autoCommit = (u8)desiredAutoCommit;
}
- if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
+ if( tdsqlite3VdbeHalt(p)==SQLITE_BUSY ){
p->pc = (int)(pOp - aOp);
db->autoCommit = (u8)(1-desiredAutoCommit);
p->rc = rc = SQLITE_BUSY;
goto vdbe_return;
}
- assert( db->nStatement==0 );
- sqlite3CloseSavepoints(db);
+ tdsqlite3CloseSavepoints(db);
if( p->rc==SQLITE_OK ){
rc = SQLITE_DONE;
}else{
@@ -83504,7 +91923,7 @@ case OP_AutoCommit: {
}
goto vdbe_return;
}else{
- sqlite3VdbeError(p,
+ tdsqlite3VdbeError(p,
(!desiredAutoCommit)?"cannot start a transaction within a transaction":(
(iRollback)?"cannot rollback - no transaction is active":
"cannot commit - no transaction is active"));
@@ -83512,7 +91931,7 @@ case OP_AutoCommit: {
rc = SQLITE_ERROR;
goto abort_due_to_error;
}
- break;
+ /*NOTREACHED*/ assert(0);
}
/* Opcode: Transaction P1 P2 P3 P4 P5
@@ -83546,13 +91965,12 @@ case OP_AutoCommit: {
** cookie in P3 differs from the schema cookie in the database header or
** if the schema generation counter in P4 differs from the current
** generation counter, then an SQLITE_SCHEMA error is raised and execution
-** halts. The sqlite3_step() wrapper function might then reprepare the
+** halts. The tdsqlite3_step() wrapper function might then reprepare the
** statement and rerun it from the beginning.
*/
case OP_Transaction: {
Btree *pBt;
- int iMeta;
- int iGen;
+ int iMeta = 0;
assert( p->bIsReader );
assert( p->readOnly==0 || pOp->p2==0 );
@@ -83565,7 +91983,7 @@ case OP_Transaction: {
pBt = db->aDb[pOp->p1].pBt;
if( pBt ){
- rc = sqlite3BtreeBeginTrans(pBt, pOp->p2);
+ rc = tdsqlite3BtreeBeginTrans(pBt, pOp->p2, &iMeta);
testcase( rc==SQLITE_BUSY_SNAPSHOT );
testcase( rc==SQLITE_BUSY_RECOVERY );
if( rc!=SQLITE_OK ){
@@ -83577,19 +91995,20 @@ case OP_Transaction: {
goto abort_due_to_error;
}
- if( pOp->p2 && p->usesStmtJournal
+ if( p->usesStmtJournal
+ && pOp->p2
&& (db->autoCommit==0 || db->nVdbeRead>1)
){
- assert( sqlite3BtreeIsInTrans(pBt) );
+ assert( tdsqlite3BtreeIsInTrans(pBt) );
if( p->iStatement==0 ){
assert( db->nStatement>=0 && db->nSavepoint>=0 );
db->nStatement++;
p->iStatement = db->nSavepoint + db->nStatement;
}
- rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement-1);
+ rc = tdsqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement-1);
if( rc==SQLITE_OK ){
- rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
+ rc = tdsqlite3BtreeBeginStmt(pBt, p->iStatement);
}
/* Store the current value of the database handles deferred constraint
@@ -83598,21 +92017,19 @@ case OP_Transaction: {
p->nStmtDefCons = db->nDeferredCons;
p->nStmtDefImmCons = db->nDeferredImmCons;
}
-
- /* Gather the schema version number for checking:
+ }
+ assert( pOp->p5==0 || pOp->p4type==P4_INT32 );
+ if( pOp->p5
+ && (iMeta!=pOp->p3
+ || db->aDb[pOp->p1].pSchema->iGeneration!=pOp->p4.i)
+ ){
+ /*
** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema
** version is checked to ensure that the schema has not changed since the
** SQL statement was prepared.
*/
- sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&iMeta);
- iGen = db->aDb[pOp->p1].pSchema->iGeneration;
- }else{
- iGen = iMeta = 0;
- }
- assert( pOp->p5==0 || pOp->p4type==P4_INT32 );
- if( pOp->p5 && (iMeta!=pOp->p3 || iGen!=pOp->p4.i) ){
- sqlite3DbFree(db, p->zErrMsg);
- p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed");
+ tdsqlite3DbFree(db, p->zErrMsg);
+ p->zErrMsg = tdsqlite3DbStrDup(db, "database schema has changed");
/* If the schema-cookie from the database file matches the cookie
** stored with the in-memory representation of the schema, do
** not reload the schema from the database file.
@@ -83622,12 +92039,12 @@ case OP_Transaction: {
** are queried from within xNext() and other v-table methods using
** prepared queries. If such a query is out-of-date, we do not want to
** discard the database schema, as the user code implementing the
- ** v-table would have to be ready for the sqlite3_vtab structure itself
- ** to be invalidated whenever sqlite3_step() is called from within
+ ** v-table would have to be ready for the tdsqlite3_vtab structure itself
+ ** to be invalidated whenever tdsqlite3_step() is called from within
** a v-table method.
*/
if( db->aDb[pOp->p1].pSchema->schema_cookie!=iMeta ){
- sqlite3ResetOneSchema(db, pOp->p1);
+ tdsqlite3ResetOneSchema(db, pOp->p1);
}
p->expired = 1;
rc = SQLITE_SCHEMA;
@@ -83661,7 +92078,7 @@ case OP_ReadCookie: { /* out2 */
assert( db->aDb[iDb].pBt!=0 );
assert( DbMaskTest(p->btreeMask, iDb) );
- sqlite3BtreeGetMeta(db->aDb[iDb].pBt, iCookie, (u32 *)&iMeta);
+ tdsqlite3BtreeGetMeta(db->aDb[iDb].pBt, iCookie, (u32 *)&iMeta);
pOut = out2Prerelease(p, pOp);
pOut->u.i = iMeta;
break;
@@ -83679,19 +92096,21 @@ case OP_ReadCookie: { /* out2 */
*/
case OP_SetCookie: {
Db *pDb;
+
+ tdsqlite3VdbeIncrWriteCounter(p, 0);
assert( pOp->p2<SQLITE_N_BTREE_META );
assert( pOp->p1>=0 && pOp->p1<db->nDb );
assert( DbMaskTest(p->btreeMask, pOp->p1) );
assert( p->readOnly==0 );
pDb = &db->aDb[pOp->p1];
assert( pDb->pBt!=0 );
- assert( sqlite3SchemaMutexHeld(db, pOp->p1, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, pOp->p1, 0) );
/* See note about index shifting on OP_ReadCookie */
- rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3);
+ rc = tdsqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3);
if( pOp->p2==BTREE_SCHEMA_VERSION ){
/* When the schema cookie changes, record the new cookie internally */
pDb->pSchema->schema_cookie = pOp->p3;
- db->flags |= SQLITE_InternChanges;
+ db->mDbFlags |= DBFLAG_SchemaChange;
}else if( pOp->p2==BTREE_FILE_FORMAT ){
/* Record changes in the file format */
pDb->pSchema->file_format = pOp->p3;
@@ -83699,7 +92118,7 @@ case OP_SetCookie: {
if( pOp->p1==1 ){
/* Invalidate all prepared statements whenever the TEMP database
** schema is changed. Ticket #1644 */
- sqlite3ExpirePreparedStatements(db);
+ tdsqlite3ExpirePreparedStatements(db, 0);
p->expired = 0;
}
if( rc ) goto abort_due_to_error;
@@ -83717,59 +92136,78 @@ case OP_SetCookie: {
** values need not be contiguous but all P1 values should be small integers.
** It is an error for P1 to be negative.
**
-** If P5!=0 then use the content of register P2 as the root page, not
-** the value of P2 itself.
-**
-** There will be a read lock on the database whenever there is an
-** open cursor. If the database was unlocked prior to this instruction
-** then a read lock is acquired as part of this instruction. A read
-** lock allows other processes to read the database but prohibits
-** any other process from modifying the database. The read lock is
-** released when all cursors are closed. If this instruction attempts
-** to get a read lock but fails, the script terminates with an
-** SQLITE_BUSY error code.
+** Allowed P5 bits:
+** <ul>
+** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
+** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
+** of OP_SeekLE/OP_IdxGT)
+** </ul>
**
** The P4 value may be either an integer (P4_INT32) or a pointer to
** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo
-** structure, then said structure defines the content and collating
-** sequence of the index being opened. Otherwise, if P4 is an integer
-** value, it is set to the number of columns in the table.
+** object, then table being opened must be an [index b-tree] where the
+** KeyInfo object defines the content and collating
+** sequence of that index b-tree. Otherwise, if P4 is an integer
+** value, then the table being opened must be a [table b-tree] with a
+** number of columns no less than the value of P4.
**
** See also: OpenWrite, ReopenIdx
*/
/* Opcode: ReopenIdx P1 P2 P3 P4 P5
** Synopsis: root=P2 iDb=P3
**
-** The ReopenIdx opcode works exactly like ReadOpen except that it first
-** checks to see if the cursor on P1 is already open with a root page
-** number of P2 and if it is this opcode becomes a no-op. In other words,
+** The ReopenIdx opcode works like OP_OpenRead except that it first
+** checks to see if the cursor on P1 is already open on the same
+** b-tree and if it is this opcode becomes a no-op. In other words,
** if the cursor is already open, do not reopen it.
**
-** The ReopenIdx opcode may only be used with P5==0 and with P4 being
-** a P4_KEYINFO object. Furthermore, the P3 value must be the same as
-** every other ReopenIdx or OpenRead for the same cursor number.
+** The ReopenIdx opcode may only be used with P5==0 or P5==OPFLAG_SEEKEQ
+** and with P4 being a P4_KEYINFO object. Furthermore, the P3 value must
+** be the same as every other ReopenIdx or OpenRead for the same cursor
+** number.
**
-** See the OpenRead opcode documentation for additional information.
+** Allowed P5 bits:
+** <ul>
+** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
+** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
+** of OP_SeekLE/OP_IdxGT)
+** </ul>
+**
+** See also: OP_OpenRead, OP_OpenWrite
*/
/* Opcode: OpenWrite P1 P2 P3 P4 P5
** Synopsis: root=P2 iDb=P3
**
** Open a read/write cursor named P1 on the table or index whose root
-** page is P2. Or if P5!=0 use the content of register P2 to find the
-** root page.
+** page is P2 (or whose root page is held in register P2 if the
+** OPFLAG_P2ISREG bit is set in P5 - see below).
**
** The P4 value may be either an integer (P4_INT32) or a pointer to
** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo
-** structure, then said structure defines the content and collating
-** sequence of the index being opened. Otherwise, if P4 is an integer
-** value, it is set to the number of columns in the table, or to the
-** largest index of any column of the table that is actually used.
+** object, then table being opened must be an [index b-tree] where the
+** KeyInfo object defines the content and collating
+** sequence of that index b-tree. Otherwise, if P4 is an integer
+** value, then the table being opened must be a [table b-tree] with a
+** number of columns no less than the value of P4.
**
-** This instruction works just like OpenRead except that it opens the cursor
-** in read/write mode. For a given table, there can be one or more read-only
-** cursors or a single read/write cursor but not both.
+** Allowed P5 bits:
+** <ul>
+** <li> <b>0x02 OPFLAG_SEEKEQ</b>: This cursor will only be used for
+** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT
+** of OP_SeekLE/OP_IdxGT)
+** <li> <b>0x08 OPFLAG_FORDELETE</b>: This cursor is used only to seek
+** and subsequently delete entries in an index btree. This is a
+** hint to the storage engine that the storage engine is allowed to
+** ignore. The hint is not used by the official SQLite b*tree storage
+** engine, but is used by COMDB2.
+** <li> <b>0x10 OPFLAG_P2ISREG</b>: Use the content of register P2
+** as the root page, not the value of P2 itself.
+** </ul>
**
-** See also OpenRead.
+** This instruction works like OpenRead except that it opens the cursor
+** in read/write mode.
+**
+** See also: OP_OpenRead, OP_ReopenIdx
*/
case OP_ReopenIdx: {
int nField;
@@ -83798,7 +92236,7 @@ case OP_OpenWrite:
assert( pOp->opcode==OP_OpenRead || pOp->opcode==OP_ReopenIdx
|| p->readOnly==0 );
- if( p->expired ){
+ if( p->expired==1 ){
rc = SQLITE_ABORT_ROLLBACK;
goto abort_due_to_error;
}
@@ -83815,7 +92253,7 @@ case OP_OpenWrite:
if( pOp->opcode==OP_OpenWrite ){
assert( OPFLAG_FORDELETE==BTREE_FORDELETE );
wrFlag = BTREE_WRCSR | (pOp->p5 & OPFLAG_FORDELETE);
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
if( pDb->pSchema->file_format < p->minWriteFileFormat ){
p->minWriteFileFormat = pDb->pSchema->file_format;
}
@@ -83825,12 +92263,13 @@ case OP_OpenWrite:
if( pOp->p5 & OPFLAG_P2ISREG ){
assert( p2>0 );
assert( p2<=(p->nMem+1 - p->nCursor) );
+ assert( pOp->opcode==OP_OpenWrite );
pIn2 = &aMem[p2];
assert( memIsValid(pIn2) );
assert( (pIn2->flags & MEM_Int)!=0 );
- sqlite3VdbeMemIntegerify(pIn2);
+ tdsqlite3VdbeMemIntegerify(pIn2);
p2 = (int)pIn2->u.i;
- /* The p2 value always comes from a prior OP_CreateTable opcode and
+ /* The p2 value always comes from a prior OP_CreateBtree opcode and
** that opcode will always set the p2 value to 2 or more or else fail.
** If there were a failure, the prepared statement would have halted
** before reaching this instruction. */
@@ -83840,7 +92279,7 @@ case OP_OpenWrite:
pKeyInfo = pOp->p4.pKeyInfo;
assert( pKeyInfo->enc==ENC(db) );
assert( pKeyInfo->db==db );
- nField = pKeyInfo->nField+pKeyInfo->nXField;
+ nField = pKeyInfo->nAllField;
}else if( pOp->p4type==P4_INT32 ){
nField = pOp->p4.i;
}
@@ -83855,7 +92294,7 @@ case OP_OpenWrite:
#ifdef SQLITE_DEBUG
pCur->wrFlag = wrFlag;
#endif
- rc = sqlite3BtreeCursor(pX, p2, wrFlag, pKeyInfo, pCur->uc.pCursor);
+ rc = tdsqlite3BtreeCursor(pX, p2, wrFlag, pKeyInfo, pCur->uc.pCursor);
pCur->pKeyInfo = pKeyInfo;
/* Set the VdbeCursor.isTable variable. Previous versions of
** SQLite used to check if the root-page flags were sane at this point
@@ -83870,12 +92309,46 @@ open_cursor_set_hints:
#ifdef SQLITE_ENABLE_CURSOR_HINTS
testcase( pOp->p2 & OPFLAG_SEEKEQ );
#endif
- sqlite3BtreeCursorHintFlags(pCur->uc.pCursor,
+ tdsqlite3BtreeCursorHintFlags(pCur->uc.pCursor,
(pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ)));
if( rc ) goto abort_due_to_error;
break;
}
+/* Opcode: OpenDup P1 P2 * * *
+**
+** Open a new cursor P1 that points to the same ephemeral table as
+** cursor P2. The P2 cursor must have been opened by a prior OP_OpenEphemeral
+** opcode. Only ephemeral cursors may be duplicated.
+**
+** Duplicate ephemeral cursors are used for self-joins of materialized views.
+*/
+case OP_OpenDup: {
+ VdbeCursor *pOrig; /* The original cursor to be duplicated */
+ VdbeCursor *pCx; /* The new cursor */
+
+ pOrig = p->apCsr[pOp->p2];
+ assert( pOrig );
+ assert( pOrig->pBtx!=0 ); /* Only ephemeral cursors can be duplicated */
+
+ pCx = allocateCursor(p, pOp->p1, pOrig->nField, -1, CURTYPE_BTREE);
+ if( pCx==0 ) goto no_mem;
+ pCx->nullRow = 1;
+ pCx->isEphemeral = 1;
+ pCx->pKeyInfo = pOrig->pKeyInfo;
+ pCx->isTable = pOrig->isTable;
+ pCx->pgnoRoot = pOrig->pgnoRoot;
+ pCx->isOrdered = pOrig->isOrdered;
+ rc = tdsqlite3BtreeCursor(pOrig->pBtx, pCx->pgnoRoot, BTREE_WRCSR,
+ pCx->pKeyInfo, pCx->uc.pCursor);
+ /* The tdsqlite3BtreeCursor() routine can only fail for the first cursor
+ ** opened for a database. Since there is already an open cursor when this
+ ** opcode is run, the tdsqlite3BtreeCursor() cannot fail */
+ assert( rc==SQLITE_OK );
+ break;
+}
+
+
/* Opcode: OpenEphemeral P1 P2 * P4 P5
** Synopsis: nColumn=P2
**
@@ -83884,6 +92357,9 @@ open_cursor_set_hints:
** the main database is read-only. The ephemeral
** table is deleted automatically when the cursor is closed.
**
+** If the cursor P1 is already opened on an ephemeral table, the table
+** is cleared (all content is erased).
+**
** P2 is the number of columns in the ephemeral table.
** The cursor points to a BTree table if P4==0 and to a BTree index
** if P4 is not 0. If P4 is not NULL, it points to a KeyInfo structure
@@ -83915,42 +92391,53 @@ case OP_OpenEphemeral: {
SQLITE_OPEN_TRANSIENT_DB;
assert( pOp->p1>=0 );
assert( pOp->p2>=0 );
- pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE);
- if( pCx==0 ) goto no_mem;
- pCx->nullRow = 1;
- pCx->isEphemeral = 1;
- rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBt,
- BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags);
- if( rc==SQLITE_OK ){
- rc = sqlite3BtreeBeginTrans(pCx->pBt, 1);
- }
- if( rc==SQLITE_OK ){
- /* If a transient index is required, create it by calling
- ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before
- ** opening it. If a transient table is required, just use the
- ** automatically created table with root-page 1 (an BLOB_INTKEY table).
- */
- if( (pKeyInfo = pOp->p4.pKeyInfo)!=0 ){
- int pgno;
- assert( pOp->p4type==P4_KEYINFO );
- rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_BLOBKEY | pOp->p5);
- if( rc==SQLITE_OK ){
- assert( pgno==MASTER_ROOT+1 );
- assert( pKeyInfo->db==db );
- assert( pKeyInfo->enc==ENC(db) );
- pCx->pKeyInfo = pKeyInfo;
- rc = sqlite3BtreeCursor(pCx->pBt, pgno, BTREE_WRCSR,
- pKeyInfo, pCx->uc.pCursor);
+ pCx = p->apCsr[pOp->p1];
+ if( pCx && pCx->pBtx ){
+ /* If the ephermeral table is already open, erase all existing content
+ ** so that the table is empty again, rather than creating a new table. */
+ assert( pCx->isEphemeral );
+ pCx->seqCount = 0;
+ pCx->cacheStatus = CACHE_STALE;
+ rc = tdsqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0);
+ }else{
+ pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE);
+ if( pCx==0 ) goto no_mem;
+ pCx->isEphemeral = 1;
+ rc = tdsqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx,
+ BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5,
+ vfsFlags);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3BtreeBeginTrans(pCx->pBtx, 1, 0);
+ }
+ if( rc==SQLITE_OK ){
+ /* If a transient index is required, create it by calling
+ ** tdsqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before
+ ** opening it. If a transient table is required, just use the
+ ** automatically created table with root-page 1 (an BLOB_INTKEY table).
+ */
+ if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){
+ assert( pOp->p4type==P4_KEYINFO );
+ rc = tdsqlite3BtreeCreateTable(pCx->pBtx, (int*)&pCx->pgnoRoot,
+ BTREE_BLOBKEY | pOp->p5);
+ if( rc==SQLITE_OK ){
+ assert( pCx->pgnoRoot==MASTER_ROOT+1 );
+ assert( pKeyInfo->db==db );
+ assert( pKeyInfo->enc==ENC(db) );
+ rc = tdsqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR,
+ pKeyInfo, pCx->uc.pCursor);
+ }
+ pCx->isTable = 0;
+ }else{
+ pCx->pgnoRoot = MASTER_ROOT;
+ rc = tdsqlite3BtreeCursor(pCx->pBtx, MASTER_ROOT, BTREE_WRCSR,
+ 0, pCx->uc.pCursor);
+ pCx->isTable = 1;
}
- pCx->isTable = 0;
- }else{
- rc = sqlite3BtreeCursor(pCx->pBt, MASTER_ROOT, BTREE_WRCSR,
- 0, pCx->uc.pCursor);
- pCx->isTable = 1;
}
+ pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);
}
if( rc ) goto abort_due_to_error;
- pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);
+ pCx->nullRow = 1;
break;
}
@@ -83974,7 +92461,7 @@ case OP_SorterOpen: {
pCx->pKeyInfo = pOp->p4.pKeyInfo;
assert( pCx->pKeyInfo->db==db );
assert( pCx->pKeyInfo->enc==ENC(db) );
- rc = sqlite3VdbeSorterInit(db, pOp->p3, pCx);
+ rc = tdsqlite3VdbeSorterInit(db, pOp->p3, pCx);
if( rc ) goto abort_due_to_error;
break;
}
@@ -84021,8 +92508,13 @@ case OP_OpenPseudo: {
pCx = allocateCursor(p, pOp->p1, pOp->p3, -1, CURTYPE_PSEUDO);
if( pCx==0 ) goto no_mem;
pCx->nullRow = 1;
- pCx->uc.pseudoTableReg = pOp->p2;
+ pCx->seekResult = pOp->p2;
pCx->isTable = 1;
+ /* Give this pseudo-cursor a fake BtCursor pointer so that pCx
+ ** can be safely passed to tdsqlite3VdbeCursorMoveto(). This avoids a test
+ ** for pCx->eCurType==CURTYPE_BTREE inside of tdsqlite3VdbeCursorMoveto()
+ ** which is a performance optimization */
+ pCx->uc.pCursor = tdsqlite3BtreeFakeValidCursor();
assert( pOp->p5==0 );
break;
}
@@ -84034,7 +92526,7 @@ case OP_OpenPseudo: {
*/
case OP_Close: {
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
- sqlite3VdbeFreeCursor(p, p->apCsr[pOp->p1]);
+ tdsqlite3VdbeFreeCursor(p, p->apCsr[pOp->p1]);
p->apCsr[pOp->p1] = 0;
break;
}
@@ -84145,10 +92637,10 @@ case OP_ColumnsUsed: {
**
** See also: Found, NotFound, SeekGt, SeekGe, SeekLt
*/
-case OP_SeekLT: /* jump, in3 */
-case OP_SeekLE: /* jump, in3 */
-case OP_SeekGE: /* jump, in3 */
-case OP_SeekGT: { /* jump, in3 */
+case OP_SeekLT: /* jump, in3, group */
+case OP_SeekLE: /* jump, in3, group */
+case OP_SeekGE: /* jump, in3, group */
+case OP_SeekGT: { /* jump, in3, group */
int res; /* Comparison result */
int oc; /* Opcode */
VdbeCursor *pC; /* The cursor to seek */
@@ -84174,28 +92666,39 @@ case OP_SeekGT: { /* jump, in3 */
pC->seekOp = pOp->opcode;
#endif
+ pC->deferredMoveto = 0;
+ pC->cacheStatus = CACHE_STALE;
if( pC->isTable ){
+ u16 flags3, newType;
/* The BTREE_SEEK_EQ flag is only set on index cursors */
- assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0 );
+ assert( tdsqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0
+ || CORRUPT_DB );
/* The input value in P3 might be of any type: integer, real, string,
** blob, or NULL. But it needs to be an integer before we can do
** the seek, so convert it. */
pIn3 = &aMem[pOp->p3];
- if( (pIn3->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){
+ flags3 = pIn3->flags;
+ if( (flags3 & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Str))==MEM_Str ){
applyNumericAffinity(pIn3, 0);
}
- iKey = sqlite3VdbeIntValue(pIn3);
+ iKey = tdsqlite3VdbeIntValue(pIn3); /* Get the integer key value */
+ newType = pIn3->flags; /* Record the type after applying numeric affinity */
+ pIn3->flags = flags3; /* But convert the type back to its original */
/* If the P3 value could not be converted into an integer without
** loss of information, then special processing is required... */
- if( (pIn3->flags & MEM_Int)==0 ){
- if( (pIn3->flags & MEM_Real)==0 ){
- /* If the P3 value cannot be converted into any kind of a number,
- ** then the seek is not possible, so jump to P2 */
- VdbeBranchTaken(1,2); goto jump_to_p2;
- break;
- }
+ if( (newType & (MEM_Int|MEM_IntReal))==0 ){
+ if( (newType & MEM_Real)==0 ){
+ if( (newType & MEM_Null) || oc>=OP_SeekGE ){
+ VdbeBranchTaken(1,2);
+ goto jump_to_p2;
+ }else{
+ rc = tdsqlite3BtreeLast(pC->uc.pCursor, &res);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ goto seek_not_found;
+ }
+ }else
/* If the approximation iKey is larger than the actual real search
** term, substitute >= for > and < for <=. e.g. if the search term
@@ -84219,8 +92722,8 @@ case OP_SeekGT: { /* jump, in3 */
assert( (OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001) );
if( (oc & 0x0001)==(OP_SeekLT & 0x0001) ) oc++;
}
- }
- rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)iKey, 0, &res);
+ }
+ rc = tdsqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)iKey, 0, &res);
pC->movetoTarget = iKey; /* Used by OP_Delete */
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
@@ -84230,7 +92733,7 @@ case OP_SeekGT: { /* jump, in3 */
** OP_SeekLE opcodes are allowed, and these must be immediately followed
** by an OP_IdxGT or OP_IdxLT opcode, respectively, with the same key.
*/
- if( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ) ){
+ if( tdsqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ) ){
eqOnly = 1;
assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE );
assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT );
@@ -84264,7 +92767,7 @@ case OP_SeekGT: { /* jump, in3 */
{ int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); }
#endif
r.eqSeen = 0;
- rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, &r, 0, 0, &res);
+ rc = tdsqlite3BtreeMovetoUnpacked(pC->uc.pCursor, &r, 0, 0, &res);
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
}
@@ -84273,16 +92776,21 @@ case OP_SeekGT: { /* jump, in3 */
goto seek_not_found;
}
}
- pC->deferredMoveto = 0;
- pC->cacheStatus = CACHE_STALE;
#ifdef SQLITE_TEST
- sqlite3_search_count++;
+ tdsqlite3_search_count++;
#endif
if( oc>=OP_SeekGE ){ assert( oc==OP_SeekGE || oc==OP_SeekGT );
if( res<0 || (res==0 && oc==OP_SeekGT) ){
res = 0;
- rc = sqlite3BtreeNext(pC->uc.pCursor, &res);
- if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ rc = tdsqlite3BtreeNext(pC->uc.pCursor, 0);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ res = 1;
+ }else{
+ goto abort_due_to_error;
+ }
+ }
}else{
res = 0;
}
@@ -84290,13 +92798,20 @@ case OP_SeekGT: { /* jump, in3 */
assert( oc==OP_SeekLT || oc==OP_SeekLE );
if( res>0 || (res==0 && oc==OP_SeekLT) ){
res = 0;
- rc = sqlite3BtreePrevious(pC->uc.pCursor, &res);
- if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ rc = tdsqlite3BtreePrevious(pC->uc.pCursor, 0);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ res = 1;
+ }else{
+ goto abort_due_to_error;
+ }
+ }
}else{
/* res might be negative because the table is empty. Check to
** see if this is the case.
*/
- res = sqlite3BtreeEof(pC->uc.pCursor);
+ res = tdsqlite3BtreeEof(pC->uc.pCursor);
}
}
seek_not_found:
@@ -84311,6 +92826,39 @@ seek_not_found:
break;
}
+/* Opcode: SeekHit P1 P2 * * *
+** Synopsis: seekHit=P2
+**
+** Set the seekHit flag on cursor P1 to the value in P2.
+* The seekHit flag is used by the IfNoHope opcode.
+**
+** P1 must be a valid b-tree cursor. P2 must be a boolean value,
+** either 0 or 1.
+*/
+case OP_SeekHit: {
+ VdbeCursor *pC;
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ assert( pOp->p2==0 || pOp->p2==1 );
+ pC->seekHit = pOp->p2 & 1;
+ break;
+}
+
+/* Opcode: IfNotOpen P1 P2 * * *
+** Synopsis: if( !csr[P1] ) goto P2
+**
+** If cursor P1 is not open, jump to instruction P2. Otherwise, fall through.
+*/
+case OP_IfNotOpen: { /* jump */
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ VdbeBranchTaken(p->apCsr[pOp->p1]==0, 2);
+ if( !p->apCsr[pOp->p1] ){
+ goto jump_to_p2_and_check_for_interrupt;
+ }
+ break;
+}
+
/* Opcode: Found P1 P2 P3 P4 *
** Synopsis: key=r[P3@P4]
**
@@ -84345,7 +92893,34 @@ seek_not_found:
** advanced in either direction. In other words, the Next and Prev
** opcodes do not work after this operation.
**
-** See also: Found, NotExists, NoConflict
+** See also: Found, NotExists, NoConflict, IfNoHope
+*/
+/* Opcode: IfNoHope P1 P2 P3 P4 *
+** Synopsis: key=r[P3@P4]
+**
+** Register P3 is the first of P4 registers that form an unpacked
+** record.
+**
+** Cursor P1 is on an index btree. If the seekHit flag is set on P1, then
+** this opcode is a no-op. But if the seekHit flag of P1 is clear, then
+** check to see if there is any entry in P1 that matches the
+** prefix identified by P3 and P4. If no entry matches the prefix,
+** jump to P2. Otherwise fall through.
+**
+** This opcode behaves like OP_NotFound if the seekHit
+** flag is clear and it behaves like OP_Noop if the seekHit flag is set.
+**
+** This opcode is used in IN clause processing for a multi-column key.
+** If an IN clause is attached to an element of the key other than the
+** left-most element, and if there are no matches on the most recent
+** seek over the whole key, then it might be that one of the key element
+** to the left is prohibiting a match, and hence there is "no hope" of
+** any match regardless of how many IN clause elements are checked.
+** In such a case, we abandon the IN clause search early, using this
+** opcode. The opcode name comes from the fact that the
+** jump is taken if there is "no hope" of achieving a match.
+**
+** See also: NotFound, SeekHit
*/
/* Opcode: NoConflict P1 P2 P3 P4 *
** Synopsis: key=r[P3@P4]
@@ -84370,6 +92945,14 @@ seek_not_found:
**
** See also: NotFound, Found, NotExists
*/
+case OP_IfNoHope: { /* jump, in3 */
+ VdbeCursor *pC;
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ if( pC->seekHit ) break;
+ /* Fall through into OP_NotFound */
+}
case OP_NoConflict: /* jump, in3 */
case OP_NotFound: /* jump, in3 */
case OP_Found: { /* jump, in3 */
@@ -84378,13 +92961,12 @@ case OP_Found: { /* jump, in3 */
int ii;
VdbeCursor *pC;
int res;
- char *pFree;
+ UnpackedRecord *pFree;
UnpackedRecord *pIdxKey;
UnpackedRecord r;
- char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*4 + 7];
#ifdef SQLITE_TEST
- if( pOp->opcode!=OP_NoConflict ) sqlite3_found_count++;
+ if( pOp->opcode!=OP_NoConflict ) tdsqlite3_found_count++;
#endif
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
@@ -84398,7 +92980,6 @@ case OP_Found: { /* jump, in3 */
assert( pC->eCurType==CURTYPE_BTREE );
assert( pC->uc.pCursor!=0 );
assert( pC->isTable==0 );
- pFree = 0;
if( pOp->p4.i>0 ){
r.pKeyInfo = pC->pKeyInfo;
r.nField = (u16)pOp->p4.i;
@@ -84411,14 +92992,15 @@ case OP_Found: { /* jump, in3 */
}
#endif
pIdxKey = &r;
+ pFree = 0;
}else{
- pIdxKey = sqlite3VdbeAllocUnpackedRecord(
- pC->pKeyInfo, aTempRec, sizeof(aTempRec), &pFree
- );
- if( pIdxKey==0 ) goto no_mem;
assert( pIn3->flags & MEM_Blob );
- (void)ExpandBlob(pIn3);
- sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey);
+ rc = ExpandBlob(pIn3);
+ assert( rc==SQLITE_OK || rc==SQLITE_NOMEM );
+ if( rc ) goto no_mem;
+ pFree = pIdxKey = tdsqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo);
+ if( pIdxKey==0 ) goto no_mem;
+ tdsqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey);
}
pIdxKey->default_rc = 0;
takeJump = 0;
@@ -84433,8 +93015,8 @@ case OP_Found: { /* jump, in3 */
}
}
}
- rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, pIdxKey, 0, 0, &res);
- sqlite3DbFree(db, pFree);
+ rc = tdsqlite3BtreeMovetoUnpacked(pC->uc.pCursor, pIdxKey, 0, 0, &res);
+ if( pFree ) tdsqlite3DbFreeNN(db, pFree);
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
}
@@ -84507,27 +93089,40 @@ case OP_SeekRowid: { /* jump, in3 */
u64 iKey;
pIn3 = &aMem[pOp->p3];
- if( (pIn3->flags & MEM_Int)==0 ){
- applyAffinity(pIn3, SQLITE_AFF_NUMERIC, encoding);
- if( (pIn3->flags & MEM_Int)==0 ) goto jump_to_p2;
+ testcase( pIn3->flags & MEM_Int );
+ testcase( pIn3->flags & MEM_IntReal );
+ testcase( pIn3->flags & MEM_Real );
+ testcase( (pIn3->flags & (MEM_Str|MEM_Int))==MEM_Str );
+ if( (pIn3->flags & (MEM_Int|MEM_IntReal))==0 ){
+ /* If pIn3->u.i does not contain an integer, compute iKey as the
+ ** integer value of pIn3. Jump to P2 if pIn3 cannot be converted
+ ** into an integer without loss of information. Take care to avoid
+ ** changing the datatype of pIn3, however, as it is used by other
+ ** parts of the prepared statement. */
+ Mem x = pIn3[0];
+ applyAffinity(&x, SQLITE_AFF_NUMERIC, encoding);
+ if( (x.flags & MEM_Int)==0 ) goto jump_to_p2;
+ iKey = x.u.i;
+ goto notExistsWithKey;
}
/* Fall through into OP_NotExists */
case OP_NotExists: /* jump, in3 */
pIn3 = &aMem[pOp->p3];
- assert( pIn3->flags & MEM_Int );
+ assert( (pIn3->flags & MEM_Int)!=0 || pOp->opcode==OP_SeekRowid );
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ iKey = pIn3->u.i;
+notExistsWithKey:
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
#ifdef SQLITE_DEBUG
- pC->seekOp = 0;
+ if( pOp->opcode==OP_SeekRowid ) pC->seekOp = OP_SeekRowid;
#endif
assert( pC->isTable );
assert( pC->eCurType==CURTYPE_BTREE );
pCrsr = pC->uc.pCursor;
assert( pCrsr!=0 );
res = 0;
- iKey = pIn3->u.i;
- rc = sqlite3BtreeMovetoUnpacked(pCrsr, 0, iKey, 0, &res);
+ rc = tdsqlite3BtreeMovetoUnpacked(pCrsr, 0, iKey, 0, &res);
assert( rc==SQLITE_OK || res==0 );
pC->movetoTarget = iKey; /* Used by OP_Delete */
pC->nullRow = 0;
@@ -84583,7 +93178,7 @@ case OP_Sequence: { /* out2 */
case OP_NewRowid: { /* out2 */
i64 v; /* The new rowid */
VdbeCursor *pC; /* Cursor of table to get the new rowid */
- int res; /* Result of an sqlite3BtreeLast() */
+ int res; /* Result of an tdsqlite3BtreeLast() */
int cnt; /* Counter to limit the number of searches */
Mem *pMem; /* Register holding largest rowid for AUTOINCREMENT */
VdbeFrame *pFrame; /* Root frame of VDBE */
@@ -84594,6 +93189,7 @@ case OP_NewRowid: { /* out2 */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
+ assert( pC->isTable );
assert( pC->eCurType==CURTYPE_BTREE );
assert( pC->uc.pCursor!=0 );
{
@@ -84623,15 +93219,15 @@ case OP_NewRowid: { /* out2 */
#endif
if( !pC->useRandomRowid ){
- rc = sqlite3BtreeLast(pC->uc.pCursor, &res);
+ rc = tdsqlite3BtreeLast(pC->uc.pCursor, &res);
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
}
if( res ){
v = 1; /* IMP: R-61914-48074 */
}else{
- assert( sqlite3BtreeCursorIsValid(pC->uc.pCursor) );
- v = sqlite3BtreeIntegerKey(pC->uc.pCursor);
+ assert( tdsqlite3BtreeCursorIsValid(pC->uc.pCursor) );
+ v = tdsqlite3BtreeIntegerKey(pC->uc.pCursor);
if( v>=MAX_ROWID ){
pC->useRandomRowid = 1;
}else{
@@ -84658,10 +93254,10 @@ case OP_NewRowid: { /* out2 */
assert( memIsValid(pMem) );
REGISTER_TRACE(pOp->p3, pMem);
- sqlite3VdbeMemIntegerify(pMem);
+ tdsqlite3VdbeMemIntegerify(pMem);
assert( (pMem->flags & MEM_Int)!=0 ); /* mem(P3) holds an integer */
if( pMem->u.i==MAX_ROWID || pC->useRandomRowid ){
- rc = SQLITE_FULL; /* IMP: R-12275-61338 */
+ rc = SQLITE_FULL; /* IMP: R-17817-00630 */
goto abort_due_to_error;
}
if( v<pMem->u.i+1 ){
@@ -84679,9 +93275,9 @@ case OP_NewRowid: { /* out2 */
** an AUTOINCREMENT table. */
cnt = 0;
do{
- sqlite3_randomness(sizeof(v), &v);
+ tdsqlite3_randomness(sizeof(v), &v);
v &= (MAX_ROWID>>1); v++; /* Ensure that v is greater than zero */
- }while( ((rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)v,
+ }while( ((rc = tdsqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)v,
0, &res))==SQLITE_OK)
&& (res==0)
&& (++cnt<100));
@@ -84711,17 +93307,12 @@ case OP_NewRowid: { /* out2 */
** If the OPFLAG_NCHANGE flag of P5 is set, then the row change count is
** incremented (otherwise not). If the OPFLAG_LASTROWID flag of P5 is set,
** then rowid is stored for subsequent return by the
-** sqlite3_last_insert_rowid() function (otherwise it is unmodified).
-**
-** If the OPFLAG_USESEEKRESULT flag of P5 is set and if the result of
-** the last seek operation (OP_NotExists or OP_SeekRowid) was a success,
-** then this
-** operation will not attempt to find the appropriate row before doing
-** the insert but will instead overwrite the row that the cursor is
-** currently pointing to. Presumably, the prior OP_NotExists or
-** OP_SeekRowid opcode
-** has already positioned the cursor correctly. This is an optimization
-** that boosts performance by avoiding redundant seeks.
+** tdsqlite3_last_insert_rowid() function (otherwise it is unmodified).
+**
+** If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might
+** run faster by avoiding an unnecessary seek on cursor P1. However,
+** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior
+** seeks on the cursor or if the most recent seek used a key equal to P3.
**
** If the OPFLAG_ISUPDATE flag is set, then this opcode is part of an
** UPDATE operation. Otherwise (if the flag is clear) then this opcode
@@ -84741,78 +93332,63 @@ case OP_NewRowid: { /* out2 */
** This instruction only works on tables. The equivalent instruction
** for indices is OP_IdxInsert.
*/
-/* Opcode: InsertInt P1 P2 P3 P4 P5
-** Synopsis: intkey=P3 data=r[P2]
-**
-** This works exactly like OP_Insert except that the key is the
-** integer value P3, not the value of the integer stored in register P3.
-*/
-case OP_Insert:
-case OP_InsertInt: {
+case OP_Insert: {
Mem *pData; /* MEM cell holding data for the record to be inserted */
Mem *pKey; /* MEM cell holding key for the record */
VdbeCursor *pC; /* Cursor to table into which insert is written */
int seekResult; /* Result of prior seek or 0 if no USESEEKRESULT flag */
const char *zDb; /* database name - used by the update hook */
Table *pTab; /* Table structure - used by update and pre-update hooks */
- int op; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */
BtreePayload x; /* Payload to be inserted */
- op = 0;
pData = &aMem[pOp->p2];
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
assert( memIsValid(pData) );
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
assert( pC->eCurType==CURTYPE_BTREE );
+ assert( pC->deferredMoveto==0 );
assert( pC->uc.pCursor!=0 );
- assert( pC->isTable );
+ assert( (pOp->p5 & OPFLAG_ISNOOP) || pC->isTable );
assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC );
REGISTER_TRACE(pOp->p2, pData);
+ tdsqlite3VdbeIncrWriteCounter(p, pC);
- if( pOp->opcode==OP_Insert ){
- pKey = &aMem[pOp->p3];
- assert( pKey->flags & MEM_Int );
- assert( memIsValid(pKey) );
- REGISTER_TRACE(pOp->p3, pKey);
- x.nKey = pKey->u.i;
- }else{
- assert( pOp->opcode==OP_InsertInt );
- x.nKey = pOp->p3;
- }
+ pKey = &aMem[pOp->p3];
+ assert( pKey->flags & MEM_Int );
+ assert( memIsValid(pKey) );
+ REGISTER_TRACE(pOp->p3, pKey);
+ x.nKey = pKey->u.i;
if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){
- assert( pC->isTable );
assert( pC->iDb>=0 );
zDb = db->aDb[pC->iDb].zDbSName;
pTab = pOp->p4.pTab;
- assert( HasRowid(pTab) );
- op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
+ assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) );
}else{
- pTab = 0; /* Not needed. Silence a comiler warning. */
+ pTab = 0;
zDb = 0; /* Not needed. Silence a compiler warning. */
}
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/* Invoke the pre-update hook, if any */
- if( db->xPreUpdateCallback
- && pOp->p4type==P4_TABLE
- && !(pOp->p5 & OPFLAG_ISUPDATE)
- ){
- sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey, pOp->p2);
+ if( pTab ){
+ if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){
+ tdsqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey,pOp->p2);
+ }
+ if( db->xUpdateCallback==0 || pTab->aCol==0 ){
+ /* Prevent post-update hook from running in cases when it should not */
+ pTab = 0;
+ }
}
+ if( pOp->p5 & OPFLAG_ISNOOP ) break;
#endif
if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++;
- if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = lastRowid = x.nKey;
- if( pData->flags & MEM_Null ){
- x.pData = 0;
- x.nData = 0;
- }else{
- assert( pData->flags & (MEM_Blob|MEM_Str) );
- x.pData = pData->z;
- x.nData = pData->n;
- }
+ if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey;
+ assert( pData->flags & (MEM_Blob|MEM_Str) );
+ x.pData = pData->z;
+ x.nData = pData->n;
seekResult = ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0);
if( pData->flags & MEM_Zero ){
x.nZero = pData->u.nZero;
@@ -84820,16 +93396,20 @@ case OP_InsertInt: {
x.nZero = 0;
}
x.pKey = 0;
- rc = sqlite3BtreeInsert(pC->uc.pCursor, &x,
- (pOp->p5 & OPFLAG_APPEND)!=0, seekResult
+ rc = tdsqlite3BtreeInsert(pC->uc.pCursor, &x,
+ (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)), seekResult
);
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
/* Invoke the update-hook if required. */
if( rc ) goto abort_due_to_error;
- if( db->xUpdateCallback && op ){
- db->xUpdateCallback(db->pUpdateArg, op, zDb, pTab->zName, x.nKey);
+ if( pTab ){
+ assert( db->xUpdateCallback!=0 );
+ assert( pTab->aCol!=0 );
+ db->xUpdateCallback(db->pUpdateArg,
+ (pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT,
+ zDb, pTab->zName, x.nKey);
}
break;
}
@@ -84882,14 +93462,19 @@ case OP_Delete: {
assert( pC->eCurType==CURTYPE_BTREE );
assert( pC->uc.pCursor!=0 );
assert( pC->deferredMoveto==0 );
+ tdsqlite3VdbeIncrWriteCounter(p, pC);
#ifdef SQLITE_DEBUG
- if( pOp->p4type==P4_TABLE && HasRowid(pOp->p4.pTab) && pOp->p5==0 ){
+ if( pOp->p4type==P4_TABLE
+ && HasRowid(pOp->p4.pTab)
+ && pOp->p5==0
+ && tdsqlite3BtreeCursorIsValidNN(pC->uc.pCursor)
+ ){
/* If p5 is zero, the seek operation that positioned the cursor prior to
** OP_Delete will have also set the pC->movetoTarget field to the rowid of
** the row that is being deleted */
- i64 iKey = sqlite3BtreeIntegerKey(pC->uc.pCursor);
- assert( pC->movetoTarget==iKey );
+ i64 iKey = tdsqlite3BtreeIntegerKey(pC->uc.pCursor);
+ assert( CORRUPT_DB || pC->movetoTarget==iKey );
}
#endif
@@ -84904,7 +93489,7 @@ case OP_Delete: {
zDb = db->aDb[pC->iDb].zDbSName;
pTab = pOp->p4.pTab;
if( (pOp->p5 & OPFLAG_SAVEPOSITION)!=0 && pC->isTable ){
- pC->movetoTarget = sqlite3BtreeIntegerKey(pC->uc.pCursor);
+ pC->movetoTarget = tdsqlite3BtreeIntegerKey(pC->uc.pCursor);
}
}else{
zDb = 0; /* Not needed. Silence a compiler warning. */
@@ -84913,9 +93498,12 @@ case OP_Delete: {
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/* Invoke the pre-update-hook if required. */
- if( db->xPreUpdateCallback && pOp->p4.pTab && HasRowid(pTab) ){
- assert( !(opflags & OPFLAG_ISUPDATE) || (aMem[pOp->p3].flags & MEM_Int) );
- sqlite3VdbePreUpdateHook(p, pC,
+ if( db->xPreUpdateCallback && pOp->p4.pTab ){
+ assert( !(opflags & OPFLAG_ISUPDATE)
+ || HasRowid(pTab)==0
+ || (aMem[pOp->p3].flags & MEM_Int)
+ );
+ tdsqlite3VdbePreUpdateHook(p, pC,
(opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE,
zDb, pTab, pC->movetoTarget,
pOp->p3
@@ -84943,8 +93531,9 @@ case OP_Delete: {
}
#endif
- rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5);
+ rc = tdsqlite3BtreeDelete(pC->uc.pCursor, pOp->p5);
pC->cacheStatus = CACHE_STALE;
+ pC->seekResult = 0;
if( rc ) goto abort_due_to_error;
/* Invoke the update-hook if required. */
@@ -84962,12 +93551,12 @@ case OP_Delete: {
/* Opcode: ResetCount * * * * *
**
** The value of the change counter is copied to the database handle
-** change counter (returned by subsequent calls to sqlite3_changes()).
+** change counter (returned by subsequent calls to tdsqlite3_changes()).
** Then the VMs internal change counter resets to 0.
** This is used by trigger programs.
*/
case OP_ResetCount: {
- sqlite3VdbeSetChanges(db, p->nChange);
+ tdsqlite3VdbeSetChanges(db, p->nChange);
p->nChange = 0;
break;
}
@@ -84998,7 +93587,7 @@ case OP_SorterCompare: {
pIn3 = &aMem[pOp->p3];
nKeyCol = pOp->p4.i;
res = 0;
- rc = sqlite3VdbeSorterCompare(pC, pIn3, nKeyCol, &res);
+ rc = tdsqlite3VdbeSorterCompare(pC, pIn3, nKeyCol, &res);
VdbeBranchTaken(res!=0,2);
if( rc ) goto abort_due_to_error;
if( res ) goto jump_to_p2;
@@ -85023,7 +93612,7 @@ case OP_SorterData: {
pOut = &aMem[pOp->p2];
pC = p->apCsr[pOp->p1];
assert( isSorter(pC) );
- rc = sqlite3VdbeSorterRowkey(pC, pOut);
+ rc = tdsqlite3VdbeSorterRowkey(pC, pOut);
assert( rc!=SQLITE_OK || (pOut->flags & MEM_Blob) );
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
if( rc ) goto abort_due_to_error;
@@ -85031,81 +93620,73 @@ case OP_SorterData: {
break;
}
-/* Opcode: RowData P1 P2 * * *
+/* Opcode: RowData P1 P2 P3 * *
** Synopsis: r[P2]=data
**
-** Write into register P2 the complete row data for cursor P1.
+** Write into register P2 the complete row content for the row at
+** which cursor P1 is currently pointing.
** There is no interpretation of the data.
** It is just copied onto the P2 register exactly as
** it is found in the database file.
**
+** If cursor P1 is an index, then the content is the key of the row.
+** If cursor P2 is a table, then the content extracted is the data.
+**
** If the P1 cursor must be pointing to a valid row (not a NULL row)
** of a real table, not a pseudo-table.
-*/
-/* Opcode: RowKey P1 P2 * * *
-** Synopsis: r[P2]=key
**
-** Write into register P2 the complete row key for cursor P1.
-** There is no interpretation of the data.
-** The key is copied onto the P2 register exactly as
-** it is found in the database file.
+** If P3!=0 then this opcode is allowed to make an ephemeral pointer
+** into the database page. That means that the content of the output
+** register will be invalidated as soon as the cursor moves - including
+** moves caused by other cursors that "save" the current cursors
+** position in order that they can write to the same table. If P3==0
+** then a copy of the data is made into memory. P3!=0 is faster, but
+** P3==0 is safer.
**
-** If the P1 cursor must be pointing to a valid row (not a NULL row)
-** of a real table, not a pseudo-table.
+** If P3!=0 then the content of the P2 register is unsuitable for use
+** in OP_Result and any OP_Result will invalidate the P2 register content.
+** The P2 register content is invalidated by opcodes like OP_Function or
+** by any use of another cursor pointing to the same table.
*/
-case OP_RowKey:
case OP_RowData: {
VdbeCursor *pC;
BtCursor *pCrsr;
u32 n;
- pOut = &aMem[pOp->p2];
- memAboutToChange(p, pOut);
+ pOut = out2Prerelease(p, pOp);
- /* Note that RowKey and RowData are really exactly the same instruction */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
assert( pC->eCurType==CURTYPE_BTREE );
assert( isSorter(pC)==0 );
- assert( pC->isTable || pOp->opcode!=OP_RowData );
- assert( pC->isTable==0 || pOp->opcode==OP_RowData );
assert( pC->nullRow==0 );
assert( pC->uc.pCursor!=0 );
pCrsr = pC->uc.pCursor;
- /* The OP_RowKey and OP_RowData opcodes always follow OP_NotExists or
+ /* The OP_RowData opcodes always follow OP_NotExists or
** OP_SeekRowid or OP_Rewind/Op_Next with no intervening instructions
** that might invalidate the cursor.
** If this where not the case, on of the following assert()s
** would fail. Should this ever change (because of changes in the code
** generator) then the fix would be to insert a call to
- ** sqlite3VdbeCursorMoveto().
+ ** tdsqlite3VdbeCursorMoveto().
*/
assert( pC->deferredMoveto==0 );
- assert( sqlite3BtreeCursorIsValid(pCrsr) );
+ assert( tdsqlite3BtreeCursorIsValid(pCrsr) );
#if 0 /* Not required due to the previous to assert() statements */
- rc = sqlite3VdbeCursorMoveto(pC);
+ rc = tdsqlite3VdbeCursorMoveto(pC);
if( rc!=SQLITE_OK ) goto abort_due_to_error;
#endif
- n = sqlite3BtreePayloadSize(pCrsr);
+ n = tdsqlite3BtreePayloadSize(pCrsr);
if( n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){
goto too_big;
}
testcase( n==0 );
- if( sqlite3VdbeMemClearAndResize(pOut, MAX(n,32)) ){
- goto no_mem;
- }
- pOut->n = n;
- MemSetTypeFlag(pOut, MEM_Blob);
- if( pC->isTable==0 ){
- rc = sqlite3BtreeKey(pCrsr, 0, n, pOut->z);
- }else{
- rc = sqlite3BtreeData(pCrsr, 0, n, pOut->z);
- }
+ rc = tdsqlite3VdbeMemFromBtree(pCrsr, 0, n, pOut);
if( rc ) goto abort_due_to_error;
- pOut->enc = SQLITE_UTF8; /* In case the blob is ever cast to text */
+ if( !pOp->p3 ) Deephemeralize(pOut);
UPDATE_MAX_BLOBSIZE(pOut);
REGISTER_TRACE(pOp->p2, pOut);
break;
@@ -85124,8 +93705,8 @@ case OP_RowData: {
case OP_Rowid: { /* out2 */
VdbeCursor *pC;
i64 v;
- sqlite3_vtab *pVtab;
- const sqlite3_module *pModule;
+ tdsqlite3_vtab *pVtab;
+ const tdsqlite3_module *pModule;
pOut = out2Prerelease(p, pOp);
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
@@ -85144,19 +93725,19 @@ case OP_Rowid: { /* out2 */
pModule = pVtab->pModule;
assert( pModule->xRowid );
rc = pModule->xRowid(pC->uc.pVCur, &v);
- sqlite3VtabImportErrmsg(p, pVtab);
+ tdsqlite3VtabImportErrmsg(p, pVtab);
if( rc ) goto abort_due_to_error;
#endif /* SQLITE_OMIT_VIRTUALTABLE */
}else{
assert( pC->eCurType==CURTYPE_BTREE );
assert( pC->uc.pCursor!=0 );
- rc = sqlite3VdbeCursorRestore(pC);
+ rc = tdsqlite3VdbeCursorRestore(pC);
if( rc ) goto abort_due_to_error;
if( pC->nullRow ){
pOut->flags = MEM_Null;
break;
}
- v = sqlite3BtreeIntegerKey(pC->uc.pCursor);
+ v = tdsqlite3BtreeIntegerKey(pC->uc.pCursor);
}
pOut->u.i = v;
break;
@@ -85178,12 +93759,25 @@ case OP_NullRow: {
pC->cacheStatus = CACHE_STALE;
if( pC->eCurType==CURTYPE_BTREE ){
assert( pC->uc.pCursor!=0 );
- sqlite3BtreeClearCursor(pC->uc.pCursor);
+ tdsqlite3BtreeClearCursor(pC->uc.pCursor);
}
+#ifdef SQLITE_DEBUG
+ if( pC->seekOp==0 ) pC->seekOp = OP_NullRow;
+#endif
break;
}
-/* Opcode: Last P1 P2 P3 * *
+/* Opcode: SeekEnd P1 * * * *
+**
+** Position cursor P1 at the end of the btree for the purpose of
+** appending a new entry onto the btree.
+**
+** It is assumed that the cursor is used only for appending and so
+** if the cursor is valid, then the cursor must already be pointing
+** at the end of the btree and so no changes are made to
+** the cursor.
+*/
+/* Opcode: Last P1 P2 * * *
**
** The next use of the Rowid or Column or Prev instruction for P1
** will refer to the last entry in the database table or index.
@@ -85195,6 +93789,7 @@ case OP_NullRow: {
** from the end toward the beginning. In other words, the cursor is
** configured to use Prev, not Next.
*/
+case OP_SeekEnd:
case OP_Last: { /* jump */
VdbeCursor *pC;
BtCursor *pCrsr;
@@ -85207,14 +93802,20 @@ case OP_Last: { /* jump */
pCrsr = pC->uc.pCursor;
res = 0;
assert( pCrsr!=0 );
- rc = sqlite3BtreeLast(pCrsr, &res);
+#ifdef SQLITE_DEBUG
+ pC->seekOp = pOp->opcode;
+#endif
+ if( pOp->opcode==OP_SeekEnd ){
+ assert( pOp->p2==0 );
+ pC->seekResult = -1;
+ if( tdsqlite3BtreeCursorIsValidNN(pCrsr) ){
+ break;
+ }
+ }
+ rc = tdsqlite3BtreeLast(pCrsr, &res);
pC->nullRow = (u8)res;
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
- pC->seekResult = pOp->p3;
-#ifdef SQLITE_DEBUG
- pC->seekOp = OP_Last;
-#endif
if( rc ) goto abort_due_to_error;
if( pOp->p2>0 ){
VdbeBranchTaken(res!=0,2);
@@ -85223,7 +93824,43 @@ case OP_Last: { /* jump */
break;
}
+/* Opcode: IfSmaller P1 P2 P3 * *
+**
+** Estimate the number of rows in the table P1. Jump to P2 if that
+** estimate is less than approximately 2**(0.1*P3).
+*/
+case OP_IfSmaller: { /* jump */
+ VdbeCursor *pC;
+ BtCursor *pCrsr;
+ int res;
+ i64 sz;
+
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ pCrsr = pC->uc.pCursor;
+ assert( pCrsr );
+ rc = tdsqlite3BtreeFirst(pCrsr, &res);
+ if( rc ) goto abort_due_to_error;
+ if( res==0 ){
+ sz = tdsqlite3BtreeRowCountEst(pCrsr);
+ if( ALWAYS(sz>=0) && tdsqlite3LogEst((u64)sz)<pOp->p3 ) res = 1;
+ }
+ VdbeBranchTaken(res!=0,2);
+ if( res ) goto jump_to_p2;
+ break;
+}
+
+/* Opcode: SorterSort P1 P2 * * *
+**
+** After all records have been inserted into the Sorter object
+** identified by P1, invoke this opcode to actually do the sorting.
+** Jump to P2 if there are no records to be sorted.
+**
+** This opcode is an alias for OP_Sort and OP_Rewind that is used
+** for Sorter objects.
+*/
/* Opcode: Sort P1 P2 * * *
**
** This opcode does exactly the same thing as OP_Rewind except that
@@ -85239,8 +93876,8 @@ case OP_Last: { /* jump */
case OP_SorterSort: /* jump */
case OP_Sort: { /* jump */
#ifdef SQLITE_TEST
- sqlite3_sort_count++;
- sqlite3_search_count--;
+ tdsqlite3_sort_count++;
+ tdsqlite3_search_count--;
#endif
p->aCounter[SQLITE_STMTSTATUS_SORT]++;
/* Fall through into OP_Rewind */
@@ -85263,6 +93900,7 @@ case OP_Rewind: { /* jump */
int res;
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ assert( pOp->p5==0 );
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
assert( isSorter(pC)==(pOp->opcode==OP_SorterSort) );
@@ -85271,12 +93909,12 @@ case OP_Rewind: { /* jump */
pC->seekOp = OP_Rewind;
#endif
if( isSorter(pC) ){
- rc = sqlite3VdbeSorterRewind(pC, &res);
+ rc = tdsqlite3VdbeSorterRewind(pC, &res);
}else{
assert( pC->eCurType==CURTYPE_BTREE );
pCrsr = pC->uc.pCursor;
assert( pCrsr );
- rc = sqlite3BtreeFirst(pCrsr, &res);
+ rc = tdsqlite3BtreeFirst(pCrsr, &res);
pC->deferredMoveto = 0;
pC->cacheStatus = CACHE_STALE;
}
@@ -85308,17 +93946,12 @@ case OP_Rewind: { /* jump */
** always either 0 or 1.
**
** P4 is always of type P4_ADVANCE. The function pointer points to
-** sqlite3BtreeNext().
+** tdsqlite3BtreeNext().
**
** If P5 is positive and the jump is taken, then event counter
** number P5-1 in the prepared statement is incremented.
**
-** See also: Prev, NextIfOpen
-*/
-/* Opcode: NextIfOpen P1 P2 P3 P4 P5
-**
-** This opcode works just like Next except that if cursor P1 is not
-** open it behaves a no-op.
+** See also: Prev
*/
/* Opcode: Prev P1 P2 P3 P4 P5
**
@@ -85341,93 +93974,101 @@ case OP_Rewind: { /* jump */
** always either 0 or 1.
**
** P4 is always of type P4_ADVANCE. The function pointer points to
-** sqlite3BtreePrevious().
+** tdsqlite3BtreePrevious().
**
** If P5 is positive and the jump is taken, then event counter
** number P5-1 in the prepared statement is incremented.
*/
-/* Opcode: PrevIfOpen P1 P2 P3 P4 P5
+/* Opcode: SorterNext P1 P2 * * P5
**
-** This opcode works just like Prev except that if cursor P1 is not
-** open it behaves a no-op.
+** This opcode works just like OP_Next except that P1 must be a
+** sorter object for which the OP_SorterSort opcode has been
+** invoked. This opcode advances the cursor to the next sorted
+** record, or jumps to P2 if there are no more sorted records.
*/
case OP_SorterNext: { /* jump */
VdbeCursor *pC;
- int res;
pC = p->apCsr[pOp->p1];
assert( isSorter(pC) );
- res = 0;
- rc = sqlite3VdbeSorterNext(db, pC, &res);
+ rc = tdsqlite3VdbeSorterNext(db, pC);
goto next_tail;
-case OP_PrevIfOpen: /* jump */
-case OP_NextIfOpen: /* jump */
- if( p->apCsr[pOp->p1]==0 ) break;
- /* Fall through */
case OP_Prev: /* jump */
case OP_Next: /* jump */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
assert( pOp->p5<ArraySize(p->aCounter) );
pC = p->apCsr[pOp->p1];
- res = pOp->p3;
assert( pC!=0 );
assert( pC->deferredMoveto==0 );
assert( pC->eCurType==CURTYPE_BTREE );
- assert( res==0 || (res==1 && pC->isTable==0) );
- testcase( res==1 );
- assert( pOp->opcode!=OP_Next || pOp->p4.xAdvance==sqlite3BtreeNext );
- assert( pOp->opcode!=OP_Prev || pOp->p4.xAdvance==sqlite3BtreePrevious );
- assert( pOp->opcode!=OP_NextIfOpen || pOp->p4.xAdvance==sqlite3BtreeNext );
- assert( pOp->opcode!=OP_PrevIfOpen || pOp->p4.xAdvance==sqlite3BtreePrevious);
-
- /* The Next opcode is only used after SeekGT, SeekGE, and Rewind.
+ assert( pOp->opcode!=OP_Next || pOp->p4.xAdvance==tdsqlite3BtreeNext );
+ assert( pOp->opcode!=OP_Prev || pOp->p4.xAdvance==tdsqlite3BtreePrevious );
+
+ /* The Next opcode is only used after SeekGT, SeekGE, Rewind, and Found.
** The Prev opcode is only used after SeekLT, SeekLE, and Last. */
- assert( pOp->opcode!=OP_Next || pOp->opcode!=OP_NextIfOpen
+ assert( pOp->opcode!=OP_Next
|| pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE
- || pC->seekOp==OP_Rewind || pC->seekOp==OP_Found);
- assert( pOp->opcode!=OP_Prev || pOp->opcode!=OP_PrevIfOpen
+ || pC->seekOp==OP_Rewind || pC->seekOp==OP_Found
+ || pC->seekOp==OP_NullRow|| pC->seekOp==OP_SeekRowid
+ || pC->seekOp==OP_IfNoHope);
+ assert( pOp->opcode!=OP_Prev
|| pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE
- || pC->seekOp==OP_Last );
+ || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope
+ || pC->seekOp==OP_NullRow);
- rc = pOp->p4.xAdvance(pC->uc.pCursor, &res);
+ rc = pOp->p4.xAdvance(pC->uc.pCursor, pOp->p3);
next_tail:
pC->cacheStatus = CACHE_STALE;
- VdbeBranchTaken(res==0,2);
- if( rc ) goto abort_due_to_error;
- if( res==0 ){
+ VdbeBranchTaken(rc==SQLITE_OK,2);
+ if( rc==SQLITE_OK ){
pC->nullRow = 0;
p->aCounter[pOp->p5]++;
#ifdef SQLITE_TEST
- sqlite3_search_count++;
+ tdsqlite3_search_count++;
#endif
goto jump_to_p2_and_check_for_interrupt;
- }else{
- pC->nullRow = 1;
}
+ if( rc!=SQLITE_DONE ) goto abort_due_to_error;
+ rc = SQLITE_OK;
+ pC->nullRow = 1;
goto check_for_interrupt;
}
-/* Opcode: IdxInsert P1 P2 P3 * P5
+/* Opcode: IdxInsert P1 P2 P3 P4 P5
** Synopsis: key=r[P2]
**
** Register P2 holds an SQL index key made using the
** MakeRecord instructions. This opcode writes that key
** into the index P1. Data for the entry is nil.
**
-** P3 is a flag that provides a hint to the b-tree layer that this
-** insert is likely to be an append.
+** If P4 is not zero, then it is the number of values in the unpacked
+** key of reg(P2). In that case, P3 is the index of the first register
+** for the unpacked key. The availability of the unpacked key can sometimes
+** be an optimization.
+**
+** If P5 has the OPFLAG_APPEND bit set, that is a hint to the b-tree layer
+** that this insert is likely to be an append.
**
** If P5 has the OPFLAG_NCHANGE bit set, then the change counter is
** incremented by this instruction. If the OPFLAG_NCHANGE bit is clear,
** then the change counter is unchanged.
**
-** If P5 has the OPFLAG_USESEEKRESULT bit set, then the cursor must have
-** just done a seek to the spot where the new entry is to be inserted.
-** This flag avoids doing an extra seek.
+** If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might
+** run faster by avoiding an unnecessary seek on cursor P1. However,
+** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior
+** seeks on the cursor or if the most recent seek used a key equivalent
+** to P2.
**
** This instruction only works for indices. The equivalent instruction
** for tables is OP_Insert.
*/
+/* Opcode: SorterInsert P1 P2 * * *
+** Synopsis: key=r[P2]
+**
+** Register P2 holds an SQL index key made using the
+** MakeRecord instructions. This opcode writes that key
+** into the sorter P1. Data for the entry is nil.
+*/
case OP_SorterInsert: /* in2 */
case OP_IdxInsert: { /* in2 */
VdbeCursor *pC;
@@ -85435,6 +94076,7 @@ case OP_IdxInsert: { /* in2 */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
+ tdsqlite3VdbeIncrWriteCounter(p, pC);
assert( pC!=0 );
assert( isSorter(pC)==(pOp->opcode==OP_SorterInsert) );
pIn2 = &aMem[pOp->p2];
@@ -85445,11 +94087,14 @@ case OP_IdxInsert: { /* in2 */
rc = ExpandBlob(pIn2);
if( rc ) goto abort_due_to_error;
if( pOp->opcode==OP_SorterInsert ){
- rc = sqlite3VdbeSorterWrite(pC, pIn2);
+ rc = tdsqlite3VdbeSorterWrite(pC, pIn2);
}else{
x.nKey = pIn2->n;
x.pKey = pIn2->z;
- rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, pOp->p3,
+ x.aMem = aMem + pOp->p3;
+ x.nMem = (u16)pOp->p4.i;
+ rc = tdsqlite3BtreeInsert(pC->uc.pCursor, &x,
+ (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)),
((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0)
);
assert( pC->deferredMoveto==0 );
@@ -85478,6 +94123,7 @@ case OP_IdxDelete: {
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
assert( pC->eCurType==CURTYPE_BTREE );
+ tdsqlite3VdbeIncrWriteCounter(p, pC);
pCrsr = pC->uc.pCursor;
assert( pCrsr!=0 );
assert( pOp->p5==0 );
@@ -85485,19 +94131,20 @@ case OP_IdxDelete: {
r.nField = (u16)pOp->p3;
r.default_rc = 0;
r.aMem = &aMem[pOp->p2];
- rc = sqlite3BtreeMovetoUnpacked(pCrsr, &r, 0, 0, &res);
+ rc = tdsqlite3BtreeMovetoUnpacked(pCrsr, &r, 0, 0, &res);
if( rc ) goto abort_due_to_error;
if( res==0 ){
- rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE);
+ rc = tdsqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE);
if( rc ) goto abort_due_to_error;
}
assert( pC->deferredMoveto==0 );
pC->cacheStatus = CACHE_STALE;
+ pC->seekResult = 0;
break;
}
-/* Opcode: Seek P1 * P3 P4 *
-** Synopsis: Move P3 to P1.rowid
+/* Opcode: DeferredSeek P1 * P3 P4 *
+** Synopsis: Move P3 to P1.rowid if needed
**
** P1 is an open index cursor and P3 is a cursor on the corresponding
** table. This opcode does a deferred seek of the P3 table cursor
@@ -85524,11 +94171,11 @@ case OP_IdxDelete: {
**
** See also: Rowid, MakeRecord.
*/
-case OP_Seek:
-case OP_IdxRowid: { /* out2 */
- VdbeCursor *pC; /* The P1 index cursor */
- VdbeCursor *pTabCur; /* The P2 table cursor (OP_Seek only) */
- i64 rowid; /* Rowid that P1 current points to */
+case OP_DeferredSeek:
+case OP_IdxRowid: { /* out2 */
+ VdbeCursor *pC; /* The P1 index cursor */
+ VdbeCursor *pTabCur; /* The P2 table cursor (OP_DeferredSeek only) */
+ i64 rowid; /* Rowid that P1 current points to */
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
pC = p->apCsr[pOp->p1];
@@ -85540,21 +94187,21 @@ case OP_IdxRowid: { /* out2 */
assert( !pC->nullRow || pOp->opcode==OP_IdxRowid );
/* The IdxRowid and Seek opcodes are combined because of the commonality
- ** of sqlite3VdbeCursorRestore() and sqlite3VdbeIdxRowid(). */
- rc = sqlite3VdbeCursorRestore(pC);
+ ** of tdsqlite3VdbeCursorRestore() and tdsqlite3VdbeIdxRowid(). */
+ rc = tdsqlite3VdbeCursorRestore(pC);
- /* sqlite3VbeCursorRestore() can only fail if the record has been deleted
+ /* tdsqlite3VbeCursorRestore() can only fail if the record has been deleted
** out from under the cursor. That will never happens for an IdxRowid
** or Seek opcode */
if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error;
if( !pC->nullRow ){
rowid = 0; /* Not needed. Only used to silence a warning. */
- rc = sqlite3VdbeIdxRowid(db, pC->uc.pCursor, &rowid);
+ rc = tdsqlite3VdbeIdxRowid(db, pC->uc.pCursor, &rowid);
if( rc!=SQLITE_OK ){
goto abort_due_to_error;
}
- if( pOp->opcode==OP_Seek ){
+ if( pOp->opcode==OP_DeferredSeek ){
assert( pOp->p3>=0 && pOp->p3<p->nCursor );
pTabCur = p->apCsr[pOp->p3];
assert( pTabCur!=0 );
@@ -85570,11 +94217,28 @@ case OP_IdxRowid: { /* out2 */
}else{
pOut = out2Prerelease(p, pOp);
pOut->u.i = rowid;
- pOut->flags = MEM_Int;
}
}else{
assert( pOp->opcode==OP_IdxRowid );
- sqlite3VdbeMemSetNull(&aMem[pOp->p2]);
+ tdsqlite3VdbeMemSetNull(&aMem[pOp->p2]);
+ }
+ break;
+}
+
+/* Opcode: FinishSeek P1 * * * *
+**
+** If cursor P1 was previously moved via OP_DeferredSeek, complete that
+** seek operation now, without further delay. If the cursor seek has
+** already occurred, this instruction is a no-op.
+*/
+case OP_FinishSeek: {
+ VdbeCursor *pC; /* The P1 index cursor */
+
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = p->apCsr[pOp->p1];
+ if( pC->deferredMoveto ){
+ rc = tdsqlite3VdbeFinishMoveto(pC);
+ if( rc ) goto abort_due_to_error;
}
break;
}
@@ -85651,10 +94315,16 @@ case OP_IdxGE: { /* jump */
}
r.aMem = &aMem[pOp->p3];
#ifdef SQLITE_DEBUG
- { int i; for(i=0; i<r.nField; i++) assert( memIsValid(&r.aMem[i]) ); }
+ {
+ int i;
+ for(i=0; i<r.nField; i++){
+ assert( memIsValid(&r.aMem[i]) );
+ REGISTER_TRACE(pOp->p3+i, &aMem[pOp->p3+i]);
+ }
+ }
#endif
res = 0; /* Not needed. Only used to silence a warning. */
- rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res);
+ rc = tdsqlite3VdbeIdxKeyCompare(db, pC, &r, &res);
assert( (OP_IdxLE&1)==(OP_IdxLT&1) && (OP_IdxGE&1)==(OP_IdxGT&1) );
if( (pOp->opcode&1)==(OP_IdxLT&1) ){
assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT );
@@ -85682,10 +94352,17 @@ case OP_IdxGE: { /* jump */
** might be moved into the newly deleted root page in order to keep all
** root pages contiguous at the beginning of the database. The former
** value of the root page that moved - its value before the move occurred -
-** is stored in register P2. If no page
-** movement was required (because the table being dropped was already
-** the last one in the database) then a zero is stored in register P2.
-** If AUTOVACUUM is disabled then a zero is stored in register P2.
+** is stored in register P2. If no page movement was required (because the
+** table being dropped was already the last one in the database) then a
+** zero is stored in register P2. If AUTOVACUUM is disabled then a zero
+** is stored in register P2.
+**
+** This opcode throws an error if there are any active reader VMs when
+** it is invoked. This is done to avoid the difficulty associated with
+** updating existing cursors when a root page is moved in an AUTOVACUUM
+** database. This error is thrown even if the database is not an AUTOVACUUM
+** db in order to avoid introducing an incompatibility between autovacuum
+** and non-autovacuum modes.
**
** See also: Clear
*/
@@ -85693,6 +94370,7 @@ case OP_Destroy: { /* out2 */
int iMoved;
int iDb;
+ tdsqlite3VdbeIncrWriteCounter(p, 0);
assert( p->readOnly==0 );
assert( pOp->p1>1 );
pOut = out2Prerelease(p, pOp);
@@ -85705,13 +94383,13 @@ case OP_Destroy: { /* out2 */
iDb = pOp->p3;
assert( DbMaskTest(p->btreeMask, iDb) );
iMoved = 0; /* Not needed. Only to silence a warning. */
- rc = sqlite3BtreeDropTable(db->aDb[iDb].pBt, pOp->p1, &iMoved);
+ rc = tdsqlite3BtreeDropTable(db->aDb[iDb].pBt, pOp->p1, &iMoved);
pOut->flags = MEM_Int;
pOut->u.i = iMoved;
if( rc ) goto abort_due_to_error;
#ifndef SQLITE_OMIT_AUTOVACUUM
if( iMoved!=0 ){
- sqlite3RootPageMoved(db, iDb, iMoved, pOp->p1);
+ tdsqlite3RootPageMoved(db, iDb, iMoved, pOp->p1);
/* All OP_Destroy operations occur on the same btree */
assert( resetSchemaOnFault==0 || resetSchemaOnFault==iDb+1 );
resetSchemaOnFault = iDb+1;
@@ -85742,10 +94420,11 @@ case OP_Destroy: { /* out2 */
case OP_Clear: {
int nChange;
+ tdsqlite3VdbeIncrWriteCounter(p, 0);
nChange = 0;
assert( p->readOnly==0 );
assert( DbMaskTest(p->btreeMask, pOp->p2) );
- rc = sqlite3BtreeClearTable(
+ rc = tdsqlite3BtreeClearTable(
db->aDb[pOp->p2].pBt, pOp->p1, (pOp->p3 ? &nChange : 0)
);
if( pOp->p3 ){
@@ -85775,69 +94454,62 @@ case OP_ResetSorter: {
pC = p->apCsr[pOp->p1];
assert( pC!=0 );
if( isSorter(pC) ){
- sqlite3VdbeSorterReset(db, pC->uc.pSorter);
+ tdsqlite3VdbeSorterReset(db, pC->uc.pSorter);
}else{
assert( pC->eCurType==CURTYPE_BTREE );
assert( pC->isEphemeral );
- rc = sqlite3BtreeClearTableOfCursor(pC->uc.pCursor);
+ rc = tdsqlite3BtreeClearTableOfCursor(pC->uc.pCursor);
if( rc ) goto abort_due_to_error;
}
break;
}
-/* Opcode: CreateTable P1 P2 * * *
-** Synopsis: r[P2]=root iDb=P1
-**
-** Allocate a new table in the main database file if P1==0 or in the
-** auxiliary database file if P1==1 or in an attached database if
-** P1>1. Write the root page number of the new table into
-** register P2
-**
-** The difference between a table and an index is this: A table must
-** have a 4-byte integer key and can have arbitrary data. An index
-** has an arbitrary key but no data.
+/* Opcode: CreateBtree P1 P2 P3 * *
+** Synopsis: r[P2]=root iDb=P1 flags=P3
**
-** See also: CreateIndex
+** Allocate a new b-tree in the main database file if P1==0 or in the
+** TEMP database file if P1==1 or in an attached database if
+** P1>1. The P3 argument must be 1 (BTREE_INTKEY) for a rowid table
+** it must be 2 (BTREE_BLOBKEY) for an index or WITHOUT ROWID table.
+** The root page number of the new b-tree is stored in register P2.
*/
-/* Opcode: CreateIndex P1 P2 * * *
-** Synopsis: r[P2]=root iDb=P1
-**
-** Allocate a new index in the main database file if P1==0 or in the
-** auxiliary database file if P1==1 or in an attached database if
-** P1>1. Write the root page number of the new table into
-** register P2.
-**
-** See documentation on OP_CreateTable for additional information.
-*/
-case OP_CreateIndex: /* out2 */
-case OP_CreateTable: { /* out2 */
+case OP_CreateBtree: { /* out2 */
int pgno;
- int flags;
Db *pDb;
+ tdsqlite3VdbeIncrWriteCounter(p, 0);
pOut = out2Prerelease(p, pOp);
pgno = 0;
+ assert( pOp->p3==BTREE_INTKEY || pOp->p3==BTREE_BLOBKEY );
assert( pOp->p1>=0 && pOp->p1<db->nDb );
assert( DbMaskTest(p->btreeMask, pOp->p1) );
assert( p->readOnly==0 );
pDb = &db->aDb[pOp->p1];
assert( pDb->pBt!=0 );
- if( pOp->opcode==OP_CreateTable ){
- /* flags = BTREE_INTKEY; */
- flags = BTREE_INTKEY;
- }else{
- flags = BTREE_BLOBKEY;
- }
- rc = sqlite3BtreeCreateTable(pDb->pBt, &pgno, flags);
+ rc = tdsqlite3BtreeCreateTable(pDb->pBt, &pgno, pOp->p3);
if( rc ) goto abort_due_to_error;
pOut->u.i = pgno;
break;
}
+/* Opcode: SqlExec * * * P4 *
+**
+** Run the SQL statement or statements specified in the P4 string.
+*/
+case OP_SqlExec: {
+ tdsqlite3VdbeIncrWriteCounter(p, 0);
+ db->nSqlExec++;
+ rc = tdsqlite3_exec(db, pOp->p4.z, 0, 0, 0);
+ db->nSqlExec--;
+ if( rc ) goto abort_due_to_error;
+ break;
+}
+
/* Opcode: ParseSchema P1 * * P4 *
**
** Read and parse all entries from the SQLITE_MASTER table of database P1
-** that match the WHERE clause P4.
+** that match the WHERE clause P4. If P4 is a NULL pointer, then the
+** entire schema for P1 is reparsed.
**
** This opcode invokes the parser to create a new virtual machine,
** then runs the new virtual machine. It is thus a re-entrant opcode.
@@ -85850,24 +94522,35 @@ case OP_ParseSchema: {
/* Any prepared statement that invokes this opcode will hold mutexes
** on every btree. This is a prerequisite for invoking
- ** sqlite3InitCallback().
+ ** tdsqlite3InitCallback().
*/
#ifdef SQLITE_DEBUG
for(iDb=0; iDb<db->nDb; iDb++){
- assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
+ assert( iDb==1 || tdsqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
}
#endif
iDb = pOp->p1;
assert( iDb>=0 && iDb<db->nDb );
assert( DbHasProperty(db, iDb, DB_SchemaLoaded) );
- /* Used to be a conditional */ {
- zMaster = SCHEMA_TABLE(iDb);
+
+#ifndef SQLITE_OMIT_ALTERTABLE
+ if( pOp->p4.z==0 ){
+ tdsqlite3SchemaClear(db->aDb[iDb].pSchema);
+ db->mDbFlags &= ~DBFLAG_SchemaKnownOk;
+ rc = tdsqlite3InitOne(db, iDb, &p->zErrMsg, INITFLAG_AlterTable);
+ db->mDbFlags |= DBFLAG_SchemaChange;
+ p->expired = 0;
+ }else
+#endif
+ {
+ zMaster = MASTER_NAME;
initData.db = db;
- initData.iDb = pOp->p1;
+ initData.iDb = iDb;
initData.pzErrMsg = &p->zErrMsg;
- zSql = sqlite3MPrintf(db,
- "SELECT name, rootpage, sql FROM '%q'.%s WHERE %s ORDER BY rowid",
+ initData.mInitFlags = 0;
+ zSql = tdsqlite3MPrintf(db,
+ "SELECT*FROM\"%w\".%s WHERE %s ORDER BY rowid",
db->aDb[iDb].zDbSName, zMaster, pOp->p4.z);
if( zSql==0 ){
rc = SQLITE_NOMEM_BKPT;
@@ -85875,15 +94558,22 @@ case OP_ParseSchema: {
assert( db->init.busy==0 );
db->init.busy = 1;
initData.rc = SQLITE_OK;
+ initData.nInitRow = 0;
assert( !db->mallocFailed );
- rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0);
+ rc = tdsqlite3_exec(db, zSql, tdsqlite3InitCallback, &initData, 0);
if( rc==SQLITE_OK ) rc = initData.rc;
- sqlite3DbFree(db, zSql);
+ if( rc==SQLITE_OK && initData.nInitRow==0 ){
+ /* The OP_ParseSchema opcode with a non-NULL P4 argument should parse
+ ** at least one SQL statement. Any less than that indicates that
+ ** the sqlite_master table is corrupt. */
+ rc = SQLITE_CORRUPT_BKPT;
+ }
+ tdsqlite3DbFreeNN(db, zSql);
db->init.busy = 0;
}
}
if( rc ){
- sqlite3ResetAllSchemasOfConnection(db);
+ tdsqlite3ResetAllSchemasOfConnection(db);
if( rc==SQLITE_NOMEM ){
goto no_mem;
}
@@ -85901,7 +94591,7 @@ case OP_ParseSchema: {
*/
case OP_LoadAnalysis: {
assert( pOp->p1>=0 && pOp->p1<db->nDb );
- rc = sqlite3AnalysisLoad(db, pOp->p1);
+ rc = tdsqlite3AnalysisLoad(db, pOp->p1);
if( rc ) goto abort_due_to_error;
break;
}
@@ -85916,7 +94606,8 @@ case OP_LoadAnalysis: {
** schema consistent with what is on disk.
*/
case OP_DropTable: {
- sqlite3UnlinkAndDeleteTable(db, pOp->p1, pOp->p4.z);
+ tdsqlite3VdbeIncrWriteCounter(p, 0);
+ tdsqlite3UnlinkAndDeleteTable(db, pOp->p1, pOp->p4.z);
break;
}
@@ -85929,7 +94620,8 @@ case OP_DropTable: {
** schema consistent with what is on disk.
*/
case OP_DropIndex: {
- sqlite3UnlinkAndDeleteIndex(db, pOp->p1, pOp->p4.z);
+ tdsqlite3VdbeIncrWriteCounter(p, 0);
+ tdsqlite3UnlinkAndDeleteIndex(db, pOp->p1, pOp->p4.z);
break;
}
@@ -85942,7 +94634,8 @@ case OP_DropIndex: {
** schema consistent with what is on disk.
*/
case OP_DropTrigger: {
- sqlite3UnlinkAndDeleteTrigger(db, pOp->p1, pOp->p4.z);
+ tdsqlite3VdbeIncrWriteCounter(p, 0);
+ tdsqlite3UnlinkAndDeleteTrigger(db, pOp->p1, pOp->p4.z);
break;
}
@@ -85954,7 +94647,7 @@ case OP_DropTrigger: {
** register P1 the text of an error message describing any problems.
** If no problems are found, store a NULL in register P1.
**
-** The register P3 contains the maximum number of allowed errors.
+** The register P3 contains one less than the maximum number of allowed errors.
** At most reg(P3) errors will be reported.
** In other words, the analysis stops as soon as reg(P1) errors are
** seen. Reg(P1) is updated with the number of errors remaining.
@@ -85978,7 +94671,7 @@ case OP_IntegrityCk: {
nRoot = pOp->p2;
aRoot = pOp->p4.ai;
assert( nRoot>0 );
- assert( aRoot[nRoot]==0 );
+ assert( aRoot[0]==nRoot );
assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
pnErr = &aMem[pOp->p3];
assert( (pnErr->flags & MEM_Int)!=0 );
@@ -85986,27 +94679,27 @@ case OP_IntegrityCk: {
pIn1 = &aMem[pOp->p1];
assert( pOp->p5<db->nDb );
assert( DbMaskTest(p->btreeMask, pOp->p5) );
- z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p5].pBt, aRoot, nRoot,
- (int)pnErr->u.i, &nErr);
- pnErr->u.i -= nErr;
- sqlite3VdbeMemSetNull(pIn1);
+ z = tdsqlite3BtreeIntegrityCheck(db, db->aDb[pOp->p5].pBt, &aRoot[1], nRoot,
+ (int)pnErr->u.i+1, &nErr);
+ tdsqlite3VdbeMemSetNull(pIn1);
if( nErr==0 ){
assert( z==0 );
}else if( z==0 ){
goto no_mem;
}else{
- sqlite3VdbeMemSetStr(pIn1, z, -1, SQLITE_UTF8, sqlite3_free);
+ pnErr->u.i -= nErr-1;
+ tdsqlite3VdbeMemSetStr(pIn1, z, -1, SQLITE_UTF8, tdsqlite3_free);
}
UPDATE_MAX_BLOBSIZE(pIn1);
- sqlite3VdbeChangeEncoding(pIn1, encoding);
- break;
+ tdsqlite3VdbeChangeEncoding(pIn1, encoding);
+ goto check_for_interrupt;
}
#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
/* Opcode: RowSetAdd P1 P2 * * *
** Synopsis: rowset(P1)=r[P2]
**
-** Insert the integer value held by register P2 into a boolean index
+** Insert the integer value held by register P2 into a RowSet object
** held in register P1.
**
** An assertion fails if P2 is not an integer.
@@ -86015,36 +94708,38 @@ case OP_RowSetAdd: { /* in1, in2 */
pIn1 = &aMem[pOp->p1];
pIn2 = &aMem[pOp->p2];
assert( (pIn2->flags & MEM_Int)!=0 );
- if( (pIn1->flags & MEM_RowSet)==0 ){
- sqlite3VdbeMemSetRowSet(pIn1);
- if( (pIn1->flags & MEM_RowSet)==0 ) goto no_mem;
+ if( (pIn1->flags & MEM_Blob)==0 ){
+ if( tdsqlite3VdbeMemSetRowSet(pIn1) ) goto no_mem;
}
- sqlite3RowSetInsert(pIn1->u.pRowSet, pIn2->u.i);
+ assert( tdsqlite3VdbeMemIsRowSet(pIn1) );
+ tdsqlite3RowSetInsert((RowSet*)pIn1->z, pIn2->u.i);
break;
}
/* Opcode: RowSetRead P1 P2 P3 * *
** Synopsis: r[P3]=rowset(P1)
**
-** Extract the smallest value from boolean index P1 and put that value into
-** register P3. Or, if boolean index P1 is initially empty, leave P3
+** Extract the smallest value from the RowSet object in P1
+** and put that value into register P3.
+** Or, if RowSet object P1 is initially empty, leave P3
** unchanged and jump to instruction P2.
*/
case OP_RowSetRead: { /* jump, in1, out3 */
i64 val;
pIn1 = &aMem[pOp->p1];
- if( (pIn1->flags & MEM_RowSet)==0
- || sqlite3RowSetNext(pIn1->u.pRowSet, &val)==0
+ assert( (pIn1->flags & MEM_Blob)==0 || tdsqlite3VdbeMemIsRowSet(pIn1) );
+ if( (pIn1->flags & MEM_Blob)==0
+ || tdsqlite3RowSetNext((RowSet*)pIn1->z, &val)==0
){
/* The boolean index is empty */
- sqlite3VdbeMemSetNull(pIn1);
+ tdsqlite3VdbeMemSetNull(pIn1);
VdbeBranchTaken(1,2);
goto jump_to_p2_and_check_for_interrupt;
}else{
/* A value was pulled from the index */
VdbeBranchTaken(0,2);
- sqlite3VdbeMemSetInt64(&aMem[pOp->p3], val);
+ tdsqlite3VdbeMemSetInt64(&aMem[pOp->p3], val);
}
goto check_for_interrupt;
}
@@ -86058,15 +94753,14 @@ case OP_RowSetRead: { /* jump, in1, out3 */
** integer in P3 into the RowSet and continue on to the
** next opcode.
**
-** The RowSet object is optimized for the case where successive sets
-** of integers, where each set contains no duplicates. Each set
-** of values is identified by a unique P4 value. The first set
-** must have P4==0, the final set P4=-1. P4 must be either -1 or
-** non-negative. For non-negative values of P4 only the lower 4
-** bits are significant.
+** The RowSet object is optimized for the case where sets of integers
+** are inserted in distinct phases, which each set contains no duplicates.
+** Each set is identified by a unique P4 value. The first set
+** must have P4==0, the final set must have P4==-1, and for all other sets
+** must have P4>0.
**
** This allows optimizations: (a) when P4==0 there is no need to test
-** the rowset object for P3, as it is guaranteed not to contain it,
+** the RowSet object for P3, as it is guaranteed not to contain it,
** (b) when P4==-1 there is no need to insert the value, as it will
** never be tested for, and (c) when a value that is part of set X is
** inserted, there is no need to search to see if the same value was
@@ -86085,20 +94779,19 @@ case OP_RowSetTest: { /* jump, in1, in3 */
/* If there is anything other than a rowset object in memory cell P1,
** delete it now and initialize P1 with an empty rowset
*/
- if( (pIn1->flags & MEM_RowSet)==0 ){
- sqlite3VdbeMemSetRowSet(pIn1);
- if( (pIn1->flags & MEM_RowSet)==0 ) goto no_mem;
+ if( (pIn1->flags & MEM_Blob)==0 ){
+ if( tdsqlite3VdbeMemSetRowSet(pIn1) ) goto no_mem;
}
-
+ assert( tdsqlite3VdbeMemIsRowSet(pIn1) );
assert( pOp->p4type==P4_INT32 );
assert( iSet==-1 || iSet>=0 );
if( iSet ){
- exists = sqlite3RowSetTest(pIn1->u.pRowSet, iSet, pIn3->u.i);
+ exists = tdsqlite3RowSetTest((RowSet*)pIn1->z, iSet, pIn3->u.i);
VdbeBranchTaken(exists!=0,2);
if( exists ) goto jump_to_p2;
}
if( iSet>=0 ){
- sqlite3RowSetInsert(pIn1->u.pRowSet, pIn3->u.i);
+ tdsqlite3RowSetInsert((RowSet*)pIn1->z, pIn3->u.i);
}
break;
}
@@ -86154,7 +94847,7 @@ case OP_Program: { /* jump */
if( p->nFrame>=db->aLimit[SQLITE_LIMIT_TRIGGER_DEPTH] ){
rc = SQLITE_ERROR;
- sqlite3VdbeError(p, "too many levels of trigger recursion");
+ tdsqlite3VdbeError(p, "too many levels of trigger recursion");
goto abort_due_to_error;
}
@@ -86162,7 +94855,7 @@ case OP_Program: { /* jump */
** of the current program, and the memory required at runtime to execute
** the trigger program. If this trigger has been fired before, then pRt
** is already allocated. Otherwise, it must be initialized. */
- if( (pRt->flags&MEM_Frame)==0 ){
+ if( (pRt->flags&MEM_Blob)==0 ){
/* SubProgram.nMem is set to the number of memory cells used by the
** program stored in SubProgram.aOp. As well as these, one memory
** cell is required for each cursor used by the program. Set local
@@ -86173,14 +94866,17 @@ case OP_Program: { /* jump */
if( pProgram->nCsr==0 ) nMem++;
nByte = ROUND8(sizeof(VdbeFrame))
+ nMem * sizeof(Mem)
- + pProgram->nCsr * sizeof(VdbeCursor *);
- pFrame = sqlite3DbMallocZero(db, nByte);
+ + pProgram->nCsr * sizeof(VdbeCursor*)
+ + (pProgram->nOp + 7)/8;
+ pFrame = tdsqlite3DbMallocZero(db, nByte);
if( !pFrame ){
goto no_mem;
}
- sqlite3VdbeMemRelease(pRt);
- pRt->flags = MEM_Frame;
- pRt->u.pFrame = pFrame;
+ tdsqlite3VdbeMemRelease(pRt);
+ pRt->flags = MEM_Blob|MEM_Dyn;
+ pRt->z = (char*)pFrame;
+ pRt->n = nByte;
+ pRt->xDel = tdsqlite3VdbeFrameMemDel;
pFrame->v = p;
pFrame->nChildMem = nMem;
@@ -86196,6 +94892,9 @@ case OP_Program: { /* jump */
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
pFrame->anExec = p->anExec;
#endif
+#ifdef SQLITE_DEBUG
+ pFrame->iFrameMagic = SQLITE_FRAME_MAGIC;
+#endif
pEnd = &VdbeFrameMem(pFrame)[pFrame->nChildMem];
for(pMem=VdbeFrameMem(pFrame); pMem!=pEnd; pMem++){
@@ -86203,7 +94902,8 @@ case OP_Program: { /* jump */
pMem->db = db;
}
}else{
- pFrame = pRt->u.pFrame;
+ pFrame = (VdbeFrame*)pRt->z;
+ assert( pRt->xDel==tdsqlite3VdbeFrameMemDel );
assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem
|| (pProgram->nCsr==0 && pProgram->nMem+1==pFrame->nChildMem) );
assert( pProgram->nCsr==pFrame->nChildCsr );
@@ -86212,7 +94912,7 @@ case OP_Program: { /* jump */
p->nFrame++;
pFrame->pParent = p->pFrame;
- pFrame->lastRowid = lastRowid;
+ pFrame->lastRowid = db->lastRowid;
pFrame->nChange = p->nChange;
pFrame->nDbChange = p->db->nChange;
assert( pFrame->pAuxData==0 );
@@ -86224,14 +94924,26 @@ case OP_Program: { /* jump */
p->nMem = pFrame->nChildMem;
p->nCursor = (u16)pFrame->nChildCsr;
p->apCsr = (VdbeCursor **)&aMem[p->nMem];
+ pFrame->aOnce = (u8*)&p->apCsr[pProgram->nCsr];
+ memset(pFrame->aOnce, 0, (pProgram->nOp + 7)/8);
p->aOp = aOp = pProgram->aOp;
p->nOp = pProgram->nOp;
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
p->anExec = 0;
#endif
+#ifdef SQLITE_DEBUG
+ /* Verify that second and subsequent executions of the same trigger do not
+ ** try to reuse register values from the first use. */
+ {
+ int i;
+ for(i=0; i<p->nMem; i++){
+ aMem[i].pScopyFrom = 0; /* Prevent false-positive AboutToChange() errs */
+ aMem[i].flags |= MEM_Undefined; /* Cause a fault if this reg is reused */
+ }
+ }
+#endif
pOp = &aOp[-1];
-
- break;
+ goto check_for_interrupt;
}
/* Opcode: Param P1 P2 * * *
@@ -86252,7 +94964,7 @@ case OP_Param: { /* out2 */
pOut = out2Prerelease(p, pOp);
pFrame = p->pFrame;
pIn = &pFrame->aMem[pOp->p1 + pFrame->aOp[pFrame->pc].p1];
- sqlite3VdbeMemShallowCopy(pOut, pIn, MEM_Ephem);
+ tdsqlite3VdbeMemShallowCopy(pOut, pIn, MEM_Ephem);
break;
}
@@ -86323,9 +95035,9 @@ case OP_MemMax: { /* in2 */
pIn1 = &aMem[pOp->p1];
}
assert( memIsValid(pIn1) );
- sqlite3VdbeMemIntegerify(pIn1);
+ tdsqlite3VdbeMemIntegerify(pIn1);
pIn2 = &aMem[pOp->p2];
- sqlite3VdbeMemIntegerify(pIn2);
+ tdsqlite3VdbeMemIntegerify(pIn2);
if( pIn1->u.i<pIn2->u.i){
pIn1->u.i = pIn2->u.i;
}
@@ -86373,29 +95085,42 @@ case OP_IfPos: { /* jump, in1 */
** Otherwise, r[P2] is set to the sum of r[P1] and r[P3].
*/
case OP_OffsetLimit: { /* in1, out2, in3 */
+ i64 x;
pIn1 = &aMem[pOp->p1];
pIn3 = &aMem[pOp->p3];
pOut = out2Prerelease(p, pOp);
assert( pIn1->flags & MEM_Int );
assert( pIn3->flags & MEM_Int );
- pOut->u.i = pIn1->u.i<=0 ? -1 : pIn1->u.i+(pIn3->u.i>0?pIn3->u.i:0);
+ x = pIn1->u.i;
+ if( x<=0 || tdsqlite3AddInt64(&x, pIn3->u.i>0?pIn3->u.i:0) ){
+ /* If the LIMIT is less than or equal to zero, loop forever. This
+ ** is documented. But also, if the LIMIT+OFFSET exceeds 2^63 then
+ ** also loop forever. This is undocumented. In fact, one could argue
+ ** that the loop should terminate. But assuming 1 billion iterations
+ ** per second (far exceeding the capabilities of any current hardware)
+ ** it would take nearly 300 years to actually reach the limit. So
+ ** looping forever is a reasonable approximation. */
+ pOut->u.i = -1;
+ }else{
+ pOut->u.i = x;
+ }
break;
}
-/* Opcode: IfNotZero P1 P2 P3 * *
-** Synopsis: if r[P1]!=0 then r[P1]-=P3, goto P2
+/* Opcode: IfNotZero P1 P2 * * *
+** Synopsis: if r[P1]!=0 then r[P1]--, goto P2
**
** Register P1 must contain an integer. If the content of register P1 is
-** initially nonzero, then subtract P3 from the value in register P1 and
-** jump to P2. If register P1 is initially zero, leave it unchanged
-** and fall through.
+** initially greater than zero, then decrement the value in register P1.
+** If it is non-zero (negative or positive) and then also jump to P2.
+** If register P1 is initially zero, leave it unchanged and fall through.
*/
case OP_IfNotZero: { /* jump, in1 */
pIn1 = &aMem[pOp->p1];
assert( pIn1->flags&MEM_Int );
VdbeBranchTaken(pIn1->u.i<0, 2);
if( pIn1->u.i ){
- pIn1->u.i -= pOp->p3;
+ if( pIn1->u.i>0 ) pIn1->u.i--;
goto jump_to_p2;
}
break;
@@ -86404,82 +95129,113 @@ case OP_IfNotZero: { /* jump, in1 */
/* Opcode: DecrJumpZero P1 P2 * * *
** Synopsis: if (--r[P1])==0 goto P2
**
-** Register P1 must hold an integer. Decrement the value in register P1
-** then jump to P2 if the new value is exactly zero.
+** Register P1 must hold an integer. Decrement the value in P1
+** and jump to P2 if the new value is exactly zero.
*/
case OP_DecrJumpZero: { /* jump, in1 */
pIn1 = &aMem[pOp->p1];
assert( pIn1->flags&MEM_Int );
- pIn1->u.i--;
+ if( pIn1->u.i>SMALLEST_INT64 ) pIn1->u.i--;
VdbeBranchTaken(pIn1->u.i==0, 2);
if( pIn1->u.i==0 ) goto jump_to_p2;
break;
}
-/* Opcode: AggStep0 * P2 P3 P4 P5
+/* Opcode: AggStep * P2 P3 P4 P5
** Synopsis: accum=r[P3] step(r[P2@P5])
**
-** Execute the step function for an aggregate. The
-** function has P5 arguments. P4 is a pointer to the FuncDef
-** structure that specifies the function. Register P3 is the
+** Execute the xStep function for an aggregate.
+** The function has P5 arguments. P4 is a pointer to the
+** FuncDef structure that specifies the function. Register P3 is the
** accumulator.
**
** The P5 arguments are taken from register P2 and its
** successors.
*/
-/* Opcode: AggStep * P2 P3 P4 P5
+/* Opcode: AggInverse * P2 P3 P4 P5
+** Synopsis: accum=r[P3] inverse(r[P2@P5])
+**
+** Execute the xInverse function for an aggregate.
+** The function has P5 arguments. P4 is a pointer to the
+** FuncDef structure that specifies the function. Register P3 is the
+** accumulator.
+**
+** The P5 arguments are taken from register P2 and its
+** successors.
+*/
+/* Opcode: AggStep1 P1 P2 P3 P4 P5
** Synopsis: accum=r[P3] step(r[P2@P5])
**
-** Execute the step function for an aggregate. The
-** function has P5 arguments. P4 is a pointer to an sqlite3_context
-** object that is used to run the function. Register P3 is
-** as the accumulator.
+** Execute the xStep (if P1==0) or xInverse (if P1!=0) function for an
+** aggregate. The function has P5 arguments. P4 is a pointer to the
+** FuncDef structure that specifies the function. Register P3 is the
+** accumulator.
**
** The P5 arguments are taken from register P2 and its
** successors.
**
** This opcode is initially coded as OP_AggStep0. On first evaluation,
-** the FuncDef stored in P4 is converted into an sqlite3_context and
+** the FuncDef stored in P4 is converted into an tdsqlite3_context and
** the opcode is changed. In this way, the initialization of the
-** sqlite3_context only happens once, instead of on each call to the
+** tdsqlite3_context only happens once, instead of on each call to the
** step function.
*/
-case OP_AggStep0: {
+case OP_AggInverse:
+case OP_AggStep: {
int n;
- sqlite3_context *pCtx;
+ tdsqlite3_context *pCtx;
assert( pOp->p4type==P4_FUNCDEF );
n = pOp->p5;
assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) );
assert( pOp->p3<pOp->p2 || pOp->p3>=pOp->p2+n );
- pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*));
+ pCtx = tdsqlite3DbMallocRawNN(db, n*sizeof(tdsqlite3_value*) +
+ (sizeof(pCtx[0]) + sizeof(Mem) - sizeof(tdsqlite3_value*)));
if( pCtx==0 ) goto no_mem;
pCtx->pMem = 0;
+ pCtx->pOut = (Mem*)&(pCtx->argv[n]);
+ tdsqlite3VdbeMemInit(pCtx->pOut, db, MEM_Null);
pCtx->pFunc = pOp->p4.pFunc;
pCtx->iOp = (int)(pOp - aOp);
pCtx->pVdbe = p;
+ pCtx->skipFlag = 0;
+ pCtx->isError = 0;
pCtx->argc = n;
pOp->p4type = P4_FUNCCTX;
pOp->p4.pCtx = pCtx;
- pOp->opcode = OP_AggStep;
+
+ /* OP_AggInverse must have P1==1 and OP_AggStep must have P1==0 */
+ assert( pOp->p1==(pOp->opcode==OP_AggInverse) );
+
+ pOp->opcode = OP_AggStep1;
/* Fall through into OP_AggStep */
}
-case OP_AggStep: {
+case OP_AggStep1: {
int i;
- sqlite3_context *pCtx;
+ tdsqlite3_context *pCtx;
Mem *pMem;
- Mem t;
assert( pOp->p4type==P4_FUNCCTX );
pCtx = pOp->p4.pCtx;
pMem = &aMem[pOp->p3];
+#ifdef SQLITE_DEBUG
+ if( pOp->p1 ){
+ /* This is an OP_AggInverse call. Verify that xStep has always
+ ** been called at least once prior to any xInverse call. */
+ assert( pMem->uTemp==0x1122e0e3 );
+ }else{
+ /* This is an OP_AggStep call. Mark it as such. */
+ pMem->uTemp = 0x1122e0e3;
+ }
+#endif
+
/* If this function is inside of a trigger, the register array in aMem[]
** might change from one evaluation to the next. The next block of code
** checks to see if the register array has changed, and if so it
- ** reinitializes the relavant parts of the sqlite3_context object */
+ ** reinitializes the relavant parts of the tdsqlite3_context object */
if( pCtx->pMem != pMem ){
pCtx->pMem = pMem;
for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i];
@@ -86493,55 +95249,88 @@ case OP_AggStep: {
#endif
pMem->n++;
- sqlite3VdbeMemInit(&t, db, MEM_Null);
- pCtx->pOut = &t;
- pCtx->fErrorOrAux = 0;
- pCtx->skipFlag = 0;
+ assert( pCtx->pOut->flags==MEM_Null );
+ assert( pCtx->isError==0 );
+ assert( pCtx->skipFlag==0 );
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( pOp->p1 ){
+ (pCtx->pFunc->xInverse)(pCtx,pCtx->argc,pCtx->argv);
+ }else
+#endif
(pCtx->pFunc->xSFunc)(pCtx,pCtx->argc,pCtx->argv); /* IMP: R-24505-23230 */
- if( pCtx->fErrorOrAux ){
- if( pCtx->isError ){
- sqlite3VdbeError(p, "%s", sqlite3_value_text(&t));
+
+ if( pCtx->isError ){
+ if( pCtx->isError>0 ){
+ tdsqlite3VdbeError(p, "%s", tdsqlite3_value_text(pCtx->pOut));
rc = pCtx->isError;
}
- sqlite3VdbeMemRelease(&t);
+ if( pCtx->skipFlag ){
+ assert( pOp[-1].opcode==OP_CollSeq );
+ i = pOp[-1].p1;
+ if( i ) tdsqlite3VdbeMemSetInt64(&aMem[i], 1);
+ pCtx->skipFlag = 0;
+ }
+ tdsqlite3VdbeMemRelease(pCtx->pOut);
+ pCtx->pOut->flags = MEM_Null;
+ pCtx->isError = 0;
if( rc ) goto abort_due_to_error;
- }else{
- assert( t.flags==MEM_Null );
- }
- if( pCtx->skipFlag ){
- assert( pOp[-1].opcode==OP_CollSeq );
- i = pOp[-1].p1;
- if( i ) sqlite3VdbeMemSetInt64(&aMem[i], 1);
}
+ assert( pCtx->pOut->flags==MEM_Null );
+ assert( pCtx->skipFlag==0 );
break;
}
/* Opcode: AggFinal P1 P2 * P4 *
** Synopsis: accum=r[P1] N=P2
**
-** Execute the finalizer function for an aggregate. P1 is
-** the memory location that is the accumulator for the aggregate.
+** P1 is the memory location that is the accumulator for an aggregate
+** or window function. Execute the finalizer function
+** for an aggregate and store the result in P1.
**
** P2 is the number of arguments that the step function takes and
** P4 is a pointer to the FuncDef for this function. The P2
** argument is not used by this opcode. It is only there to disambiguate
** functions that can take varying numbers of arguments. The
-** P4 argument is only needed for the degenerate case where
+** P4 argument is only needed for the case where
** the step function was not previously called.
*/
+/* Opcode: AggValue * P2 P3 P4 *
+** Synopsis: r[P3]=value N=P2
+**
+** Invoke the xValue() function and store the result in register P3.
+**
+** P2 is the number of arguments that the step function takes and
+** P4 is a pointer to the FuncDef for this function. The P2
+** argument is not used by this opcode. It is only there to disambiguate
+** functions that can take varying numbers of arguments. The
+** P4 argument is only needed for the case where
+** the step function was not previously called.
+*/
+case OP_AggValue:
case OP_AggFinal: {
Mem *pMem;
assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) );
+ assert( pOp->p3==0 || pOp->opcode==OP_AggValue );
pMem = &aMem[pOp->p1];
assert( (pMem->flags & ~(MEM_Null|MEM_Agg))==0 );
- rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( pOp->p3 ){
+ memAboutToChange(p, &aMem[pOp->p3]);
+ rc = tdsqlite3VdbeMemAggValue(pMem, &aMem[pOp->p3], pOp->p4.pFunc);
+ pMem = &aMem[pOp->p3];
+ }else
+#endif
+ {
+ rc = tdsqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc);
+ }
+
if( rc ){
- sqlite3VdbeError(p, "%s", sqlite3_value_text(pMem));
+ tdsqlite3VdbeError(p, "%s", tdsqlite3_value_text(pMem));
goto abort_due_to_error;
}
- sqlite3VdbeChangeEncoding(pMem, encoding);
+ tdsqlite3VdbeChangeEncoding(pMem, encoding);
UPDATE_MAX_BLOBSIZE(pMem);
- if( sqlite3VdbeMemTooBig(pMem) ){
+ if( tdsqlite3VdbeMemTooBig(pMem) ){
goto too_big;
}
break;
@@ -86572,14 +95361,14 @@ case OP_Checkpoint: {
|| pOp->p2==SQLITE_CHECKPOINT_RESTART
|| pOp->p2==SQLITE_CHECKPOINT_TRUNCATE
);
- rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]);
+ rc = tdsqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]);
if( rc ){
if( rc!=SQLITE_BUSY ) goto abort_due_to_error;
rc = SQLITE_OK;
aRes[0] = 1;
}
for(i=0, pMem = &aMem[pOp->p3]; i<3; i++, pMem++){
- sqlite3VdbeMemSetInt64(pMem, (i64)aRes[i]);
+ tdsqlite3VdbeMemSetInt64(pMem, (i64)aRes[i]);
}
break;
};
@@ -86620,20 +95409,20 @@ case OP_JournalMode: { /* out2 */
assert( p->readOnly==0 );
pBt = db->aDb[pOp->p1].pBt;
- pPager = sqlite3BtreePager(pBt);
- eOld = sqlite3PagerGetJournalMode(pPager);
+ pPager = tdsqlite3BtreePager(pBt);
+ eOld = tdsqlite3PagerGetJournalMode(pPager);
if( eNew==PAGER_JOURNALMODE_QUERY ) eNew = eOld;
- if( !sqlite3PagerOkToChangeJournalMode(pPager) ) eNew = eOld;
+ if( !tdsqlite3PagerOkToChangeJournalMode(pPager) ) eNew = eOld;
#ifndef SQLITE_OMIT_WAL
- zFilename = sqlite3PagerFilename(pPager, 1);
+ zFilename = tdsqlite3PagerFilename(pPager, 1);
/* Do not allow a transition to journal_mode=WAL for a database
** in temporary storage or if the VFS does not support shared memory
*/
if( eNew==PAGER_JOURNALMODE_WAL
- && (sqlite3Strlen30(zFilename)==0 /* Temp file */
- || !sqlite3PagerWalSupported(pPager)) /* No shared-memory support */
+ && (tdsqlite3Strlen30(zFilename)==0 /* Temp file */
+ || !tdsqlite3PagerWalSupported(pPager)) /* No shared-memory support */
){
eNew = eOld;
}
@@ -86643,7 +95432,7 @@ case OP_JournalMode: { /* out2 */
){
if( !db->autoCommit || db->nVdbeRead>1 ){
rc = SQLITE_ERROR;
- sqlite3VdbeError(p,
+ tdsqlite3VdbeError(p,
"cannot change %s wal mode from within a transaction",
(eNew==PAGER_JOURNALMODE_WAL ? "into" : "out of")
);
@@ -86656,49 +95445,54 @@ case OP_JournalMode: { /* out2 */
** file. An EXCLUSIVE lock may still be held on the database file
** after a successful return.
*/
- rc = sqlite3PagerCloseWal(pPager);
+ rc = tdsqlite3PagerCloseWal(pPager, db);
if( rc==SQLITE_OK ){
- sqlite3PagerSetJournalMode(pPager, eNew);
+ tdsqlite3PagerSetJournalMode(pPager, eNew);
}
}else if( eOld==PAGER_JOURNALMODE_MEMORY ){
/* Cannot transition directly from MEMORY to WAL. Use mode OFF
** as an intermediate */
- sqlite3PagerSetJournalMode(pPager, PAGER_JOURNALMODE_OFF);
+ tdsqlite3PagerSetJournalMode(pPager, PAGER_JOURNALMODE_OFF);
}
/* Open a transaction on the database file. Regardless of the journal
** mode, this transaction always uses a rollback journal.
*/
- assert( sqlite3BtreeIsInTrans(pBt)==0 );
+ assert( tdsqlite3BtreeIsInTrans(pBt)==0 );
if( rc==SQLITE_OK ){
- rc = sqlite3BtreeSetVersion(pBt, (eNew==PAGER_JOURNALMODE_WAL ? 2 : 1));
+ rc = tdsqlite3BtreeSetVersion(pBt, (eNew==PAGER_JOURNALMODE_WAL ? 2 : 1));
}
}
}
#endif /* ifndef SQLITE_OMIT_WAL */
if( rc ) eNew = eOld;
- eNew = sqlite3PagerSetJournalMode(pPager, eNew);
+ eNew = tdsqlite3PagerSetJournalMode(pPager, eNew);
pOut->flags = MEM_Str|MEM_Static|MEM_Term;
- pOut->z = (char *)sqlite3JournalModename(eNew);
- pOut->n = sqlite3Strlen30(pOut->z);
+ pOut->z = (char *)tdsqlite3JournalModename(eNew);
+ pOut->n = tdsqlite3Strlen30(pOut->z);
pOut->enc = SQLITE_UTF8;
- sqlite3VdbeChangeEncoding(pOut, encoding);
+ tdsqlite3VdbeChangeEncoding(pOut, encoding);
if( rc ) goto abort_due_to_error;
break;
};
#endif /* SQLITE_OMIT_PRAGMA */
#if !defined(SQLITE_OMIT_VACUUM) && !defined(SQLITE_OMIT_ATTACH)
-/* Opcode: Vacuum P1 * * * *
+/* Opcode: Vacuum P1 P2 * * *
**
** Vacuum the entire database P1. P1 is 0 for "main", and 2 or more
** for an attached database. The "temp" database may not be vacuumed.
+**
+** If P2 is not zero, then it is a register holding a string which is
+** the file into which the result of vacuum should be written. When
+** P2 is zero, the vacuum overwrites the original database.
*/
case OP_Vacuum: {
assert( p->readOnly==0 );
- rc = sqlite3RunVacuum(&p->zErrMsg, db, pOp->p1);
+ rc = tdsqlite3RunVacuum(&p->zErrMsg, db, pOp->p1,
+ pOp->p2 ? &aMem[pOp->p2] : 0);
if( rc ) goto abort_due_to_error;
break;
}
@@ -86718,7 +95512,7 @@ case OP_IncrVacuum: { /* jump */
assert( DbMaskTest(p->btreeMask, pOp->p1) );
assert( p->readOnly==0 );
pBt = db->aDb[pOp->p1].pBt;
- rc = sqlite3BtreeIncrVacuum(pBt);
+ rc = tdsqlite3BtreeIncrVacuum(pBt);
VdbeBranchTaken(rc==SQLITE_DONE,2);
if( rc ){
if( rc!=SQLITE_DONE ) goto abort_due_to_error;
@@ -86729,25 +95523,62 @@ case OP_IncrVacuum: { /* jump */
}
#endif
-/* Opcode: Expire P1 * * * *
+/* Opcode: Expire P1 P2 * * *
**
** Cause precompiled statements to expire. When an expired statement
-** is executed using sqlite3_step() it will either automatically
-** reprepare itself (if it was originally created using sqlite3_prepare_v2())
+** is executed using tdsqlite3_step() it will either automatically
+** reprepare itself (if it was originally created using tdsqlite3_prepare_v2())
** or it will fail with SQLITE_SCHEMA.
**
** If P1 is 0, then all SQL statements become expired. If P1 is non-zero,
** then only the currently executing statement is expired.
+**
+** If P2 is 0, then SQL statements are expired immediately. If P2 is 1,
+** then running SQL statements are allowed to continue to run to completion.
+** The P2==1 case occurs when a CREATE INDEX or similar schema change happens
+** that might help the statement run faster but which does not affect the
+** correctness of operation.
*/
case OP_Expire: {
+ assert( pOp->p2==0 || pOp->p2==1 );
if( !pOp->p1 ){
- sqlite3ExpirePreparedStatements(db);
+ tdsqlite3ExpirePreparedStatements(db, pOp->p2);
}else{
- p->expired = 1;
+ p->expired = pOp->p2+1;
}
break;
}
+/* Opcode: CursorLock P1 * * * *
+**
+** Lock the btree to which cursor P1 is pointing so that the btree cannot be
+** written by an other cursor.
+*/
+case OP_CursorLock: {
+ VdbeCursor *pC;
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ assert( pC->eCurType==CURTYPE_BTREE );
+ tdsqlite3BtreeCursorPin(pC->uc.pCursor);
+ break;
+}
+
+/* Opcode: CursorUnlock P1 * * * *
+**
+** Unlock the btree to which cursor P1 is pointing so that it can be
+** written by other cursors.
+*/
+case OP_CursorUnlock: {
+ VdbeCursor *pC;
+ assert( pOp->p1>=0 && pOp->p1<p->nCursor );
+ pC = p->apCsr[pOp->p1];
+ assert( pC!=0 );
+ assert( pC->eCurType==CURTYPE_BTREE );
+ tdsqlite3BtreeCursorUnpin(pC->uc.pCursor);
+ break;
+}
+
#ifndef SQLITE_OMIT_SHARED_CACHE
/* Opcode: TableLock P1 P2 P3 P4 *
** Synopsis: iDb=P1 root=P2 write=P3
@@ -86766,16 +95597,16 @@ case OP_Expire: {
*/
case OP_TableLock: {
u8 isWriteLock = (u8)pOp->p3;
- if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommitted) ){
+ if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommit) ){
int p1 = pOp->p1;
assert( p1>=0 && p1<db->nDb );
assert( DbMaskTest(p->btreeMask, p1) );
assert( isWriteLock==0 || isWriteLock==1 );
- rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock);
+ rc = tdsqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock);
if( rc ){
if( (rc&0xFF)==SQLITE_LOCKED ){
const char *z = pOp->p4.z;
- sqlite3VdbeError(p, "database table is locked: %s", z);
+ tdsqlite3VdbeError(p, "database table is locked: %s", z);
}
goto abort_due_to_error;
}
@@ -86787,7 +95618,7 @@ case OP_TableLock: {
#ifndef SQLITE_OMIT_VIRTUALTABLE
/* Opcode: VBegin * * * P4 *
**
-** P4 may be a pointer to an sqlite3_vtab structure. If so, call the
+** P4 may be a pointer to an tdsqlite3_vtab structure. If so, call the
** xBegin method for that table.
**
** Also, whether or not P4 is set, check that this is not being called from
@@ -86797,8 +95628,8 @@ case OP_TableLock: {
case OP_VBegin: {
VTable *pVTab;
pVTab = pOp->p4.pVtab;
- rc = sqlite3VtabBegin(db, pVTab);
- if( pVTab ) sqlite3VtabImportErrmsg(p, pVTab->pVtab);
+ rc = tdsqlite3VtabBegin(db, pVTab);
+ if( pVTab ) tdsqlite3VtabImportErrmsg(p, pVTab->pVtab);
if( rc ) goto abort_due_to_error;
break;
}
@@ -86817,17 +95648,17 @@ case OP_VCreate: {
memset(&sMem, 0, sizeof(sMem));
sMem.db = db;
/* Because P2 is always a static string, it is impossible for the
- ** sqlite3VdbeMemCopy() to fail */
+ ** tdsqlite3VdbeMemCopy() to fail */
assert( (aMem[pOp->p2].flags & MEM_Str)!=0 );
assert( (aMem[pOp->p2].flags & MEM_Static)!=0 );
- rc = sqlite3VdbeMemCopy(&sMem, &aMem[pOp->p2]);
+ rc = tdsqlite3VdbeMemCopy(&sMem, &aMem[pOp->p2]);
assert( rc==SQLITE_OK );
- zTab = (const char*)sqlite3_value_text(&sMem);
+ zTab = (const char*)tdsqlite3_value_text(&sMem);
assert( zTab || db->mallocFailed );
if( zTab ){
- rc = sqlite3VtabCallCreate(db, pOp->p1, zTab, &p->zErrMsg);
+ rc = tdsqlite3VtabCallCreate(db, pOp->p1, zTab, &p->zErrMsg);
}
- sqlite3VdbeMemRelease(&sMem);
+ tdsqlite3VdbeMemRelease(&sMem);
if( rc ) goto abort_due_to_error;
break;
}
@@ -86841,8 +95672,9 @@ case OP_VCreate: {
*/
case OP_VDestroy: {
db->nVDestroy++;
- rc = sqlite3VtabCallDestroy(db, pOp->p1, pOp->p4.z);
+ rc = tdsqlite3VtabCallDestroy(db, pOp->p1, pOp->p4.z);
db->nVDestroy--;
+ assert( p->errorAction==OE_Abort && p->usesStmtJournal );
if( rc ) goto abort_due_to_error;
break;
}
@@ -86851,15 +95683,15 @@ case OP_VDestroy: {
#ifndef SQLITE_OMIT_VIRTUALTABLE
/* Opcode: VOpen P1 * * P4 *
**
-** P4 is a pointer to a virtual table object, an sqlite3_vtab structure.
+** P4 is a pointer to a virtual table object, an tdsqlite3_vtab structure.
** P1 is a cursor number. This opcode opens a cursor to the virtual
** table and stores that cursor in P1.
*/
case OP_VOpen: {
VdbeCursor *pCur;
- sqlite3_vtab_cursor *pVCur;
- sqlite3_vtab *pVtab;
- const sqlite3_module *pModule;
+ tdsqlite3_vtab_cursor *pVCur;
+ tdsqlite3_vtab *pVtab;
+ const tdsqlite3_module *pModule;
assert( p->bIsReader );
pCur = 0;
@@ -86871,10 +95703,10 @@ case OP_VOpen: {
}
pModule = pVtab->pModule;
rc = pModule->xOpen(pVtab, &pVCur);
- sqlite3VtabImportErrmsg(p, pVtab);
+ tdsqlite3VtabImportErrmsg(p, pVtab);
if( rc ) goto abort_due_to_error;
- /* Initialize sqlite3_vtab_cursor base class */
+ /* Initialize tdsqlite3_vtab_cursor base class */
pVCur->pVtab = pVtab;
/* Initialize vdbe cursor object */
@@ -86914,11 +95746,11 @@ case OP_VOpen: {
case OP_VFilter: { /* jump */
int nArg;
int iQuery;
- const sqlite3_module *pModule;
+ const tdsqlite3_module *pModule;
Mem *pQuery;
Mem *pArgc;
- sqlite3_vtab_cursor *pVCur;
- sqlite3_vtab *pVtab;
+ tdsqlite3_vtab_cursor *pVCur;
+ tdsqlite3_vtab *pVtab;
VdbeCursor *pCur;
int res;
int i;
@@ -86946,7 +95778,7 @@ case OP_VFilter: { /* jump */
apArg[i] = &pArgc[i+1];
}
rc = pModule->xFilter(pVCur, iQuery, pOp->p4.z, nArg, apArg);
- sqlite3VtabImportErrmsg(p, pVtab);
+ tdsqlite3VtabImportErrmsg(p, pVtab);
if( rc ) goto abort_due_to_error;
res = pModule->xEof(pVCur);
pCur->nullRow = 0;
@@ -86957,18 +95789,25 @@ case OP_VFilter: { /* jump */
#endif /* SQLITE_OMIT_VIRTUALTABLE */
#ifndef SQLITE_OMIT_VIRTUALTABLE
-/* Opcode: VColumn P1 P2 P3 * *
+/* Opcode: VColumn P1 P2 P3 * P5
** Synopsis: r[P3]=vcolumn(P2)
**
-** Store the value of the P2-th column of
-** the row of the virtual-table that the
-** P1 cursor is pointing to into register P3.
+** Store in register P3 the value of the P2-th column of
+** the current row of the virtual-table of cursor P1.
+**
+** If the VColumn opcode is being used to fetch the value of
+** an unchanging column during an UPDATE operation, then the P5
+** value is OPFLAG_NOCHNG. This will cause the tdsqlite3_vtab_nochange()
+** function to return true inside the xColumn method of the virtual
+** table implementation. The P5 column might also contain other
+** bits (OPFLAG_LENGTHARG or OPFLAG_TYPEOFARG) but those bits are
+** unused by OP_VColumn.
*/
case OP_VColumn: {
- sqlite3_vtab *pVtab;
- const sqlite3_module *pModule;
+ tdsqlite3_vtab *pVtab;
+ const tdsqlite3_module *pModule;
Mem *pDest;
- sqlite3_context sContext;
+ tdsqlite3_context sContext;
VdbeCursor *pCur = p->apCsr[pOp->p1];
assert( pCur->eCurType==CURTYPE_VTAB );
@@ -86976,7 +95815,7 @@ case OP_VColumn: {
pDest = &aMem[pOp->p3];
memAboutToChange(p, pDest);
if( pCur->nullRow ){
- sqlite3VdbeMemSetNull(pDest);
+ tdsqlite3VdbeMemSetNull(pDest);
break;
}
pVtab = pCur->uc.pVCur->pVtab;
@@ -86984,17 +95823,25 @@ case OP_VColumn: {
assert( pModule->xColumn );
memset(&sContext, 0, sizeof(sContext));
sContext.pOut = pDest;
- MemSetTypeFlag(pDest, MEM_Null);
+ assert( pOp->p5==OPFLAG_NOCHNG || pOp->p5==0 );
+ if( pOp->p5 & OPFLAG_NOCHNG ){
+ tdsqlite3VdbeMemSetNull(pDest);
+ pDest->flags = MEM_Null|MEM_Zero;
+ pDest->u.nZero = 0;
+ }else{
+ MemSetTypeFlag(pDest, MEM_Null);
+ }
rc = pModule->xColumn(pCur->uc.pVCur, &sContext, pOp->p2);
- sqlite3VtabImportErrmsg(p, pVtab);
- if( sContext.isError ){
+ tdsqlite3VtabImportErrmsg(p, pVtab);
+ if( sContext.isError>0 ){
+ tdsqlite3VdbeError(p, "%s", tdsqlite3_value_text(pDest));
rc = sContext.isError;
}
- sqlite3VdbeChangeEncoding(pDest, encoding);
+ tdsqlite3VdbeChangeEncoding(pDest, encoding);
REGISTER_TRACE(pOp->p3, pDest);
UPDATE_MAX_BLOBSIZE(pDest);
- if( sqlite3VdbeMemTooBig(pDest) ){
+ if( tdsqlite3VdbeMemTooBig(pDest) ){
goto too_big;
}
if( rc ) goto abort_due_to_error;
@@ -87010,8 +95857,8 @@ case OP_VColumn: {
** the end of its result set, then fall through to the next instruction.
*/
case OP_VNext: { /* jump */
- sqlite3_vtab *pVtab;
- const sqlite3_module *pModule;
+ tdsqlite3_vtab *pVtab;
+ const tdsqlite3_module *pModule;
int res;
VdbeCursor *pCur;
@@ -87032,7 +95879,7 @@ case OP_VNext: { /* jump */
** some other method is next invoked on the save virtual table cursor.
*/
rc = pModule->xNext(pCur->uc.pVCur);
- sqlite3VtabImportErrmsg(p, pVtab);
+ tdsqlite3VtabImportErrmsg(p, pVtab);
if( rc ) goto abort_due_to_error;
res = pModule->xEof(pCur->uc.pVCur);
VdbeBranchTaken(!res,2);
@@ -87047,14 +95894,17 @@ case OP_VNext: { /* jump */
#ifndef SQLITE_OMIT_VIRTUALTABLE
/* Opcode: VRename P1 * * P4 *
**
-** P4 is a pointer to a virtual table object, an sqlite3_vtab structure.
+** P4 is a pointer to a virtual table object, an tdsqlite3_vtab structure.
** This opcode invokes the corresponding xRename method. The value
** in register P1 is passed as the zName argument to the xRename method.
*/
case OP_VRename: {
- sqlite3_vtab *pVtab;
+ tdsqlite3_vtab *pVtab;
Mem *pName;
-
+ int isLegacy;
+
+ isLegacy = (db->flags & SQLITE_LegacyAlter);
+ db->flags |= SQLITE_LegacyAlter;
pVtab = pOp->p4.pVtab->pVtab;
pName = &aMem[pOp->p1];
assert( pVtab->pModule->xRename );
@@ -87065,10 +95915,11 @@ case OP_VRename: {
testcase( pName->enc==SQLITE_UTF8 );
testcase( pName->enc==SQLITE_UTF16BE );
testcase( pName->enc==SQLITE_UTF16LE );
- rc = sqlite3VdbeChangeEncoding(pName, SQLITE_UTF8);
+ rc = tdsqlite3VdbeChangeEncoding(pName, SQLITE_UTF8);
if( rc ) goto abort_due_to_error;
rc = pVtab->pModule->xRename(pVtab, pName->z);
- sqlite3VtabImportErrmsg(p, pVtab);
+ if( isLegacy==0 ) db->flags &= ~(u64)SQLITE_LegacyAlter;
+ tdsqlite3VtabImportErrmsg(p, pVtab);
p->expired = 0;
if( rc ) goto abort_due_to_error;
break;
@@ -87079,7 +95930,7 @@ case OP_VRename: {
/* Opcode: VUpdate P1 P2 P3 P4 P5
** Synopsis: data=r[P3@P2]
**
-** P4 is a pointer to a virtual table object, an sqlite3_vtab structure.
+** P4 is a pointer to a virtual table object, an tdsqlite3_vtab structure.
** This opcode invokes the corresponding xUpdate method. P2 values
** are contiguous memory cells starting at P3 to pass to the xUpdate
** invocation. The value in register (P3+P2-1) corresponds to the
@@ -87097,15 +95948,15 @@ case OP_VRename: {
** a row to delete.
**
** P1 is a boolean flag. If it is set to true and the xUpdate call
-** is successful, then the value returned by sqlite3_last_insert_rowid()
+** is successful, then the value returned by tdsqlite3_last_insert_rowid()
** is set to the value of the rowid for the row just inserted.
**
** P5 is the error actions (OE_Replace, OE_Fail, OE_Ignore, etc) to
** apply in the case of a constraint failure on an insert or update.
*/
case OP_VUpdate: {
- sqlite3_vtab *pVtab;
- const sqlite3_module *pModule;
+ tdsqlite3_vtab *pVtab;
+ const tdsqlite3_module *pModule;
int nArg;
int i;
sqlite_int64 rowid;
@@ -87116,6 +95967,8 @@ case OP_VUpdate: {
|| pOp->p5==OE_Abort || pOp->p5==OE_Ignore || pOp->p5==OE_Replace
);
assert( p->readOnly==0 );
+ if( db->mallocFailed ) goto no_mem;
+ tdsqlite3VdbeIncrWriteCounter(p, 0);
pVtab = pOp->p4.pVtab->pVtab;
if( pVtab==0 || NEVER(pVtab->pModule==0) ){
rc = SQLITE_LOCKED;
@@ -87137,10 +95990,10 @@ case OP_VUpdate: {
db->vtabOnConflict = pOp->p5;
rc = pModule->xUpdate(pVtab, nArg, apArg, &rowid);
db->vtabOnConflict = vtabOnConflict;
- sqlite3VtabImportErrmsg(p, pVtab);
+ tdsqlite3VtabImportErrmsg(p, pVtab);
if( rc==SQLITE_OK && pOp->p1 ){
assert( nArg>1 && apArg[0] && (apArg[0]->flags&MEM_Null) );
- db->lastRowid = lastRowid = rowid;
+ db->lastRowid = rowid;
}
if( (rc&0xff)==SQLITE_CONSTRAINT && pOp->p4.pVtab->bConstraint ){
if( pOp->p5==OE_Ignore ){
@@ -87164,7 +96017,7 @@ case OP_VUpdate: {
*/
case OP_Pagecount: { /* out2 */
pOut = out2Prerelease(p, pOp);
- pOut->u.i = sqlite3BtreeLastPage(db->aDb[pOp->p1].pBt);
+ pOut->u.i = tdsqlite3BtreeLastPage(db->aDb[pOp->p1].pBt);
break;
}
#endif
@@ -87187,45 +96040,158 @@ case OP_MaxPgcnt: { /* out2 */
pBt = db->aDb[pOp->p1].pBt;
newMax = 0;
if( pOp->p3 ){
- newMax = sqlite3BtreeLastPage(pBt);
+ newMax = tdsqlite3BtreeLastPage(pBt);
if( newMax < (unsigned)pOp->p3 ) newMax = (unsigned)pOp->p3;
}
- pOut->u.i = sqlite3BtreeMaxPageCount(pBt, newMax);
+ pOut->u.i = tdsqlite3BtreeMaxPageCount(pBt, newMax);
break;
}
#endif
+/* Opcode: Function P1 P2 P3 P4 *
+** Synopsis: r[P3]=func(r[P2@P5])
+**
+** Invoke a user function (P4 is a pointer to an tdsqlite3_context object that
+** contains a pointer to the function to be run) with arguments taken
+** from register P2 and successors. The number of arguments is in
+** the tdsqlite3_context object that P4 points to.
+** The result of the function is stored
+** in register P3. Register P3 must not be one of the function inputs.
+**
+** P1 is a 32-bit bitmask indicating whether or not each argument to the
+** function was determined to be constant at compile time. If the first
+** argument was constant then bit 0 of P1 is set. This is used to determine
+** whether meta data associated with a user function argument using the
+** tdsqlite3_set_auxdata() API may be safely retained until the next
+** invocation of this opcode.
+**
+** See also: AggStep, AggFinal, PureFunc
+*/
+/* Opcode: PureFunc P1 P2 P3 P4 *
+** Synopsis: r[P3]=func(r[P2@P5])
+**
+** Invoke a user function (P4 is a pointer to an tdsqlite3_context object that
+** contains a pointer to the function to be run) with arguments taken
+** from register P2 and successors. The number of arguments is in
+** the tdsqlite3_context object that P4 points to.
+** The result of the function is stored
+** in register P3. Register P3 must not be one of the function inputs.
+**
+** P1 is a 32-bit bitmask indicating whether or not each argument to the
+** function was determined to be constant at compile time. If the first
+** argument was constant then bit 0 of P1 is set. This is used to determine
+** whether meta data associated with a user function argument using the
+** tdsqlite3_set_auxdata() API may be safely retained until the next
+** invocation of this opcode.
+**
+** This opcode works exactly like OP_Function. The only difference is in
+** its name. This opcode is used in places where the function must be
+** purely non-deterministic. Some built-in date/time functions can be
+** either determinitic of non-deterministic, depending on their arguments.
+** When those function are used in a non-deterministic way, they will check
+** to see if they were called using OP_PureFunc instead of OP_Function, and
+** if they were, they throw an error.
+**
+** See also: AggStep, AggFinal, Function
+*/
+case OP_PureFunc: /* group */
+case OP_Function: { /* group */
+ int i;
+ tdsqlite3_context *pCtx;
+
+ assert( pOp->p4type==P4_FUNCCTX );
+ pCtx = pOp->p4.pCtx;
+
+ /* If this function is inside of a trigger, the register array in aMem[]
+ ** might change from one evaluation to the next. The next block of code
+ ** checks to see if the register array has changed, and if so it
+ ** reinitializes the relavant parts of the tdsqlite3_context object */
+ pOut = &aMem[pOp->p3];
+ if( pCtx->pOut != pOut ){
+ pCtx->pVdbe = p;
+ pCtx->pOut = pOut;
+ for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i];
+ }
+ assert( pCtx->pVdbe==p );
+
+ memAboutToChange(p, pOut);
+#ifdef SQLITE_DEBUG
+ for(i=0; i<pCtx->argc; i++){
+ assert( memIsValid(pCtx->argv[i]) );
+ REGISTER_TRACE(pOp->p2+i, pCtx->argv[i]);
+ }
+#endif
+ MemSetTypeFlag(pOut, MEM_Null);
+ assert( pCtx->isError==0 );
+ (*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */
+
+ /* If the function returned an error, throw an exception */
+ if( pCtx->isError ){
+ if( pCtx->isError>0 ){
+ tdsqlite3VdbeError(p, "%s", tdsqlite3_value_text(pOut));
+ rc = pCtx->isError;
+ }
+ tdsqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1);
+ pCtx->isError = 0;
+ if( rc ) goto abort_due_to_error;
+ }
-/* Opcode: Init P1 P2 * P4 *
+ /* Copy the result of the function into register P3 */
+ if( pOut->flags & (MEM_Str|MEM_Blob) ){
+ tdsqlite3VdbeChangeEncoding(pOut, encoding);
+ if( tdsqlite3VdbeMemTooBig(pOut) ) goto too_big;
+ }
+
+ REGISTER_TRACE(pOp->p3, pOut);
+ UPDATE_MAX_BLOBSIZE(pOut);
+ break;
+}
+
+/* Opcode: Trace P1 P2 * P4 *
+**
+** Write P4 on the statement trace output if statement tracing is
+** enabled.
+**
+** Operand P1 must be 0x7fffffff and P2 must positive.
+*/
+/* Opcode: Init P1 P2 P3 P4 *
** Synopsis: Start at P2
**
** Programs contain a single instance of this opcode as the very first
** opcode.
**
-** If tracing is enabled (by the sqlite3_trace()) interface, then
+** If tracing is enabled (by the tdsqlite3_trace()) interface, then
** the UTF-8 string contained in P4 is emitted on the trace callback.
-** Or if P4 is blank, use the string returned by sqlite3_sql().
+** Or if P4 is blank, use the string returned by tdsqlite3_sql().
**
** If P2 is not zero, jump to instruction P2.
**
** Increment the value of P1 so that OP_Once opcodes will jump the
** first time they are evaluated for this run.
+**
+** If P3 is not zero, then it is an address to jump to if an SQLITE_CORRUPT
+** error is encountered.
*/
+case OP_Trace:
case OP_Init: { /* jump */
- char *zTrace;
int i;
+#ifndef SQLITE_OMIT_TRACE
+ char *zTrace;
+#endif
/* If the P4 argument is not NULL, then it must be an SQL comment string.
** The "--" string is broken up to prevent false-positives with srcck1.c.
**
** This assert() provides evidence for:
** EVIDENCE-OF: R-50676-09860 The callback can compute the same text that
- ** would have been returned by the legacy sqlite3_trace() interface by
+ ** would have been returned by the legacy tdsqlite3_trace() interface by
** using the X argument when X begins with "--" and invoking
- ** sqlite3_expanded_sql(P) otherwise.
+ ** tdsqlite3_expanded_sql(P) otherwise.
*/
assert( pOp->p4.z==0 || strncmp(pOp->p4.z, "-" "- ", 3)==0 );
- assert( pOp==p->aOp ); /* Always instruction 0 */
+
+ /* OP_Init is always instruction 0 */
+ assert( pOp==p->aOp || pOp->opcode==OP_Trace );
#ifndef SQLITE_OMIT_TRACE
if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0
@@ -87235,12 +96201,16 @@ case OP_Init: { /* jump */
#ifndef SQLITE_OMIT_DEPRECATED
if( db->mTrace & SQLITE_TRACE_LEGACY ){
void (*x)(void*,const char*) = (void(*)(void*,const char*))db->xTrace;
- char *z = sqlite3VdbeExpandSql(p, zTrace);
+ char *z = tdsqlite3VdbeExpandSql(p, zTrace);
x(db->pTraceArg, z);
- sqlite3_free(z);
+ tdsqlite3_free(z);
}else
#endif
- {
+ if( db->nVdbeExec>1 ){
+ char *z = tdsqlite3MPrintf(db, "-- %s", zTrace);
+ (void)db->xTrace(SQLITE_TRACE_STMT, db->pTraceArg, p, z);
+ tdsqlite3DbFree(db, z);
+ }else{
(void)db->xTrace(SQLITE_TRACE_STMT, db->pTraceArg, p, zTrace);
}
}
@@ -87250,7 +96220,7 @@ case OP_Init: { /* jump */
int j;
for(j=0; j<db->nDb; j++){
if( DbMaskTest(p->btreeMask, j)==0 ) continue;
- sqlite3_file_control(db, db->aDb[j].zDbSName, SQLITE_FCNTL_TRACE, zTrace);
+ tdsqlite3_file_control(db, db->aDb[j].zDbSName, SQLITE_FCNTL_TRACE, zTrace);
}
}
#endif /* SQLITE_USE_FCNTL_TRACE */
@@ -87258,18 +96228,20 @@ case OP_Init: { /* jump */
if( (db->flags & SQLITE_SqlTrace)!=0
&& (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0
){
- sqlite3DebugPrintf("SQL-trace: %s\n", zTrace);
+ tdsqlite3DebugPrintf("SQL-trace: %s\n", zTrace);
}
#endif /* SQLITE_DEBUG */
#endif /* SQLITE_OMIT_TRACE */
assert( pOp->p2>0 );
- if( pOp->p1>=sqlite3GlobalConfig.iOnceResetThreshold ){
+ if( pOp->p1>=tdsqlite3GlobalConfig.iOnceResetThreshold ){
+ if( pOp->opcode==OP_Trace ) break;
for(i=1; i<p->nOp; i++){
if( p->aOp[i].opcode==OP_Once ) p->aOp[i].p1 = 0;
}
pOp->p1 = 0;
}
pOp->p1++;
+ p->aCounter[SQLITE_STMTSTATUS_RUN]++;
goto jump_to_p2;
}
@@ -87289,13 +96261,78 @@ case OP_CursorHint: {
pC = p->apCsr[pOp->p1];
if( pC ){
assert( pC->eCurType==CURTYPE_BTREE );
- sqlite3BtreeCursorHint(pC->uc.pCursor, BTREE_HINT_RANGE,
+ tdsqlite3BtreeCursorHint(pC->uc.pCursor, BTREE_HINT_RANGE,
pOp->p4.pExpr, aMem);
}
break;
}
#endif /* SQLITE_ENABLE_CURSOR_HINTS */
+#ifdef SQLITE_DEBUG
+/* Opcode: Abortable * * * * *
+**
+** Verify that an Abort can happen. Assert if an Abort at this point
+** might cause database corruption. This opcode only appears in debugging
+** builds.
+**
+** An Abort is safe if either there have been no writes, or if there is
+** an active statement journal.
+*/
+case OP_Abortable: {
+ tdsqlite3VdbeAssertAbortable(p);
+ break;
+}
+#endif
+
+#ifdef SQLITE_DEBUG
+/* Opcode: ReleaseReg P1 P2 P3 * P5
+** Synopsis: release r[P1@P2] mask P3
+**
+** Release registers from service. Any content that was in the
+** the registers is unreliable after this opcode completes.
+**
+** The registers released will be the P2 registers starting at P1,
+** except if bit ii of P3 set, then do not release register P1+ii.
+** In other words, P3 is a mask of registers to preserve.
+**
+** Releasing a register clears the Mem.pScopyFrom pointer. That means
+** that if the content of the released register was set using OP_SCopy,
+** a change to the value of the source register for the OP_SCopy will no longer
+** generate an assertion fault in tdsqlite3VdbeMemAboutToChange().
+**
+** If P5 is set, then all released registers have their type set
+** to MEM_Undefined so that any subsequent attempt to read the released
+** register (before it is reinitialized) will generate an assertion fault.
+**
+** P5 ought to be set on every call to this opcode.
+** However, there are places in the code generator will release registers
+** before their are used, under the (valid) assumption that the registers
+** will not be reallocated for some other purpose before they are used and
+** hence are safe to release.
+**
+** This opcode is only available in testing and debugging builds. It is
+** not generated for release builds. The purpose of this opcode is to help
+** validate the generated bytecode. This opcode does not actually contribute
+** to computing an answer.
+*/
+case OP_ReleaseReg: {
+ Mem *pMem;
+ int i;
+ u32 constMask;
+ assert( pOp->p1>0 );
+ assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 );
+ pMem = &aMem[pOp->p1];
+ constMask = pOp->p3;
+ for(i=0; i<pOp->p2; i++, pMem++){
+ if( i>=32 || (constMask & MASKBIT32(i))==0 ){
+ pMem->pScopyFrom = 0;
+ if( i<32 && pOp->p5 ) MemSetTypeFlag(pMem, MEM_Undefined);
+ }
+ }
+ break;
+}
+#endif
+
/* Opcode: Noop * * * * *
**
** Do nothing. This instruction is often useful as a jump
@@ -87307,8 +96344,9 @@ case OP_CursorHint: {
** This opcode records information from the optimizer. It is the
** the same as a no-op. This opcodesnever appears in a real VM program.
*/
-default: { /* This is really OP_Noop and OP_Explain */
+default: { /* This is really OP_Noop, OP_Explain */
assert( pOp->opcode==OP_Noop || pOp->opcode==OP_Explain );
+
break;
}
@@ -87322,7 +96360,7 @@ default: { /* This is really OP_Noop and OP_Explain */
#ifdef VDBE_PROFILE
{
- u64 endTime = sqlite3Hwtime();
+ u64 endTime = tdsqlite3NProfileCnt ? tdsqlite3NProfileCnt : tdsqlite3Hwtime();
if( endTime>start ) pOrigOp->cycles += endTime - start;
pOrigOp->cnt++;
}
@@ -87338,7 +96376,7 @@ default: { /* This is really OP_Noop and OP_Explain */
#ifdef SQLITE_DEBUG
if( db->flags & SQLITE_VdbeTrace ){
- u8 opProperty = sqlite3OpcodeProperty[pOrigOp->opcode];
+ u8 opProperty = tdsqlite3OpcodeProperty[pOrigOp->opcode];
if( rc!=0 ) printf("rc=%d\n",rc);
if( opProperty & (OPFLG_OUT2) ){
registerTrace(pOrigOp->p2, &aMem[pOrigOp->p2]);
@@ -87346,6 +96384,12 @@ default: { /* This is really OP_Noop and OP_Explain */
if( opProperty & OPFLG_OUT3 ){
registerTrace(pOrigOp->p3, &aMem[pOrigOp->p3]);
}
+ if( opProperty==0xff ){
+ /* Never happens. This code exists to avoid a harmless linkage
+ ** warning aboud tdsqlite3VdbeRegisterDump() being defined but not
+ ** used. */
+ tdsqlite3VdbeRegisterDump(p);
+ }
}
#endif /* SQLITE_DEBUG */
#endif /* NDEBUG */
@@ -87358,30 +96402,38 @@ abort_due_to_error:
if( db->mallocFailed ) rc = SQLITE_NOMEM_BKPT;
assert( rc );
if( p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM ){
- sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc));
+ tdsqlite3VdbeError(p, "%s", tdsqlite3ErrStr(rc));
}
p->rc = rc;
- sqlite3SystemError(db, rc);
- testcase( sqlite3GlobalConfig.xLog!=0 );
- sqlite3_log(rc, "statement aborts at %d: [%s] %s",
+ tdsqlite3SystemError(db, rc);
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ tdsqlite3_log(rc, "statement aborts at %d: [%s] %s",
(int)(pOp - aOp), p->zSql, p->zErrMsg);
- sqlite3VdbeHalt(p);
- if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db);
+ tdsqlite3VdbeHalt(p);
+ if( rc==SQLITE_IOERR_NOMEM ) tdsqlite3OomFault(db);
rc = SQLITE_ERROR;
if( resetSchemaOnFault>0 ){
- sqlite3ResetOneSchema(db, resetSchemaOnFault-1);
+ tdsqlite3ResetOneSchema(db, resetSchemaOnFault-1);
}
/* This is the only way out of this procedure. We have to
** release the mutexes on btrees that were acquired at the
** top. */
vdbe_return:
- db->lastRowid = lastRowid;
- testcase( nVmStep>0 );
+#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
+ while( nVmStep>=nProgressLimit && db->xProgress!=0 ){
+ nProgressLimit += db->nProgressOps;
+ if( db->xProgress(db->pProgressArg) ){
+ nProgressLimit = 0xffffffff;
+ rc = SQLITE_INTERRUPT;
+ goto abort_due_to_error;
+ }
+ }
+#endif
p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep;
- sqlite3VdbeLeave(p);
+ tdsqlite3VdbeLeave(p);
assert( rc!=SQLITE_OK || nExtraDelete==0
- || sqlite3_strlike("DELETE%",p->zSql,0)!=0
+ || tdsqlite3_strlike("DELETE%",p->zSql,0)!=0
);
return rc;
@@ -87389,26 +96441,26 @@ vdbe_return:
** is encountered.
*/
too_big:
- sqlite3VdbeError(p, "string or blob too big");
+ tdsqlite3VdbeError(p, "string or blob too big");
rc = SQLITE_TOOBIG;
goto abort_due_to_error;
/* Jump to here if a malloc() fails.
*/
no_mem:
- sqlite3OomFault(db);
- sqlite3VdbeError(p, "out of memory");
+ tdsqlite3OomFault(db);
+ tdsqlite3VdbeError(p, "out of memory");
rc = SQLITE_NOMEM_BKPT;
goto abort_due_to_error;
- /* Jump to here if the sqlite3_interrupt() API sets the interrupt
+ /* Jump to here if the tdsqlite3_interrupt() API sets the interrupt
** flag.
*/
abort_due_to_interrupt:
assert( db->u1.isInterrupted );
rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT;
p->rc = rc;
- sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc));
+ tdsqlite3VdbeError(p, "%s", tdsqlite3ErrStr(rc));
goto abort_due_to_error;
}
@@ -87436,17 +96488,16 @@ abort_due_to_interrupt:
#ifndef SQLITE_OMIT_INCRBLOB
/*
-** Valid sqlite3_blob* handles point to Incrblob structures.
+** Valid tdsqlite3_blob* handles point to Incrblob structures.
*/
typedef struct Incrblob Incrblob;
struct Incrblob {
- int flags; /* Copy of "flags" passed to sqlite3_blob_open() */
int nByte; /* Size of open blob, in bytes */
int iOffset; /* Byte offset of blob in cursor data */
- int iCol; /* Table column this handle is open on */
+ u16 iCol; /* Table column this handle is open on */
BtCursor *pCsr; /* Cursor pointing at blob row */
- sqlite3_stmt *pStmt; /* Statement holding cursor open */
- sqlite3 *db; /* The associated database */
+ tdsqlite3_stmt *pStmt; /* Statement holding cursor open */
+ tdsqlite3 *db; /* The associated database */
char *zDb; /* Database name */
Table *pTab; /* Table object */
};
@@ -87456,60 +96507,71 @@ struct Incrblob {
** This function is used by both blob_open() and blob_reopen(). It seeks
** the b-tree cursor associated with blob handle p to point to row iRow.
** If successful, SQLITE_OK is returned and subsequent calls to
-** sqlite3_blob_read() or sqlite3_blob_write() access the specified row.
+** tdsqlite3_blob_read() or tdsqlite3_blob_write() access the specified row.
**
** If an error occurs, or if the specified row does not exist or does not
** contain a value of type TEXT or BLOB in the column nominated when the
** blob handle was opened, then an error code is returned and *pzErr may
** be set to point to a buffer containing an error message. It is the
** responsibility of the caller to free the error message buffer using
-** sqlite3DbFree().
+** tdsqlite3DbFree().
**
** If an error does occur, then the b-tree cursor is closed. All subsequent
-** calls to sqlite3_blob_read(), blob_write() or blob_reopen() will
+** calls to tdsqlite3_blob_read(), blob_write() or blob_reopen() will
** immediately return SQLITE_ABORT.
*/
-static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){
+static int blobSeekToRow(Incrblob *p, tdsqlite3_int64 iRow, char **pzErr){
int rc; /* Error code */
char *zErr = 0; /* Error message */
Vdbe *v = (Vdbe *)p->pStmt;
- /* Set the value of the SQL statements only variable to integer iRow.
- ** This is done directly instead of using sqlite3_bind_int64() to avoid
- ** triggering asserts related to mutexes.
+ /* Set the value of register r[1] in the SQL statement to integer iRow.
+ ** This is done directly as a performance optimization
*/
- assert( v->aVar[0].flags&MEM_Int );
- v->aVar[0].u.i = iRow;
+ v->aMem[1].flags = MEM_Int;
+ v->aMem[1].u.i = iRow;
- rc = sqlite3_step(p->pStmt);
+ /* If the statement has been run before (and is paused at the OP_ResultRow)
+ ** then back it up to the point where it does the OP_NotExists. This could
+ ** have been down with an extra OP_Goto, but simply setting the program
+ ** counter is faster. */
+ if( v->pc>4 ){
+ v->pc = 4;
+ assert( v->aOp[v->pc].opcode==OP_NotExists );
+ rc = tdsqlite3VdbeExec(v);
+ }else{
+ rc = tdsqlite3_step(p->pStmt);
+ }
if( rc==SQLITE_ROW ){
VdbeCursor *pC = v->apCsr[0];
- u32 type = pC->aType[p->iCol];
+ u32 type = pC->nHdrParsed>p->iCol ? pC->aType[p->iCol] : 0;
+ testcase( pC->nHdrParsed==p->iCol );
+ testcase( pC->nHdrParsed==p->iCol+1 );
if( type<12 ){
- zErr = sqlite3MPrintf(p->db, "cannot open value of type %s",
+ zErr = tdsqlite3MPrintf(p->db, "cannot open value of type %s",
type==0?"null": type==7?"real": "integer"
);
rc = SQLITE_ERROR;
- sqlite3_finalize(p->pStmt);
+ tdsqlite3_finalize(p->pStmt);
p->pStmt = 0;
}else{
p->iOffset = pC->aType[p->iCol + pC->nField];
- p->nByte = sqlite3VdbeSerialTypeLen(type);
+ p->nByte = tdsqlite3VdbeSerialTypeLen(type);
p->pCsr = pC->uc.pCursor;
- sqlite3BtreeIncrblobCursor(p->pCsr);
+ tdsqlite3BtreeIncrblobCursor(p->pCsr);
}
}
if( rc==SQLITE_ROW ){
rc = SQLITE_OK;
}else if( p->pStmt ){
- rc = sqlite3_finalize(p->pStmt);
+ rc = tdsqlite3_finalize(p->pStmt);
p->pStmt = 0;
if( rc==SQLITE_OK ){
- zErr = sqlite3MPrintf(p->db, "no such rowid: %lld", iRow);
+ zErr = tdsqlite3MPrintf(p->db, "no such rowid: %lld", iRow);
rc = SQLITE_ERROR;
}else{
- zErr = sqlite3MPrintf(p->db, "%s", sqlite3_errmsg(p->db));
+ zErr = tdsqlite3MPrintf(p->db, "%s", tdsqlite3_errmsg(p->db));
}
}
@@ -87523,22 +96585,22 @@ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){
/*
** Open a blob handle.
*/
-SQLITE_API int sqlite3_blob_open(
- sqlite3* db, /* The database connection */
+SQLITE_API int tdsqlite3_blob_open(
+ tdsqlite3* db, /* The database connection */
const char *zDb, /* The attached database containing the blob */
const char *zTable, /* The table containing the blob */
const char *zColumn, /* The column containing the blob */
sqlite_int64 iRow, /* The row containing the glob */
- int flags, /* True -> read/write access, false -> read-only */
- sqlite3_blob **ppBlob /* Handle for accessing the blob returned here */
+ int wrFlag, /* True -> read/write access, false -> read-only */
+ tdsqlite3_blob **ppBlob /* Handle for accessing the blob returned here */
){
int nAttempt = 0;
int iCol; /* Index of zColumn in row-record */
int rc = SQLITE_OK;
char *zErr = 0;
Table *pTab;
- Parse *pParse = 0;
Incrblob *pBlob = 0;
+ Parse sParse;
#ifdef SQLITE_ENABLE_API_ARMOR
if( ppBlob==0 ){
@@ -87547,73 +96609,69 @@ SQLITE_API int sqlite3_blob_open(
#endif
*ppBlob = 0;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || zTable==0 ){
+ if( !tdsqlite3SafetyCheckOk(db) || zTable==0 ){
return SQLITE_MISUSE_BKPT;
}
#endif
- flags = !!flags; /* flags = (flags ? 1 : 0); */
+ wrFlag = !!wrFlag; /* wrFlag = (wrFlag ? 1 : 0); */
- sqlite3_mutex_enter(db->mutex);
-
- pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob));
- if( !pBlob ) goto blob_open_out;
- pParse = sqlite3StackAllocRaw(db, sizeof(*pParse));
- if( !pParse ) goto blob_open_out;
+ tdsqlite3_mutex_enter(db->mutex);
+ pBlob = (Incrblob *)tdsqlite3DbMallocZero(db, sizeof(Incrblob));
do {
- memset(pParse, 0, sizeof(Parse));
- pParse->db = db;
- sqlite3DbFree(db, zErr);
+ memset(&sParse, 0, sizeof(Parse));
+ if( !pBlob ) goto blob_open_out;
+ sParse.db = db;
+ tdsqlite3DbFree(db, zErr);
zErr = 0;
- sqlite3BtreeEnterAll(db);
- pTab = sqlite3LocateTable(pParse, 0, zTable, zDb);
+ tdsqlite3BtreeEnterAll(db);
+ pTab = tdsqlite3LocateTable(&sParse, 0, zTable, zDb);
if( pTab && IsVirtual(pTab) ){
pTab = 0;
- sqlite3ErrorMsg(pParse, "cannot open virtual table: %s", zTable);
+ tdsqlite3ErrorMsg(&sParse, "cannot open virtual table: %s", zTable);
}
if( pTab && !HasRowid(pTab) ){
pTab = 0;
- sqlite3ErrorMsg(pParse, "cannot open table without rowid: %s", zTable);
+ tdsqlite3ErrorMsg(&sParse, "cannot open table without rowid: %s", zTable);
}
#ifndef SQLITE_OMIT_VIEW
if( pTab && pTab->pSelect ){
pTab = 0;
- sqlite3ErrorMsg(pParse, "cannot open view: %s", zTable);
+ tdsqlite3ErrorMsg(&sParse, "cannot open view: %s", zTable);
}
#endif
if( !pTab ){
- if( pParse->zErrMsg ){
- sqlite3DbFree(db, zErr);
- zErr = pParse->zErrMsg;
- pParse->zErrMsg = 0;
+ if( sParse.zErrMsg ){
+ tdsqlite3DbFree(db, zErr);
+ zErr = sParse.zErrMsg;
+ sParse.zErrMsg = 0;
}
rc = SQLITE_ERROR;
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3BtreeLeaveAll(db);
goto blob_open_out;
}
pBlob->pTab = pTab;
- pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName;
+ pBlob->zDb = db->aDb[tdsqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName;
/* Now search pTab for the exact column. */
for(iCol=0; iCol<pTab->nCol; iCol++) {
- if( sqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){
+ if( tdsqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){
break;
}
}
if( iCol==pTab->nCol ){
- sqlite3DbFree(db, zErr);
- zErr = sqlite3MPrintf(db, "no such column: \"%s\"", zColumn);
+ tdsqlite3DbFree(db, zErr);
+ zErr = tdsqlite3MPrintf(db, "no such column: \"%s\"", zColumn);
rc = SQLITE_ERROR;
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3BtreeLeaveAll(db);
goto blob_open_out;
}
/* If the value is being opened for writing, check that the
** column is not indexed, and that it is not part of a foreign key.
- ** It is against the rules to open a column to which either of these
- ** descriptions applies for writing. */
- if( flags ){
+ */
+ if( wrFlag ){
const char *zFault = 0;
Index *pIdx;
#ifndef SQLITE_OMIT_FOREIGN_KEY
@@ -87643,15 +96701,15 @@ SQLITE_API int sqlite3_blob_open(
}
}
if( zFault ){
- sqlite3DbFree(db, zErr);
- zErr = sqlite3MPrintf(db, "cannot open %s column for writing", zFault);
+ tdsqlite3DbFree(db, zErr);
+ zErr = tdsqlite3MPrintf(db, "cannot open %s column for writing", zFault);
rc = SQLITE_ERROR;
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3BtreeLeaveAll(db);
goto blob_open_out;
}
}
- pBlob->pStmt = (sqlite3_stmt *)sqlite3VdbeCreate(pParse);
+ pBlob->pStmt = (tdsqlite3_stmt *)tdsqlite3VdbeCreate(&sParse);
assert( pBlob->pStmt || db->mallocFailed );
if( pBlob->pStmt ){
@@ -87666,7 +96724,7 @@ SQLITE_API int sqlite3_blob_open(
** uses it to implement the blob_read(), blob_write() and
** blob_bytes() functions.
**
- ** The sqlite3_blob_close() function finalizes the vdbe program,
+ ** The tdsqlite3_blob_close() function finalizes the vdbe program,
** which closes the b-tree cursor and (possibly) commits the
** transaction.
*/
@@ -87674,26 +96732,25 @@ SQLITE_API int sqlite3_blob_open(
static const VdbeOpList openBlob[] = {
{OP_TableLock, 0, 0, 0}, /* 0: Acquire a read or write lock */
{OP_OpenRead, 0, 0, 0}, /* 1: Open a cursor */
- {OP_Variable, 1, 1, 0}, /* 2: Move ?1 into reg[1] */
- {OP_NotExists, 0, 7, 1}, /* 3: Seek the cursor */
- {OP_Column, 0, 0, 1}, /* 4 */
- {OP_ResultRow, 1, 0, 0}, /* 5 */
- {OP_Goto, 0, 2, 0}, /* 6 */
- {OP_Close, 0, 0, 0}, /* 7 */
- {OP_Halt, 0, 0, 0}, /* 8 */
+ /* blobSeekToRow() will initialize r[1] to the desired rowid */
+ {OP_NotExists, 0, 5, 1}, /* 2: Seek the cursor to rowid=r[1] */
+ {OP_Column, 0, 0, 1}, /* 3 */
+ {OP_ResultRow, 1, 0, 0}, /* 4 */
+ {OP_Halt, 0, 0, 0}, /* 5 */
};
Vdbe *v = (Vdbe *)pBlob->pStmt;
- int iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ int iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
VdbeOp *aOp;
- sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, flags,
+ tdsqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, wrFlag,
pTab->pSchema->schema_cookie,
pTab->pSchema->iGeneration);
- sqlite3VdbeChangeP5(v, 1);
- aOp = sqlite3VdbeAddOpList(v, ArraySize(openBlob), openBlob, iLn);
+ tdsqlite3VdbeChangeP5(v, 1);
+ assert( tdsqlite3VdbeCurrentAddr(v)==2 || db->mallocFailed );
+ aOp = tdsqlite3VdbeAddOpList(v, ArraySize(openBlob), openBlob, iLn);
/* Make sure a mutex is held on the table to be accessed */
- sqlite3VdbeUsesBtree(v, iDb);
+ tdsqlite3VdbeUsesBtree(v, iDb);
if( db->mallocFailed==0 ){
assert( aOp!=0 );
@@ -87703,15 +96760,15 @@ SQLITE_API int sqlite3_blob_open(
#else
aOp[0].p1 = iDb;
aOp[0].p2 = pTab->tnum;
- aOp[0].p3 = flags;
- sqlite3VdbeChangeP4(v, 1, pTab->zName, P4_TRANSIENT);
+ aOp[0].p3 = wrFlag;
+ tdsqlite3VdbeChangeP4(v, 2, pTab->zName, P4_TRANSIENT);
}
if( db->mallocFailed==0 ){
#endif
/* Remove either the OP_OpenWrite or OpenRead. Set the P2
** parameter of the other to pTab->tnum. */
- if( flags ) aOp[1].opcode = OP_OpenWrite;
+ if( wrFlag ) aOp[1].opcode = OP_OpenWrite;
aOp[1].p2 = pTab->tnum;
aOp[1].p3 = iDb;
@@ -87724,57 +96781,55 @@ SQLITE_API int sqlite3_blob_open(
*/
aOp[1].p4type = P4_INT32;
aOp[1].p4.i = pTab->nCol+1;
- aOp[4].p2 = pTab->nCol;
+ aOp[3].p2 = pTab->nCol;
- pParse->nVar = 1;
- pParse->nMem = 1;
- pParse->nTab = 1;
- sqlite3VdbeMakeReady(v, pParse);
+ sParse.nVar = 0;
+ sParse.nMem = 1;
+ sParse.nTab = 1;
+ tdsqlite3VdbeMakeReady(v, &sParse);
}
}
- pBlob->flags = flags;
pBlob->iCol = iCol;
pBlob->db = db;
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3BtreeLeaveAll(db);
if( db->mallocFailed ){
goto blob_open_out;
}
- sqlite3_bind_int64(pBlob->pStmt, 1, iRow);
rc = blobSeekToRow(pBlob, iRow, &zErr);
} while( (++nAttempt)<SQLITE_MAX_SCHEMA_RETRY && rc==SQLITE_SCHEMA );
blob_open_out:
if( rc==SQLITE_OK && db->mallocFailed==0 ){
- *ppBlob = (sqlite3_blob *)pBlob;
+ *ppBlob = (tdsqlite3_blob *)pBlob;
}else{
- if( pBlob && pBlob->pStmt ) sqlite3VdbeFinalize((Vdbe *)pBlob->pStmt);
- sqlite3DbFree(db, pBlob);
+ if( pBlob && pBlob->pStmt ) tdsqlite3VdbeFinalize((Vdbe *)pBlob->pStmt);
+ tdsqlite3DbFree(db, pBlob);
}
- sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr);
- sqlite3DbFree(db, zErr);
- sqlite3ParserReset(pParse);
- sqlite3StackFree(db, pParse);
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr);
+ tdsqlite3DbFree(db, zErr);
+ tdsqlite3ParserReset(&sParse);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
/*
** Close a blob handle that was previously created using
-** sqlite3_blob_open().
+** tdsqlite3_blob_open().
*/
-SQLITE_API int sqlite3_blob_close(sqlite3_blob *pBlob){
+SQLITE_API int tdsqlite3_blob_close(tdsqlite3_blob *pBlob){
Incrblob *p = (Incrblob *)pBlob;
int rc;
- sqlite3 *db;
+ tdsqlite3 *db;
if( p ){
+ tdsqlite3_stmt *pStmt = p->pStmt;
db = p->db;
- sqlite3_mutex_enter(db->mutex);
- rc = sqlite3_finalize(p->pStmt);
- sqlite3DbFree(db, p);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
+ tdsqlite3DbFree(db, p);
+ tdsqlite3_mutex_leave(db->mutex);
+ rc = tdsqlite3_finalize(pStmt);
}else{
rc = SQLITE_OK;
}
@@ -87785,7 +96840,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *pBlob){
** Perform a read or write operation on a blob
*/
static int blobReadWrite(
- sqlite3_blob *pBlob,
+ tdsqlite3_blob *pBlob,
void *z,
int n,
int iOffset,
@@ -87794,14 +96849,14 @@ static int blobReadWrite(
int rc;
Incrblob *p = (Incrblob *)pBlob;
Vdbe *v;
- sqlite3 *db;
+ tdsqlite3 *db;
if( p==0 ) return SQLITE_MISUSE_BKPT;
db = p->db;
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
v = (Vdbe*)p->pStmt;
- if( n<0 || iOffset<0 || ((sqlite3_int64)iOffset+n)>p->nByte ){
+ if( n<0 || iOffset<0 || ((tdsqlite3_int64)iOffset+n)>p->nByte ){
/* Request is out of range. Return a transient error. */
rc = SQLITE_ERROR;
}else if( v==0 ){
@@ -87814,10 +96869,10 @@ static int blobReadWrite(
** returned, clean-up the statement handle.
*/
assert( db == v->db );
- sqlite3BtreeEnterCursor(p->pCsr);
+ tdsqlite3BtreeEnterCursor(p->pCsr);
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
- if( xCall==sqlite3BtreePutData && db->xPreUpdateCallback ){
+ if( xCall==tdsqlite3BtreePutData && db->xPreUpdateCallback ){
/* If a pre-update hook is registered and this is a write cursor,
** invoke it here.
**
@@ -87831,41 +96886,41 @@ static int blobReadWrite(
** using the incremental-blob API, this works. For the sessions module
** anyhow.
*/
- sqlite3_int64 iKey;
- iKey = sqlite3BtreeIntegerKey(p->pCsr);
- sqlite3VdbePreUpdateHook(
+ tdsqlite3_int64 iKey;
+ iKey = tdsqlite3BtreeIntegerKey(p->pCsr);
+ tdsqlite3VdbePreUpdateHook(
v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1
);
}
#endif
rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
- sqlite3BtreeLeaveCursor(p->pCsr);
+ tdsqlite3BtreeLeaveCursor(p->pCsr);
if( rc==SQLITE_ABORT ){
- sqlite3VdbeFinalize(v);
+ tdsqlite3VdbeFinalize(v);
p->pStmt = 0;
}else{
v->rc = rc;
}
}
- sqlite3Error(db, rc);
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3Error(db, rc);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
/*
** Read data from a blob handle.
*/
-SQLITE_API int sqlite3_blob_read(sqlite3_blob *pBlob, void *z, int n, int iOffset){
- return blobReadWrite(pBlob, z, n, iOffset, sqlite3BtreeData);
+SQLITE_API int tdsqlite3_blob_read(tdsqlite3_blob *pBlob, void *z, int n, int iOffset){
+ return blobReadWrite(pBlob, z, n, iOffset, tdsqlite3BtreePayloadChecked);
}
/*
** Write data to a blob handle.
*/
-SQLITE_API int sqlite3_blob_write(sqlite3_blob *pBlob, const void *z, int n, int iOffset){
- return blobReadWrite(pBlob, (void *)z, n, iOffset, sqlite3BtreePutData);
+SQLITE_API int tdsqlite3_blob_write(tdsqlite3_blob *pBlob, const void *z, int n, int iOffset){
+ return blobReadWrite(pBlob, (void *)z, n, iOffset, tdsqlite3BtreePutData);
}
/*
@@ -87874,7 +96929,7 @@ SQLITE_API int sqlite3_blob_write(sqlite3_blob *pBlob, const void *z, int n, int
** The Incrblob.nByte field is fixed for the lifetime of the Incrblob
** so no mutex is required for access.
*/
-SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *pBlob){
+SQLITE_API int tdsqlite3_blob_bytes(tdsqlite3_blob *pBlob){
Incrblob *p = (Incrblob *)pBlob;
return (p && p->pStmt) ? p->nByte : 0;
}
@@ -87886,17 +96941,17 @@ SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *pBlob){
** If an error occurs, or if the specified row does not exist or does not
** contain a blob or text value, then an error code is returned and the
** database handle error code and message set. If this happens, then all
-** subsequent calls to sqlite3_blob_xxx() functions (except blob_close())
+** subsequent calls to tdsqlite3_blob_xxx() functions (except blob_close())
** immediately return SQLITE_ABORT.
*/
-SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
+SQLITE_API int tdsqlite3_blob_reopen(tdsqlite3_blob *pBlob, tdsqlite3_int64 iRow){
int rc;
Incrblob *p = (Incrblob *)pBlob;
- sqlite3 *db;
+ tdsqlite3 *db;
if( p==0 ) return SQLITE_MISUSE_BKPT;
db = p->db;
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
if( p->pStmt==0 ){
/* If there is no statement handle, then the blob-handle has
@@ -87907,15 +96962,15 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
char *zErr;
rc = blobSeekToRow(p, iRow, &zErr);
if( rc!=SQLITE_OK ){
- sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr);
- sqlite3DbFree(db, zErr);
+ tdsqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr);
+ tdsqlite3DbFree(db, zErr);
}
assert( rc!=SQLITE_SCHEMA );
}
- rc = sqlite3ApiExit(db, rc);
+ rc = tdsqlite3ApiExit(db, rc);
assert( rc==SQLITE_OK || p->pStmt==0 );
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -87946,9 +97001,9 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
** Here is the (internal, non-API) interface between this module and the
** rest of the SQLite system:
**
-** sqlite3VdbeSorterInit() Create a new VdbeSorter object.
+** tdsqlite3VdbeSorterInit() Create a new VdbeSorter object.
**
-** sqlite3VdbeSorterWrite() Add a single new row to the VdbeSorter
+** tdsqlite3VdbeSorterWrite() Add a single new row to the VdbeSorter
** object. The row is a binary blob in the
** OP_MakeRecord format that contains both
** the ORDER BY key columns and result columns
@@ -87956,27 +97011,27 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
** the complete record for an index entry
** in the case of a CREATE INDEX.
**
-** sqlite3VdbeSorterRewind() Sort all content previously added.
+** tdsqlite3VdbeSorterRewind() Sort all content previously added.
** Position the read cursor on the
** first sorted element.
**
-** sqlite3VdbeSorterNext() Advance the read cursor to the next sorted
+** tdsqlite3VdbeSorterNext() Advance the read cursor to the next sorted
** element.
**
-** sqlite3VdbeSorterRowkey() Return the complete binary blob for the
+** tdsqlite3VdbeSorterRowkey() Return the complete binary blob for the
** row currently under the read cursor.
**
-** sqlite3VdbeSorterCompare() Compare the binary blob for the row
+** tdsqlite3VdbeSorterCompare() Compare the binary blob for the row
** currently under the read cursor against
** another binary blob X and report if
** X is strictly less than the read cursor.
** Used to enforce uniqueness in a
** CREATE UNIQUE INDEX statement.
**
-** sqlite3VdbeSorterClose() Close the VdbeSorter object and reclaim
+** tdsqlite3VdbeSorterClose() Close the VdbeSorter object and reclaim
** all resources.
**
-** sqlite3VdbeSorterReset() Refurbish the VdbeSorter for reuse. This
+** tdsqlite3VdbeSorterReset() Refurbish the VdbeSorter for reuse. This
** is like Close() followed by Init() only
** much faster.
**
@@ -88096,7 +97151,7 @@ typedef struct IncrMerger IncrMerger; /* Read & merge multiple PMAs */
** stored in the file.
*/
struct SorterFile {
- sqlite3_file *pFd; /* File handle */
+ tdsqlite3_file *pFd; /* File handle */
i64 iEof; /* Bytes of data stored in pFd */
};
@@ -88209,7 +97264,7 @@ struct MergeEngine {
** to block until it finishes).
**
** 2. If SQLITE_DEBUG_SORTER_THREADS is defined, to determine if a call
-** to sqlite3ThreadJoin() is likely to block. Cases that are likely to
+** to tdsqlite3ThreadJoin() is likely to block. Cases that are likely to
** block provoke debugging output.
**
** In both cases, the effects of the main thread seeing (bDone==0) even
@@ -88235,7 +97290,7 @@ struct SortSubtask {
** sorter cursor created by the VDBE.
**
** mxKeysize:
-** As records are added to the sorter by calls to sqlite3VdbeSorterWrite(),
+** As records are added to the sorter by calls to tdsqlite3VdbeSorterWrite(),
** this variable is updated so as to be set to the size on disk of the
** largest record in the sorter.
*/
@@ -88246,7 +97301,7 @@ struct VdbeSorter {
int pgsz; /* Main database page size */
PmaReader *pReader; /* Readr data from here after Rewind() */
MergeEngine *pMerger; /* Or here, if bUseThreads==0 */
- sqlite3 *db; /* Database connection */
+ tdsqlite3 *db; /* Database connection */
KeyInfo *pKeyInfo; /* How to compare records */
UnpackedRecord *pUnpacked; /* Used by VdbeSorterCompare() */
SorterList list; /* List of in-memory records */
@@ -88277,7 +97332,7 @@ struct PmaReader {
i64 iEof; /* 1 byte past EOF for this PmaReader */
int nAlloc; /* Bytes of space at aAlloc */
int nKey; /* Number of bytes in key */
- sqlite3_file *pFd; /* File handle we are reading from */
+ tdsqlite3_file *pFd; /* File handle we are reading from */
u8 *aAlloc; /* Space for aKey if aBuffer and pMap wont work */
u8 *aKey; /* Pointer to current key */
u8 *aBuffer; /* Current read buffer */
@@ -88343,7 +97398,7 @@ struct PmaWriter {
int iBufStart; /* First byte of buffer to write */
int iBufEnd; /* Last byte of buffer to write */
i64 iWriteOff; /* Offset of start of buffer in file */
- sqlite3_file *pFd; /* File handle to write to */
+ tdsqlite3_file *pFd; /* File handle to write to */
};
/*
@@ -88358,7 +97413,7 @@ struct PmaWriter {
** Or, if using the single large allocation method (VdbeSorter.list.aMemory!=0),
** then while records are being accumulated the list is linked using the
** SorterRecord.u.iNext offset. This is because the aMemory[] array may
-** be sqlite3Realloc()ed while records are being accumulated. Once the VM
+** be tdsqlite3Realloc()ed while records are being accumulated. Once the VM
** has finished passing records to the sorter, or when the in-memory buffer
** is full, the list is sorted. As part of the sorting process, it is
** converted to use the SorterRecord.u.pNext pointers. See function
@@ -88392,9 +97447,9 @@ static void vdbeIncrFree(IncrMerger *);
** argument. All structure fields are set to zero before returning.
*/
static void vdbePmaReaderClear(PmaReader *pReadr){
- sqlite3_free(pReadr->aAlloc);
- sqlite3_free(pReadr->aBuffer);
- if( pReadr->aMap ) sqlite3OsUnfetch(pReadr->pFd, 0, pReadr->aMap);
+ tdsqlite3_free(pReadr->aAlloc);
+ tdsqlite3_free(pReadr->aBuffer);
+ if( pReadr->aMap ) tdsqlite3OsUnfetch(pReadr->pFd, 0, pReadr->aMap);
vdbeIncrFree(pReadr->pIncr);
memset(pReadr, 0, sizeof(PmaReader));
}
@@ -88430,7 +97485,7 @@ static int vdbePmaReadBlob(
iBuf = p->iReadOff % p->nBuffer;
if( iBuf==0 ){
int nRead; /* Bytes to read from disk */
- int rc; /* sqlite3OsRead() return code */
+ int rc; /* tdsqlite3OsRead() return code */
/* Determine how many bytes of data to read. */
if( (p->iEof - p->iReadOff) > (i64)p->nBuffer ){
@@ -88441,7 +97496,7 @@ static int vdbePmaReadBlob(
assert( nRead>0 );
/* Readr data from the file. Return early if an error occurs. */
- rc = sqlite3OsRead(p->pFd, p->aBuffer, nRead, p->iReadOff);
+ rc = tdsqlite3OsRead(p->pFd, p->aBuffer, nRead, p->iReadOff);
assert( rc!=SQLITE_IOERR_SHORT_READ );
if( rc!=SQLITE_OK ) return rc;
}
@@ -88462,9 +97517,9 @@ static int vdbePmaReadBlob(
/* Extend the p->aAlloc[] allocation if required. */
if( p->nAlloc<nByte ){
u8 *aNew;
- int nNew = MAX(128, p->nAlloc*2);
+ tdsqlite3_int64 nNew = MAX(128, 2*(tdsqlite3_int64)p->nAlloc);
while( nByte>nNew ) nNew = nNew*2;
- aNew = sqlite3Realloc(p->aAlloc, nNew);
+ aNew = tdsqlite3Realloc(p->aAlloc, nNew);
if( !aNew ) return SQLITE_NOMEM_BKPT;
p->nAlloc = nNew;
p->aAlloc = aNew;
@@ -88506,11 +97561,11 @@ static int vdbePmaReadVarint(PmaReader *p, u64 *pnOut){
int iBuf;
if( p->aMap ){
- p->iReadOff += sqlite3GetVarint(&p->aMap[p->iReadOff], pnOut);
+ p->iReadOff += tdsqlite3GetVarint(&p->aMap[p->iReadOff], pnOut);
}else{
iBuf = p->iReadOff % p->nBuffer;
if( iBuf && (p->nBuffer-iBuf)>=9 ){
- p->iReadOff += sqlite3GetVarint(&p->aBuffer[iBuf], pnOut);
+ p->iReadOff += tdsqlite3GetVarint(&p->aBuffer[iBuf], pnOut);
}else{
u8 aVarint[16], *a;
int i = 0, rc;
@@ -88519,7 +97574,7 @@ static int vdbePmaReadVarint(PmaReader *p, u64 *pnOut){
if( rc ) return rc;
aVarint[(i++)&0xf] = a[0];
}while( (a[0]&0x80)!=0 );
- sqlite3GetVarint(aVarint, pnOut);
+ tdsqlite3GetVarint(aVarint, pnOut);
}
}
@@ -88538,9 +97593,9 @@ static int vdbePmaReadVarint(PmaReader *p, u64 *pnOut){
static int vdbeSorterMapFile(SortSubtask *pTask, SorterFile *pFile, u8 **pp){
int rc = SQLITE_OK;
if( pFile->iEof<=(i64)(pTask->pSorter->db->nMaxSorterMmap) ){
- sqlite3_file *pFd = pFile->pFd;
+ tdsqlite3_file *pFd = pFile->pFd;
if( pFd->pMethods->iVersion>=3 ){
- rc = sqlite3OsFetch(pFd, 0, (int)pFile->iEof, (void**)pp);
+ rc = tdsqlite3OsFetch(pFd, 0, (int)pFile->iEof, (void**)pp);
testcase( rc!=SQLITE_OK );
}
}
@@ -88562,9 +97617,9 @@ static int vdbePmaReaderSeek(
assert( pReadr->pIncr==0 || pReadr->pIncr->bEof==0 );
- if( sqlite3FaultSim(201) ) return SQLITE_IOERR_READ;
+ if( tdsqlite3FaultSim(201) ) return SQLITE_IOERR_READ;
if( pReadr->aMap ){
- sqlite3OsUnfetch(pReadr->pFd, 0, pReadr->aMap);
+ tdsqlite3OsUnfetch(pReadr->pFd, 0, pReadr->aMap);
pReadr->aMap = 0;
}
pReadr->iReadOff = iOff;
@@ -88576,7 +97631,7 @@ static int vdbePmaReaderSeek(
int pgsz = pTask->pSorter->pgsz;
int iBuf = pReadr->iReadOff % pgsz;
if( pReadr->aBuffer==0 ){
- pReadr->aBuffer = (u8*)sqlite3Malloc(pgsz);
+ pReadr->aBuffer = (u8*)tdsqlite3Malloc(pgsz);
if( pReadr->aBuffer==0 ) rc = SQLITE_NOMEM_BKPT;
pReadr->nBuffer = pgsz;
}
@@ -88585,7 +97640,7 @@ static int vdbePmaReaderSeek(
if( (pReadr->iReadOff + nRead) > pReadr->iEof ){
nRead = (int)(pReadr->iEof - pReadr->iReadOff);
}
- rc = sqlite3OsRead(
+ rc = tdsqlite3OsRead(
pReadr->pFd, &pReadr->aBuffer[iBuf], nRead, pReadr->iReadOff
);
testcase( rc!=SQLITE_OK );
@@ -88687,10 +97742,10 @@ static int vdbeSorterCompareTail(
){
UnpackedRecord *r2 = pTask->pUnpacked;
if( *pbKey2Cached==0 ){
- sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2);
+ tdsqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2);
*pbKey2Cached = 1;
}
- return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, r2, 1);
+ return tdsqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, r2, 1);
}
/*
@@ -88714,10 +97769,10 @@ static int vdbeSorterCompare(
){
UnpackedRecord *r2 = pTask->pUnpacked;
if( !*pbKey2Cached ){
- sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2);
+ tdsqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2);
*pbKey2Cached = 1;
}
- return sqlite3VdbeRecordCompare(nKey1, pKey1, r2);
+ return tdsqlite3VdbeRecordCompare(nKey1, pKey1, r2);
}
/*
@@ -88740,21 +97795,22 @@ static int vdbeSorterCompareText(
int n2;
int res;
- getVarint32(&p1[1], n1); n1 = (n1 - 13) / 2;
- getVarint32(&p2[1], n2); n2 = (n2 - 13) / 2;
- res = memcmp(v1, v2, MIN(n1, n2));
+ getVarint32(&p1[1], n1);
+ getVarint32(&p2[1], n2);
+ res = memcmp(v1, v2, (MIN(n1, n2) - 13)/2);
if( res==0 ){
res = n1 - n2;
}
if( res==0 ){
- if( pTask->pSorter->pKeyInfo->nField>1 ){
+ if( pTask->pSorter->pKeyInfo->nKeyField>1 ){
res = vdbeSorterCompareTail(
pTask, pbKey2Cached, pKey1, nKey1, pKey2, nKey2
);
}
}else{
- if( pTask->pSorter->pKeyInfo->aSortOrder[0] ){
+ assert( !(pTask->pSorter->pKeyInfo->aSortFlags[0]&KEYINFO_ORDER_BIGNULL) );
+ if( pTask->pSorter->pKeyInfo->aSortFlags[0] ){
res = res * -1;
}
}
@@ -88783,47 +97839,47 @@ static int vdbeSorterCompareInt(
assert( (s1>0 && s1<7) || s1==8 || s1==9 );
assert( (s2>0 && s2<7) || s2==8 || s2==9 );
- if( s1>7 && s2>7 ){
- res = s1 - s2;
- }else{
- if( s1==s2 ){
- if( (*v1 ^ *v2) & 0x80 ){
- /* The two values have different signs */
- res = (*v1 & 0x80) ? -1 : +1;
- }else{
- /* The two values have the same sign. Compare using memcmp(). */
- static const u8 aLen[] = {0, 1, 2, 3, 4, 6, 8 };
- int i;
- res = 0;
- for(i=0; i<aLen[s1]; i++){
- if( (res = v1[i] - v2[i]) ) break;
+ if( s1==s2 ){
+ /* The two values have the same sign. Compare using memcmp(). */
+ static const u8 aLen[] = {0, 1, 2, 3, 4, 6, 8, 0, 0, 0 };
+ const u8 n = aLen[s1];
+ int i;
+ res = 0;
+ for(i=0; i<n; i++){
+ if( (res = v1[i] - v2[i])!=0 ){
+ if( ((v1[0] ^ v2[0]) & 0x80)!=0 ){
+ res = v1[0] & 0x80 ? -1 : +1;
}
+ break;
}
+ }
+ }else if( s1>7 && s2>7 ){
+ res = s1 - s2;
+ }else{
+ if( s2>7 ){
+ res = +1;
+ }else if( s1>7 ){
+ res = -1;
}else{
- if( s2>7 ){
- res = +1;
- }else if( s1>7 ){
- res = -1;
- }else{
- res = s1 - s2;
- }
- assert( res!=0 );
+ res = s1 - s2;
+ }
+ assert( res!=0 );
- if( res>0 ){
- if( *v1 & 0x80 ) res = -1;
- }else{
- if( *v2 & 0x80 ) res = +1;
- }
+ if( res>0 ){
+ if( *v1 & 0x80 ) res = -1;
+ }else{
+ if( *v2 & 0x80 ) res = +1;
}
}
if( res==0 ){
- if( pTask->pSorter->pKeyInfo->nField>1 ){
+ if( pTask->pSorter->pKeyInfo->nKeyField>1 ){
res = vdbeSorterCompareTail(
pTask, pbKey2Cached, pKey1, nKey1, pKey2, nKey2
);
}
- }else if( pTask->pSorter->pKeyInfo->aSortOrder[0] ){
+ }else if( pTask->pSorter->pKeyInfo->aSortFlags[0] ){
+ assert( !(pTask->pSorter->pKeyInfo->aSortFlags[0]&KEYINFO_ORDER_BIGNULL) );
res = res * -1;
}
@@ -88833,7 +97889,7 @@ static int vdbeSorterCompareInt(
/*
** Initialize the temporary index cursor just opened as a sorter cursor.
**
-** Usually, the sorter module uses the value of (pCsr->pKeyInfo->nField)
+** Usually, the sorter module uses the value of (pCsr->pKeyInfo->nKeyField)
** to determine the number of fields that should be compared from the
** records being sorted. However, if the value passed as argument nField
** is non-zero and the sorter is able to guarantee a stable sort, nField
@@ -88849,8 +97905,8 @@ static int vdbeSorterCompareInt(
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
*/
-SQLITE_PRIVATE int sqlite3VdbeSorterInit(
- sqlite3 *db, /* Database connection (for malloc()) */
+SQLITE_PRIVATE int tdsqlite3VdbeSorterInit(
+ tdsqlite3 *db, /* Database connection (for malloc()) */
int nField, /* Number of key fields in each record */
VdbeCursor *pCsr /* Cursor that holds the new sorter */
){
@@ -88869,7 +97925,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
/* Initialize the upper limit on the number of worker threads */
#if SQLITE_MAX_WORKER_THREADS>0
- if( sqlite3TempInMemory(db) || sqlite3GlobalConfig.bCoreMutex==0 ){
+ if( tdsqlite3TempInMemory(db) || tdsqlite3GlobalConfig.bCoreMutex==0 ){
nWorker = 0;
}else{
nWorker = db->aLimit[SQLITE_LIMIT_WORKER_THREADS];
@@ -88884,12 +97940,12 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
}
#endif
- assert( pCsr->pKeyInfo && pCsr->pBt==0 );
+ assert( pCsr->pKeyInfo && pCsr->pBtx==0 );
assert( pCsr->eCurType==CURTYPE_SORTER );
- szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nField-1)*sizeof(CollSeq*);
+ szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nKeyField-1)*sizeof(CollSeq*);
sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask);
- pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo);
+ pSorter = (VdbeSorter*)tdsqlite3DbMallocZero(db, sz + szKeyInfo);
pCsr->uc.pSorter = pSorter;
if( pSorter==0 ){
rc = SQLITE_NOMEM_BKPT;
@@ -88898,10 +97954,9 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo);
pKeyInfo->db = 0;
if( nField && nWorker==0 ){
- pKeyInfo->nXField += (pKeyInfo->nField - nField);
- pKeyInfo->nField = nField;
+ pKeyInfo->nKeyField = nField;
}
- pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(db->aDb[0].pBt);
+ pSorter->pgsz = pgsz = tdsqlite3BtreeGetPageSize(db->aDb[0].pBt);
pSorter->nTask = nWorker + 1;
pSorter->iPrev = (u8)(nWorker - 1);
pSorter->bUseThreads = (pSorter->nTask>1);
@@ -88911,9 +97966,9 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
pTask->pSorter = pSorter;
}
- if( !sqlite3TempInMemory(db) ){
+ if( !tdsqlite3TempInMemory(db) ){
i64 mxCache; /* Cache size in bytes*/
- u32 szPma = sqlite3GlobalConfig.szPma;
+ u32 szPma = tdsqlite3GlobalConfig.szPma;
pSorter->mnPmaSize = szPma * pgsz;
mxCache = db->aDb[0].pSchema->cache_size;
@@ -88927,20 +97982,19 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
mxCache = MIN(mxCache, SQLITE_MAX_PMASZ);
pSorter->mxPmaSize = MAX(pSorter->mnPmaSize, (int)mxCache);
- /* EVIDENCE-OF: R-26747-61719 When the application provides any amount of
- ** scratch memory using SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary
- ** large heap allocations.
- */
- if( sqlite3GlobalConfig.pScratch==0 ){
+ /* Avoid large memory allocations if the application has requested
+ ** SQLITE_CONFIG_SMALL_MALLOC. */
+ if( tdsqlite3GlobalConfig.bSmallMalloc==0 ){
assert( pSorter->iMemory==0 );
pSorter->nMemory = pgsz;
- pSorter->list.aMemory = (u8*)sqlite3Malloc(pgsz);
+ pSorter->list.aMemory = (u8*)tdsqlite3Malloc(pgsz);
if( !pSorter->list.aMemory ) rc = SQLITE_NOMEM_BKPT;
}
}
- if( (pKeyInfo->nField+pKeyInfo->nXField)<13
+ if( pKeyInfo->nAllField<13
&& (pKeyInfo->aColl[0]==0 || pKeyInfo->aColl[0]==db->pDfltColl)
+ && (pKeyInfo->aSortFlags[0] & KEYINFO_ORDER_BIGNULL)==0
){
pSorter->typeMask = SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT;
}
@@ -88953,12 +98007,12 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
/*
** Free the list of sorted records starting at pRecord.
*/
-static void vdbeSorterRecordFree(sqlite3 *db, SorterRecord *pRecord){
+static void vdbeSorterRecordFree(tdsqlite3 *db, SorterRecord *pRecord){
SorterRecord *p;
SorterRecord *pNext;
for(p=pRecord; p; p=pNext){
pNext = p->u.pNext;
- sqlite3DbFree(db, p);
+ tdsqlite3DbFree(db, p);
}
}
@@ -88966,13 +98020,13 @@ static void vdbeSorterRecordFree(sqlite3 *db, SorterRecord *pRecord){
** Free all resources owned by the object indicated by argument pTask. All
** fields of *pTask are zeroed before returning.
*/
-static void vdbeSortSubtaskCleanup(sqlite3 *db, SortSubtask *pTask){
- sqlite3DbFree(db, pTask->pUnpacked);
+static void vdbeSortSubtaskCleanup(tdsqlite3 *db, SortSubtask *pTask){
+ tdsqlite3DbFree(db, pTask->pUnpacked);
#if SQLITE_MAX_WORKER_THREADS>0
/* pTask->list.aMemory can only be non-zero if it was handed memory
** from the main thread. That only occurs SQLITE_MAX_WORKER_THREADS>0 */
if( pTask->list.aMemory ){
- sqlite3_free(pTask->list.aMemory);
+ tdsqlite3_free(pTask->list.aMemory);
}else
#endif
{
@@ -88980,10 +98034,10 @@ static void vdbeSortSubtaskCleanup(sqlite3 *db, SortSubtask *pTask){
vdbeSorterRecordFree(0, pTask->list.pList);
}
if( pTask->file.pFd ){
- sqlite3OsCloseFree(pTask->file.pFd);
+ tdsqlite3OsCloseFree(pTask->file.pFd);
}
if( pTask->file2.pFd ){
- sqlite3OsCloseFree(pTask->file2.pFd);
+ tdsqlite3OsCloseFree(pTask->file2.pFd);
}
memset(pTask, 0, sizeof(SortSubtask));
}
@@ -88992,12 +98046,12 @@ static void vdbeSortSubtaskCleanup(sqlite3 *db, SortSubtask *pTask){
static void vdbeSorterWorkDebug(SortSubtask *pTask, const char *zEvent){
i64 t;
int iTask = (pTask - pTask->pSorter->aTask);
- sqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t);
+ tdsqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t);
fprintf(stderr, "%lld:%d %s\n", t, iTask, zEvent);
}
static void vdbeSorterRewindDebug(const char *zEvent){
i64 t;
- sqlite3OsCurrentTimeInt64(sqlite3_vfs_find(0), &t);
+ tdsqlite3OsCurrentTimeInt64(tdsqlite3_vfs_find(0), &t);
fprintf(stderr, "%lld:X %s\n", t, zEvent);
}
static void vdbeSorterPopulateDebug(
@@ -89006,7 +98060,7 @@ static void vdbeSorterPopulateDebug(
){
i64 t;
int iTask = (pTask - pTask->pSorter->aTask);
- sqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t);
+ tdsqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t);
fprintf(stderr, "%lld:bg%d %s\n", t, iTask, zEvent);
}
static void vdbeSorterBlockDebug(
@@ -89016,7 +98070,7 @@ static void vdbeSorterBlockDebug(
){
if( bBlocked ){
i64 t;
- sqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t);
+ tdsqlite3OsCurrentTimeInt64(pTask->pSorter->db->pVfs, &t);
fprintf(stderr, "%lld:main %s\n", t, zEvent);
}
}
@@ -89039,7 +98093,7 @@ static int vdbeSorterJoinThread(SortSubtask *pTask){
#endif
void *pRet = SQLITE_INT_TO_PTR(SQLITE_ERROR);
vdbeSorterBlockDebug(pTask, !bDone, "enter");
- (void)sqlite3ThreadJoin(pTask->pThread, &pRet);
+ (void)tdsqlite3ThreadJoin(pTask->pThread, &pRet);
vdbeSorterBlockDebug(pTask, !bDone, "exit");
rc = SQLITE_PTR_TO_INT(pRet);
assert( pTask->bDone==1 );
@@ -89058,7 +98112,7 @@ static int vdbeSorterCreateThread(
void *pIn /* Argument passed into xTask() */
){
assert( pTask->pThread==0 && pTask->bDone==0 );
- return sqlite3ThreadCreate(&pTask->pThread, xTask, pIn);
+ return tdsqlite3ThreadCreate(&pTask->pThread, xTask, pIn);
}
/*
@@ -89105,7 +98159,7 @@ static MergeEngine *vdbeMergeEngineNew(int nReader){
while( N<nReader ) N += N;
nByte = sizeof(MergeEngine) + N * (sizeof(int) + sizeof(PmaReader));
- pNew = sqlite3FaultSim(100) ? 0 : (MergeEngine*)sqlite3MallocZero(nByte);
+ pNew = tdsqlite3FaultSim(100) ? 0 : (MergeEngine*)tdsqlite3MallocZero(nByte);
if( pNew ){
pNew->nTree = N;
pNew->pTask = 0;
@@ -89125,7 +98179,7 @@ static void vdbeMergeEngineFree(MergeEngine *pMerger){
vdbePmaReaderClear(&pMerger->aReadr[i]);
}
}
- sqlite3_free(pMerger);
+ tdsqlite3_free(pMerger);
}
/*
@@ -89137,26 +98191,26 @@ static void vdbeIncrFree(IncrMerger *pIncr){
#if SQLITE_MAX_WORKER_THREADS>0
if( pIncr->bUseThread ){
vdbeSorterJoinThread(pIncr->pTask);
- if( pIncr->aFile[0].pFd ) sqlite3OsCloseFree(pIncr->aFile[0].pFd);
- if( pIncr->aFile[1].pFd ) sqlite3OsCloseFree(pIncr->aFile[1].pFd);
+ if( pIncr->aFile[0].pFd ) tdsqlite3OsCloseFree(pIncr->aFile[0].pFd);
+ if( pIncr->aFile[1].pFd ) tdsqlite3OsCloseFree(pIncr->aFile[1].pFd);
}
#endif
vdbeMergeEngineFree(pIncr->pMerger);
- sqlite3_free(pIncr);
+ tdsqlite3_free(pIncr);
}
}
/*
** Reset a sorting cursor back to its original empty state.
*/
-SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *db, VdbeSorter *pSorter){
+SQLITE_PRIVATE void tdsqlite3VdbeSorterReset(tdsqlite3 *db, VdbeSorter *pSorter){
int i;
(void)vdbeSorterJoinAll(pSorter, SQLITE_OK);
assert( pSorter->bUseThreads || pSorter->pReader==0 );
#if SQLITE_MAX_WORKER_THREADS>0
if( pSorter->pReader ){
vdbePmaReaderClear(pSorter->pReader);
- sqlite3DbFree(db, pSorter->pReader);
+ tdsqlite3DbFree(db, pSorter->pReader);
pSorter->pReader = 0;
}
#endif
@@ -89175,21 +98229,21 @@ SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *db, VdbeSorter *pSorter){
pSorter->bUsePMA = 0;
pSorter->iMemory = 0;
pSorter->mxKeysize = 0;
- sqlite3DbFree(db, pSorter->pUnpacked);
+ tdsqlite3DbFree(db, pSorter->pUnpacked);
pSorter->pUnpacked = 0;
}
/*
-** Free any cursor components allocated by sqlite3VdbeSorterXXX routines.
+** Free any cursor components allocated by tdsqlite3VdbeSorterXXX routines.
*/
-SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){
+SQLITE_PRIVATE void tdsqlite3VdbeSorterClose(tdsqlite3 *db, VdbeCursor *pCsr){
VdbeSorter *pSorter;
assert( pCsr->eCurType==CURTYPE_SORTER );
pSorter = pCsr->uc.pSorter;
if( pSorter ){
- sqlite3VdbeSorterReset(db, pSorter);
- sqlite3_free(pSorter->list.aMemory);
- sqlite3DbFree(db, pSorter);
+ tdsqlite3VdbeSorterReset(db, pSorter);
+ tdsqlite3_free(pSorter->list.aMemory);
+ tdsqlite3DbFree(db, pSorter);
pCsr->uc.pSorter = 0;
}
}
@@ -89204,14 +98258,14 @@ SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){
** Whether or not the file does end up memory mapped of course depends on
** the specific VFS implementation.
*/
-static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){
+static void vdbeSorterExtendFile(tdsqlite3 *db, tdsqlite3_file *pFd, i64 nByte){
if( nByte<=(i64)(db->nMaxSorterMmap) && pFd->pMethods->iVersion>=3 ){
void *p = 0;
int chunksize = 4*1024;
- sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize);
- sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte);
- sqlite3OsFetch(pFd, 0, (int)nByte, &p);
- sqlite3OsUnfetch(pFd, 0, p);
+ tdsqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize);
+ tdsqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte);
+ tdsqlite3OsFetch(pFd, 0, (int)nByte, &p);
+ tdsqlite3OsUnfetch(pFd, 0, p);
}
}
#else
@@ -89224,20 +98278,20 @@ static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){
** Otherwise, set *ppFd to 0 and return an SQLite error code.
*/
static int vdbeSorterOpenTempFile(
- sqlite3 *db, /* Database handle doing sort */
+ tdsqlite3 *db, /* Database handle doing sort */
i64 nExtend, /* Attempt to extend file to this size */
- sqlite3_file **ppFd
+ tdsqlite3_file **ppFd
){
int rc;
- if( sqlite3FaultSim(202) ) return SQLITE_IOERR_ACCESS;
- rc = sqlite3OsOpenMalloc(db->pVfs, 0, ppFd,
+ if( tdsqlite3FaultSim(202) ) return SQLITE_IOERR_ACCESS;
+ rc = tdsqlite3OsOpenMalloc(db->pVfs, 0, ppFd,
SQLITE_OPEN_TEMP_JOURNAL |
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE, &rc
);
if( rc==SQLITE_OK ){
i64 max = SQLITE_MAX_MMAP_SIZE;
- sqlite3OsFileControlHint(*ppFd, SQLITE_FCNTL_MMAP_SIZE, (void*)&max);
+ tdsqlite3OsFileControlHint(*ppFd, SQLITE_FCNTL_MMAP_SIZE, (void*)&max);
if( nExtend>0 ){
vdbeSorterExtendFile(db, *ppFd, nExtend);
}
@@ -89252,13 +98306,9 @@ static int vdbeSorterOpenTempFile(
*/
static int vdbeSortAllocUnpacked(SortSubtask *pTask){
if( pTask->pUnpacked==0 ){
- char *pFree;
- pTask->pUnpacked = sqlite3VdbeAllocUnpackedRecord(
- pTask->pSorter->pKeyInfo, 0, 0, &pFree
- );
- assert( pTask->pUnpacked==(UnpackedRecord*)pFree );
- if( pFree==0 ) return SQLITE_NOMEM_BKPT;
- pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nField;
+ pTask->pUnpacked = tdsqlite3VdbeAllocUnpackedRecord(pTask->pSorter->pKeyInfo);
+ if( pTask->pUnpacked==0 ) return SQLITE_NOMEM_BKPT;
+ pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nKeyField;
pTask->pUnpacked->errCode = 0;
}
return SQLITE_OK;
@@ -89326,20 +98376,16 @@ static SorterCompare vdbeSorterGetCompare(VdbeSorter *p){
*/
static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){
int i;
- SorterRecord **aSlot;
SorterRecord *p;
int rc;
+ SorterRecord *aSlot[64];
rc = vdbeSortAllocUnpacked(pTask);
if( rc!=SQLITE_OK ) return rc;
p = pList->pList;
pTask->xCompare = vdbeSorterGetCompare(pTask->pSorter);
-
- aSlot = (SorterRecord **)sqlite3MallocZero(64 * sizeof(SorterRecord *));
- if( !aSlot ){
- return SQLITE_NOMEM_BKPT;
- }
+ memset(aSlot, 0, sizeof(aSlot));
while( p ){
SorterRecord *pNext;
@@ -89347,7 +98393,7 @@ static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){
if( (u8*)p==pList->aMemory ){
pNext = 0;
}else{
- assert( p->u.iNext<sqlite3MallocSize(pList->aMemory) );
+ assert( p->u.iNext<tdsqlite3MallocSize(pList->aMemory) );
pNext = (SorterRecord*)&pList->aMemory[p->u.iNext];
}
}else{
@@ -89364,13 +98410,12 @@ static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){
}
p = 0;
- for(i=0; i<64; i++){
+ for(i=0; i<ArraySize(aSlot); i++){
if( aSlot[i]==0 ) continue;
p = p ? vdbeSorterMerge(pTask, p, aSlot[i]) : aSlot[i];
}
pList->pList = p;
- sqlite3_free(aSlot);
assert( pTask->pUnpacked->errCode==SQLITE_OK
|| pTask->pUnpacked->errCode==SQLITE_NOMEM
);
@@ -89381,13 +98426,13 @@ static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){
** Initialize a PMA-writer object.
*/
static void vdbePmaWriterInit(
- sqlite3_file *pFd, /* File handle to write to */
+ tdsqlite3_file *pFd, /* File handle to write to */
PmaWriter *p, /* Object to populate */
int nBuf, /* Buffer size */
i64 iStart /* Offset of pFd to begin writing at */
){
memset(p, 0, sizeof(PmaWriter));
- p->aBuffer = (u8*)sqlite3Malloc(nBuf);
+ p->aBuffer = (u8*)tdsqlite3Malloc(nBuf);
if( !p->aBuffer ){
p->eFWErr = SQLITE_NOMEM_BKPT;
}else{
@@ -89413,7 +98458,7 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){
memcpy(&p->aBuffer[p->iBufEnd], &pData[nData-nRem], nCopy);
p->iBufEnd += nCopy;
if( p->iBufEnd==p->nBuffer ){
- p->eFWErr = sqlite3OsWrite(p->pFd,
+ p->eFWErr = tdsqlite3OsWrite(p->pFd,
&p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart,
p->iWriteOff + p->iBufStart
);
@@ -89438,13 +98483,13 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){
static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){
int rc;
if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){
- p->eFWErr = sqlite3OsWrite(p->pFd,
+ p->eFWErr = tdsqlite3OsWrite(p->pFd,
&p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart,
p->iWriteOff + p->iBufStart
);
}
*piEof = (p->iWriteOff + p->iBufEnd);
- sqlite3_free(p->aBuffer);
+ tdsqlite3_free(p->aBuffer);
rc = p->eFWErr;
memset(p, 0, sizeof(PmaWriter));
return rc;
@@ -89457,7 +98502,7 @@ static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){
static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){
int nByte;
u8 aByte[10];
- nByte = sqlite3PutVarint(aByte, iVal);
+ nByte = tdsqlite3PutVarint(aByte, iVal);
vdbePmaWriteBlob(p, aByte, nByte);
}
@@ -89476,14 +98521,14 @@ static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){
** key). The varint is the number of bytes in the blob of data.
*/
static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){
- sqlite3 *db = pTask->pSorter->db;
+ tdsqlite3 *db = pTask->pSorter->db;
int rc = SQLITE_OK; /* Return code */
PmaWriter writer; /* Object used to write to the file */
#ifdef SQLITE_DEBUG
/* Set iSz to the expected size of file pTask->file after writing the PMA.
** This is used by an assert() statement at the end of this function. */
- i64 iSz = pList->szPMA + sqlite3VarintLen(pList->szPMA) + pTask->file.iEof;
+ i64 iSz = pList->szPMA + tdsqlite3VarintLen(pList->szPMA) + pTask->file.iEof;
#endif
vdbeSorterWorkDebug(pTask, "enter");
@@ -89520,7 +98565,7 @@ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){
pNext = p->u.pNext;
vdbePmaWriteVarint(&writer, p->nVal);
vdbePmaWriteBlob(&writer, SRVAL(p), p->nVal);
- if( pList->aMemory==0 ) sqlite3_free(p);
+ if( pList->aMemory==0 ) tdsqlite3_free(p);
}
pList->pList = p;
rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof);
@@ -89661,22 +98706,25 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){
rc = vdbeSorterListToPMA(&pSorter->aTask[nWorker], &pSorter->list);
}else{
/* Launch a background thread for this operation */
- u8 *aMem = pTask->list.aMemory;
- void *pCtx = (void*)pTask;
+ u8 *aMem;
+ void *pCtx;
+ assert( pTask!=0 );
assert( pTask->pThread==0 && pTask->bDone==0 );
assert( pTask->list.pList==0 );
assert( pTask->list.aMemory==0 || pSorter->list.aMemory!=0 );
+ aMem = pTask->list.aMemory;
+ pCtx = (void*)pTask;
pSorter->iPrev = (u8)(pTask - pSorter->aTask);
pTask->list = pSorter->list;
pSorter->list.pList = 0;
pSorter->list.szPMA = 0;
if( aMem ){
pSorter->list.aMemory = aMem;
- pSorter->nMemory = sqlite3MallocSize(aMem);
+ pSorter->nMemory = tdsqlite3MallocSize(aMem);
}else if( pSorter->list.aMemory ){
- pSorter->list.aMemory = sqlite3Malloc(pSorter->nMemory);
+ pSorter->list.aMemory = tdsqlite3Malloc(pSorter->nMemory);
if( !pSorter->list.aMemory ) return SQLITE_NOMEM_BKPT;
}
@@ -89691,7 +98739,7 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){
/*
** Add a record to the sorter.
*/
-SQLITE_PRIVATE int sqlite3VdbeSorterWrite(
+SQLITE_PRIVATE int tdsqlite3VdbeSorterWrite(
const VdbeCursor *pCsr, /* Sorter cursor */
Mem *pVal /* Memory cell containing record */
){
@@ -89730,17 +98778,17 @@ SQLITE_PRIVATE int sqlite3VdbeSorterWrite(
** than (page-size * cache-size), or
**
** * The total memory allocated for the in-memory list is greater
- ** than (page-size * 10) and sqlite3HeapNearlyFull() returns true.
+ ** than (page-size * 10) and tdsqlite3HeapNearlyFull() returns true.
*/
nReq = pVal->n + sizeof(SorterRecord);
- nPMA = pVal->n + sqlite3VarintLen(pVal->n);
+ nPMA = pVal->n + tdsqlite3VarintLen(pVal->n);
if( pSorter->mxPmaSize ){
if( pSorter->list.aMemory ){
bFlush = pSorter->iMemory && (pSorter->iMemory+nReq) > pSorter->mxPmaSize;
}else{
bFlush = (
(pSorter->list.szPMA > pSorter->mxPmaSize)
- || (pSorter->list.szPMA > pSorter->mnPmaSize && sqlite3HeapNearlyFull())
+ || (pSorter->list.szPMA > pSorter->mnPmaSize && tdsqlite3HeapNearlyFull())
);
}
if( bFlush ){
@@ -89761,15 +98809,19 @@ SQLITE_PRIVATE int sqlite3VdbeSorterWrite(
if( nMin>pSorter->nMemory ){
u8 *aNew;
- int iListOff = (u8*)pSorter->list.pList - pSorter->list.aMemory;
- int nNew = pSorter->nMemory * 2;
+ tdsqlite3_int64 nNew = 2 * (tdsqlite3_int64)pSorter->nMemory;
+ int iListOff = -1;
+ if( pSorter->list.pList ){
+ iListOff = (u8*)pSorter->list.pList - pSorter->list.aMemory;
+ }
while( nNew < nMin ) nNew = nNew*2;
if( nNew > pSorter->mxPmaSize ) nNew = pSorter->mxPmaSize;
if( nNew < nMin ) nNew = nMin;
-
- aNew = sqlite3Realloc(pSorter->list.aMemory, nNew);
+ aNew = tdsqlite3Realloc(pSorter->list.aMemory, nNew);
if( !aNew ) return SQLITE_NOMEM_BKPT;
- pSorter->list.pList = (SorterRecord*)&aNew[iListOff];
+ if( iListOff>=0 ){
+ pSorter->list.pList = (SorterRecord*)&aNew[iListOff];
+ }
pSorter->list.aMemory = aNew;
pSorter->nMemory = nNew;
}
@@ -89780,7 +98832,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterWrite(
pNew->u.iNext = (int)((u8*)(pSorter->list.pList) - pSorter->list.aMemory);
}
}else{
- pNew = (SorterRecord *)sqlite3Malloc(nReq);
+ pNew = (SorterRecord *)tdsqlite3Malloc(nReq);
if( pNew==0 ){
return SQLITE_NOMEM_BKPT;
}
@@ -89821,7 +98873,7 @@ static int vdbeIncrPopulate(IncrMerger *pIncr){
/* Check if the output file is full or if the input has been exhausted.
** In either case exit the loop. */
if( pReader->pFd==0 ) break;
- if( (iEof + nKey + sqlite3VarintLen(nKey))>(iStart + pIncr->mxSz) ) break;
+ if( (iEof + nKey + tdsqlite3VarintLen(nKey))>(iStart + pIncr->mxSz) ) break;
/* Write the next key to the output. */
vdbePmaWriteVarint(&writer, nKey);
@@ -89921,7 +98973,7 @@ static int vdbeIncrMergerNew(
){
int rc = SQLITE_OK;
IncrMerger *pIncr = *ppOut = (IncrMerger*)
- (sqlite3FaultSim(100) ? 0 : sqlite3MallocZero(sizeof(*pIncr)));
+ (tdsqlite3FaultSim(100) ? 0 : tdsqlite3MallocZero(sizeof(*pIncr)));
if( pIncr ){
pIncr->pMerger = pMerger;
pIncr->pTask = pTask;
@@ -90040,7 +99092,11 @@ static int vdbeMergeEngineInit(
){
int rc = SQLITE_OK; /* Return code */
int i; /* For looping over PmaReader objects */
- int nTree = pMerger->nTree;
+ int nTree; /* Number of subtrees to merge */
+
+ /* Failure to allocate the merge would have been detected prior to
+ ** invoking this routine */
+ assert( pMerger!=0 );
/* eMode is always INCRINIT_NORMAL in single-threaded mode */
assert( SQLITE_MAX_WORKER_THREADS>0 || eMode==INCRINIT_NORMAL );
@@ -90049,6 +99105,7 @@ static int vdbeMergeEngineInit(
assert( pMerger->pTask==0 );
pMerger->pTask = pTask;
+ nTree = pMerger->nTree;
for(i=0; i<nTree; i++){
if( SQLITE_MAX_WORKER_THREADS>0 && eMode==INCRINIT_ROOT ){
/* PmaReaders should be normally initialized in order, as if they are
@@ -90108,7 +99165,7 @@ static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){
int rc = SQLITE_OK;
IncrMerger *pIncr = pReadr->pIncr;
SortSubtask *pTask = pIncr->pTask;
- sqlite3 *db = pTask->pSorter->db;
+ tdsqlite3 *db = pTask->pSorter->db;
/* eMode is always INCRINIT_NORMAL in single-threaded mode */
assert( SQLITE_MAX_WORKER_THREADS>0 || eMode==INCRINIT_NORMAL );
@@ -90406,7 +99463,7 @@ static int vdbeSorterMergeTreeBuild(
}
/*
-** This function is called as part of an sqlite3VdbeSorterRewind() operation
+** This function is called as part of an tdsqlite3VdbeSorterRewind() operation
** on a sorter that has written two or more PMAs to temporary files. It sets
** up either VdbeSorter.pMerger (for single threaded sorters) or pReader
** (for multi-threaded sorters) so that it can be used to iterate through
@@ -90419,7 +99476,7 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){
SortSubtask *pTask0 = &pSorter->aTask[0];
MergeEngine *pMain = 0;
#if SQLITE_MAX_WORKER_THREADS
- sqlite3 *db = pTask0->pSorter->db;
+ tdsqlite3 *db = pTask0->pSorter->db;
int i;
SorterCompare xCompare = vdbeSorterGetCompare(pSorter);
for(i=0; i<pSorter->nTask; i++){
@@ -90437,7 +99494,7 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){
SortSubtask *pLast = &pSorter->aTask[pSorter->nTask-1];
rc = vdbeSortAllocUnpacked(pLast);
if( rc==SQLITE_OK ){
- pReadr = (PmaReader*)sqlite3DbMallocZero(db, sizeof(PmaReader));
+ pReadr = (PmaReader*)tdsqlite3DbMallocZero(db, sizeof(PmaReader));
pSorter->pReader = pReadr;
if( pReadr==0 ) rc = SQLITE_NOMEM_BKPT;
}
@@ -90492,11 +99549,11 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){
/*
-** Once the sorter has been populated by calls to sqlite3VdbeSorterWrite,
+** Once the sorter has been populated by calls to tdsqlite3VdbeSorterWrite,
** this function is called to prepare for iterating through the records
** in sorted order.
*/
-SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){
+SQLITE_PRIVATE int tdsqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){
VdbeSorter *pSorter;
int rc = SQLITE_OK; /* Return code */
@@ -90542,9 +99599,13 @@ SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){
}
/*
-** Advance to the next element in the sorter.
+** Advance to the next element in the sorter. Return value:
+**
+** SQLITE_OK success
+** SQLITE_DONE end of data
+** otherwise some kind of error.
*/
-SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, int *pbEof){
+SQLITE_PRIVATE int tdsqlite3VdbeSorterNext(tdsqlite3 *db, const VdbeCursor *pCsr){
VdbeSorter *pSorter;
int rc; /* Return code */
@@ -90558,21 +99619,22 @@ SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, in
#if SQLITE_MAX_WORKER_THREADS>0
if( pSorter->bUseThreads ){
rc = vdbePmaReaderNext(pSorter->pReader);
- *pbEof = (pSorter->pReader->pFd==0);
+ if( rc==SQLITE_OK && pSorter->pReader->pFd==0 ) rc = SQLITE_DONE;
}else
#endif
/*if( !pSorter->bUseThreads )*/ {
+ int res = 0;
assert( pSorter->pMerger!=0 );
assert( pSorter->pMerger->pTask==(&pSorter->aTask[0]) );
- rc = vdbeMergeEngineStep(pSorter->pMerger, pbEof);
+ rc = vdbeMergeEngineStep(pSorter->pMerger, &res);
+ if( rc==SQLITE_OK && res ) rc = SQLITE_DONE;
}
}else{
SorterRecord *pFree = pSorter->list.pList;
pSorter->list.pList = pFree->u.pNext;
pFree->u.pNext = 0;
if( pSorter->list.aMemory==0 ) vdbeSorterRecordFree(db, pFree);
- *pbEof = !pSorter->list.pList;
- rc = SQLITE_OK;
+ rc = pSorter->list.pList ? SQLITE_OK : SQLITE_DONE;
}
return rc;
}
@@ -90608,14 +99670,14 @@ static void *vdbeSorterRowkey(
/*
** Copy the current sorter key into the memory cell pOut.
*/
-SQLITE_PRIVATE int sqlite3VdbeSorterRowkey(const VdbeCursor *pCsr, Mem *pOut){
+SQLITE_PRIVATE int tdsqlite3VdbeSorterRowkey(const VdbeCursor *pCsr, Mem *pOut){
VdbeSorter *pSorter;
void *pKey; int nKey; /* Sorter key to copy into pOut */
assert( pCsr->eCurType==CURTYPE_SORTER );
pSorter = pCsr->uc.pSorter;
pKey = vdbeSorterRowkey(pSorter, &nKey);
- if( sqlite3VdbeMemClearAndResize(pOut, nKey) ){
+ if( tdsqlite3VdbeMemClearAndResize(pOut, nKey) ){
return SQLITE_NOMEM_BKPT;
}
pOut->n = nKey;
@@ -90641,7 +99703,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterRowkey(const VdbeCursor *pCsr, Mem *pOut){
** This routine forms the core of the OP_SorterCompare opcode, which in
** turn is used to verify uniqueness when constructing a UNIQUE INDEX.
*/
-SQLITE_PRIVATE int sqlite3VdbeSorterCompare(
+SQLITE_PRIVATE int tdsqlite3VdbeSorterCompare(
const VdbeCursor *pCsr, /* Sorter cursor */
Mem *pVal, /* Value to compare to current sorter key */
int nKeyCol, /* Compare this many columns */
@@ -90658,16 +99720,14 @@ SQLITE_PRIVATE int sqlite3VdbeSorterCompare(
r2 = pSorter->pUnpacked;
pKeyInfo = pCsr->pKeyInfo;
if( r2==0 ){
- char *p;
- r2 = pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo,0,0,&p);
- assert( pSorter->pUnpacked==(UnpackedRecord*)p );
+ r2 = pSorter->pUnpacked = tdsqlite3VdbeAllocUnpackedRecord(pKeyInfo);
if( r2==0 ) return SQLITE_NOMEM_BKPT;
r2->nField = nKeyCol;
}
assert( r2->nField==nKeyCol );
pKey = vdbeSorterRowkey(pSorter, &nKey);
- sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, r2);
+ tdsqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, r2);
for(i=0; i<nKeyCol; i++){
if( r2->aMem[i].flags & MEM_Null ){
*pRes = -1;
@@ -90675,7 +99735,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterCompare(
}
}
- *pRes = sqlite3VdbeRecordCompare(pVal->n, pVal->z, r2);
+ *pRes = tdsqlite3VdbeRecordCompare(pVal->n, pVal->z, r2);
return SQLITE_OK;
}
@@ -90740,16 +99800,16 @@ struct FileChunk {
** The cursor can be either for reading or writing.
*/
struct FilePoint {
- sqlite3_int64 iOffset; /* Offset from the beginning of the file */
+ tdsqlite3_int64 iOffset; /* Offset from the beginning of the file */
FileChunk *pChunk; /* Specific chunk into which cursor points */
};
/*
-** This structure is a subclass of sqlite3_file. Each open memory-journal
+** This structure is a subclass of tdsqlite3_file. Each open memory-journal
** is an instance of this class.
*/
struct MemJournal {
- const sqlite3_io_methods *pMethod; /* Parent class. MUST BE FIRST */
+ const tdsqlite3_io_methods *pMethod; /* Parent class. MUST BE FIRST */
int nChunkSize; /* In-memory chunk-size */
int nSpill; /* Bytes of data before flushing */
@@ -90759,16 +99819,16 @@ struct MemJournal {
FilePoint readpoint; /* Pointer to the end of the last xRead() */
int flags; /* xOpen flags */
- sqlite3_vfs *pVfs; /* The "real" underlying VFS */
+ tdsqlite3_vfs *pVfs; /* The "real" underlying VFS */
const char *zJournal; /* Name of the journal file */
};
/*
** Read data from the in-memory journal file. This is the implementation
-** of the sqlite3_vfs.xRead method.
+** of the tdsqlite3_vfs.xRead method.
*/
static int memjrnlRead(
- sqlite3_file *pJfd, /* The journal file from which to read */
+ tdsqlite3_file *pJfd, /* The journal file from which to read */
void *zBuf, /* Put the results here */
int iAmt, /* Number of bytes to read */
sqlite_int64 iOfst /* Begin reading at this offset */
@@ -90779,16 +99839,12 @@ static int memjrnlRead(
int iChunkOffset;
FileChunk *pChunk;
-#ifdef SQLITE_ENABLE_ATOMIC_WRITE
if( (iAmt+iOfst)>p->endpoint.iOffset ){
return SQLITE_IOERR_SHORT_READ;
}
-#endif
-
- assert( (iAmt+iOfst)<=p->endpoint.iOffset );
assert( p->readpoint.iOffset==0 || p->readpoint.pChunk!=0 );
if( p->readpoint.iOffset!=iOfst || iOfst==0 ){
- sqlite3_int64 iOff = 0;
+ tdsqlite3_int64 iOff = 0;
for(pChunk=p->pFirst;
ALWAYS(pChunk) && (iOff+p->nChunkSize)<=iOfst;
pChunk=pChunk->pNext
@@ -90823,7 +99879,7 @@ static void memjrnlFreeChunks(MemJournal *p){
FileChunk *pNext;
for(pIter=p->pFirst; pIter; pIter=pNext){
pNext = pIter->pNext;
- sqlite3_free(pIter);
+ tdsqlite3_free(pIter);
}
p->pFirst = 0;
}
@@ -90833,11 +99889,11 @@ static void memjrnlFreeChunks(MemJournal *p){
*/
static int memjrnlCreateFile(MemJournal *p){
int rc;
- sqlite3_file *pReal = (sqlite3_file*)p;
+ tdsqlite3_file *pReal = (tdsqlite3_file*)p;
MemJournal copy = *p;
memset(p, 0, sizeof(MemJournal));
- rc = sqlite3OsOpen(copy.pVfs, copy.zJournal, pReal, copy.flags, 0);
+ rc = tdsqlite3OsOpen(copy.pVfs, copy.zJournal, pReal, copy.flags, 0);
if( rc==SQLITE_OK ){
int nChunk = copy.nChunkSize;
i64 iOff = 0;
@@ -90846,7 +99902,7 @@ static int memjrnlCreateFile(MemJournal *p){
if( iOff + nChunk > copy.endpoint.iOffset ){
nChunk = copy.endpoint.iOffset - iOff;
}
- rc = sqlite3OsWrite(pReal, (u8*)pIter->zChunk, nChunk, iOff);
+ rc = tdsqlite3OsWrite(pReal, (u8*)pIter->zChunk, nChunk, iOff);
if( rc ) break;
iOff += nChunk;
}
@@ -90860,7 +99916,7 @@ static int memjrnlCreateFile(MemJournal *p){
** the original before returning. This way, SQLite uses the in-memory
** journal data to roll back changes made to the internal page-cache
** before this function was called. */
- sqlite3OsClose(pReal);
+ tdsqlite3OsClose(pReal);
*p = copy;
}
return rc;
@@ -90871,7 +99927,7 @@ static int memjrnlCreateFile(MemJournal *p){
** Write data to the file.
*/
static int memjrnlWrite(
- sqlite3_file *pJfd, /* The journal file into which to write */
+ tdsqlite3_file *pJfd, /* The journal file into which to write */
const void *zBuf, /* Take data to be written from here */
int iAmt, /* Number of bytes to write */
sqlite_int64 iOfst /* Begin writing at this offset into the file */
@@ -90885,7 +99941,7 @@ static int memjrnlWrite(
if( p->nSpill>0 && (iAmt+iOfst)>p->nSpill ){
int rc = memjrnlCreateFile(p);
if( rc==SQLITE_OK ){
- rc = sqlite3OsWrite(pJfd, zBuf, iAmt, iOfst);
+ rc = tdsqlite3OsWrite(pJfd, zBuf, iAmt, iOfst);
}
return rc;
}
@@ -90898,7 +99954,8 @@ static int memjrnlWrite(
** atomic-write optimization. In this case the first 28 bytes of the
** journal file may be written as part of committing the transaction. */
assert( iOfst==p->endpoint.iOffset || iOfst==0 );
-#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \
+ || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE)
if( iOfst==0 && p->pFirst ){
assert( p->nChunkSize>iAmt );
memcpy((u8*)p->pFirst->zChunk, zBuf, iAmt);
@@ -90914,7 +99971,7 @@ static int memjrnlWrite(
if( iChunkOffset==0 ){
/* New chunk is required to extend the file. */
- FileChunk *pNew = sqlite3_malloc(fileChunkSize(p->nChunkSize));
+ FileChunk *pNew = tdsqlite3_malloc(fileChunkSize(p->nChunkSize));
if( !pNew ){
return SQLITE_IOERR_NOMEM_BKPT;
}
@@ -90948,7 +100005,7 @@ static int memjrnlWrite(
** is still in main memory but is being truncated to zero bytes in size,
** ignore
*/
-static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){
+static int memjrnlTruncate(tdsqlite3_file *pJfd, sqlite_int64 size){
MemJournal *p = (MemJournal *)pJfd;
if( ALWAYS(size==0) ){
memjrnlFreeChunks(p);
@@ -90964,7 +100021,7 @@ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){
/*
** Close the file.
*/
-static int memjrnlClose(sqlite3_file *pJfd){
+static int memjrnlClose(tdsqlite3_file *pJfd){
MemJournal *p = (MemJournal *)pJfd;
memjrnlFreeChunks(p);
return SQLITE_OK;
@@ -90976,7 +100033,7 @@ static int memjrnlClose(sqlite3_file *pJfd){
** If the real file has been created, call its xSync method. Otherwise,
** syncing an in-memory journal is a no-op.
*/
-static int memjrnlSync(sqlite3_file *pJfd, int flags){
+static int memjrnlSync(tdsqlite3_file *pJfd, int flags){
UNUSED_PARAMETER2(pJfd, flags);
return SQLITE_OK;
}
@@ -90984,16 +100041,16 @@ static int memjrnlSync(sqlite3_file *pJfd, int flags){
/*
** Query the size of the file in bytes.
*/
-static int memjrnlFileSize(sqlite3_file *pJfd, sqlite_int64 *pSize){
+static int memjrnlFileSize(tdsqlite3_file *pJfd, sqlite_int64 *pSize){
MemJournal *p = (MemJournal *)pJfd;
*pSize = (sqlite_int64) p->endpoint.iOffset;
return SQLITE_OK;
}
/*
-** Table of methods for MemJournal sqlite3_file object.
+** Table of methods for MemJournal tdsqlite3_file object.
*/
-static const struct sqlite3_io_methods MemJournalMethods = {
+static const struct tdsqlite3_io_methods MemJournalMethods = {
1, /* iVersion */
memjrnlClose, /* xClose */
memjrnlRead, /* xRead */
@@ -91025,24 +100082,24 @@ static const struct sqlite3_io_methods MemJournalMethods = {
** positive value, then the journal file is initially created in-memory
** but may be flushed to disk later on. In this case the journal file is
** flushed to disk either when it grows larger than nSpill bytes in size,
-** or when sqlite3JournalCreate() is called.
+** or when tdsqlite3JournalCreate() is called.
*/
-SQLITE_PRIVATE int sqlite3JournalOpen(
- sqlite3_vfs *pVfs, /* The VFS to use for actual file I/O */
+SQLITE_PRIVATE int tdsqlite3JournalOpen(
+ tdsqlite3_vfs *pVfs, /* The VFS to use for actual file I/O */
const char *zName, /* Name of the journal file */
- sqlite3_file *pJfd, /* Preallocated, blank file handle */
+ tdsqlite3_file *pJfd, /* Preallocated, blank file handle */
int flags, /* Opening flags */
int nSpill /* Bytes buffered before opening the file */
){
MemJournal *p = (MemJournal*)pJfd;
/* Zero the file-handle object. If nSpill was passed zero, initialize
- ** it using the sqlite3OsOpen() function of the underlying VFS. In this
+ ** it using the tdsqlite3OsOpen() function of the underlying VFS. In this
** case none of the code in this module is executed as a result of calls
** made on the journal file-handle. */
memset(p, 0, sizeof(MemJournal));
if( nSpill==0 ){
- return sqlite3OsOpen(pVfs, zName, pJfd, flags, 0);
+ return tdsqlite3OsOpen(pVfs, zName, pJfd, flags, 0);
}
if( nSpill>0 ){
@@ -91052,7 +100109,7 @@ SQLITE_PRIVATE int sqlite3JournalOpen(
assert( MEMJOURNAL_DFLT_FILECHUNKSIZE==fileChunkSize(p->nChunkSize) );
}
- p->pMethod = (const sqlite3_io_methods*)&MemJournalMethods;
+ p->pMethod = (const tdsqlite3_io_methods*)&MemJournalMethods;
p->nSpill = nSpill;
p->flags = flags;
p->zJournal = zName;
@@ -91063,21 +100120,35 @@ SQLITE_PRIVATE int sqlite3JournalOpen(
/*
** Open an in-memory journal file.
*/
-SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *pJfd){
- sqlite3JournalOpen(0, 0, pJfd, 0, -1);
+SQLITE_PRIVATE void tdsqlite3MemJournalOpen(tdsqlite3_file *pJfd){
+ tdsqlite3JournalOpen(0, 0, pJfd, 0, -1);
}
-#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \
+ || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE)
/*
** If the argument p points to a MemJournal structure that is not an
** in-memory-only journal file (i.e. is one that was opened with a +ve
-** nSpill parameter), and the underlying file has not yet been created,
-** create it now.
+** nSpill parameter or as SQLITE_OPEN_MAIN_JOURNAL), and the underlying
+** file has not yet been created, create it now.
*/
-SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *p){
+SQLITE_PRIVATE int tdsqlite3JournalCreate(tdsqlite3_file *pJfd){
int rc = SQLITE_OK;
- if( p->pMethods==&MemJournalMethods && ((MemJournal*)p)->nSpill>0 ){
- rc = memjrnlCreateFile((MemJournal*)p);
+ MemJournal *p = (MemJournal*)pJfd;
+ if( p->pMethod==&MemJournalMethods && (
+#ifdef SQLITE_ENABLE_ATOMIC_WRITE
+ p->nSpill>0
+#else
+ /* While this appears to not be possible without ATOMIC_WRITE, the
+ ** paths are complex, so it seems prudent to leave the test in as
+ ** a NEVER(), in case our analysis is subtly flawed. */
+ NEVER(p->nSpill>0)
+#endif
+#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE
+ || (p->flags & SQLITE_OPEN_MAIN_JOURNAL)
+#endif
+ )){
+ rc = memjrnlCreateFile(p);
}
return rc;
}
@@ -91088,7 +100159,7 @@ SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *p){
** Return true if this "journal file" is currently stored in heap memory,
** or false otherwise.
*/
-SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p){
+SQLITE_PRIVATE int tdsqlite3JournalIsInMemory(tdsqlite3_file *p){
return p->pMethods==&MemJournalMethods;
}
@@ -91096,7 +100167,7 @@ SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p){
** Return the number of bytes required to store a JournalFile that uses vfs
** pVfs to create the underlying on-disk files.
*/
-SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){
+SQLITE_PRIVATE int tdsqlite3JournalSize(tdsqlite3_vfs *pVfs){
return MAX(pVfs->szOsFile, (int)sizeof(MemJournal));
}
@@ -91121,6 +100192,35 @@ SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){
/* #include <string.h> */
+#if !defined(SQLITE_OMIT_WINDOWFUNC)
+/*
+** Walk all expressions linked into the list of Window objects passed
+** as the second argument.
+*/
+static int walkWindowList(Walker *pWalker, Window *pList){
+ Window *pWin;
+ for(pWin=pList; pWin; pWin=pWin->pNextWin){
+ int rc;
+ rc = tdsqlite3WalkExprList(pWalker, pWin->pOrderBy);
+ if( rc ) return WRC_Abort;
+ rc = tdsqlite3WalkExprList(pWalker, pWin->pPartition);
+ if( rc ) return WRC_Abort;
+ rc = tdsqlite3WalkExpr(pWalker, pWin->pFilter);
+ if( rc ) return WRC_Abort;
+
+ /* The next two are purely for calls to tdsqlite3RenameExprUnmap()
+ ** within tdsqlite3WindowOffsetExpr(). Because of constraints imposed
+ ** by tdsqlite3WindowOffsetExpr(), they can never fail. The results do
+ ** not matter anyhow. */
+ rc = tdsqlite3WalkExpr(pWalker, pWin->pStart);
+ if( NEVER(rc) ) return WRC_Abort;
+ rc = tdsqlite3WalkExpr(pWalker, pWin->pEnd);
+ if( NEVER(rc) ) return WRC_Abort;
+ }
+ return WRC_Continue;
+}
+#endif
+
/*
** Walk an expression tree. Invoke the callback once for each node
** of the expression, while descending. (In other words, the callback
@@ -91131,11 +100231,11 @@ SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){
**
** WRC_Continue Continue descending down the tree.
**
-** WRC_Prune Do not descend into child nodes. But allow
+** WRC_Prune Do not descend into child nodes, but allow
** the walk to continue with sibling nodes.
**
** WRC_Abort Do no more callbacks. Unwind the stack and
-** return the top-level walk call.
+** return from the top-level walk call.
**
** The return value from this routine is WRC_Abort to abandon the tree walk
** and WRC_Continue to continue.
@@ -91144,33 +100244,48 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){
int rc;
testcase( ExprHasProperty(pExpr, EP_TokenOnly) );
testcase( ExprHasProperty(pExpr, EP_Reduced) );
- rc = pWalker->xExprCallback(pWalker, pExpr);
- if( rc || ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){
- return rc & WRC_Abort;
- }
- if( pExpr->pLeft && walkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort;
- if( pExpr->pRight && walkExpr(pWalker, pExpr->pRight) ) return WRC_Abort;
- if( ExprHasProperty(pExpr, EP_xIsSelect) ){
- if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort;
- }else if( pExpr->x.pList ){
- if( sqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort;
+ while(1){
+ rc = pWalker->xExprCallback(pWalker, pExpr);
+ if( rc ) return rc & WRC_Abort;
+ if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){
+ assert( pExpr->x.pList==0 || pExpr->pRight==0 );
+ if( pExpr->pLeft && walkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort;
+ if( pExpr->pRight ){
+ assert( !ExprHasProperty(pExpr, EP_WinFunc) );
+ pExpr = pExpr->pRight;
+ continue;
+ }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){
+ assert( !ExprHasProperty(pExpr, EP_WinFunc) );
+ if( tdsqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort;
+ }else{
+ if( pExpr->x.pList ){
+ if( tdsqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort;
+ }
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( ExprHasProperty(pExpr, EP_WinFunc) ){
+ if( walkWindowList(pWalker, pExpr->y.pWin) ) return WRC_Abort;
+ }
+#endif
+ }
+ }
+ break;
}
return WRC_Continue;
}
-SQLITE_PRIVATE int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){
+SQLITE_PRIVATE int tdsqlite3WalkExpr(Walker *pWalker, Expr *pExpr){
return pExpr ? walkExpr(pWalker,pExpr) : WRC_Continue;
}
/*
-** Call sqlite3WalkExpr() for every expression in list p or until
+** Call tdsqlite3WalkExpr() for every expression in list p or until
** an abort request is seen.
*/
-SQLITE_PRIVATE int sqlite3WalkExprList(Walker *pWalker, ExprList *p){
+SQLITE_PRIVATE int tdsqlite3WalkExprList(Walker *pWalker, ExprList *p){
int i;
struct ExprList_item *pItem;
if( p ){
for(i=p->nExpr, pItem=p->a; i>0; i--, pItem++){
- if( sqlite3WalkExpr(pWalker, pItem->pExpr) ) return WRC_Abort;
+ if( tdsqlite3WalkExpr(pWalker, pItem->pExpr) ) return WRC_Abort;
}
}
return WRC_Continue;
@@ -91182,14 +100297,24 @@ SQLITE_PRIVATE int sqlite3WalkExprList(Walker *pWalker, ExprList *p){
** any expr callbacks and SELECT callbacks that come from subqueries.
** Return WRC_Abort or WRC_Continue.
*/
-SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){
- if( sqlite3WalkExprList(pWalker, p->pEList) ) return WRC_Abort;
- if( sqlite3WalkExpr(pWalker, p->pWhere) ) return WRC_Abort;
- if( sqlite3WalkExprList(pWalker, p->pGroupBy) ) return WRC_Abort;
- if( sqlite3WalkExpr(pWalker, p->pHaving) ) return WRC_Abort;
- if( sqlite3WalkExprList(pWalker, p->pOrderBy) ) return WRC_Abort;
- if( sqlite3WalkExpr(pWalker, p->pLimit) ) return WRC_Abort;
- if( sqlite3WalkExpr(pWalker, p->pOffset) ) return WRC_Abort;
+SQLITE_PRIVATE int tdsqlite3WalkSelectExpr(Walker *pWalker, Select *p){
+ if( tdsqlite3WalkExprList(pWalker, p->pEList) ) return WRC_Abort;
+ if( tdsqlite3WalkExpr(pWalker, p->pWhere) ) return WRC_Abort;
+ if( tdsqlite3WalkExprList(pWalker, p->pGroupBy) ) return WRC_Abort;
+ if( tdsqlite3WalkExpr(pWalker, p->pHaving) ) return WRC_Abort;
+ if( tdsqlite3WalkExprList(pWalker, p->pOrderBy) ) return WRC_Abort;
+ if( tdsqlite3WalkExpr(pWalker, p->pLimit) ) return WRC_Abort;
+#if !defined(SQLITE_OMIT_WINDOWFUNC) && !defined(SQLITE_OMIT_ALTERTABLE)
+ {
+ Parse *pParse = pWalker->pParse;
+ if( pParse && IN_RENAME_OBJECT ){
+ /* The following may return WRC_Abort if there are unresolvable
+ ** symbols (e.g. a table that does not exist) in a window definition. */
+ int rc = walkWindowList(pWalker, p->pWinDefn);
+ return rc;
+ }
+ }
+#endif
return WRC_Continue;
}
@@ -91200,36 +100325,36 @@ SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){
** and on any subqueries further down in the tree. Return
** WRC_Abort or WRC_Continue;
*/
-SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){
+SQLITE_PRIVATE int tdsqlite3WalkSelectFrom(Walker *pWalker, Select *p){
SrcList *pSrc;
int i;
struct SrcList_item *pItem;
pSrc = p->pSrc;
- if( ALWAYS(pSrc) ){
- for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
- if( sqlite3WalkSelect(pWalker, pItem->pSelect) ){
- return WRC_Abort;
- }
- if( pItem->fg.isTabFunc
- && sqlite3WalkExprList(pWalker, pItem->u1.pFuncArg)
- ){
- return WRC_Abort;
- }
+ assert( pSrc!=0 );
+ for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
+ if( pItem->pSelect && tdsqlite3WalkSelect(pWalker, pItem->pSelect) ){
+ return WRC_Abort;
+ }
+ if( pItem->fg.isTabFunc
+ && tdsqlite3WalkExprList(pWalker, pItem->u1.pFuncArg)
+ ){
+ return WRC_Abort;
}
}
return WRC_Continue;
}
/*
-** Call sqlite3WalkExpr() for every expression in Select statement p.
-** Invoke sqlite3WalkSelect() for subqueries in the FROM clause and
+** Call tdsqlite3WalkExpr() for every expression in Select statement p.
+** Invoke tdsqlite3WalkSelect() for subqueries in the FROM clause and
** on the compound select chain, p->pPrior.
**
** If it is not NULL, the xSelectCallback() callback is invoked before
** the walk of the expressions and FROM clause. The xSelectCallback2()
-** method, if it is not NULL, is invoked following the walk of the
-** expressions and FROM clause.
+** method is invoked following the walk of the expressions and FROM clause,
+** but only if both xSelectCallback and xSelectCallback2 are both non-NULL
+** and if the expressions and FROM clause both return WRC_Continue;
**
** Return WRC_Continue under normal conditions. Return WRC_Abort if
** there is an abort request.
@@ -91237,31 +100362,24 @@ SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){
** If the Walker does not have an xSelectCallback() then this routine
** is a no-op returning WRC_Continue.
*/
-SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){
+SQLITE_PRIVATE int tdsqlite3WalkSelect(Walker *pWalker, Select *p){
int rc;
- if( p==0 || (pWalker->xSelectCallback==0 && pWalker->xSelectCallback2==0) ){
- return WRC_Continue;
- }
- rc = WRC_Continue;
- pWalker->walkerDepth++;
- while( p ){
- if( pWalker->xSelectCallback ){
- rc = pWalker->xSelectCallback(pWalker, p);
- if( rc ) break;
- }
- if( sqlite3WalkSelectExpr(pWalker, p)
- || sqlite3WalkSelectFrom(pWalker, p)
+ if( p==0 ) return WRC_Continue;
+ if( pWalker->xSelectCallback==0 ) return WRC_Continue;
+ do{
+ rc = pWalker->xSelectCallback(pWalker, p);
+ if( rc ) return rc & WRC_Abort;
+ if( tdsqlite3WalkSelectExpr(pWalker, p)
+ || tdsqlite3WalkSelectFrom(pWalker, p)
){
- pWalker->walkerDepth--;
return WRC_Abort;
}
if( pWalker->xSelectCallback2 ){
pWalker->xSelectCallback2(pWalker, p);
}
p = p->pPrior;
- }
- pWalker->walkerDepth--;
- return rc & WRC_Abort;
+ }while( p!=0 );
+ return WRC_Continue;
}
/************** End of walker.c **********************************************/
@@ -91283,8 +100401,6 @@ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){
** table and column.
*/
/* #include "sqliteInt.h" */
-/* #include <stdlib.h> */
-/* #include <string.h> */
/*
** Walk the expression tree pExpr and increase the aggregate function
@@ -91305,7 +100421,7 @@ static void incrAggFunctionDepth(Expr *pExpr, int N){
memset(&w, 0, sizeof(w));
w.xExprCallback = incrAggDepth;
w.u.n = N;
- sqlite3WalkExpr(&w, pExpr);
+ tdsqlite3WalkExpr(&w, pExpr);
}
}
@@ -91338,36 +100454,44 @@ static void resolveAlias(
){
Expr *pOrig; /* The iCol-th column of the result set */
Expr *pDup; /* Copy of pOrig */
- sqlite3 *db; /* The database connection */
+ tdsqlite3 *db; /* The database connection */
assert( iCol>=0 && iCol<pEList->nExpr );
pOrig = pEList->a[iCol].pExpr;
assert( pOrig!=0 );
db = pParse->db;
- pDup = sqlite3ExprDup(db, pOrig, 0);
- if( pDup==0 ) return;
- if( zType[0]!='G' ) incrAggFunctionDepth(pDup, nSubquery);
- if( pExpr->op==TK_COLLATE ){
- pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken);
- }
- ExprSetProperty(pDup, EP_Alias);
+ pDup = tdsqlite3ExprDup(db, pOrig, 0);
+ if( pDup!=0 ){
+ if( zType[0]!='G' ) incrAggFunctionDepth(pDup, nSubquery);
+ if( pExpr->op==TK_COLLATE ){
+ pDup = tdsqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken);
+ }
- /* Before calling sqlite3ExprDelete(), set the EP_Static flag. This
- ** prevents ExprDelete() from deleting the Expr structure itself,
- ** allowing it to be repopulated by the memcpy() on the following line.
- ** The pExpr->u.zToken might point into memory that will be freed by the
- ** sqlite3DbFree(db, pDup) on the last line of this block, so be sure to
- ** make a copy of the token before doing the sqlite3DbFree().
- */
- ExprSetProperty(pExpr, EP_Static);
- sqlite3ExprDelete(db, pExpr);
- memcpy(pExpr, pDup, sizeof(*pExpr));
- if( !ExprHasProperty(pExpr, EP_IntValue) && pExpr->u.zToken!=0 ){
- assert( (pExpr->flags & (EP_Reduced|EP_TokenOnly))==0 );
- pExpr->u.zToken = sqlite3DbStrDup(db, pExpr->u.zToken);
- pExpr->flags |= EP_MemToken;
+ /* Before calling tdsqlite3ExprDelete(), set the EP_Static flag. This
+ ** prevents ExprDelete() from deleting the Expr structure itself,
+ ** allowing it to be repopulated by the memcpy() on the following line.
+ ** The pExpr->u.zToken might point into memory that will be freed by the
+ ** tdsqlite3DbFree(db, pDup) on the last line of this block, so be sure to
+ ** make a copy of the token before doing the tdsqlite3DbFree().
+ */
+ ExprSetProperty(pExpr, EP_Static);
+ tdsqlite3ExprDelete(db, pExpr);
+ memcpy(pExpr, pDup, sizeof(*pExpr));
+ if( !ExprHasProperty(pExpr, EP_IntValue) && pExpr->u.zToken!=0 ){
+ assert( (pExpr->flags & (EP_Reduced|EP_TokenOnly))==0 );
+ pExpr->u.zToken = tdsqlite3DbStrDup(db, pExpr->u.zToken);
+ pExpr->flags |= EP_MemToken;
+ }
+ if( ExprHasProperty(pExpr, EP_WinFunc) ){
+ if( pExpr->y.pWin!=0 ){
+ pExpr->y.pWin->pOwner = pExpr;
+ }else{
+ assert( db->mallocFailed );
+ }
+ }
+ tdsqlite3DbFree(db, pDup);
}
- sqlite3DbFree(db, pDup);
+ ExprSetProperty(pExpr, EP_Alias);
}
@@ -91381,7 +100505,7 @@ static int nameInUsingClause(IdList *pUsing, const char *zCol){
if( pUsing ){
int k;
for(k=0; k<pUsing->nId; k++){
- if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ) return 1;
+ if( tdsqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ) return 1;
}
}
return 0;
@@ -91394,30 +100518,50 @@ static int nameInUsingClause(IdList *pUsing, const char *zCol){
** and zCol. If any of zDb, zTab, and zCol are NULL then those fields will
** match anything.
*/
-SQLITE_PRIVATE int sqlite3MatchSpanName(
- const char *zSpan,
+SQLITE_PRIVATE int tdsqlite3MatchEName(
+ const struct ExprList_item *pItem,
const char *zCol,
const char *zTab,
const char *zDb
){
int n;
+ const char *zSpan;
+ if( NEVER(pItem->eEName!=ENAME_TAB) ) return 0;
+ zSpan = pItem->zEName;
for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){}
- if( zDb && (sqlite3StrNICmp(zSpan, zDb, n)!=0 || zDb[n]!=0) ){
+ if( zDb && (tdsqlite3StrNICmp(zSpan, zDb, n)!=0 || zDb[n]!=0) ){
return 0;
}
zSpan += n+1;
for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){}
- if( zTab && (sqlite3StrNICmp(zSpan, zTab, n)!=0 || zTab[n]!=0) ){
+ if( zTab && (tdsqlite3StrNICmp(zSpan, zTab, n)!=0 || zTab[n]!=0) ){
return 0;
}
zSpan += n+1;
- if( zCol && sqlite3StrICmp(zSpan, zCol)!=0 ){
+ if( zCol && tdsqlite3StrICmp(zSpan, zCol)!=0 ){
return 0;
}
return 1;
}
/*
+** Return TRUE if the double-quoted string mis-feature should be supported.
+*/
+static int areDoubleQuotedStringsEnabled(tdsqlite3 *db, NameContext *pTopNC){
+ if( db->init.busy ) return 1; /* Always support for legacy schemas */
+ if( pTopNC->ncFlags & NC_IsDDL ){
+ /* Currently parsing a DDL statement */
+ if( tdsqlite3WritableSchema(db) && (db->flags & SQLITE_DqsDML)!=0 ){
+ return 1;
+ }
+ return (db->flags & SQLITE_DqsDDL)!=0;
+ }else{
+ /* Currently parsing a DML statement */
+ return (db->flags & SQLITE_DqsDML)!=0;
+ }
+}
+
+/*
** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up
** that name in the set of source tables in pSrcList and make the pExpr
** expression node refer back to that source column. The following changes
@@ -91427,7 +100571,7 @@ SQLITE_PRIVATE int sqlite3MatchSpanName(
** (even if X is implied).
** pExpr->iTable Set to the cursor number for the table obtained
** from pSrcList.
-** pExpr->pTab Points to the Table structure of X.Y (even if
+** pExpr->y.pTab Points to the Table structure of X.Y (even if
** X and/or Y are implied.)
** pExpr->iColumn Set to the column number within the table.
** pExpr->op Set to TK_COLUMN.
@@ -91456,12 +100600,12 @@ static int lookupName(
int cnt = 0; /* Number of matching column names */
int cntTab = 0; /* Number of matching table names */
int nSubquery = 0; /* How many levels of subquery */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
struct SrcList_item *pItem; /* Use for looping over pSrcList items */
struct SrcList_item *pMatch = 0; /* The matching pSrcList item */
NameContext *pTopNC = pNC; /* First namecontext in the list */
Schema *pSchema = 0; /* Schema of the expression */
- int isTrigger = 0; /* True if resolved to a trigger column */
+ int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */
Table *pTab = 0; /* Table hold the row */
Column *pCol; /* A column of pTab */
@@ -91471,7 +100615,6 @@ static int lookupName(
/* Initialize the node to no-match */
pExpr->iTable = -1;
- pExpr->pTab = 0;
ExprSetVVAProperty(pExpr, EP_NoReduce);
/* Translate the schema name in zDb into a pointer to the corresponding
@@ -91490,7 +100633,7 @@ static int lookupName(
}else{
for(i=0; i<db->nDb; i++){
assert( db->aDb[i].zDbSName );
- if( sqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){
+ if( tdsqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){
pSchema = db->aDb[i].pSchema;
break;
}
@@ -91499,7 +100642,8 @@ static int lookupName(
}
/* Start at the inner-most context and move outward until a match is found */
- while( pNC && cnt==0 ){
+ assert( pNC && cnt==0 );
+ do{
ExprList *pEList;
SrcList *pSrcList = pNC->pSrcList;
@@ -91512,7 +100656,7 @@ static int lookupName(
int hit = 0;
pEList = pItem->pSelect->pEList;
for(j=0; j<pEList->nExpr; j++){
- if( sqlite3MatchSpanName(pEList->a[j].zSpan, zCol, zTab, zDb) ){
+ if( tdsqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb) ){
cnt++;
cntTab = 2;
pMatch = pItem;
@@ -91528,15 +100672,18 @@ static int lookupName(
if( zTab ){
const char *zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName;
assert( zTabName!=0 );
- if( sqlite3StrICmp(zTabName, zTab)!=0 ){
+ if( tdsqlite3StrICmp(zTabName, zTab)!=0 ){
continue;
}
+ if( IN_RENAME_OBJECT && pItem->zAlias ){
+ tdsqlite3RenameTokenRemap(pParse, 0, (void*)&pExpr->y.pTab);
+ }
}
if( 0==(cntTab++) ){
pMatch = pItem;
}
for(j=0, pCol=pTab->aCol; j<pTab->nCol; j++, pCol++){
- if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
+ if( tdsqlite3StrICmp(pCol->zName, zCol)==0 ){
/* If there has been exactly one prior match and this match
** is for the right-hand table of a NATURAL JOIN or is in a
** USING clause, then skip this match.
@@ -91555,69 +100702,100 @@ static int lookupName(
}
if( pMatch ){
pExpr->iTable = pMatch->iCursor;
- pExpr->pTab = pMatch->pTab;
+ pExpr->y.pTab = pMatch->pTab;
/* RIGHT JOIN not (yet) supported */
assert( (pMatch->fg.jointype & JT_RIGHT)==0 );
if( (pMatch->fg.jointype & JT_LEFT)!=0 ){
ExprSetProperty(pExpr, EP_CanBeNull);
}
- pSchema = pExpr->pTab->pSchema;
+ pSchema = pExpr->y.pTab->pSchema;
}
} /* if( pSrcList ) */
-#ifndef SQLITE_OMIT_TRIGGER
+#if !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT)
/* If we have not already resolved the name, then maybe
- ** it is a new.* or old.* trigger argument reference
+ ** it is a new.* or old.* trigger argument reference. Or
+ ** maybe it is an excluded.* from an upsert.
*/
- if( zDb==0 && zTab!=0 && cntTab==0 && pParse->pTriggerTab!=0 ){
- int op = pParse->eTriggerOp;
- assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT );
- if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){
- pExpr->iTable = 1;
- pTab = pParse->pTriggerTab;
- }else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){
- pExpr->iTable = 0;
- pTab = pParse->pTriggerTab;
- }else{
- pTab = 0;
+ if( zDb==0 && zTab!=0 && cntTab==0 ){
+ pTab = 0;
+#ifndef SQLITE_OMIT_TRIGGER
+ if( pParse->pTriggerTab!=0 ){
+ int op = pParse->eTriggerOp;
+ assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT );
+ if( op!=TK_DELETE && tdsqlite3StrICmp("new",zTab) == 0 ){
+ pExpr->iTable = 1;
+ pTab = pParse->pTriggerTab;
+ }else if( op!=TK_INSERT && tdsqlite3StrICmp("old",zTab)==0 ){
+ pExpr->iTable = 0;
+ pTab = pParse->pTriggerTab;
+ }
+ }
+#endif /* SQLITE_OMIT_TRIGGER */
+#ifndef SQLITE_OMIT_UPSERT
+ if( (pNC->ncFlags & NC_UUpsert)!=0 ){
+ Upsert *pUpsert = pNC->uNC.pUpsert;
+ if( pUpsert && tdsqlite3StrICmp("excluded",zTab)==0 ){
+ pTab = pUpsert->pUpsertSrc->a[0].pTab;
+ pExpr->iTable = 2;
+ }
}
+#endif /* SQLITE_OMIT_UPSERT */
if( pTab ){
int iCol;
pSchema = pTab->pSchema;
cntTab++;
for(iCol=0, pCol=pTab->aCol; iCol<pTab->nCol; iCol++, pCol++){
- if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
+ if( tdsqlite3StrICmp(pCol->zName, zCol)==0 ){
if( iCol==pTab->iPKey ){
iCol = -1;
}
break;
}
}
- if( iCol>=pTab->nCol && sqlite3IsRowid(zCol) && VisibleRowid(pTab) ){
+ if( iCol>=pTab->nCol && tdsqlite3IsRowid(zCol) && VisibleRowid(pTab) ){
/* IMP: R-51414-32910 */
iCol = -1;
}
if( iCol<pTab->nCol ){
cnt++;
- if( iCol<0 ){
- pExpr->affinity = SQLITE_AFF_INTEGER;
- }else if( pExpr->iTable==0 ){
- testcase( iCol==31 );
- testcase( iCol==32 );
- pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
- }else{
- testcase( iCol==31 );
- testcase( iCol==32 );
- pParse->newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
+#ifndef SQLITE_OMIT_UPSERT
+ if( pExpr->iTable==2 ){
+ testcase( iCol==(-1) );
+ if( IN_RENAME_OBJECT ){
+ pExpr->iColumn = iCol;
+ pExpr->y.pTab = pTab;
+ eNewExprOp = TK_COLUMN;
+ }else{
+ pExpr->iTable = pNC->uNC.pUpsert->regData + iCol;
+ eNewExprOp = TK_REGISTER;
+ ExprSetProperty(pExpr, EP_Alias);
+ }
+ }else
+#endif /* SQLITE_OMIT_UPSERT */
+ {
+#ifndef SQLITE_OMIT_TRIGGER
+ if( iCol<0 ){
+ pExpr->affExpr = SQLITE_AFF_INTEGER;
+ }else if( pExpr->iTable==0 ){
+ testcase( iCol==31 );
+ testcase( iCol==32 );
+ pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
+ }else{
+ testcase( iCol==31 );
+ testcase( iCol==32 );
+ pParse->newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
+ }
+ pExpr->y.pTab = pTab;
+ pExpr->iColumn = (i16)iCol;
+ eNewExprOp = TK_TRIGGER;
+#endif /* SQLITE_OMIT_TRIGGER */
}
- pExpr->iColumn = (i16)iCol;
- pExpr->pTab = pTab;
- isTrigger = 1;
}
}
}
-#endif /* !defined(SQLITE_OMIT_TRIGGER) */
+#endif /* !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT) */
/*
** Perhaps the name is a reference to the ROWID
@@ -91625,13 +100803,13 @@ static int lookupName(
if( cnt==0
&& cntTab==1
&& pMatch
- && (pNC->ncFlags & NC_IdxExpr)==0
- && sqlite3IsRowid(zCol)
+ && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0
+ && tdsqlite3IsRowid(zCol)
&& VisibleRowid(pMatch->pTab)
){
cnt = 1;
pExpr->iColumn = -1;
- pExpr->affinity = SQLITE_AFF_INTEGER;
+ pExpr->affExpr = SQLITE_AFF_INTEGER;
}
/*
@@ -91650,32 +100828,45 @@ static int lookupName(
** or HAVING clauses, or as part of a larger expression in the ORDER BY
** clause is not standard SQL. This is a (goofy) SQLite extension, that
** is supported for backwards compatibility only. Hence, we issue a warning
- ** on sqlite3_log() whenever the capability is used.
+ ** on tdsqlite3_log() whenever the capability is used.
*/
- if( (pEList = pNC->pEList)!=0
- && zTab==0
+ if( (pNC->ncFlags & NC_UEList)!=0
&& cnt==0
+ && zTab==0
){
+ pEList = pNC->uNC.pEList;
+ assert( pEList!=0 );
for(j=0; j<pEList->nExpr; j++){
- char *zAs = pEList->a[j].zName;
- if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){
+ char *zAs = pEList->a[j].zEName;
+ if( pEList->a[j].eEName==ENAME_NAME
+ && tdsqlite3_stricmp(zAs, zCol)==0
+ ){
Expr *pOrig;
assert( pExpr->pLeft==0 && pExpr->pRight==0 );
assert( pExpr->x.pList==0 );
assert( pExpr->x.pSelect==0 );
pOrig = pEList->a[j].pExpr;
if( (pNC->ncFlags&NC_AllowAgg)==0 && ExprHasProperty(pOrig, EP_Agg) ){
- sqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs);
+ tdsqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs);
+ return WRC_Abort;
+ }
+ if( ExprHasProperty(pOrig, EP_Win)
+ && ((pNC->ncFlags&NC_AllowWin)==0 || pNC!=pTopNC )
+ ){
+ tdsqlite3ErrorMsg(pParse, "misuse of aliased window function %s",zAs);
return WRC_Abort;
}
- if( sqlite3ExprVectorSize(pOrig)!=1 ){
- sqlite3ErrorMsg(pParse, "row value misused");
+ if( tdsqlite3ExprVectorSize(pOrig)!=1 ){
+ tdsqlite3ErrorMsg(pParse, "row value misused");
return WRC_Abort;
}
resolveAlias(pParse, pEList, j, pExpr, "", nSubquery);
cnt = 1;
pMatch = 0;
assert( zTab==0 && zDb==0 );
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenRemap(pParse, 0, (void*)pExpr);
+ }
goto lookupname_end;
}
}
@@ -91684,11 +100875,11 @@ static int lookupName(
/* Advance to the next name context. The loop will exit when either
** we have a match (cnt>0) or when we run out of name contexts.
*/
- if( cnt==0 ){
- pNC = pNC->pNext;
- nSubquery++;
- }
- }
+ if( cnt ) break;
+ pNC = pNC->pNext;
+ nSubquery++;
+ }while( pNC );
+
/*
** If X and Y are NULL (in other words if only the column name Z is
@@ -91700,10 +100891,37 @@ static int lookupName(
** Because no reference was made to outer contexts, the pNC->nRef
** fields are not changed in any context.
*/
- if( cnt==0 && zTab==0 && ExprHasProperty(pExpr,EP_DblQuoted) ){
- pExpr->op = TK_STRING;
- pExpr->pTab = 0;
- return WRC_Prune;
+ if( cnt==0 && zTab==0 ){
+ assert( pExpr->op==TK_ID );
+ if( ExprHasProperty(pExpr,EP_DblQuoted)
+ && areDoubleQuotedStringsEnabled(db, pTopNC)
+ ){
+ /* If a double-quoted identifier does not match any known column name,
+ ** then treat it as a string.
+ **
+ ** This hack was added in the early days of SQLite in a misguided attempt
+ ** to be compatible with MySQL 3.x, which used double-quotes for strings.
+ ** I now sorely regret putting in this hack. The effect of this hack is
+ ** that misspelled identifier names are silently converted into strings
+ ** rather than causing an error, to the frustration of countless
+ ** programmers. To all those frustrated programmers, my apologies.
+ **
+ ** Someday, I hope to get rid of this hack. Unfortunately there is
+ ** a huge amount of legacy SQL that uses it. So for now, we just
+ ** issue a warning.
+ */
+ tdsqlite3_log(SQLITE_WARNING,
+ "double-quoted string literal: \"%w\"", zCol);
+#ifdef SQLITE_ENABLE_NORMALIZE
+ tdsqlite3VdbeAddDblquoteStr(db, pParse->pVdbe, zCol);
+#endif
+ pExpr->op = TK_STRING;
+ pExpr->y.pTab = 0;
+ return WRC_Prune;
+ }
+ if( tdsqlite3ExprIdToTrueFalse(pExpr) ){
+ return WRC_Prune;
+ }
}
/*
@@ -91714,11 +100932,11 @@ static int lookupName(
const char *zErr;
zErr = cnt==0 ? "no such column" : "ambiguous column name";
if( zDb ){
- sqlite3ErrorMsg(pParse, "%s: %s.%s.%s", zErr, zDb, zTab, zCol);
+ tdsqlite3ErrorMsg(pParse, "%s: %s.%s.%s", zErr, zDb, zTab, zCol);
}else if( zTab ){
- sqlite3ErrorMsg(pParse, "%s: %s.%s", zErr, zTab, zCol);
+ tdsqlite3ErrorMsg(pParse, "%s: %s.%s", zErr, zTab, zCol);
}else{
- sqlite3ErrorMsg(pParse, "%s: %s", zErr, zCol);
+ tdsqlite3ErrorMsg(pParse, "%s: %s", zErr, zCol);
}
pParse->checkSchema = 1;
pTopNC->nErr++;
@@ -91726,32 +100944,50 @@ static int lookupName(
/* If a column from a table in pSrcList is referenced, then record
** this fact in the pSrcList.a[].colUsed bitmask. Column 0 causes
- ** bit 0 to be set. Column 1 sets bit 1. And so forth. If the
- ** column number is greater than the number of bits in the bitmask
- ** then set the high-order bit of the bitmask.
+ ** bit 0 to be set. Column 1 sets bit 1. And so forth. Bit 63 is
+ ** set if the 63rd or any subsequent column is used.
+ **
+ ** The colUsed mask is an optimization used to help determine if an
+ ** index is a covering index. The correct answer is still obtained
+ ** if the mask contains extra set bits. However, it is important to
+ ** avoid setting bits beyond the maximum column number of the table.
+ ** (See ticket [b92e5e8ec2cdbaa1]).
+ **
+ ** If a generated column is referenced, set bits for every column
+ ** of the table.
*/
if( pExpr->iColumn>=0 && pMatch!=0 ){
int n = pExpr->iColumn;
- testcase( n==BMS-1 );
- if( n>=BMS ){
- n = BMS-1;
- }
+ Table *pExTab = pExpr->y.pTab;
+ assert( pExTab!=0 );
assert( pMatch->iCursor==pExpr->iTable );
- pMatch->colUsed |= ((Bitmask)1)<<n;
+ if( (pExTab->tabFlags & TF_HasGenerated)!=0
+ && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0
+ ){
+ testcase( pExTab->nCol==BMS-1 );
+ testcase( pExTab->nCol==BMS );
+ pMatch->colUsed = pExTab->nCol>=BMS ? ALLBITS : MASKBIT(pExTab->nCol)-1;
+ }else{
+ testcase( n==BMS-1 );
+ testcase( n==BMS );
+ if( n>=BMS ) n = BMS-1;
+ pMatch->colUsed |= ((Bitmask)1)<<n;
+ }
}
/* Clean up and return
*/
- sqlite3ExprDelete(db, pExpr->pLeft);
+ tdsqlite3ExprDelete(db, pExpr->pLeft);
pExpr->pLeft = 0;
- sqlite3ExprDelete(db, pExpr->pRight);
+ tdsqlite3ExprDelete(db, pExpr->pRight);
pExpr->pRight = 0;
- pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN);
+ pExpr->op = eNewExprOp;
+ ExprSetProperty(pExpr, EP_Leaf);
lookupname_end:
if( cnt==1 ){
assert( pNC!=0 );
if( !ExprHasProperty(pExpr, EP_Alias) ){
- sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList);
+ tdsqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList);
}
/* Increment the nRef value on all name contexts from TopNC up to
** the point where the name matched. */
@@ -91771,21 +101007,28 @@ lookupname_end:
** Allocate and return a pointer to an expression to load the column iCol
** from datasource iSrc in SrcList pSrc.
*/
-SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSrc, int iCol){
- Expr *p = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0);
+SQLITE_PRIVATE Expr *tdsqlite3CreateColumnExpr(tdsqlite3 *db, SrcList *pSrc, int iSrc, int iCol){
+ Expr *p = tdsqlite3ExprAlloc(db, TK_COLUMN, 0, 0);
if( p ){
struct SrcList_item *pItem = &pSrc->a[iSrc];
- p->pTab = pItem->pTab;
+ Table *pTab = p->y.pTab = pItem->pTab;
p->iTable = pItem->iCursor;
- if( p->pTab->iPKey==iCol ){
+ if( p->y.pTab->iPKey==iCol ){
p->iColumn = -1;
}else{
p->iColumn = (ynVar)iCol;
- testcase( iCol==BMS );
- testcase( iCol==BMS-1 );
- pItem->colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol);
+ if( (pTab->tabFlags & TF_HasGenerated)!=0
+ && (pTab->aCol[iCol].colFlags & COLFLAG_GENERATED)!=0
+ ){
+ testcase( pTab->nCol==63 );
+ testcase( pTab->nCol==64 );
+ pItem->colUsed = pTab->nCol>=64 ? ALLBITS : MASKBIT(pTab->nCol)-1;
+ }else{
+ testcase( iCol==BMS );
+ testcase( iCol==BMS-1 );
+ pItem->colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol);
+ }
}
- ExprSetProperty(p, EP_Resolved);
}
return p;
}
@@ -91793,23 +101036,39 @@ SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSr
/*
** Report an error that an expression is not valid for some set of
** pNC->ncFlags values determined by validMask.
-*/
-static void notValid(
- Parse *pParse, /* Leave error message here */
- NameContext *pNC, /* The name context */
- const char *zMsg, /* Type of error */
- int validMask /* Set of contexts for which prohibited */
-){
- assert( (validMask&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr))==0 );
- if( (pNC->ncFlags & validMask)!=0 ){
- const char *zIn = "partial index WHERE clauses";
- if( pNC->ncFlags & NC_IdxExpr ) zIn = "index expressions";
+**
+** static void notValid(
+** Parse *pParse, // Leave error message here
+** NameContext *pNC, // The name context
+** const char *zMsg, // Type of error
+** int validMask, // Set of contexts for which prohibited
+** Expr *pExpr // Invalidate this expression on error
+** ){...}
+**
+** As an optimization, since the conditional is almost always false
+** (because errors are rare), the conditional is moved outside of the
+** function call using a macro.
+*/
+static void notValidImpl(
+ Parse *pParse, /* Leave error message here */
+ NameContext *pNC, /* The name context */
+ const char *zMsg, /* Type of error */
+ Expr *pExpr /* Invalidate this expression on error */
+){
+ const char *zIn = "partial index WHERE clauses";
+ if( pNC->ncFlags & NC_IdxExpr ) zIn = "index expressions";
#ifndef SQLITE_OMIT_CHECK
- else if( pNC->ncFlags & NC_IsCheck ) zIn = "CHECK constraints";
+ else if( pNC->ncFlags & NC_IsCheck ) zIn = "CHECK constraints";
#endif
- sqlite3ErrorMsg(pParse, "%s prohibited in %s", zMsg, zIn);
- }
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ else if( pNC->ncFlags & NC_GenCol ) zIn = "generated columns";
+#endif
+ tdsqlite3ErrorMsg(pParse, "%s prohibited in %s", zMsg, zIn);
+ if( pExpr ) pExpr->op = TK_NULL;
}
+#define tdsqlite3ResolveNotValid(P,N,M,X,E) \
+ assert( ((X)&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol))==0 ); \
+ if( ((N)->ncFlags & (X))!=0 ) notValidImpl(P,N,M,E);
/*
** Expression p should encode a floating point value between 1.0 and 0.0.
@@ -91819,14 +101078,14 @@ static void notValid(
static int exprProbability(Expr *p){
double r = -1.0;
if( p->op!=TK_FLOAT ) return -1;
- sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8);
+ tdsqlite3AtoF(p->u.zToken, &r, tdsqlite3Strlen30(p->u.zToken), SQLITE_UTF8);
assert( r>=0.0 );
if( r>1.0 ) return -1;
return (int)(r*134217728.0);
}
/*
-** This routine is callback for sqlite3WalkExpr().
+** This routine is callback for tdsqlite3WalkExpr().
**
** Resolve symbolic names into TK_COLUMN operators for the current
** node in the expression tree. Return 0 to continue the search down
@@ -91845,8 +101104,6 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
pParse = pNC->pParse;
assert( pParse==pWalker->pParse );
- if( ExprHasProperty(pExpr, EP_Resolved) ) return WRC_Prune;
- ExprSetProperty(pExpr, EP_Resolved);
#ifndef NDEBUG
if( pNC->pSrcList && pNC->pSrcList->nAlloc>0 ){
SrcList *pSrcList = pNC->pSrcList;
@@ -91867,44 +101124,58 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
SrcList *pSrcList = pNC->pSrcList;
struct SrcList_item *pItem;
assert( pSrcList && pSrcList->nSrc==1 );
- pItem = pSrcList->a;
+ pItem = pSrcList->a;
+ assert( HasRowid(pItem->pTab) && pItem->pTab->pSelect==0 );
pExpr->op = TK_COLUMN;
- pExpr->pTab = pItem->pTab;
+ pExpr->y.pTab = pItem->pTab;
pExpr->iTable = pItem->iCursor;
pExpr->iColumn = -1;
- pExpr->affinity = SQLITE_AFF_INTEGER;
+ pExpr->affExpr = SQLITE_AFF_INTEGER;
break;
}
#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
&& !defined(SQLITE_OMIT_SUBQUERY) */
- /* A lone identifier is the name of a column.
- */
- case TK_ID: {
- return lookupName(pParse, 0, 0, pExpr->u.zToken, pNC, pExpr);
- }
-
- /* A table name and column name: ID.ID
+ /* A column name: ID
+ ** Or table name and column name: ID.ID
** Or a database, table and column: ID.ID.ID
+ **
+ ** The TK_ID and TK_OUT cases are combined so that there will only
+ ** be one call to lookupName(). Then the compiler will in-line
+ ** lookupName() for a size reduction and performance increase.
*/
+ case TK_ID:
case TK_DOT: {
const char *zColumn;
const char *zTable;
const char *zDb;
Expr *pRight;
- /* if( pSrcList==0 ) break; */
- notValid(pParse, pNC, "the \".\" operator", NC_IdxExpr);
- pRight = pExpr->pRight;
- if( pRight->op==TK_ID ){
+ if( pExpr->op==TK_ID ){
zDb = 0;
- zTable = pExpr->pLeft->u.zToken;
- zColumn = pRight->u.zToken;
+ zTable = 0;
+ zColumn = pExpr->u.zToken;
}else{
- assert( pRight->op==TK_DOT );
- zDb = pExpr->pLeft->u.zToken;
- zTable = pRight->pLeft->u.zToken;
- zColumn = pRight->pRight->u.zToken;
+ Expr *pLeft = pExpr->pLeft;
+ testcase( pNC->ncFlags & NC_IdxExpr );
+ testcase( pNC->ncFlags & NC_GenCol );
+ tdsqlite3ResolveNotValid(pParse, pNC, "the \".\" operator",
+ NC_IdxExpr|NC_GenCol, 0);
+ pRight = pExpr->pRight;
+ if( pRight->op==TK_ID ){
+ zDb = 0;
+ }else{
+ assert( pRight->op==TK_DOT );
+ zDb = pLeft->u.zToken;
+ pLeft = pRight->pLeft;
+ pRight = pRight->pRight;
+ }
+ zTable = pLeft->u.zToken;
+ zColumn = pRight->u.zToken;
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenRemap(pParse, (void*)pExpr, (void*)pRight);
+ tdsqlite3RenameTokenRemap(pParse, (void*)&pExpr->y.pTab, (void*)pLeft);
+ }
}
return lookupName(pParse, zDb, zTable, zColumn, pNC, pExpr);
}
@@ -91921,13 +101192,16 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
const char *zId; /* The function name. */
FuncDef *pDef; /* Information about the function */
u8 enc = ENC(pParse->db); /* The database encoding */
-
+ int savedAllowFlags = (pNC->ncFlags & (NC_AllowAgg | NC_AllowWin));
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ Window *pWin = (IsWindowFunc(pExpr) ? pExpr->y.pWin : 0);
+#endif
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
zId = pExpr->u.zToken;
- nId = sqlite3Strlen30(zId);
- pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0);
+ nId = tdsqlite3Strlen30(zId);
+ pDef = tdsqlite3FindFunction(pParse->db, zId, n, enc, 0);
if( pDef==0 ){
- pDef = sqlite3FindFunction(pParse->db, zId, -2, enc, 0);
+ pDef = tdsqlite3FindFunction(pParse->db, zId, -2, enc, 0);
if( pDef==0 ){
no_such_func = 1;
}else{
@@ -91936,11 +101210,11 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
}else{
is_agg = pDef->xFinalize!=0;
if( pDef->funcFlags & SQLITE_FUNC_UNLIKELY ){
- ExprSetProperty(pExpr, EP_Unlikely|EP_Skip);
+ ExprSetProperty(pExpr, EP_Unlikely);
if( n==2 ){
pExpr->iTable = exprProbability(pList->a[1].pExpr);
if( pExpr->iTable<0 ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"second argument to likelihood() must be a "
"constant between 0.0 and 1.0");
pNC->nErr++;
@@ -91960,10 +101234,10 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
}
#ifndef SQLITE_OMIT_AUTHORIZATION
{
- int auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0,pDef->zName,0);
+ int auth = tdsqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0,pDef->zName,0);
if( auth!=SQLITE_OK ){
if( auth==SQLITE_DENY ){
- sqlite3ErrorMsg(pParse, "not authorized to use function: %s",
+ tdsqlite3ErrorMsg(pParse, "not authorized to use function: %s",
pDef->zName);
pNC->nErr++;
}
@@ -91975,51 +101249,150 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
if( pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG) ){
/* For the purposes of the EP_ConstFunc flag, date and time
** functions and other functions that change slowly are considered
- ** constant because they are constant for the duration of one query */
+ ** constant because they are constant for the duration of one query.
+ ** This allows them to be factored out of inner loops. */
ExprSetProperty(pExpr,EP_ConstFunc);
}
if( (pDef->funcFlags & SQLITE_FUNC_CONSTANT)==0 ){
- /* Date/time functions that use 'now', and other functions like
+ /* Clearly non-deterministic functions like random(), but also
+ ** date/time functions that use 'now', and other functions like
** sqlite_version() that might change over time cannot be used
- ** in an index. */
- notValid(pParse, pNC, "non-deterministic functions",
- NC_IdxExpr|NC_PartIdx);
+ ** in an index or generated column. Curiously, they can be used
+ ** in a CHECK constraint. SQLServer, MySQL, and PostgreSQL all
+ ** all this. */
+ tdsqlite3ResolveNotValid(pParse, pNC, "non-deterministic functions",
+ NC_IdxExpr|NC_PartIdx|NC_GenCol, 0);
+ }else{
+ assert( (NC_SelfRef & 0xff)==NC_SelfRef ); /* Must fit in 8 bits */
+ pExpr->op2 = pNC->ncFlags & NC_SelfRef;
+ if( pNC->ncFlags & NC_FromDDL ) ExprSetProperty(pExpr, EP_FromDDL);
+ }
+ if( (pDef->funcFlags & SQLITE_FUNC_INTERNAL)!=0
+ && pParse->nested==0
+ && (pParse->db->mDbFlags & DBFLAG_InternalFunc)==0
+ ){
+ /* Internal-use-only functions are disallowed unless the
+ ** SQL is being compiled using tdsqlite3NestedParse() or
+ ** the SQLITE_TESTCTRL_INTERNAL_FUNCTIONS test-control has be
+ ** used to activate internal functionsn for testing purposes */
+ no_such_func = 1;
+ pDef = 0;
+ }else
+ if( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0
+ && !IN_RENAME_OBJECT
+ ){
+ tdsqlite3ExprFunctionUsable(pParse, pExpr, pDef);
}
}
- if( is_agg && (pNC->ncFlags & NC_AllowAgg)==0 ){
- sqlite3ErrorMsg(pParse, "misuse of aggregate function %.*s()", nId,zId);
- pNC->nErr++;
- is_agg = 0;
- }else if( no_such_func && pParse->db->init.busy==0
+
+ if( 0==IN_RENAME_OBJECT ){
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ assert( is_agg==0 || (pDef->funcFlags & SQLITE_FUNC_MINMAX)
+ || (pDef->xValue==0 && pDef->xInverse==0)
+ || (pDef->xValue && pDef->xInverse && pDef->xSFunc && pDef->xFinalize)
+ );
+ if( pDef && pDef->xValue==0 && pWin ){
+ tdsqlite3ErrorMsg(pParse,
+ "%.*s() may not be used as a window function", nId, zId
+ );
+ pNC->nErr++;
+ }else if(
+ (is_agg && (pNC->ncFlags & NC_AllowAgg)==0)
+ || (is_agg && (pDef->funcFlags&SQLITE_FUNC_WINDOW) && !pWin)
+ || (is_agg && pWin && (pNC->ncFlags & NC_AllowWin)==0)
+ ){
+ const char *zType;
+ if( (pDef->funcFlags & SQLITE_FUNC_WINDOW) || pWin ){
+ zType = "window";
+ }else{
+ zType = "aggregate";
+ }
+ tdsqlite3ErrorMsg(pParse, "misuse of %s function %.*s()",zType,nId,zId);
+ pNC->nErr++;
+ is_agg = 0;
+ }
+#else
+ if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) ){
+ tdsqlite3ErrorMsg(pParse,"misuse of aggregate function %.*s()",nId,zId);
+ pNC->nErr++;
+ is_agg = 0;
+ }
+#endif
+ else if( no_such_func && pParse->db->init.busy==0
#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
- && pParse->explain==0
+ && pParse->explain==0
+#endif
+ ){
+ tdsqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId);
+ pNC->nErr++;
+ }else if( wrong_num_args ){
+ tdsqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()",
+ nId, zId);
+ pNC->nErr++;
+ }
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ else if( is_agg==0 && ExprHasProperty(pExpr, EP_WinFunc) ){
+ tdsqlite3ErrorMsg(pParse,
+ "FILTER may not be used with non-aggregate %.*s()",
+ nId, zId
+ );
+ pNC->nErr++;
+ }
+#endif
+ if( is_agg ){
+ /* Window functions may not be arguments of aggregate functions.
+ ** Or arguments of other window functions. But aggregate functions
+ ** may be arguments for window functions. */
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ pNC->ncFlags &= ~(NC_AllowWin | (!pWin ? NC_AllowAgg : 0));
+#else
+ pNC->ncFlags &= ~NC_AllowAgg;
#endif
- ){
- sqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId);
- pNC->nErr++;
- }else if( wrong_num_args ){
- sqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()",
- nId, zId);
- pNC->nErr++;
- }
- if( is_agg ) pNC->ncFlags &= ~NC_AllowAgg;
- sqlite3WalkExprList(pWalker, pList);
- if( is_agg ){
- NameContext *pNC2 = pNC;
- pExpr->op = TK_AGG_FUNCTION;
- pExpr->op2 = 0;
- while( pNC2 && !sqlite3FunctionUsesThisSrc(pExpr, pNC2->pSrcList) ){
- pExpr->op2++;
- pNC2 = pNC2->pNext;
}
- assert( pDef!=0 );
- if( pNC2 ){
- assert( SQLITE_FUNC_MINMAX==NC_MinMaxAgg );
- testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 );
- pNC2->ncFlags |= NC_HasAgg | (pDef->funcFlags & SQLITE_FUNC_MINMAX);
+ }
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ else if( ExprHasProperty(pExpr, EP_WinFunc) ){
+ is_agg = 1;
+ }
+#endif
+ tdsqlite3WalkExprList(pWalker, pList);
+ if( is_agg ){
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( pWin ){
+ Select *pSel = pNC->pWinSelect;
+ assert( pWin==pExpr->y.pWin );
+ if( IN_RENAME_OBJECT==0 ){
+ tdsqlite3WindowUpdate(pParse, pSel ? pSel->pWinDefn : 0, pWin, pDef);
+ }
+ tdsqlite3WalkExprList(pWalker, pWin->pPartition);
+ tdsqlite3WalkExprList(pWalker, pWin->pOrderBy);
+ tdsqlite3WalkExpr(pWalker, pWin->pFilter);
+ tdsqlite3WindowLink(pSel, pWin);
+ pNC->ncFlags |= NC_HasWin;
+ }else
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+ {
+ NameContext *pNC2 = pNC;
+ pExpr->op = TK_AGG_FUNCTION;
+ pExpr->op2 = 0;
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( ExprHasProperty(pExpr, EP_WinFunc) ){
+ tdsqlite3WalkExpr(pWalker, pExpr->y.pWin->pFilter);
+ }
+#endif
+ while( pNC2 && !tdsqlite3FunctionUsesThisSrc(pExpr, pNC2->pSrcList) ){
+ pExpr->op2++;
+ pNC2 = pNC2->pNext;
+ }
+ assert( pDef!=0 || IN_RENAME_OBJECT );
+ if( pNC2 && pDef ){
+ assert( SQLITE_FUNC_MINMAX==NC_MinMaxAgg );
+ testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 );
+ pNC2->ncFlags |= NC_HasAgg | (pDef->funcFlags & SQLITE_FUNC_MINMAX);
+ }
}
- pNC->ncFlags |= NC_AllowAgg;
+ pNC->ncFlags |= savedAllowFlags;
}
/* FIX ME: Compute pExpr->affinity based on the expected return
** type of the function
@@ -92034,8 +101407,13 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
testcase( pExpr->op==TK_IN );
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
int nRef = pNC->nRef;
- notValid(pParse, pNC, "subqueries", NC_IsCheck|NC_PartIdx|NC_IdxExpr);
- sqlite3WalkSelect(pWalker, pExpr->x.pSelect);
+ testcase( pNC->ncFlags & NC_IsCheck );
+ testcase( pNC->ncFlags & NC_PartIdx );
+ testcase( pNC->ncFlags & NC_IdxExpr );
+ testcase( pNC->ncFlags & NC_GenCol );
+ tdsqlite3ResolveNotValid(pParse, pNC, "subqueries",
+ NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr);
+ tdsqlite3WalkSelect(pWalker, pExpr->x.pSelect);
assert( pNC->nRef>=nRef );
if( nRef!=pNC->nRef ){
ExprSetProperty(pExpr, EP_VarSelect);
@@ -92045,30 +101423,50 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
break;
}
case TK_VARIABLE: {
- notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr);
+ testcase( pNC->ncFlags & NC_IsCheck );
+ testcase( pNC->ncFlags & NC_PartIdx );
+ testcase( pNC->ncFlags & NC_IdxExpr );
+ testcase( pNC->ncFlags & NC_GenCol );
+ tdsqlite3ResolveNotValid(pParse, pNC, "parameters",
+ NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr);
break;
}
+ case TK_IS:
+ case TK_ISNOT: {
+ Expr *pRight = tdsqlite3ExprSkipCollateAndLikely(pExpr->pRight);
+ assert( !ExprHasProperty(pExpr, EP_Reduced) );
+ /* Handle special cases of "x IS TRUE", "x IS FALSE", "x IS NOT TRUE",
+ ** and "x IS NOT FALSE". */
+ if( pRight->op==TK_ID ){
+ int rc = resolveExprStep(pWalker, pRight);
+ if( rc==WRC_Abort ) return WRC_Abort;
+ if( pRight->op==TK_TRUEFALSE ){
+ pExpr->op2 = pExpr->op;
+ pExpr->op = TK_TRUTH;
+ return WRC_Continue;
+ }
+ }
+ /* Fall thru */
+ }
case TK_BETWEEN:
case TK_EQ:
case TK_NE:
case TK_LT:
case TK_LE:
case TK_GT:
- case TK_GE:
- case TK_IS:
- case TK_ISNOT: {
+ case TK_GE: {
int nLeft, nRight;
if( pParse->db->mallocFailed ) break;
assert( pExpr->pLeft!=0 );
- nLeft = sqlite3ExprVectorSize(pExpr->pLeft);
+ nLeft = tdsqlite3ExprVectorSize(pExpr->pLeft);
if( pExpr->op==TK_BETWEEN ){
- nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[0].pExpr);
+ nRight = tdsqlite3ExprVectorSize(pExpr->x.pList->a[0].pExpr);
if( nRight==nLeft ){
- nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[1].pExpr);
+ nRight = tdsqlite3ExprVectorSize(pExpr->x.pList->a[1].pExpr);
}
}else{
assert( pExpr->pRight!=0 );
- nRight = sqlite3ExprVectorSize(pExpr->pRight);
+ nRight = tdsqlite3ExprVectorSize(pExpr->pRight);
}
if( nLeft!=nRight ){
testcase( pExpr->op==TK_EQ );
@@ -92080,7 +101478,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
testcase( pExpr->op==TK_IS );
testcase( pExpr->op==TK_ISNOT );
testcase( pExpr->op==TK_BETWEEN );
- sqlite3ErrorMsg(pParse, "row value misused");
+ tdsqlite3ErrorMsg(pParse, "row value misused");
}
break;
}
@@ -92112,8 +101510,9 @@ static int resolveAsName(
if( pE->op==TK_ID ){
char *zCol = pE->u.zToken;
for(i=0; i<pEList->nExpr; i++){
- char *zAs = pEList->a[i].zName;
- if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){
+ if( pEList->a[i].eEName==ENAME_NAME
+ && tdsqlite3_stricmp(pEList->a[i].zEName, zCol)==0
+ ){
return i+1;
}
}
@@ -92147,11 +101546,11 @@ static int resolveOrderByTermToExprList(
int i; /* Loop counter */
ExprList *pEList; /* The columns of the result set */
NameContext nc; /* Name context for resolving pE */
- sqlite3 *db; /* Database connection */
+ tdsqlite3 *db; /* Database connection */
int rc; /* Return code from subprocedures */
u8 savedSuppErr; /* Saved value of db->suppressErr */
- assert( sqlite3ExprIsInteger(pE, &i)==0 );
+ assert( tdsqlite3ExprIsInteger(pE, &i)==0 );
pEList = pSelect->pEList;
/* Resolve all names in the ORDER BY term expression
@@ -92159,13 +101558,13 @@ static int resolveOrderByTermToExprList(
memset(&nc, 0, sizeof(nc));
nc.pParse = pParse;
nc.pSrcList = pSelect->pSrc;
- nc.pEList = pEList;
- nc.ncFlags = NC_AllowAgg;
+ nc.uNC.pEList = pEList;
+ nc.ncFlags = NC_AllowAgg|NC_UEList;
nc.nErr = 0;
db = pParse->db;
savedSuppErr = db->suppressErr;
db->suppressErr = 1;
- rc = sqlite3ResolveExprNames(&nc, pE);
+ rc = tdsqlite3ResolveExprNames(&nc, pE);
db->suppressErr = savedSuppErr;
if( rc ) return 0;
@@ -92174,7 +101573,7 @@ static int resolveOrderByTermToExprList(
** result-set entry.
*/
for(i=0; i<pEList->nExpr; i++){
- if( sqlite3ExprCompare(pEList->a[i].pExpr, pE, -1)<2 ){
+ if( tdsqlite3ExprCompare(0, pEList->a[i].pExpr, pE, -1)<2 ){
return i+1;
}
}
@@ -92192,7 +101591,7 @@ static void resolveOutOfRangeError(
int i, /* The index (1-based) of the term out of range */
int mx /* Largest permissible value of i */
){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"%r %s BY term out of range - should be "
"between 1 and %d", i, zType, mx);
}
@@ -92219,18 +101618,16 @@ static int resolveCompoundOrderBy(
int i;
ExprList *pOrderBy;
ExprList *pEList;
- sqlite3 *db;
+ tdsqlite3 *db;
int moreToDo = 1;
pOrderBy = pSelect->pOrderBy;
if( pOrderBy==0 ) return 0;
db = pParse->db;
-#if SQLITE_MAX_COLUMN
if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){
- sqlite3ErrorMsg(pParse, "too many terms in ORDER BY clause");
+ tdsqlite3ErrorMsg(pParse, "too many terms in ORDER BY clause");
return 1;
}
-#endif
for(i=0; i<pOrderBy->nExpr; i++){
pOrderBy->a[i].done = 0;
}
@@ -92248,8 +101645,8 @@ static int resolveCompoundOrderBy(
int iCol = -1;
Expr *pE, *pDup;
if( pItem->done ) continue;
- pE = sqlite3ExprSkipCollate(pItem->pExpr);
- if( sqlite3ExprIsInteger(pE, &iCol) ){
+ pE = tdsqlite3ExprSkipCollateAndLikely(pItem->pExpr);
+ if( tdsqlite3ExprIsInteger(pE, &iCol) ){
if( iCol<=0 || iCol>pEList->nExpr ){
resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr);
return 1;
@@ -92257,32 +101654,53 @@ static int resolveCompoundOrderBy(
}else{
iCol = resolveAsName(pParse, pEList, pE);
if( iCol==0 ){
- pDup = sqlite3ExprDup(db, pE, 0);
+ /* Now test if expression pE matches one of the values returned
+ ** by pSelect. In the usual case this is done by duplicating the
+ ** expression, resolving any symbols in it, and then comparing
+ ** it against each expression returned by the SELECT statement.
+ ** Once the comparisons are finished, the duplicate expression
+ ** is deleted.
+ **
+ ** Or, if this is running as part of an ALTER TABLE operation,
+ ** resolve the symbols in the actual expression, not a duplicate.
+ ** And, if one of the comparisons is successful, leave the expression
+ ** as is instead of transforming it to an integer as in the usual
+ ** case. This allows the code in alter.c to modify column
+ ** refererences within the ORDER BY expression as required. */
+ if( IN_RENAME_OBJECT ){
+ pDup = pE;
+ }else{
+ pDup = tdsqlite3ExprDup(db, pE, 0);
+ }
if( !db->mallocFailed ){
assert(pDup);
iCol = resolveOrderByTermToExprList(pParse, pSelect, pDup);
}
- sqlite3ExprDelete(db, pDup);
+ if( !IN_RENAME_OBJECT ){
+ tdsqlite3ExprDelete(db, pDup);
+ }
}
}
if( iCol>0 ){
/* Convert the ORDER BY term into an integer column number iCol,
** taking care to preserve the COLLATE clause if it exists */
- Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0);
- if( pNew==0 ) return 1;
- pNew->flags |= EP_IntValue;
- pNew->u.iValue = iCol;
- if( pItem->pExpr==pE ){
- pItem->pExpr = pNew;
- }else{
- Expr *pParent = pItem->pExpr;
- assert( pParent->op==TK_COLLATE );
- while( pParent->pLeft->op==TK_COLLATE ) pParent = pParent->pLeft;
- assert( pParent->pLeft==pE );
- pParent->pLeft = pNew;
+ if( !IN_RENAME_OBJECT ){
+ Expr *pNew = tdsqlite3Expr(db, TK_INTEGER, 0);
+ if( pNew==0 ) return 1;
+ pNew->flags |= EP_IntValue;
+ pNew->u.iValue = iCol;
+ if( pItem->pExpr==pE ){
+ pItem->pExpr = pNew;
+ }else{
+ Expr *pParent = pItem->pExpr;
+ assert( pParent->op==TK_COLLATE );
+ while( pParent->pLeft->op==TK_COLLATE ) pParent = pParent->pLeft;
+ assert( pParent->pLeft==pE );
+ pParent->pLeft = pNew;
+ }
+ tdsqlite3ExprDelete(db, pE);
+ pItem->u.x.iOrderByCol = (u16)iCol;
}
- sqlite3ExprDelete(db, pE);
- pItem->u.x.iOrderByCol = (u16)iCol;
pItem->done = 1;
}else{
moreToDo = 1;
@@ -92292,7 +101710,7 @@ static int resolveCompoundOrderBy(
}
for(i=0; i<pOrderBy->nExpr; i++){
if( pOrderBy->a[i].done==0 ){
- sqlite3ErrorMsg(pParse, "%r ORDER BY term does not match any "
+ tdsqlite3ErrorMsg(pParse, "%r ORDER BY term does not match any "
"column in the result set", i+1);
return 1;
}
@@ -92310,26 +101728,24 @@ static int resolveCompoundOrderBy(
** If any errors are detected, add an error message to pParse and
** return non-zero. Return zero if no errors are seen.
*/
-SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(
+SQLITE_PRIVATE int tdsqlite3ResolveOrderGroupBy(
Parse *pParse, /* Parsing context. Leave error messages here */
Select *pSelect, /* The SELECT statement containing the clause */
ExprList *pOrderBy, /* The ORDER BY or GROUP BY clause to be processed */
const char *zType /* "ORDER" or "GROUP" */
){
int i;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
ExprList *pEList;
struct ExprList_item *pItem;
- if( pOrderBy==0 || pParse->db->mallocFailed ) return 0;
-#if SQLITE_MAX_COLUMN
+ if( pOrderBy==0 || pParse->db->mallocFailed || IN_RENAME_OBJECT ) return 0;
if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){
- sqlite3ErrorMsg(pParse, "too many terms in %s BY clause", zType);
+ tdsqlite3ErrorMsg(pParse, "too many terms in %s BY clause", zType);
return 1;
}
-#endif
pEList = pSelect->pEList;
- assert( pEList!=0 ); /* sqlite3SelectNew() guarantees this */
+ assert( pEList!=0 ); /* tdsqlite3SelectNew() guarantees this */
for(i=0, pItem=pOrderBy->a; i<pOrderBy->nExpr; i++, pItem++){
if( pItem->u.x.iOrderByCol ){
if( pItem->u.x.iOrderByCol>pEList->nExpr ){
@@ -92343,6 +101759,36 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(
return 0;
}
+#ifndef SQLITE_OMIT_WINDOWFUNC
+/*
+** Walker callback for windowRemoveExprFromSelect().
+*/
+static int resolveRemoveWindowsCb(Walker *pWalker, Expr *pExpr){
+ UNUSED_PARAMETER(pWalker);
+ if( ExprHasProperty(pExpr, EP_WinFunc) ){
+ Window *pWin = pExpr->y.pWin;
+ tdsqlite3WindowUnlinkFromSelect(pWin);
+ }
+ return WRC_Continue;
+}
+
+/*
+** Remove any Window objects owned by the expression pExpr from the
+** Select.pWin list of Select object pSelect.
+*/
+static void windowRemoveExprFromSelect(Select *pSelect, Expr *pExpr){
+ if( pSelect->pWin ){
+ Walker sWalker;
+ memset(&sWalker, 0, sizeof(Walker));
+ sWalker.xExprCallback = resolveRemoveWindowsCb;
+ sWalker.u.pSelect = pSelect;
+ tdsqlite3WalkExpr(&sWalker, pExpr);
+ }
+}
+#else
+# define windowRemoveExprFromSelect(a, b)
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+
/*
** pOrderBy is an ORDER BY or GROUP BY clause in SELECT statement pSelect.
** The Name context of the SELECT statement is pNC. zType is either
@@ -92355,7 +101801,7 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(
** the order-by term is an identifier that corresponds to the AS-name of
** a result-set expression, then the term resolves to a copy of the
** result-set expression. Otherwise, the expression is resolved in
-** the usual way - using sqlite3ResolveExprNames().
+** the usual way - using tdsqlite3ResolveExprNames().
**
** This routine returns the number of errors. If errors occur, then
** an appropriate error message might be left in pParse. (OOM errors
@@ -92378,21 +101824,21 @@ static int resolveOrderGroupBy(
pParse = pNC->pParse;
for(i=0, pItem=pOrderBy->a; i<pOrderBy->nExpr; i++, pItem++){
Expr *pE = pItem->pExpr;
- Expr *pE2 = sqlite3ExprSkipCollate(pE);
+ Expr *pE2 = tdsqlite3ExprSkipCollateAndLikely(pE);
if( zType[0]!='G' ){
iCol = resolveAsName(pParse, pSelect->pEList, pE2);
if( iCol>0 ){
/* If an AS-name match is found, mark this ORDER BY column as being
** a copy of the iCol-th result-set column. The subsequent call to
- ** sqlite3ResolveOrderGroupBy() will convert the expression to a
+ ** tdsqlite3ResolveOrderGroupBy() will convert the expression to a
** copy of the iCol-th result-set expression. */
pItem->u.x.iOrderByCol = (u16)iCol;
continue;
}
}
- if( sqlite3ExprIsInteger(pE2, &iCol) ){
+ if( tdsqlite3ExprIsInteger(pE2, &iCol) ){
/* The ORDER BY term is an integer constant. Again, set the column
- ** number so that sqlite3ResolveOrderGroupBy() will convert the
+ ** number so that tdsqlite3ResolveOrderGroupBy() will convert the
** order-by term to a copy of the result-set expression */
if( iCol<1 || iCol>0xffff ){
resolveOutOfRangeError(pParse, zType, i+1, nResult);
@@ -92404,16 +101850,20 @@ static int resolveOrderGroupBy(
/* Otherwise, treat the ORDER BY term as an ordinary expression */
pItem->u.x.iOrderByCol = 0;
- if( sqlite3ResolveExprNames(pNC, pE) ){
+ if( tdsqlite3ResolveExprNames(pNC, pE) ){
return 1;
}
for(j=0; j<pSelect->pEList->nExpr; j++){
- if( sqlite3ExprCompare(pE, pSelect->pEList->a[j].pExpr, -1)==0 ){
+ if( tdsqlite3ExprCompare(0, pE, pSelect->pEList->a[j].pExpr, -1)==0 ){
+ /* Since this expresion is being changed into a reference
+ ** to an identical expression in the result set, remove all Window
+ ** objects belonging to the expression from the Select.pWin list. */
+ windowRemoveExprFromSelect(pSelect, pE);
pItem->u.x.iOrderByCol = j+1;
}
}
}
- return sqlite3ResolveOrderGroupBy(pParse, pSelect, pOrderBy, zType);
+ return tdsqlite3ResolveOrderGroupBy(pParse, pSelect, pOrderBy, zType);
}
/*
@@ -92428,7 +101878,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
int i; /* Loop counter */
ExprList *pGroupBy; /* The GROUP BY clause */
Select *pLeftmost; /* Left-most of SELECT of a compound */
- sqlite3 *db; /* Database connection */
+ tdsqlite3 *db; /* Database connection */
assert( p!=0 );
@@ -92439,16 +101889,16 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
pParse = pWalker->pParse;
db = pParse->db;
- /* Normally sqlite3SelectExpand() will be called first and will have
+ /* Normally tdsqlite3SelectExpand() will be called first and will have
** already expanded this SELECT. However, if this is a subquery within
- ** an expression, sqlite3ResolveExprNames() will be called without a
- ** prior call to sqlite3SelectExpand(). When that happens, let
- ** sqlite3SelectPrep() do all of the processing for this SELECT.
- ** sqlite3SelectPrep() will invoke both sqlite3SelectExpand() and
+ ** an expression, tdsqlite3ResolveExprNames() will be called without a
+ ** prior call to tdsqlite3SelectExpand(). When that happens, let
+ ** tdsqlite3SelectPrep() do all of the processing for this SELECT.
+ ** tdsqlite3SelectPrep() will invoke both tdsqlite3SelectExpand() and
** this routine in the correct order.
*/
if( (p->selFlags & SF_Expanded)==0 ){
- sqlite3SelectPrep(pParse, p, pOuterNC);
+ tdsqlite3SelectPrep(pParse, p, pOuterNC);
return (pParse->nErr || db->mallocFailed) ? WRC_Abort : WRC_Prune;
}
@@ -92465,8 +101915,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
*/
memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pParse;
- if( sqlite3ResolveExprNames(&sNC, p->pLimit) ||
- sqlite3ResolveExprNames(&sNC, p->pOffset) ){
+ sNC.pWinSelect = p;
+ if( tdsqlite3ResolveExprNames(&sNC, p->pLimit) ){
return WRC_Abort;
}
@@ -92488,7 +101938,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
*/
for(i=0; i<p->pSrc->nSrc; i++){
struct SrcList_item *pItem = &p->pSrc->a[i];
- if( pItem->pSelect ){
+ if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){
NameContext *pNC; /* Used to iterate name contexts */
int nRef = 0; /* Refcount for pOuterNC and outer contexts */
const char *zSavedContext = pParse->zAuthContext;
@@ -92501,7 +101951,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
for(pNC=pOuterNC; pNC; pNC=pNC->pNext) nRef += pNC->nRef;
if( pItem->zName ) pParse->zAuthContext = pItem->zName;
- sqlite3ResolveSelectNames(pParse, pItem->pSelect, pOuterNC);
+ tdsqlite3ResolveSelectNames(pParse, pItem->pSelect, pOuterNC);
pParse->zAuthContext = zSavedContext;
if( pParse->nErr || db->mallocFailed ) return WRC_Abort;
@@ -92511,15 +101961,16 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
}
}
- /* Set up the local name-context to pass to sqlite3ResolveExprNames() to
+ /* Set up the local name-context to pass to tdsqlite3ResolveExprNames() to
** resolve the result-set expression list.
*/
- sNC.ncFlags = NC_AllowAgg;
+ sNC.ncFlags = NC_AllowAgg|NC_AllowWin;
sNC.pSrcList = p->pSrc;
sNC.pNext = pOuterNC;
/* Resolve names in the result set. */
- if( sqlite3ResolveExprListNames(&sNC, p->pEList) ) return WRC_Abort;
+ if( tdsqlite3ResolveExprListNames(&sNC, p->pEList) ) return WRC_Abort;
+ sNC.ncFlags &= ~NC_AllowWin;
/* If there are no aggregate functions in the result-set, and no GROUP BY
** expression, do not allow aggregates in any of the other expressions.
@@ -92536,7 +101987,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
/* If a HAVING clause is present, then there must be a GROUP BY clause.
*/
if( p->pHaving && !pGroupBy ){
- sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING");
+ tdsqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING");
return WRC_Abort;
}
@@ -92548,15 +101999,17 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
** Minor point: If this is the case, then the expression will be
** re-evaluated for each reference to it.
*/
- sNC.pEList = p->pEList;
- if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort;
- if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort;
+ assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert))==0 );
+ sNC.uNC.pEList = p->pEList;
+ sNC.ncFlags |= NC_UEList;
+ if( tdsqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort;
+ if( tdsqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort;
/* Resolve names in table-valued-function arguments */
for(i=0; i<p->pSrc->nSrc; i++){
struct SrcList_item *pItem = &p->pSrc->a[i];
if( pItem->fg.isTabFunc
- && sqlite3ResolveExprListNames(&sNC, pItem->u1.pFuncArg)
+ && tdsqlite3ResolveExprListNames(&sNC, pItem->u1.pFuncArg)
){
return WRC_Abort;
}
@@ -92566,7 +102019,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
** outer queries
*/
sNC.pNext = 0;
- sNC.ncFlags |= NC_AllowAgg;
+ sNC.ncFlags |= NC_AllowAgg|NC_AllowWin;
/* If this is a converted compound query, move the ORDER BY clause from
** the sub-query back to the parent query. At this point each term
@@ -92597,6 +102050,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
if( db->mallocFailed ){
return WRC_Abort;
}
+ sNC.ncFlags &= ~NC_AllowWin;
/* Resolve the GROUP BY clause. At the same time, make sure
** the GROUP BY clause does not contain aggregate functions.
@@ -92609,17 +102063,30 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
}
for(i=0, pItem=pGroupBy->a; i<pGroupBy->nExpr; i++, pItem++){
if( ExprHasProperty(pItem->pExpr, EP_Agg) ){
- sqlite3ErrorMsg(pParse, "aggregate functions are not allowed in "
+ tdsqlite3ErrorMsg(pParse, "aggregate functions are not allowed in "
"the GROUP BY clause");
return WRC_Abort;
}
}
}
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( IN_RENAME_OBJECT ){
+ Window *pWin;
+ for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){
+ if( tdsqlite3ResolveExprListNames(&sNC, pWin->pOrderBy)
+ || tdsqlite3ResolveExprListNames(&sNC, pWin->pPartition)
+ ){
+ return WRC_Abort;
+ }
+ }
+ }
+#endif
+
/* If this is part of a compound SELECT, check that it has the right
** number of expressions in the select list. */
if( p->pNext && p->pEList->nExpr!=p->pNext->pEList->nExpr ){
- sqlite3SelectWrongNumTermsError(pParse, p->pNext);
+ tdsqlite3SelectWrongNumTermsError(pParse, p->pNext);
return WRC_Abort;
}
@@ -92687,59 +102154,53 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
** An error message is left in pParse if anything is amiss. The number
** if errors is returned.
*/
-SQLITE_PRIVATE int sqlite3ResolveExprNames(
+SQLITE_PRIVATE int tdsqlite3ResolveExprNames(
NameContext *pNC, /* Namespace to resolve expressions in. */
Expr *pExpr /* The expression to be analyzed. */
){
- u16 savedHasAgg;
+ int savedHasAgg;
Walker w;
- if( pExpr==0 ) return 0;
-#if SQLITE_MAX_EXPR_DEPTH>0
- {
- Parse *pParse = pNC->pParse;
- if( sqlite3ExprCheckHeight(pParse, pExpr->nHeight+pNC->pParse->nHeight) ){
- return 1;
- }
- pParse->nHeight += pExpr->nHeight;
- }
-#endif
- savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg);
- pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg);
+ if( pExpr==0 ) return SQLITE_OK;
+ savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin);
+ pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin);
w.pParse = pNC->pParse;
w.xExprCallback = resolveExprStep;
w.xSelectCallback = resolveSelectStep;
w.xSelectCallback2 = 0;
- w.walkerDepth = 0;
- w.eCode = 0;
w.u.pNC = pNC;
- sqlite3WalkExpr(&w, pExpr);
#if SQLITE_MAX_EXPR_DEPTH>0
- pNC->pParse->nHeight -= pExpr->nHeight;
-#endif
- if( pNC->nErr>0 || w.pParse->nErr>0 ){
- ExprSetProperty(pExpr, EP_Error);
- }
- if( pNC->ncFlags & NC_HasAgg ){
- ExprSetProperty(pExpr, EP_Agg);
+ w.pParse->nHeight += pExpr->nHeight;
+ if( tdsqlite3ExprCheckHeight(w.pParse, w.pParse->nHeight) ){
+ return SQLITE_ERROR;
}
+#endif
+ tdsqlite3WalkExpr(&w, pExpr);
+#if SQLITE_MAX_EXPR_DEPTH>0
+ w.pParse->nHeight -= pExpr->nHeight;
+#endif
+ assert( EP_Agg==NC_HasAgg );
+ assert( EP_Win==NC_HasWin );
+ testcase( pNC->ncFlags & NC_HasAgg );
+ testcase( pNC->ncFlags & NC_HasWin );
+ ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) );
pNC->ncFlags |= savedHasAgg;
- return ExprHasProperty(pExpr, EP_Error);
+ return pNC->nErr>0 || w.pParse->nErr>0;
}
/*
** Resolve all names for all expression in an expression list. This is
-** just like sqlite3ResolveExprNames() except that it works for an expression
+** just like tdsqlite3ResolveExprNames() except that it works for an expression
** list rather than a single expression.
*/
-SQLITE_PRIVATE int sqlite3ResolveExprListNames(
+SQLITE_PRIVATE int tdsqlite3ResolveExprListNames(
NameContext *pNC, /* Namespace to resolve expressions in. */
ExprList *pList /* The expression list to be analyzed. */
){
int i;
if( pList ){
for(i=0; i<pList->nExpr; i++){
- if( sqlite3ResolveExprNames(pNC, pList->a[i].pExpr) ) return WRC_Abort;
+ if( tdsqlite3ResolveExprNames(pNC, pList->a[i].pExpr) ) return WRC_Abort;
}
}
return WRC_Continue;
@@ -92751,13 +102212,13 @@ SQLITE_PRIVATE int sqlite3ResolveExprListNames(
** subqueries in expressions, and subqueries used as FROM clause
** terms.
**
-** See sqlite3ResolveExprNames() for a description of the kinds of
+** See tdsqlite3ResolveExprNames() for a description of the kinds of
** transformations that occur.
**
** All SELECT statements should have been expanded using
-** sqlite3SelectExpand() prior to invoking this routine.
+** tdsqlite3SelectExpand() prior to invoking this routine.
*/
-SQLITE_PRIVATE void sqlite3ResolveSelectNames(
+SQLITE_PRIVATE void tdsqlite3ResolveSelectNames(
Parse *pParse, /* The parser context */
Select *p, /* The SELECT statement being coded. */
NameContext *pOuterNC /* Name context for parent SELECT statement */
@@ -92765,47 +102226,65 @@ SQLITE_PRIVATE void sqlite3ResolveSelectNames(
Walker w;
assert( p!=0 );
- memset(&w, 0, sizeof(w));
w.xExprCallback = resolveExprStep;
w.xSelectCallback = resolveSelectStep;
+ w.xSelectCallback2 = 0;
w.pParse = pParse;
w.u.pNC = pOuterNC;
- sqlite3WalkSelect(&w, p);
+ tdsqlite3WalkSelect(&w, p);
}
/*
-** Resolve names in expressions that can only reference a single table:
+** Resolve names in expressions that can only reference a single table
+** or which cannot reference any tables at all. Examples:
**
-** * CHECK constraints
-** * WHERE clauses on partial indices
+** "type" flag
+** ------------
+** (1) CHECK constraints NC_IsCheck
+** (2) WHERE clauses on partial indices NC_PartIdx
+** (3) Expressions in indexes on expressions NC_IdxExpr
+** (4) Expression arguments to VACUUM INTO. 0
+** (5) GENERATED ALWAYS as expressions NC_GenCol
**
-** The Expr.iTable value for Expr.op==TK_COLUMN nodes of the expression
-** is set to -1 and the Expr.iColumn value is set to the column number.
+** In all cases except (4), the Expr.iTable value for Expr.op==TK_COLUMN
+** nodes of the expression is set to -1 and the Expr.iColumn value is
+** set to the column number. In case (4), TK_COLUMN nodes cause an error.
**
** Any errors cause an error message to be set in pParse.
*/
-SQLITE_PRIVATE void sqlite3ResolveSelfReference(
- Parse *pParse, /* Parsing context */
- Table *pTab, /* The table being referenced */
- int type, /* NC_IsCheck or NC_PartIdx or NC_IdxExpr */
- Expr *pExpr, /* Expression to resolve. May be NULL. */
- ExprList *pList /* Expression list to resolve. May be NUL. */
+SQLITE_PRIVATE int tdsqlite3ResolveSelfReference(
+ Parse *pParse, /* Parsing context */
+ Table *pTab, /* The table being referenced, or NULL */
+ int type, /* NC_IsCheck, NC_PartIdx, NC_IdxExpr, NC_GenCol, or 0 */
+ Expr *pExpr, /* Expression to resolve. May be NULL. */
+ ExprList *pList /* Expression list to resolve. May be NULL. */
){
SrcList sSrc; /* Fake SrcList for pParse->pNewTable */
NameContext sNC; /* Name context for pParse->pNewTable */
+ int rc;
- assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr );
+ assert( type==0 || pTab!=0 );
+ assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr
+ || type==NC_GenCol || pTab==0 );
memset(&sNC, 0, sizeof(sNC));
memset(&sSrc, 0, sizeof(sSrc));
- sSrc.nSrc = 1;
- sSrc.a[0].zName = pTab->zName;
- sSrc.a[0].pTab = pTab;
- sSrc.a[0].iCursor = -1;
+ if( pTab ){
+ sSrc.nSrc = 1;
+ sSrc.a[0].zName = pTab->zName;
+ sSrc.a[0].pTab = pTab;
+ sSrc.a[0].iCursor = -1;
+ if( pTab->pSchema!=pParse->db->aDb[1].pSchema ){
+ /* Cause EP_FromDDL to be set on TK_FUNCTION nodes of non-TEMP
+ ** schema elements */
+ type |= NC_FromDDL;
+ }
+ }
sNC.pParse = pParse;
sNC.pSrcList = &sSrc;
- sNC.ncFlags = type;
- if( sqlite3ResolveExprNames(&sNC, pExpr) ) return;
- if( pList ) sqlite3ResolveExprListNames(&sNC, pList);
+ sNC.ncFlags = type | NC_IsDDL;
+ if( (rc = tdsqlite3ResolveExprNames(&sNC, pExpr))!=SQLITE_OK ) return rc;
+ if( pList ) rc = tdsqlite3ResolveExprListNames(&sNC, pList);
+ return rc;
}
/************** End of resolve.c *********************************************/
@@ -92833,7 +102312,7 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree);
/*
** Return the affinity character for a single column of a table.
*/
-SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table *pTab, int iCol){
+SQLITE_PRIVATE char tdsqlite3TableColumnAffinity(Table *pTab, int iCol){
assert( iCol<pTab->nCol );
return iCol>=0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER;
}
@@ -92854,32 +102333,38 @@ SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table *pTab, int iCol){
** SELECT a AS b FROM t1 WHERE b;
** SELECT * FROM t1 WHERE (select a from t1);
*/
-SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){
+SQLITE_PRIVATE char tdsqlite3ExprAffinity(Expr *pExpr){
int op;
- pExpr = sqlite3ExprSkipCollate(pExpr);
- if( pExpr->flags & EP_Generic ) return 0;
+ while( ExprHasProperty(pExpr, EP_Skip) ){
+ assert( pExpr->op==TK_COLLATE );
+ pExpr = pExpr->pLeft;
+ assert( pExpr!=0 );
+ }
op = pExpr->op;
if( op==TK_SELECT ){
assert( pExpr->flags&EP_xIsSelect );
- return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr);
+ return tdsqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr);
}
if( op==TK_REGISTER ) op = pExpr->op2;
#ifndef SQLITE_OMIT_CAST
if( op==TK_CAST ){
assert( !ExprHasProperty(pExpr, EP_IntValue) );
- return sqlite3AffinityType(pExpr->u.zToken, 0);
+ return tdsqlite3AffinityType(pExpr->u.zToken, 0);
}
#endif
- if( op==TK_AGG_COLUMN || op==TK_COLUMN ){
- return sqlite3TableColumnAffinity(pExpr->pTab, pExpr->iColumn);
+ if( (op==TK_AGG_COLUMN || op==TK_COLUMN) && pExpr->y.pTab ){
+ return tdsqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn);
}
if( op==TK_SELECT_COLUMN ){
assert( pExpr->pLeft->flags&EP_xIsSelect );
- return sqlite3ExprAffinity(
+ return tdsqlite3ExprAffinity(
pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr
);
}
- return pExpr->affinity;
+ if( op==TK_VECTOR ){
+ return tdsqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr);
+ }
+ return pExpr->affExpr;
}
/*
@@ -92890,14 +102375,14 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){
** If a memory allocation error occurs, that fact is recorded in pParse->db
** and the pExpr parameter is returned unchanged.
*/
-SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(
+SQLITE_PRIVATE Expr *tdsqlite3ExprAddCollateToken(
Parse *pParse, /* Parsing context */
Expr *pExpr, /* Add the "COLLATE" clause to this expression */
const Token *pCollName, /* Name of collating sequence */
int dequote /* True to dequote pCollName */
){
if( pCollName->n>0 ){
- Expr *pNew = sqlite3ExprAlloc(pParse->db, TK_COLLATE, pCollName, dequote);
+ Expr *pNew = tdsqlite3ExprAlloc(pParse->db, TK_COLLATE, pCollName, dequote);
if( pNew ){
pNew->pLeft = pExpr;
pNew->flags |= EP_Collate|EP_Skip;
@@ -92906,19 +102391,31 @@ SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(
}
return pExpr;
}
-SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){
+SQLITE_PRIVATE Expr *tdsqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){
Token s;
assert( zC!=0 );
- sqlite3TokenInit(&s, (char*)zC);
- return sqlite3ExprAddCollateToken(pParse, pExpr, &s, 0);
+ tdsqlite3TokenInit(&s, (char*)zC);
+ return tdsqlite3ExprAddCollateToken(pParse, pExpr, &s, 0);
}
/*
-** Skip over any TK_COLLATE operators and any unlikely()
-** or likelihood() function at the root of an expression.
+** Skip over any TK_COLLATE operators.
*/
-SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr *pExpr){
+SQLITE_PRIVATE Expr *tdsqlite3ExprSkipCollate(Expr *pExpr){
while( pExpr && ExprHasProperty(pExpr, EP_Skip) ){
+ assert( pExpr->op==TK_COLLATE );
+ pExpr = pExpr->pLeft;
+ }
+ return pExpr;
+}
+
+/*
+** Skip over any TK_COLLATE operators and/or any unlikely()
+** or likelihood() or likely() functions at the root of an
+** expression.
+*/
+SQLITE_PRIVATE Expr *tdsqlite3ExprSkipCollateAndLikely(Expr *pExpr){
+ while( pExpr && ExprHasProperty(pExpr, EP_Skip|EP_Unlikely) ){
if( ExprHasProperty(pExpr, EP_Unlikely) ){
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
assert( pExpr->x.pList->nExpr>0 );
@@ -92936,39 +102433,47 @@ SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr *pExpr){
** Return the collation sequence for the expression pExpr. If
** there is no defined collating sequence, return NULL.
**
+** See also: tdsqlite3ExprNNCollSeq()
+**
+** The tdsqlite3ExprNNCollSeq() works the same exact that it returns the
+** default collation if pExpr has no defined collation.
+**
** The collating sequence might be determined by a COLLATE operator
** or by the presence of a column with a defined collating sequence.
** COLLATE operators take first precedence. Left operands take
** precedence over right operands.
*/
-SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
- sqlite3 *db = pParse->db;
+SQLITE_PRIVATE CollSeq *tdsqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
+ tdsqlite3 *db = pParse->db;
CollSeq *pColl = 0;
Expr *p = pExpr;
while( p ){
int op = p->op;
- if( p->flags & EP_Generic ) break;
- if( op==TK_CAST || op==TK_UPLUS ){
- p = p->pLeft;
- continue;
- }
- if( op==TK_COLLATE || (op==TK_REGISTER && p->op2==TK_COLLATE) ){
- pColl = sqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken);
- break;
- }
- if( (op==TK_AGG_COLUMN || op==TK_COLUMN
- || op==TK_REGISTER || op==TK_TRIGGER)
- && p->pTab!=0
+ if( op==TK_REGISTER ) op = p->op2;
+ if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER)
+ && p->y.pTab!=0
){
- /* op==TK_REGISTER && p->pTab!=0 happens when pExpr was originally
+ /* op==TK_REGISTER && p->y.pTab!=0 happens when pExpr was originally
** a TK_COLUMN but was previously evaluated and cached in a register */
int j = p->iColumn;
if( j>=0 ){
- const char *zColl = p->pTab->aCol[j].zColl;
- pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0);
+ const char *zColl = p->y.pTab->aCol[j].zColl;
+ pColl = tdsqlite3FindCollSeq(db, ENC(db), zColl, 0);
}
break;
}
+ if( op==TK_CAST || op==TK_UPLUS ){
+ p = p->pLeft;
+ continue;
+ }
+ if( op==TK_VECTOR ){
+ p = p->x.pList->a[0].pExpr;
+ continue;
+ }
+ if( op==TK_COLLATE ){
+ pColl = tdsqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken);
+ break;
+ }
if( p->flags & EP_Collate ){
if( p->pLeft && (p->pLeft->flags & EP_Collate)!=0 ){
p = p->pLeft;
@@ -92976,12 +102481,12 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
Expr *pNext = p->pRight;
/* The Expr.x union is never used at the same time as Expr.pRight */
assert( p->x.pList==0 || p->pRight==0 );
- /* p->flags holds EP_Collate and p->pLeft->flags does not. And
- ** p->x.pSelect cannot. So if p->x.pLeft exists, it must hold at
- ** least one EP_Collate. Thus the following two ALWAYS. */
- if( p->x.pList!=0 && ALWAYS(!ExprHasProperty(p, EP_xIsSelect)) ){
+ if( p->x.pList!=0
+ && !db->mallocFailed
+ && ALWAYS(!ExprHasProperty(p, EP_xIsSelect))
+ ){
int i;
- for(i=0; ALWAYS(i<p->x.pList->nExpr); i++){
+ for(i=0; i<p->x.pList->nExpr; i++){
if( ExprHasProperty(p->x.pList->a[i].pExpr, EP_Collate) ){
pNext = p->x.pList->a[i].pExpr;
break;
@@ -92994,37 +102499,58 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
break;
}
}
- if( sqlite3CheckCollSeq(pParse, pColl) ){
+ if( tdsqlite3CheckCollSeq(pParse, pColl) ){
pColl = 0;
}
return pColl;
}
/*
+** Return the collation sequence for the expression pExpr. If
+** there is no defined collating sequence, return a pointer to the
+** defautl collation sequence.
+**
+** See also: tdsqlite3ExprCollSeq()
+**
+** The tdsqlite3ExprCollSeq() routine works the same except that it
+** returns NULL if there is no defined collation.
+*/
+SQLITE_PRIVATE CollSeq *tdsqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr){
+ CollSeq *p = tdsqlite3ExprCollSeq(pParse, pExpr);
+ if( p==0 ) p = pParse->db->pDfltColl;
+ assert( p!=0 );
+ return p;
+}
+
+/*
+** Return TRUE if the two expressions have equivalent collating sequences.
+*/
+SQLITE_PRIVATE int tdsqlite3ExprCollSeqMatch(Parse *pParse, Expr *pE1, Expr *pE2){
+ CollSeq *pColl1 = tdsqlite3ExprNNCollSeq(pParse, pE1);
+ CollSeq *pColl2 = tdsqlite3ExprNNCollSeq(pParse, pE2);
+ return tdsqlite3StrICmp(pColl1->zName, pColl2->zName)==0;
+}
+
+/*
** pExpr is an operand of a comparison operator. aff2 is the
** type affinity of the other operand. This routine returns the
** type affinity that should be used for the comparison operator.
*/
-SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2){
- char aff1 = sqlite3ExprAffinity(pExpr);
- if( aff1 && aff2 ){
+SQLITE_PRIVATE char tdsqlite3CompareAffinity(Expr *pExpr, char aff2){
+ char aff1 = tdsqlite3ExprAffinity(pExpr);
+ if( aff1>SQLITE_AFF_NONE && aff2>SQLITE_AFF_NONE ){
/* Both sides of the comparison are columns. If one has numeric
** affinity, use that. Otherwise use no affinity.
*/
- if( sqlite3IsNumericAffinity(aff1) || sqlite3IsNumericAffinity(aff2) ){
+ if( tdsqlite3IsNumericAffinity(aff1) || tdsqlite3IsNumericAffinity(aff2) ){
return SQLITE_AFF_NUMERIC;
}else{
return SQLITE_AFF_BLOB;
}
- }else if( !aff1 && !aff2 ){
- /* Neither side of the comparison is a column. Compare the
- ** results directly.
- */
- return SQLITE_AFF_BLOB;
}else{
/* One side is a column, the other is not. Use the columns affinity. */
- assert( aff1==0 || aff2==0 );
- return (aff1 + aff2);
+ assert( aff1<=SQLITE_AFF_NONE || aff2<=SQLITE_AFF_NONE );
+ return (aff1<=SQLITE_AFF_NONE ? aff2 : aff1) | SQLITE_AFF_NONE;
}
}
@@ -93038,12 +102564,12 @@ static char comparisonAffinity(Expr *pExpr){
pExpr->op==TK_GT || pExpr->op==TK_GE || pExpr->op==TK_LE ||
pExpr->op==TK_NE || pExpr->op==TK_IS || pExpr->op==TK_ISNOT );
assert( pExpr->pLeft );
- aff = sqlite3ExprAffinity(pExpr->pLeft);
+ aff = tdsqlite3ExprAffinity(pExpr->pLeft);
if( pExpr->pRight ){
- aff = sqlite3CompareAffinity(pExpr->pRight, aff);
+ aff = tdsqlite3CompareAffinity(pExpr->pRight, aff);
}else if( ExprHasProperty(pExpr, EP_xIsSelect) ){
- aff = sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr, aff);
- }else if( NEVER(aff==0) ){
+ aff = tdsqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr, aff);
+ }else if( aff==0 ){
aff = SQLITE_AFF_BLOB;
}
return aff;
@@ -93055,16 +102581,15 @@ static char comparisonAffinity(Expr *pExpr){
** if the index with affinity idx_affinity may be used to implement
** the comparison in pExpr.
*/
-SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){
+SQLITE_PRIVATE int tdsqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){
char aff = comparisonAffinity(pExpr);
- switch( aff ){
- case SQLITE_AFF_BLOB:
- return 1;
- case SQLITE_AFF_TEXT:
- return idx_affinity==SQLITE_AFF_TEXT;
- default:
- return sqlite3IsNumericAffinity(idx_affinity);
+ if( aff<SQLITE_AFF_TEXT ){
+ return 1;
+ }
+ if( aff==SQLITE_AFF_TEXT ){
+ return idx_affinity==SQLITE_AFF_TEXT;
}
+ return tdsqlite3IsNumericAffinity(idx_affinity);
}
/*
@@ -93072,8 +102597,8 @@ SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){
** opcode (OP_Eq, OP_Ge etc.) used to compare pExpr1 and pExpr2.
*/
static u8 binaryCompareP5(Expr *pExpr1, Expr *pExpr2, int jumpIfNull){
- u8 aff = (char)sqlite3ExprAffinity(pExpr2);
- aff = (u8)sqlite3CompareAffinity(pExpr1, aff) | (u8)jumpIfNull;
+ u8 aff = (char)tdsqlite3ExprAffinity(pExpr2);
+ aff = (u8)tdsqlite3CompareAffinity(pExpr1, aff) | (u8)jumpIfNull;
return aff;
}
@@ -93089,7 +102614,7 @@ static u8 binaryCompareP5(Expr *pExpr1, Expr *pExpr2, int jumpIfNull){
** Argument pRight (but not pLeft) may be a null pointer. In this case,
** it is not considered.
*/
-SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(
+SQLITE_PRIVATE CollSeq *tdsqlite3BinaryCompareCollSeq(
Parse *pParse,
Expr *pLeft,
Expr *pRight
@@ -93097,18 +102622,34 @@ SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(
CollSeq *pColl;
assert( pLeft );
if( pLeft->flags & EP_Collate ){
- pColl = sqlite3ExprCollSeq(pParse, pLeft);
+ pColl = tdsqlite3ExprCollSeq(pParse, pLeft);
}else if( pRight && (pRight->flags & EP_Collate)!=0 ){
- pColl = sqlite3ExprCollSeq(pParse, pRight);
+ pColl = tdsqlite3ExprCollSeq(pParse, pRight);
}else{
- pColl = sqlite3ExprCollSeq(pParse, pLeft);
+ pColl = tdsqlite3ExprCollSeq(pParse, pLeft);
if( !pColl ){
- pColl = sqlite3ExprCollSeq(pParse, pRight);
+ pColl = tdsqlite3ExprCollSeq(pParse, pRight);
}
}
return pColl;
}
+/* Expresssion p is a comparison operator. Return a collation sequence
+** appropriate for the comparison operator.
+**
+** This is normally just a wrapper around tdsqlite3BinaryCompareCollSeq().
+** However, if the OP_Commuted flag is set, then the order of the operands
+** is reversed in the tdsqlite3BinaryCompareCollSeq() call so that the
+** correct collating sequence is found.
+*/
+SQLITE_PRIVATE CollSeq *tdsqlite3ExprCompareCollSeq(Parse *pParse, Expr *p){
+ if( ExprHasProperty(p, EP_Commuted) ){
+ return tdsqlite3BinaryCompareCollSeq(pParse, p->pRight, p->pLeft);
+ }else{
+ return tdsqlite3BinaryCompareCollSeq(pParse, p->pLeft, p->pRight);
+ }
+}
+
/*
** Generate code for a comparison operator.
*/
@@ -93119,17 +102660,23 @@ static int codeCompare(
int opcode, /* The comparison opcode */
int in1, int in2, /* Register holding operands */
int dest, /* Jump here if true. */
- int jumpIfNull /* If true, jump if either operand is NULL */
+ int jumpIfNull, /* If true, jump if either operand is NULL */
+ int isCommuted /* The comparison has been commuted */
){
int p5;
int addr;
CollSeq *p4;
- p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
+ if( pParse->nErr ) return 0;
+ if( isCommuted ){
+ p4 = tdsqlite3BinaryCompareCollSeq(pParse, pRight, pLeft);
+ }else{
+ p4 = tdsqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
+ }
p5 = binaryCompareP5(pLeft, pRight, jumpIfNull);
- addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
+ addr = tdsqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
(void*)p4, P4_COLLSEQ);
- sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5);
+ tdsqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5);
return addr;
}
@@ -93142,8 +102689,8 @@ static int codeCompare(
** But a TK_SELECT might be either a vector or a scalar. It is only
** considered a vector if it has two or more result columns.
*/
-SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr){
- return sqlite3ExprVectorSize(pExpr)>1;
+SQLITE_PRIVATE int tdsqlite3ExprIsVector(Expr *pExpr){
+ return tdsqlite3ExprVectorSize(pExpr)>1;
}
/*
@@ -93152,7 +102699,7 @@ SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr){
** is a sub-select, return the number of columns in the sub-select. For
** any other type of expression, return 1.
*/
-SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){
+SQLITE_PRIVATE int tdsqlite3ExprVectorSize(Expr *pExpr){
u8 op = pExpr->op;
if( op==TK_REGISTER ) op = pExpr->op2;
if( op==TK_VECTOR ){
@@ -93164,7 +102711,6 @@ SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){
}
}
-#ifndef SQLITE_OMIT_SUBQUERY
/*
** Return a pointer to a subexpression of pVector that is the i-th
** column of the vector (numbered starting with 0). The caller must
@@ -93180,9 +102726,9 @@ SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){
** not be ready for evaluation because the table cursor has not yet
** been positioned.
*/
-SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){
- assert( i<sqlite3ExprVectorSize(pVector) );
- if( sqlite3ExprIsVector(pVector) ){
+SQLITE_PRIVATE Expr *tdsqlite3VectorFieldSubexpr(Expr *pVector, int i){
+ assert( i<tdsqlite3ExprVectorSize(pVector) );
+ if( tdsqlite3ExprIsVector(pVector) ){
assert( pVector->op2==0 || pVector->op==TK_REGISTER );
if( pVector->op==TK_SELECT || pVector->op2==TK_SELECT ){
return pVector->x.pSelect->pEList->a[i].pExpr;
@@ -93192,16 +102738,14 @@ SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){
}
return pVector;
}
-#endif /* !defined(SQLITE_OMIT_SUBQUERY) */
-#ifndef SQLITE_OMIT_SUBQUERY
/*
** Compute and return a new Expr object which when passed to
-** sqlite3ExprCode() will generate all necessary code to compute
+** tdsqlite3ExprCode() will generate all necessary code to compute
** the iField-th column of the vector expression pVector.
**
** It is ok for pVector to be a scalar (as long as iField==0).
-** In that case, this routine works like sqlite3ExprDup().
+** In that case, this routine works like tdsqlite3ExprDup().
**
** The caller owns the returned Expr object and is responsible for
** ensuring that the returned value eventually gets freed.
@@ -93216,7 +102760,7 @@ SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){
** the returned Expr object is to attach the pVector to the pRight field
** of the returned TK_SELECT_COLUMN Expr object.
*/
-SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(
+SQLITE_PRIVATE Expr *tdsqlite3ExprForVectorField(
Parse *pParse, /* Parsing context */
Expr *pVector, /* The vector. List of expressions or a sub-SELECT */
int iField /* Which column of the vector to return */
@@ -93226,20 +102770,21 @@ SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(
assert( pVector->flags & EP_xIsSelect );
/* The TK_SELECT_COLUMN Expr node:
**
- ** pLeft: pVector containing TK_SELECT
+ ** pLeft: pVector containing TK_SELECT. Not deleted.
** pRight: not used. But recursively deleted.
** iColumn: Index of a column in pVector
+ ** iTable: 0 or the number of columns on the LHS of an assignment
** pLeft->iTable: First in an array of register holding result, or 0
** if the result is not yet computed.
**
- ** sqlite3ExprDelete() specifically skips the recursive delete of
+ ** tdsqlite3ExprDelete() specifically skips the recursive delete of
** pLeft on TK_SELECT_COLUMN nodes. But pRight is followed, so pVector
** can be attached to pRight to cause this node to take ownership of
** pVector. Typically there will be multiple TK_SELECT_COLUMN nodes
** with the same pLeft pointer to the pVector, but only one of them
** will own the pVector.
*/
- pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0, 0);
+ pRet = tdsqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0);
if( pRet ){
pRet->iColumn = iField;
pRet->pLeft = pVector;
@@ -93247,11 +102792,11 @@ SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(
assert( pRet==0 || pRet->iTable==0 );
}else{
if( pVector->op==TK_VECTOR ) pVector = pVector->x.pList->a[iField].pExpr;
- pRet = sqlite3ExprDup(pParse->db, pVector, 0);
+ pRet = tdsqlite3ExprDup(pParse->db, pVector, 0);
+ tdsqlite3RenameTokenRemap(pParse, pRet, pVector);
}
return pRet;
}
-#endif /* !define(SQLITE_OMIT_SUBQUERY) */
/*
** If expression pExpr is of type TK_SELECT, generate code to evaluate
@@ -93265,7 +102810,7 @@ static int exprCodeSubselect(Parse *pParse, Expr *pExpr){
int reg = 0;
#ifndef SQLITE_OMIT_SUBQUERY
if( pExpr->op==TK_SELECT ){
- reg = sqlite3CodeSubselect(pParse, pExpr, 0, 0);
+ reg = tdsqlite3CodeSubselect(pParse, pExpr);
}
#endif
return reg;
@@ -93300,7 +102845,7 @@ static int exprVectorRegister(
u8 op = pVector->op;
assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT );
if( op==TK_REGISTER ){
- *ppExpr = sqlite3VectorFieldSubexpr(pVector, iField);
+ *ppExpr = tdsqlite3VectorFieldSubexpr(pVector, iField);
return pVector->iTable+iField;
}
if( op==TK_SELECT ){
@@ -93308,7 +102853,7 @@ static int exprVectorRegister(
return regSelect+iField;
}
*ppExpr = pVector->x.pList->a[iField].pExpr;
- return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree);
+ return tdsqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree);
}
/*
@@ -93332,14 +102877,19 @@ static void codeVectorCompare(
Vdbe *v = pParse->pVdbe;
Expr *pLeft = pExpr->pLeft;
Expr *pRight = pExpr->pRight;
- int nLeft = sqlite3ExprVectorSize(pLeft);
+ int nLeft = tdsqlite3ExprVectorSize(pLeft);
int i;
int regLeft = 0;
int regRight = 0;
u8 opx = op;
- int addrDone = sqlite3VdbeMakeLabel(v);
+ int addrDone = tdsqlite3VdbeMakeLabel(pParse);
+ int isCommuted = ExprHasProperty(pExpr,EP_Commuted);
- assert( nLeft==sqlite3ExprVectorSize(pRight) );
+ if( pParse->nErr ) return;
+ if( nLeft!=tdsqlite3ExprVectorSize(pRight) ){
+ tdsqlite3ErrorMsg(pParse, "row value misused");
+ return;
+ }
assert( pExpr->op==TK_EQ || pExpr->op==TK_NE
|| pExpr->op==TK_IS || pExpr->op==TK_ISNOT
|| pExpr->op==TK_LT || pExpr->op==TK_GT
@@ -93362,31 +102912,29 @@ static void codeVectorCompare(
Expr *pL, *pR;
int r1, r2;
assert( i>=0 && i<nLeft );
- if( i>0 ) sqlite3ExprCachePush(pParse);
r1 = exprVectorRegister(pParse, pLeft, i, regLeft, &pL, &regFree1);
r2 = exprVectorRegister(pParse, pRight, i, regRight, &pR, &regFree2);
- codeCompare(pParse, pL, pR, opx, r1, r2, dest, p5);
+ codeCompare(pParse, pL, pR, opx, r1, r2, dest, p5, isCommuted);
testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le);
testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt);
testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge);
testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq);
testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne);
- sqlite3ReleaseTempReg(pParse, regFree1);
- sqlite3ReleaseTempReg(pParse, regFree2);
- if( i>0 ) sqlite3ExprCachePop(pParse);
+ tdsqlite3ReleaseTempReg(pParse, regFree1);
+ tdsqlite3ReleaseTempReg(pParse, regFree2);
if( i==nLeft-1 ){
break;
}
if( opx==TK_EQ ){
- sqlite3VdbeAddOp2(v, OP_IfNot, dest, addrDone); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_IfNot, dest, addrDone); VdbeCoverage(v);
p5 |= SQLITE_KEEPNULL;
}else if( opx==TK_NE ){
- sqlite3VdbeAddOp2(v, OP_If, dest, addrDone); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_If, dest, addrDone); VdbeCoverage(v);
p5 |= SQLITE_KEEPNULL;
}else{
assert( op==TK_LT || op==TK_GT || op==TK_LE || op==TK_GE );
- sqlite3VdbeAddOp2(v, OP_ElseNotEq, 0, addrDone);
+ tdsqlite3VdbeAddOp2(v, OP_ElseNotEq, 0, addrDone);
VdbeCoverageIf(v, op==TK_LT);
VdbeCoverageIf(v, op==TK_GT);
VdbeCoverageIf(v, op==TK_LE);
@@ -93394,7 +102942,7 @@ static void codeVectorCompare(
if( i==nLeft-2 ) opx = op;
}
}
- sqlite3VdbeResolveLabel(v, addrDone);
+ tdsqlite3VdbeResolveLabel(v, addrDone);
}
#if SQLITE_MAX_EXPR_DEPTH>0
@@ -93403,11 +102951,11 @@ static void codeVectorCompare(
** expression depth allowed. If it is not, leave an error message in
** pParse.
*/
-SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse *pParse, int nHeight){
+SQLITE_PRIVATE int tdsqlite3ExprCheckHeight(Parse *pParse, int nHeight){
int rc = SQLITE_OK;
int mxHeight = pParse->db->aLimit[SQLITE_LIMIT_EXPR_DEPTH];
if( nHeight>mxHeight ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"Expression tree is too large (maximum depth %d)", mxHeight
);
rc = SQLITE_ERROR;
@@ -93439,16 +102987,15 @@ static void heightOfExprList(ExprList *p, int *pnHeight){
}
}
}
-static void heightOfSelect(Select *p, int *pnHeight){
- if( p ){
+static void heightOfSelect(Select *pSelect, int *pnHeight){
+ Select *p;
+ for(p=pSelect; p; p=p->pPrior){
heightOfExpr(p->pWhere, pnHeight);
heightOfExpr(p->pHaving, pnHeight);
heightOfExpr(p->pLimit, pnHeight);
- heightOfExpr(p->pOffset, pnHeight);
heightOfExprList(p->pEList, pnHeight);
heightOfExprList(p->pGroupBy, pnHeight);
heightOfExprList(p->pOrderBy, pnHeight);
- heightOfSelect(p->pPrior, pnHeight);
}
}
@@ -93470,7 +103017,7 @@ static void exprSetHeight(Expr *p){
heightOfSelect(p->x.pSelect, &nHeight);
}else if( p->x.pList ){
heightOfExprList(p->x.pList, &nHeight);
- p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList);
+ p->flags |= EP_Propagate & tdsqlite3ExprListFlags(p->x.pList);
}
p->nHeight = nHeight + 1;
}
@@ -93483,17 +103030,17 @@ static void exprSetHeight(Expr *p){
** Also propagate all EP_Propagate flags from the Expr.x.pList into
** Expr.flags.
*/
-SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){
+SQLITE_PRIVATE void tdsqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){
if( pParse->nErr ) return;
exprSetHeight(p);
- sqlite3ExprCheckHeight(pParse, p->nHeight);
+ tdsqlite3ExprCheckHeight(pParse, p->nHeight);
}
/*
** Return the maximum height of any expression tree referenced
** by the select statement passed as an argument.
*/
-SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){
+SQLITE_PRIVATE int tdsqlite3SelectExprHeight(Select *p){
int nHeight = 0;
heightOfSelect(p, &nHeight);
return nHeight;
@@ -93503,9 +103050,9 @@ SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){
** Propagate all EP_Propagate flags from the Expr.x.pList into
** Expr.flags.
*/
-SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){
+SQLITE_PRIVATE void tdsqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){
if( p && p->x.pList && !ExprHasProperty(p, EP_xIsSelect) ){
- p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList);
+ p->flags |= EP_Propagate & tdsqlite3ExprListFlags(p->x.pList);
}
}
#define exprSetHeight(y)
@@ -93516,7 +103063,7 @@ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){
**
** Construct a new expression node and return a pointer to it. Memory
** for this node and for the pToken argument is a single allocation
-** obtained from sqlite3DbMalloc(). The calling function
+** obtained from tdsqlite3DbMalloc(). The calling function
** is responsible for making sure the node eventually gets freed.
**
** If dequote is true, then the token (if it exists) is dequoted.
@@ -93531,8 +103078,8 @@ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){
** into u.iValue and the EP_IntValue flag is set. No extra storage
** is allocated to hold the integer text and the dequote flag is ignored.
*/
-SQLITE_PRIVATE Expr *sqlite3ExprAlloc(
- sqlite3 *db, /* Handle for sqlite3DbMallocRawNN() */
+SQLITE_PRIVATE Expr *tdsqlite3ExprAlloc(
+ tdsqlite3 *db, /* Handle for tdsqlite3DbMallocRawNN() */
int op, /* Expression opcode */
const Token *pToken, /* Token argument. Might be NULL */
int dequote /* True to dequote */
@@ -93544,28 +103091,27 @@ SQLITE_PRIVATE Expr *sqlite3ExprAlloc(
assert( db!=0 );
if( pToken ){
if( op!=TK_INTEGER || pToken->z==0
- || sqlite3GetInt32(pToken->z, &iValue)==0 ){
+ || tdsqlite3GetInt32(pToken->z, &iValue)==0 ){
nExtra = pToken->n+1;
assert( iValue>=0 );
}
}
- pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)+nExtra);
+ pNew = tdsqlite3DbMallocRawNN(db, sizeof(Expr)+nExtra);
if( pNew ){
memset(pNew, 0, sizeof(Expr));
pNew->op = (u8)op;
pNew->iAgg = -1;
if( pToken ){
if( nExtra==0 ){
- pNew->flags |= EP_IntValue;
+ pNew->flags |= EP_IntValue|EP_Leaf|(iValue?EP_IsTrue:EP_IsFalse);
pNew->u.iValue = iValue;
}else{
pNew->u.zToken = (char*)&pNew[1];
assert( pToken->z!=0 || pToken->n==0 );
if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n);
pNew->u.zToken[pToken->n] = 0;
- if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){
- if( pNew->u.zToken[0]=='"' ) pNew->flags |= EP_DblQuoted;
- sqlite3Dequote(pNew->u.zToken);
+ if( dequote && tdsqlite3Isquote(pNew->u.zToken[0]) ){
+ tdsqlite3DequoteExpr(pNew);
}
}
}
@@ -93580,15 +103126,15 @@ SQLITE_PRIVATE Expr *sqlite3ExprAlloc(
** Allocate a new expression node from a zero-terminated token that has
** already been dequoted.
*/
-SQLITE_PRIVATE Expr *sqlite3Expr(
- sqlite3 *db, /* Handle for sqlite3DbMallocZero() (may be null) */
+SQLITE_PRIVATE Expr *tdsqlite3Expr(
+ tdsqlite3 *db, /* Handle for tdsqlite3DbMallocZero() (may be null) */
int op, /* Expression opcode */
const char *zToken /* Token argument. Might be NULL */
){
Token x;
x.z = zToken;
- x.n = zToken ? sqlite3Strlen30(zToken) : 0;
- return sqlite3ExprAlloc(db, op, &x, 0);
+ x.n = tdsqlite3Strlen30(zToken);
+ return tdsqlite3ExprAlloc(db, op, &x, 0);
}
/*
@@ -93597,16 +103143,16 @@ SQLITE_PRIVATE Expr *sqlite3Expr(
** If pRoot==NULL that means that a memory allocation error has occurred.
** In that case, delete the subtrees pLeft and pRight.
*/
-SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(
- sqlite3 *db,
+SQLITE_PRIVATE void tdsqlite3ExprAttachSubtrees(
+ tdsqlite3 *db,
Expr *pRoot,
Expr *pLeft,
Expr *pRight
){
if( pRoot==0 ){
assert( db->mallocFailed );
- sqlite3ExprDelete(db, pLeft);
- sqlite3ExprDelete(db, pRight);
+ tdsqlite3ExprDelete(db, pLeft);
+ tdsqlite3ExprDelete(db, pRight);
}else{
if( pRight ){
pRoot->pRight = pRight;
@@ -93627,23 +103173,23 @@ SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(
** Expr node. Or, if an OOM error occurs, set pParse->db->mallocFailed,
** free the subtrees and return NULL.
*/
-SQLITE_PRIVATE Expr *sqlite3PExpr(
+SQLITE_PRIVATE Expr *tdsqlite3PExpr(
Parse *pParse, /* Parsing context */
int op, /* Expression opcode */
Expr *pLeft, /* Left operand */
- Expr *pRight, /* Right operand */
- const Token *pToken /* Argument token */
+ Expr *pRight /* Right operand */
){
Expr *p;
- if( op==TK_AND && pParse->nErr==0 ){
- /* Take advantage of short-circuit false optimization for AND */
- p = sqlite3ExprAnd(pParse->db, pLeft, pRight);
+ p = tdsqlite3DbMallocRawNN(pParse->db, sizeof(Expr));
+ if( p ){
+ memset(p, 0, sizeof(Expr));
+ p->op = op & 0xff;
+ p->iAgg = -1;
+ tdsqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight);
+ tdsqlite3ExprCheckHeight(pParse, p->nHeight);
}else{
- p = sqlite3ExprAlloc(pParse->db, op & TKFLG_MASK, pToken, 1);
- sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight);
- }
- if( p ) {
- sqlite3ExprCheckHeight(pParse, p->nHeight);
+ tdsqlite3ExprDelete(pParse->db, pLeft);
+ tdsqlite3ExprDelete(pParse->db, pRight);
}
return p;
}
@@ -93652,46 +103198,19 @@ SQLITE_PRIVATE Expr *sqlite3PExpr(
** Add pSelect to the Expr.x.pSelect field. Or, if pExpr is NULL (due
** do a memory allocation failure) then delete the pSelect object.
*/
-SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse *pParse, Expr *pExpr, Select *pSelect){
+SQLITE_PRIVATE void tdsqlite3PExprAddSelect(Parse *pParse, Expr *pExpr, Select *pSelect){
if( pExpr ){
pExpr->x.pSelect = pSelect;
ExprSetProperty(pExpr, EP_xIsSelect|EP_Subquery);
- sqlite3ExprSetHeightAndFlags(pParse, pExpr);
+ tdsqlite3ExprSetHeightAndFlags(pParse, pExpr);
}else{
assert( pParse->db->mallocFailed );
- sqlite3SelectDelete(pParse->db, pSelect);
+ tdsqlite3SelectDelete(pParse->db, pSelect);
}
}
/*
-** If the expression is always either TRUE or FALSE (respectively),
-** then return 1. If one cannot determine the truth value of the
-** expression at compile-time return 0.
-**
-** This is an optimization. If is OK to return 0 here even if
-** the expression really is always false or false (a false negative).
-** But it is a bug to return 1 if the expression might have different
-** boolean values in different circumstances (a false positive.)
-**
-** Note that if the expression is part of conditional for a
-** LEFT JOIN, then we cannot determine at compile-time whether or not
-** is it true or false, so always return 0.
-*/
-static int exprAlwaysTrue(Expr *p){
- int v = 0;
- if( ExprHasProperty(p, EP_FromJoin) ) return 0;
- if( !sqlite3ExprIsInteger(p, &v) ) return 0;
- return v!=0;
-}
-static int exprAlwaysFalse(Expr *p){
- int v = 0;
- if( ExprHasProperty(p, EP_FromJoin) ) return 0;
- if( !sqlite3ExprIsInteger(p, &v) ) return 0;
- return v==0;
-}
-
-/*
** Join two expressions using an AND operator. If either expression is
** NULL, then just return the other expression.
**
@@ -93699,19 +103218,20 @@ static int exprAlwaysFalse(Expr *p){
** of returning an AND expression, just return a constant expression with
** a value of false.
*/
-SQLITE_PRIVATE Expr *sqlite3ExprAnd(sqlite3 *db, Expr *pLeft, Expr *pRight){
- if( pLeft==0 ){
+SQLITE_PRIVATE Expr *tdsqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){
+ tdsqlite3 *db = pParse->db;
+ if( pLeft==0 ){
return pRight;
}else if( pRight==0 ){
return pLeft;
- }else if( exprAlwaysFalse(pLeft) || exprAlwaysFalse(pRight) ){
- sqlite3ExprDelete(db, pLeft);
- sqlite3ExprDelete(db, pRight);
- return sqlite3ExprAlloc(db, TK_INTEGER, &sqlite3IntTokens[0], 0);
+ }else if( (ExprAlwaysFalse(pLeft) || ExprAlwaysFalse(pRight))
+ && !IN_RENAME_OBJECT
+ ){
+ tdsqlite3ExprDelete(db, pLeft);
+ tdsqlite3ExprDelete(db, pRight);
+ return tdsqlite3Expr(db, TK_INTEGER, "0");
}else{
- Expr *pNew = sqlite3ExprAlloc(db, TK_AND, 0, 0);
- sqlite3ExprAttachSubtrees(db, pNew, pLeft, pRight);
- return pNew;
+ return tdsqlite3PExpr(pParse, TK_AND, pLeft, pRight);
}
}
@@ -93719,22 +103239,66 @@ SQLITE_PRIVATE Expr *sqlite3ExprAnd(sqlite3 *db, Expr *pLeft, Expr *pRight){
** Construct a new expression node for a function with multiple
** arguments.
*/
-SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse *pParse, ExprList *pList, Token *pToken){
+SQLITE_PRIVATE Expr *tdsqlite3ExprFunction(
+ Parse *pParse, /* Parsing context */
+ ExprList *pList, /* Argument list */
+ Token *pToken, /* Name of the function */
+ int eDistinct /* SF_Distinct or SF_ALL or 0 */
+){
Expr *pNew;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
assert( pToken );
- pNew = sqlite3ExprAlloc(db, TK_FUNCTION, pToken, 1);
+ pNew = tdsqlite3ExprAlloc(db, TK_FUNCTION, pToken, 1);
if( pNew==0 ){
- sqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */
+ tdsqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */
return 0;
}
+ if( pList && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){
+ tdsqlite3ErrorMsg(pParse, "too many arguments on function %T", pToken);
+ }
pNew->x.pList = pList;
+ ExprSetProperty(pNew, EP_HasFunc);
assert( !ExprHasProperty(pNew, EP_xIsSelect) );
- sqlite3ExprSetHeightAndFlags(pParse, pNew);
+ tdsqlite3ExprSetHeightAndFlags(pParse, pNew);
+ if( eDistinct==SF_Distinct ) ExprSetProperty(pNew, EP_Distinct);
return pNew;
}
/*
+** Check to see if a function is usable according to current access
+** rules:
+**
+** SQLITE_FUNC_DIRECT - Only usable from top-level SQL
+**
+** SQLITE_FUNC_UNSAFE - Usable if TRUSTED_SCHEMA or from
+** top-level SQL
+**
+** If the function is not usable, create an error.
+*/
+SQLITE_PRIVATE void tdsqlite3ExprFunctionUsable(
+ Parse *pParse, /* Parsing and code generating context */
+ Expr *pExpr, /* The function invocation */
+ FuncDef *pDef /* The function being invoked */
+){
+ assert( !IN_RENAME_OBJECT );
+ assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 );
+ if( ExprHasProperty(pExpr, EP_FromDDL) ){
+ if( (pDef->funcFlags & SQLITE_FUNC_DIRECT)!=0
+ || (pParse->db->flags & SQLITE_TrustedSchema)==0
+ ){
+ /* Functions prohibited in triggers and views if:
+ ** (1) tagged with SQLITE_DIRECTONLY
+ ** (2) not tagged with SQLITE_INNOCUOUS (which means it
+ ** is tagged with SQLITE_FUNC_UNSAFE) and
+ ** SQLITE_DBCONFIG_TRUSTED_SCHEMA is off (meaning
+ ** that the schema is possibly tainted).
+ */
+ tdsqlite3ErrorMsg(pParse, "unsafe use of %s()", pDef->zName);
+ }
+ }
+}
+
+/*
** Assign a variable number to an expression that encodes a wildcard
** in the original SQL statement.
**
@@ -93742,7 +103306,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse *pParse, ExprList *pList, Token *
** variable number.
**
** Wildcards of the form "?nnn" are assigned the number "nnn". We make
-** sure "nnn" is not too be to avoid a denial of service attack when
+** sure "nnn" is not too big to avoid a denial of service attack when
** the SQL statement comes from an external source.
**
** Wildcards of the form ":aaa", "@aaa", or "$aaa" are assigned the same number
@@ -93750,82 +103314,82 @@ SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse *pParse, ExprList *pList, Token *
** instance of the wildcard, the next sequential variable number is
** assigned.
*/
-SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n){
- sqlite3 *db = pParse->db;
+SQLITE_PRIVATE void tdsqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n){
+ tdsqlite3 *db = pParse->db;
const char *z;
+ ynVar x;
if( pExpr==0 ) return;
assert( !ExprHasProperty(pExpr, EP_IntValue|EP_Reduced|EP_TokenOnly) );
z = pExpr->u.zToken;
assert( z!=0 );
assert( z[0]!=0 );
- assert( n==sqlite3Strlen30(z) );
+ assert( n==(u32)tdsqlite3Strlen30(z) );
if( z[1]==0 ){
/* Wildcard of the form "?". Assign the next variable number */
assert( z[0]=='?' );
- pExpr->iColumn = (ynVar)(++pParse->nVar);
+ x = (ynVar)(++pParse->nVar);
}else{
- ynVar x;
+ int doAdd = 0;
if( z[0]=='?' ){
/* Wildcard of the form "?nnn". Convert "nnn" to an integer and
** use it as the variable number */
i64 i;
- int bOk = 0==sqlite3Atoi64(&z[1], &i, n-1, SQLITE_UTF8);
- x = (ynVar)i;
+ int bOk;
+ if( n==2 ){ /*OPTIMIZATION-IF-TRUE*/
+ i = z[1]-'0'; /* The common case of ?N for a single digit N */
+ bOk = 1;
+ }else{
+ bOk = 0==tdsqlite3Atoi64(&z[1], &i, n-1, SQLITE_UTF8);
+ }
testcase( i==0 );
testcase( i==1 );
testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]-1 );
testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] );
if( bOk==0 || i<1 || i>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){
- sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d",
+ tdsqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d",
db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]);
return;
}
- if( i>pParse->nVar ){
- pParse->nVar = (int)i;
+ x = (ynVar)i;
+ if( x>pParse->nVar ){
+ pParse->nVar = (int)x;
+ doAdd = 1;
+ }else if( tdsqlite3VListNumToName(pParse->pVList, x)==0 ){
+ doAdd = 1;
}
}else{
/* Wildcards like ":aaa", "$aaa" or "@aaa". Reuse the same variable
** number as the prior appearance of the same name, or if the name
** has never appeared before, reuse the same variable number
*/
- ynVar i;
- for(i=x=0; i<pParse->nzVar; i++){
- if( pParse->azVar[i] && strcmp(pParse->azVar[i],z)==0 ){
- x = (ynVar)i+1;
- break;
- }
+ x = (ynVar)tdsqlite3VListNameToNum(pParse->pVList, z, n);
+ if( x==0 ){
+ x = (ynVar)(++pParse->nVar);
+ doAdd = 1;
}
- if( x==0 ) x = (ynVar)(++pParse->nVar);
}
- pExpr->iColumn = x;
- if( x>pParse->nzVar ){
- char **a;
- a = sqlite3DbRealloc(db, pParse->azVar, x*sizeof(a[0]));
- if( a==0 ){
- assert( db->mallocFailed ); /* Error reported through mallocFailed */
- return;
- }
- pParse->azVar = a;
- memset(&a[pParse->nzVar], 0, (x-pParse->nzVar)*sizeof(a[0]));
- pParse->nzVar = x;
- }
- if( pParse->azVar[x-1]==0 ){
- pParse->azVar[x-1] = sqlite3DbStrNDup(db, z, n);
+ if( doAdd ){
+ pParse->pVList = tdsqlite3VListAdd(db, pParse->pVList, z, n, x);
}
- }
- if( pParse->nVar>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){
- sqlite3ErrorMsg(pParse, "too many SQL variables");
+ }
+ pExpr->iColumn = x;
+ if( x>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){
+ tdsqlite3ErrorMsg(pParse, "too many SQL variables");
}
}
/*
** Recursively delete an expression tree.
*/
-static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){
+static SQLITE_NOINLINE void tdsqlite3ExprDeleteNN(tdsqlite3 *db, Expr *p){
assert( p!=0 );
/* Sanity check: Assert that the IntValue is non-negative if it exists */
assert( !ExprHasProperty(p, EP_IntValue) || p->u.iValue>=0 );
+
+ assert( !ExprHasProperty(p, EP_WinFunc) || p->y.pWin!=0 || db->mallocFailed );
+ assert( p->op!=TK_FUNCTION || ExprHasProperty(p, EP_TokenOnly|EP_Reduced)
+ || p->y.pWin==0 || ExprHasProperty(p, EP_WinFunc) );
#ifdef SQLITE_DEBUG
if( ExprHasProperty(p, EP_Leaf) && !ExprHasProperty(p, EP_TokenOnly) ){
assert( p->pLeft==0 );
@@ -93836,21 +103400,41 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){
if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){
/* The Expr.x union is never used at the same time as Expr.pRight */
assert( p->x.pList==0 || p->pRight==0 );
- if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft);
- sqlite3ExprDelete(db, p->pRight);
- if( ExprHasProperty(p, EP_xIsSelect) ){
- sqlite3SelectDelete(db, p->x.pSelect);
+ if( p->pLeft && p->op!=TK_SELECT_COLUMN ) tdsqlite3ExprDeleteNN(db, p->pLeft);
+ if( p->pRight ){
+ assert( !ExprHasProperty(p, EP_WinFunc) );
+ tdsqlite3ExprDeleteNN(db, p->pRight);
+ }else if( ExprHasProperty(p, EP_xIsSelect) ){
+ assert( !ExprHasProperty(p, EP_WinFunc) );
+ tdsqlite3SelectDelete(db, p->x.pSelect);
}else{
- sqlite3ExprListDelete(db, p->x.pList);
+ tdsqlite3ExprListDelete(db, p->x.pList);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( ExprHasProperty(p, EP_WinFunc) ){
+ tdsqlite3WindowDelete(db, p->y.pWin);
+ }
+#endif
}
}
- if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken);
+ if( ExprHasProperty(p, EP_MemToken) ) tdsqlite3DbFree(db, p->u.zToken);
if( !ExprHasProperty(p, EP_Static) ){
- sqlite3DbFree(db, p);
+ tdsqlite3DbFreeNN(db, p);
}
}
-SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){
- if( p ) sqlite3ExprDeleteNN(db, p);
+SQLITE_PRIVATE void tdsqlite3ExprDelete(tdsqlite3 *db, Expr *p){
+ if( p ) tdsqlite3ExprDeleteNN(db, p);
+}
+
+/* Invoke tdsqlite3RenameExprUnmap() and tdsqlite3ExprDelete() on the
+** expression.
+*/
+SQLITE_PRIVATE void tdsqlite3ExprUnmapAndDelete(Parse *pParse, Expr *p){
+ if( p ){
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameExprUnmap(pParse, p);
+ }
+ tdsqlite3ExprDeleteNN(pParse->db, p);
+ }
}
/*
@@ -93891,7 +103475,7 @@ static int exprStructSize(Expr *p){
** Note that with flags==EXPRDUP_REDUCE, this routines works on full-size
** (unreduced) Expr objects as they or originally constructed by the parser.
** During expression analysis, extra information is computed and moved into
-** later parts of teh Expr object and that extra information might get chopped
+** later parts of the Expr object and that extra information might get chopped
** off if the expression is reduced. Note also that it does not work to
** make an EXPRDUP_REDUCE copy of a reduced expression. It is only legal
** to reduce a pristine expression tree from the parser. The implementation
@@ -93903,7 +103487,11 @@ static int dupedExprStructSize(Expr *p, int flags){
assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */
assert( EXPR_FULLSIZE<=0xfff );
assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 );
- if( 0==flags ){
+ if( 0==flags || p->op==TK_SELECT_COLUMN
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ || ExprHasProperty(p, EP_WinFunc)
+#endif
+ ){
nSize = EXPR_FULLSIZE;
}else{
assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
@@ -93928,7 +103516,7 @@ static int dupedExprStructSize(Expr *p, int flags){
static int dupedExprNodeSize(Expr *p, int flags){
int nByte = dupedExprStructSize(p, flags) & 0xfff;
if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){
- nByte += sqlite3Strlen30(p->u.zToken)+1;
+ nByte += tdsqlite3Strlen30NN(p->u.zToken)+1;
}
return ROUND8(nByte);
}
@@ -93958,14 +103546,14 @@ static int dupedExprSize(Expr *p, int flags){
}
/*
-** This function is similar to sqlite3ExprDup(), except that if pzBuffer
+** This function is similar to tdsqlite3ExprDup(), except that if pzBuffer
** is not NULL then *pzBuffer is assumed to point to a buffer large enough
** to store the copy of expression p, the copies of p->u.zToken
** (if applicable), and the copies of the p->pLeft and p->pRight expressions,
** if any. Before returning, *pzBuffer is set to the first byte past the
** portion of the buffer copied into by this function.
*/
-static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
+static Expr *exprDup(tdsqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
Expr *pNew; /* Value to return */
u8 *zAlloc; /* Memory space from which to build Expr object */
u32 staticFlag; /* EP_Static if space not obtained from malloc */
@@ -93980,7 +103568,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
zAlloc = *pzBuffer;
staticFlag = EP_Static;
}else{
- zAlloc = sqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags));
+ zAlloc = tdsqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags));
staticFlag = 0;
}
pNew = (Expr *)zAlloc;
@@ -93995,7 +103583,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
const int nNewSize = nStructSize & 0xfff;
int nToken;
if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){
- nToken = sqlite3Strlen30(p->u.zToken) + 1;
+ nToken = tdsqlite3Strlen30(p->u.zToken) + 1;
}else{
nToken = 0;
}
@@ -94024,14 +103612,14 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
if( 0==((p->flags|pNew->flags) & (EP_TokenOnly|EP_Leaf)) ){
/* Fill in the pNew->x.pSelect or pNew->x.pList member. */
if( ExprHasProperty(p, EP_xIsSelect) ){
- pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, dupFlags);
+ pNew->x.pSelect = tdsqlite3SelectDup(db, p->x.pSelect, dupFlags);
}else{
- pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, dupFlags);
+ pNew->x.pList = tdsqlite3ExprListDup(db, p->x.pList, dupFlags);
}
}
/* Fill in pNew->pLeft and pNew->pRight. */
- if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly) ){
+ if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly|EP_WinFunc) ){
zAlloc += dupedExprNodeSize(p, dupFlags);
if( !ExprHasProperty(pNew, EP_TokenOnly|EP_Leaf) ){
pNew->pLeft = p->pLeft ?
@@ -94039,6 +103627,12 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
pNew->pRight = p->pRight ?
exprDup(db, p->pRight, EXPRDUP_REDUCE, &zAlloc) : 0;
}
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( ExprHasProperty(p, EP_WinFunc) ){
+ pNew->y.pWin = tdsqlite3WindowDup(db, pNew, p->y.pWin);
+ assert( ExprHasProperty(pNew, EP_WinFunc) );
+ }
+#endif /* SQLITE_OMIT_WINDOWFUNC */
if( pzBuffer ){
*pzBuffer = zAlloc;
}
@@ -94046,10 +103640,12 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){
if( pNew->op==TK_SELECT_COLUMN ){
pNew->pLeft = p->pLeft;
+ assert( p->iColumn==0 || p->pRight==0 );
+ assert( p->pRight==0 || p->pRight==p->pLeft );
}else{
- pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0);
+ pNew->pLeft = tdsqlite3ExprDup(db, p->pLeft, 0);
}
- pNew->pRight = sqlite3ExprDup(db, p->pRight, 0);
+ pNew->pRight = tdsqlite3ExprDup(db, p->pRight, 0);
}
}
}
@@ -94062,18 +103658,18 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){
** and the db->mallocFailed flag set.
*/
#ifndef SQLITE_OMIT_CTE
-static With *withDup(sqlite3 *db, With *p){
+static With *withDup(tdsqlite3 *db, With *p){
With *pRet = 0;
if( p ){
- int nByte = sizeof(*p) + sizeof(p->a[0]) * (p->nCte-1);
- pRet = sqlite3DbMallocZero(db, nByte);
+ tdsqlite3_int64 nByte = sizeof(*p) + sizeof(p->a[0]) * (p->nCte-1);
+ pRet = tdsqlite3DbMallocZero(db, nByte);
if( pRet ){
int i;
pRet->nCte = p->nCte;
for(i=0; i<p->nCte; i++){
- pRet->a[i].pSelect = sqlite3SelectDup(db, p->a[i].pSelect, 0);
- pRet->a[i].pCols = sqlite3ExprListDup(db, p->a[i].pCols, 0);
- pRet->a[i].zName = sqlite3DbStrDup(db, p->a[i].zName);
+ pRet->a[i].pSelect = tdsqlite3SelectDup(db, p->a[i].pSelect, 0);
+ pRet->a[i].pCols = tdsqlite3ExprListDup(db, p->a[i].pCols, 0);
+ pRet->a[i].zName = tdsqlite3DbStrDup(db, p->a[i].zName);
}
}
}
@@ -94083,14 +103679,47 @@ static With *withDup(sqlite3 *db, With *p){
# define withDup(x,y) 0
#endif
+#ifndef SQLITE_OMIT_WINDOWFUNC
+/*
+** The gatherSelectWindows() procedure and its helper routine
+** gatherSelectWindowsCallback() are used to scan all the expressions
+** an a newly duplicated SELECT statement and gather all of the Window
+** objects found there, assembling them onto the linked list at Select->pWin.
+*/
+static int gatherSelectWindowsCallback(Walker *pWalker, Expr *pExpr){
+ if( pExpr->op==TK_FUNCTION && ExprHasProperty(pExpr, EP_WinFunc) ){
+ Select *pSelect = pWalker->u.pSelect;
+ Window *pWin = pExpr->y.pWin;
+ assert( pWin );
+ assert( IsWindowFunc(pExpr) );
+ assert( pWin->ppThis==0 );
+ tdsqlite3WindowLink(pSelect, pWin);
+ }
+ return WRC_Continue;
+}
+static int gatherSelectWindowsSelectCallback(Walker *pWalker, Select *p){
+ return p==pWalker->u.pSelect ? WRC_Continue : WRC_Prune;
+}
+static void gatherSelectWindows(Select *p){
+ Walker w;
+ w.xExprCallback = gatherSelectWindowsCallback;
+ w.xSelectCallback = gatherSelectWindowsSelectCallback;
+ w.xSelectCallback2 = 0;
+ w.pParse = 0;
+ w.u.pSelect = p;
+ tdsqlite3WalkSelect(&w, p);
+}
+#endif
+
+
/*
** The following group of routines make deep copies of expressions,
** expression lists, ID lists, and select statements. The copies can
** be deleted (by being passed to their respective ...Delete() routines)
** without effecting the originals.
**
-** The expression list, ID, and source lists return by sqlite3ExprListDup(),
-** sqlite3IdListDup(), and sqlite3SrcListDup() can not be further expanded
+** The expression list, ID, and source lists return by tdsqlite3ExprListDup(),
+** tdsqlite3IdListDup(), and tdsqlite3SrcListDup() can not be further expanded
** by subsequent calls to sqlite*ListAppend() routines.
**
** Any tables that the SrcList might point to are not duplicated.
@@ -94100,34 +103729,48 @@ static With *withDup(sqlite3 *db, With *p){
** truncated version of the usual Expr structure that will be stored as
** part of the in-memory representation of the database schema.
*/
-SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, Expr *p, int flags){
+SQLITE_PRIVATE Expr *tdsqlite3ExprDup(tdsqlite3 *db, Expr *p, int flags){
assert( flags==0 || flags==EXPRDUP_REDUCE );
return p ? exprDup(db, p, flags, 0) : 0;
}
-SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){
+SQLITE_PRIVATE ExprList *tdsqlite3ExprListDup(tdsqlite3 *db, ExprList *p, int flags){
ExprList *pNew;
struct ExprList_item *pItem, *pOldItem;
int i;
+ Expr *pPriorSelectCol = 0;
assert( db!=0 );
if( p==0 ) return 0;
- pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) );
+ pNew = tdsqlite3DbMallocRawNN(db, tdsqlite3DbMallocSize(db, p));
if( pNew==0 ) return 0;
- pNew->nExpr = i = p->nExpr;
- if( (flags & EXPRDUP_REDUCE)==0 ) for(i=1; i<p->nExpr; i+=i){}
- pNew->a = pItem = sqlite3DbMallocRawNN(db, i*sizeof(p->a[0]) );
- if( pItem==0 ){
- sqlite3DbFree(db, pNew);
- return 0;
- }
+ pNew->nExpr = p->nExpr;
+ pItem = pNew->a;
pOldItem = p->a;
for(i=0; i<p->nExpr; i++, pItem++, pOldItem++){
Expr *pOldExpr = pOldItem->pExpr;
- pItem->pExpr = sqlite3ExprDup(db, pOldExpr, flags);
- pItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
- pItem->zSpan = sqlite3DbStrDup(db, pOldItem->zSpan);
- pItem->sortOrder = pOldItem->sortOrder;
+ Expr *pNewExpr;
+ pItem->pExpr = tdsqlite3ExprDup(db, pOldExpr, flags);
+ if( pOldExpr
+ && pOldExpr->op==TK_SELECT_COLUMN
+ && (pNewExpr = pItem->pExpr)!=0
+ ){
+ assert( pNewExpr->iColumn==0 || i>0 );
+ if( pNewExpr->iColumn==0 ){
+ assert( pOldExpr->pLeft==pOldExpr->pRight );
+ pPriorSelectCol = pNewExpr->pLeft = pNewExpr->pRight;
+ }else{
+ assert( i>0 );
+ assert( pItem[-1].pExpr!=0 );
+ assert( pNewExpr->iColumn==pItem[-1].pExpr->iColumn+1 );
+ assert( pPriorSelectCol==pItem[-1].pExpr->pLeft );
+ pNewExpr->pLeft = pPriorSelectCol;
+ }
+ }
+ pItem->zEName = tdsqlite3DbStrDup(db, pOldItem->zEName);
+ pItem->sortFlags = pOldItem->sortFlags;
+ pItem->eEName = pOldItem->eEName;
pItem->done = 0;
- pItem->bSpanIsTab = pOldItem->bSpanIsTab;
+ pItem->bNulls = pOldItem->bNulls;
+ pItem->bSorterRef = pOldItem->bSorterRef;
pItem->u = pOldItem->u;
}
return pNew;
@@ -94136,19 +103779,19 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags)
/*
** If cursors, triggers, views and subqueries are all omitted from
** the build, then none of the following routines, except for
-** sqlite3SelectDup(), can be called. sqlite3SelectDup() is sometimes
+** tdsqlite3SelectDup(), can be called. tdsqlite3SelectDup() is sometimes
** called with a NULL argument.
*/
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) \
|| !defined(SQLITE_OMIT_SUBQUERY)
-SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){
+SQLITE_PRIVATE SrcList *tdsqlite3SrcListDup(tdsqlite3 *db, SrcList *p, int flags){
SrcList *pNew;
int i;
int nByte;
assert( db!=0 );
if( p==0 ) return 0;
nByte = sizeof(*p) + (p->nSrc>0 ? sizeof(p->a[0]) * (p->nSrc-1) : 0);
- pNew = sqlite3DbMallocRawNN(db, nByte );
+ pNew = tdsqlite3DbMallocRawNN(db, nByte );
if( pNew==0 ) return 0;
pNew->nSrc = pNew->nAlloc = p->nSrc;
for(i=0; i<p->nSrc; i++){
@@ -94156,86 +103799,98 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){
struct SrcList_item *pOldItem = &p->a[i];
Table *pTab;
pNewItem->pSchema = pOldItem->pSchema;
- pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase);
- pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
- pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias);
+ pNewItem->zDatabase = tdsqlite3DbStrDup(db, pOldItem->zDatabase);
+ pNewItem->zName = tdsqlite3DbStrDup(db, pOldItem->zName);
+ pNewItem->zAlias = tdsqlite3DbStrDup(db, pOldItem->zAlias);
pNewItem->fg = pOldItem->fg;
pNewItem->iCursor = pOldItem->iCursor;
pNewItem->addrFillSub = pOldItem->addrFillSub;
pNewItem->regReturn = pOldItem->regReturn;
if( pNewItem->fg.isIndexedBy ){
- pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy);
+ pNewItem->u1.zIndexedBy = tdsqlite3DbStrDup(db, pOldItem->u1.zIndexedBy);
}
pNewItem->pIBIndex = pOldItem->pIBIndex;
if( pNewItem->fg.isTabFunc ){
pNewItem->u1.pFuncArg =
- sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags);
+ tdsqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags);
}
pTab = pNewItem->pTab = pOldItem->pTab;
if( pTab ){
- pTab->nRef++;
+ pTab->nTabRef++;
}
- pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags);
- pNewItem->pOn = sqlite3ExprDup(db, pOldItem->pOn, flags);
- pNewItem->pUsing = sqlite3IdListDup(db, pOldItem->pUsing);
+ pNewItem->pSelect = tdsqlite3SelectDup(db, pOldItem->pSelect, flags);
+ pNewItem->pOn = tdsqlite3ExprDup(db, pOldItem->pOn, flags);
+ pNewItem->pUsing = tdsqlite3IdListDup(db, pOldItem->pUsing);
pNewItem->colUsed = pOldItem->colUsed;
}
return pNew;
}
-SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){
+SQLITE_PRIVATE IdList *tdsqlite3IdListDup(tdsqlite3 *db, IdList *p){
IdList *pNew;
int i;
assert( db!=0 );
if( p==0 ) return 0;
- pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) );
+ pNew = tdsqlite3DbMallocRawNN(db, sizeof(*pNew) );
if( pNew==0 ) return 0;
pNew->nId = p->nId;
- pNew->a = sqlite3DbMallocRawNN(db, p->nId*sizeof(p->a[0]) );
+ pNew->a = tdsqlite3DbMallocRawNN(db, p->nId*sizeof(p->a[0]) );
if( pNew->a==0 ){
- sqlite3DbFree(db, pNew);
+ tdsqlite3DbFreeNN(db, pNew);
return 0;
}
/* Note that because the size of the allocation for p->a[] is not
- ** necessarily a power of two, sqlite3IdListAppend() may not be called
+ ** necessarily a power of two, tdsqlite3IdListAppend() may not be called
** on the duplicate created by this function. */
for(i=0; i<p->nId; i++){
struct IdList_item *pNewItem = &pNew->a[i];
struct IdList_item *pOldItem = &p->a[i];
- pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName);
+ pNewItem->zName = tdsqlite3DbStrDup(db, pOldItem->zName);
pNewItem->idx = pOldItem->idx;
}
return pNew;
}
-SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){
- Select *pNew, *pPrior;
+SQLITE_PRIVATE Select *tdsqlite3SelectDup(tdsqlite3 *db, Select *pDup, int flags){
+ Select *pRet = 0;
+ Select *pNext = 0;
+ Select **pp = &pRet;
+ Select *p;
+
assert( db!=0 );
- if( p==0 ) return 0;
- pNew = sqlite3DbMallocRawNN(db, sizeof(*p) );
- if( pNew==0 ) return 0;
- pNew->pEList = sqlite3ExprListDup(db, p->pEList, flags);
- pNew->pSrc = sqlite3SrcListDup(db, p->pSrc, flags);
- pNew->pWhere = sqlite3ExprDup(db, p->pWhere, flags);
- pNew->pGroupBy = sqlite3ExprListDup(db, p->pGroupBy, flags);
- pNew->pHaving = sqlite3ExprDup(db, p->pHaving, flags);
- pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy, flags);
- pNew->op = p->op;
- pNew->pPrior = pPrior = sqlite3SelectDup(db, p->pPrior, flags);
- if( pPrior ) pPrior->pNext = pNew;
- pNew->pNext = 0;
- pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags);
- pNew->pOffset = sqlite3ExprDup(db, p->pOffset, flags);
- pNew->iLimit = 0;
- pNew->iOffset = 0;
- pNew->selFlags = p->selFlags & ~SF_UsesEphemeral;
- pNew->addrOpenEphm[0] = -1;
- pNew->addrOpenEphm[1] = -1;
- pNew->nSelectRow = p->nSelectRow;
- pNew->pWith = withDup(db, p->pWith);
- sqlite3SelectSetName(pNew, p->zSelName);
- return pNew;
+ for(p=pDup; p; p=p->pPrior){
+ Select *pNew = tdsqlite3DbMallocRawNN(db, sizeof(*p) );
+ if( pNew==0 ) break;
+ pNew->pEList = tdsqlite3ExprListDup(db, p->pEList, flags);
+ pNew->pSrc = tdsqlite3SrcListDup(db, p->pSrc, flags);
+ pNew->pWhere = tdsqlite3ExprDup(db, p->pWhere, flags);
+ pNew->pGroupBy = tdsqlite3ExprListDup(db, p->pGroupBy, flags);
+ pNew->pHaving = tdsqlite3ExprDup(db, p->pHaving, flags);
+ pNew->pOrderBy = tdsqlite3ExprListDup(db, p->pOrderBy, flags);
+ pNew->op = p->op;
+ pNew->pNext = pNext;
+ pNew->pPrior = 0;
+ pNew->pLimit = tdsqlite3ExprDup(db, p->pLimit, flags);
+ pNew->iLimit = 0;
+ pNew->iOffset = 0;
+ pNew->selFlags = p->selFlags & ~SF_UsesEphemeral;
+ pNew->addrOpenEphm[0] = -1;
+ pNew->addrOpenEphm[1] = -1;
+ pNew->nSelectRow = p->nSelectRow;
+ pNew->pWith = withDup(db, p->pWith);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ pNew->pWin = 0;
+ pNew->pWinDefn = tdsqlite3WindowListDup(db, p->pWinDefn);
+ if( p->pWin && db->mallocFailed==0 ) gatherSelectWindows(pNew);
+#endif
+ pNew->selId = p->selId;
+ *pp = pNew;
+ pp = &pNew->pPrior;
+ pNext = pNew;
+ }
+
+ return pRet;
}
#else
-SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){
+SQLITE_PRIVATE Select *tdsqlite3SelectDup(tdsqlite3 *db, Select *p, int flags){
assert( p==0 );
return 0;
}
@@ -94246,46 +103901,51 @@ SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){
** Add a new element to the end of an expression list. If pList is
** initially NULL, then create a new expression list.
**
+** The pList argument must be either NULL or a pointer to an ExprList
+** obtained from a prior call to tdsqlite3ExprListAppend(). This routine
+** may not be used with an ExprList obtained from tdsqlite3ExprListDup().
+** Reason: This routine assumes that the number of slots in pList->a[]
+** is a power of two. That is true for tdsqlite3ExprListAppend() returns
+** but is not necessarily true from the return value of tdsqlite3ExprListDup().
+**
** If a memory allocation error occurs, the entire list is freed and
** NULL is returned. If non-NULL is returned, then it is guaranteed
** that the new entry was successfully appended.
*/
-SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(
+SQLITE_PRIVATE ExprList *tdsqlite3ExprListAppend(
Parse *pParse, /* Parsing context */
ExprList *pList, /* List to which to append. Might be NULL */
Expr *pExpr /* Expression to be appended. Might be NULL */
){
- sqlite3 *db = pParse->db;
+ struct ExprList_item *pItem;
+ tdsqlite3 *db = pParse->db;
assert( db!=0 );
if( pList==0 ){
- pList = sqlite3DbMallocRawNN(db, sizeof(ExprList) );
+ pList = tdsqlite3DbMallocRawNN(db, sizeof(ExprList) );
if( pList==0 ){
goto no_mem;
}
pList->nExpr = 0;
- pList->a = sqlite3DbMallocRawNN(db, sizeof(pList->a[0]));
- if( pList->a==0 ) goto no_mem;
}else if( (pList->nExpr & (pList->nExpr-1))==0 ){
- struct ExprList_item *a;
- assert( pList->nExpr>0 );
- a = sqlite3DbRealloc(db, pList->a, pList->nExpr*2*sizeof(pList->a[0]));
- if( a==0 ){
+ ExprList *pNew;
+ pNew = tdsqlite3DbRealloc(db, pList,
+ sizeof(*pList)+(2*(tdsqlite3_int64)pList->nExpr-1)*sizeof(pList->a[0]));
+ if( pNew==0 ){
goto no_mem;
}
- pList->a = a;
- }
- assert( pList->a!=0 );
- if( 1 ){
- struct ExprList_item *pItem = &pList->a[pList->nExpr++];
- memset(pItem, 0, sizeof(*pItem));
- pItem->pExpr = pExpr;
+ pList = pNew;
}
+ pItem = &pList->a[pList->nExpr++];
+ assert( offsetof(struct ExprList_item,zEName)==sizeof(pItem->pExpr) );
+ assert( offsetof(struct ExprList_item,pExpr)==0 );
+ memset(&pItem->zEName,0,sizeof(*pItem)-offsetof(struct ExprList_item,zEName));
+ pItem->pExpr = pExpr;
return pList;
no_mem:
/* Avoid leaking memory if malloc has failed. */
- sqlite3ExprDelete(db, pExpr);
- sqlite3ExprListDelete(db, pList);
+ tdsqlite3ExprDelete(db, pExpr);
+ tdsqlite3ExprListDelete(db, pList);
return 0;
}
@@ -94297,16 +103957,16 @@ no_mem:
** Or: (a,b,c) = (SELECT x,y,z FROM ....)
**
** For each term of the vector assignment, append new entries to the
-** expression list pList. In the case of a subquery on the LHS, append
+** expression list pList. In the case of a subquery on the RHS, append
** TK_SELECT_COLUMN expressions.
*/
-SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(
+SQLITE_PRIVATE ExprList *tdsqlite3ExprListAppendVector(
Parse *pParse, /* Parsing context */
ExprList *pList, /* List to which to append. Might be NULL */
IdList *pColumns, /* List of names of LHS of the assignment */
Expr *pExpr /* Vector expression to be appended. Might be NULL */
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
int n;
int i;
int iFirst = pList ? pList->nExpr : 0;
@@ -94314,58 +103974,95 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(
** exit prior to this routine being invoked */
if( NEVER(pColumns==0) ) goto vector_append_error;
if( pExpr==0 ) goto vector_append_error;
- n = sqlite3ExprVectorSize(pExpr);
- if( pColumns->nId!=n ){
- sqlite3ErrorMsg(pParse, "%d columns assigned %d values",
+
+ /* If the RHS is a vector, then we can immediately check to see that
+ ** the size of the RHS and LHS match. But if the RHS is a SELECT,
+ ** wildcards ("*") in the result set of the SELECT must be expanded before
+ ** we can do the size check, so defer the size check until code generation.
+ */
+ if( pExpr->op!=TK_SELECT && pColumns->nId!=(n=tdsqlite3ExprVectorSize(pExpr)) ){
+ tdsqlite3ErrorMsg(pParse, "%d columns assigned %d values",
pColumns->nId, n);
goto vector_append_error;
}
- for(i=0; i<n; i++){
- Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i);
- pList = sqlite3ExprListAppend(pParse, pList, pSubExpr);
+
+ for(i=0; i<pColumns->nId; i++){
+ Expr *pSubExpr = tdsqlite3ExprForVectorField(pParse, pExpr, i);
+ assert( pSubExpr!=0 || db->mallocFailed );
+ assert( pSubExpr==0 || pSubExpr->iTable==0 );
+ if( pSubExpr==0 ) continue;
+ pSubExpr->iTable = pColumns->nId;
+ pList = tdsqlite3ExprListAppend(pParse, pList, pSubExpr);
if( pList ){
assert( pList->nExpr==iFirst+i+1 );
- pList->a[pList->nExpr-1].zName = pColumns->a[i].zName;
+ pList->a[pList->nExpr-1].zEName = pColumns->a[i].zName;
pColumns->a[i].zName = 0;
}
}
- if( pExpr->op==TK_SELECT ){
- if( pList && pList->a[iFirst].pExpr ){
- assert( pList->a[iFirst].pExpr->op==TK_SELECT_COLUMN );
- pList->a[iFirst].pExpr->pRight = pExpr;
- pExpr = 0;
- }
+
+ if( !db->mallocFailed && pExpr->op==TK_SELECT && ALWAYS(pList!=0) ){
+ Expr *pFirst = pList->a[iFirst].pExpr;
+ assert( pFirst!=0 );
+ assert( pFirst->op==TK_SELECT_COLUMN );
+
+ /* Store the SELECT statement in pRight so it will be deleted when
+ ** tdsqlite3ExprListDelete() is called */
+ pFirst->pRight = pExpr;
+ pExpr = 0;
+
+ /* Remember the size of the LHS in iTable so that we can check that
+ ** the RHS and LHS sizes match during code generation. */
+ pFirst->iTable = pColumns->nId;
}
vector_append_error:
- sqlite3ExprDelete(db, pExpr);
- sqlite3IdListDelete(db, pColumns);
+ tdsqlite3ExprUnmapAndDelete(pParse, pExpr);
+ tdsqlite3IdListDelete(db, pColumns);
return pList;
}
/*
** Set the sort order for the last element on the given ExprList.
*/
-SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder){
+SQLITE_PRIVATE void tdsqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder, int eNulls){
+ struct ExprList_item *pItem;
if( p==0 ) return;
- assert( SQLITE_SO_UNDEFINED<0 && SQLITE_SO_ASC>=0 && SQLITE_SO_DESC>0 );
assert( p->nExpr>0 );
- if( iSortOrder<0 ){
- assert( p->a[p->nExpr-1].sortOrder==SQLITE_SO_ASC );
- return;
+
+ assert( SQLITE_SO_UNDEFINED<0 && SQLITE_SO_ASC==0 && SQLITE_SO_DESC>0 );
+ assert( iSortOrder==SQLITE_SO_UNDEFINED
+ || iSortOrder==SQLITE_SO_ASC
+ || iSortOrder==SQLITE_SO_DESC
+ );
+ assert( eNulls==SQLITE_SO_UNDEFINED
+ || eNulls==SQLITE_SO_ASC
+ || eNulls==SQLITE_SO_DESC
+ );
+
+ pItem = &p->a[p->nExpr-1];
+ assert( pItem->bNulls==0 );
+ if( iSortOrder==SQLITE_SO_UNDEFINED ){
+ iSortOrder = SQLITE_SO_ASC;
+ }
+ pItem->sortFlags = (u8)iSortOrder;
+
+ if( eNulls!=SQLITE_SO_UNDEFINED ){
+ pItem->bNulls = 1;
+ if( iSortOrder!=eNulls ){
+ pItem->sortFlags |= KEYINFO_ORDER_BIGNULL;
+ }
}
- p->a[p->nExpr-1].sortOrder = (u8)iSortOrder;
}
/*
-** Set the ExprList.a[].zName element of the most recently added item
+** Set the ExprList.a[].zEName element of the most recently added item
** on the expression list.
**
** pList might be NULL following an OOM error. But pName should never be
** NULL. If a memory allocation fails, the pParse->db->mallocFailed flag
** is set.
*/
-SQLITE_PRIVATE void sqlite3ExprListSetName(
+SQLITE_PRIVATE void tdsqlite3ExprListSetName(
Parse *pParse, /* Parsing context */
ExprList *pList, /* List to which to add the span. */
Token *pName, /* Name to be added */
@@ -94376,9 +104073,13 @@ SQLITE_PRIVATE void sqlite3ExprListSetName(
struct ExprList_item *pItem;
assert( pList->nExpr>0 );
pItem = &pList->a[pList->nExpr-1];
- assert( pItem->zName==0 );
- pItem->zName = sqlite3DbStrNDup(pParse->db, pName->z, pName->n);
- if( dequote ) sqlite3Dequote(pItem->zName);
+ assert( pItem->zEName==0 );
+ assert( pItem->eEName==ENAME_NAME );
+ pItem->zEName = tdsqlite3DbStrNDup(pParse->db, pName->z, pName->n);
+ if( dequote ) tdsqlite3Dequote(pItem->zEName);
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenMap(pParse, (void*)pItem->zEName, pName);
+ }
}
}
@@ -94390,20 +104091,21 @@ SQLITE_PRIVATE void sqlite3ExprListSetName(
** NULL. If a memory allocation fails, the pParse->db->mallocFailed flag
** is set.
*/
-SQLITE_PRIVATE void sqlite3ExprListSetSpan(
+SQLITE_PRIVATE void tdsqlite3ExprListSetSpan(
Parse *pParse, /* Parsing context */
ExprList *pList, /* List to which to add the span. */
- ExprSpan *pSpan /* The span to be added */
+ const char *zStart, /* Start of the span */
+ const char *zEnd /* End of the span */
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
assert( pList!=0 || db->mallocFailed!=0 );
if( pList ){
struct ExprList_item *pItem = &pList->a[pList->nExpr-1];
assert( pList->nExpr>0 );
- assert( db->mallocFailed || pItem->pExpr==pSpan->pExpr );
- sqlite3DbFree(db, pItem->zSpan);
- pItem->zSpan = sqlite3DbStrNDup(db, (char*)pSpan->zStart,
- (int)(pSpan->zEnd - pSpan->zStart));
+ if( pItem->zEName==0 ){
+ pItem->zEName = tdsqlite3DbSpanDup(db, zStart, zEnd);
+ pItem->eEName = ENAME_SPAN;
+ }
}
}
@@ -94411,7 +104113,7 @@ SQLITE_PRIVATE void sqlite3ExprListSetSpan(
** If the expression list pEList contains more than iLimit elements,
** leave an error message in pParse.
*/
-SQLITE_PRIVATE void sqlite3ExprListCheckLength(
+SQLITE_PRIVATE void tdsqlite3ExprListCheckLength(
Parse *pParse,
ExprList *pEList,
const char *zObject
@@ -94420,26 +104122,25 @@ SQLITE_PRIVATE void sqlite3ExprListCheckLength(
testcase( pEList && pEList->nExpr==mx );
testcase( pEList && pEList->nExpr==mx+1 );
if( pEList && pEList->nExpr>mx ){
- sqlite3ErrorMsg(pParse, "too many columns in %s", zObject);
+ tdsqlite3ErrorMsg(pParse, "too many columns in %s", zObject);
}
}
/*
** Delete an entire expression list.
*/
-static SQLITE_NOINLINE void exprListDeleteNN(sqlite3 *db, ExprList *pList){
- int i;
- struct ExprList_item *pItem;
- assert( pList->a!=0 || pList->nExpr==0 );
- for(pItem=pList->a, i=0; i<pList->nExpr; i++, pItem++){
- sqlite3ExprDelete(db, pItem->pExpr);
- sqlite3DbFree(db, pItem->zName);
- sqlite3DbFree(db, pItem->zSpan);
- }
- sqlite3DbFree(db, pList->a);
- sqlite3DbFree(db, pList);
+static SQLITE_NOINLINE void exprListDeleteNN(tdsqlite3 *db, ExprList *pList){
+ int i = pList->nExpr;
+ struct ExprList_item *pItem = pList->a;
+ assert( pList->nExpr>0 );
+ do{
+ tdsqlite3ExprDelete(db, pItem->pExpr);
+ tdsqlite3DbFree(db, pItem->zEName);
+ pItem++;
+ }while( --i>0 );
+ tdsqlite3DbFreeNN(db, pList);
}
-SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){
+SQLITE_PRIVATE void tdsqlite3ExprListDelete(tdsqlite3 *db, ExprList *pList){
if( pList ) exprListDeleteNN(db, pList);
}
@@ -94447,20 +104148,105 @@ SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){
** Return the bitwise-OR of all Expr.flags fields in the given
** ExprList.
*/
-SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList *pList){
+SQLITE_PRIVATE u32 tdsqlite3ExprListFlags(const ExprList *pList){
int i;
u32 m = 0;
- if( pList ){
- for(i=0; i<pList->nExpr; i++){
- Expr *pExpr = pList->a[i].pExpr;
- assert( pExpr!=0 );
- m |= pExpr->flags;
- }
+ assert( pList!=0 );
+ for(i=0; i<pList->nExpr; i++){
+ Expr *pExpr = pList->a[i].pExpr;
+ assert( pExpr!=0 );
+ m |= pExpr->flags;
}
return m;
}
/*
+** This is a SELECT-node callback for the expression walker that
+** always "fails". By "fail" in this case, we mean set
+** pWalker->eCode to zero and abort.
+**
+** This callback is used by multiple expression walkers.
+*/
+SQLITE_PRIVATE int tdsqlite3SelectWalkFail(Walker *pWalker, Select *NotUsed){
+ UNUSED_PARAMETER(NotUsed);
+ pWalker->eCode = 0;
+ return WRC_Abort;
+}
+
+/*
+** Check the input string to see if it is "true" or "false" (in any case).
+**
+** If the string is.... Return
+** "true" EP_IsTrue
+** "false" EP_IsFalse
+** anything else 0
+*/
+SQLITE_PRIVATE u32 tdsqlite3IsTrueOrFalse(const char *zIn){
+ if( tdsqlite3StrICmp(zIn, "true")==0 ) return EP_IsTrue;
+ if( tdsqlite3StrICmp(zIn, "false")==0 ) return EP_IsFalse;
+ return 0;
+}
+
+
+/*
+** If the input expression is an ID with the name "true" or "false"
+** then convert it into an TK_TRUEFALSE term. Return non-zero if
+** the conversion happened, and zero if the expression is unaltered.
+*/
+SQLITE_PRIVATE int tdsqlite3ExprIdToTrueFalse(Expr *pExpr){
+ u32 v;
+ assert( pExpr->op==TK_ID || pExpr->op==TK_STRING );
+ if( !ExprHasProperty(pExpr, EP_Quoted)
+ && (v = tdsqlite3IsTrueOrFalse(pExpr->u.zToken))!=0
+ ){
+ pExpr->op = TK_TRUEFALSE;
+ ExprSetProperty(pExpr, v);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** The argument must be a TK_TRUEFALSE Expr node. Return 1 if it is TRUE
+** and 0 if it is FALSE.
+*/
+SQLITE_PRIVATE int tdsqlite3ExprTruthValue(const Expr *pExpr){
+ pExpr = tdsqlite3ExprSkipCollate((Expr*)pExpr);
+ assert( pExpr->op==TK_TRUEFALSE );
+ assert( tdsqlite3StrICmp(pExpr->u.zToken,"true")==0
+ || tdsqlite3StrICmp(pExpr->u.zToken,"false")==0 );
+ return pExpr->u.zToken[4]==0;
+}
+
+/*
+** If pExpr is an AND or OR expression, try to simplify it by eliminating
+** terms that are always true or false. Return the simplified expression.
+** Or return the original expression if no simplification is possible.
+**
+** Examples:
+**
+** (x<10) AND true => (x<10)
+** (x<10) AND false => false
+** (x<10) AND (y=22 OR false) => (x<10) AND (y=22)
+** (x<10) AND (y=22 OR true) => (x<10)
+** (y=22) OR true => true
+*/
+SQLITE_PRIVATE Expr *tdsqlite3ExprSimplifiedAndOr(Expr *pExpr){
+ assert( pExpr!=0 );
+ if( pExpr->op==TK_AND || pExpr->op==TK_OR ){
+ Expr *pRight = tdsqlite3ExprSimplifiedAndOr(pExpr->pRight);
+ Expr *pLeft = tdsqlite3ExprSimplifiedAndOr(pExpr->pLeft);
+ if( ExprAlwaysTrue(pLeft) || ExprAlwaysFalse(pRight) ){
+ pExpr = pExpr->op==TK_AND ? pRight : pLeft;
+ }else if( ExprAlwaysTrue(pRight) || ExprAlwaysFalse(pLeft) ){
+ pExpr = pExpr->op==TK_AND ? pLeft : pRight;
+ }
+ }
+ return pExpr;
+}
+
+
+/*
** These routines are Walker callbacks used to check expressions to
** see if they are "constant" for some definition of constant. The
** Walker.eCode value determines the type of "constant" we are looking
@@ -94468,18 +104254,19 @@ SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList *pList){
**
** These callback routines are used to implement the following:
**
-** sqlite3ExprIsConstant() pWalker->eCode==1
-** sqlite3ExprIsConstantNotJoin() pWalker->eCode==2
-** sqlite3ExprIsTableConstant() pWalker->eCode==3
-** sqlite3ExprIsConstantOrFunction() pWalker->eCode==4 or 5
+** tdsqlite3ExprIsConstant() pWalker->eCode==1
+** tdsqlite3ExprIsConstantNotJoin() pWalker->eCode==2
+** tdsqlite3ExprIsTableConstant() pWalker->eCode==3
+** tdsqlite3ExprIsConstantOrFunction() pWalker->eCode==4 or 5
**
** In all cases, the callbacks set Walker.eCode=0 and abort if the expression
** is found to not be a constant.
**
-** The sqlite3ExprIsConstantOrFunction() is used for evaluating expressions
-** in a CREATE TABLE statement. The Walker.eCode value is 5 when parsing
-** an existing schema and 4 when processing a new statement. A bound
-** parameter raises an error for new statements, but is silently converted
+** The tdsqlite3ExprIsConstantOrFunction() is used for evaluating DEFAULT
+** expressions in a CREATE TABLE statement. The Walker.eCode value is 5
+** when parsing an existing schema out of the sqlite_master table and 4
+** when processing a new CREATE TABLE statement. A bound parameter raises
+** an error for new statements, but is silently converted
** to NULL for existing schemas. This allows sqlite_master tables that
** contain a bound parameter because they were generated by older versions
** of SQLite to be parsed by newer versions of SQLite without raising a
@@ -94500,13 +104287,22 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){
** and either pWalker->eCode==4 or 5 or the function has the
** SQLITE_FUNC_CONST flag. */
case TK_FUNCTION:
- if( pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_ConstFunc) ){
+ if( (pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_ConstFunc))
+ && !ExprHasProperty(pExpr, EP_WinFunc)
+ ){
+ if( pWalker->eCode==5 ) ExprSetProperty(pExpr, EP_FromDDL);
return WRC_Continue;
}else{
pWalker->eCode = 0;
return WRC_Abort;
}
case TK_ID:
+ /* Convert "true" or "false" in a DEFAULT clause into the
+ ** appropriate TK_TRUEFALSE operator */
+ if( tdsqlite3ExprIdToTrueFalse(pExpr) ){
+ return WRC_Prune;
+ }
+ /* Fall thru */
case TK_COLUMN:
case TK_AGG_FUNCTION:
case TK_AGG_COLUMN:
@@ -94514,12 +104310,19 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){
testcase( pExpr->op==TK_COLUMN );
testcase( pExpr->op==TK_AGG_FUNCTION );
testcase( pExpr->op==TK_AGG_COLUMN );
+ if( ExprHasProperty(pExpr, EP_FixedCol) && pWalker->eCode!=2 ){
+ return WRC_Continue;
+ }
if( pWalker->eCode==3 && pExpr->iTable==pWalker->u.iCur ){
return WRC_Continue;
- }else{
- pWalker->eCode = 0;
- return WRC_Abort;
}
+ /* Fall through */
+ case TK_IF_NULL_ROW:
+ case TK_REGISTER:
+ testcase( pExpr->op==TK_REGISTER );
+ testcase( pExpr->op==TK_IF_NULL_ROW );
+ pWalker->eCode = 0;
+ return WRC_Abort;
case TK_VARIABLE:
if( pWalker->eCode==5 ){
/* Silently convert bound parameters that appear inside of CREATE
@@ -94528,30 +104331,27 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){
pExpr->op = TK_NULL;
}else if( pWalker->eCode==4 ){
/* A bound parameter in a CREATE statement that originates from
- ** sqlite3_prepare() causes an error */
+ ** tdsqlite3_prepare() causes an error */
pWalker->eCode = 0;
return WRC_Abort;
}
/* Fall through */
default:
- testcase( pExpr->op==TK_SELECT ); /* selectNodeIsConstant will disallow */
- testcase( pExpr->op==TK_EXISTS ); /* selectNodeIsConstant will disallow */
+ testcase( pExpr->op==TK_SELECT ); /* tdsqlite3SelectWalkFail() disallows */
+ testcase( pExpr->op==TK_EXISTS ); /* tdsqlite3SelectWalkFail() disallows */
return WRC_Continue;
}
}
-static int selectNodeIsConstant(Walker *pWalker, Select *NotUsed){
- UNUSED_PARAMETER(NotUsed);
- pWalker->eCode = 0;
- return WRC_Abort;
-}
static int exprIsConst(Expr *p, int initFlag, int iCur){
Walker w;
- memset(&w, 0, sizeof(w));
w.eCode = initFlag;
w.xExprCallback = exprNodeIsConstant;
- w.xSelectCallback = selectNodeIsConstant;
+ w.xSelectCallback = tdsqlite3SelectWalkFail;
+#ifdef SQLITE_DEBUG
+ w.xSelectCallback2 = tdsqlite3SelectWalkAssert2;
+#endif
w.u.iCur = iCur;
- sqlite3WalkExpr(&w, p);
+ tdsqlite3WalkExpr(&w, p);
return w.eCode;
}
@@ -94563,17 +104363,24 @@ static int exprIsConst(Expr *p, int initFlag, int iCur){
** is considered a variable but a single-quoted string (ex: 'abc') is
** a constant.
*/
-SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr *p){
+SQLITE_PRIVATE int tdsqlite3ExprIsConstant(Expr *p){
return exprIsConst(p, 1, 0);
}
/*
-** Walk an expression tree. Return non-zero if the expression is constant
-** that does no originate from the ON or USING clauses of a join.
-** Return 0 if it involves variables or function calls or terms from
-** an ON or USING clause.
+** Walk an expression tree. Return non-zero if
+**
+** (1) the expression is constant, and
+** (2) the expression does originate in the ON or USING clause
+** of a LEFT JOIN, and
+** (3) the expression does not contain any EP_FixedCol TK_COLUMN
+** operands created by the constant propagation optimization.
+**
+** When this routine returns true, it indicates that the expression
+** can be added to the pParse->pConstExpr list and evaluated once when
+** the prepared statement starts up. See tdsqlite3ExprCodeAtInit().
*/
-SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){
+SQLITE_PRIVATE int tdsqlite3ExprIsConstantNotJoin(Expr *p){
return exprIsConst(p, 2, 0);
}
@@ -94583,20 +104390,91 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){
** expression must not refer to any non-deterministic function nor any
** table other than iCur.
*/
-SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){
+SQLITE_PRIVATE int tdsqlite3ExprIsTableConstant(Expr *p, int iCur){
return exprIsConst(p, 3, iCur);
}
+
/*
-** Walk an expression tree. Return non-zero if the expression is constant
-** or a function call with constant arguments. Return and 0 if there
-** are any variables.
+** tdsqlite3WalkExpr() callback used by tdsqlite3ExprIsConstantOrGroupBy().
+*/
+static int exprNodeIsConstantOrGroupBy(Walker *pWalker, Expr *pExpr){
+ ExprList *pGroupBy = pWalker->u.pGroupBy;
+ int i;
+
+ /* Check if pExpr is identical to any GROUP BY term. If so, consider
+ ** it constant. */
+ for(i=0; i<pGroupBy->nExpr; i++){
+ Expr *p = pGroupBy->a[i].pExpr;
+ if( tdsqlite3ExprCompare(0, pExpr, p, -1)<2 ){
+ CollSeq *pColl = tdsqlite3ExprNNCollSeq(pWalker->pParse, p);
+ if( tdsqlite3IsBinary(pColl) ){
+ return WRC_Prune;
+ }
+ }
+ }
+
+ /* Check if pExpr is a sub-select. If so, consider it variable. */
+ if( ExprHasProperty(pExpr, EP_xIsSelect) ){
+ pWalker->eCode = 0;
+ return WRC_Abort;
+ }
+
+ return exprNodeIsConstant(pWalker, pExpr);
+}
+
+/*
+** Walk the expression tree passed as the first argument. Return non-zero
+** if the expression consists entirely of constants or copies of terms
+** in pGroupBy that sort with the BINARY collation sequence.
+**
+** This routine is used to determine if a term of the HAVING clause can
+** be promoted into the WHERE clause. In order for such a promotion to work,
+** the value of the HAVING clause term must be the same for all members of
+** a "group". The requirement that the GROUP BY term must be BINARY
+** assumes that no other collating sequence will have a finer-grained
+** grouping than binary. In other words (A=B COLLATE binary) implies
+** A=B in every other collating sequence. The requirement that the
+** GROUP BY be BINARY is stricter than necessary. It would also work
+** to promote HAVING clauses that use the same alternative collating
+** sequence as the GROUP BY term, but that is much harder to check,
+** alternative collating sequences are uncommon, and this is only an
+** optimization, so we take the easy way out and simply require the
+** GROUP BY to use the BINARY collating sequence.
+*/
+SQLITE_PRIVATE int tdsqlite3ExprIsConstantOrGroupBy(Parse *pParse, Expr *p, ExprList *pGroupBy){
+ Walker w;
+ w.eCode = 1;
+ w.xExprCallback = exprNodeIsConstantOrGroupBy;
+ w.xSelectCallback = 0;
+ w.u.pGroupBy = pGroupBy;
+ w.pParse = pParse;
+ tdsqlite3WalkExpr(&w, p);
+ return w.eCode;
+}
+
+/*
+** Walk an expression tree for the DEFAULT field of a column definition
+** in a CREATE TABLE statement. Return non-zero if the expression is
+** acceptable for use as a DEFAULT. That is to say, return non-zero if
+** the expression is constant or a function call with constant arguments.
+** Return and 0 if there are any variables.
+**
+** isInit is true when parsing from sqlite_master. isInit is false when
+** processing a new CREATE TABLE statement. When isInit is true, parameters
+** (such as ? or $abc) in the expression are converted into NULL. When
+** isInit is false, parameters raise an error. Parameters should not be
+** allowed in a CREATE TABLE statement, but some legacy versions of SQLite
+** allowed it, so we need to support it when reading sqlite_master for
+** backwards compatibility.
+**
+** If isInit is true, set EP_FromDDL on every TK_FUNCTION node.
**
** For the purposes of this function, a double-quoted string (ex: "abc")
** is considered a variable but a single-quoted string (ex: 'abc') is
** a constant.
*/
-SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){
+SQLITE_PRIVATE int tdsqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){
assert( isInit==0 || isInit==1 );
return exprIsConst(p, 4+isInit, 0);
}
@@ -94606,13 +104484,15 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){
** Walk an expression tree. Return 1 if the expression contains a
** subquery of some kind. Return 0 if there are no subqueries.
*/
-SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr *p){
+SQLITE_PRIVATE int tdsqlite3ExprContainsSubquery(Expr *p){
Walker w;
- memset(&w, 0, sizeof(w));
w.eCode = 1;
- w.xExprCallback = sqlite3ExprWalkNoop;
- w.xSelectCallback = selectNodeIsConstant;
- sqlite3WalkExpr(&w, p);
+ w.xExprCallback = tdsqlite3ExprWalkNoop;
+ w.xSelectCallback = tdsqlite3SelectWalkFail;
+#ifdef SQLITE_DEBUG
+ w.xSelectCallback2 = tdsqlite3SelectWalkAssert2;
+#endif
+ tdsqlite3WalkExpr(&w, p);
return w.eCode==0;
}
#endif
@@ -94623,13 +104503,14 @@ SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr *p){
** in *pValue. If the expression is not an integer or if it is too big
** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged.
*/
-SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){
+SQLITE_PRIVATE int tdsqlite3ExprIsInteger(Expr *p, int *pValue){
int rc = 0;
+ if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */
/* If an expression is an integer literal that fits in a signed 32-bit
** integer, then the EP_IntValue flag will have already been set */
assert( p->op!=TK_INTEGER || (p->flags & EP_IntValue)!=0
- || sqlite3GetInt32(p->u.zToken, &rc)==0 );
+ || tdsqlite3GetInt32(p->u.zToken, &rc)==0 );
if( p->flags & EP_IntValue ){
*pValue = p->u.iValue;
@@ -94637,12 +104518,12 @@ SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){
}
switch( p->op ){
case TK_UPLUS: {
- rc = sqlite3ExprIsInteger(p->pLeft, pValue);
+ rc = tdsqlite3ExprIsInteger(p->pLeft, pValue);
break;
}
case TK_UMINUS: {
int v;
- if( sqlite3ExprIsInteger(p->pLeft, &v) ){
+ if( tdsqlite3ExprIsInteger(p->pLeft, &v) ){
assert( v!=(-2147483647-1) );
*pValue = -v;
rc = 1;
@@ -94668,9 +104549,11 @@ SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){
** will likely result in an incorrect answer. So when in doubt, return
** TRUE.
*/
-SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){
+SQLITE_PRIVATE int tdsqlite3ExprCanBeNull(const Expr *p){
u8 op;
- while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ p = p->pLeft; }
+ while( p->op==TK_UPLUS || p->op==TK_UMINUS ){
+ p = p->pLeft;
+ }
op = p->op;
if( op==TK_REGISTER ) op = p->op2;
switch( op ){
@@ -94680,9 +104563,11 @@ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){
case TK_BLOB:
return 0;
case TK_COLUMN:
- assert( p->pTab!=0 );
return ExprHasProperty(p, EP_CanBeNull) ||
- (p->iColumn>=0 && p->pTab->aCol[p->iColumn].notNull==0);
+ p->y.pTab==0 || /* Reference to column of index on expression */
+ (p->iColumn>=0
+ && ALWAYS(p->y.pTab->aCol!=0) /* Defense against OOM problems */
+ && p->y.pTab->aCol[p->iColumn].notNull==0);
default:
return 1;
}
@@ -94698,29 +104583,32 @@ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){
** is harmless. A false positive, however, can result in the wrong
** answer.
*/
-SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr *p, char aff){
+SQLITE_PRIVATE int tdsqlite3ExprNeedsNoAffinityChange(const Expr *p, char aff){
u8 op;
+ int unaryMinus = 0;
if( aff==SQLITE_AFF_BLOB ) return 1;
- while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ p = p->pLeft; }
+ while( p->op==TK_UPLUS || p->op==TK_UMINUS ){
+ if( p->op==TK_UMINUS ) unaryMinus = 1;
+ p = p->pLeft;
+ }
op = p->op;
if( op==TK_REGISTER ) op = p->op2;
switch( op ){
case TK_INTEGER: {
- return aff==SQLITE_AFF_INTEGER || aff==SQLITE_AFF_NUMERIC;
+ return aff>=SQLITE_AFF_NUMERIC;
}
case TK_FLOAT: {
- return aff==SQLITE_AFF_REAL || aff==SQLITE_AFF_NUMERIC;
+ return aff>=SQLITE_AFF_NUMERIC;
}
case TK_STRING: {
- return aff==SQLITE_AFF_TEXT;
+ return !unaryMinus && aff==SQLITE_AFF_TEXT;
}
case TK_BLOB: {
- return 1;
+ return !unaryMinus;
}
case TK_COLUMN: {
assert( p->iTable>=0 ); /* p cannot be part of a CHECK constraint */
- return p->iColumn<0
- && (aff==SQLITE_AFF_INTEGER || aff==SQLITE_AFF_NUMERIC);
+ return aff>=SQLITE_AFF_NUMERIC && p->iColumn<0;
}
default: {
return 0;
@@ -94731,10 +104619,10 @@ SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr *p, char aff){
/*
** Return TRUE if the given string is a row-id column name.
*/
-SQLITE_PRIVATE int sqlite3IsRowid(const char *z){
- if( sqlite3StrICmp(z, "_ROWID_")==0 ) return 1;
- if( sqlite3StrICmp(z, "ROWID")==0 ) return 1;
- if( sqlite3StrICmp(z, "OID")==0 ) return 1;
+SQLITE_PRIVATE int tdsqlite3IsRowid(const char *z){
+ if( tdsqlite3StrICmp(z, "_ROWID_")==0 ) return 1;
+ if( tdsqlite3StrICmp(z, "ROWID")==0 ) return 1;
+ if( tdsqlite3StrICmp(z, "OID")==0 ) return 1;
return 0;
}
@@ -94763,7 +104651,6 @@ static Select *isCandidateForInOpt(Expr *pX){
}
assert( p->pGroupBy==0 ); /* Has no GROUP BY clause */
if( p->pLimit ) return 0; /* Has no LIMIT clause */
- assert( p->pOffset==0 ); /* No LIMIT means no OFFSET */
if( p->pWhere ) return 0; /* Has no WHERE clause */
pSrc = p->pSrc;
assert( pSrc!=0 );
@@ -94792,14 +104679,14 @@ static Select *isCandidateForInOpt(Expr *pX){
** to a non-NULL value if iCur contains no NULLs. Cause register regHasNull
** to be set to NULL if iCur contains one or more NULL values.
*/
-static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){
+static void tdsqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){
int addr1;
- sqlite3VdbeAddOp2(v, OP_Integer, 0, regHasNull);
- addr1 = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_Column, iCur, 0, regHasNull);
- sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regHasNull);
+ addr1 = tdsqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iCur, 0, regHasNull);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
VdbeComment((v, "first_entry_in(%d)", iCur));
- sqlite3VdbeJumpHere(v, addr1);
+ tdsqlite3VdbeJumpHere(v, addr1);
}
#endif
@@ -94809,13 +104696,13 @@ static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){
** The argument is an IN operator with a list (not a subquery) on the
** right-hand side. Return TRUE if that list is constant.
*/
-static int sqlite3InRhsIsConstant(Expr *pIn){
+static int tdsqlite3InRhsIsConstant(Expr *pIn){
Expr *pLHS;
int res;
assert( !ExprHasProperty(pIn, EP_xIsSelect) );
pLHS = pIn->pLeft;
pIn->pLeft = 0;
- res = sqlite3ExprIsConstant(pIn);
+ res = tdsqlite3ExprIsConstant(pIn);
pIn->pLeft = pLHS;
return res;
}
@@ -94853,16 +104740,15 @@ static int sqlite3InRhsIsConstant(Expr *pIn){
** pX->iTable made to point to the ephemeral table instead of an
** existing table.
**
-** The inFlags parameter must contain exactly one of the bits
-** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP. If inFlags contains
-** IN_INDEX_MEMBERSHIP, then the generated table will be used for a
-** fast membership test. When the IN_INDEX_LOOP bit is set, the
-** IN index will be used to loop over all values of the RHS of the
-** IN operator.
+** The inFlags parameter must contain, at a minimum, one of the bits
+** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP but not both. If inFlags contains
+** IN_INDEX_MEMBERSHIP, then the generated table will be used for a fast
+** membership test. When the IN_INDEX_LOOP bit is set, the IN index will
+** be used to loop over all values of the RHS of the IN operator.
**
** When IN_INDEX_LOOP is used (and the b-tree will be used to iterate
** through the set members) then the b-tree must not contain duplicates.
-** An epheremal table must be used unless the selected columns are guaranteed
+** An epheremal table will be created unless the selected columns are guaranteed
** to be unique - either because it is an INTEGER PRIMARY KEY or due to
** a UNIQUE constraint or index.
**
@@ -94903,18 +104789,19 @@ static int sqlite3InRhsIsConstant(Expr *pIn){
** then aiMap[] is populated with {2, 0, 1}.
*/
#ifndef SQLITE_OMIT_SUBQUERY
-SQLITE_PRIVATE int sqlite3FindInIndex(
+SQLITE_PRIVATE int tdsqlite3FindInIndex(
Parse *pParse, /* Parsing context */
- Expr *pX, /* The right-hand side (RHS) of the IN operator */
+ Expr *pX, /* The IN expression */
u32 inFlags, /* IN_INDEX_LOOP, _MEMBERSHIP, and/or _NOOP_OK */
int *prRhsHasNull, /* Register holding NULL status. See notes */
- int *aiMap /* Mapping from Index fields to RHS fields */
+ int *aiMap, /* Mapping from Index fields to RHS fields */
+ int *piTab /* OUT: index to use */
){
Select *p; /* SELECT to the right of IN operator */
int eType = 0; /* Type of RHS table. IN_INDEX_* */
int iTab = pParse->nTab++; /* Cursor of the RHS table */
int mustBeUnique; /* True if RHS must be unique */
- Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */
+ Vdbe *v = tdsqlite3GetVdbe(pParse); /* Virtual machine being coded */
assert( pX->op==TK_IN );
mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0;
@@ -94928,7 +104815,7 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
int i;
ExprList *pEList = pX->x.pSelect->pEList;
for(i=0; i<pEList->nExpr; i++){
- if( sqlite3ExprCanBeNull(pEList->a[i].pExpr) ) break;
+ if( tdsqlite3ExprCanBeNull(pEList->a[i].pExpr) ) break;
}
if( i==pEList->nExpr ){
prRhsHasNull = 0;
@@ -94939,7 +104826,7 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
** satisfy the query. This is preferable to generating a new
** ephemeral table. */
if( pParse->nErr==0 && (p = isCandidateForInOpt(pX))!=0 ){
- sqlite3 *db = pParse->db; /* Database connection */
+ tdsqlite3 *db = pParse->db; /* Database connection */
Table *pTab; /* Table <table>. */
i16 iDb; /* Database idx for pTab */
ExprList *pEList = p->pEList;
@@ -94951,20 +104838,21 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
pTab = p->pSrc->a[0].pTab;
/* Code an OP_Transaction and OP_TableLock for <table>. */
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
- sqlite3CodeVerifySchema(pParse, iDb);
- sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
+ tdsqlite3CodeVerifySchema(pParse, iDb);
+ tdsqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
- assert(v); /* sqlite3GetVdbe() has always been previously called */
+ assert(v); /* tdsqlite3GetVdbe() has always been previously called */
if( nExpr==1 && pEList->a[0].pExpr->iColumn<0 ){
/* The "x IN (SELECT rowid FROM table)" case */
- int iAddr = sqlite3VdbeAddOp0(v, OP_Once);
+ int iAddr = tdsqlite3VdbeAddOp0(v, OP_Once);
VdbeCoverage(v);
- sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead);
+ tdsqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead);
eType = IN_INDEX_ROWID;
-
- sqlite3VdbeJumpHere(v, iAddr);
+ ExplainQueryPlan((pParse, 0,
+ "USING ROWID SEARCH ON TABLE %s FOR IN-OPERATOR",pTab->zName));
+ tdsqlite3VdbeJumpHere(v, iAddr);
}else{
Index *pIdx; /* Iterator variable */
int affinity_ok = 1;
@@ -94975,24 +104863,24 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
** on the RHS of the IN operator. If it not, it is not possible to
** use any index of the RHS table. */
for(i=0; i<nExpr && affinity_ok; i++){
- Expr *pLhs = sqlite3VectorFieldSubexpr(pX->pLeft, i);
+ Expr *pLhs = tdsqlite3VectorFieldSubexpr(pX->pLeft, i);
int iCol = pEList->a[i].pExpr->iColumn;
- char idxaff = sqlite3TableColumnAffinity(pTab,iCol); /* RHS table */
- char cmpaff = sqlite3CompareAffinity(pLhs, idxaff);
+ char idxaff = tdsqlite3TableColumnAffinity(pTab,iCol); /* RHS table */
+ char cmpaff = tdsqlite3CompareAffinity(pLhs, idxaff);
testcase( cmpaff==SQLITE_AFF_BLOB );
testcase( cmpaff==SQLITE_AFF_TEXT );
switch( cmpaff ){
case SQLITE_AFF_BLOB:
break;
case SQLITE_AFF_TEXT:
- /* sqlite3CompareAffinity() only returns TEXT if one side or the
+ /* tdsqlite3CompareAffinity() only returns TEXT if one side or the
** other has no affinity and the other side is TEXT. Hence,
** the only way for cmpaff to be TEXT is for idxaff to be TEXT
** and for the term on the LHS of the IN to have no affinity. */
assert( idxaff==SQLITE_AFF_TEXT );
break;
default:
- affinity_ok = sqlite3IsNumericAffinity(idxaff);
+ affinity_ok = tdsqlite3IsNumericAffinity(idxaff);
}
}
@@ -95002,6 +104890,7 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
Bitmask colUsed; /* Columns of the index used */
Bitmask mCol; /* Mask for the current column */
if( pIdx->nColumn<nExpr ) continue;
+ if( pIdx->pPartIdxWhere!=0 ) continue;
/* Maximum nColumn is BMS-2, not BMS-1, so that we can compute
** BITMASK(nExpr) without overflowing */
testcase( pIdx->nColumn==BMS-2 );
@@ -95017,16 +104906,16 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
colUsed = 0; /* Columns of index used so far */
for(i=0; i<nExpr; i++){
- Expr *pLhs = sqlite3VectorFieldSubexpr(pX->pLeft, i);
+ Expr *pLhs = tdsqlite3VectorFieldSubexpr(pX->pLeft, i);
Expr *pRhs = pEList->a[i].pExpr;
- CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs);
+ CollSeq *pReq = tdsqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs);
int j;
assert( pReq!=0 || pRhs->iColumn==XN_ROWID || pParse->nErr );
for(j=0; j<nExpr; j++){
if( pIdx->aiColumn[j]!=pRhs->iColumn ) continue;
assert( pIdx->azColl[j] );
- if( pReq!=0 && sqlite3StrICmp(pReq->zName, pIdx->azColl[j])!=0 ){
+ if( pReq!=0 && tdsqlite3StrICmp(pReq->zName, pIdx->azColl[j])!=0 ){
continue;
}
break;
@@ -95041,14 +104930,11 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
assert( i==nExpr || colUsed!=(MASKBIT(nExpr)-1) );
if( colUsed==(MASKBIT(nExpr)-1) ){
/* If we reach this point, that means the index pIdx is usable */
- int iAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
-#ifndef SQLITE_OMIT_EXPLAIN
- sqlite3VdbeAddOp4(v, OP_Explain, 0, 0, 0,
- sqlite3MPrintf(db, "USING INDEX %s FOR IN-OPERATOR",pIdx->zName),
- P4_DYNAMIC);
-#endif
- sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb);
- sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+ int iAddr = tdsqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ ExplainQueryPlan((pParse, 0,
+ "USING INDEX %s FOR IN-OPERATOR",pIdx->zName));
+ tdsqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pIdx);
VdbeComment((v, "%s", pIdx->zName));
assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 );
eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0];
@@ -95056,15 +104942,15 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
if( prRhsHasNull ){
#ifdef SQLITE_ENABLE_COLUMN_USED_MASK
i64 mask = (1<<nExpr)-1;
- sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed,
+ tdsqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed,
iTab, 0, 0, (u8*)&mask, P4_INT64);
#endif
*prRhsHasNull = ++pParse->nMem;
if( nExpr==1 ){
- sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull);
+ tdsqlite3SetHasNullFlag(v, iTab, *prRhsHasNull);
}
}
- sqlite3VdbeJumpHere(v, iAddr);
+ tdsqlite3VdbeJumpHere(v, iAddr);
}
} /* End loop over indexes */
} /* End if( affinity_ok ) */
@@ -95081,7 +104967,7 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
if( eType==0
&& (inFlags & IN_INDEX_NOOP_OK)
&& !ExprHasProperty(pX, EP_xIsSelect)
- && (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2)
+ && (!tdsqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2)
){
eType = IN_INDEX_NOOP;
}
@@ -95095,23 +104981,23 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
eType = IN_INDEX_EPH;
if( inFlags & IN_INDEX_LOOP ){
pParse->nQueryLoop = 0;
- if( pX->pLeft->iColumn<0 && !ExprHasProperty(pX, EP_xIsSelect) ){
- eType = IN_INDEX_ROWID;
- }
}else if( prRhsHasNull ){
*prRhsHasNull = rMayHaveNull = ++pParse->nMem;
}
- sqlite3CodeSubselect(pParse, pX, rMayHaveNull, eType==IN_INDEX_ROWID);
+ assert( pX->op==TK_IN );
+ tdsqlite3CodeRhsOfIN(pParse, pX, iTab);
+ if( rMayHaveNull ){
+ tdsqlite3SetHasNullFlag(v, iTab, rMayHaveNull);
+ }
pParse->nQueryLoop = savedNQueryLoop;
- }else{
- pX->iTable = iTab;
}
if( aiMap && eType!=IN_INDEX_INDEX_ASC && eType!=IN_INDEX_INDEX_DESC ){
int i, n;
- n = sqlite3ExprVectorSize(pX->pLeft);
+ n = tdsqlite3ExprVectorSize(pX->pLeft);
for(i=0; i<n; i++) aiMap[i] = i;
}
+ *piTab = iTab;
return eType;
}
#endif
@@ -95123,23 +105009,23 @@ SQLITE_PRIVATE int sqlite3FindInIndex(
** the affinities to be used for each column of the comparison.
**
** It is the responsibility of the caller to ensure that the returned
-** string is eventually freed using sqlite3DbFree().
+** string is eventually freed using tdsqlite3DbFree().
*/
static char *exprINAffinity(Parse *pParse, Expr *pExpr){
Expr *pLeft = pExpr->pLeft;
- int nVal = sqlite3ExprVectorSize(pLeft);
+ int nVal = tdsqlite3ExprVectorSize(pLeft);
Select *pSelect = (pExpr->flags & EP_xIsSelect) ? pExpr->x.pSelect : 0;
char *zRet;
assert( pExpr->op==TK_IN );
- zRet = sqlite3DbMallocZero(pParse->db, nVal+1);
+ zRet = tdsqlite3DbMallocRaw(pParse->db, nVal+1);
if( zRet ){
int i;
for(i=0; i<nVal; i++){
- Expr *pA = sqlite3VectorFieldSubexpr(pLeft, i);
- char a = sqlite3ExprAffinity(pA);
+ Expr *pA = tdsqlite3VectorFieldSubexpr(pLeft, i);
+ char a = tdsqlite3ExprAffinity(pA);
if( pSelect ){
- zRet[i] = sqlite3CompareAffinity(pSelect->pEList->a[i].pExpr, a);
+ zRet[i] = tdsqlite3CompareAffinity(pSelect->pEList->a[i].pExpr, a);
}else{
zRet[i] = a;
}
@@ -95157,274 +105043,347 @@ static char *exprINAffinity(Parse *pParse, Expr *pExpr){
**
** "sub-select returns N columns - expected M"
*/
-SQLITE_PRIVATE void sqlite3SubselectError(Parse *pParse, int nActual, int nExpect){
- const char *zFmt = "sub-select returns %d columns - expected %d";
- sqlite3ErrorMsg(pParse, zFmt, nActual, nExpect);
+SQLITE_PRIVATE void tdsqlite3SubselectError(Parse *pParse, int nActual, int nExpect){
+ if( pParse->nErr==0 ){
+ const char *zFmt = "sub-select returns %d columns - expected %d";
+ tdsqlite3ErrorMsg(pParse, zFmt, nActual, nExpect);
+ }
}
#endif
/*
-** Generate code for scalar subqueries used as a subquery expression, EXISTS,
-** or IN operators. Examples:
+** Expression pExpr is a vector that has been used in a context where
+** it is not permitted. If pExpr is a sub-select vector, this routine
+** loads the Parse object with a message of the form:
**
-** (SELECT a FROM b) -- subquery
-** EXISTS (SELECT a FROM b) -- EXISTS subquery
-** x IN (4,5,11) -- IN operator with list on right-hand side
-** x IN (SELECT a FROM b) -- IN operator with subquery on the right
+** "sub-select returns N columns - expected 1"
**
-** The pExpr parameter describes the expression that contains the IN
-** operator or subquery.
+** Or, if it is a regular scalar vector:
**
-** If parameter isRowid is non-zero, then expression pExpr is guaranteed
-** to be of the form "<rowid> IN (?, ?, ?)", where <rowid> is a reference
-** to some integer key column of a table B-Tree. In this case, use an
-** intkey B-Tree to store the set of IN(...) values instead of the usual
-** (slower) variable length keys B-Tree.
+** "row value misused"
+*/
+SQLITE_PRIVATE void tdsqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){
+#ifndef SQLITE_OMIT_SUBQUERY
+ if( pExpr->flags & EP_xIsSelect ){
+ tdsqlite3SubselectError(pParse, pExpr->x.pSelect->pEList->nExpr, 1);
+ }else
+#endif
+ {
+ tdsqlite3ErrorMsg(pParse, "row value misused");
+ }
+}
+
+#ifndef SQLITE_OMIT_SUBQUERY
+/*
+** Generate code that will construct an ephemeral table containing all terms
+** in the RHS of an IN operator. The IN operator can be in either of two
+** forms:
**
-** If rMayHaveNull is non-zero, that means that the operation is an IN
-** (not a SELECT or EXISTS) and that the RHS might contains NULLs.
-** All this routine does is initialize the register given by rMayHaveNull
-** to NULL. Calling routines will take care of changing this register
-** value to non-NULL if the RHS is NULL-free.
+** x IN (4,5,11) -- IN operator with list on right-hand side
+** x IN (SELECT a FROM b) -- IN operator with subquery on the right
**
-** For a SELECT or EXISTS operator, return the register that holds the
-** result. For a multi-column SELECT, the result is stored in a contiguous
-** array of registers and the return value is the register of the left-most
-** result column. Return 0 for IN operators or if an error occurs.
-*/
-#ifndef SQLITE_OMIT_SUBQUERY
-SQLITE_PRIVATE int sqlite3CodeSubselect(
+** The pExpr parameter is the IN operator. The cursor number for the
+** constructed ephermeral table is returned. The first time the ephemeral
+** table is computed, the cursor number is also stored in pExpr->iTable,
+** however the cursor number returned might not be the same, as it might
+** have been duplicated using OP_OpenDup.
+**
+** If the LHS expression ("x" in the examples) is a column value, or
+** the SELECT statement returns a column value, then the affinity of that
+** column is used to build the index keys. If both 'x' and the
+** SELECT... statement are columns, then numeric affinity is used
+** if either column has NUMERIC or INTEGER affinity. If neither
+** 'x' nor the SELECT... statement are columns, then numeric affinity
+** is used.
+*/
+SQLITE_PRIVATE void tdsqlite3CodeRhsOfIN(
Parse *pParse, /* Parsing context */
- Expr *pExpr, /* The IN, SELECT, or EXISTS operator */
- int rHasNullFlag, /* Register that records whether NULLs exist in RHS */
- int isRowid /* If true, LHS of IN operator is a rowid */
+ Expr *pExpr, /* The IN operator */
+ int iTab /* Use this cursor number */
){
- int jmpIfDynamic = -1; /* One-time test address */
- int rReg = 0; /* Register storing resulting */
- Vdbe *v = sqlite3GetVdbe(pParse);
- if( NEVER(v==0) ) return 0;
- sqlite3ExprCachePush(pParse);
+ int addrOnce = 0; /* Address of the OP_Once instruction at top */
+ int addr; /* Address of OP_OpenEphemeral instruction */
+ Expr *pLeft; /* the LHS of the IN operator */
+ KeyInfo *pKeyInfo = 0; /* Key information */
+ int nVal; /* Size of vector pLeft */
+ Vdbe *v; /* The prepared statement under construction */
- /* The evaluation of the IN/EXISTS/SELECT must be repeated every time it
+ v = pParse->pVdbe;
+ assert( v!=0 );
+
+ /* The evaluation of the IN must be repeated every time it
** is encountered if any of the following is true:
**
** * The right-hand side is a correlated subquery
** * The right-hand side is an expression list containing variables
** * We are inside a trigger
**
- ** If all of the above are false, then we can run this code just once
- ** save the results, and reuse the same result on subsequent invocations.
+ ** If all of the above are false, then we can compute the RHS just once
+ ** and reuse it many names.
*/
- if( !ExprHasProperty(pExpr, EP_VarSelect) ){
- jmpIfDynamic = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
- }
+ if( !ExprHasProperty(pExpr, EP_VarSelect) && pParse->iSelfTab==0 ){
+ /* Reuse of the RHS is allowed */
+ /* If this routine has already been coded, but the previous code
+ ** might not have been invoked yet, so invoke it now as a subroutine.
+ */
+ if( ExprHasProperty(pExpr, EP_Subrtn) ){
+ addrOnce = tdsqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ if( ExprHasProperty(pExpr, EP_xIsSelect) ){
+ ExplainQueryPlan((pParse, 0, "REUSE LIST SUBQUERY %d",
+ pExpr->x.pSelect->selId));
+ }
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn,
+ pExpr->y.sub.iAddr);
+ tdsqlite3VdbeAddOp2(v, OP_OpenDup, iTab, pExpr->iTable);
+ tdsqlite3VdbeJumpHere(v, addrOnce);
+ return;
+ }
-#ifndef SQLITE_OMIT_EXPLAIN
- if( pParse->explain==2 ){
- char *zMsg = sqlite3MPrintf(pParse->db, "EXECUTE %s%s SUBQUERY %d",
- jmpIfDynamic>=0?"":"CORRELATED ",
- pExpr->op==TK_IN?"LIST":"SCALAR",
- pParse->iNextSelectId
- );
- sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
- }
-#endif
+ /* Begin coding the subroutine */
+ ExprSetProperty(pExpr, EP_Subrtn);
+ pExpr->y.sub.regReturn = ++pParse->nMem;
+ pExpr->y.sub.iAddr =
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
+ VdbeComment((v, "return address"));
- switch( pExpr->op ){
- case TK_IN: {
- int addr; /* Address of OP_OpenEphemeral instruction */
- Expr *pLeft = pExpr->pLeft; /* the LHS of the IN operator */
- KeyInfo *pKeyInfo = 0; /* Key information */
- int nVal; /* Size of vector pLeft */
-
- nVal = sqlite3ExprVectorSize(pLeft);
- assert( !isRowid || nVal==1 );
+ addrOnce = tdsqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ }
- /* Whether this is an 'x IN(SELECT...)' or an 'x IN(<exprlist>)'
- ** expression it is handled the same way. An ephemeral table is
- ** filled with index keys representing the results from the
- ** SELECT or the <exprlist>.
- **
- ** If the 'x' expression is a column value, or the SELECT...
- ** statement returns a column value, then the affinity of that
- ** column is used to build the index keys. If both 'x' and the
- ** SELECT... statement are columns, then numeric affinity is used
- ** if either column has NUMERIC or INTEGER affinity. If neither
- ** 'x' nor the SELECT... statement are columns, then numeric affinity
- ** is used.
- */
- pExpr->iTable = pParse->nTab++;
- addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral,
- pExpr->iTable, (isRowid?0:nVal));
- pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, nVal, 1);
+ /* Check to see if this is a vector IN operator */
+ pLeft = pExpr->pLeft;
+ nVal = tdsqlite3ExprVectorSize(pLeft);
- if( ExprHasProperty(pExpr, EP_xIsSelect) ){
- /* Case 1: expr IN (SELECT ...)
- **
- ** Generate code to write the results of the select into the temporary
- ** table allocated and opened above.
- */
- Select *pSelect = pExpr->x.pSelect;
- ExprList *pEList = pSelect->pEList;
-
- assert( !isRowid );
- /* If the LHS and RHS of the IN operator do not match, that
- ** error will have been caught long before we reach this point. */
- if( ALWAYS(pEList->nExpr==nVal) ){
- SelectDest dest;
- int i;
- sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable);
- dest.zAffSdst = exprINAffinity(pParse, pExpr);
- assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable );
- pSelect->iLimit = 0;
- testcase( pSelect->selFlags & SF_Distinct );
- testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */
- if( sqlite3Select(pParse, pSelect, &dest) ){
- sqlite3DbFree(pParse->db, dest.zAffSdst);
- sqlite3KeyInfoUnref(pKeyInfo);
- return 0;
- }
- sqlite3DbFree(pParse->db, dest.zAffSdst);
- assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */
- assert( pEList!=0 );
- assert( pEList->nExpr>0 );
- assert( sqlite3KeyInfoIsWriteable(pKeyInfo) );
- for(i=0; i<nVal; i++){
- Expr *p = sqlite3VectorFieldSubexpr(pLeft, i);
- pKeyInfo->aColl[i] = sqlite3BinaryCompareCollSeq(
- pParse, p, pEList->a[i].pExpr
- );
- }
- }
- }else if( ALWAYS(pExpr->x.pList!=0) ){
- /* Case 2: expr IN (exprlist)
- **
- ** For each expression, build an index key from the evaluation and
- ** store it in the temporary table. If <expr> is a column, then use
- ** that columns affinity when building index keys. If <expr> is not
- ** a column, use numeric affinity.
- */
- char affinity; /* Affinity of the LHS of the IN */
- int i;
- ExprList *pList = pExpr->x.pList;
- struct ExprList_item *pItem;
- int r1, r2, r3;
+ /* Construct the ephemeral table that will contain the content of
+ ** RHS of the IN operator.
+ */
+ pExpr->iTable = iTab;
+ addr = tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, nVal);
+#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
+ if( ExprHasProperty(pExpr, EP_xIsSelect) ){
+ VdbeComment((v, "Result of SELECT %u", pExpr->x.pSelect->selId));
+ }else{
+ VdbeComment((v, "RHS of IN operator"));
+ }
+#endif
+ pKeyInfo = tdsqlite3KeyInfoAlloc(pParse->db, nVal, 1);
- affinity = sqlite3ExprAffinity(pLeft);
- if( !affinity ){
- affinity = SQLITE_AFF_BLOB;
- }
- if( pKeyInfo ){
- assert( sqlite3KeyInfoIsWriteable(pKeyInfo) );
- pKeyInfo->aColl[0] = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
- }
-
- /* Loop through each expression in <exprlist>. */
- r1 = sqlite3GetTempReg(pParse);
- r2 = sqlite3GetTempReg(pParse);
- if( isRowid ) sqlite3VdbeAddOp2(v, OP_Null, 0, r2);
- for(i=pList->nExpr, pItem=pList->a; i>0; i--, pItem++){
- Expr *pE2 = pItem->pExpr;
- int iValToIns;
-
- /* If the expression is not constant then we will need to
- ** disable the test that was generated above that makes sure
- ** this code only executes once. Because for a non-constant
- ** expression we need to rerun this code each time.
- */
- if( jmpIfDynamic>=0 && !sqlite3ExprIsConstant(pE2) ){
- sqlite3VdbeChangeToNoop(v, jmpIfDynamic);
- jmpIfDynamic = -1;
- }
+ if( ExprHasProperty(pExpr, EP_xIsSelect) ){
+ /* Case 1: expr IN (SELECT ...)
+ **
+ ** Generate code to write the results of the select into the temporary
+ ** table allocated and opened above.
+ */
+ Select *pSelect = pExpr->x.pSelect;
+ ExprList *pEList = pSelect->pEList;
- /* Evaluate the expression and insert it into the temp table */
- if( isRowid && sqlite3ExprIsInteger(pE2, &iValToIns) ){
- sqlite3VdbeAddOp3(v, OP_InsertInt, pExpr->iTable, r2, iValToIns);
- }else{
- r3 = sqlite3ExprCodeTarget(pParse, pE2, r1);
- if( isRowid ){
- sqlite3VdbeAddOp2(v, OP_MustBeInt, r3,
- sqlite3VdbeCurrentAddr(v)+2);
- VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_Insert, pExpr->iTable, r2, r3);
- }else{
- sqlite3VdbeAddOp4(v, OP_MakeRecord, r3, 1, r2, &affinity, 1);
- sqlite3ExprCacheAffinityChange(pParse, r3, 1);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, pExpr->iTable, r2);
- }
- }
- }
- sqlite3ReleaseTempReg(pParse, r1);
- sqlite3ReleaseTempReg(pParse, r2);
+ ExplainQueryPlan((pParse, 1, "%sLIST SUBQUERY %d",
+ addrOnce?"":"CORRELATED ", pSelect->selId
+ ));
+ /* If the LHS and RHS of the IN operator do not match, that
+ ** error will have been caught long before we reach this point. */
+ if( ALWAYS(pEList->nExpr==nVal) ){
+ SelectDest dest;
+ int i;
+ tdsqlite3SelectDestInit(&dest, SRT_Set, iTab);
+ dest.zAffSdst = exprINAffinity(pParse, pExpr);
+ pSelect->iLimit = 0;
+ testcase( pSelect->selFlags & SF_Distinct );
+ testcase( pKeyInfo==0 ); /* Caused by OOM in tdsqlite3KeyInfoAlloc() */
+ if( tdsqlite3Select(pParse, pSelect, &dest) ){
+ tdsqlite3DbFree(pParse->db, dest.zAffSdst);
+ tdsqlite3KeyInfoUnref(pKeyInfo);
+ return;
}
- if( pKeyInfo ){
- sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO);
+ tdsqlite3DbFree(pParse->db, dest.zAffSdst);
+ assert( pKeyInfo!=0 ); /* OOM will cause exit after tdsqlite3Select() */
+ assert( pEList!=0 );
+ assert( pEList->nExpr>0 );
+ assert( tdsqlite3KeyInfoIsWriteable(pKeyInfo) );
+ for(i=0; i<nVal; i++){
+ Expr *p = tdsqlite3VectorFieldSubexpr(pLeft, i);
+ pKeyInfo->aColl[i] = tdsqlite3BinaryCompareCollSeq(
+ pParse, p, pEList->a[i].pExpr
+ );
}
- break;
+ }
+ }else if( ALWAYS(pExpr->x.pList!=0) ){
+ /* Case 2: expr IN (exprlist)
+ **
+ ** For each expression, build an index key from the evaluation and
+ ** store it in the temporary table. If <expr> is a column, then use
+ ** that columns affinity when building index keys. If <expr> is not
+ ** a column, use numeric affinity.
+ */
+ char affinity; /* Affinity of the LHS of the IN */
+ int i;
+ ExprList *pList = pExpr->x.pList;
+ struct ExprList_item *pItem;
+ int r1, r2;
+ affinity = tdsqlite3ExprAffinity(pLeft);
+ if( affinity<=SQLITE_AFF_NONE ){
+ affinity = SQLITE_AFF_BLOB;
+ }
+ if( pKeyInfo ){
+ assert( tdsqlite3KeyInfoIsWriteable(pKeyInfo) );
+ pKeyInfo->aColl[0] = tdsqlite3ExprCollSeq(pParse, pExpr->pLeft);
}
- case TK_EXISTS:
- case TK_SELECT:
- default: {
- /* Case 3: (SELECT ... FROM ...)
- ** or: EXISTS(SELECT ... FROM ...)
- **
- ** For a SELECT, generate code to put the values for all columns of
- ** the first row into an array of registers and return the index of
- ** the first register.
- **
- ** If this is an EXISTS, write an integer 0 (not exists) or 1 (exists)
- ** into a register and return that register number.
- **
- ** In both cases, the query is augmented with "LIMIT 1". Any
- ** preexisting limit is discarded in place of the new LIMIT 1.
- */
- Select *pSel; /* SELECT statement to encode */
- SelectDest dest; /* How to deal with SELECT result */
- int nReg; /* Registers to allocate */
-
- testcase( pExpr->op==TK_EXISTS );
- testcase( pExpr->op==TK_SELECT );
- assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT );
- assert( ExprHasProperty(pExpr, EP_xIsSelect) );
+ /* Loop through each expression in <exprlist>. */
+ r1 = tdsqlite3GetTempReg(pParse);
+ r2 = tdsqlite3GetTempReg(pParse);
+ for(i=pList->nExpr, pItem=pList->a; i>0; i--, pItem++){
+ Expr *pE2 = pItem->pExpr;
- pSel = pExpr->x.pSelect;
- nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1;
- sqlite3SelectDestInit(&dest, 0, pParse->nMem+1);
- pParse->nMem += nReg;
- if( pExpr->op==TK_SELECT ){
- dest.eDest = SRT_Mem;
- dest.iSdst = dest.iSDParm;
- dest.nSdst = nReg;
- sqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, dest.iSDParm+nReg-1);
- VdbeComment((v, "Init subquery result"));
- }else{
- dest.eDest = SRT_Exists;
- sqlite3VdbeAddOp2(v, OP_Integer, 0, dest.iSDParm);
- VdbeComment((v, "Init EXISTS result"));
- }
- sqlite3ExprDelete(pParse->db, pSel->pLimit);
- pSel->pLimit = sqlite3ExprAlloc(pParse->db, TK_INTEGER,
- &sqlite3IntTokens[1], 0);
- pSel->iLimit = 0;
- pSel->selFlags &= ~SF_MultiValue;
- if( sqlite3Select(pParse, pSel, &dest) ){
- return 0;
+ /* If the expression is not constant then we will need to
+ ** disable the test that was generated above that makes sure
+ ** this code only executes once. Because for a non-constant
+ ** expression we need to rerun this code each time.
+ */
+ if( addrOnce && !tdsqlite3ExprIsConstant(pE2) ){
+ tdsqlite3VdbeChangeToNoop(v, addrOnce);
+ ExprClearProperty(pExpr, EP_Subrtn);
+ addrOnce = 0;
}
- rReg = dest.iSDParm;
- ExprSetVVAProperty(pExpr, EP_NoReduce);
- break;
+
+ /* Evaluate the expression and insert it into the temp table */
+ tdsqlite3ExprCode(pParse, pE2, r1);
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, r1, 1, r2, &affinity, 1);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r2, r1, 1);
}
+ tdsqlite3ReleaseTempReg(pParse, r1);
+ tdsqlite3ReleaseTempReg(pParse, r2);
+ }
+ if( pKeyInfo ){
+ tdsqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO);
}
+ if( addrOnce ){
+ tdsqlite3VdbeJumpHere(v, addrOnce);
+ /* Subroutine return */
+ tdsqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn);
+ tdsqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, tdsqlite3VdbeCurrentAddr(v)-1);
+ tdsqlite3ClearTempRegCache(pParse);
+ }
+}
+#endif /* SQLITE_OMIT_SUBQUERY */
+
+/*
+** Generate code for scalar subqueries used as a subquery expression
+** or EXISTS operator:
+**
+** (SELECT a FROM b) -- subquery
+** EXISTS (SELECT a FROM b) -- EXISTS subquery
+**
+** The pExpr parameter is the SELECT or EXISTS operator to be coded.
+**
+** Return the register that holds the result. For a multi-column SELECT,
+** the result is stored in a contiguous array of registers and the
+** return value is the register of the left-most result column.
+** Return 0 if an error occurs.
+*/
+#ifndef SQLITE_OMIT_SUBQUERY
+SQLITE_PRIVATE int tdsqlite3CodeSubselect(Parse *pParse, Expr *pExpr){
+ int addrOnce = 0; /* Address of OP_Once at top of subroutine */
+ int rReg = 0; /* Register storing resulting */
+ Select *pSel; /* SELECT statement to encode */
+ SelectDest dest; /* How to deal with SELECT result */
+ int nReg; /* Registers to allocate */
+ Expr *pLimit; /* New limit expression */
+
+ Vdbe *v = pParse->pVdbe;
+ assert( v!=0 );
+ testcase( pExpr->op==TK_EXISTS );
+ testcase( pExpr->op==TK_SELECT );
+ assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT );
+ assert( ExprHasProperty(pExpr, EP_xIsSelect) );
+ pSel = pExpr->x.pSelect;
- if( rHasNullFlag ){
- sqlite3SetHasNullFlag(v, pExpr->iTable, rHasNullFlag);
+ /* The evaluation of the EXISTS/SELECT must be repeated every time it
+ ** is encountered if any of the following is true:
+ **
+ ** * The right-hand side is a correlated subquery
+ ** * The right-hand side is an expression list containing variables
+ ** * We are inside a trigger
+ **
+ ** If all of the above are false, then we can run this code just once
+ ** save the results, and reuse the same result on subsequent invocations.
+ */
+ if( !ExprHasProperty(pExpr, EP_VarSelect) ){
+ /* If this routine has already been coded, then invoke it as a
+ ** subroutine. */
+ if( ExprHasProperty(pExpr, EP_Subrtn) ){
+ ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId));
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn,
+ pExpr->y.sub.iAddr);
+ return pExpr->iTable;
+ }
+
+ /* Begin coding the subroutine */
+ ExprSetProperty(pExpr, EP_Subrtn);
+ pExpr->y.sub.regReturn = ++pParse->nMem;
+ pExpr->y.sub.iAddr =
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1;
+ VdbeComment((v, "return address"));
+
+ addrOnce = tdsqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ }
+
+ /* For a SELECT, generate code to put the values for all columns of
+ ** the first row into an array of registers and return the index of
+ ** the first register.
+ **
+ ** If this is an EXISTS, write an integer 0 (not exists) or 1 (exists)
+ ** into a register and return that register number.
+ **
+ ** In both cases, the query is augmented with "LIMIT 1". Any
+ ** preexisting limit is discarded in place of the new LIMIT 1.
+ */
+ ExplainQueryPlan((pParse, 1, "%sSCALAR SUBQUERY %d",
+ addrOnce?"":"CORRELATED ", pSel->selId));
+ nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1;
+ tdsqlite3SelectDestInit(&dest, 0, pParse->nMem+1);
+ pParse->nMem += nReg;
+ if( pExpr->op==TK_SELECT ){
+ dest.eDest = SRT_Mem;
+ dest.iSdst = dest.iSDParm;
+ dest.nSdst = nReg;
+ tdsqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, dest.iSDParm+nReg-1);
+ VdbeComment((v, "Init subquery result"));
+ }else{
+ dest.eDest = SRT_Exists;
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, dest.iSDParm);
+ VdbeComment((v, "Init EXISTS result"));
+ }
+ if( pSel->pLimit ){
+ /* The subquery already has a limit. If the pre-existing limit is X
+ ** then make the new limit X<>0 so that the new limit is either 1 or 0 */
+ tdsqlite3 *db = pParse->db;
+ pLimit = tdsqlite3Expr(db, TK_INTEGER, "0");
+ if( pLimit ){
+ pLimit->affExpr = SQLITE_AFF_NUMERIC;
+ pLimit = tdsqlite3PExpr(pParse, TK_NE,
+ tdsqlite3ExprDup(db, pSel->pLimit->pLeft, 0), pLimit);
+ }
+ tdsqlite3ExprDelete(db, pSel->pLimit->pLeft);
+ pSel->pLimit->pLeft = pLimit;
+ }else{
+ /* If there is no pre-existing limit add a limit of 1 */
+ pLimit = tdsqlite3Expr(pParse->db, TK_INTEGER, "1");
+ pSel->pLimit = tdsqlite3PExpr(pParse, TK_LIMIT, pLimit, 0);
+ }
+ pSel->iLimit = 0;
+ if( tdsqlite3Select(pParse, pSel, &dest) ){
+ return 0;
}
+ pExpr->iTable = rReg = dest.iSDParm;
+ ExprSetVVAProperty(pExpr, EP_NoReduce);
+ if( addrOnce ){
+ tdsqlite3VdbeJumpHere(v, addrOnce);
- if( jmpIfDynamic>=0 ){
- sqlite3VdbeJumpHere(v, jmpIfDynamic);
+ /* Subroutine return */
+ tdsqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn);
+ tdsqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, tdsqlite3VdbeCurrentAddr(v)-1);
+ tdsqlite3ClearTempRegCache(pParse);
}
- sqlite3ExprCachePop(pParse);
return rReg;
}
@@ -95437,19 +105396,15 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(
** columns as the vector on the LHS. Or, if the RHS of the IN() is not
** a sub-query, that the LHS is a vector of size 1.
*/
-SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){
- int nVector = sqlite3ExprVectorSize(pIn->pLeft);
+SQLITE_PRIVATE int tdsqlite3ExprCheckIN(Parse *pParse, Expr *pIn){
+ int nVector = tdsqlite3ExprVectorSize(pIn->pLeft);
if( (pIn->flags & EP_xIsSelect) ){
if( nVector!=pIn->x.pSelect->pEList->nExpr ){
- sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector);
+ tdsqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector);
return 1;
}
}else if( nVector!=1 ){
- if( (pIn->pLeft->flags & EP_xIsSelect) ){
- sqlite3SubselectError(pParse, nVector, 1);
- }else{
- sqlite3ErrorMsg(pParse, "row value misused");
- }
+ tdsqlite3VectorErrorMsg(pParse, pIn->pLeft);
return 1;
}
return 0;
@@ -95482,7 +105437,7 @@ SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){
** See the separate in-operator.md documentation file in the canonical
** SQLite source tree for additional information.
*/
-static void sqlite3ExprCodeIN(
+static void tdsqlite3ExprCodeIN(
Parse *pParse, /* Parsing and code generating context */
Expr *pExpr, /* The IN expression */
int destIfFalse, /* Jump here if LHS is not contained in the RHS */
@@ -95504,26 +105459,28 @@ static void sqlite3ExprCodeIN(
int addrTruthOp; /* Address of opcode that determines the IN is true */
int destNotNull; /* Jump here if a comparison is not true in step 6 */
int addrTop; /* Top of the step-6 loop */
+ int iTab = 0; /* Index to use */
pLeft = pExpr->pLeft;
- if( sqlite3ExprCheckIN(pParse, pExpr) ) return;
+ if( tdsqlite3ExprCheckIN(pParse, pExpr) ) return;
zAff = exprINAffinity(pParse, pExpr);
- nVector = sqlite3ExprVectorSize(pExpr->pLeft);
- aiMap = (int*)sqlite3DbMallocZero(
+ nVector = tdsqlite3ExprVectorSize(pExpr->pLeft);
+ aiMap = (int*)tdsqlite3DbMallocZero(
pParse->db, nVector*(sizeof(int) + sizeof(char)) + 1
);
- if( pParse->db->mallocFailed ) goto sqlite3ExprCodeIN_oom_error;
+ if( pParse->db->mallocFailed ) goto tdsqlite3ExprCodeIN_oom_error;
/* Attempt to compute the RHS. After this step, if anything other than
- ** IN_INDEX_NOOP is returned, the table opened ith cursor pExpr->iTable
+ ** IN_INDEX_NOOP is returned, the table opened with cursor iTab
** contains the values that make up the RHS. If IN_INDEX_NOOP is returned,
** the RHS has not yet been coded. */
v = pParse->pVdbe;
assert( v!=0 ); /* OOM detected prior to this routine */
VdbeNoopComment((v, "begin IN expr"));
- eType = sqlite3FindInIndex(pParse, pExpr,
+ eType = tdsqlite3FindInIndex(pParse, pExpr,
IN_INDEX_MEMBERSHIP | IN_INDEX_NOOP_OK,
- destIfFalse==destIfNull ? 0 : &rRhsHasNull, aiMap);
+ destIfFalse==destIfNull ? 0 : &rRhsHasNull,
+ aiMap, &iTab);
assert( pParse->nErr || nVector==1 || eType==IN_INDEX_EPH
|| eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC
@@ -95542,12 +105499,11 @@ static void sqlite3ExprCodeIN(
** vector, then it is stored in an array of nVector registers starting
** at r1.
**
- ** sqlite3FindInIndex() might have reordered the fields of the LHS vector
+ ** tdsqlite3FindInIndex() might have reordered the fields of the LHS vector
** so that the fields are in the same order as an existing index. The
** aiMap[] array contains a mapping from the original LHS field order to
** the field order that matches the RHS index.
*/
- sqlite3ExprCachePush(pParse);
rLhsOrig = exprCodeVector(pParse, pLeft, &iDummy);
for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */
if( i==nVector ){
@@ -95555,13 +105511,13 @@ static void sqlite3ExprCodeIN(
rLhs = rLhsOrig;
}else{
/* Need to reorder the LHS fields according to aiMap */
- rLhs = sqlite3GetTempRange(pParse, nVector);
+ rLhs = tdsqlite3GetTempRange(pParse, nVector);
for(i=0; i<nVector; i++){
- sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0);
+ tdsqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0);
}
}
- /* If sqlite3FindInIndex() did not find or create an index that is
+ /* If tdsqlite3FindInIndex() did not find or create an index that is
** suitable for evaluating the IN operator, then evaluate using a
** sequence of comparisons.
**
@@ -95569,42 +105525,56 @@ static void sqlite3ExprCodeIN(
*/
if( eType==IN_INDEX_NOOP ){
ExprList *pList = pExpr->x.pList;
- CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
- int labelOk = sqlite3VdbeMakeLabel(v);
+ CollSeq *pColl = tdsqlite3ExprCollSeq(pParse, pExpr->pLeft);
+ int labelOk = tdsqlite3VdbeMakeLabel(pParse);
int r2, regToFree;
int regCkNull = 0;
int ii;
+ int bLhsReal; /* True if the LHS of the IN has REAL affinity */
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
if( destIfNull!=destIfFalse ){
- regCkNull = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp3(v, OP_BitAnd, rLhs, rLhs, regCkNull);
+ regCkNull = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_BitAnd, rLhs, rLhs, regCkNull);
}
+ bLhsReal = tdsqlite3ExprAffinity(pExpr->pLeft)==SQLITE_AFF_REAL;
for(ii=0; ii<pList->nExpr; ii++){
- r2 = sqlite3ExprCodeTemp(pParse, pList->a[ii].pExpr, &regToFree);
- if( regCkNull && sqlite3ExprCanBeNull(pList->a[ii].pExpr) ){
- sqlite3VdbeAddOp3(v, OP_BitAnd, regCkNull, r2, regCkNull);
+ if( bLhsReal ){
+ r2 = regToFree = tdsqlite3GetTempReg(pParse);
+ tdsqlite3ExprCode(pParse, pList->a[ii].pExpr, r2);
+ tdsqlite3VdbeAddOp4(v, OP_Affinity, r2, 1, 0, "E", P4_STATIC);
+ }else{
+ r2 = tdsqlite3ExprCodeTemp(pParse, pList->a[ii].pExpr, &regToFree);
+ }
+ if( regCkNull && tdsqlite3ExprCanBeNull(pList->a[ii].pExpr) ){
+ tdsqlite3VdbeAddOp3(v, OP_BitAnd, regCkNull, r2, regCkNull);
}
+ tdsqlite3ReleaseTempReg(pParse, regToFree);
if( ii<pList->nExpr-1 || destIfNull!=destIfFalse ){
- sqlite3VdbeAddOp4(v, OP_Eq, rLhs, labelOk, r2,
+ int op = rLhs!=r2 ? OP_Eq : OP_NotNull;
+ tdsqlite3VdbeAddOp4(v, op, rLhs, labelOk, r2,
(void*)pColl, P4_COLLSEQ);
- VdbeCoverageIf(v, ii<pList->nExpr-1);
- VdbeCoverageIf(v, ii==pList->nExpr-1);
- sqlite3VdbeChangeP5(v, zAff[0]);
+ VdbeCoverageIf(v, ii<pList->nExpr-1 && op==OP_Eq);
+ VdbeCoverageIf(v, ii==pList->nExpr-1 && op==OP_Eq);
+ VdbeCoverageIf(v, ii<pList->nExpr-1 && op==OP_NotNull);
+ VdbeCoverageIf(v, ii==pList->nExpr-1 && op==OP_NotNull);
+ tdsqlite3VdbeChangeP5(v, zAff[0]);
}else{
+ int op = rLhs!=r2 ? OP_Ne : OP_IsNull;
assert( destIfNull==destIfFalse );
- sqlite3VdbeAddOp4(v, OP_Ne, rLhs, destIfFalse, r2,
- (void*)pColl, P4_COLLSEQ); VdbeCoverage(v);
- sqlite3VdbeChangeP5(v, zAff[0] | SQLITE_JUMPIFNULL);
+ tdsqlite3VdbeAddOp4(v, op, rLhs, destIfFalse, r2,
+ (void*)pColl, P4_COLLSEQ);
+ VdbeCoverageIf(v, op==OP_Ne);
+ VdbeCoverageIf(v, op==OP_IsNull);
+ tdsqlite3VdbeChangeP5(v, zAff[0] | SQLITE_JUMPIFNULL);
}
- sqlite3ReleaseTempReg(pParse, regToFree);
}
if( regCkNull ){
- sqlite3VdbeAddOp2(v, OP_IsNull, regCkNull, destIfNull); VdbeCoverage(v);
- sqlite3VdbeGoto(v, destIfFalse);
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, regCkNull, destIfNull); VdbeCoverage(v);
+ tdsqlite3VdbeGoto(v, destIfFalse);
}
- sqlite3VdbeResolveLabel(v, labelOk);
- sqlite3ReleaseTempReg(pParse, regCkNull);
- goto sqlite3ExprCodeIN_finished;
+ tdsqlite3VdbeResolveLabel(v, labelOk);
+ tdsqlite3ReleaseTempReg(pParse, regCkNull);
+ goto tdsqlite3ExprCodeIN_finished;
}
/* Step 2: Check to see if the LHS contains any NULL columns. If the
@@ -95614,12 +105584,13 @@ static void sqlite3ExprCodeIN(
if( destIfNull==destIfFalse ){
destStep2 = destIfFalse;
}else{
- destStep2 = destStep6 = sqlite3VdbeMakeLabel(v);
+ destStep2 = destStep6 = tdsqlite3VdbeMakeLabel(pParse);
}
+ if( pParse->nErr ) goto tdsqlite3ExprCodeIN_finished;
for(i=0; i<nVector; i++){
- Expr *p = sqlite3VectorFieldSubexpr(pExpr->pLeft, i);
- if( sqlite3ExprCanBeNull(p) ){
- sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2);
+ Expr *p = tdsqlite3VectorFieldSubexpr(pExpr->pLeft, i);
+ if( tdsqlite3ExprCanBeNull(p) ){
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2);
VdbeCoverage(v);
}
}
@@ -95632,19 +105603,19 @@ static void sqlite3ExprCodeIN(
/* In this case, the RHS is the ROWID of table b-tree and so we also
** know that the RHS is non-NULL. Hence, we combine steps 3 and 4
** into a single opcode. */
- sqlite3VdbeAddOp3(v, OP_SeekRowid, pExpr->iTable, destIfFalse, rLhs);
+ tdsqlite3VdbeAddOp3(v, OP_SeekRowid, iTab, destIfFalse, rLhs);
VdbeCoverage(v);
- addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */
+ addrTruthOp = tdsqlite3VdbeAddOp0(v, OP_Goto); /* Return True */
}else{
- sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector);
+ tdsqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector);
if( destIfFalse==destIfNull ){
/* Combine Step 3 and Step 5 into a single opcode */
- sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable, destIfFalse,
+ tdsqlite3VdbeAddOp4Int(v, OP_NotFound, iTab, destIfFalse,
rLhs, nVector); VdbeCoverage(v);
- goto sqlite3ExprCodeIN_finished;
+ goto tdsqlite3ExprCodeIN_finished;
}
/* Ordinary Step 3, for the case where FALSE and NULL are distinct */
- addrTruthOp = sqlite3VdbeAddOp4Int(v, OP_Found, pExpr->iTable, 0,
+ addrTruthOp = tdsqlite3VdbeAddOp4Int(v, OP_Found, iTab, 0,
rLhs, nVector); VdbeCoverage(v);
}
@@ -95652,14 +105623,14 @@ static void sqlite3ExprCodeIN(
** an match on the search above, then the result must be FALSE.
*/
if( rRhsHasNull && nVector==1 ){
- sqlite3VdbeAddOp2(v, OP_NotNull, rRhsHasNull, destIfFalse);
+ tdsqlite3VdbeAddOp2(v, OP_NotNull, rRhsHasNull, destIfFalse);
VdbeCoverage(v);
}
/* Step 5. If we do not care about the difference between NULL and
** FALSE, then just return false.
*/
- if( destIfFalse==destIfNull ) sqlite3VdbeGoto(v, destIfFalse);
+ if( destIfFalse==destIfNull ) tdsqlite3VdbeGoto(v, destIfFalse);
/* Step 6: Loop through rows of the RHS. Compare each row to the LHS.
** If any comparison is NULL, then the result is NULL. If all
@@ -95668,11 +105639,11 @@ static void sqlite3ExprCodeIN(
** For a scalar LHS, it is sufficient to check just the first row
** of the RHS.
*/
- if( destStep6 ) sqlite3VdbeResolveLabel(v, destStep6);
- addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse);
+ if( destStep6 ) tdsqlite3VdbeResolveLabel(v, destStep6);
+ addrTop = tdsqlite3VdbeAddOp2(v, OP_Rewind, iTab, destIfFalse);
VdbeCoverage(v);
if( nVector>1 ){
- destNotNull = sqlite3VdbeMakeLabel(v);
+ destNotNull = tdsqlite3VdbeMakeLabel(pParse);
}else{
/* For nVector==1, combine steps 6 and 7 by immediately returning
** FALSE if the first comparison is not NULL */
@@ -95681,36 +105652,35 @@ static void sqlite3ExprCodeIN(
for(i=0; i<nVector; i++){
Expr *p;
CollSeq *pColl;
- int r3 = sqlite3GetTempReg(pParse);
- p = sqlite3VectorFieldSubexpr(pLeft, i);
- pColl = sqlite3ExprCollSeq(pParse, p);
- sqlite3VdbeAddOp3(v, OP_Column, pExpr->iTable, i, r3);
- sqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3,
+ int r3 = tdsqlite3GetTempReg(pParse);
+ p = tdsqlite3VectorFieldSubexpr(pLeft, i);
+ pColl = tdsqlite3ExprCollSeq(pParse, p);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iTab, i, r3);
+ tdsqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3,
(void*)pColl, P4_COLLSEQ);
VdbeCoverage(v);
- sqlite3ReleaseTempReg(pParse, r3);
+ tdsqlite3ReleaseTempReg(pParse, r3);
}
- sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfNull);
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, destIfNull);
if( nVector>1 ){
- sqlite3VdbeResolveLabel(v, destNotNull);
- sqlite3VdbeAddOp2(v, OP_Next, pExpr->iTable, addrTop+1);
+ tdsqlite3VdbeResolveLabel(v, destNotNull);
+ tdsqlite3VdbeAddOp2(v, OP_Next, iTab, addrTop+1);
VdbeCoverage(v);
/* Step 7: If we reach this point, we know that the result must
** be false. */
- sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse);
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse);
}
/* Jumps here in order to return true. */
- sqlite3VdbeJumpHere(v, addrTruthOp);
+ tdsqlite3VdbeJumpHere(v, addrTruthOp);
-sqlite3ExprCodeIN_finished:
- if( rLhs!=rLhsOrig ) sqlite3ReleaseTempReg(pParse, rLhs);
- sqlite3ExprCachePop(pParse);
+tdsqlite3ExprCodeIN_finished:
+ if( rLhs!=rLhsOrig ) tdsqlite3ReleaseTempReg(pParse, rLhs);
VdbeComment((v, "end IN expr"));
-sqlite3ExprCodeIN_oom_error:
- sqlite3DbFree(pParse->db, aiMap);
- sqlite3DbFree(pParse->db, zAff);
+tdsqlite3ExprCodeIN_oom_error:
+ tdsqlite3DbFree(pParse->db, aiMap);
+ tdsqlite3DbFree(pParse->db, zAff);
}
#endif /* SQLITE_OMIT_SUBQUERY */
@@ -95726,10 +105696,10 @@ sqlite3ExprCodeIN_oom_error:
static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){
if( ALWAYS(z!=0) ){
double value;
- sqlite3AtoF(z, &value, sqlite3Strlen30(z), SQLITE_UTF8);
- assert( !sqlite3IsNaN(value) ); /* The new AtoF never returns NaN */
+ tdsqlite3AtoF(z, &value, tdsqlite3Strlen30(z), SQLITE_UTF8);
+ assert( !tdsqlite3IsNaN(value) ); /* The new AtoF never returns NaN */
if( negateFlag ) value = -value;
- sqlite3VdbeAddOp4Dup8(v, OP_Real, 0, iMem, 0, (u8*)&value, P4_REAL);
+ tdsqlite3VdbeAddOp4Dup8(v, OP_Real, 0, iMem, 0, (u8*)&value, P4_REAL);
}
}
#endif
@@ -95747,177 +105717,38 @@ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){
int i = pExpr->u.iValue;
assert( i>=0 );
if( negFlag ) i = -i;
- sqlite3VdbeAddOp2(v, OP_Integer, i, iMem);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, i, iMem);
}else{
int c;
i64 value;
const char *z = pExpr->u.zToken;
assert( z!=0 );
- c = sqlite3DecOrHexToI64(z, &value);
- if( c==0 || (c==2 && negFlag) ){
- if( negFlag ){ value = c==2 ? SMALLEST_INT64 : -value; }
- sqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, iMem, 0, (u8*)&value, P4_INT64);
- }else{
+ c = tdsqlite3DecOrHexToI64(z, &value);
+ if( (c==3 && !negFlag) || (c==2) || (negFlag && value==SMALLEST_INT64)){
#ifdef SQLITE_OMIT_FLOATING_POINT
- sqlite3ErrorMsg(pParse, "oversized integer: %s%s", negFlag ? "-" : "", z);
+ tdsqlite3ErrorMsg(pParse, "oversized integer: %s%s", negFlag ? "-" : "", z);
#else
#ifndef SQLITE_OMIT_HEX_INTEGER
- if( sqlite3_strnicmp(z,"0x",2)==0 ){
- sqlite3ErrorMsg(pParse, "hex literal too big: %s", z);
+ if( tdsqlite3_strnicmp(z,"0x",2)==0 ){
+ tdsqlite3ErrorMsg(pParse, "hex literal too big: %s%s", negFlag?"-":"",z);
}else
#endif
{
codeReal(v, z, negFlag, iMem);
}
#endif
- }
- }
-}
-
-/*
-** Erase column-cache entry number i
-*/
-static void cacheEntryClear(Parse *pParse, int i){
- if( pParse->aColCache[i].tempReg ){
- if( pParse->nTempReg<ArraySize(pParse->aTempReg) ){
- pParse->aTempReg[pParse->nTempReg++] = pParse->aColCache[i].iReg;
- }
- }
- pParse->nColCache--;
- if( i<pParse->nColCache ){
- pParse->aColCache[i] = pParse->aColCache[pParse->nColCache];
- }
-}
-
-
-/*
-** Record in the column cache that a particular column from a
-** particular table is stored in a particular register.
-*/
-SQLITE_PRIVATE void sqlite3ExprCacheStore(Parse *pParse, int iTab, int iCol, int iReg){
- int i;
- int minLru;
- int idxLru;
- struct yColCache *p;
-
- /* Unless an error has occurred, register numbers are always positive. */
- assert( iReg>0 || pParse->nErr || pParse->db->mallocFailed );
- assert( iCol>=-1 && iCol<32768 ); /* Finite column numbers */
-
- /* The SQLITE_ColumnCache flag disables the column cache. This is used
- ** for testing only - to verify that SQLite always gets the same answer
- ** with and without the column cache.
- */
- if( OptimizationDisabled(pParse->db, SQLITE_ColumnCache) ) return;
-
- /* First replace any existing entry.
- **
- ** Actually, the way the column cache is currently used, we are guaranteed
- ** that the object will never already be in cache. Verify this guarantee.
- */
-#ifndef NDEBUG
- for(i=0, p=pParse->aColCache; i<pParse->nColCache; i++, p++){
- assert( p->iTable!=iTab || p->iColumn!=iCol );
- }
-#endif
-
- /* If the cache is already full, delete the least recently used entry */
- if( pParse->nColCache>=SQLITE_N_COLCACHE ){
- minLru = 0x7fffffff;
- idxLru = -1;
- for(i=0, p=pParse->aColCache; i<SQLITE_N_COLCACHE; i++, p++){
- if( p->lru<minLru ){
- idxLru = i;
- minLru = p->lru;
- }
- }
- p = &pParse->aColCache[idxLru];
- }else{
- p = &pParse->aColCache[pParse->nColCache++];
- }
-
- /* Add the new entry to the end of the cache */
- p->iLevel = pParse->iCacheLevel;
- p->iTable = iTab;
- p->iColumn = iCol;
- p->iReg = iReg;
- p->tempReg = 0;
- p->lru = pParse->iCacheCnt++;
-}
-
-/*
-** Indicate that registers between iReg..iReg+nReg-1 are being overwritten.
-** Purge the range of registers from the column cache.
-*/
-SQLITE_PRIVATE void sqlite3ExprCacheRemove(Parse *pParse, int iReg, int nReg){
- int i = 0;
- while( i<pParse->nColCache ){
- struct yColCache *p = &pParse->aColCache[i];
- if( p->iReg >= iReg && p->iReg < iReg+nReg ){
- cacheEntryClear(pParse, i);
- }else{
- i++;
- }
- }
-}
-
-/*
-** Remember the current column cache context. Any new entries added
-** added to the column cache after this call are removed when the
-** corresponding pop occurs.
-*/
-SQLITE_PRIVATE void sqlite3ExprCachePush(Parse *pParse){
- pParse->iCacheLevel++;
-#ifdef SQLITE_DEBUG
- if( pParse->db->flags & SQLITE_VdbeAddopTrace ){
- printf("PUSH to %d\n", pParse->iCacheLevel);
- }
-#endif
-}
-
-/*
-** Remove from the column cache any entries that were added since the
-** the previous sqlite3ExprCachePush operation. In other words, restore
-** the cache to the state it was in prior the most recent Push.
-*/
-SQLITE_PRIVATE void sqlite3ExprCachePop(Parse *pParse){
- int i = 0;
- assert( pParse->iCacheLevel>=1 );
- pParse->iCacheLevel--;
-#ifdef SQLITE_DEBUG
- if( pParse->db->flags & SQLITE_VdbeAddopTrace ){
- printf("POP to %d\n", pParse->iCacheLevel);
- }
-#endif
- while( i<pParse->nColCache ){
- if( pParse->aColCache[i].iLevel>pParse->iCacheLevel ){
- cacheEntryClear(pParse, i);
}else{
- i++;
+ if( negFlag ){ value = c==3 ? SMALLEST_INT64 : -value; }
+ tdsqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, iMem, 0, (u8*)&value, P4_INT64);
}
}
}
-/*
-** When a cached column is reused, make sure that its register is
-** no longer available as a temp register. ticket #3879: that same
-** register might be in the cache in multiple places, so be sure to
-** get them all.
-*/
-static void sqlite3ExprCachePinRegister(Parse *pParse, int iReg){
- int i;
- struct yColCache *p;
- for(i=0, p=pParse->aColCache; i<pParse->nColCache; i++, p++){
- if( p->iReg==iReg ){
- p->tempReg = 0;
- }
- }
-}
/* Generate code that will load into register regOut a value that is
** appropriate for the iIdxCol-th column of index pIdx.
*/
-SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn(
+SQLITE_PRIVATE void tdsqlite3ExprCodeLoadIndexColumn(
Parse *pParse, /* The parsing context */
Index *pIdx, /* The index whose column is to be loaded */
int iTabCur, /* Cursor pointing to a table row */
@@ -95928,52 +105759,103 @@ SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn(
if( iTabCol==XN_EXPR ){
assert( pIdx->aColExpr );
assert( pIdx->aColExpr->nExpr>iIdxCol );
- pParse->iSelfTab = iTabCur;
- sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut);
+ pParse->iSelfTab = iTabCur + 1;
+ tdsqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut);
+ pParse->iSelfTab = 0;
}else{
- sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable, iTabCur,
+ tdsqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable, iTabCur,
iTabCol, regOut);
}
}
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+/*
+** Generate code that will compute the value of generated column pCol
+** and store the result in register regOut
+*/
+SQLITE_PRIVATE void tdsqlite3ExprCodeGeneratedColumn(
+ Parse *pParse,
+ Column *pCol,
+ int regOut
+){
+ int iAddr;
+ Vdbe *v = pParse->pVdbe;
+ assert( v!=0 );
+ assert( pParse->iSelfTab!=0 );
+ if( pParse->iSelfTab>0 ){
+ iAddr = tdsqlite3VdbeAddOp3(v, OP_IfNullRow, pParse->iSelfTab-1, 0, regOut);
+ }else{
+ iAddr = 0;
+ }
+ tdsqlite3ExprCode(pParse, pCol->pDflt, regOut);
+ if( pCol->affinity>=SQLITE_AFF_TEXT ){
+ tdsqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1);
+ }
+ if( iAddr ) tdsqlite3VdbeJumpHere(v, iAddr);
+}
+#endif /* SQLITE_OMIT_GENERATED_COLUMNS */
+
/*
** Generate code to extract the value of the iCol-th column of a table.
*/
-SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable(
- Vdbe *v, /* The VDBE under construction */
+SQLITE_PRIVATE void tdsqlite3ExprCodeGetColumnOfTable(
+ Vdbe *v, /* Parsing context */
Table *pTab, /* The table containing the value */
int iTabCur, /* The table cursor. Or the PK cursor for WITHOUT ROWID */
int iCol, /* Index of the column to extract */
int regOut /* Extract the value into this register */
){
+ Column *pCol;
+ assert( v!=0 );
+ if( pTab==0 ){
+ tdsqlite3VdbeAddOp3(v, OP_Column, iTabCur, iCol, regOut);
+ return;
+ }
if( iCol<0 || iCol==pTab->iPKey ){
- sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut);
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut);
}else{
- int op = IsVirtual(pTab) ? OP_VColumn : OP_Column;
- int x = iCol;
- if( !HasRowid(pTab) && !IsVirtual(pTab) ){
- x = sqlite3ColumnOfIndex(sqlite3PrimaryKeyIndex(pTab), iCol);
+ int op;
+ int x;
+ if( IsVirtual(pTab) ){
+ op = OP_VColumn;
+ x = iCol;
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ }else if( (pCol = &pTab->aCol[iCol])->colFlags & COLFLAG_VIRTUAL ){
+ Parse *pParse = tdsqlite3VdbeParser(v);
+ if( pCol->colFlags & COLFLAG_BUSY ){
+ tdsqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pCol->zName);
+ }else{
+ int savedSelfTab = pParse->iSelfTab;
+ pCol->colFlags |= COLFLAG_BUSY;
+ pParse->iSelfTab = iTabCur+1;
+ tdsqlite3ExprCodeGeneratedColumn(pParse, pCol, regOut);
+ pParse->iSelfTab = savedSelfTab;
+ pCol->colFlags &= ~COLFLAG_BUSY;
+ }
+ return;
+#endif
+ }else if( !HasRowid(pTab) ){
+ testcase( iCol!=tdsqlite3TableColumnToStorage(pTab, iCol) );
+ x = tdsqlite3TableColumnToIndex(tdsqlite3PrimaryKeyIndex(pTab), iCol);
+ op = OP_Column;
+ }else{
+ x = tdsqlite3TableColumnToStorage(pTab,iCol);
+ testcase( x!=iCol );
+ op = OP_Column;
}
- sqlite3VdbeAddOp3(v, op, iTabCur, x, regOut);
- }
- if( iCol>=0 ){
- sqlite3ColumnDefault(v, pTab, iCol, regOut);
+ tdsqlite3VdbeAddOp3(v, op, iTabCur, x, regOut);
+ tdsqlite3ColumnDefault(v, pTab, iCol, regOut);
}
}
/*
** Generate code that will extract the iColumn-th column from
-** table pTab and store the column value in a register.
-**
-** An effort is made to store the column value in register iReg. This
-** is not garanteeed for GetColumn() - the result can be stored in
-** any register. But the result is guaranteed to land in register iReg
-** for GetColumnToReg().
+** table pTab and store the column value in register iReg.
**
** There must be an open cursor to pTab in iTable when this routine
** is called. If iColumn<0 then code is generated that extracts the rowid.
*/
-SQLITE_PRIVATE int sqlite3ExprCodeGetColumn(
+SQLITE_PRIVATE int tdsqlite3ExprCodeGetColumn(
Parse *pParse, /* Parsing and code generating context */
Table *pTab, /* Description of the table we are reading from */
int iColumn, /* Index of the table column */
@@ -95981,103 +105863,30 @@ SQLITE_PRIVATE int sqlite3ExprCodeGetColumn(
int iReg, /* Store results here */
u8 p5 /* P5 value for OP_Column + FLAGS */
){
- Vdbe *v = pParse->pVdbe;
- int i;
- struct yColCache *p;
-
- for(i=0, p=pParse->aColCache; i<pParse->nColCache; i++, p++){
- if( p->iTable==iTable && p->iColumn==iColumn ){
- p->lru = pParse->iCacheCnt++;
- sqlite3ExprCachePinRegister(pParse, p->iReg);
- return p->iReg;
- }
- }
- assert( v!=0 );
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iTable, iColumn, iReg);
+ assert( pParse->pVdbe!=0 );
+ tdsqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pTab, iTable, iColumn, iReg);
if( p5 ){
- sqlite3VdbeChangeP5(v, p5);
- }else{
- sqlite3ExprCacheStore(pParse, iTable, iColumn, iReg);
+ VdbeOp *pOp = tdsqlite3VdbeGetOp(pParse->pVdbe,-1);
+ if( pOp->opcode==OP_Column ) pOp->p5 = p5;
}
return iReg;
}
-SQLITE_PRIVATE void sqlite3ExprCodeGetColumnToReg(
- Parse *pParse, /* Parsing and code generating context */
- Table *pTab, /* Description of the table we are reading from */
- int iColumn, /* Index of the table column */
- int iTable, /* The cursor pointing to the table */
- int iReg /* Store results here */
-){
- int r1 = sqlite3ExprCodeGetColumn(pParse, pTab, iColumn, iTable, iReg, 0);
- if( r1!=iReg ) sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, r1, iReg);
-}
-
-
-/*
-** Clear all column cache entries.
-*/
-SQLITE_PRIVATE void sqlite3ExprCacheClear(Parse *pParse){
- int i;
-
-#if SQLITE_DEBUG
- if( pParse->db->flags & SQLITE_VdbeAddopTrace ){
- printf("CLEAR\n");
- }
-#endif
- for(i=0; i<pParse->nColCache; i++){
- if( pParse->aColCache[i].tempReg
- && pParse->nTempReg<ArraySize(pParse->aTempReg)
- ){
- pParse->aTempReg[pParse->nTempReg++] = pParse->aColCache[i].iReg;
- }
- }
- pParse->nColCache = 0;
-}
-
-/*
-** Record the fact that an affinity change has occurred on iCount
-** registers starting with iStart.
-*/
-SQLITE_PRIVATE void sqlite3ExprCacheAffinityChange(Parse *pParse, int iStart, int iCount){
- sqlite3ExprCacheRemove(pParse, iStart, iCount);
-}
/*
** Generate code to move content from registers iFrom...iFrom+nReg-1
-** over to iTo..iTo+nReg-1. Keep the column cache up-to-date.
-*/
-SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo, int nReg){
- assert( iFrom>=iTo+nReg || iFrom+nReg<=iTo );
- sqlite3VdbeAddOp3(pParse->pVdbe, OP_Move, iFrom, iTo, nReg);
- sqlite3ExprCacheRemove(pParse, iFrom, nReg);
-}
-
-#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST)
-/*
-** Return true if any register in the range iFrom..iTo (inclusive)
-** is used as part of the column cache.
-**
-** This routine is used within assert() and testcase() macros only
-** and does not appear in a normal build.
+** over to iTo..iTo+nReg-1.
*/
-static int usedAsColumnCache(Parse *pParse, int iFrom, int iTo){
- int i;
- struct yColCache *p;
- for(i=0, p=pParse->aColCache; i<pParse->nColCache; i++, p++){
- int r = p->iReg;
- if( r>=iFrom && r<=iTo ) return 1; /*NO_TEST*/
- }
- return 0;
+SQLITE_PRIVATE void tdsqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo, int nReg){
+ tdsqlite3VdbeAddOp3(pParse->pVdbe, OP_Move, iFrom, iTo, nReg);
}
-#endif /* SQLITE_DEBUG || SQLITE_COVERAGE_TEST */
-
/*
** Convert a scalar expression node to a TK_REGISTER referencing
** register iReg. The caller must ensure that iReg already contains
** the correct value for the expression.
*/
-static void exprToRegister(Expr *p, int iReg){
+static void exprToRegister(Expr *pExpr, int iReg){
+ Expr *p = tdsqlite3ExprSkipCollateAndLikely(pExpr);
p->op2 = p->op;
p->op = TK_REGISTER;
p->iTable = iReg;
@@ -96096,25 +105905,132 @@ static void exprToRegister(Expr *p, int iReg){
*/
static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){
int iResult;
- int nResult = sqlite3ExprVectorSize(p);
+ int nResult = tdsqlite3ExprVectorSize(p);
if( nResult==1 ){
- iResult = sqlite3ExprCodeTemp(pParse, p, piFreeable);
+ iResult = tdsqlite3ExprCodeTemp(pParse, p, piFreeable);
}else{
*piFreeable = 0;
if( p->op==TK_SELECT ){
- iResult = sqlite3CodeSubselect(pParse, p, 0, 0);
+#if SQLITE_OMIT_SUBQUERY
+ iResult = 0;
+#else
+ iResult = tdsqlite3CodeSubselect(pParse, p);
+#endif
}else{
int i;
iResult = pParse->nMem+1;
pParse->nMem += nResult;
for(i=0; i<nResult; i++){
- sqlite3ExprCodeFactorable(pParse, p->x.pList->a[i].pExpr, i+iResult);
+ tdsqlite3ExprCodeFactorable(pParse, p->x.pList->a[i].pExpr, i+iResult);
}
}
}
return iResult;
}
+/*
+** Generate code to implement special SQL functions that are implemented
+** in-line rather than by using the usual callbacks.
+*/
+static int exprCodeInlineFunction(
+ Parse *pParse, /* Parsing context */
+ ExprList *pFarg, /* List of function arguments */
+ int iFuncId, /* Function ID. One of the INTFUNC_... values */
+ int target /* Store function result in this register */
+){
+ int nFarg;
+ Vdbe *v = pParse->pVdbe;
+ assert( v!=0 );
+ assert( pFarg!=0 );
+ nFarg = pFarg->nExpr;
+ assert( nFarg>0 ); /* All in-line functions have at least one argument */
+ switch( iFuncId ){
+ case INLINEFUNC_coalesce: {
+ /* Attempt a direct implementation of the built-in COALESCE() and
+ ** IFNULL() functions. This avoids unnecessary evaluation of
+ ** arguments past the first non-NULL argument.
+ */
+ int endCoalesce = tdsqlite3VdbeMakeLabel(pParse);
+ int i;
+ assert( nFarg>=2 );
+ tdsqlite3ExprCode(pParse, pFarg->a[0].pExpr, target);
+ for(i=1; i<nFarg; i++){
+ tdsqlite3VdbeAddOp2(v, OP_NotNull, target, endCoalesce);
+ VdbeCoverage(v);
+ tdsqlite3ExprCode(pParse, pFarg->a[i].pExpr, target);
+ }
+ if( tdsqlite3VdbeGetOp(v, -1)->opcode==OP_Copy ){
+ tdsqlite3VdbeChangeP5(v, 1); /* Tag trailing OP_Copy as not mergable */
+ }
+ tdsqlite3VdbeResolveLabel(v, endCoalesce);
+ break;
+ }
+
+ default: {
+ /* The UNLIKELY() function is a no-op. The result is the value
+ ** of the first argument.
+ */
+ assert( nFarg==1 || nFarg==2 );
+ target = tdsqlite3ExprCodeTarget(pParse, pFarg->a[0].pExpr, target);
+ break;
+ }
+
+ /***********************************************************************
+ ** Test-only SQL functions that are only usable if enabled
+ ** via SQLITE_TESTCTRL_INTERNAL_FUNCTIONS
+ */
+ case INLINEFUNC_expr_compare: {
+ /* Compare two expressions using tdsqlite3ExprCompare() */
+ assert( nFarg==2 );
+ tdsqlite3VdbeAddOp2(v, OP_Integer,
+ tdsqlite3ExprCompare(0,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1),
+ target);
+ break;
+ }
+
+ case INLINEFUNC_expr_implies_expr: {
+ /* Compare two expressions using tdsqlite3ExprImpliesExpr() */
+ assert( nFarg==2 );
+ tdsqlite3VdbeAddOp2(v, OP_Integer,
+ tdsqlite3ExprImpliesExpr(pParse,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1),
+ target);
+ break;
+ }
+
+ case INLINEFUNC_implies_nonnull_row: {
+ /* REsult of tdsqlite3ExprImpliesNonNullRow() */
+ Expr *pA1;
+ assert( nFarg==2 );
+ pA1 = pFarg->a[1].pExpr;
+ if( pA1->op==TK_COLUMN ){
+ tdsqlite3VdbeAddOp2(v, OP_Integer,
+ tdsqlite3ExprImpliesNonNullRow(pFarg->a[0].pExpr,pA1->iTable),
+ target);
+ }else{
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ }
+ break;
+ }
+
+#ifdef SQLITE_DEBUG
+ case INLINEFUNC_affinity: {
+ /* The AFFINITY() function evaluates to a string that describes
+ ** the type affinity of the argument. This is used for testing of
+ ** the SQLite type logic.
+ */
+ const char *azAff[] = { "blob", "text", "numeric", "integer", "real" };
+ char aff;
+ assert( nFarg==1 );
+ aff = tdsqlite3ExprAffinity(pFarg->a[0].pExpr);
+ tdsqlite3VdbeLoadString(v, target,
+ (aff<=SQLITE_AFF_NONE) ? "none" : azAff[aff-SQLITE_AFF_BLOB]);
+ break;
+ }
+#endif
+ }
+ return target;
+}
+
/*
** Generate code into the current Vdbe to evaluate the given
@@ -96127,7 +106043,7 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){
** must check the return code and move the results to the desired
** register.
*/
-SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
+SQLITE_PRIVATE int tdsqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
Vdbe *v = pParse->pVdbe; /* The VM under construction */
int op; /* The opcode being coded */
int inReg = target; /* Results stored in register inReg */
@@ -96143,6 +106059,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
return 0;
}
+expr_code_doover:
if( pExpr==0 ){
op = TK_NULL;
}else{
@@ -96156,7 +106073,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
assert( pCol->iMem>0 );
return pCol->iMem;
}else if( pAggInfo->useSortingIdx ){
- sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab,
+ tdsqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab,
pCol->iSorterColumn, target);
return target;
}
@@ -96164,24 +106081,99 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
}
case TK_COLUMN: {
int iTab = pExpr->iTable;
+ int iReg;
+ if( ExprHasProperty(pExpr, EP_FixedCol) ){
+ /* This COLUMN expression is really a constant due to WHERE clause
+ ** constraints, and that constant is coded by the pExpr->pLeft
+ ** expresssion. However, make sure the constant has the correct
+ ** datatype by applying the Affinity of the table column to the
+ ** constant.
+ */
+ int aff;
+ iReg = tdsqlite3ExprCodeTarget(pParse, pExpr->pLeft,target);
+ if( pExpr->y.pTab ){
+ aff = tdsqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn);
+ }else{
+ aff = pExpr->affExpr;
+ }
+ if( aff>SQLITE_AFF_BLOB ){
+ static const char zAff[] = "B\000C\000D\000E";
+ assert( SQLITE_AFF_BLOB=='A' );
+ assert( SQLITE_AFF_TEXT=='B' );
+ if( iReg!=target ){
+ tdsqlite3VdbeAddOp2(v, OP_SCopy, iReg, target);
+ iReg = target;
+ }
+ tdsqlite3VdbeAddOp4(v, OP_Affinity, iReg, 1, 0,
+ &zAff[(aff-'B')*2], P4_STATIC);
+ }
+ return iReg;
+ }
if( iTab<0 ){
- if( pParse->ckBase>0 ){
- /* Generating CHECK constraints or inserting into partial index */
- return pExpr->iColumn + pParse->ckBase;
+ if( pParse->iSelfTab<0 ){
+ /* Other columns in the same row for CHECK constraints or
+ ** generated columns or for inserting into partial index.
+ ** The row is unpacked into registers beginning at
+ ** 0-(pParse->iSelfTab). The rowid (if any) is in a register
+ ** immediately prior to the first column.
+ */
+ Column *pCol;
+ Table *pTab = pExpr->y.pTab;
+ int iSrc;
+ int iCol = pExpr->iColumn;
+ assert( pTab!=0 );
+ assert( iCol>=XN_ROWID );
+ assert( iCol<pTab->nCol );
+ if( iCol<0 ){
+ return -1-pParse->iSelfTab;
+ }
+ pCol = pTab->aCol + iCol;
+ testcase( iCol!=tdsqlite3TableColumnToStorage(pTab,iCol) );
+ iSrc = tdsqlite3TableColumnToStorage(pTab, iCol) - pParse->iSelfTab;
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ if( pCol->colFlags & COLFLAG_GENERATED ){
+ if( pCol->colFlags & COLFLAG_BUSY ){
+ tdsqlite3ErrorMsg(pParse, "generated column loop on \"%s\"",
+ pCol->zName);
+ return 0;
+ }
+ pCol->colFlags |= COLFLAG_BUSY;
+ if( pCol->colFlags & COLFLAG_NOTAVAIL ){
+ tdsqlite3ExprCodeGeneratedColumn(pParse, pCol, iSrc);
+ }
+ pCol->colFlags &= ~(COLFLAG_BUSY|COLFLAG_NOTAVAIL);
+ return iSrc;
+ }else
+#endif /* SQLITE_OMIT_GENERATED_COLUMNS */
+ if( pCol->affinity==SQLITE_AFF_REAL ){
+ tdsqlite3VdbeAddOp2(v, OP_SCopy, iSrc, target);
+ tdsqlite3VdbeAddOp1(v, OP_RealAffinity, target);
+ return target;
+ }else{
+ return iSrc;
+ }
}else{
/* Coding an expression that is part of an index where column names
** in the index refer to the table to which the index belongs */
- iTab = pParse->iSelfTab;
+ iTab = pParse->iSelfTab - 1;
}
}
- return sqlite3ExprCodeGetColumn(pParse, pExpr->pTab,
+ iReg = tdsqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab,
pExpr->iColumn, iTab, target,
pExpr->op2);
+ if( pExpr->y.pTab==0 && pExpr->affExpr==SQLITE_AFF_REAL ){
+ tdsqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
+ }
+ return iReg;
}
case TK_INTEGER: {
codeInteger(pParse, pExpr, 0, target);
return target;
}
+ case TK_TRUEFALSE: {
+ tdsqlite3VdbeAddOp2(v, OP_Integer, tdsqlite3ExprTruthValue(pExpr), target);
+ return target;
+ }
#ifndef SQLITE_OMIT_FLOATING_POINT
case TK_FLOAT: {
assert( !ExprHasProperty(pExpr, EP_IntValue) );
@@ -96191,11 +106183,16 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
#endif
case TK_STRING: {
assert( !ExprHasProperty(pExpr, EP_IntValue) );
- sqlite3VdbeLoadString(v, target, pExpr->u.zToken);
+ tdsqlite3VdbeLoadString(v, target, pExpr->u.zToken);
return target;
}
- case TK_NULL: {
- sqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ default: {
+ /* Make NULL the default case so that if a bug causes an illegal
+ ** Expr node to be passed into this function, it will be handled
+ ** sanely and not crash. But keep the assert() to bring the problem
+ ** to the attention of the developers. */
+ assert( op==TK_NULL );
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, target);
return target;
}
#ifndef SQLITE_OMIT_BLOB_LITERAL
@@ -96207,10 +106204,10 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
assert( pExpr->u.zToken[0]=='x' || pExpr->u.zToken[0]=='X' );
assert( pExpr->u.zToken[1]=='\'' );
z = &pExpr->u.zToken[2];
- n = sqlite3Strlen30(z) - 1;
+ n = tdsqlite3Strlen30(z) - 1;
assert( z[n]=='\'' );
- zBlob = sqlite3HexToBlob(sqlite3VdbeDb(v), z, n);
- sqlite3VdbeAddOp4(v, OP_Blob, n/2, target, 0, zBlob, P4_DYNAMIC);
+ zBlob = tdsqlite3HexToBlob(tdsqlite3VdbeDb(v), z, n);
+ tdsqlite3VdbeAddOp4(v, OP_Blob, n/2, target, 0, zBlob, P4_DYNAMIC);
return target;
}
#endif
@@ -96218,11 +106215,12 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
assert( !ExprHasProperty(pExpr, EP_IntValue) );
assert( pExpr->u.zToken!=0 );
assert( pExpr->u.zToken[0]!=0 );
- sqlite3VdbeAddOp2(v, OP_Variable, pExpr->iColumn, target);
+ tdsqlite3VdbeAddOp2(v, OP_Variable, pExpr->iColumn, target);
if( pExpr->u.zToken[1]!=0 ){
- assert( pExpr->u.zToken[0]=='?'
- || strcmp(pExpr->u.zToken, pParse->azVar[pExpr->iColumn-1])==0 );
- sqlite3VdbeChangeP4(v, -1, pParse->azVar[pExpr->iColumn-1], P4_STATIC);
+ const char *z = tdsqlite3VListNumToName(pParse->pVList, pExpr->iColumn);
+ assert( pExpr->u.zToken[0]=='?' || (z && !strcmp(pExpr->u.zToken, z)) );
+ pParse->pVList[0] = 0; /* Indicate VList may no longer be enlarged */
+ tdsqlite3VdbeAppendP4(v, (char*)z, P4_STATIC);
}
return target;
}
@@ -96232,15 +106230,13 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
#ifndef SQLITE_OMIT_CAST
case TK_CAST: {
/* Expressions of the form: CAST(pLeft AS token) */
- inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
+ inReg = tdsqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
if( inReg!=target ){
- sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target);
+ tdsqlite3VdbeAddOp2(v, OP_SCopy, inReg, target);
inReg = target;
}
- sqlite3VdbeAddOp2(v, OP_Cast, target,
- sqlite3AffinityType(pExpr->u.zToken, 0));
- testcase( usedAsColumnCache(pParse, inReg, inReg) );
- sqlite3ExprCacheAffinityChange(pParse, inReg, 1);
+ tdsqlite3VdbeAddOp2(v, OP_Cast, target,
+ tdsqlite3AffinityType(pExpr->u.zToken, 0));
return inReg;
}
#endif /* SQLITE_OMIT_CAST */
@@ -96256,13 +106252,14 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
case TK_NE:
case TK_EQ: {
Expr *pLeft = pExpr->pLeft;
- if( sqlite3ExprIsVector(pLeft) ){
+ if( tdsqlite3ExprIsVector(pLeft) ){
codeVectorCompare(pParse, pExpr, target, op, p5);
}else{
- r1 = sqlite3ExprCodeTemp(pParse, pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pLeft, &regFree1);
+ r2 = tdsqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
codeCompare(pParse, pLeft, pExpr->pRight, op,
- r1, r2, inReg, SQLITE_STOREP2 | p5);
+ r1, r2, inReg, SQLITE_STOREP2 | p5,
+ ExprHasProperty(pExpr,EP_Commuted));
assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le);
assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt);
@@ -96297,9 +106294,9 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
assert( TK_LSHIFT==OP_ShiftLeft ); testcase( op==TK_LSHIFT );
assert( TK_RSHIFT==OP_ShiftRight ); testcase( op==TK_RSHIFT );
assert( TK_CONCAT==OP_Concat ); testcase( op==TK_CONCAT );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
- sqlite3VdbeAddOp3(v, op, r2, r1, target);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = tdsqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ tdsqlite3VdbeAddOp3(v, op, r2, r1, target);
testcase( regFree1==0 );
testcase( regFree2==0 );
break;
@@ -96320,9 +106317,9 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
tempX.op = TK_INTEGER;
tempX.flags = EP_IntValue|EP_TokenOnly;
tempX.u.iValue = 0;
- r1 = sqlite3ExprCodeTemp(pParse, &tempX, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree2);
- sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target);
+ r1 = tdsqlite3ExprCodeTemp(pParse, &tempX, &regFree1);
+ r2 = tdsqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree2);
+ tdsqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target);
testcase( regFree2==0 );
}
break;
@@ -96331,9 +106328,21 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
case TK_NOT: {
assert( TK_BITNOT==OP_BitNot ); testcase( op==TK_BITNOT );
assert( TK_NOT==OP_Not ); testcase( op==TK_NOT );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
testcase( regFree1==0 );
- sqlite3VdbeAddOp2(v, op, r1, inReg);
+ tdsqlite3VdbeAddOp2(v, op, r1, inReg);
+ break;
+ }
+ case TK_TRUTH: {
+ int isTrue; /* IS TRUE or IS NOT TRUE */
+ int bNormal; /* IS TRUE or IS FALSE */
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ testcase( regFree1==0 );
+ isTrue = tdsqlite3ExprTruthValue(pExpr->pRight);
+ bNormal = pExpr->op2==TK_IS;
+ testcase( isTrue && bNormal);
+ testcase( !isTrue && bNormal);
+ tdsqlite3VdbeAddOp4Int(v, OP_IsTrue, r1, inReg, !isTrue, isTrue ^ bNormal);
break;
}
case TK_ISNULL:
@@ -96341,21 +106350,21 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
int addr;
assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL );
assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL );
- sqlite3VdbeAddOp2(v, OP_Integer, 1, target);
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, target);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
testcase( regFree1==0 );
- addr = sqlite3VdbeAddOp1(v, op, r1);
+ addr = tdsqlite3VdbeAddOp1(v, op, r1);
VdbeCoverageIf(v, op==TK_ISNULL);
VdbeCoverageIf(v, op==TK_NOTNULL);
- sqlite3VdbeAddOp2(v, OP_Integer, 0, target);
- sqlite3VdbeJumpHere(v, addr);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, target);
+ tdsqlite3VdbeJumpHere(v, addr);
break;
}
case TK_AGG_FUNCTION: {
AggInfo *pInfo = pExpr->pAggInfo;
if( pInfo==0 ){
assert( !ExprHasProperty(pExpr, EP_IntValue) );
- sqlite3ErrorMsg(pParse, "misuse of aggregate: %s()", pExpr->u.zToken);
+ tdsqlite3ErrorMsg(pParse, "misuse of aggregate: %s()", pExpr->u.zToken);
}else{
return pInfo->aFunc[pExpr->iAgg].iMem;
}
@@ -96368,10 +106377,21 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
const char *zId; /* The function name */
u32 constMask = 0; /* Mask of function arguments that are constant */
int i; /* Loop counter */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
u8 enc = ENC(db); /* The text encoding used by this database */
CollSeq *pColl = 0; /* A collating sequence */
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( ExprHasProperty(pExpr, EP_WinFunc) ){
+ return pExpr->y.pWin->regResult;
+ }
+#endif
+
+ if( ConstFactorOk(pParse) && tdsqlite3ExprIsConstantNotJoin(pExpr) ){
+ /* SQL functions can be expensive. So try to move constant functions
+ ** out of the inner loop, even if that means an extra OP_Copy. */
+ return tdsqlite3ExprCodeAtInit(pParse, pExpr, -1);
+ }
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
if( ExprHasProperty(pExpr, EP_TokenOnly) ){
pFarg = 0;
@@ -96381,52 +106401,32 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
nFarg = pFarg ? pFarg->nExpr : 0;
assert( !ExprHasProperty(pExpr, EP_IntValue) );
zId = pExpr->u.zToken;
- pDef = sqlite3FindFunction(db, zId, nFarg, enc, 0);
+ pDef = tdsqlite3FindFunction(db, zId, nFarg, enc, 0);
#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
if( pDef==0 && pParse->explain ){
- pDef = sqlite3FindFunction(db, "unknown", nFarg, enc, 0);
+ pDef = tdsqlite3FindFunction(db, "unknown", nFarg, enc, 0);
}
#endif
if( pDef==0 || pDef->xFinalize!=0 ){
- sqlite3ErrorMsg(pParse, "unknown function: %s()", zId);
- break;
- }
-
- /* Attempt a direct implementation of the built-in COALESCE() and
- ** IFNULL() functions. This avoids unnecessary evaluation of
- ** arguments past the first non-NULL argument.
- */
- if( pDef->funcFlags & SQLITE_FUNC_COALESCE ){
- int endCoalesce = sqlite3VdbeMakeLabel(v);
- assert( nFarg>=2 );
- sqlite3ExprCode(pParse, pFarg->a[0].pExpr, target);
- for(i=1; i<nFarg; i++){
- sqlite3VdbeAddOp2(v, OP_NotNull, target, endCoalesce);
- VdbeCoverage(v);
- sqlite3ExprCacheRemove(pParse, target, 1);
- sqlite3ExprCachePush(pParse);
- sqlite3ExprCode(pParse, pFarg->a[i].pExpr, target);
- sqlite3ExprCachePop(pParse);
- }
- sqlite3VdbeResolveLabel(v, endCoalesce);
+ tdsqlite3ErrorMsg(pParse, "unknown function: %s()", zId);
break;
}
-
- /* The UNLIKELY() function is a no-op. The result is the value
- ** of the first argument.
- */
- if( pDef->funcFlags & SQLITE_FUNC_UNLIKELY ){
- assert( nFarg>=1 );
- return sqlite3ExprCodeTarget(pParse, pFarg->a[0].pExpr, target);
+ if( pDef->funcFlags & SQLITE_FUNC_INLINE ){
+ assert( (pDef->funcFlags & SQLITE_FUNC_UNSAFE)==0 );
+ assert( (pDef->funcFlags & SQLITE_FUNC_DIRECT)==0 );
+ return exprCodeInlineFunction(pParse, pFarg,
+ SQLITE_PTR_TO_INT(pDef->pUserData), target);
+ }else if( pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE) ){
+ tdsqlite3ExprFunctionUsable(pParse, pExpr, pDef);
}
for(i=0; i<nFarg; i++){
- if( i<32 && sqlite3ExprIsConstant(pFarg->a[i].pExpr) ){
+ if( i<32 && tdsqlite3ExprIsConstant(pFarg->a[i].pExpr) ){
testcase( i==31 );
constMask |= MASKBIT32(i);
}
if( (pDef->funcFlags & SQLITE_FUNC_NEEDCOLL)!=0 && !pColl ){
- pColl = sqlite3ExprCollSeq(pParse, pFarg->a[i].pExpr);
+ pColl = tdsqlite3ExprCollSeq(pParse, pFarg->a[i].pExpr);
}
}
if( pFarg ){
@@ -96434,7 +106434,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
r1 = pParse->nMem+1;
pParse->nMem += nFarg;
}else{
- r1 = sqlite3GetTempRange(pParse, nFarg);
+ r1 = tdsqlite3GetTempRange(pParse, nFarg);
}
/* For length() and typeof() functions with a column argument,
@@ -96456,10 +106456,8 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
}
}
- sqlite3ExprCachePush(pParse); /* Ticket 2ea2425d34be */
- sqlite3ExprCodeExprList(pParse, pFarg, r1, 0,
+ tdsqlite3ExprCodeExprList(pParse, pFarg, r1, 0,
SQLITE_ECEL_DUP|SQLITE_ECEL_FACTOR);
- sqlite3ExprCachePop(pParse); /* Ticket 2ea2425d34be */
}else{
r1 = 0;
}
@@ -96476,21 +106474,36 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
** "glob(B,A). We want to use the A in "A glob B" to test
** for function overloading. But we use the B term in "glob(B,A)".
*/
- if( nFarg>=2 && (pExpr->flags & EP_InfixFunc) ){
- pDef = sqlite3VtabOverloadFunction(db, pDef, nFarg, pFarg->a[1].pExpr);
+ if( nFarg>=2 && ExprHasProperty(pExpr, EP_InfixFunc) ){
+ pDef = tdsqlite3VtabOverloadFunction(db, pDef, nFarg, pFarg->a[1].pExpr);
}else if( nFarg>0 ){
- pDef = sqlite3VtabOverloadFunction(db, pDef, nFarg, pFarg->a[0].pExpr);
+ pDef = tdsqlite3VtabOverloadFunction(db, pDef, nFarg, pFarg->a[0].pExpr);
}
#endif
if( pDef->funcFlags & SQLITE_FUNC_NEEDCOLL ){
if( !pColl ) pColl = db->pDfltColl;
- sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ);
+ tdsqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ);
}
- sqlite3VdbeAddOp4(v, OP_Function0, constMask, r1, target,
- (char*)pDef, P4_FUNCDEF);
- sqlite3VdbeChangeP5(v, (u8)nFarg);
- if( nFarg && constMask==0 ){
- sqlite3ReleaseTempRange(pParse, r1, nFarg);
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+ if( pDef->funcFlags & SQLITE_FUNC_OFFSET ){
+ Expr *pArg = pFarg->a[0].pExpr;
+ if( pArg->op==TK_COLUMN ){
+ tdsqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target);
+ }else{
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ }
+ }else
+#endif
+ {
+ tdsqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg,
+ pDef, pExpr->op2);
+ }
+ if( nFarg ){
+ if( constMask==0 ){
+ tdsqlite3ReleaseTempRange(pParse, r1, nFarg);
+ }else{
+ tdsqlite3VdbeReleaseRegisters(pParse, r1, nFarg, constMask, 1);
+ }
}
return target;
}
@@ -96501,27 +106514,35 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
testcase( op==TK_EXISTS );
testcase( op==TK_SELECT );
if( op==TK_SELECT && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 ){
- sqlite3SubselectError(pParse, nCol, 1);
+ tdsqlite3SubselectError(pParse, nCol, 1);
}else{
- return sqlite3CodeSubselect(pParse, pExpr, 0, 0);
+ return tdsqlite3CodeSubselect(pParse, pExpr);
}
break;
}
case TK_SELECT_COLUMN: {
+ int n;
if( pExpr->pLeft->iTable==0 ){
- pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft, 0, 0);
+ pExpr->pLeft->iTable = tdsqlite3CodeSubselect(pParse, pExpr->pLeft);
+ }
+ assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT );
+ if( pExpr->iTable!=0
+ && pExpr->iTable!=(n = tdsqlite3ExprVectorSize(pExpr->pLeft))
+ ){
+ tdsqlite3ErrorMsg(pParse, "%d columns assigned %d values",
+ pExpr->iTable, n);
}
return pExpr->pLeft->iTable + pExpr->iColumn;
}
case TK_IN: {
- int destIfFalse = sqlite3VdbeMakeLabel(v);
- int destIfNull = sqlite3VdbeMakeLabel(v);
- sqlite3VdbeAddOp2(v, OP_Null, 0, target);
- sqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull);
- sqlite3VdbeAddOp2(v, OP_Integer, 1, target);
- sqlite3VdbeResolveLabel(v, destIfFalse);
- sqlite3VdbeAddOp2(v, OP_AddImm, target, 0);
- sqlite3VdbeResolveLabel(v, destIfNull);
+ int destIfFalse = tdsqlite3VdbeMakeLabel(pParse);
+ int destIfNull = tdsqlite3VdbeMakeLabel(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ tdsqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, target);
+ tdsqlite3VdbeResolveLabel(v, destIfFalse);
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, target, 0);
+ tdsqlite3VdbeResolveLabel(v, destIfNull);
return target;
}
#endif /* SQLITE_OMIT_SUBQUERY */
@@ -96545,7 +106566,8 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
case TK_SPAN:
case TK_COLLATE:
case TK_UPLUS: {
- return sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
+ pExpr = pExpr->pLeft;
+ goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. OSSFuzz. */
}
case TK_TRIGGER: {
@@ -96574,19 +106596,20 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
** p1==1 -> old.a p1==4 -> new.a
** p1==2 -> old.b p1==5 -> new.b
*/
- Table *pTab = pExpr->pTab;
- int p1 = pExpr->iTable * (pTab->nCol+1) + 1 + pExpr->iColumn;
+ Table *pTab = pExpr->y.pTab;
+ int iCol = pExpr->iColumn;
+ int p1 = pExpr->iTable * (pTab->nCol+1) + 1
+ + tdsqlite3TableColumnToStorage(pTab, iCol);
assert( pExpr->iTable==0 || pExpr->iTable==1 );
- assert( pExpr->iColumn>=-1 && pExpr->iColumn<pTab->nCol );
- assert( pTab->iPKey<0 || pExpr->iColumn!=pTab->iPKey );
+ assert( iCol>=-1 && iCol<pTab->nCol );
+ assert( pTab->iPKey<0 || iCol!=pTab->iPKey );
assert( p1>=0 && p1<(pTab->nCol*2+2) );
- sqlite3VdbeAddOp2(v, OP_Param, p1, target);
- VdbeComment((v, "%s.%s -> $%d",
+ tdsqlite3VdbeAddOp2(v, OP_Param, p1, target);
+ VdbeComment((v, "r[%d]=%s.%s", target,
(pExpr->iTable ? "new" : "old"),
- (pExpr->iColumn<0 ? "rowid" : pExpr->pTab->aCol[pExpr->iColumn].zName),
- target
+ (pExpr->iColumn<0 ? "rowid" : pExpr->y.pTab->aCol[iCol].zName)
));
#ifndef SQLITE_OMIT_FLOATING_POINT
@@ -96595,17 +106618,37 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
**
** EVIDENCE-OF: R-60985-57662 SQLite will convert the value back to
** floating point when extracting it from the record. */
- if( pExpr->iColumn>=0
- && pTab->aCol[pExpr->iColumn].affinity==SQLITE_AFF_REAL
- ){
- sqlite3VdbeAddOp1(v, OP_RealAffinity, target);
+ if( iCol>=0 && pTab->aCol[iCol].affinity==SQLITE_AFF_REAL ){
+ tdsqlite3VdbeAddOp1(v, OP_RealAffinity, target);
}
#endif
break;
}
case TK_VECTOR: {
- sqlite3ErrorMsg(pParse, "row value misused");
+ tdsqlite3ErrorMsg(pParse, "row value misused");
+ break;
+ }
+
+ /* TK_IF_NULL_ROW Expr nodes are inserted ahead of expressions
+ ** that derive from the right-hand table of a LEFT JOIN. The
+ ** Expr.iTable value is the table number for the right-hand table.
+ ** The expression is only evaluated if that table is not currently
+ ** on a LEFT JOIN NULL row.
+ */
+ case TK_IF_NULL_ROW: {
+ int addrINR;
+ u8 okConstFactor = pParse->okConstFactor;
+ addrINR = tdsqlite3VdbeAddOp1(v, OP_IfNullRow, pExpr->iTable);
+ /* Temporarily disable factoring of constant expressions, since
+ ** even though expressions may appear to be constant, they are not
+ ** really constant because they originate from the right-hand side
+ ** of a LEFT JOIN. */
+ pParse->okConstFactor = 0;
+ inReg = tdsqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
+ pParse->okConstFactor = okConstFactor;
+ tdsqlite3VdbeJumpHere(v, addrINR);
+ tdsqlite3VdbeChangeP3(v, addrINR, inReg);
break;
}
@@ -96630,7 +106673,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
** or if there is no matching Ei, the ELSE term Y, or if there is
** no ELSE term, NULL.
*/
- default: assert( op==TK_CASE ); {
+ case TK_CASE: {
int endLabel; /* GOTO label for end of CASE stmt */
int nextCase; /* GOTO label for next WHEN clause */
int nExpr; /* 2x number of WHEN terms */
@@ -96640,22 +106683,27 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
Expr opCompare; /* The X==Ei expression */
Expr *pX; /* The X expression */
Expr *pTest = 0; /* X==Ei (form A) or just Ei (form B) */
- VVA_ONLY( int iCacheLevel = pParse->iCacheLevel; )
+ Expr *pDel = 0;
+ tdsqlite3 *db = pParse->db;
assert( !ExprHasProperty(pExpr, EP_xIsSelect) && pExpr->x.pList );
assert(pExpr->x.pList->nExpr > 0);
pEList = pExpr->x.pList;
aListelem = pEList->a;
nExpr = pEList->nExpr;
- endLabel = sqlite3VdbeMakeLabel(v);
+ endLabel = tdsqlite3VdbeMakeLabel(pParse);
if( (pX = pExpr->pLeft)!=0 ){
- tempX = *pX;
+ pDel = tdsqlite3ExprDup(db, pX, 0);
+ if( db->mallocFailed ){
+ tdsqlite3ExprDelete(db, pDel);
+ break;
+ }
testcase( pX->op==TK_COLUMN );
- exprToRegister(&tempX, exprCodeVector(pParse, &tempX, &regFree1));
+ exprToRegister(pDel, exprCodeVector(pParse, pDel, &regFree1));
testcase( regFree1==0 );
memset(&opCompare, 0, sizeof(opCompare));
opCompare.op = TK_EQ;
- opCompare.pLeft = &tempX;
+ opCompare.pLeft = pDel;
pTest = &opCompare;
/* Ticket b351d95f9cd5ef17e9d9dbae18f5ca8611190001:
** The value in regFree1 might get SCopy-ed into the file result.
@@ -96664,88 +106712,99 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target)
regFree1 = 0;
}
for(i=0; i<nExpr-1; i=i+2){
- sqlite3ExprCachePush(pParse);
if( pX ){
assert( pTest!=0 );
opCompare.pRight = aListelem[i].pExpr;
}else{
pTest = aListelem[i].pExpr;
}
- nextCase = sqlite3VdbeMakeLabel(v);
+ nextCase = tdsqlite3VdbeMakeLabel(pParse);
testcase( pTest->op==TK_COLUMN );
- sqlite3ExprIfFalse(pParse, pTest, nextCase, SQLITE_JUMPIFNULL);
+ tdsqlite3ExprIfFalse(pParse, pTest, nextCase, SQLITE_JUMPIFNULL);
testcase( aListelem[i+1].pExpr->op==TK_COLUMN );
- sqlite3ExprCode(pParse, aListelem[i+1].pExpr, target);
- sqlite3VdbeGoto(v, endLabel);
- sqlite3ExprCachePop(pParse);
- sqlite3VdbeResolveLabel(v, nextCase);
+ tdsqlite3ExprCode(pParse, aListelem[i+1].pExpr, target);
+ tdsqlite3VdbeGoto(v, endLabel);
+ tdsqlite3VdbeResolveLabel(v, nextCase);
}
if( (nExpr&1)!=0 ){
- sqlite3ExprCachePush(pParse);
- sqlite3ExprCode(pParse, pEList->a[nExpr-1].pExpr, target);
- sqlite3ExprCachePop(pParse);
+ tdsqlite3ExprCode(pParse, pEList->a[nExpr-1].pExpr, target);
}else{
- sqlite3VdbeAddOp2(v, OP_Null, 0, target);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, target);
}
- assert( pParse->db->mallocFailed || pParse->nErr>0
- || pParse->iCacheLevel==iCacheLevel );
- sqlite3VdbeResolveLabel(v, endLabel);
+ tdsqlite3ExprDelete(db, pDel);
+ tdsqlite3VdbeResolveLabel(v, endLabel);
break;
}
#ifndef SQLITE_OMIT_TRIGGER
case TK_RAISE: {
- assert( pExpr->affinity==OE_Rollback
- || pExpr->affinity==OE_Abort
- || pExpr->affinity==OE_Fail
- || pExpr->affinity==OE_Ignore
+ assert( pExpr->affExpr==OE_Rollback
+ || pExpr->affExpr==OE_Abort
+ || pExpr->affExpr==OE_Fail
+ || pExpr->affExpr==OE_Ignore
);
if( !pParse->pTriggerTab ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"RAISE() may only be used within a trigger-program");
return 0;
}
- if( pExpr->affinity==OE_Abort ){
- sqlite3MayAbort(pParse);
+ if( pExpr->affExpr==OE_Abort ){
+ tdsqlite3MayAbort(pParse);
}
assert( !ExprHasProperty(pExpr, EP_IntValue) );
- if( pExpr->affinity==OE_Ignore ){
- sqlite3VdbeAddOp4(
+ if( pExpr->affExpr==OE_Ignore ){
+ tdsqlite3VdbeAddOp4(
v, OP_Halt, SQLITE_OK, OE_Ignore, 0, pExpr->u.zToken,0);
VdbeCoverage(v);
}else{
- sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_TRIGGER,
- pExpr->affinity, pExpr->u.zToken, 0, 0);
+ tdsqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_TRIGGER,
+ pExpr->affExpr, pExpr->u.zToken, 0, 0);
}
break;
}
#endif
}
- sqlite3ReleaseTempReg(pParse, regFree1);
- sqlite3ReleaseTempReg(pParse, regFree2);
+ tdsqlite3ReleaseTempReg(pParse, regFree1);
+ tdsqlite3ReleaseTempReg(pParse, regFree2);
return inReg;
}
/*
** Factor out the code of the given expression to initialization time.
+**
+** If regDest>=0 then the result is always stored in that register and the
+** result is not reusable. If regDest<0 then this routine is free to
+** store the value whereever it wants. The register where the expression
+** is stored is returned. When regDest<0, two identical expressions will
+** code to the same register.
*/
-SQLITE_PRIVATE void sqlite3ExprCodeAtInit(
+SQLITE_PRIVATE int tdsqlite3ExprCodeAtInit(
Parse *pParse, /* Parsing context */
Expr *pExpr, /* The expression to code when the VDBE initializes */
- int regDest, /* Store the value in this register */
- u8 reusable /* True if this expression is reusable */
+ int regDest /* Store the value in this register */
){
ExprList *p;
assert( ConstFactorOk(pParse) );
p = pParse->pConstExpr;
- pExpr = sqlite3ExprDup(pParse->db, pExpr, 0);
- p = sqlite3ExprListAppend(pParse, p, pExpr);
+ if( regDest<0 && p ){
+ struct ExprList_item *pItem;
+ int i;
+ for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){
+ if( pItem->reusable && tdsqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 ){
+ return pItem->u.iConstExprReg;
+ }
+ }
+ }
+ pExpr = tdsqlite3ExprDup(pParse->db, pExpr, 0);
+ p = tdsqlite3ExprListAppend(pParse, p, pExpr);
if( p ){
struct ExprList_item *pItem = &p->a[p->nExpr-1];
+ pItem->reusable = regDest<0;
+ if( regDest<0 ) regDest = ++pParse->nMem;
pItem->u.iConstExprReg = regDest;
- pItem->reusable = reusable;
}
pParse->pConstExpr = p;
+ return regDest;
}
/*
@@ -96761,33 +106820,22 @@ SQLITE_PRIVATE void sqlite3ExprCodeAtInit(
** code to fill the register in the initialization section of the
** VDBE program, in order to factor it out of the evaluation loop.
*/
-SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){
+SQLITE_PRIVATE int tdsqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){
int r2;
- pExpr = sqlite3ExprSkipCollate(pExpr);
+ pExpr = tdsqlite3ExprSkipCollateAndLikely(pExpr);
if( ConstFactorOk(pParse)
&& pExpr->op!=TK_REGISTER
- && sqlite3ExprIsConstantNotJoin(pExpr)
+ && tdsqlite3ExprIsConstantNotJoin(pExpr)
){
- ExprList *p = pParse->pConstExpr;
- int i;
*pReg = 0;
- if( p ){
- struct ExprList_item *pItem;
- for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){
- if( pItem->reusable && sqlite3ExprCompare(pItem->pExpr,pExpr,-1)==0 ){
- return pItem->u.iConstExprReg;
- }
- }
- }
- r2 = ++pParse->nMem;
- sqlite3ExprCodeAtInit(pParse, pExpr, r2, 1);
+ r2 = tdsqlite3ExprCodeAtInit(pParse, pExpr, -1);
}else{
- int r1 = sqlite3GetTempReg(pParse);
- r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1);
+ int r1 = tdsqlite3GetTempReg(pParse);
+ r2 = tdsqlite3ExprCodeTarget(pParse, pExpr, r1);
if( r2==r1 ){
*pReg = r1;
}else{
- sqlite3ReleaseTempReg(pParse, r1);
+ tdsqlite3ReleaseTempReg(pParse, r1);
*pReg = 0;
}
}
@@ -96799,31 +106847,33 @@ SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){
** results in register target. The results are guaranteed to appear
** in register target.
*/
-SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){
+SQLITE_PRIVATE void tdsqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){
int inReg;
assert( target>0 && target<=pParse->nMem );
- if( pExpr && pExpr->op==TK_REGISTER ){
- sqlite3VdbeAddOp2(pParse->pVdbe, OP_Copy, pExpr->iTable, target);
- }else{
- inReg = sqlite3ExprCodeTarget(pParse, pExpr, target);
- assert( pParse->pVdbe!=0 || pParse->db->mallocFailed );
- if( inReg!=target && pParse->pVdbe ){
- sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, inReg, target);
+ inReg = tdsqlite3ExprCodeTarget(pParse, pExpr, target);
+ assert( pParse->pVdbe!=0 || pParse->db->mallocFailed );
+ if( inReg!=target && pParse->pVdbe ){
+ u8 op;
+ if( ExprHasProperty(pExpr,EP_Subquery) ){
+ op = OP_Copy;
+ }else{
+ op = OP_SCopy;
}
+ tdsqlite3VdbeAddOp2(pParse->pVdbe, op, inReg, target);
}
}
/*
** Make a transient copy of expression pExpr and then code it using
-** sqlite3ExprCode(). This routine works just like sqlite3ExprCode()
+** tdsqlite3ExprCode(). This routine works just like tdsqlite3ExprCode()
** except that the input expression is guaranteed to be unchanged.
*/
-SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse *pParse, Expr *pExpr, int target){
- sqlite3 *db = pParse->db;
- pExpr = sqlite3ExprDup(db, pExpr, 0);
- if( !db->mallocFailed ) sqlite3ExprCode(pParse, pExpr, target);
- sqlite3ExprDelete(db, pExpr);
+SQLITE_PRIVATE void tdsqlite3ExprCodeCopy(Parse *pParse, Expr *pExpr, int target){
+ tdsqlite3 *db = pParse->db;
+ pExpr = tdsqlite3ExprDup(db, pExpr, 0);
+ if( !db->mallocFailed ) tdsqlite3ExprCode(pParse, pExpr, target);
+ tdsqlite3ExprDelete(db, pExpr);
}
/*
@@ -96832,43 +106882,21 @@ SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse *pParse, Expr *pExpr, int target){
** in register target. If the expression is constant, then this routine
** might choose to code the expression at initialization time.
*/
-SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){
- if( pParse->okConstFactor && sqlite3ExprIsConstant(pExpr) ){
- sqlite3ExprCodeAtInit(pParse, pExpr, target, 0);
+SQLITE_PRIVATE void tdsqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){
+ if( pParse->okConstFactor && tdsqlite3ExprIsConstantNotJoin(pExpr) ){
+ tdsqlite3ExprCodeAtInit(pParse, pExpr, target);
}else{
- sqlite3ExprCode(pParse, pExpr, target);
+ tdsqlite3ExprCode(pParse, pExpr, target);
}
}
/*
-** Generate code that evaluates the given expression and puts the result
-** in register target.
-**
-** Also make a copy of the expression results into another "cache" register
-** and modify the expression so that the next time it is evaluated,
-** the result is a copy of the cache register.
-**
-** This routine is used for expressions that are used multiple
-** times. They are evaluated once and the results of the expression
-** are reused.
-*/
-SQLITE_PRIVATE void sqlite3ExprCodeAndCache(Parse *pParse, Expr *pExpr, int target){
- Vdbe *v = pParse->pVdbe;
- int iMem;
-
- assert( target>0 );
- assert( pExpr->op!=TK_REGISTER );
- sqlite3ExprCode(pParse, pExpr, target);
- iMem = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Copy, target, iMem);
- exprToRegister(pExpr, iMem);
-}
-
-/*
** Generate code that pushes the value of every element of the given
** expression list into a sequence of registers beginning at target.
**
-** Return the number of elements evaluated.
+** Return the number of elements evaluated. The number returned will
+** usually be pList->nExpr but might be reduced if SQLITE_ECEL_OMITREF
+** is defined.
**
** The SQLITE_ECEL_DUP flag prevents the arguments from being
** filled using OP_SCopy. OP_Copy must be used instead.
@@ -96879,8 +106907,10 @@ SQLITE_PRIVATE void sqlite3ExprCodeAndCache(Parse *pParse, Expr *pExpr, int targ
** The SQLITE_ECEL_REF flag means that expressions in the list with
** ExprList.a[].u.x.iOrderByCol>0 have already been evaluated and stored
** in registers at srcReg, and so the value can be copied from there.
+** If SQLITE_ECEL_OMITREF is also set, then the values with u.x.iOrderByCol>0
+** are simply omitted rather than being copied from srcReg.
*/
-SQLITE_PRIVATE int sqlite3ExprCodeExprList(
+SQLITE_PRIVATE int tdsqlite3ExprCodeExprList(
Parse *pParse, /* Parsing context */
ExprList *pList, /* The expression list to be coded */
int target, /* Where to write results */
@@ -96898,22 +106928,36 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList(
if( !ConstFactorOk(pParse) ) flags &= ~SQLITE_ECEL_FACTOR;
for(pItem=pList->a, i=0; i<n; i++, pItem++){
Expr *pExpr = pItem->pExpr;
- if( (flags & SQLITE_ECEL_REF)!=0 && (j = pList->a[i].u.x.iOrderByCol)>0 ){
- sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i);
- }else if( (flags & SQLITE_ECEL_FACTOR)!=0 && sqlite3ExprIsConstant(pExpr) ){
- sqlite3ExprCodeAtInit(pParse, pExpr, target+i, 0);
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( pItem->bSorterRef ){
+ i--;
+ n--;
+ }else
+#endif
+ if( (flags & SQLITE_ECEL_REF)!=0 && (j = pItem->u.x.iOrderByCol)>0 ){
+ if( flags & SQLITE_ECEL_OMITREF ){
+ i--;
+ n--;
+ }else{
+ tdsqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i);
+ }
+ }else if( (flags & SQLITE_ECEL_FACTOR)!=0
+ && tdsqlite3ExprIsConstantNotJoin(pExpr)
+ ){
+ tdsqlite3ExprCodeAtInit(pParse, pExpr, target+i);
}else{
- int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i);
+ int inReg = tdsqlite3ExprCodeTarget(pParse, pExpr, target+i);
if( inReg!=target+i ){
VdbeOp *pOp;
if( copyOp==OP_Copy
- && (pOp=sqlite3VdbeGetOp(v, -1))->opcode==OP_Copy
+ && (pOp=tdsqlite3VdbeGetOp(v, -1))->opcode==OP_Copy
&& pOp->p1+pOp->p3+1==inReg
&& pOp->p2+pOp->p3+1==target+i
+ && pOp->p5==0 /* The do-not-merge flag must be clear */
){
pOp->p3++;
}else{
- sqlite3VdbeAddOp2(v, copyOp, inReg, target+i);
+ tdsqlite3VdbeAddOp2(v, copyOp, inReg, target+i);
}
}
}
@@ -96936,8 +106980,8 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList(
** The xJumpIf parameter determines details:
**
** NULL: Store the boolean result in reg[dest]
-** sqlite3ExprIfTrue: Jump to dest if true
-** sqlite3ExprIfFalse: Jump to dest if false
+** tdsqlite3ExprIfTrue: Jump to dest if true
+** tdsqlite3ExprIfFalse: Jump to dest if false
**
** The jumpIfNull parameter is ignored if xJumpIf is NULL.
*/
@@ -96948,46 +106992,54 @@ static void exprCodeBetween(
void (*xJump)(Parse*,Expr*,int,int), /* Action to take */
int jumpIfNull /* Take the jump if the BETWEEN is NULL */
){
- Expr exprAnd; /* The AND operator in x>=y AND x<=z */
+ Expr exprAnd; /* The AND operator in x>=y AND x<=z */
Expr compLeft; /* The x>=y term */
Expr compRight; /* The x<=z term */
- Expr exprX; /* The x subexpression */
int regFree1 = 0; /* Temporary use register */
-
+ Expr *pDel = 0;
+ tdsqlite3 *db = pParse->db;
memset(&compLeft, 0, sizeof(Expr));
memset(&compRight, 0, sizeof(Expr));
memset(&exprAnd, 0, sizeof(Expr));
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
- exprX = *pExpr->pLeft;
- exprAnd.op = TK_AND;
- exprAnd.pLeft = &compLeft;
- exprAnd.pRight = &compRight;
- compLeft.op = TK_GE;
- compLeft.pLeft = &exprX;
- compLeft.pRight = pExpr->x.pList->a[0].pExpr;
- compRight.op = TK_LE;
- compRight.pLeft = &exprX;
- compRight.pRight = pExpr->x.pList->a[1].pExpr;
- exprToRegister(&exprX, exprCodeVector(pParse, &exprX, &regFree1));
- if( xJump ){
- xJump(pParse, &exprAnd, dest, jumpIfNull);
- }else{
- exprX.flags |= EP_FromJoin;
- sqlite3ExprCodeTarget(pParse, &exprAnd, dest);
- }
- sqlite3ReleaseTempReg(pParse, regFree1);
+ pDel = tdsqlite3ExprDup(db, pExpr->pLeft, 0);
+ if( db->mallocFailed==0 ){
+ exprAnd.op = TK_AND;
+ exprAnd.pLeft = &compLeft;
+ exprAnd.pRight = &compRight;
+ compLeft.op = TK_GE;
+ compLeft.pLeft = pDel;
+ compLeft.pRight = pExpr->x.pList->a[0].pExpr;
+ compRight.op = TK_LE;
+ compRight.pLeft = pDel;
+ compRight.pRight = pExpr->x.pList->a[1].pExpr;
+ exprToRegister(pDel, exprCodeVector(pParse, pDel, &regFree1));
+ if( xJump ){
+ xJump(pParse, &exprAnd, dest, jumpIfNull);
+ }else{
+ /* Mark the expression is being from the ON or USING clause of a join
+ ** so that the tdsqlite3ExprCodeTarget() routine will not attempt to move
+ ** it into the Parse.pConstExpr list. We should use a new bit for this,
+ ** for clarity, but we are out of bits in the Expr.flags field so we
+ ** have to reuse the EP_FromJoin bit. Bummer. */
+ pDel->flags |= EP_FromJoin;
+ tdsqlite3ExprCodeTarget(pParse, &exprAnd, dest);
+ }
+ tdsqlite3ReleaseTempReg(pParse, regFree1);
+ }
+ tdsqlite3ExprDelete(db, pDel);
/* Ensure adequate test coverage */
- testcase( xJump==sqlite3ExprIfTrue && jumpIfNull==0 && regFree1==0 );
- testcase( xJump==sqlite3ExprIfTrue && jumpIfNull==0 && regFree1!=0 );
- testcase( xJump==sqlite3ExprIfTrue && jumpIfNull!=0 && regFree1==0 );
- testcase( xJump==sqlite3ExprIfTrue && jumpIfNull!=0 && regFree1!=0 );
- testcase( xJump==sqlite3ExprIfFalse && jumpIfNull==0 && regFree1==0 );
- testcase( xJump==sqlite3ExprIfFalse && jumpIfNull==0 && regFree1!=0 );
- testcase( xJump==sqlite3ExprIfFalse && jumpIfNull!=0 && regFree1==0 );
- testcase( xJump==sqlite3ExprIfFalse && jumpIfNull!=0 && regFree1!=0 );
+ testcase( xJump==tdsqlite3ExprIfTrue && jumpIfNull==0 && regFree1==0 );
+ testcase( xJump==tdsqlite3ExprIfTrue && jumpIfNull==0 && regFree1!=0 );
+ testcase( xJump==tdsqlite3ExprIfTrue && jumpIfNull!=0 && regFree1==0 );
+ testcase( xJump==tdsqlite3ExprIfTrue && jumpIfNull!=0 && regFree1!=0 );
+ testcase( xJump==tdsqlite3ExprIfFalse && jumpIfNull==0 && regFree1==0 );
+ testcase( xJump==tdsqlite3ExprIfFalse && jumpIfNull==0 && regFree1!=0 );
+ testcase( xJump==tdsqlite3ExprIfFalse && jumpIfNull!=0 && regFree1==0 );
+ testcase( xJump==tdsqlite3ExprIfFalse && jumpIfNull!=0 && regFree1!=0 );
testcase( xJump==0 );
}
@@ -97005,7 +107057,7 @@ static void exprCodeBetween(
** the make process cause these values to align. Assert()s in the code
** below verify that the numbers are aligned correctly.
*/
-SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
+SQLITE_PRIVATE void tdsqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
Vdbe *v = pParse->pVdbe;
int op = 0;
int regFree1 = 0;
@@ -97017,27 +107069,45 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
if( NEVER(pExpr==0) ) return; /* No way this can happen */
op = pExpr->op;
switch( op ){
- case TK_AND: {
- int d2 = sqlite3VdbeMakeLabel(v);
- testcase( jumpIfNull==0 );
- sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2,jumpIfNull^SQLITE_JUMPIFNULL);
- sqlite3ExprCachePush(pParse);
- sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
- sqlite3VdbeResolveLabel(v, d2);
- sqlite3ExprCachePop(pParse);
+ case TK_AND:
+ case TK_OR: {
+ Expr *pAlt = tdsqlite3ExprSimplifiedAndOr(pExpr);
+ if( pAlt!=pExpr ){
+ tdsqlite3ExprIfTrue(pParse, pAlt, dest, jumpIfNull);
+ }else if( op==TK_AND ){
+ int d2 = tdsqlite3VdbeMakeLabel(pParse);
+ testcase( jumpIfNull==0 );
+ tdsqlite3ExprIfFalse(pParse, pExpr->pLeft, d2,
+ jumpIfNull^SQLITE_JUMPIFNULL);
+ tdsqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ tdsqlite3VdbeResolveLabel(v, d2);
+ }else{
+ testcase( jumpIfNull==0 );
+ tdsqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
+ tdsqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
+ }
break;
}
- case TK_OR: {
+ case TK_NOT: {
testcase( jumpIfNull==0 );
- sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
- sqlite3ExprCachePush(pParse);
- sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull);
- sqlite3ExprCachePop(pParse);
+ tdsqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
break;
}
- case TK_NOT: {
+ case TK_TRUTH: {
+ int isNot; /* IS NOT TRUE or IS NOT FALSE */
+ int isTrue; /* IS TRUE or IS NOT TRUE */
testcase( jumpIfNull==0 );
- sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
+ isNot = pExpr->op2==TK_ISNOT;
+ isTrue = tdsqlite3ExprTruthValue(pExpr->pRight);
+ testcase( isTrue && isNot );
+ testcase( !isTrue && isNot );
+ if( isTrue ^ isNot ){
+ tdsqlite3ExprIfTrue(pParse, pExpr->pLeft, dest,
+ isNot ? SQLITE_JUMPIFNULL : 0);
+ }else{
+ tdsqlite3ExprIfFalse(pParse, pExpr->pLeft, dest,
+ isNot ? SQLITE_JUMPIFNULL : 0);
+ }
break;
}
case TK_IS:
@@ -97053,12 +107123,12 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
case TK_GE:
case TK_NE:
case TK_EQ: {
- if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr;
+ if( tdsqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr;
testcase( jumpIfNull==0 );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = tdsqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op,
- r1, r2, dest, jumpIfNull);
+ r1, r2, dest, jumpIfNull, ExprHasProperty(pExpr,EP_Commuted));
assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le);
assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt);
@@ -97077,8 +107147,8 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
case TK_NOTNULL: {
assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL );
assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- sqlite3VdbeAddOp2(v, op, r1, dest);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ tdsqlite3VdbeAddOp2(v, op, r1, dest);
VdbeCoverageIf(v, op==TK_ISNULL);
VdbeCoverageIf(v, op==TK_NOTNULL);
testcase( regFree1==0 );
@@ -97086,28 +107156,28 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
}
case TK_BETWEEN: {
testcase( jumpIfNull==0 );
- exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfTrue, jumpIfNull);
+ exprCodeBetween(pParse, pExpr, dest, tdsqlite3ExprIfTrue, jumpIfNull);
break;
}
#ifndef SQLITE_OMIT_SUBQUERY
case TK_IN: {
- int destIfFalse = sqlite3VdbeMakeLabel(v);
+ int destIfFalse = tdsqlite3VdbeMakeLabel(pParse);
int destIfNull = jumpIfNull ? dest : destIfFalse;
- sqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull);
- sqlite3VdbeGoto(v, dest);
- sqlite3VdbeResolveLabel(v, destIfFalse);
+ tdsqlite3ExprCodeIN(pParse, pExpr, destIfFalse, destIfNull);
+ tdsqlite3VdbeGoto(v, dest);
+ tdsqlite3VdbeResolveLabel(v, destIfFalse);
break;
}
#endif
default: {
default_expr:
- if( exprAlwaysTrue(pExpr) ){
- sqlite3VdbeGoto(v, dest);
- }else if( exprAlwaysFalse(pExpr) ){
+ if( ExprAlwaysTrue(pExpr) ){
+ tdsqlite3VdbeGoto(v, dest);
+ }else if( ExprAlwaysFalse(pExpr) ){
/* No-op */
}else{
- r1 = sqlite3ExprCodeTemp(pParse, pExpr, &regFree1);
- sqlite3VdbeAddOp3(v, OP_If, r1, dest, jumpIfNull!=0);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr, &regFree1);
+ tdsqlite3VdbeAddOp3(v, OP_If, r1, dest, jumpIfNull!=0);
VdbeCoverage(v);
testcase( regFree1==0 );
testcase( jumpIfNull==0 );
@@ -97115,8 +107185,8 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
break;
}
}
- sqlite3ReleaseTempReg(pParse, regFree1);
- sqlite3ReleaseTempReg(pParse, regFree2);
+ tdsqlite3ReleaseTempReg(pParse, regFree1);
+ tdsqlite3ReleaseTempReg(pParse, regFree2);
}
/*
@@ -97128,7 +107198,7 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int
** jump if jumpIfNull is SQLITE_JUMPIFNULL or fall through if jumpIfNull
** is 0.
*/
-SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
+SQLITE_PRIVATE void tdsqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){
Vdbe *v = pParse->pVdbe;
int op = 0;
int regFree1 = 0;
@@ -97171,27 +107241,48 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
assert( pExpr->op!=TK_GE || op==OP_Lt );
switch( pExpr->op ){
- case TK_AND: {
- testcase( jumpIfNull==0 );
- sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
- sqlite3ExprCachePush(pParse);
- sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
- sqlite3ExprCachePop(pParse);
+ case TK_AND:
+ case TK_OR: {
+ Expr *pAlt = tdsqlite3ExprSimplifiedAndOr(pExpr);
+ if( pAlt!=pExpr ){
+ tdsqlite3ExprIfFalse(pParse, pAlt, dest, jumpIfNull);
+ }else if( pExpr->op==TK_AND ){
+ testcase( jumpIfNull==0 );
+ tdsqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull);
+ tdsqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
+ }else{
+ int d2 = tdsqlite3VdbeMakeLabel(pParse);
+ testcase( jumpIfNull==0 );
+ tdsqlite3ExprIfTrue(pParse, pExpr->pLeft, d2,
+ jumpIfNull^SQLITE_JUMPIFNULL);
+ tdsqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
+ tdsqlite3VdbeResolveLabel(v, d2);
+ }
break;
}
- case TK_OR: {
- int d2 = sqlite3VdbeMakeLabel(v);
+ case TK_NOT: {
testcase( jumpIfNull==0 );
- sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2, jumpIfNull^SQLITE_JUMPIFNULL);
- sqlite3ExprCachePush(pParse);
- sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull);
- sqlite3VdbeResolveLabel(v, d2);
- sqlite3ExprCachePop(pParse);
+ tdsqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
break;
}
- case TK_NOT: {
+ case TK_TRUTH: {
+ int isNot; /* IS NOT TRUE or IS NOT FALSE */
+ int isTrue; /* IS TRUE or IS NOT TRUE */
testcase( jumpIfNull==0 );
- sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull);
+ isNot = pExpr->op2==TK_ISNOT;
+ isTrue = tdsqlite3ExprTruthValue(pExpr->pRight);
+ testcase( isTrue && isNot );
+ testcase( !isTrue && isNot );
+ if( isTrue ^ isNot ){
+ /* IS TRUE and IS NOT FALSE */
+ tdsqlite3ExprIfFalse(pParse, pExpr->pLeft, dest,
+ isNot ? 0 : SQLITE_JUMPIFNULL);
+
+ }else{
+ /* IS FALSE and IS NOT TRUE */
+ tdsqlite3ExprIfTrue(pParse, pExpr->pLeft, dest,
+ isNot ? 0 : SQLITE_JUMPIFNULL);
+ }
break;
}
case TK_IS:
@@ -97207,12 +107298,12 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
case TK_GE:
case TK_NE:
case TK_EQ: {
- if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr;
+ if( tdsqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr;
testcase( jumpIfNull==0 );
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ r2 = tdsqlite3ExprCodeTemp(pParse, pExpr->pRight, &regFree2);
codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op,
- r1, r2, dest, jumpIfNull);
+ r1, r2, dest, jumpIfNull,ExprHasProperty(pExpr,EP_Commuted));
assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt);
assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le);
assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt);
@@ -97229,8 +107320,8 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
}
case TK_ISNULL:
case TK_NOTNULL: {
- r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
- sqlite3VdbeAddOp2(v, op, r1, dest);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr->pLeft, &regFree1);
+ tdsqlite3VdbeAddOp2(v, op, r1, dest);
testcase( op==TK_ISNULL ); VdbeCoverageIf(v, op==TK_ISNULL);
testcase( op==TK_NOTNULL ); VdbeCoverageIf(v, op==TK_NOTNULL);
testcase( regFree1==0 );
@@ -97238,30 +107329,30 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
}
case TK_BETWEEN: {
testcase( jumpIfNull==0 );
- exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfFalse, jumpIfNull);
+ exprCodeBetween(pParse, pExpr, dest, tdsqlite3ExprIfFalse, jumpIfNull);
break;
}
#ifndef SQLITE_OMIT_SUBQUERY
case TK_IN: {
if( jumpIfNull ){
- sqlite3ExprCodeIN(pParse, pExpr, dest, dest);
+ tdsqlite3ExprCodeIN(pParse, pExpr, dest, dest);
}else{
- int destIfNull = sqlite3VdbeMakeLabel(v);
- sqlite3ExprCodeIN(pParse, pExpr, dest, destIfNull);
- sqlite3VdbeResolveLabel(v, destIfNull);
+ int destIfNull = tdsqlite3VdbeMakeLabel(pParse);
+ tdsqlite3ExprCodeIN(pParse, pExpr, dest, destIfNull);
+ tdsqlite3VdbeResolveLabel(v, destIfNull);
}
break;
}
#endif
default: {
default_expr:
- if( exprAlwaysFalse(pExpr) ){
- sqlite3VdbeGoto(v, dest);
- }else if( exprAlwaysTrue(pExpr) ){
+ if( ExprAlwaysFalse(pExpr) ){
+ tdsqlite3VdbeGoto(v, dest);
+ }else if( ExprAlwaysTrue(pExpr) ){
/* no-op */
}else{
- r1 = sqlite3ExprCodeTemp(pParse, pExpr, &regFree1);
- sqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pExpr, &regFree1);
+ tdsqlite3VdbeAddOp3(v, OP_IfNot, r1, dest, jumpIfNull!=0);
VdbeCoverage(v);
testcase( regFree1==0 );
testcase( jumpIfNull==0 );
@@ -97269,24 +107360,59 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int
break;
}
}
- sqlite3ReleaseTempReg(pParse, regFree1);
- sqlite3ReleaseTempReg(pParse, regFree2);
+ tdsqlite3ReleaseTempReg(pParse, regFree1);
+ tdsqlite3ReleaseTempReg(pParse, regFree2);
}
/*
-** Like sqlite3ExprIfFalse() except that a copy is made of pExpr before
+** Like tdsqlite3ExprIfFalse() except that a copy is made of pExpr before
** code generation, and that copy is deleted after code generation. This
** ensures that the original pExpr is unchanged.
*/
-SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse *pParse, Expr *pExpr, int dest,int jumpIfNull){
- sqlite3 *db = pParse->db;
- Expr *pCopy = sqlite3ExprDup(db, pExpr, 0);
+SQLITE_PRIVATE void tdsqlite3ExprIfFalseDup(Parse *pParse, Expr *pExpr, int dest,int jumpIfNull){
+ tdsqlite3 *db = pParse->db;
+ Expr *pCopy = tdsqlite3ExprDup(db, pExpr, 0);
if( db->mallocFailed==0 ){
- sqlite3ExprIfFalse(pParse, pCopy, dest, jumpIfNull);
+ tdsqlite3ExprIfFalse(pParse, pCopy, dest, jumpIfNull);
}
- sqlite3ExprDelete(db, pCopy);
+ tdsqlite3ExprDelete(db, pCopy);
}
+/*
+** Expression pVar is guaranteed to be an SQL variable. pExpr may be any
+** type of expression.
+**
+** If pExpr is a simple SQL value - an integer, real, string, blob
+** or NULL value - then the VDBE currently being prepared is configured
+** to re-prepare each time a new value is bound to variable pVar.
+**
+** Additionally, if pExpr is a simple SQL value and the value is the
+** same as that currently bound to variable pVar, non-zero is returned.
+** Otherwise, if the values are not the same or if pExpr is not a simple
+** SQL value, zero is returned.
+*/
+static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){
+ int res = 0;
+ int iVar;
+ tdsqlite3_value *pL, *pR = 0;
+
+ tdsqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
+ if( pR ){
+ iVar = pVar->iColumn;
+ tdsqlite3VdbeSetVarmask(pParse->pVdbe, iVar);
+ pL = tdsqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB);
+ if( pL ){
+ if( tdsqlite3_value_type(pL)==SQLITE_TEXT ){
+ tdsqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */
+ }
+ res = 0==tdsqlite3MemCompare(pL, pR, 0);
+ }
+ tdsqlite3ValueFree(pR);
+ tdsqlite3ValueFree(pL);
+ }
+
+ return res;
+}
/*
** Do a deep comparison of two expression trees. Return 0 if the two
@@ -97309,12 +107435,22 @@ SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse *pParse, Expr *pExpr, int dest,i
** this routine is used, it does not hurt to get an extra 2 - that
** just might result in some slightly slower code. But returning
** an incorrect 0 or 1 could lead to a malfunction.
+**
+** If pParse is not NULL then TK_VARIABLE terms in pA with bindings in
+** pParse->pReprepare can be matched against literals in pB. The
+** pParse->pVdbe->expmask bitmask is updated for each variable referenced.
+** If pParse is NULL (the normal case) then any TK_VARIABLE term in
+** Argument pParse should normally be NULL. If it is not NULL and pA or
+** pB causes a return value of 2.
*/
-SQLITE_PRIVATE int sqlite3ExprCompare(Expr *pA, Expr *pB, int iTab){
+SQLITE_PRIVATE int tdsqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){
u32 combinedFlags;
if( pA==0 || pB==0 ){
return pB==pA ? 0 : 2;
}
+ if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){
+ return 0;
+ }
combinedFlags = pA->flags | pB->flags;
if( combinedFlags & EP_IntValue ){
if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){
@@ -97322,40 +107458,77 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Expr *pA, Expr *pB, int iTab){
}
return 2;
}
- if( pA->op!=pB->op ){
- if( pA->op==TK_COLLATE && sqlite3ExprCompare(pA->pLeft, pB, iTab)<2 ){
+ if( pA->op!=pB->op || pA->op==TK_RAISE ){
+ if( pA->op==TK_COLLATE && tdsqlite3ExprCompare(pParse, pA->pLeft,pB,iTab)<2 ){
return 1;
}
- if( pB->op==TK_COLLATE && sqlite3ExprCompare(pA, pB->pLeft, iTab)<2 ){
+ if( pB->op==TK_COLLATE && tdsqlite3ExprCompare(pParse, pA,pB->pLeft,iTab)<2 ){
return 1;
}
return 2;
}
if( pA->op!=TK_COLUMN && pA->op!=TK_AGG_COLUMN && pA->u.zToken ){
- if( pA->op==TK_FUNCTION ){
- if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2;
- }else if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){
- return pA->op==TK_COLLATE ? 1 : 2;
+ if( pA->op==TK_FUNCTION || pA->op==TK_AGG_FUNCTION ){
+ if( tdsqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2;
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ assert( pA->op==pB->op );
+ if( ExprHasProperty(pA,EP_WinFunc)!=ExprHasProperty(pB,EP_WinFunc) ){
+ return 2;
+ }
+ if( ExprHasProperty(pA,EP_WinFunc) ){
+ if( tdsqlite3WindowCompare(pParse, pA->y.pWin, pB->y.pWin, 1)!=0 ){
+ return 2;
+ }
+ }
+#endif
+ }else if( pA->op==TK_NULL ){
+ return 0;
+ }else if( pA->op==TK_COLLATE ){
+ if( tdsqlite3_stricmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2;
+ }else if( ALWAYS(pB->u.zToken!=0) && strcmp(pA->u.zToken,pB->u.zToken)!=0 ){
+ return 2;
}
}
- if( (pA->flags & EP_Distinct)!=(pB->flags & EP_Distinct) ) return 2;
- if( ALWAYS((combinedFlags & EP_TokenOnly)==0) ){
+ if( (pA->flags & (EP_Distinct|EP_Commuted))
+ != (pB->flags & (EP_Distinct|EP_Commuted)) ) return 2;
+ if( (combinedFlags & EP_TokenOnly)==0 ){
if( combinedFlags & EP_xIsSelect ) return 2;
- if( sqlite3ExprCompare(pA->pLeft, pB->pLeft, iTab) ) return 2;
- if( sqlite3ExprCompare(pA->pRight, pB->pRight, iTab) ) return 2;
- if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2;
- if( ALWAYS((combinedFlags & EP_Reduced)==0) && pA->op!=TK_STRING ){
+ if( (combinedFlags & EP_FixedCol)==0
+ && tdsqlite3ExprCompare(pParse, pA->pLeft, pB->pLeft, iTab) ) return 2;
+ if( tdsqlite3ExprCompare(pParse, pA->pRight, pB->pRight, iTab) ) return 2;
+ if( tdsqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2;
+ if( pA->op!=TK_STRING
+ && pA->op!=TK_TRUEFALSE
+ && (combinedFlags & EP_Reduced)==0
+ ){
if( pA->iColumn!=pB->iColumn ) return 2;
- if( pA->iTable!=pB->iTable
- && (pA->iTable!=iTab || NEVER(pB->iTable>=0)) ) return 2;
+ if( pA->op2!=pB->op2 ){
+ if( pA->op==TK_TRUTH ) return 2;
+ if( pA->op==TK_FUNCTION && iTab<0 ){
+ /* Ex: CREATE TABLE t1(a CHECK( a<julianday('now') ));
+ ** INSERT INTO t1(a) VALUES(julianday('now')+10);
+ ** Without this test, tdsqlite3ExprCodeAtInit() will run on the
+ ** the julianday() of INSERT first, and remember that expression.
+ ** Then tdsqlite3ExprCodeInit() will see the julianday() in the CHECK
+ ** constraint as redundant, reusing the one from the INSERT, even
+ ** though the julianday() in INSERT lacks the critical NC_IsCheck
+ ** flag. See ticket [830277d9db6c3ba1] (2019-10-30)
+ */
+ return 2;
+ }
+ }
+ if( pA->op!=TK_IN && pA->iTable!=pB->iTable && pA->iTable!=iTab ){
+ return 2;
+ }
}
}
return 0;
}
/*
-** Compare two ExprList objects. Return 0 if they are identical and
-** non-zero if they differ in any way.
+** Compare two ExprList objects. Return 0 if they are identical, 1
+** if they are certainly different, or 2 if it is not possible to
+** determine if they are identical or not.
**
** If any subelement of pB has Expr.iTable==(-1) then it is allowed
** to compare equal to an equivalent element in pA with Expr.iTable==iTab.
@@ -97368,16 +107541,105 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Expr *pA, Expr *pB, int iTab){
** Two NULL pointers are considered to be the same. But a NULL pointer
** always differs from a non-NULL pointer.
*/
-SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){
+SQLITE_PRIVATE int tdsqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){
int i;
if( pA==0 && pB==0 ) return 0;
if( pA==0 || pB==0 ) return 1;
if( pA->nExpr!=pB->nExpr ) return 1;
for(i=0; i<pA->nExpr; i++){
+ int res;
Expr *pExprA = pA->a[i].pExpr;
Expr *pExprB = pB->a[i].pExpr;
- if( pA->a[i].sortOrder!=pB->a[i].sortOrder ) return 1;
- if( sqlite3ExprCompare(pExprA, pExprB, iTab) ) return 1;
+ if( pA->a[i].sortFlags!=pB->a[i].sortFlags ) return 1;
+ if( (res = tdsqlite3ExprCompare(0, pExprA, pExprB, iTab)) ) return res;
+ }
+ return 0;
+}
+
+/*
+** Like tdsqlite3ExprCompare() except COLLATE operators at the top-level
+** are ignored.
+*/
+SQLITE_PRIVATE int tdsqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){
+ return tdsqlite3ExprCompare(0,
+ tdsqlite3ExprSkipCollateAndLikely(pA),
+ tdsqlite3ExprSkipCollateAndLikely(pB),
+ iTab);
+}
+
+/*
+** Return non-zero if Expr p can only be true if pNN is not NULL.
+**
+** Or if seenNot is true, return non-zero if Expr p can only be
+** non-NULL if pNN is not NULL
+*/
+static int exprImpliesNotNull(
+ Parse *pParse, /* Parsing context */
+ Expr *p, /* The expression to be checked */
+ Expr *pNN, /* The expression that is NOT NULL */
+ int iTab, /* Table being evaluated */
+ int seenNot /* Return true only if p can be any non-NULL value */
+){
+ assert( p );
+ assert( pNN );
+ if( tdsqlite3ExprCompare(pParse, p, pNN, iTab)==0 ){
+ return pNN->op!=TK_NULL;
+ }
+ switch( p->op ){
+ case TK_IN: {
+ if( seenNot && ExprHasProperty(p, EP_xIsSelect) ) return 0;
+ assert( ExprHasProperty(p,EP_xIsSelect)
+ || (p->x.pList!=0 && p->x.pList->nExpr>0) );
+ return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1);
+ }
+ case TK_BETWEEN: {
+ ExprList *pList = p->x.pList;
+ assert( pList!=0 );
+ assert( pList->nExpr==2 );
+ if( seenNot ) return 0;
+ if( exprImpliesNotNull(pParse, pList->a[0].pExpr, pNN, iTab, 1)
+ || exprImpliesNotNull(pParse, pList->a[1].pExpr, pNN, iTab, 1)
+ ){
+ return 1;
+ }
+ return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1);
+ }
+ case TK_EQ:
+ case TK_NE:
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ case TK_PLUS:
+ case TK_MINUS:
+ case TK_BITOR:
+ case TK_LSHIFT:
+ case TK_RSHIFT:
+ case TK_CONCAT:
+ seenNot = 1;
+ /* Fall thru */
+ case TK_STAR:
+ case TK_REM:
+ case TK_BITAND:
+ case TK_SLASH: {
+ if( exprImpliesNotNull(pParse, p->pRight, pNN, iTab, seenNot) ) return 1;
+ /* Fall thru into the next case */
+ }
+ case TK_SPAN:
+ case TK_COLLATE:
+ case TK_UPLUS:
+ case TK_UMINUS: {
+ return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, seenNot);
+ }
+ case TK_TRUTH: {
+ if( seenNot ) return 0;
+ if( p->op2!=TK_IS ) return 0;
+ return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1);
+ }
+ case TK_BITNOT:
+ case TK_NOT: {
+ return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1);
+ }
}
return 0;
}
@@ -97398,23 +107660,27 @@ SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){
** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has
** Expr.iTable<0 then assume a table number given by iTab.
**
+** If pParse is not NULL, then the values of bound variables in pE1 are
+** compared against literal values in pE2 and pParse->pVdbe->expmask is
+** modified to record which bound variables are referenced. If pParse
+** is NULL, then false will be returned if pE1 contains any bound variables.
+**
** When in doubt, return false. Returning true might give a performance
** improvement. Returning false might cause a performance reduction, but
** it will always give the correct answer and is hence always safe.
*/
-SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Expr *pE1, Expr *pE2, int iTab){
- if( sqlite3ExprCompare(pE1, pE2, iTab)==0 ){
+SQLITE_PRIVATE int tdsqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, int iTab){
+ if( tdsqlite3ExprCompare(pParse, pE1, pE2, iTab)==0 ){
return 1;
}
if( pE2->op==TK_OR
- && (sqlite3ExprImpliesExpr(pE1, pE2->pLeft, iTab)
- || sqlite3ExprImpliesExpr(pE1, pE2->pRight, iTab) )
+ && (tdsqlite3ExprImpliesExpr(pParse, pE1, pE2->pLeft, iTab)
+ || tdsqlite3ExprImpliesExpr(pParse, pE1, pE2->pRight, iTab) )
){
return 1;
}
if( pE2->op==TK_NOTNULL
- && sqlite3ExprCompare(pE1->pLeft, pE2->pLeft, iTab)==0
- && (pE1->op!=TK_ISNULL && pE1->op!=TK_IS)
+ && exprImpliesNotNull(pParse, pE1, pE2->pLeft, iTab, 0)
){
return 1;
}
@@ -97422,6 +107688,134 @@ SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Expr *pE1, Expr *pE2, int iTab){
}
/*
+** This is the Expr node callback for tdsqlite3ExprImpliesNonNullRow().
+** If the expression node requires that the table at pWalker->iCur
+** have one or more non-NULL column, then set pWalker->eCode to 1 and abort.
+**
+** This routine controls an optimization. False positives (setting
+** pWalker->eCode to 1 when it should not be) are deadly, but false-negatives
+** (never setting pWalker->eCode) is a harmless missed optimization.
+*/
+static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){
+ testcase( pExpr->op==TK_AGG_COLUMN );
+ testcase( pExpr->op==TK_AGG_FUNCTION );
+ if( ExprHasProperty(pExpr, EP_FromJoin) ) return WRC_Prune;
+ switch( pExpr->op ){
+ case TK_ISNOT:
+ case TK_ISNULL:
+ case TK_NOTNULL:
+ case TK_IS:
+ case TK_OR:
+ case TK_VECTOR:
+ case TK_CASE:
+ case TK_IN:
+ case TK_FUNCTION:
+ case TK_TRUTH:
+ testcase( pExpr->op==TK_ISNOT );
+ testcase( pExpr->op==TK_ISNULL );
+ testcase( pExpr->op==TK_NOTNULL );
+ testcase( pExpr->op==TK_IS );
+ testcase( pExpr->op==TK_OR );
+ testcase( pExpr->op==TK_VECTOR );
+ testcase( pExpr->op==TK_CASE );
+ testcase( pExpr->op==TK_IN );
+ testcase( pExpr->op==TK_FUNCTION );
+ testcase( pExpr->op==TK_TRUTH );
+ return WRC_Prune;
+ case TK_COLUMN:
+ if( pWalker->u.iCur==pExpr->iTable ){
+ pWalker->eCode = 1;
+ return WRC_Abort;
+ }
+ return WRC_Prune;
+
+ case TK_AND:
+ if( pWalker->eCode==0 ){
+ tdsqlite3WalkExpr(pWalker, pExpr->pLeft);
+ if( pWalker->eCode ){
+ pWalker->eCode = 0;
+ tdsqlite3WalkExpr(pWalker, pExpr->pRight);
+ }
+ }
+ return WRC_Prune;
+
+ case TK_BETWEEN:
+ if( tdsqlite3WalkExpr(pWalker, pExpr->pLeft)==WRC_Abort ){
+ assert( pWalker->eCode );
+ return WRC_Abort;
+ }
+ return WRC_Prune;
+
+ /* Virtual tables are allowed to use constraints like x=NULL. So
+ ** a term of the form x=y does not prove that y is not null if x
+ ** is the column of a virtual table */
+ case TK_EQ:
+ case TK_NE:
+ case TK_LT:
+ case TK_LE:
+ case TK_GT:
+ case TK_GE:
+ testcase( pExpr->op==TK_EQ );
+ testcase( pExpr->op==TK_NE );
+ testcase( pExpr->op==TK_LT );
+ testcase( pExpr->op==TK_LE );
+ testcase( pExpr->op==TK_GT );
+ testcase( pExpr->op==TK_GE );
+ if( (pExpr->pLeft->op==TK_COLUMN && IsVirtual(pExpr->pLeft->y.pTab))
+ || (pExpr->pRight->op==TK_COLUMN && IsVirtual(pExpr->pRight->y.pTab))
+ ){
+ return WRC_Prune;
+ }
+
+ default:
+ return WRC_Continue;
+ }
+}
+
+/*
+** Return true (non-zero) if expression p can only be true if at least
+** one column of table iTab is non-null. In other words, return true
+** if expression p will always be NULL or false if every column of iTab
+** is NULL.
+**
+** False negatives are acceptable. In other words, it is ok to return
+** zero even if expression p will never be true of every column of iTab
+** is NULL. A false negative is merely a missed optimization opportunity.
+**
+** False positives are not allowed, however. A false positive may result
+** in an incorrect answer.
+**
+** Terms of p that are marked with EP_FromJoin (and hence that come from
+** the ON or USING clauses of LEFT JOINS) are excluded from the analysis.
+**
+** This routine is used to check if a LEFT JOIN can be converted into
+** an ordinary JOIN. The p argument is the WHERE clause. If the WHERE
+** clause requires that some column of the right table of the LEFT JOIN
+** be non-NULL, then the LEFT JOIN can be safely converted into an
+** ordinary join.
+*/
+SQLITE_PRIVATE int tdsqlite3ExprImpliesNonNullRow(Expr *p, int iTab){
+ Walker w;
+ p = tdsqlite3ExprSkipCollateAndLikely(p);
+ if( p==0 ) return 0;
+ if( p->op==TK_NOTNULL ){
+ p = p->pLeft;
+ }else{
+ while( p->op==TK_AND ){
+ if( tdsqlite3ExprImpliesNonNullRow(p->pLeft, iTab) ) return 1;
+ p = p->pRight;
+ }
+ }
+ w.xExprCallback = impliesNotNullRow;
+ w.xSelectCallback = 0;
+ w.xSelectCallback2 = 0;
+ w.eCode = 0;
+ w.u.iCur = iTab;
+ tdsqlite3WalkExpr(&w, p);
+ return w.eCode;
+}
+
+/*
** An instance of the following structure is used by the tree walker
** to determine if an expression can be evaluated by reference to the
** index only, without having to do a search for the corresponding
@@ -97441,7 +107835,7 @@ struct IdxCover {
static int exprIdxCover(Walker *pWalker, Expr *pExpr){
if( pExpr->op==TK_COLUMN
&& pExpr->iTable==pWalker->u.pIdxCover->iCur
- && sqlite3ColumnOfIndex(pWalker->u.pIdxCover->pIdx, pExpr->iColumn)<0
+ && tdsqlite3TableColumnToIndex(pWalker->u.pIdxCover->pIdx, pExpr->iColumn)<0
){
pWalker->eCode = 1;
return WRC_Abort;
@@ -97459,7 +107853,7 @@ static int exprIdxCover(Walker *pWalker, Expr *pExpr){
** evaluated using only the index and without having to lookup the
** corresponding table entry.
*/
-SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(
+SQLITE_PRIVATE int tdsqlite3ExprCoveredByIndex(
Expr *pExpr, /* The index to be tested */
int iCur, /* The cursor number for the corresponding table */
Index *pIdx /* The index that might be used for coverage */
@@ -97471,7 +107865,7 @@ SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(
xcov.pIdx = pIdx;
w.xExprCallback = exprIdxCover;
w.u.pIdxCover = &xcov;
- sqlite3WalkExpr(&w, pExpr);
+ tdsqlite3WalkExpr(&w, pExpr);
return !w.eCode;
}
@@ -97480,7 +107874,7 @@ SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(
** An instance of the following structure is used by the tree walker
** to count references to table columns in the arguments of an
** aggregate function, in order to implement the
-** sqlite3FunctionThisSrc() routine.
+** tdsqlite3FunctionThisSrc() routine.
*/
struct SrcCount {
SrcList *pSrc; /* One particular FROM clause in a nested query */
@@ -97492,12 +107886,13 @@ struct SrcCount {
** Count the number of references to columns.
*/
static int exprSrcCount(Walker *pWalker, Expr *pExpr){
- /* The NEVER() on the second term is because sqlite3FunctionUsesThisSrc()
- ** is always called before sqlite3ExprAnalyzeAggregates() and so the
- ** TK_COLUMNs have not yet been converted into TK_AGG_COLUMN. If
- ** sqlite3FunctionUsesThisSrc() is used differently in the future, the
- ** NEVER() will need to be removed. */
- if( pExpr->op==TK_COLUMN || NEVER(pExpr->op==TK_AGG_COLUMN) ){
+ /* There was once a NEVER() on the second term on the grounds that
+ ** tdsqlite3FunctionUsesThisSrc() was always called before
+ ** tdsqlite3ExprAnalyzeAggregates() and so the TK_COLUMNs have not yet
+ ** been converted into TK_AGG_COLUMN. But this is no longer true due
+ ** to window functions - tdsqlite3WindowRewrite() may now indirectly call
+ ** FunctionUsesThisSrc() when creating a new sub-select. */
+ if( pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN ){
int i;
struct SrcCount *p = pWalker->u.pSrcCount;
SrcList *pSrc = p->pSrc;
@@ -97507,7 +107902,10 @@ static int exprSrcCount(Walker *pWalker, Expr *pExpr){
}
if( i<nSrc ){
p->nThis++;
- }else{
+ }else if( nSrc==0 || pExpr->iTable<pSrc->a[0].iCursor ){
+ /* In a well-formed parse tree (no name resolution errors),
+ ** TK_COLUMN nodes with smaller Expr.iTable values are in an
+ ** outer context. Those are the only ones to count as "other" */
p->nOther++;
}
}
@@ -97520,17 +107918,23 @@ static int exprSrcCount(Walker *pWalker, Expr *pExpr){
** has no arguments or has only constant arguments. Return false if pExpr
** references columns but not columns of tables found in pSrcList.
*/
-SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr *pExpr, SrcList *pSrcList){
+SQLITE_PRIVATE int tdsqlite3FunctionUsesThisSrc(Expr *pExpr, SrcList *pSrcList){
Walker w;
struct SrcCount cnt;
assert( pExpr->op==TK_AGG_FUNCTION );
memset(&w, 0, sizeof(w));
w.xExprCallback = exprSrcCount;
+ w.xSelectCallback = tdsqlite3SelectWalkNoop;
w.u.pSrcCount = &cnt;
cnt.pSrc = pSrcList;
cnt.nThis = 0;
cnt.nOther = 0;
- sqlite3WalkExprList(&w, pExpr->x.pList);
+ tdsqlite3WalkExprList(&w, pExpr->x.pList);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( ExprHasProperty(pExpr, EP_WinFunc) ){
+ tdsqlite3WalkExpr(&w, pExpr->y.pWin->pFilter);
+ }
+#endif
return cnt.nThis>0 || cnt.nOther==0;
}
@@ -97538,9 +107942,9 @@ SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr *pExpr, SrcList *pSrcList){
** Add a new element to the pAggInfo->aCol[] array. Return the index of
** the new element. Return a negative number if malloc fails.
*/
-static int addAggInfoColumn(sqlite3 *db, AggInfo *pInfo){
+static int addAggInfoColumn(tdsqlite3 *db, AggInfo *pInfo){
int i;
- pInfo->aCol = sqlite3ArrayAllocate(
+ pInfo->aCol = tdsqlite3ArrayAllocate(
db,
pInfo->aCol,
sizeof(pInfo->aCol[0]),
@@ -97554,9 +107958,9 @@ static int addAggInfoColumn(sqlite3 *db, AggInfo *pInfo){
** Add a new element to the pAggInfo->aFunc[] array. Return the index of
** the new element. Return a negative number if malloc fails.
*/
-static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){
+static int addAggInfoFunc(tdsqlite3 *db, AggInfo *pInfo){
int i;
- pInfo->aFunc = sqlite3ArrayAllocate(
+ pInfo->aFunc = tdsqlite3ArrayAllocate(
db,
pInfo->aFunc,
sizeof(pInfo->aFunc[0]),
@@ -97568,7 +107972,7 @@ static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){
/*
** This is the xExprCallback for a tree walker. It is used to
-** implement sqlite3ExprAnalyzeAggregates(). See sqlite3ExprAnalyzeAggregates
+** implement tdsqlite3ExprAnalyzeAggregates(). See tdsqlite3ExprAnalyzeAggregates
** for additional information.
*/
static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
@@ -97576,8 +107980,9 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
NameContext *pNC = pWalker->u.pNC;
Parse *pParse = pNC->pParse;
SrcList *pSrcList = pNC->pSrcList;
- AggInfo *pAggInfo = pNC->pAggInfo;
+ AggInfo *pAggInfo = pNC->uNC.pAggInfo;
+ assert( pNC->ncFlags & NC_UAggInfo );
switch( pExpr->op ){
case TK_AGG_COLUMN:
case TK_COLUMN: {
@@ -97609,7 +108014,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
&& (k = addAggInfoColumn(pParse->db, pAggInfo))>=0
){
pCol = &pAggInfo->aCol[k];
- pCol->pTab = pExpr->pTab;
+ pCol->pTab = pExpr->y.pTab;
pCol->iTable = pExpr->iTable;
pCol->iColumn = pExpr->iColumn;
pCol->iMem = ++pParse->nMem;
@@ -97657,7 +108062,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
*/
struct AggInfo_func *pItem = pAggInfo->aFunc;
for(i=0; i<pAggInfo->nFunc; i++, pItem++){
- if( sqlite3ExprCompare(pItem->pExpr, pExpr, -1)==0 ){
+ if( tdsqlite3ExprCompare(0, pItem->pExpr, pExpr, -1)==0 ){
break;
}
}
@@ -97672,7 +108077,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
pItem->pExpr = pExpr;
pItem->iMem = ++pParse->nMem;
assert( !ExprHasProperty(pExpr, EP_IntValue) );
- pItem->pFunc = sqlite3FindFunction(pParse->db,
+ pItem->pFunc = tdsqlite3FindFunction(pParse->db,
pExpr->u.zToken,
pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0);
if( pExpr->flags & EP_Distinct ){
@@ -97697,10 +108102,14 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
return WRC_Continue;
}
static int analyzeAggregatesInSelect(Walker *pWalker, Select *pSelect){
- UNUSED_PARAMETER(pWalker);
UNUSED_PARAMETER(pSelect);
+ pWalker->walkerDepth++;
return WRC_Continue;
}
+static void analyzeAggregatesInSelectEnd(Walker *pWalker, Select *pSelect){
+ UNUSED_PARAMETER(pSelect);
+ pWalker->walkerDepth--;
+}
/*
** Analyze the pExpr expression looking for aggregate functions and
@@ -97709,30 +108118,32 @@ static int analyzeAggregatesInSelect(Walker *pWalker, Select *pSelect){
** necessary.
**
** This routine should only be called after the expression has been
-** analyzed by sqlite3ResolveExprNames().
+** analyzed by tdsqlite3ResolveExprNames().
*/
-SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext *pNC, Expr *pExpr){
+SQLITE_PRIVATE void tdsqlite3ExprAnalyzeAggregates(NameContext *pNC, Expr *pExpr){
Walker w;
- memset(&w, 0, sizeof(w));
w.xExprCallback = analyzeAggregate;
w.xSelectCallback = analyzeAggregatesInSelect;
+ w.xSelectCallback2 = analyzeAggregatesInSelectEnd;
+ w.walkerDepth = 0;
w.u.pNC = pNC;
+ w.pParse = 0;
assert( pNC->pSrcList!=0 );
- sqlite3WalkExpr(&w, pExpr);
+ tdsqlite3WalkExpr(&w, pExpr);
}
/*
-** Call sqlite3ExprAnalyzeAggregates() for every expression in an
+** Call tdsqlite3ExprAnalyzeAggregates() for every expression in an
** expression list. Return the number of errors.
**
** If an error is found, the analysis is cut short.
*/
-SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext *pNC, ExprList *pList){
+SQLITE_PRIVATE void tdsqlite3ExprAnalyzeAggList(NameContext *pNC, ExprList *pList){
struct ExprList_item *pItem;
int i;
if( pList ){
for(pItem=pList->a, i=0; i<pList->nExpr; i++, pItem++){
- sqlite3ExprAnalyzeAggregates(pNC, pItem->pExpr);
+ tdsqlite3ExprAnalyzeAggregates(pNC, pItem->pExpr);
}
}
}
@@ -97740,7 +108151,7 @@ SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext *pNC, ExprList *pList)
/*
** Allocate a single new register for use to hold some intermediate result.
*/
-SQLITE_PRIVATE int sqlite3GetTempReg(Parse *pParse){
+SQLITE_PRIVATE int tdsqlite3GetTempReg(Parse *pParse){
if( pParse->nTempReg==0 ){
return ++pParse->nMem;
}
@@ -97750,35 +108161,25 @@ SQLITE_PRIVATE int sqlite3GetTempReg(Parse *pParse){
/*
** Deallocate a register, making available for reuse for some other
** purpose.
-**
-** If a register is currently being used by the column cache, then
-** the deallocation is deferred until the column cache line that uses
-** the register becomes stale.
*/
-SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse *pParse, int iReg){
- if( iReg && pParse->nTempReg<ArraySize(pParse->aTempReg) ){
- int i;
- struct yColCache *p;
- for(i=0, p=pParse->aColCache; i<pParse->nColCache; i++, p++){
- if( p->iReg==iReg ){
- p->tempReg = 1;
- return;
- }
+SQLITE_PRIVATE void tdsqlite3ReleaseTempReg(Parse *pParse, int iReg){
+ if( iReg ){
+ tdsqlite3VdbeReleaseRegisters(pParse, iReg, 1, 0, 0);
+ if( pParse->nTempReg<ArraySize(pParse->aTempReg) ){
+ pParse->aTempReg[pParse->nTempReg++] = iReg;
}
- pParse->aTempReg[pParse->nTempReg++] = iReg;
}
}
/*
** Allocate or deallocate a block of nReg consecutive registers.
*/
-SQLITE_PRIVATE int sqlite3GetTempRange(Parse *pParse, int nReg){
+SQLITE_PRIVATE int tdsqlite3GetTempRange(Parse *pParse, int nReg){
int i, n;
- if( nReg==1 ) return sqlite3GetTempReg(pParse);
+ if( nReg==1 ) return tdsqlite3GetTempReg(pParse);
i = pParse->iRangeReg;
n = pParse->nRangeReg;
if( nReg<=n ){
- assert( !usedAsColumnCache(pParse, i, i+n-1) );
pParse->iRangeReg += nReg;
pParse->nRangeReg -= nReg;
}else{
@@ -97787,12 +108188,12 @@ SQLITE_PRIVATE int sqlite3GetTempRange(Parse *pParse, int nReg){
}
return i;
}
-SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse *pParse, int iReg, int nReg){
+SQLITE_PRIVATE void tdsqlite3ReleaseTempRange(Parse *pParse, int iReg, int nReg){
if( nReg==1 ){
- sqlite3ReleaseTempReg(pParse, iReg);
+ tdsqlite3ReleaseTempReg(pParse, iReg);
return;
}
- sqlite3ExprCacheRemove(pParse, iReg, nReg);
+ tdsqlite3VdbeReleaseRegisters(pParse, iReg, nReg, 0, 0);
if( nReg>pParse->nRangeReg ){
pParse->nRangeReg = nReg;
pParse->iRangeReg = iReg;
@@ -97801,8 +108202,13 @@ SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse *pParse, int iReg, int nReg){
/*
** Mark all temporary registers as being unavailable for reuse.
+**
+** Always invoke this procedure after coding a subroutine or co-routine
+** that might be invoked from other parts of the code, to ensure that
+** the sub/co-routine does not use registers in common with the code that
+** invokes the sub/co-routine.
*/
-SQLITE_PRIVATE void sqlite3ClearTempRegCache(Parse *pParse){
+SQLITE_PRIVATE void tdsqlite3ClearTempRegCache(Parse *pParse){
pParse->nTempReg = 0;
pParse->nRangeReg = 0;
}
@@ -97813,11 +108219,11 @@ SQLITE_PRIVATE void sqlite3ClearTempRegCache(Parse *pParse){
** statements.
*/
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){
+SQLITE_PRIVATE int tdsqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){
int i;
if( pParse->nRangeReg>0
- && pParse->iRangeReg+pParse->nRangeReg<iLast
- && pParse->iRangeReg>=iFirst
+ && pParse->iRangeReg+pParse->nRangeReg > iFirst
+ && pParse->iRangeReg <= iLast
){
return 0;
}
@@ -97854,373 +108260,76 @@ SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){
*/
#ifndef SQLITE_OMIT_ALTERTABLE
-
-/*
-** This function is used by SQL generated to implement the
-** ALTER TABLE command. The first argument is the text of a CREATE TABLE or
-** CREATE INDEX command. The second is a table name. The table name in
-** the CREATE TABLE or CREATE INDEX statement is replaced with the third
-** argument and the result returned. Examples:
-**
-** sqlite_rename_table('CREATE TABLE abc(a, b, c)', 'def')
-** -> 'CREATE TABLE def(a, b, c)'
-**
-** sqlite_rename_table('CREATE INDEX i ON abc(a)', 'def')
-** -> 'CREATE INDEX i ON def(a, b, c)'
-*/
-static void renameTableFunc(
- sqlite3_context *context,
- int NotUsed,
- sqlite3_value **argv
-){
- unsigned char const *zSql = sqlite3_value_text(argv[0]);
- unsigned char const *zTableName = sqlite3_value_text(argv[1]);
-
- int token;
- Token tname;
- unsigned char const *zCsr = zSql;
- int len = 0;
- char *zRet;
-
- sqlite3 *db = sqlite3_context_db_handle(context);
-
- UNUSED_PARAMETER(NotUsed);
-
- /* The principle used to locate the table name in the CREATE TABLE
- ** statement is that the table name is the first non-space token that
- ** is immediately followed by a TK_LP or TK_USING token.
- */
- if( zSql ){
- do {
- if( !*zCsr ){
- /* Ran out of input before finding an opening bracket. Return NULL. */
- return;
- }
-
- /* Store the token that zCsr points to in tname. */
- tname.z = (char*)zCsr;
- tname.n = len;
-
- /* Advance zCsr to the next token. Store that token type in 'token',
- ** and its length in 'len' (to be used next iteration of this loop).
- */
- do {
- zCsr += len;
- len = sqlite3GetToken(zCsr, &token);
- } while( token==TK_SPACE );
- assert( len>0 );
- } while( token!=TK_LP && token!=TK_USING );
-
- zRet = sqlite3MPrintf(db, "%.*s\"%w\"%s", (int)(((u8*)tname.z) - zSql),
- zSql, zTableName, tname.z+tname.n);
- sqlite3_result_text(context, zRet, -1, SQLITE_DYNAMIC);
- }
-}
-
/*
-** This C function implements an SQL user function that is used by SQL code
-** generated by the ALTER TABLE ... RENAME command to modify the definition
-** of any foreign key constraints that use the table being renamed as the
-** parent table. It is passed three arguments:
-**
-** 1) The complete text of the CREATE TABLE statement being modified,
-** 2) The old name of the table being renamed, and
-** 3) The new name of the table being renamed.
-**
-** It returns the new CREATE TABLE statement. For example:
+** Parameter zName is the name of a table that is about to be altered
+** (either with ALTER TABLE ... RENAME TO or ALTER TABLE ... ADD COLUMN).
+** If the table is a system table, this function leaves an error message
+** in pParse->zErr (system tables may not be altered) and returns non-zero.
**
-** sqlite_rename_parent('CREATE TABLE t1(a REFERENCES t2)', 't2', 't3')
-** -> 'CREATE TABLE t1(a REFERENCES t3)'
-*/
-#ifndef SQLITE_OMIT_FOREIGN_KEY
-static void renameParentFunc(
- sqlite3_context *context,
- int NotUsed,
- sqlite3_value **argv
-){
- sqlite3 *db = sqlite3_context_db_handle(context);
- char *zOutput = 0;
- char *zResult;
- unsigned char const *zInput = sqlite3_value_text(argv[0]);
- unsigned char const *zOld = sqlite3_value_text(argv[1]);
- unsigned char const *zNew = sqlite3_value_text(argv[2]);
-
- unsigned const char *z; /* Pointer to token */
- int n; /* Length of token z */
- int token; /* Type of token */
-
- UNUSED_PARAMETER(NotUsed);
- if( zInput==0 || zOld==0 ) return;
- for(z=zInput; *z; z=z+n){
- n = sqlite3GetToken(z, &token);
- if( token==TK_REFERENCES ){
- char *zParent;
- do {
- z += n;
- n = sqlite3GetToken(z, &token);
- }while( token==TK_SPACE );
-
- if( token==TK_ILLEGAL ) break;
- zParent = sqlite3DbStrNDup(db, (const char *)z, n);
- if( zParent==0 ) break;
- sqlite3Dequote(zParent);
- if( 0==sqlite3StrICmp((const char *)zOld, zParent) ){
- char *zOut = sqlite3MPrintf(db, "%s%.*s\"%w\"",
- (zOutput?zOutput:""), (int)(z-zInput), zInput, (const char *)zNew
- );
- sqlite3DbFree(db, zOutput);
- zOutput = zOut;
- zInput = &z[n];
- }
- sqlite3DbFree(db, zParent);
- }
- }
-
- zResult = sqlite3MPrintf(db, "%s%s", (zOutput?zOutput:""), zInput),
- sqlite3_result_text(context, zResult, -1, SQLITE_DYNAMIC);
- sqlite3DbFree(db, zOutput);
-}
-#endif
-
-#ifndef SQLITE_OMIT_TRIGGER
-/* This function is used by SQL generated to implement the
-** ALTER TABLE command. The first argument is the text of a CREATE TRIGGER
-** statement. The second is a table name. The table name in the CREATE
-** TRIGGER statement is replaced with the third argument and the result
-** returned. This is analagous to renameTableFunc() above, except for CREATE
-** TRIGGER, not CREATE INDEX and CREATE TABLE.
-*/
-static void renameTriggerFunc(
- sqlite3_context *context,
- int NotUsed,
- sqlite3_value **argv
-){
- unsigned char const *zSql = sqlite3_value_text(argv[0]);
- unsigned char const *zTableName = sqlite3_value_text(argv[1]);
-
- int token;
- Token tname;
- int dist = 3;
- unsigned char const *zCsr = zSql;
- int len = 0;
- char *zRet;
- sqlite3 *db = sqlite3_context_db_handle(context);
-
- UNUSED_PARAMETER(NotUsed);
-
- /* The principle used to locate the table name in the CREATE TRIGGER
- ** statement is that the table name is the first token that is immediately
- ** preceded by either TK_ON or TK_DOT and immediately followed by one
- ** of TK_WHEN, TK_BEGIN or TK_FOR.
- */
- if( zSql ){
- do {
-
- if( !*zCsr ){
- /* Ran out of input before finding the table name. Return NULL. */
- return;
- }
-
- /* Store the token that zCsr points to in tname. */
- tname.z = (char*)zCsr;
- tname.n = len;
-
- /* Advance zCsr to the next token. Store that token type in 'token',
- ** and its length in 'len' (to be used next iteration of this loop).
- */
- do {
- zCsr += len;
- len = sqlite3GetToken(zCsr, &token);
- }while( token==TK_SPACE );
- assert( len>0 );
-
- /* Variable 'dist' stores the number of tokens read since the most
- ** recent TK_DOT or TK_ON. This means that when a WHEN, FOR or BEGIN
- ** token is read and 'dist' equals 2, the condition stated above
- ** to be met.
- **
- ** Note that ON cannot be a database, table or column name, so
- ** there is no need to worry about syntax like
- ** "CREATE TRIGGER ... ON ON.ON BEGIN ..." etc.
- */
- dist++;
- if( token==TK_DOT || token==TK_ON ){
- dist = 0;
- }
- } while( dist!=2 || (token!=TK_WHEN && token!=TK_FOR && token!=TK_BEGIN) );
-
- /* Variable tname now contains the token that is the old table-name
- ** in the CREATE TRIGGER statement.
- */
- zRet = sqlite3MPrintf(db, "%.*s\"%w\"%s", (int)(((u8*)tname.z) - zSql),
- zSql, zTableName, tname.z+tname.n);
- sqlite3_result_text(context, zRet, -1, SQLITE_DYNAMIC);
- }
-}
-#endif /* !SQLITE_OMIT_TRIGGER */
-
-/*
-** Register built-in functions used to help implement ALTER TABLE
+** Or, if zName is not a system table, zero is returned.
*/
-SQLITE_PRIVATE void sqlite3AlterFunctions(void){
- static FuncDef aAlterTableFuncs[] = {
- FUNCTION(sqlite_rename_table, 2, 0, 0, renameTableFunc),
-#ifndef SQLITE_OMIT_TRIGGER
- FUNCTION(sqlite_rename_trigger, 2, 0, 0, renameTriggerFunc),
-#endif
-#ifndef SQLITE_OMIT_FOREIGN_KEY
- FUNCTION(sqlite_rename_parent, 3, 0, 0, renameParentFunc),
+static int isAlterableTable(Parse *pParse, Table *pTab){
+ if( 0==tdsqlite3StrNICmp(pTab->zName, "sqlite_", 7)
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ || ( (pTab->tabFlags & TF_Shadow)!=0
+ && tdsqlite3ReadOnlyShadowTables(pParse->db)
+ )
#endif
- };
- sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs));
-}
-
-/*
-** This function is used to create the text of expressions of the form:
-**
-** name=<constant1> OR name=<constant2> OR ...
-**
-** If argument zWhere is NULL, then a pointer string containing the text
-** "name=<constant>" is returned, where <constant> is the quoted version
-** of the string passed as argument zConstant. The returned buffer is
-** allocated using sqlite3DbMalloc(). It is the responsibility of the
-** caller to ensure that it is eventually freed.
-**
-** If argument zWhere is not NULL, then the string returned is
-** "<where> OR name=<constant>", where <where> is the contents of zWhere.
-** In this case zWhere is passed to sqlite3DbFree() before returning.
-**
-*/
-static char *whereOrName(sqlite3 *db, char *zWhere, char *zConstant){
- char *zNew;
- if( !zWhere ){
- zNew = sqlite3MPrintf(db, "name=%Q", zConstant);
- }else{
- zNew = sqlite3MPrintf(db, "%s OR name=%Q", zWhere, zConstant);
- sqlite3DbFree(db, zWhere);
- }
- return zNew;
-}
-
-#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
-/*
-** Generate the text of a WHERE expression which can be used to select all
-** tables that have foreign key constraints that refer to table pTab (i.e.
-** constraints for which pTab is the parent table) from the sqlite_master
-** table.
-*/
-static char *whereForeignKeys(Parse *pParse, Table *pTab){
- FKey *p;
- char *zWhere = 0;
- for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
- zWhere = whereOrName(pParse->db, zWhere, p->pFrom->zName);
+ ){
+ tdsqlite3ErrorMsg(pParse, "table %s may not be altered", pTab->zName);
+ return 1;
}
- return zWhere;
+ return 0;
}
-#endif
/*
-** Generate the text of a WHERE expression which can be used to select all
-** temporary triggers on table pTab from the sqlite_temp_master table. If
-** table pTab has no temporary triggers, or is itself stored in the
-** temporary database, NULL is returned.
+** Generate code to verify that the schemas of database zDb and, if
+** bTemp is not true, database "temp", can still be parsed. This is
+** called at the end of the generation of an ALTER TABLE ... RENAME ...
+** statement to ensure that the operation has not rendered any schema
+** objects unusable.
*/
-static char *whereTempTriggers(Parse *pParse, Table *pTab){
- Trigger *pTrig;
- char *zWhere = 0;
- const Schema *pTempSchema = pParse->db->aDb[1].pSchema; /* Temp db schema */
+static void renameTestSchema(Parse *pParse, const char *zDb, int bTemp){
+ tdsqlite3NestedParse(pParse,
+ "SELECT 1 "
+ "FROM \"%w\".%s "
+ "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'"
+ " AND sql NOT LIKE 'create virtual%%'"
+ " AND sqlite_rename_test(%Q, sql, type, name, %d)=NULL ",
+ zDb, MASTER_NAME,
+ zDb, bTemp
+ );
- /* If the table is not located in the temp-db (in which case NULL is
- ** returned, loop through the tables list of triggers. For each trigger
- ** that is not part of the temp-db schema, add a clause to the WHERE
- ** expression being built up in zWhere.
- */
- if( pTab->pSchema!=pTempSchema ){
- sqlite3 *db = pParse->db;
- for(pTrig=sqlite3TriggerList(pParse, pTab); pTrig; pTrig=pTrig->pNext){
- if( pTrig->pSchema==pTempSchema ){
- zWhere = whereOrName(db, zWhere, pTrig->zName);
- }
- }
- }
- if( zWhere ){
- char *zNew = sqlite3MPrintf(pParse->db, "type='trigger' AND (%s)", zWhere);
- sqlite3DbFree(pParse->db, zWhere);
- zWhere = zNew;
+ if( bTemp==0 ){
+ tdsqlite3NestedParse(pParse,
+ "SELECT 1 "
+ "FROM temp.%s "
+ "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'"
+ " AND sql NOT LIKE 'create virtual%%'"
+ " AND sqlite_rename_test(%Q, sql, type, name, 1)=NULL ",
+ MASTER_NAME, zDb
+ );
}
- return zWhere;
}
/*
-** Generate code to drop and reload the internal representation of table
-** pTab from the database, including triggers and temporary triggers.
-** Argument zName is the name of the table in the database schema at
-** the time the generated code is executed. This can be different from
-** pTab->zName if this function is being called to code part of an
-** "ALTER TABLE RENAME TO" statement.
+** Generate code to reload the schema for database iDb. And, if iDb!=1, for
+** the temp database as well.
*/
-static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){
- Vdbe *v;
- char *zWhere;
- int iDb; /* Index of database containing pTab */
-#ifndef SQLITE_OMIT_TRIGGER
- Trigger *pTrig;
-#endif
-
- v = sqlite3GetVdbe(pParse);
- if( NEVER(v==0) ) return;
- assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
- iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
- assert( iDb>=0 );
-
-#ifndef SQLITE_OMIT_TRIGGER
- /* Drop any table triggers from the internal schema. */
- for(pTrig=sqlite3TriggerList(pParse, pTab); pTrig; pTrig=pTrig->pNext){
- int iTrigDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema);
- assert( iTrigDb==iDb || iTrigDb==1 );
- sqlite3VdbeAddOp4(v, OP_DropTrigger, iTrigDb, 0, 0, pTrig->zName, 0);
- }
-#endif
-
- /* Drop the table and index from the internal schema. */
- sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0);
-
- /* Reload the table, index and permanent trigger schemas. */
- zWhere = sqlite3MPrintf(pParse->db, "tbl_name=%Q", zName);
- if( !zWhere ) return;
- sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere);
-
-#ifndef SQLITE_OMIT_TRIGGER
- /* Now, if the table is not stored in the temp database, reload any temp
- ** triggers. Don't use IN(...) in case SQLITE_OMIT_SUBQUERY is defined.
- */
- if( (zWhere=whereTempTriggers(pParse, pTab))!=0 ){
- sqlite3VdbeAddParseSchemaOp(v, 1, zWhere);
- }
-#endif
-}
-
-/*
-** Parameter zName is the name of a table that is about to be altered
-** (either with ALTER TABLE ... RENAME TO or ALTER TABLE ... ADD COLUMN).
-** If the table is a system table, this function leaves an error message
-** in pParse->zErr (system tables may not be altered) and returns non-zero.
-**
-** Or, if zName is not a system table, zero is returned.
-*/
-static int isSystemTable(Parse *pParse, const char *zName){
- if( sqlite3Strlen30(zName)>6 && 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){
- sqlite3ErrorMsg(pParse, "table %s may not be altered", zName);
- return 1;
+static void renameReloadSchema(Parse *pParse, int iDb){
+ Vdbe *v = pParse->pVdbe;
+ if( v ){
+ tdsqlite3ChangeCookie(pParse, iDb);
+ tdsqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0);
+ if( iDb!=1 ) tdsqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0);
}
- return 0;
}
/*
** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy"
** command.
*/
-SQLITE_PRIVATE void sqlite3AlterRenameTable(
+SQLITE_PRIVATE void tdsqlite3AlterRenameTable(
Parse *pParse, /* Parser context. */
SrcList *pSrc, /* The table to rename. */
Token *pName /* The new table name. */
@@ -98229,36 +108338,33 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable(
char *zDb; /* Name of database iDb */
Table *pTab; /* Table being renamed */
char *zName = 0; /* NULL-terminated version of pName */
- sqlite3 *db = pParse->db; /* Database connection */
+ tdsqlite3 *db = pParse->db; /* Database connection */
int nTabName; /* Number of UTF-8 characters in zTabName */
const char *zTabName; /* Original name of the table */
Vdbe *v;
-#ifndef SQLITE_OMIT_TRIGGER
- char *zWhere = 0; /* Where clause to locate temp triggers */
-#endif
VTable *pVTab = 0; /* Non-zero if this is a v-tab with an xRename() */
- int savedDbFlags; /* Saved value of db->flags */
+ u32 savedDbFlags; /* Saved value of db->mDbFlags */
- savedDbFlags = db->flags;
+ savedDbFlags = db->mDbFlags;
if( NEVER(db->mallocFailed) ) goto exit_rename_table;
assert( pSrc->nSrc==1 );
- assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
+ assert( tdsqlite3BtreeHoldsAllMutexes(pParse->db) );
- pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]);
+ pTab = tdsqlite3LocateTableItem(pParse, 0, &pSrc->a[0]);
if( !pTab ) goto exit_rename_table;
- iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(pParse->db, pTab->pSchema);
zDb = db->aDb[iDb].zDbSName;
- db->flags |= SQLITE_PreferBuiltin;
+ db->mDbFlags |= DBFLAG_PreferBuiltin;
/* Get a NULL terminated version of the new table name. */
- zName = sqlite3NameFromToken(db, pName);
+ zName = tdsqlite3NameFromToken(db, pName);
if( !zName ) goto exit_rename_table;
/* Check that a table or index named 'zName' does not already exist
** in database iDb. If so, this is an error.
*/
- if( sqlite3FindTable(db, zName, zDb) || sqlite3FindIndex(db, zName, zDb) ){
- sqlite3ErrorMsg(pParse,
+ if( tdsqlite3FindTable(db, zName, zDb) || tdsqlite3FindIndex(db, zName, zDb) ){
+ tdsqlite3ErrorMsg(pParse,
"there is already another table or index with this name: %s", zName);
goto exit_rename_table;
}
@@ -98266,154 +108372,127 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable(
/* Make sure it is not a system table being altered, or a reserved name
** that the table is being renamed to.
*/
- if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){
+ if( SQLITE_OK!=isAlterableTable(pParse, pTab) ){
goto exit_rename_table;
}
- if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto
- exit_rename_table;
+ if( SQLITE_OK!=tdsqlite3CheckObjectName(pParse,zName,"table",zName) ){
+ goto exit_rename_table;
}
#ifndef SQLITE_OMIT_VIEW
if( pTab->pSelect ){
- sqlite3ErrorMsg(pParse, "view %s may not be altered", pTab->zName);
+ tdsqlite3ErrorMsg(pParse, "view %s may not be altered", pTab->zName);
goto exit_rename_table;
}
#endif
#ifndef SQLITE_OMIT_AUTHORIZATION
/* Invoke the authorization callback. */
- if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){
goto exit_rename_table;
}
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+ if( tdsqlite3ViewGetColumnNames(pParse, pTab) ){
goto exit_rename_table;
}
if( IsVirtual(pTab) ){
- pVTab = sqlite3GetVTable(db, pTab);
+ pVTab = tdsqlite3GetVTable(db, pTab);
if( pVTab->pVtab->pModule->xRename==0 ){
pVTab = 0;
}
}
#endif
- /* Begin a transaction for database iDb.
- ** Then modify the schema cookie (since the ALTER TABLE modifies the
- ** schema). Open a statement transaction if the table is a virtual
- ** table.
- */
- v = sqlite3GetVdbe(pParse);
+ /* Begin a transaction for database iDb. Then modify the schema cookie
+ ** (since the ALTER TABLE modifies the schema). Call tdsqlite3MayAbort(),
+ ** as the scalar functions (e.g. sqlite_rename_table()) invoked by the
+ ** nested SQL may raise an exception. */
+ v = tdsqlite3GetVdbe(pParse);
if( v==0 ){
goto exit_rename_table;
}
- sqlite3BeginWriteOperation(pParse, pVTab!=0, iDb);
- sqlite3ChangeCookie(pParse, iDb);
-
- /* If this is a virtual table, invoke the xRename() function if
- ** one is defined. The xRename() callback will modify the names
- ** of any resources used by the v-table implementation (including other
- ** SQLite tables) that are identified by the name of the virtual table.
- */
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( pVTab ){
- int i = ++pParse->nMem;
- sqlite3VdbeLoadString(v, i, zName);
- sqlite3VdbeAddOp4(v, OP_VRename, i, 0, 0,(const char*)pVTab, P4_VTAB);
- sqlite3MayAbort(pParse);
- }
-#endif
+ tdsqlite3MayAbort(pParse);
/* figure out how many UTF-8 characters are in zName */
zTabName = pTab->zName;
- nTabName = sqlite3Utf8CharLen(zTabName, -1);
-
-#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
- if( db->flags&SQLITE_ForeignKeys ){
- /* If foreign-key support is enabled, rewrite the CREATE TABLE
- ** statements corresponding to all child tables of foreign key constraints
- ** for which the renamed table is the parent table. */
- if( (zWhere=whereForeignKeys(pParse, pTab))!=0 ){
- sqlite3NestedParse(pParse,
- "UPDATE \"%w\".%s SET "
- "sql = sqlite_rename_parent(sql, %Q, %Q) "
- "WHERE %s;", zDb, SCHEMA_TABLE(iDb), zTabName, zName, zWhere);
- sqlite3DbFree(db, zWhere);
- }
- }
-#endif
+ nTabName = tdsqlite3Utf8CharLen(zTabName, -1);
+
+ /* Rewrite all CREATE TABLE, INDEX, TRIGGER or VIEW statements in
+ ** the schema to use the new table name. */
+ tdsqlite3NestedParse(pParse,
+ "UPDATE \"%w\".%s SET "
+ "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, %d) "
+ "WHERE (type!='index' OR tbl_name=%Q COLLATE nocase)"
+ "AND name NOT LIKE 'sqliteX_%%' ESCAPE 'X'"
+ , zDb, MASTER_NAME, zDb, zTabName, zName, (iDb==1), zTabName
+ );
- /* Modify the sqlite_master table to use the new table name. */
- sqlite3NestedParse(pParse,
+ /* Update the tbl_name and name columns of the sqlite_master table
+ ** as required. */
+ tdsqlite3NestedParse(pParse,
"UPDATE %Q.%s SET "
-#ifdef SQLITE_OMIT_TRIGGER
- "sql = sqlite_rename_table(sql, %Q), "
-#else
- "sql = CASE "
- "WHEN type = 'trigger' THEN sqlite_rename_trigger(sql, %Q)"
- "ELSE sqlite_rename_table(sql, %Q) END, "
-#endif
"tbl_name = %Q, "
"name = CASE "
"WHEN type='table' THEN %Q "
- "WHEN name LIKE 'sqlite_autoindex%%' AND type='index' THEN "
+ "WHEN name LIKE 'sqliteX_autoindex%%' ESCAPE 'X' "
+ " AND type='index' THEN "
"'sqlite_autoindex_' || %Q || substr(name,%d+18) "
"ELSE name END "
"WHERE tbl_name=%Q COLLATE nocase AND "
"(type='table' OR type='index' OR type='trigger');",
- zDb, SCHEMA_TABLE(iDb), zName, zName, zName,
-#ifndef SQLITE_OMIT_TRIGGER
- zName,
-#endif
- zName, nTabName, zTabName
+ zDb, MASTER_NAME,
+ zName, zName, zName,
+ nTabName, zTabName
);
#ifndef SQLITE_OMIT_AUTOINCREMENT
/* If the sqlite_sequence table exists in this database, then update
** it with the new table name.
*/
- if( sqlite3FindTable(db, "sqlite_sequence", zDb) ){
- sqlite3NestedParse(pParse,
+ if( tdsqlite3FindTable(db, "sqlite_sequence", zDb) ){
+ tdsqlite3NestedParse(pParse,
"UPDATE \"%w\".sqlite_sequence set name = %Q WHERE name = %Q",
zDb, zName, pTab->zName);
}
#endif
-#ifndef SQLITE_OMIT_TRIGGER
- /* If there are TEMP triggers on this table, modify the sqlite_temp_master
- ** table. Don't do this if the table being ALTERed is itself located in
- ** the temp database.
- */
- if( (zWhere=whereTempTriggers(pParse, pTab))!=0 ){
- sqlite3NestedParse(pParse,
+ /* If the table being renamed is not itself part of the temp database,
+ ** edit view and trigger definitions within the temp database
+ ** as required. */
+ if( iDb!=1 ){
+ tdsqlite3NestedParse(pParse,
"UPDATE sqlite_temp_master SET "
- "sql = sqlite_rename_trigger(sql, %Q), "
- "tbl_name = %Q "
- "WHERE %s;", zName, zName, zWhere);
- sqlite3DbFree(db, zWhere);
+ "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, 1), "
+ "tbl_name = "
+ "CASE WHEN tbl_name=%Q COLLATE nocase AND "
+ " sqlite_rename_test(%Q, sql, type, name, 1) "
+ "THEN %Q ELSE tbl_name END "
+ "WHERE type IN ('view', 'trigger')"
+ , zDb, zTabName, zName, zTabName, zDb, zName);
}
-#endif
-#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
- if( db->flags&SQLITE_ForeignKeys ){
- FKey *p;
- for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
- Table *pFrom = p->pFrom;
- if( pFrom!=pTab ){
- reloadTableSchema(pParse, p->pFrom, pFrom->zName);
- }
- }
+ /* If this is a virtual table, invoke the xRename() function if
+ ** one is defined. The xRename() callback will modify the names
+ ** of any resources used by the v-table implementation (including other
+ ** SQLite tables) that are identified by the name of the virtual table.
+ */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( pVTab ){
+ int i = ++pParse->nMem;
+ tdsqlite3VdbeLoadString(v, i, zName);
+ tdsqlite3VdbeAddOp4(v, OP_VRename, i, 0, 0,(const char*)pVTab, P4_VTAB);
}
#endif
- /* Drop and reload the internal table schema. */
- reloadTableSchema(pParse, pTab, zName);
+ renameReloadSchema(pParse, iDb);
+ renameTestSchema(pParse, zDb, iDb==1);
exit_rename_table:
- sqlite3SrcListDelete(db, pSrc);
- sqlite3DbFree(db, zName);
- db->flags = savedDbFlags;
+ tdsqlite3SrcListDelete(db, pSrc);
+ tdsqlite3DbFree(db, zName);
+ db->mDbFlags = savedDbFlags;
}
/*
@@ -98424,7 +108503,7 @@ exit_rename_table:
** The Table structure pParse->pNewTable was extended to include
** the new column during parsing.
*/
-SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){
+SQLITE_PRIVATE void tdsqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){
Table *pNew; /* Copy of pParse->pNewTable */
Table *pTab; /* Table being altered */
int iDb; /* Database number */
@@ -98433,118 +108512,126 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){
char *zCol; /* Null-terminated column definition */
Column *pCol; /* The new column */
Expr *pDflt; /* Default value for the new column */
- sqlite3 *db; /* The database connection; */
- Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */
+ tdsqlite3 *db; /* The database connection; */
+ Vdbe *v; /* The prepared statement under construction */
int r1; /* Temporary registers */
db = pParse->db;
if( pParse->nErr || db->mallocFailed ) return;
- assert( v!=0 );
pNew = pParse->pNewTable;
assert( pNew );
- assert( sqlite3BtreeHoldsAllMutexes(db) );
- iDb = sqlite3SchemaToIndex(db, pNew->pSchema);
+ assert( tdsqlite3BtreeHoldsAllMutexes(db) );
+ iDb = tdsqlite3SchemaToIndex(db, pNew->pSchema);
zDb = db->aDb[iDb].zDbSName;
zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */
pCol = &pNew->aCol[pNew->nCol-1];
pDflt = pCol->pDflt;
- pTab = sqlite3FindTable(db, zTab, zDb);
+ pTab = tdsqlite3FindTable(db, zTab, zDb);
assert( pTab );
#ifndef SQLITE_OMIT_AUTHORIZATION
/* Invoke the authorization callback. */
- if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){
return;
}
#endif
- /* If the default value for the new column was specified with a
- ** literal NULL, then set pDflt to 0. This simplifies checking
- ** for an SQL NULL default below.
- */
- assert( pDflt==0 || pDflt->op==TK_SPAN );
- if( pDflt && pDflt->pLeft->op==TK_NULL ){
- pDflt = 0;
- }
/* Check that the new column is not specified as PRIMARY KEY or UNIQUE.
** If there is a NOT NULL constraint, then the default value for the
** column must not be NULL.
*/
if( pCol->colFlags & COLFLAG_PRIMKEY ){
- sqlite3ErrorMsg(pParse, "Cannot add a PRIMARY KEY column");
+ tdsqlite3ErrorMsg(pParse, "Cannot add a PRIMARY KEY column");
return;
}
if( pNew->pIndex ){
- sqlite3ErrorMsg(pParse, "Cannot add a UNIQUE column");
+ tdsqlite3ErrorMsg(pParse, "Cannot add a UNIQUE column");
return;
}
- if( (db->flags&SQLITE_ForeignKeys) && pNew->pFKey && pDflt ){
- sqlite3ErrorMsg(pParse,
- "Cannot add a REFERENCES column with non-NULL default value");
- return;
- }
- if( pCol->notNull && !pDflt ){
- sqlite3ErrorMsg(pParse,
- "Cannot add a NOT NULL column with default value NULL");
- return;
- }
-
- /* Ensure the default expression is something that sqlite3ValueFromExpr()
- ** can handle (i.e. not CURRENT_TIME etc.)
- */
- if( pDflt ){
- sqlite3_value *pVal = 0;
- int rc;
- rc = sqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_BLOB, &pVal);
- assert( rc==SQLITE_OK || rc==SQLITE_NOMEM );
- if( rc!=SQLITE_OK ){
- assert( db->mallocFailed == 1 );
+ if( (pCol->colFlags & COLFLAG_GENERATED)==0 ){
+ /* If the default value for the new column was specified with a
+ ** literal NULL, then set pDflt to 0. This simplifies checking
+ ** for an SQL NULL default below.
+ */
+ assert( pDflt==0 || pDflt->op==TK_SPAN );
+ if( pDflt && pDflt->pLeft->op==TK_NULL ){
+ pDflt = 0;
+ }
+ if( (db->flags&SQLITE_ForeignKeys) && pNew->pFKey && pDflt ){
+ tdsqlite3ErrorMsg(pParse,
+ "Cannot add a REFERENCES column with non-NULL default value");
return;
}
- if( !pVal ){
- sqlite3ErrorMsg(pParse, "Cannot add a column with non-constant default");
+ if( pCol->notNull && !pDflt ){
+ tdsqlite3ErrorMsg(pParse,
+ "Cannot add a NOT NULL column with default value NULL");
return;
}
- sqlite3ValueFree(pVal);
+
+ /* Ensure the default expression is something that tdsqlite3ValueFromExpr()
+ ** can handle (i.e. not CURRENT_TIME etc.)
+ */
+ if( pDflt ){
+ tdsqlite3_value *pVal = 0;
+ int rc;
+ rc = tdsqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_BLOB, &pVal);
+ assert( rc==SQLITE_OK || rc==SQLITE_NOMEM );
+ if( rc!=SQLITE_OK ){
+ assert( db->mallocFailed == 1 );
+ return;
+ }
+ if( !pVal ){
+ tdsqlite3ErrorMsg(pParse,"Cannot add a column with non-constant default");
+ return;
+ }
+ tdsqlite3ValueFree(pVal);
+ }
+ }else if( pCol->colFlags & COLFLAG_STORED ){
+ tdsqlite3ErrorMsg(pParse, "cannot add a STORED column");
+ return;
}
+
/* Modify the CREATE TABLE statement. */
- zCol = sqlite3DbStrNDup(db, (char*)pColDef->z, pColDef->n);
+ zCol = tdsqlite3DbStrNDup(db, (char*)pColDef->z, pColDef->n);
if( zCol ){
char *zEnd = &zCol[pColDef->n-1];
- int savedDbFlags = db->flags;
- while( zEnd>zCol && (*zEnd==';' || sqlite3Isspace(*zEnd)) ){
+ u32 savedDbFlags = db->mDbFlags;
+ while( zEnd>zCol && (*zEnd==';' || tdsqlite3Isspace(*zEnd)) ){
*zEnd-- = '\0';
}
- db->flags |= SQLITE_PreferBuiltin;
- sqlite3NestedParse(pParse,
+ db->mDbFlags |= DBFLAG_PreferBuiltin;
+ tdsqlite3NestedParse(pParse,
"UPDATE \"%w\".%s SET "
"sql = substr(sql,1,%d) || ', ' || %Q || substr(sql,%d) "
"WHERE type = 'table' AND name = %Q",
- zDb, SCHEMA_TABLE(iDb), pNew->addColOffset, zCol, pNew->addColOffset+1,
+ zDb, MASTER_NAME, pNew->addColOffset, zCol, pNew->addColOffset+1,
zTab
);
- sqlite3DbFree(db, zCol);
- db->flags = savedDbFlags;
+ tdsqlite3DbFree(db, zCol);
+ db->mDbFlags = savedDbFlags;
}
/* Make sure the schema version is at least 3. But do not upgrade
** from less than 3 to 4, as that will corrupt any preexisting DESC
** index.
*/
- r1 = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, BTREE_FILE_FORMAT);
- sqlite3VdbeUsesBtree(v, iDb);
- sqlite3VdbeAddOp2(v, OP_AddImm, r1, -2);
- sqlite3VdbeAddOp2(v, OP_IfPos, r1, sqlite3VdbeCurrentAddr(v)+2);
- VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, 3);
- sqlite3ReleaseTempReg(pParse, r1);
+ v = tdsqlite3GetVdbe(pParse);
+ if( v ){
+ r1 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, BTREE_FILE_FORMAT);
+ tdsqlite3VdbeUsesBtree(v, iDb);
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, r1, -2);
+ tdsqlite3VdbeAddOp2(v, OP_IfPos, r1, tdsqlite3VdbeCurrentAddr(v)+2);
+ VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, 3);
+ tdsqlite3ReleaseTempReg(pParse, r1);
+ }
- /* Reload the schema of the modified table. */
- reloadTableSchema(pParse, pTab, pTab->zName);
+ /* Reload the table definition */
+ renameReloadSchema(pParse, iDb);
}
/*
@@ -98555,65 +108642,65 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){
** This routine makes a (partial) copy of the Table structure
** for the table being altered and sets Parse.pNewTable to point
** to it. Routines called by the parser as the column definition
-** is parsed (i.e. sqlite3AddColumn()) add the new Column data to
+** is parsed (i.e. tdsqlite3AddColumn()) add the new Column data to
** the copy. The copy of the Table structure is deleted by tokenize.c
** after parsing is finished.
**
-** Routine sqlite3AlterFinishAddColumn() will be called to complete
+** Routine tdsqlite3AlterFinishAddColumn() will be called to complete
** coding the "ALTER TABLE ... ADD" statement.
*/
-SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){
+SQLITE_PRIVATE void tdsqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){
Table *pNew;
Table *pTab;
- Vdbe *v;
int iDb;
int i;
int nAlloc;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
/* Look up the table being altered. */
assert( pParse->pNewTable==0 );
- assert( sqlite3BtreeHoldsAllMutexes(db) );
+ assert( tdsqlite3BtreeHoldsAllMutexes(db) );
if( db->mallocFailed ) goto exit_begin_add_column;
- pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]);
+ pTab = tdsqlite3LocateTableItem(pParse, 0, &pSrc->a[0]);
if( !pTab ) goto exit_begin_add_column;
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pTab) ){
- sqlite3ErrorMsg(pParse, "virtual tables may not be altered");
+ tdsqlite3ErrorMsg(pParse, "virtual tables may not be altered");
goto exit_begin_add_column;
}
#endif
/* Make sure this is not an attempt to ALTER a view. */
if( pTab->pSelect ){
- sqlite3ErrorMsg(pParse, "Cannot add a column to a view");
+ tdsqlite3ErrorMsg(pParse, "Cannot add a column to a view");
goto exit_begin_add_column;
}
- if( SQLITE_OK!=isSystemTable(pParse, pTab->zName) ){
+ if( SQLITE_OK!=isAlterableTable(pParse, pTab) ){
goto exit_begin_add_column;
}
+ tdsqlite3MayAbort(pParse);
assert( pTab->addColOffset>0 );
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
/* Put a copy of the Table struct in Parse.pNewTable for the
- ** sqlite3AddColumn() function and friends to modify. But modify
+ ** tdsqlite3AddColumn() function and friends to modify. But modify
** the name by adding an "sqlite_altertab_" prefix. By adding this
** prefix, we insure that the name will not collide with an existing
** table because user table are not allowed to have the "sqlite_"
** prefix on their name.
*/
- pNew = (Table*)sqlite3DbMallocZero(db, sizeof(Table));
+ pNew = (Table*)tdsqlite3DbMallocZero(db, sizeof(Table));
if( !pNew ) goto exit_begin_add_column;
pParse->pNewTable = pNew;
- pNew->nRef = 1;
+ pNew->nTabRef = 1;
pNew->nCol = pTab->nCol;
assert( pNew->nCol>0 );
nAlloc = (((pNew->nCol-1)/8)*8)+8;
assert( nAlloc>=pNew->nCol && nAlloc%8==0 && nAlloc-pNew->nCol<8 );
- pNew->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column)*nAlloc);
- pNew->zName = sqlite3MPrintf(db, "sqlite_altertab_%s", pTab->zName);
+ pNew->aCol = (Column*)tdsqlite3DbMallocZero(db, sizeof(Column)*nAlloc);
+ pNew->zName = tdsqlite3MPrintf(db, "sqlite_altertab_%s", pTab->zName);
if( !pNew->aCol || !pNew->zName ){
assert( db->mallocFailed );
goto exit_begin_add_column;
@@ -98621,24 +108708,1239 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){
memcpy(pNew->aCol, pTab->aCol, sizeof(Column)*pNew->nCol);
for(i=0; i<pNew->nCol; i++){
Column *pCol = &pNew->aCol[i];
- pCol->zName = sqlite3DbStrDup(db, pCol->zName);
+ pCol->zName = tdsqlite3DbStrDup(db, pCol->zName);
pCol->zColl = 0;
pCol->pDflt = 0;
}
pNew->pSchema = db->aDb[iDb].pSchema;
pNew->addColOffset = pTab->addColOffset;
- pNew->nRef = 1;
-
- /* Begin a transaction and increment the schema cookie. */
- sqlite3BeginWriteOperation(pParse, 0, iDb);
- v = sqlite3GetVdbe(pParse);
- if( !v ) goto exit_begin_add_column;
- sqlite3ChangeCookie(pParse, iDb);
+ pNew->nTabRef = 1;
exit_begin_add_column:
- sqlite3SrcListDelete(db, pSrc);
+ tdsqlite3SrcListDelete(db, pSrc);
+ return;
+}
+
+/*
+** Parameter pTab is the subject of an ALTER TABLE ... RENAME COLUMN
+** command. This function checks if the table is a view or virtual
+** table (columns of views or virtual tables may not be renamed). If so,
+** it loads an error message into pParse and returns non-zero.
+**
+** Or, if pTab is not a view or virtual table, zero is returned.
+*/
+#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE)
+static int isRealTable(Parse *pParse, Table *pTab){
+ const char *zType = 0;
+#ifndef SQLITE_OMIT_VIEW
+ if( pTab->pSelect ){
+ zType = "view";
+ }
+#endif
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( IsVirtual(pTab) ){
+ zType = "virtual table";
+ }
+#endif
+ if( zType ){
+ tdsqlite3ErrorMsg(
+ pParse, "cannot rename columns of %s \"%s\"", zType, pTab->zName
+ );
+ return 1;
+ }
+ return 0;
+}
+#else /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */
+# define isRealTable(x,y) (0)
+#endif
+
+/*
+** Handles the following parser reduction:
+**
+** cmd ::= ALTER TABLE pSrc RENAME COLUMN pOld TO pNew
+*/
+SQLITE_PRIVATE void tdsqlite3AlterRenameColumn(
+ Parse *pParse, /* Parsing context */
+ SrcList *pSrc, /* Table being altered. pSrc->nSrc==1 */
+ Token *pOld, /* Name of column being changed */
+ Token *pNew /* New column name */
+){
+ tdsqlite3 *db = pParse->db; /* Database connection */
+ Table *pTab; /* Table being updated */
+ int iCol; /* Index of column being renamed */
+ char *zOld = 0; /* Old column name */
+ char *zNew = 0; /* New column name */
+ const char *zDb; /* Name of schema containing the table */
+ int iSchema; /* Index of the schema */
+ int bQuote; /* True to quote the new name */
+
+ /* Locate the table to be altered */
+ pTab = tdsqlite3LocateTableItem(pParse, 0, &pSrc->a[0]);
+ if( !pTab ) goto exit_rename_column;
+
+ /* Cannot alter a system table */
+ if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_rename_column;
+ if( SQLITE_OK!=isRealTable(pParse, pTab) ) goto exit_rename_column;
+
+ /* Which schema holds the table to be altered */
+ iSchema = tdsqlite3SchemaToIndex(db, pTab->pSchema);
+ assert( iSchema>=0 );
+ zDb = db->aDb[iSchema].zDbSName;
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ /* Invoke the authorization callback. */
+ if( tdsqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, 0) ){
+ goto exit_rename_column;
+ }
+#endif
+
+ /* Make sure the old name really is a column name in the table to be
+ ** altered. Set iCol to be the index of the column being renamed */
+ zOld = tdsqlite3NameFromToken(db, pOld);
+ if( !zOld ) goto exit_rename_column;
+ for(iCol=0; iCol<pTab->nCol; iCol++){
+ if( 0==tdsqlite3StrICmp(pTab->aCol[iCol].zName, zOld) ) break;
+ }
+ if( iCol==pTab->nCol ){
+ tdsqlite3ErrorMsg(pParse, "no such column: \"%s\"", zOld);
+ goto exit_rename_column;
+ }
+
+ /* Do the rename operation using a recursive UPDATE statement that
+ ** uses the sqlite_rename_column() SQL function to compute the new
+ ** CREATE statement text for the sqlite_master table.
+ */
+ tdsqlite3MayAbort(pParse);
+ zNew = tdsqlite3NameFromToken(db, pNew);
+ if( !zNew ) goto exit_rename_column;
+ assert( pNew->n>0 );
+ bQuote = tdsqlite3Isquote(pNew->z[0]);
+ tdsqlite3NestedParse(pParse,
+ "UPDATE \"%w\".%s SET "
+ "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) "
+ "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' "
+ " AND (type != 'index' OR tbl_name = %Q)"
+ " AND sql NOT LIKE 'create virtual%%'",
+ zDb, MASTER_NAME,
+ zDb, pTab->zName, iCol, zNew, bQuote, iSchema==1,
+ pTab->zName
+ );
+
+ tdsqlite3NestedParse(pParse,
+ "UPDATE temp.%s SET "
+ "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, 1) "
+ "WHERE type IN ('trigger', 'view')",
+ MASTER_NAME,
+ zDb, pTab->zName, iCol, zNew, bQuote
+ );
+
+ /* Drop and reload the database schema. */
+ renameReloadSchema(pParse, iSchema);
+ renameTestSchema(pParse, zDb, iSchema==1);
+
+ exit_rename_column:
+ tdsqlite3SrcListDelete(db, pSrc);
+ tdsqlite3DbFree(db, zOld);
+ tdsqlite3DbFree(db, zNew);
+ return;
+}
+
+/*
+** Each RenameToken object maps an element of the parse tree into
+** the token that generated that element. The parse tree element
+** might be one of:
+**
+** * A pointer to an Expr that represents an ID
+** * The name of a table column in Column.zName
+**
+** A list of RenameToken objects can be constructed during parsing.
+** Each new object is created by tdsqlite3RenameTokenMap().
+** As the parse tree is transformed, the tdsqlite3RenameTokenRemap()
+** routine is used to keep the mapping current.
+**
+** After the parse finishes, renameTokenFind() routine can be used
+** to look up the actual token value that created some element in
+** the parse tree.
+*/
+struct RenameToken {
+ void *p; /* Parse tree element created by token t */
+ Token t; /* The token that created parse tree element p */
+ RenameToken *pNext; /* Next is a list of all RenameToken objects */
+};
+
+/*
+** The context of an ALTER TABLE RENAME COLUMN operation that gets passed
+** down into the Walker.
+*/
+typedef struct RenameCtx RenameCtx;
+struct RenameCtx {
+ RenameToken *pList; /* List of tokens to overwrite */
+ int nList; /* Number of tokens in pList */
+ int iCol; /* Index of column being renamed */
+ Table *pTab; /* Table being ALTERed */
+ const char *zOld; /* Old column name */
+};
+
+#ifdef SQLITE_DEBUG
+/*
+** This function is only for debugging. It performs two tasks:
+**
+** 1. Checks that pointer pPtr does not already appear in the
+** rename-token list.
+**
+** 2. Dereferences each pointer in the rename-token list.
+**
+** The second is most effective when debugging under valgrind or
+** address-sanitizer or similar. If any of these pointers no longer
+** point to valid objects, an exception is raised by the memory-checking
+** tool.
+**
+** The point of this is to prevent comparisons of invalid pointer values.
+** Even though this always seems to work, it is undefined according to the
+** C standard. Example of undefined comparison:
+**
+** tdsqlite3_free(x);
+** if( x==y ) ...
+**
+** Technically, as x no longer points into a valid object or to the byte
+** following a valid object, it may not be used in comparison operations.
+*/
+static void renameTokenCheckAll(Parse *pParse, void *pPtr){
+ if( pParse->nErr==0 && pParse->db->mallocFailed==0 ){
+ RenameToken *p;
+ u8 i = 0;
+ for(p=pParse->pRename; p; p=p->pNext){
+ if( p->p ){
+ assert( p->p!=pPtr );
+ i += *(u8*)(p->p);
+ }
+ }
+ }
+}
+#else
+# define renameTokenCheckAll(x,y)
+#endif
+
+/*
+** Remember that the parser tree element pPtr was created using
+** the token pToken.
+**
+** In other words, construct a new RenameToken object and add it
+** to the list of RenameToken objects currently being built up
+** in pParse->pRename.
+**
+** The pPtr argument is returned so that this routine can be used
+** with tail recursion in tokenExpr() routine, for a small performance
+** improvement.
+*/
+SQLITE_PRIVATE void *tdsqlite3RenameTokenMap(Parse *pParse, void *pPtr, Token *pToken){
+ RenameToken *pNew;
+ assert( pPtr || pParse->db->mallocFailed );
+ renameTokenCheckAll(pParse, pPtr);
+ if( pParse->eParseMode!=PARSE_MODE_UNMAP ){
+ pNew = tdsqlite3DbMallocZero(pParse->db, sizeof(RenameToken));
+ if( pNew ){
+ pNew->p = pPtr;
+ pNew->t = *pToken;
+ pNew->pNext = pParse->pRename;
+ pParse->pRename = pNew;
+ }
+ }
+
+ return pPtr;
+}
+
+/*
+** It is assumed that there is already a RenameToken object associated
+** with parse tree element pFrom. This function remaps the associated token
+** to parse tree element pTo.
+*/
+SQLITE_PRIVATE void tdsqlite3RenameTokenRemap(Parse *pParse, void *pTo, void *pFrom){
+ RenameToken *p;
+ renameTokenCheckAll(pParse, pTo);
+ for(p=pParse->pRename; p; p=p->pNext){
+ if( p->p==pFrom ){
+ p->p = pTo;
+ break;
+ }
+ }
+}
+
+/*
+** Walker callback used by tdsqlite3RenameExprUnmap().
+*/
+static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){
+ Parse *pParse = pWalker->pParse;
+ tdsqlite3RenameTokenRemap(pParse, 0, (void*)pExpr);
+ return WRC_Continue;
+}
+
+/*
+** Iterate through the Select objects that are part of WITH clauses attached
+** to select statement pSelect.
+*/
+static void renameWalkWith(Walker *pWalker, Select *pSelect){
+ With *pWith = pSelect->pWith;
+ if( pWith ){
+ int i;
+ for(i=0; i<pWith->nCte; i++){
+ Select *p = pWith->a[i].pSelect;
+ NameContext sNC;
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pWalker->pParse;
+ tdsqlite3SelectPrep(sNC.pParse, p, &sNC);
+ tdsqlite3WalkSelect(pWalker, p);
+ tdsqlite3RenameExprlistUnmap(pWalker->pParse, pWith->a[i].pCols);
+ }
+ }
+}
+
+/*
+** Walker callback used by tdsqlite3RenameExprUnmap().
+*/
+static int renameUnmapSelectCb(Walker *pWalker, Select *p){
+ Parse *pParse = pWalker->pParse;
+ int i;
+ if( pParse->nErr ) return WRC_Abort;
+ if( NEVER(p->selFlags & SF_View) ) return WRC_Prune;
+ if( ALWAYS(p->pEList) ){
+ ExprList *pList = p->pEList;
+ for(i=0; i<pList->nExpr; i++){
+ if( pList->a[i].zEName && pList->a[i].eEName==ENAME_NAME ){
+ tdsqlite3RenameTokenRemap(pParse, 0, (void*)pList->a[i].zEName);
+ }
+ }
+ }
+ if( ALWAYS(p->pSrc) ){ /* Every Select as a SrcList, even if it is empty */
+ SrcList *pSrc = p->pSrc;
+ for(i=0; i<pSrc->nSrc; i++){
+ tdsqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName);
+ if( tdsqlite3WalkExpr(pWalker, pSrc->a[i].pOn) ) return WRC_Abort;
+ }
+ }
+
+ renameWalkWith(pWalker, p);
+ return WRC_Continue;
+}
+
+/*
+** Remove all nodes that are part of expression pExpr from the rename list.
+*/
+SQLITE_PRIVATE void tdsqlite3RenameExprUnmap(Parse *pParse, Expr *pExpr){
+ u8 eMode = pParse->eParseMode;
+ Walker sWalker;
+ memset(&sWalker, 0, sizeof(Walker));
+ sWalker.pParse = pParse;
+ sWalker.xExprCallback = renameUnmapExprCb;
+ sWalker.xSelectCallback = renameUnmapSelectCb;
+ pParse->eParseMode = PARSE_MODE_UNMAP;
+ tdsqlite3WalkExpr(&sWalker, pExpr);
+ pParse->eParseMode = eMode;
+}
+
+/*
+** Remove all nodes that are part of expression-list pEList from the
+** rename list.
+*/
+SQLITE_PRIVATE void tdsqlite3RenameExprlistUnmap(Parse *pParse, ExprList *pEList){
+ if( pEList ){
+ int i;
+ Walker sWalker;
+ memset(&sWalker, 0, sizeof(Walker));
+ sWalker.pParse = pParse;
+ sWalker.xExprCallback = renameUnmapExprCb;
+ tdsqlite3WalkExprList(&sWalker, pEList);
+ for(i=0; i<pEList->nExpr; i++){
+ if( ALWAYS(pEList->a[i].eEName==ENAME_NAME) ){
+ tdsqlite3RenameTokenRemap(pParse, 0, (void*)pEList->a[i].zEName);
+ }
+ }
+ }
+}
+
+/*
+** Free the list of RenameToken objects given in the second argument
+*/
+static void renameTokenFree(tdsqlite3 *db, RenameToken *pToken){
+ RenameToken *pNext;
+ RenameToken *p;
+ for(p=pToken; p; p=pNext){
+ pNext = p->pNext;
+ tdsqlite3DbFree(db, p);
+ }
+}
+
+/*
+** Search the Parse object passed as the first argument for a RenameToken
+** object associated with parse tree element pPtr. If found, remove it
+** from the Parse object and add it to the list maintained by the
+** RenameCtx object passed as the second argument.
+*/
+static void renameTokenFind(Parse *pParse, struct RenameCtx *pCtx, void *pPtr){
+ RenameToken **pp;
+ assert( pPtr!=0 );
+ for(pp=&pParse->pRename; (*pp); pp=&(*pp)->pNext){
+ if( (*pp)->p==pPtr ){
+ RenameToken *pToken = *pp;
+ *pp = pToken->pNext;
+ pToken->pNext = pCtx->pList;
+ pCtx->pList = pToken;
+ pCtx->nList++;
+ break;
+ }
+ }
+}
+
+/*
+** This is a Walker select callback. It does nothing. It is only required
+** because without a dummy callback, tdsqlite3WalkExpr() and similar do not
+** descend into sub-select statements.
+*/
+static int renameColumnSelectCb(Walker *pWalker, Select *p){
+ if( p->selFlags & SF_View ) return WRC_Prune;
+ renameWalkWith(pWalker, p);
+ return WRC_Continue;
+}
+
+/*
+** This is a Walker expression callback.
+**
+** For every TK_COLUMN node in the expression tree, search to see
+** if the column being references is the column being renamed by an
+** ALTER TABLE statement. If it is, then attach its associated
+** RenameToken object to the list of RenameToken objects being
+** constructed in RenameCtx object at pWalker->u.pRename.
+*/
+static int renameColumnExprCb(Walker *pWalker, Expr *pExpr){
+ RenameCtx *p = pWalker->u.pRename;
+ if( pExpr->op==TK_TRIGGER
+ && pExpr->iColumn==p->iCol
+ && pWalker->pParse->pTriggerTab==p->pTab
+ ){
+ renameTokenFind(pWalker->pParse, p, (void*)pExpr);
+ }else if( pExpr->op==TK_COLUMN
+ && pExpr->iColumn==p->iCol
+ && p->pTab==pExpr->y.pTab
+ ){
+ renameTokenFind(pWalker->pParse, p, (void*)pExpr);
+ }
+ return WRC_Continue;
+}
+
+/*
+** The RenameCtx contains a list of tokens that reference a column that
+** is being renamed by an ALTER TABLE statement. Return the "last"
+** RenameToken in the RenameCtx and remove that RenameToken from the
+** RenameContext. "Last" means the last RenameToken encountered when
+** the input SQL is parsed from left to right. Repeated calls to this routine
+** return all column name tokens in the order that they are encountered
+** in the SQL statement.
+*/
+static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){
+ RenameToken *pBest = pCtx->pList;
+ RenameToken *pToken;
+ RenameToken **pp;
+
+ for(pToken=pBest->pNext; pToken; pToken=pToken->pNext){
+ if( pToken->t.z>pBest->t.z ) pBest = pToken;
+ }
+ for(pp=&pCtx->pList; *pp!=pBest; pp=&(*pp)->pNext);
+ *pp = pBest->pNext;
+
+ return pBest;
+}
+
+/*
+** An error occured while parsing or otherwise processing a database
+** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an
+** ALTER TABLE RENAME COLUMN program. The error message emitted by the
+** sub-routine is currently stored in pParse->zErrMsg. This function
+** adds context to the error message and then stores it in pCtx.
+*/
+static void renameColumnParseError(
+ tdsqlite3_context *pCtx,
+ int bPost,
+ tdsqlite3_value *pType,
+ tdsqlite3_value *pObject,
+ Parse *pParse
+){
+ const char *zT = (const char*)tdsqlite3_value_text(pType);
+ const char *zN = (const char*)tdsqlite3_value_text(pObject);
+ char *zErr;
+
+ zErr = tdsqlite3_mprintf("error in %s %s%s: %s",
+ zT, zN, (bPost ? " after rename" : ""),
+ pParse->zErrMsg
+ );
+ tdsqlite3_result_error(pCtx, zErr, -1);
+ tdsqlite3_free(zErr);
+}
+
+/*
+** For each name in the the expression-list pEList (i.e. each
+** pEList->a[i].zName) that matches the string in zOld, extract the
+** corresponding rename-token from Parse object pParse and add it
+** to the RenameCtx pCtx.
+*/
+static void renameColumnElistNames(
+ Parse *pParse,
+ RenameCtx *pCtx,
+ ExprList *pEList,
+ const char *zOld
+){
+ if( pEList ){
+ int i;
+ for(i=0; i<pEList->nExpr; i++){
+ char *zName = pEList->a[i].zEName;
+ if( ALWAYS(pEList->a[i].eEName==ENAME_NAME)
+ && ALWAYS(zName!=0)
+ && 0==tdsqlite3_stricmp(zName, zOld)
+ ){
+ renameTokenFind(pParse, pCtx, (void*)zName);
+ }
+ }
+ }
+}
+
+/*
+** For each name in the the id-list pIdList (i.e. each pIdList->a[i].zName)
+** that matches the string in zOld, extract the corresponding rename-token
+** from Parse object pParse and add it to the RenameCtx pCtx.
+*/
+static void renameColumnIdlistNames(
+ Parse *pParse,
+ RenameCtx *pCtx,
+ IdList *pIdList,
+ const char *zOld
+){
+ if( pIdList ){
+ int i;
+ for(i=0; i<pIdList->nId; i++){
+ char *zName = pIdList->a[i].zName;
+ if( 0==tdsqlite3_stricmp(zName, zOld) ){
+ renameTokenFind(pParse, pCtx, (void*)zName);
+ }
+ }
+ }
+}
+
+/*
+** Parse the SQL statement zSql using Parse object (*p). The Parse object
+** is initialized by this function before it is used.
+*/
+static int renameParseSql(
+ Parse *p, /* Memory to use for Parse object */
+ const char *zDb, /* Name of schema SQL belongs to */
+ tdsqlite3 *db, /* Database handle */
+ const char *zSql, /* SQL to parse */
+ int bTemp /* True if SQL is from temp schema */
+){
+ int rc;
+ char *zErr = 0;
+
+ db->init.iDb = bTemp ? 1 : tdsqlite3FindDbName(db, zDb);
+
+ /* Parse the SQL statement passed as the first argument. If no error
+ ** occurs and the parse does not result in a new table, index or
+ ** trigger object, the database must be corrupt. */
+ memset(p, 0, sizeof(Parse));
+ p->eParseMode = PARSE_MODE_RENAME;
+ p->db = db;
+ p->nQueryLoop = 1;
+ rc = tdsqlite3RunParser(p, zSql, &zErr);
+ assert( p->zErrMsg==0 );
+ assert( rc!=SQLITE_OK || zErr==0 );
+ p->zErrMsg = zErr;
+ if( db->mallocFailed ) rc = SQLITE_NOMEM;
+ if( rc==SQLITE_OK
+ && p->pNewTable==0 && p->pNewIndex==0 && p->pNewTrigger==0
+ ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }
+
+#ifdef SQLITE_DEBUG
+ /* Ensure that all mappings in the Parse.pRename list really do map to
+ ** a part of the input string. */
+ if( rc==SQLITE_OK ){
+ int nSql = tdsqlite3Strlen30(zSql);
+ RenameToken *pToken;
+ for(pToken=p->pRename; pToken; pToken=pToken->pNext){
+ assert( pToken->t.z>=zSql && &pToken->t.z[pToken->t.n]<=&zSql[nSql] );
+ }
+ }
+#endif
+
+ db->init.iDb = 0;
+ return rc;
+}
+
+/*
+** This function edits SQL statement zSql, replacing each token identified
+** by the linked list pRename with the text of zNew. If argument bQuote is
+** true, then zNew is always quoted first. If no error occurs, the result
+** is loaded into context object pCtx as the result.
+**
+** Or, if an error occurs (i.e. an OOM condition), an error is left in
+** pCtx and an SQLite error code returned.
+*/
+static int renameEditSql(
+ tdsqlite3_context *pCtx, /* Return result here */
+ RenameCtx *pRename, /* Rename context */
+ const char *zSql, /* SQL statement to edit */
+ const char *zNew, /* New token text */
+ int bQuote /* True to always quote token */
+){
+ int nNew = tdsqlite3Strlen30(zNew);
+ int nSql = tdsqlite3Strlen30(zSql);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(pCtx);
+ int rc = SQLITE_OK;
+ char *zQuot;
+ char *zOut;
+ int nQuot;
+
+ /* Set zQuot to point to a buffer containing a quoted copy of the
+ ** identifier zNew. If the corresponding identifier in the original
+ ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to
+ ** point to zQuot so that all substitutions are made using the
+ ** quoted version of the new column name. */
+ zQuot = tdsqlite3MPrintf(db, "\"%w\"", zNew);
+ if( zQuot==0 ){
+ return SQLITE_NOMEM;
+ }else{
+ nQuot = tdsqlite3Strlen30(zQuot);
+ }
+ if( bQuote ){
+ zNew = zQuot;
+ nNew = nQuot;
+ }
+
+ /* At this point pRename->pList contains a list of RenameToken objects
+ ** corresponding to all tokens in the input SQL that must be replaced
+ ** with the new column name. All that remains is to construct and
+ ** return the edited SQL string. */
+ assert( nQuot>=nNew );
+ zOut = tdsqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1);
+ if( zOut ){
+ int nOut = nSql;
+ memcpy(zOut, zSql, nSql);
+ while( pRename->pList ){
+ int iOff; /* Offset of token to replace in zOut */
+ RenameToken *pBest = renameColumnTokenNext(pRename);
+
+ u32 nReplace;
+ const char *zReplace;
+ if( tdsqlite3IsIdChar(*pBest->t.z) ){
+ nReplace = nNew;
+ zReplace = zNew;
+ }else{
+ nReplace = nQuot;
+ zReplace = zQuot;
+ }
+
+ iOff = pBest->t.z - zSql;
+ if( pBest->t.n!=nReplace ){
+ memmove(&zOut[iOff + nReplace], &zOut[iOff + pBest->t.n],
+ nOut - (iOff + pBest->t.n)
+ );
+ nOut += nReplace - pBest->t.n;
+ zOut[nOut] = '\0';
+ }
+ memcpy(&zOut[iOff], zReplace, nReplace);
+ tdsqlite3DbFree(db, pBest);
+ }
+
+ tdsqlite3_result_text(pCtx, zOut, -1, SQLITE_TRANSIENT);
+ tdsqlite3DbFree(db, zOut);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+
+ tdsqlite3_free(zQuot);
+ return rc;
+}
+
+/*
+** Resolve all symbols in the trigger at pParse->pNewTrigger, assuming
+** it was read from the schema of database zDb. Return SQLITE_OK if
+** successful. Otherwise, return an SQLite error code and leave an error
+** message in the Parse object.
+*/
+static int renameResolveTrigger(Parse *pParse, const char *zDb){
+ tdsqlite3 *db = pParse->db;
+ Trigger *pNew = pParse->pNewTrigger;
+ TriggerStep *pStep;
+ NameContext sNC;
+ int rc = SQLITE_OK;
+
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+ assert( pNew->pTabSchema );
+ pParse->pTriggerTab = tdsqlite3FindTable(db, pNew->table,
+ db->aDb[tdsqlite3SchemaToIndex(db, pNew->pTabSchema)].zDbSName
+ );
+ pParse->eTriggerOp = pNew->op;
+ /* ALWAYS() because if the table of the trigger does not exist, the
+ ** error would have been hit before this point */
+ if( ALWAYS(pParse->pTriggerTab) ){
+ rc = tdsqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab);
+ }
+
+ /* Resolve symbols in WHEN clause */
+ if( rc==SQLITE_OK && pNew->pWhen ){
+ rc = tdsqlite3ResolveExprNames(&sNC, pNew->pWhen);
+ }
+
+ for(pStep=pNew->step_list; rc==SQLITE_OK && pStep; pStep=pStep->pNext){
+ if( pStep->pSelect ){
+ tdsqlite3SelectPrep(pParse, pStep->pSelect, &sNC);
+ if( pParse->nErr ) rc = pParse->rc;
+ }
+ if( rc==SQLITE_OK && pStep->zTarget ){
+ Table *pTarget = tdsqlite3LocateTable(pParse, 0, pStep->zTarget, zDb);
+ if( pTarget==0 ){
+ rc = SQLITE_ERROR;
+ }else if( SQLITE_OK==(rc = tdsqlite3ViewGetColumnNames(pParse, pTarget)) ){
+ SrcList sSrc;
+ memset(&sSrc, 0, sizeof(sSrc));
+ sSrc.nSrc = 1;
+ sSrc.a[0].zName = pStep->zTarget;
+ sSrc.a[0].pTab = pTarget;
+ sNC.pSrcList = &sSrc;
+ if( pStep->pWhere ){
+ rc = tdsqlite3ResolveExprNames(&sNC, pStep->pWhere);
+ }
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3ResolveExprListNames(&sNC, pStep->pExprList);
+ }
+ assert( !pStep->pUpsert || (!pStep->pWhere && !pStep->pExprList) );
+ if( pStep->pUpsert ){
+ Upsert *pUpsert = pStep->pUpsert;
+ assert( rc==SQLITE_OK );
+ pUpsert->pUpsertSrc = &sSrc;
+ sNC.uNC.pUpsert = pUpsert;
+ sNC.ncFlags = NC_UUpsert;
+ rc = tdsqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget);
+ if( rc==SQLITE_OK ){
+ ExprList *pUpsertSet = pUpsert->pUpsertSet;
+ rc = tdsqlite3ResolveExprListNames(&sNC, pUpsertSet);
+ }
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3ResolveExprNames(&sNC, pUpsert->pUpsertWhere);
+ }
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere);
+ }
+ sNC.ncFlags = 0;
+ }
+ sNC.pSrcList = 0;
+ }
+ }
+ }
+ return rc;
+}
+
+/*
+** Invoke tdsqlite3WalkExpr() or tdsqlite3WalkSelect() on all Select or Expr
+** objects that are part of the trigger passed as the second argument.
+*/
+static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){
+ TriggerStep *pStep;
+
+ /* Find tokens to edit in WHEN clause */
+ tdsqlite3WalkExpr(pWalker, pTrigger->pWhen);
+
+ /* Find tokens to edit in trigger steps */
+ for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){
+ tdsqlite3WalkSelect(pWalker, pStep->pSelect);
+ tdsqlite3WalkExpr(pWalker, pStep->pWhere);
+ tdsqlite3WalkExprList(pWalker, pStep->pExprList);
+ if( pStep->pUpsert ){
+ Upsert *pUpsert = pStep->pUpsert;
+ tdsqlite3WalkExprList(pWalker, pUpsert->pUpsertTarget);
+ tdsqlite3WalkExprList(pWalker, pUpsert->pUpsertSet);
+ tdsqlite3WalkExpr(pWalker, pUpsert->pUpsertWhere);
+ tdsqlite3WalkExpr(pWalker, pUpsert->pUpsertTargetWhere);
+ }
+ }
+}
+
+/*
+** Free the contents of Parse object (*pParse). Do not free the memory
+** occupied by the Parse object itself.
+*/
+static void renameParseCleanup(Parse *pParse){
+ tdsqlite3 *db = pParse->db;
+ Index *pIdx;
+ if( pParse->pVdbe ){
+ tdsqlite3VdbeFinalize(pParse->pVdbe);
+ }
+ tdsqlite3DeleteTable(db, pParse->pNewTable);
+ while( (pIdx = pParse->pNewIndex)!=0 ){
+ pParse->pNewIndex = pIdx->pNext;
+ tdsqlite3FreeIndex(db, pIdx);
+ }
+ tdsqlite3DeleteTrigger(db, pParse->pNewTrigger);
+ tdsqlite3DbFree(db, pParse->zErrMsg);
+ renameTokenFree(db, pParse->pRename);
+ tdsqlite3ParserReset(pParse);
+}
+
+/*
+** SQL function:
+**
+** sqlite_rename_column(zSql, iCol, bQuote, zNew, zTable, zOld)
+**
+** 0. zSql: SQL statement to rewrite
+** 1. type: Type of object ("table", "view" etc.)
+** 2. object: Name of object
+** 3. Database: Database name (e.g. "main")
+** 4. Table: Table name
+** 5. iCol: Index of column to rename
+** 6. zNew: New column name
+** 7. bQuote: Non-zero if the new column name should be quoted.
+** 8. bTemp: True if zSql comes from temp schema
+**
+** Do a column rename operation on the CREATE statement given in zSql.
+** The iCol-th column (left-most is 0) of table zTable is renamed from zCol
+** into zNew. The name should be quoted if bQuote is true.
+**
+** This function is used internally by the ALTER TABLE RENAME COLUMN command.
+** It is only accessible to SQL created using tdsqlite3NestedParse(). It is
+** not reachable from ordinary SQL passed into tdsqlite3_prepare().
+*/
+static void renameColumnFunc(
+ tdsqlite3_context *context,
+ int NotUsed,
+ tdsqlite3_value **argv
+){
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
+ RenameCtx sCtx;
+ const char *zSql = (const char*)tdsqlite3_value_text(argv[0]);
+ const char *zDb = (const char*)tdsqlite3_value_text(argv[3]);
+ const char *zTable = (const char*)tdsqlite3_value_text(argv[4]);
+ int iCol = tdsqlite3_value_int(argv[5]);
+ const char *zNew = (const char*)tdsqlite3_value_text(argv[6]);
+ int bQuote = tdsqlite3_value_int(argv[7]);
+ int bTemp = tdsqlite3_value_int(argv[8]);
+ const char *zOld;
+ int rc;
+ Parse sParse;
+ Walker sWalker;
+ Index *pIdx;
+ int i;
+ Table *pTab;
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ tdsqlite3_xauth xAuth = db->xAuth;
+#endif
+
+ UNUSED_PARAMETER(NotUsed);
+ if( zSql==0 ) return;
+ if( zTable==0 ) return;
+ if( zNew==0 ) return;
+ if( iCol<0 ) return;
+ tdsqlite3BtreeEnterAll(db);
+ pTab = tdsqlite3FindTable(db, zTable, zDb);
+ if( pTab==0 || iCol>=pTab->nCol ){
+ tdsqlite3BtreeLeaveAll(db);
+ return;
+ }
+ zOld = pTab->aCol[iCol].zName;
+ memset(&sCtx, 0, sizeof(sCtx));
+ sCtx.iCol = ((iCol==pTab->iPKey) ? -1 : iCol);
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ db->xAuth = 0;
+#endif
+ rc = renameParseSql(&sParse, zDb, db, zSql, bTemp);
+
+ /* Find tokens that need to be replaced. */
+ memset(&sWalker, 0, sizeof(Walker));
+ sWalker.pParse = &sParse;
+ sWalker.xExprCallback = renameColumnExprCb;
+ sWalker.xSelectCallback = renameColumnSelectCb;
+ sWalker.u.pRename = &sCtx;
+
+ sCtx.pTab = pTab;
+ if( rc!=SQLITE_OK ) goto renameColumnFunc_done;
+ if( sParse.pNewTable ){
+ Select *pSelect = sParse.pNewTable->pSelect;
+ if( pSelect ){
+ pSelect->selFlags &= ~SF_View;
+ sParse.rc = SQLITE_OK;
+ tdsqlite3SelectPrep(&sParse, pSelect, 0);
+ rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc);
+ if( rc==SQLITE_OK ){
+ tdsqlite3WalkSelect(&sWalker, pSelect);
+ }
+ if( rc!=SQLITE_OK ) goto renameColumnFunc_done;
+ }else{
+ /* A regular table */
+ int bFKOnly = tdsqlite3_stricmp(zTable, sParse.pNewTable->zName);
+ FKey *pFKey;
+ assert( sParse.pNewTable->pSelect==0 );
+ sCtx.pTab = sParse.pNewTable;
+ if( bFKOnly==0 ){
+ renameTokenFind(
+ &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zName
+ );
+ if( sCtx.iCol<0 ){
+ renameTokenFind(&sParse, &sCtx, (void*)&sParse.pNewTable->iPKey);
+ }
+ tdsqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck);
+ for(pIdx=sParse.pNewTable->pIndex; pIdx; pIdx=pIdx->pNext){
+ tdsqlite3WalkExprList(&sWalker, pIdx->aColExpr);
+ }
+ for(pIdx=sParse.pNewIndex; pIdx; pIdx=pIdx->pNext){
+ tdsqlite3WalkExprList(&sWalker, pIdx->aColExpr);
+ }
+ }
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ for(i=0; i<sParse.pNewTable->nCol; i++){
+ tdsqlite3WalkExpr(&sWalker, sParse.pNewTable->aCol[i].pDflt);
+ }
+#endif
+
+ for(pFKey=sParse.pNewTable->pFKey; pFKey; pFKey=pFKey->pNextFrom){
+ for(i=0; i<pFKey->nCol; i++){
+ if( bFKOnly==0 && pFKey->aCol[i].iFrom==iCol ){
+ renameTokenFind(&sParse, &sCtx, (void*)&pFKey->aCol[i]);
+ }
+ if( 0==tdsqlite3_stricmp(pFKey->zTo, zTable)
+ && 0==tdsqlite3_stricmp(pFKey->aCol[i].zCol, zOld)
+ ){
+ renameTokenFind(&sParse, &sCtx, (void*)pFKey->aCol[i].zCol);
+ }
+ }
+ }
+ }
+ }else if( sParse.pNewIndex ){
+ tdsqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr);
+ tdsqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere);
+ }else{
+ /* A trigger */
+ TriggerStep *pStep;
+ rc = renameResolveTrigger(&sParse, (bTemp ? 0 : zDb));
+ if( rc!=SQLITE_OK ) goto renameColumnFunc_done;
+
+ for(pStep=sParse.pNewTrigger->step_list; pStep; pStep=pStep->pNext){
+ if( pStep->zTarget ){
+ Table *pTarget = tdsqlite3LocateTable(&sParse, 0, pStep->zTarget, zDb);
+ if( pTarget==pTab ){
+ if( pStep->pUpsert ){
+ ExprList *pUpsertSet = pStep->pUpsert->pUpsertSet;
+ renameColumnElistNames(&sParse, &sCtx, pUpsertSet, zOld);
+ }
+ renameColumnIdlistNames(&sParse, &sCtx, pStep->pIdList, zOld);
+ renameColumnElistNames(&sParse, &sCtx, pStep->pExprList, zOld);
+ }
+ }
+ }
+
+
+ /* Find tokens to edit in UPDATE OF clause */
+ if( sParse.pTriggerTab==pTab ){
+ renameColumnIdlistNames(&sParse, &sCtx,sParse.pNewTrigger->pColumns,zOld);
+ }
+
+ /* Find tokens to edit in various expressions and selects */
+ renameWalkTrigger(&sWalker, sParse.pNewTrigger);
+ }
+
+ assert( rc==SQLITE_OK );
+ rc = renameEditSql(context, &sCtx, zSql, zNew, bQuote);
+
+renameColumnFunc_done:
+ if( rc!=SQLITE_OK ){
+ if( sParse.zErrMsg ){
+ renameColumnParseError(context, 0, argv[1], argv[2], &sParse);
+ }else{
+ tdsqlite3_result_error_code(context, rc);
+ }
+ }
+
+ renameParseCleanup(&sParse);
+ renameTokenFree(db, sCtx.pList);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ db->xAuth = xAuth;
+#endif
+ tdsqlite3BtreeLeaveAll(db);
+}
+
+/*
+** Walker expression callback used by "RENAME TABLE".
+*/
+static int renameTableExprCb(Walker *pWalker, Expr *pExpr){
+ RenameCtx *p = pWalker->u.pRename;
+ if( pExpr->op==TK_COLUMN && p->pTab==pExpr->y.pTab ){
+ renameTokenFind(pWalker->pParse, p, (void*)&pExpr->y.pTab);
+ }
+ return WRC_Continue;
+}
+
+/*
+** Walker select callback used by "RENAME TABLE".
+*/
+static int renameTableSelectCb(Walker *pWalker, Select *pSelect){
+ int i;
+ RenameCtx *p = pWalker->u.pRename;
+ SrcList *pSrc = pSelect->pSrc;
+ if( pSelect->selFlags & SF_View ) return WRC_Prune;
+ if( pSrc==0 ){
+ assert( pWalker->pParse->db->mallocFailed );
+ return WRC_Abort;
+ }
+ for(i=0; i<pSrc->nSrc; i++){
+ struct SrcList_item *pItem = &pSrc->a[i];
+ if( pItem->pTab==p->pTab ){
+ renameTokenFind(pWalker->pParse, p, pItem->zName);
+ }
+ }
+ renameWalkWith(pWalker, pSelect);
+
+ return WRC_Continue;
+}
+
+
+/*
+** This C function implements an SQL user function that is used by SQL code
+** generated by the ALTER TABLE ... RENAME command to modify the definition
+** of any foreign key constraints that use the table being renamed as the
+** parent table. It is passed three arguments:
+**
+** 0: The database containing the table being renamed.
+** 1. type: Type of object ("table", "view" etc.)
+** 2. object: Name of object
+** 3: The complete text of the schema statement being modified,
+** 4: The old name of the table being renamed, and
+** 5: The new name of the table being renamed.
+** 6: True if the schema statement comes from the temp db.
+**
+** It returns the new schema statement. For example:
+**
+** sqlite_rename_table('main', 'CREATE TABLE t1(a REFERENCES t2)','t2','t3',0)
+** -> 'CREATE TABLE t1(a REFERENCES t3)'
+*/
+static void renameTableFunc(
+ tdsqlite3_context *context,
+ int NotUsed,
+ tdsqlite3_value **argv
+){
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
+ const char *zDb = (const char*)tdsqlite3_value_text(argv[0]);
+ const char *zInput = (const char*)tdsqlite3_value_text(argv[3]);
+ const char *zOld = (const char*)tdsqlite3_value_text(argv[4]);
+ const char *zNew = (const char*)tdsqlite3_value_text(argv[5]);
+ int bTemp = tdsqlite3_value_int(argv[6]);
+ UNUSED_PARAMETER(NotUsed);
+
+ if( zInput && zOld && zNew ){
+ Parse sParse;
+ int rc;
+ int bQuote = 1;
+ RenameCtx sCtx;
+ Walker sWalker;
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ tdsqlite3_xauth xAuth = db->xAuth;
+ db->xAuth = 0;
+#endif
+
+ tdsqlite3BtreeEnterAll(db);
+
+ memset(&sCtx, 0, sizeof(RenameCtx));
+ sCtx.pTab = tdsqlite3FindTable(db, zOld, zDb);
+ memset(&sWalker, 0, sizeof(Walker));
+ sWalker.pParse = &sParse;
+ sWalker.xExprCallback = renameTableExprCb;
+ sWalker.xSelectCallback = renameTableSelectCb;
+ sWalker.u.pRename = &sCtx;
+
+ rc = renameParseSql(&sParse, zDb, db, zInput, bTemp);
+
+ if( rc==SQLITE_OK ){
+ int isLegacy = (db->flags & SQLITE_LegacyAlter);
+ if( sParse.pNewTable ){
+ Table *pTab = sParse.pNewTable;
+
+ if( pTab->pSelect ){
+ if( isLegacy==0 ){
+ Select *pSelect = pTab->pSelect;
+ NameContext sNC;
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = &sParse;
+
+ assert( pSelect->selFlags & SF_View );
+ pSelect->selFlags &= ~SF_View;
+ tdsqlite3SelectPrep(&sParse, pTab->pSelect, &sNC);
+ if( sParse.nErr ){
+ rc = sParse.rc;
+ }else{
+ tdsqlite3WalkSelect(&sWalker, pTab->pSelect);
+ }
+ }
+ }else{
+ /* Modify any FK definitions to point to the new table. */
+#ifndef SQLITE_OMIT_FOREIGN_KEY
+ if( isLegacy==0 || (db->flags & SQLITE_ForeignKeys) ){
+ FKey *pFKey;
+ for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){
+ if( tdsqlite3_stricmp(pFKey->zTo, zOld)==0 ){
+ renameTokenFind(&sParse, &sCtx, (void*)pFKey->zTo);
+ }
+ }
+ }
+#endif
+
+ /* If this is the table being altered, fix any table refs in CHECK
+ ** expressions. Also update the name that appears right after the
+ ** "CREATE [VIRTUAL] TABLE" bit. */
+ if( tdsqlite3_stricmp(zOld, pTab->zName)==0 ){
+ sCtx.pTab = pTab;
+ if( isLegacy==0 ){
+ tdsqlite3WalkExprList(&sWalker, pTab->pCheck);
+ }
+ renameTokenFind(&sParse, &sCtx, pTab->zName);
+ }
+ }
+ }
+
+ else if( sParse.pNewIndex ){
+ renameTokenFind(&sParse, &sCtx, sParse.pNewIndex->zName);
+ if( isLegacy==0 ){
+ tdsqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere);
+ }
+ }
+
+#ifndef SQLITE_OMIT_TRIGGER
+ else{
+ Trigger *pTrigger = sParse.pNewTrigger;
+ TriggerStep *pStep;
+ if( 0==tdsqlite3_stricmp(sParse.pNewTrigger->table, zOld)
+ && sCtx.pTab->pSchema==pTrigger->pTabSchema
+ ){
+ renameTokenFind(&sParse, &sCtx, sParse.pNewTrigger->table);
+ }
+
+ if( isLegacy==0 ){
+ rc = renameResolveTrigger(&sParse, bTemp ? 0 : zDb);
+ if( rc==SQLITE_OK ){
+ renameWalkTrigger(&sWalker, pTrigger);
+ for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){
+ if( pStep->zTarget && 0==tdsqlite3_stricmp(pStep->zTarget, zOld) ){
+ renameTokenFind(&sParse, &sCtx, pStep->zTarget);
+ }
+ }
+ }
+ }
+ }
+#endif
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = renameEditSql(context, &sCtx, zInput, zNew, bQuote);
+ }
+ if( rc!=SQLITE_OK ){
+ if( sParse.zErrMsg ){
+ renameColumnParseError(context, 0, argv[1], argv[2], &sParse);
+ }else{
+ tdsqlite3_result_error_code(context, rc);
+ }
+ }
+
+ renameParseCleanup(&sParse);
+ renameTokenFree(db, sCtx.pList);
+ tdsqlite3BtreeLeaveAll(db);
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ db->xAuth = xAuth;
+#endif
+ }
+
return;
}
+
+/*
+** An SQL user function that checks that there are no parse or symbol
+** resolution problems in a CREATE TRIGGER|TABLE|VIEW|INDEX statement.
+** After an ALTER TABLE .. RENAME operation is performed and the schema
+** reloaded, this function is called on each SQL statement in the schema
+** to ensure that it is still usable.
+**
+** 0: Database name ("main", "temp" etc.).
+** 1: SQL statement.
+** 2: Object type ("view", "table", "trigger" or "index").
+** 3: Object name.
+** 4: True if object is from temp schema.
+**
+** Unless it finds an error, this function normally returns NULL. However, it
+** returns integer value 1 if:
+**
+** * the SQL argument creates a trigger, and
+** * the table that the trigger is attached to is in database zDb.
+*/
+static void renameTableTest(
+ tdsqlite3_context *context,
+ int NotUsed,
+ tdsqlite3_value **argv
+){
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
+ char const *zDb = (const char*)tdsqlite3_value_text(argv[0]);
+ char const *zInput = (const char*)tdsqlite3_value_text(argv[1]);
+ int bTemp = tdsqlite3_value_int(argv[4]);
+ int isLegacy = (db->flags & SQLITE_LegacyAlter);
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ tdsqlite3_xauth xAuth = db->xAuth;
+ db->xAuth = 0;
+#endif
+
+ UNUSED_PARAMETER(NotUsed);
+ if( zDb && zInput ){
+ int rc;
+ Parse sParse;
+ rc = renameParseSql(&sParse, zDb, db, zInput, bTemp);
+ if( rc==SQLITE_OK ){
+ if( isLegacy==0 && sParse.pNewTable && sParse.pNewTable->pSelect ){
+ NameContext sNC;
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = &sParse;
+ tdsqlite3SelectPrep(&sParse, sParse.pNewTable->pSelect, &sNC);
+ if( sParse.nErr ) rc = sParse.rc;
+ }
+
+ else if( sParse.pNewTrigger ){
+ if( isLegacy==0 ){
+ rc = renameResolveTrigger(&sParse, bTemp ? 0 : zDb);
+ }
+ if( rc==SQLITE_OK ){
+ int i1 = tdsqlite3SchemaToIndex(db, sParse.pNewTrigger->pTabSchema);
+ int i2 = tdsqlite3FindDbName(db, zDb);
+ if( i1==i2 ) tdsqlite3_result_int(context, 1);
+ }
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ renameColumnParseError(context, 1, argv[2], argv[3], &sParse);
+ }
+ renameParseCleanup(&sParse);
+ }
+
+#ifndef SQLITE_OMIT_AUTHORIZATION
+ db->xAuth = xAuth;
+#endif
+}
+
+/*
+** Register built-in functions used to help implement ALTER TABLE
+*/
+SQLITE_PRIVATE void tdsqlite3AlterFunctions(void){
+ static FuncDef aAlterTableFuncs[] = {
+ INTERNAL_FUNCTION(sqlite_rename_column, 9, renameColumnFunc),
+ INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc),
+ INTERNAL_FUNCTION(sqlite_rename_test, 5, renameTableTest),
+ };
+ tdsqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs));
+}
#endif /* SQLITE_ALTER_TABLE */
/************** End of alter.c ***********************************************/
@@ -98672,13 +109974,13 @@ exit_begin_add_column:
** is between 3.6.18 and 3.7.8, inclusive, and unless SQLite is compiled
** with SQLITE_ENABLE_STAT2. The sqlite_stat2 table is deprecated.
** The sqlite_stat2 table is superseded by sqlite_stat3, which is only
-** created and used by SQLite versions 3.7.9 and later and with
+** created and used by SQLite versions 3.7.9 through 3.29.0 when
** SQLITE_ENABLE_STAT3 defined. The functionality of sqlite_stat3
-** is a superset of sqlite_stat2. The sqlite_stat4 is an enhanced
-** version of sqlite_stat3 and is only available when compiled with
-** SQLITE_ENABLE_STAT4 and in SQLite versions 3.8.1 and later. It is
-** not possible to enable both STAT3 and STAT4 at the same time. If they
-** are both enabled, then STAT4 takes precedence.
+** is a superset of sqlite_stat2 and is also now deprecated. The
+** sqlite_stat4 is an enhanced version of sqlite_stat3 and is only
+** available when compiled with SQLITE_ENABLE_STAT4 and in SQLite
+** versions 3.8.1 and later. STAT4 is the only variant that is still
+** supported.
**
** For most applications, sqlite_stat1 provides all the statistics required
** for the query planner to make good choices.
@@ -98789,17 +110091,11 @@ exit_begin_add_column:
#if defined(SQLITE_ENABLE_STAT4)
# define IsStat4 1
-# define IsStat3 0
-#elif defined(SQLITE_ENABLE_STAT3)
-# define IsStat4 0
-# define IsStat3 1
#else
# define IsStat4 0
-# define IsStat3 0
# undef SQLITE_STAT4_SAMPLES
# define SQLITE_STAT4_SAMPLES 1
#endif
-#define IsStat34 (IsStat3+IsStat4) /* 1 for STAT3 or STAT4. 0 otherwise */
/*
** This routine generates code that opens the sqlite_statN tables.
@@ -98828,25 +110124,21 @@ static void openStatTable(
{ "sqlite_stat1", "tbl,idx,stat" },
#if defined(SQLITE_ENABLE_STAT4)
{ "sqlite_stat4", "tbl,idx,neq,nlt,ndlt,sample" },
- { "sqlite_stat3", 0 },
-#elif defined(SQLITE_ENABLE_STAT3)
- { "sqlite_stat3", "tbl,idx,neq,nlt,ndlt,sample" },
- { "sqlite_stat4", 0 },
#else
- { "sqlite_stat3", 0 },
{ "sqlite_stat4", 0 },
#endif
+ { "sqlite_stat3", 0 },
};
int i;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Db *pDb;
- Vdbe *v = sqlite3GetVdbe(pParse);
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
int aRoot[ArraySize(aTable)];
u8 aCreateTbl[ArraySize(aTable)];
if( v==0 ) return;
- assert( sqlite3BtreeHoldsAllMutexes(db) );
- assert( sqlite3VdbeDb(v)==db );
+ assert( tdsqlite3BtreeHoldsAllMutexes(db) );
+ assert( tdsqlite3VdbeDb(v)==db );
pDb = &db->aDb[iDb];
/* Create new statistic tables if they do not exist, or clear them
@@ -98855,13 +110147,13 @@ static void openStatTable(
for(i=0; i<ArraySize(aTable); i++){
const char *zTab = aTable[i].zName;
Table *pStat;
- if( (pStat = sqlite3FindTable(db, zTab, pDb->zDbSName))==0 ){
+ if( (pStat = tdsqlite3FindTable(db, zTab, pDb->zDbSName))==0 ){
if( aTable[i].zCols ){
/* The sqlite_statN table does not exist. Create it. Note that a
** side-effect of the CREATE TABLE statement is to leave the rootpage
** of the new table in register pParse->regRoot. This is important
** because the OpenWrite opcode below will be needing it. */
- sqlite3NestedParse(pParse,
+ tdsqlite3NestedParse(pParse,
"CREATE TABLE %Q.%s(%s)", pDb->zDbSName, zTab, aTable[i].zCols
);
aRoot[i] = pParse->regRoot;
@@ -98873,15 +110165,19 @@ static void openStatTable(
** entire contents of the table. */
aRoot[i] = pStat->tnum;
aCreateTbl[i] = 0;
- sqlite3TableLock(pParse, iDb, aRoot[i], 1, zTab);
+ tdsqlite3TableLock(pParse, iDb, aRoot[i], 1, zTab);
if( zWhere ){
- sqlite3NestedParse(pParse,
+ tdsqlite3NestedParse(pParse,
"DELETE FROM %Q.%s WHERE %s=%Q",
pDb->zDbSName, zTab, zWhereType, zWhere
);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ }else if( db->xPreUpdateCallback ){
+ tdsqlite3NestedParse(pParse, "DELETE FROM %Q.%s", pDb->zDbSName, zTab);
+#endif
}else{
/* The sqlite_stat[134] table already exists. Delete all rows. */
- sqlite3VdbeAddOp2(v, OP_Clear, aRoot[i], iDb);
+ tdsqlite3VdbeAddOp2(v, OP_Clear, aRoot[i], iDb);
}
}
}
@@ -98889,8 +110185,8 @@ static void openStatTable(
/* Open the sqlite_stat[134] tables for writing. */
for(i=0; aTable[i].zCols; i++){
assert( i<ArraySize(aTable) );
- sqlite3VdbeAddOp4Int(v, OP_OpenWrite, iStatCur+i, aRoot[i], iDb, 3);
- sqlite3VdbeChangeP5(v, aCreateTbl[i]);
+ tdsqlite3VdbeAddOp4Int(v, OP_OpenWrite, iStatCur+i, aRoot[i], iDb, 3);
+ tdsqlite3VdbeChangeP5(v, aCreateTbl[i]);
VdbeComment((v, aTable[i].zName));
}
}
@@ -98912,7 +110208,7 @@ typedef struct Stat4Sample Stat4Sample;
struct Stat4Sample {
tRowcnt *anEq; /* sqlite_stat4.nEq */
tRowcnt *anDLt; /* sqlite_stat4.nDLt */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
tRowcnt *anLt; /* sqlite_stat4.nLt */
union {
i64 iRowid; /* Rowid in main table of the key */
@@ -98935,18 +110231,19 @@ struct Stat4Accum {
Stat4Sample *aBest; /* Array of nCol best samples */
int iMin; /* Index in a[] of entry with minimum score */
int nSample; /* Current number of samples */
+ int nMaxEqZero; /* Max leading 0 in anEq[] for any a[] entry */
int iGet; /* Index of current sample accessed by stat_get() */
Stat4Sample *a; /* Array of mxSample Stat4Sample objects */
- sqlite3 *db; /* Database connection, for malloc() */
+ tdsqlite3 *db; /* Database connection, for malloc() */
};
/* Reclaim memory used by a Stat4Sample
*/
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
-static void sampleClear(sqlite3 *db, Stat4Sample *p){
+#ifdef SQLITE_ENABLE_STAT4
+static void sampleClear(tdsqlite3 *db, Stat4Sample *p){
assert( db!=0 );
if( p->nRowid ){
- sqlite3DbFree(db, p->u.aRowid);
+ tdsqlite3DbFree(db, p->u.aRowid);
p->nRowid = 0;
}
}
@@ -98954,11 +110251,11 @@ static void sampleClear(sqlite3 *db, Stat4Sample *p){
/* Initialize the BLOB value of a ROWID
*/
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
-static void sampleSetRowid(sqlite3 *db, Stat4Sample *p, int n, const u8 *pData){
+#ifdef SQLITE_ENABLE_STAT4
+static void sampleSetRowid(tdsqlite3 *db, Stat4Sample *p, int n, const u8 *pData){
assert( db!=0 );
- if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid);
- p->u.aRowid = sqlite3DbMallocRawNN(db, n);
+ if( p->nRowid ) tdsqlite3DbFree(db, p->u.aRowid);
+ p->u.aRowid = tdsqlite3DbMallocRawNN(db, n);
if( p->u.aRowid ){
p->nRowid = n;
memcpy(p->u.aRowid, pData, n);
@@ -98970,10 +110267,10 @@ static void sampleSetRowid(sqlite3 *db, Stat4Sample *p, int n, const u8 *pData){
/* Initialize the INTEGER value of a ROWID.
*/
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
-static void sampleSetRowidInt64(sqlite3 *db, Stat4Sample *p, i64 iRowid){
+#ifdef SQLITE_ENABLE_STAT4
+static void sampleSetRowidInt64(tdsqlite3 *db, Stat4Sample *p, i64 iRowid){
assert( db!=0 );
- if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid);
+ if( p->nRowid ) tdsqlite3DbFree(db, p->u.aRowid);
p->nRowid = 0;
p->u.iRowid = iRowid;
}
@@ -98983,7 +110280,7 @@ static void sampleSetRowidInt64(sqlite3 *db, Stat4Sample *p, i64 iRowid){
/*
** Copy the contents of object (*pFrom) into (*pTo).
*/
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){
pTo->isPSample = pFrom->isPSample;
pTo->iCol = pFrom->iCol;
@@ -99004,13 +110301,13 @@ static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){
*/
static void stat4Destructor(void *pOld){
Stat4Accum *p = (Stat4Accum*)pOld;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
int i;
for(i=0; i<p->nCol; i++) sampleClear(p->db, p->aBest+i);
for(i=0; i<p->mxSample; i++) sampleClear(p->db, p->a+i);
sampleClear(p->db, &p->current);
#endif
- sqlite3DbFree(p->db, p);
+ tdsqlite3DbFree(p->db, p);
}
/*
@@ -99024,7 +110321,7 @@ static void stat4Destructor(void *pOld){
** WITHOUT ROWID table, N is the number of PRIMARY KEY columns, not the
** total number of columns in the table.
**
-** Note 2: C is only used for STAT3 and STAT4.
+** Note 2: C is only used for STAT4.
**
** For indexes on ordinary rowid tables, N==K+1. But for indexes on
** WITHOUT ROWID tables, N=K+P where P is the number of columns in the
@@ -99037,26 +110334,26 @@ static void stat4Destructor(void *pOld){
** object.
*/
static void statInit(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
Stat4Accum *p;
int nCol; /* Number of columns in index being sampled */
int nKeyCol; /* Number of key columns */
int nColUp; /* nCol rounded up for alignment */
int n; /* Bytes of space to allocate */
- sqlite3 *db; /* Database connection */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+ tdsqlite3 *db; /* Database connection */
+#ifdef SQLITE_ENABLE_STAT4
int mxSample = SQLITE_STAT4_SAMPLES;
#endif
/* Decode the three function arguments */
UNUSED_PARAMETER(argc);
- nCol = sqlite3_value_int(argv[0]);
+ nCol = tdsqlite3_value_int(argv[0]);
assert( nCol>0 );
nColUp = sizeof(tRowcnt)<8 ? (nCol+1)&~1 : nCol;
- nKeyCol = sqlite3_value_int(argv[1]);
+ nKeyCol = tdsqlite3_value_int(argv[1]);
assert( nKeyCol<=nCol );
assert( nKeyCol>0 );
@@ -99064,16 +110361,16 @@ static void statInit(
n = sizeof(*p)
+ sizeof(tRowcnt)*nColUp /* Stat4Accum.anEq */
+ sizeof(tRowcnt)*nColUp /* Stat4Accum.anDLt */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
+ sizeof(tRowcnt)*nColUp /* Stat4Accum.anLt */
+ sizeof(Stat4Sample)*(nCol+mxSample) /* Stat4Accum.aBest[], a[] */
+ sizeof(tRowcnt)*3*nColUp*(nCol+mxSample)
#endif
;
- db = sqlite3_context_db_handle(context);
- p = sqlite3DbMallocZero(db, n);
+ db = tdsqlite3_context_db_handle(context);
+ p = tdsqlite3DbMallocZero(db, n);
if( p==0 ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
return;
}
@@ -99084,16 +110381,16 @@ static void statInit(
p->current.anDLt = (tRowcnt*)&p[1];
p->current.anEq = &p->current.anDLt[nColUp];
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
{
u8 *pSpace; /* Allocated space not yet assigned */
int i; /* Used to iterate through p->aSample[] */
p->iGet = -1;
p->mxSample = mxSample;
- p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[2])/(mxSample/3+1) + 1);
+ p->nPSample = (tRowcnt)(tdsqlite3_value_int64(argv[2])/(mxSample/3+1) + 1);
p->current.anLt = &p->current.anEq[nColUp];
- p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]);
+ p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)tdsqlite3_value_int(argv[2]);
/* Set up the Stat4Accum.a[] and aBest[] arrays */
p->a = (struct Stat4Sample*)&p->current.anLt[nColUp];
@@ -99116,15 +110413,16 @@ static void statInit(
** only the pointer (the 2nd parameter) matters. The size of the object
** (given by the 3rd parameter) is never used and can be any positive
** value. */
- sqlite3_result_blob(context, p, sizeof(*p), stat4Destructor);
+ tdsqlite3_result_blob(context, p, sizeof(*p), stat4Destructor);
}
static const FuncDef statInitFuncdef = {
- 2+IsStat34, /* nArg */
+ 2+IsStat4, /* nArg */
SQLITE_UTF8, /* funcFlags */
0, /* pUserData */
0, /* pNext */
statInit, /* xSFunc */
0, /* xFinalize */
+ 0, 0, /* xValue, xInverse */
"stat_init", /* zName */
{0}
};
@@ -99158,7 +110456,7 @@ static int sampleIsBetterPost(
}
#endif
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
/*
** Return true if pNew is to be preferred over pOld.
**
@@ -99177,15 +110475,11 @@ static int sampleIsBetter(
assert( IsStat4 || (pNew->iCol==0 && pOld->iCol==0) );
if( (nEqNew>nEqOld) ) return 1;
-#ifdef SQLITE_ENABLE_STAT4
if( nEqNew==nEqOld ){
if( pNew->iCol<pOld->iCol ) return 1;
return (pNew->iCol==pOld->iCol && sampleIsBetterPost(pAccum, pNew, pOld));
}
return 0;
-#else
- return (nEqNew==nEqOld && pNew->iHash>pOld->iHash);
-#endif
}
/*
@@ -99198,7 +110492,13 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){
assert( IsStat4 || nEqZero==0 );
-#ifdef SQLITE_ENABLE_STAT4
+ /* Stat4Accum.nMaxEqZero is set to the maximum number of leading 0
+ ** values in the anEq[] array of any sample in Stat4Accum.a[]. In
+ ** other words, if nMaxEqZero is n, then it is guaranteed that there
+ ** are no samples with Stat4Sample.anEq[m]==0 for (m>=n). */
+ if( nEqZero>p->nMaxEqZero ){
+ p->nMaxEqZero = nEqZero;
+ }
if( pNew->isPSample==0 ){
Stat4Sample *pUpgrade = 0;
assert( pNew->anEq[pNew->iCol]>0 );
@@ -99225,7 +110525,6 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){
goto find_new_min;
}
}
-#endif
/* If necessary, remove sample iMin to make room for the new sample. */
if( p->nSample>=p->mxSample ){
@@ -99246,10 +110545,8 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){
/* The "rows less-than" for the rowid column must be greater than that
** for the last sample in the p->a[] array. Otherwise, the samples would
** be out of order. */
-#ifdef SQLITE_ENABLE_STAT4
assert( p->nSample==0
|| pNew->anLt[p->nCol-1] > p->a[p->nSample-1].anLt[p->nCol-1] );
-#endif
/* Insert the new sample */
pSample = &p->a[p->nSample];
@@ -99259,9 +110556,7 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){
/* Zero the first nEqZero entries in the anEq[] array. */
memset(pSample->anEq, 0, sizeof(tRowcnt)*nEqZero);
-#ifdef SQLITE_ENABLE_STAT4
- find_new_min:
-#endif
+find_new_min:
if( p->nSample>=p->mxSample ){
int iMin = -1;
for(i=0; i<p->mxSample; i++){
@@ -99274,7 +110569,7 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){
p->iMin = iMin;
}
}
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
/*
** Field iChng of the index being scanned has changed. So at this point
@@ -99296,37 +110591,26 @@ static void samplePushPrevious(Stat4Accum *p, int iChng){
}
}
- /* Update the anEq[] fields of any samples already collected. */
+ /* Check that no sample contains an anEq[] entry with an index of
+ ** p->nMaxEqZero or greater set to zero. */
for(i=p->nSample-1; i>=0; i--){
int j;
- for(j=iChng; j<p->nCol; j++){
- if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j];
- }
+ for(j=p->nMaxEqZero; j<p->nCol; j++) assert( p->a[i].anEq[j]>0 );
}
-#endif
-
-#if defined(SQLITE_ENABLE_STAT3) && !defined(SQLITE_ENABLE_STAT4)
- if( iChng==0 ){
- tRowcnt nLt = p->current.anLt[0];
- tRowcnt nEq = p->current.anEq[0];
-
- /* Check if this is to be a periodic sample. If so, add it. */
- if( (nLt/p->nPSample)!=(nLt+nEq)/p->nPSample ){
- p->current.isPSample = 1;
- sampleInsert(p, &p->current, 0);
- p->current.isPSample = 0;
- }else
- /* Or if it is a non-periodic sample. Add it in this case too. */
- if( p->nSample<p->mxSample
- || sampleIsBetter(p, &p->current, &p->a[p->iMin])
- ){
- sampleInsert(p, &p->current, 0);
+ /* Update the anEq[] fields of any samples already collected. */
+ if( iChng<p->nMaxEqZero ){
+ for(i=p->nSample-1; i>=0; i--){
+ int j;
+ for(j=iChng; j<p->nCol; j++){
+ if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j];
+ }
}
+ p->nMaxEqZero = iChng;
}
#endif
-#ifndef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifndef SQLITE_ENABLE_STAT4
UNUSED_PARAMETER( p );
UNUSED_PARAMETER( iChng );
#endif
@@ -99346,18 +110630,18 @@ static void samplePushPrevious(Stat4Accum *p, int iChng){
** index being analyzed. The stat_get() SQL function will later be used to
** extract relevant information for constructing the sqlite_statN tables.
**
-** The R parameter is only used for STAT3 and STAT4
+** The R parameter is only used for STAT4
*/
static void statPush(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
int i;
/* The three function arguments */
- Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]);
- int iChng = sqlite3_value_int(argv[1]);
+ Stat4Accum *p = (Stat4Accum*)tdsqlite3_value_blob(argv[0]);
+ int iChng = tdsqlite3_value_int(argv[1]);
UNUSED_PARAMETER( argc );
UNUSED_PARAMETER( context );
@@ -99378,19 +110662,19 @@ static void statPush(
}
for(i=iChng; i<p->nCol; i++){
p->current.anDLt[i]++;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
p->current.anLt[i] += p->current.anEq[i];
#endif
p->current.anEq[i] = 1;
}
}
p->nRow++;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- if( sqlite3_value_type(argv[2])==SQLITE_INTEGER ){
- sampleSetRowidInt64(p->db, &p->current, sqlite3_value_int64(argv[2]));
+#ifdef SQLITE_ENABLE_STAT4
+ if( tdsqlite3_value_type(argv[2])==SQLITE_INTEGER ){
+ sampleSetRowidInt64(p->db, &p->current, tdsqlite3_value_int64(argv[2]));
}else{
- sampleSetRowid(p->db, &p->current, sqlite3_value_bytes(argv[2]),
- sqlite3_value_blob(argv[2]));
+ sampleSetRowid(p->db, &p->current, tdsqlite3_value_bytes(argv[2]),
+ tdsqlite3_value_blob(argv[2]));
}
p->current.iHash = p->iPrn = p->iPrn*1103515245 + 12345;
#endif
@@ -99418,12 +110702,13 @@ static void statPush(
#endif
}
static const FuncDef statPushFuncdef = {
- 2+IsStat34, /* nArg */
+ 2+IsStat4, /* nArg */
SQLITE_UTF8, /* funcFlags */
0, /* pUserData */
0, /* pNext */
statPush, /* xSFunc */
0, /* xFinalize */
+ 0, 0, /* xValue, xInverse */
"stat_push", /* zName */
{0}
};
@@ -99442,20 +110727,26 @@ static const FuncDef statPushFuncdef = {
** The content to returned is determined by the parameter J
** which is one of the STAT_GET_xxxx values defined above.
**
-** If neither STAT3 nor STAT4 are enabled, then J is always
+** The stat_get(P,J) function is not available to generic SQL. It is
+** inserted as part of a manually constructed bytecode program. (See
+** the callStatGet() routine below.) It is guaranteed that the P
+** parameter will always be a poiner to a Stat4Accum object, never a
+** NULL.
+**
+** If STAT4 is not enabled, then J is always
** STAT_GET_STAT1 and is hence omitted and this routine becomes
** a one-parameter function, stat_get(P), that always returns the
** stat1 table entry information.
*/
static void statGet(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]);
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- /* STAT3 and STAT4 have a parameter on this routine. */
- int eCall = sqlite3_value_int(argv[1]);
+ Stat4Accum *p = (Stat4Accum*)tdsqlite3_value_blob(argv[0]);
+#ifdef SQLITE_ENABLE_STAT4
+ /* STAT4 has a parameter on this routine. */
+ int eCall = tdsqlite3_value_int(argv[1]);
assert( argc==2 );
assert( eCall==STAT_GET_STAT1 || eCall==STAT_GET_NEQ
|| eCall==STAT_GET_ROWID || eCall==STAT_GET_NLT
@@ -99490,26 +110781,26 @@ static void statGet(
char *z;
int i;
- char *zRet = sqlite3MallocZero( (p->nKeyCol+1)*25 );
+ char *zRet = tdsqlite3MallocZero( (p->nKeyCol+1)*25 );
if( zRet==0 ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
return;
}
- sqlite3_snprintf(24, zRet, "%llu", (u64)p->nRow);
- z = zRet + sqlite3Strlen30(zRet);
+ tdsqlite3_snprintf(24, zRet, "%llu", (u64)p->nRow);
+ z = zRet + tdsqlite3Strlen30(zRet);
for(i=0; i<p->nKeyCol; i++){
u64 nDistinct = p->current.anDLt[i] + 1;
u64 iVal = (p->nRow + nDistinct - 1) / nDistinct;
- sqlite3_snprintf(24, z, " %llu", iVal);
- z += sqlite3Strlen30(z);
+ tdsqlite3_snprintf(24, z, " %llu", iVal);
+ z += tdsqlite3Strlen30(z);
assert( p->current.anEq[i] );
}
assert( z[0]=='\0' && z>zRet );
- sqlite3_result_text(context, zRet, -1, sqlite3_free);
+ tdsqlite3_result_text(context, zRet, -1, tdsqlite3_free);
}
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
else if( eCall==STAT_GET_ROWID ){
if( p->iGet<0 ){
samplePushPrevious(p, 0);
@@ -99518,9 +110809,9 @@ static void statGet(
if( p->iGet<p->nSample ){
Stat4Sample *pS = p->a + p->iGet;
if( pS->nRowid==0 ){
- sqlite3_result_int64(context, pS->u.iRowid);
+ tdsqlite3_result_int64(context, pS->u.iRowid);
}else{
- sqlite3_result_blob(context, pS->u.aRowid, pS->nRowid,
+ tdsqlite3_result_blob(context, pS->u.aRowid, pS->nRowid,
SQLITE_TRANSIENT);
}
}
@@ -99538,53 +110829,51 @@ static void statGet(
}
}
- if( IsStat3 ){
- sqlite3_result_int64(context, (i64)aCnt[0]);
- }else{
- char *zRet = sqlite3MallocZero(p->nCol * 25);
+ {
+ char *zRet = tdsqlite3MallocZero(p->nCol * 25);
if( zRet==0 ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
}else{
int i;
char *z = zRet;
for(i=0; i<p->nCol; i++){
- sqlite3_snprintf(24, z, "%llu ", (u64)aCnt[i]);
- z += sqlite3Strlen30(z);
+ tdsqlite3_snprintf(24, z, "%llu ", (u64)aCnt[i]);
+ z += tdsqlite3Strlen30(z);
}
assert( z[0]=='\0' && z>zRet );
z[-1] = '\0';
- sqlite3_result_text(context, zRet, -1, sqlite3_free);
+ tdsqlite3_result_text(context, zRet, -1, tdsqlite3_free);
}
}
}
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
#ifndef SQLITE_DEBUG
UNUSED_PARAMETER( argc );
#endif
}
static const FuncDef statGetFuncdef = {
- 1+IsStat34, /* nArg */
+ 1+IsStat4, /* nArg */
SQLITE_UTF8, /* funcFlags */
0, /* pUserData */
0, /* pNext */
statGet, /* xSFunc */
0, /* xFinalize */
+ 0, 0, /* xValue, xInverse */
"stat_get", /* zName */
{0}
};
-static void callStatGet(Vdbe *v, int regStat4, int iParam, int regOut){
- assert( regOut!=regStat4 && regOut!=regStat4+1 );
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- sqlite3VdbeAddOp2(v, OP_Integer, iParam, regStat4+1);
+static void callStatGet(Parse *pParse, int regStat4, int iParam, int regOut){
+#ifdef SQLITE_ENABLE_STAT4
+ tdsqlite3VdbeAddOp2(pParse->pVdbe, OP_Integer, iParam, regStat4+1);
#elif SQLITE_DEBUG
assert( iParam==STAT_GET_STAT1 );
#else
UNUSED_PARAMETER( iParam );
#endif
- sqlite3VdbeAddOp4(v, OP_Function0, 0, regStat4, regOut,
- (char*)&statGetFuncdef, P4_FUNCDEF);
- sqlite3VdbeChangeP5(v, 1 + IsStat34);
+ assert( regOut!=regStat4 && regOut!=regStat4+1 );
+ tdsqlite3VdbeAddFunctionCall(pParse, 0, regStat4, regOut, 1+IsStat4,
+ &statGetFuncdef, 0);
}
/*
@@ -99599,7 +110888,7 @@ static void analyzeOneTable(
int iMem, /* Available memory locations begin here */
int iTab /* Next available cursor */
){
- sqlite3 *db = pParse->db; /* Database handle */
+ tdsqlite3 *db = pParse->db; /* Database handle */
Index *pIdx; /* An index to being analyzed */
int iIdxCur; /* Cursor open on index being analyzed */
int iTabCur; /* Table cursor */
@@ -99611,7 +110900,7 @@ static void analyzeOneTable(
int regNewRowid = iMem++; /* Rowid for the inserted record */
int regStat4 = iMem++; /* Register to hold Stat4Accum object */
int regChng = iMem++; /* Index of changed index field */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
int regRowid = iMem++; /* Rowid argument passed to stat_push() */
#endif
int regTemp = iMem++; /* Temporary use register */
@@ -99619,9 +110908,12 @@ static void analyzeOneTable(
int regIdxname = iMem++; /* Register containing index name */
int regStat1 = iMem++; /* Value for the stat column of sqlite_stat1 */
int regPrev = iMem; /* MUST BE LAST (see below) */
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ Table *pStat1 = 0;
+#endif
pParse->nMem = MAX(pParse->nMem, iMem);
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( v==0 || NEVER(pTab==0) ){
return;
}
@@ -99629,31 +110921,43 @@ static void analyzeOneTable(
/* Do not gather statistics on views or virtual tables */
return;
}
- if( sqlite3_strlike("sqlite_%", pTab->zName, 0)==0 ){
+ if( tdsqlite3_strlike("sqlite\\_%", pTab->zName, '\\')==0 ){
/* Do not gather statistics on system tables */
return;
}
- assert( sqlite3BtreeHoldsAllMutexes(db) );
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ assert( tdsqlite3BtreeHoldsAllMutexes(db) );
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
assert( iDb>=0 );
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
#ifndef SQLITE_OMIT_AUTHORIZATION
- if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0,
+ if( tdsqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0,
db->aDb[iDb].zDbSName ) ){
return;
}
#endif
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( db->xPreUpdateCallback ){
+ pStat1 = (Table*)tdsqlite3DbMallocZero(db, sizeof(Table) + 13);
+ if( pStat1==0 ) return;
+ pStat1->zName = (char*)&pStat1[1];
+ memcpy(pStat1->zName, "sqlite_stat1", 13);
+ pStat1->nCol = 3;
+ pStat1->iPKey = -1;
+ tdsqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNBLOB);
+ }
+#endif
+
/* Establish a read-lock on the table at the shared-cache level.
** Open a read-only cursor on the table. Also allocate a cursor number
** to use for scanning indexes (iIdxCur). No index cursor is opened at
** this time though. */
- sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
+ tdsqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
iTabCur = iTab++;
iIdxCur = iTab++;
pParse->nTab = MAX(pParse->nTab, iTab);
- sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead);
- sqlite3VdbeLoadString(v, regTabname, pTab->zName);
+ tdsqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead);
+ tdsqlite3VdbeLoadString(v, regTabname, pTab->zName);
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
int nCol; /* Number of columns in pIdx. "N" */
@@ -99675,7 +110979,7 @@ static void analyzeOneTable(
}
/* Populate the register containing the index name. */
- sqlite3VdbeLoadString(v, regIdxname, zIdxName);
+ tdsqlite3VdbeLoadString(v, regIdxname, zIdxName);
VdbeComment((v, "Analysis for %s.%s", pTab->zName, zIdxName));
/*
@@ -99717,9 +111021,9 @@ static void analyzeOneTable(
pParse->nMem = MAX(pParse->nMem, regPrev+nColTest);
/* Open a read-only cursor on the index being analyzed. */
- assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) );
- sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb);
- sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+ assert( iDb==tdsqlite3SchemaToIndex(db, pIdx->pSchema) );
+ tdsqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pIdx);
VdbeComment((v, "%s", pIdx->zName));
/* Invoke the stat_init() function. The arguments are:
@@ -99730,16 +111034,15 @@ static void analyzeOneTable(
** (3) the number of rows in the index,
**
**
- ** The third argument is only used for STAT3 and STAT4
+ ** The third argument is only used for STAT4
*/
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat4+3);
+#ifdef SQLITE_ENABLE_STAT4
+ tdsqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat4+3);
#endif
- sqlite3VdbeAddOp2(v, OP_Integer, nCol, regStat4+1);
- sqlite3VdbeAddOp2(v, OP_Integer, pIdx->nKeyCol, regStat4+2);
- sqlite3VdbeAddOp4(v, OP_Function0, 0, regStat4+1, regStat4,
- (char*)&statInitFuncdef, P4_FUNCDEF);
- sqlite3VdbeChangeP5(v, 2+IsStat34);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, nCol, regStat4+1);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, pIdx->nKeyCol, regStat4+2);
+ tdsqlite3VdbeAddFunctionCall(pParse, 0, regStat4+1, regStat4, 2+IsStat4,
+ &statInitFuncdef, 0);
/* Implementation of the following:
**
@@ -99749,15 +111052,15 @@ static void analyzeOneTable(
** goto next_push_0;
**
*/
- addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur);
+ addrRewind = tdsqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur);
VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng);
- addrNextRow = sqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regChng);
+ addrNextRow = tdsqlite3VdbeCurrentAddr(v);
if( nColTest>0 ){
- int endDistinctTest = sqlite3VdbeMakeLabel(v);
+ int endDistinctTest = tdsqlite3VdbeMakeLabel(pParse);
int *aGotoChng; /* Array of jump instruction addresses */
- aGotoChng = sqlite3DbMallocRawNN(db, sizeof(int)*nColTest);
+ aGotoChng = tdsqlite3DbMallocRawNN(db, sizeof(int)*nColTest);
if( aGotoChng==0 ) continue;
/*
@@ -99770,26 +111073,26 @@ static void analyzeOneTable(
** regChng = N
** goto endDistinctTest
*/
- sqlite3VdbeAddOp0(v, OP_Goto);
- addrNextRow = sqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp0(v, OP_Goto);
+ addrNextRow = tdsqlite3VdbeCurrentAddr(v);
if( nColTest==1 && pIdx->nKeyCol==1 && IsUniqueIndex(pIdx) ){
/* For a single-column UNIQUE index, once we have found a non-NULL
** row, we know that all the rest will be distinct, so skip
** subsequent distinctness tests. */
- sqlite3VdbeAddOp2(v, OP_NotNull, regPrev, endDistinctTest);
+ tdsqlite3VdbeAddOp2(v, OP_NotNull, regPrev, endDistinctTest);
VdbeCoverage(v);
}
for(i=0; i<nColTest; i++){
- char *pColl = (char*)sqlite3LocateCollSeq(pParse, pIdx->azColl[i]);
- sqlite3VdbeAddOp2(v, OP_Integer, i, regChng);
- sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regTemp);
+ char *pColl = (char*)tdsqlite3LocateCollSeq(pParse, pIdx->azColl[i]);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, i, regChng);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regTemp);
aGotoChng[i] =
- sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0, regPrev+i, pColl, P4_COLLSEQ);
- sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
+ tdsqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0, regPrev+i, pColl, P4_COLLSEQ);
+ tdsqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
VdbeCoverage(v);
}
- sqlite3VdbeAddOp2(v, OP_Integer, nColTest, regChng);
- sqlite3VdbeGoto(v, endDistinctTest);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, nColTest, regChng);
+ tdsqlite3VdbeGoto(v, endDistinctTest);
/*
@@ -99799,56 +111102,58 @@ static void analyzeOneTable(
** regPrev(1) = idx(1)
** ...
*/
- sqlite3VdbeJumpHere(v, addrNextRow-1);
+ tdsqlite3VdbeJumpHere(v, addrNextRow-1);
for(i=0; i<nColTest; i++){
- sqlite3VdbeJumpHere(v, aGotoChng[i]);
- sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regPrev+i);
+ tdsqlite3VdbeJumpHere(v, aGotoChng[i]);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regPrev+i);
}
- sqlite3VdbeResolveLabel(v, endDistinctTest);
- sqlite3DbFree(db, aGotoChng);
+ tdsqlite3VdbeResolveLabel(v, endDistinctTest);
+ tdsqlite3DbFree(db, aGotoChng);
}
/*
** chng_addr_N:
- ** regRowid = idx(rowid) // STAT34 only
- ** stat_push(P, regChng, regRowid) // 3rd parameter STAT34 only
+ ** regRowid = idx(rowid) // STAT4 only
+ ** stat_push(P, regChng, regRowid) // 3rd parameter STAT4 only
** Next csr
** if !eof(csr) goto next_row;
*/
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
assert( regRowid==(regStat4+2) );
if( HasRowid(pTab) ){
- sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid);
+ tdsqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid);
}else{
- Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
+ Index *pPk = tdsqlite3PrimaryKeyIndex(pIdx->pTable);
int j, k, regKey;
- regKey = sqlite3GetTempRange(pParse, pPk->nKeyCol);
+ regKey = tdsqlite3GetTempRange(pParse, pPk->nKeyCol);
for(j=0; j<pPk->nKeyCol; j++){
- k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]);
- assert( k>=0 && k<pTab->nCol );
- sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKey+j);
+ k = tdsqlite3TableColumnToIndex(pIdx, pPk->aiColumn[j]);
+ assert( k>=0 && k<pIdx->nColumn );
+ tdsqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKey+j);
VdbeComment((v, "%s", pTab->aCol[pPk->aiColumn[j]].zName));
}
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regKey, pPk->nKeyCol, regRowid);
- sqlite3ReleaseTempRange(pParse, regKey, pPk->nKeyCol);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regKey, pPk->nKeyCol, regRowid);
+ tdsqlite3ReleaseTempRange(pParse, regKey, pPk->nKeyCol);
}
#endif
assert( regChng==(regStat4+1) );
- sqlite3VdbeAddOp4(v, OP_Function0, 1, regStat4, regTemp,
- (char*)&statPushFuncdef, P4_FUNCDEF);
- sqlite3VdbeChangeP5(v, 2+IsStat34);
- sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); VdbeCoverage(v);
+ tdsqlite3VdbeAddFunctionCall(pParse, 1, regStat4, regTemp, 2+IsStat4,
+ &statPushFuncdef, 0);
+ tdsqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); VdbeCoverage(v);
/* Add the entry to the stat1 table. */
- callStatGet(v, regStat4, STAT_GET_STAT1, regStat1);
+ callStatGet(pParse, regStat4, STAT_GET_STAT1, regStat1);
assert( "BBB"[0]==SQLITE_AFF_TEXT );
- sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0);
- sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid);
- sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid);
- sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0);
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ tdsqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE);
+#endif
+ tdsqlite3VdbeChangeP5(v, OPFLAG_APPEND);
- /* Add the entries to the stat3 or stat4 table. */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+ /* Add the entries to the stat4 table. */
+#ifdef SQLITE_ENABLE_STAT4
{
int regEq = regStat1;
int regLt = regStat1+1;
@@ -99862,36 +111167,29 @@ static void analyzeOneTable(
pParse->nMem = MAX(pParse->nMem, regCol+nCol);
- addrNext = sqlite3VdbeCurrentAddr(v);
- callStatGet(v, regStat4, STAT_GET_ROWID, regSampleRowid);
- addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regSampleRowid);
+ addrNext = tdsqlite3VdbeCurrentAddr(v);
+ callStatGet(pParse, regStat4, STAT_GET_ROWID, regSampleRowid);
+ addrIsNull = tdsqlite3VdbeAddOp1(v, OP_IsNull, regSampleRowid);
+ VdbeCoverage(v);
+ callStatGet(pParse, regStat4, STAT_GET_NEQ, regEq);
+ callStatGet(pParse, regStat4, STAT_GET_NLT, regLt);
+ callStatGet(pParse, regStat4, STAT_GET_NDLT, regDLt);
+ tdsqlite3VdbeAddOp4Int(v, seekOp, iTabCur, addrNext, regSampleRowid, 0);
VdbeCoverage(v);
- callStatGet(v, regStat4, STAT_GET_NEQ, regEq);
- callStatGet(v, regStat4, STAT_GET_NLT, regLt);
- callStatGet(v, regStat4, STAT_GET_NDLT, regDLt);
- sqlite3VdbeAddOp4Int(v, seekOp, iTabCur, addrNext, regSampleRowid, 0);
- /* We know that the regSampleRowid row exists because it was read by
- ** the previous loop. Thus the not-found jump of seekOp will never
- ** be taken */
- VdbeCoverageNeverTaken(v);
-#ifdef SQLITE_ENABLE_STAT3
- sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iTabCur, 0, regSample);
-#else
for(i=0; i<nCol; i++){
- sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iTabCur, i, regCol+i);
+ tdsqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iTabCur, i, regCol+i);
}
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, nCol, regSample);
-#endif
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regTabname, 6, regTemp);
- sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur+1, regNewRowid);
- sqlite3VdbeAddOp3(v, OP_Insert, iStatCur+1, regTemp, regNewRowid);
- sqlite3VdbeAddOp2(v, OP_Goto, 1, addrNext); /* P1==1 for end-of-loop */
- sqlite3VdbeJumpHere(v, addrIsNull);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, nCol, regSample);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regTabname, 6, regTemp);
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur+1, regNewRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, iStatCur+1, regTemp, regNewRowid);
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 1, addrNext); /* P1==1 for end-of-loop */
+ tdsqlite3VdbeJumpHere(v, addrIsNull);
}
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
/* End of analysis */
- sqlite3VdbeJumpHere(v, addrRewind);
+ tdsqlite3VdbeJumpHere(v, addrRewind);
}
@@ -99900,15 +111198,18 @@ static void analyzeOneTable(
*/
if( pOnlyIdx==0 && needTableCnt ){
VdbeComment((v, "%s", pTab->zName));
- sqlite3VdbeAddOp2(v, OP_Count, iTabCur, regStat1);
- jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname);
+ tdsqlite3VdbeAddOp2(v, OP_Count, iTabCur, regStat1);
+ jZeroRows = tdsqlite3VdbeAddOp1(v, OP_IfNot, regStat1); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname);
assert( "BBB"[0]==SQLITE_AFF_TEXT );
- sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0);
- sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid);
- sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid);
- sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
- sqlite3VdbeJumpHere(v, jZeroRows);
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0);
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ tdsqlite3VdbeChangeP4(v, -1, (char*)pStat1, P4_TABLE);
+#endif
+ tdsqlite3VdbeJumpHere(v, jZeroRows);
}
}
@@ -99918,9 +111219,9 @@ static void analyzeOneTable(
** be loaded into internal hash tables where is can be used.
*/
static void loadAnalysis(Parse *pParse, int iDb){
- Vdbe *v = sqlite3GetVdbe(pParse);
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
if( v ){
- sqlite3VdbeAddOp1(v, OP_LoadAnalysis, iDb);
+ tdsqlite3VdbeAddOp1(v, OP_LoadAnalysis, iDb);
}
}
@@ -99928,20 +111229,20 @@ static void loadAnalysis(Parse *pParse, int iDb){
** Generate code that will do an analysis of an entire database
*/
static void analyzeDatabase(Parse *pParse, int iDb){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Schema *pSchema = db->aDb[iDb].pSchema; /* Schema of database iDb */
HashElem *k;
int iStatCur;
int iMem;
int iTab;
- sqlite3BeginWriteOperation(pParse, 0, iDb);
+ tdsqlite3BeginWriteOperation(pParse, 0, iDb);
iStatCur = pParse->nTab;
pParse->nTab += 3;
openStatTable(pParse, iDb, iStatCur, 0, 0);
iMem = pParse->nMem+1;
iTab = pParse->nTab;
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){
Table *pTab = (Table*)sqliteHashData(k);
analyzeOneTable(pParse, pTab, 0, iStatCur, iMem, iTab);
@@ -99959,9 +111260,9 @@ static void analyzeTable(Parse *pParse, Table *pTab, Index *pOnlyIdx){
int iStatCur;
assert( pTab!=0 );
- assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
- iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
- sqlite3BeginWriteOperation(pParse, 0, iDb);
+ assert( tdsqlite3BtreeHoldsAllMutexes(pParse->db) );
+ iDb = tdsqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ tdsqlite3BeginWriteOperation(pParse, 0, iDb);
iStatCur = pParse->nTab;
pParse->nTab += 3;
if( pOnlyIdx ){
@@ -99985,8 +111286,8 @@ static void analyzeTable(Parse *pParse, Table *pTab, Index *pOnlyIdx){
** Form 2 analyzes all indices the single database named.
** Form 3 analyzes all indices associated with the named table.
*/
-SQLITE_PRIVATE void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){
- sqlite3 *db = pParse->db;
+SQLITE_PRIVATE void tdsqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){
+ tdsqlite3 *db = pParse->db;
int iDb;
int i;
char *z, *zDb;
@@ -99997,8 +111298,8 @@ SQLITE_PRIVATE void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){
/* Read the database schema. If an error occurs, leave an error message
** and code in pParse and return NULL. */
- assert( sqlite3BtreeHoldsAllMutexes(pParse->db) );
- if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ assert( tdsqlite3BtreeHoldsAllMutexes(pParse->db) );
+ if( SQLITE_OK!=tdsqlite3ReadSchema(pParse) ){
return;
}
@@ -100009,40 +111310,28 @@ SQLITE_PRIVATE void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){
if( i==1 ) continue; /* Do not analyze the TEMP database */
analyzeDatabase(pParse, i);
}
- }else if( pName2->n==0 ){
- /* Form 2: Analyze the database or table named */
- iDb = sqlite3FindDb(db, pName1);
- if( iDb>=0 ){
- analyzeDatabase(pParse, iDb);
- }else{
- z = sqlite3NameFromToken(db, pName1);
- if( z ){
- if( (pIdx = sqlite3FindIndex(db, z, 0))!=0 ){
- analyzeTable(pParse, pIdx->pTable, pIdx);
- }else if( (pTab = sqlite3LocateTable(pParse, 0, z, 0))!=0 ){
- analyzeTable(pParse, pTab, 0);
- }
- sqlite3DbFree(db, z);
- }
- }
+ }else if( pName2->n==0 && (iDb = tdsqlite3FindDb(db, pName1))>=0 ){
+ /* Analyze the schema named as the argument */
+ analyzeDatabase(pParse, iDb);
}else{
- /* Form 3: Analyze the fully qualified table name */
- iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pTableName);
+ /* Form 3: Analyze the table or index named as an argument */
+ iDb = tdsqlite3TwoPartName(pParse, pName1, pName2, &pTableName);
if( iDb>=0 ){
- zDb = db->aDb[iDb].zDbSName;
- z = sqlite3NameFromToken(db, pTableName);
+ zDb = pName2->n ? db->aDb[iDb].zDbSName : 0;
+ z = tdsqlite3NameFromToken(db, pTableName);
if( z ){
- if( (pIdx = sqlite3FindIndex(db, z, zDb))!=0 ){
+ if( (pIdx = tdsqlite3FindIndex(db, z, zDb))!=0 ){
analyzeTable(pParse, pIdx->pTable, pIdx);
- }else if( (pTab = sqlite3LocateTable(pParse, 0, z, zDb))!=0 ){
+ }else if( (pTab = tdsqlite3LocateTable(pParse, 0, z, zDb))!=0 ){
analyzeTable(pParse, pTab, 0);
}
- sqlite3DbFree(db, z);
+ tdsqlite3DbFree(db, z);
}
- }
+ }
+ }
+ if( db->nSqlExec==0 && (v = tdsqlite3GetVdbe(pParse))!=0 ){
+ tdsqlite3VdbeAddOp0(v, OP_Expire);
}
- v = sqlite3GetVdbe(pParse);
- if( v ) sqlite3VdbeAddOp0(v, OP_Expire);
}
/*
@@ -100051,7 +111340,7 @@ SQLITE_PRIVATE void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){
*/
typedef struct analysisInfo analysisInfo;
struct analysisInfo {
- sqlite3 *db;
+ tdsqlite3 *db;
const char *zDatabase;
};
@@ -100072,7 +111361,7 @@ static void decodeIntArray(
int i;
tRowcnt v;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
if( z==0 ) z = "";
#else
assert( z!=0 );
@@ -100083,18 +111372,18 @@ static void decodeIntArray(
v = v*10 + c - '0';
z++;
}
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
if( aOut ) aOut[i] = v;
- if( aLog ) aLog[i] = sqlite3LogEst(v);
+ if( aLog ) aLog[i] = tdsqlite3LogEst(v);
#else
assert( aOut==0 );
UNUSED_PARAMETER(aOut);
assert( aLog!=0 );
- aLog[i] = sqlite3LogEst(v);
+ aLog[i] = tdsqlite3LogEst(v);
#endif
if( *z==' ' ) z++;
}
-#ifndef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifndef SQLITE_ENABLE_STAT4
assert( pIndex!=0 ); {
#else
if( pIndex ){
@@ -100102,16 +111391,18 @@ static void decodeIntArray(
pIndex->bUnordered = 0;
pIndex->noSkipScan = 0;
while( z[0] ){
- if( sqlite3_strglob("unordered*", z)==0 ){
+ if( tdsqlite3_strglob("unordered*", z)==0 ){
pIndex->bUnordered = 1;
- }else if( sqlite3_strglob("sz=[0-9]*", z)==0 ){
- pIndex->szIdxRow = sqlite3LogEst(sqlite3Atoi(z+3));
- }else if( sqlite3_strglob("noskipscan*", z)==0 ){
+ }else if( tdsqlite3_strglob("sz=[0-9]*", z)==0 ){
+ int sz = tdsqlite3Atoi(z+3);
+ if( sz<2 ) sz = 2;
+ pIndex->szIdxRow = tdsqlite3LogEst(sz);
+ }else if( tdsqlite3_strglob("noskipscan*", z)==0 ){
pIndex->noSkipScan = 1;
}
#ifdef SQLITE_ENABLE_COSTMULT
- else if( sqlite3_strglob("costmult=[0-9]*",z)==0 ){
- pIndex->pTable->costMult = sqlite3LogEst(sqlite3Atoi(z+9));
+ else if( tdsqlite3_strglob("costmult=[0-9]*",z)==0 ){
+ pIndex->pTable->costMult = tdsqlite3LogEst(tdsqlite3Atoi(z+9));
}
#endif
while( z[0]!=0 && z[0]!=' ' ) z++;
@@ -100143,35 +111434,39 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){
if( argv==0 || argv[0]==0 || argv[2]==0 ){
return 0;
}
- pTable = sqlite3FindTable(pInfo->db, argv[0], pInfo->zDatabase);
+ pTable = tdsqlite3FindTable(pInfo->db, argv[0], pInfo->zDatabase);
if( pTable==0 ){
return 0;
}
if( argv[1]==0 ){
pIndex = 0;
- }else if( sqlite3_stricmp(argv[0],argv[1])==0 ){
- pIndex = sqlite3PrimaryKeyIndex(pTable);
+ }else if( tdsqlite3_stricmp(argv[0],argv[1])==0 ){
+ pIndex = tdsqlite3PrimaryKeyIndex(pTable);
}else{
- pIndex = sqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase);
+ pIndex = tdsqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase);
}
z = argv[2];
if( pIndex ){
tRowcnt *aiRowEst = 0;
int nCol = pIndex->nKeyCol+1;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
/* Index.aiRowEst may already be set here if there are duplicate
** sqlite_stat1 entries for this index. In that case just clobber
** the old data with the new instead of allocating a new array. */
if( pIndex->aiRowEst==0 ){
- pIndex->aiRowEst = (tRowcnt*)sqlite3MallocZero(sizeof(tRowcnt) * nCol);
- if( pIndex->aiRowEst==0 ) sqlite3OomFault(pInfo->db);
+ pIndex->aiRowEst = (tRowcnt*)tdsqlite3MallocZero(sizeof(tRowcnt) * nCol);
+ if( pIndex->aiRowEst==0 ) tdsqlite3OomFault(pInfo->db);
}
aiRowEst = pIndex->aiRowEst;
#endif
pIndex->bUnordered = 0;
decodeIntArray((char*)z, nCol, aiRowEst, pIndex->aiRowLogEst, pIndex);
- if( pIndex->pPartIdxWhere==0 ) pTable->nRowLogEst = pIndex->aiRowLogEst[0];
+ pIndex->hasStat1 = 1;
+ if( pIndex->pPartIdxWhere==0 ){
+ pTable->nRowLogEst = pIndex->aiRowLogEst[0];
+ pTable->tabFlags |= TF_HasStat1;
+ }
}else{
Index fakeIdx;
fakeIdx.szIdxRow = pTable->szTabRow;
@@ -100180,6 +111475,7 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){
#endif
decodeIntArray((char*)z, 1, 0, &pTable->nRowLogEst, &fakeIdx);
pTable->szTabRow = fakeIdx.szIdxRow;
+ pTable->tabFlags |= TF_HasStat1;
}
return 0;
@@ -100189,15 +111485,15 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){
** If the Index.aSample variable is not NULL, delete the aSample[] array
** and its contents.
*/
-SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+SQLITE_PRIVATE void tdsqlite3DeleteIndexSamples(tdsqlite3 *db, Index *pIdx){
+#ifdef SQLITE_ENABLE_STAT4
if( pIdx->aSample ){
int j;
for(j=0; j<pIdx->nSample; j++){
IndexSample *p = &pIdx->aSample[j];
- sqlite3DbFree(db, p->p);
+ tdsqlite3DbFree(db, p->p);
}
- sqlite3DbFree(db, pIdx->aSample);
+ tdsqlite3DbFree(db, pIdx->aSample);
}
if( db && db->pnBytesFreed==0 ){
pIdx->nSample = 0;
@@ -100206,10 +111502,10 @@ SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){
#else
UNUSED_PARAMETER(db);
UNUSED_PARAMETER(pIdx);
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
}
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
/*
** Populate the pIdx->aAvgEq[] array based on the samples currently
** stored in pIdx->aSample[].
@@ -100260,7 +111556,7 @@ static void initAvgEq(Index *pIdx){
}
}
- if( nDist100>nSum100 ){
+ if( nDist100>nSum100 && sumEq<nRow ){
avgEq = ((i64)100 * (nRow - sumEq))/(nDist100 - nSum100);
}
if( avgEq==0 ) avgEq = 1;
@@ -100274,25 +111570,24 @@ static void initAvgEq(Index *pIdx){
** is supplied instead, find the PRIMARY KEY index for that table.
*/
static Index *findIndexOrPrimaryKey(
- sqlite3 *db,
+ tdsqlite3 *db,
const char *zName,
const char *zDb
){
- Index *pIdx = sqlite3FindIndex(db, zName, zDb);
+ Index *pIdx = tdsqlite3FindIndex(db, zName, zDb);
if( pIdx==0 ){
- Table *pTab = sqlite3FindTable(db, zName, zDb);
- if( pTab && !HasRowid(pTab) ) pIdx = sqlite3PrimaryKeyIndex(pTab);
+ Table *pTab = tdsqlite3FindTable(db, zName, zDb);
+ if( pTab && !HasRowid(pTab) ) pIdx = tdsqlite3PrimaryKeyIndex(pTab);
}
return pIdx;
}
/*
-** Load the content from either the sqlite_stat4 or sqlite_stat3 table
+** Load the content from either the sqlite_stat4
** into the relevant Index.aSample[] arrays.
**
** Arguments zSql1 and zSql2 must point to SQL statements that return
-** data equivalent to the following (statements are different for stat3,
-** see the caller of this function for details):
+** data equivalent to the following:
**
** zSql1: SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx
** zSql2: SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4
@@ -100300,28 +111595,27 @@ static Index *findIndexOrPrimaryKey(
** where %Q is replaced with the database name before the SQL is executed.
*/
static int loadStatTbl(
- sqlite3 *db, /* Database handle */
- int bStat3, /* Assume single column records only */
+ tdsqlite3 *db, /* Database handle */
const char *zSql1, /* SQL statement 1 (see above) */
const char *zSql2, /* SQL statement 2 (see above) */
const char *zDb /* Database name (e.g. "main") */
){
int rc; /* Result codes from subroutines */
- sqlite3_stmt *pStmt = 0; /* An SQL statement being run */
+ tdsqlite3_stmt *pStmt = 0; /* An SQL statement being run */
char *zSql; /* Text of the SQL statement */
Index *pPrevIdx = 0; /* Previous index in the loop */
IndexSample *pSample; /* A slot in pIdx->aSample[] */
assert( db->lookaside.bDisable );
- zSql = sqlite3MPrintf(db, zSql1, zDb);
+ zSql = tdsqlite3MPrintf(db, zSql1, zDb);
if( !zSql ){
return SQLITE_NOMEM_BKPT;
}
- rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
- sqlite3DbFree(db, zSql);
+ rc = tdsqlite3_prepare(db, zSql, -1, &pStmt, 0);
+ tdsqlite3DbFree(db, zSql);
if( rc ) return rc;
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ while( tdsqlite3_step(pStmt)==SQLITE_ROW ){
int nIdxCol = 1; /* Number of columns in stat4 records */
char *zIndex; /* Index name */
@@ -100331,30 +111625,26 @@ static int loadStatTbl(
int i; /* Bytes of space required */
tRowcnt *pSpace;
- zIndex = (char *)sqlite3_column_text(pStmt, 0);
+ zIndex = (char *)tdsqlite3_column_text(pStmt, 0);
if( zIndex==0 ) continue;
- nSample = sqlite3_column_int(pStmt, 1);
+ nSample = tdsqlite3_column_int(pStmt, 1);
pIdx = findIndexOrPrimaryKey(db, zIndex, zDb);
- assert( pIdx==0 || bStat3 || pIdx->nSample==0 );
- /* Index.nSample is non-zero at this point if data has already been
- ** loaded from the stat4 table. In this case ignore stat3 data. */
- if( pIdx==0 || pIdx->nSample ) continue;
- if( bStat3==0 ){
- assert( !HasRowid(pIdx->pTable) || pIdx->nColumn==pIdx->nKeyCol+1 );
- if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){
- nIdxCol = pIdx->nKeyCol;
- }else{
- nIdxCol = pIdx->nColumn;
- }
+ assert( pIdx==0 || pIdx->nSample==0 );
+ if( pIdx==0 ) continue;
+ assert( !HasRowid(pIdx->pTable) || pIdx->nColumn==pIdx->nKeyCol+1 );
+ if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){
+ nIdxCol = pIdx->nKeyCol;
+ }else{
+ nIdxCol = pIdx->nColumn;
}
pIdx->nSampleCol = nIdxCol;
nByte = sizeof(IndexSample) * nSample;
nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample;
nByte += nIdxCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */
- pIdx->aSample = sqlite3DbMallocZero(db, nByte);
+ pIdx->aSample = tdsqlite3DbMallocZero(db, nByte);
if( pIdx->aSample==0 ){
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
return SQLITE_NOMEM_BKPT;
}
pSpace = (tRowcnt*)&pIdx->aSample[nSample];
@@ -100366,99 +111656,91 @@ static int loadStatTbl(
}
assert( ((u8*)pSpace)-nByte==(u8*)(pIdx->aSample) );
}
- rc = sqlite3_finalize(pStmt);
+ rc = tdsqlite3_finalize(pStmt);
if( rc ) return rc;
- zSql = sqlite3MPrintf(db, zSql2, zDb);
+ zSql = tdsqlite3MPrintf(db, zSql2, zDb);
if( !zSql ){
return SQLITE_NOMEM_BKPT;
}
- rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
- sqlite3DbFree(db, zSql);
+ rc = tdsqlite3_prepare(db, zSql, -1, &pStmt, 0);
+ tdsqlite3DbFree(db, zSql);
if( rc ) return rc;
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ while( tdsqlite3_step(pStmt)==SQLITE_ROW ){
char *zIndex; /* Index name */
Index *pIdx; /* Pointer to the index object */
int nCol = 1; /* Number of columns in index */
- zIndex = (char *)sqlite3_column_text(pStmt, 0);
+ zIndex = (char *)tdsqlite3_column_text(pStmt, 0);
if( zIndex==0 ) continue;
pIdx = findIndexOrPrimaryKey(db, zIndex, zDb);
if( pIdx==0 ) continue;
/* This next condition is true if data has already been loaded from
- ** the sqlite_stat4 table. In this case ignore stat3 data. */
+ ** the sqlite_stat4 table. */
nCol = pIdx->nSampleCol;
- if( bStat3 && nCol>1 ) continue;
if( pIdx!=pPrevIdx ){
initAvgEq(pPrevIdx);
pPrevIdx = pIdx;
}
pSample = &pIdx->aSample[pIdx->nSample];
- decodeIntArray((char*)sqlite3_column_text(pStmt,1),nCol,pSample->anEq,0,0);
- decodeIntArray((char*)sqlite3_column_text(pStmt,2),nCol,pSample->anLt,0,0);
- decodeIntArray((char*)sqlite3_column_text(pStmt,3),nCol,pSample->anDLt,0,0);
+ decodeIntArray((char*)tdsqlite3_column_text(pStmt,1),nCol,pSample->anEq,0,0);
+ decodeIntArray((char*)tdsqlite3_column_text(pStmt,2),nCol,pSample->anLt,0,0);
+ decodeIntArray((char*)tdsqlite3_column_text(pStmt,3),nCol,pSample->anDLt,0,0);
/* Take a copy of the sample. Add two 0x00 bytes the end of the buffer.
** This is in case the sample record is corrupted. In that case, the
- ** sqlite3VdbeRecordCompare() may read up to two varints past the
+ ** tdsqlite3VdbeRecordCompare() may read up to two varints past the
** end of the allocated buffer before it realizes it is dealing with
** a corrupt record. Adding the two 0x00 bytes prevents this from causing
** a buffer overread. */
- pSample->n = sqlite3_column_bytes(pStmt, 4);
- pSample->p = sqlite3DbMallocZero(db, pSample->n + 2);
+ pSample->n = tdsqlite3_column_bytes(pStmt, 4);
+ pSample->p = tdsqlite3DbMallocZero(db, pSample->n + 2);
if( pSample->p==0 ){
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
return SQLITE_NOMEM_BKPT;
}
- memcpy(pSample->p, sqlite3_column_blob(pStmt, 4), pSample->n);
+ if( pSample->n ){
+ memcpy(pSample->p, tdsqlite3_column_blob(pStmt, 4), pSample->n);
+ }
pIdx->nSample++;
}
- rc = sqlite3_finalize(pStmt);
+ rc = tdsqlite3_finalize(pStmt);
if( rc==SQLITE_OK ) initAvgEq(pPrevIdx);
return rc;
}
/*
-** Load content from the sqlite_stat4 and sqlite_stat3 tables into
+** Load content from the sqlite_stat4 table into
** the Index.aSample[] arrays of all indices.
*/
-static int loadStat4(sqlite3 *db, const char *zDb){
+static int loadStat4(tdsqlite3 *db, const char *zDb){
int rc = SQLITE_OK; /* Result codes from subroutines */
assert( db->lookaside.bDisable );
- if( sqlite3FindTable(db, "sqlite_stat4", zDb) ){
- rc = loadStatTbl(db, 0,
+ if( tdsqlite3FindTable(db, "sqlite_stat4", zDb) ){
+ rc = loadStatTbl(db,
"SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx",
"SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4",
zDb
);
}
-
- if( rc==SQLITE_OK && sqlite3FindTable(db, "sqlite_stat3", zDb) ){
- rc = loadStatTbl(db, 1,
- "SELECT idx,count(*) FROM %Q.sqlite_stat3 GROUP BY idx",
- "SELECT idx,neq,nlt,ndlt,sqlite_record(sample) FROM %Q.sqlite_stat3",
- zDb
- );
- }
-
return rc;
}
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
/*
-** Load the content of the sqlite_stat1 and sqlite_stat3/4 tables. The
+** Load the content of the sqlite_stat1 and sqlite_stat4 tables. The
** contents of sqlite_stat1 are used to populate the Index.aiRowEst[]
-** arrays. The contents of sqlite_stat3/4 are used to populate the
+** arrays. The contents of sqlite_stat4 are used to populate the
** Index.aSample[] arrays.
**
** If the sqlite_stat1 table is not present in the database, SQLITE_ERROR
-** is returned. In this case, even if SQLITE_ENABLE_STAT3/4 was defined
-** during compilation and the sqlite_stat3/4 table is present, no data is
+** is returned. In this case, even if SQLITE_ENABLE_STAT4 was defined
+** during compilation and the sqlite_stat4 table is present, no data is
** read from it.
**
-** If SQLITE_ENABLE_STAT3/4 was defined during compilation and the
+** If SQLITE_ENABLE_STAT4 was defined during compilation and the
** sqlite_stat4 table is not present in the database, SQLITE_ERROR is
** returned. However, in this case, data is read from the sqlite_stat1
** table (if it is present) before returning.
@@ -100467,22 +111749,27 @@ static int loadStat4(sqlite3 *db, const char *zDb){
** This means if the caller does not care about other errors, the return
** code may be ignored.
*/
-SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){
+SQLITE_PRIVATE int tdsqlite3AnalysisLoad(tdsqlite3 *db, int iDb){
analysisInfo sInfo;
HashElem *i;
char *zSql;
int rc = SQLITE_OK;
+ Schema *pSchema = db->aDb[iDb].pSchema;
assert( iDb>=0 && iDb<db->nDb );
assert( db->aDb[iDb].pBt!=0 );
/* Clear any prior statistics */
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
- for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
+ for(i=sqliteHashFirst(&pSchema->tblHash); i; i=sqliteHashNext(i)){
+ Table *pTab = sqliteHashData(i);
+ pTab->tabFlags &= ~TF_HasStat1;
+ }
+ for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){
Index *pIdx = sqliteHashData(i);
- pIdx->aiRowLogEst[0] = 0;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- sqlite3DeleteIndexSamples(db, pIdx);
+ pIdx->hasStat1 = 0;
+#ifdef SQLITE_ENABLE_STAT4
+ tdsqlite3DeleteIndexSamples(db, pIdx);
pIdx->aSample = 0;
#endif
}
@@ -100490,40 +111777,40 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){
/* Load new statistics out of the sqlite_stat1 table */
sInfo.db = db;
sInfo.zDatabase = db->aDb[iDb].zDbSName;
- if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){
- zSql = sqlite3MPrintf(db,
+ if( tdsqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){
+ zSql = tdsqlite3MPrintf(db,
"SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase);
if( zSql==0 ){
rc = SQLITE_NOMEM_BKPT;
}else{
- rc = sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0);
- sqlite3DbFree(db, zSql);
+ rc = tdsqlite3_exec(db, zSql, analysisLoader, &sInfo, 0);
+ tdsqlite3DbFree(db, zSql);
}
}
/* Set appropriate defaults on all indexes not in the sqlite_stat1 table */
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
- for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
+ for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){
Index *pIdx = sqliteHashData(i);
- if( pIdx->aiRowLogEst[0]==0 ) sqlite3DefaultRowEst(pIdx);
+ if( !pIdx->hasStat1 ) tdsqlite3DefaultRowEst(pIdx);
}
/* Load the statistics from the sqlite_stat4 table. */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- if( rc==SQLITE_OK && OptimizationEnabled(db, SQLITE_Stat34) ){
- db->lookaside.bDisable++;
+#ifdef SQLITE_ENABLE_STAT4
+ if( rc==SQLITE_OK ){
+ DisableLookaside;
rc = loadStat4(db, sInfo.zDatabase);
- db->lookaside.bDisable--;
+ EnableLookaside;
}
- for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){
+ for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){
Index *pIdx = sqliteHashData(i);
- sqlite3_free(pIdx->aiRowEst);
+ tdsqlite3_free(pIdx->aiRowEst);
pIdx->aiRowEst = 0;
}
#endif
if( rc==SQLITE_NOMEM ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
}
return rc;
}
@@ -100572,7 +111859,7 @@ static int resolveAttachExpr(NameContext *pName, Expr *pExpr)
int rc = SQLITE_OK;
if( pExpr ){
if( pExpr->op!=TK_ID ){
- rc = sqlite3ResolveExprNames(pName, pExpr);
+ rc = tdsqlite3ResolveExprNames(pName, pExpr);
}else{
pExpr->op = TK_STRING;
}
@@ -100590,186 +111877,218 @@ static int resolveAttachExpr(NameContext *pName, Expr *pExpr)
**
** If the optional "KEY z" syntax is omitted, an SQL NULL is passed as the
** third argument.
+**
+** If the db->init.reopenMemdb flags is set, then instead of attaching a
+** new database, close the database on db->init.iDb and reopen it as an
+** empty MemDB.
*/
static void attachFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
int i;
int rc = 0;
- sqlite3 *db = sqlite3_context_db_handle(context);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
const char *zName;
const char *zFile;
char *zPath = 0;
char *zErr = 0;
unsigned int flags;
- Db *aNew;
+ Db *aNew; /* New array of Db pointers */
+ Db *pNew; /* Db object for the newly attached database */
char *zErrDyn = 0;
- sqlite3_vfs *pVfs;
+ tdsqlite3_vfs *pVfs;
UNUSED_PARAMETER(NotUsed);
-
- zFile = (const char *)sqlite3_value_text(argv[0]);
- zName = (const char *)sqlite3_value_text(argv[1]);
+ zFile = (const char *)tdsqlite3_value_text(argv[0]);
+ zName = (const char *)tdsqlite3_value_text(argv[1]);
if( zFile==0 ) zFile = "";
if( zName==0 ) zName = "";
- /* Check for the following errors:
- **
- ** * Too many attached databases,
- ** * Transaction currently open
- ** * Specified database name already being used.
- */
- if( db->nDb>=db->aLimit[SQLITE_LIMIT_ATTACHED]+2 ){
- zErrDyn = sqlite3MPrintf(db, "too many attached databases - max %d",
- db->aLimit[SQLITE_LIMIT_ATTACHED]
- );
- goto attach_error;
- }
- if( !db->autoCommit ){
- zErrDyn = sqlite3MPrintf(db, "cannot ATTACH database within transaction");
- goto attach_error;
- }
- for(i=0; i<db->nDb; i++){
- char *z = db->aDb[i].zDbSName;
- assert( z && zName );
- if( sqlite3StrICmp(z, zName)==0 ){
- zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName);
+#ifdef SQLITE_ENABLE_DESERIALIZE
+# define REOPEN_AS_MEMDB(db) (db->init.reopenMemdb)
+#else
+# define REOPEN_AS_MEMDB(db) (0)
+#endif
+
+ if( REOPEN_AS_MEMDB(db) ){
+ /* This is not a real ATTACH. Instead, this routine is being called
+ ** from tdsqlite3_deserialize() to close database db->init.iDb and
+ ** reopen it as a MemDB */
+ pVfs = tdsqlite3_vfs_find("memdb");
+ if( pVfs==0 ) return;
+ pNew = &db->aDb[db->init.iDb];
+ if( pNew->pBt ) tdsqlite3BtreeClose(pNew->pBt);
+ pNew->pBt = 0;
+ pNew->pSchema = 0;
+ rc = tdsqlite3BtreeOpen(pVfs, "x\0", db, &pNew->pBt, 0, SQLITE_OPEN_MAIN_DB);
+ }else{
+ /* This is a real ATTACH
+ **
+ ** Check for the following errors:
+ **
+ ** * Too many attached databases,
+ ** * Transaction currently open
+ ** * Specified database name already being used.
+ */
+ if( db->nDb>=db->aLimit[SQLITE_LIMIT_ATTACHED]+2 ){
+ zErrDyn = tdsqlite3MPrintf(db, "too many attached databases - max %d",
+ db->aLimit[SQLITE_LIMIT_ATTACHED]
+ );
goto attach_error;
}
+ for(i=0; i<db->nDb; i++){
+ char *z = db->aDb[i].zDbSName;
+ assert( z && zName );
+ if( tdsqlite3StrICmp(z, zName)==0 ){
+ zErrDyn = tdsqlite3MPrintf(db, "database %s is already in use", zName);
+ goto attach_error;
+ }
+ }
+
+ /* Allocate the new entry in the db->aDb[] array and initialize the schema
+ ** hash tables.
+ */
+ if( db->aDb==db->aDbStatic ){
+ aNew = tdsqlite3DbMallocRawNN(db, sizeof(db->aDb[0])*3 );
+ if( aNew==0 ) return;
+ memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2);
+ }else{
+ aNew = tdsqlite3DbRealloc(db, db->aDb, sizeof(db->aDb[0])*(db->nDb+1) );
+ if( aNew==0 ) return;
+ }
+ db->aDb = aNew;
+ pNew = &db->aDb[db->nDb];
+ memset(pNew, 0, sizeof(*pNew));
+
+ /* Open the database file. If the btree is successfully opened, use
+ ** it to obtain the database schema. At this point the schema may
+ ** or may not be initialized.
+ */
+ flags = db->openFlags;
+ rc = tdsqlite3ParseUri(db->pVfs->zName, zFile, &flags, &pVfs, &zPath, &zErr);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_NOMEM ) tdsqlite3OomFault(db);
+ tdsqlite3_result_error(context, zErr, -1);
+ tdsqlite3_free(zErr);
+ return;
+ }
+ assert( pVfs );
+ flags |= SQLITE_OPEN_MAIN_DB;
+ rc = tdsqlite3BtreeOpen(pVfs, zPath, db, &pNew->pBt, 0, flags);
+ db->nDb++;
+ pNew->zDbSName = tdsqlite3DbStrDup(db, zName);
}
-
- /* Allocate the new entry in the db->aDb[] array and initialize the schema
- ** hash tables.
- */
- if( db->aDb==db->aDbStatic ){
- aNew = sqlite3DbMallocRawNN(db, sizeof(db->aDb[0])*3 );
- if( aNew==0 ) return;
- memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2);
- }else{
- aNew = sqlite3DbRealloc(db, db->aDb, sizeof(db->aDb[0])*(db->nDb+1) );
- if( aNew==0 ) return;
- }
- db->aDb = aNew;
- aNew = &db->aDb[db->nDb];
- memset(aNew, 0, sizeof(*aNew));
-
- /* Open the database file. If the btree is successfully opened, use
- ** it to obtain the database schema. At this point the schema may
- ** or may not be initialized.
- */
- flags = db->openFlags;
- rc = sqlite3ParseUri(db->pVfs->zName, zFile, &flags, &pVfs, &zPath, &zErr);
- if( rc!=SQLITE_OK ){
- if( rc==SQLITE_NOMEM ) sqlite3OomFault(db);
- sqlite3_result_error(context, zErr, -1);
- sqlite3_free(zErr);
- return;
- }
- assert( pVfs );
- flags |= SQLITE_OPEN_MAIN_DB;
- rc = sqlite3BtreeOpen(pVfs, zPath, db, &aNew->pBt, 0, flags);
- sqlite3_free( zPath );
- db->nDb++;
+ db->noSharedCache = 0;
if( rc==SQLITE_CONSTRAINT ){
rc = SQLITE_ERROR;
- zErrDyn = sqlite3MPrintf(db, "database is already attached");
+ zErrDyn = tdsqlite3MPrintf(db, "database is already attached");
}else if( rc==SQLITE_OK ){
Pager *pPager;
- aNew->pSchema = sqlite3SchemaGet(db, aNew->pBt);
- if( !aNew->pSchema ){
+ pNew->pSchema = tdsqlite3SchemaGet(db, pNew->pBt);
+ if( !pNew->pSchema ){
rc = SQLITE_NOMEM_BKPT;
- }else if( aNew->pSchema->file_format && aNew->pSchema->enc!=ENC(db) ){
- zErrDyn = sqlite3MPrintf(db,
+ }else if( pNew->pSchema->file_format && pNew->pSchema->enc!=ENC(db) ){
+ zErrDyn = tdsqlite3MPrintf(db,
"attached databases must use the same text encoding as main database");
rc = SQLITE_ERROR;
}
- sqlite3BtreeEnter(aNew->pBt);
- pPager = sqlite3BtreePager(aNew->pBt);
- sqlite3PagerLockingMode(pPager, db->dfltLockMode);
- sqlite3BtreeSecureDelete(aNew->pBt,
- sqlite3BtreeSecureDelete(db->aDb[0].pBt,-1) );
+ tdsqlite3BtreeEnter(pNew->pBt);
+ pPager = tdsqlite3BtreePager(pNew->pBt);
+ tdsqlite3PagerLockingMode(pPager, db->dfltLockMode);
+ tdsqlite3BtreeSecureDelete(pNew->pBt,
+ tdsqlite3BtreeSecureDelete(db->aDb[0].pBt,-1) );
#ifndef SQLITE_OMIT_PAGER_PRAGMAS
- sqlite3BtreeSetPagerFlags(aNew->pBt,
+ tdsqlite3BtreeSetPagerFlags(pNew->pBt,
PAGER_SYNCHRONOUS_FULL | (db->flags & PAGER_FLAGS_MASK));
#endif
- sqlite3BtreeLeave(aNew->pBt);
+ tdsqlite3BtreeLeave(pNew->pBt);
}
- aNew->safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1;
- aNew->zDbSName = sqlite3DbStrDup(db, zName);
- if( rc==SQLITE_OK && aNew->zDbSName==0 ){
+ pNew->safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1;
+ if( rc==SQLITE_OK && pNew->zDbSName==0 ){
rc = SQLITE_NOMEM_BKPT;
}
#ifdef SQLITE_HAS_CODEC
if( rc==SQLITE_OK ){
- extern int sqlite3CodecAttach(sqlite3*, int, const void*, int);
- extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*);
+ extern int tdsqlite3CodecAttach(tdsqlite3*, int, const void*, int);
+ extern void tdsqlite3CodecGetKey(tdsqlite3*, int, void**, int*);
int nKey;
char *zKey;
- int t = sqlite3_value_type(argv[2]);
+ int t = tdsqlite3_value_type(argv[2]);
switch( t ){
case SQLITE_INTEGER:
case SQLITE_FLOAT:
- zErrDyn = sqlite3DbStrDup(db, "Invalid key value");
+ zErrDyn = tdsqlite3DbStrDup(db, "Invalid key value");
rc = SQLITE_ERROR;
break;
case SQLITE_TEXT:
case SQLITE_BLOB:
- nKey = sqlite3_value_bytes(argv[2]);
- zKey = (char *)sqlite3_value_blob(argv[2]);
- rc = sqlite3CodecAttach(db, db->nDb-1, zKey, nKey);
+ nKey = tdsqlite3_value_bytes(argv[2]);
+ zKey = (char *)tdsqlite3_value_blob(argv[2]);
+ rc = tdsqlite3CodecAttach(db, db->nDb-1, zKey, nKey);
break;
case SQLITE_NULL:
- /* No key specified. Use the key from the main database */
- sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey);
- if( nKey || sqlite3BtreeGetOptimalReserve(db->aDb[0].pBt)>0 ){
- rc = sqlite3CodecAttach(db, db->nDb-1, zKey, nKey);
+ /* No key specified. Use the key from URI filename, or if none,
+ ** use the key from the main database. */
+ if( tdsqlite3CodecQueryParameters(db, zName, zPath)==0 ){
+ tdsqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey);
+ if( nKey || tdsqlite3BtreeGetOptimalReserve(db->aDb[0].pBt)>0 ){
+ rc = tdsqlite3CodecAttach(db, db->nDb-1, zKey, nKey);
+ }
}
break;
}
}
#endif
+ tdsqlite3_free( zPath );
/* If the file was opened successfully, read the schema for the new database.
** If this fails, or if opening the file failed, then close the file and
- ** remove the entry from the db->aDb[] array. i.e. put everything back the way
- ** we found it.
+ ** remove the entry from the db->aDb[] array. i.e. put everything back the
+ ** way we found it.
*/
if( rc==SQLITE_OK ){
- sqlite3BtreeEnterAll(db);
- rc = sqlite3Init(db, &zErrDyn);
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3BtreeEnterAll(db);
+ db->init.iDb = 0;
+ db->mDbFlags &= ~(DBFLAG_SchemaKnownOk);
+ if( !REOPEN_AS_MEMDB(db) ){
+ rc = tdsqlite3Init(db, &zErrDyn);
+ }
+ tdsqlite3BtreeLeaveAll(db);
+ assert( zErrDyn==0 || rc!=SQLITE_OK );
}
#ifdef SQLITE_USER_AUTHENTICATION
- if( rc==SQLITE_OK ){
+ if( rc==SQLITE_OK && !REOPEN_AS_MEMDB(db) ){
u8 newAuth = 0;
- rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth);
+ rc = tdsqlite3UserAuthCheckLogin(db, zName, &newAuth);
if( newAuth<db->auth.authLevel ){
rc = SQLITE_AUTH_USER;
}
}
#endif
if( rc ){
- int iDb = db->nDb - 1;
- assert( iDb>=2 );
- if( db->aDb[iDb].pBt ){
- sqlite3BtreeClose(db->aDb[iDb].pBt);
- db->aDb[iDb].pBt = 0;
- db->aDb[iDb].pSchema = 0;
- }
- sqlite3ResetAllSchemasOfConnection(db);
- db->nDb = iDb;
- if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
- sqlite3OomFault(db);
- sqlite3DbFree(db, zErrDyn);
- zErrDyn = sqlite3MPrintf(db, "out of memory");
- }else if( zErrDyn==0 ){
- zErrDyn = sqlite3MPrintf(db, "unable to open database: %s", zFile);
+ if( !REOPEN_AS_MEMDB(db) ){
+ int iDb = db->nDb - 1;
+ assert( iDb>=2 );
+ if( db->aDb[iDb].pBt ){
+ tdsqlite3BtreeClose(db->aDb[iDb].pBt);
+ db->aDb[iDb].pBt = 0;
+ db->aDb[iDb].pSchema = 0;
+ }
+ tdsqlite3ResetAllSchemasOfConnection(db);
+ db->nDb = iDb;
+ if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
+ tdsqlite3OomFault(db);
+ tdsqlite3DbFree(db, zErrDyn);
+ zErrDyn = tdsqlite3MPrintf(db, "out of memory");
+ }else if( zErrDyn==0 ){
+ zErrDyn = tdsqlite3MPrintf(db, "unable to open database: %s", zFile);
+ }
}
goto attach_error;
}
@@ -100779,10 +112098,10 @@ static void attachFunc(
attach_error:
/* Return an error if we get here */
if( zErrDyn ){
- sqlite3_result_error(context, zErrDyn, -1);
- sqlite3DbFree(db, zErrDyn);
+ tdsqlite3_result_error(context, zErrDyn, -1);
+ tdsqlite3DbFree(db, zErrDyn);
}
- if( rc ) sqlite3_result_error_code(context, rc);
+ if( rc ) tdsqlite3_result_error_code(context, rc);
}
/*
@@ -100794,14 +112113,15 @@ attach_error:
** SELECT sqlite_detach(x)
*/
static void detachFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- const char *zName = (const char *)sqlite3_value_text(argv[0]);
- sqlite3 *db = sqlite3_context_db_handle(context);
+ const char *zName = (const char *)tdsqlite3_value_text(argv[0]);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
int i;
Db *pDb = 0;
+ HashElem *pEntry;
char zErr[128];
UNUSED_PARAMETER(NotUsed);
@@ -100810,35 +112130,42 @@ static void detachFunc(
for(i=0; i<db->nDb; i++){
pDb = &db->aDb[i];
if( pDb->pBt==0 ) continue;
- if( sqlite3StrICmp(pDb->zDbSName, zName)==0 ) break;
+ if( tdsqlite3StrICmp(pDb->zDbSName, zName)==0 ) break;
}
if( i>=db->nDb ){
- sqlite3_snprintf(sizeof(zErr),zErr, "no such database: %s", zName);
+ tdsqlite3_snprintf(sizeof(zErr),zErr, "no such database: %s", zName);
goto detach_error;
}
if( i<2 ){
- sqlite3_snprintf(sizeof(zErr),zErr, "cannot detach database %s", zName);
+ tdsqlite3_snprintf(sizeof(zErr),zErr, "cannot detach database %s", zName);
goto detach_error;
}
- if( !db->autoCommit ){
- sqlite3_snprintf(sizeof(zErr), zErr,
- "cannot DETACH database within transaction");
+ if( tdsqlite3BtreeIsInReadTrans(pDb->pBt) || tdsqlite3BtreeIsInBackup(pDb->pBt) ){
+ tdsqlite3_snprintf(sizeof(zErr),zErr, "database %s is locked", zName);
goto detach_error;
}
- if( sqlite3BtreeIsInReadTrans(pDb->pBt) || sqlite3BtreeIsInBackup(pDb->pBt) ){
- sqlite3_snprintf(sizeof(zErr),zErr, "database %s is locked", zName);
- goto detach_error;
+
+ /* If any TEMP triggers reference the schema being detached, move those
+ ** triggers to reference the TEMP schema itself. */
+ assert( db->aDb[1].pSchema );
+ pEntry = sqliteHashFirst(&db->aDb[1].pSchema->trigHash);
+ while( pEntry ){
+ Trigger *pTrig = (Trigger*)sqliteHashData(pEntry);
+ if( pTrig->pTabSchema==pDb->pSchema ){
+ pTrig->pTabSchema = pTrig->pSchema;
+ }
+ pEntry = sqliteHashNext(pEntry);
}
- sqlite3BtreeClose(pDb->pBt);
+ tdsqlite3BtreeClose(pDb->pBt);
pDb->pBt = 0;
pDb->pSchema = 0;
- sqlite3CollapseDatabaseArray(db);
+ tdsqlite3CollapseDatabaseArray(db);
return;
detach_error:
- sqlite3_result_error(context, zErr, -1);
+ tdsqlite3_result_error(context, zErr, -1);
}
/*
@@ -100857,7 +112184,7 @@ static void codeAttach(
int rc;
NameContext sName;
Vdbe *v;
- sqlite3* db = pParse->db;
+ tdsqlite3* db = pParse->db;
int regArgs;
if( pParse->nErr ) goto attach_end;
@@ -100880,7 +112207,7 @@ static void codeAttach(
}else{
zAuthArg = 0;
}
- rc = sqlite3AuthCheck(pParse, type, zAuthArg, 0, 0);
+ rc = tdsqlite3AuthCheck(pParse, type, zAuthArg, 0, 0);
if(rc!=SQLITE_OK ){
goto attach_end;
}
@@ -100888,30 +112215,27 @@ static void codeAttach(
#endif /* SQLITE_OMIT_AUTHORIZATION */
- v = sqlite3GetVdbe(pParse);
- regArgs = sqlite3GetTempRange(pParse, 4);
- sqlite3ExprCode(pParse, pFilename, regArgs);
- sqlite3ExprCode(pParse, pDbname, regArgs+1);
- sqlite3ExprCode(pParse, pKey, regArgs+2);
+ v = tdsqlite3GetVdbe(pParse);
+ regArgs = tdsqlite3GetTempRange(pParse, 4);
+ tdsqlite3ExprCode(pParse, pFilename, regArgs);
+ tdsqlite3ExprCode(pParse, pDbname, regArgs+1);
+ tdsqlite3ExprCode(pParse, pKey, regArgs+2);
assert( v || db->mallocFailed );
if( v ){
- sqlite3VdbeAddOp4(v, OP_Function0, 0, regArgs+3-pFunc->nArg, regArgs+3,
- (char *)pFunc, P4_FUNCDEF);
- assert( pFunc->nArg==-1 || (pFunc->nArg&0xff)==pFunc->nArg );
- sqlite3VdbeChangeP5(v, (u8)(pFunc->nArg));
-
+ tdsqlite3VdbeAddFunctionCall(pParse, 0, regArgs+3-pFunc->nArg, regArgs+3,
+ pFunc->nArg, pFunc, 0);
/* Code an OP_Expire. For an ATTACH statement, set P1 to true (expire this
** statement only). For DETACH, set it to false (expire all existing
** statements).
*/
- sqlite3VdbeAddOp1(v, OP_Expire, (type==SQLITE_ATTACH));
+ tdsqlite3VdbeAddOp1(v, OP_Expire, (type==SQLITE_ATTACH));
}
attach_end:
- sqlite3ExprDelete(db, pFilename);
- sqlite3ExprDelete(db, pDbname);
- sqlite3ExprDelete(db, pKey);
+ tdsqlite3ExprDelete(db, pFilename);
+ tdsqlite3ExprDelete(db, pDbname);
+ tdsqlite3ExprDelete(db, pKey);
}
/*
@@ -100919,7 +112243,7 @@ attach_end:
**
** DETACH pDbname
*/
-SQLITE_PRIVATE void sqlite3Detach(Parse *pParse, Expr *pDbname){
+SQLITE_PRIVATE void tdsqlite3Detach(Parse *pParse, Expr *pDbname){
static const FuncDef detach_func = {
1, /* nArg */
SQLITE_UTF8, /* funcFlags */
@@ -100927,6 +112251,7 @@ SQLITE_PRIVATE void sqlite3Detach(Parse *pParse, Expr *pDbname){
0, /* pNext */
detachFunc, /* xSFunc */
0, /* xFinalize */
+ 0, 0, /* xValue, xInverse */
"sqlite_detach", /* zName */
{0}
};
@@ -100938,7 +112263,7 @@ SQLITE_PRIVATE void sqlite3Detach(Parse *pParse, Expr *pDbname){
**
** ATTACH p AS pDbname KEY pKey
*/
-SQLITE_PRIVATE void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *pKey){
+SQLITE_PRIVATE void tdsqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *pKey){
static const FuncDef attach_func = {
3, /* nArg */
SQLITE_UTF8, /* funcFlags */
@@ -100946,6 +112271,7 @@ SQLITE_PRIVATE void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *p
0, /* pNext */
attachFunc, /* xSFunc */
0, /* xFinalize */
+ 0, 0, /* xValue, xInverse */
"sqlite_attach", /* zName */
{0}
};
@@ -100957,14 +112283,14 @@ SQLITE_PRIVATE void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *p
** Initialize a DbFixer structure. This routine must be called prior
** to passing the structure to one of the sqliteFixAAAA() routines below.
*/
-SQLITE_PRIVATE void sqlite3FixInit(
+SQLITE_PRIVATE void tdsqlite3FixInit(
DbFixer *pFix, /* The fixer to be initialized */
Parse *pParse, /* Error messages will be written here */
int iDb, /* This is the database that must be used */
const char *zType, /* "view", "trigger", or "index" */
const Token *pName /* Name of the view, trigger, or index */
){
- sqlite3 *db;
+ tdsqlite3 *db;
db = pParse->db;
assert( db->nDb>iDb );
@@ -100973,14 +112299,14 @@ SQLITE_PRIVATE void sqlite3FixInit(
pFix->pSchema = db->aDb[iDb].pSchema;
pFix->zType = zType;
pFix->pName = pName;
- pFix->bVarOnly = (iDb==1);
+ pFix->bTemp = (iDb==1);
}
/*
** The following set of routines walk through the parse tree and assign
** a specific database to all table references where the database name
** was left unspecified in the original SQL statement. The pFix structure
-** must have been initialized by a prior call to sqlite3FixInit().
+** must have been initialized by a prior call to tdsqlite3FixInit().
**
** These routines are used to make sure that an index, trigger, or
** view in one database does not refer to objects in a different database.
@@ -100990,7 +112316,7 @@ SQLITE_PRIVATE void sqlite3FixInit(
** pParse->zErrMsg and these routines return non-zero. If everything
** checks out, these routines return 0.
*/
-SQLITE_PRIVATE int sqlite3FixSrcList(
+SQLITE_PRIVATE int tdsqlite3FixSrcList(
DbFixer *pFix, /* Context of the fixation */
SrcList *pList /* The Source list to check and modify */
){
@@ -101001,85 +112327,95 @@ SQLITE_PRIVATE int sqlite3FixSrcList(
if( NEVER(pList==0) ) return 0;
zDb = pFix->zDb;
for(i=0, pItem=pList->a; i<pList->nSrc; i++, pItem++){
- if( pFix->bVarOnly==0 ){
- if( pItem->zDatabase && sqlite3StrICmp(pItem->zDatabase, zDb) ){
- sqlite3ErrorMsg(pFix->pParse,
+ if( pFix->bTemp==0 ){
+ if( pItem->zDatabase && tdsqlite3StrICmp(pItem->zDatabase, zDb) ){
+ tdsqlite3ErrorMsg(pFix->pParse,
"%s %T cannot reference objects in database %s",
pFix->zType, pFix->pName, pItem->zDatabase);
return 1;
}
- sqlite3DbFree(pFix->pParse->db, pItem->zDatabase);
+ tdsqlite3DbFree(pFix->pParse->db, pItem->zDatabase);
pItem->zDatabase = 0;
pItem->pSchema = pFix->pSchema;
+ pItem->fg.fromDDL = 1;
}
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
- if( sqlite3FixSelect(pFix, pItem->pSelect) ) return 1;
- if( sqlite3FixExpr(pFix, pItem->pOn) ) return 1;
+ if( tdsqlite3FixSelect(pFix, pItem->pSelect) ) return 1;
+ if( tdsqlite3FixExpr(pFix, pItem->pOn) ) return 1;
#endif
+ if( pItem->fg.isTabFunc && tdsqlite3FixExprList(pFix, pItem->u1.pFuncArg) ){
+ return 1;
+ }
}
return 0;
}
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER)
-SQLITE_PRIVATE int sqlite3FixSelect(
+SQLITE_PRIVATE int tdsqlite3FixSelect(
DbFixer *pFix, /* Context of the fixation */
Select *pSelect /* The SELECT statement to be fixed to one database */
){
while( pSelect ){
- if( sqlite3FixExprList(pFix, pSelect->pEList) ){
+ if( tdsqlite3FixExprList(pFix, pSelect->pEList) ){
return 1;
}
- if( sqlite3FixSrcList(pFix, pSelect->pSrc) ){
+ if( tdsqlite3FixSrcList(pFix, pSelect->pSrc) ){
return 1;
}
- if( sqlite3FixExpr(pFix, pSelect->pWhere) ){
+ if( tdsqlite3FixExpr(pFix, pSelect->pWhere) ){
return 1;
}
- if( sqlite3FixExprList(pFix, pSelect->pGroupBy) ){
+ if( tdsqlite3FixExprList(pFix, pSelect->pGroupBy) ){
return 1;
}
- if( sqlite3FixExpr(pFix, pSelect->pHaving) ){
+ if( tdsqlite3FixExpr(pFix, pSelect->pHaving) ){
return 1;
}
- if( sqlite3FixExprList(pFix, pSelect->pOrderBy) ){
+ if( tdsqlite3FixExprList(pFix, pSelect->pOrderBy) ){
return 1;
}
- if( sqlite3FixExpr(pFix, pSelect->pLimit) ){
+ if( tdsqlite3FixExpr(pFix, pSelect->pLimit) ){
return 1;
}
- if( sqlite3FixExpr(pFix, pSelect->pOffset) ){
- return 1;
+ if( pSelect->pWith ){
+ int i;
+ for(i=0; i<pSelect->pWith->nCte; i++){
+ if( tdsqlite3FixSelect(pFix, pSelect->pWith->a[i].pSelect) ){
+ return 1;
+ }
+ }
}
pSelect = pSelect->pPrior;
}
return 0;
}
-SQLITE_PRIVATE int sqlite3FixExpr(
+SQLITE_PRIVATE int tdsqlite3FixExpr(
DbFixer *pFix, /* Context of the fixation */
Expr *pExpr /* The expression to be fixed to one database */
){
while( pExpr ){
+ if( !pFix->bTemp ) ExprSetProperty(pExpr, EP_FromDDL);
if( pExpr->op==TK_VARIABLE ){
if( pFix->pParse->db->init.busy ){
pExpr->op = TK_NULL;
}else{
- sqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType);
+ tdsqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType);
return 1;
}
}
if( ExprHasProperty(pExpr, EP_TokenOnly|EP_Leaf) ) break;
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
- if( sqlite3FixSelect(pFix, pExpr->x.pSelect) ) return 1;
+ if( tdsqlite3FixSelect(pFix, pExpr->x.pSelect) ) return 1;
}else{
- if( sqlite3FixExprList(pFix, pExpr->x.pList) ) return 1;
+ if( tdsqlite3FixExprList(pFix, pExpr->x.pList) ) return 1;
}
- if( sqlite3FixExpr(pFix, pExpr->pRight) ){
+ if( tdsqlite3FixExpr(pFix, pExpr->pRight) ){
return 1;
}
pExpr = pExpr->pLeft;
}
return 0;
}
-SQLITE_PRIVATE int sqlite3FixExprList(
+SQLITE_PRIVATE int tdsqlite3FixExprList(
DbFixer *pFix, /* Context of the fixation */
ExprList *pList /* The expression to be fixed to one database */
){
@@ -101087,7 +112423,7 @@ SQLITE_PRIVATE int sqlite3FixExprList(
struct ExprList_item *pItem;
if( pList==0 ) return 0;
for(i=0, pItem=pList->a; i<pList->nExpr; i++, pItem++){
- if( sqlite3FixExpr(pFix, pItem->pExpr) ){
+ if( tdsqlite3FixExpr(pFix, pItem->pExpr) ){
return 1;
}
}
@@ -101096,20 +112432,32 @@ SQLITE_PRIVATE int sqlite3FixExprList(
#endif
#ifndef SQLITE_OMIT_TRIGGER
-SQLITE_PRIVATE int sqlite3FixTriggerStep(
+SQLITE_PRIVATE int tdsqlite3FixTriggerStep(
DbFixer *pFix, /* Context of the fixation */
TriggerStep *pStep /* The trigger step be fixed to one database */
){
while( pStep ){
- if( sqlite3FixSelect(pFix, pStep->pSelect) ){
+ if( tdsqlite3FixSelect(pFix, pStep->pSelect) ){
return 1;
}
- if( sqlite3FixExpr(pFix, pStep->pWhere) ){
+ if( tdsqlite3FixExpr(pFix, pStep->pWhere) ){
return 1;
}
- if( sqlite3FixExprList(pFix, pStep->pExprList) ){
+ if( tdsqlite3FixExprList(pFix, pStep->pExprList) ){
return 1;
}
+#ifndef SQLITE_OMIT_UPSERT
+ if( pStep->pUpsert ){
+ Upsert *pUp = pStep->pUpsert;
+ if( tdsqlite3FixExprList(pFix, pUp->pUpsertTarget)
+ || tdsqlite3FixExpr(pFix, pUp->pUpsertTargetWhere)
+ || tdsqlite3FixExprList(pFix, pUp->pUpsertSet)
+ || tdsqlite3FixExpr(pFix, pUp->pUpsertWhere)
+ ){
+ return 1;
+ }
+ }
+#endif
pStep = pStep->pNext;
}
return 0;
@@ -101129,7 +112477,7 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep(
** May you share freely, never taking more than you give.
**
*************************************************************************
-** This file contains code used to implement the sqlite3_set_authorizer()
+** This file contains code used to implement the tdsqlite3_set_authorizer()
** API. This facility is an optional feature of the library. Embedded
** systems that do not need this facility may omit it by recompiling
** the library with -DSQLITE_OMIT_AUTHORIZATION=1
@@ -101179,7 +112527,7 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep(
** the table and the column that are being accessed. The auth function
** should return either SQLITE_OK, SQLITE_DENY, or SQLITE_IGNORE. If
** SQLITE_OK is returned, it means that access is allowed. SQLITE_DENY
-** means that the SQL statement will never-run - the sqlite3_exec() call
+** means that the SQL statement will never-run - the tdsqlite3_exec() call
** will return with an error. SQLITE_IGNORE means that the SQL statement
** should run but attempts to read the specified column will return NULL
** and attempts to write the column will be ignored.
@@ -101187,19 +112535,19 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep(
** Setting the auth function to NULL disables this hook. The default
** setting of the auth function is NULL.
*/
-SQLITE_API int sqlite3_set_authorizer(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_set_authorizer(
+ tdsqlite3 *db,
int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
void *pArg
){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
- db->xAuth = (sqlite3_xauth)xAuth;
+ tdsqlite3_mutex_enter(db->mutex);
+ db->xAuth = (tdsqlite3_xauth)xAuth;
db->pAuthArg = pArg;
- sqlite3ExpirePreparedStatements(db);
- sqlite3_mutex_leave(db->mutex);
+ if( db->xAuth ) tdsqlite3ExpirePreparedStatements(db, 1);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
}
@@ -101208,7 +112556,7 @@ SQLITE_API int sqlite3_set_authorizer(
** user-supplied authorization function returned an illegal value.
*/
static void sqliteAuthBadReturnCode(Parse *pParse){
- sqlite3ErrorMsg(pParse, "authorizer malfunction");
+ tdsqlite3ErrorMsg(pParse, "authorizer malfunction");
pParse->rc = SQLITE_ERROR;
}
@@ -101221,13 +112569,13 @@ static void sqliteAuthBadReturnCode(Parse *pParse){
** to an SQL NULL expression. Otherwise, if pExpr is NULL, then SQLITE_IGNORE
** is treated as SQLITE_DENY. In this case an error is left in pParse.
*/
-SQLITE_PRIVATE int sqlite3AuthReadCol(
+SQLITE_PRIVATE int tdsqlite3AuthReadCol(
Parse *pParse, /* The parser context */
const char *zTab, /* Table name */
const char *zCol, /* Column name */
int iDb /* Index of containing database. */
){
- sqlite3 *db = pParse->db; /* Database handle */
+ tdsqlite3 *db = pParse->db; /* Database handle */
char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */
int rc; /* Auth callback return code */
@@ -101238,11 +112586,9 @@ SQLITE_PRIVATE int sqlite3AuthReadCol(
#endif
);
if( rc==SQLITE_DENY ){
- if( db->nDb>2 || iDb!=0 ){
- sqlite3ErrorMsg(pParse, "access to %s.%s.%s is prohibited",zDb,zTab,zCol);
- }else{
- sqlite3ErrorMsg(pParse, "access to %s.%s is prohibited", zTab, zCol);
- }
+ char *z = tdsqlite3_mprintf("%s.%s", zTab, zCol);
+ if( db->nDb>2 || iDb!=0 ) z = tdsqlite3_mprintf("%s.%z", zDb, z);
+ tdsqlite3ErrorMsg(pParse, "access to %z is prohibited", z);
pParse->rc = SQLITE_AUTH;
}else if( rc!=SQLITE_IGNORE && rc!=SQLITE_OK ){
sqliteAuthBadReturnCode(pParse);
@@ -101259,28 +112605,29 @@ SQLITE_PRIVATE int sqlite3AuthReadCol(
** instruction into a TK_NULL. If the auth function returns SQLITE_DENY,
** then generate an error.
*/
-SQLITE_PRIVATE void sqlite3AuthRead(
+SQLITE_PRIVATE void tdsqlite3AuthRead(
Parse *pParse, /* The parser context */
Expr *pExpr, /* The expression to check authorization on */
Schema *pSchema, /* The schema of the expression */
SrcList *pTabList /* All table that pExpr might refer to */
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Table *pTab = 0; /* The table being read */
const char *zCol; /* Name of the column of the table */
int iSrc; /* Index in pTabList->a[] of table being read */
int iDb; /* The index of the database the expression refers to */
int iCol; /* Index of column in table */
+ assert( pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER );
+ assert( !IN_RENAME_OBJECT || db->xAuth==0 );
if( db->xAuth==0 ) return;
- iDb = sqlite3SchemaToIndex(pParse->db, pSchema);
+ iDb = tdsqlite3SchemaToIndex(pParse->db, pSchema);
if( iDb<0 ){
/* An attempt to read a column out of a subquery or other
** temporary table. */
return;
}
- assert( pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER );
if( pExpr->op==TK_TRIGGER ){
pTab = pParse->pTriggerTab;
}else{
@@ -101305,7 +112652,7 @@ SQLITE_PRIVATE void sqlite3AuthRead(
zCol = "ROWID";
}
assert( iDb>=0 && iDb<db->nDb );
- if( SQLITE_IGNORE==sqlite3AuthReadCol(pParse, pTab->zName, zCol, iDb) ){
+ if( SQLITE_IGNORE==tdsqlite3AuthReadCol(pParse, pTab->zName, zCol, iDb) ){
pExpr->op = TK_NULL;
}
}
@@ -101316,33 +112663,46 @@ SQLITE_PRIVATE void sqlite3AuthRead(
** is returned, then the error count and error message in pParse are
** modified appropriately.
*/
-SQLITE_PRIVATE int sqlite3AuthCheck(
+SQLITE_PRIVATE int tdsqlite3AuthCheck(
Parse *pParse,
int code,
const char *zArg1,
const char *zArg2,
const char *zArg3
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
int rc;
/* Don't do any authorization checks if the database is initialising
- ** or if the parser is being invoked from within sqlite3_declare_vtab.
+ ** or if the parser is being invoked from within tdsqlite3_declare_vtab.
*/
- if( db->init.busy || IN_DECLARE_VTAB ){
+ assert( !IN_RENAME_OBJECT || db->xAuth==0 );
+ if( db->init.busy || IN_SPECIAL_PARSE ){
return SQLITE_OK;
}
if( db->xAuth==0 ){
return SQLITE_OK;
}
+
+ /* EVIDENCE-OF: R-43249-19882 The third through sixth parameters to the
+ ** callback are either NULL pointers or zero-terminated strings that
+ ** contain additional details about the action to be authorized.
+ **
+ ** The following testcase() macros show that any of the 3rd through 6th
+ ** parameters can be either NULL or a string. */
+ testcase( zArg1==0 );
+ testcase( zArg2==0 );
+ testcase( zArg3==0 );
+ testcase( pParse->zAuthContext==0 );
+
rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext
#ifdef SQLITE_USER_AUTHENTICATION
,db->auth.zAuthUser
#endif
);
if( rc==SQLITE_DENY ){
- sqlite3ErrorMsg(pParse, "not authorized");
+ tdsqlite3ErrorMsg(pParse, "not authorized");
pParse->rc = SQLITE_AUTH;
}else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){
rc = SQLITE_DENY;
@@ -101356,7 +112716,7 @@ SQLITE_PRIVATE int sqlite3AuthCheck(
** zArg3 argument to authorization callbacks will be zContext until
** popped. Or if pParse==0, this routine is a no-op.
*/
-SQLITE_PRIVATE void sqlite3AuthContextPush(
+SQLITE_PRIVATE void tdsqlite3AuthContextPush(
Parse *pParse,
AuthContext *pContext,
const char *zContext
@@ -101369,9 +112729,9 @@ SQLITE_PRIVATE void sqlite3AuthContextPush(
/*
** Pop an authorization context that was previously pushed
-** by sqlite3AuthContextPush
+** by tdsqlite3AuthContextPush
*/
-SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext *pContext){
+SQLITE_PRIVATE void tdsqlite3AuthContextPop(AuthContext *pContext){
if( pContext->pParse ){
pContext->pParse->zAuthContext = pContext->zAuthContext;
pContext->pParse = 0;
@@ -101410,14 +112770,14 @@ SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext *pContext){
#ifndef SQLITE_OMIT_SHARED_CACHE
/*
-** The TableLock structure is only used by the sqlite3TableLock() and
+** The TableLock structure is only used by the tdsqlite3TableLock() and
** codeTableLocks() functions.
*/
struct TableLock {
- int iDb; /* The database containing the table to be locked */
- int iTab; /* The root page of the table to be locked */
- u8 isWriteLock; /* True for write lock. False for a read lock */
- const char *zName; /* Name of the table */
+ int iDb; /* The database containing the table to be locked */
+ int iTab; /* The root page of the table to be locked */
+ u8 isWriteLock; /* True for write lock. False for a read lock */
+ const char *zLockName; /* Name of the table */
};
/*
@@ -101428,21 +112788,23 @@ struct TableLock {
**
** This routine just records the fact that the lock is desired. The
** code to make the lock occur is generated by a later call to
-** codeTableLocks() which occurs during sqlite3FinishCoding().
+** codeTableLocks() which occurs during tdsqlite3FinishCoding().
*/
-SQLITE_PRIVATE void sqlite3TableLock(
+SQLITE_PRIVATE void tdsqlite3TableLock(
Parse *pParse, /* Parsing context */
int iDb, /* Index of the database containing the table to lock */
int iTab, /* Root page number of the table to be locked */
u8 isWriteLock, /* True for a write lock */
const char *zName /* Name of the table to be locked */
){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
+ Parse *pToplevel = tdsqlite3ParseToplevel(pParse);
int i;
int nBytes;
TableLock *p;
assert( iDb>=0 );
+ if( iDb==1 ) return;
+ if( !tdsqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return;
for(i=0; i<pToplevel->nTableLock; i++){
p = &pToplevel->aTableLock[i];
if( p->iDb==iDb && p->iTab==iTab ){
@@ -101453,35 +112815,35 @@ SQLITE_PRIVATE void sqlite3TableLock(
nBytes = sizeof(TableLock) * (pToplevel->nTableLock+1);
pToplevel->aTableLock =
- sqlite3DbReallocOrFree(pToplevel->db, pToplevel->aTableLock, nBytes);
+ tdsqlite3DbReallocOrFree(pToplevel->db, pToplevel->aTableLock, nBytes);
if( pToplevel->aTableLock ){
p = &pToplevel->aTableLock[pToplevel->nTableLock++];
p->iDb = iDb;
p->iTab = iTab;
p->isWriteLock = isWriteLock;
- p->zName = zName;
+ p->zLockName = zName;
}else{
pToplevel->nTableLock = 0;
- sqlite3OomFault(pToplevel->db);
+ tdsqlite3OomFault(pToplevel->db);
}
}
/*
** Code an OP_TableLock instruction for each table locked by the
-** statement (configured by calls to sqlite3TableLock()).
+** statement (configured by calls to tdsqlite3TableLock()).
*/
static void codeTableLocks(Parse *pParse){
int i;
Vdbe *pVdbe;
- pVdbe = sqlite3GetVdbe(pParse);
- assert( pVdbe!=0 ); /* sqlite3GetVdbe cannot fail: VDBE already allocated */
+ pVdbe = tdsqlite3GetVdbe(pParse);
+ assert( pVdbe!=0 ); /* tdsqlite3GetVdbe cannot fail: VDBE already allocated */
for(i=0; i<pParse->nTableLock; i++){
TableLock *p = &pParse->aTableLock[i];
int p1 = p->iDb;
- sqlite3VdbeAddOp4(pVdbe, OP_TableLock, p1, p->iTab, p->isWriteLock,
- p->zName, P4_STATIC);
+ tdsqlite3VdbeAddOp4(pVdbe, OP_TableLock, p1, p->iTab, p->isWriteLock,
+ p->zLockName, P4_STATIC);
}
}
#else
@@ -101494,7 +112856,7 @@ static void codeTableLocks(Parse *pParse){
** macros when SQLITE_MAX_ATTACHED is greater than 30.
*/
#if SQLITE_MAX_ATTACHED>30
-SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask m){
+SQLITE_PRIVATE int tdsqlite3DbMaskAllZero(yDbMask m){
int i;
for(i=0; i<sizeof(yDbMask); i++) if( m[i] ) return 0;
return 1;
@@ -101511,8 +112873,8 @@ SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask m){
** Note that if an error occurred, it might be the case that
** no VDBE code was generated.
*/
-SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
- sqlite3 *db;
+SQLITE_PRIVATE void tdsqlite3FinishCoding(Parse *pParse){
+ tdsqlite3 *db;
Vdbe *v;
assert( pParse->pToplevel==0 );
@@ -101526,17 +112888,17 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
/* Begin by generating some termination code at the end of the
** vdbe program
*/
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
assert( !pParse->isMultiWrite
- || sqlite3VdbeAssertMayAbort(v, pParse->mayAbort));
+ || tdsqlite3VdbeAssertMayAbort(v, pParse->mayAbort));
if( v ){
- sqlite3VdbeAddOp0(v, OP_Halt);
+ tdsqlite3VdbeAddOp0(v, OP_Halt);
#if SQLITE_USER_AUTHENTICATION
if( pParse->nTableLock>0 && db->init.busy==0 ){
- sqlite3UserAuthInit(db);
+ tdsqlite3UserAuthInit(db);
if( db->auth.authLevel<UAUTH_User ){
- sqlite3ErrorMsg(pParse, "user not authenticated");
+ tdsqlite3ErrorMsg(pParse, "user not authenticated");
pParse->rc = SQLITE_AUTH_USER;
return;
}
@@ -101553,28 +112915,28 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
&& (DbMaskNonZero(pParse->cookieMask) || pParse->pConstExpr)
){
int iDb, i;
- assert( sqlite3VdbeGetOp(v, 0)->opcode==OP_Init );
- sqlite3VdbeJumpHere(v, 0);
+ assert( tdsqlite3VdbeGetOp(v, 0)->opcode==OP_Init );
+ tdsqlite3VdbeJumpHere(v, 0);
for(iDb=0; iDb<db->nDb; iDb++){
Schema *pSchema;
if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue;
- sqlite3VdbeUsesBtree(v, iDb);
+ tdsqlite3VdbeUsesBtree(v, iDb);
pSchema = db->aDb[iDb].pSchema;
- sqlite3VdbeAddOp4Int(v,
+ tdsqlite3VdbeAddOp4Int(v,
OP_Transaction, /* Opcode */
iDb, /* P1 */
DbMaskTest(pParse->writeMask,iDb), /* P2 */
pSchema->schema_cookie, /* P3 */
pSchema->iGeneration /* P4 */
);
- if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1);
+ if( db->init.busy==0 ) tdsqlite3VdbeChangeP5(v, 1);
VdbeComment((v,
"usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite));
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
for(i=0; i<pParse->nVtabLock; i++){
- char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]);
- sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB);
+ char *vtab = (char *)tdsqlite3GetVTable(db, pParse->apVtabLock[i]);
+ tdsqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB);
}
pParse->nVtabLock = 0;
#endif
@@ -101587,19 +112949,19 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
/* Initialize any AUTOINCREMENT data structures required.
*/
- sqlite3AutoincrementBegin(pParse);
+ tdsqlite3AutoincrementBegin(pParse);
/* Code constant expressions that where factored out of inner loops */
if( pParse->pConstExpr ){
ExprList *pEL = pParse->pConstExpr;
pParse->okConstFactor = 0;
for(i=0; i<pEL->nExpr; i++){
- sqlite3ExprCode(pParse, pEL->a[i].pExpr, pEL->a[i].u.iConstExprReg);
+ tdsqlite3ExprCode(pParse, pEL->a[i].pExpr, pEL->a[i].u.iConstExprReg);
}
}
/* Finally, jump back to the beginning of the executable code. */
- sqlite3VdbeGoto(v, 1);
+ tdsqlite3VdbeGoto(v, 1);
}
}
@@ -101607,11 +112969,10 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
/* Get the VDBE program ready for execution
*/
if( v && pParse->nErr==0 && !db->mallocFailed ){
- assert( pParse->iCacheLevel==0 ); /* Disables and re-enables match */
/* A minimum of one cursor is required if autoincrement is used
* See ticket [a696379c1f08866] */
- if( pParse->pAinc!=0 && pParse->nTab==0 ) pParse->nTab = 1;
- sqlite3VdbeMakeReady(v, pParse);
+ assert( pParse->pAinc==0 || pParse->nTab>0 );
+ tdsqlite3VdbeMakeReady(v, pParse);
pParse->rc = SQLITE_DONE;
}else{
pParse->rc = SQLITE_ERROR;
@@ -101630,27 +112991,32 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){
** INSERT, UPDATE, and DELETE operations against SQLITE_MASTER. Use
** care if you decide to try to use this routine for some other purposes.
*/
-SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){
+SQLITE_PRIVATE void tdsqlite3NestedParse(Parse *pParse, const char *zFormat, ...){
va_list ap;
char *zSql;
char *zErrMsg = 0;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
char saveBuf[PARSE_TAIL_SZ];
if( pParse->nErr ) return;
assert( pParse->nested<10 ); /* Nesting should only be of limited depth */
va_start(ap, zFormat);
- zSql = sqlite3VMPrintf(db, zFormat, ap);
+ zSql = tdsqlite3VMPrintf(db, zFormat, ap);
va_end(ap);
if( zSql==0 ){
- return; /* A malloc must have failed */
+ /* This can result either from an OOM or because the formatted string
+ ** exceeds SQLITE_LIMIT_LENGTH. In the latter case, we need to set
+ ** an error */
+ if( !db->mallocFailed ) pParse->rc = SQLITE_TOOBIG;
+ pParse->nErr++;
+ return;
}
pParse->nested++;
memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ);
memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ);
- sqlite3RunParser(pParse, zSql, &zErrMsg);
- sqlite3DbFree(db, zErrMsg);
- sqlite3DbFree(db, zSql);
+ tdsqlite3RunParser(pParse, zSql, &zErrMsg);
+ tdsqlite3DbFree(db, zErrMsg);
+ tdsqlite3DbFree(db, zSql);
memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ);
pParse->nested--;
}
@@ -101660,8 +113026,8 @@ SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){
** Return TRUE if zTable is the name of the system table that stores the
** list of users and their access credentials.
*/
-SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){
- return sqlite3_stricmp(zTable, "sqlite_user")==0;
+SQLITE_PRIVATE int tdsqlite3UserAuthTable(const char *zTable){
+ return tdsqlite3_stricmp(zTable, "sqlite_user")==0;
}
#endif
@@ -101675,30 +113041,37 @@ SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){
** names is done.) The search order is TEMP first, then MAIN, then any
** auxiliary databases added using the ATTACH command.
**
-** See also sqlite3LocateTable().
+** See also tdsqlite3LocateTable().
*/
-SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){
+SQLITE_PRIVATE Table *tdsqlite3FindTable(tdsqlite3 *db, const char *zName, const char *zDatabase){
Table *p = 0;
int i;
/* All mutexes are required for schema access. Make sure we hold them. */
- assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) );
+ assert( zDatabase!=0 || tdsqlite3BtreeHoldsAllMutexes(db) );
#if SQLITE_USER_AUTHENTICATION
/* Only the admin user is allowed to know that the sqlite_user table
** exists */
- if( db->auth.authLevel<UAUTH_Admin && sqlite3UserAuthTable(zName)!=0 ){
+ if( db->auth.authLevel<UAUTH_Admin && tdsqlite3UserAuthTable(zName)!=0 ){
return 0;
}
#endif
- for(i=OMIT_TEMPDB; i<db->nDb; i++){
- int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
- if( zDatabase==0 || sqlite3StrICmp(zDatabase, db->aDb[j].zDbSName)==0 ){
- assert( sqlite3SchemaMutexHeld(db, j, 0) );
- p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName);
- if( p ) break;
+ while(1){
+ for(i=OMIT_TEMPDB; i<db->nDb; i++){
+ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
+ if( zDatabase==0 || tdsqlite3StrICmp(zDatabase, db->aDb[j].zDbSName)==0 ){
+ assert( tdsqlite3SchemaMutexHeld(db, j, 0) );
+ p = tdsqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName);
+ if( p ) return p;
+ }
}
+ /* Not found. If the name we were looking for was temp.sqlite_master
+ ** then change the name to sqlite_temp_master and try again. */
+ if( tdsqlite3StrICmp(zName, MASTER_NAME)!=0 ) break;
+ if( tdsqlite3_stricmp(zDatabase, db->aDb[1].zDbSName)!=0 ) break;
+ zName = TEMP_MASTER_NAME;
}
- return p;
+ return 0;
}
/*
@@ -101707,45 +113080,55 @@ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const cha
** database containing the table. Return NULL if not found. Also leave an
** error message in pParse->zErrMsg.
**
-** The difference between this routine and sqlite3FindTable() is that this
+** The difference between this routine and tdsqlite3FindTable() is that this
** routine leaves an error message in pParse->zErrMsg where
-** sqlite3FindTable() does not.
+** tdsqlite3FindTable() does not.
*/
-SQLITE_PRIVATE Table *sqlite3LocateTable(
+SQLITE_PRIVATE Table *tdsqlite3LocateTable(
Parse *pParse, /* context in which to report errors */
u32 flags, /* LOCATE_VIEW or LOCATE_NOERR */
const char *zName, /* Name of the table we are looking for */
const char *zDbase /* Name of the database. Might be NULL */
){
Table *p;
+ tdsqlite3 *db = pParse->db;
/* Read the database schema. If an error occurs, leave an error message
** and code in pParse and return NULL. */
- if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ if( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0
+ && SQLITE_OK!=tdsqlite3ReadSchema(pParse)
+ ){
return 0;
}
- p = sqlite3FindTable(pParse->db, zName, zDbase);
+ p = tdsqlite3FindTable(db, zName, zDbase);
if( p==0 ){
- const char *zMsg = flags & LOCATE_VIEW ? "no such view" : "no such table";
#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( sqlite3FindDbName(pParse->db, zDbase)<1 ){
- /* If zName is the not the name of a table in the schema created using
- ** CREATE, then check to see if it is the name of an virtual table that
- ** can be an eponymous virtual table. */
- Module *pMod = (Module*)sqlite3HashFind(&pParse->db->aModule, zName);
- if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){
+ /* If zName is the not the name of a table in the schema created using
+ ** CREATE, then check to see if it is the name of an virtual table that
+ ** can be an eponymous virtual table. */
+ if( pParse->disableVtab==0 ){
+ Module *pMod = (Module*)tdsqlite3HashFind(&db->aModule, zName);
+ if( pMod==0 && tdsqlite3_strnicmp(zName, "pragma_", 7)==0 ){
+ pMod = tdsqlite3PragmaVtabRegister(db, zName);
+ }
+ if( pMod && tdsqlite3VtabEponymousTableInit(pParse, pMod) ){
return pMod->pEpoTab;
}
}
#endif
- if( (flags & LOCATE_NOERR)==0 ){
- if( zDbase ){
- sqlite3ErrorMsg(pParse, "%s: %s.%s", zMsg, zDbase, zName);
- }else{
- sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName);
- }
- pParse->checkSchema = 1;
+ if( flags & LOCATE_NOERR ) return 0;
+ pParse->checkSchema = 1;
+ }else if( IsVirtual(p) && pParse->disableVtab ){
+ p = 0;
+ }
+
+ if( p==0 ){
+ const char *zMsg = flags & LOCATE_VIEW ? "no such view" : "no such table";
+ if( zDbase ){
+ tdsqlite3ErrorMsg(pParse, "%s: %s.%s", zMsg, zDbase, zName);
+ }else{
+ tdsqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName);
}
}
@@ -101755,13 +113138,13 @@ SQLITE_PRIVATE Table *sqlite3LocateTable(
/*
** Locate the table identified by *p.
**
-** This is a wrapper around sqlite3LocateTable(). The difference between
-** sqlite3LocateTable() and this function is that this function restricts
+** This is a wrapper around tdsqlite3LocateTable(). The difference between
+** tdsqlite3LocateTable() and this function is that this function restricts
** the search to schema (p->pSchema) if it is not NULL. p->pSchema may be
** non-NULL if it is part of a view or trigger program definition. See
-** sqlite3FixSrcList() for details.
+** tdsqlite3FixSrcList() for details.
*/
-SQLITE_PRIVATE Table *sqlite3LocateTableItem(
+SQLITE_PRIVATE Table *tdsqlite3LocateTableItem(
Parse *pParse,
u32 flags,
struct SrcList_item *p
@@ -101769,12 +113152,12 @@ SQLITE_PRIVATE Table *sqlite3LocateTableItem(
const char *zDb;
assert( p->pSchema==0 || p->zDatabase==0 );
if( p->pSchema ){
- int iDb = sqlite3SchemaToIndex(pParse->db, p->pSchema);
+ int iDb = tdsqlite3SchemaToIndex(pParse->db, p->pSchema);
zDb = pParse->db->aDb[iDb].zDbSName;
}else{
zDb = p->zDatabase;
}
- return sqlite3LocateTable(pParse, flags, p->zName, zDb);
+ return tdsqlite3LocateTable(pParse, flags, p->zName, zDb);
}
/*
@@ -101789,18 +113172,18 @@ SQLITE_PRIVATE Table *sqlite3LocateTableItem(
** TEMP first, then MAIN, then any auxiliary databases added
** using the ATTACH command.
*/
-SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const char *zDb){
+SQLITE_PRIVATE Index *tdsqlite3FindIndex(tdsqlite3 *db, const char *zName, const char *zDb){
Index *p = 0;
int i;
/* All mutexes are required for schema access. Make sure we hold them. */
- assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
+ assert( zDb!=0 || tdsqlite3BtreeHoldsAllMutexes(db) );
for(i=OMIT_TEMPDB; i<db->nDb; i++){
int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
Schema *pSchema = db->aDb[j].pSchema;
assert( pSchema );
- if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zDbSName) ) continue;
- assert( sqlite3SchemaMutexHeld(db, j, 0) );
- p = sqlite3HashFind(&pSchema->idxHash, zName);
+ if( zDb && tdsqlite3StrICmp(zDb, db->aDb[j].zDbSName) ) continue;
+ assert( tdsqlite3SchemaMutexHeld(db, j, 0) );
+ p = tdsqlite3HashFind(&pSchema->idxHash, zName);
if( p ) break;
}
return p;
@@ -101809,18 +113192,18 @@ SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const cha
/*
** Reclaim the memory used by an index
*/
-static void freeIndex(sqlite3 *db, Index *p){
+SQLITE_PRIVATE void tdsqlite3FreeIndex(tdsqlite3 *db, Index *p){
#ifndef SQLITE_OMIT_ANALYZE
- sqlite3DeleteIndexSamples(db, p);
+ tdsqlite3DeleteIndexSamples(db, p);
#endif
- sqlite3ExprDelete(db, p->pPartIdxWhere);
- sqlite3ExprListDelete(db, p->aColExpr);
- sqlite3DbFree(db, p->zColAff);
- if( p->isResized ) sqlite3DbFree(db, (void *)p->azColl);
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- sqlite3_free(p->aiRowEst);
+ tdsqlite3ExprDelete(db, p->pPartIdxWhere);
+ tdsqlite3ExprListDelete(db, p->aColExpr);
+ tdsqlite3DbFree(db, p->zColAff);
+ if( p->isResized ) tdsqlite3DbFree(db, (void *)p->azColl);
+#ifdef SQLITE_ENABLE_STAT4
+ tdsqlite3_free(p->aiRowEst);
#endif
- sqlite3DbFree(db, p);
+ tdsqlite3DbFree(db, p);
}
/*
@@ -101829,13 +113212,13 @@ static void freeIndex(sqlite3 *db, Index *p){
** the index hash table and free all memory structures associated
** with the index.
*/
-SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char *zIdxName){
+SQLITE_PRIVATE void tdsqlite3UnlinkAndDeleteIndex(tdsqlite3 *db, int iDb, const char *zIdxName){
Index *pIndex;
Hash *pHash;
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
pHash = &db->aDb[iDb].pSchema->idxHash;
- pIndex = sqlite3HashInsert(pHash, zIdxName, 0);
+ pIndex = tdsqlite3HashInsert(pHash, zIdxName, 0);
if( ALWAYS(pIndex) ){
if( pIndex->pTable->pIndex==pIndex ){
pIndex->pTable->pIndex = pIndex->pNext;
@@ -101849,9 +113232,9 @@ SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char
p->pNext = pIndex->pNext;
}
}
- freeIndex(db, pIndex);
+ tdsqlite3FreeIndex(db, pIndex);
}
- db->flags |= SQLITE_InternChanges;
+ db->mDbFlags |= DBFLAG_SchemaChange;
}
/*
@@ -101862,12 +113245,12 @@ SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3 *db, int iDb, const char
** Entry 0 (the "main" database) and entry 1 (the "temp" database)
** are never candidates for being collapsed.
*/
-SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3CollapseDatabaseArray(tdsqlite3 *db){
int i, j;
for(i=j=2; i<db->nDb; i++){
struct Db *pDb = &db->aDb[i];
if( pDb->pBt==0 ){
- sqlite3DbFree(db, pDb->zDbSName);
+ tdsqlite3DbFree(db, pDb->zDbSName);
pDb->zDbSName = 0;
continue;
}
@@ -101879,78 +113262,83 @@ SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3 *db){
db->nDb = j;
if( db->nDb<=2 && db->aDb!=db->aDbStatic ){
memcpy(db->aDbStatic, db->aDb, 2*sizeof(db->aDb[0]));
- sqlite3DbFree(db, db->aDb);
+ tdsqlite3DbFree(db, db->aDb);
db->aDb = db->aDbStatic;
}
}
/*
** Reset the schema for the database at index iDb. Also reset the
-** TEMP schema.
+** TEMP schema. The reset is deferred if db->nSchemaLock is not zero.
+** Deferred resets may be run by calling with iDb<0.
*/
-SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3 *db, int iDb){
- Db *pDb;
+SQLITE_PRIVATE void tdsqlite3ResetOneSchema(tdsqlite3 *db, int iDb){
+ int i;
assert( iDb<db->nDb );
- /* Case 1: Reset the single schema identified by iDb */
- pDb = &db->aDb[iDb];
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
- assert( pDb->pSchema!=0 );
- sqlite3SchemaClear(pDb->pSchema);
+ if( iDb>=0 ){
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
+ DbSetProperty(db, iDb, DB_ResetWanted);
+ DbSetProperty(db, 1, DB_ResetWanted);
+ db->mDbFlags &= ~DBFLAG_SchemaKnownOk;
+ }
- /* If any database other than TEMP is reset, then also reset TEMP
- ** since TEMP might be holding triggers that reference tables in the
- ** other database.
- */
- if( iDb!=1 ){
- pDb = &db->aDb[1];
- assert( pDb->pSchema!=0 );
- sqlite3SchemaClear(pDb->pSchema);
+ if( db->nSchemaLock==0 ){
+ for(i=0; i<db->nDb; i++){
+ if( DbHasProperty(db, i, DB_ResetWanted) ){
+ tdsqlite3SchemaClear(db->aDb[i].pSchema);
+ }
+ }
}
- return;
}
/*
** Erase all schema information from all attached databases (including
** "main" and "temp") for a single database connection.
*/
-SQLITE_PRIVATE void sqlite3ResetAllSchemasOfConnection(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3ResetAllSchemasOfConnection(tdsqlite3 *db){
int i;
- sqlite3BtreeEnterAll(db);
+ tdsqlite3BtreeEnterAll(db);
for(i=0; i<db->nDb; i++){
Db *pDb = &db->aDb[i];
if( pDb->pSchema ){
- sqlite3SchemaClear(pDb->pSchema);
+ if( db->nSchemaLock==0 ){
+ tdsqlite3SchemaClear(pDb->pSchema);
+ }else{
+ DbSetProperty(db, i, DB_ResetWanted);
+ }
}
}
- db->flags &= ~SQLITE_InternChanges;
- sqlite3VtabUnlockList(db);
- sqlite3BtreeLeaveAll(db);
- sqlite3CollapseDatabaseArray(db);
+ db->mDbFlags &= ~(DBFLAG_SchemaChange|DBFLAG_SchemaKnownOk);
+ tdsqlite3VtabUnlockList(db);
+ tdsqlite3BtreeLeaveAll(db);
+ if( db->nSchemaLock==0 ){
+ tdsqlite3CollapseDatabaseArray(db);
+ }
}
/*
** This routine is called when a commit occurs.
*/
-SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3 *db){
- db->flags &= ~SQLITE_InternChanges;
+SQLITE_PRIVATE void tdsqlite3CommitInternalChanges(tdsqlite3 *db){
+ db->mDbFlags &= ~DBFLAG_SchemaChange;
}
/*
** Delete memory allocated for the column names of a table or view (the
** Table.aCol[] array).
*/
-SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){
+SQLITE_PRIVATE void tdsqlite3DeleteColumnNames(tdsqlite3 *db, Table *pTable){
int i;
Column *pCol;
assert( pTable!=0 );
if( (pCol = pTable->aCol)!=0 ){
for(i=0; i<pTable->nCol; i++, pCol++){
- sqlite3DbFree(db, pCol->zName);
- sqlite3ExprDelete(db, pCol->pDflt);
- sqlite3DbFree(db, pCol->zColl);
+ tdsqlite3DbFree(db, pCol->zName);
+ tdsqlite3ExprDelete(db, pCol->pDflt);
+ tdsqlite3DbFree(db, pCol->zColl);
}
- sqlite3DbFree(db, pTable->aCol);
+ tdsqlite3DbFree(db, pTable->aCol);
}
}
@@ -101969,15 +113357,22 @@ SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){
** db parameter can be used with db->pnBytesFreed to measure the memory
** used by the Table object.
*/
-static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){
+static void SQLITE_NOINLINE deleteTable(tdsqlite3 *db, Table *pTable){
Index *pIndex, *pNext;
- TESTONLY( int nLookaside; ) /* Used to verify lookaside not used for schema */
+#ifdef SQLITE_DEBUG
/* Record the number of outstanding lookaside allocations in schema Tables
- ** prior to doing any free() operations. Since schema Tables do not use
- ** lookaside, this number should not change. */
- TESTONLY( nLookaside = (db && (pTable->tabFlags & TF_Ephemeral)==0) ?
- db->lookaside.nOut : 0 );
+ ** prior to doing any free() operations. Since schema Tables do not use
+ ** lookaside, this number should not change.
+ **
+ ** If malloc has already failed, it may be that it failed while allocating
+ ** a Table object that was going to be marked ephemeral. So do not check
+ ** that no lookaside memory is used in this case either. */
+ int nLookaside = 0;
+ if( db && !db->mallocFailed && (pTable->tabFlags & TF_Ephemeral)==0 ){
+ nLookaside = tdsqlite3LookasideUsed(db, 0);
+ }
+#endif
/* Delete all indices associated with this table. */
for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){
@@ -101986,37 +113381,37 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){
|| (IsVirtual(pTable) && pIndex->idxType!=SQLITE_IDXTYPE_APPDEF) );
if( (db==0 || db->pnBytesFreed==0) && !IsVirtual(pTable) ){
char *zName = pIndex->zName;
- TESTONLY ( Index *pOld = ) sqlite3HashInsert(
+ TESTONLY ( Index *pOld = ) tdsqlite3HashInsert(
&pIndex->pSchema->idxHash, zName, 0
);
- assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
+ assert( db==0 || tdsqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
assert( pOld==pIndex || pOld==0 );
}
- freeIndex(db, pIndex);
+ tdsqlite3FreeIndex(db, pIndex);
}
/* Delete any foreign keys attached to this table. */
- sqlite3FkDelete(db, pTable);
+ tdsqlite3FkDelete(db, pTable);
/* Delete the Table structure itself.
*/
- sqlite3DeleteColumnNames(db, pTable);
- sqlite3DbFree(db, pTable->zName);
- sqlite3DbFree(db, pTable->zColAff);
- sqlite3SelectDelete(db, pTable->pSelect);
- sqlite3ExprListDelete(db, pTable->pCheck);
+ tdsqlite3DeleteColumnNames(db, pTable);
+ tdsqlite3DbFree(db, pTable->zName);
+ tdsqlite3DbFree(db, pTable->zColAff);
+ tdsqlite3SelectDelete(db, pTable->pSelect);
+ tdsqlite3ExprListDelete(db, pTable->pCheck);
#ifndef SQLITE_OMIT_VIRTUALTABLE
- sqlite3VtabClear(db, pTable);
+ tdsqlite3VtabClear(db, pTable);
#endif
- sqlite3DbFree(db, pTable);
+ tdsqlite3DbFree(db, pTable);
/* Verify that no lookaside memory was used by schema tables */
- assert( nLookaside==0 || nLookaside==db->lookaside.nOut );
+ assert( nLookaside==0 || nLookaside==tdsqlite3LookasideUsed(db,0) );
}
-SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3 *db, Table *pTable){
+SQLITE_PRIVATE void tdsqlite3DeleteTable(tdsqlite3 *db, Table *pTable){
/* Do not delete the table until the reference count reaches zero. */
if( !pTable ) return;
- if( ((!db || db->pnBytesFreed==0) && (--pTable->nRef)>0) ) return;
+ if( ((!db || db->pnBytesFreed==0) && (--pTable->nTabRef)>0) ) return;
deleteTable(db, pTable);
}
@@ -102025,19 +113420,19 @@ SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3 *db, Table *pTable){
** Unlink the given table from the hash tables and the delete the
** table structure with all its indices and foreign keys.
*/
-SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char *zTabName){
+SQLITE_PRIVATE void tdsqlite3UnlinkAndDeleteTable(tdsqlite3 *db, int iDb, const char *zTabName){
Table *p;
Db *pDb;
assert( db!=0 );
assert( iDb>=0 && iDb<db->nDb );
assert( zTabName );
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
testcase( zTabName[0]==0 ); /* Zero-length table names are allowed */
pDb = &db->aDb[iDb];
- p = sqlite3HashInsert(&pDb->pSchema->tblHash, zTabName, 0);
- sqlite3DeleteTable(db, p);
- db->flags |= SQLITE_InternChanges;
+ p = tdsqlite3HashInsert(&pDb->pSchema->tblHash, zTabName, 0);
+ tdsqlite3DeleteTable(db, p);
+ db->mDbFlags |= DBFLAG_SchemaChange;
}
/*
@@ -102053,11 +113448,11 @@ SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char
** are not \000 terminated and are not persistent. The returned string
** is \000 terminated and is persistent.
*/
-SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){
+SQLITE_PRIVATE char *tdsqlite3NameFromToken(tdsqlite3 *db, Token *pName){
char *zName;
if( pName ){
- zName = sqlite3DbStrNDup(db, (char*)pName->z, pName->n);
- sqlite3Dequote(zName);
+ zName = tdsqlite3DbStrNDup(db, (char*)pName->z, pName->n);
+ tdsqlite3Dequote(zName);
}else{
zName = 0;
}
@@ -102068,10 +113463,10 @@ SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){
** Open the sqlite_master table stored in database number iDb for
** writing. The table is opened using cursor 0.
*/
-SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *p, int iDb){
- Vdbe *v = sqlite3GetVdbe(p);
- sqlite3TableLock(p, iDb, MASTER_ROOT, 1, SCHEMA_TABLE(iDb));
- sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, MASTER_ROOT, iDb, 5);
+SQLITE_PRIVATE void tdsqlite3OpenMasterTable(Parse *p, int iDb){
+ Vdbe *v = tdsqlite3GetVdbe(p);
+ tdsqlite3TableLock(p, iDb, MASTER_ROOT, 1, MASTER_NAME);
+ tdsqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, MASTER_ROOT, iDb, 5);
if( p->nTab==0 ){
p->nTab = 1;
}
@@ -102083,12 +113478,15 @@ SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *p, int iDb){
** function returns the index of the named database in db->aDb[], or
** -1 if the named db cannot be found.
*/
-SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *db, const char *zName){
+SQLITE_PRIVATE int tdsqlite3FindDbName(tdsqlite3 *db, const char *zName){
int i = -1; /* Database number */
if( zName ){
Db *pDb;
for(i=(db->nDb-1), pDb=&db->aDb[i]; i>=0; i--, pDb--){
- if( 0==sqlite3StrICmp(pDb->zDbSName, zName) ) break;
+ if( 0==tdsqlite3_stricmp(pDb->zDbSName, zName) ) break;
+ /* "main" is always an acceptable alias for the primary database
+ ** even if it has been renamed using SQLITE_DBCONFIG_MAINDBNAME. */
+ if( i==0 && 0==tdsqlite3_stricmp("main", zName) ) break;
}
}
return i;
@@ -102100,12 +113498,12 @@ SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *db, const char *zName){
** index of the named database in db->aDb[], or -1 if the named db
** does not exist.
*/
-SQLITE_PRIVATE int sqlite3FindDb(sqlite3 *db, Token *pName){
+SQLITE_PRIVATE int tdsqlite3FindDb(tdsqlite3 *db, Token *pName){
int i; /* Database number */
char *zName; /* Name we are searching for */
- zName = sqlite3NameFromToken(db, pName);
- i = sqlite3FindDbName(db, zName);
- sqlite3DbFree(db, zName);
+ zName = tdsqlite3NameFromToken(db, pName);
+ i = tdsqlite3FindDbName(db, zName);
+ tdsqlite3DbFree(db, zName);
return i;
}
@@ -102125,29 +113523,30 @@ SQLITE_PRIVATE int sqlite3FindDb(sqlite3 *db, Token *pName){
** pName2) that stores the unqualified table name. The index of the
** database "xxx" is returned.
*/
-SQLITE_PRIVATE int sqlite3TwoPartName(
+SQLITE_PRIVATE int tdsqlite3TwoPartName(
Parse *pParse, /* Parsing and code generating context */
Token *pName1, /* The "xxx" in the name "xxx.yyy" or "xxx" */
Token *pName2, /* The "yyy" in the name "xxx.yyy" */
Token **pUnqual /* Write the unqualified object name here */
){
int iDb; /* Database holding the object */
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
assert( pName2!=0 );
if( pName2->n>0 ){
if( db->init.busy ) {
- sqlite3ErrorMsg(pParse, "corrupt database");
+ tdsqlite3ErrorMsg(pParse, "corrupt database");
return -1;
}
*pUnqual = pName2;
- iDb = sqlite3FindDb(db, pName1);
+ iDb = tdsqlite3FindDb(db, pName1);
if( iDb<0 ){
- sqlite3ErrorMsg(pParse, "unknown database %T", pName1);
+ tdsqlite3ErrorMsg(pParse, "unknown database %T", pName1);
return -1;
}
}else{
- assert( db->init.iDb==0 || db->init.busy || (db->flags & SQLITE_Vacuum)!=0);
+ assert( db->init.iDb==0 || db->init.busy || IN_RENAME_OBJECT
+ || (db->mDbFlags & DBFLAG_Vacuum)!=0);
iDb = db->init.iDb;
*pUnqual = pName1;
}
@@ -102155,18 +113554,60 @@ SQLITE_PRIVATE int sqlite3TwoPartName(
}
/*
+** True if PRAGMA writable_schema is ON
+*/
+SQLITE_PRIVATE int tdsqlite3WritableSchema(tdsqlite3 *db){
+ testcase( (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))==0 );
+ testcase( (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))==
+ SQLITE_WriteSchema );
+ testcase( (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))==
+ SQLITE_Defensive );
+ testcase( (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))==
+ (SQLITE_WriteSchema|SQLITE_Defensive) );
+ return (db->flags&(SQLITE_WriteSchema|SQLITE_Defensive))==SQLITE_WriteSchema;
+}
+
+/*
** This routine is used to check if the UTF-8 string zName is a legal
** unqualified name for a new schema object (table, index, view or
** trigger). All names are legal except those that begin with the string
** "sqlite_" (in upper, lower or mixed case). This portion of the namespace
** is reserved for internal use.
+**
+** When parsing the sqlite_master table, this routine also checks to
+** make sure the "type", "name", and "tbl_name" columns are consistent
+** with the SQL.
*/
-SQLITE_PRIVATE int sqlite3CheckObjectName(Parse *pParse, const char *zName){
- if( !pParse->db->init.busy && pParse->nested==0
- && (pParse->db->flags & SQLITE_WriteSchema)==0
- && 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){
- sqlite3ErrorMsg(pParse, "object name reserved for internal use: %s", zName);
- return SQLITE_ERROR;
+SQLITE_PRIVATE int tdsqlite3CheckObjectName(
+ Parse *pParse, /* Parsing context */
+ const char *zName, /* Name of the object to check */
+ const char *zType, /* Type of this object */
+ const char *zTblName /* Parent table name for triggers and indexes */
+){
+ tdsqlite3 *db = pParse->db;
+ if( tdsqlite3WritableSchema(db) || db->init.imposterTable ){
+ /* Skip these error checks for writable_schema=ON */
+ return SQLITE_OK;
+ }
+ if( db->init.busy ){
+ if( tdsqlite3_stricmp(zType, db->init.azInit[0])
+ || tdsqlite3_stricmp(zName, db->init.azInit[1])
+ || tdsqlite3_stricmp(zTblName, db->init.azInit[2])
+ ){
+ if( tdsqlite3Config.bExtraSchemaChecks ){
+ tdsqlite3ErrorMsg(pParse, ""); /* corruptSchema() will supply the error */
+ return SQLITE_ERROR;
+ }
+ }
+ }else{
+ if( (pParse->nested==0 && 0==tdsqlite3StrNICmp(zName, "sqlite_", 7))
+ || (tdsqlite3ReadOnlyShadowTables(db) && tdsqlite3ShadowTableName(db, zName))
+ ){
+ tdsqlite3ErrorMsg(pParse, "object name reserved for internal use: %s",
+ zName);
+ return SQLITE_ERROR;
+ }
+
}
return SQLITE_OK;
}
@@ -102174,17 +113615,19 @@ SQLITE_PRIVATE int sqlite3CheckObjectName(Parse *pParse, const char *zName){
/*
** Return the PRIMARY KEY index of a table
*/
-SQLITE_PRIVATE Index *sqlite3PrimaryKeyIndex(Table *pTab){
+SQLITE_PRIVATE Index *tdsqlite3PrimaryKeyIndex(Table *pTab){
Index *p;
for(p=pTab->pIndex; p && !IsPrimaryKeyIndex(p); p=p->pNext){}
return p;
}
/*
-** Return the column of index pIdx that corresponds to table
-** column iCol. Return -1 if not found.
+** Convert an table column number into a index column number. That is,
+** for the column iCol in the table (as defined by the CREATE TABLE statement)
+** find the (first) offset of that column in index pIdx. Or return -1
+** if column iCol is not used in index pIdx.
*/
-SQLITE_PRIVATE i16 sqlite3ColumnOfIndex(Index *pIdx, i16 iCol){
+SQLITE_PRIVATE i16 tdsqlite3TableColumnToIndex(Index *pIdx, i16 iCol){
int i;
for(i=0; i<pIdx->nColumn; i++){
if( iCol==pIdx->aiColumn[i] ) return i;
@@ -102192,6 +113635,84 @@ SQLITE_PRIVATE i16 sqlite3ColumnOfIndex(Index *pIdx, i16 iCol){
return -1;
}
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+/* Convert a storage column number into a table column number.
+**
+** The storage column number (0,1,2,....) is the index of the value
+** as it appears in the record on disk. The true column number
+** is the index (0,1,2,...) of the column in the CREATE TABLE statement.
+**
+** The storage column number is less than the table column number if
+** and only there are VIRTUAL columns to the left.
+**
+** If SQLITE_OMIT_GENERATED_COLUMNS, this routine is a no-op macro.
+*/
+SQLITE_PRIVATE i16 tdsqlite3StorageColumnToTable(Table *pTab, i16 iCol){
+ if( pTab->tabFlags & TF_HasVirtual ){
+ int i;
+ for(i=0; i<=iCol; i++){
+ if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) iCol++;
+ }
+ }
+ return iCol;
+}
+#endif
+
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+/* Convert a table column number into a storage column number.
+**
+** The storage column number (0,1,2,....) is the index of the value
+** as it appears in the record on disk. Or, if the input column is
+** the N-th virtual column (zero-based) then the storage number is
+** the number of non-virtual columns in the table plus N.
+**
+** The true column number is the index (0,1,2,...) of the column in
+** the CREATE TABLE statement.
+**
+** If the input column is a VIRTUAL column, then it should not appear
+** in storage. But the value sometimes is cached in registers that
+** follow the range of registers used to construct storage. This
+** avoids computing the same VIRTUAL column multiple times, and provides
+** values for use by OP_Param opcodes in triggers. Hence, if the
+** input column is a VIRTUAL table, put it after all the other columns.
+**
+** In the following, N means "normal column", S means STORED, and
+** V means VIRTUAL. Suppose the CREATE TABLE has columns like this:
+**
+** CREATE TABLE ex(N,S,V,N,S,V,N,S,V);
+** -- 0 1 2 3 4 5 6 7 8
+**
+** Then the mapping from this function is as follows:
+**
+** INPUTS: 0 1 2 3 4 5 6 7 8
+** OUTPUTS: 0 1 6 2 3 7 4 5 8
+**
+** So, in other words, this routine shifts all the virtual columns to
+** the end.
+**
+** If SQLITE_OMIT_GENERATED_COLUMNS then there are no virtual columns and
+** this routine is a no-op macro. If the pTab does not have any virtual
+** columns, then this routine is no-op that always return iCol. If iCol
+** is negative (indicating the ROWID column) then this routine return iCol.
+*/
+SQLITE_PRIVATE i16 tdsqlite3TableColumnToStorage(Table *pTab, i16 iCol){
+ int i;
+ i16 n;
+ assert( iCol<pTab->nCol );
+ if( (pTab->tabFlags & TF_HasVirtual)==0 || iCol<0 ) return iCol;
+ for(i=0, n=0; i<iCol; i++){
+ if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ) n++;
+ }
+ if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ){
+ /* iCol is a virtual column itself */
+ return pTab->nNVCol + i - n;
+ }else{
+ /* iCol is a normal or stored column */
+ return n;
+ }
+}
+#endif
+
/*
** Begin constructing a new table representation in memory. This is
** the first of several action routines that get called in response
@@ -102205,10 +113726,10 @@ SQLITE_PRIVATE i16 sqlite3ColumnOfIndex(Index *pIdx, i16 iCol){
** The new table record is initialized and put in pParse->pNewTable.
** As more of the CREATE TABLE statement is parsed, additional action
** routines will be called to add more information to this record.
-** At the end of the CREATE TABLE statement, the sqlite3EndTable() routine
+** At the end of the CREATE TABLE statement, the tdsqlite3EndTable() routine
** is called to complete the construction of the new table record.
*/
-SQLITE_PRIVATE void sqlite3StartTable(
+SQLITE_PRIVATE void tdsqlite3StartTable(
Parse *pParse, /* Parser context */
Token *pName1, /* First part of the name of the table or view */
Token *pName2, /* Second part of the name of the table or view */
@@ -102219,7 +113740,7 @@ SQLITE_PRIVATE void sqlite3StartTable(
){
Table *pTable;
char *zName = 0; /* The name of the new table */
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Vdbe *v;
int iDb; /* Database number to create the table in */
Token *pName; /* Unqualified name of the table to create */
@@ -102227,24 +113748,27 @@ SQLITE_PRIVATE void sqlite3StartTable(
if( db->init.busy && db->init.newTnum==1 ){
/* Special case: Parsing the sqlite_master or sqlite_temp_master schema */
iDb = db->init.iDb;
- zName = sqlite3DbStrDup(db, SCHEMA_TABLE(iDb));
+ zName = tdsqlite3DbStrDup(db, SCHEMA_TABLE(iDb));
pName = pName1;
}else{
/* The common case */
- iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
+ iDb = tdsqlite3TwoPartName(pParse, pName1, pName2, &pName);
if( iDb<0 ) return;
if( !OMIT_TEMPDB && isTemp && pName2->n>0 && iDb!=1 ){
/* If creating a temp table, the name may not be qualified. Unless
** the database name is "temp" anyway. */
- sqlite3ErrorMsg(pParse, "temporary table name must be unqualified");
+ tdsqlite3ErrorMsg(pParse, "temporary table name must be unqualified");
return;
}
if( !OMIT_TEMPDB && isTemp ) iDb = 1;
- zName = sqlite3NameFromToken(db, pName);
+ zName = tdsqlite3NameFromToken(db, pName);
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenMap(pParse, (void*)zName, pName);
+ }
}
pParse->sNameToken = *pName;
if( zName==0 ) return;
- if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
+ if( tdsqlite3CheckObjectName(pParse, zName, isView?"view":"table", zName) ){
goto begin_table_error;
}
if( db->init.iDb==1 ) isTemp = 1;
@@ -102259,10 +113783,10 @@ SQLITE_PRIVATE void sqlite3StartTable(
SQLITE_CREATE_TEMP_VIEW
};
char *zDb = db->aDb[iDb].zDbSName;
- if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){
goto begin_table_error;
}
- if( !isVirtual && sqlite3AuthCheck(pParse, (int)aCode[isTemp+2*isView],
+ if( !isVirtual && tdsqlite3AuthCheck(pParse, (int)aCode[isTemp+2*isView],
zName, 0, zDb) ){
goto begin_table_error;
}
@@ -102272,32 +113796,32 @@ SQLITE_PRIVATE void sqlite3StartTable(
/* Make sure the new table name does not collide with an existing
** index or table name in the same database. Issue an error message if
** it does. The exception is if the statement being parsed was passed
- ** to an sqlite3_declare_vtab() call. In that case only the column names
+ ** to an tdsqlite3_declare_vtab() call. In that case only the column names
** and types will be used, so there is no need to test for namespace
** collisions.
*/
- if( !IN_DECLARE_VTAB ){
+ if( !IN_SPECIAL_PARSE ){
char *zDb = db->aDb[iDb].zDbSName;
- if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ if( SQLITE_OK!=tdsqlite3ReadSchema(pParse) ){
goto begin_table_error;
}
- pTable = sqlite3FindTable(db, zName, zDb);
+ pTable = tdsqlite3FindTable(db, zName, zDb);
if( pTable ){
if( !noErr ){
- sqlite3ErrorMsg(pParse, "table %T already exists", pName);
+ tdsqlite3ErrorMsg(pParse, "table %T already exists", pName);
}else{
assert( !db->init.busy || CORRUPT_DB );
- sqlite3CodeVerifySchema(pParse, iDb);
+ tdsqlite3CodeVerifySchema(pParse, iDb);
}
goto begin_table_error;
}
- if( sqlite3FindIndex(db, zName, zDb)!=0 ){
- sqlite3ErrorMsg(pParse, "there is already an index named %s", zName);
+ if( tdsqlite3FindIndex(db, zName, zDb)!=0 ){
+ tdsqlite3ErrorMsg(pParse, "there is already an index named %s", zName);
goto begin_table_error;
}
}
- pTable = sqlite3DbMallocZero(db, sizeof(Table));
+ pTable = tdsqlite3DbMallocZero(db, sizeof(Table));
if( pTable==0 ){
assert( db->mallocFailed );
pParse->rc = SQLITE_NOMEM_BKPT;
@@ -102307,8 +113831,12 @@ SQLITE_PRIVATE void sqlite3StartTable(
pTable->zName = zName;
pTable->iPKey = -1;
pTable->pSchema = db->aDb[iDb].pSchema;
- pTable->nRef = 1;
- pTable->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
+ pTable->nTabRef = 1;
+#ifdef SQLITE_DEFAULT_ROWEST
+ pTable->nRowLogEst = tdsqlite3LogEst(SQLITE_DEFAULT_ROWEST);
+#else
+ pTable->nRowLogEst = 200; assert( 200==tdsqlite3LogEst(1048576) );
+#endif
assert( pParse->pNewTable==0 );
pParse->pNewTable = pTable;
@@ -102318,7 +113846,7 @@ SQLITE_PRIVATE void sqlite3StartTable(
*/
#ifndef SQLITE_OMIT_AUTOINCREMENT
if( !pParse->nested && strcmp(zName, "sqlite_sequence")==0 ){
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
pTable->pSchema->pSeqTab = pTable;
}
#endif
@@ -102331,17 +113859,17 @@ SQLITE_PRIVATE void sqlite3StartTable(
** indices. Hence, the record number for the table must be allocated
** now.
*/
- if( !db->init.busy && (v = sqlite3GetVdbe(pParse))!=0 ){
+ if( !db->init.busy && (v = tdsqlite3GetVdbe(pParse))!=0 ){
int addr1;
int fileFormat;
int reg1, reg2, reg3;
/* nullRow[] is an OP_Record encoding of a row containing 5 NULLs */
static const char nullRow[] = { 6, 0, 0, 0, 0, 0 };
- sqlite3BeginWriteOperation(pParse, 1, iDb);
+ tdsqlite3BeginWriteOperation(pParse, 1, iDb);
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( isVirtual ){
- sqlite3VdbeAddOp0(v, OP_VBegin);
+ tdsqlite3VdbeAddOp0(v, OP_VBegin);
}
#endif
@@ -102351,38 +113879,39 @@ SQLITE_PRIVATE void sqlite3StartTable(
reg1 = pParse->regRowid = ++pParse->nMem;
reg2 = pParse->regRoot = ++pParse->nMem;
reg3 = ++pParse->nMem;
- sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, reg3, BTREE_FILE_FORMAT);
- sqlite3VdbeUsesBtree(v, iDb);
- addr1 = sqlite3VdbeAddOp1(v, OP_If, reg3); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, reg3, BTREE_FILE_FORMAT);
+ tdsqlite3VdbeUsesBtree(v, iDb);
+ addr1 = tdsqlite3VdbeAddOp1(v, OP_If, reg3); VdbeCoverage(v);
fileFormat = (db->flags & SQLITE_LegacyFileFmt)!=0 ?
1 : SQLITE_MAX_FILE_FORMAT;
- sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, fileFormat);
- sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_TEXT_ENCODING, ENC(db));
- sqlite3VdbeJumpHere(v, addr1);
+ tdsqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, fileFormat);
+ tdsqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_TEXT_ENCODING, ENC(db));
+ tdsqlite3VdbeJumpHere(v, addr1);
/* This just creates a place-holder record in the sqlite_master table.
** The record created does not contain anything yet. It will be replaced
- ** by the real entry in code generated at sqlite3EndTable().
+ ** by the real entry in code generated at tdsqlite3EndTable().
**
** The rowid for the new entry is left in register pParse->regRowid.
** The root page number of the new table is left in reg pParse->regRoot.
** The rowid and root page number values are needed by the code that
- ** sqlite3EndTable will generate.
+ ** tdsqlite3EndTable will generate.
*/
#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE)
if( isView || isVirtual ){
- sqlite3VdbeAddOp2(v, OP_Integer, 0, reg2);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, reg2);
}else
#endif
{
- pParse->addrCrTab = sqlite3VdbeAddOp2(v, OP_CreateTable, iDb, reg2);
+ pParse->addrCrTab =
+ tdsqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, reg2, BTREE_INTKEY);
}
- sqlite3OpenMasterTable(pParse, iDb);
- sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1);
- sqlite3VdbeAddOp4(v, OP_Blob, 6, reg3, 0, nullRow, P4_STATIC);
- sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1);
- sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
- sqlite3VdbeAddOp0(v, OP_Close);
+ tdsqlite3OpenMasterTable(pParse, iDb);
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1);
+ tdsqlite3VdbeAddOp4(v, OP_Blob, 6, reg3, 0, nullRow, P4_STATIC);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ tdsqlite3VdbeAddOp0(v, OP_Close);
}
/* Normal (non-error) return. */
@@ -102390,7 +113919,7 @@ SQLITE_PRIVATE void sqlite3StartTable(
/* If an error occurs, we jump here */
begin_table_error:
- sqlite3DbFree(db, zName);
+ tdsqlite3DbFree(db, zName);
return;
}
@@ -102398,8 +113927,8 @@ begin_table_error:
** name of the column.
*/
#if SQLITE_ENABLE_HIDDEN_COLUMNS
-SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){
- if( sqlite3_strnicmp(pCol->zName, "__hidden__", 10)==0 ){
+SQLITE_PRIVATE void tdsqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){
+ if( tdsqlite3_strnicmp(pCol->zName, "__hidden__", 10)==0 ){
pCol->colFlags |= COLFLAG_HIDDEN;
}else if( pTab && pCol!=pTab->aCol && (pCol[-1].colFlags & COLFLAG_HIDDEN) ){
pTab->tabFlags |= TF_OOOHidden;
@@ -102412,41 +113941,40 @@ SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){
** Add a new column to the table currently being constructed.
**
** The parser calls this routine once for each column declaration
-** in a CREATE TABLE statement. sqlite3StartTable() gets called
+** in a CREATE TABLE statement. tdsqlite3StartTable() gets called
** first to get things going. Then this routine is called for each
** column.
*/
-SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
+SQLITE_PRIVATE void tdsqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
Table *p;
int i;
char *z;
char *zType;
Column *pCol;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
if( (p = pParse->pNewTable)==0 ) return;
-#if SQLITE_MAX_COLUMN
if( p->nCol+1>db->aLimit[SQLITE_LIMIT_COLUMN] ){
- sqlite3ErrorMsg(pParse, "too many columns on %s", p->zName);
+ tdsqlite3ErrorMsg(pParse, "too many columns on %s", p->zName);
return;
}
-#endif
- z = sqlite3DbMallocRaw(db, pName->n + pType->n + 2);
+ z = tdsqlite3DbMallocRaw(db, pName->n + pType->n + 2);
if( z==0 ) return;
+ if( IN_RENAME_OBJECT ) tdsqlite3RenameTokenMap(pParse, (void*)z, pName);
memcpy(z, pName->z, pName->n);
z[pName->n] = 0;
- sqlite3Dequote(z);
+ tdsqlite3Dequote(z);
for(i=0; i<p->nCol; i++){
- if( sqlite3_stricmp(z, p->aCol[i].zName)==0 ){
- sqlite3ErrorMsg(pParse, "duplicate column name: %s", z);
- sqlite3DbFree(db, z);
+ if( tdsqlite3_stricmp(z, p->aCol[i].zName)==0 ){
+ tdsqlite3ErrorMsg(pParse, "duplicate column name: %s", z);
+ tdsqlite3DbFree(db, z);
return;
}
}
if( (p->nCol & 0x7)==0 ){
Column *aNew;
- aNew = sqlite3DbRealloc(db,p->aCol,(p->nCol+8)*sizeof(p->aCol[0]));
+ aNew = tdsqlite3DbRealloc(db,p->aCol,(p->nCol+8)*sizeof(p->aCol[0]));
if( aNew==0 ){
- sqlite3DbFree(db, z);
+ tdsqlite3DbFree(db, z);
return;
}
p->aCol = aNew;
@@ -102454,22 +113982,28 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
pCol = &p->aCol[p->nCol];
memset(pCol, 0, sizeof(p->aCol[0]));
pCol->zName = z;
- sqlite3ColumnPropertiesFromName(p, pCol);
+ tdsqlite3ColumnPropertiesFromName(p, pCol);
if( pType->n==0 ){
/* If there is no type specified, columns have the default affinity
- ** 'BLOB'. */
+ ** 'BLOB' with a default size of 4 bytes. */
pCol->affinity = SQLITE_AFF_BLOB;
pCol->szEst = 1;
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( 4>=tdsqlite3GlobalConfig.szSorterRef ){
+ pCol->colFlags |= COLFLAG_SORTERREF;
+ }
+#endif
}else{
- zType = z + sqlite3Strlen30(z) + 1;
+ zType = z + tdsqlite3Strlen30(z) + 1;
memcpy(zType, pType->z, pType->n);
zType[pType->n] = 0;
- sqlite3Dequote(zType);
- pCol->affinity = sqlite3AffinityType(zType, &pCol->szEst);
+ tdsqlite3Dequote(zType);
+ pCol->affinity = tdsqlite3AffinityType(zType, pCol);
pCol->colFlags |= COLFLAG_HASTYPE;
}
p->nCol++;
+ p->nNVCol++;
pParse->constraintName.n = 0;
}
@@ -102479,11 +114013,26 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
** been seen on a column. This routine sets the notNull flag on
** the column currently under construction.
*/
-SQLITE_PRIVATE void sqlite3AddNotNull(Parse *pParse, int onError){
+SQLITE_PRIVATE void tdsqlite3AddNotNull(Parse *pParse, int onError){
Table *p;
+ Column *pCol;
p = pParse->pNewTable;
if( p==0 || NEVER(p->nCol<1) ) return;
- p->aCol[p->nCol-1].notNull = (u8)onError;
+ pCol = &p->aCol[p->nCol-1];
+ pCol->notNull = (u8)onError;
+ p->tabFlags |= TF_HasNotNull;
+
+ /* Set the uniqNotNull flag on any UNIQUE or PK indexes already created
+ ** on this column. */
+ if( pCol->colFlags & COLFLAG_UNIQUE ){
+ Index *pIdx;
+ for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){
+ assert( pIdx->nKeyCol==1 && pIdx->onError!=OE_None );
+ if( pIdx->aiColumn[0]==p->nCol-1 ){
+ pIdx->uniqNotNull = 1;
+ }
+ }
+ }
}
/*
@@ -102511,14 +114060,14 @@ SQLITE_PRIVATE void sqlite3AddNotNull(Parse *pParse, int onError){
** If none of the substrings in the above table are found,
** SQLITE_AFF_NUMERIC is returned.
*/
-SQLITE_PRIVATE char sqlite3AffinityType(const char *zIn, u8 *pszEst){
+SQLITE_PRIVATE char tdsqlite3AffinityType(const char *zIn, Column *pCol){
u32 h = 0;
char aff = SQLITE_AFF_NUMERIC;
const char *zChar = 0;
assert( zIn!=0 );
while( zIn[0] ){
- h = (h<<8) + sqlite3UpperToLower[(*zIn)&0xff];
+ h = (h<<8) + tdsqlite3UpperToLower[(*zIn)&0xff];
zIn++;
if( h==(('c'<<24)+('h'<<16)+('a'<<8)+'r') ){ /* CHAR */
aff = SQLITE_AFF_TEXT;
@@ -102548,27 +114097,32 @@ SQLITE_PRIVATE char sqlite3AffinityType(const char *zIn, u8 *pszEst){
}
}
- /* If pszEst is not NULL, store an estimate of the field size. The
+ /* If pCol is not NULL, store an estimate of the field size. The
** estimate is scaled so that the size of an integer is 1. */
- if( pszEst ){
- *pszEst = 1; /* default size is approx 4 bytes */
+ if( pCol ){
+ int v = 0; /* default size is approx 4 bytes */
if( aff<SQLITE_AFF_NUMERIC ){
if( zChar ){
while( zChar[0] ){
- if( sqlite3Isdigit(zChar[0]) ){
- int v = 0;
- sqlite3GetInt32(zChar, &v);
- v = v/4 + 1;
- if( v>255 ) v = 255;
- *pszEst = v; /* BLOB(k), VARCHAR(k), CHAR(k) -> r=(k/4+1) */
+ if( tdsqlite3Isdigit(zChar[0]) ){
+ /* BLOB(k), VARCHAR(k), CHAR(k) -> r=(k/4+1) */
+ tdsqlite3GetInt32(zChar, &v);
break;
}
zChar++;
}
}else{
- *pszEst = 5; /* BLOB, TEXT, CLOB -> r=5 (approx 20 bytes)*/
+ v = 16; /* BLOB, TEXT, CLOB -> r=5 (approx 20 bytes)*/
}
}
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( v>=tdsqlite3GlobalConfig.szSorterRef ){
+ pCol->colFlags |= COLFLAG_SORTERREF;
+ }
+#endif
+ v = v/4 + 1;
+ if( v>255 ) v = 255;
+ pCol->szEst = v;
}
return aff;
}
@@ -102583,34 +114137,47 @@ SQLITE_PRIVATE char sqlite3AffinityType(const char *zIn, u8 *pszEst){
** This routine is called by the parser while in the middle of
** parsing a CREATE TABLE statement.
*/
-SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse *pParse, ExprSpan *pSpan){
+SQLITE_PRIVATE void tdsqlite3AddDefaultValue(
+ Parse *pParse, /* Parsing context */
+ Expr *pExpr, /* The parsed expression of the default value */
+ const char *zStart, /* Start of the default value text */
+ const char *zEnd /* First character past end of defaut value text */
+){
Table *p;
Column *pCol;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
p = pParse->pNewTable;
if( p!=0 ){
+ int isInit = db->init.busy && db->init.iDb!=1;
pCol = &(p->aCol[p->nCol-1]);
- if( !sqlite3ExprIsConstantOrFunction(pSpan->pExpr, db->init.busy) ){
- sqlite3ErrorMsg(pParse, "default value of column [%s] is not constant",
+ if( !tdsqlite3ExprIsConstantOrFunction(pExpr, isInit) ){
+ tdsqlite3ErrorMsg(pParse, "default value of column [%s] is not constant",
pCol->zName);
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ }else if( pCol->colFlags & COLFLAG_GENERATED ){
+ testcase( pCol->colFlags & COLFLAG_VIRTUAL );
+ testcase( pCol->colFlags & COLFLAG_STORED );
+ tdsqlite3ErrorMsg(pParse, "cannot use DEFAULT on a generated column");
+#endif
}else{
/* A copy of pExpr is used instead of the original, as pExpr contains
- ** tokens that point to volatile memory. The 'span' of the expression
- ** is required by pragma table_info.
+ ** tokens that point to volatile memory.
*/
Expr x;
- sqlite3ExprDelete(db, pCol->pDflt);
+ tdsqlite3ExprDelete(db, pCol->pDflt);
memset(&x, 0, sizeof(x));
x.op = TK_SPAN;
- x.u.zToken = sqlite3DbStrNDup(db, (char*)pSpan->zStart,
- (int)(pSpan->zEnd - pSpan->zStart));
- x.pLeft = pSpan->pExpr;
+ x.u.zToken = tdsqlite3DbSpanDup(db, zStart, zEnd);
+ x.pLeft = pExpr;
x.flags = EP_Skip;
- pCol->pDflt = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE);
- sqlite3DbFree(db, x.u.zToken);
+ pCol->pDflt = tdsqlite3ExprDup(db, &x, EXPRDUP_REDUCE);
+ tdsqlite3DbFree(db, x.u.zToken);
}
}
- sqlite3ExprDelete(db, pSpan->pExpr);
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameExprUnmap(pParse, pExpr);
+ }
+ tdsqlite3ExprDelete(db, pExpr);
}
/*
@@ -102626,10 +114193,10 @@ SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse *pParse, ExprSpan *pSpan){
** accept it. This routine does the necessary conversion. It converts
** the expression given in its argument from a TK_STRING into a TK_ID
** if the expression is just a TK_STRING with an optional COLLATE clause.
-** If the epxression is anything other than TK_STRING, the expression is
+** If the expression is anything other than TK_STRING, the expression is
** unchanged.
*/
-static void sqlite3StringToId(Expr *p){
+static void tdsqlite3StringToId(Expr *p){
if( p->op==TK_STRING ){
p->op = TK_ID;
}else if( p->op==TK_COLLATE && p->pLeft->op==TK_STRING ){
@@ -102638,6 +114205,21 @@ static void sqlite3StringToId(Expr *p){
}
/*
+** Tag the given column as being part of the PRIMARY KEY
+*/
+static void makeColumnPartOfPrimaryKey(Parse *pParse, Column *pCol){
+ pCol->colFlags |= COLFLAG_PRIMKEY;
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ if( pCol->colFlags & COLFLAG_GENERATED ){
+ testcase( pCol->colFlags & COLFLAG_VIRTUAL );
+ testcase( pCol->colFlags & COLFLAG_STORED );
+ tdsqlite3ErrorMsg(pParse,
+ "generated columns cannot be part of the PRIMARY KEY");
+ }
+#endif
+}
+
+/*
** Designate the PRIMARY KEY for the table. pList is a list of names
** of columns that form the primary key. If pList is NULL, then the
** most recently added column of the table is the primary key.
@@ -102655,7 +114237,7 @@ static void sqlite3StringToId(Expr *p){
** If the key is not an INTEGER PRIMARY KEY, then create a unique
** index for the key. No index is created for INTEGER PRIMARY KEYs.
*/
-SQLITE_PRIVATE void sqlite3AddPrimaryKey(
+SQLITE_PRIVATE void tdsqlite3AddPrimaryKey(
Parse *pParse, /* Parsing context */
ExprList *pList, /* List of field names to be indexed */
int onError, /* What to do with a uniqueness conflict */
@@ -102668,7 +114250,7 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey(
int nTerm;
if( pTab==0 ) goto primary_key_exit;
if( pTab->tabFlags & TF_HasPrimaryKey ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"table \"%s\" has more than one primary key", pTab->zName);
goto primary_key_exit;
}
@@ -102676,20 +114258,20 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey(
if( pList==0 ){
iCol = pTab->nCol - 1;
pCol = &pTab->aCol[iCol];
- pCol->colFlags |= COLFLAG_PRIMKEY;
+ makeColumnPartOfPrimaryKey(pParse, pCol);
nTerm = 1;
}else{
nTerm = pList->nExpr;
for(i=0; i<nTerm; i++){
- Expr *pCExpr = sqlite3ExprSkipCollate(pList->a[i].pExpr);
+ Expr *pCExpr = tdsqlite3ExprSkipCollate(pList->a[i].pExpr);
assert( pCExpr!=0 );
- sqlite3StringToId(pCExpr);
+ tdsqlite3StringToId(pCExpr);
if( pCExpr->op==TK_ID ){
const char *zCName = pCExpr->u.zToken;
for(iCol=0; iCol<pTab->nCol; iCol++){
- if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zName)==0 ){
+ if( tdsqlite3StrICmp(zCName, pTab->aCol[iCol].zName)==0 ){
pCol = &pTab->aCol[iCol];
- pCol->colFlags |= COLFLAG_PRIMKEY;
+ makeColumnPartOfPrimaryKey(pParse, pCol);
break;
}
}
@@ -102698,51 +114280,56 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey(
}
if( nTerm==1
&& pCol
- && sqlite3StrICmp(sqlite3ColumnType(pCol,""), "INTEGER")==0
+ && tdsqlite3StrICmp(tdsqlite3ColumnType(pCol,""), "INTEGER")==0
&& sortOrder!=SQLITE_SO_DESC
){
+ if( IN_RENAME_OBJECT && pList ){
+ Expr *pCExpr = tdsqlite3ExprSkipCollate(pList->a[0].pExpr);
+ tdsqlite3RenameTokenRemap(pParse, &pTab->iPKey, pCExpr);
+ }
pTab->iPKey = iCol;
pTab->keyConf = (u8)onError;
assert( autoInc==0 || autoInc==1 );
pTab->tabFlags |= autoInc*TF_Autoincrement;
- if( pList ) pParse->iPkSortOrder = pList->a[0].sortOrder;
+ if( pList ) pParse->iPkSortOrder = pList->a[0].sortFlags;
+ (void)tdsqlite3HasExplicitNulls(pParse, pList);
}else if( autoInc ){
#ifndef SQLITE_OMIT_AUTOINCREMENT
- sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
+ tdsqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an "
"INTEGER PRIMARY KEY");
#endif
}else{
- sqlite3CreateIndex(pParse, 0, 0, 0, pList, onError, 0,
+ tdsqlite3CreateIndex(pParse, 0, 0, 0, pList, onError, 0,
0, sortOrder, 0, SQLITE_IDXTYPE_PRIMARYKEY);
pList = 0;
}
primary_key_exit:
- sqlite3ExprListDelete(pParse->db, pList);
+ tdsqlite3ExprListDelete(pParse->db, pList);
return;
}
/*
** Add a new CHECK constraint to the table currently under construction.
*/
-SQLITE_PRIVATE void sqlite3AddCheckConstraint(
+SQLITE_PRIVATE void tdsqlite3AddCheckConstraint(
Parse *pParse, /* Parsing context */
Expr *pCheckExpr /* The check expression */
){
#ifndef SQLITE_OMIT_CHECK
Table *pTab = pParse->pNewTable;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
if( pTab && !IN_DECLARE_VTAB
- && !sqlite3BtreeIsReadonly(db->aDb[db->init.iDb].pBt)
+ && !tdsqlite3BtreeIsReadonly(db->aDb[db->init.iDb].pBt)
){
- pTab->pCheck = sqlite3ExprListAppend(pParse, pTab->pCheck, pCheckExpr);
+ pTab->pCheck = tdsqlite3ExprListAppend(pParse, pTab->pCheck, pCheckExpr);
if( pParse->constraintName.n ){
- sqlite3ExprListSetName(pParse, pTab->pCheck, &pParse->constraintName, 1);
+ tdsqlite3ExprListSetName(pParse, pTab->pCheck, &pParse->constraintName, 1);
}
}else
#endif
{
- sqlite3ExprDelete(pParse->db, pCheckExpr);
+ tdsqlite3ExprDelete(pParse->db, pCheckExpr);
}
}
@@ -102750,21 +114337,21 @@ SQLITE_PRIVATE void sqlite3AddCheckConstraint(
** Set the collation function of the most recently parsed table column
** to the CollSeq given.
*/
-SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){
+SQLITE_PRIVATE void tdsqlite3AddCollateType(Parse *pParse, Token *pToken){
Table *p;
int i;
char *zColl; /* Dequoted name of collation sequence */
- sqlite3 *db;
+ tdsqlite3 *db;
if( (p = pParse->pNewTable)==0 ) return;
i = p->nCol-1;
db = pParse->db;
- zColl = sqlite3NameFromToken(db, pToken);
+ zColl = tdsqlite3NameFromToken(db, pToken);
if( !zColl ) return;
- if( sqlite3LocateCollSeq(pParse, zColl) ){
+ if( tdsqlite3LocateCollSeq(pParse, zColl) ){
Index *pIdx;
- sqlite3DbFree(db, p->aCol[i].zColl);
+ tdsqlite3DbFree(db, p->aCol[i].zColl);
p->aCol[i].zColl = zColl;
/* If the column is declared as "<name> PRIMARY KEY COLLATE <type>",
@@ -102778,45 +114365,62 @@ SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){
}
}
}else{
- sqlite3DbFree(db, zColl);
+ tdsqlite3DbFree(db, zColl);
}
}
-/*
-** This function returns the collation sequence for database native text
-** encoding identified by the string zName, length nName.
-**
-** If the requested collation sequence is not available, or not available
-** in the database native encoding, the collation factory is invoked to
-** request it. If the collation factory does not supply such a sequence,
-** and the sequence is available in another text encoding, then that is
-** returned instead.
-**
-** If no versions of the requested collations sequence are available, or
-** another error occurs, NULL is returned and an error message written into
-** pParse.
-**
-** This routine is a wrapper around sqlite3FindCollSeq(). This routine
-** invokes the collation factory if the named collation cannot be found
-** and generates an error message.
-**
-** See also: sqlite3FindCollSeq(), sqlite3GetCollSeq()
+/* Change the most recently parsed column to be a GENERATED ALWAYS AS
+** column.
*/
-SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName){
- sqlite3 *db = pParse->db;
- u8 enc = ENC(db);
- u8 initbusy = db->init.busy;
- CollSeq *pColl;
-
- pColl = sqlite3FindCollSeq(db, enc, zName, initbusy);
- if( !initbusy && (!pColl || !pColl->xCmp) ){
- pColl = sqlite3GetCollSeq(pParse, enc, pColl, zName);
+SQLITE_PRIVATE void tdsqlite3AddGenerated(Parse *pParse, Expr *pExpr, Token *pType){
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ u8 eType = COLFLAG_VIRTUAL;
+ Table *pTab = pParse->pNewTable;
+ Column *pCol;
+ if( pTab==0 ){
+ /* generated column in an CREATE TABLE IF NOT EXISTS that already exists */
+ goto generated_done;
+ }
+ pCol = &(pTab->aCol[pTab->nCol-1]);
+ if( IN_DECLARE_VTAB ){
+ tdsqlite3ErrorMsg(pParse, "virtual tables cannot use computed columns");
+ goto generated_done;
+ }
+ if( pCol->pDflt ) goto generated_error;
+ if( pType ){
+ if( pType->n==7 && tdsqlite3StrNICmp("virtual",pType->z,7)==0 ){
+ /* no-op */
+ }else if( pType->n==6 && tdsqlite3StrNICmp("stored",pType->z,6)==0 ){
+ eType = COLFLAG_STORED;
+ }else{
+ goto generated_error;
+ }
+ }
+ if( eType==COLFLAG_VIRTUAL ) pTab->nNVCol--;
+ pCol->colFlags |= eType;
+ assert( TF_HasVirtual==COLFLAG_VIRTUAL );
+ assert( TF_HasStored==COLFLAG_STORED );
+ pTab->tabFlags |= eType;
+ if( pCol->colFlags & COLFLAG_PRIMKEY ){
+ makeColumnPartOfPrimaryKey(pParse, pCol); /* For the error message */
}
+ pCol->pDflt = pExpr;
+ pExpr = 0;
+ goto generated_done;
- return pColl;
+generated_error:
+ tdsqlite3ErrorMsg(pParse, "error in generated column \"%s\"",
+ pCol->zName);
+generated_done:
+ tdsqlite3ExprDelete(pParse->db, pExpr);
+#else
+ /* Throw and error for the GENERATED ALWAYS AS clause if the
+ ** SQLITE_OMIT_GENERATED_COLUMNS compile-time option is used. */
+ tdsqlite3ErrorMsg(pParse, "generated columns not supported");
+ tdsqlite3ExprDelete(pParse->db, pExpr);
+#endif
}
-
/*
** Generate code that will increment the schema cookie.
**
@@ -102836,12 +114440,12 @@ SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName){
** IMPLEMENTATION-OF: R-34230-56049 SQLite automatically increments
** the schema-version whenever the schema changes.
*/
-SQLITE_PRIVATE void sqlite3ChangeCookie(Parse *pParse, int iDb){
- sqlite3 *db = pParse->db;
+SQLITE_PRIVATE void tdsqlite3ChangeCookie(Parse *pParse, int iDb){
+ tdsqlite3 *db = pParse->db;
Vdbe *v = pParse->pVdbe;
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
- sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION,
- db->aDb[iDb].pSchema->schema_cookie+1);
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
+ tdsqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION,
+ (int)(1+(unsigned)db->aDb[iDb].pSchema->schema_cookie));
}
/*
@@ -102879,10 +114483,10 @@ static void identPut(char *z, int *pIdx, char *zSignedIdent){
i = *pIdx;
for(j=0; zIdent[j]; j++){
- if( !sqlite3Isalnum(zIdent[j]) && zIdent[j]!='_' ) break;
+ if( !tdsqlite3Isalnum(zIdent[j]) && zIdent[j]!='_' ) break;
}
- needQuote = sqlite3Isdigit(zIdent[0])
- || sqlite3KeywordCode(zIdent, j)!=TK_ID
+ needQuote = tdsqlite3Isdigit(zIdent[0])
+ || tdsqlite3KeywordCode(zIdent, j)!=TK_ID
|| zIdent[j]!=0
|| j==0;
@@ -102901,7 +114505,7 @@ static void identPut(char *z, int *pIdx, char *zSignedIdent){
** table. Memory to hold the text of the statement is obtained
** from sqliteMalloc() and must be freed by the calling function.
*/
-static char *createTableStmt(sqlite3 *db, Table *p){
+static char *createTableStmt(tdsqlite3 *db, Table *p){
int i, k, n;
char *zStmt;
char *zSep, *zSep2, *zEnd;
@@ -102921,13 +114525,13 @@ static char *createTableStmt(sqlite3 *db, Table *p){
zEnd = "\n)";
}
n += 35 + 6*p->nCol;
- zStmt = sqlite3DbMallocRaw(0, n);
+ zStmt = tdsqlite3DbMallocRaw(0, n);
if( zStmt==0 ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
return 0;
}
- sqlite3_snprintf(n, zStmt, "CREATE TABLE ");
- k = sqlite3Strlen30(zStmt);
+ tdsqlite3_snprintf(n, zStmt, "CREATE TABLE ");
+ k = tdsqlite3Strlen30(zStmt);
identPut(zStmt, &k, p->zName);
zStmt[k++] = '(';
for(pCol=p->aCol, i=0; i<p->nCol; i++, pCol++){
@@ -102941,8 +114545,8 @@ static char *createTableStmt(sqlite3 *db, Table *p){
int len;
const char *zType;
- sqlite3_snprintf(n-k, &zStmt[k], zSep);
- k += sqlite3Strlen30(&zStmt[k]);
+ tdsqlite3_snprintf(n-k, &zStmt[k], zSep);
+ k += tdsqlite3Strlen30(&zStmt[k]);
zSep = zSep2;
identPut(zStmt, &k, pCol->zName);
assert( pCol->affinity-SQLITE_AFF_BLOB >= 0 );
@@ -102954,14 +114558,14 @@ static char *createTableStmt(sqlite3 *db, Table *p){
testcase( pCol->affinity==SQLITE_AFF_REAL );
zType = azType[pCol->affinity - SQLITE_AFF_BLOB];
- len = sqlite3Strlen30(zType);
+ len = tdsqlite3Strlen30(zType);
assert( pCol->affinity==SQLITE_AFF_BLOB
- || pCol->affinity==sqlite3AffinityType(zType, 0) );
+ || pCol->affinity==tdsqlite3AffinityType(zType, 0) );
memcpy(&zStmt[k], zType, len);
k += len;
assert( k<=n );
}
- sqlite3_snprintf(n-k, &zStmt[k], "%s", zEnd);
+ tdsqlite3_snprintf(n-k, &zStmt[k], "%s", zEnd);
return zStmt;
}
@@ -102969,13 +114573,13 @@ static char *createTableStmt(sqlite3 *db, Table *p){
** Resize an Index object to hold N columns total. Return SQLITE_OK
** on success and SQLITE_NOMEM on an OOM error.
*/
-static int resizeIndexObject(sqlite3 *db, Index *pIdx, int N){
+static int resizeIndexObject(tdsqlite3 *db, Index *pIdx, int N){
char *zExtra;
int nByte;
if( pIdx->nColumn>=N ) return SQLITE_OK;
assert( pIdx->isResized==0 );
nByte = (sizeof(char*) + sizeof(i16) + 1)*N;
- zExtra = sqlite3DbMallocZero(db, nByte);
+ zExtra = tdsqlite3DbMallocZero(db, nByte);
if( zExtra==0 ) return SQLITE_NOMEM_BKPT;
memcpy(zExtra, pIdx->azColl, sizeof(char*)*pIdx->nColumn);
pIdx->azColl = (const char**)zExtra;
@@ -103001,7 +114605,7 @@ static void estimateTableWidth(Table *pTab){
wTable += pTabCol->szEst;
}
if( pTab->iPKey<0 ) wTable++;
- pTab->szTabRow = sqlite3LogEst(wTable*4);
+ pTab->szTabRow = tdsqlite3LogEst(wTable*4);
}
/*
@@ -103016,16 +114620,91 @@ static void estimateIndexWidth(Index *pIdx){
assert( x<pIdx->pTable->nCol );
wIndex += x<0 ? 1 : aCol[pIdx->aiColumn[i]].szEst;
}
- pIdx->szIdxRow = sqlite3LogEst(wIndex*4);
+ pIdx->szIdxRow = tdsqlite3LogEst(wIndex*4);
}
-/* Return true if value x is found any of the first nCol entries of aiCol[]
+/* Return true if column number x is any of the first nCol entries of aiCol[].
+** This is used to determine if the column number x appears in any of the
+** first nCol entries of an index.
*/
static int hasColumn(const i16 *aiCol, int nCol, int x){
- while( nCol-- > 0 ) if( x==*(aiCol++) ) return 1;
+ while( nCol-- > 0 ){
+ assert( aiCol[0]>=0 );
+ if( x==*(aiCol++) ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** Return true if any of the first nKey entries of index pIdx exactly
+** match the iCol-th entry of pPk. pPk is always a WITHOUT ROWID
+** PRIMARY KEY index. pIdx is an index on the same table. pIdx may
+** or may not be the same index as pPk.
+**
+** The first nKey entries of pIdx are guaranteed to be ordinary columns,
+** not a rowid or expression.
+**
+** This routine differs from hasColumn() in that both the column and the
+** collating sequence must match for this routine, but for hasColumn() only
+** the column name must match.
+*/
+static int isDupColumn(Index *pIdx, int nKey, Index *pPk, int iCol){
+ int i, j;
+ assert( nKey<=pIdx->nColumn );
+ assert( iCol<MAX(pPk->nColumn,pPk->nKeyCol) );
+ assert( pPk->idxType==SQLITE_IDXTYPE_PRIMARYKEY );
+ assert( pPk->pTable->tabFlags & TF_WithoutRowid );
+ assert( pPk->pTable==pIdx->pTable );
+ testcase( pPk==pIdx );
+ j = pPk->aiColumn[iCol];
+ assert( j!=XN_ROWID && j!=XN_EXPR );
+ for(i=0; i<nKey; i++){
+ assert( pIdx->aiColumn[i]>=0 || j>=0 );
+ if( pIdx->aiColumn[i]==j
+ && tdsqlite3StrICmp(pIdx->azColl[i], pPk->azColl[iCol])==0
+ ){
+ return 1;
+ }
+ }
return 0;
}
+/* Recompute the colNotIdxed field of the Index.
+**
+** colNotIdxed is a bitmask that has a 0 bit representing each indexed
+** columns that are within the first 63 columns of the table. The
+** high-order bit of colNotIdxed is always 1. All unindexed columns
+** of the table have a 1.
+**
+** 2019-10-24: For the purpose of this computation, virtual columns are
+** not considered to be covered by the index, even if they are in the
+** index, because we do not trust the logic in whereIndexExprTrans() to be
+** able to find all instances of a reference to the indexed table column
+** and convert them into references to the index. Hence we always want
+** the actual table at hand in order to recompute the virtual column, if
+** necessary.
+**
+** The colNotIdxed mask is AND-ed with the SrcList.a[].colUsed mask
+** to determine if the index is covering index.
+*/
+static void recomputeColumnsNotIndexed(Index *pIdx){
+ Bitmask m = 0;
+ int j;
+ Table *pTab = pIdx->pTable;
+ for(j=pIdx->nColumn-1; j>=0; j--){
+ int x = pIdx->aiColumn[j];
+ if( x>=0 && (pTab->aCol[x].colFlags & COLFLAG_VIRTUAL)==0 ){
+ testcase( x==BMS-1 );
+ testcase( x==BMS-2 );
+ if( x<BMS-1 ) m |= MASKBIT(x);
+ }
+ }
+ pIdx->colNotIdxed = ~m;
+ assert( (pIdx->colNotIdxed>>63)==1 );
+}
+
/*
** This routine runs at the end of parsing a CREATE TABLE statement that
** has a WITHOUT ROWID clause. The job of this routine is to convert both
@@ -103034,9 +114713,8 @@ static int hasColumn(const i16 *aiCol, int nCol, int x){
** Changes include:
**
** (1) Set all columns of the PRIMARY KEY schema object to be NOT NULL.
-** (2) Convert the OP_CreateTable into an OP_CreateIndex. There is
-** no rowid btree for a WITHOUT ROWID. Instead, the canonical
-** data storage is a covering index btree.
+** (2) Convert P3 parameter of the OP_CreateBtree from BTREE_INTKEY
+** into BTREE_BLOBKEY.
** (3) Bypass the creation of the sqlite_master table entry
** for the PRIMARY KEY as the primary key index is now
** identified by the sqlite_master table entry of the table itself.
@@ -103044,7 +114722,7 @@ static int hasColumn(const i16 *aiCol, int nCol, int x){
** schema to the rootpage from the main table.
** (5) Add all table columns to the PRIMARY KEY Index object
** so that the PRIMARY KEY is a covering index. The surplus
-** columns are part of KeyInfo.nXField and are not used for
+** columns are part of KeyInfo.nAllField and are not used for
** sorting or lookup or uniqueness checks.
** (6) Replace the rowid tail on all automatically generated UNIQUE
** indices with the PRIMARY KEY columns.
@@ -103055,8 +114733,9 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
Index *pIdx;
Index *pPk;
int nPk;
+ int nExtra;
int i, j;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Vdbe *v = pParse->pVdbe;
/* Mark every PRIMARY KEY column as NOT NULL (except for imposter tables)
@@ -103067,19 +114746,15 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
pTab->aCol[i].notNull = OE_Abort;
}
}
+ pTab->tabFlags |= TF_HasNotNull;
}
- /* The remaining transformations only apply to b-tree tables, not to
- ** virtual tables */
- if( IN_DECLARE_VTAB ) return;
-
- /* Convert the OP_CreateTable opcode that would normally create the
- ** root-page for the table into an OP_CreateIndex opcode. The index
- ** created will become the PRIMARY KEY index.
+ /* Convert the P3 operand of the OP_CreateBtree opcode from BTREE_INTKEY
+ ** into BTREE_BLOBKEY.
*/
if( pParse->addrCrTab ){
assert( v );
- sqlite3VdbeChangeOpcode(v, pParse->addrCrTab, OP_CreateIndex);
+ tdsqlite3VdbeChangeP3(v, pParse->addrCrTab, BTREE_BLOBKEY);
}
/* Locate the PRIMARY KEY index. Or, if this table was originally
@@ -103088,28 +114763,24 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
if( pTab->iPKey>=0 ){
ExprList *pList;
Token ipkToken;
- sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zName);
- pList = sqlite3ExprListAppend(pParse, 0,
- sqlite3ExprAlloc(db, TK_ID, &ipkToken, 0));
+ tdsqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zName);
+ pList = tdsqlite3ExprListAppend(pParse, 0,
+ tdsqlite3ExprAlloc(db, TK_ID, &ipkToken, 0));
if( pList==0 ) return;
- pList->a[0].sortOrder = pParse->iPkSortOrder;
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenRemap(pParse, pList->a[0].pExpr, &pTab->iPKey);
+ }
+ pList->a[0].sortFlags = pParse->iPkSortOrder;
assert( pParse->pNewTable==pTab );
- sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0,
- SQLITE_IDXTYPE_PRIMARYKEY);
- if( db->mallocFailed ) return;
- pPk = sqlite3PrimaryKeyIndex(pTab);
pTab->iPKey = -1;
+ tdsqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0,
+ SQLITE_IDXTYPE_PRIMARYKEY);
+ if( db->mallocFailed || pParse->nErr ) return;
+ pPk = tdsqlite3PrimaryKeyIndex(pTab);
+ assert( pPk->nKeyCol==1 );
}else{
- pPk = sqlite3PrimaryKeyIndex(pTab);
-
- /* Bypass the creation of the PRIMARY KEY btree and the sqlite_master
- ** table entry. This is only required if currently generating VDBE
- ** code for a CREATE TABLE (not when parsing one as part of reading
- ** a database schema). */
- if( v ){
- assert( db->init.busy==0 );
- sqlite3VdbeChangeOpcode(v, pPk->tnum, OP_Goto);
- }
+ pPk = tdsqlite3PrimaryKeyIndex(pTab);
+ assert( pPk!=0 );
/*
** Remove all redundant columns from the PRIMARY KEY. For example, change
@@ -103117,9 +114788,12 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
** code assumes the PRIMARY KEY contains no repeated columns.
*/
for(i=j=1; i<pPk->nKeyCol; i++){
- if( hasColumn(pPk->aiColumn, j, pPk->aiColumn[i]) ){
+ if( isDupColumn(pPk, j, pPk, i) ){
pPk->nColumn--;
}else{
+ testcase( hasColumn(pPk->aiColumn, j, pPk->aiColumn[i]) );
+ pPk->azColl[j] = pPk->azColl[i];
+ pPk->aSortOrder[j] = pPk->aSortOrder[i];
pPk->aiColumn[j++] = pPk->aiColumn[i];
}
}
@@ -103128,7 +114802,16 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
assert( pPk!=0 );
pPk->isCovering = 1;
if( !db->init.imposterTable ) pPk->uniqNotNull = 1;
- nPk = pPk->nKeyCol;
+ nPk = pPk->nColumn = pPk->nKeyCol;
+
+ /* Bypass the creation of the PRIMARY KEY btree and the sqlite_master
+ ** table entry. This is only required if currently generating VDBE
+ ** code for a CREATE TABLE (not when parsing one as part of reading
+ ** a database schema). */
+ if( v && pPk->tnum>0 ){
+ assert( db->init.busy==0 );
+ tdsqlite3VdbeChangeOpcode(v, pPk->tnum, OP_Goto);
+ }
/* The root page of the PRIMARY KEY is the table root page */
pPk->tnum = pTab->tnum;
@@ -103140,7 +114823,10 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
int n;
if( IsPrimaryKeyIndex(pIdx) ) continue;
for(i=n=0; i<nPk; i++){
- if( !hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ) n++;
+ if( !isDupColumn(pIdx, pIdx->nKeyCol, pPk, i) ){
+ testcase( hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) );
+ n++;
+ }
}
if( n==0 ){
/* This index is a superset of the primary key */
@@ -103149,9 +114835,14 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
}
if( resizeIndexObject(db, pIdx, pIdx->nKeyCol+n) ) return;
for(i=0, j=pIdx->nKeyCol; i<nPk; i++){
- if( !hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ){
+ if( !isDupColumn(pIdx, pIdx->nKeyCol, pPk, i) ){
+ testcase( hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) );
pIdx->aiColumn[j] = pPk->aiColumn[i];
pIdx->azColl[j] = pPk->azColl[i];
+ if( pPk->aSortOrder[i] ){
+ /* See ticket https://www.sqlite.org/src/info/bba7b69f9849b5bf */
+ pIdx->bAscKeyBug = 1;
+ }
j++;
}
}
@@ -103161,22 +114852,54 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
/* Add all table columns to the PRIMARY KEY index
*/
- if( nPk<pTab->nCol ){
- if( resizeIndexObject(db, pPk, pTab->nCol) ) return;
- for(i=0, j=nPk; i<pTab->nCol; i++){
- if( !hasColumn(pPk->aiColumn, j, i) ){
- assert( j<pPk->nColumn );
- pPk->aiColumn[j] = i;
- pPk->azColl[j] = sqlite3StrBINARY;
- j++;
- }
+ nExtra = 0;
+ for(i=0; i<pTab->nCol; i++){
+ if( !hasColumn(pPk->aiColumn, nPk, i)
+ && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ) nExtra++;
+ }
+ if( resizeIndexObject(db, pPk, nPk+nExtra) ) return;
+ for(i=0, j=nPk; i<pTab->nCol; i++){
+ if( !hasColumn(pPk->aiColumn, j, i)
+ && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0
+ ){
+ assert( j<pPk->nColumn );
+ pPk->aiColumn[j] = i;
+ pPk->azColl[j] = tdsqlite3StrBINARY;
+ j++;
}
- assert( pPk->nColumn==j );
- assert( pTab->nCol==j );
- }else{
- pPk->nColumn = pTab->nCol;
}
+ assert( pPk->nColumn==j );
+ assert( pTab->nNVCol<=j );
+ recomputeColumnsNotIndexed(pPk);
+}
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/*
+** Return true if zName is a shadow table name in the current database
+** connection.
+**
+** zName is temporarily modified while this routine is running, but is
+** restored to its original value prior to this routine returning.
+*/
+SQLITE_PRIVATE int tdsqlite3ShadowTableName(tdsqlite3 *db, const char *zName){
+ char *zTail; /* Pointer to the last "_" in zName */
+ Table *pTab; /* Table that zName is a shadow of */
+ Module *pMod; /* Module for the virtual table */
+
+ zTail = strrchr(zName, '_');
+ if( zTail==0 ) return 0;
+ *zTail = 0;
+ pTab = tdsqlite3FindTable(db, zName, 0);
+ *zTail = '_';
+ if( pTab==0 ) return 0;
+ if( !IsVirtual(pTab) ) return 0;
+ pMod = (Module*)tdsqlite3HashFind(&db->aModule, pTab->azModuleArg[0]);
+ if( pMod==0 ) return 0;
+ if( pMod->pModule->iVersion<3 ) return 0;
+ if( pMod->pModule->xShadowName==0 ) return 0;
+ return pMod->pModule->xShadowName(zTail+1);
}
+#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */
/*
** This routine is called to report the final ")" that terminates
@@ -103198,7 +114921,7 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
** "CREATE TABLE ... AS SELECT ..." statement. The column names of
** the new table will match the result set of the SELECT.
*/
-SQLITE_PRIVATE void sqlite3EndTable(
+SQLITE_PRIVATE void tdsqlite3EndTable(
Parse *pParse, /* Parse context */
Token *pCons, /* The ',' token after the last column defn. */
Token *pEnd, /* The ')' before options in the CREATE TABLE */
@@ -103206,7 +114929,7 @@ SQLITE_PRIVATE void sqlite3EndTable(
Select *pSelect /* Select from a "CREATE ... AS SELECT" */
){
Table *p; /* The new table */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
int iDb; /* Database in which the table lives */
Index *pIdx; /* An implied index of the table */
@@ -103217,7 +114940,9 @@ SQLITE_PRIVATE void sqlite3EndTable(
p = pParse->pNewTable;
if( p==0 ) return;
- assert( !db->init.busy || !pSelect );
+ if( pSelect==0 && tdsqlite3ShadowTableName(db, p->zName) ){
+ p->tabFlags |= TF_Shadow;
+ }
/* If the db->init.busy is 1 it means we are reading the SQL off the
** "sqlite_master" or "sqlite_temp_master" table on the disk.
@@ -103229,34 +114954,79 @@ SQLITE_PRIVATE void sqlite3EndTable(
** table itself. So mark it read-only.
*/
if( db->init.busy ){
+ if( pSelect ){
+ tdsqlite3ErrorMsg(pParse, "");
+ return;
+ }
p->tnum = db->init.newTnum;
if( p->tnum==1 ) p->tabFlags |= TF_Readonly;
}
+ assert( (p->tabFlags & TF_HasPrimaryKey)==0
+ || p->iPKey>=0 || tdsqlite3PrimaryKeyIndex(p)!=0 );
+ assert( (p->tabFlags & TF_HasPrimaryKey)!=0
+ || (p->iPKey<0 && tdsqlite3PrimaryKeyIndex(p)==0) );
+
/* Special processing for WITHOUT ROWID Tables */
if( tabOpts & TF_WithoutRowid ){
if( (p->tabFlags & TF_Autoincrement) ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"AUTOINCREMENT not allowed on WITHOUT ROWID tables");
return;
}
if( (p->tabFlags & TF_HasPrimaryKey)==0 ){
- sqlite3ErrorMsg(pParse, "PRIMARY KEY missing on table %s", p->zName);
- }else{
- p->tabFlags |= TF_WithoutRowid | TF_NoVisibleRowid;
- convertToWithoutRowidTable(pParse, p);
+ tdsqlite3ErrorMsg(pParse, "PRIMARY KEY missing on table %s", p->zName);
+ return;
}
+ p->tabFlags |= TF_WithoutRowid | TF_NoVisibleRowid;
+ convertToWithoutRowidTable(pParse, p);
}
-
- iDb = sqlite3SchemaToIndex(db, p->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, p->pSchema);
#ifndef SQLITE_OMIT_CHECK
/* Resolve names in all CHECK constraint expressions.
*/
if( p->pCheck ){
- sqlite3ResolveSelfReference(pParse, p, NC_IsCheck, 0, p->pCheck);
+ tdsqlite3ResolveSelfReference(pParse, p, NC_IsCheck, 0, p->pCheck);
+ if( pParse->nErr ){
+ /* If errors are seen, delete the CHECK constraints now, else they might
+ ** actually be used if PRAGMA writable_schema=ON is set. */
+ tdsqlite3ExprListDelete(db, p->pCheck);
+ p->pCheck = 0;
+ }
}
#endif /* !defined(SQLITE_OMIT_CHECK) */
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ if( p->tabFlags & TF_HasGenerated ){
+ int ii, nNG = 0;
+ testcase( p->tabFlags & TF_HasVirtual );
+ testcase( p->tabFlags & TF_HasStored );
+ for(ii=0; ii<p->nCol; ii++){
+ u32 colFlags = p->aCol[ii].colFlags;
+ if( (colFlags & COLFLAG_GENERATED)!=0 ){
+ Expr *pX = p->aCol[ii].pDflt;
+ testcase( colFlags & COLFLAG_VIRTUAL );
+ testcase( colFlags & COLFLAG_STORED );
+ if( tdsqlite3ResolveSelfReference(pParse, p, NC_GenCol, pX, 0) ){
+ /* If there are errors in resolving the expression, change the
+ ** expression to a NULL. This prevents code generators that operate
+ ** on the expression from inserting extra parts into the expression
+ ** tree that have been allocated from lookaside memory, which is
+ ** illegal in a schema and will lead to errors or heap corruption
+ ** when the database connection closes. */
+ tdsqlite3ExprDelete(db, pX);
+ p->aCol[ii].pDflt = tdsqlite3ExprAlloc(db, TK_NULL, 0, 0);
+ }
+ }else{
+ nNG++;
+ }
+ }
+ if( nNG==0 ){
+ tdsqlite3ErrorMsg(pParse, "must have at least one non-generated column");
+ return;
+ }
+ }
+#endif
/* Estimate the average row size for the table and for all implied indices */
estimateTableWidth(p);
@@ -103277,10 +115047,10 @@ SQLITE_PRIVATE void sqlite3EndTable(
char *zType2; /* "VIEW" or "TABLE" */
char *zStmt; /* Text of the CREATE TABLE or CREATE VIEW statement */
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( NEVER(v==0) ) return;
- sqlite3VdbeAddOp1(v, OP_Close, 0);
+ tdsqlite3VdbeAddOp1(v, OP_Close, 0);
/*
** Initialize zType for the new view or table.
@@ -103301,7 +115071,7 @@ SQLITE_PRIVATE void sqlite3EndTable(
** statement to populate the new table. The root-page number for the
** new table is in register pParse->regRoot.
**
- ** Once the SELECT has been coded by sqlite3Select(), it is in a
+ ** Once the SELECT has been coded by tdsqlite3Select(), it is in a
** suitable state to query for the column names and types to be used
** by the new table.
**
@@ -103323,34 +115093,35 @@ SQLITE_PRIVATE void sqlite3EndTable(
regRec = ++pParse->nMem;
regRowid = ++pParse->nMem;
assert(pParse->nTab==1);
- sqlite3MayAbort(pParse);
- sqlite3VdbeAddOp3(v, OP_OpenWrite, 1, pParse->regRoot, iDb);
- sqlite3VdbeChangeP5(v, OPFLAG_P2ISREG);
+ tdsqlite3MayAbort(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_OpenWrite, 1, pParse->regRoot, iDb);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_P2ISREG);
pParse->nTab = 2;
- addrTop = sqlite3VdbeCurrentAddr(v) + 1;
- sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop);
- sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield);
- sqlite3Select(pParse, pSelect, &dest);
- sqlite3VdbeEndCoroutine(v, regYield);
- sqlite3VdbeJumpHere(v, addrTop - 1);
+ addrTop = tdsqlite3VdbeCurrentAddr(v) + 1;
+ tdsqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop);
if( pParse->nErr ) return;
- pSelTab = sqlite3ResultSetOfSelect(pParse, pSelect);
+ pSelTab = tdsqlite3ResultSetOfSelect(pParse, pSelect, SQLITE_AFF_BLOB);
if( pSelTab==0 ) return;
assert( p->aCol==0 );
- p->nCol = pSelTab->nCol;
+ p->nCol = p->nNVCol = pSelTab->nCol;
p->aCol = pSelTab->aCol;
pSelTab->nCol = 0;
pSelTab->aCol = 0;
- sqlite3DeleteTable(db, pSelTab);
- addrInsLoop = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm);
+ tdsqlite3DeleteTable(db, pSelTab);
+ tdsqlite3SelectDestInit(&dest, SRT_Coroutine, regYield);
+ tdsqlite3Select(pParse, pSelect, &dest);
+ if( pParse->nErr ) return;
+ tdsqlite3VdbeEndCoroutine(v, regYield);
+ tdsqlite3VdbeJumpHere(v, addrTop - 1);
+ addrInsLoop = tdsqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm);
VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, dest.iSdst, dest.nSdst, regRec);
- sqlite3TableAffinity(v, p, 0);
- sqlite3VdbeAddOp2(v, OP_NewRowid, 1, regRowid);
- sqlite3VdbeAddOp3(v, OP_Insert, 1, regRec, regRowid);
- sqlite3VdbeGoto(v, addrInsLoop);
- sqlite3VdbeJumpHere(v, addrInsLoop);
- sqlite3VdbeAddOp1(v, OP_Close, 1);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, dest.iSdst, dest.nSdst, regRec);
+ tdsqlite3TableAffinity(v, p, 0);
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, 1, regRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, 1, regRec, regRowid);
+ tdsqlite3VdbeGoto(v, addrInsLoop);
+ tdsqlite3VdbeJumpHere(v, addrInsLoop);
+ tdsqlite3VdbeAddOp1(v, OP_Close, 1);
}
/* Compute the complete text of the CREATE statement */
@@ -103360,7 +115131,7 @@ SQLITE_PRIVATE void sqlite3EndTable(
Token *pEnd2 = tabOpts ? &pParse->sLastToken : pEnd;
n = (int)(pEnd2->z - pParse->sNameToken.z);
if( pEnd2->z[0]!=';' ) n += pEnd2->n;
- zStmt = sqlite3MPrintf(db,
+ zStmt = tdsqlite3MPrintf(db,
"CREATE %s %.*s", zType2, n, pParse->sNameToken.z
);
}
@@ -103369,11 +115140,11 @@ SQLITE_PRIVATE void sqlite3EndTable(
** SQLITE_MASTER table. We just need to update that slot with all
** the information we've collected.
*/
- sqlite3NestedParse(pParse,
+ tdsqlite3NestedParse(pParse,
"UPDATE %Q.%s "
"SET type='%s', name=%Q, tbl_name=%Q, rootpage=#%d, sql=%Q "
"WHERE rowid=#%d",
- db->aDb[iDb].zDbSName, SCHEMA_TABLE(iDb),
+ db->aDb[iDb].zDbSName, MASTER_NAME,
zType,
p->zName,
p->zName,
@@ -103381,8 +115152,8 @@ SQLITE_PRIVATE void sqlite3EndTable(
zStmt,
pParse->regRowid
);
- sqlite3DbFree(db, zStmt);
- sqlite3ChangeCookie(pParse, iDb);
+ tdsqlite3DbFree(db, zStmt);
+ tdsqlite3ChangeCookie(pParse, iDb);
#ifndef SQLITE_OMIT_AUTOINCREMENT
/* Check to see if we need to create an sqlite_sequence table for
@@ -103390,9 +115161,9 @@ SQLITE_PRIVATE void sqlite3EndTable(
*/
if( (p->tabFlags & TF_Autoincrement)!=0 ){
Db *pDb = &db->aDb[iDb];
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
if( pDb->pSchema->pSeqTab==0 ){
- sqlite3NestedParse(pParse,
+ tdsqlite3NestedParse(pParse,
"CREATE TABLE %Q.sqlite_sequence(name,seq)",
pDb->zDbSName
);
@@ -103401,25 +115172,24 @@ SQLITE_PRIVATE void sqlite3EndTable(
#endif
/* Reparse everything to update our internal data structures */
- sqlite3VdbeAddParseSchemaOp(v, iDb,
- sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName));
+ tdsqlite3VdbeAddParseSchemaOp(v, iDb,
+ tdsqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName));
}
-
/* Add the table to the in-memory representation of the database.
*/
if( db->init.busy ){
Table *pOld;
Schema *pSchema = p->pSchema;
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
- pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, p);
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
+ pOld = tdsqlite3HashInsert(&pSchema->tblHash, p->zName, p);
if( pOld ){
assert( p==pOld ); /* Malloc must have failed inside HashInsert() */
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
return;
}
pParse->pNewTable = 0;
- db->flags |= SQLITE_InternChanges;
+ db->mDbFlags |= DBFLAG_SchemaChange;
#ifndef SQLITE_OMIT_ALTERTABLE
if( !p->pSelect ){
@@ -103430,7 +115200,7 @@ SQLITE_PRIVATE void sqlite3EndTable(
pCons = pEnd;
}
nName = (int)((const char *)pCons->z - zName);
- p->addColOffset = 13 + sqlite3Utf8CharLen(zName, nName);
+ p->addColOffset = 13 + tdsqlite3Utf8CharLen(zName, nName);
}
#endif
}
@@ -103440,7 +115210,7 @@ SQLITE_PRIVATE void sqlite3EndTable(
/*
** The parser calls this routine in order to create a new VIEW
*/
-SQLITE_PRIVATE void sqlite3CreateView(
+SQLITE_PRIVATE void tdsqlite3CreateView(
Parse *pParse, /* The parsing context */
Token *pBegin, /* The CREATE token that begins the statement */
Token *pName1, /* The token that holds the name of the view */
@@ -103457,34 +115227,40 @@ SQLITE_PRIVATE void sqlite3CreateView(
DbFixer sFix;
Token *pName = 0;
int iDb;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
if( pParse->nVar>0 ){
- sqlite3ErrorMsg(pParse, "parameters are not allowed in views");
+ tdsqlite3ErrorMsg(pParse, "parameters are not allowed in views");
goto create_view_fail;
}
- sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr);
+ tdsqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr);
p = pParse->pNewTable;
if( p==0 || pParse->nErr ) goto create_view_fail;
- sqlite3TwoPartName(pParse, pName1, pName2, &pName);
- iDb = sqlite3SchemaToIndex(db, p->pSchema);
- sqlite3FixInit(&sFix, pParse, iDb, "view", pName);
- if( sqlite3FixSelect(&sFix, pSelect) ) goto create_view_fail;
+ tdsqlite3TwoPartName(pParse, pName1, pName2, &pName);
+ iDb = tdsqlite3SchemaToIndex(db, p->pSchema);
+ tdsqlite3FixInit(&sFix, pParse, iDb, "view", pName);
+ if( tdsqlite3FixSelect(&sFix, pSelect) ) goto create_view_fail;
/* Make a copy of the entire SELECT statement that defines the view.
** This will force all the Expr.token.z values to be dynamically
** allocated rather than point to the input string - which means that
- ** they will persist after the current sqlite3_exec() call returns.
+ ** they will persist after the current tdsqlite3_exec() call returns.
*/
- p->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE);
- p->pCheck = sqlite3ExprListDup(db, pCNames, EXPRDUP_REDUCE);
+ pSelect->selFlags |= SF_View;
+ if( IN_RENAME_OBJECT ){
+ p->pSelect = pSelect;
+ pSelect = 0;
+ }else{
+ p->pSelect = tdsqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE);
+ }
+ p->pCheck = tdsqlite3ExprListDup(db, pCNames, EXPRDUP_REDUCE);
if( db->mallocFailed ) goto create_view_fail;
/* Locate the end of the CREATE VIEW statement. Make sEnd point to
** the end.
*/
sEnd = pParse->sLastToken;
- assert( sEnd.z[0]!=0 );
+ assert( sEnd.z[0]!=0 || sEnd.n==0 );
if( sEnd.z[0]!=';' ){
sEnd.z += sEnd.n;
}
@@ -103492,16 +115268,19 @@ SQLITE_PRIVATE void sqlite3CreateView(
n = (int)(sEnd.z - pBegin->z);
assert( n>0 );
z = pBegin->z;
- while( sqlite3Isspace(z[n-1]) ){ n--; }
+ while( tdsqlite3Isspace(z[n-1]) ){ n--; }
sEnd.z = &z[n-1];
sEnd.n = 1;
- /* Use sqlite3EndTable() to add the view to the SQLITE_MASTER table */
- sqlite3EndTable(pParse, 0, &sEnd, 0, 0);
+ /* Use tdsqlite3EndTable() to add the view to the SQLITE_MASTER table */
+ tdsqlite3EndTable(pParse, 0, &sEnd, 0, 0);
create_view_fail:
- sqlite3SelectDelete(db, pSelect);
- sqlite3ExprListDelete(db, pCNames);
+ tdsqlite3SelectDelete(db, pSelect);
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameExprlistUnmap(pParse, pCNames);
+ }
+ tdsqlite3ExprListDelete(db, pCNames);
return;
}
#endif /* SQLITE_OMIT_VIEW */
@@ -103512,21 +115291,27 @@ create_view_fail:
** the columns of the view in the pTable structure. Return the number
** of errors. If an error is seen leave an error message in pParse->zErrMsg.
*/
-SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
+SQLITE_PRIVATE int tdsqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
Table *pSelTab; /* A fake table from which we get the result set */
Select *pSel; /* Copy of the SELECT that implements the view */
int nErr = 0; /* Number of errors encountered */
int n; /* Temporarily holds the number of cursors assigned */
- sqlite3 *db = pParse->db; /* Database connection for malloc errors */
+ tdsqlite3 *db = pParse->db; /* Database connection for malloc errors */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ int rc;
+#endif
#ifndef SQLITE_OMIT_AUTHORIZATION
- sqlite3_xauth xAuth; /* Saved xAuth pointer */
+ tdsqlite3_xauth xAuth; /* Saved xAuth pointer */
#endif
assert( pTable );
#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( sqlite3VtabCallConnect(pParse, pTable) ){
- return SQLITE_ERROR;
+ db->nSchemaLock++;
+ rc = tdsqlite3VtabCallConnect(pParse, pTable);
+ db->nSchemaLock--;
+ if( rc ){
+ return 1;
}
if( IsVirtual(pTable) ) return 0;
#endif
@@ -103553,50 +115338,58 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
** SELECT * FROM temp.ex1;
*/
if( pTable->nCol<0 ){
- sqlite3ErrorMsg(pParse, "view %s is circularly defined", pTable->zName);
+ tdsqlite3ErrorMsg(pParse, "view %s is circularly defined", pTable->zName);
return 1;
}
assert( pTable->nCol>=0 );
/* If we get this far, it means we need to compute the table names.
- ** Note that the call to sqlite3ResultSetOfSelect() will expand any
+ ** Note that the call to tdsqlite3ResultSetOfSelect() will expand any
** "*" elements in the results set of the view and will assign cursors
** to the elements of the FROM clause. But we do not want these changes
** to be permanent. So the computation is done on a copy of the SELECT
** statement that defines the view.
*/
assert( pTable->pSelect );
- pSel = sqlite3SelectDup(db, pTable->pSelect, 0);
+ pSel = tdsqlite3SelectDup(db, pTable->pSelect, 0);
if( pSel ){
+#ifndef SQLITE_OMIT_ALTERTABLE
+ u8 eParseMode = pParse->eParseMode;
+ pParse->eParseMode = PARSE_MODE_NORMAL;
+#endif
n = pParse->nTab;
- sqlite3SrcListAssignCursors(pParse, pSel->pSrc);
+ tdsqlite3SrcListAssignCursors(pParse, pSel->pSrc);
pTable->nCol = -1;
- db->lookaside.bDisable++;
+ DisableLookaside;
#ifndef SQLITE_OMIT_AUTHORIZATION
xAuth = db->xAuth;
db->xAuth = 0;
- pSelTab = sqlite3ResultSetOfSelect(pParse, pSel);
+ pSelTab = tdsqlite3ResultSetOfSelect(pParse, pSel, SQLITE_AFF_NONE);
db->xAuth = xAuth;
#else
- pSelTab = sqlite3ResultSetOfSelect(pParse, pSel);
+ pSelTab = tdsqlite3ResultSetOfSelect(pParse, pSel, SQLITE_AFF_NONE);
#endif
pParse->nTab = n;
- if( pTable->pCheck ){
+ if( pSelTab==0 ){
+ pTable->nCol = 0;
+ nErr++;
+ }else if( pTable->pCheck ){
/* CREATE VIEW name(arglist) AS ...
** The names of the columns in the table are taken from
** arglist which is stored in pTable->pCheck. The pCheck field
** normally holds CHECK constraints on an ordinary table, but for
** a VIEW it holds the list of column names.
*/
- sqlite3ColumnsFromExprList(pParse, pTable->pCheck,
+ tdsqlite3ColumnsFromExprList(pParse, pTable->pCheck,
&pTable->nCol, &pTable->aCol);
if( db->mallocFailed==0
&& pParse->nErr==0
&& pTable->nCol==pSel->pEList->nExpr
){
- sqlite3SelectAddColumnTypeAndCollation(pParse, pTable, pSel);
+ tdsqlite3SelectAddColumnTypeAndCollation(pParse, pTable, pSel,
+ SQLITE_AFF_NONE);
}
- }else if( pSelTab ){
+ }else{
/* CREATE VIEW name AS... without an argument list. Construct
** the column names from the SELECT statement that defines the view.
*/
@@ -103605,18 +115398,24 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
pTable->aCol = pSelTab->aCol;
pSelTab->nCol = 0;
pSelTab->aCol = 0;
- assert( sqlite3SchemaMutexHeld(db, 0, pTable->pSchema) );
- }else{
- pTable->nCol = 0;
- nErr++;
+ assert( tdsqlite3SchemaMutexHeld(db, 0, pTable->pSchema) );
}
- sqlite3DeleteTable(db, pSelTab);
- sqlite3SelectDelete(db, pSel);
- db->lookaside.bDisable--;
+ pTable->nNVCol = pTable->nCol;
+ tdsqlite3DeleteTable(db, pSelTab);
+ tdsqlite3SelectDelete(db, pSel);
+ EnableLookaside;
+#ifndef SQLITE_OMIT_ALTERTABLE
+ pParse->eParseMode = eParseMode;
+#endif
} else {
nErr++;
}
pTable->pSchema->schemaFlags |= DB_UnresetViews;
+ if( db->mallocFailed ){
+ tdsqlite3DeleteColumnNames(db, pTable);
+ pTable->aCol = 0;
+ pTable->nCol = 0;
+ }
#endif /* SQLITE_OMIT_VIEW */
return nErr;
}
@@ -103626,14 +115425,14 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){
/*
** Clear the column names from every VIEW in database idx.
*/
-static void sqliteViewResetAll(sqlite3 *db, int idx){
+static void sqliteViewResetAll(tdsqlite3 *db, int idx){
HashElem *i;
- assert( sqlite3SchemaMutexHeld(db, idx, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, idx, 0) );
if( !DbHasProperty(db, idx, DB_UnresetViews) ) return;
for(i=sqliteHashFirst(&db->aDb[idx].pSchema->tblHash); i;i=sqliteHashNext(i)){
Table *pTab = sqliteHashData(i);
if( pTab->pSelect ){
- sqlite3DeleteColumnNames(db, pTab);
+ tdsqlite3DeleteColumnNames(db, pTab);
pTab->aCol = 0;
pTab->nCol = 0;
}
@@ -103662,12 +115461,12 @@ static void sqliteViewResetAll(sqlite3 *db, int idx){
** in order to be certain that we got the right one.
*/
#ifndef SQLITE_OMIT_AUTOVACUUM
-SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3 *db, int iDb, int iFrom, int iTo){
+SQLITE_PRIVATE void tdsqlite3RootPageMoved(tdsqlite3 *db, int iDb, int iFrom, int iTo){
HashElem *pElem;
Hash *pHash;
Db *pDb;
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
pDb = &db->aDb[iDb];
pHash = &pDb->pSchema->tblHash;
for(pElem=sqliteHashFirst(pHash); pElem; pElem=sqliteHashNext(pElem)){
@@ -103693,11 +115492,11 @@ SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3 *db, int iDb, int iFrom, int iT
** erasing iTable (this can happen with an auto-vacuum database).
*/
static void destroyRootPage(Parse *pParse, int iTable, int iDb){
- Vdbe *v = sqlite3GetVdbe(pParse);
- int r1 = sqlite3GetTempReg(pParse);
- assert( iTable>1 );
- sqlite3VdbeAddOp3(v, OP_Destroy, iTable, r1, iDb);
- sqlite3MayAbort(pParse);
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ int r1 = tdsqlite3GetTempReg(pParse);
+ if( iTable<2 ) tdsqlite3ErrorMsg(pParse, "corrupt schema");
+ tdsqlite3VdbeAddOp3(v, OP_Destroy, iTable, r1, iDb);
+ tdsqlite3MayAbort(pParse);
#ifndef SQLITE_OMIT_AUTOVACUUM
/* OP_Destroy stores an in integer r1. If this integer
** is non-zero, then it is the root page number of a table moved to
@@ -103708,11 +115507,11 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){
** is in register NNN. See grammar rules associated with the TK_REGISTER
** token for additional information.
*/
- sqlite3NestedParse(pParse,
+ tdsqlite3NestedParse(pParse,
"UPDATE %Q.%s SET rootpage=%d WHERE #%d AND rootpage=#%d",
- pParse->db->aDb[iDb].zDbSName, SCHEMA_TABLE(iDb), iTable, r1, r1);
+ pParse->db->aDb[iDb].zDbSName, MASTER_NAME, iTable, r1, r1);
#endif
- sqlite3ReleaseTempReg(pParse, r1);
+ tdsqlite3ReleaseTempReg(pParse, r1);
}
/*
@@ -103722,14 +115521,6 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){
** is also added (this can happen with an auto-vacuum database).
*/
static void destroyTable(Parse *pParse, Table *pTab){
-#ifdef SQLITE_OMIT_AUTOVACUUM
- Index *pIdx;
- int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
- destroyRootPage(pParse, pTab->tnum, iDb);
- for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- destroyRootPage(pParse, pIdx->tnum, iDb);
- }
-#else
/* If the database may be auto-vacuum capable (if SQLITE_OMIT_AUTOVACUUM
** is not defined), then it is important to call OP_Destroy on the
** table and index root-pages in order, starting with the numerically
@@ -103766,20 +115557,19 @@ static void destroyTable(Parse *pParse, Table *pTab){
if( iLargest==0 ){
return;
}else{
- int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ int iDb = tdsqlite3SchemaToIndex(pParse->db, pTab->pSchema);
assert( iDb>=0 && iDb<pParse->db->nDb );
destroyRootPage(pParse, iLargest, iDb);
iDestroyed = iLargest;
}
}
-#endif
}
/*
** Remove entries from the sqlite_statN tables (for N in (1,2,3))
** after a DROP INDEX or DROP TABLE command.
*/
-static void sqlite3ClearStatTables(
+static void tdsqlite3ClearStatTables(
Parse *pParse, /* The parsing context */
int iDb, /* The database number */
const char *zType, /* "idx" or "tbl" */
@@ -103789,9 +115579,9 @@ static void sqlite3ClearStatTables(
const char *zDbName = pParse->db->aDb[iDb].zDbSName;
for(i=1; i<=4; i++){
char zTab[24];
- sqlite3_snprintf(sizeof(zTab),zTab,"sqlite_stat%d",i);
- if( sqlite3FindTable(pParse->db, zTab, zDbName) ){
- sqlite3NestedParse(pParse,
+ tdsqlite3_snprintf(sizeof(zTab),zTab,"sqlite_stat%d",i);
+ if( tdsqlite3FindTable(pParse->db, zTab, zDbName) ){
+ tdsqlite3NestedParse(pParse,
"DELETE FROM %Q.%s WHERE %s=%Q",
zDbName, zTab, zType, zName
);
@@ -103802,19 +115592,19 @@ static void sqlite3ClearStatTables(
/*
** Generate code to drop a table.
*/
-SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){
+SQLITE_PRIVATE void tdsqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, int isView){
Vdbe *v;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Trigger *pTrigger;
Db *pDb = &db->aDb[iDb];
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
assert( v!=0 );
- sqlite3BeginWriteOperation(pParse, 1, iDb);
+ tdsqlite3BeginWriteOperation(pParse, 1, iDb);
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pTab) ){
- sqlite3VdbeAddOp0(v, OP_VBegin);
+ tdsqlite3VdbeAddOp0(v, OP_VBegin);
}
#endif
@@ -103822,11 +115612,11 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in
** is generated to remove entries from sqlite_master and/or
** sqlite_temp_master if required.
*/
- pTrigger = sqlite3TriggerList(pParse, pTab);
+ pTrigger = tdsqlite3TriggerList(pParse, pTab);
while( pTrigger ){
assert( pTrigger->pSchema==pTab->pSchema ||
pTrigger->pSchema==db->aDb[1].pSchema );
- sqlite3DropTriggerPtr(pParse, pTrigger);
+ tdsqlite3DropTriggerPtr(pParse, pTrigger);
pTrigger = pTrigger->pNext;
}
@@ -103837,7 +115627,7 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in
** move as a result of the drop (can happen in auto-vacuum mode).
*/
if( pTab->tabFlags & TF_Autoincrement ){
- sqlite3NestedParse(pParse,
+ tdsqlite3NestedParse(pParse,
"DELETE FROM %Q.sqlite_sequence WHERE name=%Q",
pDb->zDbSName, pTab->zName
);
@@ -103851,9 +115641,9 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in
** created in the temp database that refers to a table in another
** database.
*/
- sqlite3NestedParse(pParse,
+ tdsqlite3NestedParse(pParse,
"DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'",
- pDb->zDbSName, SCHEMA_TABLE(iDb), pTab->zName);
+ pDb->zDbSName, MASTER_NAME, pTab->zName);
if( !isView && !IsVirtual(pTab) ){
destroyTable(pParse, pTab);
}
@@ -103862,21 +115652,53 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in
** the schema cookie.
*/
if( IsVirtual(pTab) ){
- sqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0);
+ tdsqlite3VdbeAddOp4(v, OP_VDestroy, iDb, 0, 0, pTab->zName, 0);
+ tdsqlite3MayAbort(pParse);
}
- sqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0);
- sqlite3ChangeCookie(pParse, iDb);
+ tdsqlite3VdbeAddOp4(v, OP_DropTable, iDb, 0, 0, pTab->zName, 0);
+ tdsqlite3ChangeCookie(pParse, iDb);
sqliteViewResetAll(db, iDb);
}
/*
+** Return TRUE if shadow tables should be read-only in the current
+** context.
+*/
+SQLITE_PRIVATE int tdsqlite3ReadOnlyShadowTables(tdsqlite3 *db){
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( (db->flags & SQLITE_Defensive)!=0
+ && db->pVtabCtx==0
+ && db->nVdbeExec==0
+ ){
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/*
+** Return true if it is not allowed to drop the given table
+*/
+static int tableMayNotBeDropped(tdsqlite3 *db, Table *pTab){
+ if( tdsqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){
+ if( tdsqlite3StrNICmp(pTab->zName+7, "stat", 4)==0 ) return 0;
+ if( tdsqlite3StrNICmp(pTab->zName+7, "parameters", 10)==0 ) return 0;
+ return 1;
+ }
+ if( (pTab->tabFlags & TF_Shadow)!=0 && tdsqlite3ReadOnlyShadowTables(db) ){
+ return 1;
+ }
+ return 0;
+}
+
+/*
** This routine is called to do the work of a DROP TABLE statement.
** pName is the name of the table to be dropped.
*/
-SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
+SQLITE_PRIVATE void tdsqlite3DropTable(Parse *pParse, SrcList *pName, int isView, int noErr){
Table *pTab;
Vdbe *v;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
int iDb;
if( db->mallocFailed ){
@@ -103884,23 +115706,23 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView,
}
assert( pParse->nErr==0 );
assert( pName->nSrc==1 );
- if( sqlite3ReadSchema(pParse) ) goto exit_drop_table;
+ if( tdsqlite3ReadSchema(pParse) ) goto exit_drop_table;
if( noErr ) db->suppressErr++;
assert( isView==0 || isView==LOCATE_VIEW );
- pTab = sqlite3LocateTableItem(pParse, isView, &pName->a[0]);
+ pTab = tdsqlite3LocateTableItem(pParse, isView, &pName->a[0]);
if( noErr ) db->suppressErr--;
if( pTab==0 ){
- if( noErr ) sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
+ if( noErr ) tdsqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
goto exit_drop_table;
}
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
assert( iDb>=0 && iDb<db->nDb );
/* If pTab is a virtual table, call ViewGetColumnNames() to ensure
** it is initialized.
*/
- if( IsVirtual(pTab) && sqlite3ViewGetColumnNames(pParse, pTab) ){
+ if( IsVirtual(pTab) && tdsqlite3ViewGetColumnNames(pParse, pTab) ){
goto exit_drop_table;
}
#ifndef SQLITE_OMIT_AUTHORIZATION
@@ -103909,7 +115731,7 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView,
const char *zTab = SCHEMA_TABLE(iDb);
const char *zDb = db->aDb[iDb].zDbSName;
const char *zArg2 = 0;
- if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb)){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb)){
goto exit_drop_table;
}
if( isView ){
@@ -103921,7 +115743,7 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView,
#ifndef SQLITE_OMIT_VIRTUALTABLE
}else if( IsVirtual(pTab) ){
code = SQLITE_DROP_VTABLE;
- zArg2 = sqlite3GetVTable(db, pTab)->pMod->zName;
+ zArg2 = tdsqlite3GetVTable(db, pTab)->pMod->zName;
#endif
}else{
if( !OMIT_TEMPDB && iDb==1 ){
@@ -103930,17 +115752,16 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView,
code = SQLITE_DROP_TABLE;
}
}
- if( sqlite3AuthCheck(pParse, code, pTab->zName, zArg2, zDb) ){
+ if( tdsqlite3AuthCheck(pParse, code, pTab->zName, zArg2, zDb) ){
goto exit_drop_table;
}
- if( sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb) ){
goto exit_drop_table;
}
}
#endif
- if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0
- && sqlite3StrNICmp(pTab->zName, "sqlite_stat", 11)!=0 ){
- sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName);
+ if( tableMayNotBeDropped(db, pTab) ){
+ tdsqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName);
goto exit_drop_table;
}
@@ -103949,11 +115770,11 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView,
** on a table.
*/
if( isView && pTab->pSelect==0 ){
- sqlite3ErrorMsg(pParse, "use DROP TABLE to delete table %s", pTab->zName);
+ tdsqlite3ErrorMsg(pParse, "use DROP TABLE to delete table %s", pTab->zName);
goto exit_drop_table;
}
if( !isView && pTab->pSelect ){
- sqlite3ErrorMsg(pParse, "use DROP VIEW to delete view %s", pTab->zName);
+ tdsqlite3ErrorMsg(pParse, "use DROP VIEW to delete view %s", pTab->zName);
goto exit_drop_table;
}
#endif
@@ -103961,16 +115782,18 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView,
/* Generate code to remove the table from the master table
** on disk.
*/
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( v ){
- sqlite3BeginWriteOperation(pParse, 1, iDb);
- sqlite3ClearStatTables(pParse, iDb, "tbl", pTab->zName);
- sqlite3FkDropTable(pParse, pName, pTab);
- sqlite3CodeDropTable(pParse, pTab, iDb, isView);
+ tdsqlite3BeginWriteOperation(pParse, 1, iDb);
+ if( !isView ){
+ tdsqlite3ClearStatTables(pParse, iDb, "tbl", pTab->zName);
+ tdsqlite3FkDropTable(pParse, pName, pTab);
+ }
+ tdsqlite3CodeDropTable(pParse, pTab, iDb, isView);
}
exit_drop_table:
- sqlite3SrcListDelete(db, pName);
+ tdsqlite3SrcListDelete(db, pName);
}
/*
@@ -103987,16 +115810,16 @@ exit_drop_table:
** under construction in the pParse->pNewTable field.
**
** The foreign key is set for IMMEDIATE processing. A subsequent call
-** to sqlite3DeferForeignKey() might change this to DEFERRED.
+** to tdsqlite3DeferForeignKey() might change this to DEFERRED.
*/
-SQLITE_PRIVATE void sqlite3CreateForeignKey(
+SQLITE_PRIVATE void tdsqlite3CreateForeignKey(
Parse *pParse, /* Parsing context */
ExprList *pFromCol, /* Columns in this table that point to other table */
Token *pTo, /* Name of the other table */
ExprList *pToCol, /* Columns in the other table */
int flags /* Conflict resolution algorithms. */
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
#ifndef SQLITE_OMIT_FOREIGN_KEY
FKey *pFKey = 0;
FKey *pNextTo;
@@ -104012,14 +115835,14 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey(
int iCol = p->nCol-1;
if( NEVER(iCol<0) ) goto fk_end;
if( pToCol && pToCol->nExpr!=1 ){
- sqlite3ErrorMsg(pParse, "foreign key on %s"
+ tdsqlite3ErrorMsg(pParse, "foreign key on %s"
" should reference only one column of table %T",
p->aCol[iCol].zName, pTo);
goto fk_end;
}
nCol = 1;
}else if( pToCol && pToCol->nExpr!=pFromCol->nExpr ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"number of columns in foreign key does not match the number of "
"columns in the referenced table");
goto fk_end;
@@ -104029,10 +115852,10 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey(
nByte = sizeof(*pFKey) + (nCol-1)*sizeof(pFKey->aCol[0]) + pTo->n + 1;
if( pToCol ){
for(i=0; i<pToCol->nExpr; i++){
- nByte += sqlite3Strlen30(pToCol->a[i].zName) + 1;
+ nByte += tdsqlite3Strlen30(pToCol->a[i].zEName) + 1;
}
}
- pFKey = sqlite3DbMallocZero(db, nByte );
+ pFKey = tdsqlite3DbMallocZero(db, nByte );
if( pFKey==0 ){
goto fk_end;
}
@@ -104040,9 +115863,12 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey(
pFKey->pNextFrom = p->pFKey;
z = (char*)&pFKey->aCol[nCol];
pFKey->zTo = z;
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenMap(pParse, (void*)z, pTo);
+ }
memcpy(z, pTo->z, pTo->n);
z[pTo->n] = 0;
- sqlite3Dequote(z);
+ tdsqlite3Dequote(z);
z += pTo->n+1;
pFKey->nCol = nCol;
if( pFromCol==0 ){
@@ -104051,24 +115877,30 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey(
for(i=0; i<nCol; i++){
int j;
for(j=0; j<p->nCol; j++){
- if( sqlite3StrICmp(p->aCol[j].zName, pFromCol->a[i].zName)==0 ){
+ if( tdsqlite3StrICmp(p->aCol[j].zName, pFromCol->a[i].zEName)==0 ){
pFKey->aCol[i].iFrom = j;
break;
}
}
if( j>=p->nCol ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"unknown column \"%s\" in foreign key definition",
- pFromCol->a[i].zName);
+ pFromCol->a[i].zEName);
goto fk_end;
}
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenRemap(pParse, &pFKey->aCol[i], pFromCol->a[i].zEName);
+ }
}
}
if( pToCol ){
for(i=0; i<nCol; i++){
- int n = sqlite3Strlen30(pToCol->a[i].zName);
+ int n = tdsqlite3Strlen30(pToCol->a[i].zEName);
pFKey->aCol[i].zCol = z;
- memcpy(z, pToCol->a[i].zName, n);
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenRemap(pParse, z, pToCol->a[i].zEName);
+ }
+ memcpy(z, pToCol->a[i].zEName, n);
z[n] = 0;
z += n+1;
}
@@ -104077,12 +115909,12 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey(
pFKey->aAction[0] = (u8)(flags & 0xff); /* ON DELETE action */
pFKey->aAction[1] = (u8)((flags >> 8 ) & 0xff); /* ON UPDATE action */
- assert( sqlite3SchemaMutexHeld(db, 0, p->pSchema) );
- pNextTo = (FKey *)sqlite3HashInsert(&p->pSchema->fkeyHash,
+ assert( tdsqlite3SchemaMutexHeld(db, 0, p->pSchema) );
+ pNextTo = (FKey *)tdsqlite3HashInsert(&p->pSchema->fkeyHash,
pFKey->zTo, (void *)pFKey
);
if( pNextTo==pFKey ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
goto fk_end;
}
if( pNextTo ){
@@ -104097,10 +115929,10 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey(
pFKey = 0;
fk_end:
- sqlite3DbFree(db, pFKey);
+ tdsqlite3DbFree(db, pFKey);
#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */
- sqlite3ExprListDelete(db, pFromCol);
- sqlite3ExprListDelete(db, pToCol);
+ tdsqlite3ExprListDelete(db, pFromCol);
+ tdsqlite3ExprListDelete(db, pToCol);
}
/*
@@ -104110,7 +115942,7 @@ fk_end:
** The behavior of the most recently created foreign key is adjusted
** accordingly.
*/
-SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse *pParse, int isDeferred){
+SQLITE_PRIVATE void tdsqlite3DeferForeignKey(Parse *pParse, int isDeferred){
#ifndef SQLITE_OMIT_FOREIGN_KEY
Table *pTab;
FKey *pFKey;
@@ -104131,7 +115963,7 @@ SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse *pParse, int isDeferred){
** the index already exists and must be cleared before being refilled and
** the root page number of the index is taken from pIndex->tnum.
*/
-static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
+static void tdsqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
Table *pTab = pIndex->pTable; /* The table that is indexed */
int iTab = pParse->nTab++; /* Btree cursor used for pTab */
int iIdx = pParse->nTab++; /* Btree cursor used for pIndex */
@@ -104143,72 +115975,91 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
Vdbe *v; /* Generate code into this virtual machine */
KeyInfo *pKey; /* KeyInfo for index */
int regRecord; /* Register holding assembled index record */
- sqlite3 *db = pParse->db; /* The database connection */
- int iDb = sqlite3SchemaToIndex(db, pIndex->pSchema);
+ tdsqlite3 *db = pParse->db; /* The database connection */
+ int iDb = tdsqlite3SchemaToIndex(db, pIndex->pSchema);
#ifndef SQLITE_OMIT_AUTHORIZATION
- if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0,
+ if( tdsqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0,
db->aDb[iDb].zDbSName ) ){
return;
}
#endif
/* Require a write-lock on the table to perform this operation */
- sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
+ tdsqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( v==0 ) return;
if( memRootPage>=0 ){
tnum = memRootPage;
}else{
tnum = pIndex->tnum;
}
- pKey = sqlite3KeyInfoOfIndex(pParse, pIndex);
+ pKey = tdsqlite3KeyInfoOfIndex(pParse, pIndex);
assert( pKey!=0 || db->mallocFailed || pParse->nErr );
/* Open the sorter cursor if we are to use one. */
iSorter = pParse->nTab++;
- sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, pIndex->nKeyCol, (char*)
- sqlite3KeyInfoRef(pKey), P4_KEYINFO);
+ tdsqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, pIndex->nKeyCol, (char*)
+ tdsqlite3KeyInfoRef(pKey), P4_KEYINFO);
/* Open the table. Loop through all rows of the table, inserting index
** records into the sorter. */
- sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead);
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0); VdbeCoverage(v);
- regRecord = sqlite3GetTempReg(pParse);
-
- sqlite3GenerateIndexKey(pParse,pIndex,iTab,regRecord,0,&iPartIdxLabel,0,0);
- sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
- sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
- sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr1);
- if( memRootPage<0 ) sqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb);
- sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, iDb,
+ tdsqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead);
+ addr1 = tdsqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0); VdbeCoverage(v);
+ regRecord = tdsqlite3GetTempReg(pParse);
+ tdsqlite3MultiWrite(pParse);
+
+ tdsqlite3GenerateIndexKey(pParse,pIndex,iTab,regRecord,0,&iPartIdxLabel,0,0);
+ tdsqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord);
+ tdsqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
+ tdsqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addr1);
+ if( memRootPage<0 ) tdsqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb);
+ tdsqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, iDb,
(char *)pKey, P4_KEYINFO);
- sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0));
+ tdsqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0));
- addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); VdbeCoverage(v);
+ addr1 = tdsqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); VdbeCoverage(v);
if( IsUniqueIndex(pIndex) ){
- int j2 = sqlite3VdbeCurrentAddr(v) + 3;
- sqlite3VdbeGoto(v, j2);
- addr2 = sqlite3VdbeCurrentAddr(v);
- sqlite3VdbeAddOp4Int(v, OP_SorterCompare, iSorter, j2, regRecord,
+ int j2 = tdsqlite3VdbeGoto(v, 1);
+ addr2 = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeVerifyAbortable(v, OE_Abort);
+ tdsqlite3VdbeAddOp4Int(v, OP_SorterCompare, iSorter, j2, regRecord,
pIndex->nKeyCol); VdbeCoverage(v);
- sqlite3UniqueConstraint(pParse, OE_Abort, pIndex);
- }else{
- addr2 = sqlite3VdbeCurrentAddr(v);
+ tdsqlite3UniqueConstraint(pParse, OE_Abort, pIndex);
+ tdsqlite3VdbeJumpHere(v, j2);
+ }else{
+ /* Most CREATE INDEX and REINDEX statements that are not UNIQUE can not
+ ** abort. The exception is if one of the indexed expressions contains a
+ ** user function that throws an exception when it is evaluated. But the
+ ** overhead of adding a statement journal to a CREATE INDEX statement is
+ ** very small (since most of the pages written do not contain content that
+ ** needs to be restored if the statement aborts), so we call
+ ** tdsqlite3MayAbort() for all CREATE INDEX statements. */
+ tdsqlite3MayAbort(pParse);
+ addr2 = tdsqlite3VdbeCurrentAddr(v);
+ }
+ tdsqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx);
+ if( !pIndex->bAscKeyBug ){
+ /* This OP_SeekEnd opcode makes index insert for a REINDEX go much
+ ** faster by avoiding unnecessary seeks. But the optimization does
+ ** not work for UNIQUE constraint indexes on WITHOUT ROWID tables
+ ** with DESC primary keys, since those indexes have there keys in
+ ** a different order from the main table.
+ ** See ticket: https://www.sqlite.org/src/info/bba7b69f9849b5bf
+ */
+ tdsqlite3VdbeAddOp1(v, OP_SeekEnd, iIdx);
}
- sqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx);
- sqlite3VdbeAddOp3(v, OP_Last, iIdx, 0, -1);
- sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 0);
- sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
- sqlite3ReleaseTempReg(pParse, regRecord);
- sqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr1);
+ tdsqlite3VdbeAddOp2(v, OP_IdxInsert, iIdx, regRecord);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
+ tdsqlite3ReleaseTempReg(pParse, regRecord);
+ tdsqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp1(v, OP_Close, iTab);
- sqlite3VdbeAddOp1(v, OP_Close, iIdx);
- sqlite3VdbeAddOp1(v, OP_Close, iSorter);
+ tdsqlite3VdbeAddOp1(v, OP_Close, iTab);
+ tdsqlite3VdbeAddOp1(v, OP_Close, iIdx);
+ tdsqlite3VdbeAddOp1(v, OP_Close, iSorter);
}
/*
@@ -104218,8 +116069,8 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){
** of 8-byte aligned space after the Index object and return a
** pointer to this extra space in *ppExtra.
*/
-SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(
- sqlite3 *db, /* Database connection */
+SQLITE_PRIVATE Index *tdsqlite3AllocateIndexObject(
+ tdsqlite3 *db, /* Database connection */
i16 nCol, /* Total number of columns in the index */
int nExtra, /* Number of bytes of extra space to alloc */
char **ppExtra /* Pointer to the "extra" space */
@@ -104232,7 +116083,7 @@ SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(
ROUND8(sizeof(LogEst)*(nCol+1) + /* Index.aiRowLogEst */
sizeof(i16)*nCol + /* Index.aiColumn */
sizeof(u8)*nCol); /* Index.aSortOrder */
- p = sqlite3DbMallocZero(db, nByte + nExtra);
+ p = tdsqlite3DbMallocZero(db, nByte + nExtra);
if( p ){
char *pExtra = ((char*)p)+ROUND8(sizeof(Index));
p->azColl = (const char**)pExtra; pExtra += ROUND8(sizeof(char*)*nCol);
@@ -104247,6 +116098,27 @@ SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(
}
/*
+** If expression list pList contains an expression that was parsed with
+** an explicit "NULLS FIRST" or "NULLS LAST" clause, leave an error in
+** pParse and return non-zero. Otherwise, return zero.
+*/
+SQLITE_PRIVATE int tdsqlite3HasExplicitNulls(Parse *pParse, ExprList *pList){
+ if( pList ){
+ int i;
+ for(i=0; i<pList->nExpr; i++){
+ if( pList->a[i].bNulls ){
+ u8 sf = pList->a[i].sortFlags;
+ tdsqlite3ErrorMsg(pParse, "unsupported use of NULLS %s",
+ (sf==0 || sf==3) ? "FIRST" : "LAST"
+ );
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
** Create a new index for an SQL table. pName1.pName2 is the name of the index
** and pTblList is the name of the table that is to be indexed. Both will
** be NULL for a primary key or an index that is created to satisfy a
@@ -104258,7 +116130,7 @@ SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(
** is a primary key or unique-constraint on the most recent column added
** to the table currently under construction.
*/
-SQLITE_PRIVATE void sqlite3CreateIndex(
+SQLITE_PRIVATE void tdsqlite3CreateIndex(
Parse *pParse, /* All information about this parse */
Token *pName1, /* First part of index name. May be NULL */
Token *pName2, /* Second part of index name. May be NULL */
@@ -104278,7 +116150,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
int i, j;
DbFixer sFix; /* For assigning database names to pTable */
int sortOrderMask; /* 1 to honor DESC in index. 0 to ignore. */
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Db *pDb; /* The specific table containing the indexed database */
int iDb; /* Index of the database that is being written */
Token *pName = 0; /* Unqualified name of the index to create */
@@ -104294,7 +116166,10 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
if( IN_DECLARE_VTAB && idxType!=SQLITE_IDXTYPE_PRIMARYKEY ){
goto exit_create_index;
}
- if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ if( SQLITE_OK!=tdsqlite3ReadSchema(pParse) ){
+ goto exit_create_index;
+ }
+ if( tdsqlite3HasExplicitNulls(pParse, pList) ){
goto exit_create_index;
}
@@ -104308,7 +116183,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
** before looking up the table.
*/
assert( pName1 && pName2 );
- iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
+ iDb = tdsqlite3TwoPartName(pParse, pName1, pName2, &pName);
if( iDb<0 ) goto exit_create_index;
assert( pName && pName->z );
@@ -104318,58 +116193,62 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
** if initialising a database schema.
*/
if( !db->init.busy ){
- pTab = sqlite3SrcListLookup(pParse, pTblName);
+ pTab = tdsqlite3SrcListLookup(pParse, pTblName);
if( pName2->n==0 && pTab && pTab->pSchema==db->aDb[1].pSchema ){
iDb = 1;
}
}
#endif
- sqlite3FixInit(&sFix, pParse, iDb, "index", pName);
- if( sqlite3FixSrcList(&sFix, pTblName) ){
+ tdsqlite3FixInit(&sFix, pParse, iDb, "index", pName);
+ if( tdsqlite3FixSrcList(&sFix, pTblName) ){
/* Because the parser constructs pTblName from a single identifier,
- ** sqlite3FixSrcList can never fail. */
+ ** tdsqlite3FixSrcList can never fail. */
assert(0);
}
- pTab = sqlite3LocateTableItem(pParse, 0, &pTblName->a[0]);
+ pTab = tdsqlite3LocateTableItem(pParse, 0, &pTblName->a[0]);
assert( db->mallocFailed==0 || pTab==0 );
if( pTab==0 ) goto exit_create_index;
if( iDb==1 && db->aDb[iDb].pSchema!=pTab->pSchema ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"cannot create a TEMP index on non-TEMP table \"%s\"",
pTab->zName);
goto exit_create_index;
}
- if( !HasRowid(pTab) ) pPk = sqlite3PrimaryKeyIndex(pTab);
+ if( !HasRowid(pTab) ) pPk = tdsqlite3PrimaryKeyIndex(pTab);
}else{
assert( pName==0 );
assert( pStart==0 );
pTab = pParse->pNewTable;
if( !pTab ) goto exit_create_index;
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
}
pDb = &db->aDb[iDb];
assert( pTab!=0 );
assert( pParse->nErr==0 );
- if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0
+ if( tdsqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0
&& db->init.busy==0
+ && pTblName!=0
#if SQLITE_USER_AUTHENTICATION
- && sqlite3UserAuthTable(pTab->zName)==0
+ && tdsqlite3UserAuthTable(pTab->zName)==0
+#endif
+#ifdef SQLITE_ALLOW_SQLITE_MASTER_INDEX
+ && tdsqlite3StrICmp(&pTab->zName[7],"master")!=0
#endif
- && sqlite3StrNICmp(&pTab->zName[7],"altertab_",9)!=0 ){
- sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
+ ){
+ tdsqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
goto exit_create_index;
}
#ifndef SQLITE_OMIT_VIEW
if( pTab->pSelect ){
- sqlite3ErrorMsg(pParse, "views may not be indexed");
+ tdsqlite3ErrorMsg(pParse, "views may not be indexed");
goto exit_create_index;
}
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pTab) ){
- sqlite3ErrorMsg(pParse, "virtual tables may not be indexed");
+ tdsqlite3ErrorMsg(pParse, "virtual tables may not be indexed");
goto exit_create_index;
}
#endif
@@ -104388,55 +116267,57 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
** own name.
*/
if( pName ){
- zName = sqlite3NameFromToken(db, pName);
+ zName = tdsqlite3NameFromToken(db, pName);
if( zName==0 ) goto exit_create_index;
assert( pName->z!=0 );
- if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
+ if( SQLITE_OK!=tdsqlite3CheckObjectName(pParse, zName,"index",pTab->zName) ){
goto exit_create_index;
}
- if( !db->init.busy ){
- if( sqlite3FindTable(db, zName, 0)!=0 ){
- sqlite3ErrorMsg(pParse, "there is already a table named %s", zName);
- goto exit_create_index;
+ if( !IN_RENAME_OBJECT ){
+ if( !db->init.busy ){
+ if( tdsqlite3FindTable(db, zName, 0)!=0 ){
+ tdsqlite3ErrorMsg(pParse, "there is already a table named %s", zName);
+ goto exit_create_index;
+ }
}
- }
- if( sqlite3FindIndex(db, zName, pDb->zDbSName)!=0 ){
- if( !ifNotExist ){
- sqlite3ErrorMsg(pParse, "index %s already exists", zName);
- }else{
- assert( !db->init.busy );
- sqlite3CodeVerifySchema(pParse, iDb);
+ if( tdsqlite3FindIndex(db, zName, pDb->zDbSName)!=0 ){
+ if( !ifNotExist ){
+ tdsqlite3ErrorMsg(pParse, "index %s already exists", zName);
+ }else{
+ assert( !db->init.busy );
+ tdsqlite3CodeVerifySchema(pParse, iDb);
+ }
+ goto exit_create_index;
}
- goto exit_create_index;
}
}else{
int n;
Index *pLoop;
for(pLoop=pTab->pIndex, n=1; pLoop; pLoop=pLoop->pNext, n++){}
- zName = sqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->zName, n);
+ zName = tdsqlite3MPrintf(db, "sqlite_autoindex_%s_%d", pTab->zName, n);
if( zName==0 ){
goto exit_create_index;
}
- /* Automatic index names generated from within sqlite3_declare_vtab()
+ /* Automatic index names generated from within tdsqlite3_declare_vtab()
** must have names that are distinct from normal automatic index names.
- ** The following statement converts "sqlite3_autoindex..." into
- ** "sqlite3_butoindex..." in order to make the names distinct.
+ ** The following statement converts "tdsqlite3_autoindex..." into
+ ** "tdsqlite3_butoindex..." in order to make the names distinct.
** The "vtab_err.test" test demonstrates the need of this statement. */
- if( IN_DECLARE_VTAB ) zName[7]++;
+ if( IN_SPECIAL_PARSE ) zName[7]++;
}
/* Check for authorization to create an index.
*/
#ifndef SQLITE_OMIT_AUTHORIZATION
- {
+ if( !IN_RENAME_OBJECT ){
const char *zDb = pDb->zDbSName;
- if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iDb), 0, zDb) ){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iDb), 0, zDb) ){
goto exit_create_index;
}
i = SQLITE_CREATE_INDEX;
if( !OMIT_TEMPDB && iDb==1 ) i = SQLITE_CREATE_TEMP_INDEX;
- if( sqlite3AuthCheck(pParse, i, zName, pTab->zName, zDb) ){
+ if( tdsqlite3AuthCheck(pParse, i, zName, pTab->zName, zDb) ){
goto exit_create_index;
}
}
@@ -104448,14 +116329,17 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
*/
if( pList==0 ){
Token prevCol;
- sqlite3TokenInit(&prevCol, pTab->aCol[pTab->nCol-1].zName);
- pList = sqlite3ExprListAppend(pParse, 0,
- sqlite3ExprAlloc(db, TK_ID, &prevCol, 0));
+ Column *pCol = &pTab->aCol[pTab->nCol-1];
+ pCol->colFlags |= COLFLAG_UNIQUE;
+ tdsqlite3TokenInit(&prevCol, pCol->zName);
+ pList = tdsqlite3ExprListAppend(pParse, 0,
+ tdsqlite3ExprAlloc(db, TK_ID, &prevCol, 0));
if( pList==0 ) goto exit_create_index;
assert( pList->nExpr==1 );
- sqlite3ExprListSetSortOrder(pList, sortOrder);
+ tdsqlite3ExprListSetSortOrder(pList, sortOrder, SQLITE_SO_UNDEFINED);
}else{
- sqlite3ExprListCheckLength(pParse, pList, "index");
+ tdsqlite3ExprListCheckLength(pParse, pList, "index");
+ if( pParse->nErr ) goto exit_create_index;
}
/* Figure out how many bytes of space are required to store explicitly
@@ -104465,16 +116349,17 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
Expr *pExpr = pList->a[i].pExpr;
assert( pExpr!=0 );
if( pExpr->op==TK_COLLATE ){
- nExtra += (1 + sqlite3Strlen30(pExpr->u.zToken));
+ nExtra += (1 + tdsqlite3Strlen30(pExpr->u.zToken));
}
}
/*
** Allocate the index structure.
*/
- nName = sqlite3Strlen30(zName);
+ nName = tdsqlite3Strlen30(zName);
nExtraCol = pPk ? pPk->nKeyCol : 1;
- pIndex = sqlite3AllocateIndexObject(db, pList->nExpr + nExtraCol,
+ assert( pList->nExpr + nExtraCol <= 32767 /* Fits in i16 */ );
+ pIndex = tdsqlite3AllocateIndexObject(db, pList->nExpr + nExtraCol,
nName + nExtra + 1, &zExtra);
if( db->mallocFailed ){
goto exit_create_index;
@@ -104491,11 +116376,11 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
pIndex->pSchema = db->aDb[iDb].pSchema;
pIndex->nKeyCol = pList->nExpr;
if( pPIWhere ){
- sqlite3ResolveSelfReference(pParse, pTab, NC_PartIdx, pPIWhere, 0);
+ tdsqlite3ResolveSelfReference(pParse, pTab, NC_PartIdx, pPIWhere, 0);
pIndex->pPartIdxWhere = pPIWhere;
pPIWhere = 0;
}
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
/* Check to see if we should honor DESC requests on index columns
*/
@@ -104514,28 +116399,29 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
** TODO: Issue a warning if the table primary key is used as part of the
** index key.
*/
- for(i=0, pListItem=pList->a; i<pList->nExpr; i++, pListItem++){
+ pListItem = pList->a;
+ if( IN_RENAME_OBJECT ){
+ pIndex->aColExpr = pList;
+ pList = 0;
+ }
+ for(i=0; i<pIndex->nKeyCol; i++, pListItem++){
Expr *pCExpr; /* The i-th index expression */
int requestedSortOrder; /* ASC or DESC on the i-th expression */
const char *zColl; /* Collation sequence name */
- sqlite3StringToId(pListItem->pExpr);
- sqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr, pListItem->pExpr, 0);
+ tdsqlite3StringToId(pListItem->pExpr);
+ tdsqlite3ResolveSelfReference(pParse, pTab, NC_IdxExpr, pListItem->pExpr, 0);
if( pParse->nErr ) goto exit_create_index;
- pCExpr = sqlite3ExprSkipCollate(pListItem->pExpr);
+ pCExpr = tdsqlite3ExprSkipCollate(pListItem->pExpr);
if( pCExpr->op!=TK_COLUMN ){
if( pTab==pParse->pNewTable ){
- sqlite3ErrorMsg(pParse, "expressions prohibited in PRIMARY KEY and "
+ tdsqlite3ErrorMsg(pParse, "expressions prohibited in PRIMARY KEY and "
"UNIQUE constraints");
goto exit_create_index;
}
if( pIndex->aColExpr==0 ){
- ExprList *pCopy = sqlite3ExprListDup(db, pList, 0);
- pIndex->aColExpr = pCopy;
- if( !db->mallocFailed ){
- assert( pCopy!=0 );
- pListItem = &pCopy->a[i];
- }
+ pIndex->aColExpr = pList;
+ pList = 0;
}
j = XN_EXPR;
pIndex->aiColumn[i] = XN_EXPR;
@@ -104545,8 +116431,13 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
assert( j<=0x7fff );
if( j<0 ){
j = pTab->iPKey;
- }else if( pTab->aCol[j].notNull==0 ){
- pIndex->uniqNotNull = 0;
+ }else{
+ if( pTab->aCol[j].notNull==0 ){
+ pIndex->uniqNotNull = 0;
+ }
+ if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){
+ pIndex->bHasVCol = 1;
+ }
}
pIndex->aiColumn[i] = (i16)j;
}
@@ -104554,7 +116445,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
if( pListItem->pExpr->op==TK_COLLATE ){
int nColl;
zColl = pListItem->pExpr->u.zToken;
- nColl = sqlite3Strlen30(zColl) + 1;
+ nColl = tdsqlite3Strlen30(zColl) + 1;
assert( nExtra>=nColl );
memcpy(zExtra, zColl, nColl);
zColl = zExtra;
@@ -104563,12 +116454,12 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
}else if( j>=0 ){
zColl = pTab->aCol[j].zColl;
}
- if( !zColl ) zColl = sqlite3StrBINARY;
- if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl) ){
+ if( !zColl ) zColl = tdsqlite3StrBINARY;
+ if( !db->init.busy && !tdsqlite3LocateCollSeq(pParse, zColl) ){
goto exit_create_index;
}
pIndex->azColl[i] = zColl;
- requestedSortOrder = pListItem->sortOrder & sortOrderMask;
+ requestedSortOrder = pListItem->sortFlags & sortOrderMask;
pIndex->aSortOrder[i] = (u8)requestedSortOrder;
}
@@ -104580,9 +116471,10 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
for(j=0; j<pPk->nKeyCol; j++){
int x = pPk->aiColumn[j];
assert( x>=0 );
- if( hasColumn(pIndex->aiColumn, pIndex->nKeyCol, x) ){
+ if( isDupColumn(pIndex, pIndex->nKeyCol, pPk, j) ){
pIndex->nColumn--;
}else{
+ testcase( hasColumn(pIndex->aiColumn,pIndex->nKeyCol,x) );
pIndex->aiColumn[i] = x;
pIndex->azColl[i] = pPk->azColl[j];
pIndex->aSortOrder[i] = pPk->aSortOrder[j];
@@ -104592,20 +116484,21 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
assert( i==pIndex->nColumn );
}else{
pIndex->aiColumn[i] = XN_ROWID;
- pIndex->azColl[i] = sqlite3StrBINARY;
+ pIndex->azColl[i] = tdsqlite3StrBINARY;
}
- sqlite3DefaultRowEst(pIndex);
+ tdsqlite3DefaultRowEst(pIndex);
if( pParse->pNewTable==0 ) estimateIndexWidth(pIndex);
/* If this index contains every column of its table, then mark
** it as a covering index */
assert( HasRowid(pTab)
- || pTab->iPKey<0 || sqlite3ColumnOfIndex(pIndex, pTab->iPKey)>=0 );
+ || pTab->iPKey<0 || tdsqlite3TableColumnToIndex(pIndex, pTab->iPKey)>=0 );
+ recomputeColumnsNotIndexed(pIndex);
if( pTblName!=0 && pIndex->nColumn>=pTab->nCol ){
pIndex->isCovering = 1;
for(j=0; j<pTab->nCol; j++){
if( j==pTab->iPKey ) continue;
- if( sqlite3ColumnOfIndex(pIndex,j)>=0 ) continue;
+ if( tdsqlite3TableColumnToIndex(pIndex,j)>=0 ) continue;
pIndex->isCovering = 0;
break;
}
@@ -104648,7 +116541,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
if( pIdx->aiColumn[k]!=pIndex->aiColumn[k] ) break;
z1 = pIdx->azColl[k];
z2 = pIndex->azColl[k];
- if( sqlite3StrICmp(z1, z2) ) break;
+ if( tdsqlite3StrICmp(z1, z2) ) break;
}
if( k==pIdx->nKeyCol ){
if( pIdx->onError!=pIndex->onError ){
@@ -104660,7 +116553,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
** explicitly specified behavior for the index.
*/
if( !(pIdx->onError==OE_Default || pIndex->onError==OE_Default) ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"conflicting ON CONFLICT clauses specified", 0);
}
if( pIdx->onError==OE_Default ){
@@ -104668,134 +116561,151 @@ SQLITE_PRIVATE void sqlite3CreateIndex(
}
}
if( idxType==SQLITE_IDXTYPE_PRIMARYKEY ) pIdx->idxType = idxType;
+ if( IN_RENAME_OBJECT ){
+ pIndex->pNext = pParse->pNewIndex;
+ pParse->pNewIndex = pIndex;
+ pIndex = 0;
+ }
goto exit_create_index;
}
}
}
- /* Link the new Index structure to its table and to the other
- ** in-memory database structures.
- */
- assert( pParse->nErr==0 );
- if( db->init.busy ){
- Index *p;
- assert( !IN_DECLARE_VTAB );
- assert( sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
- p = sqlite3HashInsert(&pIndex->pSchema->idxHash,
- pIndex->zName, pIndex);
- if( p ){
- assert( p==pIndex ); /* Malloc must have failed */
- sqlite3OomFault(db);
- goto exit_create_index;
- }
- db->flags |= SQLITE_InternChanges;
- if( pTblName!=0 ){
- pIndex->tnum = db->init.newTnum;
- }
- }
-
- /* If this is the initial CREATE INDEX statement (or CREATE TABLE if the
- ** index is an implied index for a UNIQUE or PRIMARY KEY constraint) then
- ** emit code to allocate the index rootpage on disk and make an entry for
- ** the index in the sqlite_master table and populate the index with
- ** content. But, do not do this if we are simply reading the sqlite_master
- ** table to parse the schema, or if this index is the PRIMARY KEY index
- ** of a WITHOUT ROWID table.
- **
- ** If pTblName==0 it means this index is generated as an implied PRIMARY KEY
- ** or UNIQUE index in a CREATE TABLE statement. Since the table
- ** has just been created, it contains no data and the index initialization
- ** step can be skipped.
- */
- else if( HasRowid(pTab) || pTblName!=0 ){
- Vdbe *v;
- char *zStmt;
- int iMem = ++pParse->nMem;
-
- v = sqlite3GetVdbe(pParse);
- if( v==0 ) goto exit_create_index;
+ if( !IN_RENAME_OBJECT ){
- sqlite3BeginWriteOperation(pParse, 1, iDb);
-
- /* Create the rootpage for the index using CreateIndex. But before
- ** doing so, code a Noop instruction and store its address in
- ** Index.tnum. This is required in case this index is actually a
- ** PRIMARY KEY and the table is actually a WITHOUT ROWID table. In
- ** that case the convertToWithoutRowidTable() routine will replace
- ** the Noop with a Goto to jump over the VDBE code generated below. */
- pIndex->tnum = sqlite3VdbeAddOp0(v, OP_Noop);
- sqlite3VdbeAddOp2(v, OP_CreateIndex, iDb, iMem);
-
- /* Gather the complete text of the CREATE INDEX statement into
- ** the zStmt variable
+ /* Link the new Index structure to its table and to the other
+ ** in-memory database structures.
*/
- if( pStart ){
- int n = (int)(pParse->sLastToken.z - pName->z) + pParse->sLastToken.n;
- if( pName->z[n-1]==';' ) n--;
- /* A named index with an explicit CREATE INDEX statement */
- zStmt = sqlite3MPrintf(db, "CREATE%s INDEX %.*s",
- onError==OE_None ? "" : " UNIQUE", n, pName->z);
- }else{
- /* An automatic index created by a PRIMARY KEY or UNIQUE constraint */
- /* zStmt = sqlite3MPrintf(""); */
- zStmt = 0;
+ assert( pParse->nErr==0 );
+ if( db->init.busy ){
+ Index *p;
+ assert( !IN_SPECIAL_PARSE );
+ assert( tdsqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
+ if( pTblName!=0 ){
+ pIndex->tnum = db->init.newTnum;
+ if( tdsqlite3IndexHasDuplicateRootPage(pIndex) ){
+ tdsqlite3ErrorMsg(pParse, "invalid rootpage");
+ pParse->rc = SQLITE_CORRUPT_BKPT;
+ goto exit_create_index;
+ }
+ }
+ p = tdsqlite3HashInsert(&pIndex->pSchema->idxHash,
+ pIndex->zName, pIndex);
+ if( p ){
+ assert( p==pIndex ); /* Malloc must have failed */
+ tdsqlite3OomFault(db);
+ goto exit_create_index;
+ }
+ db->mDbFlags |= DBFLAG_SchemaChange;
}
- /* Add an entry in sqlite_master for this index
- */
- sqlite3NestedParse(pParse,
- "INSERT INTO %Q.%s VALUES('index',%Q,%Q,#%d,%Q);",
- db->aDb[iDb].zDbSName, SCHEMA_TABLE(iDb),
- pIndex->zName,
- pTab->zName,
- iMem,
- zStmt
- );
- sqlite3DbFree(db, zStmt);
-
- /* Fill the index with data and reparse the schema. Code an OP_Expire
- ** to invalidate all pre-compiled statements.
+ /* If this is the initial CREATE INDEX statement (or CREATE TABLE if the
+ ** index is an implied index for a UNIQUE or PRIMARY KEY constraint) then
+ ** emit code to allocate the index rootpage on disk and make an entry for
+ ** the index in the sqlite_master table and populate the index with
+ ** content. But, do not do this if we are simply reading the sqlite_master
+ ** table to parse the schema, or if this index is the PRIMARY KEY index
+ ** of a WITHOUT ROWID table.
+ **
+ ** If pTblName==0 it means this index is generated as an implied PRIMARY KEY
+ ** or UNIQUE index in a CREATE TABLE statement. Since the table
+ ** has just been created, it contains no data and the index initialization
+ ** step can be skipped.
*/
- if( pTblName ){
- sqlite3RefillIndex(pParse, pIndex, iMem);
- sqlite3ChangeCookie(pParse, iDb);
- sqlite3VdbeAddParseSchemaOp(v, iDb,
- sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName));
- sqlite3VdbeAddOp0(v, OP_Expire);
- }
+ else if( HasRowid(pTab) || pTblName!=0 ){
+ Vdbe *v;
+ char *zStmt;
+ int iMem = ++pParse->nMem;
+
+ v = tdsqlite3GetVdbe(pParse);
+ if( v==0 ) goto exit_create_index;
+
+ tdsqlite3BeginWriteOperation(pParse, 1, iDb);
+
+ /* Create the rootpage for the index using CreateIndex. But before
+ ** doing so, code a Noop instruction and store its address in
+ ** Index.tnum. This is required in case this index is actually a
+ ** PRIMARY KEY and the table is actually a WITHOUT ROWID table. In
+ ** that case the convertToWithoutRowidTable() routine will replace
+ ** the Noop with a Goto to jump over the VDBE code generated below. */
+ pIndex->tnum = tdsqlite3VdbeAddOp0(v, OP_Noop);
+ tdsqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, iMem, BTREE_BLOBKEY);
+
+ /* Gather the complete text of the CREATE INDEX statement into
+ ** the zStmt variable
+ */
+ assert( pName!=0 || pStart==0 );
+ if( pStart ){
+ int n = (int)(pParse->sLastToken.z - pName->z) + pParse->sLastToken.n;
+ if( pName->z[n-1]==';' ) n--;
+ /* A named index with an explicit CREATE INDEX statement */
+ zStmt = tdsqlite3MPrintf(db, "CREATE%s INDEX %.*s",
+ onError==OE_None ? "" : " UNIQUE", n, pName->z);
+ }else{
+ /* An automatic index created by a PRIMARY KEY or UNIQUE constraint */
+ /* zStmt = tdsqlite3MPrintf(""); */
+ zStmt = 0;
+ }
- sqlite3VdbeJumpHere(v, pIndex->tnum);
- }
+ /* Add an entry in sqlite_master for this index
+ */
+ tdsqlite3NestedParse(pParse,
+ "INSERT INTO %Q.%s VALUES('index',%Q,%Q,#%d,%Q);",
+ db->aDb[iDb].zDbSName, MASTER_NAME,
+ pIndex->zName,
+ pTab->zName,
+ iMem,
+ zStmt
+ );
+ tdsqlite3DbFree(db, zStmt);
- /* When adding an index to the list of indices for a table, make
- ** sure all indices labeled OE_Replace come after all those labeled
- ** OE_Ignore. This is necessary for the correct constraint check
- ** processing (in sqlite3GenerateConstraintChecks()) as part of
- ** UPDATE and INSERT statements.
- */
- if( db->init.busy || pTblName==0 ){
- if( onError!=OE_Replace || pTab->pIndex==0
- || pTab->pIndex->onError==OE_Replace){
- pIndex->pNext = pTab->pIndex;
- pTab->pIndex = pIndex;
- }else{
- Index *pOther = pTab->pIndex;
- while( pOther->pNext && pOther->pNext->onError!=OE_Replace ){
- pOther = pOther->pNext;
+ /* Fill the index with data and reparse the schema. Code an OP_Expire
+ ** to invalidate all pre-compiled statements.
+ */
+ if( pTblName ){
+ tdsqlite3RefillIndex(pParse, pIndex, iMem);
+ tdsqlite3ChangeCookie(pParse, iDb);
+ tdsqlite3VdbeAddParseSchemaOp(v, iDb,
+ tdsqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName));
+ tdsqlite3VdbeAddOp2(v, OP_Expire, 0, 1);
}
- pIndex->pNext = pOther->pNext;
- pOther->pNext = pIndex;
+
+ tdsqlite3VdbeJumpHere(v, pIndex->tnum);
}
+ }
+ if( db->init.busy || pTblName==0 ){
+ pIndex->pNext = pTab->pIndex;
+ pTab->pIndex = pIndex;
+ pIndex = 0;
+ }
+ else if( IN_RENAME_OBJECT ){
+ assert( pParse->pNewIndex==0 );
+ pParse->pNewIndex = pIndex;
pIndex = 0;
}
/* Clean up before exiting */
exit_create_index:
- if( pIndex ) freeIndex(db, pIndex);
- sqlite3ExprDelete(db, pPIWhere);
- sqlite3ExprListDelete(db, pList);
- sqlite3SrcListDelete(db, pTblName);
- sqlite3DbFree(db, zName);
+ if( pIndex ) tdsqlite3FreeIndex(db, pIndex);
+ if( pTab ){ /* Ensure all REPLACE indexes are at the end of the list */
+ Index **ppFrom = &pTab->pIndex;
+ Index *pThis;
+ for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){
+ Index *pNext;
+ if( pThis->onError!=OE_Replace ) continue;
+ while( (pNext = pThis->pNext)!=0 && pNext->onError!=OE_Replace ){
+ *ppFrom = pNext;
+ pThis->pNext = pNext->pNext;
+ pNext->pNext = pThis;
+ ppFrom = &pNext->pNext;
+ }
+ break;
+ }
+ }
+ tdsqlite3ExprDelete(db, pPIWhere);
+ tdsqlite3ExprListDelete(db, pList);
+ tdsqlite3SrcListDelete(db, pTblName);
+ tdsqlite3DbFree(db, zName);
}
/*
@@ -104816,28 +116726,31 @@ exit_create_index:
** how aiRowEst[] should be initialized. The numbers generated here
** are based on typical values found in actual indices.
*/
-SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){
+SQLITE_PRIVATE void tdsqlite3DefaultRowEst(Index *pIdx){
/* 10, 9, 8, 7, 6 */
LogEst aVal[] = { 33, 32, 30, 28, 26 };
LogEst *a = pIdx->aiRowLogEst;
int nCopy = MIN(ArraySize(aVal), pIdx->nKeyCol);
int i;
+ /* Indexes with default row estimates should not have stat1 data */
+ assert( !pIdx->hasStat1 );
+
/* Set the first entry (number of rows in the index) to the estimated
** number of rows in the table, or half the number of rows in the table
** for a partial index. But do not let the estimate drop below 10. */
a[0] = pIdx->pTable->nRowLogEst;
- if( pIdx->pPartIdxWhere!=0 ) a[0] -= 10; assert( 10==sqlite3LogEst(2) );
- if( a[0]<33 ) a[0] = 33; assert( 33==sqlite3LogEst(10) );
+ if( pIdx->pPartIdxWhere!=0 ) a[0] -= 10; assert( 10==tdsqlite3LogEst(2) );
+ if( a[0]<33 ) a[0] = 33; assert( 33==tdsqlite3LogEst(10) );
/* Estimate that a[1] is 10, a[2] is 9, a[3] is 8, a[4] is 7, a[5] is
** 6 and each subsequent value (if any) is 5. */
memcpy(&a[1], aVal, nCopy*sizeof(LogEst));
for(i=nCopy+1; i<=pIdx->nKeyCol; i++){
- a[i] = 23; assert( 23==sqlite3LogEst(5) );
+ a[i] = 23; assert( 23==tdsqlite3LogEst(5) );
}
- assert( 0==sqlite3LogEst(1) );
+ assert( 0==tdsqlite3LogEst(1) );
if( IsUniqueIndex(pIdx) ) a[pIdx->nKeyCol] = 0;
}
@@ -104845,10 +116758,10 @@ SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){
** This routine will drop an existing named index. This routine
** implements the DROP INDEX statement.
*/
-SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
+SQLITE_PRIVATE void tdsqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists){
Index *pIndex;
Vdbe *v;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
int iDb;
assert( pParse->nErr==0 ); /* Never called with prior errors */
@@ -104856,62 +116769,62 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists
goto exit_drop_index;
}
assert( pName->nSrc==1 );
- if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ if( SQLITE_OK!=tdsqlite3ReadSchema(pParse) ){
goto exit_drop_index;
}
- pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase);
+ pIndex = tdsqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase);
if( pIndex==0 ){
if( !ifExists ){
- sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0);
+ tdsqlite3ErrorMsg(pParse, "no such index: %S", pName, 0);
}else{
- sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
+ tdsqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase);
}
pParse->checkSchema = 1;
goto exit_drop_index;
}
if( pIndex->idxType!=SQLITE_IDXTYPE_APPDEF ){
- sqlite3ErrorMsg(pParse, "index associated with UNIQUE "
+ tdsqlite3ErrorMsg(pParse, "index associated with UNIQUE "
"or PRIMARY KEY constraint cannot be dropped", 0);
goto exit_drop_index;
}
- iDb = sqlite3SchemaToIndex(db, pIndex->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pIndex->pSchema);
#ifndef SQLITE_OMIT_AUTHORIZATION
{
int code = SQLITE_DROP_INDEX;
Table *pTab = pIndex->pTable;
const char *zDb = db->aDb[iDb].zDbSName;
const char *zTab = SCHEMA_TABLE(iDb);
- if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
goto exit_drop_index;
}
if( !OMIT_TEMPDB && iDb ) code = SQLITE_DROP_TEMP_INDEX;
- if( sqlite3AuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){
+ if( tdsqlite3AuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){
goto exit_drop_index;
}
}
#endif
/* Generate code to remove the index and from the master table */
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( v ){
- sqlite3BeginWriteOperation(pParse, 1, iDb);
- sqlite3NestedParse(pParse,
+ tdsqlite3BeginWriteOperation(pParse, 1, iDb);
+ tdsqlite3NestedParse(pParse,
"DELETE FROM %Q.%s WHERE name=%Q AND type='index'",
- db->aDb[iDb].zDbSName, SCHEMA_TABLE(iDb), pIndex->zName
+ db->aDb[iDb].zDbSName, MASTER_NAME, pIndex->zName
);
- sqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName);
- sqlite3ChangeCookie(pParse, iDb);
+ tdsqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName);
+ tdsqlite3ChangeCookie(pParse, iDb);
destroyRootPage(pParse, pIndex->tnum, iDb);
- sqlite3VdbeAddOp4(v, OP_DropIndex, iDb, 0, 0, pIndex->zName, 0);
+ tdsqlite3VdbeAddOp4(v, OP_DropIndex, iDb, 0, 0, pIndex->zName, 0);
}
exit_drop_index:
- sqlite3SrcListDelete(db, pName);
+ tdsqlite3SrcListDelete(db, pName);
}
/*
** pArray is a pointer to an array of objects. Each object in the
-** array is szEntry bytes in size. This routine uses sqlite3DbRealloc()
+** array is szEntry bytes in size. This routine uses tdsqlite3DbRealloc()
** to extend the array so that there is space for a new object at the end.
**
** When this function is called, *pnEntry contains the current size of
@@ -104926,18 +116839,18 @@ exit_drop_index:
** Otherwise, if the realloc() fails, *pIdx is set to -1, *pnEntry remains
** unchanged and a copy of pArray returned.
*/
-SQLITE_PRIVATE void *sqlite3ArrayAllocate(
- sqlite3 *db, /* Connection to notify of malloc failures */
+SQLITE_PRIVATE void *tdsqlite3ArrayAllocate(
+ tdsqlite3 *db, /* Connection to notify of malloc failures */
void *pArray, /* Array of objects. Might be reallocated */
int szEntry, /* Size of each object in the array */
int *pnEntry, /* Number of objects currently in use */
int *pIdx /* Write the index of a new slot here */
){
char *z;
- int n = *pnEntry;
+ tdsqlite3_int64 n = *pIdx = *pnEntry;
if( (n & (n-1))==0 ){
- int sz = (n==0) ? 1 : 2*n;
- void *pNew = sqlite3DbRealloc(db, pArray, sz*szEntry);
+ tdsqlite3_int64 sz = (n==0) ? 1 : 2*n;
+ void *pNew = tdsqlite3DbRealloc(db, pArray, sz*szEntry);
if( pNew==0 ){
*pIdx = -1;
return pArray;
@@ -104946,7 +116859,6 @@ SQLITE_PRIVATE void *sqlite3ArrayAllocate(
}
z = (char*)pArray;
memset(&z[n * szEntry], 0, szEntry);
- *pIdx = n;
++*pnEntry;
return pArray;
}
@@ -104957,13 +116869,14 @@ SQLITE_PRIVATE void *sqlite3ArrayAllocate(
**
** A new IdList is returned, or NULL if malloc() fails.
*/
-SQLITE_PRIVATE IdList *sqlite3IdListAppend(sqlite3 *db, IdList *pList, Token *pToken){
+SQLITE_PRIVATE IdList *tdsqlite3IdListAppend(Parse *pParse, IdList *pList, Token *pToken){
+ tdsqlite3 *db = pParse->db;
int i;
if( pList==0 ){
- pList = sqlite3DbMallocZero(db, sizeof(IdList) );
+ pList = tdsqlite3DbMallocZero(db, sizeof(IdList) );
if( pList==0 ) return 0;
}
- pList->a = sqlite3ArrayAllocate(
+ pList->a = tdsqlite3ArrayAllocate(
db,
pList->a,
sizeof(pList->a[0]),
@@ -104971,40 +116884,55 @@ SQLITE_PRIVATE IdList *sqlite3IdListAppend(sqlite3 *db, IdList *pList, Token *pT
&i
);
if( i<0 ){
- sqlite3IdListDelete(db, pList);
+ tdsqlite3IdListDelete(db, pList);
return 0;
}
- pList->a[i].zName = sqlite3NameFromToken(db, pToken);
+ pList->a[i].zName = tdsqlite3NameFromToken(db, pToken);
+ if( IN_RENAME_OBJECT && pList->a[i].zName ){
+ tdsqlite3RenameTokenMap(pParse, (void*)pList->a[i].zName, pToken);
+ }
return pList;
}
/*
** Delete an IdList.
*/
-SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3 *db, IdList *pList){
+SQLITE_PRIVATE void tdsqlite3IdListDelete(tdsqlite3 *db, IdList *pList){
int i;
if( pList==0 ) return;
for(i=0; i<pList->nId; i++){
- sqlite3DbFree(db, pList->a[i].zName);
+ tdsqlite3DbFree(db, pList->a[i].zName);
}
- sqlite3DbFree(db, pList->a);
- sqlite3DbFree(db, pList);
+ tdsqlite3DbFree(db, pList->a);
+ tdsqlite3DbFreeNN(db, pList);
}
/*
** Return the index in pList of the identifier named zId. Return -1
** if not found.
*/
-SQLITE_PRIVATE int sqlite3IdListIndex(IdList *pList, const char *zName){
+SQLITE_PRIVATE int tdsqlite3IdListIndex(IdList *pList, const char *zName){
int i;
if( pList==0 ) return -1;
for(i=0; i<pList->nId; i++){
- if( sqlite3StrICmp(pList->a[i].zName, zName)==0 ) return i;
+ if( tdsqlite3StrICmp(pList->a[i].zName, zName)==0 ) return i;
}
return -1;
}
/*
+** Maximum size of a SrcList object.
+** The SrcList object is used to represent the FROM clause of a
+** SELECT statement, and the query planner cannot deal with more
+** than 64 tables in a join. So any value larger than 64 here
+** is sufficient for most uses. Smaller values, like say 10, are
+** appropriate for small and memory-limited applications.
+*/
+#ifndef SQLITE_MAX_SRCLIST
+# define SQLITE_MAX_SRCLIST 200
+#endif
+
+/*
** Expand the space allocated for the given SrcList object by
** creating nExtra new slots beginning at iStart. iStart is zero based.
** New slots are zeroed.
@@ -105012,7 +116940,7 @@ SQLITE_PRIVATE int sqlite3IdListIndex(IdList *pList, const char *zName){
** For example, suppose a SrcList initially contains two entries: A,B.
** To append 3 new entries onto the end, do this:
**
-** sqlite3SrcListEnlarge(db, pSrclist, 3, 2);
+** tdsqlite3SrcListEnlarge(db, pSrclist, 3, 2);
**
** After the call above it would contain: A, B, nil, nil, nil.
** If the iStart argument had been 1 instead of 2, then the result
@@ -105020,11 +116948,12 @@ SQLITE_PRIVATE int sqlite3IdListIndex(IdList *pList, const char *zName){
** the iStart value would be 0. The result then would
** be: nil, nil, nil, A, B.
**
-** If a memory allocation fails the SrcList is unchanged. The
-** db->mallocFailed flag will be set to true.
+** If a memory allocation fails or the SrcList becomes too large, leave
+** the original SrcList unchanged, return NULL, and leave an error message
+** in pParse.
*/
-SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(
- sqlite3 *db, /* Database connection to notify of OOM errors */
+SQLITE_PRIVATE SrcList *tdsqlite3SrcListEnlarge(
+ Parse *pParse, /* Parsing context into which errors are reported */
SrcList *pSrc, /* The SrcList to be enlarged */
int nExtra, /* Number of new slots to add to pSrc->a[] */
int iStart /* Index in pSrc->a[] of first new slot */
@@ -105040,17 +116969,23 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(
/* Allocate additional space if needed */
if( (u32)pSrc->nSrc+nExtra>pSrc->nAlloc ){
SrcList *pNew;
- int nAlloc = pSrc->nSrc+nExtra;
- int nGot;
- pNew = sqlite3DbRealloc(db, pSrc,
+ tdsqlite3_int64 nAlloc = 2*(tdsqlite3_int64)pSrc->nSrc+nExtra;
+ tdsqlite3 *db = pParse->db;
+
+ if( pSrc->nSrc+nExtra>=SQLITE_MAX_SRCLIST ){
+ tdsqlite3ErrorMsg(pParse, "too many FROM clause terms, max: %d",
+ SQLITE_MAX_SRCLIST);
+ return 0;
+ }
+ if( nAlloc>SQLITE_MAX_SRCLIST ) nAlloc = SQLITE_MAX_SRCLIST;
+ pNew = tdsqlite3DbRealloc(db, pSrc,
sizeof(*pSrc) + (nAlloc-1)*sizeof(pSrc->a[0]) );
if( pNew==0 ){
assert( db->mallocFailed );
- return pSrc;
+ return 0;
}
pSrc = pNew;
- nGot = (sqlite3DbMallocSize(db, pNew) - sizeof(*pSrc))/sizeof(pSrc->a[0])+1;
- pSrc->nAlloc = nGot;
+ pSrc->nAlloc = nAlloc;
}
/* Move existing slots that come after the newly inserted slots
@@ -105075,7 +117010,8 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(
** Append a new table name to the given SrcList. Create a new SrcList if
** need be. A new entry is created in the SrcList even if pTable is NULL.
**
-** A SrcList is returned, or NULL if there is an OOM error. The returned
+** A SrcList is returned, or NULL if there is an OOM error or if the
+** SrcList grows to large. The returned
** SrcList might be the same as the SrcList that was input or it might be
** a new one. If an OOM error does occurs, then the prior value of pList
** that is input to this routine is automatically freed.
@@ -105090,59 +117026,67 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(
**
** In other words, if call like this:
**
-** sqlite3SrcListAppend(D,A,B,0);
+** tdsqlite3SrcListAppend(D,A,B,0);
**
** Then B is a table name and the database name is unspecified. If called
** like this:
**
-** sqlite3SrcListAppend(D,A,B,C);
+** tdsqlite3SrcListAppend(D,A,B,C);
**
** Then C is the table name and B is the database name. If C is defined
** then so is B. In other words, we never have a case where:
**
-** sqlite3SrcListAppend(D,A,0,C);
+** tdsqlite3SrcListAppend(D,A,0,C);
**
** Both pTable and pDatabase are assumed to be quoted. They are dequoted
** before being added to the SrcList.
*/
-SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(
- sqlite3 *db, /* Connection to notify of malloc failures */
+SQLITE_PRIVATE SrcList *tdsqlite3SrcListAppend(
+ Parse *pParse, /* Parsing context, in which errors are reported */
SrcList *pList, /* Append to this SrcList. NULL creates a new SrcList */
Token *pTable, /* Table to append */
Token *pDatabase /* Database of the table */
){
struct SrcList_item *pItem;
+ tdsqlite3 *db;
assert( pDatabase==0 || pTable!=0 ); /* Cannot have C without B */
- assert( db!=0 );
+ assert( pParse!=0 );
+ assert( pParse->db!=0 );
+ db = pParse->db;
if( pList==0 ){
- pList = sqlite3DbMallocRawNN(db, sizeof(SrcList) );
+ pList = tdsqlite3DbMallocRawNN(pParse->db, sizeof(SrcList) );
if( pList==0 ) return 0;
pList->nAlloc = 1;
- pList->nSrc = 0;
- }
- pList = sqlite3SrcListEnlarge(db, pList, 1, pList->nSrc);
- if( db->mallocFailed ){
- sqlite3SrcListDelete(db, pList);
- return 0;
+ pList->nSrc = 1;
+ memset(&pList->a[0], 0, sizeof(pList->a[0]));
+ pList->a[0].iCursor = -1;
+ }else{
+ SrcList *pNew = tdsqlite3SrcListEnlarge(pParse, pList, 1, pList->nSrc);
+ if( pNew==0 ){
+ tdsqlite3SrcListDelete(db, pList);
+ return 0;
+ }else{
+ pList = pNew;
+ }
}
pItem = &pList->a[pList->nSrc-1];
if( pDatabase && pDatabase->z==0 ){
pDatabase = 0;
}
if( pDatabase ){
- Token *pTemp = pDatabase;
- pDatabase = pTable;
- pTable = pTemp;
+ pItem->zName = tdsqlite3NameFromToken(db, pDatabase);
+ pItem->zDatabase = tdsqlite3NameFromToken(db, pTable);
+ }else{
+ pItem->zName = tdsqlite3NameFromToken(db, pTable);
+ pItem->zDatabase = 0;
}
- pItem->zName = sqlite3NameFromToken(db, pTable);
- pItem->zDatabase = sqlite3NameFromToken(db, pDatabase);
return pList;
}
/*
** Assign VdbeCursor index numbers to all tables in a SrcList
*/
-SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
+SQLITE_PRIVATE void tdsqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
int i;
struct SrcList_item *pItem;
assert(pList || pParse->db->mallocFailed );
@@ -105151,7 +117095,7 @@ SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
if( pItem->iCursor>=0 ) break;
pItem->iCursor = pParse->nTab++;
if( pItem->pSelect ){
- sqlite3SrcListAssignCursors(pParse, pItem->pSelect->pSrc);
+ tdsqlite3SrcListAssignCursors(pParse, pItem->pSelect->pSrc);
}
}
}
@@ -105160,22 +117104,22 @@ SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){
/*
** Delete an entire SrcList including all its substructure.
*/
-SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){
+SQLITE_PRIVATE void tdsqlite3SrcListDelete(tdsqlite3 *db, SrcList *pList){
int i;
struct SrcList_item *pItem;
if( pList==0 ) return;
for(pItem=pList->a, i=0; i<pList->nSrc; i++, pItem++){
- sqlite3DbFree(db, pItem->zDatabase);
- sqlite3DbFree(db, pItem->zName);
- sqlite3DbFree(db, pItem->zAlias);
- if( pItem->fg.isIndexedBy ) sqlite3DbFree(db, pItem->u1.zIndexedBy);
- if( pItem->fg.isTabFunc ) sqlite3ExprListDelete(db, pItem->u1.pFuncArg);
- sqlite3DeleteTable(db, pItem->pTab);
- sqlite3SelectDelete(db, pItem->pSelect);
- sqlite3ExprDelete(db, pItem->pOn);
- sqlite3IdListDelete(db, pItem->pUsing);
+ tdsqlite3DbFree(db, pItem->zDatabase);
+ tdsqlite3DbFree(db, pItem->zName);
+ tdsqlite3DbFree(db, pItem->zAlias);
+ if( pItem->fg.isIndexedBy ) tdsqlite3DbFree(db, pItem->u1.zIndexedBy);
+ if( pItem->fg.isTabFunc ) tdsqlite3ExprListDelete(db, pItem->u1.pFuncArg);
+ tdsqlite3DeleteTable(db, pItem->pTab);
+ tdsqlite3SelectDelete(db, pItem->pSelect);
+ tdsqlite3ExprDelete(db, pItem->pOn);
+ tdsqlite3IdListDelete(db, pItem->pUsing);
}
- sqlite3DbFree(db, pList);
+ tdsqlite3DbFreeNN(db, pList);
}
/*
@@ -105194,7 +117138,7 @@ SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){
** Return a new SrcList which encodes is the FROM with the new
** term added.
*/
-SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(
+SQLITE_PRIVATE SrcList *tdsqlite3SrcListAppendFromTerm(
Parse *pParse, /* Parsing context */
SrcList *p, /* The left part of the FROM clause already seen */
Token *pTable, /* Name of the table to add to the FROM clause */
@@ -105205,21 +117149,28 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(
IdList *pUsing /* The USING clause of a join */
){
struct SrcList_item *pItem;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
if( !p && (pOn || pUsing) ){
- sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s",
+ tdsqlite3ErrorMsg(pParse, "a JOIN clause is required before %s",
(pOn ? "ON" : "USING")
);
goto append_from_error;
}
- p = sqlite3SrcListAppend(db, p, pTable, pDatabase);
- if( p==0 || NEVER(p->nSrc==0) ){
+ p = tdsqlite3SrcListAppend(pParse, p, pTable, pDatabase);
+ if( p==0 ){
goto append_from_error;
}
+ assert( p->nSrc>0 );
pItem = &p->a[p->nSrc-1];
+ assert( (pTable==0)==(pDatabase==0) );
+ assert( pItem->zName==0 || pDatabase!=0 );
+ if( IN_RENAME_OBJECT && pItem->zName ){
+ Token *pToken = (ALWAYS(pDatabase) && pDatabase->z) ? pDatabase : pTable;
+ tdsqlite3RenameTokenMap(pParse, pItem->zName, pToken);
+ }
assert( pAlias!=0 );
if( pAlias->n ){
- pItem->zAlias = sqlite3NameFromToken(db, pAlias);
+ pItem->zAlias = tdsqlite3NameFromToken(db, pAlias);
}
pItem->pSelect = pSubquery;
pItem->pOn = pOn;
@@ -105228,9 +117179,9 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(
append_from_error:
assert( p==0 );
- sqlite3ExprDelete(db, pOn);
- sqlite3IdListDelete(db, pUsing);
- sqlite3SelectDelete(db, pSubquery);
+ tdsqlite3ExprDelete(db, pOn);
+ tdsqlite3IdListDelete(db, pUsing);
+ tdsqlite3SelectDelete(db, pSubquery);
return 0;
}
@@ -105238,10 +117189,12 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(
** Add an INDEXED BY or NOT INDEXED clause to the most recently added
** element of the source-list passed as the second argument.
*/
-SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){
+SQLITE_PRIVATE void tdsqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){
assert( pIndexedBy!=0 );
- if( p && ALWAYS(p->nSrc>0) ){
- struct SrcList_item *pItem = &p->a[p->nSrc-1];
+ if( p && pIndexedBy->n>0 ){
+ struct SrcList_item *pItem;
+ assert( p->nSrc>0 );
+ pItem = &p->a[p->nSrc-1];
assert( pItem->fg.notIndexed==0 );
assert( pItem->fg.isIndexedBy==0 );
assert( pItem->fg.isTabFunc==0 );
@@ -105250,8 +117203,8 @@ SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pI
** construct "indexed_opt" for details. */
pItem->fg.notIndexed = 1;
}else{
- pItem->u1.zIndexedBy = sqlite3NameFromToken(pParse->db, pIndexedBy);
- pItem->fg.isIndexedBy = (pItem->u1.zIndexedBy!=0);
+ pItem->u1.zIndexedBy = tdsqlite3NameFromToken(pParse->db, pIndexedBy);
+ pItem->fg.isIndexedBy = 1;
}
}
}
@@ -105260,7 +117213,7 @@ SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pI
** Add the list of function arguments to the SrcList entry for a
** table-valued-function.
*/
-SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *pList){
+SQLITE_PRIVATE void tdsqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *pList){
if( p ){
struct SrcList_item *pItem = &p->a[p->nSrc-1];
assert( pItem->fg.notIndexed==0 );
@@ -105269,7 +117222,7 @@ SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *
pItem->u1.pFuncArg = pList;
pItem->fg.isTabFunc = 1;
}else{
- sqlite3ExprListDelete(pParse->db, pList);
+ tdsqlite3ExprListDelete(pParse->db, pList);
}
}
@@ -105288,7 +117241,7 @@ SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *
** in p->a[0] and p->a[1], respectively. The parser initially stores the
** operator with A. This routine shifts that operator over to B.
*/
-SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList *p){
+SQLITE_PRIVATE void tdsqlite3SrcListShiftJoinType(SrcList *p){
if( p ){
int i;
for(i=p->nSrc-1; i>0; i--){
@@ -105301,59 +117254,48 @@ SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList *p){
/*
** Generate VDBE code for a BEGIN statement.
*/
-SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){
- sqlite3 *db;
+SQLITE_PRIVATE void tdsqlite3BeginTransaction(Parse *pParse, int type){
+ tdsqlite3 *db;
Vdbe *v;
int i;
assert( pParse!=0 );
db = pParse->db;
assert( db!=0 );
- if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "BEGIN", 0, 0) ){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "BEGIN", 0, 0) ){
return;
}
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( !v ) return;
if( type!=TK_DEFERRED ){
for(i=0; i<db->nDb; i++){
- sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1);
- sqlite3VdbeUsesBtree(v, i);
+ tdsqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1);
+ tdsqlite3VdbeUsesBtree(v, i);
}
}
- sqlite3VdbeAddOp0(v, OP_AutoCommit);
-}
-
-/*
-** Generate VDBE code for a COMMIT statement.
-*/
-SQLITE_PRIVATE void sqlite3CommitTransaction(Parse *pParse){
- Vdbe *v;
-
- assert( pParse!=0 );
- assert( pParse->db!=0 );
- if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ){
- return;
- }
- v = sqlite3GetVdbe(pParse);
- if( v ){
- sqlite3VdbeAddOp1(v, OP_AutoCommit, 1);
- }
+ tdsqlite3VdbeAddOp0(v, OP_AutoCommit);
}
/*
-** Generate VDBE code for a ROLLBACK statement.
+** Generate VDBE code for a COMMIT or ROLLBACK statement.
+** Code for ROLLBACK is generated if eType==TK_ROLLBACK. Otherwise
+** code is generated for a COMMIT.
*/
-SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse *pParse){
+SQLITE_PRIVATE void tdsqlite3EndTransaction(Parse *pParse, int eType){
Vdbe *v;
+ int isRollback;
assert( pParse!=0 );
assert( pParse->db!=0 );
- if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ){
+ assert( eType==TK_COMMIT || eType==TK_END || eType==TK_ROLLBACK );
+ isRollback = eType==TK_ROLLBACK;
+ if( tdsqlite3AuthCheck(pParse, SQLITE_TRANSACTION,
+ isRollback ? "ROLLBACK" : "COMMIT", 0, 0) ){
return;
}
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( v ){
- sqlite3VdbeAddOp2(v, OP_AutoCommit, 1, 1);
+ tdsqlite3VdbeAddOp2(v, OP_AutoCommit, 1, isRollback);
}
}
@@ -105361,19 +117303,19 @@ SQLITE_PRIVATE void sqlite3RollbackTransaction(Parse *pParse){
** This function is called by the parser when it parses a command to create,
** release or rollback an SQL savepoint.
*/
-SQLITE_PRIVATE void sqlite3Savepoint(Parse *pParse, int op, Token *pName){
- char *zName = sqlite3NameFromToken(pParse->db, pName);
+SQLITE_PRIVATE void tdsqlite3Savepoint(Parse *pParse, int op, Token *pName){
+ char *zName = tdsqlite3NameFromToken(pParse->db, pName);
if( zName ){
- Vdbe *v = sqlite3GetVdbe(pParse);
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
#ifndef SQLITE_OMIT_AUTHORIZATION
static const char * const az[] = { "BEGIN", "RELEASE", "ROLLBACK" };
assert( !SAVEPOINT_BEGIN && SAVEPOINT_RELEASE==1 && SAVEPOINT_ROLLBACK==2 );
#endif
- if( !v || sqlite3AuthCheck(pParse, SQLITE_SAVEPOINT, az[op], zName, 0) ){
- sqlite3DbFree(pParse->db, zName);
+ if( !v || tdsqlite3AuthCheck(pParse, SQLITE_SAVEPOINT, az[op], zName, 0) ){
+ tdsqlite3DbFree(pParse->db, zName);
return;
}
- sqlite3VdbeAddOp4(v, OP_Savepoint, op, 0, 0, zName, P4_DYNAMIC);
+ tdsqlite3VdbeAddOp4(v, OP_Savepoint, op, 0, 0, zName, P4_DYNAMIC);
}
}
@@ -105381,8 +117323,8 @@ SQLITE_PRIVATE void sqlite3Savepoint(Parse *pParse, int op, Token *pName){
** Make sure the TEMP database is open and available for use. Return
** the number of errors. Leave any error messages in the pParse structure.
*/
-SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){
- sqlite3 *db = pParse->db;
+SQLITE_PRIVATE int tdsqlite3OpenTempDatabase(Parse *pParse){
+ tdsqlite3 *db = pParse->db;
if( db->aDb[1].pBt==0 && !pParse->explain ){
int rc;
Btree *pBt;
@@ -105393,17 +117335,17 @@ SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){
SQLITE_OPEN_DELETEONCLOSE |
SQLITE_OPEN_TEMP_DB;
- rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pBt, 0, flags);
+ rc = tdsqlite3BtreeOpen(db->pVfs, 0, db, &pBt, 0, flags);
if( rc!=SQLITE_OK ){
- sqlite3ErrorMsg(pParse, "unable to open a temporary database "
+ tdsqlite3ErrorMsg(pParse, "unable to open a temporary database "
"file for storing temporary tables");
pParse->rc = rc;
return 1;
}
db->aDb[1].pBt = pBt;
assert( db->aDb[1].pSchema );
- if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize, -1, 0) ){
- sqlite3OomFault(db);
+ if( SQLITE_NOMEM==tdsqlite3BtreeSetPageSize(pBt, db->nextPagesize, -1, 0) ){
+ tdsqlite3OomFault(db);
return 1;
}
}
@@ -105414,34 +117356,34 @@ SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){
** Record the fact that the schema cookie will need to be verified
** for database iDb. The code to actually verify the schema cookie
** will occur at the end of the top-level VDBE and will be generated
-** later, by sqlite3FinishCoding().
+** later, by tdsqlite3FinishCoding().
*/
-SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
+SQLITE_PRIVATE void tdsqlite3CodeVerifySchema(Parse *pParse, int iDb){
+ Parse *pToplevel = tdsqlite3ParseToplevel(pParse);
assert( iDb>=0 && iDb<pParse->db->nDb );
assert( pParse->db->aDb[iDb].pBt!=0 || iDb==1 );
assert( iDb<SQLITE_MAX_ATTACHED+2 );
- assert( sqlite3SchemaMutexHeld(pParse->db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(pParse->db, iDb, 0) );
if( DbMaskTest(pToplevel->cookieMask, iDb)==0 ){
DbMaskSet(pToplevel->cookieMask, iDb);
if( !OMIT_TEMPDB && iDb==1 ){
- sqlite3OpenTempDatabase(pToplevel);
+ tdsqlite3OpenTempDatabase(pToplevel);
}
}
}
/*
-** If argument zDb is NULL, then call sqlite3CodeVerifySchema() for each
+** If argument zDb is NULL, then call tdsqlite3CodeVerifySchema() for each
** attached database. Otherwise, invoke it for the database named zDb only.
*/
-SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb){
- sqlite3 *db = pParse->db;
+SQLITE_PRIVATE void tdsqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb){
+ tdsqlite3 *db = pParse->db;
int i;
for(i=0; i<db->nDb; i++){
Db *pDb = &db->aDb[i];
- if( pDb->pBt && (!zDb || 0==sqlite3StrICmp(zDb, pDb->zDbSName)) ){
- sqlite3CodeVerifySchema(pParse, i);
+ if( pDb->pBt && (!zDb || 0==tdsqlite3StrICmp(zDb, pDb->zDbSName)) ){
+ tdsqlite3CodeVerifySchema(pParse, i);
}
}
}
@@ -105459,9 +117401,9 @@ SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb)
** can be checked before any changes are made to the database, it is never
** necessary to undo a write and the checkpoint should not be set.
*/
-SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
- sqlite3CodeVerifySchema(pParse, iDb);
+SQLITE_PRIVATE void tdsqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){
+ Parse *pToplevel = tdsqlite3ParseToplevel(pParse);
+ tdsqlite3CodeVerifySchema(pParse, iDb);
DbMaskSet(pToplevel->writeMask, iDb);
pToplevel->isMultiWrite |= setStatement;
}
@@ -105473,8 +117415,8 @@ SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse *pParse, int setStatement,
** If an abort occurs after some of these writes have completed, then it will
** be necessary to undo the completed writes.
*/
-SQLITE_PRIVATE void sqlite3MultiWrite(Parse *pParse){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
+SQLITE_PRIVATE void tdsqlite3MultiWrite(Parse *pParse){
+ Parse *pToplevel = tdsqlite3ParseToplevel(pParse);
pToplevel->isMultiWrite = 1;
}
@@ -105491,11 +117433,11 @@ SQLITE_PRIVATE void sqlite3MultiWrite(Parse *pParse){
** go a little faster. But taking advantage of this time dependency
** makes it more difficult to prove that the code is correct (in
** particular, it prevents us from writing an effective
-** implementation of sqlite3AssertMayAbort()) and so we have chosen
+** implementation of tdsqlite3AssertMayAbort()) and so we have chosen
** to take the safe route and skip the optimization.
*/
-SQLITE_PRIVATE void sqlite3MayAbort(Parse *pParse){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
+SQLITE_PRIVATE void tdsqlite3MayAbort(Parse *pParse){
+ Parse *pToplevel = tdsqlite3ParseToplevel(pParse);
pToplevel->mayAbort = 1;
}
@@ -105504,7 +117446,7 @@ SQLITE_PRIVATE void sqlite3MayAbort(Parse *pParse){
** error. The onError parameter determines which (if any) of the statement
** and/or current transaction is rolled back.
*/
-SQLITE_PRIVATE void sqlite3HaltConstraint(
+SQLITE_PRIVATE void tdsqlite3HaltConstraint(
Parse *pParse, /* Parsing context */
int errCode, /* extended error code */
int onError, /* Constraint type */
@@ -105512,19 +117454,19 @@ SQLITE_PRIVATE void sqlite3HaltConstraint(
i8 p4type, /* P4_STATIC or P4_TRANSIENT */
u8 p5Errmsg /* P5_ErrMsg type */
){
- Vdbe *v = sqlite3GetVdbe(pParse);
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
assert( (errCode&0xff)==SQLITE_CONSTRAINT );
if( onError==OE_Abort ){
- sqlite3MayAbort(pParse);
+ tdsqlite3MayAbort(pParse);
}
- sqlite3VdbeAddOp4(v, OP_Halt, errCode, onError, 0, p4, p4type);
- sqlite3VdbeChangeP5(v, p5Errmsg);
+ tdsqlite3VdbeAddOp4(v, OP_Halt, errCode, onError, 0, p4, p4type);
+ tdsqlite3VdbeChangeP5(v, p5Errmsg);
}
/*
** Code an OP_Halt due to UNIQUE or PRIMARY KEY constraint violation.
*/
-SQLITE_PRIVATE void sqlite3UniqueConstraint(
+SQLITE_PRIVATE void tdsqlite3UniqueConstraint(
Parse *pParse, /* Parsing context */
int onError, /* Constraint type */
Index *pIdx /* The index that triggers the constraint */
@@ -105534,20 +117476,23 @@ SQLITE_PRIVATE void sqlite3UniqueConstraint(
StrAccum errMsg;
Table *pTab = pIdx->pTable;
- sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, 200);
+ tdsqlite3StrAccumInit(&errMsg, pParse->db, 0, 0,
+ pParse->db->aLimit[SQLITE_LIMIT_LENGTH]);
if( pIdx->aColExpr ){
- sqlite3XPrintf(&errMsg, "index '%q'", pIdx->zName);
+ tdsqlite3_str_appendf(&errMsg, "index '%q'", pIdx->zName);
}else{
for(j=0; j<pIdx->nKeyCol; j++){
char *zCol;
assert( pIdx->aiColumn[j]>=0 );
zCol = pTab->aCol[pIdx->aiColumn[j]].zName;
- if( j ) sqlite3StrAccumAppend(&errMsg, ", ", 2);
- sqlite3XPrintf(&errMsg, "%s.%s", pTab->zName, zCol);
+ if( j ) tdsqlite3_str_append(&errMsg, ", ", 2);
+ tdsqlite3_str_appendall(&errMsg, pTab->zName);
+ tdsqlite3_str_append(&errMsg, ".", 1);
+ tdsqlite3_str_appendall(&errMsg, zCol);
}
}
- zErr = sqlite3StrAccumFinish(&errMsg);
- sqlite3HaltConstraint(pParse,
+ zErr = tdsqlite3StrAccumFinish(&errMsg);
+ tdsqlite3HaltConstraint(pParse,
IsPrimaryKeyIndex(pIdx) ? SQLITE_CONSTRAINT_PRIMARYKEY
: SQLITE_CONSTRAINT_UNIQUE,
onError, zErr, P4_DYNAMIC, P5_ConstraintUnique);
@@ -105557,7 +117502,7 @@ SQLITE_PRIVATE void sqlite3UniqueConstraint(
/*
** Code an OP_Halt due to non-unique rowid.
*/
-SQLITE_PRIVATE void sqlite3RowidConstraint(
+SQLITE_PRIVATE void tdsqlite3RowidConstraint(
Parse *pParse, /* Parsing context */
int onError, /* Conflict resolution algorithm */
Table *pTab /* The table with the non-unique rowid */
@@ -105565,14 +117510,14 @@ SQLITE_PRIVATE void sqlite3RowidConstraint(
char *zMsg;
int rc;
if( pTab->iPKey>=0 ){
- zMsg = sqlite3MPrintf(pParse->db, "%s.%s", pTab->zName,
+ zMsg = tdsqlite3MPrintf(pParse->db, "%s.%s", pTab->zName,
pTab->aCol[pTab->iPKey].zName);
rc = SQLITE_CONSTRAINT_PRIMARYKEY;
}else{
- zMsg = sqlite3MPrintf(pParse->db, "%s.rowid", pTab->zName);
+ zMsg = tdsqlite3MPrintf(pParse->db, "%s.rowid", pTab->zName);
rc = SQLITE_CONSTRAINT_ROWID;
}
- sqlite3HaltConstraint(pParse, rc, onError, zMsg, P4_DYNAMIC,
+ tdsqlite3HaltConstraint(pParse, rc, onError, zMsg, P4_DYNAMIC,
P5_ConstraintUnique);
}
@@ -105587,7 +117532,7 @@ static int collationMatch(const char *zColl, Index *pIndex){
for(i=0; i<pIndex->nColumn; i++){
const char *z = pIndex->azColl[i];
assert( z!=0 || pIndex->aiColumn[i]<0 );
- if( pIndex->aiColumn[i]>=0 && 0==sqlite3StrICmp(z, zColl) ){
+ if( pIndex->aiColumn[i]>=0 && 0==tdsqlite3StrICmp(z, zColl) ){
return 1;
}
}
@@ -105601,13 +117546,15 @@ static int collationMatch(const char *zColl, Index *pIndex){
*/
#ifndef SQLITE_OMIT_REINDEX
static void reindexTable(Parse *pParse, Table *pTab, char const *zColl){
- Index *pIndex; /* An index associated with pTab */
+ if( !IsVirtual(pTab) ){
+ Index *pIndex; /* An index associated with pTab */
- for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){
- if( zColl==0 || collationMatch(zColl, pIndex) ){
- int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
- sqlite3BeginWriteOperation(pParse, 0, iDb);
- sqlite3RefillIndex(pParse, pIndex, -1);
+ for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){
+ if( zColl==0 || collationMatch(zColl, pIndex) ){
+ int iDb = tdsqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ tdsqlite3BeginWriteOperation(pParse, 0, iDb);
+ tdsqlite3RefillIndex(pParse, pIndex, -1);
+ }
}
}
}
@@ -105622,11 +117569,11 @@ static void reindexTable(Parse *pParse, Table *pTab, char const *zColl){
static void reindexDatabases(Parse *pParse, char const *zColl){
Db *pDb; /* A single database */
int iDb; /* The database index number */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
HashElem *k; /* For looping over tables in pDb */
Table *pTab; /* A table in the database */
- assert( sqlite3BtreeHoldsAllMutexes(db) ); /* Needed for schema access */
+ assert( tdsqlite3BtreeHoldsAllMutexes(db) ); /* Needed for schema access */
for(iDb=0, pDb=db->aDb; iDb<db->nDb; iDb++, pDb++){
assert( pDb!=0 );
for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){
@@ -105651,19 +117598,19 @@ static void reindexDatabases(Parse *pParse, char const *zColl){
** indices associated with the named table.
*/
#ifndef SQLITE_OMIT_REINDEX
-SQLITE_PRIVATE void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){
+SQLITE_PRIVATE void tdsqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){
CollSeq *pColl; /* Collating sequence to be reindexed, or NULL */
char *z; /* Name of a table or index */
const char *zDb; /* Name of the database */
Table *pTab; /* A table in the database */
Index *pIndex; /* An index associated with pTab */
int iDb; /* The database index number */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
Token *pObjName; /* Name of the table or index to be reindexed */
/* Read the database schema. If an error occurs, leave an error message
** and code in pParse and return NULL. */
- if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ if( SQLITE_OK!=tdsqlite3ReadSchema(pParse) ){
return;
}
@@ -105673,65 +117620,78 @@ SQLITE_PRIVATE void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){
}else if( NEVER(pName2==0) || pName2->z==0 ){
char *zColl;
assert( pName1->z );
- zColl = sqlite3NameFromToken(pParse->db, pName1);
+ zColl = tdsqlite3NameFromToken(pParse->db, pName1);
if( !zColl ) return;
- pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0);
+ pColl = tdsqlite3FindCollSeq(db, ENC(db), zColl, 0);
if( pColl ){
reindexDatabases(pParse, zColl);
- sqlite3DbFree(db, zColl);
+ tdsqlite3DbFree(db, zColl);
return;
}
- sqlite3DbFree(db, zColl);
+ tdsqlite3DbFree(db, zColl);
}
- iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pObjName);
+ iDb = tdsqlite3TwoPartName(pParse, pName1, pName2, &pObjName);
if( iDb<0 ) return;
- z = sqlite3NameFromToken(db, pObjName);
+ z = tdsqlite3NameFromToken(db, pObjName);
if( z==0 ) return;
zDb = db->aDb[iDb].zDbSName;
- pTab = sqlite3FindTable(db, z, zDb);
+ pTab = tdsqlite3FindTable(db, z, zDb);
if( pTab ){
reindexTable(pParse, pTab, 0);
- sqlite3DbFree(db, z);
+ tdsqlite3DbFree(db, z);
return;
}
- pIndex = sqlite3FindIndex(db, z, zDb);
- sqlite3DbFree(db, z);
+ pIndex = tdsqlite3FindIndex(db, z, zDb);
+ tdsqlite3DbFree(db, z);
if( pIndex ){
- sqlite3BeginWriteOperation(pParse, 0, iDb);
- sqlite3RefillIndex(pParse, pIndex, -1);
+ tdsqlite3BeginWriteOperation(pParse, 0, iDb);
+ tdsqlite3RefillIndex(pParse, pIndex, -1);
return;
}
- sqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed");
+ tdsqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed");
}
#endif
/*
** Return a KeyInfo structure that is appropriate for the given Index.
**
-** The caller should invoke sqlite3KeyInfoUnref() on the returned object
+** The caller should invoke tdsqlite3KeyInfoUnref() on the returned object
** when it has finished using it.
*/
-SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){
+SQLITE_PRIVATE KeyInfo *tdsqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){
int i;
int nCol = pIdx->nColumn;
int nKey = pIdx->nKeyCol;
KeyInfo *pKey;
if( pParse->nErr ) return 0;
if( pIdx->uniqNotNull ){
- pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey);
+ pKey = tdsqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey);
}else{
- pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0);
+ pKey = tdsqlite3KeyInfoAlloc(pParse->db, nCol, 0);
}
if( pKey ){
- assert( sqlite3KeyInfoIsWriteable(pKey) );
+ assert( tdsqlite3KeyInfoIsWriteable(pKey) );
for(i=0; i<nCol; i++){
const char *zColl = pIdx->azColl[i];
- pKey->aColl[i] = zColl==sqlite3StrBINARY ? 0 :
- sqlite3LocateCollSeq(pParse, zColl);
- pKey->aSortOrder[i] = pIdx->aSortOrder[i];
+ pKey->aColl[i] = zColl==tdsqlite3StrBINARY ? 0 :
+ tdsqlite3LocateCollSeq(pParse, zColl);
+ pKey->aSortFlags[i] = pIdx->aSortOrder[i];
+ assert( 0==(pKey->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) );
}
if( pParse->nErr ){
- sqlite3KeyInfoUnref(pKey);
+ assert( pParse->rc==SQLITE_ERROR_MISSING_COLLSEQ );
+ if( pIdx->bNoQuery==0 ){
+ /* Deactivate the index because it contains an unknown collating
+ ** sequence. The only way to reactive the index is to reload the
+ ** schema. Adding the missing collating sequence later does not
+ ** reactive the index. The application had the chance to register
+ ** the missing index using the collation-needed callback. For
+ ** simplicity, SQLite will not give the application a second chance.
+ */
+ pIdx->bNoQuery = 1;
+ pParse->rc = SQLITE_ERROR_RETRY;
+ }
+ tdsqlite3KeyInfoUnref(pKey);
pKey = 0;
}
}
@@ -105743,41 +117703,41 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){
** This routine is invoked once per CTE by the parser while parsing a
** WITH clause.
*/
-SQLITE_PRIVATE With *sqlite3WithAdd(
+SQLITE_PRIVATE With *tdsqlite3WithAdd(
Parse *pParse, /* Parsing context */
With *pWith, /* Existing WITH clause, or NULL */
Token *pName, /* Name of the common-table */
ExprList *pArglist, /* Optional column name list for the table */
Select *pQuery /* Query used to initialize the table */
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
With *pNew;
char *zName;
/* Check that the CTE name is unique within this WITH clause. If
** not, store an error in the Parse structure. */
- zName = sqlite3NameFromToken(pParse->db, pName);
+ zName = tdsqlite3NameFromToken(pParse->db, pName);
if( zName && pWith ){
int i;
for(i=0; i<pWith->nCte; i++){
- if( sqlite3StrICmp(zName, pWith->a[i].zName)==0 ){
- sqlite3ErrorMsg(pParse, "duplicate WITH table name: %s", zName);
+ if( tdsqlite3StrICmp(zName, pWith->a[i].zName)==0 ){
+ tdsqlite3ErrorMsg(pParse, "duplicate WITH table name: %s", zName);
}
}
}
if( pWith ){
- int nByte = sizeof(*pWith) + (sizeof(pWith->a[1]) * pWith->nCte);
- pNew = sqlite3DbRealloc(db, pWith, nByte);
+ tdsqlite3_int64 nByte = sizeof(*pWith) + (sizeof(pWith->a[1]) * pWith->nCte);
+ pNew = tdsqlite3DbRealloc(db, pWith, nByte);
}else{
- pNew = sqlite3DbMallocZero(db, sizeof(*pWith));
+ pNew = tdsqlite3DbMallocZero(db, sizeof(*pWith));
}
assert( (pNew!=0 && zName!=0) || db->mallocFailed );
if( db->mallocFailed ){
- sqlite3ExprListDelete(db, pArglist);
- sqlite3SelectDelete(db, pQuery);
- sqlite3DbFree(db, zName);
+ tdsqlite3ExprListDelete(db, pArglist);
+ tdsqlite3SelectDelete(db, pQuery);
+ tdsqlite3DbFree(db, zName);
pNew = pWith;
}else{
pNew->a[pNew->nCte].pSelect = pQuery;
@@ -105793,16 +117753,16 @@ SQLITE_PRIVATE With *sqlite3WithAdd(
/*
** Free the contents of the With object passed as the second argument.
*/
-SQLITE_PRIVATE void sqlite3WithDelete(sqlite3 *db, With *pWith){
+SQLITE_PRIVATE void tdsqlite3WithDelete(tdsqlite3 *db, With *pWith){
if( pWith ){
int i;
for(i=0; i<pWith->nCte; i++){
struct Cte *pCte = &pWith->a[i];
- sqlite3ExprListDelete(db, pCte->pCols);
- sqlite3SelectDelete(db, pCte->pSelect);
- sqlite3DbFree(db, pCte->zName);
+ tdsqlite3ExprListDelete(db, pCte->pCols);
+ tdsqlite3SelectDelete(db, pCte->pSelect);
+ tdsqlite3DbFree(db, pCte->zName);
}
- sqlite3DbFree(db, pWith);
+ tdsqlite3DbFree(db, pWith);
}
}
#endif /* !defined(SQLITE_OMIT_CTE) */
@@ -105831,24 +117791,24 @@ SQLITE_PRIVATE void sqlite3WithDelete(sqlite3 *db, With *pWith){
** Invoke the 'collation needed' callback to request a collation sequence
** in the encoding enc of name zName, length nName.
*/
-static void callCollNeeded(sqlite3 *db, int enc, const char *zName){
+static void callCollNeeded(tdsqlite3 *db, int enc, const char *zName){
assert( !db->xCollNeeded || !db->xCollNeeded16 );
if( db->xCollNeeded ){
- char *zExternal = sqlite3DbStrDup(db, zName);
+ char *zExternal = tdsqlite3DbStrDup(db, zName);
if( !zExternal ) return;
db->xCollNeeded(db->pCollNeededArg, db, enc, zExternal);
- sqlite3DbFree(db, zExternal);
+ tdsqlite3DbFree(db, zExternal);
}
#ifndef SQLITE_OMIT_UTF16
if( db->xCollNeeded16 ){
char const *zExternal;
- sqlite3_value *pTmp = sqlite3ValueNew(db);
- sqlite3ValueSetStr(pTmp, -1, zName, SQLITE_UTF8, SQLITE_STATIC);
- zExternal = sqlite3ValueText(pTmp, SQLITE_UTF16NATIVE);
+ tdsqlite3_value *pTmp = tdsqlite3ValueNew(db);
+ tdsqlite3ValueSetStr(pTmp, -1, zName, SQLITE_UTF8, SQLITE_STATIC);
+ zExternal = tdsqlite3ValueText(pTmp, SQLITE_UTF16NATIVE);
if( zExternal ){
db->xCollNeeded16(db->pCollNeededArg, db, (int)ENC(db), zExternal);
}
- sqlite3ValueFree(pTmp);
+ tdsqlite3ValueFree(pTmp);
}
#endif
}
@@ -105860,13 +117820,13 @@ static void callCollNeeded(sqlite3 *db, int enc, const char *zName){
** of these instead if they exist. Avoid a UTF-8 <-> UTF-16 conversion if
** possible.
*/
-static int synthCollSeq(sqlite3 *db, CollSeq *pColl){
+static int synthCollSeq(tdsqlite3 *db, CollSeq *pColl){
CollSeq *pColl2;
char *z = pColl->zName;
int i;
static const u8 aEnc[] = { SQLITE_UTF16BE, SQLITE_UTF16LE, SQLITE_UTF8 };
for(i=0; i<3; i++){
- pColl2 = sqlite3FindCollSeq(db, aEnc[i], z, 0);
+ pColl2 = tdsqlite3FindCollSeq(db, aEnc[i], z, 0);
if( pColl2->xCmp!=0 ){
memcpy(pColl, pColl2, sizeof(CollSeq));
pColl->xDel = 0; /* Do not copy the destructor */
@@ -105877,65 +117837,21 @@ static int synthCollSeq(sqlite3 *db, CollSeq *pColl){
}
/*
-** This function is responsible for invoking the collation factory callback
-** or substituting a collation sequence of a different encoding when the
-** requested collation sequence is not available in the desired encoding.
-**
-** If it is not NULL, then pColl must point to the database native encoding
-** collation sequence with name zName, length nName.
-**
-** The return value is either the collation sequence to be used in database
-** db for collation type name zName, length nName, or NULL, if no collation
-** sequence can be found. If no collation is found, leave an error message.
-**
-** See also: sqlite3LocateCollSeq(), sqlite3FindCollSeq()
-*/
-SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(
- Parse *pParse, /* Parsing context */
- u8 enc, /* The desired encoding for the collating sequence */
- CollSeq *pColl, /* Collating sequence with native encoding, or NULL */
- const char *zName /* Collating sequence name */
-){
- CollSeq *p;
- sqlite3 *db = pParse->db;
-
- p = pColl;
- if( !p ){
- p = sqlite3FindCollSeq(db, enc, zName, 0);
- }
- if( !p || !p->xCmp ){
- /* No collation sequence of this type for this encoding is registered.
- ** Call the collation factory to see if it can supply us with one.
- */
- callCollNeeded(db, enc, zName);
- p = sqlite3FindCollSeq(db, enc, zName, 0);
- }
- if( p && !p->xCmp && synthCollSeq(db, p) ){
- p = 0;
- }
- assert( !p || p->xCmp );
- if( p==0 ){
- sqlite3ErrorMsg(pParse, "no such collation sequence: %s", zName);
- }
- return p;
-}
-
-/*
** This routine is called on a collation sequence before it is used to
** check that it is defined. An undefined collation sequence exists when
** a database is loaded that contains references to collation sequences
-** that have not been defined by sqlite3_create_collation() etc.
+** that have not been defined by tdsqlite3_create_collation() etc.
**
** If required, this routine calls the 'collation needed' callback to
** request a definition of the collating sequence. If this doesn't work,
** an equivalent collating sequence that uses a text encoding different
** from the main database is substituted, if one is available.
*/
-SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *pParse, CollSeq *pColl){
- if( pColl ){
+SQLITE_PRIVATE int tdsqlite3CheckCollSeq(Parse *pParse, CollSeq *pColl){
+ if( pColl && pColl->xCmp==0 ){
const char *zName = pColl->zName;
- sqlite3 *db = pParse->db;
- CollSeq *p = sqlite3GetCollSeq(pParse, ENC(db), pColl, zName);
+ tdsqlite3 *db = pParse->db;
+ CollSeq *p = tdsqlite3GetCollSeq(pParse, ENC(db), pColl, zName);
if( !p ){
return SQLITE_ERROR;
}
@@ -105960,16 +117876,16 @@ SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *pParse, CollSeq *pColl){
** each collation sequence structure.
*/
static CollSeq *findCollSeqEntry(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
const char *zName, /* Name of the collating sequence */
int create /* Create a new entry if true */
){
CollSeq *pColl;
- pColl = sqlite3HashFind(&db->aCollSeq, zName);
+ pColl = tdsqlite3HashFind(&db->aCollSeq, zName);
if( 0==pColl && create ){
- int nName = sqlite3Strlen30(zName);
- pColl = sqlite3DbMallocZero(db, 3*sizeof(*pColl) + nName + 1);
+ int nName = tdsqlite3Strlen30(zName) + 1;
+ pColl = tdsqlite3DbMallocZero(db, 3*sizeof(*pColl) + nName);
if( pColl ){
CollSeq *pDel = 0;
pColl[0].zName = (char*)&pColl[3];
@@ -105979,17 +117895,16 @@ static CollSeq *findCollSeqEntry(
pColl[2].zName = (char*)&pColl[3];
pColl[2].enc = SQLITE_UTF16BE;
memcpy(pColl[0].zName, zName, nName);
- pColl[0].zName[nName] = 0;
- pDel = sqlite3HashInsert(&db->aCollSeq, pColl[0].zName, pColl);
+ pDel = tdsqlite3HashInsert(&db->aCollSeq, pColl[0].zName, pColl);
- /* If a malloc() failure occurred in sqlite3HashInsert(), it will
+ /* If a malloc() failure occurred in tdsqlite3HashInsert(), it will
** return the pColl pointer to be deleted (because it wasn't added
** to the hash table).
*/
assert( pDel==0 || pDel==pColl );
if( pDel!=0 ){
- sqlite3OomFault(db);
- sqlite3DbFree(db, pDel);
+ tdsqlite3OomFault(db);
+ tdsqlite3DbFree(db, pDel);
pColl = 0;
}
}
@@ -106005,18 +117920,18 @@ static CollSeq *findCollSeqEntry(
** If the entry specified is not found and 'create' is true, then create a
** new entry. Otherwise return NULL.
**
-** A separate function sqlite3LocateCollSeq() is a wrapper around
-** this routine. sqlite3LocateCollSeq() invokes the collation factory
+** A separate function tdsqlite3LocateCollSeq() is a wrapper around
+** this routine. tdsqlite3LocateCollSeq() invokes the collation factory
** if necessary and generates an error message if the collating sequence
** cannot be found.
**
-** See also: sqlite3LocateCollSeq(), sqlite3GetCollSeq()
+** See also: tdsqlite3LocateCollSeq(), tdsqlite3GetCollSeq()
*/
-SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(
- sqlite3 *db,
- u8 enc,
- const char *zName,
- int create
+SQLITE_PRIVATE CollSeq *tdsqlite3FindCollSeq(
+ tdsqlite3 *db, /* Database connection to search */
+ u8 enc, /* Desired text encoding */
+ const char *zName, /* Name of the collating sequence. Might be NULL */
+ int create /* True to create CollSeq if doesn't already exist */
){
CollSeq *pColl;
if( zName ){
@@ -106030,6 +117945,85 @@ SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(
return pColl;
}
+/*
+** This function is responsible for invoking the collation factory callback
+** or substituting a collation sequence of a different encoding when the
+** requested collation sequence is not available in the desired encoding.
+**
+** If it is not NULL, then pColl must point to the database native encoding
+** collation sequence with name zName, length nName.
+**
+** The return value is either the collation sequence to be used in database
+** db for collation type name zName, length nName, or NULL, if no collation
+** sequence can be found. If no collation is found, leave an error message.
+**
+** See also: tdsqlite3LocateCollSeq(), tdsqlite3FindCollSeq()
+*/
+SQLITE_PRIVATE CollSeq *tdsqlite3GetCollSeq(
+ Parse *pParse, /* Parsing context */
+ u8 enc, /* The desired encoding for the collating sequence */
+ CollSeq *pColl, /* Collating sequence with native encoding, or NULL */
+ const char *zName /* Collating sequence name */
+){
+ CollSeq *p;
+ tdsqlite3 *db = pParse->db;
+
+ p = pColl;
+ if( !p ){
+ p = tdsqlite3FindCollSeq(db, enc, zName, 0);
+ }
+ if( !p || !p->xCmp ){
+ /* No collation sequence of this type for this encoding is registered.
+ ** Call the collation factory to see if it can supply us with one.
+ */
+ callCollNeeded(db, enc, zName);
+ p = tdsqlite3FindCollSeq(db, enc, zName, 0);
+ }
+ if( p && !p->xCmp && synthCollSeq(db, p) ){
+ p = 0;
+ }
+ assert( !p || p->xCmp );
+ if( p==0 ){
+ tdsqlite3ErrorMsg(pParse, "no such collation sequence: %s", zName);
+ pParse->rc = SQLITE_ERROR_MISSING_COLLSEQ;
+ }
+ return p;
+}
+
+/*
+** This function returns the collation sequence for database native text
+** encoding identified by the string zName.
+**
+** If the requested collation sequence is not available, or not available
+** in the database native encoding, the collation factory is invoked to
+** request it. If the collation factory does not supply such a sequence,
+** and the sequence is available in another text encoding, then that is
+** returned instead.
+**
+** If no versions of the requested collations sequence are available, or
+** another error occurs, NULL is returned and an error message written into
+** pParse.
+**
+** This routine is a wrapper around tdsqlite3FindCollSeq(). This routine
+** invokes the collation factory if the named collation cannot be found
+** and generates an error message.
+**
+** See also: tdsqlite3FindCollSeq(), tdsqlite3GetCollSeq()
+*/
+SQLITE_PRIVATE CollSeq *tdsqlite3LocateCollSeq(Parse *pParse, const char *zName){
+ tdsqlite3 *db = pParse->db;
+ u8 enc = ENC(db);
+ u8 initbusy = db->init.busy;
+ CollSeq *pColl;
+
+ pColl = tdsqlite3FindCollSeq(db, enc, zName, initbusy);
+ if( !initbusy && (!pColl || !pColl->xCmp) ){
+ pColl = tdsqlite3GetCollSeq(pParse, enc, pColl, zName);
+ }
+
+ return pColl;
+}
+
/* During the search for the best function definition, this procedure
** is called to test how well the function passed as the first argument
** matches the request for a function with nArg arguments in a system
@@ -106065,12 +118059,13 @@ static int matchQuality(
u8 enc /* Desired text encoding */
){
int match;
-
- /* nArg of -2 is a special case */
- if( nArg==(-2) ) return (p->xSFunc==0) ? 0 : FUNC_PERFECT_MATCH;
+ assert( p->nArg>=-1 );
/* Wrong number of arguments means "no match" */
- if( p->nArg!=nArg && p->nArg>=0 ) return 0;
+ if( p->nArg!=nArg ){
+ if( nArg==(-2) ) return (p->xSFunc==0) ? 0 : FUNC_PERFECT_MATCH;
+ if( p->nArg>=0 ) return 0;
+ }
/* Give a better score to a function with a specific number of arguments
** than to function that accepts any number of arguments. */
@@ -106094,13 +118089,13 @@ static int matchQuality(
** Search a FuncDefHash for a function with the given name. Return
** a pointer to the matching FuncDef if found, or 0 if there is no match.
*/
-static FuncDef *functionSearch(
+SQLITE_PRIVATE FuncDef *tdsqlite3FunctionSearch(
int h, /* Hash of the name */
const char *zFunc /* Name of function */
){
FuncDef *p;
- for(p=sqlite3BuiltinFunctions.a[h]; p; p=p->u.pHash){
- if( sqlite3StrICmp(p->zName, zFunc)==0 ){
+ for(p=tdsqlite3BuiltinFunctions.a[h]; p; p=p->u.pHash){
+ if( tdsqlite3StrICmp(p->zName, zFunc)==0 ){
return p;
}
}
@@ -106110,7 +118105,7 @@ static FuncDef *functionSearch(
/*
** Insert a new FuncDef into a FuncDefHash hash table.
*/
-SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(
+SQLITE_PRIVATE void tdsqlite3InsertBuiltinFuncs(
FuncDef *aDef, /* List of global functions to be inserted */
int nDef /* Length of the apDef[] list */
){
@@ -106118,17 +118113,18 @@ SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(
for(i=0; i<nDef; i++){
FuncDef *pOther;
const char *zName = aDef[i].zName;
- int nName = sqlite3Strlen30(zName);
- int h = (sqlite3UpperToLower[(u8)zName[0]] + nName) % SQLITE_FUNC_HASH_SZ;
- pOther = functionSearch(h, zName);
+ int nName = tdsqlite3Strlen30(zName);
+ int h = SQLITE_FUNC_HASH(zName[0], nName);
+ assert( zName[0]>='a' && zName[0]<='z' );
+ pOther = tdsqlite3FunctionSearch(h, zName);
if( pOther ){
assert( pOther!=&aDef[i] && pOther->pNext!=&aDef[i] );
aDef[i].pNext = pOther->pNext;
pOther->pNext = &aDef[i];
}else{
aDef[i].pNext = 0;
- aDef[i].u.pHash = sqlite3BuiltinFunctions.a[h];
- sqlite3BuiltinFunctions.a[h] = &aDef[i];
+ aDef[i].u.pHash = tdsqlite3BuiltinFunctions.a[h];
+ tdsqlite3BuiltinFunctions.a[h] = &aDef[i];
}
}
}
@@ -106154,8 +118150,8 @@ SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(
** number of arguments may be returned even if the eTextRep flag does not
** match that requested.
*/
-SQLITE_PRIVATE FuncDef *sqlite3FindFunction(
- sqlite3 *db, /* An open database */
+SQLITE_PRIVATE FuncDef *tdsqlite3FindFunction(
+ tdsqlite3 *db, /* An open database */
const char *zName, /* Name of the function. zero-terminated */
int nArg, /* Number of arguments. -1 means any number */
u8 enc, /* Preferred text encoding */
@@ -106169,11 +118165,11 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction(
assert( nArg>=(-2) );
assert( nArg>=(-1) || createFlag==0 );
- nName = sqlite3Strlen30(zName);
+ nName = tdsqlite3Strlen30(zName);
/* First search for a match amongst the application-defined functions.
*/
- p = (FuncDef*)sqlite3HashFind(&db->aFunc, zName);
+ p = (FuncDef*)tdsqlite3HashFind(&db->aFunc, zName);
while( p ){
int score = matchQuality(p, nArg, enc);
if( score>bestScore ){
@@ -106185,7 +118181,7 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction(
/* If no match is found, search the built-in functions.
**
- ** If the SQLITE_PreferBuiltin flag is set, then search the built-in
+ ** If the DBFLAG_PreferBuiltin flag is set, then search the built-in
** functions even if a prior app-defined function was found. And give
** priority to built-in functions.
**
@@ -106195,10 +118191,10 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction(
** new function. But the FuncDefs for built-in functions are read-only.
** So we must not search for built-ins when creating a new function.
*/
- if( !createFlag && (pBest==0 || (db->flags & SQLITE_PreferBuiltin)!=0) ){
+ if( !createFlag && (pBest==0 || (db->mDbFlags & DBFLAG_PreferBuiltin)!=0) ){
bestScore = 0;
- h = (sqlite3UpperToLower[(u8)zName[0]] + nName) % SQLITE_FUNC_HASH_SZ;
- p = functionSearch(h, zName);
+ h = SQLITE_FUNC_HASH(tdsqlite3UpperToLower[(u8)zName[0]], nName);
+ p = tdsqlite3FunctionSearch(h, zName);
while( p ){
int score = matchQuality(p, nArg, enc);
if( score>bestScore ){
@@ -106214,16 +118210,18 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction(
** new entry to the hash table and return it.
*/
if( createFlag && bestScore<FUNC_PERFECT_MATCH &&
- (pBest = sqlite3DbMallocZero(db, sizeof(*pBest)+nName+1))!=0 ){
+ (pBest = tdsqlite3DbMallocZero(db, sizeof(*pBest)+nName+1))!=0 ){
FuncDef *pOther;
+ u8 *z;
pBest->zName = (const char*)&pBest[1];
pBest->nArg = (u16)nArg;
pBest->funcFlags = enc;
memcpy((char*)&pBest[1], zName, nName+1);
- pOther = (FuncDef*)sqlite3HashInsert(&db->aFunc, pBest->zName, pBest);
+ for(z=(u8*)pBest->zName; *z; z++) *z = tdsqlite3UpperToLower[*z];
+ pOther = (FuncDef*)tdsqlite3HashInsert(&db->aFunc, pBest->zName, pBest);
if( pOther==pBest ){
- sqlite3DbFree(db, pBest);
- sqlite3OomFault(db);
+ tdsqlite3DbFree(db, pBest);
+ tdsqlite3OomFault(db);
return 0;
}else{
pBest->pNext = pOther;
@@ -106238,13 +118236,13 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction(
/*
** Free all resources held by the schema structure. The void* argument points
-** at a Schema struct. This function does not call sqlite3DbFree(db, ) on the
+** at a Schema struct. This function does not call tdsqlite3DbFree(db, ) on the
** pointer itself, it just cleans up subsidiary resources (i.e. the contents
** of the schema hash tables).
**
** The Schema.cache_size variable is not cleared.
*/
-SQLITE_PRIVATE void sqlite3SchemaClear(void *p){
+SQLITE_PRIVATE void tdsqlite3SchemaClear(void *p){
Hash temp1;
Hash temp2;
HashElem *pElem;
@@ -106252,44 +118250,44 @@ SQLITE_PRIVATE void sqlite3SchemaClear(void *p){
temp1 = pSchema->tblHash;
temp2 = pSchema->trigHash;
- sqlite3HashInit(&pSchema->trigHash);
- sqlite3HashClear(&pSchema->idxHash);
+ tdsqlite3HashInit(&pSchema->trigHash);
+ tdsqlite3HashClear(&pSchema->idxHash);
for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
- sqlite3DeleteTrigger(0, (Trigger*)sqliteHashData(pElem));
+ tdsqlite3DeleteTrigger(0, (Trigger*)sqliteHashData(pElem));
}
- sqlite3HashClear(&temp2);
- sqlite3HashInit(&pSchema->tblHash);
+ tdsqlite3HashClear(&temp2);
+ tdsqlite3HashInit(&pSchema->tblHash);
for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
Table *pTab = sqliteHashData(pElem);
- sqlite3DeleteTable(0, pTab);
+ tdsqlite3DeleteTable(0, pTab);
}
- sqlite3HashClear(&temp1);
- sqlite3HashClear(&pSchema->fkeyHash);
+ tdsqlite3HashClear(&temp1);
+ tdsqlite3HashClear(&pSchema->fkeyHash);
pSchema->pSeqTab = 0;
if( pSchema->schemaFlags & DB_SchemaLoaded ){
pSchema->iGeneration++;
- pSchema->schemaFlags &= ~DB_SchemaLoaded;
}
+ pSchema->schemaFlags &= ~(DB_SchemaLoaded|DB_ResetWanted);
}
/*
** Find and return the schema associated with a BTree. Create
** a new one if necessary.
*/
-SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){
+SQLITE_PRIVATE Schema *tdsqlite3SchemaGet(tdsqlite3 *db, Btree *pBt){
Schema * p;
if( pBt ){
- p = (Schema *)sqlite3BtreeSchema(pBt, sizeof(Schema), sqlite3SchemaClear);
+ p = (Schema *)tdsqlite3BtreeSchema(pBt, sizeof(Schema), tdsqlite3SchemaClear);
}else{
- p = (Schema *)sqlite3DbMallocZero(0, sizeof(Schema));
+ p = (Schema *)tdsqlite3DbMallocZero(0, sizeof(Schema));
}
if( !p ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
}else if ( 0==p->file_format ){
- sqlite3HashInit(&p->tblHash);
- sqlite3HashInit(&p->idxHash);
- sqlite3HashInit(&p->trigHash);
- sqlite3HashInit(&p->fkeyHash);
+ tdsqlite3HashInit(&p->tblHash);
+ tdsqlite3HashInit(&p->idxHash);
+ tdsqlite3HashInit(&p->trigHash);
+ tdsqlite3HashInit(&p->fkeyHash);
p->enc = SQLITE_UTF8;
}
return p;
@@ -106327,51 +118325,64 @@ SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){
** pSrc->a[0].pIndex Pointer to the INDEXED BY index, if there is one
**
*/
-SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
+SQLITE_PRIVATE Table *tdsqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
struct SrcList_item *pItem = pSrc->a;
Table *pTab;
assert( pItem && pSrc->nSrc==1 );
- pTab = sqlite3LocateTableItem(pParse, 0, pItem);
- sqlite3DeleteTable(pParse->db, pItem->pTab);
+ pTab = tdsqlite3LocateTableItem(pParse, 0, pItem);
+ tdsqlite3DeleteTable(pParse->db, pItem->pTab);
pItem->pTab = pTab;
if( pTab ){
- pTab->nRef++;
+ pTab->nTabRef++;
}
- if( sqlite3IndexedByLookup(pParse, pItem) ){
+ if( tdsqlite3IndexedByLookup(pParse, pItem) ){
pTab = 0;
}
return pTab;
}
+/* Return true if table pTab is read-only.
+**
+** A table is read-only if any of the following are true:
+**
+** 1) It is a virtual table and no implementation of the xUpdate method
+** has been provided
+**
+** 2) It is a system table (i.e. sqlite_master), this call is not
+** part of a nested parse and writable_schema pragma has not
+** been specified
+**
+** 3) The table is a shadow table, the database connection is in
+** defensive mode, and the current tdsqlite3_prepare()
+** is for a top-level SQL statement.
+*/
+static int tabIsReadOnly(Parse *pParse, Table *pTab){
+ tdsqlite3 *db;
+ if( IsVirtual(pTab) ){
+ return tdsqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0;
+ }
+ if( (pTab->tabFlags & (TF_Readonly|TF_Shadow))==0 ) return 0;
+ db = pParse->db;
+ if( (pTab->tabFlags & TF_Readonly)!=0 ){
+ return tdsqlite3WritableSchema(db)==0 && pParse->nested==0;
+ }
+ assert( pTab->tabFlags & TF_Shadow );
+ return tdsqlite3ReadOnlyShadowTables(db);
+}
+
/*
** Check to make sure the given table is writable. If it is not
** writable, generate an error message and return 1. If it is
** writable return 0;
*/
-SQLITE_PRIVATE int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){
- /* A table is not writable under the following circumstances:
- **
- ** 1) It is a virtual table and no implementation of the xUpdate method
- ** has been provided, or
- ** 2) It is a system table (i.e. sqlite_master), this call is not
- ** part of a nested parse and writable_schema pragma has not
- ** been specified.
- **
- ** In either case leave an error message in pParse and return non-zero.
- */
- if( ( IsVirtual(pTab)
- && sqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0 )
- || ( (pTab->tabFlags & TF_Readonly)!=0
- && (pParse->db->flags & SQLITE_WriteSchema)==0
- && pParse->nested==0 )
- ){
- sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName);
+SQLITE_PRIVATE int tdsqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){
+ if( tabIsReadOnly(pParse, pTab) ){
+ tdsqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName);
return 1;
}
-
#ifndef SQLITE_OMIT_VIEW
if( !viewOk && pTab->pSelect ){
- sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName);
+ tdsqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName);
return 1;
}
#endif
@@ -106385,31 +118396,33 @@ SQLITE_PRIVATE int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){
** pWhere argument is an optional WHERE clause that restricts the
** set of rows in the view that are to be added to the ephemeral table.
*/
-SQLITE_PRIVATE void sqlite3MaterializeView(
+SQLITE_PRIVATE void tdsqlite3MaterializeView(
Parse *pParse, /* Parsing context */
Table *pView, /* View definition */
Expr *pWhere, /* Optional WHERE clause to be added */
+ ExprList *pOrderBy, /* Optional ORDER BY clause */
+ Expr *pLimit, /* Optional LIMIT clause */
int iCur /* Cursor number for ephemeral table */
){
SelectDest dest;
Select *pSel;
SrcList *pFrom;
- sqlite3 *db = pParse->db;
- int iDb = sqlite3SchemaToIndex(db, pView->pSchema);
- pWhere = sqlite3ExprDup(db, pWhere, 0);
- pFrom = sqlite3SrcListAppend(db, 0, 0, 0);
+ tdsqlite3 *db = pParse->db;
+ int iDb = tdsqlite3SchemaToIndex(db, pView->pSchema);
+ pWhere = tdsqlite3ExprDup(db, pWhere, 0);
+ pFrom = tdsqlite3SrcListAppend(pParse, 0, 0, 0);
if( pFrom ){
assert( pFrom->nSrc==1 );
- pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName);
- pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName);
+ pFrom->a[0].zName = tdsqlite3DbStrDup(db, pView->zName);
+ pFrom->a[0].zDatabase = tdsqlite3DbStrDup(db, db->aDb[iDb].zDbSName);
assert( pFrom->a[0].pOn==0 );
assert( pFrom->a[0].pUsing==0 );
}
- pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, 0,
- SF_IncludeHidden, 0, 0);
- sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
- sqlite3Select(pParse, pSel, &dest);
- sqlite3SelectDelete(db, pSel);
+ pSel = tdsqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy,
+ SF_IncludeHidden, pLimit);
+ tdsqlite3SelectDestInit(&dest, SRT_EphemTab, iCur);
+ tdsqlite3Select(pParse, pSel, &dest);
+ tdsqlite3SelectDelete(db, pSel);
}
#endif /* !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) */
@@ -106422,35 +118435,35 @@ SQLITE_PRIVATE void sqlite3MaterializeView(
** \__________________________/
** pLimitWhere (pInClause)
*/
-SQLITE_PRIVATE Expr *sqlite3LimitWhere(
+SQLITE_PRIVATE Expr *tdsqlite3LimitWhere(
Parse *pParse, /* The parser context */
SrcList *pSrc, /* the FROM clause -- which tables to scan */
Expr *pWhere, /* The WHERE clause. May be null */
ExprList *pOrderBy, /* The ORDER BY clause. May be null */
Expr *pLimit, /* The LIMIT clause. May be null */
- Expr *pOffset, /* The OFFSET clause. May be null */
char *zStmtType /* Either DELETE or UPDATE. For err msgs. */
){
- Expr *pWhereRowid = NULL; /* WHERE rowid .. */
+ tdsqlite3 *db = pParse->db;
+ Expr *pLhs = NULL; /* LHS of IN(SELECT...) operator */
Expr *pInClause = NULL; /* WHERE rowid IN ( select ) */
- Expr *pSelectRowid = NULL; /* SELECT rowid ... */
ExprList *pEList = NULL; /* Expression list contaning only pSelectRowid */
SrcList *pSelectSrc = NULL; /* SELECT rowid FROM x ... (dup of pSrc) */
Select *pSelect = NULL; /* Complete SELECT tree */
+ Table *pTab;
/* Check that there isn't an ORDER BY without a LIMIT clause.
*/
- if( pOrderBy && (pLimit == 0) ) {
- sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType);
- goto limit_where_cleanup;
+ if( pOrderBy && pLimit==0 ) {
+ tdsqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType);
+ tdsqlite3ExprDelete(pParse->db, pWhere);
+ tdsqlite3ExprListDelete(pParse->db, pOrderBy);
+ return 0;
}
/* We only need to generate a select expression if there
** is a limit/offset term to enforce.
*/
if( pLimit == 0 ) {
- /* if pLimit is null, pOffset will always be null as well. */
- assert( pOffset == 0 );
return pWhere;
}
@@ -106463,36 +118476,47 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere(
** );
*/
- pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0, 0);
- if( pSelectRowid == 0 ) goto limit_where_cleanup;
- pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid);
- if( pEList == 0 ) goto limit_where_cleanup;
+ pTab = pSrc->a[0].pTab;
+ if( HasRowid(pTab) ){
+ pLhs = tdsqlite3PExpr(pParse, TK_ROW, 0, 0);
+ pEList = tdsqlite3ExprListAppend(
+ pParse, 0, tdsqlite3PExpr(pParse, TK_ROW, 0, 0)
+ );
+ }else{
+ Index *pPk = tdsqlite3PrimaryKeyIndex(pTab);
+ if( pPk->nKeyCol==1 ){
+ const char *zName = pTab->aCol[pPk->aiColumn[0]].zName;
+ pLhs = tdsqlite3Expr(db, TK_ID, zName);
+ pEList = tdsqlite3ExprListAppend(pParse, 0, tdsqlite3Expr(db, TK_ID, zName));
+ }else{
+ int i;
+ for(i=0; i<pPk->nKeyCol; i++){
+ Expr *p = tdsqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zName);
+ pEList = tdsqlite3ExprListAppend(pParse, pEList, p);
+ }
+ pLhs = tdsqlite3PExpr(pParse, TK_VECTOR, 0, 0);
+ if( pLhs ){
+ pLhs->x.pList = tdsqlite3ExprListDup(db, pEList, 0);
+ }
+ }
+ }
/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
** and the SELECT subtree. */
- pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0);
- if( pSelectSrc == 0 ) {
- sqlite3ExprListDelete(pParse->db, pEList);
- goto limit_where_cleanup;
- }
+ pSrc->a[0].pTab = 0;
+ pSelectSrc = tdsqlite3SrcListDup(pParse->db, pSrc, 0);
+ pSrc->a[0].pTab = pTab;
+ pSrc->a[0].pIBIndex = 0;
/* generate the SELECT expression tree. */
- pSelect = sqlite3SelectNew(pParse,pEList,pSelectSrc,pWhere,0,0,
- pOrderBy,0,pLimit,pOffset);
- if( pSelect == 0 ) return 0;
+ pSelect = tdsqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0,
+ pOrderBy,0,pLimit
+ );
/* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
- pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0, 0);
- pInClause = pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0, 0) : 0;
- sqlite3PExprAddSelect(pParse, pInClause, pSelect);
+ pInClause = tdsqlite3PExpr(pParse, TK_IN, pLhs, 0);
+ tdsqlite3PExprAddSelect(pParse, pInClause, pSelect);
return pInClause;
-
-limit_where_cleanup:
- sqlite3ExprDelete(pParse->db, pWhere);
- sqlite3ExprListDelete(pParse->db, pOrderBy);
- sqlite3ExprDelete(pParse->db, pLimit);
- sqlite3ExprDelete(pParse->db, pOffset);
- return 0;
}
#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) */
/* && !defined(SQLITE_OMIT_SUBQUERY) */
@@ -106504,10 +118528,12 @@ limit_where_cleanup:
** \________/ \________________/
** pTabList pWhere
*/
-SQLITE_PRIVATE void sqlite3DeleteFrom(
+SQLITE_PRIVATE void tdsqlite3DeleteFrom(
Parse *pParse, /* The parser context */
SrcList *pTabList, /* The table from which we should delete things */
- Expr *pWhere /* The WHERE clause. May be null */
+ Expr *pWhere, /* The WHERE clause. May be null */
+ ExprList *pOrderBy, /* ORDER BY clause. May be null */
+ Expr *pLimit /* LIMIT clause. May be null */
){
Vdbe *v; /* The virtual database engine */
Table *pTab; /* The table from which records will be deleted */
@@ -106518,11 +118544,11 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
int iDataCur = 0; /* VDBE cursor for the canonical data source */
int iIdxCur = 0; /* Cursor number of the first index */
int nIdx; /* Number of indices */
- sqlite3 *db; /* Main database structure */
+ tdsqlite3 *db; /* Main database structure */
AuthContext sContext; /* Authorization context */
NameContext sNC; /* Name context to resolve expressions in */
int iDb; /* Database number */
- int memCnt = -1; /* Memory cell used for change counting */
+ int memCnt = 0; /* Memory cell used for change counting */
int rcauth; /* Value returned by authorization callback */
int eOnePass; /* ONEPASS_OFF or _SINGLE or _MULTI */
int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */
@@ -106552,42 +118578,53 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
}
assert( pTabList->nSrc==1 );
+
/* Locate the table which we want to delete. This table has to be
** put in an SrcList structure because some of the subroutines we
** will be calling are designed to work with multiple tables and expect
** an SrcList* parameter instead of just a Table* parameter.
*/
- pTab = sqlite3SrcListLookup(pParse, pTabList);
+ pTab = tdsqlite3SrcListLookup(pParse, pTabList);
if( pTab==0 ) goto delete_from_cleanup;
/* Figure out if we have any triggers and if the table being
** deleted from is a view
*/
#ifndef SQLITE_OMIT_TRIGGER
- pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
+ pTrigger = tdsqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
isView = pTab->pSelect!=0;
- bComplex = pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0);
#else
# define pTrigger 0
# define isView 0
#endif
+ bComplex = pTrigger || tdsqlite3FkRequired(pParse, pTab, 0, 0);
#ifdef SQLITE_OMIT_VIEW
# undef isView
# define isView 0
#endif
+#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
+ if( !isView ){
+ pWhere = tdsqlite3LimitWhere(
+ pParse, pTabList, pWhere, pOrderBy, pLimit, "DELETE"
+ );
+ pOrderBy = 0;
+ pLimit = 0;
+ }
+#endif
+
/* If pTab is really a view, make sure it has been initialized.
*/
- if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+ if( tdsqlite3ViewGetColumnNames(pParse, pTab) ){
goto delete_from_cleanup;
}
- if( sqlite3IsReadOnly(pParse, pTab, (pTrigger?1:0)) ){
+ if( tdsqlite3IsReadOnly(pParse, pTab, (pTrigger?1:0)) ){
goto delete_from_cleanup;
}
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
assert( iDb<db->nDb );
- rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0,
+ rcauth = tdsqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0,
db->aDb[iDb].zDbSName);
assert( rcauth==SQLITE_OK || rcauth==SQLITE_DENY || rcauth==SQLITE_IGNORE );
if( rcauth==SQLITE_DENY ){
@@ -106606,25 +118643,29 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
/* Start the view context
*/
if( isView ){
- sqlite3AuthContextPush(pParse, &sContext, pTab->zName);
+ tdsqlite3AuthContextPush(pParse, &sContext, pTab->zName);
}
/* Begin generating code.
*/
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( v==0 ){
goto delete_from_cleanup;
}
- if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
- sqlite3BeginWriteOperation(pParse, 1, iDb);
+ if( pParse->nested==0 ) tdsqlite3VdbeCountChanges(v);
+ tdsqlite3BeginWriteOperation(pParse, bComplex, iDb);
/* If we are trying to delete from a view, realize that view into
** an ephemeral table.
*/
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
if( isView ){
- sqlite3MaterializeView(pParse, pTab, pWhere, iTabCur);
+ tdsqlite3MaterializeView(pParse, pTab,
+ pWhere, pOrderBy, pLimit, iTabCur
+ );
iDataCur = iIdxCur = iTabCur;
+ pOrderBy = 0;
+ pLimit = 0;
}
#endif
@@ -106633,23 +118674,33 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pParse;
sNC.pSrcList = pTabList;
- if( sqlite3ResolveExprNames(&sNC, pWhere) ){
+ if( tdsqlite3ResolveExprNames(&sNC, pWhere) ){
goto delete_from_cleanup;
}
/* Initialize the counter of the number of rows deleted, if
** we are counting rows.
*/
- if( db->flags & SQLITE_CountRows ){
+ if( (db->flags & SQLITE_CountRows)!=0
+ && !pParse->nested
+ && !pParse->pTriggerTab
+ ){
memCnt = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
}
#ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
/* Special case: A DELETE without a WHERE clause deletes everything.
** It is easier just to erase the whole table. Prior to version 3.6.5,
** this optimization caused the row change count (the value returned by
- ** API function sqlite3_count_changes) to be set incorrectly. */
+ ** API function tdsqlite3_count_changes) to be set incorrectly.
+ **
+ ** The "rcauth==SQLITE_OK" terms is the
+ ** IMPLEMENTATION-OF: R-17228-37124 If the action code is SQLITE_DELETE and
+ ** the callback returns SQLITE_IGNORE then the DELETE operation proceeds but
+ ** the truncate optimization is disabled and all rows are deleted
+ ** individually.
+ */
if( rcauth==SQLITE_OK
&& pWhere==0
&& !bComplex
@@ -106659,14 +118710,14 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
#endif
){
assert( !isView );
- sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
+ tdsqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
if( HasRowid(pTab) ){
- sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt,
+ tdsqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt ? memCnt : -1,
pTab->zName, P4_STATIC);
}
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
assert( pIdx->pSchema==pTab->pSchema );
- sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb);
+ tdsqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb);
}
}else
#endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
@@ -106679,18 +118730,18 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
pPk = 0;
nPk = 1;
iRowSet = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet);
}else{
/* For a WITHOUT ROWID table, create an ephemeral table used to
** hold all primary keys for rows to be deleted. */
- pPk = sqlite3PrimaryKeyIndex(pTab);
+ pPk = tdsqlite3PrimaryKeyIndex(pTab);
assert( pPk!=0 );
nPk = pPk->nKeyCol;
iPk = pParse->nMem+1;
pParse->nMem += nPk;
iEphCur = pParse->nTab++;
- addrEphOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur, nPk);
- sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+ addrEphOpen = tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur, nPk);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pPk);
}
/* Construct a query to find the rowid or primary key for every row
@@ -106701,29 +118752,29 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
** ONEPASS_SINGLE: One-pass approach - at most one row deleted.
** ONEPASS_MULTI: One-pass approach - any number of rows may be deleted.
*/
- pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1);
+ pWInfo = tdsqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1);
if( pWInfo==0 ) goto delete_from_cleanup;
- eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
+ eOnePass = tdsqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI );
assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF );
+ if( eOnePass!=ONEPASS_SINGLE ) tdsqlite3MultiWrite(pParse);
/* Keep track of the number of rows to be deleted */
- if( db->flags & SQLITE_CountRows ){
- sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
+ if( memCnt ){
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1);
}
/* Extract the rowid or primary key for the current row */
if( pPk ){
for(i=0; i<nPk; i++){
assert( pPk->aiColumn[i]>=0 );
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur,
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur,
pPk->aiColumn[i], iPk+i);
}
iKey = iPk;
}else{
- iKey = pParse->nMem + 1;
- iKey = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iTabCur, iKey, 0);
- if( iKey>pParse->nMem ) pParse->nMem = iKey;
+ iKey = ++pParse->nMem;
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, -1, iKey);
}
if( eOnePass!=ONEPASS_OFF ){
@@ -106731,37 +118782,37 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
** one, so just keep it in its register(s) and fall through to the
** delete code. */
nKey = nPk; /* OP_Found will use an unpacked key */
- aToOpen = sqlite3DbMallocRawNN(db, nIdx+2);
+ aToOpen = tdsqlite3DbMallocRawNN(db, nIdx+2);
if( aToOpen==0 ){
- sqlite3WhereEnd(pWInfo);
+ tdsqlite3WhereEnd(pWInfo);
goto delete_from_cleanup;
}
memset(aToOpen, 1, nIdx+1);
aToOpen[nIdx+1] = 0;
if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iTabCur] = 0;
if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iTabCur] = 0;
- if( addrEphOpen ) sqlite3VdbeChangeToNoop(v, addrEphOpen);
+ if( addrEphOpen ) tdsqlite3VdbeChangeToNoop(v, addrEphOpen);
}else{
if( pPk ){
/* Add the PK key for this row to the temporary table */
iKey = ++pParse->nMem;
nKey = 0; /* Zero tells OP_Found to use a composite key */
- sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey,
- sqlite3IndexAffinityStr(pParse->db, pPk), nPk);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey);
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey,
+ tdsqlite3IndexAffinityStr(pParse->db, pPk), nPk);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEphCur, iKey, iPk, nPk);
}else{
/* Add the rowid of the row to be deleted to the RowSet */
- nKey = 1; /* OP_Seek always uses a single rowid */
- sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey);
+ nKey = 1; /* OP_DeferredSeek always uses a single rowid */
+ tdsqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey);
}
}
/* If this DELETE cannot use the ONEPASS strategy, this is the
** end of the WHERE loop */
if( eOnePass!=ONEPASS_OFF ){
- addrBypass = sqlite3VdbeMakeLabel(v);
+ addrBypass = tdsqlite3VdbeMakeLabel(pParse);
}else{
- sqlite3WhereEnd(pWInfo);
+ tdsqlite3WhereEnd(pWInfo);
}
/* Unless this is a view, open cursors for the table we are
@@ -106772,14 +118823,14 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
if( !isView ){
int iAddrOnce = 0;
if( eOnePass==ONEPASS_MULTI ){
- iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ iAddrOnce = tdsqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
}
testcase( IsVirtual(pTab) );
- sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, OPFLAG_FORDELETE,
+ tdsqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, OPFLAG_FORDELETE,
iTabCur, aToOpen, &iDataCur, &iIdxCur);
assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur );
assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 );
- if( eOnePass==ONEPASS_MULTI ) sqlite3VdbeJumpHere(v, iAddrOnce);
+ if( eOnePass==ONEPASS_MULTI ) tdsqlite3VdbeJumpHere(v, iAddrOnce);
}
/* Set up a loop over the rowids/primary-keys that were found in the
@@ -106789,15 +118840,19 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
assert( nKey==nPk ); /* OP_Found will use an unpacked key */
if( !IsVirtual(pTab) && aToOpen[iDataCur-iTabCur] ){
assert( pPk!=0 || pTab->pSelect!=0 );
- sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey);
+ tdsqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey);
VdbeCoverage(v);
}
}else if( pPk ){
- addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_RowKey, iEphCur, iKey);
+ addrLoop = tdsqlite3VdbeAddOp1(v, OP_Rewind, iEphCur); VdbeCoverage(v);
+ if( IsVirtual(pTab) ){
+ tdsqlite3VdbeAddOp3(v, OP_Column, iEphCur, 0, iKey);
+ }else{
+ tdsqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey);
+ }
assert( nKey==0 ); /* OP_Found will use a composite key */
}else{
- addrLoop = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey);
+ addrLoop = tdsqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey);
VdbeCoverage(v);
assert( nKey==1 );
}
@@ -106805,46 +118860,37 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
/* Delete the row */
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pTab) ){
- const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
- sqlite3VtabMakeWritable(pParse, pTab);
- sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iKey, pVTab, P4_VTAB);
- sqlite3VdbeChangeP5(v, OE_Abort);
+ const char *pVTab = (const char *)tdsqlite3GetVTable(db, pTab);
+ tdsqlite3VtabMakeWritable(pParse, pTab);
assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE );
- sqlite3MayAbort(pParse);
- if( eOnePass==ONEPASS_SINGLE && sqlite3IsToplevel(pParse) ){
- pParse->isMultiWrite = 0;
+ tdsqlite3MayAbort(pParse);
+ if( eOnePass==ONEPASS_SINGLE ){
+ tdsqlite3VdbeAddOp1(v, OP_Close, iTabCur);
+ if( tdsqlite3IsToplevel(pParse) ){
+ pParse->isMultiWrite = 0;
+ }
}
+ tdsqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iKey, pVTab, P4_VTAB);
+ tdsqlite3VdbeChangeP5(v, OE_Abort);
}else
#endif
{
int count = (pParse->nested==0); /* True to count changes */
- int iIdxNoSeek = -1;
- if( bComplex==0 && aiCurOnePass[1]!=iDataCur ){
- iIdxNoSeek = aiCurOnePass[1];
- }
- sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
- iKey, nKey, count, OE_Default, eOnePass, iIdxNoSeek);
+ tdsqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
+ iKey, nKey, count, OE_Default, eOnePass, aiCurOnePass[1]);
}
/* End of the loop over all rowids/primary-keys. */
if( eOnePass!=ONEPASS_OFF ){
- sqlite3VdbeResolveLabel(v, addrBypass);
- sqlite3WhereEnd(pWInfo);
+ tdsqlite3VdbeResolveLabel(v, addrBypass);
+ tdsqlite3WhereEnd(pWInfo);
}else if( pPk ){
- sqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop+1); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addrLoop);
+ tdsqlite3VdbeAddOp2(v, OP_Next, iEphCur, addrLoop+1); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addrLoop);
}else{
- sqlite3VdbeGoto(v, addrLoop);
- sqlite3VdbeJumpHere(v, addrLoop);
+ tdsqlite3VdbeGoto(v, addrLoop);
+ tdsqlite3VdbeJumpHere(v, addrLoop);
}
-
- /* Close the cursors open on the table and its indexes. */
- if( !isView && !IsVirtual(pTab) ){
- if( !pPk ) sqlite3VdbeAddOp1(v, OP_Close, iDataCur);
- for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
- sqlite3VdbeAddOp1(v, OP_Close, iIdxCur + i);
- }
- }
} /* End non-truncate path */
/* Update the sqlite_sequence table by storing the content of the
@@ -106852,24 +118898,28 @@ SQLITE_PRIVATE void sqlite3DeleteFrom(
** autoincrement tables.
*/
if( pParse->nested==0 && pParse->pTriggerTab==0 ){
- sqlite3AutoincrementEnd(pParse);
+ tdsqlite3AutoincrementEnd(pParse);
}
/* Return the number of rows that were deleted. If this routine is
- ** generating code because of a call to sqlite3NestedParse(), do not
+ ** generating code because of a call to tdsqlite3NestedParse(), do not
** invoke the callback function.
*/
- if( (db->flags&SQLITE_CountRows) && !pParse->nested && !pParse->pTriggerTab ){
- sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
- sqlite3VdbeSetNumCols(v, 1);
- sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC);
+ if( memCnt ){
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
+ tdsqlite3VdbeSetNumCols(v, 1);
+ tdsqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC);
}
delete_from_cleanup:
- sqlite3AuthContextPop(&sContext);
- sqlite3SrcListDelete(db, pTabList);
- sqlite3ExprDelete(db, pWhere);
- sqlite3DbFree(db, aToOpen);
+ tdsqlite3AuthContextPop(&sContext);
+ tdsqlite3SrcListDelete(db, pTabList);
+ tdsqlite3ExprDelete(db, pWhere);
+#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
+ tdsqlite3ExprListDelete(db, pOrderBy);
+ tdsqlite3ExprDelete(db, pLimit);
+#endif
+ tdsqlite3DbFree(db, aToOpen);
return;
}
/* Make sure "isView" and other macros defined above are undefined. Otherwise
@@ -106911,17 +118961,19 @@ delete_from_cleanup:
**
** If eMode is ONEPASS_MULTI, then this call is being made as part
** of a ONEPASS delete that affects multiple rows. In this case, if
-** iIdxNoSeek is a valid cursor number (>=0), then its position should
-** be preserved following the delete operation. Or, if iIdxNoSeek is not
-** a valid cursor number, the position of iDataCur should be preserved
-** instead.
+** iIdxNoSeek is a valid cursor number (>=0) and is not the same as
+** iDataCur, then its position should be preserved following the delete
+** operation. Or, if iIdxNoSeek is not a valid cursor number, the
+** position of iDataCur should be preserved instead.
**
** iIdxNoSeek:
-** If iIdxNoSeek is a valid cursor number (>=0), then it identifies an
-** index cursor (from within array of cursors starting at iIdxCur) that
-** already points to the index entry to be deleted.
+** If iIdxNoSeek is a valid cursor number (>=0) not equal to iDataCur,
+** then it identifies an index cursor (from within array of cursors
+** starting at iIdxCur) that already points to the index entry to be deleted.
+** Except, this optimization is disabled if there are BEFORE triggers since
+** the trigger body might have moved the cursor.
*/
-SQLITE_PRIVATE void sqlite3GenerateRowDelete(
+SQLITE_PRIVATE void tdsqlite3GenerateRowDelete(
Parse *pParse, /* Parsing context */
Table *pTab, /* Table containing the row to be deleted */
Trigger *pTrigger, /* List of triggers to (potentially) fire */
@@ -106947,62 +118999,68 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete(
/* Seek cursor iCur to the row to delete. If this row no longer exists
** (this can happen if a trigger program has already deleted it), do
** not attempt to delete it or fire any DELETE triggers. */
- iLabel = sqlite3VdbeMakeLabel(v);
+ iLabel = tdsqlite3VdbeMakeLabel(pParse);
opSeek = HasRowid(pTab) ? OP_NotExists : OP_NotFound;
if( eMode==ONEPASS_OFF ){
- sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk);
+ tdsqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk);
VdbeCoverageIf(v, opSeek==OP_NotExists);
VdbeCoverageIf(v, opSeek==OP_NotFound);
}
/* If there are any triggers to fire, allocate a range of registers to
** use for the old.* references in the triggers. */
- if( sqlite3FkRequired(pParse, pTab, 0, 0) || pTrigger ){
+ if( tdsqlite3FkRequired(pParse, pTab, 0, 0) || pTrigger ){
u32 mask; /* Mask of OLD.* columns in use */
int iCol; /* Iterator used while populating OLD.* */
int addrStart; /* Start of BEFORE trigger programs */
/* TODO: Could use temporary registers here. Also could attempt to
** avoid copying the contents of the rowid register. */
- mask = sqlite3TriggerColmask(
+ mask = tdsqlite3TriggerColmask(
pParse, pTrigger, 0, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onconf
);
- mask |= sqlite3FkOldmask(pParse, pTab);
+ mask |= tdsqlite3FkOldmask(pParse, pTab);
iOld = pParse->nMem+1;
pParse->nMem += (1 + pTab->nCol);
/* Populate the OLD.* pseudo-table register array. These values will be
** used by any BEFORE and AFTER triggers that exist. */
- sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld);
+ tdsqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld);
for(iCol=0; iCol<pTab->nCol; iCol++){
testcase( mask!=0xffffffff && iCol==31 );
testcase( mask!=0xffffffff && iCol==32 );
if( mask==0xffffffff || (iCol<=31 && (mask & MASKBIT32(iCol))!=0) ){
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, iCol, iOld+iCol+1);
+ int kk = tdsqlite3TableColumnToStorage(pTab, iCol);
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, iCol, iOld+kk+1);
}
}
/* Invoke BEFORE DELETE trigger programs. */
- addrStart = sqlite3VdbeCurrentAddr(v);
- sqlite3CodeRowTrigger(pParse, pTrigger,
+ addrStart = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3CodeRowTrigger(pParse, pTrigger,
TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld, onconf, iLabel
);
/* If any BEFORE triggers were coded, then seek the cursor to the
** row to be deleted again. It may be that the BEFORE triggers moved
- ** the cursor or of already deleted the row that the cursor was
+ ** the cursor or already deleted the row that the cursor was
** pointing to.
+ **
+ ** Also disable the iIdxNoSeek optimization since the BEFORE trigger
+ ** may have moved that cursor.
*/
- if( addrStart<sqlite3VdbeCurrentAddr(v) ){
- sqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk);
+ if( addrStart<tdsqlite3VdbeCurrentAddr(v) ){
+ tdsqlite3VdbeAddOp4Int(v, opSeek, iDataCur, iLabel, iPk, nPk);
VdbeCoverageIf(v, opSeek==OP_NotExists);
VdbeCoverageIf(v, opSeek==OP_NotFound);
+ testcase( iIdxNoSeek>=0 );
+ iIdxNoSeek = -1;
}
/* Do FK processing. This call checks that any FK constraints that
** refer to this table (i.e. constraints attached to other tables)
** are not violated by deleting this row. */
- sqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0);
+ tdsqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0);
}
/* Delete the index and table entries. Skip this step if pTab is really
@@ -107017,33 +119075,35 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete(
*/
if( pTab->pSelect==0 ){
u8 p5 = 0;
- sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);
- sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0));
- sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE);
+ tdsqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);
+ tdsqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0));
+ if( pParse->nested==0 || 0==tdsqlite3_stricmp(pTab->zName, "sqlite_stat1") ){
+ tdsqlite3VdbeAppendP4(v, (char*)pTab, P4_TABLE);
+ }
if( eMode!=ONEPASS_OFF ){
- sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE);
}
- if( iIdxNoSeek>=0 ){
- sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek);
+ if( iIdxNoSeek>=0 && iIdxNoSeek!=iDataCur ){
+ tdsqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek);
}
if( eMode==ONEPASS_MULTI ) p5 |= OPFLAG_SAVEPOSITION;
- sqlite3VdbeChangeP5(v, p5);
+ tdsqlite3VdbeChangeP5(v, p5);
}
/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
** handle rows (possibly in other tables) that refer via a foreign key
** to the row just deleted. */
- sqlite3FkActions(pParse, pTab, 0, iOld, 0, 0);
+ tdsqlite3FkActions(pParse, pTab, 0, iOld, 0, 0);
/* Invoke AFTER DELETE trigger programs. */
- sqlite3CodeRowTrigger(pParse, pTrigger,
+ tdsqlite3CodeRowTrigger(pParse, pTrigger,
TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, iLabel
);
/* Jump here if the row had already been deleted before any BEFORE
** trigger programs were invoked. Or if a trigger program throws a
** RAISE(IGNORE) exception. */
- sqlite3VdbeResolveLabel(v, iLabel);
+ tdsqlite3VdbeResolveLabel(v, iLabel);
VdbeModuleComment((v, "END: GenRowDel()"));
}
@@ -107065,7 +119125,7 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete(
** 3. The "iDataCur" cursor must be already be positioned on the row
** that is to be deleted.
*/
-SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(
+SQLITE_PRIVATE void tdsqlite3GenerateRowIndexDelete(
Parse *pParse, /* Parsing and code generating context */
Table *pTab, /* Table containing the row to be deleted */
int iDataCur, /* Cursor of table holding data. */
@@ -107082,18 +119142,18 @@ SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(
Index *pPk; /* PRIMARY KEY index, or NULL for rowid tables */
v = pParse->pVdbe;
- pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab);
+ pPk = HasRowid(pTab) ? 0 : tdsqlite3PrimaryKeyIndex(pTab);
for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
assert( iIdxCur+i!=iDataCur || pPk==pIdx );
if( aRegIdx!=0 && aRegIdx[i]==0 ) continue;
if( pIdx==pPk ) continue;
if( iIdxCur+i==iIdxNoSeek ) continue;
VdbeModuleComment((v, "GenRowIdxDel for %s", pIdx->zName));
- r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 1,
+ r1 = tdsqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 1,
&iPartIdxLabel, pPrior, r1);
- sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1,
+ tdsqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1,
pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn);
- sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
+ tdsqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel);
pPrior = pIdx;
}
}
@@ -107112,11 +119172,11 @@ SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(
**
** If *piPartIdxLabel is not NULL, fill it in with a label and jump
** to that label if pIdx is a partial index that should be skipped.
-** The label should be resolved using sqlite3ResolvePartIdxLabel().
+** The label should be resolved using tdsqlite3ResolvePartIdxLabel().
** A partial index should be skipped if its WHERE clause evaluates
** to false or null. If pIdx is not a partial index, *piPartIdxLabel
** will be set to zero which is an empty label that is ignored by
-** sqlite3ResolvePartIdxLabel().
+** tdsqlite3ResolvePartIdxLabel().
**
** The pPrior and regPrior parameters are used to implement a cache to
** avoid unnecessary register loads. If pPrior is not NULL, then it is
@@ -107129,7 +119189,7 @@ SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(
** on a table with multiple indices, and especially with the ROWID or
** PRIMARY KEY columns of the index.
*/
-SQLITE_PRIVATE int sqlite3GenerateIndexKey(
+SQLITE_PRIVATE int tdsqlite3GenerateIndexKey(
Parse *pParse, /* Parsing context */
Index *pIdx, /* The index for which to generate a key */
int iDataCur, /* Cursor number from which to take column data */
@@ -107146,17 +119206,19 @@ SQLITE_PRIVATE int sqlite3GenerateIndexKey(
if( piPartIdxLabel ){
if( pIdx->pPartIdxWhere ){
- *piPartIdxLabel = sqlite3VdbeMakeLabel(v);
- pParse->iSelfTab = iDataCur;
- sqlite3ExprCachePush(pParse);
- sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel,
+ *piPartIdxLabel = tdsqlite3VdbeMakeLabel(pParse);
+ pParse->iSelfTab = iDataCur + 1;
+ tdsqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel,
SQLITE_JUMPIFNULL);
+ pParse->iSelfTab = 0;
+ pPrior = 0; /* Ticket a9efb42811fa41ee 2019-11-02;
+ ** pPartIdxWhere may have corrupted regPrior registers */
}else{
*piPartIdxLabel = 0;
}
}
nCol = (prefixOnly && pIdx->uniqNotNull) ? pIdx->nKeyCol : pIdx->nColumn;
- regBase = sqlite3GetTempRange(pParse, nCol);
+ regBase = tdsqlite3GetTempRange(pParse, nCol);
if( pPrior && (regBase!=regPrior || pPrior->pPartIdxWhere) ) pPrior = 0;
for(j=0; j<nCol; j++){
if( pPrior
@@ -107166,31 +119228,34 @@ SQLITE_PRIVATE int sqlite3GenerateIndexKey(
/* This column was already computed by the previous index */
continue;
}
- sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j, regBase+j);
+ tdsqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j, regBase+j);
/* If the column affinity is REAL but the number is an integer, then it
** might be stored in the table as an integer (using a compact
** representation) then converted to REAL by an OP_RealAffinity opcode.
** But we are getting ready to store this value back into an index, where
** it should be converted by to INTEGER again. So omit the OP_RealAffinity
** opcode if it is present */
- sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
+ tdsqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity);
}
if( regOut ){
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut);
+ if( pIdx->pTable->pSelect ){
+ const char *zAff = tdsqlite3IndexAffinityStr(pParse->db, pIdx);
+ tdsqlite3VdbeChangeP4(v, -1, zAff, P4_TRANSIENT);
+ }
}
- sqlite3ReleaseTempRange(pParse, regBase, nCol);
+ tdsqlite3ReleaseTempRange(pParse, regBase, nCol);
return regBase;
}
/*
-** If a prior call to sqlite3GenerateIndexKey() generated a jump-over label
+** If a prior call to tdsqlite3GenerateIndexKey() generated a jump-over label
** because it was a partial index, then this routine should be called to
** resolve that label.
*/
-SQLITE_PRIVATE void sqlite3ResolvePartIdxLabel(Parse *pParse, int iLabel){
+SQLITE_PRIVATE void tdsqlite3ResolvePartIdxLabel(Parse *pParse, int iLabel){
if( iLabel ){
- sqlite3VdbeResolveLabel(pParse->pVdbe, iLabel);
- sqlite3ExprCachePop(pParse);
+ tdsqlite3VdbeResolveLabel(pParse->pVdbe, iLabel);
}
}
@@ -107214,12 +119279,15 @@ SQLITE_PRIVATE void sqlite3ResolvePartIdxLabel(Parse *pParse, int iLabel){
/* #include "sqliteInt.h" */
/* #include <stdlib.h> */
/* #include <assert.h> */
+#ifndef SQLITE_OMIT_FLOATING_POINT
+/* #include <math.h> */
+#endif
/* #include "vdbeInt.h" */
/*
** Return the collating function associated with a function.
*/
-static CollSeq *sqlite3GetFuncCollSeq(sqlite3_context *context){
+static CollSeq *tdsqlite3GetFuncCollSeq(tdsqlite3_context *context){
VdbeOp *pOp;
assert( context->pVdbe!=0 );
pOp = &context->pVdbe->aOp[context->iOp-1];
@@ -107232,7 +119300,9 @@ static CollSeq *sqlite3GetFuncCollSeq(sqlite3_context *context){
** Indicate that the accumulator load should be skipped on this
** iteration of the aggregate loop.
*/
-static void sqlite3SkipAccumulatorLoad(sqlite3_context *context){
+static void tdsqlite3SkipAccumulatorLoad(tdsqlite3_context *context){
+ assert( context->isError<=0 );
+ context->isError = -1;
context->skipFlag = 1;
}
@@ -107240,9 +119310,9 @@ static void sqlite3SkipAccumulatorLoad(sqlite3_context *context){
** Implementation of the non-aggregate min() and max() functions
*/
static void minmaxFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
int i;
int mask; /* 0 for min() or 0xffffffff for max() */
@@ -107250,40 +119320,44 @@ static void minmaxFunc(
CollSeq *pColl;
assert( argc>1 );
- mask = sqlite3_user_data(context)==0 ? 0 : -1;
- pColl = sqlite3GetFuncCollSeq(context);
+ mask = tdsqlite3_user_data(context)==0 ? 0 : -1;
+ pColl = tdsqlite3GetFuncCollSeq(context);
assert( pColl );
assert( mask==-1 || mask==0 );
iBest = 0;
- if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+ if( tdsqlite3_value_type(argv[0])==SQLITE_NULL ) return;
for(i=1; i<argc; i++){
- if( sqlite3_value_type(argv[i])==SQLITE_NULL ) return;
- if( (sqlite3MemCompare(argv[iBest], argv[i], pColl)^mask)>=0 ){
+ if( tdsqlite3_value_type(argv[i])==SQLITE_NULL ) return;
+ if( (tdsqlite3MemCompare(argv[iBest], argv[i], pColl)^mask)>=0 ){
testcase( mask==0 );
iBest = i;
}
}
- sqlite3_result_value(context, argv[iBest]);
+ tdsqlite3_result_value(context, argv[iBest]);
}
/*
** Return the type of the argument.
*/
static void typeofFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- const char *z = 0;
+ static const char *azType[] = { "integer", "real", "text", "blob", "null" };
+ int i = tdsqlite3_value_type(argv[0]) - 1;
UNUSED_PARAMETER(NotUsed);
- switch( sqlite3_value_type(argv[0]) ){
- case SQLITE_INTEGER: z = "integer"; break;
- case SQLITE_TEXT: z = "text"; break;
- case SQLITE_FLOAT: z = "real"; break;
- case SQLITE_BLOB: z = "blob"; break;
- default: z = "null"; break;
- }
- sqlite3_result_text(context, z, -1, SQLITE_STATIC);
+ assert( i>=0 && i<ArraySize(azType) );
+ assert( SQLITE_INTEGER==1 );
+ assert( SQLITE_FLOAT==2 );
+ assert( SQLITE_TEXT==3 );
+ assert( SQLITE_BLOB==4 );
+ assert( SQLITE_NULL==5 );
+ /* EVIDENCE-OF: R-01470-60482 The tdsqlite3_value_type(V) interface returns
+ ** the datatype code for the initial datatype of the tdsqlite3_value object
+ ** V. The returned value is one of SQLITE_INTEGER, SQLITE_FLOAT,
+ ** SQLITE_TEXT, SQLITE_BLOB, or SQLITE_NULL. */
+ tdsqlite3_result_text(context, azType[i], -1, SQLITE_STATIC);
}
@@ -107291,34 +119365,36 @@ static void typeofFunc(
** Implementation of the length() function
*/
static void lengthFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- int len;
-
assert( argc==1 );
UNUSED_PARAMETER(argc);
- switch( sqlite3_value_type(argv[0]) ){
+ switch( tdsqlite3_value_type(argv[0]) ){
case SQLITE_BLOB:
case SQLITE_INTEGER:
case SQLITE_FLOAT: {
- sqlite3_result_int(context, sqlite3_value_bytes(argv[0]));
+ tdsqlite3_result_int(context, tdsqlite3_value_bytes(argv[0]));
break;
}
case SQLITE_TEXT: {
- const unsigned char *z = sqlite3_value_text(argv[0]);
+ const unsigned char *z = tdsqlite3_value_text(argv[0]);
+ const unsigned char *z0;
+ unsigned char c;
if( z==0 ) return;
- len = 0;
- while( *z ){
- len++;
- SQLITE_SKIP_UTF8(z);
+ z0 = z;
+ while( (c = *z)!=0 ){
+ z++;
+ if( c>=0xc0 ){
+ while( (*z & 0xc0)==0x80 ){ z++; z0++; }
+ }
}
- sqlite3_result_int(context, len);
+ tdsqlite3_result_int(context, (int)(z-z0));
break;
}
default: {
- sqlite3_result_null(context);
+ tdsqlite3_result_null(context);
break;
}
}
@@ -107330,39 +119406,39 @@ static void lengthFunc(
** IMP: R-23979-26855 The abs(X) function returns the absolute value of
** the numeric argument X.
*/
-static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+static void absFunc(tdsqlite3_context *context, int argc, tdsqlite3_value **argv){
assert( argc==1 );
UNUSED_PARAMETER(argc);
- switch( sqlite3_value_type(argv[0]) ){
+ switch( tdsqlite3_value_type(argv[0]) ){
case SQLITE_INTEGER: {
- i64 iVal = sqlite3_value_int64(argv[0]);
+ i64 iVal = tdsqlite3_value_int64(argv[0]);
if( iVal<0 ){
if( iVal==SMALLEST_INT64 ){
/* IMP: R-31676-45509 If X is the integer -9223372036854775808
** then abs(X) throws an integer overflow error since there is no
** equivalent positive 64-bit two complement value. */
- sqlite3_result_error(context, "integer overflow", -1);
+ tdsqlite3_result_error(context, "integer overflow", -1);
return;
}
iVal = -iVal;
}
- sqlite3_result_int64(context, iVal);
+ tdsqlite3_result_int64(context, iVal);
break;
}
case SQLITE_NULL: {
/* IMP: R-37434-19929 Abs(X) returns NULL if X is NULL. */
- sqlite3_result_null(context);
+ tdsqlite3_result_null(context);
break;
}
default: {
- /* Because sqlite3_value_double() returns 0.0 if the argument is not
+ /* Because tdsqlite3_value_double() returns 0.0 if the argument is not
** something that can be converted into a number, we have:
** IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob
** that cannot be converted to a numeric value.
*/
- double rVal = sqlite3_value_double(argv[0]);
+ double rVal = tdsqlite3_value_double(argv[0]);
if( rVal<0 ) rVal = -rVal;
- sqlite3_result_double(context, rVal);
+ tdsqlite3_result_double(context, rVal);
break;
}
}
@@ -107380,9 +119456,9 @@ static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
** or 0 if needle never occurs in haystack.
*/
static void instrFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
const unsigned char *zHaystack;
const unsigned char *zNeedle;
@@ -107391,58 +119467,82 @@ static void instrFunc(
int typeHaystack, typeNeedle;
int N = 1;
int isText;
+ unsigned char firstChar;
+ tdsqlite3_value *pC1 = 0;
+ tdsqlite3_value *pC2 = 0;
UNUSED_PARAMETER(argc);
- typeHaystack = sqlite3_value_type(argv[0]);
- typeNeedle = sqlite3_value_type(argv[1]);
+ typeHaystack = tdsqlite3_value_type(argv[0]);
+ typeNeedle = tdsqlite3_value_type(argv[1]);
if( typeHaystack==SQLITE_NULL || typeNeedle==SQLITE_NULL ) return;
- nHaystack = sqlite3_value_bytes(argv[0]);
- nNeedle = sqlite3_value_bytes(argv[1]);
- if( typeHaystack==SQLITE_BLOB && typeNeedle==SQLITE_BLOB ){
- zHaystack = sqlite3_value_blob(argv[0]);
- zNeedle = sqlite3_value_blob(argv[1]);
- isText = 0;
- }else{
- zHaystack = sqlite3_value_text(argv[0]);
- zNeedle = sqlite3_value_text(argv[1]);
- isText = 1;
- if( zNeedle==0 ) return;
- assert( zHaystack );
- }
- while( nNeedle<=nHaystack && memcmp(zHaystack, zNeedle, nNeedle)!=0 ){
- N++;
- do{
- nHaystack--;
- zHaystack++;
- }while( isText && (zHaystack[0]&0xc0)==0x80 );
+ nHaystack = tdsqlite3_value_bytes(argv[0]);
+ nNeedle = tdsqlite3_value_bytes(argv[1]);
+ if( nNeedle>0 ){
+ if( typeHaystack==SQLITE_BLOB && typeNeedle==SQLITE_BLOB ){
+ zHaystack = tdsqlite3_value_blob(argv[0]);
+ zNeedle = tdsqlite3_value_blob(argv[1]);
+ isText = 0;
+ }else if( typeHaystack!=SQLITE_BLOB && typeNeedle!=SQLITE_BLOB ){
+ zHaystack = tdsqlite3_value_text(argv[0]);
+ zNeedle = tdsqlite3_value_text(argv[1]);
+ isText = 1;
+ }else{
+ pC1 = tdsqlite3_value_dup(argv[0]);
+ zHaystack = tdsqlite3_value_text(pC1);
+ if( zHaystack==0 ) goto endInstrOOM;
+ nHaystack = tdsqlite3_value_bytes(pC1);
+ pC2 = tdsqlite3_value_dup(argv[1]);
+ zNeedle = tdsqlite3_value_text(pC2);
+ if( zNeedle==0 ) goto endInstrOOM;
+ nNeedle = tdsqlite3_value_bytes(pC2);
+ isText = 1;
+ }
+ if( zNeedle==0 || (nHaystack && zHaystack==0) ) goto endInstrOOM;
+ firstChar = zNeedle[0];
+ while( nNeedle<=nHaystack
+ && (zHaystack[0]!=firstChar || memcmp(zHaystack, zNeedle, nNeedle)!=0)
+ ){
+ N++;
+ do{
+ nHaystack--;
+ zHaystack++;
+ }while( isText && (zHaystack[0]&0xc0)==0x80 );
+ }
+ if( nNeedle>nHaystack ) N = 0;
}
- if( nNeedle>nHaystack ) N = 0;
- sqlite3_result_int(context, N);
+ tdsqlite3_result_int(context, N);
+endInstr:
+ tdsqlite3_value_free(pC1);
+ tdsqlite3_value_free(pC2);
+ return;
+endInstrOOM:
+ tdsqlite3_result_error_nomem(context);
+ goto endInstr;
}
/*
** Implementation of the printf() function.
*/
static void printfFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
PrintfArguments x;
StrAccum str;
const char *zFormat;
int n;
- sqlite3 *db = sqlite3_context_db_handle(context);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
- if( argc>=1 && (zFormat = (const char*)sqlite3_value_text(argv[0]))!=0 ){
+ if( argc>=1 && (zFormat = (const char*)tdsqlite3_value_text(argv[0]))!=0 ){
x.nArg = argc-1;
x.nUsed = 0;
x.apArg = argv+1;
- sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]);
+ tdsqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]);
str.printfFlags = SQLITE_PRINTF_SQLFUNC;
- sqlite3XPrintf(&str, zFormat, &x);
+ tdsqlite3_str_appendf(&str, zFormat, &x);
n = str.nChar;
- sqlite3_result_text(context, sqlite3StrAccumFinish(&str), n,
+ tdsqlite3_result_text(context, tdsqlite3StrAccumFinish(&str), n,
SQLITE_DYNAMIC);
}
}
@@ -107460,9 +119560,9 @@ static void printfFunc(
** If p2 is negative, return the p2 characters preceding p1.
*/
static void substrFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
const unsigned char *z;
const unsigned char *z2;
@@ -107472,20 +119572,20 @@ static void substrFunc(
int negP2 = 0;
assert( argc==3 || argc==2 );
- if( sqlite3_value_type(argv[1])==SQLITE_NULL
- || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL)
+ if( tdsqlite3_value_type(argv[1])==SQLITE_NULL
+ || (argc==3 && tdsqlite3_value_type(argv[2])==SQLITE_NULL)
){
return;
}
- p0type = sqlite3_value_type(argv[0]);
- p1 = sqlite3_value_int(argv[1]);
+ p0type = tdsqlite3_value_type(argv[0]);
+ p1 = tdsqlite3_value_int(argv[1]);
if( p0type==SQLITE_BLOB ){
- len = sqlite3_value_bytes(argv[0]);
- z = sqlite3_value_blob(argv[0]);
+ len = tdsqlite3_value_bytes(argv[0]);
+ z = tdsqlite3_value_blob(argv[0]);
if( z==0 ) return;
- assert( len==sqlite3_value_bytes(argv[0]) );
+ assert( len==tdsqlite3_value_bytes(argv[0]) );
}else{
- z = sqlite3_value_text(argv[0]);
+ z = tdsqlite3_value_text(argv[0]);
if( z==0 ) return;
len = 0;
if( p1<0 ){
@@ -107503,13 +119603,13 @@ static void substrFunc(
if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */
#endif
if( argc==3 ){
- p2 = sqlite3_value_int(argv[2]);
+ p2 = tdsqlite3_value_int(argv[2]);
if( p2<0 ){
p2 = -p2;
negP2 = 1;
}
}else{
- p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH];
+ p2 = tdsqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH];
}
if( p1<0 ){
p1 += len;
@@ -107539,14 +119639,14 @@ static void substrFunc(
for(z2=z; *z2 && p2; p2--){
SQLITE_SKIP_UTF8(z2);
}
- sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT,
+ tdsqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT,
SQLITE_UTF8);
}else{
if( p1+p2>len ){
p2 = len-p1;
if( p2<0 ) p2 = 0;
}
- sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT);
+ tdsqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT);
}
}
@@ -107554,60 +119654,60 @@ static void substrFunc(
** Implementation of the round() function
*/
#ifndef SQLITE_OMIT_FLOATING_POINT
-static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+static void roundFunc(tdsqlite3_context *context, int argc, tdsqlite3_value **argv){
int n = 0;
double r;
char *zBuf;
assert( argc==1 || argc==2 );
if( argc==2 ){
- if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return;
- n = sqlite3_value_int(argv[1]);
+ if( SQLITE_NULL==tdsqlite3_value_type(argv[1]) ) return;
+ n = tdsqlite3_value_int(argv[1]);
if( n>30 ) n = 30;
if( n<0 ) n = 0;
}
- if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
- r = sqlite3_value_double(argv[0]);
+ if( tdsqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+ r = tdsqlite3_value_double(argv[0]);
/* If Y==0 and X will fit in a 64-bit int,
** handle the rounding directly,
** otherwise use printf.
*/
- if( n==0 && r>=0 && r<LARGEST_INT64-1 ){
- r = (double)((sqlite_int64)(r+0.5));
- }else if( n==0 && r<0 && (-r)<LARGEST_INT64-1 ){
- r = -(double)((sqlite_int64)((-r)+0.5));
+ if( r<-4503599627370496.0 || r>+4503599627370496.0 ){
+ /* The value has no fractional part so there is nothing to round */
+ }else if( n==0 ){
+ r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5)));
}else{
- zBuf = sqlite3_mprintf("%.*f",n,r);
+ zBuf = tdsqlite3_mprintf("%.*f",n,r);
if( zBuf==0 ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
return;
}
- sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8);
- sqlite3_free(zBuf);
+ tdsqlite3AtoF(zBuf, &r, tdsqlite3Strlen30(zBuf), SQLITE_UTF8);
+ tdsqlite3_free(zBuf);
}
- sqlite3_result_double(context, r);
+ tdsqlite3_result_double(context, r);
}
#endif
/*
-** Allocate nByte bytes of space using sqlite3Malloc(). If the
-** allocation fails, call sqlite3_result_error_nomem() to notify
+** Allocate nByte bytes of space using tdsqlite3Malloc(). If the
+** allocation fails, call tdsqlite3_result_error_nomem() to notify
** the database handle that malloc() has failed and return NULL.
** If nByte is larger than the maximum string or blob length, then
** raise an SQLITE_TOOBIG exception and return NULL.
*/
-static void *contextMalloc(sqlite3_context *context, i64 nByte){
+static void *contextMalloc(tdsqlite3_context *context, i64 nByte){
char *z;
- sqlite3 *db = sqlite3_context_db_handle(context);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
assert( nByte>0 );
testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH] );
testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH]+1 );
if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){
- sqlite3_result_error_toobig(context);
+ tdsqlite3_result_error_toobig(context);
z = 0;
}else{
- z = sqlite3Malloc(nByte);
+ z = tdsqlite3Malloc(nByte);
if( !z ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
}
}
return z;
@@ -107616,41 +119716,41 @@ static void *contextMalloc(sqlite3_context *context, i64 nByte){
/*
** Implementation of the upper() and lower() SQL functions.
*/
-static void upperFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+static void upperFunc(tdsqlite3_context *context, int argc, tdsqlite3_value **argv){
char *z1;
const char *z2;
int i, n;
UNUSED_PARAMETER(argc);
- z2 = (char*)sqlite3_value_text(argv[0]);
- n = sqlite3_value_bytes(argv[0]);
+ z2 = (char*)tdsqlite3_value_text(argv[0]);
+ n = tdsqlite3_value_bytes(argv[0]);
/* Verify that the call to _bytes() does not invalidate the _text() pointer */
- assert( z2==(char*)sqlite3_value_text(argv[0]) );
+ assert( z2==(char*)tdsqlite3_value_text(argv[0]) );
if( z2 ){
z1 = contextMalloc(context, ((i64)n)+1);
if( z1 ){
for(i=0; i<n; i++){
- z1[i] = (char)sqlite3Toupper(z2[i]);
+ z1[i] = (char)tdsqlite3Toupper(z2[i]);
}
- sqlite3_result_text(context, z1, n, sqlite3_free);
+ tdsqlite3_result_text(context, z1, n, tdsqlite3_free);
}
}
}
-static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+static void lowerFunc(tdsqlite3_context *context, int argc, tdsqlite3_value **argv){
char *z1;
const char *z2;
int i, n;
UNUSED_PARAMETER(argc);
- z2 = (char*)sqlite3_value_text(argv[0]);
- n = sqlite3_value_bytes(argv[0]);
+ z2 = (char*)tdsqlite3_value_text(argv[0]);
+ n = tdsqlite3_value_bytes(argv[0]);
/* Verify that the call to _bytes() does not invalidate the _text() pointer */
- assert( z2==(char*)sqlite3_value_text(argv[0]) );
+ assert( z2==(char*)tdsqlite3_value_text(argv[0]) );
if( z2 ){
z1 = contextMalloc(context, ((i64)n)+1);
if( z1 ){
for(i=0; i<n; i++){
- z1[i] = sqlite3Tolower(z2[i]);
+ z1[i] = tdsqlite3Tolower(z2[i]);
}
- sqlite3_result_text(context, z1, n, sqlite3_free);
+ tdsqlite3_result_text(context, z1, n, tdsqlite3_free);
}
}
}
@@ -107669,13 +119769,13 @@ static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
** Implementation of random(). Return a random integer.
*/
static void randomFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **NotUsed2
+ tdsqlite3_value **NotUsed2
){
sqlite_int64 r;
UNUSED_PARAMETER2(NotUsed, NotUsed2);
- sqlite3_randomness(sizeof(r), &r);
+ tdsqlite3_randomness(sizeof(r), &r);
if( r<0 ){
/* We need to prevent a random number of 0x8000000000000000
** (or -9223372036854775808) since when you do abs() of that
@@ -107687,7 +119787,7 @@ static void randomFunc(
*/
r = -(r & LARGEST_INT64);
}
- sqlite3_result_int64(context, r);
+ tdsqlite3_result_int64(context, r);
}
/*
@@ -107695,73 +119795,73 @@ static void randomFunc(
** that is N bytes long.
*/
static void randomBlob(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- int n;
+ tdsqlite3_int64 n;
unsigned char *p;
assert( argc==1 );
UNUSED_PARAMETER(argc);
- n = sqlite3_value_int(argv[0]);
+ n = tdsqlite3_value_int64(argv[0]);
if( n<1 ){
n = 1;
}
p = contextMalloc(context, n);
if( p ){
- sqlite3_randomness(n, p);
- sqlite3_result_blob(context, (char*)p, n, sqlite3_free);
+ tdsqlite3_randomness(n, p);
+ tdsqlite3_result_blob(context, (char*)p, n, tdsqlite3_free);
}
}
/*
** Implementation of the last_insert_rowid() SQL function. The return
-** value is the same as the sqlite3_last_insert_rowid() API function.
+** value is the same as the tdsqlite3_last_insert_rowid() API function.
*/
static void last_insert_rowid(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **NotUsed2
+ tdsqlite3_value **NotUsed2
){
- sqlite3 *db = sqlite3_context_db_handle(context);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
UNUSED_PARAMETER2(NotUsed, NotUsed2);
/* IMP: R-51513-12026 The last_insert_rowid() SQL function is a
- ** wrapper around the sqlite3_last_insert_rowid() C/C++ interface
+ ** wrapper around the tdsqlite3_last_insert_rowid() C/C++ interface
** function. */
- sqlite3_result_int64(context, sqlite3_last_insert_rowid(db));
+ tdsqlite3_result_int64(context, tdsqlite3_last_insert_rowid(db));
}
/*
** Implementation of the changes() SQL function.
**
** IMP: R-62073-11209 The changes() SQL function is a wrapper
-** around the sqlite3_changes() C/C++ function and hence follows the same
+** around the tdsqlite3_changes() C/C++ function and hence follows the same
** rules for counting changes.
*/
static void changes(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **NotUsed2
+ tdsqlite3_value **NotUsed2
){
- sqlite3 *db = sqlite3_context_db_handle(context);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
UNUSED_PARAMETER2(NotUsed, NotUsed2);
- sqlite3_result_int(context, sqlite3_changes(db));
+ tdsqlite3_result_int(context, tdsqlite3_changes(db));
}
/*
** Implementation of the total_changes() SQL function. The return value is
-** the same as the sqlite3_total_changes() API function.
+** the same as the tdsqlite3_total_changes() API function.
*/
static void total_changes(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **NotUsed2
+ tdsqlite3_value **NotUsed2
){
- sqlite3 *db = sqlite3_context_db_handle(context);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
UNUSED_PARAMETER2(NotUsed, NotUsed2);
/* IMP: R-52756-41993 This function is a wrapper around the
- ** sqlite3_total_changes() C/C++ interface. */
- sqlite3_result_int(context, sqlite3_total_changes(db));
+ ** tdsqlite3_total_changes() C/C++ interface. */
+ tdsqlite3_result_int(context, tdsqlite3_total_changes(db));
}
/*
@@ -107781,10 +119881,10 @@ struct compareInfo {
** the next character is ASCII.
*/
#if defined(SQLITE_EBCDIC)
-# define sqlite3Utf8Read(A) (*((*A)++))
+# define tdsqlite3Utf8Read(A) (*((*A)++))
# define Utf8Read(A) (*(A++))
#else
-# define Utf8Read(A) (A[0]<0x80?*(A++):sqlite3Utf8Read(&A))
+# define Utf8Read(A) (A[0]<0x80?*(A++):tdsqlite3Utf8Read(&A))
#endif
static const struct compareInfo globInfo = { '*', '?', '[', 0 };
@@ -107796,9 +119896,19 @@ static const struct compareInfo likeInfoNorm = { '%', '_', 0, 1 };
static const struct compareInfo likeInfoAlt = { '%', '_', 0, 0 };
/*
-** Compare two UTF-8 strings for equality where the first string can
-** potentially be a "glob" or "like" expression. Return true (1) if they
-** are the same and false (0) if they are different.
+** Possible error returns from patternMatch()
+*/
+#define SQLITE_MATCH 0
+#define SQLITE_NOMATCH 1
+#define SQLITE_NOWILDCARDMATCH 2
+
+/*
+** Compare two UTF-8 strings for equality where the first string is
+** a GLOB or LIKE expression. Return values:
+**
+** SQLITE_MATCH: Match
+** SQLITE_NOMATCH: No match
+** SQLITE_NOWILDCARDMATCH: No match in spite of having * or % wildcards.
**
** Globbing rules:
**
@@ -107848,31 +119958,32 @@ static int patternCompare(
** are also "?" characters, skip those as well, but consume a
** single character of the input string for each "?" skipped */
while( (c=Utf8Read(zPattern)) == matchAll || c == matchOne ){
- if( c==matchOne && sqlite3Utf8Read(&zString)==0 ){
- return 0;
+ if( c==matchOne && tdsqlite3Utf8Read(&zString)==0 ){
+ return SQLITE_NOWILDCARDMATCH;
}
}
if( c==0 ){
- return 1; /* "*" at the end of the pattern matches */
+ return SQLITE_MATCH; /* "*" at the end of the pattern matches */
}else if( c==matchOther ){
if( pInfo->matchSet==0 ){
- c = sqlite3Utf8Read(&zPattern);
- if( c==0 ) return 0;
+ c = tdsqlite3Utf8Read(&zPattern);
+ if( c==0 ) return SQLITE_NOWILDCARDMATCH;
}else{
/* "[...]" immediately follows the "*". We have to do a slow
** recursive search in this case, but it is an unusual case. */
assert( matchOther<0x80 ); /* '[' is a single-byte character */
- while( *zString
- && patternCompare(&zPattern[-1],zString,pInfo,matchOther)==0 ){
+ while( *zString ){
+ int bMatch = patternCompare(&zPattern[-1],zString,pInfo,matchOther);
+ if( bMatch!=SQLITE_NOMATCH ) return bMatch;
SQLITE_SKIP_UTF8(zString);
}
- return *zString!=0;
+ return SQLITE_NOWILDCARDMATCH;
}
}
/* At this point variable c contains the first character of the
** pattern string past the "*". Search in the input string for the
- ** first matching character and recursively contine the match from
+ ** first matching character and recursively continue the match from
** that point.
**
** For a case-insensitive search, set variable cx to be the same as
@@ -107880,48 +119991,56 @@ static int patternCompare(
** c or cx.
*/
if( c<=0x80 ){
- u32 cx;
+ char zStop[3];
+ int bMatch;
if( noCase ){
- cx = sqlite3Toupper(c);
- c = sqlite3Tolower(c);
+ zStop[0] = tdsqlite3Toupper(c);
+ zStop[1] = tdsqlite3Tolower(c);
+ zStop[2] = 0;
}else{
- cx = c;
+ zStop[0] = c;
+ zStop[1] = 0;
}
- while( (c2 = *(zString++))!=0 ){
- if( c2!=c && c2!=cx ) continue;
- if( patternCompare(zPattern,zString,pInfo,matchOther) ) return 1;
+ while(1){
+ zString += strcspn((const char*)zString, zStop);
+ if( zString[0]==0 ) break;
+ zString++;
+ bMatch = patternCompare(zPattern,zString,pInfo,matchOther);
+ if( bMatch!=SQLITE_NOMATCH ) return bMatch;
}
}else{
+ int bMatch;
while( (c2 = Utf8Read(zString))!=0 ){
if( c2!=c ) continue;
- if( patternCompare(zPattern,zString,pInfo,matchOther) ) return 1;
+ bMatch = patternCompare(zPattern,zString,pInfo,matchOther);
+ if( bMatch!=SQLITE_NOMATCH ) return bMatch;
}
}
- return 0;
+ return SQLITE_NOWILDCARDMATCH;
}
if( c==matchOther ){
if( pInfo->matchSet==0 ){
- c = sqlite3Utf8Read(&zPattern);
- if( c==0 ) return 0;
+ c = tdsqlite3Utf8Read(&zPattern);
+ if( c==0 ) return SQLITE_NOMATCH;
zEscaped = zPattern;
}else{
u32 prior_c = 0;
int seen = 0;
int invert = 0;
- c = sqlite3Utf8Read(&zString);
- if( c==0 ) return 0;
- c2 = sqlite3Utf8Read(&zPattern);
+ c = tdsqlite3Utf8Read(&zString);
+ if( c==0 ) return SQLITE_NOMATCH;
+ c2 = tdsqlite3Utf8Read(&zPattern);
if( c2=='^' ){
invert = 1;
- c2 = sqlite3Utf8Read(&zPattern);
+ c2 = tdsqlite3Utf8Read(&zPattern);
}
if( c2==']' ){
if( c==']' ) seen = 1;
- c2 = sqlite3Utf8Read(&zPattern);
+ c2 = tdsqlite3Utf8Read(&zPattern);
}
while( c2 && c2!=']' ){
if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){
- c2 = sqlite3Utf8Read(&zPattern);
+ c2 = tdsqlite3Utf8Read(&zPattern);
if( c>=prior_c && c<=c2 ) seen = 1;
prior_c = 0;
}else{
@@ -107930,37 +120049,39 @@ static int patternCompare(
}
prior_c = c2;
}
- c2 = sqlite3Utf8Read(&zPattern);
+ c2 = tdsqlite3Utf8Read(&zPattern);
}
if( c2==0 || (seen ^ invert)==0 ){
- return 0;
+ return SQLITE_NOMATCH;
}
continue;
}
}
c2 = Utf8Read(zString);
if( c==c2 ) continue;
- if( noCase && sqlite3Tolower(c)==sqlite3Tolower(c2) && c<0x80 && c2<0x80 ){
+ if( noCase && tdsqlite3Tolower(c)==tdsqlite3Tolower(c2) && c<0x80 && c2<0x80 ){
continue;
}
if( c==matchOne && zPattern!=zEscaped && c2!=0 ) continue;
- return 0;
+ return SQLITE_NOMATCH;
}
- return *zString==0;
+ return *zString==0 ? SQLITE_MATCH : SQLITE_NOMATCH;
}
/*
-** The sqlite3_strglob() interface.
+** The tdsqlite3_strglob() interface. Return 0 on a match (like strcmp()) and
+** non-zero if there is no match.
*/
-SQLITE_API int sqlite3_strglob(const char *zGlobPattern, const char *zString){
- return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '[')==0;
+SQLITE_API int tdsqlite3_strglob(const char *zGlobPattern, const char *zString){
+ return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '[');
}
/*
-** The sqlite3_strlike() interface.
+** The tdsqlite3_strlike() interface. Return 0 on a match and non-zero for
+** a miss - like strcmp().
*/
-SQLITE_API int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){
- return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc)==0;
+SQLITE_API int tdsqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){
+ return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc);
}
/*
@@ -107969,7 +120090,7 @@ SQLITE_API int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned
** only.
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_like_count = 0;
+SQLITE_API int tdsqlite3_like_count = 0;
#endif
@@ -107986,62 +120107,61 @@ SQLITE_API int sqlite3_like_count = 0;
** the GLOB operator.
*/
static void likeFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
const unsigned char *zA, *zB;
u32 escape;
int nPat;
- sqlite3 *db = sqlite3_context_db_handle(context);
- struct compareInfo *pInfo = sqlite3_user_data(context);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
+ struct compareInfo *pInfo = tdsqlite3_user_data(context);
#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS
- if( sqlite3_value_type(argv[0])==SQLITE_BLOB
- || sqlite3_value_type(argv[1])==SQLITE_BLOB
+ if( tdsqlite3_value_type(argv[0])==SQLITE_BLOB
+ || tdsqlite3_value_type(argv[1])==SQLITE_BLOB
){
#ifdef SQLITE_TEST
- sqlite3_like_count++;
+ tdsqlite3_like_count++;
#endif
- sqlite3_result_int(context, 0);
+ tdsqlite3_result_int(context, 0);
return;
}
#endif
- zB = sqlite3_value_text(argv[0]);
- zA = sqlite3_value_text(argv[1]);
/* Limit the length of the LIKE or GLOB pattern to avoid problems
** of deep recursion and N*N behavior in patternCompare().
*/
- nPat = sqlite3_value_bytes(argv[0]);
+ nPat = tdsqlite3_value_bytes(argv[0]);
testcase( nPat==db->aLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH] );
testcase( nPat==db->aLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH]+1 );
if( nPat > db->aLimit[SQLITE_LIMIT_LIKE_PATTERN_LENGTH] ){
- sqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1);
+ tdsqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1);
return;
}
- assert( zB==sqlite3_value_text(argv[0]) ); /* Encoding did not change */
-
if( argc==3 ){
/* The escape character string must consist of a single UTF-8 character.
** Otherwise, return an error.
*/
- const unsigned char *zEsc = sqlite3_value_text(argv[2]);
+ const unsigned char *zEsc = tdsqlite3_value_text(argv[2]);
if( zEsc==0 ) return;
- if( sqlite3Utf8CharLen((char*)zEsc, -1)!=1 ){
- sqlite3_result_error(context,
+ if( tdsqlite3Utf8CharLen((char*)zEsc, -1)!=1 ){
+ tdsqlite3_result_error(context,
"ESCAPE expression must be a single character", -1);
return;
}
- escape = sqlite3Utf8Read(&zEsc);
+ escape = tdsqlite3Utf8Read(&zEsc);
}else{
escape = pInfo->matchSet;
}
+ zB = tdsqlite3_value_text(argv[0]);
+ zA = tdsqlite3_value_text(argv[1]);
if( zA && zB ){
#ifdef SQLITE_TEST
- sqlite3_like_count++;
+ tdsqlite3_like_count++;
#endif
- sqlite3_result_int(context, patternCompare(zB, zA, pInfo, escape));
+ tdsqlite3_result_int(context,
+ patternCompare(zB, zA, pInfo, escape)==SQLITE_MATCH);
}
}
@@ -108051,14 +120171,14 @@ static void likeFunc(
** arguments are equal to each other.
*/
static void nullifFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- CollSeq *pColl = sqlite3GetFuncCollSeq(context);
+ CollSeq *pColl = tdsqlite3GetFuncCollSeq(context);
UNUSED_PARAMETER(NotUsed);
- if( sqlite3MemCompare(argv[0], argv[1], pColl)!=0 ){
- sqlite3_result_value(context, argv[0]);
+ if( tdsqlite3MemCompare(argv[0], argv[1], pColl)!=0 ){
+ tdsqlite3_result_value(context, argv[0]);
}
}
@@ -108067,14 +120187,14 @@ static void nullifFunc(
** of the SQLite library that is running.
*/
static void versionFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **NotUsed2
+ tdsqlite3_value **NotUsed2
){
UNUSED_PARAMETER2(NotUsed, NotUsed2);
/* IMP: R-48699-48617 This function is an SQL wrapper around the
- ** sqlite3_libversion() C-interface. */
- sqlite3_result_text(context, sqlite3_libversion(), -1, SQLITE_STATIC);
+ ** tdsqlite3_libversion() C-interface. */
+ tdsqlite3_result_text(context, tdsqlite3_libversion(), -1, SQLITE_STATIC);
}
/*
@@ -108083,29 +120203,29 @@ static void versionFunc(
** SQLite.
*/
static void sourceidFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **NotUsed2
+ tdsqlite3_value **NotUsed2
){
UNUSED_PARAMETER2(NotUsed, NotUsed2);
/* IMP: R-24470-31136 This function is an SQL wrapper around the
- ** sqlite3_sourceid() C interface. */
- sqlite3_result_text(context, sqlite3_sourceid(), -1, SQLITE_STATIC);
+ ** tdsqlite3_sourceid() C interface. */
+ tdsqlite3_result_text(context, tdsqlite3_sourceid(), -1, SQLITE_STATIC);
}
/*
** Implementation of the sqlite_log() function. This is a wrapper around
-** sqlite3_log(). The return value is NULL. The function exists purely for
+** tdsqlite3_log(). The return value is NULL. The function exists purely for
** its side-effects.
*/
static void errlogFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
UNUSED_PARAMETER(argc);
UNUSED_PARAMETER(context);
- sqlite3_log(sqlite3_value_int(argv[0]), "%s", sqlite3_value_text(argv[1]));
+ tdsqlite3_log(tdsqlite3_value_int(argv[0]), "%s", tdsqlite3_value_text(argv[1]));
}
/*
@@ -108115,19 +120235,19 @@ static void errlogFunc(
*/
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
static void compileoptionusedFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
const char *zOptName;
assert( argc==1 );
UNUSED_PARAMETER(argc);
/* IMP: R-39564-36305 The sqlite_compileoption_used() SQL
- ** function is a wrapper around the sqlite3_compileoption_used() C/C++
+ ** function is a wrapper around the tdsqlite3_compileoption_used() C/C++
** function.
*/
- if( (zOptName = (const char*)sqlite3_value_text(argv[0]))!=0 ){
- sqlite3_result_int(context, sqlite3_compileoption_used(zOptName));
+ if( (zOptName = (const char*)tdsqlite3_value_text(argv[0]))!=0 ){
+ tdsqlite3_result_int(context, tdsqlite3_compileoption_used(zOptName));
}
}
#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
@@ -108139,18 +120259,18 @@ static void compileoptionusedFunc(
*/
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
static void compileoptiongetFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
int n;
assert( argc==1 );
UNUSED_PARAMETER(argc);
/* IMP: R-04922-24076 The sqlite_compileoption_get() SQL function
- ** is a wrapper around the sqlite3_compileoption_get() C/C++ function.
+ ** is a wrapper around the tdsqlite3_compileoption_get() C/C++ function.
*/
- n = sqlite3_value_int(argv[0]);
- sqlite3_result_text(context, sqlite3_compileoption_get(n), -1, SQLITE_STATIC);
+ n = tdsqlite3_value_int(argv[0]);
+ tdsqlite3_result_text(context, tdsqlite3_compileoption_get(n), -1, SQLITE_STATIC);
}
#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
@@ -108168,31 +120288,31 @@ static const char hexdigits[] = {
** "NULL". Otherwise, the argument is enclosed in single quotes with
** single-quote escapes.
*/
-static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
+static void quoteFunc(tdsqlite3_context *context, int argc, tdsqlite3_value **argv){
assert( argc==1 );
UNUSED_PARAMETER(argc);
- switch( sqlite3_value_type(argv[0]) ){
+ switch( tdsqlite3_value_type(argv[0]) ){
case SQLITE_FLOAT: {
double r1, r2;
char zBuf[50];
- r1 = sqlite3_value_double(argv[0]);
- sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
- sqlite3AtoF(zBuf, &r2, 20, SQLITE_UTF8);
+ r1 = tdsqlite3_value_double(argv[0]);
+ tdsqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
+ tdsqlite3AtoF(zBuf, &r2, 20, SQLITE_UTF8);
if( r1!=r2 ){
- sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.20e", r1);
+ tdsqlite3_snprintf(sizeof(zBuf), zBuf, "%!.20e", r1);
}
- sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
break;
}
case SQLITE_INTEGER: {
- sqlite3_result_value(context, argv[0]);
+ tdsqlite3_result_value(context, argv[0]);
break;
}
case SQLITE_BLOB: {
char *zText = 0;
- char const *zBlob = sqlite3_value_blob(argv[0]);
- int nBlob = sqlite3_value_bytes(argv[0]);
- assert( zBlob==sqlite3_value_blob(argv[0]) ); /* No encoding change */
+ char const *zBlob = tdsqlite3_value_blob(argv[0]);
+ int nBlob = tdsqlite3_value_bytes(argv[0]);
+ assert( zBlob==tdsqlite3_value_blob(argv[0]) ); /* No encoding change */
zText = (char *)contextMalloc(context, (2*(i64)nBlob)+4);
if( zText ){
int i;
@@ -108204,15 +120324,15 @@ static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
zText[(nBlob*2)+3] = '\0';
zText[0] = 'X';
zText[1] = '\'';
- sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT);
- sqlite3_free(zText);
+ tdsqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT);
+ tdsqlite3_free(zText);
}
break;
}
case SQLITE_TEXT: {
int i,j;
u64 n;
- const unsigned char *zArg = sqlite3_value_text(argv[0]);
+ const unsigned char *zArg = tdsqlite3_value_text(argv[0]);
char *z;
if( zArg==0 ) return;
@@ -108228,13 +120348,13 @@ static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
}
z[j++] = '\'';
z[j] = 0;
- sqlite3_result_text(context, z, j, sqlite3_free);
+ tdsqlite3_result_text(context, z, j, tdsqlite3_free);
}
break;
}
default: {
- assert( sqlite3_value_type(argv[0])==SQLITE_NULL );
- sqlite3_result_text(context, "NULL", 4, SQLITE_STATIC);
+ assert( tdsqlite3_value_type(argv[0])==SQLITE_NULL );
+ tdsqlite3_result_text(context, "NULL", 4, SQLITE_STATIC);
break;
}
}
@@ -108245,13 +120365,13 @@ static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
** for the first character of the input string.
*/
static void unicodeFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- const unsigned char *z = sqlite3_value_text(argv[0]);
+ const unsigned char *z = tdsqlite3_value_text(argv[0]);
(void)argc;
- if( z && z[0] ) sqlite3_result_int(context, sqlite3Utf8Read(&z));
+ if( z && z[0] ) tdsqlite3_result_int(context, tdsqlite3Utf8Read(&z));
}
/*
@@ -108260,21 +120380,21 @@ static void unicodeFunc(
** is the unicode character for the corresponding integer argument.
*/
static void charFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
unsigned char *z, *zOut;
int i;
- zOut = z = sqlite3_malloc64( argc*4+1 );
+ zOut = z = tdsqlite3_malloc64( argc*4+1 );
if( z==0 ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
return;
}
for(i=0; i<argc; i++){
- sqlite3_int64 x;
+ tdsqlite3_int64 x;
unsigned c;
- x = sqlite3_value_int64(argv[i]);
+ x = tdsqlite3_value_int64(argv[i]);
if( x<0 || x>0x10ffff ) x = 0xfffd;
c = (unsigned)(x & 0x1fffff);
if( c<0x00080 ){
@@ -108293,7 +120413,7 @@ static void charFunc(
*zOut++ = 0x80 + (u8)(c & 0x3F);
} \
}
- sqlite3_result_text64(context, (char*)z, zOut-z, sqlite3_free, SQLITE_UTF8);
+ tdsqlite3_result_text64(context, (char*)z, zOut-z, tdsqlite3_free, SQLITE_UTF8);
}
/*
@@ -108301,18 +120421,18 @@ static void charFunc(
** a hexadecimal rendering as text.
*/
static void hexFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
int i, n;
const unsigned char *pBlob;
char *zHex, *z;
assert( argc==1 );
UNUSED_PARAMETER(argc);
- pBlob = sqlite3_value_blob(argv[0]);
- n = sqlite3_value_bytes(argv[0]);
- assert( pBlob==sqlite3_value_blob(argv[0]) ); /* No encoding change */
+ pBlob = tdsqlite3_value_blob(argv[0]);
+ n = tdsqlite3_value_bytes(argv[0]);
+ assert( pBlob==tdsqlite3_value_blob(argv[0]) ); /* No encoding change */
z = zHex = contextMalloc(context, ((i64)n)*2 + 1);
if( zHex ){
for(i=0; i<n; i++, pBlob++){
@@ -108321,7 +120441,7 @@ static void hexFunc(
*(z++) = hexdigits[c&0xf];
}
*z = 0;
- sqlite3_result_text(context, zHex, n*2, sqlite3_free);
+ tdsqlite3_result_text(context, zHex, n*2, tdsqlite3_free);
}
}
@@ -108329,19 +120449,19 @@ static void hexFunc(
** The zeroblob(N) function returns a zero-filled blob of size N bytes.
*/
static void zeroblobFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
i64 n;
int rc;
assert( argc==1 );
UNUSED_PARAMETER(argc);
- n = sqlite3_value_int64(argv[0]);
+ n = tdsqlite3_value_int64(argv[0]);
if( n<0 ) n = 0;
- rc = sqlite3_result_zeroblob64(context, n); /* IMP: R-00293-64994 */
+ rc = tdsqlite3_result_zeroblob64(context, n); /* IMP: R-00293-64994 */
if( rc ){
- sqlite3_result_error_code(context, rc);
+ tdsqlite3_result_error_code(context, rc);
}
}
@@ -108352,9 +120472,9 @@ static void zeroblobFunc(
** must be exact. Collating sequences are not used.
*/
static void replaceFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
const unsigned char *zStr; /* The input string A */
const unsigned char *zPattern; /* The pattern string B */
@@ -108366,30 +120486,32 @@ static void replaceFunc(
i64 nOut; /* Maximum size of zOut */
int loopLimit; /* Last zStr[] that might match zPattern[] */
int i, j; /* Loop counters */
+ unsigned cntExpand; /* Number zOut expansions */
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
assert( argc==3 );
UNUSED_PARAMETER(argc);
- zStr = sqlite3_value_text(argv[0]);
+ zStr = tdsqlite3_value_text(argv[0]);
if( zStr==0 ) return;
- nStr = sqlite3_value_bytes(argv[0]);
- assert( zStr==sqlite3_value_text(argv[0]) ); /* No encoding change */
- zPattern = sqlite3_value_text(argv[1]);
+ nStr = tdsqlite3_value_bytes(argv[0]);
+ assert( zStr==tdsqlite3_value_text(argv[0]) ); /* No encoding change */
+ zPattern = tdsqlite3_value_text(argv[1]);
if( zPattern==0 ){
- assert( sqlite3_value_type(argv[1])==SQLITE_NULL
- || sqlite3_context_db_handle(context)->mallocFailed );
+ assert( tdsqlite3_value_type(argv[1])==SQLITE_NULL
+ || tdsqlite3_context_db_handle(context)->mallocFailed );
return;
}
if( zPattern[0]==0 ){
- assert( sqlite3_value_type(argv[1])!=SQLITE_NULL );
- sqlite3_result_value(context, argv[0]);
+ assert( tdsqlite3_value_type(argv[1])!=SQLITE_NULL );
+ tdsqlite3_result_value(context, argv[0]);
return;
}
- nPattern = sqlite3_value_bytes(argv[1]);
- assert( zPattern==sqlite3_value_text(argv[1]) ); /* No encoding change */
- zRep = sqlite3_value_text(argv[2]);
+ nPattern = tdsqlite3_value_bytes(argv[1]);
+ assert( zPattern==tdsqlite3_value_text(argv[1]) ); /* No encoding change */
+ zRep = tdsqlite3_value_text(argv[2]);
if( zRep==0 ) return;
- nRep = sqlite3_value_bytes(argv[2]);
- assert( zRep==sqlite3_value_text(argv[2]) );
+ nRep = tdsqlite3_value_bytes(argv[2]);
+ assert( zRep==tdsqlite3_value_text(argv[2]) );
nOut = nStr + 1;
assert( nOut<SQLITE_MAX_LENGTH );
zOut = contextMalloc(context, (i64)nOut);
@@ -108397,38 +120519,45 @@ static void replaceFunc(
return;
}
loopLimit = nStr - nPattern;
+ cntExpand = 0;
for(i=j=0; i<=loopLimit; i++){
if( zStr[i]!=zPattern[0] || memcmp(&zStr[i], zPattern, nPattern) ){
zOut[j++] = zStr[i];
}else{
- u8 *zOld;
- sqlite3 *db = sqlite3_context_db_handle(context);
- nOut += nRep - nPattern;
- testcase( nOut-1==db->aLimit[SQLITE_LIMIT_LENGTH] );
- testcase( nOut-2==db->aLimit[SQLITE_LIMIT_LENGTH] );
- if( nOut-1>db->aLimit[SQLITE_LIMIT_LENGTH] ){
- sqlite3_result_error_toobig(context);
- sqlite3_free(zOut);
- return;
- }
- zOld = zOut;
- zOut = sqlite3_realloc64(zOut, (int)nOut);
- if( zOut==0 ){
- sqlite3_result_error_nomem(context);
- sqlite3_free(zOld);
- return;
+ if( nRep>nPattern ){
+ nOut += nRep - nPattern;
+ testcase( nOut-1==db->aLimit[SQLITE_LIMIT_LENGTH] );
+ testcase( nOut-2==db->aLimit[SQLITE_LIMIT_LENGTH] );
+ if( nOut-1>db->aLimit[SQLITE_LIMIT_LENGTH] ){
+ tdsqlite3_result_error_toobig(context);
+ tdsqlite3_free(zOut);
+ return;
+ }
+ cntExpand++;
+ if( (cntExpand&(cntExpand-1))==0 ){
+ /* Grow the size of the output buffer only on substitutions
+ ** whose index is a power of two: 1, 2, 4, 8, 16, 32, ... */
+ u8 *zOld;
+ zOld = zOut;
+ zOut = tdsqlite3_realloc64(zOut, (int)nOut + (nOut - nStr - 1));
+ if( zOut==0 ){
+ tdsqlite3_result_error_nomem(context);
+ tdsqlite3_free(zOld);
+ return;
+ }
+ }
}
memcpy(&zOut[j], zRep, nRep);
j += nRep;
i += nPattern-1;
}
}
- assert( j+nStr-i+1==nOut );
+ assert( j+nStr-i+1<=nOut );
memcpy(&zOut[j], &zStr[i], nStr-i);
j += nStr - i;
assert( j<=nOut );
zOut[j] = 0;
- sqlite3_result_text(context, (char*)zOut, j, sqlite3_free);
+ tdsqlite3_result_text(context, (char*)zOut, j, tdsqlite3_free);
}
/*
@@ -108436,9 +120565,9 @@ static void replaceFunc(
** The userdata is 0x1 for left trim, 0x2 for right trim, 0x3 for both.
*/
static void trimFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
const unsigned char *zIn; /* Input string */
const unsigned char *zCharSet; /* Set of characters to trim */
@@ -108449,13 +120578,13 @@ static void trimFunc(
unsigned char **azChar = 0; /* Individual characters in zCharSet */
int nChar; /* Number of characters in zCharSet */
- if( sqlite3_value_type(argv[0])==SQLITE_NULL ){
+ if( tdsqlite3_value_type(argv[0])==SQLITE_NULL ){
return;
}
- zIn = sqlite3_value_text(argv[0]);
+ zIn = tdsqlite3_value_text(argv[0]);
if( zIn==0 ) return;
- nIn = sqlite3_value_bytes(argv[0]);
- assert( zIn==sqlite3_value_text(argv[0]) );
+ nIn = tdsqlite3_value_bytes(argv[0]);
+ assert( zIn==tdsqlite3_value_text(argv[0]) );
if( argc==1 ){
static const unsigned char lenOne[] = { 1 };
static unsigned char * const azOne[] = { (u8*)" " };
@@ -108463,7 +120592,7 @@ static void trimFunc(
aLen = (u8*)lenOne;
azChar = (unsigned char **)azOne;
zCharSet = 0;
- }else if( (zCharSet = sqlite3_value_text(argv[1]))==0 ){
+ }else if( (zCharSet = tdsqlite3_value_text(argv[1]))==0 ){
return;
}else{
const unsigned char *z;
@@ -108484,7 +120613,7 @@ static void trimFunc(
}
}
if( nChar>0 ){
- flags = SQLITE_PTR_TO_INT(sqlite3_user_data(context));
+ flags = SQLITE_PTR_TO_INT(tdsqlite3_user_data(context));
if( flags & 1 ){
while( nIn>0 ){
int len = 0;
@@ -108509,10 +120638,10 @@ static void trimFunc(
}
}
if( zCharSet ){
- sqlite3_free(azChar);
+ tdsqlite3_free(azChar);
}
}
- sqlite3_result_text(context, (char*)zIn, nIn, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(context, (char*)zIn, nIn, SQLITE_TRANSIENT);
}
@@ -108521,15 +120650,15 @@ static void trimFunc(
** The "unknown" function is automatically substituted in place of
** any unrecognized function name when doing an EXPLAIN or EXPLAIN QUERY PLAN
** when the SQLITE_ENABLE_UNKNOWN_FUNCTION compile-time option is used.
-** When the "sqlite3" command-line shell is built using this functionality,
+** When the "tdsqlite3" command-line shell is built using this functionality,
** that allows an EXPLAIN or EXPLAIN QUERY PLAN for complex queries
** involving application-defined functions to be examined in a generic
-** sqlite3 shell.
+** tdsqlite3 shell.
*/
static void unknownFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
/* no-op */
}
@@ -108548,9 +120677,9 @@ static void unknownFunc(
** soundex encoding of the string X.
*/
static void soundexFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
char zResult[8];
const u8 *zIn;
@@ -108566,12 +120695,12 @@ static void soundexFunc(
1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
};
assert( argc==1 );
- zIn = (u8*)sqlite3_value_text(argv[0]);
+ zIn = (u8*)tdsqlite3_value_text(argv[0]);
if( zIn==0 ) zIn = (u8*)"";
- for(i=0; zIn[i] && !sqlite3Isalpha(zIn[i]); i++){}
+ for(i=0; zIn[i] && !tdsqlite3Isalpha(zIn[i]); i++){}
if( zIn[i] ){
u8 prevcode = iCode[zIn[i]&0x7f];
- zResult[0] = sqlite3Toupper(zIn[i]);
+ zResult[0] = tdsqlite3Toupper(zIn[i]);
for(j=1; j<4 && zIn[i]; i++){
int code = iCode[zIn[i]&0x7f];
if( code>0 ){
@@ -108587,11 +120716,11 @@ static void soundexFunc(
zResult[j++] = '0';
}
zResult[j] = 0;
- sqlite3_result_text(context, zResult, 4, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(context, zResult, 4, SQLITE_TRANSIENT);
}else{
/* IMP: R-64894-50321 The string "?000" is returned if the argument
** is NULL or contains no ASCII alphabetic characters. */
- sqlite3_result_text(context, "?000", 4, SQLITE_STATIC);
+ tdsqlite3_result_text(context, "?000", 4, SQLITE_STATIC);
}
}
#endif /* SQLITE_SOUNDEX */
@@ -108600,28 +120729,28 @@ static void soundexFunc(
/*
** A function that loads a shared-library extension then returns NULL.
*/
-static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){
- const char *zFile = (const char *)sqlite3_value_text(argv[0]);
+static void loadExt(tdsqlite3_context *context, int argc, tdsqlite3_value **argv){
+ const char *zFile = (const char *)tdsqlite3_value_text(argv[0]);
const char *zProc;
- sqlite3 *db = sqlite3_context_db_handle(context);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
char *zErrMsg = 0;
/* Disallow the load_extension() SQL function unless the SQLITE_LoadExtFunc
- ** flag is set. See the sqlite3_enable_load_extension() API.
+ ** flag is set. See the tdsqlite3_enable_load_extension() API.
*/
if( (db->flags & SQLITE_LoadExtFunc)==0 ){
- sqlite3_result_error(context, "not authorized", -1);
+ tdsqlite3_result_error(context, "not authorized", -1);
return;
}
if( argc==2 ){
- zProc = (const char *)sqlite3_value_text(argv[1]);
+ zProc = (const char *)tdsqlite3_value_text(argv[1]);
}else{
zProc = 0;
}
- if( zFile && sqlite3_load_extension(db, zFile, zProc, &zErrMsg) ){
- sqlite3_result_error(context, zErrMsg, -1);
- sqlite3_free(zErrMsg);
+ if( zFile && tdsqlite3_load_extension(db, zFile, zProc, &zErrMsg) ){
+ tdsqlite3_result_error(context, zErrMsg, -1);
+ tdsqlite3_free(zErrMsg);
}
}
#endif
@@ -108650,52 +120779,78 @@ struct SumCtx {
** value. TOTAL never fails, but SUM might through an exception if
** it overflows an integer.
*/
-static void sumStep(sqlite3_context *context, int argc, sqlite3_value **argv){
+static void sumStep(tdsqlite3_context *context, int argc, tdsqlite3_value **argv){
SumCtx *p;
int type;
assert( argc==1 );
UNUSED_PARAMETER(argc);
- p = sqlite3_aggregate_context(context, sizeof(*p));
- type = sqlite3_value_numeric_type(argv[0]);
+ p = tdsqlite3_aggregate_context(context, sizeof(*p));
+ type = tdsqlite3_value_numeric_type(argv[0]);
if( p && type!=SQLITE_NULL ){
p->cnt++;
if( type==SQLITE_INTEGER ){
- i64 v = sqlite3_value_int64(argv[0]);
+ i64 v = tdsqlite3_value_int64(argv[0]);
p->rSum += v;
- if( (p->approx|p->overflow)==0 && sqlite3AddInt64(&p->iSum, v) ){
- p->overflow = 1;
+ if( (p->approx|p->overflow)==0 && tdsqlite3AddInt64(&p->iSum, v) ){
+ p->approx = p->overflow = 1;
}
}else{
- p->rSum += sqlite3_value_double(argv[0]);
+ p->rSum += tdsqlite3_value_double(argv[0]);
p->approx = 1;
}
}
}
-static void sumFinalize(sqlite3_context *context){
+#ifndef SQLITE_OMIT_WINDOWFUNC
+static void sumInverse(tdsqlite3_context *context, int argc, tdsqlite3_value**argv){
SumCtx *p;
- p = sqlite3_aggregate_context(context, 0);
+ int type;
+ assert( argc==1 );
+ UNUSED_PARAMETER(argc);
+ p = tdsqlite3_aggregate_context(context, sizeof(*p));
+ type = tdsqlite3_value_numeric_type(argv[0]);
+ /* p is always non-NULL because sumStep() will have been called first
+ ** to initialize it */
+ if( ALWAYS(p) && type!=SQLITE_NULL ){
+ assert( p->cnt>0 );
+ p->cnt--;
+ assert( type==SQLITE_INTEGER || p->approx );
+ if( type==SQLITE_INTEGER && p->approx==0 ){
+ i64 v = tdsqlite3_value_int64(argv[0]);
+ p->rSum -= v;
+ p->iSum -= v;
+ }else{
+ p->rSum -= tdsqlite3_value_double(argv[0]);
+ }
+ }
+}
+#else
+# define sumInverse 0
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+static void sumFinalize(tdsqlite3_context *context){
+ SumCtx *p;
+ p = tdsqlite3_aggregate_context(context, 0);
if( p && p->cnt>0 ){
if( p->overflow ){
- sqlite3_result_error(context,"integer overflow",-1);
+ tdsqlite3_result_error(context,"integer overflow",-1);
}else if( p->approx ){
- sqlite3_result_double(context, p->rSum);
+ tdsqlite3_result_double(context, p->rSum);
}else{
- sqlite3_result_int64(context, p->iSum);
+ tdsqlite3_result_int64(context, p->iSum);
}
}
}
-static void avgFinalize(sqlite3_context *context){
+static void avgFinalize(tdsqlite3_context *context){
SumCtx *p;
- p = sqlite3_aggregate_context(context, 0);
+ p = tdsqlite3_aggregate_context(context, 0);
if( p && p->cnt>0 ){
- sqlite3_result_double(context, p->rSum/(double)p->cnt);
+ tdsqlite3_result_double(context, p->rSum/(double)p->cnt);
}
}
-static void totalFinalize(sqlite3_context *context){
+static void totalFinalize(tdsqlite3_context *context){
SumCtx *p;
- p = sqlite3_aggregate_context(context, 0);
+ p = tdsqlite3_aggregate_context(context, 0);
/* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */
- sqlite3_result_double(context, p ? p->rSum : (double)0);
+ tdsqlite3_result_double(context, p ? p->rSum : (double)0);
}
/*
@@ -108705,219 +120860,298 @@ static void totalFinalize(sqlite3_context *context){
typedef struct CountCtx CountCtx;
struct CountCtx {
i64 n;
+#ifdef SQLITE_DEBUG
+ int bInverse; /* True if xInverse() ever called */
+#endif
};
/*
** Routines to implement the count() aggregate function.
*/
-static void countStep(sqlite3_context *context, int argc, sqlite3_value **argv){
+static void countStep(tdsqlite3_context *context, int argc, tdsqlite3_value **argv){
CountCtx *p;
- p = sqlite3_aggregate_context(context, sizeof(*p));
- if( (argc==0 || SQLITE_NULL!=sqlite3_value_type(argv[0])) && p ){
+ p = tdsqlite3_aggregate_context(context, sizeof(*p));
+ if( (argc==0 || SQLITE_NULL!=tdsqlite3_value_type(argv[0])) && p ){
p->n++;
}
#ifndef SQLITE_OMIT_DEPRECATED
- /* The sqlite3_aggregate_count() function is deprecated. But just to make
+ /* The tdsqlite3_aggregate_count() function is deprecated. But just to make
** sure it still operates correctly, verify that its count agrees with our
** internal count when using count(*) and when the total count can be
** expressed as a 32-bit integer. */
- assert( argc==1 || p==0 || p->n>0x7fffffff
- || p->n==sqlite3_aggregate_count(context) );
+ assert( argc==1 || p==0 || p->n>0x7fffffff || p->bInverse
+ || p->n==tdsqlite3_aggregate_count(context) );
#endif
}
-static void countFinalize(sqlite3_context *context){
+static void countFinalize(tdsqlite3_context *context){
CountCtx *p;
- p = sqlite3_aggregate_context(context, 0);
- sqlite3_result_int64(context, p ? p->n : 0);
+ p = tdsqlite3_aggregate_context(context, 0);
+ tdsqlite3_result_int64(context, p ? p->n : 0);
}
+#ifndef SQLITE_OMIT_WINDOWFUNC
+static void countInverse(tdsqlite3_context *ctx, int argc, tdsqlite3_value **argv){
+ CountCtx *p;
+ p = tdsqlite3_aggregate_context(ctx, sizeof(*p));
+ /* p is always non-NULL since countStep() will have been called first */
+ if( (argc==0 || SQLITE_NULL!=tdsqlite3_value_type(argv[0])) && ALWAYS(p) ){
+ p->n--;
+#ifdef SQLITE_DEBUG
+ p->bInverse = 1;
+#endif
+ }
+}
+#else
+# define countInverse 0
+#endif /* SQLITE_OMIT_WINDOWFUNC */
/*
** Routines to implement min() and max() aggregate functions.
*/
static void minmaxStep(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int NotUsed,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
Mem *pArg = (Mem *)argv[0];
Mem *pBest;
UNUSED_PARAMETER(NotUsed);
- pBest = (Mem *)sqlite3_aggregate_context(context, sizeof(*pBest));
+ pBest = (Mem *)tdsqlite3_aggregate_context(context, sizeof(*pBest));
if( !pBest ) return;
- if( sqlite3_value_type(argv[0])==SQLITE_NULL ){
- if( pBest->flags ) sqlite3SkipAccumulatorLoad(context);
+ if( tdsqlite3_value_type(pArg)==SQLITE_NULL ){
+ if( pBest->flags ) tdsqlite3SkipAccumulatorLoad(context);
}else if( pBest->flags ){
int max;
int cmp;
- CollSeq *pColl = sqlite3GetFuncCollSeq(context);
+ CollSeq *pColl = tdsqlite3GetFuncCollSeq(context);
/* This step function is used for both the min() and max() aggregates,
** the only difference between the two being that the sense of the
** comparison is inverted. For the max() aggregate, the
- ** sqlite3_user_data() function returns (void *)-1. For min() it
- ** returns (void *)db, where db is the sqlite3* database pointer.
+ ** tdsqlite3_user_data() function returns (void *)-1. For min() it
+ ** returns (void *)db, where db is the tdsqlite3* database pointer.
** Therefore the next statement sets variable 'max' to 1 for the max()
** aggregate, or 0 for min().
*/
- max = sqlite3_user_data(context)!=0;
- cmp = sqlite3MemCompare(pBest, pArg, pColl);
+ max = tdsqlite3_user_data(context)!=0;
+ cmp = tdsqlite3MemCompare(pBest, pArg, pColl);
if( (max && cmp<0) || (!max && cmp>0) ){
- sqlite3VdbeMemCopy(pBest, pArg);
+ tdsqlite3VdbeMemCopy(pBest, pArg);
}else{
- sqlite3SkipAccumulatorLoad(context);
+ tdsqlite3SkipAccumulatorLoad(context);
}
}else{
- pBest->db = sqlite3_context_db_handle(context);
- sqlite3VdbeMemCopy(pBest, pArg);
+ pBest->db = tdsqlite3_context_db_handle(context);
+ tdsqlite3VdbeMemCopy(pBest, pArg);
}
}
-static void minMaxFinalize(sqlite3_context *context){
- sqlite3_value *pRes;
- pRes = (sqlite3_value *)sqlite3_aggregate_context(context, 0);
+static void minMaxValueFinalize(tdsqlite3_context *context, int bValue){
+ tdsqlite3_value *pRes;
+ pRes = (tdsqlite3_value *)tdsqlite3_aggregate_context(context, 0);
if( pRes ){
if( pRes->flags ){
- sqlite3_result_value(context, pRes);
+ tdsqlite3_result_value(context, pRes);
}
- sqlite3VdbeMemRelease(pRes);
+ if( bValue==0 ) tdsqlite3VdbeMemRelease(pRes);
}
}
+#ifndef SQLITE_OMIT_WINDOWFUNC
+static void minMaxValue(tdsqlite3_context *context){
+ minMaxValueFinalize(context, 1);
+}
+#else
+# define minMaxValue 0
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+static void minMaxFinalize(tdsqlite3_context *context){
+ minMaxValueFinalize(context, 0);
+}
/*
** group_concat(EXPR, ?SEPARATOR?)
*/
static void groupConcatStep(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
const char *zVal;
StrAccum *pAccum;
const char *zSep;
int nVal, nSep;
assert( argc==1 || argc==2 );
- if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
- pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum));
+ if( tdsqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+ pAccum = (StrAccum*)tdsqlite3_aggregate_context(context, sizeof(*pAccum));
if( pAccum ){
- sqlite3 *db = sqlite3_context_db_handle(context);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
int firstTerm = pAccum->mxAlloc==0;
pAccum->mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH];
if( !firstTerm ){
if( argc==2 ){
- zSep = (char*)sqlite3_value_text(argv[1]);
- nSep = sqlite3_value_bytes(argv[1]);
+ zSep = (char*)tdsqlite3_value_text(argv[1]);
+ nSep = tdsqlite3_value_bytes(argv[1]);
}else{
zSep = ",";
nSep = 1;
}
- if( nSep ) sqlite3StrAccumAppend(pAccum, zSep, nSep);
+ if( zSep ) tdsqlite3_str_append(pAccum, zSep, nSep);
}
- zVal = (char*)sqlite3_value_text(argv[0]);
- nVal = sqlite3_value_bytes(argv[0]);
- if( zVal ) sqlite3StrAccumAppend(pAccum, zVal, nVal);
+ zVal = (char*)tdsqlite3_value_text(argv[0]);
+ nVal = tdsqlite3_value_bytes(argv[0]);
+ if( zVal ) tdsqlite3_str_append(pAccum, zVal, nVal);
}
}
-static void groupConcatFinalize(sqlite3_context *context){
+#ifndef SQLITE_OMIT_WINDOWFUNC
+static void groupConcatInverse(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ int n;
StrAccum *pAccum;
- pAccum = sqlite3_aggregate_context(context, 0);
+ assert( argc==1 || argc==2 );
+ if( tdsqlite3_value_type(argv[0])==SQLITE_NULL ) return;
+ pAccum = (StrAccum*)tdsqlite3_aggregate_context(context, sizeof(*pAccum));
+ /* pAccum is always non-NULL since groupConcatStep() will have always
+ ** run frist to initialize it */
+ if( ALWAYS(pAccum) ){
+ n = tdsqlite3_value_bytes(argv[0]);
+ if( argc==2 ){
+ n += tdsqlite3_value_bytes(argv[1]);
+ }else{
+ n++;
+ }
+ if( n>=(int)pAccum->nChar ){
+ pAccum->nChar = 0;
+ }else{
+ pAccum->nChar -= n;
+ memmove(pAccum->zText, &pAccum->zText[n], pAccum->nChar);
+ }
+ if( pAccum->nChar==0 ) pAccum->mxAlloc = 0;
+ }
+}
+#else
+# define groupConcatInverse 0
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+static void groupConcatFinalize(tdsqlite3_context *context){
+ StrAccum *pAccum;
+ pAccum = tdsqlite3_aggregate_context(context, 0);
+ if( pAccum ){
+ if( pAccum->accError==SQLITE_TOOBIG ){
+ tdsqlite3_result_error_toobig(context);
+ }else if( pAccum->accError==SQLITE_NOMEM ){
+ tdsqlite3_result_error_nomem(context);
+ }else{
+ tdsqlite3_result_text(context, tdsqlite3StrAccumFinish(pAccum), -1,
+ tdsqlite3_free);
+ }
+ }
+}
+#ifndef SQLITE_OMIT_WINDOWFUNC
+static void groupConcatValue(tdsqlite3_context *context){
+ tdsqlite3_str *pAccum;
+ pAccum = (tdsqlite3_str*)tdsqlite3_aggregate_context(context, 0);
if( pAccum ){
- if( pAccum->accError==STRACCUM_TOOBIG ){
- sqlite3_result_error_toobig(context);
- }else if( pAccum->accError==STRACCUM_NOMEM ){
- sqlite3_result_error_nomem(context);
+ if( pAccum->accError==SQLITE_TOOBIG ){
+ tdsqlite3_result_error_toobig(context);
+ }else if( pAccum->accError==SQLITE_NOMEM ){
+ tdsqlite3_result_error_nomem(context);
}else{
- sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1,
- sqlite3_free);
+ const char *zText = tdsqlite3_str_value(pAccum);
+ tdsqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT);
}
}
}
+#else
+# define groupConcatValue 0
+#endif /* SQLITE_OMIT_WINDOWFUNC */
/*
** This routine does per-connection function registration. Most
** of the built-in functions above are part of the global function set.
** This routine only deals with those that are not global.
*/
-SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3 *db){
- int rc = sqlite3_overload_function(db, "MATCH", 2);
-/* BEGIN SQLCIPHER */
-#ifdef SQLITE_HAS_CODEC
-#ifndef OMIT_EXPORT
- extern void sqlcipher_exportFunc(sqlite3_context *, int, sqlite3_value **);
-#endif
-#endif
-/* END SQLCIPHER */
+SQLITE_PRIVATE void tdsqlite3RegisterPerConnectionBuiltinFunctions(tdsqlite3 *db){
+ int rc = tdsqlite3_overload_function(db, "MATCH", 2);
assert( rc==SQLITE_NOMEM || rc==SQLITE_OK );
if( rc==SQLITE_NOMEM ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
}
/* BEGIN SQLCIPHER */
#ifdef SQLITE_HAS_CODEC
#ifndef OMIT_EXPORT
- sqlite3CreateFunc(db, "sqlcipher_export", 1, SQLITE_TEXT, 0, sqlcipher_exportFunc, 0, 0, 0);
+ {
+ tdsqlite3CreateFunc(db, "sqlcipher_export", -1, SQLITE_TEXT, 0, sqlcipher_exportFunc, 0, 0, 0, 0, 0);
+ }
+#endif
+#ifdef SQLCIPHER_EXT
+#include "sqlcipher_funcs_init.h"
#endif
#endif
/* END SQLCIPHER */
}
/*
-** Set the LIKEOPT flag on the 2-argument function with the given name.
-*/
-static void setLikeOptFlag(sqlite3 *db, const char *zName, u8 flagVal){
- FuncDef *pDef;
- pDef = sqlite3FindFunction(db, zName, 2, SQLITE_UTF8, 0);
- if( ALWAYS(pDef) ){
- pDef->funcFlags |= flagVal;
- }
-}
-
-/*
-** Register the built-in LIKE and GLOB functions. The caseSensitive
+** Re-register the built-in LIKE functions. The caseSensitive
** parameter determines whether or not the LIKE operator is case
-** sensitive. GLOB is always case sensitive.
+** sensitive.
*/
-SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){
+SQLITE_PRIVATE void tdsqlite3RegisterLikeFunctions(tdsqlite3 *db, int caseSensitive){
struct compareInfo *pInfo;
+ int flags;
if( caseSensitive ){
pInfo = (struct compareInfo*)&likeInfoAlt;
+ flags = SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE;
}else{
pInfo = (struct compareInfo*)&likeInfoNorm;
+ flags = SQLITE_FUNC_LIKE;
}
- sqlite3CreateFunc(db, "like", 2, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0);
- sqlite3CreateFunc(db, "like", 3, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0);
- sqlite3CreateFunc(db, "glob", 2, SQLITE_UTF8,
- (struct compareInfo*)&globInfo, likeFunc, 0, 0, 0);
- setLikeOptFlag(db, "glob", SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE);
- setLikeOptFlag(db, "like",
- caseSensitive ? (SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE) : SQLITE_FUNC_LIKE);
+ tdsqlite3CreateFunc(db, "like", 2, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0);
+ tdsqlite3CreateFunc(db, "like", 3, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0);
+ tdsqlite3FindFunction(db, "like", 2, SQLITE_UTF8, 0)->funcFlags |= flags;
+ tdsqlite3FindFunction(db, "like", 3, SQLITE_UTF8, 0)->funcFlags |= flags;
}
/*
** pExpr points to an expression which implements a function. If
** it is appropriate to apply the LIKE optimization to that function
-** then set aWc[0] through aWc[2] to the wildcard characters and
-** return TRUE. If the function is not a LIKE-style function then
-** return FALSE.
+** then set aWc[0] through aWc[2] to the wildcard characters and the
+** escape character and then return TRUE. If the function is not a
+** LIKE-style function then return FALSE.
+**
+** The expression "a LIKE b ESCAPE c" is only considered a valid LIKE
+** operator if c is a string literal that is exactly one byte in length.
+** That one byte is stored in aWc[3]. aWc[3] is set to zero if there is
+** no ESCAPE clause.
**
** *pIsNocase is set to true if uppercase and lowercase are equivalent for
** the function (default for LIKE). If the function makes the distinction
** between uppercase and lowercase (as does GLOB) then *pIsNocase is set to
** false.
*/
-SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){
+SQLITE_PRIVATE int tdsqlite3IsLikeFunction(tdsqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){
FuncDef *pDef;
- if( pExpr->op!=TK_FUNCTION
- || !pExpr->x.pList
- || pExpr->x.pList->nExpr!=2
- ){
+ int nExpr;
+ if( pExpr->op!=TK_FUNCTION || !pExpr->x.pList ){
return 0;
}
assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
- pDef = sqlite3FindFunction(db, pExpr->u.zToken, 2, SQLITE_UTF8, 0);
+ nExpr = pExpr->x.pList->nExpr;
+ pDef = tdsqlite3FindFunction(db, pExpr->u.zToken, nExpr, SQLITE_UTF8, 0);
if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_FUNC_LIKE)==0 ){
return 0;
}
+ if( nExpr<3 ){
+ aWc[3] = 0;
+ }else{
+ Expr *pEscape = pExpr->x.pList->a[2].pExpr;
+ char *zEscape;
+ if( pEscape->op!=TK_STRING ) return 0;
+ zEscape = pEscape->u.zToken;
+ if( zEscape[0]==0 || zEscape[1]!=0 ) return 0;
+ aWc[3] = zEscape[0];
+ }
/* The memcpy() statement assumes that the wildcard characters are
** the first three statements in the compareInfo structure. The
@@ -108934,11 +121168,11 @@ SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocas
/*
** All of the FuncDef structures in the aBuiltinFunc[] array above
** to the global function hash table. This occurs at start-time (as
-** a consequence of calling sqlite3_initialize()).
+** a consequence of calling tdsqlite3_initialize()).
**
** After this routine runs
*/
-SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
+SQLITE_PRIVATE void tdsqlite3RegisterBuiltinFunctions(void){
/*
** The following array holds FuncDef structures for all of the functions
** defined in this file.
@@ -108950,23 +121184,35 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
** For peak efficiency, put the most frequently used function last.
*/
static FuncDef aBuiltinFunc[] = {
+/***** Functions only available with SQLITE_TESTCTRL_INTERNAL_FUNCTIONS *****/
+ TEST_FUNC(implies_nonnull_row, 2, INLINEFUNC_implies_nonnull_row, 0),
+ TEST_FUNC(expr_compare, 2, INLINEFUNC_expr_compare, 0),
+ TEST_FUNC(expr_implies_expr, 2, INLINEFUNC_expr_implies_expr, 0),
+#ifdef SQLITE_DEBUG
+ TEST_FUNC(affinity, 1, INLINEFUNC_affinity, 0),
+#endif
+/***** Regular functions *****/
#ifdef SQLITE_SOUNDEX
FUNCTION(soundex, 1, 0, 0, soundexFunc ),
#endif
#ifndef SQLITE_OMIT_LOAD_EXTENSION
- VFUNCTION(load_extension, 1, 0, 0, loadExt ),
- VFUNCTION(load_extension, 2, 0, 0, loadExt ),
+ SFUNCTION(load_extension, 1, 0, 0, loadExt ),
+ SFUNCTION(load_extension, 2, 0, 0, loadExt ),
#endif
#if SQLITE_USER_AUTHENTICATION
- FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ),
+ FUNCTION(sqlite_crypt, 2, 0, 0, tdsqlite3CryptFunc ),
#endif
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
DFUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ),
DFUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ),
#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
- FUNCTION2(unlikely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY),
- FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY),
- FUNCTION2(likely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY),
+ INLINE_FUNC(unlikely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
+ INLINE_FUNC(likelihood, 2, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
+ INLINE_FUNC(likely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+ FUNCTION2(sqlite_offset, 1, 0, 0, noopFunc, SQLITE_FUNC_OFFSET|
+ SQLITE_FUNC_TYPEOF),
+#endif
FUNCTION(ltrim, 1, 1, 0, trimFunc ),
FUNCTION(ltrim, 2, 1, 0, trimFunc ),
FUNCTION(rtrim, 1, 2, 0, trimFunc ),
@@ -108975,11 +121221,11 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
FUNCTION(trim, 2, 3, 0, trimFunc ),
FUNCTION(min, -1, 0, 1, minmaxFunc ),
FUNCTION(min, 0, 0, 1, 0 ),
- AGGREGATE2(min, 1, 0, 1, minmaxStep, minMaxFinalize,
+ WAGGREGATE(min, 1, 0, 1, minmaxStep, minMaxFinalize, minMaxValue, 0,
SQLITE_FUNC_MINMAX ),
FUNCTION(max, -1, 1, 1, minmaxFunc ),
FUNCTION(max, 0, 1, 1, 0 ),
- AGGREGATE2(max, 1, 1, 1, minmaxStep, minMaxFinalize,
+ WAGGREGATE(max, 1, 1, 1, minmaxStep, minMaxFinalize, minMaxValue, 0,
SQLITE_FUNC_MINMAX ),
FUNCTION2(typeof, 1, 0, 0, typeofFunc, SQLITE_FUNC_TYPEOF),
FUNCTION2(length, 1, 0, 0, lengthFunc, SQLITE_FUNC_LENGTH),
@@ -108995,7 +121241,7 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
FUNCTION(upper, 1, 0, 0, upperFunc ),
FUNCTION(lower, 1, 0, 0, lowerFunc ),
FUNCTION(hex, 1, 0, 0, hexFunc ),
- FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQLITE_FUNC_COALESCE),
+ INLINE_FUNC(ifnull, 2, INLINEFUNC_coalesce, SQLITE_FUNC_COALESCE),
VFUNCTION(random, 0, 0, 0, randomFunc ),
VFUNCTION(randomblob, 1, 0, 0, randomBlob ),
FUNCTION(nullif, 2, 0, 1, nullifFunc ),
@@ -109010,14 +121256,17 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
FUNCTION(zeroblob, 1, 0, 0, zeroblobFunc ),
FUNCTION(substr, 2, 0, 0, substrFunc ),
FUNCTION(substr, 3, 0, 0, substrFunc ),
- AGGREGATE(sum, 1, 0, 0, sumStep, sumFinalize ),
- AGGREGATE(total, 1, 0, 0, sumStep, totalFinalize ),
- AGGREGATE(avg, 1, 0, 0, sumStep, avgFinalize ),
- AGGREGATE2(count, 0, 0, 0, countStep, countFinalize,
- SQLITE_FUNC_COUNT ),
- AGGREGATE(count, 1, 0, 0, countStep, countFinalize ),
- AGGREGATE(group_concat, 1, 0, 0, groupConcatStep, groupConcatFinalize),
- AGGREGATE(group_concat, 2, 0, 0, groupConcatStep, groupConcatFinalize),
+ WAGGREGATE(sum, 1,0,0, sumStep, sumFinalize, sumFinalize, sumInverse, 0),
+ WAGGREGATE(total, 1,0,0, sumStep,totalFinalize,totalFinalize,sumInverse, 0),
+ WAGGREGATE(avg, 1,0,0, sumStep, avgFinalize, avgFinalize, sumInverse, 0),
+ WAGGREGATE(count, 0,0,0, countStep,
+ countFinalize, countFinalize, countInverse, SQLITE_FUNC_COUNT ),
+ WAGGREGATE(count, 1,0,0, countStep,
+ countFinalize, countFinalize, countInverse, 0 ),
+ WAGGREGATE(group_concat, 1, 0, 0, groupConcatStep,
+ groupConcatFinalize, groupConcatValue, groupConcatInverse, 0),
+ WAGGREGATE(group_concat, 2, 0, 0, groupConcatStep,
+ groupConcatFinalize, groupConcatValue, groupConcatInverse, 0),
LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE),
#ifdef SQLITE_CASE_SENSITIVE_LIKE
@@ -109032,16 +121281,14 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
#endif
FUNCTION(coalesce, 1, 0, 0, 0 ),
FUNCTION(coalesce, 0, 0, 0, 0 ),
- FUNCTION2(coalesce, -1, 0, 0, noopFunc, SQLITE_FUNC_COALESCE),
+ INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, SQLITE_FUNC_COALESCE),
};
#ifndef SQLITE_OMIT_ALTERTABLE
- sqlite3AlterFunctions();
-#endif
-#if defined(SQLITE_ENABLE_STAT3) || defined(SQLITE_ENABLE_STAT4)
- sqlite3AnalyzeFunctions();
+ tdsqlite3AlterFunctions();
#endif
- sqlite3RegisterDateTimeFunctions();
- sqlite3InsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc));
+ tdsqlite3WindowFunctions();
+ tdsqlite3RegisterDateTimeFunctions();
+ tdsqlite3InsertBuiltinFuncs(aBuiltinFunc, ArraySize(aBuiltinFunc));
#if 0 /* Enable to print out how the built-in functions are hashed */
{
@@ -109049,8 +121296,8 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
FuncDef *p;
for(i=0; i<SQLITE_FUNC_HASH_SZ; i++){
printf("FUNC-HASH %02d:", i);
- for(p=sqlite3BuiltinFunctions.a[i]; p; p=p->u.pHash){
- int n = sqlite3Strlen30(p->zName);
+ for(p=tdsqlite3BuiltinFunctions.a[i]; p; p=p->u.pHash){
+ int n = tdsqlite3Strlen30(p->zName);
int h = p->zName[0] + n;
printf(" %s(%d)", p->zName, h);
}
@@ -109175,16 +121422,16 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
** coding an INSERT operation. The functions used by the UPDATE/DELETE
** generation code to query for this information are:
**
-** sqlite3FkRequired() - Test to see if FK processing is required.
-** sqlite3FkOldmask() - Query for the set of required old.* columns.
+** tdsqlite3FkRequired() - Test to see if FK processing is required.
+** tdsqlite3FkOldmask() - Query for the set of required old.* columns.
**
**
** Externally accessible module functions
** --------------------------------------
**
-** sqlite3FkCheck() - Check for foreign key violations.
-** sqlite3FkActions() - Code triggers for ON UPDATE/ON DELETE actions.
-** sqlite3FkDelete() - Delete an FKey structure.
+** tdsqlite3FkCheck() - Check for foreign key violations.
+** tdsqlite3FkActions() - Code triggers for ON UPDATE/ON DELETE actions.
+** tdsqlite3FkDelete() - Delete an FKey structure.
*/
/*
@@ -109244,7 +121491,7 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){
** into pParse. If an OOM error occurs, non-zero is returned and the
** pParse->db->mallocFailed flag is set.
*/
-SQLITE_PRIVATE int sqlite3FkLocateIndex(
+SQLITE_PRIVATE int tdsqlite3FkLocateIndex(
Parse *pParse, /* Parse context to store any error in */
Table *pParent, /* Parent table of FK constraint pFKey */
FKey *pFKey, /* Foreign key to find index for */
@@ -109279,17 +121526,17 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex(
*/
if( pParent->iPKey>=0 ){
if( !zKey ) return 0;
- if( !sqlite3StrICmp(pParent->aCol[pParent->iPKey].zName, zKey) ) return 0;
+ if( !tdsqlite3StrICmp(pParent->aCol[pParent->iPKey].zName, zKey) ) return 0;
}
}else if( paiCol ){
assert( nCol>1 );
- aiCol = (int *)sqlite3DbMallocRawNN(pParse->db, nCol*sizeof(int));
+ aiCol = (int *)tdsqlite3DbMallocRawNN(pParse->db, nCol*sizeof(int));
if( !aiCol ) return 1;
*paiCol = aiCol;
}
for(pIdx=pParent->pIndex; pIdx; pIdx=pIdx->pNext){
- if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) ){
+ if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) && pIdx->pPartIdxWhere==0 ){
/* pIdx is a UNIQUE index (or a PRIMARY KEY) and has the right number
** of columns. If each indexed column corresponds to a foreign key
** column of pFKey, then this index is a winner. */
@@ -109322,12 +121569,12 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex(
** the default collation sequence for the column, this index is
** unusable. Bail out early in this case. */
zDfltColl = pParent->aCol[iCol].zColl;
- if( !zDfltColl ) zDfltColl = sqlite3StrBINARY;
- if( sqlite3StrICmp(pIdx->azColl[i], zDfltColl) ) break;
+ if( !zDfltColl ) zDfltColl = tdsqlite3StrBINARY;
+ if( tdsqlite3StrICmp(pIdx->azColl[i], zDfltColl) ) break;
zIdxCol = pParent->aCol[iCol].zName;
for(j=0; j<nCol; j++){
- if( sqlite3StrICmp(pFKey->aCol[j].zCol, zIdxCol)==0 ){
+ if( tdsqlite3StrICmp(pFKey->aCol[j].zCol, zIdxCol)==0 ){
if( aiCol ) aiCol[i] = pFKey->aCol[j].iFrom;
break;
}
@@ -109341,11 +121588,11 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex(
if( !pIdx ){
if( !pParse->disableTriggers ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"foreign key mismatch - \"%w\" referencing \"%w\"",
pFKey->pFrom->zName, pFKey->zTo);
}
- sqlite3DbFree(pParse->db, aiCol);
+ tdsqlite3DbFree(pParse->db, aiCol);
return 1;
}
@@ -109391,9 +121638,15 @@ static void fkLookupParent(
int isIgnore /* If true, pretend pTab contains all NULL values */
){
int i; /* Iterator variable */
- Vdbe *v = sqlite3GetVdbe(pParse); /* Vdbe to add code to */
+ Vdbe *v = tdsqlite3GetVdbe(pParse); /* Vdbe to add code to */
int iCur = pParse->nTab - 1; /* Cursor number to use */
- int iOk = sqlite3VdbeMakeLabel(v); /* jump here if parent key found */
+ int iOk = tdsqlite3VdbeMakeLabel(pParse); /* jump here if parent key found */
+
+ tdsqlite3VdbeVerifyAbortable(v,
+ (!pFKey->isDeferred
+ && !(pParse->db->flags & SQLITE_DeferFKs)
+ && !pParse->pToplevel
+ && !pParse->isMultiWrite) ? OE_Abort : OE_Ignore);
/* If nIncr is less than zero, then check at runtime if there are any
** outstanding constraints to resolve. If there are not, there is no need
@@ -109403,12 +121656,12 @@ static void fkLookupParent(
** any are, then the constraint is considered satisfied. No need to
** search for a matching row in the parent table. */
if( nIncr<0 ){
- sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, iOk);
+ tdsqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, iOk);
VdbeCoverage(v);
}
for(i=0; i<pFKey->nCol; i++){
- int iReg = aiCol[i] + regData + 1;
- sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iOk); VdbeCoverage(v);
+ int iReg = tdsqlite3TableColumnToStorage(pFKey->pFrom,aiCol[i]) + regData + 1;
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, iReg, iOk); VdbeCoverage(v);
}
if( isIgnore==0 ){
@@ -109416,15 +121669,16 @@ static void fkLookupParent(
/* If pIdx is NULL, then the parent key is the INTEGER PRIMARY KEY
** column of the parent table (table pTab). */
int iMustBeInt; /* Address of MustBeInt instruction */
- int regTemp = sqlite3GetTempReg(pParse);
+ int regTemp = tdsqlite3GetTempReg(pParse);
/* Invoke MustBeInt to coerce the child key value to an integer (i.e.
** apply the affinity of the parent key). If this fails, then there
** is no matching parent key. Before using MustBeInt, make a copy of
** the value. Otherwise, the value inserted into the child key column
** will have INTEGER affinity applied to it, which may not be correct. */
- sqlite3VdbeAddOp2(v, OP_SCopy, aiCol[0]+1+regData, regTemp);
- iMustBeInt = sqlite3VdbeAddOp2(v, OP_MustBeInt, regTemp, 0);
+ tdsqlite3VdbeAddOp2(v, OP_SCopy,
+ tdsqlite3TableColumnToStorage(pFKey->pFrom,aiCol[0])+1+regData, regTemp);
+ iMustBeInt = tdsqlite3VdbeAddOp2(v, OP_MustBeInt, regTemp, 0);
VdbeCoverage(v);
/* If the parent table is the same as the child table, and we are about
@@ -109432,25 +121686,27 @@ static void fkLookupParent(
** then check if the row being inserted matches itself. If so, do not
** increment the constraint-counter. */
if( pTab==pFKey->pFrom && nIncr==1 ){
- sqlite3VdbeAddOp3(v, OP_Eq, regData, iOk, regTemp); VdbeCoverage(v);
- sqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
+ tdsqlite3VdbeAddOp3(v, OP_Eq, regData, iOk, regTemp); VdbeCoverage(v);
+ tdsqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
}
- sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead);
- sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regTemp); VdbeCoverage(v);
- sqlite3VdbeGoto(v, iOk);
- sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2);
- sqlite3VdbeJumpHere(v, iMustBeInt);
- sqlite3ReleaseTempReg(pParse, regTemp);
+ tdsqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead);
+ tdsqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regTemp); VdbeCoverage(v);
+ tdsqlite3VdbeGoto(v, iOk);
+ tdsqlite3VdbeJumpHere(v, tdsqlite3VdbeCurrentAddr(v)-2);
+ tdsqlite3VdbeJumpHere(v, iMustBeInt);
+ tdsqlite3ReleaseTempReg(pParse, regTemp);
}else{
int nCol = pFKey->nCol;
- int regTemp = sqlite3GetTempRange(pParse, nCol);
- int regRec = sqlite3GetTempReg(pParse);
+ int regTemp = tdsqlite3GetTempRange(pParse, nCol);
+ int regRec = tdsqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb);
- sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+ tdsqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pIdx);
for(i=0; i<nCol; i++){
- sqlite3VdbeAddOp2(v, OP_Copy, aiCol[i]+1+regData, regTemp+i);
+ tdsqlite3VdbeAddOp2(v, OP_Copy,
+ tdsqlite3TableColumnToStorage(pFKey->pFrom, aiCol[i])+1+regData,
+ regTemp+i);
}
/* If the parent table is the same as the child table, and we are about
@@ -109464,28 +121720,31 @@ static void fkLookupParent(
** none of the child key values are).
*/
if( pTab==pFKey->pFrom && nIncr==1 ){
- int iJump = sqlite3VdbeCurrentAddr(v) + nCol + 1;
+ int iJump = tdsqlite3VdbeCurrentAddr(v) + nCol + 1;
for(i=0; i<nCol; i++){
- int iChild = aiCol[i]+1+regData;
- int iParent = pIdx->aiColumn[i]+1+regData;
+ int iChild = tdsqlite3TableColumnToStorage(pFKey->pFrom,aiCol[i])
+ +1+regData;
+ int iParent = 1+regData;
+ iParent += tdsqlite3TableColumnToStorage(pIdx->pTable,
+ pIdx->aiColumn[i]);
assert( pIdx->aiColumn[i]>=0 );
assert( aiCol[i]!=pTab->iPKey );
if( pIdx->aiColumn[i]==pTab->iPKey ){
/* The parent key is a composite key that includes the IPK column */
iParent = regData;
}
- sqlite3VdbeAddOp3(v, OP_Ne, iChild, iJump, iParent); VdbeCoverage(v);
- sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL);
+ tdsqlite3VdbeAddOp3(v, OP_Ne, iChild, iJump, iParent); VdbeCoverage(v);
+ tdsqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL);
}
- sqlite3VdbeGoto(v, iOk);
+ tdsqlite3VdbeGoto(v, iOk);
}
- sqlite3VdbeAddOp4(v, OP_MakeRecord, regTemp, nCol, regRec,
- sqlite3IndexAffinityStr(pParse->db,pIdx), nCol);
- sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, regTemp, nCol, regRec,
+ tdsqlite3IndexAffinityStr(pParse->db,pIdx), nCol);
+ tdsqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); VdbeCoverage(v);
- sqlite3ReleaseTempReg(pParse, regRec);
- sqlite3ReleaseTempRange(pParse, regTemp, nCol);
+ tdsqlite3ReleaseTempReg(pParse, regRec);
+ tdsqlite3ReleaseTempRange(pParse, regTemp, nCol);
}
}
@@ -109498,17 +121757,17 @@ static void fkLookupParent(
** incrementing a counter. This is necessary as the VM code is being
** generated for will not open a statement transaction. */
assert( nIncr==1 );
- sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY,
+ tdsqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY,
OE_Abort, 0, P4_STATIC, P5_ConstraintFK);
}else{
if( nIncr>0 && pFKey->isDeferred==0 ){
- sqlite3MayAbort(pParse);
+ tdsqlite3MayAbort(pParse);
}
- sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
+ tdsqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
}
- sqlite3VdbeResolveLabel(v, iOk);
- sqlite3VdbeAddOp1(v, OP_Close, iCur);
+ tdsqlite3VdbeResolveLabel(v, iOk);
+ tdsqlite3VdbeAddOp1(v, OP_Close, iCur);
}
@@ -109529,20 +121788,20 @@ static Expr *exprTableRegister(
Expr *pExpr;
Column *pCol;
const char *zColl;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
- pExpr = sqlite3Expr(db, TK_REGISTER, 0);
+ pExpr = tdsqlite3Expr(db, TK_REGISTER, 0);
if( pExpr ){
if( iCol>=0 && iCol!=pTab->iPKey ){
pCol = &pTab->aCol[iCol];
- pExpr->iTable = regBase + iCol + 1;
- pExpr->affinity = pCol->affinity;
+ pExpr->iTable = regBase + tdsqlite3TableColumnToStorage(pTab,iCol) + 1;
+ pExpr->affExpr = pCol->affinity;
zColl = pCol->zColl;
if( zColl==0 ) zColl = db->pDfltColl->zName;
- pExpr = sqlite3ExprAddCollateString(pParse, pExpr, zColl);
+ pExpr = tdsqlite3ExprAddCollateString(pParse, pExpr, zColl);
}else{
pExpr->iTable = regBase;
- pExpr->affinity = SQLITE_AFF_INTEGER;
+ pExpr->affExpr = SQLITE_AFF_INTEGER;
}
}
return pExpr;
@@ -109553,14 +121812,14 @@ static Expr *exprTableRegister(
** has cursor iCur.
*/
static Expr *exprTableColumn(
- sqlite3 *db, /* The database connection */
+ tdsqlite3 *db, /* The database connection */
Table *pTab, /* The table whose column is desired */
int iCursor, /* The open cursor on the table */
i16 iCol /* The column that is wanted */
){
- Expr *pExpr = sqlite3Expr(db, TK_COLUMN, 0);
+ Expr *pExpr = tdsqlite3Expr(db, TK_COLUMN, 0);
if( pExpr ){
- pExpr->pTab = pTab;
+ pExpr->y.pTab = pTab;
pExpr->iTable = iCursor;
pExpr->iColumn = iCol;
}
@@ -109609,13 +121868,13 @@ static void fkScanChildren(
int regData, /* Parent row data starts here */
int nIncr /* Amount to increment deferred counter by */
){
- sqlite3 *db = pParse->db; /* Database handle */
+ tdsqlite3 *db = pParse->db; /* Database handle */
int i; /* Iterator variable */
Expr *pWhere = 0; /* WHERE clause to scan with */
NameContext sNameContext; /* Context used to resolve WHERE clause */
- WhereInfo *pWInfo; /* Context used by sqlite3WhereXXX() */
+ WhereInfo *pWInfo; /* Context used by tdsqlite3WhereXXX() */
int iFkIfZero = 0; /* Address of OP_FkIfZero */
- Vdbe *v = sqlite3GetVdbe(pParse);
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
assert( pIdx==0 || pIdx->pTable==pTab );
assert( pIdx==0 || pIdx->nKeyCol==pFKey->nCol );
@@ -109623,7 +121882,7 @@ static void fkScanChildren(
assert( pIdx!=0 || HasRowid(pTab) );
if( nIncr<0 ){
- iFkIfZero = sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, 0);
+ iFkIfZero = tdsqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, 0);
VdbeCoverage(v);
}
@@ -109647,9 +121906,9 @@ static void fkScanChildren(
iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom;
assert( iCol>=0 );
zCol = pFKey->pFrom->aCol[iCol].zName;
- pRight = sqlite3Expr(db, TK_ID, zCol);
- pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0);
- pWhere = sqlite3ExprAnd(db, pWhere, pEq);
+ pRight = tdsqlite3Expr(db, TK_ID, zCol);
+ pEq = tdsqlite3PExpr(pParse, TK_EQ, pLeft, pRight);
+ pWhere = tdsqlite3ExprAnd(pParse, pWhere, pEq);
}
/* If the child table is the same as the parent table, then add terms
@@ -109660,8 +121919,11 @@ static void fkScanChildren(
** NOT( $current_a==a AND $current_b==b AND ... )
**
** The first form is used for rowid tables. The second form is used
- ** for WITHOUT ROWID tables. In the second form, the primary key is
- ** (a,b,...)
+ ** for WITHOUT ROWID tables. In the second form, the *parent* key is
+ ** (a,b,...). Either the parent or primary key could be used to
+ ** uniquely identify the current row, but the parent key is more convenient
+ ** as the required values have already been loaded into registers
+ ** by the caller.
*/
if( pTab==pFKey->pFrom && nIncr>0 ){
Expr *pNe; /* Expression (pLeft != pRight) */
@@ -109670,43 +121932,44 @@ static void fkScanChildren(
if( HasRowid(pTab) ){
pLeft = exprTableRegister(pParse, pTab, regData, -1);
pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, -1);
- pNe = sqlite3PExpr(pParse, TK_NE, pLeft, pRight, 0);
+ pNe = tdsqlite3PExpr(pParse, TK_NE, pLeft, pRight);
}else{
Expr *pEq, *pAll = 0;
- Index *pPk = sqlite3PrimaryKeyIndex(pTab);
assert( pIdx!=0 );
- for(i=0; i<pPk->nKeyCol; i++){
+ for(i=0; i<pIdx->nKeyCol; i++){
i16 iCol = pIdx->aiColumn[i];
assert( iCol>=0 );
pLeft = exprTableRegister(pParse, pTab, regData, iCol);
- pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, iCol);
- pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0);
- pAll = sqlite3ExprAnd(db, pAll, pEq);
+ pRight = tdsqlite3Expr(db, TK_ID, pTab->aCol[iCol].zName);
+ pEq = tdsqlite3PExpr(pParse, TK_IS, pLeft, pRight);
+ pAll = tdsqlite3ExprAnd(pParse, pAll, pEq);
}
- pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0, 0);
+ pNe = tdsqlite3PExpr(pParse, TK_NOT, pAll, 0);
}
- pWhere = sqlite3ExprAnd(db, pWhere, pNe);
+ pWhere = tdsqlite3ExprAnd(pParse, pWhere, pNe);
}
/* Resolve the references in the WHERE clause. */
memset(&sNameContext, 0, sizeof(NameContext));
sNameContext.pSrcList = pSrc;
sNameContext.pParse = pParse;
- sqlite3ResolveExprNames(&sNameContext, pWhere);
+ tdsqlite3ResolveExprNames(&sNameContext, pWhere);
/* Create VDBE to loop through the entries in pSrc that match the WHERE
** clause. For each row found, increment either the deferred or immediate
** foreign key constraint counter. */
- pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0);
- sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
- if( pWInfo ){
- sqlite3WhereEnd(pWInfo);
+ if( pParse->nErr==0 ){
+ pWInfo = tdsqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0);
+ tdsqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
+ if( pWInfo ){
+ tdsqlite3WhereEnd(pWInfo);
+ }
}
/* Clean up the WHERE clause constructed above. */
- sqlite3ExprDelete(db, pWhere);
+ tdsqlite3ExprDelete(db, pWhere);
if( iFkIfZero ){
- sqlite3VdbeJumpHere(v, iFkIfZero);
+ tdsqlite3VdbeJumpHere(v, iFkIfZero);
}
}
@@ -109724,8 +121987,8 @@ static void fkScanChildren(
** NULL pointer (as there are no FK constraints for which t2 is the parent
** table).
*/
-SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *pTab){
- return (FKey *)sqlite3HashFind(&pTab->pSchema->fkeyHash, pTab->zName);
+SQLITE_PRIVATE FKey *tdsqlite3FkReferences(Table *pTab){
+ return (FKey *)tdsqlite3HashFind(&pTab->pSchema->fkeyHash, pTab->zName);
}
/*
@@ -109736,14 +121999,14 @@ SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *pTab){
** The Trigger structure or any of its sub-components may be allocated from
** the lookaside buffer belonging to database handle dbMem.
*/
-static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){
+static void fkTriggerDelete(tdsqlite3 *dbMem, Trigger *p){
if( p ){
TriggerStep *pStep = p->step_list;
- sqlite3ExprDelete(dbMem, pStep->pWhere);
- sqlite3ExprListDelete(dbMem, pStep->pExprList);
- sqlite3SelectDelete(dbMem, pStep->pSelect);
- sqlite3ExprDelete(dbMem, p->pWhen);
- sqlite3DbFree(dbMem, p);
+ tdsqlite3ExprDelete(dbMem, pStep->pWhere);
+ tdsqlite3ExprListDelete(dbMem, pStep->pExprList);
+ tdsqlite3SelectDelete(dbMem, pStep->pSelect);
+ tdsqlite3ExprDelete(dbMem, p->pWhen);
+ tdsqlite3DbFree(dbMem, p);
}
}
@@ -109764,14 +122027,15 @@ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){
** the table from the database. Triggers are disabled while running this
** DELETE, but foreign key actions are not.
*/
-SQLITE_PRIVATE void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){
- sqlite3 *db = pParse->db;
- if( (db->flags&SQLITE_ForeignKeys) && !IsVirtual(pTab) && !pTab->pSelect ){
+SQLITE_PRIVATE void tdsqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){
+ tdsqlite3 *db = pParse->db;
+ if( (db->flags&SQLITE_ForeignKeys) && !IsVirtual(pTab) ){
int iSkip = 0;
- Vdbe *v = sqlite3GetVdbe(pParse);
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
assert( v ); /* VDBE has already been allocated */
- if( sqlite3FkReferences(pTab)==0 ){
+ assert( pTab->pSelect==0 ); /* Not a view */
+ if( tdsqlite3FkReferences(pTab)==0 ){
/* Search for a deferred foreign key constraint for which this table
** is the child table. If one cannot be found, return without
** generating any VDBE code. If one can be found, then jump over
@@ -109782,12 +122046,12 @@ SQLITE_PRIVATE void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTa
if( p->isDeferred || (db->flags & SQLITE_DeferFKs) ) break;
}
if( !p ) return;
- iSkip = sqlite3VdbeMakeLabel(v);
- sqlite3VdbeAddOp2(v, OP_FkIfZero, 1, iSkip); VdbeCoverage(v);
+ iSkip = tdsqlite3VdbeMakeLabel(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_FkIfZero, 1, iSkip); VdbeCoverage(v);
}
pParse->disableTriggers = 1;
- sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0);
+ tdsqlite3DeleteFrom(pParse, tdsqlite3SrcListDup(db, pName, 0), 0, 0, 0);
pParse->disableTriggers = 0;
/* If the DELETE has generated immediate foreign key constraint
@@ -109800,14 +122064,15 @@ SQLITE_PRIVATE void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTa
** constraints are violated.
*/
if( (db->flags & SQLITE_DeferFKs)==0 ){
- sqlite3VdbeAddOp2(v, OP_FkIfZero, 0, sqlite3VdbeCurrentAddr(v)+2);
+ tdsqlite3VdbeVerifyAbortable(v, OE_Abort);
+ tdsqlite3VdbeAddOp2(v, OP_FkIfZero, 0, tdsqlite3VdbeCurrentAddr(v)+2);
VdbeCoverage(v);
- sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY,
+ tdsqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY,
OE_Abort, 0, P4_STATIC, P5_ConstraintFK);
}
if( iSkip ){
- sqlite3VdbeResolveLabel(v, iSkip);
+ tdsqlite3VdbeResolveLabel(v, iSkip);
}
}
}
@@ -109866,7 +122131,7 @@ static int fkParentIsModified(
if( aChange[iKey]>=0 || (iKey==pTab->iPKey && bChngRowid) ){
Column *pCol = &pTab->aCol[iKey];
if( zKey ){
- if( 0==sqlite3StrICmp(pCol->zName, zKey) ) return 1;
+ if( 0==tdsqlite3StrICmp(pCol->zName, zKey) ) return 1;
}else if( pCol->colFlags & COLFLAG_PRIMKEY ){
return 1;
}
@@ -109882,7 +122147,7 @@ static int fkParentIsModified(
** to trigger pFKey.
*/
static int isSetNullAction(Parse *pParse, FKey *pFKey){
- Parse *pTop = sqlite3ParseToplevel(pParse);
+ Parse *pTop = tdsqlite3ParseToplevel(pParse);
if( pTop->pTriggerPrg ){
Trigger *p = pTop->pTriggerPrg->pTrigger;
if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull)
@@ -109914,7 +122179,7 @@ static int isSetNullAction(Parse *pParse, FKey *pFKey){
** described for DELETE. Then again after the original record is deleted
** but before the new record is inserted using the INSERT convention.
*/
-SQLITE_PRIVATE void sqlite3FkCheck(
+SQLITE_PRIVATE void tdsqlite3FkCheck(
Parse *pParse, /* Parse context */
Table *pTab, /* Row is being deleted from this table */
int regOld, /* Previous row data is stored here */
@@ -109922,7 +122187,7 @@ SQLITE_PRIVATE void sqlite3FkCheck(
int *aChange, /* Array indicating UPDATEd columns (or 0) */
int bChngRowid /* True if rowid is UPDATEd */
){
- sqlite3 *db = pParse->db; /* Database handle */
+ tdsqlite3 *db = pParse->db; /* Database handle */
FKey *pFKey; /* Used to iterate through FKs */
int iDb; /* Index of database containing pTab */
const char *zDb; /* Name of database containing pTab */
@@ -109934,7 +122199,7 @@ SQLITE_PRIVATE void sqlite3FkCheck(
/* If foreign-keys are disabled, this function is a no-op. */
if( (db->flags&SQLITE_ForeignKeys)==0 ) return;
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
zDb = db->aDb[iDb].zDbSName;
/* Loop through all the foreign key constraints for which pTab is the
@@ -109949,7 +122214,7 @@ SQLITE_PRIVATE void sqlite3FkCheck(
int bIgnore = 0;
if( aChange
- && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0
+ && tdsqlite3_stricmp(pTab->zName, pFKey->zTo)!=0
&& fkChildIsModified(pTab, pFKey, aChange, bChngRowid)==0
){
continue;
@@ -109960,11 +122225,11 @@ SQLITE_PRIVATE void sqlite3FkCheck(
** schema items cannot be located, set an error in pParse and return
** early. */
if( pParse->disableTriggers ){
- pTo = sqlite3FindTable(db, pFKey->zTo, zDb);
+ pTo = tdsqlite3FindTable(db, pFKey->zTo, zDb);
}else{
- pTo = sqlite3LocateTable(pParse, 0, pFKey->zTo, zDb);
+ pTo = tdsqlite3LocateTable(pParse, 0, pFKey->zTo, zDb);
}
- if( !pTo || sqlite3FkLocateIndex(pParse, pTo, pFKey, &pIdx, &aiFree) ){
+ if( !pTo || tdsqlite3FkLocateIndex(pParse, pTo, pFKey, &pIdx, &aiFree) ){
assert( isIgnoreErrors==0 || (regOld!=0 && regNew==0) );
if( !isIgnoreErrors || db->mallocFailed ) return;
if( pTo==0 ){
@@ -109975,13 +122240,15 @@ SQLITE_PRIVATE void sqlite3FkCheck(
** missing, behave as if it is empty. i.e. decrement the relevant
** FK counter for each row of the current table with non-NULL keys.
*/
- Vdbe *v = sqlite3GetVdbe(pParse);
- int iJump = sqlite3VdbeCurrentAddr(v) + pFKey->nCol + 1;
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ int iJump = tdsqlite3VdbeCurrentAddr(v) + pFKey->nCol + 1;
for(i=0; i<pFKey->nCol; i++){
- int iReg = pFKey->aCol[i].iFrom + regOld + 1;
- sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iJump); VdbeCoverage(v);
+ int iFromCol, iReg;
+ iFromCol = pFKey->aCol[i].iFrom;
+ iReg = tdsqlite3TableColumnToStorage(pFKey->pFrom,iFromCol) + regOld+1;
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, iReg, iJump); VdbeCoverage(v);
}
- sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, -1);
+ tdsqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, -1);
}
continue;
}
@@ -110005,7 +122272,7 @@ SQLITE_PRIVATE void sqlite3FkCheck(
if( db->xAuth ){
int rcauth;
char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName;
- rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb);
+ rcauth = tdsqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb);
bIgnore = (rcauth==SQLITE_IGNORE);
}
#endif
@@ -110014,7 +122281,7 @@ SQLITE_PRIVATE void sqlite3FkCheck(
/* Take a shared-cache advisory read-lock on the parent table. Allocate
** a cursor to use to search the unique index on the parent key columns
** in the parent table. */
- sqlite3TableLock(pParse, iDb, pTo->tnum, 0, pTo->zName);
+ tdsqlite3TableLock(pParse, iDb, pTo->tnum, 0, pTo->zName);
pParse->nTab++;
if( regOld!=0 ){
@@ -110035,12 +122302,12 @@ SQLITE_PRIVATE void sqlite3FkCheck(
fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1, bIgnore);
}
- sqlite3DbFree(db, aiFree);
+ tdsqlite3DbFree(db, aiFree);
}
/* Loop through all the foreign key constraints that refer to this table.
** (the "child" constraints) */
- for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){
+ for(pFKey = tdsqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){
Index *pIdx = 0; /* Foreign key index for pFKey */
SrcList *pSrc;
int *aiCol = 0;
@@ -110058,20 +122325,20 @@ SQLITE_PRIVATE void sqlite3FkCheck(
continue;
}
- if( sqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ){
+ if( tdsqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ){
if( !isIgnoreErrors || db->mallocFailed ) return;
continue;
}
assert( aiCol || pFKey->nCol==1 );
/* Create a SrcList structure containing the child table. We need the
- ** child table as a SrcList for sqlite3WhereBegin() */
- pSrc = sqlite3SrcListAppend(db, 0, 0, 0);
+ ** child table as a SrcList for tdsqlite3WhereBegin() */
+ pSrc = tdsqlite3SrcListAppend(pParse, 0, 0, 0);
if( pSrc ){
struct SrcList_item *pItem = pSrc->a;
pItem->pTab = pFKey->pFrom;
pItem->zName = pFKey->pFrom->zName;
- pItem->pTab->nRef++;
+ pItem->pTab->nTabRef++;
pItem->iCursor = pParse->nTab++;
if( regNew!=0 ){
@@ -110098,13 +122365,13 @@ SQLITE_PRIVATE void sqlite3FkCheck(
** might be set incorrectly if any OP_FkCounter related scans are
** omitted. */
if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){
- sqlite3MayAbort(pParse);
+ tdsqlite3MayAbort(pParse);
}
}
pItem->zName = 0;
- sqlite3SrcListDelete(db, pSrc);
+ tdsqlite3SrcListDelete(db, pSrc);
}
- sqlite3DbFree(db, aiCol);
+ tdsqlite3DbFree(db, aiCol);
}
}
@@ -110114,7 +122381,7 @@ SQLITE_PRIVATE void sqlite3FkCheck(
** This function is called before generating code to update or delete a
** row contained in table pTab.
*/
-SQLITE_PRIVATE u32 sqlite3FkOldmask(
+SQLITE_PRIVATE u32 tdsqlite3FkOldmask(
Parse *pParse, /* Parse context */
Table *pTab /* Table being modified */
){
@@ -110125,9 +122392,9 @@ SQLITE_PRIVATE u32 sqlite3FkOldmask(
for(p=pTab->pFKey; p; p=p->pNextFrom){
for(i=0; i<p->nCol; i++) mask |= COLUMN_MASK(p->aCol[i].iFrom);
}
- for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
+ for(p=tdsqlite3FkReferences(pTab); p; p=p->pNextTo){
Index *pIdx = 0;
- sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
+ tdsqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
if( pIdx ){
for(i=0; i<pIdx->nKeyCol; i++){
assert( pIdx->aiColumn[i]>=0 );
@@ -110151,21 +122418,30 @@ SQLITE_PRIVATE u32 sqlite3FkOldmask(
** UPDATE statement modifies the rowid fields of the table.
**
** If any foreign key processing will be required, this function returns
-** true. If there is no foreign key related processing, this function
-** returns false.
+** non-zero. If there is no foreign key related processing, this function
+** returns zero.
+**
+** For an UPDATE, this function returns 2 if:
+**
+** * There are any FKs for which pTab is the child and the parent table, or
+** * the UPDATE modifies one or more parent keys for which the action is
+** not "NO ACTION" (i.e. is CASCADE, SET DEFAULT or SET NULL).
+**
+** Or, assuming some other foreign key processing is required, 1.
*/
-SQLITE_PRIVATE int sqlite3FkRequired(
+SQLITE_PRIVATE int tdsqlite3FkRequired(
Parse *pParse, /* Parse context */
Table *pTab, /* Table being modified */
int *aChange, /* Non-NULL for UPDATE operations */
int chngRowid /* True for UPDATE that affects rowid */
){
+ int eRet = 0;
if( pParse->db->flags&SQLITE_ForeignKeys ){
if( !aChange ){
/* A DELETE operation. Foreign key processing is required if the
** table in question is either the child or parent table for any
** foreign key constraint. */
- return (sqlite3FkReferences(pTab) || pTab->pFKey);
+ eRet = (tdsqlite3FkReferences(pTab) || pTab->pFKey);
}else{
/* This is an UPDATE. Foreign key processing is only required if the
** operation modifies one or more child or parent key columns. */
@@ -110173,16 +122449,22 @@ SQLITE_PRIVATE int sqlite3FkRequired(
/* Check if any child key columns are being modified. */
for(p=pTab->pFKey; p; p=p->pNextFrom){
- if( fkChildIsModified(pTab, p, aChange, chngRowid) ) return 1;
+ if( 0==tdsqlite3_stricmp(pTab->zName, p->zTo) ) return 2;
+ if( fkChildIsModified(pTab, p, aChange, chngRowid) ){
+ eRet = 1;
+ }
}
/* Check if any parent key columns are being modified. */
- for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
- if( fkParentIsModified(pTab, p, aChange, chngRowid) ) return 1;
+ for(p=tdsqlite3FkReferences(pTab); p; p=p->pNextTo){
+ if( fkParentIsModified(pTab, p, aChange, chngRowid) ){
+ if( p->aAction[1]!=OE_None ) return 2;
+ eRet = 1;
+ }
}
}
}
- return 0;
+ return eRet;
}
/*
@@ -110212,7 +122494,7 @@ SQLITE_PRIVATE int sqlite3FkRequired(
**
** The returned pointer is cached as part of the foreign key object. It
** is eventually freed along with the rest of the foreign key object by
-** sqlite3FkDelete().
+** tdsqlite3FkDelete().
*/
static Trigger *fkActionTrigger(
Parse *pParse, /* Parse context */
@@ -110220,7 +122502,7 @@ static Trigger *fkActionTrigger(
FKey *pFKey, /* Foreign key to get action for */
ExprList *pChanges /* Change-list for UPDATE, NULL for DELETE */
){
- sqlite3 *db = pParse->db; /* Database handle */
+ tdsqlite3 *db = pParse->db; /* Database handle */
int action; /* One of OE_None, OE_Cascade etc. */
Trigger *pTrigger; /* Trigger definition to return */
int iAction = (pChanges!=0); /* 1 for UPDATE, 0 for DELETE */
@@ -110243,7 +122525,7 @@ static Trigger *fkActionTrigger(
int i; /* Iterator variable */
Expr *pWhen = 0; /* WHEN clause for the trigger */
- if( sqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ) return 0;
+ if( tdsqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ) return 0;
assert( aiCol || pFKey->nCol==1 );
for(i=0; i<pFKey->nCol; i++){
@@ -110258,22 +122540,21 @@ static Trigger *fkActionTrigger(
assert( iFromCol>=0 );
assert( pIdx!=0 || (pTab->iPKey>=0 && pTab->iPKey<pTab->nCol) );
assert( pIdx==0 || pIdx->aiColumn[i]>=0 );
- sqlite3TokenInit(&tToCol,
+ tdsqlite3TokenInit(&tToCol,
pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zName);
- sqlite3TokenInit(&tFromCol, pFKey->pFrom->aCol[iFromCol].zName);
+ tdsqlite3TokenInit(&tFromCol, pFKey->pFrom->aCol[iFromCol].zName);
/* Create the expression "OLD.zToCol = zFromCol". It is important
** that the "OLD.zToCol" term is on the LHS of the = operator, so
** that the affinity and collation sequence associated with the
** parent table are used for the comparison. */
- pEq = sqlite3PExpr(pParse, TK_EQ,
- sqlite3PExpr(pParse, TK_DOT,
- sqlite3ExprAlloc(db, TK_ID, &tOld, 0),
- sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)
- , 0),
- sqlite3ExprAlloc(db, TK_ID, &tFromCol, 0)
- , 0);
- pWhere = sqlite3ExprAnd(db, pWhere, pEq);
+ pEq = tdsqlite3PExpr(pParse, TK_EQ,
+ tdsqlite3PExpr(pParse, TK_DOT,
+ tdsqlite3ExprAlloc(db, TK_ID, &tOld, 0),
+ tdsqlite3ExprAlloc(db, TK_ID, &tToCol, 0)),
+ tdsqlite3ExprAlloc(db, TK_ID, &tFromCol, 0)
+ );
+ pWhere = tdsqlite3ExprAnd(pParse, pWhere, pEq);
/* For ON UPDATE, construct the next term of the WHEN clause.
** The final WHEN clause will be like this:
@@ -110281,44 +122562,49 @@ static Trigger *fkActionTrigger(
** WHEN NOT(old.col1 IS new.col1 AND ... AND old.colN IS new.colN)
*/
if( pChanges ){
- pEq = sqlite3PExpr(pParse, TK_IS,
- sqlite3PExpr(pParse, TK_DOT,
- sqlite3ExprAlloc(db, TK_ID, &tOld, 0),
- sqlite3ExprAlloc(db, TK_ID, &tToCol, 0),
- 0),
- sqlite3PExpr(pParse, TK_DOT,
- sqlite3ExprAlloc(db, TK_ID, &tNew, 0),
- sqlite3ExprAlloc(db, TK_ID, &tToCol, 0),
- 0),
- 0);
- pWhen = sqlite3ExprAnd(db, pWhen, pEq);
+ pEq = tdsqlite3PExpr(pParse, TK_IS,
+ tdsqlite3PExpr(pParse, TK_DOT,
+ tdsqlite3ExprAlloc(db, TK_ID, &tOld, 0),
+ tdsqlite3ExprAlloc(db, TK_ID, &tToCol, 0)),
+ tdsqlite3PExpr(pParse, TK_DOT,
+ tdsqlite3ExprAlloc(db, TK_ID, &tNew, 0),
+ tdsqlite3ExprAlloc(db, TK_ID, &tToCol, 0))
+ );
+ pWhen = tdsqlite3ExprAnd(pParse, pWhen, pEq);
}
if( action!=OE_Restrict && (action!=OE_Cascade || pChanges) ){
Expr *pNew;
if( action==OE_Cascade ){
- pNew = sqlite3PExpr(pParse, TK_DOT,
- sqlite3ExprAlloc(db, TK_ID, &tNew, 0),
- sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)
- , 0);
+ pNew = tdsqlite3PExpr(pParse, TK_DOT,
+ tdsqlite3ExprAlloc(db, TK_ID, &tNew, 0),
+ tdsqlite3ExprAlloc(db, TK_ID, &tToCol, 0));
}else if( action==OE_SetDflt ){
- Expr *pDflt = pFKey->pFrom->aCol[iFromCol].pDflt;
+ Column *pCol = pFKey->pFrom->aCol + iFromCol;
+ Expr *pDflt;
+ if( pCol->colFlags & COLFLAG_GENERATED ){
+ testcase( pCol->colFlags & COLFLAG_VIRTUAL );
+ testcase( pCol->colFlags & COLFLAG_STORED );
+ pDflt = 0;
+ }else{
+ pDflt = pCol->pDflt;
+ }
if( pDflt ){
- pNew = sqlite3ExprDup(db, pDflt, 0);
+ pNew = tdsqlite3ExprDup(db, pDflt, 0);
}else{
- pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0);
+ pNew = tdsqlite3ExprAlloc(db, TK_NULL, 0, 0);
}
}else{
- pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0);
+ pNew = tdsqlite3ExprAlloc(db, TK_NULL, 0, 0);
}
- pList = sqlite3ExprListAppend(pParse, pList, pNew);
- sqlite3ExprListSetName(pParse, pList, &tFromCol, 0);
+ pList = tdsqlite3ExprListAppend(pParse, pList, pNew);
+ tdsqlite3ExprListSetName(pParse, pList, &tFromCol, 0);
}
}
- sqlite3DbFree(db, aiCol);
+ tdsqlite3DbFree(db, aiCol);
zFrom = pFKey->pFrom->zName;
- nFrom = sqlite3Strlen30(zFrom);
+ nFrom = tdsqlite3Strlen30(zFrom);
if( action==OE_Restrict ){
Token tFrom;
@@ -110326,23 +122612,23 @@ static Trigger *fkActionTrigger(
tFrom.z = zFrom;
tFrom.n = nFrom;
- pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed");
+ pRaise = tdsqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed");
if( pRaise ){
- pRaise->affinity = OE_Abort;
+ pRaise->affExpr = OE_Abort;
}
- pSelect = sqlite3SelectNew(pParse,
- sqlite3ExprListAppend(pParse, 0, pRaise),
- sqlite3SrcListAppend(db, 0, &tFrom, 0),
+ pSelect = tdsqlite3SelectNew(pParse,
+ tdsqlite3ExprListAppend(pParse, 0, pRaise),
+ tdsqlite3SrcListAppend(pParse, 0, &tFrom, 0),
pWhere,
- 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0
);
pWhere = 0;
}
/* Disable lookaside memory allocation */
- db->lookaside.bDisable++;
+ DisableLookaside;
- pTrigger = (Trigger *)sqlite3DbMallocZero(db,
+ pTrigger = (Trigger *)tdsqlite3DbMallocZero(db,
sizeof(Trigger) + /* struct Trigger */
sizeof(TriggerStep) + /* Single step in trigger program */
nFrom + 1 /* Space for pStep->zTarget */
@@ -110352,27 +122638,28 @@ static Trigger *fkActionTrigger(
pStep->zTarget = (char *)&pStep[1];
memcpy((char *)pStep->zTarget, zFrom, nFrom);
- pStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
- pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE);
- pStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE);
+ pStep->pWhere = tdsqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
+ pStep->pExprList = tdsqlite3ExprListDup(db, pList, EXPRDUP_REDUCE);
+ pStep->pSelect = tdsqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE);
if( pWhen ){
- pWhen = sqlite3PExpr(pParse, TK_NOT, pWhen, 0, 0);
- pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE);
+ pWhen = tdsqlite3PExpr(pParse, TK_NOT, pWhen, 0);
+ pTrigger->pWhen = tdsqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE);
}
}
/* Re-enable the lookaside buffer, if it was disabled earlier. */
- db->lookaside.bDisable--;
+ EnableLookaside;
- sqlite3ExprDelete(db, pWhere);
- sqlite3ExprDelete(db, pWhen);
- sqlite3ExprListDelete(db, pList);
- sqlite3SelectDelete(db, pSelect);
+ tdsqlite3ExprDelete(db, pWhere);
+ tdsqlite3ExprDelete(db, pWhen);
+ tdsqlite3ExprListDelete(db, pList);
+ tdsqlite3SelectDelete(db, pSelect);
if( db->mallocFailed==1 ){
fkTriggerDelete(db, pTrigger);
return 0;
}
assert( pStep!=0 );
+ assert( pTrigger!=0 );
switch( action ){
case OE_Restrict:
@@ -110400,7 +122687,7 @@ static Trigger *fkActionTrigger(
** This function is called when deleting or updating a row to implement
** any required CASCADE, SET NULL or SET DEFAULT actions.
*/
-SQLITE_PRIVATE void sqlite3FkActions(
+SQLITE_PRIVATE void tdsqlite3FkActions(
Parse *pParse, /* Parse context */
Table *pTab, /* Table being updated or deleted from */
ExprList *pChanges, /* Change-list for UPDATE, NULL for DELETE */
@@ -110414,11 +122701,11 @@ SQLITE_PRIVATE void sqlite3FkActions(
** trigger sub-program. */
if( pParse->db->flags&SQLITE_ForeignKeys ){
FKey *pFKey; /* Iterator variable */
- for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){
+ for(pFKey = tdsqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){
if( aChange==0 || fkParentIsModified(pTab, pFKey, aChange, bChngRowid) ){
Trigger *pAct = fkActionTrigger(pParse, pTab, pFKey, pChanges);
if( pAct ){
- sqlite3CodeRowTriggerDirect(pParse, pAct, pTab, regOld, OE_Abort, 0);
+ tdsqlite3CodeRowTriggerDirect(pParse, pAct, pTab, regOld, OE_Abort, 0);
}
}
}
@@ -110432,12 +122719,12 @@ SQLITE_PRIVATE void sqlite3FkActions(
** table pTab. Remove the deleted foreign keys from the Schema.fkeyHash
** hash table.
*/
-SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){
+SQLITE_PRIVATE void tdsqlite3FkDelete(tdsqlite3 *db, Table *pTab){
FKey *pFKey; /* Iterator variable */
FKey *pNext; /* Copy of pFKey->pNextFrom */
assert( db==0 || IsVirtual(pTab)
- || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) );
+ || tdsqlite3SchemaMutexHeld(db, 0, pTab->pSchema) );
for(pFKey=pTab->pFKey; pFKey; pFKey=pNext){
/* Remove the FK from the fkeyHash hash table. */
@@ -110447,7 +122734,7 @@ SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){
}else{
void *p = (void *)pFKey->pNextTo;
const char *z = (p ? pFKey->pNextTo->zTo : pFKey->zTo);
- sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, p);
+ tdsqlite3HashInsert(&pTab->pSchema->fkeyHash, z, p);
}
if( pFKey->pNextTo ){
pFKey->pNextTo->pPrevTo = pFKey->pPrevTo;
@@ -110466,7 +122753,7 @@ SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){
#endif
pNext = pFKey->pNextFrom;
- sqlite3DbFree(db, pFKey);
+ tdsqlite3DbFree(db, pFKey);
}
}
#endif /* ifndef SQLITE_OMIT_FOREIGN_KEY */
@@ -110498,7 +122785,7 @@ SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){
** If pTab is a WITHOUT ROWID table, then it is the PRIMARY KEY index
** for that table that is actually opened.
*/
-SQLITE_PRIVATE void sqlite3OpenTable(
+SQLITE_PRIVATE void tdsqlite3OpenTable(
Parse *pParse, /* Generate code into this VDBE */
int iCur, /* The cursor number of the table */
int iDb, /* The database index in sqlite3.aDb[] */
@@ -110507,19 +122794,19 @@ SQLITE_PRIVATE void sqlite3OpenTable(
){
Vdbe *v;
assert( !IsVirtual(pTab) );
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
assert( opcode==OP_OpenWrite || opcode==OP_OpenRead );
- sqlite3TableLock(pParse, iDb, pTab->tnum,
+ tdsqlite3TableLock(pParse, iDb, pTab->tnum,
(opcode==OP_OpenWrite)?1:0, pTab->zName);
if( HasRowid(pTab) ){
- sqlite3VdbeAddOp4Int(v, opcode, iCur, pTab->tnum, iDb, pTab->nCol);
+ tdsqlite3VdbeAddOp4Int(v, opcode, iCur, pTab->tnum, iDb, pTab->nNVCol);
VdbeComment((v, "%s", pTab->zName));
}else{
- Index *pPk = sqlite3PrimaryKeyIndex(pTab);
+ Index *pPk = tdsqlite3PrimaryKeyIndex(pTab);
assert( pPk!=0 );
assert( pPk->tnum==pTab->tnum );
- sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, iDb);
- sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+ tdsqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, iDb);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pPk);
VdbeComment((v, "%s", pTab->zName));
}
}
@@ -110542,9 +122829,9 @@ SQLITE_PRIVATE void sqlite3OpenTable(
**
** Memory for the buffer containing the column index affinity string
** is managed along with the rest of the Index structure. It will be
-** released when sqlite3DeleteIndex() is called.
+** released when tdsqlite3DeleteIndex() is called.
*/
-SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){
+SQLITE_PRIVATE const char *tdsqlite3IndexAffinityStr(tdsqlite3 *db, Index *pIdx){
if( !pIdx->zColAff ){
/* The first time a column affinity string for a particular index is
** required, it is allocated and populated here. It is then stored as
@@ -110556,25 +122843,26 @@ SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){
*/
int n;
Table *pTab = pIdx->pTable;
- pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+1);
+ pIdx->zColAff = (char *)tdsqlite3DbMallocRaw(0, pIdx->nColumn+1);
if( !pIdx->zColAff ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
return 0;
}
for(n=0; n<pIdx->nColumn; n++){
i16 x = pIdx->aiColumn[n];
+ char aff;
if( x>=0 ){
- pIdx->zColAff[n] = pTab->aCol[x].affinity;
+ aff = pTab->aCol[x].affinity;
}else if( x==XN_ROWID ){
- pIdx->zColAff[n] = SQLITE_AFF_INTEGER;
+ aff = SQLITE_AFF_INTEGER;
}else{
- char aff;
assert( x==XN_EXPR );
assert( pIdx->aColExpr!=0 );
- aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr);
- if( aff==0 ) aff = SQLITE_AFF_BLOB;
- pIdx->zColAff[n] = aff;
+ aff = tdsqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr);
}
+ if( aff<SQLITE_AFF_BLOB ) aff = SQLITE_AFF_BLOB;
+ if( aff>SQLITE_AFF_NUMERIC) aff = SQLITE_AFF_NUMERIC;
+ pIdx->zColAff[n] = aff;
}
pIdx->zColAff[n] = 0;
}
@@ -110602,31 +122890,35 @@ SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){
** 'D' INTEGER
** 'E' REAL
*/
-SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
- int i;
+SQLITE_PRIVATE void tdsqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
+ int i, j;
char *zColAff = pTab->zColAff;
if( zColAff==0 ){
- sqlite3 *db = sqlite3VdbeDb(v);
- zColAff = (char *)sqlite3DbMallocRaw(0, pTab->nCol+1);
+ tdsqlite3 *db = tdsqlite3VdbeDb(v);
+ zColAff = (char *)tdsqlite3DbMallocRaw(0, pTab->nCol+1);
if( !zColAff ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
return;
}
- for(i=0; i<pTab->nCol; i++){
- zColAff[i] = pTab->aCol[i].affinity;
+ for(i=j=0; i<pTab->nCol; i++){
+ assert( pTab->aCol[i].affinity!=0 );
+ if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){
+ zColAff[j++] = pTab->aCol[i].affinity;
+ }
}
do{
- zColAff[i--] = 0;
- }while( i>=0 && zColAff[i]==SQLITE_AFF_BLOB );
+ zColAff[j--] = 0;
+ }while( j>=0 && zColAff[j]<=SQLITE_AFF_BLOB );
pTab->zColAff = zColAff;
}
- i = sqlite3Strlen30(zColAff);
+ assert( zColAff!=0 );
+ i = tdsqlite3Strlen30NN(zColAff);
if( i ){
if( iReg ){
- sqlite3VdbeAddOp4(v, OP_Affinity, iReg, i, 0, zColAff, i);
+ tdsqlite3VdbeAddOp4(v, OP_Affinity, iReg, i, 0, zColAff, i);
}else{
- sqlite3VdbeChangeP4(v, -1, zColAff, i);
+ tdsqlite3VdbeChangeP4(v, -1, zColAff, i);
}
}
}
@@ -110638,15 +122930,15 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){
** run without using a temporary table for the results of the SELECT.
*/
static int readsTable(Parse *p, int iDb, Table *pTab){
- Vdbe *v = sqlite3GetVdbe(p);
+ Vdbe *v = tdsqlite3GetVdbe(p);
int i;
- int iEnd = sqlite3VdbeCurrentAddr(v);
+ int iEnd = tdsqlite3VdbeCurrentAddr(v);
#ifndef SQLITE_OMIT_VIRTUALTABLE
- VTable *pVTab = IsVirtual(pTab) ? sqlite3GetVTable(p->db, pTab) : 0;
+ VTable *pVTab = IsVirtual(pTab) ? tdsqlite3GetVTable(p->db, pTab) : 0;
#endif
for(i=1; i<iEnd; i++){
- VdbeOp *pOp = sqlite3VdbeGetOp(v, i);
+ VdbeOp *pOp = tdsqlite3VdbeGetOp(v, i);
assert( pOp!=0 );
if( pOp->opcode==OP_OpenRead && pOp->p3==iDb ){
Index *pIndex;
@@ -110671,6 +122963,119 @@ static int readsTable(Parse *p, int iDb, Table *pTab){
return 0;
}
+/* This walker callback will compute the union of colFlags flags for all
+** referenced columns in a CHECK constraint or generated column expression.
+*/
+static int exprColumnFlagUnion(Walker *pWalker, Expr *pExpr){
+ if( pExpr->op==TK_COLUMN && pExpr->iColumn>=0 ){
+ assert( pExpr->iColumn < pWalker->u.pTab->nCol );
+ pWalker->eCode |= pWalker->u.pTab->aCol[pExpr->iColumn].colFlags;
+ }
+ return WRC_Continue;
+}
+
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+/*
+** All regular columns for table pTab have been puts into registers
+** starting with iRegStore. The registers that correspond to STORED
+** or VIRTUAL columns have not yet been initialized. This routine goes
+** back and computes the values for those columns based on the previously
+** computed normal columns.
+*/
+SQLITE_PRIVATE void tdsqlite3ComputeGeneratedColumns(
+ Parse *pParse, /* Parsing context */
+ int iRegStore, /* Register holding the first column */
+ Table *pTab /* The table */
+){
+ int i;
+ Walker w;
+ Column *pRedo;
+ int eProgress;
+ VdbeOp *pOp;
+
+ assert( pTab->tabFlags & TF_HasGenerated );
+ testcase( pTab->tabFlags & TF_HasVirtual );
+ testcase( pTab->tabFlags & TF_HasStored );
+
+ /* Before computing generated columns, first go through and make sure
+ ** that appropriate affinity has been applied to the regular columns
+ */
+ tdsqlite3TableAffinity(pParse->pVdbe, pTab, iRegStore);
+ if( (pTab->tabFlags & TF_HasStored)!=0
+ && (pOp = tdsqlite3VdbeGetOp(pParse->pVdbe,-1))->opcode==OP_Affinity
+ ){
+ /* Change the OP_Affinity argument to '@' (NONE) for all stored
+ ** columns. '@' is the no-op affinity and those columns have not
+ ** yet been computed. */
+ int ii, jj;
+ char *zP4 = pOp->p4.z;
+ assert( zP4!=0 );
+ assert( pOp->p4type==P4_DYNAMIC );
+ for(ii=jj=0; zP4[jj]; ii++){
+ if( pTab->aCol[ii].colFlags & COLFLAG_VIRTUAL ){
+ continue;
+ }
+ if( pTab->aCol[ii].colFlags & COLFLAG_STORED ){
+ zP4[jj] = SQLITE_AFF_NONE;
+ }
+ jj++;
+ }
+ }
+
+ /* Because there can be multiple generated columns that refer to one another,
+ ** this is a two-pass algorithm. On the first pass, mark all generated
+ ** columns as "not available".
+ */
+ for(i=0; i<pTab->nCol; i++){
+ if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){
+ testcase( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL );
+ testcase( pTab->aCol[i].colFlags & COLFLAG_STORED );
+ pTab->aCol[i].colFlags |= COLFLAG_NOTAVAIL;
+ }
+ }
+
+ w.u.pTab = pTab;
+ w.xExprCallback = exprColumnFlagUnion;
+ w.xSelectCallback = 0;
+ w.xSelectCallback2 = 0;
+
+ /* On the second pass, compute the value of each NOT-AVAILABLE column.
+ ** Companion code in the TK_COLUMN case of tdsqlite3ExprCodeTarget() will
+ ** compute dependencies and mark remove the COLSPAN_NOTAVAIL mark, as
+ ** they are needed.
+ */
+ pParse->iSelfTab = -iRegStore;
+ do{
+ eProgress = 0;
+ pRedo = 0;
+ for(i=0; i<pTab->nCol; i++){
+ Column *pCol = pTab->aCol + i;
+ if( (pCol->colFlags & COLFLAG_NOTAVAIL)!=0 ){
+ int x;
+ pCol->colFlags |= COLFLAG_BUSY;
+ w.eCode = 0;
+ tdsqlite3WalkExpr(&w, pCol->pDflt);
+ pCol->colFlags &= ~COLFLAG_BUSY;
+ if( w.eCode & COLFLAG_NOTAVAIL ){
+ pRedo = pCol;
+ continue;
+ }
+ eProgress = 1;
+ assert( pCol->colFlags & COLFLAG_GENERATED );
+ x = tdsqlite3TableColumnToStorage(pTab, i) + iRegStore;
+ tdsqlite3ExprCodeGeneratedColumn(pParse, pCol, x);
+ pCol->colFlags &= ~COLFLAG_NOTAVAIL;
+ }
+ }
+ }while( pRedo && eProgress );
+ if( pRedo ){
+ tdsqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pRedo->zName);
+ }
+ pParse->iSelfTab = 0;
+}
+#endif /* SQLITE_OMIT_GENERATED_COLUMNS */
+
+
#ifndef SQLITE_OMIT_AUTOINCREMENT
/*
** Locate or create an AutoincInfo structure associated with table pTab
@@ -110685,11 +123090,12 @@ static int readsTable(Parse *p, int iDb, Table *pTab){
** first use of table pTab. On 2nd and subsequent uses, the original
** AutoincInfo structure is used.
**
-** Three memory locations are allocated:
+** Four consecutive registers are allocated:
**
-** (1) Register to hold the name of the pTab table.
-** (2) Register to hold the maximum ROWID of pTab.
-** (3) Register to hold the rowid in sqlite_sequence of pTab
+** (1) The name of the pTab table.
+** (2) The maximum ROWID of pTab.
+** (3) The rowid in sqlite_sequence of pTab
+** (4) The original value of the max ROWID in pTab, or NULL if none
**
** The 2nd register is the one that is returned. That is all the
** insert routine needs to know about.
@@ -110700,16 +123106,31 @@ static int autoIncBegin(
Table *pTab /* The table we are writing to */
){
int memId = 0; /* Register holding maximum rowid */
+ assert( pParse->db->aDb[iDb].pSchema!=0 );
if( (pTab->tabFlags & TF_Autoincrement)!=0
- && (pParse->db->flags & SQLITE_Vacuum)==0
+ && (pParse->db->mDbFlags & DBFLAG_Vacuum)==0
){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
+ Parse *pToplevel = tdsqlite3ParseToplevel(pParse);
AutoincInfo *pInfo;
+ Table *pSeqTab = pParse->db->aDb[iDb].pSchema->pSeqTab;
+
+ /* Verify that the sqlite_sequence table exists and is an ordinary
+ ** rowid table with exactly two columns.
+ ** Ticket d8dc2b3a58cd5dc2918a1d4acb 2018-05-23 */
+ if( pSeqTab==0
+ || !HasRowid(pSeqTab)
+ || IsVirtual(pSeqTab)
+ || pSeqTab->nCol!=2
+ ){
+ pParse->nErr++;
+ pParse->rc = SQLITE_CORRUPT_SEQUENCE;
+ return 0;
+ }
pInfo = pToplevel->pAinc;
while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; }
if( pInfo==0 ){
- pInfo = sqlite3DbMallocRawNN(pParse->db, sizeof(*pInfo));
+ pInfo = tdsqlite3DbMallocRawNN(pParse->db, sizeof(*pInfo));
if( pInfo==0 ) return 0;
pInfo->pNext = pToplevel->pAinc;
pToplevel->pAinc = pInfo;
@@ -110717,7 +123138,7 @@ static int autoIncBegin(
pInfo->iDb = iDb;
pToplevel->nMem++; /* Register to hold name of table */
pInfo->regCtr = ++pToplevel->nMem; /* Max rowid register */
- pToplevel->nMem++; /* Rowid in sqlite_sequence */
+ pToplevel->nMem +=2; /* Rowid in sqlite_sequence + orig max val */
}
memId = pInfo->regCtr;
}
@@ -110728,9 +123149,9 @@ static int autoIncBegin(
** This routine generates code that will initialize all of the
** register used by the autoincrement tracker.
*/
-SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse){
+SQLITE_PRIVATE void tdsqlite3AutoincrementBegin(Parse *pParse){
AutoincInfo *p; /* Information about an AUTOINCREMENT */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
Db *pDb; /* Database only autoinc table */
int memId; /* Register holding max rowid */
Vdbe *v = pParse->pVdbe; /* VDBE under construction */
@@ -110738,40 +123159,46 @@ SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse){
/* This routine is never called during trigger-generation. It is
** only called from the top-level */
assert( pParse->pTriggerTab==0 );
- assert( sqlite3IsToplevel(pParse) );
+ assert( tdsqlite3IsToplevel(pParse) );
assert( v ); /* We failed long ago if this is not so */
for(p = pParse->pAinc; p; p = p->pNext){
static const int iLn = VDBE_OFFSET_LINENO(2);
static const VdbeOpList autoInc[] = {
/* 0 */ {OP_Null, 0, 0, 0},
- /* 1 */ {OP_Rewind, 0, 9, 0},
+ /* 1 */ {OP_Rewind, 0, 10, 0},
/* 2 */ {OP_Column, 0, 0, 0},
- /* 3 */ {OP_Ne, 0, 7, 0},
+ /* 3 */ {OP_Ne, 0, 9, 0},
/* 4 */ {OP_Rowid, 0, 0, 0},
/* 5 */ {OP_Column, 0, 1, 0},
- /* 6 */ {OP_Goto, 0, 9, 0},
- /* 7 */ {OP_Next, 0, 2, 0},
- /* 8 */ {OP_Integer, 0, 0, 0},
- /* 9 */ {OP_Close, 0, 0, 0}
+ /* 6 */ {OP_AddImm, 0, 0, 0},
+ /* 7 */ {OP_Copy, 0, 0, 0},
+ /* 8 */ {OP_Goto, 0, 11, 0},
+ /* 9 */ {OP_Next, 0, 2, 0},
+ /* 10 */ {OP_Integer, 0, 0, 0},
+ /* 11 */ {OP_Close, 0, 0, 0}
};
VdbeOp *aOp;
pDb = &db->aDb[p->iDb];
memId = p->regCtr;
- assert( sqlite3SchemaMutexHeld(db, 0, pDb->pSchema) );
- sqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenRead);
- sqlite3VdbeLoadString(v, memId-1, p->pTab->zName);
- aOp = sqlite3VdbeAddOpList(v, ArraySize(autoInc), autoInc, iLn);
+ assert( tdsqlite3SchemaMutexHeld(db, 0, pDb->pSchema) );
+ tdsqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenRead);
+ tdsqlite3VdbeLoadString(v, memId-1, p->pTab->zName);
+ aOp = tdsqlite3VdbeAddOpList(v, ArraySize(autoInc), autoInc, iLn);
if( aOp==0 ) break;
aOp[0].p2 = memId;
- aOp[0].p3 = memId+1;
+ aOp[0].p3 = memId+2;
aOp[2].p3 = memId;
aOp[3].p1 = memId-1;
aOp[3].p3 = memId;
aOp[3].p5 = SQLITE_JUMPIFNULL;
aOp[4].p2 = memId+1;
aOp[5].p3 = memId;
- aOp[8].p2 = memId;
+ aOp[6].p1 = memId;
+ aOp[7].p2 = memId+2;
+ aOp[7].p1 = memId;
+ aOp[10].p2 = memId;
+ if( pParse->nTab==0 ) pParse->nTab = 1;
}
}
@@ -110785,7 +123212,7 @@ SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse){
*/
static void autoIncStep(Parse *pParse, int memId, int regRowid){
if( memId>0 ){
- sqlite3VdbeAddOp2(pParse->pVdbe, OP_MemMax, memId, regRowid);
+ tdsqlite3VdbeAddOp2(pParse->pVdbe, OP_MemMax, memId, regRowid);
}
}
@@ -110799,7 +123226,7 @@ static void autoIncStep(Parse *pParse, int memId, int regRowid){
static SQLITE_NOINLINE void autoIncrementEnd(Parse *pParse){
AutoincInfo *p;
Vdbe *v = pParse->pVdbe;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
assert( v );
for(p = pParse->pAinc; p; p = p->pNext){
@@ -110816,10 +123243,12 @@ static SQLITE_NOINLINE void autoIncrementEnd(Parse *pParse){
int iRec;
int memId = p->regCtr;
- iRec = sqlite3GetTempReg(pParse);
- assert( sqlite3SchemaMutexHeld(db, 0, pDb->pSchema) );
- sqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenWrite);
- aOp = sqlite3VdbeAddOpList(v, ArraySize(autoIncEnd), autoIncEnd, iLn);
+ iRec = tdsqlite3GetTempReg(pParse);
+ assert( tdsqlite3SchemaMutexHeld(db, 0, pDb->pSchema) );
+ tdsqlite3VdbeAddOp3(v, OP_Le, memId+2, tdsqlite3VdbeCurrentAddr(v)+7, memId);
+ VdbeCoverage(v);
+ tdsqlite3OpenTable(pParse, 0, p->iDb, pDb->pSchema->pSeqTab, OP_OpenWrite);
+ aOp = tdsqlite3VdbeAddOpList(v, ArraySize(autoIncEnd), autoIncEnd, iLn);
if( aOp==0 ) break;
aOp[0].p1 = memId+1;
aOp[1].p2 = memId+1;
@@ -110828,10 +123257,10 @@ static SQLITE_NOINLINE void autoIncrementEnd(Parse *pParse){
aOp[3].p2 = iRec;
aOp[3].p3 = memId+1;
aOp[3].p5 = OPFLAG_APPEND;
- sqlite3ReleaseTempReg(pParse, iRec);
+ tdsqlite3ReleaseTempReg(pParse, iRec);
}
}
-SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse){
+SQLITE_PRIVATE void tdsqlite3AutoincrementEnd(Parse *pParse){
if( pParse->pAinc ) autoIncrementEnd(pParse);
}
#else
@@ -110950,17 +123379,17 @@ static int xferOptimization(
** end loop
** D: cleanup
*/
-SQLITE_PRIVATE void sqlite3Insert(
+SQLITE_PRIVATE void tdsqlite3Insert(
Parse *pParse, /* Parser context */
SrcList *pTabList, /* Name of table into which we are inserting */
Select *pSelect, /* A SELECT statement to use as the data source */
- IdList *pColumn, /* Column names corresponding to IDLIST. */
- int onError /* How to handle constraint errors */
+ IdList *pColumn, /* Column names corresponding to IDLIST, or NULL. */
+ int onError, /* How to handle constraint errors */
+ Upsert *pUpsert /* ON CONFLICT clauses for upsert, or NULL */
){
- sqlite3 *db; /* The main database structure */
+ tdsqlite3 *db; /* The main database structure */
Table *pTab; /* The table to insert into. aka TABLE */
- char *zTab; /* Name of the table into which we are inserting */
- int i, j, idx; /* Loop counters */
+ int i, j; /* Loop counters */
Vdbe *v; /* Generate code into this virtual machine */
Index *pIdx; /* For looping over indices of the table */
int nColumn; /* Number of columns in the data */
@@ -110979,6 +123408,7 @@ SQLITE_PRIVATE void sqlite3Insert(
u8 withoutRowid; /* 0 for normal table. 1 for WITHOUT ROWID table */
u8 bIdListInOrder; /* True if IDLIST is in table order */
ExprList *pList = 0; /* List of VALUES() to be inserted */
+ int iRegStore; /* Register in which to store next column */
/* Register allocations */
int regFromSelect = 0;/* Base register for data coming from SELECT */
@@ -110996,10 +123426,10 @@ SQLITE_PRIVATE void sqlite3Insert(
#endif
db = pParse->db;
- memset(&dest, 0, sizeof(dest));
if( pParse->nErr || db->mallocFailed ){
goto insert_cleanup;
}
+ dest.iSDParm = 0; /* Suppress a harmless compiler warning */
/* If the Select object is really just a simple VALUES() list with a
** single row (the common case) then keep that one row of values
@@ -111008,22 +123438,20 @@ SQLITE_PRIVATE void sqlite3Insert(
if( pSelect && (pSelect->selFlags & SF_Values)!=0 && pSelect->pPrior==0 ){
pList = pSelect->pEList;
pSelect->pEList = 0;
- sqlite3SelectDelete(db, pSelect);
+ tdsqlite3SelectDelete(db, pSelect);
pSelect = 0;
}
/* Locate the table into which we will be inserting new information.
*/
assert( pTabList->nSrc==1 );
- zTab = pTabList->a[0].zName;
- if( NEVER(zTab==0) ) goto insert_cleanup;
- pTab = sqlite3SrcListLookup(pParse, pTabList);
+ pTab = tdsqlite3SrcListLookup(pParse, pTabList);
if( pTab==0 ){
goto insert_cleanup;
}
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
assert( iDb<db->nDb );
- if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0,
+ if( tdsqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0,
db->aDb[iDb].zDbSName) ){
goto insert_cleanup;
}
@@ -111033,7 +123461,7 @@ SQLITE_PRIVATE void sqlite3Insert(
** inserted into is a view
*/
#ifndef SQLITE_OMIT_TRIGGER
- pTrigger = sqlite3TriggersExist(pParse, pTab, TK_INSERT, 0, &tmask);
+ pTrigger = tdsqlite3TriggersExist(pParse, pTab, TK_INSERT, 0, &tmask);
isView = pTab->pSelect!=0;
#else
# define pTrigger 0
@@ -111049,22 +123477,22 @@ SQLITE_PRIVATE void sqlite3Insert(
/* If pTab is really a view, make sure it has been initialized.
** ViewGetColumnNames() is a no-op if pTab is not a view.
*/
- if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+ if( tdsqlite3ViewGetColumnNames(pParse, pTab) ){
goto insert_cleanup;
}
/* Cannot insert into a read-only table.
*/
- if( sqlite3IsReadOnly(pParse, pTab, tmask) ){
+ if( tdsqlite3IsReadOnly(pParse, pTab, tmask) ){
goto insert_cleanup;
}
/* Allocate a VDBE
*/
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( v==0 ) goto insert_cleanup;
- if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
- sqlite3BeginWriteOperation(pParse, pSelect || pTrigger, iDb);
+ if( pParse->nested==0 ) tdsqlite3VdbeCountChanges(v);
+ tdsqlite3BeginWriteOperation(pParse, pSelect || pTrigger, iDb);
#ifndef SQLITE_OMIT_XFER_OPT
/* If the statement is of the form
@@ -111088,8 +123516,8 @@ SQLITE_PRIVATE void sqlite3Insert(
*/
regAutoinc = autoIncBegin(pParse, iDb, pTab);
- /* Allocate registers for holding the rowid of the new row,
- ** the content of the new row, and the assembled row record.
+ /* Allocate a block registers to hold the rowid and the values
+ ** for all columns of the new row.
*/
regRowid = regIns = pParse->nMem+1;
pParse->nMem += pTab->nCol + 1;
@@ -111108,30 +123536,46 @@ SQLITE_PRIVATE void sqlite3Insert(
** the index into IDLIST of the primary key column. ipkColumn is
** the index of the primary key as it appears in IDLIST, not as
** is appears in the original table. (The index of the INTEGER
- ** PRIMARY KEY in the original table is pTab->iPKey.)
+ ** PRIMARY KEY in the original table is pTab->iPKey.) After this
+ ** loop, if ipkColumn==(-1), that means that integer primary key
+ ** is unspecified, and hence the table is either WITHOUT ROWID or
+ ** it will automatically generated an integer primary key.
+ **
+ ** bIdListInOrder is true if the columns in IDLIST are in storage
+ ** order. This enables an optimization that avoids shuffling the
+ ** columns into storage order. False negatives are harmless,
+ ** but false positives will cause database corruption.
*/
- bIdListInOrder = (pTab->tabFlags & TF_OOOHidden)==0;
+ bIdListInOrder = (pTab->tabFlags & (TF_OOOHidden|TF_HasStored))==0;
if( pColumn ){
for(i=0; i<pColumn->nId; i++){
pColumn->a[i].idx = -1;
}
for(i=0; i<pColumn->nId; i++){
for(j=0; j<pTab->nCol; j++){
- if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){
+ if( tdsqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){
pColumn->a[i].idx = j;
if( i!=j ) bIdListInOrder = 0;
if( j==pTab->iPKey ){
ipkColumn = i; assert( !withoutRowid );
}
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ if( pTab->aCol[j].colFlags & (COLFLAG_STORED|COLFLAG_VIRTUAL) ){
+ tdsqlite3ErrorMsg(pParse,
+ "cannot INSERT into generated column \"%s\"",
+ pTab->aCol[j].zName);
+ goto insert_cleanup;
+ }
+#endif
break;
}
}
if( j>=pTab->nCol ){
- if( sqlite3IsRowid(pColumn->a[i].zName) && !withoutRowid ){
+ if( tdsqlite3IsRowid(pColumn->a[i].zName) && !withoutRowid ){
ipkColumn = i;
bIdListInOrder = 0;
}else{
- sqlite3ErrorMsg(pParse, "table %S has no column named %s",
+ tdsqlite3ErrorMsg(pParse, "table %S has no column named %s",
pTabList, 0, pColumn->a[i].zName);
pParse->checkSchema = 1;
goto insert_cleanup;
@@ -111153,16 +123597,16 @@ SQLITE_PRIVATE void sqlite3Insert(
int rc; /* Result code */
regYield = ++pParse->nMem;
- addrTop = sqlite3VdbeCurrentAddr(v) + 1;
- sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop);
- sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield);
+ addrTop = tdsqlite3VdbeCurrentAddr(v) + 1;
+ tdsqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop);
+ tdsqlite3SelectDestInit(&dest, SRT_Coroutine, regYield);
dest.iSdst = bIdListInOrder ? regData : 0;
dest.nSdst = pTab->nCol;
- rc = sqlite3Select(pParse, pSelect, &dest);
+ rc = tdsqlite3Select(pParse, pSelect, &dest);
regFromSelect = dest.iSdst;
if( rc || db->mallocFailed || pParse->nErr ) goto insert_cleanup;
- sqlite3VdbeEndCoroutine(v, regYield);
- sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */
+ tdsqlite3VdbeEndCoroutine(v, regYield);
+ tdsqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */
assert( pSelect->pEList );
nColumn = pSelect->pEList->nExpr;
@@ -111195,17 +123639,17 @@ SQLITE_PRIVATE void sqlite3Insert(
int addrL; /* Label "L" */
srcTab = pParse->nTab++;
- regRec = sqlite3GetTempReg(pParse);
- regTempRowid = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp2(v, OP_OpenEphemeral, srcTab, nColumn);
- addrL = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regFromSelect, nColumn, regRec);
- sqlite3VdbeAddOp2(v, OP_NewRowid, srcTab, regTempRowid);
- sqlite3VdbeAddOp3(v, OP_Insert, srcTab, regRec, regTempRowid);
- sqlite3VdbeGoto(v, addrL);
- sqlite3VdbeJumpHere(v, addrL);
- sqlite3ReleaseTempReg(pParse, regRec);
- sqlite3ReleaseTempReg(pParse, regTempRowid);
+ regRec = tdsqlite3GetTempReg(pParse);
+ regTempRowid = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, srcTab, nColumn);
+ addrL = tdsqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regFromSelect, nColumn, regRec);
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, srcTab, regTempRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, srcTab, regRec, regTempRowid);
+ tdsqlite3VdbeGoto(v, addrL);
+ tdsqlite3VdbeJumpHere(v, addrL);
+ tdsqlite3ReleaseTempReg(pParse, regRec);
+ tdsqlite3ReleaseTempReg(pParse, regTempRowid);
}
}else{
/* This is the case if the data for the INSERT is coming from a
@@ -111218,7 +123662,7 @@ SQLITE_PRIVATE void sqlite3Insert(
assert( useTempTable==0 );
if( pList ){
nColumn = pList->nExpr;
- if( sqlite3ResolveExprListNames(&sNC, pList) ){
+ if( tdsqlite3ResolveExprListNames(&sNC, pList) ){
goto insert_cleanup;
}
}else{
@@ -111232,45 +123676,89 @@ SQLITE_PRIVATE void sqlite3Insert(
*/
if( pColumn==0 && nColumn>0 ){
ipkColumn = pTab->iPKey;
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ if( ipkColumn>=0 && (pTab->tabFlags & TF_HasGenerated)!=0 ){
+ testcase( pTab->tabFlags & TF_HasVirtual );
+ testcase( pTab->tabFlags & TF_HasStored );
+ for(i=ipkColumn-1; i>=0; i--){
+ if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){
+ testcase( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL );
+ testcase( pTab->aCol[i].colFlags & COLFLAG_STORED );
+ ipkColumn--;
+ }
+ }
+ }
+#endif
}
/* Make sure the number of columns in the source data matches the number
** of columns to be inserted into the table.
*/
for(i=0; i<pTab->nCol; i++){
- nHidden += (IsHiddenColumn(&pTab->aCol[i]) ? 1 : 0);
+ if( pTab->aCol[i].colFlags & COLFLAG_NOINSERT ) nHidden++;
}
if( pColumn==0 && nColumn && nColumn!=(pTab->nCol-nHidden) ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"table %S has %d columns but %d values were supplied",
pTabList, 0, pTab->nCol-nHidden, nColumn);
goto insert_cleanup;
}
if( pColumn!=0 && nColumn!=pColumn->nId ){
- sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId);
+ tdsqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId);
goto insert_cleanup;
}
/* Initialize the count of rows to be inserted
*/
- if( db->flags & SQLITE_CountRows ){
+ if( (db->flags & SQLITE_CountRows)!=0
+ && !pParse->nested
+ && !pParse->pTriggerTab
+ ){
regRowCount = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
}
/* If this is not a view, open the table and and all indices */
if( !isView ){
int nIdx;
- nIdx = sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, -1, 0,
+ nIdx = tdsqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, -1, 0,
&iDataCur, &iIdxCur);
- aRegIdx = sqlite3DbMallocRawNN(db, sizeof(int)*(nIdx+1));
+ aRegIdx = tdsqlite3DbMallocRawNN(db, sizeof(int)*(nIdx+2));
if( aRegIdx==0 ){
goto insert_cleanup;
}
- for(i=0; i<nIdx; i++){
+ for(i=0, pIdx=pTab->pIndex; i<nIdx; pIdx=pIdx->pNext, i++){
+ assert( pIdx );
aRegIdx[i] = ++pParse->nMem;
+ pParse->nMem += pIdx->nColumn;
+ }
+ aRegIdx[i] = ++pParse->nMem; /* Register to store the table record */
+ }
+#ifndef SQLITE_OMIT_UPSERT
+ if( pUpsert ){
+ if( IsVirtual(pTab) ){
+ tdsqlite3ErrorMsg(pParse, "UPSERT not implemented for virtual table \"%s\"",
+ pTab->zName);
+ goto insert_cleanup;
+ }
+ if( pTab->pSelect ){
+ tdsqlite3ErrorMsg(pParse, "cannot UPSERT a view");
+ goto insert_cleanup;
+ }
+ if( tdsqlite3HasExplicitNulls(pParse, pUpsert->pUpsertTarget) ){
+ goto insert_cleanup;
+ }
+ pTabList->a[0].iCursor = iDataCur;
+ pUpsert->pUpsertSrc = pTabList;
+ pUpsert->regData = regData;
+ pUpsert->iDataCur = iDataCur;
+ pUpsert->iIdxCur = iIdxCur;
+ if( pUpsert->pUpsertTarget ){
+ tdsqlite3UpsertAnalyzeTarget(pParse, pTabList, pUpsert);
}
}
+#endif
+
/* This is the top of the main insertion loop */
if( useTempTable ){
@@ -111283,8 +123771,8 @@ SQLITE_PRIVATE void sqlite3Insert(
** end loop
** D: ...
*/
- addrInsTop = sqlite3VdbeAddOp1(v, OP_Rewind, srcTab); VdbeCoverage(v);
- addrCont = sqlite3VdbeCurrentAddr(v);
+ addrInsTop = tdsqlite3VdbeAddOp1(v, OP_Rewind, srcTab); VdbeCoverage(v);
+ addrCont = tdsqlite3VdbeCurrentAddr(v);
}else if( pSelect ){
/* This block codes the top of loop only. The complete loop is the
** following pseudocode (template 3):
@@ -111294,15 +123782,96 @@ SQLITE_PRIVATE void sqlite3Insert(
** goto C
** D: ...
*/
- addrInsTop = addrCont = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm);
+ tdsqlite3VdbeReleaseRegisters(pParse, regData, pTab->nCol, 0, 0);
+ addrInsTop = addrCont = tdsqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm);
VdbeCoverage(v);
+ if( ipkColumn>=0 ){
+ /* tag-20191021-001: If the INTEGER PRIMARY KEY is being generated by the
+ ** SELECT, go ahead and copy the value into the rowid slot now, so that
+ ** the value does not get overwritten by a NULL at tag-20191021-002. */
+ tdsqlite3VdbeAddOp2(v, OP_Copy, regFromSelect+ipkColumn, regRowid);
+ }
+ }
+
+ /* Compute data for ordinary columns of the new entry. Values
+ ** are written in storage order into registers starting with regData.
+ ** Only ordinary columns are computed in this loop. The rowid
+ ** (if there is one) is computed later and generated columns are
+ ** computed after the rowid since they might depend on the value
+ ** of the rowid.
+ */
+ nHidden = 0;
+ iRegStore = regData; assert( regData==regRowid+1 );
+ for(i=0; i<pTab->nCol; i++, iRegStore++){
+ int k;
+ u32 colFlags;
+ assert( i>=nHidden );
+ if( i==pTab->iPKey ){
+ /* tag-20191021-002: References to the INTEGER PRIMARY KEY are filled
+ ** using the rowid. So put a NULL in the IPK slot of the record to avoid
+ ** using excess space. The file format definition requires this extra
+ ** NULL - we cannot optimize further by skipping the column completely */
+ tdsqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore);
+ continue;
+ }
+ if( ((colFlags = pTab->aCol[i].colFlags) & COLFLAG_NOINSERT)!=0 ){
+ nHidden++;
+ if( (colFlags & COLFLAG_VIRTUAL)!=0 ){
+ /* Virtual columns do not participate in OP_MakeRecord. So back up
+ ** iRegStore by one slot to compensate for the iRegStore++ in the
+ ** outer for() loop */
+ iRegStore--;
+ continue;
+ }else if( (colFlags & COLFLAG_STORED)!=0 ){
+ /* Stored columns are computed later. But if there are BEFORE
+ ** triggers, the slots used for stored columns will be OP_Copy-ed
+ ** to a second block of registers, so the register needs to be
+ ** initialized to NULL to avoid an uninitialized register read */
+ if( tmask & TRIGGER_BEFORE ){
+ tdsqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore);
+ }
+ continue;
+ }else if( pColumn==0 ){
+ /* Hidden columns that are not explicitly named in the INSERT
+ ** get there default value */
+ tdsqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore);
+ continue;
+ }
+ }
+ if( pColumn ){
+ for(j=0; j<pColumn->nId && pColumn->a[j].idx!=i; j++){}
+ if( j>=pColumn->nId ){
+ /* A column not named in the insert column list gets its
+ ** default value */
+ tdsqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore);
+ continue;
+ }
+ k = j;
+ }else if( nColumn==0 ){
+ /* This is INSERT INTO ... DEFAULT VALUES. Load the default value. */
+ tdsqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore);
+ continue;
+ }else{
+ k = i - nHidden;
+ }
+
+ if( useTempTable ){
+ tdsqlite3VdbeAddOp3(v, OP_Column, srcTab, k, iRegStore);
+ }else if( pSelect ){
+ if( regFromSelect!=regData ){
+ tdsqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+k, iRegStore);
+ }
+ }else{
+ tdsqlite3ExprCode(pParse, pList->a[k].pExpr, iRegStore);
+ }
}
+
/* Run the BEFORE and INSTEAD OF triggers, if there are any
*/
- endOfLoop = sqlite3VdbeMakeLabel(v);
+ endOfLoop = tdsqlite3VdbeMakeLabel(pParse);
if( tmask & TRIGGER_BEFORE ){
- int regCols = sqlite3GetTempRange(pParse, pTab->nCol+1);
+ int regCols = tdsqlite3GetTempRange(pParse, pTab->nCol+1);
/* build the NEW.* reference row. Note that if there is an INTEGER
** PRIMARY KEY into which a NULL is being inserted, that NULL will be
@@ -111311,20 +123880,20 @@ SQLITE_PRIVATE void sqlite3Insert(
** not happened yet) so we substitute a rowid of -1
*/
if( ipkColumn<0 ){
- sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, -1, regCols);
}else{
int addr1;
assert( !withoutRowid );
if( useTempTable ){
- sqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regCols);
+ tdsqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regCols);
}else{
assert( pSelect==0 ); /* Otherwise useTempTable is true */
- sqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regCols);
+ tdsqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regCols);
}
- addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, regCols); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols);
- sqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); VdbeCoverage(v);
+ addr1 = tdsqlite3VdbeAddOp1(v, OP_NotNull, regCols); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, -1, regCols);
+ tdsqlite3VdbeJumpHere(v, addr1);
+ tdsqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); VdbeCoverage(v);
}
/* Cannot have triggers on a virtual table. If it were possible,
@@ -111332,25 +123901,21 @@ SQLITE_PRIVATE void sqlite3Insert(
*/
assert( !IsVirtual(pTab) );
- /* Create the new column data
- */
- for(i=j=0; i<pTab->nCol; i++){
- if( pColumn ){
- for(j=0; j<pColumn->nId; j++){
- if( pColumn->a[j].idx==i ) break;
- }
- }
- if( (!useTempTable && !pList) || (pColumn && j>=pColumn->nId)
- || (pColumn==0 && IsOrdinaryHiddenColumn(&pTab->aCol[i])) ){
- sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regCols+i+1);
- }else if( useTempTable ){
- sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, regCols+i+1);
- }else{
- assert( pSelect==0 ); /* Otherwise useTempTable is true */
- sqlite3ExprCodeAndCache(pParse, pList->a[j].pExpr, regCols+i+1);
- }
- if( pColumn==0 && !IsOrdinaryHiddenColumn(&pTab->aCol[i]) ) j++;
+ /* Copy the new data already generated. */
+ assert( pTab->nNVCol>0 );
+ tdsqlite3VdbeAddOp3(v, OP_Copy, regRowid+1, regCols+1, pTab->nNVCol-1);
+
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ /* Compute the new value for generated columns after all other
+ ** columns have already been computed. This must be done after
+ ** computing the ROWID in case one of the generated columns
+ ** refers to the ROWID. */
+ if( pTab->tabFlags & TF_HasGenerated ){
+ testcase( pTab->tabFlags & TF_HasVirtual );
+ testcase( pTab->tabFlags & TF_HasStored );
+ tdsqlite3ComputeGeneratedColumns(pParse, regCols+1, pTab);
}
+#endif
/* If this is an INSERT on a view with an INSTEAD OF INSERT trigger,
** do not attempt any conversions before assembling the record.
@@ -111358,39 +123923,34 @@ SQLITE_PRIVATE void sqlite3Insert(
** table column affinities.
*/
if( !isView ){
- sqlite3TableAffinity(v, pTab, regCols+1);
+ tdsqlite3TableAffinity(v, pTab, regCols+1);
}
/* Fire BEFORE or INSTEAD OF triggers */
- sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE,
+ tdsqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE,
pTab, regCols-pTab->nCol-1, onError, endOfLoop);
- sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol+1);
+ tdsqlite3ReleaseTempRange(pParse, regCols, pTab->nCol+1);
}
- /* Compute the content of the next row to insert into a range of
- ** registers beginning at regIns.
- */
if( !isView ){
if( IsVirtual(pTab) ){
/* The row that the VUpdate opcode will delete: none */
- sqlite3VdbeAddOp2(v, OP_Null, 0, regIns);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, regIns);
}
if( ipkColumn>=0 ){
+ /* Compute the new rowid */
if( useTempTable ){
- sqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regRowid);
}else if( pSelect ){
- sqlite3VdbeAddOp2(v, OP_Copy, regFromSelect+ipkColumn, regRowid);
+ /* Rowid already initialized at tag-20191021-001 */
}else{
- VdbeOp *pOp;
- sqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regRowid);
- pOp = sqlite3VdbeGetOp(v, -1);
- if( ALWAYS(pOp) && pOp->opcode==OP_Null && !IsVirtual(pTab) ){
+ Expr *pIpk = pList->a[ipkColumn].pExpr;
+ if( pIpk->op==TK_NULL && !IsVirtual(pTab) ){
+ tdsqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc);
appendFlag = 1;
- pOp->opcode = OP_NewRowid;
- pOp->p1 = iDataCur;
- pOp->p2 = regRowid;
- pOp->p3 = regAutoinc;
+ }else{
+ tdsqlite3ExprCode(pParse, pList->a[ipkColumn].pExpr, regRowid);
}
}
/* If the PRIMARY KEY expression is NULL, then use OP_NewRowid
@@ -111399,117 +123959,100 @@ SQLITE_PRIVATE void sqlite3Insert(
if( !appendFlag ){
int addr1;
if( !IsVirtual(pTab) ){
- addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, regRowid); VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc);
- sqlite3VdbeJumpHere(v, addr1);
+ addr1 = tdsqlite3VdbeAddOp1(v, OP_NotNull, regRowid); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc);
+ tdsqlite3VdbeJumpHere(v, addr1);
}else{
- addr1 = sqlite3VdbeCurrentAddr(v);
- sqlite3VdbeAddOp2(v, OP_IsNull, regRowid, addr1+2); VdbeCoverage(v);
+ addr1 = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, regRowid, addr1+2); VdbeCoverage(v);
}
- sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid); VdbeCoverage(v);
}
}else if( IsVirtual(pTab) || withoutRowid ){
- sqlite3VdbeAddOp2(v, OP_Null, 0, regRowid);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, regRowid);
}else{
- sqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc);
+ tdsqlite3VdbeAddOp3(v, OP_NewRowid, iDataCur, regRowid, regAutoinc);
appendFlag = 1;
}
autoIncStep(pParse, regAutoinc, regRowid);
- /* Compute data for all columns of the new entry, beginning
- ** with the first column.
- */
- nHidden = 0;
- for(i=0; i<pTab->nCol; i++){
- int iRegStore = regRowid+1+i;
- if( i==pTab->iPKey ){
- /* The value of the INTEGER PRIMARY KEY column is always a NULL.
- ** Whenever this column is read, the rowid will be substituted
- ** in its place. Hence, fill this column with a NULL to avoid
- ** taking up data space with information that will never be used.
- ** As there may be shallow copies of this value, make it a soft-NULL */
- sqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore);
- continue;
- }
- if( pColumn==0 ){
- if( IsHiddenColumn(&pTab->aCol[i]) ){
- j = -1;
- nHidden++;
- }else{
- j = i - nHidden;
- }
- }else{
- for(j=0; j<pColumn->nId; j++){
- if( pColumn->a[j].idx==i ) break;
- }
- }
- if( j<0 || nColumn==0 || (pColumn && j>=pColumn->nId) ){
- sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore);
- }else if( useTempTable ){
- sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, iRegStore);
- }else if( pSelect ){
- if( regFromSelect!=regData ){
- sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+j, iRegStore);
- }
- }else{
- sqlite3ExprCode(pParse, pList->a[j].pExpr, iRegStore);
- }
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ /* Compute the new value for generated columns after all other
+ ** columns have already been computed. This must be done after
+ ** computing the ROWID in case one of the generated columns
+ ** is derived from the INTEGER PRIMARY KEY. */
+ if( pTab->tabFlags & TF_HasGenerated ){
+ tdsqlite3ComputeGeneratedColumns(pParse, regRowid+1, pTab);
}
+#endif
/* Generate code to check constraints and generate index keys and
** do the insertion.
*/
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( IsVirtual(pTab) ){
- const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
- sqlite3VtabMakeWritable(pParse, pTab);
- sqlite3VdbeAddOp4(v, OP_VUpdate, 1, pTab->nCol+2, regIns, pVTab, P4_VTAB);
- sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
- sqlite3MayAbort(pParse);
+ const char *pVTab = (const char *)tdsqlite3GetVTable(db, pTab);
+ tdsqlite3VtabMakeWritable(pParse, pTab);
+ tdsqlite3VdbeAddOp4(v, OP_VUpdate, 1, pTab->nCol+2, regIns, pVTab, P4_VTAB);
+ tdsqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
+ tdsqlite3MayAbort(pParse);
}else
#endif
{
int isReplace; /* Set to true if constraints may cause a replace */
- sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
- regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0
+ int bUseSeek; /* True to use OPFLAG_SEEKRESULT */
+ tdsqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
+ regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert
+ );
+ tdsqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0);
+
+ /* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE
+ ** constraints or (b) there are no triggers and this table is not a
+ ** parent table in a foreign key constraint. It is safe to set the
+ ** flag in the second case as if any REPLACE constraint is hit, an
+ ** OP_Delete or OP_IdxDelete instruction will be executed on each
+ ** cursor that is disturbed. And these instructions both clear the
+ ** VdbeCursor.seekResult variable, disabling the OPFLAG_USESEEKRESULT
+ ** functionality. */
+ bUseSeek = (isReplace==0 || !tdsqlite3VdbeHasSubProgram(v));
+ tdsqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur,
+ regIns, aRegIdx, 0, appendFlag, bUseSeek
);
- sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0);
- sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur,
- regIns, aRegIdx, 0, appendFlag, isReplace==0);
}
}
/* Update the count of rows that are inserted
*/
- if( (db->flags & SQLITE_CountRows)!=0 ){
- sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
+ if( regRowCount ){
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
}
if( pTrigger ){
/* Code AFTER triggers */
- sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER,
+ tdsqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER,
pTab, regData-2-pTab->nCol, onError, endOfLoop);
}
/* The bottom of the main insertion loop, if the data source
** is a SELECT statement.
*/
- sqlite3VdbeResolveLabel(v, endOfLoop);
+ tdsqlite3VdbeResolveLabel(v, endOfLoop);
if( useTempTable ){
- sqlite3VdbeAddOp2(v, OP_Next, srcTab, addrCont); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addrInsTop);
- sqlite3VdbeAddOp1(v, OP_Close, srcTab);
+ tdsqlite3VdbeAddOp2(v, OP_Next, srcTab, addrCont); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addrInsTop);
+ tdsqlite3VdbeAddOp1(v, OP_Close, srcTab);
}else if( pSelect ){
- sqlite3VdbeGoto(v, addrCont);
- sqlite3VdbeJumpHere(v, addrInsTop);
- }
-
- if( !IsVirtual(pTab) && !isView ){
- /* Close all tables opened */
- if( iDataCur<iIdxCur ) sqlite3VdbeAddOp1(v, OP_Close, iDataCur);
- for(idx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
- sqlite3VdbeAddOp1(v, OP_Close, idx+iIdxCur);
+ tdsqlite3VdbeGoto(v, addrCont);
+#ifdef SQLITE_DEBUG
+ /* If we are jumping back to an OP_Yield that is preceded by an
+ ** OP_ReleaseReg, set the p5 flag on the OP_Goto so that the
+ ** OP_ReleaseReg will be included in the loop. */
+ if( tdsqlite3VdbeGetOp(v, addrCont-1)->opcode==OP_ReleaseReg ){
+ assert( tdsqlite3VdbeGetOp(v, addrCont)->opcode==OP_Yield );
+ tdsqlite3VdbeChangeP5(v, 1);
}
+#endif
+ tdsqlite3VdbeJumpHere(v, addrInsTop);
}
insert_end:
@@ -111518,26 +124061,27 @@ insert_end:
** autoincrement tables.
*/
if( pParse->nested==0 && pParse->pTriggerTab==0 ){
- sqlite3AutoincrementEnd(pParse);
+ tdsqlite3AutoincrementEnd(pParse);
}
/*
** Return the number of rows inserted. If this routine is
- ** generating code because of a call to sqlite3NestedParse(), do not
+ ** generating code because of a call to tdsqlite3NestedParse(), do not
** invoke the callback function.
*/
- if( (db->flags&SQLITE_CountRows) && !pParse->nested && !pParse->pTriggerTab ){
- sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
- sqlite3VdbeSetNumCols(v, 1);
- sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC);
+ if( regRowCount ){
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
+ tdsqlite3VdbeSetNumCols(v, 1);
+ tdsqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC);
}
insert_cleanup:
- sqlite3SrcListDelete(db, pTabList);
- sqlite3ExprListDelete(db, pList);
- sqlite3SelectDelete(db, pSelect);
- sqlite3IdListDelete(db, pColumn);
- sqlite3DbFree(db, aRegIdx);
+ tdsqlite3SrcListDelete(db, pTabList);
+ tdsqlite3ExprListDelete(db, pList);
+ tdsqlite3UpsertDelete(db, pUpsert);
+ tdsqlite3SelectDelete(db, pSelect);
+ tdsqlite3IdListDelete(db, pColumn);
+ tdsqlite3DbFree(db, aRegIdx);
}
/* Make sure "isView" and other macros defined above are undefined. Otherwise
@@ -111554,14 +124098,15 @@ insert_cleanup:
#endif
/*
-** Meanings of bits in of pWalker->eCode for checkConstraintUnchanged()
+** Meanings of bits in of pWalker->eCode for
+** tdsqlite3ExprReferencesUpdatedColumn()
*/
#define CKCNSTRNT_COLUMN 0x01 /* CHECK constraint uses a changing column */
#define CKCNSTRNT_ROWID 0x02 /* CHECK constraint references the ROWID */
-/* This is the Walker callback from checkConstraintUnchanged(). Set
-** bit 0x01 of pWalker->eCode if
-** pWalker->eCode to 0 if this expression node references any of the
+/* This is the Walker callback from tdsqlite3ExprReferencesUpdatedColumn().
+* Set bit 0x01 of pWalker->eCode if pWalker->eCode to 0 and if this
+** expression node references any of the
** columns that are being modifed by an UPDATE statement.
*/
static int checkConstraintExprNode(Walker *pWalker, Expr *pExpr){
@@ -111583,18 +124128,27 @@ static int checkConstraintExprNode(Walker *pWalker, Expr *pExpr){
** only columns that are modified by the UPDATE are those for which
** aiChng[i]>=0, and also the ROWID is modified if chngRowid is true.
**
-** Return true if CHECK constraint pExpr does not use any of the
+** Return true if CHECK constraint pExpr uses any of the
** changing columns (or the rowid if it is changing). In other words,
-** return true if this CHECK constraint can be skipped when validating
+** return true if this CHECK constraint must be validated for
** the new row in the UPDATE statement.
+**
+** 2018-09-15: pExpr might also be an expression for an index-on-expressions.
+** The operation of this routine is the same - return true if an only if
+** the expression uses one or more of columns identified by the second and
+** third arguments.
*/
-static int checkConstraintUnchanged(Expr *pExpr, int *aiChng, int chngRowid){
+SQLITE_PRIVATE int tdsqlite3ExprReferencesUpdatedColumn(
+ Expr *pExpr, /* The expression to be checked */
+ int *aiChng, /* aiChng[x]>=0 if column x changed by the UPDATE */
+ int chngRowid /* True if UPDATE changes the rowid */
+){
Walker w;
memset(&w, 0, sizeof(w));
w.eCode = 0;
w.xExprCallback = checkConstraintExprNode;
w.u.aiCol = aiChng;
- sqlite3WalkExpr(&w, pExpr);
+ tdsqlite3WalkExpr(&w, pExpr);
if( !chngRowid ){
testcase( (w.eCode & CKCNSTRNT_ROWID)!=0 );
w.eCode &= ~CKCNSTRNT_ROWID;
@@ -111603,7 +124157,7 @@ static int checkConstraintUnchanged(Expr *pExpr, int *aiChng, int chngRowid){
testcase( w.eCode==CKCNSTRNT_COLUMN );
testcase( w.eCode==CKCNSTRNT_ROWID );
testcase( w.eCode==(CKCNSTRNT_ROWID|CKCNSTRNT_COLUMN) );
- return !w.eCode;
+ return w.eCode!=0;
}
/*
@@ -111641,6 +124195,14 @@ static int checkConstraintUnchanged(Expr *pExpr, int *aiChng, int chngRowid){
** the same as the order of indices on the linked list of indices
** at pTab->pIndex.
**
+** (2019-05-07) The generated code also creates a new record for the
+** main table, if pTab is a rowid table, and stores that record in the
+** register identified by aRegIdx[nIdx] - in other words in the first
+** entry of aRegIdx[] past the last index. It is important that the
+** record be generated during constraint checks to avoid affinity changes
+** to the register content that occur after constraint checks but before
+** the new record is inserted.
+**
** The caller must have already opened writeable cursors on the main
** table and all applicable indices (that is to say, all indices for which
** aRegIdx[] is not zero). iDataCur is the cursor for the main table when
@@ -111657,12 +124219,12 @@ static int checkConstraintUnchanged(Expr *pExpr, int *aiChng, int chngRowid){
** Constraint type Action What Happens
** --------------- ---------- ----------------------------------------
** any ROLLBACK The current transaction is rolled back and
-** sqlite3_step() returns immediately with a
+** tdsqlite3_step() returns immediately with a
** return code of SQLITE_CONSTRAINT.
**
** any ABORT Back out changes from the current command
** only (do not do a complete rollback) then
-** cause sqlite3_step() to return immediately
+** cause tdsqlite3_step() to return immediately
** with SQLITE_CONSTRAINT.
**
** any FAIL Sqlite3_step() returns immediately with a
@@ -111689,7 +124251,7 @@ static int checkConstraintUnchanged(Expr *pExpr, int *aiChng, int chngRowid){
** is used. Or if pParse->onError==OE_Default then the onError value
** for the constraint is used.
*/
-SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
+SQLITE_PRIVATE void tdsqlite3GenerateConstraintChecks(
Parse *pParse, /* The parser context */
Table *pTab, /* The table being inserted or updated */
int *aRegIdx, /* Use register aRegIdx[i] for index i. 0 for unused */
@@ -111701,28 +124263,37 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
u8 overrideError, /* Override onError to this if not OE_Default */
int ignoreDest, /* Jump to this label on an OE_Ignore resolution */
int *pbMayReplace, /* OUT: Set to true if constraint may cause a replace */
- int *aiChng /* column i is unchanged if aiChng[i]<0 */
+ int *aiChng, /* column i is unchanged if aiChng[i]<0 */
+ Upsert *pUpsert /* ON CONFLICT clauses, if any. NULL otherwise */
){
Vdbe *v; /* VDBE under constrution */
Index *pIdx; /* Pointer to one of the indices */
Index *pPk = 0; /* The PRIMARY KEY index */
- sqlite3 *db; /* Database connection */
+ tdsqlite3 *db; /* Database connection */
int i; /* loop counter */
int ix; /* Index loop counter */
int nCol; /* Number of columns */
int onError; /* Conflict resolution strategy */
- int addr1; /* Address of jump instruction */
int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */
int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */
- int ipkTop = 0; /* Top of the rowid change constraint check */
- int ipkBottom = 0; /* Bottom of the rowid change constraint check */
+ Index *pUpIdx = 0; /* Index to which to apply the upsert */
u8 isUpdate; /* True if this is an UPDATE operation */
u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */
- int regRowid = -1; /* Register holding ROWID value */
+ int upsertBypass = 0; /* Address of Goto to bypass upsert subroutine */
+ int upsertJump = 0; /* Address of Goto that jumps into upsert subroutine */
+ int ipkTop = 0; /* Top of the IPK uniqueness check */
+ int ipkBottom = 0; /* OP_Goto at the end of the IPK uniqueness check */
+ /* Variables associated with retesting uniqueness constraints after
+ ** replace triggers fire have run */
+ int regTrigCnt; /* Register used to count replace trigger invocations */
+ int addrRecheck = 0; /* Jump here to recheck all uniqueness constraints */
+ int lblRecheckOk = 0; /* Each recheck jumps to this label if it passes */
+ Trigger *pTrigger; /* List of DELETE triggers on the table pTab */
+ int nReplaceTrig = 0; /* Number of replace triggers coded */
isUpdate = regOldData!=0;
db = pParse->db;
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
assert( v!=0 );
assert( pTab->pSelect==0 ); /* This table is not a VIEW */
nCol = pTab->nCol;
@@ -111735,7 +124306,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
pPk = 0;
nPkField = 1;
}else{
- pPk = sqlite3PrimaryKeyIndex(pTab);
+ pPk = tdsqlite3PrimaryKeyIndex(pTab);
nPkField = pPk->nKeyCol;
}
@@ -111745,89 +124316,233 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
/* Test all NOT NULL constraints.
*/
- for(i=0; i<nCol; i++){
- if( i==pTab->iPKey ){
- continue; /* ROWID is never NULL */
- }
- if( aiChng && aiChng[i]<0 ){
- /* Don't bother checking for NOT NULL on columns that do not change */
- continue;
- }
- onError = pTab->aCol[i].notNull;
- if( onError==OE_None ) continue; /* This column is allowed to be NULL */
- if( overrideError!=OE_Default ){
- onError = overrideError;
- }else if( onError==OE_Default ){
- onError = OE_Abort;
- }
- if( onError==OE_Replace && pTab->aCol[i].pDflt==0 ){
- onError = OE_Abort;
- }
- assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail
- || onError==OE_Ignore || onError==OE_Replace );
- switch( onError ){
- case OE_Abort:
- sqlite3MayAbort(pParse);
- /* Fall through */
- case OE_Rollback:
- case OE_Fail: {
- char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName,
- pTab->aCol[i].zName);
- sqlite3VdbeAddOp4(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError,
- regNewData+1+i, zMsg, P4_DYNAMIC);
- sqlite3VdbeChangeP5(v, P5_ConstraintNotNull);
- VdbeCoverage(v);
- break;
- }
- case OE_Ignore: {
- sqlite3VdbeAddOp2(v, OP_IsNull, regNewData+1+i, ignoreDest);
- VdbeCoverage(v);
+ if( pTab->tabFlags & TF_HasNotNull ){
+ int b2ndPass = 0; /* True if currently running 2nd pass */
+ int nSeenReplace = 0; /* Number of ON CONFLICT REPLACE operations */
+ int nGenerated = 0; /* Number of generated columns with NOT NULL */
+ while(1){ /* Make 2 passes over columns. Exit loop via "break" */
+ for(i=0; i<nCol; i++){
+ int iReg; /* Register holding column value */
+ Column *pCol = &pTab->aCol[i]; /* The column to check for NOT NULL */
+ int isGenerated; /* non-zero if column is generated */
+ onError = pCol->notNull;
+ if( onError==OE_None ) continue; /* No NOT NULL on this column */
+ if( i==pTab->iPKey ){
+ continue; /* ROWID is never NULL */
+ }
+ isGenerated = pCol->colFlags & COLFLAG_GENERATED;
+ if( isGenerated && !b2ndPass ){
+ nGenerated++;
+ continue; /* Generated columns processed on 2nd pass */
+ }
+ if( aiChng && aiChng[i]<0 && !isGenerated ){
+ /* Do not check NOT NULL on columns that do not change */
+ continue;
+ }
+ if( overrideError!=OE_Default ){
+ onError = overrideError;
+ }else if( onError==OE_Default ){
+ onError = OE_Abort;
+ }
+ if( onError==OE_Replace ){
+ if( b2ndPass /* REPLACE becomes ABORT on the 2nd pass */
+ || pCol->pDflt==0 /* REPLACE is ABORT if no DEFAULT value */
+ ){
+ testcase( pCol->colFlags & COLFLAG_VIRTUAL );
+ testcase( pCol->colFlags & COLFLAG_STORED );
+ testcase( pCol->colFlags & COLFLAG_GENERATED );
+ onError = OE_Abort;
+ }else{
+ assert( !isGenerated );
+ }
+ }else if( b2ndPass && !isGenerated ){
+ continue;
+ }
+ assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail
+ || onError==OE_Ignore || onError==OE_Replace );
+ testcase( i!=tdsqlite3TableColumnToStorage(pTab, i) );
+ iReg = tdsqlite3TableColumnToStorage(pTab, i) + regNewData + 1;
+ switch( onError ){
+ case OE_Replace: {
+ int addr1 = tdsqlite3VdbeAddOp1(v, OP_NotNull, iReg);
+ VdbeCoverage(v);
+ assert( (pCol->colFlags & COLFLAG_GENERATED)==0 );
+ nSeenReplace++;
+ tdsqlite3ExprCode(pParse, pCol->pDflt, iReg);
+ tdsqlite3VdbeJumpHere(v, addr1);
+ break;
+ }
+ case OE_Abort:
+ tdsqlite3MayAbort(pParse);
+ /* Fall through */
+ case OE_Rollback:
+ case OE_Fail: {
+ char *zMsg = tdsqlite3MPrintf(db, "%s.%s", pTab->zName,
+ pCol->zName);
+ tdsqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL,
+ onError, iReg);
+ tdsqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC);
+ tdsqlite3VdbeChangeP5(v, P5_ConstraintNotNull);
+ VdbeCoverage(v);
+ break;
+ }
+ default: {
+ assert( onError==OE_Ignore );
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, iReg, ignoreDest);
+ VdbeCoverage(v);
+ break;
+ }
+ } /* end switch(onError) */
+ } /* end loop i over columns */
+ if( nGenerated==0 && nSeenReplace==0 ){
+ /* If there are no generated columns with NOT NULL constraints
+ ** and no NOT NULL ON CONFLICT REPLACE constraints, then a single
+ ** pass is sufficient */
break;
}
- default: {
- assert( onError==OE_Replace );
- addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, regNewData+1+i);
- VdbeCoverage(v);
- sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regNewData+1+i);
- sqlite3VdbeJumpHere(v, addr1);
- break;
+ if( b2ndPass ) break; /* Never need more than 2 passes */
+ b2ndPass = 1;
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ if( nSeenReplace>0 && (pTab->tabFlags & TF_HasGenerated)!=0 ){
+ /* If any NOT NULL ON CONFLICT REPLACE constraints fired on the
+ ** first pass, recomputed values for all generated columns, as
+ ** those values might depend on columns affected by the REPLACE.
+ */
+ tdsqlite3ComputeGeneratedColumns(pParse, regNewData+1, pTab);
}
- }
- }
+#endif
+ } /* end of 2-pass loop */
+ } /* end if( has-not-null-constraints ) */
/* Test all CHECK constraints
*/
#ifndef SQLITE_OMIT_CHECK
if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){
ExprList *pCheck = pTab->pCheck;
- pParse->ckBase = regNewData+1;
+ pParse->iSelfTab = -(regNewData+1);
onError = overrideError!=OE_Default ? overrideError : OE_Abort;
for(i=0; i<pCheck->nExpr; i++){
int allOk;
Expr *pExpr = pCheck->a[i].pExpr;
- if( aiChng && checkConstraintUnchanged(pExpr, aiChng, pkChng) ) continue;
- allOk = sqlite3VdbeMakeLabel(v);
- sqlite3ExprIfTrue(pParse, pExpr, allOk, SQLITE_JUMPIFNULL);
+ if( aiChng
+ && !tdsqlite3ExprReferencesUpdatedColumn(pExpr, aiChng, pkChng)
+ ){
+ /* The check constraints do not reference any of the columns being
+ ** updated so there is no point it verifying the check constraint */
+ continue;
+ }
+ allOk = tdsqlite3VdbeMakeLabel(pParse);
+ tdsqlite3VdbeVerifyAbortable(v, onError);
+ tdsqlite3ExprIfTrue(pParse, pExpr, allOk, SQLITE_JUMPIFNULL);
if( onError==OE_Ignore ){
- sqlite3VdbeGoto(v, ignoreDest);
+ tdsqlite3VdbeGoto(v, ignoreDest);
}else{
- char *zName = pCheck->a[i].zName;
+ char *zName = pCheck->a[i].zEName;
if( zName==0 ) zName = pTab->zName;
- if( onError==OE_Replace ) onError = OE_Abort; /* IMP: R-15569-63625 */
- sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_CHECK,
+ if( onError==OE_Replace ) onError = OE_Abort; /* IMP: R-26383-51744 */
+ tdsqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_CHECK,
onError, zName, P4_TRANSIENT,
P5_ConstraintCheck);
}
- sqlite3VdbeResolveLabel(v, allOk);
+ tdsqlite3VdbeResolveLabel(v, allOk);
}
+ pParse->iSelfTab = 0;
}
#endif /* !defined(SQLITE_OMIT_CHECK) */
+ /* UNIQUE and PRIMARY KEY constraints should be handled in the following
+ ** order:
+ **
+ ** (1) OE_Update
+ ** (2) OE_Abort, OE_Fail, OE_Rollback, OE_Ignore
+ ** (3) OE_Replace
+ **
+ ** OE_Fail and OE_Ignore must happen before any changes are made.
+ ** OE_Update guarantees that only a single row will change, so it
+ ** must happen before OE_Replace. Technically, OE_Abort and OE_Rollback
+ ** could happen in any order, but they are grouped up front for
+ ** convenience.
+ **
+ ** 2018-08-14: Ticket https://www.sqlite.org/src/info/908f001483982c43
+ ** The order of constraints used to have OE_Update as (2) and OE_Abort
+ ** and so forth as (1). But apparently PostgreSQL checks the OE_Update
+ ** constraint before any others, so it had to be moved.
+ **
+ ** Constraint checking code is generated in this order:
+ ** (A) The rowid constraint
+ ** (B) Unique index constraints that do not have OE_Replace as their
+ ** default conflict resolution strategy
+ ** (C) Unique index that do use OE_Replace by default.
+ **
+ ** The ordering of (2) and (3) is accomplished by making sure the linked
+ ** list of indexes attached to a table puts all OE_Replace indexes last
+ ** in the list. See tdsqlite3CreateIndex() for where that happens.
+ */
+
+ if( pUpsert ){
+ if( pUpsert->pUpsertTarget==0 ){
+ /* An ON CONFLICT DO NOTHING clause, without a constraint-target.
+ ** Make all unique constraint resolution be OE_Ignore */
+ assert( pUpsert->pUpsertSet==0 );
+ overrideError = OE_Ignore;
+ pUpsert = 0;
+ }else if( (pUpIdx = pUpsert->pUpsertIdx)!=0 ){
+ /* If the constraint-target uniqueness check must be run first.
+ ** Jump to that uniqueness check now */
+ upsertJump = tdsqlite3VdbeAddOp0(v, OP_Goto);
+ VdbeComment((v, "UPSERT constraint goes first"));
+ }
+ }
+
+ /* Determine if it is possible that triggers (either explicitly coded
+ ** triggers or FK resolution actions) might run as a result of deletes
+ ** that happen when OE_Replace conflict resolution occurs. (Call these
+ ** "replace triggers".) If any replace triggers run, we will need to
+ ** recheck all of the uniqueness constraints after they have all run.
+ ** But on the recheck, the resolution is OE_Abort instead of OE_Replace.
+ **
+ ** If replace triggers are a possibility, then
+ **
+ ** (1) Allocate register regTrigCnt and initialize it to zero.
+ ** That register will count the number of replace triggers that
+ ** fire. Constraint recheck only occurs if the number is positive.
+ ** (2) Initialize pTrigger to the list of all DELETE triggers on pTab.
+ ** (3) Initialize addrRecheck and lblRecheckOk
+ **
+ ** The uniqueness rechecking code will create a series of tests to run
+ ** in a second pass. The addrRecheck and lblRecheckOk variables are
+ ** used to link together these tests which are separated from each other
+ ** in the generate bytecode.
+ */
+ if( (db->flags & (SQLITE_RecTriggers|SQLITE_ForeignKeys))==0 ){
+ /* There are not DELETE triggers nor FK constraints. No constraint
+ ** rechecks are needed. */
+ pTrigger = 0;
+ regTrigCnt = 0;
+ }else{
+ if( db->flags&SQLITE_RecTriggers ){
+ pTrigger = tdsqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
+ regTrigCnt = pTrigger!=0 || tdsqlite3FkRequired(pParse, pTab, 0, 0);
+ }else{
+ pTrigger = 0;
+ regTrigCnt = tdsqlite3FkRequired(pParse, pTab, 0, 0);
+ }
+ if( regTrigCnt ){
+ /* Replace triggers might exist. Allocate the counter and
+ ** initialize it to zero. */
+ regTrigCnt = ++pParse->nMem;
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regTrigCnt);
+ VdbeComment((v, "trigger count"));
+ lblRecheckOk = tdsqlite3VdbeMakeLabel(pParse);
+ addrRecheck = lblRecheckOk;
+ }
+ }
+
/* If rowid is changing, make sure the new rowid does not previously
** exist in the table.
*/
if( pkChng && pPk==0 ){
- int addrRowidOk = sqlite3VdbeMakeLabel(v);
+ int addrRowidOk = tdsqlite3VdbeMakeLabel(pParse);
/* Figure out what action to take in case of a rowid collision */
onError = pTab->keyConf;
@@ -111837,13 +124552,13 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
onError = OE_Abort;
}
- if( isUpdate ){
- /* pkChng!=0 does not mean that the rowid has change, only that
- ** it might have changed. Skip the conflict logic below if the rowid
- ** is unchanged. */
- sqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRowidOk, regOldData);
- sqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
- VdbeCoverage(v);
+ /* figure out whether or not upsert applies in this case */
+ if( pUpsert && pUpsert->pUpsertIdx==0 ){
+ if( pUpsert->pUpsertSet==0 ){
+ onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */
+ }else{
+ onError = OE_Update; /* DO UPDATE */
+ }
}
/* If the response to a rowid conflict is REPLACE but the response
@@ -111851,21 +124566,30 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
** to defer the running of the rowid conflict checking until after
** the UNIQUE constraints have run.
*/
- if( onError==OE_Replace && overrideError!=OE_Replace ){
- for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- if( pIdx->onError==OE_Ignore || pIdx->onError==OE_Fail ){
- ipkTop = sqlite3VdbeAddOp0(v, OP_Goto);
- break;
- }
- }
+ if( onError==OE_Replace /* IPK rule is REPLACE */
+ && onError!=overrideError /* Rules for other contraints are different */
+ && pTab->pIndex /* There exist other constraints */
+ ){
+ ipkTop = tdsqlite3VdbeAddOp0(v, OP_Goto)+1;
+ VdbeComment((v, "defer IPK REPLACE until last"));
+ }
+
+ if( isUpdate ){
+ /* pkChng!=0 does not mean that the rowid has changed, only that
+ ** it might have changed. Skip the conflict logic below if the rowid
+ ** is unchanged. */
+ tdsqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRowidOk, regOldData);
+ tdsqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
+ VdbeCoverage(v);
}
/* Check to see if the new rowid already exists in the table. Skip
** the following conflict logic if it does not. */
- sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, addrRowidOk, regNewData);
+ VdbeNoopComment((v, "uniqueness check for ROWID"));
+ tdsqlite3VdbeVerifyAbortable(v, onError);
+ tdsqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, addrRowidOk, regNewData);
VdbeCoverage(v);
- /* Generate code that deals with a rowid collision */
switch( onError ){
default: {
onError = OE_Abort;
@@ -111874,7 +124598,10 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
case OE_Rollback:
case OE_Abort:
case OE_Fail: {
- sqlite3RowidConstraint(pParse, onError, pTab);
+ testcase( onError==OE_Rollback );
+ testcase( onError==OE_Abort );
+ testcase( onError==OE_Fail );
+ tdsqlite3RowidConstraint(pParse, onError, pTab);
break;
}
case OE_Replace: {
@@ -111892,7 +124619,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
** If either GenerateRowDelete() or GenerateRowIndexDelete() is called,
** also invoke MultiWrite() to indicate that this VDBE may require
** statement rollback (if the statement is aborted after the delete
- ** takes place). Earlier versions called sqlite3MultiWrite() regardless,
+ ** takes place). Earlier versions called tdsqlite3MultiWrite() regardless,
** but being more selective here allows statements like:
**
** REPLACE INTO t(rowid) VALUES($newrowid)
@@ -111900,43 +124627,46 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
** to run without a statement journal if there are no indexes on the
** table.
*/
- Trigger *pTrigger = 0;
- if( db->flags&SQLITE_RecTriggers ){
- pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
- }
- if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){
- sqlite3MultiWrite(pParse);
- sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
+ if( regTrigCnt ){
+ tdsqlite3MultiWrite(pParse);
+ tdsqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
regNewData, 1, 0, OE_Replace, 1, -1);
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, regTrigCnt, 1); /* incr trigger cnt */
+ nReplaceTrig++;
}else{
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
- if( HasRowid(pTab) ){
- /* This OP_Delete opcode fires the pre-update-hook only. It does
- ** not modify the b-tree. It is more efficient to let the coming
- ** OP_Insert replace the existing entry than it is to delete the
- ** existing entry and then insert a new one. */
- sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, OPFLAG_ISNOOP);
- sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE);
- }
+ assert( HasRowid(pTab) );
+ /* This OP_Delete opcode fires the pre-update-hook only. It does
+ ** not modify the b-tree. It is more efficient to let the coming
+ ** OP_Insert replace the existing entry than it is to delete the
+ ** existing entry and then insert a new one. */
+ tdsqlite3VdbeAddOp2(v, OP_Delete, iDataCur, OPFLAG_ISNOOP);
+ tdsqlite3VdbeAppendP4(v, pTab, P4_TABLE);
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
if( pTab->pIndex ){
- sqlite3MultiWrite(pParse);
- sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,-1);
+ tdsqlite3MultiWrite(pParse);
+ tdsqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,-1);
}
}
seenReplace = 1;
break;
}
+#ifndef SQLITE_OMIT_UPSERT
+ case OE_Update: {
+ tdsqlite3UpsertDoUpdate(pParse, pUpsert, pTab, 0, iDataCur);
+ /* Fall through */
+ }
+#endif
case OE_Ignore: {
- /*assert( seenReplace==0 );*/
- sqlite3VdbeGoto(v, ignoreDest);
+ testcase( onError==OE_Ignore );
+ tdsqlite3VdbeGoto(v, ignoreDest);
break;
}
}
- sqlite3VdbeResolveLabel(v, addrRowidOk);
+ tdsqlite3VdbeResolveLabel(v, addrRowidOk);
if( ipkTop ){
- ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto);
- sqlite3VdbeJumpHere(v, ipkTop);
+ ipkBottom = tdsqlite3VdbeAddOp0(v, OP_Goto);
+ tdsqlite3VdbeJumpHere(v, ipkTop-1);
}
}
@@ -111952,66 +124682,79 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
int regR; /* Range of registers holding conflicting PK */
int iThisCur; /* Cursor for this UNIQUE index */
int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */
+ int addrConflictCk; /* First opcode in the conflict check logic */
if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */
- if( bAffinityDone==0 ){
- sqlite3TableAffinity(v, pTab, regNewData+1);
+ if( pUpIdx==pIdx ){
+ addrUniqueOk = upsertJump+1;
+ upsertBypass = tdsqlite3VdbeGoto(v, 0);
+ VdbeComment((v, "Skip upsert subroutine"));
+ tdsqlite3VdbeJumpHere(v, upsertJump);
+ }else{
+ addrUniqueOk = tdsqlite3VdbeMakeLabel(pParse);
+ }
+ if( bAffinityDone==0 && (pUpIdx==0 || pUpIdx==pIdx) ){
+ tdsqlite3TableAffinity(v, pTab, regNewData+1);
bAffinityDone = 1;
}
+ VdbeNoopComment((v, "uniqueness check for %s", pIdx->zName));
iThisCur = iIdxCur+ix;
- addrUniqueOk = sqlite3VdbeMakeLabel(v);
+
/* Skip partial indices for which the WHERE clause is not true */
if( pIdx->pPartIdxWhere ){
- sqlite3VdbeAddOp2(v, OP_Null, 0, aRegIdx[ix]);
- pParse->ckBase = regNewData+1;
- sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, addrUniqueOk,
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, aRegIdx[ix]);
+ pParse->iSelfTab = -(regNewData+1);
+ tdsqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, addrUniqueOk,
SQLITE_JUMPIFNULL);
- pParse->ckBase = 0;
+ pParse->iSelfTab = 0;
}
/* Create a record for this index entry as it should appear after
** the insert or update. Store that record in the aRegIdx[ix] register
*/
- regIdx = sqlite3GetTempRange(pParse, pIdx->nColumn);
+ regIdx = aRegIdx[ix]+1;
for(i=0; i<pIdx->nColumn; i++){
int iField = pIdx->aiColumn[i];
int x;
if( iField==XN_EXPR ){
- pParse->ckBase = regNewData+1;
- sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[i].pExpr, regIdx+i);
- pParse->ckBase = 0;
+ pParse->iSelfTab = -(regNewData+1);
+ tdsqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[i].pExpr, regIdx+i);
+ pParse->iSelfTab = 0;
VdbeComment((v, "%s column %d", pIdx->zName, i));
+ }else if( iField==XN_ROWID || iField==pTab->iPKey ){
+ x = regNewData;
+ tdsqlite3VdbeAddOp2(v, OP_IntCopy, x, regIdx+i);
+ VdbeComment((v, "rowid"));
}else{
- if( iField==XN_ROWID || iField==pTab->iPKey ){
- if( regRowid==regIdx+i ) continue; /* ROWID already in regIdx+i */
- x = regNewData;
- regRowid = pIdx->pPartIdxWhere ? -1 : regIdx+i;
- }else{
- x = iField + regNewData + 1;
- }
- sqlite3VdbeAddOp2(v, iField<0 ? OP_IntCopy : OP_SCopy, x, regIdx+i);
- VdbeComment((v, "%s", iField<0 ? "rowid" : pTab->aCol[iField].zName));
+ testcase( tdsqlite3TableColumnToStorage(pTab, iField)!=iField );
+ x = tdsqlite3TableColumnToStorage(pTab, iField) + regNewData + 1;
+ tdsqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i);
+ VdbeComment((v, "%s", pTab->aCol[iField].zName));
}
}
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]);
VdbeComment((v, "for %s", pIdx->zName));
- sqlite3ExprCacheAffinityChange(pParse, regIdx, pIdx->nColumn);
+#ifdef SQLITE_ENABLE_NULL_TRIM
+ if( pIdx->idxType==SQLITE_IDXTYPE_PRIMARYKEY ){
+ tdsqlite3SetMakeRecordP5(v, pIdx->pTable);
+ }
+#endif
+ tdsqlite3VdbeReleaseRegisters(pParse, regIdx, pIdx->nColumn, 0, 0);
/* In an UPDATE operation, if this index is the PRIMARY KEY index
** of a WITHOUT ROWID table and there has been no change the
** primary key, then no collision is possible. The collision detection
** logic below can all be skipped. */
if( isUpdate && pPk==pIdx && pkChng==0 ){
- sqlite3VdbeResolveLabel(v, addrUniqueOk);
+ tdsqlite3VdbeResolveLabel(v, addrUniqueOk);
continue;
}
/* Find out what action to take in case there is a uniqueness conflict */
onError = pIdx->onError;
if( onError==OE_None ){
- sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn);
- sqlite3VdbeResolveLabel(v, addrUniqueOk);
+ tdsqlite3VdbeResolveLabel(v, addrUniqueOk);
continue; /* pIdx is not a UNIQUE index */
}
if( overrideError!=OE_Default ){
@@ -112019,21 +124762,56 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
}else if( onError==OE_Default ){
onError = OE_Abort;
}
-
+
+ /* Figure out if the upsert clause applies to this index */
+ if( pUpIdx==pIdx ){
+ if( pUpsert->pUpsertSet==0 ){
+ onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */
+ }else{
+ onError = OE_Update; /* DO UPDATE */
+ }
+ }
+
+ /* Collision detection may be omitted if all of the following are true:
+ ** (1) The conflict resolution algorithm is REPLACE
+ ** (2) The table is a WITHOUT ROWID table
+ ** (3) There are no secondary indexes on the table
+ ** (4) No delete triggers need to be fired if there is a conflict
+ ** (5) No FK constraint counters need to be updated if a conflict occurs.
+ **
+ ** This is not possible for ENABLE_PREUPDATE_HOOK builds, as the row
+ ** must be explicitly deleted in order to ensure any pre-update hook
+ ** is invoked. */
+#ifndef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( (ix==0 && pIdx->pNext==0) /* Condition 3 */
+ && pPk==pIdx /* Condition 2 */
+ && onError==OE_Replace /* Condition 1 */
+ && ( 0==(db->flags&SQLITE_RecTriggers) || /* Condition 4 */
+ 0==tdsqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0))
+ && ( 0==(db->flags&SQLITE_ForeignKeys) || /* Condition 5 */
+ (0==pTab->pFKey && 0==tdsqlite3FkReferences(pTab)))
+ ){
+ tdsqlite3VdbeResolveLabel(v, addrUniqueOk);
+ continue;
+ }
+#endif /* ifndef SQLITE_ENABLE_PREUPDATE_HOOK */
+
/* Check to see if the new index entry will be unique */
- sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk,
- regIdx, pIdx->nKeyCol); VdbeCoverage(v);
+ tdsqlite3VdbeVerifyAbortable(v, onError);
+ addrConflictCk =
+ tdsqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk,
+ regIdx, pIdx->nKeyCol); VdbeCoverage(v);
/* Generate code to handle collisions */
- regR = (pIdx==pPk) ? regIdx : sqlite3GetTempRange(pParse, nPkField);
+ regR = (pIdx==pPk) ? regIdx : tdsqlite3GetTempRange(pParse, nPkField);
if( isUpdate || onError==OE_Replace ){
if( HasRowid(pTab) ){
- sqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR);
+ tdsqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR);
/* Conflict only if the rowid of the existing index entry
** is different from old-rowid */
if( isUpdate ){
- sqlite3VdbeAddOp3(v, OP_Eq, regR, addrUniqueOk, regOldData);
- sqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
+ tdsqlite3VdbeAddOp3(v, OP_Eq, regR, addrUniqueOk, regOldData);
+ tdsqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
VdbeCoverage(v);
}
}else{
@@ -112043,8 +124821,8 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
if( pIdx!=pPk ){
for(i=0; i<pPk->nKeyCol; i++){
assert( pPk->aiColumn[i]>=0 );
- x = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[i]);
- sqlite3VdbeAddOp3(v, OP_Column, iThisCur, x, regR+i);
+ x = tdsqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iThisCur, x, regR+i);
VdbeComment((v, "%s.%s", pTab->zName,
pTab->aCol[pPk->aiColumn[i]].zName));
}
@@ -112057,22 +124835,23 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
** For a UNIQUE index, only conflict if the PRIMARY KEY values
** of the matched index row are different from the original PRIMARY
** KEY values of this row before the update. */
- int addrJump = sqlite3VdbeCurrentAddr(v)+pPk->nKeyCol;
+ int addrJump = tdsqlite3VdbeCurrentAddr(v)+pPk->nKeyCol;
int op = OP_Ne;
int regCmp = (IsPrimaryKeyIndex(pIdx) ? regIdx : regR);
for(i=0; i<pPk->nKeyCol; i++){
- char *p4 = (char*)sqlite3LocateCollSeq(pParse, pPk->azColl[i]);
+ char *p4 = (char*)tdsqlite3LocateCollSeq(pParse, pPk->azColl[i]);
x = pPk->aiColumn[i];
assert( x>=0 );
if( i==(pPk->nKeyCol-1) ){
addrJump = addrUniqueOk;
op = OP_Eq;
}
- sqlite3VdbeAddOp4(v, op,
+ x = tdsqlite3TableColumnToStorage(pTab, x);
+ tdsqlite3VdbeAddOp4(v, op,
regOldData+1+x, addrJump, regCmp+i, p4, P4_COLLSEQ
);
- sqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
+ tdsqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
VdbeCoverageIf(v, op==OP_Eq);
VdbeCoverageIf(v, op==OP_Ne);
}
@@ -112082,103 +124861,241 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(
/* Generate code that executes if the new index entry is not unique */
assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail
- || onError==OE_Ignore || onError==OE_Replace );
+ || onError==OE_Ignore || onError==OE_Replace || onError==OE_Update );
switch( onError ){
case OE_Rollback:
case OE_Abort:
case OE_Fail: {
- sqlite3UniqueConstraint(pParse, onError, pIdx);
+ testcase( onError==OE_Rollback );
+ testcase( onError==OE_Abort );
+ testcase( onError==OE_Fail );
+ tdsqlite3UniqueConstraint(pParse, onError, pIdx);
break;
}
+#ifndef SQLITE_OMIT_UPSERT
+ case OE_Update: {
+ tdsqlite3UpsertDoUpdate(pParse, pUpsert, pTab, pIdx, iIdxCur+ix);
+ /* Fall through */
+ }
+#endif
case OE_Ignore: {
- sqlite3VdbeGoto(v, ignoreDest);
+ testcase( onError==OE_Ignore );
+ tdsqlite3VdbeGoto(v, ignoreDest);
break;
}
default: {
- Trigger *pTrigger = 0;
+ int nConflictCk; /* Number of opcodes in conflict check logic */
+
assert( onError==OE_Replace );
- sqlite3MultiWrite(pParse);
- if( db->flags&SQLITE_RecTriggers ){
- pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
+ nConflictCk = tdsqlite3VdbeCurrentAddr(v) - addrConflictCk;
+ assert( nConflictCk>0 );
+ testcase( nConflictCk>1 );
+ if( regTrigCnt ){
+ tdsqlite3MultiWrite(pParse);
+ nReplaceTrig++;
+ }
+ if( pTrigger && isUpdate ){
+ tdsqlite3VdbeAddOp1(v, OP_CursorLock, iDataCur);
}
- sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
+ tdsqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
regR, nPkField, 0, OE_Replace,
- (pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), -1);
+ (pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), iThisCur);
+ if( pTrigger && isUpdate ){
+ tdsqlite3VdbeAddOp1(v, OP_CursorUnlock, iDataCur);
+ }
+ if( regTrigCnt ){
+ int addrBypass; /* Jump destination to bypass recheck logic */
+
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, regTrigCnt, 1); /* incr trigger cnt */
+ addrBypass = tdsqlite3VdbeAddOp0(v, OP_Goto); /* Bypass recheck */
+ VdbeComment((v, "bypass recheck"));
+
+ /* Here we insert code that will be invoked after all constraint
+ ** checks have run, if and only if one or more replace triggers
+ ** fired. */
+ tdsqlite3VdbeResolveLabel(v, lblRecheckOk);
+ lblRecheckOk = tdsqlite3VdbeMakeLabel(pParse);
+ if( pIdx->pPartIdxWhere ){
+ /* Bypass the recheck if this partial index is not defined
+ ** for the current row */
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, regIdx-1, lblRecheckOk);
+ VdbeCoverage(v);
+ }
+ /* Copy the constraint check code from above, except change
+ ** the constraint-ok jump destination to be the address of
+ ** the next retest block */
+ while( nConflictCk>0 ){
+ VdbeOp x; /* Conflict check opcode to copy */
+ /* The tdsqlite3VdbeAddOp4() call might reallocate the opcode array.
+ ** Hence, make a complete copy of the opcode, rather than using
+ ** a pointer to the opcode. */
+ x = *tdsqlite3VdbeGetOp(v, addrConflictCk);
+ if( x.opcode!=OP_IdxRowid ){
+ int p2; /* New P2 value for copied conflict check opcode */
+ if( tdsqlite3OpcodeProperty[x.opcode]&OPFLG_JUMP ){
+ p2 = lblRecheckOk;
+ }else{
+ p2 = x.p2;
+ }
+ tdsqlite3VdbeAddOp4(v, x.opcode, x.p1, p2, x.p3, x.p4.z, x.p4type);
+ tdsqlite3VdbeChangeP5(v, x.p5);
+ VdbeCoverageIf(v, p2!=x.p2);
+ }
+ nConflictCk--;
+ addrConflictCk++;
+ }
+ /* If the retest fails, issue an abort */
+ tdsqlite3UniqueConstraint(pParse, OE_Abort, pIdx);
+
+ tdsqlite3VdbeJumpHere(v, addrBypass); /* Terminate the recheck bypass */
+ }
seenReplace = 1;
break;
}
}
- sqlite3VdbeResolveLabel(v, addrUniqueOk);
- sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn);
- if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField);
+ if( pUpIdx==pIdx ){
+ tdsqlite3VdbeGoto(v, upsertJump+1);
+ tdsqlite3VdbeJumpHere(v, upsertBypass);
+ }else{
+ tdsqlite3VdbeResolveLabel(v, addrUniqueOk);
+ }
+ if( regR!=regIdx ) tdsqlite3ReleaseTempRange(pParse, regR, nPkField);
}
+
+ /* If the IPK constraint is a REPLACE, run it last */
if( ipkTop ){
- sqlite3VdbeGoto(v, ipkTop+1);
- sqlite3VdbeJumpHere(v, ipkBottom);
+ tdsqlite3VdbeGoto(v, ipkTop);
+ VdbeComment((v, "Do IPK REPLACE"));
+ tdsqlite3VdbeJumpHere(v, ipkBottom);
+ }
+
+ /* Recheck all uniqueness constraints after replace triggers have run */
+ testcase( regTrigCnt!=0 && nReplaceTrig==0 );
+ assert( regTrigCnt!=0 || nReplaceTrig==0 );
+ if( nReplaceTrig ){
+ tdsqlite3VdbeAddOp2(v, OP_IfNot, regTrigCnt, lblRecheckOk);VdbeCoverage(v);
+ if( !pPk ){
+ if( isUpdate ){
+ tdsqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRecheck, regOldData);
+ tdsqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
+ VdbeCoverage(v);
+ }
+ tdsqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, addrRecheck, regNewData);
+ VdbeCoverage(v);
+ tdsqlite3RowidConstraint(pParse, OE_Abort, pTab);
+ }else{
+ tdsqlite3VdbeGoto(v, addrRecheck);
+ }
+ tdsqlite3VdbeResolveLabel(v, lblRecheckOk);
}
-
+
+ /* Generate the table record */
+ if( HasRowid(pTab) ){
+ int regRec = aRegIdx[ix];
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regNewData+1, pTab->nNVCol, regRec);
+ tdsqlite3SetMakeRecordP5(v, pTab);
+ if( !bAffinityDone ){
+ tdsqlite3TableAffinity(v, pTab, 0);
+ }
+ }
+
*pbMayReplace = seenReplace;
VdbeModuleComment((v, "END: GenCnstCks(%d)", seenReplace));
}
+#ifdef SQLITE_ENABLE_NULL_TRIM
+/*
+** Change the P5 operand on the last opcode (which should be an OP_MakeRecord)
+** to be the number of columns in table pTab that must not be NULL-trimmed.
+**
+** Or if no columns of pTab may be NULL-trimmed, leave P5 at zero.
+*/
+SQLITE_PRIVATE void tdsqlite3SetMakeRecordP5(Vdbe *v, Table *pTab){
+ u16 i;
+
+ /* Records with omitted columns are only allowed for schema format
+ ** version 2 and later (SQLite version 3.1.4, 2005-02-20). */
+ if( pTab->pSchema->file_format<2 ) return;
+
+ for(i=pTab->nCol-1; i>0; i--){
+ if( pTab->aCol[i].pDflt!=0 ) break;
+ if( pTab->aCol[i].colFlags & COLFLAG_PRIMKEY ) break;
+ }
+ tdsqlite3VdbeChangeP5(v, i+1);
+}
+#endif
+
/*
** This routine generates code to finish the INSERT or UPDATE operation
-** that was started by a prior call to sqlite3GenerateConstraintChecks.
+** that was started by a prior call to tdsqlite3GenerateConstraintChecks.
** A consecutive range of registers starting at regNewData contains the
** rowid and the content to be inserted.
**
** The arguments to this routine should be the same as the first six
-** arguments to sqlite3GenerateConstraintChecks.
+** arguments to tdsqlite3GenerateConstraintChecks.
*/
-SQLITE_PRIVATE void sqlite3CompleteInsertion(
+SQLITE_PRIVATE void tdsqlite3CompleteInsertion(
Parse *pParse, /* The parser context */
Table *pTab, /* the table into which we are inserting */
int iDataCur, /* Cursor of the canonical data source */
int iIdxCur, /* First index cursor */
int regNewData, /* Range of content */
int *aRegIdx, /* Register used by each index. 0 for unused indices */
- int isUpdate, /* True for UPDATE, False for INSERT */
+ int update_flags, /* True for UPDATE, False for INSERT */
int appendBias, /* True if this is likely to be an append */
int useSeekResult /* True to set the USESEEKRESULT flag on OP_[Idx]Insert */
){
Vdbe *v; /* Prepared statements under construction */
Index *pIdx; /* An index being inserted or updated */
u8 pik_flags; /* flag values passed to the btree insert */
- int regData; /* Content registers (after the rowid) */
- int regRec; /* Register holding assembled record for the table */
int i; /* Loop counter */
- u8 bAffinityDone = 0; /* True if OP_Affinity has been run already */
- v = sqlite3GetVdbe(pParse);
+ assert( update_flags==0
+ || update_flags==OPFLAG_ISUPDATE
+ || update_flags==(OPFLAG_ISUPDATE|OPFLAG_SAVEPOSITION)
+ );
+
+ v = tdsqlite3GetVdbe(pParse);
assert( v!=0 );
assert( pTab->pSelect==0 ); /* This table is not a VIEW */
for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
+ /* All REPLACE indexes are at the end of the list */
+ assert( pIdx->onError!=OE_Replace
+ || pIdx->pNext==0
+ || pIdx->pNext->onError==OE_Replace );
if( aRegIdx[i]==0 ) continue;
- bAffinityDone = 1;
if( pIdx->pPartIdxWhere ){
- sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2);
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], tdsqlite3VdbeCurrentAddr(v)+2);
VdbeCoverage(v);
}
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i]);
- pik_flags = 0;
- if( useSeekResult ) pik_flags = OPFLAG_USESEEKRESULT;
+ pik_flags = (useSeekResult ? OPFLAG_USESEEKRESULT : 0);
if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){
assert( pParse->nested==0 );
pik_flags |= OPFLAG_NCHANGE;
+ pik_flags |= (update_flags & OPFLAG_SAVEPOSITION);
+#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
+ if( update_flags==0 ){
+ int r = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, r);
+ tdsqlite3VdbeAddOp4(v, OP_Insert,
+ iIdxCur+i, aRegIdx[i], r, (char*)pTab, P4_TABLE
+ );
+ tdsqlite3VdbeChangeP5(v, OPFLAG_ISNOOP);
+ tdsqlite3ReleaseTempReg(pParse, r);
+ }
+#endif
}
- sqlite3VdbeChangeP5(v, pik_flags);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i],
+ aRegIdx[i]+1,
+ pIdx->uniqNotNull ? pIdx->nKeyCol: pIdx->nColumn);
+ tdsqlite3VdbeChangeP5(v, pik_flags);
}
if( !HasRowid(pTab) ) return;
- regData = regNewData + 1;
- regRec = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec);
- if( !bAffinityDone ) sqlite3TableAffinity(v, pTab, 0);
- sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol);
if( pParse->nested ){
pik_flags = 0;
}else{
pik_flags = OPFLAG_NCHANGE;
- pik_flags |= (isUpdate?OPFLAG_ISUPDATE:OPFLAG_LASTROWID);
+ pik_flags |= (update_flags?update_flags:OPFLAG_LASTROWID);
}
if( appendBias ){
pik_flags |= OPFLAG_APPEND;
@@ -112186,11 +125103,11 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion(
if( useSeekResult ){
pik_flags |= OPFLAG_USESEEKRESULT;
}
- sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, regRec, regNewData);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, iDataCur, aRegIdx[i], regNewData);
if( !pParse->nested ){
- sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE);
+ tdsqlite3VdbeAppendP4(v, pTab, P4_TABLE);
}
- sqlite3VdbeChangeP5(v, pik_flags);
+ tdsqlite3VdbeChangeP5(v, pik_flags);
}
/*
@@ -112214,7 +125131,7 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion(
** If pTab is a virtual table, then this routine is a no-op and the
** *piDataCur and *piIdxCur values are left uninitialized.
*/
-SQLITE_PRIVATE int sqlite3OpenTableAndIndices(
+SQLITE_PRIVATE int tdsqlite3OpenTableAndIndices(
Parse *pParse, /* Parsing context */
Table *pTab, /* Table to be opened */
int op, /* OP_OpenRead or OP_OpenWrite */
@@ -112238,16 +125155,16 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices(
** can detect if they are used by mistake in the caller. */
return 0;
}
- iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
- v = sqlite3GetVdbe(pParse);
+ iDb = tdsqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ v = tdsqlite3GetVdbe(pParse);
assert( v!=0 );
if( iBase<0 ) iBase = pParse->nTab;
iDataCur = iBase++;
if( piDataCur ) *piDataCur = iDataCur;
if( HasRowid(pTab) && (aToOpen==0 || aToOpen[0]) ){
- sqlite3OpenTable(pParse, iDataCur, iDb, pTab, op);
+ tdsqlite3OpenTable(pParse, iDataCur, iDb, pTab, op);
}else{
- sqlite3TableLock(pParse, iDb, pTab->tnum, op==OP_OpenWrite, pTab->zName);
+ tdsqlite3TableLock(pParse, iDb, pTab->tnum, op==OP_OpenWrite, pTab->zName);
}
if( piIdxCur ) *piIdxCur = iBase;
for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
@@ -112258,9 +125175,9 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices(
p5 = 0;
}
if( aToOpen==0 || aToOpen[i+1] ){
- sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum, iDb);
- sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
- sqlite3VdbeChangeP5(v, p5);
+ tdsqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum, iDb);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+ tdsqlite3VdbeChangeP5(v, p5);
VdbeComment((v, "%s", pIdx->zName));
}
}
@@ -112276,7 +125193,7 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices(
** purposes only - to make sure the transfer optimization really
** is happening when it is supposed to.
*/
-SQLITE_API int sqlite3_xferopt_count;
+SQLITE_API int tdsqlite3_xferopt_count;
#endif /* SQLITE_TEST */
@@ -112296,7 +125213,7 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){
int i;
assert( pDest && pSrc );
assert( pDest->pTable!=pSrc->pTable );
- if( pDest->nKeyCol!=pSrc->nKeyCol ){
+ if( pDest->nKeyCol!=pSrc->nKeyCol || pDest->nColumn!=pSrc->nColumn ){
return 0; /* Different number of columns */
}
if( pDest->onError!=pSrc->onError ){
@@ -112308,7 +125225,7 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){
}
if( pSrc->aiColumn[i]==XN_EXPR ){
assert( pSrc->aColExpr!=0 && pDest->aColExpr!=0 );
- if( sqlite3ExprCompare(pSrc->aColExpr->a[i].pExpr,
+ if( tdsqlite3ExprCompare(0, pSrc->aColExpr->a[i].pExpr,
pDest->aColExpr->a[i].pExpr, -1)!=0 ){
return 0; /* Different expressions in the index */
}
@@ -112316,11 +125233,11 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){
if( pSrc->aSortOrder[i]!=pDest->aSortOrder[i] ){
return 0; /* Different sort orders */
}
- if( sqlite3_stricmp(pSrc->azColl[i],pDest->azColl[i])!=0 ){
+ if( tdsqlite3_stricmp(pSrc->azColl[i],pDest->azColl[i])!=0 ){
return 0; /* Different collating sequences */
}
}
- if( sqlite3ExprCompare(pSrc->pPartIdxWhere, pDest->pPartIdxWhere, -1) ){
+ if( tdsqlite3ExprCompare(0, pSrc->pPartIdxWhere, pDest->pPartIdxWhere, -1) ){
return 0; /* Different WHERE clauses */
}
@@ -112360,7 +125277,7 @@ static int xferOptimization(
int onError, /* How to handle constraint errors */
int iDbDest /* The database of pDest */
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
ExprList *pEList; /* The result set of the SELECT */
Table *pSrc; /* The table in the FROM clause of SELECT */
Index *pSrcIdx, *pDestIdx; /* Source and destination indices */
@@ -112385,11 +125302,11 @@ static int xferOptimization(
** error if pSelect reads from a CTE named "xxx". */
return 0;
}
- if( sqlite3TriggerList(pParse, pDest) ){
+ if( tdsqlite3TriggerList(pParse, pDest) ){
return 0; /* tab1 must not have triggers */
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( pDest->tabFlags & TF_Virtual ){
+ if( IsVirtual(pDest) ){
return 0; /* tab1 must not be a virtual table */
}
#endif
@@ -112418,7 +125335,6 @@ static int xferOptimization(
if( pSelect->pLimit ){
return 0; /* SELECT may not have a LIMIT clause */
}
- assert( pSelect->pOffset==0 ); /* Must be so if pLimit==0 */
if( pSelect->pPrior ){
return 0; /* SELECT may not be a compound query */
}
@@ -112440,18 +125356,19 @@ static int xferOptimization(
** we have to check the semantics.
*/
pItem = pSelect->pSrc->a;
- pSrc = sqlite3LocateTableItem(pParse, 0, pItem);
+ pSrc = tdsqlite3LocateTableItem(pParse, 0, pItem);
if( pSrc==0 ){
return 0; /* FROM clause does not contain a real table */
}
- if( pSrc==pDest ){
+ if( pSrc->tnum==pDest->tnum && pSrc->pSchema==pDest->pSchema ){
+ testcase( pSrc!=pDest ); /* Possible due to bad sqlite_master.rootpage */
return 0; /* tab1 and tab2 may not be the same table */
}
if( HasRowid(pDest)!=HasRowid(pSrc) ){
return 0; /* source and destination must both be WITHOUT ROWID or not */
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( pSrc->tabFlags & TF_Virtual ){
+ if( IsVirtual(pSrc) ){
return 0; /* tab2 must not be a virtual table */
}
#endif
@@ -112468,23 +125385,56 @@ static int xferOptimization(
Column *pDestCol = &pDest->aCol[i];
Column *pSrcCol = &pSrc->aCol[i];
#ifdef SQLITE_ENABLE_HIDDEN_COLUMNS
- if( (db->flags & SQLITE_Vacuum)==0
+ if( (db->mDbFlags & DBFLAG_Vacuum)==0
&& (pDestCol->colFlags | pSrcCol->colFlags) & COLFLAG_HIDDEN
){
return 0; /* Neither table may have __hidden__ columns */
}
#endif
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ /* Even if tables t1 and t2 have identical schemas, if they contain
+ ** generated columns, then this statement is semantically incorrect:
+ **
+ ** INSERT INTO t2 SELECT * FROM t1;
+ **
+ ** The reason is that generated column values are returned by the
+ ** the SELECT statement on the right but the INSERT statement on the
+ ** left wants them to be omitted.
+ **
+ ** Nevertheless, this is a useful notational shorthand to tell SQLite
+ ** to do a bulk transfer all of the content from t1 over to t2.
+ **
+ ** We could, in theory, disable this (except for internal use by the
+ ** VACUUM command where it is actually needed). But why do that? It
+ ** seems harmless enough, and provides a useful service.
+ */
+ if( (pDestCol->colFlags & COLFLAG_GENERATED) !=
+ (pSrcCol->colFlags & COLFLAG_GENERATED) ){
+ return 0; /* Both columns have the same generated-column type */
+ }
+ /* But the transfer is only allowed if both the source and destination
+ ** tables have the exact same expressions for generated columns.
+ ** This requirement could be relaxed for VIRTUAL columns, I suppose.
+ */
+ if( (pDestCol->colFlags & COLFLAG_GENERATED)!=0 ){
+ if( tdsqlite3ExprCompare(0, pSrcCol->pDflt, pDestCol->pDflt, -1)!=0 ){
+ testcase( pDestCol->colFlags & COLFLAG_VIRTUAL );
+ testcase( pDestCol->colFlags & COLFLAG_STORED );
+ return 0; /* Different generator expressions */
+ }
+ }
+#endif
if( pDestCol->affinity!=pSrcCol->affinity ){
return 0; /* Affinity must be the same on all columns */
}
- if( sqlite3_stricmp(pDestCol->zColl, pSrcCol->zColl)!=0 ){
+ if( tdsqlite3_stricmp(pDestCol->zColl, pSrcCol->zColl)!=0 ){
return 0; /* Collating sequence must be the same on all columns */
}
if( pDestCol->notNull && !pSrcCol->notNull ){
return 0; /* tab2 must be NOT NULL if tab1 is */
}
/* Default values for second and subsequent columns need to match. */
- if( i>0 ){
+ if( (pDestCol->colFlags & COLFLAG_GENERATED)==0 && i>0 ){
assert( pDestCol->pDflt==0 || pDestCol->pDflt->op==TK_SPAN );
assert( pSrcCol->pDflt==0 || pSrcCol->pDflt->op==TK_SPAN );
if( (pDestCol->pDflt==0)!=(pSrcCol->pDflt==0)
@@ -112505,9 +125455,16 @@ static int xferOptimization(
if( pSrcIdx==0 ){
return 0; /* pDestIdx has no corresponding index in pSrc */
}
+ if( pSrcIdx->tnum==pDestIdx->tnum && pSrc->pSchema==pDest->pSchema
+ && tdsqlite3FaultSim(411)==SQLITE_OK ){
+ /* The tdsqlite3FaultSim() call allows this corruption test to be
+ ** bypassed during testing, in order to exercise other corruption tests
+ ** further downstream. */
+ return 0; /* Corrupt schema - two indexes on the same btree */
+ }
}
#ifndef SQLITE_OMIT_CHECK
- if( pDest->pCheck && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){
+ if( pDest->pCheck && tdsqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){
return 0; /* Tables have different CHECK constraints. Ticket #2252 */
}
#endif
@@ -112532,27 +125489,27 @@ static int xferOptimization(
** table (tab1) is initially empty.
*/
#ifdef SQLITE_TEST
- sqlite3_xferopt_count++;
+ tdsqlite3_xferopt_count++;
#endif
- iDbSrc = sqlite3SchemaToIndex(db, pSrc->pSchema);
- v = sqlite3GetVdbe(pParse);
- sqlite3CodeVerifySchema(pParse, iDbSrc);
+ iDbSrc = tdsqlite3SchemaToIndex(db, pSrc->pSchema);
+ v = tdsqlite3GetVdbe(pParse);
+ tdsqlite3CodeVerifySchema(pParse, iDbSrc);
iSrc = pParse->nTab++;
iDest = pParse->nTab++;
regAutoinc = autoIncBegin(pParse, iDbDest, pDest);
- regData = sqlite3GetTempReg(pParse);
- regRowid = sqlite3GetTempReg(pParse);
- sqlite3OpenTable(pParse, iDest, iDbDest, pDest, OP_OpenWrite);
+ regData = tdsqlite3GetTempReg(pParse);
+ regRowid = tdsqlite3GetTempReg(pParse);
+ tdsqlite3OpenTable(pParse, iDest, iDbDest, pDest, OP_OpenWrite);
assert( HasRowid(pDest) || destHasUniqueIdx );
- if( (db->flags & SQLITE_Vacuum)==0 && (
+ if( (db->mDbFlags & DBFLAG_Vacuum)==0 && (
(pDest->iPKey<0 && pDest->pIndex!=0) /* (1) */
|| destHasUniqueIdx /* (2) */
|| (onError!=OE_Abort && onError!=OE_Rollback) /* (3) */
)){
/* In some circumstances, we are able to run the xfer optimization
** only if the destination table is initially empty. Unless the
- ** SQLITE_Vacuum flag is set, this block generates code to make
- ** that determination. If SQLITE_Vacuum is set, then the destination
+ ** DBFLAG_Vacuum flag is set, this block generates code to make
+ ** that determination. If DBFLAG_Vacuum is set, then the destination
** table is always empty.
**
** Conditions under which the destination must be empty:
@@ -112566,36 +125523,45 @@ static int xferOptimization(
**
** (3) onError is something other than OE_Abort and OE_Rollback.
*/
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0); VdbeCoverage(v);
- emptyDestTest = sqlite3VdbeAddOp0(v, OP_Goto);
- sqlite3VdbeJumpHere(v, addr1);
+ addr1 = tdsqlite3VdbeAddOp2(v, OP_Rewind, iDest, 0); VdbeCoverage(v);
+ emptyDestTest = tdsqlite3VdbeAddOp0(v, OP_Goto);
+ tdsqlite3VdbeJumpHere(v, addr1);
}
if( HasRowid(pSrc) ){
- sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead);
- emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v);
+ u8 insFlags;
+ tdsqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead);
+ emptySrcTest = tdsqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v);
if( pDest->iPKey>=0 ){
- addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid);
- addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid);
+ addr1 = tdsqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid);
+ tdsqlite3VdbeVerifyAbortable(v, onError);
+ addr2 = tdsqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid);
VdbeCoverage(v);
- sqlite3RowidConstraint(pParse, onError, pDest);
- sqlite3VdbeJumpHere(v, addr2);
+ tdsqlite3RowidConstraint(pParse, onError, pDest);
+ tdsqlite3VdbeJumpHere(v, addr2);
autoIncStep(pParse, regAutoinc, regRowid);
- }else if( pDest->pIndex==0 ){
- addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid);
+ }else if( pDest->pIndex==0 && !(db->mDbFlags & DBFLAG_VacuumInto) ){
+ addr1 = tdsqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid);
}else{
- addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid);
+ addr1 = tdsqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid);
assert( (pDest->tabFlags & TF_Autoincrement)==0 );
}
- sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
- sqlite3VdbeAddOp4(v, OP_Insert, iDest, regData, regRowid,
+ tdsqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1);
+ if( db->mDbFlags & DBFLAG_Vacuum ){
+ tdsqlite3VdbeAddOp1(v, OP_SeekEnd, iDest);
+ insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|
+ OPFLAG_APPEND|OPFLAG_USESEEKRESULT;
+ }else{
+ insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND;
+ }
+ tdsqlite3VdbeAddOp4(v, OP_Insert, iDest, regData, regRowid,
(char*)pDest, P4_TABLE);
- sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND);
- sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
- sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+ tdsqlite3VdbeChangeP5(v, insFlags);
+ tdsqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+ tdsqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
}else{
- sqlite3TableLock(pParse, iDbDest, pDest->tnum, 1, pDest->zName);
- sqlite3TableLock(pParse, iDbSrc, pSrc->tnum, 0, pSrc->zName);
+ tdsqlite3TableLock(pParse, iDbDest, pDest->tnum, 1, pDest->zName);
+ tdsqlite3TableLock(pParse, iDbSrc, pSrc->tnum, 0, pSrc->zName);
}
for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){
u8 idxInsFlags = 0;
@@ -112603,22 +125569,22 @@ static int xferOptimization(
if( xferCompatibleIndex(pDestIdx, pSrcIdx) ) break;
}
assert( pSrcIdx );
- sqlite3VdbeAddOp3(v, OP_OpenRead, iSrc, pSrcIdx->tnum, iDbSrc);
- sqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
+ tdsqlite3VdbeAddOp3(v, OP_OpenRead, iSrc, pSrcIdx->tnum, iDbSrc);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pSrcIdx);
VdbeComment((v, "%s", pSrcIdx->zName));
- sqlite3VdbeAddOp3(v, OP_OpenWrite, iDest, pDestIdx->tnum, iDbDest);
- sqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
- sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
+ tdsqlite3VdbeAddOp3(v, OP_OpenWrite, iDest, pDestIdx->tnum, iDbDest);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pDestIdx);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_BULKCSR);
VdbeComment((v, "%s", pDestIdx->zName));
- addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_RowKey, iSrc, regData);
- if( db->flags & SQLITE_Vacuum ){
+ addr1 = tdsqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1);
+ if( db->mDbFlags & DBFLAG_Vacuum ){
/* This INSERT command is part of a VACUUM operation, which guarantees
** that the destination table is empty. If all indexed columns use
** collation sequence BINARY, then it can also be assumed that the
** index will be populated by inserting keys in strictly sorted
** order. In this case, instead of seeking within the b-tree as part
- ** of every OP_IdxInsert opcode, an OP_Last is added before the
+ ** of every OP_IdxInsert opcode, an OP_SeekEnd is added before the
** OP_IdxInsert to seek to the point within the b-tree where each key
** should be inserted. This is faster.
**
@@ -112629,33 +125595,31 @@ static int xferOptimization(
** sorted order. */
for(i=0; i<pSrcIdx->nColumn; i++){
const char *zColl = pSrcIdx->azColl[i];
- assert( sqlite3_stricmp(sqlite3StrBINARY, zColl)!=0
- || sqlite3StrBINARY==zColl );
- if( sqlite3_stricmp(sqlite3StrBINARY, zColl) ) break;
+ if( tdsqlite3_stricmp(tdsqlite3StrBINARY, zColl) ) break;
}
if( i==pSrcIdx->nColumn ){
idxInsFlags = OPFLAG_USESEEKRESULT;
- sqlite3VdbeAddOp3(v, OP_Last, iDest, 0, -1);
+ tdsqlite3VdbeAddOp1(v, OP_SeekEnd, iDest);
}
}
- if( !HasRowid(pSrc) && pDestIdx->idxType==2 ){
+ if( !HasRowid(pSrc) && pDestIdx->idxType==SQLITE_IDXTYPE_PRIMARYKEY ){
idxInsFlags |= OPFLAG_NCHANGE;
}
- sqlite3VdbeAddOp3(v, OP_IdxInsert, iDest, regData, 1);
- sqlite3VdbeChangeP5(v, idxInsFlags);
- sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
- sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+ tdsqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData);
+ tdsqlite3VdbeChangeP5(v, idxInsFlags|OPFLAG_APPEND);
+ tdsqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addr1);
+ tdsqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
+ tdsqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
}
- if( emptySrcTest ) sqlite3VdbeJumpHere(v, emptySrcTest);
- sqlite3ReleaseTempReg(pParse, regRowid);
- sqlite3ReleaseTempReg(pParse, regData);
+ if( emptySrcTest ) tdsqlite3VdbeJumpHere(v, emptySrcTest);
+ tdsqlite3ReleaseTempReg(pParse, regRowid);
+ tdsqlite3ReleaseTempReg(pParse, regData);
if( emptyDestTest ){
- sqlite3AutoincrementEnd(pParse);
- sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_OK, 0);
- sqlite3VdbeJumpHere(v, emptyDestTest);
- sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
+ tdsqlite3AutoincrementEnd(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_Halt, SQLITE_OK, 0);
+ tdsqlite3VdbeJumpHere(v, emptyDestTest);
+ tdsqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
return 0;
}else{
return 1;
@@ -112694,30 +125658,30 @@ static int xferOptimization(
** argument to xCallback(). If xCallback=NULL then no callback
** is invoked, even for queries.
*/
-SQLITE_API int sqlite3_exec(
- sqlite3 *db, /* The database on which the SQL executes */
+SQLITE_API int tdsqlite3_exec(
+ tdsqlite3 *db, /* The database on which the SQL executes */
const char *zSql, /* The SQL to be executed */
- sqlite3_callback xCallback, /* Invoke this callback routine */
+ tdsqlite3_callback xCallback, /* Invoke this callback routine */
void *pArg, /* First argument to xCallback() */
char **pzErrMsg /* Write error messages here */
){
int rc = SQLITE_OK; /* Return code */
const char *zLeftover; /* Tail of unprocessed SQL */
- sqlite3_stmt *pStmt = 0; /* The current SQL statement */
+ tdsqlite3_stmt *pStmt = 0; /* The current SQL statement */
char **azCols = 0; /* Names of result columns */
int callbackIsInit; /* True if callback data is initialized */
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
if( zSql==0 ) zSql = "";
- sqlite3_mutex_enter(db->mutex);
- sqlite3Error(db, SQLITE_OK);
+ tdsqlite3_mutex_enter(db->mutex);
+ tdsqlite3Error(db, SQLITE_OK);
while( rc==SQLITE_OK && zSql[0] ){
- int nCol;
+ int nCol = 0;
char **azVals = 0;
pStmt = 0;
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
+ rc = tdsqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
assert( rc==SQLITE_OK || pStmt==0 );
if( rc!=SQLITE_OK ){
continue;
@@ -112727,27 +125691,26 @@ SQLITE_API int sqlite3_exec(
zSql = zLeftover;
continue;
}
-
callbackIsInit = 0;
- nCol = sqlite3_column_count(pStmt);
while( 1 ){
int i;
- rc = sqlite3_step(pStmt);
+ rc = tdsqlite3_step(pStmt);
/* Invoke the callback function if required */
if( xCallback && (SQLITE_ROW==rc ||
(SQLITE_DONE==rc && !callbackIsInit
&& db->flags&SQLITE_NullCallback)) ){
if( !callbackIsInit ){
- azCols = sqlite3DbMallocZero(db, 2*nCol*sizeof(const char*) + 1);
+ nCol = tdsqlite3_column_count(pStmt);
+ azCols = tdsqlite3DbMallocRaw(db, (2*nCol+1)*sizeof(const char*));
if( azCols==0 ){
goto exec_out;
}
for(i=0; i<nCol; i++){
- azCols[i] = (char *)sqlite3_column_name(pStmt, i);
- /* sqlite3VdbeSetColName() installs column names as UTF8
- ** strings so there is no way for sqlite3_column_name() to fail. */
+ azCols[i] = (char *)tdsqlite3_column_name(pStmt, i);
+ /* tdsqlite3VdbeSetColName() installs column names as UTF8
+ ** strings so there is no way for tdsqlite3_column_name() to fail. */
assert( azCols[i]!=0 );
}
callbackIsInit = 1;
@@ -112755,58 +125718,56 @@ SQLITE_API int sqlite3_exec(
if( rc==SQLITE_ROW ){
azVals = &azCols[nCol];
for(i=0; i<nCol; i++){
- azVals[i] = (char *)sqlite3_column_text(pStmt, i);
- if( !azVals[i] && sqlite3_column_type(pStmt, i)!=SQLITE_NULL ){
- sqlite3OomFault(db);
+ azVals[i] = (char *)tdsqlite3_column_text(pStmt, i);
+ if( !azVals[i] && tdsqlite3_column_type(pStmt, i)!=SQLITE_NULL ){
+ tdsqlite3OomFault(db);
goto exec_out;
}
}
+ azVals[i] = 0;
}
if( xCallback(pArg, nCol, azVals, azCols) ){
/* EVIDENCE-OF: R-38229-40159 If the callback function to
- ** sqlite3_exec() returns non-zero, then sqlite3_exec() will
+ ** tdsqlite3_exec() returns non-zero, then tdsqlite3_exec() will
** return SQLITE_ABORT. */
rc = SQLITE_ABORT;
- sqlite3VdbeFinalize((Vdbe *)pStmt);
+ tdsqlite3VdbeFinalize((Vdbe *)pStmt);
pStmt = 0;
- sqlite3Error(db, SQLITE_ABORT);
+ tdsqlite3Error(db, SQLITE_ABORT);
goto exec_out;
}
}
if( rc!=SQLITE_ROW ){
- rc = sqlite3VdbeFinalize((Vdbe *)pStmt);
+ rc = tdsqlite3VdbeFinalize((Vdbe *)pStmt);
pStmt = 0;
zSql = zLeftover;
- while( sqlite3Isspace(zSql[0]) ) zSql++;
+ while( tdsqlite3Isspace(zSql[0]) ) zSql++;
break;
}
}
- sqlite3DbFree(db, azCols);
+ tdsqlite3DbFree(db, azCols);
azCols = 0;
}
exec_out:
- if( pStmt ) sqlite3VdbeFinalize((Vdbe *)pStmt);
- sqlite3DbFree(db, azCols);
+ if( pStmt ) tdsqlite3VdbeFinalize((Vdbe *)pStmt);
+ tdsqlite3DbFree(db, azCols);
- rc = sqlite3ApiExit(db, rc);
+ rc = tdsqlite3ApiExit(db, rc);
if( rc!=SQLITE_OK && pzErrMsg ){
- int nErrMsg = 1 + sqlite3Strlen30(sqlite3_errmsg(db));
- *pzErrMsg = sqlite3Malloc(nErrMsg);
- if( *pzErrMsg ){
- memcpy(*pzErrMsg, sqlite3_errmsg(db), nErrMsg);
- }else{
+ *pzErrMsg = tdsqlite3DbStrDup(0, tdsqlite3_errmsg(db));
+ if( *pzErrMsg==0 ){
rc = SQLITE_NOMEM_BKPT;
- sqlite3Error(db, SQLITE_NOMEM);
+ tdsqlite3Error(db, SQLITE_NOMEM);
}
}else if( pzErrMsg ){
*pzErrMsg = 0;
}
assert( (rc&db->errMask)==rc );
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -112828,10 +125789,10 @@ exec_out:
*/
#ifndef SQLITE_CORE
- #define SQLITE_CORE 1 /* Disable the API redefinition in sqlite3ext.h */
+ #define SQLITE_CORE 1 /* Disable the API redefinition in tdsqlite3ext.h */
#endif
-/************** Include sqlite3ext.h in the middle of loadext.c **************/
-/************** Begin file sqlite3ext.h **************************************/
+/************** Include tdsqlite3ext.h in the middle of loadext.c **************/
+/************** Begin file tdsqlite3ext.h **************************************/
/*
** 2006 June 7
**
@@ -112863,526 +125824,616 @@ exec_out:
** versions of SQLite will not be able to load each other's shared
** libraries!
*/
-struct sqlite3_api_routines {
- void * (*aggregate_context)(sqlite3_context*,int nBytes);
- int (*aggregate_count)(sqlite3_context*);
- int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
- int (*bind_double)(sqlite3_stmt*,int,double);
- int (*bind_int)(sqlite3_stmt*,int,int);
- int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
- int (*bind_null)(sqlite3_stmt*,int);
- int (*bind_parameter_count)(sqlite3_stmt*);
- int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
- const char * (*bind_parameter_name)(sqlite3_stmt*,int);
- int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
- int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
- int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
- int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
- int (*busy_timeout)(sqlite3*,int ms);
- int (*changes)(sqlite3*);
- int (*close)(sqlite3*);
- int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
+struct tdsqlite3_api_routines {
+ void * (*aggregate_context)(tdsqlite3_context*,int nBytes);
+ int (*aggregate_count)(tdsqlite3_context*);
+ int (*bind_blob)(tdsqlite3_stmt*,int,const void*,int n,void(*)(void*));
+ int (*bind_double)(tdsqlite3_stmt*,int,double);
+ int (*bind_int)(tdsqlite3_stmt*,int,int);
+ int (*bind_int64)(tdsqlite3_stmt*,int,sqlite_int64);
+ int (*bind_null)(tdsqlite3_stmt*,int);
+ int (*bind_parameter_count)(tdsqlite3_stmt*);
+ int (*bind_parameter_index)(tdsqlite3_stmt*,const char*zName);
+ const char * (*bind_parameter_name)(tdsqlite3_stmt*,int);
+ int (*bind_text)(tdsqlite3_stmt*,int,const char*,int n,void(*)(void*));
+ int (*bind_text16)(tdsqlite3_stmt*,int,const void*,int,void(*)(void*));
+ int (*bind_value)(tdsqlite3_stmt*,int,const tdsqlite3_value*);
+ int (*busy_handler)(tdsqlite3*,int(*)(void*,int),void*);
+ int (*busy_timeout)(tdsqlite3*,int ms);
+ int (*changes)(tdsqlite3*);
+ int (*close)(tdsqlite3*);
+ int (*collation_needed)(tdsqlite3*,void*,void(*)(void*,tdsqlite3*,
int eTextRep,const char*));
- int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
+ int (*collation_needed16)(tdsqlite3*,void*,void(*)(void*,tdsqlite3*,
int eTextRep,const void*));
- const void * (*column_blob)(sqlite3_stmt*,int iCol);
- int (*column_bytes)(sqlite3_stmt*,int iCol);
- int (*column_bytes16)(sqlite3_stmt*,int iCol);
- int (*column_count)(sqlite3_stmt*pStmt);
- const char * (*column_database_name)(sqlite3_stmt*,int);
- const void * (*column_database_name16)(sqlite3_stmt*,int);
- const char * (*column_decltype)(sqlite3_stmt*,int i);
- const void * (*column_decltype16)(sqlite3_stmt*,int);
- double (*column_double)(sqlite3_stmt*,int iCol);
- int (*column_int)(sqlite3_stmt*,int iCol);
- sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
- const char * (*column_name)(sqlite3_stmt*,int);
- const void * (*column_name16)(sqlite3_stmt*,int);
- const char * (*column_origin_name)(sqlite3_stmt*,int);
- const void * (*column_origin_name16)(sqlite3_stmt*,int);
- const char * (*column_table_name)(sqlite3_stmt*,int);
- const void * (*column_table_name16)(sqlite3_stmt*,int);
- const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
- const void * (*column_text16)(sqlite3_stmt*,int iCol);
- int (*column_type)(sqlite3_stmt*,int iCol);
- sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
- void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
+ const void * (*column_blob)(tdsqlite3_stmt*,int iCol);
+ int (*column_bytes)(tdsqlite3_stmt*,int iCol);
+ int (*column_bytes16)(tdsqlite3_stmt*,int iCol);
+ int (*column_count)(tdsqlite3_stmt*pStmt);
+ const char * (*column_database_name)(tdsqlite3_stmt*,int);
+ const void * (*column_database_name16)(tdsqlite3_stmt*,int);
+ const char * (*column_decltype)(tdsqlite3_stmt*,int i);
+ const void * (*column_decltype16)(tdsqlite3_stmt*,int);
+ double (*column_double)(tdsqlite3_stmt*,int iCol);
+ int (*column_int)(tdsqlite3_stmt*,int iCol);
+ sqlite_int64 (*column_int64)(tdsqlite3_stmt*,int iCol);
+ const char * (*column_name)(tdsqlite3_stmt*,int);
+ const void * (*column_name16)(tdsqlite3_stmt*,int);
+ const char * (*column_origin_name)(tdsqlite3_stmt*,int);
+ const void * (*column_origin_name16)(tdsqlite3_stmt*,int);
+ const char * (*column_table_name)(tdsqlite3_stmt*,int);
+ const void * (*column_table_name16)(tdsqlite3_stmt*,int);
+ const unsigned char * (*column_text)(tdsqlite3_stmt*,int iCol);
+ const void * (*column_text16)(tdsqlite3_stmt*,int iCol);
+ int (*column_type)(tdsqlite3_stmt*,int iCol);
+ tdsqlite3_value* (*column_value)(tdsqlite3_stmt*,int iCol);
+ void * (*commit_hook)(tdsqlite3*,int(*)(void*),void*);
int (*complete)(const char*sql);
int (*complete16)(const void*sql);
- int (*create_collation)(sqlite3*,const char*,int,void*,
+ int (*create_collation)(tdsqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*));
- int (*create_collation16)(sqlite3*,const void*,int,void*,
+ int (*create_collation16)(tdsqlite3*,const void*,int,void*,
int(*)(void*,int,const void*,int,const void*));
- int (*create_function)(sqlite3*,const char*,int,int,void*,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*));
- int (*create_function16)(sqlite3*,const void*,int,int,void*,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*));
- int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
- int (*data_count)(sqlite3_stmt*pStmt);
- sqlite3 * (*db_handle)(sqlite3_stmt*);
- int (*declare_vtab)(sqlite3*,const char*);
+ int (*create_function)(tdsqlite3*,const char*,int,int,void*,
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*));
+ int (*create_function16)(tdsqlite3*,const void*,int,int,void*,
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*));
+ int (*create_module)(tdsqlite3*,const char*,const tdsqlite3_module*,void*);
+ int (*data_count)(tdsqlite3_stmt*pStmt);
+ tdsqlite3 * (*db_handle)(tdsqlite3_stmt*);
+ int (*declare_vtab)(tdsqlite3*,const char*);
int (*enable_shared_cache)(int);
- int (*errcode)(sqlite3*db);
- const char * (*errmsg)(sqlite3*);
- const void * (*errmsg16)(sqlite3*);
- int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
- int (*expired)(sqlite3_stmt*);
- int (*finalize)(sqlite3_stmt*pStmt);
+ int (*errcode)(tdsqlite3*db);
+ const char * (*errmsg)(tdsqlite3*);
+ const void * (*errmsg16)(tdsqlite3*);
+ int (*exec)(tdsqlite3*,const char*,tdsqlite3_callback,void*,char**);
+ int (*expired)(tdsqlite3_stmt*);
+ int (*finalize)(tdsqlite3_stmt*pStmt);
void (*free)(void*);
void (*free_table)(char**result);
- int (*get_autocommit)(sqlite3*);
- void * (*get_auxdata)(sqlite3_context*,int);
- int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
+ int (*get_autocommit)(tdsqlite3*);
+ void * (*get_auxdata)(tdsqlite3_context*,int);
+ int (*get_table)(tdsqlite3*,const char*,char***,int*,int*,char**);
int (*global_recover)(void);
- void (*interruptx)(sqlite3*);
- sqlite_int64 (*last_insert_rowid)(sqlite3*);
+ void (*interruptx)(tdsqlite3*);
+ sqlite_int64 (*last_insert_rowid)(tdsqlite3*);
const char * (*libversion)(void);
int (*libversion_number)(void);
void *(*malloc)(int);
char * (*mprintf)(const char*,...);
- int (*open)(const char*,sqlite3**);
- int (*open16)(const void*,sqlite3**);
- int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
- int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
- void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
- void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
+ int (*open)(const char*,tdsqlite3**);
+ int (*open16)(const void*,tdsqlite3**);
+ int (*prepare)(tdsqlite3*,const char*,int,tdsqlite3_stmt**,const char**);
+ int (*prepare16)(tdsqlite3*,const void*,int,tdsqlite3_stmt**,const void**);
+ void * (*profile)(tdsqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
+ void (*progress_handler)(tdsqlite3*,int,int(*)(void*),void*);
void *(*realloc)(void*,int);
- int (*reset)(sqlite3_stmt*pStmt);
- void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
- void (*result_double)(sqlite3_context*,double);
- void (*result_error)(sqlite3_context*,const char*,int);
- void (*result_error16)(sqlite3_context*,const void*,int);
- void (*result_int)(sqlite3_context*,int);
- void (*result_int64)(sqlite3_context*,sqlite_int64);
- void (*result_null)(sqlite3_context*);
- void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
- void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
- void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
- void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
- void (*result_value)(sqlite3_context*,sqlite3_value*);
- void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
- int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
+ int (*reset)(tdsqlite3_stmt*pStmt);
+ void (*result_blob)(tdsqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_double)(tdsqlite3_context*,double);
+ void (*result_error)(tdsqlite3_context*,const char*,int);
+ void (*result_error16)(tdsqlite3_context*,const void*,int);
+ void (*result_int)(tdsqlite3_context*,int);
+ void (*result_int64)(tdsqlite3_context*,sqlite_int64);
+ void (*result_null)(tdsqlite3_context*);
+ void (*result_text)(tdsqlite3_context*,const char*,int,void(*)(void*));
+ void (*result_text16)(tdsqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_text16be)(tdsqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_text16le)(tdsqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_value)(tdsqlite3_context*,tdsqlite3_value*);
+ void * (*rollback_hook)(tdsqlite3*,void(*)(void*),void*);
+ int (*set_authorizer)(tdsqlite3*,int(*)(void*,int,const char*,const char*,
const char*,const char*),void*);
- void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
- char * (*snprintf)(int,char*,const char*,...);
- int (*step)(sqlite3_stmt*);
- int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
+ void (*set_auxdata)(tdsqlite3_context*,int,void*,void (*)(void*));
+ char * (*xsnprintf)(int,char*,const char*,...);
+ int (*step)(tdsqlite3_stmt*);
+ int (*table_column_metadata)(tdsqlite3*,const char*,const char*,const char*,
char const**,char const**,int*,int*,int*);
void (*thread_cleanup)(void);
- int (*total_changes)(sqlite3*);
- void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
- int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
- void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
+ int (*total_changes)(tdsqlite3*);
+ void * (*trace)(tdsqlite3*,void(*xTrace)(void*,const char*),void*);
+ int (*transfer_bindings)(tdsqlite3_stmt*,tdsqlite3_stmt*);
+ void * (*update_hook)(tdsqlite3*,void(*)(void*,int ,char const*,char const*,
sqlite_int64),void*);
- void * (*user_data)(sqlite3_context*);
- const void * (*value_blob)(sqlite3_value*);
- int (*value_bytes)(sqlite3_value*);
- int (*value_bytes16)(sqlite3_value*);
- double (*value_double)(sqlite3_value*);
- int (*value_int)(sqlite3_value*);
- sqlite_int64 (*value_int64)(sqlite3_value*);
- int (*value_numeric_type)(sqlite3_value*);
- const unsigned char * (*value_text)(sqlite3_value*);
- const void * (*value_text16)(sqlite3_value*);
- const void * (*value_text16be)(sqlite3_value*);
- const void * (*value_text16le)(sqlite3_value*);
- int (*value_type)(sqlite3_value*);
+ void * (*user_data)(tdsqlite3_context*);
+ const void * (*value_blob)(tdsqlite3_value*);
+ int (*value_bytes)(tdsqlite3_value*);
+ int (*value_bytes16)(tdsqlite3_value*);
+ double (*value_double)(tdsqlite3_value*);
+ int (*value_int)(tdsqlite3_value*);
+ sqlite_int64 (*value_int64)(tdsqlite3_value*);
+ int (*value_numeric_type)(tdsqlite3_value*);
+ const unsigned char * (*value_text)(tdsqlite3_value*);
+ const void * (*value_text16)(tdsqlite3_value*);
+ const void * (*value_text16be)(tdsqlite3_value*);
+ const void * (*value_text16le)(tdsqlite3_value*);
+ int (*value_type)(tdsqlite3_value*);
char *(*vmprintf)(const char*,va_list);
/* Added ??? */
- int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
+ int (*overload_function)(tdsqlite3*, const char *zFuncName, int nArg);
/* Added by 3.3.13 */
- int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
- int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
- int (*clear_bindings)(sqlite3_stmt*);
+ int (*prepare_v2)(tdsqlite3*,const char*,int,tdsqlite3_stmt**,const char**);
+ int (*prepare16_v2)(tdsqlite3*,const void*,int,tdsqlite3_stmt**,const void**);
+ int (*clear_bindings)(tdsqlite3_stmt*);
/* Added by 3.4.1 */
- int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
+ int (*create_module_v2)(tdsqlite3*,const char*,const tdsqlite3_module*,void*,
void (*xDestroy)(void *));
/* Added by 3.5.0 */
- int (*bind_zeroblob)(sqlite3_stmt*,int,int);
- int (*blob_bytes)(sqlite3_blob*);
- int (*blob_close)(sqlite3_blob*);
- int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
- int,sqlite3_blob**);
- int (*blob_read)(sqlite3_blob*,void*,int,int);
- int (*blob_write)(sqlite3_blob*,const void*,int,int);
- int (*create_collation_v2)(sqlite3*,const char*,int,void*,
+ int (*bind_zeroblob)(tdsqlite3_stmt*,int,int);
+ int (*blob_bytes)(tdsqlite3_blob*);
+ int (*blob_close)(tdsqlite3_blob*);
+ int (*blob_open)(tdsqlite3*,const char*,const char*,const char*,tdsqlite3_int64,
+ int,tdsqlite3_blob**);
+ int (*blob_read)(tdsqlite3_blob*,void*,int,int);
+ int (*blob_write)(tdsqlite3_blob*,const void*,int,int);
+ int (*create_collation_v2)(tdsqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*),
void(*)(void*));
- int (*file_control)(sqlite3*,const char*,int,void*);
- sqlite3_int64 (*memory_highwater)(int);
- sqlite3_int64 (*memory_used)(void);
- sqlite3_mutex *(*mutex_alloc)(int);
- void (*mutex_enter)(sqlite3_mutex*);
- void (*mutex_free)(sqlite3_mutex*);
- void (*mutex_leave)(sqlite3_mutex*);
- int (*mutex_try)(sqlite3_mutex*);
- int (*open_v2)(const char*,sqlite3**,int,const char*);
+ int (*file_control)(tdsqlite3*,const char*,int,void*);
+ tdsqlite3_int64 (*memory_highwater)(int);
+ tdsqlite3_int64 (*memory_used)(void);
+ tdsqlite3_mutex *(*mutex_alloc)(int);
+ void (*mutex_enter)(tdsqlite3_mutex*);
+ void (*mutex_free)(tdsqlite3_mutex*);
+ void (*mutex_leave)(tdsqlite3_mutex*);
+ int (*mutex_try)(tdsqlite3_mutex*);
+ int (*open_v2)(const char*,tdsqlite3**,int,const char*);
int (*release_memory)(int);
- void (*result_error_nomem)(sqlite3_context*);
- void (*result_error_toobig)(sqlite3_context*);
+ void (*result_error_nomem)(tdsqlite3_context*);
+ void (*result_error_toobig)(tdsqlite3_context*);
int (*sleep)(int);
void (*soft_heap_limit)(int);
- sqlite3_vfs *(*vfs_find)(const char*);
- int (*vfs_register)(sqlite3_vfs*,int);
- int (*vfs_unregister)(sqlite3_vfs*);
+ tdsqlite3_vfs *(*vfs_find)(const char*);
+ int (*vfs_register)(tdsqlite3_vfs*,int);
+ int (*vfs_unregister)(tdsqlite3_vfs*);
int (*xthreadsafe)(void);
- void (*result_zeroblob)(sqlite3_context*,int);
- void (*result_error_code)(sqlite3_context*,int);
+ void (*result_zeroblob)(tdsqlite3_context*,int);
+ void (*result_error_code)(tdsqlite3_context*,int);
int (*test_control)(int, ...);
void (*randomness)(int,void*);
- sqlite3 *(*context_db_handle)(sqlite3_context*);
- int (*extended_result_codes)(sqlite3*,int);
- int (*limit)(sqlite3*,int,int);
- sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
- const char *(*sql)(sqlite3_stmt*);
+ tdsqlite3 *(*context_db_handle)(tdsqlite3_context*);
+ int (*extended_result_codes)(tdsqlite3*,int);
+ int (*limit)(tdsqlite3*,int,int);
+ tdsqlite3_stmt *(*next_stmt)(tdsqlite3*,tdsqlite3_stmt*);
+ const char *(*sql)(tdsqlite3_stmt*);
int (*status)(int,int*,int*,int);
- int (*backup_finish)(sqlite3_backup*);
- sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
- int (*backup_pagecount)(sqlite3_backup*);
- int (*backup_remaining)(sqlite3_backup*);
- int (*backup_step)(sqlite3_backup*,int);
+ int (*backup_finish)(tdsqlite3_backup*);
+ tdsqlite3_backup *(*backup_init)(tdsqlite3*,const char*,tdsqlite3*,const char*);
+ int (*backup_pagecount)(tdsqlite3_backup*);
+ int (*backup_remaining)(tdsqlite3_backup*);
+ int (*backup_step)(tdsqlite3_backup*,int);
const char *(*compileoption_get)(int);
int (*compileoption_used)(const char*);
- int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*),
+ int (*create_function_v2)(tdsqlite3*,const char*,int,int,void*,
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*),
void(*xDestroy)(void*));
- int (*db_config)(sqlite3*,int,...);
- sqlite3_mutex *(*db_mutex)(sqlite3*);
- int (*db_status)(sqlite3*,int,int*,int*,int);
- int (*extended_errcode)(sqlite3*);
+ int (*db_config)(tdsqlite3*,int,...);
+ tdsqlite3_mutex *(*db_mutex)(tdsqlite3*);
+ int (*db_status)(tdsqlite3*,int,int*,int*,int);
+ int (*extended_errcode)(tdsqlite3*);
void (*log)(int,const char*,...);
- sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
+ tdsqlite3_int64 (*soft_heap_limit64)(tdsqlite3_int64);
const char *(*sourceid)(void);
- int (*stmt_status)(sqlite3_stmt*,int,int);
+ int (*stmt_status)(tdsqlite3_stmt*,int,int);
int (*strnicmp)(const char*,const char*,int);
- int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
- int (*wal_autocheckpoint)(sqlite3*,int);
- int (*wal_checkpoint)(sqlite3*,const char*);
- void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
- int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
- int (*vtab_config)(sqlite3*,int op,...);
- int (*vtab_on_conflict)(sqlite3*);
+ int (*unlock_notify)(tdsqlite3*,void(*)(void**,int),void*);
+ int (*wal_autocheckpoint)(tdsqlite3*,int);
+ int (*wal_checkpoint)(tdsqlite3*,const char*);
+ void *(*wal_hook)(tdsqlite3*,int(*)(void*,tdsqlite3*,const char*,int),void*);
+ int (*blob_reopen)(tdsqlite3_blob*,tdsqlite3_int64);
+ int (*vtab_config)(tdsqlite3*,int op,...);
+ int (*vtab_on_conflict)(tdsqlite3*);
/* Version 3.7.16 and later */
- int (*close_v2)(sqlite3*);
- const char *(*db_filename)(sqlite3*,const char*);
- int (*db_readonly)(sqlite3*,const char*);
- int (*db_release_memory)(sqlite3*);
+ int (*close_v2)(tdsqlite3*);
+ const char *(*db_filename)(tdsqlite3*,const char*);
+ int (*db_readonly)(tdsqlite3*,const char*);
+ int (*db_release_memory)(tdsqlite3*);
const char *(*errstr)(int);
- int (*stmt_busy)(sqlite3_stmt*);
- int (*stmt_readonly)(sqlite3_stmt*);
+ int (*stmt_busy)(tdsqlite3_stmt*);
+ int (*stmt_readonly)(tdsqlite3_stmt*);
int (*stricmp)(const char*,const char*);
int (*uri_boolean)(const char*,const char*,int);
- sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
+ tdsqlite3_int64 (*uri_int64)(const char*,const char*,tdsqlite3_int64);
const char *(*uri_parameter)(const char*,const char*);
- char *(*vsnprintf)(int,char*,const char*,va_list);
- int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
+ char *(*xvsnprintf)(int,char*,const char*,va_list);
+ int (*wal_checkpoint_v2)(tdsqlite3*,const char*,int,int*,int*);
/* Version 3.8.7 and later */
int (*auto_extension)(void(*)(void));
- int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
+ int (*bind_blob64)(tdsqlite3_stmt*,int,const void*,tdsqlite3_uint64,
void(*)(void*));
- int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
+ int (*bind_text64)(tdsqlite3_stmt*,int,const char*,tdsqlite3_uint64,
void(*)(void*),unsigned char);
int (*cancel_auto_extension)(void(*)(void));
- int (*load_extension)(sqlite3*,const char*,const char*,char**);
- void *(*malloc64)(sqlite3_uint64);
- sqlite3_uint64 (*msize)(void*);
- void *(*realloc64)(void*,sqlite3_uint64);
+ int (*load_extension)(tdsqlite3*,const char*,const char*,char**);
+ void *(*malloc64)(tdsqlite3_uint64);
+ tdsqlite3_uint64 (*msize)(void*);
+ void *(*realloc64)(void*,tdsqlite3_uint64);
void (*reset_auto_extension)(void);
- void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
+ void (*result_blob64)(tdsqlite3_context*,const void*,tdsqlite3_uint64,
void(*)(void*));
- void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
+ void (*result_text64)(tdsqlite3_context*,const char*,tdsqlite3_uint64,
void(*)(void*), unsigned char);
int (*strglob)(const char*,const char*);
/* Version 3.8.11 and later */
- sqlite3_value *(*value_dup)(const sqlite3_value*);
- void (*value_free)(sqlite3_value*);
- int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
- int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
+ tdsqlite3_value *(*value_dup)(const tdsqlite3_value*);
+ void (*value_free)(tdsqlite3_value*);
+ int (*result_zeroblob64)(tdsqlite3_context*,tdsqlite3_uint64);
+ int (*bind_zeroblob64)(tdsqlite3_stmt*, int, tdsqlite3_uint64);
/* Version 3.9.0 and later */
- unsigned int (*value_subtype)(sqlite3_value*);
- void (*result_subtype)(sqlite3_context*,unsigned int);
+ unsigned int (*value_subtype)(tdsqlite3_value*);
+ void (*result_subtype)(tdsqlite3_context*,unsigned int);
/* Version 3.10.0 and later */
- int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
+ int (*status64)(int,tdsqlite3_int64*,tdsqlite3_int64*,int);
int (*strlike)(const char*,const char*,unsigned int);
- int (*db_cacheflush)(sqlite3*);
+ int (*db_cacheflush)(tdsqlite3*);
/* Version 3.12.0 and later */
- int (*system_errno)(sqlite3*);
+ int (*system_errno)(tdsqlite3*);
/* Version 3.14.0 and later */
- int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
- char *(*expanded_sql)(sqlite3_stmt*);
+ int (*trace_v2)(tdsqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
+ char *(*expanded_sql)(tdsqlite3_stmt*);
+ /* Version 3.18.0 and later */
+ void (*set_last_insert_rowid)(tdsqlite3*,tdsqlite3_int64);
+ /* Version 3.20.0 and later */
+ int (*prepare_v3)(tdsqlite3*,const char*,int,unsigned int,
+ tdsqlite3_stmt**,const char**);
+ int (*prepare16_v3)(tdsqlite3*,const void*,int,unsigned int,
+ tdsqlite3_stmt**,const void**);
+ int (*bind_pointer)(tdsqlite3_stmt*,int,void*,const char*,void(*)(void*));
+ void (*result_pointer)(tdsqlite3_context*,void*,const char*,void(*)(void*));
+ void *(*value_pointer)(tdsqlite3_value*,const char*);
+ int (*vtab_nochange)(tdsqlite3_context*);
+ int (*value_nochange)(tdsqlite3_value*);
+ const char *(*vtab_collation)(tdsqlite3_index_info*,int);
+ /* Version 3.24.0 and later */
+ int (*keyword_count)(void);
+ int (*keyword_name)(int,const char**,int*);
+ int (*keyword_check)(const char*,int);
+ tdsqlite3_str *(*str_new)(tdsqlite3*);
+ char *(*str_finish)(tdsqlite3_str*);
+ void (*str_appendf)(tdsqlite3_str*, const char *zFormat, ...);
+ void (*str_vappendf)(tdsqlite3_str*, const char *zFormat, va_list);
+ void (*str_append)(tdsqlite3_str*, const char *zIn, int N);
+ void (*str_appendall)(tdsqlite3_str*, const char *zIn);
+ void (*str_appendchar)(tdsqlite3_str*, int N, char C);
+ void (*str_reset)(tdsqlite3_str*);
+ int (*str_errcode)(tdsqlite3_str*);
+ int (*str_length)(tdsqlite3_str*);
+ char *(*str_value)(tdsqlite3_str*);
+ /* Version 3.25.0 and later */
+ int (*create_window_function)(tdsqlite3*,const char*,int,int,void*,
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*),
+ void (*xValue)(tdsqlite3_context*),
+ void (*xInv)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void(*xDestroy)(void*));
+ /* Version 3.26.0 and later */
+ const char *(*normalized_sql)(tdsqlite3_stmt*);
+ /* Version 3.28.0 and later */
+ int (*stmt_isexplain)(tdsqlite3_stmt*);
+ int (*value_frombind)(tdsqlite3_value*);
+ /* Version 3.30.0 and later */
+ int (*drop_modules)(tdsqlite3*,const char**);
+ /* Version 3.31.0 and later */
+ tdsqlite3_int64 (*hard_heap_limit64)(tdsqlite3_int64);
+ const char *(*uri_key)(const char*,int);
+ const char *(*filename_database)(const char*);
+ const char *(*filename_journal)(const char*);
+ const char *(*filename_wal)(const char*);
};
/*
** This is the function signature used for all extension entry points. It
** is also defined in the file "loadext.c".
*/
-typedef int (*sqlite3_loadext_entry)(
- sqlite3 *db, /* Handle to the database. */
+typedef int (*tdsqlite3_loadext_entry)(
+ tdsqlite3 *db, /* Handle to the database. */
char **pzErrMsg, /* Used to set error string on failure. */
- const sqlite3_api_routines *pThunk /* Extension API function pointers. */
+ const tdsqlite3_api_routines *pThunk /* Extension API function pointers. */
);
/*
** The following macros redefine the API routines so that they are
-** redirected through the global sqlite3_api structure.
+** redirected through the global tdsqlite3_api structure.
**
** This header file is also used by the loadext.c source file
** (part of the main SQLite library - not an extension) so that
-** it can get access to the sqlite3_api_routines structure
+** it can get access to the tdsqlite3_api_routines structure
** definition. But the main library does not want to redefine
** the API. So the redefinition macros are only valid if the
** SQLITE_CORE macros is undefined.
*/
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
-#define sqlite3_aggregate_context sqlite3_api->aggregate_context
+#define tdsqlite3_aggregate_context tdsqlite3_api->aggregate_context
#ifndef SQLITE_OMIT_DEPRECATED
-#define sqlite3_aggregate_count sqlite3_api->aggregate_count
-#endif
-#define sqlite3_bind_blob sqlite3_api->bind_blob
-#define sqlite3_bind_double sqlite3_api->bind_double
-#define sqlite3_bind_int sqlite3_api->bind_int
-#define sqlite3_bind_int64 sqlite3_api->bind_int64
-#define sqlite3_bind_null sqlite3_api->bind_null
-#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
-#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
-#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
-#define sqlite3_bind_text sqlite3_api->bind_text
-#define sqlite3_bind_text16 sqlite3_api->bind_text16
-#define sqlite3_bind_value sqlite3_api->bind_value
-#define sqlite3_busy_handler sqlite3_api->busy_handler
-#define sqlite3_busy_timeout sqlite3_api->busy_timeout
-#define sqlite3_changes sqlite3_api->changes
-#define sqlite3_close sqlite3_api->close
-#define sqlite3_collation_needed sqlite3_api->collation_needed
-#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
-#define sqlite3_column_blob sqlite3_api->column_blob
-#define sqlite3_column_bytes sqlite3_api->column_bytes
-#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
-#define sqlite3_column_count sqlite3_api->column_count
-#define sqlite3_column_database_name sqlite3_api->column_database_name
-#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
-#define sqlite3_column_decltype sqlite3_api->column_decltype
-#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
-#define sqlite3_column_double sqlite3_api->column_double
-#define sqlite3_column_int sqlite3_api->column_int
-#define sqlite3_column_int64 sqlite3_api->column_int64
-#define sqlite3_column_name sqlite3_api->column_name
-#define sqlite3_column_name16 sqlite3_api->column_name16
-#define sqlite3_column_origin_name sqlite3_api->column_origin_name
-#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
-#define sqlite3_column_table_name sqlite3_api->column_table_name
-#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
-#define sqlite3_column_text sqlite3_api->column_text
-#define sqlite3_column_text16 sqlite3_api->column_text16
-#define sqlite3_column_type sqlite3_api->column_type
-#define sqlite3_column_value sqlite3_api->column_value
-#define sqlite3_commit_hook sqlite3_api->commit_hook
-#define sqlite3_complete sqlite3_api->complete
-#define sqlite3_complete16 sqlite3_api->complete16
-#define sqlite3_create_collation sqlite3_api->create_collation
-#define sqlite3_create_collation16 sqlite3_api->create_collation16
-#define sqlite3_create_function sqlite3_api->create_function
-#define sqlite3_create_function16 sqlite3_api->create_function16
-#define sqlite3_create_module sqlite3_api->create_module
-#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
-#define sqlite3_data_count sqlite3_api->data_count
-#define sqlite3_db_handle sqlite3_api->db_handle
-#define sqlite3_declare_vtab sqlite3_api->declare_vtab
-#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
-#define sqlite3_errcode sqlite3_api->errcode
-#define sqlite3_errmsg sqlite3_api->errmsg
-#define sqlite3_errmsg16 sqlite3_api->errmsg16
-#define sqlite3_exec sqlite3_api->exec
+#define tdsqlite3_aggregate_count tdsqlite3_api->aggregate_count
+#endif
+#define tdsqlite3_bind_blob tdsqlite3_api->bind_blob
+#define tdsqlite3_bind_double tdsqlite3_api->bind_double
+#define tdsqlite3_bind_int tdsqlite3_api->bind_int
+#define tdsqlite3_bind_int64 tdsqlite3_api->bind_int64
+#define tdsqlite3_bind_null tdsqlite3_api->bind_null
+#define tdsqlite3_bind_parameter_count tdsqlite3_api->bind_parameter_count
+#define tdsqlite3_bind_parameter_index tdsqlite3_api->bind_parameter_index
+#define tdsqlite3_bind_parameter_name tdsqlite3_api->bind_parameter_name
+#define tdsqlite3_bind_text tdsqlite3_api->bind_text
+#define tdsqlite3_bind_text16 tdsqlite3_api->bind_text16
+#define tdsqlite3_bind_value tdsqlite3_api->bind_value
+#define tdsqlite3_busy_handler tdsqlite3_api->busy_handler
+#define tdsqlite3_busy_timeout tdsqlite3_api->busy_timeout
+#define tdsqlite3_changes tdsqlite3_api->changes
+#define tdsqlite3_close tdsqlite3_api->close
+#define tdsqlite3_collation_needed tdsqlite3_api->collation_needed
+#define tdsqlite3_collation_needed16 tdsqlite3_api->collation_needed16
+#define tdsqlite3_column_blob tdsqlite3_api->column_blob
+#define tdsqlite3_column_bytes tdsqlite3_api->column_bytes
+#define tdsqlite3_column_bytes16 tdsqlite3_api->column_bytes16
+#define tdsqlite3_column_count tdsqlite3_api->column_count
+#define tdsqlite3_column_database_name tdsqlite3_api->column_database_name
+#define tdsqlite3_column_database_name16 tdsqlite3_api->column_database_name16
+#define tdsqlite3_column_decltype tdsqlite3_api->column_decltype
+#define tdsqlite3_column_decltype16 tdsqlite3_api->column_decltype16
+#define tdsqlite3_column_double tdsqlite3_api->column_double
+#define tdsqlite3_column_int tdsqlite3_api->column_int
+#define tdsqlite3_column_int64 tdsqlite3_api->column_int64
+#define tdsqlite3_column_name tdsqlite3_api->column_name
+#define tdsqlite3_column_name16 tdsqlite3_api->column_name16
+#define tdsqlite3_column_origin_name tdsqlite3_api->column_origin_name
+#define tdsqlite3_column_origin_name16 tdsqlite3_api->column_origin_name16
+#define tdsqlite3_column_table_name tdsqlite3_api->column_table_name
+#define tdsqlite3_column_table_name16 tdsqlite3_api->column_table_name16
+#define tdsqlite3_column_text tdsqlite3_api->column_text
+#define tdsqlite3_column_text16 tdsqlite3_api->column_text16
+#define tdsqlite3_column_type tdsqlite3_api->column_type
+#define tdsqlite3_column_value tdsqlite3_api->column_value
+#define tdsqlite3_commit_hook tdsqlite3_api->commit_hook
+#define tdsqlite3_complete tdsqlite3_api->complete
+#define tdsqlite3_complete16 tdsqlite3_api->complete16
+#define tdsqlite3_create_collation tdsqlite3_api->create_collation
+#define tdsqlite3_create_collation16 tdsqlite3_api->create_collation16
+#define tdsqlite3_create_function tdsqlite3_api->create_function
+#define tdsqlite3_create_function16 tdsqlite3_api->create_function16
+#define tdsqlite3_create_module tdsqlite3_api->create_module
+#define tdsqlite3_create_module_v2 tdsqlite3_api->create_module_v2
+#define tdsqlite3_data_count tdsqlite3_api->data_count
+#define tdsqlite3_db_handle tdsqlite3_api->db_handle
+#define tdsqlite3_declare_vtab tdsqlite3_api->declare_vtab
+#define tdsqlite3_enable_shared_cache tdsqlite3_api->enable_shared_cache
+#define tdsqlite3_errcode tdsqlite3_api->errcode
+#define tdsqlite3_errmsg tdsqlite3_api->errmsg
+#define tdsqlite3_errmsg16 tdsqlite3_api->errmsg16
+#define tdsqlite3_exec tdsqlite3_api->exec
#ifndef SQLITE_OMIT_DEPRECATED
-#define sqlite3_expired sqlite3_api->expired
-#endif
-#define sqlite3_finalize sqlite3_api->finalize
-#define sqlite3_free sqlite3_api->free
-#define sqlite3_free_table sqlite3_api->free_table
-#define sqlite3_get_autocommit sqlite3_api->get_autocommit
-#define sqlite3_get_auxdata sqlite3_api->get_auxdata
-#define sqlite3_get_table sqlite3_api->get_table
+#define tdsqlite3_expired tdsqlite3_api->expired
+#endif
+#define tdsqlite3_finalize tdsqlite3_api->finalize
+#define tdsqlite3_free tdsqlite3_api->free
+#define tdsqlite3_free_table tdsqlite3_api->free_table
+#define tdsqlite3_get_autocommit tdsqlite3_api->get_autocommit
+#define tdsqlite3_get_auxdata tdsqlite3_api->get_auxdata
+#define tdsqlite3_get_table tdsqlite3_api->get_table
#ifndef SQLITE_OMIT_DEPRECATED
-#define sqlite3_global_recover sqlite3_api->global_recover
-#endif
-#define sqlite3_interrupt sqlite3_api->interruptx
-#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
-#define sqlite3_libversion sqlite3_api->libversion
-#define sqlite3_libversion_number sqlite3_api->libversion_number
-#define sqlite3_malloc sqlite3_api->malloc
-#define sqlite3_mprintf sqlite3_api->mprintf
-#define sqlite3_open sqlite3_api->open
-#define sqlite3_open16 sqlite3_api->open16
-#define sqlite3_prepare sqlite3_api->prepare
-#define sqlite3_prepare16 sqlite3_api->prepare16
-#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
-#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
-#define sqlite3_profile sqlite3_api->profile
-#define sqlite3_progress_handler sqlite3_api->progress_handler
-#define sqlite3_realloc sqlite3_api->realloc
-#define sqlite3_reset sqlite3_api->reset
-#define sqlite3_result_blob sqlite3_api->result_blob
-#define sqlite3_result_double sqlite3_api->result_double
-#define sqlite3_result_error sqlite3_api->result_error
-#define sqlite3_result_error16 sqlite3_api->result_error16
-#define sqlite3_result_int sqlite3_api->result_int
-#define sqlite3_result_int64 sqlite3_api->result_int64
-#define sqlite3_result_null sqlite3_api->result_null
-#define sqlite3_result_text sqlite3_api->result_text
-#define sqlite3_result_text16 sqlite3_api->result_text16
-#define sqlite3_result_text16be sqlite3_api->result_text16be
-#define sqlite3_result_text16le sqlite3_api->result_text16le
-#define sqlite3_result_value sqlite3_api->result_value
-#define sqlite3_rollback_hook sqlite3_api->rollback_hook
-#define sqlite3_set_authorizer sqlite3_api->set_authorizer
-#define sqlite3_set_auxdata sqlite3_api->set_auxdata
-#define sqlite3_snprintf sqlite3_api->snprintf
-#define sqlite3_step sqlite3_api->step
-#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
-#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
-#define sqlite3_total_changes sqlite3_api->total_changes
-#define sqlite3_trace sqlite3_api->trace
+#define tdsqlite3_global_recover tdsqlite3_api->global_recover
+#endif
+#define tdsqlite3_interrupt tdsqlite3_api->interruptx
+#define tdsqlite3_last_insert_rowid tdsqlite3_api->last_insert_rowid
+#define tdsqlite3_libversion tdsqlite3_api->libversion
+#define tdsqlite3_libversion_number tdsqlite3_api->libversion_number
+#define tdsqlite3_malloc tdsqlite3_api->malloc
+#define tdsqlite3_mprintf tdsqlite3_api->mprintf
+#define tdsqlite3_open tdsqlite3_api->open
+#define tdsqlite3_open16 tdsqlite3_api->open16
+#define tdsqlite3_prepare tdsqlite3_api->prepare
+#define tdsqlite3_prepare16 tdsqlite3_api->prepare16
+#define tdsqlite3_prepare_v2 tdsqlite3_api->prepare_v2
+#define tdsqlite3_prepare16_v2 tdsqlite3_api->prepare16_v2
+#define tdsqlite3_profile tdsqlite3_api->profile
+#define tdsqlite3_progress_handler tdsqlite3_api->progress_handler
+#define tdsqlite3_realloc tdsqlite3_api->realloc
+#define tdsqlite3_reset tdsqlite3_api->reset
+#define tdsqlite3_result_blob tdsqlite3_api->result_blob
+#define tdsqlite3_result_double tdsqlite3_api->result_double
+#define tdsqlite3_result_error tdsqlite3_api->result_error
+#define tdsqlite3_result_error16 tdsqlite3_api->result_error16
+#define tdsqlite3_result_int tdsqlite3_api->result_int
+#define tdsqlite3_result_int64 tdsqlite3_api->result_int64
+#define tdsqlite3_result_null tdsqlite3_api->result_null
+#define tdsqlite3_result_text tdsqlite3_api->result_text
+#define tdsqlite3_result_text16 tdsqlite3_api->result_text16
+#define tdsqlite3_result_text16be tdsqlite3_api->result_text16be
+#define tdsqlite3_result_text16le tdsqlite3_api->result_text16le
+#define tdsqlite3_result_value tdsqlite3_api->result_value
+#define tdsqlite3_rollback_hook tdsqlite3_api->rollback_hook
+#define tdsqlite3_set_authorizer tdsqlite3_api->set_authorizer
+#define tdsqlite3_set_auxdata tdsqlite3_api->set_auxdata
+#define tdsqlite3_snprintf tdsqlite3_api->xsnprintf
+#define tdsqlite3_step tdsqlite3_api->step
+#define tdsqlite3_table_column_metadata tdsqlite3_api->table_column_metadata
+#define tdsqlite3_thread_cleanup tdsqlite3_api->thread_cleanup
+#define tdsqlite3_total_changes tdsqlite3_api->total_changes
+#define tdsqlite3_trace tdsqlite3_api->trace
#ifndef SQLITE_OMIT_DEPRECATED
-#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
-#endif
-#define sqlite3_update_hook sqlite3_api->update_hook
-#define sqlite3_user_data sqlite3_api->user_data
-#define sqlite3_value_blob sqlite3_api->value_blob
-#define sqlite3_value_bytes sqlite3_api->value_bytes
-#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
-#define sqlite3_value_double sqlite3_api->value_double
-#define sqlite3_value_int sqlite3_api->value_int
-#define sqlite3_value_int64 sqlite3_api->value_int64
-#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
-#define sqlite3_value_text sqlite3_api->value_text
-#define sqlite3_value_text16 sqlite3_api->value_text16
-#define sqlite3_value_text16be sqlite3_api->value_text16be
-#define sqlite3_value_text16le sqlite3_api->value_text16le
-#define sqlite3_value_type sqlite3_api->value_type
-#define sqlite3_vmprintf sqlite3_api->vmprintf
-#define sqlite3_vsnprintf sqlite3_api->vsnprintf
-#define sqlite3_overload_function sqlite3_api->overload_function
-#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
-#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
-#define sqlite3_clear_bindings sqlite3_api->clear_bindings
-#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
-#define sqlite3_blob_bytes sqlite3_api->blob_bytes
-#define sqlite3_blob_close sqlite3_api->blob_close
-#define sqlite3_blob_open sqlite3_api->blob_open
-#define sqlite3_blob_read sqlite3_api->blob_read
-#define sqlite3_blob_write sqlite3_api->blob_write
-#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
-#define sqlite3_file_control sqlite3_api->file_control
-#define sqlite3_memory_highwater sqlite3_api->memory_highwater
-#define sqlite3_memory_used sqlite3_api->memory_used
-#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
-#define sqlite3_mutex_enter sqlite3_api->mutex_enter
-#define sqlite3_mutex_free sqlite3_api->mutex_free
-#define sqlite3_mutex_leave sqlite3_api->mutex_leave
-#define sqlite3_mutex_try sqlite3_api->mutex_try
-#define sqlite3_open_v2 sqlite3_api->open_v2
-#define sqlite3_release_memory sqlite3_api->release_memory
-#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
-#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
-#define sqlite3_sleep sqlite3_api->sleep
-#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
-#define sqlite3_vfs_find sqlite3_api->vfs_find
-#define sqlite3_vfs_register sqlite3_api->vfs_register
-#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
-#define sqlite3_threadsafe sqlite3_api->xthreadsafe
-#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
-#define sqlite3_result_error_code sqlite3_api->result_error_code
-#define sqlite3_test_control sqlite3_api->test_control
-#define sqlite3_randomness sqlite3_api->randomness
-#define sqlite3_context_db_handle sqlite3_api->context_db_handle
-#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes
-#define sqlite3_limit sqlite3_api->limit
-#define sqlite3_next_stmt sqlite3_api->next_stmt
-#define sqlite3_sql sqlite3_api->sql
-#define sqlite3_status sqlite3_api->status
-#define sqlite3_backup_finish sqlite3_api->backup_finish
-#define sqlite3_backup_init sqlite3_api->backup_init
-#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
-#define sqlite3_backup_remaining sqlite3_api->backup_remaining
-#define sqlite3_backup_step sqlite3_api->backup_step
-#define sqlite3_compileoption_get sqlite3_api->compileoption_get
-#define sqlite3_compileoption_used sqlite3_api->compileoption_used
-#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
-#define sqlite3_db_config sqlite3_api->db_config
-#define sqlite3_db_mutex sqlite3_api->db_mutex
-#define sqlite3_db_status sqlite3_api->db_status
-#define sqlite3_extended_errcode sqlite3_api->extended_errcode
-#define sqlite3_log sqlite3_api->log
-#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
-#define sqlite3_sourceid sqlite3_api->sourceid
-#define sqlite3_stmt_status sqlite3_api->stmt_status
-#define sqlite3_strnicmp sqlite3_api->strnicmp
-#define sqlite3_unlock_notify sqlite3_api->unlock_notify
-#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
-#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
-#define sqlite3_wal_hook sqlite3_api->wal_hook
-#define sqlite3_blob_reopen sqlite3_api->blob_reopen
-#define sqlite3_vtab_config sqlite3_api->vtab_config
-#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict
+#define tdsqlite3_transfer_bindings tdsqlite3_api->transfer_bindings
+#endif
+#define tdsqlite3_update_hook tdsqlite3_api->update_hook
+#define tdsqlite3_user_data tdsqlite3_api->user_data
+#define tdsqlite3_value_blob tdsqlite3_api->value_blob
+#define tdsqlite3_value_bytes tdsqlite3_api->value_bytes
+#define tdsqlite3_value_bytes16 tdsqlite3_api->value_bytes16
+#define tdsqlite3_value_double tdsqlite3_api->value_double
+#define tdsqlite3_value_int tdsqlite3_api->value_int
+#define tdsqlite3_value_int64 tdsqlite3_api->value_int64
+#define tdsqlite3_value_numeric_type tdsqlite3_api->value_numeric_type
+#define tdsqlite3_value_text tdsqlite3_api->value_text
+#define tdsqlite3_value_text16 tdsqlite3_api->value_text16
+#define tdsqlite3_value_text16be tdsqlite3_api->value_text16be
+#define tdsqlite3_value_text16le tdsqlite3_api->value_text16le
+#define tdsqlite3_value_type tdsqlite3_api->value_type
+#define tdsqlite3_vmprintf tdsqlite3_api->vmprintf
+#define tdsqlite3_vsnprintf tdsqlite3_api->xvsnprintf
+#define tdsqlite3_overload_function tdsqlite3_api->overload_function
+#define tdsqlite3_prepare_v2 tdsqlite3_api->prepare_v2
+#define tdsqlite3_prepare16_v2 tdsqlite3_api->prepare16_v2
+#define tdsqlite3_clear_bindings tdsqlite3_api->clear_bindings
+#define tdsqlite3_bind_zeroblob tdsqlite3_api->bind_zeroblob
+#define tdsqlite3_blob_bytes tdsqlite3_api->blob_bytes
+#define tdsqlite3_blob_close tdsqlite3_api->blob_close
+#define tdsqlite3_blob_open tdsqlite3_api->blob_open
+#define tdsqlite3_blob_read tdsqlite3_api->blob_read
+#define tdsqlite3_blob_write tdsqlite3_api->blob_write
+#define tdsqlite3_create_collation_v2 tdsqlite3_api->create_collation_v2
+#define tdsqlite3_file_control tdsqlite3_api->file_control
+#define tdsqlite3_memory_highwater tdsqlite3_api->memory_highwater
+#define tdsqlite3_memory_used tdsqlite3_api->memory_used
+#define tdsqlite3_mutex_alloc tdsqlite3_api->mutex_alloc
+#define tdsqlite3_mutex_enter tdsqlite3_api->mutex_enter
+#define tdsqlite3_mutex_free tdsqlite3_api->mutex_free
+#define tdsqlite3_mutex_leave tdsqlite3_api->mutex_leave
+#define tdsqlite3_mutex_try tdsqlite3_api->mutex_try
+#define tdsqlite3_open_v2 tdsqlite3_api->open_v2
+#define tdsqlite3_release_memory tdsqlite3_api->release_memory
+#define tdsqlite3_result_error_nomem tdsqlite3_api->result_error_nomem
+#define tdsqlite3_result_error_toobig tdsqlite3_api->result_error_toobig
+#define tdsqlite3_sleep tdsqlite3_api->sleep
+#define tdsqlite3_soft_heap_limit tdsqlite3_api->soft_heap_limit
+#define tdsqlite3_vfs_find tdsqlite3_api->vfs_find
+#define tdsqlite3_vfs_register tdsqlite3_api->vfs_register
+#define tdsqlite3_vfs_unregister tdsqlite3_api->vfs_unregister
+#define tdsqlite3_threadsafe tdsqlite3_api->xthreadsafe
+#define tdsqlite3_result_zeroblob tdsqlite3_api->result_zeroblob
+#define tdsqlite3_result_error_code tdsqlite3_api->result_error_code
+#define tdsqlite3_test_control tdsqlite3_api->test_control
+#define tdsqlite3_randomness tdsqlite3_api->randomness
+#define tdsqlite3_context_db_handle tdsqlite3_api->context_db_handle
+#define tdsqlite3_extended_result_codes tdsqlite3_api->extended_result_codes
+#define tdsqlite3_limit tdsqlite3_api->limit
+#define tdsqlite3_next_stmt tdsqlite3_api->next_stmt
+#define tdsqlite3_sql tdsqlite3_api->sql
+#define tdsqlite3_status tdsqlite3_api->status
+#define tdsqlite3_backup_finish tdsqlite3_api->backup_finish
+#define tdsqlite3_backup_init tdsqlite3_api->backup_init
+#define tdsqlite3_backup_pagecount tdsqlite3_api->backup_pagecount
+#define tdsqlite3_backup_remaining tdsqlite3_api->backup_remaining
+#define tdsqlite3_backup_step tdsqlite3_api->backup_step
+#define tdsqlite3_compileoption_get tdsqlite3_api->compileoption_get
+#define tdsqlite3_compileoption_used tdsqlite3_api->compileoption_used
+#define tdsqlite3_create_function_v2 tdsqlite3_api->create_function_v2
+#define tdsqlite3_db_config tdsqlite3_api->db_config
+#define tdsqlite3_db_mutex tdsqlite3_api->db_mutex
+#define tdsqlite3_db_status tdsqlite3_api->db_status
+#define tdsqlite3_extended_errcode tdsqlite3_api->extended_errcode
+#define tdsqlite3_log tdsqlite3_api->log
+#define tdsqlite3_soft_heap_limit64 tdsqlite3_api->soft_heap_limit64
+#define tdsqlite3_sourceid tdsqlite3_api->sourceid
+#define tdsqlite3_stmt_status tdsqlite3_api->stmt_status
+#define tdsqlite3_strnicmp tdsqlite3_api->strnicmp
+#define tdsqlite3_unlock_notify tdsqlite3_api->unlock_notify
+#define tdsqlite3_wal_autocheckpoint tdsqlite3_api->wal_autocheckpoint
+#define tdsqlite3_wal_checkpoint tdsqlite3_api->wal_checkpoint
+#define tdsqlite3_wal_hook tdsqlite3_api->wal_hook
+#define tdsqlite3_blob_reopen tdsqlite3_api->blob_reopen
+#define tdsqlite3_vtab_config tdsqlite3_api->vtab_config
+#define tdsqlite3_vtab_on_conflict tdsqlite3_api->vtab_on_conflict
/* Version 3.7.16 and later */
-#define sqlite3_close_v2 sqlite3_api->close_v2
-#define sqlite3_db_filename sqlite3_api->db_filename
-#define sqlite3_db_readonly sqlite3_api->db_readonly
-#define sqlite3_db_release_memory sqlite3_api->db_release_memory
-#define sqlite3_errstr sqlite3_api->errstr
-#define sqlite3_stmt_busy sqlite3_api->stmt_busy
-#define sqlite3_stmt_readonly sqlite3_api->stmt_readonly
-#define sqlite3_stricmp sqlite3_api->stricmp
-#define sqlite3_uri_boolean sqlite3_api->uri_boolean
-#define sqlite3_uri_int64 sqlite3_api->uri_int64
-#define sqlite3_uri_parameter sqlite3_api->uri_parameter
-#define sqlite3_uri_vsnprintf sqlite3_api->vsnprintf
-#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
+#define tdsqlite3_close_v2 tdsqlite3_api->close_v2
+#define tdsqlite3_db_filename tdsqlite3_api->db_filename
+#define tdsqlite3_db_readonly tdsqlite3_api->db_readonly
+#define tdsqlite3_db_release_memory tdsqlite3_api->db_release_memory
+#define tdsqlite3_errstr tdsqlite3_api->errstr
+#define tdsqlite3_stmt_busy tdsqlite3_api->stmt_busy
+#define tdsqlite3_stmt_readonly tdsqlite3_api->stmt_readonly
+#define tdsqlite3_stricmp tdsqlite3_api->stricmp
+#define tdsqlite3_uri_boolean tdsqlite3_api->uri_boolean
+#define tdsqlite3_uri_int64 tdsqlite3_api->uri_int64
+#define tdsqlite3_uri_parameter tdsqlite3_api->uri_parameter
+#define tdsqlite3_uri_vsnprintf tdsqlite3_api->xvsnprintf
+#define tdsqlite3_wal_checkpoint_v2 tdsqlite3_api->wal_checkpoint_v2
/* Version 3.8.7 and later */
-#define sqlite3_auto_extension sqlite3_api->auto_extension
-#define sqlite3_bind_blob64 sqlite3_api->bind_blob64
-#define sqlite3_bind_text64 sqlite3_api->bind_text64
-#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension
-#define sqlite3_load_extension sqlite3_api->load_extension
-#define sqlite3_malloc64 sqlite3_api->malloc64
-#define sqlite3_msize sqlite3_api->msize
-#define sqlite3_realloc64 sqlite3_api->realloc64
-#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension
-#define sqlite3_result_blob64 sqlite3_api->result_blob64
-#define sqlite3_result_text64 sqlite3_api->result_text64
-#define sqlite3_strglob sqlite3_api->strglob
+#define tdsqlite3_auto_extension tdsqlite3_api->auto_extension
+#define tdsqlite3_bind_blob64 tdsqlite3_api->bind_blob64
+#define tdsqlite3_bind_text64 tdsqlite3_api->bind_text64
+#define tdsqlite3_cancel_auto_extension tdsqlite3_api->cancel_auto_extension
+#define tdsqlite3_load_extension tdsqlite3_api->load_extension
+#define tdsqlite3_malloc64 tdsqlite3_api->malloc64
+#define tdsqlite3_msize tdsqlite3_api->msize
+#define tdsqlite3_realloc64 tdsqlite3_api->realloc64
+#define tdsqlite3_reset_auto_extension tdsqlite3_api->reset_auto_extension
+#define tdsqlite3_result_blob64 tdsqlite3_api->result_blob64
+#define tdsqlite3_result_text64 tdsqlite3_api->result_text64
+#define tdsqlite3_strglob tdsqlite3_api->strglob
/* Version 3.8.11 and later */
-#define sqlite3_value_dup sqlite3_api->value_dup
-#define sqlite3_value_free sqlite3_api->value_free
-#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64
-#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64
+#define tdsqlite3_value_dup tdsqlite3_api->value_dup
+#define tdsqlite3_value_free tdsqlite3_api->value_free
+#define tdsqlite3_result_zeroblob64 tdsqlite3_api->result_zeroblob64
+#define tdsqlite3_bind_zeroblob64 tdsqlite3_api->bind_zeroblob64
/* Version 3.9.0 and later */
-#define sqlite3_value_subtype sqlite3_api->value_subtype
-#define sqlite3_result_subtype sqlite3_api->result_subtype
+#define tdsqlite3_value_subtype tdsqlite3_api->value_subtype
+#define tdsqlite3_result_subtype tdsqlite3_api->result_subtype
/* Version 3.10.0 and later */
-#define sqlite3_status64 sqlite3_api->status64
-#define sqlite3_strlike sqlite3_api->strlike
-#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush
+#define tdsqlite3_status64 tdsqlite3_api->status64
+#define tdsqlite3_strlike tdsqlite3_api->strlike
+#define tdsqlite3_db_cacheflush tdsqlite3_api->db_cacheflush
/* Version 3.12.0 and later */
-#define sqlite3_system_errno sqlite3_api->system_errno
+#define tdsqlite3_system_errno tdsqlite3_api->system_errno
/* Version 3.14.0 and later */
-#define sqlite3_trace_v2 sqlite3_api->trace_v2
-#define sqlite3_expanded_sql sqlite3_api->expanded_sql
+#define tdsqlite3_trace_v2 tdsqlite3_api->trace_v2
+#define tdsqlite3_expanded_sql tdsqlite3_api->expanded_sql
+/* Version 3.18.0 and later */
+#define tdsqlite3_set_last_insert_rowid tdsqlite3_api->set_last_insert_rowid
+/* Version 3.20.0 and later */
+#define tdsqlite3_prepare_v3 tdsqlite3_api->prepare_v3
+#define tdsqlite3_prepare16_v3 tdsqlite3_api->prepare16_v3
+#define tdsqlite3_bind_pointer tdsqlite3_api->bind_pointer
+#define tdsqlite3_result_pointer tdsqlite3_api->result_pointer
+#define tdsqlite3_value_pointer tdsqlite3_api->value_pointer
+/* Version 3.22.0 and later */
+#define tdsqlite3_vtab_nochange tdsqlite3_api->vtab_nochange
+#define tdsqlite3_value_nochange tdsqlite3_api->value_nochange
+#define tdsqlite3_vtab_collation tdsqlite3_api->vtab_collation
+/* Version 3.24.0 and later */
+#define tdsqlite3_keyword_count tdsqlite3_api->keyword_count
+#define tdsqlite3_keyword_name tdsqlite3_api->keyword_name
+#define tdsqlite3_keyword_check tdsqlite3_api->keyword_check
+#define tdsqlite3_str_new tdsqlite3_api->str_new
+#define tdsqlite3_str_finish tdsqlite3_api->str_finish
+#define tdsqlite3_str_appendf tdsqlite3_api->str_appendf
+#define tdsqlite3_str_vappendf tdsqlite3_api->str_vappendf
+#define tdsqlite3_str_append tdsqlite3_api->str_append
+#define tdsqlite3_str_appendall tdsqlite3_api->str_appendall
+#define tdsqlite3_str_appendchar tdsqlite3_api->str_appendchar
+#define tdsqlite3_str_reset tdsqlite3_api->str_reset
+#define tdsqlite3_str_errcode tdsqlite3_api->str_errcode
+#define tdsqlite3_str_length tdsqlite3_api->str_length
+#define tdsqlite3_str_value tdsqlite3_api->str_value
+/* Version 3.25.0 and later */
+#define tdsqlite3_create_window_function tdsqlite3_api->create_window_function
+/* Version 3.26.0 and later */
+#define tdsqlite3_normalized_sql tdsqlite3_api->normalized_sql
+/* Version 3.28.0 and later */
+#define tdsqlite3_stmt_isexplain tdsqlite3_api->isexplain
+#define tdsqlite3_value_frombind tdsqlite3_api->frombind
+/* Version 3.30.0 and later */
+#define tdsqlite3_drop_modules tdsqlite3_api->drop_modules
+/* Version 3.31.0 andn later */
+#define tdsqlite3_hard_heap_limit64 tdsqlite3_api->hard_heap_limit64
+#define tdsqlite3_uri_key tdsqlite3_api->uri_key
+#define tdsqlite3_filename_database tdsqlite3_api->filename_database
+#define tdsqlite3_filename_journal tdsqlite3_api->filename_journal
+#define tdsqlite3_filename_wal tdsqlite3_api->filename_wal
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
/* This case when the file really is being compiled as a loadable
** extension */
-# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0;
-# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v;
+# define SQLITE_EXTENSION_INIT1 const tdsqlite3_api_routines *tdsqlite3_api=0;
+# define SQLITE_EXTENSION_INIT2(v) tdsqlite3_api=v;
# define SQLITE_EXTENSION_INIT3 \
- extern const sqlite3_api_routines *sqlite3_api;
+ extern const tdsqlite3_api_routines *tdsqlite3_api;
#else
/* This case when the file is being statically linked into the
** application */
@@ -113393,10 +126444,9 @@ typedef int (*sqlite3_loadext_entry)(
#endif /* SQLITE3EXT_H */
-/************** End of sqlite3ext.h ******************************************/
+/************** End of tdsqlite3ext.h ******************************************/
/************** Continuing where we left off in loadext.c ********************/
/* #include "sqliteInt.h" */
-/* #include <string.h> */
#ifndef SQLITE_OMIT_LOAD_EXTENSION
/*
@@ -113405,91 +126455,93 @@ typedef int (*sqlite3_loadext_entry)(
** for any missing APIs.
*/
#ifndef SQLITE_ENABLE_COLUMN_METADATA
-# define sqlite3_column_database_name 0
-# define sqlite3_column_database_name16 0
-# define sqlite3_column_table_name 0
-# define sqlite3_column_table_name16 0
-# define sqlite3_column_origin_name 0
-# define sqlite3_column_origin_name16 0
+# define tdsqlite3_column_database_name 0
+# define tdsqlite3_column_database_name16 0
+# define tdsqlite3_column_table_name 0
+# define tdsqlite3_column_table_name16 0
+# define tdsqlite3_column_origin_name 0
+# define tdsqlite3_column_origin_name16 0
#endif
#ifdef SQLITE_OMIT_AUTHORIZATION
-# define sqlite3_set_authorizer 0
+# define tdsqlite3_set_authorizer 0
#endif
#ifdef SQLITE_OMIT_UTF16
-# define sqlite3_bind_text16 0
-# define sqlite3_collation_needed16 0
-# define sqlite3_column_decltype16 0
-# define sqlite3_column_name16 0
-# define sqlite3_column_text16 0
-# define sqlite3_complete16 0
-# define sqlite3_create_collation16 0
-# define sqlite3_create_function16 0
-# define sqlite3_errmsg16 0
-# define sqlite3_open16 0
-# define sqlite3_prepare16 0
-# define sqlite3_prepare16_v2 0
-# define sqlite3_result_error16 0
-# define sqlite3_result_text16 0
-# define sqlite3_result_text16be 0
-# define sqlite3_result_text16le 0
-# define sqlite3_value_text16 0
-# define sqlite3_value_text16be 0
-# define sqlite3_value_text16le 0
-# define sqlite3_column_database_name16 0
-# define sqlite3_column_table_name16 0
-# define sqlite3_column_origin_name16 0
+# define tdsqlite3_bind_text16 0
+# define tdsqlite3_collation_needed16 0
+# define tdsqlite3_column_decltype16 0
+# define tdsqlite3_column_name16 0
+# define tdsqlite3_column_text16 0
+# define tdsqlite3_complete16 0
+# define tdsqlite3_create_collation16 0
+# define tdsqlite3_create_function16 0
+# define tdsqlite3_errmsg16 0
+# define tdsqlite3_open16 0
+# define tdsqlite3_prepare16 0
+# define tdsqlite3_prepare16_v2 0
+# define tdsqlite3_prepare16_v3 0
+# define tdsqlite3_result_error16 0
+# define tdsqlite3_result_text16 0
+# define tdsqlite3_result_text16be 0
+# define tdsqlite3_result_text16le 0
+# define tdsqlite3_value_text16 0
+# define tdsqlite3_value_text16be 0
+# define tdsqlite3_value_text16le 0
+# define tdsqlite3_column_database_name16 0
+# define tdsqlite3_column_table_name16 0
+# define tdsqlite3_column_origin_name16 0
#endif
#ifdef SQLITE_OMIT_COMPLETE
-# define sqlite3_complete 0
-# define sqlite3_complete16 0
+# define tdsqlite3_complete 0
+# define tdsqlite3_complete16 0
#endif
#ifdef SQLITE_OMIT_DECLTYPE
-# define sqlite3_column_decltype16 0
-# define sqlite3_column_decltype 0
+# define tdsqlite3_column_decltype16 0
+# define tdsqlite3_column_decltype 0
#endif
#ifdef SQLITE_OMIT_PROGRESS_CALLBACK
-# define sqlite3_progress_handler 0
+# define tdsqlite3_progress_handler 0
#endif
#ifdef SQLITE_OMIT_VIRTUALTABLE
-# define sqlite3_create_module 0
-# define sqlite3_create_module_v2 0
-# define sqlite3_declare_vtab 0
-# define sqlite3_vtab_config 0
-# define sqlite3_vtab_on_conflict 0
+# define tdsqlite3_create_module 0
+# define tdsqlite3_create_module_v2 0
+# define tdsqlite3_declare_vtab 0
+# define tdsqlite3_vtab_config 0
+# define tdsqlite3_vtab_on_conflict 0
+# define tdsqlite3_vtab_collation 0
#endif
#ifdef SQLITE_OMIT_SHARED_CACHE
-# define sqlite3_enable_shared_cache 0
+# define tdsqlite3_enable_shared_cache 0
#endif
#if defined(SQLITE_OMIT_TRACE) || defined(SQLITE_OMIT_DEPRECATED)
-# define sqlite3_profile 0
-# define sqlite3_trace 0
+# define tdsqlite3_profile 0
+# define tdsqlite3_trace 0
#endif
#ifdef SQLITE_OMIT_GET_TABLE
-# define sqlite3_free_table 0
-# define sqlite3_get_table 0
+# define tdsqlite3_free_table 0
+# define tdsqlite3_get_table 0
#endif
#ifdef SQLITE_OMIT_INCRBLOB
-#define sqlite3_bind_zeroblob 0
-#define sqlite3_blob_bytes 0
-#define sqlite3_blob_close 0
-#define sqlite3_blob_open 0
-#define sqlite3_blob_read 0
-#define sqlite3_blob_write 0
-#define sqlite3_blob_reopen 0
+#define tdsqlite3_bind_zeroblob 0
+#define tdsqlite3_blob_bytes 0
+#define tdsqlite3_blob_close 0
+#define tdsqlite3_blob_open 0
+#define tdsqlite3_blob_read 0
+#define tdsqlite3_blob_write 0
+#define tdsqlite3_blob_reopen 0
#endif
#if defined(SQLITE_OMIT_TRACE)
-# define sqlite3_trace_v2 0
+# define tdsqlite3_trace_v2 0
#endif
/*
@@ -113502,178 +126554,178 @@ typedef int (*sqlite3_loadext_entry)(
** in order to preserve backwards compatibility.
**
** Extensions that use newer APIs should first call the
-** sqlite3_libversion_number() to make sure that the API they
+** tdsqlite3_libversion_number() to make sure that the API they
** intend to use is supported by the library. Extensions should
** also check to make sure that the pointer to the function is
** not NULL before calling it.
*/
-static const sqlite3_api_routines sqlite3Apis = {
- sqlite3_aggregate_context,
+static const tdsqlite3_api_routines tdsqlite3Apis = {
+ tdsqlite3_aggregate_context,
#ifndef SQLITE_OMIT_DEPRECATED
- sqlite3_aggregate_count,
+ tdsqlite3_aggregate_count,
#else
0,
#endif
- sqlite3_bind_blob,
- sqlite3_bind_double,
- sqlite3_bind_int,
- sqlite3_bind_int64,
- sqlite3_bind_null,
- sqlite3_bind_parameter_count,
- sqlite3_bind_parameter_index,
- sqlite3_bind_parameter_name,
- sqlite3_bind_text,
- sqlite3_bind_text16,
- sqlite3_bind_value,
- sqlite3_busy_handler,
- sqlite3_busy_timeout,
- sqlite3_changes,
- sqlite3_close,
- sqlite3_collation_needed,
- sqlite3_collation_needed16,
- sqlite3_column_blob,
- sqlite3_column_bytes,
- sqlite3_column_bytes16,
- sqlite3_column_count,
- sqlite3_column_database_name,
- sqlite3_column_database_name16,
- sqlite3_column_decltype,
- sqlite3_column_decltype16,
- sqlite3_column_double,
- sqlite3_column_int,
- sqlite3_column_int64,
- sqlite3_column_name,
- sqlite3_column_name16,
- sqlite3_column_origin_name,
- sqlite3_column_origin_name16,
- sqlite3_column_table_name,
- sqlite3_column_table_name16,
- sqlite3_column_text,
- sqlite3_column_text16,
- sqlite3_column_type,
- sqlite3_column_value,
- sqlite3_commit_hook,
- sqlite3_complete,
- sqlite3_complete16,
- sqlite3_create_collation,
- sqlite3_create_collation16,
- sqlite3_create_function,
- sqlite3_create_function16,
- sqlite3_create_module,
- sqlite3_data_count,
- sqlite3_db_handle,
- sqlite3_declare_vtab,
- sqlite3_enable_shared_cache,
- sqlite3_errcode,
- sqlite3_errmsg,
- sqlite3_errmsg16,
- sqlite3_exec,
+ tdsqlite3_bind_blob,
+ tdsqlite3_bind_double,
+ tdsqlite3_bind_int,
+ tdsqlite3_bind_int64,
+ tdsqlite3_bind_null,
+ tdsqlite3_bind_parameter_count,
+ tdsqlite3_bind_parameter_index,
+ tdsqlite3_bind_parameter_name,
+ tdsqlite3_bind_text,
+ tdsqlite3_bind_text16,
+ tdsqlite3_bind_value,
+ tdsqlite3_busy_handler,
+ tdsqlite3_busy_timeout,
+ tdsqlite3_changes,
+ tdsqlite3_close,
+ tdsqlite3_collation_needed,
+ tdsqlite3_collation_needed16,
+ tdsqlite3_column_blob,
+ tdsqlite3_column_bytes,
+ tdsqlite3_column_bytes16,
+ tdsqlite3_column_count,
+ tdsqlite3_column_database_name,
+ tdsqlite3_column_database_name16,
+ tdsqlite3_column_decltype,
+ tdsqlite3_column_decltype16,
+ tdsqlite3_column_double,
+ tdsqlite3_column_int,
+ tdsqlite3_column_int64,
+ tdsqlite3_column_name,
+ tdsqlite3_column_name16,
+ tdsqlite3_column_origin_name,
+ tdsqlite3_column_origin_name16,
+ tdsqlite3_column_table_name,
+ tdsqlite3_column_table_name16,
+ tdsqlite3_column_text,
+ tdsqlite3_column_text16,
+ tdsqlite3_column_type,
+ tdsqlite3_column_value,
+ tdsqlite3_commit_hook,
+ tdsqlite3_complete,
+ tdsqlite3_complete16,
+ tdsqlite3_create_collation,
+ tdsqlite3_create_collation16,
+ tdsqlite3_create_function,
+ tdsqlite3_create_function16,
+ tdsqlite3_create_module,
+ tdsqlite3_data_count,
+ tdsqlite3_db_handle,
+ tdsqlite3_declare_vtab,
+ tdsqlite3_enable_shared_cache,
+ tdsqlite3_errcode,
+ tdsqlite3_errmsg,
+ tdsqlite3_errmsg16,
+ tdsqlite3_exec,
#ifndef SQLITE_OMIT_DEPRECATED
- sqlite3_expired,
+ tdsqlite3_expired,
#else
0,
#endif
- sqlite3_finalize,
- sqlite3_free,
- sqlite3_free_table,
- sqlite3_get_autocommit,
- sqlite3_get_auxdata,
- sqlite3_get_table,
- 0, /* Was sqlite3_global_recover(), but that function is deprecated */
- sqlite3_interrupt,
- sqlite3_last_insert_rowid,
- sqlite3_libversion,
- sqlite3_libversion_number,
- sqlite3_malloc,
- sqlite3_mprintf,
- sqlite3_open,
- sqlite3_open16,
- sqlite3_prepare,
- sqlite3_prepare16,
- sqlite3_profile,
- sqlite3_progress_handler,
- sqlite3_realloc,
- sqlite3_reset,
- sqlite3_result_blob,
- sqlite3_result_double,
- sqlite3_result_error,
- sqlite3_result_error16,
- sqlite3_result_int,
- sqlite3_result_int64,
- sqlite3_result_null,
- sqlite3_result_text,
- sqlite3_result_text16,
- sqlite3_result_text16be,
- sqlite3_result_text16le,
- sqlite3_result_value,
- sqlite3_rollback_hook,
- sqlite3_set_authorizer,
- sqlite3_set_auxdata,
- sqlite3_snprintf,
- sqlite3_step,
- sqlite3_table_column_metadata,
+ tdsqlite3_finalize,
+ tdsqlite3_free,
+ tdsqlite3_free_table,
+ tdsqlite3_get_autocommit,
+ tdsqlite3_get_auxdata,
+ tdsqlite3_get_table,
+ 0, /* Was tdsqlite3_global_recover(), but that function is deprecated */
+ tdsqlite3_interrupt,
+ tdsqlite3_last_insert_rowid,
+ tdsqlite3_libversion,
+ tdsqlite3_libversion_number,
+ tdsqlite3_malloc,
+ tdsqlite3_mprintf,
+ tdsqlite3_open,
+ tdsqlite3_open16,
+ tdsqlite3_prepare,
+ tdsqlite3_prepare16,
+ tdsqlite3_profile,
+ tdsqlite3_progress_handler,
+ tdsqlite3_realloc,
+ tdsqlite3_reset,
+ tdsqlite3_result_blob,
+ tdsqlite3_result_double,
+ tdsqlite3_result_error,
+ tdsqlite3_result_error16,
+ tdsqlite3_result_int,
+ tdsqlite3_result_int64,
+ tdsqlite3_result_null,
+ tdsqlite3_result_text,
+ tdsqlite3_result_text16,
+ tdsqlite3_result_text16be,
+ tdsqlite3_result_text16le,
+ tdsqlite3_result_value,
+ tdsqlite3_rollback_hook,
+ tdsqlite3_set_authorizer,
+ tdsqlite3_set_auxdata,
+ tdsqlite3_snprintf,
+ tdsqlite3_step,
+ tdsqlite3_table_column_metadata,
#ifndef SQLITE_OMIT_DEPRECATED
- sqlite3_thread_cleanup,
+ tdsqlite3_thread_cleanup,
#else
0,
#endif
- sqlite3_total_changes,
- sqlite3_trace,
+ tdsqlite3_total_changes,
+ tdsqlite3_trace,
#ifndef SQLITE_OMIT_DEPRECATED
- sqlite3_transfer_bindings,
+ tdsqlite3_transfer_bindings,
#else
0,
#endif
- sqlite3_update_hook,
- sqlite3_user_data,
- sqlite3_value_blob,
- sqlite3_value_bytes,
- sqlite3_value_bytes16,
- sqlite3_value_double,
- sqlite3_value_int,
- sqlite3_value_int64,
- sqlite3_value_numeric_type,
- sqlite3_value_text,
- sqlite3_value_text16,
- sqlite3_value_text16be,
- sqlite3_value_text16le,
- sqlite3_value_type,
- sqlite3_vmprintf,
+ tdsqlite3_update_hook,
+ tdsqlite3_user_data,
+ tdsqlite3_value_blob,
+ tdsqlite3_value_bytes,
+ tdsqlite3_value_bytes16,
+ tdsqlite3_value_double,
+ tdsqlite3_value_int,
+ tdsqlite3_value_int64,
+ tdsqlite3_value_numeric_type,
+ tdsqlite3_value_text,
+ tdsqlite3_value_text16,
+ tdsqlite3_value_text16be,
+ tdsqlite3_value_text16le,
+ tdsqlite3_value_type,
+ tdsqlite3_vmprintf,
/*
** The original API set ends here. All extensions can call any
** of the APIs above provided that the pointer is not NULL. But
** before calling APIs that follow, extension should check the
- ** sqlite3_libversion_number() to make sure they are dealing with
+ ** tdsqlite3_libversion_number() to make sure they are dealing with
** a library that is new enough to support that API.
*************************************************************************
*/
- sqlite3_overload_function,
+ tdsqlite3_overload_function,
/*
** Added after 3.3.13
*/
- sqlite3_prepare_v2,
- sqlite3_prepare16_v2,
- sqlite3_clear_bindings,
+ tdsqlite3_prepare_v2,
+ tdsqlite3_prepare16_v2,
+ tdsqlite3_clear_bindings,
/*
** Added for 3.4.1
*/
- sqlite3_create_module_v2,
+ tdsqlite3_create_module_v2,
/*
** Added for 3.5.0
*/
- sqlite3_bind_zeroblob,
- sqlite3_blob_bytes,
- sqlite3_blob_close,
- sqlite3_blob_open,
- sqlite3_blob_read,
- sqlite3_blob_write,
- sqlite3_create_collation_v2,
- sqlite3_file_control,
- sqlite3_memory_highwater,
- sqlite3_memory_used,
+ tdsqlite3_bind_zeroblob,
+ tdsqlite3_blob_bytes,
+ tdsqlite3_blob_close,
+ tdsqlite3_blob_open,
+ tdsqlite3_blob_read,
+ tdsqlite3_blob_write,
+ tdsqlite3_create_collation_v2,
+ tdsqlite3_file_control,
+ tdsqlite3_memory_highwater,
+ tdsqlite3_memory_used,
#ifdef SQLITE_MUTEX_OMIT
0,
0,
@@ -113681,154 +126733,204 @@ static const sqlite3_api_routines sqlite3Apis = {
0,
0,
#else
- sqlite3_mutex_alloc,
- sqlite3_mutex_enter,
- sqlite3_mutex_free,
- sqlite3_mutex_leave,
- sqlite3_mutex_try,
-#endif
- sqlite3_open_v2,
- sqlite3_release_memory,
- sqlite3_result_error_nomem,
- sqlite3_result_error_toobig,
- sqlite3_sleep,
- sqlite3_soft_heap_limit,
- sqlite3_vfs_find,
- sqlite3_vfs_register,
- sqlite3_vfs_unregister,
+ tdsqlite3_mutex_alloc,
+ tdsqlite3_mutex_enter,
+ tdsqlite3_mutex_free,
+ tdsqlite3_mutex_leave,
+ tdsqlite3_mutex_try,
+#endif
+ tdsqlite3_open_v2,
+ tdsqlite3_release_memory,
+ tdsqlite3_result_error_nomem,
+ tdsqlite3_result_error_toobig,
+ tdsqlite3_sleep,
+ tdsqlite3_soft_heap_limit,
+ tdsqlite3_vfs_find,
+ tdsqlite3_vfs_register,
+ tdsqlite3_vfs_unregister,
/*
** Added for 3.5.8
*/
- sqlite3_threadsafe,
- sqlite3_result_zeroblob,
- sqlite3_result_error_code,
- sqlite3_test_control,
- sqlite3_randomness,
- sqlite3_context_db_handle,
+ tdsqlite3_threadsafe,
+ tdsqlite3_result_zeroblob,
+ tdsqlite3_result_error_code,
+ tdsqlite3_test_control,
+ tdsqlite3_randomness,
+ tdsqlite3_context_db_handle,
/*
** Added for 3.6.0
*/
- sqlite3_extended_result_codes,
- sqlite3_limit,
- sqlite3_next_stmt,
- sqlite3_sql,
- sqlite3_status,
+ tdsqlite3_extended_result_codes,
+ tdsqlite3_limit,
+ tdsqlite3_next_stmt,
+ tdsqlite3_sql,
+ tdsqlite3_status,
/*
** Added for 3.7.4
*/
- sqlite3_backup_finish,
- sqlite3_backup_init,
- sqlite3_backup_pagecount,
- sqlite3_backup_remaining,
- sqlite3_backup_step,
+ tdsqlite3_backup_finish,
+ tdsqlite3_backup_init,
+ tdsqlite3_backup_pagecount,
+ tdsqlite3_backup_remaining,
+ tdsqlite3_backup_step,
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
- sqlite3_compileoption_get,
- sqlite3_compileoption_used,
+ tdsqlite3_compileoption_get,
+ tdsqlite3_compileoption_used,
#else
0,
0,
#endif
- sqlite3_create_function_v2,
- sqlite3_db_config,
- sqlite3_db_mutex,
- sqlite3_db_status,
- sqlite3_extended_errcode,
- sqlite3_log,
- sqlite3_soft_heap_limit64,
- sqlite3_sourceid,
- sqlite3_stmt_status,
- sqlite3_strnicmp,
+ tdsqlite3_create_function_v2,
+ tdsqlite3_db_config,
+ tdsqlite3_db_mutex,
+ tdsqlite3_db_status,
+ tdsqlite3_extended_errcode,
+ tdsqlite3_log,
+ tdsqlite3_soft_heap_limit64,
+ tdsqlite3_sourceid,
+ tdsqlite3_stmt_status,
+ tdsqlite3_strnicmp,
#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
- sqlite3_unlock_notify,
+ tdsqlite3_unlock_notify,
#else
0,
#endif
#ifndef SQLITE_OMIT_WAL
- sqlite3_wal_autocheckpoint,
- sqlite3_wal_checkpoint,
- sqlite3_wal_hook,
+ tdsqlite3_wal_autocheckpoint,
+ tdsqlite3_wal_checkpoint,
+ tdsqlite3_wal_hook,
#else
0,
0,
0,
#endif
- sqlite3_blob_reopen,
- sqlite3_vtab_config,
- sqlite3_vtab_on_conflict,
- sqlite3_close_v2,
- sqlite3_db_filename,
- sqlite3_db_readonly,
- sqlite3_db_release_memory,
- sqlite3_errstr,
- sqlite3_stmt_busy,
- sqlite3_stmt_readonly,
- sqlite3_stricmp,
- sqlite3_uri_boolean,
- sqlite3_uri_int64,
- sqlite3_uri_parameter,
- sqlite3_vsnprintf,
- sqlite3_wal_checkpoint_v2,
+ tdsqlite3_blob_reopen,
+ tdsqlite3_vtab_config,
+ tdsqlite3_vtab_on_conflict,
+ tdsqlite3_close_v2,
+ tdsqlite3_db_filename,
+ tdsqlite3_db_readonly,
+ tdsqlite3_db_release_memory,
+ tdsqlite3_errstr,
+ tdsqlite3_stmt_busy,
+ tdsqlite3_stmt_readonly,
+ tdsqlite3_stricmp,
+ tdsqlite3_uri_boolean,
+ tdsqlite3_uri_int64,
+ tdsqlite3_uri_parameter,
+ tdsqlite3_vsnprintf,
+ tdsqlite3_wal_checkpoint_v2,
/* Version 3.8.7 and later */
- sqlite3_auto_extension,
- sqlite3_bind_blob64,
- sqlite3_bind_text64,
- sqlite3_cancel_auto_extension,
- sqlite3_load_extension,
- sqlite3_malloc64,
- sqlite3_msize,
- sqlite3_realloc64,
- sqlite3_reset_auto_extension,
- sqlite3_result_blob64,
- sqlite3_result_text64,
- sqlite3_strglob,
+ tdsqlite3_auto_extension,
+ tdsqlite3_bind_blob64,
+ tdsqlite3_bind_text64,
+ tdsqlite3_cancel_auto_extension,
+ tdsqlite3_load_extension,
+ tdsqlite3_malloc64,
+ tdsqlite3_msize,
+ tdsqlite3_realloc64,
+ tdsqlite3_reset_auto_extension,
+ tdsqlite3_result_blob64,
+ tdsqlite3_result_text64,
+ tdsqlite3_strglob,
/* Version 3.8.11 and later */
- (sqlite3_value*(*)(const sqlite3_value*))sqlite3_value_dup,
- sqlite3_value_free,
- sqlite3_result_zeroblob64,
- sqlite3_bind_zeroblob64,
+ (tdsqlite3_value*(*)(const tdsqlite3_value*))tdsqlite3_value_dup,
+ tdsqlite3_value_free,
+ tdsqlite3_result_zeroblob64,
+ tdsqlite3_bind_zeroblob64,
/* Version 3.9.0 and later */
- sqlite3_value_subtype,
- sqlite3_result_subtype,
+ tdsqlite3_value_subtype,
+ tdsqlite3_result_subtype,
/* Version 3.10.0 and later */
- sqlite3_status64,
- sqlite3_strlike,
- sqlite3_db_cacheflush,
+ tdsqlite3_status64,
+ tdsqlite3_strlike,
+ tdsqlite3_db_cacheflush,
/* Version 3.12.0 and later */
- sqlite3_system_errno,
+ tdsqlite3_system_errno,
/* Version 3.14.0 and later */
- sqlite3_trace_v2,
- sqlite3_expanded_sql
+ tdsqlite3_trace_v2,
+ tdsqlite3_expanded_sql,
+ /* Version 3.18.0 and later */
+ tdsqlite3_set_last_insert_rowid,
+ /* Version 3.20.0 and later */
+ tdsqlite3_prepare_v3,
+ tdsqlite3_prepare16_v3,
+ tdsqlite3_bind_pointer,
+ tdsqlite3_result_pointer,
+ tdsqlite3_value_pointer,
+ /* Version 3.22.0 and later */
+ tdsqlite3_vtab_nochange,
+ tdsqlite3_value_nochange,
+ tdsqlite3_vtab_collation,
+ /* Version 3.24.0 and later */
+ tdsqlite3_keyword_count,
+ tdsqlite3_keyword_name,
+ tdsqlite3_keyword_check,
+ tdsqlite3_str_new,
+ tdsqlite3_str_finish,
+ tdsqlite3_str_appendf,
+ tdsqlite3_str_vappendf,
+ tdsqlite3_str_append,
+ tdsqlite3_str_appendall,
+ tdsqlite3_str_appendchar,
+ tdsqlite3_str_reset,
+ tdsqlite3_str_errcode,
+ tdsqlite3_str_length,
+ tdsqlite3_str_value,
+ /* Version 3.25.0 and later */
+ tdsqlite3_create_window_function,
+ /* Version 3.26.0 and later */
+#ifdef SQLITE_ENABLE_NORMALIZE
+ tdsqlite3_normalized_sql,
+#else
+ 0,
+#endif
+ /* Version 3.28.0 and later */
+ tdsqlite3_stmt_isexplain,
+ tdsqlite3_value_frombind,
+ /* Version 3.30.0 and later */
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ tdsqlite3_drop_modules,
+#else
+ 0,
+#endif
+ /* Version 3.31.0 and later */
+ tdsqlite3_hard_heap_limit64,
+ tdsqlite3_uri_key,
+ tdsqlite3_filename_database,
+ tdsqlite3_filename_journal,
+ tdsqlite3_filename_wal,
};
/*
** Attempt to load an SQLite extension library contained in the file
** zFile. The entry point is zProc. zProc may be 0 in which case a
-** default entry point name (sqlite3_extension_init) is used. Use
+** default entry point name (tdsqlite3_extension_init) is used. Use
** of the default name is recommended.
**
** Return SQLITE_OK on success and SQLITE_ERROR if something goes wrong.
**
** If an error occurs and pzErrMsg is not 0, then fill *pzErrMsg with
** error message text. The calling function should free this memory
-** by calling sqlite3DbFree(db, ).
+** by calling tdsqlite3DbFree(db, ).
*/
-static int sqlite3LoadExtension(
- sqlite3 *db, /* Load the extension into this database connection */
+static int tdsqlite3LoadExtension(
+ tdsqlite3 *db, /* Load the extension into this database connection */
const char *zFile, /* Name of the shared library containing extension */
- const char *zProc, /* Entry point. Use "sqlite3_extension_init" if 0 */
+ const char *zProc, /* Entry point. Use "tdsqlite3_extension_init" if 0 */
char **pzErrMsg /* Put error message here if not 0 */
){
- sqlite3_vfs *pVfs = db->pVfs;
+ tdsqlite3_vfs *pVfs = db->pVfs;
void *handle;
- sqlite3_loadext_entry xInit;
+ tdsqlite3_loadext_entry xInit;
char *zErrmsg = 0;
const char *zEntry;
char *zAltEntry = 0;
void **aHandle;
- u64 nMsg = 300 + sqlite3Strlen30(zFile);
+ u64 nMsg = 300 + tdsqlite3Strlen30(zFile);
int ii;
int rc;
@@ -113849,124 +126951,124 @@ static int sqlite3LoadExtension(
/* Ticket #1863. To avoid a creating security problems for older
** applications that relink against newer versions of SQLite, the
** ability to run load_extension is turned off by default. One
- ** must call either sqlite3_enable_load_extension(db) or
- ** sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, 0)
+ ** must call either tdsqlite3_enable_load_extension(db) or
+ ** tdsqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, 0)
** to turn on extension loading.
*/
if( (db->flags & SQLITE_LoadExtension)==0 ){
if( pzErrMsg ){
- *pzErrMsg = sqlite3_mprintf("not authorized");
+ *pzErrMsg = tdsqlite3_mprintf("not authorized");
}
return SQLITE_ERROR;
}
- zEntry = zProc ? zProc : "sqlite3_extension_init";
+ zEntry = zProc ? zProc : "tdsqlite3_extension_init";
- handle = sqlite3OsDlOpen(pVfs, zFile);
+ handle = tdsqlite3OsDlOpen(pVfs, zFile);
#if SQLITE_OS_UNIX || SQLITE_OS_WIN
for(ii=0; ii<ArraySize(azEndings) && handle==0; ii++){
- char *zAltFile = sqlite3_mprintf("%s.%s", zFile, azEndings[ii]);
+ char *zAltFile = tdsqlite3_mprintf("%s.%s", zFile, azEndings[ii]);
if( zAltFile==0 ) return SQLITE_NOMEM_BKPT;
- handle = sqlite3OsDlOpen(pVfs, zAltFile);
- sqlite3_free(zAltFile);
+ handle = tdsqlite3OsDlOpen(pVfs, zAltFile);
+ tdsqlite3_free(zAltFile);
}
#endif
if( handle==0 ){
if( pzErrMsg ){
- *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg);
+ *pzErrMsg = zErrmsg = tdsqlite3_malloc64(nMsg);
if( zErrmsg ){
- sqlite3_snprintf(nMsg, zErrmsg,
+ tdsqlite3_snprintf(nMsg, zErrmsg,
"unable to open shared library [%s]", zFile);
- sqlite3OsDlError(pVfs, nMsg-1, zErrmsg);
+ tdsqlite3OsDlError(pVfs, nMsg-1, zErrmsg);
}
}
return SQLITE_ERROR;
}
- xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry);
+ xInit = (tdsqlite3_loadext_entry)tdsqlite3OsDlSym(pVfs, handle, zEntry);
/* If no entry point was specified and the default legacy
- ** entry point name "sqlite3_extension_init" was not found, then
- ** construct an entry point name "sqlite3_X_init" where the X is
+ ** entry point name "tdsqlite3_extension_init" was not found, then
+ ** construct an entry point name "tdsqlite3_X_init" where the X is
** replaced by the lowercase value of every ASCII alphabetic
** character in the filename after the last "/" upto the first ".",
** and eliding the first three characters if they are "lib".
** Examples:
**
- ** /usr/local/lib/libExample5.4.3.so ==> sqlite3_example_init
- ** C:/lib/mathfuncs.dll ==> sqlite3_mathfuncs_init
+ ** /usr/local/lib/libExample5.4.3.so ==> tdsqlite3_example_init
+ ** C:/lib/mathfuncs.dll ==> tdsqlite3_mathfuncs_init
*/
if( xInit==0 && zProc==0 ){
int iFile, iEntry, c;
- int ncFile = sqlite3Strlen30(zFile);
- zAltEntry = sqlite3_malloc64(ncFile+30);
+ int ncFile = tdsqlite3Strlen30(zFile);
+ zAltEntry = tdsqlite3_malloc64(ncFile+30);
if( zAltEntry==0 ){
- sqlite3OsDlClose(pVfs, handle);
+ tdsqlite3OsDlClose(pVfs, handle);
return SQLITE_NOMEM_BKPT;
}
- memcpy(zAltEntry, "sqlite3_", 8);
+ memcpy(zAltEntry, "tdsqlite3_", 8);
for(iFile=ncFile-1; iFile>=0 && zFile[iFile]!='/'; iFile--){}
iFile++;
- if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3;
+ if( tdsqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3;
for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){
- if( sqlite3Isalpha(c) ){
- zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c];
+ if( tdsqlite3Isalpha(c) ){
+ zAltEntry[iEntry++] = (char)tdsqlite3UpperToLower[(unsigned)c];
}
}
memcpy(zAltEntry+iEntry, "_init", 6);
zEntry = zAltEntry;
- xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry);
+ xInit = (tdsqlite3_loadext_entry)tdsqlite3OsDlSym(pVfs, handle, zEntry);
}
if( xInit==0 ){
if( pzErrMsg ){
- nMsg += sqlite3Strlen30(zEntry);
- *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg);
+ nMsg += tdsqlite3Strlen30(zEntry);
+ *pzErrMsg = zErrmsg = tdsqlite3_malloc64(nMsg);
if( zErrmsg ){
- sqlite3_snprintf(nMsg, zErrmsg,
+ tdsqlite3_snprintf(nMsg, zErrmsg,
"no entry point [%s] in shared library [%s]", zEntry, zFile);
- sqlite3OsDlError(pVfs, nMsg-1, zErrmsg);
+ tdsqlite3OsDlError(pVfs, nMsg-1, zErrmsg);
}
}
- sqlite3OsDlClose(pVfs, handle);
- sqlite3_free(zAltEntry);
+ tdsqlite3OsDlClose(pVfs, handle);
+ tdsqlite3_free(zAltEntry);
return SQLITE_ERROR;
}
- sqlite3_free(zAltEntry);
- rc = xInit(db, &zErrmsg, &sqlite3Apis);
+ tdsqlite3_free(zAltEntry);
+ rc = xInit(db, &zErrmsg, &tdsqlite3Apis);
if( rc ){
if( rc==SQLITE_OK_LOAD_PERMANENTLY ) return SQLITE_OK;
if( pzErrMsg ){
- *pzErrMsg = sqlite3_mprintf("error during initialization: %s", zErrmsg);
+ *pzErrMsg = tdsqlite3_mprintf("error during initialization: %s", zErrmsg);
}
- sqlite3_free(zErrmsg);
- sqlite3OsDlClose(pVfs, handle);
+ tdsqlite3_free(zErrmsg);
+ tdsqlite3OsDlClose(pVfs, handle);
return SQLITE_ERROR;
}
/* Append the new shared library handle to the db->aExtension array. */
- aHandle = sqlite3DbMallocZero(db, sizeof(handle)*(db->nExtension+1));
+ aHandle = tdsqlite3DbMallocZero(db, sizeof(handle)*(db->nExtension+1));
if( aHandle==0 ){
return SQLITE_NOMEM_BKPT;
}
if( db->nExtension>0 ){
memcpy(aHandle, db->aExtension, sizeof(handle)*db->nExtension);
}
- sqlite3DbFree(db, db->aExtension);
+ tdsqlite3DbFree(db, db->aExtension);
db->aExtension = aHandle;
db->aExtension[db->nExtension++] = handle;
return SQLITE_OK;
}
-SQLITE_API int sqlite3_load_extension(
- sqlite3 *db, /* Load the extension into this database connection */
+SQLITE_API int tdsqlite3_load_extension(
+ tdsqlite3 *db, /* Load the extension into this database connection */
const char *zFile, /* Name of the shared library containing extension */
- const char *zProc, /* Entry point. Use "sqlite3_extension_init" if 0 */
+ const char *zProc, /* Entry point. Use "tdsqlite3_extension_init" if 0 */
char **pzErrMsg /* Put error message here if not 0 */
){
int rc;
- sqlite3_mutex_enter(db->mutex);
- rc = sqlite3LoadExtension(db, zFile, zProc, pzErrMsg);
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
+ rc = tdsqlite3LoadExtension(db, zFile, zProc, pzErrMsg);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -113974,27 +127076,27 @@ SQLITE_API int sqlite3_load_extension(
** Call this routine when the database connection is closing in order
** to clean up loaded extensions
*/
-SQLITE_PRIVATE void sqlite3CloseExtensions(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3CloseExtensions(tdsqlite3 *db){
int i;
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
for(i=0; i<db->nExtension; i++){
- sqlite3OsDlClose(db->pVfs, db->aExtension[i]);
+ tdsqlite3OsDlClose(db->pVfs, db->aExtension[i]);
}
- sqlite3DbFree(db, db->aExtension);
+ tdsqlite3DbFree(db, db->aExtension);
}
/*
** Enable or disable extension loading. Extension loading is disabled by
** default so as not to open security holes in older applications.
*/
-SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff){
- sqlite3_mutex_enter(db->mutex);
+SQLITE_API int tdsqlite3_enable_load_extension(tdsqlite3 *db, int onoff){
+ tdsqlite3_mutex_enter(db->mutex);
if( onoff ){
db->flags |= SQLITE_LoadExtension|SQLITE_LoadExtFunc;
}else{
- db->flags &= ~(SQLITE_LoadExtension|SQLITE_LoadExtFunc);
+ db->flags &= ~(u64)(SQLITE_LoadExtension|SQLITE_LoadExtFunc);
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
}
@@ -114007,25 +127109,25 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff){
** This list is shared across threads. The SQLITE_MUTEX_STATIC_MASTER
** mutex must be held while accessing this list.
*/
-typedef struct sqlite3AutoExtList sqlite3AutoExtList;
-static SQLITE_WSD struct sqlite3AutoExtList {
+typedef struct tdsqlite3AutoExtList tdsqlite3AutoExtList;
+static SQLITE_WSD struct tdsqlite3AutoExtList {
u32 nExt; /* Number of entries in aExt[] */
void (**aExt)(void); /* Pointers to the extension init functions */
-} sqlite3Autoext = { 0, 0 };
+} tdsqlite3Autoext = { 0, 0 };
/* The "wsdAutoext" macro will resolve to the autoextension
** state vector. If writable static data is unsupported on the target,
** we have to locate the state vector at run-time. In the more common
** case where writable static data is supported, wsdStat can refer directly
-** to the "sqlite3Autoext" state vector declared above.
+** to the "tdsqlite3Autoext" state vector declared above.
*/
#ifdef SQLITE_OMIT_WSD
# define wsdAutoextInit \
- sqlite3AutoExtList *x = &GLOBAL(sqlite3AutoExtList,sqlite3Autoext)
+ tdsqlite3AutoExtList *x = &GLOBAL(tdsqlite3AutoExtList,tdsqlite3Autoext)
# define wsdAutoext x[0]
#else
# define wsdAutoextInit
-# define wsdAutoext sqlite3Autoext
+# define wsdAutoext tdsqlite3Autoext
#endif
@@ -114033,12 +127135,12 @@ static SQLITE_WSD struct sqlite3AutoExtList {
** Register a statically linked extension that is automatically
** loaded by every new database connection.
*/
-SQLITE_API int sqlite3_auto_extension(
+SQLITE_API int tdsqlite3_auto_extension(
void (*xInit)(void)
){
int rc = SQLITE_OK;
#ifndef SQLITE_OMIT_AUTOINIT
- rc = sqlite3_initialize();
+ rc = tdsqlite3_initialize();
if( rc ){
return rc;
}else
@@ -114046,17 +127148,17 @@ SQLITE_API int sqlite3_auto_extension(
{
u32 i;
#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
+ tdsqlite3_mutex *mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
#endif
wsdAutoextInit;
- sqlite3_mutex_enter(mutex);
+ tdsqlite3_mutex_enter(mutex);
for(i=0; i<wsdAutoext.nExt; i++){
if( wsdAutoext.aExt[i]==xInit ) break;
}
if( i==wsdAutoext.nExt ){
u64 nByte = (wsdAutoext.nExt+1)*sizeof(wsdAutoext.aExt[0]);
void (**aNew)(void);
- aNew = sqlite3_realloc64(wsdAutoext.aExt, nByte);
+ aNew = tdsqlite3_realloc64(wsdAutoext.aExt, nByte);
if( aNew==0 ){
rc = SQLITE_NOMEM_BKPT;
}else{
@@ -114065,14 +127167,14 @@ SQLITE_API int sqlite3_auto_extension(
wsdAutoext.nExt++;
}
}
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
assert( (rc&0xff)==rc );
return rc;
}
}
/*
-** Cancel a prior call to sqlite3_auto_extension. Remove xInit from the
+** Cancel a prior call to tdsqlite3_auto_extension. Remove xInit from the
** set of routines that is invoked for each new database connection, if it
** is currently on the list. If xInit is not on the list, then this
** routine is a no-op.
@@ -114080,16 +127182,16 @@ SQLITE_API int sqlite3_auto_extension(
** Return 1 if xInit was found on the list and removed. Return 0 if xInit
** was not on the list.
*/
-SQLITE_API int sqlite3_cancel_auto_extension(
+SQLITE_API int tdsqlite3_cancel_auto_extension(
void (*xInit)(void)
){
#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
+ tdsqlite3_mutex *mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
#endif
int i;
int n = 0;
wsdAutoextInit;
- sqlite3_mutex_enter(mutex);
+ tdsqlite3_mutex_enter(mutex);
for(i=(int)wsdAutoext.nExt-1; i>=0; i--){
if( wsdAutoext.aExt[i]==xInit ){
wsdAutoext.nExt--;
@@ -114098,27 +127200,27 @@ SQLITE_API int sqlite3_cancel_auto_extension(
break;
}
}
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
return n;
}
/*
** Reset the automatic extension loading mechanism.
*/
-SQLITE_API void sqlite3_reset_auto_extension(void){
+SQLITE_API void tdsqlite3_reset_auto_extension(void){
#ifndef SQLITE_OMIT_AUTOINIT
- if( sqlite3_initialize()==SQLITE_OK )
+ if( tdsqlite3_initialize()==SQLITE_OK )
#endif
{
#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
+ tdsqlite3_mutex *mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
#endif
wsdAutoextInit;
- sqlite3_mutex_enter(mutex);
- sqlite3_free(wsdAutoext.aExt);
+ tdsqlite3_mutex_enter(mutex);
+ tdsqlite3_free(wsdAutoext.aExt);
wsdAutoext.aExt = 0;
wsdAutoext.nExt = 0;
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
}
}
@@ -114127,11 +127229,11 @@ SQLITE_API void sqlite3_reset_auto_extension(void){
**
** If anything goes wrong, set an error in the database connection.
*/
-SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3AutoLoadExtensions(tdsqlite3 *db){
u32 i;
int go = 1;
int rc;
- sqlite3_loadext_entry xInit;
+ tdsqlite3_loadext_entry xInit;
wsdAutoextInit;
if( wsdAutoext.nExt==0 ){
@@ -114141,28 +127243,28 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){
for(i=0; go; i++){
char *zErrmsg;
#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
+ tdsqlite3_mutex *mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);
#endif
#ifdef SQLITE_OMIT_LOAD_EXTENSION
- const sqlite3_api_routines *pThunk = 0;
+ const tdsqlite3_api_routines *pThunk = 0;
#else
- const sqlite3_api_routines *pThunk = &sqlite3Apis;
+ const tdsqlite3_api_routines *pThunk = &tdsqlite3Apis;
#endif
- sqlite3_mutex_enter(mutex);
+ tdsqlite3_mutex_enter(mutex);
if( i>=wsdAutoext.nExt ){
xInit = 0;
go = 0;
}else{
- xInit = (sqlite3_loadext_entry)wsdAutoext.aExt[i];
+ xInit = (tdsqlite3_loadext_entry)wsdAutoext.aExt[i];
}
- sqlite3_mutex_leave(mutex);
+ tdsqlite3_mutex_leave(mutex);
zErrmsg = 0;
if( xInit && (rc = xInit(db, &zErrmsg, pThunk))!=0 ){
- sqlite3ErrorWithMsg(db, rc,
+ tdsqlite3ErrorWithMsg(db, rc,
"automatic extension loading failed: %s", zErrmsg);
go = 0;
}
- sqlite3_free(zErrmsg);
+ tdsqlite3_free(zErrmsg);
}
}
@@ -114205,6 +127307,8 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){
** ../tool/mkpragmatab.tcl. To update the set of pragmas, edit
** that script and rerun it.
*/
+
+/* The various pragma types */
#define PragTyp_HEADER_VALUE 0
#define PragTyp_AUTO_VACUUM 1
#define PragTyp_FLAG 2
@@ -114220,450 +127324,667 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){
#define PragTyp_ENCODING 12
#define PragTyp_FOREIGN_KEY_CHECK 13
#define PragTyp_FOREIGN_KEY_LIST 14
-#define PragTyp_INCREMENTAL_VACUUM 15
-#define PragTyp_INDEX_INFO 16
-#define PragTyp_INDEX_LIST 17
-#define PragTyp_INTEGRITY_CHECK 18
-#define PragTyp_JOURNAL_MODE 19
-#define PragTyp_JOURNAL_SIZE_LIMIT 20
-#define PragTyp_LOCK_PROXY_FILE 21
-#define PragTyp_LOCKING_MODE 22
-#define PragTyp_PAGE_COUNT 23
-#define PragTyp_MMAP_SIZE 24
-#define PragTyp_PAGE_SIZE 25
-#define PragTyp_SECURE_DELETE 26
-#define PragTyp_SHRINK_MEMORY 27
-#define PragTyp_SOFT_HEAP_LIMIT 28
-#define PragTyp_STATS 29
-#define PragTyp_SYNCHRONOUS 30
-#define PragTyp_TABLE_INFO 31
-#define PragTyp_TEMP_STORE 32
-#define PragTyp_TEMP_STORE_DIRECTORY 33
-#define PragTyp_THREADS 34
-#define PragTyp_WAL_AUTOCHECKPOINT 35
-#define PragTyp_WAL_CHECKPOINT 36
-#define PragTyp_ACTIVATE_EXTENSIONS 37
-#define PragTyp_HEXKEY 38
-#define PragTyp_KEY 39
-#define PragTyp_REKEY 40
-#define PragTyp_LOCK_STATUS 41
-#define PragTyp_PARSER_TRACE 42
-#define PragFlag_NeedSchema 0x01
-#define PragFlag_ReadOnly 0x02
-static const struct sPragmaNames {
- const char *const zName; /* Name of pragma */
- u8 ePragTyp; /* PragTyp_XXX value */
- u8 mPragFlag; /* Zero or more PragFlag_XXX values */
- u32 iArg; /* Extra argument */
-} aPragmaNames[] = {
+#define PragTyp_FUNCTION_LIST 15
+#define PragTyp_HARD_HEAP_LIMIT 16
+#define PragTyp_INCREMENTAL_VACUUM 17
+#define PragTyp_INDEX_INFO 18
+#define PragTyp_INDEX_LIST 19
+#define PragTyp_INTEGRITY_CHECK 20
+#define PragTyp_JOURNAL_MODE 21
+#define PragTyp_JOURNAL_SIZE_LIMIT 22
+#define PragTyp_LOCK_PROXY_FILE 23
+#define PragTyp_LOCKING_MODE 24
+#define PragTyp_PAGE_COUNT 25
+#define PragTyp_MMAP_SIZE 26
+#define PragTyp_MODULE_LIST 27
+#define PragTyp_OPTIMIZE 28
+#define PragTyp_PAGE_SIZE 29
+#define PragTyp_PRAGMA_LIST 30
+#define PragTyp_SECURE_DELETE 31
+#define PragTyp_SHRINK_MEMORY 32
+#define PragTyp_SOFT_HEAP_LIMIT 33
+#define PragTyp_SYNCHRONOUS 34
+#define PragTyp_TABLE_INFO 35
+#define PragTyp_TEMP_STORE 36
+#define PragTyp_TEMP_STORE_DIRECTORY 37
+#define PragTyp_THREADS 38
+#define PragTyp_WAL_AUTOCHECKPOINT 39
+#define PragTyp_WAL_CHECKPOINT 40
+#define PragTyp_ACTIVATE_EXTENSIONS 41
+#define PragTyp_KEY 42
+#define PragTyp_LOCK_STATUS 43
+#define PragTyp_STATS 44
+
+/* Property flags associated with various pragma. */
+#define PragFlg_NeedSchema 0x01 /* Force schema load before running */
+#define PragFlg_NoColumns 0x02 /* OP_ResultRow called with zero columns */
+#define PragFlg_NoColumns1 0x04 /* zero columns if RHS argument is present */
+#define PragFlg_ReadOnly 0x08 /* Read-only HEADER_VALUE */
+#define PragFlg_Result0 0x10 /* Acts as query when no argument */
+#define PragFlg_Result1 0x20 /* Acts as query when has one argument */
+#define PragFlg_SchemaOpt 0x40 /* Schema restricts name search if present */
+#define PragFlg_SchemaReq 0x80 /* Schema required - "main" is default */
+
+/* Names of columns for pragmas that return multi-column result
+** or that return single-column results where the name of the
+** result column is different from the name of the pragma
+*/
+static const char *const pragCName[] = {
+ /* 0 */ "id", /* Used by: foreign_key_list */
+ /* 1 */ "seq",
+ /* 2 */ "table",
+ /* 3 */ "from",
+ /* 4 */ "to",
+ /* 5 */ "on_update",
+ /* 6 */ "on_delete",
+ /* 7 */ "match",
+ /* 8 */ "cid", /* Used by: table_xinfo */
+ /* 9 */ "name",
+ /* 10 */ "type",
+ /* 11 */ "notnull",
+ /* 12 */ "dflt_value",
+ /* 13 */ "pk",
+ /* 14 */ "hidden",
+ /* table_info reuses 8 */
+ /* 15 */ "seqno", /* Used by: index_xinfo */
+ /* 16 */ "cid",
+ /* 17 */ "name",
+ /* 18 */ "desc",
+ /* 19 */ "coll",
+ /* 20 */ "key",
+ /* 21 */ "name", /* Used by: function_list */
+ /* 22 */ "builtin",
+ /* 23 */ "type",
+ /* 24 */ "enc",
+ /* 25 */ "narg",
+ /* 26 */ "flags",
+ /* 27 */ "tbl", /* Used by: stats */
+ /* 28 */ "idx",
+ /* 29 */ "wdth",
+ /* 30 */ "hght",
+ /* 31 */ "flgs",
+ /* 32 */ "seq", /* Used by: index_list */
+ /* 33 */ "name",
+ /* 34 */ "unique",
+ /* 35 */ "origin",
+ /* 36 */ "partial",
+ /* 37 */ "table", /* Used by: foreign_key_check */
+ /* 38 */ "rowid",
+ /* 39 */ "parent",
+ /* 40 */ "fkid",
+ /* index_info reuses 15 */
+ /* 41 */ "seq", /* Used by: database_list */
+ /* 42 */ "name",
+ /* 43 */ "file",
+ /* 44 */ "busy", /* Used by: wal_checkpoint */
+ /* 45 */ "log",
+ /* 46 */ "checkpointed",
+ /* collation_list reuses 32 */
+ /* 47 */ "database", /* Used by: lock_status */
+ /* 48 */ "status",
+ /* 49 */ "cache_size", /* Used by: default_cache_size */
+ /* module_list pragma_list reuses 9 */
+ /* 50 */ "timeout", /* Used by: busy_timeout */
+};
+
+/* Definitions of all built-in pragmas */
+typedef struct PragmaName {
+ const char *const zName; /* Name of pragma */
+ u8 ePragTyp; /* PragTyp_XXX value */
+ u8 mPragFlg; /* Zero or more PragFlg_XXX values */
+ u8 iPragCName; /* Start of column names in pragCName[] */
+ u8 nPragCName; /* Num of col names. 0 means use pragma name */
+ u64 iArg; /* Extra argument */
+} PragmaName;
+static const PragmaName aPragmaName[] = {
#if defined(SQLITE_HAS_CODEC) || defined(SQLITE_ENABLE_CEROD)
- { /* zName: */ "activate_extensions",
- /* ePragTyp: */ PragTyp_ACTIVATE_EXTENSIONS,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "activate_extensions",
+ /* ePragTyp: */ PragTyp_ACTIVATE_EXTENSIONS,
+ /* ePragFlg: */ 0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
- { /* zName: */ "application_id",
- /* ePragTyp: */ PragTyp_HEADER_VALUE,
- /* ePragFlag: */ 0,
- /* iArg: */ BTREE_APPLICATION_ID },
+ {/* zName: */ "application_id",
+ /* ePragTyp: */ PragTyp_HEADER_VALUE,
+ /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ BTREE_APPLICATION_ID },
#endif
#if !defined(SQLITE_OMIT_AUTOVACUUM)
- { /* zName: */ "auto_vacuum",
- /* ePragTyp: */ PragTyp_AUTO_VACUUM,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "auto_vacuum",
+ /* ePragTyp: */ PragTyp_AUTO_VACUUM,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
#if !defined(SQLITE_OMIT_AUTOMATIC_INDEX)
- { /* zName: */ "automatic_index",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_AutoIndex },
-#endif
-#endif
- { /* zName: */ "busy_timeout",
- /* ePragTyp: */ PragTyp_BUSY_TIMEOUT,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "automatic_index",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_AutoIndex },
+#endif
+#endif
+ {/* zName: */ "busy_timeout",
+ /* ePragTyp: */ PragTyp_BUSY_TIMEOUT,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 50, 1,
+ /* iArg: */ 0 },
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
- { /* zName: */ "cache_size",
- /* ePragTyp: */ PragTyp_CACHE_SIZE,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "cache_size",
+ /* ePragTyp: */ PragTyp_CACHE_SIZE,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "cache_spill",
- /* ePragTyp: */ PragTyp_CACHE_SPILL,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
-#endif
- { /* zName: */ "case_sensitive_like",
- /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
- { /* zName: */ "cell_size_check",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_CellSizeCk },
+ {/* zName: */ "cache_spill",
+ /* ePragTyp: */ PragTyp_CACHE_SPILL,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+#endif
+#if !defined(SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA)
+ {/* zName: */ "case_sensitive_like",
+ /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE,
+ /* ePragFlg: */ PragFlg_NoColumns,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+#endif
+ {/* zName: */ "cell_size_check",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_CellSizeCk },
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "checkpoint_fullfsync",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_CkptFullFSync },
+ {/* zName: */ "checkpoint_fullfsync",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_CkptFullFSync },
#endif
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
- { /* zName: */ "collation_list",
- /* ePragTyp: */ PragTyp_COLLATION_LIST,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "collation_list",
+ /* ePragTyp: */ PragTyp_COLLATION_LIST,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 32, 2,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS)
- { /* zName: */ "compile_options",
- /* ePragTyp: */ PragTyp_COMPILE_OPTIONS,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "compile_options",
+ /* ePragTyp: */ PragTyp_COMPILE_OPTIONS,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "count_changes",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_CountRows },
+ {/* zName: */ "count_changes",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_CountRows },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_OS_WIN
- { /* zName: */ "data_store_directory",
- /* ePragTyp: */ PragTyp_DATA_STORE_DIRECTORY,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "data_store_directory",
+ /* ePragTyp: */ PragTyp_DATA_STORE_DIRECTORY,
+ /* ePragFlg: */ PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
- { /* zName: */ "data_version",
- /* ePragTyp: */ PragTyp_HEADER_VALUE,
- /* ePragFlag: */ PragFlag_ReadOnly,
- /* iArg: */ BTREE_DATA_VERSION },
+ {/* zName: */ "data_version",
+ /* ePragTyp: */ PragTyp_HEADER_VALUE,
+ /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ BTREE_DATA_VERSION },
#endif
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
- { /* zName: */ "database_list",
- /* ePragTyp: */ PragTyp_DATABASE_LIST,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "database_list",
+ /* ePragTyp: */ PragTyp_DATABASE_LIST,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0,
+ /* ColNames: */ 41, 3,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED)
- { /* zName: */ "default_cache_size",
- /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "default_cache_size",
+ /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
+ /* ColNames: */ 49, 1,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
- { /* zName: */ "defer_foreign_keys",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_DeferFKs },
+ {/* zName: */ "defer_foreign_keys",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_DeferFKs },
#endif
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "empty_result_callbacks",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_NullCallback },
+ {/* zName: */ "empty_result_callbacks",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_NullCallback },
#endif
#if !defined(SQLITE_OMIT_UTF16)
- { /* zName: */ "encoding",
- /* ePragTyp: */ PragTyp_ENCODING,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "encoding",
+ /* ePragTyp: */ PragTyp_ENCODING,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
- { /* zName: */ "foreign_key_check",
- /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "foreign_key_check",
+ /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0,
+ /* ColNames: */ 37, 4,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FOREIGN_KEY)
- { /* zName: */ "foreign_key_list",
- /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "foreign_key_list",
+ /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
+ /* ColNames: */ 0, 8,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
#if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
- { /* zName: */ "foreign_keys",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_ForeignKeys },
+ {/* zName: */ "foreign_keys",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_ForeignKeys },
#endif
#endif
#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
- { /* zName: */ "freelist_count",
- /* ePragTyp: */ PragTyp_HEADER_VALUE,
- /* ePragFlag: */ PragFlag_ReadOnly,
- /* iArg: */ BTREE_FREE_PAGE_COUNT },
+ {/* zName: */ "freelist_count",
+ /* ePragTyp: */ PragTyp_HEADER_VALUE,
+ /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ BTREE_FREE_PAGE_COUNT },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "full_column_names",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_FullColNames },
- { /* zName: */ "fullfsync",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_FullFSync },
+ {/* zName: */ "full_column_names",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_FullColNames },
+ {/* zName: */ "fullfsync",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_FullFSync },
#endif
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+ {/* zName: */ "function_list",
+ /* ePragTyp: */ PragTyp_FUNCTION_LIST,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 21, 6,
+ /* iArg: */ 0 },
+#endif
+#endif
+ {/* zName: */ "hard_heap_limit",
+ /* ePragTyp: */ PragTyp_HARD_HEAP_LIMIT,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#if defined(SQLITE_HAS_CODEC)
- { /* zName: */ "hexkey",
- /* ePragTyp: */ PragTyp_HEXKEY,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
- { /* zName: */ "hexrekey",
- /* ePragTyp: */ PragTyp_HEXKEY,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "hexkey",
+ /* ePragTyp: */ PragTyp_KEY,
+ /* ePragFlg: */ 0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 2 },
+ {/* zName: */ "hexrekey",
+ /* ePragTyp: */ PragTyp_KEY,
+ /* ePragFlg: */ 0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 3 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
#if !defined(SQLITE_OMIT_CHECK)
- { /* zName: */ "ignore_check_constraints",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_IgnoreChecks },
+ {/* zName: */ "ignore_check_constraints",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_IgnoreChecks },
#endif
#endif
#if !defined(SQLITE_OMIT_AUTOVACUUM)
- { /* zName: */ "incremental_vacuum",
- /* ePragTyp: */ PragTyp_INCREMENTAL_VACUUM,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "incremental_vacuum",
+ /* ePragTyp: */ PragTyp_INCREMENTAL_VACUUM,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_NoColumns,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
- { /* zName: */ "index_info",
- /* ePragTyp: */ PragTyp_INDEX_INFO,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
- { /* zName: */ "index_list",
- /* ePragTyp: */ PragTyp_INDEX_LIST,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
- { /* zName: */ "index_xinfo",
- /* ePragTyp: */ PragTyp_INDEX_INFO,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 1 },
+ {/* zName: */ "index_info",
+ /* ePragTyp: */ PragTyp_INDEX_INFO,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
+ /* ColNames: */ 15, 3,
+ /* iArg: */ 0 },
+ {/* zName: */ "index_list",
+ /* ePragTyp: */ PragTyp_INDEX_LIST,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
+ /* ColNames: */ 32, 5,
+ /* iArg: */ 0 },
+ {/* zName: */ "index_xinfo",
+ /* ePragTyp: */ PragTyp_INDEX_INFO,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
+ /* ColNames: */ 15, 6,
+ /* iArg: */ 1 },
#endif
#if !defined(SQLITE_OMIT_INTEGRITY_CHECK)
- { /* zName: */ "integrity_check",
- /* ePragTyp: */ PragTyp_INTEGRITY_CHECK,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "integrity_check",
+ /* ePragTyp: */ PragTyp_INTEGRITY_CHECK,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
- { /* zName: */ "journal_mode",
- /* ePragTyp: */ PragTyp_JOURNAL_MODE,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
- { /* zName: */ "journal_size_limit",
- /* ePragTyp: */ PragTyp_JOURNAL_SIZE_LIMIT,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "journal_mode",
+ /* ePragTyp: */ PragTyp_JOURNAL_MODE,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+ {/* zName: */ "journal_size_limit",
+ /* ePragTyp: */ PragTyp_JOURNAL_SIZE_LIMIT,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if defined(SQLITE_HAS_CODEC)
- { /* zName: */ "key",
- /* ePragTyp: */ PragTyp_KEY,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "key",
+ /* ePragTyp: */ PragTyp_KEY,
+ /* ePragFlg: */ 0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "legacy_file_format",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_LegacyFileFmt },
+ {/* zName: */ "legacy_alter_table",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_LegacyAlter },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_ENABLE_LOCKING_STYLE
- { /* zName: */ "lock_proxy_file",
- /* ePragTyp: */ PragTyp_LOCK_PROXY_FILE,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "lock_proxy_file",
+ /* ePragTyp: */ PragTyp_LOCK_PROXY_FILE,
+ /* ePragFlg: */ PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
- { /* zName: */ "lock_status",
- /* ePragTyp: */ PragTyp_LOCK_STATUS,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "lock_status",
+ /* ePragTyp: */ PragTyp_LOCK_STATUS,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 47, 2,
+ /* iArg: */ 0 },
+#endif
+#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
+ {/* zName: */ "locking_mode",
+ /* ePragTyp: */ PragTyp_LOCKING_MODE,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+ {/* zName: */ "max_page_count",
+ /* ePragTyp: */ PragTyp_PAGE_COUNT,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+ {/* zName: */ "mmap_size",
+ /* ePragTyp: */ PragTyp_MMAP_SIZE,
+ /* ePragFlg: */ 0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
+#if !defined(SQLITE_OMIT_VIRTUALTABLE)
+#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+ {/* zName: */ "module_list",
+ /* ePragTyp: */ PragTyp_MODULE_LIST,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 9, 1,
+ /* iArg: */ 0 },
+#endif
+#endif
+#endif
+ {/* zName: */ "optimize",
+ /* ePragTyp: */ PragTyp_OPTIMIZE,
+ /* ePragFlg: */ PragFlg_Result1|PragFlg_NeedSchema,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
- { /* zName: */ "locking_mode",
- /* ePragTyp: */ PragTyp_LOCKING_MODE,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
- { /* zName: */ "max_page_count",
- /* ePragTyp: */ PragTyp_PAGE_COUNT,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
- { /* zName: */ "mmap_size",
- /* ePragTyp: */ PragTyp_MMAP_SIZE,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
- { /* zName: */ "page_count",
- /* ePragTyp: */ PragTyp_PAGE_COUNT,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
- { /* zName: */ "page_size",
- /* ePragTyp: */ PragTyp_PAGE_SIZE,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
-#endif
-#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_PARSER_TRACE)
- { /* zName: */ "parser_trace",
- /* ePragTyp: */ PragTyp_PARSER_TRACE,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "page_count",
+ /* ePragTyp: */ PragTyp_PAGE_COUNT,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+ {/* zName: */ "page_size",
+ /* ePragTyp: */ PragTyp_PAGE_SIZE,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "query_only",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_QueryOnly },
+#if defined(SQLITE_DEBUG)
+ {/* zName: */ "parser_trace",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_ParserTrace },
+#endif
+#endif
+#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS)
+ {/* zName: */ "pragma_list",
+ /* ePragTyp: */ PragTyp_PRAGMA_LIST,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 9, 1,
+ /* iArg: */ 0 },
+#endif
+#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ {/* zName: */ "query_only",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_QueryOnly },
#endif
#if !defined(SQLITE_OMIT_INTEGRITY_CHECK)
- { /* zName: */ "quick_check",
- /* ePragTyp: */ PragTyp_INTEGRITY_CHECK,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "quick_check",
+ /* ePragTyp: */ PragTyp_INTEGRITY_CHECK,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "read_uncommitted",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_ReadUncommitted },
- { /* zName: */ "recursive_triggers",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_RecTriggers },
+ {/* zName: */ "read_uncommitted",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_ReadUncommit },
+ {/* zName: */ "recursive_triggers",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_RecTriggers },
#endif
#if defined(SQLITE_HAS_CODEC)
- { /* zName: */ "rekey",
- /* ePragTyp: */ PragTyp_REKEY,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "rekey",
+ /* ePragTyp: */ PragTyp_KEY,
+ /* ePragFlg: */ 0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 1 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "reverse_unordered_selects",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_ReverseOrder },
+ {/* zName: */ "reverse_unordered_selects",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_ReverseOrder },
#endif
#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
- { /* zName: */ "schema_version",
- /* ePragTyp: */ PragTyp_HEADER_VALUE,
- /* ePragFlag: */ 0,
- /* iArg: */ BTREE_SCHEMA_VERSION },
+ {/* zName: */ "schema_version",
+ /* ePragTyp: */ PragTyp_HEADER_VALUE,
+ /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ BTREE_SCHEMA_VERSION },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
- { /* zName: */ "secure_delete",
- /* ePragTyp: */ PragTyp_SECURE_DELETE,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "secure_delete",
+ /* ePragTyp: */ PragTyp_SECURE_DELETE,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "short_column_names",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_ShortColNames },
-#endif
- { /* zName: */ "shrink_memory",
- /* ePragTyp: */ PragTyp_SHRINK_MEMORY,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
- { /* zName: */ "soft_heap_limit",
- /* ePragTyp: */ PragTyp_SOFT_HEAP_LIMIT,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "short_column_names",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_ShortColNames },
+#endif
+ {/* zName: */ "shrink_memory",
+ /* ePragTyp: */ PragTyp_SHRINK_MEMORY,
+ /* ePragFlg: */ PragFlg_NoColumns,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+ {/* zName: */ "soft_heap_limit",
+ /* ePragTyp: */ PragTyp_SOFT_HEAP_LIMIT,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
#if defined(SQLITE_DEBUG)
- { /* zName: */ "sql_trace",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_SqlTrace },
+ {/* zName: */ "sql_trace",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_SqlTrace },
#endif
#endif
-#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
- { /* zName: */ "stats",
- /* ePragTyp: */ PragTyp_STATS,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && defined(SQLITE_DEBUG)
+ {/* zName: */ "stats",
+ /* ePragTyp: */ PragTyp_STATS,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq,
+ /* ColNames: */ 27, 5,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
- { /* zName: */ "synchronous",
- /* ePragTyp: */ PragTyp_SYNCHRONOUS,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "synchronous",
+ /* ePragTyp: */ PragTyp_SYNCHRONOUS,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS)
- { /* zName: */ "table_info",
- /* ePragTyp: */ PragTyp_TABLE_INFO,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "table_info",
+ /* ePragTyp: */ PragTyp_TABLE_INFO,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
+ /* ColNames: */ 8, 6,
+ /* iArg: */ 0 },
+ {/* zName: */ "table_xinfo",
+ /* ePragTyp: */ PragTyp_TABLE_INFO,
+ /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt,
+ /* ColNames: */ 8, 7,
+ /* iArg: */ 1 },
#endif
#if !defined(SQLITE_OMIT_PAGER_PRAGMAS)
- { /* zName: */ "temp_store",
- /* ePragTyp: */ PragTyp_TEMP_STORE,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
- { /* zName: */ "temp_store_directory",
- /* ePragTyp: */ PragTyp_TEMP_STORE_DIRECTORY,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
-#endif
- { /* zName: */ "threads",
- /* ePragTyp: */ PragTyp_THREADS,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
+ {/* zName: */ "temp_store",
+ /* ePragTyp: */ PragTyp_TEMP_STORE,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+ {/* zName: */ "temp_store_directory",
+ /* ePragTyp: */ PragTyp_TEMP_STORE_DIRECTORY,
+ /* ePragFlg: */ PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+#endif
+#if defined(SQLITE_HAS_CODEC)
+ {/* zName: */ "textkey",
+ /* ePragTyp: */ PragTyp_KEY,
+ /* ePragFlg: */ 0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 4 },
+ {/* zName: */ "textrekey",
+ /* ePragTyp: */ PragTyp_KEY,
+ /* ePragFlg: */ 0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 5 },
+#endif
+ {/* zName: */ "threads",
+ /* ePragTyp: */ PragTyp_THREADS,
+ /* ePragFlg: */ PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
+ {/* zName: */ "trusted_schema",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_TrustedSchema },
+#endif
#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS)
- { /* zName: */ "user_version",
- /* ePragTyp: */ PragTyp_HEADER_VALUE,
- /* ePragFlag: */ 0,
- /* iArg: */ BTREE_USER_VERSION },
+ {/* zName: */ "user_version",
+ /* ePragTyp: */ PragTyp_HEADER_VALUE,
+ /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ BTREE_USER_VERSION },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
#if defined(SQLITE_DEBUG)
- { /* zName: */ "vdbe_addoptrace",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_VdbeAddopTrace },
- { /* zName: */ "vdbe_debug",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace },
- { /* zName: */ "vdbe_eqp",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_VdbeEQP },
- { /* zName: */ "vdbe_listing",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_VdbeListing },
- { /* zName: */ "vdbe_trace",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_VdbeTrace },
+ {/* zName: */ "vdbe_addoptrace",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_VdbeAddopTrace },
+ {/* zName: */ "vdbe_debug",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace },
+ {/* zName: */ "vdbe_eqp",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_VdbeEQP },
+ {/* zName: */ "vdbe_listing",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_VdbeListing },
+ {/* zName: */ "vdbe_trace",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_VdbeTrace },
#endif
#endif
#if !defined(SQLITE_OMIT_WAL)
- { /* zName: */ "wal_autocheckpoint",
- /* ePragTyp: */ PragTyp_WAL_AUTOCHECKPOINT,
- /* ePragFlag: */ 0,
- /* iArg: */ 0 },
- { /* zName: */ "wal_checkpoint",
- /* ePragTyp: */ PragTyp_WAL_CHECKPOINT,
- /* ePragFlag: */ PragFlag_NeedSchema,
- /* iArg: */ 0 },
+ {/* zName: */ "wal_autocheckpoint",
+ /* ePragTyp: */ PragTyp_WAL_AUTOCHECKPOINT,
+ /* ePragFlg: */ 0,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ 0 },
+ {/* zName: */ "wal_checkpoint",
+ /* ePragTyp: */ PragTyp_WAL_CHECKPOINT,
+ /* ePragFlg: */ PragFlg_NeedSchema,
+ /* ColNames: */ 44, 3,
+ /* iArg: */ 0 },
#endif
#if !defined(SQLITE_OMIT_FLAG_PRAGMAS)
- { /* zName: */ "writable_schema",
- /* ePragTyp: */ PragTyp_FLAG,
- /* ePragFlag: */ 0,
- /* iArg: */ SQLITE_WriteSchema|SQLITE_RecoveryMode },
+ {/* zName: */ "writable_schema",
+ /* ePragTyp: */ PragTyp_FLAG,
+ /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1,
+ /* ColNames: */ 0, 0,
+ /* iArg: */ SQLITE_WriteSchema|SQLITE_NoSchemaError },
#endif
};
-/* Number of pragmas: 60 on by default, 73 total. */
+/* Number of pragmas: 66 on by default, 82 total. */
/************** End of pragma.h **********************************************/
/************** Continuing where we left off in pragma.c *********************/
@@ -114675,7 +127996,7 @@ static const struct sPragmaNames {
** if the omitFull parameter it 1.
**
** Note that the values returned are one less that the values that
-** should be passed into sqlite3BtreeSetSafetyLevel(). The is done
+** should be passed into tdsqlite3BtreeSetSafetyLevel(). The is done
** to support legacy SQL code. The safety level used to be boolean
** and older scripts may have used numbers 0 for OFF and 1 for ON.
*/
@@ -114687,12 +128008,12 @@ static u8 getSafetyLevel(const char *z, int omitFull, u8 dflt){
static const u8 iValue[] = {1, 0, 0, 0, 1, 1, 3, 2};
/* on no off false yes true extra full */
int i, n;
- if( sqlite3Isdigit(*z) ){
- return (u8)sqlite3Atoi(z);
+ if( tdsqlite3Isdigit(*z) ){
+ return (u8)tdsqlite3Atoi(z);
}
- n = sqlite3Strlen30(z);
+ n = tdsqlite3Strlen30(z);
for(i=0; i<ArraySize(iLength); i++){
- if( iLength[i]==n && sqlite3StrNICmp(&zText[iOffset[i]],z,n)==0
+ if( iLength[i]==n && tdsqlite3StrNICmp(&zText[iOffset[i]],z,n)==0
&& (!omitFull || iValue[i]<=1)
){
return iValue[i];
@@ -114704,11 +128025,11 @@ static u8 getSafetyLevel(const char *z, int omitFull, u8 dflt){
/*
** Interpret the given string as a boolean value.
*/
-SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z, u8 dflt){
+SQLITE_PRIVATE u8 tdsqlite3GetBoolean(const char *z, u8 dflt){
return getSafetyLevel(z,1,dflt)!=0;
}
-/* The sqlite3GetBoolean() function is used by other modules but the
+/* The tdsqlite3GetBoolean() function is used by other modules but the
** remainder of this file is specific to PRAGMA processing. So omit
** the rest of the file if PRAGMAs are omitted from the build.
*/
@@ -114719,8 +128040,8 @@ SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z, u8 dflt){
*/
static int getLockingMode(const char *z){
if( z ){
- if( 0==sqlite3StrICmp(z, "exclusive") ) return PAGER_LOCKINGMODE_EXCLUSIVE;
- if( 0==sqlite3StrICmp(z, "normal") ) return PAGER_LOCKINGMODE_NORMAL;
+ if( 0==tdsqlite3StrICmp(z, "exclusive") ) return PAGER_LOCKINGMODE_EXCLUSIVE;
+ if( 0==tdsqlite3StrICmp(z, "normal") ) return PAGER_LOCKINGMODE_NORMAL;
}
return PAGER_LOCKINGMODE_QUERY;
}
@@ -114734,10 +128055,10 @@ static int getLockingMode(const char *z){
*/
static int getAutoVacuum(const char *z){
int i;
- if( 0==sqlite3StrICmp(z, "none") ) return BTREE_AUTOVACUUM_NONE;
- if( 0==sqlite3StrICmp(z, "full") ) return BTREE_AUTOVACUUM_FULL;
- if( 0==sqlite3StrICmp(z, "incremental") ) return BTREE_AUTOVACUUM_INCR;
- i = sqlite3Atoi(z);
+ if( 0==tdsqlite3StrICmp(z, "none") ) return BTREE_AUTOVACUUM_NONE;
+ if( 0==tdsqlite3StrICmp(z, "full") ) return BTREE_AUTOVACUUM_FULL;
+ if( 0==tdsqlite3StrICmp(z, "incremental") ) return BTREE_AUTOVACUUM_INCR;
+ i = tdsqlite3Atoi(z);
return (u8)((i>=0&&i<=2)?i:0);
}
#endif /* ifndef SQLITE_OMIT_AUTOVACUUM */
@@ -114751,9 +128072,9 @@ static int getAutoVacuum(const char *z){
static int getTempStore(const char *z){
if( z[0]>='0' && z[0]<='2' ){
return z[0] - '0';
- }else if( sqlite3StrICmp(z, "file")==0 ){
+ }else if( tdsqlite3StrICmp(z, "file")==0 ){
return 1;
- }else if( sqlite3StrICmp(z, "memory")==0 ){
+ }else if( tdsqlite3StrICmp(z, "memory")==0 ){
return 2;
}else{
return 0;
@@ -114767,16 +128088,16 @@ static int getTempStore(const char *z){
** from default, or when 'file' and the temp_store_directory has changed
*/
static int invalidateTempStorage(Parse *pParse){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
if( db->aDb[1].pBt!=0 ){
- if( !db->autoCommit || sqlite3BtreeIsInReadTrans(db->aDb[1].pBt) ){
- sqlite3ErrorMsg(pParse, "temporary storage cannot be changed "
+ if( !db->autoCommit || tdsqlite3BtreeIsInReadTrans(db->aDb[1].pBt) ){
+ tdsqlite3ErrorMsg(pParse, "temporary storage cannot be changed "
"from within a transaction");
return SQLITE_ERROR;
}
- sqlite3BtreeClose(db->aDb[1].pBt);
+ tdsqlite3BtreeClose(db->aDb[1].pBt);
db->aDb[1].pBt = 0;
- sqlite3ResetAllSchemasOfConnection(db);
+ tdsqlite3ResetAllSchemasOfConnection(db);
}
return SQLITE_OK;
}
@@ -114790,7 +128111,7 @@ static int invalidateTempStorage(Parse *pParse){
*/
static int changeTempStorage(Parse *pParse, const char *zStorageType){
int ts = getTempStore(zStorageType);
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
if( db->temp_store==ts ) return SQLITE_OK;
if( invalidateTempStorage( pParse ) != SQLITE_OK ){
return SQLITE_ERROR;
@@ -114801,30 +128122,30 @@ static int changeTempStorage(Parse *pParse, const char *zStorageType){
#endif /* SQLITE_PAGER_PRAGMAS */
/*
-** Set the names of the first N columns to the values in azCol[]
+** Set result column names for a pragma.
*/
-static void setAllColumnNames(
- Vdbe *v, /* The query under construction */
- int N, /* Number of columns */
- const char **azCol /* Names of columns */
+static void setPragmaResultColumnNames(
+ Vdbe *v, /* The query under construction */
+ const PragmaName *pPragma /* The pragma */
){
- int i;
- sqlite3VdbeSetNumCols(v, N);
- for(i=0; i<N; i++){
- sqlite3VdbeSetColName(v, i, COLNAME_NAME, azCol[i], SQLITE_STATIC);
+ u8 n = pPragma->nPragCName;
+ tdsqlite3VdbeSetNumCols(v, n==0 ? 1 : n);
+ if( n==0 ){
+ tdsqlite3VdbeSetColName(v, 0, COLNAME_NAME, pPragma->zName, SQLITE_STATIC);
+ }else{
+ int i, j;
+ for(i=0, j=pPragma->iPragCName; i<n; i++, j++){
+ tdsqlite3VdbeSetColName(v, i, COLNAME_NAME, pragCName[j], SQLITE_STATIC);
+ }
}
}
-static void setOneColumnName(Vdbe *v, const char *z){
- setAllColumnNames(v, 1, &z);
-}
/*
** Generate code to return a single integer value.
*/
-static void returnSingleInt(Vdbe *v, const char *zLabel, i64 value){
- sqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, 1, 0, (const u8*)&value, P4_INT64);
- setOneColumnName(v, zLabel);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+static void returnSingleInt(Vdbe *v, i64 value){
+ tdsqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, 1, 0, (const u8*)&value, P4_INT64);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
}
/*
@@ -114832,13 +128153,11 @@ static void returnSingleInt(Vdbe *v, const char *zLabel, i64 value){
*/
static void returnSingleText(
Vdbe *v, /* Prepared statement under construction */
- const char *zLabel, /* Name of the result column */
const char *zValue /* Value to be returned */
){
if( zValue ){
- sqlite3VdbeLoadString(v, 1, (const char*)zValue);
- setOneColumnName(v, zLabel);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ tdsqlite3VdbeLoadString(v, 1, (const char*)zValue);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
}
}
@@ -114848,7 +128167,7 @@ static void returnSingleText(
** set these values for all pagers.
*/
#ifndef SQLITE_OMIT_PAGER_PRAGMAS
-static void setAllPagerFlags(sqlite3 *db){
+static void setAllPagerFlags(tdsqlite3 *db){
if( db->autoCommit ){
Db *pDb = db->aDb;
int n = db->nDb;
@@ -114860,7 +128179,7 @@ static void setAllPagerFlags(sqlite3 *db){
assert( (pDb->safety_level & PAGER_SYNCHRONOUS_MASK)==pDb->safety_level );
while( (n--) > 0 ){
if( pDb->pBt ){
- sqlite3BtreeSetPagerFlags(pDb->pBt,
+ tdsqlite3BtreeSetPagerFlags(pDb->pBt,
pDb->safety_level | (db->flags & PAGER_FLAGS_MASK) );
}
pDb++;
@@ -114896,7 +128215,7 @@ static const char *actionName(u8 action){
** defined in pager.h. This function returns the associated lowercase
** journal-mode name.
*/
-SQLITE_PRIVATE const char *sqlite3JournalModename(int eMode){
+SQLITE_PRIVATE const char *tdsqlite3JournalModename(int eMode){
static char * const azModeName[] = {
"delete", "persist", "off", "truncate", "memory"
#ifndef SQLITE_OMIT_WAL
@@ -114916,6 +128235,91 @@ SQLITE_PRIVATE const char *sqlite3JournalModename(int eMode){
}
/*
+** Locate a pragma in the aPragmaName[] array.
+*/
+static const PragmaName *pragmaLocate(const char *zName){
+ int upr, lwr, mid = 0, rc;
+ lwr = 0;
+ upr = ArraySize(aPragmaName)-1;
+ while( lwr<=upr ){
+ mid = (lwr+upr)/2;
+ rc = tdsqlite3_stricmp(zName, aPragmaName[mid].zName);
+ if( rc==0 ) break;
+ if( rc<0 ){
+ upr = mid - 1;
+ }else{
+ lwr = mid + 1;
+ }
+ }
+ return lwr>upr ? 0 : &aPragmaName[mid];
+}
+
+/*
+** Create zero or more entries in the output for the SQL functions
+** defined by FuncDef p.
+*/
+static void pragmaFunclistLine(
+ Vdbe *v, /* The prepared statement being created */
+ FuncDef *p, /* A particular function definition */
+ int isBuiltin, /* True if this is a built-in function */
+ int showInternFuncs /* True if showing internal functions */
+){
+ for(; p; p=p->pNext){
+ const char *zType;
+ static const u32 mask =
+ SQLITE_DETERMINISTIC |
+ SQLITE_DIRECTONLY |
+ SQLITE_SUBTYPE |
+ SQLITE_INNOCUOUS |
+ SQLITE_FUNC_INTERNAL
+ ;
+ static const char *azEnc[] = { 0, "utf8", "utf16le", "utf16be" };
+
+ assert( SQLITE_FUNC_ENCMASK==0x3 );
+ assert( strcmp(azEnc[SQLITE_UTF8],"utf8")==0 );
+ assert( strcmp(azEnc[SQLITE_UTF16LE],"utf16le")==0 );
+ assert( strcmp(azEnc[SQLITE_UTF16BE],"utf16be")==0 );
+
+ if( p->xSFunc==0 ) continue;
+ if( (p->funcFlags & SQLITE_FUNC_INTERNAL)!=0
+ && showInternFuncs==0
+ ){
+ continue;
+ }
+ if( p->xValue!=0 ){
+ zType = "w";
+ }else if( p->xFinalize!=0 ){
+ zType = "a";
+ }else{
+ zType = "s";
+ }
+ tdsqlite3VdbeMultiLoad(v, 1, "sissii",
+ p->zName, isBuiltin,
+ zType, azEnc[p->funcFlags&SQLITE_FUNC_ENCMASK],
+ p->nArg,
+ (p->funcFlags & mask) ^ SQLITE_INNOCUOUS
+ );
+ }
+}
+
+
+/*
+** Helper subroutine for PRAGMA integrity_check:
+**
+** Generate code to output a single-column result row with a value of the
+** string held in register 3. Decrement the result count in register 1
+** and halt if the maximum number of result rows have been issued.
+*/
+static int integrityCheckResultRow(Vdbe *v){
+ int addr;
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1);
+ addr = tdsqlite3VdbeAddOp3(v, OP_IfPos, 1, tdsqlite3VdbeCurrentAddr(v)+2, 1);
+ VdbeCoverage(v);
+ tdsqlite3VdbeAddOp0(v, OP_Halt);
+ return addr;
+}
+
+/*
** Process a pragma statement.
**
** Pragmas are of this form:
@@ -114930,7 +128334,7 @@ SQLITE_PRIVATE const char *sqlite3JournalModename(int eMode){
** and pId2 is the id. If the left side is just "id" then pId1 is the
** id and pId2 is any empty string.
*/
-SQLITE_PRIVATE void sqlite3Pragma(
+SQLITE_PRIVATE void tdsqlite3Pragma(
Parse *pParse,
Token *pId1, /* First part of [schema.]id field */
Token *pId2, /* Second part of [schema.]id field, or NULL */
@@ -114943,46 +128347,40 @@ SQLITE_PRIVATE void sqlite3Pragma(
Token *pId; /* Pointer to <id> token */
char *aFcntl[4]; /* Argument to SQLITE_FCNTL_PRAGMA */
int iDb; /* Database index for <database> */
- int lwr, upr, mid = 0; /* Binary search bounds */
int rc; /* return value form SQLITE_FCNTL_PRAGMA */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
Db *pDb; /* The specific database being pragmaed */
- Vdbe *v = sqlite3GetVdbe(pParse); /* Prepared statement */
- const struct sPragmaNames *pPragma;
-/* BEGIN SQLCIPHER */
-#ifdef SQLITE_HAS_CODEC
- extern int sqlcipher_codec_pragma(sqlite3*, int, Parse *, const char *, const char *);
-#endif
-/* END SQLCIPHER */
+ Vdbe *v = tdsqlite3GetVdbe(pParse); /* Prepared statement */
+ const PragmaName *pPragma; /* The pragma */
if( v==0 ) return;
- sqlite3VdbeRunOnlyOnce(v);
+ tdsqlite3VdbeRunOnlyOnce(v);
pParse->nMem = 2;
/* Interpret the [schema.] part of the pragma statement. iDb is the
** index of the database this pragma is being applied to in db.aDb[]. */
- iDb = sqlite3TwoPartName(pParse, pId1, pId2, &pId);
+ iDb = tdsqlite3TwoPartName(pParse, pId1, pId2, &pId);
if( iDb<0 ) return;
pDb = &db->aDb[iDb];
/* If the temp database has been explicitly named as part of the
** pragma, make sure it is open.
*/
- if( iDb==1 && sqlite3OpenTempDatabase(pParse) ){
+ if( iDb==1 && tdsqlite3OpenTempDatabase(pParse) ){
return;
}
- zLeft = sqlite3NameFromToken(db, pId);
+ zLeft = tdsqlite3NameFromToken(db, pId);
if( !zLeft ) return;
if( minusFlag ){
- zRight = sqlite3MPrintf(db, "-%T", pValue);
+ zRight = tdsqlite3MPrintf(db, "-%T", pValue);
}else{
- zRight = sqlite3NameFromToken(db, pValue);
+ zRight = tdsqlite3NameFromToken(db, pValue);
}
assert( pId2 );
zDb = pId2->n>0 ? pDb->zDbSName : 0;
- if( sqlite3AuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, zDb) ){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, zDb) ){
goto pragma_out;
}
@@ -114991,7 +128389,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
** handled the pragma and generate a no-op prepared statement.
**
** IMPLEMENTATION-OF: R-12238-55120 Whenever a PRAGMA statement is parsed,
- ** an SQLITE_FCNTL_PRAGMA file control is sent to the open sqlite3_file
+ ** an SQLITE_FCNTL_PRAGMA file control is sent to the open tdsqlite3_file
** object corresponding to the database file to which the pragma
** statement refers.
**
@@ -115006,16 +128404,18 @@ SQLITE_PRIVATE void sqlite3Pragma(
aFcntl[2] = zRight;
aFcntl[3] = 0;
db->busyHandler.nBusy = 0;
- rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_PRAGMA, (void*)aFcntl);
+ rc = tdsqlite3_file_control(db, zDb, SQLITE_FCNTL_PRAGMA, (void*)aFcntl);
if( rc==SQLITE_OK ){
- returnSingleText(v, "result", aFcntl[0]);
- sqlite3_free(aFcntl[0]);
+ tdsqlite3VdbeSetNumCols(v, 1);
+ tdsqlite3VdbeSetColName(v, 0, COLNAME_NAME, aFcntl[0], SQLITE_TRANSIENT);
+ returnSingleText(v, aFcntl[0]);
+ tdsqlite3_free(aFcntl[0]);
goto pragma_out;
}
if( rc!=SQLITE_NOTFOUND ){
if( aFcntl[0] ){
- sqlite3ErrorMsg(pParse, "%s", aFcntl[0]);
- sqlite3_free(aFcntl[0]);
+ tdsqlite3ErrorMsg(pParse, "%s", aFcntl[0]);
+ tdsqlite3_free(aFcntl[0]);
}
pParse->nErr++;
pParse->rc = rc;
@@ -115033,24 +128433,19 @@ SQLITE_PRIVATE void sqlite3Pragma(
/* END SQLCIPHER */
/* Locate the pragma in the lookup table */
- lwr = 0;
- upr = ArraySize(aPragmaNames)-1;
- while( lwr<=upr ){
- mid = (lwr+upr)/2;
- rc = sqlite3_stricmp(zLeft, aPragmaNames[mid].zName);
- if( rc==0 ) break;
- if( rc<0 ){
- upr = mid - 1;
- }else{
- lwr = mid + 1;
- }
- }
- if( lwr>upr ) goto pragma_out;
- pPragma = &aPragmaNames[mid];
+ pPragma = pragmaLocate(zLeft);
+ if( pPragma==0 ) goto pragma_out;
/* Make sure the database schema is loaded if the pragma requires that */
- if( (pPragma->mPragFlag & PragFlag_NeedSchema)!=0 ){
- if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ if( (pPragma->mPragFlg & PragFlg_NeedSchema)!=0 ){
+ if( tdsqlite3ReadSchema(pParse) ) goto pragma_out;
+ }
+
+ /* Register the result column names for pragmas that return results */
+ if( (pPragma->mPragFlg & PragFlg_NoColumns)==0
+ && ((pPragma->mPragFlg & PragFlg_NoColumns1)==0 || zRight==0)
+ ){
+ setPragmaResultColumnNames(v, pPragma);
}
/* Jump to the appropriate pragma handler */
@@ -115087,23 +128482,22 @@ SQLITE_PRIVATE void sqlite3Pragma(
{ OP_ResultRow, 1, 1, 0},
};
VdbeOp *aOp;
- sqlite3VdbeUsesBtree(v, iDb);
+ tdsqlite3VdbeUsesBtree(v, iDb);
if( !zRight ){
- setOneColumnName(v, "cache_size");
pParse->nMem += 2;
- sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(getCacheSize));
- aOp = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize, iLn);
+ tdsqlite3VdbeVerifyNoMallocRequired(v, ArraySize(getCacheSize));
+ aOp = tdsqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize, iLn);
if( ONLY_IF_REALLOC_STRESS(aOp==0) ) break;
aOp[0].p1 = iDb;
aOp[1].p1 = iDb;
aOp[6].p1 = SQLITE_DEFAULT_CACHE_SIZE;
}else{
- int size = sqlite3AbsInt32(sqlite3Atoi(zRight));
- sqlite3BeginWriteOperation(pParse, 0, iDb);
- sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_DEFAULT_CACHE_SIZE, size);
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ int size = tdsqlite3AbsInt32(tdsqlite3Atoi(zRight));
+ tdsqlite3BeginWriteOperation(pParse, 0, iDb);
+ tdsqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_DEFAULT_CACHE_SIZE, size);
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
pDb->pSchema->cache_size = size;
- sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
+ tdsqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
}
break;
}
@@ -115123,15 +128517,15 @@ SQLITE_PRIVATE void sqlite3Pragma(
Btree *pBt = pDb->pBt;
assert( pBt!=0 );
if( !zRight ){
- int size = ALWAYS(pBt) ? sqlite3BtreeGetPageSize(pBt) : 0;
- returnSingleInt(v, "page_size", size);
+ int size = ALWAYS(pBt) ? tdsqlite3BtreeGetPageSize(pBt) : 0;
+ returnSingleInt(v, size);
}else{
/* Malloc may fail when setting the page-size, as there is an internal
- ** buffer that the pager module resizes using sqlite3_realloc().
+ ** buffer that the pager module resizes using tdsqlite3_realloc().
*/
- db->nextPagesize = sqlite3Atoi(zRight);
- if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize,-1,0) ){
- sqlite3OomFault(db);
+ db->nextPagesize = tdsqlite3Atoi(zRight);
+ if( SQLITE_NOMEM==tdsqlite3BtreeSetPageSize(pBt, db->nextPagesize,-1,0) ){
+ tdsqlite3OomFault(db);
}
}
break;
@@ -115139,27 +128533,31 @@ SQLITE_PRIVATE void sqlite3Pragma(
/*
** PRAGMA [schema.]secure_delete
- ** PRAGMA [schema.]secure_delete=ON/OFF
+ ** PRAGMA [schema.]secure_delete=ON/OFF/FAST
**
** The first form reports the current setting for the
** secure_delete flag. The second form changes the secure_delete
- ** flag setting and reports thenew value.
+ ** flag setting and reports the new value.
*/
case PragTyp_SECURE_DELETE: {
Btree *pBt = pDb->pBt;
int b = -1;
assert( pBt!=0 );
if( zRight ){
- b = sqlite3GetBoolean(zRight, 0);
+ if( tdsqlite3_stricmp(zRight, "fast")==0 ){
+ b = 2;
+ }else{
+ b = tdsqlite3GetBoolean(zRight, 0);
+ }
}
if( pId2->n==0 && b>=0 ){
int ii;
for(ii=0; ii<db->nDb; ii++){
- sqlite3BtreeSecureDelete(db->aDb[ii].pBt, b);
+ tdsqlite3BtreeSecureDelete(db->aDb[ii].pBt, b);
}
}
- b = sqlite3BtreeSecureDelete(pBt, b);
- returnSingleInt(v, "secure_delete", b);
+ b = tdsqlite3BtreeSecureDelete(pBt, b);
+ returnSingleInt(v, b);
break;
}
@@ -115174,7 +128572,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
**
** The absolute value of N is used. This is undocumented and might
** change. The only purpose is to provide an easy way to test
- ** the sqlite3AbsInt32() function.
+ ** the tdsqlite3AbsInt32() function.
**
** PRAGMA [schema.]page_count
**
@@ -115182,17 +128580,15 @@ SQLITE_PRIVATE void sqlite3Pragma(
*/
case PragTyp_PAGE_COUNT: {
int iReg;
- sqlite3CodeVerifySchema(pParse, iDb);
+ tdsqlite3CodeVerifySchema(pParse, iDb);
iReg = ++pParse->nMem;
- if( sqlite3Tolower(zLeft[0])=='p' ){
- sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg);
+ if( tdsqlite3Tolower(zLeft[0])=='p' ){
+ tdsqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg);
}else{
- sqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg,
- sqlite3AbsInt32(sqlite3Atoi(zRight)));
+ tdsqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg,
+ tdsqlite3AbsInt32(tdsqlite3Atoi(zRight)));
}
- sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1);
- sqlite3VdbeSetNumCols(v, 1);
- sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, SQLITE_TRANSIENT);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1);
break;
}
@@ -115224,13 +128620,13 @@ SQLITE_PRIVATE void sqlite3Pragma(
int ii;
assert(pDb==&db->aDb[0]);
for(ii=2; ii<db->nDb; ii++){
- pPager = sqlite3BtreePager(db->aDb[ii].pBt);
- sqlite3PagerLockingMode(pPager, eMode);
+ pPager = tdsqlite3BtreePager(db->aDb[ii].pBt);
+ tdsqlite3PagerLockingMode(pPager, eMode);
}
db->dfltLockMode = (u8)eMode;
}
- pPager = sqlite3BtreePager(pDb->pBt);
- eMode = sqlite3PagerLockingMode(pPager, eMode);
+ pPager = tdsqlite3BtreePager(pDb->pBt);
+ eMode = tdsqlite3PagerLockingMode(pPager, eMode);
}
assert( eMode==PAGER_LOCKINGMODE_NORMAL
@@ -115238,7 +128634,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
if( eMode==PAGER_LOCKINGMODE_EXCLUSIVE ){
zRet = "exclusive";
}
- returnSingleText(v, "locking_mode", zRet);
+ returnSingleText(v, zRet);
break;
}
@@ -115251,22 +128647,26 @@ SQLITE_PRIVATE void sqlite3Pragma(
int eMode; /* One of the PAGER_JOURNALMODE_XXX symbols */
int ii; /* Loop counter */
- setOneColumnName(v, "journal_mode");
if( zRight==0 ){
/* If there is no "=MODE" part of the pragma, do a query for the
** current mode */
eMode = PAGER_JOURNALMODE_QUERY;
}else{
const char *zMode;
- int n = sqlite3Strlen30(zRight);
- for(eMode=0; (zMode = sqlite3JournalModename(eMode))!=0; eMode++){
- if( sqlite3StrNICmp(zRight, zMode, n)==0 ) break;
+ int n = tdsqlite3Strlen30(zRight);
+ for(eMode=0; (zMode = tdsqlite3JournalModename(eMode))!=0; eMode++){
+ if( tdsqlite3StrNICmp(zRight, zMode, n)==0 ) break;
}
if( !zMode ){
/* If the "=MODE" part does not match any known journal mode,
** then do a query */
eMode = PAGER_JOURNALMODE_QUERY;
}
+ if( eMode==PAGER_JOURNALMODE_OFF && (db->flags & SQLITE_Defensive)!=0 ){
+ /* Do not allow journal-mode "OFF" in defensive since the database
+ ** can become corrupted using ordinary SQL when the journal is off */
+ eMode = PAGER_JOURNALMODE_QUERY;
+ }
}
if( eMode==PAGER_JOURNALMODE_QUERY && pId2->n==0 ){
/* Convert "PRAGMA journal_mode" into "PRAGMA main.journal_mode" */
@@ -115275,11 +128675,11 @@ SQLITE_PRIVATE void sqlite3Pragma(
}
for(ii=db->nDb-1; ii>=0; ii--){
if( db->aDb[ii].pBt && (ii==iDb || pId2->n==0) ){
- sqlite3VdbeUsesBtree(v, ii);
- sqlite3VdbeAddOp3(v, OP_JournalMode, ii, 1, eMode);
+ tdsqlite3VdbeUsesBtree(v, ii);
+ tdsqlite3VdbeAddOp3(v, OP_JournalMode, ii, 1, eMode);
}
}
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
break;
}
@@ -115290,14 +128690,14 @@ SQLITE_PRIVATE void sqlite3Pragma(
** Get or set the size limit on rollback journal files.
*/
case PragTyp_JOURNAL_SIZE_LIMIT: {
- Pager *pPager = sqlite3BtreePager(pDb->pBt);
+ Pager *pPager = tdsqlite3BtreePager(pDb->pBt);
i64 iLimit = -2;
if( zRight ){
- sqlite3DecOrHexToI64(zRight, &iLimit);
+ tdsqlite3DecOrHexToI64(zRight, &iLimit);
if( iLimit<-1 ) iLimit = -1;
}
- iLimit = sqlite3PagerJournalSizeLimit(pPager, iLimit);
- returnSingleInt(v, "journal_size_limit", iLimit);
+ iLimit = tdsqlite3PagerJournalSizeLimit(pPager, iLimit);
+ returnSingleInt(v, iLimit);
break;
}
@@ -115315,7 +128715,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
Btree *pBt = pDb->pBt;
assert( pBt!=0 );
if( !zRight ){
- returnSingleInt(v, "auto_vacuum", sqlite3BtreeGetAutoVacuum(pBt));
+ returnSingleInt(v, tdsqlite3BtreeGetAutoVacuum(pBt));
}else{
int eAuto = getAutoVacuum(zRight);
assert( eAuto>=0 && eAuto<=2 );
@@ -115325,7 +128725,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
** creates the database file. It is important that it is created
** as an auto-vacuum capable db.
*/
- rc = sqlite3BtreeSetAutoVacuum(pBt, eAuto);
+ rc = tdsqlite3BtreeSetAutoVacuum(pBt, eAuto);
if( rc==SQLITE_OK && (eAuto==1 || eAuto==2) ){
/* When setting the auto_vacuum mode to either "full" or
** "incremental", write the value of meta[6] in the database
@@ -115341,16 +128741,16 @@ SQLITE_PRIVATE void sqlite3Pragma(
{ OP_SetCookie, 0, BTREE_INCR_VACUUM, 0}, /* 4 */
};
VdbeOp *aOp;
- int iAddr = sqlite3VdbeCurrentAddr(v);
- sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(setMeta6));
- aOp = sqlite3VdbeAddOpList(v, ArraySize(setMeta6), setMeta6, iLn);
+ int iAddr = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeVerifyNoMallocRequired(v, ArraySize(setMeta6));
+ aOp = tdsqlite3VdbeAddOpList(v, ArraySize(setMeta6), setMeta6, iLn);
if( ONLY_IF_REALLOC_STRESS(aOp==0) ) break;
aOp[0].p1 = iDb;
aOp[1].p1 = iDb;
aOp[2].p2 = iAddr+4;
aOp[4].p1 = iDb;
aOp[4].p3 = eAuto - 1;
- sqlite3VdbeUsesBtree(v, iDb);
+ tdsqlite3VdbeUsesBtree(v, iDb);
}
}
break;
@@ -115365,16 +128765,16 @@ SQLITE_PRIVATE void sqlite3Pragma(
#ifndef SQLITE_OMIT_AUTOVACUUM
case PragTyp_INCREMENTAL_VACUUM: {
int iLimit, addr;
- if( zRight==0 || !sqlite3GetInt32(zRight, &iLimit) || iLimit<=0 ){
+ if( zRight==0 || !tdsqlite3GetInt32(zRight, &iLimit) || iLimit<=0 ){
iLimit = 0x7fffffff;
}
- sqlite3BeginWriteOperation(pParse, 0, iDb);
- sqlite3VdbeAddOp2(v, OP_Integer, iLimit, 1);
- addr = sqlite3VdbeAddOp1(v, OP_IncrVacuum, iDb); VdbeCoverage(v);
- sqlite3VdbeAddOp1(v, OP_ResultRow, 1);
- sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1);
- sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr);
+ tdsqlite3BeginWriteOperation(pParse, 0, iDb);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, iLimit, 1);
+ addr = tdsqlite3VdbeAddOp1(v, OP_IncrVacuum, iDb); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp1(v, OP_ResultRow, 1);
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, 1, -1);
+ tdsqlite3VdbeAddOp2(v, OP_IfPos, 1, addr); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addr);
break;
}
#endif
@@ -115392,13 +128792,13 @@ SQLITE_PRIVATE void sqlite3Pragma(
** of memory.
*/
case PragTyp_CACHE_SIZE: {
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
if( !zRight ){
- returnSingleInt(v, "cache_size", pDb->pSchema->cache_size);
+ returnSingleInt(v, pDb->pSchema->cache_size);
}else{
- int size = sqlite3Atoi(zRight);
+ int size = tdsqlite3Atoi(zRight);
pDb->pSchema->cache_size = size;
- sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
+ tdsqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
}
break;
}
@@ -115426,20 +128826,20 @@ SQLITE_PRIVATE void sqlite3Pragma(
** not just the schema specified.
*/
case PragTyp_CACHE_SPILL: {
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
if( !zRight ){
- returnSingleInt(v, "cache_spill",
+ returnSingleInt(v,
(db->flags & SQLITE_CacheSpill)==0 ? 0 :
- sqlite3BtreeSetSpillSize(pDb->pBt,0));
+ tdsqlite3BtreeSetSpillSize(pDb->pBt,0));
}else{
int size = 1;
- if( sqlite3GetInt32(zRight, &size) ){
- sqlite3BtreeSetSpillSize(pDb->pBt, size);
+ if( tdsqlite3GetInt32(zRight, &size) ){
+ tdsqlite3BtreeSetSpillSize(pDb->pBt, size);
}
- if( sqlite3GetBoolean(zRight, size!=0) ){
+ if( tdsqlite3GetBoolean(zRight, size!=0) ){
db->flags |= SQLITE_CacheSpill;
}else{
- db->flags &= ~SQLITE_CacheSpill;
+ db->flags &= ~(u64)SQLITE_CacheSpill;
}
setAllPagerFlags(db);
}
@@ -115453,7 +128853,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
** used to limit the aggregate size of all memory mapped regions of the
** database file. If this parameter is set to zero, then memory mapping
** is not used at all. If N is negative, then the default memory map
- ** limit determined by sqlite3_config(SQLITE_CONFIG_MMAP_SIZE) is set.
+ ** limit determined by tdsqlite3_config(SQLITE_CONFIG_MMAP_SIZE) is set.
** The parameter N is measured in bytes.
**
** This value is advisory. The underlying VFS is free to memory map
@@ -115461,28 +128861,28 @@ SQLITE_PRIVATE void sqlite3Pragma(
** upper layers will never invoke the xFetch interfaces to the VFS.
*/
case PragTyp_MMAP_SIZE: {
- sqlite3_int64 sz;
+ tdsqlite3_int64 sz;
#if SQLITE_MAX_MMAP_SIZE>0
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
if( zRight ){
int ii;
- sqlite3DecOrHexToI64(zRight, &sz);
- if( sz<0 ) sz = sqlite3GlobalConfig.szMmap;
+ tdsqlite3DecOrHexToI64(zRight, &sz);
+ if( sz<0 ) sz = tdsqlite3GlobalConfig.szMmap;
if( pId2->n==0 ) db->szMmap = sz;
for(ii=db->nDb-1; ii>=0; ii--){
if( db->aDb[ii].pBt && (ii==iDb || pId2->n==0) ){
- sqlite3BtreeSetMmapLimit(db->aDb[ii].pBt, sz);
+ tdsqlite3BtreeSetMmapLimit(db->aDb[ii].pBt, sz);
}
}
}
sz = -1;
- rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_MMAP_SIZE, &sz);
+ rc = tdsqlite3_file_control(db, zDb, SQLITE_FCNTL_MMAP_SIZE, &sz);
#else
sz = 0;
rc = SQLITE_OK;
#endif
if( rc==SQLITE_OK ){
- returnSingleInt(v, "mmap_size", sz);
+ returnSingleInt(v, sz);
}else if( rc!=SQLITE_NOTFOUND ){
pParse->nErr++;
pParse->rc = rc;
@@ -115503,7 +128903,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
*/
case PragTyp_TEMP_STORE: {
if( !zRight ){
- returnSingleInt(v, "temp_store", db->temp_store);
+ returnSingleInt(v, db->temp_store);
}else{
changeTempStorage(pParse, zRight);
}
@@ -115522,14 +128922,14 @@ SQLITE_PRIVATE void sqlite3Pragma(
*/
case PragTyp_TEMP_STORE_DIRECTORY: {
if( !zRight ){
- returnSingleText(v, "temp_store_directory", sqlite3_temp_directory);
+ returnSingleText(v, tdsqlite3_temp_directory);
}else{
#ifndef SQLITE_OMIT_WSD
if( zRight[0] ){
int res;
- rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res);
+ rc = tdsqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res);
if( rc!=SQLITE_OK || res==0 ){
- sqlite3ErrorMsg(pParse, "not a writable directory");
+ tdsqlite3ErrorMsg(pParse, "not a writable directory");
goto pragma_out;
}
}
@@ -115539,11 +128939,11 @@ SQLITE_PRIVATE void sqlite3Pragma(
){
invalidateTempStorage(pParse);
}
- sqlite3_free(sqlite3_temp_directory);
+ tdsqlite3_free(tdsqlite3_temp_directory);
if( zRight[0] ){
- sqlite3_temp_directory = sqlite3_mprintf("%s", zRight);
+ tdsqlite3_temp_directory = tdsqlite3_mprintf("%s", zRight);
}else{
- sqlite3_temp_directory = 0;
+ tdsqlite3_temp_directory = 0;
}
#endif /* SQLITE_OMIT_WSD */
}
@@ -115566,22 +128966,22 @@ SQLITE_PRIVATE void sqlite3Pragma(
*/
case PragTyp_DATA_STORE_DIRECTORY: {
if( !zRight ){
- returnSingleText(v, "data_store_directory", sqlite3_data_directory);
+ returnSingleText(v, tdsqlite3_data_directory);
}else{
#ifndef SQLITE_OMIT_WSD
if( zRight[0] ){
int res;
- rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res);
+ rc = tdsqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res);
if( rc!=SQLITE_OK || res==0 ){
- sqlite3ErrorMsg(pParse, "not a writable directory");
+ tdsqlite3ErrorMsg(pParse, "not a writable directory");
goto pragma_out;
}
}
- sqlite3_free(sqlite3_data_directory);
+ tdsqlite3_free(tdsqlite3_data_directory);
if( zRight[0] ){
- sqlite3_data_directory = sqlite3_mprintf("%s", zRight);
+ tdsqlite3_data_directory = tdsqlite3_mprintf("%s", zRight);
}else{
- sqlite3_data_directory = 0;
+ tdsqlite3_data_directory = 0;
}
#endif /* SQLITE_OMIT_WSD */
}
@@ -115600,25 +129000,25 @@ SQLITE_PRIVATE void sqlite3Pragma(
*/
case PragTyp_LOCK_PROXY_FILE: {
if( !zRight ){
- Pager *pPager = sqlite3BtreePager(pDb->pBt);
+ Pager *pPager = tdsqlite3BtreePager(pDb->pBt);
char *proxy_file_path = NULL;
- sqlite3_file *pFile = sqlite3PagerFile(pPager);
- sqlite3OsFileControlHint(pFile, SQLITE_GET_LOCKPROXYFILE,
+ tdsqlite3_file *pFile = tdsqlite3PagerFile(pPager);
+ tdsqlite3OsFileControlHint(pFile, SQLITE_GET_LOCKPROXYFILE,
&proxy_file_path);
- returnSingleText(v, "lock_proxy_file", proxy_file_path);
+ returnSingleText(v, proxy_file_path);
}else{
- Pager *pPager = sqlite3BtreePager(pDb->pBt);
- sqlite3_file *pFile = sqlite3PagerFile(pPager);
+ Pager *pPager = tdsqlite3BtreePager(pDb->pBt);
+ tdsqlite3_file *pFile = tdsqlite3PagerFile(pPager);
int res;
if( zRight[0] ){
- res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE,
+ res=tdsqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE,
zRight);
} else {
- res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE,
+ res=tdsqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE,
NULL);
}
if( res!=SQLITE_OK ){
- sqlite3ErrorMsg(pParse, "failed to set lock proxy file");
+ tdsqlite3ErrorMsg(pParse, "failed to set lock proxy file");
goto pragma_out;
}
}
@@ -115637,12 +129037,12 @@ SQLITE_PRIVATE void sqlite3Pragma(
*/
case PragTyp_SYNCHRONOUS: {
if( !zRight ){
- returnSingleInt(v, "synchronous", pDb->safety_level-1);
+ returnSingleInt(v, pDb->safety_level-1);
}else{
if( !db->autoCommit ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"Safety level may not be changed inside a transaction");
- }else{
+ }else if( iDb!=1 ){
int iLevel = (getSafetyLevel(zRight,0,1)+1) & PAGER_SYNCHRONOUS_MASK;
if( iLevel==0 ) iLevel = 1;
pDb->safety_level = iLevel;
@@ -115657,9 +129057,10 @@ SQLITE_PRIVATE void sqlite3Pragma(
#ifndef SQLITE_OMIT_FLAG_PRAGMAS
case PragTyp_FLAG: {
if( zRight==0 ){
- returnSingleInt(v, pPragma->zName, (db->flags & pPragma->iArg)!=0 );
+ setPragmaResultColumnNames(v, pPragma);
+ returnSingleInt(v, (db->flags & pPragma->iArg)!=0 );
}else{
- int mask = pPragma->iArg; /* Mask of bits to set or clear. */
+ u64 mask = pPragma->iArg; /* Mask of bits to set or clear. */
if( db->autoCommit==0 ){
/* Foreign key support may not be enabled or disabled while not
** in auto-commit mode. */
@@ -115672,7 +129073,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
}
#endif
- if( sqlite3GetBoolean(zRight, 0) ){
+ if( tdsqlite3GetBoolean(zRight, 0) ){
db->flags |= mask;
}else{
db->flags &= ~mask;
@@ -115683,7 +129084,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
** compiler (eg. count_changes). So add an opcode to expire all
** compiled SQL statements after modifying a pragma value.
*/
- sqlite3VdbeAddOp0(v, OP_Expire);
+ tdsqlite3VdbeAddOp0(v, OP_Expire);
setAllPagerFlags(db);
}
break;
@@ -115702,26 +129103,34 @@ SQLITE_PRIVATE void sqlite3Pragma(
** type: Column declaration type.
** notnull: True if 'NOT NULL' is part of column declaration
** dflt_value: The default value for the column, if any.
+ ** pk: Non-zero for PK fields.
*/
case PragTyp_TABLE_INFO: if( zRight ){
Table *pTab;
- pTab = sqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb);
+ pTab = tdsqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb);
if( pTab ){
- static const char *azCol[] = {
- "cid", "name", "type", "notnull", "dflt_value", "pk"
- };
+ int iTabDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
int i, k;
int nHidden = 0;
Column *pCol;
- Index *pPk = sqlite3PrimaryKeyIndex(pTab);
- pParse->nMem = 6;
- sqlite3CodeVerifySchema(pParse, iDb);
- setAllColumnNames(v, 6, azCol); assert( 6==ArraySize(azCol) );
- sqlite3ViewGetColumnNames(pParse, pTab);
+ Index *pPk = tdsqlite3PrimaryKeyIndex(pTab);
+ pParse->nMem = 7;
+ tdsqlite3CodeVerifySchema(pParse, iTabDb);
+ tdsqlite3ViewGetColumnNames(pParse, pTab);
for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){
- if( IsHiddenColumn(pCol) ){
- nHidden++;
- continue;
+ int isHidden = 0;
+ if( pCol->colFlags & COLFLAG_NOINSERT ){
+ if( pPragma->iArg==0 ){
+ nHidden++;
+ continue;
+ }
+ if( pCol->colFlags & COLFLAG_VIRTUAL ){
+ isHidden = 2; /* GENERATED ALWAYS AS ... VIRTUAL */
+ }else if( pCol->colFlags & COLFLAG_STORED ){
+ isHidden = 3; /* GENERATED ALWAYS AS ... STORED */
+ }else{ assert( pCol->colFlags & COLFLAG_HIDDEN );
+ isHidden = 1; /* HIDDEN */
+ }
}
if( (pCol->colFlags & COLFLAG_PRIMKEY)==0 ){
k = 0;
@@ -115730,55 +129139,62 @@ SQLITE_PRIVATE void sqlite3Pragma(
}else{
for(k=1; k<=pTab->nCol && pPk->aiColumn[k-1]!=i; k++){}
}
- assert( pCol->pDflt==0 || pCol->pDflt->op==TK_SPAN );
- sqlite3VdbeMultiLoad(v, 1, "issisi",
+ assert( pCol->pDflt==0 || pCol->pDflt->op==TK_SPAN || isHidden>=2 );
+ tdsqlite3VdbeMultiLoad(v, 1, pPragma->iArg ? "issisii" : "issisi",
i-nHidden,
pCol->zName,
- sqlite3ColumnType(pCol,""),
+ tdsqlite3ColumnType(pCol,""),
pCol->notNull ? 1 : 0,
- pCol->pDflt ? pCol->pDflt->u.zToken : 0,
- k);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 6);
+ pCol->pDflt && isHidden<2 ? pCol->pDflt->u.zToken : 0,
+ k,
+ isHidden);
}
}
}
break;
+#ifdef SQLITE_DEBUG
case PragTyp_STATS: {
- static const char *azCol[] = { "table", "index", "width", "height" };
Index *pIdx;
HashElem *i;
- v = sqlite3GetVdbe(pParse);
- pParse->nMem = 4;
- sqlite3CodeVerifySchema(pParse, iDb);
- setAllColumnNames(v, 4, azCol); assert( 4==ArraySize(azCol) );
+ pParse->nMem = 5;
+ tdsqlite3CodeVerifySchema(pParse, iDb);
for(i=sqliteHashFirst(&pDb->pSchema->tblHash); i; i=sqliteHashNext(i)){
Table *pTab = sqliteHashData(i);
- sqlite3VdbeMultiLoad(v, 1, "ssii",
+ tdsqlite3VdbeMultiLoad(v, 1, "ssiii",
pTab->zName,
0,
pTab->szTabRow,
- pTab->nRowLogEst);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4);
+ pTab->nRowLogEst,
+ pTab->tabFlags);
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- sqlite3VdbeMultiLoad(v, 2, "sii",
+ tdsqlite3VdbeMultiLoad(v, 2, "siiiX",
pIdx->zName,
pIdx->szIdxRow,
- pIdx->aiRowLogEst[0]);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4);
+ pIdx->aiRowLogEst[0],
+ pIdx->hasStat1);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5);
}
}
}
break;
+#endif
case PragTyp_INDEX_INFO: if( zRight ){
Index *pIdx;
Table *pTab;
- pIdx = sqlite3FindIndex(db, zRight, zDb);
+ pIdx = tdsqlite3FindIndex(db, zRight, zDb);
+ if( pIdx==0 ){
+ /* If there is no index named zRight, check to see if there is a
+ ** WITHOUT ROWID table named zRight, and if there is, show the
+ ** structure of the PRIMARY KEY index for that table. */
+ pTab = tdsqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb);
+ if( pTab && !HasRowid(pTab) ){
+ pIdx = tdsqlite3PrimaryKeyIndex(pTab);
+ }
+ }
if( pIdx ){
- static const char *azCol[] = {
- "seqno", "cid", "name", "desc", "coll", "key"
- };
+ int iIdxDb = tdsqlite3SchemaToIndex(db, pIdx->pSchema);
int i;
int mx;
if( pPragma->iArg ){
@@ -115791,20 +129207,19 @@ SQLITE_PRIVATE void sqlite3Pragma(
pParse->nMem = 3;
}
pTab = pIdx->pTable;
- sqlite3CodeVerifySchema(pParse, iDb);
- assert( pParse->nMem<=ArraySize(azCol) );
- setAllColumnNames(v, pParse->nMem, azCol);
+ tdsqlite3CodeVerifySchema(pParse, iIdxDb);
+ assert( pParse->nMem<=pPragma->nPragCName );
for(i=0; i<mx; i++){
i16 cnum = pIdx->aiColumn[i];
- sqlite3VdbeMultiLoad(v, 1, "iis", i, cnum,
+ tdsqlite3VdbeMultiLoad(v, 1, "iisX", i, cnum,
cnum<0 ? 0 : pTab->aCol[cnum].zName);
if( pPragma->iArg ){
- sqlite3VdbeMultiLoad(v, 4, "isi",
+ tdsqlite3VdbeMultiLoad(v, 4, "isiX",
pIdx->aSortOrder[i],
pIdx->azColl[i],
i<pIdx->nKeyCol);
}
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, pParse->nMem);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, pParse->nMem);
}
}
}
@@ -115814,82 +129229,107 @@ SQLITE_PRIVATE void sqlite3Pragma(
Index *pIdx;
Table *pTab;
int i;
- pTab = sqlite3FindTable(db, zRight, zDb);
+ pTab = tdsqlite3FindTable(db, zRight, zDb);
if( pTab ){
- static const char *azCol[] = {
- "seq", "name", "unique", "origin", "partial"
- };
- v = sqlite3GetVdbe(pParse);
+ int iTabDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
pParse->nMem = 5;
- sqlite3CodeVerifySchema(pParse, iDb);
- setAllColumnNames(v, 5, azCol); assert( 5==ArraySize(azCol) );
+ tdsqlite3CodeVerifySchema(pParse, iTabDb);
for(pIdx=pTab->pIndex, i=0; pIdx; pIdx=pIdx->pNext, i++){
const char *azOrigin[] = { "c", "u", "pk" };
- sqlite3VdbeMultiLoad(v, 1, "isisi",
+ tdsqlite3VdbeMultiLoad(v, 1, "isisi",
i,
pIdx->zName,
IsUniqueIndex(pIdx),
azOrigin[pIdx->idxType],
pIdx->pPartIdxWhere!=0);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5);
}
}
}
break;
case PragTyp_DATABASE_LIST: {
- static const char *azCol[] = { "seq", "name", "file" };
int i;
pParse->nMem = 3;
- setAllColumnNames(v, 3, azCol); assert( 3==ArraySize(azCol) );
for(i=0; i<db->nDb; i++){
if( db->aDb[i].pBt==0 ) continue;
assert( db->aDb[i].zDbSName!=0 );
- sqlite3VdbeMultiLoad(v, 1, "iss",
+ tdsqlite3VdbeMultiLoad(v, 1, "iss",
i,
db->aDb[i].zDbSName,
- sqlite3BtreeGetFilename(db->aDb[i].pBt));
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3);
+ tdsqlite3BtreeGetFilename(db->aDb[i].pBt));
}
}
break;
case PragTyp_COLLATION_LIST: {
- static const char *azCol[] = { "seq", "name" };
int i = 0;
HashElem *p;
pParse->nMem = 2;
- setAllColumnNames(v, 2, azCol); assert( 2==ArraySize(azCol) );
for(p=sqliteHashFirst(&db->aCollSeq); p; p=sqliteHashNext(p)){
CollSeq *pColl = (CollSeq *)sqliteHashData(p);
- sqlite3VdbeMultiLoad(v, 1, "is", i++, pColl->zName);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 2);
+ tdsqlite3VdbeMultiLoad(v, 1, "is", i++, pColl->zName);
}
}
break;
+
+#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
+ case PragTyp_FUNCTION_LIST: {
+ int i;
+ HashElem *j;
+ FuncDef *p;
+ int showInternFunc = (db->mDbFlags & DBFLAG_InternalFunc)!=0;
+ pParse->nMem = 6;
+ for(i=0; i<SQLITE_FUNC_HASH_SZ; i++){
+ for(p=tdsqlite3BuiltinFunctions.a[i]; p; p=p->u.pHash ){
+ pragmaFunclistLine(v, p, 1, showInternFunc);
+ }
+ }
+ for(j=sqliteHashFirst(&db->aFunc); j; j=sqliteHashNext(j)){
+ p = (FuncDef*)sqliteHashData(j);
+ pragmaFunclistLine(v, p, 0, showInternFunc);
+ }
+ }
+ break;
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ case PragTyp_MODULE_LIST: {
+ HashElem *j;
+ pParse->nMem = 1;
+ for(j=sqliteHashFirst(&db->aModule); j; j=sqliteHashNext(j)){
+ Module *pMod = (Module*)sqliteHashData(j);
+ tdsqlite3VdbeMultiLoad(v, 1, "s", pMod->zName);
+ }
+ }
+ break;
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+ case PragTyp_PRAGMA_LIST: {
+ int i;
+ for(i=0; i<ArraySize(aPragmaName); i++){
+ tdsqlite3VdbeMultiLoad(v, 1, "s", aPragmaName[i].zName);
+ }
+ }
+ break;
+#endif /* SQLITE_INTROSPECTION_PRAGMAS */
+
#endif /* SQLITE_OMIT_SCHEMA_PRAGMAS */
#ifndef SQLITE_OMIT_FOREIGN_KEY
case PragTyp_FOREIGN_KEY_LIST: if( zRight ){
FKey *pFK;
Table *pTab;
- pTab = sqlite3FindTable(db, zRight, zDb);
+ pTab = tdsqlite3FindTable(db, zRight, zDb);
if( pTab ){
- v = sqlite3GetVdbe(pParse);
pFK = pTab->pFKey;
if( pFK ){
- static const char *azCol[] = {
- "id", "seq", "table", "from", "to", "on_update", "on_delete",
- "match"
- };
+ int iTabDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
int i = 0;
pParse->nMem = 8;
- sqlite3CodeVerifySchema(pParse, iDb);
- setAllColumnNames(v, 8, azCol); assert( 8==ArraySize(azCol) );
+ tdsqlite3CodeVerifySchema(pParse, iTabDb);
while(pFK){
int j;
for(j=0; j<pFK->nCol; j++){
- sqlite3VdbeMultiLoad(v, 1, "iissssss",
+ tdsqlite3VdbeMultiLoad(v, 1, "iissssss",
i,
j,
pFK->zTo,
@@ -115898,7 +129338,6 @@ SQLITE_PRIVATE void sqlite3Pragma(
actionName(pFK->aAction[1]), /* ON UPDATE */
actionName(pFK->aAction[0]), /* ON DELETE */
"NONE");
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 8);
}
++i;
pFK = pFK->pNextFrom;
@@ -115926,41 +129365,40 @@ SQLITE_PRIVATE void sqlite3Pragma(
int addrTop; /* Top of a loop checking foreign keys */
int addrOk; /* Jump here if the key is OK */
int *aiCols; /* child to parent column mapping */
- static const char *azCol[] = { "table", "rowid", "parent", "fkid" };
regResult = pParse->nMem+1;
pParse->nMem += 4;
regKey = ++pParse->nMem;
regRow = ++pParse->nMem;
- v = sqlite3GetVdbe(pParse);
- setAllColumnNames(v, 4, azCol); assert( 4==ArraySize(azCol) );
- sqlite3CodeVerifySchema(pParse, iDb);
k = sqliteHashFirst(&db->aDb[iDb].pSchema->tblHash);
while( k ){
+ int iTabDb;
if( zRight ){
- pTab = sqlite3LocateTable(pParse, 0, zRight, zDb);
+ pTab = tdsqlite3LocateTable(pParse, 0, zRight, zDb);
k = 0;
}else{
pTab = (Table*)sqliteHashData(k);
k = sqliteHashNext(k);
}
if( pTab==0 || pTab->pFKey==0 ) continue;
- sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
+ iTabDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
+ tdsqlite3CodeVerifySchema(pParse, iTabDb);
+ tdsqlite3TableLock(pParse, iTabDb, pTab->tnum, 0, pTab->zName);
if( pTab->nCol+regRow>pParse->nMem ) pParse->nMem = pTab->nCol + regRow;
- sqlite3OpenTable(pParse, 0, iDb, pTab, OP_OpenRead);
- sqlite3VdbeLoadString(v, regResult, pTab->zName);
+ tdsqlite3OpenTable(pParse, 0, iTabDb, pTab, OP_OpenRead);
+ tdsqlite3VdbeLoadString(v, regResult, pTab->zName);
for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){
- pParent = sqlite3FindTable(db, pFK->zTo, zDb);
+ pParent = tdsqlite3FindTable(db, pFK->zTo, zDb);
if( pParent==0 ) continue;
pIdx = 0;
- sqlite3TableLock(pParse, iDb, pParent->tnum, 0, pParent->zName);
- x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, 0);
+ tdsqlite3TableLock(pParse, iTabDb, pParent->tnum, 0, pParent->zName);
+ x = tdsqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, 0);
if( x==0 ){
if( pIdx==0 ){
- sqlite3OpenTable(pParse, i, iDb, pParent, OP_OpenRead);
+ tdsqlite3OpenTable(pParse, i, iTabDb, pParent, OP_OpenRead);
}else{
- sqlite3VdbeAddOp3(v, OP_OpenRead, i, pIdx->tnum, iDb);
- sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+ tdsqlite3VdbeAddOp3(v, OP_OpenRead, i, pIdx->tnum, iTabDb);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pIdx);
}
}else{
k = 0;
@@ -115970,92 +129408,93 @@ SQLITE_PRIVATE void sqlite3Pragma(
assert( pParse->nErr>0 || pFK==0 );
if( pFK ) break;
if( pParse->nTab<i ) pParse->nTab = i;
- addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, 0); VdbeCoverage(v);
+ addrTop = tdsqlite3VdbeAddOp1(v, OP_Rewind, 0); VdbeCoverage(v);
for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){
- pParent = sqlite3FindTable(db, pFK->zTo, zDb);
+ pParent = tdsqlite3FindTable(db, pFK->zTo, zDb);
pIdx = 0;
aiCols = 0;
if( pParent ){
- x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, &aiCols);
+ x = tdsqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, &aiCols);
assert( x==0 );
}
- addrOk = sqlite3VdbeMakeLabel(v);
- if( pParent && pIdx==0 ){
- int iKey = pFK->aCol[0].iFrom;
- assert( iKey>=0 && iKey<pTab->nCol );
- if( iKey!=pTab->iPKey ){
- sqlite3VdbeAddOp3(v, OP_Column, 0, iKey, regRow);
- sqlite3ColumnDefault(v, pTab, iKey, regRow);
- sqlite3VdbeAddOp2(v, OP_IsNull, regRow, addrOk); VdbeCoverage(v);
- }else{
- sqlite3VdbeAddOp2(v, OP_Rowid, 0, regRow);
- }
- sqlite3VdbeAddOp3(v, OP_SeekRowid, i, 0, regRow); VdbeCoverage(v);
- sqlite3VdbeGoto(v, addrOk);
- sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2);
+ addrOk = tdsqlite3VdbeMakeLabel(pParse);
+
+ /* Generate code to read the child key values into registers
+ ** regRow..regRow+n. If any of the child key values are NULL, this
+ ** row cannot cause an FK violation. Jump directly to addrOk in
+ ** this case. */
+ for(j=0; j<pFK->nCol; j++){
+ int iCol = aiCols ? aiCols[j] : pFK->aCol[j].iFrom;
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, 0, iCol, regRow+j);
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, regRow+j, addrOk); VdbeCoverage(v);
+ }
+
+ /* Generate code to query the parent index for a matching parent
+ ** key. If a match is found, jump to addrOk. */
+ if( pIdx ){
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, pFK->nCol, regKey,
+ tdsqlite3IndexAffinityStr(db,pIdx), pFK->nCol);
+ tdsqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0);
+ VdbeCoverage(v);
+ }else if( pParent ){
+ int jmp = tdsqlite3VdbeCurrentAddr(v)+2;
+ tdsqlite3VdbeAddOp3(v, OP_SeekRowid, i, jmp, regRow); VdbeCoverage(v);
+ tdsqlite3VdbeGoto(v, addrOk);
+ assert( pFK->nCol==1 );
+ }
+
+ /* Generate code to report an FK violation to the caller. */
+ if( HasRowid(pTab) ){
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, 0, regResult+1);
}else{
- for(j=0; j<pFK->nCol; j++){
- sqlite3ExprCodeGetColumnOfTable(v, pTab, 0,
- aiCols ? aiCols[j] : pFK->aCol[j].iFrom, regRow+j);
- sqlite3VdbeAddOp2(v, OP_IsNull, regRow+j, addrOk); VdbeCoverage(v);
- }
- if( pParent ){
- sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, pFK->nCol, regKey,
- sqlite3IndexAffinityStr(db,pIdx), pFK->nCol);
- sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0);
- VdbeCoverage(v);
- }
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, regResult+1);
}
- sqlite3VdbeAddOp2(v, OP_Rowid, 0, regResult+1);
- sqlite3VdbeMultiLoad(v, regResult+2, "si", pFK->zTo, i-1);
- sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, 4);
- sqlite3VdbeResolveLabel(v, addrOk);
- sqlite3DbFree(db, aiCols);
+ tdsqlite3VdbeMultiLoad(v, regResult+2, "siX", pFK->zTo, i-1);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, regResult, 4);
+ tdsqlite3VdbeResolveLabel(v, addrOk);
+ tdsqlite3DbFree(db, aiCols);
}
- sqlite3VdbeAddOp2(v, OP_Next, 0, addrTop+1); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addrTop);
+ tdsqlite3VdbeAddOp2(v, OP_Next, 0, addrTop+1); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addrTop);
}
}
break;
#endif /* !defined(SQLITE_OMIT_TRIGGER) */
#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */
-#ifndef NDEBUG
- case PragTyp_PARSER_TRACE: {
- if( zRight ){
- if( sqlite3GetBoolean(zRight, 0) ){
- sqlite3ParserTrace(stdout, "parser: ");
- }else{
- sqlite3ParserTrace(0, 0);
- }
- }
- }
- break;
-#endif
-
+#ifndef SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA
/* Reinstall the LIKE and GLOB functions. The variant of LIKE
** used will be case sensitive or not depending on the RHS.
*/
case PragTyp_CASE_SENSITIVE_LIKE: {
if( zRight ){
- sqlite3RegisterLikeFunctions(db, sqlite3GetBoolean(zRight, 0));
+ tdsqlite3RegisterLikeFunctions(db, tdsqlite3GetBoolean(zRight, 0));
}
}
break;
+#endif /* SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA */
#ifndef SQLITE_INTEGRITY_CHECK_ERROR_MAX
# define SQLITE_INTEGRITY_CHECK_ERROR_MAX 100
#endif
#ifndef SQLITE_OMIT_INTEGRITY_CHECK
- /* Pragma "quick_check" is reduced version of
+ /* PRAGMA integrity_check
+ ** PRAGMA integrity_check(N)
+ ** PRAGMA quick_check
+ ** PRAGMA quick_check(N)
+ **
+ ** Verify the integrity of the database.
+ **
+ ** The "quick_check" is reduced version of
** integrity_check designed to detect most database corruption
- ** without most of the overhead of a full integrity-check.
+ ** without the overhead of cross-checking indexes. Quick_check
+ ** is linear time wherease integrity_check is O(NlogN).
*/
case PragTyp_INTEGRITY_CHECK: {
int i, j, addr, mxErr;
- int isQuick = (sqlite3Tolower(zLeft[0])=='q');
+ int isQuick = (tdsqlite3Tolower(zLeft[0])=='q');
/* If the PRAGMA command was of the form "PRAGMA <db>.integrity_check",
** then iDb is set to the index of the database identified by <db>.
@@ -116072,80 +129511,75 @@ SQLITE_PRIVATE void sqlite3Pragma(
/* Initialize the VDBE program */
pParse->nMem = 6;
- setOneColumnName(v, "integrity_check");
/* Set the maximum error count */
mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX;
if( zRight ){
- sqlite3GetInt32(zRight, &mxErr);
+ tdsqlite3GetInt32(zRight, &mxErr);
if( mxErr<=0 ){
mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX;
}
}
- sqlite3VdbeAddOp2(v, OP_Integer, mxErr, 1); /* reg[1] holds errors left */
+ tdsqlite3VdbeAddOp2(v, OP_Integer, mxErr-1, 1); /* reg[1] holds errors left */
/* Do an integrity check on each database file */
for(i=0; i<db->nDb; i++){
- HashElem *x;
- Hash *pTbls;
- int *aRoot;
- int cnt = 0;
- int mxIdx = 0;
- int nIdx;
+ HashElem *x; /* For looping over tables in the schema */
+ Hash *pTbls; /* Set of all tables in the schema */
+ int *aRoot; /* Array of root page numbers of all btrees */
+ int cnt = 0; /* Number of entries in aRoot[] */
+ int mxIdx = 0; /* Maximum number of indexes for any table */
if( OMIT_TEMPDB && i==1 ) continue;
if( iDb>=0 && i!=iDb ) continue;
- sqlite3CodeVerifySchema(pParse, i);
- addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1); /* Halt if out of errors */
- VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_Halt, 0, 0);
- sqlite3VdbeJumpHere(v, addr);
+ tdsqlite3CodeVerifySchema(pParse, i);
/* Do an integrity check of the B-Tree
**
** Begin by finding the root pages numbers
** for all tables and indices in the database.
*/
- assert( sqlite3SchemaMutexHeld(db, i, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, i, 0) );
pTbls = &db->aDb[i].pSchema->tblHash;
for(cnt=0, x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
- Table *pTab = sqliteHashData(x);
- Index *pIdx;
+ Table *pTab = sqliteHashData(x); /* Current table */
+ Index *pIdx; /* An index on pTab */
+ int nIdx; /* Number of indexes on pTab */
if( HasRowid(pTab) ) cnt++;
for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; }
if( nIdx>mxIdx ) mxIdx = nIdx;
}
- aRoot = sqlite3DbMallocRawNN(db, sizeof(int)*(cnt+1));
+ aRoot = tdsqlite3DbMallocRawNN(db, sizeof(int)*(cnt+1));
if( aRoot==0 ) break;
for(cnt=0, x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
Table *pTab = sqliteHashData(x);
Index *pIdx;
- if( HasRowid(pTab) ) aRoot[cnt++] = pTab->tnum;
+ if( HasRowid(pTab) ) aRoot[++cnt] = pTab->tnum;
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- aRoot[cnt++] = pIdx->tnum;
+ aRoot[++cnt] = pIdx->tnum;
}
}
- aRoot[cnt] = 0;
+ aRoot[0] = cnt;
/* Make sure sufficient number of registers have been allocated */
pParse->nMem = MAX( pParse->nMem, 8+mxIdx );
+ tdsqlite3ClearTempRegCache(pParse);
/* Do the b-tree integrity checks */
- sqlite3VdbeAddOp4(v, OP_IntegrityCk, 2, cnt, 1, (char*)aRoot,P4_INTARRAY);
- sqlite3VdbeChangeP5(v, (u8)i);
- addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v);
- sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
- sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName),
+ tdsqlite3VdbeAddOp4(v, OP_IntegrityCk, 2, cnt, 1, (char*)aRoot,P4_INTARRAY);
+ tdsqlite3VdbeChangeP5(v, (u8)i);
+ addr = tdsqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
+ tdsqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName),
P4_DYNAMIC);
- sqlite3VdbeAddOp3(v, OP_Move, 2, 4, 1);
- sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 2);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 2, 1);
- sqlite3VdbeJumpHere(v, addr);
+ tdsqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3);
+ integrityCheckResultRow(v);
+ tdsqlite3VdbeJumpHere(v, addr);
/* Make sure all the indices are constructed correctly.
*/
- for(x=sqliteHashFirst(pTbls); x && !isQuick; x=sqliteHashNext(x)){
+ for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
Table *pTab = sqliteHashData(x);
Index *pIdx, *pPk;
Index *pPrior = 0;
@@ -116153,108 +129587,130 @@ SQLITE_PRIVATE void sqlite3Pragma(
int iDataCur, iIdxCur;
int r1 = -1;
- if( pTab->pIndex==0 ) continue;
- pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab);
- addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1); /* Stop if out of errors */
- VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_Halt, 0, 0);
- sqlite3VdbeJumpHere(v, addr);
- sqlite3ExprCacheClear(pParse);
- sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0,
+ if( pTab->tnum<1 ) continue; /* Skip VIEWs or VIRTUAL TABLEs */
+ pPk = HasRowid(pTab) ? 0 : tdsqlite3PrimaryKeyIndex(pTab);
+ tdsqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0,
1, 0, &iDataCur, &iIdxCur);
- sqlite3VdbeAddOp2(v, OP_Integer, 0, 7);
+ /* reg[7] counts the number of entries in the table.
+ ** reg[8+i] counts the number of entries in the i-th index
+ */
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, 7);
for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
- sqlite3VdbeAddOp2(v, OP_Integer, 0, 8+j); /* index entries counter */
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, 8+j); /* index entries counter */
}
assert( pParse->nMem>=8+j );
- assert( sqlite3NoTempsInRange(pParse,1,7+j) );
- sqlite3VdbeAddOp2(v, OP_Rewind, iDataCur, 0); VdbeCoverage(v);
- loopTop = sqlite3VdbeAddOp2(v, OP_AddImm, 7, 1);
+ assert( tdsqlite3NoTempsInRange(pParse,1,7+j) );
+ tdsqlite3VdbeAddOp2(v, OP_Rewind, iDataCur, 0); VdbeCoverage(v);
+ loopTop = tdsqlite3VdbeAddOp2(v, OP_AddImm, 7, 1);
+ if( !isQuick ){
+ /* Sanity check on record header decoding */
+ tdsqlite3VdbeAddOp3(v, OP_Column, iDataCur, pTab->nNVCol-1,3);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
+ }
/* Verify that all NOT NULL columns really are NOT NULL */
for(j=0; j<pTab->nCol; j++){
char *zErr;
- int jmp2, jmp3;
+ int jmp2;
if( j==pTab->iPKey ) continue;
if( pTab->aCol[j].notNull==0 ) continue;
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3);
- sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
- jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */
- zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName,
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3);
+ if( tdsqlite3VdbeGetOp(v,-1)->opcode==OP_Column ){
+ tdsqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
+ }
+ jmp2 = tdsqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v);
+ zErr = tdsqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName,
pTab->aCol[j].zName);
- sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1);
- jmp3 = sqlite3VdbeAddOp1(v, OP_IfPos, 1); VdbeCoverage(v);
- sqlite3VdbeAddOp0(v, OP_Halt);
- sqlite3VdbeJumpHere(v, jmp2);
- sqlite3VdbeJumpHere(v, jmp3);
- }
- /* Validate index entries for the current row */
- for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
- int jmp2, jmp3, jmp4, jmp5;
- int ckUniq = sqlite3VdbeMakeLabel(v);
- if( pPk==pIdx ) continue;
- r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 0, &jmp3,
- pPrior, r1);
- pPrior = pIdx;
- sqlite3VdbeAddOp2(v, OP_AddImm, 8+j, 1); /* increment entry count */
- /* Verify that an index entry exists for the current table row */
- jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1,
- pIdx->nColumn); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */
- sqlite3VdbeLoadString(v, 3, "row ");
- sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3);
- sqlite3VdbeLoadString(v, 4, " missing from index ");
- sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3);
- jmp5 = sqlite3VdbeLoadString(v, 4, pIdx->zName);
- sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1);
- jmp4 = sqlite3VdbeAddOp1(v, OP_IfPos, 1); VdbeCoverage(v);
- sqlite3VdbeAddOp0(v, OP_Halt);
- sqlite3VdbeJumpHere(v, jmp2);
- /* For UNIQUE indexes, verify that only one entry exists with the
- ** current key. The entry is unique if (1) any column is NULL
- ** or (2) the next entry has a different key */
- if( IsUniqueIndex(pIdx) ){
- int uniqOk = sqlite3VdbeMakeLabel(v);
- int jmp6;
- int kk;
- for(kk=0; kk<pIdx->nKeyCol; kk++){
- int iCol = pIdx->aiColumn[kk];
- assert( iCol!=XN_ROWID && iCol<pTab->nCol );
- if( iCol>=0 && pTab->aCol[iCol].notNull ) continue;
- sqlite3VdbeAddOp2(v, OP_IsNull, r1+kk, uniqOk);
- VdbeCoverage(v);
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC);
+ integrityCheckResultRow(v);
+ tdsqlite3VdbeJumpHere(v, jmp2);
+ }
+ /* Verify CHECK constraints */
+ if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){
+ ExprList *pCheck = tdsqlite3ExprListDup(db, pTab->pCheck, 0);
+ if( db->mallocFailed==0 ){
+ int addrCkFault = tdsqlite3VdbeMakeLabel(pParse);
+ int addrCkOk = tdsqlite3VdbeMakeLabel(pParse);
+ char *zErr;
+ int k;
+ pParse->iSelfTab = iDataCur + 1;
+ for(k=pCheck->nExpr-1; k>0; k--){
+ tdsqlite3ExprIfFalse(pParse, pCheck->a[k].pExpr, addrCkFault, 0);
+ }
+ tdsqlite3ExprIfTrue(pParse, pCheck->a[0].pExpr, addrCkOk,
+ SQLITE_JUMPIFNULL);
+ tdsqlite3VdbeResolveLabel(v, addrCkFault);
+ pParse->iSelfTab = 0;
+ zErr = tdsqlite3MPrintf(db, "CHECK constraint failed in %s",
+ pTab->zName);
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC);
+ integrityCheckResultRow(v);
+ tdsqlite3VdbeResolveLabel(v, addrCkOk);
+ }
+ tdsqlite3ExprListDelete(db, pCheck);
+ }
+ if( !isQuick ){ /* Omit the remaining tests for quick_check */
+ /* Validate index entries for the current row */
+ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ int jmp2, jmp3, jmp4, jmp5;
+ int ckUniq = tdsqlite3VdbeMakeLabel(pParse);
+ if( pPk==pIdx ) continue;
+ r1 = tdsqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 0, &jmp3,
+ pPrior, r1);
+ pPrior = pIdx;
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, 8+j, 1);/* increment entry count */
+ /* Verify that an index entry exists for the current table row */
+ jmp2 = tdsqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1,
+ pIdx->nColumn); VdbeCoverage(v);
+ tdsqlite3VdbeLoadString(v, 3, "row ");
+ tdsqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3);
+ tdsqlite3VdbeLoadString(v, 4, " missing from index ");
+ tdsqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3);
+ jmp5 = tdsqlite3VdbeLoadString(v, 4, pIdx->zName);
+ tdsqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3);
+ jmp4 = integrityCheckResultRow(v);
+ tdsqlite3VdbeJumpHere(v, jmp2);
+ /* For UNIQUE indexes, verify that only one entry exists with the
+ ** current key. The entry is unique if (1) any column is NULL
+ ** or (2) the next entry has a different key */
+ if( IsUniqueIndex(pIdx) ){
+ int uniqOk = tdsqlite3VdbeMakeLabel(pParse);
+ int jmp6;
+ int kk;
+ for(kk=0; kk<pIdx->nKeyCol; kk++){
+ int iCol = pIdx->aiColumn[kk];
+ assert( iCol!=XN_ROWID && iCol<pTab->nCol );
+ if( iCol>=0 && pTab->aCol[iCol].notNull ) continue;
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, r1+kk, uniqOk);
+ VdbeCoverage(v);
+ }
+ jmp6 = tdsqlite3VdbeAddOp1(v, OP_Next, iIdxCur+j); VdbeCoverage(v);
+ tdsqlite3VdbeGoto(v, uniqOk);
+ tdsqlite3VdbeJumpHere(v, jmp6);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxGT, iIdxCur+j, uniqOk, r1,
+ pIdx->nKeyCol); VdbeCoverage(v);
+ tdsqlite3VdbeLoadString(v, 3, "non-unique entry in index ");
+ tdsqlite3VdbeGoto(v, jmp5);
+ tdsqlite3VdbeResolveLabel(v, uniqOk);
}
- jmp6 = sqlite3VdbeAddOp1(v, OP_Next, iIdxCur+j); VdbeCoverage(v);
- sqlite3VdbeGoto(v, uniqOk);
- sqlite3VdbeJumpHere(v, jmp6);
- sqlite3VdbeAddOp4Int(v, OP_IdxGT, iIdxCur+j, uniqOk, r1,
- pIdx->nKeyCol); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */
- sqlite3VdbeLoadString(v, 3, "non-unique entry in index ");
- sqlite3VdbeGoto(v, jmp5);
- sqlite3VdbeResolveLabel(v, uniqOk);
+ tdsqlite3VdbeJumpHere(v, jmp4);
+ tdsqlite3ResolvePartIdxLabel(pParse, jmp3);
}
- sqlite3VdbeJumpHere(v, jmp4);
- sqlite3ResolvePartIdxLabel(pParse, jmp3);
}
- sqlite3VdbeAddOp2(v, OP_Next, iDataCur, loopTop); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, loopTop-1);
+ tdsqlite3VdbeAddOp2(v, OP_Next, iDataCur, loopTop); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, loopTop-1);
#ifndef SQLITE_OMIT_BTREECOUNT
- sqlite3VdbeLoadString(v, 2, "wrong # of entries in index ");
- for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
- if( pPk==pIdx ) continue;
- addr = sqlite3VdbeCurrentAddr(v);
- sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr+2); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_Halt, 0, 0);
- sqlite3VdbeAddOp2(v, OP_Count, iIdxCur+j, 3);
- sqlite3VdbeAddOp3(v, OP_Eq, 8+j, addr+8, 3); VdbeCoverage(v);
- sqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
- sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1);
- sqlite3VdbeLoadString(v, 3, pIdx->zName);
- sqlite3VdbeAddOp3(v, OP_Concat, 3, 2, 7);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 7, 1);
+ if( !isQuick ){
+ tdsqlite3VdbeLoadString(v, 2, "wrong # of entries in index ");
+ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ if( pPk==pIdx ) continue;
+ tdsqlite3VdbeAddOp2(v, OP_Count, iIdxCur+j, 3);
+ addr = tdsqlite3VdbeAddOp3(v, OP_Eq, 8+j, 0, 3); VdbeCoverage(v);
+ tdsqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
+ tdsqlite3VdbeLoadString(v, 4, pIdx->zName);
+ tdsqlite3VdbeAddOp3(v, OP_Concat, 4, 2, 3);
+ integrityCheckResultRow(v);
+ tdsqlite3VdbeJumpHere(v, addr);
+ }
}
#endif /* SQLITE_OMIT_BTREECOUNT */
}
@@ -116263,18 +129719,24 @@ SQLITE_PRIVATE void sqlite3Pragma(
static const int iLn = VDBE_OFFSET_LINENO(2);
static const VdbeOpList endCode[] = {
{ OP_AddImm, 1, 0, 0}, /* 0 */
- { OP_If, 1, 4, 0}, /* 1 */
+ { OP_IfNotZero, 1, 4, 0}, /* 1 */
{ OP_String8, 0, 3, 0}, /* 2 */
{ OP_ResultRow, 3, 1, 0}, /* 3 */
+ { OP_Halt, 0, 0, 0}, /* 4 */
+ { OP_String8, 0, 3, 0}, /* 5 */
+ { OP_Goto, 0, 3, 0}, /* 6 */
};
VdbeOp *aOp;
- aOp = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode, iLn);
+ aOp = tdsqlite3VdbeAddOpList(v, ArraySize(endCode), endCode, iLn);
if( aOp ){
- aOp[0].p2 = -mxErr;
+ aOp[0].p2 = 1-mxErr;
aOp[2].p4type = P4_STATIC;
aOp[2].p4.z = "ok";
+ aOp[5].p4type = P4_STATIC;
+ aOp[5].p4.z = (char*)tdsqlite3ErrStr(SQLITE_CORRUPT);
}
+ tdsqlite3VdbeChangeP3(v, 0, tdsqlite3VdbeCurrentAddr(v)-2);
}
}
break;
@@ -116320,30 +129782,37 @@ SQLITE_PRIVATE void sqlite3Pragma(
};
const struct EncName *pEnc;
if( !zRight ){ /* "PRAGMA encoding" */
- if( sqlite3ReadSchema(pParse) ) goto pragma_out;
+ if( tdsqlite3ReadSchema(pParse) ) goto pragma_out;
assert( encnames[SQLITE_UTF8].enc==SQLITE_UTF8 );
assert( encnames[SQLITE_UTF16LE].enc==SQLITE_UTF16LE );
assert( encnames[SQLITE_UTF16BE].enc==SQLITE_UTF16BE );
- returnSingleText(v, "encoding", encnames[ENC(pParse->db)].zName);
+ returnSingleText(v, encnames[ENC(pParse->db)].zName);
}else{ /* "PRAGMA encoding = XXX" */
/* Only change the value of sqlite.enc if the database handle is not
** initialized. If the main database exists, the new sqlite.enc value
** will be overwritten when the schema is next loaded. If it does not
** already exists, it will be created to use the new encoding value.
*/
- if(
- !(DbHasProperty(db, 0, DB_SchemaLoaded)) ||
- DbHasProperty(db, 0, DB_Empty)
- ){
+ int canChangeEnc = 1; /* True if allowed to change the encoding */
+ int i; /* For looping over all attached databases */
+ for(i=0; i<db->nDb; i++){
+ if( db->aDb[i].pBt!=0
+ && DbHasProperty(db,i,DB_SchemaLoaded)
+ && !DbHasProperty(db,i,DB_Empty)
+ ){
+ canChangeEnc = 0;
+ }
+ }
+ if( canChangeEnc ){
for(pEnc=&encnames[0]; pEnc->zName; pEnc++){
- if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){
+ if( 0==tdsqlite3StrICmp(zRight, pEnc->zName) ){
SCHEMA_ENC(db) = ENC(db) =
pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE;
break;
}
}
if( !pEnc->zName ){
- sqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight);
+ tdsqlite3ErrorMsg(pParse, "unsupported encoding: %s", zRight);
}
}
}
@@ -116386,21 +129855,21 @@ SQLITE_PRIVATE void sqlite3Pragma(
*/
case PragTyp_HEADER_VALUE: {
int iCookie = pPragma->iArg; /* Which cookie to read or write */
- sqlite3VdbeUsesBtree(v, iDb);
- if( zRight && (pPragma->mPragFlag & PragFlag_ReadOnly)==0 ){
+ tdsqlite3VdbeUsesBtree(v, iDb);
+ if( zRight && (pPragma->mPragFlg & PragFlg_ReadOnly)==0 ){
/* Write the specified cookie value */
static const VdbeOpList setCookie[] = {
{ OP_Transaction, 0, 1, 0}, /* 0 */
{ OP_SetCookie, 0, 0, 0}, /* 1 */
};
VdbeOp *aOp;
- sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(setCookie));
- aOp = sqlite3VdbeAddOpList(v, ArraySize(setCookie), setCookie, 0);
+ tdsqlite3VdbeVerifyNoMallocRequired(v, ArraySize(setCookie));
+ aOp = tdsqlite3VdbeAddOpList(v, ArraySize(setCookie), setCookie, 0);
if( ONLY_IF_REALLOC_STRESS(aOp==0) ) break;
aOp[0].p1 = iDb;
aOp[1].p1 = iDb;
aOp[1].p2 = iCookie;
- aOp[1].p3 = sqlite3Atoi(zRight);
+ aOp[1].p3 = tdsqlite3Atoi(zRight);
}else{
/* Read the specified cookie value */
static const VdbeOpList readCookie[] = {
@@ -116409,15 +129878,13 @@ SQLITE_PRIVATE void sqlite3Pragma(
{ OP_ResultRow, 1, 1, 0}
};
VdbeOp *aOp;
- sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(readCookie));
- aOp = sqlite3VdbeAddOpList(v, ArraySize(readCookie),readCookie,0);
+ tdsqlite3VdbeVerifyNoMallocRequired(v, ArraySize(readCookie));
+ aOp = tdsqlite3VdbeAddOpList(v, ArraySize(readCookie),readCookie,0);
if( ONLY_IF_REALLOC_STRESS(aOp==0) ) break;
aOp[0].p1 = iDb;
aOp[1].p1 = iDb;
aOp[1].p3 = iCookie;
- sqlite3VdbeSetNumCols(v, 1);
- sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, SQLITE_TRANSIENT);
- sqlite3VdbeReusable(v);
+ tdsqlite3VdbeReusable(v);
}
}
break;
@@ -116434,12 +129901,11 @@ SQLITE_PRIVATE void sqlite3Pragma(
int i = 0;
const char *zOpt;
pParse->nMem = 1;
- setOneColumnName(v, "compile_option");
- while( (zOpt = sqlite3_compileoption_get(i++))!=0 ){
- sqlite3VdbeLoadString(v, 1, zOpt);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+ while( (zOpt = tdsqlite3_compileoption_get(i++))!=0 ){
+ tdsqlite3VdbeLoadString(v, 1, zOpt);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
}
- sqlite3VdbeReusable(v);
+ tdsqlite3VdbeReusable(v);
}
break;
#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
@@ -116451,22 +129917,20 @@ SQLITE_PRIVATE void sqlite3Pragma(
** Checkpoint the database.
*/
case PragTyp_WAL_CHECKPOINT: {
- static const char *azCol[] = { "busy", "log", "checkpointed" };
int iBt = (pId2->z?iDb:SQLITE_MAX_ATTACHED);
int eMode = SQLITE_CHECKPOINT_PASSIVE;
if( zRight ){
- if( sqlite3StrICmp(zRight, "full")==0 ){
+ if( tdsqlite3StrICmp(zRight, "full")==0 ){
eMode = SQLITE_CHECKPOINT_FULL;
- }else if( sqlite3StrICmp(zRight, "restart")==0 ){
+ }else if( tdsqlite3StrICmp(zRight, "restart")==0 ){
eMode = SQLITE_CHECKPOINT_RESTART;
- }else if( sqlite3StrICmp(zRight, "truncate")==0 ){
+ }else if( tdsqlite3StrICmp(zRight, "truncate")==0 ){
eMode = SQLITE_CHECKPOINT_TRUNCATE;
}
}
- setAllColumnNames(v, 3, azCol); assert( 3==ArraySize(azCol) );
pParse->nMem = 3;
- sqlite3VdbeAddOp3(v, OP_Checkpoint, iBt, eMode, 1);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3);
+ tdsqlite3VdbeAddOp3(v, OP_Checkpoint, iBt, eMode, 1);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3);
}
break;
@@ -116480,10 +129944,10 @@ SQLITE_PRIVATE void sqlite3Pragma(
*/
case PragTyp_WAL_AUTOCHECKPOINT: {
if( zRight ){
- sqlite3_wal_autocheckpoint(db, sqlite3Atoi(zRight));
+ tdsqlite3_wal_autocheckpoint(db, tdsqlite3Atoi(zRight));
}
- returnSingleInt(v, "wal_autocheckpoint",
- db->xWalCallback==sqlite3WalDefaultHook ?
+ returnSingleInt(v,
+ db->xWalCallback==tdsqlite3WalDefaultHook ?
SQLITE_PTR_TO_INT(db->pWalArg) : 0);
}
break;
@@ -116494,10 +129958,123 @@ SQLITE_PRIVATE void sqlite3Pragma(
**
** IMPLEMENTATION-OF: R-23445-46109 This pragma causes the database
** connection on which it is invoked to free up as much memory as it
- ** can, by calling sqlite3_db_release_memory().
+ ** can, by calling tdsqlite3_db_release_memory().
*/
case PragTyp_SHRINK_MEMORY: {
- sqlite3_db_release_memory(db);
+ tdsqlite3_db_release_memory(db);
+ break;
+ }
+
+ /*
+ ** PRAGMA optimize
+ ** PRAGMA optimize(MASK)
+ ** PRAGMA schema.optimize
+ ** PRAGMA schema.optimize(MASK)
+ **
+ ** Attempt to optimize the database. All schemas are optimized in the first
+ ** two forms, and only the specified schema is optimized in the latter two.
+ **
+ ** The details of optimizations performed by this pragma are expected
+ ** to change and improve over time. Applications should anticipate that
+ ** this pragma will perform new optimizations in future releases.
+ **
+ ** The optional argument is a bitmask of optimizations to perform:
+ **
+ ** 0x0001 Debugging mode. Do not actually perform any optimizations
+ ** but instead return one line of text for each optimization
+ ** that would have been done. Off by default.
+ **
+ ** 0x0002 Run ANALYZE on tables that might benefit. On by default.
+ ** See below for additional information.
+ **
+ ** 0x0004 (Not yet implemented) Record usage and performance
+ ** information from the current session in the
+ ** database file so that it will be available to "optimize"
+ ** pragmas run by future database connections.
+ **
+ ** 0x0008 (Not yet implemented) Create indexes that might have
+ ** been helpful to recent queries
+ **
+ ** The default MASK is and always shall be 0xfffe. 0xfffe means perform all
+ ** of the optimizations listed above except Debug Mode, including new
+ ** optimizations that have not yet been invented. If new optimizations are
+ ** ever added that should be off by default, those off-by-default
+ ** optimizations will have bitmasks of 0x10000 or larger.
+ **
+ ** DETERMINATION OF WHEN TO RUN ANALYZE
+ **
+ ** In the current implementation, a table is analyzed if only if all of
+ ** the following are true:
+ **
+ ** (1) MASK bit 0x02 is set.
+ **
+ ** (2) The query planner used sqlite_stat1-style statistics for one or
+ ** more indexes of the table at some point during the lifetime of
+ ** the current connection.
+ **
+ ** (3) One or more indexes of the table are currently unanalyzed OR
+ ** the number of rows in the table has increased by 25 times or more
+ ** since the last time ANALYZE was run.
+ **
+ ** The rules for when tables are analyzed are likely to change in
+ ** future releases.
+ */
+ case PragTyp_OPTIMIZE: {
+ int iDbLast; /* Loop termination point for the schema loop */
+ int iTabCur; /* Cursor for a table whose size needs checking */
+ HashElem *k; /* Loop over tables of a schema */
+ Schema *pSchema; /* The current schema */
+ Table *pTab; /* A table in the schema */
+ Index *pIdx; /* An index of the table */
+ LogEst szThreshold; /* Size threshold above which reanalysis is needd */
+ char *zSubSql; /* SQL statement for the OP_SqlExec opcode */
+ u32 opMask; /* Mask of operations to perform */
+
+ if( zRight ){
+ opMask = (u32)tdsqlite3Atoi(zRight);
+ if( (opMask & 0x02)==0 ) break;
+ }else{
+ opMask = 0xfffe;
+ }
+ iTabCur = pParse->nTab++;
+ for(iDbLast = zDb?iDb:db->nDb-1; iDb<=iDbLast; iDb++){
+ if( iDb==1 ) continue;
+ tdsqlite3CodeVerifySchema(pParse, iDb);
+ pSchema = db->aDb[iDb].pSchema;
+ for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){
+ pTab = (Table*)sqliteHashData(k);
+
+ /* If table pTab has not been used in a way that would benefit from
+ ** having analysis statistics during the current session, then skip it.
+ ** This also has the effect of skipping virtual tables and views */
+ if( (pTab->tabFlags & TF_StatsUsed)==0 ) continue;
+
+ /* Reanalyze if the table is 25 times larger than the last analysis */
+ szThreshold = pTab->nRowLogEst + 46; assert( tdsqlite3LogEst(25)==46 );
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ if( !pIdx->hasStat1 ){
+ szThreshold = 0; /* Always analyze if any index lacks statistics */
+ break;
+ }
+ }
+ if( szThreshold ){
+ tdsqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead);
+ tdsqlite3VdbeAddOp3(v, OP_IfSmaller, iTabCur,
+ tdsqlite3VdbeCurrentAddr(v)+2+(opMask&1), szThreshold);
+ VdbeCoverage(v);
+ }
+ zSubSql = tdsqlite3MPrintf(db, "ANALYZE \"%w\".\"%w\"",
+ db->aDb[iDb].zDbSName, pTab->zName);
+ if( opMask & 0x01 ){
+ int r1 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, r1, 0, zSubSql, P4_DYNAMIC);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, r1, 1);
+ }else{
+ tdsqlite3VdbeAddOp4(v, OP_SqlExec, 0, 0, 0, zSubSql, P4_DYNAMIC);
+ }
+ }
+ }
+ tdsqlite3VdbeAddOp0(v, OP_Expire);
break;
}
@@ -116505,7 +130082,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
** PRAGMA busy_timeout
** PRAGMA busy_timeout = N
**
- ** Call sqlite3_busy_timeout(db, N). Return the current timeout value
+ ** Call tdsqlite3_busy_timeout(db, N). Return the current timeout value
** if one is set. If no busy handler or a different busy handler is set
** then 0 is returned. Setting the busy_timeout to 0 or negative
** disables the timeout.
@@ -116513,9 +130090,9 @@ SQLITE_PRIVATE void sqlite3Pragma(
/*case PragTyp_BUSY_TIMEOUT*/ default: {
assert( pPragma->ePragTyp==PragTyp_BUSY_TIMEOUT );
if( zRight ){
- sqlite3_busy_timeout(db, sqlite3Atoi(zRight));
+ tdsqlite3_busy_timeout(db, tdsqlite3Atoi(zRight));
}
- returnSingleInt(v, "timeout", db->busyTimeout);
+ returnSingleInt(v, db->busyTimeout);
break;
}
@@ -116524,18 +130101,39 @@ SQLITE_PRIVATE void sqlite3Pragma(
** PRAGMA soft_heap_limit = N
**
** IMPLEMENTATION-OF: R-26343-45930 This pragma invokes the
- ** sqlite3_soft_heap_limit64() interface with the argument N, if N is
+ ** tdsqlite3_soft_heap_limit64() interface with the argument N, if N is
** specified and is a non-negative integer.
** IMPLEMENTATION-OF: R-64451-07163 The soft_heap_limit pragma always
** returns the same integer that would be returned by the
- ** sqlite3_soft_heap_limit64(-1) C-language function.
+ ** tdsqlite3_soft_heap_limit64(-1) C-language function.
*/
case PragTyp_SOFT_HEAP_LIMIT: {
- sqlite3_int64 N;
- if( zRight && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK ){
- sqlite3_soft_heap_limit64(N);
+ tdsqlite3_int64 N;
+ if( zRight && tdsqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK ){
+ tdsqlite3_soft_heap_limit64(N);
}
- returnSingleInt(v, "soft_heap_limit", sqlite3_soft_heap_limit64(-1));
+ returnSingleInt(v, tdsqlite3_soft_heap_limit64(-1));
+ break;
+ }
+
+ /*
+ ** PRAGMA hard_heap_limit
+ ** PRAGMA hard_heap_limit = N
+ **
+ ** Invoke tdsqlite3_hard_heap_limit64() to query or set the hard heap
+ ** limit. The hard heap limit can be activated or lowered by this
+ ** pragma, but not raised or deactivated. Only the
+ ** tdsqlite3_hard_heap_limit64() C-language API can raise or deactivate
+ ** the hard heap limit. This allows an application to set a heap limit
+ ** constraint that cannot be relaxed by an untrusted SQL script.
+ */
+ case PragTyp_HARD_HEAP_LIMIT: {
+ tdsqlite3_int64 N;
+ if( zRight && tdsqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK ){
+ tdsqlite3_int64 iPrior = tdsqlite3_hard_heap_limit64(-1);
+ if( N>0 && (iPrior==0 || iPrior>N) ) tdsqlite3_hard_heap_limit64(N);
+ }
+ returnSingleInt(v, tdsqlite3_hard_heap_limit64(-1));
break;
}
@@ -116547,15 +130145,14 @@ SQLITE_PRIVATE void sqlite3Pragma(
** maximum, which might be less than requested.
*/
case PragTyp_THREADS: {
- sqlite3_int64 N;
+ tdsqlite3_int64 N;
if( zRight
- && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK
+ && tdsqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK
&& N>=0
){
- sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, (int)(N&0x7fffffff));
+ tdsqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, (int)(N&0x7fffffff));
}
- returnSingleInt(v, "threads",
- sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, -1));
+ returnSingleInt(v, tdsqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, -1));
break;
}
@@ -116567,9 +130164,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
static const char *const azLockName[] = {
"unlocked", "shared", "reserved", "pending", "exclusive"
};
- static const char *azCol[] = { "database", "status" };
int i;
- setAllColumnNames(v, 2, azCol); assert( 2==ArraySize(azCol) );
pParse->nMem = 2;
for(i=0; i<db->nDb; i++){
Btree *pBt;
@@ -116577,41 +130172,54 @@ SQLITE_PRIVATE void sqlite3Pragma(
int j;
if( db->aDb[i].zDbSName==0 ) continue;
pBt = db->aDb[i].pBt;
- if( pBt==0 || sqlite3BtreePager(pBt)==0 ){
+ if( pBt==0 || tdsqlite3BtreePager(pBt)==0 ){
zState = "closed";
- }else if( sqlite3_file_control(db, i ? db->aDb[i].zDbSName : 0,
+ }else if( tdsqlite3_file_control(db, i ? db->aDb[i].zDbSName : 0,
SQLITE_FCNTL_LOCKSTATE, &j)==SQLITE_OK ){
zState = azLockName[j];
}
- sqlite3VdbeMultiLoad(v, 1, "ss", db->aDb[i].zDbSName, zState);
- sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 2);
+ tdsqlite3VdbeMultiLoad(v, 1, "ss", db->aDb[i].zDbSName, zState);
}
break;
}
#endif
#ifdef SQLITE_HAS_CODEC
+ /* Pragma iArg
+ ** ---------- ------
+ ** key 0
+ ** rekey 1
+ ** hexkey 2
+ ** hexrekey 3
+ ** textkey 4
+ ** textrekey 5
+ */
case PragTyp_KEY: {
- if( zRight ) sqlite3_key_v2(db, zDb, zRight, sqlite3Strlen30(zRight));
- break;
- }
- case PragTyp_REKEY: {
- if( zRight ) sqlite3_rekey_v2(db, zDb, zRight, sqlite3Strlen30(zRight));
- break;
- }
- case PragTyp_HEXKEY: {
if( zRight ){
- u8 iByte;
- int i;
- char zKey[40];
- for(i=0, iByte=0; i<sizeof(zKey)*2 && sqlite3Isxdigit(zRight[i]); i++){
- iByte = (iByte<<4) + sqlite3HexToInt(zRight[i]);
- if( (i&1)!=0 ) zKey[i/2] = iByte;
+ char zBuf[40];
+ const char *zKey = zRight;
+ int n;
+ if( pPragma->iArg==2 || pPragma->iArg==3 ){
+ u8 iByte;
+ int i;
+ for(i=0, iByte=0; i<sizeof(zBuf)*2 && tdsqlite3Isxdigit(zRight[i]); i++){
+ iByte = (iByte<<4) + tdsqlite3HexToInt(zRight[i]);
+ if( (i&1)!=0 ) zBuf[i/2] = iByte;
+ }
+ zKey = zBuf;
+ n = i/2;
+ }else{
+ n = pPragma->iArg<4 ? tdsqlite3Strlen30(zRight) : -1;
}
- if( (zLeft[3] & 0xf)==0xb ){
- sqlite3_key_v2(db, zDb, zKey, i/2);
+ if( (pPragma->iArg & 1)==0 ){
+ rc = tdsqlite3_key_v2(db, zDb, zKey, n);
}else{
- sqlite3_rekey_v2(db, zDb, zKey, i/2);
+ rc = tdsqlite3_rekey_v2(db, zDb, zKey, n);
+ }
+ if( rc==SQLITE_OK && n!=0 ){
+ tdsqlite3VdbeSetNumCols(v, 1);
+ tdsqlite3VdbeSetColName(v, 0, COLNAME_NAME, "ok", SQLITE_STATIC);
+ returnSingleText(v, "ok");
}
}
break;
@@ -116620,13 +130228,13 @@ SQLITE_PRIVATE void sqlite3Pragma(
#if defined(SQLITE_HAS_CODEC) || defined(SQLITE_ENABLE_CEROD)
case PragTyp_ACTIVATE_EXTENSIONS: if( zRight ){
#ifdef SQLITE_HAS_CODEC
- if( sqlite3StrNICmp(zRight, "see-", 4)==0 ){
- sqlite3_activate_see(&zRight[4]);
+ if( tdsqlite3StrNICmp(zRight, "see-", 4)==0 ){
+ tdsqlite3_activate_see(&zRight[4]);
}
#endif
#ifdef SQLITE_ENABLE_CEROD
- if( sqlite3StrNICmp(zRight, "cerod-", 6)==0 ){
- sqlite3_activate_cerod(&zRight[6]);
+ if( tdsqlite3StrNICmp(zRight, "cerod-", 6)==0 ){
+ tdsqlite3_activate_cerod(&zRight[6]);
}
#endif
}
@@ -116635,11 +130243,330 @@ SQLITE_PRIVATE void sqlite3Pragma(
} /* End of the PRAGMA switch */
+ /* The following block is a no-op unless SQLITE_DEBUG is defined. Its only
+ ** purpose is to execute assert() statements to verify that if the
+ ** PragFlg_NoColumns1 flag is set and the caller specified an argument
+ ** to the PRAGMA, the implementation has not added any OP_ResultRow
+ ** instructions to the VM. */
+ if( (pPragma->mPragFlg & PragFlg_NoColumns1) && zRight ){
+ tdsqlite3VdbeVerifyNoResultRow(v);
+ }
+
pragma_out:
- sqlite3DbFree(db, zLeft);
- sqlite3DbFree(db, zRight);
+ tdsqlite3DbFree(db, zLeft);
+ tdsqlite3DbFree(db, zRight);
+}
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/*****************************************************************************
+** Implementation of an eponymous virtual table that runs a pragma.
+**
+*/
+typedef struct PragmaVtab PragmaVtab;
+typedef struct PragmaVtabCursor PragmaVtabCursor;
+struct PragmaVtab {
+ tdsqlite3_vtab base; /* Base class. Must be first */
+ tdsqlite3 *db; /* The database connection to which it belongs */
+ const PragmaName *pName; /* Name of the pragma */
+ u8 nHidden; /* Number of hidden columns */
+ u8 iHidden; /* Index of the first hidden column */
+};
+struct PragmaVtabCursor {
+ tdsqlite3_vtab_cursor base; /* Base class. Must be first */
+ tdsqlite3_stmt *pPragma; /* The pragma statement to run */
+ sqlite_int64 iRowid; /* Current rowid */
+ char *azArg[2]; /* Value of the argument and schema */
+};
+
+/*
+** Pragma virtual table module xConnect method.
+*/
+static int pragmaVtabConnect(
+ tdsqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ tdsqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ const PragmaName *pPragma = (const PragmaName*)pAux;
+ PragmaVtab *pTab = 0;
+ int rc;
+ int i, j;
+ char cSep = '(';
+ StrAccum acc;
+ char zBuf[200];
+
+ UNUSED_PARAMETER(argc);
+ UNUSED_PARAMETER(argv);
+ tdsqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0);
+ tdsqlite3_str_appendall(&acc, "CREATE TABLE x");
+ for(i=0, j=pPragma->iPragCName; i<pPragma->nPragCName; i++, j++){
+ tdsqlite3_str_appendf(&acc, "%c\"%s\"", cSep, pragCName[j]);
+ cSep = ',';
+ }
+ if( i==0 ){
+ tdsqlite3_str_appendf(&acc, "(\"%s\"", pPragma->zName);
+ i++;
+ }
+ j = 0;
+ if( pPragma->mPragFlg & PragFlg_Result1 ){
+ tdsqlite3_str_appendall(&acc, ",arg HIDDEN");
+ j++;
+ }
+ if( pPragma->mPragFlg & (PragFlg_SchemaOpt|PragFlg_SchemaReq) ){
+ tdsqlite3_str_appendall(&acc, ",schema HIDDEN");
+ j++;
+ }
+ tdsqlite3_str_append(&acc, ")", 1);
+ tdsqlite3StrAccumFinish(&acc);
+ assert( strlen(zBuf) < sizeof(zBuf)-1 );
+ rc = tdsqlite3_declare_vtab(db, zBuf);
+ if( rc==SQLITE_OK ){
+ pTab = (PragmaVtab*)tdsqlite3_malloc(sizeof(PragmaVtab));
+ if( pTab==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pTab, 0, sizeof(PragmaVtab));
+ pTab->pName = pPragma;
+ pTab->db = db;
+ pTab->iHidden = i;
+ pTab->nHidden = j;
+ }
+ }else{
+ *pzErr = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
+ }
+
+ *ppVtab = (tdsqlite3_vtab*)pTab;
+ return rc;
+}
+
+/*
+** Pragma virtual table module xDisconnect method.
+*/
+static int pragmaVtabDisconnect(tdsqlite3_vtab *pVtab){
+ PragmaVtab *pTab = (PragmaVtab*)pVtab;
+ tdsqlite3_free(pTab);
+ return SQLITE_OK;
}
+/* Figure out the best index to use to search a pragma virtual table.
+**
+** There are not really any index choices. But we want to encourage the
+** query planner to give == constraints on as many hidden parameters as
+** possible, and especially on the first hidden parameter. So return a
+** high cost if hidden parameters are unconstrained.
+*/
+static int pragmaVtabBestIndex(tdsqlite3_vtab *tab, tdsqlite3_index_info *pIdxInfo){
+ PragmaVtab *pTab = (PragmaVtab*)tab;
+ const struct tdsqlite3_index_constraint *pConstraint;
+ int i, j;
+ int seen[2];
+
+ pIdxInfo->estimatedCost = (double)1;
+ if( pTab->nHidden==0 ){ return SQLITE_OK; }
+ pConstraint = pIdxInfo->aConstraint;
+ seen[0] = 0;
+ seen[1] = 0;
+ for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
+ if( pConstraint->usable==0 ) continue;
+ if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
+ if( pConstraint->iColumn < pTab->iHidden ) continue;
+ j = pConstraint->iColumn - pTab->iHidden;
+ assert( j < 2 );
+ seen[j] = i+1;
+ }
+ if( seen[0]==0 ){
+ pIdxInfo->estimatedCost = (double)2147483647;
+ pIdxInfo->estimatedRows = 2147483647;
+ return SQLITE_OK;
+ }
+ j = seen[0]-1;
+ pIdxInfo->aConstraintUsage[j].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[j].omit = 1;
+ if( seen[1]==0 ) return SQLITE_OK;
+ pIdxInfo->estimatedCost = (double)20;
+ pIdxInfo->estimatedRows = 20;
+ j = seen[1]-1;
+ pIdxInfo->aConstraintUsage[j].argvIndex = 2;
+ pIdxInfo->aConstraintUsage[j].omit = 1;
+ return SQLITE_OK;
+}
+
+/* Create a new cursor for the pragma virtual table */
+static int pragmaVtabOpen(tdsqlite3_vtab *pVtab, tdsqlite3_vtab_cursor **ppCursor){
+ PragmaVtabCursor *pCsr;
+ pCsr = (PragmaVtabCursor*)tdsqlite3_malloc(sizeof(*pCsr));
+ if( pCsr==0 ) return SQLITE_NOMEM;
+ memset(pCsr, 0, sizeof(PragmaVtabCursor));
+ pCsr->base.pVtab = pVtab;
+ *ppCursor = &pCsr->base;
+ return SQLITE_OK;
+}
+
+/* Clear all content from pragma virtual table cursor. */
+static void pragmaVtabCursorClear(PragmaVtabCursor *pCsr){
+ int i;
+ tdsqlite3_finalize(pCsr->pPragma);
+ pCsr->pPragma = 0;
+ for(i=0; i<ArraySize(pCsr->azArg); i++){
+ tdsqlite3_free(pCsr->azArg[i]);
+ pCsr->azArg[i] = 0;
+ }
+}
+
+/* Close a pragma virtual table cursor */
+static int pragmaVtabClose(tdsqlite3_vtab_cursor *cur){
+ PragmaVtabCursor *pCsr = (PragmaVtabCursor*)cur;
+ pragmaVtabCursorClear(pCsr);
+ tdsqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+/* Advance the pragma virtual table cursor to the next row */
+static int pragmaVtabNext(tdsqlite3_vtab_cursor *pVtabCursor){
+ PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor;
+ int rc = SQLITE_OK;
+
+ /* Increment the xRowid value */
+ pCsr->iRowid++;
+ assert( pCsr->pPragma );
+ if( SQLITE_ROW!=tdsqlite3_step(pCsr->pPragma) ){
+ rc = tdsqlite3_finalize(pCsr->pPragma);
+ pCsr->pPragma = 0;
+ pragmaVtabCursorClear(pCsr);
+ }
+ return rc;
+}
+
+/*
+** Pragma virtual table module xFilter method.
+*/
+static int pragmaVtabFilter(
+ tdsqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, tdsqlite3_value **argv
+){
+ PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor;
+ PragmaVtab *pTab = (PragmaVtab*)(pVtabCursor->pVtab);
+ int rc;
+ int i, j;
+ StrAccum acc;
+ char *zSql;
+
+ UNUSED_PARAMETER(idxNum);
+ UNUSED_PARAMETER(idxStr);
+ pragmaVtabCursorClear(pCsr);
+ j = (pTab->pName->mPragFlg & PragFlg_Result1)!=0 ? 0 : 1;
+ for(i=0; i<argc; i++, j++){
+ const char *zText = (const char*)tdsqlite3_value_text(argv[i]);
+ assert( j<ArraySize(pCsr->azArg) );
+ assert( pCsr->azArg[j]==0 );
+ if( zText ){
+ pCsr->azArg[j] = tdsqlite3_mprintf("%s", zText);
+ if( pCsr->azArg[j]==0 ){
+ return SQLITE_NOMEM;
+ }
+ }
+ }
+ tdsqlite3StrAccumInit(&acc, 0, 0, 0, pTab->db->aLimit[SQLITE_LIMIT_SQL_LENGTH]);
+ tdsqlite3_str_appendall(&acc, "PRAGMA ");
+ if( pCsr->azArg[1] ){
+ tdsqlite3_str_appendf(&acc, "%Q.", pCsr->azArg[1]);
+ }
+ tdsqlite3_str_appendall(&acc, pTab->pName->zName);
+ if( pCsr->azArg[0] ){
+ tdsqlite3_str_appendf(&acc, "=%Q", pCsr->azArg[0]);
+ }
+ zSql = tdsqlite3StrAccumFinish(&acc);
+ if( zSql==0 ) return SQLITE_NOMEM;
+ rc = tdsqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pPragma, 0);
+ tdsqlite3_free(zSql);
+ if( rc!=SQLITE_OK ){
+ pTab->base.zErrMsg = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(pTab->db));
+ return rc;
+ }
+ return pragmaVtabNext(pVtabCursor);
+}
+
+/*
+** Pragma virtual table module xEof method.
+*/
+static int pragmaVtabEof(tdsqlite3_vtab_cursor *pVtabCursor){
+ PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor;
+ return (pCsr->pPragma==0);
+}
+
+/* The xColumn method simply returns the corresponding column from
+** the PRAGMA.
+*/
+static int pragmaVtabColumn(
+ tdsqlite3_vtab_cursor *pVtabCursor,
+ tdsqlite3_context *ctx,
+ int i
+){
+ PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor;
+ PragmaVtab *pTab = (PragmaVtab*)(pVtabCursor->pVtab);
+ if( i<pTab->iHidden ){
+ tdsqlite3_result_value(ctx, tdsqlite3_column_value(pCsr->pPragma, i));
+ }else{
+ tdsqlite3_result_text(ctx, pCsr->azArg[i-pTab->iHidden],-1,SQLITE_TRANSIENT);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Pragma virtual table module xRowid method.
+*/
+static int pragmaVtabRowid(tdsqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *p){
+ PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor;
+ *p = pCsr->iRowid;
+ return SQLITE_OK;
+}
+
+/* The pragma virtual table object */
+static const tdsqlite3_module pragmaVtabModule = {
+ 0, /* iVersion */
+ 0, /* xCreate - create a table */
+ pragmaVtabConnect, /* xConnect - connect to an existing table */
+ pragmaVtabBestIndex, /* xBestIndex - Determine search strategy */
+ pragmaVtabDisconnect, /* xDisconnect - Disconnect from a table */
+ 0, /* xDestroy - Drop a table */
+ pragmaVtabOpen, /* xOpen - open a cursor */
+ pragmaVtabClose, /* xClose - close a cursor */
+ pragmaVtabFilter, /* xFilter - configure scan constraints */
+ pragmaVtabNext, /* xNext - advance a cursor */
+ pragmaVtabEof, /* xEof */
+ pragmaVtabColumn, /* xColumn - read data */
+ pragmaVtabRowid, /* xRowid - read data */
+ 0, /* xUpdate - write data */
+ 0, /* xBegin - begin transaction */
+ 0, /* xSync - sync transaction */
+ 0, /* xCommit - commit transaction */
+ 0, /* xRollback - rollback transaction */
+ 0, /* xFindFunction - function overloading */
+ 0, /* xRename - rename the table */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
+};
+
+/*
+** Check to see if zTabName is really the name of a pragma. If it is,
+** then register an eponymous virtual table for that pragma and return
+** a pointer to the Module object for the new virtual table.
+*/
+SQLITE_PRIVATE Module *tdsqlite3PragmaVtabRegister(tdsqlite3 *db, const char *zName){
+ const PragmaName *pName;
+ assert( tdsqlite3_strnicmp(zName, "pragma_", 7)==0 );
+ pName = pragmaLocate(zName+7);
+ if( pName==0 ) return 0;
+ if( (pName->mPragFlg & (PragFlg_Result0|PragFlg_Result1))==0 ) return 0;
+ assert( tdsqlite3HashFind(&db->aModule, zName)==0 );
+ return tdsqlite3VtabCreateModule(db, zName, &pragmaVtabModule, (void*)pName, 0);
+}
+
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
#endif /* SQLITE_OMIT_PRAGMA */
/************** End of pragma.c **********************************************/
@@ -116655,7 +130582,7 @@ pragma_out:
** May you share freely, never taking more than you give.
**
*************************************************************************
-** This file contains the implementation of the sqlite3_prepare()
+** This file contains the implementation of the tdsqlite3_prepare()
** interface, and routines that contribute to loading the database schema
** from disk.
*/
@@ -116670,49 +130597,85 @@ static void corruptSchema(
const char *zObj, /* Object being parsed at the point of error */
const char *zExtra /* Error information */
){
- sqlite3 *db = pData->db;
- if( !db->mallocFailed && (db->flags & SQLITE_RecoveryMode)==0 ){
+ tdsqlite3 *db = pData->db;
+ if( db->mallocFailed ){
+ pData->rc = SQLITE_NOMEM_BKPT;
+ }else if( pData->pzErrMsg[0]!=0 ){
+ /* A error message has already been generated. Do not overwrite it */
+ }else if( pData->mInitFlags & INITFLAG_AlterTable ){
+ *pData->pzErrMsg = tdsqlite3DbStrDup(db, zExtra);
+ pData->rc = SQLITE_ERROR;
+ }else if( db->flags & SQLITE_WriteSchema ){
+ pData->rc = SQLITE_CORRUPT_BKPT;
+ }else{
char *z;
if( zObj==0 ) zObj = "?";
- z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj);
- if( zExtra ) z = sqlite3MPrintf(db, "%z - %s", z, zExtra);
- sqlite3DbFree(db, *pData->pzErrMsg);
+ z = tdsqlite3MPrintf(db, "malformed database schema (%s)", zObj);
+ if( zExtra && zExtra[0] ) z = tdsqlite3MPrintf(db, "%z - %s", z, zExtra);
*pData->pzErrMsg = z;
+ pData->rc = SQLITE_CORRUPT_BKPT;
+ }
+}
+
+/*
+** Check to see if any sibling index (another index on the same table)
+** of pIndex has the same root page number, and if it does, return true.
+** This would indicate a corrupt schema.
+*/
+SQLITE_PRIVATE int tdsqlite3IndexHasDuplicateRootPage(Index *pIndex){
+ Index *p;
+ for(p=pIndex->pTable->pIndex; p; p=p->pNext){
+ if( p->tnum==pIndex->tnum && p!=pIndex ) return 1;
}
- pData->rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_CORRUPT_BKPT;
+ return 0;
}
+/* forward declaration */
+static int tdsqlite3Prepare(
+ tdsqlite3 *db, /* Database handle. */
+ const char *zSql, /* UTF-8 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */
+ Vdbe *pReprepare, /* VM being reprepared */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const char **pzTail /* OUT: End of parsed string */
+);
+
+
/*
** This is the callback routine for the code that initializes the
-** database. See sqlite3Init() below for additional information.
+** database. See tdsqlite3Init() below for additional information.
** This routine is also called from the OP_ParseSchema opcode of the VDBE.
**
** Each callback contains the following information:
**
-** argv[0] = name of thing being created
-** argv[1] = root page number for table or index. 0 for trigger or view.
-** argv[2] = SQL text for the CREATE statement.
+** argv[0] = type of object: "table", "index", "trigger", or "view".
+** argv[1] = name of thing being created
+** argv[2] = associated table if an index or trigger
+** argv[3] = root page number for table or index. 0 for trigger or view.
+** argv[4] = SQL text for the CREATE statement.
**
*/
-SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){
+SQLITE_PRIVATE int tdsqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){
InitData *pData = (InitData*)pInit;
- sqlite3 *db = pData->db;
+ tdsqlite3 *db = pData->db;
int iDb = pData->iDb;
- assert( argc==3 );
+ assert( argc==5 );
UNUSED_PARAMETER2(NotUsed, argc);
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
DbClearProperty(db, iDb, DB_Empty);
+ pData->nInitRow++;
if( db->mallocFailed ){
- corruptSchema(pData, argv[0], 0);
+ corruptSchema(pData, argv[1], 0);
return 1;
}
assert( iDb>=0 && iDb<db->nDb );
if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */
- if( argv[1]==0 ){
- corruptSchema(pData, argv[0], 0);
- }else if( sqlite3_strnicmp(argv[2],"create ",7)==0 ){
+ if( argv[3]==0 ){
+ corruptSchema(pData, argv[1], 0);
+ }else if( tdsqlite3_strnicmp(argv[4],"create ",7)==0 ){
/* Call the parser to process a CREATE TABLE, INDEX or VIEW.
** But because db->init.busy is set to 1, no VDBE code is generated
** or executed. All the parser does is build the internal data
@@ -116720,33 +130683,35 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char
*/
int rc;
u8 saved_iDb = db->init.iDb;
- sqlite3_stmt *pStmt;
- TESTONLY(int rcp); /* Return code from sqlite3_prepare() */
+ tdsqlite3_stmt *pStmt;
+ TESTONLY(int rcp); /* Return code from tdsqlite3_prepare() */
assert( db->init.busy );
db->init.iDb = iDb;
- db->init.newTnum = sqlite3Atoi(argv[1]);
+ db->init.newTnum = tdsqlite3Atoi(argv[3]);
db->init.orphanTrigger = 0;
- TESTONLY(rcp = ) sqlite3_prepare(db, argv[2], -1, &pStmt, 0);
+ db->init.azInit = argv;
+ pStmt = 0;
+ TESTONLY(rcp = ) tdsqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0);
rc = db->errCode;
assert( (rc&0xFF)==(rcp&0xFF) );
db->init.iDb = saved_iDb;
- assert( saved_iDb==0 || (db->flags & SQLITE_Vacuum)!=0 );
+ /* assert( saved_iDb==0 || (db->mDbFlags & DBFLAG_Vacuum)!=0 ); */
if( SQLITE_OK!=rc ){
if( db->init.orphanTrigger ){
assert( iDb==1 );
}else{
- pData->rc = rc;
+ if( rc > pData->rc ) pData->rc = rc;
if( rc==SQLITE_NOMEM ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
}else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){
- corruptSchema(pData, argv[0], sqlite3_errmsg(db));
+ corruptSchema(pData, argv[1], tdsqlite3_errmsg(db));
}
}
}
- sqlite3_finalize(pStmt);
- }else if( argv[0]==0 || (argv[2]!=0 && argv[2][0]!=0) ){
- corruptSchema(pData, argv[0], 0);
+ tdsqlite3_finalize(pStmt);
+ }else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){
+ corruptSchema(pData, argv[1], 0);
}else{
/* If the SQL column is blank it means this is an index that
** was created to be the PRIMARY KEY or to fulfill a UNIQUE
@@ -116755,16 +130720,13 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char
** to do here is record the root page number for that index.
*/
Index *pIndex;
- pIndex = sqlite3FindIndex(db, argv[0], db->aDb[iDb].zDbSName);
- if( pIndex==0 ){
- /* This can occur if there exists an index on a TEMP table which
- ** has the same name as another index on a permanent index. Since
- ** the permanent table is hidden by the TEMP table, we can also
- ** safely ignore the index on the permanent table.
- */
- /* Do Nothing */;
- }else if( sqlite3GetInt32(argv[1], &pIndex->tnum)==0 ){
- corruptSchema(pData, argv[0], "invalid rootpage");
+ pIndex = tdsqlite3FindIndex(db, argv[1], db->aDb[iDb].zDbSName);
+ if( pIndex==0
+ || tdsqlite3GetInt32(argv[3],&pIndex->tnum)==0
+ || pIndex->tnum<2
+ || tdsqlite3IndexHasDuplicateRootPage(pIndex)
+ ){
+ corruptSchema(pData, argv[1], pIndex?"invalid rootpage":"orphan index");
}
}
return 0;
@@ -116778,39 +130740,46 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char
** auxiliary databases. Return one of the SQLITE_ error codes to
** indicate success or failure.
*/
-static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
+SQLITE_PRIVATE int tdsqlite3InitOne(tdsqlite3 *db, int iDb, char **pzErrMsg, u32 mFlags){
int rc;
int i;
#ifndef SQLITE_OMIT_DEPRECATED
int size;
#endif
Db *pDb;
- char const *azArg[4];
+ char const *azArg[6];
int meta[5];
InitData initData;
const char *zMasterName;
int openedTransaction = 0;
+ assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 );
assert( iDb>=0 && iDb<db->nDb );
assert( db->aDb[iDb].pSchema );
- assert( sqlite3_mutex_held(db->mutex) );
- assert( iDb==1 || sqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
+ assert( iDb==1 || tdsqlite3BtreeHoldsMutex(db->aDb[iDb].pBt) );
+
+ db->init.busy = 1;
/* Construct the in-memory representation schema tables (sqlite_master or
** sqlite_temp_master) by invoking the parser directly. The appropriate
** table name will be inserted automatically by the parser so we can just
** use the abbreviation "x" here. The parser will also automatically tag
** the schema table as read-only. */
- azArg[0] = zMasterName = SCHEMA_TABLE(iDb);
- azArg[1] = "1";
- azArg[2] = "CREATE TABLE x(type text,name text,tbl_name text,"
- "rootpage integer,sql text)";
- azArg[3] = 0;
+ azArg[0] = "table";
+ azArg[1] = zMasterName = SCHEMA_TABLE(iDb);
+ azArg[2] = azArg[1];
+ azArg[3] = "1";
+ azArg[4] = "CREATE TABLE x(type text,name text,tbl_name text,"
+ "rootpage int,sql text)";
+ azArg[5] = 0;
initData.db = db;
initData.iDb = iDb;
initData.rc = SQLITE_OK;
initData.pzErrMsg = pzErrMsg;
- sqlite3InitCallback(&initData, 3, (char **)azArg, 0);
+ initData.mInitFlags = mFlags;
+ initData.nInitRow = 0;
+ tdsqlite3InitCallback(&initData, 5, (char **)azArg, 0);
if( initData.rc ){
rc = initData.rc;
goto error_out;
@@ -116820,20 +130789,20 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
*/
pDb = &db->aDb[iDb];
if( pDb->pBt==0 ){
- if( !OMIT_TEMPDB && ALWAYS(iDb==1) ){
- DbSetProperty(db, 1, DB_SchemaLoaded);
- }
- return SQLITE_OK;
+ assert( iDb==1 );
+ DbSetProperty(db, 1, DB_SchemaLoaded);
+ rc = SQLITE_OK;
+ goto error_out;
}
/* If there is not already a read-only (or read-write) transaction opened
** on the b-tree database, open one now. If a transaction is opened, it
** will be closed before this function returns. */
- sqlite3BtreeEnter(pDb->pBt);
- if( !sqlite3BtreeIsInReadTrans(pDb->pBt) ){
- rc = sqlite3BtreeBeginTrans(pDb->pBt, 0);
+ tdsqlite3BtreeEnter(pDb->pBt);
+ if( !tdsqlite3BtreeIsInReadTrans(pDb->pBt) ){
+ rc = tdsqlite3BtreeBeginTrans(pDb->pBt, 0, 0);
if( rc!=SQLITE_OK ){
- sqlite3SetString(pzErrMsg, db, sqlite3ErrStr(rc));
+ tdsqlite3SetString(pzErrMsg, db, tdsqlite3ErrStr(rc));
goto initone_error_out;
}
openedTransaction = 1;
@@ -116857,7 +130826,10 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
** the possible values of meta[4].
*/
for(i=0; i<ArraySize(meta); i++){
- sqlite3BtreeGetMeta(pDb->pBt, i+1, (u32 *)&meta[i]);
+ tdsqlite3BtreeGetMeta(pDb->pBt, i+1, (u32 *)&meta[i]);
+ }
+ if( (db->flags & SQLITE_ResetDatabase)!=0 ){
+ memset(meta, 0, sizeof(meta));
}
pDb->pSchema->schema_cookie = meta[BTREE_SCHEMA_VERSION-1];
@@ -116880,7 +130852,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
}else{
/* If opening an attached database, the encoding much match ENC(db) */
if( meta[BTREE_TEXT_ENCODING-1]!=ENC(db) ){
- sqlite3SetString(pzErrMsg, db, "attached databases must use the same"
+ tdsqlite3SetString(pzErrMsg, db, "attached databases must use the same"
" text encoding as main database");
rc = SQLITE_ERROR;
goto initone_error_out;
@@ -116893,13 +130865,13 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
if( pDb->pSchema->cache_size==0 ){
#ifndef SQLITE_OMIT_DEPRECATED
- size = sqlite3AbsInt32(meta[BTREE_DEFAULT_CACHE_SIZE-1]);
+ size = tdsqlite3AbsInt32(meta[BTREE_DEFAULT_CACHE_SIZE-1]);
if( size==0 ){ size = SQLITE_DEFAULT_CACHE_SIZE; }
pDb->pSchema->cache_size = size;
#else
pDb->pSchema->cache_size = SQLITE_DEFAULT_CACHE_SIZE;
#endif
- sqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
+ tdsqlite3BtreeSetCacheSize(pDb->pBt, pDb->pSchema->cache_size);
}
/*
@@ -116913,7 +130885,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
pDb->pSchema->file_format = 1;
}
if( pDb->pSchema->file_format>SQLITE_MAX_FILE_FORMAT ){
- sqlite3SetString(pzErrMsg, db, "unsupported file format");
+ tdsqlite3SetString(pzErrMsg, db, "unsupported file format");
rc = SQLITE_ERROR;
goto initone_error_out;
}
@@ -116924,7 +130896,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
** indices that the user might have created.
*/
if( iDb==0 && meta[BTREE_FILE_FORMAT-1]>=4 ){
- db->flags &= ~SQLITE_LegacyFileFmt;
+ db->flags &= ~(u64)SQLITE_LegacyFileFmt;
}
/* Read the schema information out of the schema tables
@@ -116932,36 +130904,36 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
assert( db->init.busy );
{
char *zSql;
- zSql = sqlite3MPrintf(db,
- "SELECT name, rootpage, sql FROM \"%w\".%s ORDER BY rowid",
+ zSql = tdsqlite3MPrintf(db,
+ "SELECT*FROM\"%w\".%s ORDER BY rowid",
db->aDb[iDb].zDbSName, zMasterName);
#ifndef SQLITE_OMIT_AUTHORIZATION
{
- sqlite3_xauth xAuth;
+ tdsqlite3_xauth xAuth;
xAuth = db->xAuth;
db->xAuth = 0;
#endif
- rc = sqlite3_exec(db, zSql, sqlite3InitCallback, &initData, 0);
+ rc = tdsqlite3_exec(db, zSql, tdsqlite3InitCallback, &initData, 0);
#ifndef SQLITE_OMIT_AUTHORIZATION
db->xAuth = xAuth;
}
#endif
if( rc==SQLITE_OK ) rc = initData.rc;
- sqlite3DbFree(db, zSql);
+ tdsqlite3DbFree(db, zSql);
#ifndef SQLITE_OMIT_ANALYZE
if( rc==SQLITE_OK ){
- sqlite3AnalysisLoad(db, iDb);
+ tdsqlite3AnalysisLoad(db, iDb);
}
#endif
}
if( db->mallocFailed ){
rc = SQLITE_NOMEM_BKPT;
- sqlite3ResetAllSchemasOfConnection(db);
+ tdsqlite3ResetAllSchemasOfConnection(db);
}
- if( rc==SQLITE_OK || (db->flags&SQLITE_RecoveryMode)){
- /* Black magic: If the SQLITE_RecoveryMode flag is set, then consider
+ if( rc==SQLITE_OK || (db->flags&SQLITE_NoSchemaError)){
+ /* Black magic: If the SQLITE_NoSchemaError flag is set, then consider
** the schema loaded, even if errors occurred. In this situation the
- ** current sqlite3_prepare() operation will fail, but the following one
+ ** current tdsqlite3_prepare() operation will fail, but the following one
** will attempt to compile the supplied statement against whatever subset
** of the schema was loaded before the error occurred. The primary
** purpose of this is to allow access to the sqlite_master table
@@ -116972,19 +130944,23 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){
}
/* Jump here for an error that occurs after successfully allocating
- ** curMain and calling sqlite3BtreeEnter(). For an error that occurs
+ ** curMain and calling tdsqlite3BtreeEnter(). For an error that occurs
** before that point, jump to error_out.
*/
initone_error_out:
if( openedTransaction ){
- sqlite3BtreeCommit(pDb->pBt);
+ tdsqlite3BtreeCommit(pDb->pBt);
}
- sqlite3BtreeLeave(pDb->pBt);
+ tdsqlite3BtreeLeave(pDb->pBt);
error_out:
- if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
- sqlite3OomFault(db);
+ if( rc ){
+ if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
+ tdsqlite3OomFault(db);
+ }
+ tdsqlite3ResetOneSchema(db, iDb);
}
+ db->init.busy = 0;
return rc;
}
@@ -116998,60 +130974,50 @@ error_out:
** bit is set in the flags field of the Db structure. If the database
** file was of zero-length, then the DB_Empty flag is also set.
*/
-SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){
+SQLITE_PRIVATE int tdsqlite3Init(tdsqlite3 *db, char **pzErrMsg){
int i, rc;
- int commit_internal = !(db->flags&SQLITE_InternChanges);
+ int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange);
- assert( sqlite3_mutex_held(db->mutex) );
- assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3BtreeHoldsMutex(db->aDb[0].pBt) );
assert( db->init.busy==0 );
- rc = SQLITE_OK;
- db->init.busy = 1;
ENC(db) = SCHEMA_ENC(db);
- for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
- if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue;
- rc = sqlite3InitOne(db, i, pzErrMsg);
- if( rc ){
- sqlite3ResetOneSchema(db, i);
- }
+ assert( db->nDb>0 );
+ /* Do the main schema first */
+ if( !DbHasProperty(db, 0, DB_SchemaLoaded) ){
+ rc = tdsqlite3InitOne(db, 0, pzErrMsg, 0);
+ if( rc ) return rc;
}
-
- /* Once all the other databases have been initialized, load the schema
- ** for the TEMP database. This is loaded last, as the TEMP database
- ** schema may contain references to objects in other databases.
- */
-#ifndef SQLITE_OMIT_TEMPDB
- assert( db->nDb>1 );
- if( rc==SQLITE_OK && !DbHasProperty(db, 1, DB_SchemaLoaded) ){
- rc = sqlite3InitOne(db, 1, pzErrMsg);
- if( rc ){
- sqlite3ResetOneSchema(db, 1);
+ /* All other schemas after the main schema. The "temp" schema must be last */
+ for(i=db->nDb-1; i>0; i--){
+ assert( i==1 || tdsqlite3BtreeHoldsMutex(db->aDb[i].pBt) );
+ if( !DbHasProperty(db, i, DB_SchemaLoaded) ){
+ rc = tdsqlite3InitOne(db, i, pzErrMsg, 0);
+ if( rc ) return rc;
}
}
-#endif
-
- db->init.busy = 0;
- if( rc==SQLITE_OK && commit_internal ){
- sqlite3CommitInternalChanges(db);
+ if( commit_internal ){
+ tdsqlite3CommitInternalChanges(db);
}
-
- return rc;
+ return SQLITE_OK;
}
/*
** This routine is a no-op if the database schema is already initialized.
** Otherwise, the schema is loaded. An error code is returned.
*/
-SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse){
+SQLITE_PRIVATE int tdsqlite3ReadSchema(Parse *pParse){
int rc = SQLITE_OK;
- sqlite3 *db = pParse->db;
- assert( sqlite3_mutex_held(db->mutex) );
+ tdsqlite3 *db = pParse->db;
+ assert( tdsqlite3_mutex_held(db->mutex) );
if( !db->init.busy ){
- rc = sqlite3Init(db, &pParse->zErrMsg);
- }
- if( rc!=SQLITE_OK ){
- pParse->rc = rc;
- pParse->nErr++;
+ rc = tdsqlite3Init(db, &pParse->zErrMsg);
+ if( rc!=SQLITE_OK ){
+ pParse->rc = rc;
+ pParse->nErr++;
+ }else if( db->noSharedCache ){
+ db->mDbFlags |= DBFLAG_SchemaKnownOk;
+ }
}
return rc;
}
@@ -117063,13 +131029,13 @@ SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse){
** make no changes to pParse->rc.
*/
static void schemaIsValid(Parse *pParse){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
int iDb;
int rc;
int cookie;
assert( pParse->checkSchema );
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
for(iDb=0; iDb<db->nDb; iDb++){
int openedTransaction = 0; /* True if a transaction is opened */
Btree *pBt = db->aDb[iDb].pBt; /* Btree database to read cookie from */
@@ -117078,10 +131044,10 @@ static void schemaIsValid(Parse *pParse){
/* If there is not already a read-only (or read-write) transaction opened
** on the b-tree database, open one now. If a transaction is opened, it
** will be closed immediately after reading the meta-value. */
- if( !sqlite3BtreeIsInReadTrans(pBt) ){
- rc = sqlite3BtreeBeginTrans(pBt, 0);
+ if( !tdsqlite3BtreeIsInReadTrans(pBt) ){
+ rc = tdsqlite3BtreeBeginTrans(pBt, 0, 0);
if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
}
if( rc!=SQLITE_OK ) return;
openedTransaction = 1;
@@ -117090,16 +131056,16 @@ static void schemaIsValid(Parse *pParse){
/* Read the schema cookie from the database. If it does not match the
** value stored as part of the in-memory schema representation,
** set Parse.rc to SQLITE_SCHEMA. */
- sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&cookie);
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ tdsqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&cookie);
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
if( cookie!=db->aDb[iDb].pSchema->schema_cookie ){
- sqlite3ResetOneSchema(db, iDb);
+ tdsqlite3ResetOneSchema(db, iDb);
pParse->rc = SQLITE_SCHEMA;
}
/* Close the transaction, if one was opened. */
if( openedTransaction ){
- sqlite3BtreeCommit(pBt);
+ tdsqlite3BtreeCommit(pBt);
}
}
}
@@ -117111,7 +131077,7 @@ static void schemaIsValid(Parse *pParse){
** If the same database is attached more than once, the first
** attached database is returned.
*/
-SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){
+SQLITE_PRIVATE int tdsqlite3SchemaToIndex(tdsqlite3 *db, Schema *pSchema){
int i = -1000000;
/* If pSchema is NULL, then return -1000000. This happens when code in
@@ -117124,9 +131090,10 @@ SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){
** more likely to cause a segfault than -1 (of course there are assert()
** statements too, but it never hurts to play the odds).
*/
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
if( pSchema ){
- for(i=0; ALWAYS(i<db->nDb); i++){
+ for(i=0; 1; i++){
+ assert( i<db->nDb );
if( db->aDb[i].pSchema==pSchema ){
break;
}
@@ -117139,29 +131106,28 @@ SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){
/*
** Free all memory allocations in the pParse object
*/
-SQLITE_PRIVATE void sqlite3ParserReset(Parse *pParse){
- if( pParse ){
- sqlite3 *db = pParse->db;
- sqlite3DbFree(db, pParse->aLabel);
- sqlite3ExprListDelete(db, pParse->pConstExpr);
- if( db ){
- assert( db->lookaside.bDisable >= pParse->disableLookaside );
- db->lookaside.bDisable -= pParse->disableLookaside;
- }
- pParse->disableLookaside = 0;
+SQLITE_PRIVATE void tdsqlite3ParserReset(Parse *pParse){
+ tdsqlite3 *db = pParse->db;
+ tdsqlite3DbFree(db, pParse->aLabel);
+ tdsqlite3ExprListDelete(db, pParse->pConstExpr);
+ if( db ){
+ assert( db->lookaside.bDisable >= pParse->disableLookaside );
+ db->lookaside.bDisable -= pParse->disableLookaside;
+ db->lookaside.sz = db->lookaside.bDisable ? 0 : db->lookaside.szTrue;
}
+ pParse->disableLookaside = 0;
}
/*
** Compile the UTF-8 encoded SQL statement zSql into a statement handle.
*/
-static int sqlite3Prepare(
- sqlite3 *db, /* Database handle. */
+static int tdsqlite3Prepare(
+ tdsqlite3 *db, /* Database handle. */
const char *zSql, /* UTF-8 encoded SQL statement. */
int nBytes, /* Length of zSql in bytes. */
- int saveSqlFlag, /* True to copy SQL text into the sqlite3_stmt */
+ u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */
Vdbe *pReprepare, /* VM being reprepared */
- sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
const char **pzTail /* OUT: End of parsed string */
){
char *zErrMsg = 0; /* Error message */
@@ -117174,7 +131140,16 @@ static int sqlite3Prepare(
sParse.pReprepare = pReprepare;
assert( ppStmt && *ppStmt==0 );
/* assert( !db->mallocFailed ); // not true with SQLITE_USE_ALLOCA */
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
+
+ /* For a long-term use prepared statement avoid the use of
+ ** lookaside memory.
+ */
+ if( prepFlags & SQLITE_PREPARE_PERSISTENT ){
+ sParse.disableLookaside++;
+ DisableLookaside;
+ }
+ sParse.disableVtab = (prepFlags & SQLITE_PREPARE_NO_VTAB)!=0;
/* Check to verify that it is possible to get a read lock on all
** database schemas. The inability to get a read lock indicates that
@@ -117189,7 +131164,7 @@ static int sqlite3Prepare(
** the schema change. Disaster would follow.
**
** This thread is currently holding mutexes on all Btrees (because
- ** of the sqlite3BtreeEnterAll() in sqlite3LockAndPrepare()) so it
+ ** of the tdsqlite3BtreeEnterAll() in tdsqlite3LockAndPrepare()) so it
** is not possible for another thread to start a new schema change
** while this routine is running. Hence, we do not need to hold
** locks on the schema, we just need to make sure nobody else is
@@ -117199,21 +131174,23 @@ static int sqlite3Prepare(
** but it does *not* override schema lock detection, so this all still
** works even if READ_UNCOMMITTED is set.
*/
- for(i=0; i<db->nDb; i++) {
- Btree *pBt = db->aDb[i].pBt;
- if( pBt ){
- assert( sqlite3BtreeHoldsMutex(pBt) );
- rc = sqlite3BtreeSchemaLocked(pBt);
- if( rc ){
- const char *zDb = db->aDb[i].zDbSName;
- sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb);
- testcase( db->flags & SQLITE_ReadUncommitted );
- goto end_prepare;
+ if( !db->noSharedCache ){
+ for(i=0; i<db->nDb; i++) {
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ){
+ assert( tdsqlite3BtreeHoldsMutex(pBt) );
+ rc = tdsqlite3BtreeSchemaLocked(pBt);
+ if( rc ){
+ const char *zDb = db->aDb[i].zDbSName;
+ tdsqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb);
+ testcase( db->flags & SQLITE_ReadUncommit );
+ goto end_prepare;
+ }
}
}
}
- sqlite3VtabUnlockList(db);
+ tdsqlite3VtabUnlockList(db);
sParse.db = db;
if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){
@@ -117222,154 +131199,138 @@ static int sqlite3Prepare(
testcase( nBytes==mxLen );
testcase( nBytes==mxLen+1 );
if( nBytes>mxLen ){
- sqlite3ErrorWithMsg(db, SQLITE_TOOBIG, "statement too long");
- rc = sqlite3ApiExit(db, SQLITE_TOOBIG);
+ tdsqlite3ErrorWithMsg(db, SQLITE_TOOBIG, "statement too long");
+ rc = tdsqlite3ApiExit(db, SQLITE_TOOBIG);
goto end_prepare;
}
- zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes);
+ zSqlCopy = tdsqlite3DbStrNDup(db, zSql, nBytes);
if( zSqlCopy ){
- sqlite3RunParser(&sParse, zSqlCopy, &zErrMsg);
+ tdsqlite3RunParser(&sParse, zSqlCopy, &zErrMsg);
sParse.zTail = &zSql[sParse.zTail-zSqlCopy];
- sqlite3DbFree(db, zSqlCopy);
+ tdsqlite3DbFree(db, zSqlCopy);
}else{
sParse.zTail = &zSql[nBytes];
}
}else{
- sqlite3RunParser(&sParse, zSql, &zErrMsg);
+ tdsqlite3RunParser(&sParse, zSql, &zErrMsg);
}
assert( 0==sParse.nQueryLoop );
- if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK;
+ if( sParse.rc==SQLITE_DONE ){
+ sParse.rc = SQLITE_OK;
+ }
if( sParse.checkSchema ){
schemaIsValid(&sParse);
}
- if( db->mallocFailed ){
- sParse.rc = SQLITE_NOMEM_BKPT;
- }
if( pzTail ){
*pzTail = sParse.zTail;
}
- rc = sParse.rc;
-
-#ifndef SQLITE_OMIT_EXPLAIN
- if( rc==SQLITE_OK && sParse.pVdbe && sParse.explain ){
- static const char * const azColName[] = {
- "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment",
- "selectid", "order", "from", "detail"
- };
- int iFirst, mx;
- if( sParse.explain==2 ){
- sqlite3VdbeSetNumCols(sParse.pVdbe, 4);
- iFirst = 8;
- mx = 12;
- }else{
- sqlite3VdbeSetNumCols(sParse.pVdbe, 8);
- iFirst = 0;
- mx = 8;
- }
- for(i=iFirst; i<mx; i++){
- sqlite3VdbeSetColName(sParse.pVdbe, i-iFirst, COLNAME_NAME,
- azColName[i], SQLITE_STATIC);
- }
- }
-#endif
if( db->init.busy==0 ){
- Vdbe *pVdbe = sParse.pVdbe;
- sqlite3VdbeSetSql(pVdbe, zSql, (int)(sParse.zTail-zSql), saveSqlFlag);
+ tdsqlite3VdbeSetSql(sParse.pVdbe, zSql, (int)(sParse.zTail-zSql), prepFlags);
}
- if( sParse.pVdbe && (rc!=SQLITE_OK || db->mallocFailed) ){
- sqlite3VdbeFinalize(sParse.pVdbe);
+ if( db->mallocFailed ){
+ sParse.rc = SQLITE_NOMEM_BKPT;
+ }
+ rc = sParse.rc;
+ if( rc!=SQLITE_OK ){
+ if( sParse.pVdbe ) tdsqlite3VdbeFinalize(sParse.pVdbe);
assert(!(*ppStmt));
}else{
- *ppStmt = (sqlite3_stmt*)sParse.pVdbe;
+ *ppStmt = (tdsqlite3_stmt*)sParse.pVdbe;
}
if( zErrMsg ){
- sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg);
- sqlite3DbFree(db, zErrMsg);
+ tdsqlite3ErrorWithMsg(db, rc, "%s", zErrMsg);
+ tdsqlite3DbFree(db, zErrMsg);
}else{
- sqlite3Error(db, rc);
+ tdsqlite3Error(db, rc);
}
/* Delete any TriggerPrg structures allocated while parsing this statement. */
while( sParse.pTriggerPrg ){
TriggerPrg *pT = sParse.pTriggerPrg;
sParse.pTriggerPrg = pT->pNext;
- sqlite3DbFree(db, pT);
+ tdsqlite3DbFree(db, pT);
}
end_prepare:
- sqlite3ParserReset(&sParse);
- rc = sqlite3ApiExit(db, rc);
- assert( (rc&db->errMask)==rc );
+ tdsqlite3ParserReset(&sParse);
return rc;
}
-static int sqlite3LockAndPrepare(
- sqlite3 *db, /* Database handle. */
+static int tdsqlite3LockAndPrepare(
+ tdsqlite3 *db, /* Database handle. */
const char *zSql, /* UTF-8 encoded SQL statement. */
int nBytes, /* Length of zSql in bytes. */
- int saveSqlFlag, /* True to copy SQL text into the sqlite3_stmt */
+ u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */
Vdbe *pOld, /* VM being reprepared */
- sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
const char **pzTail /* OUT: End of parsed string */
){
int rc;
+ int cnt = 0;
#ifdef SQLITE_ENABLE_API_ARMOR
if( ppStmt==0 ) return SQLITE_MISUSE_BKPT;
#endif
*ppStmt = 0;
- if( !sqlite3SafetyCheckOk(db)||zSql==0 ){
+ if( !tdsqlite3SafetyCheckOk(db)||zSql==0 ){
return SQLITE_MISUSE_BKPT;
}
- sqlite3_mutex_enter(db->mutex);
- sqlite3BtreeEnterAll(db);
- rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, pOld, ppStmt, pzTail);
- if( rc==SQLITE_SCHEMA ){
- sqlite3_finalize(*ppStmt);
- rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, pOld, ppStmt, pzTail);
- }
- sqlite3BtreeLeaveAll(db);
- sqlite3_mutex_leave(db->mutex);
- assert( rc==SQLITE_OK || *ppStmt==0 );
+ tdsqlite3_mutex_enter(db->mutex);
+ tdsqlite3BtreeEnterAll(db);
+ do{
+ /* Make multiple attempts to compile the SQL, until it either succeeds
+ ** or encounters a permanent error. A schema problem after one schema
+ ** reset is considered a permanent error. */
+ rc = tdsqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail);
+ assert( rc==SQLITE_OK || *ppStmt==0 );
+ }while( rc==SQLITE_ERROR_RETRY
+ || (rc==SQLITE_SCHEMA && (tdsqlite3ResetOneSchema(db,-1), cnt++)==0) );
+ tdsqlite3BtreeLeaveAll(db);
+ rc = tdsqlite3ApiExit(db, rc);
+ assert( (rc&db->errMask)==rc );
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
+
/*
** Rerun the compilation of a statement after a schema change.
**
** If the statement is successfully recompiled, return SQLITE_OK. Otherwise,
** if the statement cannot be recompiled because another connection has
-** locked the sqlite3_master table, return SQLITE_LOCKED. If any other error
+** locked the tdsqlite3_master table, return SQLITE_LOCKED. If any other error
** occurs, return SQLITE_SCHEMA.
*/
-SQLITE_PRIVATE int sqlite3Reprepare(Vdbe *p){
+SQLITE_PRIVATE int tdsqlite3Reprepare(Vdbe *p){
int rc;
- sqlite3_stmt *pNew;
+ tdsqlite3_stmt *pNew;
const char *zSql;
- sqlite3 *db;
+ tdsqlite3 *db;
+ u8 prepFlags;
- assert( sqlite3_mutex_held(sqlite3VdbeDb(p)->mutex) );
- zSql = sqlite3_sql((sqlite3_stmt *)p);
+ assert( tdsqlite3_mutex_held(tdsqlite3VdbeDb(p)->mutex) );
+ zSql = tdsqlite3_sql((tdsqlite3_stmt *)p);
assert( zSql!=0 ); /* Reprepare only called for prepare_v2() statements */
- db = sqlite3VdbeDb(p);
- assert( sqlite3_mutex_held(db->mutex) );
- rc = sqlite3LockAndPrepare(db, zSql, -1, 0, p, &pNew, 0);
+ db = tdsqlite3VdbeDb(p);
+ assert( tdsqlite3_mutex_held(db->mutex) );
+ prepFlags = tdsqlite3VdbePrepareFlags(p);
+ rc = tdsqlite3LockAndPrepare(db, zSql, -1, prepFlags, p, &pNew, 0);
if( rc ){
if( rc==SQLITE_NOMEM ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
}
assert( pNew==0 );
return rc;
}else{
assert( pNew!=0 );
}
- sqlite3VdbeSwap((Vdbe*)pNew, p);
- sqlite3TransferBindings(pNew, (sqlite3_stmt*)p);
- sqlite3VdbeResetStepResult((Vdbe*)pNew);
- sqlite3VdbeFinalize((Vdbe*)pNew);
+ tdsqlite3VdbeSwap((Vdbe*)pNew, p);
+ tdsqlite3TransferBindings(pNew, (tdsqlite3_stmt*)p);
+ tdsqlite3VdbeResetStepResult((Vdbe*)pNew);
+ tdsqlite3VdbeFinalize((Vdbe*)pNew);
return SQLITE_OK;
}
@@ -117378,32 +131339,60 @@ SQLITE_PRIVATE int sqlite3Reprepare(Vdbe *p){
** Two versions of the official API. Legacy and new use. In the legacy
** version, the original SQL text is not saved in the prepared statement
** and so if a schema change occurs, SQLITE_SCHEMA is returned by
-** sqlite3_step(). In the new version, the original SQL text is retained
+** tdsqlite3_step(). In the new version, the original SQL text is retained
** and the statement is automatically recompiled if an schema change
** occurs.
*/
-SQLITE_API int sqlite3_prepare(
- sqlite3 *db, /* Database handle. */
+SQLITE_API int tdsqlite3_prepare(
+ tdsqlite3 *db, /* Database handle. */
const char *zSql, /* UTF-8 encoded SQL statement. */
int nBytes, /* Length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
const char **pzTail /* OUT: End of parsed string */
){
int rc;
- rc = sqlite3LockAndPrepare(db,zSql,nBytes,0,0,ppStmt,pzTail);
+ rc = tdsqlite3LockAndPrepare(db,zSql,nBytes,0,0,ppStmt,pzTail);
assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
return rc;
}
-SQLITE_API int sqlite3_prepare_v2(
- sqlite3 *db, /* Database handle. */
+SQLITE_API int tdsqlite3_prepare_v2(
+ tdsqlite3 *db, /* Database handle. */
const char *zSql, /* UTF-8 encoded SQL statement. */
int nBytes, /* Length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
const char **pzTail /* OUT: End of parsed string */
){
int rc;
- rc = sqlite3LockAndPrepare(db,zSql,nBytes,1,0,ppStmt,pzTail);
- assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
+ /* EVIDENCE-OF: R-37923-12173 The tdsqlite3_prepare_v2() interface works
+ ** exactly the same as tdsqlite3_prepare_v3() with a zero prepFlags
+ ** parameter.
+ **
+ ** Proof in that the 5th parameter to tdsqlite3LockAndPrepare is 0 */
+ rc = tdsqlite3LockAndPrepare(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,0,
+ ppStmt,pzTail);
+ assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 );
+ return rc;
+}
+SQLITE_API int tdsqlite3_prepare_v3(
+ tdsqlite3 *db, /* Database handle. */
+ const char *zSql, /* UTF-8 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_* flags */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const char **pzTail /* OUT: End of parsed string */
+){
+ int rc;
+ /* EVIDENCE-OF: R-56861-42673 tdsqlite3_prepare_v3() differs from
+ ** tdsqlite3_prepare_v2() only in having the extra prepFlags parameter,
+ ** which is a bit array consisting of zero or more of the
+ ** SQLITE_PREPARE_* flags.
+ **
+ ** Proof by comparison to the implementation of tdsqlite3_prepare_v2()
+ ** directly above. */
+ rc = tdsqlite3LockAndPrepare(db,zSql,nBytes,
+ SQLITE_PREPARE_SAVESQL|(prepFlags&SQLITE_PREPARE_MASK),
+ 0,ppStmt,pzTail);
+ assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 );
return rc;
}
@@ -117412,16 +131401,16 @@ SQLITE_API int sqlite3_prepare_v2(
/*
** Compile the UTF-16 encoded SQL statement zSql into a statement handle.
*/
-static int sqlite3Prepare16(
- sqlite3 *db, /* Database handle. */
+static int tdsqlite3Prepare16(
+ tdsqlite3 *db, /* Database handle. */
const void *zSql, /* UTF-16 encoded SQL statement. */
int nBytes, /* Length of zSql in bytes. */
- int saveSqlFlag, /* True to save SQL text into the sqlite3_stmt */
- sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
const void **pzTail /* OUT: End of parsed string */
){
/* This function currently works by first transforming the UTF-16
- ** encoded string to UTF-8, then invoking sqlite3_prepare(). The
+ ** encoded string to UTF-8, then invoking tdsqlite3_prepare(). The
** tricky bit is figuring out the pointer to return in *pzTail.
*/
char *zSql8;
@@ -117432,7 +131421,7 @@ static int sqlite3Prepare16(
if( ppStmt==0 ) return SQLITE_MISUSE_BKPT;
#endif
*ppStmt = 0;
- if( !sqlite3SafetyCheckOk(db)||zSql==0 ){
+ if( !tdsqlite3SafetyCheckOk(db)||zSql==0 ){
return SQLITE_MISUSE_BKPT;
}
if( nBytes>=0 ){
@@ -117441,24 +131430,24 @@ static int sqlite3Prepare16(
for(sz=0; sz<nBytes && (z[sz]!=0 || z[sz+1]!=0); sz += 2){}
nBytes = sz;
}
- sqlite3_mutex_enter(db->mutex);
- zSql8 = sqlite3Utf16to8(db, zSql, nBytes, SQLITE_UTF16NATIVE);
+ tdsqlite3_mutex_enter(db->mutex);
+ zSql8 = tdsqlite3Utf16to8(db, zSql, nBytes, SQLITE_UTF16NATIVE);
if( zSql8 ){
- rc = sqlite3LockAndPrepare(db, zSql8, -1, saveSqlFlag, 0, ppStmt, &zTail8);
+ rc = tdsqlite3LockAndPrepare(db, zSql8, -1, prepFlags, 0, ppStmt, &zTail8);
}
if( zTail8 && pzTail ){
- /* If sqlite3_prepare returns a tail pointer, we calculate the
+ /* If tdsqlite3_prepare returns a tail pointer, we calculate the
** equivalent pointer into the UTF-16 string by counting the unicode
** characters between zSql8 and zTail8, and then returning a pointer
** the same number of characters into the UTF-16 string.
*/
- int chars_parsed = sqlite3Utf8CharLen(zSql8, (int)(zTail8-zSql8));
- *pzTail = (u8 *)zSql + sqlite3Utf16ByteLen(zSql, chars_parsed);
+ int chars_parsed = tdsqlite3Utf8CharLen(zSql8, (int)(zTail8-zSql8));
+ *pzTail = (u8 *)zSql + tdsqlite3Utf16ByteLen(zSql, chars_parsed);
}
- sqlite3DbFree(db, zSql8);
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3DbFree(db, zSql8);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -117466,31 +131455,46 @@ static int sqlite3Prepare16(
** Two versions of the official API. Legacy and new use. In the legacy
** version, the original SQL text is not saved in the prepared statement
** and so if a schema change occurs, SQLITE_SCHEMA is returned by
-** sqlite3_step(). In the new version, the original SQL text is retained
+** tdsqlite3_step(). In the new version, the original SQL text is retained
** and the statement is automatically recompiled if an schema change
** occurs.
*/
-SQLITE_API int sqlite3_prepare16(
- sqlite3 *db, /* Database handle. */
+SQLITE_API int tdsqlite3_prepare16(
+ tdsqlite3 *db, /* Database handle. */
const void *zSql, /* UTF-16 encoded SQL statement. */
int nBytes, /* Length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
const void **pzTail /* OUT: End of parsed string */
){
int rc;
- rc = sqlite3Prepare16(db,zSql,nBytes,0,ppStmt,pzTail);
+ rc = tdsqlite3Prepare16(db,zSql,nBytes,0,ppStmt,pzTail);
assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
return rc;
}
-SQLITE_API int sqlite3_prepare16_v2(
- sqlite3 *db, /* Database handle. */
+SQLITE_API int tdsqlite3_prepare16_v2(
+ tdsqlite3 *db, /* Database handle. */
const void *zSql, /* UTF-16 encoded SQL statement. */
int nBytes, /* Length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
const void **pzTail /* OUT: End of parsed string */
){
int rc;
- rc = sqlite3Prepare16(db,zSql,nBytes,1,ppStmt,pzTail);
+ rc = tdsqlite3Prepare16(db,zSql,nBytes,SQLITE_PREPARE_SAVESQL,ppStmt,pzTail);
+ assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
+ return rc;
+}
+SQLITE_API int tdsqlite3_prepare16_v3(
+ tdsqlite3 *db, /* Database handle. */
+ const void *zSql, /* UTF-16 encoded SQL statement. */
+ int nBytes, /* Length of zSql in bytes. */
+ unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_* flags */
+ tdsqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */
+ const void **pzTail /* OUT: End of parsed string */
+){
+ int rc;
+ rc = tdsqlite3Prepare16(db,zSql,nBytes,
+ SQLITE_PREPARE_SAVESQL|(prepFlags&SQLITE_PREPARE_MASK),
+ ppStmt,pzTail);
assert( rc==SQLITE_OK || ppStmt==0 || *ppStmt==0 ); /* VERIFY: F13021 */
return rc;
}
@@ -117519,12 +131523,11 @@ SQLITE_API int sqlite3_prepare16_v2(
** Trace output macros
*/
#if SELECTTRACE_ENABLED
-/***/ int sqlite3SelectTrace = 0;
+/***/ int tdsqlite3SelectTrace = 0;
# define SELECTTRACE(K,P,S,X) \
- if(sqlite3SelectTrace&(K)) \
- sqlite3DebugPrintf("%*s%s.%p: ",(P)->nSelectIndent*2-2,"",\
- (S)->zSelName,(S)),\
- sqlite3DebugPrintf X
+ if(tdsqlite3SelectTrace&(K)) \
+ tdsqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\
+ tdsqlite3DebugPrintf X
#else
# define SELECTTRACE(K,P,S,X)
#endif
@@ -117546,6 +131549,20 @@ struct DistinctCtx {
/*
** An instance of the following object is used to record information about
** the ORDER BY (or GROUP BY) clause of query is being coded.
+**
+** The aDefer[] array is used by the sorter-references optimization. For
+** example, assuming there is no index that can be used for the ORDER BY,
+** for the query:
+**
+** SELECT a, bigblob FROM t1 ORDER BY a LIMIT 10;
+**
+** it may be more efficient to add just the "a" values to the sorter, and
+** retrieve the associated "bigblob" values directly from table t1 as the
+** 10 smallest "a" values are extracted from the sorter.
+**
+** When the sorter-reference optimization is used, there is one entry in the
+** aDefer[] array for each database table that may be read as values are
+** extracted from the sorter.
*/
typedef struct SortCtx SortCtx;
struct SortCtx {
@@ -117556,28 +131573,45 @@ struct SortCtx {
int labelBkOut; /* Start label for the block-output subroutine */
int addrSortIndex; /* Address of the OP_SorterOpen or OP_OpenEphemeral */
int labelDone; /* Jump here when done, ex: LIMIT reached */
+ int labelOBLopt; /* Jump here when sorter is full */
u8 sortFlags; /* Zero or more SORTFLAG_* bits */
- u8 bOrderedInnerLoop; /* ORDER BY correctly sorts the inner loop */
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ u8 nDefer; /* Number of valid entries in aDefer[] */
+ struct DeferredCsr {
+ Table *pTab; /* Table definition */
+ int iCsr; /* Cursor number for table */
+ int nKey; /* Number of PK columns for table pTab (>=1) */
+ } aDefer[4];
+#endif
+ struct RowLoadInfo *pDeferredRowLoad; /* Deferred row loading info or NULL */
};
#define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */
/*
** Delete all the content of a Select structure. Deallocate the structure
-** itself only if bFree is true.
+** itself depending on the value of bFree
+**
+** If bFree==1, call tdsqlite3DbFree() on the p object.
+** If bFree==0, Leave the first Select object unfreed
*/
-static void clearSelect(sqlite3 *db, Select *p, int bFree){
+static void clearSelect(tdsqlite3 *db, Select *p, int bFree){
while( p ){
Select *pPrior = p->pPrior;
- sqlite3ExprListDelete(db, p->pEList);
- sqlite3SrcListDelete(db, p->pSrc);
- sqlite3ExprDelete(db, p->pWhere);
- sqlite3ExprListDelete(db, p->pGroupBy);
- sqlite3ExprDelete(db, p->pHaving);
- sqlite3ExprListDelete(db, p->pOrderBy);
- sqlite3ExprDelete(db, p->pLimit);
- sqlite3ExprDelete(db, p->pOffset);
- if( p->pWith ) sqlite3WithDelete(db, p->pWith);
- if( bFree ) sqlite3DbFree(db, p);
+ tdsqlite3ExprListDelete(db, p->pEList);
+ tdsqlite3SrcListDelete(db, p->pSrc);
+ tdsqlite3ExprDelete(db, p->pWhere);
+ tdsqlite3ExprListDelete(db, p->pGroupBy);
+ tdsqlite3ExprDelete(db, p->pHaving);
+ tdsqlite3ExprListDelete(db, p->pOrderBy);
+ tdsqlite3ExprDelete(db, p->pLimit);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){
+ tdsqlite3WindowListDelete(db, p->pWinDefn);
+ }
+ assert( p->pWin==0 );
+#endif
+ if( OK_IF_ALWAYS_TRUE(p->pWith) ) tdsqlite3WithDelete(db, p->pWith);
+ if( bFree ) tdsqlite3DbFreeNN(db, p);
p = pPrior;
bFree = 1;
}
@@ -117586,7 +131620,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){
/*
** Initialize a SelectDest structure.
*/
-SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest *pDest, int eDest, int iParm){
+SQLITE_PRIVATE void tdsqlite3SelectDestInit(SelectDest *pDest, int eDest, int iParm){
pDest->eDest = (u8)eDest;
pDest->iSDParm = iParm;
pDest->zAffSdst = 0;
@@ -117599,7 +131633,7 @@ SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest *pDest, int eDest, int iPar
** Allocate a new Select structure and return a pointer to that
** structure.
*/
-SQLITE_PRIVATE Select *sqlite3SelectNew(
+SQLITE_PRIVATE Select *tdsqlite3SelectNew(
Parse *pParse, /* Parsing context */
ExprList *pEList, /* which columns to include in the result */
SrcList *pSrc, /* the FROM clause -- which tables to scan */
@@ -117608,32 +131642,29 @@ SQLITE_PRIVATE Select *sqlite3SelectNew(
Expr *pHaving, /* the HAVING clause */
ExprList *pOrderBy, /* the ORDER BY clause */
u32 selFlags, /* Flag parameters, such as SF_Distinct */
- Expr *pLimit, /* LIMIT value. NULL means not used */
- Expr *pOffset /* OFFSET value. NULL means no offset */
+ Expr *pLimit /* LIMIT value. NULL means not used */
){
Select *pNew;
Select standin;
- sqlite3 *db = pParse->db;
- pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) );
+ pNew = tdsqlite3DbMallocRawNN(pParse->db, sizeof(*pNew) );
if( pNew==0 ){
- assert( db->mallocFailed );
+ assert( pParse->db->mallocFailed );
pNew = &standin;
}
if( pEList==0 ){
- pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db,TK_ASTERISK,0));
+ pEList = tdsqlite3ExprListAppend(pParse, 0,
+ tdsqlite3Expr(pParse->db,TK_ASTERISK,0));
}
pNew->pEList = pEList;
pNew->op = TK_SELECT;
pNew->selFlags = selFlags;
pNew->iLimit = 0;
pNew->iOffset = 0;
-#if SELECTTRACE_ENABLED
- pNew->zSelName[0] = 0;
-#endif
+ pNew->selId = ++pParse->nSelect;
pNew->addrOpenEphm[0] = -1;
pNew->addrOpenEphm[1] = -1;
pNew->nSelectRow = 0;
- if( pSrc==0 ) pSrc = sqlite3DbMallocZero(db, sizeof(*pSrc));
+ if( pSrc==0 ) pSrc = tdsqlite3DbMallocZero(pParse->db, sizeof(*pSrc));
pNew->pSrc = pSrc;
pNew->pWhere = pWhere;
pNew->pGroupBy = pGroupBy;
@@ -117642,11 +131673,13 @@ SQLITE_PRIVATE Select *sqlite3SelectNew(
pNew->pPrior = 0;
pNew->pNext = 0;
pNew->pLimit = pLimit;
- pNew->pOffset = pOffset;
pNew->pWith = 0;
- assert( pOffset==0 || pLimit!=0 || pParse->nErr>0 || db->mallocFailed!=0 );
- if( db->mallocFailed ) {
- clearSelect(db, pNew, pNew!=&standin);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ pNew->pWin = 0;
+ pNew->pWinDefn = 0;
+#endif
+ if( pParse->db->mallocFailed ) {
+ clearSelect(pParse->db, pNew, pNew!=&standin);
pNew = 0;
}else{
assert( pNew->pSrc!=0 || pParse->nErr>0 );
@@ -117655,23 +131688,27 @@ SQLITE_PRIVATE Select *sqlite3SelectNew(
return pNew;
}
-#if SELECTTRACE_ENABLED
+
/*
-** Set the name of a Select object
+** Delete the given Select structure and all of its substructures.
*/
-SQLITE_PRIVATE void sqlite3SelectSetName(Select *p, const char *zName){
- if( p && zName ){
- sqlite3_snprintf(sizeof(p->zSelName), p->zSelName, "%s", zName);
- }
+SQLITE_PRIVATE void tdsqlite3SelectDelete(tdsqlite3 *db, Select *p){
+ if( OK_IF_ALWAYS_TRUE(p) ) clearSelect(db, p, 1);
}
-#endif
-
/*
-** Delete the given Select structure and all of its substructures.
+** Delete all the substructure for p, but keep p allocated. Redefine
+** p to be a single SELECT where every column of the result set has a
+** value of NULL.
*/
-SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3 *db, Select *p){
- if( p ) clearSelect(db, p, 1);
+SQLITE_PRIVATE void tdsqlite3SelectReset(Parse *pParse, Select *p){
+ if( ALWAYS(p) ){
+ clearSelect(pParse->db, p, 0);
+ memset(&p->iLimit, 0, sizeof(Select) - offsetof(Select,iLimit));
+ p->pEList = tdsqlite3ExprListAppend(pParse, 0,
+ tdsqlite3ExprAlloc(pParse->db,TK_NULL,0,0));
+ p->pSrc = tdsqlite3DbMallocZero(pParse->db, sizeof(SrcList));
+ }
}
/*
@@ -117699,7 +131736,7 @@ static Select *findRightmost(Select *p){
** If an illegal or unsupported join type is seen, then still return
** a join type, but put an error in the pParse structure.
*/
-SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){
+SQLITE_PRIVATE int tdsqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){
int jointype = 0;
Token *apAll[3];
Token *p;
@@ -117726,7 +131763,7 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p
p = apAll[i];
for(j=0; j<ArraySize(aKeyword); j++){
if( p->n==aKeyword[j].nChar
- && sqlite3StrNICmp((char*)p->z, &zKeyText[aKeyword[j].i], p->n)==0 ){
+ && tdsqlite3StrNICmp((char*)p->z, &zKeyText[aKeyword[j].i], p->n)==0 ){
jointype |= aKeyword[j].code;
break;
}
@@ -117744,12 +131781,12 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p
const char *zSp = " ";
assert( pB!=0 );
if( pC==0 ){ zSp++; }
- sqlite3ErrorMsg(pParse, "unknown or unsupported join type: "
+ tdsqlite3ErrorMsg(pParse, "unknown or unsupported join type: "
"%T %T%s%T", pA, pB, zSp, pC);
jointype = JT_INNER;
}else if( (jointype & JT_OUTER)!=0
&& (jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ){
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"RIGHT and FULL OUTER JOINs are not currently supported");
jointype = JT_INNER;
}
@@ -117763,7 +131800,7 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p
static int columnIndex(Table *pTab, const char *zCol){
int i;
for(i=0; i<pTab->nCol; i++){
- if( sqlite3StrICmp(pTab->aCol[i].zName, zCol)==0 ) return i;
+ if( tdsqlite3StrICmp(pTab->aCol[i].zName, zCol)==0 ) return i;
}
return -1;
}
@@ -117782,7 +131819,8 @@ static int tableAndColumnIndex(
int N, /* Number of tables in pSrc->a[] to search */
const char *zCol, /* Name of the column we are looking for */
int *piTab, /* Write index of pSrc->a[] here */
- int *piCol /* Write index of pSrc->a[*piTab].pTab->aCol[] here */
+ int *piCol, /* Write index of pSrc->a[*piTab].pTab->aCol[] here */
+ int bIgnoreHidden /* True to ignore hidden columns */
){
int i; /* For looping over tables in pSrc */
int iCol; /* Index of column matching zCol */
@@ -117790,7 +131828,9 @@ static int tableAndColumnIndex(
assert( (piTab==0)==(piCol==0) ); /* Both or neither are NULL */
for(i=0; i<N; i++){
iCol = columnIndex(pSrc->a[i].pTab, zCol);
- if( iCol>=0 ){
+ if( iCol>=0
+ && (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pTab->aCol[iCol])==0)
+ ){
if( piTab ){
*piTab = i;
*piCol = iCol;
@@ -117822,7 +131862,7 @@ static void addWhereTerm(
int isOuterJoin, /* True if this is an OUTER join */
Expr **ppWhere /* IN/OUT: The WHERE clause to add to */
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Expr *pE1;
Expr *pE2;
Expr *pEq;
@@ -117832,17 +131872,17 @@ static void addWhereTerm(
assert( pSrc->a[iLeft].pTab );
assert( pSrc->a[iRight].pTab );
- pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft);
- pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight);
+ pE1 = tdsqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft);
+ pE2 = tdsqlite3CreateColumnExpr(db, pSrc, iRight, iColRight);
- pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2, 0);
+ pEq = tdsqlite3PExpr(pParse, TK_EQ, pE1, pE2);
if( pEq && isOuterJoin ){
ExprSetProperty(pEq, EP_FromJoin);
assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) );
ExprSetVVAProperty(pEq, EP_NoReduce);
pEq->iRightJoinTable = (i16)pE2->iTable;
}
- *ppWhere = sqlite3ExprAnd(db, *ppWhere, pEq);
+ *ppWhere = tdsqlite3ExprAnd(pParse, *ppWhere, pEq);
}
/*
@@ -117871,7 +131911,7 @@ static void addWhereTerm(
** after the t1 loop and rows with t1.x!=5 will never appear in
** the output, which is incorrect.
*/
-static void setJoinExpr(Expr *p, int iTable){
+SQLITE_PRIVATE void tdsqlite3SetJoinExpr(Expr *p, int iTable){
while( p ){
ExprSetProperty(p, EP_FromJoin);
assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) );
@@ -117880,10 +131920,33 @@ static void setJoinExpr(Expr *p, int iTable){
if( p->op==TK_FUNCTION && p->x.pList ){
int i;
for(i=0; i<p->x.pList->nExpr; i++){
- setJoinExpr(p->x.pList->a[i].pExpr, iTable);
+ tdsqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable);
}
}
- setJoinExpr(p->pLeft, iTable);
+ tdsqlite3SetJoinExpr(p->pLeft, iTable);
+ p = p->pRight;
+ }
+}
+
+/* Undo the work of tdsqlite3SetJoinExpr(). In the expression p, convert every
+** term that is marked with EP_FromJoin and iRightJoinTable==iTable into
+** an ordinary term that omits the EP_FromJoin mark.
+**
+** This happens when a LEFT JOIN is simplified into an ordinary JOIN.
+*/
+static void unsetJoinExpr(Expr *p, int iTable){
+ while( p ){
+ if( ExprHasProperty(p, EP_FromJoin)
+ && (iTable<0 || p->iRightJoinTable==iTable) ){
+ ExprClearProperty(p, EP_FromJoin);
+ }
+ if( p->op==TK_FUNCTION && p->x.pList ){
+ int i;
+ for(i=0; i<p->x.pList->nExpr; i++){
+ unsetJoinExpr(p->x.pList->a[i].pExpr, iTable);
+ }
+ }
+ unsetJoinExpr(p->pLeft, iTable);
p = p->pRight;
}
}
@@ -117912,11 +131975,10 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
pLeft = &pSrc->a[0];
pRight = &pLeft[1];
for(i=0; i<pSrc->nSrc-1; i++, pRight++, pLeft++){
- Table *pLeftTab = pLeft->pTab;
Table *pRightTab = pRight->pTab;
int isOuter;
- if( NEVER(pLeftTab==0 || pRightTab==0) ) continue;
+ if( NEVER(pLeft->pTab==0 || pRightTab==0) ) continue;
isOuter = (pRight->fg.jointype & JT_OUTER)!=0;
/* When the NATURAL keyword is present, add WHERE clause terms for
@@ -117924,7 +131986,7 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
*/
if( pRight->fg.jointype & JT_NATURAL ){
if( pRight->pOn || pRight->pUsing ){
- sqlite3ErrorMsg(pParse, "a NATURAL join may not have "
+ tdsqlite3ErrorMsg(pParse, "a NATURAL join may not have "
"an ON or USING clause", 0);
return 1;
}
@@ -117933,10 +131995,11 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
int iLeft; /* Matching left table */
int iLeftCol; /* Matching column in the left table */
+ if( IsHiddenColumn(&pRightTab->aCol[j]) ) continue;
zName = pRightTab->aCol[j].zName;
- if( tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol) ){
+ if( tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 1) ){
addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, j,
- isOuter, &p->pWhere);
+ isOuter, &p->pWhere);
}
}
}
@@ -117944,7 +132007,7 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
/* Disallow both ON and USING clauses in the same join
*/
if( pRight->pOn && pRight->pUsing ){
- sqlite3ErrorMsg(pParse, "cannot have both ON and USING "
+ tdsqlite3ErrorMsg(pParse, "cannot have both ON and USING "
"clauses in the same join");
return 1;
}
@@ -117953,8 +132016,8 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
** an AND operator.
*/
if( pRight->pOn ){
- if( isOuter ) setJoinExpr(pRight->pOn, pRight->iCursor);
- p->pWhere = sqlite3ExprAnd(pParse->db, p->pWhere, pRight->pOn);
+ if( isOuter ) tdsqlite3SetJoinExpr(pRight->pOn, pRight->iCursor);
+ p->pWhere = tdsqlite3ExprAnd(pParse, p->pWhere, pRight->pOn);
pRight->pOn = 0;
}
@@ -117976,9 +132039,9 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
zName = pList->a[j].zName;
iRightCol = columnIndex(pRightTab, zName);
if( iRightCol<0
- || !tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol)
+ || !tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol, 0)
){
- sqlite3ErrorMsg(pParse, "cannot join using column %s - column "
+ tdsqlite3ErrorMsg(pParse, "cannot join using column %s - column "
"not present in both tables", zName);
return 1;
}
@@ -117990,13 +132053,61 @@ static int sqliteProcessJoin(Parse *pParse, Select *p){
return 0;
}
-/* Forward reference */
-static KeyInfo *keyInfoFromExprList(
- Parse *pParse, /* Parsing context */
- ExprList *pList, /* Form the KeyInfo object from this ExprList */
- int iStart, /* Begin with this column of pList */
- int nExtra /* Add this many extra columns to the end */
-);
+/*
+** An instance of this object holds information (beyond pParse and pSelect)
+** needed to load the next result row that is to be added to the sorter.
+*/
+typedef struct RowLoadInfo RowLoadInfo;
+struct RowLoadInfo {
+ int regResult; /* Store results in array of registers here */
+ u8 ecelFlags; /* Flag argument to ExprCodeExprList() */
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ ExprList *pExtra; /* Extra columns needed by sorter refs */
+ int regExtraResult; /* Where to load the extra columns */
+#endif
+};
+
+/*
+** This routine does the work of loading query data into an array of
+** registers so that it can be added to the sorter.
+*/
+static void innerLoopLoadRow(
+ Parse *pParse, /* Statement under construction */
+ Select *pSelect, /* The query being coded */
+ RowLoadInfo *pInfo /* Info needed to complete the row load */
+){
+ tdsqlite3ExprCodeExprList(pParse, pSelect->pEList, pInfo->regResult,
+ 0, pInfo->ecelFlags);
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( pInfo->pExtra ){
+ tdsqlite3ExprCodeExprList(pParse, pInfo->pExtra, pInfo->regExtraResult, 0, 0);
+ tdsqlite3ExprListDelete(pParse->db, pInfo->pExtra);
+ }
+#endif
+}
+
+/*
+** Code the OP_MakeRecord instruction that generates the entry to be
+** added into the sorter.
+**
+** Return the register in which the result is stored.
+*/
+static int makeSorterRecord(
+ Parse *pParse,
+ SortCtx *pSort,
+ Select *pSelect,
+ int regBase,
+ int nBase
+){
+ int nOBSat = pSort->nOBSat;
+ Vdbe *v = pParse->pVdbe;
+ int regOut = ++pParse->nMem;
+ if( pSort->pDeferredRowLoad ){
+ innerLoopLoadRow(pParse, pSelect, pSort->pDeferredRowLoad);
+ }
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regBase+nOBSat, nBase-nOBSat, regOut);
+ return regOut;
+}
/*
** Generate code that will push the record in registers regData
@@ -118008,7 +132119,7 @@ static void pushOntoSorter(
Select *pSelect, /* The whole SELECT statement */
int regData, /* First register holding data to be sorted */
int regOrigData, /* First register holding data before packing */
- int nData, /* Number of elements in the data array */
+ int nData, /* Number of elements in the regData data array */
int nPrefixReg /* No. of reg prior to regData available for use */
){
Vdbe *v = pParse->pVdbe; /* Stmt under construction */
@@ -118016,32 +132127,47 @@ static void pushOntoSorter(
int nExpr = pSort->pOrderBy->nExpr; /* No. of ORDER BY terms */
int nBase = nExpr + bSeq + nData; /* Fields in sorter record */
int regBase; /* Regs for sorter record */
- int regRecord = ++pParse->nMem; /* Assembled sorter record */
+ int regRecord = 0; /* Assembled sorter record */
int nOBSat = pSort->nOBSat; /* ORDER BY terms to skip */
int op; /* Opcode to add sorter record to sorter */
int iLimit; /* LIMIT counter */
+ int iSkip = 0; /* End of the sorter insert loop */
assert( bSeq==0 || bSeq==1 );
- assert( nData==1 || regData==regOrigData );
+
+ /* Three cases:
+ ** (1) The data to be sorted has already been packed into a Record
+ ** by a prior OP_MakeRecord. In this case nData==1 and regData
+ ** will be completely unrelated to regOrigData.
+ ** (2) All output columns are included in the sort record. In that
+ ** case regData==regOrigData.
+ ** (3) Some output columns are omitted from the sort record due to
+ ** the SQLITE_ENABLE_SORTER_REFERENCE optimization, or due to the
+ ** SQLITE_ECEL_OMITREF optimization, or due to the
+ ** SortCtx.pDeferredRowLoad optimiation. In any of these cases
+ ** regOrigData is 0 to prevent this routine from trying to copy
+ ** values that might not yet exist.
+ */
+ assert( nData==1 || regData==regOrigData || regOrigData==0 );
+
if( nPrefixReg ){
assert( nPrefixReg==nExpr+bSeq );
- regBase = regData - nExpr - bSeq;
+ regBase = regData - nPrefixReg;
}else{
regBase = pParse->nMem + 1;
pParse->nMem += nBase;
}
assert( pSelect->iOffset==0 || pSelect->iLimit!=0 );
iLimit = pSelect->iOffset ? pSelect->iOffset+1 : pSelect->iLimit;
- pSort->labelDone = sqlite3VdbeMakeLabel(v);
- sqlite3ExprCodeExprList(pParse, pSort->pOrderBy, regBase, regOrigData,
- SQLITE_ECEL_DUP|SQLITE_ECEL_REF);
+ pSort->labelDone = tdsqlite3VdbeMakeLabel(pParse);
+ tdsqlite3ExprCodeExprList(pParse, pSort->pOrderBy, regBase, regOrigData,
+ SQLITE_ECEL_DUP | (regOrigData? SQLITE_ECEL_REF : 0));
if( bSeq ){
- sqlite3VdbeAddOp2(v, OP_Sequence, pSort->iECursor, regBase+nExpr);
+ tdsqlite3VdbeAddOp2(v, OP_Sequence, pSort->iECursor, regBase+nExpr);
}
- if( nPrefixReg==0 ){
- sqlite3ExprCodeMove(pParse, regData, regBase+nExpr+bSeq, nData);
+ if( nPrefixReg==0 && nData>0 ){
+ tdsqlite3ExprCodeMove(pParse, regData, regBase+nExpr+bSeq, nData);
}
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase+nOBSat, nBase-nOBSat, regRecord);
if( nOBSat>0 ){
int regPrevKey; /* The first nOBSat columns of the previous row */
int addrFirst; /* Address of the OP_IfNot opcode */
@@ -118050,72 +132176,79 @@ static void pushOntoSorter(
int nKey; /* Number of sorting key columns, including OP_Sequence */
KeyInfo *pKI; /* Original KeyInfo on the sorter table */
+ regRecord = makeSorterRecord(pParse, pSort, pSelect, regBase, nBase);
regPrevKey = pParse->nMem+1;
pParse->nMem += pSort->nOBSat;
nKey = nExpr - pSort->nOBSat + bSeq;
if( bSeq ){
- addrFirst = sqlite3VdbeAddOp1(v, OP_IfNot, regBase+nExpr);
+ addrFirst = tdsqlite3VdbeAddOp1(v, OP_IfNot, regBase+nExpr);
}else{
- addrFirst = sqlite3VdbeAddOp1(v, OP_SequenceTest, pSort->iECursor);
+ addrFirst = tdsqlite3VdbeAddOp1(v, OP_SequenceTest, pSort->iECursor);
}
VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_Compare, regPrevKey, regBase, pSort->nOBSat);
- pOp = sqlite3VdbeGetOp(v, pSort->addrSortIndex);
+ tdsqlite3VdbeAddOp3(v, OP_Compare, regPrevKey, regBase, pSort->nOBSat);
+ pOp = tdsqlite3VdbeGetOp(v, pSort->addrSortIndex);
if( pParse->db->mallocFailed ) return;
pOp->p2 = nKey + nData;
pKI = pOp->p4.pKeyInfo;
- memset(pKI->aSortOrder, 0, pKI->nField); /* Makes OP_Jump below testable */
- sqlite3VdbeChangeP4(v, -1, (char*)pKI, P4_KEYINFO);
- testcase( pKI->nXField>2 );
- pOp->p4.pKeyInfo = keyInfoFromExprList(pParse, pSort->pOrderBy, nOBSat,
- pKI->nXField-1);
- addrJmp = sqlite3VdbeCurrentAddr(v);
- sqlite3VdbeAddOp3(v, OP_Jump, addrJmp+1, 0, addrJmp+1); VdbeCoverage(v);
- pSort->labelBkOut = sqlite3VdbeMakeLabel(v);
+ memset(pKI->aSortFlags, 0, pKI->nKeyField); /* Makes OP_Jump testable */
+ tdsqlite3VdbeChangeP4(v, -1, (char*)pKI, P4_KEYINFO);
+ testcase( pKI->nAllField > pKI->nKeyField+2 );
+ pOp->p4.pKeyInfo = tdsqlite3KeyInfoFromExprList(pParse,pSort->pOrderBy,nOBSat,
+ pKI->nAllField-pKI->nKeyField-1);
+ pOp = 0; /* Ensure pOp not used after sqltie3VdbeAddOp3() */
+ addrJmp = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp3(v, OP_Jump, addrJmp+1, 0, addrJmp+1); VdbeCoverage(v);
+ pSort->labelBkOut = tdsqlite3VdbeMakeLabel(pParse);
pSort->regReturn = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut);
- sqlite3VdbeAddOp1(v, OP_ResetSorter, pSort->iECursor);
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut);
+ tdsqlite3VdbeAddOp1(v, OP_ResetSorter, pSort->iECursor);
if( iLimit ){
- sqlite3VdbeAddOp2(v, OP_IfNot, iLimit, pSort->labelDone);
+ tdsqlite3VdbeAddOp2(v, OP_IfNot, iLimit, pSort->labelDone);
VdbeCoverage(v);
}
- sqlite3VdbeJumpHere(v, addrFirst);
- sqlite3ExprCodeMove(pParse, regBase, regPrevKey, pSort->nOBSat);
- sqlite3VdbeJumpHere(v, addrJmp);
+ tdsqlite3VdbeJumpHere(v, addrFirst);
+ tdsqlite3ExprCodeMove(pParse, regBase, regPrevKey, pSort->nOBSat);
+ tdsqlite3VdbeJumpHere(v, addrJmp);
+ }
+ if( iLimit ){
+ /* At this point the values for the new sorter entry are stored
+ ** in an array of registers. They need to be composed into a record
+ ** and inserted into the sorter if either (a) there are currently
+ ** less than LIMIT+OFFSET items or (b) the new record is smaller than
+ ** the largest record currently in the sorter. If (b) is true and there
+ ** are already LIMIT+OFFSET items in the sorter, delete the largest
+ ** entry before inserting the new one. This way there are never more
+ ** than LIMIT+OFFSET items in the sorter.
+ **
+ ** If the new record does not need to be inserted into the sorter,
+ ** jump to the next iteration of the loop. If the pSort->labelOBLopt
+ ** value is not zero, then it is a label of where to jump. Otherwise,
+ ** just bypass the row insert logic. See the header comment on the
+ ** tdsqlite3WhereOrderByLimitOptLabel() function for additional info.
+ */
+ int iCsr = pSort->iECursor;
+ tdsqlite3VdbeAddOp2(v, OP_IfNotZero, iLimit, tdsqlite3VdbeCurrentAddr(v)+4);
+ VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_Last, iCsr, 0);
+ iSkip = tdsqlite3VdbeAddOp4Int(v, OP_IdxLE,
+ iCsr, 0, regBase+nOBSat, nExpr-nOBSat);
+ VdbeCoverage(v);
+ tdsqlite3VdbeAddOp1(v, OP_Delete, iCsr);
+ }
+ if( regRecord==0 ){
+ regRecord = makeSorterRecord(pParse, pSort, pSelect, regBase, nBase);
}
if( pSort->sortFlags & SORTFLAG_UseSorter ){
op = OP_SorterInsert;
}else{
op = OP_IdxInsert;
}
- sqlite3VdbeAddOp2(v, op, pSort->iECursor, regRecord);
- if( iLimit ){
- int addr;
- int r1 = 0;
- /* Fill the sorter until it contains LIMIT+OFFSET entries. (The iLimit
- ** register is initialized with value of LIMIT+OFFSET.) After the sorter
- ** fills up, delete the least entry in the sorter after each insert.
- ** Thus we never hold more than the LIMIT+OFFSET rows in memory at once */
- addr = sqlite3VdbeAddOp3(v, OP_IfNotZero, iLimit, 0, 1); VdbeCoverage(v);
- sqlite3VdbeAddOp1(v, OP_Last, pSort->iECursor);
- if( pSort->bOrderedInnerLoop ){
- r1 = ++pParse->nMem;
- sqlite3VdbeAddOp3(v, OP_Column, pSort->iECursor, nExpr, r1);
- VdbeComment((v, "seq"));
- }
- sqlite3VdbeAddOp1(v, OP_Delete, pSort->iECursor);
- if( pSort->bOrderedInnerLoop ){
- /* If the inner loop is driven by an index such that values from
- ** the same iteration of the inner loop are in sorted order, then
- ** immediately jump to the next iteration of an inner loop if the
- ** entry from the current iteration does not fit into the top
- ** LIMIT+OFFSET entries of the sorter. */
- int iBrk = sqlite3VdbeCurrentAddr(v) + 2;
- sqlite3VdbeAddOp3(v, OP_Eq, regBase+nExpr, iBrk, r1);
- sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
- VdbeCoverage(v);
- }
- sqlite3VdbeJumpHere(v, addr);
+ tdsqlite3VdbeAddOp4Int(v, op, pSort->iECursor, regRecord,
+ regBase+nOBSat, nBase-nOBSat);
+ if( iSkip ){
+ tdsqlite3VdbeChangeP2(v, iSkip,
+ pSort->labelOBLopt ? pSort->labelOBLopt : tdsqlite3VdbeCurrentAddr(v));
}
}
@@ -118128,7 +132261,7 @@ static void codeOffset(
int iContinue /* Jump here to skip the current record */
){
if( iOffset>0 ){
- sqlite3VdbeAddOp3(v, OP_IfPos, iOffset, iContinue, 1); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_IfPos, iOffset, iContinue, 1); VdbeCoverage(v);
VdbeComment((v, "OFFSET"));
}
}
@@ -118153,27 +132286,108 @@ static void codeDistinct(
int r1;
v = pParse->pVdbe;
- r1 = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iTab, r1);
- sqlite3ReleaseTempReg(pParse, r1);
+ r1 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, iMem, N);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
+ tdsqlite3ReleaseTempReg(pParse, r1);
+}
+
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+/*
+** This function is called as part of inner-loop generation for a SELECT
+** statement with an ORDER BY that is not optimized by an index. It
+** determines the expressions, if any, that the sorter-reference
+** optimization should be used for. The sorter-reference optimization
+** is used for SELECT queries like:
+**
+** SELECT a, bigblob FROM t1 ORDER BY a LIMIT 10
+**
+** If the optimization is used for expression "bigblob", then instead of
+** storing values read from that column in the sorter records, the PK of
+** the row from table t1 is stored instead. Then, as records are extracted from
+** the sorter to return to the user, the required value of bigblob is
+** retrieved directly from table t1. If the values are very large, this
+** can be more efficient than storing them directly in the sorter records.
+**
+** The ExprList_item.bSorterRef flag is set for each expression in pEList
+** for which the sorter-reference optimization should be enabled.
+** Additionally, the pSort->aDefer[] array is populated with entries
+** for all cursors required to evaluate all selected expressions. Finally.
+** output variable (*ppExtra) is set to an expression list containing
+** expressions for all extra PK values that should be stored in the
+** sorter records.
+*/
+static void selectExprDefer(
+ Parse *pParse, /* Leave any error here */
+ SortCtx *pSort, /* Sorter context */
+ ExprList *pEList, /* Expressions destined for sorter */
+ ExprList **ppExtra /* Expressions to append to sorter record */
+){
+ int i;
+ int nDefer = 0;
+ ExprList *pExtra = 0;
+ for(i=0; i<pEList->nExpr; i++){
+ struct ExprList_item *pItem = &pEList->a[i];
+ if( pItem->u.x.iOrderByCol==0 ){
+ Expr *pExpr = pItem->pExpr;
+ Table *pTab = pExpr->y.pTab;
+ if( pExpr->op==TK_COLUMN && pExpr->iColumn>=0 && pTab && !IsVirtual(pTab)
+ && (pTab->aCol[pExpr->iColumn].colFlags & COLFLAG_SORTERREF)
+ ){
+ int j;
+ for(j=0; j<nDefer; j++){
+ if( pSort->aDefer[j].iCsr==pExpr->iTable ) break;
+ }
+ if( j==nDefer ){
+ if( nDefer==ArraySize(pSort->aDefer) ){
+ continue;
+ }else{
+ int nKey = 1;
+ int k;
+ Index *pPk = 0;
+ if( !HasRowid(pTab) ){
+ pPk = tdsqlite3PrimaryKeyIndex(pTab);
+ nKey = pPk->nKeyCol;
+ }
+ for(k=0; k<nKey; k++){
+ Expr *pNew = tdsqlite3PExpr(pParse, TK_COLUMN, 0, 0);
+ if( pNew ){
+ pNew->iTable = pExpr->iTable;
+ pNew->y.pTab = pExpr->y.pTab;
+ pNew->iColumn = pPk ? pPk->aiColumn[k] : -1;
+ pExtra = tdsqlite3ExprListAppend(pParse, pExtra, pNew);
+ }
+ }
+ pSort->aDefer[nDefer].pTab = pExpr->y.pTab;
+ pSort->aDefer[nDefer].iCsr = pExpr->iTable;
+ pSort->aDefer[nDefer].nKey = nKey;
+ nDefer++;
+ }
+ }
+ pItem->bSorterRef = 1;
+ }
+ }
+ }
+ pSort->nDefer = (u8)nDefer;
+ *ppExtra = pExtra;
}
+#endif
/*
** This routine generates the code for the inside of the inner loop
** of a SELECT.
**
-** If srcTab is negative, then the pEList expressions
+** If srcTab is negative, then the p->pEList expressions
** are evaluated in order to get the data for this row. If srcTab is
-** zero or more, then data is pulled from srcTab and pEList is used only
-** to get number columns and the datatype for each column.
+** zero or more, then data is pulled from srcTab and p->pEList is used only
+** to get the number of columns and the collation sequence for each column.
*/
static void selectInnerLoop(
Parse *pParse, /* The parser context */
Select *p, /* The complete select statement being coded */
- ExprList *pEList, /* List of values being extracted */
- int srcTab, /* Pull data from this table */
+ int srcTab, /* Pull data from this table if non-negative */
SortCtx *pSort, /* If not NULL, info on how to process ORDER BY */
DistinctCtx *pDistinct, /* If not NULL, info on how to process DISTINCT */
SelectDest *pDest, /* How to dispose of the results */
@@ -118182,15 +132396,23 @@ static void selectInnerLoop(
){
Vdbe *v = pParse->pVdbe;
int i;
- int hasDistinct; /* True if the DISTINCT keyword is present */
- int regResult; /* Start of memory holding result set */
+ int hasDistinct; /* True if the DISTINCT keyword is present */
int eDest = pDest->eDest; /* How to dispose of results */
int iParm = pDest->iSDParm; /* First argument to disposal method */
int nResultCol; /* Number of result columns */
int nPrefixReg = 0; /* Number of extra registers before regResult */
+ RowLoadInfo sRowLoadInfo; /* Info for deferred row loading */
+
+ /* Usually, regResult is the first cell in an array of memory cells
+ ** containing the current result row. In this case regOrig is set to the
+ ** same value. However, if the results are being sent to the sorter, the
+ ** values for any expressions that are also part of the sort-key are omitted
+ ** from this array. In this case regOrig is set to zero. */
+ int regResult; /* Start of memory holding current results */
+ int regOrig; /* Start of memory holding full result (or 0) */
assert( v );
- assert( pEList!=0 );
+ assert( p->pEList!=0 );
hasDistinct = pDistinct ? pDistinct->eTnctType : WHERE_DISTINCT_NOOP;
if( pSort && pSort->pOrderBy==0 ) pSort = 0;
if( pSort==0 && !hasDistinct ){
@@ -118200,7 +132422,7 @@ static void selectInnerLoop(
/* Pull the requested columns.
*/
- nResultCol = pEList->nExpr;
+ nResultCol = p->pEList->nExpr;
if( pDest->iSdst==0 ){
if( pSort ){
@@ -118219,23 +132441,96 @@ static void selectInnerLoop(
pParse->nMem += nResultCol;
}
pDest->nSdst = nResultCol;
- regResult = pDest->iSdst;
+ regOrig = regResult = pDest->iSdst;
if( srcTab>=0 ){
for(i=0; i<nResultCol; i++){
- sqlite3VdbeAddOp3(v, OP_Column, srcTab, i, regResult+i);
- VdbeComment((v, "%s", pEList->a[i].zName));
+ tdsqlite3VdbeAddOp3(v, OP_Column, srcTab, i, regResult+i);
+ VdbeComment((v, "%s", p->pEList->a[i].zEName));
}
}else if( eDest!=SRT_Exists ){
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ ExprList *pExtra = 0;
+#endif
/* If the destination is an EXISTS(...) expression, the actual
** values returned by the SELECT are not required.
*/
- u8 ecelFlags;
+ u8 ecelFlags; /* "ecel" is an abbreviation of "ExprCodeExprList" */
+ ExprList *pEList;
if( eDest==SRT_Mem || eDest==SRT_Output || eDest==SRT_Coroutine ){
ecelFlags = SQLITE_ECEL_DUP;
}else{
ecelFlags = 0;
}
- sqlite3ExprCodeExprList(pParse, pEList, regResult, 0, ecelFlags);
+ if( pSort && hasDistinct==0 && eDest!=SRT_EphemTab && eDest!=SRT_Table ){
+ /* For each expression in p->pEList that is a copy of an expression in
+ ** the ORDER BY clause (pSort->pOrderBy), set the associated
+ ** iOrderByCol value to one more than the index of the ORDER BY
+ ** expression within the sort-key that pushOntoSorter() will generate.
+ ** This allows the p->pEList field to be omitted from the sorted record,
+ ** saving space and CPU cycles. */
+ ecelFlags |= (SQLITE_ECEL_OMITREF|SQLITE_ECEL_REF);
+
+ for(i=pSort->nOBSat; i<pSort->pOrderBy->nExpr; i++){
+ int j;
+ if( (j = pSort->pOrderBy->a[i].u.x.iOrderByCol)>0 ){
+ p->pEList->a[j-1].u.x.iOrderByCol = i+1-pSort->nOBSat;
+ }
+ }
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ selectExprDefer(pParse, pSort, p->pEList, &pExtra);
+ if( pExtra && pParse->db->mallocFailed==0 ){
+ /* If there are any extra PK columns to add to the sorter records,
+ ** allocate extra memory cells and adjust the OpenEphemeral
+ ** instruction to account for the larger records. This is only
+ ** required if there are one or more WITHOUT ROWID tables with
+ ** composite primary keys in the SortCtx.aDefer[] array. */
+ VdbeOp *pOp = tdsqlite3VdbeGetOp(v, pSort->addrSortIndex);
+ pOp->p2 += (pExtra->nExpr - pSort->nDefer);
+ pOp->p4.pKeyInfo->nAllField += (pExtra->nExpr - pSort->nDefer);
+ pParse->nMem += pExtra->nExpr;
+ }
+#endif
+
+ /* Adjust nResultCol to account for columns that are omitted
+ ** from the sorter by the optimizations in this branch */
+ pEList = p->pEList;
+ for(i=0; i<pEList->nExpr; i++){
+ if( pEList->a[i].u.x.iOrderByCol>0
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ || pEList->a[i].bSorterRef
+#endif
+ ){
+ nResultCol--;
+ regOrig = 0;
+ }
+ }
+
+ testcase( regOrig );
+ testcase( eDest==SRT_Set );
+ testcase( eDest==SRT_Mem );
+ testcase( eDest==SRT_Coroutine );
+ testcase( eDest==SRT_Output );
+ assert( eDest==SRT_Set || eDest==SRT_Mem
+ || eDest==SRT_Coroutine || eDest==SRT_Output );
+ }
+ sRowLoadInfo.regResult = regResult;
+ sRowLoadInfo.ecelFlags = ecelFlags;
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ sRowLoadInfo.pExtra = pExtra;
+ sRowLoadInfo.regExtraResult = regResult + nResultCol;
+ if( pExtra ) nResultCol += pExtra->nExpr;
+#endif
+ if( p->iLimit
+ && (ecelFlags & SQLITE_ECEL_OMITREF)!=0
+ && nPrefixReg>0
+ ){
+ assert( pSort!=0 );
+ assert( hasDistinct==0 );
+ pSort->pDeferredRowLoad = &sRowLoadInfo;
+ regOrig = 0;
+ }else{
+ innerLoopLoadRow(pParse, p, &sRowLoadInfo);
+ }
}
/* If the DISTINCT keyword was present on the SELECT statement
@@ -118259,32 +132554,33 @@ static void selectInnerLoop(
** fail on the first iteration of the loop even if the first
** row is all NULLs.
*/
- sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct);
- pOp = sqlite3VdbeGetOp(v, pDistinct->addrTnct);
+ tdsqlite3VdbeChangeToNoop(v, pDistinct->addrTnct);
+ pOp = tdsqlite3VdbeGetOp(v, pDistinct->addrTnct);
pOp->opcode = OP_Null;
pOp->p1 = 1;
pOp->p2 = regPrev;
+ pOp = 0; /* Ensure pOp is not used after tdsqlite3VdbeAddOp() */
- iJump = sqlite3VdbeCurrentAddr(v) + nResultCol;
+ iJump = tdsqlite3VdbeCurrentAddr(v) + nResultCol;
for(i=0; i<nResultCol; i++){
- CollSeq *pColl = sqlite3ExprCollSeq(pParse, pEList->a[i].pExpr);
+ CollSeq *pColl = tdsqlite3ExprCollSeq(pParse, p->pEList->a[i].pExpr);
if( i<nResultCol-1 ){
- sqlite3VdbeAddOp3(v, OP_Ne, regResult+i, iJump, regPrev+i);
+ tdsqlite3VdbeAddOp3(v, OP_Ne, regResult+i, iJump, regPrev+i);
VdbeCoverage(v);
}else{
- sqlite3VdbeAddOp3(v, OP_Eq, regResult+i, iContinue, regPrev+i);
+ tdsqlite3VdbeAddOp3(v, OP_Eq, regResult+i, iContinue, regPrev+i);
VdbeCoverage(v);
}
- sqlite3VdbeChangeP4(v, -1, (const char *)pColl, P4_COLLSEQ);
- sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
+ tdsqlite3VdbeChangeP4(v, -1, (const char *)pColl, P4_COLLSEQ);
+ tdsqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
}
- assert( sqlite3VdbeCurrentAddr(v)==iJump || pParse->db->mallocFailed );
- sqlite3VdbeAddOp3(v, OP_Copy, regResult, regPrev, nResultCol-1);
+ assert( tdsqlite3VdbeCurrentAddr(v)==iJump || pParse->db->mallocFailed );
+ tdsqlite3VdbeAddOp3(v, OP_Copy, regResult, regPrev, nResultCol-1);
break;
}
case WHERE_DISTINCT_UNIQUE: {
- sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct);
+ tdsqlite3VdbeChangeToNoop(v, pDistinct->addrTnct);
break;
}
@@ -118307,10 +132603,10 @@ static void selectInnerLoop(
#ifndef SQLITE_OMIT_COMPOUND_SELECT
case SRT_Union: {
int r1;
- r1 = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
- sqlite3ReleaseTempReg(pParse, r1);
+ r1 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol);
+ tdsqlite3ReleaseTempReg(pParse, r1);
break;
}
@@ -118319,7 +132615,7 @@ static void selectInnerLoop(
** the temporary table iParm.
*/
case SRT_Except: {
- sqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nResultCol);
+ tdsqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nResultCol);
break;
}
#endif /* SQLITE_OMIT_COMPOUND_SELECT */
@@ -118330,12 +132626,12 @@ static void selectInnerLoop(
case SRT_DistFifo:
case SRT_Table:
case SRT_EphemTab: {
- int r1 = sqlite3GetTempRange(pParse, nPrefixReg+1);
+ int r1 = tdsqlite3GetTempRange(pParse, nPrefixReg+1);
testcase( eDest==SRT_Table );
testcase( eDest==SRT_EphemTab );
testcase( eDest==SRT_Fifo );
testcase( eDest==SRT_DistFifo );
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg);
#ifndef SQLITE_OMIT_CTE
if( eDest==SRT_DistFifo ){
/* If the destination is DistFifo, then cursor (iParm+1) is open
@@ -118343,23 +132639,24 @@ static void selectInnerLoop(
** in the index, do not write it to the output. If not, add the
** current row to the index and proceed with writing it to the
** output table as well. */
- int addr = sqlite3VdbeCurrentAddr(v) + 4;
- sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, addr, r1, 0);
+ int addr = tdsqlite3VdbeCurrentAddr(v) + 4;
+ tdsqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, addr, r1, 0);
VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r1);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm+1, r1,regResult,nResultCol);
assert( pSort==0 );
}
#endif
if( pSort ){
- pushOntoSorter(pParse, pSort, p, r1+nPrefixReg,regResult,1,nPrefixReg);
+ assert( regResult==regOrig );
+ pushOntoSorter(pParse, pSort, p, r1+nPrefixReg, regOrig, 1, nPrefixReg);
}else{
- int r2 = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2);
- sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2);
- sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
- sqlite3ReleaseTempReg(pParse, r2);
+ int r2 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ tdsqlite3ReleaseTempReg(pParse, r2);
}
- sqlite3ReleaseTempRange(pParse, r1, nPrefixReg+1);
+ tdsqlite3ReleaseTempRange(pParse, r1, nPrefixReg+1);
break;
}
@@ -118375,15 +132672,14 @@ static void selectInnerLoop(
** does not matter. But there might be a LIMIT clause, in which
** case the order does matter */
pushOntoSorter(
- pParse, pSort, p, regResult, regResult, nResultCol, nPrefixReg);
+ pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg);
}else{
- int r1 = sqlite3GetTempReg(pParse);
- assert( sqlite3Strlen30(pDest->zAffSdst)==nResultCol );
- sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol,
+ int r1 = tdsqlite3GetTempReg(pParse);
+ assert( tdsqlite3Strlen30(pDest->zAffSdst)==nResultCol );
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol,
r1, pDest->zAffSdst, nResultCol);
- sqlite3ExprCacheAffinityChange(pParse, regResult, nResultCol);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
- sqlite3ReleaseTempReg(pParse, r1);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol);
+ tdsqlite3ReleaseTempReg(pParse, r1);
}
break;
}
@@ -118391,7 +132687,7 @@ static void selectInnerLoop(
/* If any row exist in the result set, record that fact and abort.
*/
case SRT_Exists: {
- sqlite3VdbeAddOp2(v, OP_Integer, 1, iParm);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, iParm);
/* The LIMIT clause will terminate the loop for us */
break;
}
@@ -118401,11 +132697,12 @@ static void selectInnerLoop(
** memory cells and break out of the scan loop.
*/
case SRT_Mem: {
- assert( nResultCol==pDest->nSdst );
if( pSort ){
+ assert( nResultCol<=pDest->nSdst );
pushOntoSorter(
- pParse, pSort, p, regResult, regResult, nResultCol, nPrefixReg);
+ pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg);
}else{
+ assert( nResultCol==pDest->nSdst );
assert( regResult==iParm );
/* The LIMIT clause will jump out of the loop for us */
}
@@ -118418,13 +132715,12 @@ static void selectInnerLoop(
testcase( eDest==SRT_Coroutine );
testcase( eDest==SRT_Output );
if( pSort ){
- pushOntoSorter(pParse, pSort, p, regResult, regResult, nResultCol,
+ pushOntoSorter(pParse, pSort, p, regResult, regOrig, nResultCol,
nPrefixReg);
}else if( eDest==SRT_Coroutine ){
- sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm);
+ tdsqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm);
}else{
- sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, nResultCol);
- sqlite3ExprCacheAffinityChange(pParse, regResult, nResultCol);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, regResult, nResultCol);
}
break;
}
@@ -118445,33 +132741,33 @@ static void selectInnerLoop(
pSO = pDest->pOrderBy;
assert( pSO );
nKey = pSO->nExpr;
- r1 = sqlite3GetTempReg(pParse);
- r2 = sqlite3GetTempRange(pParse, nKey+2);
+ r1 = tdsqlite3GetTempReg(pParse);
+ r2 = tdsqlite3GetTempRange(pParse, nKey+2);
r3 = r2+nKey+1;
if( eDest==SRT_DistQueue ){
/* If the destination is DistQueue, then cursor (iParm+1) is open
** on a second ephemeral index that holds all values every previously
** added to the queue. */
- addrTest = sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, 0,
+ addrTest = tdsqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, 0,
regResult, nResultCol);
VdbeCoverage(v);
}
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r3);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r3);
if( eDest==SRT_DistQueue ){
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r3);
- sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
+ tdsqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r3);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
}
for(i=0; i<nKey; i++){
- sqlite3VdbeAddOp2(v, OP_SCopy,
+ tdsqlite3VdbeAddOp2(v, OP_SCopy,
regResult + pSO->a[i].u.x.iOrderByCol - 1,
r2+i);
}
- sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1);
- if( addrTest ) sqlite3VdbeJumpHere(v, addrTest);
- sqlite3ReleaseTempReg(pParse, r1);
- sqlite3ReleaseTempRange(pParse, r2, nKey+2);
+ tdsqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, r2, nKey+2);
+ if( addrTest ) tdsqlite3VdbeJumpHere(v, addrTest);
+ tdsqlite3ReleaseTempReg(pParse, r1);
+ tdsqlite3ReleaseTempRange(pParse, r2, nKey+2);
break;
}
#endif /* SQLITE_OMIT_CTE */
@@ -118496,7 +132792,7 @@ static void selectInnerLoop(
** the output for us.
*/
if( pSort==0 && p->iLimit ){
- sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak); VdbeCoverage(v);
}
}
@@ -118504,19 +132800,19 @@ static void selectInnerLoop(
** Allocate a KeyInfo object sufficient for an index of N key columns and
** X extra columns.
*/
-SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){
- int nExtra = (N+X)*(sizeof(CollSeq*)+1);
- KeyInfo *p = sqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra);
+SQLITE_PRIVATE KeyInfo *tdsqlite3KeyInfoAlloc(tdsqlite3 *db, int N, int X){
+ int nExtra = (N+X)*(sizeof(CollSeq*)+1) - sizeof(CollSeq*);
+ KeyInfo *p = tdsqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra);
if( p ){
- p->aSortOrder = (u8*)&p->aColl[N+X];
- p->nField = (u16)N;
- p->nXField = (u16)X;
+ p->aSortFlags = (u8*)&p->aColl[N+X];
+ p->nKeyField = (u16)N;
+ p->nAllField = (u16)(N+X);
p->enc = ENC(db);
p->db = db;
p->nRef = 1;
memset(&p[1], 0, nExtra);
}else{
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
}
return p;
}
@@ -118524,18 +132820,18 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){
/*
** Deallocate a KeyInfo object
*/
-SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo *p){
+SQLITE_PRIVATE void tdsqlite3KeyInfoUnref(KeyInfo *p){
if( p ){
assert( p->nRef>0 );
p->nRef--;
- if( p->nRef==0 ) sqlite3DbFree(p->db, p);
+ if( p->nRef==0 ) tdsqlite3DbFreeNN(p->db, p);
}
}
/*
** Make a new pointer to a KeyInfo object
*/
-SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoRef(KeyInfo *p){
+SQLITE_PRIVATE KeyInfo *tdsqlite3KeyInfoRef(KeyInfo *p){
if( p ){
assert( p->nRef>0 );
p->nRef++;
@@ -118550,7 +132846,7 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoRef(KeyInfo *p){
**
** This routine is used only inside of assert() statements.
*/
-SQLITE_PRIVATE int sqlite3KeyInfoIsWriteable(KeyInfo *p){ return p->nRef==1; }
+SQLITE_PRIVATE int tdsqlite3KeyInfoIsWriteable(KeyInfo *p){ return p->nRef==1; }
#endif /* SQLITE_DEBUG */
/*
@@ -118567,7 +132863,7 @@ SQLITE_PRIVATE int sqlite3KeyInfoIsWriteable(KeyInfo *p){ return p->nRef==1; }
** function is responsible for seeing that this structure is eventually
** freed.
*/
-static KeyInfo *keyInfoFromExprList(
+SQLITE_PRIVATE KeyInfo *tdsqlite3KeyInfoFromExprList(
Parse *pParse, /* Parsing context */
ExprList *pList, /* Form the KeyInfo object from this ExprList */
int iStart, /* Begin with this column of pList */
@@ -118576,19 +132872,16 @@ static KeyInfo *keyInfoFromExprList(
int nExpr;
KeyInfo *pInfo;
struct ExprList_item *pItem;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
int i;
nExpr = pList->nExpr;
- pInfo = sqlite3KeyInfoAlloc(db, nExpr-iStart, nExtra+1);
+ pInfo = tdsqlite3KeyInfoAlloc(db, nExpr-iStart, nExtra+1);
if( pInfo ){
- assert( sqlite3KeyInfoIsWriteable(pInfo) );
+ assert( tdsqlite3KeyInfoIsWriteable(pInfo) );
for(i=iStart, pItem=pList->a+iStart; i<nExpr; i++, pItem++){
- CollSeq *pColl;
- pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
- if( !pColl ) pColl = db->pDfltColl;
- pInfo->aColl[i-iStart] = pColl;
- pInfo->aSortOrder[i-iStart] = pItem->sortOrder;
+ pInfo->aColl[i-iStart] = tdsqlite3ExprNNCollSeq(pParse, pItem->pExpr);
+ pInfo->aSortFlags[i-iStart] = pItem->sortFlags;
}
}
return pInfo;
@@ -118620,17 +132913,13 @@ static const char *selectOpName(int id){
** is determined by the zUsage argument.
*/
static void explainTempTable(Parse *pParse, const char *zUsage){
- if( pParse->explain==2 ){
- Vdbe *v = pParse->pVdbe;
- char *zMsg = sqlite3MPrintf(pParse->db, "USE TEMP B-TREE FOR %s", zUsage);
- sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
- }
+ ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s", zUsage));
}
/*
** Assign expression b to lvalue a. A second, no-op, version of this macro
** is provided when SQLITE_OMIT_EXPLAIN is defined. This allows the code
-** in sqlite3Select() to assign values to structure member variables that
+** in tdsqlite3Select() to assign values to structure member variables that
** only exist if SQLITE_OMIT_EXPLAIN is not defined without polluting the
** code with #ifndef directives.
*/
@@ -118642,42 +132931,6 @@ static void explainTempTable(Parse *pParse, const char *zUsage){
# define explainSetInteger(y,z)
#endif
-#if !defined(SQLITE_OMIT_EXPLAIN) && !defined(SQLITE_OMIT_COMPOUND_SELECT)
-/*
-** Unless an "EXPLAIN QUERY PLAN" command is being processed, this function
-** is a no-op. Otherwise, it adds a single row of output to the EQP result,
-** where the caption is of one of the two forms:
-**
-** "COMPOSITE SUBQUERIES iSub1 and iSub2 (op)"
-** "COMPOSITE SUBQUERIES iSub1 and iSub2 USING TEMP B-TREE (op)"
-**
-** where iSub1 and iSub2 are the integers passed as the corresponding
-** function parameters, and op is the text representation of the parameter
-** of the same name. The parameter "op" must be one of TK_UNION, TK_EXCEPT,
-** TK_INTERSECT or TK_ALL. The first form is used if argument bUseTmp is
-** false, or the second form if it is true.
-*/
-static void explainComposite(
- Parse *pParse, /* Parse context */
- int op, /* One of TK_UNION, TK_EXCEPT etc. */
- int iSub1, /* Subquery id 1 */
- int iSub2, /* Subquery id 2 */
- int bUseTmp /* True if a temp table was used */
-){
- assert( op==TK_UNION || op==TK_EXCEPT || op==TK_INTERSECT || op==TK_ALL );
- if( pParse->explain==2 ){
- Vdbe *v = pParse->pVdbe;
- char *zMsg = sqlite3MPrintf(
- pParse->db, "COMPOUND SUBQUERIES %d AND %d %s(%s)", iSub1, iSub2,
- bUseTmp?"USING TEMP B-TREE ":"", selectOpName(op)
- );
- sqlite3VdbeAddOp4(v, OP_Explain, pParse->iSelectId, 0, 0, zMsg, P4_DYNAMIC);
- }
-}
-#else
-/* No-op versions of the explainXXX() functions and macros. */
-# define explainComposite(v,w,x,y,z)
-#endif
/*
** If the inner loop was generated using a non-null pOrderBy argument,
@@ -118694,8 +132947,8 @@ static void generateSortTail(
){
Vdbe *v = pParse->pVdbe; /* The prepared statement */
int addrBreak = pSort->labelDone; /* Jump here to exit loop */
- int addrContinue = sqlite3VdbeMakeLabel(v); /* Jump here for next cycle */
- int addr;
+ int addrContinue = tdsqlite3VdbeMakeLabel(pParse);/* Jump here for next cycle */
+ int addr; /* Top of output loop. Jump for Next. */
int addrOnce = 0;
int iTab;
ExprList *pOrderBy = pSort->pOrderBy;
@@ -118703,69 +132956,134 @@ static void generateSortTail(
int iParm = pDest->iSDParm;
int regRow;
int regRowid;
- int nKey;
+ int iCol;
+ int nKey; /* Number of key columns in sorter record */
int iSortTab; /* Sorter cursor to read from */
- int nSortData; /* Trailing values to read from sorter */
int i;
int bSeq; /* True if sorter record includes seq. no. */
-#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
+ int nRefKey = 0;
struct ExprList_item *aOutEx = p->pEList->a;
-#endif
assert( addrBreak<0 );
if( pSort->labelBkOut ){
- sqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut);
- sqlite3VdbeGoto(v, addrBreak);
- sqlite3VdbeResolveLabel(v, pSort->labelBkOut);
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, pSort->regReturn, pSort->labelBkOut);
+ tdsqlite3VdbeGoto(v, addrBreak);
+ tdsqlite3VdbeResolveLabel(v, pSort->labelBkOut);
+ }
+
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ /* Open any cursors needed for sorter-reference expressions */
+ for(i=0; i<pSort->nDefer; i++){
+ Table *pTab = pSort->aDefer[i].pTab;
+ int iDb = tdsqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ tdsqlite3OpenTable(pParse, pSort->aDefer[i].iCsr, iDb, pTab, OP_OpenRead);
+ nRefKey = MAX(nRefKey, pSort->aDefer[i].nKey);
}
+#endif
+
iTab = pSort->iECursor;
if( eDest==SRT_Output || eDest==SRT_Coroutine || eDest==SRT_Mem ){
regRowid = 0;
regRow = pDest->iSdst;
- nSortData = nColumn;
}else{
- regRowid = sqlite3GetTempReg(pParse);
- regRow = sqlite3GetTempRange(pParse, nColumn);
- nSortData = nColumn;
+ regRowid = tdsqlite3GetTempReg(pParse);
+ if( eDest==SRT_EphemTab || eDest==SRT_Table ){
+ regRow = tdsqlite3GetTempReg(pParse);
+ nColumn = 0;
+ }else{
+ regRow = tdsqlite3GetTempRange(pParse, nColumn);
+ }
}
nKey = pOrderBy->nExpr - pSort->nOBSat;
if( pSort->sortFlags & SORTFLAG_UseSorter ){
int regSortOut = ++pParse->nMem;
iSortTab = pParse->nTab++;
if( pSort->labelBkOut ){
- addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ addrOnce = tdsqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
}
- sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, nKey+1+nSortData);
- if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce);
- addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak);
+ tdsqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut,
+ nKey+1+nColumn+nRefKey);
+ if( addrOnce ) tdsqlite3VdbeJumpHere(v, addrOnce);
+ addr = 1 + tdsqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak);
VdbeCoverage(v);
codeOffset(v, p->iOffset, addrContinue);
- sqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab);
+ tdsqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab);
bSeq = 0;
}else{
- addr = 1 + sqlite3VdbeAddOp2(v, OP_Sort, iTab, addrBreak); VdbeCoverage(v);
+ addr = 1 + tdsqlite3VdbeAddOp2(v, OP_Sort, iTab, addrBreak); VdbeCoverage(v);
codeOffset(v, p->iOffset, addrContinue);
iSortTab = iTab;
bSeq = 1;
}
- for(i=0; i<nSortData; i++){
- sqlite3VdbeAddOp3(v, OP_Column, iSortTab, nKey+bSeq+i, regRow+i);
- VdbeComment((v, "%s", aOutEx[i].zName ? aOutEx[i].zName : aOutEx[i].zSpan));
+ for(i=0, iCol=nKey+bSeq-1; i<nColumn; i++){
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( aOutEx[i].bSorterRef ) continue;
+#endif
+ if( aOutEx[i].u.x.iOrderByCol==0 ) iCol++;
+ }
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( pSort->nDefer ){
+ int iKey = iCol+1;
+ int regKey = tdsqlite3GetTempRange(pParse, nRefKey);
+
+ for(i=0; i<pSort->nDefer; i++){
+ int iCsr = pSort->aDefer[i].iCsr;
+ Table *pTab = pSort->aDefer[i].pTab;
+ int nKey = pSort->aDefer[i].nKey;
+
+ tdsqlite3VdbeAddOp1(v, OP_NullRow, iCsr);
+ if( HasRowid(pTab) ){
+ tdsqlite3VdbeAddOp3(v, OP_Column, iSortTab, iKey++, regKey);
+ tdsqlite3VdbeAddOp3(v, OP_SeekRowid, iCsr,
+ tdsqlite3VdbeCurrentAddr(v)+1, regKey);
+ }else{
+ int k;
+ int iJmp;
+ assert( tdsqlite3PrimaryKeyIndex(pTab)->nKeyCol==nKey );
+ for(k=0; k<nKey; k++){
+ tdsqlite3VdbeAddOp3(v, OP_Column, iSortTab, iKey++, regKey+k);
+ }
+ iJmp = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp4Int(v, OP_SeekGE, iCsr, iJmp+2, regKey, nKey);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxLE, iCsr, iJmp+3, regKey, nKey);
+ tdsqlite3VdbeAddOp1(v, OP_NullRow, iCsr);
+ }
+ }
+ tdsqlite3ReleaseTempRange(pParse, regKey, nRefKey);
+ }
+#endif
+ for(i=nColumn-1; i>=0; i--){
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ if( aOutEx[i].bSorterRef ){
+ tdsqlite3ExprCode(pParse, aOutEx[i].pExpr, regRow+i);
+ }else
+#endif
+ {
+ int iRead;
+ if( aOutEx[i].u.x.iOrderByCol ){
+ iRead = aOutEx[i].u.x.iOrderByCol-1;
+ }else{
+ iRead = iCol--;
+ }
+ tdsqlite3VdbeAddOp3(v, OP_Column, iSortTab, iRead, regRow+i);
+ VdbeComment((v, "%s", aOutEx[i].zEName));
+ }
}
switch( eDest ){
+ case SRT_Table:
case SRT_EphemTab: {
- sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, regRowid);
- sqlite3VdbeAddOp3(v, OP_Insert, iParm, regRow, regRowid);
- sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iSortTab, nKey+bSeq, regRow);
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, iParm, regRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, iParm, regRow, regRowid);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_APPEND);
break;
}
#ifndef SQLITE_OMIT_SUBQUERY
case SRT_Set: {
- assert( nColumn==sqlite3Strlen30(pDest->zAffSdst) );
- sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn, regRowid,
+ assert( nColumn==tdsqlite3Strlen30(pDest->zAffSdst) );
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn, regRowid,
pDest->zAffSdst, nColumn);
- sqlite3ExprCacheAffinityChange(pParse, regRow, nColumn);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRowid);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, regRowid, regRow, nColumn);
break;
}
case SRT_Mem: {
@@ -118778,32 +133096,31 @@ static void generateSortTail(
testcase( eDest==SRT_Output );
testcase( eDest==SRT_Coroutine );
if( eDest==SRT_Output ){
- sqlite3VdbeAddOp2(v, OP_ResultRow, pDest->iSdst, nColumn);
- sqlite3ExprCacheAffinityChange(pParse, pDest->iSdst, nColumn);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, pDest->iSdst, nColumn);
}else{
- sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm);
+ tdsqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm);
}
break;
}
}
if( regRowid ){
if( eDest==SRT_Set ){
- sqlite3ReleaseTempRange(pParse, regRow, nColumn);
+ tdsqlite3ReleaseTempRange(pParse, regRow, nColumn);
}else{
- sqlite3ReleaseTempReg(pParse, regRow);
+ tdsqlite3ReleaseTempReg(pParse, regRow);
}
- sqlite3ReleaseTempReg(pParse, regRowid);
+ tdsqlite3ReleaseTempReg(pParse, regRowid);
}
/* The bottom of the loop
*/
- sqlite3VdbeResolveLabel(v, addrContinue);
+ tdsqlite3VdbeResolveLabel(v, addrContinue);
if( pSort->sortFlags & SORTFLAG_UseSorter ){
- sqlite3VdbeAddOp2(v, OP_SorterNext, iTab, addr); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_SorterNext, iTab, addr); VdbeCoverage(v);
}else{
- sqlite3VdbeAddOp2(v, OP_Next, iTab, addr); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_Next, iTab, addr); VdbeCoverage(v);
}
- if( pSort->regReturn ) sqlite3VdbeAddOp1(v, OP_Return, pSort->regReturn);
- sqlite3VdbeResolveLabel(v, addrBreak);
+ if( pSort->regReturn ) tdsqlite3VdbeAddOp1(v, OP_Return, pSort->regReturn);
+ tdsqlite3VdbeResolveLabel(v, addrBreak);
}
/*
@@ -118831,23 +133148,23 @@ static void generateSortTail(
** the SQLITE_ENABLE_COLUMN_METADATA compile-time option is used.
*/
#ifdef SQLITE_ENABLE_COLUMN_METADATA
-# define columnType(A,B,C,D,E,F) columnTypeImpl(A,B,C,D,E,F)
+# define columnType(A,B,C,D,E) columnTypeImpl(A,B,C,D,E)
#else /* if !defined(SQLITE_ENABLE_COLUMN_METADATA) */
-# define columnType(A,B,C,D,E,F) columnTypeImpl(A,B,F)
+# define columnType(A,B,C,D,E) columnTypeImpl(A,B)
#endif
static const char *columnTypeImpl(
NameContext *pNC,
+#ifndef SQLITE_ENABLE_COLUMN_METADATA
+ Expr *pExpr
+#else
Expr *pExpr,
-#ifdef SQLITE_ENABLE_COLUMN_METADATA
const char **pzOrigDb,
const char **pzOrigTab,
- const char **pzOrigCol,
+ const char **pzOrigCol
#endif
- u8 *pEstWidth
){
char const *zType = 0;
int j;
- u8 estWidth = 1;
#ifdef SQLITE_ENABLE_COLUMN_METADATA
char const *zOrigDb = 0;
char const *zOrigTab = 0;
@@ -118857,7 +133174,6 @@ static const char *columnTypeImpl(
assert( pExpr!=0 );
assert( pNC->pSrcList!=0 );
switch( pExpr->op ){
- case TK_AGG_COLUMN:
case TK_COLUMN: {
/* The expression is a column. Locate the table the column is being
** extracted from in NameContext.pSrcList. This table may be real
@@ -118866,8 +133182,6 @@ static const char *columnTypeImpl(
Table *pTab = 0; /* Table structure column is extracted from */
Select *pS = 0; /* Select the column is extracted from */
int iCol = pExpr->iColumn; /* Index of column in pTab */
- testcase( pExpr->op==TK_AGG_COLUMN );
- testcase( pExpr->op==TK_COLUMN );
while( pNC && !pTab ){
SrcList *pTabList = pNC->pSrcList;
for(j=0;j<pTabList->nSrc && pTabList->a[j].iCursor!=pExpr->iTable;j++);
@@ -118900,52 +133214,48 @@ static const char *columnTypeImpl(
break;
}
- assert( pTab && pExpr->pTab==pTab );
+ assert( pTab && pExpr->y.pTab==pTab );
if( pS ){
/* The "table" is actually a sub-select or a view in the FROM clause
** of the SELECT statement. Return the declaration type and origin
** data for the result-set column of the sub-select.
*/
- if( iCol>=0 && ALWAYS(iCol<pS->pEList->nExpr) ){
+ if( iCol>=0 && iCol<pS->pEList->nExpr ){
/* If iCol is less than zero, then the expression requests the
** rowid of the sub-select or view. This expression is legal (see
** test case misc2.2.2) - it always evaluates to NULL.
- **
- ** The ALWAYS() is because iCol>=pS->pEList->nExpr will have been
- ** caught already by name resolution.
*/
NameContext sNC;
Expr *p = pS->pEList->a[iCol].pExpr;
sNC.pSrcList = pS->pSrc;
sNC.pNext = pNC;
sNC.pParse = pNC->pParse;
- zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol, &estWidth);
+ zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol);
}
- }else if( pTab->pSchema ){
- /* A real table */
+ }else{
+ /* A real table or a CTE table */
assert( !pS );
- if( iCol<0 ) iCol = pTab->iPKey;
- assert( iCol==-1 || (iCol>=0 && iCol<pTab->nCol) );
#ifdef SQLITE_ENABLE_COLUMN_METADATA
+ if( iCol<0 ) iCol = pTab->iPKey;
+ assert( iCol==XN_ROWID || (iCol>=0 && iCol<pTab->nCol) );
if( iCol<0 ){
zType = "INTEGER";
zOrigCol = "rowid";
}else{
zOrigCol = pTab->aCol[iCol].zName;
- zType = sqlite3ColumnType(&pTab->aCol[iCol],0);
- estWidth = pTab->aCol[iCol].szEst;
+ zType = tdsqlite3ColumnType(&pTab->aCol[iCol],0);
}
zOrigTab = pTab->zName;
- if( pNC->pParse ){
- int iDb = sqlite3SchemaToIndex(pNC->pParse->db, pTab->pSchema);
+ if( pNC->pParse && pTab->pSchema ){
+ int iDb = tdsqlite3SchemaToIndex(pNC->pParse->db, pTab->pSchema);
zOrigDb = pNC->pParse->db->aDb[iDb].zDbSName;
}
#else
+ assert( iCol==XN_ROWID || (iCol>=0 && iCol<pTab->nCol) );
if( iCol<0 ){
zType = "INTEGER";
}else{
- zType = sqlite3ColumnType(&pTab->aCol[iCol],0);
- estWidth = pTab->aCol[iCol].szEst;
+ zType = tdsqlite3ColumnType(&pTab->aCol[iCol],0);
}
#endif
}
@@ -118964,7 +133274,7 @@ static const char *columnTypeImpl(
sNC.pSrcList = pS->pSrc;
sNC.pNext = pNC;
sNC.pParse = pNC->pParse;
- zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol, &estWidth);
+ zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol);
break;
}
#endif
@@ -118978,7 +133288,6 @@ static const char *columnTypeImpl(
*pzOrigCol = zOrigCol;
}
#endif
- if( pEstWidth ) *pEstWidth = estWidth;
return zType;
}
@@ -118997,6 +133306,7 @@ static void generateColumnTypes(
NameContext sNC;
sNC.pSrcList = pTabList;
sNC.pParse = pParse;
+ sNC.pNext = 0;
for(i=0; i<pEList->nExpr; i++){
Expr *p = pEList->a[i].pExpr;
const char *zType;
@@ -119004,37 +133314,66 @@ static void generateColumnTypes(
const char *zOrigDb = 0;
const char *zOrigTab = 0;
const char *zOrigCol = 0;
- zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol, 0);
+ zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol);
/* The vdbe must make its own copy of the column-type and other
** column specific strings, in case the schema is reset before this
** virtual machine is deleted.
*/
- sqlite3VdbeSetColName(v, i, COLNAME_DATABASE, zOrigDb, SQLITE_TRANSIENT);
- sqlite3VdbeSetColName(v, i, COLNAME_TABLE, zOrigTab, SQLITE_TRANSIENT);
- sqlite3VdbeSetColName(v, i, COLNAME_COLUMN, zOrigCol, SQLITE_TRANSIENT);
+ tdsqlite3VdbeSetColName(v, i, COLNAME_DATABASE, zOrigDb, SQLITE_TRANSIENT);
+ tdsqlite3VdbeSetColName(v, i, COLNAME_TABLE, zOrigTab, SQLITE_TRANSIENT);
+ tdsqlite3VdbeSetColName(v, i, COLNAME_COLUMN, zOrigCol, SQLITE_TRANSIENT);
#else
- zType = columnType(&sNC, p, 0, 0, 0, 0);
+ zType = columnType(&sNC, p, 0, 0, 0);
#endif
- sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, zType, SQLITE_TRANSIENT);
+ tdsqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, zType, SQLITE_TRANSIENT);
}
#endif /* !defined(SQLITE_OMIT_DECLTYPE) */
}
+
/*
-** Generate code that will tell the VDBE the names of columns
-** in the result set. This information is used to provide the
-** azCol[] values in the callback.
+** Compute the column names for a SELECT statement.
+**
+** The only guarantee that SQLite makes about column names is that if the
+** column has an AS clause assigning it a name, that will be the name used.
+** That is the only documented guarantee. However, countless applications
+** developed over the years have made baseless assumptions about column names
+** and will break if those assumptions changes. Hence, use extreme caution
+** when modifying this routine to avoid breaking legacy.
+**
+** See Also: tdsqlite3ColumnsFromExprList()
+**
+** The PRAGMA short_column_names and PRAGMA full_column_names settings are
+** deprecated. The default setting is short=ON, full=OFF. 99.9% of all
+** applications should operate this way. Nevertheless, we need to support the
+** other modes for legacy:
+**
+** short=OFF, full=OFF: Column name is the text of the expression has it
+** originally appears in the SELECT statement. In
+** other words, the zSpan of the result expression.
+**
+** short=ON, full=OFF: (This is the default setting). If the result
+** refers directly to a table column, then the
+** result column name is just the table column
+** name: COLUMN. Otherwise use zSpan.
+**
+** full=ON, short=ANY: If the result refers directly to a table column,
+** then the result column name with the table name
+** prefix, ex: TABLE.COLUMN. Otherwise use zSpan.
*/
static void generateColumnNames(
Parse *pParse, /* Parser context */
- SrcList *pTabList, /* List of tables */
- ExprList *pEList /* Expressions defining the result set */
+ Select *pSelect /* Generate column names for this SELECT statement */
){
Vdbe *v = pParse->pVdbe;
- int i, j;
- sqlite3 *db = pParse->db;
- int fullNames, shortNames;
+ int i;
+ Table *pTab;
+ SrcList *pTabList;
+ ExprList *pEList;
+ tdsqlite3 *db = pParse->db;
+ int fullName; /* TABLE.COLUMN if no AS clause and is a direct table ref */
+ int srcName; /* COLUMN or TABLE.COLUMN if no AS clause and is direct */
#ifndef SQLITE_OMIT_EXPLAIN
/* If this is an EXPLAIN, skip this step */
@@ -119043,29 +133382,33 @@ static void generateColumnNames(
}
#endif
- if( pParse->colNamesSet || db->mallocFailed ) return;
+ if( pParse->colNamesSet ) return;
+ /* Column names are determined by the left-most term of a compound select */
+ while( pSelect->pPrior ) pSelect = pSelect->pPrior;
+ SELECTTRACE(1,pParse,pSelect,("generating column names\n"));
+ pTabList = pSelect->pSrc;
+ pEList = pSelect->pEList;
assert( v!=0 );
assert( pTabList!=0 );
pParse->colNamesSet = 1;
- fullNames = (db->flags & SQLITE_FullColNames)!=0;
- shortNames = (db->flags & SQLITE_ShortColNames)!=0;
- sqlite3VdbeSetNumCols(v, pEList->nExpr);
+ fullName = (db->flags & SQLITE_FullColNames)!=0;
+ srcName = (db->flags & SQLITE_ShortColNames)!=0 || fullName;
+ tdsqlite3VdbeSetNumCols(v, pEList->nExpr);
for(i=0; i<pEList->nExpr; i++){
- Expr *p;
- p = pEList->a[i].pExpr;
- if( NEVER(p==0) ) continue;
- if( pEList->a[i].zName ){
- char *zName = pEList->a[i].zName;
- sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_TRANSIENT);
- }else if( p->op==TK_COLUMN || p->op==TK_AGG_COLUMN ){
- Table *pTab;
+ Expr *p = pEList->a[i].pExpr;
+
+ assert( p!=0 );
+ assert( p->op!=TK_AGG_COLUMN ); /* Agg processing has not run yet */
+ assert( p->op!=TK_COLUMN || p->y.pTab!=0 ); /* Covering idx not yet coded */
+ if( pEList->a[i].zEName && pEList->a[i].eEName==ENAME_NAME ){
+ /* An AS clause always takes first priority */
+ char *zName = pEList->a[i].zEName;
+ tdsqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_TRANSIENT);
+ }else if( srcName && p->op==TK_COLUMN ){
char *zCol;
int iCol = p->iColumn;
- for(j=0; ALWAYS(j<pTabList->nSrc); j++){
- if( pTabList->a[j].iCursor==p->iTable ) break;
- }
- assert( j<pTabList->nSrc );
- pTab = pTabList->a[j].pTab;
+ pTab = p->y.pTab;
+ assert( pTab!=0 );
if( iCol<0 ) iCol = pTab->iPKey;
assert( iCol==-1 || (iCol>=0 && iCol<pTab->nCol) );
if( iCol<0 ){
@@ -119073,20 +133416,17 @@ static void generateColumnNames(
}else{
zCol = pTab->aCol[iCol].zName;
}
- if( !shortNames && !fullNames ){
- sqlite3VdbeSetColName(v, i, COLNAME_NAME,
- sqlite3DbStrDup(db, pEList->a[i].zSpan), SQLITE_DYNAMIC);
- }else if( fullNames ){
+ if( fullName ){
char *zName = 0;
- zName = sqlite3MPrintf(db, "%s.%s", pTab->zName, zCol);
- sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_DYNAMIC);
+ zName = tdsqlite3MPrintf(db, "%s.%s", pTab->zName, zCol);
+ tdsqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_DYNAMIC);
}else{
- sqlite3VdbeSetColName(v, i, COLNAME_NAME, zCol, SQLITE_TRANSIENT);
+ tdsqlite3VdbeSetColName(v, i, COLNAME_NAME, zCol, SQLITE_TRANSIENT);
}
}else{
- const char *z = pEList->a[i].zSpan;
- z = z==0 ? sqlite3MPrintf(db, "column%d", i+1) : sqlite3DbStrDup(db, z);
- sqlite3VdbeSetColName(v, i, COLNAME_NAME, z, SQLITE_DYNAMIC);
+ const char *z = pEList->a[i].zEName;
+ z = z==0 ? tdsqlite3MPrintf(db, "column%d", i+1) : tdsqlite3DbStrDup(db, z);
+ tdsqlite3VdbeSetColName(v, i, COLNAME_NAME, z, SQLITE_DYNAMIC);
}
}
generateColumnTypes(pParse, pTabList, pEList);
@@ -119104,28 +133444,37 @@ static void generateColumnNames(
**
** Return SQLITE_OK on success. If a memory allocation error occurs,
** store NULL in *paCol and 0 in *pnCol and return SQLITE_NOMEM.
+**
+** The only guarantee that SQLite makes about column names is that if the
+** column has an AS clause assigning it a name, that will be the name used.
+** That is the only documented guarantee. However, countless applications
+** developed over the years have made baseless assumptions about column names
+** and will break if those assumptions changes. Hence, use extreme caution
+** when modifying this routine to avoid breaking legacy.
+**
+** See Also: generateColumnNames()
*/
-SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
+SQLITE_PRIVATE int tdsqlite3ColumnsFromExprList(
Parse *pParse, /* Parsing context */
ExprList *pEList, /* Expr list from which to derive column names */
i16 *pnCol, /* Write the number of columns here */
Column **paCol /* Write the new column list here */
){
- sqlite3 *db = pParse->db; /* Database connection */
+ tdsqlite3 *db = pParse->db; /* Database connection */
int i, j; /* Loop counters */
u32 cnt; /* Index added to make the name unique */
Column *aCol, *pCol; /* For looping over result columns */
int nCol; /* Number of columns in the result set */
- Expr *p; /* Expression for a single result column */
char *zName; /* Column name */
int nName; /* Size of name in zName[] */
Hash ht; /* Hash table of column names */
- sqlite3HashInit(&ht);
+ tdsqlite3HashInit(&ht);
if( pEList ){
nCol = pEList->nExpr;
- aCol = sqlite3DbMallocZero(db, sizeof(aCol[0])*nCol);
+ aCol = tdsqlite3DbMallocZero(db, sizeof(aCol[0])*nCol);
testcase( aCol==0 );
+ if( nCol>32767 ) nCol = 32767;
}else{
nCol = 0;
aCol = 0;
@@ -119137,20 +133486,19 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
for(i=0, pCol=aCol; i<nCol && !db->mallocFailed; i++, pCol++){
/* Get an appropriate name for the column
*/
- p = sqlite3ExprSkipCollate(pEList->a[i].pExpr);
- if( (zName = pEList->a[i].zName)!=0 ){
+ if( (zName = pEList->a[i].zEName)!=0 && pEList->a[i].eEName==ENAME_NAME ){
/* If the column contains an "AS <name>" phrase, use <name> as the name */
}else{
- Expr *pColExpr = p; /* The expression that is the result column name */
- Table *pTab; /* Table associated with this expression */
+ Expr *pColExpr = tdsqlite3ExprSkipCollateAndLikely(pEList->a[i].pExpr);
while( pColExpr->op==TK_DOT ){
pColExpr = pColExpr->pRight;
assert( pColExpr!=0 );
}
- if( pColExpr->op==TK_COLUMN && ALWAYS(pColExpr->pTab!=0) ){
+ if( pColExpr->op==TK_COLUMN ){
/* For columns use the column name name */
int iCol = pColExpr->iColumn;
- pTab = pColExpr->pTab;
+ Table *pTab = pColExpr->y.pTab;
+ assert( pTab!=0 );
if( iCol<0 ) iCol = pTab->iPKey;
zName = iCol>=0 ? pTab->aCol[iCol].zName : "rowid";
}else if( pColExpr->op==TK_ID ){
@@ -119158,36 +133506,40 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
zName = pColExpr->u.zToken;
}else{
/* Use the original text of the column expression as its name */
- zName = pEList->a[i].zSpan;
+ zName = pEList->a[i].zEName;
}
}
- zName = sqlite3MPrintf(db, "%s", zName);
+ if( zName && !tdsqlite3IsTrueOrFalse(zName) ){
+ zName = tdsqlite3DbStrDup(db, zName);
+ }else{
+ zName = tdsqlite3MPrintf(db,"column%d",i+1);
+ }
/* Make sure the column name is unique. If the name is not unique,
** append an integer to the name so that it becomes unique.
*/
cnt = 0;
- while( zName && sqlite3HashFind(&ht, zName)!=0 ){
- nName = sqlite3Strlen30(zName);
+ while( zName && tdsqlite3HashFind(&ht, zName)!=0 ){
+ nName = tdsqlite3Strlen30(zName);
if( nName>0 ){
- for(j=nName-1; j>0 && sqlite3Isdigit(zName[j]); j--){}
+ for(j=nName-1; j>0 && tdsqlite3Isdigit(zName[j]); j--){}
if( zName[j]==':' ) nName = j;
}
- zName = sqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt);
- if( cnt>3 ) sqlite3_randomness(sizeof(cnt), &cnt);
+ zName = tdsqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt);
+ if( cnt>3 ) tdsqlite3_randomness(sizeof(cnt), &cnt);
}
pCol->zName = zName;
- sqlite3ColumnPropertiesFromName(0, pCol);
- if( zName && sqlite3HashInsert(&ht, zName, pCol)==pCol ){
- sqlite3OomFault(db);
+ tdsqlite3ColumnPropertiesFromName(0, pCol);
+ if( zName && tdsqlite3HashInsert(&ht, zName, pCol)==pCol ){
+ tdsqlite3OomFault(db);
}
}
- sqlite3HashClear(&ht);
+ tdsqlite3HashClear(&ht);
if( db->mallocFailed ){
for(j=0; j<i; j++){
- sqlite3DbFree(db, aCol[j].zName);
+ tdsqlite3DbFree(db, aCol[j].zName);
}
- sqlite3DbFree(db, aCol);
+ tdsqlite3DbFree(db, aCol);
*paCol = 0;
*pnCol = 0;
return SQLITE_NOMEM_BKPT;
@@ -119206,19 +133558,19 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList(
** This routine requires that all identifiers in the SELECT
** statement be resolved.
*/
-SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation(
+SQLITE_PRIVATE void tdsqlite3SelectAddColumnTypeAndCollation(
Parse *pParse, /* Parsing contexts */
Table *pTab, /* Add column type information to this table */
- Select *pSelect /* SELECT used to determine types and collations */
+ Select *pSelect, /* SELECT used to determine types and collations */
+ char aff /* Default affinity for columns */
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
NameContext sNC;
Column *pCol;
CollSeq *pColl;
int i;
Expr *p;
struct ExprList_item *a;
- u64 szAll = 0;
assert( pSelect!=0 );
assert( (pSelect->selFlags & SF_Resolved)!=0 );
@@ -119231,57 +133583,55 @@ SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation(
const char *zType;
int n, m;
p = a[i].pExpr;
- zType = columnType(&sNC, p, 0, 0, 0, &pCol->szEst);
- szAll += pCol->szEst;
- pCol->affinity = sqlite3ExprAffinity(p);
- if( zType && (m = sqlite3Strlen30(zType))>0 ){
- n = sqlite3Strlen30(pCol->zName);
- pCol->zName = sqlite3DbReallocOrFree(db, pCol->zName, n+m+2);
+ zType = columnType(&sNC, p, 0, 0, 0);
+ /* pCol->szEst = ... // Column size est for SELECT tables never used */
+ pCol->affinity = tdsqlite3ExprAffinity(p);
+ if( zType ){
+ m = tdsqlite3Strlen30(zType);
+ n = tdsqlite3Strlen30(pCol->zName);
+ pCol->zName = tdsqlite3DbReallocOrFree(db, pCol->zName, n+m+2);
if( pCol->zName ){
memcpy(&pCol->zName[n+1], zType, m+1);
pCol->colFlags |= COLFLAG_HASTYPE;
}
}
- if( pCol->affinity==0 ) pCol->affinity = SQLITE_AFF_BLOB;
- pColl = sqlite3ExprCollSeq(pParse, p);
+ if( pCol->affinity<=SQLITE_AFF_NONE ) pCol->affinity = aff;
+ pColl = tdsqlite3ExprCollSeq(pParse, p);
if( pColl && pCol->zColl==0 ){
- pCol->zColl = sqlite3DbStrDup(db, pColl->zName);
+ pCol->zColl = tdsqlite3DbStrDup(db, pColl->zName);
}
}
- pTab->szTabRow = sqlite3LogEst(szAll*4);
+ pTab->szTabRow = 1; /* Any non-zero value works */
}
/*
** Given a SELECT statement, generate a Table structure that describes
** the result set of that SELECT.
*/
-SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){
+SQLITE_PRIVATE Table *tdsqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect, char aff){
Table *pTab;
- sqlite3 *db = pParse->db;
- int savedFlags;
+ tdsqlite3 *db = pParse->db;
+ u64 savedFlags;
savedFlags = db->flags;
- db->flags &= ~SQLITE_FullColNames;
+ db->flags &= ~(u64)SQLITE_FullColNames;
db->flags |= SQLITE_ShortColNames;
- sqlite3SelectPrep(pParse, pSelect, 0);
+ tdsqlite3SelectPrep(pParse, pSelect, 0);
+ db->flags = savedFlags;
if( pParse->nErr ) return 0;
while( pSelect->pPrior ) pSelect = pSelect->pPrior;
- db->flags = savedFlags;
- pTab = sqlite3DbMallocZero(db, sizeof(Table) );
+ pTab = tdsqlite3DbMallocZero(db, sizeof(Table) );
if( pTab==0 ){
return 0;
}
- /* The sqlite3ResultSetOfSelect() is only used n contexts where lookaside
- ** is disabled */
- assert( db->lookaside.bDisable );
- pTab->nRef = 1;
+ pTab->nTabRef = 1;
pTab->zName = 0;
- pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
- sqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol);
- sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSelect);
+ pTab->nRowLogEst = 200; assert( 200==tdsqlite3LogEst(1048576) );
+ tdsqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol);
+ tdsqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSelect, aff);
pTab->iPKey = -1;
if( db->mallocFailed ){
- sqlite3DeleteTable(db, pTab);
+ tdsqlite3DeleteTable(db, pTab);
return 0;
}
return pTab;
@@ -119291,25 +133641,22 @@ SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){
** Get a VDBE for the given parser context. Create a new one if necessary.
** If an error occurs, return NULL and leave a message in pParse.
*/
-static SQLITE_NOINLINE Vdbe *allocVdbe(Parse *pParse){
- Vdbe *v = pParse->pVdbe = sqlite3VdbeCreate(pParse);
- if( v ) sqlite3VdbeAddOp2(v, OP_Init, 0, 1);
+SQLITE_PRIVATE Vdbe *tdsqlite3GetVdbe(Parse *pParse){
+ if( pParse->pVdbe ){
+ return pParse->pVdbe;
+ }
if( pParse->pToplevel==0
&& OptimizationEnabled(pParse->db,SQLITE_FactorOutConst)
){
pParse->okConstFactor = 1;
}
- return v;
-}
-SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){
- Vdbe *v = pParse->pVdbe;
- return v ? v : allocVdbe(pParse);
+ return tdsqlite3VdbeCreate(pParse);
}
/*
** Compute the iLimit and iOffset fields of the SELECT based on the
-** pLimit and pOffset expressions. pLimit and pOffset hold the expressions
+** pLimit expressions. pLimit->pLeft and pLimit->pRight hold the expressions
** that appear in the original SQL statement after the LIMIT and OFFSET
** keywords. Or NULL if those keywords are omitted. iLimit and iOffset
** are the integer memory register numbers for counters used to compute
@@ -119317,15 +133664,15 @@ SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){
** iLimit and iOffset are negative.
**
** This routine changes the values of iLimit and iOffset only if
-** a limit or offset is defined by pLimit and pOffset. iLimit and
-** iOffset should have been preset to appropriate default values (zero)
+** a limit or offset is defined by pLimit->pLeft and pLimit->pRight. iLimit
+** and iOffset should have been preset to appropriate default values (zero)
** prior to calling this routine.
**
** The iOffset register (if it exists) is initialized to the value
** of the OFFSET. The iLimit register is initialized to LIMIT. Register
** iOffset+1 is initialized to LIMIT+OFFSET.
**
-** Only if pLimit!=0 or pOffset!=0 do the limit registers get
+** Only if pLimit->pLeft!=0 do the limit registers get
** redefined. The UNION ALL operator uses this property to force
** the reuse of the same limit and offset registers across multiple
** SELECT statements.
@@ -119335,6 +133682,8 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){
int iLimit = 0;
int iOffset;
int n;
+ Expr *pLimit = p->pLimit;
+
if( p->iLimit ) return;
/*
@@ -119343,34 +133692,34 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){
** The current implementation interprets "LIMIT 0" to mean
** no rows.
*/
- sqlite3ExprCacheClear(pParse);
- assert( p->pOffset==0 || p->pLimit!=0 );
- if( p->pLimit ){
+ if( pLimit ){
+ assert( pLimit->op==TK_LIMIT );
+ assert( pLimit->pLeft!=0 );
p->iLimit = iLimit = ++pParse->nMem;
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
assert( v!=0 );
- if( sqlite3ExprIsInteger(p->pLimit, &n) ){
- sqlite3VdbeAddOp2(v, OP_Integer, n, iLimit);
+ if( tdsqlite3ExprIsInteger(pLimit->pLeft, &n) ){
+ tdsqlite3VdbeAddOp2(v, OP_Integer, n, iLimit);
VdbeComment((v, "LIMIT counter"));
if( n==0 ){
- sqlite3VdbeGoto(v, iBreak);
- }else if( n>=0 && p->nSelectRow>sqlite3LogEst((u64)n) ){
- p->nSelectRow = sqlite3LogEst((u64)n);
+ tdsqlite3VdbeGoto(v, iBreak);
+ }else if( n>=0 && p->nSelectRow>tdsqlite3LogEst((u64)n) ){
+ p->nSelectRow = tdsqlite3LogEst((u64)n);
p->selFlags |= SF_FixedLimit;
}
}else{
- sqlite3ExprCode(pParse, p->pLimit, iLimit);
- sqlite3VdbeAddOp1(v, OP_MustBeInt, iLimit); VdbeCoverage(v);
+ tdsqlite3ExprCode(pParse, pLimit->pLeft, iLimit);
+ tdsqlite3VdbeAddOp1(v, OP_MustBeInt, iLimit); VdbeCoverage(v);
VdbeComment((v, "LIMIT counter"));
- sqlite3VdbeAddOp2(v, OP_IfNot, iLimit, iBreak); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_IfNot, iLimit, iBreak); VdbeCoverage(v);
}
- if( p->pOffset ){
+ if( pLimit->pRight ){
p->iOffset = iOffset = ++pParse->nMem;
pParse->nMem++; /* Allocate an extra register for limit+offset */
- sqlite3ExprCode(pParse, p->pOffset, iOffset);
- sqlite3VdbeAddOp1(v, OP_MustBeInt, iOffset); VdbeCoverage(v);
+ tdsqlite3ExprCode(pParse, pLimit->pRight, iOffset);
+ tdsqlite3VdbeAddOp1(v, OP_MustBeInt, iOffset); VdbeCoverage(v);
VdbeComment((v, "OFFSET counter"));
- sqlite3VdbeAddOp3(v, OP_OffsetLimit, iLimit, iOffset+1, iOffset);
+ tdsqlite3VdbeAddOp3(v, OP_OffsetLimit, iLimit, iOffset+1, iOffset);
VdbeComment((v, "LIMIT+OFFSET"));
}
}
@@ -119397,7 +133746,7 @@ static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){
** have been thrown during name resolution and we would not have gotten
** this far */
if( pRet==0 && ALWAYS(iCol<p->pEList->nExpr) ){
- pRet = sqlite3ExprCollSeq(pParse, p->pEList->a[iCol].pExpr);
+ pRet = tdsqlite3ExprCollSeq(pParse, p->pEList->a[iCol].pExpr);
}
return pRet;
}
@@ -119414,8 +133763,8 @@ static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){
static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){
ExprList *pOrderBy = p->pOrderBy;
int nOrderBy = p->pOrderBy->nExpr;
- sqlite3 *db = pParse->db;
- KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1);
+ tdsqlite3 *db = pParse->db;
+ KeyInfo *pRet = tdsqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1);
if( pRet ){
int i;
for(i=0; i<nOrderBy; i++){
@@ -119424,16 +133773,16 @@ static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){
CollSeq *pColl;
if( pTerm->flags & EP_Collate ){
- pColl = sqlite3ExprCollSeq(pParse, pTerm);
+ pColl = tdsqlite3ExprCollSeq(pParse, pTerm);
}else{
pColl = multiSelectCollSeq(pParse, p, pItem->u.x.iOrderByCol-1);
if( pColl==0 ) pColl = db->pDfltColl;
pOrderBy->a[i].pExpr =
- sqlite3ExprAddCollateString(pParse, pTerm, pColl->zName);
+ tdsqlite3ExprAddCollateString(pParse, pTerm, pColl->zName);
}
- assert( sqlite3KeyInfoIsWriteable(pRet) );
+ assert( tdsqlite3KeyInfoIsWriteable(pRet) );
pRet->aColl[i] = pColl;
- pRet->aSortOrder[i] = pOrderBy->a[i].sortOrder;
+ pRet->aSortFlags[i] = pOrderBy->a[i].sortFlags;
}
}
@@ -119497,20 +133846,27 @@ static void generateWithRecursiveQuery(
int i; /* Loop counter */
int rc; /* Result code */
ExprList *pOrderBy; /* The ORDER BY clause */
- Expr *pLimit, *pOffset; /* Saved LIMIT and OFFSET */
+ Expr *pLimit; /* Saved LIMIT and OFFSET */
int regLimit, regOffset; /* Registers used by LIMIT and OFFSET */
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( p->pWin ){
+ tdsqlite3ErrorMsg(pParse, "cannot use window functions in recursive queries");
+ return;
+ }
+#endif
+
/* Obtain authorization to do a recursive query */
- if( sqlite3AuthCheck(pParse, SQLITE_RECURSIVE, 0, 0, 0) ) return;
+ if( tdsqlite3AuthCheck(pParse, SQLITE_RECURSIVE, 0, 0, 0) ) return;
/* Process the LIMIT and OFFSET clauses, if they exist */
- addrBreak = sqlite3VdbeMakeLabel(v);
+ addrBreak = tdsqlite3VdbeMakeLabel(pParse);
+ p->nSelectRow = 320; /* 4 billion rows */
computeLimitRegisters(pParse, p, addrBreak);
pLimit = p->pLimit;
- pOffset = p->pOffset;
regLimit = p->iLimit;
regOffset = p->iOffset;
- p->pLimit = p->pOffset = 0;
+ p->pLimit = 0;
p->iLimit = p->iOffset = 0;
pOrderBy = p->pOrderBy;
@@ -119532,22 +133888,22 @@ static void generateWithRecursiveQuery(
}else{
eDest = pOrderBy ? SRT_Queue : SRT_Fifo;
}
- sqlite3SelectDestInit(&destQueue, eDest, iQueue);
+ tdsqlite3SelectDestInit(&destQueue, eDest, iQueue);
/* Allocate cursors for Current, Queue, and Distinct. */
regCurrent = ++pParse->nMem;
- sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol);
+ tdsqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol);
if( pOrderBy ){
KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1);
- sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iQueue, pOrderBy->nExpr+2, 0,
+ tdsqlite3VdbeAddOp4(v, OP_OpenEphemeral, iQueue, pOrderBy->nExpr+2, 0,
(char*)pKeyInfo, P4_KEYINFO);
destQueue.pOrderBy = pOrderBy;
}else{
- sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iQueue, nCol);
+ tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, iQueue, nCol);
}
VdbeComment((v, "Queue table"));
if( iDistinct ){
- p->addrOpenEphm[0] = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iDistinct, 0);
+ p->addrOpenEphm[0] = tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, iDistinct, 0);
p->selFlags |= SF_UsesEphemeral;
}
@@ -119556,54 +133912,55 @@ static void generateWithRecursiveQuery(
/* Store the results of the setup-query in Queue. */
pSetup->pNext = 0;
- rc = sqlite3Select(pParse, pSetup, &destQueue);
+ ExplainQueryPlan((pParse, 1, "SETUP"));
+ rc = tdsqlite3Select(pParse, pSetup, &destQueue);
pSetup->pNext = p;
if( rc ) goto end_of_recursive_query;
/* Find the next row in the Queue and output that row */
- addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iQueue, addrBreak); VdbeCoverage(v);
+ addrTop = tdsqlite3VdbeAddOp2(v, OP_Rewind, iQueue, addrBreak); VdbeCoverage(v);
/* Transfer the next row in Queue over to Current */
- sqlite3VdbeAddOp1(v, OP_NullRow, iCurrent); /* To reset column cache */
+ tdsqlite3VdbeAddOp1(v, OP_NullRow, iCurrent); /* To reset column cache */
if( pOrderBy ){
- sqlite3VdbeAddOp3(v, OP_Column, iQueue, pOrderBy->nExpr+1, regCurrent);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iQueue, pOrderBy->nExpr+1, regCurrent);
}else{
- sqlite3VdbeAddOp2(v, OP_RowData, iQueue, regCurrent);
+ tdsqlite3VdbeAddOp2(v, OP_RowData, iQueue, regCurrent);
}
- sqlite3VdbeAddOp1(v, OP_Delete, iQueue);
+ tdsqlite3VdbeAddOp1(v, OP_Delete, iQueue);
/* Output the single row in Current */
- addrCont = sqlite3VdbeMakeLabel(v);
+ addrCont = tdsqlite3VdbeMakeLabel(pParse);
codeOffset(v, regOffset, addrCont);
- selectInnerLoop(pParse, p, p->pEList, iCurrent,
+ selectInnerLoop(pParse, p, iCurrent,
0, 0, pDest, addrCont, addrBreak);
if( regLimit ){
- sqlite3VdbeAddOp2(v, OP_DecrJumpZero, regLimit, addrBreak);
+ tdsqlite3VdbeAddOp2(v, OP_DecrJumpZero, regLimit, addrBreak);
VdbeCoverage(v);
}
- sqlite3VdbeResolveLabel(v, addrCont);
+ tdsqlite3VdbeResolveLabel(v, addrCont);
/* Execute the recursive SELECT taking the single row in Current as
** the value for the recursive-table. Store the results in the Queue.
*/
if( p->selFlags & SF_Aggregate ){
- sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported");
+ tdsqlite3ErrorMsg(pParse, "recursive aggregate queries not supported");
}else{
p->pPrior = 0;
- sqlite3Select(pParse, p, &destQueue);
+ ExplainQueryPlan((pParse, 1, "RECURSIVE STEP"));
+ tdsqlite3Select(pParse, p, &destQueue);
assert( p->pPrior==0 );
p->pPrior = pSetup;
}
/* Keep running the loop until the Queue is empty */
- sqlite3VdbeGoto(v, addrTop);
- sqlite3VdbeResolveLabel(v, addrBreak);
+ tdsqlite3VdbeGoto(v, addrTop);
+ tdsqlite3VdbeResolveLabel(v, addrBreak);
end_of_recursive_query:
- sqlite3ExprListDelete(pParse->db, p->pOrderBy);
+ tdsqlite3ExprListDelete(pParse->db, p->pOrderBy);
p->pOrderBy = pOrderBy;
p->pLimit = pLimit;
- p->pOffset = pOffset;
return;
}
#endif /* SQLITE_OMIT_CTE */
@@ -119622,36 +133979,41 @@ static int multiSelectOrderBy(
** on a VALUES clause.
**
** Because the Select object originates from a VALUES clause:
-** (1) It has no LIMIT or OFFSET
+** (1) There is no LIMIT or OFFSET or else there is a LIMIT of exactly 1
** (2) All terms are UNION ALL
** (3) There is no ORDER BY clause
+**
+** The "LIMIT of exactly 1" case of condition (1) comes about when a VALUES
+** clause occurs within scalar expression (ex: "SELECT (VALUES(1),(2),(3))").
+** The tdsqlite3CodeSubselect will have added the LIMIT 1 clause in tht case.
+** Since the limit is exactly 1, we only need to evalutes the left-most VALUES.
*/
static int multiSelectValues(
Parse *pParse, /* Parsing context */
Select *p, /* The right-most of SELECTs to be coded */
SelectDest *pDest /* What to do with query results */
){
- Select *pPrior;
int nRow = 1;
int rc = 0;
+ int bShowAll = p->pLimit==0;
assert( p->selFlags & SF_MultiValue );
do{
assert( p->selFlags & SF_Values );
assert( p->op==TK_ALL || (p->op==TK_SELECT && p->pPrior==0) );
- assert( p->pLimit==0 );
- assert( p->pOffset==0 );
assert( p->pNext==0 || p->pEList->nExpr==p->pNext->pEList->nExpr );
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( p->pWin ) return -1;
+#endif
if( p->pPrior==0 ) break;
assert( p->pPrior->pNext==p );
p = p->pPrior;
- nRow++;
+ nRow += bShowAll;
}while(1);
+ ExplainQueryPlan((pParse, 0, "SCAN %d CONSTANT ROW%s", nRow,
+ nRow==1 ? "" : "S"));
while( p ){
- pPrior = p->pPrior;
- p->pPrior = 0;
- rc = sqlite3Select(pParse, p, pDest);
- p->pPrior = pPrior;
- if( rc ) break;
+ selectInnerLoop(pParse, p, -1, 0, 0, pDest, 1, 1);
+ if( !bShowAll ) break;
p->nSelectRow = nRow;
p = p->pNext;
}
@@ -119699,41 +134061,32 @@ static int multiSelect(
Vdbe *v; /* Generate code to this VDBE */
SelectDest dest; /* Alternative data destination */
Select *pDelete = 0; /* Chain of simple selects to delete */
- sqlite3 *db; /* Database connection */
-#ifndef SQLITE_OMIT_EXPLAIN
- int iSub1 = 0; /* EQP id of left-hand query */
- int iSub2 = 0; /* EQP id of right-hand query */
-#endif
+ tdsqlite3 *db; /* Database connection */
/* Make sure there is no ORDER BY or LIMIT clause on prior SELECTs. Only
** the last (right-most) SELECT in the series may have an ORDER BY or LIMIT.
*/
assert( p && p->pPrior ); /* Calling function guarantees this much */
assert( (p->selFlags & SF_Recursive)==0 || p->op==TK_ALL || p->op==TK_UNION );
+ assert( p->selFlags & SF_Compound );
db = pParse->db;
pPrior = p->pPrior;
dest = *pDest;
- if( pPrior->pOrderBy ){
- sqlite3ErrorMsg(pParse,"ORDER BY clause should come after %s not before",
- selectOpName(p->op));
- rc = 1;
- goto multi_select_end;
- }
- if( pPrior->pLimit ){
- sqlite3ErrorMsg(pParse,"LIMIT clause should come after %s not before",
- selectOpName(p->op));
+ if( pPrior->pOrderBy || pPrior->pLimit ){
+ tdsqlite3ErrorMsg(pParse,"%s clause should come after %s not before",
+ pPrior->pOrderBy!=0 ? "ORDER BY" : "LIMIT", selectOpName(p->op));
rc = 1;
goto multi_select_end;
}
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
assert( v!=0 ); /* The VDBE already created by calling function */
/* Create the destination temporary table if necessary
*/
if( dest.eDest==SRT_EphemTab ){
assert( p->pEList );
- sqlite3VdbeAddOp2(v, OP_OpenEphemeral, dest.iSDParm, p->pEList->nExpr);
+ tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, dest.iSDParm, p->pEList->nExpr);
dest.eDest = SRT_Table;
}
@@ -119741,7 +134094,8 @@ static int multiSelect(
*/
if( p->selFlags & SF_MultiValue ){
rc = multiSelectValues(pParse, p, &dest);
- goto multi_select_end;
+ if( rc>=0 ) goto multi_select_end;
+ rc = SQLITE_OK;
}
/* Make sure all SELECTs in the statement have the same number of elements
@@ -119760,236 +134114,232 @@ static int multiSelect(
*/
if( p->pOrderBy ){
return multiSelectOrderBy(pParse, p, pDest);
- }else
+ }else{
- /* Generate code for the left and right SELECT statements.
- */
- switch( p->op ){
- case TK_ALL: {
- int addr = 0;
- int nLimit;
- assert( !pPrior->pLimit );
- pPrior->iLimit = p->iLimit;
- pPrior->iOffset = p->iOffset;
- pPrior->pLimit = p->pLimit;
- pPrior->pOffset = p->pOffset;
- explainSetInteger(iSub1, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, pPrior, &dest);
- p->pLimit = 0;
- p->pOffset = 0;
- if( rc ){
- goto multi_select_end;
- }
- p->pPrior = 0;
- p->iLimit = pPrior->iLimit;
- p->iOffset = pPrior->iOffset;
- if( p->iLimit ){
- addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v);
- VdbeComment((v, "Jump ahead if LIMIT reached"));
- if( p->iOffset ){
- sqlite3VdbeAddOp3(v, OP_OffsetLimit,
- p->iLimit, p->iOffset+1, p->iOffset);
+#ifndef SQLITE_OMIT_EXPLAIN
+ if( pPrior->pPrior==0 ){
+ ExplainQueryPlan((pParse, 1, "COMPOUND QUERY"));
+ ExplainQueryPlan((pParse, 1, "LEFT-MOST SUBQUERY"));
+ }
+#endif
+
+ /* Generate code for the left and right SELECT statements.
+ */
+ switch( p->op ){
+ case TK_ALL: {
+ int addr = 0;
+ int nLimit;
+ assert( !pPrior->pLimit );
+ pPrior->iLimit = p->iLimit;
+ pPrior->iOffset = p->iOffset;
+ pPrior->pLimit = p->pLimit;
+ rc = tdsqlite3Select(pParse, pPrior, &dest);
+ p->pLimit = 0;
+ if( rc ){
+ goto multi_select_end;
+ }
+ p->pPrior = 0;
+ p->iLimit = pPrior->iLimit;
+ p->iOffset = pPrior->iOffset;
+ if( p->iLimit ){
+ addr = tdsqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v);
+ VdbeComment((v, "Jump ahead if LIMIT reached"));
+ if( p->iOffset ){
+ tdsqlite3VdbeAddOp3(v, OP_OffsetLimit,
+ p->iLimit, p->iOffset+1, p->iOffset);
+ }
}
+ ExplainQueryPlan((pParse, 1, "UNION ALL"));
+ rc = tdsqlite3Select(pParse, p, &dest);
+ testcase( rc!=SQLITE_OK );
+ pDelete = p->pPrior;
+ p->pPrior = pPrior;
+ p->nSelectRow = tdsqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
+ if( pPrior->pLimit
+ && tdsqlite3ExprIsInteger(pPrior->pLimit->pLeft, &nLimit)
+ && nLimit>0 && p->nSelectRow > tdsqlite3LogEst((u64)nLimit)
+ ){
+ p->nSelectRow = tdsqlite3LogEst((u64)nLimit);
+ }
+ if( addr ){
+ tdsqlite3VdbeJumpHere(v, addr);
+ }
+ break;
}
- explainSetInteger(iSub2, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, p, &dest);
- testcase( rc!=SQLITE_OK );
- pDelete = p->pPrior;
- p->pPrior = pPrior;
- p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
- if( pPrior->pLimit
- && sqlite3ExprIsInteger(pPrior->pLimit, &nLimit)
- && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit)
- ){
- p->nSelectRow = sqlite3LogEst((u64)nLimit);
- }
- if( addr ){
- sqlite3VdbeJumpHere(v, addr);
- }
- break;
- }
- case TK_EXCEPT:
- case TK_UNION: {
- int unionTab; /* Cursor number of the temporary table holding result */
- u8 op = 0; /* One of the SRT_ operations to apply to self */
- int priorOp; /* The SRT_ operation to apply to prior selects */
- Expr *pLimit, *pOffset; /* Saved values of p->nLimit and p->nOffset */
- int addr;
- SelectDest uniondest;
-
- testcase( p->op==TK_EXCEPT );
- testcase( p->op==TK_UNION );
- priorOp = SRT_Union;
- if( dest.eDest==priorOp ){
- /* We can reuse a temporary table generated by a SELECT to our
- ** right.
+ case TK_EXCEPT:
+ case TK_UNION: {
+ int unionTab; /* Cursor number of the temp table holding result */
+ u8 op = 0; /* One of the SRT_ operations to apply to self */
+ int priorOp; /* The SRT_ operation to apply to prior selects */
+ Expr *pLimit; /* Saved values of p->nLimit */
+ int addr;
+ SelectDest uniondest;
+
+ testcase( p->op==TK_EXCEPT );
+ testcase( p->op==TK_UNION );
+ priorOp = SRT_Union;
+ if( dest.eDest==priorOp ){
+ /* We can reuse a temporary table generated by a SELECT to our
+ ** right.
+ */
+ assert( p->pLimit==0 ); /* Not allowed on leftward elements */
+ unionTab = dest.iSDParm;
+ }else{
+ /* We will need to create our own temporary table to hold the
+ ** intermediate results.
+ */
+ unionTab = pParse->nTab++;
+ assert( p->pOrderBy==0 );
+ addr = tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0);
+ assert( p->addrOpenEphm[0] == -1 );
+ p->addrOpenEphm[0] = addr;
+ findRightmost(p)->selFlags |= SF_UsesEphemeral;
+ assert( p->pEList );
+ }
+
+ /* Code the SELECT statements to our left
*/
- assert( p->pLimit==0 ); /* Not allowed on leftward elements */
- assert( p->pOffset==0 ); /* Not allowed on leftward elements */
- unionTab = dest.iSDParm;
- }else{
- /* We will need to create our own temporary table to hold the
- ** intermediate results.
+ assert( !pPrior->pOrderBy );
+ tdsqlite3SelectDestInit(&uniondest, priorOp, unionTab);
+ rc = tdsqlite3Select(pParse, pPrior, &uniondest);
+ if( rc ){
+ goto multi_select_end;
+ }
+
+ /* Code the current SELECT statement
*/
- unionTab = pParse->nTab++;
+ if( p->op==TK_EXCEPT ){
+ op = SRT_Except;
+ }else{
+ assert( p->op==TK_UNION );
+ op = SRT_Union;
+ }
+ p->pPrior = 0;
+ pLimit = p->pLimit;
+ p->pLimit = 0;
+ uniondest.eDest = op;
+ ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE",
+ selectOpName(p->op)));
+ rc = tdsqlite3Select(pParse, p, &uniondest);
+ testcase( rc!=SQLITE_OK );
+ /* Query flattening in tdsqlite3Select() might refill p->pOrderBy.
+ ** Be sure to delete p->pOrderBy, therefore, to avoid a memory leak. */
+ tdsqlite3ExprListDelete(db, p->pOrderBy);
+ pDelete = p->pPrior;
+ p->pPrior = pPrior;
+ p->pOrderBy = 0;
+ if( p->op==TK_UNION ){
+ p->nSelectRow = tdsqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
+ }
+ tdsqlite3ExprDelete(db, p->pLimit);
+ p->pLimit = pLimit;
+ p->iLimit = 0;
+ p->iOffset = 0;
+
+ /* Convert the data in the temporary table into whatever form
+ ** it is that we currently need.
+ */
+ assert( unionTab==dest.iSDParm || dest.eDest!=priorOp );
+ assert( p->pEList || db->mallocFailed );
+ if( dest.eDest!=priorOp && db->mallocFailed==0 ){
+ int iCont, iBreak, iStart;
+ iBreak = tdsqlite3VdbeMakeLabel(pParse);
+ iCont = tdsqlite3VdbeMakeLabel(pParse);
+ computeLimitRegisters(pParse, p, iBreak);
+ tdsqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v);
+ iStart = tdsqlite3VdbeCurrentAddr(v);
+ selectInnerLoop(pParse, p, unionTab,
+ 0, 0, &dest, iCont, iBreak);
+ tdsqlite3VdbeResolveLabel(v, iCont);
+ tdsqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v);
+ tdsqlite3VdbeResolveLabel(v, iBreak);
+ tdsqlite3VdbeAddOp2(v, OP_Close, unionTab, 0);
+ }
+ break;
+ }
+ default: assert( p->op==TK_INTERSECT ); {
+ int tab1, tab2;
+ int iCont, iBreak, iStart;
+ Expr *pLimit;
+ int addr;
+ SelectDest intersectdest;
+ int r1;
+
+ /* INTERSECT is different from the others since it requires
+ ** two temporary tables. Hence it has its own case. Begin
+ ** by allocating the tables we will need.
+ */
+ tab1 = pParse->nTab++;
+ tab2 = pParse->nTab++;
assert( p->pOrderBy==0 );
- addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0);
+
+ addr = tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0);
assert( p->addrOpenEphm[0] == -1 );
p->addrOpenEphm[0] = addr;
findRightmost(p)->selFlags |= SF_UsesEphemeral;
assert( p->pEList );
- }
-
- /* Code the SELECT statements to our left
- */
- assert( !pPrior->pOrderBy );
- sqlite3SelectDestInit(&uniondest, priorOp, unionTab);
- explainSetInteger(iSub1, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, pPrior, &uniondest);
- if( rc ){
- goto multi_select_end;
- }
-
- /* Code the current SELECT statement
- */
- if( p->op==TK_EXCEPT ){
- op = SRT_Except;
- }else{
- assert( p->op==TK_UNION );
- op = SRT_Union;
- }
- p->pPrior = 0;
- pLimit = p->pLimit;
- p->pLimit = 0;
- pOffset = p->pOffset;
- p->pOffset = 0;
- uniondest.eDest = op;
- explainSetInteger(iSub2, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, p, &uniondest);
- testcase( rc!=SQLITE_OK );
- /* Query flattening in sqlite3Select() might refill p->pOrderBy.
- ** Be sure to delete p->pOrderBy, therefore, to avoid a memory leak. */
- sqlite3ExprListDelete(db, p->pOrderBy);
- pDelete = p->pPrior;
- p->pPrior = pPrior;
- p->pOrderBy = 0;
- if( p->op==TK_UNION ){
- p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
- }
- sqlite3ExprDelete(db, p->pLimit);
- p->pLimit = pLimit;
- p->pOffset = pOffset;
- p->iLimit = 0;
- p->iOffset = 0;
-
- /* Convert the data in the temporary table into whatever form
- ** it is that we currently need.
- */
- assert( unionTab==dest.iSDParm || dest.eDest!=priorOp );
- if( dest.eDest!=priorOp ){
- int iCont, iBreak, iStart;
- assert( p->pEList );
- if( dest.eDest==SRT_Output ){
- Select *pFirst = p;
- while( pFirst->pPrior ) pFirst = pFirst->pPrior;
- generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList);
+
+ /* Code the SELECTs to our left into temporary table "tab1".
+ */
+ tdsqlite3SelectDestInit(&intersectdest, SRT_Union, tab1);
+ rc = tdsqlite3Select(pParse, pPrior, &intersectdest);
+ if( rc ){
+ goto multi_select_end;
}
- iBreak = sqlite3VdbeMakeLabel(v);
- iCont = sqlite3VdbeMakeLabel(v);
+
+ /* Code the current SELECT into temporary table "tab2"
+ */
+ addr = tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0);
+ assert( p->addrOpenEphm[1] == -1 );
+ p->addrOpenEphm[1] = addr;
+ p->pPrior = 0;
+ pLimit = p->pLimit;
+ p->pLimit = 0;
+ intersectdest.iSDParm = tab2;
+ ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE",
+ selectOpName(p->op)));
+ rc = tdsqlite3Select(pParse, p, &intersectdest);
+ testcase( rc!=SQLITE_OK );
+ pDelete = p->pPrior;
+ p->pPrior = pPrior;
+ if( p->nSelectRow>pPrior->nSelectRow ){
+ p->nSelectRow = pPrior->nSelectRow;
+ }
+ tdsqlite3ExprDelete(db, p->pLimit);
+ p->pLimit = pLimit;
+
+ /* Generate code to take the intersection of the two temporary
+ ** tables.
+ */
+ assert( p->pEList );
+ iBreak = tdsqlite3VdbeMakeLabel(pParse);
+ iCont = tdsqlite3VdbeMakeLabel(pParse);
computeLimitRegisters(pParse, p, iBreak);
- sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v);
- iStart = sqlite3VdbeCurrentAddr(v);
- selectInnerLoop(pParse, p, p->pEList, unionTab,
+ tdsqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v);
+ r1 = tdsqlite3GetTempReg(pParse);
+ iStart = tdsqlite3VdbeAddOp2(v, OP_RowData, tab1, r1);
+ tdsqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0);
+ VdbeCoverage(v);
+ tdsqlite3ReleaseTempReg(pParse, r1);
+ selectInnerLoop(pParse, p, tab1,
0, 0, &dest, iCont, iBreak);
- sqlite3VdbeResolveLabel(v, iCont);
- sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v);
- sqlite3VdbeResolveLabel(v, iBreak);
- sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0);
+ tdsqlite3VdbeResolveLabel(v, iCont);
+ tdsqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v);
+ tdsqlite3VdbeResolveLabel(v, iBreak);
+ tdsqlite3VdbeAddOp2(v, OP_Close, tab2, 0);
+ tdsqlite3VdbeAddOp2(v, OP_Close, tab1, 0);
+ break;
}
- break;
}
- default: assert( p->op==TK_INTERSECT ); {
- int tab1, tab2;
- int iCont, iBreak, iStart;
- Expr *pLimit, *pOffset;
- int addr;
- SelectDest intersectdest;
- int r1;
-
- /* INTERSECT is different from the others since it requires
- ** two temporary tables. Hence it has its own case. Begin
- ** by allocating the tables we will need.
- */
- tab1 = pParse->nTab++;
- tab2 = pParse->nTab++;
- assert( p->pOrderBy==0 );
-
- addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0);
- assert( p->addrOpenEphm[0] == -1 );
- p->addrOpenEphm[0] = addr;
- findRightmost(p)->selFlags |= SF_UsesEphemeral;
- assert( p->pEList );
-
- /* Code the SELECTs to our left into temporary table "tab1".
- */
- sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1);
- explainSetInteger(iSub1, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, pPrior, &intersectdest);
- if( rc ){
- goto multi_select_end;
- }
-
- /* Code the current SELECT into temporary table "tab2"
- */
- addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0);
- assert( p->addrOpenEphm[1] == -1 );
- p->addrOpenEphm[1] = addr;
- p->pPrior = 0;
- pLimit = p->pLimit;
- p->pLimit = 0;
- pOffset = p->pOffset;
- p->pOffset = 0;
- intersectdest.iSDParm = tab2;
- explainSetInteger(iSub2, pParse->iNextSelectId);
- rc = sqlite3Select(pParse, p, &intersectdest);
- testcase( rc!=SQLITE_OK );
- pDelete = p->pPrior;
- p->pPrior = pPrior;
- if( p->nSelectRow>pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow;
- sqlite3ExprDelete(db, p->pLimit);
- p->pLimit = pLimit;
- p->pOffset = pOffset;
-
- /* Generate code to take the intersection of the two temporary
- ** tables.
- */
- assert( p->pEList );
- if( dest.eDest==SRT_Output ){
- Select *pFirst = p;
- while( pFirst->pPrior ) pFirst = pFirst->pPrior;
- generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList);
- }
- iBreak = sqlite3VdbeMakeLabel(v);
- iCont = sqlite3VdbeMakeLabel(v);
- computeLimitRegisters(pParse, p, iBreak);
- sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v);
- r1 = sqlite3GetTempReg(pParse);
- iStart = sqlite3VdbeAddOp2(v, OP_RowKey, tab1, r1);
- sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); VdbeCoverage(v);
- sqlite3ReleaseTempReg(pParse, r1);
- selectInnerLoop(pParse, p, p->pEList, tab1,
- 0, 0, &dest, iCont, iBreak);
- sqlite3VdbeResolveLabel(v, iCont);
- sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v);
- sqlite3VdbeResolveLabel(v, iBreak);
- sqlite3VdbeAddOp2(v, OP_Close, tab2, 0);
- sqlite3VdbeAddOp2(v, OP_Close, tab1, 0);
- break;
+
+ #ifndef SQLITE_OMIT_EXPLAIN
+ if( p->pNext==0 ){
+ ExplainQueryPlanPop(pParse);
}
+ #endif
}
-
- explainComposite(pParse, p->op, iSub1, iSub2, p->op!=TK_ALL);
-
+ if( pParse->nErr ) goto multi_select_end;
+
/* Compute collating sequences used by
** temporary tables needed to implement the compound select.
** Attach the KeyInfo structure to all temporary tables.
@@ -120008,7 +134358,7 @@ static int multiSelect(
assert( p->pNext==0 );
nCol = p->pEList->nExpr;
- pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1);
+ pKeyInfo = tdsqlite3KeyInfoAlloc(db, nCol, 1);
if( !pKeyInfo ){
rc = SQLITE_NOMEM_BKPT;
goto multi_select_end;
@@ -120029,19 +134379,19 @@ static int multiSelect(
assert( pLoop->addrOpenEphm[1]<0 );
break;
}
- sqlite3VdbeChangeP2(v, addr, nCol);
- sqlite3VdbeChangeP4(v, addr, (char*)sqlite3KeyInfoRef(pKeyInfo),
+ tdsqlite3VdbeChangeP2(v, addr, nCol);
+ tdsqlite3VdbeChangeP4(v, addr, (char*)tdsqlite3KeyInfoRef(pKeyInfo),
P4_KEYINFO);
pLoop->addrOpenEphm[i] = -1;
}
}
- sqlite3KeyInfoUnref(pKeyInfo);
+ tdsqlite3KeyInfoUnref(pKeyInfo);
}
multi_select_end:
pDest->iSdst = dest.iSdst;
pDest->nSdst = dest.nSdst;
- sqlite3SelectDelete(db, pDelete);
+ tdsqlite3SelectDelete(db, pDelete);
return rc;
}
#endif /* SQLITE_OMIT_COMPOUND_SELECT */
@@ -120050,11 +134400,11 @@ multi_select_end:
** Error message for when two or more terms of a compound select have different
** size result sets.
*/
-SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){
+SQLITE_PRIVATE void tdsqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){
if( p->selFlags & SF_Values ){
- sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms");
+ tdsqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms");
}else{
- sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s"
+ tdsqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s"
" do not have the same number of result columns", selectOpName(p->op));
}
}
@@ -120093,20 +134443,20 @@ static int generateOutputSubroutine(
int iContinue;
int addr;
- addr = sqlite3VdbeCurrentAddr(v);
- iContinue = sqlite3VdbeMakeLabel(v);
+ addr = tdsqlite3VdbeCurrentAddr(v);
+ iContinue = tdsqlite3VdbeMakeLabel(pParse);
/* Suppress duplicates for UNION, EXCEPT, and INTERSECT
*/
if( regPrev ){
int addr1, addr2;
- addr1 = sqlite3VdbeAddOp1(v, OP_IfNot, regPrev); VdbeCoverage(v);
- addr2 = sqlite3VdbeAddOp4(v, OP_Compare, pIn->iSdst, regPrev+1, pIn->nSdst,
- (char*)sqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO);
- sqlite3VdbeAddOp3(v, OP_Jump, addr2+2, iContinue, addr2+2); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp3(v, OP_Copy, pIn->iSdst, regPrev+1, pIn->nSdst-1);
- sqlite3VdbeAddOp2(v, OP_Integer, 1, regPrev);
+ addr1 = tdsqlite3VdbeAddOp1(v, OP_IfNot, regPrev); VdbeCoverage(v);
+ addr2 = tdsqlite3VdbeAddOp4(v, OP_Compare, pIn->iSdst, regPrev+1, pIn->nSdst,
+ (char*)tdsqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO);
+ tdsqlite3VdbeAddOp3(v, OP_Jump, addr2+2, iContinue, addr2+2); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addr1);
+ tdsqlite3VdbeAddOp3(v, OP_Copy, pIn->iSdst, regPrev+1, pIn->nSdst-1);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, regPrev);
}
if( pParse->db->mallocFailed ) return 0;
@@ -120120,14 +134470,14 @@ static int generateOutputSubroutine(
/* Store the result as data using a unique key.
*/
case SRT_EphemTab: {
- int r1 = sqlite3GetTempReg(pParse);
- int r2 = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r1);
- sqlite3VdbeAddOp2(v, OP_NewRowid, pDest->iSDParm, r2);
- sqlite3VdbeAddOp3(v, OP_Insert, pDest->iSDParm, r1, r2);
- sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
- sqlite3ReleaseTempReg(pParse, r2);
- sqlite3ReleaseTempReg(pParse, r1);
+ int r1 = tdsqlite3GetTempReg(pParse);
+ int r2 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r1);
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, pDest->iSDParm, r2);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, pDest->iSDParm, r1, r2);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_APPEND);
+ tdsqlite3ReleaseTempReg(pParse, r2);
+ tdsqlite3ReleaseTempReg(pParse, r1);
break;
}
@@ -120137,22 +134487,25 @@ static int generateOutputSubroutine(
case SRT_Set: {
int r1;
testcase( pIn->nSdst>1 );
- r1 = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst,
+ r1 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst,
r1, pDest->zAffSdst, pIn->nSdst);
- sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst, pIn->nSdst);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iSDParm, r1);
- sqlite3ReleaseTempReg(pParse, r1);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, pDest->iSDParm, r1,
+ pIn->iSdst, pIn->nSdst);
+ tdsqlite3ReleaseTempReg(pParse, r1);
break;
}
/* If this is a scalar select that is part of an expression, then
** store the results in the appropriate memory cell and break out
- ** of the scan loop.
+ ** of the scan loop. Note that the select might return multiple columns
+ ** if it is the RHS of a row-value IN operator.
*/
case SRT_Mem: {
- assert( pIn->nSdst==1 || pParse->nErr>0 ); testcase( pIn->nSdst!=1 );
- sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm, 1);
+ if( pParse->nErr==0 ){
+ testcase( pIn->nSdst>1 );
+ tdsqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm, pIn->nSdst);
+ }
/* The LIMIT clause will jump out of the loop for us */
break;
}
@@ -120163,11 +134516,11 @@ static int generateOutputSubroutine(
*/
case SRT_Coroutine: {
if( pDest->iSdst==0 ){
- pDest->iSdst = sqlite3GetTempRange(pParse, pIn->nSdst);
+ pDest->iSdst = tdsqlite3GetTempRange(pParse, pIn->nSdst);
pDest->nSdst = pIn->nSdst;
}
- sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSdst, pIn->nSdst);
- sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm);
+ tdsqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSdst, pIn->nSdst);
+ tdsqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm);
break;
}
@@ -120176,13 +134529,12 @@ static int generateOutputSubroutine(
** destination other than the ones handled above or SRT_Output.
**
** For SRT_Output, results are stored in a sequence of registers.
- ** Then the OP_ResultRow opcode is used to cause sqlite3_step() to
+ ** Then the OP_ResultRow opcode is used to cause tdsqlite3_step() to
** return the next row of result.
*/
default: {
assert( pDest->eDest==SRT_Output );
- sqlite3VdbeAddOp2(v, OP_ResultRow, pIn->iSdst, pIn->nSdst);
- sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst, pIn->nSdst);
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, pIn->iSdst, pIn->nSdst);
break;
}
}
@@ -120190,13 +134542,13 @@ static int generateOutputSubroutine(
/* Jump to the end of the loop if the LIMIT is reached.
*/
if( p->iLimit ){
- sqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_DecrJumpZero, p->iLimit, iBreak); VdbeCoverage(v);
}
/* Generate the subroutine return
*/
- sqlite3VdbeResolveLabel(v, iContinue);
- sqlite3VdbeAddOp1(v, OP_Return, regReturn);
+ tdsqlite3VdbeResolveLabel(v, iContinue);
+ tdsqlite3VdbeAddOp1(v, OP_Return, regReturn);
return addr;
}
@@ -120322,22 +134674,18 @@ static int multiSelectOrderBy(
int op; /* One of TK_ALL, TK_UNION, TK_EXCEPT, TK_INTERSECT */
KeyInfo *pKeyDup = 0; /* Comparison information for duplicate removal */
KeyInfo *pKeyMerge; /* Comparison information for merging rows */
- sqlite3 *db; /* Database connection */
+ tdsqlite3 *db; /* Database connection */
ExprList *pOrderBy; /* The ORDER BY clause */
int nOrderBy; /* Number of terms in the ORDER BY clause */
int *aPermute; /* Mapping from ORDER BY terms to result set columns */
-#ifndef SQLITE_OMIT_EXPLAIN
- int iSub1; /* EQP id of left-hand query */
- int iSub2; /* EQP id of right-hand query */
-#endif
assert( p->pOrderBy!=0 );
assert( pKeyDup==0 ); /* "Managed" code needs this. Ticket #3382. */
db = pParse->db;
v = pParse->pVdbe;
assert( v!=0 ); /* Already thrown the error if VDBE alloc failed */
- labelEnd = sqlite3VdbeMakeLabel(v);
- labelCmpr = sqlite3VdbeMakeLabel(v);
+ labelEnd = tdsqlite3VdbeMakeLabel(pParse);
+ labelCmpr = tdsqlite3VdbeMakeLabel(pParse);
/* Patch up the ORDER BY clause
@@ -120361,11 +134709,11 @@ static int multiSelectOrderBy(
if( pItem->u.x.iOrderByCol==i ) break;
}
if( j==nOrderBy ){
- Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0);
+ Expr *pNew = tdsqlite3Expr(db, TK_INTEGER, 0);
if( pNew==0 ) return SQLITE_NOMEM_BKPT;
pNew->flags |= EP_IntValue;
pNew->u.iValue = i;
- pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew);
+ p->pOrderBy = pOrderBy = tdsqlite3ExprListAppend(pParse, pOrderBy, pNew);
if( pOrderBy ) pOrderBy->a[nOrderBy++].u.x.iOrderByCol = (u16)i;
}
}
@@ -120378,7 +134726,7 @@ static int multiSelectOrderBy(
** to the right and the left are evaluated, they use the correct
** collation.
*/
- aPermute = sqlite3DbMallocRawNN(db, sizeof(int)*(nOrderBy + 1));
+ aPermute = tdsqlite3DbMallocRawNN(db, sizeof(int)*(nOrderBy + 1));
if( aPermute ){
struct ExprList_item *pItem;
aPermute[0] = nOrderBy;
@@ -120395,7 +134743,7 @@ static int multiSelectOrderBy(
/* Reattach the ORDER BY clause to the query.
*/
p->pOrderBy = pOrderBy;
- pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0);
+ pPrior->pOrderBy = tdsqlite3ExprListDup(pParse->db, pOrderBy, 0);
/* Allocate a range of temporary registers and the KeyInfo needed
** for the logic that removes duplicate result rows when the
@@ -120408,13 +134756,13 @@ static int multiSelectOrderBy(
assert( nOrderBy>=nExpr || db->mallocFailed );
regPrev = pParse->nMem+1;
pParse->nMem += nExpr+1;
- sqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev);
- pKeyDup = sqlite3KeyInfoAlloc(db, nExpr, 1);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regPrev);
+ pKeyDup = tdsqlite3KeyInfoAlloc(db, nExpr, 1);
if( pKeyDup ){
- assert( sqlite3KeyInfoIsWriteable(pKeyDup) );
+ assert( tdsqlite3KeyInfoIsWriteable(pKeyDup) );
for(i=0; i<nExpr; i++){
pKeyDup->aColl[i] = multiSelectCollSeq(pParse, p, i);
- pKeyDup->aSortOrder[i] = 0;
+ pKeyDup->aSortFlags[i] = 0;
}
}
}
@@ -120423,9 +134771,9 @@ static int multiSelectOrderBy(
*/
p->pPrior = 0;
pPrior->pNext = 0;
- sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER");
+ tdsqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER");
if( pPrior->pPrior==0 ){
- sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER");
+ tdsqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER");
}
/* Compute the limit registers */
@@ -120433,51 +134781,51 @@ static int multiSelectOrderBy(
if( p->iLimit && op==TK_ALL ){
regLimitA = ++pParse->nMem;
regLimitB = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Copy, p->iOffset ? p->iOffset+1 : p->iLimit,
+ tdsqlite3VdbeAddOp2(v, OP_Copy, p->iOffset ? p->iOffset+1 : p->iLimit,
regLimitA);
- sqlite3VdbeAddOp2(v, OP_Copy, regLimitA, regLimitB);
+ tdsqlite3VdbeAddOp2(v, OP_Copy, regLimitA, regLimitB);
}else{
regLimitA = regLimitB = 0;
}
- sqlite3ExprDelete(db, p->pLimit);
+ tdsqlite3ExprDelete(db, p->pLimit);
p->pLimit = 0;
- sqlite3ExprDelete(db, p->pOffset);
- p->pOffset = 0;
regAddrA = ++pParse->nMem;
regAddrB = ++pParse->nMem;
regOutA = ++pParse->nMem;
regOutB = ++pParse->nMem;
- sqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA);
- sqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB);
+ tdsqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA);
+ tdsqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB);
+
+ ExplainQueryPlan((pParse, 1, "MERGE (%s)", selectOpName(p->op)));
/* Generate a coroutine to evaluate the SELECT statement to the
** left of the compound operator - the "A" select.
*/
- addrSelectA = sqlite3VdbeCurrentAddr(v) + 1;
- addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrA, 0, addrSelectA);
+ addrSelectA = tdsqlite3VdbeCurrentAddr(v) + 1;
+ addr1 = tdsqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrA, 0, addrSelectA);
VdbeComment((v, "left SELECT"));
pPrior->iLimit = regLimitA;
- explainSetInteger(iSub1, pParse->iNextSelectId);
- sqlite3Select(pParse, pPrior, &destA);
- sqlite3VdbeEndCoroutine(v, regAddrA);
- sqlite3VdbeJumpHere(v, addr1);
+ ExplainQueryPlan((pParse, 1, "LEFT"));
+ tdsqlite3Select(pParse, pPrior, &destA);
+ tdsqlite3VdbeEndCoroutine(v, regAddrA);
+ tdsqlite3VdbeJumpHere(v, addr1);
/* Generate a coroutine to evaluate the SELECT statement on
** the right - the "B" select
*/
- addrSelectB = sqlite3VdbeCurrentAddr(v) + 1;
- addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrB, 0, addrSelectB);
+ addrSelectB = tdsqlite3VdbeCurrentAddr(v) + 1;
+ addr1 = tdsqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrB, 0, addrSelectB);
VdbeComment((v, "right SELECT"));
savedLimit = p->iLimit;
savedOffset = p->iOffset;
p->iLimit = regLimitB;
p->iOffset = 0;
- explainSetInteger(iSub2, pParse->iNextSelectId);
- sqlite3Select(pParse, p, &destB);
+ ExplainQueryPlan((pParse, 1, "RIGHT"));
+ tdsqlite3Select(pParse, p, &destB);
p->iLimit = savedLimit;
p->iOffset = savedOffset;
- sqlite3VdbeEndCoroutine(v, regAddrB);
+ tdsqlite3VdbeEndCoroutine(v, regAddrB);
/* Generate a subroutine that outputs the current row of the A
** select as the next output row of the compound select.
@@ -120496,7 +134844,7 @@ static int multiSelectOrderBy(
p, &destB, pDest, regOutB,
regPrev, pKeyDup, labelEnd);
}
- sqlite3KeyInfoUnref(pKeyDup);
+ tdsqlite3KeyInfoUnref(pKeyDup);
/* Generate a subroutine to run when the results from select A
** are exhausted and only data in select B remains.
@@ -120505,11 +134853,11 @@ static int multiSelectOrderBy(
addrEofA_noB = addrEofA = labelEnd;
}else{
VdbeNoopComment((v, "eof-A subroutine"));
- addrEofA = sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB);
- addrEofA_noB = sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd);
+ addrEofA = tdsqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB);
+ addrEofA_noB = tdsqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd);
VdbeCoverage(v);
- sqlite3VdbeGoto(v, addrEofA);
- p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
+ tdsqlite3VdbeGoto(v, addrEofA);
+ p->nSelectRow = tdsqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
}
/* Generate a subroutine to run when the results from select B
@@ -120520,17 +134868,17 @@ static int multiSelectOrderBy(
if( p->nSelectRow > pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow;
}else{
VdbeNoopComment((v, "eof-B subroutine"));
- addrEofB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA);
- sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, labelEnd); VdbeCoverage(v);
- sqlite3VdbeGoto(v, addrEofB);
+ addrEofB = tdsqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA);
+ tdsqlite3VdbeAddOp2(v, OP_Yield, regAddrA, labelEnd); VdbeCoverage(v);
+ tdsqlite3VdbeGoto(v, addrEofB);
}
/* Generate code to handle the case of A<B
*/
VdbeNoopComment((v, "A-lt-B subroutine"));
- addrAltB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA);
- sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v);
- sqlite3VdbeGoto(v, labelCmpr);
+ addrAltB = tdsqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA);
+ tdsqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v);
+ tdsqlite3VdbeGoto(v, labelCmpr);
/* Generate code to handle the case of A==B
*/
@@ -120542,66 +134890,73 @@ static int multiSelectOrderBy(
}else{
VdbeNoopComment((v, "A-eq-B subroutine"));
addrAeqB =
- sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v);
- sqlite3VdbeGoto(v, labelCmpr);
+ tdsqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA); VdbeCoverage(v);
+ tdsqlite3VdbeGoto(v, labelCmpr);
}
/* Generate code to handle the case of A>B
*/
VdbeNoopComment((v, "A-gt-B subroutine"));
- addrAgtB = sqlite3VdbeCurrentAddr(v);
+ addrAgtB = tdsqlite3VdbeCurrentAddr(v);
if( op==TK_ALL || op==TK_UNION ){
- sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB);
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB);
}
- sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v);
- sqlite3VdbeGoto(v, labelCmpr);
+ tdsqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v);
+ tdsqlite3VdbeGoto(v, labelCmpr);
/* This code runs once to initialize everything.
*/
- sqlite3VdbeJumpHere(v, addr1);
- sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA_noB); VdbeCoverage(v);
- sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addr1);
+ tdsqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA_noB); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v);
/* Implement the main merge loop
*/
- sqlite3VdbeResolveLabel(v, labelCmpr);
- sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY);
- sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy,
+ tdsqlite3VdbeResolveLabel(v, labelCmpr);
+ tdsqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY);
+ tdsqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy,
(char*)pKeyMerge, P4_KEYINFO);
- sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE);
- sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); VdbeCoverage(v);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_PERMUTE);
+ tdsqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); VdbeCoverage(v);
/* Jump to the this point in order to terminate the query.
*/
- sqlite3VdbeResolveLabel(v, labelEnd);
-
- /* Set the number of output columns
- */
- if( pDest->eDest==SRT_Output ){
- Select *pFirst = pPrior;
- while( pFirst->pPrior ) pFirst = pFirst->pPrior;
- generateColumnNames(pParse, pFirst->pSrc, pFirst->pEList);
- }
+ tdsqlite3VdbeResolveLabel(v, labelEnd);
/* Reassembly the compound query so that it will be freed correctly
** by the calling function */
if( p->pPrior ){
- sqlite3SelectDelete(db, p->pPrior);
+ tdsqlite3SelectDelete(db, p->pPrior);
}
p->pPrior = pPrior;
pPrior->pNext = p;
/*** TBD: Insert subroutine calls to close cursors on incomplete
**** subqueries ****/
- explainComposite(pParse, p->op, iSub1, iSub2, 0);
+ ExplainQueryPlanPop(pParse);
return pParse->nErr!=0;
}
#endif
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
+
+/* An instance of the SubstContext object describes an substitution edit
+** to be performed on a parse tree.
+**
+** All references to columns in table iTable are to be replaced by corresponding
+** expressions in pEList.
+*/
+typedef struct SubstContext {
+ Parse *pParse; /* The parsing context */
+ int iTable; /* Replace references to this table */
+ int iNewTable; /* New table number */
+ int isLeftJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */
+ ExprList *pEList; /* Replacement expressions */
+} SubstContext;
+
/* Forward Declarations */
-static void substExprList(sqlite3*, ExprList*, int, ExprList*);
-static void substSelect(sqlite3*, Select *, int, ExprList*, int);
+static void substExprList(SubstContext*, ExprList*);
+static void substSelect(SubstContext*, Select*, int);
/*
** Scan through the expression pExpr. Replace every reference to
@@ -120612,74 +134967,118 @@ static void substSelect(sqlite3*, Select *, int, ExprList*, int);
** This routine is part of the flattening procedure. A subquery
** whose result set is defined by pEList appears as entry in the
** FROM clause of a SELECT such that the VDBE cursor assigned to that
-** FORM clause entry is iTable. This routine make the necessary
+** FORM clause entry is iTable. This routine makes the necessary
** changes to pExpr so that it refers directly to the source table
** of the subquery rather the result set of the subquery.
*/
static Expr *substExpr(
- sqlite3 *db, /* Report malloc errors to this connection */
- Expr *pExpr, /* Expr in which substitution occurs */
- int iTable, /* Table to be substituted */
- ExprList *pEList /* Substitute expressions */
+ SubstContext *pSubst, /* Description of the substitution */
+ Expr *pExpr /* Expr in which substitution occurs */
){
if( pExpr==0 ) return 0;
- if( pExpr->op==TK_COLUMN && pExpr->iTable==iTable ){
+ if( ExprHasProperty(pExpr, EP_FromJoin)
+ && pExpr->iRightJoinTable==pSubst->iTable
+ ){
+ pExpr->iRightJoinTable = pSubst->iNewTable;
+ }
+ if( pExpr->op==TK_COLUMN && pExpr->iTable==pSubst->iTable ){
if( pExpr->iColumn<0 ){
pExpr->op = TK_NULL;
}else{
Expr *pNew;
- assert( pEList!=0 && pExpr->iColumn<pEList->nExpr );
- assert( pExpr->pLeft==0 && pExpr->pRight==0 );
- pNew = sqlite3ExprDup(db, pEList->a[pExpr->iColumn].pExpr, 0);
- sqlite3ExprDelete(db, pExpr);
- pExpr = pNew;
+ Expr *pCopy = pSubst->pEList->a[pExpr->iColumn].pExpr;
+ Expr ifNullRow;
+ assert( pSubst->pEList!=0 && pExpr->iColumn<pSubst->pEList->nExpr );
+ assert( pExpr->pRight==0 );
+ if( tdsqlite3ExprIsVector(pCopy) ){
+ tdsqlite3VectorErrorMsg(pSubst->pParse, pCopy);
+ }else{
+ tdsqlite3 *db = pSubst->pParse->db;
+ if( pSubst->isLeftJoin && pCopy->op!=TK_COLUMN ){
+ memset(&ifNullRow, 0, sizeof(ifNullRow));
+ ifNullRow.op = TK_IF_NULL_ROW;
+ ifNullRow.pLeft = pCopy;
+ ifNullRow.iTable = pSubst->iNewTable;
+ pCopy = &ifNullRow;
+ }
+ testcase( ExprHasProperty(pCopy, EP_Subquery) );
+ pNew = tdsqlite3ExprDup(db, pCopy, 0);
+ if( pNew && pSubst->isLeftJoin ){
+ ExprSetProperty(pNew, EP_CanBeNull);
+ }
+ if( pNew && ExprHasProperty(pExpr,EP_FromJoin) ){
+ pNew->iRightJoinTable = pExpr->iRightJoinTable;
+ ExprSetProperty(pNew, EP_FromJoin);
+ }
+ tdsqlite3ExprDelete(db, pExpr);
+ pExpr = pNew;
+
+ /* Ensure that the expression now has an implicit collation sequence,
+ ** just as it did when it was a column of a view or sub-query. */
+ if( pExpr ){
+ if( pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE ){
+ CollSeq *pColl = tdsqlite3ExprCollSeq(pSubst->pParse, pExpr);
+ pExpr = tdsqlite3ExprAddCollateString(pSubst->pParse, pExpr,
+ (pColl ? pColl->zName : "BINARY")
+ );
+ }
+ ExprClearProperty(pExpr, EP_Collate);
+ }
+ }
}
}else{
- pExpr->pLeft = substExpr(db, pExpr->pLeft, iTable, pEList);
- pExpr->pRight = substExpr(db, pExpr->pRight, iTable, pEList);
+ if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){
+ pExpr->iTable = pSubst->iNewTable;
+ }
+ pExpr->pLeft = substExpr(pSubst, pExpr->pLeft);
+ pExpr->pRight = substExpr(pSubst, pExpr->pRight);
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
- substSelect(db, pExpr->x.pSelect, iTable, pEList, 1);
+ substSelect(pSubst, pExpr->x.pSelect, 1);
}else{
- substExprList(db, pExpr->x.pList, iTable, pEList);
+ substExprList(pSubst, pExpr->x.pList);
+ }
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( ExprHasProperty(pExpr, EP_WinFunc) ){
+ Window *pWin = pExpr->y.pWin;
+ pWin->pFilter = substExpr(pSubst, pWin->pFilter);
+ substExprList(pSubst, pWin->pPartition);
+ substExprList(pSubst, pWin->pOrderBy);
}
+#endif
}
return pExpr;
}
static void substExprList(
- sqlite3 *db, /* Report malloc errors here */
- ExprList *pList, /* List to scan and in which to make substitutes */
- int iTable, /* Table to be substituted */
- ExprList *pEList /* Substitute values */
+ SubstContext *pSubst, /* Description of the substitution */
+ ExprList *pList /* List to scan and in which to make substitutes */
){
int i;
if( pList==0 ) return;
for(i=0; i<pList->nExpr; i++){
- pList->a[i].pExpr = substExpr(db, pList->a[i].pExpr, iTable, pEList);
+ pList->a[i].pExpr = substExpr(pSubst, pList->a[i].pExpr);
}
}
static void substSelect(
- sqlite3 *db, /* Report malloc errors here */
- Select *p, /* SELECT statement in which to make substitutions */
- int iTable, /* Table to be replaced */
- ExprList *pEList, /* Substitute values */
- int doPrior /* Do substitutes on p->pPrior too */
+ SubstContext *pSubst, /* Description of the substitution */
+ Select *p, /* SELECT statement in which to make substitutions */
+ int doPrior /* Do substitutes on p->pPrior too */
){
SrcList *pSrc;
struct SrcList_item *pItem;
int i;
if( !p ) return;
do{
- substExprList(db, p->pEList, iTable, pEList);
- substExprList(db, p->pGroupBy, iTable, pEList);
- substExprList(db, p->pOrderBy, iTable, pEList);
- p->pHaving = substExpr(db, p->pHaving, iTable, pEList);
- p->pWhere = substExpr(db, p->pWhere, iTable, pEList);
+ substExprList(pSubst, p->pEList);
+ substExprList(pSubst, p->pGroupBy);
+ substExprList(pSubst, p->pOrderBy);
+ p->pHaving = substExpr(pSubst, p->pHaving);
+ p->pWhere = substExpr(pSubst, p->pWhere);
pSrc = p->pSrc;
assert( pSrc!=0 );
for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){
- substSelect(db, pItem->pSelect, iTable, pEList, 1);
+ substSelect(pSubst, pItem->pSelect, 1);
if( pItem->fg.isTabFunc ){
- substExprList(db, pItem->u1.pFuncArg, iTable, pEList);
+ substExprList(pSubst, pItem->u1.pFuncArg);
}
}
}while( doPrior && (p = p->pPrior)!=0 );
@@ -120713,66 +135112,76 @@ static void substSelect(
** exist on the table t1, a complete scan of the data might be
** avoided.
**
-** Flattening is only attempted if all of the following are true:
+** Flattening is subject to the following constraints:
**
-** (1) The subquery and the outer query do not both use aggregates.
+** (**) We no longer attempt to flatten aggregate subqueries. Was:
+** The subquery and the outer query cannot both be aggregates.
**
-** (2) The subquery is not an aggregate or (2a) the outer query is not a join
-** and (2b) the outer query does not use subqueries other than the one
-** FROM-clause subquery that is a candidate for flattening. (2b is
-** due to ticket [2f7170d73bf9abf80] from 2015-02-09.)
+** (**) We no longer attempt to flatten aggregate subqueries. Was:
+** (2) If the subquery is an aggregate then
+** (2a) the outer query must not be a join and
+** (2b) the outer query must not use subqueries
+** other than the one FROM-clause subquery that is a candidate
+** for flattening. (This is due to ticket [2f7170d73bf9abf80]
+** from 2015-02-09.)
**
-** (3) The subquery is not the right operand of a left outer join
-** (Originally ticket #306. Strengthened by ticket #3300)
+** (3) If the subquery is the right operand of a LEFT JOIN then
+** (3a) the subquery may not be a join and
+** (3b) the FROM clause of the subquery may not contain a virtual
+** table and
+** (3c) the outer query may not be an aggregate.
+** (3d) the outer query may not be DISTINCT.
**
-** (4) The subquery is not DISTINCT.
+** (4) The subquery can not be DISTINCT.
**
** (**) At one point restrictions (4) and (5) defined a subset of DISTINCT
** sub-queries that were excluded from this optimization. Restriction
** (4) has since been expanded to exclude all DISTINCT subqueries.
**
-** (6) The subquery does not use aggregates or the outer query is not
-** DISTINCT.
+** (**) We no longer attempt to flatten aggregate subqueries. Was:
+** If the subquery is aggregate, the outer query may not be DISTINCT.
**
-** (7) The subquery has a FROM clause. TODO: For subqueries without
-** A FROM clause, consider adding a FROM close with the special
+** (7) The subquery must have a FROM clause. TODO: For subqueries without
+** A FROM clause, consider adding a FROM clause with the special
** table sqlite_once that consists of a single row containing a
** single NULL.
**
-** (8) The subquery does not use LIMIT or the outer query is not a join.
+** (8) If the subquery uses LIMIT then the outer query may not be a join.
**
-** (9) The subquery does not use LIMIT or the outer query does not use
-** aggregates.
+** (9) If the subquery uses LIMIT then the outer query may not be aggregate.
**
** (**) Restriction (10) was removed from the code on 2005-02-05 but we
** accidently carried the comment forward until 2014-09-15. Original
-** text: "The subquery does not use aggregates or the outer query
-** does not use LIMIT."
+** constraint: "If the subquery is aggregate then the outer query
+** may not use LIMIT."
**
-** (11) The subquery and the outer query do not both have ORDER BY clauses.
+** (11) The subquery and the outer query may not both have ORDER BY clauses.
**
** (**) Not implemented. Subsumed into restriction (3). Was previously
** a separate restriction deriving from ticket #350.
**
-** (13) The subquery and outer query do not both use LIMIT.
+** (13) The subquery and outer query may not both use LIMIT.
**
-** (14) The subquery does not use OFFSET.
+** (14) The subquery may not use OFFSET.
**
-** (15) The outer query is not part of a compound select or the
-** subquery does not have a LIMIT clause.
+** (15) If the outer query is part of a compound select, then the
+** subquery may not use LIMIT.
** (See ticket #2339 and ticket [02a8e81d44]).
**
-** (16) The outer query is not an aggregate or the subquery does
-** not contain ORDER BY. (Ticket #2942) This used to not matter
+** (16) If the outer query is aggregate, then the subquery may not
+** use ORDER BY. (Ticket #2942) This used to not matter
** until we introduced the group_concat() function.
**
-** (17) The sub-query is not a compound select, or it is a UNION ALL
-** compound clause made up entirely of non-aggregate queries, and
-** the parent query:
-**
-** * is not itself part of a compound select,
-** * is not an aggregate or DISTINCT query, and
-** * is not a join
+** (17) If the subquery is a compound select, then
+** (17a) all compound operators must be a UNION ALL, and
+** (17b) no terms within the subquery compound may be aggregate
+** or DISTINCT, and
+** (17c) every term within the subquery compound must have a FROM clause
+** (17d) the outer query may not be
+** (17d1) aggregate, or
+** (17d2) DISTINCT, or
+** (17d3) a join.
+** (17e) the subquery may not contain window functions
**
** The parent and sub-query may contain WHERE clauses. Subject to
** rules (11), (13) and (14), they may also contain ORDER BY,
@@ -120788,10 +135197,10 @@ static void substSelect(
** syntax error and return a detailed message.
**
** (18) If the sub-query is a compound select, then all terms of the
-** ORDER by clause of the parent must be simple references to
+** ORDER BY clause of the parent must be simple references to
** columns of the sub-query.
**
-** (19) The subquery does not use LIMIT or the outer query does not
+** (19) If the subquery uses LIMIT then the outer query may not
** have a WHERE clause.
**
** (20) If the sub-query is a compound select, then it must not use
@@ -120800,25 +135209,31 @@ static void substSelect(
** appear as unmodified result columns in the outer query. But we
** have other optimizations in mind to deal with that case.
**
-** (21) The subquery does not use LIMIT or the outer query is not
+** (21) If the subquery uses LIMIT then the outer query may not be
** DISTINCT. (See ticket [752e1646fc]).
**
-** (22) The subquery is not a recursive CTE.
+** (22) The subquery may not be a recursive CTE.
**
-** (23) The parent is not a recursive CTE, or the sub-query is not a
-** compound query. This restriction is because transforming the
+** (**) Subsumed into restriction (17d3). Was: If the outer query is
+** a recursive CTE, then the sub-query may not be a compound query.
+** This restriction is because transforming the
** parent to a compound query confuses the code that handles
** recursive queries in multiSelect().
**
-** (24) The subquery is not an aggregate that uses the built-in min() or
+** (**) We no longer attempt to flatten aggregate subqueries. Was:
+** The subquery may not be an aggregate that uses the built-in min() or
** or max() functions. (Without this restriction, a query like:
** "SELECT x FROM (SELECT max(y), x FROM t1)" would not necessarily
** return the value X for which Y was maximal.)
**
+** (25) If either the subquery or the parent query contains a window
+** function in the select list or ORDER BY clause, flattening
+** is not attempted.
+**
**
** In this routine, the "p" parameter is a pointer to the outer query.
** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query
-** uses aggregates and subqueryIsAgg is true if the subquery uses aggregates.
+** uses aggregates.
**
** If flattening is not attempted, this routine is a no-op and returns 0.
** If flattening is attempted this routine returns 1.
@@ -120830,8 +135245,7 @@ static int flattenSubquery(
Parse *pParse, /* Parsing context */
Select *p, /* The parent or outer SELECT statement */
int iFrom, /* Index in p->pSrc->a[] of the inner subquery */
- int isAgg, /* True if outer SELECT uses aggregate functions */
- int subqueryIsAgg /* True if the subquery uses aggregate functions */
+ int isAgg /* True if outer SELECT uses aggregate functions */
){
const char *zSavedAuthContext = pParse->zAuthContext;
Select *pParent; /* Current UNION ALL term of the other query */
@@ -120839,17 +135253,18 @@ static int flattenSubquery(
Select *pSub1; /* Pointer to the rightmost select in sub-query */
SrcList *pSrc; /* The FROM clause of the outer query */
SrcList *pSubSrc; /* The FROM clause of the subquery */
- ExprList *pList; /* The result set of the outer query */
int iParent; /* VDBE cursor number of the pSub result set temp table */
+ int iNewParent = -1;/* Replacement table for iParent */
+ int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */
int i; /* Loop counter */
Expr *pWhere; /* The WHERE clause */
struct SrcList_item *pSubitem; /* The subquery */
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
/* Check to see if flattening is permitted. Return 0 if not.
*/
assert( p!=0 );
- assert( p->pPrior==0 ); /* Unable to flatten compound queries */
+ assert( p->pPrior==0 );
if( OptimizationDisabled(db, SQLITE_QueryFlattener) ) return 0;
pSrc = p->pSrc;
assert( pSrc && iFrom>=0 && iFrom<pSrc->nSrc );
@@ -120857,17 +135272,11 @@ static int flattenSubquery(
iParent = pSubitem->iCursor;
pSub = pSubitem->pSelect;
assert( pSub!=0 );
- if( subqueryIsAgg ){
- if( isAgg ) return 0; /* Restriction (1) */
- if( pSrc->nSrc>1 ) return 0; /* Restriction (2a) */
- if( (p->pWhere && ExprHasProperty(p->pWhere,EP_Subquery))
- || (sqlite3ExprListFlags(p->pEList) & EP_Subquery)!=0
- || (sqlite3ExprListFlags(p->pOrderBy) & EP_Subquery)!=0
- ){
- return 0; /* Restriction (2b) */
- }
- }
-
+
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( p->pWin || pSub->pWin ) return 0; /* Restriction (25) */
+#endif
+
pSubSrc = pSub->pSrc;
assert( pSubSrc );
/* Prior to version 3.1.2, when LIMIT and OFFSET had to be simple constants,
@@ -120876,18 +135285,15 @@ static int flattenSubquery(
** became arbitrary expressions, we were forced to add restrictions (13)
** and (14). */
if( pSub->pLimit && p->pLimit ) return 0; /* Restriction (13) */
- if( pSub->pOffset ) return 0; /* Restriction (14) */
+ if( pSub->pLimit && pSub->pLimit->pRight ) return 0; /* Restriction (14) */
if( (p->selFlags & SF_Compound)!=0 && pSub->pLimit ){
return 0; /* Restriction (15) */
}
if( pSubSrc->nSrc==0 ) return 0; /* Restriction (7) */
- if( pSub->selFlags & SF_Distinct ) return 0; /* Restriction (5) */
+ if( pSub->selFlags & SF_Distinct ) return 0; /* Restriction (4) */
if( pSub->pLimit && (pSrc->nSrc>1 || isAgg) ){
return 0; /* Restrictions (8)(9) */
}
- if( (p->selFlags & SF_Distinct)!=0 && subqueryIsAgg ){
- return 0; /* Restriction (6) */
- }
if( p->pOrderBy && pSub->pOrderBy ){
return 0; /* Restriction (11) */
}
@@ -120896,19 +135302,14 @@ static int flattenSubquery(
if( pSub->pLimit && (p->selFlags & SF_Distinct)!=0 ){
return 0; /* Restriction (21) */
}
- testcase( pSub->selFlags & SF_Recursive );
- testcase( pSub->selFlags & SF_MinMaxAgg );
- if( pSub->selFlags & (SF_Recursive|SF_MinMaxAgg) ){
- return 0; /* Restrictions (22) and (24) */
- }
- if( (p->selFlags & SF_Recursive) && pSub->pPrior ){
- return 0; /* Restriction (23) */
+ if( pSub->selFlags & (SF_Recursive) ){
+ return 0; /* Restrictions (22) */
}
- /* OBSOLETE COMMENT 1:
- ** Restriction 3: If the subquery is a join, make sure the subquery is
- ** not used as the right operand of an outer join. Examples of why this
- ** is not allowed:
+ /*
+ ** If the subquery is the right operand of a LEFT JOIN, then the
+ ** subquery may not be a join itself (3a). Example of why this is not
+ ** allowed:
**
** t1 LEFT OUTER JOIN (t2 JOIN t3)
**
@@ -120918,56 +135319,63 @@ static int flattenSubquery(
**
** which is not at all the same thing.
**
- ** OBSOLETE COMMENT 2:
- ** Restriction 12: If the subquery is the right operand of a left outer
- ** join, make sure the subquery has no WHERE clause.
- ** An examples of why this is not allowed:
- **
- ** t1 LEFT OUTER JOIN (SELECT * FROM t2 WHERE t2.x>0)
- **
- ** If we flatten the above, we would get
- **
- ** (t1 LEFT OUTER JOIN t2) WHERE t2.x>0
- **
- ** But the t2.x>0 test will always fail on a NULL row of t2, which
- ** effectively converts the OUTER JOIN into an INNER JOIN.
+ ** If the subquery is the right operand of a LEFT JOIN, then the outer
+ ** query cannot be an aggregate. (3c) This is an artifact of the way
+ ** aggregates are processed - there is no mechanism to determine if
+ ** the LEFT JOIN table should be all-NULL.
**
- ** THIS OVERRIDES OBSOLETE COMMENTS 1 AND 2 ABOVE:
- ** Ticket #3300 shows that flattening the right term of a LEFT JOIN
- ** is fraught with danger. Best to avoid the whole thing. If the
- ** subquery is the right term of a LEFT JOIN, then do not flatten.
+ ** See also tickets #306, #350, and #3300.
*/
if( (pSubitem->fg.jointype & JT_OUTER)!=0 ){
- return 0;
+ isLeftJoin = 1;
+ if( pSubSrc->nSrc>1 /* (3a) */
+ || isAgg /* (3b) */
+ || IsVirtual(pSubSrc->a[0].pTab) /* (3c) */
+ || (p->selFlags & SF_Distinct)!=0 /* (3d) */
+ ){
+ return 0;
+ }
}
+#ifdef SQLITE_EXTRA_IFNULLROW
+ else if( iFrom>0 && !isAgg ){
+ /* Setting isLeftJoin to -1 causes OP_IfNullRow opcodes to be generated for
+ ** every reference to any result column from subquery in a join, even
+ ** though they are not necessary. This will stress-test the OP_IfNullRow
+ ** opcode. */
+ isLeftJoin = -1;
+ }
+#endif
- /* Restriction 17: If the sub-query is a compound SELECT, then it must
+ /* Restriction (17): If the sub-query is a compound SELECT, then it must
** use only the UNION ALL operator. And none of the simple select queries
** that make up the compound SELECT are allowed to be aggregate or distinct
** queries.
*/
if( pSub->pPrior ){
if( pSub->pOrderBy ){
- return 0; /* Restriction 20 */
+ return 0; /* Restriction (20) */
}
if( isAgg || (p->selFlags & SF_Distinct)!=0 || pSrc->nSrc!=1 ){
- return 0;
+ return 0; /* (17d1), (17d2), or (17d3) */
}
for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){
testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct );
testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Aggregate );
assert( pSub->pSrc!=0 );
assert( pSub->pEList->nExpr==pSub1->pEList->nExpr );
- if( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))!=0
- || (pSub1->pPrior && pSub1->op!=TK_ALL)
- || pSub1->pSrc->nSrc<1
+ if( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))!=0 /* (17b) */
+ || (pSub1->pPrior && pSub1->op!=TK_ALL) /* (17a) */
+ || pSub1->pSrc->nSrc<1 /* (17c) */
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ || pSub1->pWin /* (17e) */
+#endif
){
return 0;
}
testcase( pSub1->pSrc->nSrc>1 );
}
- /* Restriction 18. */
+ /* Restriction (18). */
if( p->pOrderBy ){
int ii;
for(ii=0; ii<p->pOrderBy->nExpr; ii++){
@@ -120976,13 +135384,21 @@ static int flattenSubquery(
}
}
+ /* Ex-restriction (23):
+ ** The only way that the recursive part of a CTE can contain a compound
+ ** subquery is for the subquery to be one term of a join. But if the
+ ** subquery is a join, then the flattening has already been stopped by
+ ** restriction (17d3)
+ */
+ assert( (p->selFlags & SF_Recursive)==0 || pSub->pPrior==0 );
+
/***** If we reach this point, flattening is permitted. *****/
- SELECTTRACE(1,pParse,p,("flatten %s.%p from term %d\n",
- pSub->zSelName, pSub, iFrom));
+ SELECTTRACE(1,pParse,p,("flatten %u.%p from term %d\n",
+ pSub->selId, pSub, iFrom));
/* Authorize the subquery */
pParse->zAuthContext = pSubitem->zName;
- TESTONLY(i =) sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0);
+ TESTONLY(i =) tdsqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0);
testcase( i==SQLITE_DENY );
pParse->zAuthContext = zSavedAuthContext;
@@ -121023,16 +135439,12 @@ static int flattenSubquery(
Select *pNew;
ExprList *pOrderBy = p->pOrderBy;
Expr *pLimit = p->pLimit;
- Expr *pOffset = p->pOffset;
Select *pPrior = p->pPrior;
p->pOrderBy = 0;
p->pSrc = 0;
p->pPrior = 0;
p->pLimit = 0;
- p->pOffset = 0;
- pNew = sqlite3SelectDup(db, p, 0);
- sqlite3SelectSetName(pNew, pSub->zSelName);
- p->pOffset = pOffset;
+ pNew = tdsqlite3SelectDup(db, p, 0);
p->pLimit = pLimit;
p->pOrderBy = pOrderBy;
p->pSrc = pSrc;
@@ -121044,9 +135456,8 @@ static int flattenSubquery(
if( pPrior ) pPrior->pNext = pNew;
pNew->pNext = p;
p->pPrior = pNew;
- SELECTTRACE(2,pParse,p,
- ("compound-subquery flattener creates %s.%p as peer\n",
- pNew->zSelName, pNew));
+ SELECTTRACE(2,pParse,p,("compound-subquery flattener"
+ " creates %u as peer\n",pNew->selId));
}
if( db->mallocFailed ) return 1;
}
@@ -121059,9 +135470,9 @@ static int flattenSubquery(
/* Delete the transient table structure associated with the
** subquery
*/
- sqlite3DbFree(db, pSubitem->zDatabase);
- sqlite3DbFree(db, pSubitem->zName);
- sqlite3DbFree(db, pSubitem->zAlias);
+ tdsqlite3DbFree(db, pSubitem->zDatabase);
+ tdsqlite3DbFree(db, pSubitem->zName);
+ tdsqlite3DbFree(db, pSubitem->zAlias);
pSubitem->zDatabase = 0;
pSubitem->zName = 0;
pSubitem->zAlias = 0;
@@ -121076,12 +135487,12 @@ static int flattenSubquery(
*/
if( ALWAYS(pSubitem->pTab!=0) ){
Table *pTabToDel = pSubitem->pTab;
- if( pTabToDel->nRef==1 ){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
+ if( pTabToDel->nTabRef==1 ){
+ Parse *pToplevel = tdsqlite3ParseToplevel(pParse);
pTabToDel->pNextZombie = pToplevel->pZombieTab;
pToplevel->pZombieTab = pTabToDel;
}else{
- pTabToDel->nRef--;
+ pTabToDel->nTabRef--;
}
pSubitem->pTab = 0;
}
@@ -121102,6 +135513,7 @@ static int flattenSubquery(
for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){
int nSubSrc;
u8 jointype = 0;
+ assert( pSub!=0 );
pSubSrc = pSub->pSrc; /* FROM clause of subquery */
nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */
pSrc = pParent->pSrc; /* FROM clause of the outer query */
@@ -121111,11 +135523,9 @@ static int flattenSubquery(
jointype = pSubitem->fg.jointype;
}else{
assert( pParent!=p ); /* 2nd and subsequent times through the loop */
- pSrc = pParent->pSrc = sqlite3SrcListAppend(db, 0, 0, 0);
- if( pSrc==0 ){
- assert( db->mallocFailed );
- break;
- }
+ pSrc = tdsqlite3SrcListAppend(pParse, 0, 0, 0);
+ if( pSrc==0 ) break;
+ pParent->pSrc = pSrc;
}
/* The subquery uses a single slot of the FROM clause of the outer
@@ -121134,19 +135544,19 @@ static int flattenSubquery(
** for the two elements in the FROM clause of the subquery.
*/
if( nSubSrc>1 ){
- pParent->pSrc = pSrc = sqlite3SrcListEnlarge(db, pSrc, nSubSrc-1,iFrom+1);
- if( db->mallocFailed ){
- break;
- }
+ pSrc = tdsqlite3SrcListEnlarge(pParse, pSrc, nSubSrc-1,iFrom+1);
+ if( pSrc==0 ) break;
+ pParent->pSrc = pSrc;
}
/* Transfer the FROM clause terms from the subquery into the
** outer query.
*/
for(i=0; i<nSubSrc; i++){
- sqlite3IdListDelete(db, pSrc->a[i+iFrom].pUsing);
+ tdsqlite3IdListDelete(db, pSrc->a[i+iFrom].pUsing);
assert( pSrc->a[i+iFrom].fg.isTabFunc==0 );
pSrc->a[i+iFrom] = pSubSrc->a[i];
+ iNewParent = pSubSrc->a[i].iCursor;
memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i]));
}
pSrc->a[iFrom].fg.jointype = jointype;
@@ -121163,14 +135573,6 @@ static int flattenSubquery(
** We look at every expression in the outer query and every place we see
** "a" we substitute "x*3" and every place we see "b" we substitute "y+10".
*/
- pList = pParent->pEList;
- for(i=0; i<pList->nExpr; i++){
- if( pList->a[i].zName==0 ){
- char *zName = sqlite3DbStrDup(db, pList->a[i].zSpan);
- sqlite3Dequote(zName);
- pList->a[i].zName = zName;
- }
- }
if( pSub->pOrderBy ){
/* At this point, any non-zero iOrderByCol values indicate that the
** ORDER BY column expression is identical to the iOrderByCol'th
@@ -121187,29 +135589,29 @@ static int flattenSubquery(
pOrderBy->a[i].u.x.iOrderByCol = 0;
}
assert( pParent->pOrderBy==0 );
- assert( pSub->pPrior==0 );
pParent->pOrderBy = pOrderBy;
pSub->pOrderBy = 0;
}
- pWhere = sqlite3ExprDup(db, pSub->pWhere, 0);
- if( subqueryIsAgg ){
- assert( pParent->pHaving==0 );
- pParent->pHaving = pParent->pWhere;
- pParent->pWhere = pWhere;
- pParent->pHaving = sqlite3ExprAnd(db,
- sqlite3ExprDup(db, pSub->pHaving, 0), pParent->pHaving
- );
- assert( pParent->pGroupBy==0 );
- pParent->pGroupBy = sqlite3ExprListDup(db, pSub->pGroupBy, 0);
- }else{
- pParent->pWhere = sqlite3ExprAnd(db, pWhere, pParent->pWhere);
+ pWhere = pSub->pWhere;
+ pSub->pWhere = 0;
+ if( isLeftJoin>0 ){
+ tdsqlite3SetJoinExpr(pWhere, iNewParent);
+ }
+ pParent->pWhere = tdsqlite3ExprAnd(pParse, pWhere, pParent->pWhere);
+ if( db->mallocFailed==0 ){
+ SubstContext x;
+ x.pParse = pParse;
+ x.iTable = iParent;
+ x.iNewTable = iNewParent;
+ x.isLeftJoin = isLeftJoin;
+ x.pEList = pSub->pEList;
+ substSelect(&x, pParent, 0);
}
- substSelect(db, pParent, iParent, pSub->pEList, 0);
- /* The flattened query is distinct if either the inner or the
- ** outer query is distinct.
- */
- pParent->selFlags |= pSub->selFlags & SF_Distinct;
+ /* The flattened query is a compound if either the inner or the
+ ** outer query is a compound. */
+ pParent->selFlags |= pSub->selFlags & SF_Compound;
+ assert( (pSub->selFlags & SF_Distinct)==0 ); /* restriction (17b) */
/*
** SELECT ... FROM (SELECT ... LIMIT a OFFSET b) LIMIT x OFFSET y;
@@ -121226,12 +135628,12 @@ static int flattenSubquery(
/* Finially, delete what is left of the subquery and return
** success.
*/
- sqlite3SelectDelete(db, pSub1);
+ tdsqlite3SelectDelete(db, pSub1);
#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x100 ){
+ if( tdsqlite3SelectTrace & 0x100 ){
SELECTTRACE(0x100,pParse,p,("After flattening:\n"));
- sqlite3TreeViewSelect(0, p, 0);
+ tdsqlite3TreeViewSelect(0, p, 0);
}
#endif
@@ -121239,7 +135641,193 @@ static int flattenSubquery(
}
#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
+/*
+** A structure to keep track of all of the column values that are fixed to
+** a known value due to WHERE clause constraints of the form COLUMN=VALUE.
+*/
+typedef struct WhereConst WhereConst;
+struct WhereConst {
+ Parse *pParse; /* Parsing context */
+ int nConst; /* Number for COLUMN=CONSTANT terms */
+ int nChng; /* Number of times a constant is propagated */
+ Expr **apExpr; /* [i*2] is COLUMN and [i*2+1] is VALUE */
+};
+
+/*
+** Add a new entry to the pConst object. Except, do not add duplicate
+** pColumn entires. Also, do not add if doing so would not be appropriate.
+**
+** The caller guarantees the pColumn is a column and pValue is a constant.
+** This routine has to do some additional checks before completing the
+** insert.
+*/
+static void constInsert(
+ WhereConst *pConst, /* The WhereConst into which we are inserting */
+ Expr *pColumn, /* The COLUMN part of the constraint */
+ Expr *pValue, /* The VALUE part of the constraint */
+ Expr *pExpr /* Overall expression: COLUMN=VALUE or VALUE=COLUMN */
+){
+ int i;
+ assert( pColumn->op==TK_COLUMN );
+ assert( tdsqlite3ExprIsConstant(pValue) );
+
+ if( !ExprHasProperty(pValue, EP_FixedCol) && tdsqlite3ExprAffinity(pValue)!=0 ){
+ return;
+ }
+ if( !tdsqlite3IsBinary(tdsqlite3ExprCompareCollSeq(pConst->pParse,pExpr)) ){
+ return;
+ }
+
+ /* 2018-10-25 ticket [cf5ed20f]
+ ** Make sure the same pColumn is not inserted more than once */
+ for(i=0; i<pConst->nConst; i++){
+ const Expr *pE2 = pConst->apExpr[i*2];
+ assert( pE2->op==TK_COLUMN );
+ if( pE2->iTable==pColumn->iTable
+ && pE2->iColumn==pColumn->iColumn
+ ){
+ return; /* Already present. Return without doing anything. */
+ }
+ }
+
+ pConst->nConst++;
+ pConst->apExpr = tdsqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr,
+ pConst->nConst*2*sizeof(Expr*));
+ if( pConst->apExpr==0 ){
+ pConst->nConst = 0;
+ }else{
+ if( ExprHasProperty(pValue, EP_FixedCol) ){
+ pValue = pValue->pLeft;
+ }
+ pConst->apExpr[pConst->nConst*2-2] = pColumn;
+ pConst->apExpr[pConst->nConst*2-1] = pValue;
+ }
+}
+
+/*
+** Find all terms of COLUMN=VALUE or VALUE=COLUMN in pExpr where VALUE
+** is a constant expression and where the term must be true because it
+** is part of the AND-connected terms of the expression. For each term
+** found, add it to the pConst structure.
+*/
+static void findConstInWhere(WhereConst *pConst, Expr *pExpr){
+ Expr *pRight, *pLeft;
+ if( pExpr==0 ) return;
+ if( ExprHasProperty(pExpr, EP_FromJoin) ) return;
+ if( pExpr->op==TK_AND ){
+ findConstInWhere(pConst, pExpr->pRight);
+ findConstInWhere(pConst, pExpr->pLeft);
+ return;
+ }
+ if( pExpr->op!=TK_EQ ) return;
+ pRight = pExpr->pRight;
+ pLeft = pExpr->pLeft;
+ assert( pRight!=0 );
+ assert( pLeft!=0 );
+ if( pRight->op==TK_COLUMN && tdsqlite3ExprIsConstant(pLeft) ){
+ constInsert(pConst,pRight,pLeft,pExpr);
+ }
+ if( pLeft->op==TK_COLUMN && tdsqlite3ExprIsConstant(pRight) ){
+ constInsert(pConst,pLeft,pRight,pExpr);
+ }
+}
+
+/*
+** This is a Walker expression callback. pExpr is a candidate expression
+** to be replaced by a value. If pExpr is equivalent to one of the
+** columns named in pWalker->u.pConst, then overwrite it with its
+** corresponding value.
+*/
+static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){
+ int i;
+ WhereConst *pConst;
+ if( pExpr->op!=TK_COLUMN ) return WRC_Continue;
+ if( ExprHasProperty(pExpr, EP_FixedCol|EP_FromJoin) ){
+ testcase( ExprHasProperty(pExpr, EP_FixedCol) );
+ testcase( ExprHasProperty(pExpr, EP_FromJoin) );
+ return WRC_Continue;
+ }
+ pConst = pWalker->u.pConst;
+ for(i=0; i<pConst->nConst; i++){
+ Expr *pColumn = pConst->apExpr[i*2];
+ if( pColumn==pExpr ) continue;
+ if( pColumn->iTable!=pExpr->iTable ) continue;
+ if( pColumn->iColumn!=pExpr->iColumn ) continue;
+ /* A match is found. Add the EP_FixedCol property */
+ pConst->nChng++;
+ ExprClearProperty(pExpr, EP_Leaf);
+ ExprSetProperty(pExpr, EP_FixedCol);
+ assert( pExpr->pLeft==0 );
+ pExpr->pLeft = tdsqlite3ExprDup(pConst->pParse->db, pConst->apExpr[i*2+1], 0);
+ break;
+ }
+ return WRC_Prune;
+}
+/*
+** The WHERE-clause constant propagation optimization.
+**
+** If the WHERE clause contains terms of the form COLUMN=CONSTANT or
+** CONSTANT=COLUMN that are top-level AND-connected terms that are not
+** part of a ON clause from a LEFT JOIN, then throughout the query
+** replace all other occurrences of COLUMN with CONSTANT.
+**
+** For example, the query:
+**
+** SELECT * FROM t1, t2, t3 WHERE t1.a=39 AND t2.b=t1.a AND t3.c=t2.b
+**
+** Is transformed into
+**
+** SELECT * FROM t1, t2, t3 WHERE t1.a=39 AND t2.b=39 AND t3.c=39
+**
+** Return true if any transformations where made and false if not.
+**
+** Implementation note: Constant propagation is tricky due to affinity
+** and collating sequence interactions. Consider this example:
+**
+** CREATE TABLE t1(a INT,b TEXT);
+** INSERT INTO t1 VALUES(123,'0123');
+** SELECT * FROM t1 WHERE a=123 AND b=a;
+** SELECT * FROM t1 WHERE a=123 AND b=123;
+**
+** The two SELECT statements above should return different answers. b=a
+** is alway true because the comparison uses numeric affinity, but b=123
+** is false because it uses text affinity and '0123' is not the same as '123'.
+** To work around this, the expression tree is not actually changed from
+** "b=a" to "b=123" but rather the "a" in "b=a" is tagged with EP_FixedCol
+** and the "123" value is hung off of the pLeft pointer. Code generator
+** routines know to generate the constant "123" instead of looking up the
+** column value. Also, to avoid collation problems, this optimization is
+** only attempted if the "a=123" term uses the default BINARY collation.
+*/
+static int propagateConstants(
+ Parse *pParse, /* The parsing context */
+ Select *p /* The query in which to propagate constants */
+){
+ WhereConst x;
+ Walker w;
+ int nChng = 0;
+ x.pParse = pParse;
+ do{
+ x.nConst = 0;
+ x.nChng = 0;
+ x.apExpr = 0;
+ findConstInWhere(&x, p->pWhere);
+ if( x.nConst ){
+ memset(&w, 0, sizeof(w));
+ w.pParse = pParse;
+ w.xExprCallback = propagateConstantExprRewrite;
+ w.xSelectCallback = tdsqlite3SelectWalkNoop;
+ w.xSelectCallback2 = 0;
+ w.walkerDepth = 0;
+ w.u.pConst = &x;
+ tdsqlite3WalkExpr(&w, p->pWhere);
+ tdsqlite3DbFree(x.pParse->db, x.apExpr);
+ nChng += x.nChng;
+ }
+ }while( x.nChng );
+ return nChng;
+}
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
/*
@@ -121258,57 +135846,106 @@ static int flattenSubquery(
**
** Do not attempt this optimization if:
**
-** (1) The inner query is an aggregate. (In that case, we'd really want
-** to copy the outer WHERE-clause terms onto the HAVING clause of the
-** inner query. But they probably won't help there so do not bother.)
+** (1) (** This restriction was removed on 2017-09-29. We used to
+** disallow this optimization for aggregate subqueries, but now
+** it is allowed by putting the extra terms on the HAVING clause.
+** The added HAVING clause is pointless if the subquery lacks
+** a GROUP BY clause. But such a HAVING clause is also harmless
+** so there does not appear to be any reason to add extra logic
+** to suppress it. **)
**
** (2) The inner query is the recursive part of a common table expression.
**
** (3) The inner query has a LIMIT clause (since the changes to the WHERE
-** close would change the meaning of the LIMIT).
+** clause would change the meaning of the LIMIT).
**
-** (4) The inner query is the right operand of a LEFT JOIN. (The caller
-** enforces this restriction since this routine does not have enough
-** information to know.)
+** (4) The inner query is the right operand of a LEFT JOIN and the
+** expression to be pushed down does not come from the ON clause
+** on that LEFT JOIN.
**
** (5) The WHERE clause expression originates in the ON or USING clause
-** of a LEFT JOIN.
+** of a LEFT JOIN where iCursor is not the right-hand table of that
+** left join. An example:
+**
+** SELECT *
+** FROM (SELECT 1 AS a1 UNION ALL SELECT 2) AS aa
+** JOIN (SELECT 1 AS b2 UNION ALL SELECT 2) AS bb ON (a1=b2)
+** LEFT JOIN (SELECT 8 AS c3 UNION ALL SELECT 9) AS cc ON (b2=2);
+**
+** The correct answer is three rows: (1,1,NULL),(2,2,8),(2,2,9).
+** But if the (b2=2) term were to be pushed down into the bb subquery,
+** then the (1,1,NULL) row would be suppressed.
+**
+** (6) The inner query features one or more window-functions (since
+** changes to the WHERE clause of the inner query could change the
+** window over which window functions are calculated).
**
** Return 0 if no changes are made and non-zero if one or more WHERE clause
** terms are duplicated into the subquery.
*/
static int pushDownWhereTerms(
- sqlite3 *db, /* The database connection (for malloc()) */
+ Parse *pParse, /* Parse context (for malloc() and error reporting) */
Select *pSubq, /* The subquery whose WHERE clause is to be augmented */
Expr *pWhere, /* The WHERE clause of the outer query */
- int iCursor /* Cursor number of the subquery */
+ int iCursor, /* Cursor number of the subquery */
+ int isLeftJoin /* True if pSubq is the right term of a LEFT JOIN */
){
Expr *pNew;
int nChng = 0;
- Select *pX; /* For looping over compound SELECTs in pSubq */
if( pWhere==0 ) return 0;
- for(pX=pSubq; pX; pX=pX->pPrior){
- if( (pX->selFlags & (SF_Aggregate|SF_Recursive))!=0 ){
- testcase( pX->selFlags & SF_Aggregate );
- testcase( pX->selFlags & SF_Recursive );
- testcase( pX!=pSubq );
- return 0; /* restrictions (1) and (2) */
+ if( pSubq->selFlags & SF_Recursive ) return 0; /* restriction (2) */
+
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( pSubq->pWin ) return 0; /* restriction (6) */
+#endif
+
+#ifdef SQLITE_DEBUG
+ /* Only the first term of a compound can have a WITH clause. But make
+ ** sure no other terms are marked SF_Recursive in case something changes
+ ** in the future.
+ */
+ {
+ Select *pX;
+ for(pX=pSubq; pX; pX=pX->pPrior){
+ assert( (pX->selFlags & (SF_Recursive))==0 );
}
}
+#endif
+
if( pSubq->pLimit!=0 ){
return 0; /* restriction (3) */
}
while( pWhere->op==TK_AND ){
- nChng += pushDownWhereTerms(db, pSubq, pWhere->pRight, iCursor);
+ nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight,
+ iCursor, isLeftJoin);
pWhere = pWhere->pLeft;
}
- if( ExprHasProperty(pWhere,EP_FromJoin) ) return 0; /* restriction 5 */
- if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){
+ if( isLeftJoin
+ && (ExprHasProperty(pWhere,EP_FromJoin)==0
+ || pWhere->iRightJoinTable!=iCursor)
+ ){
+ return 0; /* restriction (4) */
+ }
+ if( ExprHasProperty(pWhere,EP_FromJoin) && pWhere->iRightJoinTable!=iCursor ){
+ return 0; /* restriction (5) */
+ }
+ if( tdsqlite3ExprIsTableConstant(pWhere, iCursor) ){
nChng++;
while( pSubq ){
- pNew = sqlite3ExprDup(db, pWhere, 0);
- pNew = substExpr(db, pNew, iCursor, pSubq->pEList);
- pSubq->pWhere = sqlite3ExprAnd(db, pSubq->pWhere, pNew);
+ SubstContext x;
+ pNew = tdsqlite3ExprDup(pParse->db, pWhere, 0);
+ unsetJoinExpr(pNew, -1);
+ x.pParse = pParse;
+ x.iTable = iCursor;
+ x.iNewTable = iCursor;
+ x.isLeftJoin = 0;
+ x.pEList = pSubq->pEList;
+ pNew = substExpr(&x, pNew);
+ if( pSubq->selFlags & SF_Aggregate ){
+ pSubq->pHaving = tdsqlite3ExprAnd(pParse, pSubq->pHaving, pNew);
+ }else{
+ pSubq->pWhere = tdsqlite3ExprAnd(pParse, pSubq->pWhere, pNew);
+ }
pSubq = pSubq->pPrior;
}
}
@@ -121317,42 +135954,47 @@ static int pushDownWhereTerms(
#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */
/*
-** Based on the contents of the AggInfo structure indicated by the first
-** argument, this function checks if the following are true:
+** The pFunc is the only aggregate function in the query. Check to see
+** if the query is a candidate for the min/max optimization.
**
-** * the query contains just a single aggregate function,
-** * the aggregate function is either min() or max(), and
-** * the argument to the aggregate function is a column value.
+** If the query is a candidate for the min/max optimization, then set
+** *ppMinMax to be an ORDER BY clause to be used for the optimization
+** and return either WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX depending on
+** whether pFunc is a min() or max() function.
**
-** If all of the above are true, then WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX
-** is returned as appropriate. Also, *ppMinMax is set to point to the
-** list of arguments passed to the aggregate before returning.
+** If the query is not a candidate for the min/max optimization, return
+** WHERE_ORDERBY_NORMAL (which must be zero).
**
-** Or, if the conditions above are not met, *ppMinMax is set to 0 and
-** WHERE_ORDERBY_NORMAL is returned.
+** This routine must be called after aggregate functions have been
+** located but before their arguments have been subjected to aggregate
+** analysis.
*/
-static u8 minMaxQuery(AggInfo *pAggInfo, ExprList **ppMinMax){
- int eRet = WHERE_ORDERBY_NORMAL; /* Return value */
-
- *ppMinMax = 0;
- if( pAggInfo->nFunc==1 ){
- Expr *pExpr = pAggInfo->aFunc[0].pExpr; /* Aggregate function */
- ExprList *pEList = pExpr->x.pList; /* Arguments to agg function */
-
- assert( pExpr->op==TK_AGG_FUNCTION );
- if( pEList && pEList->nExpr==1 && pEList->a[0].pExpr->op==TK_AGG_COLUMN ){
- const char *zFunc = pExpr->u.zToken;
- if( sqlite3StrICmp(zFunc, "min")==0 ){
- eRet = WHERE_ORDERBY_MIN;
- *ppMinMax = pEList;
- }else if( sqlite3StrICmp(zFunc, "max")==0 ){
- eRet = WHERE_ORDERBY_MAX;
- *ppMinMax = pEList;
- }
- }
- }
-
- assert( *ppMinMax==0 || (*ppMinMax)->nExpr==1 );
+static u8 minMaxQuery(tdsqlite3 *db, Expr *pFunc, ExprList **ppMinMax){
+ int eRet = WHERE_ORDERBY_NORMAL; /* Return value */
+ ExprList *pEList = pFunc->x.pList; /* Arguments to agg function */
+ const char *zFunc; /* Name of aggregate function pFunc */
+ ExprList *pOrderBy;
+ u8 sortFlags;
+
+ assert( *ppMinMax==0 );
+ assert( pFunc->op==TK_AGG_FUNCTION );
+ assert( !IsWindowFunc(pFunc) );
+ if( pEList==0 || pEList->nExpr!=1 || ExprHasProperty(pFunc, EP_WinFunc) ){
+ return eRet;
+ }
+ zFunc = pFunc->u.zToken;
+ if( tdsqlite3StrICmp(zFunc, "min")==0 ){
+ eRet = WHERE_ORDERBY_MIN;
+ sortFlags = KEYINFO_ORDER_BIGNULL;
+ }else if( tdsqlite3StrICmp(zFunc, "max")==0 ){
+ eRet = WHERE_ORDERBY_MAX;
+ sortFlags = KEYINFO_ORDER_DESC;
+ }else{
+ return eRet;
+ }
+ *ppMinMax = pOrderBy = tdsqlite3ExprListDup(db, pEList, 0);
+ assert( pOrderBy!=0 || db->mallocFailed );
+ if( pOrderBy ) pOrderBy->a[0].sortFlags = sortFlags;
return eRet;
}
@@ -121386,7 +136028,7 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){
if( pExpr->op!=TK_AGG_FUNCTION ) return 0;
if( NEVER(pAggInfo->nFunc==0) ) return 0;
if( (pAggInfo->aFunc[0].pFunc->funcFlags&SQLITE_FUNC_COUNT)==0 ) return 0;
- if( pExpr->flags&EP_Distinct ) return 0;
+ if( ExprHasProperty(pExpr, EP_Distinct|EP_WinFunc) ) return 0;
return pTab;
}
@@ -121398,17 +136040,17 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){
** SQLITE_ERROR and leave an error in pParse. Otherwise, populate
** pFrom->pIndex and return SQLITE_OK.
*/
-SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *pParse, struct SrcList_item *pFrom){
+SQLITE_PRIVATE int tdsqlite3IndexedByLookup(Parse *pParse, struct SrcList_item *pFrom){
if( pFrom->pTab && pFrom->fg.isIndexedBy ){
Table *pTab = pFrom->pTab;
char *zIndexedBy = pFrom->u1.zIndexedBy;
Index *pIdx;
for(pIdx=pTab->pIndex;
- pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy);
+ pIdx && tdsqlite3StrICmp(pIdx->zName, zIndexedBy);
pIdx=pIdx->pNext
);
if( !pIdx ){
- sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy, 0);
+ tdsqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy, 0);
pParse->checkSchema = 1;
return SQLITE_ERROR;
}
@@ -121441,7 +136083,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){
int i;
Select *pNew;
Select *pX;
- sqlite3 *db;
+ tdsqlite3 *db;
struct ExprList_item *a;
SrcList *pNewSrc;
Parse *pParse;
@@ -121461,14 +136103,14 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){
pParse = pWalker->pParse;
db = pParse->db;
- pNew = sqlite3DbMallocZero(db, sizeof(*pNew) );
+ pNew = tdsqlite3DbMallocZero(db, sizeof(*pNew) );
if( pNew==0 ) return WRC_Abort;
memset(&dummy, 0, sizeof(dummy));
- pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0,0);
+ pNewSrc = tdsqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0,0);
if( pNewSrc==0 ) return WRC_Abort;
*pNew = *p;
p->pSrc = pNewSrc;
- p->pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ASTERISK, 0));
+ p->pEList = tdsqlite3ExprListAppend(pParse, 0, tdsqlite3Expr(db, TK_ASTERISK, 0));
p->op = TK_SELECT;
p->pWhere = 0;
pNew->pGroupBy = 0;
@@ -121477,13 +136119,15 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){
p->pPrior = 0;
p->pNext = 0;
p->pWith = 0;
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ p->pWinDefn = 0;
+#endif
p->selFlags &= ~SF_Compound;
assert( (p->selFlags & SF_Converted)==0 );
p->selFlags |= SF_Converted;
assert( pNew->pPrior!=0 );
pNew->pPrior->pNext = pNew;
pNew->pLimit = 0;
- pNew->pOffset = 0;
return WRC_Continue;
}
@@ -121494,7 +136138,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){
*/
static int cannotBeFunction(Parse *pParse, struct SrcList_item *pFrom){
if( pFrom->fg.isTabFunc ){
- sqlite3ErrorMsg(pParse, "'%s' is not a function", pFrom->zName);
+ tdsqlite3ErrorMsg(pParse, "'%s' is not a function", pFrom->zName);
return 1;
}
return 0;
@@ -121522,7 +136166,7 @@ static struct Cte *searchWith(
for(p=pWith; p; p=p->pOuter){
int i;
for(i=0; i<p->nCte; i++){
- if( sqlite3StrICmp(zName, p->a[i].zName)==0 ){
+ if( tdsqlite3StrICmp(zName, p->a[i].zName)==0 ){
*ppContext = p;
return &p->a[i];
}
@@ -121542,7 +136186,7 @@ static struct Cte *searchWith(
** bFree==0, the With object will be freed along with the SELECT
** statement with which it is associated.
*/
-SQLITE_PRIVATE void sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){
+SQLITE_PRIVATE void tdsqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){
assert( bFree==0 || (pParse->pWith==0 && pParse->pWithToFree==0) );
if( pWith ){
assert( pParse->pWith!=pWith );
@@ -121572,11 +136216,14 @@ static int withExpand(
struct SrcList_item *pFrom
){
Parse *pParse = pWalker->pParse;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
struct Cte *pCte; /* Matched CTE (or NULL if no match) */
With *pWith; /* WITH clause that pCte belongs to */
assert( pFrom->pTab==0 );
+ if( pParse->nErr ){
+ return SQLITE_ERROR;
+ }
pCte = searchWith(pParse->pWith, pFrom, &pWith);
if( pCte ){
@@ -121592,20 +136239,20 @@ static int withExpand(
** early. If pCte->zCteErr is NULL, then this is not a recursive reference.
** In this case, proceed. */
if( pCte->zCteErr ){
- sqlite3ErrorMsg(pParse, pCte->zCteErr, pCte->zName);
+ tdsqlite3ErrorMsg(pParse, pCte->zCteErr, pCte->zName);
return SQLITE_ERROR;
}
if( cannotBeFunction(pParse, pFrom) ) return SQLITE_ERROR;
assert( pFrom->pTab==0 );
- pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table));
+ pFrom->pTab = pTab = tdsqlite3DbMallocZero(db, sizeof(Table));
if( pTab==0 ) return WRC_Abort;
- pTab->nRef = 1;
- pTab->zName = sqlite3DbStrDup(db, pCte->zName);
+ pTab->nTabRef = 1;
+ pTab->zName = tdsqlite3DbStrDup(db, pCte->zName);
pTab->iPKey = -1;
- pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
+ pTab->nRowLogEst = 200; assert( 200==tdsqlite3LogEst(1048576) );
pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid;
- pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0);
+ pFrom->pSelect = tdsqlite3SelectDup(db, pCte->pSelect, 0);
if( db->mallocFailed ) return SQLITE_NOMEM_BKPT;
assert( pFrom->pSelect );
@@ -121619,36 +136266,45 @@ static int withExpand(
struct SrcList_item *pItem = &pSrc->a[i];
if( pItem->zDatabase==0
&& pItem->zName!=0
- && 0==sqlite3StrICmp(pItem->zName, pCte->zName)
+ && 0==tdsqlite3StrICmp(pItem->zName, pCte->zName)
){
pItem->pTab = pTab;
pItem->fg.isRecursive = 1;
- pTab->nRef++;
+ pTab->nTabRef++;
pSel->selFlags |= SF_Recursive;
}
}
}
/* Only one recursive reference is permitted. */
- if( pTab->nRef>2 ){
- sqlite3ErrorMsg(
+ if( pTab->nTabRef>2 ){
+ tdsqlite3ErrorMsg(
pParse, "multiple references to recursive table: %s", pCte->zName
);
return SQLITE_ERROR;
}
- assert( pTab->nRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nRef==2 ));
+ assert( pTab->nTabRef==1 ||
+ ((pSel->selFlags&SF_Recursive) && pTab->nTabRef==2 ));
pCte->zCteErr = "circular reference: %s";
pSavedWith = pParse->pWith;
pParse->pWith = pWith;
- sqlite3WalkSelect(pWalker, bMayRecursive ? pSel->pPrior : pSel);
+ if( bMayRecursive ){
+ Select *pPrior = pSel->pPrior;
+ assert( pPrior->pWith==0 );
+ pPrior->pWith = pSel->pWith;
+ tdsqlite3WalkSelect(pWalker, pPrior);
+ pPrior->pWith = 0;
+ }else{
+ tdsqlite3WalkSelect(pWalker, pSel);
+ }
pParse->pWith = pWith;
for(pLeft=pSel; pLeft->pPrior; pLeft=pLeft->pPrior);
pEList = pLeft->pEList;
if( pCte->pCols ){
if( pEList && pEList->nExpr!=pCte->pCols->nExpr ){
- sqlite3ErrorMsg(pParse, "table %s has %d values for %d columns",
+ tdsqlite3ErrorMsg(pParse, "table %s has %d values for %d columns",
pCte->zName, pEList->nExpr, pCte->pCols->nExpr
);
pParse->pWith = pSavedWith;
@@ -121657,14 +136313,14 @@ static int withExpand(
pEList = pCte->pCols;
}
- sqlite3ColumnsFromExprList(pParse, pEList, &pTab->nCol, &pTab->aCol);
+ tdsqlite3ColumnsFromExprList(pParse, pEList, &pTab->nCol, &pTab->aCol);
if( bMayRecursive ){
if( pSel->selFlags & SF_Recursive ){
pCte->zCteErr = "multiple recursive references: %s";
}else{
pCte->zCteErr = "recursive reference in a subquery: %s";
}
- sqlite3WalkSelect(pWalker, pSel);
+ tdsqlite3WalkSelect(pWalker, pSel);
}
pCte->zCteErr = 0;
pParse->pWith = pSavedWith;
@@ -121680,15 +136336,17 @@ static int withExpand(
** clause, pop it from the stack stored as part of the Parse object.
**
** This function is used as the xSelectCallback2() callback by
-** sqlite3SelectExpand() when walking a SELECT tree to resolve table
+** tdsqlite3SelectExpand() when walking a SELECT tree to resolve table
** names and other FROM clause elements.
*/
static void selectPopWith(Walker *pWalker, Select *p){
Parse *pParse = pWalker->pParse;
- With *pWith = findRightmost(p)->pWith;
- if( pWith!=0 ){
- assert( pParse->pWith==pWith );
- pParse->pWith = pWith->pOuter;
+ if( OK_IF_ALWAYS_TRUE(pParse->pWith) && p->pPrior==0 ){
+ With *pWith = findRightmost(p)->pWith;
+ if( pWith!=0 ){
+ assert( pParse->pWith==pWith || pParse->nErr );
+ pParse->pWith = pWith->pOuter;
+ }
}
}
#else
@@ -121696,6 +136354,35 @@ static void selectPopWith(Walker *pWalker, Select *p){
#endif
/*
+** The SrcList_item structure passed as the second argument represents a
+** sub-query in the FROM clause of a SELECT statement. This function
+** allocates and populates the SrcList_item.pTab object. If successful,
+** SQLITE_OK is returned. Otherwise, if an OOM error is encountered,
+** SQLITE_NOMEM.
+*/
+SQLITE_PRIVATE int tdsqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFrom){
+ Select *pSel = pFrom->pSelect;
+ Table *pTab;
+
+ assert( pSel );
+ pFrom->pTab = pTab = tdsqlite3DbMallocZero(pParse->db, sizeof(Table));
+ if( pTab==0 ) return SQLITE_NOMEM;
+ pTab->nTabRef = 1;
+ if( pFrom->zAlias ){
+ pTab->zName = tdsqlite3DbStrDup(pParse->db, pFrom->zAlias);
+ }else{
+ pTab->zName = tdsqlite3MPrintf(pParse->db, "subquery_%u", pSel->selId);
+ }
+ while( pSel->pPrior ){ pSel = pSel->pPrior; }
+ tdsqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol);
+ pTab->iPKey = -1;
+ pTab->nRowLogEst = 200; assert( 200==tdsqlite3LogEst(1048576) );
+ pTab->tabFlags |= TF_Ephemeral;
+
+ return pParse->nErr ? SQLITE_ERROR : SQLITE_OK;
+}
+
+/*
** This routine is a Walker callback for "expanding" a SELECT statement.
** "Expanding" means to do the following:
**
@@ -121725,27 +136412,31 @@ static int selectExpander(Walker *pWalker, Select *p){
SrcList *pTabList;
ExprList *pEList;
struct SrcList_item *pFrom;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Expr *pE, *pRight, *pExpr;
u16 selFlags = p->selFlags;
+ u32 elistFlags = 0;
p->selFlags |= SF_Expanded;
if( db->mallocFailed ){
return WRC_Abort;
}
- if( NEVER(p->pSrc==0) || (selFlags & SF_Expanded)!=0 ){
+ assert( p->pSrc!=0 );
+ if( (selFlags & SF_Expanded)!=0 ){
return WRC_Prune;
}
+ if( pWalker->eCode ){
+ /* Renumber selId because it has been copied from a view */
+ p->selId = ++pParse->nSelect;
+ }
pTabList = p->pSrc;
pEList = p->pEList;
- if( pWalker->xSelectCallback2==selectPopWith ){
- sqlite3WithPush(pParse, findRightmost(p)->pWith, 0);
- }
+ tdsqlite3WithPush(pParse, p->pWith, 0);
/* Make sure cursor numbers have been assigned to all entries in
** the FROM clause of the SELECT statement.
*/
- sqlite3SrcListAssignCursors(pParse, pTabList);
+ tdsqlite3SrcListAssignCursors(pParse, pTabList);
/* Look up every table named in the FROM clause of the select. If
** an entry of the FROM clause is a subquery instead of a table or view,
@@ -121766,56 +136457,64 @@ static int selectExpander(Walker *pWalker, Select *p){
/* A sub-query in the FROM clause of a SELECT */
assert( pSel!=0 );
assert( pFrom->pTab==0 );
- if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort;
- pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table));
- if( pTab==0 ) return WRC_Abort;
- pTab->nRef = 1;
- pTab->zName = sqlite3MPrintf(db, "sqlite_sq_%p", (void*)pTab);
- while( pSel->pPrior ){ pSel = pSel->pPrior; }
- sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol);
- pTab->iPKey = -1;
- pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) );
- pTab->tabFlags |= TF_Ephemeral;
+ if( tdsqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort;
+ if( tdsqlite3ExpandSubquery(pParse, pFrom) ) return WRC_Abort;
#endif
}else{
/* An ordinary table or view name in the FROM clause */
assert( pFrom->pTab==0 );
- pFrom->pTab = pTab = sqlite3LocateTableItem(pParse, 0, pFrom);
+ pFrom->pTab = pTab = tdsqlite3LocateTableItem(pParse, 0, pFrom);
if( pTab==0 ) return WRC_Abort;
- if( pTab->nRef==0xffff ){
- sqlite3ErrorMsg(pParse, "too many references to \"%s\": max 65535",
+ if( pTab->nTabRef>=0xffff ){
+ tdsqlite3ErrorMsg(pParse, "too many references to \"%s\": max 65535",
pTab->zName);
pFrom->pTab = 0;
return WRC_Abort;
}
- pTab->nRef++;
+ pTab->nTabRef++;
if( !IsVirtual(pTab) && cannotBeFunction(pParse, pFrom) ){
return WRC_Abort;
}
-#if !defined(SQLITE_OMIT_VIEW) || !defined (SQLITE_OMIT_VIRTUALTABLE)
+#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE)
if( IsVirtual(pTab) || pTab->pSelect ){
i16 nCol;
- if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort;
+ u8 eCodeOrig = pWalker->eCode;
+ if( tdsqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort;
assert( pFrom->pSelect==0 );
- pFrom->pSelect = sqlite3SelectDup(db, pTab->pSelect, 0);
- sqlite3SelectSetName(pFrom->pSelect, pTab->zName);
+ if( pTab->pSelect && (db->flags & SQLITE_EnableView)==0 ){
+ tdsqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited",
+ pTab->zName);
+ }
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( IsVirtual(pTab)
+ && pFrom->fg.fromDDL
+ && ALWAYS(pTab->pVTable!=0)
+ && pTab->pVTable->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0)
+ ){
+ tdsqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"",
+ pTab->zName);
+ }
+#endif
+ pFrom->pSelect = tdsqlite3SelectDup(db, pTab->pSelect, 0);
nCol = pTab->nCol;
pTab->nCol = -1;
- sqlite3WalkSelect(pWalker, pFrom->pSelect);
+ pWalker->eCode = 1; /* Turn on Select.selId renumbering */
+ tdsqlite3WalkSelect(pWalker, pFrom->pSelect);
+ pWalker->eCode = eCodeOrig;
pTab->nCol = nCol;
}
#endif
}
/* Locate the index named by the INDEXED BY clause, if any. */
- if( sqlite3IndexedByLookup(pParse, pFrom) ){
+ if( tdsqlite3IndexedByLookup(pParse, pFrom) ){
return WRC_Abort;
}
}
/* Process NATURAL keywords, and ON and USING clauses of joins.
*/
- if( db->mallocFailed || sqliteProcessJoin(pParse, p) ){
+ if( pParse->nErr || db->mallocFailed || sqliteProcessJoin(pParse, p) ){
return WRC_Abort;
}
@@ -121836,6 +136535,7 @@ static int selectExpander(Walker *pWalker, Select *p){
assert( pE->op!=TK_DOT || pE->pRight!=0 );
assert( pE->op!=TK_DOT || (pE->pLeft!=0 && pE->pLeft->op==TK_ID) );
if( pE->op==TK_DOT && pE->pRight->op==TK_ASTERISK ) break;
+ elistFlags |= pE->flags;
}
if( k<pEList->nExpr ){
/*
@@ -121851,6 +136551,7 @@ static int selectExpander(Walker *pWalker, Select *p){
for(k=0; k<pEList->nExpr; k++){
pE = a[k].pExpr;
+ elistFlags |= pE->flags;
pRight = pE->pRight;
assert( pE->op!=TK_DOT || pRight!=0 );
if( pE->op!=TK_ASTERISK
@@ -121858,12 +136559,11 @@ static int selectExpander(Walker *pWalker, Select *p){
){
/* This particular expression does not need to be expanded.
*/
- pNew = sqlite3ExprListAppend(pParse, pNew, a[k].pExpr);
+ pNew = tdsqlite3ExprListAppend(pParse, pNew, a[k].pExpr);
if( pNew ){
- pNew->a[pNew->nExpr-1].zName = a[k].zName;
- pNew->a[pNew->nExpr-1].zSpan = a[k].zSpan;
- a[k].zName = 0;
- a[k].zSpan = 0;
+ pNew->a[pNew->nExpr-1].zEName = a[k].zEName;
+ pNew->a[pNew->nExpr-1].eEName = a[k].eEName;
+ a[k].zEName = 0;
}
a[k].pExpr = 0;
}else{
@@ -121888,10 +136588,10 @@ static int selectExpander(Walker *pWalker, Select *p){
if( db->mallocFailed ) break;
if( pSub==0 || (pSub->selFlags & SF_NestedFrom)==0 ){
pSub = 0;
- if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){
+ if( zTName && tdsqlite3StrICmp(zTName, zTabName)!=0 ){
continue;
}
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*";
}
for(j=0; j<pTab->nCol; j++){
@@ -121902,7 +136602,7 @@ static int selectExpander(Walker *pWalker, Select *p){
assert( zName );
if( zTName && pSub
- && sqlite3MatchSpanName(pSub->pEList->a[j].zSpan, 0, zTName, 0)==0
+ && tdsqlite3MatchEName(&pSub->pEList->a[j], 0, zTName, 0)==0
){
continue;
}
@@ -121920,72 +136620,76 @@ static int selectExpander(Walker *pWalker, Select *p){
if( i>0 && zTName==0 ){
if( (pFrom->fg.jointype & JT_NATURAL)!=0
- && tableAndColumnIndex(pTabList, i, zName, 0, 0)
+ && tableAndColumnIndex(pTabList, i, zName, 0, 0, 1)
){
/* In a NATURAL join, omit the join columns from the
** table to the right of the join */
continue;
}
- if( sqlite3IdListIndex(pFrom->pUsing, zName)>=0 ){
+ if( tdsqlite3IdListIndex(pFrom->pUsing, zName)>=0 ){
/* In a join with a USING clause, omit columns in the
** using clause from the table on the right. */
continue;
}
}
- pRight = sqlite3Expr(db, TK_ID, zName);
+ pRight = tdsqlite3Expr(db, TK_ID, zName);
zColname = zName;
zToFree = 0;
if( longNames || pTabList->nSrc>1 ){
Expr *pLeft;
- pLeft = sqlite3Expr(db, TK_ID, zTabName);
- pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0);
+ pLeft = tdsqlite3Expr(db, TK_ID, zTabName);
+ pExpr = tdsqlite3PExpr(pParse, TK_DOT, pLeft, pRight);
if( zSchemaName ){
- pLeft = sqlite3Expr(db, TK_ID, zSchemaName);
- pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr, 0);
+ pLeft = tdsqlite3Expr(db, TK_ID, zSchemaName);
+ pExpr = tdsqlite3PExpr(pParse, TK_DOT, pLeft, pExpr);
}
if( longNames ){
- zColname = sqlite3MPrintf(db, "%s.%s", zTabName, zName);
+ zColname = tdsqlite3MPrintf(db, "%s.%s", zTabName, zName);
zToFree = zColname;
}
}else{
pExpr = pRight;
}
- pNew = sqlite3ExprListAppend(pParse, pNew, pExpr);
- sqlite3TokenInit(&sColname, zColname);
- sqlite3ExprListSetName(pParse, pNew, &sColname, 0);
+ pNew = tdsqlite3ExprListAppend(pParse, pNew, pExpr);
+ tdsqlite3TokenInit(&sColname, zColname);
+ tdsqlite3ExprListSetName(pParse, pNew, &sColname, 0);
if( pNew && (p->selFlags & SF_NestedFrom)!=0 ){
struct ExprList_item *pX = &pNew->a[pNew->nExpr-1];
+ tdsqlite3DbFree(db, pX->zEName);
if( pSub ){
- pX->zSpan = sqlite3DbStrDup(db, pSub->pEList->a[j].zSpan);
- testcase( pX->zSpan==0 );
+ pX->zEName = tdsqlite3DbStrDup(db, pSub->pEList->a[j].zEName);
+ testcase( pX->zEName==0 );
}else{
- pX->zSpan = sqlite3MPrintf(db, "%s.%s.%s",
+ pX->zEName = tdsqlite3MPrintf(db, "%s.%s.%s",
zSchemaName, zTabName, zColname);
- testcase( pX->zSpan==0 );
+ testcase( pX->zEName==0 );
}
- pX->bSpanIsTab = 1;
+ pX->eEName = ENAME_TAB;
}
- sqlite3DbFree(db, zToFree);
+ tdsqlite3DbFree(db, zToFree);
}
}
if( !tableSeen ){
if( zTName ){
- sqlite3ErrorMsg(pParse, "no such table: %s", zTName);
+ tdsqlite3ErrorMsg(pParse, "no such table: %s", zTName);
}else{
- sqlite3ErrorMsg(pParse, "no tables specified");
+ tdsqlite3ErrorMsg(pParse, "no tables specified");
}
}
}
}
- sqlite3ExprListDelete(db, pEList);
+ tdsqlite3ExprListDelete(db, pEList);
p->pEList = pNew;
}
-#if SQLITE_MAX_COLUMN
- if( p->pEList && p->pEList->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){
- sqlite3ErrorMsg(pParse, "too many columns in result set");
- return WRC_Abort;
+ if( p->pEList ){
+ if( p->pEList->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){
+ tdsqlite3ErrorMsg(pParse, "too many columns in result set");
+ return WRC_Abort;
+ }
+ if( (elistFlags & (EP_HasFunc|EP_Subquery))!=0 ){
+ p->selFlags |= SF_ComplexResult;
+ }
}
-#endif
return WRC_Continue;
}
@@ -121998,12 +136702,31 @@ static int selectExpander(Walker *pWalker, Select *p){
** Walker.xSelectCallback is set to do something useful for every
** subquery in the parser tree.
*/
-SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker *NotUsed, Expr *NotUsed2){
+SQLITE_PRIVATE int tdsqlite3ExprWalkNoop(Walker *NotUsed, Expr *NotUsed2){
UNUSED_PARAMETER2(NotUsed, NotUsed2);
return WRC_Continue;
}
/*
+** No-op routine for the parse-tree walker for SELECT statements.
+** subquery in the parser tree.
+*/
+SQLITE_PRIVATE int tdsqlite3SelectWalkNoop(Walker *NotUsed, Select *NotUsed2){
+ UNUSED_PARAMETER2(NotUsed, NotUsed2);
+ return WRC_Continue;
+}
+
+#if SQLITE_DEBUG
+/*
+** Always assert. This xSelectCallback2 implementation proves that the
+** xSelectCallback2 is never invoked.
+*/
+SQLITE_PRIVATE void tdsqlite3SelectWalkAssert2(Walker *NotUsed, Select *NotUsed2){
+ UNUSED_PARAMETER2(NotUsed, NotUsed2);
+ assert( 0 );
+}
+#endif
+/*
** This routine "expands" a SELECT statement and all of its subqueries.
** For additional information on what it means to "expand" a SELECT
** statement, see the comment on the selectExpand worker callback above.
@@ -122016,26 +136739,25 @@ SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker *NotUsed, Expr *NotUsed2){
** The calling function can detect the problem by looking at pParse->nErr
** and/or pParse->db->mallocFailed.
*/
-static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){
+static void tdsqlite3SelectExpand(Parse *pParse, Select *pSelect){
Walker w;
- memset(&w, 0, sizeof(w));
- w.xExprCallback = sqlite3ExprWalkNoop;
+ w.xExprCallback = tdsqlite3ExprWalkNoop;
w.pParse = pParse;
- if( pParse->hasCompound ){
+ if( OK_IF_ALWAYS_TRUE(pParse->hasCompound) ){
w.xSelectCallback = convertCompoundSelectToSubquery;
- sqlite3WalkSelect(&w, pSelect);
+ w.xSelectCallback2 = 0;
+ tdsqlite3WalkSelect(&w, pSelect);
}
w.xSelectCallback = selectExpander;
- if( (pSelect->selFlags & SF_MultiValue)==0 ){
- w.xSelectCallback2 = selectPopWith;
- }
- sqlite3WalkSelect(&w, pSelect);
+ w.xSelectCallback2 = selectPopWith;
+ w.eCode = 0;
+ tdsqlite3WalkSelect(&w, pSelect);
}
#ifndef SQLITE_OMIT_SUBQUERY
/*
-** This is a Walker.xSelectCallback callback for the sqlite3SelectTypeInfo()
+** This is a Walker.xSelectCallback callback for the tdsqlite3SelectTypeInfo()
** interface.
**
** For each FROM-clause subquery, add Column.zType and Column.zColl
@@ -122054,7 +136776,7 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){
struct SrcList_item *pFrom;
assert( p->selFlags & SF_Resolved );
- assert( (p->selFlags & SF_HasTypeInfo)==0 );
+ if( p->selFlags & SF_HasTypeInfo ) return;
p->selFlags |= SF_HasTypeInfo;
pParse = pWalker->pParse;
pTabList = p->pSrc;
@@ -122066,7 +136788,8 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){
Select *pSel = pFrom->pSelect;
if( pSel ){
while( pSel->pPrior ) pSel = pSel->pPrior;
- sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSel);
+ tdsqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSel,
+ SQLITE_AFF_NONE);
}
}
}
@@ -122081,14 +136804,14 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){
**
** Use this routine after name resolution.
*/
-static void sqlite3SelectAddTypeInfo(Parse *pParse, Select *pSelect){
+static void tdsqlite3SelectAddTypeInfo(Parse *pParse, Select *pSelect){
#ifndef SQLITE_OMIT_SUBQUERY
Walker w;
- memset(&w, 0, sizeof(w));
+ w.xSelectCallback = tdsqlite3SelectWalkNoop;
w.xSelectCallback2 = selectAddSubqueryTypeInfo;
- w.xExprCallback = sqlite3ExprWalkNoop;
+ w.xExprCallback = tdsqlite3ExprWalkNoop;
w.pParse = pParse;
- sqlite3WalkSelect(&w, pSelect);
+ tdsqlite3WalkSelect(&w, pSelect);
#endif
}
@@ -122105,21 +136828,19 @@ static void sqlite3SelectAddTypeInfo(Parse *pParse, Select *pSelect){
**
** This routine acts recursively on all subqueries within the SELECT.
*/
-SQLITE_PRIVATE void sqlite3SelectPrep(
+SQLITE_PRIVATE void tdsqlite3SelectPrep(
Parse *pParse, /* The parser context */
Select *p, /* The SELECT statement being coded. */
NameContext *pOuterNC /* Name context for container */
){
- sqlite3 *db;
- if( NEVER(p==0) ) return;
- db = pParse->db;
- if( db->mallocFailed ) return;
+ assert( p!=0 || pParse->db->mallocFailed );
+ if( pParse->db->mallocFailed ) return;
if( p->selFlags & SF_HasTypeInfo ) return;
- sqlite3SelectExpand(pParse, p);
- if( pParse->nErr || db->mallocFailed ) return;
- sqlite3ResolveSelectNames(pParse, p, pOuterNC);
- if( pParse->nErr || db->mallocFailed ) return;
- sqlite3SelectAddTypeInfo(pParse, p);
+ tdsqlite3SelectExpand(pParse, p);
+ if( pParse->nErr || pParse->db->mallocFailed ) return;
+ tdsqlite3ResolveSelectNames(pParse, p, pOuterNC);
+ if( pParse->nErr || pParse->db->mallocFailed ) return;
+ tdsqlite3SelectAddTypeInfo(pParse, p);
}
/*
@@ -122149,18 +136870,18 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){
&& pAggInfo->aFunc[i].iMem<=pAggInfo->mxReg );
}
#endif
- sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->mnReg, pAggInfo->mxReg);
+ tdsqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->mnReg, pAggInfo->mxReg);
for(pFunc=pAggInfo->aFunc, i=0; i<pAggInfo->nFunc; i++, pFunc++){
if( pFunc->iDistinct>=0 ){
Expr *pE = pFunc->pExpr;
assert( !ExprHasProperty(pE, EP_xIsSelect) );
if( pE->x.pList==0 || pE->x.pList->nExpr!=1 ){
- sqlite3ErrorMsg(pParse, "DISTINCT aggregates must have exactly one "
+ tdsqlite3ErrorMsg(pParse, "DISTINCT aggregates must have exactly one "
"argument");
pFunc->iDistinct = -1;
}else{
- KeyInfo *pKeyInfo = keyInfoFromExprList(pParse, pE->x.pList, 0, 0);
- sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0,
+ KeyInfo *pKeyInfo = tdsqlite3KeyInfoFromExprList(pParse, pE->x.pList,0,0);
+ tdsqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0,
(char*)pKeyInfo, P4_KEYINFO);
}
}
@@ -122178,16 +136899,22 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){
for(i=0, pF=pAggInfo->aFunc; i<pAggInfo->nFunc; i++, pF++){
ExprList *pList = pF->pExpr->x.pList;
assert( !ExprHasProperty(pF->pExpr, EP_xIsSelect) );
- sqlite3VdbeAddOp4(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0, 0,
- (void*)pF->pFunc, P4_FUNCDEF);
+ tdsqlite3VdbeAddOp2(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0);
+ tdsqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
}
}
+
/*
** Update the accumulator memory cells for an aggregate based on
** the current cursor position.
+**
+** If regAcc is non-zero and there are no min() or max() aggregates
+** in pAggInfo, then only populate the pAggInfo->nAccumulator accumulator
+** registers if register regAcc contains 0. The caller will take care
+** of setting and clearing regAcc.
*/
-static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){
+static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){
Vdbe *v = pParse->pVdbe;
int i;
int regHit = 0;
@@ -122202,16 +136929,37 @@ static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){
int regAgg;
ExprList *pList = pF->pExpr->x.pList;
assert( !ExprHasProperty(pF->pExpr, EP_xIsSelect) );
+ assert( !IsWindowFunc(pF->pExpr) );
+ if( ExprHasProperty(pF->pExpr, EP_WinFunc) ){
+ Expr *pFilter = pF->pExpr->y.pWin->pFilter;
+ if( pAggInfo->nAccumulator
+ && (pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL)
+ ){
+ if( regHit==0 ) regHit = ++pParse->nMem;
+ /* If this is the first row of the group (regAcc==0), clear the
+ ** "magnet" register regHit so that the accumulator registers
+ ** are populated if the FILTER clause jumps over the the
+ ** invocation of min() or max() altogether. Or, if this is not
+ ** the first row (regAcc==1), set the magnet register so that the
+ ** accumulators are not populated unless the min()/max() is invoked and
+ ** indicates that they should be. */
+ tdsqlite3VdbeAddOp2(v, OP_Copy, regAcc, regHit);
+ }
+ addrNext = tdsqlite3VdbeMakeLabel(pParse);
+ tdsqlite3ExprIfFalse(pParse, pFilter, addrNext, SQLITE_JUMPIFNULL);
+ }
if( pList ){
nArg = pList->nExpr;
- regAgg = sqlite3GetTempRange(pParse, nArg);
- sqlite3ExprCodeExprList(pParse, pList, regAgg, 0, SQLITE_ECEL_DUP);
+ regAgg = tdsqlite3GetTempRange(pParse, nArg);
+ tdsqlite3ExprCodeExprList(pParse, pList, regAgg, 0, SQLITE_ECEL_DUP);
}else{
nArg = 0;
regAgg = 0;
}
if( pF->iDistinct>=0 ){
- addrNext = sqlite3VdbeMakeLabel(v);
+ if( addrNext==0 ){
+ addrNext = tdsqlite3VdbeMakeLabel(pParse);
+ }
testcase( nArg==0 ); /* Error condition */
testcase( nArg>1 ); /* Also an error */
codeDistinct(pParse, pF->iDistinct, addrNext, 1, regAgg);
@@ -122222,46 +136970,35 @@ static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){
int j;
assert( pList!=0 ); /* pList!=0 if pF->pFunc has NEEDCOLL */
for(j=0, pItem=pList->a; !pColl && j<nArg; j++, pItem++){
- pColl = sqlite3ExprCollSeq(pParse, pItem->pExpr);
+ pColl = tdsqlite3ExprCollSeq(pParse, pItem->pExpr);
}
if( !pColl ){
pColl = pParse->db->pDfltColl;
}
if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem;
- sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ);
+ tdsqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ);
}
- sqlite3VdbeAddOp4(v, OP_AggStep0, 0, regAgg, pF->iMem,
- (void*)pF->pFunc, P4_FUNCDEF);
- sqlite3VdbeChangeP5(v, (u8)nArg);
- sqlite3ExprCacheAffinityChange(pParse, regAgg, nArg);
- sqlite3ReleaseTempRange(pParse, regAgg, nArg);
+ tdsqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, pF->iMem);
+ tdsqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
+ tdsqlite3VdbeChangeP5(v, (u8)nArg);
+ tdsqlite3ReleaseTempRange(pParse, regAgg, nArg);
if( addrNext ){
- sqlite3VdbeResolveLabel(v, addrNext);
- sqlite3ExprCacheClear(pParse);
+ tdsqlite3VdbeResolveLabel(v, addrNext);
}
}
-
- /* Before populating the accumulator registers, clear the column cache.
- ** Otherwise, if any of the required column values are already present
- ** in registers, sqlite3ExprCode() may use OP_SCopy to copy the value
- ** to pC->iMem. But by the time the value is used, the original register
- ** may have been used, invalidating the underlying buffer holding the
- ** text or blob value. See ticket [883034dcb5].
- **
- ** Another solution would be to change the OP_SCopy used to copy cached
- ** values to an OP_Copy.
- */
+ if( regHit==0 && pAggInfo->nAccumulator ){
+ regHit = regAcc;
+ }
if( regHit ){
- addrHitTest = sqlite3VdbeAddOp1(v, OP_If, regHit); VdbeCoverage(v);
+ addrHitTest = tdsqlite3VdbeAddOp1(v, OP_If, regHit); VdbeCoverage(v);
}
- sqlite3ExprCacheClear(pParse);
for(i=0, pC=pAggInfo->aCol; i<pAggInfo->nAccumulator; i++, pC++){
- sqlite3ExprCode(pParse, pC->pExpr, pC->iMem);
+ tdsqlite3ExprCode(pParse, pC->pExpr, pC->iMem);
}
+
pAggInfo->directMode = 0;
- sqlite3ExprCacheClear(pParse);
if( addrHitTest ){
- sqlite3VdbeJumpHere(v, addrHitTest);
+ tdsqlite3VdbeJumpHere(v, addrHitTest);
}
}
@@ -122277,14 +137014,11 @@ static void explainSimpleCount(
){
if( pParse->explain==2 ){
int bCover = (pIdx!=0 && (HasRowid(pTab) || !IsPrimaryKeyIndex(pIdx)));
- char *zEqp = sqlite3MPrintf(pParse->db, "SCAN TABLE %s%s%s",
+ tdsqlite3VdbeExplain(pParse, 0, "SCAN TABLE %s%s%s",
pTab->zName,
bCover ? " USING COVERING INDEX " : "",
bCover ? pIdx->zName : ""
);
- sqlite3VdbeAddOp4(
- pParse->pVdbe, OP_Explain, pParse->iSelectId, 0, 0, zEqp, P4_DYNAMIC
- );
}
}
#else
@@ -122292,6 +137026,190 @@ static void explainSimpleCount(
#endif
/*
+** tdsqlite3WalkExpr() callback used by havingToWhere().
+**
+** If the node passed to the callback is a TK_AND node, return
+** WRC_Continue to tell tdsqlite3WalkExpr() to iterate through child nodes.
+**
+** Otherwise, return WRC_Prune. In this case, also check if the
+** sub-expression matches the criteria for being moved to the WHERE
+** clause. If so, add it to the WHERE clause and replace the sub-expression
+** within the HAVING expression with a constant "1".
+*/
+static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){
+ if( pExpr->op!=TK_AND ){
+ Select *pS = pWalker->u.pSelect;
+ if( tdsqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) ){
+ tdsqlite3 *db = pWalker->pParse->db;
+ Expr *pNew = tdsqlite3Expr(db, TK_INTEGER, "1");
+ if( pNew ){
+ Expr *pWhere = pS->pWhere;
+ SWAP(Expr, *pNew, *pExpr);
+ pNew = tdsqlite3ExprAnd(pWalker->pParse, pWhere, pNew);
+ pS->pWhere = pNew;
+ pWalker->eCode = 1;
+ }
+ }
+ return WRC_Prune;
+ }
+ return WRC_Continue;
+}
+
+/*
+** Transfer eligible terms from the HAVING clause of a query, which is
+** processed after grouping, to the WHERE clause, which is processed before
+** grouping. For example, the query:
+**
+** SELECT * FROM <tables> WHERE a=? GROUP BY b HAVING b=? AND c=?
+**
+** can be rewritten as:
+**
+** SELECT * FROM <tables> WHERE a=? AND b=? GROUP BY b HAVING c=?
+**
+** A term of the HAVING expression is eligible for transfer if it consists
+** entirely of constants and expressions that are also GROUP BY terms that
+** use the "BINARY" collation sequence.
+*/
+static void havingToWhere(Parse *pParse, Select *p){
+ Walker sWalker;
+ memset(&sWalker, 0, sizeof(sWalker));
+ sWalker.pParse = pParse;
+ sWalker.xExprCallback = havingToWhereExprCb;
+ sWalker.u.pSelect = p;
+ tdsqlite3WalkExpr(&sWalker, p->pHaving);
+#if SELECTTRACE_ENABLED
+ if( sWalker.eCode && (tdsqlite3SelectTrace & 0x100)!=0 ){
+ SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n"));
+ tdsqlite3TreeViewSelect(0, p, 0);
+ }
+#endif
+}
+
+/*
+** Check to see if the pThis entry of pTabList is a self-join of a prior view.
+** If it is, then return the SrcList_item for the prior view. If it is not,
+** then return 0.
+*/
+static struct SrcList_item *isSelfJoinView(
+ SrcList *pTabList, /* Search for self-joins in this FROM clause */
+ struct SrcList_item *pThis /* Search for prior reference to this subquery */
+){
+ struct SrcList_item *pItem;
+ for(pItem = pTabList->a; pItem<pThis; pItem++){
+ Select *pS1;
+ if( pItem->pSelect==0 ) continue;
+ if( pItem->fg.viaCoroutine ) continue;
+ if( pItem->zName==0 ) continue;
+ assert( pItem->pTab!=0 );
+ assert( pThis->pTab!=0 );
+ if( pItem->pTab->pSchema!=pThis->pTab->pSchema ) continue;
+ if( tdsqlite3_stricmp(pItem->zName, pThis->zName)!=0 ) continue;
+ pS1 = pItem->pSelect;
+ if( pItem->pTab->pSchema==0 && pThis->pSelect->selId!=pS1->selId ){
+ /* The query flattener left two different CTE tables with identical
+ ** names in the same FROM clause. */
+ continue;
+ }
+ if( tdsqlite3ExprCompare(0, pThis->pSelect->pWhere, pS1->pWhere, -1)
+ || tdsqlite3ExprCompare(0, pThis->pSelect->pHaving, pS1->pHaving, -1)
+ ){
+ /* The view was modified by some other optimization such as
+ ** pushDownWhereTerms() */
+ continue;
+ }
+ return pItem;
+ }
+ return 0;
+}
+
+#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION
+/*
+** Attempt to transform a query of the form
+**
+** SELECT count(*) FROM (SELECT x FROM t1 UNION ALL SELECT y FROM t2)
+**
+** Into this:
+**
+** SELECT (SELECT count(*) FROM t1)+(SELECT count(*) FROM t2)
+**
+** The transformation only works if all of the following are true:
+**
+** * The subquery is a UNION ALL of two or more terms
+** * The subquery does not have a LIMIT clause
+** * There is no WHERE or GROUP BY or HAVING clauses on the subqueries
+** * The outer query is a simple count(*) with no WHERE clause or other
+** extraneous syntax.
+**
+** Return TRUE if the optimization is undertaken.
+*/
+static int countOfViewOptimization(Parse *pParse, Select *p){
+ Select *pSub, *pPrior;
+ Expr *pExpr;
+ Expr *pCount;
+ tdsqlite3 *db;
+ if( (p->selFlags & SF_Aggregate)==0 ) return 0; /* This is an aggregate */
+ if( p->pEList->nExpr!=1 ) return 0; /* Single result column */
+ if( p->pWhere ) return 0;
+ if( p->pGroupBy ) return 0;
+ pExpr = p->pEList->a[0].pExpr;
+ if( pExpr->op!=TK_AGG_FUNCTION ) return 0; /* Result is an aggregate */
+ if( tdsqlite3_stricmp(pExpr->u.zToken,"count") ) return 0; /* Is count() */
+ if( pExpr->x.pList!=0 ) return 0; /* Must be count(*) */
+ if( p->pSrc->nSrc!=1 ) return 0; /* One table in FROM */
+ pSub = p->pSrc->a[0].pSelect;
+ if( pSub==0 ) return 0; /* The FROM is a subquery */
+ if( pSub->pPrior==0 ) return 0; /* Must be a compound ry */
+ do{
+ if( pSub->op!=TK_ALL && pSub->pPrior ) return 0; /* Must be UNION ALL */
+ if( pSub->pWhere ) return 0; /* No WHERE clause */
+ if( pSub->pLimit ) return 0; /* No LIMIT clause */
+ if( pSub->selFlags & SF_Aggregate ) return 0; /* Not an aggregate */
+ pSub = pSub->pPrior; /* Repeat over compound */
+ }while( pSub );
+
+ /* If we reach this point then it is OK to perform the transformation */
+
+ db = pParse->db;
+ pCount = pExpr;
+ pExpr = 0;
+ pSub = p->pSrc->a[0].pSelect;
+ p->pSrc->a[0].pSelect = 0;
+ tdsqlite3SrcListDelete(db, p->pSrc);
+ p->pSrc = tdsqlite3DbMallocZero(pParse->db, sizeof(*p->pSrc));
+ while( pSub ){
+ Expr *pTerm;
+ pPrior = pSub->pPrior;
+ pSub->pPrior = 0;
+ pSub->pNext = 0;
+ pSub->selFlags |= SF_Aggregate;
+ pSub->selFlags &= ~SF_Compound;
+ pSub->nSelectRow = 0;
+ tdsqlite3ExprListDelete(db, pSub->pEList);
+ pTerm = pPrior ? tdsqlite3ExprDup(db, pCount, 0) : pCount;
+ pSub->pEList = tdsqlite3ExprListAppend(pParse, 0, pTerm);
+ pTerm = tdsqlite3PExpr(pParse, TK_SELECT, 0, 0);
+ tdsqlite3PExprAddSelect(pParse, pTerm, pSub);
+ if( pExpr==0 ){
+ pExpr = pTerm;
+ }else{
+ pExpr = tdsqlite3PExpr(pParse, TK_PLUS, pTerm, pExpr);
+ }
+ pSub = pPrior;
+ }
+ p->pEList->a[0].pExpr = pExpr;
+ p->selFlags &= ~SF_Aggregate;
+
+#if SELECTTRACE_ENABLED
+ if( tdsqlite3SelectTrace & 0x400 ){
+ SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n"));
+ tdsqlite3TreeViewSelect(0, p, 0);
+ }
+#endif
+ return 1;
+}
+#endif /* SQLITE_COUNTOFVIEW_OPTIMIZATION */
+
+/*
** Generate code for the SELECT statement given in the p argument.
**
** The results are returned according to the SelectDest structure.
@@ -122304,13 +137222,13 @@ static void explainSimpleCount(
** This routine does NOT free the Select structure passed in. The
** calling function needs to do that.
*/
-SQLITE_PRIVATE int sqlite3Select(
+SQLITE_PRIVATE int tdsqlite3Select(
Parse *pParse, /* The parser context */
Select *p, /* The SELECT statement being coded. */
SelectDest *pDest /* What to do with the query results */
){
int i, j; /* Loop counters */
- WhereInfo *pWInfo; /* Return from sqlite3WhereBegin() */
+ WhereInfo *pWInfo; /* Return from tdsqlite3WhereBegin() */
Vdbe *v; /* The virtual machine under construction */
int isAgg; /* True for select lists like "count(*)" */
ExprList *pEList = 0; /* List of columns to extract. */
@@ -122323,24 +137241,21 @@ SQLITE_PRIVATE int sqlite3Select(
SortCtx sSort; /* Info on how to code the ORDER BY clause */
AggInfo sAggInfo; /* Information used by aggregate queries */
int iEnd; /* Address of the end of the query */
- sqlite3 *db; /* The database connection */
-
-#ifndef SQLITE_OMIT_EXPLAIN
- int iRestoreSelectId = pParse->iSelectId;
- pParse->iSelectId = pParse->iNextSelectId++;
-#endif
+ tdsqlite3 *db; /* The database connection */
+ ExprList *pMinMaxOrderBy = 0; /* Added ORDER BY for min/max queries */
+ u8 minMaxFlag; /* Flag for min/max queries */
db = pParse->db;
+ v = tdsqlite3GetVdbe(pParse);
if( p==0 || db->mallocFailed || pParse->nErr ){
return 1;
}
- if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
+ if( tdsqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1;
memset(&sAggInfo, 0, sizeof(sAggInfo));
#if SELECTTRACE_ENABLED
- pParse->nSelectIndent++;
- SELECTTRACE(1,pParse,p, ("begin processing:\n"));
- if( sqlite3SelectTrace & 0x100 ){
- sqlite3TreeViewSelect(0, p, 0);
+ SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain));
+ if( tdsqlite3SelectTrace & 0x100 ){
+ tdsqlite3TreeViewSelect(0, p, 0);
}
#endif
@@ -122355,51 +137270,117 @@ SQLITE_PRIVATE int sqlite3Select(
pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_Fifo);
/* If ORDER BY makes no difference in the output then neither does
** DISTINCT so it can be removed too. */
- sqlite3ExprListDelete(db, p->pOrderBy);
+ tdsqlite3ExprListDelete(db, p->pOrderBy);
p->pOrderBy = 0;
p->selFlags &= ~SF_Distinct;
}
- sqlite3SelectPrep(pParse, p, 0);
- memset(&sSort, 0, sizeof(sSort));
- sSort.pOrderBy = p->pOrderBy;
- pTabList = p->pSrc;
+ tdsqlite3SelectPrep(pParse, p, 0);
if( pParse->nErr || db->mallocFailed ){
goto select_end;
}
assert( p->pEList!=0 );
- isAgg = (p->selFlags & SF_Aggregate)!=0;
#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x100 ){
- SELECTTRACE(0x100,pParse,p, ("after name resolution:\n"));
- sqlite3TreeViewSelect(0, p, 0);
+ if( tdsqlite3SelectTrace & 0x104 ){
+ SELECTTRACE(0x104,pParse,p, ("after name resolution:\n"));
+ tdsqlite3TreeViewSelect(0, p, 0);
}
#endif
- /* Try to flatten subqueries in the FROM clause up into the main query
+ if( pDest->eDest==SRT_Output ){
+ generateColumnNames(pParse, p);
+ }
+
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ rc = tdsqlite3WindowRewrite(pParse, p);
+ if( rc ){
+ assert( db->mallocFailed || pParse->nErr>0 );
+ goto select_end;
+ }
+#if SELECTTRACE_ENABLED
+ if( p->pWin && (tdsqlite3SelectTrace & 0x108)!=0 ){
+ SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n"));
+ tdsqlite3TreeViewSelect(0, p, 0);
+ }
+#endif
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+ pTabList = p->pSrc;
+ isAgg = (p->selFlags & SF_Aggregate)!=0;
+ memset(&sSort, 0, sizeof(sSort));
+ sSort.pOrderBy = p->pOrderBy;
+
+ /* Try to various optimizations (flattening subqueries, and strength
+ ** reduction of join operators) in the FROM clause up into the main query
*/
#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
for(i=0; !p->pPrior && i<pTabList->nSrc; i++){
struct SrcList_item *pItem = &pTabList->a[i];
Select *pSub = pItem->pSelect;
- int isAggSub;
Table *pTab = pItem->pTab;
+
+ /* Convert LEFT JOIN into JOIN if there are terms of the right table
+ ** of the LEFT JOIN used in the WHERE clause.
+ */
+ if( (pItem->fg.jointype & JT_LEFT)!=0
+ && tdsqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor)
+ && OptimizationEnabled(db, SQLITE_SimplifyJoin)
+ ){
+ SELECTTRACE(0x100,pParse,p,
+ ("LEFT-JOIN simplifies to JOIN on term %d\n",i));
+ pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER);
+ unsetJoinExpr(p->pWhere, pItem->iCursor);
+ }
+
+ /* No futher action if this term of the FROM clause is no a subquery */
if( pSub==0 ) continue;
/* Catch mismatch in the declared columns of a view and the number of
** columns in the SELECT on the RHS */
if( pTab->nCol!=pSub->pEList->nExpr ){
- sqlite3ErrorMsg(pParse, "expected %d columns for '%s' but got %d",
+ tdsqlite3ErrorMsg(pParse, "expected %d columns for '%s' but got %d",
pTab->nCol, pTab->zName, pSub->pEList->nExpr);
goto select_end;
}
- isAggSub = (pSub->selFlags & SF_Aggregate)!=0;
- if( flattenSubquery(pParse, p, i, isAgg, isAggSub) ){
+ /* Do not try to flatten an aggregate subquery.
+ **
+ ** Flattening an aggregate subquery is only possible if the outer query
+ ** is not a join. But if the outer query is not a join, then the subquery
+ ** will be implemented as a co-routine and there is no advantage to
+ ** flattening in that case.
+ */
+ if( (pSub->selFlags & SF_Aggregate)!=0 ) continue;
+ assert( pSub->pGroupBy==0 );
+
+ /* If the outer query contains a "complex" result set (that is,
+ ** if the result set of the outer query uses functions or subqueries)
+ ** and if the subquery contains an ORDER BY clause and if
+ ** it will be implemented as a co-routine, then do not flatten. This
+ ** restriction allows SQL constructs like this:
+ **
+ ** SELECT expensive_function(x)
+ ** FROM (SELECT x FROM tab ORDER BY y LIMIT 10);
+ **
+ ** The expensive_function() is only computed on the 10 rows that
+ ** are output, rather than every row of the table.
+ **
+ ** The requirement that the outer query have a complex result set
+ ** means that flattening does occur on simpler SQL constraints without
+ ** the expensive_function() like:
+ **
+ ** SELECT x FROM (SELECT x FROM tab ORDER BY y LIMIT 10);
+ */
+ if( pSub->pOrderBy!=0
+ && i==0
+ && (p->selFlags & SF_ComplexResult)!=0
+ && (pTabList->nSrc==1
+ || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0)
+ ){
+ continue;
+ }
+
+ if( flattenSubquery(pParse, p, i, isAgg) ){
+ if( pParse->nErr ) goto select_end;
/* This subquery can be absorbed into its parent. */
- if( isAggSub ){
- isAgg = 1;
- p->selFlags |= SF_Aggregate;
- }
i = -1;
}
pTabList = p->pSrc;
@@ -122410,48 +137391,104 @@ SQLITE_PRIVATE int sqlite3Select(
}
#endif
- /* Get a pointer the VDBE under construction, allocating a new VDBE if one
- ** does not already exist */
- v = sqlite3GetVdbe(pParse);
- if( v==0 ) goto select_end;
-
#ifndef SQLITE_OMIT_COMPOUND_SELECT
/* Handle compound SELECT statements using the separate multiSelect()
** procedure.
*/
if( p->pPrior ){
rc = multiSelect(pParse, p, pDest);
- explainSetInteger(pParse->iSelectId, iRestoreSelectId);
#if SELECTTRACE_ENABLED
- SELECTTRACE(1,pParse,p,("end compound-select processing\n"));
- pParse->nSelectIndent--;
+ SELECTTRACE(0x1,pParse,p,("end compound-select processing\n"));
+ if( (tdsqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
+ tdsqlite3TreeViewSelect(0, p, 0);
+ }
#endif
+ if( p->pNext==0 ) ExplainQueryPlanPop(pParse);
return rc;
}
#endif
- /* Generate code for all sub-queries in the FROM clause
+ /* Do the WHERE-clause constant propagation optimization if this is
+ ** a join. No need to speed time on this operation for non-join queries
+ ** as the equivalent optimization will be handled by query planner in
+ ** tdsqlite3WhereBegin().
+ */
+ if( pTabList->nSrc>1
+ && OptimizationEnabled(db, SQLITE_PropagateConst)
+ && propagateConstants(pParse, p)
+ ){
+#if SELECTTRACE_ENABLED
+ if( tdsqlite3SelectTrace & 0x100 ){
+ SELECTTRACE(0x100,pParse,p,("After constant propagation:\n"));
+ tdsqlite3TreeViewSelect(0, p, 0);
+ }
+#endif
+ }else{
+ SELECTTRACE(0x100,pParse,p,("Constant propagation not helpful\n"));
+ }
+
+#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION
+ if( OptimizationEnabled(db, SQLITE_QueryFlattener|SQLITE_CountOfView)
+ && countOfViewOptimization(pParse, p)
+ ){
+ if( db->mallocFailed ) goto select_end;
+ pEList = p->pEList;
+ pTabList = p->pSrc;
+ }
+#endif
+
+ /* For each term in the FROM clause, do two things:
+ ** (1) Authorized unreferenced tables
+ ** (2) Generate code for all sub-queries
*/
-#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
for(i=0; i<pTabList->nSrc; i++){
struct SrcList_item *pItem = &pTabList->a[i];
SelectDest dest;
- Select *pSub = pItem->pSelect;
- if( pSub==0 ) continue;
+ Select *pSub;
+#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
+ const char *zSavedAuthContext;
+#endif
- /* Sometimes the code for a subquery will be generated more than
- ** once, if the subquery is part of the WHERE clause in a LEFT JOIN,
- ** for example. In that case, do not regenerate the code to manifest
- ** a view or the co-routine to implement a view. The first instance
- ** is sufficient, though the subroutine to manifest the view does need
- ** to be invoked again. */
- if( pItem->addrFillSub ){
- if( pItem->fg.viaCoroutine==0 ){
- sqlite3VdbeAddOp2(v, OP_Gosub, pItem->regReturn, pItem->addrFillSub);
- }
- continue;
+ /* Issue SQLITE_READ authorizations with a fake column name for any
+ ** tables that are referenced but from which no values are extracted.
+ ** Examples of where these kinds of null SQLITE_READ authorizations
+ ** would occur:
+ **
+ ** SELECT count(*) FROM t1; -- SQLITE_READ t1.""
+ ** SELECT t1.* FROM t1, t2; -- SQLITE_READ t2.""
+ **
+ ** The fake column name is an empty string. It is possible for a table to
+ ** have a column named by the empty string, in which case there is no way to
+ ** distinguish between an unreferenced table and an actual reference to the
+ ** "" column. The original design was for the fake column name to be a NULL,
+ ** which would be unambiguous. But legacy authorization callbacks might
+ ** assume the column name is non-NULL and segfault. The use of an empty
+ ** string for the fake column name seems safer.
+ */
+ if( pItem->colUsed==0 && pItem->zName!=0 ){
+ tdsqlite3AuthCheck(pParse, SQLITE_READ, pItem->zName, "", pItem->zDatabase);
}
+#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW)
+ /* Generate code for all sub-queries in the FROM clause
+ */
+ pSub = pItem->pSelect;
+ if( pSub==0 ) continue;
+
+ /* The code for a subquery should only be generated once, though it is
+ ** technically harmless for it to be generated multiple times. The
+ ** following assert() will detect if something changes to cause
+ ** the same subquery to be coded multiple times, as a signal to the
+ ** developers to try to optimize the situation.
+ **
+ ** Update 2019-07-24:
+ ** See ticket https://sqlite.org/src/tktview/c52b09c7f38903b1311cec40.
+ ** The dbsqlfuzz fuzzer found a case where the same subquery gets
+ ** coded twice. So this assert() now becomes a testcase(). It should
+ ** be very rare, though.
+ */
+ testcase( pItem->addrFillSub!=0 );
+
/* Increment Parse.nHeight by the height of the largest expression
** tree referred to by this, the parent select. The child select
** may contain expression trees of at most
@@ -122459,32 +137496,34 @@ SQLITE_PRIVATE int sqlite3Select(
** more conservative than necessary, but much easier than enforcing
** an exact limit.
*/
- pParse->nHeight += sqlite3SelectExprHeight(p);
+ pParse->nHeight += tdsqlite3SelectExprHeight(p);
/* Make copies of constant WHERE-clause terms in the outer query down
** inside the subquery. This can help the subquery to run more efficiently.
*/
- if( (pItem->fg.jointype & JT_OUTER)==0
- && pushDownWhereTerms(db, pSub, p->pWhere, pItem->iCursor)
+ if( OptimizationEnabled(db, SQLITE_PushDown)
+ && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor,
+ (pItem->fg.jointype & JT_OUTER)!=0)
){
#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x100 ){
- SELECTTRACE(0x100,pParse,p,("After WHERE-clause push-down:\n"));
- sqlite3TreeViewSelect(0, p, 0);
+ if( tdsqlite3SelectTrace & 0x100 ){
+ SELECTTRACE(0x100,pParse,p,
+ ("After WHERE-clause push-down into subquery %d:\n", pSub->selId));
+ tdsqlite3TreeViewSelect(0, p, 0);
}
#endif
+ }else{
+ SELECTTRACE(0x100,pParse,p,("Push-down not possible\n"));
}
+ zSavedAuthContext = pParse->zAuthContext;
+ pParse->zAuthContext = pItem->zName;
+
/* Generate code to implement the subquery
**
- ** The subquery is implemented as a co-routine if all of these are true:
- ** (1) The subquery is guaranteed to be the outer loop (so that it
- ** does not need to be computed more than once)
- ** (2) The ALL keyword after SELECT is omitted. (Applications are
- ** allowed to say "SELECT ALL" instead of just "SELECT" to disable
- ** the use of co-routines.)
- ** (3) Co-routines are not disabled using sqlite3_test_control()
- ** with SQLITE_TESTCTRL_OPTIMIZATIONS.
+ ** The subquery is implemented as a co-routine if the subquery is
+ ** guaranteed to be the outer loop (so that it does not need to be
+ ** computed more than once)
**
** TODO: Are there other reasons beside (1) to use a co-routine
** implementation?
@@ -122492,26 +137531,25 @@ SQLITE_PRIVATE int sqlite3Select(
if( i==0
&& (pTabList->nSrc==1
|| (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */
- && (p->selFlags & SF_All)==0 /* (2) */
- && OptimizationEnabled(db, SQLITE_SubqCoroutine) /* (3) */
){
/* Implement a co-routine that will return a single row of the result
** set on each invocation.
*/
- int addrTop = sqlite3VdbeCurrentAddr(v)+1;
+ int addrTop = tdsqlite3VdbeCurrentAddr(v)+1;
+
pItem->regReturn = ++pParse->nMem;
- sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop);
+ tdsqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop);
VdbeComment((v, "%s", pItem->pTab->zName));
pItem->addrFillSub = addrTop;
- sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn);
- explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId);
- sqlite3Select(pParse, pSub, &dest);
+ tdsqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn);
+ ExplainQueryPlan((pParse, 1, "CO-ROUTINE %u", pSub->selId));
+ tdsqlite3Select(pParse, pSub, &dest);
pItem->pTab->nRowLogEst = pSub->nSelectRow;
pItem->fg.viaCoroutine = 1;
pItem->regResult = dest.iSdst;
- sqlite3VdbeEndCoroutine(v, pItem->regReturn);
- sqlite3VdbeJumpHere(v, addrTop-1);
- sqlite3ClearTempRegCache(pParse);
+ tdsqlite3VdbeEndCoroutine(v, pItem->regReturn);
+ tdsqlite3VdbeJumpHere(v, addrTop-1);
+ tdsqlite3ClearTempRegCache(pParse);
}else{
/* Generate a subroutine that will fill an ephemeral table with
** the content of this subquery. pItem->addrFillSub will point
@@ -122521,33 +137559,43 @@ SQLITE_PRIVATE int sqlite3Select(
int topAddr;
int onceAddr = 0;
int retAddr;
- assert( pItem->addrFillSub==0 );
+ struct SrcList_item *pPrior;
+
+ testcase( pItem->addrFillSub==0 ); /* Ticket c52b09c7f38903b1311 */
pItem->regReturn = ++pParse->nMem;
- topAddr = sqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn);
+ topAddr = tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn);
pItem->addrFillSub = topAddr+1;
if( pItem->fg.isCorrelated==0 ){
/* If the subquery is not correlated and if we are not inside of
** a trigger, then we only need to compute the value of the subquery
** once. */
- onceAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ onceAddr = tdsqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
VdbeComment((v, "materialize \"%s\"", pItem->pTab->zName));
}else{
VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName));
}
- sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
- explainSetInteger(pItem->iSelectId, (u8)pParse->iNextSelectId);
- sqlite3Select(pParse, pSub, &dest);
+ pPrior = isSelfJoinView(pTabList, pItem);
+ if( pPrior ){
+ tdsqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor);
+ assert( pPrior->pSelect!=0 );
+ pSub->nSelectRow = pPrior->pSelect->nSelectRow;
+ }else{
+ tdsqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor);
+ ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId));
+ tdsqlite3Select(pParse, pSub, &dest);
+ }
pItem->pTab->nRowLogEst = pSub->nSelectRow;
- if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr);
- retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn);
+ if( onceAddr ) tdsqlite3VdbeJumpHere(v, onceAddr);
+ retAddr = tdsqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn);
VdbeComment((v, "end %s", pItem->pTab->zName));
- sqlite3VdbeChangeP1(v, topAddr, retAddr);
- sqlite3ClearTempRegCache(pParse);
+ tdsqlite3VdbeChangeP1(v, topAddr, retAddr);
+ tdsqlite3ClearTempRegCache(pParse);
}
if( db->mallocFailed ) goto select_end;
- pParse->nHeight -= sqlite3SelectExprHeight(p);
- }
+ pParse->nHeight -= tdsqlite3SelectExprHeight(p);
+ pParse->zAuthContext = zSavedAuthContext;
#endif
+ }
/* Various elements of the SELECT copied into local variables for
** convenience */
@@ -122558,9 +137606,9 @@ SQLITE_PRIVATE int sqlite3Select(
sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0;
#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x400 ){
+ if( tdsqlite3SelectTrace & 0x400 ){
SELECTTRACE(0x400,pParse,p,("After all FROM-clause analysis:\n"));
- sqlite3TreeViewSelect(0, p, 0);
+ tdsqlite3TreeViewSelect(0, p, 0);
}
#endif
@@ -122580,19 +137628,23 @@ SQLITE_PRIVATE int sqlite3Select(
** BY and DISTINCT, and an index or separate temp-table for the other.
*/
if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct
- && sqlite3ExprListCompare(sSort.pOrderBy, pEList, -1)==0
+ && tdsqlite3ExprListCompare(sSort.pOrderBy, pEList, -1)==0
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ && p->pWin==0
+#endif
){
p->selFlags &= ~SF_Distinct;
- pGroupBy = p->pGroupBy = sqlite3ExprListDup(db, pEList, 0);
+ pGroupBy = p->pGroupBy = tdsqlite3ExprListDup(db, pEList, 0);
+ p->selFlags |= SF_Aggregate;
/* Notice that even thought SF_Distinct has been cleared from p->selFlags,
** the sDistinct.isTnct is still set. Hence, isTnct represents the
** original setting of the SF_Distinct flag, not the current setting */
assert( sDistinct.isTnct );
#if SELECTTRACE_ENABLED
- if( sqlite3SelectTrace & 0x400 ){
+ if( tdsqlite3SelectTrace & 0x400 ){
SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n"));
- sqlite3TreeViewSelect(0, p, 0);
+ tdsqlite3TreeViewSelect(0, p, 0);
}
#endif
}
@@ -122607,10 +137659,11 @@ SQLITE_PRIVATE int sqlite3Select(
*/
if( sSort.pOrderBy ){
KeyInfo *pKeyInfo;
- pKeyInfo = keyInfoFromExprList(pParse, sSort.pOrderBy, 0, pEList->nExpr);
+ pKeyInfo = tdsqlite3KeyInfoFromExprList(
+ pParse, sSort.pOrderBy, 0, pEList->nExpr);
sSort.iECursor = pParse->nTab++;
sSort.addrSortIndex =
- sqlite3VdbeAddOp4(v, OP_OpenEphemeral,
+ tdsqlite3VdbeAddOp4(v, OP_OpenEphemeral,
sSort.iECursor, sSort.pOrderBy->nExpr+1+pEList->nExpr, 0,
(char*)pKeyInfo, P4_KEYINFO
);
@@ -122621,16 +137674,18 @@ SQLITE_PRIVATE int sqlite3Select(
/* If the output is destined for a temporary table, open that table.
*/
if( pDest->eDest==SRT_EphemTab ){
- sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pDest->iSDParm, pEList->nExpr);
+ tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, pDest->iSDParm, pEList->nExpr);
}
/* Set the limiter.
*/
- iEnd = sqlite3VdbeMakeLabel(v);
- p->nSelectRow = 320; /* 4 billion rows */
+ iEnd = tdsqlite3VdbeMakeLabel(pParse);
+ if( (p->selFlags & SF_FixedLimit)==0 ){
+ p->nSelectRow = 320; /* 4 billion rows */
+ }
computeLimitRegisters(pParse, p, iEnd);
if( p->iLimit==0 && sSort.addrSortIndex>=0 ){
- sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen);
+ tdsqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen);
sSort.sortFlags |= SORTFLAG_UseSorter;
}
@@ -122638,11 +137693,11 @@ SQLITE_PRIVATE int sqlite3Select(
*/
if( p->selFlags & SF_Distinct ){
sDistinct.tabTnct = pParse->nTab++;
- sDistinct.addrTnct = sqlite3VdbeAddOp4(v, OP_OpenEphemeral,
- sDistinct.tabTnct, 0, 0,
- (char*)keyInfoFromExprList(pParse, p->pEList,0,0),
- P4_KEYINFO);
- sqlite3VdbeChangeP5(v, BTREE_UNORDERED);
+ sDistinct.addrTnct = tdsqlite3VdbeAddOp4(v, OP_OpenEphemeral,
+ sDistinct.tabTnct, 0, 0,
+ (char*)tdsqlite3KeyInfoFromExprList(pParse, p->pEList,0,0),
+ P4_KEYINFO);
+ tdsqlite3VdbeChangeP5(v, BTREE_UNORDERED);
sDistinct.eTnctType = WHERE_DISTINCT_UNORDERED;
}else{
sDistinct.eTnctType = WHERE_DISTINCT_NOOP;
@@ -122650,23 +137705,31 @@ SQLITE_PRIVATE int sqlite3Select(
if( !isAgg && pGroupBy==0 ){
/* No aggregate functions and no GROUP BY clause */
- u16 wctrlFlags = (sDistinct.isTnct ? WHERE_WANT_DISTINCT : 0);
+ u16 wctrlFlags = (sDistinct.isTnct ? WHERE_WANT_DISTINCT : 0)
+ | (p->selFlags & SF_FixedLimit);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ Window *pWin = p->pWin; /* Master window object (or NULL) */
+ if( pWin ){
+ tdsqlite3WindowCodeInit(pParse, p);
+ }
+#endif
assert( WHERE_USE_LIMIT==SF_FixedLimit );
- wctrlFlags |= p->selFlags & SF_FixedLimit;
+
/* Begin the database scan. */
- pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
+ SELECTTRACE(1,pParse,p,("WhereBegin\n"));
+ pWInfo = tdsqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy,
p->pEList, wctrlFlags, p->nSelectRow);
if( pWInfo==0 ) goto select_end;
- if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){
- p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo);
+ if( tdsqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){
+ p->nSelectRow = tdsqlite3WhereOutputRowCount(pWInfo);
}
- if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){
- sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo);
+ if( sDistinct.isTnct && tdsqlite3WhereIsDistinct(pWInfo) ){
+ sDistinct.eTnctType = tdsqlite3WhereIsDistinct(pWInfo);
}
if( sSort.pOrderBy ){
- sSort.nOBSat = sqlite3WhereIsOrdered(pWInfo);
- sSort.bOrderedInnerLoop = sqlite3WhereOrderedInnerLoop(pWInfo);
+ sSort.nOBSat = tdsqlite3WhereIsOrdered(pWInfo);
+ sSort.labelOBLopt = tdsqlite3WhereOrderByLimitOptLabel(pWInfo);
if( sSort.nOBSat==sSort.pOrderBy->nExpr ){
sSort.pOrderBy = 0;
}
@@ -122677,17 +137740,40 @@ SQLITE_PRIVATE int sqlite3Select(
** into an OP_Noop.
*/
if( sSort.addrSortIndex>=0 && sSort.pOrderBy==0 ){
- sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex);
- }
-
- /* Use the standard inner loop. */
- selectInnerLoop(pParse, p, pEList, -1, &sSort, &sDistinct, pDest,
- sqlite3WhereContinueLabel(pWInfo),
- sqlite3WhereBreakLabel(pWInfo));
+ tdsqlite3VdbeChangeToNoop(v, sSort.addrSortIndex);
+ }
+
+ assert( p->pEList==pEList );
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( pWin ){
+ int addrGosub = tdsqlite3VdbeMakeLabel(pParse);
+ int iCont = tdsqlite3VdbeMakeLabel(pParse);
+ int iBreak = tdsqlite3VdbeMakeLabel(pParse);
+ int regGosub = ++pParse->nMem;
+
+ tdsqlite3WindowCodeStep(pParse, p, pWInfo, regGosub, addrGosub);
+
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, iBreak);
+ tdsqlite3VdbeResolveLabel(v, addrGosub);
+ VdbeNoopComment((v, "inner-loop subroutine"));
+ sSort.labelOBLopt = 0;
+ selectInnerLoop(pParse, p, -1, &sSort, &sDistinct, pDest, iCont, iBreak);
+ tdsqlite3VdbeResolveLabel(v, iCont);
+ tdsqlite3VdbeAddOp1(v, OP_Return, regGosub);
+ VdbeComment((v, "end inner-loop subroutine"));
+ tdsqlite3VdbeResolveLabel(v, iBreak);
+ }else
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+ {
+ /* Use the standard inner loop. */
+ selectInnerLoop(pParse, p, -1, &sSort, &sDistinct, pDest,
+ tdsqlite3WhereContinueLabel(pWInfo),
+ tdsqlite3WhereBreakLabel(pWInfo));
- /* End the database scan loop.
- */
- sqlite3WhereEnd(pWInfo);
+ /* End the database scan loop.
+ */
+ tdsqlite3WhereEnd(pWInfo);
+ }
}else{
/* This case when there exist aggregate functions or a GROUP BY clause
** or both */
@@ -122717,27 +137803,39 @@ SQLITE_PRIVATE int sqlite3Select(
for(k=pGroupBy->nExpr, pItem=pGroupBy->a; k>0; k--, pItem++){
pItem->u.x.iAlias = 0;
}
- assert( 66==sqlite3LogEst(100) );
+ assert( 66==tdsqlite3LogEst(100) );
if( p->nSelectRow>66 ) p->nSelectRow = 66;
+
+ /* If there is both a GROUP BY and an ORDER BY clause and they are
+ ** identical, then it may be possible to disable the ORDER BY clause
+ ** on the grounds that the GROUP BY will cause elements to come out
+ ** in the correct order. It also may not - the GROUP BY might use a
+ ** database index that causes rows to be grouped together as required
+ ** but not actually sorted. Either way, record the fact that the
+ ** ORDER BY and GROUP BY clauses are the same by setting the orderByGrp
+ ** variable. */
+ if( sSort.pOrderBy && pGroupBy->nExpr==sSort.pOrderBy->nExpr ){
+ int ii;
+ /* The GROUP BY processing doesn't care whether rows are delivered in
+ ** ASC or DESC order - only that each group is returned contiguously.
+ ** So set the ASC/DESC flags in the GROUP BY to match those in the
+ ** ORDER BY to maximize the chances of rows being delivered in an
+ ** order that makes the ORDER BY redundant. */
+ for(ii=0; ii<pGroupBy->nExpr; ii++){
+ u8 sortFlags = sSort.pOrderBy->a[ii].sortFlags & KEYINFO_ORDER_DESC;
+ pGroupBy->a[ii].sortFlags = sortFlags;
+ }
+ if( tdsqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){
+ orderByGrp = 1;
+ }
+ }
}else{
- assert( 0==sqlite3LogEst(1) );
+ assert( 0==tdsqlite3LogEst(1) );
p->nSelectRow = 0;
}
- /* If there is both a GROUP BY and an ORDER BY clause and they are
- ** identical, then it may be possible to disable the ORDER BY clause
- ** on the grounds that the GROUP BY will cause elements to come out
- ** in the correct order. It also may not - the GROUP BY might use a
- ** database index that causes rows to be grouped together as required
- ** but not actually sorted. Either way, record the fact that the
- ** ORDER BY and GROUP BY clauses are the same by setting the orderByGrp
- ** variable. */
- if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){
- orderByGrp = 1;
- }
-
/* Create a label to jump to when we want to abort the query */
- addrEnd = sqlite3VdbeMakeLabel(v);
+ addrEnd = tdsqlite3VdbeMakeLabel(pParse);
/* Convert TK_COLUMN nodes into TK_AGG_COLUMN and make entries in
** sAggInfo for all TK_AGG_FUNCTION nodes in expressions of the
@@ -122746,24 +137844,62 @@ SQLITE_PRIVATE int sqlite3Select(
memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pParse;
sNC.pSrcList = pTabList;
- sNC.pAggInfo = &sAggInfo;
+ sNC.uNC.pAggInfo = &sAggInfo;
+ VVA_ONLY( sNC.ncFlags = NC_UAggInfo; )
sAggInfo.mnReg = pParse->nMem+1;
sAggInfo.nSortingColumn = pGroupBy ? pGroupBy->nExpr : 0;
sAggInfo.pGroupBy = pGroupBy;
- sqlite3ExprAnalyzeAggList(&sNC, pEList);
- sqlite3ExprAnalyzeAggList(&sNC, sSort.pOrderBy);
+ tdsqlite3ExprAnalyzeAggList(&sNC, pEList);
+ tdsqlite3ExprAnalyzeAggList(&sNC, sSort.pOrderBy);
if( pHaving ){
- sqlite3ExprAnalyzeAggregates(&sNC, pHaving);
+ if( pGroupBy ){
+ assert( pWhere==p->pWhere );
+ assert( pHaving==p->pHaving );
+ assert( pGroupBy==p->pGroupBy );
+ havingToWhere(pParse, p);
+ pWhere = p->pWhere;
+ }
+ tdsqlite3ExprAnalyzeAggregates(&sNC, pHaving);
}
sAggInfo.nAccumulator = sAggInfo.nColumn;
+ if( p->pGroupBy==0 && p->pHaving==0 && sAggInfo.nFunc==1 ){
+ minMaxFlag = minMaxQuery(db, sAggInfo.aFunc[0].pExpr, &pMinMaxOrderBy);
+ }else{
+ minMaxFlag = WHERE_ORDERBY_NORMAL;
+ }
for(i=0; i<sAggInfo.nFunc; i++){
- assert( !ExprHasProperty(sAggInfo.aFunc[i].pExpr, EP_xIsSelect) );
+ Expr *pExpr = sAggInfo.aFunc[i].pExpr;
+ assert( !ExprHasProperty(pExpr, EP_xIsSelect) );
sNC.ncFlags |= NC_InAggFunc;
- sqlite3ExprAnalyzeAggList(&sNC, sAggInfo.aFunc[i].pExpr->x.pList);
+ tdsqlite3ExprAnalyzeAggList(&sNC, pExpr->x.pList);
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ assert( !IsWindowFunc(pExpr) );
+ if( ExprHasProperty(pExpr, EP_WinFunc) ){
+ tdsqlite3ExprAnalyzeAggregates(&sNC, pExpr->y.pWin->pFilter);
+ }
+#endif
sNC.ncFlags &= ~NC_InAggFunc;
}
sAggInfo.mxReg = pParse->nMem;
if( db->mallocFailed ) goto select_end;
+#if SELECTTRACE_ENABLED
+ if( tdsqlite3SelectTrace & 0x400 ){
+ int ii;
+ SELECTTRACE(0x400,pParse,p,("After aggregate analysis:\n"));
+ tdsqlite3TreeViewSelect(0, p, 0);
+ for(ii=0; ii<sAggInfo.nColumn; ii++){
+ tdsqlite3DebugPrintf("agg-column[%d] iMem=%d\n",
+ ii, sAggInfo.aCol[ii].iMem);
+ tdsqlite3TreeViewExpr(0, sAggInfo.aCol[ii].pExpr, 0);
+ }
+ for(ii=0; ii<sAggInfo.nFunc; ii++){
+ tdsqlite3DebugPrintf("agg-func[%d]: iMem=%d\n",
+ ii, sAggInfo.aFunc[ii].iMem);
+ tdsqlite3TreeViewExpr(0, sAggInfo.aFunc[ii].pExpr, 0);
+ }
+ }
+#endif
+
/* Processing for aggregates with GROUP BY is very different and
** much more complex than aggregates without a GROUP BY.
@@ -122785,8 +137921,8 @@ SQLITE_PRIVATE int sqlite3Select(
** will be converted into a Noop.
*/
sAggInfo.sortingIdx = pParse->nTab++;
- pKeyInfo = keyInfoFromExprList(pParse, pGroupBy, 0, sAggInfo.nColumn);
- addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen,
+ pKeyInfo = tdsqlite3KeyInfoFromExprList(pParse,pGroupBy,0,sAggInfo.nColumn);
+ addrSortingIdx = tdsqlite3VdbeAddOp4(v, OP_SorterOpen,
sAggInfo.sortingIdx, sAggInfo.nSortingColumn,
0, (char*)pKeyInfo, P4_KEYINFO);
@@ -122795,30 +137931,29 @@ SQLITE_PRIVATE int sqlite3Select(
iUseFlag = ++pParse->nMem;
iAbortFlag = ++pParse->nMem;
regOutputRow = ++pParse->nMem;
- addrOutputRow = sqlite3VdbeMakeLabel(v);
+ addrOutputRow = tdsqlite3VdbeMakeLabel(pParse);
regReset = ++pParse->nMem;
- addrReset = sqlite3VdbeMakeLabel(v);
+ addrReset = tdsqlite3VdbeMakeLabel(pParse);
iAMem = pParse->nMem + 1;
pParse->nMem += pGroupBy->nExpr;
iBMem = pParse->nMem + 1;
pParse->nMem += pGroupBy->nExpr;
- sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag);
VdbeComment((v, "clear abort flag"));
- sqlite3VdbeAddOp2(v, OP_Integer, 0, iUseFlag);
- VdbeComment((v, "indicate accumulator empty"));
- sqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1);
+ tdsqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1);
/* Begin a loop that will extract all source rows in GROUP BY order.
** This might involve two separate loops with an OP_Sort in between, or
** it might be a single loop that uses an index to extract information
** in the right order to begin with.
*/
- sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
- pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0,
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
+ SELECTTRACE(1,pParse,p,("WhereBegin\n"));
+ pWInfo = tdsqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0,
WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0), 0
);
if( pWInfo==0 ) goto select_end;
- if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){
+ if( tdsqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){
/* The optimizer is able to deliver rows in group by order so
** we do not have to sort. The OP_OpenEphemeral table will be
** cancelled later because we still need to use the pKeyInfo
@@ -122849,33 +137984,30 @@ SQLITE_PRIVATE int sqlite3Select(
j++;
}
}
- regBase = sqlite3GetTempRange(pParse, nCol);
- sqlite3ExprCacheClear(pParse);
- sqlite3ExprCodeExprList(pParse, pGroupBy, regBase, 0, 0);
+ regBase = tdsqlite3GetTempRange(pParse, nCol);
+ tdsqlite3ExprCodeExprList(pParse, pGroupBy, regBase, 0, 0);
j = nGroupBy;
for(i=0; i<sAggInfo.nColumn; i++){
struct AggInfo_col *pCol = &sAggInfo.aCol[i];
if( pCol->iSorterColumn>=j ){
int r1 = j + regBase;
- sqlite3ExprCodeGetColumnToReg(pParse,
- pCol->pTab, pCol->iColumn, pCol->iTable, r1);
+ tdsqlite3ExprCodeGetColumnOfTable(v,
+ pCol->pTab, pCol->iTable, pCol->iColumn, r1);
j++;
}
}
- regRecord = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord);
- sqlite3VdbeAddOp2(v, OP_SorterInsert, sAggInfo.sortingIdx, regRecord);
- sqlite3ReleaseTempReg(pParse, regRecord);
- sqlite3ReleaseTempRange(pParse, regBase, nCol);
- sqlite3WhereEnd(pWInfo);
+ regRecord = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord);
+ tdsqlite3VdbeAddOp2(v, OP_SorterInsert, sAggInfo.sortingIdx, regRecord);
+ tdsqlite3ReleaseTempReg(pParse, regRecord);
+ tdsqlite3ReleaseTempRange(pParse, regBase, nCol);
+ tdsqlite3WhereEnd(pWInfo);
sAggInfo.sortingIdxPTab = sortPTab = pParse->nTab++;
- sortOut = sqlite3GetTempReg(pParse);
- sqlite3VdbeAddOp3(v, OP_OpenPseudo, sortPTab, sortOut, nCol);
- sqlite3VdbeAddOp2(v, OP_SorterSort, sAggInfo.sortingIdx, addrEnd);
+ sortOut = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_OpenPseudo, sortPTab, sortOut, nCol);
+ tdsqlite3VdbeAddOp2(v, OP_SorterSort, sAggInfo.sortingIdx, addrEnd);
VdbeComment((v, "GROUP BY sort")); VdbeCoverage(v);
sAggInfo.useSortingIdx = 1;
- sqlite3ExprCacheClear(pParse);
-
}
/* If the index or temporary table used by the GROUP BY sort
@@ -122886,10 +138018,10 @@ SQLITE_PRIVATE int sqlite3Select(
** Use the SQLITE_GroupByOrder flag with SQLITE_TESTCTRL_OPTIMIZER to
** disable this optimization for testing purposes. */
if( orderByGrp && OptimizationEnabled(db, SQLITE_GroupByOrder)
- && (groupBySort || sqlite3WhereIsSorted(pWInfo))
+ && (groupBySort || tdsqlite3WhereIsSorted(pWInfo))
){
sSort.pOrderBy = 0;
- sqlite3VdbeChangeToNoop(v, sSort.addrSortIndex);
+ tdsqlite3VdbeChangeToNoop(v, sSort.addrSortIndex);
}
/* Evaluate the current GROUP BY terms and store in b0, b1, b2...
@@ -122897,24 +138029,23 @@ SQLITE_PRIVATE int sqlite3Select(
** Then compare the current GROUP BY terms against the GROUP BY terms
** from the previous row currently stored in a0, a1, a2...
*/
- addrTopOfLoop = sqlite3VdbeCurrentAddr(v);
- sqlite3ExprCacheClear(pParse);
+ addrTopOfLoop = tdsqlite3VdbeCurrentAddr(v);
if( groupBySort ){
- sqlite3VdbeAddOp3(v, OP_SorterData, sAggInfo.sortingIdx,
+ tdsqlite3VdbeAddOp3(v, OP_SorterData, sAggInfo.sortingIdx,
sortOut, sortPTab);
}
for(j=0; j<pGroupBy->nExpr; j++){
if( groupBySort ){
- sqlite3VdbeAddOp3(v, OP_Column, sortPTab, j, iBMem+j);
+ tdsqlite3VdbeAddOp3(v, OP_Column, sortPTab, j, iBMem+j);
}else{
sAggInfo.directMode = 1;
- sqlite3ExprCode(pParse, pGroupBy->a[j].pExpr, iBMem+j);
+ tdsqlite3ExprCode(pParse, pGroupBy->a[j].pExpr, iBMem+j);
}
}
- sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem, pGroupBy->nExpr,
- (char*)sqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO);
- addr1 = sqlite3VdbeCurrentAddr(v);
- sqlite3VdbeAddOp3(v, OP_Jump, addr1+1, 0, addr1+1); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem, pGroupBy->nExpr,
+ (char*)tdsqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO);
+ addr1 = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp3(v, OP_Jump, addr1+1, 0, addr1+1); VdbeCoverage(v);
/* Generate code that runs whenever the GROUP BY changes.
** Changes in the GROUP BY are detected by the previous code
@@ -122925,40 +138056,40 @@ SQLITE_PRIVATE int sqlite3Select(
** and resets the aggregate accumulator registers in preparation
** for the next GROUP BY batch.
*/
- sqlite3ExprCodeMove(pParse, iBMem, iAMem, pGroupBy->nExpr);
- sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow);
+ tdsqlite3ExprCodeMove(pParse, iBMem, iAMem, pGroupBy->nExpr);
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow);
VdbeComment((v, "output one row"));
- sqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd); VdbeCoverage(v);
VdbeComment((v, "check abort flag"));
- sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset);
VdbeComment((v, "reset accumulator"));
/* Update the aggregate accumulators based on the content of
** the current row
*/
- sqlite3VdbeJumpHere(v, addr1);
- updateAccumulator(pParse, &sAggInfo);
- sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag);
+ tdsqlite3VdbeJumpHere(v, addr1);
+ updateAccumulator(pParse, iUseFlag, &sAggInfo);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag);
VdbeComment((v, "indicate data in accumulator"));
/* End of the loop
*/
if( groupBySort ){
- sqlite3VdbeAddOp2(v, OP_SorterNext, sAggInfo.sortingIdx, addrTopOfLoop);
+ tdsqlite3VdbeAddOp2(v, OP_SorterNext, sAggInfo.sortingIdx, addrTopOfLoop);
VdbeCoverage(v);
}else{
- sqlite3WhereEnd(pWInfo);
- sqlite3VdbeChangeToNoop(v, addrSortingIdx);
+ tdsqlite3WhereEnd(pWInfo);
+ tdsqlite3VdbeChangeToNoop(v, addrSortingIdx);
}
/* Output the final row of result
*/
- sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow);
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow);
VdbeComment((v, "output final row"));
/* Jump over the subroutines
*/
- sqlite3VdbeGoto(v, addrEnd);
+ tdsqlite3VdbeGoto(v, addrEnd);
/* Generate a subroutine that outputs a single row of the result
** set. This subroutine first looks at the iUseFlag. If iUseFlag
@@ -122967,33 +138098,34 @@ SQLITE_PRIVATE int sqlite3Select(
** increments the iAbortFlag memory location before returning in
** order to signal the caller to abort.
*/
- addrSetAbort = sqlite3VdbeCurrentAddr(v);
- sqlite3VdbeAddOp2(v, OP_Integer, 1, iAbortFlag);
+ addrSetAbort = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, iAbortFlag);
VdbeComment((v, "set abort flag"));
- sqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
- sqlite3VdbeResolveLabel(v, addrOutputRow);
- addrOutputRow = sqlite3VdbeCurrentAddr(v);
- sqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2);
+ tdsqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
+ tdsqlite3VdbeResolveLabel(v, addrOutputRow);
+ addrOutputRow = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2);
VdbeCoverage(v);
VdbeComment((v, "Groupby result generator entry point"));
- sqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
+ tdsqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
finalizeAggFunctions(pParse, &sAggInfo);
- sqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, SQLITE_JUMPIFNULL);
- selectInnerLoop(pParse, p, p->pEList, -1, &sSort,
+ tdsqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, SQLITE_JUMPIFNULL);
+ selectInnerLoop(pParse, p, -1, &sSort,
&sDistinct, pDest,
addrOutputRow+1, addrSetAbort);
- sqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
+ tdsqlite3VdbeAddOp1(v, OP_Return, regOutputRow);
VdbeComment((v, "end groupby result generator"));
/* Generate a subroutine that will reset the group-by accumulator
*/
- sqlite3VdbeResolveLabel(v, addrReset);
+ tdsqlite3VdbeResolveLabel(v, addrReset);
resetAccumulator(pParse, &sAggInfo);
- sqlite3VdbeAddOp1(v, OP_Return, regReset);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, iUseFlag);
+ VdbeComment((v, "indicate accumulator empty"));
+ tdsqlite3VdbeAddOp1(v, OP_Return, regReset);
} /* endif pGroupBy. Begin aggregate queries without GROUP BY: */
else {
- ExprList *pDel = 0;
#ifndef SQLITE_OMIT_BTREECOUNT
Table *pTab;
if( (pTab = isSimpleCount(p, &sAggInfo))!=0 ){
@@ -123010,15 +138142,15 @@ SQLITE_PRIVATE int sqlite3Select(
** is better to execute the op on an index, as indexes are almost
** always spread across less pages than their corresponding tables.
*/
- const int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ const int iDb = tdsqlite3SchemaToIndex(pParse->db, pTab->pSchema);
const int iCsr = pParse->nTab++; /* Cursor to scan b-tree */
Index *pIdx; /* Iterator variable */
KeyInfo *pKeyInfo = 0; /* Keyinfo for scanned index */
Index *pBest = 0; /* Best index found so far */
int iRoot = pTab->tnum; /* Root page of scanned b-tree */
- sqlite3CodeVerifySchema(pParse, iDb);
- sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
+ tdsqlite3CodeVerifySchema(pParse, iDb);
+ tdsqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
/* Search for the index that has the lowest scan cost.
**
@@ -123029,7 +138161,7 @@ SQLITE_PRIVATE int sqlite3Select(
** In practice the KeyInfo structure will not be used. It is only
** passed to keep OP_OpenRead happy.
*/
- if( !HasRowid(pTab) ) pBest = sqlite3PrimaryKeyIndex(pTab);
+ if( !HasRowid(pTab) ) pBest = tdsqlite3PrimaryKeyIndex(pTab);
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
if( pIdx->bUnordered==0
&& pIdx->szIdxRow<pTab->szTabRow
@@ -123041,93 +138173,80 @@ SQLITE_PRIVATE int sqlite3Select(
}
if( pBest ){
iRoot = pBest->tnum;
- pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pBest);
+ pKeyInfo = tdsqlite3KeyInfoOfIndex(pParse, pBest);
}
/* Open a read-only cursor, execute the OP_Count, close the cursor. */
- sqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, iRoot, iDb, 1);
+ tdsqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, iRoot, iDb, 1);
if( pKeyInfo ){
- sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO);
+ tdsqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO);
}
- sqlite3VdbeAddOp2(v, OP_Count, iCsr, sAggInfo.aFunc[0].iMem);
- sqlite3VdbeAddOp1(v, OP_Close, iCsr);
+ tdsqlite3VdbeAddOp2(v, OP_Count, iCsr, sAggInfo.aFunc[0].iMem);
+ tdsqlite3VdbeAddOp1(v, OP_Close, iCsr);
explainSimpleCount(pParse, pTab, pBest);
}else
#endif /* SQLITE_OMIT_BTREECOUNT */
{
- /* Check if the query is of one of the following forms:
- **
- ** SELECT min(x) FROM ...
- ** SELECT max(x) FROM ...
- **
- ** If it is, then ask the code in where.c to attempt to sort results
- ** as if there was an "ORDER ON x" or "ORDER ON x DESC" clause.
- ** If where.c is able to produce results sorted in this order, then
- ** add vdbe code to break out of the processing loop after the
- ** first iteration (since the first iteration of the loop is
- ** guaranteed to operate on the row with the minimum or maximum
- ** value of x, the only row required).
- **
- ** A special flag must be passed to sqlite3WhereBegin() to slightly
- ** modify behavior as follows:
- **
- ** + If the query is a "SELECT min(x)", then the loop coded by
- ** where.c should not iterate over any values with a NULL value
- ** for x.
- **
- ** + The optimizer code in where.c (the thing that decides which
- ** index or indices to use) should place a different priority on
- ** satisfying the 'ORDER BY' clause than it does in other cases.
- ** Refer to code and comments in where.c for details.
- */
- ExprList *pMinMax = 0;
- u8 flag = WHERE_ORDERBY_NORMAL;
-
- assert( p->pGroupBy==0 );
- assert( flag==0 );
- if( p->pHaving==0 ){
- flag = minMaxQuery(&sAggInfo, &pMinMax);
- }
- assert( flag==0 || (pMinMax!=0 && pMinMax->nExpr==1) );
-
- if( flag ){
- pMinMax = sqlite3ExprListDup(db, pMinMax, 0);
- pDel = pMinMax;
- assert( db->mallocFailed || pMinMax!=0 );
- if( !db->mallocFailed ){
- pMinMax->a[0].sortOrder = flag!=WHERE_ORDERBY_MIN ?1:0;
- pMinMax->a[0].pExpr->op = TK_COLUMN;
+ int regAcc = 0; /* "populate accumulators" flag */
+
+ /* If there are accumulator registers but no min() or max() functions
+ ** without FILTER clauses, allocate register regAcc. Register regAcc
+ ** will contain 0 the first time the inner loop runs, and 1 thereafter.
+ ** The code generated by updateAccumulator() uses this to ensure
+ ** that the accumulator registers are (a) updated only once if
+ ** there are no min() or max functions or (b) always updated for the
+ ** first row visited by the aggregate, so that they are updated at
+ ** least once even if the FILTER clause means the min() or max()
+ ** function visits zero rows. */
+ if( sAggInfo.nAccumulator ){
+ for(i=0; i<sAggInfo.nFunc; i++){
+ if( ExprHasProperty(sAggInfo.aFunc[i].pExpr, EP_WinFunc) ) continue;
+ if( sAggInfo.aFunc[i].pFunc->funcFlags&SQLITE_FUNC_NEEDCOLL ) break;
+ }
+ if( i==sAggInfo.nFunc ){
+ regAcc = ++pParse->nMem;
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regAcc);
}
}
-
+
/* This case runs if the aggregate has no GROUP BY clause. The
** processing is much simpler since there is only a single row
** of output.
*/
+ assert( p->pGroupBy==0 );
resetAccumulator(pParse, &sAggInfo);
- pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMax,0,flag,0);
+
+ /* If this query is a candidate for the min/max optimization, then
+ ** minMaxFlag will have been previously set to either
+ ** WHERE_ORDERBY_MIN or WHERE_ORDERBY_MAX and pMinMaxOrderBy will
+ ** be an appropriate ORDER BY expression for the optimization.
+ */
+ assert( minMaxFlag==WHERE_ORDERBY_NORMAL || pMinMaxOrderBy!=0 );
+ assert( pMinMaxOrderBy==0 || pMinMaxOrderBy->nExpr==1 );
+
+ SELECTTRACE(1,pParse,p,("WhereBegin\n"));
+ pWInfo = tdsqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy,
+ 0, minMaxFlag, 0);
if( pWInfo==0 ){
- sqlite3ExprListDelete(db, pDel);
goto select_end;
}
- updateAccumulator(pParse, &sAggInfo);
- assert( pMinMax==0 || pMinMax->nExpr==1 );
- if( sqlite3WhereIsOrdered(pWInfo)>0 ){
- sqlite3VdbeGoto(v, sqlite3WhereBreakLabel(pWInfo));
+ updateAccumulator(pParse, regAcc, &sAggInfo);
+ if( regAcc ) tdsqlite3VdbeAddOp2(v, OP_Integer, 1, regAcc);
+ if( tdsqlite3WhereIsOrdered(pWInfo)>0 ){
+ tdsqlite3VdbeGoto(v, tdsqlite3WhereBreakLabel(pWInfo));
VdbeComment((v, "%s() by index",
- (flag==WHERE_ORDERBY_MIN?"min":"max")));
+ (minMaxFlag==WHERE_ORDERBY_MIN?"min":"max")));
}
- sqlite3WhereEnd(pWInfo);
+ tdsqlite3WhereEnd(pWInfo);
finalizeAggFunctions(pParse, &sAggInfo);
}
sSort.pOrderBy = 0;
- sqlite3ExprIfFalse(pParse, pHaving, addrEnd, SQLITE_JUMPIFNULL);
- selectInnerLoop(pParse, p, p->pEList, -1, 0, 0,
+ tdsqlite3ExprIfFalse(pParse, pHaving, addrEnd, SQLITE_JUMPIFNULL);
+ selectInnerLoop(pParse, p, -1, 0, 0,
pDest, addrEnd, addrEnd);
- sqlite3ExprListDelete(db, pDel);
}
- sqlite3VdbeResolveLabel(v, addrEnd);
+ tdsqlite3VdbeResolveLabel(v, addrEnd);
} /* endif aggregate query */
@@ -123141,12 +138260,13 @@ SQLITE_PRIVATE int sqlite3Select(
if( sSort.pOrderBy ){
explainTempTable(pParse,
sSort.nOBSat>0 ? "RIGHT PART OF ORDER BY":"ORDER BY");
+ assert( p->pEList==pEList );
generateSortTail(pParse, p, &sSort, pEList->nExpr, pDest);
}
/* Jump here to skip this query
*/
- sqlite3VdbeResolveLabel(v, iEnd);
+ tdsqlite3VdbeResolveLabel(v, iEnd);
/* The SELECT has been coded. If there is an error in the Parse structure,
** set the return code to 1. Otherwise 0. */
@@ -123156,20 +138276,16 @@ SQLITE_PRIVATE int sqlite3Select(
** successful coding of the SELECT.
*/
select_end:
- explainSetInteger(pParse->iSelectId, iRestoreSelectId);
-
- /* Identify column names if results of the SELECT are to be output.
- */
- if( rc==SQLITE_OK && pDest->eDest==SRT_Output ){
- generateColumnNames(pParse, pTabList, pEList);
- }
-
- sqlite3DbFree(db, sAggInfo.aCol);
- sqlite3DbFree(db, sAggInfo.aFunc);
+ tdsqlite3ExprListDelete(db, pMinMaxOrderBy);
+ tdsqlite3DbFree(db, sAggInfo.aCol);
+ tdsqlite3DbFree(db, sAggInfo.aFunc);
#if SELECTTRACE_ENABLED
- SELECTTRACE(1,pParse,p,("end processing\n"));
- pParse->nSelectIndent--;
+ SELECTTRACE(0x1,pParse,p,("end processing\n"));
+ if( (tdsqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){
+ tdsqlite3TreeViewSelect(0, p, 0);
+ }
#endif
+ ExplainQueryPlanPop(pParse);
return rc;
}
@@ -123186,21 +138302,19 @@ select_end:
** May you share freely, never taking more than you give.
**
*************************************************************************
-** This file contains the sqlite3_get_table() and sqlite3_free_table()
+** This file contains the tdsqlite3_get_table() and tdsqlite3_free_table()
** interface routines. These are just wrappers around the main
-** interface routine of sqlite3_exec().
+** interface routine of tdsqlite3_exec().
**
** These routines are in a separate files so that they will not be linked
** if they are not used.
*/
/* #include "sqliteInt.h" */
-/* #include <stdlib.h> */
-/* #include <string.h> */
#ifndef SQLITE_OMIT_GET_TABLE
/*
-** This structure is used to pass data from sqlite3_get_table() through
+** This structure is used to pass data from tdsqlite3_get_table() through
** to the callback function is uses to build the result.
*/
typedef struct TabResult {
@@ -123210,7 +138324,7 @@ typedef struct TabResult {
u32 nRow; /* Number of rows in the result */
u32 nColumn; /* Number of columns in the result */
u32 nData; /* Slots used in azResult[]. (nRow+1)*nColumn */
- int rc; /* Return code from sqlite3_exec() */
+ int rc; /* Return code from tdsqlite3_exec() */
} TabResult;
/*
@@ -123218,7 +138332,7 @@ typedef struct TabResult {
** is to fill in the TabResult structure appropriately, allocating new
** memory as necessary.
*/
-static int sqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){
+static int tdsqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){
TabResult *p = (TabResult*)pArg; /* Result accumulator */
int need; /* Slots needed in p->azResult[] */
int i; /* Loop counter */
@@ -123235,7 +138349,7 @@ static int sqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){
if( p->nData + need > p->nAlloc ){
char **azNew;
p->nAlloc = p->nAlloc*2 + need;
- azNew = sqlite3_realloc64( p->azResult, sizeof(char*)*p->nAlloc );
+ azNew = tdsqlite3_realloc64( p->azResult, sizeof(char*)*p->nAlloc );
if( azNew==0 ) goto malloc_failed;
p->azResult = azNew;
}
@@ -123246,14 +138360,14 @@ static int sqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){
if( p->nRow==0 ){
p->nColumn = nCol;
for(i=0; i<nCol; i++){
- z = sqlite3_mprintf("%s", colv[i]);
+ z = tdsqlite3_mprintf("%s", colv[i]);
if( z==0 ) goto malloc_failed;
p->azResult[p->nData++] = z;
}
}else if( (int)p->nColumn!=nCol ){
- sqlite3_free(p->zErrMsg);
- p->zErrMsg = sqlite3_mprintf(
- "sqlite3_get_table() called with two or more incompatible queries"
+ tdsqlite3_free(p->zErrMsg);
+ p->zErrMsg = tdsqlite3_mprintf(
+ "tdsqlite3_get_table() called with two or more incompatible queries"
);
p->rc = SQLITE_ERROR;
return 1;
@@ -123266,8 +138380,8 @@ static int sqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){
if( argv[i]==0 ){
z = 0;
}else{
- int n = sqlite3Strlen30(argv[i])+1;
- z = sqlite3_malloc64( n );
+ int n = tdsqlite3Strlen30(argv[i])+1;
+ z = tdsqlite3_malloc64( n );
if( z==0 ) goto malloc_failed;
memcpy(z, argv[i], n);
}
@@ -123289,11 +138403,11 @@ malloc_failed:
**
** The result that is written to ***pazResult is held in memory obtained
** from malloc(). But the caller cannot free this memory directly.
-** Instead, the entire table should be passed to sqlite3_free_table() when
+** Instead, the entire table should be passed to tdsqlite3_free_table() when
** the calling procedure is finished using it.
*/
-SQLITE_API int sqlite3_get_table(
- sqlite3 *db, /* The database on which the SQL executes */
+SQLITE_API int tdsqlite3_get_table(
+ tdsqlite3 *db, /* The database on which the SQL executes */
const char *zSql, /* The SQL to be executed */
char ***pazResult, /* Write the result table here */
int *pnRow, /* Write the number of rows in the result here */
@@ -123304,7 +138418,7 @@ SQLITE_API int sqlite3_get_table(
TabResult res;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || pazResult==0 ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) || pazResult==0 ) return SQLITE_MISUSE_BKPT;
#endif
*pazResult = 0;
if( pnColumn ) *pnColumn = 0;
@@ -123316,37 +138430,37 @@ SQLITE_API int sqlite3_get_table(
res.nData = 1;
res.nAlloc = 20;
res.rc = SQLITE_OK;
- res.azResult = sqlite3_malloc64(sizeof(char*)*res.nAlloc );
+ res.azResult = tdsqlite3_malloc64(sizeof(char*)*res.nAlloc );
if( res.azResult==0 ){
db->errCode = SQLITE_NOMEM;
return SQLITE_NOMEM_BKPT;
}
res.azResult[0] = 0;
- rc = sqlite3_exec(db, zSql, sqlite3_get_table_cb, &res, pzErrMsg);
+ rc = tdsqlite3_exec(db, zSql, tdsqlite3_get_table_cb, &res, pzErrMsg);
assert( sizeof(res.azResult[0])>= sizeof(res.nData) );
res.azResult[0] = SQLITE_INT_TO_PTR(res.nData);
if( (rc&0xff)==SQLITE_ABORT ){
- sqlite3_free_table(&res.azResult[1]);
+ tdsqlite3_free_table(&res.azResult[1]);
if( res.zErrMsg ){
if( pzErrMsg ){
- sqlite3_free(*pzErrMsg);
- *pzErrMsg = sqlite3_mprintf("%s",res.zErrMsg);
+ tdsqlite3_free(*pzErrMsg);
+ *pzErrMsg = tdsqlite3_mprintf("%s",res.zErrMsg);
}
- sqlite3_free(res.zErrMsg);
+ tdsqlite3_free(res.zErrMsg);
}
db->errCode = res.rc; /* Assume 32-bit assignment is atomic */
return res.rc;
}
- sqlite3_free(res.zErrMsg);
+ tdsqlite3_free(res.zErrMsg);
if( rc!=SQLITE_OK ){
- sqlite3_free_table(&res.azResult[1]);
+ tdsqlite3_free_table(&res.azResult[1]);
return rc;
}
if( res.nAlloc>res.nData ){
char **azNew;
- azNew = sqlite3_realloc64( res.azResult, sizeof(char*)*res.nData );
+ azNew = tdsqlite3_realloc64( res.azResult, sizeof(char*)*res.nData );
if( azNew==0 ){
- sqlite3_free_table(&res.azResult[1]);
+ tdsqlite3_free_table(&res.azResult[1]);
db->errCode = SQLITE_NOMEM;
return SQLITE_NOMEM_BKPT;
}
@@ -123359,18 +138473,18 @@ SQLITE_API int sqlite3_get_table(
}
/*
-** This routine frees the space the sqlite3_get_table() malloced.
+** This routine frees the space the tdsqlite3_get_table() malloced.
*/
-SQLITE_API void sqlite3_free_table(
- char **azResult /* Result returned from sqlite3_get_table() */
+SQLITE_API void tdsqlite3_free_table(
+ char **azResult /* Result returned from tdsqlite3_get_table() */
){
if( azResult ){
int i, n;
azResult--;
assert( azResult!=0 );
n = SQLITE_PTR_TO_INT(azResult[0]);
- for(i=1; i<n; i++){ if( azResult[i] ) sqlite3_free(azResult[i]); }
- sqlite3_free(azResult);
+ for(i=1; i<n; i++){ if( azResult[i] ) tdsqlite3_free(azResult[i]); }
+ tdsqlite3_free(azResult);
}
}
@@ -123396,17 +138510,19 @@ SQLITE_API void sqlite3_free_table(
/*
** Delete a linked list of TriggerStep structures.
*/
-SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){
+SQLITE_PRIVATE void tdsqlite3DeleteTriggerStep(tdsqlite3 *db, TriggerStep *pTriggerStep){
while( pTriggerStep ){
TriggerStep * pTmp = pTriggerStep;
pTriggerStep = pTriggerStep->pNext;
- sqlite3ExprDelete(db, pTmp->pWhere);
- sqlite3ExprListDelete(db, pTmp->pExprList);
- sqlite3SelectDelete(db, pTmp->pSelect);
- sqlite3IdListDelete(db, pTmp->pIdList);
+ tdsqlite3ExprDelete(db, pTmp->pWhere);
+ tdsqlite3ExprListDelete(db, pTmp->pExprList);
+ tdsqlite3SelectDelete(db, pTmp->pSelect);
+ tdsqlite3IdListDelete(db, pTmp->pIdList);
+ tdsqlite3UpsertDelete(db, pTmp->pUpsert);
+ tdsqlite3DbFree(db, pTmp->zSpan);
- sqlite3DbFree(db, pTmp);
+ tdsqlite3DbFree(db, pTmp);
}
}
@@ -123424,7 +138540,7 @@ SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerS
** that fire off of pTab. The list will include any TEMP triggers on
** pTab as well as the triggers lised in pTab->pTrigger.
*/
-SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
+SQLITE_PRIVATE Trigger *tdsqlite3TriggerList(Parse *pParse, Table *pTab){
Schema * const pTmpSchema = pParse->db->aDb[1].pSchema;
Trigger *pList = 0; /* List of triggers to return */
@@ -123434,11 +138550,11 @@ SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
if( pTmpSchema!=pTab->pSchema ){
HashElem *p;
- assert( sqlite3SchemaMutexHeld(pParse->db, 0, pTmpSchema) );
+ assert( tdsqlite3SchemaMutexHeld(pParse->db, 0, pTmpSchema) );
for(p=sqliteHashFirst(&pTmpSchema->trigHash); p; p=sqliteHashNext(p)){
Trigger *pTrig = (Trigger *)sqliteHashData(p);
if( pTrig->pTabSchema==pTab->pSchema
- && 0==sqlite3StrICmp(pTrig->table, pTab->zName)
+ && 0==tdsqlite3StrICmp(pTrig->table, pTab->zName)
){
pTrig->pNext = (pList ? pList : pTab->pTrigger);
pList = pTrig;
@@ -123454,10 +138570,10 @@ SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
** up to the point of the BEGIN before the trigger actions. A Trigger
** structure is generated based on the information available and stored
** in pParse->pNewTrigger. After the trigger actions have been parsed, the
-** sqlite3FinishTrigger() function is called to complete the trigger
+** tdsqlite3FinishTrigger() function is called to complete the trigger
** construction process.
*/
-SQLITE_PRIVATE void sqlite3BeginTrigger(
+SQLITE_PRIVATE void tdsqlite3BeginTrigger(
Parse *pParse, /* The parse context of the CREATE TRIGGER statement */
Token *pName1, /* The name of the trigger */
Token *pName2, /* The name of the trigger */
@@ -123472,7 +138588,7 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
Trigger *pTrigger = 0; /* The new trigger */
Table *pTab; /* Table that the trigger fires off of */
char *zName = 0; /* Name of the trigger */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
int iDb; /* The database to store the trigger in */
Token *pName; /* The unqualified db name */
DbFixer sFix; /* State vector for the DB fixer */
@@ -123484,14 +138600,14 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
if( isTemp ){
/* If TEMP was specified, then the trigger name may not be qualified. */
if( pName2->n>0 ){
- sqlite3ErrorMsg(pParse, "temporary trigger may not have qualified name");
+ tdsqlite3ErrorMsg(pParse, "temporary trigger may not have qualified name");
goto trigger_cleanup;
}
iDb = 1;
pName = pName1;
}else{
/* Figure out the db that the trigger will be created in */
- iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
+ iDb = tdsqlite3TwoPartName(pParse, pName1, pName2, &pName);
if( iDb<0 ){
goto trigger_cleanup;
}
@@ -123509,16 +138625,16 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
** name on pTableName if we are reparsing out of SQLITE_MASTER.
*/
if( db->init.busy && iDb!=1 ){
- sqlite3DbFree(db, pTableName->a[0].zDatabase);
+ tdsqlite3DbFree(db, pTableName->a[0].zDatabase);
pTableName->a[0].zDatabase = 0;
}
/* If the trigger name was unqualified, and the table is a temp table,
** then set iDb to 1 to create the trigger in the temporary database.
- ** If sqlite3SrcListLookup() returns 0, indicating the table does not
+ ** If tdsqlite3SrcListLookup() returns 0, indicating the table does not
** exist, the error is caught by the block below.
*/
- pTab = sqlite3SrcListLookup(pParse, pTableName);
+ pTab = tdsqlite3SrcListLookup(pParse, pTableName);
if( db->init.busy==0 && pName2->n==0 && pTab
&& pTab->pSchema==db->aDb[1].pSchema ){
iDb = 1;
@@ -123527,11 +138643,11 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
/* Ensure the table name matches database name and that the table exists */
if( db->mallocFailed ) goto trigger_cleanup;
assert( pTableName->nSrc==1 );
- sqlite3FixInit(&sFix, pParse, iDb, "trigger", pName);
- if( sqlite3FixSrcList(&sFix, pTableName) ){
+ tdsqlite3FixInit(&sFix, pParse, iDb, "trigger", pName);
+ if( tdsqlite3FixSrcList(&sFix, pTableName) ){
goto trigger_cleanup;
}
- pTab = sqlite3SrcListLookup(pParse, pTableName);
+ pTab = tdsqlite3SrcListLookup(pParse, pTableName);
if( !pTab ){
/* The table does not exist. */
if( db->init.iDb==1 ){
@@ -123548,30 +138664,36 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
goto trigger_cleanup;
}
if( IsVirtual(pTab) ){
- sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables");
+ tdsqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables");
goto trigger_cleanup;
}
/* Check that the trigger name is not reserved and that no trigger of the
** specified name exists */
- zName = sqlite3NameFromToken(db, pName);
- if( !zName || SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){
+ zName = tdsqlite3NameFromToken(db, pName);
+ if( zName==0 ){
+ assert( db->mallocFailed );
goto trigger_cleanup;
}
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
- if( sqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash),zName) ){
- if( !noErr ){
- sqlite3ErrorMsg(pParse, "trigger %T already exists", pName);
- }else{
- assert( !db->init.busy );
- sqlite3CodeVerifySchema(pParse, iDb);
- }
+ if( tdsqlite3CheckObjectName(pParse, zName, "trigger", pTab->zName) ){
goto trigger_cleanup;
}
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
+ if( !IN_RENAME_OBJECT ){
+ if( tdsqlite3HashFind(&(db->aDb[iDb].pSchema->trigHash),zName) ){
+ if( !noErr ){
+ tdsqlite3ErrorMsg(pParse, "trigger %T already exists", pName);
+ }else{
+ assert( !db->init.busy );
+ tdsqlite3CodeVerifySchema(pParse, iDb);
+ }
+ goto trigger_cleanup;
+ }
+ }
/* Do not create a trigger on a system table */
- if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){
- sqlite3ErrorMsg(pParse, "cannot create trigger on system table");
+ if( tdsqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){
+ tdsqlite3ErrorMsg(pParse, "cannot create trigger on system table");
goto trigger_cleanup;
}
@@ -123579,27 +138701,27 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
** of triggers.
*/
if( pTab->pSelect && tr_tm!=TK_INSTEAD ){
- sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S",
+ tdsqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S",
(tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0);
goto trigger_cleanup;
}
if( !pTab->pSelect && tr_tm==TK_INSTEAD ){
- sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF"
+ tdsqlite3ErrorMsg(pParse, "cannot create INSTEAD OF"
" trigger on table: %S", pTableName, 0);
goto trigger_cleanup;
}
#ifndef SQLITE_OMIT_AUTHORIZATION
- {
- int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ if( !IN_RENAME_OBJECT ){
+ int iTabDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
int code = SQLITE_CREATE_TRIGGER;
const char *zDb = db->aDb[iTabDb].zDbSName;
const char *zDbTrig = isTemp ? db->aDb[1].zDbSName : zDb;
if( iTabDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER;
- if( sqlite3AuthCheck(pParse, code, zName, pTab->zName, zDbTrig) ){
+ if( tdsqlite3AuthCheck(pParse, code, zName, pTab->zName, zDbTrig) ){
goto trigger_cleanup;
}
- if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iTabDb),0,zDb)){
+ if( tdsqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iTabDb),0,zDb)){
goto trigger_cleanup;
}
}
@@ -123615,27 +138737,34 @@ SQLITE_PRIVATE void sqlite3BeginTrigger(
}
/* Build the Trigger object */
- pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger));
+ pTrigger = (Trigger*)tdsqlite3DbMallocZero(db, sizeof(Trigger));
if( pTrigger==0 ) goto trigger_cleanup;
pTrigger->zName = zName;
zName = 0;
- pTrigger->table = sqlite3DbStrDup(db, pTableName->a[0].zName);
+ pTrigger->table = tdsqlite3DbStrDup(db, pTableName->a[0].zName);
pTrigger->pSchema = db->aDb[iDb].pSchema;
pTrigger->pTabSchema = pTab->pSchema;
pTrigger->op = (u8)op;
pTrigger->tr_tm = tr_tm==TK_BEFORE ? TRIGGER_BEFORE : TRIGGER_AFTER;
- pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE);
- pTrigger->pColumns = sqlite3IdListDup(db, pColumns);
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenRemap(pParse, pTrigger->table, pTableName->a[0].zName);
+ pTrigger->pWhen = pWhen;
+ pWhen = 0;
+ }else{
+ pTrigger->pWhen = tdsqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE);
+ }
+ pTrigger->pColumns = pColumns;
+ pColumns = 0;
assert( pParse->pNewTrigger==0 );
pParse->pNewTrigger = pTrigger;
trigger_cleanup:
- sqlite3DbFree(db, zName);
- sqlite3SrcListDelete(db, pTableName);
- sqlite3IdListDelete(db, pColumns);
- sqlite3ExprDelete(db, pWhen);
+ tdsqlite3DbFree(db, zName);
+ tdsqlite3SrcListDelete(db, pTableName);
+ tdsqlite3IdListDelete(db, pColumns);
+ tdsqlite3ExprDelete(db, pWhen);
if( !pParse->pNewTrigger ){
- sqlite3DeleteTrigger(db, pTrigger);
+ tdsqlite3DeleteTrigger(db, pTrigger);
}else{
assert( pParse->pNewTrigger==pTrigger );
}
@@ -123645,14 +138774,14 @@ trigger_cleanup:
** This routine is called after all of the trigger actions have been parsed
** in order to complete the process of building the trigger.
*/
-SQLITE_PRIVATE void sqlite3FinishTrigger(
+SQLITE_PRIVATE void tdsqlite3FinishTrigger(
Parse *pParse, /* Parser context */
TriggerStep *pStepList, /* The triggered program */
Token *pAll /* Token that describes the complete CREATE TRIGGER */
){
Trigger *pTrig = pParse->pNewTrigger; /* Trigger being finished */
char *zName; /* Name of trigger */
- sqlite3 *db = pParse->db; /* The database */
+ tdsqlite3 *db = pParse->db; /* The database */
DbFixer sFix; /* Fixer object */
int iDb; /* Database containing the trigger */
Token nameToken; /* Trigger name for error reporting */
@@ -123660,20 +138789,28 @@ SQLITE_PRIVATE void sqlite3FinishTrigger(
pParse->pNewTrigger = 0;
if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup;
zName = pTrig->zName;
- iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema);
+ iDb = tdsqlite3SchemaToIndex(pParse->db, pTrig->pSchema);
pTrig->step_list = pStepList;
while( pStepList ){
pStepList->pTrig = pTrig;
pStepList = pStepList->pNext;
}
- sqlite3TokenInit(&nameToken, pTrig->zName);
- sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken);
- if( sqlite3FixTriggerStep(&sFix, pTrig->step_list)
- || sqlite3FixExpr(&sFix, pTrig->pWhen)
+ tdsqlite3TokenInit(&nameToken, pTrig->zName);
+ tdsqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken);
+ if( tdsqlite3FixTriggerStep(&sFix, pTrig->step_list)
+ || tdsqlite3FixExpr(&sFix, pTrig->pWhen)
){
goto triggerfinish_cleanup;
}
+#ifndef SQLITE_OMIT_ALTERTABLE
+ if( IN_RENAME_OBJECT ){
+ assert( !db->init.busy );
+ pParse->pNewTrigger = pTrig;
+ pTrig = 0;
+ }else
+#endif
+
/* if we are not initializing,
** build the sqlite_master entry
*/
@@ -123682,30 +138819,32 @@ SQLITE_PRIVATE void sqlite3FinishTrigger(
char *z;
/* Make an entry in the sqlite_master table */
- v = sqlite3GetVdbe(pParse);
+ v = tdsqlite3GetVdbe(pParse);
if( v==0 ) goto triggerfinish_cleanup;
- sqlite3BeginWriteOperation(pParse, 0, iDb);
- z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n);
- sqlite3NestedParse(pParse,
+ tdsqlite3BeginWriteOperation(pParse, 0, iDb);
+ z = tdsqlite3DbStrNDup(db, (char*)pAll->z, pAll->n);
+ testcase( z==0 );
+ tdsqlite3NestedParse(pParse,
"INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')",
- db->aDb[iDb].zDbSName, SCHEMA_TABLE(iDb), zName,
+ db->aDb[iDb].zDbSName, MASTER_NAME, zName,
pTrig->table, z);
- sqlite3DbFree(db, z);
- sqlite3ChangeCookie(pParse, iDb);
- sqlite3VdbeAddParseSchemaOp(v, iDb,
- sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName));
+ tdsqlite3DbFree(db, z);
+ tdsqlite3ChangeCookie(pParse, iDb);
+ tdsqlite3VdbeAddParseSchemaOp(v, iDb,
+ tdsqlite3MPrintf(db, "type='trigger' AND name='%q'", zName));
}
if( db->init.busy ){
Trigger *pLink = pTrig;
Hash *pHash = &db->aDb[iDb].pSchema->trigHash;
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
- pTrig = sqlite3HashInsert(pHash, zName, pTrig);
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( pLink!=0 );
+ pTrig = tdsqlite3HashInsert(pHash, zName, pTrig);
if( pTrig ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
}else if( pLink->pSchema==pLink->pTabSchema ){
Table *pTab;
- pTab = sqlite3HashFind(&pLink->pTabSchema->tblHash, pLink->table);
+ pTab = tdsqlite3HashFind(&pLink->pTabSchema->tblHash, pLink->table);
assert( pTab!=0 );
pLink->pNext = pTab->pTrigger;
pTab->pTrigger = pLink;
@@ -123713,27 +138852,44 @@ SQLITE_PRIVATE void sqlite3FinishTrigger(
}
triggerfinish_cleanup:
- sqlite3DeleteTrigger(db, pTrig);
- assert( !pParse->pNewTrigger );
- sqlite3DeleteTriggerStep(db, pStepList);
+ tdsqlite3DeleteTrigger(db, pTrig);
+ assert( IN_RENAME_OBJECT || !pParse->pNewTrigger );
+ tdsqlite3DeleteTriggerStep(db, pStepList);
}
/*
+** Duplicate a range of text from an SQL statement, then convert all
+** whitespace characters into ordinary space characters.
+*/
+static char *triggerSpanDup(tdsqlite3 *db, const char *zStart, const char *zEnd){
+ char *z = tdsqlite3DbSpanDup(db, zStart, zEnd);
+ int i;
+ if( z ) for(i=0; z[i]; i++) if( tdsqlite3Isspace(z[i]) ) z[i] = ' ';
+ return z;
+}
+
+/*
** Turn a SELECT statement (that the pSelect parameter points to) into
** a trigger step. Return a pointer to a TriggerStep structure.
**
** The parser calls this routine when it finds a SELECT statement in
** body of a TRIGGER.
*/
-SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, Select *pSelect){
- TriggerStep *pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep));
+SQLITE_PRIVATE TriggerStep *tdsqlite3TriggerSelectStep(
+ tdsqlite3 *db, /* Database connection */
+ Select *pSelect, /* The SELECT statement */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
+){
+ TriggerStep *pTriggerStep = tdsqlite3DbMallocZero(db, sizeof(TriggerStep));
if( pTriggerStep==0 ) {
- sqlite3SelectDelete(db, pSelect);
+ tdsqlite3SelectDelete(db, pSelect);
return 0;
}
pTriggerStep->op = TK_SELECT;
pTriggerStep->pSelect = pSelect;
pTriggerStep->orconf = OE_Default;
+ pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd);
return pTriggerStep;
}
@@ -123744,19 +138900,26 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep(sqlite3 *db, Select *pSelec
** If an OOM error occurs, NULL is returned and db->mallocFailed is set.
*/
static TriggerStep *triggerStepAllocate(
- sqlite3 *db, /* Database connection */
+ Parse *pParse, /* Parser context */
u8 op, /* Trigger opcode */
- Token *pName /* The target name */
+ Token *pName, /* The target name */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
){
+ tdsqlite3 *db = pParse->db;
TriggerStep *pTriggerStep;
- pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1);
+ pTriggerStep = tdsqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1);
if( pTriggerStep ){
char *z = (char*)&pTriggerStep[1];
memcpy(z, pName->z, pName->n);
- sqlite3Dequote(z);
+ tdsqlite3Dequote(z);
pTriggerStep->zTarget = z;
pTriggerStep->op = op;
+ pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd);
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenMap(pParse, pTriggerStep->zTarget, pName);
+ }
}
return pTriggerStep;
}
@@ -123768,26 +138931,42 @@ static TriggerStep *triggerStepAllocate(
** The parser calls this routine when it sees an INSERT inside the
** body of a trigger.
*/
-SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(
- sqlite3 *db, /* The database connection */
+SQLITE_PRIVATE TriggerStep *tdsqlite3TriggerInsertStep(
+ Parse *pParse, /* Parser */
Token *pTableName, /* Name of the table into which we insert */
IdList *pColumn, /* List of columns in pTableName to insert into */
Select *pSelect, /* A SELECT statement that supplies values */
- u8 orconf /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */
+ u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */
+ Upsert *pUpsert, /* ON CONFLICT clauses for upsert */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
){
+ tdsqlite3 *db = pParse->db;
TriggerStep *pTriggerStep;
assert(pSelect != 0 || db->mallocFailed);
- pTriggerStep = triggerStepAllocate(db, TK_INSERT, pTableName);
+ pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTableName,zStart,zEnd);
if( pTriggerStep ){
- pTriggerStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE);
+ if( IN_RENAME_OBJECT ){
+ pTriggerStep->pSelect = pSelect;
+ pSelect = 0;
+ }else{
+ pTriggerStep->pSelect = tdsqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE);
+ }
pTriggerStep->pIdList = pColumn;
+ pTriggerStep->pUpsert = pUpsert;
pTriggerStep->orconf = orconf;
+ if( pUpsert ){
+ tdsqlite3HasExplicitNulls(pParse, pUpsert->pUpsertTarget);
+ }
}else{
- sqlite3IdListDelete(db, pColumn);
+ testcase( pColumn );
+ tdsqlite3IdListDelete(db, pColumn);
+ testcase( pUpsert );
+ tdsqlite3UpsertDelete(db, pUpsert);
}
- sqlite3SelectDelete(db, pSelect);
+ tdsqlite3SelectDelete(db, pSelect);
return pTriggerStep;
}
@@ -123797,23 +138976,33 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(
** a pointer to that trigger step. The parser calls this routine when it
** sees an UPDATE statement inside the body of a CREATE TRIGGER.
*/
-SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(
- sqlite3 *db, /* The database connection */
+SQLITE_PRIVATE TriggerStep *tdsqlite3TriggerUpdateStep(
+ Parse *pParse, /* Parser */
Token *pTableName, /* Name of the table to be updated */
ExprList *pEList, /* The SET clause: list of column and new values */
Expr *pWhere, /* The WHERE clause */
- u8 orconf /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
+ u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
){
+ tdsqlite3 *db = pParse->db;
TriggerStep *pTriggerStep;
- pTriggerStep = triggerStepAllocate(db, TK_UPDATE, pTableName);
+ pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTableName,zStart,zEnd);
if( pTriggerStep ){
- pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE);
- pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
+ if( IN_RENAME_OBJECT ){
+ pTriggerStep->pExprList = pEList;
+ pTriggerStep->pWhere = pWhere;
+ pEList = 0;
+ pWhere = 0;
+ }else{
+ pTriggerStep->pExprList = tdsqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE);
+ pTriggerStep->pWhere = tdsqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
+ }
pTriggerStep->orconf = orconf;
}
- sqlite3ExprListDelete(db, pEList);
- sqlite3ExprDelete(db, pWhere);
+ tdsqlite3ExprListDelete(db, pEList);
+ tdsqlite3ExprDelete(db, pWhere);
return pTriggerStep;
}
@@ -123822,79 +139011,87 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(
** a pointer to that trigger step. The parser calls this routine when it
** sees a DELETE statement inside the body of a CREATE TRIGGER.
*/
-SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(
- sqlite3 *db, /* Database connection */
+SQLITE_PRIVATE TriggerStep *tdsqlite3TriggerDeleteStep(
+ Parse *pParse, /* Parser */
Token *pTableName, /* The table from which rows are deleted */
- Expr *pWhere /* The WHERE clause */
+ Expr *pWhere, /* The WHERE clause */
+ const char *zStart, /* Start of SQL text */
+ const char *zEnd /* End of SQL text */
){
+ tdsqlite3 *db = pParse->db;
TriggerStep *pTriggerStep;
- pTriggerStep = triggerStepAllocate(db, TK_DELETE, pTableName);
+ pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTableName,zStart,zEnd);
if( pTriggerStep ){
- pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
+ if( IN_RENAME_OBJECT ){
+ pTriggerStep->pWhere = pWhere;
+ pWhere = 0;
+ }else{
+ pTriggerStep->pWhere = tdsqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
+ }
pTriggerStep->orconf = OE_Default;
}
- sqlite3ExprDelete(db, pWhere);
+ tdsqlite3ExprDelete(db, pWhere);
return pTriggerStep;
}
/*
** Recursively delete a Trigger structure
*/
-SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){
+SQLITE_PRIVATE void tdsqlite3DeleteTrigger(tdsqlite3 *db, Trigger *pTrigger){
if( pTrigger==0 ) return;
- sqlite3DeleteTriggerStep(db, pTrigger->step_list);
- sqlite3DbFree(db, pTrigger->zName);
- sqlite3DbFree(db, pTrigger->table);
- sqlite3ExprDelete(db, pTrigger->pWhen);
- sqlite3IdListDelete(db, pTrigger->pColumns);
- sqlite3DbFree(db, pTrigger);
+ tdsqlite3DeleteTriggerStep(db, pTrigger->step_list);
+ tdsqlite3DbFree(db, pTrigger->zName);
+ tdsqlite3DbFree(db, pTrigger->table);
+ tdsqlite3ExprDelete(db, pTrigger->pWhen);
+ tdsqlite3IdListDelete(db, pTrigger->pColumns);
+ tdsqlite3DbFree(db, pTrigger);
}
/*
** This function is called to drop a trigger from the database schema.
**
** This may be called directly from the parser and therefore identifies
-** the trigger by name. The sqlite3DropTriggerPtr() routine does the
+** the trigger by name. The tdsqlite3DropTriggerPtr() routine does the
** same job as this routine except it takes a pointer to the trigger
** instead of the trigger name.
**/
-SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){
+SQLITE_PRIVATE void tdsqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr){
Trigger *pTrigger = 0;
int i;
const char *zDb;
const char *zName;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
if( db->mallocFailed ) goto drop_trigger_cleanup;
- if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
+ if( SQLITE_OK!=tdsqlite3ReadSchema(pParse) ){
goto drop_trigger_cleanup;
}
assert( pName->nSrc==1 );
zDb = pName->a[0].zDatabase;
zName = pName->a[0].zName;
- assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) );
+ assert( zDb!=0 || tdsqlite3BtreeHoldsAllMutexes(db) );
for(i=OMIT_TEMPDB; i<db->nDb; i++){
int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */
- if( zDb && sqlite3StrICmp(db->aDb[j].zDbSName, zDb) ) continue;
- assert( sqlite3SchemaMutexHeld(db, j, 0) );
- pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName);
+ if( zDb && tdsqlite3StrICmp(db->aDb[j].zDbSName, zDb) ) continue;
+ assert( tdsqlite3SchemaMutexHeld(db, j, 0) );
+ pTrigger = tdsqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName);
if( pTrigger ) break;
}
if( !pTrigger ){
if( !noErr ){
- sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0);
+ tdsqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0);
}else{
- sqlite3CodeVerifyNamedSchema(pParse, zDb);
+ tdsqlite3CodeVerifyNamedSchema(pParse, zDb);
}
pParse->checkSchema = 1;
goto drop_trigger_cleanup;
}
- sqlite3DropTriggerPtr(pParse, pTrigger);
+ tdsqlite3DropTriggerPtr(pParse, pTrigger);
drop_trigger_cleanup:
- sqlite3SrcListDelete(db, pName);
+ tdsqlite3SrcListDelete(db, pName);
}
/*
@@ -123902,32 +139099,31 @@ drop_trigger_cleanup:
** is set on.
*/
static Table *tableOfTrigger(Trigger *pTrigger){
- return sqlite3HashFind(&pTrigger->pTabSchema->tblHash, pTrigger->table);
+ return tdsqlite3HashFind(&pTrigger->pTabSchema->tblHash, pTrigger->table);
}
/*
** Drop a trigger given a pointer to that trigger.
*/
-SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
+SQLITE_PRIVATE void tdsqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
Table *pTable;
Vdbe *v;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
int iDb;
- iDb = sqlite3SchemaToIndex(pParse->db, pTrigger->pSchema);
+ iDb = tdsqlite3SchemaToIndex(pParse->db, pTrigger->pSchema);
assert( iDb>=0 && iDb<db->nDb );
pTable = tableOfTrigger(pTrigger);
- assert( pTable );
- assert( pTable->pSchema==pTrigger->pSchema || iDb==1 );
+ assert( (pTable && pTable->pSchema==pTrigger->pSchema) || iDb==1 );
#ifndef SQLITE_OMIT_AUTHORIZATION
- {
+ if( pTable ){
int code = SQLITE_DROP_TRIGGER;
const char *zDb = db->aDb[iDb].zDbSName;
const char *zTab = SCHEMA_TABLE(iDb);
if( iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER;
- if( sqlite3AuthCheck(pParse, code, pTrigger->zName, pTable->zName, zDb) ||
- sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
+ if( tdsqlite3AuthCheck(pParse, code, pTrigger->zName, pTable->zName, zDb) ||
+ tdsqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
return;
}
}
@@ -123935,36 +139131,41 @@ SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
/* Generate code to destroy the database record of the trigger.
*/
- assert( pTable!=0 );
- if( (v = sqlite3GetVdbe(pParse))!=0 ){
- sqlite3NestedParse(pParse,
+ if( (v = tdsqlite3GetVdbe(pParse))!=0 ){
+ tdsqlite3NestedParse(pParse,
"DELETE FROM %Q.%s WHERE name=%Q AND type='trigger'",
- db->aDb[iDb].zDbSName, SCHEMA_TABLE(iDb), pTrigger->zName
+ db->aDb[iDb].zDbSName, MASTER_NAME, pTrigger->zName
);
- sqlite3ChangeCookie(pParse, iDb);
- sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0);
+ tdsqlite3ChangeCookie(pParse, iDb);
+ tdsqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0);
}
}
/*
** Remove a trigger from the hash tables of the sqlite* pointer.
*/
-SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3 *db, int iDb, const char *zName){
+SQLITE_PRIVATE void tdsqlite3UnlinkAndDeleteTrigger(tdsqlite3 *db, int iDb, const char *zName){
Trigger *pTrigger;
Hash *pHash;
- assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
+ assert( tdsqlite3SchemaMutexHeld(db, iDb, 0) );
pHash = &(db->aDb[iDb].pSchema->trigHash);
- pTrigger = sqlite3HashInsert(pHash, zName, 0);
+ pTrigger = tdsqlite3HashInsert(pHash, zName, 0);
if( ALWAYS(pTrigger) ){
if( pTrigger->pSchema==pTrigger->pTabSchema ){
Table *pTab = tableOfTrigger(pTrigger);
- Trigger **pp;
- for(pp=&pTab->pTrigger; *pp!=pTrigger; pp=&((*pp)->pNext));
- *pp = (*pp)->pNext;
+ if( pTab ){
+ Trigger **pp;
+ for(pp=&pTab->pTrigger; *pp; pp=&((*pp)->pNext)){
+ if( *pp==pTrigger ){
+ *pp = (*pp)->pNext;
+ break;
+ }
+ }
+ }
}
- sqlite3DeleteTrigger(db, pTrigger);
- db->flags |= SQLITE_InternChanges;
+ tdsqlite3DeleteTrigger(db, pTrigger);
+ db->mDbFlags |= DBFLAG_SchemaChange;
}
}
@@ -123981,7 +139182,7 @@ static int checkColumnOverlap(IdList *pIdList, ExprList *pEList){
int e;
if( pIdList==0 || NEVER(pEList==0) ) return 1;
for(e=0; e<pEList->nExpr; e++){
- if( sqlite3IdListIndex(pIdList, pEList->a[e].zName)>=0 ) return 1;
+ if( tdsqlite3IdListIndex(pIdList, pEList->a[e].zEName)>=0 ) return 1;
}
return 0;
}
@@ -123992,7 +139193,7 @@ static int checkColumnOverlap(IdList *pIdList, ExprList *pEList){
** performed on the table, and, if that operation is an UPDATE, if at
** least one of the columns in pChanges is being modified.
*/
-SQLITE_PRIVATE Trigger *sqlite3TriggersExist(
+SQLITE_PRIVATE Trigger *tdsqlite3TriggersExist(
Parse *pParse, /* Parse context */
Table *pTab, /* The table the contains the triggers */
int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
@@ -124004,7 +139205,7 @@ SQLITE_PRIVATE Trigger *sqlite3TriggersExist(
Trigger *p;
if( (pParse->db->flags & SQLITE_EnableTrigger)!=0 ){
- pList = sqlite3TriggerList(pParse, pTab);
+ pList = tdsqlite3TriggerList(pParse, pTab);
}
assert( pList==0 || IsVirtual(pTab)==0 );
for(p=pList; p; p=p->pNext){
@@ -124032,20 +139233,20 @@ static SrcList *targetSrcList(
Parse *pParse, /* The parsing context */
TriggerStep *pStep /* The trigger containing the target token */
){
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
int iDb; /* Index of the database to use */
SrcList *pSrc; /* SrcList to be returned */
- pSrc = sqlite3SrcListAppend(db, 0, 0, 0);
+ pSrc = tdsqlite3SrcListAppend(pParse, 0, 0, 0);
if( pSrc ){
assert( pSrc->nSrc>0 );
- pSrc->a[pSrc->nSrc-1].zName = sqlite3DbStrDup(db, pStep->zTarget);
- iDb = sqlite3SchemaToIndex(db, pStep->pTrig->pSchema);
+ pSrc->a[pSrc->nSrc-1].zName = tdsqlite3DbStrDup(db, pStep->zTarget);
+ iDb = tdsqlite3SchemaToIndex(db, pStep->pTrig->pSchema);
if( iDb==0 || iDb>=2 ){
const char *zDb;
assert( iDb<db->nDb );
zDb = db->aDb[iDb].zDbSName;
- pSrc->a[pSrc->nSrc-1].zDatabase = sqlite3DbStrDup(db, zDb);
+ pSrc->a[pSrc->nSrc-1].zDatabase = tdsqlite3DbStrDup(db, zDb);
}
}
return pSrc;
@@ -124062,7 +139263,7 @@ static int codeTriggerProgram(
){
TriggerStep *pStep;
Vdbe *v = pParse->pVdbe;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
assert( pParse->pTriggerTab && pParse->pToplevel );
assert( pStepList );
@@ -124084,43 +139285,52 @@ static int codeTriggerProgram(
pParse->eOrconf = (orconf==OE_Default)?pStep->orconf:(u8)orconf;
assert( pParse->okConstFactor==0 );
+#ifndef SQLITE_OMIT_TRACE
+ if( pStep->zSpan ){
+ tdsqlite3VdbeAddOp4(v, OP_Trace, 0x7fffffff, 1, 0,
+ tdsqlite3MPrintf(db, "-- %s", pStep->zSpan),
+ P4_DYNAMIC);
+ }
+#endif
+
switch( pStep->op ){
case TK_UPDATE: {
- sqlite3Update(pParse,
+ tdsqlite3Update(pParse,
targetSrcList(pParse, pStep),
- sqlite3ExprListDup(db, pStep->pExprList, 0),
- sqlite3ExprDup(db, pStep->pWhere, 0),
- pParse->eOrconf
+ tdsqlite3ExprListDup(db, pStep->pExprList, 0),
+ tdsqlite3ExprDup(db, pStep->pWhere, 0),
+ pParse->eOrconf, 0, 0, 0
);
break;
}
case TK_INSERT: {
- sqlite3Insert(pParse,
+ tdsqlite3Insert(pParse,
targetSrcList(pParse, pStep),
- sqlite3SelectDup(db, pStep->pSelect, 0),
- sqlite3IdListDup(db, pStep->pIdList),
- pParse->eOrconf
+ tdsqlite3SelectDup(db, pStep->pSelect, 0),
+ tdsqlite3IdListDup(db, pStep->pIdList),
+ pParse->eOrconf,
+ tdsqlite3UpsertDup(db, pStep->pUpsert)
);
break;
}
case TK_DELETE: {
- sqlite3DeleteFrom(pParse,
+ tdsqlite3DeleteFrom(pParse,
targetSrcList(pParse, pStep),
- sqlite3ExprDup(db, pStep->pWhere, 0)
+ tdsqlite3ExprDup(db, pStep->pWhere, 0), 0, 0
);
break;
}
default: assert( pStep->op==TK_SELECT ); {
SelectDest sDest;
- Select *pSelect = sqlite3SelectDup(db, pStep->pSelect, 0);
- sqlite3SelectDestInit(&sDest, SRT_Discard, 0);
- sqlite3Select(pParse, pSelect, &sDest);
- sqlite3SelectDelete(db, pSelect);
+ Select *pSelect = tdsqlite3SelectDup(db, pStep->pSelect, 0);
+ tdsqlite3SelectDestInit(&sDest, SRT_Discard, 0);
+ tdsqlite3Select(pParse, pSelect, &sDest);
+ tdsqlite3SelectDelete(db, pSelect);
break;
}
}
if( pStep->op!=TK_SELECT ){
- sqlite3VdbeAddOp0(v, OP_ResetCount);
+ tdsqlite3VdbeAddOp0(v, OP_ResetCount);
}
}
@@ -124158,7 +139368,7 @@ static void transferParseError(Parse *pTo, Parse *pFrom){
pTo->nErr = pFrom->nErr;
pTo->rc = pFrom->rc;
}else{
- sqlite3DbFree(pFrom->db, pFrom->zErrMsg);
+ tdsqlite3DbFree(pFrom->db, pFrom->zErrMsg);
}
}
@@ -124172,8 +139382,8 @@ static TriggerPrg *codeRowTrigger(
Table *pTab, /* The table pTrigger is attached to */
int orconf /* ON CONFLICT policy to code trigger program with */
){
- Parse *pTop = sqlite3ParseToplevel(pParse);
- sqlite3 *db = pParse->db; /* Database handle */
+ Parse *pTop = tdsqlite3ParseToplevel(pParse);
+ tdsqlite3 *db = pParse->db; /* Database handle */
TriggerPrg *pPrg; /* Value to return */
Expr *pWhen = 0; /* Duplicate of trigger WHEN expression */
Vdbe *v; /* Temporary VM */
@@ -124188,13 +139398,13 @@ static TriggerPrg *codeRowTrigger(
/* Allocate the TriggerPrg and SubProgram objects. To ensure that they
** are freed if an error occurs, link them into the Parse.pTriggerPrg
** list of the top-level Parse object sooner rather than later. */
- pPrg = sqlite3DbMallocZero(db, sizeof(TriggerPrg));
+ pPrg = tdsqlite3DbMallocZero(db, sizeof(TriggerPrg));
if( !pPrg ) return 0;
pPrg->pNext = pTop->pTriggerPrg;
pTop->pTriggerPrg = pPrg;
- pPrg->pProgram = pProgram = sqlite3DbMallocZero(db, sizeof(SubProgram));
+ pPrg->pProgram = pProgram = tdsqlite3DbMallocZero(db, sizeof(SubProgram));
if( !pProgram ) return 0;
- sqlite3VdbeLinkSubProgram(pTop->pVdbe, pProgram);
+ tdsqlite3VdbeLinkSubProgram(pTop->pVdbe, pProgram);
pPrg->pTrigger = pTrigger;
pPrg->orconf = orconf;
pPrg->aColmask[0] = 0xffffffff;
@@ -124202,7 +139412,7 @@ static TriggerPrg *codeRowTrigger(
/* Allocate and populate a new Parse context to use for coding the
** trigger sub-program. */
- pSubParse = sqlite3StackAllocZero(db, sizeof(Parse));
+ pSubParse = tdsqlite3StackAllocZero(db, sizeof(Parse));
if( !pSubParse ) return 0;
memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pSubParse;
@@ -124212,8 +139422,9 @@ static TriggerPrg *codeRowTrigger(
pSubParse->zAuthContext = pTrigger->zName;
pSubParse->eTriggerOp = pTrigger->op;
pSubParse->nQueryLoop = pParse->nQueryLoop;
+ pSubParse->disableVtab = pParse->disableVtab;
- v = sqlite3GetVdbe(pSubParse);
+ v = tdsqlite3GetVdbe(pSubParse);
if( v ){
VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)",
pTrigger->zName, onErrorText(orconf),
@@ -124224,23 +139435,25 @@ static TriggerPrg *codeRowTrigger(
pTab->zName
));
#ifndef SQLITE_OMIT_TRACE
- sqlite3VdbeChangeP4(v, -1,
- sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC
- );
+ if( pTrigger->zName ){
+ tdsqlite3VdbeChangeP4(v, -1,
+ tdsqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC
+ );
+ }
#endif
/* If one was specified, code the WHEN clause. If it evaluates to false
** (or NULL) the sub-vdbe is immediately halted by jumping to the
** OP_Halt inserted at the end of the program. */
if( pTrigger->pWhen ){
- pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0);
- if( SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen)
+ pWhen = tdsqlite3ExprDup(db, pTrigger->pWhen, 0);
+ if( SQLITE_OK==tdsqlite3ResolveExprNames(&sNC, pWhen)
&& db->mallocFailed==0
){
- iEndTrigger = sqlite3VdbeMakeLabel(v);
- sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL);
+ iEndTrigger = tdsqlite3VdbeMakeLabel(pSubParse);
+ tdsqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL);
}
- sqlite3ExprDelete(db, pWhen);
+ tdsqlite3ExprDelete(db, pWhen);
}
/* Code the trigger program into the sub-vdbe. */
@@ -124248,27 +139461,27 @@ static TriggerPrg *codeRowTrigger(
/* Insert an OP_Halt at the end of the sub-program. */
if( iEndTrigger ){
- sqlite3VdbeResolveLabel(v, iEndTrigger);
+ tdsqlite3VdbeResolveLabel(v, iEndTrigger);
}
- sqlite3VdbeAddOp0(v, OP_Halt);
+ tdsqlite3VdbeAddOp0(v, OP_Halt);
VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf)));
transferParseError(pParse, pSubParse);
- if( db->mallocFailed==0 ){
- pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg);
+ if( db->mallocFailed==0 && pParse->nErr==0 ){
+ pProgram->aOp = tdsqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg);
}
pProgram->nMem = pSubParse->nMem;
pProgram->nCsr = pSubParse->nTab;
pProgram->token = (void *)pTrigger;
pPrg->aColmask[0] = pSubParse->oldmask;
pPrg->aColmask[1] = pSubParse->newmask;
- sqlite3VdbeDelete(v);
+ tdsqlite3VdbeDelete(v);
}
assert( !pSubParse->pAinc && !pSubParse->pZombieTab );
assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg );
- sqlite3ParserReset(pSubParse);
- sqlite3StackFree(db, pSubParse);
+ tdsqlite3ParserReset(pSubParse);
+ tdsqlite3StackFree(db, pSubParse);
return pPrg;
}
@@ -124285,7 +139498,7 @@ static TriggerPrg *getRowTrigger(
Table *pTab, /* The table trigger pTrigger is attached to */
int orconf /* ON CONFLICT algorithm. */
){
- Parse *pRoot = sqlite3ParseToplevel(pParse);
+ Parse *pRoot = tdsqlite3ParseToplevel(pParse);
TriggerPrg *pPrg;
assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) );
@@ -124311,9 +139524,9 @@ static TriggerPrg *getRowTrigger(
** Generate code for the trigger program associated with trigger p on
** table pTab. The reg, orconf and ignoreJump parameters passed to this
** function are the same as those described in the header function for
-** sqlite3CodeRowTrigger()
+** tdsqlite3CodeRowTrigger()
*/
-SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(
+SQLITE_PRIVATE void tdsqlite3CodeRowTriggerDirect(
Parse *pParse, /* Parse context */
Trigger *p, /* Trigger to code */
Table *pTab, /* The table to code triggers from */
@@ -124321,7 +139534,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(
int orconf, /* ON CONFLICT policy */
int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */
){
- Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */
+ Vdbe *v = tdsqlite3GetVdbe(pParse); /* Main VM */
TriggerPrg *pPrg;
pPrg = getRowTrigger(pParse, p, pTab, orconf);
assert( pPrg || pParse->nErr || pParse->db->mallocFailed );
@@ -124331,7 +139544,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(
if( pPrg ){
int bRecursive = (p->zName && 0==(pParse->db->flags&SQLITE_RecTriggers));
- sqlite3VdbeAddOp4(v, OP_Program, reg, ignoreJump, ++pParse->nMem,
+ tdsqlite3VdbeAddOp4(v, OP_Program, reg, ignoreJump, ++pParse->nMem,
(const char *)pPrg->pProgram, P4_SUBPROGRAM);
VdbeComment(
(v, "Call: %s.%s", (p->zName?p->zName:"fkey"), onErrorText(orconf)));
@@ -124341,7 +139554,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(
** invocation is disallowed if (a) the sub-program is really a trigger,
** not a foreign key action, and (b) the flag to enable recursive triggers
** is clear. */
- sqlite3VdbeChangeP5(v, (u8)bRecursive);
+ tdsqlite3VdbeChangeP5(v, (u8)bRecursive);
}
}
@@ -124385,7 +139598,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(
** is the instruction that control should jump to if a trigger program
** raises an IGNORE exception.
*/
-SQLITE_PRIVATE void sqlite3CodeRowTrigger(
+SQLITE_PRIVATE void tdsqlite3CodeRowTrigger(
Parse *pParse, /* Parse context */
Trigger *pTrigger, /* List of triggers on table pTab */
int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
@@ -124417,7 +139630,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTrigger(
&& p->tr_tm==tr_tm
&& checkColumnOverlap(p->pColumns, pChanges)
){
- sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump);
+ tdsqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump);
}
}
}
@@ -124447,7 +139660,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTrigger(
** tr_tm parameter. Similarly, values accessed by AFTER triggers are only
** included in the returned mask if the TRIGGER_AFTER bit is set in tr_tm.
*/
-SQLITE_PRIVATE u32 sqlite3TriggerColmask(
+SQLITE_PRIVATE u32 tdsqlite3TriggerColmask(
Parse *pParse, /* Parse context */
Trigger *pTrigger, /* List of triggers on table pTab */
ExprList *pChanges, /* Changes list for any UPDATE OF triggers */
@@ -124531,34 +139744,85 @@ static void updateVirtualTable(
** into the sqlite_master table.)
**
** Therefore, the P4 parameter is only required if the default value for
-** the column is a literal number, string or null. The sqlite3ValueFromExpr()
+** the column is a literal number, string or null. The tdsqlite3ValueFromExpr()
** function is capable of transforming these types of expressions into
-** sqlite3_value objects.
+** tdsqlite3_value objects.
**
** If parameter iReg is not negative, code an OP_RealAffinity instruction
** on register iReg. This is used when an equivalent integer value is
** stored in place of an 8-byte floating point value in order to save
** space.
*/
-SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){
+SQLITE_PRIVATE void tdsqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){
assert( pTab!=0 );
if( !pTab->pSelect ){
- sqlite3_value *pValue = 0;
- u8 enc = ENC(sqlite3VdbeDb(v));
+ tdsqlite3_value *pValue = 0;
+ u8 enc = ENC(tdsqlite3VdbeDb(v));
Column *pCol = &pTab->aCol[i];
VdbeComment((v, "%s.%s", pTab->zName, pCol->zName));
assert( i<pTab->nCol );
- sqlite3ValueFromExpr(sqlite3VdbeDb(v), pCol->pDflt, enc,
+ tdsqlite3ValueFromExpr(tdsqlite3VdbeDb(v), pCol->pDflt, enc,
pCol->affinity, &pValue);
if( pValue ){
- sqlite3VdbeChangeP4(v, -1, (const char *)pValue, P4_MEM);
+ tdsqlite3VdbeAppendP4(v, pValue, P4_MEM);
}
+ }
#ifndef SQLITE_OMIT_FLOATING_POINT
- if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){
- sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
- }
+ if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){
+ tdsqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
+ }
#endif
+}
+
+/*
+** Check to see if column iCol of index pIdx references any of the
+** columns defined by aXRef and chngRowid. Return true if it does
+** and false if not. This is an optimization. False-positives are a
+** performance degradation, but false-negatives can result in a corrupt
+** index and incorrect answers.
+**
+** aXRef[j] will be non-negative if column j of the original table is
+** being updated. chngRowid will be true if the rowid of the table is
+** being updated.
+*/
+static int indexColumnIsBeingUpdated(
+ Index *pIdx, /* The index to check */
+ int iCol, /* Which column of the index to check */
+ int *aXRef, /* aXRef[j]>=0 if column j is being updated */
+ int chngRowid /* true if the rowid is being updated */
+){
+ i16 iIdxCol = pIdx->aiColumn[iCol];
+ assert( iIdxCol!=XN_ROWID ); /* Cannot index rowid */
+ if( iIdxCol>=0 ){
+ return aXRef[iIdxCol]>=0;
}
+ assert( iIdxCol==XN_EXPR );
+ assert( pIdx->aColExpr!=0 );
+ assert( pIdx->aColExpr->a[iCol].pExpr!=0 );
+ return tdsqlite3ExprReferencesUpdatedColumn(pIdx->aColExpr->a[iCol].pExpr,
+ aXRef,chngRowid);
+}
+
+/*
+** Check to see if index pIdx is a partial index whose conditional
+** expression might change values due to an UPDATE. Return true if
+** the index is subject to change and false if the index is guaranteed
+** to be unchanged. This is an optimization. False-positives are a
+** performance degradation, but false-negatives can result in a corrupt
+** index and incorrect answers.
+**
+** aXRef[j] will be non-negative if column j of the original table is
+** being updated. chngRowid will be true if the rowid of the table is
+** being updated.
+*/
+static int indexWhereClauseMightChange(
+ Index *pIdx, /* The index to check */
+ int *aXRef, /* aXRef[j]>=0 if column j is being updated */
+ int chngRowid /* true if the rowid is being updated */
+){
+ if( pIdx->pPartIdxWhere==0 ) return 0;
+ return tdsqlite3ExprReferencesUpdatedColumn(pIdx->pPartIdxWhere,
+ aXRef, chngRowid);
}
/*
@@ -124568,14 +139832,17 @@ SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){
** \_______/ \________/ \______/ \________________/
* onError pTabList pChanges pWhere
*/
-SQLITE_PRIVATE void sqlite3Update(
+SQLITE_PRIVATE void tdsqlite3Update(
Parse *pParse, /* The parser context */
SrcList *pTabList, /* The table in which we should change things */
ExprList *pChanges, /* Things to be changed */
Expr *pWhere, /* The WHERE clause. May be null */
- int onError /* How to handle constraint errors */
+ int onError, /* How to handle constraint errors */
+ ExprList *pOrderBy, /* ORDER BY clause. May be null */
+ Expr *pLimit, /* LIMIT clause. May be null */
+ Upsert *pUpsert /* ON CONFLICT clause, or null */
){
- int i, j; /* Loop counters */
+ int i, j, k; /* Loop counters */
Table *pTab; /* The table to be updated */
int addrTop = 0; /* VDBE instruction address of the start of the loop */
WhereInfo *pWInfo; /* Information about the WHERE clause */
@@ -124583,11 +139850,12 @@ SQLITE_PRIVATE void sqlite3Update(
Index *pIdx; /* For looping over indices */
Index *pPk; /* The PRIMARY KEY index for WITHOUT ROWID tables */
int nIdx; /* Number of indices that need updating */
+ int nAllIdx; /* Total number of indexes */
int iBaseCur; /* Base cursor number */
int iDataCur; /* Cursor for the canonical data btree */
int iIdxCur; /* Cursor for the first index */
- sqlite3 *db; /* The database structure */
- int *aRegIdx = 0; /* One register assigned to each index to be updated */
+ tdsqlite3 *db; /* The database structure */
+ int *aRegIdx = 0; /* Registers for to each index and the main table */
int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the
** an expression for the i-th column of the table.
** aXRef[i]==-1 if the i-th column is not changed. */
@@ -124599,10 +139867,11 @@ SQLITE_PRIVATE void sqlite3Update(
AuthContext sContext; /* The authorization context */
NameContext sNC; /* The name-context to resolve expressions in */
int iDb; /* Database containing the table being updated */
- int okOnePass; /* True for one-pass algorithm without the FIFO */
+ int eOnePass; /* ONEPASS_XXX value from where.c */
int hasFK; /* True if foreign key processing is required */
int labelBreak; /* Jump here to break out of UPDATE loop */
int labelContinue; /* Jump here to continue next step of UPDATE loop */
+ int flags; /* Flags for tdsqlite3WhereBegin() */
#ifndef SQLITE_OMIT_TRIGGER
int isView; /* True when updating a view (INSTEAD OF trigger) */
@@ -124613,6 +139882,11 @@ SQLITE_PRIVATE void sqlite3Update(
int iEph = 0; /* Ephemeral table holding all primary key values */
int nKey = 0; /* Number of elements in regKey for WITHOUT ROWID */
int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */
+ int addrOpen = 0; /* Address of OP_OpenEphemeral */
+ int iPk = 0; /* First of nPk cells holding PRIMARY KEY value */
+ i16 nPk = 0; /* Number of components of the PRIMARY KEY */
+ int bReplace = 0; /* True if REPLACE conflict resolution might happen */
+ int bFinishSeek = 1; /* The OP_FinishSeek opcode is needed */
/* Register Allocations */
int regRowCount = 0; /* A count of rows changed */
@@ -124632,15 +139906,15 @@ SQLITE_PRIVATE void sqlite3Update(
/* Locate the table which we want to update.
*/
- pTab = sqlite3SrcListLookup(pParse, pTabList);
+ pTab = tdsqlite3SrcListLookup(pParse, pTabList);
if( pTab==0 ) goto update_cleanup;
- iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(pParse->db, pTab->pSchema);
/* Figure out if we have any triggers and if the table being
** updated is a view.
*/
#ifndef SQLITE_OMIT_TRIGGER
- pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, &tmask);
+ pTrigger = tdsqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, &tmask);
isView = pTab->pSelect!=0;
assert( pTrigger || tmask==0 );
#else
@@ -124653,10 +139927,20 @@ SQLITE_PRIVATE void sqlite3Update(
# define isView 0
#endif
- if( sqlite3ViewGetColumnNames(pParse, pTab) ){
+#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
+ if( !isView ){
+ pWhere = tdsqlite3LimitWhere(
+ pParse, pTabList, pWhere, pOrderBy, pLimit, "UPDATE"
+ );
+ pOrderBy = 0;
+ pLimit = 0;
+ }
+#endif
+
+ if( tdsqlite3ViewGetColumnNames(pParse, pTab) ){
goto update_cleanup;
}
- if( sqlite3IsReadOnly(pParse, pTab, tmask) ){
+ if( tdsqlite3IsReadOnly(pParse, pTab, tmask) ){
goto update_cleanup;
}
@@ -124665,24 +139949,31 @@ SQLITE_PRIVATE void sqlite3Update(
** need to occur right after the database cursor. So go ahead and
** allocate enough space, just in case.
*/
- pTabList->a[0].iCursor = iBaseCur = iDataCur = pParse->nTab++;
+ iBaseCur = iDataCur = pParse->nTab++;
iIdxCur = iDataCur+1;
- pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab);
+ pPk = HasRowid(pTab) ? 0 : tdsqlite3PrimaryKeyIndex(pTab);
+ testcase( pPk!=0 && pPk!=pTab->pIndex );
for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){
- if( IsPrimaryKeyIndex(pIdx) && pPk!=0 ){
+ if( pPk==pIdx ){
iDataCur = pParse->nTab;
- pTabList->a[0].iCursor = iDataCur;
}
pParse->nTab++;
}
+ if( pUpsert ){
+ /* On an UPSERT, reuse the same cursors already opened by INSERT */
+ iDataCur = pUpsert->iDataCur;
+ iIdxCur = pUpsert->iIdxCur;
+ pParse->nTab = iBaseCur;
+ }
+ pTabList->a[0].iCursor = iDataCur;
/* Allocate space for aXRef[], aRegIdx[], and aToOpen[].
** Initialize aXRef[] and aToOpen[] to their default values.
*/
- aXRef = sqlite3DbMallocRawNN(db, sizeof(int) * (pTab->nCol+nIdx) + nIdx+2 );
+ aXRef = tdsqlite3DbMallocRawNN(db, sizeof(int) * (pTab->nCol+nIdx+1) + nIdx+2 );
if( aXRef==0 ) goto update_cleanup;
aRegIdx = aXRef+pTab->nCol;
- aToOpen = (u8*)(aRegIdx+nIdx);
+ aToOpen = (u8*)(aRegIdx+nIdx+1);
memset(aToOpen, 1, nIdx+1);
aToOpen[nIdx+1] = 0;
for(i=0; i<pTab->nCol; i++) aXRef[i] = -1;
@@ -124691,6 +139982,12 @@ SQLITE_PRIVATE void sqlite3Update(
memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pParse;
sNC.pSrcList = pTabList;
+ sNC.uNC.pUpsert = pUpsert;
+ sNC.ncFlags = NC_UUpsert;
+
+ /* Begin generating code. */
+ v = tdsqlite3GetVdbe(pParse);
+ if( v==0 ) goto update_cleanup;
/* Resolve the column names in all the expressions of the
** of the UPDATE statement. Also find the column index
@@ -124700,28 +139997,38 @@ SQLITE_PRIVATE void sqlite3Update(
*/
chngRowid = chngPk = 0;
for(i=0; i<pChanges->nExpr; i++){
- if( sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){
+ if( tdsqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){
goto update_cleanup;
}
for(j=0; j<pTab->nCol; j++){
- if( sqlite3StrICmp(pTab->aCol[j].zName, pChanges->a[i].zName)==0 ){
+ if( tdsqlite3StrICmp(pTab->aCol[j].zName, pChanges->a[i].zEName)==0 ){
if( j==pTab->iPKey ){
chngRowid = 1;
pRowidExpr = pChanges->a[i].pExpr;
}else if( pPk && (pTab->aCol[j].colFlags & COLFLAG_PRIMKEY)!=0 ){
chngPk = 1;
}
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ else if( pTab->aCol[j].colFlags & COLFLAG_GENERATED ){
+ testcase( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL );
+ testcase( pTab->aCol[j].colFlags & COLFLAG_STORED );
+ tdsqlite3ErrorMsg(pParse,
+ "cannot UPDATE generated column \"%s\"",
+ pTab->aCol[j].zName);
+ goto update_cleanup;
+ }
+#endif
aXRef[j] = i;
break;
}
}
if( j>=pTab->nCol ){
- if( pPk==0 && sqlite3IsRowid(pChanges->a[i].zName) ){
+ if( pPk==0 && tdsqlite3IsRowid(pChanges->a[i].zEName) ){
j = -1;
chngRowid = 1;
pRowidExpr = pChanges->a[i].pExpr;
}else{
- sqlite3ErrorMsg(pParse, "no such column: %s", pChanges->a[i].zName);
+ tdsqlite3ErrorMsg(pParse, "no such column: %s", pChanges->a[i].zEName);
pParse->checkSchema = 1;
goto update_cleanup;
}
@@ -124729,7 +140036,7 @@ SQLITE_PRIVATE void sqlite3Update(
#ifndef SQLITE_OMIT_AUTHORIZATION
{
int rc;
- rc = sqlite3AuthCheck(pParse, SQLITE_UPDATE, pTab->zName,
+ rc = tdsqlite3AuthCheck(pParse, SQLITE_UPDATE, pTab->zName,
j<0 ? "ROWID" : pTab->aCol[j].zName,
db->aDb[iDb].zDbSName);
if( rc==SQLITE_DENY ){
@@ -124745,6 +140052,33 @@ SQLITE_PRIVATE void sqlite3Update(
assert( chngPk==0 || chngPk==1 );
chngKey = chngRowid + chngPk;
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ /* Mark generated columns as changing if their generator expressions
+ ** reference any changing column. The actual aXRef[] value for
+ ** generated expressions is not used, other than to check to see that it
+ ** is non-negative, so the value of aXRef[] for generated columns can be
+ ** set to any non-negative number. We use 99999 so that the value is
+ ** obvious when looking at aXRef[] in a symbolic debugger.
+ */
+ if( pTab->tabFlags & TF_HasGenerated ){
+ int bProgress;
+ testcase( pTab->tabFlags & TF_HasVirtual );
+ testcase( pTab->tabFlags & TF_HasStored );
+ do{
+ bProgress = 0;
+ for(i=0; i<pTab->nCol; i++){
+ if( aXRef[i]>=0 ) continue;
+ if( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 ) continue;
+ if( tdsqlite3ExprReferencesUpdatedColumn(pTab->aCol[i].pDflt,
+ aXRef, chngRowid) ){
+ aXRef[i] = 99999;
+ bProgress = 1;
+ }
+ }
+ }while( bProgress );
+ }
+#endif
+
/* The SET expressions are not actually used inside the WHERE loop.
** So reset the colUsed mask. Unless this is a virtual table. In that
** case, set all bits of the colUsed mask (to ensure that the virtual
@@ -124752,41 +140086,55 @@ SQLITE_PRIVATE void sqlite3Update(
*/
pTabList->a[0].colUsed = IsVirtual(pTab) ? ALLBITS : 0;
- hasFK = sqlite3FkRequired(pParse, pTab, aXRef, chngKey);
+ hasFK = tdsqlite3FkRequired(pParse, pTab, aXRef, chngKey);
/* There is one entry in the aRegIdx[] array for each index on the table
** being updated. Fill in aRegIdx[] with a register number that will hold
** the key for accessing each index.
- **
- ** FIXME: Be smarter about omitting indexes that use expressions.
*/
- for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
+ if( onError==OE_Replace ) bReplace = 1;
+ for(nAllIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nAllIdx++){
int reg;
- if( chngKey || hasFK || pIdx->pPartIdxWhere || pIdx==pPk ){
+ if( chngKey || hasFK>1 || pIdx==pPk
+ || indexWhereClauseMightChange(pIdx,aXRef,chngRowid)
+ ){
reg = ++pParse->nMem;
+ pParse->nMem += pIdx->nColumn;
}else{
reg = 0;
for(i=0; i<pIdx->nKeyCol; i++){
- i16 iIdxCol = pIdx->aiColumn[i];
- if( iIdxCol<0 || aXRef[iIdxCol]>=0 ){
+ if( indexColumnIsBeingUpdated(pIdx, i, aXRef, chngRowid) ){
reg = ++pParse->nMem;
+ pParse->nMem += pIdx->nColumn;
+ if( onError==OE_Default && pIdx->onError==OE_Replace ){
+ bReplace = 1;
+ }
break;
}
}
}
- if( reg==0 ) aToOpen[j+1] = 0;
- aRegIdx[j] = reg;
+ if( reg==0 ) aToOpen[nAllIdx+1] = 0;
+ aRegIdx[nAllIdx] = reg;
+ }
+ aRegIdx[nAllIdx] = ++pParse->nMem; /* Register storing the table record */
+ if( bReplace ){
+ /* If REPLACE conflict resolution might be invoked, open cursors on all
+ ** indexes in case they are needed to delete records. */
+ memset(aToOpen, 1, nIdx+1);
}
- /* Begin generating code. */
- v = sqlite3GetVdbe(pParse);
- if( v==0 ) goto update_cleanup;
- if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
- sqlite3BeginWriteOperation(pParse, 1, iDb);
+ if( pParse->nested==0 ) tdsqlite3VdbeCountChanges(v);
+ tdsqlite3BeginWriteOperation(pParse, pTrigger || hasFK, iDb);
/* Allocate required registers. */
if( !IsVirtual(pTab) ){
- regRowSet = ++pParse->nMem;
+ /* For now, regRowSet and aRegIdx[nAllIdx] share the same register.
+ ** If regRowSet turns out to be needed, then aRegIdx[nAllIdx] will be
+ ** reallocated. aRegIdx[nAllIdx] is the register in which the main
+ ** table record is written. regRowSet holds the RowSet for the
+ ** two-pass update algorithm. */
+ assert( aRegIdx[nAllIdx]==pParse->nMem );
+ regRowSet = aRegIdx[nAllIdx];
regOldRowid = regNewRowid = ++pParse->nMem;
if( chngPk || pTrigger || hasFK ){
regOld = pParse->nMem + 1;
@@ -124801,7 +140149,7 @@ SQLITE_PRIVATE void sqlite3Update(
/* Start the view context. */
if( isView ){
- sqlite3AuthContextPush(pParse, &sContext, pTab->zName);
+ tdsqlite3AuthContextPush(pParse, &sContext, pTab->zName);
}
/* If we are trying to update a view, realize that view into
@@ -124809,14 +140157,18 @@ SQLITE_PRIVATE void sqlite3Update(
*/
#if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
if( isView ){
- sqlite3MaterializeView(pParse, pTab, pWhere, iDataCur);
+ tdsqlite3MaterializeView(pParse, pTab,
+ pWhere, pOrderBy, pLimit, iDataCur
+ );
+ pOrderBy = 0;
+ pLimit = 0;
}
#endif
/* Resolve the column names in all the expressions in the
** WHERE clause.
*/
- if( sqlite3ResolveExprNames(&sNC, pWhere) ){
+ if( tdsqlite3ResolveExprNames(&sNC, pWhere) ){
goto update_cleanup;
}
@@ -124829,150 +140181,199 @@ SQLITE_PRIVATE void sqlite3Update(
}
#endif
- /* Begin the database scan
- */
+ /* Jump to labelBreak to abandon further processing of this UPDATE */
+ labelContinue = labelBreak = tdsqlite3VdbeMakeLabel(pParse);
+
+ /* Not an UPSERT. Normal processing. Begin by
+ ** initialize the count of updated rows */
+ if( (db->flags&SQLITE_CountRows)!=0
+ && !pParse->pTriggerTab
+ && !pParse->nested
+ && pUpsert==0
+ ){
+ regRowCount = ++pParse->nMem;
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
+ }
+
if( HasRowid(pTab) ){
- sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid);
- pWInfo = sqlite3WhereBegin(
- pParse, pTabList, pWhere, 0, 0,
- WHERE_ONEPASS_DESIRED | WHERE_SEEK_TABLE, iIdxCur
- );
- if( pWInfo==0 ) goto update_cleanup;
- okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
-
- /* Remember the rowid of every item to be updated.
- */
- sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid);
- if( !okOnePass ){
- sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid);
- }
-
- /* End the database scan loop.
- */
- sqlite3WhereEnd(pWInfo);
+ tdsqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid);
}else{
- int iPk; /* First of nPk memory cells holding PRIMARY KEY value */
- i16 nPk; /* Number of components of the PRIMARY KEY */
- int addrOpen; /* Address of the OpenEphemeral instruction */
-
assert( pPk!=0 );
nPk = pPk->nKeyCol;
iPk = pParse->nMem+1;
pParse->nMem += nPk;
regKey = ++pParse->nMem;
- iEph = pParse->nTab++;
- sqlite3VdbeAddOp2(v, OP_Null, 0, iPk);
- addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk);
- sqlite3VdbeSetP4KeyInfo(pParse, pPk);
- pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,
- WHERE_ONEPASS_DESIRED, iIdxCur);
+ if( pUpsert==0 ){
+ iEph = pParse->nTab++;
+ tdsqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1);
+ addrOpen = tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pPk);
+ }
+ }
+
+ if( pUpsert ){
+ /* If this is an UPSERT, then all cursors have already been opened by
+ ** the outer INSERT and the data cursor should be pointing at the row
+ ** that is to be updated. So bypass the code that searches for the
+ ** row(s) to be updated.
+ */
+ pWInfo = 0;
+ eOnePass = ONEPASS_SINGLE;
+ tdsqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL);
+ bFinishSeek = 0;
+ }else{
+ /* Begin the database scan.
+ **
+ ** Do not consider a single-pass strategy for a multi-row update if
+ ** there are any triggers or foreign keys to process, or rows may
+ ** be deleted as a result of REPLACE conflict handling. Any of these
+ ** things might disturb a cursor being used to scan through the table
+ ** or index, causing a single-pass approach to malfunction. */
+ flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE;
+ if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
+ flags |= WHERE_ONEPASS_MULTIROW;
+ }
+ pWInfo = tdsqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
if( pWInfo==0 ) goto update_cleanup;
- okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
+
+ /* A one-pass strategy that might update more than one row may not
+ ** be used if any column of the index used for the scan is being
+ ** updated. Otherwise, if there is an index on "b", statements like
+ ** the following could create an infinite loop:
+ **
+ ** UPDATE t1 SET b=b+1 WHERE b>?
+ **
+ ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI
+ ** strategy that uses an index for which one or more columns are being
+ ** updated. */
+ eOnePass = tdsqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
+ bFinishSeek = tdsqlite3WhereUsesDeferredSeek(pWInfo);
+ if( eOnePass!=ONEPASS_SINGLE ){
+ tdsqlite3MultiWrite(pParse);
+ if( eOnePass==ONEPASS_MULTI ){
+ int iCur = aiCurOnePass[1];
+ if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){
+ eOnePass = ONEPASS_OFF;
+ }
+ assert( iCur!=iDataCur || !HasRowid(pTab) );
+ }
+ }
+ }
+
+ if( HasRowid(pTab) ){
+ /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF
+ ** mode, write the rowid into the FIFO. In either of the one-pass modes,
+ ** leave it in register regOldRowid. */
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid);
+ if( eOnePass==ONEPASS_OFF ){
+ /* We need to use regRowSet, so reallocate aRegIdx[nAllIdx] */
+ aRegIdx[nAllIdx] = ++pParse->nMem;
+ tdsqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid);
+ }
+ }else{
+ /* Read the PK of the current row into an array of registers. In
+ ** ONEPASS_OFF mode, serialize the array into a record and store it in
+ ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change
+ ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table
+ ** is not required) and leave the PK fields in the array of registers. */
for(i=0; i<nPk; i++){
assert( pPk->aiColumn[i]>=0 );
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, pPk->aiColumn[i],
- iPk+i);
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,
+ pPk->aiColumn[i], iPk+i);
}
- if( okOnePass ){
- sqlite3VdbeChangeToNoop(v, addrOpen);
+ if( eOnePass ){
+ if( addrOpen ) tdsqlite3VdbeChangeToNoop(v, addrOpen);
nKey = nPk;
regKey = iPk;
}else{
- sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey,
- sqlite3IndexAffinityStr(db, pPk), nPk);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, iEph, regKey);
+ tdsqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey,
+ tdsqlite3IndexAffinityStr(db, pPk), nPk);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk);
}
- sqlite3WhereEnd(pWInfo);
- }
-
- /* Initialize the count of updated rows
- */
- if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab ){
- regRowCount = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
}
- labelBreak = sqlite3VdbeMakeLabel(v);
- if( !isView ){
- /*
- ** Open every index that needs updating. Note that if any
- ** index could potentially invoke a REPLACE conflict resolution
- ** action, then we need to open all indices because we might need
- ** to be deleting some records.
- */
- if( onError==OE_Replace ){
- memset(aToOpen, 1, nIdx+1);
- }else{
- for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- if( pIdx->onError==OE_Replace ){
- memset(aToOpen, 1, nIdx+1);
- break;
- }
- }
+ if( pUpsert==0 ){
+ if( eOnePass!=ONEPASS_MULTI ){
+ tdsqlite3WhereEnd(pWInfo);
}
- if( okOnePass ){
- if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0;
- if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0;
+
+ if( !isView ){
+ int addrOnce = 0;
+
+ /* Open every index that needs updating. */
+ if( eOnePass!=ONEPASS_OFF ){
+ if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0;
+ if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0;
+ }
+
+ if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){
+ addrOnce = tdsqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ }
+ tdsqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur,
+ aToOpen, 0, 0);
+ if( addrOnce ) tdsqlite3VdbeJumpHere(v, addrOnce);
}
- sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, aToOpen,
- 0, 0);
- }
-
- /* Top of the update loop */
- if( okOnePass ){
- if( aToOpen[iDataCur-iBaseCur] && !isView ){
- assert( pPk );
- sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey, nKey);
- VdbeCoverageNeverTaken(v);
+
+ /* Top of the update loop */
+ if( eOnePass!=ONEPASS_OFF ){
+ if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){
+ assert( pPk );
+ tdsqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey,nKey);
+ VdbeCoverage(v);
+ }
+ if( eOnePass!=ONEPASS_SINGLE ){
+ labelContinue = tdsqlite3VdbeMakeLabel(pParse);
+ }
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak);
+ VdbeCoverageIf(v, pPk==0);
+ VdbeCoverageIf(v, pPk!=0);
+ }else if( pPk ){
+ labelContinue = tdsqlite3VdbeMakeLabel(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v);
+ addrTop = tdsqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
+ tdsqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0);
+ VdbeCoverage(v);
+ }else{
+ labelContinue = tdsqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet,labelBreak,
+ regOldRowid);
+ VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
+ VdbeCoverage(v);
}
- labelContinue = labelBreak;
- sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak);
- VdbeCoverageIf(v, pPk==0);
- VdbeCoverageIf(v, pPk!=0);
- }else if( pPk ){
- labelContinue = sqlite3VdbeMakeLabel(v);
- sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v);
- addrTop = sqlite3VdbeAddOp2(v, OP_RowKey, iEph, regKey);
- sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0);
- VdbeCoverage(v);
- }else{
- labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, labelBreak,
- regOldRowid);
- VdbeCoverage(v);
- sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
- VdbeCoverage(v);
}
- /* If the record number will change, set register regNewRowid to
- ** contain the new value. If the record number is not being modified,
+ /* If the rowid value will change, set register regNewRowid to
+ ** contain the new value. If the rowid is not being modified,
** then regNewRowid is the same register as regOldRowid, which is
** already populated. */
assert( chngKey || pTrigger || hasFK || regOldRowid==regNewRowid );
if( chngRowid ){
- sqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
- sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid); VdbeCoverage(v);
+ tdsqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
+ tdsqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid); VdbeCoverage(v);
}
/* Compute the old pre-UPDATE content of the row being changed, if that
** information is needed */
if( chngPk || hasFK || pTrigger ){
- u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0);
- oldmask |= sqlite3TriggerColmask(pParse,
+ u32 oldmask = (hasFK ? tdsqlite3FkOldmask(pParse, pTab) : 0);
+ oldmask |= tdsqlite3TriggerColmask(pParse,
pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError
);
for(i=0; i<pTab->nCol; i++){
+ u32 colFlags = pTab->aCol[i].colFlags;
+ k = tdsqlite3TableColumnToStorage(pTab, i) + regOld;
if( oldmask==0xffffffff
|| (i<32 && (oldmask & MASKBIT32(i))!=0)
- || (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0
+ || (colFlags & COLFLAG_PRIMKEY)!=0
){
testcase( oldmask!=0xffffffff && i==31 );
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regOld+i);
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k);
}else{
- sqlite3VdbeAddOp2(v, OP_Null, 0, regOld+i);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, k);
}
}
if( chngRowid==0 && pPk==0 ){
- sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid);
+ tdsqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid);
}
}
@@ -124989,16 +140390,18 @@ SQLITE_PRIVATE void sqlite3Update(
** may have modified them). So not loading those that are not going to
** be used eliminates some redundant opcodes.
*/
- newmask = sqlite3TriggerColmask(
+ newmask = tdsqlite3TriggerColmask(
pParse, pTrigger, pChanges, 1, TRIGGER_BEFORE, pTab, onError
);
- for(i=0; i<pTab->nCol; i++){
+ for(i=0, k=regNew; i<pTab->nCol; i++, k++){
if( i==pTab->iPKey ){
- sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, k);
+ }else if( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)!=0 ){
+ if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--;
}else{
j = aXRef[i];
if( j>=0 ){
- sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i);
+ tdsqlite3ExprCode(pParse, pChanges->a[j].pExpr, k);
}else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask & MASKBIT32(i)) ){
/* This branch loads the value of a column that will not be changed
** into a register. This is done if there are no BEFORE triggers, or
@@ -125007,19 +140410,27 @@ SQLITE_PRIVATE void sqlite3Update(
*/
testcase( i==31 );
testcase( i==32 );
- sqlite3ExprCodeGetColumnToReg(pParse, pTab, i, iDataCur, regNew+i);
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k);
+ bFinishSeek = 0;
}else{
- sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, k);
}
}
}
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ if( pTab->tabFlags & TF_HasGenerated ){
+ testcase( pTab->tabFlags & TF_HasVirtual );
+ testcase( pTab->tabFlags & TF_HasStored );
+ tdsqlite3ComputeGeneratedColumns(pParse, regNew, pTab);
+ }
+#endif
/* Fire any BEFORE UPDATE triggers. This happens before constraints are
** verified. One could argue that this is wrong.
*/
if( tmask&TRIGGER_BEFORE ){
- sqlite3TableAffinity(v, pTab, regNew);
- sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
+ tdsqlite3TableAffinity(v, pTab, regNew);
+ tdsqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue);
/* The row-trigger may have deleted the row being updated. In this
@@ -125029,50 +140440,73 @@ SQLITE_PRIVATE void sqlite3Update(
** documentation.
*/
if( pPk ){
- sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue,regKey,nKey);
+ tdsqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue,regKey,nKey);
VdbeCoverage(v);
}else{
- sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
+ tdsqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
VdbeCoverage(v);
}
- /* If it did not delete it, the row-trigger may still have modified
+ /* After-BEFORE-trigger-reload-loop:
+ ** If it did not delete it, the BEFORE trigger may still have modified
** some of the columns of the row being updated. Load the values for
- ** all columns not modified by the update statement into their
- ** registers in case this has happened.
+ ** all columns not modified by the update statement into their registers
+ ** in case this has happened. Only unmodified columns are reloaded.
+ ** The values computed for modified columns use the values before the
+ ** BEFORE trigger runs. See test case trigger1-18.0 (added 2018-04-26)
+ ** for an example.
*/
- for(i=0; i<pTab->nCol; i++){
- if( aXRef[i]<0 && i!=pTab->iPKey ){
- sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i);
+ for(i=0, k=regNew; i<pTab->nCol; i++, k++){
+ if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){
+ if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--;
+ }else if( aXRef[i]<0 && i!=pTab->iPKey ){
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k);
}
}
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ if( pTab->tabFlags & TF_HasGenerated ){
+ testcase( pTab->tabFlags & TF_HasVirtual );
+ testcase( pTab->tabFlags & TF_HasStored );
+ tdsqlite3ComputeGeneratedColumns(pParse, regNew, pTab);
+ }
+#endif
}
if( !isView ){
- int addr1 = 0; /* Address of jump instruction */
- int bReplace = 0; /* True if REPLACE conflict resolution might happen */
-
/* Do constraint checks. */
assert( regOldRowid>0 );
- sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
+ tdsqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur,
regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace,
- aXRef);
-
- /* Do FK constraint checks. */
- if( hasFK ){
- sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey);
- }
+ aXRef, 0);
- /* Delete the index entries associated with the current record. */
+ /* If REPLACE conflict handling may have been used, or if the PK of the
+ ** row is changing, then the GenerateConstraintChecks() above may have
+ ** moved cursor iDataCur. Reseek it. */
if( bReplace || chngKey ){
if( pPk ){
- addr1 = sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, 0, regKey, nKey);
+ tdsqlite3VdbeAddOp4Int(v, OP_NotFound,iDataCur,labelContinue,regKey,nKey);
}else{
- addr1 = sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, 0, regOldRowid);
+ tdsqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid);
}
VdbeCoverageNeverTaken(v);
}
- sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx, -1);
+
+ /* Do FK constraint checks. */
+ if( hasFK ){
+ tdsqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey);
+ }
+
+ /* Delete the index entries associated with the current record. */
+ tdsqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx, -1);
+
+ /* We must run the OP_FinishSeek opcode to resolve a prior
+ ** OP_DeferredSeek if there is any possibility that there have been
+ ** no OP_Column opcodes since the OP_DeferredSeek was issued. But
+ ** we want to avoid the OP_FinishSeek if possible, as running it
+ ** costs CPU cycles. */
+ if( bFinishSeek ){
+ tdsqlite3VdbeAddOp1(v, OP_FinishSeek, iDataCur);
+ }
/* If changing the rowid value, or if there are foreign key constraints
** to process, delete the old record. Otherwise, add a noop OP_Delete
@@ -125085,94 +140519,95 @@ SQLITE_PRIVATE void sqlite3Update(
*/
assert( regNew==regNewRowid+1 );
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
- sqlite3VdbeAddOp3(v, OP_Delete, iDataCur,
- OPFLAG_ISUPDATE | ((hasFK || chngKey || pPk!=0) ? 0 : OPFLAG_ISNOOP),
+ tdsqlite3VdbeAddOp3(v, OP_Delete, iDataCur,
+ OPFLAG_ISUPDATE | ((hasFK>1 || chngKey) ? 0 : OPFLAG_ISNOOP),
regNewRowid
);
+ if( eOnePass==ONEPASS_MULTI ){
+ assert( hasFK==0 && chngKey==0 );
+ tdsqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION);
+ }
if( !pParse->nested ){
- sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE);
+ tdsqlite3VdbeAppendP4(v, pTab, P4_TABLE);
}
#else
- if( hasFK || chngKey || pPk!=0 ){
- sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0);
+ if( hasFK>1 || chngKey ){
+ tdsqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0);
}
#endif
- if( bReplace || chngKey ){
- sqlite3VdbeJumpHere(v, addr1);
- }
if( hasFK ){
- sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngKey);
+ tdsqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngKey);
}
/* Insert the new index entries and the new record. */
- sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur,
- regNewRowid, aRegIdx, 1, 0, 0);
+ tdsqlite3CompleteInsertion(
+ pParse, pTab, iDataCur, iIdxCur, regNewRowid, aRegIdx,
+ OPFLAG_ISUPDATE | (eOnePass==ONEPASS_MULTI ? OPFLAG_SAVEPOSITION : 0),
+ 0, 0
+ );
/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
** handle rows (possibly in other tables) that refer via a foreign key
** to the row just updated. */
if( hasFK ){
- sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngKey);
+ tdsqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngKey);
}
}
/* Increment the row counter
*/
- if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab){
- sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
+ if( regRowCount ){
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
}
- sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
+ tdsqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue);
/* Repeat the above with the next record to be updated, until
** all record selected by the WHERE clause have been updated.
*/
- if( okOnePass ){
+ if( eOnePass==ONEPASS_SINGLE ){
/* Nothing to do at end-of-loop for a single-pass */
+ }else if( eOnePass==ONEPASS_MULTI ){
+ tdsqlite3VdbeResolveLabel(v, labelContinue);
+ tdsqlite3WhereEnd(pWInfo);
}else if( pPk ){
- sqlite3VdbeResolveLabel(v, labelContinue);
- sqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); VdbeCoverage(v);
+ tdsqlite3VdbeResolveLabel(v, labelContinue);
+ tdsqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); VdbeCoverage(v);
}else{
- sqlite3VdbeGoto(v, labelContinue);
- }
- sqlite3VdbeResolveLabel(v, labelBreak);
-
- /* Close all tables */
- for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
- assert( aRegIdx );
- if( aToOpen[i+1] ){
- sqlite3VdbeAddOp2(v, OP_Close, iIdxCur+i, 0);
- }
+ tdsqlite3VdbeGoto(v, labelContinue);
}
- if( iDataCur<iIdxCur ) sqlite3VdbeAddOp2(v, OP_Close, iDataCur, 0);
+ tdsqlite3VdbeResolveLabel(v, labelBreak);
/* Update the sqlite_sequence table by storing the content of the
** maximum rowid counter values recorded while inserting into
** autoincrement tables.
*/
- if( pParse->nested==0 && pParse->pTriggerTab==0 ){
- sqlite3AutoincrementEnd(pParse);
+ if( pParse->nested==0 && pParse->pTriggerTab==0 && pUpsert==0 ){
+ tdsqlite3AutoincrementEnd(pParse);
}
/*
- ** Return the number of rows that were changed. If this routine is
- ** generating code because of a call to sqlite3NestedParse(), do not
- ** invoke the callback function.
+ ** Return the number of rows that were changed, if we are tracking
+ ** that information.
*/
- if( (db->flags&SQLITE_CountRows) && !pParse->pTriggerTab && !pParse->nested ){
- sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
- sqlite3VdbeSetNumCols(v, 1);
- sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC);
+ if( regRowCount ){
+ tdsqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
+ tdsqlite3VdbeSetNumCols(v, 1);
+ tdsqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC);
}
update_cleanup:
- sqlite3AuthContextPop(&sContext);
- sqlite3DbFree(db, aXRef); /* Also frees aRegIdx[] and aToOpen[] */
- sqlite3SrcListDelete(db, pTabList);
- sqlite3ExprListDelete(db, pChanges);
- sqlite3ExprDelete(db, pWhere);
+ tdsqlite3AuthContextPop(&sContext);
+ tdsqlite3DbFree(db, aXRef); /* Also frees aRegIdx[] and aToOpen[] */
+ tdsqlite3SrcListDelete(db, pTabList);
+ tdsqlite3ExprListDelete(db, pChanges);
+ tdsqlite3ExprDelete(db, pWhere);
+#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT)
+ tdsqlite3ExprListDelete(db, pOrderBy);
+ tdsqlite3ExprDelete(db, pLimit);
+#endif
return;
}
/* Make sure "isView" and other macros defined above are undefined. Otherwise
@@ -125220,98 +140655,379 @@ static void updateVirtualTable(
Vdbe *v = pParse->pVdbe; /* Virtual machine under construction */
int ephemTab; /* Table holding the result of the SELECT */
int i; /* Loop counter */
- sqlite3 *db = pParse->db; /* Database connection */
- const char *pVTab = (const char*)sqlite3GetVTable(db, pTab);
+ tdsqlite3 *db = pParse->db; /* Database connection */
+ const char *pVTab = (const char*)tdsqlite3GetVTable(db, pTab);
WhereInfo *pWInfo;
int nArg = 2 + pTab->nCol; /* Number of arguments to VUpdate */
int regArg; /* First register in VUpdate arg array */
int regRec; /* Register in which to assemble record */
int regRowid; /* Register for ephem table rowid */
int iCsr = pSrc->a[0].iCursor; /* Cursor used for virtual table scan */
- int aDummy[2]; /* Unused arg for sqlite3WhereOkOnePass() */
- int bOnePass; /* True to use onepass strategy */
+ int aDummy[2]; /* Unused arg for tdsqlite3WhereOkOnePass() */
+ int eOnePass; /* True to use onepass strategy */
int addr; /* Address of OP_OpenEphemeral */
- /* Allocate nArg registers to martial the arguments to VUpdate. Then
+ /* Allocate nArg registers in which to gather the arguments for VUpdate. Then
** create and open the ephemeral table in which the records created from
** these arguments will be temporarily stored. */
assert( v );
ephemTab = pParse->nTab++;
- addr= sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, nArg);
+ addr= tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, nArg);
regArg = pParse->nMem + 1;
pParse->nMem += nArg;
regRec = ++pParse->nMem;
regRowid = ++pParse->nMem;
/* Start scanning the virtual table */
- pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0,0,WHERE_ONEPASS_DESIRED,0);
+ pWInfo = tdsqlite3WhereBegin(pParse, pSrc, pWhere, 0,0,WHERE_ONEPASS_DESIRED,0);
if( pWInfo==0 ) return;
/* Populate the argument registers. */
- sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg);
- if( pRowid ){
- sqlite3ExprCode(pParse, pRowid, regArg+1);
- }else{
- sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1);
- }
for(i=0; i<pTab->nCol; i++){
+ assert( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 );
if( aXRef[i]>=0 ){
- sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i);
+ tdsqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i);
}else{
- sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i);
+ tdsqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_NOCHNG);/* Enable tdsqlite3_vtab_nochange() */
}
}
+ if( HasRowid(pTab) ){
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg);
+ if( pRowid ){
+ tdsqlite3ExprCode(pParse, pRowid, regArg+1);
+ }else{
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1);
+ }
+ }else{
+ Index *pPk; /* PRIMARY KEY index */
+ i16 iPk; /* PRIMARY KEY column */
+ pPk = tdsqlite3PrimaryKeyIndex(pTab);
+ assert( pPk!=0 );
+ assert( pPk->nKeyCol==1 );
+ iPk = pPk->aiColumn[0];
+ tdsqlite3VdbeAddOp3(v, OP_VColumn, iCsr, iPk, regArg);
+ tdsqlite3VdbeAddOp2(v, OP_SCopy, regArg+2+iPk, regArg+1);
+ }
- bOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy);
+ eOnePass = tdsqlite3WhereOkOnePass(pWInfo, aDummy);
- if( bOnePass ){
+ /* There is no ONEPASS_MULTI on virtual tables */
+ assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE );
+
+ if( eOnePass ){
/* If using the onepass strategy, no-op out the OP_OpenEphemeral coded
- ** above. Also, if this is a top-level parse (not a trigger), clear the
- ** multi-write flag so that the VM does not open a statement journal */
- sqlite3VdbeChangeToNoop(v, addr);
- if( sqlite3IsToplevel(pParse) ){
- pParse->isMultiWrite = 0;
- }
+ ** above. */
+ tdsqlite3VdbeChangeToNoop(v, addr);
+ tdsqlite3VdbeAddOp1(v, OP_Close, iCsr);
}else{
/* Create a record from the argument register contents and insert it into
** the ephemeral table. */
- sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec);
- sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid);
- sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid);
+ tdsqlite3MultiWrite(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec);
+#ifdef SQLITE_DEBUG
+ /* Signal an assert() within OP_MakeRecord that it is allowed to
+ ** accept no-change records with serial_type 10 */
+ tdsqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC);
+#endif
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid);
}
- if( bOnePass==0 ){
+ if( eOnePass==ONEPASS_OFF ){
/* End the virtual table scan */
- sqlite3WhereEnd(pWInfo);
+ tdsqlite3WhereEnd(pWInfo);
/* Begin scannning through the ephemeral table. */
- addr = sqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v);
+ addr = tdsqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v);
/* Extract arguments from the current row of the ephemeral table and
** invoke the VUpdate method. */
for(i=0; i<nArg; i++){
- sqlite3VdbeAddOp3(v, OP_Column, ephemTab, i, regArg+i);
+ tdsqlite3VdbeAddOp3(v, OP_Column, ephemTab, i, regArg+i);
}
}
- sqlite3VtabMakeWritable(pParse, pTab);
- sqlite3VdbeAddOp4(v, OP_VUpdate, 0, nArg, regArg, pVTab, P4_VTAB);
- sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
- sqlite3MayAbort(pParse);
+ tdsqlite3VtabMakeWritable(pParse, pTab);
+ tdsqlite3VdbeAddOp4(v, OP_VUpdate, 0, nArg, regArg, pVTab, P4_VTAB);
+ tdsqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
+ tdsqlite3MayAbort(pParse);
/* End of the ephemeral table scan. Or, if using the onepass strategy,
** jump to here if the scan visited zero rows. */
- if( bOnePass==0 ){
- sqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr+1); VdbeCoverage(v);
- sqlite3VdbeJumpHere(v, addr);
- sqlite3VdbeAddOp2(v, OP_Close, ephemTab, 0);
+ if( eOnePass==ONEPASS_OFF ){
+ tdsqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr+1); VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addr);
+ tdsqlite3VdbeAddOp2(v, OP_Close, ephemTab, 0);
}else{
- sqlite3WhereEnd(pWInfo);
+ tdsqlite3WhereEnd(pWInfo);
}
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
/************** End of update.c **********************************************/
+/************** Begin file upsert.c ******************************************/
+/*
+** 2018-04-12
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code to implement various aspects of UPSERT
+** processing and handling of the Upsert object.
+*/
+/* #include "sqliteInt.h" */
+
+#ifndef SQLITE_OMIT_UPSERT
+/*
+** Free a list of Upsert objects
+*/
+SQLITE_PRIVATE void tdsqlite3UpsertDelete(tdsqlite3 *db, Upsert *p){
+ if( p ){
+ tdsqlite3ExprListDelete(db, p->pUpsertTarget);
+ tdsqlite3ExprDelete(db, p->pUpsertTargetWhere);
+ tdsqlite3ExprListDelete(db, p->pUpsertSet);
+ tdsqlite3ExprDelete(db, p->pUpsertWhere);
+ tdsqlite3DbFree(db, p);
+ }
+}
+
+/*
+** Duplicate an Upsert object.
+*/
+SQLITE_PRIVATE Upsert *tdsqlite3UpsertDup(tdsqlite3 *db, Upsert *p){
+ if( p==0 ) return 0;
+ return tdsqlite3UpsertNew(db,
+ tdsqlite3ExprListDup(db, p->pUpsertTarget, 0),
+ tdsqlite3ExprDup(db, p->pUpsertTargetWhere, 0),
+ tdsqlite3ExprListDup(db, p->pUpsertSet, 0),
+ tdsqlite3ExprDup(db, p->pUpsertWhere, 0)
+ );
+}
+
+/*
+** Create a new Upsert object.
+*/
+SQLITE_PRIVATE Upsert *tdsqlite3UpsertNew(
+ tdsqlite3 *db, /* Determines which memory allocator to use */
+ ExprList *pTarget, /* Target argument to ON CONFLICT, or NULL */
+ Expr *pTargetWhere, /* Optional WHERE clause on the target */
+ ExprList *pSet, /* UPDATE columns, or NULL for a DO NOTHING */
+ Expr *pWhere /* WHERE clause for the ON CONFLICT UPDATE */
+){
+ Upsert *pNew;
+ pNew = tdsqlite3DbMallocRaw(db, sizeof(Upsert));
+ if( pNew==0 ){
+ tdsqlite3ExprListDelete(db, pTarget);
+ tdsqlite3ExprDelete(db, pTargetWhere);
+ tdsqlite3ExprListDelete(db, pSet);
+ tdsqlite3ExprDelete(db, pWhere);
+ return 0;
+ }else{
+ pNew->pUpsertTarget = pTarget;
+ pNew->pUpsertTargetWhere = pTargetWhere;
+ pNew->pUpsertSet = pSet;
+ pNew->pUpsertWhere = pWhere;
+ pNew->pUpsertIdx = 0;
+ }
+ return pNew;
+}
+
+/*
+** Analyze the ON CONFLICT clause described by pUpsert. Resolve all
+** symbols in the conflict-target.
+**
+** Return SQLITE_OK if everything works, or an error code is something
+** is wrong.
+*/
+SQLITE_PRIVATE int tdsqlite3UpsertAnalyzeTarget(
+ Parse *pParse, /* The parsing context */
+ SrcList *pTabList, /* Table into which we are inserting */
+ Upsert *pUpsert /* The ON CONFLICT clauses */
+){
+ Table *pTab; /* That table into which we are inserting */
+ int rc; /* Result code */
+ int iCursor; /* Cursor used by pTab */
+ Index *pIdx; /* One of the indexes of pTab */
+ ExprList *pTarget; /* The conflict-target clause */
+ Expr *pTerm; /* One term of the conflict-target clause */
+ NameContext sNC; /* Context for resolving symbolic names */
+ Expr sCol[2]; /* Index column converted into an Expr */
+
+ assert( pTabList->nSrc==1 );
+ assert( pTabList->a[0].pTab!=0 );
+ assert( pUpsert!=0 );
+ assert( pUpsert->pUpsertTarget!=0 );
+
+ /* Resolve all symbolic names in the conflict-target clause, which
+ ** includes both the list of columns and the optional partial-index
+ ** WHERE clause.
+ */
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pParse;
+ sNC.pSrcList = pTabList;
+ rc = tdsqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget);
+ if( rc ) return rc;
+ rc = tdsqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere);
+ if( rc ) return rc;
+
+ /* Check to see if the conflict target matches the rowid. */
+ pTab = pTabList->a[0].pTab;
+ pTarget = pUpsert->pUpsertTarget;
+ iCursor = pTabList->a[0].iCursor;
+ if( HasRowid(pTab)
+ && pTarget->nExpr==1
+ && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN
+ && pTerm->iColumn==XN_ROWID
+ ){
+ /* The conflict-target is the rowid of the primary table */
+ assert( pUpsert->pUpsertIdx==0 );
+ return SQLITE_OK;
+ }
+
+ /* Initialize sCol[0..1] to be an expression parse tree for a
+ ** single column of an index. The sCol[0] node will be the TK_COLLATE
+ ** operator and sCol[1] will be the TK_COLUMN operator. Code below
+ ** will populate the specific collation and column number values
+ ** prior to comparing against the conflict-target expression.
+ */
+ memset(sCol, 0, sizeof(sCol));
+ sCol[0].op = TK_COLLATE;
+ sCol[0].pLeft = &sCol[1];
+ sCol[1].op = TK_COLUMN;
+ sCol[1].iTable = pTabList->a[0].iCursor;
+
+ /* Check for matches against other indexes */
+ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ int ii, jj, nn;
+ if( !IsUniqueIndex(pIdx) ) continue;
+ if( pTarget->nExpr!=pIdx->nKeyCol ) continue;
+ if( pIdx->pPartIdxWhere ){
+ if( pUpsert->pUpsertTargetWhere==0 ) continue;
+ if( tdsqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere,
+ pIdx->pPartIdxWhere, iCursor)!=0 ){
+ continue;
+ }
+ }
+ nn = pIdx->nKeyCol;
+ for(ii=0; ii<nn; ii++){
+ Expr *pExpr;
+ sCol[0].u.zToken = (char*)pIdx->azColl[ii];
+ if( pIdx->aiColumn[ii]==XN_EXPR ){
+ assert( pIdx->aColExpr!=0 );
+ assert( pIdx->aColExpr->nExpr>ii );
+ pExpr = pIdx->aColExpr->a[ii].pExpr;
+ if( pExpr->op!=TK_COLLATE ){
+ sCol[0].pLeft = pExpr;
+ pExpr = &sCol[0];
+ }
+ }else{
+ sCol[0].pLeft = &sCol[1];
+ sCol[1].iColumn = pIdx->aiColumn[ii];
+ pExpr = &sCol[0];
+ }
+ for(jj=0; jj<nn; jj++){
+ if( tdsqlite3ExprCompare(pParse, pTarget->a[jj].pExpr, pExpr,iCursor)<2 ){
+ break; /* Column ii of the index matches column jj of target */
+ }
+ }
+ if( jj>=nn ){
+ /* The target contains no match for column jj of the index */
+ break;
+ }
+ }
+ if( ii<nn ){
+ /* Column ii of the index did not match any term of the conflict target.
+ ** Continue the search with the next index. */
+ continue;
+ }
+ pUpsert->pUpsertIdx = pIdx;
+ return SQLITE_OK;
+ }
+ tdsqlite3ErrorMsg(pParse, "ON CONFLICT clause does not match any "
+ "PRIMARY KEY or UNIQUE constraint");
+ return SQLITE_ERROR;
+}
+
+/*
+** Generate bytecode that does an UPDATE as part of an upsert.
+**
+** If pIdx is NULL, then the UNIQUE constraint that failed was the IPK.
+** In this case parameter iCur is a cursor open on the table b-tree that
+** currently points to the conflicting table row. Otherwise, if pIdx
+** is not NULL, then pIdx is the constraint that failed and iCur is a
+** cursor points to the conflicting row.
+*/
+SQLITE_PRIVATE void tdsqlite3UpsertDoUpdate(
+ Parse *pParse, /* The parsing and code-generating context */
+ Upsert *pUpsert, /* The ON CONFLICT clause for the upsert */
+ Table *pTab, /* The table being updated */
+ Index *pIdx, /* The UNIQUE constraint that failed */
+ int iCur /* Cursor for pIdx (or pTab if pIdx==NULL) */
+){
+ Vdbe *v = pParse->pVdbe;
+ tdsqlite3 *db = pParse->db;
+ SrcList *pSrc; /* FROM clause for the UPDATE */
+ int iDataCur;
+ int i;
+
+ assert( v!=0 );
+ assert( pUpsert!=0 );
+ VdbeNoopComment((v, "Begin DO UPDATE of UPSERT"));
+ iDataCur = pUpsert->iDataCur;
+ if( pIdx && iCur!=iDataCur ){
+ if( HasRowid(pTab) ){
+ int regRowid = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_IdxRowid, iCur, regRowid);
+ tdsqlite3VdbeAddOp3(v, OP_SeekRowid, iDataCur, 0, regRowid);
+ VdbeCoverage(v);
+ tdsqlite3ReleaseTempReg(pParse, regRowid);
+ }else{
+ Index *pPk = tdsqlite3PrimaryKeyIndex(pTab);
+ int nPk = pPk->nKeyCol;
+ int iPk = pParse->nMem+1;
+ pParse->nMem += nPk;
+ for(i=0; i<nPk; i++){
+ int k;
+ assert( pPk->aiColumn[i]>=0 );
+ k = tdsqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iCur, k, iPk+i);
+ VdbeComment((v, "%s.%s", pIdx->zName,
+ pTab->aCol[pPk->aiColumn[i]].zName));
+ }
+ tdsqlite3VdbeVerifyAbortable(v, OE_Abort);
+ i = tdsqlite3VdbeAddOp4Int(v, OP_Found, iDataCur, 0, iPk, nPk);
+ VdbeCoverage(v);
+ tdsqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CORRUPT, OE_Abort, 0,
+ "corrupt database", P4_STATIC);
+ tdsqlite3MayAbort(pParse);
+ tdsqlite3VdbeJumpHere(v, i);
+ }
+ }
+ /* pUpsert does not own pUpsertSrc - the outer INSERT statement does. So
+ ** we have to make a copy before passing it down into tdsqlite3Update() */
+ pSrc = tdsqlite3SrcListDup(db, pUpsert->pUpsertSrc, 0);
+ /* excluded.* columns of type REAL need to be converted to a hard real */
+ for(i=0; i<pTab->nCol; i++){
+ if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){
+ tdsqlite3VdbeAddOp1(v, OP_RealAffinity, pUpsert->regData+i);
+ }
+ }
+ tdsqlite3Update(pParse, pSrc, pUpsert->pUpsertSet,
+ pUpsert->pUpsertWhere, OE_Abort, 0, 0, pUpsert);
+ pUpsert->pUpsertSet = 0; /* Will have been deleted by tdsqlite3Update() */
+ pUpsert->pUpsertWhere = 0; /* Will have been deleted by tdsqlite3Update() */
+ VdbeNoopComment((v, "End DO UPDATE of UPSERT"));
+}
+
+#endif /* SQLITE_OMIT_UPSERT */
+
+/************** End of upsert.c **********************************************/
/************** Begin file vacuum.c ******************************************/
/*
** 2003 April 6
@@ -125344,18 +141060,24 @@ static void updateVirtualTable(
** The execSqlF() routine does the same thing, except it accepts
** a format string as its third argument
*/
-static int execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){
- sqlite3_stmt *pStmt;
+static int execSql(tdsqlite3 *db, char **pzErrMsg, const char *zSql){
+ tdsqlite3_stmt *pStmt;
int rc;
/* printf("SQL: [%s]\n", zSql); fflush(stdout); */
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ rc = tdsqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
if( rc!=SQLITE_OK ) return rc;
- while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
- const char *zSubSql = (const char*)sqlite3_column_text(pStmt,0);
- assert( sqlite3_strnicmp(zSql,"SELECT",6)==0 );
- if( zSubSql ){
- assert( zSubSql[0]!='S' );
+ while( SQLITE_ROW==(rc = tdsqlite3_step(pStmt)) ){
+ const char *zSubSql = (const char*)tdsqlite3_column_text(pStmt,0);
+ assert( tdsqlite3_strnicmp(zSql,"SELECT",6)==0 );
+ /* The secondary SQL must be one of CREATE TABLE, CREATE INDEX,
+ ** or INSERT. Historically there have been attacks that first
+ ** corrupt the sqlite_master.sql field with other kinds of statements
+ ** then run VACUUM to get those statements to execute at inappropriate
+ ** times. */
+ if( zSubSql
+ && (strncmp(zSubSql,"CRE",3)==0 || strncmp(zSubSql,"INS",3)==0)
+ ){
rc = execSql(db, pzErrMsg, zSubSql);
if( rc!=SQLITE_OK ) break;
}
@@ -125363,21 +141085,21 @@ static int execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){
assert( rc!=SQLITE_ROW );
if( rc==SQLITE_DONE ) rc = SQLITE_OK;
if( rc ){
- sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db));
+ tdsqlite3SetString(pzErrMsg, db, tdsqlite3_errmsg(db));
}
- (void)sqlite3_finalize(pStmt);
+ (void)tdsqlite3_finalize(pStmt);
return rc;
}
-static int execSqlF(sqlite3 *db, char **pzErrMsg, const char *zSql, ...){
+static int execSqlF(tdsqlite3 *db, char **pzErrMsg, const char *zSql, ...){
char *z;
va_list ap;
int rc;
va_start(ap, zSql);
- z = sqlite3VMPrintf(db, zSql, ap);
+ z = tdsqlite3VMPrintf(db, zSql, ap);
va_end(ap);
if( z==0 ) return SQLITE_NOMEM;
rc = execSql(db, pzErrMsg, z);
- sqlite3DbFree(db, z);
+ tdsqlite3DbFree(db, z);
return rc;
}
@@ -125411,63 +141133,110 @@ static int execSqlF(sqlite3 *db, char **pzErrMsg, const char *zSql, ...){
** transient would cause the database file to appear to be deleted
** following reboot.
*/
-SQLITE_PRIVATE void sqlite3Vacuum(Parse *pParse, Token *pNm){
- Vdbe *v = sqlite3GetVdbe(pParse);
- int iDb = pNm ? sqlite3TwoPartName(pParse, pNm, pNm, &pNm) : 0;
- if( v && (iDb>=2 || iDb==0) ){
- sqlite3VdbeAddOp1(v, OP_Vacuum, iDb);
- sqlite3VdbeUsesBtree(v, iDb);
+SQLITE_PRIVATE void tdsqlite3Vacuum(Parse *pParse, Token *pNm, Expr *pInto){
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ int iDb = 0;
+ if( v==0 ) goto build_vacuum_end;
+ if( pParse->nErr ) goto build_vacuum_end;
+ if( pNm ){
+#ifndef SQLITE_BUG_COMPATIBLE_20160819
+ /* Default behavior: Report an error if the argument to VACUUM is
+ ** not recognized */
+ iDb = tdsqlite3TwoPartName(pParse, pNm, pNm, &pNm);
+ if( iDb<0 ) goto build_vacuum_end;
+#else
+ /* When SQLITE_BUG_COMPATIBLE_20160819 is defined, unrecognized arguments
+ ** to VACUUM are silently ignored. This is a back-out of a bug fix that
+ ** occurred on 2016-08-19 (https://www.sqlite.org/src/info/083f9e6270).
+ ** The buggy behavior is required for binary compatibility with some
+ ** legacy applications. */
+ iDb = tdsqlite3FindDb(pParse->db, pNm);
+ if( iDb<0 ) iDb = 0;
+#endif
+ }
+ if( iDb!=1 ){
+ int iIntoReg = 0;
+ if( pInto && tdsqlite3ResolveSelfReference(pParse,0,0,pInto,0)==0 ){
+ iIntoReg = ++pParse->nMem;
+ tdsqlite3ExprCode(pParse, pInto, iIntoReg);
+ }
+ tdsqlite3VdbeAddOp2(v, OP_Vacuum, iDb, iIntoReg);
+ tdsqlite3VdbeUsesBtree(v, iDb);
}
+build_vacuum_end:
+ tdsqlite3ExprDelete(pParse->db, pInto);
return;
}
/*
** This routine implements the OP_Vacuum opcode of the VDBE.
*/
-SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){
+SQLITE_PRIVATE SQLITE_NOINLINE int tdsqlite3RunVacuum(
+ char **pzErrMsg, /* Write error message here */
+ tdsqlite3 *db, /* Database connection */
+ int iDb, /* Which attached DB to vacuum */
+ tdsqlite3_value *pOut /* Write results here, if not NULL. VACUUM INTO */
+){
int rc = SQLITE_OK; /* Return code from service routines */
Btree *pMain; /* The database being vacuumed */
Btree *pTemp; /* The temporary database we vacuum into */
- int saved_flags; /* Saved value of the db->flags */
+ u32 saved_mDbFlags; /* Saved value of db->mDbFlags */
+ u64 saved_flags; /* Saved value of db->flags */
int saved_nChange; /* Saved value of db->nChange */
int saved_nTotalChange; /* Saved value of db->nTotalChange */
+ u32 saved_openFlags; /* Saved value of db->openFlags */
u8 saved_mTrace; /* Saved trace settings */
Db *pDb = 0; /* Database to detach at end of vacuum */
int isMemDb; /* True if vacuuming a :memory: database */
int nRes; /* Bytes of reserved space at the end of each page */
int nDb; /* Number of attached databases */
const char *zDbMain; /* Schema name of database to vacuum */
+ const char *zOut; /* Name of output file */
if( !db->autoCommit ){
- sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction");
- return SQLITE_ERROR;
+ tdsqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction");
+ return SQLITE_ERROR; /* IMP: R-12218-18073 */
}
if( db->nVdbeActive>1 ){
- sqlite3SetString(pzErrMsg, db,"cannot VACUUM - SQL statements in progress");
- return SQLITE_ERROR;
+ tdsqlite3SetString(pzErrMsg, db,"cannot VACUUM - SQL statements in progress");
+ return SQLITE_ERROR; /* IMP: R-15610-35227 */
+ }
+ saved_openFlags = db->openFlags;
+ if( pOut ){
+ if( tdsqlite3_value_type(pOut)!=SQLITE_TEXT ){
+ tdsqlite3SetString(pzErrMsg, db, "non-text filename");
+ return SQLITE_ERROR;
+ }
+ zOut = (const char*)tdsqlite3_value_text(pOut);
+ db->openFlags &= ~SQLITE_OPEN_READONLY;
+ db->openFlags |= SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE;
+ }else{
+ zOut = "";
}
/* Save the current value of the database flags so that it can be
** restored before returning. Then set the writable-schema flag, and
** disable CHECK and foreign key constraints. */
saved_flags = db->flags;
+ saved_mDbFlags = db->mDbFlags;
saved_nChange = db->nChange;
saved_nTotalChange = db->nTotalChange;
saved_mTrace = db->mTrace;
- db->flags |= (SQLITE_WriteSchema | SQLITE_IgnoreChecks
- | SQLITE_PreferBuiltin | SQLITE_Vacuum);
- db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_CountRows);
+ db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks;
+ db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum;
+ db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder
+ | SQLITE_Defensive | SQLITE_CountRows);
db->mTrace = 0;
zDbMain = db->aDb[iDb].zDbSName;
pMain = db->aDb[iDb].pBt;
- isMemDb = sqlite3PagerIsMemdb(sqlite3BtreePager(pMain));
+ isMemDb = tdsqlite3PagerIsMemdb(tdsqlite3BtreePager(pMain));
/* Attach the temporary database as 'vacuum_db'. The synchronous pragma
** can be set to 'off' for this file, as it is not recovered if a crash
** occurs anyway. The integrity of the database is maintained by a
** (possibly synchronous) transaction opened on the main database before
- ** sqlite3BtreeCopyFile() is called.
+ ** tdsqlite3BtreeCopyFile() is called.
**
** An optimisation would be to use a non-journaled pager.
** (Later:) I tried setting "PRAGMA vacuum_db.journal_mode=OFF" but
@@ -125478,53 +141247,57 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){
** to write the journal header file.
*/
nDb = db->nDb;
- rc = execSql(db, pzErrMsg, "ATTACH''AS vacuum_db");
+ rc = execSqlF(db, pzErrMsg, "ATTACH %Q AS vacuum_db", zOut);
+ db->openFlags = saved_openFlags;
if( rc!=SQLITE_OK ) goto end_of_vacuum;
assert( (db->nDb-1)==nDb );
pDb = &db->aDb[nDb];
assert( strcmp(pDb->zDbSName,"vacuum_db")==0 );
pTemp = pDb->pBt;
-
- /* The call to execSql() to attach the temp database has left the file
- ** locked (as there was more than one active statement when the transaction
- ** to read the schema was concluded. Unlock it here so that this doesn't
- ** cause problems for the call to BtreeSetPageSize() below. */
- sqlite3BtreeCommit(pTemp);
-
- nRes = sqlite3BtreeGetOptimalReserve(pMain);
+ if( pOut ){
+ tdsqlite3_file *id = tdsqlite3PagerFile(tdsqlite3BtreePager(pTemp));
+ i64 sz = 0;
+ if( id->pMethods!=0 && (tdsqlite3OsFileSize(id, &sz)!=SQLITE_OK || sz>0) ){
+ rc = SQLITE_ERROR;
+ tdsqlite3SetString(pzErrMsg, db, "output file already exists");
+ goto end_of_vacuum;
+ }
+ db->mDbFlags |= DBFLAG_VacuumInto;
+ }
+ nRes = tdsqlite3BtreeGetOptimalReserve(pMain);
/* A VACUUM cannot change the pagesize of an encrypted database. */
#ifdef SQLITE_HAS_CODEC
if( db->nextPagesize ){
- extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*);
+ extern void tdsqlite3CodecGetKey(tdsqlite3*, int, void**, int*);
int nKey;
char *zKey;
- sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey);
+ tdsqlite3CodecGetKey(db, iDb, (void**)&zKey, &nKey);
if( nKey ) db->nextPagesize = 0;
}
#endif
- sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size);
- sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0));
- sqlite3BtreeSetPagerFlags(pTemp, PAGER_SYNCHRONOUS_OFF|PAGER_CACHESPILL);
+ tdsqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size);
+ tdsqlite3BtreeSetSpillSize(pTemp, tdsqlite3BtreeSetSpillSize(pMain,0));
+ tdsqlite3BtreeSetPagerFlags(pTemp, PAGER_SYNCHRONOUS_OFF|PAGER_CACHESPILL);
/* Begin a transaction and take an exclusive lock on the main database
- ** file. This is done before the sqlite3BtreeGetPageSize(pMain) call below,
+ ** file. This is done before the tdsqlite3BtreeGetPageSize(pMain) call below,
** to ensure that we do not try to change the page-size on a WAL database.
*/
rc = execSql(db, pzErrMsg, "BEGIN");
if( rc!=SQLITE_OK ) goto end_of_vacuum;
- rc = sqlite3BtreeBeginTrans(pMain, 2);
+ rc = tdsqlite3BtreeBeginTrans(pMain, pOut==0 ? 2 : 0, 0);
if( rc!=SQLITE_OK ) goto end_of_vacuum;
/* Do not attempt to change the page size for a WAL database */
- if( sqlite3PagerGetJournalMode(sqlite3BtreePager(pMain))
+ if( tdsqlite3PagerGetJournalMode(tdsqlite3BtreePager(pMain))
==PAGER_JOURNALMODE_WAL ){
db->nextPagesize = 0;
}
- if( sqlite3BtreeSetPageSize(pTemp, sqlite3BtreeGetPageSize(pMain), nRes, 0)
- || (!isMemDb && sqlite3BtreeSetPageSize(pTemp, db->nextPagesize, nRes, 0))
+ if( tdsqlite3BtreeSetPageSize(pTemp, tdsqlite3BtreeGetPageSize(pMain), nRes, 0)
+ || (!isMemDb && tdsqlite3BtreeSetPageSize(pTemp, db->nextPagesize, nRes, 0))
|| NEVER(db->mallocFailed)
){
rc = SQLITE_NOMEM_BKPT;
@@ -125532,8 +141305,8 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){
}
#ifndef SQLITE_OMIT_AUTOVACUUM
- sqlite3BtreeSetAutoVacuum(pTemp, db->nextAutovac>=0 ? db->nextAutovac :
- sqlite3BtreeGetAutoVacuum(pMain));
+ tdsqlite3BtreeSetAutoVacuum(pTemp, db->nextAutovac>=0 ? db->nextAutovac :
+ tdsqlite3BtreeGetAutoVacuum(pMain));
#endif
/* Query the schema of the main database. Create a mirror schema
@@ -125549,7 +141322,7 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){
if( rc!=SQLITE_OK ) goto end_of_vacuum;
rc = execSqlF(db, pzErrMsg,
"SELECT sql FROM \"%w\".sqlite_master"
- " WHERE type='index' AND length(sql)>10",
+ " WHERE type='index'",
zDbMain
);
if( rc!=SQLITE_OK ) goto end_of_vacuum;
@@ -125566,8 +141339,8 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){
"WHERE type='table'AND coalesce(rootpage,1)>0",
zDbMain
);
- assert( (db->flags & SQLITE_Vacuum)!=0 );
- db->flags &= ~SQLITE_Vacuum;
+ assert( (db->mDbFlags & DBFLAG_Vacuum)!=0 );
+ db->mDbFlags &= ~DBFLAG_Vacuum;
if( rc!=SQLITE_OK ) goto end_of_vacuum;
/* Copy the triggers, views, and virtual tables from the main database
@@ -125587,8 +141360,8 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){
/* At this point, there is a write transaction open on both the
** vacuum database and the main database. Assuming no error occurs,
** both transactions are closed by this block - the main database
- ** transaction by sqlite3BtreeCopyFile() and the other by an explicit
- ** call to sqlite3BtreeCommit().
+ ** transaction by tdsqlite3BtreeCopyFile() and the other by an explicit
+ ** call to tdsqlite3BtreeCommit().
*/
{
u32 meta;
@@ -125608,38 +141381,45 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){
BTREE_APPLICATION_ID, 0, /* Preserve the application id */
};
- assert( 1==sqlite3BtreeIsInTrans(pTemp) );
- assert( 1==sqlite3BtreeIsInTrans(pMain) );
+ assert( 1==tdsqlite3BtreeIsInTrans(pTemp) );
+ assert( pOut!=0 || 1==tdsqlite3BtreeIsInTrans(pMain) );
/* Copy Btree meta values */
for(i=0; i<ArraySize(aCopy); i+=2){
/* GetMeta() and UpdateMeta() cannot fail in this context because
** we already have page 1 loaded into cache and marked dirty. */
- sqlite3BtreeGetMeta(pMain, aCopy[i], &meta);
- rc = sqlite3BtreeUpdateMeta(pTemp, aCopy[i], meta+aCopy[i+1]);
+ tdsqlite3BtreeGetMeta(pMain, aCopy[i], &meta);
+ rc = tdsqlite3BtreeUpdateMeta(pTemp, aCopy[i], meta+aCopy[i+1]);
if( NEVER(rc!=SQLITE_OK) ) goto end_of_vacuum;
}
- rc = sqlite3BtreeCopyFile(pMain, pTemp);
+ if( pOut==0 ){
+ rc = tdsqlite3BtreeCopyFile(pMain, pTemp);
+ }
if( rc!=SQLITE_OK ) goto end_of_vacuum;
- rc = sqlite3BtreeCommit(pTemp);
+ rc = tdsqlite3BtreeCommit(pTemp);
if( rc!=SQLITE_OK ) goto end_of_vacuum;
#ifndef SQLITE_OMIT_AUTOVACUUM
- sqlite3BtreeSetAutoVacuum(pMain, sqlite3BtreeGetAutoVacuum(pTemp));
+ if( pOut==0 ){
+ tdsqlite3BtreeSetAutoVacuum(pMain, tdsqlite3BtreeGetAutoVacuum(pTemp));
+ }
#endif
}
assert( rc==SQLITE_OK );
- rc = sqlite3BtreeSetPageSize(pMain, sqlite3BtreeGetPageSize(pTemp), nRes,1);
+ if( pOut==0 ){
+ rc = tdsqlite3BtreeSetPageSize(pMain, tdsqlite3BtreeGetPageSize(pTemp), nRes,1);
+ }
end_of_vacuum:
/* Restore the original value of db->flags */
db->init.iDb = 0;
+ db->mDbFlags = saved_mDbFlags;
db->flags = saved_flags;
db->nChange = saved_nChange;
db->nTotalChange = saved_nTotalChange;
db->mTrace = saved_mTrace;
- sqlite3BtreeSetPageSize(pMain, -1, -1, 1);
+ tdsqlite3BtreeSetPageSize(pMain, -1, -1, 1);
/* Currently there is an SQL level transaction open on the vacuum
** database. No locks are held on any other files (since the main file
@@ -125651,14 +141431,14 @@ end_of_vacuum:
db->autoCommit = 1;
if( pDb ){
- sqlite3BtreeClose(pDb->pBt);
+ tdsqlite3BtreeClose(pDb->pBt);
pDb->pBt = 0;
pDb->pSchema = 0;
}
/* This both clears the schemas and reduces the size of the db->aDb[]
** array. */
- sqlite3ResetAllSchemasOfConnection(db);
+ tdsqlite3ResetAllSchemasOfConnection(db);
return rc;
}
@@ -125687,59 +141467,86 @@ end_of_vacuum:
** Before a virtual table xCreate() or xConnect() method is invoked, the
** sqlite3.pVtabCtx member variable is set to point to an instance of
** this struct allocated on the stack. It is used by the implementation of
-** the sqlite3_declare_vtab() and sqlite3_vtab_config() APIs, both of which
+** the tdsqlite3_declare_vtab() and tdsqlite3_vtab_config() APIs, both of which
** are invoked only from within xCreate and xConnect methods.
*/
struct VtabCtx {
VTable *pVTable; /* The virtual table being constructed */
Table *pTab; /* The Table object to which the virtual table belongs */
VtabCtx *pPrior; /* Parent context (if any) */
- int bDeclared; /* True after sqlite3_declare_vtab() is called */
+ int bDeclared; /* True after tdsqlite3_declare_vtab() is called */
};
/*
+** Construct and install a Module object for a virtual table. When this
+** routine is called, it is guaranteed that all appropriate locks are held
+** and the module is not already part of the connection.
+**
+** If there already exists a module with zName, replace it with the new one.
+** If pModule==0, then delete the module zName if it exists.
+*/
+SQLITE_PRIVATE Module *tdsqlite3VtabCreateModule(
+ tdsqlite3 *db, /* Database in which module is registered */
+ const char *zName, /* Name assigned to this module */
+ const tdsqlite3_module *pModule, /* The definition of the module */
+ void *pAux, /* Context pointer for xCreate/xConnect */
+ void (*xDestroy)(void *) /* Module destructor function */
+){
+ Module *pMod;
+ Module *pDel;
+ char *zCopy;
+ if( pModule==0 ){
+ zCopy = (char*)zName;
+ pMod = 0;
+ }else{
+ int nName = tdsqlite3Strlen30(zName);
+ pMod = (Module *)tdsqlite3Malloc(sizeof(Module) + nName + 1);
+ if( pMod==0 ){
+ tdsqlite3OomFault(db);
+ return 0;
+ }
+ zCopy = (char *)(&pMod[1]);
+ memcpy(zCopy, zName, nName+1);
+ pMod->zName = zCopy;
+ pMod->pModule = pModule;
+ pMod->pAux = pAux;
+ pMod->xDestroy = xDestroy;
+ pMod->pEpoTab = 0;
+ pMod->nRefModule = 1;
+ }
+ pDel = (Module *)tdsqlite3HashInsert(&db->aModule,zCopy,(void*)pMod);
+ if( pDel ){
+ if( pDel==pMod ){
+ tdsqlite3OomFault(db);
+ tdsqlite3DbFree(db, pDel);
+ pMod = 0;
+ }else{
+ tdsqlite3VtabEponymousTableClear(db, pDel);
+ tdsqlite3VtabModuleUnref(db, pDel);
+ }
+ }
+ return pMod;
+}
+
+/*
** The actual function that does the work of creating a new module.
-** This function implements the sqlite3_create_module() and
-** sqlite3_create_module_v2() interfaces.
+** This function implements the tdsqlite3_create_module() and
+** tdsqlite3_create_module_v2() interfaces.
*/
static int createModule(
- sqlite3 *db, /* Database in which module is registered */
+ tdsqlite3 *db, /* Database in which module is registered */
const char *zName, /* Name assigned to this module */
- const sqlite3_module *pModule, /* The definition of the module */
+ const tdsqlite3_module *pModule, /* The definition of the module */
void *pAux, /* Context pointer for xCreate/xConnect */
void (*xDestroy)(void *) /* Module destructor function */
){
int rc = SQLITE_OK;
- int nName;
- sqlite3_mutex_enter(db->mutex);
- nName = sqlite3Strlen30(zName);
- if( sqlite3HashFind(&db->aModule, zName) ){
- rc = SQLITE_MISUSE_BKPT;
- }else{
- Module *pMod;
- pMod = (Module *)sqlite3DbMallocRawNN(db, sizeof(Module) + nName + 1);
- if( pMod ){
- Module *pDel;
- char *zCopy = (char *)(&pMod[1]);
- memcpy(zCopy, zName, nName+1);
- pMod->zName = zCopy;
- pMod->pModule = pModule;
- pMod->pAux = pAux;
- pMod->xDestroy = xDestroy;
- pMod->pEpoTab = 0;
- pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod);
- assert( pDel==0 || pDel==pMod );
- if( pDel ){
- sqlite3OomFault(db);
- sqlite3DbFree(db, pDel);
- }
- }
- }
- rc = sqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_enter(db->mutex);
+ (void)tdsqlite3VtabCreateModule(db, zName, pModule, pAux, xDestroy);
+ rc = tdsqlite3ApiExit(db, rc);
if( rc!=SQLITE_OK && xDestroy ) xDestroy(pAux);
-
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -125747,14 +141554,14 @@ static int createModule(
/*
** External API function used to create a new virtual-table module.
*/
-SQLITE_API int sqlite3_create_module(
- sqlite3 *db, /* Database in which module is registered */
+SQLITE_API int tdsqlite3_create_module(
+ tdsqlite3 *db, /* Database in which module is registered */
const char *zName, /* Name assigned to this module */
- const sqlite3_module *pModule, /* The definition of the module */
+ const tdsqlite3_module *pModule, /* The definition of the module */
void *pAux /* Context pointer for xCreate/xConnect */
){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
#endif
return createModule(db, zName, pModule, pAux, 0);
}
@@ -125762,20 +141569,58 @@ SQLITE_API int sqlite3_create_module(
/*
** External API function used to create a new virtual-table module.
*/
-SQLITE_API int sqlite3_create_module_v2(
- sqlite3 *db, /* Database in which module is registered */
+SQLITE_API int tdsqlite3_create_module_v2(
+ tdsqlite3 *db, /* Database in which module is registered */
const char *zName, /* Name assigned to this module */
- const sqlite3_module *pModule, /* The definition of the module */
+ const tdsqlite3_module *pModule, /* The definition of the module */
void *pAux, /* Context pointer for xCreate/xConnect */
void (*xDestroy)(void *) /* Module destructor function */
){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
#endif
return createModule(db, zName, pModule, pAux, xDestroy);
}
/*
+** External API to drop all virtual-table modules, except those named
+** on the azNames list.
+*/
+SQLITE_API int tdsqlite3_drop_modules(tdsqlite3 *db, const char** azNames){
+ HashElem *pThis, *pNext;
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+#endif
+ for(pThis=sqliteHashFirst(&db->aModule); pThis; pThis=pNext){
+ Module *pMod = (Module*)sqliteHashData(pThis);
+ pNext = sqliteHashNext(pThis);
+ if( azNames ){
+ int ii;
+ for(ii=0; azNames[ii]!=0 && strcmp(azNames[ii],pMod->zName)!=0; ii++){}
+ if( azNames[ii]!=0 ) continue;
+ }
+ createModule(db, pMod->zName, 0, 0, 0);
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Decrement the reference count on a Module object. Destroy the
+** module when the reference count reaches zero.
+*/
+SQLITE_PRIVATE void tdsqlite3VtabModuleUnref(tdsqlite3 *db, Module *pMod){
+ assert( pMod->nRefModule>0 );
+ pMod->nRefModule--;
+ if( pMod->nRefModule==0 ){
+ if( pMod->xDestroy ){
+ pMod->xDestroy(pMod->pAux);
+ }
+ assert( pMod->pEpoTab==0 );
+ tdsqlite3DbFree(db, pMod);
+ }
+}
+
+/*
** Lock the virtual table so that it cannot be disconnected.
** Locks nest. Every lock should have a corresponding unlock.
** If an unlock is omitted, resources leaks will occur.
@@ -125783,7 +141628,7 @@ SQLITE_API int sqlite3_create_module_v2(
** If a disconnect is attempted while a virtual table is locked,
** the disconnect is deferred until all locks have been removed.
*/
-SQLITE_PRIVATE void sqlite3VtabLock(VTable *pVTab){
+SQLITE_PRIVATE void tdsqlite3VtabLock(VTable *pVTab){
pVTab->nRef++;
}
@@ -125793,7 +141638,7 @@ SQLITE_PRIVATE void sqlite3VtabLock(VTable *pVTab){
** Return a pointer to the VTable object used by connection db to access
** this virtual-table, if one has been created, or NULL otherwise.
*/
-SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){
+SQLITE_PRIVATE VTable *tdsqlite3GetVTable(tdsqlite3 *db, Table *pTab){
VTable *pVtab;
assert( IsVirtual(pTab) );
for(pVtab=pTab->pVTable; pVtab && pVtab->db!=db; pVtab=pVtab->pNext);
@@ -125804,8 +141649,8 @@ SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){
** Decrement the ref-count on a virtual table object. When the ref-count
** reaches zero, call the xDisconnect() method to delete the object.
*/
-SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *pVTab){
- sqlite3 *db = pVTab->db;
+SQLITE_PRIVATE void tdsqlite3VtabUnlock(VTable *pVTab){
+ tdsqlite3 *db = pVTab->db;
assert( db );
assert( pVTab->nRef>0 );
@@ -125813,11 +141658,12 @@ SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *pVTab){
pVTab->nRef--;
if( pVTab->nRef==0 ){
- sqlite3_vtab *p = pVTab->pVtab;
+ tdsqlite3_vtab *p = pVTab->pVtab;
+ tdsqlite3VtabModuleUnref(pVTab->db, pVTab->pMod);
if( p ){
p->pModule->xDisconnect(p);
}
- sqlite3DbFree(db, pVTab);
+ tdsqlite3DbFree(db, pVTab);
}
}
@@ -125828,21 +141674,21 @@ SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *pVTab){
** Except, if argument db is not NULL, then the entry associated with
** connection db is left in the p->pVTable list.
*/
-static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){
+static VTable *vtabDisconnectAll(tdsqlite3 *db, Table *p){
VTable *pRet = 0;
VTable *pVTable = p->pVTable;
p->pVTable = 0;
/* Assert that the mutex (if any) associated with the BtShared database
** that contains table p is held by the caller. See header comments
- ** above function sqlite3VtabUnlockList() for an explanation of why
+ ** above function tdsqlite3VtabUnlockList() for an explanation of why
** this makes it safe to access the sqlite3.pDisconnect list of any
** database connection that may have an entry in the p->pVTable list.
*/
- assert( db==0 || sqlite3SchemaMutexHeld(db, 0, p->pSchema) );
+ assert( db==0 || tdsqlite3SchemaMutexHeld(db, 0, p->pSchema) );
while( pVTable ){
- sqlite3 *db2 = pVTable->db;
+ tdsqlite3 *db2 = pVTable->db;
VTable *pNext = pVTable->pNext;
assert( db2 );
if( db2==db ){
@@ -125868,18 +141714,18 @@ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){
** objects without disturbing the rest of the Schema object (which may
** be being used by other shared-cache connections).
*/
-SQLITE_PRIVATE void sqlite3VtabDisconnect(sqlite3 *db, Table *p){
+SQLITE_PRIVATE void tdsqlite3VtabDisconnect(tdsqlite3 *db, Table *p){
VTable **ppVTab;
assert( IsVirtual(p) );
- assert( sqlite3BtreeHoldsAllMutexes(db) );
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3BtreeHoldsAllMutexes(db) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
for(ppVTab=&p->pVTable; *ppVTab; ppVTab=&(*ppVTab)->pNext){
if( (*ppVTab)->db==db ){
VTable *pVTab = *ppVTab;
*ppVTab = pVTab->pNext;
- sqlite3VtabUnlock(pVTab);
+ tdsqlite3VtabUnlock(pVTab);
break;
}
}
@@ -125906,18 +141752,18 @@ SQLITE_PRIVATE void sqlite3VtabDisconnect(sqlite3 *db, Table *p){
** As a result, a sqlite3.pDisconnect cannot be accessed simultaneously
** by multiple threads. It is thread-safe.
*/
-SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3VtabUnlockList(tdsqlite3 *db){
VTable *p = db->pDisconnect;
- db->pDisconnect = 0;
- assert( sqlite3BtreeHoldsAllMutexes(db) );
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3BtreeHoldsAllMutexes(db) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
if( p ){
- sqlite3ExpirePreparedStatements(db);
+ db->pDisconnect = 0;
+ tdsqlite3ExpirePreparedStatements(db, 0);
do {
VTable *pNext = p->pNext;
- sqlite3VtabUnlock(p);
+ tdsqlite3VtabUnlock(p);
p = pNext;
}while( p );
}
@@ -125930,21 +141776,21 @@ SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3 *db){
**
** Since it is a virtual-table, the Table structure contains a pointer
** to the head of a linked list of VTable structures. Each VTable
-** structure is associated with a single sqlite3* user of the schema.
+** structure is associated with a single tdsqlite3* user of the schema.
** The reference count of the VTable structure associated with database
** connection db is decremented immediately (which may lead to the
** structure being xDisconnected and free). Any other VTable structures
** in the list are moved to the sqlite3.pDisconnect list of the associated
** database connection.
*/
-SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table *p){
+SQLITE_PRIVATE void tdsqlite3VtabClear(tdsqlite3 *db, Table *p){
if( !db || db->pnBytesFreed==0 ) vtabDisconnectAll(0, p);
if( p->azModuleArg ){
int i;
for(i=0; i<p->nModuleArg; i++){
- if( i!=1 ) sqlite3DbFree(db, p->azModuleArg[i]);
+ if( i!=1 ) tdsqlite3DbFree(db, p->azModuleArg[i]);
}
- sqlite3DbFree(db, p->azModuleArg);
+ tdsqlite3DbFree(db, p->azModuleArg);
}
}
@@ -125954,12 +141800,16 @@ SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table *p){
** string will be freed automatically when the table is
** deleted.
*/
-static void addModuleArgument(sqlite3 *db, Table *pTable, char *zArg){
- int nBytes = sizeof(char *)*(2+pTable->nModuleArg);
+static void addModuleArgument(Parse *pParse, Table *pTable, char *zArg){
+ tdsqlite3_int64 nBytes = sizeof(char *)*(2+pTable->nModuleArg);
char **azModuleArg;
- azModuleArg = sqlite3DbRealloc(db, pTable->azModuleArg, nBytes);
+ tdsqlite3 *db = pParse->db;
+ if( pTable->nModuleArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){
+ tdsqlite3ErrorMsg(pParse, "too many columns on %s", pTable->zName);
+ }
+ azModuleArg = tdsqlite3DbRealloc(db, pTable->azModuleArg, nBytes);
if( azModuleArg==0 ){
- sqlite3DbFree(db, zArg);
+ tdsqlite3DbFree(db, zArg);
}else{
int i = pTable->nModuleArg++;
azModuleArg[i] = zArg;
@@ -125973,31 +141823,27 @@ static void addModuleArgument(sqlite3 *db, Table *pTable, char *zArg){
** statement. The module name has been parsed, but the optional list
** of parameters that follow the module name are still pending.
*/
-SQLITE_PRIVATE void sqlite3VtabBeginParse(
+SQLITE_PRIVATE void tdsqlite3VtabBeginParse(
Parse *pParse, /* Parsing context */
Token *pName1, /* Name of new table, or database name */
Token *pName2, /* Name of new table or NULL */
Token *pModuleName, /* Name of the module for the virtual table */
int ifNotExists /* No error if the table already exists */
){
- int iDb; /* The database the table is being created in */
Table *pTable; /* The new virtual table */
- sqlite3 *db; /* Database connection */
+ tdsqlite3 *db; /* Database connection */
- sqlite3StartTable(pParse, pName1, pName2, 0, 0, 1, ifNotExists);
+ tdsqlite3StartTable(pParse, pName1, pName2, 0, 0, 1, ifNotExists);
pTable = pParse->pNewTable;
if( pTable==0 ) return;
assert( 0==pTable->pIndex );
db = pParse->db;
- iDb = sqlite3SchemaToIndex(db, pTable->pSchema);
- assert( iDb>=0 );
- pTable->tabFlags |= TF_Virtual;
- pTable->nModuleArg = 0;
- addModuleArgument(db, pTable, sqlite3NameFromToken(db, pModuleName));
- addModuleArgument(db, pTable, 0);
- addModuleArgument(db, pTable, sqlite3DbStrDup(db, pTable->zName));
+ assert( pTable->nModuleArg==0 );
+ addModuleArgument(pParse, pTable, tdsqlite3NameFromToken(db, pModuleName));
+ addModuleArgument(pParse, pTable, 0);
+ addModuleArgument(pParse, pTable, tdsqlite3DbStrDup(db, pTable->zName));
assert( (pParse->sNameToken.z==pName2->z && pName2->z!=0)
|| (pParse->sNameToken.z==pName1->z && pName2->z==0)
);
@@ -126008,11 +141854,13 @@ SQLITE_PRIVATE void sqlite3VtabBeginParse(
#ifndef SQLITE_OMIT_AUTHORIZATION
/* Creating a virtual table invokes the authorization callback twice.
** The first invocation, to obtain permission to INSERT a row into the
- ** sqlite_master table, has already been made by sqlite3StartTable().
+ ** sqlite_master table, has already been made by tdsqlite3StartTable().
** The second call, to obtain permission to create the table, is made now.
*/
if( pTable->azModuleArg ){
- sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName,
+ int iDb = tdsqlite3SchemaToIndex(db, pTable->pSchema);
+ assert( iDb>=0 ); /* The database the table is being created in */
+ tdsqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName,
pTable->azModuleArg[0], pParse->db->aDb[iDb].zDbSName);
}
#endif
@@ -126027,8 +141875,8 @@ static void addArgumentToVtab(Parse *pParse){
if( pParse->sArg.z && pParse->pNewTable ){
const char *z = (const char*)pParse->sArg.z;
int n = pParse->sArg.n;
- sqlite3 *db = pParse->db;
- addModuleArgument(db, pParse->pNewTable, sqlite3DbStrNDup(db, z, n));
+ tdsqlite3 *db = pParse->db;
+ addModuleArgument(pParse, pParse->pNewTable, tdsqlite3DbStrNDup(db, z, n));
}
}
@@ -126036,9 +141884,9 @@ static void addArgumentToVtab(Parse *pParse){
** The parser calls this routine after the CREATE VIRTUAL TABLE statement
** has been completely parsed.
*/
-SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
+SQLITE_PRIVATE void tdsqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
Table *pTab = pParse->pNewTable; /* The table being constructed */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
if( pTab==0 ) return;
addArgumentToVtab(pParse);
@@ -126058,11 +141906,13 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
int iReg;
Vdbe *v;
+ tdsqlite3MayAbort(pParse);
+
/* Compute the complete text of the CREATE VIRTUAL TABLE statement */
if( pEnd ){
pParse->sNameToken.n = (int)(pEnd->z - pParse->sNameToken.z) + pEnd->n;
}
- zStmt = sqlite3MPrintf(db, "CREATE VIRTUAL TABLE %T", &pParse->sNameToken);
+ zStmt = tdsqlite3MPrintf(db, "CREATE VIRTUAL TABLE %T", &pParse->sNameToken);
/* A slot for the record has already been allocated in the
** SQLITE_MASTER table. We just need to update that slot with all
@@ -126070,30 +141920,30 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
**
** The VM register number pParse->regRowid holds the rowid of an
** entry in the sqlite_master table tht was created for this vtab
- ** by sqlite3StartTable().
+ ** by tdsqlite3StartTable().
*/
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
- sqlite3NestedParse(pParse,
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
+ tdsqlite3NestedParse(pParse,
"UPDATE %Q.%s "
"SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q "
"WHERE rowid=#%d",
- db->aDb[iDb].zDbSName, SCHEMA_TABLE(iDb),
+ db->aDb[iDb].zDbSName, MASTER_NAME,
pTab->zName,
pTab->zName,
zStmt,
pParse->regRowid
);
- sqlite3DbFree(db, zStmt);
- v = sqlite3GetVdbe(pParse);
- sqlite3ChangeCookie(pParse, iDb);
+ v = tdsqlite3GetVdbe(pParse);
+ tdsqlite3ChangeCookie(pParse, iDb);
- sqlite3VdbeAddOp0(v, OP_Expire);
- zWhere = sqlite3MPrintf(db, "name='%q' AND type='table'", pTab->zName);
- sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere);
+ tdsqlite3VdbeAddOp0(v, OP_Expire);
+ zWhere = tdsqlite3MPrintf(db, "name=%Q AND sql=%Q", pTab->zName, zStmt);
+ tdsqlite3VdbeAddParseSchemaOp(v, iDb, zWhere);
+ tdsqlite3DbFree(db, zStmt);
iReg = ++pParse->nMem;
- sqlite3VdbeLoadString(v, iReg, pTab->zName);
- sqlite3VdbeAddOp2(v, OP_VCreate, iDb, iReg);
+ tdsqlite3VdbeLoadString(v, iReg, pTab->zName);
+ tdsqlite3VdbeAddOp2(v, OP_VCreate, iDb, iReg);
}
/* If we are rereading the sqlite_master table create the in-memory
@@ -126105,10 +141955,10 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
Table *pOld;
Schema *pSchema = pTab->pSchema;
const char *zName = pTab->zName;
- assert( sqlite3SchemaMutexHeld(db, 0, pSchema) );
- pOld = sqlite3HashInsert(&pSchema->tblHash, zName, pTab);
+ assert( tdsqlite3SchemaMutexHeld(db, 0, pSchema) );
+ pOld = tdsqlite3HashInsert(&pSchema->tblHash, zName, pTab);
if( pOld ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
assert( pTab==pOld ); /* Malloc must have failed inside HashInsert() */
return;
}
@@ -126120,7 +141970,7 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){
** The parser calls this routine when it sees the first token
** of an argument to the module name in a CREATE VIRTUAL TABLE statement.
*/
-SQLITE_PRIVATE void sqlite3VtabArgInit(Parse *pParse){
+SQLITE_PRIVATE void tdsqlite3VtabArgInit(Parse *pParse){
addArgumentToVtab(pParse);
pParse->sArg.z = 0;
pParse->sArg.n = 0;
@@ -126130,7 +141980,7 @@ SQLITE_PRIVATE void sqlite3VtabArgInit(Parse *pParse){
** The parser calls this routine for each token after the first token
** in an argument to the module name in a CREATE VIRTUAL TABLE statement.
*/
-SQLITE_PRIVATE void sqlite3VtabArgExtend(Parse *pParse, Token *p){
+SQLITE_PRIVATE void tdsqlite3VtabArgExtend(Parse *pParse, Token *p){
Token *pArg = &pParse->sArg;
if( pArg->z==0 ){
pArg->z = p->z;
@@ -126147,10 +141997,10 @@ SQLITE_PRIVATE void sqlite3VtabArgExtend(Parse *pParse, Token *p){
** to this procedure.
*/
static int vtabCallConstructor(
- sqlite3 *db,
+ tdsqlite3 *db,
Table *pTab,
Module *pMod,
- int (*xConstruct)(sqlite3*,void*,int,const char*const*,sqlite3_vtab**,char**),
+ int (*xConstruct)(tdsqlite3*,void*,int,const char*const*,tdsqlite3_vtab**,char**),
char **pzErr
){
VtabCtx sCtx;
@@ -126166,27 +142016,29 @@ static int vtabCallConstructor(
/* Check that the virtual-table is not already being initialized */
for(pCtx=db->pVtabCtx; pCtx; pCtx=pCtx->pPrior){
if( pCtx->pTab==pTab ){
- *pzErr = sqlite3MPrintf(db,
+ *pzErr = tdsqlite3MPrintf(db,
"vtable constructor called recursively: %s", pTab->zName
);
return SQLITE_LOCKED;
}
}
- zModuleName = sqlite3MPrintf(db, "%s", pTab->zName);
+ zModuleName = tdsqlite3DbStrDup(db, pTab->zName);
if( !zModuleName ){
return SQLITE_NOMEM_BKPT;
}
- pVTable = sqlite3DbMallocZero(db, sizeof(VTable));
+ pVTable = tdsqlite3MallocZero(sizeof(VTable));
if( !pVTable ){
- sqlite3DbFree(db, zModuleName);
+ tdsqlite3OomFault(db);
+ tdsqlite3DbFree(db, zModuleName);
return SQLITE_NOMEM_BKPT;
}
pVTable->db = db;
pVTable->pMod = pMod;
+ pVTable->eVtabRisk = SQLITE_VTABRISK_Normal;
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
pTab->azModuleArg[1] = db->aDb[iDb].zDbSName;
/* Invoke the virtual table constructor */
@@ -126199,31 +142051,32 @@ static int vtabCallConstructor(
db->pVtabCtx = &sCtx;
rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr);
db->pVtabCtx = sCtx.pPrior;
- if( rc==SQLITE_NOMEM ) sqlite3OomFault(db);
+ if( rc==SQLITE_NOMEM ) tdsqlite3OomFault(db);
assert( sCtx.pTab==pTab );
if( SQLITE_OK!=rc ){
if( zErr==0 ){
- *pzErr = sqlite3MPrintf(db, "vtable constructor failed: %s", zModuleName);
+ *pzErr = tdsqlite3MPrintf(db, "vtable constructor failed: %s", zModuleName);
}else {
- *pzErr = sqlite3MPrintf(db, "%s", zErr);
- sqlite3_free(zErr);
+ *pzErr = tdsqlite3MPrintf(db, "%s", zErr);
+ tdsqlite3_free(zErr);
}
- sqlite3DbFree(db, pVTable);
+ tdsqlite3DbFree(db, pVTable);
}else if( ALWAYS(pVTable->pVtab) ){
/* Justification of ALWAYS(): A correct vtab constructor must allocate
- ** the sqlite3_vtab object if successful. */
+ ** the tdsqlite3_vtab object if successful. */
memset(pVTable->pVtab, 0, sizeof(pVTable->pVtab[0]));
pVTable->pVtab->pModule = pMod->pModule;
+ pMod->nRefModule++;
pVTable->nRef = 1;
if( sCtx.bDeclared==0 ){
const char *zFormat = "vtable constructor did not declare schema: %s";
- *pzErr = sqlite3MPrintf(db, zFormat, pTab->zName);
- sqlite3VtabUnlock(pVTable);
+ *pzErr = tdsqlite3MPrintf(db, zFormat, pTab->zName);
+ tdsqlite3VtabUnlock(pVTable);
rc = SQLITE_ERROR;
}else{
int iCol;
- u8 oooHidden = 0;
+ u16 oooHidden = 0;
/* If everything went according to plan, link the new VTable structure
** into the linked list headed by pTab->pVTable. Then loop through the
** columns of the table to see if any of them contain the token "hidden".
@@ -126233,12 +142086,12 @@ static int vtabCallConstructor(
pTab->pVTable = pVTable;
for(iCol=0; iCol<pTab->nCol; iCol++){
- char *zType = sqlite3ColumnType(&pTab->aCol[iCol], "");
+ char *zType = tdsqlite3ColumnType(&pTab->aCol[iCol], "");
int nType;
int i = 0;
- nType = sqlite3Strlen30(zType);
+ nType = tdsqlite3Strlen30(zType);
for(i=0; i<nType; i++){
- if( 0==sqlite3StrNICmp("hidden", &zType[i], 6)
+ if( 0==tdsqlite3StrNICmp("hidden", &zType[i], 6)
&& (i==0 || zType[i-1]==' ')
&& (zType[i+6]=='\0' || zType[i+6]==' ')
){
@@ -126264,7 +142117,7 @@ static int vtabCallConstructor(
}
}
- sqlite3DbFree(db, zModuleName);
+ tdsqlite3DbFree(db, zModuleName);
return rc;
}
@@ -126275,32 +142128,33 @@ static int vtabCallConstructor(
**
** This call is a no-op if table pTab is not a virtual table.
*/
-SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){
- sqlite3 *db = pParse->db;
+SQLITE_PRIVATE int tdsqlite3VtabCallConnect(Parse *pParse, Table *pTab){
+ tdsqlite3 *db = pParse->db;
const char *zMod;
Module *pMod;
int rc;
assert( pTab );
- if( (pTab->tabFlags & TF_Virtual)==0 || sqlite3GetVTable(db, pTab) ){
+ if( !IsVirtual(pTab) || tdsqlite3GetVTable(db, pTab) ){
return SQLITE_OK;
}
/* Locate the required virtual table module */
zMod = pTab->azModuleArg[0];
- pMod = (Module*)sqlite3HashFind(&db->aModule, zMod);
+ pMod = (Module*)tdsqlite3HashFind(&db->aModule, zMod);
if( !pMod ){
const char *zModule = pTab->azModuleArg[0];
- sqlite3ErrorMsg(pParse, "no such module: %s", zModule);
+ tdsqlite3ErrorMsg(pParse, "no such module: %s", zModule);
rc = SQLITE_ERROR;
}else{
char *zErr = 0;
rc = vtabCallConstructor(db, pTab, pMod, pMod->pModule->xConnect, &zErr);
if( rc!=SQLITE_OK ){
- sqlite3ErrorMsg(pParse, "%s", zErr);
+ tdsqlite3ErrorMsg(pParse, "%s", zErr);
+ pParse->rc = rc;
}
- sqlite3DbFree(db, zErr);
+ tdsqlite3DbFree(db, zErr);
}
return rc;
@@ -126309,18 +142163,19 @@ SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){
** Grow the db->aVTrans[] array so that there is room for at least one
** more v-table. Return SQLITE_NOMEM if a malloc fails, or SQLITE_OK otherwise.
*/
-static int growVTrans(sqlite3 *db){
+static int growVTrans(tdsqlite3 *db){
const int ARRAY_INCR = 5;
/* Grow the sqlite3.aVTrans array if required */
if( (db->nVTrans%ARRAY_INCR)==0 ){
VTable **aVTrans;
- int nBytes = sizeof(sqlite3_vtab *) * (db->nVTrans + ARRAY_INCR);
- aVTrans = sqlite3DbRealloc(db, (void *)db->aVTrans, nBytes);
+ tdsqlite3_int64 nBytes = sizeof(tdsqlite3_vtab*)*
+ ((tdsqlite3_int64)db->nVTrans + ARRAY_INCR);
+ aVTrans = tdsqlite3DbRealloc(db, (void *)db->aVTrans, nBytes);
if( !aVTrans ){
return SQLITE_NOMEM_BKPT;
}
- memset(&aVTrans[db->nVTrans], 0, sizeof(sqlite3_vtab *)*ARRAY_INCR);
+ memset(&aVTrans[db->nVTrans], 0, sizeof(tdsqlite3_vtab *)*ARRAY_INCR);
db->aVTrans = aVTrans;
}
@@ -126331,10 +142186,10 @@ static int growVTrans(sqlite3 *db){
** Add the virtual table pVTab to the array sqlite3.aVTrans[]. Space should
** have already been reserved using growVTrans().
*/
-static void addToVTrans(sqlite3 *db, VTable *pVTab){
+static void addToVTrans(tdsqlite3 *db, VTable *pVTab){
/* Add pVtab to the end of sqlite3.aVTrans */
db->aVTrans[db->nVTrans++] = pVTab;
- sqlite3VtabLock(pVTab);
+ tdsqlite3VtabLock(pVTab);
}
/*
@@ -126343,38 +142198,38 @@ static void addToVTrans(sqlite3 *db, VTable *pVTab){
**
** If an error occurs, *pzErr is set to point to an English language
** description of the error and an SQLITE_XXX error code is returned.
-** In this case the caller must call sqlite3DbFree(db, ) on *pzErr.
+** In this case the caller must call tdsqlite3DbFree(db, ) on *pzErr.
*/
-SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, char **pzErr){
+SQLITE_PRIVATE int tdsqlite3VtabCallCreate(tdsqlite3 *db, int iDb, const char *zTab, char **pzErr){
int rc = SQLITE_OK;
Table *pTab;
Module *pMod;
const char *zMod;
- pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName);
- assert( pTab && (pTab->tabFlags & TF_Virtual)!=0 && !pTab->pVTable );
+ pTab = tdsqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName);
+ assert( pTab && IsVirtual(pTab) && !pTab->pVTable );
/* Locate the required virtual table module */
zMod = pTab->azModuleArg[0];
- pMod = (Module*)sqlite3HashFind(&db->aModule, zMod);
+ pMod = (Module*)tdsqlite3HashFind(&db->aModule, zMod);
/* If the module has been registered and includes a Create method,
** invoke it now. If the module has not been registered, return an
** error. Otherwise, do nothing.
*/
if( pMod==0 || pMod->pModule->xCreate==0 || pMod->pModule->xDestroy==0 ){
- *pzErr = sqlite3MPrintf(db, "no such module: %s", zMod);
+ *pzErr = tdsqlite3MPrintf(db, "no such module: %s", zMod);
rc = SQLITE_ERROR;
}else{
rc = vtabCallConstructor(db, pTab, pMod, pMod->pModule->xCreate, pzErr);
}
/* Justification of ALWAYS(): The xConstructor method is required to
- ** create a valid sqlite3_vtab if it returns SQLITE_OK. */
- if( rc==SQLITE_OK && ALWAYS(sqlite3GetVTable(db, pTab)) ){
+ ** create a valid tdsqlite3_vtab if it returns SQLITE_OK. */
+ if( rc==SQLITE_OK && ALWAYS(tdsqlite3GetVTable(db, pTab)) ){
rc = growVTrans(db);
if( rc==SQLITE_OK ){
- addToVTrans(db, sqlite3GetVTable(db, pTab));
+ addToVTrans(db, tdsqlite3GetVTable(db, pTab));
}
}
@@ -126386,81 +142241,81 @@ SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab,
** valid to call this function from within the xCreate() or xConnect() of a
** virtual table module.
*/
-SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
+SQLITE_API int tdsqlite3_declare_vtab(tdsqlite3 *db, const char *zCreateTable){
VtabCtx *pCtx;
- Parse *pParse;
int rc = SQLITE_OK;
Table *pTab;
char *zErr = 0;
+ Parse sParse;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){
+ if( !tdsqlite3SafetyCheckOk(db) || zCreateTable==0 ){
return SQLITE_MISUSE_BKPT;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
pCtx = db->pVtabCtx;
if( !pCtx || pCtx->bDeclared ){
- sqlite3Error(db, SQLITE_MISUSE);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3Error(db, SQLITE_MISUSE);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_MISUSE_BKPT;
}
pTab = pCtx->pTab;
- assert( (pTab->tabFlags & TF_Virtual)!=0 );
+ assert( IsVirtual(pTab) );
- pParse = sqlite3StackAllocZero(db, sizeof(*pParse));
- if( pParse==0 ){
- rc = SQLITE_NOMEM_BKPT;
- }else{
- pParse->declareVtab = 1;
- pParse->db = db;
- pParse->nQueryLoop = 1;
-
- if( SQLITE_OK==sqlite3RunParser(pParse, zCreateTable, &zErr)
- && pParse->pNewTable
- && !db->mallocFailed
- && !pParse->pNewTable->pSelect
- && (pParse->pNewTable->tabFlags & TF_Virtual)==0
- ){
- if( !pTab->aCol ){
- Table *pNew = pParse->pNewTable;
- Index *pIdx;
- pTab->aCol = pNew->aCol;
- pTab->nCol = pNew->nCol;
- pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid);
- pNew->nCol = 0;
- pNew->aCol = 0;
- assert( pTab->pIndex==0 );
- if( !HasRowid(pNew) && pCtx->pVTable->pMod->pModule->xUpdate!=0 ){
- rc = SQLITE_ERROR;
- }
- pIdx = pNew->pIndex;
- if( pIdx ){
- assert( pIdx->pNext==0 );
- pTab->pIndex = pIdx;
- pNew->pIndex = 0;
- pIdx->pTable = pTab;
- }
+ memset(&sParse, 0, sizeof(sParse));
+ sParse.eParseMode = PARSE_MODE_DECLARE_VTAB;
+ sParse.db = db;
+ sParse.nQueryLoop = 1;
+ if( SQLITE_OK==tdsqlite3RunParser(&sParse, zCreateTable, &zErr)
+ && sParse.pNewTable
+ && !db->mallocFailed
+ && !sParse.pNewTable->pSelect
+ && !IsVirtual(sParse.pNewTable)
+ ){
+ if( !pTab->aCol ){
+ Table *pNew = sParse.pNewTable;
+ Index *pIdx;
+ pTab->aCol = pNew->aCol;
+ pTab->nCol = pNew->nCol;
+ pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid);
+ pNew->nCol = 0;
+ pNew->aCol = 0;
+ assert( pTab->pIndex==0 );
+ assert( HasRowid(pNew) || tdsqlite3PrimaryKeyIndex(pNew)!=0 );
+ if( !HasRowid(pNew)
+ && pCtx->pVTable->pMod->pModule->xUpdate!=0
+ && tdsqlite3PrimaryKeyIndex(pNew)->nKeyCol!=1
+ ){
+ /* WITHOUT ROWID virtual tables must either be read-only (xUpdate==0)
+ ** or else must have a single-column PRIMARY KEY */
+ rc = SQLITE_ERROR;
+ }
+ pIdx = pNew->pIndex;
+ if( pIdx ){
+ assert( pIdx->pNext==0 );
+ pTab->pIndex = pIdx;
+ pNew->pIndex = 0;
+ pIdx->pTable = pTab;
}
- pCtx->bDeclared = 1;
- }else{
- sqlite3ErrorWithMsg(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr);
- sqlite3DbFree(db, zErr);
- rc = SQLITE_ERROR;
- }
- pParse->declareVtab = 0;
-
- if( pParse->pVdbe ){
- sqlite3VdbeFinalize(pParse->pVdbe);
}
- sqlite3DeleteTable(db, pParse->pNewTable);
- sqlite3ParserReset(pParse);
- sqlite3StackFree(db, pParse);
+ pCtx->bDeclared = 1;
+ }else{
+ tdsqlite3ErrorWithMsg(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr);
+ tdsqlite3DbFree(db, zErr);
+ rc = SQLITE_ERROR;
+ }
+ sParse.eParseMode = PARSE_MODE_NORMAL;
+
+ if( sParse.pVdbe ){
+ tdsqlite3VdbeFinalize(sParse.pVdbe);
}
+ tdsqlite3DeleteTable(db, sParse.pNewTable);
+ tdsqlite3ParserReset(&sParse);
assert( (rc&0xff)==rc );
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -126471,14 +142326,14 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
**
** This call is a no-op if zTab is not a virtual table.
*/
-SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab){
+SQLITE_PRIVATE int tdsqlite3VtabCallDestroy(tdsqlite3 *db, int iDb, const char *zTab){
int rc = SQLITE_OK;
Table *pTab;
- pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName);
+ pTab = tdsqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName);
if( pTab!=0 && ALWAYS(pTab->pVTable!=0) ){
VTable *p;
- int (*xDestroy)(sqlite3_vtab *);
+ int (*xDestroy)(tdsqlite3_vtab *);
for(p=pTab->pVTable; p; p=p->pNext){
assert( p->pVtab );
if( p->pVtab->nRef>0 ){
@@ -126487,15 +142342,18 @@ SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab
}
p = vtabDisconnectAll(db, pTab);
xDestroy = p->pMod->pModule->xDestroy;
- assert( xDestroy!=0 ); /* Checked before the virtual table is created */
+ if( xDestroy==0 ) xDestroy = p->pMod->pModule->xDisconnect;
+ assert( xDestroy!=0 );
+ pTab->nTabRef++;
rc = xDestroy(p->pVtab);
- /* Remove the sqlite3_vtab* from the aVTrans[] array, if applicable */
+ /* Remove the tdsqlite3_vtab* from the aVTrans[] array, if applicable */
if( rc==SQLITE_OK ){
assert( pTab->pVTable==p && p->pNext==0 );
p->pVtab = 0;
pTab->pVTable = 0;
- sqlite3VtabUnlock(p);
+ tdsqlite3VtabUnlock(p);
}
+ tdsqlite3DeleteTable(db, pTab);
}
return rc;
@@ -126505,27 +142363,27 @@ SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab
** This function invokes either the xRollback or xCommit method
** of each of the virtual tables in the sqlite3.aVTrans array. The method
** called is identified by the second argument, "offset", which is
-** the offset of the method to call in the sqlite3_module structure.
+** the offset of the method to call in the tdsqlite3_module structure.
**
** The array is cleared after invoking the callbacks.
*/
-static void callFinaliser(sqlite3 *db, int offset){
+static void callFinaliser(tdsqlite3 *db, int offset){
int i;
if( db->aVTrans ){
VTable **aVTrans = db->aVTrans;
db->aVTrans = 0;
for(i=0; i<db->nVTrans; i++){
VTable *pVTab = aVTrans[i];
- sqlite3_vtab *p = pVTab->pVtab;
+ tdsqlite3_vtab *p = pVTab->pVtab;
if( p ){
- int (*x)(sqlite3_vtab *);
- x = *(int (**)(sqlite3_vtab *))((char *)p->pModule + offset);
+ int (*x)(tdsqlite3_vtab *);
+ x = *(int (**)(tdsqlite3_vtab *))((char *)p->pModule + offset);
if( x ) x(p);
}
pVTab->iSavepoint = 0;
- sqlite3VtabUnlock(pVTab);
+ tdsqlite3VtabUnlock(pVTab);
}
- sqlite3DbFree(db, aVTrans);
+ tdsqlite3DbFree(db, aVTrans);
db->nVTrans = 0;
}
}
@@ -126537,18 +142395,18 @@ static void callFinaliser(sqlite3 *db, int offset){
**
** If an error message is available, leave it in p->zErrMsg.
*/
-SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, Vdbe *p){
+SQLITE_PRIVATE int tdsqlite3VtabSync(tdsqlite3 *db, Vdbe *p){
int i;
int rc = SQLITE_OK;
VTable **aVTrans = db->aVTrans;
db->aVTrans = 0;
for(i=0; rc==SQLITE_OK && i<db->nVTrans; i++){
- int (*x)(sqlite3_vtab *);
- sqlite3_vtab *pVtab = aVTrans[i]->pVtab;
+ int (*x)(tdsqlite3_vtab *);
+ tdsqlite3_vtab *pVtab = aVTrans[i]->pVtab;
if( pVtab && (x = pVtab->pModule->xSync)!=0 ){
rc = x(pVtab);
- sqlite3VtabImportErrmsg(p, pVtab);
+ tdsqlite3VtabImportErrmsg(p, pVtab);
}
}
db->aVTrans = aVTrans;
@@ -126559,8 +142417,8 @@ SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, Vdbe *p){
** Invoke the xRollback method of all virtual tables in the
** sqlite3.aVTrans array. Then clear the array itself.
*/
-SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db){
- callFinaliser(db, offsetof(sqlite3_module,xRollback));
+SQLITE_PRIVATE int tdsqlite3VtabRollback(tdsqlite3 *db){
+ callFinaliser(db, offsetof(tdsqlite3_module,xRollback));
return SQLITE_OK;
}
@@ -126568,8 +142426,8 @@ SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db){
** Invoke the xCommit method of all virtual tables in the
** sqlite3.aVTrans array. Then clear the array itself.
*/
-SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db){
- callFinaliser(db, offsetof(sqlite3_module,xCommit));
+SQLITE_PRIVATE int tdsqlite3VtabCommit(tdsqlite3 *db){
+ callFinaliser(db, offsetof(tdsqlite3_module,xCommit));
return SQLITE_OK;
}
@@ -126578,19 +142436,19 @@ SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db){
** (xBegin/xRollback/xCommit and optionally xSync) and a transaction is
** not currently open, invoke the xBegin method now.
**
-** If the xBegin call is successful, place the sqlite3_vtab pointer
+** If the xBegin call is successful, place the tdsqlite3_vtab pointer
** in the sqlite3.aVTrans array.
*/
-SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){
+SQLITE_PRIVATE int tdsqlite3VtabBegin(tdsqlite3 *db, VTable *pVTab){
int rc = SQLITE_OK;
- const sqlite3_module *pModule;
+ const tdsqlite3_module *pModule;
/* Special case: If db->aVTrans is NULL and db->nVTrans is greater
** than zero, then this function is being called from within a
** virtual module xSync() callback. It is illegal to write to
** virtual module tables in this case, so return SQLITE_LOCKED.
*/
- if( sqlite3VtabInSync(db) ){
+ if( tdsqlite3VtabInSync(db) ){
return SQLITE_LOCKED;
}
if( !pVTab ){
@@ -126641,7 +142499,7 @@ SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){
** function immediately. If all calls to virtual table methods are successful,
** SQLITE_OK is returned.
*/
-SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){
+SQLITE_PRIVATE int tdsqlite3VtabSavepoint(tdsqlite3 *db, int op, int iSavepoint){
int rc = SQLITE_OK;
assert( op==SAVEPOINT_RELEASE||op==SAVEPOINT_ROLLBACK||op==SAVEPOINT_BEGIN );
@@ -126650,9 +142508,10 @@ SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){
int i;
for(i=0; rc==SQLITE_OK && i<db->nVTrans; i++){
VTable *pVTab = db->aVTrans[i];
- const sqlite3_module *pMod = pVTab->pMod->pModule;
+ const tdsqlite3_module *pMod = pVTab->pMod->pModule;
if( pVTab->pVtab && pMod->iVersion>=2 ){
- int (*xMethod)(sqlite3_vtab *, int);
+ int (*xMethod)(tdsqlite3_vtab *, int);
+ tdsqlite3VtabLock(pVTab);
switch( op ){
case SAVEPOINT_BEGIN:
xMethod = pMod->xSavepoint;
@@ -126668,6 +142527,7 @@ SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){
if( xMethod && pVTab->iSavepoint>iSavepoint ){
rc = xMethod(pVTab->pVtab, iSavepoint);
}
+ tdsqlite3VtabUnlock(pVTab);
}
}
}
@@ -126687,60 +142547,63 @@ SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){
** new FuncDef structure that is marked as ephemeral using the
** SQLITE_FUNC_EPHEM flag.
*/
-SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(
- sqlite3 *db, /* Database connection for reporting malloc problems */
+SQLITE_PRIVATE FuncDef *tdsqlite3VtabOverloadFunction(
+ tdsqlite3 *db, /* Database connection for reporting malloc problems */
FuncDef *pDef, /* Function to possibly overload */
int nArg, /* Number of arguments to the function */
Expr *pExpr /* First argument to the function */
){
Table *pTab;
- sqlite3_vtab *pVtab;
- sqlite3_module *pMod;
- void (*xSFunc)(sqlite3_context*,int,sqlite3_value**) = 0;
+ tdsqlite3_vtab *pVtab;
+ tdsqlite3_module *pMod;
+ void (*xSFunc)(tdsqlite3_context*,int,tdsqlite3_value**) = 0;
void *pArg = 0;
FuncDef *pNew;
int rc = 0;
- char *zLowerName;
- unsigned char *z;
-
/* Check to see the left operand is a column in a virtual table */
if( NEVER(pExpr==0) ) return pDef;
if( pExpr->op!=TK_COLUMN ) return pDef;
- pTab = pExpr->pTab;
- if( NEVER(pTab==0) ) return pDef;
- if( (pTab->tabFlags & TF_Virtual)==0 ) return pDef;
- pVtab = sqlite3GetVTable(db, pTab)->pVtab;
+ pTab = pExpr->y.pTab;
+ if( pTab==0 ) return pDef;
+ if( !IsVirtual(pTab) ) return pDef;
+ pVtab = tdsqlite3GetVTable(db, pTab)->pVtab;
assert( pVtab!=0 );
assert( pVtab->pModule!=0 );
- pMod = (sqlite3_module *)pVtab->pModule;
+ pMod = (tdsqlite3_module *)pVtab->pModule;
if( pMod->xFindFunction==0 ) return pDef;
/* Call the xFindFunction method on the virtual table implementation
- ** to see if the implementation wants to overload this function
+ ** to see if the implementation wants to overload this function.
+ **
+ ** Though undocumented, we have historically always invoked xFindFunction
+ ** with an all lower-case function name. Continue in this tradition to
+ ** avoid any chance of an incompatibility.
*/
- zLowerName = sqlite3DbStrDup(db, pDef->zName);
- if( zLowerName ){
- for(z=(unsigned char*)zLowerName; *z; z++){
- *z = sqlite3UpperToLower[*z];
+#ifdef SQLITE_DEBUG
+ {
+ int i;
+ for(i=0; pDef->zName[i]; i++){
+ unsigned char x = (unsigned char)pDef->zName[i];
+ assert( x==tdsqlite3UpperToLower[x] );
}
- rc = pMod->xFindFunction(pVtab, nArg, zLowerName, &xSFunc, &pArg);
- sqlite3DbFree(db, zLowerName);
}
+#endif
+ rc = pMod->xFindFunction(pVtab, nArg, pDef->zName, &xSFunc, &pArg);
if( rc==0 ){
return pDef;
}
/* Create a new ephemeral function definition for the overloaded
** function */
- pNew = sqlite3DbMallocZero(db, sizeof(*pNew)
- + sqlite3Strlen30(pDef->zName) + 1);
+ pNew = tdsqlite3DbMallocZero(db, sizeof(*pNew)
+ + tdsqlite3Strlen30(pDef->zName) + 1);
if( pNew==0 ){
return pDef;
}
*pNew = *pDef;
pNew->zName = (const char*)&pNew[1];
- memcpy((char*)&pNew[1], pDef->zName, sqlite3Strlen30(pDef->zName)+1);
+ memcpy((char*)&pNew[1], pDef->zName, tdsqlite3Strlen30(pDef->zName)+1);
pNew->xSFunc = xSFunc;
pNew->pUserData = pArg;
pNew->funcFlags |= SQLITE_FUNC_EPHEM;
@@ -126753,8 +142616,8 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(
** array if it is missing. If pTab is already in the array, this routine
** is a no-op.
*/
-SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
- Parse *pToplevel = sqlite3ParseToplevel(pParse);
+SQLITE_PRIVATE void tdsqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
+ Parse *pToplevel = tdsqlite3ParseToplevel(pParse);
int i, n;
Table **apVtabLock;
@@ -126763,12 +142626,12 @@ SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
if( pTab==pToplevel->apVtabLock[i] ) return;
}
n = (pToplevel->nVtabLock+1)*sizeof(pToplevel->apVtabLock[0]);
- apVtabLock = sqlite3_realloc64(pToplevel->apVtabLock, n);
+ apVtabLock = tdsqlite3_realloc64(pToplevel->apVtabLock, n);
if( apVtabLock ){
pToplevel->apVtabLock = apVtabLock;
pToplevel->apVtabLock[pToplevel->nVtabLock++] = pTab;
}else{
- sqlite3OomFault(pToplevel->db);
+ tdsqlite3OomFault(pToplevel->db);
}
}
@@ -126786,35 +142649,34 @@ SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
** Any virtual table module for which xConnect and xCreate are the same
** method can have an eponymous virtual table instance.
*/
-SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
- const sqlite3_module *pModule = pMod->pModule;
+SQLITE_PRIVATE int tdsqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
+ const tdsqlite3_module *pModule = pMod->pModule;
Table *pTab;
char *zErr = 0;
int rc;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
if( pMod->pEpoTab ) return 1;
if( pModule->xCreate!=0 && pModule->xCreate!=pModule->xConnect ) return 0;
- pTab = sqlite3DbMallocZero(db, sizeof(Table));
+ pTab = tdsqlite3DbMallocZero(db, sizeof(Table));
if( pTab==0 ) return 0;
- pTab->zName = sqlite3DbStrDup(db, pMod->zName);
+ pTab->zName = tdsqlite3DbStrDup(db, pMod->zName);
if( pTab->zName==0 ){
- sqlite3DbFree(db, pTab);
+ tdsqlite3DbFree(db, pTab);
return 0;
}
pMod->pEpoTab = pTab;
- pTab->nRef = 1;
+ pTab->nTabRef = 1;
pTab->pSchema = db->aDb[0].pSchema;
- pTab->tabFlags |= TF_Virtual;
- pTab->nModuleArg = 0;
+ assert( pTab->nModuleArg==0 );
pTab->iPKey = -1;
- addModuleArgument(db, pTab, sqlite3DbStrDup(db, pTab->zName));
- addModuleArgument(db, pTab, 0);
- addModuleArgument(db, pTab, sqlite3DbStrDup(db, pTab->zName));
+ addModuleArgument(pParse, pTab, tdsqlite3DbStrDup(db, pTab->zName));
+ addModuleArgument(pParse, pTab, 0);
+ addModuleArgument(pParse, pTab, tdsqlite3DbStrDup(db, pTab->zName));
rc = vtabCallConstructor(db, pTab, pMod, pModule->xConnect, &zErr);
if( rc ){
- sqlite3ErrorMsg(pParse, "%s", zErr);
- sqlite3DbFree(db, zErr);
- sqlite3VtabEponymousTableClear(db, pMod);
+ tdsqlite3ErrorMsg(pParse, "%s", zErr);
+ tdsqlite3DbFree(db, zErr);
+ tdsqlite3VtabEponymousTableClear(db, pMod);
return 0;
}
return 1;
@@ -126824,14 +142686,14 @@ SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
** Erase the eponymous virtual table instance associated with
** virtual table module pMod, if it exists.
*/
-SQLITE_PRIVATE void sqlite3VtabEponymousTableClear(sqlite3 *db, Module *pMod){
+SQLITE_PRIVATE void tdsqlite3VtabEponymousTableClear(tdsqlite3 *db, Module *pMod){
Table *pTab = pMod->pEpoTab;
if( pTab!=0 ){
/* Mark the table as Ephemeral prior to deleting it, so that the
- ** sqlite3DeleteTable() routine will know that it is not stored in
+ ** tdsqlite3DeleteTable() routine will know that it is not stored in
** the schema. */
pTab->tabFlags |= TF_Ephemeral;
- sqlite3DeleteTable(db, pTab);
+ tdsqlite3DeleteTable(db, pTab);
pMod->pEpoTab = 0;
}
}
@@ -126843,12 +142705,12 @@ SQLITE_PRIVATE void sqlite3VtabEponymousTableClear(sqlite3 *db, Module *pMod){
** The results of this routine are undefined unless it is called from
** within an xUpdate method.
*/
-SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *db){
+SQLITE_API int tdsqlite3_vtab_on_conflict(tdsqlite3 *db){
static const unsigned char aMap[] = {
SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE
};
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
assert( OE_Rollback==1 && OE_Abort==2 && OE_Fail==3 );
assert( OE_Ignore==4 && OE_Replace==5 );
@@ -126861,34 +142723,44 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *db){
** the SQLite core with additional information about the behavior
** of the virtual table being implemented.
*/
-SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){
+SQLITE_API int tdsqlite3_vtab_config(tdsqlite3 *db, int op, ...){
va_list ap;
int rc = SQLITE_OK;
+ VtabCtx *p;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
- va_start(ap, op);
- switch( op ){
- case SQLITE_VTAB_CONSTRAINT_SUPPORT: {
- VtabCtx *p = db->pVtabCtx;
- if( !p ){
- rc = SQLITE_MISUSE_BKPT;
- }else{
- assert( p->pTab==0 || (p->pTab->tabFlags & TF_Virtual)!=0 );
+ tdsqlite3_mutex_enter(db->mutex);
+ p = db->pVtabCtx;
+ if( !p ){
+ rc = SQLITE_MISUSE_BKPT;
+ }else{
+ assert( p->pTab==0 || IsVirtual(p->pTab) );
+ va_start(ap, op);
+ switch( op ){
+ case SQLITE_VTAB_CONSTRAINT_SUPPORT: {
p->pVTable->bConstraint = (u8)va_arg(ap, int);
+ break;
+ }
+ case SQLITE_VTAB_INNOCUOUS: {
+ p->pVTable->eVtabRisk = SQLITE_VTABRISK_Low;
+ break;
+ }
+ case SQLITE_VTAB_DIRECTONLY: {
+ p->pVTable->eVtabRisk = SQLITE_VTABRISK_High;
+ break;
+ }
+ default: {
+ rc = SQLITE_MISUSE_BKPT;
+ break;
}
- break;
}
- default:
- rc = SQLITE_MISUSE_BKPT;
- break;
+ va_end(ap);
}
- va_end(ap);
- if( rc!=SQLITE_OK ) sqlite3Error(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ if( rc!=SQLITE_OK ) tdsqlite3Error(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -126934,16 +142806,18 @@ SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){
** planner logic in "where.c". These definitions are broken out into
** a separate source file for easier editing.
*/
+#ifndef SQLITE_WHEREINT_H
+#define SQLITE_WHEREINT_H
/*
** Trace output macros
*/
#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG)
-/***/ int sqlite3WhereTrace;
+/***/ extern int tdsqlite3WhereTrace;
#endif
#if defined(SQLITE_DEBUG) \
&& (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE))
-# define WHERETRACE(K,X) if(sqlite3WhereTrace&(K)) sqlite3DebugPrintf X
+# define WHERETRACE(K,X) if(tdsqlite3WhereTrace&(K)) tdsqlite3DebugPrintf X
# define WHERETRACE_ENABLED 1
#else
# define WHERETRACE(K,X)
@@ -126989,19 +142863,23 @@ struct WhereLevel {
int addrCont; /* Jump here to continue with the next loop cycle */
int addrFirst; /* First instruction of interior of the loop */
int addrBody; /* Beginning of the body of this loop */
+ int regBignull; /* big-null flag reg. True if a NULL-scan is needed */
+ int addrBignull; /* Jump here for next part of big-null scan */
#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
u32 iLikeRepCntr; /* LIKE range processing counter register (times 2) */
int addrLikeRep; /* LIKE range processing address */
#endif
u8 iFrom; /* Which entry in the FROM clause */
u8 op, p3, p5; /* Opcode, P3 & P5 of the opcode that ends the loop */
- int p1, p2; /* Operands of the opcode used to ends the loop */
+ int p1, p2; /* Operands of the opcode used to end the loop */
union { /* Information that depends on pWLoop->wsFlags */
struct {
int nIn; /* Number of entries in aInLoop[] */
struct InLoop {
int iCur; /* The VDBE cursor used by this IN operator */
int addrInTop; /* Top of the IN loop */
+ int iBase; /* Base register of multi-key index record */
+ int nPrefix; /* Number of prior entires in the key */
u8 eEndLoopOp; /* IN Loop terminator. OP_Next or OP_Prev */
} *aInLoop; /* Information about each nested IN operator */
} in; /* Used when pWLoop->wsFlags&WHERE_IN_ABLE */
@@ -127044,11 +142922,12 @@ struct WhereLoop {
u16 nEq; /* Number of equality constraints */
u16 nBtm; /* Size of BTM vector */
u16 nTop; /* Size of TOP vector */
+ u16 nDistinctCol; /* Index columns used to sort for DISTINCT */
Index *pIndex; /* Index used, or NULL */
} btree;
struct { /* Information for virtual tables */
int idxNum; /* Index number */
- u8 needFree; /* True if sqlite3_free(idxStr) is needed */
+ u8 needFree; /* True if tdsqlite3_free(idxStr) is needed */
i8 isOrdered; /* True if satisfies ORDER BY */
u16 omitMask; /* Terms that may be omitted */
char *idxStr; /* Index identifier string */
@@ -127187,22 +143066,23 @@ struct WhereTerm {
/*
** Allowed values of WhereTerm.wtFlags
*/
-#define TERM_DYNAMIC 0x01 /* Need to call sqlite3ExprDelete(db, pExpr) */
-#define TERM_VIRTUAL 0x02 /* Added by the optimizer. Do not code */
-#define TERM_CODED 0x04 /* This term is already coded */
-#define TERM_COPIED 0x08 /* Has a child */
-#define TERM_ORINFO 0x10 /* Need to free the WhereTerm.u.pOrInfo object */
-#define TERM_ANDINFO 0x20 /* Need to free the WhereTerm.u.pAndInfo obj */
-#define TERM_OR_OK 0x40 /* Used during OR-clause processing */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
-# define TERM_VNULL 0x80 /* Manufactured x>NULL or x<=NULL term */
+#define TERM_DYNAMIC 0x0001 /* Need to call tdsqlite3ExprDelete(db, pExpr) */
+#define TERM_VIRTUAL 0x0002 /* Added by the optimizer. Do not code */
+#define TERM_CODED 0x0004 /* This term is already coded */
+#define TERM_COPIED 0x0008 /* Has a child */
+#define TERM_ORINFO 0x0010 /* Need to free the WhereTerm.u.pOrInfo object */
+#define TERM_ANDINFO 0x0020 /* Need to free the WhereTerm.u.pAndInfo obj */
+#define TERM_OR_OK 0x0040 /* Used during OR-clause processing */
+#ifdef SQLITE_ENABLE_STAT4
+# define TERM_VNULL 0x0080 /* Manufactured x>NULL or x<=NULL term */
#else
-# define TERM_VNULL 0x00 /* Disabled if not using stat3 */
+# define TERM_VNULL 0x0000 /* Disabled if not using stat4 */
#endif
-#define TERM_LIKEOPT 0x100 /* Virtual terms from the LIKE optimization */
-#define TERM_LIKECOND 0x200 /* Conditionally this LIKE operator term */
-#define TERM_LIKE 0x400 /* The original LIKE operator */
-#define TERM_IS 0x800 /* Term.pExpr is an IS operator */
+#define TERM_LIKEOPT 0x0100 /* Virtual terms from the LIKE optimization */
+#define TERM_LIKECOND 0x0200 /* Conditionally this LIKE operator term */
+#define TERM_LIKE 0x0400 /* The original LIKE operator */
+#define TERM_IS 0x0800 /* Term.pExpr is an IS operator */
+#define TERM_VARSELECT 0x1000 /* Term.pExpr contains a correlated sub-query */
/*
** An instance of the WhereScan object is used as an iterator for locating
@@ -127238,6 +143118,7 @@ struct WhereClause {
WhereInfo *pWInfo; /* WHERE clause processing context */
WhereClause *pOuter; /* Outer conjunction */
u8 op; /* Split operator. TK_AND or TK_OR */
+ u8 hasOr; /* True if any a[].eOperator is WO_OR */
int nTerm; /* Number of terms */
int nSlot; /* Number of entries in a[] */
WhereTerm *a; /* Each a[] describes a term of the WHERE cluase */
@@ -127292,6 +143173,7 @@ struct WhereAndInfo {
** no gaps.
*/
struct WhereMaskSet {
+ int bVarSelect; /* Used by tdsqlite3WhereExprUsage() */
int n; /* Number of assigned cursor values */
int ix[BMS]; /* Cursor assigned to each bit */
};
@@ -127311,10 +143193,50 @@ struct WhereLoopBuilder {
ExprList *pOrderBy; /* ORDER BY clause */
WhereLoop *pNew; /* Template WhereLoop */
WhereOrSet *pOrSet; /* Record best loops here, if not NULL */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
UnpackedRecord *pRec; /* Probe for stat4 (if required) */
int nRecValid; /* Number of valid fields currently in pRec */
#endif
+ unsigned int bldFlags; /* SQLITE_BLDF_* flags */
+ unsigned int iPlanLimit; /* Search limiter */
+};
+
+/* Allowed values for WhereLoopBuider.bldFlags */
+#define SQLITE_BLDF_INDEXED 0x0001 /* An index is used */
+#define SQLITE_BLDF_UNIQUE 0x0002 /* All keys of a UNIQUE index used */
+
+/* The WhereLoopBuilder.iPlanLimit is used to limit the number of
+** index+constraint combinations the query planner will consider for a
+** particular query. If this parameter is unlimited, then certain
+** pathological queries can spend excess time in the tdsqlite3WhereBegin()
+** routine. The limit is high enough that is should not impact real-world
+** queries.
+**
+** SQLITE_QUERY_PLANNER_LIMIT is the baseline limit. The limit is
+** increased by SQLITE_QUERY_PLANNER_LIMIT_INCR before each term of the FROM
+** clause is processed, so that every table in a join is guaranteed to be
+** able to propose a some index+constraint combinations even if the initial
+** baseline limit was exhausted by prior tables of the join.
+*/
+#ifndef SQLITE_QUERY_PLANNER_LIMIT
+# define SQLITE_QUERY_PLANNER_LIMIT 20000
+#endif
+#ifndef SQLITE_QUERY_PLANNER_LIMIT_INCR
+# define SQLITE_QUERY_PLANNER_LIMIT_INCR 1000
+#endif
+
+/*
+** Each instance of this object records a change to a single node
+** in an expression tree to cause that node to point to a column
+** of an index rather than an expression or a virtual column. All
+** such transformations need to be undone at the end of WHERE clause
+** processing.
+*/
+typedef struct WhereExprMod WhereExprMod;
+struct WhereExprMod {
+ WhereExprMod *pNext; /* Next translation on a list of them all */
+ Expr *pExpr; /* The Expr node that was transformed */
+ Expr orig; /* Original value of the Expr node */
};
/*
@@ -127331,24 +143253,27 @@ struct WhereInfo {
Parse *pParse; /* Parsing and code generating context */
SrcList *pTabList; /* List of tables in the join */
ExprList *pOrderBy; /* The ORDER BY clause or NULL */
- ExprList *pDistinctSet; /* DISTINCT over all these values */
- LogEst iLimit; /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */
+ ExprList *pResultSet; /* Result set of the query */
+ Expr *pWhere; /* The complete WHERE clause */
int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */
int iContinue; /* Jump here to continue with next record */
int iBreak; /* Jump here to break out of the loop */
int savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */
- u16 wctrlFlags; /* Flags originally passed to sqlite3WhereBegin() */
+ u16 wctrlFlags; /* Flags originally passed to tdsqlite3WhereBegin() */
+ LogEst iLimit; /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */
u8 nLevel; /* Number of nested loop */
i8 nOBSat; /* Number of ORDER BY terms satisfied by indices */
- u8 sorted; /* True if really sorted (not just grouped) */
u8 eOnePass; /* ONEPASS_OFF, or _SINGLE, or _MULTI */
- u8 untestedTerms; /* Not all WHERE terms resolved by outer loop */
u8 eDistinct; /* One of the WHERE_DISTINCT_* values */
- u8 bOrderedInnerLoop; /* True if only the inner-most loop is ordered */
+ unsigned bDeferredSeek :1; /* Uses OP_DeferredSeek */
+ unsigned untestedTerms :1; /* Not all WHERE terms resolved by outer loop */
+ unsigned bOrderedInnerLoop:1;/* True if only the inner-most loop is ordered */
+ unsigned sorted :1; /* True if really sorted (not just grouped) */
+ LogEst nRowOut; /* Estimated number of output rows */
int iTop; /* The very beginning of the WHERE loop */
WhereLoop *pLoops; /* List of all WhereLoop objects */
+ WhereExprMod *pExprMods; /* Expression modifications */
Bitmask revMask; /* Mask of ORDER BY terms that need reversing */
- LogEst nRowOut; /* Estimated number of output rows */
WhereClause sWC; /* Decomposition of the WHERE clause */
WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */
WhereLevel a[1]; /* Information about each nest loop in WHERE */
@@ -127359,11 +143284,13 @@ struct WhereInfo {
**
** where.c:
*/
-SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet*,int);
+SQLITE_PRIVATE Bitmask tdsqlite3WhereGetMask(WhereMaskSet*,int);
#ifdef WHERETRACE_ENABLED
-SQLITE_PRIVATE void sqlite3WhereClausePrint(WhereClause *pWC);
+SQLITE_PRIVATE void tdsqlite3WhereClausePrint(WhereClause *pWC);
+SQLITE_PRIVATE void tdsqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm);
+SQLITE_PRIVATE void tdsqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC);
#endif
-SQLITE_PRIVATE WhereTerm *sqlite3WhereFindTerm(
+SQLITE_PRIVATE WhereTerm *tdsqlite3WhereFindTerm(
WhereClause *pWC, /* The WHERE clause to be searched */
int iCur, /* Cursor number of LHS */
int iColumn, /* Column number of LHS */
@@ -127374,41 +143301,43 @@ SQLITE_PRIVATE WhereTerm *sqlite3WhereFindTerm(
/* wherecode.c: */
#ifndef SQLITE_OMIT_EXPLAIN
-SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
+SQLITE_PRIVATE int tdsqlite3WhereExplainOneScan(
Parse *pParse, /* Parse context */
SrcList *pTabList, /* Table list this loop refers to */
WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
- int iLevel, /* Value for "level" column of output */
- int iFrom, /* Value for "from" column of output */
- u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
+ u16 wctrlFlags /* Flags passed to tdsqlite3WhereBegin() */
);
#else
-# define sqlite3WhereExplainOneScan(u,v,w,x,y,z) 0
+# define tdsqlite3WhereExplainOneScan(u,v,w,x) 0
#endif /* SQLITE_OMIT_EXPLAIN */
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
-SQLITE_PRIVATE void sqlite3WhereAddScanStatus(
+SQLITE_PRIVATE void tdsqlite3WhereAddScanStatus(
Vdbe *v, /* Vdbe to add scanstatus entry to */
SrcList *pSrclist, /* FROM clause pLvl reads data from */
WhereLevel *pLvl, /* Level to add scanstatus() entry for */
int addrExplain /* Address of OP_Explain (or 0) */
);
#else
-# define sqlite3WhereAddScanStatus(a, b, c, d) ((void)d)
+# define tdsqlite3WhereAddScanStatus(a, b, c, d) ((void)d)
#endif
-SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
+SQLITE_PRIVATE Bitmask tdsqlite3WhereCodeOneLoopStart(
+ Parse *pParse, /* Parsing context */
+ Vdbe *v, /* Prepared statement under construction */
WhereInfo *pWInfo, /* Complete information about the WHERE clause */
int iLevel, /* Which level of pWInfo->a[] should be coded */
+ WhereLevel *pLevel, /* The current level pointer */
Bitmask notReady /* Which tables are currently available */
);
/* whereexpr.c: */
-SQLITE_PRIVATE void sqlite3WhereClauseInit(WhereClause*,WhereInfo*);
-SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause*);
-SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause*,Expr*,u8);
-SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet*, Expr*);
-SQLITE_PRIVATE Bitmask sqlite3WhereExprListUsage(WhereMaskSet*, ExprList*);
-SQLITE_PRIVATE void sqlite3WhereExprAnalyze(SrcList*, WhereClause*);
-SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereClause*);
+SQLITE_PRIVATE void tdsqlite3WhereClauseInit(WhereClause*,WhereInfo*);
+SQLITE_PRIVATE void tdsqlite3WhereClauseClear(WhereClause*);
+SQLITE_PRIVATE void tdsqlite3WhereSplit(WhereClause*,Expr*,u8);
+SQLITE_PRIVATE Bitmask tdsqlite3WhereExprUsage(WhereMaskSet*, Expr*);
+SQLITE_PRIVATE Bitmask tdsqlite3WhereExprUsageNN(WhereMaskSet*, Expr*);
+SQLITE_PRIVATE Bitmask tdsqlite3WhereExprListUsage(WhereMaskSet*, ExprList*);
+SQLITE_PRIVATE void tdsqlite3WhereExprAnalyze(SrcList*, WhereClause*);
+SQLITE_PRIVATE void tdsqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereClause*);
@@ -127426,7 +143355,6 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereC
** WO_LE == SQLITE_INDEX_CONSTRAINT_LE
** WO_GT == SQLITE_INDEX_CONSTRAINT_GT
** WO_GE == SQLITE_INDEX_CONSTRAINT_GE
-** WO_MATCH == SQLITE_INDEX_CONSTRAINT_MATCH
*/
#define WO_IN 0x0001
#define WO_EQ 0x0002
@@ -127434,7 +143362,7 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereC
#define WO_LE (WO_EQ<<(TK_LE-TK_EQ))
#define WO_GT (WO_EQ<<(TK_GT-TK_EQ))
#define WO_GE (WO_EQ<<(TK_GE-TK_EQ))
-#define WO_MATCH 0x0040
+#define WO_AUX 0x0040 /* Op useful to virtual tables only */
#define WO_IS 0x0080
#define WO_ISNULL 0x0100
#define WO_OR 0x0200 /* Two or more OR-connected terms */
@@ -127469,6 +143397,10 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereC
#define WHERE_SKIPSCAN 0x00008000 /* Uses the skip-scan algorithm */
#define WHERE_UNQ_WANTED 0x00010000 /* WHERE_ONEROW would have been helpful*/
#define WHERE_PARTIALIDX 0x00020000 /* The automatic index is partial */
+#define WHERE_IN_EARLYOUT 0x00040000 /* Perhaps quit IN loops early */
+#define WHERE_BIGNULL_SORT 0x00080000 /* Column nEq of index is BIGNULL */
+
+#endif /* !defined(SQLITE_WHEREINT_H) */
/************** End of whereInt.h ********************************************/
/************** Continuing where we left off in wherecode.c ******************/
@@ -127504,23 +143436,23 @@ static void explainAppendTerm(
int i;
assert( nTerm>=1 );
- if( bAnd ) sqlite3StrAccumAppend(pStr, " AND ", 5);
+ if( bAnd ) tdsqlite3_str_append(pStr, " AND ", 5);
- if( nTerm>1 ) sqlite3StrAccumAppend(pStr, "(", 1);
+ if( nTerm>1 ) tdsqlite3_str_append(pStr, "(", 1);
for(i=0; i<nTerm; i++){
- if( i ) sqlite3StrAccumAppend(pStr, ",", 1);
- sqlite3StrAccumAppendAll(pStr, explainIndexColumnName(pIdx, iTerm+i));
+ if( i ) tdsqlite3_str_append(pStr, ",", 1);
+ tdsqlite3_str_appendall(pStr, explainIndexColumnName(pIdx, iTerm+i));
}
- if( nTerm>1 ) sqlite3StrAccumAppend(pStr, ")", 1);
+ if( nTerm>1 ) tdsqlite3_str_append(pStr, ")", 1);
- sqlite3StrAccumAppend(pStr, zOp, 1);
+ tdsqlite3_str_append(pStr, zOp, 1);
- if( nTerm>1 ) sqlite3StrAccumAppend(pStr, "(", 1);
+ if( nTerm>1 ) tdsqlite3_str_append(pStr, "(", 1);
for(i=0; i<nTerm; i++){
- if( i ) sqlite3StrAccumAppend(pStr, ",", 1);
- sqlite3StrAccumAppend(pStr, "?", 1);
+ if( i ) tdsqlite3_str_append(pStr, ",", 1);
+ tdsqlite3_str_append(pStr, "?", 1);
}
- if( nTerm>1 ) sqlite3StrAccumAppend(pStr, ")", 1);
+ if( nTerm>1 ) tdsqlite3_str_append(pStr, ")", 1);
}
/*
@@ -127544,11 +143476,11 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){
int i, j;
if( nEq==0 && (pLoop->wsFlags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))==0 ) return;
- sqlite3StrAccumAppend(pStr, " (", 2);
+ tdsqlite3_str_append(pStr, " (", 2);
for(i=0; i<nEq; i++){
const char *z = explainIndexColumnName(pIndex, i);
- if( i ) sqlite3StrAccumAppend(pStr, " AND ", 5);
- sqlite3XPrintf(pStr, i>=nSkip ? "%s=?" : "ANY(%s)", z);
+ if( i ) tdsqlite3_str_append(pStr, " AND ", 5);
+ tdsqlite3_str_appendf(pStr, i>=nSkip ? "%s=?" : "ANY(%s)", z);
}
j = i;
@@ -127559,7 +143491,7 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){
if( pLoop->wsFlags&WHERE_TOP_LIMIT ){
explainAppendTerm(pStr, pIndex, pLoop->u.btree.nTop, j, i, "<");
}
- sqlite3StrAccumAppend(pStr, ")", 1);
+ tdsqlite3_str_append(pStr, ")", 1);
}
/*
@@ -127571,23 +143503,20 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){
** If an OP_Explain opcode is added to the VM, its address is returned.
** Otherwise, if no OP_Explain is coded, zero is returned.
*/
-SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
+SQLITE_PRIVATE int tdsqlite3WhereExplainOneScan(
Parse *pParse, /* Parse context */
SrcList *pTabList, /* Table list this loop refers to */
WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
- int iLevel, /* Value for "level" column of output */
- int iFrom, /* Value for "from" column of output */
- u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
+ u16 wctrlFlags /* Flags passed to tdsqlite3WhereBegin() */
){
int ret = 0;
#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS)
- if( pParse->explain==2 )
+ if( tdsqlite3ParseToplevel(pParse)->explain==2 )
#endif
{
struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom];
Vdbe *v = pParse->pVdbe; /* VM being constructed */
- sqlite3 *db = pParse->db; /* Database handle */
- int iId = pParse->iSelectId; /* Select id (left-most output column) */
+ tdsqlite3 *db = pParse->db; /* Database handle */
int isSearch; /* True for a SEARCH. False for SCAN. */
WhereLoop *pLoop; /* The controlling WhereLoop object */
u32 flags; /* Flags that describe this loop */
@@ -127603,16 +143532,16 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
|| ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0))
|| (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX));
- sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH);
- sqlite3StrAccumAppendAll(&str, isSearch ? "SEARCH" : "SCAN");
+ tdsqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH);
+ tdsqlite3_str_appendall(&str, isSearch ? "SEARCH" : "SCAN");
if( pItem->pSelect ){
- sqlite3XPrintf(&str, " SUBQUERY %d", pItem->iSelectId);
+ tdsqlite3_str_appendf(&str, " SUBQUERY %u", pItem->pSelect->selId);
}else{
- sqlite3XPrintf(&str, " TABLE %s", pItem->zName);
+ tdsqlite3_str_appendf(&str, " TABLE %s", pItem->zName);
}
if( pItem->zAlias ){
- sqlite3XPrintf(&str, " AS %s", pItem->zAlias);
+ tdsqlite3_str_appendf(&str, " AS %s", pItem->zAlias);
}
if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){
const char *zFmt = 0;
@@ -127635,8 +143564,8 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
zFmt = "INDEX %s";
}
if( zFmt ){
- sqlite3StrAccumAppend(&str, " USING ", 7);
- sqlite3XPrintf(&str, zFmt, pIdx->zName);
+ tdsqlite3_str_append(&str, " USING ", 7);
+ tdsqlite3_str_appendf(&str, zFmt, pIdx->zName);
explainIndexRange(&str, pLoop);
}
}else if( (flags & WHERE_IPK)!=0 && (flags & WHERE_CONSTRAINT)!=0 ){
@@ -127651,23 +143580,27 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
assert( flags&WHERE_TOP_LIMIT);
zRangeOp = "<";
}
- sqlite3XPrintf(&str, " USING INTEGER PRIMARY KEY (rowid%s?)",zRangeOp);
+ tdsqlite3_str_appendf(&str,
+ " USING INTEGER PRIMARY KEY (rowid%s?)",zRangeOp);
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
else if( (flags & WHERE_VIRTUALTABLE)!=0 ){
- sqlite3XPrintf(&str, " VIRTUAL TABLE INDEX %d:%s",
+ tdsqlite3_str_appendf(&str, " VIRTUAL TABLE INDEX %d:%s",
pLoop->u.vtab.idxNum, pLoop->u.vtab.idxStr);
}
#endif
#ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS
if( pLoop->nOut>=10 ){
- sqlite3XPrintf(&str, " (~%llu rows)", sqlite3LogEstToInt(pLoop->nOut));
+ tdsqlite3_str_appendf(&str, " (~%llu rows)",
+ tdsqlite3LogEstToInt(pLoop->nOut));
}else{
- sqlite3StrAccumAppend(&str, " (~1 row)", 9);
+ tdsqlite3_str_append(&str, " (~1 row)", 9);
}
#endif
- zMsg = sqlite3StrAccumFinish(&str);
- ret = sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg,P4_DYNAMIC);
+ zMsg = tdsqlite3StrAccumFinish(&str);
+ tdsqlite3ExplainBreakpoint("",zMsg);
+ ret = tdsqlite3VdbeAddOp4(v, OP_Explain, tdsqlite3VdbeCurrentAddr(v),
+ pParse->addrExplain, 0, zMsg,P4_DYNAMIC);
}
return ret;
}
@@ -127676,14 +143609,14 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
/*
** Configure the VM passed as the first argument with an
-** sqlite3_stmt_scanstatus() entry corresponding to the scan used to
+** tdsqlite3_stmt_scanstatus() entry corresponding to the scan used to
** implement level pLvl. Argument pSrclist is a pointer to the FROM
** clause that the scan reads data from.
**
** If argument addrExplain is not 0, it must be the address of an
** OP_Explain instruction that describes the same loop.
*/
-SQLITE_PRIVATE void sqlite3WhereAddScanStatus(
+SQLITE_PRIVATE void tdsqlite3WhereAddScanStatus(
Vdbe *v, /* Vdbe to add scanstatus entry to */
SrcList *pSrclist, /* FROM clause pLvl reads data from */
WhereLevel *pLvl, /* Level to add scanstatus() entry for */
@@ -127696,7 +143629,7 @@ SQLITE_PRIVATE void sqlite3WhereAddScanStatus(
}else{
zObj = pSrclist->a[pLvl->iFrom].zName;
}
- sqlite3VdbeScanStatus(
+ tdsqlite3VdbeScanStatus(
v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj
);
}
@@ -127747,8 +143680,8 @@ SQLITE_PRIVATE void sqlite3WhereAddScanStatus(
*/
static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){
int nLoop = 0;
- while( ALWAYS(pTerm!=0)
- && (pTerm->wtFlags & TERM_CODED)==0
+ assert( pTerm!=0 );
+ while( (pTerm->wtFlags & TERM_CODED)==0
&& (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin))
&& (pLevel->notReady & pTerm->prereqAll)==0
){
@@ -127759,6 +143692,7 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){
}
if( pTerm->iParent<0 ) break;
pTerm = &pTerm->pWC->a[pTerm->iParent];
+ assert( pTerm!=0 );
pTerm->nChild--;
if( pTerm->nChild!=0 ) break;
nLoop++;
@@ -127769,9 +143703,9 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){
** Code an OP_Affinity opcode to apply the column affinity string zAff
** to the n registers starting at base.
**
-** As an optimization, SQLITE_AFF_BLOB entries (which are no-ops) at the
-** beginning and end of zAff are ignored. If all entries in zAff are
-** SQLITE_AFF_BLOB, then no code gets generated.
+** As an optimization, SQLITE_AFF_BLOB and SQLITE_AFF_NONE entries (which
+** are no-ops) at the beginning and end of zAff are ignored. If all entries
+** in zAff are SQLITE_AFF_BLOB or SQLITE_AFF_NONE, then no code gets generated.
**
** This routine makes its own copy of zAff so that the caller is free
** to modify zAff after this routine returns.
@@ -127784,22 +143718,22 @@ static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){
}
assert( v!=0 );
- /* Adjust base and n to skip over SQLITE_AFF_BLOB entries at the beginning
- ** and end of the affinity string.
+ /* Adjust base and n to skip over SQLITE_AFF_BLOB and SQLITE_AFF_NONE
+ ** entries at the beginning and end of the affinity string.
*/
- while( n>0 && zAff[0]==SQLITE_AFF_BLOB ){
+ assert( SQLITE_AFF_NONE<SQLITE_AFF_BLOB );
+ while( n>0 && zAff[0]<=SQLITE_AFF_BLOB ){
n--;
base++;
zAff++;
}
- while( n>1 && zAff[n-1]==SQLITE_AFF_BLOB ){
+ while( n>1 && zAff[n-1]<=SQLITE_AFF_BLOB ){
n--;
}
/* Code the OP_Affinity opcode if there is anything left to do. */
if( n>0 ){
- sqlite3VdbeAddOp4(v, OP_Affinity, base, n, 0, zAff, n);
- sqlite3ExprCacheAffinityChange(pParse, base, n);
+ tdsqlite3VdbeAddOp4(v, OP_Affinity, base, n, 0, zAff, n);
}
}
@@ -127820,15 +143754,112 @@ static void updateRangeAffinityStr(
){
int i;
for(i=0; i<n; i++){
- Expr *p = sqlite3VectorFieldSubexpr(pRight, i);
- if( sqlite3CompareAffinity(p, zAff[i])==SQLITE_AFF_BLOB
- || sqlite3ExprNeedsNoAffinityChange(p, zAff[i])
+ Expr *p = tdsqlite3VectorFieldSubexpr(pRight, i);
+ if( tdsqlite3CompareAffinity(p, zAff[i])==SQLITE_AFF_BLOB
+ || tdsqlite3ExprNeedsNoAffinityChange(p, zAff[i])
){
zAff[i] = SQLITE_AFF_BLOB;
}
}
}
+
+/*
+** pX is an expression of the form: (vector) IN (SELECT ...)
+** In other words, it is a vector IN operator with a SELECT clause on the
+** LHS. But not all terms in the vector are indexable and the terms might
+** not be in the correct order for indexing.
+**
+** This routine makes a copy of the input pX expression and then adjusts
+** the vector on the LHS with corresponding changes to the SELECT so that
+** the vector contains only index terms and those terms are in the correct
+** order. The modified IN expression is returned. The caller is responsible
+** for deleting the returned expression.
+**
+** Example:
+**
+** CREATE TABLE t1(a,b,c,d,e,f);
+** CREATE INDEX t1x1 ON t1(e,c);
+** SELECT * FROM t1 WHERE (a,b,c,d,e) IN (SELECT v,w,x,y,z FROM t2)
+** \_______________________________________/
+** The pX expression
+**
+** Since only columns e and c can be used with the index, in that order,
+** the modified IN expression that is returned will be:
+**
+** (e,c) IN (SELECT z,x FROM t2)
+**
+** The reduced pX is different from the original (obviously) and thus is
+** only used for indexing, to improve performance. The original unaltered
+** IN expression must also be run on each output row for correctness.
+*/
+static Expr *removeUnindexableInClauseTerms(
+ Parse *pParse, /* The parsing context */
+ int iEq, /* Look at loop terms starting here */
+ WhereLoop *pLoop, /* The current loop */
+ Expr *pX /* The IN expression to be reduced */
+){
+ tdsqlite3 *db = pParse->db;
+ Expr *pNew;
+ pNew = tdsqlite3ExprDup(db, pX, 0);
+ if( db->mallocFailed==0 ){
+ ExprList *pOrigRhs = pNew->x.pSelect->pEList; /* Original unmodified RHS */
+ ExprList *pOrigLhs = pNew->pLeft->x.pList; /* Original unmodified LHS */
+ ExprList *pRhs = 0; /* New RHS after modifications */
+ ExprList *pLhs = 0; /* New LHS after mods */
+ int i; /* Loop counter */
+ Select *pSelect; /* Pointer to the SELECT on the RHS */
+
+ for(i=iEq; i<pLoop->nLTerm; i++){
+ if( pLoop->aLTerm[i]->pExpr==pX ){
+ int iField = pLoop->aLTerm[i]->iField - 1;
+ if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */
+ pRhs = tdsqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
+ pOrigRhs->a[iField].pExpr = 0;
+ assert( pOrigLhs->a[iField].pExpr!=0 );
+ pLhs = tdsqlite3ExprListAppend(pParse, pLhs, pOrigLhs->a[iField].pExpr);
+ pOrigLhs->a[iField].pExpr = 0;
+ }
+ }
+ tdsqlite3ExprListDelete(db, pOrigRhs);
+ tdsqlite3ExprListDelete(db, pOrigLhs);
+ pNew->pLeft->x.pList = pLhs;
+ pNew->x.pSelect->pEList = pRhs;
+ if( pLhs && pLhs->nExpr==1 ){
+ /* Take care here not to generate a TK_VECTOR containing only a
+ ** single value. Since the parser never creates such a vector, some
+ ** of the subroutines do not handle this case. */
+ Expr *p = pLhs->a[0].pExpr;
+ pLhs->a[0].pExpr = 0;
+ tdsqlite3ExprDelete(db, pNew->pLeft);
+ pNew->pLeft = p;
+ }
+ pSelect = pNew->x.pSelect;
+ if( pSelect->pOrderBy ){
+ /* If the SELECT statement has an ORDER BY clause, zero the
+ ** iOrderByCol variables. These are set to non-zero when an
+ ** ORDER BY term exactly matches one of the terms of the
+ ** result-set. Since the result-set of the SELECT statement may
+ ** have been modified or reordered, these variables are no longer
+ ** set correctly. Since setting them is just an optimization,
+ ** it's easiest just to zero them here. */
+ ExprList *pOrderBy = pSelect->pOrderBy;
+ for(i=0; i<pOrderBy->nExpr; i++){
+ pOrderBy->a[i].u.x.iOrderByCol = 0;
+ }
+ }
+
+#if 0
+ printf("For indexing, change the IN expr:\n");
+ tdsqlite3TreeViewExpr(0, pX, 0);
+ printf("Into:\n");
+ tdsqlite3TreeViewExpr(0, pNew, 0);
+#endif
+ }
+ return pNew;
+}
+
+
/*
** Generate code for a single equality term of the WHERE clause. An equality
** term can be either X=expr or X IN (...). pTerm is the term to be
@@ -127859,10 +143890,10 @@ static int codeEqualityTerm(
assert( pLevel->pWLoop->aLTerm[iEq]==pTerm );
assert( iTarget>0 );
if( pX->op==TK_EQ || pX->op==TK_IS ){
- iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget);
+ iReg = tdsqlite3ExprCodeTarget(pParse, pX->pRight, iTarget);
}else if( pX->op==TK_ISNULL ){
iReg = iTarget;
- sqlite3VdbeAddOp2(v, OP_Null, 0, iReg);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, iReg);
#ifndef SQLITE_OMIT_SUBQUERY
}else{
int eType = IN_INDEX_NOOP;
@@ -127891,89 +143922,44 @@ static int codeEqualityTerm(
}
}
for(i=iEq;i<pLoop->nLTerm; i++){
- if( ALWAYS(pLoop->aLTerm[i]) && pLoop->aLTerm[i]->pExpr==pX ) nEq++;
+ assert( pLoop->aLTerm[i]!=0 );
+ if( pLoop->aLTerm[i]->pExpr==pX ) nEq++;
}
+ iTab = 0;
if( (pX->flags & EP_xIsSelect)==0 || pX->x.pSelect->pEList->nExpr==1 ){
- eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0);
+ eType = tdsqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab);
}else{
- Select *pSelect = pX->x.pSelect;
- sqlite3 *db = pParse->db;
- u16 savedDbOptFlags = db->dbOptFlags;
- ExprList *pOrigRhs = pSelect->pEList;
- ExprList *pOrigLhs = pX->pLeft->x.pList;
- ExprList *pRhs = 0; /* New Select.pEList for RHS */
- ExprList *pLhs = 0; /* New pX->pLeft vector */
-
- for(i=iEq;i<pLoop->nLTerm; i++){
- if( pLoop->aLTerm[i]->pExpr==pX ){
- int iField = pLoop->aLTerm[i]->iField - 1;
- Expr *pNewRhs = sqlite3ExprDup(db, pOrigRhs->a[iField].pExpr, 0);
- Expr *pNewLhs = sqlite3ExprDup(db, pOrigLhs->a[iField].pExpr, 0);
+ tdsqlite3 *db = pParse->db;
+ pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX);
- pRhs = sqlite3ExprListAppend(pParse, pRhs, pNewRhs);
- pLhs = sqlite3ExprListAppend(pParse, pLhs, pNewLhs);
- }
- }
if( !db->mallocFailed ){
- Expr *pLeft = pX->pLeft;
-
- if( pSelect->pOrderBy ){
- /* If the SELECT statement has an ORDER BY clause, zero the
- ** iOrderByCol variables. These are set to non-zero when an
- ** ORDER BY term exactly matches one of the terms of the
- ** result-set. Since the result-set of the SELECT statement may
- ** have been modified or reordered, these variables are no longer
- ** set correctly. Since setting them is just an optimization,
- ** it's easiest just to zero them here. */
- ExprList *pOrderBy = pSelect->pOrderBy;
- for(i=0; i<pOrderBy->nExpr; i++){
- pOrderBy->a[i].u.x.iOrderByCol = 0;
- }
- }
-
- /* Take care here not to generate a TK_VECTOR containing only a
- ** single value. Since the parser never creates such a vector, some
- ** of the subroutines do not handle this case. */
- if( pLhs->nExpr==1 ){
- pX->pLeft = pLhs->a[0].pExpr;
- }else{
- pLeft->x.pList = pLhs;
- aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int) * nEq);
- testcase( aiMap==0 );
- }
- pSelect->pEList = pRhs;
- db->dbOptFlags |= SQLITE_QueryFlattener;
- eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap);
- db->dbOptFlags = savedDbOptFlags;
- testcase( aiMap!=0 && aiMap[0]!=0 );
- pSelect->pEList = pOrigRhs;
- pLeft->x.pList = pOrigLhs;
- pX->pLeft = pLeft;
+ aiMap = (int*)tdsqlite3DbMallocZero(pParse->db, sizeof(int)*nEq);
+ eType = tdsqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab);
+ pTerm->pExpr->iTable = iTab;
}
- sqlite3ExprListDelete(pParse->db, pLhs);
- sqlite3ExprListDelete(pParse->db, pRhs);
+ tdsqlite3ExprDelete(db, pX);
+ pX = pTerm->pExpr;
}
if( eType==IN_INDEX_INDEX_DESC ){
testcase( bRev );
bRev = !bRev;
}
- iTab = pX->iTable;
- sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iTab, 0);
+ tdsqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iTab, 0);
VdbeCoverageIf(v, bRev);
VdbeCoverageIf(v, !bRev);
assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 );
pLoop->wsFlags |= WHERE_IN_ABLE;
if( pLevel->u.in.nIn==0 ){
- pLevel->addrNxt = sqlite3VdbeMakeLabel(v);
+ pLevel->addrNxt = tdsqlite3VdbeMakeLabel(pParse);
}
i = pLevel->u.in.nIn;
pLevel->u.in.nIn += nEq;
pLevel->u.in.aInLoop =
- sqlite3DbReallocOrFree(pParse->db, pLevel->u.in.aInLoop,
+ tdsqlite3DbReallocOrFree(pParse->db, pLevel->u.in.aInLoop,
sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn);
pIn = pLevel->u.in.aInLoop;
if( pIn ){
@@ -127983,16 +143969,22 @@ static int codeEqualityTerm(
if( pLoop->aLTerm[i]->pExpr==pX ){
int iOut = iReg + i - iEq;
if( eType==IN_INDEX_ROWID ){
- testcase( nEq>1 ); /* Happens with a UNIQUE index on ROWID */
- pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iOut);
+ pIn->addrInTop = tdsqlite3VdbeAddOp2(v, OP_Rowid, iTab, iOut);
}else{
int iCol = aiMap ? aiMap[iMap++] : 0;
- pIn->addrInTop = sqlite3VdbeAddOp3(v,OP_Column,iTab, iCol, iOut);
+ pIn->addrInTop = tdsqlite3VdbeAddOp3(v,OP_Column,iTab, iCol, iOut);
}
- sqlite3VdbeAddOp1(v, OP_IsNull, iOut); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp1(v, OP_IsNull, iOut); VdbeCoverage(v);
if( i==iEq ){
pIn->iCur = iTab;
- pIn->eEndLoopOp = bRev ? OP_PrevIfOpen : OP_NextIfOpen;
+ pIn->eEndLoopOp = bRev ? OP_Prev : OP_Next;
+ if( iEq>0 ){
+ pIn->iBase = iReg - i;
+ pIn->nPrefix = i;
+ pLoop->wsFlags |= WHERE_IN_EARLYOUT;
+ }else{
+ pIn->nPrefix = 0;
+ }
}else{
pIn->eEndLoopOp = OP_Noop;
}
@@ -128002,7 +143994,7 @@ static int codeEqualityTerm(
}else{
pLevel->u.in.nIn = 0;
}
- sqlite3DbFree(pParse->db, aiMap);
+ tdsqlite3DbFree(pParse->db, aiMap);
#endif
}
disableTerm(pLevel, pTerm);
@@ -128041,7 +144033,7 @@ static int codeEqualityTerm(
**
** Before returning, *pzAff is set to point to a buffer containing a
** copy of the column affinity string of the index allocated using
-** sqlite3DbMalloc(). Except, entries in the copy of the string associated
+** tdsqlite3DbMalloc(). Except, entries in the copy of the string associated
** with equality constraints that use BLOB or NONE affinity are set to
** SQLITE_AFF_BLOB. This is to deal with SQL such as the following:
**
@@ -128086,23 +144078,23 @@ static int codeAllEqualityTerms(
nReg = pLoop->u.btree.nEq + nExtraReg;
pParse->nMem += nReg;
- zAff = sqlite3DbStrDup(pParse->db,sqlite3IndexAffinityStr(pParse->db,pIdx));
+ zAff = tdsqlite3DbStrDup(pParse->db,tdsqlite3IndexAffinityStr(pParse->db,pIdx));
assert( zAff!=0 || pParse->db->mallocFailed );
if( nSkip ){
int iIdxCur = pLevel->iIdxCur;
- sqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur);
+ tdsqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur);
VdbeCoverageIf(v, bRev==0);
VdbeCoverageIf(v, bRev!=0);
VdbeComment((v, "begin skip-scan on %s", pIdx->zName));
- j = sqlite3VdbeAddOp0(v, OP_Goto);
- pLevel->addrSkip = sqlite3VdbeAddOp4Int(v, (bRev?OP_SeekLT:OP_SeekGT),
+ j = tdsqlite3VdbeAddOp0(v, OP_Goto);
+ pLevel->addrSkip = tdsqlite3VdbeAddOp4Int(v, (bRev?OP_SeekLT:OP_SeekGT),
iIdxCur, 0, regBase, nSkip);
VdbeCoverageIf(v, bRev==0);
VdbeCoverageIf(v, bRev!=0);
- sqlite3VdbeJumpHere(v, j);
+ tdsqlite3VdbeJumpHere(v, j);
for(j=0; j<nSkip; j++){
- sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, j, regBase+j);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iIdxCur, j, regBase+j);
testcase( pIdx->aiColumn[j]==XN_EXPR );
VdbeComment((v, "%s", explainIndexColumnName(pIdx, j)));
}
@@ -128122,31 +144114,31 @@ static int codeAllEqualityTerms(
r1 = codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, regBase+j);
if( r1!=regBase+j ){
if( nReg==1 ){
- sqlite3ReleaseTempReg(pParse, regBase);
+ tdsqlite3ReleaseTempReg(pParse, regBase);
regBase = r1;
}else{
- sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j);
+ tdsqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j);
}
}
if( pTerm->eOperator & WO_IN ){
if( pTerm->pExpr->flags & EP_xIsSelect ){
/* No affinity ever needs to be (or should be) applied to a value
** from the RHS of an "? IN (SELECT ...)" expression. The
- ** sqlite3FindInIndex() routine has already ensured that the
+ ** tdsqlite3FindInIndex() routine has already ensured that the
** affinity of the comparison has been applied to the value. */
if( zAff ) zAff[j] = SQLITE_AFF_BLOB;
}
}else if( (pTerm->eOperator & WO_ISNULL)==0 ){
Expr *pRight = pTerm->pExpr->pRight;
- if( (pTerm->wtFlags & TERM_IS)==0 && sqlite3ExprCanBeNull(pRight) ){
- sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk);
+ if( (pTerm->wtFlags & TERM_IS)==0 && tdsqlite3ExprCanBeNull(pRight) ){
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk);
VdbeCoverage(v);
}
if( zAff ){
- if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_BLOB ){
+ if( tdsqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_BLOB ){
zAff[j] = SQLITE_AFF_BLOB;
}
- if( sqlite3ExprNeedsNoAffinityChange(pRight, zAff[j]) ){
+ if( tdsqlite3ExprNeedsNoAffinityChange(pRight, zAff[j]) ){
zAff[j] = SQLITE_AFF_BLOB;
}
}
@@ -128182,7 +144174,7 @@ static void whereLikeOptimizationStringFixup(
if( pTerm->wtFlags & TERM_LIKEOPT ){
VdbeOp *pOp;
assert( pLevel->iLikeRepCntr>0 );
- pOp = sqlite3VdbeGetOp(v, -1);
+ pOp = tdsqlite3VdbeGetOp(v, -1);
assert( pOp!=0 );
assert( pOp->opcode==OP_String8
|| pTerm->pWC->pWInfo->pParse->db->mallocFailed );
@@ -128197,7 +144189,7 @@ static void whereLikeOptimizationStringFixup(
#ifdef SQLITE_ENABLE_CURSOR_HINTS
/*
** Information is passed from codeCursorHint() down to individual nodes of
-** the expression tree (by sqlite3WalkExpr()) using an instance of this
+** the expression tree (by tdsqlite3WalkExpr()) using an instance of this
** structure.
*/
struct CCurHint {
@@ -128217,7 +144209,7 @@ static int codeCursorHintCheckExpr(Walker *pWalker, Expr *pExpr){
assert( pHint->pIdx!=0 );
if( pExpr->op==TK_COLUMN
&& pExpr->iTable==pHint->iTabCur
- && sqlite3ColumnOfIndex(pHint->pIdx, pExpr->iColumn)<0
+ && tdsqlite3TableColumnToIndex(pHint->pIdx, pExpr->iColumn)<0
){
pWalker->eCode = 1;
}
@@ -128247,8 +144239,8 @@ static int codeCursorHintIsOrFunction(Walker *pWalker, Expr *pExpr){
pWalker->eCode = 1;
}else if( pExpr->op==TK_FUNCTION ){
int d1;
- char d2[3];
- if( 0==sqlite3IsLikeFunction(pWalker->pParse->db, pExpr, &d1, d2) ){
+ char d2[4];
+ if( 0==tdsqlite3IsLikeFunction(pWalker->pParse->db, pExpr, &d1, d2) ){
pWalker->eCode = 1;
}
}
@@ -128279,16 +144271,13 @@ static int codeCursorHintFixExpr(Walker *pWalker, Expr *pExpr){
struct CCurHint *pHint = pWalker->u.pCCurHint;
if( pExpr->op==TK_COLUMN ){
if( pExpr->iTable!=pHint->iTabCur ){
- Vdbe *v = pWalker->pParse->pVdbe;
int reg = ++pWalker->pParse->nMem; /* Register for column value */
- sqlite3ExprCodeGetColumnOfTable(
- v, pExpr->pTab, pExpr->iTable, pExpr->iColumn, reg
- );
+ tdsqlite3ExprCode(pWalker->pParse, pExpr, reg);
pExpr->op = TK_REGISTER;
pExpr->iTable = reg;
}else if( pHint->pIdx!=0 ){
pExpr->iTable = pHint->iIdxCur;
- pExpr->iColumn = sqlite3ColumnOfIndex(pHint->pIdx, pExpr->iColumn);
+ pExpr->iColumn = tdsqlite3TableColumnToIndex(pHint->pIdx, pExpr->iColumn);
assert( pExpr->iColumn>=0 );
}
}else if( pExpr->op==TK_AGG_FUNCTION ){
@@ -128314,7 +144303,7 @@ static void codeCursorHint(
WhereTerm *pEndRange /* Hint this end-of-scan boundary term if not NULL */
){
Parse *pParse = pWInfo->pParse;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
Vdbe *v = pParse->pVdbe;
Expr *pExpr = 0;
WhereLoop *pLoop = pLevel->pWLoop;
@@ -128369,7 +144358,7 @@ static void codeCursorHint(
){
sWalker.eCode = 0;
sWalker.xExprCallback = codeCursorHintIsOrFunction;
- sqlite3WalkExpr(&sWalker, pTerm->pExpr);
+ tdsqlite3WalkExpr(&sWalker, pTerm->pExpr);
if( sWalker.eCode ) continue;
}
}else{
@@ -128385,24 +144374,24 @@ static void codeCursorHint(
}
/* No subqueries or non-deterministic functions allowed */
- if( sqlite3ExprContainsSubquery(pTerm->pExpr) ) continue;
+ if( tdsqlite3ExprContainsSubquery(pTerm->pExpr) ) continue;
/* For an index scan, make sure referenced columns are actually in
** the index. */
if( sHint.pIdx!=0 ){
sWalker.eCode = 0;
sWalker.xExprCallback = codeCursorHintCheckExpr;
- sqlite3WalkExpr(&sWalker, pTerm->pExpr);
+ tdsqlite3WalkExpr(&sWalker, pTerm->pExpr);
if( sWalker.eCode ) continue;
}
/* If we survive all prior tests, that means this term is worth hinting */
- pExpr = sqlite3ExprAnd(db, pExpr, sqlite3ExprDup(db, pTerm->pExpr, 0));
+ pExpr = tdsqlite3ExprAnd(pParse, pExpr, tdsqlite3ExprDup(db, pTerm->pExpr, 0));
}
if( pExpr!=0 ){
sWalker.xExprCallback = codeCursorHintFixExpr;
- sqlite3WalkExpr(&sWalker, pExpr);
- sqlite3VdbeAddOp4(v, OP_CursorHint,
+ tdsqlite3WalkExpr(&sWalker, pExpr);
+ tdsqlite3VdbeAddOp4(v, OP_CursorHint,
(sHint.pIdx ? sHint.iIdxCur : sHint.iTabCur), 0, 0,
(const char*)pExpr, P4_EXPR);
}
@@ -128419,10 +144408,10 @@ static void codeCursorHint(
**
** Normally, this is just:
**
-** OP_Seek $iCur $iRowid
+** OP_DeferredSeek $iCur $iRowid
**
** However, if the scan currently being coded is a branch of an OR-loop and
-** the statement currently being coded is a SELECT, then P3 of the OP_Seek
+** the statement currently being coded is a SELECT, then P3 of OP_DeferredSeek
** is set to iIdxCur and P4 is set to point to an array of integers
** containing one entry for each column of the table cursor iCur is open
** on. For each table column, if the column is the i'th column of the
@@ -128441,20 +144430,25 @@ static void codeDeferredSeek(
assert( iIdxCur>0 );
assert( pIdx->aiColumn[pIdx->nColumn-1]==-1 );
- sqlite3VdbeAddOp3(v, OP_Seek, iIdxCur, 0, iCur);
+ pWInfo->bDeferredSeek = 1;
+ tdsqlite3VdbeAddOp3(v, OP_DeferredSeek, iIdxCur, 0, iCur);
if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)
- && DbMaskAllZero(sqlite3ParseToplevel(pParse)->writeMask)
+ && DbMaskAllZero(tdsqlite3ParseToplevel(pParse)->writeMask)
){
int i;
Table *pTab = pIdx->pTable;
- int *ai = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*(pTab->nCol+1));
+ int *ai = (int*)tdsqlite3DbMallocZero(pParse->db, sizeof(int)*(pTab->nCol+1));
if( ai ){
ai[0] = pTab->nCol;
for(i=0; i<pIdx->nColumn-1; i++){
+ int x1, x2;
assert( pIdx->aiColumn[i]<pTab->nCol );
- if( pIdx->aiColumn[i]>=0 ) ai[pIdx->aiColumn[i]+1] = i+1;
+ x1 = pIdx->aiColumn[i];
+ x2 = tdsqlite3TableColumnToStorage(pTab, x1);
+ testcase( x1!=x2 );
+ if( x1>=0 ) ai[x2+1] = i+1;
}
- sqlite3VdbeChangeP4(v, -1, (char*)ai, P4_INTARRAY);
+ tdsqlite3VdbeChangeP4(v, -1, (char*)ai, P4_INTARRAY);
}
}
}
@@ -128470,12 +144464,14 @@ static void codeDeferredSeek(
*/
static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){
assert( nReg>0 );
- if( sqlite3ExprIsVector(p) ){
+ if( p && tdsqlite3ExprIsVector(p) ){
#ifndef SQLITE_OMIT_SUBQUERY
if( (p->flags & EP_xIsSelect) ){
Vdbe *v = pParse->pVdbe;
- int iSelect = sqlite3CodeSubselect(pParse, p, 0, 0);
- sqlite3VdbeAddOp3(v, OP_Copy, iSelect, iReg, nReg-1);
+ int iSelect;
+ assert( p->op==TK_SELECT );
+ iSelect = tdsqlite3CodeSubselect(pParse, p);
+ tdsqlite3VdbeAddOp3(v, OP_Copy, iSelect, iReg, nReg-1);
}else
#endif
{
@@ -128483,12 +144479,176 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){
ExprList *pList = p->x.pList;
assert( nReg<=pList->nExpr );
for(i=0; i<nReg; i++){
- sqlite3ExprCode(pParse, pList->a[i].pExpr, iReg+i);
+ tdsqlite3ExprCode(pParse, pList->a[i].pExpr, iReg+i);
}
}
}else{
assert( nReg==1 );
- sqlite3ExprCode(pParse, p, iReg);
+ tdsqlite3ExprCode(pParse, p, iReg);
+ }
+}
+
+/* An instance of the IdxExprTrans object carries information about a
+** mapping from an expression on table columns into a column in an index
+** down through the Walker.
+*/
+typedef struct IdxExprTrans {
+ Expr *pIdxExpr; /* The index expression */
+ int iTabCur; /* The cursor of the corresponding table */
+ int iIdxCur; /* The cursor for the index */
+ int iIdxCol; /* The column for the index */
+ int iTabCol; /* The column for the table */
+ WhereInfo *pWInfo; /* Complete WHERE clause information */
+ tdsqlite3 *db; /* Database connection (for malloc()) */
+} IdxExprTrans;
+
+/*
+** Preserve pExpr on the WhereETrans list of the WhereInfo.
+*/
+static void preserveExpr(IdxExprTrans *pTrans, Expr *pExpr){
+ WhereExprMod *pNew;
+ pNew = tdsqlite3DbMallocRaw(pTrans->db, sizeof(*pNew));
+ if( pNew==0 ) return;
+ pNew->pNext = pTrans->pWInfo->pExprMods;
+ pTrans->pWInfo->pExprMods = pNew;
+ pNew->pExpr = pExpr;
+ memcpy(&pNew->orig, pExpr, sizeof(*pExpr));
+}
+
+/* The walker node callback used to transform matching expressions into
+** a reference to an index column for an index on an expression.
+**
+** If pExpr matches, then transform it into a reference to the index column
+** that contains the value of pExpr.
+*/
+static int whereIndexExprTransNode(Walker *p, Expr *pExpr){
+ IdxExprTrans *pX = p->u.pIdxTrans;
+ if( tdsqlite3ExprCompare(0, pExpr, pX->pIdxExpr, pX->iTabCur)==0 ){
+ preserveExpr(pX, pExpr);
+ pExpr->affExpr = tdsqlite3ExprAffinity(pExpr);
+ pExpr->op = TK_COLUMN;
+ pExpr->iTable = pX->iIdxCur;
+ pExpr->iColumn = pX->iIdxCol;
+ pExpr->y.pTab = 0;
+ testcase( ExprHasProperty(pExpr, EP_Skip) );
+ testcase( ExprHasProperty(pExpr, EP_Unlikely) );
+ ExprClearProperty(pExpr, EP_Skip|EP_Unlikely);
+ return WRC_Prune;
+ }else{
+ return WRC_Continue;
+ }
+}
+
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+/* A walker node callback that translates a column reference to a table
+** into a corresponding column reference of an index.
+*/
+static int whereIndexExprTransColumn(Walker *p, Expr *pExpr){
+ if( pExpr->op==TK_COLUMN ){
+ IdxExprTrans *pX = p->u.pIdxTrans;
+ if( pExpr->iTable==pX->iTabCur && pExpr->iColumn==pX->iTabCol ){
+ assert( pExpr->y.pTab!=0 );
+ preserveExpr(pX, pExpr);
+ pExpr->affExpr = tdsqlite3TableColumnAffinity(pExpr->y.pTab,pExpr->iColumn);
+ pExpr->iTable = pX->iIdxCur;
+ pExpr->iColumn = pX->iIdxCol;
+ pExpr->y.pTab = 0;
+ }
+ }
+ return WRC_Continue;
+}
+#endif /* SQLITE_OMIT_GENERATED_COLUMNS */
+
+/*
+** For an indexes on expression X, locate every instance of expression X
+** in pExpr and change that subexpression into a reference to the appropriate
+** column of the index.
+**
+** 2019-10-24: Updated to also translate references to a VIRTUAL column in
+** the table into references to the corresponding (stored) column of the
+** index.
+*/
+static void whereIndexExprTrans(
+ Index *pIdx, /* The Index */
+ int iTabCur, /* Cursor of the table that is being indexed */
+ int iIdxCur, /* Cursor of the index itself */
+ WhereInfo *pWInfo /* Transform expressions in this WHERE clause */
+){
+ int iIdxCol; /* Column number of the index */
+ ExprList *aColExpr; /* Expressions that are indexed */
+ Table *pTab;
+ Walker w;
+ IdxExprTrans x;
+ aColExpr = pIdx->aColExpr;
+ if( aColExpr==0 && !pIdx->bHasVCol ){
+ /* The index does not reference any expressions or virtual columns
+ ** so no translations are needed. */
+ return;
+ }
+ pTab = pIdx->pTable;
+ memset(&w, 0, sizeof(w));
+ w.u.pIdxTrans = &x;
+ x.iTabCur = iTabCur;
+ x.iIdxCur = iIdxCur;
+ x.pWInfo = pWInfo;
+ x.db = pWInfo->pParse->db;
+ for(iIdxCol=0; iIdxCol<pIdx->nColumn; iIdxCol++){
+ i16 iRef = pIdx->aiColumn[iIdxCol];
+ if( iRef==XN_EXPR ){
+ assert( aColExpr->a[iIdxCol].pExpr!=0 );
+ x.pIdxExpr = aColExpr->a[iIdxCol].pExpr;
+ if( tdsqlite3ExprIsConstant(x.pIdxExpr) ) continue;
+ w.xExprCallback = whereIndexExprTransNode;
+#ifndef SQLITE_OMIT_GENERATED_COLUMNS
+ }else if( iRef>=0
+ && (pTab->aCol[iRef].colFlags & COLFLAG_VIRTUAL)!=0
+ && (pTab->aCol[iRef].zColl==0
+ || tdsqlite3StrICmp(pTab->aCol[iRef].zColl, tdsqlite3StrBINARY)==0)
+ ){
+ /* Check to see if there are direct references to generated columns
+ ** that are contained in the index. Pulling the generated column
+ ** out of the index is an optimization only - the main table is always
+ ** available if the index cannot be used. To avoid unnecessary
+ ** complication, omit this optimization if the collating sequence for
+ ** the column is non-standard */
+ x.iTabCol = iRef;
+ w.xExprCallback = whereIndexExprTransColumn;
+#endif /* SQLITE_OMIT_GENERATED_COLUMNS */
+ }else{
+ continue;
+ }
+ x.iIdxCol = iIdxCol;
+ tdsqlite3WalkExpr(&w, pWInfo->pWhere);
+ tdsqlite3WalkExprList(&w, pWInfo->pOrderBy);
+ tdsqlite3WalkExprList(&w, pWInfo->pResultSet);
+ }
+}
+
+/*
+** The pTruth expression is always true because it is the WHERE clause
+** a partial index that is driving a query loop. Look through all of the
+** WHERE clause terms on the query, and if any of those terms must be
+** true because pTruth is true, then mark those WHERE clause terms as
+** coded.
+*/
+static void whereApplyPartialIndexConstraints(
+ Expr *pTruth,
+ int iTabCur,
+ WhereClause *pWC
+){
+ int i;
+ WhereTerm *pTerm;
+ while( pTruth->op==TK_AND ){
+ whereApplyPartialIndexConstraints(pTruth->pLeft, iTabCur, pWC);
+ pTruth = pTruth->pRight;
+ }
+ for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
+ Expr *pExpr;
+ if( pTerm->wtFlags & TERM_CODED ) continue;
+ pExpr = pTerm->pExpr;
+ if( tdsqlite3ExprCompare(0, pExpr, pTruth, iTabCur)==0 ){
+ pTerm->wtFlags |= TERM_CODED;
+ }
}
}
@@ -128496,42 +144656,54 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){
** Generate code for the start of the iLevel-th loop in the WHERE clause
** implementation described by pWInfo.
*/
-SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
+SQLITE_PRIVATE Bitmask tdsqlite3WhereCodeOneLoopStart(
+ Parse *pParse, /* Parsing context */
+ Vdbe *v, /* Prepared statement under construction */
WhereInfo *pWInfo, /* Complete information about the WHERE clause */
int iLevel, /* Which level of pWInfo->a[] should be coded */
+ WhereLevel *pLevel, /* The current level pointer */
Bitmask notReady /* Which tables are currently available */
){
int j, k; /* Loop counters */
int iCur; /* The VDBE cursor for the table */
int addrNxt; /* Where to jump to continue with the next IN case */
- int omitTable; /* True if we use the index only */
int bRev; /* True if we need to scan in reverse order */
- WhereLevel *pLevel; /* The where level to be coded */
WhereLoop *pLoop; /* The WhereLoop object being coded */
WhereClause *pWC; /* Decomposition of the entire WHERE clause */
WhereTerm *pTerm; /* A WHERE clause term */
- Parse *pParse; /* Parsing context */
- sqlite3 *db; /* Database connection */
- Vdbe *v; /* The prepared stmt under constructions */
+ tdsqlite3 *db; /* Database connection */
struct SrcList_item *pTabItem; /* FROM clause term being coded */
int addrBrk; /* Jump here to break out of the loop */
+ int addrHalt; /* addrBrk for the outermost loop */
int addrCont; /* Jump here to continue with next cycle */
int iRowidReg = 0; /* Rowid is stored in this register, if not zero */
int iReleaseReg = 0; /* Temp register to free before returning */
+ Index *pIdx = 0; /* Index used by loop (if any) */
+ int iLoop; /* Iteration of constraint generator loop */
- pParse = pWInfo->pParse;
- v = pParse->pVdbe;
pWC = &pWInfo->sWC;
db = pParse->db;
- pLevel = &pWInfo->a[iLevel];
pLoop = pLevel->pWLoop;
pTabItem = &pWInfo->pTabList->a[pLevel->iFrom];
iCur = pTabItem->iCursor;
- pLevel->notReady = notReady & ~sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur);
+ pLevel->notReady = notReady & ~tdsqlite3WhereGetMask(&pWInfo->sMaskSet, iCur);
bRev = (pWInfo->revMask>>iLevel)&1;
- omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0
- && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0;
VdbeModuleComment((v, "Begin WHERE-loop%d: %s",iLevel,pTabItem->pTab->zName));
+#if WHERETRACE_ENABLED /* 0x20800 */
+ if( tdsqlite3WhereTrace & 0x800 ){
+ tdsqlite3DebugPrintf("Coding level %d of %d: notReady=%llx iFrom=%d\n",
+ iLevel, pWInfo->nLevel, (u64)notReady, pLevel->iFrom);
+ tdsqlite3WhereLoopPrint(pLoop, pWC);
+ }
+ if( tdsqlite3WhereTrace & 0x20000 ){
+ if( iLevel==0 ){
+ tdsqlite3DebugPrintf("WHERE clause being coded:\n");
+ tdsqlite3TreeViewExpr(0, pWInfo->pWhere, 0);
+ }
+ tdsqlite3DebugPrintf("All WHERE-clause terms before coding:\n");
+ tdsqlite3WhereClausePrint(pWC);
+ }
+#endif
/* Create labels for the "break" and "continue" instructions
** for the current loop. Jump to addrBrk to break out of a loop.
@@ -128543,26 +144715,34 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** there are no IN operators in the constraints, the "addrNxt" label
** is the same as "addrBrk".
*/
- addrBrk = pLevel->addrBrk = pLevel->addrNxt = sqlite3VdbeMakeLabel(v);
- addrCont = pLevel->addrCont = sqlite3VdbeMakeLabel(v);
+ addrBrk = pLevel->addrBrk = pLevel->addrNxt = tdsqlite3VdbeMakeLabel(pParse);
+ addrCont = pLevel->addrCont = tdsqlite3VdbeMakeLabel(pParse);
/* If this is the right table of a LEFT OUTER JOIN, allocate and
** initialize a memory cell that records if this table matches any
** row of the left table of the join.
*/
+ assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)
+ || pLevel->iFrom>0 || (pTabItem[0].fg.jointype & JT_LEFT)==0
+ );
if( pLevel->iFrom>0 && (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){
pLevel->iLeftJoin = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Integer, 0, pLevel->iLeftJoin);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pLevel->iLeftJoin);
VdbeComment((v, "init LEFT JOIN no-match flag"));
}
+ /* Compute a safe address to jump to if we discover that the table for
+ ** this loop is empty and can never contribute content. */
+ for(j=iLevel; j>0 && pWInfo->a[j].iLeftJoin==0; j--){}
+ addrHalt = pWInfo->a[j].addrBrk;
+
/* Special case of a FROM clause subquery implemented as a co-routine */
if( pTabItem->fg.viaCoroutine ){
int regYield = pTabItem->regReturn;
- sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub);
- pLevel->p2 = sqlite3VdbeAddOp2(v, OP_Yield, regYield, addrBrk);
+ tdsqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub);
+ pLevel->p2 = tdsqlite3VdbeAddOp2(v, OP_Yield, regYield, addrBrk);
VdbeCoverage(v);
- VdbeComment((v, "next row of \"%s\"", pTabItem->pTab->zName));
+ VdbeComment((v, "next row of %s", pTabItem->pTab->zName));
pLevel->op = OP_Goto;
}else
@@ -128576,8 +144756,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
int nConstraint = pLoop->nLTerm;
int iIn; /* Counter for IN constraints */
- sqlite3ExprCachePush(pParse);
- iReg = sqlite3GetTempRange(pParse, nConstraint+2);
+ iReg = tdsqlite3GetTempRange(pParse, nConstraint+2);
addrNotFound = pLevel->addrBrk;
for(j=0; j<nConstraint; j++){
int iTarget = iReg+j+2;
@@ -128591,22 +144770,25 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
codeExprOrVector(pParse, pRight, iTarget, 1);
}
}
- sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg);
- sqlite3VdbeAddOp2(v, OP_Integer, nConstraint, iReg+1);
- sqlite3VdbeAddOp4(v, OP_VFilter, iCur, addrNotFound, iReg,
+ tdsqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, nConstraint, iReg+1);
+ tdsqlite3VdbeAddOp4(v, OP_VFilter, iCur, addrNotFound, iReg,
pLoop->u.vtab.idxStr,
- pLoop->u.vtab.needFree ? P4_MPRINTF : P4_STATIC);
+ pLoop->u.vtab.needFree ? P4_DYNAMIC : P4_STATIC);
VdbeCoverage(v);
pLoop->u.vtab.needFree = 0;
pLevel->p1 = iCur;
pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext;
- pLevel->p2 = sqlite3VdbeCurrentAddr(v);
+ pLevel->p2 = tdsqlite3VdbeCurrentAddr(v);
iIn = pLevel->u.in.nIn;
for(j=nConstraint-1; j>=0; j--){
pTerm = pLoop->aLTerm[j];
+ if( (pTerm->eOperator & WO_IN)!=0 ) iIn--;
if( j<16 && (pLoop->u.vtab.omitMask>>j)&1 ){
disableTerm(pLevel, pTerm);
- }else if( (pTerm->eOperator & WO_IN)!=0 ){
+ }else if( (pTerm->eOperator & WO_IN)!=0
+ && tdsqlite3ExprVectorSize(pTerm->pExpr->pLeft)==1
+ ){
Expr *pCompare; /* The comparison operator */
Expr *pRight; /* RHS of the comparison */
VdbeOp *pOp; /* Opcode to access the value of the IN constraint */
@@ -128617,39 +144799,39 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** encoding of the value in the register, so it *must* be reloaded. */
assert( pLevel->u.in.aInLoop!=0 || db->mallocFailed );
if( !db->mallocFailed ){
- assert( iIn>0 );
- pOp = sqlite3VdbeGetOp(v, pLevel->u.in.aInLoop[--iIn].addrInTop);
+ assert( iIn>=0 && iIn<pLevel->u.in.nIn );
+ pOp = tdsqlite3VdbeGetOp(v, pLevel->u.in.aInLoop[iIn].addrInTop);
assert( pOp->opcode==OP_Column || pOp->opcode==OP_Rowid );
assert( pOp->opcode!=OP_Column || pOp->p3==iReg+j+2 );
assert( pOp->opcode!=OP_Rowid || pOp->p2==iReg+j+2 );
testcase( pOp->opcode==OP_Rowid );
- sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3);
+ tdsqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3);
}
/* Generate code that will continue to the next row if
** the IN constraint is not satisfied */
- pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0, 0);
+ pCompare = tdsqlite3PExpr(pParse, TK_EQ, 0, 0);
assert( pCompare!=0 || db->mallocFailed );
if( pCompare ){
pCompare->pLeft = pTerm->pExpr->pLeft;
- pCompare->pRight = pRight = sqlite3Expr(db, TK_REGISTER, 0);
+ pCompare->pRight = pRight = tdsqlite3Expr(db, TK_REGISTER, 0);
if( pRight ){
pRight->iTable = iReg+j+2;
- sqlite3ExprIfFalse(pParse, pCompare, pLevel->addrCont, 0);
+ tdsqlite3ExprIfFalse(pParse, pCompare, pLevel->addrCont, 0);
}
pCompare->pLeft = 0;
- sqlite3ExprDelete(db, pCompare);
+ tdsqlite3ExprDelete(db, pCompare);
}
}
}
+ assert( iIn==0 || db->mallocFailed );
/* These registers need to be preserved in case there is an IN operator
** loop. So we could deallocate the registers here (and potentially
** reuse them later) if (pLoop->wsFlags & WHERE_IN_ABLE)==0. But it seems
** simpler and safer to simply not reuse the registers.
**
- ** sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2);
+ ** tdsqlite3ReleaseTempRange(pParse, iReg, nConstraint+2);
*/
- sqlite3ExprCachePop(pParse);
}else
#endif /* SQLITE_OMIT_VIRTUALTABLE */
@@ -128665,18 +144847,17 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
pTerm = pLoop->aLTerm[0];
assert( pTerm!=0 );
assert( pTerm->pExpr!=0 );
- assert( omitTable==0 );
testcase( pTerm->wtFlags & TERM_VIRTUAL );
iReleaseReg = ++pParse->nMem;
iRowidReg = codeEqualityTerm(pParse, pTerm, pLevel, 0, bRev, iReleaseReg);
- if( iRowidReg!=iReleaseReg ) sqlite3ReleaseTempReg(pParse, iReleaseReg);
+ if( iRowidReg!=iReleaseReg ) tdsqlite3ReleaseTempReg(pParse, iReleaseReg);
addrNxt = pLevel->addrNxt;
- sqlite3VdbeAddOp3(v, OP_SeekRowid, iCur, addrNxt, iRowidReg);
+ tdsqlite3VdbeAddOp3(v, OP_SeekRowid, iCur, addrNxt, iRowidReg);
VdbeCoverage(v);
- sqlite3ExprCacheAffinityChange(pParse, iRowidReg, 1);
- sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg);
- VdbeComment((v, "pk"));
pLevel->op = OP_Noop;
+ if( (pTerm->prereqAll & pLevel->notReady)==0 ){
+ pTerm->wtFlags |= TERM_CODED;
+ }
}else if( (pLoop->wsFlags & WHERE_IPK)!=0
&& (pLoop->wsFlags & WHERE_COLUMN_RANGE)!=0
){
@@ -128687,7 +144868,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
int memEndValue = 0;
WhereTerm *pStart, *pEnd;
- assert( omitTable==0 );
j = 0;
pStart = pEnd = 0;
if( pLoop->wsFlags & WHERE_BTM_LIMIT ) pStart = pLoop->aLTerm[j++];
@@ -128722,25 +144902,32 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
pX = pStart->pExpr;
assert( pX!=0 );
testcase( pStart->leftCursor!=iCur ); /* transitive constraints */
- if( sqlite3ExprIsVector(pX->pRight) ){
- r1 = rTemp = sqlite3GetTempReg(pParse);
+ if( tdsqlite3ExprIsVector(pX->pRight) ){
+ r1 = rTemp = tdsqlite3GetTempReg(pParse);
codeExprOrVector(pParse, pX->pRight, r1, 1);
- op = aMoveOp[(pX->op - TK_GT) | 0x0001];
+ testcase( pX->op==TK_GT );
+ testcase( pX->op==TK_GE );
+ testcase( pX->op==TK_LT );
+ testcase( pX->op==TK_LE );
+ op = aMoveOp[((pX->op - TK_GT - 1) & 0x3) | 0x1];
+ assert( pX->op!=TK_GT || op==OP_SeekGE );
+ assert( pX->op!=TK_GE || op==OP_SeekGE );
+ assert( pX->op!=TK_LT || op==OP_SeekLE );
+ assert( pX->op!=TK_LE || op==OP_SeekLE );
}else{
- r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp);
+ r1 = tdsqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp);
disableTerm(pLevel, pStart);
op = aMoveOp[(pX->op - TK_GT)];
}
- sqlite3VdbeAddOp3(v, op, iCur, addrBrk, r1);
+ tdsqlite3VdbeAddOp3(v, op, iCur, addrBrk, r1);
VdbeComment((v, "pk"));
VdbeCoverageIf(v, pX->op==TK_GT);
VdbeCoverageIf(v, pX->op==TK_LE);
VdbeCoverageIf(v, pX->op==TK_LT);
VdbeCoverageIf(v, pX->op==TK_GE);
- sqlite3ExprCacheAffinityChange(pParse, r1, 1);
- sqlite3ReleaseTempReg(pParse, rTemp);
+ tdsqlite3ReleaseTempReg(pParse, rTemp);
}else{
- sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrBrk);
+ tdsqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrHalt);
VdbeCoverageIf(v, bRev==0);
VdbeCoverageIf(v, bRev!=0);
}
@@ -128753,32 +144940,31 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
testcase( pEnd->wtFlags & TERM_VIRTUAL );
memEndValue = ++pParse->nMem;
codeExprOrVector(pParse, pX->pRight, memEndValue, 1);
- if( 0==sqlite3ExprIsVector(pX->pRight)
+ if( 0==tdsqlite3ExprIsVector(pX->pRight)
&& (pX->op==TK_LT || pX->op==TK_GT)
){
testOp = bRev ? OP_Le : OP_Ge;
}else{
testOp = bRev ? OP_Lt : OP_Gt;
}
- if( 0==sqlite3ExprIsVector(pX->pRight) ){
+ if( 0==tdsqlite3ExprIsVector(pX->pRight) ){
disableTerm(pLevel, pEnd);
}
}
- start = sqlite3VdbeCurrentAddr(v);
+ start = tdsqlite3VdbeCurrentAddr(v);
pLevel->op = bRev ? OP_Prev : OP_Next;
pLevel->p1 = iCur;
pLevel->p2 = start;
assert( pLevel->p5==0 );
if( testOp!=OP_Noop ){
iRowidReg = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Rowid, iCur, iRowidReg);
- sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg);
- sqlite3VdbeAddOp3(v, testOp, memEndValue, addrBrk, iRowidReg);
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, iCur, iRowidReg);
+ tdsqlite3VdbeAddOp3(v, testOp, memEndValue, addrBrk, iRowidReg);
VdbeCoverageIf(v, testOp==OP_Le);
VdbeCoverageIf(v, testOp==OP_Lt);
VdbeCoverageIf(v, testOp==OP_Ge);
VdbeCoverageIf(v, testOp==OP_Gt);
- sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC | SQLITE_JUMPIFNULL);
+ tdsqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC | SQLITE_JUMPIFNULL);
}
}else if( pLoop->wsFlags & WHERE_INDEXED ){
/* Case 4: A scan using an index.
@@ -128838,7 +145024,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
int endEq; /* True if range end uses ==, >= or <= */
int start_constraints; /* Start of range is constrained */
int nConstraint; /* Number of constraint terms */
- Index *pIdx; /* The index we will be using */
int iIdxCur; /* The VDBE cursor for the index */
int nExtraReg = 0; /* Number of extra registers needed */
int op; /* Instruction opcode */
@@ -128846,31 +145031,13 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
char *zEndAff = 0; /* Affinity for end of range constraint */
u8 bSeekPastNull = 0; /* True to seek past initial nulls */
u8 bStopAtNull = 0; /* Add condition to terminate at NULLs */
+ int omitTable; /* True if we use the index only */
+ int regBignull = 0; /* big-null flag register */
pIdx = pLoop->u.btree.pIndex;
iIdxCur = pLevel->iIdxCur;
assert( nEq>=pLoop->nSkip );
- /* If this loop satisfies a sort order (pOrderBy) request that
- ** was passed to this function to implement a "SELECT min(x) ..."
- ** query, then the caller will only allow the loop to run for
- ** a single iteration. This means that the first row returned
- ** should not have a NULL value stored in 'x'. If column 'x' is
- ** the first one after the nEq equality constraints in the index,
- ** this requires some special handling.
- */
- assert( pWInfo->pOrderBy==0
- || pWInfo->pOrderBy->nExpr==1
- || (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0 );
- if( (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)!=0
- && pWInfo->nOBSat>0
- && (pIdx->nKeyCol>nEq)
- ){
- assert( pLoop->nSkip==0 );
- bSeekPastNull = 1;
- nExtraReg = 1;
- }
-
/* Find any inequality constraint terms for the start and end
** of the range.
*/
@@ -128890,9 +145057,9 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
assert( pRangeStart!=0 ); /* LIKE opt constraints */
assert( pRangeStart->wtFlags & TERM_LIKEOPT ); /* occur in pairs */
pLevel->iLikeRepCntr = (u32)++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Integer, 1, (int)pLevel->iLikeRepCntr);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, (int)pLevel->iLikeRepCntr);
VdbeComment((v, "LIKE loop counter"));
- pLevel->addrLikeRep = sqlite3VdbeCurrentAddr(v);
+ pLevel->addrLikeRep = tdsqlite3VdbeCurrentAddr(v);
/* iLikeRepCntr actually stores 2x the counter register number. The
** bottom bit indicates whether the search order is ASC or DESC. */
testcase( bRev );
@@ -128911,6 +145078,25 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
}
assert( pRangeEnd==0 || (pRangeEnd->wtFlags & TERM_VNULL)==0 );
+ /* If the WHERE_BIGNULL_SORT flag is set, then index column nEq uses
+ ** a non-default "big-null" sort (either ASC NULLS LAST or DESC NULLS
+ ** FIRST). In both cases separate ordered scans are made of those
+ ** index entries for which the column is null and for those for which
+ ** it is not. For an ASC sort, the non-NULL entries are scanned first.
+ ** For DESC, NULL entries are scanned first.
+ */
+ if( (pLoop->wsFlags & (WHERE_TOP_LIMIT|WHERE_BTM_LIMIT))==0
+ && (pLoop->wsFlags & WHERE_BIGNULL_SORT)!=0
+ ){
+ assert( bSeekPastNull==0 && nExtraReg==0 && nBtm==0 && nTop==0 );
+ assert( pRangeEnd==0 && pRangeStart==0 );
+ testcase( pLoop->nSkip>0 );
+ nExtraReg = 1;
+ bSeekPastNull = 1;
+ pLevel->regBignull = regBignull = ++pParse->nMem;
+ pLevel->addrBignull = tdsqlite3VdbeMakeLabel(pParse);
+ }
+
/* If we are doing a reverse order scan on an ascending index, or
** a forward order scan on a descending index, interchange the
** start and end terms (pRangeStart and pRangeEnd).
@@ -128929,11 +145115,11 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
*/
codeCursorHint(pTabItem, pWInfo, pLevel, pRangeEnd);
regBase = codeAllEqualityTerms(pParse,pLevel,bRev,nExtraReg,&zStartAff);
- assert( zStartAff==0 || sqlite3Strlen30(zStartAff)>=nEq );
+ assert( zStartAff==0 || tdsqlite3Strlen30(zStartAff)>=nEq );
if( zStartAff && nTop ){
- zEndAff = sqlite3DbStrDup(db, &zStartAff[nEq]);
+ zEndAff = tdsqlite3DbStrDup(db, &zStartAff[nEq]);
}
- addrNxt = pLevel->addrNxt;
+ addrNxt = (regBignull ? pLevel->addrBignull : pLevel->addrNxt);
testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 );
testcase( pRangeStart && (pRangeStart->eOperator & WO_GE)!=0 );
@@ -128950,9 +145136,9 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
codeExprOrVector(pParse, pRight, regBase+nEq, nBtm);
whereLikeOptimizationStringFixup(v, pLevel, pRangeStart);
if( (pRangeStart->wtFlags & TERM_VNULL)==0
- && sqlite3ExprCanBeNull(pRight)
+ && tdsqlite3ExprCanBeNull(pRight)
){
- sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt);
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt);
VdbeCoverage(v);
}
if( zStartAff ){
@@ -128960,17 +145146,21 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
}
nConstraint += nBtm;
testcase( pRangeStart->wtFlags & TERM_VIRTUAL );
- if( sqlite3ExprIsVector(pRight)==0 ){
+ if( tdsqlite3ExprIsVector(pRight)==0 ){
disableTerm(pLevel, pRangeStart);
}else{
startEq = 1;
}
bSeekPastNull = 0;
}else if( bSeekPastNull ){
- sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq);
- nConstraint++;
startEq = 0;
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq);
+ start_constraints = 1;
+ nConstraint++;
+ }else if( regBignull ){
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq);
start_constraints = 1;
+ nConstraint++;
}
codeApplyAffinity(pParse, regBase, nConstraint - bSeekPastNull, zStartAff);
if( pLoop->nSkip>0 && nConstraint==pLoop->nSkip ){
@@ -128978,9 +145168,17 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** above has already left the cursor sitting on the correct row,
** so no further seeking is needed */
}else{
+ if( pLoop->wsFlags & WHERE_IN_EARLYOUT ){
+ tdsqlite3VdbeAddOp1(v, OP_SeekHit, iIdxCur);
+ }
+ if( regBignull ){
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, regBignull);
+ VdbeComment((v, "NULL-scan pass ctr"));
+ }
+
op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev];
assert( op!=0 );
- sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint);
+ tdsqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint);
VdbeCoverage(v);
VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind );
VdbeCoverageIf(v, op==OP_Last); testcase( op==OP_Last );
@@ -128988,6 +145186,23 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
VdbeCoverageIf(v, op==OP_SeekGE); testcase( op==OP_SeekGE );
VdbeCoverageIf(v, op==OP_SeekLE); testcase( op==OP_SeekLE );
VdbeCoverageIf(v, op==OP_SeekLT); testcase( op==OP_SeekLT );
+
+ assert( bSeekPastNull==0 || bStopAtNull==0 );
+ if( regBignull ){
+ assert( bSeekPastNull==1 || bStopAtNull==1 );
+ assert( bSeekPastNull==!bStopAtNull );
+ assert( bStopAtNull==startEq );
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, tdsqlite3VdbeCurrentAddr(v)+2);
+ op = aStartOp[(nConstraint>1)*4 + 2 + bRev];
+ tdsqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase,
+ nConstraint-startEq);
+ VdbeCoverage(v);
+ VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind );
+ VdbeCoverageIf(v, op==OP_Last); testcase( op==OP_Last );
+ VdbeCoverageIf(v, op==OP_SeekGE); testcase( op==OP_SeekGE );
+ VdbeCoverageIf(v, op==OP_SeekLE); testcase( op==OP_SeekLE );
+ assert( op==OP_Rewind || op==OP_Last || op==OP_SeekGE || op==OP_SeekLE);
+ }
}
/* Load the value for the inequality constraint at the end of the
@@ -128996,13 +145211,12 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
nConstraint = nEq;
if( pRangeEnd ){
Expr *pRight = pRangeEnd->pExpr->pRight;
- sqlite3ExprCacheRemove(pParse, regBase+nEq, 1);
codeExprOrVector(pParse, pRight, regBase+nEq, nTop);
whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd);
if( (pRangeEnd->wtFlags & TERM_VNULL)==0
- && sqlite3ExprCanBeNull(pRight)
+ && tdsqlite3ExprCanBeNull(pRight)
){
- sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt);
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt);
VdbeCoverage(v);
}
if( zEndAff ){
@@ -129014,56 +145228,129 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
nConstraint += nTop;
testcase( pRangeEnd->wtFlags & TERM_VIRTUAL );
- if( sqlite3ExprIsVector(pRight)==0 ){
+ if( tdsqlite3ExprIsVector(pRight)==0 ){
disableTerm(pLevel, pRangeEnd);
}else{
endEq = 1;
}
}else if( bStopAtNull ){
- sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq);
- endEq = 0;
+ if( regBignull==0 ){
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq);
+ endEq = 0;
+ }
nConstraint++;
}
- sqlite3DbFree(db, zStartAff);
- sqlite3DbFree(db, zEndAff);
+ tdsqlite3DbFree(db, zStartAff);
+ tdsqlite3DbFree(db, zEndAff);
/* Top of the loop body */
- pLevel->p2 = sqlite3VdbeCurrentAddr(v);
+ pLevel->p2 = tdsqlite3VdbeCurrentAddr(v);
/* Check if the index cursor is past the end of the range. */
if( nConstraint ){
+ if( regBignull ){
+ /* Except, skip the end-of-range check while doing the NULL-scan */
+ tdsqlite3VdbeAddOp2(v, OP_IfNot, regBignull, tdsqlite3VdbeCurrentAddr(v)+3);
+ VdbeComment((v, "If NULL-scan 2nd pass"));
+ VdbeCoverage(v);
+ }
op = aEndOp[bRev*2 + endEq];
- sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint);
+ tdsqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint);
testcase( op==OP_IdxGT ); VdbeCoverageIf(v, op==OP_IdxGT );
testcase( op==OP_IdxGE ); VdbeCoverageIf(v, op==OP_IdxGE );
testcase( op==OP_IdxLT ); VdbeCoverageIf(v, op==OP_IdxLT );
testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE );
}
+ if( regBignull ){
+ /* During a NULL-scan, check to see if we have reached the end of
+ ** the NULLs */
+ assert( bSeekPastNull==!bStopAtNull );
+ assert( bSeekPastNull+bStopAtNull==1 );
+ assert( nConstraint+bSeekPastNull>0 );
+ tdsqlite3VdbeAddOp2(v, OP_If, regBignull, tdsqlite3VdbeCurrentAddr(v)+2);
+ VdbeComment((v, "If NULL-scan 1st pass"));
+ VdbeCoverage(v);
+ op = aEndOp[bRev*2 + bSeekPastNull];
+ tdsqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase,
+ nConstraint+bSeekPastNull);
+ testcase( op==OP_IdxGT ); VdbeCoverageIf(v, op==OP_IdxGT );
+ testcase( op==OP_IdxGE ); VdbeCoverageIf(v, op==OP_IdxGE );
+ testcase( op==OP_IdxLT ); VdbeCoverageIf(v, op==OP_IdxLT );
+ testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE );
+ }
+
+ if( pLoop->wsFlags & WHERE_IN_EARLYOUT ){
+ tdsqlite3VdbeAddOp2(v, OP_SeekHit, iIdxCur, 1);
+ }
/* Seek the table cursor, if required */
+ omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0
+ && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0;
if( omitTable ){
/* pIdx is a covering index. No need to access the main table. */
}else if( HasRowid(pIdx->pTable) ){
- if( (pWInfo->wctrlFlags & WHERE_SEEK_TABLE)!=0 ){
+ if( (pWInfo->wctrlFlags & WHERE_SEEK_TABLE)
+ || ( (pWInfo->wctrlFlags & WHERE_SEEK_UNIQ_TABLE)!=0
+ && (pWInfo->eOnePass==ONEPASS_SINGLE || pLoop->nLTerm==0) )
+ ){
iRowidReg = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg);
- sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg);
- sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, iRowidReg);
+ tdsqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg);
+ tdsqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, iRowidReg);
VdbeCoverage(v);
}else{
codeDeferredSeek(pWInfo, pIdx, iCur, iIdxCur);
}
}else if( iCur!=iIdxCur ){
- Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable);
- iRowidReg = sqlite3GetTempRange(pParse, pPk->nKeyCol);
+ Index *pPk = tdsqlite3PrimaryKeyIndex(pIdx->pTable);
+ iRowidReg = tdsqlite3GetTempRange(pParse, pPk->nKeyCol);
for(j=0; j<pPk->nKeyCol; j++){
- k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]);
- sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, iRowidReg+j);
+ k = tdsqlite3TableColumnToIndex(pIdx, pPk->aiColumn[j]);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, iRowidReg+j);
}
- sqlite3VdbeAddOp4Int(v, OP_NotFound, iCur, addrCont,
+ tdsqlite3VdbeAddOp4Int(v, OP_NotFound, iCur, addrCont,
iRowidReg, pPk->nKeyCol); VdbeCoverage(v);
}
+ if( pLevel->iLeftJoin==0 ){
+ /* If pIdx is an index on one or more expressions, then look through
+ ** all the expressions in pWInfo and try to transform matching expressions
+ ** into reference to index columns. Also attempt to translate references
+ ** to virtual columns in the table into references to (stored) columns
+ ** of the index.
+ **
+ ** Do not do this for the RHS of a LEFT JOIN. This is because the
+ ** expression may be evaluated after OP_NullRow has been executed on
+ ** the cursor. In this case it is important to do the full evaluation,
+ ** as the result of the expression may not be NULL, even if all table
+ ** column values are. https://www.sqlite.org/src/info/7fa8049685b50b5a
+ **
+ ** Also, do not do this when processing one index an a multi-index
+ ** OR clause, since the transformation will become invalid once we
+ ** move forward to the next index.
+ ** https://sqlite.org/src/info/4e8e4857d32d401f
+ */
+ if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){
+ whereIndexExprTrans(pIdx, iCur, iIdxCur, pWInfo);
+ }
+
+ /* If a partial index is driving the loop, try to eliminate WHERE clause
+ ** terms from the query that must be true due to the WHERE clause of
+ ** the partial index.
+ **
+ ** 2019-11-02 ticket 623eff57e76d45f6: This optimization does not work
+ ** for a LEFT JOIN.
+ */
+ if( pIdx->pPartIdxWhere ){
+ whereApplyPartialIndexConstraints(pIdx->pPartIdxWhere, iCur, pWC);
+ }
+ }else{
+ testcase( pIdx->pPartIdxWhere );
+ /* The following assert() is not a requirement, merely an observation:
+ ** The OR-optimization doesn't work for the right hand table of
+ ** a LEFT JOIN: */
+ assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 );
+ }
+
/* Record the instruction used to terminate the loop. */
if( pLoop->wsFlags & WHERE_ONEROW ){
pLevel->op = OP_Noop;
@@ -129079,6 +145366,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
}else{
assert( pLevel->p5==0 );
}
+ if( omitTable ) pIdx = 0;
}else
#ifndef SQLITE_OMIT_OR_OPTIMIZATION
@@ -129104,10 +145392,10 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** into the RowSet. If it is already present, control skips the
** Gosub opcode and jumps straight to the code generated by WhereEnd().
**
- ** sqlite3WhereBegin(<term>)
+ ** tdsqlite3WhereBegin(<term>)
** RowSetTest # Insert rowid into rowset
** Gosub 2 A
- ** sqlite3WhereEnd()
+ ** tdsqlite3WhereEnd()
**
** Following the above, code to terminate the loop. Label A, the target
** of the Gosub above, jumps to the instruction right after the Goto.
@@ -129134,7 +145422,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
int regReturn = ++pParse->nMem; /* Register used with OP_Gosub */
int regRowset = 0; /* Register for RowSet object */
int regRowid = 0; /* Register holding rowid */
- int iLoopBody = sqlite3VdbeMakeLabel(v); /* Start of loop body */
+ int iLoopBody = tdsqlite3VdbeMakeLabel(pParse);/* Start of loop body */
int iRetInit; /* Address of regReturn init */
int untestedTerms = 0; /* Some terms not completely tested */
int ii; /* Loop counter */
@@ -129152,13 +145440,13 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
/* Set up a new SrcList in pOrTab containing the table being scanned
** by this loop in the a[0] slot and all notReady tables in a[1..] slots.
- ** This becomes the SrcList in the recursive call to sqlite3WhereBegin().
+ ** This becomes the SrcList in the recursive call to tdsqlite3WhereBegin().
*/
if( pWInfo->nLevel>1 ){
int nNotReady; /* The number of notReady tables */
struct SrcList_item *origSrc; /* Original list of tables */
nNotReady = pWInfo->nLevel - iLevel - 1;
- pOrTab = sqlite3StackAllocRaw(db,
+ pOrTab = tdsqlite3StackAllocRaw(db,
sizeof(*pOrTab)+ nNotReady*sizeof(pOrTab->a[0]));
if( pOrTab==0 ) return notReady;
pOrTab->nAlloc = (u8)(nNotReady + 1);
@@ -129187,21 +145475,21 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
if( (pWInfo->wctrlFlags & WHERE_DUPLICATES_OK)==0 ){
if( HasRowid(pTab) ){
regRowset = ++pParse->nMem;
- sqlite3VdbeAddOp2(v, OP_Null, 0, regRowset);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, regRowset);
}else{
- Index *pPk = sqlite3PrimaryKeyIndex(pTab);
+ Index *pPk = tdsqlite3PrimaryKeyIndex(pTab);
regRowset = pParse->nTab++;
- sqlite3VdbeAddOp2(v, OP_OpenEphemeral, regRowset, pPk->nKeyCol);
- sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+ tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, regRowset, pPk->nKeyCol);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pPk);
}
regRowid = ++pParse->nMem;
}
- iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn);
+ iRetInit = tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn);
/* If the original WHERE clause is z of the form: (x1 OR x2 OR ...) AND y
** Then for every term xN, evaluate as the subexpression: xN AND z
** That way, terms in y that are factored into the disjunction will
- ** be picked up by the recursive calls to sqlite3WhereBegin() below.
+ ** be picked up by the recursive calls to tdsqlite3WhereBegin() below.
**
** Actually, each subexpression is converted to "xN AND w" where w is
** the "interesting" terms of z - terms that did not originate in the
@@ -129217,17 +145505,21 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
for(iTerm=0; iTerm<pWC->nTerm; iTerm++){
Expr *pExpr = pWC->a[iTerm].pExpr;
if( &pWC->a[iTerm] == pTerm ) continue;
- if( ExprHasProperty(pExpr, EP_FromJoin) ) continue;
testcase( pWC->a[iTerm].wtFlags & TERM_VIRTUAL );
testcase( pWC->a[iTerm].wtFlags & TERM_CODED );
if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED))!=0 ) continue;
if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue;
testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO );
- pExpr = sqlite3ExprDup(db, pExpr, 0);
- pAndExpr = sqlite3ExprAnd(db, pAndExpr, pExpr);
+ pExpr = tdsqlite3ExprDup(db, pExpr, 0);
+ pAndExpr = tdsqlite3ExprAnd(pParse, pAndExpr, pExpr);
}
if( pAndExpr ){
- pAndExpr = sqlite3PExpr(pParse, TK_AND|TKFLG_DONTFOLD, 0, pAndExpr, 0);
+ /* The extra 0x10000 bit on the opcode is masked off and does not
+ ** become part of the new Expr.op. However, it does make the
+ ** op==TK_AND comparison inside of tdsqlite3PExpr() false, and this
+ ** prevents tdsqlite3PExpr() from implementing AND short-circuit
+ ** optimization, which we do not want here. */
+ pAndExpr = tdsqlite3PExpr(pParse, TK_AND|0x10000, 0, pAndExpr);
}
}
@@ -129236,27 +145528,32 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** sub-WHERE clause is to to invoke the main loop body as a subroutine.
*/
wctrlFlags = WHERE_OR_SUBCLAUSE | (pWInfo->wctrlFlags & WHERE_SEEK_TABLE);
+ ExplainQueryPlan((pParse, 1, "MULTI-INDEX OR"));
for(ii=0; ii<pOrWc->nTerm; ii++){
WhereTerm *pOrTerm = &pOrWc->a[ii];
if( pOrTerm->leftCursor==iCur || (pOrTerm->eOperator & WO_AND)!=0 ){
WhereInfo *pSubWInfo; /* Info for single OR-term scan */
Expr *pOrExpr = pOrTerm->pExpr; /* Current OR clause term */
int jmp1 = 0; /* Address of jump operation */
- if( pAndExpr && !ExprHasProperty(pOrExpr, EP_FromJoin) ){
+ testcase( (pTabItem[0].fg.jointype & JT_LEFT)!=0
+ && !ExprHasProperty(pOrExpr, EP_FromJoin)
+ ); /* See TH3 vtab25.400 and ticket 614b25314c766238 */
+ if( pAndExpr ){
pAndExpr->pLeft = pOrExpr;
pOrExpr = pAndExpr;
}
/* Loop through table entries that match term pOrTerm. */
+ ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1));
WHERETRACE(0xffff, ("Subplan for OR-clause:\n"));
- pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0,
+ pSubWInfo = tdsqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0,
wctrlFlags, iCovCur);
assert( pSubWInfo || pParse->nErr || db->mallocFailed );
if( pSubWInfo ){
WhereLoop *pSubLoop;
- int addrExplain = sqlite3WhereExplainOneScan(
- pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom, 0
+ int addrExplain = tdsqlite3WhereExplainOneScan(
+ pParse, pOrTab, &pSubWInfo->a[0], 0
);
- sqlite3WhereAddScanStatus(v, pOrTab, &pSubWInfo->a[0], addrExplain);
+ tdsqlite3WhereAddScanStatus(v, pOrTab, &pSubWInfo->a[0], addrExplain);
/* This is the sub-WHERE clause body. First skip over
** duplicate rows from prior sub-WHERE clauses, and record the
@@ -129264,23 +145561,23 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** row will be skipped in subsequent sub-WHERE clauses.
*/
if( (pWInfo->wctrlFlags & WHERE_DUPLICATES_OK)==0 ){
- int r;
int iSet = ((ii==pOrWc->nTerm-1)?-1:ii);
if( HasRowid(pTab) ){
- r = sqlite3ExprCodeGetColumn(pParse, pTab, -1, iCur, regRowid, 0);
- jmp1 = sqlite3VdbeAddOp4Int(v, OP_RowSetTest, regRowset, 0,
- r,iSet);
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, -1, regRowid);
+ jmp1 = tdsqlite3VdbeAddOp4Int(v, OP_RowSetTest, regRowset, 0,
+ regRowid, iSet);
VdbeCoverage(v);
}else{
- Index *pPk = sqlite3PrimaryKeyIndex(pTab);
+ Index *pPk = tdsqlite3PrimaryKeyIndex(pTab);
int nPk = pPk->nKeyCol;
int iPk;
+ int r;
/* Read the PK into an array of temp registers. */
- r = sqlite3GetTempRange(pParse, nPk);
+ r = tdsqlite3GetTempRange(pParse, nPk);
for(iPk=0; iPk<nPk; iPk++){
int iCol = pPk->aiColumn[iPk];
- sqlite3ExprCodeGetColumnToReg(pParse, pTab, iCol, iCur, r+iPk);
+ tdsqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+iPk);
}
/* Check if the temp table already contains this key. If so,
@@ -129295,26 +145592,27 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
** need to insert the key into the temp table, as it will never
** be tested for. */
if( iSet ){
- jmp1 = sqlite3VdbeAddOp4Int(v, OP_Found, regRowset, 0, r, nPk);
+ jmp1 = tdsqlite3VdbeAddOp4Int(v, OP_Found, regRowset, 0, r, nPk);
VdbeCoverage(v);
}
if( iSet>=0 ){
- sqlite3VdbeAddOp3(v, OP_MakeRecord, r, nPk, regRowid);
- sqlite3VdbeAddOp3(v, OP_IdxInsert, regRowset, regRowid, 0);
- if( iSet ) sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, r, nPk, regRowid);
+ tdsqlite3VdbeAddOp4Int(v, OP_IdxInsert, regRowset, regRowid,
+ r, nPk);
+ if( iSet ) tdsqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
}
/* Release the array of temp registers */
- sqlite3ReleaseTempRange(pParse, r, nPk);
+ tdsqlite3ReleaseTempRange(pParse, r, nPk);
}
}
/* Invoke the main loop body as a subroutine */
- sqlite3VdbeAddOp2(v, OP_Gosub, regReturn, iLoopBody);
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, regReturn, iLoopBody);
/* Jump here (skipping the main loop body subroutine) if the
** current sub-WHERE row is a duplicate from prior sub-WHEREs. */
- if( jmp1 ) sqlite3VdbeJumpHere(v, jmp1);
+ if( jmp1 ) tdsqlite3VdbeJumpHere(v, jmp1);
/* The pSubWInfo->untestedTerms flag means that this OR term
** contained one or more AND term from a notReady table. The
@@ -129325,10 +145623,10 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
/* If all of the OR-connected terms are optimized using the same
** index, and the index is opened using the same cursor number
- ** by each call to sqlite3WhereBegin() made by this loop, it may
+ ** by each call to tdsqlite3WhereBegin() made by this loop, it may
** be possible to use that index as a covering index.
**
- ** If the call to sqlite3WhereBegin() above resulted in a scan that
+ ** If the call to tdsqlite3WhereBegin() above resulted in a scan that
** uses an index, and this is either the first OR-connected term
** processed or the index is the same as that used by all previous
** terms, set pCov to the candidate covering index. Otherwise, set
@@ -129348,21 +145646,23 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
}
/* Finish the loop through table entries that match term pOrTerm. */
- sqlite3WhereEnd(pSubWInfo);
+ tdsqlite3WhereEnd(pSubWInfo);
+ ExplainQueryPlanPop(pParse);
}
}
}
+ ExplainQueryPlanPop(pParse);
pLevel->u.pCovidx = pCov;
if( pCov ) pLevel->iIdxCur = iCovCur;
if( pAndExpr ){
pAndExpr->pLeft = 0;
- sqlite3ExprDelete(db, pAndExpr);
+ tdsqlite3ExprDelete(db, pAndExpr);
}
- sqlite3VdbeChangeP1(v, iRetInit, sqlite3VdbeCurrentAddr(v));
- sqlite3VdbeGoto(v, pLevel->addrBrk);
- sqlite3VdbeResolveLabel(v, iLoopBody);
+ tdsqlite3VdbeChangeP1(v, iRetInit, tdsqlite3VdbeCurrentAddr(v));
+ tdsqlite3VdbeGoto(v, pLevel->addrBrk);
+ tdsqlite3VdbeResolveLabel(v, iLoopBody);
- if( pWInfo->nLevel>1 ) sqlite3StackFree(db, pOrTab);
+ if( pWInfo->nLevel>1 ){ tdsqlite3StackFree(db, pOrTab); }
if( !untestedTerms ) disableTerm(pLevel, pTerm);
}else
#endif /* SQLITE_OMIT_OR_OPTIMIZATION */
@@ -129382,7 +145682,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
codeCursorHint(pTabItem, pWInfo, pLevel, 0);
pLevel->op = aStep[bRev];
pLevel->p1 = iCur;
- pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrBrk);
+ pLevel->p2 = 1 + tdsqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrHalt);
VdbeCoverageIf(v, bRev==0);
VdbeCoverageIf(v, bRev!=0);
pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP;
@@ -129390,48 +145690,86 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
}
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
- pLevel->addrVisit = sqlite3VdbeCurrentAddr(v);
+ pLevel->addrVisit = tdsqlite3VdbeCurrentAddr(v);
#endif
/* Insert code to test every subexpression that can be completely
** computed using the current set of tables.
+ **
+ ** This loop may run between one and three times, depending on the
+ ** constraints to be generated. The value of stack variable iLoop
+ ** determines the constraints coded by each iteration, as follows:
+ **
+ ** iLoop==1: Code only expressions that are entirely covered by pIdx.
+ ** iLoop==2: Code remaining expressions that do not contain correlated
+ ** sub-queries.
+ ** iLoop==3: Code all remaining expressions.
+ **
+ ** An effort is made to skip unnecessary iterations of the loop.
*/
- for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){
- Expr *pE;
- int skipLikeAddr = 0;
- testcase( pTerm->wtFlags & TERM_VIRTUAL );
- testcase( pTerm->wtFlags & TERM_CODED );
- if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue;
- if( (pTerm->prereqAll & pLevel->notReady)!=0 ){
- testcase( pWInfo->untestedTerms==0
- && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)!=0 );
- pWInfo->untestedTerms = 1;
- continue;
- }
- pE = pTerm->pExpr;
- assert( pE!=0 );
- if( pLevel->iLeftJoin && !ExprHasProperty(pE, EP_FromJoin) ){
- continue;
- }
- if( pTerm->wtFlags & TERM_LIKECOND ){
- /* If the TERM_LIKECOND flag is set, that means that the range search
- ** is sufficient to guarantee that the LIKE operator is true, so we
- ** can skip the call to the like(A,B) function. But this only works
- ** for strings. So do not skip the call to the function on the pass
- ** that compares BLOBs. */
+ iLoop = (pIdx ? 1 : 2);
+ do{
+ int iNext = 0; /* Next value for iLoop */
+ for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){
+ Expr *pE;
+ int skipLikeAddr = 0;
+ testcase( pTerm->wtFlags & TERM_VIRTUAL );
+ testcase( pTerm->wtFlags & TERM_CODED );
+ if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue;
+ if( (pTerm->prereqAll & pLevel->notReady)!=0 ){
+ testcase( pWInfo->untestedTerms==0
+ && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)!=0 );
+ pWInfo->untestedTerms = 1;
+ continue;
+ }
+ pE = pTerm->pExpr;
+ assert( pE!=0 );
+ if( (pTabItem->fg.jointype&JT_LEFT) && !ExprHasProperty(pE,EP_FromJoin) ){
+ continue;
+ }
+
+ if( iLoop==1 && !tdsqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){
+ iNext = 2;
+ continue;
+ }
+ if( iLoop<3 && (pTerm->wtFlags & TERM_VARSELECT) ){
+ if( iNext==0 ) iNext = 3;
+ continue;
+ }
+
+ if( (pTerm->wtFlags & TERM_LIKECOND)!=0 ){
+ /* If the TERM_LIKECOND flag is set, that means that the range search
+ ** is sufficient to guarantee that the LIKE operator is true, so we
+ ** can skip the call to the like(A,B) function. But this only works
+ ** for strings. So do not skip the call to the function on the pass
+ ** that compares BLOBs. */
#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS
- continue;
+ continue;
#else
- u32 x = pLevel->iLikeRepCntr;
- assert( x>0 );
- skipLikeAddr = sqlite3VdbeAddOp1(v, (x&1)? OP_IfNot : OP_If, (int)(x>>1));
- VdbeCoverage(v);
+ u32 x = pLevel->iLikeRepCntr;
+ if( x>0 ){
+ skipLikeAddr = tdsqlite3VdbeAddOp1(v, (x&1)?OP_IfNot:OP_If,(int)(x>>1));
+ VdbeCoverageIf(v, (x&1)==1);
+ VdbeCoverageIf(v, (x&1)==0);
+ }
+#endif
+ }
+#ifdef WHERETRACE_ENABLED /* 0xffff */
+ if( tdsqlite3WhereTrace ){
+ VdbeNoopComment((v, "WhereTerm[%d] (%p) priority=%d",
+ pWC->nTerm-j, pTerm, iLoop));
+ }
+ if( tdsqlite3WhereTrace & 0x800 ){
+ tdsqlite3DebugPrintf("Coding auxiliary constraint:\n");
+ tdsqlite3WhereTermPrint(pTerm, pWC->nTerm-j);
+ }
#endif
+ tdsqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL);
+ if( skipLikeAddr ) tdsqlite3VdbeJumpHere(v, skipLikeAddr);
+ pTerm->wtFlags |= TERM_CODED;
}
- sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL);
- if( skipLikeAddr ) sqlite3VdbeJumpHere(v, skipLikeAddr);
- pTerm->wtFlags |= TERM_CODED;
- }
+ iLoop = iNext;
+ }while( iLoop>0 );
/* Insert code to test for implied constraints based on transitivity
** of the "==" operator.
@@ -129448,31 +145786,42 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue;
if( (pTerm->eOperator & WO_EQUIV)==0 ) continue;
if( pTerm->leftCursor!=iCur ) continue;
- if( pLevel->iLeftJoin ) continue;
+ if( pTabItem->fg.jointype & JT_LEFT ) continue;
pE = pTerm->pExpr;
+#ifdef WHERETRACE_ENABLED /* 0x800 */
+ if( tdsqlite3WhereTrace & 0x800 ){
+ tdsqlite3DebugPrintf("Coding transitive constraint:\n");
+ tdsqlite3WhereTermPrint(pTerm, pWC->nTerm-j);
+ }
+#endif
assert( !ExprHasProperty(pE, EP_FromJoin) );
assert( (pTerm->prereqRight & pLevel->notReady)!=0 );
- pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.leftColumn, notReady,
+ pAlt = tdsqlite3WhereFindTerm(pWC, iCur, pTerm->u.leftColumn, notReady,
WO_EQ|WO_IN|WO_IS, 0);
if( pAlt==0 ) continue;
if( pAlt->wtFlags & (TERM_CODED) ) continue;
+ if( (pAlt->eOperator & WO_IN)
+ && (pAlt->pExpr->flags & EP_xIsSelect)
+ && (pAlt->pExpr->x.pSelect->pEList->nExpr>1)
+ ){
+ continue;
+ }
testcase( pAlt->eOperator & WO_EQ );
testcase( pAlt->eOperator & WO_IS );
testcase( pAlt->eOperator & WO_IN );
VdbeModuleComment((v, "begin transitive constraint"));
sEAlt = *pAlt->pExpr;
sEAlt.pLeft = pE->pLeft;
- sqlite3ExprIfFalse(pParse, &sEAlt, addrCont, SQLITE_JUMPIFNULL);
+ tdsqlite3ExprIfFalse(pParse, &sEAlt, addrCont, SQLITE_JUMPIFNULL);
}
/* For a LEFT OUTER JOIN, generate code that will record the fact that
** at least one row of the right table has matched the left table.
*/
if( pLevel->iLeftJoin ){
- pLevel->addrFirst = sqlite3VdbeCurrentAddr(v);
- sqlite3VdbeAddOp2(v, OP_Integer, 1, pLevel->iLeftJoin);
+ pLevel->addrFirst = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, pLevel->iLeftJoin);
VdbeComment((v, "record LEFT JOIN hit"));
- sqlite3ExprCacheClear(pParse);
for(pTerm=pWC->a, j=0; j<pWC->nTerm; j++, pTerm++){
testcase( pTerm->wtFlags & TERM_VIRTUAL );
testcase( pTerm->wtFlags & TERM_CODED );
@@ -129482,11 +145831,22 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
continue;
}
assert( pTerm->pExpr );
- sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL);
+ tdsqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL);
pTerm->wtFlags |= TERM_CODED;
}
}
+#if WHERETRACE_ENABLED /* 0x20800 */
+ if( tdsqlite3WhereTrace & 0x20000 ){
+ tdsqlite3DebugPrintf("All WHERE-clause terms after coding level %d:\n",
+ iLevel);
+ tdsqlite3WhereClausePrint(pWC);
+ }
+ if( tdsqlite3WhereTrace & 0x800 ){
+ tdsqlite3DebugPrintf("End Coding level %d: notReady=%llx\n",
+ iLevel, (u64)pLevel->notReady);
+ }
+#endif
return pLevel->notReady;
}
@@ -129519,17 +145879,17 @@ static void exprAnalyze(SrcList*, WhereClause*, int);
/*
** Deallocate all memory associated with a WhereOrInfo object.
*/
-static void whereOrInfoDelete(sqlite3 *db, WhereOrInfo *p){
- sqlite3WhereClauseClear(&p->wc);
- sqlite3DbFree(db, p);
+static void whereOrInfoDelete(tdsqlite3 *db, WhereOrInfo *p){
+ tdsqlite3WhereClauseClear(&p->wc);
+ tdsqlite3DbFree(db, p);
}
/*
** Deallocate all memory associated with a WhereAndInfo object.
*/
-static void whereAndInfoDelete(sqlite3 *db, WhereAndInfo *p){
- sqlite3WhereClauseClear(&p->wc);
- sqlite3DbFree(db, p);
+static void whereAndInfoDelete(tdsqlite3 *db, WhereAndInfo *p){
+ tdsqlite3WhereClauseClear(&p->wc);
+ tdsqlite3DbFree(db, p);
}
/*
@@ -129557,28 +145917,28 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){
testcase( wtFlags & TERM_VIRTUAL );
if( pWC->nTerm>=pWC->nSlot ){
WhereTerm *pOld = pWC->a;
- sqlite3 *db = pWC->pWInfo->pParse->db;
- pWC->a = sqlite3DbMallocRawNN(db, sizeof(pWC->a[0])*pWC->nSlot*2 );
+ tdsqlite3 *db = pWC->pWInfo->pParse->db;
+ pWC->a = tdsqlite3DbMallocRawNN(db, sizeof(pWC->a[0])*pWC->nSlot*2 );
if( pWC->a==0 ){
if( wtFlags & TERM_DYNAMIC ){
- sqlite3ExprDelete(db, p);
+ tdsqlite3ExprDelete(db, p);
}
pWC->a = pOld;
return 0;
}
memcpy(pWC->a, pOld, sizeof(pWC->a[0])*pWC->nTerm);
if( pOld!=pWC->aStatic ){
- sqlite3DbFree(db, pOld);
+ tdsqlite3DbFree(db, pOld);
}
- pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]);
+ pWC->nSlot = tdsqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]);
}
pTerm = &pWC->a[idx = pWC->nTerm++];
if( p && ExprHasProperty(p, EP_Unlikely) ){
- pTerm->truthProb = sqlite3LogEst(p->iTable) - 270;
+ pTerm->truthProb = tdsqlite3LogEst(p->iTable) - 270;
}else{
pTerm->truthProb = 1;
}
- pTerm->pExpr = sqlite3ExprSkipCollate(p);
+ pTerm->pExpr = tdsqlite3ExprSkipCollateAndLikely(p);
pTerm->wtFlags = wtFlags;
pTerm->pWC = pWC;
pTerm->iParent = -1;
@@ -129603,31 +145963,14 @@ static int allowedOp(int op){
/*
** Commute a comparison operator. Expressions of the form "X op Y"
** are converted into "Y op X".
-**
-** If left/right precedence rules come into play when determining the
-** collating sequence, then COLLATE operators are adjusted to ensure
-** that the collating sequence does not change. For example:
-** "Y collate NOCASE op X" becomes "X op Y" because any collation sequence on
-** the left hand side of a comparison overrides any collation sequence
-** attached to the right. For the same reason the EP_Collate flag
-** is not commuted.
-*/
-static void exprCommute(Parse *pParse, Expr *pExpr){
- u16 expRight = (pExpr->pRight->flags & EP_Collate);
- u16 expLeft = (pExpr->pLeft->flags & EP_Collate);
- assert( allowedOp(pExpr->op) && pExpr->op!=TK_IN );
- if( expRight==expLeft ){
- /* Either X and Y both have COLLATE operator or neither do */
- if( expRight ){
- /* Both X and Y have COLLATE operators. Make sure X is always
- ** used by clearing the EP_Collate flag from Y. */
- pExpr->pRight->flags &= ~EP_Collate;
- }else if( sqlite3ExprCollSeq(pParse, pExpr->pLeft)!=0 ){
- /* Neither X nor Y have COLLATE operators, but X has a non-default
- ** collating sequence. So add the EP_Collate marker on X to cause
- ** it to be searched first. */
- pExpr->pLeft->flags |= EP_Collate;
- }
+*/
+static u16 exprCommute(Parse *pParse, Expr *pExpr){
+ if( pExpr->pLeft->op==TK_VECTOR
+ || pExpr->pRight->op==TK_VECTOR
+ || tdsqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pExpr->pRight) !=
+ tdsqlite3BinaryCompareCollSeq(pParse, pExpr->pRight, pExpr->pLeft)
+ ){
+ pExpr->flags ^= EP_Commuted;
}
SWAP(Expr*,pExpr->pRight,pExpr->pLeft);
if( pExpr->op>=TK_GT ){
@@ -129638,6 +145981,7 @@ static void exprCommute(Parse *pParse, Expr *pExpr){
assert( pExpr->op>=TK_GT && pExpr->op<=TK_GE );
pExpr->op = ((pExpr->op-TK_GT)^2)+TK_GT;
}
+ return 0;
}
/*
@@ -129688,18 +146032,18 @@ static int isLikeOrGlob(
int *pisComplete, /* True if the only wildcard is % in the last character */
int *pnoCase /* True if uppercase is equivalent to lowercase */
){
- const char *z = 0; /* String on RHS of LIKE operator */
+ const u8 *z = 0; /* String on RHS of LIKE operator */
Expr *pRight, *pLeft; /* Right and left size of LIKE operator */
ExprList *pList; /* List of operands to the LIKE operator */
- int c; /* One character in z[] */
+ u8 c; /* One character in z[] */
int cnt; /* Number of non-wildcard prefix characters */
- char wc[3]; /* Wildcard characters */
- sqlite3 *db = pParse->db; /* Database connection */
- sqlite3_value *pVal = 0;
+ u8 wc[4]; /* Wildcard characters */
+ tdsqlite3 *db = pParse->db; /* Database connection */
+ tdsqlite3_value *pVal = 0;
int op; /* Opcode of pRight */
int rc; /* Result code to return */
- if( !sqlite3IsLikeFunction(db, pExpr, pnoCase, wc) ){
+ if( !tdsqlite3IsLikeFunction(db, pExpr, pnoCase, (char*)wc) ){
return 0;
}
#ifdef SQLITE_EBCDIC
@@ -129707,55 +146051,111 @@ static int isLikeOrGlob(
#endif
pList = pExpr->x.pList;
pLeft = pList->a[1].pExpr;
- if( pLeft->op!=TK_COLUMN
- || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT
- || IsVirtual(pLeft->pTab) /* Value might be numeric */
- ){
- /* IMP: R-02065-49465 The left-hand side of the LIKE or GLOB operator must
- ** be the name of an indexed column with TEXT affinity. */
- return 0;
- }
- assert( pLeft->iColumn!=(-1) ); /* Because IPK never has AFF_TEXT */
- pRight = sqlite3ExprSkipCollate(pList->a[0].pExpr);
+ pRight = tdsqlite3ExprSkipCollate(pList->a[0].pExpr);
op = pRight->op;
- if( op==TK_VARIABLE ){
+ if( op==TK_VARIABLE && (db->flags & SQLITE_EnableQPSG)==0 ){
Vdbe *pReprepare = pParse->pReprepare;
int iCol = pRight->iColumn;
- pVal = sqlite3VdbeGetBoundValue(pReprepare, iCol, SQLITE_AFF_BLOB);
- if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){
- z = (char *)sqlite3_value_text(pVal);
+ pVal = tdsqlite3VdbeGetBoundValue(pReprepare, iCol, SQLITE_AFF_BLOB);
+ if( pVal && tdsqlite3_value_type(pVal)==SQLITE_TEXT ){
+ z = tdsqlite3_value_text(pVal);
}
- sqlite3VdbeSetVarmask(pParse->pVdbe, iCol);
+ tdsqlite3VdbeSetVarmask(pParse->pVdbe, iCol);
assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER );
}else if( op==TK_STRING ){
- z = pRight->u.zToken;
+ z = (u8*)pRight->u.zToken;
}
if( z ){
+
+ /* Count the number of prefix characters prior to the first wildcard */
cnt = 0;
while( (c=z[cnt])!=0 && c!=wc[0] && c!=wc[1] && c!=wc[2] ){
cnt++;
- }
- if( cnt!=0 && 255!=(u8)z[cnt-1] ){
+ if( c==wc[3] && z[cnt]!=0 ) cnt++;
+ }
+
+ /* The optimization is possible only if (1) the pattern does not begin
+ ** with a wildcard and if (2) the non-wildcard prefix does not end with
+ ** an (illegal 0xff) character, or (3) the pattern does not consist of
+ ** a single escape character. The second condition is necessary so
+ ** that we can increment the prefix key to find an upper bound for the
+ ** range search. The third is because the caller assumes that the pattern
+ ** consists of at least one character after all escapes have been
+ ** removed. */
+ if( cnt!=0 && 255!=(u8)z[cnt-1] && (cnt>1 || z[0]!=wc[3]) ){
Expr *pPrefix;
+
+ /* A "complete" match if the pattern ends with "*" or "%" */
*pisComplete = c==wc[0] && z[cnt+1]==0;
- pPrefix = sqlite3Expr(db, TK_STRING, z);
- if( pPrefix ) pPrefix->u.zToken[cnt] = 0;
+
+ /* Get the pattern prefix. Remove all escapes from the prefix. */
+ pPrefix = tdsqlite3Expr(db, TK_STRING, (char*)z);
+ if( pPrefix ){
+ int iFrom, iTo;
+ char *zNew = pPrefix->u.zToken;
+ zNew[cnt] = 0;
+ for(iFrom=iTo=0; iFrom<cnt; iFrom++){
+ if( zNew[iFrom]==wc[3] ) iFrom++;
+ zNew[iTo++] = zNew[iFrom];
+ }
+ zNew[iTo] = 0;
+ assert( iTo>0 );
+
+ /* If the LHS is not an ordinary column with TEXT affinity, then the
+ ** pattern prefix boundaries (both the start and end boundaries) must
+ ** not look like a number. Otherwise the pattern might be treated as
+ ** a number, which will invalidate the LIKE optimization.
+ **
+ ** Getting this right has been a persistent source of bugs in the
+ ** LIKE optimization. See, for example:
+ ** 2018-09-10 https://sqlite.org/src/info/c94369cae9b561b1
+ ** 2019-05-02 https://sqlite.org/src/info/b043a54c3de54b28
+ ** 2019-06-10 https://sqlite.org/src/info/fd76310a5e843e07
+ ** 2019-06-14 https://sqlite.org/src/info/ce8717f0885af975
+ ** 2019-09-03 https://sqlite.org/src/info/0f0428096f17252a
+ */
+ if( pLeft->op!=TK_COLUMN
+ || tdsqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT
+ || IsVirtual(pLeft->y.pTab) /* Value might be numeric */
+ ){
+ int isNum;
+ double rDummy;
+ isNum = tdsqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8);
+ if( isNum<=0 ){
+ if( iTo==1 && zNew[0]=='-' ){
+ isNum = +1;
+ }else{
+ zNew[iTo-1]++;
+ isNum = tdsqlite3AtoF(zNew, &rDummy, iTo, SQLITE_UTF8);
+ zNew[iTo-1]--;
+ }
+ }
+ if( isNum>0 ){
+ tdsqlite3ExprDelete(db, pPrefix);
+ tdsqlite3ValueFree(pVal);
+ return 0;
+ }
+ }
+ }
*ppPrefix = pPrefix;
+
+ /* If the RHS pattern is a bound parameter, make arrangements to
+ ** reprepare the statement when that parameter is rebound */
if( op==TK_VARIABLE ){
Vdbe *v = pParse->pVdbe;
- sqlite3VdbeSetVarmask(v, pRight->iColumn);
+ tdsqlite3VdbeSetVarmask(v, pRight->iColumn);
if( *pisComplete && pRight->u.zToken[1] ){
/* If the rhs of the LIKE expression is a variable, and the current
** value of the variable means there is no need to invoke the LIKE
** function, then no OP_Variable will be added to the program.
- ** This causes problems for the sqlite3_bind_parameter_name()
+ ** This causes problems for the tdsqlite3_bind_parameter_name()
** API. To work around them, add a dummy OP_Variable here.
*/
- int r1 = sqlite3GetTempReg(pParse);
- sqlite3ExprCodeTarget(pParse, pRight, r1);
- sqlite3VdbeChangeP3(v, sqlite3VdbeCurrentAddr(v)-1, 0);
- sqlite3ReleaseTempReg(pParse, r1);
+ int r1 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3ExprCodeTarget(pParse, pRight, r1);
+ tdsqlite3VdbeChangeP3(v, tdsqlite3VdbeCurrentAddr(v)-1, 0);
+ tdsqlite3ReleaseTempReg(pParse, r1);
}
}
}else{
@@ -129764,7 +146164,7 @@ static int isLikeOrGlob(
}
rc = (z!=0);
- sqlite3ValueFree(pVal);
+ tdsqlite3ValueFree(pVal);
return rc;
}
#endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */
@@ -129772,48 +146172,123 @@ static int isLikeOrGlob(
#ifndef SQLITE_OMIT_VIRTUALTABLE
/*
-** Check to see if the given expression is of the form
-**
-** column OP expr
-**
-** where OP is one of MATCH, GLOB, LIKE or REGEXP and "column" is a
-** column of a virtual table.
-**
-** If it is then return TRUE. If not, return FALSE.
-*/
-static int isMatchOfColumn(
+** Check to see if the pExpr expression is a form that needs to be passed
+** to the xBestIndex method of virtual tables. Forms of interest include:
+**
+** Expression Virtual Table Operator
+** ----------------------- ---------------------------------
+** 1. column MATCH expr SQLITE_INDEX_CONSTRAINT_MATCH
+** 2. column GLOB expr SQLITE_INDEX_CONSTRAINT_GLOB
+** 3. column LIKE expr SQLITE_INDEX_CONSTRAINT_LIKE
+** 4. column REGEXP expr SQLITE_INDEX_CONSTRAINT_REGEXP
+** 5. column != expr SQLITE_INDEX_CONSTRAINT_NE
+** 6. expr != column SQLITE_INDEX_CONSTRAINT_NE
+** 7. column IS NOT expr SQLITE_INDEX_CONSTRAINT_ISNOT
+** 8. expr IS NOT column SQLITE_INDEX_CONSTRAINT_ISNOT
+** 9. column IS NOT NULL SQLITE_INDEX_CONSTRAINT_ISNOTNULL
+**
+** In every case, "column" must be a column of a virtual table. If there
+** is a match, set *ppLeft to the "column" expression, set *ppRight to the
+** "expr" expression (even though in forms (6) and (8) the column is on the
+** right and the expression is on the left). Also set *peOp2 to the
+** appropriate virtual table operator. The return value is 1 or 2 if there
+** is a match. The usual return is 1, but if the RHS is also a column
+** of virtual table in forms (5) or (7) then return 2.
+**
+** If the expression matches none of the patterns above, return 0.
+*/
+static int isAuxiliaryVtabOperator(
+ tdsqlite3 *db, /* Parsing context */
Expr *pExpr, /* Test this expression */
- unsigned char *peOp2 /* OUT: 0 for MATCH, or else an op2 value */
-){
- static const struct Op2 {
- const char *zOp;
- unsigned char eOp2;
- } aOp[] = {
- { "match", SQLITE_INDEX_CONSTRAINT_MATCH },
- { "glob", SQLITE_INDEX_CONSTRAINT_GLOB },
- { "like", SQLITE_INDEX_CONSTRAINT_LIKE },
- { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP }
- };
- ExprList *pList;
- Expr *pCol; /* Column reference */
- int i;
+ unsigned char *peOp2, /* OUT: 0 for MATCH, or else an op2 value */
+ Expr **ppLeft, /* Column expression to left of MATCH/op2 */
+ Expr **ppRight /* Expression to left of MATCH/op2 */
+){
+ if( pExpr->op==TK_FUNCTION ){
+ static const struct Op2 {
+ const char *zOp;
+ unsigned char eOp2;
+ } aOp[] = {
+ { "match", SQLITE_INDEX_CONSTRAINT_MATCH },
+ { "glob", SQLITE_INDEX_CONSTRAINT_GLOB },
+ { "like", SQLITE_INDEX_CONSTRAINT_LIKE },
+ { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP }
+ };
+ ExprList *pList;
+ Expr *pCol; /* Column reference */
+ int i;
- if( pExpr->op!=TK_FUNCTION ){
- return 0;
- }
- pList = pExpr->x.pList;
- if( pList==0 || pList->nExpr!=2 ){
- return 0;
- }
- pCol = pList->a[1].pExpr;
- if( pCol->op!=TK_COLUMN || !IsVirtual(pCol->pTab) ){
- return 0;
- }
- for(i=0; i<ArraySize(aOp); i++){
- if( sqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){
- *peOp2 = aOp[i].eOp2;
- return 1;
+ pList = pExpr->x.pList;
+ if( pList==0 || pList->nExpr!=2 ){
+ return 0;
+ }
+
+ /* Built-in operators MATCH, GLOB, LIKE, and REGEXP attach to a
+ ** virtual table on their second argument, which is the same as
+ ** the left-hand side operand in their in-fix form.
+ **
+ ** vtab_column MATCH expression
+ ** MATCH(expression,vtab_column)
+ */
+ pCol = pList->a[1].pExpr;
+ if( pCol->op==TK_COLUMN && IsVirtual(pCol->y.pTab) ){
+ for(i=0; i<ArraySize(aOp); i++){
+ if( tdsqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){
+ *peOp2 = aOp[i].eOp2;
+ *ppRight = pList->a[0].pExpr;
+ *ppLeft = pCol;
+ return 1;
+ }
+ }
+ }
+
+ /* We can also match against the first column of overloaded
+ ** functions where xFindFunction returns a value of at least
+ ** SQLITE_INDEX_CONSTRAINT_FUNCTION.
+ **
+ ** OVERLOADED(vtab_column,expression)
+ **
+ ** Historically, xFindFunction expected to see lower-case function
+ ** names. But for this use case, xFindFunction is expected to deal
+ ** with function names in an arbitrary case.
+ */
+ pCol = pList->a[0].pExpr;
+ if( pCol->op==TK_COLUMN && IsVirtual(pCol->y.pTab) ){
+ tdsqlite3_vtab *pVtab;
+ tdsqlite3_module *pMod;
+ void (*xNotUsed)(tdsqlite3_context*,int,tdsqlite3_value**);
+ void *pNotUsed;
+ pVtab = tdsqlite3GetVTable(db, pCol->y.pTab)->pVtab;
+ assert( pVtab!=0 );
+ assert( pVtab->pModule!=0 );
+ pMod = (tdsqlite3_module *)pVtab->pModule;
+ if( pMod->xFindFunction!=0 ){
+ i = pMod->xFindFunction(pVtab,2, pExpr->u.zToken, &xNotUsed, &pNotUsed);
+ if( i>=SQLITE_INDEX_CONSTRAINT_FUNCTION ){
+ *peOp2 = i;
+ *ppRight = pList->a[1].pExpr;
+ *ppLeft = pCol;
+ return 1;
+ }
+ }
}
+ }else if( pExpr->op==TK_NE || pExpr->op==TK_ISNOT || pExpr->op==TK_NOTNULL ){
+ int res = 0;
+ Expr *pLeft = pExpr->pLeft;
+ Expr *pRight = pExpr->pRight;
+ if( pLeft->op==TK_COLUMN && IsVirtual(pLeft->y.pTab) ){
+ res++;
+ }
+ if( pRight && pRight->op==TK_COLUMN && IsVirtual(pRight->y.pTab) ){
+ res++;
+ SWAP(Expr*, pLeft, pRight);
+ }
+ *ppLeft = pLeft;
+ *ppRight = pRight;
+ if( pExpr->op==TK_NE ) *peOp2 = SQLITE_INDEX_CONSTRAINT_NE;
+ if( pExpr->op==TK_ISNOT ) *peOp2 = SQLITE_INDEX_CONSTRAINT_ISNOT;
+ if( pExpr->op==TK_NOTNULL ) *peOp2 = SQLITE_INDEX_CONSTRAINT_ISNOTNULL;
+ return res;
}
return 0;
}
@@ -129881,7 +146356,7 @@ static void whereCombineDisjuncts(
WhereTerm *pTwo /* Second disjunct */
){
u16 eOp = pOne->eOperator | pTwo->eOperator;
- sqlite3 *db; /* Database connection (for malloc) */
+ tdsqlite3 *db; /* Database connection (for malloc) */
Expr *pNew; /* New virtual expression */
int op; /* Operator for the combined expression */
int idxNew; /* Index in pWC of the next virtual term */
@@ -129892,8 +146367,8 @@ static void whereCombineDisjuncts(
&& (eOp & (WO_EQ|WO_GT|WO_GE))!=eOp ) return;
assert( pOne->pExpr->pLeft!=0 && pOne->pExpr->pRight!=0 );
assert( pTwo->pExpr->pLeft!=0 && pTwo->pExpr->pRight!=0 );
- if( sqlite3ExprCompare(pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return;
- if( sqlite3ExprCompare(pOne->pExpr->pRight, pTwo->pExpr->pRight, -1) )return;
+ if( tdsqlite3ExprCompare(0,pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return;
+ if( tdsqlite3ExprCompare(0,pOne->pExpr->pRight, pTwo->pExpr->pRight,-1) )return;
/* If we reach this point, it means the two subterms can be combined */
if( (eOp & (eOp-1))!=0 ){
if( eOp & (WO_LT|WO_LE) ){
@@ -129904,7 +146379,7 @@ static void whereCombineDisjuncts(
}
}
db = pWC->pWInfo->pParse->db;
- pNew = sqlite3ExprDup(db, pOne->pExpr, 0);
+ pNew = tdsqlite3ExprDup(db, pOne->pExpr, 0);
if( pNew==0 ) return;
for(op=TK_EQ; eOp!=(WO_EQ<<(op-TK_EQ)); op++){ assert( op<TK_GE ); }
pNew->op = op;
@@ -130007,7 +146482,7 @@ static void exprAnalyzeOrTerm(
){
WhereInfo *pWInfo = pWC->pWInfo; /* WHERE clause processing context */
Parse *pParse = pWInfo->pParse; /* Parser context */
- sqlite3 *db = pParse->db; /* Database connection */
+ tdsqlite3 *db = pParse->db; /* Database connection */
WhereTerm *pTerm = &pWC->a[idxTerm]; /* The term to be analyzed */
Expr *pExpr = pTerm->pExpr; /* The expression of the term */
int i; /* Loop counters */
@@ -130024,14 +146499,14 @@ static void exprAnalyzeOrTerm(
*/
assert( (pTerm->wtFlags & (TERM_DYNAMIC|TERM_ORINFO|TERM_ANDINFO))==0 );
assert( pExpr->op==TK_OR );
- pTerm->u.pOrInfo = pOrInfo = sqlite3DbMallocZero(db, sizeof(*pOrInfo));
+ pTerm->u.pOrInfo = pOrInfo = tdsqlite3DbMallocZero(db, sizeof(*pOrInfo));
if( pOrInfo==0 ) return;
pTerm->wtFlags |= TERM_ORINFO;
pOrWc = &pOrInfo->wc;
memset(pOrWc->aStatic, 0, sizeof(pOrWc->aStatic));
- sqlite3WhereClauseInit(pOrWc, pWInfo);
- sqlite3WhereSplit(pOrWc, pExpr, TK_OR);
- sqlite3WhereExprAnalyze(pSrc, pOrWc);
+ tdsqlite3WhereClauseInit(pOrWc, pWInfo);
+ tdsqlite3WhereSplit(pOrWc, pExpr, TK_OR);
+ tdsqlite3WhereExprAnalyze(pSrc, pOrWc);
if( db->mallocFailed ) return;
assert( pOrWc->nTerm>=2 );
@@ -130045,7 +146520,7 @@ static void exprAnalyzeOrTerm(
WhereAndInfo *pAndInfo;
assert( (pOrTerm->wtFlags & (TERM_ANDINFO|TERM_ORINFO))==0 );
chngToIN = 0;
- pAndInfo = sqlite3DbMallocRawNN(db, sizeof(*pAndInfo));
+ pAndInfo = tdsqlite3DbMallocRawNN(db, sizeof(*pAndInfo));
if( pAndInfo ){
WhereClause *pAndWC;
WhereTerm *pAndTerm;
@@ -130056,17 +146531,17 @@ static void exprAnalyzeOrTerm(
pOrTerm->eOperator = WO_AND;
pAndWC = &pAndInfo->wc;
memset(pAndWC->aStatic, 0, sizeof(pAndWC->aStatic));
- sqlite3WhereClauseInit(pAndWC, pWC->pWInfo);
- sqlite3WhereSplit(pAndWC, pOrTerm->pExpr, TK_AND);
- sqlite3WhereExprAnalyze(pSrc, pAndWC);
+ tdsqlite3WhereClauseInit(pAndWC, pWC->pWInfo);
+ tdsqlite3WhereSplit(pAndWC, pOrTerm->pExpr, TK_AND);
+ tdsqlite3WhereExprAnalyze(pSrc, pAndWC);
pAndWC->pOuter = pWC;
if( !db->mallocFailed ){
for(j=0, pAndTerm=pAndWC->a; j<pAndWC->nTerm; j++, pAndTerm++){
assert( pAndTerm->pExpr );
if( allowedOp(pAndTerm->pExpr->op)
- || pAndTerm->eOperator==WO_MATCH
+ || pAndTerm->eOperator==WO_AUX
){
- b |= sqlite3WhereGetMask(&pWInfo->sMaskSet, pAndTerm->leftCursor);
+ b |= tdsqlite3WhereGetMask(&pWInfo->sMaskSet, pAndTerm->leftCursor);
}
}
}
@@ -130077,10 +146552,10 @@ static void exprAnalyzeOrTerm(
** corresponding TERM_VIRTUAL term */
}else{
Bitmask b;
- b = sqlite3WhereGetMask(&pWInfo->sMaskSet, pOrTerm->leftCursor);
+ b = tdsqlite3WhereGetMask(&pWInfo->sMaskSet, pOrTerm->leftCursor);
if( pOrTerm->wtFlags & TERM_VIRTUAL ){
WhereTerm *pOther = &pOrWc->a[pOrTerm->iParent];
- b |= sqlite3WhereGetMask(&pWInfo->sMaskSet, pOther->leftCursor);
+ b |= tdsqlite3WhereGetMask(&pWInfo->sMaskSet, pOther->leftCursor);
}
indexable &= b;
if( (pOrTerm->eOperator & WO_EQ)==0 ){
@@ -130096,7 +146571,12 @@ static void exprAnalyzeOrTerm(
** empty.
*/
pOrInfo->indexable = indexable;
- pTerm->eOperator = indexable==0 ? 0 : WO_OR;
+ if( indexable ){
+ pTerm->eOperator = WO_OR;
+ pWC->hasOr = 1;
+ }else{
+ pTerm->eOperator = WO_OR;
+ }
/* For a two-way OR, attempt to implementation case 2.
*/
@@ -130146,6 +146626,7 @@ static void exprAnalyzeOrTerm(
** and column is found but leave okToChngToIN false if not found.
*/
for(j=0; j<2 && !okToChngToIN; j++){
+ Expr *pLeft = 0;
pOrTerm = pOrWc->a;
for(i=pOrWc->nTerm-1; i>=0; i--, pOrTerm++){
assert( pOrTerm->eOperator & WO_EQ );
@@ -130156,7 +146637,7 @@ static void exprAnalyzeOrTerm(
assert( j==1 );
continue;
}
- if( (chngToIN & sqlite3WhereGetMask(&pWInfo->sMaskSet,
+ if( (chngToIN & tdsqlite3WhereGetMask(&pWInfo->sMaskSet,
pOrTerm->leftCursor))==0 ){
/* This term must be of the form t1.a==t2.b where t2 is in the
** chngToIN set but t1 is not. This term will be either preceded
@@ -130169,6 +146650,7 @@ static void exprAnalyzeOrTerm(
}
iColumn = pOrTerm->u.leftColumn;
iCursor = pOrTerm->leftCursor;
+ pLeft = pOrTerm->pExpr->pLeft;
break;
}
if( i<0 ){
@@ -130176,7 +146658,7 @@ static void exprAnalyzeOrTerm(
** on the second iteration */
assert( j==1 );
assert( IsPowerOfTwo(chngToIN) );
- assert( chngToIN==sqlite3WhereGetMask(&pWInfo->sMaskSet, iCursor) );
+ assert( chngToIN==tdsqlite3WhereGetMask(&pWInfo->sMaskSet, iCursor) );
break;
}
testcase( j==1 );
@@ -130188,7 +146670,9 @@ static void exprAnalyzeOrTerm(
assert( pOrTerm->eOperator & WO_EQ );
if( pOrTerm->leftCursor!=iCursor ){
pOrTerm->wtFlags &= ~TERM_OR_OK;
- }else if( pOrTerm->u.leftColumn!=iColumn ){
+ }else if( pOrTerm->u.leftColumn!=iColumn || (iColumn==XN_EXPR
+ && tdsqlite3ExprCompare(pParse, pOrTerm->pExpr->pLeft, pLeft, -1)
+ )){
okToChngToIN = 0;
}else{
int affLeft, affRight;
@@ -130196,8 +146680,8 @@ static void exprAnalyzeOrTerm(
** of both right and left sides must be such that no type
** conversions are required on the right. (Ticket #2249)
*/
- affRight = sqlite3ExprAffinity(pOrTerm->pExpr->pRight);
- affLeft = sqlite3ExprAffinity(pOrTerm->pExpr->pLeft);
+ affRight = tdsqlite3ExprAffinity(pOrTerm->pExpr->pRight);
+ affLeft = tdsqlite3ExprAffinity(pOrTerm->pExpr->pLeft);
if( affRight!=0 && affRight!=affLeft ){
okToChngToIN = 0;
}else{
@@ -130222,13 +146706,13 @@ static void exprAnalyzeOrTerm(
assert( pOrTerm->eOperator & WO_EQ );
assert( pOrTerm->leftCursor==iCursor );
assert( pOrTerm->u.leftColumn==iColumn );
- pDup = sqlite3ExprDup(db, pOrTerm->pExpr->pRight, 0);
- pList = sqlite3ExprListAppend(pWInfo->pParse, pList, pDup);
+ pDup = tdsqlite3ExprDup(db, pOrTerm->pExpr->pRight, 0);
+ pList = tdsqlite3ExprListAppend(pWInfo->pParse, pList, pDup);
pLeft = pOrTerm->pExpr->pLeft;
}
assert( pLeft!=0 );
- pDup = sqlite3ExprDup(db, pLeft, 0);
- pNew = sqlite3PExpr(pParse, TK_IN, pDup, 0, 0);
+ pDup = tdsqlite3ExprDup(db, pLeft, 0);
+ pNew = tdsqlite3PExpr(pParse, TK_IN, pDup, 0);
if( pNew ){
int idxNew;
transferJoinMarkings(pNew, pExpr);
@@ -130237,12 +146721,11 @@ static void exprAnalyzeOrTerm(
idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC);
testcase( idxNew==0 );
exprAnalyze(pSrc, pWC, idxNew);
- pTerm = &pWC->a[idxTerm];
+ /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where used again */
markTermAsChild(pWC, idxNew, idxTerm);
}else{
- sqlite3ExprListDelete(db, pList);
+ tdsqlite3ExprListDelete(db, pList);
}
- pTerm->eOperator = WO_NOOP; /* case 1 trumps case 3 */
}
}
}
@@ -130266,24 +146749,19 @@ static void exprAnalyzeOrTerm(
static int termIsEquivalence(Parse *pParse, Expr *pExpr){
char aff1, aff2;
CollSeq *pColl;
- const char *zColl1, *zColl2;
if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0;
if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0;
if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0;
- aff1 = sqlite3ExprAffinity(pExpr->pLeft);
- aff2 = sqlite3ExprAffinity(pExpr->pRight);
+ aff1 = tdsqlite3ExprAffinity(pExpr->pLeft);
+ aff2 = tdsqlite3ExprAffinity(pExpr->pRight);
if( aff1!=aff2
- && (!sqlite3IsNumericAffinity(aff1) || !sqlite3IsNumericAffinity(aff2))
+ && (!tdsqlite3IsNumericAffinity(aff1) || !tdsqlite3IsNumericAffinity(aff2))
){
return 0;
}
- pColl = sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pExpr->pRight);
- if( pColl==0 || sqlite3StrICmp(pColl->zName, "BINARY")==0 ) return 1;
- pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft);
- zColl1 = pColl ? pColl->zName : 0;
- pColl = sqlite3ExprCollSeq(pParse, pExpr->pRight);
- zColl2 = pColl ? pColl->zName : 0;
- return sqlite3_stricmp(zColl1, zColl2)==0;
+ pColl = tdsqlite3ExprCompareCollSeq(pParse, pExpr);
+ if( tdsqlite3IsBinary(pColl) ) return 1;
+ return tdsqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight);
}
/*
@@ -130295,16 +146773,19 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
Bitmask mask = 0;
while( pS ){
SrcList *pSrc = pS->pSrc;
- mask |= sqlite3WhereExprListUsage(pMaskSet, pS->pEList);
- mask |= sqlite3WhereExprListUsage(pMaskSet, pS->pGroupBy);
- mask |= sqlite3WhereExprListUsage(pMaskSet, pS->pOrderBy);
- mask |= sqlite3WhereExprUsage(pMaskSet, pS->pWhere);
- mask |= sqlite3WhereExprUsage(pMaskSet, pS->pHaving);
+ mask |= tdsqlite3WhereExprListUsage(pMaskSet, pS->pEList);
+ mask |= tdsqlite3WhereExprListUsage(pMaskSet, pS->pGroupBy);
+ mask |= tdsqlite3WhereExprListUsage(pMaskSet, pS->pOrderBy);
+ mask |= tdsqlite3WhereExprUsage(pMaskSet, pS->pWhere);
+ mask |= tdsqlite3WhereExprUsage(pMaskSet, pS->pHaving);
if( ALWAYS(pSrc!=0) ){
int i;
for(i=0; i<pSrc->nSrc; i++){
mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect);
- mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].pOn);
+ mask |= tdsqlite3WhereExprUsage(pMaskSet, pSrc->a[i].pOn);
+ if( pSrc->a[i].fg.isTabFunc ){
+ mask |= tdsqlite3WhereExprListUsage(pMaskSet, pSrc->a[i].u1.pFuncArg);
+ }
}
}
pS = pS->pPrior;
@@ -130316,8 +146797,8 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
** Expression pExpr is one operand of a comparison operator that might
** be useful for indexing. This routine checks to see if pExpr appears
** in any index. Return TRUE (1) if pExpr is an indexed term and return
-** FALSE (0) if not. If TRUE is returned, also set *piCur to the cursor
-** number of the table that is indexed and *piColumn to the column number
+** FALSE (0) if not. If TRUE is returned, also set aiCurCol[0] to the cursor
+** number of the table that is indexed and aiCurCol[1] to the column number
** of the column that is indexed, or XN_EXPR (-2) if an expression is being
** indexed.
**
@@ -130325,18 +146806,37 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){
** true even if that particular column is not indexed, because the column
** might be added to an automatic index later.
*/
-static int exprMightBeIndexed(
+static SQLITE_NOINLINE int exprMightBeIndexed2(
SrcList *pFrom, /* The FROM clause */
- int op, /* The specific comparison operator */
Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */
- Expr *pExpr, /* An operand of a comparison operator */
- int *piCur, /* Write the referenced table cursor number here */
- int *piColumn /* Write the referenced table column number here */
+ int *aiCurCol, /* Write the referenced table cursor and column here */
+ Expr *pExpr /* An operand of a comparison operator */
){
Index *pIdx;
int i;
int iCur;
-
+ for(i=0; mPrereq>1; i++, mPrereq>>=1){}
+ iCur = pFrom->a[i].iCursor;
+ for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){
+ if( pIdx->aColExpr==0 ) continue;
+ for(i=0; i<pIdx->nKeyCol; i++){
+ if( pIdx->aiColumn[i]!=XN_EXPR ) continue;
+ if( tdsqlite3ExprCompareSkip(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){
+ aiCurCol[0] = iCur;
+ aiCurCol[1] = XN_EXPR;
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+static int exprMightBeIndexed(
+ SrcList *pFrom, /* The FROM clause */
+ Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */
+ int *aiCurCol, /* Write the referenced table cursor & column here */
+ Expr *pExpr, /* An operand of a comparison operator */
+ int op /* The specific comparison operator */
+){
/* If this expression is a vector to the left or right of a
** inequality constraint (>, <, >= or <=), perform the processing
** on the first element of the vector. */
@@ -130348,26 +146848,13 @@ static int exprMightBeIndexed(
}
if( pExpr->op==TK_COLUMN ){
- *piCur = pExpr->iTable;
- *piColumn = pExpr->iColumn;
+ aiCurCol[0] = pExpr->iTable;
+ aiCurCol[1] = pExpr->iColumn;
return 1;
}
if( mPrereq==0 ) return 0; /* No table references */
if( (mPrereq&(mPrereq-1))!=0 ) return 0; /* Refs more than one table */
- for(i=0; mPrereq>1; i++, mPrereq>>=1){}
- iCur = pFrom->a[i].iCursor;
- for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){
- if( pIdx->aColExpr==0 ) continue;
- for(i=0; i<pIdx->nKeyCol; i++){
- if( pIdx->aiColumn[i]!=XN_EXPR ) continue;
- if( sqlite3ExprCompare(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){
- *piCur = iCur;
- *piColumn = XN_EXPR;
- return 1;
- }
- }
- }
- return 0;
+ return exprMightBeIndexed2(pFrom,mPrereq,aiCurCol,pExpr);
}
/*
@@ -130405,8 +146892,9 @@ static void exprAnalyze(
int noCase = 0; /* uppercase equivalent to lowercase */
int op; /* Top-level operator. pExpr->op */
Parse *pParse = pWInfo->pParse; /* Parsing context */
- sqlite3 *db = pParse->db; /* Database connection */
- unsigned char eOp2; /* op2 value for LIKE/REGEXP/GLOB */
+ tdsqlite3 *db = pParse->db; /* Database connection */
+ unsigned char eOp2 = 0; /* op2 value for LIKE/REGEXP/GLOB */
+ int nLeft; /* Number of elements on left side vector */
if( db->mallocFailed ){
return;
@@ -130415,36 +146903,42 @@ static void exprAnalyze(
pMaskSet = &pWInfo->sMaskSet;
pExpr = pTerm->pExpr;
assert( pExpr->op!=TK_AS && pExpr->op!=TK_COLLATE );
- prereqLeft = sqlite3WhereExprUsage(pMaskSet, pExpr->pLeft);
+ prereqLeft = tdsqlite3WhereExprUsage(pMaskSet, pExpr->pLeft);
op = pExpr->op;
if( op==TK_IN ){
assert( pExpr->pRight==0 );
- if( sqlite3ExprCheckIN(pParse, pExpr) ) return;
+ if( tdsqlite3ExprCheckIN(pParse, pExpr) ) return;
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
pTerm->prereqRight = exprSelectUsage(pMaskSet, pExpr->x.pSelect);
}else{
- pTerm->prereqRight = sqlite3WhereExprListUsage(pMaskSet, pExpr->x.pList);
+ pTerm->prereqRight = tdsqlite3WhereExprListUsage(pMaskSet, pExpr->x.pList);
}
}else if( op==TK_ISNULL ){
pTerm->prereqRight = 0;
}else{
- pTerm->prereqRight = sqlite3WhereExprUsage(pMaskSet, pExpr->pRight);
+ pTerm->prereqRight = tdsqlite3WhereExprUsage(pMaskSet, pExpr->pRight);
}
- prereqAll = sqlite3WhereExprUsage(pMaskSet, pExpr);
+ pMaskSet->bVarSelect = 0;
+ prereqAll = tdsqlite3WhereExprUsageNN(pMaskSet, pExpr);
+ if( pMaskSet->bVarSelect ) pTerm->wtFlags |= TERM_VARSELECT;
if( ExprHasProperty(pExpr, EP_FromJoin) ){
- Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->iRightJoinTable);
+ Bitmask x = tdsqlite3WhereGetMask(pMaskSet, pExpr->iRightJoinTable);
prereqAll |= x;
extraRight = x-1; /* ON clause terms may not be used with an index
** on left table of a LEFT JOIN. Ticket #3015 */
+ if( (prereqAll>>1)>=x ){
+ tdsqlite3ErrorMsg(pParse, "ON clause references tables to its right");
+ return;
+ }
}
pTerm->prereqAll = prereqAll;
pTerm->leftCursor = -1;
pTerm->iParent = -1;
pTerm->eOperator = 0;
if( allowedOp(op) ){
- int iCur, iColumn;
- Expr *pLeft = sqlite3ExprSkipCollate(pExpr->pLeft);
- Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight);
+ int aiCurCol[2];
+ Expr *pLeft = tdsqlite3ExprSkipCollate(pExpr->pLeft);
+ Expr *pRight = tdsqlite3ExprSkipCollate(pExpr->pRight);
u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV;
if( pTerm->iField>0 ){
@@ -130453,14 +146947,14 @@ static void exprAnalyze(
pLeft = pLeft->x.pList->a[pTerm->iField-1].pExpr;
}
- if( exprMightBeIndexed(pSrc, op, prereqLeft, pLeft, &iCur, &iColumn) ){
- pTerm->leftCursor = iCur;
- pTerm->u.leftColumn = iColumn;
+ if( exprMightBeIndexed(pSrc, prereqLeft, aiCurCol, pLeft, op) ){
+ pTerm->leftCursor = aiCurCol[0];
+ pTerm->u.leftColumn = aiCurCol[1];
pTerm->eOperator = operatorMask(op) & opMask;
}
if( op==TK_IS ) pTerm->wtFlags |= TERM_IS;
if( pRight
- && exprMightBeIndexed(pSrc, op, pTerm->prereqRight, pRight, &iCur,&iColumn)
+ && exprMightBeIndexed(pSrc, pTerm->prereqRight, aiCurCol, pRight, op)
){
WhereTerm *pNew;
Expr *pDup;
@@ -130468,9 +146962,9 @@ static void exprAnalyze(
assert( pTerm->iField==0 );
if( pTerm->leftCursor>=0 ){
int idxNew;
- pDup = sqlite3ExprDup(db, pExpr, 0);
+ pDup = tdsqlite3ExprDup(db, pExpr, 0);
if( db->mallocFailed ){
- sqlite3ExprDelete(db, pDup);
+ tdsqlite3ExprDelete(db, pDup);
return;
}
idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC);
@@ -130489,9 +146983,9 @@ static void exprAnalyze(
pDup = pExpr;
pNew = pTerm;
}
- exprCommute(pParse, pDup);
- pNew->leftCursor = iCur;
- pNew->u.leftColumn = iColumn;
+ pNew->wtFlags |= exprCommute(pParse, pDup);
+ pNew->leftCursor = aiCurCol[0];
+ pNew->u.leftColumn = aiCurCol[1];
testcase( (prereqLeft | extraRight) != prereqLeft );
pNew->prereqRight = prereqLeft | extraRight;
pNew->prereqAll = prereqAll;
@@ -130524,9 +147018,9 @@ static void exprAnalyze(
for(i=0; i<2; i++){
Expr *pNewExpr;
int idxNew;
- pNewExpr = sqlite3PExpr(pParse, ops[i],
- sqlite3ExprDup(db, pExpr->pLeft, 0),
- sqlite3ExprDup(db, pList->a[i].pExpr, 0), 0);
+ pNewExpr = tdsqlite3PExpr(pParse, ops[i],
+ tdsqlite3ExprDup(db, pExpr->pLeft, 0),
+ tdsqlite3ExprDup(db, pList->a[i].pExpr, 0));
transferJoinMarkings(pNewExpr, pExpr);
idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
testcase( idxNew==0 );
@@ -130575,7 +147069,7 @@ static void exprAnalyze(
const u16 wtFlags = TERM_LIKEOPT | TERM_VIRTUAL | TERM_DYNAMIC;
pLeft = pExpr->x.pList->a[1].pExpr;
- pStr2 = sqlite3ExprDup(db, pStr1, 0);
+ pStr2 = tdsqlite3ExprDup(db, pStr1, 0);
/* Convert the lower bound to upper-case and the upper bound to
** lower-case (upper-case is less than lower-case in ASCII) so that
@@ -130586,14 +147080,14 @@ static void exprAnalyze(
char c;
pTerm->wtFlags |= TERM_LIKE;
for(i=0; (c = pStr1->u.zToken[i])!=0; i++){
- pStr1->u.zToken[i] = sqlite3Toupper(c);
- pStr2->u.zToken[i] = sqlite3Tolower(c);
+ pStr1->u.zToken[i] = tdsqlite3Toupper(c);
+ pStr2->u.zToken[i] = tdsqlite3Tolower(c);
}
}
if( !db->mallocFailed ){
u8 c, *pC; /* Last character before the first wildcard */
- pC = (u8*)&pStr2->u.zToken[sqlite3Strlen30(pStr2->u.zToken)-1];
+ pC = (u8*)&pStr2->u.zToken[tdsqlite3Strlen30(pStr2->u.zToken)-1];
c = *pC;
if( noCase ){
/* The point is to increment the last character before the first
@@ -130603,23 +147097,23 @@ static void exprAnalyze(
** LIKE on all candidate expressions by clearing the isComplete flag
*/
if( c=='A'-1 ) isComplete = 0;
- c = sqlite3UpperToLower[c];
+ c = tdsqlite3UpperToLower[c];
}
*pC = c + 1;
}
- zCollSeqName = noCase ? "NOCASE" : "BINARY";
- pNewExpr1 = sqlite3ExprDup(db, pLeft, 0);
- pNewExpr1 = sqlite3PExpr(pParse, TK_GE,
- sqlite3ExprAddCollateString(pParse,pNewExpr1,zCollSeqName),
- pStr1, 0);
+ zCollSeqName = noCase ? "NOCASE" : tdsqlite3StrBINARY;
+ pNewExpr1 = tdsqlite3ExprDup(db, pLeft, 0);
+ pNewExpr1 = tdsqlite3PExpr(pParse, TK_GE,
+ tdsqlite3ExprAddCollateString(pParse,pNewExpr1,zCollSeqName),
+ pStr1);
transferJoinMarkings(pNewExpr1, pExpr);
idxNew1 = whereClauseInsert(pWC, pNewExpr1, wtFlags);
testcase( idxNew1==0 );
exprAnalyze(pSrc, pWC, idxNew1);
- pNewExpr2 = sqlite3ExprDup(db, pLeft, 0);
- pNewExpr2 = sqlite3PExpr(pParse, TK_LT,
- sqlite3ExprAddCollateString(pParse,pNewExpr2,zCollSeqName),
- pStr2, 0);
+ pNewExpr2 = tdsqlite3ExprDup(db, pLeft, 0);
+ pNewExpr2 = tdsqlite3PExpr(pParse, TK_LT,
+ tdsqlite3ExprAddCollateString(pParse,pNewExpr2,zCollSeqName),
+ pStr2);
transferJoinMarkings(pNewExpr2, pExpr);
idxNew2 = whereClauseInsert(pWC, pNewExpr2, wtFlags);
testcase( idxNew2==0 );
@@ -130633,38 +147127,47 @@ static void exprAnalyze(
#endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */
#ifndef SQLITE_OMIT_VIRTUALTABLE
- /* Add a WO_MATCH auxiliary term to the constraint set if the
- ** current expression is of the form: column MATCH expr.
+ /* Add a WO_AUX auxiliary term to the constraint set if the
+ ** current expression is of the form "column OP expr" where OP
+ ** is an operator that gets passed into virtual tables but which is
+ ** not normally optimized for ordinary tables. In other words, OP
+ ** is one of MATCH, LIKE, GLOB, REGEXP, !=, IS, IS NOT, or NOT NULL.
** This information is used by the xBestIndex methods of
** virtual tables. The native query optimizer does not attempt
** to do anything with MATCH functions.
*/
- if( pWC->op==TK_AND && isMatchOfColumn(pExpr, &eOp2) ){
- int idxNew;
- Expr *pRight, *pLeft;
- WhereTerm *pNewTerm;
- Bitmask prereqColumn, prereqExpr;
-
- pRight = pExpr->x.pList->a[0].pExpr;
- pLeft = pExpr->x.pList->a[1].pExpr;
- prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight);
- prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft);
- if( (prereqExpr & prereqColumn)==0 ){
- Expr *pNewExpr;
- pNewExpr = sqlite3PExpr(pParse, TK_MATCH,
- 0, sqlite3ExprDup(db, pRight, 0), 0);
- idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
- testcase( idxNew==0 );
- pNewTerm = &pWC->a[idxNew];
- pNewTerm->prereqRight = prereqExpr;
- pNewTerm->leftCursor = pLeft->iTable;
- pNewTerm->u.leftColumn = pLeft->iColumn;
- pNewTerm->eOperator = WO_MATCH;
- pNewTerm->eMatchOp = eOp2;
- markTermAsChild(pWC, idxNew, idxTerm);
- pTerm = &pWC->a[idxTerm];
- pTerm->wtFlags |= TERM_COPIED;
- pNewTerm->prereqAll = pTerm->prereqAll;
+ if( pWC->op==TK_AND ){
+ Expr *pRight = 0, *pLeft = 0;
+ int res = isAuxiliaryVtabOperator(db, pExpr, &eOp2, &pLeft, &pRight);
+ while( res-- > 0 ){
+ int idxNew;
+ WhereTerm *pNewTerm;
+ Bitmask prereqColumn, prereqExpr;
+
+ prereqExpr = tdsqlite3WhereExprUsage(pMaskSet, pRight);
+ prereqColumn = tdsqlite3WhereExprUsage(pMaskSet, pLeft);
+ if( (prereqExpr & prereqColumn)==0 ){
+ Expr *pNewExpr;
+ pNewExpr = tdsqlite3PExpr(pParse, TK_MATCH,
+ 0, tdsqlite3ExprDup(db, pRight, 0));
+ if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){
+ ExprSetProperty(pNewExpr, EP_FromJoin);
+ pNewExpr->iRightJoinTable = pExpr->iRightJoinTable;
+ }
+ idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC);
+ testcase( idxNew==0 );
+ pNewTerm = &pWC->a[idxNew];
+ pNewTerm->prereqRight = prereqExpr;
+ pNewTerm->leftCursor = pLeft->iTable;
+ pNewTerm->u.leftColumn = pLeft->iColumn;
+ pNewTerm->eOperator = WO_AUX;
+ pNewTerm->eMatchOp = eOp2;
+ markTermAsChild(pWC, idxNew, idxTerm);
+ pTerm = &pWC->a[idxTerm];
+ pTerm->wtFlags |= TERM_COPIED;
+ pNewTerm->prereqAll = pTerm->prereqAll;
+ }
+ SWAP(Expr*, pLeft, pRight);
}
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
@@ -130678,26 +147181,25 @@ static void exprAnalyze(
** is not a sub-select. */
if( pWC->op==TK_AND
&& (pExpr->op==TK_EQ || pExpr->op==TK_IS)
- && sqlite3ExprIsVector(pExpr->pLeft)
+ && (nLeft = tdsqlite3ExprVectorSize(pExpr->pLeft))>1
+ && tdsqlite3ExprVectorSize(pExpr->pRight)==nLeft
&& ( (pExpr->pLeft->flags & EP_xIsSelect)==0
- || (pExpr->pRight->flags & EP_xIsSelect)==0
- )){
- int nLeft = sqlite3ExprVectorSize(pExpr->pLeft);
+ || (pExpr->pRight->flags & EP_xIsSelect)==0)
+ ){
int i;
- assert( nLeft==sqlite3ExprVectorSize(pExpr->pRight) );
for(i=0; i<nLeft; i++){
int idxNew;
Expr *pNew;
- Expr *pLeft = sqlite3ExprForVectorField(pParse, pExpr->pLeft, i);
- Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i);
+ Expr *pLeft = tdsqlite3ExprForVectorField(pParse, pExpr->pLeft, i);
+ Expr *pRight = tdsqlite3ExprForVectorField(pParse, pExpr->pRight, i);
- pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight, 0);
+ pNew = tdsqlite3PExpr(pParse, pExpr->op, pLeft, pRight);
transferJoinMarkings(pNew, pExpr);
idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC);
exprAnalyze(pSrc, pWC, idxNew);
}
pTerm = &pWC->a[idxTerm];
- pTerm->wtFlags = TERM_CODED|TERM_VIRTUAL; /* Disable the original */
+ pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */
pTerm->eOperator = 0;
}
@@ -130707,14 +147209,18 @@ static void exprAnalyze(
** expression). The WhereTerm.iField variable identifies the index within
** the vector on the LHS that the virtual term represents.
**
- ** This only works if the RHS is a simple SELECT, not a compound
+ ** This only works if the RHS is a simple SELECT (not a compound) that does
+ ** not use window functions.
*/
if( pWC->op==TK_AND && pExpr->op==TK_IN && pTerm->iField==0
&& pExpr->pLeft->op==TK_VECTOR
&& pExpr->x.pSelect->pPrior==0
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ && pExpr->x.pSelect->pWin==0
+#endif
){
int i;
- for(i=0; i<sqlite3ExprVectorSize(pExpr->pLeft); i++){
+ for(i=0; i<tdsqlite3ExprVectorSize(pExpr->pLeft); i++){
int idxNew;
idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL);
pWC->a[idxNew].iField = i+1;
@@ -130723,8 +147229,8 @@ static void exprAnalyze(
}
}
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- /* When sqlite_stat3 histogram data is available an operator of the
+#ifdef SQLITE_ENABLE_STAT4
+ /* When sqlite_stat4 histogram data is available an operator of the
** form "x IS NOT NULL" can sometimes be evaluated more efficiently
** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a
** virtual term of that form.
@@ -130734,16 +147240,17 @@ static void exprAnalyze(
if( pExpr->op==TK_NOTNULL
&& pExpr->pLeft->op==TK_COLUMN
&& pExpr->pLeft->iColumn>=0
- && OptimizationEnabled(db, SQLITE_Stat34)
+ && !ExprHasProperty(pExpr, EP_FromJoin)
+ && OptimizationEnabled(db, SQLITE_Stat4)
){
Expr *pNewExpr;
Expr *pLeft = pExpr->pLeft;
int idxNew;
WhereTerm *pNewTerm;
- pNewExpr = sqlite3PExpr(pParse, TK_GT,
- sqlite3ExprDup(db, pLeft, 0),
- sqlite3ExprAlloc(db, TK_NULL, 0, 0), 0);
+ pNewExpr = tdsqlite3PExpr(pParse, TK_GT,
+ tdsqlite3ExprDup(db, pLeft, 0),
+ tdsqlite3ExprAlloc(db, TK_NULL, 0, 0));
idxNew = whereClauseInsert(pWC, pNewExpr,
TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL);
@@ -130759,7 +147266,7 @@ static void exprAnalyze(
pNewTerm->prereqAll = pTerm->prereqAll;
}
}
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
/* Prevent ON clause terms of a LEFT JOIN from being used to drive
** an index for tables to the left of the join.
@@ -130791,26 +147298,27 @@ static void exprAnalyze(
** the WhereClause.a[] array. The slot[] array grows as needed to contain
** all terms of the WHERE clause.
*/
-SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause *pWC, Expr *pExpr, u8 op){
- Expr *pE2 = sqlite3ExprSkipCollate(pExpr);
+SQLITE_PRIVATE void tdsqlite3WhereSplit(WhereClause *pWC, Expr *pExpr, u8 op){
+ Expr *pE2 = tdsqlite3ExprSkipCollateAndLikely(pExpr);
pWC->op = op;
if( pE2==0 ) return;
if( pE2->op!=op ){
whereClauseInsert(pWC, pExpr, 0);
}else{
- sqlite3WhereSplit(pWC, pE2->pLeft, op);
- sqlite3WhereSplit(pWC, pE2->pRight, op);
+ tdsqlite3WhereSplit(pWC, pE2->pLeft, op);
+ tdsqlite3WhereSplit(pWC, pE2->pRight, op);
}
}
/*
** Initialize a preallocated WhereClause structure.
*/
-SQLITE_PRIVATE void sqlite3WhereClauseInit(
+SQLITE_PRIVATE void tdsqlite3WhereClauseInit(
WhereClause *pWC, /* The WhereClause to be initialized */
WhereInfo *pWInfo /* The WHERE processing context */
){
pWC->pWInfo = pWInfo;
+ pWC->hasOr = 0;
pWC->pOuter = 0;
pWC->nTerm = 0;
pWC->nSlot = ArraySize(pWC->aStatic);
@@ -130820,15 +147328,15 @@ SQLITE_PRIVATE void sqlite3WhereClauseInit(
/*
** Deallocate a WhereClause structure. The WhereClause structure
** itself is not freed. This routine is the inverse of
-** sqlite3WhereClauseInit().
+** tdsqlite3WhereClauseInit().
*/
-SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){
+SQLITE_PRIVATE void tdsqlite3WhereClauseClear(WhereClause *pWC){
int i;
WhereTerm *a;
- sqlite3 *db = pWC->pWInfo->pParse->db;
+ tdsqlite3 *db = pWC->pWInfo->pParse->db;
for(i=pWC->nTerm-1, a=pWC->a; i>=0; i--, a++){
if( a->wtFlags & TERM_DYNAMIC ){
- sqlite3ExprDelete(db, a->pExpr);
+ tdsqlite3ExprDelete(db, a->pExpr);
}
if( a->wtFlags & TERM_ORINFO ){
whereOrInfoDelete(db, a->u.pOrInfo);
@@ -130837,7 +147345,7 @@ SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){
}
}
if( pWC->a!=pWC->aStatic ){
- sqlite3DbFree(db, pWC->a);
+ tdsqlite3DbFree(db, pWC->a);
}
}
@@ -130847,29 +147355,43 @@ SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){
** a bitmask indicating which tables are used in that expression
** tree.
*/
-SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet *pMaskSet, Expr *p){
+SQLITE_PRIVATE Bitmask tdsqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){
Bitmask mask;
- if( p==0 ) return 0;
- if( p->op==TK_COLUMN ){
- mask = sqlite3WhereGetMask(pMaskSet, p->iTable);
- return mask;
+ if( p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){
+ return tdsqlite3WhereGetMask(pMaskSet, p->iTable);
+ }else if( ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){
+ assert( p->op!=TK_IF_NULL_ROW );
+ return 0;
}
- assert( !ExprHasProperty(p, EP_TokenOnly) );
- mask = p->pRight ? sqlite3WhereExprUsage(pMaskSet, p->pRight) : 0;
- if( p->pLeft ) mask |= sqlite3WhereExprUsage(pMaskSet, p->pLeft);
- if( ExprHasProperty(p, EP_xIsSelect) ){
+ mask = (p->op==TK_IF_NULL_ROW) ? tdsqlite3WhereGetMask(pMaskSet, p->iTable) : 0;
+ if( p->pLeft ) mask |= tdsqlite3WhereExprUsageNN(pMaskSet, p->pLeft);
+ if( p->pRight ){
+ mask |= tdsqlite3WhereExprUsageNN(pMaskSet, p->pRight);
+ assert( p->x.pList==0 );
+ }else if( ExprHasProperty(p, EP_xIsSelect) ){
+ if( ExprHasProperty(p, EP_VarSelect) ) pMaskSet->bVarSelect = 1;
mask |= exprSelectUsage(pMaskSet, p->x.pSelect);
}else if( p->x.pList ){
- mask |= sqlite3WhereExprListUsage(pMaskSet, p->x.pList);
+ mask |= tdsqlite3WhereExprListUsage(pMaskSet, p->x.pList);
+ }
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( (p->op==TK_FUNCTION || p->op==TK_AGG_FUNCTION) && p->y.pWin ){
+ mask |= tdsqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pPartition);
+ mask |= tdsqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pOrderBy);
+ mask |= tdsqlite3WhereExprUsage(pMaskSet, p->y.pWin->pFilter);
}
+#endif
return mask;
}
-SQLITE_PRIVATE Bitmask sqlite3WhereExprListUsage(WhereMaskSet *pMaskSet, ExprList *pList){
+SQLITE_PRIVATE Bitmask tdsqlite3WhereExprUsage(WhereMaskSet *pMaskSet, Expr *p){
+ return p ? tdsqlite3WhereExprUsageNN(pMaskSet,p) : 0;
+}
+SQLITE_PRIVATE Bitmask tdsqlite3WhereExprListUsage(WhereMaskSet *pMaskSet, ExprList *pList){
int i;
Bitmask mask = 0;
if( pList ){
for(i=0; i<pList->nExpr; i++){
- mask |= sqlite3WhereExprUsage(pMaskSet, pList->a[i].pExpr);
+ mask |= tdsqlite3WhereExprUsage(pMaskSet, pList->a[i].pExpr);
}
}
return mask;
@@ -130884,7 +147406,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereExprListUsage(WhereMaskSet *pMaskSet, ExprLis
** virtual terms, so start analyzing at the end and work forward
** so that the added virtual terms are never processed.
*/
-SQLITE_PRIVATE void sqlite3WhereExprAnalyze(
+SQLITE_PRIVATE void tdsqlite3WhereExprAnalyze(
SrcList *pTabList, /* the FROM clause */
WhereClause *pWC /* the WHERE clause to be analyzed */
){
@@ -130901,7 +147423,7 @@ SQLITE_PRIVATE void sqlite3WhereExprAnalyze(
** Each function argument translates into an equality constraint against
** a HIDDEN column in the table.
*/
-SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(
+SQLITE_PRIVATE void tdsqlite3WhereTabFuncArgs(
Parse *pParse, /* Parsing context */
struct SrcList_item *pItem, /* The FROM clause term to process */
WhereClause *pWC /* Xfer function arguments to here */
@@ -130917,19 +147439,24 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(
pArgs = pItem->u1.pFuncArg;
if( pArgs==0 ) return;
for(j=k=0; j<pArgs->nExpr; j++){
+ Expr *pRhs;
while( k<pTab->nCol && (pTab->aCol[k].colFlags & COLFLAG_HIDDEN)==0 ){k++;}
if( k>=pTab->nCol ){
- sqlite3ErrorMsg(pParse, "too many arguments on %s() - max %d",
+ tdsqlite3ErrorMsg(pParse, "too many arguments on %s() - max %d",
pTab->zName, j);
return;
}
- pColRef = sqlite3ExprAlloc(pParse->db, TK_COLUMN, 0, 0);
+ pColRef = tdsqlite3ExprAlloc(pParse->db, TK_COLUMN, 0, 0);
if( pColRef==0 ) return;
pColRef->iTable = pItem->iCursor;
pColRef->iColumn = k++;
- pColRef->pTab = pTab;
- pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef,
- sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0);
+ pColRef->y.pTab = pTab;
+ pRhs = tdsqlite3PExpr(pParse, TK_UPLUS,
+ tdsqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0);
+ pTerm = tdsqlite3PExpr(pParse, TK_EQ, pColRef, pRhs);
+ if( pItem->fg.jointype & JT_LEFT ){
+ tdsqlite3SetJoinExpr(pTerm, pItem->iCursor);
+ }
whereClauseInsert(pWC, pTerm, TERM_DYNAMIC);
}
}
@@ -130957,19 +147484,34 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(
/* #include "sqliteInt.h" */
/* #include "whereInt.h" */
+/*
+** Extra information appended to the end of tdsqlite3_index_info but not
+** visible to the xBestIndex function, at least not directly. The
+** tdsqlite3_vtab_collation() interface knows how to reach it, however.
+**
+** This object is not an API and can be changed from one release to the
+** next. As long as allocateIndexInfo() and tdsqlite3_vtab_collation()
+** agree on the structure, all will be well.
+*/
+typedef struct HiddenIndexInfo HiddenIndexInfo;
+struct HiddenIndexInfo {
+ WhereClause *pWC; /* The Where clause being analyzed */
+ Parse *pParse; /* The parsing context */
+};
+
/* Forward declaration of methods */
-static int whereLoopResize(sqlite3*, WhereLoop*, int);
+static int whereLoopResize(tdsqlite3*, WhereLoop*, int);
/* Test variable that can be set to enable WHERE tracing */
#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG)
-/***/ int sqlite3WhereTrace = 0;
+/***/ int tdsqlite3WhereTrace = 0;
#endif
/*
** Return the estimated number of output rows from a WHERE clause
*/
-SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo *pWInfo){
+SQLITE_PRIVATE LogEst tdsqlite3WhereOutputRowCount(WhereInfo *pWInfo){
return pWInfo->nRowOut;
}
@@ -130977,7 +147519,7 @@ SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo *pWInfo){
** Return one of the WHERE_DISTINCT_xxxxx values to indicate how this
** WHERE clause returns outputs for DISTINCT processing.
*/
-SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo *pWInfo){
+SQLITE_PRIVATE int tdsqlite3WhereIsDistinct(WhereInfo *pWInfo){
return pWInfo->eDistinct;
}
@@ -130985,27 +147527,50 @@ SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo *pWInfo){
** Return TRUE if the WHERE clause returns rows in ORDER BY order.
** Return FALSE if the output needs to be sorted.
*/
-SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo *pWInfo){
+SQLITE_PRIVATE int tdsqlite3WhereIsOrdered(WhereInfo *pWInfo){
return pWInfo->nOBSat;
}
/*
-** Return TRUE if the innermost loop of the WHERE clause implementation
-** returns rows in ORDER BY order for complete run of the inner loop.
+** In the ORDER BY LIMIT optimization, if the inner-most loop is known
+** to emit rows in increasing order, and if the last row emitted by the
+** inner-most loop did not fit within the sorter, then we can skip all
+** subsequent rows for the current iteration of the inner loop (because they
+** will not fit in the sorter either) and continue with the second inner
+** loop - the loop immediately outside the inner-most.
+**
+** When a row does not fit in the sorter (because the sorter already
+** holds LIMIT+OFFSET rows that are smaller), then a jump is made to the
+** label returned by this function.
+**
+** If the ORDER BY LIMIT optimization applies, the jump destination should
+** be the continuation for the second-inner-most loop. If the ORDER BY
+** LIMIT optimization does not apply, then the jump destination should
+** be the continuation for the inner-most loop.
**
-** Across multiple iterations of outer loops, the output rows need not be
-** sorted. As long as rows are sorted for just the innermost loop, this
-** routine can return TRUE.
+** It is always safe for this routine to return the continuation of the
+** inner-most loop, in the sense that a correct answer will result.
+** Returning the continuation the second inner loop is an optimization
+** that might make the code run a little faster, but should not change
+** the final answer.
*/
-SQLITE_PRIVATE int sqlite3WhereOrderedInnerLoop(WhereInfo *pWInfo){
- return pWInfo->bOrderedInnerLoop;
+SQLITE_PRIVATE int tdsqlite3WhereOrderByLimitOptLabel(WhereInfo *pWInfo){
+ WhereLevel *pInner;
+ if( !pWInfo->bOrderedInnerLoop ){
+ /* The ORDER BY LIMIT optimization does not apply. Jump to the
+ ** continuation of the inner-most loop. */
+ return pWInfo->iContinue;
+ }
+ pInner = &pWInfo->a[pWInfo->nLevel-1];
+ assert( pInner->addrNxt!=0 );
+ return pInner->addrNxt;
}
/*
** Return the VDBE address or label to jump to in order to continue
** immediately with the next row of a WHERE clause.
*/
-SQLITE_PRIVATE int sqlite3WhereContinueLabel(WhereInfo *pWInfo){
+SQLITE_PRIVATE int tdsqlite3WhereContinueLabel(WhereInfo *pWInfo){
assert( pWInfo->iContinue!=0 );
return pWInfo->iContinue;
}
@@ -131014,13 +147579,13 @@ SQLITE_PRIVATE int sqlite3WhereContinueLabel(WhereInfo *pWInfo){
** Return the VDBE address or label to jump to in order to break
** out of a WHERE loop.
*/
-SQLITE_PRIVATE int sqlite3WhereBreakLabel(WhereInfo *pWInfo){
+SQLITE_PRIVATE int tdsqlite3WhereBreakLabel(WhereInfo *pWInfo){
return pWInfo->iBreak;
}
/*
** Return ONEPASS_OFF (0) if an UPDATE or DELETE statement is unable to
-** operate directly on the rowis returned by a WHERE clause. Return
+** operate directly on the rowids returned by a WHERE clause. Return
** ONEPASS_SINGLE (1) if the statement can operation directly because only
** a single row is to be changed. Return ONEPASS_MULTI (2) if the one-pass
** optimization can be used on multiple
@@ -131035,11 +147600,11 @@ SQLITE_PRIVATE int sqlite3WhereBreakLabel(WhereInfo *pWInfo){
** aiCur[0] and aiCur[1] both get -1 if the where-clause logic is
** unable to use the ONEPASS optimization.
*/
-SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo *pWInfo, int *aiCur){
+SQLITE_PRIVATE int tdsqlite3WhereOkOnePass(WhereInfo *pWInfo, int *aiCur){
memcpy(aiCur, pWInfo->aiCurOnePass, sizeof(int)*2);
#ifdef WHERETRACE_ENABLED
- if( sqlite3WhereTrace && pWInfo->eOnePass!=ONEPASS_OFF ){
- sqlite3DebugPrintf("%s cursors: %d %d\n",
+ if( tdsqlite3WhereTrace && pWInfo->eOnePass!=ONEPASS_OFF ){
+ tdsqlite3DebugPrintf("%s cursors: %d %d\n",
pWInfo->eOnePass==ONEPASS_SINGLE ? "ONEPASS_SINGLE" : "ONEPASS_MULTI",
aiCur[0], aiCur[1]);
}
@@ -131048,6 +147613,14 @@ SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo *pWInfo, int *aiCur){
}
/*
+** Return TRUE if the WHERE loop uses the OP_DeferredSeek opcode to move
+** the data cursor to the row selected by the index cursor.
+*/
+SQLITE_PRIVATE int tdsqlite3WhereUsesDeferredSeek(WhereInfo *pWInfo){
+ return pWInfo->bDeferredSeek;
+}
+
+/*
** Move the content of pSrc into pDest
*/
static void whereOrMove(WhereOrSet *pDest, WhereOrSet *pSrc){
@@ -131099,7 +147672,7 @@ whereOrInsert_done:
** Return the bitmask for the given cursor number. Return 0 if
** iCursor is not in the set.
*/
-SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){
+SQLITE_PRIVATE Bitmask tdsqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){
int i;
assert( pMaskSet->n<=(int)sizeof(Bitmask)*8 );
for(i=0; i<pMaskSet->n; i++){
@@ -131115,7 +147688,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){
**
** There is one cursor per table in the FROM clause. The number of
** tables in the FROM clause is limited by a test early in the
-** sqlite3WhereBegin() routine. So we know that the pMaskSet->ix[]
+** tdsqlite3WhereBegin() routine. So we know that the pMaskSet->ix[]
** array will never overflow.
*/
static void createMask(WhereMaskSet *pMaskSet, int iCursor){
@@ -131136,21 +147709,25 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
WhereTerm *pTerm; /* The term being tested */
int k = pScan->k; /* Where to start scanning */
- while( pScan->iEquiv<=pScan->nEquiv ){
- iCur = pScan->aiCur[pScan->iEquiv-1];
+ assert( pScan->iEquiv<=pScan->nEquiv );
+ pWC = pScan->pWC;
+ while(1){
iColumn = pScan->aiColumn[pScan->iEquiv-1];
- if( iColumn==XN_EXPR && pScan->pIdxExpr==0 ) return 0;
- while( (pWC = pScan->pWC)!=0 ){
+ iCur = pScan->aiCur[pScan->iEquiv-1];
+ assert( pWC!=0 );
+ do{
for(pTerm=pWC->a+k; k<pWC->nTerm; k++, pTerm++){
if( pTerm->leftCursor==iCur
&& pTerm->u.leftColumn==iColumn
&& (iColumn!=XN_EXPR
- || sqlite3ExprCompare(pTerm->pExpr->pLeft,pScan->pIdxExpr,iCur)==0)
+ || tdsqlite3ExprCompareSkip(pTerm->pExpr->pLeft,
+ pScan->pIdxExpr,iCur)==0)
&& (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_FromJoin))
){
if( (pTerm->eOperator & WO_EQUIV)!=0
&& pScan->nEquiv<ArraySize(pScan->aiCur)
- && (pX = sqlite3ExprSkipCollate(pTerm->pExpr->pRight))->op==TK_COLUMN
+ && (pX = tdsqlite3ExprSkipCollateAndLikely(pTerm->pExpr->pRight))->op
+ ==TK_COLUMN
){
int j;
for(j=0; j<pScan->nEquiv; j++){
@@ -131171,14 +147748,13 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
CollSeq *pColl;
Parse *pParse = pWC->pWInfo->pParse;
pX = pTerm->pExpr;
- if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){
+ if( !tdsqlite3IndexAffinityOk(pX, pScan->idxaff) ){
continue;
}
assert(pX->pLeft);
- pColl = sqlite3BinaryCompareCollSeq(pParse,
- pX->pLeft, pX->pRight);
+ pColl = tdsqlite3ExprCompareCollSeq(pParse, pX);
if( pColl==0 ) pColl = pParse->db->pDfltColl;
- if( sqlite3StrICmp(pColl->zName, pScan->zCollName) ){
+ if( tdsqlite3StrICmp(pColl->zName, pScan->zCollName) ){
continue;
}
}
@@ -131190,15 +147766,17 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
testcase( pTerm->eOperator & WO_IS );
continue;
}
+ pScan->pWC = pWC;
pScan->k = k+1;
return pTerm;
}
}
}
- pScan->pWC = pScan->pWC->pOuter;
+ pWC = pWC->pOuter;
k = 0;
- }
- pScan->pWC = pScan->pOrigWC;
+ }while( pWC!=0 );
+ if( pScan->iEquiv>=pScan->nEquiv ) break;
+ pWC = pScan->pOrigWC;
k = 0;
pScan->iEquiv++;
}
@@ -131206,6 +147784,17 @@ static WhereTerm *whereScanNext(WhereScan *pScan){
}
/*
+** This is whereScanInit() for the case of an index on an expression.
+** It is factored out into a separate tail-recursion subroutine so that
+** the normal whereScanInit() routine, which is a high-runner, does not
+** need to push registers onto the stack as part of its prologue.
+*/
+static SQLITE_NOINLINE WhereTerm *whereScanInitIndexExpr(WhereScan *pScan){
+ pScan->idxaff = tdsqlite3ExprAffinity(pScan->pIdxExpr);
+ return whereScanNext(pScan);
+}
+
+/*
** Initialize a WHERE clause scanner object. Return a pointer to the
** first match. Return NULL if there are no matches.
**
@@ -131232,31 +147821,34 @@ static WhereTerm *whereScanInit(
u32 opMask, /* Operator(s) to scan for */
Index *pIdx /* Must be compatible with this index */
){
- int j = 0;
-
- /* memset(pScan, 0, sizeof(*pScan)); */
pScan->pOrigWC = pWC;
pScan->pWC = pWC;
pScan->pIdxExpr = 0;
- if( pIdx ){
- j = iColumn;
- iColumn = pIdx->aiColumn[j];
- if( iColumn==XN_EXPR ) pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr;
- if( iColumn==pIdx->pTable->iPKey ) iColumn = XN_ROWID;
- }
- if( pIdx && iColumn>=0 ){
- pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity;
- pScan->zCollName = pIdx->azColl[j];
- }else{
- pScan->idxaff = 0;
- pScan->zCollName = 0;
- }
+ pScan->idxaff = 0;
+ pScan->zCollName = 0;
pScan->opMask = opMask;
pScan->k = 0;
pScan->aiCur[0] = iCur;
- pScan->aiColumn[0] = iColumn;
pScan->nEquiv = 1;
pScan->iEquiv = 1;
+ if( pIdx ){
+ int j = iColumn;
+ iColumn = pIdx->aiColumn[j];
+ if( iColumn==XN_EXPR ){
+ pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr;
+ pScan->zCollName = pIdx->azColl[j];
+ pScan->aiColumn[0] = XN_EXPR;
+ return whereScanInitIndexExpr(pScan);
+ }else if( iColumn==pIdx->pTable->iPKey ){
+ iColumn = XN_ROWID;
+ }else if( iColumn>=0 ){
+ pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity;
+ pScan->zCollName = pIdx->azColl[j];
+ }
+ }else if( iColumn==XN_EXPR ){
+ return 0;
+ }
+ pScan->aiColumn[0] = iColumn;
return whereScanNext(pScan);
}
@@ -131285,7 +147877,7 @@ static WhereTerm *whereScanInit(
** the form "X <op> <const-expr>" exist. If no terms with a constant RHS
** exist, try to return a term that does not use WO_EQUIV.
*/
-SQLITE_PRIVATE WhereTerm *sqlite3WhereFindTerm(
+SQLITE_PRIVATE WhereTerm *tdsqlite3WhereFindTerm(
WhereClause *pWC, /* The WHERE clause to be searched */
int iCur, /* Cursor number of LHS */
int iColumn, /* Column number of LHS */
@@ -131330,13 +147922,13 @@ static int findIndexCol(
const char *zColl = pIdx->azColl[iCol];
for(i=0; i<pList->nExpr; i++){
- Expr *p = sqlite3ExprSkipCollate(pList->a[i].pExpr);
+ Expr *p = tdsqlite3ExprSkipCollateAndLikely(pList->a[i].pExpr);
if( p->op==TK_COLUMN
&& p->iColumn==pIdx->aiColumn[iCol]
&& p->iTable==iBase
){
- CollSeq *pColl = sqlite3ExprCollSeq(pParse, pList->a[i].pExpr);
- if( pColl && 0==sqlite3StrICmp(pColl->zName, zColl) ){
+ CollSeq *pColl = tdsqlite3ExprNNCollSeq(pParse, pList->a[i].pExpr);
+ if( 0==tdsqlite3StrICmp(pColl->zName, zColl) ){
return i;
}
}
@@ -131394,7 +147986,7 @@ static int isDistinctRedundant(
** current SELECT is a correlated sub-query.
*/
for(i=0; i<pDistinct->nExpr; i++){
- Expr *p = sqlite3ExprSkipCollate(pDistinct->a[i].pExpr);
+ Expr *p = tdsqlite3ExprSkipCollateAndLikely(pDistinct->a[i].pExpr);
if( p->op==TK_COLUMN && p->iTable==iBase && p->iColumn<0 ) return 1;
}
@@ -131414,7 +148006,7 @@ static int isDistinctRedundant(
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
if( !IsUniqueIndex(pIdx) ) continue;
for(i=0; i<pIdx->nKeyCol; i++){
- if( 0==sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask)0, WO_EQ, pIdx) ){
+ if( 0==tdsqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask)0, WO_EQ, pIdx) ){
if( findIndexCol(pParse, pDistinct, iBase, pIdx, i)<0 ) break;
if( indexColumnNotNull(pIdx, i)==0 ) break;
}
@@ -131433,7 +148025,7 @@ static int isDistinctRedundant(
** Estimate the logarithm of the input value to base 2.
*/
static LogEst estLog(LogEst N){
- return N<=10 ? 0 : sqlite3LogEst(N) - 33;
+ return N<=10 ? 0 : tdsqlite3LogEst(N) - 33;
}
/*
@@ -131443,20 +148035,22 @@ static LogEst estLog(LogEst N){
** opcodes into OP_Copy when the table is being accessed via co-routine
** instead of via table lookup.
**
-** If the bIncrRowid parameter is 0, then any OP_Rowid instructions on
-** cursor iTabCur are transformed into OP_Null. Or, if bIncrRowid is non-zero,
-** then each OP_Rowid is transformed into an instruction to increment the
-** value stored in its output register.
+** If the iAutoidxCur is not zero, then any OP_Rowid instructions on
+** cursor iTabCur are transformed into OP_Sequence opcode for the
+** iAutoidxCur cursor, in order to generate unique rowids for the
+** automatic index being generated.
*/
static void translateColumnToCopy(
- Vdbe *v, /* The VDBE containing code to translate */
+ Parse *pParse, /* Parsing context */
int iStart, /* Translate from this opcode to the end */
int iTabCur, /* OP_Column/OP_Rowid references to this table */
int iRegister, /* The first column is in this register */
- int bIncrRowid /* If non-zero, transform OP_rowid to OP_AddImm(1) */
+ int iAutoidxCur /* If non-zero, cursor of autoindex being generated */
){
- VdbeOp *pOp = sqlite3VdbeGetOp(v, iStart);
- int iEnd = sqlite3VdbeCurrentAddr(v);
+ Vdbe *v = pParse->pVdbe;
+ VdbeOp *pOp = tdsqlite3VdbeGetOp(v, iStart);
+ int iEnd = tdsqlite3VdbeCurrentAddr(v);
+ if( pParse->db->mallocFailed ) return;
for(; iStart<iEnd; iStart++, pOp++){
if( pOp->p1!=iTabCur ) continue;
if( pOp->opcode==OP_Column ){
@@ -131465,11 +148059,9 @@ static void translateColumnToCopy(
pOp->p2 = pOp->p3;
pOp->p3 = 0;
}else if( pOp->opcode==OP_Rowid ){
- if( bIncrRowid ){
- /* Increment the value stored in the P2 operand of the OP_Rowid. */
- pOp->opcode = OP_AddImm;
- pOp->p1 = pOp->p2;
- pOp->p2 = 1;
+ if( iAutoidxCur ){
+ pOp->opcode = OP_Sequence;
+ pOp->p1 = iAutoidxCur;
}else{
pOp->opcode = OP_Null;
pOp->p1 = 0;
@@ -131480,17 +148072,17 @@ static void translateColumnToCopy(
}
/*
-** Two routines for printing the content of an sqlite3_index_info
+** Two routines for printing the content of an tdsqlite3_index_info
** structure. Used for testing and debugging only. If neither
** SQLITE_TEST or SQLITE_DEBUG are defined, then these routines
** are no-ops.
*/
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(WHERETRACE_ENABLED)
-static void TRACE_IDX_INPUTS(sqlite3_index_info *p){
+static void whereTraceIndexInfoInputs(tdsqlite3_index_info *p){
int i;
- if( !sqlite3WhereTrace ) return;
+ if( !tdsqlite3WhereTrace ) return;
for(i=0; i<p->nConstraint; i++){
- sqlite3DebugPrintf(" constraint[%d]: col=%d termid=%d op=%d usabled=%d\n",
+ tdsqlite3DebugPrintf(" constraint[%d]: col=%d termid=%d op=%d usabled=%d\n",
i,
p->aConstraint[i].iColumn,
p->aConstraint[i].iTermOffset,
@@ -131498,30 +148090,30 @@ static void TRACE_IDX_INPUTS(sqlite3_index_info *p){
p->aConstraint[i].usable);
}
for(i=0; i<p->nOrderBy; i++){
- sqlite3DebugPrintf(" orderby[%d]: col=%d desc=%d\n",
+ tdsqlite3DebugPrintf(" orderby[%d]: col=%d desc=%d\n",
i,
p->aOrderBy[i].iColumn,
p->aOrderBy[i].desc);
}
}
-static void TRACE_IDX_OUTPUTS(sqlite3_index_info *p){
+static void whereTraceIndexInfoOutputs(tdsqlite3_index_info *p){
int i;
- if( !sqlite3WhereTrace ) return;
+ if( !tdsqlite3WhereTrace ) return;
for(i=0; i<p->nConstraint; i++){
- sqlite3DebugPrintf(" usage[%d]: argvIdx=%d omit=%d\n",
+ tdsqlite3DebugPrintf(" usage[%d]: argvIdx=%d omit=%d\n",
i,
p->aConstraintUsage[i].argvIndex,
p->aConstraintUsage[i].omit);
}
- sqlite3DebugPrintf(" idxNum=%d\n", p->idxNum);
- sqlite3DebugPrintf(" idxStr=%s\n", p->idxStr);
- sqlite3DebugPrintf(" orderByConsumed=%d\n", p->orderByConsumed);
- sqlite3DebugPrintf(" estimatedCost=%g\n", p->estimatedCost);
- sqlite3DebugPrintf(" estimatedRows=%lld\n", p->estimatedRows);
+ tdsqlite3DebugPrintf(" idxNum=%d\n", p->idxNum);
+ tdsqlite3DebugPrintf(" idxStr=%s\n", p->idxStr);
+ tdsqlite3DebugPrintf(" orderByConsumed=%d\n", p->orderByConsumed);
+ tdsqlite3DebugPrintf(" estimatedCost=%g\n", p->estimatedCost);
+ tdsqlite3DebugPrintf(" estimatedRows=%lld\n", p->estimatedRows);
}
#else
-#define TRACE_IDX_INPUTS(A)
-#define TRACE_IDX_OUTPUTS(A)
+#define whereTraceIndexInfoInputs(A)
+#define whereTraceIndexInfoOutputs(A)
#endif
#ifndef SQLITE_OMIT_AUTOMATIC_INDEX
@@ -131538,10 +148130,19 @@ static int termCanDriveIndex(
char aff;
if( pTerm->leftCursor!=pSrc->iCursor ) return 0;
if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) return 0;
+ if( (pSrc->fg.jointype & JT_LEFT)
+ && !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
+ && (pTerm->eOperator & WO_IS)
+ ){
+ /* Cannot use an IS term from the WHERE clause as an index driver for
+ ** the RHS of a LEFT JOIN. Such a term can only be used if it is from
+ ** the ON clause. */
+ return 0;
+ }
if( (pTerm->prereqRight & notReady)!=0 ) return 0;
if( pTerm->u.leftColumn<0 ) return 0;
aff = pSrc->pTab->aCol[pTerm->u.leftColumn].affinity;
- if( !sqlite3IndexAffinityOk(pTerm->pExpr, aff) ) return 0;
+ if( !tdsqlite3IndexAffinityOk(pTerm->pExpr, aff) ) return 0;
testcase( pTerm->pExpr->op==TK_IS );
return 1;
}
@@ -131589,7 +148190,7 @@ static void constructAutomaticIndex(
** transient index on 2nd and subsequent iterations of the loop. */
v = pParse->pVdbe;
assert( v!=0 );
- addrInit = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
+ addrInit = tdsqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v);
/* Count the number of columns that will be added to the index
** and used to match WHERE clause constraints */
@@ -131606,9 +148207,9 @@ static void constructAutomaticIndex(
if( pLoop->prereq==0
&& (pTerm->wtFlags & TERM_VIRTUAL)==0
&& !ExprHasProperty(pExpr, EP_FromJoin)
- && sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor) ){
- pPartial = sqlite3ExprAnd(pParse->db, pPartial,
- sqlite3ExprDup(pParse->db, pExpr, 0));
+ && tdsqlite3ExprIsTableConstant(pExpr, pSrc->iCursor) ){
+ pPartial = tdsqlite3ExprAnd(pParse, pPartial,
+ tdsqlite3ExprDup(pParse->db, pExpr, 0));
}
if( termCanDriveIndex(pTerm, pSrc, notReady) ){
int iCol = pTerm->u.leftColumn;
@@ -131616,7 +148217,7 @@ static void constructAutomaticIndex(
testcase( iCol==BMS );
testcase( iCol==BMS-1 );
if( !sentWarning ){
- sqlite3_log(SQLITE_WARNING_AUTOINDEX,
+ tdsqlite3_log(SQLITE_WARNING_AUTOINDEX,
"automatic index on %s(%s)", pTable->zName,
pTable->aCol[iCol].zName);
sentWarning = 1;
@@ -131655,7 +148256,7 @@ static void constructAutomaticIndex(
}
/* Construct the Index object to describe this index */
- pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+1, 0, &zNotUsed);
+ pIdx = tdsqlite3AllocateIndexObject(pParse->db, nKeyCol+1, 0, &zNotUsed);
if( pIdx==0 ) goto end_auto_index_create;
pLoop->u.btree.pIndex = pIdx;
pIdx->zName = "auto-index";
@@ -131672,8 +148273,9 @@ static void constructAutomaticIndex(
Expr *pX = pTerm->pExpr;
idxCols |= cMask;
pIdx->aiColumn[n] = pTerm->u.leftColumn;
- pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight);
- pIdx->azColl[n] = pColl ? pColl->zName : sqlite3StrBINARY;
+ pColl = tdsqlite3ExprCompareCollSeq(pParse, pX);
+ assert( pColl!=0 || pParse->nErr>0 ); /* TH3 collate01.800 */
+ pIdx->azColl[n] = pColl ? pColl->zName : tdsqlite3StrBINARY;
n++;
}
}
@@ -131685,96 +148287,98 @@ static void constructAutomaticIndex(
for(i=0; i<mxBitCol; i++){
if( extraCols & MASKBIT(i) ){
pIdx->aiColumn[n] = i;
- pIdx->azColl[n] = sqlite3StrBINARY;
+ pIdx->azColl[n] = tdsqlite3StrBINARY;
n++;
}
}
if( pSrc->colUsed & MASKBIT(BMS-1) ){
for(i=BMS-1; i<pTable->nCol; i++){
pIdx->aiColumn[n] = i;
- pIdx->azColl[n] = sqlite3StrBINARY;
+ pIdx->azColl[n] = tdsqlite3StrBINARY;
n++;
}
}
assert( n==nKeyCol );
pIdx->aiColumn[n] = XN_ROWID;
- pIdx->azColl[n] = sqlite3StrBINARY;
+ pIdx->azColl[n] = tdsqlite3StrBINARY;
/* Create the automatic index */
assert( pLevel->iIdxCur>=0 );
pLevel->iIdxCur = pParse->nTab++;
- sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1);
- sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
+ tdsqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pIdx);
VdbeComment((v, "for %s", pTable->zName));
/* Fill the automatic index with content */
- sqlite3ExprCachePush(pParse);
pTabItem = &pWC->pWInfo->pTabList->a[pLevel->iFrom];
if( pTabItem->fg.viaCoroutine ){
int regYield = pTabItem->regReturn;
- addrCounter = sqlite3VdbeAddOp2(v, OP_Integer, 0, 0);
- sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub);
- addrTop = sqlite3VdbeAddOp1(v, OP_Yield, regYield);
+ addrCounter = tdsqlite3VdbeAddOp2(v, OP_Integer, 0, 0);
+ tdsqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub);
+ addrTop = tdsqlite3VdbeAddOp1(v, OP_Yield, regYield);
VdbeCoverage(v);
- VdbeComment((v, "next row of \"%s\"", pTabItem->pTab->zName));
+ VdbeComment((v, "next row of %s", pTabItem->pTab->zName));
}else{
- addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v);
+ addrTop = tdsqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v);
}
if( pPartial ){
- iContinue = sqlite3VdbeMakeLabel(v);
- sqlite3ExprIfFalse(pParse, pPartial, iContinue, SQLITE_JUMPIFNULL);
+ iContinue = tdsqlite3VdbeMakeLabel(pParse);
+ tdsqlite3ExprIfFalse(pParse, pPartial, iContinue, SQLITE_JUMPIFNULL);
pLoop->wsFlags |= WHERE_PARTIALIDX;
}
- regRecord = sqlite3GetTempReg(pParse);
- regBase = sqlite3GenerateIndexKey(
+ regRecord = tdsqlite3GetTempReg(pParse);
+ regBase = tdsqlite3GenerateIndexKey(
pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0
);
- sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord);
- sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
- if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue);
+ tdsqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT);
+ if( pPartial ) tdsqlite3VdbeResolveLabel(v, iContinue);
if( pTabItem->fg.viaCoroutine ){
- sqlite3VdbeChangeP2(v, addrCounter, regBase+n);
- translateColumnToCopy(v, addrTop, pLevel->iTabCur, pTabItem->regResult, 1);
- sqlite3VdbeGoto(v, addrTop);
+ tdsqlite3VdbeChangeP2(v, addrCounter, regBase+n);
+ testcase( pParse->db->mallocFailed );
+ assert( pLevel->iIdxCur>0 );
+ translateColumnToCopy(pParse, addrTop, pLevel->iTabCur,
+ pTabItem->regResult, pLevel->iIdxCur);
+ tdsqlite3VdbeGoto(v, addrTop);
pTabItem->fg.viaCoroutine = 0;
}else{
- sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v);
+ tdsqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v);
+ tdsqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX);
}
- sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX);
- sqlite3VdbeJumpHere(v, addrTop);
- sqlite3ReleaseTempReg(pParse, regRecord);
- sqlite3ExprCachePop(pParse);
+ tdsqlite3VdbeJumpHere(v, addrTop);
+ tdsqlite3ReleaseTempReg(pParse, regRecord);
/* Jump here when skipping the initialization */
- sqlite3VdbeJumpHere(v, addrInit);
+ tdsqlite3VdbeJumpHere(v, addrInit);
end_auto_index_create:
- sqlite3ExprDelete(pParse->db, pPartial);
+ tdsqlite3ExprDelete(pParse->db, pPartial);
}
#endif /* SQLITE_OMIT_AUTOMATIC_INDEX */
#ifndef SQLITE_OMIT_VIRTUALTABLE
/*
-** Allocate and populate an sqlite3_index_info structure. It is the
+** Allocate and populate an tdsqlite3_index_info structure. It is the
** responsibility of the caller to eventually release the structure
-** by passing the pointer returned by this function to sqlite3_free().
+** by passing the pointer returned by this function to tdsqlite3_free().
*/
-static sqlite3_index_info *allocateIndexInfo(
- Parse *pParse,
- WhereClause *pWC,
+static tdsqlite3_index_info *allocateIndexInfo(
+ Parse *pParse, /* The parsing context */
+ WhereClause *pWC, /* The WHERE clause being analyzed */
Bitmask mUnusable, /* Ignore terms with these prereqs */
- struct SrcList_item *pSrc,
- ExprList *pOrderBy,
+ struct SrcList_item *pSrc, /* The FROM clause term that is the vtab */
+ ExprList *pOrderBy, /* The ORDER BY clause */
u16 *pmNoOmit /* Mask of terms not to omit */
){
int i, j;
int nTerm;
- struct sqlite3_index_constraint *pIdxCons;
- struct sqlite3_index_orderby *pIdxOrderBy;
- struct sqlite3_index_constraint_usage *pUsage;
+ struct tdsqlite3_index_constraint *pIdxCons;
+ struct tdsqlite3_index_orderby *pIdxOrderBy;
+ struct tdsqlite3_index_constraint_usage *pUsage;
+ struct HiddenIndexInfo *pHidden;
WhereTerm *pTerm;
int nOrderBy;
- sqlite3_index_info *pIdxInfo;
+ tdsqlite3_index_info *pIdxInfo;
u16 mNoOmit = 0;
/* Count the number of possible WHERE clause constraints referring
@@ -131787,7 +148391,7 @@ static sqlite3_index_info *allocateIndexInfo(
testcase( pTerm->eOperator & WO_ISNULL );
testcase( pTerm->eOperator & WO_IS );
testcase( pTerm->eOperator & WO_ALL );
- if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV|WO_IS))==0 ) continue;
+ if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue;
if( pTerm->wtFlags & TERM_VNULL ) continue;
assert( pTerm->u.leftColumn>=(-1) );
nTerm++;
@@ -131795,7 +148399,7 @@ static sqlite3_index_info *allocateIndexInfo(
/* If the ORDER BY clause contains only columns in the current
** virtual table then allocate space for the aOrderBy part of
- ** the sqlite3_index_info structure.
+ ** the tdsqlite3_index_info structure.
*/
nOrderBy = 0;
if( pOrderBy ){
@@ -131803,39 +148407,34 @@ static sqlite3_index_info *allocateIndexInfo(
for(i=0; i<n; i++){
Expr *pExpr = pOrderBy->a[i].pExpr;
if( pExpr->op!=TK_COLUMN || pExpr->iTable!=pSrc->iCursor ) break;
+ if( pOrderBy->a[i].sortFlags & KEYINFO_ORDER_BIGNULL ) break;
}
if( i==n){
nOrderBy = n;
}
}
- /* Allocate the sqlite3_index_info structure
+ /* Allocate the tdsqlite3_index_info structure
*/
- pIdxInfo = sqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo)
+ pIdxInfo = tdsqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo)
+ (sizeof(*pIdxCons) + sizeof(*pUsage))*nTerm
- + sizeof(*pIdxOrderBy)*nOrderBy );
+ + sizeof(*pIdxOrderBy)*nOrderBy + sizeof(*pHidden) );
if( pIdxInfo==0 ){
- sqlite3ErrorMsg(pParse, "out of memory");
+ tdsqlite3ErrorMsg(pParse, "out of memory");
return 0;
}
-
- /* Initialize the structure. The sqlite3_index_info structure contains
- ** many fields that are declared "const" to prevent xBestIndex from
- ** changing them. We have to do some funky casting in order to
- ** initialize those fields.
- */
- pIdxCons = (struct sqlite3_index_constraint*)&pIdxInfo[1];
- pIdxOrderBy = (struct sqlite3_index_orderby*)&pIdxCons[nTerm];
- pUsage = (struct sqlite3_index_constraint_usage*)&pIdxOrderBy[nOrderBy];
- *(int*)&pIdxInfo->nConstraint = nTerm;
- *(int*)&pIdxInfo->nOrderBy = nOrderBy;
- *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint = pIdxCons;
- *(struct sqlite3_index_orderby**)&pIdxInfo->aOrderBy = pIdxOrderBy;
- *(struct sqlite3_index_constraint_usage**)&pIdxInfo->aConstraintUsage =
- pUsage;
-
+ pHidden = (struct HiddenIndexInfo*)&pIdxInfo[1];
+ pIdxCons = (struct tdsqlite3_index_constraint*)&pHidden[1];
+ pIdxOrderBy = (struct tdsqlite3_index_orderby*)&pIdxCons[nTerm];
+ pUsage = (struct tdsqlite3_index_constraint_usage*)&pIdxOrderBy[nOrderBy];
+ pIdxInfo->nOrderBy = nOrderBy;
+ pIdxInfo->aConstraint = pIdxCons;
+ pIdxInfo->aOrderBy = pIdxOrderBy;
+ pIdxInfo->aConstraintUsage = pUsage;
+ pHidden->pWC = pWC;
+ pHidden->pParse = pParse;
for(i=j=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
- u8 op;
+ u16 op;
if( pTerm->leftCursor != pSrc->iCursor ) continue;
if( pTerm->prereqRight & mUnusable ) continue;
assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) );
@@ -131843,42 +148442,59 @@ static sqlite3_index_info *allocateIndexInfo(
testcase( pTerm->eOperator & WO_IS );
testcase( pTerm->eOperator & WO_ISNULL );
testcase( pTerm->eOperator & WO_ALL );
- if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV|WO_IS))==0 ) continue;
+ if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue;
if( pTerm->wtFlags & TERM_VNULL ) continue;
+
+ /* tag-20191211-002: WHERE-clause constraints are not useful to the
+ ** right-hand table of a LEFT JOIN. See tag-20191211-001 for the
+ ** equivalent restriction for ordinary tables. */
+ if( (pSrc->fg.jointype & JT_LEFT)!=0
+ && !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
+ ){
+ continue;
+ }
assert( pTerm->u.leftColumn>=(-1) );
pIdxCons[j].iColumn = pTerm->u.leftColumn;
pIdxCons[j].iTermOffset = i;
- op = (u8)pTerm->eOperator & WO_ALL;
+ op = pTerm->eOperator & WO_ALL;
if( op==WO_IN ) op = WO_EQ;
- if( op==WO_MATCH ){
- op = pTerm->eMatchOp;
- }
- pIdxCons[j].op = op;
- /* The direct assignment in the previous line is possible only because
- ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The
- ** following asserts verify this fact. */
- assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ );
- assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT );
- assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE );
- assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT );
- assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE );
- assert( WO_MATCH==SQLITE_INDEX_CONSTRAINT_MATCH );
- assert( pTerm->eOperator & (WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_MATCH) );
-
- if( op & (WO_LT|WO_LE|WO_GT|WO_GE)
- && sqlite3ExprIsVector(pTerm->pExpr->pRight)
- ){
- if( i<16 ) mNoOmit |= (1 << i);
- if( op==WO_LT ) pIdxCons[j].op = WO_LE;
- if( op==WO_GT ) pIdxCons[j].op = WO_GE;
+ if( op==WO_AUX ){
+ pIdxCons[j].op = pTerm->eMatchOp;
+ }else if( op & (WO_ISNULL|WO_IS) ){
+ if( op==WO_ISNULL ){
+ pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_ISNULL;
+ }else{
+ pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_IS;
+ }
+ }else{
+ pIdxCons[j].op = (u8)op;
+ /* The direct assignment in the previous line is possible only because
+ ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The
+ ** following asserts verify this fact. */
+ assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ );
+ assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT );
+ assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE );
+ assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT );
+ assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE );
+ assert( pTerm->eOperator&(WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_AUX) );
+
+ if( op & (WO_LT|WO_LE|WO_GT|WO_GE)
+ && tdsqlite3ExprIsVector(pTerm->pExpr->pRight)
+ ){
+ testcase( j!=i );
+ if( j<16 ) mNoOmit |= (1 << j);
+ if( op==WO_LT ) pIdxCons[j].op = WO_LE;
+ if( op==WO_GT ) pIdxCons[j].op = WO_GE;
+ }
}
j++;
}
+ pIdxInfo->nConstraint = j;
for(i=0; i<nOrderBy; i++){
Expr *pExpr = pOrderBy->a[i].pExpr;
pIdxOrderBy[i].iColumn = pExpr->iColumn;
- pIdxOrderBy[i].desc = pOrderBy->a[i].sortOrder;
+ pIdxOrderBy[i].desc = pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC;
}
*pmNoOmit = mNoOmit;
@@ -131888,53 +148504,43 @@ static sqlite3_index_info *allocateIndexInfo(
/*
** The table object reference passed as the second argument to this function
** must represent a virtual table. This function invokes the xBestIndex()
-** method of the virtual table with the sqlite3_index_info object that
+** method of the virtual table with the tdsqlite3_index_info object that
** comes in as the 3rd argument to this function.
**
-** If an error occurs, pParse is populated with an error message and a
-** non-zero value is returned. Otherwise, 0 is returned and the output
-** part of the sqlite3_index_info structure is left populated.
+** If an error occurs, pParse is populated with an error message and an
+** appropriate error code is returned. A return of SQLITE_CONSTRAINT from
+** xBestIndex is not considered an error. SQLITE_CONSTRAINT indicates that
+** the current configuration of "unusable" flags in tdsqlite3_index_info can
+** not result in a valid plan.
**
** Whether or not an error is returned, it is the responsibility of the
** caller to eventually free p->idxStr if p->needToFreeIdxStr indicates
** that this is required.
*/
-static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){
- sqlite3_vtab *pVtab = sqlite3GetVTable(pParse->db, pTab)->pVtab;
+static int vtabBestIndex(Parse *pParse, Table *pTab, tdsqlite3_index_info *p){
+ tdsqlite3_vtab *pVtab = tdsqlite3GetVTable(pParse->db, pTab)->pVtab;
int rc;
- TRACE_IDX_INPUTS(p);
+ whereTraceIndexInfoInputs(p);
rc = pVtab->pModule->xBestIndex(pVtab, p);
- TRACE_IDX_OUTPUTS(p);
+ whereTraceIndexInfoOutputs(p);
- if( rc!=SQLITE_OK ){
+ if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){
if( rc==SQLITE_NOMEM ){
- sqlite3OomFault(pParse->db);
+ tdsqlite3OomFault(pParse->db);
}else if( !pVtab->zErrMsg ){
- sqlite3ErrorMsg(pParse, "%s", sqlite3ErrStr(rc));
+ tdsqlite3ErrorMsg(pParse, "%s", tdsqlite3ErrStr(rc));
}else{
- sqlite3ErrorMsg(pParse, "%s", pVtab->zErrMsg);
+ tdsqlite3ErrorMsg(pParse, "%s", pVtab->zErrMsg);
}
}
- sqlite3_free(pVtab->zErrMsg);
+ tdsqlite3_free(pVtab->zErrMsg);
pVtab->zErrMsg = 0;
-
-#if 0
- /* This error is now caught by the caller.
- ** Search for "xBestIndex malfunction" below */
- for(i=0; i<p->nConstraint; i++){
- if( !p->aConstraint[i].usable && p->aConstraintUsage[i].argvIndex>0 ){
- sqlite3ErrorMsg(pParse,
- "table %s: xBestIndex returned an invalid plan", pTab->zName);
- }
- }
-#endif
-
- return pParse->nErr;
+ return rc;
}
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
/*
** Estimate the location of a particular key among all keys in an
** index. Store the results in aStat as follows:
@@ -132037,7 +148643,7 @@ static int whereKeyStats(
}
pRec->nField = n;
- res = sqlite3VdbeRecordCompare(aSample[iSamp].n, aSample[iSamp].p, pRec);
+ res = tdsqlite3VdbeRecordCompare(aSample[iSamp].n, aSample[iSamp].p, pRec);
if( res<0 ){
iLower = aSample[iSamp].anLt[n-1] + aSample[iSamp].anEq[n-1];
iMin = iTest+1;
@@ -132062,7 +148668,7 @@ static int whereKeyStats(
assert( i<pIdx->nSample );
assert( iCol==nField-1 );
pRec->nField = nField;
- assert( 0==sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)
+ assert( 0==tdsqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)
|| pParse->db->mallocFailed
);
}else{
@@ -132072,7 +148678,7 @@ static int whereKeyStats(
assert( i<=pIdx->nSample && i>=0 );
pRec->nField = iCol+1;
assert( i==pIdx->nSample
- || sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)>0
+ || tdsqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)>0
|| pParse->db->mallocFailed );
/* if i==0 and iCol==0, then record pRec is smaller than all samples
@@ -132081,12 +148687,12 @@ static int whereKeyStats(
** If (i>0), then pRec must also be greater than sample (i-1). */
if( iCol>0 ){
pRec->nField = iCol;
- assert( sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)<=0
+ assert( tdsqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)<=0
|| pParse->db->mallocFailed );
}
if( i>0 ){
pRec->nField = nField;
- assert( sqlite3VdbeRecordCompare(aSample[i-1].n, aSample[i-1].p, pRec)<0
+ assert( tdsqlite3VdbeRecordCompare(aSample[i-1].n, aSample[i-1].p, pRec)<0
|| pParse->db->mallocFailed );
}
}
@@ -132104,7 +148710,7 @@ static int whereKeyStats(
** is larger than all samples in the array. */
tRowcnt iUpper, iGap;
if( i>=pIdx->nSample ){
- iUpper = sqlite3LogEstToInt(pIdx->aiRowLogEst[0]);
+ iUpper = tdsqlite3LogEstToInt(pIdx->aiRowLogEst[0]);
}else{
iUpper = aSample[i].anLt[iCol];
}
@@ -132120,14 +148726,14 @@ static int whereKeyStats(
iGap = iGap/3;
}
aStat[0] = iLower + iGap;
- aStat[1] = pIdx->aAvgEq[iCol];
+ aStat[1] = pIdx->aAvgEq[nField-1];
}
/* Restore the pRec->nField value before returning. */
pRec->nField = nField;
return i;
}
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
/*
** If it is not NULL, pTerm is a term that provides an upper or lower
@@ -132146,28 +148752,29 @@ static LogEst whereRangeAdjust(WhereTerm *pTerm, LogEst nNew){
if( pTerm->truthProb<=0 ){
nRet += pTerm->truthProb;
}else if( (pTerm->wtFlags & TERM_VNULL)==0 ){
- nRet -= 20; assert( 20==sqlite3LogEst(4) );
+ nRet -= 20; assert( 20==tdsqlite3LogEst(4) );
}
}
return nRet;
}
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
/*
** Return the affinity for a single column of an index.
*/
-SQLITE_PRIVATE char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){
+SQLITE_PRIVATE char tdsqlite3IndexColumnAffinity(tdsqlite3 *db, Index *pIdx, int iCol){
assert( iCol>=0 && iCol<pIdx->nColumn );
if( !pIdx->zColAff ){
- if( sqlite3IndexAffinityStr(db, pIdx)==0 ) return SQLITE_AFF_BLOB;
+ if( tdsqlite3IndexAffinityStr(db, pIdx)==0 ) return SQLITE_AFF_BLOB;
}
+ assert( pIdx->zColAff[iCol]!=0 );
return pIdx->zColAff[iCol];
}
#endif
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
/*
** This function is called to estimate the number of rows visited by a
** range-scan on a skip-scan index. For example:
@@ -132212,24 +148819,24 @@ static int whereRangeSkipScanEst(
){
Index *p = pLoop->u.btree.pIndex;
int nEq = pLoop->u.btree.nEq;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
int nLower = -1;
int nUpper = p->nSample+1;
int rc = SQLITE_OK;
- u8 aff = sqlite3IndexColumnAffinity(db, p, nEq);
+ u8 aff = tdsqlite3IndexColumnAffinity(db, p, nEq);
CollSeq *pColl;
- sqlite3_value *p1 = 0; /* Value extracted from pLower */
- sqlite3_value *p2 = 0; /* Value extracted from pUpper */
- sqlite3_value *pVal = 0; /* Value extracted from record */
+ tdsqlite3_value *p1 = 0; /* Value extracted from pLower */
+ tdsqlite3_value *p2 = 0; /* Value extracted from pUpper */
+ tdsqlite3_value *pVal = 0; /* Value extracted from record */
- pColl = sqlite3LocateCollSeq(pParse, p->azColl[nEq]);
+ pColl = tdsqlite3LocateCollSeq(pParse, p->azColl[nEq]);
if( pLower ){
- rc = sqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight, aff, &p1);
+ rc = tdsqlite3Stat4ValueFromExpr(pParse, pLower->pExpr->pRight, aff, &p1);
nLower = 0;
}
if( pUpper && rc==SQLITE_OK ){
- rc = sqlite3Stat4ValueFromExpr(pParse, pUpper->pExpr->pRight, aff, &p2);
+ rc = tdsqlite3Stat4ValueFromExpr(pParse, pUpper->pExpr->pRight, aff, &p2);
nUpper = p2 ? 0 : p->nSample;
}
@@ -132237,13 +148844,13 @@ static int whereRangeSkipScanEst(
int i;
int nDiff;
for(i=0; rc==SQLITE_OK && i<p->nSample; i++){
- rc = sqlite3Stat4Column(db, p->aSample[i].p, p->aSample[i].n, nEq, &pVal);
+ rc = tdsqlite3Stat4Column(db, p->aSample[i].p, p->aSample[i].n, nEq, &pVal);
if( rc==SQLITE_OK && p1 ){
- int res = sqlite3MemCompare(p1, pVal, pColl);
+ int res = tdsqlite3MemCompare(p1, pVal, pColl);
if( res>=0 ) nLower++;
}
if( rc==SQLITE_OK && p2 ){
- int res = sqlite3MemCompare(p2, pVal, pColl);
+ int res = tdsqlite3MemCompare(p2, pVal, pColl);
if( res>=0 ) nUpper++;
}
}
@@ -132256,7 +148863,7 @@ static int whereRangeSkipScanEst(
** the number of rows visited. Otherwise, estimate the number of rows
** using the method described in the header comment for this function. */
if( nDiff!=1 || pUpper==0 || pLower==0 ){
- int nAdjust = (sqlite3LogEst(p->nSample) - sqlite3LogEst(nDiff));
+ int nAdjust = (tdsqlite3LogEst(p->nSample) - tdsqlite3LogEst(nDiff));
pLoop->nOut -= nAdjust;
*pbDone = 1;
WHERETRACE(0x10, ("range skip-scan regions: %u..%u adjust=%d est=%d\n",
@@ -132267,13 +148874,13 @@ static int whereRangeSkipScanEst(
assert( *pbDone==0 );
}
- sqlite3ValueFree(p1);
- sqlite3ValueFree(p2);
- sqlite3ValueFree(pVal);
+ tdsqlite3ValueFree(p1);
+ tdsqlite3ValueFree(p2);
+ tdsqlite3ValueFree(pVal);
return rc;
}
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
/*
** This function is used to estimate the number of rows that will be visited
@@ -132304,7 +148911,7 @@ static int whereRangeSkipScanEst(
**
** then nEq is set to 0.
**
-** When this function is called, *pnOut is set to the sqlite3LogEst() of the
+** When this function is called, *pnOut is set to the tdsqlite3LogEst() of the
** number of rows that the index scan is expected to visit without
** considering the range constraints. If nEq is 0, then *pnOut is the number of
** rows in the index. Assuming no error occurs, *pnOut is adjusted (reduced)
@@ -132326,11 +148933,13 @@ static int whereRangeScanEst(
int nOut = pLoop->nOut;
LogEst nNew;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
Index *p = pLoop->u.btree.pIndex;
int nEq = pLoop->u.btree.nEq;
- if( p->nSample>0 && nEq<p->nSampleCol ){
+ if( p->nSample>0 && ALWAYS(nEq<p->nSampleCol)
+ && OptimizationEnabled(pParse->db, SQLITE_Stat4)
+ ){
if( nEq==pBuilder->nRecValid ){
UnpackedRecord *pRec = pBuilder->pRec;
tRowcnt a[2];
@@ -132390,11 +148999,11 @@ static int whereRangeScanEst(
if( pLower ){
int n; /* Values extracted from pExpr */
Expr *pExpr = pLower->pExpr->pRight;
- rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nBtm, nEq, &n);
+ rc = tdsqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nBtm, nEq, &n);
if( rc==SQLITE_OK && n ){
tRowcnt iNew;
u16 mask = WO_GT|WO_LE;
- if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT);
+ if( tdsqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT);
iLwrIdx = whereKeyStats(pParse, p, pRec, 0, a);
iNew = a[0] + ((pLower->eOperator & mask) ? a[1] : 0);
if( iNew>iLower ) iLower = iNew;
@@ -132407,11 +149016,11 @@ static int whereRangeScanEst(
if( pUpper ){
int n; /* Values extracted from pExpr */
Expr *pExpr = pUpper->pExpr->pRight;
- rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nTop, nEq, &n);
+ rc = tdsqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nTop, nEq, &n);
if( rc==SQLITE_OK && n ){
tRowcnt iNew;
u16 mask = WO_GT|WO_LE;
- if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT);
+ if( tdsqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT);
iUprIdx = whereKeyStats(pParse, p, pRec, 1, a);
iNew = a[0] + ((pUpper->eOperator & mask) ? a[1] : 0);
if( iNew<iUpper ) iUpper = iNew;
@@ -132423,14 +149032,14 @@ static int whereRangeScanEst(
pBuilder->pRec = pRec;
if( rc==SQLITE_OK ){
if( iUpper>iLower ){
- nNew = sqlite3LogEst(iUpper - iLower);
+ nNew = tdsqlite3LogEst(iUpper - iLower);
/* TUNING: If both iUpper and iLower are derived from the same
** sample, then assume they are 4x more selective. This brings
** the estimated selectivity more in line with what it would be
- ** if estimated without the use of STAT3/4 tables. */
- if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==sqlite3LogEst(4) );
+ ** if estimated without the use of STAT4 tables. */
+ if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==tdsqlite3LogEst(4) );
}else{
- nNew = 10; assert( 10==sqlite3LogEst(2) );
+ nNew = 10; assert( 10==tdsqlite3LogEst(2) );
}
if( nNew<nOut ){
nOut = nNew;
@@ -132476,12 +149085,12 @@ static int whereRangeScanEst(
return rc;
}
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
/*
** Estimate the number of rows that will be returned based on
** an equality constraint x=VALUE and where that VALUE occurs in
** the histogram data. This only works when x is the left-most
-** column of an index and sqlite_stat3 histogram data is available
+** column of an index and sqlite_stat4 histogram data is available
** for that index. When pExpr==NULL that means the constraint is
** "x IS NULL" instead of "x=VALUE".
**
@@ -132519,14 +149128,14 @@ static int whereEqualScanEst(
return SQLITE_NOTFOUND;
}
- /* This is an optimization only. The call to sqlite3Stat4ProbeSetValue()
+ /* This is an optimization only. The call to tdsqlite3Stat4ProbeSetValue()
** below would return the same value. */
if( nEq>=p->nColumn ){
*pnRow = 1;
return SQLITE_OK;
}
- rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, 1, nEq-1, &bOk);
+ rc = tdsqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, 1, nEq-1, &bOk);
pBuilder->pRec = pRec;
if( rc!=SQLITE_OK ) return rc;
if( bOk==0 ) return SQLITE_NOTFOUND;
@@ -132539,9 +149148,9 @@ static int whereEqualScanEst(
return rc;
}
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
/*
** Estimate the number of rows that will be returned based on
** an IN constraint where the right-hand side of the IN operator
@@ -132565,7 +149174,7 @@ static int whereInScanEst(
tRowcnt *pnRow /* Write the revised row estimate here */
){
Index *p = pBuilder->pNew->u.btree.pIndex;
- i64 nRow0 = sqlite3LogEstToInt(p->aiRowLogEst[0]);
+ i64 nRow0 = tdsqlite3LogEstToInt(p->aiRowLogEst[0]);
int nRecValid = pBuilder->nRecValid;
int rc = SQLITE_OK; /* Subfunction return code */
tRowcnt nEst; /* Number of rows for a single term */
@@ -132588,42 +149197,50 @@ static int whereInScanEst(
assert( pBuilder->nRecValid==nRecValid );
return rc;
}
-#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */
+#endif /* SQLITE_ENABLE_STAT4 */
#ifdef WHERETRACE_ENABLED
/*
** Print the content of a WhereTerm object
*/
-static void whereTermPrint(WhereTerm *pTerm, int iTerm){
+SQLITE_PRIVATE void tdsqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){
if( pTerm==0 ){
- sqlite3DebugPrintf("TERM-%-3d NULL\n", iTerm);
+ tdsqlite3DebugPrintf("TERM-%-3d NULL\n", iTerm);
}else{
- char zType[4];
+ char zType[8];
char zLeft[50];
- memcpy(zType, "...", 4);
+ memcpy(zType, "....", 5);
if( pTerm->wtFlags & TERM_VIRTUAL ) zType[0] = 'V';
if( pTerm->eOperator & WO_EQUIV ) zType[1] = 'E';
if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) zType[2] = 'L';
+ if( pTerm->wtFlags & TERM_CODED ) zType[3] = 'C';
if( pTerm->eOperator & WO_SINGLE ){
- sqlite3_snprintf(sizeof(zLeft),zLeft,"left={%d:%d}",
+ tdsqlite3_snprintf(sizeof(zLeft),zLeft,"left={%d:%d}",
pTerm->leftCursor, pTerm->u.leftColumn);
}else if( (pTerm->eOperator & WO_OR)!=0 && pTerm->u.pOrInfo!=0 ){
- sqlite3_snprintf(sizeof(zLeft),zLeft,"indexable=0x%lld",
+ tdsqlite3_snprintf(sizeof(zLeft),zLeft,"indexable=0x%lld",
pTerm->u.pOrInfo->indexable);
}else{
- sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor);
+ tdsqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor);
+ }
+ tdsqlite3DebugPrintf(
+ "TERM-%-3d %p %s %-12s op=%03x wtFlags=%04x",
+ iTerm, pTerm, zType, zLeft, pTerm->eOperator, pTerm->wtFlags);
+ /* The 0x10000 .wheretrace flag causes extra information to be
+ ** shown about each Term */
+ if( tdsqlite3WhereTrace & 0x10000 ){
+ tdsqlite3DebugPrintf(" prob=%-3d prereq=%llx,%llx",
+ pTerm->truthProb, (u64)pTerm->prereqAll, (u64)pTerm->prereqRight);
}
- sqlite3DebugPrintf(
- "TERM-%-3d %p %s %-12s prob=%-3d op=0x%03x wtFlags=0x%04x",
- iTerm, pTerm, zType, zLeft, pTerm->truthProb,
- pTerm->eOperator, pTerm->wtFlags);
if( pTerm->iField ){
- sqlite3DebugPrintf(" iField=%d\n", pTerm->iField);
- }else{
- sqlite3DebugPrintf("\n");
+ tdsqlite3DebugPrintf(" iField=%d", pTerm->iField);
}
- sqlite3TreeViewExpr(0, pTerm->pExpr, 0);
+ if( pTerm->iParent>=0 ){
+ tdsqlite3DebugPrintf(" iParent=%d", pTerm->iParent);
+ }
+ tdsqlite3DebugPrintf("\n");
+ tdsqlite3TreeViewExpr(0, pTerm->pExpr, 0);
}
}
#endif
@@ -132632,10 +149249,10 @@ static void whereTermPrint(WhereTerm *pTerm, int iTerm){
/*
** Show the complete content of a WhereClause
*/
-SQLITE_PRIVATE void sqlite3WhereClausePrint(WhereClause *pWC){
+SQLITE_PRIVATE void tdsqlite3WhereClausePrint(WhereClause *pWC){
int i;
for(i=0; i<pWC->nTerm; i++){
- whereTermPrint(&pWC->a[i], i);
+ tdsqlite3WhereTermPrint(&pWC->a[i], i);
}
}
#endif
@@ -132644,49 +149261,49 @@ SQLITE_PRIVATE void sqlite3WhereClausePrint(WhereClause *pWC){
/*
** Print a WhereLoop object for debugging purposes
*/
-static void whereLoopPrint(WhereLoop *p, WhereClause *pWC){
+SQLITE_PRIVATE void tdsqlite3WhereLoopPrint(WhereLoop *p, WhereClause *pWC){
WhereInfo *pWInfo = pWC->pWInfo;
int nb = 1+(pWInfo->pTabList->nSrc+3)/4;
struct SrcList_item *pItem = pWInfo->pTabList->a + p->iTab;
Table *pTab = pItem->pTab;
Bitmask mAll = (((Bitmask)1)<<(nb*4)) - 1;
- sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId,
+ tdsqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId,
p->iTab, nb, p->maskSelf, nb, p->prereq & mAll);
- sqlite3DebugPrintf(" %12s",
+ tdsqlite3DebugPrintf(" %12s",
pItem->zAlias ? pItem->zAlias : pTab->zName);
if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){
const char *zName;
if( p->u.btree.pIndex && (zName = p->u.btree.pIndex->zName)!=0 ){
if( strncmp(zName, "sqlite_autoindex_", 17)==0 ){
- int i = sqlite3Strlen30(zName) - 1;
+ int i = tdsqlite3Strlen30(zName) - 1;
while( zName[i]!='_' ) i--;
zName += i;
}
- sqlite3DebugPrintf(".%-16s %2d", zName, p->u.btree.nEq);
+ tdsqlite3DebugPrintf(".%-16s %2d", zName, p->u.btree.nEq);
}else{
- sqlite3DebugPrintf("%20s","");
+ tdsqlite3DebugPrintf("%20s","");
}
}else{
char *z;
if( p->u.vtab.idxStr ){
- z = sqlite3_mprintf("(%d,\"%s\",%x)",
+ z = tdsqlite3_mprintf("(%d,\"%s\",%#x)",
p->u.vtab.idxNum, p->u.vtab.idxStr, p->u.vtab.omitMask);
}else{
- z = sqlite3_mprintf("(%d,%x)", p->u.vtab.idxNum, p->u.vtab.omitMask);
+ z = tdsqlite3_mprintf("(%d,%x)", p->u.vtab.idxNum, p->u.vtab.omitMask);
}
- sqlite3DebugPrintf(" %-19s", z);
- sqlite3_free(z);
+ tdsqlite3DebugPrintf(" %-19s", z);
+ tdsqlite3_free(z);
}
if( p->wsFlags & WHERE_SKIPSCAN ){
- sqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->nSkip);
+ tdsqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->nSkip);
}else{
- sqlite3DebugPrintf(" f %05x N %d", p->wsFlags, p->nLTerm);
+ tdsqlite3DebugPrintf(" f %05x N %d", p->wsFlags, p->nLTerm);
}
- sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut);
- if( p->nLTerm && (sqlite3WhereTrace & 0x100)!=0 ){
+ tdsqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut);
+ if( p->nLTerm && (tdsqlite3WhereTrace & 0x100)!=0 ){
int i;
for(i=0; i<p->nLTerm; i++){
- whereTermPrint(p->aLTerm[i], i);
+ tdsqlite3WhereTermPrint(p->aLTerm[i], i);
}
}
}
@@ -132706,15 +149323,15 @@ static void whereLoopInit(WhereLoop *p){
/*
** Clear the WhereLoop.u union. Leave WhereLoop.pLTerm intact.
*/
-static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){
+static void whereLoopClearUnion(tdsqlite3 *db, WhereLoop *p){
if( p->wsFlags & (WHERE_VIRTUALTABLE|WHERE_AUTO_INDEX) ){
if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 && p->u.vtab.needFree ){
- sqlite3_free(p->u.vtab.idxStr);
+ tdsqlite3_free(p->u.vtab.idxStr);
p->u.vtab.needFree = 0;
p->u.vtab.idxStr = 0;
}else if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 && p->u.btree.pIndex!=0 ){
- sqlite3DbFree(db, p->u.btree.pIndex->zColAff);
- sqlite3DbFree(db, p->u.btree.pIndex);
+ tdsqlite3DbFree(db, p->u.btree.pIndex->zColAff);
+ tdsqlite3DbFreeNN(db, p->u.btree.pIndex);
p->u.btree.pIndex = 0;
}
}
@@ -132723,8 +149340,8 @@ static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){
/*
** Deallocate internal memory used by a WhereLoop object
*/
-static void whereLoopClear(sqlite3 *db, WhereLoop *p){
- if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFree(db, p->aLTerm);
+static void whereLoopClear(tdsqlite3 *db, WhereLoop *p){
+ if( p->aLTerm!=p->aLTermSpace ) tdsqlite3DbFreeNN(db, p->aLTerm);
whereLoopClearUnion(db, p);
whereLoopInit(p);
}
@@ -132732,14 +149349,14 @@ static void whereLoopClear(sqlite3 *db, WhereLoop *p){
/*
** Increase the memory allocation for pLoop->aLTerm[] to be at least n.
*/
-static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){
+static int whereLoopResize(tdsqlite3 *db, WhereLoop *p, int n){
WhereTerm **paNew;
if( p->nLSlot>=n ) return SQLITE_OK;
n = (n+7)&~7;
- paNew = sqlite3DbMallocRawNN(db, sizeof(p->aLTerm[0])*n);
+ paNew = tdsqlite3DbMallocRawNN(db, sizeof(p->aLTerm[0])*n);
if( paNew==0 ) return SQLITE_NOMEM_BKPT;
memcpy(paNew, p->aLTerm, sizeof(p->aLTerm[0])*p->nLSlot);
- if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFree(db, p->aLTerm);
+ if( p->aLTerm!=p->aLTermSpace ) tdsqlite3DbFreeNN(db, p->aLTerm);
p->aLTerm = paNew;
p->nLSlot = n;
return SQLITE_OK;
@@ -132748,7 +149365,7 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){
/*
** Transfer content from the second pLoop into the first.
*/
-static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){
+static int whereLoopXfer(tdsqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){
whereLoopClearUnion(db, pTo);
if( whereLoopResize(db, pTo, pFrom->nLTerm) ){
memset(&pTo->u, 0, sizeof(pTo->u));
@@ -132767,49 +149384,50 @@ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){
/*
** Delete a WhereLoop object
*/
-static void whereLoopDelete(sqlite3 *db, WhereLoop *p){
+static void whereLoopDelete(tdsqlite3 *db, WhereLoop *p){
whereLoopClear(db, p);
- sqlite3DbFree(db, p);
+ tdsqlite3DbFreeNN(db, p);
}
/*
** Free a WhereInfo structure
*/
-static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){
- if( ALWAYS(pWInfo) ){
- int i;
- for(i=0; i<pWInfo->nLevel; i++){
- WhereLevel *pLevel = &pWInfo->a[i];
- if( pLevel->pWLoop && (pLevel->pWLoop->wsFlags & WHERE_IN_ABLE) ){
- sqlite3DbFree(db, pLevel->u.in.aInLoop);
- }
- }
- sqlite3WhereClauseClear(&pWInfo->sWC);
- while( pWInfo->pLoops ){
- WhereLoop *p = pWInfo->pLoops;
- pWInfo->pLoops = p->pNextLoop;
- whereLoopDelete(db, p);
+static void whereInfoFree(tdsqlite3 *db, WhereInfo *pWInfo){
+ int i;
+ assert( pWInfo!=0 );
+ for(i=0; i<pWInfo->nLevel; i++){
+ WhereLevel *pLevel = &pWInfo->a[i];
+ if( pLevel->pWLoop && (pLevel->pWLoop->wsFlags & WHERE_IN_ABLE) ){
+ tdsqlite3DbFree(db, pLevel->u.in.aInLoop);
}
- sqlite3DbFree(db, pWInfo);
}
+ tdsqlite3WhereClauseClear(&pWInfo->sWC);
+ while( pWInfo->pLoops ){
+ WhereLoop *p = pWInfo->pLoops;
+ pWInfo->pLoops = p->pNextLoop;
+ whereLoopDelete(db, p);
+ }
+ assert( pWInfo->pExprMods==0 );
+ tdsqlite3DbFreeNN(db, pWInfo);
}
/*
** Return TRUE if all of the following are true:
**
** (1) X has the same or lower cost that Y
-** (2) X is a proper subset of Y
-** (3) X skips at least as many columns as Y
-**
-** By "proper subset" we mean that X uses fewer WHERE clause terms
-** than Y and that every WHERE clause term used by X is also used
-** by Y.
+** (2) X uses fewer WHERE clause terms than Y
+** (3) Every WHERE clause term used by X is also used by Y
+** (4) X skips at least as many columns as Y
+** (5) If X is a covering index, than Y is too
**
+** Conditions (2) and (3) mean that X is a "proper subset" of Y.
** If X is a proper subset of Y then Y is a better choice and ought
** to have a lower cost. This routine returns TRUE when that cost
-** relationship is inverted and needs to be adjusted. The third rule
+** relationship is inverted and needs to be adjusted. Constraint (4)
** was added because if X uses skip-scan less than Y it still might
-** deserve a lower cost even if it is a proper subset of Y.
+** deserve a lower cost even if it is a proper subset of Y. Constraint (5)
+** was added because a covering index probably deserves to have a lower cost
+** than a non-covering index even if it is a proper subset.
*/
static int whereLoopCheaperProperSubset(
const WhereLoop *pX, /* First WhereLoop to compare */
@@ -132831,6 +149449,10 @@ static int whereLoopCheaperProperSubset(
}
if( j<0 ) return 0; /* X not a subset of Y since term X[i] not used by Y */
}
+ if( (pX->wsFlags&WHERE_IDX_ONLY)!=0
+ && (pY->wsFlags&WHERE_IDX_ONLY)==0 ){
+ return 0; /* Constraint (5) */
+ }
return 1; /* All conditions meet */
}
@@ -132873,16 +149495,17 @@ static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){
/*
** Search the list of WhereLoops in *ppPrev looking for one that can be
-** supplanted by pTemplate.
+** replaced by pTemplate.
**
-** Return NULL if the WhereLoop list contains an entry that can supplant
-** pTemplate, in other words if pTemplate does not belong on the list.
+** Return NULL if pTemplate does not belong on the WhereLoop list.
+** In other words if pTemplate ought to be dropped from further consideration.
**
-** If pX is a WhereLoop that pTemplate can supplant, then return the
+** If pX is a WhereLoop that pTemplate can replace, then return the
** link that points to pX.
**
-** If pTemplate cannot supplant any existing element of the list but needs
-** to be added to the list, then return a pointer to the tail of the list.
+** If pTemplate cannot replace any existing element of the list but needs
+** to be added to the list as a new entry, then return a pointer to the
+** tail of the list.
*/
static WhereLoop **whereLoopFindLesser(
WhereLoop **ppPrev,
@@ -132975,9 +149598,19 @@ static WhereLoop **whereLoopFindLesser(
static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){
WhereLoop **ppPrev, *p;
WhereInfo *pWInfo = pBuilder->pWInfo;
- sqlite3 *db = pWInfo->pParse->db;
+ tdsqlite3 *db = pWInfo->pParse->db;
int rc;
+ /* Stop the search once we hit the query planner search limit */
+ if( pBuilder->iPlanLimit==0 ){
+ WHERETRACE(0xffffffff,("=== query planner search limit reached ===\n"));
+ if( pBuilder->pOrSet ) pBuilder->pOrSet->n = 0;
+ return SQLITE_DONE;
+ }
+ pBuilder->iPlanLimit--;
+
+ whereLoopAdjustCost(pWInfo->pLoops, pTemplate);
+
/* If pBuilder->pOrSet is defined, then only keep track of the costs
** and prereqs.
*/
@@ -132990,9 +149623,9 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){
whereOrInsert(pBuilder->pOrSet, pTemplate->prereq, pTemplate->rRun,
pTemplate->nOut);
#if WHERETRACE_ENABLED /* 0x8 */
- if( sqlite3WhereTrace & 0x8 ){
- sqlite3DebugPrintf(x?" or-%d: ":" or-X: ", n);
- whereLoopPrint(pTemplate, pBuilder->pWC);
+ if( tdsqlite3WhereTrace & 0x8 ){
+ tdsqlite3DebugPrintf(x?" or-%d: ":" or-X: ", n);
+ tdsqlite3WhereLoopPrint(pTemplate, pBuilder->pWC);
}
#endif
}
@@ -133001,16 +149634,15 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){
/* Look for an existing WhereLoop to replace with pTemplate
*/
- whereLoopAdjustCost(pWInfo->pLoops, pTemplate);
ppPrev = whereLoopFindLesser(&pWInfo->pLoops, pTemplate);
if( ppPrev==0 ){
/* There already exists a WhereLoop on the list that is better
** than pTemplate, so just ignore pTemplate */
#if WHERETRACE_ENABLED /* 0x8 */
- if( sqlite3WhereTrace & 0x8 ){
- sqlite3DebugPrintf(" skip: ");
- whereLoopPrint(pTemplate, pBuilder->pWC);
+ if( tdsqlite3WhereTrace & 0x8 ){
+ tdsqlite3DebugPrintf(" skip: ");
+ tdsqlite3WhereLoopPrint(pTemplate, pBuilder->pWC);
}
#endif
return SQLITE_OK;
@@ -133023,18 +149655,20 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){
** WhereLoop and insert it.
*/
#if WHERETRACE_ENABLED /* 0x8 */
- if( sqlite3WhereTrace & 0x8 ){
+ if( tdsqlite3WhereTrace & 0x8 ){
if( p!=0 ){
- sqlite3DebugPrintf("replace: ");
- whereLoopPrint(p, pBuilder->pWC);
+ tdsqlite3DebugPrintf("replace: ");
+ tdsqlite3WhereLoopPrint(p, pBuilder->pWC);
+ tdsqlite3DebugPrintf(" with: ");
+ }else{
+ tdsqlite3DebugPrintf(" add: ");
}
- sqlite3DebugPrintf(" add: ");
- whereLoopPrint(pTemplate, pBuilder->pWC);
+ tdsqlite3WhereLoopPrint(pTemplate, pBuilder->pWC);
}
#endif
if( p==0 ){
/* Allocate a new WhereLoop to add to the end of the list */
- *ppPrev = p = sqlite3DbMallocRawNN(db, sizeof(WhereLoop));
+ *ppPrev = p = tdsqlite3DbMallocRawNN(db, sizeof(WhereLoop));
if( p==0 ) return SQLITE_NOMEM_BKPT;
whereLoopInit(p);
p->pNextLoop = 0;
@@ -133051,9 +149685,9 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){
if( pToDel==0 ) break;
*ppTail = pToDel->pNextLoop;
#if WHERETRACE_ENABLED /* 0x8 */
- if( sqlite3WhereTrace & 0x8 ){
- sqlite3DebugPrintf(" delete: ");
- whereLoopPrint(pToDel, pBuilder->pWC);
+ if( tdsqlite3WhereTrace & 0x8 ){
+ tdsqlite3DebugPrintf(" delete: ");
+ tdsqlite3WhereLoopPrint(pToDel, pBuilder->pWC);
}
#endif
whereLoopDelete(db, pToDel);
@@ -133062,7 +149696,7 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){
rc = whereLoopXfer(db, p, pTemplate);
if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){
Index *pIndex = p->u.btree.pIndex;
- if( pIndex && pIndex->tnum==0 ){
+ if( pIndex && pIndex->idxType==SQLITE_IDXTYPE_IPK ){
p->u.btree.pIndex = 0;
}
}
@@ -133105,11 +149739,12 @@ static void whereLoopOutputAdjust(
){
WhereTerm *pTerm, *pX;
Bitmask notAllowed = ~(pLoop->prereq|pLoop->maskSelf);
- int i, j, k;
+ int i, j;
LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */
assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 );
for(i=pWC->nTerm, pTerm=pWC->a; i>0; i--, pTerm++){
+ assert( pTerm!=0 );
if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) break;
if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue;
if( (pTerm->prereqAll & notAllowed)!=0 ) continue;
@@ -133130,8 +149765,9 @@ static void whereLoopOutputAdjust(
pLoop->nOut--;
if( pTerm->eOperator&(WO_EQ|WO_IS) ){
Expr *pRight = pTerm->pExpr->pRight;
+ int k = 0;
testcase( pTerm->pExpr->op==TK_IS );
- if( sqlite3ExprIsInteger(pRight, &k) && k>=(-1) && k<=1 ){
+ if( tdsqlite3ExprIsInteger(pRight, &k) && k>=(-1) && k<=1 ){
k = 10;
}else{
k = 20;
@@ -133168,7 +149804,7 @@ static int whereRangeVectorLen(
int nEq, /* Number of prior equality constraints on same index */
WhereTerm *pTerm /* The vector inequality constraint */
){
- int nCmp = sqlite3ExprVectorSize(pTerm->pExpr->pLeft);
+ int nCmp = tdsqlite3ExprVectorSize(pTerm->pExpr->pLeft);
int i;
nCmp = MIN(nCmp, (pIdx->nColumn - nEq));
@@ -133199,13 +149835,13 @@ static int whereRangeVectorLen(
}
testcase( pLhs->iColumn==XN_ROWID );
- aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs));
- idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn);
+ aff = tdsqlite3CompareAffinity(pRhs, tdsqlite3ExprAffinity(pLhs));
+ idxaff = tdsqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn);
if( aff!=idxaff ) break;
- pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs);
+ pColl = tdsqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs);
if( pColl==0 ) break;
- if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break;
+ if( tdsqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break;
}
return i;
}
@@ -133229,8 +149865,8 @@ static int whereRangeVectorLen(
** terms only. If it is modified, this value is restored before this
** function returns.
**
-** If pProbe->tnum==0, that means pIndex is a fake index used for the
-** INTEGER PRIMARY KEY.
+** If pProbe->idxType==SQLITE_IDXTYPE_IPK, that means pIndex is
+** a fake index used for the INTEGER PRIMARY KEY.
*/
static int whereLoopAddBtreeIndex(
WhereLoopBuilder *pBuilder, /* The WhereLoop factory */
@@ -133240,7 +149876,7 @@ static int whereLoopAddBtreeIndex(
){
WhereInfo *pWInfo = pBuilder->pWInfo; /* WHERE analyse context */
Parse *pParse = pWInfo->pParse; /* Parsing context */
- sqlite3 *db = pParse->db; /* Database connection malloc context */
+ tdsqlite3 *db = pParse->db; /* Database connection malloc context */
WhereLoop *pNew; /* Template WhereLoop under construction */
WhereTerm *pTerm; /* A WhereTerm under consideration */
int opMask; /* Valid operators for constraints */
@@ -133260,8 +149896,9 @@ static int whereLoopAddBtreeIndex(
pNew = pBuilder->pNew;
if( db->mallocFailed ) return SQLITE_NOMEM_BKPT;
- WHERETRACE(0x800, ("BEGIN addBtreeIdx(%s), nEq=%d\n",
- pProbe->zName, pNew->u.btree.nEq));
+ WHERETRACE(0x800, ("BEGIN %s.addBtreeIdx(%s), nEq=%d, nSkip=%d\n",
+ pProbe->pTable->zName,pProbe->zName,
+ pNew->u.btree.nEq, pNew->nSkip));
assert( (pNew->wsFlags & WHERE_VIRTUALTABLE)==0 );
assert( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 );
@@ -133293,7 +149930,7 @@ static int whereLoopAddBtreeIndex(
LogEst rCostIdx;
LogEst nOutUnadjusted; /* nOut before IN() and WHERE adjustments */
int nIn = 0;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
int nRecValid = pBuilder->nRecValid;
#endif
if( (eOp==WO_ISNULL || (pTerm->wtFlags&TERM_VNULL)!=0)
@@ -133307,18 +149944,20 @@ static int whereLoopAddBtreeIndex(
** to mix with a lower range bound from some other source */
if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue;
- /* Do not allow IS constraints from the WHERE clause to be used by the
- ** right table of a LEFT JOIN. Only constraints in the ON clause are
- ** allowed */
+ /* tag-20191211-001: Do not allow constraints from the WHERE clause to
+ ** be used by the right table of a LEFT JOIN. Only constraints in the
+ ** ON clause are allowed. See tag-20191211-002 for the vtab equivalent. */
if( (pSrc->fg.jointype & JT_LEFT)!=0
&& !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
- && (eOp & (WO_IS|WO_ISNULL))!=0
){
- testcase( eOp & WO_IS );
- testcase( eOp & WO_ISNULL );
continue;
}
+ if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){
+ pBuilder->bldFlags |= SQLITE_BLDF_UNIQUE;
+ }else{
+ pBuilder->bldFlags |= SQLITE_BLDF_INDEXED;
+ }
pNew->wsFlags = saved_wsFlags;
pNew->u.btree.nEq = saved_nEq;
pNew->u.btree.nBtm = saved_nBtm;
@@ -133336,11 +149975,10 @@ static int whereLoopAddBtreeIndex(
if( eOp & WO_IN ){
Expr *pExpr = pTerm->pExpr;
- pNew->wsFlags |= WHERE_COLUMN_IN;
if( ExprHasProperty(pExpr, EP_xIsSelect) ){
/* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */
int i;
- nIn = 46; assert( 46==sqlite3LogEst(25) );
+ nIn = 46; assert( 46==tdsqlite3LogEst(25) );
/* The expression may actually be of the form (x, y) IN (SELECT...).
** In this case there is a separate term for each of (x) and (y).
@@ -133352,21 +149990,57 @@ static int whereLoopAddBtreeIndex(
}
}else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){
/* "x IN (value, value, ...)" */
- nIn = sqlite3LogEst(pExpr->x.pList->nExpr);
- assert( nIn>0 ); /* RHS always has 2 or more terms... The parser
- ** changes "x IN (?)" into "x=?". */
+ nIn = tdsqlite3LogEst(pExpr->x.pList->nExpr);
+ }
+ if( pProbe->hasStat1 ){
+ LogEst M, logK, safetyMargin;
+ /* Let:
+ ** N = the total number of rows in the table
+ ** K = the number of entries on the RHS of the IN operator
+ ** M = the number of rows in the table that match terms to the
+ ** to the left in the same index. If the IN operator is on
+ ** the left-most index column, M==N.
+ **
+ ** Given the definitions above, it is better to omit the IN operator
+ ** from the index lookup and instead do a scan of the M elements,
+ ** testing each scanned row against the IN operator separately, if:
+ **
+ ** M*log(K) < K*log(N)
+ **
+ ** Our estimates for M, K, and N might be inaccurate, so we build in
+ ** a safety margin of 2 (LogEst: 10) that favors using the IN operator
+ ** with the index, as using an index has better worst-case behavior.
+ ** If we do not have real sqlite_stat1 data, always prefer to use
+ ** the index.
+ */
+ M = pProbe->aiRowLogEst[saved_nEq];
+ logK = estLog(nIn);
+ safetyMargin = 10; /* TUNING: extra weight for indexed IN */
+ if( M + logK + safetyMargin < nIn + rLogSize ){
+ WHERETRACE(0x40,
+ ("Scan preferred over IN operator on column %d of \"%s\" (%d<%d)\n",
+ saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize));
+ continue;
+ }else{
+ WHERETRACE(0x40,
+ ("IN operator preferred on column %d of \"%s\" (%d>=%d)\n",
+ saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize));
+ }
}
+ pNew->wsFlags |= WHERE_COLUMN_IN;
}else if( eOp & (WO_EQ|WO_IS) ){
int iCol = pProbe->aiColumn[saved_nEq];
pNew->wsFlags |= WHERE_COLUMN_EQ;
assert( saved_nEq==pNew->u.btree.nEq );
if( iCol==XN_ROWID
- || (iCol>0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1)
+ || (iCol>=0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1)
){
- if( iCol>=0 && pProbe->uniqNotNull==0 ){
- pNew->wsFlags |= WHERE_UNQ_WANTED;
- }else{
+ if( iCol==XN_ROWID || pProbe->uniqNotNull
+ || (pProbe->nKeyCol==1 && pProbe->onError && eOp==WO_EQ)
+ ){
pNew->wsFlags |= WHERE_ONEROW;
+ }else{
+ pNew->wsFlags |= WHERE_UNQ_WANTED;
}
}
}else if( eOp & WO_ISNULL ){
@@ -133412,7 +150086,7 @@ static int whereLoopAddBtreeIndex(
** the value of pNew->nOut to account for pTerm (but not nIn/nInMul). */
assert( pNew->nOut==saved_nOut );
if( pNew->wsFlags & WHERE_COLUMN_RANGE ){
- /* Adjust nOut using stat3/stat4 data. Or, if there is no stat3/stat4
+ /* Adjust nOut using stat4 data. Or, if there is no stat4
** data, using some other estimate. */
whereRangeScanEst(pParse, pBuilder, pBtm, pTop, pNew);
}else{
@@ -133426,12 +150100,13 @@ static int whereLoopAddBtreeIndex(
pNew->nOut += pTerm->truthProb;
pNew->nOut -= nIn;
}else{
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
tRowcnt nOut = 0;
if( nInMul==0
&& pProbe->nSample
&& pNew->u.btree.nEq<=pProbe->nSampleCol
&& ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect))
+ && OptimizationEnabled(db, SQLITE_Stat4)
){
Expr *pExpr = pTerm->pExpr;
if( (eOp & (WO_EQ|WO_ISNULL|WO_IS))!=0 ){
@@ -133445,7 +150120,7 @@ static int whereLoopAddBtreeIndex(
if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
if( rc!=SQLITE_OK ) break; /* Jump out of the pTerm loop */
if( nOut ){
- pNew->nOut = sqlite3LogEst(nOut);
+ pNew->nOut = tdsqlite3LogEst(nOut);
if( pNew->nOut>saved_nOut ) pNew->nOut = saved_nOut;
pNew->nOut -= nIn;
}
@@ -133468,10 +150143,11 @@ static int whereLoopAddBtreeIndex(
** it to pNew->rRun, which is currently set to the cost of the index
** seek only. Then, if this is a non-covering index, add the cost of
** visiting the rows in the main table. */
+ assert( pSrc->pTab->szTabRow>0 );
rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow;
- pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx);
+ pNew->rRun = tdsqlite3LogEstAdd(rLogSize, rCostIdx);
if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK))==0 ){
- pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16);
+ pNew->rRun = tdsqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16);
}
ApplyCostMultiplier(pNew->rRun, pProbe->pTable->costMult);
@@ -133493,7 +150169,7 @@ static int whereLoopAddBtreeIndex(
whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn);
}
pNew->nOut = saved_nOut;
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
+#ifdef SQLITE_ENABLE_STAT4
pBuilder->nRecValid = nRecValid;
#endif
}
@@ -133516,10 +150192,12 @@ static int whereLoopAddBtreeIndex(
** the code). And, even if it is not, it should not be too much slower.
** On the other hand, the extra seeks could end up being significantly
** more expensive. */
- assert( 42==sqlite3LogEst(18) );
+ assert( 42==tdsqlite3LogEst(18) );
if( saved_nEq==saved_nSkip
&& saved_nEq+1<pProbe->nKeyCol
+ && saved_nEq==pNew->nLTerm
&& pProbe->noSkipScan==0
+ && OptimizationEnabled(db, SQLITE_SkipScan)
&& pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */
&& (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK
){
@@ -133540,8 +150218,8 @@ static int whereLoopAddBtreeIndex(
pNew->wsFlags = saved_wsFlags;
}
- WHERETRACE(0x800, ("END addBtreeIdx(%s), nEq=%d, rc=%d\n",
- pProbe->zName, saved_nEq, rc));
+ WHERETRACE(0x800, ("END %s.addBtreeIdx(%s), nEq=%d, rc=%d\n",
+ pProbe->pTable->zName, pProbe->zName, saved_nEq, rc));
return rc;
}
@@ -133565,7 +150243,7 @@ static int indexMightHelpWithOrderBy(
if( pIndex->bUnordered ) return 0;
if( (pOB = pBuilder->pWInfo->pOrderBy)==0 ) return 0;
for(ii=0; ii<pOB->nExpr; ii++){
- Expr *pExpr = sqlite3ExprSkipCollate(pOB->a[ii].pExpr);
+ Expr *pExpr = tdsqlite3ExprSkipCollateAndLikely(pOB->a[ii].pExpr);
if( pExpr->op==TK_COLUMN && pExpr->iTable==iCursor ){
if( pExpr->iColumn<0 ) return 1;
for(jj=0; jj<pIndex->nKeyCol; jj++){
@@ -133574,7 +150252,7 @@ static int indexMightHelpWithOrderBy(
}else if( (aColExpr = pIndex->aColExpr)!=0 ){
for(jj=0; jj<pIndex->nKeyCol; jj++){
if( pIndex->aiColumn[jj]!=XN_EXPR ) continue;
- if( sqlite3ExprCompare(pExpr,aColExpr->a[jj].pExpr,iCursor)==0 ){
+ if( tdsqlite3ExprCompareSkip(pExpr,aColExpr->a[jj].pExpr,iCursor)==0 ){
return 1;
}
}
@@ -133583,38 +150261,29 @@ static int indexMightHelpWithOrderBy(
return 0;
}
-/*
-** Return a bitmask where 1s indicate that the corresponding column of
-** the table is used by an index. Only the first 63 columns are considered.
-*/
-static Bitmask columnsInIndex(Index *pIdx){
- Bitmask m = 0;
- int j;
- for(j=pIdx->nColumn-1; j>=0; j--){
- int x = pIdx->aiColumn[j];
- if( x>=0 ){
- testcase( x==BMS-1 );
- testcase( x==BMS-2 );
- if( x<BMS-1 ) m |= MASKBIT(x);
- }
- }
- return m;
-}
-
/* Check to see if a partial index with pPartIndexWhere can be used
** in the current query. Return true if it can be and false if not.
*/
-static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){
+static int whereUsablePartialIndex(
+ int iTab, /* The table for which we want an index */
+ int isLeft, /* True if iTab is the right table of a LEFT JOIN */
+ WhereClause *pWC, /* The WHERE clause of the query */
+ Expr *pWhere /* The WHERE clause from the partial index */
+){
int i;
WhereTerm *pTerm;
+ Parse *pParse = pWC->pWInfo->pParse;
while( pWhere->op==TK_AND ){
- if( !whereUsablePartialIndex(iTab,pWC,pWhere->pLeft) ) return 0;
+ if( !whereUsablePartialIndex(iTab,isLeft,pWC,pWhere->pLeft) ) return 0;
pWhere = pWhere->pRight;
}
+ if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0;
for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
- Expr *pExpr = pTerm->pExpr;
- if( sqlite3ExprImpliesExpr(pExpr, pWhere, iTab)
- && (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab)
+ Expr *pExpr;
+ pExpr = pTerm->pExpr;
+ if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab)
+ && (isLeft==0 || ExprHasProperty(pExpr, EP_FromJoin))
+ && tdsqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab)
){
return 1;
}
@@ -133705,6 +150374,7 @@ static int whereLoopAddBtree(
sPk.onError = OE_Replace;
sPk.pTable = pTab;
sPk.szIdxRow = pTab->szTabRow;
+ sPk.idxType = SQLITE_IDXTYPE_IPK;
aiRowEstPk[0] = pTab->nRowLogEst;
aiRowEstPk[1] = 0;
pFirst = pSrc->pTab->pIndex;
@@ -133743,14 +150413,16 @@ static int whereLoopAddBtree(
/* TUNING: One-time cost for computing the automatic index is
** estimated to be X*N*log2(N) where N is the number of rows in
** the table being indexed and where X is 7 (LogEst=28) for normal
- ** tables or 1.375 (LogEst=4) for views and subqueries. The value
+ ** tables or 0.5 (LogEst=-10) for views and subqueries. The value
** of X is smaller for views and subqueries so that the query planner
** will be more aggressive about generating automatic indexes for
** those objects, since there is no opportunity to add schema
** indexes on subqueries and views. */
- pNew->rSetup = rLogSize + rSize + 4;
+ pNew->rSetup = rLogSize + rSize;
if( pTab->pSelect==0 && (pTab->tabFlags & TF_Ephemeral)==0 ){
- pNew->rSetup += 24;
+ pNew->rSetup += 28;
+ }else{
+ pNew->rSetup -= 10;
}
ApplyCostMultiplier(pNew->rSetup, pTab->costMult);
if( pNew->rSetup<0 ) pNew->rSetup = 0;
@@ -133758,8 +150430,8 @@ static int whereLoopAddBtree(
** is more than the usual guess of 10 rows, since we have no way
** of knowing how selective the index will ultimately be. It would
** not be unreasonable to make this value much larger. */
- pNew->nOut = 43; assert( 43==sqlite3LogEst(20) );
- pNew->rRun = sqlite3LogEstAdd(rLogSize,pNew->nOut);
+ pNew->nOut = 43; assert( 43==tdsqlite3LogEst(20) );
+ pNew->rRun = tdsqlite3LogEstAdd(rLogSize,pNew->nOut);
pNew->wsFlags = WHERE_AUTO_INDEX;
pNew->prereq = mPrereq | pTerm->prereqRight;
rc = whereLoopInsert(pBuilder, pNew);
@@ -133768,14 +150440,20 @@ static int whereLoopAddBtree(
}
#endif /* SQLITE_OMIT_AUTOMATIC_INDEX */
- /* Loop over all indices
- */
- for(; rc==SQLITE_OK && pProbe; pProbe=pProbe->pNext, iSortIdx++){
+ /* Loop over all indices. If there was an INDEXED BY clause, then only
+ ** consider index pProbe. */
+ for(; rc==SQLITE_OK && pProbe;
+ pProbe=(pSrc->pIBIndex ? 0 : pProbe->pNext), iSortIdx++
+ ){
+ int isLeft = (pSrc->fg.jointype & JT_OUTER)!=0;
if( pProbe->pPartIdxWhere!=0
- && !whereUsablePartialIndex(pSrc->iCursor, pWC, pProbe->pPartIdxWhere) ){
+ && !whereUsablePartialIndex(pSrc->iCursor, isLeft, pWC,
+ pProbe->pPartIdxWhere)
+ ){
testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */
continue; /* Partial index inappropriate for this query */
}
+ if( pProbe->bNoQuery ) continue;
rSize = pProbe->aiRowLogEst[0];
pNew->u.btree.nEq = 0;
pNew->u.btree.nBtm = 0;
@@ -133790,7 +150468,7 @@ static int whereLoopAddBtree(
b = indexMightHelpWithOrderBy(pBuilder, pProbe, pSrc->iCursor);
/* The ONEPASS_DESIRED flags never occurs together with ORDER BY */
assert( (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || b==0 );
- if( pProbe->tnum<=0 ){
+ if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){
/* Integer primary key index */
pNew->wsFlags = WHERE_IPK;
@@ -133809,7 +150487,7 @@ static int whereLoopAddBtree(
pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED;
m = 0;
}else{
- m = pSrc->colUsed & ~columnsInIndex(pProbe);
+ m = pSrc->colUsed & pProbe->colNotIdxed;
pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED;
}
@@ -133821,7 +150499,7 @@ static int whereLoopAddBtree(
&& pProbe->bUnordered==0
&& (pProbe->szIdxRow<pTab->szTabRow)
&& (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0
- && sqlite3GlobalConfig.bUseCis
+ && tdsqlite3GlobalConfig.bUseCis
&& OptimizationEnabled(pWInfo->pParse->db, SQLITE_CoverIdxScan)
)
){
@@ -133843,7 +150521,7 @@ static int whereLoopAddBtree(
WhereClause *pWC2 = &pWInfo->sWC;
for(ii=0; ii<pWC2->nTerm; ii++){
WhereTerm *pTerm = &pWC2->a[ii];
- if( !sqlite3ExprCoveredByIndex(pTerm->pExpr, iCur, pProbe) ){
+ if( !tdsqlite3ExprCoveredByIndex(pTerm->pExpr, iCur, pProbe) ){
break;
}
/* pTerm can be evaluated using just the index. So reduce
@@ -133856,7 +150534,7 @@ static int whereLoopAddBtree(
}
}
- pNew->rRun = sqlite3LogEstAdd(pNew->rRun, nLookup);
+ pNew->rRun = tdsqlite3LogEstAdd(pNew->rRun, nLookup);
}
ApplyCostMultiplier(pNew->rRun, pTab->costMult);
whereLoopOutputAdjust(pWC, pNew, rSize);
@@ -133866,16 +150544,20 @@ static int whereLoopAddBtree(
}
}
+ pBuilder->bldFlags = 0;
rc = whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, 0);
-#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
- sqlite3Stat4ProbeFree(pBuilder->pRec);
+ if( pBuilder->bldFlags==SQLITE_BLDF_INDEXED ){
+ /* If a non-unique index is used, or if a prefix of the key for
+ ** unique index is used (making the index functionally non-unique)
+ ** then the sqlite_stat1 data becomes important for scoring the
+ ** plan */
+ pTab->tabFlags |= TF_StatsUsed;
+ }
+#ifdef SQLITE_ENABLE_STAT4
+ tdsqlite3Stat4ProbeFree(pBuilder->pRec);
pBuilder->nRecValid = 0;
pBuilder->pRec = 0;
#endif
-
- /* If there was an INDEXED BY clause, then only that one index is
- ** considered. */
- if( pSrc->pIBIndex ) break;
}
return rc;
}
@@ -133907,13 +150589,13 @@ static int whereLoopAddVirtualOne(
Bitmask mPrereq, /* Mask of tables that must be used. */
Bitmask mUsable, /* Mask of usable tables */
u16 mExclude, /* Exclude terms using these operators */
- sqlite3_index_info *pIdxInfo, /* Populated object for xBestIndex */
+ tdsqlite3_index_info *pIdxInfo, /* Populated object for xBestIndex */
u16 mNoOmit, /* Do not omit these constraints */
int *pbIn /* OUT: True if plan uses an IN(...) op */
){
WhereClause *pWC = pBuilder->pWC;
- struct sqlite3_index_constraint *pIdxCons;
- struct sqlite3_index_constraint_usage *pUsage = pIdxInfo->aConstraintUsage;
+ struct tdsqlite3_index_constraint *pIdxCons;
+ struct tdsqlite3_index_constraint_usage *pUsage = pIdxInfo->aConstraintUsage;
int i;
int mxTerm;
int rc = SQLITE_OK;
@@ -133928,7 +150610,7 @@ static int whereLoopAddVirtualOne(
/* Set the usable flag on the subset of constraints identified by
** arguments mUsable and mExclude. */
- pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint;
+ pIdxCons = *(struct tdsqlite3_index_constraint**)&pIdxInfo->aConstraint;
for(i=0; i<nConstraint; i++, pIdxCons++){
WhereTerm *pTerm = &pWC->a[pIdxCons->iTermOffset];
pIdxCons->usable = 0;
@@ -133939,7 +150621,7 @@ static int whereLoopAddVirtualOne(
}
}
- /* Initialize the output fields of the sqlite3_index_info structure */
+ /* Initialize the output fields of the tdsqlite3_index_info structure */
memset(pUsage, 0, sizeof(pUsage[0])*nConstraint);
assert( pIdxInfo->needToFreeIdxStr==0 );
pIdxInfo->idxStr = 0;
@@ -133948,17 +150630,27 @@ static int whereLoopAddVirtualOne(
pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2;
pIdxInfo->estimatedRows = 25;
pIdxInfo->idxFlags = 0;
- pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed;
+ pIdxInfo->colUsed = (tdsqlite3_int64)pSrc->colUsed;
/* Invoke the virtual table xBestIndex() method */
rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo);
- if( rc ) return rc;
+ if( rc ){
+ if( rc==SQLITE_CONSTRAINT ){
+ /* If the xBestIndex method returns SQLITE_CONSTRAINT, that means
+ ** that the particular combination of parameters provided is unusable.
+ ** Make no entries in the loop table.
+ */
+ WHERETRACE(0xffff, (" ^^^^--- non-viable plan rejected!\n"));
+ return SQLITE_OK;
+ }
+ return rc;
+ }
mxTerm = -1;
assert( pNew->nLSlot>=nConstraint );
for(i=0; i<nConstraint; i++) pNew->aLTerm[i] = 0;
pNew->u.vtab.omitMask = 0;
- pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint;
+ pIdxCons = *(struct tdsqlite3_index_constraint**)&pIdxInfo->aConstraint;
for(i=0; i<nConstraint; i++, pIdxCons++){
int iTerm;
if( (iTerm = pUsage[i].argvIndex - 1)>=0 ){
@@ -133970,9 +150662,9 @@ static int whereLoopAddVirtualOne(
|| pNew->aLTerm[iTerm]!=0
|| pIdxCons->usable==0
){
- rc = SQLITE_ERROR;
- sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName);
- return rc;
+ tdsqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName);
+ testcase( pIdxInfo->needToFreeIdxStr );
+ return SQLITE_ERROR;
}
testcase( iTerm==nConstraint-1 );
testcase( j==0 );
@@ -133984,7 +150676,14 @@ static int whereLoopAddVirtualOne(
if( iTerm>mxTerm ) mxTerm = iTerm;
testcase( iTerm==15 );
testcase( iTerm==16 );
- if( iTerm<16 && pUsage[i].omit ) pNew->u.vtab.omitMask |= 1<<iTerm;
+ if( pUsage[i].omit ){
+ if( i<16 && ((1<<i)&mNoOmit)==0 ){
+ testcase( i!=iTerm );
+ pNew->u.vtab.omitMask |= 1<<iTerm;
+ }else{
+ testcase( i!=iTerm );
+ }
+ }
if( (pTerm->eOperator & WO_IN)!=0 ){
/* A virtual table that is constrained by an IN clause may not
** consume the ORDER BY clause because (1) the order of IN terms
@@ -133997,9 +150696,17 @@ static int whereLoopAddVirtualOne(
}
}
}
- pNew->u.vtab.omitMask &= ~mNoOmit;
pNew->nLTerm = mxTerm+1;
+ for(i=0; i<=mxTerm; i++){
+ if( pNew->aLTerm[i]==0 ){
+ /* The non-zero argvIdx values must be contiguous. Raise an
+ ** error if they are not */
+ tdsqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName);
+ testcase( pIdxInfo->needToFreeIdxStr );
+ return SQLITE_ERROR;
+ }
+ }
assert( pNew->nLTerm<=pNew->nLSlot );
pNew->u.vtab.idxNum = pIdxInfo->idxNum;
pNew->u.vtab.needFree = pIdxInfo->needToFreeIdxStr;
@@ -134008,8 +150715,8 @@ static int whereLoopAddVirtualOne(
pNew->u.vtab.isOrdered = (i8)(pIdxInfo->orderByConsumed ?
pIdxInfo->nOrderBy : 0);
pNew->rSetup = 0;
- pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost);
- pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows);
+ pNew->rRun = tdsqlite3LogEstFromDouble(pIdxInfo->estimatedCost);
+ pNew->nOut = tdsqlite3LogEst(pIdxInfo->estimatedRows);
/* Set the WHERE_ONEROW flag if the xBestIndex() method indicated
** that the scan will visit at most one row. Clear it otherwise. */
@@ -134020,16 +150727,37 @@ static int whereLoopAddVirtualOne(
}
rc = whereLoopInsert(pBuilder, pNew);
if( pNew->u.vtab.needFree ){
- sqlite3_free(pNew->u.vtab.idxStr);
+ tdsqlite3_free(pNew->u.vtab.idxStr);
pNew->u.vtab.needFree = 0;
}
WHERETRACE(0xffff, (" bIn=%d prereqIn=%04llx prereqOut=%04llx\n",
- *pbIn, (sqlite3_uint64)mPrereq,
- (sqlite3_uint64)(pNew->prereq & ~mPrereq)));
+ *pbIn, (tdsqlite3_uint64)mPrereq,
+ (tdsqlite3_uint64)(pNew->prereq & ~mPrereq)));
return rc;
}
+/*
+** If this function is invoked from within an xBestIndex() callback, it
+** returns a pointer to a buffer containing the name of the collation
+** sequence associated with element iCons of the tdsqlite3_index_info.aConstraint
+** array. Or, if iCons is out of range or there is no active xBestIndex
+** call, return NULL.
+*/
+SQLITE_API const char *tdsqlite3_vtab_collation(tdsqlite3_index_info *pIdxInfo, int iCons){
+ HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1];
+ const char *zRet = 0;
+ if( iCons>=0 && iCons<pIdxInfo->nConstraint ){
+ CollSeq *pC = 0;
+ int iTerm = pIdxInfo->aConstraint[iCons].iTermOffset;
+ Expr *pX = pHidden->pWC->a[iTerm].pExpr;
+ if( pX->pLeft ){
+ pC = tdsqlite3ExprCompareCollSeq(pHidden->pParse, pX);
+ }
+ zRet = (pC ? pC->zName : tdsqlite3StrBINARY);
+ }
+ return zRet;
+}
/*
** Add all WhereLoop objects for a table of the join identified by
@@ -134066,7 +150794,7 @@ static int whereLoopAddVirtual(
Parse *pParse; /* The parsing context */
WhereClause *pWC; /* The WHERE clause */
struct SrcList_item *pSrc; /* The FROM clause term to search */
- sqlite3_index_info *p; /* Object to pass to xBestIndex() */
+ tdsqlite3_index_info *p; /* Object to pass to xBestIndex() */
int nConstraint; /* Number of constraints in p */
int bIn; /* True if plan uses IN(...) operator */
WhereLoop *pNew;
@@ -134089,20 +150817,21 @@ static int whereLoopAddVirtual(
pNew->u.vtab.needFree = 0;
nConstraint = p->nConstraint;
if( whereLoopResize(pParse->db, pNew, nConstraint) ){
- sqlite3DbFree(pParse->db, p);
+ tdsqlite3DbFree(pParse->db, p);
return SQLITE_NOMEM_BKPT;
}
/* First call xBestIndex() with all constraints usable. */
+ WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName));
WHERETRACE(0x40, (" VirtualOne: all usable\n"));
rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn);
/* If the call to xBestIndex() with all terms enabled produced a plan
- ** that does not require any source tables (IOW: a plan with mBest==0),
- ** then there is no point in making any further calls to xBestIndex()
- ** since they will all return the same result (if the xBestIndex()
- ** implementation is sane). */
- if( rc==SQLITE_OK && (mBest = (pNew->prereq & ~mPrereq))!=0 ){
+ ** that does not require any source tables (IOW: a plan with mBest==0)
+ ** and does not use an IN(...) operator, then there is no point in making
+ ** any further calls to xBestIndex() since they will all return the same
+ ** result (if the xBestIndex() implementation is sane). */
+ if( rc==SQLITE_OK && ((mBest = (pNew->prereq & ~mPrereq))!=0 || bIn) ){
int seenZero = 0; /* True if a plan with no prereqs seen */
int seenZeroNoIN = 0; /* Plan with no prereqs and no IN(...) seen */
Bitmask mPrev = 0;
@@ -134138,7 +150867,7 @@ static int whereLoopAddVirtual(
if( mNext==ALLBITS ) break;
if( mNext==mBest || mNext==mBestNoIn ) continue;
WHERETRACE(0x40, (" VirtualOne: mPrev=%04llx mNext=%04llx\n",
- (sqlite3_uint64)mPrev, (sqlite3_uint64)mNext));
+ (tdsqlite3_uint64)mPrev, (tdsqlite3_uint64)mNext));
rc = whereLoopAddVirtualOne(
pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn);
if( pNew->prereq==mPrereq ){
@@ -134167,8 +150896,9 @@ static int whereLoopAddVirtual(
}
}
- if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr);
- sqlite3DbFree(pParse->db, p);
+ if( p->needToFreeIdxStr ) tdsqlite3_free(p->idxStr);
+ tdsqlite3DbFreeNN(pParse->db, p);
+ WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pTab->zName, rc));
return rc;
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
@@ -134232,8 +150962,8 @@ static int whereLoopAddOr(
#ifdef WHERETRACE_ENABLED
WHERETRACE(0x200, ("OR-term %d of %p has %d subterms:\n",
(int)(pOrTerm-pOrWC->a), pTerm, sSubBuild.pWC->nTerm));
- if( sqlite3WhereTrace & 0x400 ){
- sqlite3WhereClausePrint(sSubBuild.pWC);
+ if( tdsqlite3WhereTrace & 0x400 ){
+ tdsqlite3WhereClausePrint(sSubBuild.pWC);
}
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
@@ -134247,7 +150977,8 @@ static int whereLoopAddOr(
if( rc==SQLITE_OK ){
rc = whereLoopAddOr(&sSubBuild, mPrereq, mUnusable);
}
- assert( rc==SQLITE_OK || sCur.n==0 );
+ assert( rc==SQLITE_OK || rc==SQLITE_DONE || sCur.n==0 );
+ testcase( rc==SQLITE_DONE );
if( sCur.n==0 ){
sSum.n = 0;
break;
@@ -134261,8 +150992,8 @@ static int whereLoopAddOr(
for(i=0; i<sPrev.n; i++){
for(j=0; j<sCur.n; j++){
whereOrInsert(&sSum, sPrev.a[i].prereq | sCur.a[j].prereq,
- sqlite3LogEstAdd(sPrev.a[i].rRun, sCur.a[j].rRun),
- sqlite3LogEstAdd(sPrev.a[i].nOut, sCur.a[j].nOut));
+ tdsqlite3LogEstAdd(sPrev.a[i].rRun, sCur.a[j].rRun),
+ tdsqlite3LogEstAdd(sPrev.a[i].nOut, sCur.a[j].nOut));
}
}
}
@@ -134308,7 +151039,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){
SrcList *pTabList = pWInfo->pTabList;
struct SrcList_item *pItem;
struct SrcList_item *pEnd = &pTabList->a[pWInfo->nLevel];
- sqlite3 *db = pWInfo->pParse->db;
+ tdsqlite3 *db = pWInfo->pParse->db;
int rc = SQLITE_OK;
WhereLoop *pNew;
u8 priorJointype = 0;
@@ -134316,10 +151047,12 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){
/* Loop over the tables in the join, from left to right */
pNew = pBuilder->pNew;
whereLoopInit(pNew);
+ pBuilder->iPlanLimit = SQLITE_QUERY_PLANNER_LIMIT;
for(iTab=0, pItem=pTabList->a; pItem<pEnd; iTab++, pItem++){
Bitmask mUnusable = 0;
pNew->iTab = iTab;
- pNew->maskSelf = sqlite3WhereGetMask(&pWInfo->sMaskSet, pItem->iCursor);
+ pBuilder->iPlanLimit += SQLITE_QUERY_PLANNER_LIMIT_INCR;
+ pNew->maskSelf = tdsqlite3WhereGetMask(&pWInfo->sMaskSet, pItem->iCursor);
if( ((pItem->fg.jointype|priorJointype) & (JT_LEFT|JT_CROSS))!=0 ){
/* This condition is true when pItem is the FROM clause term on the
** right-hand-side of a LEFT or CROSS JOIN. */
@@ -134331,7 +151064,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){
struct SrcList_item *p;
for(p=&pItem[1]; p<pEnd; p++){
if( mUnusable || (p->fg.jointype & (JT_LEFT|JT_CROSS)) ){
- mUnusable |= sqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor);
+ mUnusable |= tdsqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor);
}
}
rc = whereLoopAddVirtual(pBuilder, mPrereq, mUnusable);
@@ -134340,11 +151073,19 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){
{
rc = whereLoopAddBtree(pBuilder, mPrereq);
}
- if( rc==SQLITE_OK ){
+ if( rc==SQLITE_OK && pBuilder->pWC->hasOr ){
rc = whereLoopAddOr(pBuilder, mPrereq, mUnusable);
}
mPrior |= pNew->maskSelf;
- if( rc || db->mallocFailed ) break;
+ if( rc || db->mallocFailed ){
+ if( rc==SQLITE_DONE ){
+ /* We hit the query planner search limit set by iPlanLimit */
+ tdsqlite3_log(SQLITE_WARNING, "abbreviated query algorithm search");
+ rc = SQLITE_OK;
+ }else{
+ break;
+ }
+ }
}
whereLoopClear(db, pNew);
@@ -134352,7 +151093,7 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){
}
/*
-** Examine a WherePath (with the addition of the extra WhereLoop of the 5th
+** Examine a WherePath (with the addition of the extra WhereLoop of the 6th
** parameters) to see if it outputs rows in the requested ORDER BY
** (or GROUP BY) without requiring a separate sort operation. Return N:
**
@@ -134396,7 +151137,7 @@ static i8 wherePathSatisfiesOrderBy(
Expr *pOBExpr; /* An expression from the ORDER BY clause */
CollSeq *pColl; /* COLLATE function from an ORDER BY clause term */
Index *pIndex; /* The index associated with pLoop */
- sqlite3 *db = pWInfo->pParse->db; /* Database connection */
+ tdsqlite3 *db = pWInfo->pParse->db; /* Database connection */
Bitmask obSat = 0; /* Mask of ORDER BY terms satisfied so far */
Bitmask obDone; /* Mask of all ORDER BY terms */
Bitmask orderDistinctMask; /* Mask of all well-ordered loops */
@@ -134445,8 +151186,12 @@ static i8 wherePathSatisfiesOrderBy(
pLoop = pLast;
}
if( pLoop->wsFlags & WHERE_VIRTUALTABLE ){
- if( pLoop->u.vtab.isOrdered ) obSat = obDone;
+ if( pLoop->u.vtab.isOrdered && (wctrlFlags & WHERE_DISTINCTBY)==0 ){
+ obSat = obDone;
+ }
break;
+ }else if( wctrlFlags & WHERE_DISTINCTBY ){
+ pLoop->u.btree.nDistinctCol = 0;
}
iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor;
@@ -134457,10 +151202,10 @@ static i8 wherePathSatisfiesOrderBy(
*/
for(i=0; i<nOrderBy; i++){
if( MASKBIT(i) & obSat ) continue;
- pOBExpr = sqlite3ExprSkipCollate(pOrderBy->a[i].pExpr);
+ pOBExpr = tdsqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr);
if( pOBExpr->op!=TK_COLUMN ) continue;
if( pOBExpr->iTable!=iCur ) continue;
- pTerm = sqlite3WhereFindTerm(&pWInfo->sWC, iCur, pOBExpr->iColumn,
+ pTerm = tdsqlite3WhereFindTerm(&pWInfo->sWC, iCur, pOBExpr->iColumn,
~ready, eqOpMask, 0);
if( pTerm==0 ) continue;
if( pTerm->eOperator==WO_IN ){
@@ -134472,14 +151217,10 @@ static i8 wherePathSatisfiesOrderBy(
if( j>=pLoop->nLTerm ) continue;
}
if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && pOBExpr->iColumn>=0 ){
- const char *z1, *z2;
- pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr);
- if( !pColl ) pColl = db->pDfltColl;
- z1 = pColl->zName;
- pColl = sqlite3ExprCollSeq(pWInfo->pParse, pTerm->pExpr);
- if( !pColl ) pColl = db->pDfltColl;
- z2 = pColl->zName;
- if( sqlite3StrICmp(z1, z2)!=0 ) continue;
+ if( tdsqlite3ExprCollSeqMatch(pWInfo->pParse,
+ pOrderBy->a[i].pExpr, pTerm->pExpr)==0 ){
+ continue;
+ }
testcase( pTerm->pExpr->op==TK_IS );
}
obSat |= MASKBIT(i);
@@ -134498,7 +151239,8 @@ static i8 wherePathSatisfiesOrderBy(
assert( nColumn==nKeyCol+1 || !HasRowid(pIndex->pTable) );
assert( pIndex->aiColumn[nColumn-1]==XN_ROWID
|| !HasRowid(pIndex->pTable));
- isOrderDistinct = IsUniqueIndex(pIndex);
+ isOrderDistinct = IsUniqueIndex(pIndex)
+ && (pLoop->wsFlags & WHERE_SKIPSCAN)==0;
}
/* Loop through all columns of the index and deal with the ones
@@ -134516,15 +151258,21 @@ static i8 wherePathSatisfiesOrderBy(
u16 eOp = pLoop->aLTerm[j]->eOperator;
/* Skip over == and IS and ISNULL terms. (Also skip IN terms when
- ** doing WHERE_ORDERBY_LIMIT processing).
+ ** doing WHERE_ORDERBY_LIMIT processing). Except, IS and ISNULL
+ ** terms imply that the index is not UNIQUE NOT NULL in which case
+ ** the loop need to be marked as not order-distinct because it can
+ ** have repeated NULL rows.
**
** If the current term is a column of an ((?,?) IN (SELECT...))
** expression for which the SELECT returns more than one column,
** check that it is the only column used by this loop. Otherwise,
** if it is one of two or more, none of the columns can be
- ** considered to match an ORDER BY term. */
+ ** considered to match an ORDER BY term.
+ */
if( (eOp & eqOpMask)!=0 ){
- if( eOp & WO_ISNULL ){
+ if( eOp & (WO_ISNULL|WO_IS) ){
+ testcase( eOp & WO_ISNULL );
+ testcase( eOp & WO_IS );
testcase( isOrderDistinct );
isOrderDistinct = 0;
}
@@ -134550,8 +151298,8 @@ static i8 wherePathSatisfiesOrderBy(
*/
if( pIndex ){
iColumn = pIndex->aiColumn[j];
- revIdx = pIndex->aSortOrder[j];
- if( iColumn==pIndex->pTable->iPKey ) iColumn = -1;
+ revIdx = pIndex->aSortOrder[j] & KEYINFO_ORDER_DESC;
+ if( iColumn==pIndex->pTable->iPKey ) iColumn = XN_ROWID;
}else{
iColumn = XN_ROWID;
revIdx = 0;
@@ -134574,23 +151322,26 @@ static i8 wherePathSatisfiesOrderBy(
isMatch = 0;
for(i=0; bOnce && i<nOrderBy; i++){
if( MASKBIT(i) & obSat ) continue;
- pOBExpr = sqlite3ExprSkipCollate(pOrderBy->a[i].pExpr);
+ pOBExpr = tdsqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr);
testcase( wctrlFlags & WHERE_GROUPBY );
testcase( wctrlFlags & WHERE_DISTINCTBY );
if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0;
- if( iColumn>=(-1) ){
+ if( iColumn>=XN_ROWID ){
if( pOBExpr->op!=TK_COLUMN ) continue;
if( pOBExpr->iTable!=iCur ) continue;
if( pOBExpr->iColumn!=iColumn ) continue;
}else{
- if( sqlite3ExprCompare(pOBExpr,pIndex->aColExpr->a[j].pExpr,iCur) ){
+ Expr *pIdxExpr = pIndex->aColExpr->a[j].pExpr;
+ if( tdsqlite3ExprCompareSkip(pOBExpr, pIdxExpr, iCur) ){
continue;
}
}
- if( iColumn>=0 ){
- pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr);
- if( !pColl ) pColl = db->pDfltColl;
- if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue;
+ if( iColumn!=XN_ROWID ){
+ pColl = tdsqlite3ExprNNCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr);
+ if( tdsqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue;
+ }
+ if( wctrlFlags & WHERE_DISTINCTBY ){
+ pLoop->u.btree.nDistinctCol = j+1;
}
isMatch = 1;
break;
@@ -134599,13 +151350,22 @@ static i8 wherePathSatisfiesOrderBy(
/* Make sure the sort order is compatible in an ORDER BY clause.
** Sort order is irrelevant for a GROUP BY clause. */
if( revSet ){
- if( (rev ^ revIdx)!=pOrderBy->a[i].sortOrder ) isMatch = 0;
+ if( (rev ^ revIdx)!=(pOrderBy->a[i].sortFlags&KEYINFO_ORDER_DESC) ){
+ isMatch = 0;
+ }
}else{
- rev = revIdx ^ pOrderBy->a[i].sortOrder;
+ rev = revIdx ^ (pOrderBy->a[i].sortFlags & KEYINFO_ORDER_DESC);
if( rev ) *pRevMask |= MASKBIT(iLoop);
revSet = 1;
}
}
+ if( isMatch && (pOrderBy->a[i].sortFlags & KEYINFO_ORDER_BIGNULL) ){
+ if( j==pLoop->u.btree.nEq ){
+ pLoop->wsFlags |= WHERE_BIGNULL_SORT;
+ }else{
+ isMatch = 0;
+ }
+ }
if( isMatch ){
if( iColumn==XN_ROWID ){
testcase( distinctColumns==0 );
@@ -134635,8 +151395,8 @@ static i8 wherePathSatisfiesOrderBy(
Bitmask mTerm;
if( MASKBIT(i) & obSat ) continue;
p = pOrderBy->a[i].pExpr;
- mTerm = sqlite3WhereExprUsage(&pWInfo->sMaskSet,p);
- if( mTerm==0 && !sqlite3ExprIsConstant(p) ) continue;
+ mTerm = tdsqlite3WhereExprUsage(&pWInfo->sMaskSet,p);
+ if( mTerm==0 && !tdsqlite3ExprIsConstant(p) ) continue;
if( (mTerm&~orderDistinctMask)==0 ){
obSat |= MASKBIT(i);
}
@@ -134656,7 +151416,7 @@ static i8 wherePathSatisfiesOrderBy(
/*
-** If the WHERE_GROUPBY flag is set in the mask passed to sqlite3WhereBegin(),
+** If the WHERE_GROUPBY flag is set in the mask passed to tdsqlite3WhereBegin(),
** the planner assumes that the specified pOrderBy list is actually a GROUP
** BY clause - and so any order that groups rows as required satisfies the
** request.
@@ -134664,7 +151424,7 @@ static i8 wherePathSatisfiesOrderBy(
** Normally, in this case it is not possible for the caller to determine
** whether or not the rows are really being delivered in sorted order, or
** just in some other order that provides the required grouping. However,
-** if the WHERE_SORTBYGROUP flag is also passed to sqlite3WhereBegin(), then
+** if the WHERE_SORTBYGROUP flag is also passed to tdsqlite3WhereBegin(), then
** this function may be called on the returned WhereInfo object. It returns
** true if the rows really will be sorted in the specified order, or false
** otherwise.
@@ -134678,7 +151438,7 @@ static i8 wherePathSatisfiesOrderBy(
** SELECT * FROM t1 GROUP BY x,y ORDER BY x,y; -- IsSorted()==1
** SELECT * FROM t1 GROUP BY y,x ORDER BY y,x; -- IsSorted()==0
*/
-SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo *pWInfo){
+SQLITE_PRIVATE int tdsqlite3WhereIsSorted(WhereInfo *pWInfo){
assert( pWInfo->wctrlFlags & WHERE_GROUPBY );
assert( pWInfo->wctrlFlags & WHERE_SORTBYGROUP );
return pWInfo->sorted;
@@ -134721,8 +151481,8 @@ static LogEst whereSortingCost(
** The (Y/X) term is implemented using stack variable rScale
** below. */
LogEst rScale, rSortCost;
- assert( nOrderBy>0 && 66==sqlite3LogEst(100) );
- rScale = sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66;
+ assert( nOrderBy>0 && 66==tdsqlite3LogEst(100) );
+ rScale = tdsqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66;
rSortCost = nRow + rScale + 16;
/* Multiple by log(M) where M is the number of output rows.
@@ -134750,7 +151510,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
int mxChoice; /* Maximum number of simultaneous paths tracked */
int nLoop; /* Number of terms in the join */
Parse *pParse; /* Parsing context */
- sqlite3 *db; /* The database connection */
+ tdsqlite3 *db; /* The database connection */
int iLoop; /* Loop counter over the terms of the join */
int ii, jj; /* Loop counters */
int mxI = 0; /* Index of next entry to replace */
@@ -134792,7 +151552,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
/* Allocate and initialize space for aTo, aFrom and aSortCost[] */
nSpace = (sizeof(WherePath)+sizeof(WhereLoop*)*nLoop)*mxChoice*2;
nSpace += sizeof(LogEst) * nOrderBy;
- pSpace = sqlite3DbMallocRawNN(db, nSpace);
+ pSpace = tdsqlite3DbMallocRawNN(db, nSpace);
if( pSpace==0 ) return SQLITE_NOMEM_BKPT;
aTo = (WherePath*)pSpace;
aFrom = aTo+mxChoice;
@@ -134819,7 +151579,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
** TUNING: Do not let the number of iterations go above 28. If the cost
** of computing an automatic index is not paid back within the first 28
** rows, then do not use the automatic index. */
- aFrom[0].nRow = MIN(pParse->nQueryLoop, 48); assert( 48==sqlite3LogEst(28) );
+ aFrom[0].nRow = MIN(pParse->nQueryLoop, 48); assert( 48==tdsqlite3LogEst(28) );
nFrom = 1;
assert( aFrom[0].isOrdered==0 );
if( nOrderBy ){
@@ -134848,16 +151608,19 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
if( (pWLoop->prereq & ~pFrom->maskLoop)!=0 ) continue;
if( (pWLoop->maskSelf & pFrom->maskLoop)!=0 ) continue;
- if( (pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0 && pFrom->nRow<10 ){
+ if( (pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0 && pFrom->nRow<3 ){
/* Do not use an automatic index if the this loop is expected
- ** to run less than 2 times. */
- assert( 10==sqlite3LogEst(2) );
+ ** to run less than 1.25 times. It is tempting to also exclude
+ ** automatic index usage on an outer loop, but sometimes an automatic
+ ** index is useful in the outer loop of a correlated subquery. */
+ assert( 10==tdsqlite3LogEst(2) );
continue;
}
+
/* At this point, pWLoop is a candidate to be the next loop.
** Compute its cost */
- rUnsorted = sqlite3LogEstAdd(pWLoop->rSetup,pWLoop->rRun + pFrom->nRow);
- rUnsorted = sqlite3LogEstAdd(rUnsorted, pFrom->rUnsorted);
+ rUnsorted = tdsqlite3LogEstAdd(pWLoop->rSetup,pWLoop->rRun + pFrom->nRow);
+ rUnsorted = tdsqlite3LogEstAdd(rUnsorted, pFrom->rUnsorted);
nOut = pFrom->nRow + pWLoop->nOut;
maskNew = pFrom->maskLoop | pWLoop->maskSelf;
if( isOrdered<0 ){
@@ -134873,7 +151636,11 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
pWInfo, nRowEst, nOrderBy, isOrdered
);
}
- rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]);
+ /* TUNING: Add a small extra penalty (5) to sorting as an
+ ** extra encouragment to the query planner to select a plan
+ ** where the rows emerge in the correct order without any sorting
+ ** required. */
+ rCost = tdsqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]) + 5;
WHERETRACE(0x002,
("---- sort cost=%-3d (%d/%d) increases cost %3d to %-3d\n",
@@ -134881,6 +151648,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
rUnsorted, rCost));
}else{
rCost = rUnsorted;
+ rUnsorted -= 2; /* TUNING: Slight bias in favor of no-sort plans */
}
/* Check to see if pWLoop should be added to the set of
@@ -134911,9 +151679,9 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
** paths currently in the best-so-far buffer. So discard
** this candidate as not viable. */
#ifdef WHERETRACE_ENABLED /* 0x4 */
- if( sqlite3WhereTrace&0x4 ){
- sqlite3DebugPrintf("Skip %s cost=%-3d,%3d order=%c\n",
- wherePathName(pFrom, iLoop, pWLoop), rCost, nOut,
+ if( tdsqlite3WhereTrace&0x4 ){
+ tdsqlite3DebugPrintf("Skip %s cost=%-3d,%3d,%3d order=%c\n",
+ wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted,
isOrdered>=0 ? isOrdered+'0' : '?');
}
#endif
@@ -134930,27 +151698,37 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
}
pTo = &aTo[jj];
#ifdef WHERETRACE_ENABLED /* 0x4 */
- if( sqlite3WhereTrace&0x4 ){
- sqlite3DebugPrintf("New %s cost=%-3d,%3d order=%c\n",
- wherePathName(pFrom, iLoop, pWLoop), rCost, nOut,
+ if( tdsqlite3WhereTrace&0x4 ){
+ tdsqlite3DebugPrintf("New %s cost=%-3d,%3d,%3d order=%c\n",
+ wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted,
isOrdered>=0 ? isOrdered+'0' : '?');
}
#endif
}else{
/* Control reaches here if best-so-far path pTo=aTo[jj] covers the
- ** same set of loops and has the sam isOrdered setting as the
+ ** same set of loops and has the same isOrdered setting as the
** candidate path. Check to see if the candidate should replace
- ** pTo or if the candidate should be skipped */
- if( pTo->rCost<rCost || (pTo->rCost==rCost && pTo->nRow<=nOut) ){
+ ** pTo or if the candidate should be skipped.
+ **
+ ** The conditional is an expanded vector comparison equivalent to:
+ ** (pTo->rCost,pTo->nRow,pTo->rUnsorted) <= (rCost,nOut,rUnsorted)
+ */
+ if( pTo->rCost<rCost
+ || (pTo->rCost==rCost
+ && (pTo->nRow<nOut
+ || (pTo->nRow==nOut && pTo->rUnsorted<=rUnsorted)
+ )
+ )
+ ){
#ifdef WHERETRACE_ENABLED /* 0x4 */
- if( sqlite3WhereTrace&0x4 ){
- sqlite3DebugPrintf(
- "Skip %s cost=%-3d,%3d order=%c",
- wherePathName(pFrom, iLoop, pWLoop), rCost, nOut,
+ if( tdsqlite3WhereTrace&0x4 ){
+ tdsqlite3DebugPrintf(
+ "Skip %s cost=%-3d,%3d,%3d order=%c",
+ wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted,
isOrdered>=0 ? isOrdered+'0' : '?');
- sqlite3DebugPrintf(" vs %s cost=%-3d,%d order=%c\n",
+ tdsqlite3DebugPrintf(" vs %s cost=%-3d,%3d,%3d order=%c\n",
wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow,
- pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?');
+ pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?');
}
#endif
/* Discard the candidate path from further consideration */
@@ -134961,14 +151739,14 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
/* Control reaches here if the candidate path is better than the
** pTo path. Replace pTo with the candidate. */
#ifdef WHERETRACE_ENABLED /* 0x4 */
- if( sqlite3WhereTrace&0x4 ){
- sqlite3DebugPrintf(
- "Update %s cost=%-3d,%3d order=%c",
- wherePathName(pFrom, iLoop, pWLoop), rCost, nOut,
+ if( tdsqlite3WhereTrace&0x4 ){
+ tdsqlite3DebugPrintf(
+ "Update %s cost=%-3d,%3d,%3d order=%c",
+ wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted,
isOrdered>=0 ? isOrdered+'0' : '?');
- sqlite3DebugPrintf(" was %s cost=%-3d,%3d order=%c\n",
+ tdsqlite3DebugPrintf(" was %s cost=%-3d,%3d,%3d order=%c\n",
wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow,
- pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?');
+ pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?');
}
#endif
}
@@ -134999,16 +151777,16 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
}
#ifdef WHERETRACE_ENABLED /* >=2 */
- if( sqlite3WhereTrace & 0x02 ){
- sqlite3DebugPrintf("---- after round %d ----\n", iLoop);
+ if( tdsqlite3WhereTrace & 0x02 ){
+ tdsqlite3DebugPrintf("---- after round %d ----\n", iLoop);
for(ii=0, pTo=aTo; ii<nTo; ii++, pTo++){
- sqlite3DebugPrintf(" %s cost=%-3d nrow=%-3d order=%c",
+ tdsqlite3DebugPrintf(" %s cost=%-3d nrow=%-3d order=%c",
wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow,
pTo->isOrdered>=0 ? (pTo->isOrdered+'0') : '?');
if( pTo->isOrdered>0 ){
- sqlite3DebugPrintf(" rev=0x%llx\n", pTo->revLoop);
+ tdsqlite3DebugPrintf(" rev=0x%llx\n", pTo->revLoop);
}else{
- sqlite3DebugPrintf("\n");
+ tdsqlite3DebugPrintf("\n");
}
}
}
@@ -135022,8 +151800,8 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
}
if( nFrom==0 ){
- sqlite3ErrorMsg(pParse, "no query solution");
- sqlite3DbFree(db, pSpace);
+ tdsqlite3ErrorMsg(pParse, "no query solution");
+ tdsqlite3DbFreeNN(db, pSpace);
return SQLITE_ERROR;
}
@@ -135046,12 +151824,13 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
&& nRowEst
){
Bitmask notUsed;
- int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pDistinctSet, pFrom,
+ int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pResultSet, pFrom,
WHERE_DISTINCTBY, nLoop-1, pFrom->aLoop[nLoop-1], &notUsed);
- if( rc==pWInfo->pDistinctSet->nExpr ){
+ if( rc==pWInfo->pResultSet->nExpr ){
pWInfo->eDistinct = WHERE_DISTINCT_ORDERED;
}
}
+ pWInfo->bOrderedInnerLoop = 0;
if( pWInfo->pOrderBy ){
if( pWInfo->wctrlFlags & WHERE_DISTINCTBY ){
if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){
@@ -135099,7 +151878,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
pWInfo->nRowOut = pFrom->nRow;
/* Free temporary memory and return success */
- sqlite3DbFree(db, pSpace);
+ tdsqlite3DbFreeNN(db, pSpace);
return SQLITE_OK;
}
@@ -135107,7 +151886,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){
** Most queries use only a single table (they are not joins) and have
** simple == constraints against indexed fields. This routine attempts
** to plan those simple cases using much less ceremony than the
-** general-purpose query planner, and thereby yield faster sqlite3_prepare()
+** general-purpose query planner, and thereby yield faster tdsqlite3_prepare()
** times for the common case.
**
** Return non-zero on success, if this query can be handled by this
@@ -135137,7 +151916,7 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
pLoop = pBuilder->pNew;
pLoop->wsFlags = 0;
pLoop->nSkip = 0;
- pTerm = sqlite3WhereFindTerm(pWC, iCur, -1, 0, WO_EQ|WO_IS, 0);
+ pTerm = tdsqlite3WhereFindTerm(pWC, iCur, -1, 0, WO_EQ|WO_IS, 0);
if( pTerm ){
testcase( pTerm->eOperator & WO_IS );
pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW;
@@ -135145,7 +151924,7 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
pLoop->nLTerm = 1;
pLoop->u.btree.nEq = 1;
/* TUNING: Cost of a rowid lookup is 10 */
- pLoop->rRun = 33; /* 33==sqlite3LogEst(10) */
+ pLoop->rRun = 33; /* 33==tdsqlite3LogEst(10) */
}else{
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
int opMask;
@@ -135156,28 +151935,29 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
) continue;
opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ;
for(j=0; j<pIdx->nKeyCol; j++){
- pTerm = sqlite3WhereFindTerm(pWC, iCur, j, 0, opMask, pIdx);
+ pTerm = tdsqlite3WhereFindTerm(pWC, iCur, j, 0, opMask, pIdx);
if( pTerm==0 ) break;
testcase( pTerm->eOperator & WO_IS );
pLoop->aLTerm[j] = pTerm;
}
if( j!=pIdx->nKeyCol ) continue;
pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_ONEROW|WHERE_INDEXED;
- if( pIdx->isCovering || (pItem->colUsed & ~columnsInIndex(pIdx))==0 ){
+ if( pIdx->isCovering || (pItem->colUsed & pIdx->colNotIdxed)==0 ){
pLoop->wsFlags |= WHERE_IDX_ONLY;
}
pLoop->nLTerm = j;
pLoop->u.btree.nEq = j;
pLoop->u.btree.pIndex = pIdx;
/* TUNING: Cost of a unique index lookup is 15 */
- pLoop->rRun = 39; /* 39==sqlite3LogEst(15) */
+ pLoop->rRun = 39; /* 39==tdsqlite3LogEst(15) */
break;
}
}
if( pLoop->wsFlags ){
pLoop->nOut = (LogEst)1;
pWInfo->a[0].pWLoop = pLoop;
- pLoop->maskSelf = sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur);
+ assert( pWInfo->sMaskSet.n==1 && iCur==pWInfo->sMaskSet.ix[0] );
+ pLoop->maskSelf = 1; /* tdsqlite3WhereGetMask(&pWInfo->sMaskSet, iCur); */
pWInfo->a[0].iTabCur = iCur;
pWInfo->nRowOut = 1;
if( pWInfo->pOrderBy ) pWInfo->nOBSat = pWInfo->pOrderBy->nExpr;
@@ -135193,10 +151973,36 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
}
/*
+** Helper function for exprIsDeterministic().
+*/
+static int exprNodeIsDeterministic(Walker *pWalker, Expr *pExpr){
+ if( pExpr->op==TK_FUNCTION && ExprHasProperty(pExpr, EP_ConstFunc)==0 ){
+ pWalker->eCode = 0;
+ return WRC_Abort;
+ }
+ return WRC_Continue;
+}
+
+/*
+** Return true if the expression contains no non-deterministic SQL
+** functions. Do not consider non-deterministic SQL functions that are
+** part of sub-select statements.
+*/
+static int exprIsDeterministic(Expr *p){
+ Walker w;
+ memset(&w, 0, sizeof(w));
+ w.eCode = 1;
+ w.xExprCallback = exprNodeIsDeterministic;
+ w.xSelectCallback = tdsqlite3SelectWalkFail;
+ tdsqlite3WalkExpr(&w, p);
+ return w.eCode;
+}
+
+/*
** Generate the beginning of the loop used for WHERE clause processing.
** The return value is a pointer to an opaque structure that contains
** information needed to terminate the loop. Later, the calling routine
-** should invoke sqlite3WhereEnd() with the return value of this function
+** should invoke tdsqlite3WhereEnd() with the return value of this function
** in order to complete the WHERE clause processing.
**
** If an error occurs, this routine returns NULL.
@@ -135211,11 +152017,11 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
** Then the code generated is conceptually like the following:
**
** foreach row1 in t1 do \ Code generated
-** foreach row2 in t2 do |-- by sqlite3WhereBegin()
+** foreach row2 in t2 do |-- by tdsqlite3WhereBegin()
** foreach row3 in t3 do /
** ...
** end \ Code generated
-** end |-- by sqlite3WhereEnd()
+** end |-- by tdsqlite3WhereEnd()
** end /
**
** Note that the loops might not be nested in the order in which they
@@ -135227,9 +152033,9 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
** There are Btree cursors associated with each table. t1 uses cursor
** number pTabList->a[0].iCursor. t2 uses the cursor pTabList->a[1].iCursor.
** And so forth. This routine generates code to open those VDBE cursors
-** and sqlite3WhereEnd() generates the code to close them.
+** and tdsqlite3WhereEnd() generates the code to close them.
**
-** The code that sqlite3WhereBegin() generates leaves the cursors named
+** The code that tdsqlite3WhereBegin() generates leaves the cursors named
** in pTabList pointing at their appropriate entries. The [...] code
** can use OP_Column and OP_Rowid opcodes on these cursors to extract
** data from the various tables of the loop.
@@ -135280,12 +152086,12 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
** be used to compute the appropriate cursor depending on which index is
** used.
*/
-SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
+SQLITE_PRIVATE WhereInfo *tdsqlite3WhereBegin(
Parse *pParse, /* The parser context */
SrcList *pTabList, /* FROM clause: A list of all tables to be scanned */
Expr *pWhere, /* The WHERE clause */
ExprList *pOrderBy, /* An ORDER BY (or GROUP BY) clause, or NULL */
- ExprList *pDistinctSet, /* Try not to output two rows that duplicate these */
+ ExprList *pResultSet, /* Query result set. Req'd for DISTINCT */
u16 wctrlFlags, /* The WHERE_* flags defined in sqliteInt.h */
int iAuxArg /* If WHERE_OR_SUBCLAUSE is set, index cursor number
** If WHERE_USE_LIMIT, then the limit amount */
@@ -135300,7 +152106,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
WhereLevel *pLevel; /* A single level in pWInfo->a[] */
WhereLoop *pLoop; /* Pointer to a single WhereLoop object */
int ii; /* Loop counter */
- sqlite3 *db; /* Database connection */
+ tdsqlite3 *db; /* Database connection */
int rc; /* Return code */
u8 bFordelete = 0; /* OPFLAG_FORDELETE or zero, as appropriate */
@@ -135323,7 +152129,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
sWLB.pOrderBy = pOrderBy;
/* Disable the DISTINCT optimization if SQLITE_DistinctOpt is set via
- ** sqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */
+ ** tdsqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */
if( OptimizationDisabled(db, SQLITE_DistinctOpt) ){
wctrlFlags &= ~WHERE_WANT_DISTINCT;
}
@@ -135333,7 +152139,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
*/
testcase( pTabList->nSrc==BMS );
if( pTabList->nSrc>BMS ){
- sqlite3ErrorMsg(pParse, "at most %d tables in a join", BMS);
+ tdsqlite3ErrorMsg(pParse, "at most %d tables in a join", BMS);
return 0;
}
@@ -135352,19 +152158,20 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
** some architectures. Hence the ROUND8() below.
*/
nByteWInfo = ROUND8(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel));
- pWInfo = sqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop));
+ pWInfo = tdsqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop));
if( db->mallocFailed ){
- sqlite3DbFree(db, pWInfo);
+ tdsqlite3DbFree(db, pWInfo);
pWInfo = 0;
goto whereBeginError;
}
pWInfo->pParse = pParse;
pWInfo->pTabList = pTabList;
pWInfo->pOrderBy = pOrderBy;
- pWInfo->pDistinctSet = pDistinctSet;
+ pWInfo->pWhere = pWhere;
+ pWInfo->pResultSet = pResultSet;
pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1;
pWInfo->nLevel = nTabList;
- pWInfo->iBreak = pWInfo->iContinue = sqlite3VdbeMakeLabel(v);
+ pWInfo->iBreak = pWInfo->iContinue = tdsqlite3VdbeMakeLabel(pParse);
pWInfo->wctrlFlags = wctrlFlags;
pWInfo->iLimit = iAuxArg;
pWInfo->savedNQueryLoop = pParse->nQueryLoop;
@@ -135386,20 +152193,9 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
** subexpression is separated by an AND operator.
*/
initMaskSet(pMaskSet);
- sqlite3WhereClauseInit(&pWInfo->sWC, pWInfo);
- sqlite3WhereSplit(&pWInfo->sWC, pWhere, TK_AND);
+ tdsqlite3WhereClauseInit(&pWInfo->sWC, pWInfo);
+ tdsqlite3WhereSplit(&pWInfo->sWC, pWhere, TK_AND);
- /* Special case: a WHERE clause that is constant. Evaluate the
- ** expression and either jump over all of the code or fall thru.
- */
- for(ii=0; ii<sWLB.pWC->nTerm; ii++){
- if( nTabList==0 || sqlite3ExprIsConstantNotJoin(sWLB.pWC->a[ii].pExpr) ){
- sqlite3ExprIfFalse(pParse, sWLB.pWC->a[ii].pExpr, pWInfo->iBreak,
- SQLITE_JUMPIFNULL);
- sWLB.pWC->a[ii].wtFlags |= TERM_CODED;
- }
- }
-
/* Special case: No FROM clause
*/
if( nTabList==0 ){
@@ -135407,59 +152203,96 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
if( wctrlFlags & WHERE_WANT_DISTINCT ){
pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE;
}
+ ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW"));
+ }else{
+ /* Assign a bit from the bitmask to every term in the FROM clause.
+ **
+ ** The N-th term of the FROM clause is assigned a bitmask of 1<<N.
+ **
+ ** The rule of the previous sentence ensures thta if X is the bitmask for
+ ** a table T, then X-1 is the bitmask for all other tables to the left of T.
+ ** Knowing the bitmask for all tables to the left of a left join is
+ ** important. Ticket #3015.
+ **
+ ** Note that bitmasks are created for all pTabList->nSrc tables in
+ ** pTabList, not just the first nTabList tables. nTabList is normally
+ ** equal to pTabList->nSrc but might be shortened to 1 if the
+ ** WHERE_OR_SUBCLAUSE flag is set.
+ */
+ ii = 0;
+ do{
+ createMask(pMaskSet, pTabList->a[ii].iCursor);
+ tdsqlite3WhereTabFuncArgs(pParse, &pTabList->a[ii], &pWInfo->sWC);
+ }while( (++ii)<pTabList->nSrc );
+ #ifdef SQLITE_DEBUG
+ {
+ Bitmask mx = 0;
+ for(ii=0; ii<pTabList->nSrc; ii++){
+ Bitmask m = tdsqlite3WhereGetMask(pMaskSet, pTabList->a[ii].iCursor);
+ assert( m>=mx );
+ mx = m;
+ }
+ }
+ #endif
}
+
+ /* Analyze all of the subexpressions. */
+ tdsqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC);
+ if( db->mallocFailed ) goto whereBeginError;
- /* Assign a bit from the bitmask to every term in the FROM clause.
+ /* Special case: WHERE terms that do not refer to any tables in the join
+ ** (constant expressions). Evaluate each such term, and jump over all the
+ ** generated code if the result is not true.
**
- ** The N-th term of the FROM clause is assigned a bitmask of 1<<N.
+ ** Do not do this if the expression contains non-deterministic functions
+ ** that are not within a sub-select. This is not strictly required, but
+ ** preserves SQLite's legacy behaviour in the following two cases:
**
- ** The rule of the previous sentence ensures thta if X is the bitmask for
- ** a table T, then X-1 is the bitmask for all other tables to the left of T.
- ** Knowing the bitmask for all tables to the left of a left join is
- ** important. Ticket #3015.
- **
- ** Note that bitmasks are created for all pTabList->nSrc tables in
- ** pTabList, not just the first nTabList tables. nTabList is normally
- ** equal to pTabList->nSrc but might be shortened to 1 if the
- ** WHERE_OR_SUBCLAUSE flag is set.
+ ** FROM ... WHERE random()>0; -- eval random() once per row
+ ** FROM ... WHERE (SELECT random())>0; -- eval random() once overall
*/
- for(ii=0; ii<pTabList->nSrc; ii++){
- createMask(pMaskSet, pTabList->a[ii].iCursor);
- sqlite3WhereTabFuncArgs(pParse, &pTabList->a[ii], &pWInfo->sWC);
- }
-#ifdef SQLITE_DEBUG
- for(ii=0; ii<pTabList->nSrc; ii++){
- Bitmask m = sqlite3WhereGetMask(pMaskSet, pTabList->a[ii].iCursor);
- assert( m==MASKBIT(ii) );
+ for(ii=0; ii<sWLB.pWC->nTerm; ii++){
+ WhereTerm *pT = &sWLB.pWC->a[ii];
+ if( pT->wtFlags & TERM_VIRTUAL ) continue;
+ if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){
+ tdsqlite3ExprIfFalse(pParse, pT->pExpr, pWInfo->iBreak, SQLITE_JUMPIFNULL);
+ pT->wtFlags |= TERM_CODED;
+ }
}
-#endif
-
- /* Analyze all of the subexpressions. */
- sqlite3WhereExprAnalyze(pTabList, &pWInfo->sWC);
- if( db->mallocFailed ) goto whereBeginError;
if( wctrlFlags & WHERE_WANT_DISTINCT ){
- if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pDistinctSet) ){
+ if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){
/* The DISTINCT marking is pointless. Ignore it. */
pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE;
}else if( pOrderBy==0 ){
/* Try to ORDER BY the result set to make distinct processing easier */
pWInfo->wctrlFlags |= WHERE_DISTINCTBY;
- pWInfo->pOrderBy = pDistinctSet;
+ pWInfo->pOrderBy = pResultSet;
}
}
/* Construct the WhereLoop objects */
#if defined(WHERETRACE_ENABLED)
- if( sqlite3WhereTrace & 0xffff ){
- sqlite3DebugPrintf("*** Optimizer Start *** (wctrlFlags: 0x%x",wctrlFlags);
+ if( tdsqlite3WhereTrace & 0xffff ){
+ tdsqlite3DebugPrintf("*** Optimizer Start *** (wctrlFlags: 0x%x",wctrlFlags);
if( wctrlFlags & WHERE_USE_LIMIT ){
- sqlite3DebugPrintf(", limit: %d", iAuxArg);
+ tdsqlite3DebugPrintf(", limit: %d", iAuxArg);
+ }
+ tdsqlite3DebugPrintf(")\n");
+ if( tdsqlite3WhereTrace & 0x100 ){
+ Select sSelect;
+ memset(&sSelect, 0, sizeof(sSelect));
+ sSelect.selFlags = SF_WhereBegin;
+ sSelect.pSrc = pTabList;
+ sSelect.pWhere = pWhere;
+ sSelect.pOrderBy = pOrderBy;
+ sSelect.pEList = pResultSet;
+ tdsqlite3TreeViewSelect(0, &sSelect, 0);
}
- sqlite3DebugPrintf(")\n");
}
- if( sqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */
- sqlite3WhereClausePrint(sWLB.pWC);
+ if( tdsqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */
+ tdsqlite3DebugPrintf("---- WHERE clause at start of analysis:\n");
+ tdsqlite3WhereClausePrint(sWLB.pWC);
}
#endif
@@ -135468,14 +152301,14 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
if( rc ) goto whereBeginError;
#ifdef WHERETRACE_ENABLED
- if( sqlite3WhereTrace ){ /* Display all of the WhereLoop objects */
+ if( tdsqlite3WhereTrace ){ /* Display all of the WhereLoop objects */
WhereLoop *p;
int i;
static const char zLabel[] = "0123456789abcdefghijklmnopqrstuvwyxz"
"ABCDEFGHIJKLMNOPQRSTUVWYXZ";
for(p=pWInfo->pLoops, i=0; p; p=p->pNextLoop, i++){
- p->cId = zLabel[i%sizeof(zLabel)];
- whereLoopPrint(p, sWLB.pWC);
+ p->cId = zLabel[i%(sizeof(zLabel)-1)];
+ tdsqlite3WhereLoopPrint(p, sWLB.pWC);
}
}
#endif
@@ -135494,78 +152327,147 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
goto whereBeginError;
}
#ifdef WHERETRACE_ENABLED
- if( sqlite3WhereTrace ){
- sqlite3DebugPrintf("---- Solution nRow=%d", pWInfo->nRowOut);
+ if( tdsqlite3WhereTrace ){
+ tdsqlite3DebugPrintf("---- Solution nRow=%d", pWInfo->nRowOut);
if( pWInfo->nOBSat>0 ){
- sqlite3DebugPrintf(" ORDERBY=%d,0x%llx", pWInfo->nOBSat, pWInfo->revMask);
+ tdsqlite3DebugPrintf(" ORDERBY=%d,0x%llx", pWInfo->nOBSat, pWInfo->revMask);
}
switch( pWInfo->eDistinct ){
case WHERE_DISTINCT_UNIQUE: {
- sqlite3DebugPrintf(" DISTINCT=unique");
+ tdsqlite3DebugPrintf(" DISTINCT=unique");
break;
}
case WHERE_DISTINCT_ORDERED: {
- sqlite3DebugPrintf(" DISTINCT=ordered");
+ tdsqlite3DebugPrintf(" DISTINCT=ordered");
break;
}
case WHERE_DISTINCT_UNORDERED: {
- sqlite3DebugPrintf(" DISTINCT=unordered");
+ tdsqlite3DebugPrintf(" DISTINCT=unordered");
break;
}
}
- sqlite3DebugPrintf("\n");
+ tdsqlite3DebugPrintf("\n");
for(ii=0; ii<pWInfo->nLevel; ii++){
- whereLoopPrint(pWInfo->a[ii].pWLoop, sWLB.pWC);
+ tdsqlite3WhereLoopPrint(pWInfo->a[ii].pWLoop, sWLB.pWC);
}
}
#endif
- /* Attempt to omit tables from the join that do not effect the result */
+
+ /* Attempt to omit tables from the join that do not affect the result.
+ ** For a table to not affect the result, the following must be true:
+ **
+ ** 1) The query must not be an aggregate.
+ ** 2) The table must be the RHS of a LEFT JOIN.
+ ** 3) Either the query must be DISTINCT, or else the ON or USING clause
+ ** must contain a constraint that limits the scan of the table to
+ ** at most a single row.
+ ** 4) The table must not be referenced by any part of the query apart
+ ** from its own USING or ON clause.
+ **
+ ** For example, given:
+ **
+ ** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1);
+ ** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2);
+ ** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3);
+ **
+ ** then table t2 can be omitted from the following:
+ **
+ ** SELECT v1, v3 FROM t1
+ ** LEFT JOIN t2 ON (t1.ipk=t2.ipk)
+ ** LEFT JOIN t3 ON (t1.ipk=t3.ipk)
+ **
+ ** or from:
+ **
+ ** SELECT DISTINCT v1, v3 FROM t1
+ ** LEFT JOIN t2
+ ** LEFT JOIN t3 ON (t1.ipk=t3.ipk)
+ */
+ notReady = ~(Bitmask)0;
if( pWInfo->nLevel>=2
- && pDistinctSet!=0
+ && pResultSet!=0 /* guarantees condition (1) above */
&& OptimizationEnabled(db, SQLITE_OmitNoopJoin)
){
- Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pDistinctSet);
+ int i;
+ Bitmask tabUsed = tdsqlite3WhereExprListUsage(pMaskSet, pResultSet);
if( sWLB.pOrderBy ){
- tabUsed |= sqlite3WhereExprListUsage(pMaskSet, sWLB.pOrderBy);
+ tabUsed |= tdsqlite3WhereExprListUsage(pMaskSet, sWLB.pOrderBy);
}
- while( pWInfo->nLevel>=2 ){
+ for(i=pWInfo->nLevel-1; i>=1; i--){
WhereTerm *pTerm, *pEnd;
- pLoop = pWInfo->a[pWInfo->nLevel-1].pWLoop;
- if( (pWInfo->pTabList->a[pLoop->iTab].fg.jointype & JT_LEFT)==0 ) break;
+ struct SrcList_item *pItem;
+ pLoop = pWInfo->a[i].pWLoop;
+ pItem = &pWInfo->pTabList->a[pLoop->iTab];
+ if( (pItem->fg.jointype & JT_LEFT)==0 ) continue;
if( (wctrlFlags & WHERE_WANT_DISTINCT)==0
&& (pLoop->wsFlags & WHERE_ONEROW)==0
){
- break;
+ continue;
}
- if( (tabUsed & pLoop->maskSelf)!=0 ) break;
+ if( (tabUsed & pLoop->maskSelf)!=0 ) continue;
pEnd = sWLB.pWC->a + sWLB.pWC->nTerm;
for(pTerm=sWLB.pWC->a; pTerm<pEnd; pTerm++){
- if( (pTerm->prereqAll & pLoop->maskSelf)!=0
- && !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
- ){
- break;
+ if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){
+ if( !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
+ || pTerm->pExpr->iRightJoinTable!=pItem->iCursor
+ ){
+ break;
+ }
}
}
- if( pTerm<pEnd ) break;
+ if( pTerm<pEnd ) continue;
WHERETRACE(0xffff, ("-> drop loop %c not used\n", pLoop->cId));
+ notReady &= ~pLoop->maskSelf;
+ for(pTerm=sWLB.pWC->a; pTerm<pEnd; pTerm++){
+ if( (pTerm->prereqAll & pLoop->maskSelf)!=0 ){
+ pTerm->wtFlags |= TERM_CODED;
+ }
+ }
+ if( i!=pWInfo->nLevel-1 ){
+ int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel);
+ memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte);
+ }
pWInfo->nLevel--;
nTabList--;
}
}
+#if defined(WHERETRACE_ENABLED)
+ if( tdsqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */
+ tdsqlite3DebugPrintf("---- WHERE clause at end of analysis:\n");
+ tdsqlite3WhereClausePrint(sWLB.pWC);
+ }
WHERETRACE(0xffff,("*** Optimizer Finished ***\n"));
+#endif
pWInfo->pParse->nQueryLoop += pWInfo->nRowOut;
/* If the caller is an UPDATE or DELETE statement that is requesting
** to use a one-pass algorithm, determine if this is appropriate.
+ **
+ ** A one-pass approach can be used if the caller has requested one
+ ** and either (a) the scan visits at most one row or (b) each
+ ** of the following are true:
+ **
+ ** * the caller has indicated that a one-pass approach can be used
+ ** with multiple rows (by setting WHERE_ONEPASS_MULTIROW), and
+ ** * the table is not a virtual table, and
+ ** * either the scan does not use the OR optimization or the caller
+ ** is a DELETE operation (WHERE_DUPLICATES_OK is only specified
+ ** for DELETE).
+ **
+ ** The last qualification is because an UPDATE statement uses
+ ** WhereInfo.aiCurOnePass[1] to determine whether or not it really can
+ ** use a one-pass approach, and this is not set accurately for scans
+ ** that use the OR optimization.
*/
assert( (wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || pWInfo->nLevel==1 );
if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 ){
int wsFlags = pWInfo->a[0].pWLoop->wsFlags;
int bOnerow = (wsFlags & WHERE_ONEROW)!=0;
- if( bOnerow
- || ((wctrlFlags & WHERE_ONEPASS_MULTIROW)!=0
- && 0==(wsFlags & WHERE_VIRTUALTABLE))
- ){
+ assert( !(wsFlags & WHERE_VIRTUALTABLE) || IsVirtual(pTabList->a[0].pTab) );
+ if( bOnerow || (
+ 0!=(wctrlFlags & WHERE_ONEPASS_MULTIROW)
+ && !IsVirtual(pTabList->a[0].pTab)
+ && (0==(wsFlags & WHERE_MULTI_OR) || (wctrlFlags & WHERE_DUPLICATES_OK))
+ )){
pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI;
if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){
if( wctrlFlags & WHERE_ONEPASS_MULTIROW ){
@@ -135586,16 +152488,16 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
pTabItem = &pTabList->a[pLevel->iFrom];
pTab = pTabItem->pTab;
- iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+ iDb = tdsqlite3SchemaToIndex(db, pTab->pSchema);
pLoop = pLevel->pWLoop;
if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){
/* Do nothing */
}else
#ifndef SQLITE_OMIT_VIRTUALTABLE
if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)!=0 ){
- const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
+ const char *pVTab = (const char *)tdsqlite3GetVTable(db, pTab);
int iCur = pTabItem->iCursor;
- sqlite3VdbeAddOp4(v, OP_VOpen, iCur, 0, 0, pVTab, P4_VTAB);
+ tdsqlite3VdbeAddOp4(v, OP_VOpen, iCur, 0, 0, pVTab, P4_VTAB);
}else if( IsVirtual(pTab) ){
/* noop */
}else
@@ -135607,37 +152509,43 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
op = OP_OpenWrite;
pWInfo->aiCurOnePass[0] = pTabItem->iCursor;
};
- sqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op);
+ tdsqlite3OpenTable(pParse, pTabItem->iCursor, iDb, pTab, op);
assert( pTabItem->iCursor==pLevel->iTabCur );
testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS-1 );
testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS );
- if( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol<BMS && HasRowid(pTab) ){
+ if( pWInfo->eOnePass==ONEPASS_OFF
+ && pTab->nCol<BMS
+ && (pTab->tabFlags & (TF_HasGenerated|TF_WithoutRowid))==0
+ ){
+ /* If we know that only a prefix of the record will be used,
+ ** it is advantageous to reduce the "column count" field in
+ ** the P4 operand of the OP_OpenRead/Write opcode. */
Bitmask b = pTabItem->colUsed;
int n = 0;
for(; b; b=b>>1, n++){}
- sqlite3VdbeChangeP4(v, -1, SQLITE_INT_TO_PTR(n), P4_INT32);
+ tdsqlite3VdbeChangeP4(v, -1, SQLITE_INT_TO_PTR(n), P4_INT32);
assert( n<=pTab->nCol );
}
#ifdef SQLITE_ENABLE_CURSOR_HINTS
if( pLoop->u.btree.pIndex!=0 ){
- sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ|bFordelete);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ|bFordelete);
}else
#endif
{
- sqlite3VdbeChangeP5(v, bFordelete);
+ tdsqlite3VdbeChangeP5(v, bFordelete);
}
#ifdef SQLITE_ENABLE_COLUMN_USED_MASK
- sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, pTabItem->iCursor, 0, 0,
+ tdsqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, pTabItem->iCursor, 0, 0,
(const u8*)&pTabItem->colUsed, P4_INT64);
#endif
}else{
- sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
+ tdsqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
}
if( pLoop->wsFlags & WHERE_INDEXED ){
Index *pIx = pLoop->u.btree.pIndex;
int iIndexCur;
int op = OP_OpenRead;
- /* iAuxArg is always set if to a positive value if ONEPASS is possible */
+ /* iAuxArg is always set to a positive value if ONEPASS is possible */
assert( iAuxArg!=0 || (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 );
if( !HasRowid(pTab) && IsPrimaryKeyIndex(pIx)
&& (wctrlFlags & WHERE_OR_SUBCLAUSE)!=0
@@ -135666,13 +152574,15 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
assert( pIx->pSchema==pTab->pSchema );
assert( iIndexCur>=0 );
if( op ){
- sqlite3VdbeAddOp3(v, op, iIndexCur, pIx->tnum, iDb);
- sqlite3VdbeSetP4KeyInfo(pParse, pIx);
+ tdsqlite3VdbeAddOp3(v, op, iIndexCur, pIx->tnum, iDb);
+ tdsqlite3VdbeSetP4KeyInfo(pParse, pIx);
if( (pLoop->wsFlags & WHERE_CONSTRAINT)!=0
&& (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0
+ && (pLoop->wsFlags & WHERE_BIGNULL_SORT)==0
&& (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0
+ && pWInfo->eDistinct!=WHERE_DISTINCT_ORDERED
){
- sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); /* Hint to COMDB2 */
+ tdsqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); /* Hint to COMDB2 */
}
VdbeComment((v, "%s", pIx->zName));
#ifdef SQLITE_ENABLE_COLUMN_USED_MASK
@@ -135686,22 +152596,21 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
if( (pTabItem->colUsed & MASKBIT(jj))==0 ) continue;
colUsed |= ((u64)1)<<(ii<63 ? ii : 63);
}
- sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, iIndexCur, 0, 0,
+ tdsqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, iIndexCur, 0, 0,
(u8*)&colUsed, P4_INT64);
}
#endif /* SQLITE_ENABLE_COLUMN_USED_MASK */
}
}
- if( iDb>=0 ) sqlite3CodeVerifySchema(pParse, iDb);
+ if( iDb>=0 ) tdsqlite3CodeVerifySchema(pParse, iDb);
}
- pWInfo->iTop = sqlite3VdbeCurrentAddr(v);
+ pWInfo->iTop = tdsqlite3VdbeCurrentAddr(v);
if( db->mallocFailed ) goto whereBeginError;
/* Generate the code to do the search. Each iteration of the for
** loop below generates code for a single nested loop of the VM
** program.
*/
- notReady = ~(Bitmask)0;
for(ii=0; ii<nTabList; ii++){
int addrExplain;
int wsFlags;
@@ -135714,14 +152623,14 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(
if( db->mallocFailed ) goto whereBeginError;
}
#endif
- addrExplain = sqlite3WhereExplainOneScan(
- pParse, pTabList, pLevel, ii, pLevel->iFrom, wctrlFlags
+ addrExplain = tdsqlite3WhereExplainOneScan(
+ pParse, pTabList, pLevel, wctrlFlags
);
- pLevel->addrBody = sqlite3VdbeCurrentAddr(v);
- notReady = sqlite3WhereCodeOneLoopStart(pWInfo, ii, notReady);
+ pLevel->addrBody = tdsqlite3VdbeCurrentAddr(v);
+ notReady = tdsqlite3WhereCodeOneLoopStart(pParse,v,pWInfo,ii,pLevel,notReady);
pWInfo->iContinue = pLevel->addrCont;
if( (wsFlags&WHERE_MULTI_OR)==0 && (wctrlFlags&WHERE_OR_SUBCLAUSE)==0 ){
- sqlite3WhereAddScanStatus(v, pTabList, pLevel, addrExplain);
+ tdsqlite3WhereAddScanStatus(v, pTabList, pLevel, addrExplain);
}
}
@@ -135739,82 +152648,160 @@ whereBeginError:
}
/*
+** Part of tdsqlite3WhereEnd() will rewrite opcodes to reference the
+** index rather than the main table. In SQLITE_DEBUG mode, we want
+** to trace those changes if PRAGMA vdbe_addoptrace=on. This routine
+** does that.
+*/
+#ifndef SQLITE_DEBUG
+# define OpcodeRewriteTrace(D,K,P) /* no-op */
+#else
+# define OpcodeRewriteTrace(D,K,P) tdsqlite3WhereOpcodeRewriteTrace(D,K,P)
+ static void tdsqlite3WhereOpcodeRewriteTrace(
+ tdsqlite3 *db,
+ int pc,
+ VdbeOp *pOp
+ ){
+ if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return;
+ tdsqlite3VdbePrintOp(0, pc, pOp);
+ }
+#endif
+
+/*
** Generate the end of the WHERE loop. See comments on
-** sqlite3WhereBegin() for additional information.
+** tdsqlite3WhereBegin() for additional information.
*/
-SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
+SQLITE_PRIVATE void tdsqlite3WhereEnd(WhereInfo *pWInfo){
Parse *pParse = pWInfo->pParse;
Vdbe *v = pParse->pVdbe;
int i;
WhereLevel *pLevel;
WhereLoop *pLoop;
SrcList *pTabList = pWInfo->pTabList;
- sqlite3 *db = pParse->db;
+ tdsqlite3 *db = pParse->db;
/* Generate loop termination code.
*/
VdbeModuleComment((v, "End WHERE-core"));
- sqlite3ExprCacheClear(pParse);
for(i=pWInfo->nLevel-1; i>=0; i--){
int addr;
pLevel = &pWInfo->a[i];
pLoop = pLevel->pWLoop;
- sqlite3VdbeResolveLabel(v, pLevel->addrCont);
if( pLevel->op!=OP_Noop ){
- sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3);
- sqlite3VdbeChangeP5(v, pLevel->p5);
+#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT
+ int addrSeek = 0;
+ Index *pIdx;
+ int n;
+ if( pWInfo->eDistinct==WHERE_DISTINCT_ORDERED
+ && i==pWInfo->nLevel-1 /* Ticket [ef9318757b152e3] 2017-10-21 */
+ && (pLoop->wsFlags & WHERE_INDEXED)!=0
+ && (pIdx = pLoop->u.btree.pIndex)->hasStat1
+ && (n = pLoop->u.btree.nDistinctCol)>0
+ && pIdx->aiRowLogEst[n]>=36
+ ){
+ int r1 = pParse->nMem+1;
+ int j, op;
+ for(j=0; j<n; j++){
+ tdsqlite3VdbeAddOp3(v, OP_Column, pLevel->iIdxCur, j, r1+j);
+ }
+ pParse->nMem += n+1;
+ op = pLevel->op==OP_Prev ? OP_SeekLT : OP_SeekGT;
+ addrSeek = tdsqlite3VdbeAddOp4Int(v, op, pLevel->iIdxCur, 0, r1, n);
+ VdbeCoverageIf(v, op==OP_SeekLT);
+ VdbeCoverageIf(v, op==OP_SeekGT);
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2);
+ }
+#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */
+ /* The common case: Advance to the next row */
+ tdsqlite3VdbeResolveLabel(v, pLevel->addrCont);
+ tdsqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3);
+ tdsqlite3VdbeChangeP5(v, pLevel->p5);
VdbeCoverage(v);
VdbeCoverageIf(v, pLevel->op==OP_Next);
VdbeCoverageIf(v, pLevel->op==OP_Prev);
VdbeCoverageIf(v, pLevel->op==OP_VNext);
+ if( pLevel->regBignull ){
+ tdsqlite3VdbeResolveLabel(v, pLevel->addrBignull);
+ tdsqlite3VdbeAddOp2(v, OP_DecrJumpZero, pLevel->regBignull, pLevel->p2-1);
+ VdbeCoverage(v);
+ }
+#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT
+ if( addrSeek ) tdsqlite3VdbeJumpHere(v, addrSeek);
+#endif
+ }else{
+ tdsqlite3VdbeResolveLabel(v, pLevel->addrCont);
}
if( pLoop->wsFlags & WHERE_IN_ABLE && pLevel->u.in.nIn>0 ){
struct InLoop *pIn;
int j;
- sqlite3VdbeResolveLabel(v, pLevel->addrNxt);
+ tdsqlite3VdbeResolveLabel(v, pLevel->addrNxt);
for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){
- sqlite3VdbeJumpHere(v, pIn->addrInTop+1);
+ tdsqlite3VdbeJumpHere(v, pIn->addrInTop+1);
if( pIn->eEndLoopOp!=OP_Noop ){
- sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop);
+ if( pIn->nPrefix ){
+ assert( pLoop->wsFlags & WHERE_IN_EARLYOUT );
+ if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ){
+ tdsqlite3VdbeAddOp4Int(v, OP_IfNoHope, pLevel->iIdxCur,
+ tdsqlite3VdbeCurrentAddr(v)+2+(pLevel->iLeftJoin!=0),
+ pIn->iBase, pIn->nPrefix);
+ VdbeCoverage(v);
+ }
+ if( pLevel->iLeftJoin ){
+ /* For LEFT JOIN queries, cursor pIn->iCur may not have been
+ ** opened yet. This occurs for WHERE clauses such as
+ ** "a = ? AND b IN (...)", where the index is on (a, b). If
+ ** the RHS of the (a=?) is NULL, then the "b IN (...)" may
+ ** never have been coded, but the body of the loop run to
+ ** return the null-row. So, if the cursor is not open yet,
+ ** jump over the OP_Next or OP_Prev instruction about to
+ ** be coded. */
+ tdsqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur,
+ tdsqlite3VdbeCurrentAddr(v) + 2
+ );
+ VdbeCoverage(v);
+ }
+ }
+ tdsqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop);
VdbeCoverage(v);
- VdbeCoverageIf(v, pIn->eEndLoopOp==OP_PrevIfOpen);
- VdbeCoverageIf(v, pIn->eEndLoopOp==OP_NextIfOpen);
+ VdbeCoverageIf(v, pIn->eEndLoopOp==OP_Prev);
+ VdbeCoverageIf(v, pIn->eEndLoopOp==OP_Next);
}
- sqlite3VdbeJumpHere(v, pIn->addrInTop-1);
+ tdsqlite3VdbeJumpHere(v, pIn->addrInTop-1);
}
}
- sqlite3VdbeResolveLabel(v, pLevel->addrBrk);
+ tdsqlite3VdbeResolveLabel(v, pLevel->addrBrk);
if( pLevel->addrSkip ){
- sqlite3VdbeGoto(v, pLevel->addrSkip);
+ tdsqlite3VdbeGoto(v, pLevel->addrSkip);
VdbeComment((v, "next skip-scan on %s", pLoop->u.btree.pIndex->zName));
- sqlite3VdbeJumpHere(v, pLevel->addrSkip);
- sqlite3VdbeJumpHere(v, pLevel->addrSkip-2);
+ tdsqlite3VdbeJumpHere(v, pLevel->addrSkip);
+ tdsqlite3VdbeJumpHere(v, pLevel->addrSkip-2);
}
#ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS
if( pLevel->addrLikeRep ){
- sqlite3VdbeAddOp2(v, OP_DecrJumpZero, (int)(pLevel->iLikeRepCntr>>1),
+ tdsqlite3VdbeAddOp2(v, OP_DecrJumpZero, (int)(pLevel->iLikeRepCntr>>1),
pLevel->addrLikeRep);
VdbeCoverage(v);
}
#endif
if( pLevel->iLeftJoin ){
int ws = pLoop->wsFlags;
- addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v);
+ addr = tdsqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v);
assert( (ws & WHERE_IDX_ONLY)==0 || (ws & WHERE_INDEXED)!=0 );
if( (ws & WHERE_IDX_ONLY)==0 ){
- sqlite3VdbeAddOp1(v, OP_NullRow, pTabList->a[i].iCursor);
+ assert( pLevel->iTabCur==pTabList->a[pLevel->iFrom].iCursor );
+ tdsqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur);
}
if( (ws & WHERE_INDEXED)
|| ((ws & WHERE_MULTI_OR) && pLevel->u.pCovidx)
){
- sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur);
+ tdsqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur);
}
if( pLevel->op==OP_Return ){
- sqlite3VdbeAddOp2(v, OP_Gosub, pLevel->p1, pLevel->addrFirst);
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, pLevel->p1, pLevel->addrFirst);
}else{
- sqlite3VdbeGoto(v, pLevel->addrFirst);
+ tdsqlite3VdbeGoto(v, pLevel->addrFirst);
}
- sqlite3VdbeJumpHere(v, addr);
+ tdsqlite3VdbeJumpHere(v, addr);
}
VdbeModuleComment((v, "End WHERE-loop%d: %s", i,
pWInfo->pTabList->a[pLevel->iFrom].pTab->zName));
@@ -135823,7 +152810,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
/* The "break" point is here, just past the end of the outer loop.
** Set it.
*/
- sqlite3VdbeResolveLabel(v, pWInfo->iBreak);
+ tdsqlite3VdbeResolveLabel(v, pWInfo->iBreak);
assert( pWInfo->nLevel<=pTabList->nSrc );
for(i=0, pLevel=pWInfo->a; i<pWInfo->nLevel; i++, pLevel++){
@@ -135839,13 +152826,15 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
** the co-routine into OP_Copy of result contained in a register.
** OP_Rowid becomes OP_Null.
*/
- if( pTabItem->fg.viaCoroutine && !db->mallocFailed ){
- translateColumnToCopy(v, pLevel->addrBody, pLevel->iTabCur,
+ if( pTabItem->fg.viaCoroutine ){
+ testcase( pParse->db->mallocFailed );
+ translateColumnToCopy(pParse, pLevel->addrBody, pLevel->iTabCur,
pTabItem->regResult, 0);
continue;
}
- /* Close all of the cursors that were opened by sqlite3WhereBegin.
+#ifdef SQLITE_ENABLE_EARLY_CURSOR_CLOSE
+ /* Close all of the cursors that were opened by tdsqlite3WhereBegin.
** Except, do not close cursors that will be reused by the OR optimization
** (WHERE_OR_SUBCLAUSE). And do not close the OP_OpenWrite cursors
** created for the ONEPASS optimization.
@@ -135856,23 +152845,24 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
){
int ws = pLoop->wsFlags;
if( pWInfo->eOnePass==ONEPASS_OFF && (ws & WHERE_IDX_ONLY)==0 ){
- sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor);
+ tdsqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor);
}
if( (ws & WHERE_INDEXED)!=0
&& (ws & (WHERE_IPK|WHERE_AUTO_INDEX))==0
&& pLevel->iIdxCur!=pWInfo->aiCurOnePass[1]
){
- sqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur);
+ tdsqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur);
}
}
+#endif
/* If this scan uses an index, make VDBE code substitutions to read data
** from the index instead of from the table where possible. In some cases
** this optimization prevents the table from ever being read, which can
** yield a significant performance boost.
**
- ** Calls to the code generator in between sqlite3WhereBegin and
- ** sqlite3WhereEnd will have created code that references the table
+ ** Calls to the code generator in between tdsqlite3WhereBegin and
+ ** tdsqlite3WhereEnd will have created code that references the table
** directly. This loop scans all that code looking for opcodes
** that reference the table and converts them into opcodes that
** reference the index.
@@ -135886,33 +152876,62 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
&& (pWInfo->eOnePass==ONEPASS_OFF || !HasRowid(pIdx->pTable))
&& !db->mallocFailed
){
- last = sqlite3VdbeCurrentAddr(v);
+ last = tdsqlite3VdbeCurrentAddr(v);
k = pLevel->addrBody;
- pOp = sqlite3VdbeGetOp(v, k);
+#ifdef SQLITE_DEBUG
+ if( db->flags & SQLITE_VdbeAddopTrace ){
+ printf("TRANSLATE opcodes in range %d..%d\n", k, last-1);
+ }
+#endif
+ pOp = tdsqlite3VdbeGetOp(v, k);
for(; k<last; k++, pOp++){
if( pOp->p1!=pLevel->iTabCur ) continue;
- if( pOp->opcode==OP_Column ){
+ if( pOp->opcode==OP_Column
+#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
+ || pOp->opcode==OP_Offset
+#endif
+ ){
int x = pOp->p2;
assert( pIdx->pTable==pTab );
if( !HasRowid(pTab) ){
- Index *pPk = sqlite3PrimaryKeyIndex(pTab);
+ Index *pPk = tdsqlite3PrimaryKeyIndex(pTab);
x = pPk->aiColumn[x];
assert( x>=0 );
+ }else{
+ testcase( x!=tdsqlite3StorageColumnToTable(pTab,x) );
+ x = tdsqlite3StorageColumnToTable(pTab,x);
}
- x = sqlite3ColumnOfIndex(pIdx, x);
+ x = tdsqlite3TableColumnToIndex(pIdx, x);
if( x>=0 ){
pOp->p2 = x;
pOp->p1 = pLevel->iIdxCur;
+ OpcodeRewriteTrace(db, k, pOp);
}
- assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0 );
+ assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0
+ || pWInfo->eOnePass );
}else if( pOp->opcode==OP_Rowid ){
pOp->p1 = pLevel->iIdxCur;
pOp->opcode = OP_IdxRowid;
+ OpcodeRewriteTrace(db, k, pOp);
+ }else if( pOp->opcode==OP_IfNullRow ){
+ pOp->p1 = pLevel->iIdxCur;
+ OpcodeRewriteTrace(db, k, pOp);
}
}
+#ifdef SQLITE_DEBUG
+ if( db->flags & SQLITE_VdbeAddopTrace ) printf("TRANSLATE complete\n");
+#endif
}
}
+ /* Undo all Expr node modifications */
+ while( pWInfo->pExprMods ){
+ WhereExprMod *p = pWInfo->pExprMods;
+ pWInfo->pExprMods = p->pNext;
+ memcpy(p->pExpr, &p->orig, sizeof(p->orig));
+ tdsqlite3DbFree(db, p);
+ }
+
/* Final cleanup
*/
pParse->nQueryLoop = pWInfo->savedNQueryLoop;
@@ -135921,6 +152940,3008 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
}
/************** End of where.c ***********************************************/
+/************** Begin file window.c ******************************************/
+/*
+** 2018 May 08
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+*/
+/* #include "sqliteInt.h" */
+
+#ifndef SQLITE_OMIT_WINDOWFUNC
+
+/*
+** SELECT REWRITING
+**
+** Any SELECT statement that contains one or more window functions in
+** either the select list or ORDER BY clause (the only two places window
+** functions may be used) is transformed by function tdsqlite3WindowRewrite()
+** in order to support window function processing. For example, with the
+** schema:
+**
+** CREATE TABLE t1(a, b, c, d, e, f, g);
+**
+** the statement:
+**
+** SELECT a+1, max(b) OVER (PARTITION BY c ORDER BY d) FROM t1 ORDER BY e;
+**
+** is transformed to:
+**
+** SELECT a+1, max(b) OVER (PARTITION BY c ORDER BY d) FROM (
+** SELECT a, e, c, d, b FROM t1 ORDER BY c, d
+** ) ORDER BY e;
+**
+** The flattening optimization is disabled when processing this transformed
+** SELECT statement. This allows the implementation of the window function
+** (in this case max()) to process rows sorted in order of (c, d), which
+** makes things easier for obvious reasons. More generally:
+**
+** * FROM, WHERE, GROUP BY and HAVING clauses are all moved to
+** the sub-query.
+**
+** * ORDER BY, LIMIT and OFFSET remain part of the parent query.
+**
+** * Terminals from each of the expression trees that make up the
+** select-list and ORDER BY expressions in the parent query are
+** selected by the sub-query. For the purposes of the transformation,
+** terminals are column references and aggregate functions.
+**
+** If there is more than one window function in the SELECT that uses
+** the same window declaration (the OVER bit), then a single scan may
+** be used to process more than one window function. For example:
+**
+** SELECT max(b) OVER (PARTITION BY c ORDER BY d),
+** min(e) OVER (PARTITION BY c ORDER BY d)
+** FROM t1;
+**
+** is transformed in the same way as the example above. However:
+**
+** SELECT max(b) OVER (PARTITION BY c ORDER BY d),
+** min(e) OVER (PARTITION BY a ORDER BY b)
+** FROM t1;
+**
+** Must be transformed to:
+**
+** SELECT max(b) OVER (PARTITION BY c ORDER BY d) FROM (
+** SELECT e, min(e) OVER (PARTITION BY a ORDER BY b), c, d, b FROM
+** SELECT a, e, c, d, b FROM t1 ORDER BY a, b
+** ) ORDER BY c, d
+** ) ORDER BY e;
+**
+** so that both min() and max() may process rows in the order defined by
+** their respective window declarations.
+**
+** INTERFACE WITH SELECT.C
+**
+** When processing the rewritten SELECT statement, code in select.c calls
+** tdsqlite3WhereBegin() to begin iterating through the results of the
+** sub-query, which is always implemented as a co-routine. It then calls
+** tdsqlite3WindowCodeStep() to process rows and finish the scan by calling
+** tdsqlite3WhereEnd().
+**
+** tdsqlite3WindowCodeStep() generates VM code so that, for each row returned
+** by the sub-query a sub-routine (OP_Gosub) coded by select.c is invoked.
+** When the sub-routine is invoked:
+**
+** * The results of all window-functions for the row are stored
+** in the associated Window.regResult registers.
+**
+** * The required terminal values are stored in the current row of
+** temp table Window.iEphCsr.
+**
+** In some cases, depending on the window frame and the specific window
+** functions invoked, tdsqlite3WindowCodeStep() caches each entire partition
+** in a temp table before returning any rows. In other cases it does not.
+** This detail is encapsulated within this file, the code generated by
+** select.c is the same in either case.
+**
+** BUILT-IN WINDOW FUNCTIONS
+**
+** This implementation features the following built-in window functions:
+**
+** row_number()
+** rank()
+** dense_rank()
+** percent_rank()
+** cume_dist()
+** ntile(N)
+** lead(expr [, offset [, default]])
+** lag(expr [, offset [, default]])
+** first_value(expr)
+** last_value(expr)
+** nth_value(expr, N)
+**
+** These are the same built-in window functions supported by Postgres.
+** Although the behaviour of aggregate window functions (functions that
+** can be used as either aggregates or window funtions) allows them to
+** be implemented using an API, built-in window functions are much more
+** esoteric. Additionally, some window functions (e.g. nth_value())
+** may only be implemented by caching the entire partition in memory.
+** As such, some built-in window functions use the same API as aggregate
+** window functions and some are implemented directly using VDBE
+** instructions. Additionally, for those functions that use the API, the
+** window frame is sometimes modified before the SELECT statement is
+** rewritten. For example, regardless of the specified window frame, the
+** row_number() function always uses:
+**
+** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+**
+** See tdsqlite3WindowUpdate() for details.
+**
+** As well as some of the built-in window functions, aggregate window
+** functions min() and max() are implemented using VDBE instructions if
+** the start of the window frame is declared as anything other than
+** UNBOUNDED PRECEDING.
+*/
+
+/*
+** Implementation of built-in window function row_number(). Assumes that the
+** window frame has been coerced to:
+**
+** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+*/
+static void row_numberStepFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ i64 *p = (i64*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ) (*p)++;
+ UNUSED_PARAMETER(nArg);
+ UNUSED_PARAMETER(apArg);
+}
+static void row_numberValueFunc(tdsqlite3_context *pCtx){
+ i64 *p = (i64*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ tdsqlite3_result_int64(pCtx, (p ? *p : 0));
+}
+
+/*
+** Context object type used by rank(), dense_rank(), percent_rank() and
+** cume_dist().
+*/
+struct CallCount {
+ i64 nValue;
+ i64 nStep;
+ i64 nTotal;
+};
+
+/*
+** Implementation of built-in window function dense_rank(). Assumes that
+** the window frame has been set to:
+**
+** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+*/
+static void dense_rankStepFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct CallCount *p;
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ) p->nStep = 1;
+ UNUSED_PARAMETER(nArg);
+ UNUSED_PARAMETER(apArg);
+}
+static void dense_rankValueFunc(tdsqlite3_context *pCtx){
+ struct CallCount *p;
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ){
+ if( p->nStep ){
+ p->nValue++;
+ p->nStep = 0;
+ }
+ tdsqlite3_result_int64(pCtx, p->nValue);
+ }
+}
+
+/*
+** Implementation of built-in window function nth_value(). This
+** implementation is used in "slow mode" only - when the EXCLUDE clause
+** is not set to the default value "NO OTHERS".
+*/
+struct NthValueCtx {
+ i64 nStep;
+ tdsqlite3_value *pValue;
+};
+static void nth_valueStepFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct NthValueCtx *p;
+ p = (struct NthValueCtx*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ){
+ i64 iVal;
+ switch( tdsqlite3_value_numeric_type(apArg[1]) ){
+ case SQLITE_INTEGER:
+ iVal = tdsqlite3_value_int64(apArg[1]);
+ break;
+ case SQLITE_FLOAT: {
+ double fVal = tdsqlite3_value_double(apArg[1]);
+ if( ((i64)fVal)!=fVal ) goto error_out;
+ iVal = (i64)fVal;
+ break;
+ }
+ default:
+ goto error_out;
+ }
+ if( iVal<=0 ) goto error_out;
+
+ p->nStep++;
+ if( iVal==p->nStep ){
+ p->pValue = tdsqlite3_value_dup(apArg[0]);
+ if( !p->pValue ){
+ tdsqlite3_result_error_nomem(pCtx);
+ }
+ }
+ }
+ UNUSED_PARAMETER(nArg);
+ UNUSED_PARAMETER(apArg);
+ return;
+
+ error_out:
+ tdsqlite3_result_error(
+ pCtx, "second argument to nth_value must be a positive integer", -1
+ );
+}
+static void nth_valueFinalizeFunc(tdsqlite3_context *pCtx){
+ struct NthValueCtx *p;
+ p = (struct NthValueCtx*)tdsqlite3_aggregate_context(pCtx, 0);
+ if( p && p->pValue ){
+ tdsqlite3_result_value(pCtx, p->pValue);
+ tdsqlite3_value_free(p->pValue);
+ p->pValue = 0;
+ }
+}
+#define nth_valueInvFunc noopStepFunc
+#define nth_valueValueFunc noopValueFunc
+
+static void first_valueStepFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct NthValueCtx *p;
+ p = (struct NthValueCtx*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p && p->pValue==0 ){
+ p->pValue = tdsqlite3_value_dup(apArg[0]);
+ if( !p->pValue ){
+ tdsqlite3_result_error_nomem(pCtx);
+ }
+ }
+ UNUSED_PARAMETER(nArg);
+ UNUSED_PARAMETER(apArg);
+}
+static void first_valueFinalizeFunc(tdsqlite3_context *pCtx){
+ struct NthValueCtx *p;
+ p = (struct NthValueCtx*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p && p->pValue ){
+ tdsqlite3_result_value(pCtx, p->pValue);
+ tdsqlite3_value_free(p->pValue);
+ p->pValue = 0;
+ }
+}
+#define first_valueInvFunc noopStepFunc
+#define first_valueValueFunc noopValueFunc
+
+/*
+** Implementation of built-in window function rank(). Assumes that
+** the window frame has been set to:
+**
+** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+*/
+static void rankStepFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct CallCount *p;
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ){
+ p->nStep++;
+ if( p->nValue==0 ){
+ p->nValue = p->nStep;
+ }
+ }
+ UNUSED_PARAMETER(nArg);
+ UNUSED_PARAMETER(apArg);
+}
+static void rankValueFunc(tdsqlite3_context *pCtx){
+ struct CallCount *p;
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ){
+ tdsqlite3_result_int64(pCtx, p->nValue);
+ p->nValue = 0;
+ }
+}
+
+/*
+** Implementation of built-in window function percent_rank(). Assumes that
+** the window frame has been set to:
+**
+** GROUPS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
+*/
+static void percent_rankStepFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct CallCount *p;
+ UNUSED_PARAMETER(nArg); assert( nArg==0 );
+ UNUSED_PARAMETER(apArg);
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ){
+ p->nTotal++;
+ }
+}
+static void percent_rankInvFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct CallCount *p;
+ UNUSED_PARAMETER(nArg); assert( nArg==0 );
+ UNUSED_PARAMETER(apArg);
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ p->nStep++;
+}
+static void percent_rankValueFunc(tdsqlite3_context *pCtx){
+ struct CallCount *p;
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ){
+ p->nValue = p->nStep;
+ if( p->nTotal>1 ){
+ double r = (double)p->nValue / (double)(p->nTotal-1);
+ tdsqlite3_result_double(pCtx, r);
+ }else{
+ tdsqlite3_result_double(pCtx, 0.0);
+ }
+ }
+}
+#define percent_rankFinalizeFunc percent_rankValueFunc
+
+/*
+** Implementation of built-in window function cume_dist(). Assumes that
+** the window frame has been set to:
+**
+** GROUPS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING
+*/
+static void cume_distStepFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct CallCount *p;
+ UNUSED_PARAMETER(nArg); assert( nArg==0 );
+ UNUSED_PARAMETER(apArg);
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ){
+ p->nTotal++;
+ }
+}
+static void cume_distInvFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct CallCount *p;
+ UNUSED_PARAMETER(nArg); assert( nArg==0 );
+ UNUSED_PARAMETER(apArg);
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ p->nStep++;
+}
+static void cume_distValueFunc(tdsqlite3_context *pCtx){
+ struct CallCount *p;
+ p = (struct CallCount*)tdsqlite3_aggregate_context(pCtx, 0);
+ if( p ){
+ double r = (double)(p->nStep) / (double)(p->nTotal);
+ tdsqlite3_result_double(pCtx, r);
+ }
+}
+#define cume_distFinalizeFunc cume_distValueFunc
+
+/*
+** Context object for ntile() window function.
+*/
+struct NtileCtx {
+ i64 nTotal; /* Total rows in partition */
+ i64 nParam; /* Parameter passed to ntile(N) */
+ i64 iRow; /* Current row */
+};
+
+/*
+** Implementation of ntile(). This assumes that the window frame has
+** been coerced to:
+**
+** ROWS CURRENT ROW AND UNBOUNDED FOLLOWING
+*/
+static void ntileStepFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct NtileCtx *p;
+ assert( nArg==1 ); UNUSED_PARAMETER(nArg);
+ p = (struct NtileCtx*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ){
+ if( p->nTotal==0 ){
+ p->nParam = tdsqlite3_value_int64(apArg[0]);
+ if( p->nParam<=0 ){
+ tdsqlite3_result_error(
+ pCtx, "argument of ntile must be a positive integer", -1
+ );
+ }
+ }
+ p->nTotal++;
+ }
+}
+static void ntileInvFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct NtileCtx *p;
+ assert( nArg==1 ); UNUSED_PARAMETER(nArg);
+ UNUSED_PARAMETER(apArg);
+ p = (struct NtileCtx*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ p->iRow++;
+}
+static void ntileValueFunc(tdsqlite3_context *pCtx){
+ struct NtileCtx *p;
+ p = (struct NtileCtx*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p && p->nParam>0 ){
+ int nSize = (p->nTotal / p->nParam);
+ if( nSize==0 ){
+ tdsqlite3_result_int64(pCtx, p->iRow+1);
+ }else{
+ i64 nLarge = p->nTotal - p->nParam*nSize;
+ i64 iSmall = nLarge*(nSize+1);
+ i64 iRow = p->iRow;
+
+ assert( (nLarge*(nSize+1) + (p->nParam-nLarge)*nSize)==p->nTotal );
+
+ if( iRow<iSmall ){
+ tdsqlite3_result_int64(pCtx, 1 + iRow/(nSize+1));
+ }else{
+ tdsqlite3_result_int64(pCtx, 1 + nLarge + (iRow-iSmall)/nSize);
+ }
+ }
+ }
+}
+#define ntileFinalizeFunc ntileValueFunc
+
+/*
+** Context object for last_value() window function.
+*/
+struct LastValueCtx {
+ tdsqlite3_value *pVal;
+ int nVal;
+};
+
+/*
+** Implementation of last_value().
+*/
+static void last_valueStepFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct LastValueCtx *p;
+ UNUSED_PARAMETER(nArg);
+ p = (struct LastValueCtx*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p ){
+ tdsqlite3_value_free(p->pVal);
+ p->pVal = tdsqlite3_value_dup(apArg[0]);
+ if( p->pVal==0 ){
+ tdsqlite3_result_error_nomem(pCtx);
+ }else{
+ p->nVal++;
+ }
+ }
+}
+static void last_valueInvFunc(
+ tdsqlite3_context *pCtx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ struct LastValueCtx *p;
+ UNUSED_PARAMETER(nArg);
+ UNUSED_PARAMETER(apArg);
+ p = (struct LastValueCtx*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( ALWAYS(p) ){
+ p->nVal--;
+ if( p->nVal==0 ){
+ tdsqlite3_value_free(p->pVal);
+ p->pVal = 0;
+ }
+ }
+}
+static void last_valueValueFunc(tdsqlite3_context *pCtx){
+ struct LastValueCtx *p;
+ p = (struct LastValueCtx*)tdsqlite3_aggregate_context(pCtx, 0);
+ if( p && p->pVal ){
+ tdsqlite3_result_value(pCtx, p->pVal);
+ }
+}
+static void last_valueFinalizeFunc(tdsqlite3_context *pCtx){
+ struct LastValueCtx *p;
+ p = (struct LastValueCtx*)tdsqlite3_aggregate_context(pCtx, sizeof(*p));
+ if( p && p->pVal ){
+ tdsqlite3_result_value(pCtx, p->pVal);
+ tdsqlite3_value_free(p->pVal);
+ p->pVal = 0;
+ }
+}
+
+/*
+** Static names for the built-in window function names. These static
+** names are used, rather than string literals, so that FuncDef objects
+** can be associated with a particular window function by direct
+** comparison of the zName pointer. Example:
+**
+** if( pFuncDef->zName==row_valueName ){ ... }
+*/
+static const char row_numberName[] = "row_number";
+static const char dense_rankName[] = "dense_rank";
+static const char rankName[] = "rank";
+static const char percent_rankName[] = "percent_rank";
+static const char cume_distName[] = "cume_dist";
+static const char ntileName[] = "ntile";
+static const char last_valueName[] = "last_value";
+static const char nth_valueName[] = "nth_value";
+static const char first_valueName[] = "first_value";
+static const char leadName[] = "lead";
+static const char lagName[] = "lag";
+
+/*
+** No-op implementations of xStep() and xFinalize(). Used as place-holders
+** for built-in window functions that never call those interfaces.
+**
+** The noopValueFunc() is called but is expected to do nothing. The
+** noopStepFunc() is never called, and so it is marked with NO_TEST to
+** let the test coverage routine know not to expect this function to be
+** invoked.
+*/
+static void noopStepFunc( /*NO_TEST*/
+ tdsqlite3_context *p, /*NO_TEST*/
+ int n, /*NO_TEST*/
+ tdsqlite3_value **a /*NO_TEST*/
+){ /*NO_TEST*/
+ UNUSED_PARAMETER(p); /*NO_TEST*/
+ UNUSED_PARAMETER(n); /*NO_TEST*/
+ UNUSED_PARAMETER(a); /*NO_TEST*/
+ assert(0); /*NO_TEST*/
+} /*NO_TEST*/
+static void noopValueFunc(tdsqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ }
+
+/* Window functions that use all window interfaces: xStep, xFinal,
+** xValue, and xInverse */
+#define WINDOWFUNCALL(name,nArg,extra) { \
+ nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \
+ name ## StepFunc, name ## FinalizeFunc, name ## ValueFunc, \
+ name ## InvFunc, name ## Name, {0} \
+}
+
+/* Window functions that are implemented using bytecode and thus have
+** no-op routines for their methods */
+#define WINDOWFUNCNOOP(name,nArg,extra) { \
+ nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \
+ noopStepFunc, noopValueFunc, noopValueFunc, \
+ noopStepFunc, name ## Name, {0} \
+}
+
+/* Window functions that use all window interfaces: xStep, the
+** same routine for xFinalize and xValue and which never call
+** xInverse. */
+#define WINDOWFUNCX(name,nArg,extra) { \
+ nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \
+ name ## StepFunc, name ## ValueFunc, name ## ValueFunc, \
+ noopStepFunc, name ## Name, {0} \
+}
+
+
+/*
+** Register those built-in window functions that are not also aggregates.
+*/
+SQLITE_PRIVATE void tdsqlite3WindowFunctions(void){
+ static FuncDef aWindowFuncs[] = {
+ WINDOWFUNCX(row_number, 0, 0),
+ WINDOWFUNCX(dense_rank, 0, 0),
+ WINDOWFUNCX(rank, 0, 0),
+ WINDOWFUNCALL(percent_rank, 0, 0),
+ WINDOWFUNCALL(cume_dist, 0, 0),
+ WINDOWFUNCALL(ntile, 1, 0),
+ WINDOWFUNCALL(last_value, 1, 0),
+ WINDOWFUNCALL(nth_value, 2, 0),
+ WINDOWFUNCALL(first_value, 1, 0),
+ WINDOWFUNCNOOP(lead, 1, 0),
+ WINDOWFUNCNOOP(lead, 2, 0),
+ WINDOWFUNCNOOP(lead, 3, 0),
+ WINDOWFUNCNOOP(lag, 1, 0),
+ WINDOWFUNCNOOP(lag, 2, 0),
+ WINDOWFUNCNOOP(lag, 3, 0),
+ };
+ tdsqlite3InsertBuiltinFuncs(aWindowFuncs, ArraySize(aWindowFuncs));
+}
+
+static Window *windowFind(Parse *pParse, Window *pList, const char *zName){
+ Window *p;
+ for(p=pList; p; p=p->pNextWin){
+ if( tdsqlite3StrICmp(p->zName, zName)==0 ) break;
+ }
+ if( p==0 ){
+ tdsqlite3ErrorMsg(pParse, "no such window: %s", zName);
+ }
+ return p;
+}
+
+/*
+** This function is called immediately after resolving the function name
+** for a window function within a SELECT statement. Argument pList is a
+** linked list of WINDOW definitions for the current SELECT statement.
+** Argument pFunc is the function definition just resolved and pWin
+** is the Window object representing the associated OVER clause. This
+** function updates the contents of pWin as follows:
+**
+** * If the OVER clause refered to a named window (as in "max(x) OVER win"),
+** search list pList for a matching WINDOW definition, and update pWin
+** accordingly. If no such WINDOW clause can be found, leave an error
+** in pParse.
+**
+** * If the function is a built-in window function that requires the
+** window to be coerced (see "BUILT-IN WINDOW FUNCTIONS" at the top
+** of this file), pWin is updated here.
+*/
+SQLITE_PRIVATE void tdsqlite3WindowUpdate(
+ Parse *pParse,
+ Window *pList, /* List of named windows for this SELECT */
+ Window *pWin, /* Window frame to update */
+ FuncDef *pFunc /* Window function definition */
+){
+ if( pWin->zName && pWin->eFrmType==0 ){
+ Window *p = windowFind(pParse, pList, pWin->zName);
+ if( p==0 ) return;
+ pWin->pPartition = tdsqlite3ExprListDup(pParse->db, p->pPartition, 0);
+ pWin->pOrderBy = tdsqlite3ExprListDup(pParse->db, p->pOrderBy, 0);
+ pWin->pStart = tdsqlite3ExprDup(pParse->db, p->pStart, 0);
+ pWin->pEnd = tdsqlite3ExprDup(pParse->db, p->pEnd, 0);
+ pWin->eStart = p->eStart;
+ pWin->eEnd = p->eEnd;
+ pWin->eFrmType = p->eFrmType;
+ pWin->eExclude = p->eExclude;
+ }else{
+ tdsqlite3WindowChain(pParse, pWin, pList);
+ }
+ if( (pWin->eFrmType==TK_RANGE)
+ && (pWin->pStart || pWin->pEnd)
+ && (pWin->pOrderBy==0 || pWin->pOrderBy->nExpr!=1)
+ ){
+ tdsqlite3ErrorMsg(pParse,
+ "RANGE with offset PRECEDING/FOLLOWING requires one ORDER BY expression"
+ );
+ }else
+ if( pFunc->funcFlags & SQLITE_FUNC_WINDOW ){
+ tdsqlite3 *db = pParse->db;
+ if( pWin->pFilter ){
+ tdsqlite3ErrorMsg(pParse,
+ "FILTER clause may only be used with aggregate window functions"
+ );
+ }else{
+ struct WindowUpdate {
+ const char *zFunc;
+ int eFrmType;
+ int eStart;
+ int eEnd;
+ } aUp[] = {
+ { row_numberName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT },
+ { dense_rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT },
+ { rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT },
+ { percent_rankName, TK_GROUPS, TK_CURRENT, TK_UNBOUNDED },
+ { cume_distName, TK_GROUPS, TK_FOLLOWING, TK_UNBOUNDED },
+ { ntileName, TK_ROWS, TK_CURRENT, TK_UNBOUNDED },
+ { leadName, TK_ROWS, TK_UNBOUNDED, TK_UNBOUNDED },
+ { lagName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT },
+ };
+ int i;
+ for(i=0; i<ArraySize(aUp); i++){
+ if( pFunc->zName==aUp[i].zFunc ){
+ tdsqlite3ExprDelete(db, pWin->pStart);
+ tdsqlite3ExprDelete(db, pWin->pEnd);
+ pWin->pEnd = pWin->pStart = 0;
+ pWin->eFrmType = aUp[i].eFrmType;
+ pWin->eStart = aUp[i].eStart;
+ pWin->eEnd = aUp[i].eEnd;
+ pWin->eExclude = 0;
+ if( pWin->eStart==TK_FOLLOWING ){
+ pWin->pStart = tdsqlite3Expr(db, TK_INTEGER, "1");
+ }
+ break;
+ }
+ }
+ }
+ }
+ pWin->pFunc = pFunc;
+}
+
+/*
+** Context object passed through tdsqlite3WalkExprList() to
+** selectWindowRewriteExprCb() by selectWindowRewriteEList().
+*/
+typedef struct WindowRewrite WindowRewrite;
+struct WindowRewrite {
+ Window *pWin;
+ SrcList *pSrc;
+ ExprList *pSub;
+ Table *pTab;
+ Select *pSubSelect; /* Current sub-select, if any */
+};
+
+/*
+** Callback function used by selectWindowRewriteEList(). If necessary,
+** this function appends to the output expression-list and updates
+** expression (*ppExpr) in place.
+*/
+static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){
+ struct WindowRewrite *p = pWalker->u.pRewrite;
+ Parse *pParse = pWalker->pParse;
+ assert( p!=0 );
+ assert( p->pWin!=0 );
+
+ /* If this function is being called from within a scalar sub-select
+ ** that used by the SELECT statement being processed, only process
+ ** TK_COLUMN expressions that refer to it (the outer SELECT). Do
+ ** not process aggregates or window functions at all, as they belong
+ ** to the scalar sub-select. */
+ if( p->pSubSelect ){
+ if( pExpr->op!=TK_COLUMN ){
+ return WRC_Continue;
+ }else{
+ int nSrc = p->pSrc->nSrc;
+ int i;
+ for(i=0; i<nSrc; i++){
+ if( pExpr->iTable==p->pSrc->a[i].iCursor ) break;
+ }
+ if( i==nSrc ) return WRC_Continue;
+ }
+ }
+
+ switch( pExpr->op ){
+
+ case TK_FUNCTION:
+ if( !ExprHasProperty(pExpr, EP_WinFunc) ){
+ break;
+ }else{
+ Window *pWin;
+ for(pWin=p->pWin; pWin; pWin=pWin->pNextWin){
+ if( pExpr->y.pWin==pWin ){
+ assert( pWin->pOwner==pExpr );
+ return WRC_Prune;
+ }
+ }
+ }
+ /* Fall through. */
+
+ case TK_AGG_FUNCTION:
+ case TK_COLUMN: {
+ int iCol = -1;
+ if( p->pSub ){
+ int i;
+ for(i=0; i<p->pSub->nExpr; i++){
+ if( 0==tdsqlite3ExprCompare(0, p->pSub->a[i].pExpr, pExpr, -1) ){
+ iCol = i;
+ break;
+ }
+ }
+ }
+ if( iCol<0 ){
+ Expr *pDup = tdsqlite3ExprDup(pParse->db, pExpr, 0);
+ if( pDup && pDup->op==TK_AGG_FUNCTION ) pDup->op = TK_FUNCTION;
+ p->pSub = tdsqlite3ExprListAppend(pParse, p->pSub, pDup);
+ }
+ if( p->pSub ){
+ assert( ExprHasProperty(pExpr, EP_Static)==0 );
+ ExprSetProperty(pExpr, EP_Static);
+ tdsqlite3ExprDelete(pParse->db, pExpr);
+ ExprClearProperty(pExpr, EP_Static);
+ memset(pExpr, 0, sizeof(Expr));
+
+ pExpr->op = TK_COLUMN;
+ pExpr->iColumn = (iCol<0 ? p->pSub->nExpr-1: iCol);
+ pExpr->iTable = p->pWin->iEphCsr;
+ pExpr->y.pTab = p->pTab;
+ }
+ if( pParse->db->mallocFailed ) return WRC_Abort;
+ break;
+ }
+
+ default: /* no-op */
+ break;
+ }
+
+ return WRC_Continue;
+}
+static int selectWindowRewriteSelectCb(Walker *pWalker, Select *pSelect){
+ struct WindowRewrite *p = pWalker->u.pRewrite;
+ Select *pSave = p->pSubSelect;
+ if( pSave==pSelect ){
+ return WRC_Continue;
+ }else{
+ p->pSubSelect = pSelect;
+ tdsqlite3WalkSelect(pWalker, pSelect);
+ p->pSubSelect = pSave;
+ }
+ return WRC_Prune;
+}
+
+
+/*
+** Iterate through each expression in expression-list pEList. For each:
+**
+** * TK_COLUMN,
+** * aggregate function, or
+** * window function with a Window object that is not a member of the
+** Window list passed as the second argument (pWin).
+**
+** Append the node to output expression-list (*ppSub). And replace it
+** with a TK_COLUMN that reads the (N-1)th element of table
+** pWin->iEphCsr, where N is the number of elements in (*ppSub) after
+** appending the new one.
+*/
+static void selectWindowRewriteEList(
+ Parse *pParse,
+ Window *pWin,
+ SrcList *pSrc,
+ ExprList *pEList, /* Rewrite expressions in this list */
+ Table *pTab,
+ ExprList **ppSub /* IN/OUT: Sub-select expression-list */
+){
+ Walker sWalker;
+ WindowRewrite sRewrite;
+
+ assert( pWin!=0 );
+ memset(&sWalker, 0, sizeof(Walker));
+ memset(&sRewrite, 0, sizeof(WindowRewrite));
+
+ sRewrite.pSub = *ppSub;
+ sRewrite.pWin = pWin;
+ sRewrite.pSrc = pSrc;
+ sRewrite.pTab = pTab;
+
+ sWalker.pParse = pParse;
+ sWalker.xExprCallback = selectWindowRewriteExprCb;
+ sWalker.xSelectCallback = selectWindowRewriteSelectCb;
+ sWalker.u.pRewrite = &sRewrite;
+
+ (void)tdsqlite3WalkExprList(&sWalker, pEList);
+
+ *ppSub = sRewrite.pSub;
+}
+
+/*
+** Append a copy of each expression in expression-list pAppend to
+** expression list pList. Return a pointer to the result list.
+*/
+static ExprList *exprListAppendList(
+ Parse *pParse, /* Parsing context */
+ ExprList *pList, /* List to which to append. Might be NULL */
+ ExprList *pAppend, /* List of values to append. Might be NULL */
+ int bIntToNull
+){
+ if( pAppend ){
+ int i;
+ int nInit = pList ? pList->nExpr : 0;
+ for(i=0; i<pAppend->nExpr; i++){
+ int iDummy;
+ Expr *pDup = tdsqlite3ExprDup(pParse->db, pAppend->a[i].pExpr, 0);
+ assert( pDup==0 || !ExprHasProperty(pDup, EP_MemToken) );
+ if( bIntToNull && pDup && tdsqlite3ExprIsInteger(pDup, &iDummy) ){
+ pDup->op = TK_NULL;
+ pDup->flags &= ~(EP_IntValue|EP_IsTrue|EP_IsFalse);
+ pDup->u.zToken = 0;
+ }
+ pList = tdsqlite3ExprListAppend(pParse, pList, pDup);
+ if( pList ) pList->a[nInit+i].sortFlags = pAppend->a[i].sortFlags;
+ }
+ }
+ return pList;
+}
+
+/*
+** If the SELECT statement passed as the second argument does not invoke
+** any SQL window functions, this function is a no-op. Otherwise, it
+** rewrites the SELECT statement so that window function xStep functions
+** are invoked in the correct order as described under "SELECT REWRITING"
+** at the top of this file.
+*/
+SQLITE_PRIVATE int tdsqlite3WindowRewrite(Parse *pParse, Select *p){
+ int rc = SQLITE_OK;
+ if( p->pWin && p->pPrior==0 && (p->selFlags & SF_WinRewrite)==0 ){
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ tdsqlite3 *db = pParse->db;
+ Select *pSub = 0; /* The subquery */
+ SrcList *pSrc = p->pSrc;
+ Expr *pWhere = p->pWhere;
+ ExprList *pGroupBy = p->pGroupBy;
+ Expr *pHaving = p->pHaving;
+ ExprList *pSort = 0;
+
+ ExprList *pSublist = 0; /* Expression list for sub-query */
+ Window *pMWin = p->pWin; /* Master window object */
+ Window *pWin; /* Window object iterator */
+ Table *pTab;
+
+ pTab = tdsqlite3DbMallocZero(db, sizeof(Table));
+ if( pTab==0 ){
+ return tdsqlite3ErrorToParser(db, SQLITE_NOMEM);
+ }
+
+ p->pSrc = 0;
+ p->pWhere = 0;
+ p->pGroupBy = 0;
+ p->pHaving = 0;
+ p->selFlags &= ~SF_Aggregate;
+ p->selFlags |= SF_WinRewrite;
+
+ /* Create the ORDER BY clause for the sub-select. This is the concatenation
+ ** of the window PARTITION and ORDER BY clauses. Then, if this makes it
+ ** redundant, remove the ORDER BY from the parent SELECT. */
+ pSort = exprListAppendList(pParse, 0, pMWin->pPartition, 1);
+ pSort = exprListAppendList(pParse, pSort, pMWin->pOrderBy, 1);
+ if( pSort && p->pOrderBy && p->pOrderBy->nExpr<=pSort->nExpr ){
+ int nSave = pSort->nExpr;
+ pSort->nExpr = p->pOrderBy->nExpr;
+ if( tdsqlite3ExprListCompare(pSort, p->pOrderBy, -1)==0 ){
+ tdsqlite3ExprListDelete(db, p->pOrderBy);
+ p->pOrderBy = 0;
+ }
+ pSort->nExpr = nSave;
+ }
+
+ /* Assign a cursor number for the ephemeral table used to buffer rows.
+ ** The OpenEphemeral instruction is coded later, after it is known how
+ ** many columns the table will have. */
+ pMWin->iEphCsr = pParse->nTab++;
+ pParse->nTab += 3;
+
+ selectWindowRewriteEList(pParse, pMWin, pSrc, p->pEList, pTab, &pSublist);
+ selectWindowRewriteEList(pParse, pMWin, pSrc, p->pOrderBy, pTab, &pSublist);
+ pMWin->nBufferCol = (pSublist ? pSublist->nExpr : 0);
+
+ /* Append the PARTITION BY and ORDER BY expressions to the to the
+ ** sub-select expression list. They are required to figure out where
+ ** boundaries for partitions and sets of peer rows lie. */
+ pSublist = exprListAppendList(pParse, pSublist, pMWin->pPartition, 0);
+ pSublist = exprListAppendList(pParse, pSublist, pMWin->pOrderBy, 0);
+
+ /* Append the arguments passed to each window function to the
+ ** sub-select expression list. Also allocate two registers for each
+ ** window function - one for the accumulator, another for interim
+ ** results. */
+ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
+ ExprList *pArgs = pWin->pOwner->x.pList;
+ if( pWin->pFunc->funcFlags & SQLITE_FUNC_SUBTYPE ){
+ selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist);
+ pWin->iArgCol = (pSublist ? pSublist->nExpr : 0);
+ pWin->bExprArgs = 1;
+ }else{
+ pWin->iArgCol = (pSublist ? pSublist->nExpr : 0);
+ pSublist = exprListAppendList(pParse, pSublist, pArgs, 0);
+ }
+ if( pWin->pFilter ){
+ Expr *pFilter = tdsqlite3ExprDup(db, pWin->pFilter, 0);
+ pSublist = tdsqlite3ExprListAppend(pParse, pSublist, pFilter);
+ }
+ pWin->regAccum = ++pParse->nMem;
+ pWin->regResult = ++pParse->nMem;
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum);
+ }
+
+ /* If there is no ORDER BY or PARTITION BY clause, and the window
+ ** function accepts zero arguments, and there are no other columns
+ ** selected (e.g. "SELECT row_number() OVER () FROM t1"), it is possible
+ ** that pSublist is still NULL here. Add a constant expression here to
+ ** keep everything legal in this case.
+ */
+ if( pSublist==0 ){
+ pSublist = tdsqlite3ExprListAppend(pParse, 0,
+ tdsqlite3Expr(db, TK_INTEGER, "0")
+ );
+ }
+
+ pSub = tdsqlite3SelectNew(
+ pParse, pSublist, pSrc, pWhere, pGroupBy, pHaving, pSort, 0, 0
+ );
+ p->pSrc = tdsqlite3SrcListAppend(pParse, 0, 0, 0);
+ if( p->pSrc ){
+ Table *pTab2;
+ p->pSrc->a[0].pSelect = pSub;
+ tdsqlite3SrcListAssignCursors(pParse, p->pSrc);
+ pSub->selFlags |= SF_Expanded;
+ pTab2 = tdsqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE);
+ if( pTab2==0 ){
+ /* Might actually be some other kind of error, but in that case
+ ** pParse->nErr will be set, so if SQLITE_NOMEM is set, we will get
+ ** the correct error message regardless. */
+ rc = SQLITE_NOMEM;
+ }else{
+ memcpy(pTab, pTab2, sizeof(Table));
+ pTab->tabFlags |= TF_Ephemeral;
+ p->pSrc->a[0].pTab = pTab;
+ pTab = pTab2;
+ }
+ }else{
+ tdsqlite3SelectDelete(db, pSub);
+ }
+ if( db->mallocFailed ) rc = SQLITE_NOMEM;
+ tdsqlite3DbFree(db, pTab);
+ }
+
+ if( rc ){
+ if( pParse->nErr==0 ){
+ assert( pParse->db->mallocFailed );
+ tdsqlite3ErrorToParser(pParse->db, SQLITE_NOMEM);
+ }
+ tdsqlite3SelectReset(pParse, p);
+ }
+ return rc;
+}
+
+/*
+** Unlink the Window object from the Select to which it is attached,
+** if it is attached.
+*/
+SQLITE_PRIVATE void tdsqlite3WindowUnlinkFromSelect(Window *p){
+ if( p->ppThis ){
+ *p->ppThis = p->pNextWin;
+ if( p->pNextWin ) p->pNextWin->ppThis = p->ppThis;
+ p->ppThis = 0;
+ }
+}
+
+/*
+** Free the Window object passed as the second argument.
+*/
+SQLITE_PRIVATE void tdsqlite3WindowDelete(tdsqlite3 *db, Window *p){
+ if( p ){
+ tdsqlite3WindowUnlinkFromSelect(p);
+ tdsqlite3ExprDelete(db, p->pFilter);
+ tdsqlite3ExprListDelete(db, p->pPartition);
+ tdsqlite3ExprListDelete(db, p->pOrderBy);
+ tdsqlite3ExprDelete(db, p->pEnd);
+ tdsqlite3ExprDelete(db, p->pStart);
+ tdsqlite3DbFree(db, p->zName);
+ tdsqlite3DbFree(db, p->zBase);
+ tdsqlite3DbFree(db, p);
+ }
+}
+
+/*
+** Free the linked list of Window objects starting at the second argument.
+*/
+SQLITE_PRIVATE void tdsqlite3WindowListDelete(tdsqlite3 *db, Window *p){
+ while( p ){
+ Window *pNext = p->pNextWin;
+ tdsqlite3WindowDelete(db, p);
+ p = pNext;
+ }
+}
+
+/*
+** The argument expression is an PRECEDING or FOLLOWING offset. The
+** value should be a non-negative integer. If the value is not a
+** constant, change it to NULL. The fact that it is then a non-negative
+** integer will be caught later. But it is important not to leave
+** variable values in the expression tree.
+*/
+static Expr *tdsqlite3WindowOffsetExpr(Parse *pParse, Expr *pExpr){
+ if( 0==tdsqlite3ExprIsConstant(pExpr) ){
+ if( IN_RENAME_OBJECT ) tdsqlite3RenameExprUnmap(pParse, pExpr);
+ tdsqlite3ExprDelete(pParse->db, pExpr);
+ pExpr = tdsqlite3ExprAlloc(pParse->db, TK_NULL, 0, 0);
+ }
+ return pExpr;
+}
+
+/*
+** Allocate and return a new Window object describing a Window Definition.
+*/
+SQLITE_PRIVATE Window *tdsqlite3WindowAlloc(
+ Parse *pParse, /* Parsing context */
+ int eType, /* Frame type. TK_RANGE, TK_ROWS, TK_GROUPS, or 0 */
+ int eStart, /* Start type: CURRENT, PRECEDING, FOLLOWING, UNBOUNDED */
+ Expr *pStart, /* Start window size if TK_PRECEDING or FOLLOWING */
+ int eEnd, /* End type: CURRENT, FOLLOWING, TK_UNBOUNDED, PRECEDING */
+ Expr *pEnd, /* End window size if TK_FOLLOWING or PRECEDING */
+ u8 eExclude /* EXCLUDE clause */
+){
+ Window *pWin = 0;
+ int bImplicitFrame = 0;
+
+ /* Parser assures the following: */
+ assert( eType==0 || eType==TK_RANGE || eType==TK_ROWS || eType==TK_GROUPS );
+ assert( eStart==TK_CURRENT || eStart==TK_PRECEDING
+ || eStart==TK_UNBOUNDED || eStart==TK_FOLLOWING );
+ assert( eEnd==TK_CURRENT || eEnd==TK_FOLLOWING
+ || eEnd==TK_UNBOUNDED || eEnd==TK_PRECEDING );
+ assert( (eStart==TK_PRECEDING || eStart==TK_FOLLOWING)==(pStart!=0) );
+ assert( (eEnd==TK_FOLLOWING || eEnd==TK_PRECEDING)==(pEnd!=0) );
+
+ if( eType==0 ){
+ bImplicitFrame = 1;
+ eType = TK_RANGE;
+ }
+
+ /* Additionally, the
+ ** starting boundary type may not occur earlier in the following list than
+ ** the ending boundary type:
+ **
+ ** UNBOUNDED PRECEDING
+ ** <expr> PRECEDING
+ ** CURRENT ROW
+ ** <expr> FOLLOWING
+ ** UNBOUNDED FOLLOWING
+ **
+ ** The parser ensures that "UNBOUNDED PRECEDING" cannot be used as an ending
+ ** boundary, and than "UNBOUNDED FOLLOWING" cannot be used as a starting
+ ** frame boundary.
+ */
+ if( (eStart==TK_CURRENT && eEnd==TK_PRECEDING)
+ || (eStart==TK_FOLLOWING && (eEnd==TK_PRECEDING || eEnd==TK_CURRENT))
+ ){
+ tdsqlite3ErrorMsg(pParse, "unsupported frame specification");
+ goto windowAllocErr;
+ }
+
+ pWin = (Window*)tdsqlite3DbMallocZero(pParse->db, sizeof(Window));
+ if( pWin==0 ) goto windowAllocErr;
+ pWin->eFrmType = eType;
+ pWin->eStart = eStart;
+ pWin->eEnd = eEnd;
+ if( eExclude==0 && OptimizationDisabled(pParse->db, SQLITE_WindowFunc) ){
+ eExclude = TK_NO;
+ }
+ pWin->eExclude = eExclude;
+ pWin->bImplicitFrame = bImplicitFrame;
+ pWin->pEnd = tdsqlite3WindowOffsetExpr(pParse, pEnd);
+ pWin->pStart = tdsqlite3WindowOffsetExpr(pParse, pStart);
+ return pWin;
+
+windowAllocErr:
+ tdsqlite3ExprDelete(pParse->db, pEnd);
+ tdsqlite3ExprDelete(pParse->db, pStart);
+ return 0;
+}
+
+/*
+** Attach PARTITION and ORDER BY clauses pPartition and pOrderBy to window
+** pWin. Also, if parameter pBase is not NULL, set pWin->zBase to the
+** equivalent nul-terminated string.
+*/
+SQLITE_PRIVATE Window *tdsqlite3WindowAssemble(
+ Parse *pParse,
+ Window *pWin,
+ ExprList *pPartition,
+ ExprList *pOrderBy,
+ Token *pBase
+){
+ if( pWin ){
+ pWin->pPartition = pPartition;
+ pWin->pOrderBy = pOrderBy;
+ if( pBase ){
+ pWin->zBase = tdsqlite3DbStrNDup(pParse->db, pBase->z, pBase->n);
+ }
+ }else{
+ tdsqlite3ExprListDelete(pParse->db, pPartition);
+ tdsqlite3ExprListDelete(pParse->db, pOrderBy);
+ }
+ return pWin;
+}
+
+/*
+** Window *pWin has just been created from a WINDOW clause. Tokne pBase
+** is the base window. Earlier windows from the same WINDOW clause are
+** stored in the linked list starting at pWin->pNextWin. This function
+** either updates *pWin according to the base specification, or else
+** leaves an error in pParse.
+*/
+SQLITE_PRIVATE void tdsqlite3WindowChain(Parse *pParse, Window *pWin, Window *pList){
+ if( pWin->zBase ){
+ tdsqlite3 *db = pParse->db;
+ Window *pExist = windowFind(pParse, pList, pWin->zBase);
+ if( pExist ){
+ const char *zErr = 0;
+ /* Check for errors */
+ if( pWin->pPartition ){
+ zErr = "PARTITION clause";
+ }else if( pExist->pOrderBy && pWin->pOrderBy ){
+ zErr = "ORDER BY clause";
+ }else if( pExist->bImplicitFrame==0 ){
+ zErr = "frame specification";
+ }
+ if( zErr ){
+ tdsqlite3ErrorMsg(pParse,
+ "cannot override %s of window: %s", zErr, pWin->zBase
+ );
+ }else{
+ pWin->pPartition = tdsqlite3ExprListDup(db, pExist->pPartition, 0);
+ if( pExist->pOrderBy ){
+ assert( pWin->pOrderBy==0 );
+ pWin->pOrderBy = tdsqlite3ExprListDup(db, pExist->pOrderBy, 0);
+ }
+ tdsqlite3DbFree(db, pWin->zBase);
+ pWin->zBase = 0;
+ }
+ }
+ }
+}
+
+/*
+** Attach window object pWin to expression p.
+*/
+SQLITE_PRIVATE void tdsqlite3WindowAttach(Parse *pParse, Expr *p, Window *pWin){
+ if( p ){
+ assert( p->op==TK_FUNCTION );
+ assert( pWin );
+ p->y.pWin = pWin;
+ ExprSetProperty(p, EP_WinFunc);
+ pWin->pOwner = p;
+ if( (p->flags & EP_Distinct) && pWin->eFrmType!=TK_FILTER ){
+ tdsqlite3ErrorMsg(pParse,
+ "DISTINCT is not supported for window functions"
+ );
+ }
+ }else{
+ tdsqlite3WindowDelete(pParse->db, pWin);
+ }
+}
+
+/*
+** Possibly link window pWin into the list at pSel->pWin (window functions
+** to be processed as part of SELECT statement pSel). The window is linked
+** in if either (a) there are no other windows already linked to this
+** SELECT, or (b) the windows already linked use a compatible window frame.
+*/
+SQLITE_PRIVATE void tdsqlite3WindowLink(Select *pSel, Window *pWin){
+ if( pSel!=0
+ && (0==pSel->pWin || 0==tdsqlite3WindowCompare(0, pSel->pWin, pWin, 0))
+ ){
+ pWin->pNextWin = pSel->pWin;
+ if( pSel->pWin ){
+ pSel->pWin->ppThis = &pWin->pNextWin;
+ }
+ pSel->pWin = pWin;
+ pWin->ppThis = &pSel->pWin;
+ }
+}
+
+/*
+** Return 0 if the two window objects are identical, 1 if they are
+** different, or 2 if it cannot be determined if the objects are identical
+** or not. Identical window objects can be processed in a single scan.
+*/
+SQLITE_PRIVATE int tdsqlite3WindowCompare(Parse *pParse, Window *p1, Window *p2, int bFilter){
+ int res;
+ if( NEVER(p1==0) || NEVER(p2==0) ) return 1;
+ if( p1->eFrmType!=p2->eFrmType ) return 1;
+ if( p1->eStart!=p2->eStart ) return 1;
+ if( p1->eEnd!=p2->eEnd ) return 1;
+ if( p1->eExclude!=p2->eExclude ) return 1;
+ if( tdsqlite3ExprCompare(pParse, p1->pStart, p2->pStart, -1) ) return 1;
+ if( tdsqlite3ExprCompare(pParse, p1->pEnd, p2->pEnd, -1) ) return 1;
+ if( (res = tdsqlite3ExprListCompare(p1->pPartition, p2->pPartition, -1)) ){
+ return res;
+ }
+ if( (res = tdsqlite3ExprListCompare(p1->pOrderBy, p2->pOrderBy, -1)) ){
+ return res;
+ }
+ if( bFilter ){
+ if( (res = tdsqlite3ExprCompare(pParse, p1->pFilter, p2->pFilter, -1)) ){
+ return res;
+ }
+ }
+ return 0;
+}
+
+
+/*
+** This is called by code in select.c before it calls tdsqlite3WhereBegin()
+** to begin iterating through the sub-query results. It is used to allocate
+** and initialize registers and cursors used by tdsqlite3WindowCodeStep().
+*/
+SQLITE_PRIVATE void tdsqlite3WindowCodeInit(Parse *pParse, Select *pSelect){
+ int nEphExpr = pSelect->pSrc->a[0].pSelect->pEList->nExpr;
+ Window *pMWin = pSelect->pWin;
+ Window *pWin;
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+
+ tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, pMWin->iEphCsr, nEphExpr);
+ tdsqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+1, pMWin->iEphCsr);
+ tdsqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+2, pMWin->iEphCsr);
+ tdsqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+3, pMWin->iEphCsr);
+
+ /* Allocate registers to use for PARTITION BY values, if any. Initialize
+ ** said registers to NULL. */
+ if( pMWin->pPartition ){
+ int nExpr = pMWin->pPartition->nExpr;
+ pMWin->regPart = pParse->nMem+1;
+ pParse->nMem += nExpr;
+ tdsqlite3VdbeAddOp3(v, OP_Null, 0, pMWin->regPart, pMWin->regPart+nExpr-1);
+ }
+
+ pMWin->regOne = ++pParse->nMem;
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, pMWin->regOne);
+
+ if( pMWin->eExclude ){
+ pMWin->regStartRowid = ++pParse->nMem;
+ pMWin->regEndRowid = ++pParse->nMem;
+ pMWin->csrApp = pParse->nTab++;
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, pMWin->regStartRowid);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regEndRowid);
+ tdsqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->csrApp, pMWin->iEphCsr);
+ return;
+ }
+
+ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
+ FuncDef *p = pWin->pFunc;
+ if( (p->funcFlags & SQLITE_FUNC_MINMAX) && pWin->eStart!=TK_UNBOUNDED ){
+ /* The inline versions of min() and max() require a single ephemeral
+ ** table and 3 registers. The registers are used as follows:
+ **
+ ** regApp+0: slot to copy min()/max() argument to for MakeRecord
+ ** regApp+1: integer value used to ensure keys are unique
+ ** regApp+2: output of MakeRecord
+ */
+ ExprList *pList = pWin->pOwner->x.pList;
+ KeyInfo *pKeyInfo = tdsqlite3KeyInfoFromExprList(pParse, pList, 0, 0);
+ pWin->csrApp = pParse->nTab++;
+ pWin->regApp = pParse->nMem+1;
+ pParse->nMem += 3;
+ if( pKeyInfo && pWin->pFunc->zName[1]=='i' ){
+ assert( pKeyInfo->aSortFlags[0]==0 );
+ pKeyInfo->aSortFlags[0] = KEYINFO_ORDER_DESC;
+ }
+ tdsqlite3VdbeAddOp2(v, OP_OpenEphemeral, pWin->csrApp, 2);
+ tdsqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp+1);
+ }
+ else if( p->zName==nth_valueName || p->zName==first_valueName ){
+ /* Allocate two registers at pWin->regApp. These will be used to
+ ** store the start and end index of the current frame. */
+ pWin->regApp = pParse->nMem+1;
+ pWin->csrApp = pParse->nTab++;
+ pParse->nMem += 2;
+ tdsqlite3VdbeAddOp2(v, OP_OpenDup, pWin->csrApp, pMWin->iEphCsr);
+ }
+ else if( p->zName==leadName || p->zName==lagName ){
+ pWin->csrApp = pParse->nTab++;
+ tdsqlite3VdbeAddOp2(v, OP_OpenDup, pWin->csrApp, pMWin->iEphCsr);
+ }
+ }
+}
+
+#define WINDOW_STARTING_INT 0
+#define WINDOW_ENDING_INT 1
+#define WINDOW_NTH_VALUE_INT 2
+#define WINDOW_STARTING_NUM 3
+#define WINDOW_ENDING_NUM 4
+
+/*
+** A "PRECEDING <expr>" (eCond==0) or "FOLLOWING <expr>" (eCond==1) or the
+** value of the second argument to nth_value() (eCond==2) has just been
+** evaluated and the result left in register reg. This function generates VM
+** code to check that the value is a non-negative integer and throws an
+** exception if it is not.
+*/
+static void windowCheckValue(Parse *pParse, int reg, int eCond){
+ static const char *azErr[] = {
+ "frame starting offset must be a non-negative integer",
+ "frame ending offset must be a non-negative integer",
+ "second argument to nth_value must be a positive integer",
+ "frame starting offset must be a non-negative number",
+ "frame ending offset must be a non-negative number",
+ };
+ static int aOp[] = { OP_Ge, OP_Ge, OP_Gt, OP_Ge, OP_Ge };
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ int regZero = tdsqlite3GetTempReg(pParse);
+ assert( eCond>=0 && eCond<ArraySize(azErr) );
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regZero);
+ if( eCond>=WINDOW_STARTING_NUM ){
+ int regString = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC);
+ tdsqlite3VdbeAddOp3(v, OP_Ge, regString, tdsqlite3VdbeCurrentAddr(v)+2, reg);
+ tdsqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC|SQLITE_JUMPIFNULL);
+ VdbeCoverage(v);
+ assert( eCond==3 || eCond==4 );
+ VdbeCoverageIf(v, eCond==3);
+ VdbeCoverageIf(v, eCond==4);
+ }else{
+ tdsqlite3VdbeAddOp2(v, OP_MustBeInt, reg, tdsqlite3VdbeCurrentAddr(v)+2);
+ VdbeCoverage(v);
+ assert( eCond==0 || eCond==1 || eCond==2 );
+ VdbeCoverageIf(v, eCond==0);
+ VdbeCoverageIf(v, eCond==1);
+ VdbeCoverageIf(v, eCond==2);
+ }
+ tdsqlite3VdbeAddOp3(v, aOp[eCond], regZero, tdsqlite3VdbeCurrentAddr(v)+2, reg);
+ VdbeCoverageNeverNullIf(v, eCond==0); /* NULL case captured by */
+ VdbeCoverageNeverNullIf(v, eCond==1); /* the OP_MustBeInt */
+ VdbeCoverageNeverNullIf(v, eCond==2);
+ VdbeCoverageNeverNullIf(v, eCond==3); /* NULL case caught by */
+ VdbeCoverageNeverNullIf(v, eCond==4); /* the OP_Ge */
+ tdsqlite3MayAbort(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_Halt, SQLITE_ERROR, OE_Abort);
+ tdsqlite3VdbeAppendP4(v, (void*)azErr[eCond], P4_STATIC);
+ tdsqlite3ReleaseTempReg(pParse, regZero);
+}
+
+/*
+** Return the number of arguments passed to the window-function associated
+** with the object passed as the only argument to this function.
+*/
+static int windowArgCount(Window *pWin){
+ ExprList *pList = pWin->pOwner->x.pList;
+ return (pList ? pList->nExpr : 0);
+}
+
+typedef struct WindowCodeArg WindowCodeArg;
+typedef struct WindowCsrAndReg WindowCsrAndReg;
+
+/*
+** See comments above struct WindowCodeArg.
+*/
+struct WindowCsrAndReg {
+ int csr; /* Cursor number */
+ int reg; /* First in array of peer values */
+};
+
+/*
+** A single instance of this structure is allocated on the stack by
+** tdsqlite3WindowCodeStep() and a pointer to it passed to the various helper
+** routines. This is to reduce the number of arguments required by each
+** helper function.
+**
+** regArg:
+** Each window function requires an accumulator register (just as an
+** ordinary aggregate function does). This variable is set to the first
+** in an array of accumulator registers - one for each window function
+** in the WindowCodeArg.pMWin list.
+**
+** eDelete:
+** The window functions implementation sometimes caches the input rows
+** that it processes in a temporary table. If it is not zero, this
+** variable indicates when rows may be removed from the temp table (in
+** order to reduce memory requirements - it would always be safe just
+** to leave them there). Possible values for eDelete are:
+**
+** WINDOW_RETURN_ROW:
+** An input row can be discarded after it is returned to the caller.
+**
+** WINDOW_AGGINVERSE:
+** An input row can be discarded after the window functions xInverse()
+** callbacks have been invoked in it.
+**
+** WINDOW_AGGSTEP:
+** An input row can be discarded after the window functions xStep()
+** callbacks have been invoked in it.
+**
+** start,current,end
+** Consider a window-frame similar to the following:
+**
+** (ORDER BY a, b GROUPS BETWEEN 2 PRECEDING AND 2 FOLLOWING)
+**
+** The windows functions implmentation caches the input rows in a temp
+** table, sorted by "a, b" (it actually populates the cache lazily, and
+** aggressively removes rows once they are no longer required, but that's
+** a mere detail). It keeps three cursors open on the temp table. One
+** (current) that points to the next row to return to the query engine
+** once its window function values have been calculated. Another (end)
+** points to the next row to call the xStep() method of each window function
+** on (so that it is 2 groups ahead of current). And a third (start) that
+** points to the next row to call the xInverse() method of each window
+** function on.
+**
+** Each cursor (start, current and end) consists of a VDBE cursor
+** (WindowCsrAndReg.csr) and an array of registers (starting at
+** WindowCodeArg.reg) that always contains a copy of the peer values
+** read from the corresponding cursor.
+**
+** Depending on the window-frame in question, all three cursors may not
+** be required. In this case both WindowCodeArg.csr and reg are set to
+** 0.
+*/
+struct WindowCodeArg {
+ Parse *pParse; /* Parse context */
+ Window *pMWin; /* First in list of functions being processed */
+ Vdbe *pVdbe; /* VDBE object */
+ int addrGosub; /* OP_Gosub to this address to return one row */
+ int regGosub; /* Register used with OP_Gosub(addrGosub) */
+ int regArg; /* First in array of accumulator registers */
+ int eDelete; /* See above */
+
+ WindowCsrAndReg start;
+ WindowCsrAndReg current;
+ WindowCsrAndReg end;
+};
+
+/*
+** Generate VM code to read the window frames peer values from cursor csr into
+** an array of registers starting at reg.
+*/
+static void windowReadPeerValues(
+ WindowCodeArg *p,
+ int csr,
+ int reg
+){
+ Window *pMWin = p->pMWin;
+ ExprList *pOrderBy = pMWin->pOrderBy;
+ if( pOrderBy ){
+ Vdbe *v = tdsqlite3GetVdbe(p->pParse);
+ ExprList *pPart = pMWin->pPartition;
+ int iColOff = pMWin->nBufferCol + (pPart ? pPart->nExpr : 0);
+ int i;
+ for(i=0; i<pOrderBy->nExpr; i++){
+ tdsqlite3VdbeAddOp3(v, OP_Column, csr, iColOff+i, reg+i);
+ }
+ }
+}
+
+/*
+** Generate VM code to invoke either xStep() (if bInverse is 0) or
+** xInverse (if bInverse is non-zero) for each window function in the
+** linked list starting at pMWin. Or, for built-in window functions
+** that do not use the standard function API, generate the required
+** inline VM code.
+**
+** If argument csr is greater than or equal to 0, then argument reg is
+** the first register in an array of registers guaranteed to be large
+** enough to hold the array of arguments for each function. In this case
+** the arguments are extracted from the current row of csr into the
+** array of registers before invoking OP_AggStep or OP_AggInverse
+**
+** Or, if csr is less than zero, then the array of registers at reg is
+** already populated with all columns from the current row of the sub-query.
+**
+** If argument regPartSize is non-zero, then it is a register containing the
+** number of rows in the current partition.
+*/
+static void windowAggStep(
+ WindowCodeArg *p,
+ Window *pMWin, /* Linked list of window functions */
+ int csr, /* Read arguments from this cursor */
+ int bInverse, /* True to invoke xInverse instead of xStep */
+ int reg /* Array of registers */
+){
+ Parse *pParse = p->pParse;
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ Window *pWin;
+ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
+ FuncDef *pFunc = pWin->pFunc;
+ int regArg;
+ int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin);
+ int i;
+
+ assert( bInverse==0 || pWin->eStart!=TK_UNBOUNDED );
+
+ /* All OVER clauses in the same window function aggregate step must
+ ** be the same. */
+ assert( pWin==pMWin || tdsqlite3WindowCompare(pParse,pWin,pMWin,0)!=1 );
+
+ for(i=0; i<nArg; i++){
+ if( i!=1 || pFunc->zName!=nth_valueName ){
+ tdsqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+i, reg+i);
+ }else{
+ tdsqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, pWin->iArgCol+i, reg+i);
+ }
+ }
+ regArg = reg;
+
+ if( pMWin->regStartRowid==0
+ && (pFunc->funcFlags & SQLITE_FUNC_MINMAX)
+ && (pWin->eStart!=TK_UNBOUNDED)
+ ){
+ int addrIsNull = tdsqlite3VdbeAddOp1(v, OP_IsNull, regArg);
+ VdbeCoverage(v);
+ if( bInverse==0 ){
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1, 1);
+ tdsqlite3VdbeAddOp2(v, OP_SCopy, regArg, pWin->regApp);
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, pWin->regApp, 2, pWin->regApp+2);
+ tdsqlite3VdbeAddOp2(v, OP_IdxInsert, pWin->csrApp, pWin->regApp+2);
+ }else{
+ tdsqlite3VdbeAddOp4Int(v, OP_SeekGE, pWin->csrApp, 0, regArg, 1);
+ VdbeCoverageNeverTaken(v);
+ tdsqlite3VdbeAddOp1(v, OP_Delete, pWin->csrApp);
+ tdsqlite3VdbeJumpHere(v, tdsqlite3VdbeCurrentAddr(v)-2);
+ }
+ tdsqlite3VdbeJumpHere(v, addrIsNull);
+ }else if( pWin->regApp ){
+ assert( pFunc->zName==nth_valueName
+ || pFunc->zName==first_valueName
+ );
+ assert( bInverse==0 || bInverse==1 );
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1-bInverse, 1);
+ }else if( pFunc->xSFunc!=noopStepFunc ){
+ int addrIf = 0;
+ if( pWin->pFilter ){
+ int regTmp;
+ assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr );
+ assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 );
+ regTmp = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp);
+ addrIf = tdsqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1);
+ VdbeCoverage(v);
+ tdsqlite3ReleaseTempReg(pParse, regTmp);
+ }
+
+ if( pWin->bExprArgs ){
+ int iStart = tdsqlite3VdbeCurrentAddr(v);
+ VdbeOp *pOp, *pEnd;
+
+ nArg = pWin->pOwner->x.pList->nExpr;
+ regArg = tdsqlite3GetTempRange(pParse, nArg);
+ tdsqlite3ExprCodeExprList(pParse, pWin->pOwner->x.pList, regArg, 0, 0);
+
+ pEnd = tdsqlite3VdbeGetOp(v, -1);
+ for(pOp=tdsqlite3VdbeGetOp(v, iStart); pOp<=pEnd; pOp++){
+ if( pOp->opcode==OP_Column && pOp->p1==pWin->iEphCsr ){
+ pOp->p1 = csr;
+ }
+ }
+ }
+ if( pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){
+ CollSeq *pColl;
+ assert( nArg>0 );
+ pColl = tdsqlite3ExprNNCollSeq(pParse, pWin->pOwner->x.pList->a[0].pExpr);
+ tdsqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ);
+ }
+ tdsqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep,
+ bInverse, regArg, pWin->regAccum);
+ tdsqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF);
+ tdsqlite3VdbeChangeP5(v, (u8)nArg);
+ if( pWin->bExprArgs ){
+ tdsqlite3ReleaseTempRange(pParse, regArg, nArg);
+ }
+ if( addrIf ) tdsqlite3VdbeJumpHere(v, addrIf);
+ }
+ }
+}
+
+/*
+** Values that may be passed as the second argument to windowCodeOp().
+*/
+#define WINDOW_RETURN_ROW 1
+#define WINDOW_AGGINVERSE 2
+#define WINDOW_AGGSTEP 3
+
+/*
+** Generate VM code to invoke either xValue() (bFin==0) or xFinalize()
+** (bFin==1) for each window function in the linked list starting at
+** pMWin. Or, for built-in window-functions that do not use the standard
+** API, generate the equivalent VM code.
+*/
+static void windowAggFinal(WindowCodeArg *p, int bFin){
+ Parse *pParse = p->pParse;
+ Window *pMWin = p->pMWin;
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ Window *pWin;
+
+ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
+ if( pMWin->regStartRowid==0
+ && (pWin->pFunc->funcFlags & SQLITE_FUNC_MINMAX)
+ && (pWin->eStart!=TK_UNBOUNDED)
+ ){
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult);
+ tdsqlite3VdbeAddOp1(v, OP_Last, pWin->csrApp);
+ VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_Column, pWin->csrApp, 0, pWin->regResult);
+ tdsqlite3VdbeJumpHere(v, tdsqlite3VdbeCurrentAddr(v)-2);
+ }else if( pWin->regApp ){
+ assert( pMWin->regStartRowid==0 );
+ }else{
+ int nArg = windowArgCount(pWin);
+ if( bFin ){
+ tdsqlite3VdbeAddOp2(v, OP_AggFinal, pWin->regAccum, nArg);
+ tdsqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF);
+ tdsqlite3VdbeAddOp2(v, OP_Copy, pWin->regAccum, pWin->regResult);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum);
+ }else{
+ tdsqlite3VdbeAddOp3(v, OP_AggValue,pWin->regAccum,nArg,pWin->regResult);
+ tdsqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF);
+ }
+ }
+ }
+}
+
+/*
+** Generate code to calculate the current values of all window functions in the
+** p->pMWin list by doing a full scan of the current window frame. Store the
+** results in the Window.regResult registers, ready to return the upper
+** layer.
+*/
+static void windowFullScan(WindowCodeArg *p){
+ Window *pWin;
+ Parse *pParse = p->pParse;
+ Window *pMWin = p->pMWin;
+ Vdbe *v = p->pVdbe;
+
+ int regCRowid = 0; /* Current rowid value */
+ int regCPeer = 0; /* Current peer values */
+ int regRowid = 0; /* AggStep rowid value */
+ int regPeer = 0; /* AggStep peer values */
+
+ int nPeer;
+ int lblNext;
+ int lblBrk;
+ int addrNext;
+ int csr;
+
+ VdbeModuleComment((v, "windowFullScan begin"));
+
+ assert( pMWin!=0 );
+ csr = pMWin->csrApp;
+ nPeer = (pMWin->pOrderBy ? pMWin->pOrderBy->nExpr : 0);
+
+ lblNext = tdsqlite3VdbeMakeLabel(pParse);
+ lblBrk = tdsqlite3VdbeMakeLabel(pParse);
+
+ regCRowid = tdsqlite3GetTempReg(pParse);
+ regRowid = tdsqlite3GetTempReg(pParse);
+ if( nPeer ){
+ regCPeer = tdsqlite3GetTempRange(pParse, nPeer);
+ regPeer = tdsqlite3GetTempRange(pParse, nPeer);
+ }
+
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, pMWin->iEphCsr, regCRowid);
+ windowReadPeerValues(p, pMWin->iEphCsr, regCPeer);
+
+ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum);
+ }
+
+ tdsqlite3VdbeAddOp3(v, OP_SeekGE, csr, lblBrk, pMWin->regStartRowid);
+ VdbeCoverage(v);
+ addrNext = tdsqlite3VdbeCurrentAddr(v);
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, csr, regRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Gt, pMWin->regEndRowid, lblBrk, regRowid);
+ VdbeCoverageNeverNull(v);
+
+ if( pMWin->eExclude==TK_CURRENT ){
+ tdsqlite3VdbeAddOp3(v, OP_Eq, regCRowid, lblNext, regRowid);
+ VdbeCoverageNeverNull(v);
+ }else if( pMWin->eExclude!=TK_NO ){
+ int addr;
+ int addrEq = 0;
+ KeyInfo *pKeyInfo = 0;
+
+ if( pMWin->pOrderBy ){
+ pKeyInfo = tdsqlite3KeyInfoFromExprList(pParse, pMWin->pOrderBy, 0, 0);
+ }
+ if( pMWin->eExclude==TK_TIES ){
+ addrEq = tdsqlite3VdbeAddOp3(v, OP_Eq, regCRowid, 0, regRowid);
+ VdbeCoverageNeverNull(v);
+ }
+ if( pKeyInfo ){
+ windowReadPeerValues(p, csr, regPeer);
+ tdsqlite3VdbeAddOp3(v, OP_Compare, regPeer, regCPeer, nPeer);
+ tdsqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO);
+ addr = tdsqlite3VdbeCurrentAddr(v)+1;
+ tdsqlite3VdbeAddOp3(v, OP_Jump, addr, lblNext, addr);
+ VdbeCoverageEqNe(v);
+ }else{
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, lblNext);
+ }
+ if( addrEq ) tdsqlite3VdbeJumpHere(v, addrEq);
+ }
+
+ windowAggStep(p, pMWin, csr, 0, p->regArg);
+
+ tdsqlite3VdbeResolveLabel(v, lblNext);
+ tdsqlite3VdbeAddOp2(v, OP_Next, csr, addrNext);
+ VdbeCoverage(v);
+ tdsqlite3VdbeJumpHere(v, addrNext-1);
+ tdsqlite3VdbeJumpHere(v, addrNext+1);
+ tdsqlite3ReleaseTempReg(pParse, regRowid);
+ tdsqlite3ReleaseTempReg(pParse, regCRowid);
+ if( nPeer ){
+ tdsqlite3ReleaseTempRange(pParse, regPeer, nPeer);
+ tdsqlite3ReleaseTempRange(pParse, regCPeer, nPeer);
+ }
+
+ windowAggFinal(p, 1);
+ VdbeModuleComment((v, "windowFullScan end"));
+}
+
+/*
+** Invoke the sub-routine at regGosub (generated by code in select.c) to
+** return the current row of Window.iEphCsr. If all window functions are
+** aggregate window functions that use the standard API, a single
+** OP_Gosub instruction is all that this routine generates. Extra VM code
+** for per-row processing is only generated for the following built-in window
+** functions:
+**
+** nth_value()
+** first_value()
+** lag()
+** lead()
+*/
+static void windowReturnOneRow(WindowCodeArg *p){
+ Window *pMWin = p->pMWin;
+ Vdbe *v = p->pVdbe;
+
+ if( pMWin->regStartRowid ){
+ windowFullScan(p);
+ }else{
+ Parse *pParse = p->pParse;
+ Window *pWin;
+
+ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
+ FuncDef *pFunc = pWin->pFunc;
+ if( pFunc->zName==nth_valueName
+ || pFunc->zName==first_valueName
+ ){
+ int csr = pWin->csrApp;
+ int lbl = tdsqlite3VdbeMakeLabel(pParse);
+ int tmpReg = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult);
+
+ if( pFunc->zName==nth_valueName ){
+ tdsqlite3VdbeAddOp3(v, OP_Column,pMWin->iEphCsr,pWin->iArgCol+1,tmpReg);
+ windowCheckValue(pParse, tmpReg, 2);
+ }else{
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, tmpReg);
+ }
+ tdsqlite3VdbeAddOp3(v, OP_Add, tmpReg, pWin->regApp, tmpReg);
+ tdsqlite3VdbeAddOp3(v, OP_Gt, pWin->regApp+1, lbl, tmpReg);
+ VdbeCoverageNeverNull(v);
+ tdsqlite3VdbeAddOp3(v, OP_SeekRowid, csr, 0, tmpReg);
+ VdbeCoverageNeverTaken(v);
+ tdsqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol, pWin->regResult);
+ tdsqlite3VdbeResolveLabel(v, lbl);
+ tdsqlite3ReleaseTempReg(pParse, tmpReg);
+ }
+ else if( pFunc->zName==leadName || pFunc->zName==lagName ){
+ int nArg = pWin->pOwner->x.pList->nExpr;
+ int csr = pWin->csrApp;
+ int lbl = tdsqlite3VdbeMakeLabel(pParse);
+ int tmpReg = tdsqlite3GetTempReg(pParse);
+ int iEph = pMWin->iEphCsr;
+
+ if( nArg<3 ){
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult);
+ }else{
+ tdsqlite3VdbeAddOp3(v, OP_Column, iEph,pWin->iArgCol+2,pWin->regResult);
+ }
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, iEph, tmpReg);
+ if( nArg<2 ){
+ int val = (pFunc->zName==leadName ? 1 : -1);
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, tmpReg, val);
+ }else{
+ int op = (pFunc->zName==leadName ? OP_Add : OP_Subtract);
+ int tmpReg2 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp3(v, OP_Column, iEph, pWin->iArgCol+1, tmpReg2);
+ tdsqlite3VdbeAddOp3(v, op, tmpReg2, tmpReg, tmpReg);
+ tdsqlite3ReleaseTempReg(pParse, tmpReg2);
+ }
+
+ tdsqlite3VdbeAddOp3(v, OP_SeekRowid, csr, lbl, tmpReg);
+ VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol, pWin->regResult);
+ tdsqlite3VdbeResolveLabel(v, lbl);
+ tdsqlite3ReleaseTempReg(pParse, tmpReg);
+ }
+ }
+ }
+ tdsqlite3VdbeAddOp2(v, OP_Gosub, p->regGosub, p->addrGosub);
+}
+
+/*
+** Generate code to set the accumulator register for each window function
+** in the linked list passed as the second argument to NULL. And perform
+** any equivalent initialization required by any built-in window functions
+** in the list.
+*/
+static int windowInitAccum(Parse *pParse, Window *pMWin){
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ int regArg;
+ int nArg = 0;
+ Window *pWin;
+ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
+ FuncDef *pFunc = pWin->pFunc;
+ tdsqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum);
+ nArg = MAX(nArg, windowArgCount(pWin));
+ if( pMWin->regStartRowid==0 ){
+ if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ){
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp+1);
+ }
+
+ if( (pFunc->funcFlags & SQLITE_FUNC_MINMAX) && pWin->csrApp ){
+ assert( pWin->eStart!=TK_UNBOUNDED );
+ tdsqlite3VdbeAddOp1(v, OP_ResetSorter, pWin->csrApp);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pWin->regApp+1);
+ }
+ }
+ }
+ regArg = pParse->nMem+1;
+ pParse->nMem += nArg;
+ return regArg;
+}
+
+/*
+** Return true if the current frame should be cached in the ephemeral table,
+** even if there are no xInverse() calls required.
+*/
+static int windowCacheFrame(Window *pMWin){
+ Window *pWin;
+ if( pMWin->regStartRowid ) return 1;
+ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
+ FuncDef *pFunc = pWin->pFunc;
+ if( (pFunc->zName==nth_valueName)
+ || (pFunc->zName==first_valueName)
+ || (pFunc->zName==leadName)
+ || (pFunc->zName==lagName)
+ ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** regOld and regNew are each the first register in an array of size
+** pOrderBy->nExpr. This function generates code to compare the two
+** arrays of registers using the collation sequences and other comparison
+** parameters specified by pOrderBy.
+**
+** If the two arrays are not equal, the contents of regNew is copied to
+** regOld and control falls through. Otherwise, if the contents of the arrays
+** are equal, an OP_Goto is executed. The address of the OP_Goto is returned.
+*/
+static void windowIfNewPeer(
+ Parse *pParse,
+ ExprList *pOrderBy,
+ int regNew, /* First in array of new values */
+ int regOld, /* First in array of old values */
+ int addr /* Jump here */
+){
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ if( pOrderBy ){
+ int nVal = pOrderBy->nExpr;
+ KeyInfo *pKeyInfo = tdsqlite3KeyInfoFromExprList(pParse, pOrderBy, 0, 0);
+ tdsqlite3VdbeAddOp3(v, OP_Compare, regOld, regNew, nVal);
+ tdsqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO);
+ tdsqlite3VdbeAddOp3(v, OP_Jump,
+ tdsqlite3VdbeCurrentAddr(v)+1, addr, tdsqlite3VdbeCurrentAddr(v)+1
+ );
+ VdbeCoverageEqNe(v);
+ tdsqlite3VdbeAddOp3(v, OP_Copy, regNew, regOld, nVal-1);
+ }else{
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, addr);
+ }
+}
+
+/*
+** This function is called as part of generating VM programs for RANGE
+** offset PRECEDING/FOLLOWING frame boundaries. Assuming "ASC" order for
+** the ORDER BY term in the window, and that argument op is OP_Ge, it generates
+** code equivalent to:
+**
+** if( csr1.peerVal + regVal >= csr2.peerVal ) goto lbl;
+**
+** The value of parameter op may also be OP_Gt or OP_Le. In these cases the
+** operator in the above pseudo-code is replaced with ">" or "<=", respectively.
+**
+** If the sort-order for the ORDER BY term in the window is DESC, then the
+** comparison is reversed. Instead of adding regVal to csr1.peerVal, it is
+** subtracted. And the comparison operator is inverted to - ">=" becomes "<=",
+** ">" becomes "<", and so on. So, with DESC sort order, if the argument op
+** is OP_Ge, the generated code is equivalent to:
+**
+** if( csr1.peerVal - regVal <= csr2.peerVal ) goto lbl;
+**
+** A special type of arithmetic is used such that if csr1.peerVal is not
+** a numeric type (real or integer), then the result of the addition addition
+** or subtraction is a a copy of csr1.peerVal.
+*/
+static void windowCodeRangeTest(
+ WindowCodeArg *p,
+ int op, /* OP_Ge, OP_Gt, or OP_Le */
+ int csr1, /* Cursor number for cursor 1 */
+ int regVal, /* Register containing non-negative number */
+ int csr2, /* Cursor number for cursor 2 */
+ int lbl /* Jump destination if condition is true */
+){
+ Parse *pParse = p->pParse;
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ ExprList *pOrderBy = p->pMWin->pOrderBy; /* ORDER BY clause for window */
+ int reg1 = tdsqlite3GetTempReg(pParse); /* Reg. for csr1.peerVal+regVal */
+ int reg2 = tdsqlite3GetTempReg(pParse); /* Reg. for csr2.peerVal */
+ int regString = ++pParse->nMem; /* Reg. for constant value '' */
+ int arith = OP_Add; /* OP_Add or OP_Subtract */
+ int addrGe; /* Jump destination */
+
+ assert( op==OP_Ge || op==OP_Gt || op==OP_Le );
+ assert( pOrderBy && pOrderBy->nExpr==1 );
+ if( pOrderBy->a[0].sortFlags & KEYINFO_ORDER_DESC ){
+ switch( op ){
+ case OP_Ge: op = OP_Le; break;
+ case OP_Gt: op = OP_Lt; break;
+ default: assert( op==OP_Le ); op = OP_Ge; break;
+ }
+ arith = OP_Subtract;
+ }
+
+ /* Read the peer-value from each cursor into a register */
+ windowReadPeerValues(p, csr1, reg1);
+ windowReadPeerValues(p, csr2, reg2);
+
+ VdbeModuleComment((v, "CodeRangeTest: if( R%d %s R%d %s R%d ) goto lbl",
+ reg1, (arith==OP_Add ? "+" : "-"), regVal,
+ ((op==OP_Ge) ? ">=" : (op==OP_Le) ? "<=" : (op==OP_Gt) ? ">" : "<"), reg2
+ ));
+
+ /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1).
+ ** This block adds (or subtracts for DESC) the numeric value in regVal
+ ** from it. Or, if reg1 is not numeric (it is a NULL, a text value or a blob),
+ ** then leave reg1 as it is. In pseudo-code, this is implemented as:
+ **
+ ** if( reg1>='' ) goto addrGe;
+ ** reg1 = reg1 +/- regVal
+ ** addrGe:
+ **
+ ** Since all strings and blobs are greater-than-or-equal-to an empty string,
+ ** the add/subtract is skipped for these, as required. If reg1 is a NULL,
+ ** then the arithmetic is performed, but since adding or subtracting from
+ ** NULL is always NULL anyway, this case is handled as required too. */
+ tdsqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC);
+ addrGe = tdsqlite3VdbeAddOp3(v, OP_Ge, regString, 0, reg1);
+ VdbeCoverage(v);
+ tdsqlite3VdbeAddOp3(v, arith, regVal, reg1, reg1);
+ tdsqlite3VdbeJumpHere(v, addrGe);
+
+ /* If the BIGNULL flag is set for the ORDER BY, then it is required to
+ ** consider NULL values to be larger than all other values, instead of
+ ** the usual smaller. The VDBE opcodes OP_Ge and so on do not handle this
+ ** (and adding that capability causes a performance regression), so
+ ** instead if the BIGNULL flag is set then cases where either reg1 or
+ ** reg2 are NULL are handled separately in the following block. The code
+ ** generated is equivalent to:
+ **
+ ** if( reg1 IS NULL ){
+ ** if( op==OP_Ge ) goto lbl;
+ ** if( op==OP_Gt && reg2 IS NOT NULL ) goto lbl;
+ ** if( op==OP_Le && reg2 IS NULL ) goto lbl;
+ ** }else if( reg2 IS NULL ){
+ ** if( op==OP_Le ) goto lbl;
+ ** }
+ **
+ ** Additionally, if either reg1 or reg2 are NULL but the jump to lbl is
+ ** not taken, control jumps over the comparison operator coded below this
+ ** block. */
+ if( pOrderBy->a[0].sortFlags & KEYINFO_ORDER_BIGNULL ){
+ /* This block runs if reg1 contains a NULL. */
+ int addr = tdsqlite3VdbeAddOp1(v, OP_NotNull, reg1); VdbeCoverage(v);
+ switch( op ){
+ case OP_Ge:
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, lbl);
+ break;
+ case OP_Gt:
+ tdsqlite3VdbeAddOp2(v, OP_NotNull, reg2, lbl);
+ VdbeCoverage(v);
+ break;
+ case OP_Le:
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl);
+ VdbeCoverage(v);
+ break;
+ default: assert( op==OP_Lt ); /* no-op */ break;
+ }
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, tdsqlite3VdbeCurrentAddr(v)+3);
+
+ /* This block runs if reg1 is not NULL, but reg2 is. */
+ tdsqlite3VdbeJumpHere(v, addr);
+ tdsqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); VdbeCoverage(v);
+ if( op==OP_Gt || op==OP_Ge ){
+ tdsqlite3VdbeChangeP2(v, -1, tdsqlite3VdbeCurrentAddr(v)+1);
+ }
+ }
+
+ /* Compare registers reg2 and reg1, taking the jump if required. Note that
+ ** control skips over this test if the BIGNULL flag is set and either
+ ** reg1 or reg2 contain a NULL value. */
+ tdsqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v);
+ tdsqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
+
+ assert( op==OP_Ge || op==OP_Gt || op==OP_Lt || op==OP_Le );
+ testcase(op==OP_Ge); VdbeCoverageIf(v, op==OP_Ge);
+ testcase(op==OP_Lt); VdbeCoverageIf(v, op==OP_Lt);
+ testcase(op==OP_Le); VdbeCoverageIf(v, op==OP_Le);
+ testcase(op==OP_Gt); VdbeCoverageIf(v, op==OP_Gt);
+ tdsqlite3ReleaseTempReg(pParse, reg1);
+ tdsqlite3ReleaseTempReg(pParse, reg2);
+
+ VdbeModuleComment((v, "CodeRangeTest: end"));
+}
+
+/*
+** Helper function for tdsqlite3WindowCodeStep(). Each call to this function
+** generates VM code for a single RETURN_ROW, AGGSTEP or AGGINVERSE
+** operation. Refer to the header comment for tdsqlite3WindowCodeStep() for
+** details.
+*/
+static int windowCodeOp(
+ WindowCodeArg *p, /* Context object */
+ int op, /* WINDOW_RETURN_ROW, AGGSTEP or AGGINVERSE */
+ int regCountdown, /* Register for OP_IfPos countdown */
+ int jumpOnEof /* Jump here if stepped cursor reaches EOF */
+){
+ int csr, reg;
+ Parse *pParse = p->pParse;
+ Window *pMWin = p->pMWin;
+ int ret = 0;
+ Vdbe *v = p->pVdbe;
+ int addrContinue = 0;
+ int bPeer = (pMWin->eFrmType!=TK_ROWS);
+
+ int lblDone = tdsqlite3VdbeMakeLabel(pParse);
+ int addrNextRange = 0;
+
+ /* Special case - WINDOW_AGGINVERSE is always a no-op if the frame
+ ** starts with UNBOUNDED PRECEDING. */
+ if( op==WINDOW_AGGINVERSE && pMWin->eStart==TK_UNBOUNDED ){
+ assert( regCountdown==0 && jumpOnEof==0 );
+ return 0;
+ }
+
+ if( regCountdown>0 ){
+ if( pMWin->eFrmType==TK_RANGE ){
+ addrNextRange = tdsqlite3VdbeCurrentAddr(v);
+ assert( op==WINDOW_AGGINVERSE || op==WINDOW_AGGSTEP );
+ if( op==WINDOW_AGGINVERSE ){
+ if( pMWin->eStart==TK_FOLLOWING ){
+ windowCodeRangeTest(
+ p, OP_Le, p->current.csr, regCountdown, p->start.csr, lblDone
+ );
+ }else{
+ windowCodeRangeTest(
+ p, OP_Ge, p->start.csr, regCountdown, p->current.csr, lblDone
+ );
+ }
+ }else{
+ windowCodeRangeTest(
+ p, OP_Gt, p->end.csr, regCountdown, p->current.csr, lblDone
+ );
+ }
+ }else{
+ tdsqlite3VdbeAddOp3(v, OP_IfPos, regCountdown, lblDone, 1);
+ VdbeCoverage(v);
+ }
+ }
+
+ if( op==WINDOW_RETURN_ROW && pMWin->regStartRowid==0 ){
+ windowAggFinal(p, 0);
+ }
+ addrContinue = tdsqlite3VdbeCurrentAddr(v);
+
+ /* If this is a (RANGE BETWEEN a FOLLOWING AND b FOLLOWING) or
+ ** (RANGE BETWEEN b PRECEDING AND a PRECEDING) frame, ensure the
+ ** start cursor does not advance past the end cursor within the
+ ** temporary table. It otherwise might, if (a>b). */
+ if( pMWin->eStart==pMWin->eEnd && regCountdown
+ && pMWin->eFrmType==TK_RANGE && op==WINDOW_AGGINVERSE
+ ){
+ int regRowid1 = tdsqlite3GetTempReg(pParse);
+ int regRowid2 = tdsqlite3GetTempReg(pParse);
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1);
+ tdsqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2);
+ tdsqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1);
+ VdbeCoverage(v);
+ tdsqlite3ReleaseTempReg(pParse, regRowid1);
+ tdsqlite3ReleaseTempReg(pParse, regRowid2);
+ assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING );
+ }
+
+ switch( op ){
+ case WINDOW_RETURN_ROW:
+ csr = p->current.csr;
+ reg = p->current.reg;
+ windowReturnOneRow(p);
+ break;
+
+ case WINDOW_AGGINVERSE:
+ csr = p->start.csr;
+ reg = p->start.reg;
+ if( pMWin->regStartRowid ){
+ assert( pMWin->regEndRowid );
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, pMWin->regStartRowid, 1);
+ }else{
+ windowAggStep(p, pMWin, csr, 1, p->regArg);
+ }
+ break;
+
+ default:
+ assert( op==WINDOW_AGGSTEP );
+ csr = p->end.csr;
+ reg = p->end.reg;
+ if( pMWin->regStartRowid ){
+ assert( pMWin->regEndRowid );
+ tdsqlite3VdbeAddOp2(v, OP_AddImm, pMWin->regEndRowid, 1);
+ }else{
+ windowAggStep(p, pMWin, csr, 0, p->regArg);
+ }
+ break;
+ }
+
+ if( op==p->eDelete ){
+ tdsqlite3VdbeAddOp1(v, OP_Delete, csr);
+ tdsqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION);
+ }
+
+ if( jumpOnEof ){
+ tdsqlite3VdbeAddOp2(v, OP_Next, csr, tdsqlite3VdbeCurrentAddr(v)+2);
+ VdbeCoverage(v);
+ ret = tdsqlite3VdbeAddOp0(v, OP_Goto);
+ }else{
+ tdsqlite3VdbeAddOp2(v, OP_Next, csr, tdsqlite3VdbeCurrentAddr(v)+1+bPeer);
+ VdbeCoverage(v);
+ if( bPeer ){
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, lblDone);
+ }
+ }
+
+ if( bPeer ){
+ int nReg = (pMWin->pOrderBy ? pMWin->pOrderBy->nExpr : 0);
+ int regTmp = (nReg ? tdsqlite3GetTempRange(pParse, nReg) : 0);
+ windowReadPeerValues(p, csr, regTmp);
+ windowIfNewPeer(pParse, pMWin->pOrderBy, regTmp, reg, addrContinue);
+ tdsqlite3ReleaseTempRange(pParse, regTmp, nReg);
+ }
+
+ if( addrNextRange ){
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, addrNextRange);
+ }
+ tdsqlite3VdbeResolveLabel(v, lblDone);
+ return ret;
+}
+
+
+/*
+** Allocate and return a duplicate of the Window object indicated by the
+** third argument. Set the Window.pOwner field of the new object to
+** pOwner.
+*/
+SQLITE_PRIVATE Window *tdsqlite3WindowDup(tdsqlite3 *db, Expr *pOwner, Window *p){
+ Window *pNew = 0;
+ if( ALWAYS(p) ){
+ pNew = tdsqlite3DbMallocZero(db, sizeof(Window));
+ if( pNew ){
+ pNew->zName = tdsqlite3DbStrDup(db, p->zName);
+ pNew->zBase = tdsqlite3DbStrDup(db, p->zBase);
+ pNew->pFilter = tdsqlite3ExprDup(db, p->pFilter, 0);
+ pNew->pFunc = p->pFunc;
+ pNew->pPartition = tdsqlite3ExprListDup(db, p->pPartition, 0);
+ pNew->pOrderBy = tdsqlite3ExprListDup(db, p->pOrderBy, 0);
+ pNew->eFrmType = p->eFrmType;
+ pNew->eEnd = p->eEnd;
+ pNew->eStart = p->eStart;
+ pNew->eExclude = p->eExclude;
+ pNew->regResult = p->regResult;
+ pNew->pStart = tdsqlite3ExprDup(db, p->pStart, 0);
+ pNew->pEnd = tdsqlite3ExprDup(db, p->pEnd, 0);
+ pNew->pOwner = pOwner;
+ pNew->bImplicitFrame = p->bImplicitFrame;
+ }
+ }
+ return pNew;
+}
+
+/*
+** Return a copy of the linked list of Window objects passed as the
+** second argument.
+*/
+SQLITE_PRIVATE Window *tdsqlite3WindowListDup(tdsqlite3 *db, Window *p){
+ Window *pWin;
+ Window *pRet = 0;
+ Window **pp = &pRet;
+
+ for(pWin=p; pWin; pWin=pWin->pNextWin){
+ *pp = tdsqlite3WindowDup(db, 0, pWin);
+ if( *pp==0 ) break;
+ pp = &((*pp)->pNextWin);
+ }
+
+ return pRet;
+}
+
+/*
+** Return true if it can be determined at compile time that expression
+** pExpr evaluates to a value that, when cast to an integer, is greater
+** than zero. False otherwise.
+**
+** If an OOM error occurs, this function sets the Parse.db.mallocFailed
+** flag and returns zero.
+*/
+static int windowExprGtZero(Parse *pParse, Expr *pExpr){
+ int ret = 0;
+ tdsqlite3 *db = pParse->db;
+ tdsqlite3_value *pVal = 0;
+ tdsqlite3ValueFromExpr(db, pExpr, db->enc, SQLITE_AFF_NUMERIC, &pVal);
+ if( pVal && tdsqlite3_value_int(pVal)>0 ){
+ ret = 1;
+ }
+ tdsqlite3ValueFree(pVal);
+ return ret;
+}
+
+/*
+** tdsqlite3WhereBegin() has already been called for the SELECT statement
+** passed as the second argument when this function is invoked. It generates
+** code to populate the Window.regResult register for each window function
+** and invoke the sub-routine at instruction addrGosub once for each row.
+** tdsqlite3WhereEnd() is always called before returning.
+**
+** This function handles several different types of window frames, which
+** require slightly different processing. The following pseudo code is
+** used to implement window frames of the form:
+**
+** ROWS BETWEEN <expr1> PRECEDING AND <expr2> FOLLOWING
+**
+** Other window frame types use variants of the following:
+**
+** ... loop started by tdsqlite3WhereBegin() ...
+** if( new partition ){
+** Gosub flush
+** }
+** Insert new row into eph table.
+**
+** if( first row of partition ){
+** // Rewind three cursors, all open on the eph table.
+** Rewind(csrEnd);
+** Rewind(csrStart);
+** Rewind(csrCurrent);
+**
+** regEnd = <expr2> // FOLLOWING expression
+** regStart = <expr1> // PRECEDING expression
+** }else{
+** // First time this branch is taken, the eph table contains two
+** // rows. The first row in the partition, which all three cursors
+** // currently point to, and the following row.
+** AGGSTEP
+** if( (regEnd--)<=0 ){
+** RETURN_ROW
+** if( (regStart--)<=0 ){
+** AGGINVERSE
+** }
+** }
+** }
+** }
+** flush:
+** AGGSTEP
+** while( 1 ){
+** RETURN ROW
+** if( csrCurrent is EOF ) break;
+** if( (regStart--)<=0 ){
+** AggInverse(csrStart)
+** Next(csrStart)
+** }
+** }
+**
+** The pseudo-code above uses the following shorthand:
+**
+** AGGSTEP: invoke the aggregate xStep() function for each window function
+** with arguments read from the current row of cursor csrEnd, then
+** step cursor csrEnd forward one row (i.e. tdsqlite3BtreeNext()).
+**
+** RETURN_ROW: return a row to the caller based on the contents of the
+** current row of csrCurrent and the current state of all
+** aggregates. Then step cursor csrCurrent forward one row.
+**
+** AGGINVERSE: invoke the aggregate xInverse() function for each window
+** functions with arguments read from the current row of cursor
+** csrStart. Then step csrStart forward one row.
+**
+** There are two other ROWS window frames that are handled significantly
+** differently from the above - "BETWEEN <expr> PRECEDING AND <expr> PRECEDING"
+** and "BETWEEN <expr> FOLLOWING AND <expr> FOLLOWING". These are special
+** cases because they change the order in which the three cursors (csrStart,
+** csrCurrent and csrEnd) iterate through the ephemeral table. Cases that
+** use UNBOUNDED or CURRENT ROW are much simpler variations on one of these
+** three.
+**
+** ROWS BETWEEN <expr1> PRECEDING AND <expr2> PRECEDING
+**
+** ... loop started by tdsqlite3WhereBegin() ...
+** if( new partition ){
+** Gosub flush
+** }
+** Insert new row into eph table.
+** if( first row of partition ){
+** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent)
+** regEnd = <expr2>
+** regStart = <expr1>
+** }else{
+** if( (regEnd--)<=0 ){
+** AGGSTEP
+** }
+** RETURN_ROW
+** if( (regStart--)<=0 ){
+** AGGINVERSE
+** }
+** }
+** }
+** flush:
+** if( (regEnd--)<=0 ){
+** AGGSTEP
+** }
+** RETURN_ROW
+**
+**
+** ROWS BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING
+**
+** ... loop started by tdsqlite3WhereBegin() ...
+** if( new partition ){
+** Gosub flush
+** }
+** Insert new row into eph table.
+** if( first row of partition ){
+** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent)
+** regEnd = <expr2>
+** regStart = regEnd - <expr1>
+** }else{
+** AGGSTEP
+** if( (regEnd--)<=0 ){
+** RETURN_ROW
+** }
+** if( (regStart--)<=0 ){
+** AGGINVERSE
+** }
+** }
+** }
+** flush:
+** AGGSTEP
+** while( 1 ){
+** if( (regEnd--)<=0 ){
+** RETURN_ROW
+** if( eof ) break;
+** }
+** if( (regStart--)<=0 ){
+** AGGINVERSE
+** if( eof ) break
+** }
+** }
+** while( !eof csrCurrent ){
+** RETURN_ROW
+** }
+**
+** For the most part, the patterns above are adapted to support UNBOUNDED by
+** assuming that it is equivalent to "infinity PRECEDING/FOLLOWING" and
+** CURRENT ROW by assuming that it is equivilent to "0 PRECEDING/FOLLOWING".
+** This is optimized of course - branches that will never be taken and
+** conditions that are always true are omitted from the VM code. The only
+** exceptional case is:
+**
+** ROWS BETWEEN <expr1> FOLLOWING AND UNBOUNDED FOLLOWING
+**
+** ... loop started by tdsqlite3WhereBegin() ...
+** if( new partition ){
+** Gosub flush
+** }
+** Insert new row into eph table.
+** if( first row of partition ){
+** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent)
+** regStart = <expr1>
+** }else{
+** AGGSTEP
+** }
+** }
+** flush:
+** AGGSTEP
+** while( 1 ){
+** if( (regStart--)<=0 ){
+** AGGINVERSE
+** if( eof ) break
+** }
+** RETURN_ROW
+** }
+** while( !eof csrCurrent ){
+** RETURN_ROW
+** }
+**
+** Also requiring special handling are the cases:
+**
+** ROWS BETWEEN <expr1> PRECEDING AND <expr2> PRECEDING
+** ROWS BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING
+**
+** when (expr1 < expr2). This is detected at runtime, not by this function.
+** To handle this case, the pseudo-code programs depicted above are modified
+** slightly to be:
+**
+** ... loop started by tdsqlite3WhereBegin() ...
+** if( new partition ){
+** Gosub flush
+** }
+** Insert new row into eph table.
+** if( first row of partition ){
+** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent)
+** regEnd = <expr2>
+** regStart = <expr1>
+** if( regEnd < regStart ){
+** RETURN_ROW
+** delete eph table contents
+** continue
+** }
+** ...
+**
+** The new "continue" statement in the above jumps to the next iteration
+** of the outer loop - the one started by tdsqlite3WhereBegin().
+**
+** The various GROUPS cases are implemented using the same patterns as
+** ROWS. The VM code is modified slightly so that:
+**
+** 1. The else branch in the main loop is only taken if the row just
+** added to the ephemeral table is the start of a new group. In
+** other words, it becomes:
+**
+** ... loop started by tdsqlite3WhereBegin() ...
+** if( new partition ){
+** Gosub flush
+** }
+** Insert new row into eph table.
+** if( first row of partition ){
+** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent)
+** regEnd = <expr2>
+** regStart = <expr1>
+** }else if( new group ){
+** ...
+** }
+** }
+**
+** 2. Instead of processing a single row, each RETURN_ROW, AGGSTEP or
+** AGGINVERSE step processes the current row of the relevant cursor and
+** all subsequent rows belonging to the same group.
+**
+** RANGE window frames are a little different again. As for GROUPS, the
+** main loop runs once per group only. And RETURN_ROW, AGGSTEP and AGGINVERSE
+** deal in groups instead of rows. As for ROWS and GROUPS, there are three
+** basic cases:
+**
+** RANGE BETWEEN <expr1> PRECEDING AND <expr2> FOLLOWING
+**
+** ... loop started by tdsqlite3WhereBegin() ...
+** if( new partition ){
+** Gosub flush
+** }
+** Insert new row into eph table.
+** if( first row of partition ){
+** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent)
+** regEnd = <expr2>
+** regStart = <expr1>
+** }else{
+** AGGSTEP
+** while( (csrCurrent.key + regEnd) < csrEnd.key ){
+** RETURN_ROW
+** while( csrStart.key + regStart) < csrCurrent.key ){
+** AGGINVERSE
+** }
+** }
+** }
+** }
+** flush:
+** AGGSTEP
+** while( 1 ){
+** RETURN ROW
+** if( csrCurrent is EOF ) break;
+** while( csrStart.key + regStart) < csrCurrent.key ){
+** AGGINVERSE
+** }
+** }
+** }
+**
+** In the above notation, "csr.key" means the current value of the ORDER BY
+** expression (there is only ever 1 for a RANGE that uses an <expr> FOLLOWING
+** or <expr PRECEDING) read from cursor csr.
+**
+** RANGE BETWEEN <expr1> PRECEDING AND <expr2> PRECEDING
+**
+** ... loop started by tdsqlite3WhereBegin() ...
+** if( new partition ){
+** Gosub flush
+** }
+** Insert new row into eph table.
+** if( first row of partition ){
+** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent)
+** regEnd = <expr2>
+** regStart = <expr1>
+** }else{
+** while( (csrEnd.key + regEnd) <= csrCurrent.key ){
+** AGGSTEP
+** }
+** while( (csrStart.key + regStart) < csrCurrent.key ){
+** AGGINVERSE
+** }
+** RETURN_ROW
+** }
+** }
+** flush:
+** while( (csrEnd.key + regEnd) <= csrCurrent.key ){
+** AGGSTEP
+** }
+** while( (csrStart.key + regStart) < csrCurrent.key ){
+** AGGINVERSE
+** }
+** RETURN_ROW
+**
+** RANGE BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING
+**
+** ... loop started by tdsqlite3WhereBegin() ...
+** if( new partition ){
+** Gosub flush
+** }
+** Insert new row into eph table.
+** if( first row of partition ){
+** Rewind(csrEnd) ; Rewind(csrStart) ; Rewind(csrCurrent)
+** regEnd = <expr2>
+** regStart = <expr1>
+** }else{
+** AGGSTEP
+** while( (csrCurrent.key + regEnd) < csrEnd.key ){
+** while( (csrCurrent.key + regStart) > csrStart.key ){
+** AGGINVERSE
+** }
+** RETURN_ROW
+** }
+** }
+** }
+** flush:
+** AGGSTEP
+** while( 1 ){
+** while( (csrCurrent.key + regStart) > csrStart.key ){
+** AGGINVERSE
+** if( eof ) break "while( 1 )" loop.
+** }
+** RETURN_ROW
+** }
+** while( !eof csrCurrent ){
+** RETURN_ROW
+** }
+**
+** The text above leaves out many details. Refer to the code and comments
+** below for a more complete picture.
+*/
+SQLITE_PRIVATE void tdsqlite3WindowCodeStep(
+ Parse *pParse, /* Parse context */
+ Select *p, /* Rewritten SELECT statement */
+ WhereInfo *pWInfo, /* Context returned by tdsqlite3WhereBegin() */
+ int regGosub, /* Register for OP_Gosub */
+ int addrGosub /* OP_Gosub here to return each row */
+){
+ Window *pMWin = p->pWin;
+ ExprList *pOrderBy = pMWin->pOrderBy;
+ Vdbe *v = tdsqlite3GetVdbe(pParse);
+ int csrWrite; /* Cursor used to write to eph. table */
+ int csrInput = p->pSrc->a[0].iCursor; /* Cursor of sub-select */
+ int nInput = p->pSrc->a[0].pTab->nCol; /* Number of cols returned by sub */
+ int iInput; /* To iterate through sub cols */
+ int addrNe; /* Address of OP_Ne */
+ int addrGosubFlush = 0; /* Address of OP_Gosub to flush: */
+ int addrInteger = 0; /* Address of OP_Integer */
+ int addrEmpty; /* Address of OP_Rewind in flush: */
+ int regNew; /* Array of registers holding new input row */
+ int regRecord; /* regNew array in record form */
+ int regRowid; /* Rowid for regRecord in eph table */
+ int regNewPeer = 0; /* Peer values for new row (part of regNew) */
+ int regPeer = 0; /* Peer values for current row */
+ int regFlushPart = 0; /* Register for "Gosub flush_partition" */
+ WindowCodeArg s; /* Context object for sub-routines */
+ int lblWhereEnd; /* Label just before tdsqlite3WhereEnd() code */
+ int regStart = 0; /* Value of <expr> PRECEDING */
+ int regEnd = 0; /* Value of <expr> FOLLOWING */
+
+ assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_CURRENT
+ || pMWin->eStart==TK_FOLLOWING || pMWin->eStart==TK_UNBOUNDED
+ );
+ assert( pMWin->eEnd==TK_FOLLOWING || pMWin->eEnd==TK_CURRENT
+ || pMWin->eEnd==TK_UNBOUNDED || pMWin->eEnd==TK_PRECEDING
+ );
+ assert( pMWin->eExclude==0 || pMWin->eExclude==TK_CURRENT
+ || pMWin->eExclude==TK_GROUP || pMWin->eExclude==TK_TIES
+ || pMWin->eExclude==TK_NO
+ );
+
+ lblWhereEnd = tdsqlite3VdbeMakeLabel(pParse);
+
+ /* Fill in the context object */
+ memset(&s, 0, sizeof(WindowCodeArg));
+ s.pParse = pParse;
+ s.pMWin = pMWin;
+ s.pVdbe = v;
+ s.regGosub = regGosub;
+ s.addrGosub = addrGosub;
+ s.current.csr = pMWin->iEphCsr;
+ csrWrite = s.current.csr+1;
+ s.start.csr = s.current.csr+2;
+ s.end.csr = s.current.csr+3;
+
+ /* Figure out when rows may be deleted from the ephemeral table. There
+ ** are four options - they may never be deleted (eDelete==0), they may
+ ** be deleted as soon as they are no longer part of the window frame
+ ** (eDelete==WINDOW_AGGINVERSE), they may be deleted as after the row
+ ** has been returned to the caller (WINDOW_RETURN_ROW), or they may
+ ** be deleted after they enter the frame (WINDOW_AGGSTEP). */
+ switch( pMWin->eStart ){
+ case TK_FOLLOWING:
+ if( pMWin->eFrmType!=TK_RANGE
+ && windowExprGtZero(pParse, pMWin->pStart)
+ ){
+ s.eDelete = WINDOW_RETURN_ROW;
+ }
+ break;
+ case TK_UNBOUNDED:
+ if( windowCacheFrame(pMWin)==0 ){
+ if( pMWin->eEnd==TK_PRECEDING ){
+ if( pMWin->eFrmType!=TK_RANGE
+ && windowExprGtZero(pParse, pMWin->pEnd)
+ ){
+ s.eDelete = WINDOW_AGGSTEP;
+ }
+ }else{
+ s.eDelete = WINDOW_RETURN_ROW;
+ }
+ }
+ break;
+ default:
+ s.eDelete = WINDOW_AGGINVERSE;
+ break;
+ }
+
+ /* Allocate registers for the array of values from the sub-query, the
+ ** samve values in record form, and the rowid used to insert said record
+ ** into the ephemeral table. */
+ regNew = pParse->nMem+1;
+ pParse->nMem += nInput;
+ regRecord = ++pParse->nMem;
+ regRowid = ++pParse->nMem;
+
+ /* If the window frame contains an "<expr> PRECEDING" or "<expr> FOLLOWING"
+ ** clause, allocate registers to store the results of evaluating each
+ ** <expr>. */
+ if( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING ){
+ regStart = ++pParse->nMem;
+ }
+ if( pMWin->eEnd==TK_PRECEDING || pMWin->eEnd==TK_FOLLOWING ){
+ regEnd = ++pParse->nMem;
+ }
+
+ /* If this is not a "ROWS BETWEEN ..." frame, then allocate arrays of
+ ** registers to store copies of the ORDER BY expressions (peer values)
+ ** for the main loop, and for each cursor (start, current and end). */
+ if( pMWin->eFrmType!=TK_ROWS ){
+ int nPeer = (pOrderBy ? pOrderBy->nExpr : 0);
+ regNewPeer = regNew + pMWin->nBufferCol;
+ if( pMWin->pPartition ) regNewPeer += pMWin->pPartition->nExpr;
+ regPeer = pParse->nMem+1; pParse->nMem += nPeer;
+ s.start.reg = pParse->nMem+1; pParse->nMem += nPeer;
+ s.current.reg = pParse->nMem+1; pParse->nMem += nPeer;
+ s.end.reg = pParse->nMem+1; pParse->nMem += nPeer;
+ }
+
+ /* Load the column values for the row returned by the sub-select
+ ** into an array of registers starting at regNew. Assemble them into
+ ** a record in register regRecord. */
+ for(iInput=0; iInput<nInput; iInput++){
+ tdsqlite3VdbeAddOp3(v, OP_Column, csrInput, iInput, regNew+iInput);
+ }
+ tdsqlite3VdbeAddOp3(v, OP_MakeRecord, regNew, nInput, regRecord);
+
+ /* An input row has just been read into an array of registers starting
+ ** at regNew. If the window has a PARTITION clause, this block generates
+ ** VM code to check if the input row is the start of a new partition.
+ ** If so, it does an OP_Gosub to an address to be filled in later. The
+ ** address of the OP_Gosub is stored in local variable addrGosubFlush. */
+ if( pMWin->pPartition ){
+ int addr;
+ ExprList *pPart = pMWin->pPartition;
+ int nPart = pPart->nExpr;
+ int regNewPart = regNew + pMWin->nBufferCol;
+ KeyInfo *pKeyInfo = tdsqlite3KeyInfoFromExprList(pParse, pPart, 0, 0);
+
+ regFlushPart = ++pParse->nMem;
+ addr = tdsqlite3VdbeAddOp3(v, OP_Compare, regNewPart, pMWin->regPart, nPart);
+ tdsqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO);
+ tdsqlite3VdbeAddOp3(v, OP_Jump, addr+2, addr+4, addr+2);
+ VdbeCoverageEqNe(v);
+ addrGosubFlush = tdsqlite3VdbeAddOp1(v, OP_Gosub, regFlushPart);
+ VdbeComment((v, "call flush_partition"));
+ tdsqlite3VdbeAddOp3(v, OP_Copy, regNewPart, pMWin->regPart, nPart-1);
+ }
+
+ /* Insert the new row into the ephemeral table */
+ tdsqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, regRowid);
+ tdsqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, regRowid);
+ addrNe = tdsqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, regRowid);
+ VdbeCoverageNeverNull(v);
+
+ /* This block is run for the first row of each partition */
+ s.regArg = windowInitAccum(pParse, pMWin);
+
+ if( regStart ){
+ tdsqlite3ExprCode(pParse, pMWin->pStart, regStart);
+ windowCheckValue(pParse, regStart, 0 + (pMWin->eFrmType==TK_RANGE?3:0));
+ }
+ if( regEnd ){
+ tdsqlite3ExprCode(pParse, pMWin->pEnd, regEnd);
+ windowCheckValue(pParse, regEnd, 1 + (pMWin->eFrmType==TK_RANGE?3:0));
+ }
+
+ if( pMWin->eFrmType!=TK_RANGE && pMWin->eStart==pMWin->eEnd && regStart ){
+ int op = ((pMWin->eStart==TK_FOLLOWING) ? OP_Ge : OP_Le);
+ int addrGe = tdsqlite3VdbeAddOp3(v, op, regStart, 0, regEnd);
+ VdbeCoverageNeverNullIf(v, op==OP_Ge); /* NeverNull because bound <expr> */
+ VdbeCoverageNeverNullIf(v, op==OP_Le); /* values previously checked */
+ windowAggFinal(&s, 0);
+ tdsqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1);
+ VdbeCoverageNeverTaken(v);
+ windowReturnOneRow(&s);
+ tdsqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr);
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, lblWhereEnd);
+ tdsqlite3VdbeJumpHere(v, addrGe);
+ }
+ if( pMWin->eStart==TK_FOLLOWING && pMWin->eFrmType!=TK_RANGE && regEnd ){
+ assert( pMWin->eEnd==TK_FOLLOWING );
+ tdsqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regStart);
+ }
+
+ if( pMWin->eStart!=TK_UNBOUNDED ){
+ tdsqlite3VdbeAddOp2(v, OP_Rewind, s.start.csr, 1);
+ VdbeCoverageNeverTaken(v);
+ }
+ tdsqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1);
+ VdbeCoverageNeverTaken(v);
+ tdsqlite3VdbeAddOp2(v, OP_Rewind, s.end.csr, 1);
+ VdbeCoverageNeverTaken(v);
+ if( regPeer && pOrderBy ){
+ tdsqlite3VdbeAddOp3(v, OP_Copy, regNewPeer, regPeer, pOrderBy->nExpr-1);
+ tdsqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.start.reg, pOrderBy->nExpr-1);
+ tdsqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.current.reg, pOrderBy->nExpr-1);
+ tdsqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.end.reg, pOrderBy->nExpr-1);
+ }
+
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, lblWhereEnd);
+
+ tdsqlite3VdbeJumpHere(v, addrNe);
+
+ /* Beginning of the block executed for the second and subsequent rows. */
+ if( regPeer ){
+ windowIfNewPeer(pParse, pOrderBy, regNewPeer, regPeer, lblWhereEnd);
+ }
+ if( pMWin->eStart==TK_FOLLOWING ){
+ windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
+ if( pMWin->eEnd!=TK_UNBOUNDED ){
+ if( pMWin->eFrmType==TK_RANGE ){
+ int lbl = tdsqlite3VdbeMakeLabel(pParse);
+ int addrNext = tdsqlite3VdbeCurrentAddr(v);
+ windowCodeRangeTest(&s, OP_Ge, s.current.csr, regEnd, s.end.csr, lbl);
+ windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+ windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, addrNext);
+ tdsqlite3VdbeResolveLabel(v, lbl);
+ }else{
+ windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 0);
+ windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+ }
+ }
+ }else
+ if( pMWin->eEnd==TK_PRECEDING ){
+ int bRPS = (pMWin->eStart==TK_PRECEDING && pMWin->eFrmType==TK_RANGE);
+ windowCodeOp(&s, WINDOW_AGGSTEP, regEnd, 0);
+ if( bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+ windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
+ if( !bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+ }else{
+ int addr = 0;
+ windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
+ if( pMWin->eEnd!=TK_UNBOUNDED ){
+ if( pMWin->eFrmType==TK_RANGE ){
+ int lbl = 0;
+ addr = tdsqlite3VdbeCurrentAddr(v);
+ if( regEnd ){
+ lbl = tdsqlite3VdbeMakeLabel(pParse);
+ windowCodeRangeTest(&s, OP_Ge, s.current.csr, regEnd, s.end.csr, lbl);
+ }
+ windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
+ windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+ if( regEnd ){
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, addr);
+ tdsqlite3VdbeResolveLabel(v, lbl);
+ }
+ }else{
+ if( regEnd ){
+ addr = tdsqlite3VdbeAddOp3(v, OP_IfPos, regEnd, 0, 1);
+ VdbeCoverage(v);
+ }
+ windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
+ windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+ if( regEnd ) tdsqlite3VdbeJumpHere(v, addr);
+ }
+ }
+ }
+
+ /* End of the main input loop */
+ tdsqlite3VdbeResolveLabel(v, lblWhereEnd);
+ tdsqlite3WhereEnd(pWInfo);
+
+ /* Fall through */
+ if( pMWin->pPartition ){
+ addrInteger = tdsqlite3VdbeAddOp2(v, OP_Integer, 0, regFlushPart);
+ tdsqlite3VdbeJumpHere(v, addrGosubFlush);
+ }
+
+ addrEmpty = tdsqlite3VdbeAddOp1(v, OP_Rewind, csrWrite);
+ VdbeCoverage(v);
+ if( pMWin->eEnd==TK_PRECEDING ){
+ int bRPS = (pMWin->eStart==TK_PRECEDING && pMWin->eFrmType==TK_RANGE);
+ windowCodeOp(&s, WINDOW_AGGSTEP, regEnd, 0);
+ if( bRPS ) windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+ windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 0);
+ }else if( pMWin->eStart==TK_FOLLOWING ){
+ int addrStart;
+ int addrBreak1;
+ int addrBreak2;
+ int addrBreak3;
+ windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
+ if( pMWin->eFrmType==TK_RANGE ){
+ addrStart = tdsqlite3VdbeCurrentAddr(v);
+ addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1);
+ addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1);
+ }else
+ if( pMWin->eEnd==TK_UNBOUNDED ){
+ addrStart = tdsqlite3VdbeCurrentAddr(v);
+ addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regStart, 1);
+ addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, 0, 1);
+ }else{
+ assert( pMWin->eEnd==TK_FOLLOWING );
+ addrStart = tdsqlite3VdbeCurrentAddr(v);
+ addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 1);
+ addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1);
+ }
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart);
+ tdsqlite3VdbeJumpHere(v, addrBreak2);
+ addrStart = tdsqlite3VdbeCurrentAddr(v);
+ addrBreak3 = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1);
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart);
+ tdsqlite3VdbeJumpHere(v, addrBreak1);
+ tdsqlite3VdbeJumpHere(v, addrBreak3);
+ }else{
+ int addrBreak;
+ int addrStart;
+ windowCodeOp(&s, WINDOW_AGGSTEP, 0, 0);
+ addrStart = tdsqlite3VdbeCurrentAddr(v);
+ addrBreak = windowCodeOp(&s, WINDOW_RETURN_ROW, 0, 1);
+ windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 0);
+ tdsqlite3VdbeAddOp2(v, OP_Goto, 0, addrStart);
+ tdsqlite3VdbeJumpHere(v, addrBreak);
+ }
+ tdsqlite3VdbeJumpHere(v, addrEmpty);
+
+ tdsqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr);
+ if( pMWin->pPartition ){
+ if( pMWin->regStartRowid ){
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 1, pMWin->regStartRowid);
+ tdsqlite3VdbeAddOp2(v, OP_Integer, 0, pMWin->regEndRowid);
+ }
+ tdsqlite3VdbeChangeP1(v, addrInteger, tdsqlite3VdbeCurrentAddr(v));
+ tdsqlite3VdbeAddOp1(v, OP_Return, regFlushPart);
+ }
+}
+
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+
+/************** End of window.c **********************************************/
/************** Begin file parse.c *******************************************/
/*
** 2000-05-29
@@ -135947,6 +155968,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
** input grammar file:
*/
/* #include <stdio.h> */
+/* #include <assert.h> */
/************ Begin %include sections from the grammar ************************/
/* #include "sqliteInt.h" */
@@ -135963,25 +155985,29 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
#define yytestcase(X) testcase(X)
/*
-** Indicate that sqlite3ParserFree() will never be called with a null
+** Indicate that tdsqlite3ParserFree() will never be called with a null
** pointer.
*/
#define YYPARSEFREENEVERNULL 1
/*
-** Alternative datatype for the argument to the malloc() routine passed
-** into sqlite3ParserAlloc(). The default is size_t.
+** In the amalgamation, the parse.c file generated by lemon and the
+** tokenize.c file are concatenated. In that case, tdsqlite3RunParser()
+** has access to the the size of the yyParser object and so the parser
+** engine can be allocated from stack. In that case, only the
+** tdsqlite3ParserInit() and tdsqlite3ParserFinalize() routines are invoked
+** and the tdsqlite3ParserAlloc() and tdsqlite3ParserFree() routines can be
+** omitted.
*/
-#define YYMALLOCARGTYPE u64
+#ifdef SQLITE_AMALGAMATION
+# define tdsqlite3Parser_ENGINEALWAYSONSTACK 1
+#endif
/*
-** An instance of this structure holds information about the
-** LIMIT clause of a SELECT statement.
+** Alternative datatype for the argument to the malloc() routine passed
+** into tdsqlite3ParserAlloc(). The default is size_t.
*/
-struct LimitVal {
- Expr *pLimit; /* The LIMIT expression. NULL if there is no limit */
- Expr *pOffset; /* The OFFSET expression. NULL if there is none */
-};
+#define YYMALLOCARGTYPE u64
/*
** An instance of the following structure describes the event of a
@@ -135994,13 +156020,16 @@ struct LimitVal {
*/
struct TrigEvent { int a; IdList * b; };
+struct FrameBound { int eType; Expr *pExpr; };
+
/*
** Disable lookaside memory allocation for objects that might be
** shared across database connections.
*/
static void disableLookaside(Parse *pParse){
+ tdsqlite3 *db = pParse->db;
pParse->disableLookaside++;
- pParse->db->lookaside.bDisable++;
+ DisableLookaside;
}
@@ -136010,6 +156039,7 @@ static void disableLookaside(Parse *pParse){
** SQLITE_LIMIT_COMPOUND_SELECT.
*/
static void parserDoubleLinkSelect(Parse *pParse, Select *p){
+ assert( p!=0 );
if( p->pPrior ){
Select *pNext = 0, *pLoop;
int mxSelect, cnt = 0;
@@ -136021,106 +156051,59 @@ static void disableLookaside(Parse *pParse){
(mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 &&
cnt>mxSelect
){
- sqlite3ErrorMsg(pParse, "too many terms in compound SELECT");
+ tdsqlite3ErrorMsg(pParse, "too many terms in compound SELECT");
}
}
}
- /* This is a utility routine used to set the ExprSpan.zStart and
- ** ExprSpan.zEnd values of pOut so that the span covers the complete
- ** range of text beginning with pStart and going to the end of pEnd.
- */
- static void spanSet(ExprSpan *pOut, Token *pStart, Token *pEnd){
- pOut->zStart = pStart->z;
- pOut->zEnd = &pEnd->z[pEnd->n];
- }
/* Construct a new Expr object from a single identifier. Use the
** new Expr to populate pOut. Set the span of pOut to be the identifier
** that created the expression.
*/
- static void spanExpr(ExprSpan *pOut, Parse *pParse, int op, Token t){
- Expr *p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)+t.n+1);
+ static Expr *tokenExpr(Parse *pParse, int op, Token t){
+ Expr *p = tdsqlite3DbMallocRawNN(pParse->db, sizeof(Expr)+t.n+1);
if( p ){
- memset(p, 0, sizeof(Expr));
+ /* memset(p, 0, sizeof(Expr)); */
p->op = (u8)op;
+ p->affExpr = 0;
p->flags = EP_Leaf;
p->iAgg = -1;
+ p->pLeft = p->pRight = 0;
+ p->x.pList = 0;
+ p->pAggInfo = 0;
+ p->y.pTab = 0;
+ p->op2 = 0;
+ p->iTable = 0;
+ p->iColumn = 0;
p->u.zToken = (char*)&p[1];
memcpy(p->u.zToken, t.z, t.n);
p->u.zToken[t.n] = 0;
- if( sqlite3Isquote(p->u.zToken[0]) ){
- if( p->u.zToken[0]=='"' ) p->flags |= EP_DblQuoted;
- sqlite3Dequote(p->u.zToken);
+ if( tdsqlite3Isquote(p->u.zToken[0]) ){
+ tdsqlite3DequoteExpr(p);
}
#if SQLITE_MAX_EXPR_DEPTH>0
p->nHeight = 1;
#endif
+ if( IN_RENAME_OBJECT ){
+ return (Expr*)tdsqlite3RenameTokenMap(pParse, (void*)p, &t);
+ }
}
- pOut->pExpr = p;
- pOut->zStart = t.z;
- pOut->zEnd = &t.z[t.n];
- }
-
- /* This routine constructs a binary expression node out of two ExprSpan
- ** objects and uses the result to populate a new ExprSpan object.
- */
- static void spanBinaryExpr(
- Parse *pParse, /* The parsing context. Errors accumulate here */
- int op, /* The binary operation */
- ExprSpan *pLeft, /* The left operand, and output */
- ExprSpan *pRight /* The right operand */
- ){
- pLeft->pExpr = sqlite3PExpr(pParse, op, pLeft->pExpr, pRight->pExpr, 0);
- pLeft->zEnd = pRight->zEnd;
- }
-
- /* If doNot is true, then add a TK_NOT Expr-node wrapper around the
- ** outside of *ppExpr.
- */
- static void exprNot(Parse *pParse, int doNot, ExprSpan *pSpan){
- if( doNot ){
- pSpan->pExpr = sqlite3PExpr(pParse, TK_NOT, pSpan->pExpr, 0, 0);
- }
+ return p;
}
- /* Construct an expression node for a unary postfix operator
- */
- static void spanUnaryPostfix(
- Parse *pParse, /* Parsing context to record errors */
- int op, /* The operator */
- ExprSpan *pOperand, /* The operand, and output */
- Token *pPostOp /* The operand token for setting the span */
- ){
- pOperand->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0, 0);
- pOperand->zEnd = &pPostOp->z[pPostOp->n];
- }
/* A routine to convert a binary TK_IS or TK_ISNOT expression into a
** unary TK_ISNULL or TK_NOTNULL expression. */
static void binaryToUnaryIfNull(Parse *pParse, Expr *pY, Expr *pA, int op){
- sqlite3 *db = pParse->db;
- if( pA && pY && pY->op==TK_NULL ){
+ tdsqlite3 *db = pParse->db;
+ if( pA && pY && pY->op==TK_NULL && !IN_RENAME_OBJECT ){
pA->op = (u8)op;
- sqlite3ExprDelete(db, pA->pRight);
+ tdsqlite3ExprDelete(db, pA->pRight);
pA->pRight = 0;
}
}
- /* Construct an expression node for a unary prefix operator
- */
- static void spanUnaryPrefix(
- ExprSpan *pOut, /* Write the new expression node here */
- Parse *pParse, /* Parsing context to record errors */
- int op, /* The operator */
- ExprSpan *pOperand, /* The operand */
- Token *pPreOp /* The operand token for setting the span */
- ){
- pOut->zStart = pPreOp->z;
- pOut->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0, 0);
- pOut->zEnd = pOperand->zEnd;
- }
-
/* Add a single new term to an ExprList that is used to store a
** list of identifiers. Report an error if the ID list contains
** a COLLATE clause or an ASC or DESC keyword, except ignore the
@@ -136133,16 +156116,20 @@ static void disableLookaside(Parse *pParse){
int hasCollate,
int sortOrder
){
- ExprList *p = sqlite3ExprListAppend(pParse, pPrior, 0);
+ ExprList *p = tdsqlite3ExprListAppend(pParse, pPrior, 0);
if( (hasCollate || sortOrder!=SQLITE_SO_UNDEFINED)
&& pParse->db->init.busy==0
){
- sqlite3ErrorMsg(pParse, "syntax error after column name \"%.*s\"",
+ tdsqlite3ErrorMsg(pParse, "syntax error after column name \"%.*s\"",
pIdToken->n, pIdToken->z);
}
- sqlite3ExprListSetName(pParse, p, pIdToken, 1);
+ tdsqlite3ExprListSetName(pParse, p, pIdToken, 1);
return p;
}
+
+#if TK_SPAN>255
+# error too many tokens in the grammar
+#endif
/**************** End of %include directives **********************************/
/* These constants specify the various numeric values for terminal symbols
** in a format understandable to "makeheaders". This section is blank unless
@@ -136166,7 +156153,7 @@ static void disableLookaside(Parse *pParse){
** YYACTIONTYPE is the data type used for "action codes" - numbers
** that indicate what to do in response to the next
** token.
-** sqlite3ParserTOKENTYPE is the data type used for minor type for terminal
+** tdsqlite3ParserTOKENTYPE is the data type used for minor type for terminal
** symbols. Background: A "minor type" is a semantic
** value associated with a terminal or non-terminal
** symbols. For example, for an "ID" terminal symbol,
@@ -136177,70 +156164,86 @@ static void disableLookaside(Parse *pParse){
** symbols.
** YYMINORTYPE is the data type used for all minor types.
** This is typically a union of many types, one of
-** which is sqlite3ParserTOKENTYPE. The entry in the union
+** which is tdsqlite3ParserTOKENTYPE. The entry in the union
** for terminal symbols is called "yy0".
** YYSTACKDEPTH is the maximum depth of the parser's stack. If
** zero the stack is dynamically sized using realloc()
-** sqlite3ParserARG_SDECL A static variable declaration for the %extra_argument
-** sqlite3ParserARG_PDECL A parameter declaration for the %extra_argument
-** sqlite3ParserARG_STORE Code to store %extra_argument into yypParser
-** sqlite3ParserARG_FETCH Code to extract %extra_argument from yypParser
+** tdsqlite3ParserARG_SDECL A static variable declaration for the %extra_argument
+** tdsqlite3ParserARG_PDECL A parameter declaration for the %extra_argument
+** tdsqlite3ParserARG_PARAM Code to pass %extra_argument as a subroutine parameter
+** tdsqlite3ParserARG_STORE Code to store %extra_argument into yypParser
+** tdsqlite3ParserARG_FETCH Code to extract %extra_argument from yypParser
+** tdsqlite3ParserCTX_* As tdsqlite3ParserARG_ except for %extra_context
** YYERRORSYMBOL is the code number of the error symbol. If not
** defined, then do no error processing.
** YYNSTATE the combined number of states.
** YYNRULE the number of rules in the grammar
+** YYNTOKEN Number of terminal symbols
** YY_MAX_SHIFT Maximum value for shift actions
** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions
** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions
-** YY_MIN_REDUCE Maximum value for reduce actions
** YY_ERROR_ACTION The yy_action[] code for syntax error
** YY_ACCEPT_ACTION The yy_action[] code for accept
** YY_NO_ACTION The yy_action[] code for no-op
+** YY_MIN_REDUCE Minimum value for reduce actions
+** YY_MAX_REDUCE Maximum value for reduce actions
*/
#ifndef INTERFACE
# define INTERFACE 1
#endif
/************* Begin control #defines *****************************************/
-#define YYCODETYPE unsigned char
-#define YYNOCODE 252
+#define YYCODETYPE unsigned short int
+#define YYNOCODE 310
#define YYACTIONTYPE unsigned short int
-#define YYWILDCARD 96
-#define sqlite3ParserTOKENTYPE Token
+#define YYWILDCARD 100
+#define tdsqlite3ParserTOKENTYPE Token
typedef union {
int yyinit;
- sqlite3ParserTOKENTYPE yy0;
- Expr* yy72;
- TriggerStep* yy145;
- ExprList* yy148;
- SrcList* yy185;
- ExprSpan yy190;
- int yy194;
- Select* yy243;
- IdList* yy254;
- With* yy285;
- struct TrigEvent yy332;
- struct LimitVal yy354;
- struct {int value; int mask;} yy497;
+ tdsqlite3ParserTOKENTYPE yy0;
+ SrcList* yy47;
+ u8 yy58;
+ struct FrameBound yy77;
+ With* yy131;
+ int yy192;
+ Expr* yy202;
+ struct {int value; int mask;} yy207;
+ struct TrigEvent yy230;
+ ExprList* yy242;
+ Window* yy303;
+ Upsert* yy318;
+ const char* yy436;
+ TriggerStep* yy447;
+ Select* yy539;
+ IdList* yy600;
} YYMINORTYPE;
#ifndef YYSTACKDEPTH
#define YYSTACKDEPTH 100
#endif
-#define sqlite3ParserARG_SDECL Parse *pParse;
-#define sqlite3ParserARG_PDECL ,Parse *pParse
-#define sqlite3ParserARG_FETCH Parse *pParse = yypParser->pParse
-#define sqlite3ParserARG_STORE yypParser->pParse = pParse
+#define tdsqlite3ParserARG_SDECL
+#define tdsqlite3ParserARG_PDECL
+#define tdsqlite3ParserARG_PARAM
+#define tdsqlite3ParserARG_FETCH
+#define tdsqlite3ParserARG_STORE
+#define tdsqlite3ParserCTX_SDECL Parse *pParse;
+#define tdsqlite3ParserCTX_PDECL ,Parse *pParse
+#define tdsqlite3ParserCTX_PARAM ,pParse
+#define tdsqlite3ParserCTX_FETCH Parse *pParse=yypParser->pParse;
+#define tdsqlite3ParserCTX_STORE yypParser->pParse=pParse;
#define YYFALLBACK 1
-#define YYNSTATE 456
-#define YYNRULE 332
-#define YY_MAX_SHIFT 455
-#define YY_MIN_SHIFTREDUCE 668
-#define YY_MAX_SHIFTREDUCE 999
-#define YY_MIN_REDUCE 1000
-#define YY_MAX_REDUCE 1331
-#define YY_ERROR_ACTION 1332
-#define YY_ACCEPT_ACTION 1333
-#define YY_NO_ACTION 1334
+#define YYNSTATE 551
+#define YYNRULE 385
+#define YYNRULE_WITH_ACTION 325
+#define YYNTOKEN 181
+#define YY_MAX_SHIFT 550
+#define YY_MIN_SHIFTREDUCE 801
+#define YY_MAX_SHIFTREDUCE 1185
+#define YY_ERROR_ACTION 1186
+#define YY_ACCEPT_ACTION 1187
+#define YY_NO_ACTION 1188
+#define YY_MIN_REDUCE 1189
+#define YY_MAX_REDUCE 1573
/************* End control #defines *******************************************/
+#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])))
/* Define the yytestcase() macro to be a no-op if is not already defined
** otherwise.
@@ -136269,9 +156272,6 @@ typedef union {
** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then
** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE.
**
-** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE
-** and YY_MAX_REDUCE
-**
** N == YY_ERROR_ACTION A syntax error has occurred.
**
** N == YY_ACCEPT_ACTION The parser accepts its input.
@@ -136279,25 +156279,22 @@ typedef union {
** N == YY_NO_ACTION No such action. Denotes unused
** slots in the yy_action[] table.
**
+** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE
+** and YY_MAX_REDUCE
+**
** The action table is constructed as a single large table named yy_action[].
** Given state S and lookahead X, the action is computed as either:
**
** (A) N = yy_action[ yy_shift_ofst[S] + X ]
** (B) N = yy_default[S]
**
-** The (A) formula is preferred. The B formula is used instead if:
-** (1) The yy_shift_ofst[S]+X value is out of range, or
-** (2) yy_lookahead[yy_shift_ofst[S]+X] is not equal to X, or
-** (3) yy_shift_ofst[S] equal YY_SHIFT_USE_DFLT.
-** (Implementation note: YY_SHIFT_USE_DFLT is chosen so that
-** YY_SHIFT_USE_DFLT+X will be out of range for all possible lookaheads X.
-** Hence only tests (1) and (2) need to be evaluated.)
+** The (A) formula is preferred. The B formula is used instead if
+** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X.
**
** The formulas above are 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 yy_shift_ofst[] array.
**
** The following are the tables generated in this section:
**
@@ -136311,463 +156308,583 @@ typedef union {
** yy_default[] Default action for each state.
**
*********** Begin parsing tables **********************************************/
-#define YY_ACTTAB_COUNT (1567)
+#define YY_ACTTAB_COUNT (1958)
static const YYACTIONTYPE yy_action[] = {
- /* 0 */ 325, 832, 351, 825, 5, 203, 203, 819, 99, 100,
- /* 10 */ 90, 842, 842, 854, 857, 846, 846, 97, 97, 98,
- /* 20 */ 98, 98, 98, 301, 96, 96, 96, 96, 95, 95,
- /* 30 */ 94, 94, 94, 93, 351, 325, 977, 977, 824, 824,
- /* 40 */ 826, 947, 354, 99, 100, 90, 842, 842, 854, 857,
- /* 50 */ 846, 846, 97, 97, 98, 98, 98, 98, 338, 96,
- /* 60 */ 96, 96, 96, 95, 95, 94, 94, 94, 93, 351,
- /* 70 */ 95, 95, 94, 94, 94, 93, 351, 791, 977, 977,
- /* 80 */ 325, 94, 94, 94, 93, 351, 792, 75, 99, 100,
- /* 90 */ 90, 842, 842, 854, 857, 846, 846, 97, 97, 98,
- /* 100 */ 98, 98, 98, 450, 96, 96, 96, 96, 95, 95,
- /* 110 */ 94, 94, 94, 93, 351, 1333, 155, 155, 2, 325,
- /* 120 */ 275, 146, 132, 52, 52, 93, 351, 99, 100, 90,
- /* 130 */ 842, 842, 854, 857, 846, 846, 97, 97, 98, 98,
- /* 140 */ 98, 98, 101, 96, 96, 96, 96, 95, 95, 94,
- /* 150 */ 94, 94, 93, 351, 958, 958, 325, 268, 428, 413,
- /* 160 */ 411, 61, 752, 752, 99, 100, 90, 842, 842, 854,
- /* 170 */ 857, 846, 846, 97, 97, 98, 98, 98, 98, 60,
- /* 180 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93,
- /* 190 */ 351, 325, 270, 329, 273, 277, 959, 960, 250, 99,
- /* 200 */ 100, 90, 842, 842, 854, 857, 846, 846, 97, 97,
- /* 210 */ 98, 98, 98, 98, 301, 96, 96, 96, 96, 95,
- /* 220 */ 95, 94, 94, 94, 93, 351, 325, 938, 1326, 698,
- /* 230 */ 706, 1326, 242, 412, 99, 100, 90, 842, 842, 854,
- /* 240 */ 857, 846, 846, 97, 97, 98, 98, 98, 98, 347,
- /* 250 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93,
- /* 260 */ 351, 325, 938, 1327, 384, 699, 1327, 381, 379, 99,
- /* 270 */ 100, 90, 842, 842, 854, 857, 846, 846, 97, 97,
- /* 280 */ 98, 98, 98, 98, 701, 96, 96, 96, 96, 95,
- /* 290 */ 95, 94, 94, 94, 93, 351, 325, 92, 89, 178,
- /* 300 */ 833, 936, 373, 700, 99, 100, 90, 842, 842, 854,
- /* 310 */ 857, 846, 846, 97, 97, 98, 98, 98, 98, 375,
- /* 320 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93,
- /* 330 */ 351, 325, 1276, 947, 354, 818, 936, 739, 739, 99,
- /* 340 */ 100, 90, 842, 842, 854, 857, 846, 846, 97, 97,
- /* 350 */ 98, 98, 98, 98, 230, 96, 96, 96, 96, 95,
- /* 360 */ 95, 94, 94, 94, 93, 351, 325, 969, 227, 92,
- /* 370 */ 89, 178, 373, 300, 99, 100, 90, 842, 842, 854,
- /* 380 */ 857, 846, 846, 97, 97, 98, 98, 98, 98, 921,
- /* 390 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93,
- /* 400 */ 351, 325, 449, 447, 447, 447, 147, 737, 737, 99,
- /* 410 */ 100, 90, 842, 842, 854, 857, 846, 846, 97, 97,
- /* 420 */ 98, 98, 98, 98, 296, 96, 96, 96, 96, 95,
- /* 430 */ 95, 94, 94, 94, 93, 351, 325, 419, 231, 958,
- /* 440 */ 958, 158, 25, 422, 99, 100, 90, 842, 842, 854,
- /* 450 */ 857, 846, 846, 97, 97, 98, 98, 98, 98, 450,
- /* 460 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93,
- /* 470 */ 351, 443, 224, 224, 420, 958, 958, 962, 325, 52,
- /* 480 */ 52, 959, 960, 176, 415, 78, 99, 100, 90, 842,
- /* 490 */ 842, 854, 857, 846, 846, 97, 97, 98, 98, 98,
- /* 500 */ 98, 379, 96, 96, 96, 96, 95, 95, 94, 94,
- /* 510 */ 94, 93, 351, 325, 428, 418, 298, 959, 960, 962,
- /* 520 */ 81, 99, 88, 90, 842, 842, 854, 857, 846, 846,
- /* 530 */ 97, 97, 98, 98, 98, 98, 717, 96, 96, 96,
- /* 540 */ 96, 95, 95, 94, 94, 94, 93, 351, 325, 843,
- /* 550 */ 843, 855, 858, 996, 318, 343, 379, 100, 90, 842,
- /* 560 */ 842, 854, 857, 846, 846, 97, 97, 98, 98, 98,
- /* 570 */ 98, 450, 96, 96, 96, 96, 95, 95, 94, 94,
- /* 580 */ 94, 93, 351, 325, 350, 350, 350, 260, 377, 340,
- /* 590 */ 929, 52, 52, 90, 842, 842, 854, 857, 846, 846,
- /* 600 */ 97, 97, 98, 98, 98, 98, 361, 96, 96, 96,
- /* 610 */ 96, 95, 95, 94, 94, 94, 93, 351, 86, 445,
- /* 620 */ 847, 3, 1203, 361, 360, 378, 344, 813, 958, 958,
- /* 630 */ 1300, 86, 445, 729, 3, 212, 169, 287, 405, 282,
- /* 640 */ 404, 199, 232, 450, 300, 760, 83, 84, 280, 245,
- /* 650 */ 262, 365, 251, 85, 352, 352, 92, 89, 178, 83,
- /* 660 */ 84, 242, 412, 52, 52, 448, 85, 352, 352, 246,
- /* 670 */ 959, 960, 194, 455, 670, 402, 399, 398, 448, 243,
- /* 680 */ 221, 114, 434, 776, 361, 450, 397, 268, 747, 224,
- /* 690 */ 224, 132, 132, 198, 832, 434, 452, 451, 428, 427,
- /* 700 */ 819, 415, 734, 713, 132, 52, 52, 832, 268, 452,
- /* 710 */ 451, 734, 194, 819, 363, 402, 399, 398, 450, 1271,
- /* 720 */ 1271, 23, 958, 958, 86, 445, 397, 3, 228, 429,
- /* 730 */ 895, 824, 824, 826, 827, 19, 203, 720, 52, 52,
- /* 740 */ 428, 408, 439, 249, 824, 824, 826, 827, 19, 229,
- /* 750 */ 403, 153, 83, 84, 761, 177, 241, 450, 721, 85,
- /* 760 */ 352, 352, 120, 157, 959, 960, 58, 977, 409, 355,
- /* 770 */ 330, 448, 268, 428, 430, 320, 790, 32, 32, 86,
- /* 780 */ 445, 776, 3, 341, 98, 98, 98, 98, 434, 96,
- /* 790 */ 96, 96, 96, 95, 95, 94, 94, 94, 93, 351,
- /* 800 */ 832, 120, 452, 451, 813, 887, 819, 83, 84, 977,
- /* 810 */ 813, 132, 410, 920, 85, 352, 352, 132, 407, 789,
- /* 820 */ 958, 958, 92, 89, 178, 917, 448, 262, 370, 261,
- /* 830 */ 82, 914, 80, 262, 370, 261, 776, 824, 824, 826,
- /* 840 */ 827, 19, 934, 434, 96, 96, 96, 96, 95, 95,
- /* 850 */ 94, 94, 94, 93, 351, 832, 74, 452, 451, 958,
- /* 860 */ 958, 819, 959, 960, 120, 92, 89, 178, 945, 2,
- /* 870 */ 918, 965, 268, 1, 976, 76, 445, 762, 3, 708,
- /* 880 */ 901, 901, 387, 958, 958, 757, 919, 371, 740, 778,
- /* 890 */ 756, 257, 824, 824, 826, 827, 19, 417, 741, 450,
- /* 900 */ 24, 959, 960, 83, 84, 369, 958, 958, 177, 226,
- /* 910 */ 85, 352, 352, 885, 315, 314, 313, 215, 311, 10,
- /* 920 */ 10, 683, 448, 349, 348, 959, 960, 909, 777, 157,
- /* 930 */ 120, 958, 958, 337, 776, 416, 711, 310, 450, 434,
- /* 940 */ 450, 321, 450, 791, 103, 200, 175, 450, 959, 960,
- /* 950 */ 908, 832, 792, 452, 451, 9, 9, 819, 10, 10,
- /* 960 */ 52, 52, 51, 51, 180, 716, 248, 10, 10, 171,
- /* 970 */ 170, 167, 339, 959, 960, 247, 984, 702, 702, 450,
- /* 980 */ 715, 233, 686, 982, 889, 983, 182, 914, 824, 824,
- /* 990 */ 826, 827, 19, 183, 256, 423, 132, 181, 394, 10,
- /* 1000 */ 10, 889, 891, 749, 958, 958, 917, 268, 985, 198,
- /* 1010 */ 985, 349, 348, 425, 415, 299, 817, 832, 326, 825,
- /* 1020 */ 120, 332, 133, 819, 268, 98, 98, 98, 98, 91,
- /* 1030 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93,
- /* 1040 */ 351, 157, 810, 371, 382, 359, 959, 960, 358, 268,
- /* 1050 */ 450, 918, 368, 324, 824, 824, 826, 450, 709, 450,
- /* 1060 */ 264, 380, 889, 450, 877, 746, 253, 919, 255, 433,
- /* 1070 */ 36, 36, 234, 450, 234, 120, 269, 37, 37, 12,
- /* 1080 */ 12, 334, 272, 27, 27, 450, 330, 118, 450, 162,
- /* 1090 */ 742, 280, 450, 38, 38, 450, 985, 356, 985, 450,
- /* 1100 */ 709, 1210, 450, 132, 450, 39, 39, 450, 40, 40,
- /* 1110 */ 450, 362, 41, 41, 450, 42, 42, 450, 254, 28,
- /* 1120 */ 28, 450, 29, 29, 31, 31, 450, 43, 43, 450,
- /* 1130 */ 44, 44, 450, 714, 45, 45, 450, 11, 11, 767,
- /* 1140 */ 450, 46, 46, 450, 268, 450, 105, 105, 450, 47,
- /* 1150 */ 47, 450, 48, 48, 450, 237, 33, 33, 450, 172,
- /* 1160 */ 49, 49, 450, 50, 50, 34, 34, 274, 122, 122,
- /* 1170 */ 450, 123, 123, 450, 124, 124, 450, 898, 56, 56,
- /* 1180 */ 450, 897, 35, 35, 450, 267, 450, 817, 450, 817,
- /* 1190 */ 106, 106, 450, 53, 53, 385, 107, 107, 450, 817,
- /* 1200 */ 108, 108, 817, 450, 104, 104, 121, 121, 119, 119,
- /* 1210 */ 450, 117, 112, 112, 450, 276, 450, 225, 111, 111,
- /* 1220 */ 450, 730, 450, 109, 109, 450, 673, 674, 675, 912,
- /* 1230 */ 110, 110, 317, 998, 55, 55, 57, 57, 692, 331,
- /* 1240 */ 54, 54, 26, 26, 696, 30, 30, 317, 937, 197,
- /* 1250 */ 196, 195, 335, 281, 336, 446, 331, 745, 689, 436,
- /* 1260 */ 440, 444, 120, 72, 386, 223, 175, 345, 757, 933,
- /* 1270 */ 20, 286, 319, 756, 815, 372, 374, 202, 202, 202,
- /* 1280 */ 263, 395, 285, 74, 208, 21, 696, 719, 718, 884,
- /* 1290 */ 120, 120, 120, 120, 120, 754, 278, 828, 77, 74,
- /* 1300 */ 726, 727, 785, 783, 880, 202, 999, 208, 894, 893,
- /* 1310 */ 894, 893, 694, 816, 763, 116, 774, 1290, 431, 432,
- /* 1320 */ 302, 999, 390, 303, 823, 697, 691, 680, 159, 289,
- /* 1330 */ 679, 884, 681, 952, 291, 218, 293, 7, 316, 828,
- /* 1340 */ 173, 805, 259, 364, 252, 911, 376, 713, 295, 435,
- /* 1350 */ 308, 168, 955, 993, 135, 400, 990, 284, 882, 881,
- /* 1360 */ 205, 928, 926, 59, 333, 62, 144, 156, 130, 72,
- /* 1370 */ 802, 366, 367, 393, 137, 185, 189, 160, 139, 383,
- /* 1380 */ 67, 896, 140, 141, 142, 148, 389, 812, 775, 266,
- /* 1390 */ 219, 190, 154, 391, 913, 876, 271, 406, 191, 322,
- /* 1400 */ 682, 733, 192, 342, 732, 724, 731, 711, 723, 421,
- /* 1410 */ 705, 71, 323, 6, 204, 771, 288, 79, 297, 346,
- /* 1420 */ 772, 704, 290, 283, 703, 770, 292, 294, 967, 239,
- /* 1430 */ 769, 102, 862, 438, 426, 240, 424, 442, 73, 213,
- /* 1440 */ 688, 238, 22, 453, 953, 214, 217, 216, 454, 677,
- /* 1450 */ 676, 671, 753, 125, 115, 235, 126, 669, 353, 166,
- /* 1460 */ 127, 244, 179, 357, 306, 304, 305, 307, 113, 892,
- /* 1470 */ 327, 890, 811, 328, 134, 128, 136, 138, 743, 258,
- /* 1480 */ 907, 184, 143, 129, 910, 186, 63, 64, 145, 187,
- /* 1490 */ 906, 65, 8, 66, 13, 188, 202, 899, 265, 149,
- /* 1500 */ 987, 388, 150, 685, 161, 392, 285, 193, 279, 396,
- /* 1510 */ 151, 401, 68, 14, 15, 722, 69, 236, 831, 131,
- /* 1520 */ 830, 860, 70, 751, 16, 414, 755, 4, 174, 220,
- /* 1530 */ 222, 784, 201, 152, 779, 77, 74, 17, 18, 875,
- /* 1540 */ 861, 859, 916, 864, 915, 207, 206, 942, 163, 437,
- /* 1550 */ 948, 943, 164, 209, 1002, 441, 863, 165, 210, 829,
- /* 1560 */ 695, 87, 312, 211, 1292, 1291, 309,
+ /* 0 */ 544, 1220, 544, 449, 1258, 544, 1237, 544, 114, 111,
+ /* 10 */ 211, 544, 1535, 544, 1258, 521, 114, 111, 211, 390,
+ /* 20 */ 1230, 342, 42, 42, 42, 42, 1223, 42, 42, 71,
+ /* 30 */ 71, 935, 1222, 71, 71, 71, 71, 1460, 1491, 936,
+ /* 40 */ 818, 451, 6, 121, 122, 112, 1163, 1163, 1004, 1007,
+ /* 50 */ 997, 997, 119, 119, 120, 120, 120, 120, 1541, 390,
+ /* 60 */ 1356, 1515, 550, 2, 1191, 194, 526, 434, 143, 291,
+ /* 70 */ 526, 136, 526, 369, 261, 502, 272, 383, 1271, 525,
+ /* 80 */ 501, 491, 164, 121, 122, 112, 1163, 1163, 1004, 1007,
+ /* 90 */ 997, 997, 119, 119, 120, 120, 120, 120, 1356, 440,
+ /* 100 */ 1512, 118, 118, 118, 118, 117, 117, 116, 116, 116,
+ /* 110 */ 115, 422, 266, 266, 266, 266, 1496, 356, 1498, 433,
+ /* 120 */ 355, 1496, 515, 522, 1483, 541, 1112, 541, 1112, 390,
+ /* 130 */ 403, 241, 208, 114, 111, 211, 98, 290, 535, 221,
+ /* 140 */ 1027, 118, 118, 118, 118, 117, 117, 116, 116, 116,
+ /* 150 */ 115, 422, 1140, 121, 122, 112, 1163, 1163, 1004, 1007,
+ /* 160 */ 997, 997, 119, 119, 120, 120, 120, 120, 404, 426,
+ /* 170 */ 117, 117, 116, 116, 116, 115, 422, 1416, 466, 123,
+ /* 180 */ 118, 118, 118, 118, 117, 117, 116, 116, 116, 115,
+ /* 190 */ 422, 116, 116, 116, 115, 422, 538, 538, 538, 390,
+ /* 200 */ 503, 120, 120, 120, 120, 113, 1049, 1140, 1141, 1142,
+ /* 210 */ 1049, 118, 118, 118, 118, 117, 117, 116, 116, 116,
+ /* 220 */ 115, 422, 1459, 121, 122, 112, 1163, 1163, 1004, 1007,
+ /* 230 */ 997, 997, 119, 119, 120, 120, 120, 120, 390, 442,
+ /* 240 */ 314, 83, 461, 81, 357, 380, 1140, 80, 118, 118,
+ /* 250 */ 118, 118, 117, 117, 116, 116, 116, 115, 422, 179,
+ /* 260 */ 432, 422, 121, 122, 112, 1163, 1163, 1004, 1007, 997,
+ /* 270 */ 997, 119, 119, 120, 120, 120, 120, 432, 431, 266,
+ /* 280 */ 266, 118, 118, 118, 118, 117, 117, 116, 116, 116,
+ /* 290 */ 115, 422, 541, 1107, 901, 504, 1140, 114, 111, 211,
+ /* 300 */ 1429, 1140, 1141, 1142, 206, 489, 1107, 390, 447, 1107,
+ /* 310 */ 543, 328, 120, 120, 120, 120, 298, 1429, 1431, 17,
+ /* 320 */ 118, 118, 118, 118, 117, 117, 116, 116, 116, 115,
+ /* 330 */ 422, 121, 122, 112, 1163, 1163, 1004, 1007, 997, 997,
+ /* 340 */ 119, 119, 120, 120, 120, 120, 390, 1356, 432, 1140,
+ /* 350 */ 480, 1140, 1141, 1142, 994, 994, 1005, 1008, 443, 118,
+ /* 360 */ 118, 118, 118, 117, 117, 116, 116, 116, 115, 422,
+ /* 370 */ 121, 122, 112, 1163, 1163, 1004, 1007, 997, 997, 119,
+ /* 380 */ 119, 120, 120, 120, 120, 1052, 1052, 463, 1429, 118,
+ /* 390 */ 118, 118, 118, 117, 117, 116, 116, 116, 115, 422,
+ /* 400 */ 1140, 449, 544, 1424, 1140, 1141, 1142, 233, 964, 1140,
+ /* 410 */ 479, 476, 475, 171, 358, 390, 164, 405, 412, 840,
+ /* 420 */ 474, 164, 185, 332, 71, 71, 1241, 998, 118, 118,
+ /* 430 */ 118, 118, 117, 117, 116, 116, 116, 115, 422, 121,
+ /* 440 */ 122, 112, 1163, 1163, 1004, 1007, 997, 997, 119, 119,
+ /* 450 */ 120, 120, 120, 120, 390, 1140, 1141, 1142, 833, 12,
+ /* 460 */ 313, 507, 163, 354, 1140, 1141, 1142, 114, 111, 211,
+ /* 470 */ 506, 290, 535, 544, 276, 180, 290, 535, 121, 122,
+ /* 480 */ 112, 1163, 1163, 1004, 1007, 997, 997, 119, 119, 120,
+ /* 490 */ 120, 120, 120, 343, 482, 71, 71, 118, 118, 118,
+ /* 500 */ 118, 117, 117, 116, 116, 116, 115, 422, 1140, 209,
+ /* 510 */ 409, 521, 1140, 1107, 1569, 376, 252, 269, 340, 485,
+ /* 520 */ 335, 484, 238, 390, 511, 362, 1107, 1125, 331, 1107,
+ /* 530 */ 191, 407, 286, 32, 455, 441, 118, 118, 118, 118,
+ /* 540 */ 117, 117, 116, 116, 116, 115, 422, 121, 122, 112,
+ /* 550 */ 1163, 1163, 1004, 1007, 997, 997, 119, 119, 120, 120,
+ /* 560 */ 120, 120, 390, 1140, 1141, 1142, 985, 1140, 1141, 1142,
+ /* 570 */ 1140, 233, 490, 1490, 479, 476, 475, 6, 163, 544,
+ /* 580 */ 510, 544, 115, 422, 474, 5, 121, 122, 112, 1163,
+ /* 590 */ 1163, 1004, 1007, 997, 997, 119, 119, 120, 120, 120,
+ /* 600 */ 120, 13, 13, 13, 13, 118, 118, 118, 118, 117,
+ /* 610 */ 117, 116, 116, 116, 115, 422, 401, 500, 406, 544,
+ /* 620 */ 1484, 542, 1140, 890, 890, 1140, 1141, 1142, 1471, 1140,
+ /* 630 */ 275, 390, 806, 807, 808, 969, 420, 420, 420, 16,
+ /* 640 */ 16, 55, 55, 1240, 118, 118, 118, 118, 117, 117,
+ /* 650 */ 116, 116, 116, 115, 422, 121, 122, 112, 1163, 1163,
+ /* 660 */ 1004, 1007, 997, 997, 119, 119, 120, 120, 120, 120,
+ /* 670 */ 390, 1187, 1, 1, 550, 2, 1191, 1140, 1141, 1142,
+ /* 680 */ 194, 291, 896, 136, 1140, 1141, 1142, 895, 519, 1490,
+ /* 690 */ 1271, 3, 378, 6, 121, 122, 112, 1163, 1163, 1004,
+ /* 700 */ 1007, 997, 997, 119, 119, 120, 120, 120, 120, 856,
+ /* 710 */ 544, 922, 544, 118, 118, 118, 118, 117, 117, 116,
+ /* 720 */ 116, 116, 115, 422, 266, 266, 1090, 1567, 1140, 549,
+ /* 730 */ 1567, 1191, 13, 13, 13, 13, 291, 541, 136, 390,
+ /* 740 */ 483, 419, 418, 964, 342, 1271, 466, 408, 857, 279,
+ /* 750 */ 140, 221, 118, 118, 118, 118, 117, 117, 116, 116,
+ /* 760 */ 116, 115, 422, 121, 122, 112, 1163, 1163, 1004, 1007,
+ /* 770 */ 997, 997, 119, 119, 120, 120, 120, 120, 544, 266,
+ /* 780 */ 266, 426, 390, 1140, 1141, 1142, 1170, 828, 1170, 466,
+ /* 790 */ 429, 145, 541, 1144, 399, 313, 437, 301, 836, 1488,
+ /* 800 */ 71, 71, 410, 6, 1088, 471, 221, 100, 112, 1163,
+ /* 810 */ 1163, 1004, 1007, 997, 997, 119, 119, 120, 120, 120,
+ /* 820 */ 120, 118, 118, 118, 118, 117, 117, 116, 116, 116,
+ /* 830 */ 115, 422, 237, 1423, 544, 449, 426, 287, 984, 544,
+ /* 840 */ 236, 235, 234, 828, 97, 527, 427, 1263, 1263, 1144,
+ /* 850 */ 492, 306, 428, 836, 975, 544, 71, 71, 974, 1239,
+ /* 860 */ 544, 51, 51, 300, 118, 118, 118, 118, 117, 117,
+ /* 870 */ 116, 116, 116, 115, 422, 194, 103, 70, 70, 266,
+ /* 880 */ 266, 544, 71, 71, 266, 266, 30, 389, 342, 974,
+ /* 890 */ 974, 976, 541, 526, 1107, 326, 390, 541, 493, 395,
+ /* 900 */ 1468, 195, 528, 13, 13, 1356, 240, 1107, 277, 280,
+ /* 910 */ 1107, 280, 303, 455, 305, 331, 390, 31, 188, 417,
+ /* 920 */ 121, 122, 112, 1163, 1163, 1004, 1007, 997, 997, 119,
+ /* 930 */ 119, 120, 120, 120, 120, 142, 390, 363, 455, 984,
+ /* 940 */ 121, 122, 112, 1163, 1163, 1004, 1007, 997, 997, 119,
+ /* 950 */ 119, 120, 120, 120, 120, 975, 321, 1140, 324, 974,
+ /* 960 */ 121, 110, 112, 1163, 1163, 1004, 1007, 997, 997, 119,
+ /* 970 */ 119, 120, 120, 120, 120, 462, 375, 1183, 118, 118,
+ /* 980 */ 118, 118, 117, 117, 116, 116, 116, 115, 422, 1140,
+ /* 990 */ 974, 974, 976, 304, 9, 364, 244, 360, 118, 118,
+ /* 1000 */ 118, 118, 117, 117, 116, 116, 116, 115, 422, 312,
+ /* 1010 */ 544, 342, 1140, 1141, 1142, 299, 290, 535, 118, 118,
+ /* 1020 */ 118, 118, 117, 117, 116, 116, 116, 115, 422, 1261,
+ /* 1030 */ 1261, 1161, 13, 13, 278, 419, 418, 466, 390, 921,
+ /* 1040 */ 260, 260, 289, 1167, 1140, 1141, 1142, 189, 1169, 266,
+ /* 1050 */ 266, 466, 388, 541, 1184, 544, 1168, 263, 144, 487,
+ /* 1060 */ 920, 544, 541, 122, 112, 1163, 1163, 1004, 1007, 997,
+ /* 1070 */ 997, 119, 119, 120, 120, 120, 120, 71, 71, 1140,
+ /* 1080 */ 1170, 1270, 1170, 13, 13, 896, 1068, 1161, 544, 466,
+ /* 1090 */ 895, 107, 536, 1489, 4, 1266, 1107, 6, 523, 1047,
+ /* 1100 */ 12, 1069, 1090, 1568, 311, 453, 1568, 518, 539, 1107,
+ /* 1110 */ 56, 56, 1107, 1487, 421, 1356, 1070, 6, 343, 285,
+ /* 1120 */ 118, 118, 118, 118, 117, 117, 116, 116, 116, 115,
+ /* 1130 */ 422, 423, 1269, 319, 1140, 1141, 1142, 876, 266, 266,
+ /* 1140 */ 1275, 107, 536, 533, 4, 1486, 293, 877, 1209, 6,
+ /* 1150 */ 210, 541, 541, 164, 1540, 494, 414, 865, 539, 267,
+ /* 1160 */ 267, 1212, 396, 509, 497, 204, 266, 266, 394, 529,
+ /* 1170 */ 8, 984, 541, 517, 544, 920, 456, 105, 105, 541,
+ /* 1180 */ 1088, 423, 266, 266, 106, 415, 423, 546, 545, 266,
+ /* 1190 */ 266, 974, 516, 533, 1371, 541, 15, 15, 266, 266,
+ /* 1200 */ 454, 1118, 541, 266, 266, 1068, 1370, 513, 290, 535,
+ /* 1210 */ 544, 541, 512, 97, 442, 314, 541, 544, 920, 125,
+ /* 1220 */ 1069, 984, 974, 974, 976, 977, 27, 105, 105, 399,
+ /* 1230 */ 341, 1509, 44, 44, 106, 1070, 423, 546, 545, 57,
+ /* 1240 */ 57, 974, 341, 1509, 107, 536, 544, 4, 460, 399,
+ /* 1250 */ 214, 1118, 457, 294, 375, 1089, 532, 297, 544, 537,
+ /* 1260 */ 396, 539, 290, 535, 104, 244, 102, 524, 58, 58,
+ /* 1270 */ 544, 109, 974, 974, 976, 977, 27, 1514, 1129, 425,
+ /* 1280 */ 59, 59, 270, 237, 423, 138, 95, 373, 373, 372,
+ /* 1290 */ 255, 370, 60, 60, 815, 1178, 533, 544, 273, 544,
+ /* 1300 */ 1161, 843, 387, 386, 544, 1307, 544, 215, 210, 296,
+ /* 1310 */ 513, 847, 544, 265, 208, 514, 1306, 295, 274, 61,
+ /* 1320 */ 61, 62, 62, 436, 984, 1160, 45, 45, 46, 46,
+ /* 1330 */ 105, 105, 1184, 920, 47, 47, 1474, 106, 544, 423,
+ /* 1340 */ 546, 545, 218, 544, 974, 935, 1085, 217, 544, 377,
+ /* 1350 */ 395, 107, 536, 936, 4, 156, 1161, 843, 158, 544,
+ /* 1360 */ 49, 49, 141, 544, 38, 50, 50, 544, 539, 307,
+ /* 1370 */ 63, 63, 544, 1448, 216, 974, 974, 976, 977, 27,
+ /* 1380 */ 444, 64, 64, 544, 1447, 65, 65, 544, 524, 14,
+ /* 1390 */ 14, 423, 458, 544, 66, 66, 310, 544, 316, 97,
+ /* 1400 */ 1034, 544, 961, 533, 268, 127, 127, 544, 391, 67,
+ /* 1410 */ 67, 544, 978, 290, 535, 52, 52, 513, 544, 68,
+ /* 1420 */ 68, 1294, 512, 69, 69, 397, 165, 855, 854, 53,
+ /* 1430 */ 53, 984, 966, 151, 151, 243, 430, 105, 105, 199,
+ /* 1440 */ 152, 152, 448, 1303, 106, 243, 423, 546, 545, 1129,
+ /* 1450 */ 425, 974, 320, 270, 862, 863, 1034, 220, 373, 373,
+ /* 1460 */ 372, 255, 370, 450, 323, 815, 243, 544, 978, 544,
+ /* 1470 */ 107, 536, 544, 4, 544, 938, 939, 325, 215, 1046,
+ /* 1480 */ 296, 1046, 974, 974, 976, 977, 27, 539, 295, 76,
+ /* 1490 */ 76, 54, 54, 327, 72, 72, 128, 128, 1503, 1254,
+ /* 1500 */ 107, 536, 544, 4, 1045, 544, 1045, 531, 1238, 544,
+ /* 1510 */ 423, 544, 315, 334, 544, 97, 544, 539, 217, 544,
+ /* 1520 */ 472, 1528, 533, 239, 73, 73, 156, 129, 129, 158,
+ /* 1530 */ 467, 130, 130, 126, 126, 344, 150, 150, 149, 149,
+ /* 1540 */ 423, 134, 134, 329, 1030, 216, 97, 239, 929, 345,
+ /* 1550 */ 984, 243, 533, 1315, 339, 544, 105, 105, 900, 1355,
+ /* 1560 */ 544, 1290, 258, 106, 338, 423, 546, 545, 544, 1301,
+ /* 1570 */ 974, 893, 99, 536, 109, 4, 544, 133, 133, 391,
+ /* 1580 */ 984, 197, 131, 131, 290, 535, 105, 105, 530, 539,
+ /* 1590 */ 132, 132, 1361, 106, 1219, 423, 546, 545, 75, 75,
+ /* 1600 */ 974, 974, 974, 976, 977, 27, 544, 430, 826, 1211,
+ /* 1610 */ 894, 139, 423, 109, 544, 1200, 1199, 1201, 1522, 544,
+ /* 1620 */ 201, 544, 11, 374, 533, 1287, 347, 349, 77, 77,
+ /* 1630 */ 1340, 974, 974, 976, 977, 27, 74, 74, 351, 213,
+ /* 1640 */ 435, 43, 43, 48, 48, 302, 477, 309, 1348, 382,
+ /* 1650 */ 353, 452, 984, 337, 1237, 1420, 1419, 205, 105, 105,
+ /* 1660 */ 192, 367, 193, 534, 1525, 106, 1178, 423, 546, 545,
+ /* 1670 */ 247, 167, 974, 270, 1467, 200, 1465, 1175, 373, 373,
+ /* 1680 */ 372, 255, 370, 398, 79, 815, 83, 82, 1425, 446,
+ /* 1690 */ 161, 177, 169, 95, 1337, 438, 172, 173, 215, 174,
+ /* 1700 */ 296, 175, 35, 974, 974, 976, 977, 27, 295, 1345,
+ /* 1710 */ 439, 470, 223, 36, 379, 445, 1414, 381, 459, 1351,
+ /* 1720 */ 181, 227, 88, 465, 259, 229, 1436, 318, 186, 468,
+ /* 1730 */ 322, 230, 384, 1202, 231, 486, 1257, 1256, 217, 411,
+ /* 1740 */ 1255, 1248, 90, 847, 206, 413, 156, 505, 1539, 158,
+ /* 1750 */ 1226, 1538, 283, 1508, 1227, 336, 385, 284, 1225, 496,
+ /* 1760 */ 1537, 1298, 94, 346, 348, 216, 1247, 499, 1299, 245,
+ /* 1770 */ 246, 1297, 416, 350, 1494, 124, 1493, 10, 524, 361,
+ /* 1780 */ 1400, 101, 96, 288, 508, 253, 1135, 1208, 34, 1296,
+ /* 1790 */ 547, 254, 256, 257, 392, 548, 1197, 1192, 359, 391,
+ /* 1800 */ 1280, 1279, 196, 365, 290, 535, 366, 352, 1452, 1322,
+ /* 1810 */ 1321, 1453, 153, 137, 281, 154, 802, 424, 155, 1451,
+ /* 1820 */ 1450, 198, 292, 202, 203, 78, 212, 430, 271, 135,
+ /* 1830 */ 1044, 1042, 958, 168, 219, 157, 170, 879, 308, 222,
+ /* 1840 */ 1058, 176, 159, 962, 400, 84, 402, 178, 85, 86,
+ /* 1850 */ 87, 166, 160, 393, 1061, 224, 225, 1057, 146, 18,
+ /* 1860 */ 226, 317, 1050, 1172, 243, 464, 182, 228, 37, 183,
+ /* 1870 */ 817, 469, 338, 232, 330, 481, 184, 89, 845, 19,
+ /* 1880 */ 20, 92, 473, 478, 333, 91, 162, 858, 147, 488,
+ /* 1890 */ 282, 1123, 148, 1010, 928, 1093, 39, 93, 40, 495,
+ /* 1900 */ 1094, 187, 498, 207, 262, 264, 923, 242, 1109, 109,
+ /* 1910 */ 1113, 1111, 1097, 33, 21, 1117, 520, 1025, 22, 23,
+ /* 1920 */ 24, 1116, 25, 190, 97, 1011, 1009, 26, 1013, 1067,
+ /* 1930 */ 248, 7, 1066, 249, 1014, 28, 41, 889, 979, 827,
+ /* 1940 */ 108, 29, 250, 540, 251, 1530, 371, 368, 1131, 1130,
+ /* 1950 */ 1188, 1188, 1188, 1188, 1188, 1188, 1188, 1529,
};
static const YYCODETYPE yy_lookahead[] = {
- /* 0 */ 19, 95, 53, 97, 22, 24, 24, 101, 27, 28,
- /* 10 */ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
- /* 20 */ 39, 40, 41, 152, 43, 44, 45, 46, 47, 48,
- /* 30 */ 49, 50, 51, 52, 53, 19, 55, 55, 132, 133,
- /* 40 */ 134, 1, 2, 27, 28, 29, 30, 31, 32, 33,
- /* 50 */ 34, 35, 36, 37, 38, 39, 40, 41, 187, 43,
- /* 60 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
- /* 70 */ 47, 48, 49, 50, 51, 52, 53, 61, 97, 97,
- /* 80 */ 19, 49, 50, 51, 52, 53, 70, 26, 27, 28,
- /* 90 */ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
- /* 100 */ 39, 40, 41, 152, 43, 44, 45, 46, 47, 48,
- /* 110 */ 49, 50, 51, 52, 53, 144, 145, 146, 147, 19,
- /* 120 */ 16, 22, 92, 172, 173, 52, 53, 27, 28, 29,
- /* 130 */ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
- /* 140 */ 40, 41, 81, 43, 44, 45, 46, 47, 48, 49,
- /* 150 */ 50, 51, 52, 53, 55, 56, 19, 152, 207, 208,
- /* 160 */ 115, 24, 117, 118, 27, 28, 29, 30, 31, 32,
- /* 170 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 79,
- /* 180 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 190 */ 53, 19, 88, 157, 90, 23, 97, 98, 193, 27,
- /* 200 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
- /* 210 */ 38, 39, 40, 41, 152, 43, 44, 45, 46, 47,
- /* 220 */ 48, 49, 50, 51, 52, 53, 19, 22, 23, 172,
- /* 230 */ 23, 26, 119, 120, 27, 28, 29, 30, 31, 32,
- /* 240 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 187,
- /* 250 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 260 */ 53, 19, 22, 23, 228, 23, 26, 231, 152, 27,
- /* 270 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
- /* 280 */ 38, 39, 40, 41, 172, 43, 44, 45, 46, 47,
- /* 290 */ 48, 49, 50, 51, 52, 53, 19, 221, 222, 223,
- /* 300 */ 23, 96, 152, 172, 27, 28, 29, 30, 31, 32,
- /* 310 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 152,
- /* 320 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 330 */ 53, 19, 0, 1, 2, 23, 96, 190, 191, 27,
- /* 340 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
- /* 350 */ 38, 39, 40, 41, 238, 43, 44, 45, 46, 47,
- /* 360 */ 48, 49, 50, 51, 52, 53, 19, 185, 218, 221,
- /* 370 */ 222, 223, 152, 152, 27, 28, 29, 30, 31, 32,
- /* 380 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 241,
- /* 390 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 400 */ 53, 19, 152, 168, 169, 170, 22, 190, 191, 27,
- /* 410 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
- /* 420 */ 38, 39, 40, 41, 152, 43, 44, 45, 46, 47,
- /* 430 */ 48, 49, 50, 51, 52, 53, 19, 19, 218, 55,
- /* 440 */ 56, 24, 22, 152, 27, 28, 29, 30, 31, 32,
- /* 450 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 152,
- /* 460 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 470 */ 53, 250, 194, 195, 56, 55, 56, 55, 19, 172,
- /* 480 */ 173, 97, 98, 152, 206, 138, 27, 28, 29, 30,
- /* 490 */ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
- /* 500 */ 41, 152, 43, 44, 45, 46, 47, 48, 49, 50,
- /* 510 */ 51, 52, 53, 19, 207, 208, 152, 97, 98, 97,
- /* 520 */ 138, 27, 28, 29, 30, 31, 32, 33, 34, 35,
- /* 530 */ 36, 37, 38, 39, 40, 41, 181, 43, 44, 45,
- /* 540 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 30,
- /* 550 */ 31, 32, 33, 247, 248, 19, 152, 28, 29, 30,
- /* 560 */ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
- /* 570 */ 41, 152, 43, 44, 45, 46, 47, 48, 49, 50,
- /* 580 */ 51, 52, 53, 19, 168, 169, 170, 238, 19, 53,
- /* 590 */ 152, 172, 173, 29, 30, 31, 32, 33, 34, 35,
- /* 600 */ 36, 37, 38, 39, 40, 41, 152, 43, 44, 45,
- /* 610 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 20,
- /* 620 */ 101, 22, 23, 169, 170, 56, 207, 85, 55, 56,
- /* 630 */ 23, 19, 20, 26, 22, 99, 100, 101, 102, 103,
- /* 640 */ 104, 105, 238, 152, 152, 210, 47, 48, 112, 152,
- /* 650 */ 108, 109, 110, 54, 55, 56, 221, 222, 223, 47,
- /* 660 */ 48, 119, 120, 172, 173, 66, 54, 55, 56, 152,
- /* 670 */ 97, 98, 99, 148, 149, 102, 103, 104, 66, 154,
- /* 680 */ 23, 156, 83, 26, 230, 152, 113, 152, 163, 194,
- /* 690 */ 195, 92, 92, 30, 95, 83, 97, 98, 207, 208,
- /* 700 */ 101, 206, 179, 180, 92, 172, 173, 95, 152, 97,
- /* 710 */ 98, 188, 99, 101, 219, 102, 103, 104, 152, 119,
- /* 720 */ 120, 196, 55, 56, 19, 20, 113, 22, 193, 163,
- /* 730 */ 11, 132, 133, 134, 135, 136, 24, 65, 172, 173,
- /* 740 */ 207, 208, 250, 152, 132, 133, 134, 135, 136, 193,
- /* 750 */ 78, 84, 47, 48, 49, 98, 199, 152, 86, 54,
- /* 760 */ 55, 56, 196, 152, 97, 98, 209, 55, 163, 244,
- /* 770 */ 107, 66, 152, 207, 208, 164, 175, 172, 173, 19,
- /* 780 */ 20, 124, 22, 111, 38, 39, 40, 41, 83, 43,
- /* 790 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
- /* 800 */ 95, 196, 97, 98, 85, 152, 101, 47, 48, 97,
- /* 810 */ 85, 92, 207, 193, 54, 55, 56, 92, 49, 175,
- /* 820 */ 55, 56, 221, 222, 223, 12, 66, 108, 109, 110,
- /* 830 */ 137, 163, 139, 108, 109, 110, 26, 132, 133, 134,
- /* 840 */ 135, 136, 152, 83, 43, 44, 45, 46, 47, 48,
- /* 850 */ 49, 50, 51, 52, 53, 95, 26, 97, 98, 55,
- /* 860 */ 56, 101, 97, 98, 196, 221, 222, 223, 146, 147,
- /* 870 */ 57, 171, 152, 22, 26, 19, 20, 49, 22, 179,
- /* 880 */ 108, 109, 110, 55, 56, 116, 73, 219, 75, 124,
- /* 890 */ 121, 152, 132, 133, 134, 135, 136, 163, 85, 152,
- /* 900 */ 232, 97, 98, 47, 48, 237, 55, 56, 98, 5,
- /* 910 */ 54, 55, 56, 193, 10, 11, 12, 13, 14, 172,
- /* 920 */ 173, 17, 66, 47, 48, 97, 98, 152, 124, 152,
- /* 930 */ 196, 55, 56, 186, 124, 152, 106, 160, 152, 83,
- /* 940 */ 152, 164, 152, 61, 22, 211, 212, 152, 97, 98,
- /* 950 */ 152, 95, 70, 97, 98, 172, 173, 101, 172, 173,
- /* 960 */ 172, 173, 172, 173, 60, 181, 62, 172, 173, 47,
- /* 970 */ 48, 123, 186, 97, 98, 71, 100, 55, 56, 152,
- /* 980 */ 181, 186, 21, 107, 152, 109, 82, 163, 132, 133,
- /* 990 */ 134, 135, 136, 89, 16, 207, 92, 93, 19, 172,
- /* 1000 */ 173, 169, 170, 195, 55, 56, 12, 152, 132, 30,
- /* 1010 */ 134, 47, 48, 186, 206, 225, 152, 95, 114, 97,
- /* 1020 */ 196, 245, 246, 101, 152, 38, 39, 40, 41, 42,
- /* 1030 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- /* 1040 */ 53, 152, 163, 219, 152, 141, 97, 98, 193, 152,
- /* 1050 */ 152, 57, 91, 164, 132, 133, 134, 152, 55, 152,
- /* 1060 */ 152, 237, 230, 152, 103, 193, 88, 73, 90, 75,
- /* 1070 */ 172, 173, 183, 152, 185, 196, 152, 172, 173, 172,
- /* 1080 */ 173, 217, 152, 172, 173, 152, 107, 22, 152, 24,
- /* 1090 */ 193, 112, 152, 172, 173, 152, 132, 242, 134, 152,
- /* 1100 */ 97, 140, 152, 92, 152, 172, 173, 152, 172, 173,
- /* 1110 */ 152, 100, 172, 173, 152, 172, 173, 152, 140, 172,
- /* 1120 */ 173, 152, 172, 173, 172, 173, 152, 172, 173, 152,
- /* 1130 */ 172, 173, 152, 152, 172, 173, 152, 172, 173, 213,
- /* 1140 */ 152, 172, 173, 152, 152, 152, 172, 173, 152, 172,
- /* 1150 */ 173, 152, 172, 173, 152, 210, 172, 173, 152, 26,
- /* 1160 */ 172, 173, 152, 172, 173, 172, 173, 152, 172, 173,
- /* 1170 */ 152, 172, 173, 152, 172, 173, 152, 59, 172, 173,
- /* 1180 */ 152, 63, 172, 173, 152, 193, 152, 152, 152, 152,
- /* 1190 */ 172, 173, 152, 172, 173, 77, 172, 173, 152, 152,
- /* 1200 */ 172, 173, 152, 152, 172, 173, 172, 173, 172, 173,
- /* 1210 */ 152, 22, 172, 173, 152, 152, 152, 22, 172, 173,
- /* 1220 */ 152, 152, 152, 172, 173, 152, 7, 8, 9, 163,
- /* 1230 */ 172, 173, 22, 23, 172, 173, 172, 173, 166, 167,
- /* 1240 */ 172, 173, 172, 173, 55, 172, 173, 22, 23, 108,
- /* 1250 */ 109, 110, 217, 152, 217, 166, 167, 163, 163, 163,
- /* 1260 */ 163, 163, 196, 130, 217, 211, 212, 217, 116, 23,
- /* 1270 */ 22, 101, 26, 121, 23, 23, 23, 26, 26, 26,
- /* 1280 */ 23, 23, 112, 26, 26, 37, 97, 100, 101, 55,
- /* 1290 */ 196, 196, 196, 196, 196, 23, 23, 55, 26, 26,
- /* 1300 */ 7, 8, 23, 152, 23, 26, 96, 26, 132, 132,
- /* 1310 */ 134, 134, 23, 152, 152, 26, 152, 122, 152, 191,
- /* 1320 */ 152, 96, 234, 152, 152, 152, 152, 152, 197, 210,
- /* 1330 */ 152, 97, 152, 152, 210, 233, 210, 198, 150, 97,
- /* 1340 */ 184, 201, 239, 214, 214, 201, 239, 180, 214, 227,
- /* 1350 */ 200, 198, 155, 67, 243, 176, 69, 175, 175, 175,
- /* 1360 */ 122, 159, 159, 240, 159, 240, 22, 220, 27, 130,
- /* 1370 */ 201, 18, 159, 18, 189, 158, 158, 220, 192, 159,
- /* 1380 */ 137, 236, 192, 192, 192, 189, 74, 189, 159, 235,
- /* 1390 */ 159, 158, 22, 177, 201, 201, 159, 107, 158, 177,
- /* 1400 */ 159, 174, 158, 76, 174, 182, 174, 106, 182, 125,
- /* 1410 */ 174, 107, 177, 22, 159, 216, 215, 137, 159, 53,
- /* 1420 */ 216, 176, 215, 174, 174, 216, 215, 215, 174, 229,
- /* 1430 */ 216, 129, 224, 177, 126, 229, 127, 177, 128, 25,
- /* 1440 */ 162, 226, 26, 161, 13, 153, 6, 153, 151, 151,
- /* 1450 */ 151, 151, 205, 165, 178, 178, 165, 4, 3, 22,
- /* 1460 */ 165, 142, 15, 94, 202, 204, 203, 201, 16, 23,
- /* 1470 */ 249, 23, 120, 249, 246, 111, 131, 123, 20, 16,
- /* 1480 */ 1, 125, 123, 111, 56, 64, 37, 37, 131, 122,
- /* 1490 */ 1, 37, 5, 37, 22, 107, 26, 80, 140, 80,
- /* 1500 */ 87, 72, 107, 20, 24, 19, 112, 105, 23, 79,
- /* 1510 */ 22, 79, 22, 22, 22, 58, 22, 79, 23, 68,
- /* 1520 */ 23, 23, 26, 116, 22, 26, 23, 22, 122, 23,
- /* 1530 */ 23, 56, 64, 22, 124, 26, 26, 64, 64, 23,
- /* 1540 */ 23, 23, 23, 11, 23, 22, 26, 23, 22, 24,
- /* 1550 */ 1, 23, 22, 26, 251, 24, 23, 22, 122, 23,
- /* 1560 */ 23, 22, 15, 122, 122, 122, 23,
+ /* 0 */ 189, 211, 189, 189, 218, 189, 220, 189, 267, 268,
+ /* 10 */ 269, 189, 210, 189, 228, 189, 267, 268, 269, 19,
+ /* 20 */ 218, 189, 211, 212, 211, 212, 211, 211, 212, 211,
+ /* 30 */ 212, 31, 211, 211, 212, 211, 212, 288, 300, 39,
+ /* 40 */ 21, 189, 304, 43, 44, 45, 46, 47, 48, 49,
+ /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 225, 19,
+ /* 60 */ 189, 183, 184, 185, 186, 189, 248, 263, 236, 191,
+ /* 70 */ 248, 193, 248, 197, 208, 257, 262, 201, 200, 257,
+ /* 80 */ 200, 257, 81, 43, 44, 45, 46, 47, 48, 49,
+ /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 189, 80,
+ /* 100 */ 189, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 110 */ 110, 111, 234, 235, 234, 235, 305, 306, 305, 118,
+ /* 120 */ 307, 305, 306, 297, 298, 247, 86, 247, 88, 19,
+ /* 130 */ 259, 251, 252, 267, 268, 269, 26, 136, 137, 261,
+ /* 140 */ 121, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 150 */ 110, 111, 59, 43, 44, 45, 46, 47, 48, 49,
+ /* 160 */ 50, 51, 52, 53, 54, 55, 56, 57, 259, 291,
+ /* 170 */ 105, 106, 107, 108, 109, 110, 111, 158, 189, 69,
+ /* 180 */ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ /* 190 */ 111, 107, 108, 109, 110, 111, 205, 206, 207, 19,
+ /* 200 */ 19, 54, 55, 56, 57, 58, 29, 114, 115, 116,
+ /* 210 */ 33, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 220 */ 110, 111, 233, 43, 44, 45, 46, 47, 48, 49,
+ /* 230 */ 50, 51, 52, 53, 54, 55, 56, 57, 19, 126,
+ /* 240 */ 127, 148, 65, 24, 214, 200, 59, 67, 101, 102,
+ /* 250 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 22,
+ /* 260 */ 189, 111, 43, 44, 45, 46, 47, 48, 49, 50,
+ /* 270 */ 51, 52, 53, 54, 55, 56, 57, 206, 207, 234,
+ /* 280 */ 235, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 290 */ 110, 111, 247, 76, 107, 114, 59, 267, 268, 269,
+ /* 300 */ 189, 114, 115, 116, 162, 163, 89, 19, 263, 92,
+ /* 310 */ 189, 23, 54, 55, 56, 57, 189, 206, 207, 22,
+ /* 320 */ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ /* 330 */ 111, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ /* 340 */ 52, 53, 54, 55, 56, 57, 19, 189, 277, 59,
+ /* 350 */ 23, 114, 115, 116, 46, 47, 48, 49, 61, 101,
+ /* 360 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ /* 370 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ /* 380 */ 53, 54, 55, 56, 57, 125, 126, 127, 277, 101,
+ /* 390 */ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ /* 400 */ 59, 189, 189, 276, 114, 115, 116, 117, 73, 59,
+ /* 410 */ 120, 121, 122, 72, 214, 19, 81, 259, 19, 23,
+ /* 420 */ 130, 81, 72, 24, 211, 212, 221, 119, 101, 102,
+ /* 430 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 43,
+ /* 440 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+ /* 450 */ 54, 55, 56, 57, 19, 114, 115, 116, 23, 208,
+ /* 460 */ 125, 248, 189, 189, 114, 115, 116, 267, 268, 269,
+ /* 470 */ 189, 136, 137, 189, 262, 22, 136, 137, 43, 44,
+ /* 480 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ /* 490 */ 55, 56, 57, 189, 95, 211, 212, 101, 102, 103,
+ /* 500 */ 104, 105, 106, 107, 108, 109, 110, 111, 59, 189,
+ /* 510 */ 111, 189, 59, 76, 294, 295, 117, 118, 119, 120,
+ /* 520 */ 121, 122, 123, 19, 87, 189, 89, 23, 129, 92,
+ /* 530 */ 279, 227, 248, 22, 189, 284, 101, 102, 103, 104,
+ /* 540 */ 105, 106, 107, 108, 109, 110, 111, 43, 44, 45,
+ /* 550 */ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
+ /* 560 */ 56, 57, 19, 114, 115, 116, 23, 114, 115, 116,
+ /* 570 */ 59, 117, 299, 300, 120, 121, 122, 304, 189, 189,
+ /* 580 */ 143, 189, 110, 111, 130, 22, 43, 44, 45, 46,
+ /* 590 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+ /* 600 */ 57, 211, 212, 211, 212, 101, 102, 103, 104, 105,
+ /* 610 */ 106, 107, 108, 109, 110, 111, 226, 189, 226, 189,
+ /* 620 */ 298, 132, 59, 134, 135, 114, 115, 116, 189, 59,
+ /* 630 */ 285, 19, 7, 8, 9, 23, 205, 206, 207, 211,
+ /* 640 */ 212, 211, 212, 221, 101, 102, 103, 104, 105, 106,
+ /* 650 */ 107, 108, 109, 110, 111, 43, 44, 45, 46, 47,
+ /* 660 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+ /* 670 */ 19, 181, 182, 183, 184, 185, 186, 114, 115, 116,
+ /* 680 */ 189, 191, 133, 193, 114, 115, 116, 138, 299, 300,
+ /* 690 */ 200, 22, 201, 304, 43, 44, 45, 46, 47, 48,
+ /* 700 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 35,
+ /* 710 */ 189, 141, 189, 101, 102, 103, 104, 105, 106, 107,
+ /* 720 */ 108, 109, 110, 111, 234, 235, 22, 23, 59, 184,
+ /* 730 */ 26, 186, 211, 212, 211, 212, 191, 247, 193, 19,
+ /* 740 */ 66, 105, 106, 73, 189, 200, 189, 226, 74, 226,
+ /* 750 */ 22, 261, 101, 102, 103, 104, 105, 106, 107, 108,
+ /* 760 */ 109, 110, 111, 43, 44, 45, 46, 47, 48, 49,
+ /* 770 */ 50, 51, 52, 53, 54, 55, 56, 57, 189, 234,
+ /* 780 */ 235, 291, 19, 114, 115, 116, 150, 59, 152, 189,
+ /* 790 */ 233, 236, 247, 59, 189, 125, 126, 127, 59, 300,
+ /* 800 */ 211, 212, 128, 304, 100, 19, 261, 156, 45, 46,
+ /* 810 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+ /* 820 */ 57, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+ /* 830 */ 110, 111, 46, 233, 189, 189, 291, 248, 99, 189,
+ /* 840 */ 125, 126, 127, 115, 26, 200, 289, 230, 231, 115,
+ /* 850 */ 200, 16, 189, 114, 115, 189, 211, 212, 119, 221,
+ /* 860 */ 189, 211, 212, 258, 101, 102, 103, 104, 105, 106,
+ /* 870 */ 107, 108, 109, 110, 111, 189, 156, 211, 212, 234,
+ /* 880 */ 235, 189, 211, 212, 234, 235, 22, 201, 189, 150,
+ /* 890 */ 151, 152, 247, 248, 76, 16, 19, 247, 248, 113,
+ /* 900 */ 189, 24, 257, 211, 212, 189, 26, 89, 262, 223,
+ /* 910 */ 92, 225, 77, 189, 79, 129, 19, 53, 226, 248,
+ /* 920 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ /* 930 */ 53, 54, 55, 56, 57, 236, 19, 271, 189, 99,
+ /* 940 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ /* 950 */ 53, 54, 55, 56, 57, 115, 77, 59, 79, 119,
+ /* 960 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ /* 970 */ 53, 54, 55, 56, 57, 259, 22, 23, 101, 102,
+ /* 980 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 59,
+ /* 990 */ 150, 151, 152, 158, 22, 244, 24, 246, 101, 102,
+ /* 1000 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 285,
+ /* 1010 */ 189, 189, 114, 115, 116, 200, 136, 137, 101, 102,
+ /* 1020 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 230,
+ /* 1030 */ 231, 59, 211, 212, 285, 105, 106, 189, 19, 141,
+ /* 1040 */ 234, 235, 239, 113, 114, 115, 116, 226, 118, 234,
+ /* 1050 */ 235, 189, 249, 247, 100, 189, 126, 23, 236, 107,
+ /* 1060 */ 26, 189, 247, 44, 45, 46, 47, 48, 49, 50,
+ /* 1070 */ 51, 52, 53, 54, 55, 56, 57, 211, 212, 59,
+ /* 1080 */ 150, 233, 152, 211, 212, 133, 12, 115, 189, 189,
+ /* 1090 */ 138, 19, 20, 300, 22, 233, 76, 304, 226, 11,
+ /* 1100 */ 208, 27, 22, 23, 200, 19, 26, 87, 36, 89,
+ /* 1110 */ 211, 212, 92, 300, 248, 189, 42, 304, 189, 250,
+ /* 1120 */ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ /* 1130 */ 111, 59, 200, 233, 114, 115, 116, 63, 234, 235,
+ /* 1140 */ 235, 19, 20, 71, 22, 300, 189, 73, 200, 304,
+ /* 1150 */ 116, 247, 247, 81, 23, 200, 227, 26, 36, 234,
+ /* 1160 */ 235, 203, 204, 143, 200, 26, 234, 235, 194, 200,
+ /* 1170 */ 48, 99, 247, 66, 189, 141, 284, 105, 106, 247,
+ /* 1180 */ 100, 59, 234, 235, 112, 259, 114, 115, 116, 234,
+ /* 1190 */ 235, 119, 85, 71, 266, 247, 211, 212, 234, 235,
+ /* 1200 */ 114, 94, 247, 234, 235, 12, 266, 85, 136, 137,
+ /* 1210 */ 189, 247, 90, 26, 126, 127, 247, 189, 26, 22,
+ /* 1220 */ 27, 99, 150, 151, 152, 153, 154, 105, 106, 189,
+ /* 1230 */ 302, 303, 211, 212, 112, 42, 114, 115, 116, 211,
+ /* 1240 */ 212, 119, 302, 303, 19, 20, 189, 22, 274, 189,
+ /* 1250 */ 15, 144, 278, 189, 22, 23, 63, 189, 189, 203,
+ /* 1260 */ 204, 36, 136, 137, 155, 24, 157, 143, 211, 212,
+ /* 1270 */ 189, 26, 150, 151, 152, 153, 154, 0, 1, 2,
+ /* 1280 */ 211, 212, 5, 46, 59, 161, 147, 10, 11, 12,
+ /* 1290 */ 13, 14, 211, 212, 17, 60, 71, 189, 258, 189,
+ /* 1300 */ 59, 59, 105, 106, 189, 189, 189, 30, 116, 32,
+ /* 1310 */ 85, 124, 189, 251, 252, 90, 189, 40, 258, 211,
+ /* 1320 */ 212, 211, 212, 189, 99, 26, 211, 212, 211, 212,
+ /* 1330 */ 105, 106, 100, 141, 211, 212, 189, 112, 189, 114,
+ /* 1340 */ 115, 116, 24, 189, 119, 31, 23, 70, 189, 26,
+ /* 1350 */ 113, 19, 20, 39, 22, 78, 115, 115, 81, 189,
+ /* 1360 */ 211, 212, 22, 189, 24, 211, 212, 189, 36, 189,
+ /* 1370 */ 211, 212, 189, 189, 97, 150, 151, 152, 153, 154,
+ /* 1380 */ 127, 211, 212, 189, 189, 211, 212, 189, 143, 211,
+ /* 1390 */ 212, 59, 189, 189, 211, 212, 23, 189, 189, 26,
+ /* 1400 */ 59, 189, 149, 71, 22, 211, 212, 189, 131, 211,
+ /* 1410 */ 212, 189, 59, 136, 137, 211, 212, 85, 189, 211,
+ /* 1420 */ 212, 253, 90, 211, 212, 292, 293, 118, 119, 211,
+ /* 1430 */ 212, 99, 23, 211, 212, 26, 159, 105, 106, 140,
+ /* 1440 */ 211, 212, 23, 189, 112, 26, 114, 115, 116, 1,
+ /* 1450 */ 2, 119, 189, 5, 7, 8, 115, 139, 10, 11,
+ /* 1460 */ 12, 13, 14, 23, 189, 17, 26, 189, 115, 189,
+ /* 1470 */ 19, 20, 189, 22, 189, 83, 84, 189, 30, 150,
+ /* 1480 */ 32, 152, 150, 151, 152, 153, 154, 36, 40, 211,
+ /* 1490 */ 212, 211, 212, 189, 211, 212, 211, 212, 309, 189,
+ /* 1500 */ 19, 20, 189, 22, 150, 189, 152, 231, 189, 189,
+ /* 1510 */ 59, 189, 23, 189, 189, 26, 189, 36, 70, 189,
+ /* 1520 */ 23, 139, 71, 26, 211, 212, 78, 211, 212, 81,
+ /* 1530 */ 281, 211, 212, 211, 212, 189, 211, 212, 211, 212,
+ /* 1540 */ 59, 211, 212, 23, 23, 97, 26, 26, 23, 189,
+ /* 1550 */ 99, 26, 71, 189, 119, 189, 105, 106, 107, 189,
+ /* 1560 */ 189, 189, 280, 112, 129, 114, 115, 116, 189, 189,
+ /* 1570 */ 119, 23, 19, 20, 26, 22, 189, 211, 212, 131,
+ /* 1580 */ 99, 237, 211, 212, 136, 137, 105, 106, 189, 36,
+ /* 1590 */ 211, 212, 189, 112, 189, 114, 115, 116, 211, 212,
+ /* 1600 */ 119, 150, 151, 152, 153, 154, 189, 159, 23, 189,
+ /* 1610 */ 23, 26, 59, 26, 189, 189, 189, 189, 189, 189,
+ /* 1620 */ 209, 189, 238, 187, 71, 250, 250, 250, 211, 212,
+ /* 1630 */ 241, 150, 151, 152, 153, 154, 211, 212, 250, 290,
+ /* 1640 */ 254, 211, 212, 211, 212, 254, 215, 286, 241, 241,
+ /* 1650 */ 254, 286, 99, 214, 220, 214, 214, 224, 105, 106,
+ /* 1660 */ 244, 240, 244, 273, 192, 112, 60, 114, 115, 116,
+ /* 1670 */ 139, 290, 119, 5, 196, 238, 196, 38, 10, 11,
+ /* 1680 */ 12, 13, 14, 196, 287, 17, 148, 287, 276, 113,
+ /* 1690 */ 43, 22, 229, 147, 241, 18, 232, 232, 30, 232,
+ /* 1700 */ 32, 232, 264, 150, 151, 152, 153, 154, 40, 265,
+ /* 1710 */ 196, 18, 195, 264, 241, 241, 241, 265, 196, 229,
+ /* 1720 */ 229, 195, 155, 62, 196, 195, 283, 282, 22, 216,
+ /* 1730 */ 196, 195, 216, 196, 195, 113, 213, 213, 70, 64,
+ /* 1740 */ 213, 222, 22, 124, 162, 111, 78, 142, 219, 81,
+ /* 1750 */ 215, 219, 275, 303, 213, 213, 216, 275, 213, 216,
+ /* 1760 */ 213, 256, 113, 255, 255, 97, 222, 216, 256, 196,
+ /* 1770 */ 91, 256, 82, 255, 308, 146, 308, 22, 143, 196,
+ /* 1780 */ 270, 155, 145, 272, 144, 25, 13, 199, 26, 256,
+ /* 1790 */ 198, 190, 190, 6, 296, 188, 188, 188, 244, 131,
+ /* 1800 */ 245, 245, 243, 242, 136, 137, 241, 255, 208, 260,
+ /* 1810 */ 260, 208, 202, 217, 217, 202, 4, 3, 202, 208,
+ /* 1820 */ 208, 22, 160, 209, 209, 208, 15, 159, 98, 16,
+ /* 1830 */ 23, 23, 137, 148, 24, 128, 140, 20, 16, 142,
+ /* 1840 */ 1, 140, 128, 149, 61, 53, 37, 148, 53, 53,
+ /* 1850 */ 53, 293, 128, 296, 114, 34, 139, 1, 5, 22,
+ /* 1860 */ 113, 158, 68, 75, 26, 41, 68, 139, 24, 113,
+ /* 1870 */ 20, 19, 129, 123, 23, 96, 22, 22, 59, 22,
+ /* 1880 */ 22, 147, 67, 67, 24, 22, 37, 28, 23, 22,
+ /* 1890 */ 67, 23, 23, 23, 114, 23, 22, 26, 22, 24,
+ /* 1900 */ 23, 22, 24, 139, 23, 23, 141, 34, 88, 26,
+ /* 1910 */ 75, 86, 23, 22, 34, 75, 24, 23, 34, 34,
+ /* 1920 */ 34, 93, 34, 26, 26, 23, 23, 34, 23, 23,
+ /* 1930 */ 26, 44, 23, 22, 11, 22, 22, 133, 23, 23,
+ /* 1940 */ 22, 22, 139, 26, 139, 139, 15, 23, 1, 1,
+ /* 1950 */ 310, 310, 310, 310, 310, 310, 310, 139, 310, 310,
+ /* 1960 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 1970 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 1980 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 1990 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2000 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2010 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2020 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2030 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2040 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2050 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2060 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2070 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2080 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2090 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2100 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2110 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2120 */ 310, 310, 310, 310, 310, 310, 310, 310, 310, 310,
+ /* 2130 */ 310, 310, 310, 310, 310, 310, 310, 310, 310,
};
-#define YY_SHIFT_USE_DFLT (1567)
-#define YY_SHIFT_COUNT (455)
-#define YY_SHIFT_MIN (-94)
-#define YY_SHIFT_MAX (1549)
-static const short yy_shift_ofst[] = {
- /* 0 */ 40, 599, 904, 612, 760, 760, 760, 760, 725, -19,
- /* 10 */ 16, 16, 100, 760, 760, 760, 760, 760, 760, 760,
- /* 20 */ 876, 876, 573, 542, 719, 600, 61, 137, 172, 207,
- /* 30 */ 242, 277, 312, 347, 382, 417, 459, 459, 459, 459,
- /* 40 */ 459, 459, 459, 459, 459, 459, 459, 459, 459, 459,
- /* 50 */ 459, 459, 459, 494, 459, 529, 564, 564, 705, 760,
- /* 60 */ 760, 760, 760, 760, 760, 760, 760, 760, 760, 760,
- /* 70 */ 760, 760, 760, 760, 760, 760, 760, 760, 760, 760,
- /* 80 */ 760, 760, 760, 760, 760, 760, 760, 760, 760, 760,
- /* 90 */ 856, 760, 760, 760, 760, 760, 760, 760, 760, 760,
- /* 100 */ 760, 760, 760, 760, 987, 746, 746, 746, 746, 746,
- /* 110 */ 801, 23, 32, 949, 961, 979, 964, 964, 949, 73,
- /* 120 */ 113, -51, 1567, 1567, 1567, 536, 536, 536, 99, 99,
- /* 130 */ 813, 813, 667, 205, 240, 949, 949, 949, 949, 949,
- /* 140 */ 949, 949, 949, 949, 949, 949, 949, 949, 949, 949,
- /* 150 */ 949, 949, 949, 949, 949, 332, 1011, 422, 422, 113,
- /* 160 */ 30, 30, 30, 30, 30, 30, 1567, 1567, 1567, 922,
- /* 170 */ -94, -94, 384, 613, 828, 420, 765, 804, 851, 949,
- /* 180 */ 949, 949, 949, 949, 949, 949, 949, 949, 949, 949,
- /* 190 */ 949, 949, 949, 949, 949, 672, 672, 672, 949, 949,
- /* 200 */ 657, 949, 949, 949, -18, 949, 949, 994, 949, 949,
- /* 210 */ 949, 949, 949, 949, 949, 949, 949, 949, 772, 1118,
- /* 220 */ 712, 712, 712, 810, 45, 769, 1219, 1133, 418, 418,
- /* 230 */ 569, 1133, 569, 830, 607, 663, 882, 418, 693, 882,
- /* 240 */ 882, 848, 1152, 1065, 1286, 1238, 1238, 1287, 1287, 1238,
- /* 250 */ 1344, 1341, 1239, 1353, 1353, 1353, 1353, 1238, 1355, 1239,
- /* 260 */ 1344, 1341, 1341, 1239, 1238, 1355, 1243, 1312, 1238, 1238,
- /* 270 */ 1355, 1370, 1238, 1355, 1238, 1355, 1370, 1290, 1290, 1290,
- /* 280 */ 1327, 1370, 1290, 1301, 1290, 1327, 1290, 1290, 1284, 1304,
- /* 290 */ 1284, 1304, 1284, 1304, 1284, 1304, 1238, 1391, 1238, 1280,
- /* 300 */ 1370, 1366, 1366, 1370, 1302, 1308, 1310, 1309, 1239, 1414,
- /* 310 */ 1416, 1431, 1431, 1440, 1440, 1440, 1440, 1567, 1567, 1567,
- /* 320 */ 1567, 1567, 1567, 1567, 1567, 519, 978, 1210, 1225, 104,
- /* 330 */ 1141, 1189, 1246, 1248, 1251, 1252, 1253, 1257, 1258, 1273,
- /* 340 */ 1003, 1187, 1293, 1170, 1272, 1279, 1234, 1281, 1176, 1177,
- /* 350 */ 1289, 1242, 1195, 1453, 1455, 1437, 1319, 1447, 1369, 1452,
- /* 360 */ 1446, 1448, 1352, 1345, 1364, 1354, 1458, 1356, 1463, 1479,
- /* 370 */ 1359, 1357, 1449, 1450, 1454, 1456, 1372, 1428, 1421, 1367,
- /* 380 */ 1489, 1487, 1472, 1388, 1358, 1417, 1470, 1419, 1413, 1429,
- /* 390 */ 1395, 1480, 1483, 1486, 1394, 1402, 1488, 1430, 1490, 1491,
- /* 400 */ 1485, 1492, 1432, 1457, 1494, 1438, 1451, 1495, 1497, 1498,
- /* 410 */ 1496, 1407, 1502, 1503, 1505, 1499, 1406, 1506, 1507, 1475,
- /* 420 */ 1468, 1511, 1410, 1509, 1473, 1510, 1474, 1516, 1509, 1517,
- /* 430 */ 1518, 1519, 1520, 1521, 1523, 1532, 1524, 1526, 1525, 1527,
- /* 440 */ 1528, 1530, 1531, 1527, 1533, 1535, 1536, 1537, 1539, 1436,
- /* 450 */ 1441, 1442, 1443, 1543, 1547, 1549,
+#define YY_SHIFT_COUNT (550)
+#define YY_SHIFT_MIN (0)
+#define YY_SHIFT_MAX (1948)
+static const unsigned short int yy_shift_ofst[] = {
+ /* 0 */ 1448, 1277, 1668, 1072, 1072, 340, 1122, 1225, 1332, 1481,
+ /* 10 */ 1481, 1481, 335, 0, 0, 180, 897, 1481, 1481, 1481,
+ /* 20 */ 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
+ /* 30 */ 930, 930, 1020, 1020, 290, 1, 340, 340, 340, 340,
+ /* 40 */ 340, 340, 40, 110, 219, 288, 327, 396, 435, 504,
+ /* 50 */ 543, 612, 651, 720, 877, 897, 897, 897, 897, 897,
+ /* 60 */ 897, 897, 897, 897, 897, 897, 897, 897, 897, 897,
+ /* 70 */ 897, 897, 897, 917, 897, 1019, 763, 763, 1451, 1481,
+ /* 80 */ 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
+ /* 90 */ 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
+ /* 100 */ 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
+ /* 110 */ 1481, 1481, 1553, 1481, 1481, 1481, 1481, 1481, 1481, 1481,
+ /* 120 */ 1481, 1481, 1481, 1481, 1481, 1481, 147, 258, 258, 258,
+ /* 130 */ 258, 258, 79, 65, 84, 449, 19, 786, 449, 636,
+ /* 140 */ 636, 449, 880, 880, 880, 880, 113, 142, 142, 472,
+ /* 150 */ 150, 1958, 1958, 399, 399, 399, 93, 237, 341, 237,
+ /* 160 */ 237, 1074, 1074, 437, 350, 704, 1080, 449, 449, 449,
+ /* 170 */ 449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
+ /* 180 */ 449, 449, 449, 449, 449, 449, 449, 449, 818, 818,
+ /* 190 */ 449, 1088, 217, 217, 734, 734, 1124, 1126, 1958, 1958,
+ /* 200 */ 1958, 739, 840, 840, 453, 454, 511, 187, 563, 570,
+ /* 210 */ 898, 669, 449, 449, 449, 449, 449, 449, 449, 449,
+ /* 220 */ 449, 670, 449, 449, 449, 449, 449, 449, 449, 449,
+ /* 230 */ 449, 449, 449, 449, 674, 674, 674, 449, 449, 449,
+ /* 240 */ 449, 1034, 449, 449, 449, 972, 1107, 449, 449, 1193,
+ /* 250 */ 449, 449, 449, 449, 449, 449, 449, 449, 260, 177,
+ /* 260 */ 489, 1241, 1241, 1241, 1241, 1192, 489, 489, 952, 1197,
+ /* 270 */ 625, 1235, 1139, 181, 181, 1086, 1139, 1139, 1086, 1187,
+ /* 280 */ 1131, 1237, 1314, 1314, 1314, 181, 1245, 1245, 1109, 1299,
+ /* 290 */ 549, 1340, 1606, 1531, 1531, 1639, 1639, 1531, 1538, 1576,
+ /* 300 */ 1669, 1647, 1546, 1677, 1677, 1677, 1677, 1531, 1693, 1546,
+ /* 310 */ 1546, 1576, 1669, 1647, 1647, 1546, 1531, 1693, 1567, 1661,
+ /* 320 */ 1531, 1693, 1706, 1531, 1693, 1531, 1693, 1706, 1622, 1622,
+ /* 330 */ 1622, 1675, 1720, 1720, 1706, 1622, 1619, 1622, 1675, 1622,
+ /* 340 */ 1622, 1582, 1706, 1634, 1634, 1706, 1605, 1649, 1605, 1649,
+ /* 350 */ 1605, 1649, 1605, 1649, 1531, 1679, 1679, 1690, 1690, 1629,
+ /* 360 */ 1635, 1755, 1531, 1626, 1629, 1637, 1640, 1546, 1760, 1762,
+ /* 370 */ 1773, 1773, 1787, 1787, 1787, 1958, 1958, 1958, 1958, 1958,
+ /* 380 */ 1958, 1958, 1958, 1958, 1958, 1958, 1958, 1958, 1958, 1958,
+ /* 390 */ 308, 835, 954, 1232, 879, 715, 728, 1323, 864, 1318,
+ /* 400 */ 1253, 1373, 297, 1409, 1419, 1440, 1489, 1497, 1520, 1242,
+ /* 410 */ 1309, 1447, 1435, 1341, 1521, 1525, 1392, 1548, 1329, 1354,
+ /* 420 */ 1585, 1587, 1353, 1382, 1812, 1814, 1799, 1662, 1811, 1730,
+ /* 430 */ 1813, 1807, 1808, 1695, 1685, 1707, 1810, 1696, 1817, 1697,
+ /* 440 */ 1822, 1839, 1701, 1694, 1714, 1783, 1809, 1699, 1792, 1795,
+ /* 450 */ 1796, 1797, 1724, 1740, 1821, 1717, 1856, 1853, 1837, 1747,
+ /* 460 */ 1703, 1794, 1838, 1798, 1788, 1824, 1728, 1756, 1844, 1850,
+ /* 470 */ 1852, 1743, 1750, 1854, 1815, 1855, 1857, 1851, 1858, 1816,
+ /* 480 */ 1819, 1860, 1779, 1859, 1863, 1823, 1849, 1865, 1734, 1867,
+ /* 490 */ 1868, 1869, 1870, 1871, 1872, 1874, 1875, 1877, 1876, 1878,
+ /* 500 */ 1764, 1881, 1882, 1780, 1873, 1879, 1765, 1883, 1880, 1884,
+ /* 510 */ 1885, 1886, 1820, 1835, 1825, 1887, 1840, 1828, 1888, 1889,
+ /* 520 */ 1891, 1892, 1897, 1898, 1893, 1894, 1883, 1902, 1903, 1905,
+ /* 530 */ 1906, 1904, 1909, 1911, 1923, 1913, 1914, 1915, 1916, 1918,
+ /* 540 */ 1919, 1917, 1804, 1803, 1805, 1806, 1818, 1924, 1931, 1947,
+ /* 550 */ 1948,
};
-#define YY_REDUCE_USE_DFLT (-130)
-#define YY_REDUCE_COUNT (324)
-#define YY_REDUCE_MIN (-129)
-#define YY_REDUCE_MAX (1300)
+#define YY_REDUCE_COUNT (389)
+#define YY_REDUCE_MIN (-262)
+#define YY_REDUCE_MAX (1617)
static const short yy_reduce_ofst[] = {
- /* 0 */ -29, 566, 525, 605, -49, 307, 491, 533, 668, 435,
- /* 10 */ 601, 644, 148, 747, 786, 795, 419, 788, 827, 790,
- /* 20 */ 454, 832, 889, 495, 824, 734, 76, 76, 76, 76,
- /* 30 */ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76,
- /* 40 */ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76,
- /* 50 */ 76, 76, 76, 76, 76, 76, 76, 76, 783, 898,
- /* 60 */ 905, 907, 911, 921, 933, 936, 940, 943, 947, 950,
- /* 70 */ 952, 955, 958, 962, 965, 969, 974, 977, 980, 984,
- /* 80 */ 988, 991, 993, 996, 999, 1002, 1006, 1010, 1018, 1021,
- /* 90 */ 1024, 1028, 1032, 1034, 1036, 1040, 1046, 1051, 1058, 1062,
- /* 100 */ 1064, 1068, 1070, 1073, 76, 76, 76, 76, 76, 76,
- /* 110 */ 76, 76, 76, 855, 36, 523, 235, 416, 777, 76,
- /* 120 */ 278, 76, 76, 76, 76, 700, 700, 700, 150, 220,
- /* 130 */ 147, 217, 221, 306, 306, 611, 5, 535, 556, 620,
- /* 140 */ 720, 872, 897, 116, 864, 349, 1035, 1037, 404, 1047,
- /* 150 */ 992, -129, 1050, 492, 62, 722, 879, 1072, 1089, 808,
- /* 160 */ 1066, 1094, 1095, 1096, 1097, 1098, 776, 1054, 557, 57,
- /* 170 */ 112, 131, 167, 182, 250, 272, 291, 331, 364, 438,
- /* 180 */ 497, 517, 591, 653, 690, 739, 775, 798, 892, 908,
- /* 190 */ 924, 930, 1015, 1063, 1069, 355, 784, 799, 981, 1101,
- /* 200 */ 926, 1151, 1161, 1162, 945, 1164, 1166, 1128, 1168, 1171,
- /* 210 */ 1172, 250, 1173, 1174, 1175, 1178, 1180, 1181, 1088, 1102,
- /* 220 */ 1119, 1124, 1126, 926, 1131, 1139, 1188, 1140, 1129, 1130,
- /* 230 */ 1103, 1144, 1107, 1179, 1156, 1167, 1182, 1134, 1122, 1183,
- /* 240 */ 1184, 1150, 1153, 1197, 1111, 1202, 1203, 1123, 1125, 1205,
- /* 250 */ 1147, 1185, 1169, 1186, 1190, 1191, 1192, 1213, 1217, 1193,
- /* 260 */ 1157, 1196, 1198, 1194, 1220, 1218, 1145, 1154, 1229, 1231,
- /* 270 */ 1233, 1216, 1237, 1240, 1241, 1244, 1222, 1227, 1230, 1232,
- /* 280 */ 1223, 1235, 1236, 1245, 1249, 1226, 1250, 1254, 1199, 1201,
- /* 290 */ 1204, 1207, 1209, 1211, 1214, 1212, 1255, 1208, 1259, 1215,
- /* 300 */ 1256, 1200, 1206, 1260, 1247, 1261, 1263, 1262, 1266, 1278,
- /* 310 */ 1282, 1292, 1294, 1297, 1298, 1299, 1300, 1221, 1224, 1228,
- /* 320 */ 1288, 1291, 1276, 1277, 1295,
+ /* 0 */ 490, -122, 545, 645, 650, -120, -189, -187, -184, -182,
+ /* 10 */ -178, -176, 45, 30, 200, -251, -134, 390, 392, 521,
+ /* 20 */ 523, 213, 692, 821, 284, 589, 872, 666, 671, 866,
+ /* 30 */ 71, 111, 273, 389, 686, 815, 904, 932, 948, 955,
+ /* 40 */ 964, 969, -259, -259, -259, -259, -259, -259, -259, -259,
+ /* 50 */ -259, -259, -259, -259, -259, -259, -259, -259, -259, -259,
+ /* 60 */ -259, -259, -259, -259, -259, -259, -259, -259, -259, -259,
+ /* 70 */ -259, -259, -259, -259, -259, -259, -259, -259, 428, 430,
+ /* 80 */ 899, 985, 1021, 1028, 1057, 1069, 1081, 1108, 1110, 1115,
+ /* 90 */ 1117, 1123, 1149, 1154, 1159, 1170, 1174, 1178, 1183, 1194,
+ /* 100 */ 1198, 1204, 1208, 1212, 1218, 1222, 1229, 1278, 1280, 1283,
+ /* 110 */ 1285, 1313, 1316, 1320, 1322, 1325, 1327, 1330, 1366, 1371,
+ /* 120 */ 1379, 1387, 1417, 1425, 1430, 1432, -259, -259, -259, -259,
+ /* 130 */ -259, -259, -259, -259, -259, 557, 974, -214, -174, -9,
+ /* 140 */ 431, -124, 806, 925, 806, 925, 251, 928, 940, -259,
+ /* 150 */ -259, -259, -259, -198, -198, -198, 127, -186, -168, 212,
+ /* 160 */ 646, 617, 799, -262, 555, 220, 220, 491, 605, 1040,
+ /* 170 */ 1060, 699, -11, 600, 848, 862, 345, -129, 724, -91,
+ /* 180 */ 158, 749, 716, 900, 304, 822, 929, 926, 499, 793,
+ /* 190 */ 322, 892, 813, 845, 958, 1056, 751, 905, 1133, 1062,
+ /* 200 */ 803, -210, -185, -179, -148, -167, -89, 121, 274, 281,
+ /* 210 */ 320, 336, 439, 663, 711, 957, 1064, 1068, 1116, 1127,
+ /* 220 */ 1134, -196, 1147, 1180, 1184, 1195, 1203, 1209, 1254, 1263,
+ /* 230 */ 1275, 1288, 1304, 1310, 205, 422, 638, 1319, 1324, 1346,
+ /* 240 */ 1360, 1168, 1364, 1370, 1372, 869, 1189, 1380, 1399, 1276,
+ /* 250 */ 1403, 121, 1405, 1420, 1426, 1427, 1428, 1429, 1249, 1282,
+ /* 260 */ 1344, 1375, 1376, 1377, 1388, 1168, 1344, 1344, 1384, 1411,
+ /* 270 */ 1436, 1349, 1389, 1386, 1391, 1361, 1407, 1408, 1365, 1431,
+ /* 280 */ 1433, 1434, 1439, 1441, 1442, 1396, 1416, 1418, 1390, 1421,
+ /* 290 */ 1437, 1472, 1381, 1478, 1480, 1397, 1400, 1487, 1412, 1444,
+ /* 300 */ 1438, 1463, 1453, 1464, 1465, 1467, 1469, 1514, 1517, 1473,
+ /* 310 */ 1474, 1452, 1449, 1490, 1491, 1475, 1522, 1526, 1443, 1445,
+ /* 320 */ 1528, 1530, 1513, 1534, 1536, 1537, 1539, 1516, 1523, 1524,
+ /* 330 */ 1527, 1519, 1529, 1532, 1540, 1541, 1535, 1542, 1544, 1545,
+ /* 340 */ 1547, 1450, 1543, 1477, 1482, 1551, 1505, 1508, 1512, 1509,
+ /* 350 */ 1515, 1518, 1533, 1552, 1573, 1466, 1468, 1549, 1550, 1555,
+ /* 360 */ 1554, 1510, 1583, 1511, 1556, 1559, 1561, 1565, 1588, 1592,
+ /* 370 */ 1601, 1602, 1607, 1608, 1609, 1498, 1557, 1558, 1610, 1600,
+ /* 380 */ 1603, 1611, 1612, 1613, 1596, 1597, 1614, 1615, 1617, 1616,
};
static const YYACTIONTYPE yy_default[] = {
- /* 0 */ 1281, 1271, 1271, 1271, 1203, 1203, 1203, 1203, 1271, 1096,
- /* 10 */ 1125, 1125, 1255, 1332, 1332, 1332, 1332, 1332, 1332, 1202,
- /* 20 */ 1332, 1332, 1332, 1332, 1271, 1100, 1131, 1332, 1332, 1332,
- /* 30 */ 1332, 1204, 1205, 1332, 1332, 1332, 1254, 1256, 1141, 1140,
- /* 40 */ 1139, 1138, 1237, 1112, 1136, 1129, 1133, 1204, 1198, 1199,
- /* 50 */ 1197, 1201, 1205, 1332, 1132, 1167, 1182, 1166, 1332, 1332,
- /* 60 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 70 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 80 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 90 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 100 */ 1332, 1332, 1332, 1332, 1176, 1181, 1188, 1180, 1177, 1169,
- /* 110 */ 1168, 1170, 1171, 1332, 1019, 1067, 1332, 1332, 1332, 1172,
- /* 120 */ 1332, 1173, 1185, 1184, 1183, 1262, 1289, 1288, 1332, 1332,
- /* 130 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 140 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 150 */ 1332, 1332, 1332, 1332, 1332, 1281, 1271, 1025, 1025, 1332,
- /* 160 */ 1271, 1271, 1271, 1271, 1271, 1271, 1267, 1100, 1091, 1332,
- /* 170 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 180 */ 1259, 1257, 1332, 1218, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 190 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 200 */ 1332, 1332, 1332, 1332, 1096, 1332, 1332, 1332, 1332, 1332,
- /* 210 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1283, 1332, 1232,
- /* 220 */ 1096, 1096, 1096, 1098, 1080, 1090, 1004, 1135, 1114, 1114,
- /* 230 */ 1321, 1135, 1321, 1042, 1303, 1039, 1125, 1114, 1200, 1125,
- /* 240 */ 1125, 1097, 1090, 1332, 1324, 1105, 1105, 1323, 1323, 1105,
- /* 250 */ 1146, 1070, 1135, 1076, 1076, 1076, 1076, 1105, 1016, 1135,
- /* 260 */ 1146, 1070, 1070, 1135, 1105, 1016, 1236, 1318, 1105, 1105,
- /* 270 */ 1016, 1211, 1105, 1016, 1105, 1016, 1211, 1068, 1068, 1068,
- /* 280 */ 1057, 1211, 1068, 1042, 1068, 1057, 1068, 1068, 1118, 1113,
- /* 290 */ 1118, 1113, 1118, 1113, 1118, 1113, 1105, 1206, 1105, 1332,
- /* 300 */ 1211, 1215, 1215, 1211, 1130, 1119, 1128, 1126, 1135, 1022,
- /* 310 */ 1060, 1286, 1286, 1282, 1282, 1282, 1282, 1329, 1329, 1267,
- /* 320 */ 1298, 1298, 1044, 1044, 1298, 1332, 1332, 1332, 1332, 1332,
- /* 330 */ 1332, 1293, 1332, 1220, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 340 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 350 */ 1332, 1332, 1152, 1332, 1000, 1264, 1332, 1332, 1263, 1332,
- /* 360 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 370 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1320,
- /* 380 */ 1332, 1332, 1332, 1332, 1332, 1332, 1235, 1234, 1332, 1332,
- /* 390 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 400 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332,
- /* 410 */ 1332, 1082, 1332, 1332, 1332, 1307, 1332, 1332, 1332, 1332,
- /* 420 */ 1332, 1332, 1332, 1127, 1332, 1120, 1332, 1332, 1311, 1332,
- /* 430 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1273,
- /* 440 */ 1332, 1332, 1332, 1272, 1332, 1332, 1332, 1332, 1332, 1154,
- /* 450 */ 1332, 1153, 1157, 1332, 1010, 1332,
+ /* 0 */ 1573, 1573, 1573, 1409, 1186, 1295, 1186, 1186, 1186, 1409,
+ /* 10 */ 1409, 1409, 1186, 1325, 1325, 1462, 1217, 1186, 1186, 1186,
+ /* 20 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1408, 1186, 1186,
+ /* 30 */ 1186, 1186, 1492, 1492, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 40 */ 1186, 1186, 1186, 1334, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 50 */ 1410, 1411, 1186, 1186, 1186, 1461, 1463, 1426, 1344, 1343,
+ /* 60 */ 1342, 1341, 1444, 1312, 1339, 1332, 1336, 1404, 1405, 1403,
+ /* 70 */ 1407, 1411, 1410, 1186, 1335, 1375, 1389, 1374, 1186, 1186,
+ /* 80 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 90 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 100 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 110 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 120 */ 1186, 1186, 1186, 1186, 1186, 1186, 1383, 1388, 1394, 1387,
+ /* 130 */ 1384, 1377, 1376, 1378, 1379, 1186, 1207, 1259, 1186, 1186,
+ /* 140 */ 1186, 1186, 1480, 1479, 1186, 1186, 1217, 1369, 1368, 1380,
+ /* 150 */ 1381, 1391, 1390, 1469, 1527, 1526, 1427, 1186, 1186, 1186,
+ /* 160 */ 1186, 1186, 1186, 1492, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 170 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 180 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1492, 1492,
+ /* 190 */ 1186, 1217, 1492, 1492, 1213, 1213, 1319, 1186, 1475, 1295,
+ /* 200 */ 1286, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 210 */ 1186, 1186, 1186, 1186, 1186, 1466, 1464, 1186, 1186, 1186,
+ /* 220 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 230 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 240 */ 1186, 1186, 1186, 1186, 1186, 1291, 1186, 1186, 1186, 1186,
+ /* 250 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1521, 1186, 1439,
+ /* 260 */ 1273, 1291, 1291, 1291, 1291, 1293, 1274, 1272, 1285, 1218,
+ /* 270 */ 1193, 1565, 1338, 1314, 1314, 1562, 1338, 1338, 1562, 1234,
+ /* 280 */ 1543, 1229, 1325, 1325, 1325, 1314, 1319, 1319, 1406, 1292,
+ /* 290 */ 1285, 1186, 1565, 1300, 1300, 1564, 1564, 1300, 1427, 1347,
+ /* 300 */ 1353, 1262, 1338, 1268, 1268, 1268, 1268, 1300, 1204, 1338,
+ /* 310 */ 1338, 1347, 1353, 1262, 1262, 1338, 1300, 1204, 1443, 1559,
+ /* 320 */ 1300, 1204, 1417, 1300, 1204, 1300, 1204, 1417, 1260, 1260,
+ /* 330 */ 1260, 1249, 1186, 1186, 1417, 1260, 1234, 1260, 1249, 1260,
+ /* 340 */ 1260, 1510, 1417, 1421, 1421, 1417, 1318, 1313, 1318, 1313,
+ /* 350 */ 1318, 1313, 1318, 1313, 1300, 1502, 1502, 1328, 1328, 1333,
+ /* 360 */ 1319, 1412, 1300, 1186, 1333, 1331, 1329, 1338, 1210, 1252,
+ /* 370 */ 1524, 1524, 1520, 1520, 1520, 1570, 1570, 1475, 1536, 1217,
+ /* 380 */ 1217, 1217, 1217, 1536, 1236, 1236, 1218, 1218, 1217, 1536,
+ /* 390 */ 1186, 1186, 1186, 1186, 1186, 1186, 1531, 1186, 1428, 1304,
+ /* 400 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 410 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 420 */ 1186, 1186, 1186, 1358, 1186, 1189, 1472, 1186, 1186, 1470,
+ /* 430 */ 1186, 1186, 1186, 1186, 1186, 1186, 1305, 1186, 1186, 1186,
+ /* 440 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 450 */ 1186, 1186, 1186, 1186, 1186, 1561, 1186, 1186, 1186, 1186,
+ /* 460 */ 1186, 1186, 1442, 1441, 1186, 1186, 1302, 1186, 1186, 1186,
+ /* 470 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 480 */ 1232, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 490 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 500 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1330, 1186, 1186,
+ /* 510 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 520 */ 1186, 1186, 1507, 1320, 1186, 1186, 1552, 1186, 1186, 1186,
+ /* 530 */ 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186, 1186,
+ /* 540 */ 1186, 1547, 1276, 1360, 1186, 1359, 1363, 1186, 1198, 1186,
+ /* 550 */ 1186,
};
/********** End of lemon-generated parsing tables *****************************/
@@ -136789,36 +156906,52 @@ static const YYACTIONTYPE yy_default[] = {
static const YYCODETYPE yyFallback[] = {
0, /* $ => nothing */
0, /* SEMI => nothing */
- 55, /* EXPLAIN => ID */
- 55, /* QUERY => ID */
- 55, /* PLAN => ID */
- 55, /* BEGIN => ID */
+ 59, /* EXPLAIN => ID */
+ 59, /* QUERY => ID */
+ 59, /* PLAN => ID */
+ 59, /* BEGIN => ID */
0, /* TRANSACTION => nothing */
- 55, /* DEFERRED => ID */
- 55, /* IMMEDIATE => ID */
- 55, /* EXCLUSIVE => ID */
+ 59, /* DEFERRED => ID */
+ 59, /* IMMEDIATE => ID */
+ 59, /* EXCLUSIVE => ID */
0, /* COMMIT => nothing */
- 55, /* END => ID */
- 55, /* ROLLBACK => ID */
- 55, /* SAVEPOINT => ID */
- 55, /* RELEASE => ID */
+ 59, /* END => ID */
+ 59, /* ROLLBACK => ID */
+ 59, /* SAVEPOINT => ID */
+ 59, /* RELEASE => ID */
0, /* TO => nothing */
0, /* TABLE => nothing */
0, /* CREATE => nothing */
- 55, /* IF => ID */
+ 59, /* IF => ID */
0, /* NOT => nothing */
0, /* EXISTS => nothing */
- 55, /* TEMP => ID */
+ 59, /* TEMP => ID */
0, /* LP => nothing */
0, /* RP => nothing */
0, /* AS => nothing */
- 55, /* WITHOUT => ID */
+ 59, /* WITHOUT => ID */
0, /* COMMA => nothing */
+ 59, /* ABORT => ID */
+ 59, /* ACTION => ID */
+ 59, /* AFTER => ID */
+ 59, /* ANALYZE => ID */
+ 59, /* ASC => ID */
+ 59, /* ATTACH => ID */
+ 59, /* BEFORE => ID */
+ 59, /* BY => ID */
+ 59, /* CASCADE => ID */
+ 59, /* CAST => ID */
+ 59, /* CONFLICT => ID */
+ 59, /* DATABASE => ID */
+ 59, /* DESC => ID */
+ 59, /* DETACH => ID */
+ 59, /* EACH => ID */
+ 59, /* FAIL => ID */
0, /* OR => nothing */
0, /* AND => nothing */
0, /* IS => nothing */
- 55, /* MATCH => ID */
- 55, /* LIKE_KW => ID */
+ 59, /* MATCH => ID */
+ 59, /* LIKE_KW => ID */
0, /* BETWEEN => nothing */
0, /* IN => nothing */
0, /* ISNULL => nothing */
@@ -136830,6 +156963,48 @@ static const YYCODETYPE yyFallback[] = {
0, /* LT => nothing */
0, /* GE => nothing */
0, /* ESCAPE => nothing */
+ 0, /* ID => nothing */
+ 59, /* COLUMNKW => ID */
+ 59, /* DO => ID */
+ 59, /* FOR => ID */
+ 59, /* IGNORE => ID */
+ 59, /* INITIALLY => ID */
+ 59, /* INSTEAD => ID */
+ 59, /* NO => ID */
+ 59, /* KEY => ID */
+ 59, /* OF => ID */
+ 59, /* OFFSET => ID */
+ 59, /* PRAGMA => ID */
+ 59, /* RAISE => ID */
+ 59, /* RECURSIVE => ID */
+ 59, /* REPLACE => ID */
+ 59, /* RESTRICT => ID */
+ 59, /* ROW => ID */
+ 59, /* ROWS => ID */
+ 59, /* TRIGGER => ID */
+ 59, /* VACUUM => ID */
+ 59, /* VIEW => ID */
+ 59, /* VIRTUAL => ID */
+ 59, /* WITH => ID */
+ 59, /* NULLS => ID */
+ 59, /* FIRST => ID */
+ 59, /* LAST => ID */
+ 59, /* CURRENT => ID */
+ 59, /* FOLLOWING => ID */
+ 59, /* PARTITION => ID */
+ 59, /* PRECEDING => ID */
+ 59, /* RANGE => ID */
+ 59, /* UNBOUNDED => ID */
+ 59, /* EXCLUDE => ID */
+ 59, /* GROUPS => ID */
+ 59, /* OTHERS => ID */
+ 59, /* TIES => ID */
+ 59, /* GENERATED => ID */
+ 59, /* ALWAYS => ID */
+ 59, /* REINDEX => ID */
+ 59, /* RENAME => ID */
+ 59, /* CTIME_KW => ID */
+ 0, /* ANY => nothing */
0, /* BITAND => nothing */
0, /* BITOR => nothing */
0, /* LSHIFT => nothing */
@@ -136842,47 +157017,74 @@ static const YYCODETYPE yyFallback[] = {
0, /* CONCAT => nothing */
0, /* COLLATE => nothing */
0, /* BITNOT => nothing */
- 0, /* ID => nothing */
+ 0, /* ON => nothing */
0, /* INDEXED => nothing */
- 55, /* ABORT => ID */
- 55, /* ACTION => ID */
- 55, /* AFTER => ID */
- 55, /* ANALYZE => ID */
- 55, /* ASC => ID */
- 55, /* ATTACH => ID */
- 55, /* BEFORE => ID */
- 55, /* BY => ID */
- 55, /* CASCADE => ID */
- 55, /* CAST => ID */
- 55, /* COLUMNKW => ID */
- 55, /* CONFLICT => ID */
- 55, /* DATABASE => ID */
- 55, /* DESC => ID */
- 55, /* DETACH => ID */
- 55, /* EACH => ID */
- 55, /* FAIL => ID */
- 55, /* FOR => ID */
- 55, /* IGNORE => ID */
- 55, /* INITIALLY => ID */
- 55, /* INSTEAD => ID */
- 55, /* NO => ID */
- 55, /* KEY => ID */
- 55, /* OF => ID */
- 55, /* OFFSET => ID */
- 55, /* PRAGMA => ID */
- 55, /* RAISE => ID */
- 55, /* RECURSIVE => ID */
- 55, /* REPLACE => ID */
- 55, /* RESTRICT => ID */
- 55, /* ROW => ID */
- 55, /* TRIGGER => ID */
- 55, /* VACUUM => ID */
- 55, /* VIEW => ID */
- 55, /* VIRTUAL => ID */
- 55, /* WITH => ID */
- 55, /* REINDEX => ID */
- 55, /* RENAME => ID */
- 55, /* CTIME_KW => ID */
+ 0, /* STRING => nothing */
+ 0, /* JOIN_KW => nothing */
+ 0, /* CONSTRAINT => nothing */
+ 0, /* DEFAULT => nothing */
+ 0, /* NULL => nothing */
+ 0, /* PRIMARY => nothing */
+ 0, /* UNIQUE => nothing */
+ 0, /* CHECK => nothing */
+ 0, /* REFERENCES => nothing */
+ 0, /* AUTOINCR => nothing */
+ 0, /* INSERT => nothing */
+ 0, /* DELETE => nothing */
+ 0, /* UPDATE => nothing */
+ 0, /* SET => nothing */
+ 0, /* DEFERRABLE => nothing */
+ 0, /* FOREIGN => nothing */
+ 0, /* DROP => nothing */
+ 0, /* UNION => nothing */
+ 0, /* ALL => nothing */
+ 0, /* EXCEPT => nothing */
+ 0, /* INTERSECT => nothing */
+ 0, /* SELECT => nothing */
+ 0, /* VALUES => nothing */
+ 0, /* DISTINCT => nothing */
+ 0, /* DOT => nothing */
+ 0, /* FROM => nothing */
+ 0, /* JOIN => nothing */
+ 0, /* USING => nothing */
+ 0, /* ORDER => nothing */
+ 0, /* GROUP => nothing */
+ 0, /* HAVING => nothing */
+ 0, /* LIMIT => nothing */
+ 0, /* WHERE => nothing */
+ 0, /* INTO => nothing */
+ 0, /* NOTHING => nothing */
+ 0, /* FLOAT => nothing */
+ 0, /* BLOB => nothing */
+ 0, /* INTEGER => nothing */
+ 0, /* VARIABLE => nothing */
+ 0, /* CASE => nothing */
+ 0, /* WHEN => nothing */
+ 0, /* THEN => nothing */
+ 0, /* ELSE => nothing */
+ 0, /* INDEX => nothing */
+ 0, /* ALTER => nothing */
+ 0, /* ADD => nothing */
+ 0, /* WINDOW => nothing */
+ 0, /* OVER => nothing */
+ 0, /* FILTER => nothing */
+ 0, /* COLUMN => nothing */
+ 0, /* AGG_FUNCTION => nothing */
+ 0, /* AGG_COLUMN => nothing */
+ 0, /* TRUEFALSE => nothing */
+ 0, /* ISNOT => nothing */
+ 0, /* FUNCTION => nothing */
+ 0, /* UMINUS => nothing */
+ 0, /* UPLUS => nothing */
+ 0, /* TRUTH => nothing */
+ 0, /* REGISTER => nothing */
+ 0, /* VECTOR => nothing */
+ 0, /* SELECT_COLUMN => nothing */
+ 0, /* IF_NULL_ROW => nothing */
+ 0, /* ASTERISK => nothing */
+ 0, /* SPAN => nothing */
+ 0, /* SPACE => nothing */
+ 0, /* ILLEGAL => nothing */
};
#endif /* YYFALLBACK */
@@ -136921,13 +157123,15 @@ struct yyParser {
#ifndef YYNOERRORRECOVERY
int yyerrcnt; /* Shifts left before out of the error */
#endif
- sqlite3ParserARG_SDECL /* A place to hold %extra_argument */
+ tdsqlite3ParserARG_SDECL /* A place to hold %extra_argument */
+ tdsqlite3ParserCTX_SDECL /* A place to hold %extra_context */
#if YYSTACKDEPTH<=0
int yystksz; /* Current side of the stack */
yyStackEntry *yystack; /* The parser's stack */
yyStackEntry yystk0; /* First stack entry */
#else
yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
+ yyStackEntry *yystackEnd; /* Last entry in the stack */
#endif
};
typedef struct yyParser yyParser;
@@ -136956,7 +157160,7 @@ static char *yyTracePrompt = 0;
** Outputs:
** None.
*/
-SQLITE_PRIVATE void sqlite3ParserTrace(FILE *TraceFILE, char *zTracePrompt){
+SQLITE_PRIVATE void tdsqlite3ParserTrace(FILE *TraceFILE, char *zTracePrompt){
yyTraceFILE = TraceFILE;
yyTracePrompt = zTracePrompt;
if( yyTraceFILE==0 ) yyTracePrompt = 0;
@@ -136964,75 +157168,322 @@ SQLITE_PRIVATE void sqlite3ParserTrace(FILE *TraceFILE, char *zTracePrompt){
}
#endif /* NDEBUG */
-#ifndef NDEBUG
+#if defined(YYCOVERAGE) || !defined(NDEBUG)
/* For tracing shifts, the names of all terminals and nonterminals
** are required. The following table supplies these names */
static const char *const yyTokenName[] = {
- "$", "SEMI", "EXPLAIN", "QUERY",
- "PLAN", "BEGIN", "TRANSACTION", "DEFERRED",
- "IMMEDIATE", "EXCLUSIVE", "COMMIT", "END",
- "ROLLBACK", "SAVEPOINT", "RELEASE", "TO",
- "TABLE", "CREATE", "IF", "NOT",
- "EXISTS", "TEMP", "LP", "RP",
- "AS", "WITHOUT", "COMMA", "OR",
- "AND", "IS", "MATCH", "LIKE_KW",
- "BETWEEN", "IN", "ISNULL", "NOTNULL",
- "NE", "EQ", "GT", "LE",
- "LT", "GE", "ESCAPE", "BITAND",
- "BITOR", "LSHIFT", "RSHIFT", "PLUS",
- "MINUS", "STAR", "SLASH", "REM",
- "CONCAT", "COLLATE", "BITNOT", "ID",
- "INDEXED", "ABORT", "ACTION", "AFTER",
- "ANALYZE", "ASC", "ATTACH", "BEFORE",
- "BY", "CASCADE", "CAST", "COLUMNKW",
- "CONFLICT", "DATABASE", "DESC", "DETACH",
- "EACH", "FAIL", "FOR", "IGNORE",
- "INITIALLY", "INSTEAD", "NO", "KEY",
- "OF", "OFFSET", "PRAGMA", "RAISE",
- "RECURSIVE", "REPLACE", "RESTRICT", "ROW",
- "TRIGGER", "VACUUM", "VIEW", "VIRTUAL",
- "WITH", "REINDEX", "RENAME", "CTIME_KW",
- "ANY", "STRING", "JOIN_KW", "CONSTRAINT",
- "DEFAULT", "NULL", "PRIMARY", "UNIQUE",
- "CHECK", "REFERENCES", "AUTOINCR", "ON",
- "INSERT", "DELETE", "UPDATE", "SET",
- "DEFERRABLE", "FOREIGN", "DROP", "UNION",
- "ALL", "EXCEPT", "INTERSECT", "SELECT",
- "VALUES", "DISTINCT", "DOT", "FROM",
- "JOIN", "USING", "ORDER", "GROUP",
- "HAVING", "LIMIT", "WHERE", "INTO",
- "FLOAT", "BLOB", "INTEGER", "VARIABLE",
- "CASE", "WHEN", "THEN", "ELSE",
- "INDEX", "ALTER", "ADD", "error",
- "input", "cmdlist", "ecmd", "explain",
- "cmdx", "cmd", "transtype", "trans_opt",
- "nm", "savepoint_opt", "create_table", "create_table_args",
- "createkw", "temp", "ifnotexists", "dbnm",
- "columnlist", "conslist_opt", "table_options", "select",
- "columnname", "carglist", "typetoken", "typename",
- "signed", "plus_num", "minus_num", "ccons",
- "term", "expr", "onconf", "sortorder",
- "autoinc", "eidlist_opt", "refargs", "defer_subclause",
- "refarg", "refact", "init_deferred_pred_opt", "conslist",
- "tconscomma", "tcons", "sortlist", "eidlist",
- "defer_subclause_opt", "orconf", "resolvetype", "raisetype",
- "ifexists", "fullname", "selectnowith", "oneselect",
- "with", "multiselect_op", "distinct", "selcollist",
- "from", "where_opt", "groupby_opt", "having_opt",
- "orderby_opt", "limit_opt", "values", "nexprlist",
- "exprlist", "sclp", "as", "seltablist",
- "stl_prefix", "joinop", "indexed_opt", "on_opt",
- "using_opt", "idlist", "setlist", "insert_cmd",
- "idlist_opt", "likeop", "between_op", "in_op",
- "paren_exprlist", "case_operand", "case_exprlist", "case_else",
- "uniqueflag", "collate", "nmnum", "trigger_decl",
- "trigger_cmd_list", "trigger_time", "trigger_event", "foreach_clause",
- "when_clause", "trigger_cmd", "trnm", "tridxby",
- "database_kw_opt", "key_opt", "add_column_fullname", "kwcolumn_opt",
- "create_vtab", "vtabarglist", "vtabarg", "vtabargtoken",
- "lp", "anylist", "wqlist",
+ /* 0 */ "$",
+ /* 1 */ "SEMI",
+ /* 2 */ "EXPLAIN",
+ /* 3 */ "QUERY",
+ /* 4 */ "PLAN",
+ /* 5 */ "BEGIN",
+ /* 6 */ "TRANSACTION",
+ /* 7 */ "DEFERRED",
+ /* 8 */ "IMMEDIATE",
+ /* 9 */ "EXCLUSIVE",
+ /* 10 */ "COMMIT",
+ /* 11 */ "END",
+ /* 12 */ "ROLLBACK",
+ /* 13 */ "SAVEPOINT",
+ /* 14 */ "RELEASE",
+ /* 15 */ "TO",
+ /* 16 */ "TABLE",
+ /* 17 */ "CREATE",
+ /* 18 */ "IF",
+ /* 19 */ "NOT",
+ /* 20 */ "EXISTS",
+ /* 21 */ "TEMP",
+ /* 22 */ "LP",
+ /* 23 */ "RP",
+ /* 24 */ "AS",
+ /* 25 */ "WITHOUT",
+ /* 26 */ "COMMA",
+ /* 27 */ "ABORT",
+ /* 28 */ "ACTION",
+ /* 29 */ "AFTER",
+ /* 30 */ "ANALYZE",
+ /* 31 */ "ASC",
+ /* 32 */ "ATTACH",
+ /* 33 */ "BEFORE",
+ /* 34 */ "BY",
+ /* 35 */ "CASCADE",
+ /* 36 */ "CAST",
+ /* 37 */ "CONFLICT",
+ /* 38 */ "DATABASE",
+ /* 39 */ "DESC",
+ /* 40 */ "DETACH",
+ /* 41 */ "EACH",
+ /* 42 */ "FAIL",
+ /* 43 */ "OR",
+ /* 44 */ "AND",
+ /* 45 */ "IS",
+ /* 46 */ "MATCH",
+ /* 47 */ "LIKE_KW",
+ /* 48 */ "BETWEEN",
+ /* 49 */ "IN",
+ /* 50 */ "ISNULL",
+ /* 51 */ "NOTNULL",
+ /* 52 */ "NE",
+ /* 53 */ "EQ",
+ /* 54 */ "GT",
+ /* 55 */ "LE",
+ /* 56 */ "LT",
+ /* 57 */ "GE",
+ /* 58 */ "ESCAPE",
+ /* 59 */ "ID",
+ /* 60 */ "COLUMNKW",
+ /* 61 */ "DO",
+ /* 62 */ "FOR",
+ /* 63 */ "IGNORE",
+ /* 64 */ "INITIALLY",
+ /* 65 */ "INSTEAD",
+ /* 66 */ "NO",
+ /* 67 */ "KEY",
+ /* 68 */ "OF",
+ /* 69 */ "OFFSET",
+ /* 70 */ "PRAGMA",
+ /* 71 */ "RAISE",
+ /* 72 */ "RECURSIVE",
+ /* 73 */ "REPLACE",
+ /* 74 */ "RESTRICT",
+ /* 75 */ "ROW",
+ /* 76 */ "ROWS",
+ /* 77 */ "TRIGGER",
+ /* 78 */ "VACUUM",
+ /* 79 */ "VIEW",
+ /* 80 */ "VIRTUAL",
+ /* 81 */ "WITH",
+ /* 82 */ "NULLS",
+ /* 83 */ "FIRST",
+ /* 84 */ "LAST",
+ /* 85 */ "CURRENT",
+ /* 86 */ "FOLLOWING",
+ /* 87 */ "PARTITION",
+ /* 88 */ "PRECEDING",
+ /* 89 */ "RANGE",
+ /* 90 */ "UNBOUNDED",
+ /* 91 */ "EXCLUDE",
+ /* 92 */ "GROUPS",
+ /* 93 */ "OTHERS",
+ /* 94 */ "TIES",
+ /* 95 */ "GENERATED",
+ /* 96 */ "ALWAYS",
+ /* 97 */ "REINDEX",
+ /* 98 */ "RENAME",
+ /* 99 */ "CTIME_KW",
+ /* 100 */ "ANY",
+ /* 101 */ "BITAND",
+ /* 102 */ "BITOR",
+ /* 103 */ "LSHIFT",
+ /* 104 */ "RSHIFT",
+ /* 105 */ "PLUS",
+ /* 106 */ "MINUS",
+ /* 107 */ "STAR",
+ /* 108 */ "SLASH",
+ /* 109 */ "REM",
+ /* 110 */ "CONCAT",
+ /* 111 */ "COLLATE",
+ /* 112 */ "BITNOT",
+ /* 113 */ "ON",
+ /* 114 */ "INDEXED",
+ /* 115 */ "STRING",
+ /* 116 */ "JOIN_KW",
+ /* 117 */ "CONSTRAINT",
+ /* 118 */ "DEFAULT",
+ /* 119 */ "NULL",
+ /* 120 */ "PRIMARY",
+ /* 121 */ "UNIQUE",
+ /* 122 */ "CHECK",
+ /* 123 */ "REFERENCES",
+ /* 124 */ "AUTOINCR",
+ /* 125 */ "INSERT",
+ /* 126 */ "DELETE",
+ /* 127 */ "UPDATE",
+ /* 128 */ "SET",
+ /* 129 */ "DEFERRABLE",
+ /* 130 */ "FOREIGN",
+ /* 131 */ "DROP",
+ /* 132 */ "UNION",
+ /* 133 */ "ALL",
+ /* 134 */ "EXCEPT",
+ /* 135 */ "INTERSECT",
+ /* 136 */ "SELECT",
+ /* 137 */ "VALUES",
+ /* 138 */ "DISTINCT",
+ /* 139 */ "DOT",
+ /* 140 */ "FROM",
+ /* 141 */ "JOIN",
+ /* 142 */ "USING",
+ /* 143 */ "ORDER",
+ /* 144 */ "GROUP",
+ /* 145 */ "HAVING",
+ /* 146 */ "LIMIT",
+ /* 147 */ "WHERE",
+ /* 148 */ "INTO",
+ /* 149 */ "NOTHING",
+ /* 150 */ "FLOAT",
+ /* 151 */ "BLOB",
+ /* 152 */ "INTEGER",
+ /* 153 */ "VARIABLE",
+ /* 154 */ "CASE",
+ /* 155 */ "WHEN",
+ /* 156 */ "THEN",
+ /* 157 */ "ELSE",
+ /* 158 */ "INDEX",
+ /* 159 */ "ALTER",
+ /* 160 */ "ADD",
+ /* 161 */ "WINDOW",
+ /* 162 */ "OVER",
+ /* 163 */ "FILTER",
+ /* 164 */ "COLUMN",
+ /* 165 */ "AGG_FUNCTION",
+ /* 166 */ "AGG_COLUMN",
+ /* 167 */ "TRUEFALSE",
+ /* 168 */ "ISNOT",
+ /* 169 */ "FUNCTION",
+ /* 170 */ "UMINUS",
+ /* 171 */ "UPLUS",
+ /* 172 */ "TRUTH",
+ /* 173 */ "REGISTER",
+ /* 174 */ "VECTOR",
+ /* 175 */ "SELECT_COLUMN",
+ /* 176 */ "IF_NULL_ROW",
+ /* 177 */ "ASTERISK",
+ /* 178 */ "SPAN",
+ /* 179 */ "SPACE",
+ /* 180 */ "ILLEGAL",
+ /* 181 */ "input",
+ /* 182 */ "cmdlist",
+ /* 183 */ "ecmd",
+ /* 184 */ "cmdx",
+ /* 185 */ "explain",
+ /* 186 */ "cmd",
+ /* 187 */ "transtype",
+ /* 188 */ "trans_opt",
+ /* 189 */ "nm",
+ /* 190 */ "savepoint_opt",
+ /* 191 */ "create_table",
+ /* 192 */ "create_table_args",
+ /* 193 */ "createkw",
+ /* 194 */ "temp",
+ /* 195 */ "ifnotexists",
+ /* 196 */ "dbnm",
+ /* 197 */ "columnlist",
+ /* 198 */ "conslist_opt",
+ /* 199 */ "table_options",
+ /* 200 */ "select",
+ /* 201 */ "columnname",
+ /* 202 */ "carglist",
+ /* 203 */ "typetoken",
+ /* 204 */ "typename",
+ /* 205 */ "signed",
+ /* 206 */ "plus_num",
+ /* 207 */ "minus_num",
+ /* 208 */ "scanpt",
+ /* 209 */ "scantok",
+ /* 210 */ "ccons",
+ /* 211 */ "term",
+ /* 212 */ "expr",
+ /* 213 */ "onconf",
+ /* 214 */ "sortorder",
+ /* 215 */ "autoinc",
+ /* 216 */ "eidlist_opt",
+ /* 217 */ "refargs",
+ /* 218 */ "defer_subclause",
+ /* 219 */ "generated",
+ /* 220 */ "refarg",
+ /* 221 */ "refact",
+ /* 222 */ "init_deferred_pred_opt",
+ /* 223 */ "conslist",
+ /* 224 */ "tconscomma",
+ /* 225 */ "tcons",
+ /* 226 */ "sortlist",
+ /* 227 */ "eidlist",
+ /* 228 */ "defer_subclause_opt",
+ /* 229 */ "orconf",
+ /* 230 */ "resolvetype",
+ /* 231 */ "raisetype",
+ /* 232 */ "ifexists",
+ /* 233 */ "fullname",
+ /* 234 */ "selectnowith",
+ /* 235 */ "oneselect",
+ /* 236 */ "wqlist",
+ /* 237 */ "multiselect_op",
+ /* 238 */ "distinct",
+ /* 239 */ "selcollist",
+ /* 240 */ "from",
+ /* 241 */ "where_opt",
+ /* 242 */ "groupby_opt",
+ /* 243 */ "having_opt",
+ /* 244 */ "orderby_opt",
+ /* 245 */ "limit_opt",
+ /* 246 */ "window_clause",
+ /* 247 */ "values",
+ /* 248 */ "nexprlist",
+ /* 249 */ "sclp",
+ /* 250 */ "as",
+ /* 251 */ "seltablist",
+ /* 252 */ "stl_prefix",
+ /* 253 */ "joinop",
+ /* 254 */ "indexed_opt",
+ /* 255 */ "on_opt",
+ /* 256 */ "using_opt",
+ /* 257 */ "exprlist",
+ /* 258 */ "xfullname",
+ /* 259 */ "idlist",
+ /* 260 */ "nulls",
+ /* 261 */ "with",
+ /* 262 */ "setlist",
+ /* 263 */ "insert_cmd",
+ /* 264 */ "idlist_opt",
+ /* 265 */ "upsert",
+ /* 266 */ "filter_over",
+ /* 267 */ "likeop",
+ /* 268 */ "between_op",
+ /* 269 */ "in_op",
+ /* 270 */ "paren_exprlist",
+ /* 271 */ "case_operand",
+ /* 272 */ "case_exprlist",
+ /* 273 */ "case_else",
+ /* 274 */ "uniqueflag",
+ /* 275 */ "collate",
+ /* 276 */ "vinto",
+ /* 277 */ "nmnum",
+ /* 278 */ "trigger_decl",
+ /* 279 */ "trigger_cmd_list",
+ /* 280 */ "trigger_time",
+ /* 281 */ "trigger_event",
+ /* 282 */ "foreach_clause",
+ /* 283 */ "when_clause",
+ /* 284 */ "trigger_cmd",
+ /* 285 */ "trnm",
+ /* 286 */ "tridxby",
+ /* 287 */ "database_kw_opt",
+ /* 288 */ "key_opt",
+ /* 289 */ "add_column_fullname",
+ /* 290 */ "kwcolumn_opt",
+ /* 291 */ "create_vtab",
+ /* 292 */ "vtabarglist",
+ /* 293 */ "vtabarg",
+ /* 294 */ "vtabargtoken",
+ /* 295 */ "lp",
+ /* 296 */ "anylist",
+ /* 297 */ "windowdefn_list",
+ /* 298 */ "windowdefn",
+ /* 299 */ "window",
+ /* 300 */ "frame_opt",
+ /* 301 */ "part_opt",
+ /* 302 */ "filter_clause",
+ /* 303 */ "over_clause",
+ /* 304 */ "range_or_rows",
+ /* 305 */ "frame_bound",
+ /* 306 */ "frame_bound_s",
+ /* 307 */ "frame_bound_e",
+ /* 308 */ "frame_exclude_opt",
+ /* 309 */ "frame_exclude",
};
-#endif /* NDEBUG */
+#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */
#ifndef NDEBUG
/* For tracing reduce actions, the names of all rules are required.
@@ -137046,330 +157497,383 @@ static const char *const yyRuleName[] = {
/* 5 */ "transtype ::= DEFERRED",
/* 6 */ "transtype ::= IMMEDIATE",
/* 7 */ "transtype ::= EXCLUSIVE",
- /* 8 */ "cmd ::= COMMIT trans_opt",
- /* 9 */ "cmd ::= END trans_opt",
- /* 10 */ "cmd ::= ROLLBACK trans_opt",
- /* 11 */ "cmd ::= SAVEPOINT nm",
- /* 12 */ "cmd ::= RELEASE savepoint_opt nm",
- /* 13 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt nm",
- /* 14 */ "create_table ::= createkw temp TABLE ifnotexists nm dbnm",
- /* 15 */ "createkw ::= CREATE",
- /* 16 */ "ifnotexists ::=",
- /* 17 */ "ifnotexists ::= IF NOT EXISTS",
- /* 18 */ "temp ::= TEMP",
- /* 19 */ "temp ::=",
- /* 20 */ "create_table_args ::= LP columnlist conslist_opt RP table_options",
- /* 21 */ "create_table_args ::= AS select",
- /* 22 */ "table_options ::=",
- /* 23 */ "table_options ::= WITHOUT nm",
- /* 24 */ "columnname ::= nm typetoken",
- /* 25 */ "typetoken ::=",
- /* 26 */ "typetoken ::= typename LP signed RP",
- /* 27 */ "typetoken ::= typename LP signed COMMA signed RP",
- /* 28 */ "typename ::= typename ID|STRING",
- /* 29 */ "ccons ::= CONSTRAINT nm",
- /* 30 */ "ccons ::= DEFAULT term",
- /* 31 */ "ccons ::= DEFAULT LP expr RP",
- /* 32 */ "ccons ::= DEFAULT PLUS term",
- /* 33 */ "ccons ::= DEFAULT MINUS term",
- /* 34 */ "ccons ::= DEFAULT ID|INDEXED",
- /* 35 */ "ccons ::= NOT NULL onconf",
- /* 36 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc",
- /* 37 */ "ccons ::= UNIQUE onconf",
- /* 38 */ "ccons ::= CHECK LP expr RP",
- /* 39 */ "ccons ::= REFERENCES nm eidlist_opt refargs",
- /* 40 */ "ccons ::= defer_subclause",
- /* 41 */ "ccons ::= COLLATE ID|STRING",
- /* 42 */ "autoinc ::=",
- /* 43 */ "autoinc ::= AUTOINCR",
- /* 44 */ "refargs ::=",
- /* 45 */ "refargs ::= refargs refarg",
- /* 46 */ "refarg ::= MATCH nm",
- /* 47 */ "refarg ::= ON INSERT refact",
- /* 48 */ "refarg ::= ON DELETE refact",
- /* 49 */ "refarg ::= ON UPDATE refact",
- /* 50 */ "refact ::= SET NULL",
- /* 51 */ "refact ::= SET DEFAULT",
- /* 52 */ "refact ::= CASCADE",
- /* 53 */ "refact ::= RESTRICT",
- /* 54 */ "refact ::= NO ACTION",
- /* 55 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt",
- /* 56 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt",
- /* 57 */ "init_deferred_pred_opt ::=",
- /* 58 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED",
- /* 59 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE",
- /* 60 */ "conslist_opt ::=",
- /* 61 */ "tconscomma ::= COMMA",
- /* 62 */ "tcons ::= CONSTRAINT nm",
- /* 63 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf",
- /* 64 */ "tcons ::= UNIQUE LP sortlist RP onconf",
- /* 65 */ "tcons ::= CHECK LP expr RP onconf",
- /* 66 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt",
- /* 67 */ "defer_subclause_opt ::=",
- /* 68 */ "onconf ::=",
- /* 69 */ "onconf ::= ON CONFLICT resolvetype",
- /* 70 */ "orconf ::=",
- /* 71 */ "orconf ::= OR resolvetype",
- /* 72 */ "resolvetype ::= IGNORE",
- /* 73 */ "resolvetype ::= REPLACE",
- /* 74 */ "cmd ::= DROP TABLE ifexists fullname",
- /* 75 */ "ifexists ::= IF EXISTS",
- /* 76 */ "ifexists ::=",
- /* 77 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select",
- /* 78 */ "cmd ::= DROP VIEW ifexists fullname",
- /* 79 */ "cmd ::= select",
- /* 80 */ "select ::= with selectnowith",
- /* 81 */ "selectnowith ::= selectnowith multiselect_op oneselect",
- /* 82 */ "multiselect_op ::= UNION",
- /* 83 */ "multiselect_op ::= UNION ALL",
- /* 84 */ "multiselect_op ::= EXCEPT|INTERSECT",
- /* 85 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt",
- /* 86 */ "values ::= VALUES LP nexprlist RP",
- /* 87 */ "values ::= values COMMA LP exprlist RP",
- /* 88 */ "distinct ::= DISTINCT",
- /* 89 */ "distinct ::= ALL",
- /* 90 */ "distinct ::=",
- /* 91 */ "sclp ::=",
- /* 92 */ "selcollist ::= sclp expr as",
- /* 93 */ "selcollist ::= sclp STAR",
- /* 94 */ "selcollist ::= sclp nm DOT STAR",
- /* 95 */ "as ::= AS nm",
- /* 96 */ "as ::=",
- /* 97 */ "from ::=",
- /* 98 */ "from ::= FROM seltablist",
- /* 99 */ "stl_prefix ::= seltablist joinop",
- /* 100 */ "stl_prefix ::=",
- /* 101 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt",
- /* 102 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt",
- /* 103 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt",
- /* 104 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt",
- /* 105 */ "dbnm ::=",
- /* 106 */ "dbnm ::= DOT nm",
- /* 107 */ "fullname ::= nm dbnm",
- /* 108 */ "joinop ::= COMMA|JOIN",
- /* 109 */ "joinop ::= JOIN_KW JOIN",
- /* 110 */ "joinop ::= JOIN_KW nm JOIN",
- /* 111 */ "joinop ::= JOIN_KW nm nm JOIN",
- /* 112 */ "on_opt ::= ON expr",
- /* 113 */ "on_opt ::=",
- /* 114 */ "indexed_opt ::=",
- /* 115 */ "indexed_opt ::= INDEXED BY nm",
- /* 116 */ "indexed_opt ::= NOT INDEXED",
- /* 117 */ "using_opt ::= USING LP idlist RP",
- /* 118 */ "using_opt ::=",
- /* 119 */ "orderby_opt ::=",
- /* 120 */ "orderby_opt ::= ORDER BY sortlist",
- /* 121 */ "sortlist ::= sortlist COMMA expr sortorder",
- /* 122 */ "sortlist ::= expr sortorder",
- /* 123 */ "sortorder ::= ASC",
- /* 124 */ "sortorder ::= DESC",
- /* 125 */ "sortorder ::=",
- /* 126 */ "groupby_opt ::=",
- /* 127 */ "groupby_opt ::= GROUP BY nexprlist",
- /* 128 */ "having_opt ::=",
- /* 129 */ "having_opt ::= HAVING expr",
- /* 130 */ "limit_opt ::=",
- /* 131 */ "limit_opt ::= LIMIT expr",
- /* 132 */ "limit_opt ::= LIMIT expr OFFSET expr",
- /* 133 */ "limit_opt ::= LIMIT expr COMMA expr",
- /* 134 */ "cmd ::= with DELETE FROM fullname indexed_opt where_opt",
- /* 135 */ "where_opt ::=",
- /* 136 */ "where_opt ::= WHERE expr",
- /* 137 */ "cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt",
- /* 138 */ "setlist ::= setlist COMMA nm EQ expr",
- /* 139 */ "setlist ::= setlist COMMA LP idlist RP EQ expr",
- /* 140 */ "setlist ::= nm EQ expr",
- /* 141 */ "setlist ::= LP idlist RP EQ expr",
- /* 142 */ "cmd ::= with insert_cmd INTO fullname idlist_opt select",
- /* 143 */ "cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES",
- /* 144 */ "insert_cmd ::= INSERT orconf",
- /* 145 */ "insert_cmd ::= REPLACE",
- /* 146 */ "idlist_opt ::=",
- /* 147 */ "idlist_opt ::= LP idlist RP",
- /* 148 */ "idlist ::= idlist COMMA nm",
- /* 149 */ "idlist ::= nm",
- /* 150 */ "expr ::= LP expr RP",
- /* 151 */ "term ::= NULL",
- /* 152 */ "expr ::= ID|INDEXED",
- /* 153 */ "expr ::= JOIN_KW",
- /* 154 */ "expr ::= nm DOT nm",
- /* 155 */ "expr ::= nm DOT nm DOT nm",
- /* 156 */ "term ::= FLOAT|BLOB",
- /* 157 */ "term ::= STRING",
- /* 158 */ "term ::= INTEGER",
- /* 159 */ "expr ::= VARIABLE",
- /* 160 */ "expr ::= expr COLLATE ID|STRING",
- /* 161 */ "expr ::= CAST LP expr AS typetoken RP",
- /* 162 */ "expr ::= ID|INDEXED LP distinct exprlist RP",
- /* 163 */ "expr ::= ID|INDEXED LP STAR RP",
- /* 164 */ "term ::= CTIME_KW",
- /* 165 */ "expr ::= LP nexprlist COMMA expr RP",
- /* 166 */ "expr ::= expr AND expr",
- /* 167 */ "expr ::= expr OR expr",
- /* 168 */ "expr ::= expr LT|GT|GE|LE expr",
- /* 169 */ "expr ::= expr EQ|NE expr",
- /* 170 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr",
- /* 171 */ "expr ::= expr PLUS|MINUS expr",
- /* 172 */ "expr ::= expr STAR|SLASH|REM expr",
- /* 173 */ "expr ::= expr CONCAT expr",
- /* 174 */ "likeop ::= LIKE_KW|MATCH",
- /* 175 */ "likeop ::= NOT LIKE_KW|MATCH",
- /* 176 */ "expr ::= expr likeop expr",
- /* 177 */ "expr ::= expr likeop expr ESCAPE expr",
- /* 178 */ "expr ::= expr ISNULL|NOTNULL",
- /* 179 */ "expr ::= expr NOT NULL",
- /* 180 */ "expr ::= expr IS expr",
- /* 181 */ "expr ::= expr IS NOT expr",
- /* 182 */ "expr ::= NOT expr",
- /* 183 */ "expr ::= BITNOT expr",
- /* 184 */ "expr ::= MINUS expr",
- /* 185 */ "expr ::= PLUS expr",
- /* 186 */ "between_op ::= BETWEEN",
- /* 187 */ "between_op ::= NOT BETWEEN",
- /* 188 */ "expr ::= expr between_op expr AND expr",
- /* 189 */ "in_op ::= IN",
- /* 190 */ "in_op ::= NOT IN",
- /* 191 */ "expr ::= expr in_op LP exprlist RP",
- /* 192 */ "expr ::= LP select RP",
- /* 193 */ "expr ::= expr in_op LP select RP",
- /* 194 */ "expr ::= expr in_op nm dbnm paren_exprlist",
- /* 195 */ "expr ::= EXISTS LP select RP",
- /* 196 */ "expr ::= CASE case_operand case_exprlist case_else END",
- /* 197 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr",
- /* 198 */ "case_exprlist ::= WHEN expr THEN expr",
- /* 199 */ "case_else ::= ELSE expr",
- /* 200 */ "case_else ::=",
- /* 201 */ "case_operand ::= expr",
- /* 202 */ "case_operand ::=",
- /* 203 */ "exprlist ::=",
- /* 204 */ "nexprlist ::= nexprlist COMMA expr",
- /* 205 */ "nexprlist ::= expr",
- /* 206 */ "paren_exprlist ::=",
- /* 207 */ "paren_exprlist ::= LP exprlist RP",
- /* 208 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt",
- /* 209 */ "uniqueflag ::= UNIQUE",
- /* 210 */ "uniqueflag ::=",
- /* 211 */ "eidlist_opt ::=",
- /* 212 */ "eidlist_opt ::= LP eidlist RP",
- /* 213 */ "eidlist ::= eidlist COMMA nm collate sortorder",
- /* 214 */ "eidlist ::= nm collate sortorder",
- /* 215 */ "collate ::=",
- /* 216 */ "collate ::= COLLATE ID|STRING",
- /* 217 */ "cmd ::= DROP INDEX ifexists fullname",
- /* 218 */ "cmd ::= VACUUM",
- /* 219 */ "cmd ::= VACUUM nm",
- /* 220 */ "cmd ::= PRAGMA nm dbnm",
- /* 221 */ "cmd ::= PRAGMA nm dbnm EQ nmnum",
- /* 222 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP",
- /* 223 */ "cmd ::= PRAGMA nm dbnm EQ minus_num",
- /* 224 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP",
- /* 225 */ "plus_num ::= PLUS INTEGER|FLOAT",
- /* 226 */ "minus_num ::= MINUS INTEGER|FLOAT",
- /* 227 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END",
- /* 228 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause",
- /* 229 */ "trigger_time ::= BEFORE",
- /* 230 */ "trigger_time ::= AFTER",
- /* 231 */ "trigger_time ::= INSTEAD OF",
- /* 232 */ "trigger_time ::=",
- /* 233 */ "trigger_event ::= DELETE|INSERT",
- /* 234 */ "trigger_event ::= UPDATE",
- /* 235 */ "trigger_event ::= UPDATE OF idlist",
- /* 236 */ "when_clause ::=",
- /* 237 */ "when_clause ::= WHEN expr",
- /* 238 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI",
- /* 239 */ "trigger_cmd_list ::= trigger_cmd SEMI",
- /* 240 */ "trnm ::= nm DOT nm",
- /* 241 */ "tridxby ::= INDEXED BY nm",
- /* 242 */ "tridxby ::= NOT INDEXED",
- /* 243 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt",
- /* 244 */ "trigger_cmd ::= insert_cmd INTO trnm idlist_opt select",
- /* 245 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt",
- /* 246 */ "trigger_cmd ::= select",
- /* 247 */ "expr ::= RAISE LP IGNORE RP",
- /* 248 */ "expr ::= RAISE LP raisetype COMMA nm RP",
- /* 249 */ "raisetype ::= ROLLBACK",
- /* 250 */ "raisetype ::= ABORT",
- /* 251 */ "raisetype ::= FAIL",
- /* 252 */ "cmd ::= DROP TRIGGER ifexists fullname",
- /* 253 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt",
- /* 254 */ "cmd ::= DETACH database_kw_opt expr",
- /* 255 */ "key_opt ::=",
- /* 256 */ "key_opt ::= KEY expr",
- /* 257 */ "cmd ::= REINDEX",
- /* 258 */ "cmd ::= REINDEX nm dbnm",
- /* 259 */ "cmd ::= ANALYZE",
- /* 260 */ "cmd ::= ANALYZE nm dbnm",
- /* 261 */ "cmd ::= ALTER TABLE fullname RENAME TO nm",
- /* 262 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist",
- /* 263 */ "add_column_fullname ::= fullname",
- /* 264 */ "cmd ::= create_vtab",
- /* 265 */ "cmd ::= create_vtab LP vtabarglist RP",
- /* 266 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm",
- /* 267 */ "vtabarg ::=",
- /* 268 */ "vtabargtoken ::= ANY",
- /* 269 */ "vtabargtoken ::= lp anylist RP",
- /* 270 */ "lp ::= LP",
- /* 271 */ "with ::=",
- /* 272 */ "with ::= WITH wqlist",
- /* 273 */ "with ::= WITH RECURSIVE wqlist",
- /* 274 */ "wqlist ::= nm eidlist_opt AS LP select RP",
- /* 275 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP",
- /* 276 */ "input ::= cmdlist",
- /* 277 */ "cmdlist ::= cmdlist ecmd",
- /* 278 */ "cmdlist ::= ecmd",
- /* 279 */ "ecmd ::= SEMI",
- /* 280 */ "ecmd ::= explain cmdx SEMI",
- /* 281 */ "explain ::=",
- /* 282 */ "trans_opt ::=",
- /* 283 */ "trans_opt ::= TRANSACTION",
- /* 284 */ "trans_opt ::= TRANSACTION nm",
- /* 285 */ "savepoint_opt ::= SAVEPOINT",
- /* 286 */ "savepoint_opt ::=",
- /* 287 */ "cmd ::= create_table create_table_args",
- /* 288 */ "columnlist ::= columnlist COMMA columnname carglist",
- /* 289 */ "columnlist ::= columnname carglist",
- /* 290 */ "nm ::= ID|INDEXED",
- /* 291 */ "nm ::= STRING",
- /* 292 */ "nm ::= JOIN_KW",
- /* 293 */ "typetoken ::= typename",
- /* 294 */ "typename ::= ID|STRING",
- /* 295 */ "signed ::= plus_num",
- /* 296 */ "signed ::= minus_num",
- /* 297 */ "carglist ::= carglist ccons",
- /* 298 */ "carglist ::=",
- /* 299 */ "ccons ::= NULL onconf",
- /* 300 */ "conslist_opt ::= COMMA conslist",
- /* 301 */ "conslist ::= conslist tconscomma tcons",
- /* 302 */ "conslist ::= tcons",
- /* 303 */ "tconscomma ::=",
- /* 304 */ "defer_subclause_opt ::= defer_subclause",
- /* 305 */ "resolvetype ::= raisetype",
- /* 306 */ "selectnowith ::= oneselect",
- /* 307 */ "oneselect ::= values",
- /* 308 */ "sclp ::= selcollist COMMA",
- /* 309 */ "as ::= ID|STRING",
- /* 310 */ "expr ::= term",
- /* 311 */ "exprlist ::= nexprlist",
- /* 312 */ "nmnum ::= plus_num",
- /* 313 */ "nmnum ::= nm",
- /* 314 */ "nmnum ::= ON",
- /* 315 */ "nmnum ::= DELETE",
- /* 316 */ "nmnum ::= DEFAULT",
- /* 317 */ "plus_num ::= INTEGER|FLOAT",
- /* 318 */ "foreach_clause ::=",
- /* 319 */ "foreach_clause ::= FOR EACH ROW",
- /* 320 */ "trnm ::= nm",
- /* 321 */ "tridxby ::=",
- /* 322 */ "database_kw_opt ::= DATABASE",
- /* 323 */ "database_kw_opt ::=",
- /* 324 */ "kwcolumn_opt ::=",
- /* 325 */ "kwcolumn_opt ::= COLUMNKW",
- /* 326 */ "vtabarglist ::= vtabarg",
- /* 327 */ "vtabarglist ::= vtabarglist COMMA vtabarg",
- /* 328 */ "vtabarg ::= vtabarg vtabargtoken",
- /* 329 */ "anylist ::=",
- /* 330 */ "anylist ::= anylist LP anylist RP",
- /* 331 */ "anylist ::= anylist ANY",
+ /* 8 */ "cmd ::= COMMIT|END trans_opt",
+ /* 9 */ "cmd ::= ROLLBACK trans_opt",
+ /* 10 */ "cmd ::= SAVEPOINT nm",
+ /* 11 */ "cmd ::= RELEASE savepoint_opt nm",
+ /* 12 */ "cmd ::= ROLLBACK trans_opt TO savepoint_opt nm",
+ /* 13 */ "create_table ::= createkw temp TABLE ifnotexists nm dbnm",
+ /* 14 */ "createkw ::= CREATE",
+ /* 15 */ "ifnotexists ::=",
+ /* 16 */ "ifnotexists ::= IF NOT EXISTS",
+ /* 17 */ "temp ::= TEMP",
+ /* 18 */ "temp ::=",
+ /* 19 */ "create_table_args ::= LP columnlist conslist_opt RP table_options",
+ /* 20 */ "create_table_args ::= AS select",
+ /* 21 */ "table_options ::=",
+ /* 22 */ "table_options ::= WITHOUT nm",
+ /* 23 */ "columnname ::= nm typetoken",
+ /* 24 */ "typetoken ::=",
+ /* 25 */ "typetoken ::= typename LP signed RP",
+ /* 26 */ "typetoken ::= typename LP signed COMMA signed RP",
+ /* 27 */ "typename ::= typename ID|STRING",
+ /* 28 */ "scanpt ::=",
+ /* 29 */ "scantok ::=",
+ /* 30 */ "ccons ::= CONSTRAINT nm",
+ /* 31 */ "ccons ::= DEFAULT scantok term",
+ /* 32 */ "ccons ::= DEFAULT LP expr RP",
+ /* 33 */ "ccons ::= DEFAULT PLUS scantok term",
+ /* 34 */ "ccons ::= DEFAULT MINUS scantok term",
+ /* 35 */ "ccons ::= DEFAULT scantok ID|INDEXED",
+ /* 36 */ "ccons ::= NOT NULL onconf",
+ /* 37 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc",
+ /* 38 */ "ccons ::= UNIQUE onconf",
+ /* 39 */ "ccons ::= CHECK LP expr RP",
+ /* 40 */ "ccons ::= REFERENCES nm eidlist_opt refargs",
+ /* 41 */ "ccons ::= defer_subclause",
+ /* 42 */ "ccons ::= COLLATE ID|STRING",
+ /* 43 */ "generated ::= LP expr RP",
+ /* 44 */ "generated ::= LP expr RP ID",
+ /* 45 */ "autoinc ::=",
+ /* 46 */ "autoinc ::= AUTOINCR",
+ /* 47 */ "refargs ::=",
+ /* 48 */ "refargs ::= refargs refarg",
+ /* 49 */ "refarg ::= MATCH nm",
+ /* 50 */ "refarg ::= ON INSERT refact",
+ /* 51 */ "refarg ::= ON DELETE refact",
+ /* 52 */ "refarg ::= ON UPDATE refact",
+ /* 53 */ "refact ::= SET NULL",
+ /* 54 */ "refact ::= SET DEFAULT",
+ /* 55 */ "refact ::= CASCADE",
+ /* 56 */ "refact ::= RESTRICT",
+ /* 57 */ "refact ::= NO ACTION",
+ /* 58 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt",
+ /* 59 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt",
+ /* 60 */ "init_deferred_pred_opt ::=",
+ /* 61 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED",
+ /* 62 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE",
+ /* 63 */ "conslist_opt ::=",
+ /* 64 */ "tconscomma ::= COMMA",
+ /* 65 */ "tcons ::= CONSTRAINT nm",
+ /* 66 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf",
+ /* 67 */ "tcons ::= UNIQUE LP sortlist RP onconf",
+ /* 68 */ "tcons ::= CHECK LP expr RP onconf",
+ /* 69 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt",
+ /* 70 */ "defer_subclause_opt ::=",
+ /* 71 */ "onconf ::=",
+ /* 72 */ "onconf ::= ON CONFLICT resolvetype",
+ /* 73 */ "orconf ::=",
+ /* 74 */ "orconf ::= OR resolvetype",
+ /* 75 */ "resolvetype ::= IGNORE",
+ /* 76 */ "resolvetype ::= REPLACE",
+ /* 77 */ "cmd ::= DROP TABLE ifexists fullname",
+ /* 78 */ "ifexists ::= IF EXISTS",
+ /* 79 */ "ifexists ::=",
+ /* 80 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select",
+ /* 81 */ "cmd ::= DROP VIEW ifexists fullname",
+ /* 82 */ "cmd ::= select",
+ /* 83 */ "select ::= WITH wqlist selectnowith",
+ /* 84 */ "select ::= WITH RECURSIVE wqlist selectnowith",
+ /* 85 */ "select ::= selectnowith",
+ /* 86 */ "selectnowith ::= selectnowith multiselect_op oneselect",
+ /* 87 */ "multiselect_op ::= UNION",
+ /* 88 */ "multiselect_op ::= UNION ALL",
+ /* 89 */ "multiselect_op ::= EXCEPT|INTERSECT",
+ /* 90 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt",
+ /* 91 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt",
+ /* 92 */ "values ::= VALUES LP nexprlist RP",
+ /* 93 */ "values ::= values COMMA LP nexprlist RP",
+ /* 94 */ "distinct ::= DISTINCT",
+ /* 95 */ "distinct ::= ALL",
+ /* 96 */ "distinct ::=",
+ /* 97 */ "sclp ::=",
+ /* 98 */ "selcollist ::= sclp scanpt expr scanpt as",
+ /* 99 */ "selcollist ::= sclp scanpt STAR",
+ /* 100 */ "selcollist ::= sclp scanpt nm DOT STAR",
+ /* 101 */ "as ::= AS nm",
+ /* 102 */ "as ::=",
+ /* 103 */ "from ::=",
+ /* 104 */ "from ::= FROM seltablist",
+ /* 105 */ "stl_prefix ::= seltablist joinop",
+ /* 106 */ "stl_prefix ::=",
+ /* 107 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt",
+ /* 108 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt",
+ /* 109 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt",
+ /* 110 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt",
+ /* 111 */ "dbnm ::=",
+ /* 112 */ "dbnm ::= DOT nm",
+ /* 113 */ "fullname ::= nm",
+ /* 114 */ "fullname ::= nm DOT nm",
+ /* 115 */ "xfullname ::= nm",
+ /* 116 */ "xfullname ::= nm DOT nm",
+ /* 117 */ "xfullname ::= nm DOT nm AS nm",
+ /* 118 */ "xfullname ::= nm AS nm",
+ /* 119 */ "joinop ::= COMMA|JOIN",
+ /* 120 */ "joinop ::= JOIN_KW JOIN",
+ /* 121 */ "joinop ::= JOIN_KW nm JOIN",
+ /* 122 */ "joinop ::= JOIN_KW nm nm JOIN",
+ /* 123 */ "on_opt ::= ON expr",
+ /* 124 */ "on_opt ::=",
+ /* 125 */ "indexed_opt ::=",
+ /* 126 */ "indexed_opt ::= INDEXED BY nm",
+ /* 127 */ "indexed_opt ::= NOT INDEXED",
+ /* 128 */ "using_opt ::= USING LP idlist RP",
+ /* 129 */ "using_opt ::=",
+ /* 130 */ "orderby_opt ::=",
+ /* 131 */ "orderby_opt ::= ORDER BY sortlist",
+ /* 132 */ "sortlist ::= sortlist COMMA expr sortorder nulls",
+ /* 133 */ "sortlist ::= expr sortorder nulls",
+ /* 134 */ "sortorder ::= ASC",
+ /* 135 */ "sortorder ::= DESC",
+ /* 136 */ "sortorder ::=",
+ /* 137 */ "nulls ::= NULLS FIRST",
+ /* 138 */ "nulls ::= NULLS LAST",
+ /* 139 */ "nulls ::=",
+ /* 140 */ "groupby_opt ::=",
+ /* 141 */ "groupby_opt ::= GROUP BY nexprlist",
+ /* 142 */ "having_opt ::=",
+ /* 143 */ "having_opt ::= HAVING expr",
+ /* 144 */ "limit_opt ::=",
+ /* 145 */ "limit_opt ::= LIMIT expr",
+ /* 146 */ "limit_opt ::= LIMIT expr OFFSET expr",
+ /* 147 */ "limit_opt ::= LIMIT expr COMMA expr",
+ /* 148 */ "cmd ::= with DELETE FROM xfullname indexed_opt where_opt",
+ /* 149 */ "where_opt ::=",
+ /* 150 */ "where_opt ::= WHERE expr",
+ /* 151 */ "cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist where_opt",
+ /* 152 */ "setlist ::= setlist COMMA nm EQ expr",
+ /* 153 */ "setlist ::= setlist COMMA LP idlist RP EQ expr",
+ /* 154 */ "setlist ::= nm EQ expr",
+ /* 155 */ "setlist ::= LP idlist RP EQ expr",
+ /* 156 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert",
+ /* 157 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES",
+ /* 158 */ "upsert ::=",
+ /* 159 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt",
+ /* 160 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING",
+ /* 161 */ "upsert ::= ON CONFLICT DO NOTHING",
+ /* 162 */ "insert_cmd ::= INSERT orconf",
+ /* 163 */ "insert_cmd ::= REPLACE",
+ /* 164 */ "idlist_opt ::=",
+ /* 165 */ "idlist_opt ::= LP idlist RP",
+ /* 166 */ "idlist ::= idlist COMMA nm",
+ /* 167 */ "idlist ::= nm",
+ /* 168 */ "expr ::= LP expr RP",
+ /* 169 */ "expr ::= ID|INDEXED",
+ /* 170 */ "expr ::= JOIN_KW",
+ /* 171 */ "expr ::= nm DOT nm",
+ /* 172 */ "expr ::= nm DOT nm DOT nm",
+ /* 173 */ "term ::= NULL|FLOAT|BLOB",
+ /* 174 */ "term ::= STRING",
+ /* 175 */ "term ::= INTEGER",
+ /* 176 */ "expr ::= VARIABLE",
+ /* 177 */ "expr ::= expr COLLATE ID|STRING",
+ /* 178 */ "expr ::= CAST LP expr AS typetoken RP",
+ /* 179 */ "expr ::= ID|INDEXED LP distinct exprlist RP",
+ /* 180 */ "expr ::= ID|INDEXED LP STAR RP",
+ /* 181 */ "expr ::= ID|INDEXED LP distinct exprlist RP filter_over",
+ /* 182 */ "expr ::= ID|INDEXED LP STAR RP filter_over",
+ /* 183 */ "term ::= CTIME_KW",
+ /* 184 */ "expr ::= LP nexprlist COMMA expr RP",
+ /* 185 */ "expr ::= expr AND expr",
+ /* 186 */ "expr ::= expr OR expr",
+ /* 187 */ "expr ::= expr LT|GT|GE|LE expr",
+ /* 188 */ "expr ::= expr EQ|NE expr",
+ /* 189 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr",
+ /* 190 */ "expr ::= expr PLUS|MINUS expr",
+ /* 191 */ "expr ::= expr STAR|SLASH|REM expr",
+ /* 192 */ "expr ::= expr CONCAT expr",
+ /* 193 */ "likeop ::= NOT LIKE_KW|MATCH",
+ /* 194 */ "expr ::= expr likeop expr",
+ /* 195 */ "expr ::= expr likeop expr ESCAPE expr",
+ /* 196 */ "expr ::= expr ISNULL|NOTNULL",
+ /* 197 */ "expr ::= expr NOT NULL",
+ /* 198 */ "expr ::= expr IS expr",
+ /* 199 */ "expr ::= expr IS NOT expr",
+ /* 200 */ "expr ::= NOT expr",
+ /* 201 */ "expr ::= BITNOT expr",
+ /* 202 */ "expr ::= PLUS|MINUS expr",
+ /* 203 */ "between_op ::= BETWEEN",
+ /* 204 */ "between_op ::= NOT BETWEEN",
+ /* 205 */ "expr ::= expr between_op expr AND expr",
+ /* 206 */ "in_op ::= IN",
+ /* 207 */ "in_op ::= NOT IN",
+ /* 208 */ "expr ::= expr in_op LP exprlist RP",
+ /* 209 */ "expr ::= LP select RP",
+ /* 210 */ "expr ::= expr in_op LP select RP",
+ /* 211 */ "expr ::= expr in_op nm dbnm paren_exprlist",
+ /* 212 */ "expr ::= EXISTS LP select RP",
+ /* 213 */ "expr ::= CASE case_operand case_exprlist case_else END",
+ /* 214 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr",
+ /* 215 */ "case_exprlist ::= WHEN expr THEN expr",
+ /* 216 */ "case_else ::= ELSE expr",
+ /* 217 */ "case_else ::=",
+ /* 218 */ "case_operand ::= expr",
+ /* 219 */ "case_operand ::=",
+ /* 220 */ "exprlist ::=",
+ /* 221 */ "nexprlist ::= nexprlist COMMA expr",
+ /* 222 */ "nexprlist ::= expr",
+ /* 223 */ "paren_exprlist ::=",
+ /* 224 */ "paren_exprlist ::= LP exprlist RP",
+ /* 225 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt",
+ /* 226 */ "uniqueflag ::= UNIQUE",
+ /* 227 */ "uniqueflag ::=",
+ /* 228 */ "eidlist_opt ::=",
+ /* 229 */ "eidlist_opt ::= LP eidlist RP",
+ /* 230 */ "eidlist ::= eidlist COMMA nm collate sortorder",
+ /* 231 */ "eidlist ::= nm collate sortorder",
+ /* 232 */ "collate ::=",
+ /* 233 */ "collate ::= COLLATE ID|STRING",
+ /* 234 */ "cmd ::= DROP INDEX ifexists fullname",
+ /* 235 */ "cmd ::= VACUUM vinto",
+ /* 236 */ "cmd ::= VACUUM nm vinto",
+ /* 237 */ "vinto ::= INTO expr",
+ /* 238 */ "vinto ::=",
+ /* 239 */ "cmd ::= PRAGMA nm dbnm",
+ /* 240 */ "cmd ::= PRAGMA nm dbnm EQ nmnum",
+ /* 241 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP",
+ /* 242 */ "cmd ::= PRAGMA nm dbnm EQ minus_num",
+ /* 243 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP",
+ /* 244 */ "plus_num ::= PLUS INTEGER|FLOAT",
+ /* 245 */ "minus_num ::= MINUS INTEGER|FLOAT",
+ /* 246 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END",
+ /* 247 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause",
+ /* 248 */ "trigger_time ::= BEFORE|AFTER",
+ /* 249 */ "trigger_time ::= INSTEAD OF",
+ /* 250 */ "trigger_time ::=",
+ /* 251 */ "trigger_event ::= DELETE|INSERT",
+ /* 252 */ "trigger_event ::= UPDATE",
+ /* 253 */ "trigger_event ::= UPDATE OF idlist",
+ /* 254 */ "when_clause ::=",
+ /* 255 */ "when_clause ::= WHEN expr",
+ /* 256 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI",
+ /* 257 */ "trigger_cmd_list ::= trigger_cmd SEMI",
+ /* 258 */ "trnm ::= nm DOT nm",
+ /* 259 */ "tridxby ::= INDEXED BY nm",
+ /* 260 */ "tridxby ::= NOT INDEXED",
+ /* 261 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt scanpt",
+ /* 262 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt",
+ /* 263 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt",
+ /* 264 */ "trigger_cmd ::= scanpt select scanpt",
+ /* 265 */ "expr ::= RAISE LP IGNORE RP",
+ /* 266 */ "expr ::= RAISE LP raisetype COMMA nm RP",
+ /* 267 */ "raisetype ::= ROLLBACK",
+ /* 268 */ "raisetype ::= ABORT",
+ /* 269 */ "raisetype ::= FAIL",
+ /* 270 */ "cmd ::= DROP TRIGGER ifexists fullname",
+ /* 271 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt",
+ /* 272 */ "cmd ::= DETACH database_kw_opt expr",
+ /* 273 */ "key_opt ::=",
+ /* 274 */ "key_opt ::= KEY expr",
+ /* 275 */ "cmd ::= REINDEX",
+ /* 276 */ "cmd ::= REINDEX nm dbnm",
+ /* 277 */ "cmd ::= ANALYZE",
+ /* 278 */ "cmd ::= ANALYZE nm dbnm",
+ /* 279 */ "cmd ::= ALTER TABLE fullname RENAME TO nm",
+ /* 280 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist",
+ /* 281 */ "add_column_fullname ::= fullname",
+ /* 282 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm",
+ /* 283 */ "cmd ::= create_vtab",
+ /* 284 */ "cmd ::= create_vtab LP vtabarglist RP",
+ /* 285 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm",
+ /* 286 */ "vtabarg ::=",
+ /* 287 */ "vtabargtoken ::= ANY",
+ /* 288 */ "vtabargtoken ::= lp anylist RP",
+ /* 289 */ "lp ::= LP",
+ /* 290 */ "with ::= WITH wqlist",
+ /* 291 */ "with ::= WITH RECURSIVE wqlist",
+ /* 292 */ "wqlist ::= nm eidlist_opt AS LP select RP",
+ /* 293 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP",
+ /* 294 */ "windowdefn_list ::= windowdefn",
+ /* 295 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn",
+ /* 296 */ "windowdefn ::= nm AS LP window RP",
+ /* 297 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt",
+ /* 298 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt",
+ /* 299 */ "window ::= ORDER BY sortlist frame_opt",
+ /* 300 */ "window ::= nm ORDER BY sortlist frame_opt",
+ /* 301 */ "window ::= frame_opt",
+ /* 302 */ "window ::= nm frame_opt",
+ /* 303 */ "frame_opt ::=",
+ /* 304 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt",
+ /* 305 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt",
+ /* 306 */ "range_or_rows ::= RANGE|ROWS|GROUPS",
+ /* 307 */ "frame_bound_s ::= frame_bound",
+ /* 308 */ "frame_bound_s ::= UNBOUNDED PRECEDING",
+ /* 309 */ "frame_bound_e ::= frame_bound",
+ /* 310 */ "frame_bound_e ::= UNBOUNDED FOLLOWING",
+ /* 311 */ "frame_bound ::= expr PRECEDING|FOLLOWING",
+ /* 312 */ "frame_bound ::= CURRENT ROW",
+ /* 313 */ "frame_exclude_opt ::=",
+ /* 314 */ "frame_exclude_opt ::= EXCLUDE frame_exclude",
+ /* 315 */ "frame_exclude ::= NO OTHERS",
+ /* 316 */ "frame_exclude ::= CURRENT ROW",
+ /* 317 */ "frame_exclude ::= GROUP|TIES",
+ /* 318 */ "window_clause ::= WINDOW windowdefn_list",
+ /* 319 */ "filter_over ::= filter_clause over_clause",
+ /* 320 */ "filter_over ::= over_clause",
+ /* 321 */ "filter_over ::= filter_clause",
+ /* 322 */ "over_clause ::= OVER LP window RP",
+ /* 323 */ "over_clause ::= OVER nm",
+ /* 324 */ "filter_clause ::= FILTER LP WHERE expr RP",
+ /* 325 */ "input ::= cmdlist",
+ /* 326 */ "cmdlist ::= cmdlist ecmd",
+ /* 327 */ "cmdlist ::= ecmd",
+ /* 328 */ "ecmd ::= SEMI",
+ /* 329 */ "ecmd ::= cmdx SEMI",
+ /* 330 */ "ecmd ::= explain cmdx SEMI",
+ /* 331 */ "trans_opt ::=",
+ /* 332 */ "trans_opt ::= TRANSACTION",
+ /* 333 */ "trans_opt ::= TRANSACTION nm",
+ /* 334 */ "savepoint_opt ::= SAVEPOINT",
+ /* 335 */ "savepoint_opt ::=",
+ /* 336 */ "cmd ::= create_table create_table_args",
+ /* 337 */ "columnlist ::= columnlist COMMA columnname carglist",
+ /* 338 */ "columnlist ::= columnname carglist",
+ /* 339 */ "nm ::= ID|INDEXED",
+ /* 340 */ "nm ::= STRING",
+ /* 341 */ "nm ::= JOIN_KW",
+ /* 342 */ "typetoken ::= typename",
+ /* 343 */ "typename ::= ID|STRING",
+ /* 344 */ "signed ::= plus_num",
+ /* 345 */ "signed ::= minus_num",
+ /* 346 */ "carglist ::= carglist ccons",
+ /* 347 */ "carglist ::=",
+ /* 348 */ "ccons ::= NULL onconf",
+ /* 349 */ "ccons ::= GENERATED ALWAYS AS generated",
+ /* 350 */ "ccons ::= AS generated",
+ /* 351 */ "conslist_opt ::= COMMA conslist",
+ /* 352 */ "conslist ::= conslist tconscomma tcons",
+ /* 353 */ "conslist ::= tcons",
+ /* 354 */ "tconscomma ::=",
+ /* 355 */ "defer_subclause_opt ::= defer_subclause",
+ /* 356 */ "resolvetype ::= raisetype",
+ /* 357 */ "selectnowith ::= oneselect",
+ /* 358 */ "oneselect ::= values",
+ /* 359 */ "sclp ::= selcollist COMMA",
+ /* 360 */ "as ::= ID|STRING",
+ /* 361 */ "expr ::= term",
+ /* 362 */ "likeop ::= LIKE_KW|MATCH",
+ /* 363 */ "exprlist ::= nexprlist",
+ /* 364 */ "nmnum ::= plus_num",
+ /* 365 */ "nmnum ::= nm",
+ /* 366 */ "nmnum ::= ON",
+ /* 367 */ "nmnum ::= DELETE",
+ /* 368 */ "nmnum ::= DEFAULT",
+ /* 369 */ "plus_num ::= INTEGER|FLOAT",
+ /* 370 */ "foreach_clause ::=",
+ /* 371 */ "foreach_clause ::= FOR EACH ROW",
+ /* 372 */ "trnm ::= nm",
+ /* 373 */ "tridxby ::=",
+ /* 374 */ "database_kw_opt ::= DATABASE",
+ /* 375 */ "database_kw_opt ::=",
+ /* 376 */ "kwcolumn_opt ::=",
+ /* 377 */ "kwcolumn_opt ::= COLUMNKW",
+ /* 378 */ "vtabarglist ::= vtabarg",
+ /* 379 */ "vtabarglist ::= vtabarglist COMMA vtabarg",
+ /* 380 */ "vtabarg ::= vtabarg vtabargtoken",
+ /* 381 */ "anylist ::=",
+ /* 382 */ "anylist ::= anylist LP anylist RP",
+ /* 383 */ "anylist ::= anylist ANY",
+ /* 384 */ "with ::=",
};
#endif /* NDEBUG */
@@ -137408,7 +157912,7 @@ static int yyGrowStack(yyParser *p){
#endif
/* Datatype of the argument to the memory allocated passed as the
-** second argument to sqlite3ParserAlloc() below. This can be changed by
+** second argument to tdsqlite3ParserAlloc() below. This can be changed by
** putting an appropriate #define in the %include section of the input
** grammar.
*/
@@ -137416,6 +157920,35 @@ static int yyGrowStack(yyParser *p){
# define YYMALLOCARGTYPE size_t
#endif
+/* Initialize a new parser that has already been allocated.
+*/
+SQLITE_PRIVATE void tdsqlite3ParserInit(void *yypRawParser tdsqlite3ParserCTX_PDECL){
+ yyParser *yypParser = (yyParser*)yypRawParser;
+ tdsqlite3ParserCTX_STORE
+#ifdef YYTRACKMAXSTACKDEPTH
+ yypParser->yyhwm = 0;
+#endif
+#if YYSTACKDEPTH<=0
+ yypParser->yytos = NULL;
+ yypParser->yystack = NULL;
+ yypParser->yystksz = 0;
+ if( yyGrowStack(yypParser) ){
+ yypParser->yystack = &yypParser->yystk0;
+ yypParser->yystksz = 1;
+ }
+#endif
+#ifndef YYNOERRORRECOVERY
+ yypParser->yyerrcnt = -1;
+#endif
+ yypParser->yytos = yypParser->yystack;
+ yypParser->yystack[0].stateno = 0;
+ yypParser->yystack[0].major = 0;
+#if YYSTACKDEPTH>0
+ yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1];
+#endif
+}
+
+#ifndef tdsqlite3Parser_ENGINEALWAYSONSTACK
/*
** This function allocates a new parser.
** The only argument is a pointer to a function which works like
@@ -137426,33 +157959,19 @@ static int yyGrowStack(yyParser *p){
**
** Outputs:
** A pointer to a parser. This pointer is used in subsequent calls
-** to sqlite3Parser and sqlite3ParserFree.
+** to tdsqlite3Parser and tdsqlite3ParserFree.
*/
-SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(YYMALLOCARGTYPE)){
- yyParser *pParser;
- pParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) );
- if( pParser ){
-#ifdef YYTRACKMAXSTACKDEPTH
- pParser->yyhwm = 0;
-#endif
-#if YYSTACKDEPTH<=0
- pParser->yytos = NULL;
- pParser->yystack = NULL;
- pParser->yystksz = 0;
- if( yyGrowStack(pParser) ){
- pParser->yystack = &pParser->yystk0;
- pParser->yystksz = 1;
- }
-#endif
-#ifndef YYNOERRORRECOVERY
- pParser->yyerrcnt = -1;
-#endif
- pParser->yytos = pParser->yystack;
- pParser->yystack[0].stateno = 0;
- pParser->yystack[0].major = 0;
+SQLITE_PRIVATE void *tdsqlite3ParserAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) tdsqlite3ParserCTX_PDECL){
+ yyParser *yypParser;
+ yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) );
+ if( yypParser ){
+ tdsqlite3ParserCTX_STORE
+ tdsqlite3ParserInit(yypParser tdsqlite3ParserCTX_PARAM);
}
- return pParser;
+ return (void*)yypParser;
}
+#endif /* tdsqlite3Parser_ENGINEALWAYSONSTACK */
+
/* The following function deletes the "minor type" or semantic value
** associated with a symbol. The symbol can be either a terminal
@@ -137466,7 +157985,8 @@ static void yy_destructor(
YYCODETYPE yymajor, /* Type code for object to destroy */
YYMINORTYPE *yypminor /* The object to be destroyed */
){
- sqlite3ParserARG_FETCH;
+ tdsqlite3ParserARG_FETCH
+ tdsqlite3ParserCTX_FETCH
switch( yymajor ){
/* Here is inserted the actions which take place when a
** terminal or non-terminal is destroyed. This can happen
@@ -137479,77 +157999,98 @@ static void yy_destructor(
** inside the C code.
*/
/********* Begin destructor definitions ***************************************/
- case 163: /* select */
- case 194: /* selectnowith */
- case 195: /* oneselect */
- case 206: /* values */
+ case 200: /* select */
+ case 234: /* selectnowith */
+ case 235: /* oneselect */
+ case 247: /* values */
+{
+tdsqlite3SelectDelete(pParse->db, (yypminor->yy539));
+}
+ break;
+ case 211: /* term */
+ case 212: /* expr */
+ case 241: /* where_opt */
+ case 243: /* having_opt */
+ case 255: /* on_opt */
+ case 271: /* case_operand */
+ case 273: /* case_else */
+ case 276: /* vinto */
+ case 283: /* when_clause */
+ case 288: /* key_opt */
+ case 302: /* filter_clause */
+{
+tdsqlite3ExprDelete(pParse->db, (yypminor->yy202));
+}
+ break;
+ case 216: /* eidlist_opt */
+ case 226: /* sortlist */
+ case 227: /* eidlist */
+ case 239: /* selcollist */
+ case 242: /* groupby_opt */
+ case 244: /* orderby_opt */
+ case 248: /* nexprlist */
+ case 249: /* sclp */
+ case 257: /* exprlist */
+ case 262: /* setlist */
+ case 270: /* paren_exprlist */
+ case 272: /* case_exprlist */
+ case 301: /* part_opt */
{
-sqlite3SelectDelete(pParse->db, (yypminor->yy243));
+tdsqlite3ExprListDelete(pParse->db, (yypminor->yy242));
}
break;
- case 172: /* term */
- case 173: /* expr */
+ case 233: /* fullname */
+ case 240: /* from */
+ case 251: /* seltablist */
+ case 252: /* stl_prefix */
+ case 258: /* xfullname */
{
-sqlite3ExprDelete(pParse->db, (yypminor->yy190).pExpr);
+tdsqlite3SrcListDelete(pParse->db, (yypminor->yy47));
}
break;
- case 177: /* eidlist_opt */
- case 186: /* sortlist */
- case 187: /* eidlist */
- case 199: /* selcollist */
- case 202: /* groupby_opt */
- case 204: /* orderby_opt */
- case 207: /* nexprlist */
- case 208: /* exprlist */
- case 209: /* sclp */
- case 218: /* setlist */
- case 224: /* paren_exprlist */
- case 226: /* case_exprlist */
+ case 236: /* wqlist */
{
-sqlite3ExprListDelete(pParse->db, (yypminor->yy148));
+tdsqlite3WithDelete(pParse->db, (yypminor->yy131));
}
break;
- case 193: /* fullname */
- case 200: /* from */
- case 211: /* seltablist */
- case 212: /* stl_prefix */
+ case 246: /* window_clause */
+ case 297: /* windowdefn_list */
{
-sqlite3SrcListDelete(pParse->db, (yypminor->yy185));
+tdsqlite3WindowListDelete(pParse->db, (yypminor->yy303));
}
break;
- case 196: /* with */
- case 250: /* wqlist */
+ case 256: /* using_opt */
+ case 259: /* idlist */
+ case 264: /* idlist_opt */
{
-sqlite3WithDelete(pParse->db, (yypminor->yy285));
+tdsqlite3IdListDelete(pParse->db, (yypminor->yy600));
}
break;
- case 201: /* where_opt */
- case 203: /* having_opt */
- case 215: /* on_opt */
- case 225: /* case_operand */
- case 227: /* case_else */
- case 236: /* when_clause */
- case 241: /* key_opt */
+ case 266: /* filter_over */
+ case 298: /* windowdefn */
+ case 299: /* window */
+ case 300: /* frame_opt */
+ case 303: /* over_clause */
{
-sqlite3ExprDelete(pParse->db, (yypminor->yy72));
+tdsqlite3WindowDelete(pParse->db, (yypminor->yy303));
}
break;
- case 216: /* using_opt */
- case 217: /* idlist */
- case 220: /* idlist_opt */
+ case 279: /* trigger_cmd_list */
+ case 284: /* trigger_cmd */
{
-sqlite3IdListDelete(pParse->db, (yypminor->yy254));
+tdsqlite3DeleteTriggerStep(pParse->db, (yypminor->yy447));
}
break;
- case 232: /* trigger_cmd_list */
- case 237: /* trigger_cmd */
+ case 281: /* trigger_event */
{
-sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy145));
+tdsqlite3IdListDelete(pParse->db, (yypminor->yy230).b);
}
break;
- case 234: /* trigger_event */
+ case 305: /* frame_bound */
+ case 306: /* frame_bound_s */
+ case 307: /* frame_bound_e */
{
-sqlite3IdListDelete(pParse->db, (yypminor->yy332).b);
+tdsqlite3ExprDelete(pParse->db, (yypminor->yy77).pExpr);
}
break;
/********* End destructor definitions *****************************************/
@@ -137578,6 +158119,18 @@ static void yy_pop_parser_stack(yyParser *pParser){
yy_destructor(pParser, yytos->major, &yytos->minor);
}
+/*
+** Clear all secondary memory allocations from the parser
+*/
+SQLITE_PRIVATE void tdsqlite3ParserFinalize(void *p){
+ yyParser *pParser = (yyParser*)p;
+ while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser);
+#if YYSTACKDEPTH<=0
+ if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack);
+#endif
+}
+
+#ifndef tdsqlite3Parser_ENGINEALWAYSONSTACK
/*
** Deallocate and destroy a parser. Destructors are called for
** all stack elements before shutting the parser down.
@@ -137586,53 +158139,95 @@ static void yy_pop_parser_stack(yyParser *pParser){
** is defined in a %include section of the input grammar) then it is
** assumed that the input pointer is never NULL.
*/
-SQLITE_PRIVATE void sqlite3ParserFree(
+SQLITE_PRIVATE void tdsqlite3ParserFree(
void *p, /* The parser to be deleted */
void (*freeProc)(void*) /* Function used to reclaim memory */
){
- yyParser *pParser = (yyParser*)p;
#ifndef YYPARSEFREENEVERNULL
- if( pParser==0 ) return;
-#endif
- while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser);
-#if YYSTACKDEPTH<=0
- if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack);
+ if( p==0 ) return;
#endif
- (*freeProc)((void*)pParser);
+ tdsqlite3ParserFinalize(p);
+ (*freeProc)(p);
}
+#endif /* tdsqlite3Parser_ENGINEALWAYSONSTACK */
/*
** Return the peak depth of the stack for a parser.
*/
#ifdef YYTRACKMAXSTACKDEPTH
-SQLITE_PRIVATE int sqlite3ParserStackPeak(void *p){
+SQLITE_PRIVATE int tdsqlite3ParserStackPeak(void *p){
yyParser *pParser = (yyParser*)p;
return pParser->yyhwm;
}
#endif
+/* This array of booleans keeps track of the parser statement
+** coverage. The element yycoverage[X][Y] is set when the parser
+** is in state X and has a lookahead token Y. In a well-tested
+** systems, every element of this matrix should end up being set.
+*/
+#if defined(YYCOVERAGE)
+static unsigned char yycoverage[YYNSTATE][YYNTOKEN];
+#endif
+
+/*
+** Write into out a description of every state/lookahead combination that
+**
+** (1) has not been used by the parser, and
+** (2) is not a syntax error.
+**
+** Return the number of missed state/lookahead combinations.
+*/
+#if defined(YYCOVERAGE)
+SQLITE_PRIVATE int tdsqlite3ParserCoverage(FILE *out){
+ int stateno, iLookAhead, i;
+ int nMissed = 0;
+ for(stateno=0; stateno<YYNSTATE; stateno++){
+ i = yy_shift_ofst[stateno];
+ for(iLookAhead=0; iLookAhead<YYNTOKEN; iLookAhead++){
+ if( yy_lookahead[i+iLookAhead]!=iLookAhead ) continue;
+ if( yycoverage[stateno][iLookAhead]==0 ) nMissed++;
+ if( out ){
+ fprintf(out,"State %d lookahead %s %s\n", stateno,
+ yyTokenName[iLookAhead],
+ yycoverage[stateno][iLookAhead] ? "ok" : "missed");
+ }
+ }
+ }
+ return nMissed;
+}
+#endif
+
/*
** Find the appropriate action for a parser given the terminal
** look-ahead token iLookAhead.
*/
-static unsigned int yy_find_shift_action(
- yyParser *pParser, /* The parser */
- YYCODETYPE iLookAhead /* The look-ahead token */
+static YYACTIONTYPE yy_find_shift_action(
+ YYCODETYPE iLookAhead, /* The look-ahead token */
+ YYACTIONTYPE stateno /* Current state number */
){
int i;
- int stateno = pParser->yytos->stateno;
-
- if( stateno>=YY_MIN_REDUCE ) return stateno;
+
+ if( stateno>YY_MAX_SHIFT ) return stateno;
assert( stateno <= YY_SHIFT_COUNT );
+#if defined(YYCOVERAGE)
+ yycoverage[stateno][iLookAhead] = 1;
+#endif
do{
i = yy_shift_ofst[stateno];
+ assert( i>=0 );
+ assert( i<=YY_ACTTAB_COUNT );
+ assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD );
assert( iLookAhead!=YYNOCODE );
+ assert( iLookAhead < YYNTOKEN );
i += iLookAhead;
- if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){
+ assert( i<(int)YY_NLOOKAHEAD );
+ if( yy_lookahead[i]!=iLookAhead ){
#ifdef YYFALLBACK
YYCODETYPE iFallback; /* Fallback token */
- if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
- && (iFallback = yyFallback[iLookAhead])!=0 ){
+ assert( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0]) );
+ iFallback = yyFallback[iLookAhead];
+ if( iFallback!=0 ){
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
@@ -137647,15 +158242,8 @@ static unsigned int yy_find_shift_action(
#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 && iLookAhead>0
- ){
+ assert( j<(int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])) );
+ if( yy_lookahead[j]==YYWILDCARD && iLookAhead>0 ){
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n",
@@ -137669,6 +158257,7 @@ static unsigned int yy_find_shift_action(
#endif /* YYWILDCARD */
return yy_default[stateno];
}else{
+ assert( i>=0 && i<sizeof(yy_action)/sizeof(yy_action[0]) );
return yy_action[i];
}
}while(1);
@@ -137678,8 +158267,8 @@ static unsigned int yy_find_shift_action(
** Find the appropriate action for a parser given the non-terminal
** look-ahead token iLookAhead.
*/
-static int yy_find_reduce_action(
- int stateno, /* Current state number */
+static YYACTIONTYPE yy_find_reduce_action(
+ YYACTIONTYPE stateno, /* Current state number */
YYCODETYPE iLookAhead /* The look-ahead token */
){
int i;
@@ -137691,7 +158280,6 @@ static int yy_find_reduce_action(
assert( stateno<=YY_REDUCE_COUNT );
#endif
i = yy_reduce_ofst[stateno];
- assert( i!=YY_REDUCE_USE_DFLT );
assert( iLookAhead!=YYNOCODE );
i += iLookAhead;
#ifdef YYERRORSYMBOL
@@ -137709,8 +158297,8 @@ static int yy_find_reduce_action(
** The following routine is called if the stack overflows.
*/
static void yyStackOverflow(yyParser *yypParser){
- sqlite3ParserARG_FETCH;
- yypParser->yytos--;
+ tdsqlite3ParserARG_FETCH
+ tdsqlite3ParserCTX_FETCH
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
@@ -137721,29 +158309,31 @@ static void yyStackOverflow(yyParser *yypParser){
** stack every overflows */
/******** Begin %stack_overflow code ******************************************/
- sqlite3ErrorMsg(pParse, "parser stack overflow");
+ tdsqlite3ErrorMsg(pParse, "parser stack overflow");
/******** End %stack_overflow code ********************************************/
- sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument var */
+ tdsqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument var */
+ tdsqlite3ParserCTX_STORE
}
/*
** Print tracing information for a SHIFT action
*/
#ifndef NDEBUG
-static void yyTraceShift(yyParser *yypParser, int yyNewState){
+static void yyTraceShift(yyParser *yypParser, int yyNewState, const char *zTag){
if( yyTraceFILE ){
if( yyNewState<YYNSTATE ){
- fprintf(yyTraceFILE,"%sShift '%s', go to state %d\n",
- yyTracePrompt,yyTokenName[yypParser->yytos->major],
+ fprintf(yyTraceFILE,"%s%s '%s', go to state %d\n",
+ yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
yyNewState);
}else{
- fprintf(yyTraceFILE,"%sShift '%s'\n",
- yyTracePrompt,yyTokenName[yypParser->yytos->major]);
+ fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n",
+ yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major],
+ yyNewState - YY_MIN_REDUCE);
}
}
}
#else
-# define yyTraceShift(X,Y)
+# define yyTraceShift(X,Y,Z)
#endif
/*
@@ -137751,9 +158341,9 @@ static void yyTraceShift(yyParser *yypParser, int yyNewState){
*/
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 */
- sqlite3ParserTOKENTYPE yyMinor /* The minor token to shift in */
+ YYACTIONTYPE yyNewState, /* The new state to shift in */
+ YYCODETYPE yyMajor, /* The major token to shift in */
+ tdsqlite3ParserTOKENTYPE yyMinor /* The minor token to shift in */
){
yyStackEntry *yytos;
yypParser->yytos++;
@@ -137764,13 +158354,15 @@ static void yy_shift(
}
#endif
#if YYSTACKDEPTH>0
- if( yypParser->yytos>=&yypParser->yystack[YYSTACKDEPTH] ){
+ if( yypParser->yytos>yypParser->yystackEnd ){
+ yypParser->yytos--;
yyStackOverflow(yypParser);
return;
}
#else
if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){
if( yyGrowStack(yypParser) ){
+ yypParser->yytos--;
yyStackOverflow(yypParser);
return;
}
@@ -137780,351 +158372,790 @@ static void yy_shift(
yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE;
}
yytos = yypParser->yytos;
- yytos->stateno = (YYACTIONTYPE)yyNewState;
- yytos->major = (YYCODETYPE)yyMajor;
+ yytos->stateno = yyNewState;
+ yytos->major = yyMajor;
yytos->minor.yy0 = yyMinor;
- yyTraceShift(yypParser, yyNewState);
-}
+ yyTraceShift(yypParser, yyNewState, "Shift");
+}
+
+/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side
+** of that rule */
+static const YYCODETYPE yyRuleInfoLhs[] = {
+ 185, /* (0) explain ::= EXPLAIN */
+ 185, /* (1) explain ::= EXPLAIN QUERY PLAN */
+ 184, /* (2) cmdx ::= cmd */
+ 186, /* (3) cmd ::= BEGIN transtype trans_opt */
+ 187, /* (4) transtype ::= */
+ 187, /* (5) transtype ::= DEFERRED */
+ 187, /* (6) transtype ::= IMMEDIATE */
+ 187, /* (7) transtype ::= EXCLUSIVE */
+ 186, /* (8) cmd ::= COMMIT|END trans_opt */
+ 186, /* (9) cmd ::= ROLLBACK trans_opt */
+ 186, /* (10) cmd ::= SAVEPOINT nm */
+ 186, /* (11) cmd ::= RELEASE savepoint_opt nm */
+ 186, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */
+ 191, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */
+ 193, /* (14) createkw ::= CREATE */
+ 195, /* (15) ifnotexists ::= */
+ 195, /* (16) ifnotexists ::= IF NOT EXISTS */
+ 194, /* (17) temp ::= TEMP */
+ 194, /* (18) temp ::= */
+ 192, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_options */
+ 192, /* (20) create_table_args ::= AS select */
+ 199, /* (21) table_options ::= */
+ 199, /* (22) table_options ::= WITHOUT nm */
+ 201, /* (23) columnname ::= nm typetoken */
+ 203, /* (24) typetoken ::= */
+ 203, /* (25) typetoken ::= typename LP signed RP */
+ 203, /* (26) typetoken ::= typename LP signed COMMA signed RP */
+ 204, /* (27) typename ::= typename ID|STRING */
+ 208, /* (28) scanpt ::= */
+ 209, /* (29) scantok ::= */
+ 210, /* (30) ccons ::= CONSTRAINT nm */
+ 210, /* (31) ccons ::= DEFAULT scantok term */
+ 210, /* (32) ccons ::= DEFAULT LP expr RP */
+ 210, /* (33) ccons ::= DEFAULT PLUS scantok term */
+ 210, /* (34) ccons ::= DEFAULT MINUS scantok term */
+ 210, /* (35) ccons ::= DEFAULT scantok ID|INDEXED */
+ 210, /* (36) ccons ::= NOT NULL onconf */
+ 210, /* (37) ccons ::= PRIMARY KEY sortorder onconf autoinc */
+ 210, /* (38) ccons ::= UNIQUE onconf */
+ 210, /* (39) ccons ::= CHECK LP expr RP */
+ 210, /* (40) ccons ::= REFERENCES nm eidlist_opt refargs */
+ 210, /* (41) ccons ::= defer_subclause */
+ 210, /* (42) ccons ::= COLLATE ID|STRING */
+ 219, /* (43) generated ::= LP expr RP */
+ 219, /* (44) generated ::= LP expr RP ID */
+ 215, /* (45) autoinc ::= */
+ 215, /* (46) autoinc ::= AUTOINCR */
+ 217, /* (47) refargs ::= */
+ 217, /* (48) refargs ::= refargs refarg */
+ 220, /* (49) refarg ::= MATCH nm */
+ 220, /* (50) refarg ::= ON INSERT refact */
+ 220, /* (51) refarg ::= ON DELETE refact */
+ 220, /* (52) refarg ::= ON UPDATE refact */
+ 221, /* (53) refact ::= SET NULL */
+ 221, /* (54) refact ::= SET DEFAULT */
+ 221, /* (55) refact ::= CASCADE */
+ 221, /* (56) refact ::= RESTRICT */
+ 221, /* (57) refact ::= NO ACTION */
+ 218, /* (58) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */
+ 218, /* (59) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */
+ 222, /* (60) init_deferred_pred_opt ::= */
+ 222, /* (61) init_deferred_pred_opt ::= INITIALLY DEFERRED */
+ 222, /* (62) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */
+ 198, /* (63) conslist_opt ::= */
+ 224, /* (64) tconscomma ::= COMMA */
+ 225, /* (65) tcons ::= CONSTRAINT nm */
+ 225, /* (66) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */
+ 225, /* (67) tcons ::= UNIQUE LP sortlist RP onconf */
+ 225, /* (68) tcons ::= CHECK LP expr RP onconf */
+ 225, /* (69) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */
+ 228, /* (70) defer_subclause_opt ::= */
+ 213, /* (71) onconf ::= */
+ 213, /* (72) onconf ::= ON CONFLICT resolvetype */
+ 229, /* (73) orconf ::= */
+ 229, /* (74) orconf ::= OR resolvetype */
+ 230, /* (75) resolvetype ::= IGNORE */
+ 230, /* (76) resolvetype ::= REPLACE */
+ 186, /* (77) cmd ::= DROP TABLE ifexists fullname */
+ 232, /* (78) ifexists ::= IF EXISTS */
+ 232, /* (79) ifexists ::= */
+ 186, /* (80) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */
+ 186, /* (81) cmd ::= DROP VIEW ifexists fullname */
+ 186, /* (82) cmd ::= select */
+ 200, /* (83) select ::= WITH wqlist selectnowith */
+ 200, /* (84) select ::= WITH RECURSIVE wqlist selectnowith */
+ 200, /* (85) select ::= selectnowith */
+ 234, /* (86) selectnowith ::= selectnowith multiselect_op oneselect */
+ 237, /* (87) multiselect_op ::= UNION */
+ 237, /* (88) multiselect_op ::= UNION ALL */
+ 237, /* (89) multiselect_op ::= EXCEPT|INTERSECT */
+ 235, /* (90) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */
+ 235, /* (91) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */
+ 247, /* (92) values ::= VALUES LP nexprlist RP */
+ 247, /* (93) values ::= values COMMA LP nexprlist RP */
+ 238, /* (94) distinct ::= DISTINCT */
+ 238, /* (95) distinct ::= ALL */
+ 238, /* (96) distinct ::= */
+ 249, /* (97) sclp ::= */
+ 239, /* (98) selcollist ::= sclp scanpt expr scanpt as */
+ 239, /* (99) selcollist ::= sclp scanpt STAR */
+ 239, /* (100) selcollist ::= sclp scanpt nm DOT STAR */
+ 250, /* (101) as ::= AS nm */
+ 250, /* (102) as ::= */
+ 240, /* (103) from ::= */
+ 240, /* (104) from ::= FROM seltablist */
+ 252, /* (105) stl_prefix ::= seltablist joinop */
+ 252, /* (106) stl_prefix ::= */
+ 251, /* (107) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
+ 251, /* (108) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
+ 251, /* (109) seltablist ::= stl_prefix LP select RP as on_opt using_opt */
+ 251, /* (110) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
+ 196, /* (111) dbnm ::= */
+ 196, /* (112) dbnm ::= DOT nm */
+ 233, /* (113) fullname ::= nm */
+ 233, /* (114) fullname ::= nm DOT nm */
+ 258, /* (115) xfullname ::= nm */
+ 258, /* (116) xfullname ::= nm DOT nm */
+ 258, /* (117) xfullname ::= nm DOT nm AS nm */
+ 258, /* (118) xfullname ::= nm AS nm */
+ 253, /* (119) joinop ::= COMMA|JOIN */
+ 253, /* (120) joinop ::= JOIN_KW JOIN */
+ 253, /* (121) joinop ::= JOIN_KW nm JOIN */
+ 253, /* (122) joinop ::= JOIN_KW nm nm JOIN */
+ 255, /* (123) on_opt ::= ON expr */
+ 255, /* (124) on_opt ::= */
+ 254, /* (125) indexed_opt ::= */
+ 254, /* (126) indexed_opt ::= INDEXED BY nm */
+ 254, /* (127) indexed_opt ::= NOT INDEXED */
+ 256, /* (128) using_opt ::= USING LP idlist RP */
+ 256, /* (129) using_opt ::= */
+ 244, /* (130) orderby_opt ::= */
+ 244, /* (131) orderby_opt ::= ORDER BY sortlist */
+ 226, /* (132) sortlist ::= sortlist COMMA expr sortorder nulls */
+ 226, /* (133) sortlist ::= expr sortorder nulls */
+ 214, /* (134) sortorder ::= ASC */
+ 214, /* (135) sortorder ::= DESC */
+ 214, /* (136) sortorder ::= */
+ 260, /* (137) nulls ::= NULLS FIRST */
+ 260, /* (138) nulls ::= NULLS LAST */
+ 260, /* (139) nulls ::= */
+ 242, /* (140) groupby_opt ::= */
+ 242, /* (141) groupby_opt ::= GROUP BY nexprlist */
+ 243, /* (142) having_opt ::= */
+ 243, /* (143) having_opt ::= HAVING expr */
+ 245, /* (144) limit_opt ::= */
+ 245, /* (145) limit_opt ::= LIMIT expr */
+ 245, /* (146) limit_opt ::= LIMIT expr OFFSET expr */
+ 245, /* (147) limit_opt ::= LIMIT expr COMMA expr */
+ 186, /* (148) cmd ::= with DELETE FROM xfullname indexed_opt where_opt */
+ 241, /* (149) where_opt ::= */
+ 241, /* (150) where_opt ::= WHERE expr */
+ 186, /* (151) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist where_opt */
+ 262, /* (152) setlist ::= setlist COMMA nm EQ expr */
+ 262, /* (153) setlist ::= setlist COMMA LP idlist RP EQ expr */
+ 262, /* (154) setlist ::= nm EQ expr */
+ 262, /* (155) setlist ::= LP idlist RP EQ expr */
+ 186, /* (156) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */
+ 186, /* (157) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES */
+ 265, /* (158) upsert ::= */
+ 265, /* (159) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt */
+ 265, /* (160) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING */
+ 265, /* (161) upsert ::= ON CONFLICT DO NOTHING */
+ 263, /* (162) insert_cmd ::= INSERT orconf */
+ 263, /* (163) insert_cmd ::= REPLACE */
+ 264, /* (164) idlist_opt ::= */
+ 264, /* (165) idlist_opt ::= LP idlist RP */
+ 259, /* (166) idlist ::= idlist COMMA nm */
+ 259, /* (167) idlist ::= nm */
+ 212, /* (168) expr ::= LP expr RP */
+ 212, /* (169) expr ::= ID|INDEXED */
+ 212, /* (170) expr ::= JOIN_KW */
+ 212, /* (171) expr ::= nm DOT nm */
+ 212, /* (172) expr ::= nm DOT nm DOT nm */
+ 211, /* (173) term ::= NULL|FLOAT|BLOB */
+ 211, /* (174) term ::= STRING */
+ 211, /* (175) term ::= INTEGER */
+ 212, /* (176) expr ::= VARIABLE */
+ 212, /* (177) expr ::= expr COLLATE ID|STRING */
+ 212, /* (178) expr ::= CAST LP expr AS typetoken RP */
+ 212, /* (179) expr ::= ID|INDEXED LP distinct exprlist RP */
+ 212, /* (180) expr ::= ID|INDEXED LP STAR RP */
+ 212, /* (181) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */
+ 212, /* (182) expr ::= ID|INDEXED LP STAR RP filter_over */
+ 211, /* (183) term ::= CTIME_KW */
+ 212, /* (184) expr ::= LP nexprlist COMMA expr RP */
+ 212, /* (185) expr ::= expr AND expr */
+ 212, /* (186) expr ::= expr OR expr */
+ 212, /* (187) expr ::= expr LT|GT|GE|LE expr */
+ 212, /* (188) expr ::= expr EQ|NE expr */
+ 212, /* (189) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */
+ 212, /* (190) expr ::= expr PLUS|MINUS expr */
+ 212, /* (191) expr ::= expr STAR|SLASH|REM expr */
+ 212, /* (192) expr ::= expr CONCAT expr */
+ 267, /* (193) likeop ::= NOT LIKE_KW|MATCH */
+ 212, /* (194) expr ::= expr likeop expr */
+ 212, /* (195) expr ::= expr likeop expr ESCAPE expr */
+ 212, /* (196) expr ::= expr ISNULL|NOTNULL */
+ 212, /* (197) expr ::= expr NOT NULL */
+ 212, /* (198) expr ::= expr IS expr */
+ 212, /* (199) expr ::= expr IS NOT expr */
+ 212, /* (200) expr ::= NOT expr */
+ 212, /* (201) expr ::= BITNOT expr */
+ 212, /* (202) expr ::= PLUS|MINUS expr */
+ 268, /* (203) between_op ::= BETWEEN */
+ 268, /* (204) between_op ::= NOT BETWEEN */
+ 212, /* (205) expr ::= expr between_op expr AND expr */
+ 269, /* (206) in_op ::= IN */
+ 269, /* (207) in_op ::= NOT IN */
+ 212, /* (208) expr ::= expr in_op LP exprlist RP */
+ 212, /* (209) expr ::= LP select RP */
+ 212, /* (210) expr ::= expr in_op LP select RP */
+ 212, /* (211) expr ::= expr in_op nm dbnm paren_exprlist */
+ 212, /* (212) expr ::= EXISTS LP select RP */
+ 212, /* (213) expr ::= CASE case_operand case_exprlist case_else END */
+ 272, /* (214) case_exprlist ::= case_exprlist WHEN expr THEN expr */
+ 272, /* (215) case_exprlist ::= WHEN expr THEN expr */
+ 273, /* (216) case_else ::= ELSE expr */
+ 273, /* (217) case_else ::= */
+ 271, /* (218) case_operand ::= expr */
+ 271, /* (219) case_operand ::= */
+ 257, /* (220) exprlist ::= */
+ 248, /* (221) nexprlist ::= nexprlist COMMA expr */
+ 248, /* (222) nexprlist ::= expr */
+ 270, /* (223) paren_exprlist ::= */
+ 270, /* (224) paren_exprlist ::= LP exprlist RP */
+ 186, /* (225) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
+ 274, /* (226) uniqueflag ::= UNIQUE */
+ 274, /* (227) uniqueflag ::= */
+ 216, /* (228) eidlist_opt ::= */
+ 216, /* (229) eidlist_opt ::= LP eidlist RP */
+ 227, /* (230) eidlist ::= eidlist COMMA nm collate sortorder */
+ 227, /* (231) eidlist ::= nm collate sortorder */
+ 275, /* (232) collate ::= */
+ 275, /* (233) collate ::= COLLATE ID|STRING */
+ 186, /* (234) cmd ::= DROP INDEX ifexists fullname */
+ 186, /* (235) cmd ::= VACUUM vinto */
+ 186, /* (236) cmd ::= VACUUM nm vinto */
+ 276, /* (237) vinto ::= INTO expr */
+ 276, /* (238) vinto ::= */
+ 186, /* (239) cmd ::= PRAGMA nm dbnm */
+ 186, /* (240) cmd ::= PRAGMA nm dbnm EQ nmnum */
+ 186, /* (241) cmd ::= PRAGMA nm dbnm LP nmnum RP */
+ 186, /* (242) cmd ::= PRAGMA nm dbnm EQ minus_num */
+ 186, /* (243) cmd ::= PRAGMA nm dbnm LP minus_num RP */
+ 206, /* (244) plus_num ::= PLUS INTEGER|FLOAT */
+ 207, /* (245) minus_num ::= MINUS INTEGER|FLOAT */
+ 186, /* (246) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
+ 278, /* (247) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+ 280, /* (248) trigger_time ::= BEFORE|AFTER */
+ 280, /* (249) trigger_time ::= INSTEAD OF */
+ 280, /* (250) trigger_time ::= */
+ 281, /* (251) trigger_event ::= DELETE|INSERT */
+ 281, /* (252) trigger_event ::= UPDATE */
+ 281, /* (253) trigger_event ::= UPDATE OF idlist */
+ 283, /* (254) when_clause ::= */
+ 283, /* (255) when_clause ::= WHEN expr */
+ 279, /* (256) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+ 279, /* (257) trigger_cmd_list ::= trigger_cmd SEMI */
+ 285, /* (258) trnm ::= nm DOT nm */
+ 286, /* (259) tridxby ::= INDEXED BY nm */
+ 286, /* (260) tridxby ::= NOT INDEXED */
+ 284, /* (261) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt scanpt */
+ 284, /* (262) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
+ 284, /* (263) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
+ 284, /* (264) trigger_cmd ::= scanpt select scanpt */
+ 212, /* (265) expr ::= RAISE LP IGNORE RP */
+ 212, /* (266) expr ::= RAISE LP raisetype COMMA nm RP */
+ 231, /* (267) raisetype ::= ROLLBACK */
+ 231, /* (268) raisetype ::= ABORT */
+ 231, /* (269) raisetype ::= FAIL */
+ 186, /* (270) cmd ::= DROP TRIGGER ifexists fullname */
+ 186, /* (271) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+ 186, /* (272) cmd ::= DETACH database_kw_opt expr */
+ 288, /* (273) key_opt ::= */
+ 288, /* (274) key_opt ::= KEY expr */
+ 186, /* (275) cmd ::= REINDEX */
+ 186, /* (276) cmd ::= REINDEX nm dbnm */
+ 186, /* (277) cmd ::= ANALYZE */
+ 186, /* (278) cmd ::= ANALYZE nm dbnm */
+ 186, /* (279) cmd ::= ALTER TABLE fullname RENAME TO nm */
+ 186, /* (280) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
+ 289, /* (281) add_column_fullname ::= fullname */
+ 186, /* (282) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
+ 186, /* (283) cmd ::= create_vtab */
+ 186, /* (284) cmd ::= create_vtab LP vtabarglist RP */
+ 291, /* (285) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+ 293, /* (286) vtabarg ::= */
+ 294, /* (287) vtabargtoken ::= ANY */
+ 294, /* (288) vtabargtoken ::= lp anylist RP */
+ 295, /* (289) lp ::= LP */
+ 261, /* (290) with ::= WITH wqlist */
+ 261, /* (291) with ::= WITH RECURSIVE wqlist */
+ 236, /* (292) wqlist ::= nm eidlist_opt AS LP select RP */
+ 236, /* (293) wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */
+ 297, /* (294) windowdefn_list ::= windowdefn */
+ 297, /* (295) windowdefn_list ::= windowdefn_list COMMA windowdefn */
+ 298, /* (296) windowdefn ::= nm AS LP window RP */
+ 299, /* (297) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
+ 299, /* (298) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
+ 299, /* (299) window ::= ORDER BY sortlist frame_opt */
+ 299, /* (300) window ::= nm ORDER BY sortlist frame_opt */
+ 299, /* (301) window ::= frame_opt */
+ 299, /* (302) window ::= nm frame_opt */
+ 300, /* (303) frame_opt ::= */
+ 300, /* (304) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
+ 300, /* (305) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
+ 304, /* (306) range_or_rows ::= RANGE|ROWS|GROUPS */
+ 306, /* (307) frame_bound_s ::= frame_bound */
+ 306, /* (308) frame_bound_s ::= UNBOUNDED PRECEDING */
+ 307, /* (309) frame_bound_e ::= frame_bound */
+ 307, /* (310) frame_bound_e ::= UNBOUNDED FOLLOWING */
+ 305, /* (311) frame_bound ::= expr PRECEDING|FOLLOWING */
+ 305, /* (312) frame_bound ::= CURRENT ROW */
+ 308, /* (313) frame_exclude_opt ::= */
+ 308, /* (314) frame_exclude_opt ::= EXCLUDE frame_exclude */
+ 309, /* (315) frame_exclude ::= NO OTHERS */
+ 309, /* (316) frame_exclude ::= CURRENT ROW */
+ 309, /* (317) frame_exclude ::= GROUP|TIES */
+ 246, /* (318) window_clause ::= WINDOW windowdefn_list */
+ 266, /* (319) filter_over ::= filter_clause over_clause */
+ 266, /* (320) filter_over ::= over_clause */
+ 266, /* (321) filter_over ::= filter_clause */
+ 303, /* (322) over_clause ::= OVER LP window RP */
+ 303, /* (323) over_clause ::= OVER nm */
+ 302, /* (324) filter_clause ::= FILTER LP WHERE expr RP */
+ 181, /* (325) input ::= cmdlist */
+ 182, /* (326) cmdlist ::= cmdlist ecmd */
+ 182, /* (327) cmdlist ::= ecmd */
+ 183, /* (328) ecmd ::= SEMI */
+ 183, /* (329) ecmd ::= cmdx SEMI */
+ 183, /* (330) ecmd ::= explain cmdx SEMI */
+ 188, /* (331) trans_opt ::= */
+ 188, /* (332) trans_opt ::= TRANSACTION */
+ 188, /* (333) trans_opt ::= TRANSACTION nm */
+ 190, /* (334) savepoint_opt ::= SAVEPOINT */
+ 190, /* (335) savepoint_opt ::= */
+ 186, /* (336) cmd ::= create_table create_table_args */
+ 197, /* (337) columnlist ::= columnlist COMMA columnname carglist */
+ 197, /* (338) columnlist ::= columnname carglist */
+ 189, /* (339) nm ::= ID|INDEXED */
+ 189, /* (340) nm ::= STRING */
+ 189, /* (341) nm ::= JOIN_KW */
+ 203, /* (342) typetoken ::= typename */
+ 204, /* (343) typename ::= ID|STRING */
+ 205, /* (344) signed ::= plus_num */
+ 205, /* (345) signed ::= minus_num */
+ 202, /* (346) carglist ::= carglist ccons */
+ 202, /* (347) carglist ::= */
+ 210, /* (348) ccons ::= NULL onconf */
+ 210, /* (349) ccons ::= GENERATED ALWAYS AS generated */
+ 210, /* (350) ccons ::= AS generated */
+ 198, /* (351) conslist_opt ::= COMMA conslist */
+ 223, /* (352) conslist ::= conslist tconscomma tcons */
+ 223, /* (353) conslist ::= tcons */
+ 224, /* (354) tconscomma ::= */
+ 228, /* (355) defer_subclause_opt ::= defer_subclause */
+ 230, /* (356) resolvetype ::= raisetype */
+ 234, /* (357) selectnowith ::= oneselect */
+ 235, /* (358) oneselect ::= values */
+ 249, /* (359) sclp ::= selcollist COMMA */
+ 250, /* (360) as ::= ID|STRING */
+ 212, /* (361) expr ::= term */
+ 267, /* (362) likeop ::= LIKE_KW|MATCH */
+ 257, /* (363) exprlist ::= nexprlist */
+ 277, /* (364) nmnum ::= plus_num */
+ 277, /* (365) nmnum ::= nm */
+ 277, /* (366) nmnum ::= ON */
+ 277, /* (367) nmnum ::= DELETE */
+ 277, /* (368) nmnum ::= DEFAULT */
+ 206, /* (369) plus_num ::= INTEGER|FLOAT */
+ 282, /* (370) foreach_clause ::= */
+ 282, /* (371) foreach_clause ::= FOR EACH ROW */
+ 285, /* (372) trnm ::= nm */
+ 286, /* (373) tridxby ::= */
+ 287, /* (374) database_kw_opt ::= DATABASE */
+ 287, /* (375) database_kw_opt ::= */
+ 290, /* (376) kwcolumn_opt ::= */
+ 290, /* (377) kwcolumn_opt ::= COLUMNKW */
+ 292, /* (378) vtabarglist ::= vtabarg */
+ 292, /* (379) vtabarglist ::= vtabarglist COMMA vtabarg */
+ 293, /* (380) vtabarg ::= vtabarg vtabargtoken */
+ 296, /* (381) anylist ::= */
+ 296, /* (382) anylist ::= anylist LP anylist RP */
+ 296, /* (383) anylist ::= anylist ANY */
+ 261, /* (384) with ::= */
+};
-/* 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[] = {
- { 147, 1 },
- { 147, 3 },
- { 148, 1 },
- { 149, 3 },
- { 150, 0 },
- { 150, 1 },
- { 150, 1 },
- { 150, 1 },
- { 149, 2 },
- { 149, 2 },
- { 149, 2 },
- { 149, 2 },
- { 149, 3 },
- { 149, 5 },
- { 154, 6 },
- { 156, 1 },
- { 158, 0 },
- { 158, 3 },
- { 157, 1 },
- { 157, 0 },
- { 155, 5 },
- { 155, 2 },
- { 162, 0 },
- { 162, 2 },
- { 164, 2 },
- { 166, 0 },
- { 166, 4 },
- { 166, 6 },
- { 167, 2 },
- { 171, 2 },
- { 171, 2 },
- { 171, 4 },
- { 171, 3 },
- { 171, 3 },
- { 171, 2 },
- { 171, 3 },
- { 171, 5 },
- { 171, 2 },
- { 171, 4 },
- { 171, 4 },
- { 171, 1 },
- { 171, 2 },
- { 176, 0 },
- { 176, 1 },
- { 178, 0 },
- { 178, 2 },
- { 180, 2 },
- { 180, 3 },
- { 180, 3 },
- { 180, 3 },
- { 181, 2 },
- { 181, 2 },
- { 181, 1 },
- { 181, 1 },
- { 181, 2 },
- { 179, 3 },
- { 179, 2 },
- { 182, 0 },
- { 182, 2 },
- { 182, 2 },
- { 161, 0 },
- { 184, 1 },
- { 185, 2 },
- { 185, 7 },
- { 185, 5 },
- { 185, 5 },
- { 185, 10 },
- { 188, 0 },
- { 174, 0 },
- { 174, 3 },
- { 189, 0 },
- { 189, 2 },
- { 190, 1 },
- { 190, 1 },
- { 149, 4 },
- { 192, 2 },
- { 192, 0 },
- { 149, 9 },
- { 149, 4 },
- { 149, 1 },
- { 163, 2 },
- { 194, 3 },
- { 197, 1 },
- { 197, 2 },
- { 197, 1 },
- { 195, 9 },
- { 206, 4 },
- { 206, 5 },
- { 198, 1 },
- { 198, 1 },
- { 198, 0 },
- { 209, 0 },
- { 199, 3 },
- { 199, 2 },
- { 199, 4 },
- { 210, 2 },
- { 210, 0 },
- { 200, 0 },
- { 200, 2 },
- { 212, 2 },
- { 212, 0 },
- { 211, 7 },
- { 211, 9 },
- { 211, 7 },
- { 211, 7 },
- { 159, 0 },
- { 159, 2 },
- { 193, 2 },
- { 213, 1 },
- { 213, 2 },
- { 213, 3 },
- { 213, 4 },
- { 215, 2 },
- { 215, 0 },
- { 214, 0 },
- { 214, 3 },
- { 214, 2 },
- { 216, 4 },
- { 216, 0 },
- { 204, 0 },
- { 204, 3 },
- { 186, 4 },
- { 186, 2 },
- { 175, 1 },
- { 175, 1 },
- { 175, 0 },
- { 202, 0 },
- { 202, 3 },
- { 203, 0 },
- { 203, 2 },
- { 205, 0 },
- { 205, 2 },
- { 205, 4 },
- { 205, 4 },
- { 149, 6 },
- { 201, 0 },
- { 201, 2 },
- { 149, 8 },
- { 218, 5 },
- { 218, 7 },
- { 218, 3 },
- { 218, 5 },
- { 149, 6 },
- { 149, 7 },
- { 219, 2 },
- { 219, 1 },
- { 220, 0 },
- { 220, 3 },
- { 217, 3 },
- { 217, 1 },
- { 173, 3 },
- { 172, 1 },
- { 173, 1 },
- { 173, 1 },
- { 173, 3 },
- { 173, 5 },
- { 172, 1 },
- { 172, 1 },
- { 172, 1 },
- { 173, 1 },
- { 173, 3 },
- { 173, 6 },
- { 173, 5 },
- { 173, 4 },
- { 172, 1 },
- { 173, 5 },
- { 173, 3 },
- { 173, 3 },
- { 173, 3 },
- { 173, 3 },
- { 173, 3 },
- { 173, 3 },
- { 173, 3 },
- { 173, 3 },
- { 221, 1 },
- { 221, 2 },
- { 173, 3 },
- { 173, 5 },
- { 173, 2 },
- { 173, 3 },
- { 173, 3 },
- { 173, 4 },
- { 173, 2 },
- { 173, 2 },
- { 173, 2 },
- { 173, 2 },
- { 222, 1 },
- { 222, 2 },
- { 173, 5 },
- { 223, 1 },
- { 223, 2 },
- { 173, 5 },
- { 173, 3 },
- { 173, 5 },
- { 173, 5 },
- { 173, 4 },
- { 173, 5 },
- { 226, 5 },
- { 226, 4 },
- { 227, 2 },
- { 227, 0 },
- { 225, 1 },
- { 225, 0 },
- { 208, 0 },
- { 207, 3 },
- { 207, 1 },
- { 224, 0 },
- { 224, 3 },
- { 149, 12 },
- { 228, 1 },
- { 228, 0 },
- { 177, 0 },
- { 177, 3 },
- { 187, 5 },
- { 187, 3 },
- { 229, 0 },
- { 229, 2 },
- { 149, 4 },
- { 149, 1 },
- { 149, 2 },
- { 149, 3 },
- { 149, 5 },
- { 149, 6 },
- { 149, 5 },
- { 149, 6 },
- { 169, 2 },
- { 170, 2 },
- { 149, 5 },
- { 231, 11 },
- { 233, 1 },
- { 233, 1 },
- { 233, 2 },
- { 233, 0 },
- { 234, 1 },
- { 234, 1 },
- { 234, 3 },
- { 236, 0 },
- { 236, 2 },
- { 232, 3 },
- { 232, 2 },
- { 238, 3 },
- { 239, 3 },
- { 239, 2 },
- { 237, 7 },
- { 237, 5 },
- { 237, 5 },
- { 237, 1 },
- { 173, 4 },
- { 173, 6 },
- { 191, 1 },
- { 191, 1 },
- { 191, 1 },
- { 149, 4 },
- { 149, 6 },
- { 149, 3 },
- { 241, 0 },
- { 241, 2 },
- { 149, 1 },
- { 149, 3 },
- { 149, 1 },
- { 149, 3 },
- { 149, 6 },
- { 149, 7 },
- { 242, 1 },
- { 149, 1 },
- { 149, 4 },
- { 244, 8 },
- { 246, 0 },
- { 247, 1 },
- { 247, 3 },
- { 248, 1 },
- { 196, 0 },
- { 196, 2 },
- { 196, 3 },
- { 250, 6 },
- { 250, 8 },
- { 144, 1 },
- { 145, 2 },
- { 145, 1 },
- { 146, 1 },
- { 146, 3 },
- { 147, 0 },
- { 151, 0 },
- { 151, 1 },
- { 151, 2 },
- { 153, 1 },
- { 153, 0 },
- { 149, 2 },
- { 160, 4 },
- { 160, 2 },
- { 152, 1 },
- { 152, 1 },
- { 152, 1 },
- { 166, 1 },
- { 167, 1 },
- { 168, 1 },
- { 168, 1 },
- { 165, 2 },
- { 165, 0 },
- { 171, 2 },
- { 161, 2 },
- { 183, 3 },
- { 183, 1 },
- { 184, 0 },
- { 188, 1 },
- { 190, 1 },
- { 194, 1 },
- { 195, 1 },
- { 209, 2 },
- { 210, 1 },
- { 173, 1 },
- { 208, 1 },
- { 230, 1 },
- { 230, 1 },
- { 230, 1 },
- { 230, 1 },
- { 230, 1 },
- { 169, 1 },
- { 235, 0 },
- { 235, 3 },
- { 238, 1 },
- { 239, 0 },
- { 240, 1 },
- { 240, 0 },
- { 243, 0 },
- { 243, 1 },
- { 245, 1 },
- { 245, 3 },
- { 246, 2 },
- { 249, 0 },
- { 249, 4 },
- { 249, 2 },
+/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number
+** of symbols on the right-hand side of that rule. */
+static const signed char yyRuleInfoNRhs[] = {
+ -1, /* (0) explain ::= EXPLAIN */
+ -3, /* (1) explain ::= EXPLAIN QUERY PLAN */
+ -1, /* (2) cmdx ::= cmd */
+ -3, /* (3) cmd ::= BEGIN transtype trans_opt */
+ 0, /* (4) transtype ::= */
+ -1, /* (5) transtype ::= DEFERRED */
+ -1, /* (6) transtype ::= IMMEDIATE */
+ -1, /* (7) transtype ::= EXCLUSIVE */
+ -2, /* (8) cmd ::= COMMIT|END trans_opt */
+ -2, /* (9) cmd ::= ROLLBACK trans_opt */
+ -2, /* (10) cmd ::= SAVEPOINT nm */
+ -3, /* (11) cmd ::= RELEASE savepoint_opt nm */
+ -5, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */
+ -6, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */
+ -1, /* (14) createkw ::= CREATE */
+ 0, /* (15) ifnotexists ::= */
+ -3, /* (16) ifnotexists ::= IF NOT EXISTS */
+ -1, /* (17) temp ::= TEMP */
+ 0, /* (18) temp ::= */
+ -5, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_options */
+ -2, /* (20) create_table_args ::= AS select */
+ 0, /* (21) table_options ::= */
+ -2, /* (22) table_options ::= WITHOUT nm */
+ -2, /* (23) columnname ::= nm typetoken */
+ 0, /* (24) typetoken ::= */
+ -4, /* (25) typetoken ::= typename LP signed RP */
+ -6, /* (26) typetoken ::= typename LP signed COMMA signed RP */
+ -2, /* (27) typename ::= typename ID|STRING */
+ 0, /* (28) scanpt ::= */
+ 0, /* (29) scantok ::= */
+ -2, /* (30) ccons ::= CONSTRAINT nm */
+ -3, /* (31) ccons ::= DEFAULT scantok term */
+ -4, /* (32) ccons ::= DEFAULT LP expr RP */
+ -4, /* (33) ccons ::= DEFAULT PLUS scantok term */
+ -4, /* (34) ccons ::= DEFAULT MINUS scantok term */
+ -3, /* (35) ccons ::= DEFAULT scantok ID|INDEXED */
+ -3, /* (36) ccons ::= NOT NULL onconf */
+ -5, /* (37) ccons ::= PRIMARY KEY sortorder onconf autoinc */
+ -2, /* (38) ccons ::= UNIQUE onconf */
+ -4, /* (39) ccons ::= CHECK LP expr RP */
+ -4, /* (40) ccons ::= REFERENCES nm eidlist_opt refargs */
+ -1, /* (41) ccons ::= defer_subclause */
+ -2, /* (42) ccons ::= COLLATE ID|STRING */
+ -3, /* (43) generated ::= LP expr RP */
+ -4, /* (44) generated ::= LP expr RP ID */
+ 0, /* (45) autoinc ::= */
+ -1, /* (46) autoinc ::= AUTOINCR */
+ 0, /* (47) refargs ::= */
+ -2, /* (48) refargs ::= refargs refarg */
+ -2, /* (49) refarg ::= MATCH nm */
+ -3, /* (50) refarg ::= ON INSERT refact */
+ -3, /* (51) refarg ::= ON DELETE refact */
+ -3, /* (52) refarg ::= ON UPDATE refact */
+ -2, /* (53) refact ::= SET NULL */
+ -2, /* (54) refact ::= SET DEFAULT */
+ -1, /* (55) refact ::= CASCADE */
+ -1, /* (56) refact ::= RESTRICT */
+ -2, /* (57) refact ::= NO ACTION */
+ -3, /* (58) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */
+ -2, /* (59) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */
+ 0, /* (60) init_deferred_pred_opt ::= */
+ -2, /* (61) init_deferred_pred_opt ::= INITIALLY DEFERRED */
+ -2, /* (62) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */
+ 0, /* (63) conslist_opt ::= */
+ -1, /* (64) tconscomma ::= COMMA */
+ -2, /* (65) tcons ::= CONSTRAINT nm */
+ -7, /* (66) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */
+ -5, /* (67) tcons ::= UNIQUE LP sortlist RP onconf */
+ -5, /* (68) tcons ::= CHECK LP expr RP onconf */
+ -10, /* (69) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */
+ 0, /* (70) defer_subclause_opt ::= */
+ 0, /* (71) onconf ::= */
+ -3, /* (72) onconf ::= ON CONFLICT resolvetype */
+ 0, /* (73) orconf ::= */
+ -2, /* (74) orconf ::= OR resolvetype */
+ -1, /* (75) resolvetype ::= IGNORE */
+ -1, /* (76) resolvetype ::= REPLACE */
+ -4, /* (77) cmd ::= DROP TABLE ifexists fullname */
+ -2, /* (78) ifexists ::= IF EXISTS */
+ 0, /* (79) ifexists ::= */
+ -9, /* (80) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */
+ -4, /* (81) cmd ::= DROP VIEW ifexists fullname */
+ -1, /* (82) cmd ::= select */
+ -3, /* (83) select ::= WITH wqlist selectnowith */
+ -4, /* (84) select ::= WITH RECURSIVE wqlist selectnowith */
+ -1, /* (85) select ::= selectnowith */
+ -3, /* (86) selectnowith ::= selectnowith multiselect_op oneselect */
+ -1, /* (87) multiselect_op ::= UNION */
+ -2, /* (88) multiselect_op ::= UNION ALL */
+ -1, /* (89) multiselect_op ::= EXCEPT|INTERSECT */
+ -9, /* (90) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */
+ -10, /* (91) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */
+ -4, /* (92) values ::= VALUES LP nexprlist RP */
+ -5, /* (93) values ::= values COMMA LP nexprlist RP */
+ -1, /* (94) distinct ::= DISTINCT */
+ -1, /* (95) distinct ::= ALL */
+ 0, /* (96) distinct ::= */
+ 0, /* (97) sclp ::= */
+ -5, /* (98) selcollist ::= sclp scanpt expr scanpt as */
+ -3, /* (99) selcollist ::= sclp scanpt STAR */
+ -5, /* (100) selcollist ::= sclp scanpt nm DOT STAR */
+ -2, /* (101) as ::= AS nm */
+ 0, /* (102) as ::= */
+ 0, /* (103) from ::= */
+ -2, /* (104) from ::= FROM seltablist */
+ -2, /* (105) stl_prefix ::= seltablist joinop */
+ 0, /* (106) stl_prefix ::= */
+ -7, /* (107) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
+ -9, /* (108) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
+ -7, /* (109) seltablist ::= stl_prefix LP select RP as on_opt using_opt */
+ -7, /* (110) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
+ 0, /* (111) dbnm ::= */
+ -2, /* (112) dbnm ::= DOT nm */
+ -1, /* (113) fullname ::= nm */
+ -3, /* (114) fullname ::= nm DOT nm */
+ -1, /* (115) xfullname ::= nm */
+ -3, /* (116) xfullname ::= nm DOT nm */
+ -5, /* (117) xfullname ::= nm DOT nm AS nm */
+ -3, /* (118) xfullname ::= nm AS nm */
+ -1, /* (119) joinop ::= COMMA|JOIN */
+ -2, /* (120) joinop ::= JOIN_KW JOIN */
+ -3, /* (121) joinop ::= JOIN_KW nm JOIN */
+ -4, /* (122) joinop ::= JOIN_KW nm nm JOIN */
+ -2, /* (123) on_opt ::= ON expr */
+ 0, /* (124) on_opt ::= */
+ 0, /* (125) indexed_opt ::= */
+ -3, /* (126) indexed_opt ::= INDEXED BY nm */
+ -2, /* (127) indexed_opt ::= NOT INDEXED */
+ -4, /* (128) using_opt ::= USING LP idlist RP */
+ 0, /* (129) using_opt ::= */
+ 0, /* (130) orderby_opt ::= */
+ -3, /* (131) orderby_opt ::= ORDER BY sortlist */
+ -5, /* (132) sortlist ::= sortlist COMMA expr sortorder nulls */
+ -3, /* (133) sortlist ::= expr sortorder nulls */
+ -1, /* (134) sortorder ::= ASC */
+ -1, /* (135) sortorder ::= DESC */
+ 0, /* (136) sortorder ::= */
+ -2, /* (137) nulls ::= NULLS FIRST */
+ -2, /* (138) nulls ::= NULLS LAST */
+ 0, /* (139) nulls ::= */
+ 0, /* (140) groupby_opt ::= */
+ -3, /* (141) groupby_opt ::= GROUP BY nexprlist */
+ 0, /* (142) having_opt ::= */
+ -2, /* (143) having_opt ::= HAVING expr */
+ 0, /* (144) limit_opt ::= */
+ -2, /* (145) limit_opt ::= LIMIT expr */
+ -4, /* (146) limit_opt ::= LIMIT expr OFFSET expr */
+ -4, /* (147) limit_opt ::= LIMIT expr COMMA expr */
+ -6, /* (148) cmd ::= with DELETE FROM xfullname indexed_opt where_opt */
+ 0, /* (149) where_opt ::= */
+ -2, /* (150) where_opt ::= WHERE expr */
+ -8, /* (151) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist where_opt */
+ -5, /* (152) setlist ::= setlist COMMA nm EQ expr */
+ -7, /* (153) setlist ::= setlist COMMA LP idlist RP EQ expr */
+ -3, /* (154) setlist ::= nm EQ expr */
+ -5, /* (155) setlist ::= LP idlist RP EQ expr */
+ -7, /* (156) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */
+ -7, /* (157) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES */
+ 0, /* (158) upsert ::= */
+ -11, /* (159) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt */
+ -8, /* (160) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING */
+ -4, /* (161) upsert ::= ON CONFLICT DO NOTHING */
+ -2, /* (162) insert_cmd ::= INSERT orconf */
+ -1, /* (163) insert_cmd ::= REPLACE */
+ 0, /* (164) idlist_opt ::= */
+ -3, /* (165) idlist_opt ::= LP idlist RP */
+ -3, /* (166) idlist ::= idlist COMMA nm */
+ -1, /* (167) idlist ::= nm */
+ -3, /* (168) expr ::= LP expr RP */
+ -1, /* (169) expr ::= ID|INDEXED */
+ -1, /* (170) expr ::= JOIN_KW */
+ -3, /* (171) expr ::= nm DOT nm */
+ -5, /* (172) expr ::= nm DOT nm DOT nm */
+ -1, /* (173) term ::= NULL|FLOAT|BLOB */
+ -1, /* (174) term ::= STRING */
+ -1, /* (175) term ::= INTEGER */
+ -1, /* (176) expr ::= VARIABLE */
+ -3, /* (177) expr ::= expr COLLATE ID|STRING */
+ -6, /* (178) expr ::= CAST LP expr AS typetoken RP */
+ -5, /* (179) expr ::= ID|INDEXED LP distinct exprlist RP */
+ -4, /* (180) expr ::= ID|INDEXED LP STAR RP */
+ -6, /* (181) expr ::= ID|INDEXED LP distinct exprlist RP filter_over */
+ -5, /* (182) expr ::= ID|INDEXED LP STAR RP filter_over */
+ -1, /* (183) term ::= CTIME_KW */
+ -5, /* (184) expr ::= LP nexprlist COMMA expr RP */
+ -3, /* (185) expr ::= expr AND expr */
+ -3, /* (186) expr ::= expr OR expr */
+ -3, /* (187) expr ::= expr LT|GT|GE|LE expr */
+ -3, /* (188) expr ::= expr EQ|NE expr */
+ -3, /* (189) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */
+ -3, /* (190) expr ::= expr PLUS|MINUS expr */
+ -3, /* (191) expr ::= expr STAR|SLASH|REM expr */
+ -3, /* (192) expr ::= expr CONCAT expr */
+ -2, /* (193) likeop ::= NOT LIKE_KW|MATCH */
+ -3, /* (194) expr ::= expr likeop expr */
+ -5, /* (195) expr ::= expr likeop expr ESCAPE expr */
+ -2, /* (196) expr ::= expr ISNULL|NOTNULL */
+ -3, /* (197) expr ::= expr NOT NULL */
+ -3, /* (198) expr ::= expr IS expr */
+ -4, /* (199) expr ::= expr IS NOT expr */
+ -2, /* (200) expr ::= NOT expr */
+ -2, /* (201) expr ::= BITNOT expr */
+ -2, /* (202) expr ::= PLUS|MINUS expr */
+ -1, /* (203) between_op ::= BETWEEN */
+ -2, /* (204) between_op ::= NOT BETWEEN */
+ -5, /* (205) expr ::= expr between_op expr AND expr */
+ -1, /* (206) in_op ::= IN */
+ -2, /* (207) in_op ::= NOT IN */
+ -5, /* (208) expr ::= expr in_op LP exprlist RP */
+ -3, /* (209) expr ::= LP select RP */
+ -5, /* (210) expr ::= expr in_op LP select RP */
+ -5, /* (211) expr ::= expr in_op nm dbnm paren_exprlist */
+ -4, /* (212) expr ::= EXISTS LP select RP */
+ -5, /* (213) expr ::= CASE case_operand case_exprlist case_else END */
+ -5, /* (214) case_exprlist ::= case_exprlist WHEN expr THEN expr */
+ -4, /* (215) case_exprlist ::= WHEN expr THEN expr */
+ -2, /* (216) case_else ::= ELSE expr */
+ 0, /* (217) case_else ::= */
+ -1, /* (218) case_operand ::= expr */
+ 0, /* (219) case_operand ::= */
+ 0, /* (220) exprlist ::= */
+ -3, /* (221) nexprlist ::= nexprlist COMMA expr */
+ -1, /* (222) nexprlist ::= expr */
+ 0, /* (223) paren_exprlist ::= */
+ -3, /* (224) paren_exprlist ::= LP exprlist RP */
+ -12, /* (225) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
+ -1, /* (226) uniqueflag ::= UNIQUE */
+ 0, /* (227) uniqueflag ::= */
+ 0, /* (228) eidlist_opt ::= */
+ -3, /* (229) eidlist_opt ::= LP eidlist RP */
+ -5, /* (230) eidlist ::= eidlist COMMA nm collate sortorder */
+ -3, /* (231) eidlist ::= nm collate sortorder */
+ 0, /* (232) collate ::= */
+ -2, /* (233) collate ::= COLLATE ID|STRING */
+ -4, /* (234) cmd ::= DROP INDEX ifexists fullname */
+ -2, /* (235) cmd ::= VACUUM vinto */
+ -3, /* (236) cmd ::= VACUUM nm vinto */
+ -2, /* (237) vinto ::= INTO expr */
+ 0, /* (238) vinto ::= */
+ -3, /* (239) cmd ::= PRAGMA nm dbnm */
+ -5, /* (240) cmd ::= PRAGMA nm dbnm EQ nmnum */
+ -6, /* (241) cmd ::= PRAGMA nm dbnm LP nmnum RP */
+ -5, /* (242) cmd ::= PRAGMA nm dbnm EQ minus_num */
+ -6, /* (243) cmd ::= PRAGMA nm dbnm LP minus_num RP */
+ -2, /* (244) plus_num ::= PLUS INTEGER|FLOAT */
+ -2, /* (245) minus_num ::= MINUS INTEGER|FLOAT */
+ -5, /* (246) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
+ -11, /* (247) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+ -1, /* (248) trigger_time ::= BEFORE|AFTER */
+ -2, /* (249) trigger_time ::= INSTEAD OF */
+ 0, /* (250) trigger_time ::= */
+ -1, /* (251) trigger_event ::= DELETE|INSERT */
+ -1, /* (252) trigger_event ::= UPDATE */
+ -3, /* (253) trigger_event ::= UPDATE OF idlist */
+ 0, /* (254) when_clause ::= */
+ -2, /* (255) when_clause ::= WHEN expr */
+ -3, /* (256) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+ -2, /* (257) trigger_cmd_list ::= trigger_cmd SEMI */
+ -3, /* (258) trnm ::= nm DOT nm */
+ -3, /* (259) tridxby ::= INDEXED BY nm */
+ -2, /* (260) tridxby ::= NOT INDEXED */
+ -8, /* (261) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt scanpt */
+ -8, /* (262) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
+ -6, /* (263) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
+ -3, /* (264) trigger_cmd ::= scanpt select scanpt */
+ -4, /* (265) expr ::= RAISE LP IGNORE RP */
+ -6, /* (266) expr ::= RAISE LP raisetype COMMA nm RP */
+ -1, /* (267) raisetype ::= ROLLBACK */
+ -1, /* (268) raisetype ::= ABORT */
+ -1, /* (269) raisetype ::= FAIL */
+ -4, /* (270) cmd ::= DROP TRIGGER ifexists fullname */
+ -6, /* (271) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+ -3, /* (272) cmd ::= DETACH database_kw_opt expr */
+ 0, /* (273) key_opt ::= */
+ -2, /* (274) key_opt ::= KEY expr */
+ -1, /* (275) cmd ::= REINDEX */
+ -3, /* (276) cmd ::= REINDEX nm dbnm */
+ -1, /* (277) cmd ::= ANALYZE */
+ -3, /* (278) cmd ::= ANALYZE nm dbnm */
+ -6, /* (279) cmd ::= ALTER TABLE fullname RENAME TO nm */
+ -7, /* (280) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
+ -1, /* (281) add_column_fullname ::= fullname */
+ -8, /* (282) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
+ -1, /* (283) cmd ::= create_vtab */
+ -4, /* (284) cmd ::= create_vtab LP vtabarglist RP */
+ -8, /* (285) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+ 0, /* (286) vtabarg ::= */
+ -1, /* (287) vtabargtoken ::= ANY */
+ -3, /* (288) vtabargtoken ::= lp anylist RP */
+ -1, /* (289) lp ::= LP */
+ -2, /* (290) with ::= WITH wqlist */
+ -3, /* (291) with ::= WITH RECURSIVE wqlist */
+ -6, /* (292) wqlist ::= nm eidlist_opt AS LP select RP */
+ -8, /* (293) wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */
+ -1, /* (294) windowdefn_list ::= windowdefn */
+ -3, /* (295) windowdefn_list ::= windowdefn_list COMMA windowdefn */
+ -5, /* (296) windowdefn ::= nm AS LP window RP */
+ -5, /* (297) window ::= PARTITION BY nexprlist orderby_opt frame_opt */
+ -6, /* (298) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
+ -4, /* (299) window ::= ORDER BY sortlist frame_opt */
+ -5, /* (300) window ::= nm ORDER BY sortlist frame_opt */
+ -1, /* (301) window ::= frame_opt */
+ -2, /* (302) window ::= nm frame_opt */
+ 0, /* (303) frame_opt ::= */
+ -3, /* (304) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
+ -6, /* (305) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
+ -1, /* (306) range_or_rows ::= RANGE|ROWS|GROUPS */
+ -1, /* (307) frame_bound_s ::= frame_bound */
+ -2, /* (308) frame_bound_s ::= UNBOUNDED PRECEDING */
+ -1, /* (309) frame_bound_e ::= frame_bound */
+ -2, /* (310) frame_bound_e ::= UNBOUNDED FOLLOWING */
+ -2, /* (311) frame_bound ::= expr PRECEDING|FOLLOWING */
+ -2, /* (312) frame_bound ::= CURRENT ROW */
+ 0, /* (313) frame_exclude_opt ::= */
+ -2, /* (314) frame_exclude_opt ::= EXCLUDE frame_exclude */
+ -2, /* (315) frame_exclude ::= NO OTHERS */
+ -2, /* (316) frame_exclude ::= CURRENT ROW */
+ -1, /* (317) frame_exclude ::= GROUP|TIES */
+ -2, /* (318) window_clause ::= WINDOW windowdefn_list */
+ -2, /* (319) filter_over ::= filter_clause over_clause */
+ -1, /* (320) filter_over ::= over_clause */
+ -1, /* (321) filter_over ::= filter_clause */
+ -4, /* (322) over_clause ::= OVER LP window RP */
+ -2, /* (323) over_clause ::= OVER nm */
+ -5, /* (324) filter_clause ::= FILTER LP WHERE expr RP */
+ -1, /* (325) input ::= cmdlist */
+ -2, /* (326) cmdlist ::= cmdlist ecmd */
+ -1, /* (327) cmdlist ::= ecmd */
+ -1, /* (328) ecmd ::= SEMI */
+ -2, /* (329) ecmd ::= cmdx SEMI */
+ -3, /* (330) ecmd ::= explain cmdx SEMI */
+ 0, /* (331) trans_opt ::= */
+ -1, /* (332) trans_opt ::= TRANSACTION */
+ -2, /* (333) trans_opt ::= TRANSACTION nm */
+ -1, /* (334) savepoint_opt ::= SAVEPOINT */
+ 0, /* (335) savepoint_opt ::= */
+ -2, /* (336) cmd ::= create_table create_table_args */
+ -4, /* (337) columnlist ::= columnlist COMMA columnname carglist */
+ -2, /* (338) columnlist ::= columnname carglist */
+ -1, /* (339) nm ::= ID|INDEXED */
+ -1, /* (340) nm ::= STRING */
+ -1, /* (341) nm ::= JOIN_KW */
+ -1, /* (342) typetoken ::= typename */
+ -1, /* (343) typename ::= ID|STRING */
+ -1, /* (344) signed ::= plus_num */
+ -1, /* (345) signed ::= minus_num */
+ -2, /* (346) carglist ::= carglist ccons */
+ 0, /* (347) carglist ::= */
+ -2, /* (348) ccons ::= NULL onconf */
+ -4, /* (349) ccons ::= GENERATED ALWAYS AS generated */
+ -2, /* (350) ccons ::= AS generated */
+ -2, /* (351) conslist_opt ::= COMMA conslist */
+ -3, /* (352) conslist ::= conslist tconscomma tcons */
+ -1, /* (353) conslist ::= tcons */
+ 0, /* (354) tconscomma ::= */
+ -1, /* (355) defer_subclause_opt ::= defer_subclause */
+ -1, /* (356) resolvetype ::= raisetype */
+ -1, /* (357) selectnowith ::= oneselect */
+ -1, /* (358) oneselect ::= values */
+ -2, /* (359) sclp ::= selcollist COMMA */
+ -1, /* (360) as ::= ID|STRING */
+ -1, /* (361) expr ::= term */
+ -1, /* (362) likeop ::= LIKE_KW|MATCH */
+ -1, /* (363) exprlist ::= nexprlist */
+ -1, /* (364) nmnum ::= plus_num */
+ -1, /* (365) nmnum ::= nm */
+ -1, /* (366) nmnum ::= ON */
+ -1, /* (367) nmnum ::= DELETE */
+ -1, /* (368) nmnum ::= DEFAULT */
+ -1, /* (369) plus_num ::= INTEGER|FLOAT */
+ 0, /* (370) foreach_clause ::= */
+ -3, /* (371) foreach_clause ::= FOR EACH ROW */
+ -1, /* (372) trnm ::= nm */
+ 0, /* (373) tridxby ::= */
+ -1, /* (374) database_kw_opt ::= DATABASE */
+ 0, /* (375) database_kw_opt ::= */
+ 0, /* (376) kwcolumn_opt ::= */
+ -1, /* (377) kwcolumn_opt ::= COLUMNKW */
+ -1, /* (378) vtabarglist ::= vtabarg */
+ -3, /* (379) vtabarglist ::= vtabarglist COMMA vtabarg */
+ -2, /* (380) vtabarg ::= vtabarg vtabargtoken */
+ 0, /* (381) anylist ::= */
+ -4, /* (382) anylist ::= anylist LP anylist RP */
+ -2, /* (383) anylist ::= anylist ANY */
+ 0, /* (384) with ::= */
};
static void yy_accept(yyParser*); /* Forward Declaration */
@@ -138132,29 +159163,49 @@ static void yy_accept(yyParser*); /* Forward Declaration */
/*
** Perform a reduce action and the shift that must immediately
** follow the reduce.
+**
+** The yyLookahead and yyLookaheadToken parameters provide reduce actions
+** access to the lookahead token (if any). The yyLookahead will be YYNOCODE
+** if the lookahead token has already been consumed. As this procedure is
+** only called from one place, optimizing compilers will in-line it, which
+** means that the extra parameters have no performance impact.
*/
-static void yy_reduce(
+static YYACTIONTYPE yy_reduce(
yyParser *yypParser, /* The parser */
- unsigned int yyruleno /* Number of the rule by which to reduce */
+ unsigned int yyruleno, /* Number of the rule by which to reduce */
+ int yyLookahead, /* Lookahead token, or YYNOCODE if none */
+ tdsqlite3ParserTOKENTYPE yyLookaheadToken /* Value of the lookahead token */
+ tdsqlite3ParserCTX_PDECL /* %extra_context */
){
int yygoto; /* The next state */
- int yyact; /* The next action */
+ YYACTIONTYPE yyact; /* The next action */
yyStackEntry *yymsp; /* The top of the parser's stack */
int yysize; /* Amount to pop the stack */
- sqlite3ParserARG_FETCH;
+ tdsqlite3ParserARG_FETCH
+ (void)yyLookahead;
+ (void)yyLookaheadToken;
yymsp = yypParser->yytos;
#ifndef NDEBUG
if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
- yysize = yyRuleInfo[yyruleno].nrhs;
- fprintf(yyTraceFILE, "%sReduce [%s], go to state %d.\n", yyTracePrompt,
- yyRuleName[yyruleno], yymsp[-yysize].stateno);
+ yysize = yyRuleInfoNRhs[yyruleno];
+ if( yysize ){
+ fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n",
+ yyTracePrompt,
+ yyruleno, yyRuleName[yyruleno],
+ yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action",
+ yymsp[yysize].stateno);
+ }else{
+ fprintf(yyTraceFILE, "%sReduce %d [%s]%s.\n",
+ yyTracePrompt, yyruleno, yyRuleName[yyruleno],
+ yyruleno<YYNRULE_WITH_ACTION ? "" : " without external action");
+ }
}
#endif /* NDEBUG */
/* Check that the stack is large enough to grow by a single entry
** if the RHS of the rule is empty. This ensures that there is room
** enough on the stack to push the LHS value */
- if( yyRuleInfo[yyruleno].nrhs==0 ){
+ if( yyRuleInfoNRhs[yyruleno]==0 ){
#ifdef YYTRACKMAXSTACKDEPTH
if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){
yypParser->yyhwm++;
@@ -138162,15 +159213,21 @@ static void yy_reduce(
}
#endif
#if YYSTACKDEPTH>0
- if( yypParser->yytos>=&yypParser->yystack[YYSTACKDEPTH-1] ){
+ if( yypParser->yytos>=yypParser->yystackEnd ){
yyStackOverflow(yypParser);
- return;
+ /* The call to yyStackOverflow() above pops the stack until it is
+ ** empty, causing the main parser loop to exit. So the return value
+ ** is never used and does not matter. */
+ return 0;
}
#else
if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){
if( yyGrowStack(yypParser) ){
yyStackOverflow(yypParser);
- return;
+ /* The call to yyStackOverflow() above pops the stack until it is
+ ** empty, causing the main parser loop to exit. So the return value
+ ** is never used and does not matter. */
+ return 0;
}
yymsp = yypParser->yytos;
}
@@ -138195,804 +159252,880 @@ static void yy_reduce(
{ pParse->explain = 2; }
break;
case 2: /* cmdx ::= cmd */
-{ sqlite3FinishCoding(pParse); }
+{ tdsqlite3FinishCoding(pParse); }
break;
case 3: /* cmd ::= BEGIN transtype trans_opt */
-{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy194);}
+{tdsqlite3BeginTransaction(pParse, yymsp[-1].minor.yy192);}
break;
case 4: /* transtype ::= */
-{yymsp[1].minor.yy194 = TK_DEFERRED;}
+{yymsp[1].minor.yy192 = TK_DEFERRED;}
break;
case 5: /* transtype ::= DEFERRED */
case 6: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==6);
case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7);
-{yymsp[0].minor.yy194 = yymsp[0].major; /*A-overwrites-X*/}
- break;
- case 8: /* cmd ::= COMMIT trans_opt */
- case 9: /* cmd ::= END trans_opt */ yytestcase(yyruleno==9);
-{sqlite3CommitTransaction(pParse);}
+ case 306: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==306);
+{yymsp[0].minor.yy192 = yymsp[0].major; /*A-overwrites-X*/}
break;
- case 10: /* cmd ::= ROLLBACK trans_opt */
-{sqlite3RollbackTransaction(pParse);}
+ case 8: /* cmd ::= COMMIT|END trans_opt */
+ case 9: /* cmd ::= ROLLBACK trans_opt */ yytestcase(yyruleno==9);
+{tdsqlite3EndTransaction(pParse,yymsp[-1].major);}
break;
- case 11: /* cmd ::= SAVEPOINT nm */
+ case 10: /* cmd ::= SAVEPOINT nm */
{
- sqlite3Savepoint(pParse, SAVEPOINT_BEGIN, &yymsp[0].minor.yy0);
+ tdsqlite3Savepoint(pParse, SAVEPOINT_BEGIN, &yymsp[0].minor.yy0);
}
break;
- case 12: /* cmd ::= RELEASE savepoint_opt nm */
+ case 11: /* cmd ::= RELEASE savepoint_opt nm */
{
- sqlite3Savepoint(pParse, SAVEPOINT_RELEASE, &yymsp[0].minor.yy0);
+ tdsqlite3Savepoint(pParse, SAVEPOINT_RELEASE, &yymsp[0].minor.yy0);
}
break;
- case 13: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */
+ case 12: /* cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */
{
- sqlite3Savepoint(pParse, SAVEPOINT_ROLLBACK, &yymsp[0].minor.yy0);
+ tdsqlite3Savepoint(pParse, SAVEPOINT_ROLLBACK, &yymsp[0].minor.yy0);
}
break;
- case 14: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */
+ case 13: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */
{
- sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy194,0,0,yymsp[-2].minor.yy194);
+ tdsqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy192,0,0,yymsp[-2].minor.yy192);
}
break;
- case 15: /* createkw ::= CREATE */
+ case 14: /* createkw ::= CREATE */
{disableLookaside(pParse);}
break;
- case 16: /* ifnotexists ::= */
- case 19: /* temp ::= */ yytestcase(yyruleno==19);
- case 22: /* table_options ::= */ yytestcase(yyruleno==22);
- case 42: /* autoinc ::= */ yytestcase(yyruleno==42);
- case 57: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==57);
- case 67: /* defer_subclause_opt ::= */ yytestcase(yyruleno==67);
- case 76: /* ifexists ::= */ yytestcase(yyruleno==76);
- case 90: /* distinct ::= */ yytestcase(yyruleno==90);
- case 215: /* collate ::= */ yytestcase(yyruleno==215);
-{yymsp[1].minor.yy194 = 0;}
- break;
- case 17: /* ifnotexists ::= IF NOT EXISTS */
-{yymsp[-2].minor.yy194 = 1;}
- break;
- case 18: /* temp ::= TEMP */
- case 43: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==43);
-{yymsp[0].minor.yy194 = 1;}
- break;
- case 20: /* create_table_args ::= LP columnlist conslist_opt RP table_options */
+ case 15: /* ifnotexists ::= */
+ case 18: /* temp ::= */ yytestcase(yyruleno==18);
+ case 21: /* table_options ::= */ yytestcase(yyruleno==21);
+ case 45: /* autoinc ::= */ yytestcase(yyruleno==45);
+ case 60: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==60);
+ case 70: /* defer_subclause_opt ::= */ yytestcase(yyruleno==70);
+ case 79: /* ifexists ::= */ yytestcase(yyruleno==79);
+ case 96: /* distinct ::= */ yytestcase(yyruleno==96);
+ case 232: /* collate ::= */ yytestcase(yyruleno==232);
+{yymsp[1].minor.yy192 = 0;}
+ break;
+ case 16: /* ifnotexists ::= IF NOT EXISTS */
+{yymsp[-2].minor.yy192 = 1;}
+ break;
+ case 17: /* temp ::= TEMP */
+ case 46: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==46);
+{yymsp[0].minor.yy192 = 1;}
+ break;
+ case 19: /* create_table_args ::= LP columnlist conslist_opt RP table_options */
{
- sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy194,0);
+ tdsqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy192,0);
}
break;
- case 21: /* create_table_args ::= AS select */
+ case 20: /* create_table_args ::= AS select */
{
- sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy243);
- sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy243);
+ tdsqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy539);
+ tdsqlite3SelectDelete(pParse->db, yymsp[0].minor.yy539);
}
break;
- case 23: /* table_options ::= WITHOUT nm */
+ case 22: /* table_options ::= WITHOUT nm */
{
- if( yymsp[0].minor.yy0.n==5 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){
- yymsp[-1].minor.yy194 = TF_WithoutRowid | TF_NoVisibleRowid;
+ if( yymsp[0].minor.yy0.n==5 && tdsqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){
+ yymsp[-1].minor.yy192 = TF_WithoutRowid | TF_NoVisibleRowid;
}else{
- yymsp[-1].minor.yy194 = 0;
- sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z);
+ yymsp[-1].minor.yy192 = 0;
+ tdsqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z);
}
}
break;
- case 24: /* columnname ::= nm typetoken */
-{sqlite3AddColumn(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);}
+ case 23: /* columnname ::= nm typetoken */
+{tdsqlite3AddColumn(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);}
break;
- case 25: /* typetoken ::= */
- case 60: /* conslist_opt ::= */ yytestcase(yyruleno==60);
- case 96: /* as ::= */ yytestcase(yyruleno==96);
+ case 24: /* typetoken ::= */
+ case 63: /* conslist_opt ::= */ yytestcase(yyruleno==63);
+ case 102: /* as ::= */ yytestcase(yyruleno==102);
{yymsp[1].minor.yy0.n = 0; yymsp[1].minor.yy0.z = 0;}
break;
- case 26: /* typetoken ::= typename LP signed RP */
+ case 25: /* typetoken ::= typename LP signed RP */
{
yymsp[-3].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy0.z);
}
break;
- case 27: /* typetoken ::= typename LP signed COMMA signed RP */
+ case 26: /* typetoken ::= typename LP signed COMMA signed RP */
{
yymsp[-5].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy0.z);
}
break;
- case 28: /* typename ::= typename ID|STRING */
+ case 27: /* typename ::= typename ID|STRING */
{yymsp[-1].minor.yy0.n=yymsp[0].minor.yy0.n+(int)(yymsp[0].minor.yy0.z-yymsp[-1].minor.yy0.z);}
break;
- case 29: /* ccons ::= CONSTRAINT nm */
- case 62: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==62);
+ case 28: /* scanpt ::= */
+{
+ assert( yyLookahead!=YYNOCODE );
+ yymsp[1].minor.yy436 = yyLookaheadToken.z;
+}
+ break;
+ case 29: /* scantok ::= */
+{
+ assert( yyLookahead!=YYNOCODE );
+ yymsp[1].minor.yy0 = yyLookaheadToken;
+}
+ break;
+ case 30: /* ccons ::= CONSTRAINT nm */
+ case 65: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==65);
{pParse->constraintName = yymsp[0].minor.yy0;}
break;
- case 30: /* ccons ::= DEFAULT term */
- case 32: /* ccons ::= DEFAULT PLUS term */ yytestcase(yyruleno==32);
-{sqlite3AddDefaultValue(pParse,&yymsp[0].minor.yy190);}
+ case 31: /* ccons ::= DEFAULT scantok term */
+{tdsqlite3AddDefaultValue(pParse,yymsp[0].minor.yy202,yymsp[-1].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);}
+ break;
+ case 32: /* ccons ::= DEFAULT LP expr RP */
+{tdsqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy202,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);}
break;
- case 31: /* ccons ::= DEFAULT LP expr RP */
-{sqlite3AddDefaultValue(pParse,&yymsp[-1].minor.yy190);}
+ case 33: /* ccons ::= DEFAULT PLUS scantok term */
+{tdsqlite3AddDefaultValue(pParse,yymsp[0].minor.yy202,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);}
break;
- case 33: /* ccons ::= DEFAULT MINUS term */
+ case 34: /* ccons ::= DEFAULT MINUS scantok term */
{
- ExprSpan v;
- v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy190.pExpr, 0, 0);
- v.zStart = yymsp[-1].minor.yy0.z;
- v.zEnd = yymsp[0].minor.yy190.zEnd;
- sqlite3AddDefaultValue(pParse,&v);
+ Expr *p = tdsqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy202, 0);
+ tdsqlite3AddDefaultValue(pParse,p,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);
}
break;
- case 34: /* ccons ::= DEFAULT ID|INDEXED */
+ case 35: /* ccons ::= DEFAULT scantok ID|INDEXED */
{
- ExprSpan v;
- spanExpr(&v, pParse, TK_STRING, yymsp[0].minor.yy0);
- sqlite3AddDefaultValue(pParse,&v);
+ Expr *p = tokenExpr(pParse, TK_STRING, yymsp[0].minor.yy0);
+ if( p ){
+ tdsqlite3ExprIdToTrueFalse(p);
+ testcase( p->op==TK_TRUEFALSE && tdsqlite3ExprTruthValue(p) );
+ }
+ tdsqlite3AddDefaultValue(pParse,p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.z+yymsp[0].minor.yy0.n);
}
break;
- case 35: /* ccons ::= NOT NULL onconf */
-{sqlite3AddNotNull(pParse, yymsp[0].minor.yy194);}
+ case 36: /* ccons ::= NOT NULL onconf */
+{tdsqlite3AddNotNull(pParse, yymsp[0].minor.yy192);}
break;
- case 36: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */
-{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy194,yymsp[0].minor.yy194,yymsp[-2].minor.yy194);}
+ case 37: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */
+{tdsqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy192,yymsp[0].minor.yy192,yymsp[-2].minor.yy192);}
break;
- case 37: /* ccons ::= UNIQUE onconf */
-{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy194,0,0,0,0,
+ case 38: /* ccons ::= UNIQUE onconf */
+{tdsqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy192,0,0,0,0,
SQLITE_IDXTYPE_UNIQUE);}
break;
- case 38: /* ccons ::= CHECK LP expr RP */
-{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy190.pExpr);}
+ case 39: /* ccons ::= CHECK LP expr RP */
+{tdsqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy202);}
+ break;
+ case 40: /* ccons ::= REFERENCES nm eidlist_opt refargs */
+{tdsqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy242,yymsp[0].minor.yy192);}
break;
- case 39: /* ccons ::= REFERENCES nm eidlist_opt refargs */
-{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy148,yymsp[0].minor.yy194);}
+ case 41: /* ccons ::= defer_subclause */
+{tdsqlite3DeferForeignKey(pParse,yymsp[0].minor.yy192);}
break;
- case 40: /* ccons ::= defer_subclause */
-{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy194);}
+ case 42: /* ccons ::= COLLATE ID|STRING */
+{tdsqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);}
break;
- case 41: /* ccons ::= COLLATE ID|STRING */
-{sqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);}
+ case 43: /* generated ::= LP expr RP */
+{tdsqlite3AddGenerated(pParse,yymsp[-1].minor.yy202,0);}
break;
- case 44: /* refargs ::= */
-{ yymsp[1].minor.yy194 = OE_None*0x0101; /* EV: R-19803-45884 */}
+ case 44: /* generated ::= LP expr RP ID */
+{tdsqlite3AddGenerated(pParse,yymsp[-2].minor.yy202,&yymsp[0].minor.yy0);}
break;
- case 45: /* refargs ::= refargs refarg */
-{ yymsp[-1].minor.yy194 = (yymsp[-1].minor.yy194 & ~yymsp[0].minor.yy497.mask) | yymsp[0].minor.yy497.value; }
+ case 47: /* refargs ::= */
+{ yymsp[1].minor.yy192 = OE_None*0x0101; /* EV: R-19803-45884 */}
break;
- case 46: /* refarg ::= MATCH nm */
-{ yymsp[-1].minor.yy497.value = 0; yymsp[-1].minor.yy497.mask = 0x000000; }
+ case 48: /* refargs ::= refargs refarg */
+{ yymsp[-1].minor.yy192 = (yymsp[-1].minor.yy192 & ~yymsp[0].minor.yy207.mask) | yymsp[0].minor.yy207.value; }
break;
- case 47: /* refarg ::= ON INSERT refact */
-{ yymsp[-2].minor.yy497.value = 0; yymsp[-2].minor.yy497.mask = 0x000000; }
+ case 49: /* refarg ::= MATCH nm */
+{ yymsp[-1].minor.yy207.value = 0; yymsp[-1].minor.yy207.mask = 0x000000; }
break;
- case 48: /* refarg ::= ON DELETE refact */
-{ yymsp[-2].minor.yy497.value = yymsp[0].minor.yy194; yymsp[-2].minor.yy497.mask = 0x0000ff; }
+ case 50: /* refarg ::= ON INSERT refact */
+{ yymsp[-2].minor.yy207.value = 0; yymsp[-2].minor.yy207.mask = 0x000000; }
break;
- case 49: /* refarg ::= ON UPDATE refact */
-{ yymsp[-2].minor.yy497.value = yymsp[0].minor.yy194<<8; yymsp[-2].minor.yy497.mask = 0x00ff00; }
+ case 51: /* refarg ::= ON DELETE refact */
+{ yymsp[-2].minor.yy207.value = yymsp[0].minor.yy192; yymsp[-2].minor.yy207.mask = 0x0000ff; }
break;
- case 50: /* refact ::= SET NULL */
-{ yymsp[-1].minor.yy194 = OE_SetNull; /* EV: R-33326-45252 */}
+ case 52: /* refarg ::= ON UPDATE refact */
+{ yymsp[-2].minor.yy207.value = yymsp[0].minor.yy192<<8; yymsp[-2].minor.yy207.mask = 0x00ff00; }
break;
- case 51: /* refact ::= SET DEFAULT */
-{ yymsp[-1].minor.yy194 = OE_SetDflt; /* EV: R-33326-45252 */}
+ case 53: /* refact ::= SET NULL */
+{ yymsp[-1].minor.yy192 = OE_SetNull; /* EV: R-33326-45252 */}
break;
- case 52: /* refact ::= CASCADE */
-{ yymsp[0].minor.yy194 = OE_Cascade; /* EV: R-33326-45252 */}
+ case 54: /* refact ::= SET DEFAULT */
+{ yymsp[-1].minor.yy192 = OE_SetDflt; /* EV: R-33326-45252 */}
break;
- case 53: /* refact ::= RESTRICT */
-{ yymsp[0].minor.yy194 = OE_Restrict; /* EV: R-33326-45252 */}
+ case 55: /* refact ::= CASCADE */
+{ yymsp[0].minor.yy192 = OE_Cascade; /* EV: R-33326-45252 */}
break;
- case 54: /* refact ::= NO ACTION */
-{ yymsp[-1].minor.yy194 = OE_None; /* EV: R-33326-45252 */}
+ case 56: /* refact ::= RESTRICT */
+{ yymsp[0].minor.yy192 = OE_Restrict; /* EV: R-33326-45252 */}
break;
- case 55: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */
-{yymsp[-2].minor.yy194 = 0;}
+ case 57: /* refact ::= NO ACTION */
+{ yymsp[-1].minor.yy192 = OE_None; /* EV: R-33326-45252 */}
break;
- case 56: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */
- case 71: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==71);
- case 144: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==144);
-{yymsp[-1].minor.yy194 = yymsp[0].minor.yy194;}
+ case 58: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */
+{yymsp[-2].minor.yy192 = 0;}
break;
- case 58: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */
- case 75: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==75);
- case 187: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==187);
- case 190: /* in_op ::= NOT IN */ yytestcase(yyruleno==190);
- case 216: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==216);
-{yymsp[-1].minor.yy194 = 1;}
+ case 59: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */
+ case 74: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==74);
+ case 162: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==162);
+{yymsp[-1].minor.yy192 = yymsp[0].minor.yy192;}
break;
- case 59: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */
-{yymsp[-1].minor.yy194 = 0;}
+ case 61: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */
+ case 78: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==78);
+ case 204: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==204);
+ case 207: /* in_op ::= NOT IN */ yytestcase(yyruleno==207);
+ case 233: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==233);
+{yymsp[-1].minor.yy192 = 1;}
break;
- case 61: /* tconscomma ::= COMMA */
+ case 62: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */
+{yymsp[-1].minor.yy192 = 0;}
+ break;
+ case 64: /* tconscomma ::= COMMA */
{pParse->constraintName.n = 0;}
break;
- case 63: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */
-{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy148,yymsp[0].minor.yy194,yymsp[-2].minor.yy194,0);}
+ case 66: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */
+{tdsqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy242,yymsp[0].minor.yy192,yymsp[-2].minor.yy192,0);}
break;
- case 64: /* tcons ::= UNIQUE LP sortlist RP onconf */
-{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy148,yymsp[0].minor.yy194,0,0,0,0,
+ case 67: /* tcons ::= UNIQUE LP sortlist RP onconf */
+{tdsqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy242,yymsp[0].minor.yy192,0,0,0,0,
SQLITE_IDXTYPE_UNIQUE);}
break;
- case 65: /* tcons ::= CHECK LP expr RP onconf */
-{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy190.pExpr);}
+ case 68: /* tcons ::= CHECK LP expr RP onconf */
+{tdsqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy202);}
break;
- case 66: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */
+ case 69: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */
{
- sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy148, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy148, yymsp[-1].minor.yy194);
- sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy194);
+ tdsqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy242, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy242, yymsp[-1].minor.yy192);
+ tdsqlite3DeferForeignKey(pParse, yymsp[0].minor.yy192);
}
break;
- case 68: /* onconf ::= */
- case 70: /* orconf ::= */ yytestcase(yyruleno==70);
-{yymsp[1].minor.yy194 = OE_Default;}
+ case 71: /* onconf ::= */
+ case 73: /* orconf ::= */ yytestcase(yyruleno==73);
+{yymsp[1].minor.yy192 = OE_Default;}
break;
- case 69: /* onconf ::= ON CONFLICT resolvetype */
-{yymsp[-2].minor.yy194 = yymsp[0].minor.yy194;}
+ case 72: /* onconf ::= ON CONFLICT resolvetype */
+{yymsp[-2].minor.yy192 = yymsp[0].minor.yy192;}
break;
- case 72: /* resolvetype ::= IGNORE */
-{yymsp[0].minor.yy194 = OE_Ignore;}
+ case 75: /* resolvetype ::= IGNORE */
+{yymsp[0].minor.yy192 = OE_Ignore;}
break;
- case 73: /* resolvetype ::= REPLACE */
- case 145: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==145);
-{yymsp[0].minor.yy194 = OE_Replace;}
+ case 76: /* resolvetype ::= REPLACE */
+ case 163: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==163);
+{yymsp[0].minor.yy192 = OE_Replace;}
break;
- case 74: /* cmd ::= DROP TABLE ifexists fullname */
+ case 77: /* cmd ::= DROP TABLE ifexists fullname */
{
- sqlite3DropTable(pParse, yymsp[0].minor.yy185, 0, yymsp[-1].minor.yy194);
+ tdsqlite3DropTable(pParse, yymsp[0].minor.yy47, 0, yymsp[-1].minor.yy192);
}
break;
- case 77: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */
+ case 80: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */
{
- sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy148, yymsp[0].minor.yy243, yymsp[-7].minor.yy194, yymsp[-5].minor.yy194);
+ tdsqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy242, yymsp[0].minor.yy539, yymsp[-7].minor.yy192, yymsp[-5].minor.yy192);
}
break;
- case 78: /* cmd ::= DROP VIEW ifexists fullname */
+ case 81: /* cmd ::= DROP VIEW ifexists fullname */
{
- sqlite3DropTable(pParse, yymsp[0].minor.yy185, 1, yymsp[-1].minor.yy194);
+ tdsqlite3DropTable(pParse, yymsp[0].minor.yy47, 1, yymsp[-1].minor.yy192);
}
break;
- case 79: /* cmd ::= select */
+ case 82: /* cmd ::= select */
{
SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0};
- sqlite3Select(pParse, yymsp[0].minor.yy243, &dest);
- sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy243);
+ tdsqlite3Select(pParse, yymsp[0].minor.yy539, &dest);
+ tdsqlite3SelectDelete(pParse->db, yymsp[0].minor.yy539);
+}
+ break;
+ case 83: /* select ::= WITH wqlist selectnowith */
+{
+ Select *p = yymsp[0].minor.yy539;
+ if( p ){
+ p->pWith = yymsp[-1].minor.yy131;
+ parserDoubleLinkSelect(pParse, p);
+ }else{
+ tdsqlite3WithDelete(pParse->db, yymsp[-1].minor.yy131);
+ }
+ yymsp[-2].minor.yy539 = p;
}
break;
- case 80: /* select ::= with selectnowith */
+ case 84: /* select ::= WITH RECURSIVE wqlist selectnowith */
{
- Select *p = yymsp[0].minor.yy243;
+ Select *p = yymsp[0].minor.yy539;
if( p ){
- p->pWith = yymsp[-1].minor.yy285;
+ p->pWith = yymsp[-1].minor.yy131;
parserDoubleLinkSelect(pParse, p);
}else{
- sqlite3WithDelete(pParse->db, yymsp[-1].minor.yy285);
+ tdsqlite3WithDelete(pParse->db, yymsp[-1].minor.yy131);
}
- yymsp[-1].minor.yy243 = p; /*A-overwrites-W*/
+ yymsp[-3].minor.yy539 = p;
}
break;
- case 81: /* selectnowith ::= selectnowith multiselect_op oneselect */
+ case 85: /* select ::= selectnowith */
{
- Select *pRhs = yymsp[0].minor.yy243;
- Select *pLhs = yymsp[-2].minor.yy243;
+ Select *p = yymsp[0].minor.yy539;
+ if( p ){
+ parserDoubleLinkSelect(pParse, p);
+ }
+ yymsp[0].minor.yy539 = p; /*A-overwrites-X*/
+}
+ break;
+ case 86: /* selectnowith ::= selectnowith multiselect_op oneselect */
+{
+ Select *pRhs = yymsp[0].minor.yy539;
+ Select *pLhs = yymsp[-2].minor.yy539;
if( pRhs && pRhs->pPrior ){
SrcList *pFrom;
Token x;
x.n = 0;
parserDoubleLinkSelect(pParse, pRhs);
- pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0,0);
- pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0,0);
+ pFrom = tdsqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0,0);
+ pRhs = tdsqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0);
}
if( pRhs ){
- pRhs->op = (u8)yymsp[-1].minor.yy194;
+ pRhs->op = (u8)yymsp[-1].minor.yy192;
pRhs->pPrior = pLhs;
if( ALWAYS(pLhs) ) pLhs->selFlags &= ~SF_MultiValue;
pRhs->selFlags &= ~SF_MultiValue;
- if( yymsp[-1].minor.yy194!=TK_ALL ) pParse->hasCompound = 1;
+ if( yymsp[-1].minor.yy192!=TK_ALL ) pParse->hasCompound = 1;
}else{
- sqlite3SelectDelete(pParse->db, pLhs);
+ tdsqlite3SelectDelete(pParse->db, pLhs);
}
- yymsp[-2].minor.yy243 = pRhs;
+ yymsp[-2].minor.yy539 = pRhs;
}
break;
- case 82: /* multiselect_op ::= UNION */
- case 84: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==84);
-{yymsp[0].minor.yy194 = yymsp[0].major; /*A-overwrites-OP*/}
+ case 87: /* multiselect_op ::= UNION */
+ case 89: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==89);
+{yymsp[0].minor.yy192 = yymsp[0].major; /*A-overwrites-OP*/}
break;
- case 83: /* multiselect_op ::= UNION ALL */
-{yymsp[-1].minor.yy194 = TK_ALL;}
+ case 88: /* multiselect_op ::= UNION ALL */
+{yymsp[-1].minor.yy192 = TK_ALL;}
break;
- case 85: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */
+ case 90: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */
{
-#if SELECTTRACE_ENABLED
- Token s = yymsp[-8].minor.yy0; /*A-overwrites-S*/
-#endif
- yymsp[-8].minor.yy243 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy148,yymsp[-5].minor.yy185,yymsp[-4].minor.yy72,yymsp[-3].minor.yy148,yymsp[-2].minor.yy72,yymsp[-1].minor.yy148,yymsp[-7].minor.yy194,yymsp[0].minor.yy354.pLimit,yymsp[0].minor.yy354.pOffset);
-#if SELECTTRACE_ENABLED
- /* Populate the Select.zSelName[] string that is used to help with
- ** query planner debugging, to differentiate between multiple Select
- ** objects in a complex query.
- **
- ** If the SELECT keyword is immediately followed by a C-style comment
- ** then extract the first few alphanumeric characters from within that
- ** comment to be the zSelName value. Otherwise, the label is #N where
- ** is an integer that is incremented with each SELECT statement seen.
- */
- if( yymsp[-8].minor.yy243!=0 ){
- const char *z = s.z+6;
- int i;
- sqlite3_snprintf(sizeof(yymsp[-8].minor.yy243->zSelName), yymsp[-8].minor.yy243->zSelName, "#%d",
- ++pParse->nSelect);
- while( z[0]==' ' ) z++;
- if( z[0]=='/' && z[1]=='*' ){
- z += 2;
- while( z[0]==' ' ) z++;
- for(i=0; sqlite3Isalnum(z[i]); i++){}
- sqlite3_snprintf(sizeof(yymsp[-8].minor.yy243->zSelName), yymsp[-8].minor.yy243->zSelName, "%.*s", i, z);
- }
+ yymsp[-8].minor.yy539 = tdsqlite3SelectNew(pParse,yymsp[-6].minor.yy242,yymsp[-5].minor.yy47,yymsp[-4].minor.yy202,yymsp[-3].minor.yy242,yymsp[-2].minor.yy202,yymsp[-1].minor.yy242,yymsp[-7].minor.yy192,yymsp[0].minor.yy202);
+}
+ break;
+ case 91: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */
+{
+ yymsp[-9].minor.yy539 = tdsqlite3SelectNew(pParse,yymsp[-7].minor.yy242,yymsp[-6].minor.yy47,yymsp[-5].minor.yy202,yymsp[-4].minor.yy242,yymsp[-3].minor.yy202,yymsp[-1].minor.yy242,yymsp[-8].minor.yy192,yymsp[0].minor.yy202);
+ if( yymsp[-9].minor.yy539 ){
+ yymsp[-9].minor.yy539->pWinDefn = yymsp[-2].minor.yy303;
+ }else{
+ tdsqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy303);
}
-#endif /* SELECTRACE_ENABLED */
}
break;
- case 86: /* values ::= VALUES LP nexprlist RP */
+ case 92: /* values ::= VALUES LP nexprlist RP */
{
- yymsp[-3].minor.yy243 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy148,0,0,0,0,0,SF_Values,0,0);
+ yymsp[-3].minor.yy539 = tdsqlite3SelectNew(pParse,yymsp[-1].minor.yy242,0,0,0,0,0,SF_Values,0);
}
break;
- case 87: /* values ::= values COMMA LP exprlist RP */
+ case 93: /* values ::= values COMMA LP nexprlist RP */
{
- Select *pRight, *pLeft = yymsp[-4].minor.yy243;
- pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy148,0,0,0,0,0,SF_Values|SF_MultiValue,0,0);
+ Select *pRight, *pLeft = yymsp[-4].minor.yy539;
+ pRight = tdsqlite3SelectNew(pParse,yymsp[-1].minor.yy242,0,0,0,0,0,SF_Values|SF_MultiValue,0);
if( ALWAYS(pLeft) ) pLeft->selFlags &= ~SF_MultiValue;
if( pRight ){
pRight->op = TK_ALL;
pRight->pPrior = pLeft;
- yymsp[-4].minor.yy243 = pRight;
+ yymsp[-4].minor.yy539 = pRight;
}else{
- yymsp[-4].minor.yy243 = pLeft;
+ yymsp[-4].minor.yy539 = pLeft;
}
}
break;
- case 88: /* distinct ::= DISTINCT */
-{yymsp[0].minor.yy194 = SF_Distinct;}
+ case 94: /* distinct ::= DISTINCT */
+{yymsp[0].minor.yy192 = SF_Distinct;}
break;
- case 89: /* distinct ::= ALL */
-{yymsp[0].minor.yy194 = SF_All;}
+ case 95: /* distinct ::= ALL */
+{yymsp[0].minor.yy192 = SF_All;}
break;
- case 91: /* sclp ::= */
- case 119: /* orderby_opt ::= */ yytestcase(yyruleno==119);
- case 126: /* groupby_opt ::= */ yytestcase(yyruleno==126);
- case 203: /* exprlist ::= */ yytestcase(yyruleno==203);
- case 206: /* paren_exprlist ::= */ yytestcase(yyruleno==206);
- case 211: /* eidlist_opt ::= */ yytestcase(yyruleno==211);
-{yymsp[1].minor.yy148 = 0;}
+ case 97: /* sclp ::= */
+ case 130: /* orderby_opt ::= */ yytestcase(yyruleno==130);
+ case 140: /* groupby_opt ::= */ yytestcase(yyruleno==140);
+ case 220: /* exprlist ::= */ yytestcase(yyruleno==220);
+ case 223: /* paren_exprlist ::= */ yytestcase(yyruleno==223);
+ case 228: /* eidlist_opt ::= */ yytestcase(yyruleno==228);
+{yymsp[1].minor.yy242 = 0;}
break;
- case 92: /* selcollist ::= sclp expr as */
+ case 98: /* selcollist ::= sclp scanpt expr scanpt as */
{
- yymsp[-2].minor.yy148 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy148, yymsp[-1].minor.yy190.pExpr);
- if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-2].minor.yy148, &yymsp[0].minor.yy0, 1);
- sqlite3ExprListSetSpan(pParse,yymsp[-2].minor.yy148,&yymsp[-1].minor.yy190);
+ yymsp[-4].minor.yy242 = tdsqlite3ExprListAppend(pParse, yymsp[-4].minor.yy242, yymsp[-2].minor.yy202);
+ if( yymsp[0].minor.yy0.n>0 ) tdsqlite3ExprListSetName(pParse, yymsp[-4].minor.yy242, &yymsp[0].minor.yy0, 1);
+ tdsqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy242,yymsp[-3].minor.yy436,yymsp[-1].minor.yy436);
}
break;
- case 93: /* selcollist ::= sclp STAR */
+ case 99: /* selcollist ::= sclp scanpt STAR */
{
- Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0);
- yymsp[-1].minor.yy148 = sqlite3ExprListAppend(pParse, yymsp[-1].minor.yy148, p);
+ Expr *p = tdsqlite3Expr(pParse->db, TK_ASTERISK, 0);
+ yymsp[-2].minor.yy242 = tdsqlite3ExprListAppend(pParse, yymsp[-2].minor.yy242, p);
}
break;
- case 94: /* selcollist ::= sclp nm DOT STAR */
+ case 100: /* selcollist ::= sclp scanpt nm DOT STAR */
{
- Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0, 0);
- Expr *pLeft = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0);
- Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0);
- yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy148, pDot);
+ Expr *pRight = tdsqlite3PExpr(pParse, TK_ASTERISK, 0, 0);
+ Expr *pLeft = tdsqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1);
+ Expr *pDot = tdsqlite3PExpr(pParse, TK_DOT, pLeft, pRight);
+ yymsp[-4].minor.yy242 = tdsqlite3ExprListAppend(pParse,yymsp[-4].minor.yy242, pDot);
}
break;
- case 95: /* as ::= AS nm */
- case 106: /* dbnm ::= DOT nm */ yytestcase(yyruleno==106);
- case 225: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==225);
- case 226: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==226);
+ case 101: /* as ::= AS nm */
+ case 112: /* dbnm ::= DOT nm */ yytestcase(yyruleno==112);
+ case 244: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==244);
+ case 245: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==245);
{yymsp[-1].minor.yy0 = yymsp[0].minor.yy0;}
break;
- case 97: /* from ::= */
-{yymsp[1].minor.yy185 = sqlite3DbMallocZero(pParse->db, sizeof(*yymsp[1].minor.yy185));}
+ case 103: /* from ::= */
+{yymsp[1].minor.yy47 = tdsqlite3DbMallocZero(pParse->db, sizeof(*yymsp[1].minor.yy47));}
break;
- case 98: /* from ::= FROM seltablist */
+ case 104: /* from ::= FROM seltablist */
{
- yymsp[-1].minor.yy185 = yymsp[0].minor.yy185;
- sqlite3SrcListShiftJoinType(yymsp[-1].minor.yy185);
+ yymsp[-1].minor.yy47 = yymsp[0].minor.yy47;
+ tdsqlite3SrcListShiftJoinType(yymsp[-1].minor.yy47);
}
break;
- case 99: /* stl_prefix ::= seltablist joinop */
+ case 105: /* stl_prefix ::= seltablist joinop */
{
- if( ALWAYS(yymsp[-1].minor.yy185 && yymsp[-1].minor.yy185->nSrc>0) ) yymsp[-1].minor.yy185->a[yymsp[-1].minor.yy185->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy194;
+ if( ALWAYS(yymsp[-1].minor.yy47 && yymsp[-1].minor.yy47->nSrc>0) ) yymsp[-1].minor.yy47->a[yymsp[-1].minor.yy47->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy192;
}
break;
- case 100: /* stl_prefix ::= */
-{yymsp[1].minor.yy185 = 0;}
+ case 106: /* stl_prefix ::= */
+{yymsp[1].minor.yy47 = 0;}
break;
- case 101: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
+ case 107: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */
{
- yymsp[-6].minor.yy185 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy185,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy72,yymsp[0].minor.yy254);
- sqlite3SrcListIndexedBy(pParse, yymsp[-6].minor.yy185, &yymsp[-2].minor.yy0);
+ yymsp[-6].minor.yy47 = tdsqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy47,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
+ tdsqlite3SrcListIndexedBy(pParse, yymsp[-6].minor.yy47, &yymsp[-2].minor.yy0);
}
break;
- case 102: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
+ case 108: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */
{
- yymsp[-8].minor.yy185 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy185,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy72,yymsp[0].minor.yy254);
- sqlite3SrcListFuncArgs(pParse, yymsp[-8].minor.yy185, yymsp[-4].minor.yy148);
+ yymsp[-8].minor.yy47 = tdsqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy47,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
+ tdsqlite3SrcListFuncArgs(pParse, yymsp[-8].minor.yy47, yymsp[-4].minor.yy242);
}
break;
- case 103: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */
+ case 109: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */
{
- yymsp[-6].minor.yy185 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy185,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy243,yymsp[-1].minor.yy72,yymsp[0].minor.yy254);
+ yymsp[-6].minor.yy47 = tdsqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy47,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy539,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
}
break;
- case 104: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
+ case 110: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */
{
- if( yymsp[-6].minor.yy185==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy72==0 && yymsp[0].minor.yy254==0 ){
- yymsp[-6].minor.yy185 = yymsp[-4].minor.yy185;
- }else if( yymsp[-4].minor.yy185->nSrc==1 ){
- yymsp[-6].minor.yy185 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy185,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy72,yymsp[0].minor.yy254);
- if( yymsp[-6].minor.yy185 ){
- struct SrcList_item *pNew = &yymsp[-6].minor.yy185->a[yymsp[-6].minor.yy185->nSrc-1];
- struct SrcList_item *pOld = yymsp[-4].minor.yy185->a;
+ if( yymsp[-6].minor.yy47==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy202==0 && yymsp[0].minor.yy600==0 ){
+ yymsp[-6].minor.yy47 = yymsp[-4].minor.yy47;
+ }else if( yymsp[-4].minor.yy47->nSrc==1 ){
+ yymsp[-6].minor.yy47 = tdsqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy47,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
+ if( yymsp[-6].minor.yy47 ){
+ struct SrcList_item *pNew = &yymsp[-6].minor.yy47->a[yymsp[-6].minor.yy47->nSrc-1];
+ struct SrcList_item *pOld = yymsp[-4].minor.yy47->a;
pNew->zName = pOld->zName;
pNew->zDatabase = pOld->zDatabase;
pNew->pSelect = pOld->pSelect;
+ if( pOld->fg.isTabFunc ){
+ pNew->u1.pFuncArg = pOld->u1.pFuncArg;
+ pOld->u1.pFuncArg = 0;
+ pOld->fg.isTabFunc = 0;
+ pNew->fg.isTabFunc = 1;
+ }
pOld->zName = pOld->zDatabase = 0;
pOld->pSelect = 0;
}
- sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy185);
+ tdsqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy47);
}else{
Select *pSubquery;
- sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy185);
- pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy185,0,0,0,0,SF_NestedFrom,0,0);
- yymsp[-6].minor.yy185 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy185,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy72,yymsp[0].minor.yy254);
+ tdsqlite3SrcListShiftJoinType(yymsp[-4].minor.yy47);
+ pSubquery = tdsqlite3SelectNew(pParse,0,yymsp[-4].minor.yy47,0,0,0,0,SF_NestedFrom,0);
+ yymsp[-6].minor.yy47 = tdsqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy47,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy202,yymsp[0].minor.yy600);
}
}
break;
- case 105: /* dbnm ::= */
- case 114: /* indexed_opt ::= */ yytestcase(yyruleno==114);
+ case 111: /* dbnm ::= */
+ case 125: /* indexed_opt ::= */ yytestcase(yyruleno==125);
{yymsp[1].minor.yy0.z=0; yymsp[1].minor.yy0.n=0;}
break;
- case 107: /* fullname ::= nm dbnm */
-{yymsp[-1].minor.yy185 = sqlite3SrcListAppend(pParse->db,0,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/}
+ case 113: /* fullname ::= nm */
+{
+ yylhsminor.yy47 = tdsqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0);
+ if( IN_RENAME_OBJECT && yylhsminor.yy47 ) tdsqlite3RenameTokenMap(pParse, yylhsminor.yy47->a[0].zName, &yymsp[0].minor.yy0);
+}
+ yymsp[0].minor.yy47 = yylhsminor.yy47;
+ break;
+ case 114: /* fullname ::= nm DOT nm */
+{
+ yylhsminor.yy47 = tdsqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0);
+ if( IN_RENAME_OBJECT && yylhsminor.yy47 ) tdsqlite3RenameTokenMap(pParse, yylhsminor.yy47->a[0].zName, &yymsp[0].minor.yy0);
+}
+ yymsp[-2].minor.yy47 = yylhsminor.yy47;
+ break;
+ case 115: /* xfullname ::= nm */
+{yymsp[0].minor.yy47 = tdsqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); /*A-overwrites-X*/}
+ break;
+ case 116: /* xfullname ::= nm DOT nm */
+{yymsp[-2].minor.yy47 = tdsqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/}
+ break;
+ case 117: /* xfullname ::= nm DOT nm AS nm */
+{
+ yymsp[-4].minor.yy47 = tdsqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); /*A-overwrites-X*/
+ if( yymsp[-4].minor.yy47 ) yymsp[-4].minor.yy47->a[0].zAlias = tdsqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0);
+}
+ break;
+ case 118: /* xfullname ::= nm AS nm */
+{
+ yymsp[-2].minor.yy47 = tdsqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); /*A-overwrites-X*/
+ if( yymsp[-2].minor.yy47 ) yymsp[-2].minor.yy47->a[0].zAlias = tdsqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0);
+}
break;
- case 108: /* joinop ::= COMMA|JOIN */
-{ yymsp[0].minor.yy194 = JT_INNER; }
+ case 119: /* joinop ::= COMMA|JOIN */
+{ yymsp[0].minor.yy192 = JT_INNER; }
break;
- case 109: /* joinop ::= JOIN_KW JOIN */
-{yymsp[-1].minor.yy194 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/}
+ case 120: /* joinop ::= JOIN_KW JOIN */
+{yymsp[-1].minor.yy192 = tdsqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/}
break;
- case 110: /* joinop ::= JOIN_KW nm JOIN */
-{yymsp[-2].minor.yy194 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/}
+ case 121: /* joinop ::= JOIN_KW nm JOIN */
+{yymsp[-2].minor.yy192 = tdsqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/}
break;
- case 111: /* joinop ::= JOIN_KW nm nm JOIN */
-{yymsp[-3].minor.yy194 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/}
+ case 122: /* joinop ::= JOIN_KW nm nm JOIN */
+{yymsp[-3].minor.yy192 = tdsqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/}
break;
- case 112: /* on_opt ::= ON expr */
- case 129: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==129);
- case 136: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==136);
- case 199: /* case_else ::= ELSE expr */ yytestcase(yyruleno==199);
-{yymsp[-1].minor.yy72 = yymsp[0].minor.yy190.pExpr;}
+ case 123: /* on_opt ::= ON expr */
+ case 143: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==143);
+ case 150: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==150);
+ case 216: /* case_else ::= ELSE expr */ yytestcase(yyruleno==216);
+ case 237: /* vinto ::= INTO expr */ yytestcase(yyruleno==237);
+{yymsp[-1].minor.yy202 = yymsp[0].minor.yy202;}
break;
- case 113: /* on_opt ::= */
- case 128: /* having_opt ::= */ yytestcase(yyruleno==128);
- case 135: /* where_opt ::= */ yytestcase(yyruleno==135);
- case 200: /* case_else ::= */ yytestcase(yyruleno==200);
- case 202: /* case_operand ::= */ yytestcase(yyruleno==202);
-{yymsp[1].minor.yy72 = 0;}
+ case 124: /* on_opt ::= */
+ case 142: /* having_opt ::= */ yytestcase(yyruleno==142);
+ case 144: /* limit_opt ::= */ yytestcase(yyruleno==144);
+ case 149: /* where_opt ::= */ yytestcase(yyruleno==149);
+ case 217: /* case_else ::= */ yytestcase(yyruleno==217);
+ case 219: /* case_operand ::= */ yytestcase(yyruleno==219);
+ case 238: /* vinto ::= */ yytestcase(yyruleno==238);
+{yymsp[1].minor.yy202 = 0;}
break;
- case 115: /* indexed_opt ::= INDEXED BY nm */
+ case 126: /* indexed_opt ::= INDEXED BY nm */
{yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;}
break;
- case 116: /* indexed_opt ::= NOT INDEXED */
+ case 127: /* indexed_opt ::= NOT INDEXED */
{yymsp[-1].minor.yy0.z=0; yymsp[-1].minor.yy0.n=1;}
break;
- case 117: /* using_opt ::= USING LP idlist RP */
-{yymsp[-3].minor.yy254 = yymsp[-1].minor.yy254;}
+ case 128: /* using_opt ::= USING LP idlist RP */
+{yymsp[-3].minor.yy600 = yymsp[-1].minor.yy600;}
break;
- case 118: /* using_opt ::= */
- case 146: /* idlist_opt ::= */ yytestcase(yyruleno==146);
-{yymsp[1].minor.yy254 = 0;}
+ case 129: /* using_opt ::= */
+ case 164: /* idlist_opt ::= */ yytestcase(yyruleno==164);
+{yymsp[1].minor.yy600 = 0;}
break;
- case 120: /* orderby_opt ::= ORDER BY sortlist */
- case 127: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==127);
-{yymsp[-2].minor.yy148 = yymsp[0].minor.yy148;}
+ case 131: /* orderby_opt ::= ORDER BY sortlist */
+ case 141: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==141);
+{yymsp[-2].minor.yy242 = yymsp[0].minor.yy242;}
break;
- case 121: /* sortlist ::= sortlist COMMA expr sortorder */
+ case 132: /* sortlist ::= sortlist COMMA expr sortorder nulls */
{
- yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy148,yymsp[-1].minor.yy190.pExpr);
- sqlite3ExprListSetSortOrder(yymsp[-3].minor.yy148,yymsp[0].minor.yy194);
+ yymsp[-4].minor.yy242 = tdsqlite3ExprListAppend(pParse,yymsp[-4].minor.yy242,yymsp[-2].minor.yy202);
+ tdsqlite3ExprListSetSortOrder(yymsp[-4].minor.yy242,yymsp[-1].minor.yy192,yymsp[0].minor.yy192);
}
break;
- case 122: /* sortlist ::= expr sortorder */
+ case 133: /* sortlist ::= expr sortorder nulls */
{
- yymsp[-1].minor.yy148 = sqlite3ExprListAppend(pParse,0,yymsp[-1].minor.yy190.pExpr); /*A-overwrites-Y*/
- sqlite3ExprListSetSortOrder(yymsp[-1].minor.yy148,yymsp[0].minor.yy194);
+ yymsp[-2].minor.yy242 = tdsqlite3ExprListAppend(pParse,0,yymsp[-2].minor.yy202); /*A-overwrites-Y*/
+ tdsqlite3ExprListSetSortOrder(yymsp[-2].minor.yy242,yymsp[-1].minor.yy192,yymsp[0].minor.yy192);
}
break;
- case 123: /* sortorder ::= ASC */
-{yymsp[0].minor.yy194 = SQLITE_SO_ASC;}
+ case 134: /* sortorder ::= ASC */
+{yymsp[0].minor.yy192 = SQLITE_SO_ASC;}
break;
- case 124: /* sortorder ::= DESC */
-{yymsp[0].minor.yy194 = SQLITE_SO_DESC;}
+ case 135: /* sortorder ::= DESC */
+{yymsp[0].minor.yy192 = SQLITE_SO_DESC;}
break;
- case 125: /* sortorder ::= */
-{yymsp[1].minor.yy194 = SQLITE_SO_UNDEFINED;}
+ case 136: /* sortorder ::= */
+ case 139: /* nulls ::= */ yytestcase(yyruleno==139);
+{yymsp[1].minor.yy192 = SQLITE_SO_UNDEFINED;}
break;
- case 130: /* limit_opt ::= */
-{yymsp[1].minor.yy354.pLimit = 0; yymsp[1].minor.yy354.pOffset = 0;}
+ case 137: /* nulls ::= NULLS FIRST */
+{yymsp[-1].minor.yy192 = SQLITE_SO_ASC;}
break;
- case 131: /* limit_opt ::= LIMIT expr */
-{yymsp[-1].minor.yy354.pLimit = yymsp[0].minor.yy190.pExpr; yymsp[-1].minor.yy354.pOffset = 0;}
+ case 138: /* nulls ::= NULLS LAST */
+{yymsp[-1].minor.yy192 = SQLITE_SO_DESC;}
break;
- case 132: /* limit_opt ::= LIMIT expr OFFSET expr */
-{yymsp[-3].minor.yy354.pLimit = yymsp[-2].minor.yy190.pExpr; yymsp[-3].minor.yy354.pOffset = yymsp[0].minor.yy190.pExpr;}
+ case 145: /* limit_opt ::= LIMIT expr */
+{yymsp[-1].minor.yy202 = tdsqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy202,0);}
break;
- case 133: /* limit_opt ::= LIMIT expr COMMA expr */
-{yymsp[-3].minor.yy354.pOffset = yymsp[-2].minor.yy190.pExpr; yymsp[-3].minor.yy354.pLimit = yymsp[0].minor.yy190.pExpr;}
+ case 146: /* limit_opt ::= LIMIT expr OFFSET expr */
+{yymsp[-3].minor.yy202 = tdsqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy202,yymsp[0].minor.yy202);}
break;
- case 134: /* cmd ::= with DELETE FROM fullname indexed_opt where_opt */
+ case 147: /* limit_opt ::= LIMIT expr COMMA expr */
+{yymsp[-3].minor.yy202 = tdsqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy202,yymsp[-2].minor.yy202);}
+ break;
+ case 148: /* cmd ::= with DELETE FROM xfullname indexed_opt where_opt */
{
- sqlite3WithPush(pParse, yymsp[-5].minor.yy285, 1);
- sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy185, &yymsp[-1].minor.yy0);
- sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy185,yymsp[0].minor.yy72);
+ tdsqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy47, &yymsp[-1].minor.yy0);
+ tdsqlite3DeleteFrom(pParse,yymsp[-2].minor.yy47,yymsp[0].minor.yy202,0,0);
}
break;
- case 137: /* cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt */
+ case 151: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist where_opt */
{
- sqlite3WithPush(pParse, yymsp[-7].minor.yy285, 1);
- sqlite3SrcListIndexedBy(pParse, yymsp[-4].minor.yy185, &yymsp[-3].minor.yy0);
- sqlite3ExprListCheckLength(pParse,yymsp[-1].minor.yy148,"set list");
- sqlite3Update(pParse,yymsp[-4].minor.yy185,yymsp[-1].minor.yy148,yymsp[0].minor.yy72,yymsp[-5].minor.yy194);
+ tdsqlite3SrcListIndexedBy(pParse, yymsp[-4].minor.yy47, &yymsp[-3].minor.yy0);
+ tdsqlite3ExprListCheckLength(pParse,yymsp[-1].minor.yy242,"set list");
+ tdsqlite3Update(pParse,yymsp[-4].minor.yy47,yymsp[-1].minor.yy242,yymsp[0].minor.yy202,yymsp[-5].minor.yy192,0,0,0);
}
break;
- case 138: /* setlist ::= setlist COMMA nm EQ expr */
+ case 152: /* setlist ::= setlist COMMA nm EQ expr */
{
- yymsp[-4].minor.yy148 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy148, yymsp[0].minor.yy190.pExpr);
- sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy148, &yymsp[-2].minor.yy0, 1);
+ yymsp[-4].minor.yy242 = tdsqlite3ExprListAppend(pParse, yymsp[-4].minor.yy242, yymsp[0].minor.yy202);
+ tdsqlite3ExprListSetName(pParse, yymsp[-4].minor.yy242, &yymsp[-2].minor.yy0, 1);
}
break;
- case 139: /* setlist ::= setlist COMMA LP idlist RP EQ expr */
+ case 153: /* setlist ::= setlist COMMA LP idlist RP EQ expr */
{
- yymsp[-6].minor.yy148 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy148, yymsp[-3].minor.yy254, yymsp[0].minor.yy190.pExpr);
+ yymsp[-6].minor.yy242 = tdsqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy242, yymsp[-3].minor.yy600, yymsp[0].minor.yy202);
}
break;
- case 140: /* setlist ::= nm EQ expr */
+ case 154: /* setlist ::= nm EQ expr */
{
- yylhsminor.yy148 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy190.pExpr);
- sqlite3ExprListSetName(pParse, yylhsminor.yy148, &yymsp[-2].minor.yy0, 1);
+ yylhsminor.yy242 = tdsqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy202);
+ tdsqlite3ExprListSetName(pParse, yylhsminor.yy242, &yymsp[-2].minor.yy0, 1);
}
- yymsp[-2].minor.yy148 = yylhsminor.yy148;
+ yymsp[-2].minor.yy242 = yylhsminor.yy242;
break;
- case 141: /* setlist ::= LP idlist RP EQ expr */
+ case 155: /* setlist ::= LP idlist RP EQ expr */
{
- yymsp[-4].minor.yy148 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy254, yymsp[0].minor.yy190.pExpr);
+ yymsp[-4].minor.yy242 = tdsqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy600, yymsp[0].minor.yy202);
}
break;
- case 142: /* cmd ::= with insert_cmd INTO fullname idlist_opt select */
+ case 156: /* cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */
{
- sqlite3WithPush(pParse, yymsp[-5].minor.yy285, 1);
- sqlite3Insert(pParse, yymsp[-2].minor.yy185, yymsp[0].minor.yy243, yymsp[-1].minor.yy254, yymsp[-4].minor.yy194);
+ tdsqlite3Insert(pParse, yymsp[-3].minor.yy47, yymsp[-1].minor.yy539, yymsp[-2].minor.yy600, yymsp[-5].minor.yy192, yymsp[0].minor.yy318);
}
break;
- case 143: /* cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES */
+ case 157: /* cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES */
{
- sqlite3WithPush(pParse, yymsp[-6].minor.yy285, 1);
- sqlite3Insert(pParse, yymsp[-3].minor.yy185, 0, yymsp[-2].minor.yy254, yymsp[-5].minor.yy194);
+ tdsqlite3Insert(pParse, yymsp[-3].minor.yy47, 0, yymsp[-2].minor.yy600, yymsp[-5].minor.yy192, 0);
}
break;
- case 147: /* idlist_opt ::= LP idlist RP */
-{yymsp[-2].minor.yy254 = yymsp[-1].minor.yy254;}
+ case 158: /* upsert ::= */
+{ yymsp[1].minor.yy318 = 0; }
+ break;
+ case 159: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt */
+{ yymsp[-10].minor.yy318 = tdsqlite3UpsertNew(pParse->db,yymsp[-7].minor.yy242,yymsp[-5].minor.yy202,yymsp[-1].minor.yy242,yymsp[0].minor.yy202);}
+ break;
+ case 160: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING */
+{ yymsp[-7].minor.yy318 = tdsqlite3UpsertNew(pParse->db,yymsp[-4].minor.yy242,yymsp[-2].minor.yy202,0,0); }
break;
- case 148: /* idlist ::= idlist COMMA nm */
-{yymsp[-2].minor.yy254 = sqlite3IdListAppend(pParse->db,yymsp[-2].minor.yy254,&yymsp[0].minor.yy0);}
+ case 161: /* upsert ::= ON CONFLICT DO NOTHING */
+{ yymsp[-3].minor.yy318 = tdsqlite3UpsertNew(pParse->db,0,0,0,0); }
break;
- case 149: /* idlist ::= nm */
-{yymsp[0].minor.yy254 = sqlite3IdListAppend(pParse->db,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/}
+ case 165: /* idlist_opt ::= LP idlist RP */
+{yymsp[-2].minor.yy600 = yymsp[-1].minor.yy600;}
break;
- case 150: /* expr ::= LP expr RP */
-{spanSet(&yymsp[-2].minor.yy190,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ yymsp[-2].minor.yy190.pExpr = yymsp[-1].minor.yy190.pExpr;}
+ case 166: /* idlist ::= idlist COMMA nm */
+{yymsp[-2].minor.yy600 = tdsqlite3IdListAppend(pParse,yymsp[-2].minor.yy600,&yymsp[0].minor.yy0);}
break;
- case 151: /* term ::= NULL */
- case 156: /* term ::= FLOAT|BLOB */ yytestcase(yyruleno==156);
- case 157: /* term ::= STRING */ yytestcase(yyruleno==157);
-{spanExpr(&yymsp[0].minor.yy190,pParse,yymsp[0].major,yymsp[0].minor.yy0);/*A-overwrites-X*/}
+ case 167: /* idlist ::= nm */
+{yymsp[0].minor.yy600 = tdsqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/}
break;
- case 152: /* expr ::= ID|INDEXED */
- case 153: /* expr ::= JOIN_KW */ yytestcase(yyruleno==153);
-{spanExpr(&yymsp[0].minor.yy190,pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/}
+ case 168: /* expr ::= LP expr RP */
+{yymsp[-2].minor.yy202 = yymsp[-1].minor.yy202;}
break;
- case 154: /* expr ::= nm DOT nm */
+ case 169: /* expr ::= ID|INDEXED */
+ case 170: /* expr ::= JOIN_KW */ yytestcase(yyruleno==170);
+{yymsp[0].minor.yy202=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/}
+ break;
+ case 171: /* expr ::= nm DOT nm */
{
- Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1);
- Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1);
- spanSet(&yymsp[-2].minor.yy190,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/
- yymsp[-2].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0);
+ Expr *temp1 = tdsqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1);
+ Expr *temp2 = tdsqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1);
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenMap(pParse, (void*)temp2, &yymsp[0].minor.yy0);
+ tdsqlite3RenameTokenMap(pParse, (void*)temp1, &yymsp[-2].minor.yy0);
+ }
+ yylhsminor.yy202 = tdsqlite3PExpr(pParse, TK_DOT, temp1, temp2);
}
+ yymsp[-2].minor.yy202 = yylhsminor.yy202;
break;
- case 155: /* expr ::= nm DOT nm DOT nm */
+ case 172: /* expr ::= nm DOT nm DOT nm */
{
- Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-4].minor.yy0, 1);
- Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1);
- Expr *temp3 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1);
- Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3, 0);
- spanSet(&yymsp[-4].minor.yy190,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/
- yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4, 0);
+ Expr *temp1 = tdsqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-4].minor.yy0, 1);
+ Expr *temp2 = tdsqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1);
+ Expr *temp3 = tdsqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1);
+ Expr *temp4 = tdsqlite3PExpr(pParse, TK_DOT, temp2, temp3);
+ if( IN_RENAME_OBJECT ){
+ tdsqlite3RenameTokenMap(pParse, (void*)temp3, &yymsp[0].minor.yy0);
+ tdsqlite3RenameTokenMap(pParse, (void*)temp2, &yymsp[-2].minor.yy0);
+ }
+ yylhsminor.yy202 = tdsqlite3PExpr(pParse, TK_DOT, temp1, temp4);
}
+ yymsp[-4].minor.yy202 = yylhsminor.yy202;
break;
- case 158: /* term ::= INTEGER */
+ case 173: /* term ::= NULL|FLOAT|BLOB */
+ case 174: /* term ::= STRING */ yytestcase(yyruleno==174);
+{yymsp[0].minor.yy202=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/}
+ break;
+ case 175: /* term ::= INTEGER */
{
- yylhsminor.yy190.pExpr = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1);
- yylhsminor.yy190.zStart = yymsp[0].minor.yy0.z;
- yylhsminor.yy190.zEnd = yymsp[0].minor.yy0.z + yymsp[0].minor.yy0.n;
- if( yylhsminor.yy190.pExpr ) yylhsminor.yy190.pExpr->flags |= EP_Leaf;
+ yylhsminor.yy202 = tdsqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1);
}
- yymsp[0].minor.yy190 = yylhsminor.yy190;
+ yymsp[0].minor.yy202 = yylhsminor.yy202;
break;
- case 159: /* expr ::= VARIABLE */
+ case 176: /* expr ::= VARIABLE */
{
- if( !(yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){
+ if( !(yymsp[0].minor.yy0.z[0]=='#' && tdsqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){
u32 n = yymsp[0].minor.yy0.n;
- spanExpr(&yymsp[0].minor.yy190, pParse, TK_VARIABLE, yymsp[0].minor.yy0);
- sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy190.pExpr, n);
+ yymsp[0].minor.yy202 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0);
+ tdsqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy202, n);
}else{
/* When doing a nested parse, one can include terms in an expression
** that look like this: #1 #2 ... These terms refer to registers
** in the virtual machine. #N is the N-th register. */
Token t = yymsp[0].minor.yy0; /*A-overwrites-X*/
assert( t.n>=2 );
- spanSet(&yymsp[0].minor.yy190, &t, &t);
if( pParse->nested==0 ){
- sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t);
- yymsp[0].minor.yy190.pExpr = 0;
+ tdsqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t);
+ yymsp[0].minor.yy202 = 0;
}else{
- yymsp[0].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0, 0);
- if( yymsp[0].minor.yy190.pExpr ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy190.pExpr->iTable);
+ yymsp[0].minor.yy202 = tdsqlite3PExpr(pParse, TK_REGISTER, 0, 0);
+ if( yymsp[0].minor.yy202 ) tdsqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy202->iTable);
}
}
}
break;
- case 160: /* expr ::= expr COLLATE ID|STRING */
+ case 177: /* expr ::= expr COLLATE ID|STRING */
{
- yymsp[-2].minor.yy190.pExpr = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy190.pExpr, &yymsp[0].minor.yy0, 1);
- yymsp[-2].minor.yy190.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n];
+ yymsp[-2].minor.yy202 = tdsqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy202, &yymsp[0].minor.yy0, 1);
}
break;
- case 161: /* expr ::= CAST LP expr AS typetoken RP */
+ case 178: /* expr ::= CAST LP expr AS typetoken RP */
{
- spanSet(&yymsp[-5].minor.yy190,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/
- yymsp[-5].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_CAST, yymsp[-3].minor.yy190.pExpr, 0, &yymsp[-1].minor.yy0);
+ yymsp[-5].minor.yy202 = tdsqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1);
+ tdsqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy202, yymsp[-3].minor.yy202, 0);
}
break;
- case 162: /* expr ::= ID|INDEXED LP distinct exprlist RP */
+ case 179: /* expr ::= ID|INDEXED LP distinct exprlist RP */
{
- if( yymsp[-1].minor.yy148 && yymsp[-1].minor.yy148->nExpr>pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){
- sqlite3ErrorMsg(pParse, "too many arguments on function %T", &yymsp[-4].minor.yy0);
- }
- yylhsminor.yy190.pExpr = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy148, &yymsp[-4].minor.yy0);
- spanSet(&yylhsminor.yy190,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0);
- if( yymsp[-2].minor.yy194==SF_Distinct && yylhsminor.yy190.pExpr ){
- yylhsminor.yy190.pExpr->flags |= EP_Distinct;
- }
+ yylhsminor.yy202 = tdsqlite3ExprFunction(pParse, yymsp[-1].minor.yy242, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy192);
}
- yymsp[-4].minor.yy190 = yylhsminor.yy190;
+ yymsp[-4].minor.yy202 = yylhsminor.yy202;
break;
- case 163: /* expr ::= ID|INDEXED LP STAR RP */
+ case 180: /* expr ::= ID|INDEXED LP STAR RP */
{
- yylhsminor.yy190.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0);
- spanSet(&yylhsminor.yy190,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0);
+ yylhsminor.yy202 = tdsqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0);
}
- yymsp[-3].minor.yy190 = yylhsminor.yy190;
+ yymsp[-3].minor.yy202 = yylhsminor.yy202;
break;
- case 164: /* term ::= CTIME_KW */
+ case 181: /* expr ::= ID|INDEXED LP distinct exprlist RP filter_over */
{
- yylhsminor.yy190.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0);
- spanSet(&yylhsminor.yy190, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0);
+ yylhsminor.yy202 = tdsqlite3ExprFunction(pParse, yymsp[-2].minor.yy242, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy192);
+ tdsqlite3WindowAttach(pParse, yylhsminor.yy202, yymsp[0].minor.yy303);
}
- yymsp[0].minor.yy190 = yylhsminor.yy190;
+ yymsp[-5].minor.yy202 = yylhsminor.yy202;
break;
- case 165: /* expr ::= LP nexprlist COMMA expr RP */
+ case 182: /* expr ::= ID|INDEXED LP STAR RP filter_over */
{
- ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy148, yymsp[-1].minor.yy190.pExpr);
- yylhsminor.yy190.pExpr = sqlite3PExpr(pParse, TK_VECTOR, 0, 0, 0);
- if( yylhsminor.yy190.pExpr ){
- yylhsminor.yy190.pExpr->x.pList = pList;
- spanSet(&yylhsminor.yy190, &yymsp[-4].minor.yy0, &yymsp[0].minor.yy0);
+ yylhsminor.yy202 = tdsqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0);
+ tdsqlite3WindowAttach(pParse, yylhsminor.yy202, yymsp[0].minor.yy303);
+}
+ yymsp[-4].minor.yy202 = yylhsminor.yy202;
+ break;
+ case 183: /* term ::= CTIME_KW */
+{
+ yylhsminor.yy202 = tdsqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0);
+}
+ yymsp[0].minor.yy202 = yylhsminor.yy202;
+ break;
+ case 184: /* expr ::= LP nexprlist COMMA expr RP */
+{
+ ExprList *pList = tdsqlite3ExprListAppend(pParse, yymsp[-3].minor.yy242, yymsp[-1].minor.yy202);
+ yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_VECTOR, 0, 0);
+ if( yymsp[-4].minor.yy202 ){
+ yymsp[-4].minor.yy202->x.pList = pList;
+ if( ALWAYS(pList->nExpr) ){
+ yymsp[-4].minor.yy202->flags |= pList->a[0].pExpr->flags & EP_Propagate;
+ }
}else{
- sqlite3ExprListDelete(pParse->db, pList);
+ tdsqlite3ExprListDelete(pParse->db, pList);
}
}
- yymsp[-4].minor.yy190 = yylhsminor.yy190;
break;
- case 166: /* expr ::= expr AND expr */
- case 167: /* expr ::= expr OR expr */ yytestcase(yyruleno==167);
- case 168: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==168);
- case 169: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==169);
- case 170: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==170);
- case 171: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==171);
- case 172: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==172);
- case 173: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==173);
-{spanBinaryExpr(pParse,yymsp[-1].major,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy190);}
+ case 185: /* expr ::= expr AND expr */
+{yymsp[-2].minor.yy202=tdsqlite3ExprAnd(pParse,yymsp[-2].minor.yy202,yymsp[0].minor.yy202);}
break;
- case 174: /* likeop ::= LIKE_KW|MATCH */
-{yymsp[0].minor.yy0=yymsp[0].minor.yy0;/*A-overwrites-X*/}
+ case 186: /* expr ::= expr OR expr */
+ case 187: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==187);
+ case 188: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==188);
+ case 189: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==189);
+ case 190: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==190);
+ case 191: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==191);
+ case 192: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==192);
+{yymsp[-2].minor.yy202=tdsqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy202,yymsp[0].minor.yy202);}
break;
- case 175: /* likeop ::= NOT LIKE_KW|MATCH */
+ case 193: /* likeop ::= NOT LIKE_KW|MATCH */
{yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.n|=0x80000000; /*yymsp[-1].minor.yy0-overwrite-yymsp[0].minor.yy0*/}
break;
- case 176: /* expr ::= expr likeop expr */
+ case 194: /* expr ::= expr likeop expr */
{
ExprList *pList;
int bNot = yymsp[-1].minor.yy0.n & 0x80000000;
yymsp[-1].minor.yy0.n &= 0x7fffffff;
- pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy190.pExpr);
- pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy190.pExpr);
- yymsp[-2].minor.yy190.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0);
- exprNot(pParse, bNot, &yymsp[-2].minor.yy190);
- yymsp[-2].minor.yy190.zEnd = yymsp[0].minor.yy190.zEnd;
- if( yymsp[-2].minor.yy190.pExpr ) yymsp[-2].minor.yy190.pExpr->flags |= EP_InfixFunc;
+ pList = tdsqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy202);
+ pList = tdsqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy202);
+ yymsp[-2].minor.yy202 = tdsqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0);
+ if( bNot ) yymsp[-2].minor.yy202 = tdsqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy202, 0);
+ if( yymsp[-2].minor.yy202 ) yymsp[-2].minor.yy202->flags |= EP_InfixFunc;
}
break;
- case 177: /* expr ::= expr likeop expr ESCAPE expr */
+ case 195: /* expr ::= expr likeop expr ESCAPE expr */
{
ExprList *pList;
int bNot = yymsp[-3].minor.yy0.n & 0x80000000;
yymsp[-3].minor.yy0.n &= 0x7fffffff;
- pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy190.pExpr);
- pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy190.pExpr);
- pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy190.pExpr);
- yymsp[-4].minor.yy190.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0);
- exprNot(pParse, bNot, &yymsp[-4].minor.yy190);
- yymsp[-4].minor.yy190.zEnd = yymsp[0].minor.yy190.zEnd;
- if( yymsp[-4].minor.yy190.pExpr ) yymsp[-4].minor.yy190.pExpr->flags |= EP_InfixFunc;
+ pList = tdsqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy202);
+ pList = tdsqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy202);
+ pList = tdsqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy202);
+ yymsp[-4].minor.yy202 = tdsqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0);
+ if( bNot ) yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
+ if( yymsp[-4].minor.yy202 ) yymsp[-4].minor.yy202->flags |= EP_InfixFunc;
}
break;
- case 178: /* expr ::= expr ISNULL|NOTNULL */
-{spanUnaryPostfix(pParse,yymsp[0].major,&yymsp[-1].minor.yy190,&yymsp[0].minor.yy0);}
+ case 196: /* expr ::= expr ISNULL|NOTNULL */
+{yymsp[-1].minor.yy202 = tdsqlite3PExpr(pParse,yymsp[0].major,yymsp[-1].minor.yy202,0);}
break;
- case 179: /* expr ::= expr NOT NULL */
-{spanUnaryPostfix(pParse,TK_NOTNULL,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy0);}
+ case 197: /* expr ::= expr NOT NULL */
+{yymsp[-2].minor.yy202 = tdsqlite3PExpr(pParse,TK_NOTNULL,yymsp[-2].minor.yy202,0);}
break;
- case 180: /* expr ::= expr IS expr */
+ case 198: /* expr ::= expr IS expr */
{
- spanBinaryExpr(pParse,TK_IS,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy190);
- binaryToUnaryIfNull(pParse, yymsp[0].minor.yy190.pExpr, yymsp[-2].minor.yy190.pExpr, TK_ISNULL);
+ yymsp[-2].minor.yy202 = tdsqlite3PExpr(pParse,TK_IS,yymsp[-2].minor.yy202,yymsp[0].minor.yy202);
+ binaryToUnaryIfNull(pParse, yymsp[0].minor.yy202, yymsp[-2].minor.yy202, TK_ISNULL);
}
break;
- case 181: /* expr ::= expr IS NOT expr */
+ case 199: /* expr ::= expr IS NOT expr */
{
- spanBinaryExpr(pParse,TK_ISNOT,&yymsp[-3].minor.yy190,&yymsp[0].minor.yy190);
- binaryToUnaryIfNull(pParse, yymsp[0].minor.yy190.pExpr, yymsp[-3].minor.yy190.pExpr, TK_NOTNULL);
+ yymsp[-3].minor.yy202 = tdsqlite3PExpr(pParse,TK_ISNOT,yymsp[-3].minor.yy202,yymsp[0].minor.yy202);
+ binaryToUnaryIfNull(pParse, yymsp[0].minor.yy202, yymsp[-3].minor.yy202, TK_NOTNULL);
}
break;
- case 182: /* expr ::= NOT expr */
- case 183: /* expr ::= BITNOT expr */ yytestcase(yyruleno==183);
-{spanUnaryPrefix(&yymsp[-1].minor.yy190,pParse,yymsp[-1].major,&yymsp[0].minor.yy190,&yymsp[-1].minor.yy0);/*A-overwrites-B*/}
+ case 200: /* expr ::= NOT expr */
+ case 201: /* expr ::= BITNOT expr */ yytestcase(yyruleno==201);
+{yymsp[-1].minor.yy202 = tdsqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy202, 0);/*A-overwrites-B*/}
break;
- case 184: /* expr ::= MINUS expr */
-{spanUnaryPrefix(&yymsp[-1].minor.yy190,pParse,TK_UMINUS,&yymsp[0].minor.yy190,&yymsp[-1].minor.yy0);/*A-overwrites-B*/}
- break;
- case 185: /* expr ::= PLUS expr */
-{spanUnaryPrefix(&yymsp[-1].minor.yy190,pParse,TK_UPLUS,&yymsp[0].minor.yy190,&yymsp[-1].minor.yy0);/*A-overwrites-B*/}
+ case 202: /* expr ::= PLUS|MINUS expr */
+{
+ yymsp[-1].minor.yy202 = tdsqlite3PExpr(pParse, yymsp[-1].major==TK_PLUS ? TK_UPLUS : TK_UMINUS, yymsp[0].minor.yy202, 0);
+ /*A-overwrites-B*/
+}
break;
- case 186: /* between_op ::= BETWEEN */
- case 189: /* in_op ::= IN */ yytestcase(yyruleno==189);
-{yymsp[0].minor.yy194 = 0;}
+ case 203: /* between_op ::= BETWEEN */
+ case 206: /* in_op ::= IN */ yytestcase(yyruleno==206);
+{yymsp[0].minor.yy192 = 0;}
break;
- case 188: /* expr ::= expr between_op expr AND expr */
+ case 205: /* expr ::= expr between_op expr AND expr */
{
- ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy190.pExpr);
- pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy190.pExpr);
- yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy190.pExpr, 0, 0);
- if( yymsp[-4].minor.yy190.pExpr ){
- yymsp[-4].minor.yy190.pExpr->x.pList = pList;
+ ExprList *pList = tdsqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy202);
+ pList = tdsqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy202);
+ yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy202, 0);
+ if( yymsp[-4].minor.yy202 ){
+ yymsp[-4].minor.yy202->x.pList = pList;
}else{
- sqlite3ExprListDelete(pParse->db, pList);
+ tdsqlite3ExprListDelete(pParse->db, pList);
}
- exprNot(pParse, yymsp[-3].minor.yy194, &yymsp[-4].minor.yy190);
- yymsp[-4].minor.yy190.zEnd = yymsp[0].minor.yy190.zEnd;
+ if( yymsp[-3].minor.yy192 ) yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
}
break;
- case 191: /* expr ::= expr in_op LP exprlist RP */
+ case 208: /* expr ::= expr in_op LP exprlist RP */
{
- if( yymsp[-1].minor.yy148==0 ){
+ if( yymsp[-1].minor.yy242==0 ){
/* Expressions of the form
**
** expr1 IN ()
@@ -139001,440 +160134,556 @@ static void yy_reduce(
** simplify to constants 0 (false) and 1 (true), respectively,
** regardless of the value of expr1.
*/
- sqlite3ExprDelete(pParse->db, yymsp[-4].minor.yy190.pExpr);
- yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &sqlite3IntTokens[yymsp[-3].minor.yy194]);
- }else if( yymsp[-1].minor.yy148->nExpr==1 ){
- /* Expressions of the form:
- **
- ** expr1 IN (?1)
- ** expr1 NOT IN (?2)
- **
- ** with exactly one value on the RHS can be simplified to something
- ** like this:
- **
- ** expr1 == ?1
- ** expr1 <> ?2
- **
- ** But, the RHS of the == or <> is marked with the EP_Generic flag
- ** so that it may not contribute to the computation of comparison
- ** affinity or the collating sequence to use for comparison. Otherwise,
- ** the semantics would be subtly different from IN or NOT IN.
- */
- Expr *pRHS = yymsp[-1].minor.yy148->a[0].pExpr;
- yymsp[-1].minor.yy148->a[0].pExpr = 0;
- sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy148);
- /* pRHS cannot be NULL because a malloc error would have been detected
- ** before now and control would have never reached this point */
- if( ALWAYS(pRHS) ){
- pRHS->flags &= ~EP_Collate;
- pRHS->flags |= EP_Generic;
- }
- yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, yymsp[-3].minor.yy194 ? TK_NE : TK_EQ, yymsp[-4].minor.yy190.pExpr, pRHS, 0);
- }else{
- yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0, 0);
- if( yymsp[-4].minor.yy190.pExpr ){
- yymsp[-4].minor.yy190.pExpr->x.pList = yymsp[-1].minor.yy148;
- sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy190.pExpr);
+ tdsqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy202);
+ yymsp[-4].minor.yy202 = tdsqlite3Expr(pParse->db, TK_INTEGER, yymsp[-3].minor.yy192 ? "1" : "0");
+ }else{
+ yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy202, 0);
+ if( yymsp[-4].minor.yy202 ){
+ yymsp[-4].minor.yy202->x.pList = yymsp[-1].minor.yy242;
+ tdsqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy202);
}else{
- sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy148);
+ tdsqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy242);
}
- exprNot(pParse, yymsp[-3].minor.yy194, &yymsp[-4].minor.yy190);
+ if( yymsp[-3].minor.yy192 ) yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
}
- yymsp[-4].minor.yy190.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n];
}
break;
- case 192: /* expr ::= LP select RP */
+ case 209: /* expr ::= LP select RP */
{
- spanSet(&yymsp[-2].minor.yy190,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/
- yymsp[-2].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0, 0);
- sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy190.pExpr, yymsp[-1].minor.yy243);
+ yymsp[-2].minor.yy202 = tdsqlite3PExpr(pParse, TK_SELECT, 0, 0);
+ tdsqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy202, yymsp[-1].minor.yy539);
}
break;
- case 193: /* expr ::= expr in_op LP select RP */
+ case 210: /* expr ::= expr in_op LP select RP */
{
- yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0, 0);
- sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy190.pExpr, yymsp[-1].minor.yy243);
- exprNot(pParse, yymsp[-3].minor.yy194, &yymsp[-4].minor.yy190);
- yymsp[-4].minor.yy190.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n];
+ yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy202, 0);
+ tdsqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy202, yymsp[-1].minor.yy539);
+ if( yymsp[-3].minor.yy192 ) yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
}
break;
- case 194: /* expr ::= expr in_op nm dbnm paren_exprlist */
+ case 211: /* expr ::= expr in_op nm dbnm paren_exprlist */
{
- SrcList *pSrc = sqlite3SrcListAppend(pParse->db, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);
- Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0);
- if( yymsp[0].minor.yy148 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy148);
- yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0, 0);
- sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy190.pExpr, pSelect);
- exprNot(pParse, yymsp[-3].minor.yy194, &yymsp[-4].minor.yy190);
- yymsp[-4].minor.yy190.zEnd = yymsp[-1].minor.yy0.z ? &yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n] : &yymsp[-2].minor.yy0.z[yymsp[-2].minor.yy0.n];
+ SrcList *pSrc = tdsqlite3SrcListAppend(pParse, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);
+ Select *pSelect = tdsqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0);
+ if( yymsp[0].minor.yy242 ) tdsqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy242);
+ yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy202, 0);
+ tdsqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy202, pSelect);
+ if( yymsp[-3].minor.yy192 ) yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy202, 0);
}
break;
- case 195: /* expr ::= EXISTS LP select RP */
+ case 212: /* expr ::= EXISTS LP select RP */
{
Expr *p;
- spanSet(&yymsp[-3].minor.yy190,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/
- p = yymsp[-3].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0, 0);
- sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy243);
+ p = yymsp[-3].minor.yy202 = tdsqlite3PExpr(pParse, TK_EXISTS, 0, 0);
+ tdsqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy539);
}
break;
- case 196: /* expr ::= CASE case_operand case_exprlist case_else END */
+ case 213: /* expr ::= CASE case_operand case_exprlist case_else END */
{
- spanSet(&yymsp[-4].minor.yy190,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-C*/
- yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy72, 0, 0);
- if( yymsp[-4].minor.yy190.pExpr ){
- yymsp[-4].minor.yy190.pExpr->x.pList = yymsp[-1].minor.yy72 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy148,yymsp[-1].minor.yy72) : yymsp[-2].minor.yy148;
- sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy190.pExpr);
+ yymsp[-4].minor.yy202 = tdsqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy202, 0);
+ if( yymsp[-4].minor.yy202 ){
+ yymsp[-4].minor.yy202->x.pList = yymsp[-1].minor.yy202 ? tdsqlite3ExprListAppend(pParse,yymsp[-2].minor.yy242,yymsp[-1].minor.yy202) : yymsp[-2].minor.yy242;
+ tdsqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy202);
}else{
- sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy148);
- sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy72);
+ tdsqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy242);
+ tdsqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy202);
}
}
break;
- case 197: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */
+ case 214: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */
{
- yymsp[-4].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy148, yymsp[-2].minor.yy190.pExpr);
- yymsp[-4].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy148, yymsp[0].minor.yy190.pExpr);
+ yymsp[-4].minor.yy242 = tdsqlite3ExprListAppend(pParse,yymsp[-4].minor.yy242, yymsp[-2].minor.yy202);
+ yymsp[-4].minor.yy242 = tdsqlite3ExprListAppend(pParse,yymsp[-4].minor.yy242, yymsp[0].minor.yy202);
}
break;
- case 198: /* case_exprlist ::= WHEN expr THEN expr */
+ case 215: /* case_exprlist ::= WHEN expr THEN expr */
{
- yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy190.pExpr);
- yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy148, yymsp[0].minor.yy190.pExpr);
+ yymsp[-3].minor.yy242 = tdsqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy202);
+ yymsp[-3].minor.yy242 = tdsqlite3ExprListAppend(pParse,yymsp[-3].minor.yy242, yymsp[0].minor.yy202);
}
break;
- case 201: /* case_operand ::= expr */
-{yymsp[0].minor.yy72 = yymsp[0].minor.yy190.pExpr; /*A-overwrites-X*/}
+ case 218: /* case_operand ::= expr */
+{yymsp[0].minor.yy202 = yymsp[0].minor.yy202; /*A-overwrites-X*/}
break;
- case 204: /* nexprlist ::= nexprlist COMMA expr */
-{yymsp[-2].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy148,yymsp[0].minor.yy190.pExpr);}
+ case 221: /* nexprlist ::= nexprlist COMMA expr */
+{yymsp[-2].minor.yy242 = tdsqlite3ExprListAppend(pParse,yymsp[-2].minor.yy242,yymsp[0].minor.yy202);}
break;
- case 205: /* nexprlist ::= expr */
-{yymsp[0].minor.yy148 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy190.pExpr); /*A-overwrites-Y*/}
+ case 222: /* nexprlist ::= expr */
+{yymsp[0].minor.yy242 = tdsqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy202); /*A-overwrites-Y*/}
break;
- case 207: /* paren_exprlist ::= LP exprlist RP */
- case 212: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==212);
-{yymsp[-2].minor.yy148 = yymsp[-1].minor.yy148;}
+ case 224: /* paren_exprlist ::= LP exprlist RP */
+ case 229: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==229);
+{yymsp[-2].minor.yy242 = yymsp[-1].minor.yy242;}
break;
- case 208: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
+ case 225: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */
{
- sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0,
- sqlite3SrcListAppend(pParse->db,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy148, yymsp[-10].minor.yy194,
- &yymsp[-11].minor.yy0, yymsp[0].minor.yy72, SQLITE_SO_ASC, yymsp[-8].minor.yy194, SQLITE_IDXTYPE_APPDEF);
+ tdsqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0,
+ tdsqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy242, yymsp[-10].minor.yy192,
+ &yymsp[-11].minor.yy0, yymsp[0].minor.yy202, SQLITE_SO_ASC, yymsp[-8].minor.yy192, SQLITE_IDXTYPE_APPDEF);
+ if( IN_RENAME_OBJECT && pParse->pNewIndex ){
+ tdsqlite3RenameTokenMap(pParse, pParse->pNewIndex->zName, &yymsp[-4].minor.yy0);
+ }
}
break;
- case 209: /* uniqueflag ::= UNIQUE */
- case 250: /* raisetype ::= ABORT */ yytestcase(yyruleno==250);
-{yymsp[0].minor.yy194 = OE_Abort;}
+ case 226: /* uniqueflag ::= UNIQUE */
+ case 268: /* raisetype ::= ABORT */ yytestcase(yyruleno==268);
+{yymsp[0].minor.yy192 = OE_Abort;}
break;
- case 210: /* uniqueflag ::= */
-{yymsp[1].minor.yy194 = OE_None;}
+ case 227: /* uniqueflag ::= */
+{yymsp[1].minor.yy192 = OE_None;}
break;
- case 213: /* eidlist ::= eidlist COMMA nm collate sortorder */
+ case 230: /* eidlist ::= eidlist COMMA nm collate sortorder */
{
- yymsp[-4].minor.yy148 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy148, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy194, yymsp[0].minor.yy194);
+ yymsp[-4].minor.yy242 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy242, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy192, yymsp[0].minor.yy192);
}
break;
- case 214: /* eidlist ::= nm collate sortorder */
+ case 231: /* eidlist ::= nm collate sortorder */
{
- yymsp[-2].minor.yy148 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy194, yymsp[0].minor.yy194); /*A-overwrites-Y*/
+ yymsp[-2].minor.yy242 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy192, yymsp[0].minor.yy192); /*A-overwrites-Y*/
}
break;
- case 217: /* cmd ::= DROP INDEX ifexists fullname */
-{sqlite3DropIndex(pParse, yymsp[0].minor.yy185, yymsp[-1].minor.yy194);}
+ case 234: /* cmd ::= DROP INDEX ifexists fullname */
+{tdsqlite3DropIndex(pParse, yymsp[0].minor.yy47, yymsp[-1].minor.yy192);}
break;
- case 218: /* cmd ::= VACUUM */
-{sqlite3Vacuum(pParse,0);}
+ case 235: /* cmd ::= VACUUM vinto */
+{tdsqlite3Vacuum(pParse,0,yymsp[0].minor.yy202);}
break;
- case 219: /* cmd ::= VACUUM nm */
-{sqlite3Vacuum(pParse,&yymsp[0].minor.yy0);}
+ case 236: /* cmd ::= VACUUM nm vinto */
+{tdsqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy202);}
break;
- case 220: /* cmd ::= PRAGMA nm dbnm */
-{sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);}
+ case 239: /* cmd ::= PRAGMA nm dbnm */
+{tdsqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);}
break;
- case 221: /* cmd ::= PRAGMA nm dbnm EQ nmnum */
-{sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);}
+ case 240: /* cmd ::= PRAGMA nm dbnm EQ nmnum */
+{tdsqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);}
break;
- case 222: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */
-{sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);}
+ case 241: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */
+{tdsqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);}
break;
- case 223: /* cmd ::= PRAGMA nm dbnm EQ minus_num */
-{sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);}
+ case 242: /* cmd ::= PRAGMA nm dbnm EQ minus_num */
+{tdsqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);}
break;
- case 224: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */
-{sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);}
+ case 243: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */
+{tdsqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);}
break;
- case 227: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
+ case 246: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */
{
Token all;
all.z = yymsp[-3].minor.yy0.z;
all.n = (int)(yymsp[0].minor.yy0.z - yymsp[-3].minor.yy0.z) + yymsp[0].minor.yy0.n;
- sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy145, &all);
+ tdsqlite3FinishTrigger(pParse, yymsp[-1].minor.yy447, &all);
}
break;
- case 228: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
+ case 247: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */
{
- sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy194, yymsp[-4].minor.yy332.a, yymsp[-4].minor.yy332.b, yymsp[-2].minor.yy185, yymsp[0].minor.yy72, yymsp[-10].minor.yy194, yymsp[-8].minor.yy194);
+ tdsqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy192, yymsp[-4].minor.yy230.a, yymsp[-4].minor.yy230.b, yymsp[-2].minor.yy47, yymsp[0].minor.yy202, yymsp[-10].minor.yy192, yymsp[-8].minor.yy192);
yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/
}
break;
- case 229: /* trigger_time ::= BEFORE */
-{ yymsp[0].minor.yy194 = TK_BEFORE; }
- break;
- case 230: /* trigger_time ::= AFTER */
-{ yymsp[0].minor.yy194 = TK_AFTER; }
+ case 248: /* trigger_time ::= BEFORE|AFTER */
+{ yymsp[0].minor.yy192 = yymsp[0].major; /*A-overwrites-X*/ }
break;
- case 231: /* trigger_time ::= INSTEAD OF */
-{ yymsp[-1].minor.yy194 = TK_INSTEAD;}
+ case 249: /* trigger_time ::= INSTEAD OF */
+{ yymsp[-1].minor.yy192 = TK_INSTEAD;}
break;
- case 232: /* trigger_time ::= */
-{ yymsp[1].minor.yy194 = TK_BEFORE; }
+ case 250: /* trigger_time ::= */
+{ yymsp[1].minor.yy192 = TK_BEFORE; }
break;
- case 233: /* trigger_event ::= DELETE|INSERT */
- case 234: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==234);
-{yymsp[0].minor.yy332.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy332.b = 0;}
+ case 251: /* trigger_event ::= DELETE|INSERT */
+ case 252: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==252);
+{yymsp[0].minor.yy230.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy230.b = 0;}
break;
- case 235: /* trigger_event ::= UPDATE OF idlist */
-{yymsp[-2].minor.yy332.a = TK_UPDATE; yymsp[-2].minor.yy332.b = yymsp[0].minor.yy254;}
+ case 253: /* trigger_event ::= UPDATE OF idlist */
+{yymsp[-2].minor.yy230.a = TK_UPDATE; yymsp[-2].minor.yy230.b = yymsp[0].minor.yy600;}
break;
- case 236: /* when_clause ::= */
- case 255: /* key_opt ::= */ yytestcase(yyruleno==255);
-{ yymsp[1].minor.yy72 = 0; }
+ case 254: /* when_clause ::= */
+ case 273: /* key_opt ::= */ yytestcase(yyruleno==273);
+{ yymsp[1].minor.yy202 = 0; }
break;
- case 237: /* when_clause ::= WHEN expr */
- case 256: /* key_opt ::= KEY expr */ yytestcase(yyruleno==256);
-{ yymsp[-1].minor.yy72 = yymsp[0].minor.yy190.pExpr; }
+ case 255: /* when_clause ::= WHEN expr */
+ case 274: /* key_opt ::= KEY expr */ yytestcase(yyruleno==274);
+{ yymsp[-1].minor.yy202 = yymsp[0].minor.yy202; }
break;
- case 238: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
+ case 256: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */
{
- assert( yymsp[-2].minor.yy145!=0 );
- yymsp[-2].minor.yy145->pLast->pNext = yymsp[-1].minor.yy145;
- yymsp[-2].minor.yy145->pLast = yymsp[-1].minor.yy145;
+ assert( yymsp[-2].minor.yy447!=0 );
+ yymsp[-2].minor.yy447->pLast->pNext = yymsp[-1].minor.yy447;
+ yymsp[-2].minor.yy447->pLast = yymsp[-1].minor.yy447;
}
break;
- case 239: /* trigger_cmd_list ::= trigger_cmd SEMI */
+ case 257: /* trigger_cmd_list ::= trigger_cmd SEMI */
{
- assert( yymsp[-1].minor.yy145!=0 );
- yymsp[-1].minor.yy145->pLast = yymsp[-1].minor.yy145;
+ assert( yymsp[-1].minor.yy447!=0 );
+ yymsp[-1].minor.yy447->pLast = yymsp[-1].minor.yy447;
}
break;
- case 240: /* trnm ::= nm DOT nm */
+ case 258: /* trnm ::= nm DOT nm */
{
yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"qualified table names are not allowed on INSERT, UPDATE, and DELETE "
"statements within triggers");
}
break;
- case 241: /* tridxby ::= INDEXED BY nm */
+ case 259: /* tridxby ::= INDEXED BY nm */
{
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"the INDEXED BY clause is not allowed on UPDATE or DELETE statements "
"within triggers");
}
break;
- case 242: /* tridxby ::= NOT INDEXED */
+ case 260: /* tridxby ::= NOT INDEXED */
{
- sqlite3ErrorMsg(pParse,
+ tdsqlite3ErrorMsg(pParse,
"the NOT INDEXED clause is not allowed on UPDATE or DELETE statements "
"within triggers");
}
break;
- case 243: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt */
-{yymsp[-6].minor.yy145 = sqlite3TriggerUpdateStep(pParse->db, &yymsp[-4].minor.yy0, yymsp[-1].minor.yy148, yymsp[0].minor.yy72, yymsp[-5].minor.yy194);}
+ case 261: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt scanpt */
+{yylhsminor.yy447 = tdsqlite3TriggerUpdateStep(pParse, &yymsp[-5].minor.yy0, yymsp[-2].minor.yy242, yymsp[-1].minor.yy202, yymsp[-6].minor.yy192, yymsp[-7].minor.yy0.z, yymsp[0].minor.yy436);}
+ yymsp[-7].minor.yy447 = yylhsminor.yy447;
break;
- case 244: /* trigger_cmd ::= insert_cmd INTO trnm idlist_opt select */
-{yymsp[-4].minor.yy145 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy254, yymsp[0].minor.yy243, yymsp[-4].minor.yy194);/*A-overwrites-R*/}
+ case 262: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */
+{
+ yylhsminor.yy447 = tdsqlite3TriggerInsertStep(pParse,&yymsp[-4].minor.yy0,yymsp[-3].minor.yy600,yymsp[-2].minor.yy539,yymsp[-6].minor.yy192,yymsp[-1].minor.yy318,yymsp[-7].minor.yy436,yymsp[0].minor.yy436);/*yylhsminor.yy447-overwrites-yymsp[-6].minor.yy192*/
+}
+ yymsp[-7].minor.yy447 = yylhsminor.yy447;
break;
- case 245: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt */
-{yymsp[-4].minor.yy145 = sqlite3TriggerDeleteStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[0].minor.yy72);}
+ case 263: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */
+{yylhsminor.yy447 = tdsqlite3TriggerDeleteStep(pParse, &yymsp[-3].minor.yy0, yymsp[-1].minor.yy202, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy436);}
+ yymsp[-5].minor.yy447 = yylhsminor.yy447;
break;
- case 246: /* trigger_cmd ::= select */
-{yymsp[0].minor.yy145 = sqlite3TriggerSelectStep(pParse->db, yymsp[0].minor.yy243); /*A-overwrites-X*/}
+ case 264: /* trigger_cmd ::= scanpt select scanpt */
+{yylhsminor.yy447 = tdsqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy539, yymsp[-2].minor.yy436, yymsp[0].minor.yy436); /*yylhsminor.yy447-overwrites-yymsp[-1].minor.yy539*/}
+ yymsp[-2].minor.yy447 = yylhsminor.yy447;
break;
- case 247: /* expr ::= RAISE LP IGNORE RP */
+ case 265: /* expr ::= RAISE LP IGNORE RP */
{
- spanSet(&yymsp[-3].minor.yy190,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/
- yymsp[-3].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, 0);
- if( yymsp[-3].minor.yy190.pExpr ){
- yymsp[-3].minor.yy190.pExpr->affinity = OE_Ignore;
+ yymsp[-3].minor.yy202 = tdsqlite3PExpr(pParse, TK_RAISE, 0, 0);
+ if( yymsp[-3].minor.yy202 ){
+ yymsp[-3].minor.yy202->affExpr = OE_Ignore;
}
}
break;
- case 248: /* expr ::= RAISE LP raisetype COMMA nm RP */
+ case 266: /* expr ::= RAISE LP raisetype COMMA nm RP */
{
- spanSet(&yymsp[-5].minor.yy190,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/
- yymsp[-5].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, &yymsp[-1].minor.yy0);
- if( yymsp[-5].minor.yy190.pExpr ) {
- yymsp[-5].minor.yy190.pExpr->affinity = (char)yymsp[-3].minor.yy194;
+ yymsp[-5].minor.yy202 = tdsqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1);
+ if( yymsp[-5].minor.yy202 ) {
+ yymsp[-5].minor.yy202->affExpr = (char)yymsp[-3].minor.yy192;
}
}
break;
- case 249: /* raisetype ::= ROLLBACK */
-{yymsp[0].minor.yy194 = OE_Rollback;}
+ case 267: /* raisetype ::= ROLLBACK */
+{yymsp[0].minor.yy192 = OE_Rollback;}
break;
- case 251: /* raisetype ::= FAIL */
-{yymsp[0].minor.yy194 = OE_Fail;}
+ case 269: /* raisetype ::= FAIL */
+{yymsp[0].minor.yy192 = OE_Fail;}
break;
- case 252: /* cmd ::= DROP TRIGGER ifexists fullname */
+ case 270: /* cmd ::= DROP TRIGGER ifexists fullname */
{
- sqlite3DropTrigger(pParse,yymsp[0].minor.yy185,yymsp[-1].minor.yy194);
+ tdsqlite3DropTrigger(pParse,yymsp[0].minor.yy47,yymsp[-1].minor.yy192);
}
break;
- case 253: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
+ case 271: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */
{
- sqlite3Attach(pParse, yymsp[-3].minor.yy190.pExpr, yymsp[-1].minor.yy190.pExpr, yymsp[0].minor.yy72);
+ tdsqlite3Attach(pParse, yymsp[-3].minor.yy202, yymsp[-1].minor.yy202, yymsp[0].minor.yy202);
}
break;
- case 254: /* cmd ::= DETACH database_kw_opt expr */
+ case 272: /* cmd ::= DETACH database_kw_opt expr */
{
- sqlite3Detach(pParse, yymsp[0].minor.yy190.pExpr);
+ tdsqlite3Detach(pParse, yymsp[0].minor.yy202);
}
break;
- case 257: /* cmd ::= REINDEX */
-{sqlite3Reindex(pParse, 0, 0);}
+ case 275: /* cmd ::= REINDEX */
+{tdsqlite3Reindex(pParse, 0, 0);}
break;
- case 258: /* cmd ::= REINDEX nm dbnm */
-{sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);}
+ case 276: /* cmd ::= REINDEX nm dbnm */
+{tdsqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);}
break;
- case 259: /* cmd ::= ANALYZE */
-{sqlite3Analyze(pParse, 0, 0);}
+ case 277: /* cmd ::= ANALYZE */
+{tdsqlite3Analyze(pParse, 0, 0);}
break;
- case 260: /* cmd ::= ANALYZE nm dbnm */
-{sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);}
+ case 278: /* cmd ::= ANALYZE nm dbnm */
+{tdsqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);}
break;
- case 261: /* cmd ::= ALTER TABLE fullname RENAME TO nm */
+ case 279: /* cmd ::= ALTER TABLE fullname RENAME TO nm */
{
- sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy185,&yymsp[0].minor.yy0);
+ tdsqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy47,&yymsp[0].minor.yy0);
}
break;
- case 262: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
+ case 280: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */
{
yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n;
- sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0);
+ tdsqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0);
}
break;
- case 263: /* add_column_fullname ::= fullname */
+ case 281: /* add_column_fullname ::= fullname */
{
disableLookaside(pParse);
- sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy185);
+ tdsqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy47);
+}
+ break;
+ case 282: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */
+{
+ tdsqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy47, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0);
+}
+ break;
+ case 283: /* cmd ::= create_vtab */
+{tdsqlite3VtabFinishParse(pParse,0);}
+ break;
+ case 284: /* cmd ::= create_vtab LP vtabarglist RP */
+{tdsqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);}
+ break;
+ case 285: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+{
+ tdsqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy192);
+}
+ break;
+ case 286: /* vtabarg ::= */
+{tdsqlite3VtabArgInit(pParse);}
+ break;
+ case 287: /* vtabargtoken ::= ANY */
+ case 288: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==288);
+ case 289: /* lp ::= LP */ yytestcase(yyruleno==289);
+{tdsqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);}
+ break;
+ case 290: /* with ::= WITH wqlist */
+ case 291: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==291);
+{ tdsqlite3WithPush(pParse, yymsp[0].minor.yy131, 1); }
+ break;
+ case 292: /* wqlist ::= nm eidlist_opt AS LP select RP */
+{
+ yymsp[-5].minor.yy131 = tdsqlite3WithAdd(pParse, 0, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy242, yymsp[-1].minor.yy539); /*A-overwrites-X*/
}
break;
- case 264: /* cmd ::= create_vtab */
-{sqlite3VtabFinishParse(pParse,0);}
+ case 293: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */
+{
+ yymsp[-7].minor.yy131 = tdsqlite3WithAdd(pParse, yymsp[-7].minor.yy131, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy242, yymsp[-1].minor.yy539);
+}
break;
- case 265: /* cmd ::= create_vtab LP vtabarglist RP */
-{sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);}
+ case 294: /* windowdefn_list ::= windowdefn */
+{ yylhsminor.yy303 = yymsp[0].minor.yy303; }
+ yymsp[0].minor.yy303 = yylhsminor.yy303;
break;
- case 266: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */
+ case 295: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */
{
- sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy194);
+ assert( yymsp[0].minor.yy303!=0 );
+ tdsqlite3WindowChain(pParse, yymsp[0].minor.yy303, yymsp[-2].minor.yy303);
+ yymsp[0].minor.yy303->pNextWin = yymsp[-2].minor.yy303;
+ yylhsminor.yy303 = yymsp[0].minor.yy303;
}
+ yymsp[-2].minor.yy303 = yylhsminor.yy303;
break;
- case 267: /* vtabarg ::= */
-{sqlite3VtabArgInit(pParse);}
+ case 296: /* windowdefn ::= nm AS LP window RP */
+{
+ if( ALWAYS(yymsp[-1].minor.yy303) ){
+ yymsp[-1].minor.yy303->zName = tdsqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n);
+ }
+ yylhsminor.yy303 = yymsp[-1].minor.yy303;
+}
+ yymsp[-4].minor.yy303 = yylhsminor.yy303;
break;
- case 268: /* vtabargtoken ::= ANY */
- case 269: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==269);
- case 270: /* lp ::= LP */ yytestcase(yyruleno==270);
-{sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);}
+ case 297: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */
+{
+ yymsp[-4].minor.yy303 = tdsqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, yymsp[-2].minor.yy242, yymsp[-1].minor.yy242, 0);
+}
break;
- case 271: /* with ::= */
-{yymsp[1].minor.yy285 = 0;}
+ case 298: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */
+{
+ yylhsminor.yy303 = tdsqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, yymsp[-2].minor.yy242, yymsp[-1].minor.yy242, &yymsp[-5].minor.yy0);
+}
+ yymsp[-5].minor.yy303 = yylhsminor.yy303;
break;
- case 272: /* with ::= WITH wqlist */
-{ yymsp[-1].minor.yy285 = yymsp[0].minor.yy285; }
+ case 299: /* window ::= ORDER BY sortlist frame_opt */
+{
+ yymsp[-3].minor.yy303 = tdsqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, 0, yymsp[-1].minor.yy242, 0);
+}
break;
- case 273: /* with ::= WITH RECURSIVE wqlist */
-{ yymsp[-2].minor.yy285 = yymsp[0].minor.yy285; }
+ case 300: /* window ::= nm ORDER BY sortlist frame_opt */
+{
+ yylhsminor.yy303 = tdsqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, 0, yymsp[-1].minor.yy242, &yymsp[-4].minor.yy0);
+}
+ yymsp[-4].minor.yy303 = yylhsminor.yy303;
break;
- case 274: /* wqlist ::= nm eidlist_opt AS LP select RP */
+ case 301: /* window ::= frame_opt */
+ case 320: /* filter_over ::= over_clause */ yytestcase(yyruleno==320);
{
- yymsp[-5].minor.yy285 = sqlite3WithAdd(pParse, 0, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy148, yymsp[-1].minor.yy243); /*A-overwrites-X*/
+ yylhsminor.yy303 = yymsp[0].minor.yy303;
}
+ yymsp[0].minor.yy303 = yylhsminor.yy303;
break;
- case 275: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */
+ case 302: /* window ::= nm frame_opt */
{
- yymsp[-7].minor.yy285 = sqlite3WithAdd(pParse, yymsp[-7].minor.yy285, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy148, yymsp[-1].minor.yy243);
+ yylhsminor.yy303 = tdsqlite3WindowAssemble(pParse, yymsp[0].minor.yy303, 0, 0, &yymsp[-1].minor.yy0);
+}
+ yymsp[-1].minor.yy303 = yylhsminor.yy303;
+ break;
+ case 303: /* frame_opt ::= */
+{
+ yymsp[1].minor.yy303 = tdsqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0);
}
break;
+ case 304: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */
+{
+ yylhsminor.yy303 = tdsqlite3WindowAlloc(pParse, yymsp[-2].minor.yy192, yymsp[-1].minor.yy77.eType, yymsp[-1].minor.yy77.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy58);
+}
+ yymsp[-2].minor.yy303 = yylhsminor.yy303;
+ break;
+ case 305: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */
+{
+ yylhsminor.yy303 = tdsqlite3WindowAlloc(pParse, yymsp[-5].minor.yy192, yymsp[-3].minor.yy77.eType, yymsp[-3].minor.yy77.pExpr, yymsp[-1].minor.yy77.eType, yymsp[-1].minor.yy77.pExpr, yymsp[0].minor.yy58);
+}
+ yymsp[-5].minor.yy303 = yylhsminor.yy303;
+ break;
+ case 307: /* frame_bound_s ::= frame_bound */
+ case 309: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==309);
+{yylhsminor.yy77 = yymsp[0].minor.yy77;}
+ yymsp[0].minor.yy77 = yylhsminor.yy77;
+ break;
+ case 308: /* frame_bound_s ::= UNBOUNDED PRECEDING */
+ case 310: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==310);
+ case 312: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==312);
+{yylhsminor.yy77.eType = yymsp[-1].major; yylhsminor.yy77.pExpr = 0;}
+ yymsp[-1].minor.yy77 = yylhsminor.yy77;
+ break;
+ case 311: /* frame_bound ::= expr PRECEDING|FOLLOWING */
+{yylhsminor.yy77.eType = yymsp[0].major; yylhsminor.yy77.pExpr = yymsp[-1].minor.yy202;}
+ yymsp[-1].minor.yy77 = yylhsminor.yy77;
+ break;
+ case 313: /* frame_exclude_opt ::= */
+{yymsp[1].minor.yy58 = 0;}
+ break;
+ case 314: /* frame_exclude_opt ::= EXCLUDE frame_exclude */
+{yymsp[-1].minor.yy58 = yymsp[0].minor.yy58;}
+ break;
+ case 315: /* frame_exclude ::= NO OTHERS */
+ case 316: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==316);
+{yymsp[-1].minor.yy58 = yymsp[-1].major; /*A-overwrites-X*/}
+ break;
+ case 317: /* frame_exclude ::= GROUP|TIES */
+{yymsp[0].minor.yy58 = yymsp[0].major; /*A-overwrites-X*/}
+ break;
+ case 318: /* window_clause ::= WINDOW windowdefn_list */
+{ yymsp[-1].minor.yy303 = yymsp[0].minor.yy303; }
+ break;
+ case 319: /* filter_over ::= filter_clause over_clause */
+{
+ yymsp[0].minor.yy303->pFilter = yymsp[-1].minor.yy202;
+ yylhsminor.yy303 = yymsp[0].minor.yy303;
+}
+ yymsp[-1].minor.yy303 = yylhsminor.yy303;
+ break;
+ case 321: /* filter_over ::= filter_clause */
+{
+ yylhsminor.yy303 = (Window*)tdsqlite3DbMallocZero(pParse->db, sizeof(Window));
+ if( yylhsminor.yy303 ){
+ yylhsminor.yy303->eFrmType = TK_FILTER;
+ yylhsminor.yy303->pFilter = yymsp[0].minor.yy202;
+ }else{
+ tdsqlite3ExprDelete(pParse->db, yymsp[0].minor.yy202);
+ }
+}
+ yymsp[0].minor.yy303 = yylhsminor.yy303;
+ break;
+ case 322: /* over_clause ::= OVER LP window RP */
+{
+ yymsp[-3].minor.yy303 = yymsp[-1].minor.yy303;
+ assert( yymsp[-3].minor.yy303!=0 );
+}
+ break;
+ case 323: /* over_clause ::= OVER nm */
+{
+ yymsp[-1].minor.yy303 = (Window*)tdsqlite3DbMallocZero(pParse->db, sizeof(Window));
+ if( yymsp[-1].minor.yy303 ){
+ yymsp[-1].minor.yy303->zName = tdsqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n);
+ }
+}
+ break;
+ case 324: /* filter_clause ::= FILTER LP WHERE expr RP */
+{ yymsp[-4].minor.yy202 = yymsp[-1].minor.yy202; }
+ break;
default:
- /* (276) input ::= cmdlist */ yytestcase(yyruleno==276);
- /* (277) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==277);
- /* (278) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=278);
- /* (279) ecmd ::= SEMI */ yytestcase(yyruleno==279);
- /* (280) ecmd ::= explain cmdx SEMI */ yytestcase(yyruleno==280);
- /* (281) explain ::= */ yytestcase(yyruleno==281);
- /* (282) trans_opt ::= */ yytestcase(yyruleno==282);
- /* (283) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==283);
- /* (284) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==284);
- /* (285) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==285);
- /* (286) savepoint_opt ::= */ yytestcase(yyruleno==286);
- /* (287) cmd ::= create_table create_table_args */ yytestcase(yyruleno==287);
- /* (288) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==288);
- /* (289) columnlist ::= columnname carglist */ yytestcase(yyruleno==289);
- /* (290) nm ::= ID|INDEXED */ yytestcase(yyruleno==290);
- /* (291) nm ::= STRING */ yytestcase(yyruleno==291);
- /* (292) nm ::= JOIN_KW */ yytestcase(yyruleno==292);
- /* (293) typetoken ::= typename */ yytestcase(yyruleno==293);
- /* (294) typename ::= ID|STRING */ yytestcase(yyruleno==294);
- /* (295) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=295);
- /* (296) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=296);
- /* (297) carglist ::= carglist ccons */ yytestcase(yyruleno==297);
- /* (298) carglist ::= */ yytestcase(yyruleno==298);
- /* (299) ccons ::= NULL onconf */ yytestcase(yyruleno==299);
- /* (300) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==300);
- /* (301) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==301);
- /* (302) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=302);
- /* (303) tconscomma ::= */ yytestcase(yyruleno==303);
- /* (304) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=304);
- /* (305) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=305);
- /* (306) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=306);
- /* (307) oneselect ::= values */ yytestcase(yyruleno==307);
- /* (308) sclp ::= selcollist COMMA */ yytestcase(yyruleno==308);
- /* (309) as ::= ID|STRING */ yytestcase(yyruleno==309);
- /* (310) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=310);
- /* (311) exprlist ::= nexprlist */ yytestcase(yyruleno==311);
- /* (312) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=312);
- /* (313) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=313);
- /* (314) nmnum ::= ON */ yytestcase(yyruleno==314);
- /* (315) nmnum ::= DELETE */ yytestcase(yyruleno==315);
- /* (316) nmnum ::= DEFAULT */ yytestcase(yyruleno==316);
- /* (317) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==317);
- /* (318) foreach_clause ::= */ yytestcase(yyruleno==318);
- /* (319) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==319);
- /* (320) trnm ::= nm */ yytestcase(yyruleno==320);
- /* (321) tridxby ::= */ yytestcase(yyruleno==321);
- /* (322) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==322);
- /* (323) database_kw_opt ::= */ yytestcase(yyruleno==323);
- /* (324) kwcolumn_opt ::= */ yytestcase(yyruleno==324);
- /* (325) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==325);
- /* (326) vtabarglist ::= vtabarg */ yytestcase(yyruleno==326);
- /* (327) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==327);
- /* (328) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==328);
- /* (329) anylist ::= */ yytestcase(yyruleno==329);
- /* (330) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==330);
- /* (331) anylist ::= anylist ANY */ yytestcase(yyruleno==331);
+ /* (325) input ::= cmdlist */ yytestcase(yyruleno==325);
+ /* (326) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==326);
+ /* (327) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=327);
+ /* (328) ecmd ::= SEMI */ yytestcase(yyruleno==328);
+ /* (329) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==329);
+ /* (330) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=330);
+ /* (331) trans_opt ::= */ yytestcase(yyruleno==331);
+ /* (332) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==332);
+ /* (333) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==333);
+ /* (334) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==334);
+ /* (335) savepoint_opt ::= */ yytestcase(yyruleno==335);
+ /* (336) cmd ::= create_table create_table_args */ yytestcase(yyruleno==336);
+ /* (337) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==337);
+ /* (338) columnlist ::= columnname carglist */ yytestcase(yyruleno==338);
+ /* (339) nm ::= ID|INDEXED */ yytestcase(yyruleno==339);
+ /* (340) nm ::= STRING */ yytestcase(yyruleno==340);
+ /* (341) nm ::= JOIN_KW */ yytestcase(yyruleno==341);
+ /* (342) typetoken ::= typename */ yytestcase(yyruleno==342);
+ /* (343) typename ::= ID|STRING */ yytestcase(yyruleno==343);
+ /* (344) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=344);
+ /* (345) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=345);
+ /* (346) carglist ::= carglist ccons */ yytestcase(yyruleno==346);
+ /* (347) carglist ::= */ yytestcase(yyruleno==347);
+ /* (348) ccons ::= NULL onconf */ yytestcase(yyruleno==348);
+ /* (349) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==349);
+ /* (350) ccons ::= AS generated */ yytestcase(yyruleno==350);
+ /* (351) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==351);
+ /* (352) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==352);
+ /* (353) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=353);
+ /* (354) tconscomma ::= */ yytestcase(yyruleno==354);
+ /* (355) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=355);
+ /* (356) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=356);
+ /* (357) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=357);
+ /* (358) oneselect ::= values */ yytestcase(yyruleno==358);
+ /* (359) sclp ::= selcollist COMMA */ yytestcase(yyruleno==359);
+ /* (360) as ::= ID|STRING */ yytestcase(yyruleno==360);
+ /* (361) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=361);
+ /* (362) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==362);
+ /* (363) exprlist ::= nexprlist */ yytestcase(yyruleno==363);
+ /* (364) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=364);
+ /* (365) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=365);
+ /* (366) nmnum ::= ON */ yytestcase(yyruleno==366);
+ /* (367) nmnum ::= DELETE */ yytestcase(yyruleno==367);
+ /* (368) nmnum ::= DEFAULT */ yytestcase(yyruleno==368);
+ /* (369) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==369);
+ /* (370) foreach_clause ::= */ yytestcase(yyruleno==370);
+ /* (371) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==371);
+ /* (372) trnm ::= nm */ yytestcase(yyruleno==372);
+ /* (373) tridxby ::= */ yytestcase(yyruleno==373);
+ /* (374) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==374);
+ /* (375) database_kw_opt ::= */ yytestcase(yyruleno==375);
+ /* (376) kwcolumn_opt ::= */ yytestcase(yyruleno==376);
+ /* (377) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==377);
+ /* (378) vtabarglist ::= vtabarg */ yytestcase(yyruleno==378);
+ /* (379) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==379);
+ /* (380) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==380);
+ /* (381) anylist ::= */ yytestcase(yyruleno==381);
+ /* (382) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==382);
+ /* (383) anylist ::= anylist ANY */ yytestcase(yyruleno==383);
+ /* (384) with ::= */ yytestcase(yyruleno==384);
break;
/********** End reduce actions ************************************************/
};
- assert( yyruleno<sizeof(yyRuleInfo)/sizeof(yyRuleInfo[0]) );
- yygoto = yyRuleInfo[yyruleno].lhs;
- yysize = yyRuleInfo[yyruleno].nrhs;
- yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto);
- if( yyact <= YY_MAX_SHIFTREDUCE ){
- if( yyact>YY_MAX_SHIFT ){
- yyact += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE;
- }
- yymsp -= yysize-1;
- yypParser->yytos = yymsp;
- yymsp->stateno = (YYACTIONTYPE)yyact;
- yymsp->major = (YYCODETYPE)yygoto;
- yyTraceShift(yypParser, yyact);
- }else{
- assert( yyact == YY_ACCEPT_ACTION );
- yypParser->yytos -= yysize;
- yy_accept(yypParser);
- }
+ assert( yyruleno<sizeof(yyRuleInfoLhs)/sizeof(yyRuleInfoLhs[0]) );
+ yygoto = yyRuleInfoLhs[yyruleno];
+ yysize = yyRuleInfoNRhs[yyruleno];
+ yyact = yy_find_reduce_action(yymsp[yysize].stateno,(YYCODETYPE)yygoto);
+
+ /* There are no SHIFTREDUCE actions on nonterminals because the table
+ ** generator has simplified them to pure REDUCE actions. */
+ assert( !(yyact>YY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) );
+
+ /* It is not possible for a REDUCE to be followed by an error */
+ assert( yyact!=YY_ERROR_ACTION );
+
+ yymsp += yysize+1;
+ yypParser->yytos = yymsp;
+ yymsp->stateno = (YYACTIONTYPE)yyact;
+ yymsp->major = (YYCODETYPE)yygoto;
+ yyTraceShift(yypParser, yyact, "... then shift");
+ return yyact;
}
/*
@@ -139444,7 +160693,8 @@ static void yy_reduce(
static void yy_parse_failed(
yyParser *yypParser /* The parser */
){
- sqlite3ParserARG_FETCH;
+ tdsqlite3ParserARG_FETCH
+ tdsqlite3ParserCTX_FETCH
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
@@ -139455,7 +160705,8 @@ static void yy_parse_failed(
** parser fails */
/************ Begin %parse_failure code ***************************************/
/************ End %parse_failure code *****************************************/
- sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3ParserCTX_STORE
}
#endif /* YYNOERRORRECOVERY */
@@ -139465,17 +160716,22 @@ static void yy_parse_failed(
static void yy_syntax_error(
yyParser *yypParser, /* The parser */
int yymajor, /* The major type of the error token */
- sqlite3ParserTOKENTYPE yyminor /* The minor type of the error token */
+ tdsqlite3ParserTOKENTYPE yyminor /* The minor type of the error token */
){
- sqlite3ParserARG_FETCH;
+ tdsqlite3ParserARG_FETCH
+ tdsqlite3ParserCTX_FETCH
#define TOKEN yyminor
/************ Begin %syntax_error code ****************************************/
UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */
- assert( TOKEN.z[0] ); /* The tokenizer always gives us a token */
- sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN);
+ if( TOKEN.z[0] ){
+ tdsqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN);
+ }else{
+ tdsqlite3ErrorMsg(pParse, "incomplete input");
+ }
/************ End %syntax_error code ******************************************/
- sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3ParserCTX_STORE
}
/*
@@ -139484,7 +160740,8 @@ static void yy_syntax_error(
static void yy_accept(
yyParser *yypParser /* The parser */
){
- sqlite3ParserARG_FETCH;
+ tdsqlite3ParserARG_FETCH
+ tdsqlite3ParserCTX_FETCH
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
@@ -139498,12 +160755,13 @@ static void yy_accept(
** parser accepts */
/*********** Begin %parse_accept code *****************************************/
/*********** End %parse_accept code *******************************************/
- sqlite3ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3ParserCTX_STORE
}
/* The main parser program.
** The first argument is a pointer to a structure obtained from
-** "sqlite3ParserAlloc" which describes the current state of the parser.
+** "tdsqlite3ParserAlloc" 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
@@ -139520,45 +160778,58 @@ static void yy_accept(
** Outputs:
** None.
*/
-SQLITE_PRIVATE void sqlite3Parser(
+SQLITE_PRIVATE void tdsqlite3Parser(
void *yyp, /* The parser */
int yymajor, /* The major token code number */
- sqlite3ParserTOKENTYPE yyminor /* The value for the token */
- sqlite3ParserARG_PDECL /* Optional %extra_argument parameter */
+ tdsqlite3ParserTOKENTYPE yyminor /* The value for the token */
+ tdsqlite3ParserARG_PDECL /* Optional %extra_argument parameter */
){
YYMINORTYPE yyminorunion;
- unsigned int yyact; /* The parser action. */
+ YYACTIONTYPE 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 */
+ yyParser *yypParser = (yyParser*)yyp; /* The parser */
+ tdsqlite3ParserCTX_FETCH
+ tdsqlite3ParserARG_STORE
- yypParser = (yyParser*)yyp;
assert( yypParser->yytos!=0 );
#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY)
yyendofinput = (yymajor==0);
#endif
- sqlite3ParserARG_STORE;
+ yyact = yypParser->yytos->stateno;
#ifndef NDEBUG
if( yyTraceFILE ){
- fprintf(yyTraceFILE,"%sInput '%s'\n",yyTracePrompt,yyTokenName[yymajor]);
+ if( yyact < YY_MIN_REDUCE ){
+ fprintf(yyTraceFILE,"%sInput '%s' in state %d\n",
+ yyTracePrompt,yyTokenName[yymajor],yyact);
+ }else{
+ fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n",
+ yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE);
+ }
}
#endif
do{
- yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor);
- if( yyact <= YY_MAX_SHIFTREDUCE ){
- yy_shift(yypParser,yyact,yymajor,yyminor);
+ assert( yyact==yypParser->yytos->stateno );
+ yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact);
+ if( yyact >= YY_MIN_REDUCE ){
+ yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor,
+ yyminor tdsqlite3ParserCTX_PARAM);
+ }else if( yyact <= YY_MAX_SHIFTREDUCE ){
+ yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor);
#ifndef YYNOERRORRECOVERY
yypParser->yyerrcnt--;
#endif
- yymajor = YYNOCODE;
- }else if( yyact <= YY_MAX_REDUCE ){
- yy_reduce(yypParser,yyact-YY_MIN_REDUCE);
+ break;
+ }else if( yyact==YY_ACCEPT_ACTION ){
+ yypParser->yytos--;
+ yy_accept(yypParser);
+ return;
}else{
assert( yyact == YY_ERROR_ACTION );
yyminorunion.yy0 = yyminor;
@@ -139605,10 +160876,9 @@ SQLITE_PRIVATE void sqlite3Parser(
yymajor = YYNOCODE;
}else{
while( yypParser->yytos >= yypParser->yystack
- && yymx != YYERRORSYMBOL
&& (yyact = yy_find_reduce_action(
yypParser->yytos->stateno,
- YYERRORSYMBOL)) >= YY_MIN_REDUCE
+ YYERRORSYMBOL)) > YY_MAX_SHIFTREDUCE
){
yy_pop_parser_stack(yypParser);
}
@@ -139625,6 +160895,8 @@ SQLITE_PRIVATE void sqlite3Parser(
}
yypParser->yyerrcnt = 3;
yyerrorhit = 1;
+ if( yymajor==YYNOCODE ) break;
+ yyact = yypParser->yytos->stateno;
#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
@@ -139635,8 +160907,7 @@ SQLITE_PRIVATE void sqlite3Parser(
*/
yy_syntax_error(yypParser,yymajor, yyminor);
yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
- yymajor = YYNOCODE;
-
+ break;
#else /* YYERRORSYMBOL is not defined */
/* This is what we do if the grammar does not define ERROR:
**
@@ -139658,10 +160929,10 @@ SQLITE_PRIVATE void sqlite3Parser(
yypParser->yyerrcnt = -1;
#endif
}
- yymajor = YYNOCODE;
+ break;
#endif
}
- }while( yymajor!=YYNOCODE && yypParser->yytos>yypParser->yystack );
+ }while( yypParser->yytos>yypParser->yystack );
#ifndef NDEBUG
if( yyTraceFILE ){
yyStackEntry *i;
@@ -139677,6 +160948,20 @@ SQLITE_PRIVATE void sqlite3Parser(
return;
}
+/*
+** Return the fallback token corresponding to canonical token iToken, or
+** 0 if iToken has no fallback.
+*/
+SQLITE_PRIVATE int tdsqlite3ParserFallback(int iToken){
+#ifdef YYFALLBACK
+ assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) );
+ return yyFallback[iToken];
+#else
+ (void)iToken;
+ return 0;
+#endif
+}
+
/************** End of parse.c ***********************************************/
/************** Begin file tokenize.c ****************************************/
/*
@@ -139701,7 +160986,7 @@ SQLITE_PRIVATE void sqlite3Parser(
/* Character classes for tokenizing
**
-** In the sqlite3GetToken() function, a switch() on aiClass[c] is implemented
+** In the tdsqlite3GetToken() function, a switch() on aiClass[c] is implemented
** using a lookup table, whereas a switch() directly on c uses a binary search.
** The lookup table is much faster. To maximize speed, and to ensure that
** a lookup table is used, all of the classes need to be small integers and
@@ -139735,11 +161020,12 @@ SQLITE_PRIVATE void sqlite3Parser(
#define CC_TILDA 25 /* '~' */
#define CC_DOT 26 /* '.' */
#define CC_ILLEGAL 27 /* Illegal character */
+#define CC_NUL 28 /* 0x00 */
static const unsigned char aiClass[] = {
#ifdef SQLITE_ASCII
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
-/* 0x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 7, 7, 27, 7, 7, 27, 27,
+/* 0x */ 28, 27, 27, 27, 27, 27, 27, 27, 27, 7, 7, 27, 7, 7, 27, 27,
/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
/* 2x */ 7, 15, 8, 5, 4, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16,
/* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6,
@@ -139762,13 +161048,13 @@ static const unsigned char aiClass[] = {
/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
/* 2x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
/* 3x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
-/* 4x */ 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 12, 17, 20, 10,
+/* 4x */ 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 12, 17, 20, 10,
/* 5x */ 24, 27, 27, 27, 27, 27, 27, 27, 27, 27, 15, 4, 21, 18, 19, 27,
-/* 6x */ 11, 16, 27, 27, 27, 27, 27, 27, 27, 27, 27, 23, 22, 1, 13, 7,
+/* 6x */ 11, 16, 27, 27, 27, 27, 27, 27, 27, 27, 27, 23, 22, 1, 13, 6,
/* 7x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 8, 5, 5, 5, 8, 14, 8,
/* 8x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27,
/* 9x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27,
-/* 9x */ 25, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27,
+/* Ax */ 27, 25, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27,
/* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 9, 27, 27, 27, 27, 27,
/* Cx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27,
/* Dx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27,
@@ -139787,7 +161073,7 @@ static const unsigned char aiClass[] = {
** Used by keywordhash.h
*/
#ifdef SQLITE_ASCII
-# define charMap(X) sqlite3UpperToLower[(unsigned char)X]
+# define charMap(X) tdsqlite3UpperToLower[(unsigned char)X]
#endif
#ifdef SQLITE_EBCDIC
# define charMap(X) ebcdicToAscii[(unsigned char)X]
@@ -139813,7 +161099,7 @@ const unsigned char ebcdicToAscii[] = {
#endif
/*
-** The sqlite3KeywordCode function looks up an identifier to determine if
+** The tdsqlite3KeywordCode function looks up an identifier to determine if
** it is a keyword. If it is a keyword, the token code of that keyword is
** returned. If the input is not a keyword, TK_ID is returned.
**
@@ -139838,135 +161124,291 @@ const unsigned char ebcdicToAscii[] = {
** is substantially reduced. This is important for embedded applications
** on platforms with limited memory.
*/
-/* Hash score: 182 */
+/* Hash score: 227 */
+/* zKWText[] encodes 984 bytes of keyword text in 648 bytes */
+/* REINDEXEDESCAPEACHECKEYBEFOREIGNOREGEXPLAINSTEADDATABASELECT */
+/* ABLEFTHENDEFERRABLELSEXCLUDELETEMPORARYISNULLSAVEPOINTERSECT */
+/* IESNOTNULLIKEXCEPTRANSACTIONATURALTERAISEXCLUSIVEXISTS */
+/* CONSTRAINTOFFSETRIGGERANGENERATEDETACHAVINGLOBEGINNEREFERENCES */
+/* UNIQUERYWITHOUTERELEASEATTACHBETWEENOTHINGROUPSCASCADEFAULT */
+/* CASECOLLATECREATECURRENT_DATEIMMEDIATEJOINSERTMATCHPLANALYZE */
+/* PRAGMABORTUPDATEVALUESVIRTUALWAYSWHENWHERECURSIVEAFTERENAMEAND */
+/* EFERREDISTINCTAUTOINCREMENTCASTCOLUMNCOMMITCONFLICTCROSS */
+/* CURRENT_TIMESTAMPARTITIONDROPRECEDINGFAILASTFILTEREPLACEFIRST */
+/* FOLLOWINGFROMFULLIMITIFORDERESTRICTOTHERSOVERIGHTROLLBACKROWS */
+/* UNBOUNDEDUNIONUSINGVACUUMVIEWINDOWBYINITIALLYPRIMARY */
+static const char zKWText[647] = {
+ 'R','E','I','N','D','E','X','E','D','E','S','C','A','P','E','A','C','H',
+ 'E','C','K','E','Y','B','E','F','O','R','E','I','G','N','O','R','E','G',
+ 'E','X','P','L','A','I','N','S','T','E','A','D','D','A','T','A','B','A',
+ 'S','E','L','E','C','T','A','B','L','E','F','T','H','E','N','D','E','F',
+ 'E','R','R','A','B','L','E','L','S','E','X','C','L','U','D','E','L','E',
+ 'T','E','M','P','O','R','A','R','Y','I','S','N','U','L','L','S','A','V',
+ 'E','P','O','I','N','T','E','R','S','E','C','T','I','E','S','N','O','T',
+ 'N','U','L','L','I','K','E','X','C','E','P','T','R','A','N','S','A','C',
+ 'T','I','O','N','A','T','U','R','A','L','T','E','R','A','I','S','E','X',
+ 'C','L','U','S','I','V','E','X','I','S','T','S','C','O','N','S','T','R',
+ 'A','I','N','T','O','F','F','S','E','T','R','I','G','G','E','R','A','N',
+ 'G','E','N','E','R','A','T','E','D','E','T','A','C','H','A','V','I','N',
+ 'G','L','O','B','E','G','I','N','N','E','R','E','F','E','R','E','N','C',
+ 'E','S','U','N','I','Q','U','E','R','Y','W','I','T','H','O','U','T','E',
+ 'R','E','L','E','A','S','E','A','T','T','A','C','H','B','E','T','W','E',
+ 'E','N','O','T','H','I','N','G','R','O','U','P','S','C','A','S','C','A',
+ 'D','E','F','A','U','L','T','C','A','S','E','C','O','L','L','A','T','E',
+ 'C','R','E','A','T','E','C','U','R','R','E','N','T','_','D','A','T','E',
+ 'I','M','M','E','D','I','A','T','E','J','O','I','N','S','E','R','T','M',
+ 'A','T','C','H','P','L','A','N','A','L','Y','Z','E','P','R','A','G','M',
+ 'A','B','O','R','T','U','P','D','A','T','E','V','A','L','U','E','S','V',
+ 'I','R','T','U','A','L','W','A','Y','S','W','H','E','N','W','H','E','R',
+ 'E','C','U','R','S','I','V','E','A','F','T','E','R','E','N','A','M','E',
+ 'A','N','D','E','F','E','R','R','E','D','I','S','T','I','N','C','T','A',
+ 'U','T','O','I','N','C','R','E','M','E','N','T','C','A','S','T','C','O',
+ 'L','U','M','N','C','O','M','M','I','T','C','O','N','F','L','I','C','T',
+ 'C','R','O','S','S','C','U','R','R','E','N','T','_','T','I','M','E','S',
+ 'T','A','M','P','A','R','T','I','T','I','O','N','D','R','O','P','R','E',
+ 'C','E','D','I','N','G','F','A','I','L','A','S','T','F','I','L','T','E',
+ 'R','E','P','L','A','C','E','F','I','R','S','T','F','O','L','L','O','W',
+ 'I','N','G','F','R','O','M','F','U','L','L','I','M','I','T','I','F','O',
+ 'R','D','E','R','E','S','T','R','I','C','T','O','T','H','E','R','S','O',
+ 'V','E','R','I','G','H','T','R','O','L','L','B','A','C','K','R','O','W',
+ 'S','U','N','B','O','U','N','D','E','D','U','N','I','O','N','U','S','I',
+ 'N','G','V','A','C','U','U','M','V','I','E','W','I','N','D','O','W','B',
+ 'Y','I','N','I','T','I','A','L','L','Y','P','R','I','M','A','R','Y',
+};
+/* aKWHash[i] is the hash value for the i-th keyword */
+static const unsigned char aKWHash[127] = {
+ 84, 102, 132, 82, 114, 29, 0, 0, 91, 0, 85, 72, 0,
+ 53, 35, 86, 15, 0, 42, 94, 54, 126, 133, 19, 0, 0,
+ 138, 0, 40, 128, 0, 22, 104, 0, 9, 0, 0, 122, 80,
+ 0, 78, 6, 0, 65, 99, 145, 0, 134, 112, 0, 0, 48,
+ 0, 100, 24, 0, 17, 0, 27, 70, 23, 26, 5, 60, 140,
+ 107, 121, 0, 73, 101, 71, 143, 61, 119, 74, 0, 49, 0,
+ 11, 41, 0, 110, 0, 0, 0, 106, 10, 108, 113, 124, 14,
+ 50, 123, 0, 89, 0, 18, 120, 142, 56, 129, 137, 88, 83,
+ 37, 30, 125, 0, 0, 105, 51, 130, 127, 0, 34, 0, 0,
+ 44, 0, 95, 38, 39, 0, 20, 45, 116, 90,
+};
+/* aKWNext[] forms the hash collision chain. If aKWHash[i]==0
+** then the i-th keyword has no more hash collisions. Otherwise,
+** the next keyword with the same hash is aKWHash[i]-1. */
+static const unsigned char aKWNext[145] = {
+ 0, 0, 0, 0, 4, 0, 43, 0, 0, 103, 111, 0, 0,
+ 0, 2, 0, 0, 141, 0, 0, 0, 13, 0, 0, 0, 0,
+ 139, 0, 0, 118, 52, 0, 0, 135, 12, 0, 0, 62, 0,
+ 136, 0, 131, 0, 0, 36, 0, 0, 28, 77, 0, 0, 0,
+ 0, 59, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 69, 0, 0, 0, 0, 0, 144, 3, 0, 58, 0, 1,
+ 75, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 64, 66,
+ 63, 0, 0, 0, 0, 46, 0, 16, 0, 115, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 81, 97, 0, 8, 0, 109,
+ 21, 7, 67, 0, 79, 93, 117, 0, 0, 68, 0, 0, 96,
+ 0, 55, 0, 76, 0, 92, 32, 33, 57, 25, 0, 98, 0,
+ 0, 87,
+};
+/* aKWLen[i] is the length (in bytes) of the i-th keyword */
+static const unsigned char aKWLen[145] = {
+ 7, 7, 5, 4, 6, 4, 5, 3, 6, 7, 3, 6, 6,
+ 7, 7, 3, 8, 2, 6, 5, 4, 4, 3, 10, 4, 7,
+ 6, 9, 4, 2, 6, 5, 9, 9, 4, 7, 3, 2, 4,
+ 4, 6, 11, 6, 2, 7, 5, 5, 9, 6, 10, 4, 6,
+ 2, 3, 7, 5, 9, 6, 6, 4, 5, 5, 10, 6, 5,
+ 7, 4, 5, 7, 6, 7, 7, 6, 5, 7, 3, 7, 4,
+ 7, 6, 12, 9, 4, 6, 5, 4, 7, 6, 5, 6, 6,
+ 7, 6, 4, 5, 9, 5, 6, 3, 8, 8, 2, 13, 2,
+ 2, 4, 6, 6, 8, 5, 17, 12, 7, 9, 4, 9, 4,
+ 4, 6, 7, 5, 9, 4, 4, 5, 2, 5, 8, 6, 4,
+ 5, 8, 4, 3, 9, 5, 5, 6, 4, 6, 2, 2, 9,
+ 3, 7,
+};
+/* aKWOffset[i] is the index into zKWText[] of the start of
+** the text for the i-th keyword. */
+static const unsigned short int aKWOffset[145] = {
+ 0, 2, 2, 8, 9, 14, 16, 20, 23, 25, 25, 29, 33,
+ 36, 41, 46, 48, 53, 54, 59, 62, 65, 67, 69, 78, 81,
+ 86, 90, 90, 94, 99, 101, 105, 111, 119, 123, 123, 123, 126,
+ 129, 132, 137, 142, 146, 147, 152, 156, 160, 168, 174, 181, 184,
+ 184, 187, 189, 195, 198, 206, 211, 216, 219, 222, 226, 236, 239,
+ 244, 244, 248, 252, 259, 265, 271, 277, 277, 283, 284, 288, 295,
+ 299, 306, 312, 324, 333, 335, 341, 346, 348, 355, 360, 365, 371,
+ 377, 382, 388, 392, 395, 404, 408, 414, 416, 423, 424, 431, 433,
+ 435, 444, 448, 454, 460, 468, 473, 473, 473, 489, 498, 501, 510,
+ 513, 517, 522, 529, 534, 543, 547, 550, 555, 557, 561, 569, 575,
+ 578, 583, 591, 591, 595, 604, 609, 614, 620, 623, 626, 629, 631,
+ 636, 640,
+};
+/* aKWCode[i] is the parser symbol code for the i-th keyword */
+static const unsigned char aKWCode[145] = {
+ TK_REINDEX, TK_INDEXED, TK_INDEX, TK_DESC, TK_ESCAPE,
+ TK_EACH, TK_CHECK, TK_KEY, TK_BEFORE, TK_FOREIGN,
+ TK_FOR, TK_IGNORE, TK_LIKE_KW, TK_EXPLAIN, TK_INSTEAD,
+ TK_ADD, TK_DATABASE, TK_AS, TK_SELECT, TK_TABLE,
+ TK_JOIN_KW, TK_THEN, TK_END, TK_DEFERRABLE, TK_ELSE,
+ TK_EXCLUDE, TK_DELETE, TK_TEMP, TK_TEMP, TK_OR,
+ TK_ISNULL, TK_NULLS, TK_SAVEPOINT, TK_INTERSECT, TK_TIES,
+ TK_NOTNULL, TK_NOT, TK_NO, TK_NULL, TK_LIKE_KW,
+ TK_EXCEPT, TK_TRANSACTION,TK_ACTION, TK_ON, TK_JOIN_KW,
+ TK_ALTER, TK_RAISE, TK_EXCLUSIVE, TK_EXISTS, TK_CONSTRAINT,
+ TK_INTO, TK_OFFSET, TK_OF, TK_SET, TK_TRIGGER,
+ TK_RANGE, TK_GENERATED, TK_DETACH, TK_HAVING, TK_LIKE_KW,
+ TK_BEGIN, TK_JOIN_KW, TK_REFERENCES, TK_UNIQUE, TK_QUERY,
+ TK_WITHOUT, TK_WITH, TK_JOIN_KW, TK_RELEASE, TK_ATTACH,
+ TK_BETWEEN, TK_NOTHING, TK_GROUPS, TK_GROUP, TK_CASCADE,
+ TK_ASC, TK_DEFAULT, TK_CASE, TK_COLLATE, TK_CREATE,
+ TK_CTIME_KW, TK_IMMEDIATE, TK_JOIN, TK_INSERT, TK_MATCH,
+ TK_PLAN, TK_ANALYZE, TK_PRAGMA, TK_ABORT, TK_UPDATE,
+ TK_VALUES, TK_VIRTUAL, TK_ALWAYS, TK_WHEN, TK_WHERE,
+ TK_RECURSIVE, TK_AFTER, TK_RENAME, TK_AND, TK_DEFERRED,
+ TK_DISTINCT, TK_IS, TK_AUTOINCR, TK_TO, TK_IN,
+ TK_CAST, TK_COLUMNKW, TK_COMMIT, TK_CONFLICT, TK_JOIN_KW,
+ TK_CTIME_KW, TK_CTIME_KW, TK_CURRENT, TK_PARTITION, TK_DROP,
+ TK_PRECEDING, TK_FAIL, TK_LAST, TK_FILTER, TK_REPLACE,
+ TK_FIRST, TK_FOLLOWING, TK_FROM, TK_JOIN_KW, TK_LIMIT,
+ TK_IF, TK_ORDER, TK_RESTRICT, TK_OTHERS, TK_OVER,
+ TK_JOIN_KW, TK_ROLLBACK, TK_ROWS, TK_ROW, TK_UNBOUNDED,
+ TK_UNION, TK_USING, TK_VACUUM, TK_VIEW, TK_WINDOW,
+ TK_DO, TK_BY, TK_INITIALLY, TK_ALL, TK_PRIMARY,
+};
+/* Hash table decoded:
+** 0: INSERT
+** 1: IS
+** 2: ROLLBACK TRIGGER
+** 3: IMMEDIATE
+** 4: PARTITION
+** 5: TEMP
+** 6:
+** 7:
+** 8: VALUES WITHOUT
+** 9:
+** 10: MATCH
+** 11: NOTHING
+** 12:
+** 13: OF
+** 14: TIES IGNORE
+** 15: PLAN
+** 16: INSTEAD INDEXED
+** 17:
+** 18: TRANSACTION RIGHT
+** 19: WHEN
+** 20: SET HAVING
+** 21: IF
+** 22: ROWS
+** 23: SELECT
+** 24:
+** 25:
+** 26: VACUUM SAVEPOINT
+** 27:
+** 28: LIKE UNION VIRTUAL REFERENCES
+** 29: RESTRICT
+** 30:
+** 31: THEN REGEXP
+** 32: TO
+** 33:
+** 34: BEFORE
+** 35:
+** 36:
+** 37: FOLLOWING COLLATE CASCADE
+** 38: CREATE
+** 39:
+** 40: CASE REINDEX
+** 41: EACH
+** 42:
+** 43: QUERY
+** 44: AND ADD
+** 45: PRIMARY ANALYZE
+** 46:
+** 47: ROW ASC DETACH
+** 48: CURRENT_TIME CURRENT_DATE
+** 49:
+** 50:
+** 51: EXCLUSIVE TEMPORARY
+** 52:
+** 53: DEFERRED
+** 54: DEFERRABLE
+** 55:
+** 56: DATABASE
+** 57:
+** 58: DELETE VIEW GENERATED
+** 59: ATTACH
+** 60: END
+** 61: EXCLUDE
+** 62: ESCAPE DESC
+** 63: GLOB
+** 64: WINDOW ELSE
+** 65: COLUMN
+** 66: FIRST
+** 67:
+** 68: GROUPS ALL
+** 69: DISTINCT DROP KEY
+** 70: BETWEEN
+** 71: INITIALLY
+** 72: BEGIN
+** 73: FILTER CHECK ACTION
+** 74: GROUP INDEX
+** 75:
+** 76: EXISTS DEFAULT
+** 77:
+** 78: FOR CURRENT_TIMESTAMP
+** 79: EXCEPT
+** 80:
+** 81: CROSS
+** 82:
+** 83:
+** 84:
+** 85: CAST
+** 86: FOREIGN AUTOINCREMENT
+** 87: COMMIT
+** 88: CURRENT AFTER ALTER
+** 89: FULL FAIL CONFLICT
+** 90: EXPLAIN
+** 91: CONSTRAINT
+** 92: FROM ALWAYS
+** 93:
+** 94: ABORT
+** 95:
+** 96: AS DO
+** 97: REPLACE WITH RELEASE
+** 98: BY RENAME
+** 99: RANGE RAISE
+** 100: OTHERS
+** 101: USING NULLS
+** 102: PRAGMA
+** 103: JOIN ISNULL OFFSET
+** 104: NOT
+** 105: OR LAST LEFT
+** 106: LIMIT
+** 107:
+** 108:
+** 109: IN
+** 110: INTO
+** 111: OVER RECURSIVE
+** 112: ORDER OUTER
+** 113:
+** 114: INTERSECT UNBOUNDED
+** 115:
+** 116:
+** 117: ON
+** 118:
+** 119: WHERE
+** 120: NO INNER
+** 121: NULL
+** 122:
+** 123: TABLE
+** 124: NATURAL NOTNULL
+** 125: PRECEDING
+** 126: UPDATE UNIQUE
+*/
+/* Check to see if z[0..n-1] is a keyword. If it is, write the
+** parser symbol code for that keyword into *pType. Always
+** return the integer n (the length of the token). */
static int keywordCode(const char *z, int n, int *pType){
- /* zText[] encodes 834 bytes of keywords in 554 bytes */
- /* REINDEXEDESCAPEACHECKEYBEFOREIGNOREGEXPLAINSTEADDATABASELECT */
- /* ABLEFTHENDEFERRABLELSEXCEPTRANSACTIONATURALTERAISEXCLUSIVE */
- /* XISTSAVEPOINTERSECTRIGGEREFERENCESCONSTRAINTOFFSETEMPORARY */
- /* UNIQUERYWITHOUTERELEASEATTACHAVINGROUPDATEBEGINNERECURSIVE */
- /* BETWEENOTNULLIKECASCADELETECASECOLLATECREATECURRENT_DATEDETACH */
- /* IMMEDIATEJOINSERTMATCHPLANALYZEPRAGMABORTVALUESVIRTUALIMITWHEN */
- /* WHERENAMEAFTEREPLACEANDEFAULTAUTOINCREMENTCASTCOLUMNCOMMIT */
- /* CONFLICTCROSSCURRENT_TIMESTAMPRIMARYDEFERREDISTINCTDROPFAIL */
- /* FROMFULLGLOBYIFISNULLORDERESTRICTRIGHTROLLBACKROWUNIONUSING */
- /* VACUUMVIEWINITIALLY */
- static const char zText[553] = {
- 'R','E','I','N','D','E','X','E','D','E','S','C','A','P','E','A','C','H',
- 'E','C','K','E','Y','B','E','F','O','R','E','I','G','N','O','R','E','G',
- 'E','X','P','L','A','I','N','S','T','E','A','D','D','A','T','A','B','A',
- 'S','E','L','E','C','T','A','B','L','E','F','T','H','E','N','D','E','F',
- 'E','R','R','A','B','L','E','L','S','E','X','C','E','P','T','R','A','N',
- 'S','A','C','T','I','O','N','A','T','U','R','A','L','T','E','R','A','I',
- 'S','E','X','C','L','U','S','I','V','E','X','I','S','T','S','A','V','E',
- 'P','O','I','N','T','E','R','S','E','C','T','R','I','G','G','E','R','E',
- 'F','E','R','E','N','C','E','S','C','O','N','S','T','R','A','I','N','T',
- 'O','F','F','S','E','T','E','M','P','O','R','A','R','Y','U','N','I','Q',
- 'U','E','R','Y','W','I','T','H','O','U','T','E','R','E','L','E','A','S',
- 'E','A','T','T','A','C','H','A','V','I','N','G','R','O','U','P','D','A',
- 'T','E','B','E','G','I','N','N','E','R','E','C','U','R','S','I','V','E',
- 'B','E','T','W','E','E','N','O','T','N','U','L','L','I','K','E','C','A',
- 'S','C','A','D','E','L','E','T','E','C','A','S','E','C','O','L','L','A',
- 'T','E','C','R','E','A','T','E','C','U','R','R','E','N','T','_','D','A',
- 'T','E','D','E','T','A','C','H','I','M','M','E','D','I','A','T','E','J',
- 'O','I','N','S','E','R','T','M','A','T','C','H','P','L','A','N','A','L',
- 'Y','Z','E','P','R','A','G','M','A','B','O','R','T','V','A','L','U','E',
- 'S','V','I','R','T','U','A','L','I','M','I','T','W','H','E','N','W','H',
- 'E','R','E','N','A','M','E','A','F','T','E','R','E','P','L','A','C','E',
- 'A','N','D','E','F','A','U','L','T','A','U','T','O','I','N','C','R','E',
- 'M','E','N','T','C','A','S','T','C','O','L','U','M','N','C','O','M','M',
- 'I','T','C','O','N','F','L','I','C','T','C','R','O','S','S','C','U','R',
- 'R','E','N','T','_','T','I','M','E','S','T','A','M','P','R','I','M','A',
- 'R','Y','D','E','F','E','R','R','E','D','I','S','T','I','N','C','T','D',
- 'R','O','P','F','A','I','L','F','R','O','M','F','U','L','L','G','L','O',
- 'B','Y','I','F','I','S','N','U','L','L','O','R','D','E','R','E','S','T',
- 'R','I','C','T','R','I','G','H','T','R','O','L','L','B','A','C','K','R',
- 'O','W','U','N','I','O','N','U','S','I','N','G','V','A','C','U','U','M',
- 'V','I','E','W','I','N','I','T','I','A','L','L','Y',
- };
- static const unsigned char aHash[127] = {
- 76, 105, 117, 74, 0, 45, 0, 0, 82, 0, 77, 0, 0,
- 42, 12, 78, 15, 0, 116, 85, 54, 112, 0, 19, 0, 0,
- 121, 0, 119, 115, 0, 22, 93, 0, 9, 0, 0, 70, 71,
- 0, 69, 6, 0, 48, 90, 102, 0, 118, 101, 0, 0, 44,
- 0, 103, 24, 0, 17, 0, 122, 53, 23, 0, 5, 110, 25,
- 96, 0, 0, 124, 106, 60, 123, 57, 28, 55, 0, 91, 0,
- 100, 26, 0, 99, 0, 0, 0, 95, 92, 97, 88, 109, 14,
- 39, 108, 0, 81, 0, 18, 89, 111, 32, 0, 120, 80, 113,
- 62, 46, 84, 0, 0, 94, 40, 59, 114, 0, 36, 0, 0,
- 29, 0, 86, 63, 64, 0, 20, 61, 0, 56,
- };
- static const unsigned char aNext[124] = {
- 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 2, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0,
- 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 33, 0, 21, 0, 0, 0, 0, 0, 50,
- 0, 43, 3, 47, 0, 0, 0, 0, 30, 0, 58, 0, 38,
- 0, 0, 0, 1, 66, 0, 0, 67, 0, 41, 0, 0, 0,
- 0, 0, 0, 49, 65, 0, 0, 0, 0, 31, 52, 16, 34,
- 10, 0, 0, 0, 0, 0, 0, 0, 11, 72, 79, 0, 8,
- 0, 104, 98, 0, 107, 0, 87, 0, 75, 51, 0, 27, 37,
- 73, 83, 0, 35, 68, 0, 0,
- };
- static const unsigned char aLen[124] = {
- 7, 7, 5, 4, 6, 4, 5, 3, 6, 7, 3, 6, 6,
- 7, 7, 3, 8, 2, 6, 5, 4, 4, 3, 10, 4, 6,
- 11, 6, 2, 7, 5, 5, 9, 6, 9, 9, 7, 10, 10,
- 4, 6, 2, 3, 9, 4, 2, 6, 5, 7, 4, 5, 7,
- 6, 6, 5, 6, 5, 5, 9, 7, 7, 3, 2, 4, 4,
- 7, 3, 6, 4, 7, 6, 12, 6, 9, 4, 6, 5, 4,
- 7, 6, 5, 6, 7, 5, 4, 5, 6, 5, 7, 3, 7,
- 13, 2, 2, 4, 6, 6, 8, 5, 17, 12, 7, 8, 8,
- 2, 4, 4, 4, 4, 4, 2, 2, 6, 5, 8, 5, 8,
- 3, 5, 5, 6, 4, 9, 3,
- };
- static const unsigned short int aOffset[124] = {
- 0, 2, 2, 8, 9, 14, 16, 20, 23, 25, 25, 29, 33,
- 36, 41, 46, 48, 53, 54, 59, 62, 65, 67, 69, 78, 81,
- 86, 91, 95, 96, 101, 105, 109, 117, 122, 128, 136, 142, 152,
- 159, 162, 162, 165, 167, 167, 171, 176, 179, 184, 184, 188, 192,
- 199, 204, 209, 212, 218, 221, 225, 234, 240, 240, 240, 243, 246,
- 250, 251, 255, 261, 265, 272, 278, 290, 296, 305, 307, 313, 318,
- 320, 327, 332, 337, 343, 349, 354, 358, 361, 367, 371, 378, 380,
- 387, 389, 391, 400, 404, 410, 416, 424, 429, 429, 445, 452, 459,
- 460, 467, 471, 475, 479, 483, 486, 488, 490, 496, 500, 508, 513,
- 521, 524, 529, 534, 540, 544, 549,
- };
- static const unsigned char aCode[124] = {
- TK_REINDEX, TK_INDEXED, TK_INDEX, TK_DESC, TK_ESCAPE,
- TK_EACH, TK_CHECK, TK_KEY, TK_BEFORE, TK_FOREIGN,
- TK_FOR, TK_IGNORE, TK_LIKE_KW, TK_EXPLAIN, TK_INSTEAD,
- TK_ADD, TK_DATABASE, TK_AS, TK_SELECT, TK_TABLE,
- TK_JOIN_KW, TK_THEN, TK_END, TK_DEFERRABLE, TK_ELSE,
- TK_EXCEPT, TK_TRANSACTION,TK_ACTION, TK_ON, TK_JOIN_KW,
- TK_ALTER, TK_RAISE, TK_EXCLUSIVE, TK_EXISTS, TK_SAVEPOINT,
- TK_INTERSECT, TK_TRIGGER, TK_REFERENCES, TK_CONSTRAINT, TK_INTO,
- TK_OFFSET, TK_OF, TK_SET, TK_TEMP, TK_TEMP,
- TK_OR, TK_UNIQUE, TK_QUERY, TK_WITHOUT, TK_WITH,
- TK_JOIN_KW, TK_RELEASE, TK_ATTACH, TK_HAVING, TK_GROUP,
- TK_UPDATE, TK_BEGIN, TK_JOIN_KW, TK_RECURSIVE, TK_BETWEEN,
- TK_NOTNULL, TK_NOT, TK_NO, TK_NULL, TK_LIKE_KW,
- TK_CASCADE, TK_ASC, TK_DELETE, TK_CASE, TK_COLLATE,
- TK_CREATE, TK_CTIME_KW, TK_DETACH, TK_IMMEDIATE, TK_JOIN,
- TK_INSERT, TK_MATCH, TK_PLAN, TK_ANALYZE, TK_PRAGMA,
- TK_ABORT, TK_VALUES, TK_VIRTUAL, TK_LIMIT, TK_WHEN,
- TK_WHERE, TK_RENAME, TK_AFTER, TK_REPLACE, TK_AND,
- TK_DEFAULT, TK_AUTOINCR, TK_TO, TK_IN, TK_CAST,
- TK_COLUMNKW, TK_COMMIT, TK_CONFLICT, TK_JOIN_KW, TK_CTIME_KW,
- TK_CTIME_KW, TK_PRIMARY, TK_DEFERRED, TK_DISTINCT, TK_IS,
- TK_DROP, TK_FAIL, TK_FROM, TK_JOIN_KW, TK_LIKE_KW,
- TK_BY, TK_IF, TK_ISNULL, TK_ORDER, TK_RESTRICT,
- TK_JOIN_KW, TK_ROLLBACK, TK_ROW, TK_UNION, TK_USING,
- TK_VACUUM, TK_VIEW, TK_INITIALLY, TK_ALL,
- };
int i, j;
const char *zKW;
if( n>=2 ){
i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n) % 127;
- for(i=((int)aHash[i])-1; i>=0; i=((int)aNext[i])-1){
- if( aLen[i]!=n ) continue;
+ for(i=((int)aKWHash[i])-1; i>=0; i=((int)aKWNext[i])-1){
+ if( aKWLen[i]!=n ) continue;
j = 0;
- zKW = &zText[aOffset[i]];
+ zKW = &zKWText[aKWOffset[i]];
#ifdef SQLITE_ASCII
while( j<n && (z[j]&~0x20)==zKW[j] ){ j++; }
#endif
@@ -139999,117 +161441,148 @@ static int keywordCode(const char *z, int n, int *pType){
testcase( i==22 ); /* END */
testcase( i==23 ); /* DEFERRABLE */
testcase( i==24 ); /* ELSE */
- testcase( i==25 ); /* EXCEPT */
- testcase( i==26 ); /* TRANSACTION */
- testcase( i==27 ); /* ACTION */
- testcase( i==28 ); /* ON */
- testcase( i==29 ); /* NATURAL */
- testcase( i==30 ); /* ALTER */
- testcase( i==31 ); /* RAISE */
- testcase( i==32 ); /* EXCLUSIVE */
- testcase( i==33 ); /* EXISTS */
- testcase( i==34 ); /* SAVEPOINT */
- testcase( i==35 ); /* INTERSECT */
- testcase( i==36 ); /* TRIGGER */
- testcase( i==37 ); /* REFERENCES */
- testcase( i==38 ); /* CONSTRAINT */
- testcase( i==39 ); /* INTO */
- testcase( i==40 ); /* OFFSET */
- testcase( i==41 ); /* OF */
- testcase( i==42 ); /* SET */
- testcase( i==43 ); /* TEMPORARY */
- testcase( i==44 ); /* TEMP */
- testcase( i==45 ); /* OR */
- testcase( i==46 ); /* UNIQUE */
- testcase( i==47 ); /* QUERY */
- testcase( i==48 ); /* WITHOUT */
- testcase( i==49 ); /* WITH */
- testcase( i==50 ); /* OUTER */
- testcase( i==51 ); /* RELEASE */
- testcase( i==52 ); /* ATTACH */
- testcase( i==53 ); /* HAVING */
- testcase( i==54 ); /* GROUP */
- testcase( i==55 ); /* UPDATE */
- testcase( i==56 ); /* BEGIN */
- testcase( i==57 ); /* INNER */
- testcase( i==58 ); /* RECURSIVE */
- testcase( i==59 ); /* BETWEEN */
- testcase( i==60 ); /* NOTNULL */
- testcase( i==61 ); /* NOT */
- testcase( i==62 ); /* NO */
- testcase( i==63 ); /* NULL */
- testcase( i==64 ); /* LIKE */
- testcase( i==65 ); /* CASCADE */
- testcase( i==66 ); /* ASC */
- testcase( i==67 ); /* DELETE */
- testcase( i==68 ); /* CASE */
- testcase( i==69 ); /* COLLATE */
- testcase( i==70 ); /* CREATE */
- testcase( i==71 ); /* CURRENT_DATE */
- testcase( i==72 ); /* DETACH */
- testcase( i==73 ); /* IMMEDIATE */
- testcase( i==74 ); /* JOIN */
- testcase( i==75 ); /* INSERT */
- testcase( i==76 ); /* MATCH */
- testcase( i==77 ); /* PLAN */
- testcase( i==78 ); /* ANALYZE */
- testcase( i==79 ); /* PRAGMA */
- testcase( i==80 ); /* ABORT */
- testcase( i==81 ); /* VALUES */
- testcase( i==82 ); /* VIRTUAL */
- testcase( i==83 ); /* LIMIT */
- testcase( i==84 ); /* WHEN */
- testcase( i==85 ); /* WHERE */
- testcase( i==86 ); /* RENAME */
- testcase( i==87 ); /* AFTER */
- testcase( i==88 ); /* REPLACE */
- testcase( i==89 ); /* AND */
- testcase( i==90 ); /* DEFAULT */
- testcase( i==91 ); /* AUTOINCREMENT */
- testcase( i==92 ); /* TO */
- testcase( i==93 ); /* IN */
- testcase( i==94 ); /* CAST */
- testcase( i==95 ); /* COLUMN */
- testcase( i==96 ); /* COMMIT */
- testcase( i==97 ); /* CONFLICT */
- testcase( i==98 ); /* CROSS */
- testcase( i==99 ); /* CURRENT_TIMESTAMP */
- testcase( i==100 ); /* CURRENT_TIME */
- testcase( i==101 ); /* PRIMARY */
- testcase( i==102 ); /* DEFERRED */
- testcase( i==103 ); /* DISTINCT */
- testcase( i==104 ); /* IS */
- testcase( i==105 ); /* DROP */
- testcase( i==106 ); /* FAIL */
- testcase( i==107 ); /* FROM */
- testcase( i==108 ); /* FULL */
- testcase( i==109 ); /* GLOB */
- testcase( i==110 ); /* BY */
- testcase( i==111 ); /* IF */
- testcase( i==112 ); /* ISNULL */
- testcase( i==113 ); /* ORDER */
- testcase( i==114 ); /* RESTRICT */
- testcase( i==115 ); /* RIGHT */
- testcase( i==116 ); /* ROLLBACK */
- testcase( i==117 ); /* ROW */
- testcase( i==118 ); /* UNION */
- testcase( i==119 ); /* USING */
- testcase( i==120 ); /* VACUUM */
- testcase( i==121 ); /* VIEW */
- testcase( i==122 ); /* INITIALLY */
- testcase( i==123 ); /* ALL */
- *pType = aCode[i];
+ testcase( i==25 ); /* EXCLUDE */
+ testcase( i==26 ); /* DELETE */
+ testcase( i==27 ); /* TEMPORARY */
+ testcase( i==28 ); /* TEMP */
+ testcase( i==29 ); /* OR */
+ testcase( i==30 ); /* ISNULL */
+ testcase( i==31 ); /* NULLS */
+ testcase( i==32 ); /* SAVEPOINT */
+ testcase( i==33 ); /* INTERSECT */
+ testcase( i==34 ); /* TIES */
+ testcase( i==35 ); /* NOTNULL */
+ testcase( i==36 ); /* NOT */
+ testcase( i==37 ); /* NO */
+ testcase( i==38 ); /* NULL */
+ testcase( i==39 ); /* LIKE */
+ testcase( i==40 ); /* EXCEPT */
+ testcase( i==41 ); /* TRANSACTION */
+ testcase( i==42 ); /* ACTION */
+ testcase( i==43 ); /* ON */
+ testcase( i==44 ); /* NATURAL */
+ testcase( i==45 ); /* ALTER */
+ testcase( i==46 ); /* RAISE */
+ testcase( i==47 ); /* EXCLUSIVE */
+ testcase( i==48 ); /* EXISTS */
+ testcase( i==49 ); /* CONSTRAINT */
+ testcase( i==50 ); /* INTO */
+ testcase( i==51 ); /* OFFSET */
+ testcase( i==52 ); /* OF */
+ testcase( i==53 ); /* SET */
+ testcase( i==54 ); /* TRIGGER */
+ testcase( i==55 ); /* RANGE */
+ testcase( i==56 ); /* GENERATED */
+ testcase( i==57 ); /* DETACH */
+ testcase( i==58 ); /* HAVING */
+ testcase( i==59 ); /* GLOB */
+ testcase( i==60 ); /* BEGIN */
+ testcase( i==61 ); /* INNER */
+ testcase( i==62 ); /* REFERENCES */
+ testcase( i==63 ); /* UNIQUE */
+ testcase( i==64 ); /* QUERY */
+ testcase( i==65 ); /* WITHOUT */
+ testcase( i==66 ); /* WITH */
+ testcase( i==67 ); /* OUTER */
+ testcase( i==68 ); /* RELEASE */
+ testcase( i==69 ); /* ATTACH */
+ testcase( i==70 ); /* BETWEEN */
+ testcase( i==71 ); /* NOTHING */
+ testcase( i==72 ); /* GROUPS */
+ testcase( i==73 ); /* GROUP */
+ testcase( i==74 ); /* CASCADE */
+ testcase( i==75 ); /* ASC */
+ testcase( i==76 ); /* DEFAULT */
+ testcase( i==77 ); /* CASE */
+ testcase( i==78 ); /* COLLATE */
+ testcase( i==79 ); /* CREATE */
+ testcase( i==80 ); /* CURRENT_DATE */
+ testcase( i==81 ); /* IMMEDIATE */
+ testcase( i==82 ); /* JOIN */
+ testcase( i==83 ); /* INSERT */
+ testcase( i==84 ); /* MATCH */
+ testcase( i==85 ); /* PLAN */
+ testcase( i==86 ); /* ANALYZE */
+ testcase( i==87 ); /* PRAGMA */
+ testcase( i==88 ); /* ABORT */
+ testcase( i==89 ); /* UPDATE */
+ testcase( i==90 ); /* VALUES */
+ testcase( i==91 ); /* VIRTUAL */
+ testcase( i==92 ); /* ALWAYS */
+ testcase( i==93 ); /* WHEN */
+ testcase( i==94 ); /* WHERE */
+ testcase( i==95 ); /* RECURSIVE */
+ testcase( i==96 ); /* AFTER */
+ testcase( i==97 ); /* RENAME */
+ testcase( i==98 ); /* AND */
+ testcase( i==99 ); /* DEFERRED */
+ testcase( i==100 ); /* DISTINCT */
+ testcase( i==101 ); /* IS */
+ testcase( i==102 ); /* AUTOINCREMENT */
+ testcase( i==103 ); /* TO */
+ testcase( i==104 ); /* IN */
+ testcase( i==105 ); /* CAST */
+ testcase( i==106 ); /* COLUMN */
+ testcase( i==107 ); /* COMMIT */
+ testcase( i==108 ); /* CONFLICT */
+ testcase( i==109 ); /* CROSS */
+ testcase( i==110 ); /* CURRENT_TIMESTAMP */
+ testcase( i==111 ); /* CURRENT_TIME */
+ testcase( i==112 ); /* CURRENT */
+ testcase( i==113 ); /* PARTITION */
+ testcase( i==114 ); /* DROP */
+ testcase( i==115 ); /* PRECEDING */
+ testcase( i==116 ); /* FAIL */
+ testcase( i==117 ); /* LAST */
+ testcase( i==118 ); /* FILTER */
+ testcase( i==119 ); /* REPLACE */
+ testcase( i==120 ); /* FIRST */
+ testcase( i==121 ); /* FOLLOWING */
+ testcase( i==122 ); /* FROM */
+ testcase( i==123 ); /* FULL */
+ testcase( i==124 ); /* LIMIT */
+ testcase( i==125 ); /* IF */
+ testcase( i==126 ); /* ORDER */
+ testcase( i==127 ); /* RESTRICT */
+ testcase( i==128 ); /* OTHERS */
+ testcase( i==129 ); /* OVER */
+ testcase( i==130 ); /* RIGHT */
+ testcase( i==131 ); /* ROLLBACK */
+ testcase( i==132 ); /* ROWS */
+ testcase( i==133 ); /* ROW */
+ testcase( i==134 ); /* UNBOUNDED */
+ testcase( i==135 ); /* UNION */
+ testcase( i==136 ); /* USING */
+ testcase( i==137 ); /* VACUUM */
+ testcase( i==138 ); /* VIEW */
+ testcase( i==139 ); /* WINDOW */
+ testcase( i==140 ); /* DO */
+ testcase( i==141 ); /* BY */
+ testcase( i==142 ); /* INITIALLY */
+ testcase( i==143 ); /* ALL */
+ testcase( i==144 ); /* PRIMARY */
+ *pType = aKWCode[i];
break;
}
}
return n;
}
-SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char *z, int n){
+SQLITE_PRIVATE int tdsqlite3KeywordCode(const unsigned char *z, int n){
int id = TK_ID;
keywordCode((char*)z, n, &id);
return id;
}
-#define SQLITE_N_KEYWORD 124
+#define SQLITE_N_KEYWORD 145
+SQLITE_API int tdsqlite3_keyword_name(int i,const char **pzName,int *pnName){
+ if( i<0 || i>=SQLITE_N_KEYWORD ) return SQLITE_ERROR;
+ *pzName = zKWText + aKWOffset[i];
+ *pnName = aKWLen[i];
+ return SQLITE_OK;
+}
+SQLITE_API int tdsqlite3_keyword_count(void){ return SQLITE_N_KEYWORD; }
+SQLITE_API int tdsqlite3_keyword_check(const char *zName, int nName){
+ return TK_ID!=tdsqlite3KeywordCode((const u8*)zName, nName);
+}
/************** End of keywordhash.h *****************************************/
/************** Continuing where we left off in tokenize.c *******************/
@@ -140121,7 +161594,7 @@ SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char *z, int n){
**
** For ASCII, any character with the high-order bit set is
** allowed in an identifier. For 7-bit characters,
-** sqlite3IsIdChar[X] must be 1.
+** tdsqlite3IsIdChar[X] must be 1.
**
** For EBCDIC, the rules are more complex but have the same
** end result.
@@ -140132,10 +161605,10 @@ SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char *z, int n){
** But the feature is undocumented.
*/
#ifdef SQLITE_ASCII
-#define IdChar(C) ((sqlite3CtypeMap[(unsigned char)C]&0x46)!=0)
+#define IdChar(C) ((tdsqlite3CtypeMap[(unsigned char)C]&0x46)!=0)
#endif
#ifdef SQLITE_EBCDIC
-SQLITE_PRIVATE const char sqlite3IsEbcdicIdChar[] = {
+SQLITE_PRIVATE const char tdsqlite3IsEbcdicIdChar[] = {
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 4x */
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, /* 5x */
@@ -140150,20 +161623,94 @@ SQLITE_PRIVATE const char sqlite3IsEbcdicIdChar[] = {
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, /* Ex */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, /* Fx */
};
-#define IdChar(C) (((c=C)>=0x42 && sqlite3IsEbcdicIdChar[c-0x40]))
+#define IdChar(C) (((c=C)>=0x42 && tdsqlite3IsEbcdicIdChar[c-0x40]))
#endif
-/* Make the IdChar function accessible from ctime.c */
-#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
-SQLITE_PRIVATE int sqlite3IsIdChar(u8 c){ return IdChar(c); }
-#endif
+/* Make the IdChar function accessible from ctime.c and alter.c */
+SQLITE_PRIVATE int tdsqlite3IsIdChar(u8 c){ return IdChar(c); }
+#ifndef SQLITE_OMIT_WINDOWFUNC
+/*
+** Return the id of the next token in string (*pz). Before returning, set
+** (*pz) to point to the byte following the parsed token.
+*/
+static int getToken(const unsigned char **pz){
+ const unsigned char *z = *pz;
+ int t; /* Token type to return */
+ do {
+ z += tdsqlite3GetToken(z, &t);
+ }while( t==TK_SPACE );
+ if( t==TK_ID
+ || t==TK_STRING
+ || t==TK_JOIN_KW
+ || t==TK_WINDOW
+ || t==TK_OVER
+ || tdsqlite3ParserFallback(t)==TK_ID
+ ){
+ t = TK_ID;
+ }
+ *pz = z;
+ return t;
+}
+
+/*
+** The following three functions are called immediately after the tokenizer
+** reads the keywords WINDOW, OVER and FILTER, respectively, to determine
+** whether the token should be treated as a keyword or an SQL identifier.
+** This cannot be handled by the usual lemon %fallback method, due to
+** the ambiguity in some constructions. e.g.
+**
+** SELECT sum(x) OVER ...
+**
+** In the above, "OVER" might be a keyword, or it might be an alias for the
+** sum(x) expression. If a "%fallback ID OVER" directive were added to
+** grammar, then SQLite would always treat "OVER" as an alias, making it
+** impossible to call a window-function without a FILTER clause.
+**
+** WINDOW is treated as a keyword if:
+**
+** * the following token is an identifier, or a keyword that can fallback
+** to being an identifier, and
+** * the token after than one is TK_AS.
+**
+** OVER is a keyword if:
+**
+** * the previous token was TK_RP, and
+** * the next token is either TK_LP or an identifier.
+**
+** FILTER is a keyword if:
+**
+** * the previous token was TK_RP, and
+** * the next token is TK_LP.
+*/
+static int analyzeWindowKeyword(const unsigned char *z){
+ int t;
+ t = getToken(&z);
+ if( t!=TK_ID ) return TK_ID;
+ t = getToken(&z);
+ if( t!=TK_AS ) return TK_ID;
+ return TK_WINDOW;
+}
+static int analyzeOverKeyword(const unsigned char *z, int lastToken){
+ if( lastToken==TK_RP ){
+ int t = getToken(&z);
+ if( t==TK_LP || t==TK_ID ) return TK_OVER;
+ }
+ return TK_ID;
+}
+static int analyzeFilterKeyword(const unsigned char *z, int lastToken){
+ if( lastToken==TK_RP && getToken(&z)==TK_LP ){
+ return TK_FILTER;
+ }
+ return TK_ID;
+}
+#endif /* SQLITE_OMIT_WINDOWFUNC */
/*
** Return the length (in bytes) of the token that begins at z[0].
** Store the token type in *tokenType before returning.
*/
-SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
+SQLITE_PRIVATE int tdsqlite3GetToken(const unsigned char *z, int *tokenType){
int i, c;
switch( aiClass[*z] ){ /* Switch on the character-class of the first byte
** of the token. See the comment on the CC_ defines
@@ -140174,7 +161721,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
testcase( z[0]=='\n' );
testcase( z[0]=='\f' );
testcase( z[0]=='\r' );
- for(i=1; sqlite3Isspace(z[i]); i++){}
+ for(i=1; tdsqlite3Isspace(z[i]); i++){}
*tokenType = TK_SPACE;
return i;
}
@@ -140309,7 +161856,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
}
case CC_DOT: {
#ifndef SQLITE_OMIT_FLOATING_POINT
- if( !sqlite3Isdigit(z[1]) )
+ if( !tdsqlite3Isdigit(z[1]) )
#endif
{
*tokenType = TK_DOT;
@@ -140325,25 +161872,25 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
testcase( z[0]=='9' );
*tokenType = TK_INTEGER;
#ifndef SQLITE_OMIT_HEX_INTEGER
- if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){
- for(i=3; sqlite3Isxdigit(z[i]); i++){}
+ if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && tdsqlite3Isxdigit(z[2]) ){
+ for(i=3; tdsqlite3Isxdigit(z[i]); i++){}
return i;
}
#endif
- for(i=0; sqlite3Isdigit(z[i]); i++){}
+ for(i=0; tdsqlite3Isdigit(z[i]); i++){}
#ifndef SQLITE_OMIT_FLOATING_POINT
if( z[i]=='.' ){
i++;
- while( sqlite3Isdigit(z[i]) ){ i++; }
+ while( tdsqlite3Isdigit(z[i]) ){ i++; }
*tokenType = TK_FLOAT;
}
if( (z[i]=='e' || z[i]=='E') &&
- ( sqlite3Isdigit(z[i+1])
- || ((z[i+1]=='+' || z[i+1]=='-') && sqlite3Isdigit(z[i+2]))
+ ( tdsqlite3Isdigit(z[i+1])
+ || ((z[i+1]=='+' || z[i+1]=='-') && tdsqlite3Isdigit(z[i+2]))
)
){
i += 2;
- while( sqlite3Isdigit(z[i]) ){ i++; }
+ while( tdsqlite3Isdigit(z[i]) ){ i++; }
*tokenType = TK_FLOAT;
}
#endif
@@ -140360,7 +161907,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
}
case CC_VARNUM: {
*tokenType = TK_VARIABLE;
- for(i=1; sqlite3Isdigit(z[i]); i++){}
+ for(i=1; tdsqlite3Isdigit(z[i]); i++){}
return i;
}
case CC_DOLLAR:
@@ -140376,7 +161923,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
}else if( c=='(' && n>0 ){
do{
i++;
- }while( (c=z[i])!=0 && !sqlite3Isspace(c) && c!=')' );
+ }while( (c=z[i])!=0 && !tdsqlite3Isspace(c) && c!=')' );
if( c==')' ){
i++;
}else{
@@ -140410,7 +161957,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
testcase( z[0]=='x' ); testcase( z[0]=='X' );
if( z[1]=='\'' ){
*tokenType = TK_BLOB;
- for(i=2; sqlite3Isxdigit(z[i]); i++){}
+ for(i=2; tdsqlite3Isxdigit(z[i]); i++){}
if( z[i]!='\'' || i%2 ){
*tokenType = TK_ILLEGAL;
while( z[i] && z[i]!='\'' ){ i++; }
@@ -140426,6 +161973,10 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
i = 1;
break;
}
+ case CC_NUL: {
+ *tokenType = TK_ILLEGAL;
+ return 0;
+ }
default: {
*tokenType = TK_ILLEGAL;
return 1;
@@ -140440,17 +161991,21 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){
** Run the parser on the given SQL string. The parser structure is
** passed in. An SQLITE_ status code is returned. If an error occurs
** then an and attempt is made to write an error message into
-** memory obtained from sqlite3_malloc() and to make *pzErrMsg point to that
+** memory obtained from tdsqlite3_malloc() and to make *pzErrMsg point to that
** error message.
*/
-SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
+SQLITE_PRIVATE int tdsqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
int nErr = 0; /* Number of errors encountered */
- int i; /* Loop counter */
void *pEngine; /* The LEMON-generated LALR(1) parser */
+ int n = 0; /* Length of the next token token */
int tokenType; /* type of the next token */
int lastTokenParsed = -1; /* type of the previous token */
- sqlite3 *db = pParse->db; /* The database connection */
+ tdsqlite3 *db = pParse->db; /* The database connection */
int mxSqlLen; /* Max length of an SQL string */
+#ifdef tdsqlite3Parser_ENGINEALWAYSONSTACK
+ yyParser sEngine; /* Space to hold the Lemon-generated Parser object */
+#endif
+ VVA_ONLY( u8 startedWithOom = db->mallocFailed );
assert( zSql!=0 );
mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH];
@@ -140459,121 +162014,297 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr
}
pParse->rc = SQLITE_OK;
pParse->zTail = zSql;
- i = 0;
assert( pzErrMsg!=0 );
- /* sqlite3ParserTrace(stdout, "parser: "); */
- pEngine = sqlite3ParserAlloc(sqlite3Malloc);
+#ifdef SQLITE_DEBUG
+ if( db->flags & SQLITE_ParserTrace ){
+ printf("parser: [[[%s]]]\n", zSql);
+ tdsqlite3ParserTrace(stdout, "parser: ");
+ }else{
+ tdsqlite3ParserTrace(0, 0);
+ }
+#endif
+#ifdef tdsqlite3Parser_ENGINEALWAYSONSTACK
+ pEngine = &sEngine;
+ tdsqlite3ParserInit(pEngine, pParse);
+#else
+ pEngine = tdsqlite3ParserAlloc(tdsqlite3Malloc, pParse);
if( pEngine==0 ){
- sqlite3OomFault(db);
+ tdsqlite3OomFault(db);
return SQLITE_NOMEM_BKPT;
}
+#endif
assert( pParse->pNewTable==0 );
assert( pParse->pNewTrigger==0 );
assert( pParse->nVar==0 );
- assert( pParse->nzVar==0 );
- assert( pParse->azVar==0 );
+ assert( pParse->pVList==0 );
+ pParse->pParentParse = db->pParse;
+ db->pParse = pParse;
while( 1 ){
- assert( i>=0 );
- if( zSql[i]!=0 ){
- pParse->sLastToken.z = &zSql[i];
- pParse->sLastToken.n = sqlite3GetToken((u8*)&zSql[i],&tokenType);
- i += pParse->sLastToken.n;
- if( i>mxSqlLen ){
- pParse->rc = SQLITE_TOOBIG;
- break;
- }
- }else{
- /* Upon reaching the end of input, call the parser two more times
- ** with tokens TK_SEMI and 0, in that order. */
- if( lastTokenParsed==TK_SEMI ){
- tokenType = 0;
- }else if( lastTokenParsed==0 ){
- break;
- }else{
- tokenType = TK_SEMI;
- }
+ n = tdsqlite3GetToken((u8*)zSql, &tokenType);
+ mxSqlLen -= n;
+ if( mxSqlLen<0 ){
+ pParse->rc = SQLITE_TOOBIG;
+ break;
}
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ if( tokenType>=TK_WINDOW ){
+ assert( tokenType==TK_SPACE || tokenType==TK_OVER || tokenType==TK_FILTER
+ || tokenType==TK_ILLEGAL || tokenType==TK_WINDOW
+ );
+#else
if( tokenType>=TK_SPACE ){
assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL );
+#endif /* SQLITE_OMIT_WINDOWFUNC */
if( db->u1.isInterrupted ){
pParse->rc = SQLITE_INTERRUPT;
break;
}
- if( tokenType==TK_ILLEGAL ){
- sqlite3ErrorMsg(pParse, "unrecognized token: \"%T\"",
- &pParse->sLastToken);
+ if( tokenType==TK_SPACE ){
+ zSql += n;
+ continue;
+ }
+ if( zSql[0]==0 ){
+ /* Upon reaching the end of input, call the parser two more times
+ ** with tokens TK_SEMI and 0, in that order. */
+ if( lastTokenParsed==TK_SEMI ){
+ tokenType = 0;
+ }else if( lastTokenParsed==0 ){
+ break;
+ }else{
+ tokenType = TK_SEMI;
+ }
+ n = 0;
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ }else if( tokenType==TK_WINDOW ){
+ assert( n==6 );
+ tokenType = analyzeWindowKeyword((const u8*)&zSql[6]);
+ }else if( tokenType==TK_OVER ){
+ assert( n==4 );
+ tokenType = analyzeOverKeyword((const u8*)&zSql[4], lastTokenParsed);
+ }else if( tokenType==TK_FILTER ){
+ assert( n==6 );
+ tokenType = analyzeFilterKeyword((const u8*)&zSql[6], lastTokenParsed);
+#endif /* SQLITE_OMIT_WINDOWFUNC */
+ }else{
+ tdsqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql);
break;
}
- }else{
- sqlite3Parser(pEngine, tokenType, pParse->sLastToken, pParse);
- lastTokenParsed = tokenType;
- if( pParse->rc!=SQLITE_OK || db->mallocFailed ) break;
}
+ pParse->sLastToken.z = zSql;
+ pParse->sLastToken.n = n;
+ tdsqlite3Parser(pEngine, tokenType, pParse->sLastToken);
+ lastTokenParsed = tokenType;
+ zSql += n;
+ assert( db->mallocFailed==0 || pParse->rc!=SQLITE_OK || startedWithOom );
+ if( pParse->rc!=SQLITE_OK ) break;
}
assert( nErr==0 );
- pParse->zTail = &zSql[i];
#ifdef YYTRACKMAXSTACKDEPTH
- sqlite3_mutex_enter(sqlite3MallocMutex());
- sqlite3StatusHighwater(SQLITE_STATUS_PARSER_STACK,
- sqlite3ParserStackPeak(pEngine)
+ tdsqlite3_mutex_enter(tdsqlite3MallocMutex());
+ tdsqlite3StatusHighwater(SQLITE_STATUS_PARSER_STACK,
+ tdsqlite3ParserStackPeak(pEngine)
);
- sqlite3_mutex_leave(sqlite3MallocMutex());
+ tdsqlite3_mutex_leave(tdsqlite3MallocMutex());
#endif /* YYDEBUG */
- sqlite3ParserFree(pEngine, sqlite3_free);
+#ifdef tdsqlite3Parser_ENGINEALWAYSONSTACK
+ tdsqlite3ParserFinalize(pEngine);
+#else
+ tdsqlite3ParserFree(pEngine, tdsqlite3_free);
+#endif
if( db->mallocFailed ){
pParse->rc = SQLITE_NOMEM_BKPT;
}
if( pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE && pParse->zErrMsg==0 ){
- pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc));
+ pParse->zErrMsg = tdsqlite3MPrintf(db, "%s", tdsqlite3ErrStr(pParse->rc));
}
assert( pzErrMsg!=0 );
if( pParse->zErrMsg ){
*pzErrMsg = pParse->zErrMsg;
- sqlite3_log(pParse->rc, "%s", *pzErrMsg);
+ tdsqlite3_log(pParse->rc, "%s in \"%s\"",
+ *pzErrMsg, pParse->zTail);
pParse->zErrMsg = 0;
nErr++;
}
+ pParse->zTail = zSql;
if( pParse->pVdbe && pParse->nErr>0 && pParse->nested==0 ){
- sqlite3VdbeDelete(pParse->pVdbe);
+ tdsqlite3VdbeDelete(pParse->pVdbe);
pParse->pVdbe = 0;
}
#ifndef SQLITE_OMIT_SHARED_CACHE
if( pParse->nested==0 ){
- sqlite3DbFree(db, pParse->aTableLock);
+ tdsqlite3DbFree(db, pParse->aTableLock);
pParse->aTableLock = 0;
pParse->nTableLock = 0;
}
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
- sqlite3_free(pParse->apVtabLock);
+ tdsqlite3_free(pParse->apVtabLock);
#endif
- if( !IN_DECLARE_VTAB ){
+ if( !IN_SPECIAL_PARSE ){
/* If the pParse->declareVtab flag is set, do not delete any table
** structure built up in pParse->pNewTable. The calling code (see vtab.c)
** will take responsibility for freeing the Table structure.
*/
- sqlite3DeleteTable(db, pParse->pNewTable);
+ tdsqlite3DeleteTable(db, pParse->pNewTable);
+ }
+ if( !IN_RENAME_OBJECT ){
+ tdsqlite3DeleteTrigger(db, pParse->pNewTrigger);
}
- if( pParse->pWithToFree ) sqlite3WithDelete(db, pParse->pWithToFree);
- sqlite3DeleteTrigger(db, pParse->pNewTrigger);
- for(i=pParse->nzVar-1; i>=0; i--) sqlite3DbFree(db, pParse->azVar[i]);
- sqlite3DbFree(db, pParse->azVar);
+ if( pParse->pWithToFree ) tdsqlite3WithDelete(db, pParse->pWithToFree);
+ tdsqlite3DbFree(db, pParse->pVList);
while( pParse->pAinc ){
AutoincInfo *p = pParse->pAinc;
pParse->pAinc = p->pNext;
- sqlite3DbFree(db, p);
+ tdsqlite3DbFreeNN(db, p);
}
while( pParse->pZombieTab ){
Table *p = pParse->pZombieTab;
pParse->pZombieTab = p->pNextZombie;
- sqlite3DeleteTable(db, p);
+ tdsqlite3DeleteTable(db, p);
}
+ db->pParse = pParse->pParentParse;
+ pParse->pParentParse = 0;
assert( nErr==0 || pParse->rc!=SQLITE_OK );
return nErr;
}
+
+#ifdef SQLITE_ENABLE_NORMALIZE
+/*
+** Insert a single space character into pStr if the current string
+** ends with an identifier
+*/
+static void addSpaceSeparator(tdsqlite3_str *pStr){
+ if( pStr->nChar && tdsqlite3IsIdChar(pStr->zText[pStr->nChar-1]) ){
+ tdsqlite3_str_append(pStr, " ", 1);
+ }
+}
+
+/*
+** Compute a normalization of the SQL given by zSql[0..nSql-1]. Return
+** the normalization in space obtained from tdsqlite3DbMalloc(). Or return
+** NULL if anything goes wrong or if zSql is NULL.
+*/
+SQLITE_PRIVATE char *tdsqlite3Normalize(
+ Vdbe *pVdbe, /* VM being reprepared */
+ const char *zSql /* The original SQL string */
+){
+ tdsqlite3 *db; /* The database connection */
+ int i; /* Next unread byte of zSql[] */
+ int n; /* length of current token */
+ int tokenType; /* type of current token */
+ int prevType = 0; /* Previous non-whitespace token */
+ int nParen; /* Number of nested levels of parentheses */
+ int iStartIN; /* Start of RHS of IN operator in z[] */
+ int nParenAtIN; /* Value of nParent at start of RHS of IN operator */
+ u32 j; /* Bytes of normalized SQL generated so far */
+ tdsqlite3_str *pStr; /* The normalized SQL string under construction */
+
+ db = tdsqlite3VdbeDb(pVdbe);
+ tokenType = -1;
+ nParen = iStartIN = nParenAtIN = 0;
+ pStr = tdsqlite3_str_new(db);
+ assert( pStr!=0 ); /* tdsqlite3_str_new() never returns NULL */
+ for(i=0; zSql[i] && pStr->accError==0; i+=n){
+ if( tokenType!=TK_SPACE ){
+ prevType = tokenType;
+ }
+ n = tdsqlite3GetToken((unsigned char*)zSql+i, &tokenType);
+ if( NEVER(n<=0) ) break;
+ switch( tokenType ){
+ case TK_SPACE: {
+ break;
+ }
+ case TK_NULL: {
+ if( prevType==TK_IS || prevType==TK_NOT ){
+ tdsqlite3_str_append(pStr, " NULL", 5);
+ break;
+ }
+ /* Fall through */
+ }
+ case TK_STRING:
+ case TK_INTEGER:
+ case TK_FLOAT:
+ case TK_VARIABLE:
+ case TK_BLOB: {
+ tdsqlite3_str_append(pStr, "?", 1);
+ break;
+ }
+ case TK_LP: {
+ nParen++;
+ if( prevType==TK_IN ){
+ iStartIN = pStr->nChar;
+ nParenAtIN = nParen;
+ }
+ tdsqlite3_str_append(pStr, "(", 1);
+ break;
+ }
+ case TK_RP: {
+ if( iStartIN>0 && nParen==nParenAtIN ){
+ assert( pStr->nChar>=(u32)iStartIN );
+ pStr->nChar = iStartIN+1;
+ tdsqlite3_str_append(pStr, "?,?,?", 5);
+ iStartIN = 0;
+ }
+ nParen--;
+ tdsqlite3_str_append(pStr, ")", 1);
+ break;
+ }
+ case TK_ID: {
+ iStartIN = 0;
+ j = pStr->nChar;
+ if( tdsqlite3Isquote(zSql[i]) ){
+ char *zId = tdsqlite3DbStrNDup(db, zSql+i, n);
+ int nId;
+ int eType = 0;
+ if( zId==0 ) break;
+ tdsqlite3Dequote(zId);
+ if( zSql[i]=='"' && tdsqlite3VdbeUsesDoubleQuotedString(pVdbe, zId) ){
+ tdsqlite3_str_append(pStr, "?", 1);
+ tdsqlite3DbFree(db, zId);
+ break;
+ }
+ nId = tdsqlite3Strlen30(zId);
+ if( tdsqlite3GetToken((u8*)zId, &eType)==nId && eType==TK_ID ){
+ addSpaceSeparator(pStr);
+ tdsqlite3_str_append(pStr, zId, nId);
+ }else{
+ tdsqlite3_str_appendf(pStr, "\"%w\"", zId);
+ }
+ tdsqlite3DbFree(db, zId);
+ }else{
+ addSpaceSeparator(pStr);
+ tdsqlite3_str_append(pStr, zSql+i, n);
+ }
+ while( j<pStr->nChar ){
+ pStr->zText[j] = tdsqlite3Tolower(pStr->zText[j]);
+ j++;
+ }
+ break;
+ }
+ case TK_SELECT: {
+ iStartIN = 0;
+ /* fall through */
+ }
+ default: {
+ if( tdsqlite3IsIdChar(zSql[i]) ) addSpaceSeparator(pStr);
+ j = pStr->nChar;
+ tdsqlite3_str_append(pStr, zSql+i, n);
+ while( j<pStr->nChar ){
+ pStr->zText[j] = tdsqlite3Toupper(pStr->zText[j]);
+ j++;
+ }
+ break;
+ }
+ }
+ }
+ if( tokenType!=TK_SEMI ) tdsqlite3_str_append(pStr, ";", 1);
+ return tdsqlite3_str_finish(pStr);
+}
+#endif /* SQLITE_ENABLE_NORMALIZE */
+
/************** End of tokenize.c ********************************************/
/************** Begin file complete.c ****************************************/
/*
@@ -140589,7 +162320,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr
*************************************************************************
** An tokenizer for SQL
**
-** This file contains C code that implements the sqlite3_complete() API.
+** This file contains C code that implements the tdsqlite3_complete() API.
** This code used to be part of the tokenizer.c source file. But by
** separating it out, the code will be automatically omitted from
** static links that do not use it.
@@ -140602,17 +162333,17 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr
*/
#ifndef SQLITE_AMALGAMATION
#ifdef SQLITE_ASCII
-#define IdChar(C) ((sqlite3CtypeMap[(unsigned char)C]&0x46)!=0)
+#define IdChar(C) ((tdsqlite3CtypeMap[(unsigned char)C]&0x46)!=0)
#endif
#ifdef SQLITE_EBCDIC
-SQLITE_PRIVATE const char sqlite3IsEbcdicIdChar[];
-#define IdChar(C) (((c=C)>=0x42 && sqlite3IsEbcdicIdChar[c-0x40]))
+SQLITE_PRIVATE const char tdsqlite3IsEbcdicIdChar[];
+#define IdChar(C) (((c=C)>=0x42 && tdsqlite3IsEbcdicIdChar[c-0x40]))
#endif
#endif /* SQLITE_AMALGAMATION */
/*
-** Token types used by the sqlite3_complete() routine. See the header
+** Token types used by the tdsqlite3_complete() routine. See the header
** comments on that procedure for additional information.
*/
#define tkSEMI 0
@@ -140679,7 +162410,7 @@ SQLITE_PRIVATE const char sqlite3IsEbcdicIdChar[];
** to recognize the end of a trigger can be omitted. All we have to do
** is look for a semicolon that is not part of an string or comment.
*/
-SQLITE_API int sqlite3_complete(const char *zSql){
+SQLITE_API int tdsqlite3_complete(const char *zSql){
u8 state = 0; /* Current state, using numbers defined in header comment */
u8 token; /* Value of the next token */
@@ -140785,7 +162516,7 @@ SQLITE_API int sqlite3_complete(const char *zSql){
#else
switch( *zSql ){
case 'c': case 'C': {
- if( nId==6 && sqlite3StrNICmp(zSql, "create", 6)==0 ){
+ if( nId==6 && tdsqlite3StrNICmp(zSql, "create", 6)==0 ){
token = tkCREATE;
}else{
token = tkOTHER;
@@ -140793,11 +162524,11 @@ SQLITE_API int sqlite3_complete(const char *zSql){
break;
}
case 't': case 'T': {
- if( nId==7 && sqlite3StrNICmp(zSql, "trigger", 7)==0 ){
+ if( nId==7 && tdsqlite3StrNICmp(zSql, "trigger", 7)==0 ){
token = tkTRIGGER;
- }else if( nId==4 && sqlite3StrNICmp(zSql, "temp", 4)==0 ){
+ }else if( nId==4 && tdsqlite3StrNICmp(zSql, "temp", 4)==0 ){
token = tkTEMP;
- }else if( nId==9 && sqlite3StrNICmp(zSql, "temporary", 9)==0 ){
+ }else if( nId==9 && tdsqlite3StrNICmp(zSql, "temporary", 9)==0 ){
token = tkTEMP;
}else{
token = tkOTHER;
@@ -140805,11 +162536,11 @@ SQLITE_API int sqlite3_complete(const char *zSql){
break;
}
case 'e': case 'E': {
- if( nId==3 && sqlite3StrNICmp(zSql, "end", 3)==0 ){
+ if( nId==3 && tdsqlite3StrNICmp(zSql, "end", 3)==0 ){
token = tkEND;
}else
#ifndef SQLITE_OMIT_EXPLAIN
- if( nId==7 && sqlite3StrNICmp(zSql, "explain", 7)==0 ){
+ if( nId==7 && tdsqlite3StrNICmp(zSql, "explain", 7)==0 ){
token = tkEXPLAIN;
}else
#endif
@@ -140840,28 +162571,28 @@ SQLITE_API int sqlite3_complete(const char *zSql){
#ifndef SQLITE_OMIT_UTF16
/*
-** This routine is the same as the sqlite3_complete() routine described
+** This routine is the same as the tdsqlite3_complete() routine described
** above, except that the parameter is required to be UTF-16 encoded, not
** UTF-8.
*/
-SQLITE_API int sqlite3_complete16(const void *zSql){
- sqlite3_value *pVal;
+SQLITE_API int tdsqlite3_complete16(const void *zSql){
+ tdsqlite3_value *pVal;
char const *zSql8;
int rc;
#ifndef SQLITE_OMIT_AUTOINIT
- rc = sqlite3_initialize();
+ rc = tdsqlite3_initialize();
if( rc ) return rc;
#endif
- pVal = sqlite3ValueNew(0);
- sqlite3ValueSetStr(pVal, -1, zSql, SQLITE_UTF16NATIVE, SQLITE_STATIC);
- zSql8 = sqlite3ValueText(pVal, SQLITE_UTF8);
+ pVal = tdsqlite3ValueNew(0);
+ tdsqlite3ValueSetStr(pVal, -1, zSql, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ zSql8 = tdsqlite3ValueText(pVal, SQLITE_UTF8);
if( zSql8 ){
- rc = sqlite3_complete(zSql8);
+ rc = tdsqlite3_complete(zSql8);
}else{
rc = SQLITE_NOMEM_BKPT;
}
- sqlite3ValueFree(pVal);
+ tdsqlite3ValueFree(pVal);
return rc & 0xff;
}
#endif /* SQLITE_OMIT_UTF16 */
@@ -140903,7 +162634,7 @@ SQLITE_API int sqlite3_complete16(const void *zSql){
******************************************************************************
**
** This header file is used by programs that want to link against the
-** FTS3 library. All it does is declare the sqlite3Fts3Init() interface.
+** FTS3 library. All it does is declare the tdsqlite3Fts3Init() interface.
*/
/* #include "sqlite3.h" */
@@ -140911,7 +162642,7 @@ SQLITE_API int sqlite3_complete16(const void *zSql){
extern "C" {
#endif /* __cplusplus */
-SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db);
+SQLITE_PRIVATE int tdsqlite3Fts3Init(tdsqlite3 *db);
#if 0
} /* extern "C" */
@@ -140936,15 +162667,19 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db);
******************************************************************************
**
** This header file is used by programs that want to link against the
-** RTREE library. All it does is declare the sqlite3RtreeInit() interface.
+** RTREE library. All it does is declare the tdsqlite3RtreeInit() interface.
*/
/* #include "sqlite3.h" */
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+# undef SQLITE_ENABLE_RTREE
+#endif
+
#if 0
extern "C" {
#endif /* __cplusplus */
-SQLITE_PRIVATE int sqlite3RtreeInit(sqlite3 *db);
+SQLITE_PRIVATE int tdsqlite3RtreeInit(tdsqlite3 *db);
#if 0
} /* extern "C" */
@@ -140953,7 +162688,7 @@ SQLITE_PRIVATE int sqlite3RtreeInit(sqlite3 *db);
/************** End of rtree.h ***********************************************/
/************** Continuing where we left off in main.c ***********************/
#endif
-#ifdef SQLITE_ENABLE_ICU
+#if defined(SQLITE_ENABLE_ICU) || defined(SQLITE_ENABLE_ICU_COLLATIONS)
/************** Include sqliteicu.h in the middle of main.c ******************/
/************** Begin file sqliteicu.h ***************************************/
/*
@@ -140969,7 +162704,7 @@ SQLITE_PRIVATE int sqlite3RtreeInit(sqlite3 *db);
******************************************************************************
**
** This header file is used by programs that want to link against the
-** ICU extension. All it does is declare the sqlite3IcuInit() interface.
+** ICU extension. All it does is declare the tdsqlite3IcuInit() interface.
*/
/* #include "sqlite3.h" */
@@ -140977,7 +162712,7 @@ SQLITE_PRIVATE int sqlite3RtreeInit(sqlite3 *db);
extern "C" {
#endif /* __cplusplus */
-SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db);
+SQLITE_PRIVATE int tdsqlite3IcuInit(tdsqlite3 *db);
#if 0
} /* extern "C" */
@@ -140988,40 +162723,45 @@ SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db);
/************** Continuing where we left off in main.c ***********************/
#endif
#ifdef SQLITE_ENABLE_JSON1
-SQLITE_PRIVATE int sqlite3Json1Init(sqlite3*);
+SQLITE_PRIVATE int tdsqlite3Json1Init(tdsqlite3*);
+#endif
+#ifdef SQLITE_ENABLE_STMTVTAB
+SQLITE_PRIVATE int tdsqlite3StmtVtabInit(tdsqlite3*);
#endif
#ifdef SQLITE_ENABLE_FTS5
-SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3*);
+SQLITE_PRIVATE int tdsqlite3Fts5Init(tdsqlite3*);
#endif
#ifndef SQLITE_AMALGAMATION
-/* IMPLEMENTATION-OF: R-46656-45156 The sqlite3_version[] string constant
+/* IMPLEMENTATION-OF: R-46656-45156 The tdsqlite3_version[] string constant
** contains the text of SQLITE_VERSION macro.
*/
-SQLITE_API const char sqlite3_version[] = SQLITE_VERSION;
+SQLITE_API const char tdsqlite3_version[] = SQLITE_VERSION;
#endif
-/* IMPLEMENTATION-OF: R-53536-42575 The sqlite3_libversion() function returns
-** a pointer to the to the sqlite3_version[] string constant.
+/* IMPLEMENTATION-OF: R-53536-42575 The tdsqlite3_libversion() function returns
+** a pointer to the to the tdsqlite3_version[] string constant.
*/
-SQLITE_API const char *sqlite3_libversion(void){ return sqlite3_version; }
+SQLITE_API const char *tdsqlite3_libversion(void){ return tdsqlite3_version; }
-/* IMPLEMENTATION-OF: R-63124-39300 The sqlite3_sourceid() function returns a
+/* IMPLEMENTATION-OF: R-25063-23286 The tdsqlite3_sourceid() function returns a
** pointer to a string constant whose value is the same as the
-** SQLITE_SOURCE_ID C preprocessor macro.
+** SQLITE_SOURCE_ID C preprocessor macro. Except if SQLite is built using
+** an edited copy of the amalgamation, then the last four characters of
+** the hash might be different from SQLITE_SOURCE_ID.
*/
-SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
+/* SQLITE_API const char *tdsqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } */
-/* IMPLEMENTATION-OF: R-35210-63508 The sqlite3_libversion_number() function
+/* IMPLEMENTATION-OF: R-35210-63508 The tdsqlite3_libversion_number() function
** returns an integer equal to SQLITE_VERSION_NUMBER.
*/
-SQLITE_API int sqlite3_libversion_number(void){ return SQLITE_VERSION_NUMBER; }
+SQLITE_API int tdsqlite3_libversion_number(void){ return SQLITE_VERSION_NUMBER; }
-/* IMPLEMENTATION-OF: R-20790-14025 The sqlite3_threadsafe() function returns
+/* IMPLEMENTATION-OF: R-20790-14025 The tdsqlite3_threadsafe() function returns
** zero if and only if SQLite was compiled with mutexing code omitted due to
** the SQLITE_THREADSAFE compile-time option being set to 0.
*/
-SQLITE_API int sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; }
+SQLITE_API int tdsqlite3_threadsafe(void){ return SQLITE_THREADSAFE; }
/*
** When compiling the test fixture or with debugging enabled (on Win32),
@@ -141032,7 +162772,7 @@ SQLITE_API int sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; }
# ifndef SQLITE_DEBUG_OS_TRACE
# define SQLITE_DEBUG_OS_TRACE 0
# endif
- int sqlite3OSTrace = SQLITE_DEBUG_OS_TRACE;
+ int tdsqlite3OSTrace = SQLITE_DEBUG_OS_TRACE;
#endif
#if !defined(SQLITE_OMIT_TRACE) && defined(SQLITE_ENABLE_IOTRACE)
@@ -141042,7 +162782,7 @@ SQLITE_API int sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; }
** I/O active are written using this function. These messages
** are intended for debugging activity only.
*/
-SQLITE_API void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...) = 0;
+SQLITE_API void (SQLITE_CDECL *tdsqlite3IoTrace)(const char*, ...) = 0;
#endif
/*
@@ -141052,7 +162792,7 @@ SQLITE_API void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...) = 0;
**
** See also the "PRAGMA temp_store_directory" SQL command.
*/
-SQLITE_API char *sqlite3_temp_directory = 0;
+SQLITE_API char *tdsqlite3_temp_directory = 0;
/*
** If the following global variable points to a string which is the
@@ -141061,7 +162801,7 @@ SQLITE_API char *sqlite3_temp_directory = 0;
**
** See also the "PRAGMA data_store_directory" SQL command.
*/
-SQLITE_API char *sqlite3_data_directory = 0;
+SQLITE_API char *tdsqlite3_data_directory = 0;
/*
** Initialize SQLite.
@@ -141070,10 +162810,10 @@ SQLITE_API char *sqlite3_data_directory = 0;
** VFS, and mutex subsystems prior to doing any serious work with
** SQLite. But as long as you do not compile with SQLITE_OMIT_AUTOINIT
** this routine will be called automatically by key routines such as
-** sqlite3_open().
+** tdsqlite3_open().
**
** This routine is a no-op except on its very first call for the process,
-** or for the first call after a call to sqlite3_shutdown.
+** or for the first call after a call to tdsqlite3_shutdown.
**
** The first thread to call this routine runs the initialization to
** completion. If subsequent threads call this routine before the first
@@ -141094,15 +162834,15 @@ SQLITE_API char *sqlite3_data_directory = 0;
** * Recursive calls to this routine from thread X return immediately
** without blocking.
*/
-SQLITE_API int sqlite3_initialize(void){
- MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */
+SQLITE_API int tdsqlite3_initialize(void){
+ MUTEX_LOGIC( tdsqlite3_mutex *pMaster; ) /* The main static mutex */
int rc; /* Result code */
#ifdef SQLITE_EXTRA_INIT
int bRunExtraInit = 0; /* Extra initialization needed */
#endif
#ifdef SQLITE_OMIT_WSD
- rc = sqlite3_wsd_init(4096, 24);
+ rc = tdsqlite3_wsd_init(4096, 24);
if( rc!=SQLITE_OK ){
return rc;
}
@@ -141114,11 +162854,11 @@ SQLITE_API int sqlite3_initialize(void){
assert( SQLITE_PTRSIZE==sizeof(char*) );
/* If SQLite is already completely initialized, then this call
- ** to sqlite3_initialize() should be a no-op. But the initialization
+ ** to tdsqlite3_initialize() should be a no-op. But the initialization
** must be complete. So isInit must not be set until the very end
** of this routine.
*/
- if( sqlite3GlobalConfig.isInit ) return SQLITE_OK;
+ if( tdsqlite3GlobalConfig.isInit ) return SQLITE_OK;
/* Make sure the mutex subsystem is initialized. If unable to
** initialize the mutex subsystem, return early with the error.
@@ -141128,7 +162868,7 @@ SQLITE_API int sqlite3_initialize(void){
** The mutex subsystem must take care of serializing its own
** initialization.
*/
- rc = sqlite3MutexInit();
+ rc = tdsqlite3MutexInit();
if( rc ) return rc;
/* Initialize the malloc() system and the recursive pInitMutex mutex.
@@ -141137,26 +162877,26 @@ SQLITE_API int sqlite3_initialize(void){
** malloc subsystem - this implies that the allocation of a static
** mutex must not require support from the malloc subsystem.
*/
- MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
- sqlite3_mutex_enter(pMaster);
- sqlite3GlobalConfig.isMutexInit = 1;
- if( !sqlite3GlobalConfig.isMallocInit ){
- rc = sqlite3MallocInit();
+ MUTEX_LOGIC( pMaster = tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); )
+ tdsqlite3_mutex_enter(pMaster);
+ tdsqlite3GlobalConfig.isMutexInit = 1;
+ if( !tdsqlite3GlobalConfig.isMallocInit ){
+ rc = tdsqlite3MallocInit();
}
if( rc==SQLITE_OK ){
- sqlite3GlobalConfig.isMallocInit = 1;
- if( !sqlite3GlobalConfig.pInitMutex ){
- sqlite3GlobalConfig.pInitMutex =
- sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE);
- if( sqlite3GlobalConfig.bCoreMutex && !sqlite3GlobalConfig.pInitMutex ){
+ tdsqlite3GlobalConfig.isMallocInit = 1;
+ if( !tdsqlite3GlobalConfig.pInitMutex ){
+ tdsqlite3GlobalConfig.pInitMutex =
+ tdsqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE);
+ if( tdsqlite3GlobalConfig.bCoreMutex && !tdsqlite3GlobalConfig.pInitMutex ){
rc = SQLITE_NOMEM_BKPT;
}
}
}
if( rc==SQLITE_OK ){
- sqlite3GlobalConfig.nRefInitMutex++;
+ tdsqlite3GlobalConfig.nRefInitMutex++;
}
- sqlite3_mutex_leave(pMaster);
+ tdsqlite3_mutex_leave(pMaster);
/* If rc is not SQLITE_OK at this point, then either the malloc
** subsystem could not be initialized or the system failed to allocate
@@ -141167,58 +162907,63 @@ SQLITE_API int sqlite3_initialize(void){
/* Do the rest of the initialization under the recursive mutex so
** that we will be able to handle recursive calls into
- ** sqlite3_initialize(). The recursive calls normally come through
- ** sqlite3_os_init() when it invokes sqlite3_vfs_register(), but other
+ ** tdsqlite3_initialize(). The recursive calls normally come through
+ ** tdsqlite3_os_init() when it invokes tdsqlite3_vfs_register(), but other
** recursive calls might also be possible.
**
** IMPLEMENTATION-OF: R-00140-37445 SQLite automatically serializes calls
** to the xInit method, so the xInit method need not be threadsafe.
**
** The following mutex is what serializes access to the appdef pcache xInit
- ** methods. The sqlite3_pcache_methods.xInit() all is embedded in the
- ** call to sqlite3PcacheInitialize().
+ ** methods. The tdsqlite3_pcache_methods.xInit() all is embedded in the
+ ** call to tdsqlite3PcacheInitialize().
*/
- sqlite3_mutex_enter(sqlite3GlobalConfig.pInitMutex);
- if( sqlite3GlobalConfig.isInit==0 && sqlite3GlobalConfig.inProgress==0 ){
- sqlite3GlobalConfig.inProgress = 1;
+ tdsqlite3_mutex_enter(tdsqlite3GlobalConfig.pInitMutex);
+ if( tdsqlite3GlobalConfig.isInit==0 && tdsqlite3GlobalConfig.inProgress==0 ){
+ tdsqlite3GlobalConfig.inProgress = 1;
#ifdef SQLITE_ENABLE_SQLLOG
{
- extern void sqlite3_init_sqllog(void);
- sqlite3_init_sqllog();
+ extern void tdsqlite3_init_sqllog(void);
+ tdsqlite3_init_sqllog();
}
#endif
- memset(&sqlite3BuiltinFunctions, 0, sizeof(sqlite3BuiltinFunctions));
- sqlite3RegisterBuiltinFunctions();
- if( sqlite3GlobalConfig.isPCacheInit==0 ){
- rc = sqlite3PcacheInitialize();
+ memset(&tdsqlite3BuiltinFunctions, 0, sizeof(tdsqlite3BuiltinFunctions));
+ tdsqlite3RegisterBuiltinFunctions();
+ if( tdsqlite3GlobalConfig.isPCacheInit==0 ){
+ rc = tdsqlite3PcacheInitialize();
}
if( rc==SQLITE_OK ){
- sqlite3GlobalConfig.isPCacheInit = 1;
- rc = sqlite3OsInit();
+ tdsqlite3GlobalConfig.isPCacheInit = 1;
+ rc = tdsqlite3OsInit();
}
+#ifdef SQLITE_ENABLE_DESERIALIZE
if( rc==SQLITE_OK ){
- sqlite3PCacheBufferSetup( sqlite3GlobalConfig.pPage,
- sqlite3GlobalConfig.szPage, sqlite3GlobalConfig.nPage);
- sqlite3GlobalConfig.isInit = 1;
+ rc = tdsqlite3MemdbInit();
+ }
+#endif
+ if( rc==SQLITE_OK ){
+ tdsqlite3PCacheBufferSetup( tdsqlite3GlobalConfig.pPage,
+ tdsqlite3GlobalConfig.szPage, tdsqlite3GlobalConfig.nPage);
+ tdsqlite3GlobalConfig.isInit = 1;
#ifdef SQLITE_EXTRA_INIT
bRunExtraInit = 1;
#endif
}
- sqlite3GlobalConfig.inProgress = 0;
+ tdsqlite3GlobalConfig.inProgress = 0;
}
- sqlite3_mutex_leave(sqlite3GlobalConfig.pInitMutex);
+ tdsqlite3_mutex_leave(tdsqlite3GlobalConfig.pInitMutex);
/* Go back under the static mutex and clean up the recursive
** mutex to prevent a resource leak.
*/
- sqlite3_mutex_enter(pMaster);
- sqlite3GlobalConfig.nRefInitMutex--;
- if( sqlite3GlobalConfig.nRefInitMutex<=0 ){
- assert( sqlite3GlobalConfig.nRefInitMutex==0 );
- sqlite3_mutex_free(sqlite3GlobalConfig.pInitMutex);
- sqlite3GlobalConfig.pInitMutex = 0;
+ tdsqlite3_mutex_enter(pMaster);
+ tdsqlite3GlobalConfig.nRefInitMutex--;
+ if( tdsqlite3GlobalConfig.nRefInitMutex<=0 ){
+ assert( tdsqlite3GlobalConfig.nRefInitMutex==0 );
+ tdsqlite3_mutex_free(tdsqlite3GlobalConfig.pInitMutex);
+ tdsqlite3GlobalConfig.pInitMutex = 0;
}
- sqlite3_mutex_leave(pMaster);
+ tdsqlite3_mutex_leave(pMaster);
/* The following is just a sanity check to make sure SQLite has
** been compiled correctly. It is important to run this code, but
@@ -141228,13 +162973,13 @@ SQLITE_API int sqlite3_initialize(void){
#ifndef NDEBUG
#ifndef SQLITE_OMIT_FLOATING_POINT
/* This section of code's only "output" is via assert() statements. */
- if ( rc==SQLITE_OK ){
+ if( rc==SQLITE_OK ){
u64 x = (((u64)1)<<63)-1;
double y;
assert(sizeof(x)==8);
assert(sizeof(x)==sizeof(y));
memcpy(&y, &x, 8);
- assert( sqlite3IsNaN(y) );
+ assert( tdsqlite3IsNaN(y) );
}
#endif
#endif
@@ -141253,53 +162998,53 @@ SQLITE_API int sqlite3_initialize(void){
}
/*
-** Undo the effects of sqlite3_initialize(). Must not be called while
+** Undo the effects of tdsqlite3_initialize(). Must not be called while
** there are outstanding database connections or memory allocations or
** while any part of SQLite is otherwise in use in any thread. This
** routine is not threadsafe. But it is safe to invoke this routine
** on when SQLite is already shut down. If SQLite is already shut down
** when this routine is invoked, then this routine is a harmless no-op.
*/
-SQLITE_API int sqlite3_shutdown(void){
+SQLITE_API int tdsqlite3_shutdown(void){
#ifdef SQLITE_OMIT_WSD
- int rc = sqlite3_wsd_init(4096, 24);
+ int rc = tdsqlite3_wsd_init(4096, 24);
if( rc!=SQLITE_OK ){
return rc;
}
#endif
- if( sqlite3GlobalConfig.isInit ){
+ if( tdsqlite3GlobalConfig.isInit ){
#ifdef SQLITE_EXTRA_SHUTDOWN
void SQLITE_EXTRA_SHUTDOWN(void);
SQLITE_EXTRA_SHUTDOWN();
#endif
- sqlite3_os_end();
- sqlite3_reset_auto_extension();
- sqlite3GlobalConfig.isInit = 0;
+ tdsqlite3_os_end();
+ tdsqlite3_reset_auto_extension();
+ tdsqlite3GlobalConfig.isInit = 0;
}
- if( sqlite3GlobalConfig.isPCacheInit ){
- sqlite3PcacheShutdown();
- sqlite3GlobalConfig.isPCacheInit = 0;
+ if( tdsqlite3GlobalConfig.isPCacheInit ){
+ tdsqlite3PcacheShutdown();
+ tdsqlite3GlobalConfig.isPCacheInit = 0;
}
- if( sqlite3GlobalConfig.isMallocInit ){
- sqlite3MallocEnd();
- sqlite3GlobalConfig.isMallocInit = 0;
+ if( tdsqlite3GlobalConfig.isMallocInit ){
+ tdsqlite3MallocEnd();
+ tdsqlite3GlobalConfig.isMallocInit = 0;
#ifndef SQLITE_OMIT_SHUTDOWN_DIRECTORIES
/* The heap subsystem has now been shutdown and these values are supposed
- ** to be NULL or point to memory that was obtained from sqlite3_malloc(),
+ ** to be NULL or point to memory that was obtained from tdsqlite3_malloc(),
** which would rely on that heap subsystem; therefore, make sure these
** values cannot refer to heap memory that was just invalidated when the
** heap subsystem was shutdown. This is only done if the current call to
** this function resulted in the heap subsystem actually being shutdown.
*/
- sqlite3_data_directory = 0;
- sqlite3_temp_directory = 0;
+ tdsqlite3_data_directory = 0;
+ tdsqlite3_temp_directory = 0;
#endif
}
- if( sqlite3GlobalConfig.isMutexInit ){
- sqlite3MutexEnd();
- sqlite3GlobalConfig.isMutexInit = 0;
+ if( tdsqlite3GlobalConfig.isMutexInit ){
+ tdsqlite3MutexEnd();
+ tdsqlite3GlobalConfig.isMutexInit = 0;
}
return SQLITE_OK;
@@ -141314,13 +163059,13 @@ SQLITE_API int sqlite3_shutdown(void){
** threadsafe. Failure to heed these warnings can lead to unpredictable
** behavior.
*/
-SQLITE_API int sqlite3_config(int op, ...){
+SQLITE_API int tdsqlite3_config(int op, ...){
va_list ap;
int rc = SQLITE_OK;
- /* sqlite3_config() shall return SQLITE_MISUSE if it is invoked while
+ /* tdsqlite3_config() shall return SQLITE_MISUSE if it is invoked while
** the SQLite library is in use. */
- if( sqlite3GlobalConfig.isInit ) return SQLITE_MISUSE_BKPT;
+ if( tdsqlite3GlobalConfig.isInit ) return SQLITE_MISUSE_BKPT;
va_start(ap, op);
switch( op ){
@@ -141332,8 +163077,8 @@ SQLITE_API int sqlite3_config(int op, ...){
case SQLITE_CONFIG_SINGLETHREAD: {
/* EVIDENCE-OF: R-02748-19096 This option sets the threading mode to
** Single-thread. */
- sqlite3GlobalConfig.bCoreMutex = 0; /* Disable mutex on core */
- sqlite3GlobalConfig.bFullMutex = 0; /* Disable mutex on connections */
+ tdsqlite3GlobalConfig.bCoreMutex = 0; /* Disable mutex on core */
+ tdsqlite3GlobalConfig.bFullMutex = 0; /* Disable mutex on connections */
break;
}
#endif
@@ -141341,8 +163086,8 @@ SQLITE_API int sqlite3_config(int op, ...){
case SQLITE_CONFIG_MULTITHREAD: {
/* EVIDENCE-OF: R-14374-42468 This option sets the threading mode to
** Multi-thread. */
- sqlite3GlobalConfig.bCoreMutex = 1; /* Enable mutex on core */
- sqlite3GlobalConfig.bFullMutex = 0; /* Disable mutex on connections */
+ tdsqlite3GlobalConfig.bCoreMutex = 1; /* Enable mutex on core */
+ tdsqlite3GlobalConfig.bFullMutex = 0; /* Disable mutex on connections */
break;
}
#endif
@@ -141350,22 +163095,22 @@ SQLITE_API int sqlite3_config(int op, ...){
case SQLITE_CONFIG_SERIALIZED: {
/* EVIDENCE-OF: R-41220-51800 This option sets the threading mode to
** Serialized. */
- sqlite3GlobalConfig.bCoreMutex = 1; /* Enable mutex on core */
- sqlite3GlobalConfig.bFullMutex = 1; /* Enable mutex on connections */
+ tdsqlite3GlobalConfig.bCoreMutex = 1; /* Enable mutex on core */
+ tdsqlite3GlobalConfig.bFullMutex = 1; /* Enable mutex on connections */
break;
}
#endif
#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-63666-48755 */
case SQLITE_CONFIG_MUTEX: {
/* Specify an alternative mutex implementation */
- sqlite3GlobalConfig.mutex = *va_arg(ap, sqlite3_mutex_methods*);
+ tdsqlite3GlobalConfig.mutex = *va_arg(ap, tdsqlite3_mutex_methods*);
break;
}
#endif
#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-14450-37597 */
case SQLITE_CONFIG_GETMUTEX: {
/* Retrieve the current mutex implementation */
- *va_arg(ap, sqlite3_mutex_methods*) = sqlite3GlobalConfig.mutex;
+ *va_arg(ap, tdsqlite3_mutex_methods*) = tdsqlite3GlobalConfig.mutex;
break;
}
#endif
@@ -141373,36 +163118,30 @@ SQLITE_API int sqlite3_config(int op, ...){
case SQLITE_CONFIG_MALLOC: {
/* EVIDENCE-OF: R-55594-21030 The SQLITE_CONFIG_MALLOC option takes a
** single argument which is a pointer to an instance of the
- ** sqlite3_mem_methods structure. The argument specifies alternative
+ ** tdsqlite3_mem_methods structure. The argument specifies alternative
** low-level memory allocation routines to be used in place of the memory
** allocation routines built into SQLite. */
- sqlite3GlobalConfig.m = *va_arg(ap, sqlite3_mem_methods*);
+ tdsqlite3GlobalConfig.m = *va_arg(ap, tdsqlite3_mem_methods*);
break;
}
case SQLITE_CONFIG_GETMALLOC: {
/* EVIDENCE-OF: R-51213-46414 The SQLITE_CONFIG_GETMALLOC option takes a
** single argument which is a pointer to an instance of the
- ** sqlite3_mem_methods structure. The sqlite3_mem_methods structure is
+ ** tdsqlite3_mem_methods structure. The tdsqlite3_mem_methods structure is
** filled with the currently defined memory allocation routines. */
- if( sqlite3GlobalConfig.m.xMalloc==0 ) sqlite3MemSetDefault();
- *va_arg(ap, sqlite3_mem_methods*) = sqlite3GlobalConfig.m;
+ if( tdsqlite3GlobalConfig.m.xMalloc==0 ) tdsqlite3MemSetDefault();
+ *va_arg(ap, tdsqlite3_mem_methods*) = tdsqlite3GlobalConfig.m;
break;
}
case SQLITE_CONFIG_MEMSTATUS: {
/* EVIDENCE-OF: R-61275-35157 The SQLITE_CONFIG_MEMSTATUS option takes
** single argument of type int, interpreted as a boolean, which enables
** or disables the collection of memory allocation statistics. */
- sqlite3GlobalConfig.bMemstat = va_arg(ap, int);
+ tdsqlite3GlobalConfig.bMemstat = va_arg(ap, int);
break;
}
- case SQLITE_CONFIG_SCRATCH: {
- /* EVIDENCE-OF: R-08404-60887 There are three arguments to
- ** SQLITE_CONFIG_SCRATCH: A pointer an 8-byte aligned memory buffer from
- ** which the scratch allocations will be drawn, the size of each scratch
- ** allocation (sz), and the maximum number of scratch allocations (N). */
- sqlite3GlobalConfig.pScratch = va_arg(ap, void*);
- sqlite3GlobalConfig.szScratch = va_arg(ap, int);
- sqlite3GlobalConfig.nScratch = va_arg(ap, int);
+ case SQLITE_CONFIG_SMALL_MALLOC: {
+ tdsqlite3GlobalConfig.bSmallMalloc = va_arg(ap, int);
break;
}
case SQLITE_CONFIG_PAGECACHE: {
@@ -141410,9 +163149,9 @@ SQLITE_API int sqlite3_config(int op, ...){
** SQLITE_CONFIG_PAGECACHE: A pointer to 8-byte aligned memory (pMem),
** the size of each page cache line (sz), and the number of cache lines
** (N). */
- sqlite3GlobalConfig.pPage = va_arg(ap, void*);
- sqlite3GlobalConfig.szPage = va_arg(ap, int);
- sqlite3GlobalConfig.nPage = va_arg(ap, int);
+ tdsqlite3GlobalConfig.pPage = va_arg(ap, void*);
+ tdsqlite3GlobalConfig.szPage = va_arg(ap, int);
+ tdsqlite3GlobalConfig.nPage = va_arg(ap, int);
break;
}
case SQLITE_CONFIG_PCACHE_HDRSZ: {
@@ -141421,9 +163160,9 @@ SQLITE_API int sqlite3_config(int op, ...){
** that integer the number of extra bytes per page required for each page
** in SQLITE_CONFIG_PAGECACHE. */
*va_arg(ap, int*) =
- sqlite3HeaderSizeBtree() +
- sqlite3HeaderSizePcache() +
- sqlite3HeaderSizePcache1();
+ tdsqlite3HeaderSizeBtree() +
+ tdsqlite3HeaderSizePcache() +
+ tdsqlite3HeaderSizePcache1();
break;
}
@@ -141439,21 +163178,21 @@ SQLITE_API int sqlite3_config(int op, ...){
case SQLITE_CONFIG_PCACHE2: {
/* EVIDENCE-OF: R-63325-48378 The SQLITE_CONFIG_PCACHE2 option takes a
- ** single argument which is a pointer to an sqlite3_pcache_methods2
+ ** single argument which is a pointer to an tdsqlite3_pcache_methods2
** object. This object specifies the interface to a custom page cache
** implementation. */
- sqlite3GlobalConfig.pcache2 = *va_arg(ap, sqlite3_pcache_methods2*);
+ tdsqlite3GlobalConfig.pcache2 = *va_arg(ap, tdsqlite3_pcache_methods2*);
break;
}
case SQLITE_CONFIG_GETPCACHE2: {
/* EVIDENCE-OF: R-22035-46182 The SQLITE_CONFIG_GETPCACHE2 option takes a
- ** single argument which is a pointer to an sqlite3_pcache_methods2
+ ** single argument which is a pointer to an tdsqlite3_pcache_methods2
** object. SQLite copies of the current page cache implementation into
** that object. */
- if( sqlite3GlobalConfig.pcache2.xInit==0 ){
- sqlite3PCacheSetDefault();
+ if( tdsqlite3GlobalConfig.pcache2.xInit==0 ){
+ tdsqlite3PCacheSetDefault();
}
- *va_arg(ap, sqlite3_pcache_methods2*) = sqlite3GlobalConfig.pcache2;
+ *va_arg(ap, tdsqlite3_pcache_methods2*) = tdsqlite3GlobalConfig.pcache2;
break;
}
@@ -141466,36 +163205,36 @@ SQLITE_API int sqlite3_config(int op, ...){
** SQLITE_CONFIG_HEAP: An 8-byte aligned pointer to the memory, the
** number of bytes in the memory buffer, and the minimum allocation size.
*/
- sqlite3GlobalConfig.pHeap = va_arg(ap, void*);
- sqlite3GlobalConfig.nHeap = va_arg(ap, int);
- sqlite3GlobalConfig.mnReq = va_arg(ap, int);
+ tdsqlite3GlobalConfig.pHeap = va_arg(ap, void*);
+ tdsqlite3GlobalConfig.nHeap = va_arg(ap, int);
+ tdsqlite3GlobalConfig.mnReq = va_arg(ap, int);
- if( sqlite3GlobalConfig.mnReq<1 ){
- sqlite3GlobalConfig.mnReq = 1;
- }else if( sqlite3GlobalConfig.mnReq>(1<<12) ){
+ if( tdsqlite3GlobalConfig.mnReq<1 ){
+ tdsqlite3GlobalConfig.mnReq = 1;
+ }else if( tdsqlite3GlobalConfig.mnReq>(1<<12) ){
/* cap min request size at 2^12 */
- sqlite3GlobalConfig.mnReq = (1<<12);
+ tdsqlite3GlobalConfig.mnReq = (1<<12);
}
- if( sqlite3GlobalConfig.pHeap==0 ){
+ if( tdsqlite3GlobalConfig.pHeap==0 ){
/* EVIDENCE-OF: R-49920-60189 If the first pointer (the memory pointer)
** is NULL, then SQLite reverts to using its default memory allocator
** (the system malloc() implementation), undoing any prior invocation of
** SQLITE_CONFIG_MALLOC.
**
- ** Setting sqlite3GlobalConfig.m to all zeros will cause malloc to
- ** revert to its default implementation when sqlite3_initialize() is run
+ ** Setting tdsqlite3GlobalConfig.m to all zeros will cause malloc to
+ ** revert to its default implementation when tdsqlite3_initialize() is run
*/
- memset(&sqlite3GlobalConfig.m, 0, sizeof(sqlite3GlobalConfig.m));
+ memset(&tdsqlite3GlobalConfig.m, 0, sizeof(tdsqlite3GlobalConfig.m));
}else{
/* EVIDENCE-OF: R-61006-08918 If the memory pointer is not NULL then the
** alternative memory allocator is engaged to handle all of SQLites
** memory allocation needs. */
#ifdef SQLITE_ENABLE_MEMSYS3
- sqlite3GlobalConfig.m = *sqlite3MemGetMemsys3();
+ tdsqlite3GlobalConfig.m = *tdsqlite3MemGetMemsys3();
#endif
#ifdef SQLITE_ENABLE_MEMSYS5
- sqlite3GlobalConfig.m = *sqlite3MemGetMemsys5();
+ tdsqlite3GlobalConfig.m = *tdsqlite3MemGetMemsys5();
#endif
}
break;
@@ -141503,8 +163242,8 @@ SQLITE_API int sqlite3_config(int op, ...){
#endif
case SQLITE_CONFIG_LOOKASIDE: {
- sqlite3GlobalConfig.szLookaside = va_arg(ap, int);
- sqlite3GlobalConfig.nLookaside = va_arg(ap, int);
+ tdsqlite3GlobalConfig.szLookaside = va_arg(ap, int);
+ tdsqlite3GlobalConfig.nLookaside = va_arg(ap, int);
break;
}
@@ -141515,25 +163254,25 @@ SQLITE_API int sqlite3_config(int op, ...){
case SQLITE_CONFIG_LOG: {
/* MSVC is picky about pulling func ptrs from va lists.
** http://support.microsoft.com/kb/47961
- ** sqlite3GlobalConfig.xLog = va_arg(ap, void(*)(void*,int,const char*));
+ ** tdsqlite3GlobalConfig.xLog = va_arg(ap, void(*)(void*,int,const char*));
*/
typedef void(*LOGFUNC_t)(void*,int,const char*);
- sqlite3GlobalConfig.xLog = va_arg(ap, LOGFUNC_t);
- sqlite3GlobalConfig.pLogArg = va_arg(ap, void*);
+ tdsqlite3GlobalConfig.xLog = va_arg(ap, LOGFUNC_t);
+ tdsqlite3GlobalConfig.pLogArg = va_arg(ap, void*);
break;
}
/* EVIDENCE-OF: R-55548-33817 The compile-time setting for URI filenames
** can be changed at start-time using the
- ** sqlite3_config(SQLITE_CONFIG_URI,1) or
- ** sqlite3_config(SQLITE_CONFIG_URI,0) configuration calls.
+ ** tdsqlite3_config(SQLITE_CONFIG_URI,1) or
+ ** tdsqlite3_config(SQLITE_CONFIG_URI,0) configuration calls.
*/
case SQLITE_CONFIG_URI: {
/* EVIDENCE-OF: R-25451-61125 The SQLITE_CONFIG_URI option takes a single
** argument of type int. If non-zero, then URI handling is globally
** enabled. If the parameter is zero, then URI handling is globally
** disabled. */
- sqlite3GlobalConfig.bOpenUri = va_arg(ap, int);
+ tdsqlite3GlobalConfig.bOpenUri = va_arg(ap, int);
break;
}
@@ -141542,26 +163281,26 @@ SQLITE_API int sqlite3_config(int op, ...){
** option takes a single integer argument which is interpreted as a
** boolean in order to enable or disable the use of covering indices for
** full table scans in the query optimizer. */
- sqlite3GlobalConfig.bUseCis = va_arg(ap, int);
+ tdsqlite3GlobalConfig.bUseCis = va_arg(ap, int);
break;
}
#ifdef SQLITE_ENABLE_SQLLOG
case SQLITE_CONFIG_SQLLOG: {
- typedef void(*SQLLOGFUNC_t)(void*, sqlite3*, const char*, int);
- sqlite3GlobalConfig.xSqllog = va_arg(ap, SQLLOGFUNC_t);
- sqlite3GlobalConfig.pSqllogArg = va_arg(ap, void *);
+ typedef void(*SQLLOGFUNC_t)(void*, tdsqlite3*, const char*, int);
+ tdsqlite3GlobalConfig.xSqllog = va_arg(ap, SQLLOGFUNC_t);
+ tdsqlite3GlobalConfig.pSqllogArg = va_arg(ap, void *);
break;
}
#endif
case SQLITE_CONFIG_MMAP_SIZE: {
/* EVIDENCE-OF: R-58063-38258 SQLITE_CONFIG_MMAP_SIZE takes two 64-bit
- ** integer (sqlite3_int64) values that are the default mmap size limit
+ ** integer (tdsqlite3_int64) values that are the default mmap size limit
** (the default setting for PRAGMA mmap_size) and the maximum allowed
** mmap size limit. */
- sqlite3_int64 szMmap = va_arg(ap, sqlite3_int64);
- sqlite3_int64 mxMmap = va_arg(ap, sqlite3_int64);
+ tdsqlite3_int64 szMmap = va_arg(ap, tdsqlite3_int64);
+ tdsqlite3_int64 mxMmap = va_arg(ap, tdsqlite3_int64);
/* EVIDENCE-OF: R-53367-43190 If either argument to this option is
** negative, then that argument is changed to its compile-time default.
**
@@ -141575,8 +163314,8 @@ SQLITE_API int sqlite3_config(int op, ...){
}
if( szMmap<0 ) szMmap = SQLITE_DEFAULT_MMAP_SIZE;
if( szMmap>mxMmap) szMmap = mxMmap;
- sqlite3GlobalConfig.mxMmap = mxMmap;
- sqlite3GlobalConfig.szMmap = szMmap;
+ tdsqlite3GlobalConfig.mxMmap = mxMmap;
+ tdsqlite3GlobalConfig.szMmap = szMmap;
break;
}
@@ -141585,21 +163324,39 @@ SQLITE_API int sqlite3_config(int op, ...){
/* EVIDENCE-OF: R-34926-03360 SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit
** unsigned integer value that specifies the maximum size of the created
** heap. */
- sqlite3GlobalConfig.nHeap = va_arg(ap, int);
+ tdsqlite3GlobalConfig.nHeap = va_arg(ap, int);
break;
}
#endif
case SQLITE_CONFIG_PMASZ: {
- sqlite3GlobalConfig.szPma = va_arg(ap, unsigned int);
+ tdsqlite3GlobalConfig.szPma = va_arg(ap, unsigned int);
break;
}
case SQLITE_CONFIG_STMTJRNL_SPILL: {
- sqlite3GlobalConfig.nStmtSpill = va_arg(ap, int);
+ tdsqlite3GlobalConfig.nStmtSpill = va_arg(ap, int);
break;
}
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ case SQLITE_CONFIG_SORTERREF_SIZE: {
+ int iVal = va_arg(ap, int);
+ if( iVal<0 ){
+ iVal = SQLITE_DEFAULT_SORTERREF_SIZE;
+ }
+ tdsqlite3GlobalConfig.szSorterRef = (u32)iVal;
+ break;
+ }
+#endif /* SQLITE_ENABLE_SORTER_REFERENCES */
+
+#ifdef SQLITE_ENABLE_DESERIALIZE
+ case SQLITE_CONFIG_MEMDB_MAXSIZE: {
+ tdsqlite3GlobalConfig.mxMemdbSize = va_arg(ap, tdsqlite3_int64);
+ break;
+ }
+#endif /* SQLITE_ENABLE_DESERIALIZE */
+
default: {
rc = SQLITE_ERROR;
break;
@@ -141616,14 +163373,18 @@ SQLITE_API int sqlite3_config(int op, ...){
**
** The sz parameter is the number of bytes in each lookaside slot.
** The cnt parameter is the number of slots. If pStart is NULL the
-** space for the lookaside memory is obtained from sqlite3_malloc().
+** space for the lookaside memory is obtained from tdsqlite3_malloc().
** If pStart is not NULL then it is sz*cnt bytes of memory to use for
** the lookaside memory.
*/
-static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
+static int setupLookaside(tdsqlite3 *db, void *pBuf, int sz, int cnt){
#ifndef SQLITE_OMIT_LOOKASIDE
void *pStart;
- if( db->lookaside.nOut ){
+ tdsqlite3_int64 szAlloc = sz*(tdsqlite3_int64)cnt;
+ int nBig; /* Number of full-size slots */
+ int nSm; /* Number smaller LOOKASIDE_SMALL-byte slots */
+
+ if( tdsqlite3LookasideUsed(db,0)>0 ){
return SQLITE_BUSY;
}
/* Free any existing lookaside buffer for this handle before
@@ -141631,7 +163392,7 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
** both at the same time.
*/
if( db->lookaside.bMalloced ){
- sqlite3_free(db->lookaside.pStart);
+ tdsqlite3_free(db->lookaside.pStart);
}
/* The size of a lookaside slot after ROUNDDOWN8 needs to be larger
** than a pointer to be useful.
@@ -141643,35 +163404,72 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
sz = 0;
pStart = 0;
}else if( pBuf==0 ){
- sqlite3BeginBenignMalloc();
- pStart = sqlite3Malloc( sz*cnt ); /* IMP: R-61949-35727 */
- sqlite3EndBenignMalloc();
- if( pStart ) cnt = sqlite3MallocSize(pStart)/sz;
+ tdsqlite3BeginBenignMalloc();
+ pStart = tdsqlite3Malloc( szAlloc ); /* IMP: R-61949-35727 */
+ tdsqlite3EndBenignMalloc();
+ if( pStart ) szAlloc = tdsqlite3MallocSize(pStart);
}else{
pStart = pBuf;
}
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ if( sz>=LOOKASIDE_SMALL*3 ){
+ nBig = szAlloc/(3*LOOKASIDE_SMALL+sz);
+ nSm = (szAlloc - sz*nBig)/LOOKASIDE_SMALL;
+ }else if( sz>=LOOKASIDE_SMALL*2 ){
+ nBig = szAlloc/(LOOKASIDE_SMALL+sz);
+ nSm = (szAlloc - sz*nBig)/LOOKASIDE_SMALL;
+ }else
+#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
+ if( sz>0 ){
+ nBig = szAlloc/sz;
+ nSm = 0;
+ }else{
+ nBig = nSm = 0;
+ }
db->lookaside.pStart = pStart;
+ db->lookaside.pInit = 0;
db->lookaside.pFree = 0;
db->lookaside.sz = (u16)sz;
+ db->lookaside.szTrue = (u16)sz;
if( pStart ){
int i;
LookasideSlot *p;
assert( sz > (int)sizeof(LookasideSlot*) );
p = (LookasideSlot*)pStart;
- for(i=cnt-1; i>=0; i--){
- p->pNext = db->lookaside.pFree;
- db->lookaside.pFree = p;
+ for(i=0; i<nBig; i++){
+ p->pNext = db->lookaside.pInit;
+ db->lookaside.pInit = p;
p = (LookasideSlot*)&((u8*)p)[sz];
}
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ db->lookaside.pSmallInit = 0;
+ db->lookaside.pSmallFree = 0;
+ db->lookaside.pMiddle = p;
+ for(i=0; i<nSm; i++){
+ p->pNext = db->lookaside.pSmallInit;
+ db->lookaside.pSmallInit = p;
+ p = (LookasideSlot*)&((u8*)p)[LOOKASIDE_SMALL];
+ }
+#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
+ assert( ((uptr)p)<=szAlloc + (uptr)pStart );
db->lookaside.pEnd = p;
db->lookaside.bDisable = 0;
db->lookaside.bMalloced = pBuf==0 ?1:0;
+ db->lookaside.nSlot = nBig+nSm;
}else{
db->lookaside.pStart = db;
+#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE
+ db->lookaside.pSmallInit = 0;
+ db->lookaside.pSmallFree = 0;
+ db->lookaside.pMiddle = db;
+#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */
db->lookaside.pEnd = db;
db->lookaside.bDisable = 1;
+ db->lookaside.sz = 0;
db->lookaside.bMalloced = 0;
+ db->lookaside.nSlot = 0;
}
+ assert( tdsqlite3LookasideUsed(db,0)==0 );
#endif /* SQLITE_OMIT_LOOKASIDE */
return SQLITE_OK;
}
@@ -141679,9 +163477,9 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){
/*
** Return the mutex associated with a database connection.
*/
-SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3 *db){
+SQLITE_API tdsqlite3_mutex *tdsqlite3_db_mutex(tdsqlite3 *db){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
@@ -141693,23 +163491,23 @@ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3 *db){
** Free up as much memory as we can from the given database
** connection.
*/
-SQLITE_API int sqlite3_db_release_memory(sqlite3 *db){
+SQLITE_API int tdsqlite3_db_release_memory(tdsqlite3 *db){
int i;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
- sqlite3BtreeEnterAll(db);
+ tdsqlite3_mutex_enter(db->mutex);
+ tdsqlite3BtreeEnterAll(db);
for(i=0; i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
if( pBt ){
- Pager *pPager = sqlite3BtreePager(pBt);
- sqlite3PagerShrink(pPager);
+ Pager *pPager = tdsqlite3BtreePager(pBt);
+ tdsqlite3PagerShrink(pPager);
}
}
- sqlite3BtreeLeaveAll(db);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3BtreeLeaveAll(db);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
}
@@ -141717,41 +163515,43 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3 *db){
** Flush any dirty pages in the pager-cache for any attached database
** to disk.
*/
-SQLITE_API int sqlite3_db_cacheflush(sqlite3 *db){
+SQLITE_API int tdsqlite3_db_cacheflush(tdsqlite3 *db){
int i;
int rc = SQLITE_OK;
int bSeenBusy = 0;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
- sqlite3BtreeEnterAll(db);
+ tdsqlite3_mutex_enter(db->mutex);
+ tdsqlite3BtreeEnterAll(db);
for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
Btree *pBt = db->aDb[i].pBt;
- if( pBt && sqlite3BtreeIsInTrans(pBt) ){
- Pager *pPager = sqlite3BtreePager(pBt);
- rc = sqlite3PagerFlush(pPager);
+ if( pBt && tdsqlite3BtreeIsInTrans(pBt) ){
+ Pager *pPager = tdsqlite3BtreePager(pBt);
+ rc = tdsqlite3PagerFlush(pPager);
if( rc==SQLITE_BUSY ){
bSeenBusy = 1;
rc = SQLITE_OK;
}
}
}
- sqlite3BtreeLeaveAll(db);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3BtreeLeaveAll(db);
+ tdsqlite3_mutex_leave(db->mutex);
return ((rc==SQLITE_OK && bSeenBusy) ? SQLITE_BUSY : rc);
}
/*
** Configuration settings for an individual database connection
*/
-SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){
+SQLITE_API int tdsqlite3_db_config(tdsqlite3 *db, int op, ...){
va_list ap;
int rc;
va_start(ap, op);
switch( op ){
case SQLITE_DBCONFIG_MAINDBNAME: {
+ /* IMP: R-06824-28531 */
+ /* IMP: R-36257-52125 */
db->aDb[0].zDbSName = va_arg(ap,char*);
rc = SQLITE_OK;
break;
@@ -141770,8 +163570,21 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){
} aFlagOp[] = {
{ SQLITE_DBCONFIG_ENABLE_FKEY, SQLITE_ForeignKeys },
{ SQLITE_DBCONFIG_ENABLE_TRIGGER, SQLITE_EnableTrigger },
+ { SQLITE_DBCONFIG_ENABLE_VIEW, SQLITE_EnableView },
{ SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, SQLITE_Fts3Tokenizer },
{ SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_LoadExtension },
+ { SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, SQLITE_NoCkptOnClose },
+ { SQLITE_DBCONFIG_ENABLE_QPSG, SQLITE_EnableQPSG },
+ { SQLITE_DBCONFIG_TRIGGER_EQP, SQLITE_TriggerEQP },
+ { SQLITE_DBCONFIG_RESET_DATABASE, SQLITE_ResetDatabase },
+ { SQLITE_DBCONFIG_DEFENSIVE, SQLITE_Defensive },
+ { SQLITE_DBCONFIG_WRITABLE_SCHEMA, SQLITE_WriteSchema|
+ SQLITE_NoSchemaError },
+ { SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, SQLITE_LegacyAlter },
+ { SQLITE_DBCONFIG_DQS_DDL, SQLITE_DqsDDL },
+ { SQLITE_DBCONFIG_DQS_DML, SQLITE_DqsDML },
+ { SQLITE_DBCONFIG_LEGACY_FILE_FORMAT, SQLITE_LegacyFileFmt },
+ { SQLITE_DBCONFIG_TRUSTED_SCHEMA, SQLITE_TrustedSchema },
};
unsigned int i;
rc = SQLITE_ERROR; /* IMP: R-42790-23372 */
@@ -141779,14 +163592,14 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){
if( aFlagOp[i].op==op ){
int onoff = va_arg(ap, int);
int *pRes = va_arg(ap, int*);
- int oldFlags = db->flags;
+ u64 oldFlags = db->flags;
if( onoff>0 ){
db->flags |= aFlagOp[i].mask;
}else if( onoff==0 ){
- db->flags &= ~aFlagOp[i].mask;
+ db->flags &= ~(u64)aFlagOp[i].mask;
}
if( oldFlags!=db->flags ){
- sqlite3ExpirePreparedStatements(db);
+ tdsqlite3ExpirePreparedStatements(db, 0);
}
if( pRes ){
*pRes = (db->flags & aFlagOp[i].mask)!=0;
@@ -141802,51 +163615,54 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){
return rc;
}
-
-/*
-** Return true if the buffer z[0..n-1] contains all spaces.
-*/
-static int allSpaces(const char *z, int n){
- while( n>0 && z[n-1]==' ' ){ n--; }
- return n==0;
-}
-
/*
** This is the default collating function named "BINARY" which is always
** available.
-**
-** If the padFlag argument is not NULL then space padding at the end
-** of strings is ignored. This implements the RTRIM collation.
*/
static int binCollFunc(
- void *padFlag,
+ void *NotUsed,
int nKey1, const void *pKey1,
int nKey2, const void *pKey2
){
int rc, n;
+ UNUSED_PARAMETER(NotUsed);
n = nKey1<nKey2 ? nKey1 : nKey2;
/* EVIDENCE-OF: R-65033-28449 The built-in BINARY collation compares
** strings byte by byte using the memcmp() function from the standard C
** library. */
+ assert( pKey1 && pKey2 );
rc = memcmp(pKey1, pKey2, n);
if( rc==0 ){
- if( padFlag
- && allSpaces(((char*)pKey1)+n, nKey1-n)
- && allSpaces(((char*)pKey2)+n, nKey2-n)
- ){
- /* EVIDENCE-OF: R-31624-24737 RTRIM is like BINARY except that extra
- ** spaces at the end of either string do not change the result. In other
- ** words, strings will compare equal to one another as long as they
- ** differ only in the number of spaces at the end.
- */
- }else{
- rc = nKey1 - nKey2;
- }
+ rc = nKey1 - nKey2;
}
return rc;
}
/*
+** This is the collating function named "RTRIM" which is always
+** available. Ignore trailing spaces.
+*/
+static int rtrimCollFunc(
+ void *pUser,
+ int nKey1, const void *pKey1,
+ int nKey2, const void *pKey2
+){
+ const u8 *pK1 = (const u8*)pKey1;
+ const u8 *pK2 = (const u8*)pKey2;
+ while( nKey1 && pK1[nKey1-1]==' ' ) nKey1--;
+ while( nKey2 && pK2[nKey2-1]==' ' ) nKey2--;
+ return binCollFunc(pUser, nKey1, pKey1, nKey2, pKey2);
+}
+
+/*
+** Return true if CollSeq is the default built-in BINARY.
+*/
+SQLITE_PRIVATE int tdsqlite3IsBinary(const CollSeq *p){
+ assert( p==0 || p->xCmp!=binCollFunc || strcmp(p->zName,"BINARY")==0 );
+ return p==0 || p->xCmp==binCollFunc;
+}
+
+/*
** Another built-in collating sequence: NOCASE.
**
** This collating sequence is intended to be used for "case independent
@@ -141860,7 +163676,7 @@ static int nocaseCollatingFunc(
int nKey1, const void *pKey1,
int nKey2, const void *pKey2
){
- int r = sqlite3StrNICmp(
+ int r = tdsqlite3StrNICmp(
(const char *)pKey1, (const char *)pKey2, (nKey1<nKey2)?nKey1:nKey2);
UNUSED_PARAMETER(NotUsed);
if( 0==r ){
@@ -141872,9 +163688,9 @@ static int nocaseCollatingFunc(
/*
** Return the ROWID of the most recent insert
*/
-SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){
+SQLITE_API sqlite_int64 tdsqlite3_last_insert_rowid(tdsqlite3 *db){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
@@ -141883,11 +163699,26 @@ SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){
}
/*
-** Return the number of changes in the most recent call to sqlite3_exec().
+** Set the value returned by the tdsqlite3_last_insert_rowid() API function.
*/
-SQLITE_API int sqlite3_changes(sqlite3 *db){
+SQLITE_API void tdsqlite3_set_last_insert_rowid(tdsqlite3 *db, tdsqlite3_int64 iRowid){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
+ (void)SQLITE_MISUSE_BKPT;
+ return;
+ }
+#endif
+ tdsqlite3_mutex_enter(db->mutex);
+ db->lastRowid = iRowid;
+ tdsqlite3_mutex_leave(db->mutex);
+}
+
+/*
+** Return the number of changes in the most recent call to tdsqlite3_exec().
+*/
+SQLITE_API int tdsqlite3_changes(tdsqlite3 *db){
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
@@ -141898,9 +163729,9 @@ SQLITE_API int sqlite3_changes(sqlite3 *db){
/*
** Return the number of changes since the database handle was opened.
*/
-SQLITE_API int sqlite3_total_changes(sqlite3 *db){
+SQLITE_API int tdsqlite3_total_changes(tdsqlite3 *db){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
@@ -141913,11 +163744,11 @@ SQLITE_API int sqlite3_total_changes(sqlite3 *db){
** database handle object, it does not close any savepoints that may be open
** at the b-tree/pager level.
*/
-SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3CloseSavepoints(tdsqlite3 *db){
while( db->pSavepoint ){
Savepoint *pTmp = db->pSavepoint;
db->pSavepoint = pTmp->pNext;
- sqlite3DbFree(db, pTmp);
+ tdsqlite3DbFree(db, pTmp);
}
db->nSavepoint = 0;
db->nStatement = 0;
@@ -141930,43 +163761,43 @@ SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *db){
** copies of a single function are created when create_function() is called
** with SQLITE_ANY as the encoding.
*/
-static void functionDestroy(sqlite3 *db, FuncDef *p){
+static void functionDestroy(tdsqlite3 *db, FuncDef *p){
FuncDestructor *pDestructor = p->u.pDestructor;
if( pDestructor ){
pDestructor->nRef--;
if( pDestructor->nRef==0 ){
pDestructor->xDestroy(pDestructor->pUserData);
- sqlite3DbFree(db, pDestructor);
+ tdsqlite3DbFree(db, pDestructor);
}
}
}
/*
-** Disconnect all sqlite3_vtab objects that belong to database connection
+** Disconnect all tdsqlite3_vtab objects that belong to database connection
** db. This is called when db is being closed.
*/
-static void disconnectAllVtab(sqlite3 *db){
+static void disconnectAllVtab(tdsqlite3 *db){
#ifndef SQLITE_OMIT_VIRTUALTABLE
int i;
HashElem *p;
- sqlite3BtreeEnterAll(db);
+ tdsqlite3BtreeEnterAll(db);
for(i=0; i<db->nDb; i++){
Schema *pSchema = db->aDb[i].pSchema;
- if( db->aDb[i].pSchema ){
+ if( pSchema ){
for(p=sqliteHashFirst(&pSchema->tblHash); p; p=sqliteHashNext(p)){
Table *pTab = (Table *)sqliteHashData(p);
- if( IsVirtual(pTab) ) sqlite3VtabDisconnect(db, pTab);
+ if( IsVirtual(pTab) ) tdsqlite3VtabDisconnect(db, pTab);
}
}
}
for(p=sqliteHashFirst(&db->aModule); p; p=sqliteHashNext(p)){
Module *pMod = (Module *)sqliteHashData(p);
if( pMod->pEpoTab ){
- sqlite3VtabDisconnect(db, pMod->pEpoTab);
+ tdsqlite3VtabDisconnect(db, pMod->pEpoTab);
}
}
- sqlite3VtabUnlockList(db);
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3VtabUnlockList(db);
+ tdsqlite3BtreeLeaveAll(db);
#else
UNUSED_PARAMETER(db);
#endif
@@ -141974,15 +163805,15 @@ static void disconnectAllVtab(sqlite3 *db){
/*
** Return TRUE if database connection db has unfinalized prepared
-** statements or unfinished sqlite3_backup objects.
+** statements or unfinished tdsqlite3_backup objects.
*/
-static int connectionIsBusy(sqlite3 *db){
+static int connectionIsBusy(tdsqlite3 *db){
int j;
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
if( db->pVdbe ) return 1;
for(j=0; j<db->nDb; j++){
Btree *pBt = db->aDb[j].pBt;
- if( pBt && sqlite3BtreeIsInBackup(pBt) ) return 1;
+ if( pBt && tdsqlite3BtreeIsInBackup(pBt) ) return 1;
}
return 0;
}
@@ -141990,16 +163821,16 @@ static int connectionIsBusy(sqlite3 *db){
/*
** Close an existing SQLite database
*/
-static int sqlite3Close(sqlite3 *db, int forceZombie){
+static int tdsqlite3Close(tdsqlite3 *db, int forceZombie){
if( !db ){
- /* EVIDENCE-OF: R-63257-11740 Calling sqlite3_close() or
- ** sqlite3_close_v2() with a NULL pointer argument is a harmless no-op. */
+ /* EVIDENCE-OF: R-63257-11740 Calling tdsqlite3_close() or
+ ** tdsqlite3_close_v2() with a NULL pointer argument is a harmless no-op. */
return SQLITE_OK;
}
- if( !sqlite3SafetyCheckSickOrOk(db) ){
+ if( !tdsqlite3SafetyCheckSickOrOk(db) ){
return SQLITE_MISUSE_BKPT;
}
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
if( db->mTrace & SQLITE_TRACE_CLOSE ){
db->xTrace(SQLITE_TRACE_CLOSE, db->pTraceArg, db, 0);
}
@@ -142009,74 +163840,74 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){
/* If a transaction is open, the disconnectAllVtab() call above
** will not have called the xDisconnect() method on any virtual
- ** tables in the db->aVTrans[] array. The following sqlite3VtabRollback()
+ ** tables in the db->aVTrans[] array. The following tdsqlite3VtabRollback()
** call will do so. We need to do this before the check for active
** SQL statements below, as the v-table implementation may be storing
** some prepared statements internally.
*/
- sqlite3VtabRollback(db);
+ tdsqlite3VtabRollback(db);
- /* Legacy behavior (sqlite3_close() behavior) is to return
+ /* Legacy behavior (tdsqlite3_close() behavior) is to return
** SQLITE_BUSY if the connection can not be closed immediately.
*/
if( !forceZombie && connectionIsBusy(db) ){
- sqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to close due to unfinalized "
+ tdsqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to close due to unfinalized "
"statements or unfinished backups");
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_BUSY;
}
#ifdef SQLITE_ENABLE_SQLLOG
- if( sqlite3GlobalConfig.xSqllog ){
+ if( tdsqlite3GlobalConfig.xSqllog ){
/* Closing the handle. Fourth parameter is passed the value 2. */
- sqlite3GlobalConfig.xSqllog(sqlite3GlobalConfig.pSqllogArg, db, 0, 2);
+ tdsqlite3GlobalConfig.xSqllog(tdsqlite3GlobalConfig.pSqllogArg, db, 0, 2);
}
#endif
/* Convert the connection into a zombie and then close it.
*/
db->magic = SQLITE_MAGIC_ZOMBIE;
- sqlite3LeaveMutexAndCloseZombie(db);
+ tdsqlite3LeaveMutexAndCloseZombie(db);
return SQLITE_OK;
}
/*
** Two variations on the public interface for closing a database
-** connection. The sqlite3_close() version returns SQLITE_BUSY and
+** connection. The tdsqlite3_close() version returns SQLITE_BUSY and
** leaves the connection option if there are unfinalized prepared
-** statements or unfinished sqlite3_backups. The sqlite3_close_v2()
+** statements or unfinished tdsqlite3_backups. The tdsqlite3_close_v2()
** version forces the connection to become a zombie if there are
** unclosed resources, and arranges for deallocation when the last
-** prepare statement or sqlite3_backup closes.
+** prepare statement or tdsqlite3_backup closes.
*/
-SQLITE_API int sqlite3_close(sqlite3 *db){ return sqlite3Close(db,0); }
-SQLITE_API int sqlite3_close_v2(sqlite3 *db){ return sqlite3Close(db,1); }
+SQLITE_API int tdsqlite3_close(tdsqlite3 *db){ return tdsqlite3Close(db,0); }
+SQLITE_API int tdsqlite3_close_v2(tdsqlite3 *db){ return tdsqlite3Close(db,1); }
/*
** Close the mutex on database connection db.
**
** Furthermore, if database connection db is a zombie (meaning that there
-** has been a prior call to sqlite3_close(db) or sqlite3_close_v2(db)) and
-** every sqlite3_stmt has now been finalized and every sqlite3_backup has
+** has been a prior call to tdsqlite3_close(db) or tdsqlite3_close_v2(db)) and
+** every tdsqlite3_stmt has now been finalized and every tdsqlite3_backup has
** finished, then free all resources.
*/
-SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3LeaveMutexAndCloseZombie(tdsqlite3 *db){
HashElem *i; /* Hash table iterator */
int j;
- /* If there are outstanding sqlite3_stmt or sqlite3_backup objects
- ** or if the connection has not yet been closed by sqlite3_close_v2(),
+ /* If there are outstanding tdsqlite3_stmt or tdsqlite3_backup objects
+ ** or if the connection has not yet been closed by tdsqlite3_close_v2(),
** then just leave the mutex and return.
*/
if( db->magic!=SQLITE_MAGIC_ZOMBIE || connectionIsBusy(db) ){
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return;
}
/* If we reach this point, it means that the database connection has
- ** closed all sqlite3_stmt and sqlite3_backup objects and has been
- ** passed to sqlite3_close (meaning that it is a zombie). Therefore,
+ ** closed all tdsqlite3_stmt and tdsqlite3_backup objects and has been
+ ** passed to tdsqlite3_close (meaning that it is a zombie). Therefore,
** go ahead and free all resources.
*/
@@ -142084,16 +163915,16 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
** any database schemas have been modified by an uncommitted transaction
** they are reset. And that the required b-tree mutex is held to make
** the pager rollback and schema reset an atomic operation. */
- sqlite3RollbackAll(db, SQLITE_OK);
+ tdsqlite3RollbackAll(db, SQLITE_OK);
/* Free any outstanding Savepoint structures. */
- sqlite3CloseSavepoints(db);
+ tdsqlite3CloseSavepoints(db);
/* Close all database connections */
for(j=0; j<db->nDb; j++){
struct Db *pDb = &db->aDb[j];
if( pDb->pBt ){
- sqlite3BtreeClose(pDb->pBt);
+ tdsqlite3BtreeClose(pDb->pBt);
pDb->pBt = 0;
if( j!=1 ){
pDb->pSchema = 0;
@@ -142102,19 +163933,19 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
}
/* Clear the TEMP schema separately and last */
if( db->aDb[1].pSchema ){
- sqlite3SchemaClear(db->aDb[1].pSchema);
+ tdsqlite3SchemaClear(db->aDb[1].pSchema);
}
- sqlite3VtabUnlockList(db);
+ tdsqlite3VtabUnlockList(db);
/* Free up the array of auxiliary databases */
- sqlite3CollapseDatabaseArray(db);
+ tdsqlite3CollapseDatabaseArray(db);
assert( db->nDb<=2 );
assert( db->aDb==db->aDbStatic );
/* Tell the code in notify.c that the connection no longer holds any
** locks and does not require any further unlock-notify callbacks.
*/
- sqlite3ConnectionClosed(db);
+ tdsqlite3ConnectionClosed(db);
for(i=sqliteHashFirst(&db->aFunc); i; i=sqliteHashNext(i)){
FuncDef *pNext, *p;
@@ -142122,11 +163953,11 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
do{
functionDestroy(db, p);
pNext = p->pNext;
- sqlite3DbFree(db, p);
+ tdsqlite3DbFree(db, p);
p = pNext;
}while( p );
}
- sqlite3HashClear(&db->aFunc);
+ tdsqlite3HashClear(&db->aFunc);
for(i=sqliteHashFirst(&db->aCollSeq); i; i=sqliteHashNext(i)){
CollSeq *pColl = (CollSeq *)sqliteHashData(i);
/* Invoke any destructors registered for collation sequence user data. */
@@ -142135,46 +163966,43 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
pColl[j].xDel(pColl[j].pUser);
}
}
- sqlite3DbFree(db, pColl);
+ tdsqlite3DbFree(db, pColl);
}
- sqlite3HashClear(&db->aCollSeq);
+ tdsqlite3HashClear(&db->aCollSeq);
#ifndef SQLITE_OMIT_VIRTUALTABLE
for(i=sqliteHashFirst(&db->aModule); i; i=sqliteHashNext(i)){
Module *pMod = (Module *)sqliteHashData(i);
- if( pMod->xDestroy ){
- pMod->xDestroy(pMod->pAux);
- }
- sqlite3VtabEponymousTableClear(db, pMod);
- sqlite3DbFree(db, pMod);
+ tdsqlite3VtabEponymousTableClear(db, pMod);
+ tdsqlite3VtabModuleUnref(db, pMod);
}
- sqlite3HashClear(&db->aModule);
+ tdsqlite3HashClear(&db->aModule);
#endif
- sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */
- sqlite3ValueFree(db->pErr);
- sqlite3CloseExtensions(db);
+ tdsqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */
+ tdsqlite3ValueFree(db->pErr);
+ tdsqlite3CloseExtensions(db);
#if SQLITE_USER_AUTHENTICATION
- sqlite3_free(db->auth.zAuthUser);
- sqlite3_free(db->auth.zAuthPW);
+ tdsqlite3_free(db->auth.zAuthUser);
+ tdsqlite3_free(db->auth.zAuthPW);
#endif
db->magic = SQLITE_MAGIC_ERROR;
/* The temp-database schema is allocated differently from the other schema
- ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()).
+ ** objects (using sqliteMalloc() directly, instead of tdsqlite3BtreeSchema()).
** So it needs to be freed here. Todo: Why not roll the temp schema into
** the same sqliteMalloc() as the one that allocates the database
** structure?
*/
- sqlite3DbFree(db, db->aDb[1].pSchema);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3DbFree(db, db->aDb[1].pSchema);
+ tdsqlite3_mutex_leave(db->mutex);
db->magic = SQLITE_MAGIC_CLOSED;
- sqlite3_mutex_free(db->mutex);
- assert( db->lookaside.nOut==0 ); /* Fails on a lookaside memory leak */
+ tdsqlite3_mutex_free(db->mutex);
+ assert( tdsqlite3LookasideUsed(db,0)==0 );
if( db->lookaside.bMalloced ){
- sqlite3_free(db->lookaside.pStart);
+ tdsqlite3_free(db->lookaside.pStart);
}
- sqlite3_free(db);
+ tdsqlite3_free(db);
}
/*
@@ -142184,12 +164012,12 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
** attempts to use that cursor. Read cursors remain open and valid
** but are "saved" in case the table pages are moved around.
*/
-SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){
+SQLITE_PRIVATE void tdsqlite3RollbackAll(tdsqlite3 *db, int tripCode){
int i;
int inTrans = 0;
int schemaChange;
- assert( sqlite3_mutex_held(db->mutex) );
- sqlite3BeginBenignMalloc();
+ assert( tdsqlite3_mutex_held(db->mutex) );
+ tdsqlite3BeginBenignMalloc();
/* Obtain all b-tree mutexes before making any calls to BtreeRollback().
** This is important in case the transaction being rolled back has
@@ -142197,31 +164025,31 @@ SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){
** here, then another shared-cache connection might sneak in between
** the database rollback and schema reset, which can cause false
** corruption reports in some cases. */
- sqlite3BtreeEnterAll(db);
- schemaChange = (db->flags & SQLITE_InternChanges)!=0 && db->init.busy==0;
+ tdsqlite3BtreeEnterAll(db);
+ schemaChange = (db->mDbFlags & DBFLAG_SchemaChange)!=0 && db->init.busy==0;
for(i=0; i<db->nDb; i++){
Btree *p = db->aDb[i].pBt;
if( p ){
- if( sqlite3BtreeIsInTrans(p) ){
+ if( tdsqlite3BtreeIsInTrans(p) ){
inTrans = 1;
}
- sqlite3BtreeRollback(p, tripCode, !schemaChange);
+ tdsqlite3BtreeRollback(p, tripCode, !schemaChange);
}
}
- sqlite3VtabRollback(db);
- sqlite3EndBenignMalloc();
+ tdsqlite3VtabRollback(db);
+ tdsqlite3EndBenignMalloc();
- if( (db->flags&SQLITE_InternChanges)!=0 && db->init.busy==0 ){
- sqlite3ExpirePreparedStatements(db);
- sqlite3ResetAllSchemasOfConnection(db);
+ if( schemaChange ){
+ tdsqlite3ExpirePreparedStatements(db, 0);
+ tdsqlite3ResetAllSchemasOfConnection(db);
}
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3BtreeLeaveAll(db);
/* Any deferred constraint violations have now been resolved. */
db->nDeferredCons = 0;
db->nDeferredImmCons = 0;
- db->flags &= ~SQLITE_DeferFKs;
+ db->flags &= ~(u64)SQLITE_DeferFKs;
/* If one has been configured, invoke the rollback-hook callback */
if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){
@@ -142234,13 +164062,14 @@ SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){
** specified in the argument.
*/
#if defined(SQLITE_NEED_ERR_NAME)
-SQLITE_PRIVATE const char *sqlite3ErrName(int rc){
+SQLITE_PRIVATE const char *tdsqlite3ErrName(int rc){
const char *zName = 0;
int i, origRc = rc;
for(i=0; i<2 && zName==0; i++, rc &= 0xff){
switch( rc ){
case SQLITE_OK: zName = "SQLITE_OK"; break;
case SQLITE_ERROR: zName = "SQLITE_ERROR"; break;
+ case SQLITE_ERROR_SNAPSHOT: zName = "SQLITE_ERROR_SNAPSHOT"; break;
case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break;
case SQLITE_PERM: zName = "SQLITE_PERM"; break;
case SQLITE_ABORT: zName = "SQLITE_ABORT"; break;
@@ -142253,9 +164082,10 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){
case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break;
case SQLITE_READONLY: zName = "SQLITE_READONLY"; break;
case SQLITE_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break;
- case SQLITE_READONLY_CANTLOCK: zName = "SQLITE_READONLY_CANTLOCK"; break;
+ case SQLITE_READONLY_CANTINIT: zName = "SQLITE_READONLY_CANTINIT"; break;
case SQLITE_READONLY_ROLLBACK: zName = "SQLITE_READONLY_ROLLBACK"; break;
case SQLITE_READONLY_DBMOVED: zName = "SQLITE_READONLY_DBMOVED"; break;
+ case SQLITE_READONLY_DIRECTORY: zName = "SQLITE_READONLY_DIRECTORY";break;
case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break;
case SQLITE_IOERR: zName = "SQLITE_IOERR"; break;
case SQLITE_IOERR_READ: zName = "SQLITE_IOERR_READ"; break;
@@ -142293,6 +164123,7 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){
case SQLITE_CANTOPEN_ISDIR: zName = "SQLITE_CANTOPEN_ISDIR"; break;
case SQLITE_CANTOPEN_FULLPATH: zName = "SQLITE_CANTOPEN_FULLPATH"; break;
case SQLITE_CANTOPEN_CONVPATH: zName = "SQLITE_CANTOPEN_CONVPATH"; break;
+ case SQLITE_CANTOPEN_SYMLINK: zName = "SQLITE_CANTOPEN_SYMLINK"; break;
case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break;
case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break;
case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break;
@@ -142331,7 +164162,7 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){
}
if( zName==0 ){
static char zBuf[50];
- sqlite3_snprintf(sizeof(zBuf), zBuf, "SQLITE_UNKNOWN(%d)", origRc);
+ tdsqlite3_snprintf(sizeof(zBuf), zBuf, "SQLITE_UNKNOWN(%d)", origRc);
zName = zBuf;
}
return zName;
@@ -142342,13 +164173,13 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){
** Return a static string that describes the kind of error specified in the
** argument.
*/
-SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){
+SQLITE_PRIVATE const char *tdsqlite3ErrStr(int rc){
static const char* const aMsg[] = {
/* SQLITE_OK */ "not an error",
- /* SQLITE_ERROR */ "SQL logic error or missing database",
+ /* SQLITE_ERROR */ "SQL logic error",
/* SQLITE_INTERNAL */ 0,
/* SQLITE_PERM */ "access permission denied",
- /* SQLITE_ABORT */ "callback requested query abort",
+ /* SQLITE_ABORT */ "query aborted",
/* SQLITE_BUSY */ "database is locked",
/* SQLITE_LOCKED */ "database table is locked",
/* SQLITE_NOMEM */ "out of memory",
@@ -142360,17 +164191,23 @@ SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){
/* SQLITE_FULL */ "database or disk is full",
/* SQLITE_CANTOPEN */ "unable to open database file",
/* SQLITE_PROTOCOL */ "locking protocol",
- /* SQLITE_EMPTY */ "table contains no data",
+ /* SQLITE_EMPTY */ 0,
/* SQLITE_SCHEMA */ "database schema has changed",
/* SQLITE_TOOBIG */ "string or blob too big",
/* SQLITE_CONSTRAINT */ "constraint failed",
/* SQLITE_MISMATCH */ "datatype mismatch",
- /* SQLITE_MISUSE */ "library routine called out of sequence",
+ /* SQLITE_MISUSE */ "bad parameter or other API misuse",
+#ifdef SQLITE_DISABLE_LFS
/* SQLITE_NOLFS */ "large file support is disabled",
+#else
+ /* SQLITE_NOLFS */ 0,
+#endif
/* SQLITE_AUTH */ "authorization denied",
- /* SQLITE_FORMAT */ "auxiliary database format error",
- /* SQLITE_RANGE */ "bind or column index out of range",
- /* SQLITE_NOTADB */ "file is encrypted or is not a database",
+ /* SQLITE_FORMAT */ 0,
+ /* SQLITE_RANGE */ "column index out of range",
+ /* SQLITE_NOTADB */ "file is not a database",
+ /* SQLITE_NOTICE */ "notification message",
+ /* SQLITE_WARNING */ "warning message",
};
const char *zErr = "unknown error";
switch( rc ){
@@ -142378,6 +164215,14 @@ SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){
zErr = "abort due to ROLLBACK";
break;
}
+ case SQLITE_ROW: {
+ zErr = "another row available";
+ break;
+ }
+ case SQLITE_DONE: {
+ zErr = "no more rows available";
+ break;
+ }
default: {
rc &= 0xff;
if( ALWAYS(rc>=0) && rc<ArraySize(aMsg) && aMsg[rc]!=0 ){
@@ -142394,21 +164239,40 @@ SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){
** again until a timeout value is reached. The timeout value is
** an integer number of milliseconds passed in as the first
** argument.
+**
+** Return non-zero to retry the lock. Return zero to stop trying
+** and cause SQLite to return SQLITE_BUSY.
*/
static int sqliteDefaultBusyCallback(
- void *ptr, /* Database connection */
- int count /* Number of times table has been busy */
+ void *ptr, /* Database connection */
+ int count, /* Number of times table has been busy */
+ tdsqlite3_file *pFile /* The file on which the lock occurred */
){
#if SQLITE_OS_WIN || HAVE_USLEEP
+ /* This case is for systems that have support for sleeping for fractions of
+ ** a second. Examples: All windows systems, unix systems with usleep() */
static const u8 delays[] =
{ 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 };
static const u8 totals[] =
{ 0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228 };
# define NDELAY ArraySize(delays)
- sqlite3 *db = (sqlite3 *)ptr;
- int timeout = db->busyTimeout;
+ tdsqlite3 *db = (tdsqlite3 *)ptr;
+ int tmout = db->busyTimeout;
int delay, prior;
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
+ if( tdsqlite3OsFileControl(pFile,SQLITE_FCNTL_LOCK_TIMEOUT,&tmout)==SQLITE_OK ){
+ if( count ){
+ tmout = 0;
+ tdsqlite3OsFileControl(pFile, SQLITE_FCNTL_LOCK_TIMEOUT, &tmout);
+ return 0;
+ }else{
+ return 1;
+ }
+ }
+#else
+ UNUSED_PARAMETER(pFile);
+#endif
assert( count>=0 );
if( count < NDELAY ){
delay = delays[count];
@@ -142417,19 +164281,22 @@ static int sqliteDefaultBusyCallback(
delay = delays[NDELAY-1];
prior = totals[NDELAY-1] + delay*(count-(NDELAY-1));
}
- if( prior + delay > timeout ){
- delay = timeout - prior;
+ if( prior + delay > tmout ){
+ delay = tmout - prior;
if( delay<=0 ) return 0;
}
- sqlite3OsSleep(db->pVfs, delay*1000);
+ tdsqlite3OsSleep(db->pVfs, delay*1000);
return 1;
#else
- sqlite3 *db = (sqlite3 *)ptr;
- int timeout = ((sqlite3 *)ptr)->busyTimeout;
- if( (count+1)*1000 > timeout ){
+ /* This case for unix systems that lack usleep() support. Sleeping
+ ** must be done in increments of whole seconds */
+ tdsqlite3 *db = (tdsqlite3 *)ptr;
+ int tmout = ((tdsqlite3 *)ptr)->busyTimeout;
+ UNUSED_PARAMETER(pFile);
+ if( (count+1)*1000 > tmout ){
return 0;
}
- sqlite3OsSleep(db->pVfs, 1000000);
+ tdsqlite3OsSleep(db->pVfs, 1000000);
return 1;
#endif
}
@@ -142437,14 +164304,25 @@ static int sqliteDefaultBusyCallback(
/*
** Invoke the given busy handler.
**
-** This routine is called when an operation failed with a lock.
+** This routine is called when an operation failed to acquire a
+** lock on VFS file pFile.
+**
** If this routine returns non-zero, the lock is retried. If it
** returns 0, the operation aborts with an SQLITE_BUSY error.
*/
-SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler *p){
+SQLITE_PRIVATE int tdsqlite3InvokeBusyHandler(BusyHandler *p, tdsqlite3_file *pFile){
int rc;
- if( NEVER(p==0) || p->xFunc==0 || p->nBusy<0 ) return 0;
- rc = p->xFunc(p->pArg, p->nBusy);
+ if( p->xBusyHandler==0 || p->nBusy<0 ) return 0;
+ if( p->bExtraFileArg ){
+ /* Add an extra parameter with the pFile pointer to the end of the
+ ** callback argument list */
+ int (*xTra)(void*,int,tdsqlite3_file*);
+ xTra = (int(*)(void*,int,tdsqlite3_file*))p->xBusyHandler;
+ rc = xTra(p->pBusyArg, p->nBusy, pFile);
+ }else{
+ /* Legacy style busy handler callback */
+ rc = p->xBusyHandler(p->pBusyArg, p->nBusy);
+ }
if( rc==0 ){
p->nBusy = -1;
}else{
@@ -142457,20 +164335,21 @@ SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler *p){
** This routine sets the busy callback for an Sqlite database to the
** given callback function with the given argument.
*/
-SQLITE_API int sqlite3_busy_handler(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_busy_handler(
+ tdsqlite3 *db,
int (*xBusy)(void*,int),
void *pArg
){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
- db->busyHandler.xFunc = xBusy;
- db->busyHandler.pArg = pArg;
+ tdsqlite3_mutex_enter(db->mutex);
+ db->busyHandler.xBusyHandler = xBusy;
+ db->busyHandler.pBusyArg = pArg;
db->busyHandler.nBusy = 0;
+ db->busyHandler.bExtraFileArg = 0;
db->busyTimeout = 0;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
}
@@ -142480,19 +164359,19 @@ SQLITE_API int sqlite3_busy_handler(
** given callback function with the given argument. The progress callback will
** be invoked every nOps opcodes.
*/
-SQLITE_API void sqlite3_progress_handler(
- sqlite3 *db,
+SQLITE_API void tdsqlite3_progress_handler(
+ tdsqlite3 *db,
int nOps,
int (*xProgress)(void*),
void *pArg
){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
if( nOps>0 ){
db->xProgress = xProgress;
db->nProgressOps = (unsigned)nOps;
@@ -142502,7 +164381,7 @@ SQLITE_API void sqlite3_progress_handler(
db->nProgressOps = 0;
db->pProgressArg = 0;
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
}
#endif
@@ -142511,15 +164390,17 @@ SQLITE_API void sqlite3_progress_handler(
** This routine installs a default busy handler that waits for the
** specified number of milliseconds before returning 0.
*/
-SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){
+SQLITE_API int tdsqlite3_busy_timeout(tdsqlite3 *db, int ms){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
if( ms>0 ){
- sqlite3_busy_handler(db, sqliteDefaultBusyCallback, (void*)db);
+ tdsqlite3_busy_handler(db, (int(*)(void*,int))sqliteDefaultBusyCallback,
+ (void*)db);
db->busyTimeout = ms;
+ db->busyHandler.bExtraFileArg = 1;
}else{
- sqlite3_busy_handler(db, 0, 0);
+ tdsqlite3_busy_handler(db, 0, 0);
}
return SQLITE_OK;
}
@@ -142527,9 +164408,9 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){
/*
** Cause any pending operation to stop at its earliest opportunity.
*/
-SQLITE_API void sqlite3_interrupt(sqlite3 *db){
+SQLITE_API void tdsqlite3_interrupt(tdsqlite3 *db){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) && (db==0 || db->magic!=SQLITE_MAGIC_ZOMBIE) ){
(void)SQLITE_MISUSE_BKPT;
return;
}
@@ -142539,39 +164420,51 @@ SQLITE_API void sqlite3_interrupt(sqlite3 *db){
/*
-** This function is exactly the same as sqlite3_create_function(), except
+** This function is exactly the same as tdsqlite3_create_function(), except
** that it is designed to be called by internal code. The difference is
-** that if a malloc() fails in sqlite3_create_function(), an error code
+** that if a malloc() fails in tdsqlite3_create_function(), an error code
** is returned and the mallocFailed flag cleared.
*/
-SQLITE_PRIVATE int sqlite3CreateFunc(
- sqlite3 *db,
+SQLITE_PRIVATE int tdsqlite3CreateFunc(
+ tdsqlite3 *db,
const char *zFunctionName,
int nArg,
int enc,
void *pUserData,
- void (*xSFunc)(sqlite3_context*,int,sqlite3_value **),
- void (*xStep)(sqlite3_context*,int,sqlite3_value **),
- void (*xFinal)(sqlite3_context*),
+ void (*xSFunc)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*xFinal)(tdsqlite3_context*),
+ void (*xValue)(tdsqlite3_context*),
+ void (*xInverse)(tdsqlite3_context*,int,tdsqlite3_value **),
FuncDestructor *pDestructor
){
FuncDef *p;
int nName;
int extraFlags;
- assert( sqlite3_mutex_held(db->mutex) );
- if( zFunctionName==0 ||
- (xSFunc && (xFinal || xStep)) ||
- (!xSFunc && (xFinal && !xStep)) ||
- (!xSFunc && (!xFinal && xStep)) ||
- (nArg<-1 || nArg>SQLITE_MAX_FUNCTION_ARG) ||
- (255<(nName = sqlite3Strlen30( zFunctionName))) ){
+ assert( tdsqlite3_mutex_held(db->mutex) );
+ assert( xValue==0 || xSFunc==0 );
+ if( zFunctionName==0 /* Must have a valid name */
+ || (xSFunc!=0 && xFinal!=0) /* Not both xSFunc and xFinal */
+ || ((xFinal==0)!=(xStep==0)) /* Both or neither of xFinal and xStep */
+ || ((xValue==0)!=(xInverse==0)) /* Both or neither of xValue, xInverse */
+ || (nArg<-1 || nArg>SQLITE_MAX_FUNCTION_ARG)
+ || (255<(nName = tdsqlite3Strlen30( zFunctionName)))
+ ){
return SQLITE_MISUSE_BKPT;
}
assert( SQLITE_FUNC_CONSTANT==SQLITE_DETERMINISTIC );
- extraFlags = enc & SQLITE_DETERMINISTIC;
+ assert( SQLITE_FUNC_DIRECT==SQLITE_DIRECTONLY );
+ extraFlags = enc & (SQLITE_DETERMINISTIC|SQLITE_DIRECTONLY|
+ SQLITE_SUBTYPE|SQLITE_INNOCUOUS);
enc &= (SQLITE_FUNC_ENCMASK|SQLITE_ANY);
+
+ /* The SQLITE_INNOCUOUS flag is the same bit as SQLITE_FUNC_UNSAFE. But
+ ** the meaning is inverted. So flip the bit. */
+ assert( SQLITE_FUNC_UNSAFE==SQLITE_INNOCUOUS );
+ extraFlags ^= SQLITE_FUNC_UNSAFE;
+
#ifndef SQLITE_OMIT_UTF16
/* If SQLITE_UTF16 is specified as the encoding type, transform this
@@ -142585,11 +164478,13 @@ SQLITE_PRIVATE int sqlite3CreateFunc(
enc = SQLITE_UTF16NATIVE;
}else if( enc==SQLITE_ANY ){
int rc;
- rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF8|extraFlags,
- pUserData, xSFunc, xStep, xFinal, pDestructor);
+ rc = tdsqlite3CreateFunc(db, zFunctionName, nArg,
+ (SQLITE_UTF8|extraFlags)^SQLITE_FUNC_UNSAFE,
+ pUserData, xSFunc, xStep, xFinal, xValue, xInverse, pDestructor);
if( rc==SQLITE_OK ){
- rc = sqlite3CreateFunc(db, zFunctionName, nArg, SQLITE_UTF16LE|extraFlags,
- pUserData, xSFunc, xStep, xFinal, pDestructor);
+ rc = tdsqlite3CreateFunc(db, zFunctionName, nArg,
+ (SQLITE_UTF16LE|extraFlags)^SQLITE_FUNC_UNSAFE,
+ pUserData, xSFunc, xStep, xFinal, xValue, xInverse, pDestructor);
}
if( rc!=SQLITE_OK ){
return rc;
@@ -142605,19 +164500,19 @@ SQLITE_PRIVATE int sqlite3CreateFunc(
** is being overridden/deleted but there are no active VMs, allow the
** operation to continue but invalidate all precompiled statements.
*/
- p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 0);
- if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==enc && p->nArg==nArg ){
+ p = tdsqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 0);
+ if( p && (p->funcFlags & SQLITE_FUNC_ENCMASK)==(u32)enc && p->nArg==nArg ){
if( db->nVdbeActive ){
- sqlite3ErrorWithMsg(db, SQLITE_BUSY,
+ tdsqlite3ErrorWithMsg(db, SQLITE_BUSY,
"unable to delete/modify user-function due to active statements");
assert( !db->mallocFailed );
return SQLITE_BUSY;
}else{
- sqlite3ExpirePreparedStatements(db);
+ tdsqlite3ExpirePreparedStatements(db, 0);
}
}
- p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 1);
+ p = tdsqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 1);
assert(p || db->mallocFailed);
if( !p ){
return SQLITE_NOMEM_BKPT;
@@ -142633,102 +164528,169 @@ SQLITE_PRIVATE int sqlite3CreateFunc(
p->u.pDestructor = pDestructor;
p->funcFlags = (p->funcFlags & SQLITE_FUNC_ENCMASK) | extraFlags;
testcase( p->funcFlags & SQLITE_DETERMINISTIC );
+ testcase( p->funcFlags & SQLITE_DIRECTONLY );
p->xSFunc = xSFunc ? xSFunc : xStep;
p->xFinalize = xFinal;
+ p->xValue = xValue;
+ p->xInverse = xInverse;
p->pUserData = pUserData;
p->nArg = (u16)nArg;
return SQLITE_OK;
}
/*
-** Create new user functions.
+** Worker function used by utf-8 APIs that create new functions:
+**
+** tdsqlite3_create_function()
+** tdsqlite3_create_function_v2()
+** tdsqlite3_create_window_function()
*/
-SQLITE_API int sqlite3_create_function(
- sqlite3 *db,
- const char *zFunc,
- int nArg,
- int enc,
- void *p,
- void (*xSFunc)(sqlite3_context*,int,sqlite3_value **),
- void (*xStep)(sqlite3_context*,int,sqlite3_value **),
- void (*xFinal)(sqlite3_context*)
-){
- return sqlite3_create_function_v2(db, zFunc, nArg, enc, p, xSFunc, xStep,
- xFinal, 0);
-}
-
-SQLITE_API int sqlite3_create_function_v2(
- sqlite3 *db,
+static int createFunctionApi(
+ tdsqlite3 *db,
const char *zFunc,
int nArg,
int enc,
void *p,
- void (*xSFunc)(sqlite3_context*,int,sqlite3_value **),
- void (*xStep)(sqlite3_context*,int,sqlite3_value **),
- void (*xFinal)(sqlite3_context*),
- void (*xDestroy)(void *)
+ void (*xSFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*),
+ void (*xValue)(tdsqlite3_context*),
+ void (*xInverse)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void(*xDestroy)(void*)
){
int rc = SQLITE_ERROR;
FuncDestructor *pArg = 0;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
return SQLITE_MISUSE_BKPT;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
if( xDestroy ){
- pArg = (FuncDestructor *)sqlite3DbMallocZero(db, sizeof(FuncDestructor));
+ pArg = (FuncDestructor *)tdsqlite3Malloc(sizeof(FuncDestructor));
if( !pArg ){
+ tdsqlite3OomFault(db);
xDestroy(p);
goto out;
}
+ pArg->nRef = 0;
pArg->xDestroy = xDestroy;
pArg->pUserData = p;
}
- rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, xSFunc, xStep, xFinal, pArg);
+ rc = tdsqlite3CreateFunc(db, zFunc, nArg, enc, p,
+ xSFunc, xStep, xFinal, xValue, xInverse, pArg
+ );
if( pArg && pArg->nRef==0 ){
assert( rc!=SQLITE_OK );
xDestroy(p);
- sqlite3DbFree(db, pArg);
+ tdsqlite3_free(pArg);
}
out:
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
+/*
+** Create new user functions.
+*/
+SQLITE_API int tdsqlite3_create_function(
+ tdsqlite3 *db,
+ const char *zFunc,
+ int nArg,
+ int enc,
+ void *p,
+ void (*xSFunc)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*xFinal)(tdsqlite3_context*)
+){
+ return createFunctionApi(db, zFunc, nArg, enc, p, xSFunc, xStep,
+ xFinal, 0, 0, 0);
+}
+SQLITE_API int tdsqlite3_create_function_v2(
+ tdsqlite3 *db,
+ const char *zFunc,
+ int nArg,
+ int enc,
+ void *p,
+ void (*xSFunc)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*xFinal)(tdsqlite3_context*),
+ void (*xDestroy)(void *)
+){
+ return createFunctionApi(db, zFunc, nArg, enc, p, xSFunc, xStep,
+ xFinal, 0, 0, xDestroy);
+}
+SQLITE_API int tdsqlite3_create_window_function(
+ tdsqlite3 *db,
+ const char *zFunc,
+ int nArg,
+ int enc,
+ void *p,
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*xFinal)(tdsqlite3_context*),
+ void (*xValue)(tdsqlite3_context*),
+ void (*xInverse)(tdsqlite3_context*,int,tdsqlite3_value **),
+ void (*xDestroy)(void *)
+){
+ return createFunctionApi(db, zFunc, nArg, enc, p, 0, xStep,
+ xFinal, xValue, xInverse, xDestroy);
+}
+
#ifndef SQLITE_OMIT_UTF16
-SQLITE_API int sqlite3_create_function16(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_create_function16(
+ tdsqlite3 *db,
const void *zFunctionName,
int nArg,
int eTextRep,
void *p,
- void (*xSFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*)
+ void (*xSFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*)
){
int rc;
char *zFunc8;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || zFunctionName==0 ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) || zFunctionName==0 ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
assert( !db->mallocFailed );
- zFunc8 = sqlite3Utf16to8(db, zFunctionName, -1, SQLITE_UTF16NATIVE);
- rc = sqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xSFunc,xStep,xFinal,0);
- sqlite3DbFree(db, zFunc8);
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ zFunc8 = tdsqlite3Utf16to8(db, zFunctionName, -1, SQLITE_UTF16NATIVE);
+ rc = tdsqlite3CreateFunc(db, zFunc8, nArg, eTextRep, p, xSFunc,xStep,xFinal,0,0,0);
+ tdsqlite3DbFree(db, zFunc8);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
#endif
/*
+** The following is the implementation of an SQL function that always
+** fails with an error message stating that the function is used in the
+** wrong context. The tdsqlite3_overload_function() API might construct
+** SQL function that use this routine so that the functions will exist
+** for name resolution but are actually overloaded by the xFindFunction
+** method of virtual tables.
+*/
+static void tdsqlite3InvalidFunction(
+ tdsqlite3_context *context, /* The function calling context */
+ int NotUsed, /* Number of arguments to the function */
+ tdsqlite3_value **NotUsed2 /* Value of each argument */
+){
+ const char *zName = (const char*)tdsqlite3_user_data(context);
+ char *zErr;
+ UNUSED_PARAMETER2(NotUsed, NotUsed2);
+ zErr = tdsqlite3_mprintf(
+ "unable to use function %s in the requested context", zName);
+ tdsqlite3_result_error(context, zErr, -1);
+ tdsqlite3_free(zErr);
+}
+
+/*
** Declare that a function has been overloaded by a virtual table.
**
** If the function already exists as a regular global function, then
@@ -142740,26 +164702,27 @@ SQLITE_API int sqlite3_create_function16(
** A global function must exist in order for name resolution to work
** properly.
*/
-SQLITE_API int sqlite3_overload_function(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_overload_function(
+ tdsqlite3 *db,
const char *zName,
int nArg
){
- int rc = SQLITE_OK;
+ int rc;
+ char *zCopy;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || zName==0 || nArg<-2 ){
+ if( !tdsqlite3SafetyCheckOk(db) || zName==0 || nArg<-2 ){
return SQLITE_MISUSE_BKPT;
}
#endif
- sqlite3_mutex_enter(db->mutex);
- if( sqlite3FindFunction(db, zName, nArg, SQLITE_UTF8, 0)==0 ){
- rc = sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8,
- 0, sqlite3InvalidFunction, 0, 0, 0);
- }
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
- return rc;
+ tdsqlite3_mutex_enter(db->mutex);
+ rc = tdsqlite3FindFunction(db, zName, nArg, SQLITE_UTF8, 0)!=0;
+ tdsqlite3_mutex_leave(db->mutex);
+ if( rc ) return SQLITE_OK;
+ zCopy = tdsqlite3_mprintf(zName);
+ if( zCopy==0 ) return SQLITE_NOMEM;
+ return tdsqlite3_create_function_v2(db, zName, nArg, SQLITE_UTF8,
+ zCopy, tdsqlite3InvalidFunction, 0, 0, tdsqlite3_free);
}
#ifndef SQLITE_OMIT_TRACE
@@ -142772,45 +164735,45 @@ SQLITE_API int sqlite3_overload_function(
** SQL statement.
*/
#ifndef SQLITE_OMIT_DEPRECATED
-SQLITE_API void *sqlite3_trace(sqlite3 *db, void(*xTrace)(void*,const char*), void *pArg){
+SQLITE_API void *tdsqlite3_trace(tdsqlite3 *db, void(*xTrace)(void*,const char*), void *pArg){
void *pOld;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
pOld = db->pTraceArg;
db->mTrace = xTrace ? SQLITE_TRACE_LEGACY : 0;
db->xTrace = (int(*)(u32,void*,void*,void*))xTrace;
db->pTraceArg = pArg;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return pOld;
}
#endif /* SQLITE_OMIT_DEPRECATED */
/* Register a trace callback using the version-2 interface.
*/
-SQLITE_API int sqlite3_trace_v2(
- sqlite3 *db, /* Trace this connection */
+SQLITE_API int tdsqlite3_trace_v2(
+ tdsqlite3 *db, /* Trace this connection */
unsigned mTrace, /* Mask of events to be traced */
int(*xTrace)(unsigned,void*,void*,void*), /* Callback to invoke */
void *pArg /* Context */
){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
return SQLITE_MISUSE_BKPT;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
if( mTrace==0 ) xTrace = 0;
if( xTrace==0 ) mTrace = 0;
db->mTrace = mTrace;
db->xTrace = xTrace;
db->pTraceArg = pArg;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
}
@@ -142823,24 +164786,26 @@ SQLITE_API int sqlite3_trace_v2(
** profile is a pointer to a function that is invoked at the conclusion of
** each SQL statement that is run.
*/
-SQLITE_API void *sqlite3_profile(
- sqlite3 *db,
+SQLITE_API void *tdsqlite3_profile(
+ tdsqlite3 *db,
void (*xProfile)(void*,const char*,sqlite_uint64),
void *pArg
){
void *pOld;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
pOld = db->pProfileArg;
db->xProfile = xProfile;
db->pProfileArg = pArg;
- sqlite3_mutex_leave(db->mutex);
+ db->mTrace &= SQLITE_TRACE_NONLEGACY_MASK;
+ if( db->xProfile ) db->mTrace |= SQLITE_TRACE_XPROFILE;
+ tdsqlite3_mutex_leave(db->mutex);
return pOld;
}
#endif /* SQLITE_OMIT_DEPRECATED */
@@ -142851,24 +164816,24 @@ SQLITE_API void *sqlite3_profile(
** If the invoked function returns non-zero, then the commit becomes a
** rollback.
*/
-SQLITE_API void *sqlite3_commit_hook(
- sqlite3 *db, /* Attach the hook to this database */
+SQLITE_API void *tdsqlite3_commit_hook(
+ tdsqlite3 *db, /* Attach the hook to this database */
int (*xCallback)(void*), /* Function to invoke on each commit */
void *pArg /* Argument to the function */
){
void *pOld;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
pOld = db->pCommitArg;
db->xCommitCallback = xCallback;
db->pCommitArg = pArg;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return pOld;
}
@@ -142876,24 +164841,24 @@ SQLITE_API void *sqlite3_commit_hook(
** Register a callback to be invoked each time a row is updated,
** inserted or deleted using this database connection.
*/
-SQLITE_API void *sqlite3_update_hook(
- sqlite3 *db, /* Attach the hook to this database */
+SQLITE_API void *tdsqlite3_update_hook(
+ tdsqlite3 *db, /* Attach the hook to this database */
void (*xCallback)(void*,int,char const *,char const *,sqlite_int64),
void *pArg /* Argument to the function */
){
void *pRet;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
pRet = db->pUpdateArg;
db->xUpdateCallback = xCallback;
db->pUpdateArg = pArg;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return pRet;
}
@@ -142901,24 +164866,24 @@ SQLITE_API void *sqlite3_update_hook(
** Register a callback to be invoked each time a transaction is rolled
** back by this database connection.
*/
-SQLITE_API void *sqlite3_rollback_hook(
- sqlite3 *db, /* Attach the hook to this database */
+SQLITE_API void *tdsqlite3_rollback_hook(
+ tdsqlite3 *db, /* Attach the hook to this database */
void (*xCallback)(void*), /* Callback function */
void *pArg /* Argument to the function */
){
void *pRet;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
pRet = db->pRollbackArg;
db->xRollbackCallback = xCallback;
db->pRollbackArg = pArg;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return pRet;
}
@@ -142927,67 +164892,67 @@ SQLITE_API void *sqlite3_rollback_hook(
** Register a callback to be invoked each time a row is updated,
** inserted or deleted using this database connection.
*/
-SQLITE_API void *sqlite3_preupdate_hook(
- sqlite3 *db, /* Attach the hook to this database */
+SQLITE_API void *tdsqlite3_preupdate_hook(
+ tdsqlite3 *db, /* Attach the hook to this database */
void(*xCallback)( /* Callback function */
- void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64),
+ void*,tdsqlite3*,int,char const*,char const*,tdsqlite3_int64,tdsqlite3_int64),
void *pArg /* First callback argument */
){
void *pRet;
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
pRet = db->pPreUpdateArg;
db->xPreUpdateCallback = xCallback;
db->pPreUpdateArg = pArg;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return pRet;
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
#ifndef SQLITE_OMIT_WAL
/*
-** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint().
-** Invoke sqlite3_wal_checkpoint if the number of frames in the log file
+** The tdsqlite3_wal_hook() callback registered by tdsqlite3_wal_autocheckpoint().
+** Invoke tdsqlite3_wal_checkpoint if the number of frames in the log file
** is greater than sqlite3.pWalArg cast to an integer (the value configured by
** wal_autocheckpoint()).
*/
-SQLITE_PRIVATE int sqlite3WalDefaultHook(
+SQLITE_PRIVATE int tdsqlite3WalDefaultHook(
void *pClientData, /* Argument */
- sqlite3 *db, /* Connection */
+ tdsqlite3 *db, /* Connection */
const char *zDb, /* Database */
int nFrame /* Size of WAL */
){
if( nFrame>=SQLITE_PTR_TO_INT(pClientData) ){
- sqlite3BeginBenignMalloc();
- sqlite3_wal_checkpoint(db, zDb);
- sqlite3EndBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
+ tdsqlite3_wal_checkpoint(db, zDb);
+ tdsqlite3EndBenignMalloc();
}
return SQLITE_OK;
}
#endif /* SQLITE_OMIT_WAL */
/*
-** Configure an sqlite3_wal_hook() callback to automatically checkpoint
+** Configure an tdsqlite3_wal_hook() callback to automatically checkpoint
** a database after committing a transaction if there are nFrame or
** more frames in the log file. Passing zero or a negative value as the
** nFrame parameter disables automatic checkpoints entirely.
**
** The callback registered by this function replaces any existing callback
-** registered using sqlite3_wal_hook(). Likewise, registering a callback
-** using sqlite3_wal_hook() disables the automatic checkpoint mechanism
+** registered using tdsqlite3_wal_hook(). Likewise, registering a callback
+** using tdsqlite3_wal_hook() disables the automatic checkpoint mechanism
** configured by this function.
*/
-SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int nFrame){
+SQLITE_API int tdsqlite3_wal_autocheckpoint(tdsqlite3 *db, int nFrame){
#ifdef SQLITE_OMIT_WAL
UNUSED_PARAMETER(db);
UNUSED_PARAMETER(nFrame);
#else
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
if( nFrame>0 ){
- sqlite3_wal_hook(db, sqlite3WalDefaultHook, SQLITE_INT_TO_PTR(nFrame));
+ tdsqlite3_wal_hook(db, tdsqlite3WalDefaultHook, SQLITE_INT_TO_PTR(nFrame));
}else{
- sqlite3_wal_hook(db, 0, 0);
+ tdsqlite3_wal_hook(db, 0, 0);
}
#endif
return SQLITE_OK;
@@ -142997,24 +164962,24 @@ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int nFrame){
** Register a callback to be invoked each time a transaction is written
** into the write-ahead-log by this database connection.
*/
-SQLITE_API void *sqlite3_wal_hook(
- sqlite3 *db, /* Attach the hook to this db handle */
- int(*xCallback)(void *, sqlite3*, const char*, int),
+SQLITE_API void *tdsqlite3_wal_hook(
+ tdsqlite3 *db, /* Attach the hook to this db handle */
+ int(*xCallback)(void *, tdsqlite3*, const char*, int),
void *pArg /* First argument passed to xCallback() */
){
#ifndef SQLITE_OMIT_WAL
void *pRet;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
pRet = db->pWalArg;
db->xWalCallback = xCallback;
db->pWalArg = pArg;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return pRet;
#else
return 0;
@@ -143024,8 +164989,8 @@ SQLITE_API void *sqlite3_wal_hook(
/*
** Checkpoint database zDb.
*/
-SQLITE_API int sqlite3_wal_checkpoint_v2(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_wal_checkpoint_v2(
+ tdsqlite3 *db, /* Database handle */
const char *zDb, /* Name of attached database (or NULL) */
int eMode, /* SQLITE_CHECKPOINT_* value */
int *pnLog, /* OUT: Size of WAL log in frames */
@@ -143038,7 +165003,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
int iDb = SQLITE_MAX_ATTACHED; /* sqlite3.aDb[] index of db to checkpoint */
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
/* Initialize the output variables to -1 in case an error occurs. */
@@ -143055,20 +165020,27 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
return SQLITE_MISUSE;
}
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
if( zDb && zDb[0] ){
- iDb = sqlite3FindDbName(db, zDb);
+ iDb = tdsqlite3FindDbName(db, zDb);
}
if( iDb<0 ){
rc = SQLITE_ERROR;
- sqlite3ErrorWithMsg(db, SQLITE_ERROR, "unknown database: %s", zDb);
+ tdsqlite3ErrorWithMsg(db, SQLITE_ERROR, "unknown database: %s", zDb);
}else{
db->busyHandler.nBusy = 0;
- rc = sqlite3Checkpoint(db, iDb, eMode, pnLog, pnCkpt);
- sqlite3Error(db, rc);
+ rc = tdsqlite3Checkpoint(db, iDb, eMode, pnLog, pnCkpt);
+ tdsqlite3Error(db, rc);
}
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ rc = tdsqlite3ApiExit(db, rc);
+
+ /* If there are no active statements, clear the interrupt flag at this
+ ** point. */
+ if( db->nVdbeActive==0 ){
+ db->u1.isInterrupted = 0;
+ }
+
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
#endif
}
@@ -143079,10 +165051,10 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
** to contains a zero-length string, all attached databases are
** checkpointed.
*/
-SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){
- /* EVIDENCE-OF: R-41613-20553 The sqlite3_wal_checkpoint(D,X) is equivalent to
- ** sqlite3_wal_checkpoint_v2(D,X,SQLITE_CHECKPOINT_PASSIVE,0,0). */
- return sqlite3_wal_checkpoint_v2(db,zDb,SQLITE_CHECKPOINT_PASSIVE,0,0);
+SQLITE_API int tdsqlite3_wal_checkpoint(tdsqlite3 *db, const char *zDb){
+ /* EVIDENCE-OF: R-41613-20553 The tdsqlite3_wal_checkpoint(D,X) is equivalent to
+ ** tdsqlite3_wal_checkpoint_v2(D,X,SQLITE_CHECKPOINT_PASSIVE,0,0). */
+ return tdsqlite3_wal_checkpoint_v2(db,zDb,SQLITE_CHECKPOINT_PASSIVE,0,0);
}
#ifndef SQLITE_OMIT_WAL
@@ -143103,20 +165075,21 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){
** checkpointed. If an error is encountered it is returned immediately -
** no attempt is made to checkpoint any remaining databases.
**
-** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART.
+** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL, RESTART
+** or TRUNCATE.
*/
-SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3 *db, int iDb, int eMode, int *pnLog, int *pnCkpt){
+SQLITE_PRIVATE int tdsqlite3Checkpoint(tdsqlite3 *db, int iDb, int eMode, int *pnLog, int *pnCkpt){
int rc = SQLITE_OK; /* Return code */
int i; /* Used to iterate through attached dbs */
int bBusy = 0; /* True if SQLITE_BUSY has been encountered */
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
assert( !pnLog || *pnLog==-1 );
assert( !pnCkpt || *pnCkpt==-1 );
for(i=0; i<db->nDb && rc==SQLITE_OK; i++){
if( i==iDb || iDb==SQLITE_MAX_ATTACHED ){
- rc = sqlite3BtreeCheckpoint(db->aDb[i].pBt, eMode, pnLog, pnCkpt);
+ rc = tdsqlite3BtreeCheckpoint(db->aDb[i].pBt, eMode, pnLog, pnCkpt);
pnLog = 0;
pnCkpt = 0;
if( rc==SQLITE_BUSY ){
@@ -143149,7 +165122,7 @@ SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3 *db, int iDb, int eMode, int *pnLog
** 2 0 memory (return 1)
** 3 any memory (return 1)
*/
-SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3 *db){
+SQLITE_PRIVATE int tdsqlite3TempInMemory(const tdsqlite3 *db){
#if SQLITE_TEMP_STORE==1
return ( db->temp_store==2 );
#endif
@@ -143170,26 +165143,26 @@ SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3 *db){
** Return UTF-8 encoded English language explanation of the most recent
** error.
*/
-SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){
+SQLITE_API const char *tdsqlite3_errmsg(tdsqlite3 *db){
const char *z;
if( !db ){
- return sqlite3ErrStr(SQLITE_NOMEM_BKPT);
+ return tdsqlite3ErrStr(SQLITE_NOMEM_BKPT);
}
- if( !sqlite3SafetyCheckSickOrOk(db) ){
- return sqlite3ErrStr(SQLITE_MISUSE_BKPT);
+ if( !tdsqlite3SafetyCheckSickOrOk(db) ){
+ return tdsqlite3ErrStr(SQLITE_MISUSE_BKPT);
}
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
if( db->mallocFailed ){
- z = sqlite3ErrStr(SQLITE_NOMEM_BKPT);
+ z = tdsqlite3ErrStr(SQLITE_NOMEM_BKPT);
}else{
testcase( db->pErr==0 );
- z = (char*)sqlite3_value_text(db->pErr);
+ z = db->errCode ? (char*)tdsqlite3_value_text(db->pErr) : 0;
assert( !db->mallocFailed );
if( z==0 ){
- z = sqlite3ErrStr(db->errCode);
+ z = tdsqlite3ErrStr(db->errCode);
}
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return z;
}
@@ -143198,53 +165171,50 @@ SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){
** Return UTF-16 encoded English language explanation of the most recent
** error.
*/
-SQLITE_API const void *sqlite3_errmsg16(sqlite3 *db){
+SQLITE_API const void *tdsqlite3_errmsg16(tdsqlite3 *db){
static const u16 outOfMem[] = {
'o', 'u', 't', ' ', 'o', 'f', ' ', 'm', 'e', 'm', 'o', 'r', 'y', 0
};
static const u16 misuse[] = {
- 'l', 'i', 'b', 'r', 'a', 'r', 'y', ' ',
- 'r', 'o', 'u', 't', 'i', 'n', 'e', ' ',
- 'c', 'a', 'l', 'l', 'e', 'd', ' ',
- 'o', 'u', 't', ' ',
- 'o', 'f', ' ',
- 's', 'e', 'q', 'u', 'e', 'n', 'c', 'e', 0
+ 'b', 'a', 'd', ' ', 'p', 'a', 'r', 'a', 'm', 'e', 't', 'e', 'r', ' ',
+ 'o', 'r', ' ', 'o', 't', 'h', 'e', 'r', ' ', 'A', 'P', 'I', ' ',
+ 'm', 'i', 's', 'u', 's', 'e', 0
};
const void *z;
if( !db ){
return (void *)outOfMem;
}
- if( !sqlite3SafetyCheckSickOrOk(db) ){
+ if( !tdsqlite3SafetyCheckSickOrOk(db) ){
return (void *)misuse;
}
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
if( db->mallocFailed ){
z = (void *)outOfMem;
}else{
- z = sqlite3_value_text16(db->pErr);
+ z = tdsqlite3_value_text16(db->pErr);
if( z==0 ){
- sqlite3ErrorWithMsg(db, db->errCode, sqlite3ErrStr(db->errCode));
- z = sqlite3_value_text16(db->pErr);
+ tdsqlite3ErrorWithMsg(db, db->errCode, tdsqlite3ErrStr(db->errCode));
+ z = tdsqlite3_value_text16(db->pErr);
}
- /* A malloc() may have failed within the call to sqlite3_value_text16()
+ /* A malloc() may have failed within the call to tdsqlite3_value_text16()
** above. If this is the case, then the db->mallocFailed flag needs to
** be cleared before returning. Do this directly, instead of via
- ** sqlite3ApiExit(), to avoid setting the database handle error message.
+ ** tdsqlite3ApiExit(), to avoid setting the database handle error message.
*/
- sqlite3OomClear(db);
+ tdsqlite3OomClear(db);
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return z;
}
#endif /* SQLITE_OMIT_UTF16 */
/*
** Return the most recent error code generated by an SQLite routine. If NULL is
-** passed to this function, we assume a malloc() failed during sqlite3_open().
+** passed to this function, we assume a malloc() failed during tdsqlite3_open().
*/
-SQLITE_API int sqlite3_errcode(sqlite3 *db){
- if( db && !sqlite3SafetyCheckSickOrOk(db) ){
+SQLITE_API int tdsqlite3_errcode(tdsqlite3 *db){
+ if( db && !tdsqlite3SafetyCheckSickOrOk(db) ){
return SQLITE_MISUSE_BKPT;
}
if( !db || db->mallocFailed ){
@@ -143252,8 +165222,8 @@ SQLITE_API int sqlite3_errcode(sqlite3 *db){
}
return db->errCode & db->errMask;
}
-SQLITE_API int sqlite3_extended_errcode(sqlite3 *db){
- if( db && !sqlite3SafetyCheckSickOrOk(db) ){
+SQLITE_API int tdsqlite3_extended_errcode(tdsqlite3 *db){
+ if( db && !tdsqlite3SafetyCheckSickOrOk(db) ){
return SQLITE_MISUSE_BKPT;
}
if( !db || db->mallocFailed ){
@@ -143261,17 +165231,17 @@ SQLITE_API int sqlite3_extended_errcode(sqlite3 *db){
}
return db->errCode;
}
-SQLITE_API int sqlite3_system_errno(sqlite3 *db){
+SQLITE_API int tdsqlite3_system_errno(tdsqlite3 *db){
return db ? db->iSysErrno : 0;
}
/*
** Return a string that describes the kind of error specified in the
-** argument. For now, this simply calls the internal sqlite3ErrStr()
+** argument. For now, this simply calls the internal tdsqlite3ErrStr()
** function.
*/
-SQLITE_API const char *sqlite3_errstr(int rc){
- return sqlite3ErrStr(rc);
+SQLITE_API const char *tdsqlite3_errstr(int rc){
+ return tdsqlite3ErrStr(rc);
}
/*
@@ -143279,7 +165249,7 @@ SQLITE_API const char *sqlite3_errstr(int rc){
** and the encoding is enc.
*/
static int createCollation(
- sqlite3* db,
+ tdsqlite3* db,
const char *zName,
u8 enc,
void* pCtx,
@@ -143289,7 +165259,7 @@ static int createCollation(
CollSeq *pColl;
int enc2;
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
/* If SQLITE_UTF16 is specified as the encoding type, transform this
** to one of SQLITE_UTF16LE or SQLITE_UTF16BE using the
@@ -143309,23 +165279,23 @@ static int createCollation(
** sequence. If so, and there are active VMs, return busy. If there
** are no active VMs, invalidate any pre-compiled statements.
*/
- pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 0);
+ pColl = tdsqlite3FindCollSeq(db, (u8)enc2, zName, 0);
if( pColl && pColl->xCmp ){
if( db->nVdbeActive ){
- sqlite3ErrorWithMsg(db, SQLITE_BUSY,
+ tdsqlite3ErrorWithMsg(db, SQLITE_BUSY,
"unable to delete/modify collation sequence due to active statements");
return SQLITE_BUSY;
}
- sqlite3ExpirePreparedStatements(db);
+ tdsqlite3ExpirePreparedStatements(db, 0);
/* If collation sequence pColl was created directly by a call to
- ** sqlite3_create_collation, and not generated by synthCollSeq(),
+ ** tdsqlite3_create_collation, and not generated by synthCollSeq(),
** then any copies made by synthCollSeq() need to be invalidated.
** Also, collation destructor - CollSeq.xDel() - function may need
** to be called.
*/
if( (pColl->enc & ~SQLITE_UTF16_ALIGNED)==enc2 ){
- CollSeq *aColl = sqlite3HashFind(&db->aCollSeq, zName);
+ CollSeq *aColl = tdsqlite3HashFind(&db->aCollSeq, zName);
int j;
for(j=0; j<3; j++){
CollSeq *p = &aColl[j];
@@ -143339,13 +165309,13 @@ static int createCollation(
}
}
- pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 1);
+ pColl = tdsqlite3FindCollSeq(db, (u8)enc2, zName, 1);
if( pColl==0 ) return SQLITE_NOMEM_BKPT;
pColl->xCmp = xCompare;
pColl->pUser = pCtx;
pColl->xDel = xDel;
pColl->enc = (u8)(enc2 | (enc & SQLITE_UTF16_ALIGNED));
- sqlite3Error(db, SQLITE_OK);
+ tdsqlite3Error(db, SQLITE_OK);
return SQLITE_OK;
}
@@ -143418,11 +165388,11 @@ static const int aHardLimit[] = {
** It merely prevents new constructs that exceed the limit
** from forming.
*/
-SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){
+SQLITE_API int tdsqlite3_limit(tdsqlite3 *db, int limitId, int newLimit){
int oldLimit;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return -1;
}
@@ -143464,7 +165434,7 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){
/*
** This function is used to parse both URIs and non-URI filenames passed by the
-** user to API functions sqlite3_open() or sqlite3_open_v2(), and for database
+** user to API functions tdsqlite3_open() or tdsqlite3_open_v2(), and for database
** URIs specified as part of ATTACH statements.
**
** The first argument to this function is the name of the VFS to use (or
@@ -143478,19 +165448,19 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){
** If successful, SQLITE_OK is returned. In this case *ppVfs is set to point to
** the VFS that should be used to open the database file. *pzFile is set to
** point to a buffer containing the name of the file to open. It is the
-** responsibility of the caller to eventually call sqlite3_free() to release
+** responsibility of the caller to eventually call tdsqlite3_free() to release
** this buffer.
**
** If an error occurs, then an SQLite error code is returned and *pzErrMsg
** may be set to point to a buffer containing an English language error
** message. It is the responsibility of the caller to eventually release
-** this buffer by calling sqlite3_free().
+** this buffer by calling tdsqlite3_free().
*/
-SQLITE_PRIVATE int sqlite3ParseUri(
+SQLITE_PRIVATE int tdsqlite3ParseUri(
const char *zDefaultVfs, /* VFS to use if no "vfs=xxx" query option */
const char *zUri, /* Nul-terminated URI to parse */
unsigned int *pFlags, /* IN/OUT: SQLITE_OPEN_XXX flags */
- sqlite3_vfs **ppVfs, /* OUT: VFS to use */
+ tdsqlite3_vfs **ppVfs, /* OUT: VFS to use */
char **pzFile, /* OUT: Filename component of URI */
char **pzErrMsg /* OUT: Error message (if rc!=SQLITE_OK) */
){
@@ -143499,12 +165469,12 @@ SQLITE_PRIVATE int sqlite3ParseUri(
const char *zVfs = zDefaultVfs;
char *zFile;
char c;
- int nUri = sqlite3Strlen30(zUri);
+ int nUri = tdsqlite3Strlen30(zUri);
assert( *pzErrMsg==0 );
if( ((flags & SQLITE_OPEN_URI) /* IMP: R-48725-32206 */
- || sqlite3GlobalConfig.bOpenUri) /* IMP: R-51689-46548 */
+ || tdsqlite3GlobalConfig.bOpenUri) /* IMP: R-51689-46548 */
&& nUri>=5 && memcmp(zUri, "file:", 5)==0 /* IMP: R-57884-37496 */
){
char *zOpt;
@@ -143518,7 +165488,7 @@ SQLITE_PRIVATE int sqlite3ParseUri(
flags |= SQLITE_OPEN_URI;
for(iIn=0; iIn<nUri; iIn++) nByte += (zUri[iIn]=='&');
- zFile = sqlite3_malloc64(nByte);
+ zFile = tdsqlite3_malloc64(nByte);
if( !zFile ) return SQLITE_NOMEM_BKPT;
iIn = 5;
@@ -143540,7 +165510,7 @@ SQLITE_PRIVATE int sqlite3ParseUri(
iIn = 7;
while( zUri[iIn] && zUri[iIn]!='/' ) iIn++;
if( iIn!=7 && (iIn!=16 || memcmp("localhost", &zUri[7], 9)) ){
- *pzErrMsg = sqlite3_mprintf("invalid uri authority: %.*s",
+ *pzErrMsg = tdsqlite3_mprintf("invalid uri authority: %.*s",
iIn-7, &zUri[7]);
rc = SQLITE_ERROR;
goto parse_uri_out;
@@ -143562,14 +165532,15 @@ SQLITE_PRIVATE int sqlite3ParseUri(
while( (c = zUri[iIn])!=0 && c!='#' ){
iIn++;
if( c=='%'
- && sqlite3Isxdigit(zUri[iIn])
- && sqlite3Isxdigit(zUri[iIn+1])
+ && tdsqlite3Isxdigit(zUri[iIn])
+ && tdsqlite3Isxdigit(zUri[iIn+1])
){
- int octet = (sqlite3HexToInt(zUri[iIn++]) << 4);
- octet += sqlite3HexToInt(zUri[iIn++]);
+ int octet = (tdsqlite3HexToInt(zUri[iIn++]) << 4);
+ octet += tdsqlite3HexToInt(zUri[iIn++]);
assert( octet>=0 && octet<256 );
if( octet==0 ){
+#ifndef SQLITE_ENABLE_URI_00_ERROR
/* This branch is taken when "%00" appears within the URI. In this
** case we ignore all text in the remainder of the path, name or
** value currently being parsed. So ignore the current character
@@ -143582,6 +165553,12 @@ SQLITE_PRIVATE int sqlite3ParseUri(
iIn++;
}
continue;
+#else
+ /* If ENABLE_URI_00_ERROR is defined, "%00" in a URI is an error. */
+ *pzErrMsg = tdsqlite3_mprintf("unexpected %%00 in uri");
+ rc = SQLITE_ERROR;
+ goto parse_uri_out;
+#endif
}
c = octet;
}else if( eState==1 && (c=='&' || c=='=') ){
@@ -143608,13 +165585,13 @@ SQLITE_PRIVATE int sqlite3ParseUri(
/* Check if there were any options specified that should be interpreted
** here. Options that are interpreted here include "vfs" and those that
- ** correspond to flags that may be passed to the sqlite3_open_v2()
+ ** correspond to flags that may be passed to the tdsqlite3_open_v2()
** method. */
- zOpt = &zFile[sqlite3Strlen30(zFile)+1];
+ zOpt = &zFile[tdsqlite3Strlen30(zFile)+1];
while( zOpt[0] ){
- int nOpt = sqlite3Strlen30(zOpt);
+ int nOpt = tdsqlite3Strlen30(zOpt);
char *zVal = &zOpt[nOpt+1];
- int nVal = sqlite3Strlen30(zVal);
+ int nVal = tdsqlite3Strlen30(zVal);
if( nOpt==3 && memcmp("vfs", zOpt, 3)==0 ){
zVfs = zVal;
@@ -143660,18 +165637,18 @@ SQLITE_PRIVATE int sqlite3ParseUri(
int mode = 0;
for(i=0; aMode[i].z; i++){
const char *z = aMode[i].z;
- if( nVal==sqlite3Strlen30(z) && 0==memcmp(zVal, z, nVal) ){
+ if( nVal==tdsqlite3Strlen30(z) && 0==memcmp(zVal, z, nVal) ){
mode = aMode[i].mode;
break;
}
}
if( mode==0 ){
- *pzErrMsg = sqlite3_mprintf("no such %s mode: %s", zModeType, zVal);
+ *pzErrMsg = tdsqlite3_mprintf("no such %s mode: %s", zModeType, zVal);
rc = SQLITE_ERROR;
goto parse_uri_out;
}
if( (mode & ~SQLITE_OPEN_MEMORY)>limit ){
- *pzErrMsg = sqlite3_mprintf("%s mode not allowed: %s",
+ *pzErrMsg = tdsqlite3_mprintf("%s mode not allowed: %s",
zModeType, zVal);
rc = SQLITE_PERM;
goto parse_uri_out;
@@ -143684,22 +165661,24 @@ SQLITE_PRIVATE int sqlite3ParseUri(
}
}else{
- zFile = sqlite3_malloc64(nUri+2);
+ zFile = tdsqlite3_malloc64(nUri+2);
if( !zFile ) return SQLITE_NOMEM_BKPT;
- memcpy(zFile, zUri, nUri);
+ if( nUri ){
+ memcpy(zFile, zUri, nUri);
+ }
zFile[nUri] = '\0';
zFile[nUri+1] = '\0';
flags &= ~SQLITE_OPEN_URI;
}
- *ppVfs = sqlite3_vfs_find(zVfs);
+ *ppVfs = tdsqlite3_vfs_find(zVfs);
if( *ppVfs==0 ){
- *pzErrMsg = sqlite3_mprintf("no such vfs: %s", zVfs);
+ *pzErrMsg = tdsqlite3_mprintf("no such vfs: %s", zVfs);
rc = SQLITE_ERROR;
}
parse_uri_out:
if( rc!=SQLITE_OK ){
- sqlite3_free(zFile);
+ tdsqlite3_free(zFile);
zFile = 0;
}
*pFlags = flags;
@@ -143707,65 +165686,80 @@ SQLITE_PRIVATE int sqlite3ParseUri(
return rc;
}
+#if defined(SQLITE_HAS_CODEC)
+/*
+** Process URI filename query parameters relevant to the SQLite Encryption
+** Extension. Return true if any of the relevant query parameters are
+** seen and return false if not.
+*/
+SQLITE_PRIVATE int tdsqlite3CodecQueryParameters(
+ tdsqlite3 *db, /* Database connection */
+ const char *zDb, /* Which schema is being created/attached */
+ const char *zUri /* URI filename */
+){
+ const char *zKey;
+ if( (zKey = tdsqlite3_uri_parameter(zUri, "hexkey"))!=0 && zKey[0] ){
+ u8 iByte;
+ int i;
+ char zDecoded[40];
+ for(i=0, iByte=0; i<sizeof(zDecoded)*2 && tdsqlite3Isxdigit(zKey[i]); i++){
+ iByte = (iByte<<4) + tdsqlite3HexToInt(zKey[i]);
+ if( (i&1)!=0 ) zDecoded[i/2] = iByte;
+ }
+ tdsqlite3_key_v2(db, zDb, zDecoded, i/2);
+ return 1;
+ }else if( (zKey = tdsqlite3_uri_parameter(zUri, "key"))!=0 ){
+ tdsqlite3_key_v2(db, zDb, zKey, tdsqlite3Strlen30(zKey));
+ return 1;
+ }else if( (zKey = tdsqlite3_uri_parameter(zUri, "textkey"))!=0 ){
+ tdsqlite3_key_v2(db, zDb, zKey, -1);
+ return 1;
+ }else{
+ return 0;
+ }
+}
+#endif
+
/*
** This routine does the work of opening a database on behalf of
-** sqlite3_open() and sqlite3_open16(). The database filename "zFilename"
+** tdsqlite3_open() and tdsqlite3_open16(). The database filename "zFilename"
** is UTF-8 encoded.
*/
static int openDatabase(
const char *zFilename, /* Database filename UTF-8 encoded */
- sqlite3 **ppDb, /* OUT: Returned database handle */
+ tdsqlite3 **ppDb, /* OUT: Returned database handle */
unsigned int flags, /* Operational flags */
const char *zVfs /* Name of the VFS to use */
){
- sqlite3 *db; /* Store allocated handle here */
+ tdsqlite3 *db; /* Store allocated handle here */
int rc; /* Return code */
int isThreadsafe; /* True for threadsafe connections */
char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */
- char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */
+ char *zErrMsg = 0; /* Error message from tdsqlite3ParseUri() */
#ifdef SQLITE_ENABLE_API_ARMOR
if( ppDb==0 ) return SQLITE_MISUSE_BKPT;
#endif
*ppDb = 0;
#ifndef SQLITE_OMIT_AUTOINIT
- rc = sqlite3_initialize();
+ rc = tdsqlite3_initialize();
if( rc ) return rc;
#endif
- /* Only allow sensible combinations of bits in the flags argument.
- ** Throw an error if any non-sense combination is used. If we
- ** do not block illegal combinations here, it could trigger
- ** assert() statements in deeper layers. Sensible combinations
- ** are:
- **
- ** 1: SQLITE_OPEN_READONLY
- ** 2: SQLITE_OPEN_READWRITE
- ** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
- */
- assert( SQLITE_OPEN_READONLY == 0x01 );
- assert( SQLITE_OPEN_READWRITE == 0x02 );
- assert( SQLITE_OPEN_CREATE == 0x04 );
- testcase( (1<<(flags&7))==0x02 ); /* READONLY */
- testcase( (1<<(flags&7))==0x04 ); /* READWRITE */
- testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */
- if( ((1<<(flags&7)) & 0x46)==0 ){
- return SQLITE_MISUSE_BKPT; /* IMP: R-65497-44594 */
- }
-
- if( sqlite3GlobalConfig.bCoreMutex==0 ){
+ if( tdsqlite3GlobalConfig.bCoreMutex==0 ){
isThreadsafe = 0;
}else if( flags & SQLITE_OPEN_NOMUTEX ){
isThreadsafe = 0;
}else if( flags & SQLITE_OPEN_FULLMUTEX ){
isThreadsafe = 1;
}else{
- isThreadsafe = sqlite3GlobalConfig.bFullMutex;
+ isThreadsafe = tdsqlite3GlobalConfig.bFullMutex;
}
+
if( flags & SQLITE_OPEN_PRIVATECACHE ){
flags &= ~SQLITE_OPEN_SHAREDCACHE;
- }else if( sqlite3GlobalConfig.sharedCacheEnabled ){
+ }else if( tdsqlite3GlobalConfig.sharedCacheEnabled ){
flags |= SQLITE_OPEN_SHAREDCACHE;
}
@@ -143773,7 +165767,7 @@ static int openDatabase(
**
** The SQLITE_OPEN_NOMUTEX and SQLITE_OPEN_FULLMUTEX flags were
** dealt with in the previous code block. Besides these, the only
- ** valid input flags for sqlite3_open_v2() are SQLITE_OPEN_READONLY,
+ ** valid input flags for tdsqlite3_open_v2() are SQLITE_OPEN_READONLY,
** SQLITE_OPEN_READWRITE, SQLITE_OPEN_CREATE, SQLITE_OPEN_SHAREDCACHE,
** SQLITE_OPEN_PRIVATECACHE, and some reserved bits. Silently mask
** off all other flags.
@@ -143793,31 +165787,71 @@ static int openDatabase(
);
/* Allocate the sqlite data structure */
- db = sqlite3MallocZero( sizeof(sqlite3) );
+ db = tdsqlite3MallocZero( sizeof(tdsqlite3) );
if( db==0 ) goto opendb_out;
- if( isThreadsafe ){
- db->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE);
+ if( isThreadsafe
+#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS
+ || tdsqlite3GlobalConfig.bCoreMutex
+#endif
+ ){
+ db->mutex = tdsqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE);
if( db->mutex==0 ){
- sqlite3_free(db);
+ tdsqlite3_free(db);
db = 0;
goto opendb_out;
}
+ if( isThreadsafe==0 ){
+ tdsqlite3MutexWarnOnContention(db->mutex);
+ }
}
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
db->errMask = 0xff;
db->nDb = 2;
db->magic = SQLITE_MAGIC_BUSY;
db->aDb = db->aDbStatic;
+ db->lookaside.bDisable = 1;
+ db->lookaside.sz = 0;
assert( sizeof(db->aLimit)==sizeof(aHardLimit) );
memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit));
db->aLimit[SQLITE_LIMIT_WORKER_THREADS] = SQLITE_DEFAULT_WORKER_THREADS;
db->autoCommit = 1;
db->nextAutovac = -1;
- db->szMmap = sqlite3GlobalConfig.szMmap;
+ db->szMmap = tdsqlite3GlobalConfig.szMmap;
db->nextPagesize = 0;
db->nMaxSorterMmap = 0x7FFFFFFF;
- db->flags |= SQLITE_ShortColNames | SQLITE_EnableTrigger | SQLITE_CacheSpill
+ db->flags |= SQLITE_ShortColNames
+ | SQLITE_EnableTrigger
+ | SQLITE_EnableView
+ | SQLITE_CacheSpill
+#if !defined(SQLITE_TRUSTED_SCHEMA) || SQLITE_TRUSTED_SCHEMA+0!=0
+ | SQLITE_TrustedSchema
+#endif
+/* The SQLITE_DQS compile-time option determines the default settings
+** for SQLITE_DBCONFIG_DQS_DDL and SQLITE_DBCONFIG_DQS_DML.
+**
+** SQLITE_DQS SQLITE_DBCONFIG_DQS_DDL SQLITE_DBCONFIG_DQS_DML
+** ---------- ----------------------- -----------------------
+** undefined on on
+** 3 on on
+** 2 on off
+** 1 off on
+** 0 off off
+**
+** Legacy behavior is 3 (double-quoted string literals are allowed anywhere)
+** and so that is the default. But developers are encouranged to use
+** -DSQLITE_DQS=0 (best) or -DSQLITE_DQS=1 (second choice) if possible.
+*/
+#if !defined(SQLITE_DQS)
+# define SQLITE_DQS 3
+#endif
+#if (SQLITE_DQS&1)==1
+ | SQLITE_DqsDML
+#endif
+#if (SQLITE_DQS&2)==2
+ | SQLITE_DqsDDL
+#endif
+
#if !defined(SQLITE_DEFAULT_AUTOMATIC_INDEX) || SQLITE_DEFAULT_AUTOMATIC_INDEX
| SQLITE_AutoIndex
#endif
@@ -143845,10 +165879,16 @@ static int openDatabase(
#if defined(SQLITE_ENABLE_FTS3_TOKENIZER)
| SQLITE_Fts3Tokenizer
#endif
+#if defined(SQLITE_ENABLE_QPSG)
+ | SQLITE_EnableQPSG
+#endif
+#if defined(SQLITE_DEFAULT_DEFENSIVE)
+ | SQLITE_Defensive
+#endif
;
- sqlite3HashInit(&db->aCollSeq);
+ tdsqlite3HashInit(&db->aCollSeq);
#ifndef SQLITE_OMIT_VIRTUALTABLE
- sqlite3HashInit(&db->aModule);
+ tdsqlite3HashInit(&db->aModule);
#endif
/* Add the default collation sequence BINARY. BINARY works for both UTF-8
@@ -143858,45 +165898,66 @@ static int openDatabase(
** EVIDENCE-OF: R-52786-44878 SQLite defines three built-in collating
** functions:
*/
- createCollation(db, sqlite3StrBINARY, SQLITE_UTF8, 0, binCollFunc, 0);
- createCollation(db, sqlite3StrBINARY, SQLITE_UTF16BE, 0, binCollFunc, 0);
- createCollation(db, sqlite3StrBINARY, SQLITE_UTF16LE, 0, binCollFunc, 0);
+ createCollation(db, tdsqlite3StrBINARY, SQLITE_UTF8, 0, binCollFunc, 0);
+ createCollation(db, tdsqlite3StrBINARY, SQLITE_UTF16BE, 0, binCollFunc, 0);
+ createCollation(db, tdsqlite3StrBINARY, SQLITE_UTF16LE, 0, binCollFunc, 0);
createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);
- createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0);
+ createCollation(db, "RTRIM", SQLITE_UTF8, 0, rtrimCollFunc, 0);
if( db->mallocFailed ){
goto opendb_out;
}
/* EVIDENCE-OF: R-08308-17224 The default collating function for all
** strings is BINARY.
*/
- db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, sqlite3StrBINARY, 0);
+ db->pDfltColl = tdsqlite3FindCollSeq(db, SQLITE_UTF8, tdsqlite3StrBINARY, 0);
assert( db->pDfltColl!=0 );
- /* Parse the filename/URI argument. */
+ /* Parse the filename/URI argument
+ **
+ ** Only allow sensible combinations of bits in the flags argument.
+ ** Throw an error if any non-sense combination is used. If we
+ ** do not block illegal combinations here, it could trigger
+ ** assert() statements in deeper layers. Sensible combinations
+ ** are:
+ **
+ ** 1: SQLITE_OPEN_READONLY
+ ** 2: SQLITE_OPEN_READWRITE
+ ** 6: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE
+ */
db->openFlags = flags;
- rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg);
+ assert( SQLITE_OPEN_READONLY == 0x01 );
+ assert( SQLITE_OPEN_READWRITE == 0x02 );
+ assert( SQLITE_OPEN_CREATE == 0x04 );
+ testcase( (1<<(flags&7))==0x02 ); /* READONLY */
+ testcase( (1<<(flags&7))==0x04 ); /* READWRITE */
+ testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */
+ if( ((1<<(flags&7)) & 0x46)==0 ){
+ rc = SQLITE_MISUSE_BKPT; /* IMP: R-65497-44594 */
+ }else{
+ rc = tdsqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg);
+ }
if( rc!=SQLITE_OK ){
- if( rc==SQLITE_NOMEM ) sqlite3OomFault(db);
- sqlite3ErrorWithMsg(db, rc, zErrMsg ? "%s" : 0, zErrMsg);
- sqlite3_free(zErrMsg);
+ if( rc==SQLITE_NOMEM ) tdsqlite3OomFault(db);
+ tdsqlite3ErrorWithMsg(db, rc, zErrMsg ? "%s" : 0, zErrMsg);
+ tdsqlite3_free(zErrMsg);
goto opendb_out;
}
/* Open the backend database driver */
- rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0,
+ rc = tdsqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0,
flags | SQLITE_OPEN_MAIN_DB);
if( rc!=SQLITE_OK ){
if( rc==SQLITE_IOERR_NOMEM ){
rc = SQLITE_NOMEM_BKPT;
}
- sqlite3Error(db, rc);
+ tdsqlite3Error(db, rc);
goto opendb_out;
}
- sqlite3BtreeEnter(db->aDb[0].pBt);
- db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt);
+ tdsqlite3BtreeEnter(db->aDb[0].pBt);
+ db->aDb[0].pSchema = tdsqlite3SchemaGet(db, db->aDb[0].pBt);
if( !db->mallocFailed ) ENC(db) = SCHEMA_ENC(db);
- sqlite3BtreeLeave(db->aDb[0].pBt);
- db->aDb[1].pSchema = sqlite3SchemaGet(db, 0);
+ tdsqlite3BtreeLeave(db->aDb[0].pBt);
+ db->aDb[1].pSchema = tdsqlite3SchemaGet(db, 0);
/* The default safety_level for the main database is FULL; for the temp
** database it is OFF. This matches the pager layer defaults.
@@ -143915,25 +165976,25 @@ static int openDatabase(
** database schema yet. This is delayed until the first time the database
** is accessed.
*/
- sqlite3Error(db, SQLITE_OK);
- sqlite3RegisterPerConnectionBuiltinFunctions(db);
- rc = sqlite3_errcode(db);
+ tdsqlite3Error(db, SQLITE_OK);
+ tdsqlite3RegisterPerConnectionBuiltinFunctions(db);
+ rc = tdsqlite3_errcode(db);
#ifdef SQLITE_ENABLE_FTS5
/* Register any built-in FTS5 module before loading the automatic
** extensions. This allows automatic extensions to register FTS5
** tokenizers and auxiliary functions. */
if( !db->mallocFailed && rc==SQLITE_OK ){
- rc = sqlite3Fts5Init(db);
+ rc = tdsqlite3Fts5Init(db);
}
#endif
/* Load automatic extensions - extensions that have been registered
- ** using the sqlite3_automatic_extension() API.
+ ** using the tdsqlite3_automatic_extension() API.
*/
if( rc==SQLITE_OK ){
- sqlite3AutoLoadExtensions(db);
- rc = sqlite3_errcode(db);
+ tdsqlite3AutoLoadExtensions(db);
+ rc = tdsqlite3_errcode(db);
if( rc!=SQLITE_OK ){
goto opendb_out;
}
@@ -143941,120 +166002,135 @@ static int openDatabase(
#ifdef SQLITE_ENABLE_FTS1
if( !db->mallocFailed ){
- extern int sqlite3Fts1Init(sqlite3*);
- rc = sqlite3Fts1Init(db);
+ extern int tdsqlite3Fts1Init(tdsqlite3*);
+ rc = tdsqlite3Fts1Init(db);
}
#endif
#ifdef SQLITE_ENABLE_FTS2
if( !db->mallocFailed && rc==SQLITE_OK ){
- extern int sqlite3Fts2Init(sqlite3*);
- rc = sqlite3Fts2Init(db);
+ extern int tdsqlite3Fts2Init(tdsqlite3*);
+ rc = tdsqlite3Fts2Init(db);
}
#endif
#ifdef SQLITE_ENABLE_FTS3 /* automatically defined by SQLITE_ENABLE_FTS4 */
if( !db->mallocFailed && rc==SQLITE_OK ){
- rc = sqlite3Fts3Init(db);
+ rc = tdsqlite3Fts3Init(db);
}
#endif
-#ifdef SQLITE_ENABLE_ICU
+#if defined(SQLITE_ENABLE_ICU) || defined(SQLITE_ENABLE_ICU_COLLATIONS)
if( !db->mallocFailed && rc==SQLITE_OK ){
- rc = sqlite3IcuInit(db);
+ rc = tdsqlite3IcuInit(db);
}
#endif
#ifdef SQLITE_ENABLE_RTREE
if( !db->mallocFailed && rc==SQLITE_OK){
- rc = sqlite3RtreeInit(db);
+ rc = tdsqlite3RtreeInit(db);
+ }
+#endif
+
+#ifdef SQLITE_ENABLE_DBPAGE_VTAB
+ if( !db->mallocFailed && rc==SQLITE_OK){
+ rc = tdsqlite3DbpageRegister(db);
}
#endif
#ifdef SQLITE_ENABLE_DBSTAT_VTAB
if( !db->mallocFailed && rc==SQLITE_OK){
- rc = sqlite3DbstatRegister(db);
+ rc = tdsqlite3DbstatRegister(db);
}
#endif
#ifdef SQLITE_ENABLE_JSON1
if( !db->mallocFailed && rc==SQLITE_OK){
- rc = sqlite3Json1Init(db);
+ rc = tdsqlite3Json1Init(db);
+ }
+#endif
+
+#ifdef SQLITE_ENABLE_STMTVTAB
+ if( !db->mallocFailed && rc==SQLITE_OK){
+ rc = tdsqlite3StmtVtabInit(db);
+ }
+#endif
+
+#ifdef SQLCIPHER_EXT
+ if( !db->mallocFailed && rc==SQLITE_OK ){
+ extern int sqlcipherVtabInit(tdsqlite3 *);
+ rc = sqlcipherVtabInit(db);
}
#endif
+#ifdef SQLITE_ENABLE_INTERNAL_FUNCTIONS
+ /* Testing use only!!! The -DSQLITE_ENABLE_INTERNAL_FUNCTIONS=1 compile-time
+ ** option gives access to internal functions by default.
+ ** Testing use only!!! */
+ db->mDbFlags |= DBFLAG_InternalFunc;
+#endif
+
/* -DSQLITE_DEFAULT_LOCKING_MODE=1 makes EXCLUSIVE the default locking
** mode. -DSQLITE_DEFAULT_LOCKING_MODE=0 make NORMAL the default locking
** mode. Doing nothing at all also makes NORMAL the default.
*/
#ifdef SQLITE_DEFAULT_LOCKING_MODE
db->dfltLockMode = SQLITE_DEFAULT_LOCKING_MODE;
- sqlite3PagerLockingMode(sqlite3BtreePager(db->aDb[0].pBt),
+ tdsqlite3PagerLockingMode(tdsqlite3BtreePager(db->aDb[0].pBt),
SQLITE_DEFAULT_LOCKING_MODE);
#endif
- if( rc ) sqlite3Error(db, rc);
+ if( rc ) tdsqlite3Error(db, rc);
/* Enable the lookaside-malloc subsystem */
- setupLookaside(db, 0, sqlite3GlobalConfig.szLookaside,
- sqlite3GlobalConfig.nLookaside);
+ setupLookaside(db, 0, tdsqlite3GlobalConfig.szLookaside,
+ tdsqlite3GlobalConfig.nLookaside);
- sqlite3_wal_autocheckpoint(db, SQLITE_DEFAULT_WAL_AUTOCHECKPOINT);
+ tdsqlite3_wal_autocheckpoint(db, SQLITE_DEFAULT_WAL_AUTOCHECKPOINT);
opendb_out:
if( db ){
assert( db->mutex!=0 || isThreadsafe==0
- || sqlite3GlobalConfig.bFullMutex==0 );
- sqlite3_mutex_leave(db->mutex);
+ || tdsqlite3GlobalConfig.bFullMutex==0 );
+ tdsqlite3_mutex_leave(db->mutex);
}
- rc = sqlite3_errcode(db);
+ rc = tdsqlite3_errcode(db);
assert( db!=0 || rc==SQLITE_NOMEM );
if( rc==SQLITE_NOMEM ){
- sqlite3_close(db);
+ tdsqlite3_close(db);
db = 0;
}else if( rc!=SQLITE_OK ){
db->magic = SQLITE_MAGIC_SICK;
}
*ppDb = db;
#ifdef SQLITE_ENABLE_SQLLOG
- if( sqlite3GlobalConfig.xSqllog ){
+ if( tdsqlite3GlobalConfig.xSqllog ){
/* Opening a db handle. Fourth parameter is passed 0. */
- void *pArg = sqlite3GlobalConfig.pSqllogArg;
- sqlite3GlobalConfig.xSqllog(pArg, db, zFilename, 0);
+ void *pArg = tdsqlite3GlobalConfig.pSqllogArg;
+ tdsqlite3GlobalConfig.xSqllog(pArg, db, zFilename, 0);
}
#endif
#if defined(SQLITE_HAS_CODEC)
- if( rc==SQLITE_OK ){
- const char *zHexKey = sqlite3_uri_parameter(zOpen, "hexkey");
- if( zHexKey && zHexKey[0] ){
- u8 iByte;
- int i;
- char zKey[40];
- for(i=0, iByte=0; i<sizeof(zKey)*2 && sqlite3Isxdigit(zHexKey[i]); i++){
- iByte = (iByte<<4) + sqlite3HexToInt(zHexKey[i]);
- if( (i&1)!=0 ) zKey[i/2] = iByte;
- }
- sqlite3_key_v2(db, 0, zKey, i/2);
- }
- }
+ if( rc==SQLITE_OK ) tdsqlite3CodecQueryParameters(db, 0, zOpen);
#endif
- sqlite3_free(zOpen);
+ tdsqlite3_free(zOpen);
return rc & 0xff;
}
+
/*
** Open a new database handle.
*/
-SQLITE_API int sqlite3_open(
+SQLITE_API int tdsqlite3_open(
const char *zFilename,
- sqlite3 **ppDb
+ tdsqlite3 **ppDb
){
return openDatabase(zFilename, ppDb,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0);
}
-SQLITE_API int sqlite3_open_v2(
+SQLITE_API int tdsqlite3_open_v2(
const char *filename, /* Database filename (UTF-8) */
- sqlite3 **ppDb, /* OUT: SQLite db handle */
+ tdsqlite3 **ppDb, /* OUT: SQLite db handle */
int flags, /* Flags */
const char *zVfs /* Name of VFS module to use */
){
@@ -144065,12 +166141,12 @@ SQLITE_API int sqlite3_open_v2(
/*
** Open a new database handle.
*/
-SQLITE_API int sqlite3_open16(
+SQLITE_API int tdsqlite3_open16(
const void *zFilename,
- sqlite3 **ppDb
+ tdsqlite3 **ppDb
){
char const *zFilename8; /* zFilename encoded in UTF-8 instead of UTF-16 */
- sqlite3_value *pVal;
+ tdsqlite3_value *pVal;
int rc;
#ifdef SQLITE_ENABLE_API_ARMOR
@@ -144078,13 +166154,13 @@ SQLITE_API int sqlite3_open16(
#endif
*ppDb = 0;
#ifndef SQLITE_OMIT_AUTOINIT
- rc = sqlite3_initialize();
+ rc = tdsqlite3_initialize();
if( rc ) return rc;
#endif
if( zFilename==0 ) zFilename = "\000\000";
- pVal = sqlite3ValueNew(0);
- sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC);
- zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8);
+ pVal = tdsqlite3ValueNew(0);
+ tdsqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC);
+ zFilename8 = tdsqlite3ValueText(pVal, SQLITE_UTF8);
if( zFilename8 ){
rc = openDatabase(zFilename8, ppDb,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0);
@@ -144095,7 +166171,7 @@ SQLITE_API int sqlite3_open16(
}else{
rc = SQLITE_NOMEM_BKPT;
}
- sqlite3ValueFree(pVal);
+ tdsqlite3ValueFree(pVal);
return rc & 0xff;
}
@@ -144104,21 +166180,21 @@ SQLITE_API int sqlite3_open16(
/*
** Register a new collation sequence with the database handle db.
*/
-SQLITE_API int sqlite3_create_collation(
- sqlite3* db,
+SQLITE_API int tdsqlite3_create_collation(
+ tdsqlite3* db,
const char *zName,
int enc,
void* pCtx,
int(*xCompare)(void*,int,const void*,int,const void*)
){
- return sqlite3_create_collation_v2(db, zName, enc, pCtx, xCompare, 0);
+ return tdsqlite3_create_collation_v2(db, zName, enc, pCtx, xCompare, 0);
}
/*
** Register a new collation sequence with the database handle db.
*/
-SQLITE_API int sqlite3_create_collation_v2(
- sqlite3* db,
+SQLITE_API int tdsqlite3_create_collation_v2(
+ tdsqlite3* db,
const char *zName,
int enc,
void* pCtx,
@@ -144128,13 +166204,13 @@ SQLITE_API int sqlite3_create_collation_v2(
int rc;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
assert( !db->mallocFailed );
rc = createCollation(db, zName, (u8)enc, pCtx, xCompare, xDel);
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -144142,8 +166218,8 @@ SQLITE_API int sqlite3_create_collation_v2(
/*
** Register a new collation sequence with the database handle db.
*/
-SQLITE_API int sqlite3_create_collation16(
- sqlite3* db,
+SQLITE_API int tdsqlite3_create_collation16(
+ tdsqlite3* db,
const void *zName,
int enc,
void* pCtx,
@@ -144153,17 +166229,17 @@ SQLITE_API int sqlite3_create_collation16(
char *zName8;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
assert( !db->mallocFailed );
- zName8 = sqlite3Utf16to8(db, zName, -1, SQLITE_UTF16NATIVE);
+ zName8 = tdsqlite3Utf16to8(db, zName, -1, SQLITE_UTF16NATIVE);
if( zName8 ){
rc = createCollation(db, zName8, (u8)enc, pCtx, xCompare, 0);
- sqlite3DbFree(db, zName8);
+ tdsqlite3DbFree(db, zName8);
}
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
#endif /* SQLITE_OMIT_UTF16 */
@@ -144172,19 +166248,19 @@ SQLITE_API int sqlite3_create_collation16(
** Register a collation sequence factory callback with the database handle
** db. Replace any previously installed collation sequence factory.
*/
-SQLITE_API int sqlite3_collation_needed(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_collation_needed(
+ tdsqlite3 *db,
void *pCollNeededArg,
- void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*)
+ void(*xCollNeeded)(void*,tdsqlite3*,int eTextRep,const char*)
){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
db->xCollNeeded = xCollNeeded;
db->xCollNeeded16 = 0;
db->pCollNeededArg = pCollNeededArg;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
}
@@ -144193,19 +166269,19 @@ SQLITE_API int sqlite3_collation_needed(
** Register a collation sequence factory callback with the database handle
** db. Replace any previously installed collation sequence factory.
*/
-SQLITE_API int sqlite3_collation_needed16(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_collation_needed16(
+ tdsqlite3 *db,
void *pCollNeededArg,
- void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*)
+ void(*xCollNeeded16)(void*,tdsqlite3*,int eTextRep,const void*)
){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
db->xCollNeeded = 0;
db->xCollNeeded16 = xCollNeeded16;
db->pCollNeededArg = pCollNeededArg;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
}
#endif /* SQLITE_OMIT_UTF16 */
@@ -144215,7 +166291,7 @@ SQLITE_API int sqlite3_collation_needed16(
** This function is now an anachronism. It used to be used to recover from a
** malloc() failure, but SQLite now does this automatically.
*/
-SQLITE_API int sqlite3_global_recover(void){
+SQLITE_API int tdsqlite3_global_recover(void){
return SQLITE_OK;
}
#endif
@@ -144226,9 +166302,9 @@ SQLITE_API int sqlite3_global_recover(void){
** by default. Autocommit is disabled by a BEGIN statement and reenabled
** by the next COMMIT or ROLLBACK.
*/
-SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){
+SQLITE_API int tdsqlite3_get_autocommit(tdsqlite3 *db){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
@@ -144244,34 +166320,40 @@ SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){
** 1. Serve as a convenient place to set a breakpoint in a debugger
** to detect when version error conditions occurs.
**
-** 2. Invoke sqlite3_log() to provide the source code location where
+** 2. Invoke tdsqlite3_log() to provide the source code location where
** a low-level error is first detected.
*/
-static int reportError(int iErr, int lineno, const char *zType){
- sqlite3_log(iErr, "%s at line %d of [%.10s]",
- zType, lineno, 20+sqlite3_sourceid());
+SQLITE_PRIVATE int tdsqlite3ReportError(int iErr, int lineno, const char *zType){
+ tdsqlite3_log(iErr, "%s at line %d of [%.10s]",
+ zType, lineno, 20+tdsqlite3_sourceid());
return iErr;
}
-SQLITE_PRIVATE int sqlite3CorruptError(int lineno){
- testcase( sqlite3GlobalConfig.xLog!=0 );
- return reportError(SQLITE_CORRUPT, lineno, "database corruption");
+SQLITE_PRIVATE int tdsqlite3CorruptError(int lineno){
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ return tdsqlite3ReportError(SQLITE_CORRUPT, lineno, "database corruption");
}
-SQLITE_PRIVATE int sqlite3MisuseError(int lineno){
- testcase( sqlite3GlobalConfig.xLog!=0 );
- return reportError(SQLITE_MISUSE, lineno, "misuse");
+SQLITE_PRIVATE int tdsqlite3MisuseError(int lineno){
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ return tdsqlite3ReportError(SQLITE_MISUSE, lineno, "misuse");
}
-SQLITE_PRIVATE int sqlite3CantopenError(int lineno){
- testcase( sqlite3GlobalConfig.xLog!=0 );
- return reportError(SQLITE_CANTOPEN, lineno, "cannot open file");
+SQLITE_PRIVATE int tdsqlite3CantopenError(int lineno){
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ return tdsqlite3ReportError(SQLITE_CANTOPEN, lineno, "cannot open file");
}
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3NomemError(int lineno){
- testcase( sqlite3GlobalConfig.xLog!=0 );
- return reportError(SQLITE_NOMEM, lineno, "OOM");
+SQLITE_PRIVATE int tdsqlite3CorruptPgnoError(int lineno, Pgno pgno){
+ char zMsg[100];
+ tdsqlite3_snprintf(sizeof(zMsg), zMsg, "database corruption page %d", pgno);
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ return tdsqlite3ReportError(SQLITE_CORRUPT, lineno, zMsg);
}
-SQLITE_PRIVATE int sqlite3IoerrnomemError(int lineno){
- testcase( sqlite3GlobalConfig.xLog!=0 );
- return reportError(SQLITE_IOERR_NOMEM, lineno, "I/O OOM error");
+SQLITE_PRIVATE int tdsqlite3NomemError(int lineno){
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ return tdsqlite3ReportError(SQLITE_NOMEM, lineno, "OOM");
+}
+SQLITE_PRIVATE int tdsqlite3IoerrnomemError(int lineno){
+ testcase( tdsqlite3GlobalConfig.xLog!=0 );
+ return tdsqlite3ReportError(SQLITE_IOERR_NOMEM, lineno, "I/O OOM error");
}
#endif
@@ -144283,7 +166365,7 @@ SQLITE_PRIVATE int sqlite3IoerrnomemError(int lineno){
** SQLite no longer uses thread-specific data so this routine is now a
** no-op. It is retained for historical compatibility.
*/
-SQLITE_API void sqlite3_thread_cleanup(void){
+SQLITE_API void tdsqlite3_thread_cleanup(void){
}
#endif
@@ -144291,8 +166373,8 @@ SQLITE_API void sqlite3_thread_cleanup(void){
** Return meta information about a specific column of a database table.
** See comment in sqlite3.h (sqlite.h.in) for details.
*/
-SQLITE_API int sqlite3_table_column_metadata(
- sqlite3 *db, /* Connection handle */
+SQLITE_API int tdsqlite3_table_column_metadata(
+ tdsqlite3 *db, /* Connection handle */
const char *zDbName, /* Database name or NULL */
const char *zTableName, /* Table name */
const char *zColumnName, /* Column name */
@@ -144315,21 +166397,21 @@ SQLITE_API int sqlite3_table_column_metadata(
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) || zTableName==0 ){
+ if( !tdsqlite3SafetyCheckOk(db) || zTableName==0 ){
return SQLITE_MISUSE_BKPT;
}
#endif
/* Ensure the database schema has been loaded */
- sqlite3_mutex_enter(db->mutex);
- sqlite3BtreeEnterAll(db);
- rc = sqlite3Init(db, &zErrMsg);
+ tdsqlite3_mutex_enter(db->mutex);
+ tdsqlite3BtreeEnterAll(db);
+ rc = tdsqlite3Init(db, &zErrMsg);
if( SQLITE_OK!=rc ){
goto error_out;
}
/* Locate the table in question */
- pTab = sqlite3FindTable(db, zTableName, zDbName);
+ pTab = tdsqlite3FindTable(db, zTableName, zDbName);
if( !pTab || pTab->pSelect ){
pTab = 0;
goto error_out;
@@ -144341,12 +166423,12 @@ SQLITE_API int sqlite3_table_column_metadata(
}else{
for(iCol=0; iCol<pTab->nCol; iCol++){
pCol = &pTab->aCol[iCol];
- if( 0==sqlite3StrICmp(pCol->zName, zColumnName) ){
+ if( 0==tdsqlite3StrICmp(pCol->zName, zColumnName) ){
break;
}
}
if( iCol==pTab->nCol ){
- if( HasRowid(pTab) && sqlite3IsRowid(zColumnName) ){
+ if( HasRowid(pTab) && tdsqlite3IsRowid(zColumnName) ){
iCol = pTab->iPKey;
pCol = iCol>=0 ? &pTab->aCol[iCol] : 0;
}else{
@@ -144367,7 +166449,7 @@ SQLITE_API int sqlite3_table_column_metadata(
** explicitly declared column. Copy meta information from *pCol.
*/
if( pCol ){
- zDataType = sqlite3ColumnType(pCol,0);
+ zDataType = tdsqlite3ColumnType(pCol,0);
zCollSeq = pCol->zColl;
notnull = pCol->notNull!=0;
primarykey = (pCol->colFlags & COLFLAG_PRIMKEY)!=0;
@@ -144377,11 +166459,11 @@ SQLITE_API int sqlite3_table_column_metadata(
primarykey = 1;
}
if( !zCollSeq ){
- zCollSeq = sqlite3StrBINARY;
+ zCollSeq = tdsqlite3StrBINARY;
}
error_out:
- sqlite3BtreeLeaveAll(db);
+ tdsqlite3BtreeLeaveAll(db);
/* Whether the function call succeeded or failed, set the output parameters
** to whatever their local counterparts contain. If an error did occur,
@@ -144394,93 +166476,94 @@ error_out:
if( pAutoinc ) *pAutoinc = autoinc;
if( SQLITE_OK==rc && !pTab ){
- sqlite3DbFree(db, zErrMsg);
- zErrMsg = sqlite3MPrintf(db, "no such table column: %s.%s", zTableName,
+ tdsqlite3DbFree(db, zErrMsg);
+ zErrMsg = tdsqlite3MPrintf(db, "no such table column: %s.%s", zTableName,
zColumnName);
rc = SQLITE_ERROR;
}
- sqlite3ErrorWithMsg(db, rc, (zErrMsg?"%s":0), zErrMsg);
- sqlite3DbFree(db, zErrMsg);
- rc = sqlite3ApiExit(db, rc);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3ErrorWithMsg(db, rc, (zErrMsg?"%s":0), zErrMsg);
+ tdsqlite3DbFree(db, zErrMsg);
+ rc = tdsqlite3ApiExit(db, rc);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
/*
** Sleep for a little while. Return the amount of time slept.
*/
-SQLITE_API int sqlite3_sleep(int ms){
- sqlite3_vfs *pVfs;
+SQLITE_API int tdsqlite3_sleep(int ms){
+ tdsqlite3_vfs *pVfs;
int rc;
- pVfs = sqlite3_vfs_find(0);
+ pVfs = tdsqlite3_vfs_find(0);
if( pVfs==0 ) return 0;
/* This function works in milliseconds, but the underlying OsSleep()
** API uses microseconds. Hence the 1000's.
*/
- rc = (sqlite3OsSleep(pVfs, 1000*ms)/1000);
+ rc = (tdsqlite3OsSleep(pVfs, 1000*ms)/1000);
return rc;
}
/*
** Enable or disable the extended result codes.
*/
-SQLITE_API int sqlite3_extended_result_codes(sqlite3 *db, int onoff){
+SQLITE_API int tdsqlite3_extended_result_codes(tdsqlite3 *db, int onoff){
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
db->errMask = onoff ? 0xffffffff : 0xff;
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return SQLITE_OK;
}
/*
** Invoke the xFileControl method on a particular database.
*/
-SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, void *pArg){
+SQLITE_API int tdsqlite3_file_control(tdsqlite3 *db, const char *zDbName, int op, void *pArg){
int rc = SQLITE_ERROR;
Btree *pBtree;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
+ if( !tdsqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
#endif
- sqlite3_mutex_enter(db->mutex);
- pBtree = sqlite3DbNameToBtree(db, zDbName);
+ tdsqlite3_mutex_enter(db->mutex);
+ pBtree = tdsqlite3DbNameToBtree(db, zDbName);
if( pBtree ){
Pager *pPager;
- sqlite3_file *fd;
- sqlite3BtreeEnter(pBtree);
- pPager = sqlite3BtreePager(pBtree);
+ tdsqlite3_file *fd;
+ tdsqlite3BtreeEnter(pBtree);
+ pPager = tdsqlite3BtreePager(pBtree);
assert( pPager!=0 );
- fd = sqlite3PagerFile(pPager);
+ fd = tdsqlite3PagerFile(pPager);
assert( fd!=0 );
if( op==SQLITE_FCNTL_FILE_POINTER ){
- *(sqlite3_file**)pArg = fd;
+ *(tdsqlite3_file**)pArg = fd;
rc = SQLITE_OK;
}else if( op==SQLITE_FCNTL_VFS_POINTER ){
- *(sqlite3_vfs**)pArg = sqlite3PagerVfs(pPager);
+ *(tdsqlite3_vfs**)pArg = tdsqlite3PagerVfs(pPager);
rc = SQLITE_OK;
}else if( op==SQLITE_FCNTL_JOURNAL_POINTER ){
- *(sqlite3_file**)pArg = sqlite3PagerJrnlFile(pPager);
+ *(tdsqlite3_file**)pArg = tdsqlite3PagerJrnlFile(pPager);
+ rc = SQLITE_OK;
+ }else if( op==SQLITE_FCNTL_DATA_VERSION ){
+ *(unsigned int*)pArg = tdsqlite3PagerDataVersion(pPager);
rc = SQLITE_OK;
- }else if( fd->pMethods ){
- rc = sqlite3OsFileControl(fd, op, pArg);
}else{
- rc = SQLITE_NOTFOUND;
+ rc = tdsqlite3OsFileControl(fd, op, pArg);
}
- sqlite3BtreeLeave(pBtree);
+ tdsqlite3BtreeLeave(pBtree);
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
/*
** Interface to the testing logic.
*/
-SQLITE_API int sqlite3_test_control(int op, ...){
+SQLITE_API int tdsqlite3_test_control(int op, ...){
int rc = 0;
-#ifdef SQLITE_OMIT_BUILTIN_TEST
+#ifdef SQLITE_UNTESTABLE
UNUSED_PARAMETER(op);
#else
va_list ap;
@@ -144491,7 +166574,7 @@ SQLITE_API int sqlite3_test_control(int op, ...){
** Save the current state of the PRNG.
*/
case SQLITE_TESTCTRL_PRNG_SAVE: {
- sqlite3PrngSaveState();
+ tdsqlite3PrngSaveState();
break;
}
@@ -144501,59 +166584,82 @@ SQLITE_API int sqlite3_test_control(int op, ...){
** this verb acts like PRNG_RESET.
*/
case SQLITE_TESTCTRL_PRNG_RESTORE: {
- sqlite3PrngRestoreState();
+ tdsqlite3PrngRestoreState();
break;
}
- /*
- ** Reset the PRNG back to its uninitialized state. The next call
- ** to sqlite3_randomness() will reseed the PRNG using a single call
- ** to the xRandomness method of the default VFS.
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_PRNG_SEED, int x, tdsqlite3 *db);
+ **
+ ** Control the seed for the pseudo-random number generator (PRNG) that
+ ** is built into SQLite. Cases:
+ **
+ ** x!=0 && db!=0 Seed the PRNG to the current value of the
+ ** schema cookie in the main database for db, or
+ ** x if the schema cookie is zero. This case
+ ** is convenient to use with database fuzzers
+ ** as it allows the fuzzer some control over the
+ ** the PRNG seed.
+ **
+ ** x!=0 && db==0 Seed the PRNG to the value of x.
+ **
+ ** x==0 && db==0 Revert to default behavior of using the
+ ** xRandomness method on the primary VFS.
+ **
+ ** This test-control also resets the PRNG so that the new seed will
+ ** be used for the next call to tdsqlite3_randomness().
*/
- case SQLITE_TESTCTRL_PRNG_RESET: {
- sqlite3_randomness(0,0);
+#ifndef SQLITE_OMIT_WSD
+ case SQLITE_TESTCTRL_PRNG_SEED: {
+ int x = va_arg(ap, int);
+ int y;
+ tdsqlite3 *db = va_arg(ap, tdsqlite3*);
+ assert( db==0 || db->aDb[0].pSchema!=0 );
+ if( db && (y = db->aDb[0].pSchema->schema_cookie)!=0 ){ x = y; }
+ tdsqlite3Config.iPrngSeed = x;
+ tdsqlite3_randomness(0,0);
break;
}
+#endif
/*
- ** sqlite3_test_control(BITVEC_TEST, size, program)
+ ** tdsqlite3_test_control(BITVEC_TEST, size, program)
**
** Run a test against a Bitvec object of size. The program argument
** is an array of integers that defines the test. Return -1 on a
** memory allocation error, 0 on success, or non-zero for an error.
- ** See the sqlite3BitvecBuiltinTest() for additional information.
+ ** See the tdsqlite3BitvecBuiltinTest() for additional information.
*/
case SQLITE_TESTCTRL_BITVEC_TEST: {
int sz = va_arg(ap, int);
int *aProg = va_arg(ap, int*);
- rc = sqlite3BitvecBuiltinTest(sz, aProg);
+ rc = tdsqlite3BitvecBuiltinTest(sz, aProg);
break;
}
/*
- ** sqlite3_test_control(FAULT_INSTALL, xCallback)
+ ** tdsqlite3_test_control(FAULT_INSTALL, xCallback)
**
- ** Arrange to invoke xCallback() whenever sqlite3FaultSim() is called,
+ ** Arrange to invoke xCallback() whenever tdsqlite3FaultSim() is called,
** if xCallback is not NULL.
**
- ** As a test of the fault simulator mechanism itself, sqlite3FaultSim(0)
+ ** As a test of the fault simulator mechanism itself, tdsqlite3FaultSim(0)
** is called immediately after installing the new callback and the return
- ** value from sqlite3FaultSim(0) becomes the return from
- ** sqlite3_test_control().
+ ** value from tdsqlite3FaultSim(0) becomes the return from
+ ** tdsqlite3_test_control().
*/
case SQLITE_TESTCTRL_FAULT_INSTALL: {
/* MSVC is picky about pulling func ptrs from va lists.
** http://support.microsoft.com/kb/47961
- ** sqlite3GlobalConfig.xTestCallback = va_arg(ap, int(*)(int));
+ ** tdsqlite3GlobalConfig.xTestCallback = va_arg(ap, int(*)(int));
*/
typedef int(*TESTCALLBACKFUNC_t)(int);
- sqlite3GlobalConfig.xTestCallback = va_arg(ap, TESTCALLBACKFUNC_t);
- rc = sqlite3FaultSim(0);
+ tdsqlite3GlobalConfig.xTestCallback = va_arg(ap, TESTCALLBACKFUNC_t);
+ rc = tdsqlite3FaultSim(0);
break;
}
/*
- ** sqlite3_test_control(BENIGN_MALLOC_HOOKS, xBegin, xEnd)
+ ** tdsqlite3_test_control(BENIGN_MALLOC_HOOKS, xBegin, xEnd)
**
** Register hooks to call to indicate which malloc() failures
** are benign.
@@ -144564,12 +166670,12 @@ SQLITE_API int sqlite3_test_control(int op, ...){
void_function xBenignEnd;
xBenignBegin = va_arg(ap, void_function);
xBenignEnd = va_arg(ap, void_function);
- sqlite3BenignMallocHooks(xBenignBegin, xBenignEnd);
+ tdsqlite3BenignMallocHooks(xBenignBegin, xBenignEnd);
break;
}
/*
- ** sqlite3_test_control(SQLITE_TESTCTRL_PENDING_BYTE, unsigned int X)
+ ** tdsqlite3_test_control(SQLITE_TESTCTRL_PENDING_BYTE, unsigned int X)
**
** Set the PENDING byte to the value in the argument, if X>0.
** Make no changes if X==0. Return the value of the pending byte
@@ -144585,14 +166691,14 @@ SQLITE_API int sqlite3_test_control(int op, ...){
#ifndef SQLITE_OMIT_WSD
{
unsigned int newVal = va_arg(ap, unsigned int);
- if( newVal ) sqlite3PendingByte = newVal;
+ if( newVal ) tdsqlite3PendingByte = newVal;
}
#endif
break;
}
/*
- ** sqlite3_test_control(SQLITE_TESTCTRL_ASSERT, int X)
+ ** tdsqlite3_test_control(SQLITE_TESTCTRL_ASSERT, int X)
**
** This action provides a run-time test to see whether or not
** assert() was enabled at compile-time. If X is true and assert()
@@ -144611,12 +166717,12 @@ SQLITE_API int sqlite3_test_control(int op, ...){
/*
- ** sqlite3_test_control(SQLITE_TESTCTRL_ALWAYS, int X)
+ ** tdsqlite3_test_control(SQLITE_TESTCTRL_ALWAYS, int X)
**
** This action provides a run-time test to see how the ALWAYS and
** NEVER macros were defined at compile-time.
**
- ** The return value is ALWAYS(X).
+ ** The return value is ALWAYS(X) if X is true, or 0 if X is false.
**
** The recommended test is X==2. If the return value is 2, that means
** ALWAYS() and NEVER() are both no-op pass-through macros, which is the
@@ -144629,9 +166735,9 @@ SQLITE_API int sqlite3_test_control(int op, ...){
**
** The run-time test procedure might look something like this:
**
- ** if( sqlite3_test_control(SQLITE_TESTCTRL_ALWAYS, 2)==2 ){
+ ** if( tdsqlite3_test_control(SQLITE_TESTCTRL_ALWAYS, 2)==2 ){
** // ALWAYS() and NEVER() are no-op pass-through macros
- ** }else if( sqlite3_test_control(SQLITE_TESTCTRL_ASSERT, 1) ){
+ ** }else if( tdsqlite3_test_control(SQLITE_TESTCTRL_ASSERT, 1) ){
** // ALWAYS(x) asserts that x is true. NEVER(x) asserts x is false.
** }else{
** // ALWAYS(x) is a constant 1. NEVER(x) is a constant 0.
@@ -144639,12 +166745,12 @@ SQLITE_API int sqlite3_test_control(int op, ...){
*/
case SQLITE_TESTCTRL_ALWAYS: {
int x = va_arg(ap,int);
- rc = ALWAYS(x);
+ rc = x ? ALWAYS(x) : 0;
break;
}
/*
- ** sqlite3_test_control(SQLITE_TESTCTRL_BYTEORDER);
+ ** tdsqlite3_test_control(SQLITE_TESTCTRL_BYTEORDER);
**
** The integer returned reveals the byte-order of the computer on which
** SQLite is running:
@@ -144659,21 +166765,21 @@ SQLITE_API int sqlite3_test_control(int op, ...){
break;
}
- /* sqlite3_test_control(SQLITE_TESTCTRL_RESERVE, sqlite3 *db, int N)
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_RESERVE, tdsqlite3 *db, int N)
**
** Set the nReserve size to N for the main database on the database
** connection db.
*/
case SQLITE_TESTCTRL_RESERVE: {
- sqlite3 *db = va_arg(ap, sqlite3*);
+ tdsqlite3 *db = va_arg(ap, tdsqlite3*);
int x = va_arg(ap,int);
- sqlite3_mutex_enter(db->mutex);
- sqlite3BtreeSetPageSize(db->aDb[0].pBt, 0, x, 0);
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
+ tdsqlite3BtreeSetPageSize(db->aDb[0].pBt, 0, x, 0);
+ tdsqlite3_mutex_leave(db->mutex);
break;
}
- /* sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite3 *db, int N)
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, tdsqlite3 *db, int N)
**
** Enable or disable various optimizations for testing purposes. The
** argument N is a bitmask of optimizations to be disabled. For normal
@@ -144683,57 +166789,33 @@ SQLITE_API int sqlite3_test_control(int op, ...){
** is obtained in every case.
*/
case SQLITE_TESTCTRL_OPTIMIZATIONS: {
- sqlite3 *db = va_arg(ap, sqlite3*);
+ tdsqlite3 *db = va_arg(ap, tdsqlite3*);
db->dbOptFlags = (u16)(va_arg(ap, int) & 0xffff);
break;
}
-#ifdef SQLITE_N_KEYWORD
- /* sqlite3_test_control(SQLITE_TESTCTRL_ISKEYWORD, const char *zWord)
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, int onoff);
**
- ** If zWord is a keyword recognized by the parser, then return the
- ** number of keywords. Or if zWord is not a keyword, return 0.
- **
- ** This test feature is only available in the amalgamation since
- ** the SQLITE_N_KEYWORD macro is not defined in this file if SQLite
- ** is built using separate source files.
+ ** If parameter onoff is non-zero, subsequent calls to localtime()
+ ** and its variants fail. If onoff is zero, undo this setting.
*/
- case SQLITE_TESTCTRL_ISKEYWORD: {
- const char *zWord = va_arg(ap, const char*);
- int n = sqlite3Strlen30(zWord);
- rc = (sqlite3KeywordCode((u8*)zWord, n)!=TK_ID) ? SQLITE_N_KEYWORD : 0;
- break;
- }
-#endif
-
- /* sqlite3_test_control(SQLITE_TESTCTRL_SCRATCHMALLOC, sz, &pNew, pFree);
- **
- ** Pass pFree into sqlite3ScratchFree().
- ** If sz>0 then allocate a scratch buffer into pNew.
- */
- case SQLITE_TESTCTRL_SCRATCHMALLOC: {
- void *pFree, **ppNew;
- int sz;
- sz = va_arg(ap, int);
- ppNew = va_arg(ap, void**);
- pFree = va_arg(ap, void*);
- if( sz ) *ppNew = sqlite3ScratchMalloc(sz);
- sqlite3ScratchFree(pFree);
+ case SQLITE_TESTCTRL_LOCALTIME_FAULT: {
+ tdsqlite3GlobalConfig.bLocaltimeFault = va_arg(ap, int);
break;
}
- /* sqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, int onoff);
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_INTERNAL_FUNCTIONS, tdsqlite3*);
**
- ** If parameter onoff is non-zero, configure the wrappers so that all
- ** subsequent calls to localtime() and variants fail. If onoff is zero,
- ** undo this setting.
+ ** Toggle the ability to use internal functions on or off for
+ ** the database connection given in the argument.
*/
- case SQLITE_TESTCTRL_LOCALTIME_FAULT: {
- sqlite3GlobalConfig.bLocaltimeFault = va_arg(ap, int);
+ case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: {
+ tdsqlite3 *db = va_arg(ap, tdsqlite3*);
+ db->mDbFlags ^= DBFLAG_InternalFunc;
break;
}
- /* sqlite3_test_control(SQLITE_TESTCTRL_NEVER_CORRUPT, int);
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_NEVER_CORRUPT, int);
**
** Set or clear a flag that indicates that the database file is always well-
** formed and never corrupt. This flag is clear by default, indicating that
@@ -144742,7 +166824,18 @@ SQLITE_API int sqlite3_test_control(int op, ...){
** that demonstrat invariants on well-formed database files.
*/
case SQLITE_TESTCTRL_NEVER_CORRUPT: {
- sqlite3GlobalConfig.neverCorrupt = va_arg(ap, int);
+ tdsqlite3GlobalConfig.neverCorrupt = va_arg(ap, int);
+ break;
+ }
+
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS, int);
+ **
+ ** Set or clear a flag that causes SQLite to verify that type, name,
+ ** and tbl_name fields of the sqlite_master table. This is normally
+ ** on, but it is sometimes useful to turn it off for testing.
+ */
+ case SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS: {
+ tdsqlite3GlobalConfig.bExtraSchemaChecks = va_arg(ap, int);
break;
}
@@ -144752,42 +166845,43 @@ SQLITE_API int sqlite3_test_control(int op, ...){
** provided to set a small and easily reachable reset value.
*/
case SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD: {
- sqlite3GlobalConfig.iOnceResetThreshold = va_arg(ap, int);
+ tdsqlite3GlobalConfig.iOnceResetThreshold = va_arg(ap, int);
break;
}
- /* sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE, xCallback, ptr);
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE, xCallback, ptr);
**
** Set the VDBE coverage callback function to xCallback with context
** pointer ptr.
*/
case SQLITE_TESTCTRL_VDBE_COVERAGE: {
#ifdef SQLITE_VDBE_COVERAGE
- typedef void (*branch_callback)(void*,int,u8,u8);
- sqlite3GlobalConfig.xVdbeBranch = va_arg(ap,branch_callback);
- sqlite3GlobalConfig.pVdbeBranchArg = va_arg(ap,void*);
+ typedef void (*branch_callback)(void*,unsigned int,
+ unsigned char,unsigned char);
+ tdsqlite3GlobalConfig.xVdbeBranch = va_arg(ap,branch_callback);
+ tdsqlite3GlobalConfig.pVdbeBranchArg = va_arg(ap,void*);
#endif
break;
}
- /* sqlite3_test_control(SQLITE_TESTCTRL_SORTER_MMAP, db, nMax); */
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_SORTER_MMAP, db, nMax); */
case SQLITE_TESTCTRL_SORTER_MMAP: {
- sqlite3 *db = va_arg(ap, sqlite3*);
+ tdsqlite3 *db = va_arg(ap, tdsqlite3*);
db->nMaxSorterMmap = va_arg(ap, int);
break;
}
- /* sqlite3_test_control(SQLITE_TESTCTRL_ISINIT);
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_ISINIT);
**
** Return SQLITE_OK if SQLite has been initialized and SQLITE_ERROR if
** not.
*/
case SQLITE_TESTCTRL_ISINIT: {
- if( sqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR;
+ if( tdsqlite3GlobalConfig.isInit==0 ) rc = SQLITE_ERROR;
break;
}
- /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum);
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum);
**
** This test control is used to create imposter tables. "db" is a pointer
** to the database connection. dbName is the database name (ex: "main" or
@@ -144804,20 +166898,52 @@ SQLITE_API int sqlite3_test_control(int op, ...){
** effect of erasing all imposter tables.
*/
case SQLITE_TESTCTRL_IMPOSTER: {
- sqlite3 *db = va_arg(ap, sqlite3*);
- sqlite3_mutex_enter(db->mutex);
- db->init.iDb = sqlite3FindDbName(db, va_arg(ap,const char*));
+ tdsqlite3 *db = va_arg(ap, tdsqlite3*);
+ tdsqlite3_mutex_enter(db->mutex);
+ db->init.iDb = tdsqlite3FindDbName(db, va_arg(ap,const char*));
db->init.busy = db->init.imposterTable = va_arg(ap,int);
db->init.newTnum = va_arg(ap,int);
if( db->init.busy==0 && db->init.newTnum>0 ){
- sqlite3ResetAllSchemasOfConnection(db);
+ tdsqlite3ResetAllSchemasOfConnection(db);
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
+ break;
+ }
+
+#if defined(YYCOVERAGE)
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_PARSER_COVERAGE, FILE *out)
+ **
+ ** This test control (only available when SQLite is compiled with
+ ** -DYYCOVERAGE) writes a report onto "out" that shows all
+ ** state/lookahead combinations in the parser state machine
+ ** which are never exercised. If any state is missed, make the
+ ** return code SQLITE_ERROR.
+ */
+ case SQLITE_TESTCTRL_PARSER_COVERAGE: {
+ FILE *out = va_arg(ap, FILE*);
+ if( tdsqlite3ParserCoverage(out) ) rc = SQLITE_ERROR;
+ break;
+ }
+#endif /* defined(YYCOVERAGE) */
+
+ /* tdsqlite3_test_control(SQLITE_TESTCTRL_RESULT_INTREAL, tdsqlite3_context*);
+ **
+ ** This test-control causes the most recent tdsqlite3_result_int64() value
+ ** to be interpreted as a MEM_IntReal instead of as an MEM_Int. Normally,
+ ** MEM_IntReal values only arise during an INSERT operation of integer
+ ** values into a REAL column, so they can be challenging to test. This
+ ** test-control enables us to write an intreal() SQL function that can
+ ** inject an intreal() value at arbitrary places in an SQL statement,
+ ** for testing purposes.
+ */
+ case SQLITE_TESTCTRL_RESULT_INTREAL: {
+ tdsqlite3_context *pCtx = va_arg(ap, tdsqlite3_context*);
+ tdsqlite3ResultIntReal(pCtx);
break;
}
}
va_end(ap);
-#endif /* SQLITE_OMIT_BUILTIN_TEST */
+#endif /* SQLITE_UNTESTABLE */
return rc;
}
@@ -144832,88 +166958,134 @@ SQLITE_API int sqlite3_test_control(int op, ...){
** parameter if it exists. If the parameter does not exist, this routine
** returns a NULL pointer.
*/
-SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam){
+SQLITE_API const char *tdsqlite3_uri_parameter(const char *zFilename, const char *zParam){
if( zFilename==0 || zParam==0 ) return 0;
- zFilename += sqlite3Strlen30(zFilename) + 1;
+ zFilename += tdsqlite3Strlen30(zFilename) + 1;
while( zFilename[0] ){
int x = strcmp(zFilename, zParam);
- zFilename += sqlite3Strlen30(zFilename) + 1;
+ zFilename += tdsqlite3Strlen30(zFilename) + 1;
if( x==0 ) return zFilename;
- zFilename += sqlite3Strlen30(zFilename) + 1;
+ zFilename += tdsqlite3Strlen30(zFilename) + 1;
}
return 0;
}
/*
+** Return a pointer to the name of Nth query parameter of the filename.
+*/
+SQLITE_API const char *tdsqlite3_uri_key(const char *zFilename, int N){
+ if( zFilename==0 || N<0 ) return 0;
+ zFilename += tdsqlite3Strlen30(zFilename) + 1;
+ while( zFilename[0] && (N--)>0 ){
+ zFilename += tdsqlite3Strlen30(zFilename) + 1;
+ zFilename += tdsqlite3Strlen30(zFilename) + 1;
+ }
+ return zFilename[0] ? zFilename : 0;
+}
+
+/*
** Return a boolean value for a query parameter.
*/
-SQLITE_API int sqlite3_uri_boolean(const char *zFilename, const char *zParam, int bDflt){
- const char *z = sqlite3_uri_parameter(zFilename, zParam);
+SQLITE_API int tdsqlite3_uri_boolean(const char *zFilename, const char *zParam, int bDflt){
+ const char *z = tdsqlite3_uri_parameter(zFilename, zParam);
bDflt = bDflt!=0;
- return z ? sqlite3GetBoolean(z, bDflt) : bDflt;
+ return z ? tdsqlite3GetBoolean(z, bDflt) : bDflt;
}
/*
** Return a 64-bit integer value for a query parameter.
*/
-SQLITE_API sqlite3_int64 sqlite3_uri_int64(
+SQLITE_API tdsqlite3_int64 tdsqlite3_uri_int64(
const char *zFilename, /* Filename as passed to xOpen */
const char *zParam, /* URI parameter sought */
- sqlite3_int64 bDflt /* return if parameter is missing */
+ tdsqlite3_int64 bDflt /* return if parameter is missing */
){
- const char *z = sqlite3_uri_parameter(zFilename, zParam);
- sqlite3_int64 v;
- if( z && sqlite3DecOrHexToI64(z, &v)==SQLITE_OK ){
+ const char *z = tdsqlite3_uri_parameter(zFilename, zParam);
+ tdsqlite3_int64 v;
+ if( z && tdsqlite3DecOrHexToI64(z, &v)==0 ){
bDflt = v;
}
return bDflt;
}
/*
-** Return the Btree pointer identified by zDbName. Return NULL if not found.
+** The Pager stores the Journal filename, WAL filename, and Database filename
+** consecutively in memory, in that order, with prefixes \000\001\000,
+** \002\000, and \003\000, in that order. Thus the three names look like query
+** parameters if you start at the first prefix.
+**
+** This routine backs up a filename to the start of the first prefix.
+**
+** This only works if the filenamed passed in was obtained from the Pager.
*/
-SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){
- int i;
- for(i=0; i<db->nDb; i++){
- if( db->aDb[i].pBt
- && (zDbName==0 || sqlite3StrICmp(zDbName, db->aDb[i].zDbSName)==0)
- ){
- return db->aDb[i].pBt;
- }
+static const char *startOfNameList(const char *zName){
+ while( zName[0]!='\001' || zName[1]!=0 ){
+ zName -= 3;
+ while( zName[0]!='\000' ){ zName--; }
+ zName++;
}
- return 0;
+ return zName-1;
+}
+
+/*
+** Translate a filename that was handed to a VFS routine into the corresponding
+** database, journal, or WAL file.
+**
+** It is an error to pass this routine a filename string that was not
+** passed into the VFS from the SQLite core. Doing so is similar to
+** passing free() a pointer that was not obtained from malloc() - it is
+** an error that we cannot easily detect but that will likely cause memory
+** corruption.
+*/
+SQLITE_API const char *tdsqlite3_filename_database(const char *zFilename){
+ return tdsqlite3_uri_parameter(zFilename - 3, "\003");
+}
+SQLITE_API const char *tdsqlite3_filename_journal(const char *zFilename){
+ const char *z = tdsqlite3_uri_parameter(startOfNameList(zFilename), "\001");
+ return ALWAYS(z) && z[0] ? z : 0;
+}
+SQLITE_API const char *tdsqlite3_filename_wal(const char *zFilename){
+ return tdsqlite3_uri_parameter(startOfNameList(zFilename), "\002");
+}
+
+/*
+** Return the Btree pointer identified by zDbName. Return NULL if not found.
+*/
+SQLITE_PRIVATE Btree *tdsqlite3DbNameToBtree(tdsqlite3 *db, const char *zDbName){
+ int iDb = zDbName ? tdsqlite3FindDbName(db, zDbName) : 0;
+ return iDb<0 ? 0 : db->aDb[iDb].pBt;
}
/*
** Return the filename of the database associated with a database
** connection.
*/
-SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName){
+SQLITE_API const char *tdsqlite3_db_filename(tdsqlite3 *db, const char *zDbName){
Btree *pBt;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return 0;
}
#endif
- pBt = sqlite3DbNameToBtree(db, zDbName);
- return pBt ? sqlite3BtreeGetFilename(pBt) : 0;
+ pBt = tdsqlite3DbNameToBtree(db, zDbName);
+ return pBt ? tdsqlite3BtreeGetFilename(pBt) : 0;
}
/*
** Return 1 if database is read-only or 0 if read/write. Return -1 if
** no such database exists.
*/
-SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){
+SQLITE_API int tdsqlite3_db_readonly(tdsqlite3 *db, const char *zDbName){
Btree *pBt;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
(void)SQLITE_MISUSE_BKPT;
return -1;
}
#endif
- pBt = sqlite3DbNameToBtree(db, zDbName);
- return pBt ? sqlite3BtreeIsReadonly(pBt) : -1;
+ pBt = tdsqlite3DbNameToBtree(db, zDbName);
+ return pBt ? tdsqlite3BtreeIsReadonly(pBt) : -1;
}
#ifdef SQLITE_ENABLE_SNAPSHOT
@@ -144921,34 +167093,35 @@ SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){
** Obtain a snapshot handle for the snapshot of database zDb currently
** being read by handle db.
*/
-SQLITE_API int sqlite3_snapshot_get(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_snapshot_get(
+ tdsqlite3 *db,
const char *zDb,
- sqlite3_snapshot **ppSnapshot
+ tdsqlite3_snapshot **ppSnapshot
){
int rc = SQLITE_ERROR;
#ifndef SQLITE_OMIT_WAL
- int iDb;
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
return SQLITE_MISUSE_BKPT;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
- iDb = sqlite3FindDbName(db, zDb);
- if( iDb==0 || iDb>1 ){
- Btree *pBt = db->aDb[iDb].pBt;
- if( 0==sqlite3BtreeIsInTrans(pBt) ){
- rc = sqlite3BtreeBeginTrans(pBt, 0);
- if( rc==SQLITE_OK ){
- rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot);
+ if( db->autoCommit==0 ){
+ int iDb = tdsqlite3FindDbName(db, zDb);
+ if( iDb==0 || iDb>1 ){
+ Btree *pBt = db->aDb[iDb].pBt;
+ if( 0==tdsqlite3BtreeIsInTrans(pBt) ){
+ rc = tdsqlite3BtreeBeginTrans(pBt, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3PagerSnapshotGet(tdsqlite3BtreePager(pBt), ppSnapshot);
+ }
}
}
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
#endif /* SQLITE_OMIT_WAL */
return rc;
}
@@ -144956,48 +167129,150 @@ SQLITE_API int sqlite3_snapshot_get(
/*
** Open a read-transaction on the snapshot idendified by pSnapshot.
*/
-SQLITE_API int sqlite3_snapshot_open(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_snapshot_open(
+ tdsqlite3 *db,
const char *zDb,
- sqlite3_snapshot *pSnapshot
+ tdsqlite3_snapshot *pSnapshot
){
int rc = SQLITE_ERROR;
#ifndef SQLITE_OMIT_WAL
#ifdef SQLITE_ENABLE_API_ARMOR
- if( !sqlite3SafetyCheckOk(db) ){
+ if( !tdsqlite3SafetyCheckOk(db) ){
return SQLITE_MISUSE_BKPT;
}
#endif
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
if( db->autoCommit==0 ){
int iDb;
- iDb = sqlite3FindDbName(db, zDb);
+ iDb = tdsqlite3FindDbName(db, zDb);
if( iDb==0 || iDb>1 ){
Btree *pBt = db->aDb[iDb].pBt;
- if( 0==sqlite3BtreeIsInReadTrans(pBt) ){
- rc = sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), pSnapshot);
+ if( tdsqlite3BtreeIsInTrans(pBt)==0 ){
+ Pager *pPager = tdsqlite3BtreePager(pBt);
+ int bUnlock = 0;
+ if( tdsqlite3BtreeIsInReadTrans(pBt) ){
+ if( db->nVdbeActive==0 ){
+ rc = tdsqlite3PagerSnapshotCheck(pPager, pSnapshot);
+ if( rc==SQLITE_OK ){
+ bUnlock = 1;
+ rc = tdsqlite3BtreeCommit(pBt);
+ }
+ }
+ }else{
+ rc = SQLITE_OK;
+ }
if( rc==SQLITE_OK ){
- rc = sqlite3BtreeBeginTrans(pBt, 0);
- sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), 0);
+ rc = tdsqlite3PagerSnapshotOpen(pPager, pSnapshot);
+ }
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3BtreeBeginTrans(pBt, 0, 0);
+ tdsqlite3PagerSnapshotOpen(pPager, 0);
+ }
+ if( bUnlock ){
+ tdsqlite3PagerSnapshotUnlock(pPager);
}
}
}
}
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3_mutex_leave(db->mutex);
+#endif /* SQLITE_OMIT_WAL */
+ return rc;
+}
+
+/*
+** Recover as many snapshots as possible from the wal file associated with
+** schema zDb of database db.
+*/
+SQLITE_API int tdsqlite3_snapshot_recover(tdsqlite3 *db, const char *zDb){
+ int rc = SQLITE_ERROR;
+ int iDb;
+#ifndef SQLITE_OMIT_WAL
+
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( !tdsqlite3SafetyCheckOk(db) ){
+ return SQLITE_MISUSE_BKPT;
+ }
+#endif
+
+ tdsqlite3_mutex_enter(db->mutex);
+ iDb = tdsqlite3FindDbName(db, zDb);
+ if( iDb==0 || iDb>1 ){
+ Btree *pBt = db->aDb[iDb].pBt;
+ if( 0==tdsqlite3BtreeIsInReadTrans(pBt) ){
+ rc = tdsqlite3BtreeBeginTrans(pBt, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3PagerSnapshotRecover(tdsqlite3BtreePager(pBt));
+ tdsqlite3BtreeCommit(pBt);
+ }
+ }
+ }
+ tdsqlite3_mutex_leave(db->mutex);
#endif /* SQLITE_OMIT_WAL */
return rc;
}
/*
-** Free a snapshot handle obtained from sqlite3_snapshot_get().
+** Free a snapshot handle obtained from tdsqlite3_snapshot_get().
*/
-SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){
- sqlite3_free(pSnapshot);
+SQLITE_API void tdsqlite3_snapshot_free(tdsqlite3_snapshot *pSnapshot){
+ tdsqlite3_free(pSnapshot);
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
+#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
+/*
+** Given the name of a compile-time option, return true if that option
+** was used and false if not.
+**
+** The name can optionally begin with "SQLITE_" but the "SQLITE_" prefix
+** is not required for a match.
+*/
+SQLITE_API int tdsqlite3_compileoption_used(const char *zOptName){
+ int i, n;
+ int nOpt;
+ const char **azCompileOpt;
+
+#if SQLITE_ENABLE_API_ARMOR
+ if( zOptName==0 ){
+ (void)SQLITE_MISUSE_BKPT;
+ return 0;
+ }
+#endif
+
+ azCompileOpt = tdsqlite3CompileOptions(&nOpt);
+
+ if( tdsqlite3StrNICmp(zOptName, "SQLITE_", 7)==0 ) zOptName += 7;
+ n = tdsqlite3Strlen30(zOptName);
+
+ /* Since nOpt is normally in single digits, a linear search is
+ ** adequate. No need for a binary search. */
+ for(i=0; i<nOpt; i++){
+ if( tdsqlite3StrNICmp(zOptName, azCompileOpt[i], n)==0
+ && tdsqlite3IsIdChar((unsigned char)azCompileOpt[i][n])==0
+ ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+** Return the N-th compile-time option string. If N is out of range,
+** return a NULL pointer.
+*/
+SQLITE_API const char *tdsqlite3_compileoption_get(int N){
+ int nOpt;
+ const char **azCompileOpt;
+ azCompileOpt = tdsqlite3CompileOptions(&nOpt);
+ if( N>=0 && N<nOpt ){
+ return azCompileOpt[N];
+ }
+ return 0;
+}
+#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
+
/************** End of main.c ************************************************/
/************** Begin file notify.c ******************************************/
/*
@@ -145012,7 +167287,7 @@ SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){
**
*************************************************************************
**
-** This file contains the implementation of the sqlite3_unlock_notify()
+** This file contains the implementation of the tdsqlite3_unlock_notify()
** API method and its associated functionality.
*/
/* #include "sqliteInt.h" */
@@ -145024,22 +167299,22 @@ SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){
/*
** Public interfaces:
**
-** sqlite3ConnectionBlocked()
-** sqlite3ConnectionUnlocked()
-** sqlite3ConnectionClosed()
-** sqlite3_unlock_notify()
+** tdsqlite3ConnectionBlocked()
+** tdsqlite3ConnectionUnlocked()
+** tdsqlite3ConnectionClosed()
+** tdsqlite3_unlock_notify()
*/
#define assertMutexHeld() \
- assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)) )
+ assert( tdsqlite3_mutex_held(tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)) )
/*
-** Head of a linked list of all sqlite3 objects created by this process
+** Head of a linked list of all tdsqlite3 objects created by this process
** for which either sqlite3.pBlockingConnection or sqlite3.pUnlockConnection
** is not NULL. This variable may only accessed while the STATIC_MASTER
** mutex is held.
*/
-static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0;
+static tdsqlite3 *SQLITE_WSD tdsqlite3BlockedList = 0;
#ifndef NDEBUG
/*
@@ -145056,17 +167331,17 @@ static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0;
** blocked connections list have pUnlockConnection or pBlockingConnection
** set to db. This is used when closing connection db.
*/
-static void checkListProperties(sqlite3 *db){
- sqlite3 *p;
- for(p=sqlite3BlockedList; p; p=p->pNextBlocked){
+static void checkListProperties(tdsqlite3 *db){
+ tdsqlite3 *p;
+ for(p=tdsqlite3BlockedList; p; p=p->pNextBlocked){
int seen = 0;
- sqlite3 *p2;
+ tdsqlite3 *p2;
/* Verify property (1) */
assert( p->pUnlockConnection || p->pBlockingConnection );
/* Verify property (2) */
- for(p2=sqlite3BlockedList; p2!=p; p2=p2->pNextBlocked){
+ for(p2=tdsqlite3BlockedList; p2!=p; p2=p2->pNextBlocked){
if( p2->xUnlockNotify==p->xUnlockNotify ) seen = 1;
assert( p2->xUnlockNotify==p->xUnlockNotify || !seen );
assert( db==0 || p->pUnlockConnection!=db );
@@ -145082,10 +167357,10 @@ static void checkListProperties(sqlite3 *db){
** Remove connection db from the blocked connections list. If connection
** db is not currently a part of the list, this function is a no-op.
*/
-static void removeFromBlockedList(sqlite3 *db){
- sqlite3 **pp;
+static void removeFromBlockedList(tdsqlite3 *db){
+ tdsqlite3 **pp;
assertMutexHeld();
- for(pp=&sqlite3BlockedList; *pp; pp = &(*pp)->pNextBlocked){
+ for(pp=&tdsqlite3BlockedList; *pp; pp = &(*pp)->pNextBlocked){
if( *pp==db ){
*pp = (*pp)->pNextBlocked;
break;
@@ -145097,11 +167372,11 @@ static void removeFromBlockedList(sqlite3 *db){
** Add connection db to the blocked connections list. It is assumed
** that it is not already a part of the list.
*/
-static void addToBlockedList(sqlite3 *db){
- sqlite3 **pp;
+static void addToBlockedList(tdsqlite3 *db){
+ tdsqlite3 **pp;
assertMutexHeld();
for(
- pp=&sqlite3BlockedList;
+ pp=&tdsqlite3BlockedList;
*pp && (*pp)->xUnlockNotify!=db->xUnlockNotify;
pp=&(*pp)->pNextBlocked
);
@@ -145113,7 +167388,7 @@ static void addToBlockedList(sqlite3 *db){
** Obtain the STATIC_MASTER mutex.
*/
static void enterMutex(void){
- sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ tdsqlite3_mutex_enter(tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
checkListProperties(0);
}
@@ -145123,7 +167398,7 @@ static void enterMutex(void){
static void leaveMutex(void){
assertMutexHeld();
checkListProperties(0);
- sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ tdsqlite3_mutex_leave(tdsqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
}
/*
@@ -145147,14 +167422,14 @@ static void leaveMutex(void){
** on the same "db". If xNotify==0 then any prior callbacks are immediately
** cancelled.
*/
-SQLITE_API int sqlite3_unlock_notify(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_unlock_notify(
+ tdsqlite3 *db,
void (*xNotify)(void **, int),
void *pArg
){
int rc = SQLITE_OK;
- sqlite3_mutex_enter(db->mutex);
+ tdsqlite3_mutex_enter(db->mutex);
enterMutex();
if( xNotify==0 ){
@@ -145170,7 +167445,7 @@ SQLITE_API int sqlite3_unlock_notify(
*/
xNotify(&pArg, 1);
}else{
- sqlite3 *p;
+ tdsqlite3 *p;
for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection){}
if( p ){
@@ -145186,8 +167461,8 @@ SQLITE_API int sqlite3_unlock_notify(
leaveMutex();
assert( !db->mallocFailed );
- sqlite3ErrorWithMsg(db, rc, (rc?"database is deadlocked":0));
- sqlite3_mutex_leave(db->mutex);
+ tdsqlite3ErrorWithMsg(db, rc, (rc?"database is deadlocked":0));
+ tdsqlite3_mutex_leave(db->mutex);
return rc;
}
@@ -145197,7 +167472,7 @@ SQLITE_API int sqlite3_unlock_notify(
** to the user because it requires a lock that will not be available
** until connection pBlocker concludes its current transaction.
*/
-SQLITE_PRIVATE void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){
+SQLITE_PRIVATE void tdsqlite3ConnectionBlocked(tdsqlite3 *db, tdsqlite3 *pBlocker){
enterMutex();
if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){
addToBlockedList(db);
@@ -145225,10 +167500,10 @@ SQLITE_PRIVATE void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){
** pUnlockConnection==0, remove the entry from the blocked connections
** list.
*/
-SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){
+SQLITE_PRIVATE void tdsqlite3ConnectionUnlocked(tdsqlite3 *db){
void (*xUnlockNotify)(void **, int) = 0; /* Unlock-notify cb to invoke */
int nArg = 0; /* Number of entries in aArg[] */
- sqlite3 **pp; /* Iterator variable */
+ tdsqlite3 **pp; /* Iterator variable */
void **aArg; /* Arguments to the unlock callback */
void **aDyn = 0; /* Dynamically allocated space for aArg[] */
void *aStatic[16]; /* Starter space for aArg[]. No malloc required */
@@ -145237,8 +167512,8 @@ SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){
enterMutex(); /* Enter STATIC_MASTER mutex */
/* This loop runs once for each entry in the blocked-connections list. */
- for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){
- sqlite3 *p = *pp;
+ for(pp=&tdsqlite3BlockedList; *pp; /* no-op */ ){
+ tdsqlite3 *p = *pp;
/* Step 1. */
if( p->pBlockingConnection==db ){
@@ -145253,17 +167528,17 @@ SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){
nArg = 0;
}
- sqlite3BeginBenignMalloc();
+ tdsqlite3BeginBenignMalloc();
assert( aArg==aDyn || (aDyn==0 && aArg==aStatic) );
assert( nArg<=(int)ArraySize(aStatic) || aArg==aDyn );
if( (!aDyn && nArg==(int)ArraySize(aStatic))
- || (aDyn && nArg==(int)(sqlite3MallocSize(aDyn)/sizeof(void*)))
+ || (aDyn && nArg==(int)(tdsqlite3MallocSize(aDyn)/sizeof(void*)))
){
/* The aArg[] array needs to grow. */
- void **pNew = (void **)sqlite3Malloc(nArg*sizeof(void *)*2);
+ void **pNew = (void **)tdsqlite3Malloc(nArg*sizeof(void *)*2);
if( pNew ){
memcpy(pNew, aArg, nArg*sizeof(void *));
- sqlite3_free(aDyn);
+ tdsqlite3_free(aDyn);
aDyn = aArg = pNew;
}else{
/* This occurs when the array of context pointers that need to
@@ -145294,7 +167569,7 @@ SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){
nArg = 0;
}
}
- sqlite3EndBenignMalloc();
+ tdsqlite3EndBenignMalloc();
aArg[nArg++] = p->pUnlockArg;
xUnlockNotify = p->xUnlockNotify;
@@ -145316,7 +167591,7 @@ SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){
if( nArg!=0 ){
xUnlockNotify(aArg, nArg);
}
- sqlite3_free(aDyn);
+ tdsqlite3_free(aDyn);
leaveMutex(); /* Leave STATIC_MASTER mutex */
}
@@ -145324,8 +167599,8 @@ SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){
** This is called when the database connection passed as an argument is
** being closed. The connection is removed from the blocked list.
*/
-SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){
- sqlite3ConnectionUnlocked(db);
+SQLITE_PRIVATE void tdsqlite3ConnectionClosed(tdsqlite3 *db){
+ tdsqlite3ConnectionUnlocked(db);
enterMutex();
removeFromBlockedList(db);
checkListProperties(db);
@@ -145664,9 +167939,9 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
-/* If not building as part of the core, include sqlite3ext.h. */
+/* If not building as part of the core, include tdsqlite3ext.h. */
#ifndef SQLITE_CORE
-/* # include "sqlite3ext.h" */
+/* # include "tdsqlite3ext.h" */
SQLITE_EXTENSION_INIT3
#endif
@@ -145682,21 +167957,21 @@ SQLITE_EXTENSION_INIT3
** Defines the interface to tokenizers used by fulltext-search. There
** are three basic components:
**
-** sqlite3_tokenizer_module is a singleton defining the tokenizer
+** tdsqlite3_tokenizer_module is a singleton defining the tokenizer
** interface functions. This is essentially the class structure for
** tokenizers.
**
-** sqlite3_tokenizer is used to define a particular tokenizer, perhaps
+** tdsqlite3_tokenizer is used to define a particular tokenizer, perhaps
** including customization information defined at creation time.
**
-** sqlite3_tokenizer_cursor is generated by a tokenizer to generate
+** tdsqlite3_tokenizer_cursor is generated by a tokenizer to generate
** tokens from a particular input.
*/
#ifndef _FTS3_TOKENIZER_H_
#define _FTS3_TOKENIZER_H_
/* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time.
-** If tokenizers are to be allowed to call sqlite3_*() functions, then
+** If tokenizers are to be allowed to call tdsqlite3_*() functions, then
** we will need a way to register the API consistently.
*/
/* #include "sqlite3.h" */
@@ -145704,27 +167979,27 @@ SQLITE_EXTENSION_INIT3
/*
** Structures used by the tokenizer interface. When a new tokenizer
** implementation is registered, the caller provides a pointer to
-** an sqlite3_tokenizer_module containing pointers to the callback
+** an tdsqlite3_tokenizer_module containing pointers to the callback
** functions that make up an implementation.
**
** When an fts3 table is created, it passes any arguments passed to
** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the
-** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer
+** tdsqlite3_tokenizer_module.xCreate() function of the requested tokenizer
** implementation. The xCreate() function in turn returns an
-** sqlite3_tokenizer structure representing the specific tokenizer to
+** tdsqlite3_tokenizer structure representing the specific tokenizer to
** be used for the fts3 table (customized by the tokenizer clause arguments).
**
-** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen()
-** method is called. It returns an sqlite3_tokenizer_cursor object
+** To tokenize an input buffer, the tdsqlite3_tokenizer_module.xOpen()
+** method is called. It returns an tdsqlite3_tokenizer_cursor object
** that may be used to tokenize a specific input buffer based on
-** the tokenization rules supplied by a specific sqlite3_tokenizer
+** the tokenization rules supplied by a specific tdsqlite3_tokenizer
** object.
*/
-typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module;
-typedef struct sqlite3_tokenizer sqlite3_tokenizer;
-typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor;
+typedef struct tdsqlite3_tokenizer_module tdsqlite3_tokenizer_module;
+typedef struct tdsqlite3_tokenizer tdsqlite3_tokenizer;
+typedef struct tdsqlite3_tokenizer_cursor tdsqlite3_tokenizer_cursor;
-struct sqlite3_tokenizer_module {
+struct tdsqlite3_tokenizer_module {
/*
** Structure version. Should always be set to 0 or 1.
@@ -145745,20 +168020,20 @@ struct sqlite3_tokenizer_module {
** This method should return either SQLITE_OK (0), or an SQLite error
** code. If SQLITE_OK is returned, then *ppTokenizer should be set
** to point at the newly created tokenizer structure. The generic
- ** sqlite3_tokenizer.pModule variable should not be initialized by
+ ** tdsqlite3_tokenizer.pModule variable should not be initialized by
** this callback. The caller will do so.
*/
int (*xCreate)(
int argc, /* Size of argv array */
const char *const*argv, /* Tokenizer argument strings */
- sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
+ tdsqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
);
/*
** Destroy an existing tokenizer. The fts3 module calls this method
** exactly once for each successful call to xCreate().
*/
- int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
+ int (*xDestroy)(tdsqlite3_tokenizer *pTokenizer);
/*
** Create a tokenizer cursor to tokenize an input buffer. The caller
@@ -145766,16 +168041,16 @@ struct sqlite3_tokenizer_module {
** until the cursor is closed (using the xClose() method).
*/
int (*xOpen)(
- sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
+ tdsqlite3_tokenizer *pTokenizer, /* Tokenizer object */
const char *pInput, int nBytes, /* Input buffer */
- sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
+ tdsqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
);
/*
** Destroy an existing tokenizer cursor. The fts3 module calls this
** method exactly once for each successful call to xOpen().
*/
- int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
+ int (*xClose)(tdsqlite3_tokenizer_cursor *pCursor);
/*
** Retrieve the next token from the tokenizer cursor pCursor. This
@@ -145802,7 +168077,7 @@ struct sqlite3_tokenizer_module {
** should be converted to zInput.
*/
int (*xNext)(
- sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
+ tdsqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */
int *piStartOffset, /* OUT: Byte offset of token in input buffer */
int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
@@ -145816,16 +168091,16 @@ struct sqlite3_tokenizer_module {
/*
** Configure the language id of a tokenizer cursor.
*/
- int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid);
+ int (*xLanguageid)(tdsqlite3_tokenizer_cursor *pCsr, int iLangid);
};
-struct sqlite3_tokenizer {
- const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */
+struct tdsqlite3_tokenizer {
+ const tdsqlite3_tokenizer_module *pModule; /* The module for this tokenizer */
/* Tokenizer implementations will typically add additional fields */
};
-struct sqlite3_tokenizer_cursor {
- sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */
+struct tdsqlite3_tokenizer_cursor {
+ tdsqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */
/* Tokenizer implementations will typically add additional fields */
};
@@ -145912,20 +168187,20 @@ struct Fts3HashElem {
/*
** Access routines. To delete, insert a NULL pointer.
*/
-SQLITE_PRIVATE void sqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey);
-SQLITE_PRIVATE void *sqlite3Fts3HashInsert(Fts3Hash*, const void *pKey, int nKey, void *pData);
-SQLITE_PRIVATE void *sqlite3Fts3HashFind(const Fts3Hash*, const void *pKey, int nKey);
-SQLITE_PRIVATE void sqlite3Fts3HashClear(Fts3Hash*);
-SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const void *, int);
+SQLITE_PRIVATE void tdsqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey);
+SQLITE_PRIVATE void *tdsqlite3Fts3HashInsert(Fts3Hash*, const void *pKey, int nKey, void *pData);
+SQLITE_PRIVATE void *tdsqlite3Fts3HashFind(const Fts3Hash*, const void *pKey, int nKey);
+SQLITE_PRIVATE void tdsqlite3Fts3HashClear(Fts3Hash*);
+SQLITE_PRIVATE Fts3HashElem *tdsqlite3Fts3HashFindElem(const Fts3Hash *, const void *, int);
/*
** Shorthand for the functions above
*/
-#define fts3HashInit sqlite3Fts3HashInit
-#define fts3HashInsert sqlite3Fts3HashInsert
-#define fts3HashFind sqlite3Fts3HashFind
-#define fts3HashClear sqlite3Fts3HashClear
-#define fts3HashFindElem sqlite3Fts3HashFindElem
+#define fts3HashInit tdsqlite3Fts3HashInit
+#define fts3HashInsert tdsqlite3Fts3HashInsert
+#define fts3HashFind tdsqlite3Fts3HashFind
+#define fts3HashClear tdsqlite3Fts3HashClear
+#define fts3HashFindElem tdsqlite3Fts3HashFindElem
/*
** Macros for looping over all elements of a hash table. The idiom is
@@ -146005,6 +168280,8 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi
*/
#define FTS3_VARINT_MAX 10
+#define FTS3_BUFFER_PADDING 8
+
/*
** FTS4 virtual tables may maintain multiple indexes - one index of all terms
** in the document set and zero or more prefix indexes. All indexes are stored
@@ -146038,6 +168315,18 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi
#define POS_END (0) /* Position-list terminator */
/*
+** The assert_fts3_nc() macro is similar to the assert() macro, except that it
+** is used for assert() conditions that are true only if it can be
+** guranteed that the database is not corrupt.
+*/
+#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
+SQLITE_API extern int tdsqlite3_fts3_may_be_corrupt;
+# define assert_fts3_nc(x) assert(tdsqlite3_fts3_may_be_corrupt || (x))
+#else
+# define assert_fts3_nc(x) assert(x)
+#endif
+
+/*
** This section provides definitions to allow the
** FTS3 extension to be compiled outside of the
** amalgamation.
@@ -146051,10 +168340,10 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi
# define ALWAYS(x) (1)
# define NEVER(X) (0)
#elif defined(SQLITE_DEBUG)
-# define ALWAYS(x) sqlite3Fts3Always((x)!=0)
-# define NEVER(x) sqlite3Fts3Never((x)!=0)
-SQLITE_PRIVATE int sqlite3Fts3Always(int b);
-SQLITE_PRIVATE int sqlite3Fts3Never(int b);
+# define ALWAYS(x) tdsqlite3Fts3Always((x)!=0)
+# define NEVER(x) tdsqlite3Fts3Never((x)!=0)
+SQLITE_PRIVATE int tdsqlite3Fts3Always(int b);
+SQLITE_PRIVATE int tdsqlite3Fts3Never(int b);
#else
# define ALWAYS(x) (x)
# define NEVER(x) (x)
@@ -146066,8 +168355,8 @@ SQLITE_PRIVATE int sqlite3Fts3Never(int b);
typedef unsigned char u8; /* 1-byte (or larger) unsigned integer */
typedef short int i16; /* 2-byte (or larger) signed integer */
typedef unsigned int u32; /* 4-byte unsigned integer */
-typedef sqlite3_uint64 u64; /* 8-byte unsigned integer */
-typedef sqlite3_int64 i64; /* 8-byte signed integer */
+typedef tdsqlite3_uint64 u64; /* 8-byte unsigned integer */
+typedef tdsqlite3_int64 i64; /* 8-byte signed integer */
/*
** Macro used to suppress compiler warnings for unused parameters.
@@ -146092,11 +168381,14 @@ typedef sqlite3_int64 i64; /* 8-byte signed integer */
# define TESTONLY(X)
#endif
+#define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32))
+#define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64)
+
#endif /* SQLITE_AMALGAMATION */
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3Fts3Corrupt(void);
-# define FTS_CORRUPT_VTAB sqlite3Fts3Corrupt()
+SQLITE_PRIVATE int tdsqlite3Fts3Corrupt(void);
+# define FTS_CORRUPT_VTAB tdsqlite3Fts3Corrupt()
#else
# define FTS_CORRUPT_VTAB SQLITE_CORRUPT_VTAB
#endif
@@ -146123,23 +168415,25 @@ typedef struct MatchinfoBuffer MatchinfoBuffer;
** arguments.
*/
struct Fts3Table {
- sqlite3_vtab base; /* Base class used by SQLite core */
- sqlite3 *db; /* The database connection */
+ tdsqlite3_vtab base; /* Base class used by SQLite core */
+ tdsqlite3 *db; /* The database connection */
const char *zDb; /* logical database name */
const char *zName; /* virtual table name */
int nColumn; /* number of named columns in virtual table */
char **azColumn; /* column names. malloced */
u8 *abNotindexed; /* True for 'notindexed' columns */
- sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */
+ tdsqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */
char *zContentTbl; /* content=xxx option, or NULL */
char *zLanguageid; /* languageid=xxx option, or NULL */
int nAutoincrmerge; /* Value configured by 'automerge' */
u32 nLeafAdd; /* Number of leaf blocks added this trans */
+ int bLock; /* Used to prevent recursive content= tbls */
/* Precompiled statements used by the implementation. Each of these
** statements is run and reset within a single virtual table API call.
*/
- sqlite3_stmt *aStmt[40];
+ tdsqlite3_stmt *aStmt[40];
+ tdsqlite3_stmt *pSeekStmt; /* Cache for fts3CursorSeekStmt() */
char *zReadExprlist;
char *zWriteExprlist;
@@ -146152,7 +168446,7 @@ struct Fts3Table {
u8 bIgnoreSavepoint; /* True to ignore xSavepoint invocations */
int nPgsz; /* Page size for host database */
char *zSegmentsTbl; /* Name of %_segments table */
- sqlite3_blob *pSegments; /* Blob handle open on %_segments table */
+ tdsqlite3_blob *pSegments; /* Blob handle open on %_segments table */
/*
** The following array of hash tables is used to buffer pending index
@@ -146192,36 +168486,47 @@ struct Fts3Table {
int mxSavepoint; /* Largest valid xSavepoint integer */
#endif
-#ifdef SQLITE_TEST
+#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
/* True to disable the incremental doclist optimization. This is controled
** by special insert command 'test-no-incr-doclist'. */
int bNoIncrDoclist;
+
+ /* Number of segments in a level */
+ int nMergeCount;
#endif
};
+/* Macro to find the number of segments to merge */
+#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
+# define MergeCount(P) ((P)->nMergeCount)
+#else
+# define MergeCount(P) FTS3_MERGE_COUNT
+#endif
+
/*
** When the core wants to read from the virtual table, it creates a
** virtual table cursor (an instance of the following structure) using
** the xOpen method. Cursors are destroyed using the xClose method.
*/
struct Fts3Cursor {
- sqlite3_vtab_cursor base; /* Base class used by SQLite core */
+ tdsqlite3_vtab_cursor base; /* Base class used by SQLite core */
i16 eSearch; /* Search strategy (see below) */
u8 isEof; /* True if at End Of Results */
u8 isRequireSeek; /* True if must seek pStmt to %_content row */
- sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */
+ u8 bSeekStmt; /* True if pStmt is a seek */
+ tdsqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */
Fts3Expr *pExpr; /* Parsed MATCH query string */
int iLangid; /* Language being queried for */
int nPhrase; /* Number of matchable phrases in query */
Fts3DeferredToken *pDeferred; /* Deferred search tokens, if any */
- sqlite3_int64 iPrevId; /* Previous id read from aDoclist */
+ tdsqlite3_int64 iPrevId; /* Previous id read from aDoclist */
char *pNextId; /* Pointer into the body of aDoclist */
char *aDoclist; /* List of docids for full-text queries */
int nDoclist; /* Size of buffer at aDoclist */
u8 bDesc; /* True to sort in descending order */
int eEvalmode; /* An FTS3_EVAL_XX constant */
int nRowAvg; /* Average size of database rows, in pages */
- sqlite3_int64 nDoc; /* Documents in table */
+ tdsqlite3_int64 nDoc; /* Documents in table */
i64 iMinDocid; /* Minimum docid to return */
i64 iMaxDocid; /* Maximum docid to return */
int isMatchinfoNeeded; /* True when aMatchinfo[] needs filling in */
@@ -146252,7 +168557,7 @@ struct Fts3Cursor {
#define FTS3_FULLTEXT_SEARCH 2 /* Full-text index search */
/*
-** The lower 16-bits of the sqlite3_index_info.idxNum value set by
+** The lower 16-bits of the tdsqlite3_index_info.idxNum value set by
** the xBestIndex() method contains the Fts3Cursor.eSearch value described
** above. The upper 16-bits contain a combination of the following
** bits, used to describe extra constraints on full-text searches.
@@ -146266,8 +168571,8 @@ struct Fts3Doclist {
int nAll; /* Size of a[] in bytes */
char *pNextDocid; /* Pointer to next docid */
- sqlite3_int64 iDocid; /* Current docid (if pList!=0) */
- int bFreeList; /* True if pList should be sqlite3_free()d */
+ tdsqlite3_int64 iDocid; /* Current docid (if pList!=0) */
+ int bFreeList; /* True if pList should be tdsqlite3_free()d */
char *pList; /* Pointer to position list following iDocid */
int nList; /* Length of position list */
};
@@ -146297,7 +168602,7 @@ struct Fts3Phrase {
int bIncr; /* True if doclist is loaded incrementally */
int iDoclistToken;
- /* Used by sqlite3Fts3EvalPhrasePoslist() if this is a descendent of an
+ /* Used by tdsqlite3Fts3EvalPhrasePoslist() if this is a descendent of an
** OR condition. */
char *pOrPoslist;
i64 iOrDocid;
@@ -146328,7 +168633,7 @@ struct Fts3Phrase {
** aMI[iCol*3 + 1] = Number of occurrences
** aMI[iCol*3 + 2] = Number of rows containing at least one instance
**
-** The aMI array is allocated using sqlite3_malloc(). It should be freed
+** The aMI array is allocated using tdsqlite3_malloc(). It should be freed
** when the expression node is.
*/
struct Fts3Expr {
@@ -146340,7 +168645,7 @@ struct Fts3Expr {
Fts3Phrase *pPhrase; /* Valid if eType==FTSQUERY_PHRASE */
/* The following are used by the fts3_eval.c module. */
- sqlite3_int64 iDocid; /* Current docid */
+ tdsqlite3_int64 iDocid; /* Current docid */
u8 bEof; /* True this expression is at EOF already */
u8 bStart; /* True if iDocid is valid */
u8 bDeferred; /* True if this expression is entirely deferred */
@@ -146369,47 +168674,47 @@ struct Fts3Expr {
/* fts3_write.c */
-SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(sqlite3_vtab*,int,sqlite3_value**,sqlite3_int64*);
-SQLITE_PRIVATE int sqlite3Fts3PendingTermsFlush(Fts3Table *);
-SQLITE_PRIVATE void sqlite3Fts3PendingTermsClear(Fts3Table *);
-SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *);
-SQLITE_PRIVATE int sqlite3Fts3SegReaderNew(int, int, sqlite3_int64,
- sqlite3_int64, sqlite3_int64, const char *, int, Fts3SegReader**);
-SQLITE_PRIVATE int sqlite3Fts3SegReaderPending(
+SQLITE_PRIVATE int tdsqlite3Fts3UpdateMethod(tdsqlite3_vtab*,int,tdsqlite3_value**,tdsqlite3_int64*);
+SQLITE_PRIVATE int tdsqlite3Fts3PendingTermsFlush(Fts3Table *);
+SQLITE_PRIVATE void tdsqlite3Fts3PendingTermsClear(Fts3Table *);
+SQLITE_PRIVATE int tdsqlite3Fts3Optimize(Fts3Table *);
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderNew(int, int, tdsqlite3_int64,
+ tdsqlite3_int64, tdsqlite3_int64, const char *, int, Fts3SegReader**);
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderPending(
Fts3Table*,int,const char*,int,int,Fts3SegReader**);
-SQLITE_PRIVATE void sqlite3Fts3SegReaderFree(Fts3SegReader *);
-SQLITE_PRIVATE int sqlite3Fts3AllSegdirs(Fts3Table*, int, int, int, sqlite3_stmt **);
-SQLITE_PRIVATE int sqlite3Fts3ReadBlock(Fts3Table*, sqlite3_int64, char **, int*, int*);
+SQLITE_PRIVATE void tdsqlite3Fts3SegReaderFree(Fts3SegReader *);
+SQLITE_PRIVATE int tdsqlite3Fts3AllSegdirs(Fts3Table*, int, int, int, tdsqlite3_stmt **);
+SQLITE_PRIVATE int tdsqlite3Fts3ReadBlock(Fts3Table*, tdsqlite3_int64, char **, int*, int*);
-SQLITE_PRIVATE int sqlite3Fts3SelectDoctotal(Fts3Table *, sqlite3_stmt **);
-SQLITE_PRIVATE int sqlite3Fts3SelectDocsize(Fts3Table *, sqlite3_int64, sqlite3_stmt **);
+SQLITE_PRIVATE int tdsqlite3Fts3SelectDoctotal(Fts3Table *, tdsqlite3_stmt **);
+SQLITE_PRIVATE int tdsqlite3Fts3SelectDocsize(Fts3Table *, tdsqlite3_int64, tdsqlite3_stmt **);
#ifndef SQLITE_DISABLE_FTS4_DEFERRED
-SQLITE_PRIVATE void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *);
-SQLITE_PRIVATE int sqlite3Fts3DeferToken(Fts3Cursor *, Fts3PhraseToken *, int);
-SQLITE_PRIVATE int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *);
-SQLITE_PRIVATE void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *);
-SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *);
+SQLITE_PRIVATE void tdsqlite3Fts3FreeDeferredTokens(Fts3Cursor *);
+SQLITE_PRIVATE int tdsqlite3Fts3DeferToken(Fts3Cursor *, Fts3PhraseToken *, int);
+SQLITE_PRIVATE int tdsqlite3Fts3CacheDeferredDoclists(Fts3Cursor *);
+SQLITE_PRIVATE void tdsqlite3Fts3FreeDeferredDoclists(Fts3Cursor *);
+SQLITE_PRIVATE int tdsqlite3Fts3DeferredTokenList(Fts3DeferredToken *, char **, int *);
#else
-# define sqlite3Fts3FreeDeferredTokens(x)
-# define sqlite3Fts3DeferToken(x,y,z) SQLITE_OK
-# define sqlite3Fts3CacheDeferredDoclists(x) SQLITE_OK
-# define sqlite3Fts3FreeDeferredDoclists(x)
-# define sqlite3Fts3DeferredTokenList(x,y,z) SQLITE_OK
+# define tdsqlite3Fts3FreeDeferredTokens(x)
+# define tdsqlite3Fts3DeferToken(x,y,z) SQLITE_OK
+# define tdsqlite3Fts3CacheDeferredDoclists(x) SQLITE_OK
+# define tdsqlite3Fts3FreeDeferredDoclists(x)
+# define tdsqlite3Fts3DeferredTokenList(x,y,z) SQLITE_OK
#endif
-SQLITE_PRIVATE void sqlite3Fts3SegmentsClose(Fts3Table *);
-SQLITE_PRIVATE int sqlite3Fts3MaxLevel(Fts3Table *, int *);
+SQLITE_PRIVATE void tdsqlite3Fts3SegmentsClose(Fts3Table *);
+SQLITE_PRIVATE int tdsqlite3Fts3MaxLevel(Fts3Table *, int *);
-/* Special values interpreted by sqlite3SegReaderCursor() */
+/* Special values interpreted by tdsqlite3SegReaderCursor() */
#define FTS3_SEGCURSOR_PENDING -1
#define FTS3_SEGCURSOR_ALL -2
-SQLITE_PRIVATE int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3MultiSegReader*, Fts3SegFilter*);
-SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3MultiSegReader *);
-SQLITE_PRIVATE void sqlite3Fts3SegReaderFinish(Fts3MultiSegReader *);
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderStart(Fts3Table*, Fts3MultiSegReader*, Fts3SegFilter*);
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderStep(Fts3Table *, Fts3MultiSegReader *);
+SQLITE_PRIVATE void tdsqlite3Fts3SegReaderFinish(Fts3MultiSegReader *);
-SQLITE_PRIVATE int sqlite3Fts3SegReaderCursor(Fts3Table *,
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderCursor(Fts3Table *,
int, int, int, const char *, int, int, int, Fts3MultiSegReader *);
/* Flags allowed as part of the 4th argument to SegmentReaderIterate() */
@@ -146429,7 +168734,7 @@ struct Fts3SegFilter {
};
struct Fts3MultiSegReader {
- /* Used internally by sqlite3Fts3SegReaderXXX() calls */
+ /* Used internally by tdsqlite3Fts3SegReaderXXX() calls */
Fts3SegReader **apSegment; /* Array of Fts3SegReader objects */
int nSegment; /* Size of apSegment array */
int nAdvance; /* How many seg-readers to advance */
@@ -146451,76 +168756,78 @@ struct Fts3MultiSegReader {
int nDoclist; /* Size of aDoclist[] in bytes */
};
-SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table*,int,int);
+SQLITE_PRIVATE int tdsqlite3Fts3Incrmerge(Fts3Table*,int,int);
#define fts3GetVarint32(p, piVal) ( \
- (*(u8*)(p)&0x80) ? sqlite3Fts3GetVarint32(p, piVal) : (*piVal=*(u8*)(p), 1) \
+ (*(u8*)(p)&0x80) ? tdsqlite3Fts3GetVarint32(p, piVal) : (*piVal=*(u8*)(p), 1) \
)
/* fts3.c */
-SQLITE_PRIVATE void sqlite3Fts3ErrMsg(char**,const char*,...);
-SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *, sqlite3_int64);
-SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *, sqlite_int64 *);
-SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *, int *);
-SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64);
-SQLITE_PRIVATE void sqlite3Fts3Dequote(char *);
-SQLITE_PRIVATE void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*);
-SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *);
-SQLITE_PRIVATE int sqlite3Fts3FirstFilter(sqlite3_int64, char *, int, char *);
-SQLITE_PRIVATE void sqlite3Fts3CreateStatTable(int*, Fts3Table*);
-SQLITE_PRIVATE int sqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc);
+SQLITE_PRIVATE void tdsqlite3Fts3ErrMsg(char**,const char*,...);
+SQLITE_PRIVATE int tdsqlite3Fts3PutVarint(char *, tdsqlite3_int64);
+SQLITE_PRIVATE int tdsqlite3Fts3GetVarint(const char *, sqlite_int64 *);
+SQLITE_PRIVATE int tdsqlite3Fts3GetVarintU(const char *, sqlite_uint64 *);
+SQLITE_PRIVATE int tdsqlite3Fts3GetVarintBounded(const char*,const char*,tdsqlite3_int64*);
+SQLITE_PRIVATE int tdsqlite3Fts3GetVarint32(const char *, int *);
+SQLITE_PRIVATE int tdsqlite3Fts3VarintLen(tdsqlite3_uint64);
+SQLITE_PRIVATE void tdsqlite3Fts3Dequote(char *);
+SQLITE_PRIVATE void tdsqlite3Fts3DoclistPrev(int,char*,int,char**,tdsqlite3_int64*,int*,u8*);
+SQLITE_PRIVATE int tdsqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *);
+SQLITE_PRIVATE int tdsqlite3Fts3FirstFilter(tdsqlite3_int64, char *, int, char *);
+SQLITE_PRIVATE void tdsqlite3Fts3CreateStatTable(int*, Fts3Table*);
+SQLITE_PRIVATE int tdsqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc);
/* fts3_tokenizer.c */
-SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *, int *);
-SQLITE_PRIVATE int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *);
-SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *,
- sqlite3_tokenizer **, char **
+SQLITE_PRIVATE const char *tdsqlite3Fts3NextToken(const char *, int *);
+SQLITE_PRIVATE int tdsqlite3Fts3InitHashTable(tdsqlite3 *, Fts3Hash *, const char *);
+SQLITE_PRIVATE int tdsqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *,
+ tdsqlite3_tokenizer **, char **
);
-SQLITE_PRIVATE int sqlite3Fts3IsIdChar(char);
+SQLITE_PRIVATE int tdsqlite3Fts3IsIdChar(char);
/* fts3_snippet.c */
-SQLITE_PRIVATE void sqlite3Fts3Offsets(sqlite3_context*, Fts3Cursor*);
-SQLITE_PRIVATE void sqlite3Fts3Snippet(sqlite3_context *, Fts3Cursor *, const char *,
+SQLITE_PRIVATE void tdsqlite3Fts3Offsets(tdsqlite3_context*, Fts3Cursor*);
+SQLITE_PRIVATE void tdsqlite3Fts3Snippet(tdsqlite3_context *, Fts3Cursor *, const char *,
const char *, const char *, int, int
);
-SQLITE_PRIVATE void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *, const char *);
-SQLITE_PRIVATE void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p);
+SQLITE_PRIVATE void tdsqlite3Fts3Matchinfo(tdsqlite3_context *, Fts3Cursor *, const char *);
+SQLITE_PRIVATE void tdsqlite3Fts3MIBufferFree(MatchinfoBuffer *p);
/* fts3_expr.c */
-SQLITE_PRIVATE int sqlite3Fts3ExprParse(sqlite3_tokenizer *, int,
+SQLITE_PRIVATE int tdsqlite3Fts3ExprParse(tdsqlite3_tokenizer *, int,
char **, int, int, int, const char *, int, Fts3Expr **, char **
);
-SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *);
+SQLITE_PRIVATE void tdsqlite3Fts3ExprFree(Fts3Expr *);
#ifdef SQLITE_TEST
-SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3 *db);
-SQLITE_PRIVATE int sqlite3Fts3InitTerm(sqlite3 *db);
+SQLITE_PRIVATE int tdsqlite3Fts3ExprInitTestInterface(tdsqlite3 *db, Fts3Hash*);
+SQLITE_PRIVATE int tdsqlite3Fts3InitTerm(tdsqlite3 *db);
#endif
-SQLITE_PRIVATE int sqlite3Fts3OpenTokenizer(sqlite3_tokenizer *, int, const char *, int,
- sqlite3_tokenizer_cursor **
+SQLITE_PRIVATE int tdsqlite3Fts3OpenTokenizer(tdsqlite3_tokenizer *, int, const char *, int,
+ tdsqlite3_tokenizer_cursor **
);
/* fts3_aux.c */
-SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db);
+SQLITE_PRIVATE int tdsqlite3Fts3InitAux(tdsqlite3 *db);
-SQLITE_PRIVATE void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *);
+SQLITE_PRIVATE void tdsqlite3Fts3EvalPhraseCleanup(Fts3Phrase *);
-SQLITE_PRIVATE int sqlite3Fts3MsrIncrStart(
+SQLITE_PRIVATE int tdsqlite3Fts3MsrIncrStart(
Fts3Table*, Fts3MultiSegReader*, int, const char*, int);
-SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext(
- Fts3Table *, Fts3MultiSegReader *, sqlite3_int64 *, char **, int *);
-SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist(Fts3Cursor *, Fts3Expr *, int iCol, char **);
-SQLITE_PRIVATE int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *);
-SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr);
+SQLITE_PRIVATE int tdsqlite3Fts3MsrIncrNext(
+ Fts3Table *, Fts3MultiSegReader *, tdsqlite3_int64 *, char **, int *);
+SQLITE_PRIVATE int tdsqlite3Fts3EvalPhrasePoslist(Fts3Cursor *, Fts3Expr *, int iCol, char **);
+SQLITE_PRIVATE int tdsqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *);
+SQLITE_PRIVATE int tdsqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr);
/* fts3_tokenize_vtab.c */
-SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3*, Fts3Hash *);
+SQLITE_PRIVATE int tdsqlite3Fts3InitTok(tdsqlite3*, Fts3Hash *);
/* fts3_unicode2.c (functions generated by parsing unicode text files) */
#ifndef SQLITE_DISABLE_FTS3_UNICODE
-SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int, int);
-SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int);
-SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int);
+SQLITE_PRIVATE int tdsqlite3FtsUnicodeFold(int, int);
+SQLITE_PRIVATE int tdsqlite3FtsUnicodeIsalnum(int);
+SQLITE_PRIVATE int tdsqlite3FtsUnicodeIsdiacritic(int);
#endif
#endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */
@@ -146543,7 +168850,7 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int);
/* #include "fts3.h" */
#ifndef SQLITE_CORE
-/* # include "sqlite3ext.h" */
+/* # include "tdsqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#endif
@@ -146554,17 +168861,25 @@ static int fts3TermSegReaderCursor(
#ifndef SQLITE_AMALGAMATION
# if defined(SQLITE_DEBUG)
-SQLITE_PRIVATE int sqlite3Fts3Always(int b) { assert( b ); return b; }
-SQLITE_PRIVATE int sqlite3Fts3Never(int b) { assert( !b ); return b; }
+SQLITE_PRIVATE int tdsqlite3Fts3Always(int b) { assert( b ); return b; }
+SQLITE_PRIVATE int tdsqlite3Fts3Never(int b) { assert( !b ); return b; }
# endif
#endif
+/*
+** This variable is set to false when running tests for which the on disk
+** structures should not be corrupt. Otherwise, true. If it is false, extra
+** assert() conditions in the fts3 code are activated - conditions that are
+** only true if it is guaranteed that the fts3 database is not corrupt.
+*/
+SQLITE_API int tdsqlite3_fts3_may_be_corrupt = 1;
+
/*
** Write a 64-bit variable-length integer to memory starting at p[0].
** The length of data written will be between 1 and FTS3_VARINT_MAX bytes.
** The number of bytes written is returned.
*/
-SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *p, sqlite_int64 v){
+SQLITE_PRIVATE int tdsqlite3Fts3PutVarint(char *p, sqlite_int64 v){
unsigned char *q = (unsigned char *) p;
sqlite_uint64 vu = v;
do{
@@ -146577,19 +168892,15 @@ SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *p, sqlite_int64 v){
}
#define GETVARINT_STEP(v, ptr, shift, mask1, mask2, var, ret) \
- v = (v & mask1) | ( (*ptr++) << shift ); \
+ v = (v & mask1) | ( (*(const unsigned char*)(ptr++)) << shift ); \
if( (v & mask2)==0 ){ var = v; return ret; }
#define GETVARINT_INIT(v, ptr, shift, mask1, mask2, var, ret) \
v = (*ptr++); \
if( (v & mask2)==0 ){ var = v; return ret; }
-/*
-** Read a 64-bit variable-length integer from memory starting at p[0].
-** Return the number of bytes read, or 0 on error.
-** The value is stored in *v.
-*/
-SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *p, sqlite_int64 *v){
- const char *pStart = p;
+SQLITE_PRIVATE int tdsqlite3Fts3GetVarintU(const char *pBuf, sqlite_uint64 *v){
+ const unsigned char *p = (const unsigned char*)pBuf;
+ const unsigned char *pStart = p;
u32 a;
u64 b;
int shift;
@@ -146609,32 +168920,70 @@ SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *p, sqlite_int64 *v){
return (int)(p - pStart);
}
+/*
+** Read a 64-bit variable-length integer from memory starting at p[0].
+** Return the number of bytes read, or 0 on error.
+** The value is stored in *v.
+*/
+SQLITE_PRIVATE int tdsqlite3Fts3GetVarint(const char *pBuf, sqlite_int64 *v){
+ return tdsqlite3Fts3GetVarintU(pBuf, (tdsqlite3_uint64*)v);
+}
+
+/*
+** Read a 64-bit variable-length integer from memory starting at p[0] and
+** not extending past pEnd[-1].
+** Return the number of bytes read, or 0 on error.
+** The value is stored in *v.
+*/
+SQLITE_PRIVATE int tdsqlite3Fts3GetVarintBounded(
+ const char *pBuf,
+ const char *pEnd,
+ sqlite_int64 *v
+){
+ const unsigned char *p = (const unsigned char*)pBuf;
+ const unsigned char *pStart = p;
+ const unsigned char *pX = (const unsigned char*)pEnd;
+ u64 b = 0;
+ int shift;
+ for(shift=0; shift<=63; shift+=7){
+ u64 c = p<pX ? *p : 0;
+ p++;
+ b += (c&0x7F) << shift;
+ if( (c & 0x80)==0 ) break;
+ }
+ *v = b;
+ return (int)(p - pStart);
+}
+
/*
-** Similar to sqlite3Fts3GetVarint(), except that the output is truncated to a
-** 32-bit integer before it is returned.
+** Similar to tdsqlite3Fts3GetVarint(), except that the output is truncated to
+** a non-negative 32-bit integer before it is returned.
*/
-SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *p, int *pi){
+SQLITE_PRIVATE int tdsqlite3Fts3GetVarint32(const char *p, int *pi){
+ const unsigned char *ptr = (const unsigned char*)p;
u32 a;
#ifndef fts3GetVarint32
- GETVARINT_INIT(a, p, 0, 0x00, 0x80, *pi, 1);
+ GETVARINT_INIT(a, ptr, 0, 0x00, 0x80, *pi, 1);
#else
- a = (*p++);
+ a = (*ptr++);
assert( a & 0x80 );
#endif
- GETVARINT_STEP(a, p, 7, 0x7F, 0x4000, *pi, 2);
- GETVARINT_STEP(a, p, 14, 0x3FFF, 0x200000, *pi, 3);
- GETVARINT_STEP(a, p, 21, 0x1FFFFF, 0x10000000, *pi, 4);
+ GETVARINT_STEP(a, ptr, 7, 0x7F, 0x4000, *pi, 2);
+ GETVARINT_STEP(a, ptr, 14, 0x3FFF, 0x200000, *pi, 3);
+ GETVARINT_STEP(a, ptr, 21, 0x1FFFFF, 0x10000000, *pi, 4);
a = (a & 0x0FFFFFFF );
- *pi = (int)(a | ((u32)(*p & 0x0F) << 28));
+ *pi = (int)(a | ((u32)(*ptr & 0x07) << 28));
+ assert( 0==(a & 0x80000000) );
+ assert( *pi>=0 );
return 5;
}
/*
** Return the number of bytes required to encode v as a varint
*/
-SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64 v){
+SQLITE_PRIVATE int tdsqlite3Fts3VarintLen(tdsqlite3_uint64 v){
int i = 0;
do{
i++;
@@ -146657,7 +169006,7 @@ SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64 v){
** `mno` becomes mno
**
*/
-SQLITE_PRIVATE void sqlite3Fts3Dequote(char *z){
+SQLITE_PRIVATE void tdsqlite3Fts3Dequote(char *z){
char quote; /* Quote character (if any ) */
quote = z[0];
@@ -146686,9 +169035,9 @@ SQLITE_PRIVATE void sqlite3Fts3Dequote(char *z){
** to the first byte past the end of the varint. Add the value of the varint
** to *pVal.
*/
-static void fts3GetDeltaVarint(char **pp, sqlite3_int64 *pVal){
- sqlite3_int64 iVal;
- *pp += sqlite3Fts3GetVarint(*pp, &iVal);
+static void fts3GetDeltaVarint(char **pp, tdsqlite3_int64 *pVal){
+ tdsqlite3_int64 iVal;
+ *pp += tdsqlite3Fts3GetVarint(*pp, &iVal);
*pVal += iVal;
}
@@ -146704,9 +169053,9 @@ static void fts3GetDeltaVarint(char **pp, sqlite3_int64 *pVal){
static void fts3GetReverseVarint(
char **pp,
char *pStart,
- sqlite3_int64 *pVal
+ tdsqlite3_int64 *pVal
){
- sqlite3_int64 iVal;
+ tdsqlite3_int64 iVal;
char *p;
/* Pointer p now points at the first byte past the varint we are
@@ -146716,14 +169065,14 @@ static void fts3GetReverseVarint(
p++;
*pp = p;
- sqlite3Fts3GetVarint(p, &iVal);
+ tdsqlite3Fts3GetVarint(p, &iVal);
*pVal = iVal;
}
/*
** The xDisconnect() virtual table method.
*/
-static int fts3DisconnectMethod(sqlite3_vtab *pVtab){
+static int fts3DisconnectMethod(tdsqlite3_vtab *pVtab){
Fts3Table *p = (Fts3Table *)pVtab;
int i;
@@ -146731,30 +169080,31 @@ static int fts3DisconnectMethod(sqlite3_vtab *pVtab){
assert( p->pSegments==0 );
/* Free any prepared statements held */
+ tdsqlite3_finalize(p->pSeekStmt);
for(i=0; i<SizeofArray(p->aStmt); i++){
- sqlite3_finalize(p->aStmt[i]);
+ tdsqlite3_finalize(p->aStmt[i]);
}
- sqlite3_free(p->zSegmentsTbl);
- sqlite3_free(p->zReadExprlist);
- sqlite3_free(p->zWriteExprlist);
- sqlite3_free(p->zContentTbl);
- sqlite3_free(p->zLanguageid);
+ tdsqlite3_free(p->zSegmentsTbl);
+ tdsqlite3_free(p->zReadExprlist);
+ tdsqlite3_free(p->zWriteExprlist);
+ tdsqlite3_free(p->zContentTbl);
+ tdsqlite3_free(p->zLanguageid);
/* Invoke the tokenizer destructor to free the tokenizer. */
p->pTokenizer->pModule->xDestroy(p->pTokenizer);
- sqlite3_free(p);
+ tdsqlite3_free(p);
return SQLITE_OK;
}
/*
** Write an error message into *pzErr
*/
-SQLITE_PRIVATE void sqlite3Fts3ErrMsg(char **pzErr, const char *zFormat, ...){
+SQLITE_PRIVATE void tdsqlite3Fts3ErrMsg(char **pzErr, const char *zFormat, ...){
va_list ap;
- sqlite3_free(*pzErr);
+ tdsqlite3_free(*pzErr);
va_start(ap, zFormat);
- *pzErr = sqlite3_vmprintf(zFormat, ap);
+ *pzErr = tdsqlite3_vmprintf(zFormat, ap);
va_end(ap);
}
@@ -146767,7 +169117,7 @@ SQLITE_PRIVATE void sqlite3Fts3ErrMsg(char **pzErr, const char *zFormat, ...){
*/
static void fts3DbExec(
int *pRc, /* Success code */
- sqlite3 *db, /* Database in which to run SQL */
+ tdsqlite3 *db, /* Database in which to run SQL */
const char *zFormat, /* Format string for SQL */
... /* Arguments to the format string */
){
@@ -146775,33 +169125,38 @@ static void fts3DbExec(
char *zSql;
if( *pRc ) return;
va_start(ap, zFormat);
- zSql = sqlite3_vmprintf(zFormat, ap);
+ zSql = tdsqlite3_vmprintf(zFormat, ap);
va_end(ap);
if( zSql==0 ){
*pRc = SQLITE_NOMEM;
}else{
- *pRc = sqlite3_exec(db, zSql, 0, 0, 0);
- sqlite3_free(zSql);
+ *pRc = tdsqlite3_exec(db, zSql, 0, 0, 0);
+ tdsqlite3_free(zSql);
}
}
/*
** The xDestroy() virtual table method.
*/
-static int fts3DestroyMethod(sqlite3_vtab *pVtab){
+static int fts3DestroyMethod(tdsqlite3_vtab *pVtab){
Fts3Table *p = (Fts3Table *)pVtab;
int rc = SQLITE_OK; /* Return code */
const char *zDb = p->zDb; /* Name of database (e.g. "main", "temp") */
- sqlite3 *db = p->db; /* Database handle */
+ tdsqlite3 *db = p->db; /* Database handle */
/* Drop the shadow tables */
- if( p->zContentTbl==0 ){
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", zDb, p->zName);
- }
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", zDb,p->zName);
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", zDb, p->zName);
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", zDb, p->zName);
- fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", zDb, p->zName);
+ fts3DbExec(&rc, db,
+ "DROP TABLE IF EXISTS %Q.'%q_segments';"
+ "DROP TABLE IF EXISTS %Q.'%q_segdir';"
+ "DROP TABLE IF EXISTS %Q.'%q_docsize';"
+ "DROP TABLE IF EXISTS %Q.'%q_stat';"
+ "%s DROP TABLE IF EXISTS %Q.'%q_content';",
+ zDb, p->zName,
+ zDb, p->zName,
+ zDb, p->zName,
+ zDb, p->zName,
+ (p->zContentTbl ? "--" : ""), zDb,p->zName
+ );
/* If everything has worked, invoke fts3DisconnectMethod() to free the
** memory associated with the Fts3Table structure and return SQLITE_OK.
@@ -146812,7 +169167,7 @@ static int fts3DestroyMethod(sqlite3_vtab *pVtab){
/*
-** Invoke sqlite3_declare_vtab() to declare the schema for the FTS3 table
+** Invoke tdsqlite3_declare_vtab() to declare the schema for the FTS3 table
** passed as the first argument. This is done as part of the xConnect()
** and xCreate() methods.
**
@@ -146829,27 +169184,27 @@ static void fts3DeclareVtab(int *pRc, Fts3Table *p){
const char *zLanguageid;
zLanguageid = (p->zLanguageid ? p->zLanguageid : "__langid");
- sqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+ tdsqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
/* Create a list of user columns for the virtual table */
- zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]);
+ zCols = tdsqlite3_mprintf("%Q, ", p->azColumn[0]);
for(i=1; zCols && i<p->nColumn; i++){
- zCols = sqlite3_mprintf("%z%Q, ", zCols, p->azColumn[i]);
+ zCols = tdsqlite3_mprintf("%z%Q, ", zCols, p->azColumn[i]);
}
/* Create the whole "CREATE TABLE" statement to pass to SQLite */
- zSql = sqlite3_mprintf(
+ zSql = tdsqlite3_mprintf(
"CREATE TABLE x(%s %Q HIDDEN, docid HIDDEN, %Q HIDDEN)",
zCols, p->zName, zLanguageid
);
if( !zCols || !zSql ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_declare_vtab(p->db, zSql);
+ rc = tdsqlite3_declare_vtab(p->db, zSql);
}
- sqlite3_free(zSql);
- sqlite3_free(zCols);
+ tdsqlite3_free(zSql);
+ tdsqlite3_free(zCols);
*pRc = rc;
}
}
@@ -146857,7 +169212,7 @@ static void fts3DeclareVtab(int *pRc, Fts3Table *p){
/*
** Create the %_stat table if it does not already exist.
*/
-SQLITE_PRIVATE void sqlite3Fts3CreateStatTable(int *pRc, Fts3Table *p){
+SQLITE_PRIVATE void tdsqlite3Fts3CreateStatTable(int *pRc, Fts3Table *p){
fts3DbExec(pRc, p->db,
"CREATE TABLE IF NOT EXISTS %Q.'%q_stat'"
"(id INTEGER PRIMARY KEY, value BLOB);",
@@ -146878,20 +169233,20 @@ SQLITE_PRIVATE void sqlite3Fts3CreateStatTable(int *pRc, Fts3Table *p){
static int fts3CreateTables(Fts3Table *p){
int rc = SQLITE_OK; /* Return code */
int i; /* Iterator variable */
- sqlite3 *db = p->db; /* The database connection */
+ tdsqlite3 *db = p->db; /* The database connection */
if( p->zContentTbl==0 ){
const char *zLanguageid = p->zLanguageid;
char *zContentCols; /* Columns of %_content table */
/* Create a list of user columns for the content table */
- zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY");
+ zContentCols = tdsqlite3_mprintf("docid INTEGER PRIMARY KEY");
for(i=0; zContentCols && i<p->nColumn; i++){
char *z = p->azColumn[i];
- zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z);
+ zContentCols = tdsqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z);
}
if( zLanguageid && zContentCols ){
- zContentCols = sqlite3_mprintf("%z, langid", zContentCols, zLanguageid);
+ zContentCols = tdsqlite3_mprintf("%z, langid", zContentCols, zLanguageid);
}
if( zContentCols==0 ) rc = SQLITE_NOMEM;
@@ -146900,7 +169255,7 @@ static int fts3CreateTables(Fts3Table *p){
"CREATE TABLE %Q.'%q_content'(%s)",
p->zDb, p->zName, zContentCols
);
- sqlite3_free(zContentCols);
+ tdsqlite3_free(zContentCols);
}
/* Create other tables */
@@ -146928,7 +169283,7 @@ static int fts3CreateTables(Fts3Table *p){
}
assert( p->bHasStat==p->bFts4 );
if( p->bHasStat ){
- sqlite3Fts3CreateStatTable(&rc, p);
+ tdsqlite3Fts3CreateStatTable(&rc, p);
}
return rc;
}
@@ -146944,24 +169299,24 @@ static void fts3DatabasePageSize(int *pRc, Fts3Table *p){
if( *pRc==SQLITE_OK ){
int rc; /* Return code */
char *zSql; /* SQL text "PRAGMA %Q.page_size" */
- sqlite3_stmt *pStmt; /* Compiled "PRAGMA %Q.page_size" statement */
+ tdsqlite3_stmt *pStmt; /* Compiled "PRAGMA %Q.page_size" statement */
- zSql = sqlite3_mprintf("PRAGMA %Q.page_size", p->zDb);
+ zSql = tdsqlite3_mprintf("PRAGMA %Q.page_size", p->zDb);
if( !zSql ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0);
+ rc = tdsqlite3_prepare(p->db, zSql, -1, &pStmt, 0);
if( rc==SQLITE_OK ){
- sqlite3_step(pStmt);
- p->nPgsz = sqlite3_column_int(pStmt, 0);
- rc = sqlite3_finalize(pStmt);
+ tdsqlite3_step(pStmt);
+ p->nPgsz = tdsqlite3_column_int(pStmt, 0);
+ rc = tdsqlite3_finalize(pStmt);
}else if( rc==SQLITE_AUTH ){
p->nPgsz = 1024;
rc = SQLITE_OK;
}
}
assert( p->nPgsz>0 || rc!=SQLITE_OK );
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
*pRc = rc;
}
}
@@ -146988,9 +169343,9 @@ static int fts3IsSpecialColumn(
}
*pnKey = (int)(zCsr-z);
- zValue = sqlite3_mprintf("%s", &zCsr[1]);
+ zValue = tdsqlite3_mprintf("%s", &zCsr[1]);
if( zValue ){
- sqlite3Fts3Dequote(zValue);
+ tdsqlite3Fts3Dequote(zValue);
}
*pzValue = zValue;
return 1;
@@ -147009,15 +169364,15 @@ static void fts3Appendf(
va_list ap;
char *z;
va_start(ap, zFormat);
- z = sqlite3_vmprintf(zFormat, ap);
+ z = tdsqlite3_vmprintf(zFormat, ap);
va_end(ap);
if( z && *pz ){
- char *z2 = sqlite3_mprintf("%s%s", *pz, z);
- sqlite3_free(z);
+ char *z2 = tdsqlite3_mprintf("%s%s", *pz, z);
+ tdsqlite3_free(z);
z = z2;
}
if( z==0 ) *pRc = SQLITE_NOMEM;
- sqlite3_free(*pz);
+ tdsqlite3_free(*pz);
*pz = z;
}
}
@@ -147028,15 +169383,15 @@ static void fts3Appendf(
**
** fts3QuoteId("un \"zip\"") -> "un \"\"zip\"\""
**
-** The pointer returned points to memory obtained from sqlite3_malloc(). It
-** is the callers responsibility to call sqlite3_free() to release this
+** The pointer returned points to memory obtained from tdsqlite3_malloc(). It
+** is the callers responsibility to call tdsqlite3_free() to release this
** memory.
*/
static char *fts3QuoteId(char const *zInput){
- int nRet;
+ tdsqlite3_int64 nRet;
char *zRet;
nRet = 2 + (int)strlen(zInput)*2 + 1;
- zRet = sqlite3_malloc(nRet);
+ zRet = tdsqlite3_malloc64(nRet);
if( zRet ){
int i;
char *z = zRet;
@@ -147066,7 +169421,7 @@ static char *fts3QuoteId(char const *zInput){
**
** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c') FROM %_content AS x"
**
-** The pointer returned points to a buffer allocated by sqlite3_malloc(). It
+** The pointer returned points to a buffer allocated by tdsqlite3_malloc(). It
** is the responsibility of the caller to eventually free it.
**
** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and
@@ -147093,7 +169448,7 @@ static char *fts3ReadExprList(Fts3Table *p, const char *zFunc, int *pRc){
if( p->zLanguageid ){
fts3Appendf(pRc, &zRet, ", x.%Q", "langid");
}
- sqlite3_free(zFree);
+ tdsqlite3_free(zFree);
}else{
fts3Appendf(pRc, &zRet, "rowid");
for(i=0; i<p->nColumn; i++){
@@ -147123,7 +169478,7 @@ static char *fts3ReadExprList(Fts3Table *p, const char *zFunc, int *pRc){
**
** "?, zip(?), zip(?), zip(?)"
**
-** The pointer returned points to a buffer allocated by sqlite3_malloc(). It
+** The pointer returned points to a buffer allocated by tdsqlite3_malloc(). It
** is the responsibility of the caller to eventually free it.
**
** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and
@@ -147149,7 +169504,7 @@ static char *fts3WriteExprList(Fts3Table *p, const char *zFunc, int *pRc){
if( p->zLanguageid ){
fts3Appendf(pRc, &zRet, ", ?");
}
- sqlite3_free(zFree);
+ tdsqlite3_free(zFree);
return zRet;
}
@@ -147199,7 +169554,7 @@ static int fts3GobbleInt(const char **pp, int *pnOut){
** array. If an error does occur, an SQLite error code is returned.
**
** Regardless of whether or not an error is returned, it is the responsibility
-** of the caller to call sqlite3_free() on the output array to free it.
+** of the caller to call tdsqlite3_free() on the output array to free it.
*/
static int fts3PrefixParameter(
const char *zParam, /* ABC in prefix=ABC parameter to parse */
@@ -147217,7 +169572,7 @@ static int fts3PrefixParameter(
}
}
- aIndex = sqlite3_malloc(sizeof(struct Fts3Index) * nIndex);
+ aIndex = tdsqlite3_malloc64(sizeof(struct Fts3Index) * nIndex);
*apIndex = aIndex;
if( !aIndex ){
return SQLITE_NOMEM;
@@ -147264,14 +169619,14 @@ static int fts3PrefixParameter(
** the name of the corresponding column in table xxx. The array
** and its contents are allocated using a single allocation. It
** is the responsibility of the caller to free this allocation
-** by eventually passing the *pazCol value to sqlite3_free().
+** by eventually passing the *pazCol value to tdsqlite3_free().
**
** If the table cannot be found, an error code is returned and the output
** variables are undefined. Or, if an OOM is encountered, SQLITE_NOMEM is
** returned (and the output variables are undefined).
*/
static int fts3ContentColumns(
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
const char *zDb, /* Name of db (i.e. "main", "temp" etc.) */
const char *zTbl, /* Name of content table */
const char ***pazCol, /* OUT: Malloc'd array of column names */
@@ -147281,49 +169636,49 @@ static int fts3ContentColumns(
){
int rc = SQLITE_OK; /* Return code */
char *zSql; /* "SELECT *" statement on zTbl */
- sqlite3_stmt *pStmt = 0; /* Compiled version of zSql */
+ tdsqlite3_stmt *pStmt = 0; /* Compiled version of zSql */
- zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", zDb, zTbl);
+ zSql = tdsqlite3_mprintf("SELECT * FROM %Q.%Q", zDb, zTbl);
if( !zSql ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
+ rc = tdsqlite3_prepare(db, zSql, -1, &pStmt, 0);
if( rc!=SQLITE_OK ){
- sqlite3Fts3ErrMsg(pzErr, "%s", sqlite3_errmsg(db));
+ tdsqlite3Fts3ErrMsg(pzErr, "%s", tdsqlite3_errmsg(db));
}
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
if( rc==SQLITE_OK ){
const char **azCol; /* Output array */
- int nStr = 0; /* Size of all column names (incl. 0x00) */
+ tdsqlite3_int64 nStr = 0; /* Size of all column names (incl. 0x00) */
int nCol; /* Number of table columns */
int i; /* Used to iterate through columns */
/* Loop through the returned columns. Set nStr to the number of bytes of
** space required to store a copy of each column name, including the
** nul-terminator byte. */
- nCol = sqlite3_column_count(pStmt);
+ nCol = tdsqlite3_column_count(pStmt);
for(i=0; i<nCol; i++){
- const char *zCol = sqlite3_column_name(pStmt, i);
- nStr += (int)strlen(zCol) + 1;
+ const char *zCol = tdsqlite3_column_name(pStmt, i);
+ nStr += strlen(zCol) + 1;
}
/* Allocate and populate the array to return. */
- azCol = (const char **)sqlite3_malloc(sizeof(char *) * nCol + nStr);
+ azCol = (const char **)tdsqlite3_malloc64(sizeof(char *) * nCol + nStr);
if( azCol==0 ){
rc = SQLITE_NOMEM;
}else{
char *p = (char *)&azCol[nCol];
for(i=0; i<nCol; i++){
- const char *zCol = sqlite3_column_name(pStmt, i);
+ const char *zCol = tdsqlite3_column_name(pStmt, i);
int n = (int)strlen(zCol)+1;
memcpy(p, zCol, n);
azCol[i] = p;
p += n;
}
}
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
/* Set the output variables. */
*pnCol = nCol;
@@ -147347,18 +169702,18 @@ static int fts3ContentColumns(
*/
static int fts3InitVtab(
int isCreate, /* True for xCreate, false for xConnect */
- sqlite3 *db, /* The SQLite database connection */
+ tdsqlite3 *db, /* The SQLite database connection */
void *pAux, /* Hash table containing tokenizers */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
+ tdsqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
char **pzErr /* Write any error message here */
){
Fts3Hash *pHash = (Fts3Hash *)pAux;
Fts3Table *p = 0; /* Pointer to allocated vtab */
int rc = SQLITE_OK; /* Return code */
int i; /* Iterator variable */
- int nByte; /* Size of allocation used for *p */
+ tdsqlite3_int64 nByte; /* Size of allocation used for *p */
int iCol; /* Column index */
int nString = 0; /* Bytes required to hold all column names */
int nCol = 0; /* Number of columns in the FTS table */
@@ -147367,7 +169722,7 @@ static int fts3InitVtab(
int nName; /* Bytes required to hold table name */
int isFts4 = (argv[0][3]=='4'); /* True for FTS4, false for FTS3 */
const char **aCol; /* Array of column names */
- sqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */
+ tdsqlite3_tokenizer *pTokenizer = 0; /* Tokenizer for this table */
int nIndex = 0; /* Size of aIndex[] array */
struct Fts3Index *aIndex = 0; /* Array of indexes for this table */
@@ -147384,18 +169739,18 @@ static int fts3InitVtab(
int nNotindexed = 0; /* Size of azNotindexed[] array */
assert( strlen(argv[0])==4 );
- assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
- || (sqlite3_strnicmp(argv[0], "fts3", 4)==0 && !isFts4)
+ assert( (tdsqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
+ || (tdsqlite3_strnicmp(argv[0], "fts3", 4)==0 && !isFts4)
);
nDb = (int)strlen(argv[1]) + 1;
nName = (int)strlen(argv[2]) + 1;
nByte = sizeof(const char *) * (argc-2);
- aCol = (const char **)sqlite3_malloc(nByte);
+ aCol = (const char **)tdsqlite3_malloc64(nByte);
if( aCol ){
memset((void*)aCol, 0, nByte);
- azNotindexed = (char **)sqlite3_malloc(nByte);
+ azNotindexed = (char **)tdsqlite3_malloc64(nByte);
}
if( azNotindexed ){
memset(azNotindexed, 0, nByte);
@@ -147424,10 +169779,10 @@ static int fts3InitVtab(
/* Check if this is a tokenizer specification */
if( !pTokenizer
&& strlen(z)>8
- && 0==sqlite3_strnicmp(z, "tokenize", 8)
- && 0==sqlite3Fts3IsIdChar(z[8])
+ && 0==tdsqlite3_strnicmp(z, "tokenize", 8)
+ && 0==tdsqlite3Fts3IsIdChar(z[8])
){
- rc = sqlite3Fts3InitTokenizer(pHash, &z[9], &pTokenizer, pzErr);
+ rc = tdsqlite3Fts3InitTokenizer(pHash, &z[9], &pTokenizer, pzErr);
}
/* Check if it is an FTS4 special argument. */
@@ -147452,71 +169807,72 @@ static int fts3InitVtab(
}else{
for(iOpt=0; iOpt<SizeofArray(aFts4Opt); iOpt++){
struct Fts4Option *pOp = &aFts4Opt[iOpt];
- if( nKey==pOp->nOpt && !sqlite3_strnicmp(z, pOp->zOpt, pOp->nOpt) ){
+ if( nKey==pOp->nOpt && !tdsqlite3_strnicmp(z, pOp->zOpt, pOp->nOpt) ){
break;
}
}
- if( iOpt==SizeofArray(aFts4Opt) ){
- sqlite3Fts3ErrMsg(pzErr, "unrecognized parameter: %s", z);
- rc = SQLITE_ERROR;
- }else{
- switch( iOpt ){
- case 0: /* MATCHINFO */
- if( strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "fts3", 4) ){
- sqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo: %s", zVal);
- rc = SQLITE_ERROR;
- }
- bNoDocsize = 1;
- break;
+ switch( iOpt ){
+ case 0: /* MATCHINFO */
+ if( strlen(zVal)!=4 || tdsqlite3_strnicmp(zVal, "fts3", 4) ){
+ tdsqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo: %s", zVal);
+ rc = SQLITE_ERROR;
+ }
+ bNoDocsize = 1;
+ break;
- case 1: /* PREFIX */
- sqlite3_free(zPrefix);
- zPrefix = zVal;
- zVal = 0;
- break;
+ case 1: /* PREFIX */
+ tdsqlite3_free(zPrefix);
+ zPrefix = zVal;
+ zVal = 0;
+ break;
- case 2: /* COMPRESS */
- sqlite3_free(zCompress);
- zCompress = zVal;
- zVal = 0;
- break;
+ case 2: /* COMPRESS */
+ tdsqlite3_free(zCompress);
+ zCompress = zVal;
+ zVal = 0;
+ break;
- case 3: /* UNCOMPRESS */
- sqlite3_free(zUncompress);
- zUncompress = zVal;
- zVal = 0;
- break;
+ case 3: /* UNCOMPRESS */
+ tdsqlite3_free(zUncompress);
+ zUncompress = zVal;
+ zVal = 0;
+ break;
- case 4: /* ORDER */
- if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3))
- && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4))
- ){
- sqlite3Fts3ErrMsg(pzErr, "unrecognized order: %s", zVal);
- rc = SQLITE_ERROR;
- }
- bDescIdx = (zVal[0]=='d' || zVal[0]=='D');
- break;
+ case 4: /* ORDER */
+ if( (strlen(zVal)!=3 || tdsqlite3_strnicmp(zVal, "asc", 3))
+ && (strlen(zVal)!=4 || tdsqlite3_strnicmp(zVal, "desc", 4))
+ ){
+ tdsqlite3Fts3ErrMsg(pzErr, "unrecognized order: %s", zVal);
+ rc = SQLITE_ERROR;
+ }
+ bDescIdx = (zVal[0]=='d' || zVal[0]=='D');
+ break;
- case 5: /* CONTENT */
- sqlite3_free(zContent);
- zContent = zVal;
- zVal = 0;
- break;
+ case 5: /* CONTENT */
+ tdsqlite3_free(zContent);
+ zContent = zVal;
+ zVal = 0;
+ break;
- case 6: /* LANGUAGEID */
- assert( iOpt==6 );
- sqlite3_free(zLanguageid);
- zLanguageid = zVal;
- zVal = 0;
- break;
+ case 6: /* LANGUAGEID */
+ assert( iOpt==6 );
+ tdsqlite3_free(zLanguageid);
+ zLanguageid = zVal;
+ zVal = 0;
+ break;
- case 7: /* NOTINDEXED */
- azNotindexed[nNotindexed++] = zVal;
- zVal = 0;
- break;
- }
+ case 7: /* NOTINDEXED */
+ azNotindexed[nNotindexed++] = zVal;
+ zVal = 0;
+ break;
+
+ default:
+ assert( iOpt==SizeofArray(aFts4Opt) );
+ tdsqlite3Fts3ErrMsg(pzErr, "unrecognized parameter: %s", z);
+ rc = SQLITE_ERROR;
+ break;
}
- sqlite3_free(zVal);
+ tdsqlite3_free(zVal);
}
}
@@ -147535,12 +169891,12 @@ static int fts3InitVtab(
** TABLE statement, use all columns from the content table.
*/
if( rc==SQLITE_OK && zContent ){
- sqlite3_free(zCompress);
- sqlite3_free(zUncompress);
+ tdsqlite3_free(zCompress);
+ tdsqlite3_free(zUncompress);
zCompress = 0;
zUncompress = 0;
if( nCol==0 ){
- sqlite3_free((void*)aCol);
+ tdsqlite3_free((void*)aCol);
aCol = 0;
rc = fts3ContentColumns(db, argv[1], zContent,&aCol,&nCol,&nString,pzErr);
@@ -147549,7 +169905,7 @@ static int fts3InitVtab(
if( rc==SQLITE_OK && zLanguageid ){
int j;
for(j=0; j<nCol; j++){
- if( sqlite3_stricmp(zLanguageid, aCol[j])==0 ){
+ if( tdsqlite3_stricmp(zLanguageid, aCol[j])==0 ){
int k;
for(k=j; k<nCol; k++) aCol[k] = aCol[k+1];
nCol--;
@@ -147569,7 +169925,7 @@ static int fts3InitVtab(
}
if( pTokenizer==0 ){
- rc = sqlite3Fts3InitTokenizer(pHash, "simple", &pTokenizer, pzErr);
+ rc = tdsqlite3Fts3InitTokenizer(pHash, "simple", &pTokenizer, pzErr);
if( rc!=SQLITE_OK ) goto fts3_init_out;
}
assert( pTokenizer );
@@ -147577,7 +169933,7 @@ static int fts3InitVtab(
rc = fts3PrefixParameter(zPrefix, &nIndex, &aIndex);
if( rc==SQLITE_ERROR ){
assert( zPrefix );
- sqlite3Fts3ErrMsg(pzErr, "error parsing prefix parameter: %s", zPrefix);
+ tdsqlite3Fts3ErrMsg(pzErr, "error parsing prefix parameter: %s", zPrefix);
}
if( rc!=SQLITE_OK ) goto fts3_init_out;
@@ -147589,7 +169945,7 @@ static int fts3InitVtab(
nName + /* zName */
nDb + /* zDb */
nString; /* Space for azColumn strings */
- p = (Fts3Table*)sqlite3_malloc(nByte);
+ p = (Fts3Table*)tdsqlite3_malloc64(nByte);
if( p==0 ){
rc = SQLITE_NOMEM;
goto fts3_init_out;
@@ -147602,9 +169958,9 @@ static int fts3InitVtab(
p->pTokenizer = pTokenizer;
p->nMaxPendingData = FTS3_MAX_PENDING_DATA;
p->bHasDocsize = (isFts4 && bNoDocsize==0);
- p->bHasStat = isFts4;
- p->bFts4 = isFts4;
- p->bDescIdx = bDescIdx;
+ p->bHasStat = (u8)isFts4;
+ p->bFts4 = (u8)isFts4;
+ p->bDescIdx = (u8)bDescIdx;
p->nAutoincrmerge = 0xff; /* 0xff means setting unknown */
p->zContentTbl = zContent;
p->zLanguageid = zLanguageid;
@@ -147634,10 +169990,12 @@ static int fts3InitVtab(
for(iCol=0; iCol<nCol; iCol++){
char *z;
int n = 0;
- z = (char *)sqlite3Fts3NextToken(aCol[iCol], &n);
- memcpy(zCsr, z, n);
+ z = (char *)tdsqlite3Fts3NextToken(aCol[iCol], &n);
+ if( n>0 ){
+ memcpy(zCsr, z, n);
+ }
zCsr[n] = '\0';
- sqlite3Fts3Dequote(zCsr);
+ tdsqlite3Fts3Dequote(zCsr);
p->azColumn[iCol] = zCsr;
zCsr += n+1;
assert( zCsr <= &((char *)p)[nByte] );
@@ -147649,17 +170007,17 @@ static int fts3InitVtab(
for(i=0; i<nNotindexed; i++){
char *zNot = azNotindexed[i];
if( zNot && n==(int)strlen(zNot)
- && 0==sqlite3_strnicmp(p->azColumn[iCol], zNot, n)
+ && 0==tdsqlite3_strnicmp(p->azColumn[iCol], zNot, n)
){
p->abNotindexed[iCol] = 1;
- sqlite3_free(zNot);
+ tdsqlite3_free(zNot);
azNotindexed[i] = 0;
}
}
}
for(i=0; i<nNotindexed; i++){
if( azNotindexed[i] ){
- sqlite3Fts3ErrMsg(pzErr, "no such column: %s", azNotindexed[i]);
+ tdsqlite3Fts3ErrMsg(pzErr, "no such column: %s", azNotindexed[i]);
rc = SQLITE_ERROR;
}
}
@@ -147667,7 +170025,7 @@ static int fts3InitVtab(
if( rc==SQLITE_OK && (zCompress==0)!=(zUncompress==0) ){
char const *zMiss = (zCompress==0 ? "compress" : "uncompress");
rc = SQLITE_ERROR;
- sqlite3Fts3ErrMsg(pzErr, "missing %s parameter in fts4 constructor", zMiss);
+ tdsqlite3Fts3ErrMsg(pzErr, "missing %s parameter in fts4 constructor", zMiss);
}
p->zReadExprlist = fts3ReadExprList(p, zUncompress, &rc);
p->zWriteExprlist = fts3WriteExprList(p, zCompress, &rc);
@@ -147692,22 +170050,26 @@ static int fts3InitVtab(
fts3DatabasePageSize(&rc, p);
p->nNodeSize = p->nPgsz-35;
+#if defined(SQLITE_DEBUG)||defined(SQLITE_TEST)
+ p->nMergeCount = FTS3_MERGE_COUNT;
+#endif
+
/* Declare the table schema to SQLite. */
fts3DeclareVtab(&rc, p);
fts3_init_out:
- sqlite3_free(zPrefix);
- sqlite3_free(aIndex);
- sqlite3_free(zCompress);
- sqlite3_free(zUncompress);
- sqlite3_free(zContent);
- sqlite3_free(zLanguageid);
- for(i=0; i<nNotindexed; i++) sqlite3_free(azNotindexed[i]);
- sqlite3_free((void *)aCol);
- sqlite3_free((void *)azNotindexed);
+ tdsqlite3_free(zPrefix);
+ tdsqlite3_free(aIndex);
+ tdsqlite3_free(zCompress);
+ tdsqlite3_free(zUncompress);
+ tdsqlite3_free(zContent);
+ tdsqlite3_free(zLanguageid);
+ for(i=0; i<nNotindexed; i++) tdsqlite3_free(azNotindexed[i]);
+ tdsqlite3_free((void *)aCol);
+ tdsqlite3_free((void *)azNotindexed);
if( rc!=SQLITE_OK ){
if( p ){
- fts3DisconnectMethod((sqlite3_vtab *)p);
+ fts3DisconnectMethod((tdsqlite3_vtab *)p);
}else if( pTokenizer ){
pTokenizer->pModule->xDestroy(pTokenizer);
}
@@ -147723,22 +170085,22 @@ fts3_init_out:
** work is done in function fts3InitVtab().
*/
static int fts3ConnectMethod(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
void *pAux, /* Pointer to tokenizer hash table */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
- char **pzErr /* OUT: sqlite3_malloc'd error message */
+ tdsqlite3_vtab **ppVtab, /* OUT: New tdsqlite3_vtab object */
+ char **pzErr /* OUT: tdsqlite3_malloc'd error message */
){
return fts3InitVtab(0, db, pAux, argc, argv, ppVtab, pzErr);
}
static int fts3CreateMethod(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
void *pAux, /* Pointer to tokenizer hash table */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
- char **pzErr /* OUT: sqlite3_malloc'd error message */
+ tdsqlite3_vtab **ppVtab, /* OUT: New tdsqlite3_vtab object */
+ char **pzErr /* OUT: tdsqlite3_malloc'd error message */
){
return fts3InitVtab(1, db, pAux, argc, argv, ppVtab, pzErr);
}
@@ -147748,9 +170110,9 @@ static int fts3CreateMethod(
** extension is currently being used by a version of SQLite too old to
** support estimatedRows. In that case this function is a no-op.
*/
-static void fts3SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){
+static void fts3SetEstimatedRows(tdsqlite3_index_info *pIdxInfo, i64 nRow){
#if SQLITE_VERSION_NUMBER>=3008002
- if( sqlite3_libversion_number()>=3008002 ){
+ if( tdsqlite3_libversion_number()>=3008002 ){
pIdxInfo->estimatedRows = nRow;
}
#endif
@@ -147761,9 +170123,9 @@ static void fts3SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){
** extension is currently being used by a version of SQLite too old to
** support index-info flags. In that case this function is a no-op.
*/
-static void fts3SetUniqueFlag(sqlite3_index_info *pIdxInfo){
+static void fts3SetUniqueFlag(tdsqlite3_index_info *pIdxInfo){
#if SQLITE_VERSION_NUMBER>=3008012
- if( sqlite3_libversion_number()>=3008012 ){
+ if( tdsqlite3_libversion_number()>=3008012 ){
pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE;
}
#endif
@@ -147777,7 +170139,7 @@ static void fts3SetUniqueFlag(sqlite3_index_info *pIdxInfo){
** 2. Full-text search using a MATCH operator on a non-docid column.
** 3. Linear scan of %_content table.
*/
-static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
+static int fts3BestIndexMethod(tdsqlite3_vtab *pVTab, tdsqlite3_index_info *pInfo){
Fts3Table *p = (Fts3Table *)pVTab;
int i; /* Iterator variable */
int iCons = -1; /* Index of constraint to use */
@@ -147787,6 +170149,10 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
int iDocidLe = -1; /* Index of docid<=x constraint, if present */
int iIdx;
+ if( p->bLock ){
+ return SQLITE_ERROR;
+ }
+
/* By default use a full table scan. This is an expensive option,
** so search through the constraints to see if a more efficient
** strategy is possible.
@@ -147795,7 +170161,7 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
pInfo->estimatedCost = 5000000;
for(i=0; i<pInfo->nConstraint; i++){
int bDocid; /* True if this constraint is on docid */
- struct sqlite3_index_constraint *pCons = &pInfo->aConstraint[i];
+ struct tdsqlite3_index_constraint *pCons = &pInfo->aConstraint[i];
if( pCons->usable==0 ){
if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH ){
/* There exists an unusable MATCH constraint. This means that if
@@ -147805,7 +170171,7 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
** this, return a very high cost here. */
pInfo->idxNum = FTS3_FULLSCAN_SEARCH;
pInfo->estimatedCost = 1e50;
- fts3SetEstimatedRows(pInfo, ((sqlite3_int64)1) << 50);
+ fts3SetEstimatedRows(pInfo, ((tdsqlite3_int64)1) << 50);
return SQLITE_OK;
}
continue;
@@ -147884,7 +170250,7 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
** docid) order. Both ascending and descending are possible.
*/
if( pInfo->nOrderBy==1 ){
- struct sqlite3_index_orderby *pOrder = &pInfo->aOrderBy[0];
+ struct tdsqlite3_index_orderby *pOrder = &pInfo->aOrderBy[0];
if( pOrder->iColumn<0 || pOrder->iColumn==p->nColumn+1 ){
if( pOrder->desc ){
pInfo->idxStr = "DESC";
@@ -147902,8 +170268,8 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
/*
** Implementation of xOpen method.
*/
-static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
- sqlite3_vtab_cursor *pCsr; /* Allocated cursor */
+static int fts3OpenMethod(tdsqlite3_vtab *pVTab, tdsqlite3_vtab_cursor **ppCsr){
+ tdsqlite3_vtab_cursor *pCsr; /* Allocated cursor */
UNUSED_PARAMETER(pVTab);
@@ -147911,7 +170277,7 @@ static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
** allocation succeeds, zero it and return SQLITE_OK. Otherwise,
** if the allocation fails, return SQLITE_NOMEM.
*/
- *ppCsr = pCsr = (sqlite3_vtab_cursor *)sqlite3_malloc(sizeof(Fts3Cursor));
+ *ppCsr = pCsr = (tdsqlite3_vtab_cursor *)tdsqlite3_malloc(sizeof(Fts3Cursor));
if( !pCsr ){
return SQLITE_NOMEM;
}
@@ -147920,19 +170286,48 @@ static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
}
/*
+** Finalize the statement handle at pCsr->pStmt.
+**
+** Or, if that statement handle is one created by fts3CursorSeekStmt(),
+** and the Fts3Table.pSeekStmt slot is currently NULL, save the statement
+** pointer there instead of finalizing it.
+*/
+static void fts3CursorFinalizeStmt(Fts3Cursor *pCsr){
+ if( pCsr->bSeekStmt ){
+ Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
+ if( p->pSeekStmt==0 ){
+ p->pSeekStmt = pCsr->pStmt;
+ tdsqlite3_reset(pCsr->pStmt);
+ pCsr->pStmt = 0;
+ }
+ pCsr->bSeekStmt = 0;
+ }
+ tdsqlite3_finalize(pCsr->pStmt);
+}
+
+/*
+** Free all resources currently held by the cursor passed as the only
+** argument.
+*/
+static void fts3ClearCursor(Fts3Cursor *pCsr){
+ fts3CursorFinalizeStmt(pCsr);
+ tdsqlite3Fts3FreeDeferredTokens(pCsr);
+ tdsqlite3_free(pCsr->aDoclist);
+ tdsqlite3Fts3MIBufferFree(pCsr->pMIBuffer);
+ tdsqlite3Fts3ExprFree(pCsr->pExpr);
+ memset(&(&pCsr->base)[1], 0, sizeof(Fts3Cursor)-sizeof(tdsqlite3_vtab_cursor));
+}
+
+/*
** Close the cursor. For additional information see the documentation
** on the xClose method of the virtual table interface.
*/
-static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){
+static int fts3CloseMethod(tdsqlite3_vtab_cursor *pCursor){
Fts3Cursor *pCsr = (Fts3Cursor *)pCursor;
assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
- sqlite3_finalize(pCsr->pStmt);
- sqlite3Fts3ExprFree(pCsr->pExpr);
- sqlite3Fts3FreeDeferredTokens(pCsr);
- sqlite3_free(pCsr->aDoclist);
- sqlite3Fts3MIBufferFree(pCsr->pMIBuffer);
+ fts3ClearCursor(pCsr);
assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
- sqlite3_free(pCsr);
+ tdsqlite3_free(pCsr);
return SQLITE_OK;
}
@@ -147944,20 +170339,27 @@ static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){
**
** (or the equivalent for a content=xxx table) and set pCsr->pStmt to
** it. If an error occurs, return an SQLite error code.
-**
-** Otherwise, set *ppStmt to point to pCsr->pStmt and return SQLITE_OK.
*/
-static int fts3CursorSeekStmt(Fts3Cursor *pCsr, sqlite3_stmt **ppStmt){
+static int fts3CursorSeekStmt(Fts3Cursor *pCsr){
int rc = SQLITE_OK;
if( pCsr->pStmt==0 ){
Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
char *zSql;
- zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist);
- if( !zSql ) return SQLITE_NOMEM;
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
- sqlite3_free(zSql);
+ if( p->pSeekStmt ){
+ pCsr->pStmt = p->pSeekStmt;
+ p->pSeekStmt = 0;
+ }else{
+ zSql = tdsqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist);
+ if( !zSql ) return SQLITE_NOMEM;
+ p->bLock++;
+ rc = tdsqlite3_prepare_v3(
+ p->db, zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0
+ );
+ p->bLock--;
+ tdsqlite3_free(zSql);
+ }
+ if( rc==SQLITE_OK ) pCsr->bSeekStmt = 1;
}
- *ppStmt = pCsr->pStmt;
return rc;
}
@@ -147966,19 +170368,21 @@ static int fts3CursorSeekStmt(Fts3Cursor *pCsr, sqlite3_stmt **ppStmt){
** of the %_content table that contains the last match. Return
** SQLITE_OK on success.
*/
-static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){
+static int fts3CursorSeek(tdsqlite3_context *pContext, Fts3Cursor *pCsr){
int rc = SQLITE_OK;
if( pCsr->isRequireSeek ){
- sqlite3_stmt *pStmt = 0;
-
- rc = fts3CursorSeekStmt(pCsr, &pStmt);
+ rc = fts3CursorSeekStmt(pCsr);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId);
+ Fts3Table *pTab = (Fts3Table*)pCsr->base.pVtab;
+ pTab->bLock++;
+ tdsqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId);
pCsr->isRequireSeek = 0;
- if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){
+ if( SQLITE_ROW==tdsqlite3_step(pCsr->pStmt) ){
+ pTab->bLock--;
return SQLITE_OK;
}else{
- rc = sqlite3_reset(pCsr->pStmt);
+ pTab->bLock--;
+ rc = tdsqlite3_reset(pCsr->pStmt);
if( rc==SQLITE_OK && ((Fts3Table *)pCsr->base.pVtab)->zContentTbl==0 ){
/* If no row was found and no error has occurred, then the %_content
** table is missing a row that is present in the full-text index.
@@ -147991,7 +170395,7 @@ static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){
}
if( rc!=SQLITE_OK && pContext ){
- sqlite3_result_error_code(pContext, rc);
+ tdsqlite3_result_error_code(pContext, rc);
}
return rc;
}
@@ -148016,16 +170420,16 @@ static int fts3ScanInteriorNode(
int nTerm, /* Size of term zTerm in bytes */
const char *zNode, /* Buffer containing segment interior node */
int nNode, /* Size of buffer at zNode */
- sqlite3_int64 *piFirst, /* OUT: Selected child node */
- sqlite3_int64 *piLast /* OUT: Selected child node */
+ tdsqlite3_int64 *piFirst, /* OUT: Selected child node */
+ tdsqlite3_int64 *piLast /* OUT: Selected child node */
){
int rc = SQLITE_OK; /* Return code */
const char *zCsr = zNode; /* Cursor to iterate through node */
const char *zEnd = &zCsr[nNode];/* End of interior node buffer */
char *zBuffer = 0; /* Buffer to load terms into */
- int nAlloc = 0; /* Size of allocated buffer */
+ i64 nAlloc = 0; /* Size of allocated buffer */
int isFirstTerm = 1; /* True when processing first term on page */
- sqlite3_int64 iChild; /* Block id of child node to descend to */
+ tdsqlite3_int64 iChild; /* Block id of child node to descend to */
/* Skip over the 'height' varint that occurs at the start of every
** interior node. Then load the blockid of the left-child of the b-tree
@@ -148038,10 +170442,10 @@ static int fts3ScanInteriorNode(
** either more than 20 bytes of allocated space following the nNode bytes of
** contents, or two zero bytes. Or, if the node is read from the %_segments
** table, then there are always 20 bytes of zeroed padding following the
- ** nNode bytes of content (see sqlite3Fts3ReadBlock() for details).
+ ** nNode bytes of content (see tdsqlite3Fts3ReadBlock() for details).
*/
- zCsr += sqlite3Fts3GetVarint(zCsr, &iChild);
- zCsr += sqlite3Fts3GetVarint(zCsr, &iChild);
+ zCsr += tdsqlite3Fts3GetVarint(zCsr, &iChild);
+ zCsr += tdsqlite3Fts3GetVarint(zCsr, &iChild);
if( zCsr>zEnd ){
return FTS_CORRUPT_VTAB;
}
@@ -148060,14 +170464,15 @@ static int fts3ScanInteriorNode(
isFirstTerm = 0;
zCsr += fts3GetVarint32(zCsr, &nSuffix);
- if( nPrefix<0 || nSuffix<0 || &zCsr[nSuffix]>zEnd ){
+ assert( nPrefix>=0 && nSuffix>=0 );
+ if( nPrefix>zCsr-zNode || nSuffix>zEnd-zCsr || nSuffix==0 ){
rc = FTS_CORRUPT_VTAB;
goto finish_scan;
}
- if( nPrefix+nSuffix>nAlloc ){
+ if( (i64)nPrefix+nSuffix>nAlloc ){
char *zNew;
- nAlloc = (nPrefix+nSuffix) * 2;
- zNew = (char *)sqlite3_realloc(zBuffer, nAlloc);
+ nAlloc = ((i64)nPrefix+nSuffix) * 2;
+ zNew = (char *)tdsqlite3_realloc64(zBuffer, nAlloc);
if( !zNew ){
rc = SQLITE_NOMEM;
goto finish_scan;
@@ -148106,7 +170511,7 @@ static int fts3ScanInteriorNode(
if( piLast ) *piLast = iChild;
finish_scan:
- sqlite3_free(zBuffer);
+ tdsqlite3_free(zBuffer);
return rc;
}
@@ -148138,8 +170543,8 @@ static int fts3SelectLeaf(
int nTerm, /* Size of term zTerm in bytes */
const char *zNode, /* Buffer containing segment interior node */
int nNode, /* Size of buffer at zNode */
- sqlite3_int64 *piLeaf, /* Selected leaf node */
- sqlite3_int64 *piLeaf2 /* Selected leaf node */
+ tdsqlite3_int64 *piLeaf, /* Selected leaf node */
+ tdsqlite3_int64 *piLeaf2 /* Selected leaf node */
){
int rc = SQLITE_OK; /* Return code */
int iHeight; /* Height of this node in tree */
@@ -148148,29 +170553,35 @@ static int fts3SelectLeaf(
fts3GetVarint32(zNode, &iHeight);
rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2);
- assert( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) );
+ assert_fts3_nc( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) );
if( rc==SQLITE_OK && iHeight>1 ){
char *zBlob = 0; /* Blob read from %_segments table */
int nBlob = 0; /* Size of zBlob in bytes */
if( piLeaf && piLeaf2 && (*piLeaf!=*piLeaf2) ){
- rc = sqlite3Fts3ReadBlock(p, *piLeaf, &zBlob, &nBlob, 0);
+ rc = tdsqlite3Fts3ReadBlock(p, *piLeaf, &zBlob, &nBlob, 0);
if( rc==SQLITE_OK ){
rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, 0);
}
- sqlite3_free(zBlob);
+ tdsqlite3_free(zBlob);
piLeaf = 0;
zBlob = 0;
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3ReadBlock(p, piLeaf?*piLeaf:*piLeaf2, &zBlob, &nBlob, 0);
+ rc = tdsqlite3Fts3ReadBlock(p, piLeaf?*piLeaf:*piLeaf2, &zBlob, &nBlob, 0);
}
if( rc==SQLITE_OK ){
- rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, piLeaf2);
+ int iNewHeight = 0;
+ fts3GetVarint32(zBlob, &iNewHeight);
+ if( iNewHeight>=iHeight ){
+ rc = FTS_CORRUPT_VTAB;
+ }else{
+ rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, piLeaf2);
+ }
}
- sqlite3_free(zBlob);
+ tdsqlite3_free(zBlob);
}
return rc;
@@ -148182,11 +170593,11 @@ static int fts3SelectLeaf(
*/
static void fts3PutDeltaVarint(
char **pp, /* IN/OUT: Output pointer */
- sqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */
- sqlite3_int64 iVal /* Write this value to the list */
+ tdsqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */
+ tdsqlite3_int64 iVal /* Write this value to the list */
){
assert( iVal-*piPrev > 0 || (*piPrev==0 && iVal==0) );
- *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev);
+ *pp += tdsqlite3Fts3PutVarint(*pp, iVal-*piPrev);
*piPrev = iVal;
}
@@ -148273,10 +170684,11 @@ static void fts3ColumnlistCopy(char **pp, char **ppPoslist){
}
/*
-** Value used to signify the end of an position-list. This is safe because
-** it is not possible to have a document with 2^31 terms.
+** Value used to signify the end of an position-list. This must be
+** as large or larger than any value that might appear on the
+** position-list, even a position list that has been corrupted.
*/
-#define POSITION_LIST_END 0x7fffffff
+#define POSITION_LIST_END LARGEST_INT64
/*
** This function is used to help parse position-lists. When this function is
@@ -148298,7 +170710,7 @@ static void fts3ColumnlistCopy(char **pp, char **ppPoslist){
*/
static void fts3ReadNextPos(
char **pp, /* IN/OUT: Pointer into position-list buffer */
- sqlite3_int64 *pi /* IN/OUT: Value read from position-list */
+ tdsqlite3_int64 *pi /* IN/OUT: Value read from position-list */
){
if( (**pp)&0xFE ){
fts3GetDeltaVarint(pp, pi);
@@ -148321,7 +170733,7 @@ static int fts3PutColNumber(char **pp, int iCol){
int n = 0; /* Number of bytes written */
if( iCol ){
char *p = *pp; /* Output pointer */
- n = 1 + sqlite3Fts3PutVarint(&p[1], iCol);
+ n = 1 + tdsqlite3Fts3PutVarint(&p[1], iCol);
*p = 0x01;
*pp = &p[n];
}
@@ -148335,7 +170747,7 @@ static int fts3PutColNumber(char **pp, int iCol){
** updated appropriately. The caller is responsible for insuring
** that there is enough space in *pp to hold the complete output.
*/
-static void fts3PoslistMerge(
+static int fts3PoslistMerge(
char **pp, /* Output buffer */
char **pp1, /* Left input list */
char **pp2 /* Right input list */
@@ -148348,18 +170760,24 @@ static void fts3PoslistMerge(
int iCol1; /* The current column index in pp1 */
int iCol2; /* The current column index in pp2 */
- if( *p1==POS_COLUMN ) fts3GetVarint32(&p1[1], &iCol1);
- else if( *p1==POS_END ) iCol1 = POSITION_LIST_END;
+ if( *p1==POS_COLUMN ){
+ fts3GetVarint32(&p1[1], &iCol1);
+ if( iCol1==0 ) return FTS_CORRUPT_VTAB;
+ }
+ else if( *p1==POS_END ) iCol1 = 0x7fffffff;
else iCol1 = 0;
- if( *p2==POS_COLUMN ) fts3GetVarint32(&p2[1], &iCol2);
- else if( *p2==POS_END ) iCol2 = POSITION_LIST_END;
+ if( *p2==POS_COLUMN ){
+ fts3GetVarint32(&p2[1], &iCol2);
+ if( iCol2==0 ) return FTS_CORRUPT_VTAB;
+ }
+ else if( *p2==POS_END ) iCol2 = 0x7fffffff;
else iCol2 = 0;
if( iCol1==iCol2 ){
- sqlite3_int64 i1 = 0; /* Last position from pp1 */
- sqlite3_int64 i2 = 0; /* Last position from pp2 */
- sqlite3_int64 iPrev = 0;
+ tdsqlite3_int64 i1 = 0; /* Last position from pp1 */
+ tdsqlite3_int64 i2 = 0; /* Last position from pp2 */
+ tdsqlite3_int64 iPrev = 0;
int n = fts3PutColNumber(&p, iCol1);
p1 += n;
p2 += n;
@@ -148400,6 +170818,7 @@ static void fts3PoslistMerge(
*pp = p;
*pp1 = p1 + 1;
*pp2 = p2 + 1;
+ return SQLITE_OK;
}
/*
@@ -148455,25 +170874,24 @@ static int fts3PoslistPhraseMerge(
while( 1 ){
if( iCol1==iCol2 ){
char *pSave = p;
- sqlite3_int64 iPrev = 0;
- sqlite3_int64 iPos1 = 0;
- sqlite3_int64 iPos2 = 0;
+ tdsqlite3_int64 iPrev = 0;
+ tdsqlite3_int64 iPos1 = 0;
+ tdsqlite3_int64 iPos2 = 0;
if( iCol1 ){
*p++ = POS_COLUMN;
- p += sqlite3Fts3PutVarint(p, iCol1);
+ p += tdsqlite3Fts3PutVarint(p, iCol1);
}
- assert( *p1!=POS_END && *p1!=POS_COLUMN );
- assert( *p2!=POS_END && *p2!=POS_COLUMN );
fts3GetDeltaVarint(&p1, &iPos1); iPos1 -= 2;
fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2;
+ if( iPos1<0 || iPos2<0 ) break;
while( 1 ){
if( iPos2==iPos1+nToken
|| (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken)
){
- sqlite3_int64 iSave;
+ tdsqlite3_int64 iSave;
iSave = isSaveLeft ? iPos1 : iPos2;
fts3PutDeltaVarint(&p, &iPrev, iSave+2); iPrev -= 2;
pSave = 0;
@@ -148611,17 +171029,17 @@ static void fts3GetDeltaVarint3(
char **pp, /* IN/OUT: Point to read varint from */
char *pEnd, /* End of buffer */
int bDescIdx, /* True if docids are descending */
- sqlite3_int64 *pVal /* IN/OUT: Integer value */
+ tdsqlite3_int64 *pVal /* IN/OUT: Integer value */
){
if( *pp>=pEnd ){
*pp = 0;
}else{
- sqlite3_int64 iVal;
- *pp += sqlite3Fts3GetVarint(*pp, &iVal);
+ u64 iVal;
+ *pp += tdsqlite3Fts3GetVarintU(*pp, &iVal);
if( bDescIdx ){
- *pVal -= iVal;
+ *pVal = (i64)((u64)*pVal - iVal);
}else{
- *pVal += iVal;
+ *pVal = (i64)((u64)*pVal + iVal);
}
}
}
@@ -148644,19 +171062,21 @@ static void fts3GetDeltaVarint3(
static void fts3PutDeltaVarint3(
char **pp, /* IN/OUT: Output pointer */
int bDescIdx, /* True for descending docids */
- sqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */
+ tdsqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */
int *pbFirst, /* IN/OUT: True after first int written */
- sqlite3_int64 iVal /* Write this value to the list */
+ tdsqlite3_int64 iVal /* Write this value to the list */
){
- sqlite3_int64 iWrite;
+ tdsqlite3_uint64 iWrite;
if( bDescIdx==0 || *pbFirst==0 ){
- iWrite = iVal - *piPrev;
+ assert_fts3_nc( *pbFirst==0 || iVal>=*piPrev );
+ iWrite = (u64)iVal - (u64)*piPrev;
}else{
- iWrite = *piPrev - iVal;
+ assert_fts3_nc( *piPrev>=iVal );
+ iWrite = (u64)*piPrev - (u64)iVal;
}
assert( *pbFirst || *piPrev==0 );
- assert( *pbFirst==0 || iWrite>0 );
- *pp += sqlite3Fts3PutVarint(*pp, iWrite);
+ assert_fts3_nc( *pbFirst==0 || iWrite>0 );
+ *pp += tdsqlite3Fts3PutVarint(*pp, iWrite);
*piPrev = iVal;
*pbFirst = 1;
}
@@ -148671,7 +171091,8 @@ static void fts3PutDeltaVarint3(
** Using this makes it easier to write code that can merge doclists that are
** sorted in either ascending or descending order.
*/
-#define DOCID_CMP(i1, i2) ((bDescDoclist?-1:1) * (i1-i2))
+/* #define DOCID_CMP(i1, i2) ((bDescDoclist?-1:1) * (i64)((u64)i1-i2)) */
+#define DOCID_CMP(i1, i2) ((bDescDoclist?-1:1) * (i1>i2?1:((i1==i2)?0:-1)))
/*
** This function does an "OR" merge of two doclists (output contains all
@@ -148680,7 +171101,7 @@ static void fts3PutDeltaVarint3(
** should be false. If they are sorted in ascending order, it should be
** passed a non-zero value.
**
-** If no error occurs, *paOut is set to point at an sqlite3_malloc'd buffer
+** If no error occurs, *paOut is set to point at an tdsqlite3_malloc'd buffer
** containing the output doclist and SQLITE_OK is returned. In this case
** *pnOut is set to the number of bytes in the output doclist.
**
@@ -148693,9 +171114,10 @@ static int fts3DoclistOrMerge(
char *a2, int n2, /* Second doclist */
char **paOut, int *pnOut /* OUT: Malloc'd doclist */
){
- sqlite3_int64 i1 = 0;
- sqlite3_int64 i2 = 0;
- sqlite3_int64 iPrev = 0;
+ int rc = SQLITE_OK;
+ tdsqlite3_int64 i1 = 0;
+ tdsqlite3_int64 i2 = 0;
+ tdsqlite3_int64 iPrev = 0;
char *pEnd1 = &a1[n1];
char *pEnd2 = &a2[n2];
char *p1 = a1;
@@ -148736,18 +171158,19 @@ static int fts3DoclistOrMerge(
** A symetric argument may be made if the doclists are in descending
** order.
*/
- aOut = sqlite3_malloc(n1+n2+FTS3_VARINT_MAX-1);
+ aOut = tdsqlite3_malloc64((i64)n1+n2+FTS3_VARINT_MAX-1+FTS3_BUFFER_PADDING);
if( !aOut ) return SQLITE_NOMEM;
p = aOut;
fts3GetDeltaVarint3(&p1, pEnd1, 0, &i1);
fts3GetDeltaVarint3(&p2, pEnd2, 0, &i2);
while( p1 || p2 ){
- sqlite3_int64 iDiff = DOCID_CMP(i1, i2);
+ tdsqlite3_int64 iDiff = DOCID_CMP(i1, i2);
if( p2 && p1 && iDiff==0 ){
fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i1);
- fts3PoslistMerge(&p, &p1, &p2);
+ rc = fts3PoslistMerge(&p, &p1, &p2);
+ if( rc ) break;
fts3GetDeltaVarint3(&p1, pEnd1, bDescDoclist, &i1);
fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2);
}else if( !p2 || (p1 && iDiff<0) ){
@@ -148759,12 +171182,20 @@ static int fts3DoclistOrMerge(
fts3PoslistCopy(&p, &p2);
fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2);
}
+
+ assert( (p-aOut)<=((p1?(p1-a1):n1)+(p2?(p2-a2):n2)+FTS3_VARINT_MAX-1) );
}
+ if( rc!=SQLITE_OK ){
+ tdsqlite3_free(aOut);
+ p = aOut = 0;
+ }else{
+ assert( (p-aOut)<=n1+n2+FTS3_VARINT_MAX-1 );
+ memset(&aOut[(p-aOut)], 0, FTS3_BUFFER_PADDING);
+ }
*paOut = aOut;
*pnOut = (int)(p-aOut);
- assert( *pnOut<=n1+n2+FTS3_VARINT_MAX-1 );
- return SQLITE_OK;
+ return rc;
}
/*
@@ -148785,9 +171216,9 @@ static int fts3DoclistPhraseMerge(
char *aLeft, int nLeft, /* Left doclist */
char **paRight, int *pnRight /* IN/OUT: Right/output doclist */
){
- sqlite3_int64 i1 = 0;
- sqlite3_int64 i2 = 0;
- sqlite3_int64 iPrev = 0;
+ tdsqlite3_int64 i1 = 0;
+ tdsqlite3_int64 i2 = 0;
+ tdsqlite3_int64 iPrev = 0;
char *aRight = *paRight;
char *pEnd1 = &aLeft[nLeft];
char *pEnd2 = &aRight[*pnRight];
@@ -148799,7 +171230,7 @@ static int fts3DoclistPhraseMerge(
assert( nDist>0 );
if( bDescDoclist ){
- aOut = sqlite3_malloc(*pnRight + FTS3_VARINT_MAX);
+ aOut = tdsqlite3_malloc64((tdsqlite3_int64)*pnRight + FTS3_VARINT_MAX);
if( aOut==0 ) return SQLITE_NOMEM;
}else{
aOut = aRight;
@@ -148810,10 +171241,10 @@ static int fts3DoclistPhraseMerge(
fts3GetDeltaVarint3(&p2, pEnd2, 0, &i2);
while( p1 && p2 ){
- sqlite3_int64 iDiff = DOCID_CMP(i1, i2);
+ tdsqlite3_int64 iDiff = DOCID_CMP(i1, i2);
if( iDiff==0 ){
char *pSave = p;
- sqlite3_int64 iPrevSave = iPrev;
+ tdsqlite3_int64 iPrevSave = iPrev;
int bFirstOutSave = bFirstOut;
fts3PutDeltaVarint3(&p, bDescDoclist, &iPrev, &bFirstOut, i1);
@@ -148835,7 +171266,7 @@ static int fts3DoclistPhraseMerge(
*pnRight = (int)(p - aOut);
if( bDescDoclist ){
- sqlite3_free(aRight);
+ tdsqlite3_free(aRight);
*paRight = aOut;
}
@@ -148850,8 +171281,8 @@ static int fts3DoclistPhraseMerge(
** of the entries from pList at position 0, and terminated by an 0x00 byte.
** The value returned is the number of bytes written to pOut (if any).
*/
-SQLITE_PRIVATE int sqlite3Fts3FirstFilter(
- sqlite3_int64 iDelta, /* Varint that may be written to pOut */
+SQLITE_PRIVATE int tdsqlite3Fts3FirstFilter(
+ tdsqlite3_int64 iDelta, /* Varint that may be written to pOut */
char *pList, /* Position list (no 0x00 term) */
int nList, /* Size of pList in bytes */
char *pOut /* Write output here */
@@ -148863,24 +171294,24 @@ SQLITE_PRIVATE int sqlite3Fts3FirstFilter(
if( *p!=0x01 ){
if( *p==0x02 ){
- nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta);
+ nOut += tdsqlite3Fts3PutVarint(&pOut[nOut], iDelta);
pOut[nOut++] = 0x02;
bWritten = 1;
}
fts3ColumnlistCopy(0, &p);
}
- while( p<pEnd && *p==0x01 ){
- sqlite3_int64 iCol;
+ while( p<pEnd ){
+ tdsqlite3_int64 iCol;
p++;
- p += sqlite3Fts3GetVarint(p, &iCol);
+ p += tdsqlite3Fts3GetVarint(p, &iCol);
if( *p==0x02 ){
if( bWritten==0 ){
- nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta);
+ nOut += tdsqlite3Fts3PutVarint(&pOut[nOut], iDelta);
bWritten = 1;
}
pOut[nOut++] = 0x01;
- nOut += sqlite3Fts3PutVarint(&pOut[nOut], iCol);
+ nOut += tdsqlite3Fts3PutVarint(&pOut[nOut], iCol);
pOut[nOut++] = 0x02;
}
fts3ColumnlistCopy(0, &p);
@@ -148924,12 +171355,12 @@ static int fts3TermSelectFinishMerge(Fts3Table *p, TermSelect *pTS){
pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut, &aNew, &nNew
);
if( rc!=SQLITE_OK ){
- sqlite3_free(aOut);
+ tdsqlite3_free(aOut);
return rc;
}
- sqlite3_free(pTS->aaOutput[i]);
- sqlite3_free(aOut);
+ tdsqlite3_free(pTS->aaOutput[i]);
+ tdsqlite3_free(aOut);
pTS->aaOutput[i] = 0;
aOut = aNew;
nOut = nNew;
@@ -148979,10 +171410,11 @@ static int fts3TermSelectMerge(
**
** Similar padding is added in the fts3DoclistOrMerge() function.
*/
- pTS->aaOutput[0] = sqlite3_malloc(nDoclist + FTS3_VARINT_MAX + 1);
+ pTS->aaOutput[0] = tdsqlite3_malloc(nDoclist + FTS3_VARINT_MAX + 1);
pTS->anOutput[0] = nDoclist;
if( pTS->aaOutput[0] ){
memcpy(pTS->aaOutput[0], aDoclist, nDoclist);
+ memset(&pTS->aaOutput[0][nDoclist], 0, FTS3_VARINT_MAX);
}else{
return SQLITE_NOMEM;
}
@@ -149005,12 +171437,12 @@ static int fts3TermSelectMerge(
pTS->aaOutput[iOut], pTS->anOutput[iOut], &aNew, &nNew
);
if( rc!=SQLITE_OK ){
- if( aMerge!=aDoclist ) sqlite3_free(aMerge);
+ if( aMerge!=aDoclist ) tdsqlite3_free(aMerge);
return rc;
}
- if( aMerge!=aDoclist ) sqlite3_free(aMerge);
- sqlite3_free(pTS->aaOutput[iOut]);
+ if( aMerge!=aDoclist ) tdsqlite3_free(aMerge);
+ tdsqlite3_free(pTS->aaOutput[iOut]);
pTS->aaOutput[iOut] = 0;
aMerge = aNew;
@@ -149034,10 +171466,10 @@ static int fts3SegReaderCursorAppend(
){
if( (pCsr->nSegment%16)==0 ){
Fts3SegReader **apNew;
- int nByte = (pCsr->nSegment + 16)*sizeof(Fts3SegReader*);
- apNew = (Fts3SegReader **)sqlite3_realloc(pCsr->apSegment, nByte);
+ tdsqlite3_int64 nByte = (pCsr->nSegment + 16)*sizeof(Fts3SegReader*);
+ apNew = (Fts3SegReader **)tdsqlite3_realloc64(pCsr->apSegment, nByte);
if( !apNew ){
- sqlite3Fts3SegReaderFree(pNew);
+ tdsqlite3Fts3SegReaderFree(pNew);
return SQLITE_NOMEM;
}
pCsr->apSegment = apNew;
@@ -149065,8 +171497,8 @@ static int fts3SegReaderCursor(
Fts3MultiSegReader *pCsr /* Cursor object to populate */
){
int rc = SQLITE_OK; /* Error code */
- sqlite3_stmt *pStmt = 0; /* Statement to iterate through segments */
- int rc2; /* Result of sqlite3_reset() */
+ tdsqlite3_stmt *pStmt = 0; /* Statement to iterate through segments */
+ int rc2; /* Result of tdsqlite3_reset() */
/* If iLevel is less than 0 and this is not a scan, include a seg-reader
** for the pending-terms. If this is a scan, then this call must be being
@@ -149074,9 +171506,9 @@ static int fts3SegReaderCursor(
** Fts3SegReaderPending might segfault, as the data structures used by
** fts4aux are not completely populated. So it's easiest to filter these
** calls out here. */
- if( iLevel<0 && p->aIndex ){
+ if( iLevel<0 && p->aIndex && p->iPrevLangid==iLangid ){
Fts3SegReader *pSeg = 0;
- rc = sqlite3Fts3SegReaderPending(p, iIndex, zTerm, nTerm, isPrefix||isScan, &pSeg);
+ rc = tdsqlite3Fts3SegReaderPending(p, iIndex, zTerm, nTerm, isPrefix||isScan, &pSeg);
if( rc==SQLITE_OK && pSeg ){
rc = fts3SegReaderCursorAppend(pCsr, pSeg);
}
@@ -149084,29 +171516,29 @@ static int fts3SegReaderCursor(
if( iLevel!=FTS3_SEGCURSOR_PENDING ){
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3AllSegdirs(p, iLangid, iIndex, iLevel, &pStmt);
+ rc = tdsqlite3Fts3AllSegdirs(p, iLangid, iIndex, iLevel, &pStmt);
}
- while( rc==SQLITE_OK && SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){
+ while( rc==SQLITE_OK && SQLITE_ROW==(rc = tdsqlite3_step(pStmt)) ){
Fts3SegReader *pSeg = 0;
/* Read the values returned by the SELECT into local variables. */
- sqlite3_int64 iStartBlock = sqlite3_column_int64(pStmt, 1);
- sqlite3_int64 iLeavesEndBlock = sqlite3_column_int64(pStmt, 2);
- sqlite3_int64 iEndBlock = sqlite3_column_int64(pStmt, 3);
- int nRoot = sqlite3_column_bytes(pStmt, 4);
- char const *zRoot = sqlite3_column_blob(pStmt, 4);
+ tdsqlite3_int64 iStartBlock = tdsqlite3_column_int64(pStmt, 1);
+ tdsqlite3_int64 iLeavesEndBlock = tdsqlite3_column_int64(pStmt, 2);
+ tdsqlite3_int64 iEndBlock = tdsqlite3_column_int64(pStmt, 3);
+ int nRoot = tdsqlite3_column_bytes(pStmt, 4);
+ char const *zRoot = tdsqlite3_column_blob(pStmt, 4);
/* If zTerm is not NULL, and this segment is not stored entirely on its
** root node, the range of leaves scanned can be reduced. Do this. */
- if( iStartBlock && zTerm ){
- sqlite3_int64 *pi = (isPrefix ? &iLeavesEndBlock : 0);
+ if( iStartBlock && zTerm && zRoot ){
+ tdsqlite3_int64 *pi = (isPrefix ? &iLeavesEndBlock : 0);
rc = fts3SelectLeaf(p, zTerm, nTerm, zRoot, nRoot, &iStartBlock, pi);
if( rc!=SQLITE_OK ) goto finished;
if( isPrefix==0 && isScan==0 ) iLeavesEndBlock = iStartBlock;
}
- rc = sqlite3Fts3SegReaderNew(pCsr->nSegment+1,
+ rc = tdsqlite3Fts3SegReaderNew(pCsr->nSegment+1,
(isPrefix==0 && isScan==0),
iStartBlock, iLeavesEndBlock,
iEndBlock, zRoot, nRoot, &pSeg
@@ -149117,7 +171549,7 @@ static int fts3SegReaderCursor(
}
finished:
- rc2 = sqlite3_reset(pStmt);
+ rc2 = tdsqlite3_reset(pStmt);
if( rc==SQLITE_DONE ) rc = rc2;
return rc;
@@ -149127,7 +171559,7 @@ static int fts3SegReaderCursor(
** Set up a cursor object for iterating through a full-text index or a
** single level therein.
*/
-SQLITE_PRIVATE int sqlite3Fts3SegReaderCursor(
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderCursor(
Fts3Table *p, /* FTS3 table handle */
int iLangid, /* Language-id to search */
int iIndex, /* Index to search (from 0 to p->nIndex-1) */
@@ -149194,7 +171626,7 @@ static int fts3TermSegReaderCursor(
Fts3MultiSegReader *pSegcsr; /* Object to allocate and return */
int rc = SQLITE_NOMEM; /* Return code */
- pSegcsr = sqlite3_malloc(sizeof(Fts3MultiSegReader));
+ pSegcsr = tdsqlite3_malloc(sizeof(Fts3MultiSegReader));
if( pSegcsr ){
int i;
int bFound = 0; /* True once an index has been found */
@@ -149204,7 +171636,7 @@ static int fts3TermSegReaderCursor(
for(i=1; bFound==0 && i<p->nIndex; i++){
if( p->aIndex[i].nPrefix==nTerm ){
bFound = 1;
- rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid,
+ rc = tdsqlite3Fts3SegReaderCursor(p, pCsr->iLangid,
i, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 0, 0, pSegcsr
);
pSegcsr->bLookup = 1;
@@ -149214,7 +171646,7 @@ static int fts3TermSegReaderCursor(
for(i=1; bFound==0 && i<p->nIndex; i++){
if( p->aIndex[i].nPrefix==nTerm+1 ){
bFound = 1;
- rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid,
+ rc = tdsqlite3Fts3SegReaderCursor(p, pCsr->iLangid,
i, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 1, 0, pSegcsr
);
if( rc==SQLITE_OK ){
@@ -149227,7 +171659,7 @@ static int fts3TermSegReaderCursor(
}
if( bFound==0 ){
- rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid,
+ rc = tdsqlite3Fts3SegReaderCursor(p, pCsr->iLangid,
0, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, 0, pSegcsr
);
pSegcsr->bLookup = !isPrefix;
@@ -149242,8 +171674,8 @@ static int fts3TermSegReaderCursor(
** Free an Fts3MultiSegReader allocated by fts3TermSegReaderCursor().
*/
static void fts3SegReaderCursorFree(Fts3MultiSegReader *pSegcsr){
- sqlite3Fts3SegReaderFinish(pSegcsr);
- sqlite3_free(pSegcsr);
+ tdsqlite3Fts3SegReaderFinish(pSegcsr);
+ tdsqlite3_free(pSegcsr);
}
/*
@@ -149273,9 +171705,9 @@ static int fts3TermSelect(
filter.zTerm = pTok->z;
filter.nTerm = pTok->n;
- rc = sqlite3Fts3SegReaderStart(p, pSegcsr, &filter);
+ rc = tdsqlite3Fts3SegReaderStart(p, pSegcsr, &filter);
while( SQLITE_OK==rc
- && SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pSegcsr))
+ && SQLITE_ROW==(rc = tdsqlite3Fts3SegReaderStep(p, pSegcsr))
){
rc = fts3TermSelectMerge(p, &tsc, pSegcsr->aDoclist, pSegcsr->nDoclist);
}
@@ -149289,7 +171721,7 @@ static int fts3TermSelect(
}else{
int i;
for(i=0; i<SizeofArray(tsc.aaOutput); i++){
- sqlite3_free(tsc.aaOutput[i]);
+ tdsqlite3_free(tsc.aaOutput[i]);
}
}
@@ -149333,17 +171765,20 @@ static int fts3DoclistCountDocids(char *aList, int nList){
** even if we reach end-of-file. The fts3EofMethod() will be called
** subsequently to determine whether or not an EOF was hit.
*/
-static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){
+static int fts3NextMethod(tdsqlite3_vtab_cursor *pCursor){
int rc;
Fts3Cursor *pCsr = (Fts3Cursor *)pCursor;
if( pCsr->eSearch==FTS3_DOCID_SEARCH || pCsr->eSearch==FTS3_FULLSCAN_SEARCH ){
- if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){
+ Fts3Table *pTab = (Fts3Table*)pCursor->pVtab;
+ pTab->bLock++;
+ if( SQLITE_ROW!=tdsqlite3_step(pCsr->pStmt) ){
pCsr->isEof = 1;
- rc = sqlite3_reset(pCsr->pStmt);
+ rc = tdsqlite3_reset(pCsr->pStmt);
}else{
- pCsr->iPrevId = sqlite3_column_int64(pCsr->pStmt, 0);
+ pCsr->iPrevId = tdsqlite3_column_int64(pCsr->pStmt, 0);
rc = SQLITE_OK;
}
+ pTab->bLock--;
}else{
rc = fts3EvalNext((Fts3Cursor *)pCursor);
}
@@ -149352,27 +171787,15 @@ static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){
}
/*
-** The following are copied from sqliteInt.h.
-**
-** Constants for the largest and smallest possible 64-bit signed integers.
-** These macros are designed to work correctly on both 32-bit and 64-bit
-** compilers.
-*/
-#ifndef SQLITE_AMALGAMATION
-# define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32))
-# define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
-#endif
-
-/*
** If the numeric type of argument pVal is "integer", then return it
** converted to a 64-bit signed integer. Otherwise, return a copy of
** the second parameter, iDefault.
*/
-static sqlite3_int64 fts3DocidRange(sqlite3_value *pVal, i64 iDefault){
+static tdsqlite3_int64 fts3DocidRange(tdsqlite3_value *pVal, i64 iDefault){
if( pVal ){
- int eType = sqlite3_value_numeric_type(pVal);
+ int eType = tdsqlite3_value_numeric_type(pVal);
if( eType==SQLITE_INTEGER ){
- return sqlite3_value_int64(pVal);
+ return tdsqlite3_value_int64(pVal);
}
}
return iDefault;
@@ -149395,11 +171818,11 @@ static sqlite3_int64 fts3DocidRange(sqlite3_value *pVal, i64 iDefault){
** side of the MATCH operator.
*/
static int fts3FilterMethod(
- sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
+ tdsqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
int idxNum, /* Strategy index */
const char *idxStr, /* Unused */
int nVal, /* Number of elements in apVal */
- sqlite3_value **apVal /* Arguments for the indexing scheme */
+ tdsqlite3_value **apVal /* Arguments for the indexing scheme */
){
int rc = SQLITE_OK;
char *zSql; /* SQL statement used to access %_content */
@@ -149407,15 +171830,19 @@ static int fts3FilterMethod(
Fts3Table *p = (Fts3Table *)pCursor->pVtab;
Fts3Cursor *pCsr = (Fts3Cursor *)pCursor;
- sqlite3_value *pCons = 0; /* The MATCH or rowid constraint, if any */
- sqlite3_value *pLangid = 0; /* The "langid = ?" constraint, if any */
- sqlite3_value *pDocidGe = 0; /* The "docid >= ?" constraint, if any */
- sqlite3_value *pDocidLe = 0; /* The "docid <= ?" constraint, if any */
+ tdsqlite3_value *pCons = 0; /* The MATCH or rowid constraint, if any */
+ tdsqlite3_value *pLangid = 0; /* The "langid = ?" constraint, if any */
+ tdsqlite3_value *pDocidGe = 0; /* The "docid >= ?" constraint, if any */
+ tdsqlite3_value *pDocidLe = 0; /* The "docid <= ?" constraint, if any */
int iIdx;
UNUSED_PARAMETER(idxStr);
UNUSED_PARAMETER(nVal);
+ if( p->bLock ){
+ return SQLITE_ERROR;
+ }
+
eSearch = (idxNum & 0x0000FFFF);
assert( eSearch>=0 && eSearch<=(FTS3_FULLTEXT_SEARCH+p->nColumn) );
assert( p->pSegments==0 );
@@ -149429,11 +171856,7 @@ static int fts3FilterMethod(
assert( iIdx==nVal );
/* In case the cursor has been used before, clear it now. */
- sqlite3_finalize(pCsr->pStmt);
- sqlite3_free(pCsr->aDoclist);
- sqlite3Fts3MIBufferFree(pCsr->pMIBuffer);
- sqlite3Fts3ExprFree(pCsr->pExpr);
- memset(&pCursor[1], 0, sizeof(Fts3Cursor)-sizeof(sqlite3_vtab_cursor));
+ fts3ClearCursor(pCsr);
/* Set the lower and upper bounds on docids to return */
pCsr->iMinDocid = fts3DocidRange(pDocidGe, SMALLEST_INT64);
@@ -149448,17 +171871,17 @@ static int fts3FilterMethod(
if( eSearch!=FTS3_DOCID_SEARCH && eSearch!=FTS3_FULLSCAN_SEARCH ){
int iCol = eSearch-FTS3_FULLTEXT_SEARCH;
- const char *zQuery = (const char *)sqlite3_value_text(pCons);
+ const char *zQuery = (const char *)tdsqlite3_value_text(pCons);
- if( zQuery==0 && sqlite3_value_type(pCons)!=SQLITE_NULL ){
+ if( zQuery==0 && tdsqlite3_value_type(pCons)!=SQLITE_NULL ){
return SQLITE_NOMEM;
}
pCsr->iLangid = 0;
- if( pLangid ) pCsr->iLangid = sqlite3_value_int(pLangid);
+ if( pLangid ) pCsr->iLangid = tdsqlite3_value_int(pLangid);
assert( p->base.zErrMsg==0 );
- rc = sqlite3Fts3ExprParse(p->pTokenizer, pCsr->iLangid,
+ rc = tdsqlite3Fts3ExprParse(p->pTokenizer, pCsr->iLangid,
p->azColumn, p->bFts4, p->nColumn, iCol, zQuery, -1, &pCsr->pExpr,
&p->base.zErrMsg
);
@@ -149467,7 +171890,7 @@ static int fts3FilterMethod(
}
rc = fts3EvalStart(pCsr);
- sqlite3Fts3SegmentsClose(p);
+ tdsqlite3Fts3SegmentsClose(p);
if( rc!=SQLITE_OK ) return rc;
pCsr->pNextId = pCsr->aDoclist;
pCsr->iPrevId = 0;
@@ -149480,26 +171903,30 @@ static int fts3FilterMethod(
*/
if( eSearch==FTS3_FULLSCAN_SEARCH ){
if( pDocidGe || pDocidLe ){
- zSql = sqlite3_mprintf(
+ zSql = tdsqlite3_mprintf(
"SELECT %s WHERE rowid BETWEEN %lld AND %lld ORDER BY rowid %s",
p->zReadExprlist, pCsr->iMinDocid, pCsr->iMaxDocid,
(pCsr->bDesc ? "DESC" : "ASC")
);
}else{
- zSql = sqlite3_mprintf("SELECT %s ORDER BY rowid %s",
+ zSql = tdsqlite3_mprintf("SELECT %s ORDER BY rowid %s",
p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC")
);
}
if( zSql ){
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
- sqlite3_free(zSql);
+ p->bLock++;
+ rc = tdsqlite3_prepare_v3(
+ p->db,zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0
+ );
+ p->bLock--;
+ tdsqlite3_free(zSql);
}else{
rc = SQLITE_NOMEM;
}
}else if( eSearch==FTS3_DOCID_SEARCH ){
- rc = fts3CursorSeekStmt(pCsr, &pCsr->pStmt);
+ rc = fts3CursorSeekStmt(pCsr);
if( rc==SQLITE_OK ){
- rc = sqlite3_bind_value(pCsr->pStmt, 1, pCons);
+ rc = tdsqlite3_bind_value(pCsr->pStmt, 1, pCons);
}
}
if( rc!=SQLITE_OK ) return rc;
@@ -149511,8 +171938,13 @@ static int fts3FilterMethod(
** This is the xEof method of the virtual table. SQLite calls this
** routine to find out if it has reached the end of a result set.
*/
-static int fts3EofMethod(sqlite3_vtab_cursor *pCursor){
- return ((Fts3Cursor *)pCursor)->isEof;
+static int fts3EofMethod(tdsqlite3_vtab_cursor *pCursor){
+ Fts3Cursor *pCsr = (Fts3Cursor*)pCursor;
+ if( pCsr->isEof ){
+ fts3ClearCursor(pCsr);
+ pCsr->isEof = 1;
+ }
+ return pCsr->isEof;
}
/*
@@ -149521,7 +171953,7 @@ static int fts3EofMethod(sqlite3_vtab_cursor *pCursor){
** exposes %_content.docid as the rowid for the virtual table. The
** rowid should be written to *pRowid.
*/
-static int fts3RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+static int fts3RowidMethod(tdsqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
Fts3Cursor *pCsr = (Fts3Cursor *) pCursor;
*pRowid = pCsr->iPrevId;
return SQLITE_OK;
@@ -149539,8 +171971,8 @@ static int fts3RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
** (iCol == p->nColumn+2) -> Langid column
*/
static int fts3ColumnMethod(
- sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
- sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
+ tdsqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
+ tdsqlite3_context *pCtx, /* Context for tdsqlite3_result_xxx() calls */
int iCol /* Index of column to read value from */
){
int rc = SQLITE_OK; /* Return Code */
@@ -149550,33 +171982,37 @@ static int fts3ColumnMethod(
/* The column value supplied by SQLite must be in range. */
assert( iCol>=0 && iCol<=p->nColumn+2 );
- if( iCol==p->nColumn+1 ){
- /* This call is a request for the "docid" column. Since "docid" is an
- ** alias for "rowid", use the xRowid() method to obtain the value.
- */
- sqlite3_result_int64(pCtx, pCsr->iPrevId);
- }else if( iCol==p->nColumn ){
- /* The extra column whose name is the same as the table.
- ** Return a blob which is a pointer to the cursor. */
- sqlite3_result_blob(pCtx, &pCsr, sizeof(pCsr), SQLITE_TRANSIENT);
- }else if( iCol==p->nColumn+2 && pCsr->pExpr ){
- sqlite3_result_int64(pCtx, pCsr->iLangid);
- }else{
- /* The requested column is either a user column (one that contains
- ** indexed data), or the language-id column. */
- rc = fts3CursorSeek(0, pCsr);
+ switch( iCol-p->nColumn ){
+ case 0:
+ /* The special 'table-name' column */
+ tdsqlite3_result_pointer(pCtx, pCsr, "fts3cursor", 0);
+ break;
- if( rc==SQLITE_OK ){
- if( iCol==p->nColumn+2 ){
- int iLangid = 0;
- if( p->zLanguageid ){
- iLangid = sqlite3_column_int(pCsr->pStmt, p->nColumn+1);
- }
- sqlite3_result_int(pCtx, iLangid);
- }else if( sqlite3_data_count(pCsr->pStmt)>(iCol+1) ){
- sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
+ case 1:
+ /* The docid column */
+ tdsqlite3_result_int64(pCtx, pCsr->iPrevId);
+ break;
+
+ case 2:
+ if( pCsr->pExpr ){
+ tdsqlite3_result_int64(pCtx, pCsr->iLangid);
+ break;
+ }else if( p->zLanguageid==0 ){
+ tdsqlite3_result_int(pCtx, 0);
+ break;
+ }else{
+ iCol = p->nColumn;
+ /* fall-through */
}
- }
+
+ default:
+ /* A user column. Or, if this is a full-table scan, possibly the
+ ** language-id column. Seek the cursor. */
+ rc = fts3CursorSeek(0, pCsr);
+ if( rc==SQLITE_OK && tdsqlite3_data_count(pCsr->pStmt)-1>iCol ){
+ tdsqlite3_result_value(pCtx, tdsqlite3_column_value(pCsr->pStmt, iCol+1));
+ }
+ break;
}
assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 );
@@ -149589,19 +172025,19 @@ static int fts3ColumnMethod(
** inserted, updated or deleted.
*/
static int fts3UpdateMethod(
- sqlite3_vtab *pVtab, /* Virtual table handle */
+ tdsqlite3_vtab *pVtab, /* Virtual table handle */
int nArg, /* Size of argument array */
- sqlite3_value **apVal, /* Array of arguments */
+ tdsqlite3_value **apVal, /* Array of arguments */
sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */
){
- return sqlite3Fts3UpdateMethod(pVtab, nArg, apVal, pRowid);
+ return tdsqlite3Fts3UpdateMethod(pVtab, nArg, apVal, pRowid);
}
/*
** Implementation of xSync() method. Flush the contents of the pending-terms
** hash-table to the database.
*/
-static int fts3SyncMethod(sqlite3_vtab *pVtab){
+static int fts3SyncMethod(tdsqlite3_vtab *pVtab){
/* Following an incremental-merge operation, assuming that the input
** segments are not completely consumed (the usual case), they are updated
@@ -149625,8 +172061,10 @@ static int fts3SyncMethod(sqlite3_vtab *pVtab){
const u32 nMinMerge = 64; /* Minimum amount of incr-merge work to do */
Fts3Table *p = (Fts3Table*)pVtab;
- int rc = sqlite3Fts3PendingTermsFlush(p);
+ int rc;
+ i64 iLastRowid = tdsqlite3_last_insert_rowid(p->db);
+ rc = tdsqlite3Fts3PendingTermsFlush(p);
if( rc==SQLITE_OK
&& p->nLeafAdd>(nMinMerge/16)
&& p->nAutoincrmerge && p->nAutoincrmerge!=0xff
@@ -149634,13 +172072,14 @@ static int fts3SyncMethod(sqlite3_vtab *pVtab){
int mxLevel = 0; /* Maximum relative level value in db */
int A; /* Incr-merge parameter A */
- rc = sqlite3Fts3MaxLevel(p, &mxLevel);
+ rc = tdsqlite3Fts3MaxLevel(p, &mxLevel);
assert( rc==SQLITE_OK || mxLevel==0 );
A = p->nLeafAdd * mxLevel;
A += (A/2);
- if( A>(int)nMinMerge ) rc = sqlite3Fts3Incrmerge(p, A, p->nAutoincrmerge);
+ if( A>(int)nMinMerge ) rc = tdsqlite3Fts3Incrmerge(p, A, p->nAutoincrmerge);
}
- sqlite3Fts3SegmentsClose(p);
+ tdsqlite3Fts3SegmentsClose(p);
+ tdsqlite3_set_last_insert_rowid(p->db, iLastRowid);
return rc;
}
@@ -149653,17 +172092,11 @@ static int fts3SyncMethod(sqlite3_vtab *pVtab){
static int fts3SetHasStat(Fts3Table *p){
int rc = SQLITE_OK;
if( p->bHasStat==2 ){
- const char *zFmt ="SELECT 1 FROM %Q.sqlite_master WHERE tbl_name='%q_stat'";
- char *zSql = sqlite3_mprintf(zFmt, p->zDb, p->zName);
- if( zSql ){
- sqlite3_stmt *pStmt = 0;
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- if( rc==SQLITE_OK ){
- int bHasStat = (sqlite3_step(pStmt)==SQLITE_ROW);
- rc = sqlite3_finalize(pStmt);
- if( rc==SQLITE_OK ) p->bHasStat = bHasStat;
- }
- sqlite3_free(zSql);
+ char *zTbl = tdsqlite3_mprintf("%s_stat", p->zName);
+ if( zTbl ){
+ int res = tdsqlite3_table_column_metadata(p->db, p->zDb, zTbl, 0,0,0,0,0,0);
+ tdsqlite3_free(zTbl);
+ p->bHasStat = (res==SQLITE_OK);
}else{
rc = SQLITE_NOMEM;
}
@@ -149674,7 +172107,7 @@ static int fts3SetHasStat(Fts3Table *p){
/*
** Implementation of xBegin() method.
*/
-static int fts3BeginMethod(sqlite3_vtab *pVtab){
+static int fts3BeginMethod(tdsqlite3_vtab *pVtab){
Fts3Table *p = (Fts3Table*)pVtab;
UNUSED_PARAMETER(pVtab);
assert( p->pSegments==0 );
@@ -149691,7 +172124,7 @@ static int fts3BeginMethod(sqlite3_vtab *pVtab){
** the pending-terms hash-table have already been flushed into the database
** by fts3SyncMethod().
*/
-static int fts3CommitMethod(sqlite3_vtab *pVtab){
+static int fts3CommitMethod(tdsqlite3_vtab *pVtab){
TESTONLY( Fts3Table *p = (Fts3Table*)pVtab );
UNUSED_PARAMETER(pVtab);
assert( p->nPendingData==0 );
@@ -149706,9 +172139,9 @@ static int fts3CommitMethod(sqlite3_vtab *pVtab){
** Implementation of xRollback(). Discard the contents of the pending-terms
** hash-table. Any changes made to the database are reverted by SQLite.
*/
-static int fts3RollbackMethod(sqlite3_vtab *pVtab){
+static int fts3RollbackMethod(tdsqlite3_vtab *pVtab){
Fts3Table *p = (Fts3Table*)pVtab;
- sqlite3Fts3PendingTermsClear(p);
+ tdsqlite3Fts3PendingTermsClear(p);
assert( p->inTransaction!=0 );
TESTONLY( p->inTransaction = 0 );
TESTONLY( p->mxSavepoint = -1; );
@@ -149765,32 +172198,31 @@ static void fts3ReversePoslist(char *pStart, char **ppPoslist){
** string passed via zFunc is used as part of the error message.
*/
static int fts3FunctionArg(
- sqlite3_context *pContext, /* SQL function call context */
+ tdsqlite3_context *pContext, /* SQL function call context */
const char *zFunc, /* Function name */
- sqlite3_value *pVal, /* argv[0] passed to function */
+ tdsqlite3_value *pVal, /* argv[0] passed to function */
Fts3Cursor **ppCsr /* OUT: Store cursor handle here */
){
- Fts3Cursor *pRet;
- if( sqlite3_value_type(pVal)!=SQLITE_BLOB
- || sqlite3_value_bytes(pVal)!=sizeof(Fts3Cursor *)
- ){
- char *zErr = sqlite3_mprintf("illegal first argument to %s", zFunc);
- sqlite3_result_error(pContext, zErr, -1);
- sqlite3_free(zErr);
- return SQLITE_ERROR;
+ int rc;
+ *ppCsr = (Fts3Cursor*)tdsqlite3_value_pointer(pVal, "fts3cursor");
+ if( (*ppCsr)!=0 ){
+ rc = SQLITE_OK;
+ }else{
+ char *zErr = tdsqlite3_mprintf("illegal first argument to %s", zFunc);
+ tdsqlite3_result_error(pContext, zErr, -1);
+ tdsqlite3_free(zErr);
+ rc = SQLITE_ERROR;
}
- memcpy(&pRet, sqlite3_value_blob(pVal), sizeof(Fts3Cursor *));
- *ppCsr = pRet;
- return SQLITE_OK;
+ return rc;
}
/*
** Implementation of the snippet() function for FTS3
*/
static void fts3SnippetFunc(
- sqlite3_context *pContext, /* SQLite function call context */
+ tdsqlite3_context *pContext, /* SQLite function call context */
int nVal, /* Size of apVal[] array */
- sqlite3_value **apVal /* Array of arguments */
+ tdsqlite3_value **apVal /* Array of arguments */
){
Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */
const char *zStart = "<b>";
@@ -149805,25 +172237,25 @@ static void fts3SnippetFunc(
assert( nVal>=1 );
if( nVal>6 ){
- sqlite3_result_error(pContext,
+ tdsqlite3_result_error(pContext,
"wrong number of arguments to function snippet()", -1);
return;
}
if( fts3FunctionArg(pContext, "snippet", apVal[0], &pCsr) ) return;
switch( nVal ){
- case 6: nToken = sqlite3_value_int(apVal[5]);
- case 5: iCol = sqlite3_value_int(apVal[4]);
- case 4: zEllipsis = (const char*)sqlite3_value_text(apVal[3]);
- case 3: zEnd = (const char*)sqlite3_value_text(apVal[2]);
- case 2: zStart = (const char*)sqlite3_value_text(apVal[1]);
+ case 6: nToken = tdsqlite3_value_int(apVal[5]);
+ case 5: iCol = tdsqlite3_value_int(apVal[4]);
+ case 4: zEllipsis = (const char*)tdsqlite3_value_text(apVal[3]);
+ case 3: zEnd = (const char*)tdsqlite3_value_text(apVal[2]);
+ case 2: zStart = (const char*)tdsqlite3_value_text(apVal[1]);
}
if( !zEllipsis || !zEnd || !zStart ){
- sqlite3_result_error_nomem(pContext);
+ tdsqlite3_result_error_nomem(pContext);
}else if( nToken==0 ){
- sqlite3_result_text(pContext, "", -1, SQLITE_STATIC);
+ tdsqlite3_result_text(pContext, "", -1, SQLITE_STATIC);
}else if( SQLITE_OK==fts3CursorSeek(pContext, pCsr) ){
- sqlite3Fts3Snippet(pContext, pCsr, zStart, zEnd, zEllipsis, iCol, nToken);
+ tdsqlite3Fts3Snippet(pContext, pCsr, zStart, zEnd, zEllipsis, iCol, nToken);
}
}
@@ -149831,9 +172263,9 @@ static void fts3SnippetFunc(
** Implementation of the offsets() function for FTS3
*/
static void fts3OffsetsFunc(
- sqlite3_context *pContext, /* SQLite function call context */
+ tdsqlite3_context *pContext, /* SQLite function call context */
int nVal, /* Size of argument array */
- sqlite3_value **apVal /* Array of arguments */
+ tdsqlite3_value **apVal /* Array of arguments */
){
Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */
@@ -149843,7 +172275,7 @@ static void fts3OffsetsFunc(
if( fts3FunctionArg(pContext, "offsets", apVal[0], &pCsr) ) return;
assert( pCsr );
if( SQLITE_OK==fts3CursorSeek(pContext, pCsr) ){
- sqlite3Fts3Offsets(pContext, pCsr);
+ tdsqlite3Fts3Offsets(pContext, pCsr);
}
}
@@ -149857,9 +172289,9 @@ static void fts3OffsetsFunc(
** where 't' is the name of an FTS3 table.
*/
static void fts3OptimizeFunc(
- sqlite3_context *pContext, /* SQLite function call context */
+ tdsqlite3_context *pContext, /* SQLite function call context */
int nVal, /* Size of argument array */
- sqlite3_value **apVal /* Array of arguments */
+ tdsqlite3_value **apVal /* Array of arguments */
){
int rc; /* Return code */
Fts3Table *p; /* Virtual table handle */
@@ -149872,17 +172304,17 @@ static void fts3OptimizeFunc(
p = (Fts3Table *)pCursor->base.pVtab;
assert( p );
- rc = sqlite3Fts3Optimize(p);
+ rc = tdsqlite3Fts3Optimize(p);
switch( rc ){
case SQLITE_OK:
- sqlite3_result_text(pContext, "Index optimized", -1, SQLITE_STATIC);
+ tdsqlite3_result_text(pContext, "Index optimized", -1, SQLITE_STATIC);
break;
case SQLITE_DONE:
- sqlite3_result_text(pContext, "Index already optimal", -1, SQLITE_STATIC);
+ tdsqlite3_result_text(pContext, "Index already optimal", -1, SQLITE_STATIC);
break;
default:
- sqlite3_result_error_code(pContext, rc);
+ tdsqlite3_result_error_code(pContext, rc);
break;
}
}
@@ -149891,18 +172323,18 @@ static void fts3OptimizeFunc(
** Implementation of the matchinfo() function for FTS3
*/
static void fts3MatchinfoFunc(
- sqlite3_context *pContext, /* SQLite function call context */
+ tdsqlite3_context *pContext, /* SQLite function call context */
int nVal, /* Size of argument array */
- sqlite3_value **apVal /* Array of arguments */
+ tdsqlite3_value **apVal /* Array of arguments */
){
Fts3Cursor *pCsr; /* Cursor handle passed through apVal[0] */
assert( nVal==1 || nVal==2 );
if( SQLITE_OK==fts3FunctionArg(pContext, "matchinfo", apVal[0], &pCsr) ){
const char *zArg = 0;
if( nVal>1 ){
- zArg = (const char *)sqlite3_value_text(apVal[1]);
+ zArg = (const char *)tdsqlite3_value_text(apVal[1]);
}
- sqlite3Fts3Matchinfo(pContext, pCsr, zArg);
+ tdsqlite3Fts3Matchinfo(pContext, pCsr, zArg);
}
}
@@ -149911,15 +172343,15 @@ static void fts3MatchinfoFunc(
** virtual table.
*/
static int fts3FindFunctionMethod(
- sqlite3_vtab *pVtab, /* Virtual table handle */
+ tdsqlite3_vtab *pVtab, /* Virtual table handle */
int nArg, /* Number of SQL function arguments */
const char *zName, /* Name of SQL function */
- void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */
+ void (**pxFunc)(tdsqlite3_context*,int,tdsqlite3_value**), /* OUT: Result */
void **ppArg /* Unused */
){
struct Overloaded {
const char *zName;
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**);
} aOverload[] = {
{ "snippet", fts3SnippetFunc },
{ "offsets", fts3OffsetsFunc },
@@ -149947,11 +172379,11 @@ static int fts3FindFunctionMethod(
** Implementation of FTS3 xRename method. Rename an fts3 table.
*/
static int fts3RenameMethod(
- sqlite3_vtab *pVtab, /* Virtual table handle */
+ tdsqlite3_vtab *pVtab, /* Virtual table handle */
const char *zName /* New name of table */
){
Fts3Table *p = (Fts3Table *)pVtab;
- sqlite3 *db = p->db; /* Database connection */
+ tdsqlite3 *db = p->db; /* Database connection */
int rc; /* Return Code */
/* At this point it must be known if the %_stat table exists or not.
@@ -149966,7 +172398,7 @@ static int fts3RenameMethod(
*/
assert( p->nPendingData==0 );
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3PendingTermsFlush(p);
+ rc = tdsqlite3Fts3PendingTermsFlush(p);
}
if( p->zContentTbl==0 ){
@@ -150004,11 +172436,11 @@ static int fts3RenameMethod(
**
** Flush the contents of the pending-terms table to disk.
*/
-static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
+static int fts3SavepointMethod(tdsqlite3_vtab *pVtab, int iSavepoint){
int rc = SQLITE_OK;
UNUSED_PARAMETER(iSavepoint);
assert( ((Fts3Table *)pVtab)->inTransaction );
- assert( ((Fts3Table *)pVtab)->mxSavepoint < iSavepoint );
+ assert( ((Fts3Table *)pVtab)->mxSavepoint <= iSavepoint );
TESTONLY( ((Fts3Table *)pVtab)->mxSavepoint = iSavepoint );
if( ((Fts3Table *)pVtab)->bIgnoreSavepoint==0 ){
rc = fts3SyncMethod(pVtab);
@@ -150021,7 +172453,7 @@ static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
**
** This is a no-op.
*/
-static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
+static int fts3ReleaseMethod(tdsqlite3_vtab *pVtab, int iSavepoint){
TESTONLY( Fts3Table *p = (Fts3Table*)pVtab );
UNUSED_PARAMETER(iSavepoint);
UNUSED_PARAMETER(pVtab);
@@ -150036,18 +172468,32 @@ static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
**
** Discard the contents of the pending terms table.
*/
-static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
+static int fts3RollbackToMethod(tdsqlite3_vtab *pVtab, int iSavepoint){
Fts3Table *p = (Fts3Table*)pVtab;
UNUSED_PARAMETER(iSavepoint);
assert( p->inTransaction );
- assert( p->mxSavepoint >= iSavepoint );
TESTONLY( p->mxSavepoint = iSavepoint );
- sqlite3Fts3PendingTermsClear(p);
+ tdsqlite3Fts3PendingTermsClear(p);
return SQLITE_OK;
}
-static const sqlite3_module fts3Module = {
- /* iVersion */ 2,
+/*
+** Return true if zName is the extension on one of the shadow tables used
+** by this module.
+*/
+static int fts3ShadowName(const char *zName){
+ static const char *azName[] = {
+ "content", "docsize", "segdir", "segments", "stat",
+ };
+ unsigned int i;
+ for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
+ if( tdsqlite3_stricmp(zName, azName[i])==0 ) return 1;
+ }
+ return 0;
+}
+
+static const tdsqlite3_module fts3Module = {
+ /* iVersion */ 3,
/* xCreate */ fts3CreateMethod,
/* xConnect */ fts3ConnectMethod,
/* xBestIndex */ fts3BestIndexMethod,
@@ -150070,6 +172516,7 @@ static const sqlite3_module fts3Module = {
/* xSavepoint */ fts3SavepointMethod,
/* xRelease */ fts3ReleaseMethod,
/* xRollbackTo */ fts3RollbackToMethod,
+ /* xShadowName */ fts3ShadowName,
};
/*
@@ -150079,8 +172526,8 @@ static const sqlite3_module fts3Module = {
*/
static void hashDestroy(void *p){
Fts3Hash *pHash = (Fts3Hash *)p;
- sqlite3Fts3HashClear(pHash);
- sqlite3_free(pHash);
+ tdsqlite3Fts3HashClear(pHash);
+ tdsqlite3_free(pHash);
}
/*
@@ -150089,72 +172536,72 @@ static void hashDestroy(void *p){
** respectively. The following three forward declarations are for functions
** declared in these files used to retrieve the respective implementations.
**
-** Calling sqlite3Fts3SimpleTokenizerModule() sets the value pointed
+** Calling tdsqlite3Fts3SimpleTokenizerModule() sets the value pointed
** to by the argument to point to the "simple" tokenizer implementation.
** And so on.
*/
-SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule);
-SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule(sqlite3_tokenizer_module const**ppModule);
+SQLITE_PRIVATE void tdsqlite3Fts3SimpleTokenizerModule(tdsqlite3_tokenizer_module const**ppModule);
+SQLITE_PRIVATE void tdsqlite3Fts3PorterTokenizerModule(tdsqlite3_tokenizer_module const**ppModule);
#ifndef SQLITE_DISABLE_FTS3_UNICODE
-SQLITE_PRIVATE void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const**ppModule);
+SQLITE_PRIVATE void tdsqlite3Fts3UnicodeTokenizer(tdsqlite3_tokenizer_module const**ppModule);
#endif
#ifdef SQLITE_ENABLE_ICU
-SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(sqlite3_tokenizer_module const**ppModule);
+SQLITE_PRIVATE void tdsqlite3Fts3IcuTokenizerModule(tdsqlite3_tokenizer_module const**ppModule);
#endif
/*
** Initialize the fts3 extension. If this extension is built as part
** of the sqlite library, then this function is called directly by
** SQLite. If fts3 is built as a dynamically loadable extension, this
-** function is called by the sqlite3_extension_init() entry point.
+** function is called by the tdsqlite3_extension_init() entry point.
*/
-SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){
+SQLITE_PRIVATE int tdsqlite3Fts3Init(tdsqlite3 *db){
int rc = SQLITE_OK;
Fts3Hash *pHash = 0;
- const sqlite3_tokenizer_module *pSimple = 0;
- const sqlite3_tokenizer_module *pPorter = 0;
+ const tdsqlite3_tokenizer_module *pSimple = 0;
+ const tdsqlite3_tokenizer_module *pPorter = 0;
#ifndef SQLITE_DISABLE_FTS3_UNICODE
- const sqlite3_tokenizer_module *pUnicode = 0;
+ const tdsqlite3_tokenizer_module *pUnicode = 0;
#endif
#ifdef SQLITE_ENABLE_ICU
- const sqlite3_tokenizer_module *pIcu = 0;
- sqlite3Fts3IcuTokenizerModule(&pIcu);
+ const tdsqlite3_tokenizer_module *pIcu = 0;
+ tdsqlite3Fts3IcuTokenizerModule(&pIcu);
#endif
#ifndef SQLITE_DISABLE_FTS3_UNICODE
- sqlite3Fts3UnicodeTokenizer(&pUnicode);
+ tdsqlite3Fts3UnicodeTokenizer(&pUnicode);
#endif
#ifdef SQLITE_TEST
- rc = sqlite3Fts3InitTerm(db);
+ rc = tdsqlite3Fts3InitTerm(db);
if( rc!=SQLITE_OK ) return rc;
#endif
- rc = sqlite3Fts3InitAux(db);
+ rc = tdsqlite3Fts3InitAux(db);
if( rc!=SQLITE_OK ) return rc;
- sqlite3Fts3SimpleTokenizerModule(&pSimple);
- sqlite3Fts3PorterTokenizerModule(&pPorter);
+ tdsqlite3Fts3SimpleTokenizerModule(&pSimple);
+ tdsqlite3Fts3PorterTokenizerModule(&pPorter);
/* Allocate and initialize the hash-table used to store tokenizers. */
- pHash = sqlite3_malloc(sizeof(Fts3Hash));
+ pHash = tdsqlite3_malloc(sizeof(Fts3Hash));
if( !pHash ){
rc = SQLITE_NOMEM;
}else{
- sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1);
+ tdsqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1);
}
/* Load the built-in tokenizers into the hash table */
if( rc==SQLITE_OK ){
- if( sqlite3Fts3HashInsert(pHash, "simple", 7, (void *)pSimple)
- || sqlite3Fts3HashInsert(pHash, "porter", 7, (void *)pPorter)
+ if( tdsqlite3Fts3HashInsert(pHash, "simple", 7, (void *)pSimple)
+ || tdsqlite3Fts3HashInsert(pHash, "porter", 7, (void *)pPorter)
#ifndef SQLITE_DISABLE_FTS3_UNICODE
- || sqlite3Fts3HashInsert(pHash, "unicode61", 10, (void *)pUnicode)
+ || tdsqlite3Fts3HashInsert(pHash, "unicode61", 10, (void *)pUnicode)
#endif
#ifdef SQLITE_ENABLE_ICU
- || (pIcu && sqlite3Fts3HashInsert(pHash, "icu", 4, (void *)pIcu))
+ || (pIcu && tdsqlite3Fts3HashInsert(pHash, "icu", 4, (void *)pIcu))
#endif
){
rc = SQLITE_NOMEM;
@@ -150163,32 +172610,32 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){
#ifdef SQLITE_TEST
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3ExprInitTestInterface(db);
+ rc = tdsqlite3Fts3ExprInitTestInterface(db, pHash);
}
#endif
/* Create the virtual table wrapper around the hash-table and overload
- ** the two scalar functions. If this is successful, register the
+ ** the four scalar functions. If this is successful, register the
** module with sqlite.
*/
if( SQLITE_OK==rc
- && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer"))
- && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1))
- && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", 1))
- && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 1))
- && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 2))
- && SQLITE_OK==(rc = sqlite3_overload_function(db, "optimize", 1))
+ && SQLITE_OK==(rc = tdsqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer"))
+ && SQLITE_OK==(rc = tdsqlite3_overload_function(db, "snippet", -1))
+ && SQLITE_OK==(rc = tdsqlite3_overload_function(db, "offsets", 1))
+ && SQLITE_OK==(rc = tdsqlite3_overload_function(db, "matchinfo", 1))
+ && SQLITE_OK==(rc = tdsqlite3_overload_function(db, "matchinfo", 2))
+ && SQLITE_OK==(rc = tdsqlite3_overload_function(db, "optimize", 1))
){
- rc = sqlite3_create_module_v2(
+ rc = tdsqlite3_create_module_v2(
db, "fts3", &fts3Module, (void *)pHash, hashDestroy
);
if( rc==SQLITE_OK ){
- rc = sqlite3_create_module_v2(
+ rc = tdsqlite3_create_module_v2(
db, "fts4", &fts3Module, (void *)pHash, 0
);
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3InitTok(db, (void *)pHash);
+ rc = tdsqlite3Fts3InitTok(db, (void *)pHash);
}
return rc;
}
@@ -150197,8 +172644,8 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){
/* An error has occurred. Delete the hash table and return the error code. */
assert( rc!=SQLITE_OK );
if( pHash ){
- sqlite3Fts3HashClear(pHash);
- sqlite3_free(pHash);
+ tdsqlite3Fts3HashClear(pHash);
+ tdsqlite3_free(pHash);
}
return rc;
}
@@ -150255,7 +172702,7 @@ static void fts3EvalAllocateReaders(
** It is merged into the main doclist stored in p->doclist.aAll/nAll.
**
** This function assumes that pList points to a buffer allocated using
-** sqlite3_malloc(). This function takes responsibility for eventually
+** tdsqlite3_malloc(). This function takes responsibility for eventually
** freeing the buffer.
**
** SQLITE_OK is returned if successful, or SQLITE_NOMEM if an error occurs.
@@ -150271,7 +172718,7 @@ static int fts3EvalPhraseMergeToken(
assert( iToken!=p->iDoclistToken );
if( pList==0 ){
- sqlite3_free(p->doclist.aAll);
+ tdsqlite3_free(p->doclist.aAll);
p->doclist.aAll = 0;
p->doclist.nAll = 0;
}
@@ -150282,7 +172729,7 @@ static int fts3EvalPhraseMergeToken(
}
else if( p->doclist.aAll==0 ){
- sqlite3_free(pList);
+ tdsqlite3_free(pList);
}
else {
@@ -150309,7 +172756,7 @@ static int fts3EvalPhraseMergeToken(
rc = fts3DoclistPhraseMerge(
pTab->bDescIdx, nDiff, pLeft, nLeft, &pRight, &nRight
);
- sqlite3_free(pLeft);
+ tdsqlite3_free(pLeft);
p->doclist.aAll = pRight;
p->doclist.nAll = nRight;
}
@@ -150350,6 +172797,7 @@ static int fts3EvalPhraseLoad(
return rc;
}
+#ifndef SQLITE_DISABLE_FTS4_DEFERRED
/*
** This function is called on each phrase after the position lists for
** any deferred tokens have been loaded into memory. It updates the phrases
@@ -150375,11 +172823,11 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
if( pDeferred ){
char *pList;
int nList;
- int rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList);
+ int rc = tdsqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList);
if( rc!=SQLITE_OK ) return rc;
if( pList==0 ){
- sqlite3_free(aPoslist);
+ tdsqlite3_free(aPoslist);
pPhrase->doclist.pList = 0;
pPhrase->doclist.nList = 0;
return SQLITE_OK;
@@ -150395,11 +172843,11 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
assert( iPrev>=0 );
fts3PoslistPhraseMerge(&aOut, iToken-iPrev, 0, 1, &p1, &p2);
- sqlite3_free(aPoslist);
+ tdsqlite3_free(aPoslist);
aPoslist = pList;
nPoslist = (int)(aOut - aPoslist);
if( nPoslist==0 ){
- sqlite3_free(aPoslist);
+ tdsqlite3_free(aPoslist);
pPhrase->doclist.pList = 0;
pPhrase->doclist.nList = 0;
return SQLITE_OK;
@@ -150432,9 +172880,9 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
nDistance = iPrev - nMaxUndeferred;
}
- aOut = (char *)sqlite3_malloc(nPoslist+8);
+ aOut = (char *)tdsqlite3_malloc(nPoslist+8);
if( !aOut ){
- sqlite3_free(aPoslist);
+ tdsqlite3_free(aPoslist);
return SQLITE_NOMEM;
}
@@ -150443,16 +172891,17 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){
pPhrase->doclist.bFreeList = 1;
pPhrase->doclist.nList = (int)(aOut - pPhrase->doclist.pList);
}else{
- sqlite3_free(aOut);
+ tdsqlite3_free(aOut);
pPhrase->doclist.pList = 0;
pPhrase->doclist.nList = 0;
}
- sqlite3_free(aPoslist);
+ tdsqlite3_free(aPoslist);
}
}
return SQLITE_OK;
}
+#endif /* SQLITE_DISABLE_FTS4_DEFERRED */
/*
** Maximum number of tokens a phrase may have to be considered for the
@@ -150486,7 +172935,7 @@ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){
int bIncrOk = (bOptOk
&& pCsr->bDesc==pTab->bDescIdx
&& p->nToken<=MAX_INCR_PHRASE_TOKENS && p->nToken>0
-#ifdef SQLITE_TEST
+#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
&& pTab->bNoIncrDoclist==0
#endif
);
@@ -150505,7 +172954,7 @@ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){
Fts3PhraseToken *pToken = &p->aToken[i];
Fts3MultiSegReader *pSegcsr = pToken->pSegcsr;
if( pSegcsr ){
- rc = sqlite3Fts3MsrIncrStart(pTab, pSegcsr, iCol, pToken->z, pToken->n);
+ rc = tdsqlite3Fts3MsrIncrStart(pTab, pSegcsr, iCol, pToken->z, pToken->n);
}
}
p->bIncr = 1;
@@ -150529,12 +172978,12 @@ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){
** descending (parameter bDescIdx==1) order of docid. Regardless, this
** function iterates from the end of the doclist to the beginning.
*/
-SQLITE_PRIVATE void sqlite3Fts3DoclistPrev(
+SQLITE_PRIVATE void tdsqlite3Fts3DoclistPrev(
int bDescIdx, /* True if the doclist is desc */
char *aDoclist, /* Pointer to entire doclist */
int nDoclist, /* Length of aDoclist in bytes */
char **ppIter, /* IN/OUT: Iterator pointer */
- sqlite3_int64 *piDocid, /* IN/OUT: Docid pointer */
+ tdsqlite3_int64 *piDocid, /* IN/OUT: Docid pointer */
int *pnList, /* OUT: List length pointer */
u8 *pbEof /* OUT: End-of-file flag */
){
@@ -150546,15 +172995,15 @@ SQLITE_PRIVATE void sqlite3Fts3DoclistPrev(
assert( !p || (p>aDoclist && p<&aDoclist[nDoclist]) );
if( p==0 ){
- sqlite3_int64 iDocid = 0;
+ tdsqlite3_int64 iDocid = 0;
char *pNext = 0;
char *pDocid = aDoclist;
char *pEnd = &aDoclist[nDoclist];
int iMul = 1;
while( pDocid<pEnd ){
- sqlite3_int64 iDelta;
- pDocid += sqlite3Fts3GetVarint(pDocid, &iDelta);
+ tdsqlite3_int64 iDelta;
+ pDocid += tdsqlite3Fts3GetVarint(pDocid, &iDelta);
iDocid += (iMul * iDelta);
pNext = pDocid;
fts3PoslistCopy(0, &pDocid);
@@ -150567,7 +173016,7 @@ SQLITE_PRIVATE void sqlite3Fts3DoclistPrev(
*piDocid = iDocid;
}else{
int iMul = (bDescIdx ? -1 : 1);
- sqlite3_int64 iDelta;
+ tdsqlite3_int64 iDelta;
fts3GetReverseVarint(&p, aDoclist, &iDelta);
*piDocid -= (iMul * iDelta);
@@ -150585,12 +173034,12 @@ SQLITE_PRIVATE void sqlite3Fts3DoclistPrev(
/*
** Iterate forwards through a doclist.
*/
-SQLITE_PRIVATE void sqlite3Fts3DoclistNext(
+SQLITE_PRIVATE void tdsqlite3Fts3DoclistNext(
int bDescIdx, /* True if the doclist is desc */
char *aDoclist, /* Pointer to entire doclist */
int nDoclist, /* Length of aDoclist in bytes */
char **ppIter, /* IN/OUT: Iterator pointer */
- sqlite3_int64 *piDocid, /* IN/OUT: Docid pointer */
+ tdsqlite3_int64 *piDocid, /* IN/OUT: Docid pointer */
u8 *pbEof /* OUT: End-of-file flag */
){
char *p = *ppIter;
@@ -150602,15 +173051,15 @@ SQLITE_PRIVATE void sqlite3Fts3DoclistNext(
if( p==0 ){
p = aDoclist;
- p += sqlite3Fts3GetVarint(p, piDocid);
+ p += tdsqlite3Fts3GetVarint(p, piDocid);
}else{
fts3PoslistCopy(0, &p);
while( p<&aDoclist[nDoclist] && *p==0 ) p++;
if( p>=&aDoclist[nDoclist] ){
*pbEof = 1;
}else{
- sqlite3_int64 iVar;
- p += sqlite3Fts3GetVarint(p, &iVar);
+ tdsqlite3_int64 iVar;
+ p += tdsqlite3Fts3GetVarint(p, &iVar);
*piDocid += ((bDescIdx ? -1 : 1) * iVar);
}
}
@@ -150628,20 +173077,21 @@ static void fts3EvalDlPhraseNext(
u8 *pbEof
){
char *pIter; /* Used to iterate through aAll */
- char *pEnd = &pDL->aAll[pDL->nAll]; /* 1 byte past end of aAll */
+ char *pEnd; /* 1 byte past end of aAll */
if( pDL->pNextDocid ){
pIter = pDL->pNextDocid;
+ assert( pDL->aAll!=0 || pIter==0 );
}else{
pIter = pDL->aAll;
}
- if( pIter>=pEnd ){
+ if( pIter==0 || pIter>=(pEnd = pDL->aAll + pDL->nAll) ){
/* We have already reached the end of this doclist. EOF. */
*pbEof = 1;
}else{
- sqlite3_int64 iDelta;
- pIter += sqlite3Fts3GetVarint(pIter, &iDelta);
+ tdsqlite3_int64 iDelta;
+ pIter += tdsqlite3Fts3GetVarint(pIter, &iDelta);
if( pTab->bDescIdx==0 || pDL->pNextDocid==0 ){
pDL->iDocid += iDelta;
}else{
@@ -150671,7 +173121,7 @@ static void fts3EvalDlPhraseNext(
typedef struct TokenDoclist TokenDoclist;
struct TokenDoclist {
int bIgnore;
- sqlite3_int64 iDocid;
+ tdsqlite3_int64 iDocid;
char *pList;
int nList;
};
@@ -150707,7 +173157,7 @@ static int incrPhraseTokenNext(
assert( pToken->pSegcsr || pPhrase->iDoclistToken>=0 );
if( pToken->pSegcsr ){
assert( p->bIgnore==0 );
- rc = sqlite3Fts3MsrIncrNext(
+ rc = tdsqlite3Fts3MsrIncrNext(
pTab, pToken->pSegcsr, &p->iDocid, &p->pList, &p->nList
);
if( p->pList==0 ) *pbEof = 1;
@@ -150751,8 +173201,8 @@ static int fts3EvalIncrPhraseNext(
** one incremental token. In which case the bIncr flag is set. */
assert( p->bIncr==1 );
- if( p->nToken==1 && p->bIncr ){
- rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr,
+ if( p->nToken==1 ){
+ rc = tdsqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr,
&pDL->iDocid, &pDL->pList, &pDL->nList
);
if( pDL->pList==0 ) bEof = 1;
@@ -150766,7 +173216,7 @@ static int fts3EvalIncrPhraseNext(
while( bEof==0 ){
int bMaxSet = 0;
- sqlite3_int64 iMax = 0; /* Largest docid for all iterators */
+ tdsqlite3_int64 iMax = 0; /* Largest docid for all iterators */
int i; /* Used to iterate through tokens */
/* Advance the iterator for each token in the phrase once. */
@@ -150797,9 +173247,10 @@ static int fts3EvalIncrPhraseNext(
if( bEof==0 ){
int nList = 0;
int nByte = a[p->nToken-1].nList;
- char *aDoclist = sqlite3_malloc(nByte+1);
+ char *aDoclist = tdsqlite3_malloc(nByte+FTS3_BUFFER_PADDING);
if( !aDoclist ) return SQLITE_NOMEM;
memcpy(aDoclist, a[p->nToken-1].pList, nByte+1);
+ memset(&aDoclist[nByte], 0, FTS3_BUFFER_PADDING);
for(i=0; i<(p->nToken-1); i++){
if( a[i].bIgnore==0 ){
@@ -150819,7 +173270,7 @@ static int fts3EvalIncrPhraseNext(
pDL->bFreeList = 1;
break;
}
- sqlite3_free(aDoclist);
+ tdsqlite3_free(aDoclist);
}
}
}
@@ -150849,7 +173300,7 @@ static int fts3EvalPhraseNext(
if( p->bIncr ){
rc = fts3EvalIncrPhraseNext(pCsr, p, pbEof);
}else if( pCsr->bDesc!=pTab->bDescIdx && pDL->nAll ){
- sqlite3Fts3DoclistPrev(pTab->bDescIdx, pDL->aAll, pDL->nAll,
+ tdsqlite3Fts3DoclistPrev(pTab->bDescIdx, pDL->aAll, pDL->nAll,
&pDL->pNextDocid, &pDL->iDocid, &pDL->nList, pbEof
);
pDL->pList = pDL->pNextDocid;
@@ -150948,7 +173399,7 @@ static void fts3EvalTokenCosts(
pTC->pRoot = pRoot;
pTC->pToken = &pPhrase->aToken[i];
pTC->iCol = pPhrase->iColumn;
- *pRc = sqlite3Fts3MsrOvfl(pCsr, pTC->pToken->pSegcsr, &pTC->nOvfl);
+ *pRc = tdsqlite3Fts3MsrOvfl(pCsr, pTC->pToken->pSegcsr, &pTC->nOvfl);
}
}else if( pExpr->eType!=FTSQUERY_NOT ){
assert( pExpr->eType==FTSQUERY_OR
@@ -150984,6 +173435,7 @@ static void fts3EvalTokenCosts(
** the number of overflow pages consumed by a record B bytes in size.
*/
static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){
+ int rc = SQLITE_OK;
if( pCsr->nRowAvg==0 ){
/* The average document size, which is required to calculate the cost
** of each doclist, has not yet been determined. Read the required
@@ -150996,38 +173448,37 @@ static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){
** data stored in all rows of each column of the table, from left
** to right.
*/
- int rc;
Fts3Table *p = (Fts3Table*)pCsr->base.pVtab;
- sqlite3_stmt *pStmt;
- sqlite3_int64 nDoc = 0;
- sqlite3_int64 nByte = 0;
+ tdsqlite3_stmt *pStmt;
+ tdsqlite3_int64 nDoc = 0;
+ tdsqlite3_int64 nByte = 0;
const char *pEnd;
const char *a;
- rc = sqlite3Fts3SelectDoctotal(p, &pStmt);
+ rc = tdsqlite3Fts3SelectDoctotal(p, &pStmt);
if( rc!=SQLITE_OK ) return rc;
- a = sqlite3_column_blob(pStmt, 0);
- assert( a );
-
- pEnd = &a[sqlite3_column_bytes(pStmt, 0)];
- a += sqlite3Fts3GetVarint(a, &nDoc);
- while( a<pEnd ){
- a += sqlite3Fts3GetVarint(a, &nByte);
+ a = tdsqlite3_column_blob(pStmt, 0);
+ testcase( a==0 ); /* If %_stat.value set to X'' */
+ if( a ){
+ pEnd = &a[tdsqlite3_column_bytes(pStmt, 0)];
+ a += tdsqlite3Fts3GetVarintBounded(a, pEnd, &nDoc);
+ while( a<pEnd ){
+ a += tdsqlite3Fts3GetVarintBounded(a, pEnd, &nByte);
+ }
}
if( nDoc==0 || nByte==0 ){
- sqlite3_reset(pStmt);
+ tdsqlite3_reset(pStmt);
return FTS_CORRUPT_VTAB;
}
pCsr->nDoc = nDoc;
pCsr->nRowAvg = (int)(((nByte / nDoc) + p->nPgsz) / p->nPgsz);
assert( pCsr->nRowAvg>0 );
- rc = sqlite3_reset(pStmt);
- if( rc!=SQLITE_OK ) return rc;
+ rc = tdsqlite3_reset(pStmt);
}
*pnPage = pCsr->nRowAvg;
- return SQLITE_OK;
+ return rc;
}
/*
@@ -151040,7 +173491,7 @@ static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){
** the cluster with root node pRoot. See comments above the definition
** of struct Fts3TokenAndCost for more details.
**
-** If no error occurs, SQLITE_OK is returned and sqlite3Fts3DeferToken()
+** If no error occurs, SQLITE_OK is returned and tdsqlite3Fts3DeferToken()
** called on each token to defer. Otherwise, an SQLite error code is
** returned.
*/
@@ -151127,7 +173578,7 @@ static int fts3EvalSelectDeferred(
** that will be loaded if all subsequent tokens are deferred.
*/
Fts3PhraseToken *pToken = pTC->pToken;
- rc = sqlite3Fts3DeferToken(pCsr, pToken, pTC->iCol);
+ rc = tdsqlite3Fts3DeferToken(pCsr, pToken, pTC->iCol);
fts3SegReaderCursorFree(pToken->pSegcsr);
pToken->pSegcsr = 0;
}else{
@@ -151191,7 +173642,7 @@ static int fts3EvalStart(Fts3Cursor *pCsr){
if( rc==SQLITE_OK && nToken>1 && pTab->bFts4 ){
Fts3TokenAndCost *aTC;
Fts3Expr **apOr;
- aTC = (Fts3TokenAndCost *)sqlite3_malloc(
+ aTC = (Fts3TokenAndCost *)tdsqlite3_malloc64(
sizeof(Fts3TokenAndCost) * nToken
+ sizeof(Fts3Expr *) * nOr * 2
);
@@ -151215,7 +173666,7 @@ static int fts3EvalStart(Fts3Cursor *pCsr){
}
}
- sqlite3_free(aTC);
+ tdsqlite3_free(aTC);
}
}
#endif
@@ -151229,7 +173680,7 @@ static int fts3EvalStart(Fts3Cursor *pCsr){
*/
static void fts3EvalInvalidatePoslist(Fts3Phrase *pPhrase){
if( pPhrase->doclist.bFreeList ){
- sqlite3_free(pPhrase->doclist.pList);
+ tdsqlite3_free(pPhrase->doclist.pList);
}
pPhrase->doclist.pList = 0;
pPhrase->doclist.nList = 0;
@@ -151329,7 +173780,7 @@ static int fts3EvalNearTrim(
** 2. NEAR is treated as AND. If the expression is "x NEAR y", it is
** advanced to point to the next row that matches "x AND y".
**
-** See sqlite3Fts3EvalTestDeferred() for details on testing if a row is
+** See tdsqlite3Fts3EvalTestDeferred() for details on testing if a row is
** really a match, taking into account deferred tokens and NEAR operators.
*/
static void fts3EvalNextRow(
@@ -151366,7 +173817,7 @@ static void fts3EvalNextRow(
fts3EvalNextRow(pCsr, pLeft, pRc);
fts3EvalNextRow(pCsr, pRight, pRc);
while( !pLeft->bEof && !pRight->bEof && *pRc==SQLITE_OK ){
- sqlite3_int64 iDiff = DOCID_CMP(pLeft->iDocid, pRight->iDocid);
+ tdsqlite3_int64 iDiff = DOCID_CMP(pLeft->iDocid, pRight->iDocid);
if( iDiff==0 ) break;
if( iDiff<0 ){
fts3EvalNextRow(pCsr, pLeft, pRc);
@@ -151377,7 +173828,8 @@ static void fts3EvalNextRow(
pExpr->iDocid = pLeft->iDocid;
pExpr->bEof = (pLeft->bEof || pRight->bEof);
if( pExpr->eType==FTSQUERY_NEAR && pExpr->bEof ){
- if( pRight->pPhrase && pRight->pPhrase->doclist.aAll ){
+ assert( pRight->eType==FTSQUERY_PHRASE );
+ if( pRight->pPhrase->doclist.aAll ){
Fts3Doclist *pDl = &pRight->pPhrase->doclist;
while( *pRc==SQLITE_OK && pRight->bEof==0 ){
memset(pDl->pList, 0, pDl->nList);
@@ -151399,14 +173851,14 @@ static void fts3EvalNextRow(
case FTSQUERY_OR: {
Fts3Expr *pLeft = pExpr->pLeft;
Fts3Expr *pRight = pExpr->pRight;
- sqlite3_int64 iCmp = DOCID_CMP(pLeft->iDocid, pRight->iDocid);
+ tdsqlite3_int64 iCmp = DOCID_CMP(pLeft->iDocid, pRight->iDocid);
assert( pLeft->bStart || pLeft->iDocid==pRight->iDocid );
assert( pRight->bStart || pLeft->iDocid==pRight->iDocid );
if( pRight->bEof || (pLeft->bEof==0 && iCmp<0) ){
fts3EvalNextRow(pCsr, pLeft, pRc);
- }else if( pLeft->bEof || (pRight->bEof==0 && iCmp>0) ){
+ }else if( pLeft->bEof || iCmp>0 ){
fts3EvalNextRow(pCsr, pRight, pRc);
}else{
fts3EvalNextRow(pCsr, pLeft, pRc);
@@ -151498,58 +173950,54 @@ static int fts3EvalNearTest(Fts3Expr *pExpr, int *pRc){
*/
if( *pRc==SQLITE_OK
&& pExpr->eType==FTSQUERY_NEAR
- && pExpr->bEof==0
&& (pExpr->pParent==0 || pExpr->pParent->eType!=FTSQUERY_NEAR)
){
Fts3Expr *p;
- int nTmp = 0; /* Bytes of temp space */
+ tdsqlite3_int64 nTmp = 0; /* Bytes of temp space */
char *aTmp; /* Temp space for PoslistNearMerge() */
/* Allocate temporary working space. */
for(p=pExpr; p->pLeft; p=p->pLeft){
+ assert( p->pRight->pPhrase->doclist.nList>0 );
nTmp += p->pRight->pPhrase->doclist.nList;
}
nTmp += p->pPhrase->doclist.nList;
- if( nTmp==0 ){
+ aTmp = tdsqlite3_malloc64(nTmp*2);
+ if( !aTmp ){
+ *pRc = SQLITE_NOMEM;
res = 0;
}else{
- aTmp = sqlite3_malloc(nTmp*2);
- if( !aTmp ){
- *pRc = SQLITE_NOMEM;
- res = 0;
- }else{
- char *aPoslist = p->pPhrase->doclist.pList;
- int nToken = p->pPhrase->nToken;
-
- for(p=p->pParent;res && p && p->eType==FTSQUERY_NEAR; p=p->pParent){
- Fts3Phrase *pPhrase = p->pRight->pPhrase;
- int nNear = p->nNear;
- res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase);
- }
-
- aPoslist = pExpr->pRight->pPhrase->doclist.pList;
- nToken = pExpr->pRight->pPhrase->nToken;
- for(p=pExpr->pLeft; p && res; p=p->pLeft){
- int nNear;
- Fts3Phrase *pPhrase;
- assert( p->pParent && p->pParent->pLeft==p );
- nNear = p->pParent->nNear;
- pPhrase = (
- p->eType==FTSQUERY_NEAR ? p->pRight->pPhrase : p->pPhrase
- );
- res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase);
- }
+ char *aPoslist = p->pPhrase->doclist.pList;
+ int nToken = p->pPhrase->nToken;
+
+ for(p=p->pParent;res && p && p->eType==FTSQUERY_NEAR; p=p->pParent){
+ Fts3Phrase *pPhrase = p->pRight->pPhrase;
+ int nNear = p->nNear;
+ res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase);
}
- sqlite3_free(aTmp);
+ aPoslist = pExpr->pRight->pPhrase->doclist.pList;
+ nToken = pExpr->pRight->pPhrase->nToken;
+ for(p=pExpr->pLeft; p && res; p=p->pLeft){
+ int nNear;
+ Fts3Phrase *pPhrase;
+ assert( p->pParent && p->pParent->pLeft==p );
+ nNear = p->pParent->nNear;
+ pPhrase = (
+ p->eType==FTSQUERY_NEAR ? p->pRight->pPhrase : p->pPhrase
+ );
+ res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase);
+ }
}
+
+ tdsqlite3_free(aTmp);
}
return res;
}
/*
-** This function is a helper function for sqlite3Fts3EvalTestDeferred().
+** This function is a helper function for tdsqlite3Fts3EvalTestDeferred().
** Assuming no error occurs or has occurred, It returns non-zero if the
** expression passed as the second argument matches the row that pCsr
** currently points to, or zero if it does not.
@@ -151670,7 +174118,7 @@ static int fts3EvalTestExpr(
** Or, if no error occurs and it seems the current row does match the FTS
** query, return 0.
*/
-SQLITE_PRIVATE int sqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc){
+SQLITE_PRIVATE int tdsqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc){
int rc = *pRc;
int bMiss = 0;
if( rc==SQLITE_OK ){
@@ -151684,13 +174132,13 @@ SQLITE_PRIVATE int sqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc){
if( pCsr->pDeferred ){
rc = fts3CursorSeek(0, pCsr);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3CacheDeferredDoclists(pCsr);
+ rc = tdsqlite3Fts3CacheDeferredDoclists(pCsr);
}
}
bMiss = (0==fts3EvalTestExpr(pCsr, pCsr->pExpr, &rc));
/* Free the position-lists accumulated for each deferred token above. */
- sqlite3Fts3FreeDeferredDoclists(pCsr);
+ tdsqlite3Fts3FreeDeferredDoclists(pCsr);
*pRc = rc;
}
return (rc==SQLITE_OK && bMiss);
@@ -151709,15 +174157,15 @@ static int fts3EvalNext(Fts3Cursor *pCsr){
}else{
do {
if( pCsr->isRequireSeek==0 ){
- sqlite3_reset(pCsr->pStmt);
+ tdsqlite3_reset(pCsr->pStmt);
}
- assert( sqlite3_data_count(pCsr->pStmt)==0 );
+ assert( tdsqlite3_data_count(pCsr->pStmt)==0 );
fts3EvalNextRow(pCsr, pExpr, &rc);
pCsr->isEof = pExpr->bEof;
pCsr->isRequireSeek = 1;
pCsr->isMatchinfoNeeded = 1;
pCsr->iPrevId = pExpr->iDocid;
- }while( pCsr->isEof==0 && sqlite3Fts3EvalTestDeferred(pCsr, &rc) );
+ }while( pCsr->isEof==0 && tdsqlite3Fts3EvalTestDeferred(pCsr, &rc) );
}
/* Check if the cursor is past the end of the docid range specified
@@ -151757,7 +174205,7 @@ static void fts3EvalRestart(
Fts3PhraseToken *pToken = &pPhrase->aToken[i];
assert( pToken->pDeferred==0 );
if( pToken->pSegcsr ){
- sqlite3Fts3MsrIncrRestart(pToken->pSegcsr);
+ tdsqlite3Fts3MsrIncrRestart(pToken->pSegcsr);
}
}
*pRc = fts3EvalPhraseStart(pCsr, 0, pPhrase);
@@ -151784,15 +174232,14 @@ static void fts3EvalRestart(
** found in Fts3Expr.pPhrase->doclist.pList for each of the phrase
** expression nodes.
*/
-static void fts3EvalUpdateCounts(Fts3Expr *pExpr){
+static void fts3EvalUpdateCounts(Fts3Expr *pExpr, int nCol){
if( pExpr ){
Fts3Phrase *pPhrase = pExpr->pPhrase;
if( pPhrase && pPhrase->doclist.pList ){
int iCol = 0;
char *p = pPhrase->doclist.pList;
- assert( *p );
- while( 1 ){
+ do{
u8 c = 0;
int iCnt = 0;
while( 0xFE & (*p | c) ){
@@ -151808,11 +174255,11 @@ static void fts3EvalUpdateCounts(Fts3Expr *pExpr){
if( *p==0x00 ) break;
p++;
p += fts3GetVarint32(p, &iCol);
- }
+ }while( iCol<nCol );
}
- fts3EvalUpdateCounts(pExpr->pLeft);
- fts3EvalUpdateCounts(pExpr->pRight);
+ fts3EvalUpdateCounts(pExpr->pLeft, nCol);
+ fts3EvalUpdateCounts(pExpr->pRight, nCol);
}
}
@@ -151839,8 +174286,8 @@ static int fts3EvalGatherStats(
Fts3Expr *pRoot; /* Root of NEAR expression */
Fts3Expr *p; /* Iterator used for several purposes */
- sqlite3_int64 iPrevId = pCsr->iPrevId;
- sqlite3_int64 iDocid;
+ tdsqlite3_int64 iPrevId = pCsr->iPrevId;
+ tdsqlite3_int64 iDocid;
u8 bEof;
/* Find the root of the NEAR expression */
@@ -151856,7 +174303,7 @@ static int fts3EvalGatherStats(
for(p=pRoot; p; p=p->pLeft){
Fts3Expr *pE = (p->eType==FTSQUERY_PHRASE?p:p->pRight);
assert( pE->aMI==0 );
- pE->aMI = (u32 *)sqlite3_malloc(pTab->nColumn * 3 * sizeof(u32));
+ pE->aMI = (u32 *)tdsqlite3_malloc64(pTab->nColumn * 3 * sizeof(u32));
if( !pE->aMI ) return SQLITE_NOMEM;
memset(pE->aMI, 0, pTab->nColumn * 3 * sizeof(u32));
}
@@ -151867,8 +174314,8 @@ static int fts3EvalGatherStats(
do {
/* Ensure the %_content statement is reset. */
- if( pCsr->isRequireSeek==0 ) sqlite3_reset(pCsr->pStmt);
- assert( sqlite3_data_count(pCsr->pStmt)==0 );
+ if( pCsr->isRequireSeek==0 ) tdsqlite3_reset(pCsr->pStmt);
+ assert( tdsqlite3_data_count(pCsr->pStmt)==0 );
/* Advance to the next document */
fts3EvalNextRow(pCsr, pRoot, &rc);
@@ -151878,11 +174325,11 @@ static int fts3EvalGatherStats(
pCsr->iPrevId = pRoot->iDocid;
}while( pCsr->isEof==0
&& pRoot->eType==FTSQUERY_NEAR
- && sqlite3Fts3EvalTestDeferred(pCsr, &rc)
+ && tdsqlite3Fts3EvalTestDeferred(pCsr, &rc)
);
if( rc==SQLITE_OK && pCsr->isEof==0 ){
- fts3EvalUpdateCounts(pRoot);
+ fts3EvalUpdateCounts(pRoot, pTab->nColumn);
}
}
@@ -151938,7 +174385,7 @@ static int fts3EvalGatherStats(
** * If the phrase is part of a NEAR expression, then only phrase instances
** that meet the NEAR constraint are included in the counts.
*/
-SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats(
+SQLITE_PRIVATE int tdsqlite3Fts3EvalPhraseStats(
Fts3Cursor *pCsr, /* FTS cursor handle */
Fts3Expr *pExpr, /* Phrase expression */
u32 *aiOut /* Array to write results into (see above) */
@@ -151986,7 +174433,7 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats(
** This function works regardless of whether or not the phrase is deferred,
** incremental, or neither.
*/
-SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist(
+SQLITE_PRIVATE int tdsqlite3Fts3EvalPhrasePoslist(
Fts3Cursor *pCsr, /* FTS3 cursor object */
Fts3Expr *pExpr, /* Phrase to return doclist for */
int iCol, /* Column to return position list for */
@@ -151996,7 +174443,7 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist(
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
char *pIter;
int iThis;
- sqlite3_int64 iDocid;
+ tdsqlite3_int64 iDocid;
/* If this phrase is applies specifically to some column other than
** column iCol, return a NULL pointer. */
@@ -152065,7 +174512,7 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist(
bEof = !pPh->doclist.nAll ||
(pIter >= (pPh->doclist.aAll + pPh->doclist.nAll));
while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)<0 ) && bEof==0 ){
- sqlite3Fts3DoclistNext(
+ tdsqlite3Fts3DoclistNext(
bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll,
&pIter, &iDocid, &bEof
);
@@ -152074,7 +174521,7 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist(
bEof = !pPh->doclist.nAll || (pIter && pIter<=pPh->doclist.aAll);
while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)>0 ) && bEof==0 ){
int dummy;
- sqlite3Fts3DoclistPrev(
+ tdsqlite3Fts3DoclistPrev(
bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll,
&pIter, &iDocid, &dummy, &bEof
);
@@ -152120,10 +174567,10 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist(
** * the contents of pPhrase->doclist, and
** * any Fts3MultiSegReader objects held by phrase tokens.
*/
-SQLITE_PRIVATE void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *pPhrase){
+SQLITE_PRIVATE void tdsqlite3Fts3EvalPhraseCleanup(Fts3Phrase *pPhrase){
if( pPhrase ){
int i;
- sqlite3_free(pPhrase->doclist.aAll);
+ tdsqlite3_free(pPhrase->doclist.aAll);
fts3EvalInvalidatePoslist(pPhrase);
memset(&pPhrase->doclist, 0, sizeof(Fts3Doclist));
for(i=0; i<pPhrase->nToken; i++){
@@ -152138,7 +174585,7 @@ SQLITE_PRIVATE void sqlite3Fts3EvalPhraseCleanup(Fts3Phrase *pPhrase){
** Return SQLITE_CORRUPT_VTAB.
*/
#ifdef SQLITE_DEBUG
-SQLITE_PRIVATE int sqlite3Fts3Corrupt(){
+SQLITE_PRIVATE int tdsqlite3Fts3Corrupt(){
return SQLITE_CORRUPT_VTAB;
}
#endif
@@ -152150,13 +174597,13 @@ SQLITE_PRIVATE int sqlite3Fts3Corrupt(){
#ifdef _WIN32
__declspec(dllexport)
#endif
-SQLITE_API int sqlite3_fts3_init(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_fts3_init(
+ tdsqlite3 *db,
char **pzErrMsg,
- const sqlite3_api_routines *pApi
+ const tdsqlite3_api_routines *pApi
){
SQLITE_EXTENSION_INIT2(pApi)
- return sqlite3Fts3Init(db);
+ return tdsqlite3Fts3Init(db);
}
#endif
@@ -152187,25 +174634,25 @@ typedef struct Fts3auxTable Fts3auxTable;
typedef struct Fts3auxCursor Fts3auxCursor;
struct Fts3auxTable {
- sqlite3_vtab base; /* Base class used by SQLite core */
+ tdsqlite3_vtab base; /* Base class used by SQLite core */
Fts3Table *pFts3Tab;
};
struct Fts3auxCursor {
- sqlite3_vtab_cursor base; /* Base class used by SQLite core */
+ tdsqlite3_vtab_cursor base; /* Base class used by SQLite core */
Fts3MultiSegReader csr; /* Must be right after "base" */
Fts3SegFilter filter;
char *zStop;
int nStop; /* Byte-length of string zStop */
int iLangid; /* Language id to query */
int isEof; /* True if cursor is at EOF */
- sqlite3_int64 iRowid; /* Current rowid */
+ tdsqlite3_int64 iRowid; /* Current rowid */
int iCol; /* Current value of 'col' column */
int nStat; /* Size of aStat[] array */
struct Fts3auxColstats {
- sqlite3_int64 nDoc; /* 'documents' values for current csr row */
- sqlite3_int64 nOcc; /* 'occurrences' values for current csr row */
+ tdsqlite3_int64 nDoc; /* 'documents' values for current csr row */
+ tdsqlite3_int64 nOcc; /* 'occurrences' values for current csr row */
} *aStat;
};
@@ -152221,18 +174668,18 @@ struct Fts3auxCursor {
** and xCreate are identical operations.
*/
static int fts3auxConnectMethod(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
void *pUnused, /* Unused */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
- char **pzErr /* OUT: sqlite3_malloc'd error message */
+ tdsqlite3_vtab **ppVtab, /* OUT: New tdsqlite3_vtab object */
+ char **pzErr /* OUT: tdsqlite3_malloc'd error message */
){
char const *zDb; /* Name of database (e.g. "main") */
char const *zFts3; /* Name of fts3 table */
int nDb; /* Result of strlen(zDb) */
int nFts3; /* Result of strlen(zFts3) */
- int nByte; /* Bytes of space to allocate here */
+ tdsqlite3_int64 nByte; /* Bytes of space to allocate here */
int rc; /* value returned by declare_vtab() */
Fts3auxTable *p; /* Virtual table object to return */
@@ -152248,7 +174695,7 @@ static int fts3auxConnectMethod(
zDb = argv[1];
nDb = (int)strlen(zDb);
if( argc==5 ){
- if( nDb==4 && 0==sqlite3_strnicmp("temp", zDb, 4) ){
+ if( nDb==4 && 0==tdsqlite3_strnicmp("temp", zDb, 4) ){
zDb = argv[3];
nDb = (int)strlen(zDb);
zFts3 = argv[4];
@@ -152260,11 +174707,11 @@ static int fts3auxConnectMethod(
}
nFts3 = (int)strlen(zFts3);
- rc = sqlite3_declare_vtab(db, FTS3_AUX_SCHEMA);
+ rc = tdsqlite3_declare_vtab(db, FTS3_AUX_SCHEMA);
if( rc!=SQLITE_OK ) return rc;
nByte = sizeof(Fts3auxTable) + sizeof(Fts3Table) + nDb + nFts3 + 2;
- p = (Fts3auxTable *)sqlite3_malloc(nByte);
+ p = (Fts3auxTable *)tdsqlite3_malloc64(nByte);
if( !p ) return SQLITE_NOMEM;
memset(p, 0, nByte);
@@ -152276,13 +174723,13 @@ static int fts3auxConnectMethod(
memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
- sqlite3Fts3Dequote((char *)p->pFts3Tab->zName);
+ tdsqlite3Fts3Dequote((char *)p->pFts3Tab->zName);
- *ppVtab = (sqlite3_vtab *)p;
+ *ppVtab = (tdsqlite3_vtab *)p;
return SQLITE_OK;
bad_args:
- sqlite3Fts3ErrMsg(pzErr, "invalid arguments to fts4aux constructor");
+ tdsqlite3Fts3ErrMsg(pzErr, "invalid arguments to fts4aux constructor");
return SQLITE_ERROR;
}
@@ -152291,17 +174738,17 @@ static int fts3auxConnectMethod(
** These tables have no persistent representation of their own, so xDisconnect
** and xDestroy are identical operations.
*/
-static int fts3auxDisconnectMethod(sqlite3_vtab *pVtab){
+static int fts3auxDisconnectMethod(tdsqlite3_vtab *pVtab){
Fts3auxTable *p = (Fts3auxTable *)pVtab;
Fts3Table *pFts3 = p->pFts3Tab;
int i;
/* Free any prepared statements held */
for(i=0; i<SizeofArray(pFts3->aStmt); i++){
- sqlite3_finalize(pFts3->aStmt[i]);
+ tdsqlite3_finalize(pFts3->aStmt[i]);
}
- sqlite3_free(pFts3->zSegmentsTbl);
- sqlite3_free(p);
+ tdsqlite3_free(pFts3->zSegmentsTbl);
+ tdsqlite3_free(p);
return SQLITE_OK;
}
@@ -152313,8 +174760,8 @@ static int fts3auxDisconnectMethod(sqlite3_vtab *pVtab){
** xBestIndex - Analyze a WHERE and ORDER BY clause.
*/
static int fts3auxBestIndexMethod(
- sqlite3_vtab *pVTab,
- sqlite3_index_info *pInfo
+ tdsqlite3_vtab *pVTab,
+ tdsqlite3_index_info *pInfo
){
int i;
int iEq = -1;
@@ -152382,39 +174829,39 @@ static int fts3auxBestIndexMethod(
/*
** xOpen - Open a cursor.
*/
-static int fts3auxOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
+static int fts3auxOpenMethod(tdsqlite3_vtab *pVTab, tdsqlite3_vtab_cursor **ppCsr){
Fts3auxCursor *pCsr; /* Pointer to cursor object to return */
UNUSED_PARAMETER(pVTab);
- pCsr = (Fts3auxCursor *)sqlite3_malloc(sizeof(Fts3auxCursor));
+ pCsr = (Fts3auxCursor *)tdsqlite3_malloc(sizeof(Fts3auxCursor));
if( !pCsr ) return SQLITE_NOMEM;
memset(pCsr, 0, sizeof(Fts3auxCursor));
- *ppCsr = (sqlite3_vtab_cursor *)pCsr;
+ *ppCsr = (tdsqlite3_vtab_cursor *)pCsr;
return SQLITE_OK;
}
/*
** xClose - Close a cursor.
*/
-static int fts3auxCloseMethod(sqlite3_vtab_cursor *pCursor){
+static int fts3auxCloseMethod(tdsqlite3_vtab_cursor *pCursor){
Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
- sqlite3Fts3SegmentsClose(pFts3);
- sqlite3Fts3SegReaderFinish(&pCsr->csr);
- sqlite3_free((void *)pCsr->filter.zTerm);
- sqlite3_free(pCsr->zStop);
- sqlite3_free(pCsr->aStat);
- sqlite3_free(pCsr);
+ tdsqlite3Fts3SegmentsClose(pFts3);
+ tdsqlite3Fts3SegReaderFinish(&pCsr->csr);
+ tdsqlite3_free((void *)pCsr->filter.zTerm);
+ tdsqlite3_free(pCsr->zStop);
+ tdsqlite3_free(pCsr->aStat);
+ tdsqlite3_free(pCsr);
return SQLITE_OK;
}
static int fts3auxGrowStatArray(Fts3auxCursor *pCsr, int nSize){
if( nSize>pCsr->nStat ){
struct Fts3auxColstats *aNew;
- aNew = (struct Fts3auxColstats *)sqlite3_realloc(pCsr->aStat,
+ aNew = (struct Fts3auxColstats *)tdsqlite3_realloc64(pCsr->aStat,
sizeof(struct Fts3auxColstats) * nSize
);
if( aNew==0 ) return SQLITE_NOMEM;
@@ -152430,7 +174877,7 @@ static int fts3auxGrowStatArray(Fts3auxCursor *pCsr, int nSize){
/*
** xNext - Advance the cursor to the next row, if any.
*/
-static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){
+static int fts3auxNextMethod(tdsqlite3_vtab_cursor *pCursor){
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
int rc;
@@ -152442,7 +174889,7 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){
if( pCsr->aStat[pCsr->iCol].nDoc>0 ) return SQLITE_OK;
}
- rc = sqlite3Fts3SegReaderStep(pFts3, &pCsr->csr);
+ rc = tdsqlite3Fts3SegReaderStep(pFts3, &pCsr->csr);
if( rc==SQLITE_ROW ){
int i = 0;
int nDoclist = pCsr->csr.nDoclist;
@@ -152465,9 +174912,9 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){
iCol = 0;
while( i<nDoclist ){
- sqlite3_int64 v = 0;
+ tdsqlite3_int64 v = 0;
- i += sqlite3Fts3GetVarint(&aDoclist[i], &v);
+ i += tdsqlite3Fts3GetVarint(&aDoclist[i], &v);
switch( eState ){
/* State 0. In this state the integer just read was a docid. */
case 0:
@@ -152525,11 +174972,11 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){
** xFilter - Initialize a cursor to point at the start of its data.
*/
static int fts3auxFilterMethod(
- sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
+ tdsqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
int idxNum, /* Strategy index */
const char *idxStr, /* Unused */
int nVal, /* Number of elements in apVal */
- sqlite3_value **apVal /* Arguments for the indexing scheme */
+ tdsqlite3_value **apVal /* Arguments for the indexing scheme */
){
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
Fts3Table *pFts3 = ((Fts3auxTable *)pCursor->pVtab)->pFts3Tab;
@@ -152569,32 +175016,32 @@ static int fts3auxFilterMethod(
/* In case this cursor is being reused, close and zero it. */
testcase(pCsr->filter.zTerm);
- sqlite3Fts3SegReaderFinish(&pCsr->csr);
- sqlite3_free((void *)pCsr->filter.zTerm);
- sqlite3_free(pCsr->aStat);
+ tdsqlite3Fts3SegReaderFinish(&pCsr->csr);
+ tdsqlite3_free((void *)pCsr->filter.zTerm);
+ tdsqlite3_free(pCsr->aStat);
memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr);
pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
if( isScan ) pCsr->filter.flags |= FTS3_SEGMENT_SCAN;
if( iEq>=0 || iGe>=0 ){
- const unsigned char *zStr = sqlite3_value_text(apVal[0]);
+ const unsigned char *zStr = tdsqlite3_value_text(apVal[0]);
assert( (iEq==0 && iGe==-1) || (iEq==-1 && iGe==0) );
if( zStr ){
- pCsr->filter.zTerm = sqlite3_mprintf("%s", zStr);
- pCsr->filter.nTerm = sqlite3_value_bytes(apVal[0]);
+ pCsr->filter.zTerm = tdsqlite3_mprintf("%s", zStr);
if( pCsr->filter.zTerm==0 ) return SQLITE_NOMEM;
+ pCsr->filter.nTerm = (int)strlen(pCsr->filter.zTerm);
}
}
if( iLe>=0 ){
- pCsr->zStop = sqlite3_mprintf("%s", sqlite3_value_text(apVal[iLe]));
- pCsr->nStop = sqlite3_value_bytes(apVal[iLe]);
+ pCsr->zStop = tdsqlite3_mprintf("%s", tdsqlite3_value_text(apVal[iLe]));
if( pCsr->zStop==0 ) return SQLITE_NOMEM;
+ pCsr->nStop = (int)strlen(pCsr->zStop);
}
if( iLangid>=0 ){
- iLangVal = sqlite3_value_int(apVal[iLangid]);
+ iLangVal = tdsqlite3_value_int(apVal[iLangid]);
/* If the user specified a negative value for the languageid, use zero
** instead. This works, as the "languageid=?" constraint will also
@@ -152605,11 +175052,11 @@ static int fts3auxFilterMethod(
}
pCsr->iLangid = iLangVal;
- rc = sqlite3Fts3SegReaderCursor(pFts3, iLangVal, 0, FTS3_SEGCURSOR_ALL,
+ rc = tdsqlite3Fts3SegReaderCursor(pFts3, iLangVal, 0, FTS3_SEGCURSOR_ALL,
pCsr->filter.zTerm, pCsr->filter.nTerm, 0, isScan, &pCsr->csr
);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter);
+ rc = tdsqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter);
}
if( rc==SQLITE_OK ) rc = fts3auxNextMethod(pCursor);
@@ -152619,7 +175066,7 @@ static int fts3auxFilterMethod(
/*
** xEof - Return true if the cursor is at EOF, or false otherwise.
*/
-static int fts3auxEofMethod(sqlite3_vtab_cursor *pCursor){
+static int fts3auxEofMethod(tdsqlite3_vtab_cursor *pCursor){
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
return pCsr->isEof;
}
@@ -152628,8 +175075,8 @@ static int fts3auxEofMethod(sqlite3_vtab_cursor *pCursor){
** xColumn - Return a column value.
*/
static int fts3auxColumnMethod(
- sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
- sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
+ tdsqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
+ tdsqlite3_context *pCtx, /* Context for tdsqlite3_result_xxx() calls */
int iCol /* Index of column to read value from */
){
Fts3auxCursor *p = (Fts3auxCursor *)pCursor;
@@ -152637,28 +175084,28 @@ static int fts3auxColumnMethod(
assert( p->isEof==0 );
switch( iCol ){
case 0: /* term */
- sqlite3_result_text(pCtx, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(pCtx, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT);
break;
case 1: /* col */
if( p->iCol ){
- sqlite3_result_int(pCtx, p->iCol-1);
+ tdsqlite3_result_int(pCtx, p->iCol-1);
}else{
- sqlite3_result_text(pCtx, "*", -1, SQLITE_STATIC);
+ tdsqlite3_result_text(pCtx, "*", -1, SQLITE_STATIC);
}
break;
case 2: /* documents */
- sqlite3_result_int64(pCtx, p->aStat[p->iCol].nDoc);
+ tdsqlite3_result_int64(pCtx, p->aStat[p->iCol].nDoc);
break;
case 3: /* occurrences */
- sqlite3_result_int64(pCtx, p->aStat[p->iCol].nOcc);
+ tdsqlite3_result_int64(pCtx, p->aStat[p->iCol].nOcc);
break;
default: /* languageid */
assert( iCol==4 );
- sqlite3_result_int(pCtx, p->iLangid);
+ tdsqlite3_result_int(pCtx, p->iLangid);
break;
}
@@ -152669,7 +175116,7 @@ static int fts3auxColumnMethod(
** xRowid - Return the current rowid for the cursor.
*/
static int fts3auxRowidMethod(
- sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
+ tdsqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
sqlite_int64 *pRowid /* OUT: Rowid value */
){
Fts3auxCursor *pCsr = (Fts3auxCursor *)pCursor;
@@ -152679,10 +175126,10 @@ static int fts3auxRowidMethod(
/*
** Register the fts3aux module with database connection db. Return SQLITE_OK
-** if successful or an error code if sqlite3_create_module() fails.
+** if successful or an error code if tdsqlite3_create_module() fails.
*/
-SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){
- static const sqlite3_module fts3aux_module = {
+SQLITE_PRIVATE int tdsqlite3Fts3InitAux(tdsqlite3 *db){
+ static const tdsqlite3_module fts3aux_module = {
0, /* iVersion */
fts3auxConnectMethod, /* xCreate */
fts3auxConnectMethod, /* xConnect */
@@ -152705,11 +175152,12 @@ SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){
0, /* xRename */
0, /* xSavepoint */
0, /* xRelease */
- 0 /* xRollbackTo */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
};
int rc; /* Return code */
- rc = sqlite3_create_module(db, "fts4aux", &fts3aux_module, 0);
+ rc = tdsqlite3_create_module(db, "fts4aux", &fts3aux_module, 0);
return rc;
}
@@ -152755,7 +175203,7 @@ SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){
** AND operators have a higher precedence than OR.
**
** If compiled with SQLITE_TEST defined, then this module exports the
-** symbol "int sqlite3_fts3_enable_parentheses". Setting this variable
+** symbol "int tdsqlite3_fts3_enable_parentheses". Setting this variable
** to zero causes the module to use the old syntax. If it is set to
** non-zero the new syntax is activated. This is so both syntaxes can
** be tested using a single build of testfixture.
@@ -152783,12 +175231,12 @@ SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){
*/
#ifdef SQLITE_TEST
-SQLITE_API int sqlite3_fts3_enable_parentheses = 0;
+SQLITE_API int tdsqlite3_fts3_enable_parentheses = 0;
#else
# ifdef SQLITE_ENABLE_FTS3_PARENTHESIS
-# define sqlite3_fts3_enable_parentheses 1
+# define tdsqlite3_fts3_enable_parentheses 1
# else
-# define sqlite3_fts3_enable_parentheses 0
+# define tdsqlite3_fts3_enable_parentheses 0
# endif
#endif
@@ -152810,14 +175258,14 @@ SQLITE_API int sqlite3_fts3_enable_parentheses = 0;
*/
typedef struct ParseContext ParseContext;
struct ParseContext {
- sqlite3_tokenizer *pTokenizer; /* Tokenizer module */
+ tdsqlite3_tokenizer *pTokenizer; /* Tokenizer module */
int iLangid; /* Language id used with tokenizer */
const char **azCol; /* Array of column names for fts3 table */
int bFts4; /* True to allow FTS4-only syntax */
int nCol; /* Number of entries in azCol[] */
int iDefaultCol; /* Default column to query */
int isNot; /* True if getNextNode() sees a unary - */
- sqlite3_context *pCtx; /* Write error message here */
+ tdsqlite3_context *pCtx; /* Write error message here */
int nNest; /* Number of nested brackets */
};
@@ -152837,25 +175285,25 @@ static int fts3isspace(char c){
}
/*
-** Allocate nByte bytes of memory using sqlite3_malloc(). If successful,
+** Allocate nByte bytes of memory using tdsqlite3_malloc(). If successful,
** zero the memory before returning a pointer to it. If unsuccessful,
** return NULL.
*/
-static void *fts3MallocZero(int nByte){
- void *pRet = sqlite3_malloc(nByte);
+static void *fts3MallocZero(tdsqlite3_int64 nByte){
+ void *pRet = tdsqlite3_malloc64(nByte);
if( pRet ) memset(pRet, 0, nByte);
return pRet;
}
-SQLITE_PRIVATE int sqlite3Fts3OpenTokenizer(
- sqlite3_tokenizer *pTokenizer,
+SQLITE_PRIVATE int tdsqlite3Fts3OpenTokenizer(
+ tdsqlite3_tokenizer *pTokenizer,
int iLangid,
const char *z,
int n,
- sqlite3_tokenizer_cursor **ppCsr
+ tdsqlite3_tokenizer_cursor **ppCsr
){
- sqlite3_tokenizer_module const *pModule = pTokenizer->pModule;
- sqlite3_tokenizer_cursor *pCsr = 0;
+ tdsqlite3_tokenizer_module const *pModule = pTokenizer->pModule;
+ tdsqlite3_tokenizer_cursor *pCsr = 0;
int rc;
rc = pModule->xOpen(pTokenizer, z, n, &pCsr);
@@ -152887,7 +175335,7 @@ static int fts3ExprParse(ParseContext *, const char *, int, Fts3Expr **, int *);
** single token and set *ppExpr to point to it. If the end of the buffer is
** reached before a token is found, set *ppExpr to zero. It is the
** responsibility of the caller to eventually deallocate the allocated
-** Fts3Expr structure (if any) by passing it to sqlite3_free().
+** Fts3Expr structure (if any) by passing it to tdsqlite3_free().
**
** Return SQLITE_OK if successful, or SQLITE_NOMEM if a memory allocation
** fails.
@@ -152899,25 +175347,25 @@ static int getNextToken(
Fts3Expr **ppExpr, /* OUT: expression */
int *pnConsumed /* OUT: Number of bytes consumed */
){
- sqlite3_tokenizer *pTokenizer = pParse->pTokenizer;
- sqlite3_tokenizer_module const *pModule = pTokenizer->pModule;
+ tdsqlite3_tokenizer *pTokenizer = pParse->pTokenizer;
+ tdsqlite3_tokenizer_module const *pModule = pTokenizer->pModule;
int rc;
- sqlite3_tokenizer_cursor *pCursor;
+ tdsqlite3_tokenizer_cursor *pCursor;
Fts3Expr *pRet = 0;
int i = 0;
/* Set variable i to the maximum number of bytes of input to tokenize. */
for(i=0; i<n; i++){
- if( sqlite3_fts3_enable_parentheses && (z[i]=='(' || z[i]==')') ) break;
+ if( tdsqlite3_fts3_enable_parentheses && (z[i]=='(' || z[i]==')') ) break;
if( z[i]=='"' ) break;
}
*pnConsumed = i;
- rc = sqlite3Fts3OpenTokenizer(pTokenizer, pParse->iLangid, z, i, &pCursor);
+ rc = tdsqlite3Fts3OpenTokenizer(pTokenizer, pParse->iLangid, z, i, &pCursor);
if( rc==SQLITE_OK ){
const char *zToken;
int nToken = 0, iStart = 0, iEnd = 0, iPosition = 0;
- int nByte; /* total space to allocate */
+ tdsqlite3_int64 nByte; /* total space to allocate */
rc = pModule->xNext(pCursor, &zToken, &nToken, &iStart, &iEnd, &iPosition);
if( rc==SQLITE_OK ){
@@ -152940,7 +175388,7 @@ static int getNextToken(
}
while( 1 ){
- if( !sqlite3_fts3_enable_parentheses
+ if( !tdsqlite3_fts3_enable_parentheses
&& iStart>0 && z[iStart-1]=='-'
){
pParse->isNot = 1;
@@ -152971,10 +175419,10 @@ static int getNextToken(
** Enlarge a memory allocation. If an out-of-memory allocation occurs,
** then free the old allocation.
*/
-static void *fts3ReallocOrFree(void *pOrig, int nNew){
- void *pRet = sqlite3_realloc(pOrig, nNew);
+static void *fts3ReallocOrFree(void *pOrig, tdsqlite3_int64 nNew){
+ void *pRet = tdsqlite3_realloc64(pOrig, nNew);
if( !pRet ){
- sqlite3_free(pOrig);
+ tdsqlite3_free(pOrig);
}
return pRet;
}
@@ -152996,11 +175444,11 @@ static int getNextString(
const char *zInput, int nInput, /* Input string */
Fts3Expr **ppExpr /* OUT: expression */
){
- sqlite3_tokenizer *pTokenizer = pParse->pTokenizer;
- sqlite3_tokenizer_module const *pModule = pTokenizer->pModule;
+ tdsqlite3_tokenizer *pTokenizer = pParse->pTokenizer;
+ tdsqlite3_tokenizer_module const *pModule = pTokenizer->pModule;
int rc;
Fts3Expr *p = 0;
- sqlite3_tokenizer_cursor *pCursor = 0;
+ tdsqlite3_tokenizer_cursor *pCursor = 0;
char *zTemp = 0;
int nTemp = 0;
@@ -153010,7 +175458,7 @@ static int getNextString(
/* The final Fts3Expr data structure, including the Fts3Phrase,
** Fts3PhraseToken structures token buffers are all stored as a single
** allocation so that the expression can be freed with a single call to
- ** sqlite3_free(). Setting this up requires a two pass approach.
+ ** tdsqlite3_free(). Setting this up requires a two pass approach.
**
** The first pass, in the block below, uses a tokenizer cursor to iterate
** through the tokens in the expression. This pass uses fts3ReallocOrFree()
@@ -153026,7 +175474,7 @@ static int getNextString(
** appends buffer zTemp to buffer p, and fills in the Fts3Expr and Fts3Phrase
** structures.
*/
- rc = sqlite3Fts3OpenTokenizer(
+ rc = tdsqlite3Fts3OpenTokenizer(
pTokenizer, pParse->iLangid, zInput, nInput, &pCursor);
if( rc==SQLITE_OK ){
int ii;
@@ -153076,7 +175524,7 @@ static int getNextString(
zBuf = (char *)&p->pPhrase->aToken[nToken];
if( zTemp ){
memcpy(zBuf, zTemp, nTemp);
- sqlite3_free(zTemp);
+ tdsqlite3_free(zTemp);
}else{
assert( nTemp==0 );
}
@@ -153095,8 +175543,8 @@ no_mem:
if( pCursor ){
pModule->xClose(pCursor);
}
- sqlite3_free(zTemp);
- sqlite3_free(p);
+ tdsqlite3_free(zTemp);
+ tdsqlite3_free(p);
*ppExpr = 0;
return SQLITE_NOMEM;
}
@@ -153152,7 +175600,7 @@ static int getNextNode(
for(ii=0; ii<(int)(sizeof(aKeyword)/sizeof(struct Fts3Keyword)); ii++){
const struct Fts3Keyword *pKey = &aKeyword[ii];
- if( (pKey->parenOnly & ~sqlite3_fts3_enable_parentheses)!=0 ){
+ if( (pKey->parenOnly & ~tdsqlite3_fts3_enable_parentheses)!=0 ){
continue;
}
@@ -153211,12 +175659,11 @@ static int getNextNode(
return getNextString(pParse, &zInput[1], ii-1, ppExpr);
}
- if( sqlite3_fts3_enable_parentheses ){
+ if( tdsqlite3_fts3_enable_parentheses ){
if( *zInput=='(' ){
int nConsumed = 0;
pParse->nNest++;
rc = fts3ExprParse(pParse, zInput+1, nInput-1, ppExpr, &nConsumed);
- if( rc==SQLITE_OK && !*ppExpr ){ rc = SQLITE_DONE; }
*pnConsumed = (int)(zInput - z) + 1 + nConsumed;
return rc;
}else if( *zInput==')' ){
@@ -153228,7 +175675,7 @@ static int getNextNode(
}
/* If control flows to this point, this must be a regular token, or
- ** the end of the input. Read a regular token using the sqlite3_tokenizer
+ ** the end of the input. Read a regular token using the tdsqlite3_tokenizer
** interface. Before doing so, figure out if there is an explicit
** column specifier for the token.
**
@@ -153244,7 +175691,7 @@ static int getNextNode(
const char *zStr = pParse->azCol[ii];
int nStr = (int)strlen(zStr);
if( nInput>nStr && zInput[nStr]==':'
- && sqlite3_strnicmp(zStr, zInput, nStr)==0
+ && tdsqlite3_strnicmp(zStr, zInput, nStr)==0
){
iCol = ii;
iColLen = (int)((zInput - z) + nStr + 1);
@@ -153277,7 +175724,7 @@ static int getNextNode(
*/
static int opPrecedence(Fts3Expr *p){
assert( p->eType!=FTSQUERY_PHRASE );
- if( sqlite3_fts3_enable_parentheses ){
+ if( tdsqlite3_fts3_enable_parentheses ){
return p->eType;
}else if( p->eType==FTSQUERY_NEAR ){
return 1;
@@ -153351,13 +175798,13 @@ static int fts3ExprParse(
if( p ){
int isPhrase;
- if( !sqlite3_fts3_enable_parentheses
+ if( !tdsqlite3_fts3_enable_parentheses
&& p->eType==FTSQUERY_PHRASE && pParse->isNot
){
/* Create an implicit NOT operator. */
Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr));
if( !pNot ){
- sqlite3Fts3ExprFree(p);
+ tdsqlite3Fts3ExprFree(p);
rc = SQLITE_NOMEM;
goto exprparse_out;
}
@@ -153380,7 +175827,7 @@ static int fts3ExprParse(
** isRequirePhrase is set, this is a syntax error.
*/
if( !isPhrase && isRequirePhrase ){
- sqlite3Fts3ExprFree(p);
+ tdsqlite3Fts3ExprFree(p);
rc = SQLITE_ERROR;
goto exprparse_out;
}
@@ -153391,7 +175838,7 @@ static int fts3ExprParse(
assert( pRet && pPrev );
pAnd = fts3MallocZero(sizeof(Fts3Expr));
if( !pAnd ){
- sqlite3Fts3ExprFree(p);
+ tdsqlite3Fts3ExprFree(p);
rc = SQLITE_NOMEM;
goto exprparse_out;
}
@@ -153413,7 +175860,7 @@ static int fts3ExprParse(
(eType==FTSQUERY_NEAR && !isPhrase && pPrev->eType!=FTSQUERY_PHRASE)
|| (eType!=FTSQUERY_PHRASE && isPhrase && pPrev->eType==FTSQUERY_NEAR)
)){
- sqlite3Fts3ExprFree(p);
+ tdsqlite3Fts3ExprFree(p);
rc = SQLITE_ERROR;
goto exprparse_out;
}
@@ -153446,7 +175893,7 @@ static int fts3ExprParse(
if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
- if( !sqlite3_fts3_enable_parentheses && pNotBranch ){
+ if( !tdsqlite3_fts3_enable_parentheses && pNotBranch ){
if( !pRet ){
rc = SQLITE_ERROR;
}else{
@@ -153464,8 +175911,8 @@ static int fts3ExprParse(
exprparse_out:
if( rc!=SQLITE_OK ){
- sqlite3Fts3ExprFree(pRet);
- sqlite3Fts3ExprFree(pNotBranch);
+ tdsqlite3Fts3ExprFree(pRet);
+ tdsqlite3Fts3ExprFree(pNotBranch);
pRet = 0;
}
*ppExpr = pRet;
@@ -153515,7 +175962,7 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){
if( rc==SQLITE_OK ){
if( (eType==FTSQUERY_AND || eType==FTSQUERY_OR) ){
Fts3Expr **apLeaf;
- apLeaf = (Fts3Expr **)sqlite3_malloc(sizeof(Fts3Expr *) * nMaxDepth);
+ apLeaf = (Fts3Expr **)tdsqlite3_malloc64(sizeof(Fts3Expr *) * nMaxDepth);
if( 0==apLeaf ){
rc = SQLITE_NOMEM;
}else{
@@ -153565,7 +176012,7 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){
}
}
if( p ){
- sqlite3Fts3ExprFree(p);
+ tdsqlite3Fts3ExprFree(p);
rc = SQLITE_TOOBIG;
break;
}
@@ -153616,19 +176063,19 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){
}else{
/* An error occurred. Delete the contents of the apLeaf[] array
** and pFree list. Everything else is cleaned up by the call to
- ** sqlite3Fts3ExprFree(pRoot) below. */
+ ** tdsqlite3Fts3ExprFree(pRoot) below. */
Fts3Expr *pDel;
for(i=0; i<nMaxDepth; i++){
- sqlite3Fts3ExprFree(apLeaf[i]);
+ tdsqlite3Fts3ExprFree(apLeaf[i]);
}
while( (pDel=pFree)!=0 ){
pFree = pDel->pParent;
- sqlite3_free(pDel);
+ tdsqlite3_free(pDel);
}
}
assert( pFree==0 );
- sqlite3_free( apLeaf );
+ tdsqlite3_free( apLeaf );
}
}else if( eType==FTSQUERY_NOT ){
Fts3Expr *pLeft = pRoot->pLeft;
@@ -153645,8 +176092,8 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){
}
if( rc!=SQLITE_OK ){
- sqlite3Fts3ExprFree(pRight);
- sqlite3Fts3ExprFree(pLeft);
+ tdsqlite3Fts3ExprFree(pRight);
+ tdsqlite3Fts3ExprFree(pLeft);
}else{
assert( pLeft && pRight );
pRoot->pLeft = pLeft;
@@ -153658,7 +176105,7 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){
}
if( rc!=SQLITE_OK ){
- sqlite3Fts3ExprFree(pRoot);
+ tdsqlite3Fts3ExprFree(pRoot);
pRoot = 0;
}
*pp = pRoot;
@@ -153666,18 +176113,18 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){
}
/*
-** This function is similar to sqlite3Fts3ExprParse(), with the following
+** This function is similar to tdsqlite3Fts3ExprParse(), with the following
** differences:
**
** 1. It does not do expression rebalancing.
** 2. It does not check that the expression does not exceed the
** maximum allowable depth.
** 3. Even if it fails, *ppExpr may still be set to point to an
-** expression tree. It should be deleted using sqlite3Fts3ExprFree()
+** expression tree. It should be deleted using tdsqlite3Fts3ExprFree()
** in this case.
*/
static int fts3ExprParseUnbalanced(
- sqlite3_tokenizer *pTokenizer, /* Tokenizer module */
+ tdsqlite3_tokenizer *pTokenizer, /* Tokenizer module */
int iLangid, /* Language id for tokenizer */
char **azCol, /* Array of column names for fts3 table */
int bFts4, /* True to allow FTS4-only syntax */
@@ -153739,8 +176186,8 @@ static int fts3ExprParseUnbalanced(
** specified as part of the query string), or -1 if tokens may by default
** match any table column.
*/
-SQLITE_PRIVATE int sqlite3Fts3ExprParse(
- sqlite3_tokenizer *pTokenizer, /* Tokenizer module */
+SQLITE_PRIVATE int tdsqlite3Fts3ExprParse(
+ tdsqlite3_tokenizer *pTokenizer, /* Tokenizer module */
int iLangid, /* Language id for tokenizer */
char **azCol, /* Array of column names for fts3 table */
int bFts4, /* True to allow FTS4-only syntax */
@@ -153748,7 +176195,7 @@ SQLITE_PRIVATE int sqlite3Fts3ExprParse(
int iDefaultCol, /* Default column to query */
const char *z, int n, /* Text of MATCH query */
Fts3Expr **ppExpr, /* OUT: Parsed query structure */
- char **pzErr /* OUT: Error message (sqlite3_malloc) */
+ char **pzErr /* OUT: Error message (tdsqlite3_malloc) */
){
int rc = fts3ExprParseUnbalanced(
pTokenizer, iLangid, azCol, bFts4, nCol, iDefaultCol, z, n, ppExpr
@@ -153764,16 +176211,16 @@ SQLITE_PRIVATE int sqlite3Fts3ExprParse(
}
if( rc!=SQLITE_OK ){
- sqlite3Fts3ExprFree(*ppExpr);
+ tdsqlite3Fts3ExprFree(*ppExpr);
*ppExpr = 0;
if( rc==SQLITE_TOOBIG ){
- sqlite3Fts3ErrMsg(pzErr,
+ tdsqlite3Fts3ErrMsg(pzErr,
"FTS expression tree is too large (maximum depth %d)",
SQLITE_FTS3_MAX_EXPR_DEPTH
);
rc = SQLITE_ERROR;
}else if( rc==SQLITE_ERROR ){
- sqlite3Fts3ErrMsg(pzErr, "malformed MATCH expression: [%s]", z);
+ tdsqlite3Fts3ErrMsg(pzErr, "malformed MATCH expression: [%s]", z);
}
}
@@ -153785,19 +176232,19 @@ SQLITE_PRIVATE int sqlite3Fts3ExprParse(
*/
static void fts3FreeExprNode(Fts3Expr *p){
assert( p->eType==FTSQUERY_PHRASE || p->pPhrase==0 );
- sqlite3Fts3EvalPhraseCleanup(p->pPhrase);
- sqlite3_free(p->aMI);
- sqlite3_free(p);
+ tdsqlite3Fts3EvalPhraseCleanup(p->pPhrase);
+ tdsqlite3_free(p->aMI);
+ tdsqlite3_free(p);
}
/*
-** Free a parsed fts3 query expression allocated by sqlite3Fts3ExprParse().
+** Free a parsed fts3 query expression allocated by tdsqlite3Fts3ExprParse().
**
** This function would be simpler if it recursively called itself. But
** that would mean passing a sufficiently large expression to ExprParse()
** could cause a stack overflow.
*/
-SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *pDel){
+SQLITE_PRIVATE void tdsqlite3Fts3ExprFree(Fts3Expr *pDel){
Fts3Expr *p;
assert( pDel==0 || pDel->pParent==0 );
for(p=pDel; p && (p->pLeft||p->pRight); p=(p->pLeft ? p->pLeft : p->pRight)){
@@ -153828,55 +176275,27 @@ SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *pDel){
/* #include <stdio.h> */
/*
-** Function to query the hash-table of tokenizers (see README.tokenizers).
-*/
-static int queryTestTokenizer(
- sqlite3 *db,
- const char *zName,
- const sqlite3_tokenizer_module **pp
-){
- int rc;
- sqlite3_stmt *pStmt;
- const char zSql[] = "SELECT fts3_tokenizer(?)";
-
- *pp = 0;
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
- if( rc!=SQLITE_OK ){
- return rc;
- }
-
- sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){
- memcpy((void *)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp));
- }
- }
-
- return sqlite3_finalize(pStmt);
-}
-
-/*
** Return a pointer to a buffer containing a text representation of the
** expression passed as the first argument. The buffer is obtained from
-** sqlite3_malloc(). It is the responsibility of the caller to use
-** sqlite3_free() to release the memory. If an OOM condition is encountered,
+** tdsqlite3_malloc(). It is the responsibility of the caller to use
+** tdsqlite3_free() to release the memory. If an OOM condition is encountered,
** NULL is returned.
**
** If the second argument is not NULL, then its contents are prepended to
-** the returned expression text and then freed using sqlite3_free().
+** the returned expression text and then freed using tdsqlite3_free().
*/
static char *exprToString(Fts3Expr *pExpr, char *zBuf){
if( pExpr==0 ){
- return sqlite3_mprintf("");
+ return tdsqlite3_mprintf("");
}
switch( pExpr->eType ){
case FTSQUERY_PHRASE: {
Fts3Phrase *pPhrase = pExpr->pPhrase;
int i;
- zBuf = sqlite3_mprintf(
+ zBuf = tdsqlite3_mprintf(
"%zPHRASE %d 0", zBuf, pPhrase->iColumn);
for(i=0; zBuf && i<pPhrase->nToken; i++){
- zBuf = sqlite3_mprintf("%z %.*s%s", zBuf,
+ zBuf = tdsqlite3_mprintf("%z %.*s%s", zBuf,
pPhrase->aToken[i].n, pPhrase->aToken[i].z,
(pPhrase->aToken[i].isPrefix?"+":"")
);
@@ -153885,25 +176304,25 @@ static char *exprToString(Fts3Expr *pExpr, char *zBuf){
}
case FTSQUERY_NEAR:
- zBuf = sqlite3_mprintf("%zNEAR/%d ", zBuf, pExpr->nNear);
+ zBuf = tdsqlite3_mprintf("%zNEAR/%d ", zBuf, pExpr->nNear);
break;
case FTSQUERY_NOT:
- zBuf = sqlite3_mprintf("%zNOT ", zBuf);
+ zBuf = tdsqlite3_mprintf("%zNOT ", zBuf);
break;
case FTSQUERY_AND:
- zBuf = sqlite3_mprintf("%zAND ", zBuf);
+ zBuf = tdsqlite3_mprintf("%zAND ", zBuf);
break;
case FTSQUERY_OR:
- zBuf = sqlite3_mprintf("%zOR ", zBuf);
+ zBuf = tdsqlite3_mprintf("%zOR ", zBuf);
break;
}
- if( zBuf ) zBuf = sqlite3_mprintf("%z{", zBuf);
+ if( zBuf ) zBuf = tdsqlite3_mprintf("%z{", zBuf);
if( zBuf ) zBuf = exprToString(pExpr->pLeft, zBuf);
- if( zBuf ) zBuf = sqlite3_mprintf("%z} {", zBuf);
+ if( zBuf ) zBuf = tdsqlite3_mprintf("%z} {", zBuf);
if( zBuf ) zBuf = exprToString(pExpr->pRight, zBuf);
- if( zBuf ) zBuf = sqlite3_mprintf("%z}", zBuf);
+ if( zBuf ) zBuf = tdsqlite3_mprintf("%z}", zBuf);
return zBuf;
}
@@ -153922,13 +176341,13 @@ static char *exprToString(Fts3Expr *pExpr, char *zBuf){
**
** SELECT fts3_exprtest('simple', 'Bill col2:Bloggs', 'col1', 'col2');
*/
-static void fts3ExprTest(
- sqlite3_context *context,
+static void fts3ExprTestCommon(
+ int bRebalance,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- sqlite3_tokenizer_module const *pModule = 0;
- sqlite3_tokenizer *pTokenizer = 0;
+ tdsqlite3_tokenizer *pTokenizer = 0;
int rc;
char **azCol = 0;
const char *zExpr;
@@ -153937,52 +176356,48 @@ static void fts3ExprTest(
int ii;
Fts3Expr *pExpr;
char *zBuf = 0;
- sqlite3 *db = sqlite3_context_db_handle(context);
+ Fts3Hash *pHash = (Fts3Hash*)tdsqlite3_user_data(context);
+ const char *zTokenizer = 0;
+ char *zErr = 0;
if( argc<3 ){
- sqlite3_result_error(context,
+ tdsqlite3_result_error(context,
"Usage: fts3_exprtest(tokenizer, expr, col1, ...", -1
);
return;
}
- rc = queryTestTokenizer(db,
- (const char *)sqlite3_value_text(argv[0]), &pModule);
- if( rc==SQLITE_NOMEM ){
- sqlite3_result_error_nomem(context);
- goto exprtest_out;
- }else if( !pModule ){
- sqlite3_result_error(context, "No such tokenizer module", -1);
- goto exprtest_out;
- }
-
- rc = pModule->xCreate(0, 0, &pTokenizer);
- assert( rc==SQLITE_NOMEM || rc==SQLITE_OK );
- if( rc==SQLITE_NOMEM ){
- sqlite3_result_error_nomem(context);
- goto exprtest_out;
+ zTokenizer = (const char*)tdsqlite3_value_text(argv[0]);
+ rc = tdsqlite3Fts3InitTokenizer(pHash, zTokenizer, &pTokenizer, &zErr);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_NOMEM ){
+ tdsqlite3_result_error_nomem(context);
+ }else{
+ tdsqlite3_result_error(context, zErr, -1);
+ }
+ tdsqlite3_free(zErr);
+ return;
}
- pTokenizer->pModule = pModule;
- zExpr = (const char *)sqlite3_value_text(argv[1]);
- nExpr = sqlite3_value_bytes(argv[1]);
+ zExpr = (const char *)tdsqlite3_value_text(argv[1]);
+ nExpr = tdsqlite3_value_bytes(argv[1]);
nCol = argc-2;
- azCol = (char **)sqlite3_malloc(nCol*sizeof(char *));
+ azCol = (char **)tdsqlite3_malloc64(nCol*sizeof(char *));
if( !azCol ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
goto exprtest_out;
}
for(ii=0; ii<nCol; ii++){
- azCol[ii] = (char *)sqlite3_value_text(argv[ii+2]);
+ azCol[ii] = (char *)tdsqlite3_value_text(argv[ii+2]);
}
- if( sqlite3_user_data(context) ){
+ if( bRebalance ){
char *zDummy = 0;
- rc = sqlite3Fts3ExprParse(
+ rc = tdsqlite3Fts3ExprParse(
pTokenizer, 0, azCol, 0, nCol, nCol, zExpr, nExpr, &pExpr, &zDummy
);
assert( rc==SQLITE_OK || pExpr==0 );
- sqlite3_free(zDummy);
+ tdsqlite3_free(zDummy);
}else{
rc = fts3ExprParseUnbalanced(
pTokenizer, 0, azCol, 0, nCol, nCol, zExpr, nExpr, &pExpr
@@ -153990,35 +176405,50 @@ static void fts3ExprTest(
}
if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){
- sqlite3Fts3ExprFree(pExpr);
- sqlite3_result_error(context, "Error parsing expression", -1);
+ tdsqlite3Fts3ExprFree(pExpr);
+ tdsqlite3_result_error(context, "Error parsing expression", -1);
}else if( rc==SQLITE_NOMEM || !(zBuf = exprToString(pExpr, 0)) ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
}else{
- sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
- sqlite3_free(zBuf);
+ tdsqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT);
+ tdsqlite3_free(zBuf);
}
- sqlite3Fts3ExprFree(pExpr);
+ tdsqlite3Fts3ExprFree(pExpr);
exprtest_out:
- if( pModule && pTokenizer ){
- rc = pModule->xDestroy(pTokenizer);
+ if( pTokenizer ){
+ rc = pTokenizer->pModule->xDestroy(pTokenizer);
}
- sqlite3_free(azCol);
+ tdsqlite3_free(azCol);
+}
+
+static void fts3ExprTest(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ fts3ExprTestCommon(0, context, argc, argv);
+}
+static void fts3ExprTestRebalance(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ fts3ExprTestCommon(1, context, argc, argv);
}
/*
** Register the query expression parser test function fts3_exprtest()
** with database connection db.
*/
-SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3* db){
- int rc = sqlite3_create_function(
- db, "fts3_exprtest", -1, SQLITE_UTF8, 0, fts3ExprTest, 0, 0
+SQLITE_PRIVATE int tdsqlite3Fts3ExprInitTestInterface(tdsqlite3 *db, Fts3Hash *pHash){
+ int rc = tdsqlite3_create_function(
+ db, "fts3_exprtest", -1, SQLITE_UTF8, (void*)pHash, fts3ExprTest, 0, 0
);
if( rc==SQLITE_OK ){
- rc = sqlite3_create_function(db, "fts3_exprtest_rebalance",
- -1, SQLITE_UTF8, (void *)1, fts3ExprTest, 0, 0
+ rc = tdsqlite3_create_function(db, "fts3_exprtest_rebalance",
+ -1, SQLITE_UTF8, (void*)pHash, fts3ExprTestRebalance, 0, 0
);
}
return rc;
@@ -154066,15 +176496,15 @@ SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3* db){
/*
** Malloc and Free functions
*/
-static void *fts3HashMalloc(int n){
- void *p = sqlite3_malloc(n);
+static void *fts3HashMalloc(tdsqlite3_int64 n){
+ void *p = tdsqlite3_malloc64(n);
if( p ){
memset(p, 0, n);
}
return p;
}
static void fts3HashFree(void *p){
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
/* Turn bulk memory into a hash table object by initializing the
@@ -154087,7 +176517,7 @@ static void fts3HashFree(void *p){
** true if the hash table should make its own private copy of keys and
** false if it should just use the supplied pointer.
*/
-SQLITE_PRIVATE void sqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey){
+SQLITE_PRIVATE void tdsqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copyKey){
assert( pNew!=0 );
assert( keyClass>=FTS3_HASH_STRING && keyClass<=FTS3_HASH_BINARY );
pNew->keyClass = keyClass;
@@ -154102,7 +176532,7 @@ SQLITE_PRIVATE void sqlite3Fts3HashInit(Fts3Hash *pNew, char keyClass, char copy
** Call this routine to delete a hash table or to reset a hash table
** to the empty state.
*/
-SQLITE_PRIVATE void sqlite3Fts3HashClear(Fts3Hash *pH){
+SQLITE_PRIVATE void tdsqlite3Fts3HashClear(Fts3Hash *pH){
Fts3HashElem *elem; /* For looping over all elements of the table */
assert( pH!=0 );
@@ -154310,7 +176740,7 @@ static void fts3RemoveElementByHash(
}
}
-SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(
+SQLITE_PRIVATE Fts3HashElem *tdsqlite3Fts3HashFindElem(
const Fts3Hash *pH,
const void *pKey,
int nKey
@@ -154331,10 +176761,10 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(
** that matches pKey,nKey. Return the data for this element if it is
** found, or NULL if there is no match.
*/
-SQLITE_PRIVATE void *sqlite3Fts3HashFind(const Fts3Hash *pH, const void *pKey, int nKey){
+SQLITE_PRIVATE void *tdsqlite3Fts3HashFind(const Fts3Hash *pH, const void *pKey, int nKey){
Fts3HashElem *pElem; /* The element that matches key (if any) */
- pElem = sqlite3Fts3HashFindElem(pH, pKey, nKey);
+ pElem = tdsqlite3Fts3HashFindElem(pH, pKey, nKey);
return pElem ? pElem->data : 0;
}
@@ -154353,7 +176783,7 @@ SQLITE_PRIVATE void *sqlite3Fts3HashFind(const Fts3Hash *pH, const void *pKey, i
** If the "data" parameter to this function is NULL, then the
** element corresponding to "key" is removed from the hash table.
*/
-SQLITE_PRIVATE void *sqlite3Fts3HashInsert(
+SQLITE_PRIVATE void *tdsqlite3Fts3HashInsert(
Fts3Hash *pH, /* The hash table to insert into */
const void *pKey, /* The key */
int nKey, /* Number of bytes in the key */
@@ -154450,17 +176880,17 @@ SQLITE_PRIVATE void *sqlite3Fts3HashInsert(
/* #include "fts3_tokenizer.h" */
/*
-** Class derived from sqlite3_tokenizer
+** Class derived from tdsqlite3_tokenizer
*/
typedef struct porter_tokenizer {
- sqlite3_tokenizer base; /* Base class */
+ tdsqlite3_tokenizer base; /* Base class */
} porter_tokenizer;
/*
-** Class derived from sqlite3_tokenizer_cursor
+** Class derived from tdsqlite3_tokenizer_cursor
*/
typedef struct porter_tokenizer_cursor {
- sqlite3_tokenizer_cursor base;
+ tdsqlite3_tokenizer_cursor base;
const char *zInput; /* input we are tokenizing */
int nInput; /* size of the input */
int iOffset; /* current position in zInput */
@@ -154475,14 +176905,14 @@ typedef struct porter_tokenizer_cursor {
*/
static int porterCreate(
int argc, const char * const *argv,
- sqlite3_tokenizer **ppTokenizer
+ tdsqlite3_tokenizer **ppTokenizer
){
porter_tokenizer *t;
UNUSED_PARAMETER(argc);
UNUSED_PARAMETER(argv);
- t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t));
+ t = (porter_tokenizer *) tdsqlite3_malloc(sizeof(*t));
if( t==NULL ) return SQLITE_NOMEM;
memset(t, 0, sizeof(*t));
*ppTokenizer = &t->base;
@@ -154492,8 +176922,8 @@ static int porterCreate(
/*
** Destroy a tokenizer
*/
-static int porterDestroy(sqlite3_tokenizer *pTokenizer){
- sqlite3_free(pTokenizer);
+static int porterDestroy(tdsqlite3_tokenizer *pTokenizer){
+ tdsqlite3_free(pTokenizer);
return SQLITE_OK;
}
@@ -154504,15 +176934,15 @@ static int porterDestroy(sqlite3_tokenizer *pTokenizer){
** *ppCursor.
*/
static int porterOpen(
- sqlite3_tokenizer *pTokenizer, /* The tokenizer */
+ tdsqlite3_tokenizer *pTokenizer, /* The tokenizer */
const char *zInput, int nInput, /* String to be tokenized */
- sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
+ tdsqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
){
porter_tokenizer_cursor *c;
UNUSED_PARAMETER(pTokenizer);
- c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c));
+ c = (porter_tokenizer_cursor *) tdsqlite3_malloc(sizeof(*c));
if( c==NULL ) return SQLITE_NOMEM;
c->zInput = zInput;
@@ -154536,10 +176966,10 @@ static int porterOpen(
** Close a tokenization cursor previously opened by a call to
** porterOpen() above.
*/
-static int porterClose(sqlite3_tokenizer_cursor *pCursor){
+static int porterClose(tdsqlite3_tokenizer_cursor *pCursor){
porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor;
- sqlite3_free(c->zToken);
- sqlite3_free(c);
+ tdsqlite3_free(c->zToken);
+ tdsqlite3_free(c);
return SQLITE_OK;
}
/*
@@ -155009,7 +177439,7 @@ static const char porterIdChar[] = {
** have been opened by a prior call to porterOpen().
*/
static int porterNext(
- sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */
+ tdsqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */
const char **pzToken, /* OUT: *pzToken is the token text */
int *pnBytes, /* OUT: Number of bytes in token */
int *piStartOffset, /* OUT: Starting offset of token */
@@ -155038,7 +177468,7 @@ static int porterNext(
if( n>c->nAllocated ){
char *pNew;
c->nAllocated = n+20;
- pNew = sqlite3_realloc(c->zToken, c->nAllocated);
+ pNew = tdsqlite3_realloc(c->zToken, c->nAllocated);
if( !pNew ) return SQLITE_NOMEM;
c->zToken = pNew;
}
@@ -155056,7 +177486,7 @@ static int porterNext(
/*
** The set of routines that implement the porter-stemmer tokenizer
*/
-static const sqlite3_tokenizer_module porterTokenizerModule = {
+static const tdsqlite3_tokenizer_module porterTokenizerModule = {
0,
porterCreate,
porterDestroy,
@@ -155070,8 +177500,8 @@ static const sqlite3_tokenizer_module porterTokenizerModule = {
** Allocate a new porter tokenizer. Return a pointer to the new
** tokenizer in *ppModule
*/
-SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule(
- sqlite3_tokenizer_module const**ppModule
+SQLITE_PRIVATE void tdsqlite3Fts3PorterTokenizerModule(
+ tdsqlite3_tokenizer_module const**ppModule
){
*ppModule = &porterTokenizerModule;
}
@@ -155113,13 +177543,13 @@ SQLITE_PRIVATE void sqlite3Fts3PorterTokenizerModule(
/*
** Return true if the two-argument version of fts3_tokenizer()
-** has been activated via a prior call to sqlite3_db_config(db,
+** has been activated via a prior call to tdsqlite3_db_config(db,
** SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0);
*/
-static int fts3TokenizerEnabled(sqlite3_context *context){
- sqlite3 *db = sqlite3_context_db_handle(context);
+static int fts3TokenizerEnabled(tdsqlite3_context *context){
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
int isEnabled = 0;
- sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,-1,&isEnabled);
+ tdsqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,-1,&isEnabled);
return isEnabled;
}
@@ -155131,7 +177561,7 @@ static int fts3TokenizerEnabled(sqlite3_context *context){
** SELECT <function-name>(<key-name>, <pointer>);
**
** where <function-name> is the name passed as the second argument
-** to the sqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer').
+** to the tdsqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer').
**
** If the <pointer> argument is specified, it must be a blob value
** containing a pointer to be stored as the hash data corresponding
@@ -155144,9 +177574,9 @@ static int fts3TokenizerEnabled(sqlite3_context *context){
** to string <key-name> (after the hash-table is updated, if applicable).
*/
static void fts3TokenizerFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
Fts3Hash *pHash;
void *pPtr = 0;
@@ -155155,43 +177585,45 @@ static void fts3TokenizerFunc(
assert( argc==1 || argc==2 );
- pHash = (Fts3Hash *)sqlite3_user_data(context);
+ pHash = (Fts3Hash *)tdsqlite3_user_data(context);
- zName = sqlite3_value_text(argv[0]);
- nName = sqlite3_value_bytes(argv[0])+1;
+ zName = tdsqlite3_value_text(argv[0]);
+ nName = tdsqlite3_value_bytes(argv[0])+1;
if( argc==2 ){
- if( fts3TokenizerEnabled(context) ){
+ if( fts3TokenizerEnabled(context) || tdsqlite3_value_frombind(argv[1]) ){
void *pOld;
- int n = sqlite3_value_bytes(argv[1]);
+ int n = tdsqlite3_value_bytes(argv[1]);
if( zName==0 || n!=sizeof(pPtr) ){
- sqlite3_result_error(context, "argument type mismatch", -1);
+ tdsqlite3_result_error(context, "argument type mismatch", -1);
return;
}
- pPtr = *(void **)sqlite3_value_blob(argv[1]);
- pOld = sqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr);
+ pPtr = *(void **)tdsqlite3_value_blob(argv[1]);
+ pOld = tdsqlite3Fts3HashInsert(pHash, (void *)zName, nName, pPtr);
if( pOld==pPtr ){
- sqlite3_result_error(context, "out of memory", -1);
+ tdsqlite3_result_error(context, "out of memory", -1);
}
}else{
- sqlite3_result_error(context, "fts3tokenize disabled", -1);
+ tdsqlite3_result_error(context, "fts3tokenize disabled", -1);
return;
}
}else{
if( zName ){
- pPtr = sqlite3Fts3HashFind(pHash, zName, nName);
+ pPtr = tdsqlite3Fts3HashFind(pHash, zName, nName);
}
if( !pPtr ){
- char *zErr = sqlite3_mprintf("unknown tokenizer: %s", zName);
- sqlite3_result_error(context, zErr, -1);
- sqlite3_free(zErr);
+ char *zErr = tdsqlite3_mprintf("unknown tokenizer: %s", zName);
+ tdsqlite3_result_error(context, zErr, -1);
+ tdsqlite3_free(zErr);
return;
}
}
- sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT);
+ if( fts3TokenizerEnabled(context) || tdsqlite3_value_frombind(argv[0]) ){
+ tdsqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT);
+ }
}
-SQLITE_PRIVATE int sqlite3Fts3IsIdChar(char c){
+SQLITE_PRIVATE int tdsqlite3Fts3IsIdChar(char c){
static const char isFtsIdChar[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */
@@ -155205,7 +177637,7 @@ SQLITE_PRIVATE int sqlite3Fts3IsIdChar(char c){
return (c&0x80 || isFtsIdChar[(int)(c)]);
}
-SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *zStr, int *pn){
+SQLITE_PRIVATE const char *tdsqlite3Fts3NextToken(const char *zStr, int *pn){
const char *z1;
const char *z2 = 0;
@@ -155229,9 +177661,9 @@ SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *zStr, int *pn){
break;
default:
- if( sqlite3Fts3IsIdChar(*z1) ){
+ if( tdsqlite3Fts3IsIdChar(*z1) ){
z2 = &z1[1];
- while( sqlite3Fts3IsIdChar(*z2) ) z2++;
+ while( tdsqlite3Fts3IsIdChar(*z2) ) z2++;
}else{
z1++;
}
@@ -155242,10 +177674,10 @@ SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *zStr, int *pn){
return z1;
}
-SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(
+SQLITE_PRIVATE int tdsqlite3Fts3InitTokenizer(
Fts3Hash *pHash, /* Tokenizer hash table */
const char *zArg, /* Tokenizer name */
- sqlite3_tokenizer **ppTok, /* OUT: Tokenizer (if applicable) */
+ tdsqlite3_tokenizer **ppTok, /* OUT: Tokenizer (if applicable) */
char **pzErr /* OUT: Set to malloced error message */
){
int rc;
@@ -155253,53 +177685,53 @@ SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(
int n = 0;
char *zCopy;
char *zEnd; /* Pointer to nul-term of zCopy */
- sqlite3_tokenizer_module *m;
+ tdsqlite3_tokenizer_module *m;
- zCopy = sqlite3_mprintf("%s", zArg);
+ zCopy = tdsqlite3_mprintf("%s", zArg);
if( !zCopy ) return SQLITE_NOMEM;
zEnd = &zCopy[strlen(zCopy)];
- z = (char *)sqlite3Fts3NextToken(zCopy, &n);
+ z = (char *)tdsqlite3Fts3NextToken(zCopy, &n);
if( z==0 ){
assert( n==0 );
z = zCopy;
}
z[n] = '\0';
- sqlite3Fts3Dequote(z);
+ tdsqlite3Fts3Dequote(z);
- m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1);
+ m = (tdsqlite3_tokenizer_module *)tdsqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1);
if( !m ){
- sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", z);
+ tdsqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", z);
rc = SQLITE_ERROR;
}else{
char const **aArg = 0;
int iArg = 0;
z = &z[n+1];
- while( z<zEnd && (NULL!=(z = (char *)sqlite3Fts3NextToken(z, &n))) ){
- int nNew = sizeof(char *)*(iArg+1);
- char const **aNew = (const char **)sqlite3_realloc((void *)aArg, nNew);
+ while( z<zEnd && (NULL!=(z = (char *)tdsqlite3Fts3NextToken(z, &n))) ){
+ tdsqlite3_int64 nNew = sizeof(char *)*(iArg+1);
+ char const **aNew = (const char **)tdsqlite3_realloc64((void *)aArg, nNew);
if( !aNew ){
- sqlite3_free(zCopy);
- sqlite3_free((void *)aArg);
+ tdsqlite3_free(zCopy);
+ tdsqlite3_free((void *)aArg);
return SQLITE_NOMEM;
}
aArg = aNew;
aArg[iArg++] = z;
z[n] = '\0';
- sqlite3Fts3Dequote(z);
+ tdsqlite3Fts3Dequote(z);
z = &z[n+1];
}
rc = m->xCreate(iArg, aArg, ppTok);
assert( rc!=SQLITE_OK || *ppTok );
if( rc!=SQLITE_OK ){
- sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer");
+ tdsqlite3Fts3ErrMsg(pzErr, "unknown tokenizer");
}else{
(*ppTok)->pModule = m;
}
- sqlite3_free((void *)aArg);
+ tdsqlite3_free((void *)aArg);
}
- sqlite3_free(zCopy);
+ tdsqlite3_free(zCopy);
return rc;
}
@@ -155321,7 +177753,7 @@ SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(
** SELECT <function-name>(<key-name>, ..., <input-string>);
**
** where <function-name> is the name passed as the second argument
-** to the sqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer')
+** to the tdsqlite3Fts3InitHashTable() function (e.g. 'fts3_tokenizer')
** concatenated with the string '_test' (e.g. 'fts3_tokenizer_test').
**
** The return value is a string that may be interpreted as a Tcl
@@ -155339,14 +177771,14 @@ SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(
**
*/
static void testFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
Fts3Hash *pHash;
- sqlite3_tokenizer_module *p;
- sqlite3_tokenizer *pTokenizer = 0;
- sqlite3_tokenizer_cursor *pCsr = 0;
+ tdsqlite3_tokenizer_module *p;
+ tdsqlite3_tokenizer *pTokenizer = 0;
+ tdsqlite3_tokenizer_cursor *pCsr = 0;
const char *zErr = 0;
@@ -155367,22 +177799,22 @@ static void testFunc(
Tcl_Obj *pRet;
if( argc<2 ){
- sqlite3_result_error(context, "insufficient arguments", -1);
+ tdsqlite3_result_error(context, "insufficient arguments", -1);
return;
}
- nName = sqlite3_value_bytes(argv[0]);
- zName = (const char *)sqlite3_value_text(argv[0]);
- nInput = sqlite3_value_bytes(argv[argc-1]);
- zInput = (const char *)sqlite3_value_text(argv[argc-1]);
+ nName = tdsqlite3_value_bytes(argv[0]);
+ zName = (const char *)tdsqlite3_value_text(argv[0]);
+ nInput = tdsqlite3_value_bytes(argv[argc-1]);
+ zInput = (const char *)tdsqlite3_value_text(argv[argc-1]);
- pHash = (Fts3Hash *)sqlite3_user_data(context);
- p = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, zName, nName+1);
+ pHash = (Fts3Hash *)tdsqlite3_user_data(context);
+ p = (tdsqlite3_tokenizer_module *)tdsqlite3Fts3HashFind(pHash, zName, nName+1);
if( !p ){
- char *zErr2 = sqlite3_mprintf("unknown tokenizer: %s", zName);
- sqlite3_result_error(context, zErr2, -1);
- sqlite3_free(zErr2);
+ char *zErr2 = tdsqlite3_mprintf("unknown tokenizer: %s", zName);
+ tdsqlite3_result_error(context, zErr2, -1);
+ tdsqlite3_free(zErr2);
return;
}
@@ -155390,7 +177822,7 @@ static void testFunc(
Tcl_IncrRefCount(pRet);
for(i=1; i<argc-1; i++){
- azArg[i-1] = (const char *)sqlite3_value_text(argv[i]);
+ azArg[i-1] = (const char *)tdsqlite3_value_text(argv[i]);
}
if( SQLITE_OK!=p->xCreate(argc-2, azArg, &pTokenizer) ){
@@ -155398,7 +177830,7 @@ static void testFunc(
goto finish;
}
pTokenizer->pModule = p;
- if( sqlite3Fts3OpenTokenizer(pTokenizer, 0, zInput, nInput, &pCsr) ){
+ if( tdsqlite3Fts3OpenTokenizer(pTokenizer, 0, zInput, nInput, &pCsr) ){
zErr = "error in xOpen()";
goto finish;
}
@@ -155422,63 +177854,65 @@ static void testFunc(
finish:
if( zErr ){
- sqlite3_result_error(context, zErr, -1);
+ tdsqlite3_result_error(context, zErr, -1);
}else{
- sqlite3_result_text(context, Tcl_GetString(pRet), -1, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(context, Tcl_GetString(pRet), -1, SQLITE_TRANSIENT);
}
Tcl_DecrRefCount(pRet);
}
static
int registerTokenizer(
- sqlite3 *db,
+ tdsqlite3 *db,
char *zName,
- const sqlite3_tokenizer_module *p
+ const tdsqlite3_tokenizer_module *p
){
int rc;
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
const char zSql[] = "SELECT fts3_tokenizer(?, ?)";
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ rc = tdsqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
if( rc!=SQLITE_OK ){
return rc;
}
- sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
- sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC);
- sqlite3_step(pStmt);
+ tdsqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
+ tdsqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC);
+ tdsqlite3_step(pStmt);
- return sqlite3_finalize(pStmt);
+ return tdsqlite3_finalize(pStmt);
}
static
int queryTokenizer(
- sqlite3 *db,
+ tdsqlite3 *db,
char *zName,
- const sqlite3_tokenizer_module **pp
+ const tdsqlite3_tokenizer_module **pp
){
int rc;
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
const char zSql[] = "SELECT fts3_tokenizer(?)";
*pp = 0;
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ rc = tdsqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
if( rc!=SQLITE_OK ){
return rc;
}
- sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){
- memcpy((void *)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp));
+ tdsqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ if( tdsqlite3_column_type(pStmt, 0)==SQLITE_BLOB
+ && tdsqlite3_column_bytes(pStmt, 0)==sizeof(*pp)
+ ){
+ memcpy((void *)pp, tdsqlite3_column_blob(pStmt, 0), sizeof(*pp));
}
}
- return sqlite3_finalize(pStmt);
+ return tdsqlite3_finalize(pStmt);
}
-SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module const**ppModule);
+SQLITE_PRIVATE void tdsqlite3Fts3SimpleTokenizerModule(tdsqlite3_tokenizer_module const**ppModule);
/*
** Implementation of the scalar function fts3_tokenizer_internal_test().
@@ -155499,27 +177933,27 @@ SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(sqlite3_tokenizer_module co
**
*/
static void intTestFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
int rc;
- const sqlite3_tokenizer_module *p1;
- const sqlite3_tokenizer_module *p2;
- sqlite3 *db = (sqlite3 *)sqlite3_user_data(context);
+ const tdsqlite3_tokenizer_module *p1;
+ const tdsqlite3_tokenizer_module *p2;
+ tdsqlite3 *db = (tdsqlite3 *)tdsqlite3_user_data(context);
UNUSED_PARAMETER(argc);
UNUSED_PARAMETER(argv);
/* Test the query function */
- sqlite3Fts3SimpleTokenizerModule(&p1);
+ tdsqlite3Fts3SimpleTokenizerModule(&p1);
rc = queryTokenizer(db, "simple", &p2);
assert( rc==SQLITE_OK );
assert( p1==p2 );
rc = queryTokenizer(db, "nosuchtokenizer", &p2);
assert( rc==SQLITE_ERROR );
assert( p2==0 );
- assert( 0==strcmp(sqlite3_errmsg(db), "unknown tokenizer: nosuchtokenizer") );
+ assert( 0==strcmp(tdsqlite3_errmsg(db), "unknown tokenizer: nosuchtokenizer") );
/* Test the storage function */
if( fts3TokenizerEnabled(context) ){
@@ -155530,7 +177964,7 @@ static void intTestFunc(
assert( p2==p1 );
}
- sqlite3_result_text(context, "ok", -1, SQLITE_STATIC);
+ tdsqlite3_result_text(context, "ok", -1, SQLITE_STATIC);
}
#endif
@@ -155541,7 +177975,7 @@ static void intTestFunc(
** been initialized to use string keys, and to take a private copy
** of the key when a value is inserted. i.e. by a call similar to:
**
-** sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1);
+** tdsqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1);
**
** This function adds a scalar function (see header comment above
** fts3TokenizerFunc() in this file for details) and, if ENABLE_TABLE is
@@ -155552,44 +177986,44 @@ static void intTestFunc(
** The third argument to this function, zName, is used as the name
** of both the scalar and, if created, the virtual table.
*/
-SQLITE_PRIVATE int sqlite3Fts3InitHashTable(
- sqlite3 *db,
+SQLITE_PRIVATE int tdsqlite3Fts3InitHashTable(
+ tdsqlite3 *db,
Fts3Hash *pHash,
const char *zName
){
int rc = SQLITE_OK;
void *p = (void *)pHash;
- const int any = SQLITE_ANY;
+ const int any = SQLITE_UTF8|SQLITE_DIRECTONLY;
#ifdef SQLITE_TEST
char *zTest = 0;
char *zTest2 = 0;
void *pdb = (void *)db;
- zTest = sqlite3_mprintf("%s_test", zName);
- zTest2 = sqlite3_mprintf("%s_internal_test", zName);
+ zTest = tdsqlite3_mprintf("%s_test", zName);
+ zTest2 = tdsqlite3_mprintf("%s_internal_test", zName);
if( !zTest || !zTest2 ){
rc = SQLITE_NOMEM;
}
#endif
if( SQLITE_OK==rc ){
- rc = sqlite3_create_function(db, zName, 1, any, p, fts3TokenizerFunc, 0, 0);
+ rc = tdsqlite3_create_function(db, zName, 1, any, p, fts3TokenizerFunc, 0, 0);
}
if( SQLITE_OK==rc ){
- rc = sqlite3_create_function(db, zName, 2, any, p, fts3TokenizerFunc, 0, 0);
+ rc = tdsqlite3_create_function(db, zName, 2, any, p, fts3TokenizerFunc, 0, 0);
}
#ifdef SQLITE_TEST
if( SQLITE_OK==rc ){
- rc = sqlite3_create_function(db, zTest, -1, any, p, testFunc, 0, 0);
+ rc = tdsqlite3_create_function(db, zTest, -1, any, p, testFunc, 0, 0);
}
if( SQLITE_OK==rc ){
- rc = sqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0);
+ rc = tdsqlite3_create_function(db, zTest2, 0, any, pdb, intTestFunc, 0, 0);
}
#endif
#ifdef SQLITE_TEST
- sqlite3_free(zTest);
- sqlite3_free(zTest2);
+ tdsqlite3_free(zTest);
+ tdsqlite3_free(zTest2);
#endif
return rc;
@@ -155634,12 +178068,12 @@ SQLITE_PRIVATE int sqlite3Fts3InitHashTable(
/* #include "fts3_tokenizer.h" */
typedef struct simple_tokenizer {
- sqlite3_tokenizer base;
+ tdsqlite3_tokenizer base;
char delim[128]; /* flag ASCII delimiters */
} simple_tokenizer;
typedef struct simple_tokenizer_cursor {
- sqlite3_tokenizer_cursor base;
+ tdsqlite3_tokenizer_cursor base;
const char *pInput; /* input we are tokenizing */
int nBytes; /* size of the input */
int iOffset; /* current position in pInput */
@@ -155661,11 +178095,11 @@ static int fts3_isalnum(int x){
*/
static int simpleCreate(
int argc, const char * const *argv,
- sqlite3_tokenizer **ppTokenizer
+ tdsqlite3_tokenizer **ppTokenizer
){
simple_tokenizer *t;
- t = (simple_tokenizer *) sqlite3_malloc(sizeof(*t));
+ t = (simple_tokenizer *) tdsqlite3_malloc(sizeof(*t));
if( t==NULL ) return SQLITE_NOMEM;
memset(t, 0, sizeof(*t));
@@ -155680,7 +178114,7 @@ static int simpleCreate(
unsigned char ch = argv[1][i];
/* We explicitly don't support UTF-8 delimiters for now. */
if( ch>=0x80 ){
- sqlite3_free(t);
+ tdsqlite3_free(t);
return SQLITE_ERROR;
}
t->delim[ch] = 1;
@@ -155700,8 +178134,8 @@ static int simpleCreate(
/*
** Destroy a tokenizer
*/
-static int simpleDestroy(sqlite3_tokenizer *pTokenizer){
- sqlite3_free(pTokenizer);
+static int simpleDestroy(tdsqlite3_tokenizer *pTokenizer){
+ tdsqlite3_free(pTokenizer);
return SQLITE_OK;
}
@@ -155712,15 +178146,15 @@ static int simpleDestroy(sqlite3_tokenizer *pTokenizer){
** *ppCursor.
*/
static int simpleOpen(
- sqlite3_tokenizer *pTokenizer, /* The tokenizer */
+ tdsqlite3_tokenizer *pTokenizer, /* The tokenizer */
const char *pInput, int nBytes, /* String to be tokenized */
- sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
+ tdsqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
){
simple_tokenizer_cursor *c;
UNUSED_PARAMETER(pTokenizer);
- c = (simple_tokenizer_cursor *) sqlite3_malloc(sizeof(*c));
+ c = (simple_tokenizer_cursor *) tdsqlite3_malloc(sizeof(*c));
if( c==NULL ) return SQLITE_NOMEM;
c->pInput = pInput;
@@ -155744,10 +178178,10 @@ static int simpleOpen(
** Close a tokenization cursor previously opened by a call to
** simpleOpen() above.
*/
-static int simpleClose(sqlite3_tokenizer_cursor *pCursor){
+static int simpleClose(tdsqlite3_tokenizer_cursor *pCursor){
simple_tokenizer_cursor *c = (simple_tokenizer_cursor *) pCursor;
- sqlite3_free(c->pToken);
- sqlite3_free(c);
+ tdsqlite3_free(c->pToken);
+ tdsqlite3_free(c);
return SQLITE_OK;
}
@@ -155756,7 +178190,7 @@ static int simpleClose(sqlite3_tokenizer_cursor *pCursor){
** have been opened by a prior call to simpleOpen().
*/
static int simpleNext(
- sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */
+ tdsqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */
const char **ppToken, /* OUT: *ppToken is the token text */
int *pnBytes, /* OUT: Number of bytes in token */
int *piStartOffset, /* OUT: Starting offset of token */
@@ -155786,7 +178220,7 @@ static int simpleNext(
if( n>c->nTokenAllocated ){
char *pNew;
c->nTokenAllocated = n+20;
- pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated);
+ pNew = tdsqlite3_realloc(c->pToken, c->nTokenAllocated);
if( !pNew ) return SQLITE_NOMEM;
c->pToken = pNew;
}
@@ -155812,7 +178246,7 @@ static int simpleNext(
/*
** The set of routines that implement the simple tokenizer
*/
-static const sqlite3_tokenizer_module simpleTokenizerModule = {
+static const tdsqlite3_tokenizer_module simpleTokenizerModule = {
0,
simpleCreate,
simpleDestroy,
@@ -155826,8 +178260,8 @@ static const sqlite3_tokenizer_module simpleTokenizerModule = {
** Allocate a new simple tokenizer. Return a pointer to the new
** tokenizer in *ppModule
*/
-SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule(
- sqlite3_tokenizer_module const**ppModule
+SQLITE_PRIVATE void tdsqlite3Fts3SimpleTokenizerModule(
+ tdsqlite3_tokenizer_module const**ppModule
){
*ppModule = &simpleTokenizerModule;
}
@@ -155889,18 +178323,18 @@ typedef struct Fts3tokCursor Fts3tokCursor;
** Virtual table structure.
*/
struct Fts3tokTable {
- sqlite3_vtab base; /* Base class used by SQLite core */
- const sqlite3_tokenizer_module *pMod;
- sqlite3_tokenizer *pTok;
+ tdsqlite3_vtab base; /* Base class used by SQLite core */
+ const tdsqlite3_tokenizer_module *pMod;
+ tdsqlite3_tokenizer *pTok;
};
/*
** Virtual table cursor structure.
*/
struct Fts3tokCursor {
- sqlite3_vtab_cursor base; /* Base class used by SQLite core */
+ tdsqlite3_vtab_cursor base; /* Base class used by SQLite core */
char *zInput; /* Input string */
- sqlite3_tokenizer_cursor *pCsr; /* Cursor to iterate through zInput */
+ tdsqlite3_tokenizer_cursor *pCsr; /* Cursor to iterate through zInput */
int iRowid; /* Current 'rowid' value */
const char *zToken; /* Current 'token' value */
int nToken; /* Size of zToken in bytes */
@@ -155915,15 +178349,15 @@ struct Fts3tokCursor {
static int fts3tokQueryTokenizer(
Fts3Hash *pHash,
const char *zName,
- const sqlite3_tokenizer_module **pp,
+ const tdsqlite3_tokenizer_module **pp,
char **pzErr
){
- sqlite3_tokenizer_module *p;
+ tdsqlite3_tokenizer_module *p;
int nName = (int)strlen(zName);
- p = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash, zName, nName+1);
+ p = (tdsqlite3_tokenizer_module *)tdsqlite3Fts3HashFind(pHash, zName, nName+1);
if( !p ){
- sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", zName);
+ tdsqlite3Fts3ErrMsg(pzErr, "unknown tokenizer: %s", zName);
return SQLITE_ERROR;
}
@@ -155939,7 +178373,7 @@ static int fts3tokQueryTokenizer(
**
** If successful, output parameter *pazDequote is set to point at the
** array of dequoted strings and SQLITE_OK is returned. The caller is
-** responsible for eventually calling sqlite3_free() to free the array
+** responsible for eventually calling tdsqlite3_free() to free the array
** in this case. Or, if an error occurs, an SQLite error code is returned.
** The final value of *pazDequote is undefined in this case.
*/
@@ -155960,7 +178394,7 @@ static int fts3tokDequoteArray(
nByte += (int)(strlen(argv[i]) + 1);
}
- *pazDequote = azDequote = sqlite3_malloc(sizeof(char *)*argc + nByte);
+ *pazDequote = azDequote = tdsqlite3_malloc64(sizeof(char *)*argc + nByte);
if( azDequote==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -155969,7 +178403,7 @@ static int fts3tokDequoteArray(
int n = (int)strlen(argv[i]);
azDequote[i] = pSpace;
memcpy(pSpace, argv[i], n+1);
- sqlite3Fts3Dequote(pSpace);
+ tdsqlite3Fts3Dequote(pSpace);
pSpace += (n+1);
}
}
@@ -155994,21 +178428,21 @@ static int fts3tokDequoteArray(
** argv[3]: first argument (tokenizer name)
*/
static int fts3tokConnectMethod(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
void *pHash, /* Hash table of tokenizers */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
- char **pzErr /* OUT: sqlite3_malloc'd error message */
+ tdsqlite3_vtab **ppVtab, /* OUT: New tdsqlite3_vtab object */
+ char **pzErr /* OUT: tdsqlite3_malloc'd error message */
){
Fts3tokTable *pTab = 0;
- const sqlite3_tokenizer_module *pMod = 0;
- sqlite3_tokenizer *pTok = 0;
+ const tdsqlite3_tokenizer_module *pMod = 0;
+ tdsqlite3_tokenizer *pTok = 0;
int rc;
char **azDequote = 0;
int nDequote;
- rc = sqlite3_declare_vtab(db, FTS3_TOK_SCHEMA);
+ rc = tdsqlite3_declare_vtab(db, FTS3_TOK_SCHEMA);
if( rc!=SQLITE_OK ) return rc;
nDequote = argc-3;
@@ -156031,7 +178465,7 @@ static int fts3tokConnectMethod(
}
if( rc==SQLITE_OK ){
- pTab = (Fts3tokTable *)sqlite3_malloc(sizeof(Fts3tokTable));
+ pTab = (Fts3tokTable *)tdsqlite3_malloc(sizeof(Fts3tokTable));
if( pTab==0 ){
rc = SQLITE_NOMEM;
}
@@ -156048,7 +178482,7 @@ static int fts3tokConnectMethod(
}
}
- sqlite3_free(azDequote);
+ tdsqlite3_free(azDequote);
return rc;
}
@@ -156057,11 +178491,11 @@ static int fts3tokConnectMethod(
** These tables have no persistent representation of their own, so xDisconnect
** and xDestroy are identical operations.
*/
-static int fts3tokDisconnectMethod(sqlite3_vtab *pVtab){
+static int fts3tokDisconnectMethod(tdsqlite3_vtab *pVtab){
Fts3tokTable *pTab = (Fts3tokTable *)pVtab;
pTab->pMod->xDestroy(pTab->pTok);
- sqlite3_free(pTab);
+ tdsqlite3_free(pTab);
return SQLITE_OK;
}
@@ -156069,8 +178503,8 @@ static int fts3tokDisconnectMethod(sqlite3_vtab *pVtab){
** xBestIndex - Analyze a WHERE and ORDER BY clause.
*/
static int fts3tokBestIndexMethod(
- sqlite3_vtab *pVTab,
- sqlite3_index_info *pInfo
+ tdsqlite3_vtab *pVTab,
+ tdsqlite3_index_info *pInfo
){
int i;
UNUSED_PARAMETER(pVTab);
@@ -156097,17 +178531,17 @@ static int fts3tokBestIndexMethod(
/*
** xOpen - Open a cursor.
*/
-static int fts3tokOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
+static int fts3tokOpenMethod(tdsqlite3_vtab *pVTab, tdsqlite3_vtab_cursor **ppCsr){
Fts3tokCursor *pCsr;
UNUSED_PARAMETER(pVTab);
- pCsr = (Fts3tokCursor *)sqlite3_malloc(sizeof(Fts3tokCursor));
+ pCsr = (Fts3tokCursor *)tdsqlite3_malloc(sizeof(Fts3tokCursor));
if( pCsr==0 ){
return SQLITE_NOMEM;
}
memset(pCsr, 0, sizeof(Fts3tokCursor));
- *ppCsr = (sqlite3_vtab_cursor *)pCsr;
+ *ppCsr = (tdsqlite3_vtab_cursor *)pCsr;
return SQLITE_OK;
}
@@ -156121,7 +178555,7 @@ static void fts3tokResetCursor(Fts3tokCursor *pCsr){
pTab->pMod->xClose(pCsr->pCsr);
pCsr->pCsr = 0;
}
- sqlite3_free(pCsr->zInput);
+ tdsqlite3_free(pCsr->zInput);
pCsr->zInput = 0;
pCsr->zToken = 0;
pCsr->nToken = 0;
@@ -156134,18 +178568,18 @@ static void fts3tokResetCursor(Fts3tokCursor *pCsr){
/*
** xClose - Close a cursor.
*/
-static int fts3tokCloseMethod(sqlite3_vtab_cursor *pCursor){
+static int fts3tokCloseMethod(tdsqlite3_vtab_cursor *pCursor){
Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor;
fts3tokResetCursor(pCsr);
- sqlite3_free(pCsr);
+ tdsqlite3_free(pCsr);
return SQLITE_OK;
}
/*
** xNext - Advance the cursor to the next row, if any.
*/
-static int fts3tokNextMethod(sqlite3_vtab_cursor *pCursor){
+static int fts3tokNextMethod(tdsqlite3_vtab_cursor *pCursor){
Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor;
Fts3tokTable *pTab = (Fts3tokTable *)(pCursor->pVtab);
int rc; /* Return code */
@@ -156168,11 +178602,11 @@ static int fts3tokNextMethod(sqlite3_vtab_cursor *pCursor){
** xFilter - Initialize a cursor to point at the start of its data.
*/
static int fts3tokFilterMethod(
- sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
+ tdsqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
int idxNum, /* Strategy index */
const char *idxStr, /* Unused */
int nVal, /* Number of elements in apVal */
- sqlite3_value **apVal /* Arguments for the indexing scheme */
+ tdsqlite3_value **apVal /* Arguments for the indexing scheme */
){
int rc = SQLITE_ERROR;
Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor;
@@ -156182,9 +178616,9 @@ static int fts3tokFilterMethod(
fts3tokResetCursor(pCsr);
if( idxNum==1 ){
- const char *zByte = (const char *)sqlite3_value_text(apVal[0]);
- int nByte = sqlite3_value_bytes(apVal[0]);
- pCsr->zInput = sqlite3_malloc(nByte+1);
+ const char *zByte = (const char *)tdsqlite3_value_text(apVal[0]);
+ int nByte = tdsqlite3_value_bytes(apVal[0]);
+ pCsr->zInput = tdsqlite3_malloc64(nByte+1);
if( pCsr->zInput==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -156204,7 +178638,7 @@ static int fts3tokFilterMethod(
/*
** xEof - Return true if the cursor is at EOF, or false otherwise.
*/
-static int fts3tokEofMethod(sqlite3_vtab_cursor *pCursor){
+static int fts3tokEofMethod(tdsqlite3_vtab_cursor *pCursor){
Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor;
return (pCsr->zToken==0);
}
@@ -156213,8 +178647,8 @@ static int fts3tokEofMethod(sqlite3_vtab_cursor *pCursor){
** xColumn - Return a column value.
*/
static int fts3tokColumnMethod(
- sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
- sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
+ tdsqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
+ tdsqlite3_context *pCtx, /* Context for tdsqlite3_result_xxx() calls */
int iCol /* Index of column to read value from */
){
Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor;
@@ -156222,20 +178656,20 @@ static int fts3tokColumnMethod(
/* CREATE TABLE x(input, token, start, end, position) */
switch( iCol ){
case 0:
- sqlite3_result_text(pCtx, pCsr->zInput, -1, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(pCtx, pCsr->zInput, -1, SQLITE_TRANSIENT);
break;
case 1:
- sqlite3_result_text(pCtx, pCsr->zToken, pCsr->nToken, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(pCtx, pCsr->zToken, pCsr->nToken, SQLITE_TRANSIENT);
break;
case 2:
- sqlite3_result_int(pCtx, pCsr->iStart);
+ tdsqlite3_result_int(pCtx, pCsr->iStart);
break;
case 3:
- sqlite3_result_int(pCtx, pCsr->iEnd);
+ tdsqlite3_result_int(pCtx, pCsr->iEnd);
break;
default:
assert( iCol==4 );
- sqlite3_result_int(pCtx, pCsr->iPos);
+ tdsqlite3_result_int(pCtx, pCsr->iPos);
break;
}
return SQLITE_OK;
@@ -156245,20 +178679,20 @@ static int fts3tokColumnMethod(
** xRowid - Return the current rowid for the cursor.
*/
static int fts3tokRowidMethod(
- sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
+ tdsqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
sqlite_int64 *pRowid /* OUT: Rowid value */
){
Fts3tokCursor *pCsr = (Fts3tokCursor *)pCursor;
- *pRowid = (sqlite3_int64)pCsr->iRowid;
+ *pRowid = (tdsqlite3_int64)pCsr->iRowid;
return SQLITE_OK;
}
/*
** Register the fts3tok module with database connection db. Return SQLITE_OK
-** if successful or an error code if sqlite3_create_module() fails.
+** if successful or an error code if tdsqlite3_create_module() fails.
*/
-SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){
- static const sqlite3_module fts3tok_module = {
+SQLITE_PRIVATE int tdsqlite3Fts3InitTok(tdsqlite3 *db, Fts3Hash *pHash){
+ static const tdsqlite3_module fts3tok_module = {
0, /* iVersion */
fts3tokConnectMethod, /* xCreate */
fts3tokConnectMethod, /* xConnect */
@@ -156281,11 +178715,12 @@ SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){
0, /* xRename */
0, /* xSavepoint */
0, /* xRelease */
- 0 /* xRollbackTo */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
};
int rc; /* Return code */
- rc = sqlite3_create_module(db, "fts3tokenize", &fts3tok_module, (void*)pHash);
+ rc = tdsqlite3_create_module(db, "fts3tokenize", &fts3tok_module, (void*)pHash);
return rc;
}
@@ -156318,7 +178753,7 @@ SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){
/* #include <string.h> */
/* #include <assert.h> */
/* #include <stdlib.h> */
-
+/* #include <stdio.h> */
#define FTS_MAX_APPENDABLE_HEIGHT 16
@@ -156362,7 +178797,7 @@ int test_fts3_node_chunk_threshold = (4*1024)*4;
#endif
/*
-** The two values that may be meaningfully bound to the :1 parameter in
+** The values that may be meaningfully bound to the :1 parameter in
** statements SQL_REPLACE_STAT and SQL_SELECT_STAT.
*/
#define FTS_STAT_DOCTOTAL 0
@@ -156370,14 +178805,14 @@ int test_fts3_node_chunk_threshold = (4*1024)*4;
#define FTS_STAT_AUTOINCRMERGE 2
/*
-** If FTS_LOG_MERGES is defined, call sqlite3_log() to report each automatic
+** If FTS_LOG_MERGES is defined, call tdsqlite3_log() to report each automatic
** and incremental merge operation that takes place. This is used for
** debugging FTS only, it should not usually be turned on in production
** systems.
*/
#ifdef FTS3_LOG_MERGES
-static void fts3LogMerge(int nMerge, sqlite3_int64 iAbsLevel){
- sqlite3_log(SQLITE_OK, "%d-way merge from level %d", nMerge, (int)iAbsLevel);
+static void fts3LogMerge(int nMerge, tdsqlite3_int64 iAbsLevel){
+ tdsqlite3_log(SQLITE_OK, "%d-way merge from level %d", nMerge, (int)iAbsLevel);
}
#else
#define fts3LogMerge(x, y)
@@ -156396,9 +178831,9 @@ struct PendingList {
int nData;
char *aData;
int nSpace;
- sqlite3_int64 iLastDocid;
- sqlite3_int64 iLastCol;
- sqlite3_int64 iLastPos;
+ tdsqlite3_int64 iLastDocid;
+ tdsqlite3_int64 iLastCol;
+ tdsqlite3_int64 iLastPos;
};
@@ -156419,9 +178854,9 @@ struct Fts3DeferredToken {
** of type Fts3SegReader* are also used by code in fts3.c to iterate through
** terms when querying the full-text index. See functions:
**
-** sqlite3Fts3SegReaderNew()
-** sqlite3Fts3SegReaderFree()
-** sqlite3Fts3SegReaderIterate()
+** tdsqlite3Fts3SegReaderNew()
+** tdsqlite3Fts3SegReaderFree()
+** tdsqlite3Fts3SegReaderIterate()
**
** Methods used to manipulate Fts3SegReader structures:
**
@@ -156434,15 +178869,15 @@ struct Fts3SegReader {
u8 bLookup; /* True for a lookup only */
u8 rootOnly; /* True for a root-only reader */
- sqlite3_int64 iStartBlock; /* Rowid of first leaf block to traverse */
- sqlite3_int64 iLeafEndBlock; /* Rowid of final leaf block to traverse */
- sqlite3_int64 iEndBlock; /* Rowid of final block in segment (or 0) */
- sqlite3_int64 iCurrentBlock; /* Current leaf block (or 0) */
+ tdsqlite3_int64 iStartBlock; /* Rowid of first leaf block to traverse */
+ tdsqlite3_int64 iLeafEndBlock; /* Rowid of final leaf block to traverse */
+ tdsqlite3_int64 iEndBlock; /* Rowid of final block in segment (or 0) */
+ tdsqlite3_int64 iCurrentBlock; /* Current leaf block (or 0) */
char *aNode; /* Pointer to node data (or NULL) */
int nNode; /* Size of buffer at aNode (or 0) */
int nPopulate; /* If >0, bytes of buffer aNode[] loaded */
- sqlite3_blob *pBlob; /* If not NULL, blob handle to read node */
+ tdsqlite3_blob *pBlob; /* If not NULL, blob handle to read node */
Fts3HashElem **ppNextElem;
@@ -156462,7 +178897,7 @@ struct Fts3SegReader {
*/
char *pOffsetList;
int nOffsetList; /* For descending pending seg-readers only */
- sqlite3_int64 iDocid;
+ tdsqlite3_int64 iDocid;
};
#define fts3SegReaderIsPending(p) ((p)->ppNextElem!=0)
@@ -156479,8 +178914,8 @@ struct Fts3SegReader {
*/
struct SegmentWriter {
SegmentNode *pTree; /* Pointer to interior tree structure */
- sqlite3_int64 iFirst; /* First slot in %_segments written */
- sqlite3_int64 iFree; /* Next free slot in %_segments */
+ tdsqlite3_int64 iFirst; /* First slot in %_segments written */
+ tdsqlite3_int64 iFree; /* Next free slot in %_segments */
char *zTerm; /* Pointer to previous term buffer */
int nTerm; /* Number of bytes in zTerm */
int nMalloc; /* Size of malloc'd buffer at zMalloc */
@@ -156582,8 +179017,8 @@ struct SegmentNode {
static int fts3SqlStmt(
Fts3Table *p, /* Virtual table handle */
int eStmt, /* One of the SQL_XXX constants above */
- sqlite3_stmt **pp, /* OUT: Statement handle */
- sqlite3_value **apVal /* Values to bind to statement */
+ tdsqlite3_stmt **pp, /* OUT: Statement handle */
+ tdsqlite3_value **apVal /* Values to bind to statement */
){
const char *azSql[] = {
/* 0 */ "DELETE FROM %Q.'%q_content' WHERE rowid = ?",
@@ -156630,11 +179065,11 @@ static int fts3SqlStmt(
** returns zero rows. */
/* 28 */ "SELECT level, count(*) AS cnt FROM %Q.'%q_segdir' "
" GROUP BY level HAVING cnt>=?"
- " ORDER BY (level %% 1024) ASC LIMIT 1",
+ " ORDER BY (level %% 1024) ASC, 2 DESC LIMIT 1",
/* Estimate the upper limit on the number of leaf nodes in a new segment
** created by merging the oldest :2 segments from absolute level :1. See
-** function sqlite3Fts3Incrmerge() for details. */
+** function tdsqlite3Fts3Incrmerge() for details. */
/* 29 */ "SELECT 2 * total(1 + leaves_end_block - start_block) "
" FROM %Q.'%q_segdir' WHERE level = ? AND idx < ?",
@@ -156684,35 +179119,37 @@ static int fts3SqlStmt(
};
int rc = SQLITE_OK;
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
assert( SizeofArray(azSql)==SizeofArray(p->aStmt) );
assert( eStmt<SizeofArray(azSql) && eStmt>=0 );
pStmt = p->aStmt[eStmt];
if( !pStmt ){
+ int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB;
char *zSql;
if( eStmt==SQL_CONTENT_INSERT ){
- zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist);
+ zSql = tdsqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist);
}else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){
- zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist);
+ f &= ~SQLITE_PREPARE_NO_VTAB;
+ zSql = tdsqlite3_mprintf(azSql[eStmt], p->zReadExprlist);
}else{
- zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName);
+ zSql = tdsqlite3_mprintf(azSql[eStmt], p->zDb, p->zName);
}
if( !zSql ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, NULL);
- sqlite3_free(zSql);
+ rc = tdsqlite3_prepare_v3(p->db, zSql, -1, f, &pStmt, NULL);
+ tdsqlite3_free(zSql);
assert( rc==SQLITE_OK || pStmt==0 );
p->aStmt[eStmt] = pStmt;
}
}
if( apVal ){
int i;
- int nParam = sqlite3_bind_parameter_count(pStmt);
+ int nParam = tdsqlite3_bind_parameter_count(pStmt);
for(i=0; rc==SQLITE_OK && i<nParam; i++){
- rc = sqlite3_bind_value(pStmt, i+1, apVal[i]);
+ rc = tdsqlite3_bind_value(pStmt, i+1, apVal[i]);
}
}
*pp = pStmt;
@@ -156722,18 +179159,18 @@ static int fts3SqlStmt(
static int fts3SelectDocsize(
Fts3Table *pTab, /* FTS3 table handle */
- sqlite3_int64 iDocid, /* Docid to bind for SQL_SELECT_DOCSIZE */
- sqlite3_stmt **ppStmt /* OUT: Statement handle */
+ tdsqlite3_int64 iDocid, /* Docid to bind for SQL_SELECT_DOCSIZE */
+ tdsqlite3_stmt **ppStmt /* OUT: Statement handle */
){
- sqlite3_stmt *pStmt = 0; /* Statement requested from fts3SqlStmt() */
+ tdsqlite3_stmt *pStmt = 0; /* Statement requested from fts3SqlStmt() */
int rc; /* Return code */
rc = fts3SqlStmt(pTab, SQL_SELECT_DOCSIZE, &pStmt, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pStmt, 1, iDocid);
- rc = sqlite3_step(pStmt);
- if( rc!=SQLITE_ROW || sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB ){
- rc = sqlite3_reset(pStmt);
+ tdsqlite3_bind_int64(pStmt, 1, iDocid);
+ rc = tdsqlite3_step(pStmt);
+ if( rc!=SQLITE_ROW || tdsqlite3_column_type(pStmt, 0)!=SQLITE_BLOB ){
+ rc = tdsqlite3_reset(pStmt);
if( rc==SQLITE_OK ) rc = FTS_CORRUPT_VTAB;
pStmt = 0;
}else{
@@ -156745,19 +179182,19 @@ static int fts3SelectDocsize(
return rc;
}
-SQLITE_PRIVATE int sqlite3Fts3SelectDoctotal(
+SQLITE_PRIVATE int tdsqlite3Fts3SelectDoctotal(
Fts3Table *pTab, /* Fts3 table handle */
- sqlite3_stmt **ppStmt /* OUT: Statement handle */
+ tdsqlite3_stmt **ppStmt /* OUT: Statement handle */
){
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
int rc;
rc = fts3SqlStmt(pTab, SQL_SELECT_STAT, &pStmt, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL);
- if( sqlite3_step(pStmt)!=SQLITE_ROW
- || sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB
+ tdsqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL);
+ if( tdsqlite3_step(pStmt)!=SQLITE_ROW
+ || tdsqlite3_column_type(pStmt, 0)!=SQLITE_BLOB
){
- rc = sqlite3_reset(pStmt);
+ rc = tdsqlite3_reset(pStmt);
if( rc==SQLITE_OK ) rc = FTS_CORRUPT_VTAB;
pStmt = 0;
}
@@ -156766,10 +179203,10 @@ SQLITE_PRIVATE int sqlite3Fts3SelectDoctotal(
return rc;
}
-SQLITE_PRIVATE int sqlite3Fts3SelectDocsize(
+SQLITE_PRIVATE int tdsqlite3Fts3SelectDocsize(
Fts3Table *pTab, /* Fts3 table handle */
- sqlite3_int64 iDocid, /* Docid to read size data for */
- sqlite3_stmt **ppStmt /* OUT: Statement handle */
+ tdsqlite3_int64 iDocid, /* Docid to read size data for */
+ tdsqlite3_stmt **ppStmt /* OUT: Statement handle */
){
return fts3SelectDocsize(pTab, iDocid, ppStmt);
}
@@ -156786,15 +179223,15 @@ static void fts3SqlExec(
int *pRC, /* Result code */
Fts3Table *p, /* The FTS3 table */
int eStmt, /* Index of statement to evaluate */
- sqlite3_value **apVal /* Parameters to bind */
+ tdsqlite3_value **apVal /* Parameters to bind */
){
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
int rc;
if( *pRC ) return;
rc = fts3SqlStmt(p, eStmt, &pStmt, apVal);
if( rc==SQLITE_OK ){
- sqlite3_step(pStmt);
- rc = sqlite3_reset(pStmt);
+ tdsqlite3_step(pStmt);
+ rc = tdsqlite3_reset(pStmt);
}
*pRC = rc;
}
@@ -156818,12 +179255,12 @@ static int fts3Writelock(Fts3Table *p){
int rc = SQLITE_OK;
if( p->nPendingData==0 ){
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_LEVEL, &pStmt, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_null(pStmt, 1);
- sqlite3_step(pStmt);
- rc = sqlite3_reset(pStmt);
+ tdsqlite3_bind_null(pStmt, 1);
+ tdsqlite3_step(pStmt);
+ rc = tdsqlite3_reset(pStmt);
}
}
@@ -156853,18 +179290,18 @@ static int fts3Writelock(Fts3Table *p){
** absolute levels that corresponds to language-id iLangid and index
** iIndex starts at absolute level ((iLangid * (nPrefix+1) + iIndex) * 1024).
*/
-static sqlite3_int64 getAbsoluteLevel(
+static tdsqlite3_int64 getAbsoluteLevel(
Fts3Table *p, /* FTS3 table handle */
int iLangid, /* Language id */
int iIndex, /* Index in p->aIndex[] */
int iLevel /* Level of segments */
){
- sqlite3_int64 iBase; /* First absolute level for iLangid/iIndex */
- assert( iLangid>=0 );
+ tdsqlite3_int64 iBase; /* First absolute level for iLangid/iIndex */
+ assert_fts3_nc( iLangid>=0 );
assert( p->nIndex>0 );
assert( iIndex>=0 && iIndex<p->nIndex );
- iBase = ((sqlite3_int64)iLangid * p->nIndex + iIndex) * FTS3_SEGDIR_MAXLEVEL;
+ iBase = ((tdsqlite3_int64)iLangid * p->nIndex + iIndex) * FTS3_SEGDIR_MAXLEVEL;
return iBase + iLevel;
}
@@ -156885,15 +179322,15 @@ static sqlite3_int64 getAbsoluteLevel(
** 3: end_block
** 4: root
*/
-SQLITE_PRIVATE int sqlite3Fts3AllSegdirs(
+SQLITE_PRIVATE int tdsqlite3Fts3AllSegdirs(
Fts3Table *p, /* FTS3 table */
int iLangid, /* Language being queried */
int iIndex, /* Index for p->aIndex[] */
int iLevel, /* Level to select (relative level) */
- sqlite3_stmt **ppStmt /* OUT: Compiled statement */
+ tdsqlite3_stmt **ppStmt /* OUT: Compiled statement */
){
int rc;
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
assert( iLevel==FTS3_SEGCURSOR_ALL || iLevel>=0 );
assert( iLevel<FTS3_SEGDIR_MAXLEVEL );
@@ -156903,8 +179340,8 @@ SQLITE_PRIVATE int sqlite3Fts3AllSegdirs(
/* "SELECT * FROM %_segdir WHERE level BETWEEN ? AND ? ORDER BY ..." */
rc = fts3SqlStmt(p, SQL_SELECT_LEVEL_RANGE, &pStmt, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0));
- sqlite3_bind_int64(pStmt, 2,
+ tdsqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0));
+ tdsqlite3_bind_int64(pStmt, 2,
getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1)
);
}
@@ -156912,7 +179349,7 @@ SQLITE_PRIVATE int sqlite3Fts3AllSegdirs(
/* "SELECT * FROM %_segdir WHERE level = ? ORDER BY ..." */
rc = fts3SqlStmt(p, SQL_SELECT_LEVEL, &pStmt, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex,iLevel));
+ tdsqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex,iLevel));
}
}
*ppStmt = pStmt;
@@ -156934,13 +179371,13 @@ SQLITE_PRIVATE int sqlite3Fts3AllSegdirs(
*/
static int fts3PendingListAppendVarint(
PendingList **pp, /* IN/OUT: Pointer to PendingList struct */
- sqlite3_int64 i /* Value to append to data */
+ tdsqlite3_int64 i /* Value to append to data */
){
PendingList *p = *pp;
/* Allocate or grow the PendingList as required. */
if( !p ){
- p = sqlite3_malloc(sizeof(*p) + 100);
+ p = tdsqlite3_malloc(sizeof(*p) + 100);
if( !p ){
return SQLITE_NOMEM;
}
@@ -156950,9 +179387,9 @@ static int fts3PendingListAppendVarint(
}
else if( p->nData+FTS3_VARINT_MAX+1>p->nSpace ){
int nNew = p->nSpace * 2;
- p = sqlite3_realloc(p, sizeof(*p) + nNew);
+ p = tdsqlite3_realloc(p, sizeof(*p) + nNew);
if( !p ){
- sqlite3_free(*pp);
+ tdsqlite3_free(*pp);
*pp = 0;
return SQLITE_NOMEM;
}
@@ -156961,7 +179398,7 @@ static int fts3PendingListAppendVarint(
}
/* Append the new serialized varint to the end of the list. */
- p->nData += sqlite3Fts3PutVarint(&p->aData[p->nData], i);
+ p->nData += tdsqlite3Fts3PutVarint(&p->aData[p->nData], i);
p->aData[p->nData] = '\0';
*pp = p;
return SQLITE_OK;
@@ -156969,7 +179406,7 @@ static int fts3PendingListAppendVarint(
/*
** Add a docid/column/position entry to a PendingList structure. Non-zero
-** is returned if the structure is sqlite3_realloced as part of adding
+** is returned if the structure is tdsqlite3_realloced as part of adding
** the entry. Otherwise, zero.
**
** If an OOM error occurs, *pRc is set to SQLITE_NOMEM before returning.
@@ -156978,9 +179415,9 @@ static int fts3PendingListAppendVarint(
*/
static int fts3PendingListAppend(
PendingList **pp, /* IN/OUT: PendingList structure */
- sqlite3_int64 iDocid, /* Docid for entry to add */
- sqlite3_int64 iCol, /* Column for entry to add */
- sqlite3_int64 iPos, /* Position of term for entry to add */
+ tdsqlite3_int64 iDocid, /* Docid for entry to add */
+ tdsqlite3_int64 iCol, /* Column for entry to add */
+ tdsqlite3_int64 iPos, /* Position of term for entry to add */
int *pRc /* OUT: Return code */
){
PendingList *p = *pp;
@@ -156989,7 +179426,7 @@ static int fts3PendingListAppend(
assert( !p || p->iLastDocid<=iDocid );
if( !p || p->iLastDocid!=iDocid ){
- sqlite3_int64 iDelta = iDocid - (p ? p->iLastDocid : 0);
+ u64 iDelta = (u64)iDocid - (u64)(p ? p->iLastDocid : 0);
if( p ){
assert( p->nData<p->nSpace );
assert( p->aData[p->nData]==0 );
@@ -157032,7 +179469,7 @@ static int fts3PendingListAppend(
** Free a PendingList object allocated by fts3PendingListAppend().
*/
static void fts3PendingListDelete(PendingList *pList){
- sqlite3_free(pList);
+ tdsqlite3_free(pList);
}
/*
@@ -157059,7 +179496,7 @@ static int fts3PendingTermsAddOne(
** happen if there was no previous entry for this token.
*/
assert( 0==fts3HashFind(pHash, zToken, nToken) );
- sqlite3_free(pList);
+ tdsqlite3_free(pList);
rc = SQLITE_NOMEM;
}
}
@@ -157092,10 +179529,10 @@ static int fts3PendingTermsAdd(
char const *zToken;
int nToken = 0;
- sqlite3_tokenizer *pTokenizer = p->pTokenizer;
- sqlite3_tokenizer_module const *pModule = pTokenizer->pModule;
- sqlite3_tokenizer_cursor *pCsr;
- int (*xNext)(sqlite3_tokenizer_cursor *pCursor,
+ tdsqlite3_tokenizer *pTokenizer = p->pTokenizer;
+ tdsqlite3_tokenizer_module const *pModule = pTokenizer->pModule;
+ tdsqlite3_tokenizer_cursor *pCsr;
+ int (*xNext)(tdsqlite3_tokenizer_cursor *pCursor,
const char**,int*,int*,int*,int*);
assert( pTokenizer && pModule );
@@ -157108,7 +179545,7 @@ static int fts3PendingTermsAdd(
return SQLITE_OK;
}
- rc = sqlite3Fts3OpenTokenizer(pTokenizer, iLangid, zText, -1, &pCsr);
+ rc = tdsqlite3Fts3OpenTokenizer(pTokenizer, iLangid, zText, -1, &pCsr);
if( rc!=SQLITE_OK ){
return rc;
}
@@ -157174,7 +179611,7 @@ static int fts3PendingTermsDocid(
|| p->iPrevLangid!=iLangid
|| p->nPendingData>p->nMaxPendingData
){
- int rc = sqlite3Fts3PendingTermsFlush(p);
+ int rc = tdsqlite3Fts3PendingTermsFlush(p);
if( rc!=SQLITE_OK ) return rc;
}
p->iPrevDocid = iDocid;
@@ -157186,7 +179623,7 @@ static int fts3PendingTermsDocid(
/*
** Discard the contents of the pending-terms hash tables.
*/
-SQLITE_PRIVATE void sqlite3Fts3PendingTermsClear(Fts3Table *p){
+SQLITE_PRIVATE void tdsqlite3Fts3PendingTermsClear(Fts3Table *p){
int i;
for(i=0; i<p->nIndex; i++){
Fts3HashElem *pElem;
@@ -157211,19 +179648,19 @@ SQLITE_PRIVATE void sqlite3Fts3PendingTermsClear(Fts3Table *p){
static int fts3InsertTerms(
Fts3Table *p,
int iLangid,
- sqlite3_value **apVal,
+ tdsqlite3_value **apVal,
u32 *aSz
){
int i; /* Iterator variable */
for(i=2; i<p->nColumn+2; i++){
int iCol = i-2;
if( p->abNotindexed[iCol]==0 ){
- const char *zText = (const char *)sqlite3_value_text(apVal[i]);
+ const char *zText = (const char *)tdsqlite3_value_text(apVal[i]);
int rc = fts3PendingTermsAdd(p, iLangid, zText, iCol, &aSz[iCol]);
if( rc!=SQLITE_OK ){
return rc;
}
- aSz[p->nColumn] += sqlite3_value_bytes(apVal[i]);
+ aSz[p->nColumn] += tdsqlite3_value_bytes(apVal[i]);
}
}
return SQLITE_OK;
@@ -157245,21 +179682,21 @@ static int fts3InsertTerms(
*/
static int fts3InsertData(
Fts3Table *p, /* Full-text table */
- sqlite3_value **apVal, /* Array of values to insert */
- sqlite3_int64 *piDocid /* OUT: Docid for row just inserted */
+ tdsqlite3_value **apVal, /* Array of values to insert */
+ tdsqlite3_int64 *piDocid /* OUT: Docid for row just inserted */
){
int rc; /* Return code */
- sqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */
+ tdsqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */
if( p->zContentTbl ){
- sqlite3_value *pRowid = apVal[p->nColumn+3];
- if( sqlite3_value_type(pRowid)==SQLITE_NULL ){
+ tdsqlite3_value *pRowid = apVal[p->nColumn+3];
+ if( tdsqlite3_value_type(pRowid)==SQLITE_NULL ){
pRowid = apVal[1];
}
- if( sqlite3_value_type(pRowid)!=SQLITE_INTEGER ){
+ if( tdsqlite3_value_type(pRowid)!=SQLITE_INTEGER ){
return SQLITE_CONSTRAINT;
}
- *piDocid = sqlite3_value_int64(pRowid);
+ *piDocid = tdsqlite3_value_int64(pRowid);
return SQLITE_OK;
}
@@ -157273,9 +179710,9 @@ static int fts3InsertData(
*/
rc = fts3SqlStmt(p, SQL_CONTENT_INSERT, &pContentInsert, &apVal[1]);
if( rc==SQLITE_OK && p->zLanguageid ){
- rc = sqlite3_bind_int(
+ rc = tdsqlite3_bind_int(
pContentInsert, p->nColumn+2,
- sqlite3_value_int(apVal[p->nColumn+4])
+ tdsqlite3_value_int(apVal[p->nColumn+4])
);
}
if( rc!=SQLITE_OK ) return rc;
@@ -157290,24 +179727,24 @@ static int fts3InsertData(
** In FTS3, this is an error. It is an error to specify non-NULL values
** for both docid and some other rowid alias.
*/
- if( SQLITE_NULL!=sqlite3_value_type(apVal[3+p->nColumn]) ){
- if( SQLITE_NULL==sqlite3_value_type(apVal[0])
- && SQLITE_NULL!=sqlite3_value_type(apVal[1])
+ if( SQLITE_NULL!=tdsqlite3_value_type(apVal[3+p->nColumn]) ){
+ if( SQLITE_NULL==tdsqlite3_value_type(apVal[0])
+ && SQLITE_NULL!=tdsqlite3_value_type(apVal[1])
){
/* A rowid/docid conflict. */
return SQLITE_ERROR;
}
- rc = sqlite3_bind_value(pContentInsert, 1, apVal[3+p->nColumn]);
+ rc = tdsqlite3_bind_value(pContentInsert, 1, apVal[3+p->nColumn]);
if( rc!=SQLITE_OK ) return rc;
}
/* Execute the statement to insert the record. Set *piDocid to the
** new docid value.
*/
- sqlite3_step(pContentInsert);
- rc = sqlite3_reset(pContentInsert);
+ tdsqlite3_step(pContentInsert);
+ rc = tdsqlite3_reset(pContentInsert);
- *piDocid = sqlite3_last_insert_rowid(p->db);
+ *piDocid = tdsqlite3_last_insert_rowid(p->db);
return rc;
}
@@ -157321,7 +179758,7 @@ static int fts3DeleteAll(Fts3Table *p, int bContent){
int rc = SQLITE_OK; /* Return code */
/* Discard the contents of the pending-terms hash table. */
- sqlite3Fts3PendingTermsClear(p);
+ tdsqlite3Fts3PendingTermsClear(p);
/* Delete everything from the shadow tables. Except, leave %_content as
** is if bContent is false. */
@@ -157341,9 +179778,9 @@ static int fts3DeleteAll(Fts3Table *p, int bContent){
/*
**
*/
-static int langidFromSelect(Fts3Table *p, sqlite3_stmt *pSelect){
+static int langidFromSelect(Fts3Table *p, tdsqlite3_stmt *pSelect){
int iLangid = 0;
- if( p->zLanguageid ) iLangid = sqlite3_column_int(pSelect, p->nColumn+1);
+ if( p->zLanguageid ) iLangid = tdsqlite3_column_int(pSelect, p->nColumn+1);
return iLangid;
}
@@ -157355,40 +179792,40 @@ static int langidFromSelect(Fts3Table *p, sqlite3_stmt *pSelect){
static void fts3DeleteTerms(
int *pRC, /* Result code */
Fts3Table *p, /* The FTS table to delete from */
- sqlite3_value *pRowid, /* The docid to be deleted */
+ tdsqlite3_value *pRowid, /* The docid to be deleted */
u32 *aSz, /* Sizes of deleted document written here */
int *pbFound /* OUT: Set to true if row really does exist */
){
int rc;
- sqlite3_stmt *pSelect;
+ tdsqlite3_stmt *pSelect;
assert( *pbFound==0 );
if( *pRC ) return;
rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pSelect, &pRowid);
if( rc==SQLITE_OK ){
- if( SQLITE_ROW==sqlite3_step(pSelect) ){
+ if( SQLITE_ROW==tdsqlite3_step(pSelect) ){
int i;
int iLangid = langidFromSelect(p, pSelect);
- i64 iDocid = sqlite3_column_int64(pSelect, 0);
+ i64 iDocid = tdsqlite3_column_int64(pSelect, 0);
rc = fts3PendingTermsDocid(p, 1, iLangid, iDocid);
for(i=1; rc==SQLITE_OK && i<=p->nColumn; i++){
int iCol = i-1;
if( p->abNotindexed[iCol]==0 ){
- const char *zText = (const char *)sqlite3_column_text(pSelect, i);
+ const char *zText = (const char *)tdsqlite3_column_text(pSelect, i);
rc = fts3PendingTermsAdd(p, iLangid, zText, -1, &aSz[iCol]);
- aSz[p->nColumn] += sqlite3_column_bytes(pSelect, i);
+ aSz[p->nColumn] += tdsqlite3_column_bytes(pSelect, i);
}
}
if( rc!=SQLITE_OK ){
- sqlite3_reset(pSelect);
+ tdsqlite3_reset(pSelect);
*pRC = rc;
return;
}
*pbFound = 1;
}
- rc = sqlite3_reset(pSelect);
+ rc = tdsqlite3_reset(pSelect);
}else{
- sqlite3_reset(pSelect);
+ tdsqlite3_reset(pSelect);
}
*pRC = rc;
}
@@ -157422,7 +179859,7 @@ static int fts3AllocateSegdirIdx(
int *piIdx
){
int rc; /* Return Code */
- sqlite3_stmt *pNextIdx; /* Query for next idx at level iLevel */
+ tdsqlite3_stmt *pNextIdx; /* Query for next idx at level iLevel */
int iNext = 0; /* Result of query pNextIdx */
assert( iLangid>=0 );
@@ -157431,13 +179868,13 @@ static int fts3AllocateSegdirIdx(
/* Set variable iNext to the next available segdir index at level iLevel. */
rc = fts3SqlStmt(p, SQL_NEXT_SEGMENT_INDEX, &pNextIdx, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(
+ tdsqlite3_bind_int64(
pNextIdx, 1, getAbsoluteLevel(p, iLangid, iIndex, iLevel)
);
- if( SQLITE_ROW==sqlite3_step(pNextIdx) ){
- iNext = sqlite3_column_int(pNextIdx, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pNextIdx) ){
+ iNext = tdsqlite3_column_int(pNextIdx, 0);
}
- rc = sqlite3_reset(pNextIdx);
+ rc = tdsqlite3_reset(pNextIdx);
}
if( rc==SQLITE_OK ){
@@ -157446,7 +179883,7 @@ static int fts3AllocateSegdirIdx(
** segment and allocate (newly freed) index 0 at level iLevel. Otherwise,
** if iNext is less than FTS3_MERGE_COUNT, allocate index iNext.
*/
- if( iNext>=FTS3_MERGE_COUNT ){
+ if( iNext>=MergeCount(p) ){
fts3LogMerge(16, getAbsoluteLevel(p, iLangid, iIndex, iLevel));
rc = fts3SegmentMerge(p, iLangid, iIndex, iLevel);
*piIdx = 0;
@@ -157465,7 +179902,7 @@ static int fts3AllocateSegdirIdx(
**
** This function reads data from a single row of the %_segments table. The
** specific row is identified by the iBlockid parameter. If paBlob is not
-** NULL, then a buffer is allocated using sqlite3_malloc() and populated
+** NULL, then a buffer is allocated using tdsqlite3_malloc() and populated
** with the contents of the blob stored in the "block" column of the
** identified table row is. Whether or not paBlob is NULL, *pnBlob is set
** to the size of the blob in bytes before returning.
@@ -157475,19 +179912,19 @@ static int fts3AllocateSegdirIdx(
** paBlob is non-NULL, then it is the responsibility of the caller to
** eventually free the returned buffer.
**
-** This function may leave an open sqlite3_blob* handle in the
+** This function may leave an open tdsqlite3_blob* handle in the
** Fts3Table.pSegments variable. This handle is reused by subsequent calls
** to this function. The handle may be closed by calling the
-** sqlite3Fts3SegmentsClose() function. Reusing a blob handle is a handy
+** tdsqlite3Fts3SegmentsClose() function. Reusing a blob handle is a handy
** performance improvement, but the blob handle should always be closed
** before control is returned to the user (to prevent a lock being held
** on the database file for longer than necessary). Thus, any virtual table
** method (xFilter etc.) that may directly or indirectly call this function
-** must call sqlite3Fts3SegmentsClose() before returning.
+** must call tdsqlite3Fts3SegmentsClose() before returning.
*/
-SQLITE_PRIVATE int sqlite3Fts3ReadBlock(
+SQLITE_PRIVATE int tdsqlite3Fts3ReadBlock(
Fts3Table *p, /* FTS3 table handle */
- sqlite3_int64 iBlockid, /* Access the row with blockid=$iBlockid */
+ tdsqlite3_int64 iBlockid, /* Access the row with blockid=$iBlockid */
char **paBlob, /* OUT: Blob data in malloc'd buffer */
int *pnBlob, /* OUT: Size of blob data */
int *pnLoad /* OUT: Bytes actually loaded */
@@ -157498,22 +179935,22 @@ SQLITE_PRIVATE int sqlite3Fts3ReadBlock(
assert( pnBlob );
if( p->pSegments ){
- rc = sqlite3_blob_reopen(p->pSegments, iBlockid);
+ rc = tdsqlite3_blob_reopen(p->pSegments, iBlockid);
}else{
if( 0==p->zSegmentsTbl ){
- p->zSegmentsTbl = sqlite3_mprintf("%s_segments", p->zName);
+ p->zSegmentsTbl = tdsqlite3_mprintf("%s_segments", p->zName);
if( 0==p->zSegmentsTbl ) return SQLITE_NOMEM;
}
- rc = sqlite3_blob_open(
+ rc = tdsqlite3_blob_open(
p->db, p->zDb, p->zSegmentsTbl, "block", iBlockid, 0, &p->pSegments
);
}
if( rc==SQLITE_OK ){
- int nByte = sqlite3_blob_bytes(p->pSegments);
+ int nByte = tdsqlite3_blob_bytes(p->pSegments);
*pnBlob = nByte;
if( paBlob ){
- char *aByte = sqlite3_malloc(nByte + FTS3_NODE_PADDING);
+ char *aByte = tdsqlite3_malloc(nByte + FTS3_NODE_PADDING);
if( !aByte ){
rc = SQLITE_NOMEM;
}else{
@@ -157521,15 +179958,17 @@ SQLITE_PRIVATE int sqlite3Fts3ReadBlock(
nByte = FTS3_NODE_CHUNKSIZE;
*pnLoad = nByte;
}
- rc = sqlite3_blob_read(p->pSegments, aByte, nByte, 0);
+ rc = tdsqlite3_blob_read(p->pSegments, aByte, nByte, 0);
memset(&aByte[nByte], 0, FTS3_NODE_PADDING);
if( rc!=SQLITE_OK ){
- sqlite3_free(aByte);
+ tdsqlite3_free(aByte);
aByte = 0;
}
}
*paBlob = aByte;
}
+ }else if( rc==SQLITE_ERROR ){
+ rc = FTS_CORRUPT_VTAB;
}
return rc;
@@ -157537,10 +179976,10 @@ SQLITE_PRIVATE int sqlite3Fts3ReadBlock(
/*
** Close the blob handle at p->pSegments, if it is open. See comments above
-** the sqlite3Fts3ReadBlock() function for details.
+** the tdsqlite3Fts3ReadBlock() function for details.
*/
-SQLITE_PRIVATE void sqlite3Fts3SegmentsClose(Fts3Table *p){
- sqlite3_blob_close(p->pSegments);
+SQLITE_PRIVATE void tdsqlite3Fts3SegmentsClose(Fts3Table *p){
+ tdsqlite3_blob_close(p->pSegments);
p->pSegments = 0;
}
@@ -157549,7 +179988,7 @@ static int fts3SegReaderIncrRead(Fts3SegReader *pReader){
int rc; /* Return code */
nRead = MIN(pReader->nNode - pReader->nPopulate, FTS3_NODE_CHUNKSIZE);
- rc = sqlite3_blob_read(
+ rc = tdsqlite3_blob_read(
pReader->pBlob,
&pReader->aNode[pReader->nPopulate],
nRead,
@@ -157560,7 +179999,7 @@ static int fts3SegReaderIncrRead(Fts3SegReader *pReader){
pReader->nPopulate += nRead;
memset(&pReader->aNode[pReader->nPopulate], 0, FTS3_NODE_PADDING);
if( pReader->nPopulate==pReader->nNode ){
- sqlite3_blob_close(pReader->pBlob);
+ tdsqlite3_blob_close(pReader->pBlob);
pReader->pBlob = 0;
pReader->nPopulate = 0;
}
@@ -157586,8 +180025,8 @@ static int fts3SegReaderRequire(Fts3SegReader *pReader, char *pFrom, int nByte){
*/
static void fts3SegReaderSetEof(Fts3SegReader *pSeg){
if( !fts3SegReaderIsRootOnly(pSeg) ){
- sqlite3_free(pSeg->aNode);
- sqlite3_blob_close(pSeg->pBlob);
+ tdsqlite3_free(pSeg->aNode);
+ tdsqlite3_blob_close(pSeg->pBlob);
pSeg->pBlob = 0;
}
pSeg->aNode = 0;
@@ -157618,7 +180057,7 @@ static int fts3SegReaderNext(
if( fts3SegReaderIsPending(pReader) ){
Fts3HashElem *pElem = *(pReader->ppNextElem);
- sqlite3_free(pReader->aNode);
+ tdsqlite3_free(pReader->aNode);
pReader->aNode = 0;
if( pElem ){
char *aCopy;
@@ -157626,7 +180065,7 @@ static int fts3SegReaderNext(
int nCopy = pList->nData+1;
pReader->zTerm = (char *)fts3HashKey(pElem);
pReader->nTerm = fts3HashKeysize(pElem);
- aCopy = (char*)sqlite3_malloc(nCopy);
+ aCopy = (char*)tdsqlite3_malloc(nCopy);
if( !aCopy ) return SQLITE_NOMEM;
memcpy(aCopy, pList->aData, nCopy);
pReader->nNode = pReader->nDoclist = nCopy;
@@ -157641,12 +180080,14 @@ static int fts3SegReaderNext(
/* If iCurrentBlock>=iLeafEndBlock, this is an EOF condition. All leaf
** blocks have already been traversed. */
- assert( pReader->iCurrentBlock<=pReader->iLeafEndBlock );
+#ifdef CORRUPT_DB
+ assert( pReader->iCurrentBlock<=pReader->iLeafEndBlock || CORRUPT_DB );
+#endif
if( pReader->iCurrentBlock>=pReader->iLeafEndBlock ){
return SQLITE_OK;
}
- rc = sqlite3Fts3ReadBlock(
+ rc = tdsqlite3Fts3ReadBlock(
p, ++pReader->iCurrentBlock, &pReader->aNode, &pReader->nNode,
(bIncr ? &pReader->nPopulate : 0)
);
@@ -157668,15 +180109,19 @@ static int fts3SegReaderNext(
** safe (no risk of overread) even if the node data is corrupted. */
pNext += fts3GetVarint32(pNext, &nPrefix);
pNext += fts3GetVarint32(pNext, &nSuffix);
- if( nPrefix<0 || nSuffix<=0
- || &pNext[nSuffix]>&pReader->aNode[pReader->nNode]
+ if( nSuffix<=0
+ || (&pReader->aNode[pReader->nNode] - pNext)<nSuffix
+ || nPrefix>pReader->nTerm
){
return FTS_CORRUPT_VTAB;
}
- if( nPrefix+nSuffix>pReader->nTermAlloc ){
- int nNew = (nPrefix+nSuffix)*2;
- char *zNew = sqlite3_realloc(pReader->zTerm, nNew);
+ /* Both nPrefix and nSuffix were read by fts3GetVarint32() and so are
+ ** between 0 and 0x7FFFFFFF. But the sum of the two may cause integer
+ ** overflow - hence the (i64) casts. */
+ if( (i64)nPrefix+nSuffix>(i64)pReader->nTermAlloc ){
+ i64 nNew = ((i64)nPrefix+nSuffix)*2;
+ char *zNew = tdsqlite3_realloc64(pReader->zTerm, nNew);
if( !zNew ){
return SQLITE_NOMEM;
}
@@ -157698,7 +180143,7 @@ static int fts3SegReaderNext(
** b-tree node. And that the final byte of the doclist is 0x00. If either
** of these statements is untrue, then the data structure is corrupt.
*/
- if( &pReader->aDoclist[pReader->nDoclist]>&pReader->aNode[pReader->nNode]
+ if( pReader->nDoclist > pReader->nNode-(pReader->aDoclist-pReader->aNode)
|| (pReader->nPopulate==0 && pReader->aDoclist[pReader->nDoclist-1])
){
return FTS_CORRUPT_VTAB;
@@ -157718,14 +180163,14 @@ static int fts3SegReaderFirstDocid(Fts3Table *pTab, Fts3SegReader *pReader){
u8 bEof = 0;
pReader->iDocid = 0;
pReader->nOffsetList = 0;
- sqlite3Fts3DoclistPrev(0,
+ tdsqlite3Fts3DoclistPrev(0,
pReader->aDoclist, pReader->nDoclist, &pReader->pOffsetList,
&pReader->iDocid, &pReader->nOffsetList, &bEof
);
}else{
rc = fts3SegReaderRequire(pReader, pReader->aDoclist, FTS3_VARINT_MAX);
if( rc==SQLITE_OK ){
- int n = sqlite3Fts3GetVarint(pReader->aDoclist, &pReader->iDocid);
+ int n = tdsqlite3Fts3GetVarint(pReader->aDoclist, &pReader->iDocid);
pReader->pOffsetList = &pReader->aDoclist[n];
}
}
@@ -157763,7 +180208,7 @@ static int fts3SegReaderNextDocid(
*ppOffsetList = pReader->pOffsetList;
*pnOffsetList = pReader->nOffsetList - 1;
}
- sqlite3Fts3DoclistPrev(0,
+ tdsqlite3Fts3DoclistPrev(0,
pReader->aDoclist, pReader->nDoclist, &p, &pReader->iDocid,
&pReader->nOffsetList, &bEof
);
@@ -157816,22 +180261,22 @@ static int fts3SegReaderNextDocid(
}else{
rc = fts3SegReaderRequire(pReader, p, FTS3_VARINT_MAX);
if( rc==SQLITE_OK ){
- sqlite3_int64 iDelta;
- pReader->pOffsetList = p + sqlite3Fts3GetVarint(p, &iDelta);
+ u64 iDelta;
+ pReader->pOffsetList = p + tdsqlite3Fts3GetVarintU(p, &iDelta);
if( pTab->bDescIdx ){
- pReader->iDocid -= iDelta;
+ pReader->iDocid = (i64)((u64)pReader->iDocid - iDelta);
}else{
- pReader->iDocid += iDelta;
+ pReader->iDocid = (i64)((u64)pReader->iDocid + iDelta);
}
}
}
}
- return SQLITE_OK;
+ return rc;
}
-SQLITE_PRIVATE int sqlite3Fts3MsrOvfl(
+SQLITE_PRIVATE int tdsqlite3Fts3MsrOvfl(
Fts3Cursor *pCsr,
Fts3MultiSegReader *pMsr,
int *pnOvfl
@@ -157850,10 +180295,10 @@ SQLITE_PRIVATE int sqlite3Fts3MsrOvfl(
if( !fts3SegReaderIsPending(pReader)
&& !fts3SegReaderIsRootOnly(pReader)
){
- sqlite3_int64 jj;
+ tdsqlite3_int64 jj;
for(jj=pReader->iStartBlock; jj<=pReader->iLeafEndBlock; jj++){
int nBlob;
- rc = sqlite3Fts3ReadBlock(p, jj, 0, &nBlob, 0);
+ rc = tdsqlite3Fts3ReadBlock(p, jj, 0, &nBlob, 0);
if( rc!=SQLITE_OK ) break;
if( (nBlob+35)>pgsz ){
nOvfl += (nBlob + 34)/pgsz;
@@ -157869,28 +180314,28 @@ SQLITE_PRIVATE int sqlite3Fts3MsrOvfl(
** Free all allocations associated with the iterator passed as the
** second argument.
*/
-SQLITE_PRIVATE void sqlite3Fts3SegReaderFree(Fts3SegReader *pReader){
+SQLITE_PRIVATE void tdsqlite3Fts3SegReaderFree(Fts3SegReader *pReader){
if( pReader ){
if( !fts3SegReaderIsPending(pReader) ){
- sqlite3_free(pReader->zTerm);
+ tdsqlite3_free(pReader->zTerm);
}
if( !fts3SegReaderIsRootOnly(pReader) ){
- sqlite3_free(pReader->aNode);
+ tdsqlite3_free(pReader->aNode);
}
- sqlite3_blob_close(pReader->pBlob);
+ tdsqlite3_blob_close(pReader->pBlob);
}
- sqlite3_free(pReader);
+ tdsqlite3_free(pReader);
}
/*
** Allocate a new SegReader object.
*/
-SQLITE_PRIVATE int sqlite3Fts3SegReaderNew(
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderNew(
int iAge, /* Segment "age". */
int bLookup, /* True for a lookup only */
- sqlite3_int64 iStartLeaf, /* First leaf to traverse */
- sqlite3_int64 iEndLeaf, /* Final leaf to traverse */
- sqlite3_int64 iEndBlock, /* Final block of segment */
+ tdsqlite3_int64 iStartLeaf, /* First leaf to traverse */
+ tdsqlite3_int64 iEndLeaf, /* Final leaf to traverse */
+ tdsqlite3_int64 iEndBlock, /* Final block of segment */
const char *zRoot, /* Buffer containing root node */
int nRoot, /* Size of buffer containing root node */
Fts3SegReader **ppReader /* OUT: Allocated Fts3SegReader */
@@ -157898,12 +180343,17 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderNew(
Fts3SegReader *pReader; /* Newly allocated SegReader object */
int nExtra = 0; /* Bytes to allocate segment root node */
- assert( iStartLeaf<=iEndLeaf );
+ assert( zRoot!=0 || nRoot==0 );
+#ifdef CORRUPT_DB
+ assert( zRoot!=0 || CORRUPT_DB );
+#endif
+
if( iStartLeaf==0 ){
+ if( iEndLeaf!=0 ) return FTS_CORRUPT_VTAB;
nExtra = nRoot + FTS3_NODE_PADDING;
}
- pReader = (Fts3SegReader *)sqlite3_malloc(sizeof(Fts3SegReader) + nExtra);
+ pReader = (Fts3SegReader *)tdsqlite3_malloc(sizeof(Fts3SegReader) + nExtra);
if( !pReader ){
return SQLITE_NOMEM;
}
@@ -157919,7 +180369,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderNew(
pReader->aNode = (char *)&pReader[1];
pReader->rootOnly = 1;
pReader->nNode = nRoot;
- memcpy(pReader->aNode, zRoot, nRoot);
+ if( nRoot ) memcpy(pReader->aNode, zRoot, nRoot);
memset(&pReader->aNode[nRoot], 0, FTS3_NODE_PADDING);
}else{
pReader->iCurrentBlock = iStartLeaf-1;
@@ -157969,7 +180419,7 @@ static int SQLITE_CDECL fts3CompareElemByTerm(
**
** firebird mysql sqlite
*/
-SQLITE_PRIVATE int sqlite3Fts3SegReaderPending(
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderPending(
Fts3Table *p, /* Virtual table handle */
int iIndex, /* Index for p->aIndex */
const char *zTerm, /* Term to search for */
@@ -157995,7 +180445,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderPending(
if( nElem==nAlloc ){
Fts3HashElem **aElem2;
nAlloc += 16;
- aElem2 = (Fts3HashElem **)sqlite3_realloc(
+ aElem2 = (Fts3HashElem **)tdsqlite3_realloc(
aElem, nAlloc*sizeof(Fts3HashElem *)
);
if( !aElem2 ){
@@ -158034,8 +180484,9 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderPending(
}
if( nElem>0 ){
- int nByte = sizeof(Fts3SegReader) + (nElem+1)*sizeof(Fts3HashElem *);
- pReader = (Fts3SegReader *)sqlite3_malloc(nByte);
+ tdsqlite3_int64 nByte;
+ nByte = sizeof(Fts3SegReader) + (nElem+1)*sizeof(Fts3HashElem *);
+ pReader = (Fts3SegReader *)tdsqlite3_malloc64(nByte);
if( !pReader ){
rc = SQLITE_NOMEM;
}else{
@@ -158047,7 +180498,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderPending(
}
if( bPrefix ){
- sqlite3_free(aElem);
+ tdsqlite3_free(aElem);
}
*ppReader = pReader;
return rc;
@@ -158191,17 +180642,18 @@ static void fts3SegReaderSort(
*/
static int fts3WriteSegment(
Fts3Table *p, /* Virtual table handle */
- sqlite3_int64 iBlock, /* Block id for new block */
+ tdsqlite3_int64 iBlock, /* Block id for new block */
char *z, /* Pointer to buffer containing block data */
int n /* Size of buffer z in bytes */
){
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
int rc = fts3SqlStmt(p, SQL_INSERT_SEGMENTS, &pStmt, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pStmt, 1, iBlock);
- sqlite3_bind_blob(pStmt, 2, z, n, SQLITE_STATIC);
- sqlite3_step(pStmt);
- rc = sqlite3_reset(pStmt);
+ tdsqlite3_bind_int64(pStmt, 1, iBlock);
+ tdsqlite3_bind_blob(pStmt, 2, z, n, SQLITE_STATIC);
+ tdsqlite3_step(pStmt);
+ rc = tdsqlite3_reset(pStmt);
+ tdsqlite3_bind_null(pStmt, 2);
}
return rc;
}
@@ -158211,17 +180663,17 @@ static int fts3WriteSegment(
** *pnMax to this value and return SQLITE_OK. Otherwise, if an error occurs,
** set *pnMax to zero and return an SQLite error code.
*/
-SQLITE_PRIVATE int sqlite3Fts3MaxLevel(Fts3Table *p, int *pnMax){
+SQLITE_PRIVATE int tdsqlite3Fts3MaxLevel(Fts3Table *p, int *pnMax){
int rc;
int mxLevel = 0;
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
rc = fts3SqlStmt(p, SQL_SELECT_MXLEVEL, &pStmt, 0);
if( rc==SQLITE_OK ){
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- mxLevel = sqlite3_column_int(pStmt, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ mxLevel = tdsqlite3_column_int(pStmt, 0);
}
- rc = sqlite3_reset(pStmt);
+ rc = tdsqlite3_reset(pStmt);
}
*pnMax = mxLevel;
return rc;
@@ -158232,32 +180684,33 @@ SQLITE_PRIVATE int sqlite3Fts3MaxLevel(Fts3Table *p, int *pnMax){
*/
static int fts3WriteSegdir(
Fts3Table *p, /* Virtual table handle */
- sqlite3_int64 iLevel, /* Value for "level" field (absolute level) */
+ tdsqlite3_int64 iLevel, /* Value for "level" field (absolute level) */
int iIdx, /* Value for "idx" field */
- sqlite3_int64 iStartBlock, /* Value for "start_block" field */
- sqlite3_int64 iLeafEndBlock, /* Value for "leaves_end_block" field */
- sqlite3_int64 iEndBlock, /* Value for "end_block" field */
- sqlite3_int64 nLeafData, /* Bytes of leaf data in segment */
+ tdsqlite3_int64 iStartBlock, /* Value for "start_block" field */
+ tdsqlite3_int64 iLeafEndBlock, /* Value for "leaves_end_block" field */
+ tdsqlite3_int64 iEndBlock, /* Value for "end_block" field */
+ tdsqlite3_int64 nLeafData, /* Bytes of leaf data in segment */
char *zRoot, /* Blob value for "root" field */
int nRoot /* Number of bytes in buffer zRoot */
){
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
int rc = fts3SqlStmt(p, SQL_INSERT_SEGDIR, &pStmt, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pStmt, 1, iLevel);
- sqlite3_bind_int(pStmt, 2, iIdx);
- sqlite3_bind_int64(pStmt, 3, iStartBlock);
- sqlite3_bind_int64(pStmt, 4, iLeafEndBlock);
+ tdsqlite3_bind_int64(pStmt, 1, iLevel);
+ tdsqlite3_bind_int(pStmt, 2, iIdx);
+ tdsqlite3_bind_int64(pStmt, 3, iStartBlock);
+ tdsqlite3_bind_int64(pStmt, 4, iLeafEndBlock);
if( nLeafData==0 ){
- sqlite3_bind_int64(pStmt, 5, iEndBlock);
+ tdsqlite3_bind_int64(pStmt, 5, iEndBlock);
}else{
- char *zEnd = sqlite3_mprintf("%lld %lld", iEndBlock, nLeafData);
+ char *zEnd = tdsqlite3_mprintf("%lld %lld", iEndBlock, nLeafData);
if( !zEnd ) return SQLITE_NOMEM;
- sqlite3_bind_text(pStmt, 5, zEnd, -1, sqlite3_free);
+ tdsqlite3_bind_text(pStmt, 5, zEnd, -1, tdsqlite3_free);
}
- sqlite3_bind_blob(pStmt, 6, zRoot, nRoot, SQLITE_STATIC);
- sqlite3_step(pStmt);
- rc = sqlite3_reset(pStmt);
+ tdsqlite3_bind_blob(pStmt, 6, zRoot, nRoot, SQLITE_STATIC);
+ tdsqlite3_step(pStmt);
+ rc = tdsqlite3_reset(pStmt);
+ tdsqlite3_bind_null(pStmt, 6);
}
return rc;
}
@@ -158309,7 +180762,12 @@ static int fts3NodeAddTerm(
nPrefix = fts3PrefixCompress(pTree->zTerm, pTree->nTerm, zTerm, nTerm);
nSuffix = nTerm-nPrefix;
- nReq += sqlite3Fts3VarintLen(nPrefix)+sqlite3Fts3VarintLen(nSuffix)+nSuffix;
+ /* If nSuffix is zero or less, then zTerm/nTerm must be a prefix of
+ ** pWriter->zTerm/pWriter->nTerm. i.e. must be equal to or less than when
+ ** compared with BINARY collation. This indicates corruption. */
+ if( nSuffix<=0 ) return FTS_CORRUPT_VTAB;
+
+ nReq += tdsqlite3Fts3VarintLen(nPrefix)+tdsqlite3Fts3VarintLen(nSuffix)+nSuffix;
if( nReq<=p->nNodeSize || !pTree->zTerm ){
if( nReq>p->nNodeSize ){
@@ -158321,7 +180779,7 @@ static int fts3NodeAddTerm(
** this is not expected to be a serious problem.
*/
assert( pTree->aData==(char *)&pTree[1] );
- pTree->aData = (char *)sqlite3_malloc(nReq);
+ pTree->aData = (char *)tdsqlite3_malloc(nReq);
if( !pTree->aData ){
return SQLITE_NOMEM;
}
@@ -158329,17 +180787,17 @@ static int fts3NodeAddTerm(
if( pTree->zTerm ){
/* There is no prefix-length field for first term in a node */
- nData += sqlite3Fts3PutVarint(&pTree->aData[nData], nPrefix);
+ nData += tdsqlite3Fts3PutVarint(&pTree->aData[nData], nPrefix);
}
- nData += sqlite3Fts3PutVarint(&pTree->aData[nData], nSuffix);
+ nData += tdsqlite3Fts3PutVarint(&pTree->aData[nData], nSuffix);
memcpy(&pTree->aData[nData], &zTerm[nPrefix], nSuffix);
pTree->nData = nData + nSuffix;
pTree->nEntry++;
if( isCopyTerm ){
if( pTree->nMalloc<nTerm ){
- char *zNew = sqlite3_realloc(pTree->zMalloc, nTerm*2);
+ char *zNew = tdsqlite3_realloc(pTree->zMalloc, nTerm*2);
if( !zNew ){
return SQLITE_NOMEM;
}
@@ -158365,7 +180823,7 @@ static int fts3NodeAddTerm(
** now. Instead, the term is inserted into the parent of pTree. If pTree
** has no parent, one is created here.
*/
- pNew = (SegmentNode *)sqlite3_malloc(sizeof(SegmentNode) + p->nNodeSize);
+ pNew = (SegmentNode *)tdsqlite3_malloc(sizeof(SegmentNode) + p->nNodeSize);
if( !pNew ){
return SQLITE_NOMEM;
}
@@ -158400,13 +180858,13 @@ static int fts3NodeAddTerm(
static int fts3TreeFinishNode(
SegmentNode *pTree,
int iHeight,
- sqlite3_int64 iLeftChild
+ tdsqlite3_int64 iLeftChild
){
int nStart;
assert( iHeight>=1 && iHeight<128 );
- nStart = FTS3_VARINT_MAX - sqlite3Fts3VarintLen(iLeftChild);
+ nStart = FTS3_VARINT_MAX - tdsqlite3Fts3VarintLen(iLeftChild);
pTree->aData[nStart] = (char)iHeight;
- sqlite3Fts3PutVarint(&pTree->aData[nStart+1], iLeftChild);
+ tdsqlite3Fts3PutVarint(&pTree->aData[nStart+1], iLeftChild);
return nStart;
}
@@ -158427,9 +180885,9 @@ static int fts3NodeWrite(
Fts3Table *p, /* Virtual table handle */
SegmentNode *pTree, /* SegmentNode handle */
int iHeight, /* Height of this node in tree */
- sqlite3_int64 iLeaf, /* Block id of first leaf node */
- sqlite3_int64 iFree, /* Block id of next free slot in %_segments */
- sqlite3_int64 *piLast, /* OUT: Block id of last entry written */
+ tdsqlite3_int64 iLeaf, /* Block id of first leaf node */
+ tdsqlite3_int64 iFree, /* Block id of next free slot in %_segments */
+ tdsqlite3_int64 *piLast, /* OUT: Block id of last entry written */
char **paRoot, /* OUT: Data for root node */
int *pnRoot /* OUT: Size of root node in bytes */
){
@@ -158443,8 +180901,8 @@ static int fts3NodeWrite(
*paRoot = &pTree->aData[nStart];
}else{
SegmentNode *pIter;
- sqlite3_int64 iNextFree = iFree;
- sqlite3_int64 iNextLeaf = iLeaf;
+ tdsqlite3_int64 iNextFree = iFree;
+ tdsqlite3_int64 iNextLeaf = iLeaf;
for(pIter=pTree->pLeftmost; pIter && rc==SQLITE_OK; pIter=pIter->pRight){
int nStart = fts3TreeFinishNode(pIter, iHeight, iNextLeaf);
int nWrite = pIter->nData - nStart;
@@ -158474,11 +180932,11 @@ static void fts3NodeFree(SegmentNode *pTree){
while( p ){
SegmentNode *pRight = p->pRight;
if( p->aData!=(char *)&p[1] ){
- sqlite3_free(p->aData);
+ tdsqlite3_free(p->aData);
}
assert( pRight==0 || p->zMalloc==0 );
- sqlite3_free(p->zMalloc);
- sqlite3_free(p);
+ tdsqlite3_free(p->zMalloc);
+ tdsqlite3_free(p);
p = pRight;
}
}
@@ -158509,27 +180967,27 @@ static int fts3SegWriterAdd(
if( !pWriter ){
int rc;
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
/* Allocate the SegmentWriter structure */
- pWriter = (SegmentWriter *)sqlite3_malloc(sizeof(SegmentWriter));
+ pWriter = (SegmentWriter *)tdsqlite3_malloc(sizeof(SegmentWriter));
if( !pWriter ) return SQLITE_NOMEM;
memset(pWriter, 0, sizeof(SegmentWriter));
*ppWriter = pWriter;
/* Allocate a buffer in which to accumulate data */
- pWriter->aData = (char *)sqlite3_malloc(p->nNodeSize);
+ pWriter->aData = (char *)tdsqlite3_malloc(p->nNodeSize);
if( !pWriter->aData ) return SQLITE_NOMEM;
pWriter->nSize = p->nNodeSize;
/* Find the next free blockid in the %_segments table */
rc = fts3SqlStmt(p, SQL_NEXT_SEGMENTS_ID, &pStmt, 0);
if( rc!=SQLITE_OK ) return rc;
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- pWriter->iFree = sqlite3_column_int64(pStmt, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ pWriter->iFree = tdsqlite3_column_int64(pStmt, 0);
pWriter->iFirst = pWriter->iFree;
}
- rc = sqlite3_reset(pStmt);
+ rc = tdsqlite3_reset(pStmt);
if( rc!=SQLITE_OK ) return rc;
}
nData = pWriter->nData;
@@ -158537,17 +180995,23 @@ static int fts3SegWriterAdd(
nPrefix = fts3PrefixCompress(pWriter->zTerm, pWriter->nTerm, zTerm, nTerm);
nSuffix = nTerm-nPrefix;
+ /* If nSuffix is zero or less, then zTerm/nTerm must be a prefix of
+ ** pWriter->zTerm/pWriter->nTerm. i.e. must be equal to or less than when
+ ** compared with BINARY collation. This indicates corruption. */
+ if( nSuffix<=0 ) return FTS_CORRUPT_VTAB;
+
/* Figure out how many bytes are required by this new entry */
- nReq = sqlite3Fts3VarintLen(nPrefix) + /* varint containing prefix size */
- sqlite3Fts3VarintLen(nSuffix) + /* varint containing suffix size */
+ nReq = tdsqlite3Fts3VarintLen(nPrefix) + /* varint containing prefix size */
+ tdsqlite3Fts3VarintLen(nSuffix) + /* varint containing suffix size */
nSuffix + /* Term suffix */
- sqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */
+ tdsqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */
nDoclist; /* Doclist data */
if( nData>0 && nData+nReq>p->nNodeSize ){
int rc;
/* The current leaf node is full. Write it out to the database. */
+ if( pWriter->iFree==LARGEST_INT64 ) return FTS_CORRUPT_VTAB;
rc = fts3WriteSegment(p, pWriter->iFree++, pWriter->aData, nData);
if( rc!=SQLITE_OK ) return rc;
p->nLeafAdd++;
@@ -158574,9 +181038,9 @@ static int fts3SegWriterAdd(
nPrefix = 0;
nSuffix = nTerm;
nReq = 1 + /* varint containing prefix size */
- sqlite3Fts3VarintLen(nTerm) + /* varint containing suffix size */
+ tdsqlite3Fts3VarintLen(nTerm) + /* varint containing suffix size */
nTerm + /* Term suffix */
- sqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */
+ tdsqlite3Fts3VarintLen(nDoclist) + /* Size of doclist */
nDoclist; /* Doclist data */
}
@@ -158587,7 +181051,7 @@ static int fts3SegWriterAdd(
** the buffer to make it large enough.
*/
if( nReq>pWriter->nSize ){
- char *aNew = sqlite3_realloc(pWriter->aData, nReq);
+ char *aNew = tdsqlite3_realloc(pWriter->aData, nReq);
if( !aNew ) return SQLITE_NOMEM;
pWriter->aData = aNew;
pWriter->nSize = nReq;
@@ -158595,11 +181059,13 @@ static int fts3SegWriterAdd(
assert( nData+nReq<=pWriter->nSize );
/* Append the prefix-compressed term and doclist to the buffer. */
- nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nPrefix);
- nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nSuffix);
+ nData += tdsqlite3Fts3PutVarint(&pWriter->aData[nData], nPrefix);
+ nData += tdsqlite3Fts3PutVarint(&pWriter->aData[nData], nSuffix);
+ assert( nSuffix>0 );
memcpy(&pWriter->aData[nData], &zTerm[nPrefix], nSuffix);
nData += nSuffix;
- nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nDoclist);
+ nData += tdsqlite3Fts3PutVarint(&pWriter->aData[nData], nDoclist);
+ assert( nDoclist>0 );
memcpy(&pWriter->aData[nData], aDoclist, nDoclist);
pWriter->nData = nData + nDoclist;
@@ -158610,7 +181076,7 @@ static int fts3SegWriterAdd(
*/
if( isCopyTerm ){
if( nTerm>pWriter->nMalloc ){
- char *zNew = sqlite3_realloc(pWriter->zMalloc, nTerm*2);
+ char *zNew = tdsqlite3_realloc(pWriter->zMalloc, nTerm*2);
if( !zNew ){
return SQLITE_NOMEM;
}
@@ -158619,6 +181085,7 @@ static int fts3SegWriterAdd(
pWriter->zTerm = zNew;
}
assert( pWriter->zTerm==pWriter->zMalloc );
+ assert( nTerm>0 );
memcpy(pWriter->zTerm, zTerm, nTerm);
}else{
pWriter->zTerm = (char *)zTerm;
@@ -158637,13 +181104,13 @@ static int fts3SegWriterAdd(
static int fts3SegWriterFlush(
Fts3Table *p, /* Virtual table handle */
SegmentWriter *pWriter, /* SegmentWriter to flush to the db */
- sqlite3_int64 iLevel, /* Value for 'level' column of %_segdir */
+ tdsqlite3_int64 iLevel, /* Value for 'level' column of %_segdir */
int iIdx /* Value for 'idx' column of %_segdir */
){
int rc; /* Return code */
if( pWriter->pTree ){
- sqlite3_int64 iLast = 0; /* Largest block id written to database */
- sqlite3_int64 iLastLeaf; /* Largest leaf block id written to db */
+ tdsqlite3_int64 iLast = 0; /* Largest block id written to database */
+ tdsqlite3_int64 iLastLeaf; /* Largest leaf block id written to db */
char *zRoot = NULL; /* Pointer to buffer containing root node */
int nRoot = 0; /* Size of buffer zRoot */
@@ -158672,10 +181139,10 @@ static int fts3SegWriterFlush(
*/
static void fts3SegWriterFree(SegmentWriter *pWriter){
if( pWriter ){
- sqlite3_free(pWriter->aData);
- sqlite3_free(pWriter->zMalloc);
+ tdsqlite3_free(pWriter->aData);
+ tdsqlite3_free(pWriter->zMalloc);
fts3NodeFree(pWriter->pTree);
- sqlite3_free(pWriter);
+ tdsqlite3_free(pWriter);
}
}
@@ -158689,8 +181156,8 @@ static void fts3SegWriterFree(SegmentWriter *pWriter){
** document pRowid, or false otherwise, and SQLITE_OK is returned. If an
** error occurs, an SQLite error code is returned.
*/
-static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){
- sqlite3_stmt *pStmt;
+static int fts3IsEmpty(Fts3Table *p, tdsqlite3_value *pRowid, int *pisEmpty){
+ tdsqlite3_stmt *pStmt;
int rc;
if( p->zContentTbl ){
/* If using the content=xxx option, assume the table is never empty */
@@ -158699,10 +181166,10 @@ static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){
}else{
rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid);
if( rc==SQLITE_OK ){
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- *pisEmpty = sqlite3_column_int(pStmt, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ *pisEmpty = tdsqlite3_column_int(pStmt, 0);
}
- rc = sqlite3_reset(pStmt);
+ rc = tdsqlite3_reset(pStmt);
}
}
return rc;
@@ -158720,9 +181187,9 @@ static int fts3SegmentMaxLevel(
Fts3Table *p,
int iLangid,
int iIndex,
- sqlite3_int64 *pnMax
+ tdsqlite3_int64 *pnMax
){
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
int rc;
assert( iIndex>=0 && iIndex<p->nIndex );
@@ -158734,14 +181201,14 @@ static int fts3SegmentMaxLevel(
*/
rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0);
if( rc!=SQLITE_OK ) return rc;
- sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0));
- sqlite3_bind_int64(pStmt, 2,
+ tdsqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0));
+ tdsqlite3_bind_int64(pStmt, 2,
getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1)
);
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- *pnMax = sqlite3_column_int64(pStmt, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ *pnMax = tdsqlite3_column_int64(pStmt, 0);
}
- return sqlite3_reset(pStmt);
+ return tdsqlite3_reset(pStmt);
}
/*
@@ -158760,19 +181227,19 @@ static int fts3SegmentIsMaxLevel(Fts3Table *p, i64 iAbsLevel, int *pbMax){
**
** (1024 is actually the value of macro FTS3_SEGDIR_PREFIXLEVEL_STR).
*/
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
int rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0);
if( rc!=SQLITE_OK ) return rc;
- sqlite3_bind_int64(pStmt, 1, iAbsLevel+1);
- sqlite3_bind_int64(pStmt, 2,
+ tdsqlite3_bind_int64(pStmt, 1, iAbsLevel+1);
+ tdsqlite3_bind_int64(pStmt, 2,
((iAbsLevel/FTS3_SEGDIR_MAXLEVEL)+1) * FTS3_SEGDIR_MAXLEVEL
);
*pbMax = 0;
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- *pbMax = sqlite3_column_type(pStmt, 0)==SQLITE_NULL;
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ *pbMax = tdsqlite3_column_type(pStmt, 0)==SQLITE_NULL;
}
- return sqlite3_reset(pStmt);
+ return tdsqlite3_reset(pStmt);
}
/*
@@ -158786,13 +181253,13 @@ static int fts3DeleteSegment(
){
int rc = SQLITE_OK; /* Return code */
if( pSeg->iStartBlock ){
- sqlite3_stmt *pDelete; /* SQL statement to delete rows */
+ tdsqlite3_stmt *pDelete; /* SQL statement to delete rows */
rc = fts3SqlStmt(p, SQL_DELETE_SEGMENTS_RANGE, &pDelete, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pDelete, 1, pSeg->iStartBlock);
- sqlite3_bind_int64(pDelete, 2, pSeg->iEndBlock);
- sqlite3_step(pDelete);
- rc = sqlite3_reset(pDelete);
+ tdsqlite3_bind_int64(pDelete, 1, pSeg->iStartBlock);
+ tdsqlite3_bind_int64(pDelete, 2, pSeg->iEndBlock);
+ tdsqlite3_step(pDelete);
+ rc = tdsqlite3_reset(pDelete);
}
}
return rc;
@@ -158822,7 +181289,7 @@ static int fts3DeleteSegdir(
){
int rc = SQLITE_OK; /* Return Code */
int i; /* Iterator variable */
- sqlite3_stmt *pDelete = 0; /* SQL statement to delete rows */
+ tdsqlite3_stmt *pDelete = 0; /* SQL statement to delete rows */
for(i=0; rc==SQLITE_OK && i<nReader; i++){
rc = fts3DeleteSegment(p, apSegment[i]);
@@ -158835,23 +181302,23 @@ static int fts3DeleteSegdir(
if( iLevel==FTS3_SEGCURSOR_ALL ){
rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_RANGE, &pDelete, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pDelete, 1, getAbsoluteLevel(p, iLangid, iIndex, 0));
- sqlite3_bind_int64(pDelete, 2,
+ tdsqlite3_bind_int64(pDelete, 1, getAbsoluteLevel(p, iLangid, iIndex, 0));
+ tdsqlite3_bind_int64(pDelete, 2,
getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1)
);
}
}else{
rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_LEVEL, &pDelete, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(
+ tdsqlite3_bind_int64(
pDelete, 1, getAbsoluteLevel(p, iLangid, iIndex, iLevel)
);
}
}
if( rc==SQLITE_OK ){
- sqlite3_step(pDelete);
- rc = sqlite3_reset(pDelete);
+ tdsqlite3_step(pDelete);
+ rc = tdsqlite3_reset(pDelete);
}
return rc;
@@ -158893,14 +181360,14 @@ static void fts3ColumnFilter(
nList -= (int)(p - pList);
pList = p;
- if( nList==0 ){
+ if( nList<=0 ){
break;
}
p = &pList[1];
p += fts3GetVarint32(p, &iCurrent);
}
- if( bZero && &pList[nList]!=pEnd ){
+ if( bZero && (pEnd - &pList[nList])>0){
memset(&pList[nList], 0, pEnd - &pList[nList]);
}
*ppList = pList;
@@ -158922,19 +181389,20 @@ static int fts3MsrBufferData(
if( nList>pMsr->nBuffer ){
char *pNew;
pMsr->nBuffer = nList*2;
- pNew = (char *)sqlite3_realloc(pMsr->aBuffer, pMsr->nBuffer);
+ pNew = (char *)tdsqlite3_realloc(pMsr->aBuffer, pMsr->nBuffer);
if( !pNew ) return SQLITE_NOMEM;
pMsr->aBuffer = pNew;
}
+ assert( nList>0 );
memcpy(pMsr->aBuffer, pList, nList);
return SQLITE_OK;
}
-SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext(
+SQLITE_PRIVATE int tdsqlite3Fts3MsrIncrNext(
Fts3Table *p, /* Virtual table handle */
Fts3MultiSegReader *pMsr, /* Multi-segment-reader handle */
- sqlite3_int64 *piDocid, /* OUT: Docid value */
+ tdsqlite3_int64 *piDocid, /* OUT: Docid value */
char **paPoslist, /* OUT: Pointer to position list */
int *pnPoslist /* OUT: Size of position list in bytes */
){
@@ -158961,7 +181429,7 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext(
char *pList;
int nList;
int j;
- sqlite3_int64 iDocid = apSegment[0]->iDocid;
+ tdsqlite3_int64 iDocid = apSegment[0]->iDocid;
rc = fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList);
j = 1;
@@ -159031,7 +181499,7 @@ static int fts3SegReaderStart(
return SQLITE_OK;
}
-SQLITE_PRIVATE int sqlite3Fts3SegReaderStart(
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderStart(
Fts3Table *p, /* Virtual table handle */
Fts3MultiSegReader *pCsr, /* Cursor object */
Fts3SegFilter *pFilter /* Restrictions on range of iteration */
@@ -159040,7 +181508,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStart(
return fts3SegReaderStart(p, pCsr, pFilter->zTerm, pFilter->nTerm);
}
-SQLITE_PRIVATE int sqlite3Fts3MsrIncrStart(
+SQLITE_PRIVATE int tdsqlite3Fts3MsrIncrStart(
Fts3Table *p, /* Virtual table handle */
Fts3MultiSegReader *pCsr, /* Cursor object */
int iCol, /* Column to match on. */
@@ -159085,17 +181553,17 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrStart(
/*
** This function is called on a MultiSegReader that has been started using
-** sqlite3Fts3MsrIncrStart(). One or more calls to MsrIncrNext() may also
+** tdsqlite3Fts3MsrIncrStart(). One or more calls to MsrIncrNext() may also
** have been made. Calling this function puts the MultiSegReader in such
** a state that if the next two calls are:
**
-** sqlite3Fts3SegReaderStart()
-** sqlite3Fts3SegReaderStep()
+** tdsqlite3Fts3SegReaderStart()
+** tdsqlite3Fts3SegReaderStep()
**
** then the entire doclist for the term is available in
** MultiSegReader.aDoclist/nDoclist.
*/
-SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){
+SQLITE_PRIVATE int tdsqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){
int i; /* Used to iterate through segment-readers */
assert( pCsr->zTerm==0 );
@@ -159115,7 +181583,7 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){
}
-SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
+SQLITE_PRIVATE int tdsqlite3Fts3SegReaderStep(
Fts3Table *p, /* Virtual table handle */
Fts3MultiSegReader *pCsr /* Cursor object */
){
@@ -159204,7 +181672,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
if( rc==SQLITE_OK ) rc = SQLITE_ROW;
}else{
int nDoclist = 0; /* Size of doclist */
- sqlite3_int64 iPrev = 0; /* Previous docid stored in doclist */
+ tdsqlite3_int64 iPrev = 0; /* Previous docid stored in doclist */
/* The current term of the first nMerge entries in the array
** of Fts3SegReader objects is the same. The doclists must be merged
@@ -159219,7 +181687,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
char *pList = 0;
int nList = 0;
int nByte;
- sqlite3_int64 iDocid = apSegment[0]->iDocid;
+ tdsqlite3_int64 iDocid = apSegment[0]->iDocid;
fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList);
j = 1;
while( j<nMerge
@@ -159238,20 +181706,20 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
/* Calculate the 'docid' delta value to write into the merged
** doclist. */
- sqlite3_int64 iDelta;
+ tdsqlite3_int64 iDelta;
if( p->bDescIdx && nDoclist>0 ){
- iDelta = iPrev - iDocid;
+ if( iPrev<=iDocid ) return FTS_CORRUPT_VTAB;
+ iDelta = (i64)((u64)iPrev - (u64)iDocid);
}else{
- iDelta = iDocid - iPrev;
+ if( nDoclist>0 && iPrev>=iDocid ) return FTS_CORRUPT_VTAB;
+ iDelta = (i64)((u64)iDocid - (u64)iPrev);
}
- assert( iDelta>0 || (nDoclist==0 && iDelta==iDocid) );
- assert( nDoclist>0 || iDelta==iDocid );
- nByte = sqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0);
+ nByte = tdsqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0);
if( nDoclist+nByte>pCsr->nBuffer ){
char *aNew;
pCsr->nBuffer = (nDoclist+nByte)*2;
- aNew = sqlite3_realloc(pCsr->aBuffer, pCsr->nBuffer);
+ aNew = tdsqlite3_realloc(pCsr->aBuffer, pCsr->nBuffer);
if( !aNew ){
return SQLITE_NOMEM;
}
@@ -159262,13 +181730,13 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
char *a = &pCsr->aBuffer[nDoclist];
int nWrite;
- nWrite = sqlite3Fts3FirstFilter(iDelta, pList, nList, a);
+ nWrite = tdsqlite3Fts3FirstFilter(iDelta, pList, nList, a);
if( nWrite ){
iPrev = iDocid;
nDoclist += nWrite;
}
}else{
- nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta);
+ nDoclist += tdsqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta);
iPrev = iDocid;
if( isRequirePos ){
memcpy(&pCsr->aBuffer[nDoclist], pList, nList);
@@ -159293,16 +181761,16 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(
}
-SQLITE_PRIVATE void sqlite3Fts3SegReaderFinish(
+SQLITE_PRIVATE void tdsqlite3Fts3SegReaderFinish(
Fts3MultiSegReader *pCsr /* Cursor object */
){
if( pCsr ){
int i;
for(i=0; i<pCsr->nSegment; i++){
- sqlite3Fts3SegReaderFree(pCsr->apSegment[i]);
+ tdsqlite3Fts3SegReaderFree(pCsr->apSegment[i]);
}
- sqlite3_free(pCsr->apSegment);
- sqlite3_free(pCsr->aBuffer);
+ tdsqlite3_free(pCsr->apSegment);
+ tdsqlite3_free(pCsr->aBuffer);
pCsr->nSegment = 0;
pCsr->apSegment = 0;
@@ -159321,12 +181789,12 @@ SQLITE_PRIVATE void sqlite3Fts3SegReaderFinish(
** set *piEndBlock to the first value and *pnByte to the second.
*/
static void fts3ReadEndBlockField(
- sqlite3_stmt *pStmt,
+ tdsqlite3_stmt *pStmt,
int iCol,
i64 *piEndBlock,
i64 *pnByte
){
- const unsigned char *zText = sqlite3_column_text(pStmt, iCol);
+ const unsigned char *zText = tdsqlite3_column_text(pStmt, iCol);
if( zText ){
int i;
int iMul = 1;
@@ -159355,11 +181823,11 @@ static void fts3ReadEndBlockField(
*/
static int fts3PromoteSegments(
Fts3Table *p, /* FTS table handle */
- sqlite3_int64 iAbsLevel, /* Absolute level just updated */
- sqlite3_int64 nByte /* Size of new segment at iAbsLevel */
+ tdsqlite3_int64 iAbsLevel, /* Absolute level just updated */
+ tdsqlite3_int64 nByte /* Size of new segment at iAbsLevel */
){
int rc = SQLITE_OK;
- sqlite3_stmt *pRange;
+ tdsqlite3_stmt *pRange;
rc = fts3SqlStmt(p, SQL_SELECT_LEVEL_RANGE2, &pRange, 0);
@@ -159373,9 +181841,9 @@ static int fts3PromoteSegments(
** at least one such segment, and it is possible to determine that all
** such segments are smaller than nLimit bytes in size, they will be
** promoted to level iAbsLevel. */
- sqlite3_bind_int64(pRange, 1, iAbsLevel+1);
- sqlite3_bind_int64(pRange, 2, iLast);
- while( SQLITE_ROW==sqlite3_step(pRange) ){
+ tdsqlite3_bind_int64(pRange, 1, iAbsLevel+1);
+ tdsqlite3_bind_int64(pRange, 2, iLast);
+ while( SQLITE_ROW==tdsqlite3_step(pRange) ){
i64 nSize = 0, dummy;
fts3ReadEndBlockField(pRange, 2, &dummy, &nSize);
if( nSize<=0 || nSize>nLimit ){
@@ -159389,12 +181857,12 @@ static int fts3PromoteSegments(
}
bOk = 1;
}
- rc = sqlite3_reset(pRange);
+ rc = tdsqlite3_reset(pRange);
if( bOk ){
int iIdx = 0;
- sqlite3_stmt *pUpdate1 = 0;
- sqlite3_stmt *pUpdate2 = 0;
+ tdsqlite3_stmt *pUpdate1 = 0;
+ tdsqlite3_stmt *pUpdate2 = 0;
if( rc==SQLITE_OK ){
rc = fts3SqlStmt(p, SQL_UPDATE_LEVEL_IDX, &pUpdate1, 0);
@@ -159414,28 +181882,28 @@ static int fts3PromoteSegments(
** setting the "idx" fields as appropriate to keep them in the same
** order. The contents of level -1 (which is never used, except
** transiently here), will be moved back to level iAbsLevel below. */
- sqlite3_bind_int64(pRange, 1, iAbsLevel);
- while( SQLITE_ROW==sqlite3_step(pRange) ){
- sqlite3_bind_int(pUpdate1, 1, iIdx++);
- sqlite3_bind_int(pUpdate1, 2, sqlite3_column_int(pRange, 0));
- sqlite3_bind_int(pUpdate1, 3, sqlite3_column_int(pRange, 1));
- sqlite3_step(pUpdate1);
- rc = sqlite3_reset(pUpdate1);
+ tdsqlite3_bind_int64(pRange, 1, iAbsLevel);
+ while( SQLITE_ROW==tdsqlite3_step(pRange) ){
+ tdsqlite3_bind_int(pUpdate1, 1, iIdx++);
+ tdsqlite3_bind_int(pUpdate1, 2, tdsqlite3_column_int(pRange, 0));
+ tdsqlite3_bind_int(pUpdate1, 3, tdsqlite3_column_int(pRange, 1));
+ tdsqlite3_step(pUpdate1);
+ rc = tdsqlite3_reset(pUpdate1);
if( rc!=SQLITE_OK ){
- sqlite3_reset(pRange);
+ tdsqlite3_reset(pRange);
break;
}
}
}
if( rc==SQLITE_OK ){
- rc = sqlite3_reset(pRange);
+ rc = tdsqlite3_reset(pRange);
}
/* Move level -1 to level iAbsLevel */
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pUpdate2, 1, iAbsLevel);
- sqlite3_step(pUpdate2);
- rc = sqlite3_reset(pUpdate2);
+ tdsqlite3_bind_int64(pUpdate2, 1, iAbsLevel);
+ tdsqlite3_step(pUpdate2);
+ rc = tdsqlite3_reset(pUpdate2);
}
}
}
@@ -159463,7 +181931,7 @@ static int fts3SegmentMerge(
){
int rc; /* Return code */
int iIdx = 0; /* Index of new segment */
- sqlite3_int64 iNewLevel = 0; /* Level/index to create new segment at */
+ tdsqlite3_int64 iNewLevel = 0; /* Level/index to create new segment at */
SegmentWriter *pWriter = 0; /* Used to write the new, merged, segment */
Fts3SegFilter filter; /* Segment term filter condition */
Fts3MultiSegReader csr; /* Cursor to iterate through level(s) */
@@ -159477,7 +181945,7 @@ static int fts3SegmentMerge(
assert( iLevel<FTS3_SEGDIR_MAXLEVEL );
assert( iIndex>=0 && iIndex<p->nIndex );
- rc = sqlite3Fts3SegReaderCursor(p, iLangid, iIndex, iLevel, 0, 0, 1, 0, &csr);
+ rc = tdsqlite3Fts3SegReaderCursor(p, iLangid, iIndex, iLevel, 0, 0, 1, 0, &csr);
if( rc!=SQLITE_OK || csr.nSegment==0 ) goto finished;
if( iLevel!=FTS3_SEGCURSOR_PENDING ){
@@ -159510,22 +181978,24 @@ static int fts3SegmentMerge(
if( rc!=SQLITE_OK ) goto finished;
assert( csr.nSegment>0 );
- assert( iNewLevel>=getAbsoluteLevel(p, iLangid, iIndex, 0) );
- assert( iNewLevel<getAbsoluteLevel(p, iLangid, iIndex,FTS3_SEGDIR_MAXLEVEL) );
+ assert_fts3_nc( iNewLevel>=getAbsoluteLevel(p, iLangid, iIndex, 0) );
+ assert_fts3_nc(
+ iNewLevel<getAbsoluteLevel(p, iLangid, iIndex,FTS3_SEGDIR_MAXLEVEL)
+ );
memset(&filter, 0, sizeof(Fts3SegFilter));
filter.flags = FTS3_SEGMENT_REQUIRE_POS;
filter.flags |= (bIgnoreEmpty ? FTS3_SEGMENT_IGNORE_EMPTY : 0);
- rc = sqlite3Fts3SegReaderStart(p, &csr, &filter);
+ rc = tdsqlite3Fts3SegReaderStart(p, &csr, &filter);
while( SQLITE_OK==rc ){
- rc = sqlite3Fts3SegReaderStep(p, &csr);
+ rc = tdsqlite3Fts3SegReaderStep(p, &csr);
if( rc!=SQLITE_ROW ) break;
rc = fts3SegWriterAdd(p, &pWriter, 1,
csr.zTerm, csr.nTerm, csr.aDoclist, csr.nDoclist);
}
if( rc!=SQLITE_OK ) goto finished;
- assert( pWriter || bIgnoreEmpty );
+ assert_fts3_nc( pWriter || bIgnoreEmpty );
if( iLevel!=FTS3_SEGCURSOR_PENDING ){
rc = fts3DeleteSegdir(
@@ -159544,7 +182014,7 @@ static int fts3SegmentMerge(
finished:
fts3SegWriterFree(pWriter);
- sqlite3Fts3SegReaderFinish(&csr);
+ tdsqlite3Fts3SegReaderFinish(&csr);
return rc;
}
@@ -159552,7 +182022,7 @@ static int fts3SegmentMerge(
/*
** Flush the contents of pendingTerms to level 0 segments.
*/
-SQLITE_PRIVATE int sqlite3Fts3PendingTermsFlush(Fts3Table *p){
+SQLITE_PRIVATE int tdsqlite3Fts3PendingTermsFlush(Fts3Table *p){
int rc = SQLITE_OK;
int i;
@@ -159560,7 +182030,7 @@ SQLITE_PRIVATE int sqlite3Fts3PendingTermsFlush(Fts3Table *p){
rc = fts3SegmentMerge(p, p->iPrevLangid, i, FTS3_SEGCURSOR_PENDING);
if( rc==SQLITE_DONE ) rc = SQLITE_OK;
}
- sqlite3Fts3PendingTermsClear(p);
+ tdsqlite3Fts3PendingTermsClear(p);
/* Determine the auto-incr-merge setting if unknown. If enabled,
** estimate the number of leaf blocks of content to be written
@@ -159568,18 +182038,18 @@ SQLITE_PRIVATE int sqlite3Fts3PendingTermsFlush(Fts3Table *p){
if( rc==SQLITE_OK && p->bHasStat
&& p->nAutoincrmerge==0xff && p->nLeafAdd>0
){
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pStmt, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE);
- rc = sqlite3_step(pStmt);
+ tdsqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE);
+ rc = tdsqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
- p->nAutoincrmerge = sqlite3_column_int(pStmt, 0);
+ p->nAutoincrmerge = tdsqlite3_column_int(pStmt, 0);
if( p->nAutoincrmerge==1 ) p->nAutoincrmerge = 8;
}else if( rc==SQLITE_DONE ){
p->nAutoincrmerge = 0;
}
- rc = sqlite3_reset(pStmt);
+ rc = tdsqlite3_reset(pStmt);
}
}
return rc;
@@ -159596,7 +182066,7 @@ static void fts3EncodeIntArray(
){
int i, j;
for(i=j=0; i<N; i++){
- j += sqlite3Fts3PutVarint(&zBuf[j], (sqlite3_int64)a[i]);
+ j += tdsqlite3Fts3PutVarint(&zBuf[j], (tdsqlite3_int64)a[i]);
}
*pNBuf = j;
}
@@ -159610,14 +182080,16 @@ static void fts3DecodeIntArray(
const char *zBuf, /* The BLOB containing the varints */
int nBuf /* size of the BLOB */
){
- int i, j;
- UNUSED_PARAMETER(nBuf);
- for(i=j=0; i<N; i++){
- sqlite3_int64 x;
- j += sqlite3Fts3GetVarint(&zBuf[j], &x);
- assert(j<=nBuf);
- a[i] = (u32)(x & 0xffffffff);
+ int i = 0;
+ if( nBuf && (zBuf[nBuf-1]&0x80)==0 ){
+ int j;
+ for(i=j=0; i<N && j<nBuf; i++){
+ tdsqlite3_int64 x;
+ j += tdsqlite3Fts3GetVarint(&zBuf[j], &x);
+ a[i] = (u32)(x & 0xffffffff);
+ }
}
+ while( i<N ) a[i++] = 0;
}
/*
@@ -159632,11 +182104,11 @@ static void fts3InsertDocsize(
){
char *pBlob; /* The BLOB encoding of the document size */
int nBlob; /* Number of bytes in the BLOB */
- sqlite3_stmt *pStmt; /* Statement used to insert the encoding */
+ tdsqlite3_stmt *pStmt; /* Statement used to insert the encoding */
int rc; /* Result code from subfunctions */
if( *pRC ) return;
- pBlob = sqlite3_malloc( 10*p->nColumn );
+ pBlob = tdsqlite3_malloc64( 10*(tdsqlite3_int64)p->nColumn );
if( pBlob==0 ){
*pRC = SQLITE_NOMEM;
return;
@@ -159644,14 +182116,14 @@ static void fts3InsertDocsize(
fts3EncodeIntArray(p->nColumn, aSz, pBlob, &nBlob);
rc = fts3SqlStmt(p, SQL_REPLACE_DOCSIZE, &pStmt, 0);
if( rc ){
- sqlite3_free(pBlob);
+ tdsqlite3_free(pBlob);
*pRC = rc;
return;
}
- sqlite3_bind_int64(pStmt, 1, p->iPrevDocid);
- sqlite3_bind_blob(pStmt, 2, pBlob, nBlob, sqlite3_free);
- sqlite3_step(pStmt);
- *pRC = sqlite3_reset(pStmt);
+ tdsqlite3_bind_int64(pStmt, 1, p->iPrevDocid);
+ tdsqlite3_bind_blob(pStmt, 2, pBlob, nBlob, tdsqlite3_free);
+ tdsqlite3_step(pStmt);
+ *pRC = tdsqlite3_reset(pStmt);
}
/*
@@ -159679,14 +182151,14 @@ static void fts3UpdateDocTotals(
char *pBlob; /* Storage for BLOB written into %_stat */
int nBlob; /* Size of BLOB written into %_stat */
u32 *a; /* Array of integers that becomes the BLOB */
- sqlite3_stmt *pStmt; /* Statement for reading and writing */
+ tdsqlite3_stmt *pStmt; /* Statement for reading and writing */
int i; /* Loop counter */
int rc; /* Result code from subfunctions */
const int nStat = p->nColumn+2;
if( *pRC ) return;
- a = sqlite3_malloc( (sizeof(u32)+10)*nStat );
+ a = tdsqlite3_malloc64( (sizeof(u32)+10)*(tdsqlite3_int64)nStat );
if( a==0 ){
*pRC = SQLITE_NOMEM;
return;
@@ -159694,21 +182166,21 @@ static void fts3UpdateDocTotals(
pBlob = (char*)&a[nStat];
rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pStmt, 0);
if( rc ){
- sqlite3_free(a);
+ tdsqlite3_free(a);
*pRC = rc;
return;
}
- sqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL);
- if( sqlite3_step(pStmt)==SQLITE_ROW ){
+ tdsqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL);
+ if( tdsqlite3_step(pStmt)==SQLITE_ROW ){
fts3DecodeIntArray(nStat, a,
- sqlite3_column_blob(pStmt, 0),
- sqlite3_column_bytes(pStmt, 0));
+ tdsqlite3_column_blob(pStmt, 0),
+ tdsqlite3_column_bytes(pStmt, 0));
}else{
memset(a, 0, sizeof(u32)*(nStat) );
}
- rc = sqlite3_reset(pStmt);
+ rc = tdsqlite3_reset(pStmt);
if( rc!=SQLITE_OK ){
- sqlite3_free(a);
+ tdsqlite3_free(a);
*pRC = rc;
return;
}
@@ -159729,15 +182201,16 @@ static void fts3UpdateDocTotals(
fts3EncodeIntArray(nStat, a, pBlob, &nBlob);
rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pStmt, 0);
if( rc ){
- sqlite3_free(a);
+ tdsqlite3_free(a);
*pRC = rc;
return;
}
- sqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL);
- sqlite3_bind_blob(pStmt, 2, pBlob, nBlob, SQLITE_STATIC);
- sqlite3_step(pStmt);
- *pRC = sqlite3_reset(pStmt);
- sqlite3_free(a);
+ tdsqlite3_bind_int(pStmt, 1, FTS_STAT_DOCTOTAL);
+ tdsqlite3_bind_blob(pStmt, 2, pBlob, nBlob, SQLITE_STATIC);
+ tdsqlite3_step(pStmt);
+ *pRC = tdsqlite3_reset(pStmt);
+ tdsqlite3_bind_null(pStmt, 2);
+ tdsqlite3_free(a);
}
/*
@@ -159747,16 +182220,19 @@ static void fts3UpdateDocTotals(
static int fts3DoOptimize(Fts3Table *p, int bReturnDone){
int bSeenDone = 0;
int rc;
- sqlite3_stmt *pAllLangid = 0;
+ tdsqlite3_stmt *pAllLangid = 0;
- rc = fts3SqlStmt(p, SQL_SELECT_ALL_LANGID, &pAllLangid, 0);
+ rc = tdsqlite3Fts3PendingTermsFlush(p);
+ if( rc==SQLITE_OK ){
+ rc = fts3SqlStmt(p, SQL_SELECT_ALL_LANGID, &pAllLangid, 0);
+ }
if( rc==SQLITE_OK ){
int rc2;
- sqlite3_bind_int(pAllLangid, 1, p->iPrevLangid);
- sqlite3_bind_int(pAllLangid, 2, p->nIndex);
- while( sqlite3_step(pAllLangid)==SQLITE_ROW ){
+ tdsqlite3_bind_int(pAllLangid, 1, p->iPrevLangid);
+ tdsqlite3_bind_int(pAllLangid, 2, p->nIndex);
+ while( tdsqlite3_step(pAllLangid)==SQLITE_ROW ){
int i;
- int iLangid = sqlite3_column_int(pAllLangid, 0);
+ int iLangid = tdsqlite3_column_int(pAllLangid, 0);
for(i=0; rc==SQLITE_OK && i<p->nIndex; i++){
rc = fts3SegmentMerge(p, iLangid, i, FTS3_SEGCURSOR_ALL);
if( rc==SQLITE_DONE ){
@@ -159765,12 +182241,11 @@ static int fts3DoOptimize(Fts3Table *p, int bReturnDone){
}
}
}
- rc2 = sqlite3_reset(pAllLangid);
+ rc2 = tdsqlite3_reset(pAllLangid);
if( rc==SQLITE_OK ) rc = rc2;
}
- sqlite3Fts3SegmentsClose(p);
- sqlite3Fts3PendingTermsClear(p);
+ tdsqlite3Fts3SegmentsClose(p);
return (rc==SQLITE_OK && bReturnDone && bSeenDone) ? SQLITE_DONE : rc;
}
@@ -159793,21 +182268,21 @@ static int fts3DoRebuild(Fts3Table *p){
u32 *aSz = 0;
u32 *aSzIns = 0;
u32 *aSzDel = 0;
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
int nEntry = 0;
/* Compose and prepare an SQL statement to loop through the content table */
- char *zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist);
+ char *zSql = tdsqlite3_mprintf("SELECT %s" , p->zReadExprlist);
if( !zSql ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
+ rc = tdsqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ tdsqlite3_free(zSql);
}
if( rc==SQLITE_OK ){
- int nByte = sizeof(u32) * (p->nColumn+1)*3;
- aSz = (u32 *)sqlite3_malloc(nByte);
+ tdsqlite3_int64 nByte = sizeof(u32) * ((tdsqlite3_int64)p->nColumn+1)*3;
+ aSz = (u32 *)tdsqlite3_malloc64(nByte);
if( aSz==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -159817,23 +182292,23 @@ static int fts3DoRebuild(Fts3Table *p){
}
}
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ while( rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pStmt) ){
int iCol;
int iLangid = langidFromSelect(p, pStmt);
- rc = fts3PendingTermsDocid(p, 0, iLangid, sqlite3_column_int64(pStmt, 0));
+ rc = fts3PendingTermsDocid(p, 0, iLangid, tdsqlite3_column_int64(pStmt, 0));
memset(aSz, 0, sizeof(aSz[0]) * (p->nColumn+1));
for(iCol=0; rc==SQLITE_OK && iCol<p->nColumn; iCol++){
if( p->abNotindexed[iCol]==0 ){
- const char *z = (const char *) sqlite3_column_text(pStmt, iCol+1);
+ const char *z = (const char *) tdsqlite3_column_text(pStmt, iCol+1);
rc = fts3PendingTermsAdd(p, iLangid, z, iCol, &aSz[iCol]);
- aSz[p->nColumn] += sqlite3_column_bytes(pStmt, iCol+1);
+ aSz[p->nColumn] += tdsqlite3_column_bytes(pStmt, iCol+1);
}
}
if( p->bHasDocsize ){
fts3InsertDocsize(&rc, p, aSz);
}
if( rc!=SQLITE_OK ){
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
pStmt = 0;
}else{
nEntry++;
@@ -159845,10 +182320,10 @@ static int fts3DoRebuild(Fts3Table *p){
if( p->bFts4 ){
fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nEntry);
}
- sqlite3_free(aSz);
+ tdsqlite3_free(aSz);
if( pStmt ){
- int rc2 = sqlite3_finalize(pStmt);
+ int rc2 = tdsqlite3_finalize(pStmt);
if( rc==SQLITE_OK ){
rc = rc2;
}
@@ -159867,18 +182342,18 @@ static int fts3DoRebuild(Fts3Table *p){
*/
static int fts3IncrmergeCsr(
Fts3Table *p, /* FTS3 table handle */
- sqlite3_int64 iAbsLevel, /* Absolute level to open */
+ tdsqlite3_int64 iAbsLevel, /* Absolute level to open */
int nSeg, /* Number of segments to merge */
Fts3MultiSegReader *pCsr /* Cursor object to populate */
){
int rc; /* Return Code */
- sqlite3_stmt *pStmt = 0; /* Statement used to read %_segdir entry */
- int nByte; /* Bytes allocated at pCsr->apSegment[] */
+ tdsqlite3_stmt *pStmt = 0; /* Statement used to read %_segdir entry */
+ tdsqlite3_int64 nByte; /* Bytes allocated at pCsr->apSegment[] */
/* Allocate space for the Fts3MultiSegReader.aCsr[] array */
memset(pCsr, 0, sizeof(*pCsr));
nByte = sizeof(Fts3SegReader *) * nSeg;
- pCsr->apSegment = (Fts3SegReader **)sqlite3_malloc(nByte);
+ pCsr->apSegment = (Fts3SegReader **)tdsqlite3_malloc64(nByte);
if( pCsr->apSegment==0 ){
rc = SQLITE_NOMEM;
@@ -159889,20 +182364,20 @@ static int fts3IncrmergeCsr(
if( rc==SQLITE_OK ){
int i;
int rc2;
- sqlite3_bind_int64(pStmt, 1, iAbsLevel);
+ tdsqlite3_bind_int64(pStmt, 1, iAbsLevel);
assert( pCsr->nSegment==0 );
- for(i=0; rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW && i<nSeg; i++){
- rc = sqlite3Fts3SegReaderNew(i, 0,
- sqlite3_column_int64(pStmt, 1), /* segdir.start_block */
- sqlite3_column_int64(pStmt, 2), /* segdir.leaves_end_block */
- sqlite3_column_int64(pStmt, 3), /* segdir.end_block */
- sqlite3_column_blob(pStmt, 4), /* segdir.root */
- sqlite3_column_bytes(pStmt, 4), /* segdir.root */
+ for(i=0; rc==SQLITE_OK && tdsqlite3_step(pStmt)==SQLITE_ROW && i<nSeg; i++){
+ rc = tdsqlite3Fts3SegReaderNew(i, 0,
+ tdsqlite3_column_int64(pStmt, 1), /* segdir.start_block */
+ tdsqlite3_column_int64(pStmt, 2), /* segdir.leaves_end_block */
+ tdsqlite3_column_int64(pStmt, 3), /* segdir.end_block */
+ tdsqlite3_column_blob(pStmt, 4), /* segdir.root */
+ tdsqlite3_column_bytes(pStmt, 4), /* segdir.root */
&pCsr->apSegment[i]
);
pCsr->nSegment++;
}
- rc2 = sqlite3_reset(pStmt);
+ rc2 = tdsqlite3_reset(pStmt);
if( rc==SQLITE_OK ) rc = rc2;
}
@@ -159931,7 +182406,7 @@ struct Blob {
** nodes (blocks).
*/
struct NodeWriter {
- sqlite3_int64 iBlock; /* Current block id */
+ tdsqlite3_int64 iBlock; /* Current block id */
Blob key; /* Last key written to the current block */
Blob block; /* Current block image */
};
@@ -159943,11 +182418,11 @@ struct NodeWriter {
struct IncrmergeWriter {
int nLeafEst; /* Space allocated for leaf blocks */
int nWork; /* Number of leaf pages flushed */
- sqlite3_int64 iAbsLevel; /* Absolute level of input segments */
+ tdsqlite3_int64 iAbsLevel; /* Absolute level of input segments */
int iIdx; /* Index of *output* segment in iAbsLevel+1 */
- sqlite3_int64 iStart; /* Block number of first allocated block */
- sqlite3_int64 iEnd; /* Block number of last allocated block */
- sqlite3_int64 nLeafData; /* Bytes of leaf page data so far */
+ tdsqlite3_int64 iStart; /* Block number of first allocated block */
+ tdsqlite3_int64 iEnd; /* Block number of last allocated block */
+ tdsqlite3_int64 nLeafData; /* Bytes of leaf page data so far */
u8 bNoLeafData; /* If true, store 0 for segment size */
NodeWriter aNodeWriter[FTS_MAX_APPENDABLE_HEIGHT];
};
@@ -159966,7 +182441,7 @@ struct NodeReader {
int iOff; /* Current offset within aNode[] */
/* Output variables. Containing the current node entry. */
- sqlite3_int64 iChild; /* Pointer to child node */
+ tdsqlite3_int64 iChild; /* Pointer to child node */
Blob term; /* Current term */
const char *aDoclist; /* Pointer to doclist */
int nDoclist; /* Size of doclist in bytes */
@@ -159984,7 +182459,7 @@ struct NodeReader {
static void blobGrowBuffer(Blob *pBlob, int nMin, int *pRc){
if( *pRc==SQLITE_OK && nMin>pBlob->nAlloc ){
int nAlloc = nMin;
- char *a = (char *)sqlite3_realloc(pBlob->a, nAlloc);
+ char *a = (char *)tdsqlite3_realloc(pBlob->a, nAlloc);
if( a ){
pBlob->nAlloc = nAlloc;
pBlob->a = a;
@@ -160021,6 +182496,9 @@ static int nodeReaderNext(NodeReader *p){
}
p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &nSuffix);
+ if( nPrefix>p->term.n || nSuffix>p->nNode-p->iOff || nSuffix==0 ){
+ return FTS_CORRUPT_VTAB;
+ }
blobGrowBuffer(&p->term, nPrefix+nSuffix, &rc);
if( rc==SQLITE_OK ){
memcpy(&p->term.a[nPrefix], &p->aNode[p->iOff], nSuffix);
@@ -160028,14 +182506,16 @@ static int nodeReaderNext(NodeReader *p){
p->iOff += nSuffix;
if( p->iChild==0 ){
p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &p->nDoclist);
+ if( (p->nNode-p->iOff)<p->nDoclist ){
+ return FTS_CORRUPT_VTAB;
+ }
p->aDoclist = &p->aNode[p->iOff];
p->iOff += p->nDoclist;
}
}
}
- assert( p->iOff<=p->nNode );
-
+ assert_fts3_nc( p->iOff<=p->nNode );
return rc;
}
@@ -160043,7 +182523,7 @@ static int nodeReaderNext(NodeReader *p){
** Release all dynamic resources held by node-reader object *p.
*/
static void nodeReaderRelease(NodeReader *p){
- sqlite3_free(p->term.a);
+ tdsqlite3_free(p->term.a);
}
/*
@@ -160059,14 +182539,14 @@ static int nodeReaderInit(NodeReader *p, const char *aNode, int nNode){
p->nNode = nNode;
/* Figure out if this is a leaf or an internal node. */
- if( p->aNode[0] ){
+ if( aNode && aNode[0] ){
/* An internal node. */
- p->iOff = 1 + sqlite3Fts3GetVarint(&p->aNode[1], &p->iChild);
+ p->iOff = 1 + tdsqlite3Fts3GetVarint(&p->aNode[1], &p->iChild);
}else{
p->iOff = 1;
}
- return nodeReaderNext(p);
+ return aNode ? nodeReaderNext(p) : SQLITE_OK;
}
/*
@@ -160085,12 +182565,12 @@ static int fts3IncrmergePush(
const char *zTerm, /* Term to write to internal node */
int nTerm /* Bytes at zTerm */
){
- sqlite3_int64 iPtr = pWriter->aNodeWriter[0].iBlock;
+ tdsqlite3_int64 iPtr = pWriter->aNodeWriter[0].iBlock;
int iLayer;
assert( nTerm>0 );
for(iLayer=1; ALWAYS(iLayer<FTS_MAX_APPENDABLE_HEIGHT); iLayer++){
- sqlite3_int64 iNextPtr = 0;
+ tdsqlite3_int64 iNextPtr = 0;
NodeWriter *pNode = &pWriter->aNodeWriter[iLayer];
int rc = SQLITE_OK;
int nPrefix;
@@ -160103,8 +182583,9 @@ static int fts3IncrmergePush(
** be added to. */
nPrefix = fts3PrefixCompress(pNode->key.a, pNode->key.n, zTerm, nTerm);
nSuffix = nTerm - nPrefix;
- nSpace = sqlite3Fts3VarintLen(nPrefix);
- nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix;
+ if(nSuffix<=0 ) return FTS_CORRUPT_VTAB;
+ nSpace = tdsqlite3Fts3VarintLen(nPrefix);
+ nSpace += tdsqlite3Fts3VarintLen(nSuffix) + nSuffix;
if( pNode->key.n==0 || (pNode->block.n + nSpace)<=p->nNodeSize ){
/* If the current node of layer iLayer contains zero keys, or if adding
@@ -160116,7 +182597,7 @@ static int fts3IncrmergePush(
blobGrowBuffer(pBlk, p->nNodeSize, &rc);
if( rc==SQLITE_OK ){
pBlk->a[0] = (char)iLayer;
- pBlk->n = 1 + sqlite3Fts3PutVarint(&pBlk->a[1], iPtr);
+ pBlk->n = 1 + tdsqlite3Fts3PutVarint(&pBlk->a[1], iPtr);
}
}
blobGrowBuffer(pBlk, pBlk->n + nSpace, &rc);
@@ -160124,9 +182605,9 @@ static int fts3IncrmergePush(
if( rc==SQLITE_OK ){
if( pNode->key.n ){
- pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nPrefix);
+ pBlk->n += tdsqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nPrefix);
}
- pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nSuffix);
+ pBlk->n += tdsqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nSuffix);
memcpy(&pBlk->a[pBlk->n], &zTerm[nPrefix], nSuffix);
pBlk->n += nSuffix;
@@ -160141,7 +182622,7 @@ static int fts3IncrmergePush(
assert( pNode->block.nAlloc>=p->nNodeSize );
pNode->block.a[0] = (char)iLayer;
- pNode->block.n = 1 + sqlite3Fts3PutVarint(&pNode->block.a[1], iPtr+1);
+ pNode->block.n = 1 + tdsqlite3Fts3PutVarint(&pNode->block.a[1], iPtr+1);
iNextPtr = pNode->iBlock;
pNode->iBlock++;
@@ -160196,25 +182677,26 @@ static int fts3AppendToNode(
/* Node must have already been started. There must be a doclist for a
** leaf node, and there must not be a doclist for an internal node. */
assert( pNode->n>0 );
- assert( (pNode->a[0]=='\0')==(aDoclist!=0) );
+ assert_fts3_nc( (pNode->a[0]=='\0')==(aDoclist!=0) );
blobGrowBuffer(pPrev, nTerm, &rc);
if( rc!=SQLITE_OK ) return rc;
nPrefix = fts3PrefixCompress(pPrev->a, pPrev->n, zTerm, nTerm);
nSuffix = nTerm - nPrefix;
+ if( nSuffix<=0 ) return FTS_CORRUPT_VTAB;
memcpy(pPrev->a, zTerm, nTerm);
pPrev->n = nTerm;
if( bFirst==0 ){
- pNode->n += sqlite3Fts3PutVarint(&pNode->a[pNode->n], nPrefix);
+ pNode->n += tdsqlite3Fts3PutVarint(&pNode->a[pNode->n], nPrefix);
}
- pNode->n += sqlite3Fts3PutVarint(&pNode->a[pNode->n], nSuffix);
+ pNode->n += tdsqlite3Fts3PutVarint(&pNode->a[pNode->n], nSuffix);
memcpy(&pNode->a[pNode->n], &zTerm[nPrefix], nSuffix);
pNode->n += nSuffix;
if( aDoclist ){
- pNode->n += sqlite3Fts3PutVarint(&pNode->a[pNode->n], nDoclist);
+ pNode->n += tdsqlite3Fts3PutVarint(&pNode->a[pNode->n], nDoclist);
memcpy(&pNode->a[pNode->n], aDoclist, nDoclist);
pNode->n += nDoclist;
}
@@ -160249,9 +182731,9 @@ static int fts3IncrmergeAppend(
nPrefix = fts3PrefixCompress(pLeaf->key.a, pLeaf->key.n, zTerm, nTerm);
nSuffix = nTerm - nPrefix;
- nSpace = sqlite3Fts3VarintLen(nPrefix);
- nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix;
- nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist;
+ nSpace = tdsqlite3Fts3VarintLen(nPrefix);
+ nSpace += tdsqlite3Fts3VarintLen(nSuffix) + nSuffix;
+ nSpace += tdsqlite3Fts3VarintLen(nDoclist) + nDoclist;
/* If the current block is not empty, and if adding this term/doclist
** to the current block would make it larger than Fts3Table.nNodeSize
@@ -160283,8 +182765,8 @@ static int fts3IncrmergeAppend(
nSuffix = nTerm;
nSpace = 1;
- nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix;
- nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist;
+ nSpace += tdsqlite3Fts3VarintLen(nSuffix) + nSuffix;
+ nSpace += tdsqlite3Fts3VarintLen(nDoclist) + nDoclist;
}
pWriter->nLeafData += nSpace;
@@ -160334,8 +182816,8 @@ static void fts3IncrmergeRelease(
if( pNode->block.n>0 ) break;
assert( *pRc || pNode->block.nAlloc==0 );
assert( *pRc || pNode->key.nAlloc==0 );
- sqlite3_free(pNode->block.a);
- sqlite3_free(pNode->key.a);
+ tdsqlite3_free(pNode->block.a);
+ tdsqlite3_free(pNode->key.a);
}
/* Empty output segment. This is a no-op. */
@@ -160361,7 +182843,7 @@ static void fts3IncrmergeRelease(
blobGrowBuffer(pBlock, 1 + FTS3_VARINT_MAX, &rc);
if( rc==SQLITE_OK ){
pBlock->a[0] = 0x01;
- pBlock->n = 1 + sqlite3Fts3PutVarint(
+ pBlock->n = 1 + tdsqlite3Fts3PutVarint(
&pBlock->a[1], pWriter->aNodeWriter[0].iBlock
);
}
@@ -160375,8 +182857,8 @@ static void fts3IncrmergeRelease(
if( pNode->block.n>0 && rc==SQLITE_OK ){
rc = fts3WriteSegment(p, pNode->iBlock, pNode->block.a, pNode->block.n);
}
- sqlite3_free(pNode->block.a);
- sqlite3_free(pNode->key.a);
+ tdsqlite3_free(pNode->block.a);
+ tdsqlite3_free(pNode->key.a);
}
/* Write the %_segdir record. */
@@ -160391,8 +182873,8 @@ static void fts3IncrmergeRelease(
pRoot->block.a, pRoot->block.n /* root */
);
}
- sqlite3_free(pRoot->block.a);
- sqlite3_free(pRoot->key.a);
+ tdsqlite3_free(pRoot->block.a);
+ tdsqlite3_free(pRoot->key.a);
*pRc = rc;
}
@@ -160412,7 +182894,7 @@ static int fts3TermCmp(
int nCmp = MIN(nLhs, nRhs);
int res;
- res = memcmp(zLhs, zRhs, nCmp);
+ res = (nCmp ? memcmp(zLhs, zRhs, nCmp) : 0);
if( res==0 ) res = nLhs - nRhs;
return res;
@@ -160431,16 +182913,16 @@ static int fts3TermCmp(
** is, then a NULL entry has been inserted into the %_segments table
** with blockid %_segdir.end_block.
*/
-static int fts3IsAppendable(Fts3Table *p, sqlite3_int64 iEnd, int *pbRes){
+static int fts3IsAppendable(Fts3Table *p, tdsqlite3_int64 iEnd, int *pbRes){
int bRes = 0; /* Result to set *pbRes to */
- sqlite3_stmt *pCheck = 0; /* Statement to query database with */
+ tdsqlite3_stmt *pCheck = 0; /* Statement to query database with */
int rc; /* Return code */
rc = fts3SqlStmt(p, SQL_SEGMENT_IS_APPENDABLE, &pCheck, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pCheck, 1, iEnd);
- if( SQLITE_ROW==sqlite3_step(pCheck) ) bRes = 1;
- rc = sqlite3_reset(pCheck);
+ tdsqlite3_bind_int64(pCheck, 1, iEnd);
+ if( SQLITE_ROW==tdsqlite3_step(pCheck) ) bRes = 1;
+ rc = tdsqlite3_reset(pCheck);
}
*pbRes = bRes;
@@ -160464,40 +182946,44 @@ static int fts3IsAppendable(Fts3Table *p, sqlite3_int64 iEnd, int *pbRes){
*/
static int fts3IncrmergeLoad(
Fts3Table *p, /* Fts3 table handle */
- sqlite3_int64 iAbsLevel, /* Absolute level of input segments */
+ tdsqlite3_int64 iAbsLevel, /* Absolute level of input segments */
int iIdx, /* Index of candidate output segment */
const char *zKey, /* First key to write */
int nKey, /* Number of bytes in nKey */
IncrmergeWriter *pWriter /* Populate this object */
){
int rc; /* Return code */
- sqlite3_stmt *pSelect = 0; /* SELECT to read %_segdir entry */
+ tdsqlite3_stmt *pSelect = 0; /* SELECT to read %_segdir entry */
rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR, &pSelect, 0);
if( rc==SQLITE_OK ){
- sqlite3_int64 iStart = 0; /* Value of %_segdir.start_block */
- sqlite3_int64 iLeafEnd = 0; /* Value of %_segdir.leaves_end_block */
- sqlite3_int64 iEnd = 0; /* Value of %_segdir.end_block */
+ tdsqlite3_int64 iStart = 0; /* Value of %_segdir.start_block */
+ tdsqlite3_int64 iLeafEnd = 0; /* Value of %_segdir.leaves_end_block */
+ tdsqlite3_int64 iEnd = 0; /* Value of %_segdir.end_block */
const char *aRoot = 0; /* Pointer to %_segdir.root buffer */
int nRoot = 0; /* Size of aRoot[] in bytes */
- int rc2; /* Return code from sqlite3_reset() */
+ int rc2; /* Return code from tdsqlite3_reset() */
int bAppendable = 0; /* Set to true if segment is appendable */
/* Read the %_segdir entry for index iIdx absolute level (iAbsLevel+1) */
- sqlite3_bind_int64(pSelect, 1, iAbsLevel+1);
- sqlite3_bind_int(pSelect, 2, iIdx);
- if( sqlite3_step(pSelect)==SQLITE_ROW ){
- iStart = sqlite3_column_int64(pSelect, 1);
- iLeafEnd = sqlite3_column_int64(pSelect, 2);
+ tdsqlite3_bind_int64(pSelect, 1, iAbsLevel+1);
+ tdsqlite3_bind_int(pSelect, 2, iIdx);
+ if( tdsqlite3_step(pSelect)==SQLITE_ROW ){
+ iStart = tdsqlite3_column_int64(pSelect, 1);
+ iLeafEnd = tdsqlite3_column_int64(pSelect, 2);
fts3ReadEndBlockField(pSelect, 3, &iEnd, &pWriter->nLeafData);
if( pWriter->nLeafData<0 ){
pWriter->nLeafData = pWriter->nLeafData * -1;
}
pWriter->bNoLeafData = (pWriter->nLeafData==0);
- nRoot = sqlite3_column_bytes(pSelect, 4);
- aRoot = sqlite3_column_blob(pSelect, 4);
+ nRoot = tdsqlite3_column_bytes(pSelect, 4);
+ aRoot = tdsqlite3_column_blob(pSelect, 4);
+ if( aRoot==0 ){
+ tdsqlite3_reset(pSelect);
+ return nRoot ? SQLITE_NOMEM : FTS_CORRUPT_VTAB;
+ }
}else{
- return sqlite3_reset(pSelect);
+ return tdsqlite3_reset(pSelect);
}
/* Check for the zero-length marker in the %_segments table */
@@ -160508,7 +182994,7 @@ static int fts3IncrmergeLoad(
char *aLeaf = 0;
int nLeaf = 0;
- rc = sqlite3Fts3ReadBlock(p, iLeafEnd, &aLeaf, &nLeaf, 0);
+ rc = tdsqlite3Fts3ReadBlock(p, iLeafEnd, &aLeaf, &nLeaf, 0);
if( rc==SQLITE_OK ){
NodeReader reader;
for(rc = nodeReaderInit(&reader, aLeaf, nLeaf);
@@ -160522,7 +183008,7 @@ static int fts3IncrmergeLoad(
}
nodeReaderRelease(&reader);
}
- sqlite3_free(aLeaf);
+ tdsqlite3_free(aLeaf);
}
if( rc==SQLITE_OK && bAppendable ){
@@ -160531,6 +183017,10 @@ static int fts3IncrmergeLoad(
int i;
int nHeight = (int)aRoot[0];
NodeWriter *pNode;
+ if( nHeight<1 || nHeight>FTS_MAX_APPENDABLE_HEIGHT ){
+ tdsqlite3_reset(pSelect);
+ return FTS_CORRUPT_VTAB;
+ }
pWriter->nLeafEst = (int)((iEnd - iStart) + 1)/FTS_MAX_APPENDABLE_HEIGHT;
pWriter->iStart = iStart;
@@ -160544,41 +183034,49 @@ static int fts3IncrmergeLoad(
pNode = &pWriter->aNodeWriter[nHeight];
pNode->iBlock = pWriter->iStart + pWriter->nLeafEst*nHeight;
- blobGrowBuffer(&pNode->block, MAX(nRoot, p->nNodeSize), &rc);
+ blobGrowBuffer(&pNode->block,
+ MAX(nRoot, p->nNodeSize)+FTS3_NODE_PADDING, &rc
+ );
if( rc==SQLITE_OK ){
memcpy(pNode->block.a, aRoot, nRoot);
pNode->block.n = nRoot;
+ memset(&pNode->block.a[nRoot], 0, FTS3_NODE_PADDING);
}
for(i=nHeight; i>=0 && rc==SQLITE_OK; i--){
NodeReader reader;
pNode = &pWriter->aNodeWriter[i];
- rc = nodeReaderInit(&reader, pNode->block.a, pNode->block.n);
- while( reader.aNode && rc==SQLITE_OK ) rc = nodeReaderNext(&reader);
- blobGrowBuffer(&pNode->key, reader.term.n, &rc);
- if( rc==SQLITE_OK ){
- memcpy(pNode->key.a, reader.term.a, reader.term.n);
- pNode->key.n = reader.term.n;
- if( i>0 ){
- char *aBlock = 0;
- int nBlock = 0;
- pNode = &pWriter->aNodeWriter[i-1];
- pNode->iBlock = reader.iChild;
- rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock, 0);
- blobGrowBuffer(&pNode->block, MAX(nBlock, p->nNodeSize), &rc);
- if( rc==SQLITE_OK ){
- memcpy(pNode->block.a, aBlock, nBlock);
- pNode->block.n = nBlock;
+ if( pNode->block.a){
+ rc = nodeReaderInit(&reader, pNode->block.a, pNode->block.n);
+ while( reader.aNode && rc==SQLITE_OK ) rc = nodeReaderNext(&reader);
+ blobGrowBuffer(&pNode->key, reader.term.n, &rc);
+ if( rc==SQLITE_OK ){
+ memcpy(pNode->key.a, reader.term.a, reader.term.n);
+ pNode->key.n = reader.term.n;
+ if( i>0 ){
+ char *aBlock = 0;
+ int nBlock = 0;
+ pNode = &pWriter->aNodeWriter[i-1];
+ pNode->iBlock = reader.iChild;
+ rc = tdsqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock, 0);
+ blobGrowBuffer(&pNode->block,
+ MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc
+ );
+ if( rc==SQLITE_OK ){
+ memcpy(pNode->block.a, aBlock, nBlock);
+ pNode->block.n = nBlock;
+ memset(&pNode->block.a[nBlock], 0, FTS3_NODE_PADDING);
+ }
+ tdsqlite3_free(aBlock);
}
- sqlite3_free(aBlock);
}
}
nodeReaderRelease(&reader);
}
}
- rc2 = sqlite3_reset(pSelect);
+ rc2 = tdsqlite3_reset(pSelect);
if( rc==SQLITE_OK ) rc = rc2;
}
@@ -160596,18 +183094,18 @@ static int fts3IncrmergeLoad(
*/
static int fts3IncrmergeOutputIdx(
Fts3Table *p, /* FTS Table handle */
- sqlite3_int64 iAbsLevel, /* Absolute index of input segments */
+ tdsqlite3_int64 iAbsLevel, /* Absolute index of input segments */
int *piIdx /* OUT: Next free index at iAbsLevel+1 */
){
int rc;
- sqlite3_stmt *pOutputIdx = 0; /* SQL used to find output index */
+ tdsqlite3_stmt *pOutputIdx = 0; /* SQL used to find output index */
rc = fts3SqlStmt(p, SQL_NEXT_SEGMENT_INDEX, &pOutputIdx, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pOutputIdx, 1, iAbsLevel+1);
- sqlite3_step(pOutputIdx);
- *piIdx = sqlite3_column_int(pOutputIdx, 0);
- rc = sqlite3_reset(pOutputIdx);
+ tdsqlite3_bind_int64(pOutputIdx, 1, iAbsLevel+1);
+ tdsqlite3_step(pOutputIdx);
+ *piIdx = tdsqlite3_column_int(pOutputIdx, 0);
+ rc = tdsqlite3_reset(pOutputIdx);
}
return rc;
@@ -160641,7 +183139,7 @@ static int fts3IncrmergeOutputIdx(
*/
static int fts3IncrmergeWriter(
Fts3Table *p, /* Fts3 table handle */
- sqlite3_int64 iAbsLevel, /* Absolute level of input segments */
+ tdsqlite3_int64 iAbsLevel, /* Absolute level of input segments */
int iIdx, /* Index of new output segment */
Fts3MultiSegReader *pCsr, /* Cursor that data will be read from */
IncrmergeWriter *pWriter /* Populate this object */
@@ -160649,30 +183147,30 @@ static int fts3IncrmergeWriter(
int rc; /* Return Code */
int i; /* Iterator variable */
int nLeafEst = 0; /* Blocks allocated for leaf nodes */
- sqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */
- sqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */
+ tdsqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */
+ tdsqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */
/* Calculate nLeafEst. */
rc = fts3SqlStmt(p, SQL_MAX_LEAF_NODE_ESTIMATE, &pLeafEst, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pLeafEst, 1, iAbsLevel);
- sqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment);
- if( SQLITE_ROW==sqlite3_step(pLeafEst) ){
- nLeafEst = sqlite3_column_int(pLeafEst, 0);
+ tdsqlite3_bind_int64(pLeafEst, 1, iAbsLevel);
+ tdsqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment);
+ if( SQLITE_ROW==tdsqlite3_step(pLeafEst) ){
+ nLeafEst = tdsqlite3_column_int(pLeafEst, 0);
}
- rc = sqlite3_reset(pLeafEst);
+ rc = tdsqlite3_reset(pLeafEst);
}
if( rc!=SQLITE_OK ) return rc;
/* Calculate the first block to use in the output segment */
rc = fts3SqlStmt(p, SQL_NEXT_SEGMENTS_ID, &pFirstBlock, 0);
if( rc==SQLITE_OK ){
- if( SQLITE_ROW==sqlite3_step(pFirstBlock) ){
- pWriter->iStart = sqlite3_column_int64(pFirstBlock, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pFirstBlock) ){
+ pWriter->iStart = tdsqlite3_column_int64(pFirstBlock, 0);
pWriter->iEnd = pWriter->iStart - 1;
pWriter->iEnd += nLeafEst * FTS_MAX_APPENDABLE_HEIGHT;
}
- rc = sqlite3_reset(pFirstBlock);
+ rc = tdsqlite3_reset(pFirstBlock);
}
if( rc!=SQLITE_OK ) return rc;
@@ -160706,18 +183204,18 @@ static int fts3IncrmergeWriter(
*/
static int fts3RemoveSegdirEntry(
Fts3Table *p, /* FTS3 table handle */
- sqlite3_int64 iAbsLevel, /* Absolute level to delete from */
+ tdsqlite3_int64 iAbsLevel, /* Absolute level to delete from */
int iIdx /* Index of %_segdir entry to delete */
){
int rc; /* Return code */
- sqlite3_stmt *pDelete = 0; /* DELETE statement */
+ tdsqlite3_stmt *pDelete = 0; /* DELETE statement */
rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_ENTRY, &pDelete, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pDelete, 1, iAbsLevel);
- sqlite3_bind_int(pDelete, 2, iIdx);
- sqlite3_step(pDelete);
- rc = sqlite3_reset(pDelete);
+ tdsqlite3_bind_int64(pDelete, 1, iAbsLevel);
+ tdsqlite3_bind_int(pDelete, 2, iIdx);
+ tdsqlite3_step(pDelete);
+ rc = tdsqlite3_reset(pDelete);
}
return rc;
@@ -160730,34 +183228,34 @@ static int fts3RemoveSegdirEntry(
*/
static int fts3RepackSegdirLevel(
Fts3Table *p, /* FTS3 table handle */
- sqlite3_int64 iAbsLevel /* Absolute level to repack */
+ tdsqlite3_int64 iAbsLevel /* Absolute level to repack */
){
int rc; /* Return code */
int *aIdx = 0; /* Array of remaining idx values */
int nIdx = 0; /* Valid entries in aIdx[] */
int nAlloc = 0; /* Allocated size of aIdx[] */
int i; /* Iterator variable */
- sqlite3_stmt *pSelect = 0; /* Select statement to read idx values */
- sqlite3_stmt *pUpdate = 0; /* Update statement to modify idx values */
+ tdsqlite3_stmt *pSelect = 0; /* Select statement to read idx values */
+ tdsqlite3_stmt *pUpdate = 0; /* Update statement to modify idx values */
rc = fts3SqlStmt(p, SQL_SELECT_INDEXES, &pSelect, 0);
if( rc==SQLITE_OK ){
int rc2;
- sqlite3_bind_int64(pSelect, 1, iAbsLevel);
- while( SQLITE_ROW==sqlite3_step(pSelect) ){
+ tdsqlite3_bind_int64(pSelect, 1, iAbsLevel);
+ while( SQLITE_ROW==tdsqlite3_step(pSelect) ){
if( nIdx>=nAlloc ){
int *aNew;
nAlloc += 16;
- aNew = sqlite3_realloc(aIdx, nAlloc*sizeof(int));
+ aNew = tdsqlite3_realloc(aIdx, nAlloc*sizeof(int));
if( !aNew ){
rc = SQLITE_NOMEM;
break;
}
aIdx = aNew;
}
- aIdx[nIdx++] = sqlite3_column_int(pSelect, 0);
+ aIdx[nIdx++] = tdsqlite3_column_int(pSelect, 0);
}
- rc2 = sqlite3_reset(pSelect);
+ rc2 = tdsqlite3_reset(pSelect);
if( rc==SQLITE_OK ) rc = rc2;
}
@@ -160765,30 +183263,30 @@ static int fts3RepackSegdirLevel(
rc = fts3SqlStmt(p, SQL_SHIFT_SEGDIR_ENTRY, &pUpdate, 0);
}
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pUpdate, 2, iAbsLevel);
+ tdsqlite3_bind_int64(pUpdate, 2, iAbsLevel);
}
assert( p->bIgnoreSavepoint==0 );
p->bIgnoreSavepoint = 1;
for(i=0; rc==SQLITE_OK && i<nIdx; i++){
if( aIdx[i]!=i ){
- sqlite3_bind_int(pUpdate, 3, aIdx[i]);
- sqlite3_bind_int(pUpdate, 1, i);
- sqlite3_step(pUpdate);
- rc = sqlite3_reset(pUpdate);
+ tdsqlite3_bind_int(pUpdate, 3, aIdx[i]);
+ tdsqlite3_bind_int(pUpdate, 1, i);
+ tdsqlite3_step(pUpdate);
+ rc = tdsqlite3_reset(pUpdate);
}
}
p->bIgnoreSavepoint = 0;
- sqlite3_free(aIdx);
+ tdsqlite3_free(aIdx);
return rc;
}
-static void fts3StartNode(Blob *pNode, int iHeight, sqlite3_int64 iChild){
+static void fts3StartNode(Blob *pNode, int iHeight, tdsqlite3_int64 iChild){
pNode->a[0] = (char)iHeight;
if( iChild ){
- assert( pNode->nAlloc>=1+sqlite3Fts3VarintLen(iChild) );
- pNode->n = 1 + sqlite3Fts3PutVarint(&pNode->a[1], iChild);
+ assert( pNode->nAlloc>=1+tdsqlite3Fts3VarintLen(iChild) );
+ pNode->n = 1 + tdsqlite3Fts3PutVarint(&pNode->a[1], iChild);
}else{
assert( pNode->nAlloc>=1 );
pNode->n = 1;
@@ -160809,12 +183307,15 @@ static int fts3TruncateNode(
Blob *pNew, /* OUT: Write new node image here */
const char *zTerm, /* Omit all terms smaller than this */
int nTerm, /* Size of zTerm in bytes */
- sqlite3_int64 *piBlock /* OUT: Block number in next layer down */
+ tdsqlite3_int64 *piBlock /* OUT: Block number in next layer down */
){
NodeReader reader; /* Reader object */
Blob prev = {0, 0, 0}; /* Previous term written to new node */
int rc = SQLITE_OK; /* Return code */
- int bLeaf = aNode[0]=='\0'; /* True for a leaf node */
+ int bLeaf; /* True for a leaf node */
+
+ if( nNode<1 ) return FTS_CORRUPT_VTAB;
+ bLeaf = aNode[0]=='\0';
/* Allocate required output space */
blobGrowBuffer(pNew, nNode, &rc);
@@ -160845,7 +183346,7 @@ static int fts3TruncateNode(
assert( pNew->n<=pNew->nAlloc );
nodeReaderRelease(&reader);
- sqlite3_free(prev.a);
+ tdsqlite3_free(prev.a);
return rc;
}
@@ -160860,7 +183361,7 @@ static int fts3TruncateNode(
*/
static int fts3TruncateSegment(
Fts3Table *p, /* FTS3 table handle */
- sqlite3_int64 iAbsLevel, /* Absolute level of segment to modify */
+ tdsqlite3_int64 iAbsLevel, /* Absolute level of segment to modify */
int iIdx, /* Index within level of segment to modify */
const char *zTerm, /* Remove terms smaller than this */
int nTerm /* Number of bytes in buffer zTerm */
@@ -160868,23 +183369,23 @@ static int fts3TruncateSegment(
int rc = SQLITE_OK; /* Return code */
Blob root = {0,0,0}; /* New root page image */
Blob block = {0,0,0}; /* Buffer used for any other block */
- sqlite3_int64 iBlock = 0; /* Block id */
- sqlite3_int64 iNewStart = 0; /* New value for iStartBlock */
- sqlite3_int64 iOldStart = 0; /* Old value for iStartBlock */
- sqlite3_stmt *pFetch = 0; /* Statement used to fetch segdir */
+ tdsqlite3_int64 iBlock = 0; /* Block id */
+ tdsqlite3_int64 iNewStart = 0; /* New value for iStartBlock */
+ tdsqlite3_int64 iOldStart = 0; /* Old value for iStartBlock */
+ tdsqlite3_stmt *pFetch = 0; /* Statement used to fetch segdir */
rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR, &pFetch, 0);
if( rc==SQLITE_OK ){
- int rc2; /* sqlite3_reset() return code */
- sqlite3_bind_int64(pFetch, 1, iAbsLevel);
- sqlite3_bind_int(pFetch, 2, iIdx);
- if( SQLITE_ROW==sqlite3_step(pFetch) ){
- const char *aRoot = sqlite3_column_blob(pFetch, 4);
- int nRoot = sqlite3_column_bytes(pFetch, 4);
- iOldStart = sqlite3_column_int64(pFetch, 1);
+ int rc2; /* tdsqlite3_reset() return code */
+ tdsqlite3_bind_int64(pFetch, 1, iAbsLevel);
+ tdsqlite3_bind_int(pFetch, 2, iIdx);
+ if( SQLITE_ROW==tdsqlite3_step(pFetch) ){
+ const char *aRoot = tdsqlite3_column_blob(pFetch, 4);
+ int nRoot = tdsqlite3_column_bytes(pFetch, 4);
+ iOldStart = tdsqlite3_column_int64(pFetch, 1);
rc = fts3TruncateNode(aRoot, nRoot, &root, zTerm, nTerm, &iBlock);
}
- rc2 = sqlite3_reset(pFetch);
+ rc2 = tdsqlite3_reset(pFetch);
if( rc==SQLITE_OK ) rc = rc2;
}
@@ -160893,43 +183394,44 @@ static int fts3TruncateSegment(
int nBlock = 0;
iNewStart = iBlock;
- rc = sqlite3Fts3ReadBlock(p, iBlock, &aBlock, &nBlock, 0);
+ rc = tdsqlite3Fts3ReadBlock(p, iBlock, &aBlock, &nBlock, 0);
if( rc==SQLITE_OK ){
rc = fts3TruncateNode(aBlock, nBlock, &block, zTerm, nTerm, &iBlock);
}
if( rc==SQLITE_OK ){
rc = fts3WriteSegment(p, iNewStart, block.a, block.n);
}
- sqlite3_free(aBlock);
+ tdsqlite3_free(aBlock);
}
/* Variable iNewStart now contains the first valid leaf node. */
if( rc==SQLITE_OK && iNewStart ){
- sqlite3_stmt *pDel = 0;
+ tdsqlite3_stmt *pDel = 0;
rc = fts3SqlStmt(p, SQL_DELETE_SEGMENTS_RANGE, &pDel, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pDel, 1, iOldStart);
- sqlite3_bind_int64(pDel, 2, iNewStart-1);
- sqlite3_step(pDel);
- rc = sqlite3_reset(pDel);
+ tdsqlite3_bind_int64(pDel, 1, iOldStart);
+ tdsqlite3_bind_int64(pDel, 2, iNewStart-1);
+ tdsqlite3_step(pDel);
+ rc = tdsqlite3_reset(pDel);
}
}
if( rc==SQLITE_OK ){
- sqlite3_stmt *pChomp = 0;
+ tdsqlite3_stmt *pChomp = 0;
rc = fts3SqlStmt(p, SQL_CHOMP_SEGDIR, &pChomp, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pChomp, 1, iNewStart);
- sqlite3_bind_blob(pChomp, 2, root.a, root.n, SQLITE_STATIC);
- sqlite3_bind_int64(pChomp, 3, iAbsLevel);
- sqlite3_bind_int(pChomp, 4, iIdx);
- sqlite3_step(pChomp);
- rc = sqlite3_reset(pChomp);
+ tdsqlite3_bind_int64(pChomp, 1, iNewStart);
+ tdsqlite3_bind_blob(pChomp, 2, root.a, root.n, SQLITE_STATIC);
+ tdsqlite3_bind_int64(pChomp, 3, iAbsLevel);
+ tdsqlite3_bind_int(pChomp, 4, iIdx);
+ tdsqlite3_step(pChomp);
+ rc = tdsqlite3_reset(pChomp);
+ tdsqlite3_bind_null(pChomp, 2);
}
}
- sqlite3_free(root.a);
- sqlite3_free(block.a);
+ tdsqlite3_free(root.a);
+ tdsqlite3_free(block.a);
return rc;
}
@@ -160945,7 +183447,7 @@ static int fts3TruncateSegment(
*/
static int fts3IncrmergeChomp(
Fts3Table *p, /* FTS table handle */
- sqlite3_int64 iAbsLevel, /* Absolute level containing segments */
+ tdsqlite3_int64 iAbsLevel, /* Absolute level containing segments */
Fts3MultiSegReader *pCsr, /* Chomp all segments opened by this cursor */
int *pnRem /* Number of segments not deleted */
){
@@ -160995,15 +183497,16 @@ static int fts3IncrmergeChomp(
** Store an incr-merge hint in the database.
*/
static int fts3IncrmergeHintStore(Fts3Table *p, Blob *pHint){
- sqlite3_stmt *pReplace = 0;
+ tdsqlite3_stmt *pReplace = 0;
int rc; /* Return code */
rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pReplace, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int(pReplace, 1, FTS_STAT_INCRMERGEHINT);
- sqlite3_bind_blob(pReplace, 2, pHint->a, pHint->n, SQLITE_STATIC);
- sqlite3_step(pReplace);
- rc = sqlite3_reset(pReplace);
+ tdsqlite3_bind_int(pReplace, 1, FTS_STAT_INCRMERGEHINT);
+ tdsqlite3_bind_blob(pReplace, 2, pHint->a, pHint->n, SQLITE_STATIC);
+ tdsqlite3_step(pReplace);
+ rc = tdsqlite3_reset(pReplace);
+ tdsqlite3_bind_null(pReplace, 2);
}
return rc;
@@ -161018,17 +183521,17 @@ static int fts3IncrmergeHintStore(Fts3Table *p, Blob *pHint){
** SQLite error code.
*/
static int fts3IncrmergeHintLoad(Fts3Table *p, Blob *pHint){
- sqlite3_stmt *pSelect = 0;
+ tdsqlite3_stmt *pSelect = 0;
int rc;
pHint->n = 0;
rc = fts3SqlStmt(p, SQL_SELECT_STAT, &pSelect, 0);
if( rc==SQLITE_OK ){
int rc2;
- sqlite3_bind_int(pSelect, 1, FTS_STAT_INCRMERGEHINT);
- if( SQLITE_ROW==sqlite3_step(pSelect) ){
- const char *aHint = sqlite3_column_blob(pSelect, 0);
- int nHint = sqlite3_column_bytes(pSelect, 0);
+ tdsqlite3_bind_int(pSelect, 1, FTS_STAT_INCRMERGEHINT);
+ if( SQLITE_ROW==tdsqlite3_step(pSelect) ){
+ const char *aHint = tdsqlite3_column_blob(pSelect, 0);
+ int nHint = tdsqlite3_column_bytes(pSelect, 0);
if( aHint ){
blobGrowBuffer(pHint, nHint, &rc);
if( rc==SQLITE_OK ){
@@ -161037,7 +183540,7 @@ static int fts3IncrmergeHintLoad(Fts3Table *p, Blob *pHint){
}
}
}
- rc2 = sqlite3_reset(pSelect);
+ rc2 = tdsqlite3_reset(pSelect);
if( rc==SQLITE_OK ) rc = rc2;
}
@@ -161061,8 +183564,8 @@ static void fts3IncrmergeHintPush(
){
blobGrowBuffer(pHint, pHint->n + 2*FTS3_VARINT_MAX, pRc);
if( *pRc==SQLITE_OK ){
- pHint->n += sqlite3Fts3PutVarint(&pHint->a[pHint->n], iAbsLevel);
- pHint->n += sqlite3Fts3PutVarint(&pHint->a[pHint->n], (i64)nInput);
+ pHint->n += tdsqlite3Fts3PutVarint(&pHint->a[pHint->n], iAbsLevel);
+ pHint->n += tdsqlite3Fts3PutVarint(&pHint->a[pHint->n], (i64)nInput);
}
}
@@ -161078,13 +183581,17 @@ static int fts3IncrmergeHintPop(Blob *pHint, i64 *piAbsLevel, int *pnInput){
const int nHint = pHint->n;
int i;
- i = pHint->n-2;
+ i = pHint->n-1;
+ if( (pHint->a[i] & 0x80) ) return FTS_CORRUPT_VTAB;
while( i>0 && (pHint->a[i-1] & 0x80) ) i--;
+ if( i==0 ) return FTS_CORRUPT_VTAB;
+ i--;
while( i>0 && (pHint->a[i-1] & 0x80) ) i--;
pHint->n = i;
- i += sqlite3Fts3GetVarint(&pHint->a[i], piAbsLevel);
+ i += tdsqlite3Fts3GetVarint(&pHint->a[i], piAbsLevel);
i += fts3GetVarint32(&pHint->a[i], pnInput);
+ assert( i<=nHint );
if( i!=nHint ) return FTS_CORRUPT_VTAB;
return SQLITE_OK;
@@ -161100,20 +183607,20 @@ static int fts3IncrmergeHintPop(Blob *pHint, i64 *piAbsLevel, int *pnInput){
** at least nMin segments. Multiple merges might occur in an attempt to
** write the quota of nMerge leaf blocks.
*/
-SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
+SQLITE_PRIVATE int tdsqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
int rc; /* Return code */
int nRem = nMerge; /* Number of leaf pages yet to be written */
Fts3MultiSegReader *pCsr; /* Cursor used to read input data */
Fts3SegFilter *pFilter; /* Filter used with cursor pCsr */
IncrmergeWriter *pWriter; /* Writer object */
int nSeg = 0; /* Number of input segments */
- sqlite3_int64 iAbsLevel = 0; /* Absolute level number to work on */
+ tdsqlite3_int64 iAbsLevel = 0; /* Absolute level number to work on */
Blob hint = {0, 0, 0}; /* Hint read from %_stat table */
int bDirtyHint = 0; /* True if blob 'hint' has been modified */
/* Allocate space for the cursor, filter and writer objects */
const int nAlloc = sizeof(*pCsr) + sizeof(*pFilter) + sizeof(*pWriter);
- pWriter = (IncrmergeWriter *)sqlite3_malloc(nAlloc);
+ pWriter = (IncrmergeWriter *)tdsqlite3_malloc(nAlloc);
if( !pWriter ) return SQLITE_NOMEM;
pFilter = (Fts3SegFilter *)&pWriter[1];
pCsr = (Fts3MultiSegReader *)&pFilter[1];
@@ -161121,7 +183628,7 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
rc = fts3IncrmergeHintLoad(p, &hint);
while( rc==SQLITE_OK && nRem>0 ){
const i64 nMod = FTS3_SEGDIR_MAXLEVEL * p->nIndex;
- sqlite3_stmt *pFindLevel = 0; /* SQL used to determine iAbsLevel */
+ tdsqlite3_stmt *pFindLevel = 0; /* SQL used to determine iAbsLevel */
int bUseHint = 0; /* True if attempting to append */
int iIdx = 0; /* Largest idx in level (iAbsLevel+1) */
@@ -161132,15 +183639,15 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
** set nSeg to -1.
*/
rc = fts3SqlStmt(p, SQL_FIND_MERGE_LEVEL, &pFindLevel, 0);
- sqlite3_bind_int(pFindLevel, 1, MAX(2, nMin));
- if( sqlite3_step(pFindLevel)==SQLITE_ROW ){
- iAbsLevel = sqlite3_column_int64(pFindLevel, 0);
- nSeg = sqlite3_column_int(pFindLevel, 1);
+ tdsqlite3_bind_int(pFindLevel, 1, MAX(2, nMin));
+ if( tdsqlite3_step(pFindLevel)==SQLITE_ROW ){
+ iAbsLevel = tdsqlite3_column_int64(pFindLevel, 0);
+ nSeg = tdsqlite3_column_int(pFindLevel, 1);
assert( nSeg>=2 );
}else{
nSeg = -1;
}
- rc = sqlite3_reset(pFindLevel);
+ rc = tdsqlite3_reset(pFindLevel);
/* If the hint read from the %_stat table is not empty, check if the
** last entry in it specifies a relative level smaller than or equal
@@ -161149,13 +183656,19 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
*/
if( rc==SQLITE_OK && hint.n ){
int nHint = hint.n;
- sqlite3_int64 iHintAbsLevel = 0; /* Hint level */
+ tdsqlite3_int64 iHintAbsLevel = 0; /* Hint level */
int nHintSeg = 0; /* Hint number of segments */
rc = fts3IncrmergeHintPop(&hint, &iHintAbsLevel, &nHintSeg);
if( nSeg<0 || (iAbsLevel % nMod) >= (iHintAbsLevel % nMod) ){
+ /* Based on the scan in the block above, it is known that there
+ ** are no levels with a relative level smaller than that of
+ ** iAbsLevel with more than nSeg segments, or if nSeg is -1,
+ ** no levels with more than nMin segments. Use this to limit the
+ ** value of nHintSeg to avoid a large memory allocation in case the
+ ** merge-hint is corrupt*/
iAbsLevel = iHintAbsLevel;
- nSeg = nHintSeg;
+ nSeg = MIN(MAX(nMin,nSeg), nHintSeg);
bUseHint = 1;
bDirtyHint = 1;
}else{
@@ -161168,7 +183681,7 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
/* If nSeg is less that zero, then there is no level with at least
** nMin segments and no hint in the %_stat table. No work to do.
** Exit early in this case. */
- if( nSeg<0 ) break;
+ if( nSeg<=0 ) break;
/* Open a cursor to iterate through the contents of the oldest nSeg
** indexes of absolute level iAbsLevel. If this cursor is opened using
@@ -161195,9 +183708,16 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
rc = fts3IncrmergeCsr(p, iAbsLevel, nSeg, pCsr);
}
if( SQLITE_OK==rc && pCsr->nSegment==nSeg
- && SQLITE_OK==(rc = sqlite3Fts3SegReaderStart(p, pCsr, pFilter))
- && SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pCsr))
+ && SQLITE_OK==(rc = tdsqlite3Fts3SegReaderStart(p, pCsr, pFilter))
){
+ int bEmpty = 0;
+ rc = tdsqlite3Fts3SegReaderStep(p, pCsr);
+ if( rc==SQLITE_OK ){
+ bEmpty = 1;
+ }else if( rc!=SQLITE_ROW ){
+ tdsqlite3Fts3SegReaderFinish(pCsr);
+ break;
+ }
if( bUseHint && iIdx>0 ){
const char *zKey = pCsr->zTerm;
int nKey = pCsr->nTerm;
@@ -161208,11 +183728,13 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
if( rc==SQLITE_OK && pWriter->nLeafEst ){
fts3LogMerge(nSeg, iAbsLevel);
- do {
- rc = fts3IncrmergeAppend(p, pWriter, pCsr);
- if( rc==SQLITE_OK ) rc = sqlite3Fts3SegReaderStep(p, pCsr);
- if( pWriter->nWork>=nRem && rc==SQLITE_ROW ) rc = SQLITE_OK;
- }while( rc==SQLITE_ROW );
+ if( bEmpty==0 ){
+ do {
+ rc = fts3IncrmergeAppend(p, pWriter, pCsr);
+ if( rc==SQLITE_OK ) rc = tdsqlite3Fts3SegReaderStep(p, pCsr);
+ if( pWriter->nWork>=nRem && rc==SQLITE_ROW ) rc = SQLITE_OK;
+ }while( rc==SQLITE_ROW );
+ }
/* Update or delete the input segments */
if( rc==SQLITE_OK ){
@@ -161234,7 +183756,7 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
}
}
- sqlite3Fts3SegReaderFinish(pCsr);
+ tdsqlite3Fts3SegReaderFinish(pCsr);
}
/* Write the hint values into the %_stat table for the next incr-merger */
@@ -161242,8 +183764,8 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
rc = fts3IncrmergeHintStore(p, &hint);
}
- sqlite3_free(pWriter);
- sqlite3_free(hint.a);
+ tdsqlite3_free(pWriter);
+ tdsqlite3_free(hint.a);
return rc;
}
@@ -161251,11 +183773,14 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){
** Convert the text beginning at *pz into an integer and return
** its value. Advance *pz to point to the first character past
** the integer.
+**
+** This function used for parameters to merge= and incrmerge=
+** commands.
*/
static int fts3Getint(const char **pz){
const char *z = *pz;
int i = 0;
- while( (*z)>='0' && (*z)<='9' ) i = 10*i + *(z++) - '0';
+ while( (*z)>='0' && (*z)<='9' && i<214748363 ) i = 10*i + *(z++) - '0';
*pz = z;
return i;
}
@@ -161274,7 +183799,7 @@ static int fts3DoIncrmerge(
const char *zParam /* Nul-terminated string containing "A,B" */
){
int rc;
- int nMin = (FTS3_MERGE_COUNT / 2);
+ int nMin = (MergeCount(p) / 2);
int nMerge = 0;
const char *z = zParam;
@@ -161294,12 +183819,12 @@ static int fts3DoIncrmerge(
rc = SQLITE_OK;
if( !p->bHasStat ){
assert( p->bFts4==0 );
- sqlite3Fts3CreateStatTable(&rc, p);
+ tdsqlite3Fts3CreateStatTable(&rc, p);
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3Incrmerge(p, nMerge, nMin);
+ rc = tdsqlite3Fts3Incrmerge(p, nMerge, nMin);
}
- sqlite3Fts3SegmentsClose(p);
+ tdsqlite3Fts3SegmentsClose(p);
}
return rc;
}
@@ -161317,22 +183842,22 @@ static int fts3DoAutoincrmerge(
const char *zParam /* Nul-terminated string containing boolean */
){
int rc = SQLITE_OK;
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
p->nAutoincrmerge = fts3Getint(&zParam);
- if( p->nAutoincrmerge==1 || p->nAutoincrmerge>FTS3_MERGE_COUNT ){
+ if( p->nAutoincrmerge==1 || p->nAutoincrmerge>MergeCount(p) ){
p->nAutoincrmerge = 8;
}
if( !p->bHasStat ){
assert( p->bFts4==0 );
- sqlite3Fts3CreateStatTable(&rc, p);
+ tdsqlite3Fts3CreateStatTable(&rc, p);
if( rc ) return rc;
}
rc = fts3SqlStmt(p, SQL_REPLACE_STAT, &pStmt, 0);
if( rc ) return rc;
- sqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE);
- sqlite3_bind_int(pStmt, 2, p->nAutoincrmerge);
- sqlite3_step(pStmt);
- rc = sqlite3_reset(pStmt);
+ tdsqlite3_bind_int(pStmt, 1, FTS_STAT_AUTOINCRMERGE);
+ tdsqlite3_bind_int(pStmt, 2, p->nAutoincrmerge);
+ tdsqlite3_step(pStmt);
+ rc = tdsqlite3_reset(pStmt);
return rc;
}
@@ -161388,35 +183913,39 @@ static u64 fts3ChecksumIndex(
filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
filter.flags |= FTS3_SEGMENT_SCAN;
- rc = sqlite3Fts3SegReaderCursor(
+ rc = tdsqlite3Fts3SegReaderCursor(
p, iLangid, iIndex, FTS3_SEGCURSOR_ALL, 0, 0, 0, 1,&csr
);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts3SegReaderStart(p, &csr, &filter);
+ rc = tdsqlite3Fts3SegReaderStart(p, &csr, &filter);
}
if( rc==SQLITE_OK ){
- while( SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, &csr)) ){
+ while( SQLITE_ROW==(rc = tdsqlite3Fts3SegReaderStep(p, &csr)) ){
char *pCsr = csr.aDoclist;
char *pEnd = &pCsr[csr.nDoclist];
i64 iDocid = 0;
i64 iCol = 0;
- i64 iPos = 0;
+ u64 iPos = 0;
- pCsr += sqlite3Fts3GetVarint(pCsr, &iDocid);
+ pCsr += tdsqlite3Fts3GetVarint(pCsr, &iDocid);
while( pCsr<pEnd ){
- i64 iVal = 0;
- pCsr += sqlite3Fts3GetVarint(pCsr, &iVal);
+ u64 iVal = 0;
+ pCsr += tdsqlite3Fts3GetVarintU(pCsr, &iVal);
if( pCsr<pEnd ){
if( iVal==0 || iVal==1 ){
iCol = 0;
iPos = 0;
if( iVal ){
- pCsr += sqlite3Fts3GetVarint(pCsr, &iCol);
+ pCsr += tdsqlite3Fts3GetVarint(pCsr, &iCol);
}else{
- pCsr += sqlite3Fts3GetVarint(pCsr, &iVal);
- iDocid += iVal;
+ pCsr += tdsqlite3Fts3GetVarintU(pCsr, &iVal);
+ if( p->bDescIdx ){
+ iDocid = (i64)((u64)iDocid - iVal);
+ }else{
+ iDocid = (i64)((u64)iDocid + iVal);
+ }
}
}else{
iPos += (iVal - 2);
@@ -161429,7 +183958,7 @@ static u64 fts3ChecksumIndex(
}
}
}
- sqlite3Fts3SegReaderFinish(&csr);
+ tdsqlite3Fts3SegReaderFinish(&csr);
*pRc = rc;
return cksum;
@@ -161448,51 +183977,50 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){
int rc = SQLITE_OK; /* Return code */
u64 cksum1 = 0; /* Checksum based on FTS index contents */
u64 cksum2 = 0; /* Checksum based on %_content contents */
- sqlite3_stmt *pAllLangid = 0; /* Statement to return all language-ids */
+ tdsqlite3_stmt *pAllLangid = 0; /* Statement to return all language-ids */
/* This block calculates the checksum according to the FTS index. */
rc = fts3SqlStmt(p, SQL_SELECT_ALL_LANGID, &pAllLangid, 0);
if( rc==SQLITE_OK ){
int rc2;
- sqlite3_bind_int(pAllLangid, 1, p->iPrevLangid);
- sqlite3_bind_int(pAllLangid, 2, p->nIndex);
- while( rc==SQLITE_OK && sqlite3_step(pAllLangid)==SQLITE_ROW ){
- int iLangid = sqlite3_column_int(pAllLangid, 0);
+ tdsqlite3_bind_int(pAllLangid, 1, p->iPrevLangid);
+ tdsqlite3_bind_int(pAllLangid, 2, p->nIndex);
+ while( rc==SQLITE_OK && tdsqlite3_step(pAllLangid)==SQLITE_ROW ){
+ int iLangid = tdsqlite3_column_int(pAllLangid, 0);
int i;
for(i=0; i<p->nIndex; i++){
cksum1 = cksum1 ^ fts3ChecksumIndex(p, iLangid, i, &rc);
}
}
- rc2 = sqlite3_reset(pAllLangid);
+ rc2 = tdsqlite3_reset(pAllLangid);
if( rc==SQLITE_OK ) rc = rc2;
}
/* This block calculates the checksum according to the %_content table */
if( rc==SQLITE_OK ){
- sqlite3_tokenizer_module const *pModule = p->pTokenizer->pModule;
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_tokenizer_module const *pModule = p->pTokenizer->pModule;
+ tdsqlite3_stmt *pStmt = 0;
char *zSql;
- zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist);
+ zSql = tdsqlite3_mprintf("SELECT %s" , p->zReadExprlist);
if( !zSql ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
+ rc = tdsqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ tdsqlite3_free(zSql);
}
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
- i64 iDocid = sqlite3_column_int64(pStmt, 0);
+ while( rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ i64 iDocid = tdsqlite3_column_int64(pStmt, 0);
int iLang = langidFromSelect(p, pStmt);
int iCol;
for(iCol=0; rc==SQLITE_OK && iCol<p->nColumn; iCol++){
if( p->abNotindexed[iCol]==0 ){
- const char *zText = (const char *)sqlite3_column_text(pStmt, iCol+1);
- int nText = sqlite3_column_bytes(pStmt, iCol+1);
- sqlite3_tokenizer_cursor *pT = 0;
+ const char *zText = (const char *)tdsqlite3_column_text(pStmt, iCol+1);
+ tdsqlite3_tokenizer_cursor *pT = 0;
- rc = sqlite3Fts3OpenTokenizer(p->pTokenizer, iLang, zText, nText,&pT);
+ rc = tdsqlite3Fts3OpenTokenizer(p->pTokenizer, iLang, zText, -1, &pT);
while( rc==SQLITE_OK ){
char const *zToken; /* Buffer containing token */
int nToken = 0; /* Number of bytes in token */
@@ -161520,7 +184048,7 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){
}
}
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
}
*pbOk = (cksum1==cksum2);
@@ -161576,47 +184104,53 @@ static int fts3DoIntegrityCheck(
** Argument pVal contains the result of <expr>. Currently the only
** meaningful value to insert is the text 'optimize'.
*/
-static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){
- int rc; /* Return Code */
- const char *zVal = (const char *)sqlite3_value_text(pVal);
- int nVal = sqlite3_value_bytes(pVal);
+static int fts3SpecialInsert(Fts3Table *p, tdsqlite3_value *pVal){
+ int rc = SQLITE_ERROR; /* Return Code */
+ const char *zVal = (const char *)tdsqlite3_value_text(pVal);
+ int nVal = tdsqlite3_value_bytes(pVal);
if( !zVal ){
return SQLITE_NOMEM;
- }else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){
+ }else if( nVal==8 && 0==tdsqlite3_strnicmp(zVal, "optimize", 8) ){
rc = fts3DoOptimize(p, 0);
- }else if( nVal==7 && 0==sqlite3_strnicmp(zVal, "rebuild", 7) ){
+ }else if( nVal==7 && 0==tdsqlite3_strnicmp(zVal, "rebuild", 7) ){
rc = fts3DoRebuild(p);
- }else if( nVal==15 && 0==sqlite3_strnicmp(zVal, "integrity-check", 15) ){
+ }else if( nVal==15 && 0==tdsqlite3_strnicmp(zVal, "integrity-check", 15) ){
rc = fts3DoIntegrityCheck(p);
- }else if( nVal>6 && 0==sqlite3_strnicmp(zVal, "merge=", 6) ){
+ }else if( nVal>6 && 0==tdsqlite3_strnicmp(zVal, "merge=", 6) ){
rc = fts3DoIncrmerge(p, &zVal[6]);
- }else if( nVal>10 && 0==sqlite3_strnicmp(zVal, "automerge=", 10) ){
+ }else if( nVal>10 && 0==tdsqlite3_strnicmp(zVal, "automerge=", 10) ){
rc = fts3DoAutoincrmerge(p, &zVal[10]);
-#ifdef SQLITE_TEST
- }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
- p->nNodeSize = atoi(&zVal[9]);
- rc = SQLITE_OK;
- }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){
- p->nMaxPendingData = atoi(&zVal[11]);
- rc = SQLITE_OK;
- }else if( nVal>21 && 0==sqlite3_strnicmp(zVal, "test-no-incr-doclist=", 21) ){
- p->bNoIncrDoclist = atoi(&zVal[21]);
- rc = SQLITE_OK;
-#endif
+#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST)
}else{
- rc = SQLITE_ERROR;
+ int v;
+ if( nVal>9 && 0==tdsqlite3_strnicmp(zVal, "nodesize=", 9) ){
+ v = atoi(&zVal[9]);
+ if( v>=24 && v<=p->nPgsz-35 ) p->nNodeSize = v;
+ rc = SQLITE_OK;
+ }else if( nVal>11 && 0==tdsqlite3_strnicmp(zVal, "maxpending=", 9) ){
+ v = atoi(&zVal[11]);
+ if( v>=64 && v<=FTS3_MAX_PENDING_DATA ) p->nMaxPendingData = v;
+ rc = SQLITE_OK;
+ }else if( nVal>21 && 0==tdsqlite3_strnicmp(zVal,"test-no-incr-doclist=",21) ){
+ p->bNoIncrDoclist = atoi(&zVal[21]);
+ rc = SQLITE_OK;
+ }else if( nVal>11 && 0==tdsqlite3_strnicmp(zVal,"mergecount=",11) ){
+ v = atoi(&zVal[11]);
+ if( v>=4 && v<=FTS3_MERGE_COUNT && (v&1)==0 ) p->nMergeCount = v;
+ rc = SQLITE_OK;
+ }
+#endif
}
-
return rc;
}
#ifndef SQLITE_DISABLE_FTS4_DEFERRED
/*
** Delete all cached deferred doclists. Deferred doclists are cached
-** (allocated) by the sqlite3Fts3CacheDeferredDoclists() function.
+** (allocated) by the tdsqlite3Fts3CacheDeferredDoclists() function.
*/
-SQLITE_PRIVATE void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){
+SQLITE_PRIVATE void tdsqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){
Fts3DeferredToken *pDef;
for(pDef=pCsr->pDeferred; pDef; pDef=pDef->pNext){
fts3PendingListDelete(pDef->pList);
@@ -161626,15 +184160,15 @@ SQLITE_PRIVATE void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){
/*
** Free all entries in the pCsr->pDeffered list. Entries are added to
-** this list using sqlite3Fts3DeferToken().
+** this list using tdsqlite3Fts3DeferToken().
*/
-SQLITE_PRIVATE void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *pCsr){
+SQLITE_PRIVATE void tdsqlite3Fts3FreeDeferredTokens(Fts3Cursor *pCsr){
Fts3DeferredToken *pDef;
Fts3DeferredToken *pNext;
for(pDef=pCsr->pDeferred; pDef; pDef=pNext){
pNext = pDef->pNext;
fts3PendingListDelete(pDef->pList);
- sqlite3_free(pDef);
+ tdsqlite3_free(pDef);
}
pCsr->pDeferred = 0;
}
@@ -161647,26 +184181,26 @@ SQLITE_PRIVATE void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *pCsr){
** included, except that it only contains entries for a single row of the
** table, not for all rows.
*/
-SQLITE_PRIVATE int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *pCsr){
+SQLITE_PRIVATE int tdsqlite3Fts3CacheDeferredDoclists(Fts3Cursor *pCsr){
int rc = SQLITE_OK; /* Return code */
if( pCsr->pDeferred ){
int i; /* Used to iterate through table columns */
- sqlite3_int64 iDocid; /* Docid of the row pCsr points to */
+ tdsqlite3_int64 iDocid; /* Docid of the row pCsr points to */
Fts3DeferredToken *pDef; /* Used to iterate through deferred tokens */
Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
- sqlite3_tokenizer *pT = p->pTokenizer;
- sqlite3_tokenizer_module const *pModule = pT->pModule;
+ tdsqlite3_tokenizer *pT = p->pTokenizer;
+ tdsqlite3_tokenizer_module const *pModule = pT->pModule;
assert( pCsr->isRequireSeek==0 );
- iDocid = sqlite3_column_int64(pCsr->pStmt, 0);
+ iDocid = tdsqlite3_column_int64(pCsr->pStmt, 0);
for(i=0; i<p->nColumn && rc==SQLITE_OK; i++){
if( p->abNotindexed[i]==0 ){
- const char *zText = (const char *)sqlite3_column_text(pCsr->pStmt, i+1);
- sqlite3_tokenizer_cursor *pTC = 0;
+ const char *zText = (const char *)tdsqlite3_column_text(pCsr->pStmt, i+1);
+ tdsqlite3_tokenizer_cursor *pTC = 0;
- rc = sqlite3Fts3OpenTokenizer(pT, pCsr->iLangid, zText, -1, &pTC);
+ rc = tdsqlite3Fts3OpenTokenizer(pT, pCsr->iLangid, zText, -1, &pTC);
while( rc==SQLITE_OK ){
char const *zToken; /* Buffer containing token */
int nToken = 0; /* Number of bytes in token */
@@ -161700,14 +184234,14 @@ SQLITE_PRIVATE int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *pCsr){
return rc;
}
-SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList(
+SQLITE_PRIVATE int tdsqlite3Fts3DeferredTokenList(
Fts3DeferredToken *p,
char **ppData,
int *pnData
){
char *pRet;
int nSkip;
- sqlite3_int64 dummy;
+ tdsqlite3_int64 dummy;
*ppData = 0;
*pnData = 0;
@@ -161716,10 +184250,10 @@ SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList(
return SQLITE_OK;
}
- pRet = (char *)sqlite3_malloc(p->pList->nData);
+ pRet = (char *)tdsqlite3_malloc(p->pList->nData);
if( !pRet ) return SQLITE_NOMEM;
- nSkip = sqlite3Fts3GetVarint(p->pList->aData, &dummy);
+ nSkip = tdsqlite3Fts3GetVarint(p->pList->aData, &dummy);
*pnData = p->pList->nData - nSkip;
*ppData = pRet;
@@ -161730,13 +184264,13 @@ SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList(
/*
** Add an entry for token pToken to the pCsr->pDeferred list.
*/
-SQLITE_PRIVATE int sqlite3Fts3DeferToken(
+SQLITE_PRIVATE int tdsqlite3Fts3DeferToken(
Fts3Cursor *pCsr, /* Fts3 table cursor */
Fts3PhraseToken *pToken, /* Token to defer */
int iCol /* Column that token must appear in (or -1) */
){
Fts3DeferredToken *pDeferred;
- pDeferred = sqlite3_malloc(sizeof(*pDeferred));
+ pDeferred = tdsqlite3_malloc(sizeof(*pDeferred));
if( !pDeferred ){
return SQLITE_NOMEM;
}
@@ -161760,7 +184294,7 @@ SQLITE_PRIVATE int sqlite3Fts3DeferToken(
*/
static int fts3DeleteByRowid(
Fts3Table *p,
- sqlite3_value *pRowid,
+ tdsqlite3_value *pRowid,
int *pnChng, /* IN/OUT: Decrement if row is deleted */
u32 *aSzDel
){
@@ -161807,15 +184341,14 @@ static int fts3DeleteByRowid(
**
**
*/
-SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(
- sqlite3_vtab *pVtab, /* FTS3 vtab object */
+SQLITE_PRIVATE int tdsqlite3Fts3UpdateMethod(
+ tdsqlite3_vtab *pVtab, /* FTS3 vtab object */
int nArg, /* Size of argument array */
- sqlite3_value **apVal, /* Array of arguments */
+ tdsqlite3_value **apVal, /* Array of arguments */
sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */
){
Fts3Table *p = (Fts3Table *)pVtab;
int rc = SQLITE_OK; /* Return Code */
- int isRemove = 0; /* True for an UPDATE or DELETE */
u32 *aSzIns = 0; /* Sizes of inserted documents */
u32 *aSzDel = 0; /* Sizes of deleted documents */
int nChng = 0; /* Net change in number of documents */
@@ -161836,20 +184369,20 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(
** INSERT INTO xyz(xyz) VALUES('command');
*/
if( nArg>1
- && sqlite3_value_type(apVal[0])==SQLITE_NULL
- && sqlite3_value_type(apVal[p->nColumn+2])!=SQLITE_NULL
+ && tdsqlite3_value_type(apVal[0])==SQLITE_NULL
+ && tdsqlite3_value_type(apVal[p->nColumn+2])!=SQLITE_NULL
){
rc = fts3SpecialInsert(p, apVal[p->nColumn+2]);
goto update_out;
}
- if( nArg>1 && sqlite3_value_int(apVal[2 + p->nColumn + 2])<0 ){
+ if( nArg>1 && tdsqlite3_value_int(apVal[2 + p->nColumn + 2])<0 ){
rc = SQLITE_CONSTRAINT;
goto update_out;
}
/* Allocate space to hold the change in document sizes */
- aSzDel = sqlite3_malloc( sizeof(aSzDel[0])*(p->nColumn+1)*2 );
+ aSzDel = tdsqlite3_malloc64(sizeof(aSzDel[0])*((tdsqlite3_int64)p->nColumn+1)*2);
if( aSzDel==0 ){
rc = SQLITE_NOMEM;
goto update_out;
@@ -161871,14 +184404,14 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(
*/
if( nArg>1 && p->zContentTbl==0 ){
/* Find the value object that holds the new rowid value. */
- sqlite3_value *pNewRowid = apVal[3+p->nColumn];
- if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
+ tdsqlite3_value *pNewRowid = apVal[3+p->nColumn];
+ if( tdsqlite3_value_type(pNewRowid)==SQLITE_NULL ){
pNewRowid = apVal[1];
}
- if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && (
- sqlite3_value_type(apVal[0])==SQLITE_NULL
- || sqlite3_value_int64(apVal[0])!=sqlite3_value_int64(pNewRowid)
+ if( tdsqlite3_value_type(pNewRowid)!=SQLITE_NULL && (
+ tdsqlite3_value_type(apVal[0])==SQLITE_NULL
+ || tdsqlite3_value_int64(apVal[0])!=tdsqlite3_value_int64(pNewRowid)
)){
/* The new rowid is not NULL (in this case the rowid will be
** automatically assigned and there is no chance of a conflict), and
@@ -161897,7 +184430,7 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(
** invoked, it will delete zero rows (since no row will have
** docid=$pNewRowid if $pNewRowid is not an integer value).
*/
- if( sqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){
+ if( tdsqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){
rc = fts3DeleteByRowid(p, pNewRowid, &nChng, aSzDel);
}else{
rc = fts3InsertData(p, apVal, pRowid);
@@ -161910,22 +184443,21 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(
}
/* If this is a DELETE or UPDATE operation, remove the old record. */
- if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
- assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER );
+ if( tdsqlite3_value_type(apVal[0])!=SQLITE_NULL ){
+ assert( tdsqlite3_value_type(apVal[0])==SQLITE_INTEGER );
rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel);
- isRemove = 1;
}
/* If this is an INSERT or UPDATE operation, insert the new record. */
if( nArg>1 && rc==SQLITE_OK ){
- int iLangid = sqlite3_value_int(apVal[2 + p->nColumn + 2]);
+ int iLangid = tdsqlite3_value_int(apVal[2 + p->nColumn + 2]);
if( bInsertDone==0 ){
rc = fts3InsertData(p, apVal, pRowid);
if( rc==SQLITE_CONSTRAINT && p->zContentTbl==0 ){
rc = FTS_CORRUPT_VTAB;
}
}
- if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){
+ if( rc==SQLITE_OK ){
rc = fts3PendingTermsDocid(p, 0, iLangid, *pRowid);
}
if( rc==SQLITE_OK ){
@@ -161943,8 +184475,8 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(
}
update_out:
- sqlite3_free(aSzDel);
- sqlite3Fts3SegmentsClose(p);
+ tdsqlite3_free(aSzDel);
+ tdsqlite3Fts3SegmentsClose(p);
return rc;
}
@@ -161953,20 +184485,20 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod(
** merge all segments in the database (including the new segment, if
** there was any data to flush) into a single segment.
*/
-SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){
+SQLITE_PRIVATE int tdsqlite3Fts3Optimize(Fts3Table *p){
int rc;
- rc = sqlite3_exec(p->db, "SAVEPOINT fts3", 0, 0, 0);
+ rc = tdsqlite3_exec(p->db, "SAVEPOINT fts3", 0, 0, 0);
if( rc==SQLITE_OK ){
rc = fts3DoOptimize(p, 1);
if( rc==SQLITE_OK || rc==SQLITE_DONE ){
- int rc2 = sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0);
+ int rc2 = tdsqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0);
if( rc2!=SQLITE_OK ) rc = rc2;
}else{
- sqlite3_exec(p->db, "ROLLBACK TO fts3", 0, 0, 0);
- sqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0);
+ tdsqlite3_exec(p->db, "ROLLBACK TO fts3", 0, 0, 0);
+ tdsqlite3_exec(p->db, "RELEASE fts3", 0, 0, 0);
}
}
- sqlite3Fts3SegmentsClose(p);
+ tdsqlite3Fts3SegmentsClose(p);
return rc;
}
@@ -162065,7 +184597,7 @@ struct MatchInfo {
Fts3Cursor *pCursor; /* FTS3 Cursor */
int nCol; /* Number of columns in table */
int nPhrase; /* Number of matchable phrases in query */
- sqlite3_int64 nDoc; /* Number of docs in database */
+ tdsqlite3_int64 nDoc; /* Number of docs in database */
char flag;
u32 *aMatchinfo; /* Pre-allocated buffer */
};
@@ -162104,17 +184636,19 @@ struct StrBuffer {
/*
** Allocate a two-slot MatchinfoBuffer object.
*/
-static MatchinfoBuffer *fts3MIBufferNew(int nElem, const char *zMatchinfo){
+static MatchinfoBuffer *fts3MIBufferNew(size_t nElem, const char *zMatchinfo){
MatchinfoBuffer *pRet;
- int nByte = sizeof(u32) * (2*nElem + 1) + sizeof(MatchinfoBuffer);
- int nStr = (int)strlen(zMatchinfo);
+ tdsqlite3_int64 nByte = sizeof(u32) * (2*(tdsqlite3_int64)nElem + 1)
+ + sizeof(MatchinfoBuffer);
+ tdsqlite3_int64 nStr = strlen(zMatchinfo);
- pRet = sqlite3_malloc(nByte + nStr+1);
+ pRet = tdsqlite3_malloc64(nByte + nStr+1);
if( pRet ){
memset(pRet, 0, nByte);
pRet->aMatchinfo[0] = (u8*)(&pRet->aMatchinfo[1]) - (u8*)pRet;
- pRet->aMatchinfo[1+nElem] = pRet->aMatchinfo[0] + sizeof(u32)*(nElem+1);
- pRet->nElem = nElem;
+ pRet->aMatchinfo[1+nElem] = pRet->aMatchinfo[0]
+ + sizeof(u32)*((int)nElem+1);
+ pRet->nElem = (int)nElem;
pRet->zMatchinfo = ((char*)pRet) + nByte;
memcpy(pRet->zMatchinfo, zMatchinfo, nStr+1);
pRet->aRef[0] = 1;
@@ -162136,7 +184670,7 @@ static void fts3MIBufferFree(void *p){
}
if( pBuf->aRef[0]==0 && pBuf->aRef[1]==0 && pBuf->aRef[2]==0 ){
- sqlite3_free(pBuf);
+ tdsqlite3_free(pBuf);
}
}
@@ -162154,9 +184688,9 @@ static void (*fts3MIBufferAlloc(MatchinfoBuffer *p, u32 **paOut))(void*){
aOut = &p->aMatchinfo[p->nElem+2];
xRet = fts3MIBufferFree;
}else{
- aOut = (u32*)sqlite3_malloc(p->nElem * sizeof(u32));
+ aOut = (u32*)tdsqlite3_malloc64(p->nElem * sizeof(u32));
if( aOut ){
- xRet = sqlite3_free;
+ xRet = tdsqlite3_free;
if( p->bGlobal ) memcpy(aOut, &p->aMatchinfo[1], p->nElem*sizeof(u32));
}
}
@@ -162173,12 +184707,12 @@ static void fts3MIBufferSetGlobal(MatchinfoBuffer *p){
/*
** Free a MatchinfoBuffer object allocated using fts3MIBufferNew()
*/
-SQLITE_PRIVATE void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p){
+SQLITE_PRIVATE void tdsqlite3Fts3MIBufferFree(MatchinfoBuffer *p){
if( p ){
assert( p->aRef[0]==1 );
p->aRef[0] = 0;
if( p->aRef[0]==0 && p->aRef[1]==0 && p->aRef[2]==0 ){
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
}
}
@@ -162405,11 +184939,12 @@ static void fts3SnippetDetails(
char *pCsr = pPhrase->pTail;
int iCsr = pPhrase->iTail;
- while( iCsr<(iStart+pIter->nSnippet) ){
+ while( iCsr<(iStart+pIter->nSnippet) && iCsr>=iStart ){
int j;
- u64 mPhrase = (u64)1 << i;
+ u64 mPhrase = (u64)1 << (i%64);
u64 mPos = (u64)1 << (iCsr - iStart);
- assert( iCsr>=iStart );
+ assert( iCsr>=iStart && (iCsr - iStart)<=64 );
+ assert( i>=0 );
if( (mCover|mCovered)&mPhrase ){
iScore++;
}else{
@@ -162445,17 +184980,20 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){
int rc;
pPhrase->nToken = pExpr->pPhrase->nToken;
- rc = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pCsr);
+ rc = tdsqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pCsr);
assert( rc==SQLITE_OK || pCsr==0 );
if( pCsr ){
int iFirst = 0;
pPhrase->pList = pCsr;
fts3GetDeltaPosition(&pCsr, &iFirst);
- assert( iFirst>=0 );
- pPhrase->pHead = pCsr;
- pPhrase->pTail = pCsr;
- pPhrase->iHead = iFirst;
- pPhrase->iTail = iFirst;
+ if( iFirst<0 ){
+ rc = FTS_CORRUPT_VTAB;
+ }else{
+ pPhrase->pHead = pCsr;
+ pPhrase->pTail = pCsr;
+ pPhrase->iHead = iFirst;
+ pPhrase->iTail = iFirst;
+ }
}else{
assert( rc!=SQLITE_OK || (
pPhrase->pList==0 && pPhrase->pHead==0 && pPhrase->pTail==0
@@ -162492,7 +185030,7 @@ static int fts3BestSnippet(
int rc; /* Return Code */
int nList; /* Number of phrases in expression */
SnippetIter sIter; /* Iterates through snippet candidates */
- int nByte; /* Number of bytes of space to allocate */
+ tdsqlite3_int64 nByte; /* Number of bytes of space to allocate */
int iBestScore = -1; /* Best snippet score found so far */
int i; /* Loop counter */
@@ -162510,7 +185048,7 @@ static int fts3BestSnippet(
** the required space using malloc().
*/
nByte = sizeof(SnippetPhrase) * nList;
- sIter.aPhrase = (SnippetPhrase *)sqlite3_malloc(nByte);
+ sIter.aPhrase = (SnippetPhrase *)tdsqlite3_malloc64(nByte);
if( !sIter.aPhrase ){
return SQLITE_NOMEM;
}
@@ -162530,7 +185068,7 @@ static int fts3BestSnippet(
/* Set the *pmSeen output variable. */
for(i=0; i<nList; i++){
if( sIter.aPhrase[i].pHead ){
- *pmSeen |= (u64)1 << i;
+ *pmSeen |= (u64)1 << (i%64);
}
}
@@ -162555,7 +185093,7 @@ static int fts3BestSnippet(
*piScore = iBestScore;
}
- sqlite3_free(sIter.aPhrase);
+ tdsqlite3_free(sIter.aPhrase);
return rc;
}
@@ -162580,8 +185118,8 @@ static int fts3StringAppend(
** appended data.
*/
if( pStr->n+nAppend+1>=pStr->nAlloc ){
- int nAlloc = pStr->nAlloc+nAppend+100;
- char *zNew = sqlite3_realloc(pStr->z, nAlloc);
+ tdsqlite3_int64 nAlloc = pStr->nAlloc+(tdsqlite3_int64)nAppend+100;
+ char *zNew = tdsqlite3_realloc64(pStr->z, nAlloc);
if( !zNew ){
return SQLITE_NOMEM;
}
@@ -162636,6 +185174,7 @@ static int fts3SnippetShift(
for(nLeft=0; !(hlmask & ((u64)1 << nLeft)); nLeft++);
for(nRight=0; !(hlmask & ((u64)1 << (nSnippet-1-nRight))); nRight++);
+ assert( (nSnippet-1-nRight)<=63 && (nSnippet-1-nRight)>=0 );
nDesired = (nLeft-nRight)/2;
/* Ideally, the start of the snippet should be pushed forward in the
@@ -162649,14 +185188,14 @@ static int fts3SnippetShift(
int nShift; /* Number of tokens to shift snippet by */
int iCurrent = 0; /* Token counter */
int rc; /* Return Code */
- sqlite3_tokenizer_module *pMod;
- sqlite3_tokenizer_cursor *pC;
- pMod = (sqlite3_tokenizer_module *)pTab->pTokenizer->pModule;
+ tdsqlite3_tokenizer_module *pMod;
+ tdsqlite3_tokenizer_cursor *pC;
+ pMod = (tdsqlite3_tokenizer_module *)pTab->pTokenizer->pModule;
/* Open a cursor on zDoc/nDoc. Check if there are (nSnippet+nDesired)
** or more tokens in zDoc/nDoc.
*/
- rc = sqlite3Fts3OpenTokenizer(pTab->pTokenizer, iLangid, zDoc, nDoc, &pC);
+ rc = tdsqlite3Fts3OpenTokenizer(pTab->pTokenizer, iLangid, zDoc, nDoc, &pC);
if( rc!=SQLITE_OK ){
return rc;
}
@@ -162703,21 +185242,21 @@ static int fts3SnippetText(
int iPos = pFragment->iPos; /* First token of snippet */
u64 hlmask = pFragment->hlmask; /* Highlight-mask for snippet */
int iCol = pFragment->iCol+1; /* Query column to extract text from */
- sqlite3_tokenizer_module *pMod; /* Tokenizer module methods object */
- sqlite3_tokenizer_cursor *pC; /* Tokenizer cursor open on zDoc/nDoc */
+ tdsqlite3_tokenizer_module *pMod; /* Tokenizer module methods object */
+ tdsqlite3_tokenizer_cursor *pC; /* Tokenizer cursor open on zDoc/nDoc */
- zDoc = (const char *)sqlite3_column_text(pCsr->pStmt, iCol);
+ zDoc = (const char *)tdsqlite3_column_text(pCsr->pStmt, iCol);
if( zDoc==0 ){
- if( sqlite3_column_type(pCsr->pStmt, iCol)!=SQLITE_NULL ){
+ if( tdsqlite3_column_type(pCsr->pStmt, iCol)!=SQLITE_NULL ){
return SQLITE_NOMEM;
}
return SQLITE_OK;
}
- nDoc = sqlite3_column_bytes(pCsr->pStmt, iCol);
+ nDoc = tdsqlite3_column_bytes(pCsr->pStmt, iCol);
/* Open a token cursor on the document. */
- pMod = (sqlite3_tokenizer_module *)pTab->pTokenizer->pModule;
- rc = sqlite3Fts3OpenTokenizer(pTab->pTokenizer, pCsr->iLangid, zDoc,nDoc,&pC);
+ pMod = (tdsqlite3_tokenizer_module *)pTab->pTokenizer->pModule;
+ rc = tdsqlite3Fts3OpenTokenizer(pTab->pTokenizer, pCsr->iLangid, zDoc,nDoc,&pC);
if( rc!=SQLITE_OK ){
return rc;
}
@@ -162828,7 +185367,7 @@ static int fts3ColumnlistCount(char **ppCollist){
/*
** This function gathers 'y' or 'b' data for a single phrase.
*/
-static void fts3ExprLHits(
+static int fts3ExprLHits(
Fts3Expr *pExpr, /* Phrase expression node */
MatchInfo *p /* Matchinfo context */
){
@@ -162858,25 +185397,29 @@ static void fts3ExprLHits(
if( *pIter!=0x01 ) break;
pIter++;
pIter += fts3GetVarint32(pIter, &iCol);
+ if( iCol>=p->nCol ) return FTS_CORRUPT_VTAB;
}
+ return SQLITE_OK;
}
/*
** Gather the results for matchinfo directives 'y' and 'b'.
*/
-static void fts3ExprLHitGather(
+static int fts3ExprLHitGather(
Fts3Expr *pExpr,
MatchInfo *p
){
+ int rc = SQLITE_OK;
assert( (pExpr->pLeft==0)==(pExpr->pRight==0) );
if( pExpr->bEof==0 && pExpr->iDocid==p->pCursor->iPrevId ){
if( pExpr->pLeft ){
- fts3ExprLHitGather(pExpr->pLeft, p);
- fts3ExprLHitGather(pExpr->pRight, p);
+ rc = fts3ExprLHitGather(pExpr->pLeft, p);
+ if( rc==SQLITE_OK ) rc = fts3ExprLHitGather(pExpr->pRight, p);
}else{
- fts3ExprLHits(pExpr, p);
+ rc = fts3ExprLHits(pExpr, p);
}
}
+ return rc;
}
/*
@@ -162912,7 +185455,7 @@ static int fts3ExprGlobalHitsCb(
void *pCtx /* Pointer to MatchInfo structure */
){
MatchInfo *p = (MatchInfo *)pCtx;
- return sqlite3Fts3EvalPhraseStats(
+ return tdsqlite3Fts3EvalPhraseStats(
p->pCursor, pExpr, &p->aMatchinfo[3*iPhrase*p->nCol]
);
}
@@ -162934,7 +185477,7 @@ static int fts3ExprLocalHitsCb(
for(i=0; i<p->nCol && rc==SQLITE_OK; i++){
char *pCsr;
- rc = sqlite3Fts3EvalPhrasePoslist(p->pCursor, pExpr, i, &pCsr);
+ rc = tdsqlite3Fts3EvalPhrasePoslist(p->pCursor, pExpr, i, &pCsr);
if( pCsr ){
p->aMatchinfo[iStart+i*3] = fts3ColumnlistCount(&pCsr);
}else{
@@ -162962,12 +185505,12 @@ static int fts3MatchinfoCheck(
){
return SQLITE_OK;
}
- sqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo request: %c", cArg);
+ tdsqlite3Fts3ErrMsg(pzErr, "unrecognized matchinfo request: %c", cArg);
return SQLITE_ERROR;
}
-static int fts3MatchinfoSize(MatchInfo *pInfo, char cArg){
- int nVal; /* Number of integers output by cArg */
+static size_t fts3MatchinfoSize(MatchInfo *pInfo, char cArg){
+ size_t nVal; /* Number of integers output by cArg */
switch( cArg ){
case FTS3_MATCHINFO_NDOC:
@@ -163001,27 +185544,39 @@ static int fts3MatchinfoSize(MatchInfo *pInfo, char cArg){
static int fts3MatchinfoSelectDoctotal(
Fts3Table *pTab,
- sqlite3_stmt **ppStmt,
- sqlite3_int64 *pnDoc,
- const char **paLen
+ tdsqlite3_stmt **ppStmt,
+ tdsqlite3_int64 *pnDoc,
+ const char **paLen,
+ const char **ppEnd
){
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
const char *a;
- sqlite3_int64 nDoc;
+ const char *pEnd;
+ tdsqlite3_int64 nDoc;
+ int n;
+
if( !*ppStmt ){
- int rc = sqlite3Fts3SelectDoctotal(pTab, ppStmt);
+ int rc = tdsqlite3Fts3SelectDoctotal(pTab, ppStmt);
if( rc!=SQLITE_OK ) return rc;
}
pStmt = *ppStmt;
- assert( sqlite3_data_count(pStmt)==1 );
+ assert( tdsqlite3_data_count(pStmt)==1 );
- a = sqlite3_column_blob(pStmt, 0);
- a += sqlite3Fts3GetVarint(a, &nDoc);
- if( nDoc==0 ) return FTS_CORRUPT_VTAB;
- *pnDoc = (u32)nDoc;
+ n = tdsqlite3_column_bytes(pStmt, 0);
+ a = tdsqlite3_column_blob(pStmt, 0);
+ if( a==0 ){
+ return FTS_CORRUPT_VTAB;
+ }
+ pEnd = a + n;
+ a += tdsqlite3Fts3GetVarintBounded(a, pEnd, &nDoc);
+ if( nDoc<=0 || a>pEnd ){
+ return FTS_CORRUPT_VTAB;
+ }
+ *pnDoc = nDoc;
if( paLen ) *paLen = a;
+ if( ppEnd ) *ppEnd = pEnd;
return SQLITE_OK;
}
@@ -163062,10 +185617,10 @@ static int fts3MatchinfoLcsCb(
*/
static int fts3LcsIteratorAdvance(LcsIterator *pIter){
char *pRead = pIter->pRead;
- sqlite3_int64 iRead;
+ tdsqlite3_int64 iRead;
int rc = 0;
- pRead += sqlite3Fts3GetVarint(pRead, &iRead);
+ pRead += tdsqlite3Fts3GetVarint(pRead, &iRead);
if( iRead==0 || iRead==1 ){
pRead = 0;
rc = 1;
@@ -163093,11 +185648,12 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){
int i;
int iCol;
int nToken = 0;
+ int rc = SQLITE_OK;
/* Allocate and populate the array of LcsIterator objects. The array
** contains one element for each matchable phrase in the query.
**/
- aIter = sqlite3_malloc(sizeof(LcsIterator) * pCsr->nPhrase);
+ aIter = tdsqlite3_malloc64(sizeof(LcsIterator) * pCsr->nPhrase);
if( !aIter ) return SQLITE_NOMEM;
memset(aIter, 0, sizeof(LcsIterator) * pCsr->nPhrase);
(void)fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter);
@@ -163113,13 +185669,16 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){
int nLive = 0; /* Number of iterators in aIter not at EOF */
for(i=0; i<pInfo->nPhrase; i++){
- int rc;
LcsIterator *pIt = &aIter[i];
- rc = sqlite3Fts3EvalPhrasePoslist(pCsr, pIt->pExpr, iCol, &pIt->pRead);
- if( rc!=SQLITE_OK ) return rc;
+ rc = tdsqlite3Fts3EvalPhrasePoslist(pCsr, pIt->pExpr, iCol, &pIt->pRead);
+ if( rc!=SQLITE_OK ) goto matchinfo_lcs_out;
if( pIt->pRead ){
pIt->iPos = pIt->iPosOffset;
- fts3LcsIteratorAdvance(&aIter[i]);
+ fts3LcsIteratorAdvance(pIt);
+ if( pIt->pRead==0 ){
+ rc = FTS_CORRUPT_VTAB;
+ goto matchinfo_lcs_out;
+ }
nLive++;
}
}
@@ -163151,8 +185710,9 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){
pInfo->aMatchinfo[iCol] = nLcs;
}
- sqlite3_free(aIter);
- return SQLITE_OK;
+ matchinfo_lcs_out:
+ tdsqlite3_free(aIter);
+ return rc;
}
/*
@@ -163181,7 +185741,7 @@ static int fts3MatchinfoValues(
int rc = SQLITE_OK;
int i;
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
- sqlite3_stmt *pSelect = 0;
+ tdsqlite3_stmt *pSelect = 0;
for(i=0; rc==SQLITE_OK && zArg[i]; i++){
pInfo->flag = zArg[i];
@@ -163196,24 +185756,29 @@ static int fts3MatchinfoValues(
case FTS3_MATCHINFO_NDOC:
if( bGlobal ){
- sqlite3_int64 nDoc = 0;
- rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, 0);
+ tdsqlite3_int64 nDoc = 0;
+ rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, 0, 0);
pInfo->aMatchinfo[0] = (u32)nDoc;
}
break;
case FTS3_MATCHINFO_AVGLENGTH:
if( bGlobal ){
- sqlite3_int64 nDoc; /* Number of rows in table */
+ tdsqlite3_int64 nDoc; /* Number of rows in table */
const char *a; /* Aggregate column length array */
+ const char *pEnd; /* First byte past end of length array */
- rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, &a);
+ rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, &a, &pEnd);
if( rc==SQLITE_OK ){
int iCol;
for(iCol=0; iCol<pInfo->nCol; iCol++){
u32 iVal;
- sqlite3_int64 nToken;
- a += sqlite3Fts3GetVarint(a, &nToken);
+ tdsqlite3_int64 nToken;
+ a += tdsqlite3Fts3GetVarint(a, &nToken);
+ if( a>pEnd ){
+ rc = SQLITE_CORRUPT_VTAB;
+ break;
+ }
iVal = (u32)(((u32)(nToken&0xffffffff)+nDoc/2)/nDoc);
pInfo->aMatchinfo[iCol] = iVal;
}
@@ -163222,18 +185787,23 @@ static int fts3MatchinfoValues(
break;
case FTS3_MATCHINFO_LENGTH: {
- sqlite3_stmt *pSelectDocsize = 0;
- rc = sqlite3Fts3SelectDocsize(pTab, pCsr->iPrevId, &pSelectDocsize);
+ tdsqlite3_stmt *pSelectDocsize = 0;
+ rc = tdsqlite3Fts3SelectDocsize(pTab, pCsr->iPrevId, &pSelectDocsize);
if( rc==SQLITE_OK ){
int iCol;
- const char *a = sqlite3_column_blob(pSelectDocsize, 0);
+ const char *a = tdsqlite3_column_blob(pSelectDocsize, 0);
+ const char *pEnd = a + tdsqlite3_column_bytes(pSelectDocsize, 0);
for(iCol=0; iCol<pInfo->nCol; iCol++){
- sqlite3_int64 nToken;
- a += sqlite3Fts3GetVarint(a, &nToken);
+ tdsqlite3_int64 nToken;
+ a += tdsqlite3Fts3GetVarintBounded(a, pEnd, &nToken);
+ if( a>pEnd ){
+ rc = SQLITE_CORRUPT_VTAB;
+ break;
+ }
pInfo->aMatchinfo[iCol] = (u32)nToken;
}
}
- sqlite3_reset(pSelectDocsize);
+ tdsqlite3_reset(pSelectDocsize);
break;
}
@@ -163246,9 +185816,9 @@ static int fts3MatchinfoValues(
case FTS3_MATCHINFO_LHITS_BM:
case FTS3_MATCHINFO_LHITS: {
- int nZero = fts3MatchinfoSize(pInfo, zArg[i]) * sizeof(u32);
+ size_t nZero = fts3MatchinfoSize(pInfo, zArg[i]) * sizeof(u32);
memset(pInfo->aMatchinfo, 0, nZero);
- fts3ExprLHitGather(pCsr->pExpr, pInfo);
+ rc = fts3ExprLHitGather(pCsr->pExpr, pInfo);
break;
}
@@ -163260,11 +185830,11 @@ static int fts3MatchinfoValues(
if( rc!=SQLITE_OK ) break;
if( bGlobal ){
if( pCsr->pDeferred ){
- rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &pInfo->nDoc, 0);
+ rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &pInfo->nDoc,0,0);
if( rc!=SQLITE_OK ) break;
}
rc = fts3ExprIterate(pExpr, fts3ExprGlobalHitsCb,(void*)pInfo);
- sqlite3Fts3EvalTestDeferred(pCsr, &rc);
+ tdsqlite3Fts3EvalTestDeferred(pCsr, &rc);
if( rc!=SQLITE_OK ) break;
}
(void)fts3ExprIterate(pExpr, fts3ExprLocalHitsCb,(void*)pInfo);
@@ -163275,7 +185845,7 @@ static int fts3MatchinfoValues(
pInfo->aMatchinfo += fts3MatchinfoSize(pInfo, zArg[i]);
}
- sqlite3_reset(pSelect);
+ tdsqlite3_reset(pSelect);
return rc;
}
@@ -163285,7 +185855,7 @@ static int fts3MatchinfoValues(
** 'matchinfo' data is an array of 32-bit unsigned integers (C type u32).
*/
static void fts3GetMatchinfo(
- sqlite3_context *pCtx, /* Return results here */
+ tdsqlite3_context *pCtx, /* Return results here */
Fts3Cursor *pCsr, /* FTS3 Cursor object */
const char *zArg /* Second argument to matchinfo() function */
){
@@ -163305,7 +185875,7 @@ static void fts3GetMatchinfo(
** cache does not match the format string for this request, discard
** the cached data. */
if( pCsr->pMIBuffer && strcmp(pCsr->pMIBuffer->zMatchinfo, zArg) ){
- sqlite3Fts3MIBufferFree(pCsr->pMIBuffer);
+ tdsqlite3Fts3MIBufferFree(pCsr->pMIBuffer);
pCsr->pMIBuffer = 0;
}
@@ -163315,7 +185885,7 @@ static void fts3GetMatchinfo(
** initialize those elements that are constant for every row.
*/
if( pCsr->pMIBuffer==0 ){
- int nMatchinfo = 0; /* Number of u32 elements in match-info */
+ size_t nMatchinfo = 0; /* Number of u32 elements in match-info */
int i; /* Used to iterate through zArg */
/* Determine the number of phrases in the query */
@@ -163326,8 +185896,8 @@ static void fts3GetMatchinfo(
for(i=0; zArg[i]; i++){
char *zErr = 0;
if( fts3MatchinfoCheck(pTab, zArg[i], &zErr) ){
- sqlite3_result_error(pCtx, zErr, -1);
- sqlite3_free(zErr);
+ tdsqlite3_result_error(pCtx, zErr, -1);
+ tdsqlite3_free(zErr);
return;
}
nMatchinfo += fts3MatchinfoSize(&sInfo, zArg[i]);
@@ -163358,19 +185928,19 @@ static void fts3GetMatchinfo(
}
if( rc!=SQLITE_OK ){
- sqlite3_result_error_code(pCtx, rc);
+ tdsqlite3_result_error_code(pCtx, rc);
if( xDestroyOut ) xDestroyOut(aOut);
}else{
int n = pCsr->pMIBuffer->nElem * sizeof(u32);
- sqlite3_result_blob(pCtx, aOut, n, xDestroyOut);
+ tdsqlite3_result_blob(pCtx, aOut, n, xDestroyOut);
}
}
/*
** Implementation of snippet() function.
*/
-SQLITE_PRIVATE void sqlite3Fts3Snippet(
- sqlite3_context *pCtx, /* SQLite function call context */
+SQLITE_PRIVATE void tdsqlite3Fts3Snippet(
+ tdsqlite3_context *pCtx, /* SQLite function call context */
Fts3Cursor *pCsr, /* Cursor object */
const char *zStart, /* Snippet start text - "<b>" */
const char *zEnd, /* Snippet end text - "</b>" */
@@ -163396,10 +185966,14 @@ SQLITE_PRIVATE void sqlite3Fts3Snippet(
int nFToken = -1; /* Number of tokens in each fragment */
if( !pCsr->pExpr ){
- sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC);
+ tdsqlite3_result_text(pCtx, "", 0, SQLITE_STATIC);
return;
}
+ /* Limit the snippet length to 64 tokens. */
+ if( nToken<-64 ) nToken = -64;
+ if( nToken>+64 ) nToken = +64;
+
for(nSnippet=1; 1; nSnippet++){
int iSnip; /* Loop counter 0..nSnippet-1 */
@@ -163458,12 +186032,12 @@ SQLITE_PRIVATE void sqlite3Fts3Snippet(
}
snippet_out:
- sqlite3Fts3SegmentsClose(pTab);
+ tdsqlite3Fts3SegmentsClose(pTab);
if( rc!=SQLITE_OK ){
- sqlite3_result_error_code(pCtx, rc);
- sqlite3_free(res.z);
+ tdsqlite3_result_error_code(pCtx, rc);
+ tdsqlite3_free(res.z);
}else{
- sqlite3_result_text(pCtx, res.z, -1, sqlite3_free);
+ tdsqlite3_result_text(pCtx, res.z, -1, tdsqlite3_free);
}
}
@@ -163481,12 +186055,12 @@ struct TermOffsetCtx {
Fts3Cursor *pCsr;
int iCol; /* Column of table to populate aTerm for */
int iTerm;
- sqlite3_int64 iDocid;
+ tdsqlite3_int64 iDocid;
TermOffset *aTerm;
};
/*
-** This function is an fts3ExprIterate() callback used by sqlite3Fts3Offsets().
+** This function is an fts3ExprIterate() callback used by tdsqlite3Fts3Offsets().
*/
static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){
TermOffsetCtx *p = (TermOffsetCtx *)ctx;
@@ -163497,11 +186071,11 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){
int rc;
UNUSED_PARAMETER(iPhrase);
- rc = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pList);
+ rc = tdsqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pList);
nTerm = pExpr->pPhrase->nToken;
if( pList ){
fts3GetDeltaPosition(&pList, &iPos);
- assert( iPos>=0 );
+ assert_fts3_nc( iPos>=0 );
}
for(iTerm=0; iTerm<nTerm; iTerm++){
@@ -163517,12 +186091,12 @@ static int fts3ExprTermOffsetInit(Fts3Expr *pExpr, int iPhrase, void *ctx){
/*
** Implementation of offsets() function.
*/
-SQLITE_PRIVATE void sqlite3Fts3Offsets(
- sqlite3_context *pCtx, /* SQLite function call context */
+SQLITE_PRIVATE void tdsqlite3Fts3Offsets(
+ tdsqlite3_context *pCtx, /* SQLite function call context */
Fts3Cursor *pCsr /* Cursor object */
){
Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab;
- sqlite3_tokenizer_module const *pMod = pTab->pTokenizer->pModule;
+ tdsqlite3_tokenizer_module const *pMod = pTab->pTokenizer->pModule;
int rc; /* Return Code */
int nToken; /* Number of tokens in query */
int iCol; /* Column currently being processed */
@@ -163530,7 +186104,7 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets(
TermOffsetCtx sCtx; /* Context for fts3ExprTermOffsetInit() */
if( !pCsr->pExpr ){
- sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC);
+ tdsqlite3_result_text(pCtx, "", 0, SQLITE_STATIC);
return;
}
@@ -163542,7 +186116,7 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets(
if( rc!=SQLITE_OK ) goto offsets_out;
/* Allocate the array of TermOffset iterators. */
- sCtx.aTerm = (TermOffset *)sqlite3_malloc(sizeof(TermOffset)*nToken);
+ sCtx.aTerm = (TermOffset *)tdsqlite3_malloc64(sizeof(TermOffset)*nToken);
if( 0==sCtx.aTerm ){
rc = SQLITE_NOMEM;
goto offsets_out;
@@ -163554,7 +186128,7 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets(
** string-buffer res for each column.
*/
for(iCol=0; iCol<pTab->nColumn; iCol++){
- sqlite3_tokenizer_cursor *pC; /* Tokenizer cursor */
+ tdsqlite3_tokenizer_cursor *pC; /* Tokenizer cursor */
const char *ZDUMMY; /* Dummy argument used with xNext() */
int NDUMMY = 0; /* Dummy argument used with xNext() */
int iStart = 0;
@@ -163577,10 +186151,10 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets(
** needs to transform the data from utf-16 to utf-8), return SQLITE_NOMEM
** to the caller.
*/
- zDoc = (const char *)sqlite3_column_text(pCsr->pStmt, iCol+1);
- nDoc = sqlite3_column_bytes(pCsr->pStmt, iCol+1);
+ zDoc = (const char *)tdsqlite3_column_text(pCsr->pStmt, iCol+1);
+ nDoc = tdsqlite3_column_bytes(pCsr->pStmt, iCol+1);
if( zDoc==0 ){
- if( sqlite3_column_type(pCsr->pStmt, iCol+1)==SQLITE_NULL ){
+ if( tdsqlite3_column_type(pCsr->pStmt, iCol+1)==SQLITE_NULL ){
continue;
}
rc = SQLITE_NOMEM;
@@ -163588,7 +186162,7 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets(
}
/* Initialize a tokenizer iterator to iterate through column iCol. */
- rc = sqlite3Fts3OpenTokenizer(pTab->pTokenizer, pCsr->iLangid,
+ rc = tdsqlite3Fts3OpenTokenizer(pTab->pTokenizer, pCsr->iLangid,
zDoc, nDoc, &pC
);
if( rc!=SQLITE_OK ) goto offsets_out;
@@ -163611,7 +186185,7 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets(
/* All offsets for this column have been gathered. */
rc = SQLITE_DONE;
}else{
- assert( iCurrent<=iMinPos );
+ assert_fts3_nc( iCurrent<=iMinPos );
if( 0==(0xFE&*pTerm->pList) ){
pTerm->pList = 0;
}else{
@@ -163622,7 +186196,7 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets(
}
if( rc==SQLITE_OK ){
char aBuffer[64];
- sqlite3_snprintf(sizeof(aBuffer), aBuffer,
+ tdsqlite3_snprintf(sizeof(aBuffer), aBuffer,
"%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart
);
rc = fts3StringAppend(&res, aBuffer, -1);
@@ -163640,14 +186214,14 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets(
}
offsets_out:
- sqlite3_free(sCtx.aTerm);
+ tdsqlite3_free(sCtx.aTerm);
assert( rc!=SQLITE_DONE );
- sqlite3Fts3SegmentsClose(pTab);
+ tdsqlite3Fts3SegmentsClose(pTab);
if( rc!=SQLITE_OK ){
- sqlite3_result_error_code(pCtx, rc);
- sqlite3_free(res.z);
+ tdsqlite3_result_error_code(pCtx, rc);
+ tdsqlite3_free(res.z);
}else{
- sqlite3_result_text(pCtx, res.z, res.n-1, sqlite3_free);
+ tdsqlite3_result_text(pCtx, res.z, res.n-1, tdsqlite3_free);
}
return;
}
@@ -163655,8 +186229,8 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets(
/*
** Implementation of matchinfo() function.
*/
-SQLITE_PRIVATE void sqlite3Fts3Matchinfo(
- sqlite3_context *pContext, /* Function call context */
+SQLITE_PRIVATE void tdsqlite3Fts3Matchinfo(
+ tdsqlite3_context *pContext, /* Function call context */
Fts3Cursor *pCsr, /* FTS3 table cursor */
const char *zArg /* Second arg to matchinfo() function */
){
@@ -163670,12 +186244,12 @@ SQLITE_PRIVATE void sqlite3Fts3Matchinfo(
}
if( !pCsr->pExpr ){
- sqlite3_result_blob(pContext, "", 0, SQLITE_STATIC);
+ tdsqlite3_result_blob(pContext, "", 0, SQLITE_STATIC);
return;
}else{
/* Retrieve matchinfo() data. */
fts3GetMatchinfo(pContext, pCsr, zFormat);
- sqlite3Fts3SegmentsClose(pTab);
+ tdsqlite3Fts3SegmentsClose(pTab);
}
}
@@ -163712,12 +186286,12 @@ SQLITE_PRIVATE void sqlite3Fts3Matchinfo(
/*
** The following two macros - READ_UTF8 and WRITE_UTF8 - have been copied
-** from the sqlite3 source file utf.c. If this file is compiled as part
+** from the tdsqlite3 source file utf.c. If this file is compiled as part
** of the amalgamation, they are not required.
*/
#ifndef SQLITE_AMALGAMATION
-static const unsigned char sqlite3Utf8Trans1[] = {
+static const unsigned char tdsqlite3Utf8Trans1[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
@@ -163731,7 +186305,7 @@ static const unsigned char sqlite3Utf8Trans1[] = {
#define READ_UTF8(zIn, zTerm, c) \
c = *(zIn++); \
if( c>=0xc0 ){ \
- c = sqlite3Utf8Trans1[c-0xc0]; \
+ c = tdsqlite3Utf8Trans1[c-0xc0]; \
while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \
c = (c<<6) + (0x3f & *(zIn++)); \
} \
@@ -163766,14 +186340,14 @@ typedef struct unicode_tokenizer unicode_tokenizer;
typedef struct unicode_cursor unicode_cursor;
struct unicode_tokenizer {
- sqlite3_tokenizer base;
- int bRemoveDiacritic;
+ tdsqlite3_tokenizer base;
+ int eRemoveDiacritic;
int nException;
int *aiException;
};
struct unicode_cursor {
- sqlite3_tokenizer_cursor base;
+ tdsqlite3_tokenizer_cursor base;
const unsigned char *aInput; /* Input text being tokenized */
int nInput; /* Size of aInput[] in bytes */
int iOff; /* Current offset within aInput[] */
@@ -163786,11 +186360,11 @@ struct unicode_cursor {
/*
** Destroy a tokenizer allocated by unicodeCreate().
*/
-static int unicodeDestroy(sqlite3_tokenizer *pTokenizer){
+static int unicodeDestroy(tdsqlite3_tokenizer *pTokenizer){
if( pTokenizer ){
unicode_tokenizer *p = (unicode_tokenizer *)pTokenizer;
- sqlite3_free(p->aiException);
- sqlite3_free(p);
+ tdsqlite3_free(p->aiException);
+ tdsqlite3_free(p);
}
return SQLITE_OK;
}
@@ -163802,13 +186376,13 @@ static int unicodeDestroy(sqlite3_tokenizer *pTokenizer){
** token characters (if bAlnum==1).
**
** For each codepoint in the zIn/nIn string, this function checks if the
-** sqlite3FtsUnicodeIsalnum() function already returns the desired result.
+** tdsqlite3FtsUnicodeIsalnum() function already returns the desired result.
** If so, no action is taken. Otherwise, the codepoint is added to the
** unicode_tokenizer.aiException[] array. For the purposes of tokenization,
-** the return value of sqlite3FtsUnicodeIsalnum() is inverted for all
+** the return value of tdsqlite3FtsUnicodeIsalnum() is inverted for all
** codepoints in the aiException[] array.
**
-** If a standalone diacritic mark (one that sqlite3FtsUnicodeIsdiacritic()
+** If a standalone diacritic mark (one that tdsqlite3FtsUnicodeIsdiacritic()
** identifies as a diacritic) occurs in the zIn/nIn string it is ignored.
** It is not possible to change the behavior of the tokenizer with respect
** to these codepoints.
@@ -163821,16 +186395,16 @@ static int unicodeAddExceptions(
){
const unsigned char *z = (const unsigned char *)zIn;
const unsigned char *zTerm = &z[nIn];
- int iCode;
+ unsigned int iCode;
int nEntry = 0;
assert( bAlnum==0 || bAlnum==1 );
while( z<zTerm ){
READ_UTF8(z, zTerm, iCode);
- assert( (sqlite3FtsUnicodeIsalnum(iCode) & 0xFFFFFFFE)==0 );
- if( sqlite3FtsUnicodeIsalnum(iCode)!=bAlnum
- && sqlite3FtsUnicodeIsdiacritic(iCode)==0
+ assert( (tdsqlite3FtsUnicodeIsalnum((int)iCode) & 0xFFFFFFFE)==0 );
+ if( tdsqlite3FtsUnicodeIsalnum((int)iCode)!=bAlnum
+ && tdsqlite3FtsUnicodeIsdiacritic((int)iCode)==0
){
nEntry++;
}
@@ -163840,20 +186414,20 @@ static int unicodeAddExceptions(
int *aNew; /* New aiException[] array */
int nNew; /* Number of valid entries in array aNew[] */
- aNew = sqlite3_realloc(p->aiException, (p->nException+nEntry)*sizeof(int));
+ aNew = tdsqlite3_realloc64(p->aiException,(p->nException+nEntry)*sizeof(int));
if( aNew==0 ) return SQLITE_NOMEM;
nNew = p->nException;
z = (const unsigned char *)zIn;
while( z<zTerm ){
READ_UTF8(z, zTerm, iCode);
- if( sqlite3FtsUnicodeIsalnum(iCode)!=bAlnum
- && sqlite3FtsUnicodeIsdiacritic(iCode)==0
+ if( tdsqlite3FtsUnicodeIsalnum((int)iCode)!=bAlnum
+ && tdsqlite3FtsUnicodeIsdiacritic((int)iCode)==0
){
int i, j;
- for(i=0; i<nNew && aNew[i]<iCode; i++);
+ for(i=0; i<nNew && aNew[i]<(int)iCode; i++);
for(j=nNew; j>i; j--) aNew[j] = aNew[j-1];
- aNew[i] = iCode;
+ aNew[i] = (int)iCode;
nNew++;
}
}
@@ -163893,8 +186467,8 @@ static int unicodeIsException(unicode_tokenizer *p, int iCode){
** considered a token character (not a separator).
*/
static int unicodeIsAlnum(unicode_tokenizer *p, int iCode){
- assert( (sqlite3FtsUnicodeIsalnum(iCode) & 0xFFFFFFFE)==0 );
- return sqlite3FtsUnicodeIsalnum(iCode) ^ unicodeIsException(p, iCode);
+ assert( (tdsqlite3FtsUnicodeIsalnum(iCode) & 0xFFFFFFFE)==0 );
+ return tdsqlite3FtsUnicodeIsalnum(iCode) ^ unicodeIsException(p, iCode);
}
/*
@@ -163903,26 +186477,29 @@ static int unicodeIsAlnum(unicode_tokenizer *p, int iCode){
static int unicodeCreate(
int nArg, /* Size of array argv[] */
const char * const *azArg, /* Tokenizer creation arguments */
- sqlite3_tokenizer **pp /* OUT: New tokenizer handle */
+ tdsqlite3_tokenizer **pp /* OUT: New tokenizer handle */
){
unicode_tokenizer *pNew; /* New tokenizer object */
int i;
int rc = SQLITE_OK;
- pNew = (unicode_tokenizer *) sqlite3_malloc(sizeof(unicode_tokenizer));
+ pNew = (unicode_tokenizer *) tdsqlite3_malloc(sizeof(unicode_tokenizer));
if( pNew==NULL ) return SQLITE_NOMEM;
memset(pNew, 0, sizeof(unicode_tokenizer));
- pNew->bRemoveDiacritic = 1;
+ pNew->eRemoveDiacritic = 1;
for(i=0; rc==SQLITE_OK && i<nArg; i++){
const char *z = azArg[i];
int n = (int)strlen(z);
if( n==19 && memcmp("remove_diacritics=1", z, 19)==0 ){
- pNew->bRemoveDiacritic = 1;
+ pNew->eRemoveDiacritic = 1;
}
else if( n==19 && memcmp("remove_diacritics=0", z, 19)==0 ){
- pNew->bRemoveDiacritic = 0;
+ pNew->eRemoveDiacritic = 0;
+ }
+ else if( n==19 && memcmp("remove_diacritics=2", z, 19)==0 ){
+ pNew->eRemoveDiacritic = 2;
}
else if( n>=11 && memcmp("tokenchars=", z, 11)==0 ){
rc = unicodeAddExceptions(pNew, 1, &z[11], n-11);
@@ -163937,10 +186514,10 @@ static int unicodeCreate(
}
if( rc!=SQLITE_OK ){
- unicodeDestroy((sqlite3_tokenizer *)pNew);
+ unicodeDestroy((tdsqlite3_tokenizer *)pNew);
pNew = 0;
}
- *pp = (sqlite3_tokenizer *)pNew;
+ *pp = (tdsqlite3_tokenizer *)pNew;
return rc;
}
@@ -163951,14 +186528,14 @@ static int unicodeCreate(
** *ppCursor.
*/
static int unicodeOpen(
- sqlite3_tokenizer *p, /* The tokenizer */
+ tdsqlite3_tokenizer *p, /* The tokenizer */
const char *aInput, /* Input string */
int nInput, /* Size of string aInput in bytes */
- sqlite3_tokenizer_cursor **pp /* OUT: New cursor object */
+ tdsqlite3_tokenizer_cursor **pp /* OUT: New cursor object */
){
unicode_cursor *pCsr;
- pCsr = (unicode_cursor *)sqlite3_malloc(sizeof(unicode_cursor));
+ pCsr = (unicode_cursor *)tdsqlite3_malloc(sizeof(unicode_cursor));
if( pCsr==0 ){
return SQLITE_NOMEM;
}
@@ -163982,10 +186559,10 @@ static int unicodeOpen(
** Close a tokenization cursor previously opened by a call to
** simpleOpen() above.
*/
-static int unicodeClose(sqlite3_tokenizer_cursor *pCursor){
+static int unicodeClose(tdsqlite3_tokenizer_cursor *pCursor){
unicode_cursor *pCsr = (unicode_cursor *) pCursor;
- sqlite3_free(pCsr->zToken);
- sqlite3_free(pCsr);
+ tdsqlite3_free(pCsr->zToken);
+ tdsqlite3_free(pCsr);
return SQLITE_OK;
}
@@ -163994,7 +186571,7 @@ static int unicodeClose(sqlite3_tokenizer_cursor *pCursor){
** have been opened by a prior call to simpleOpen().
*/
static int unicodeNext(
- sqlite3_tokenizer_cursor *pC, /* Cursor returned by simpleOpen */
+ tdsqlite3_tokenizer_cursor *pC, /* Cursor returned by simpleOpen */
const char **paToken, /* OUT: Token text */
int *pnToken, /* OUT: Number of bytes at *paToken */
int *piStart, /* OUT: Starting offset of token */
@@ -164003,7 +186580,7 @@ static int unicodeNext(
){
unicode_cursor *pCsr = (unicode_cursor *)pC;
unicode_tokenizer *p = ((unicode_tokenizer *)pCsr->base.pTokenizer);
- int iCode = 0;
+ unsigned int iCode = 0;
char *zOut;
const unsigned char *z = &pCsr->aInput[pCsr->iOff];
const unsigned char *zStart = z;
@@ -164015,7 +186592,7 @@ static int unicodeNext(
** the input. */
while( z<zTerm ){
READ_UTF8(z, zTerm, iCode);
- if( unicodeIsAlnum(p, iCode) ) break;
+ if( unicodeIsAlnum(p, (int)iCode) ) break;
zStart = z;
}
if( zStart>=zTerm ) return SQLITE_DONE;
@@ -164026,7 +186603,7 @@ static int unicodeNext(
/* Grow the output buffer if required. */
if( (zOut-pCsr->zToken)>=(pCsr->nAlloc-4) ){
- char *zNew = sqlite3_realloc(pCsr->zToken, pCsr->nAlloc+64);
+ char *zNew = tdsqlite3_realloc64(pCsr->zToken, pCsr->nAlloc+64);
if( !zNew ) return SQLITE_NOMEM;
zOut = &zNew[zOut - pCsr->zToken];
pCsr->zToken = zNew;
@@ -164035,7 +186612,7 @@ static int unicodeNext(
/* Write the folded case of the last character read to the output */
zEnd = z;
- iOut = sqlite3FtsUnicodeFold(iCode, p->bRemoveDiacritic);
+ iOut = tdsqlite3FtsUnicodeFold((int)iCode, p->eRemoveDiacritic);
if( iOut ){
WRITE_UTF8(zOut, iOut);
}
@@ -164043,8 +186620,8 @@ static int unicodeNext(
/* If the cursor is not at EOF, read the next character */
if( z>=zTerm ) break;
READ_UTF8(z, zTerm, iCode);
- }while( unicodeIsAlnum(p, iCode)
- || sqlite3FtsUnicodeIsdiacritic(iCode)
+ }while( unicodeIsAlnum(p, (int)iCode)
+ || tdsqlite3FtsUnicodeIsdiacritic((int)iCode)
);
/* Set the output variables and return. */
@@ -164058,11 +186635,11 @@ static int unicodeNext(
}
/*
-** Set *ppModule to a pointer to the sqlite3_tokenizer_module
+** Set *ppModule to a pointer to the tdsqlite3_tokenizer_module
** structure for the unicode tokenizer.
*/
-SQLITE_PRIVATE void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const **ppModule){
- static const sqlite3_tokenizer_module module = {
+SQLITE_PRIVATE void tdsqlite3Fts3UnicodeTokenizer(tdsqlite3_tokenizer_module const **ppModule){
+ static const tdsqlite3_tokenizer_module module = {
0,
unicodeCreate,
unicodeDestroy,
@@ -164080,7 +186657,7 @@ SQLITE_PRIVATE void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const *
/************** End of fts3_unicode.c ****************************************/
/************** Begin file fts3_unicode2.c ***********************************/
/*
-** 2012 May 25
+** 2012-05-25
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
@@ -164108,7 +186685,7 @@ SQLITE_PRIVATE void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const *
** The results are undefined if the value passed to this function
** is less than zero.
*/
-SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int c){
+SQLITE_PRIVATE int tdsqlite3FtsUnicodeIsalnum(int c){
/* Each unsigned integer in the following array corresponds to a contiguous
** range of unicode codepoints that are not either letters or numbers (i.e.
** codepoints for which this function should return 0).
@@ -164208,9 +186785,9 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int c){
0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001,
};
- if( c<128 ){
- return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 );
- }else if( c<(1<<22) ){
+ if( (unsigned int)c<128 ){
+ return ( (aAscii[c >> 5] & ((unsigned int)1 << (c & 0x001F)))==0 );
+ }else if( (unsigned int)c<(1<<22) ){
unsigned int key = (((unsigned int)c)<<10) | 0x000003FF;
int iRes = 0;
int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1;
@@ -164240,32 +186817,48 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int c){
** E"). The resuls of passing a codepoint that corresponds to an
** uppercase letter are undefined.
*/
-static int remove_diacritic(int c){
+static int remove_diacritic(int c, int bComplex){
unsigned short aDia[] = {
0, 1797, 1848, 1859, 1891, 1928, 1940, 1995,
2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286,
2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732,
2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336,
- 3456, 3696, 3712, 3728, 3744, 3896, 3912, 3928,
- 3968, 4008, 4040, 4106, 4138, 4170, 4202, 4234,
- 4266, 4296, 4312, 4344, 4408, 4424, 4472, 4504,
- 6148, 6198, 6264, 6280, 6360, 6429, 6505, 6529,
- 61448, 61468, 61534, 61592, 61642, 61688, 61704, 61726,
- 61784, 61800, 61836, 61880, 61914, 61948, 61998, 62122,
- 62154, 62200, 62218, 62302, 62364, 62442, 62478, 62536,
- 62554, 62584, 62604, 62640, 62648, 62656, 62664, 62730,
- 62924, 63050, 63082, 63274, 63390,
+ 3456, 3696, 3712, 3728, 3744, 3766, 3832, 3896,
+ 3912, 3928, 3944, 3968, 4008, 4040, 4056, 4106,
+ 4138, 4170, 4202, 4234, 4266, 4296, 4312, 4344,
+ 4408, 4424, 4442, 4472, 4488, 4504, 6148, 6198,
+ 6264, 6280, 6360, 6429, 6505, 6529, 61448, 61468,
+ 61512, 61534, 61592, 61610, 61642, 61672, 61688, 61704,
+ 61726, 61784, 61800, 61816, 61836, 61880, 61896, 61914,
+ 61948, 61998, 62062, 62122, 62154, 62184, 62200, 62218,
+ 62252, 62302, 62364, 62410, 62442, 62478, 62536, 62554,
+ 62584, 62604, 62640, 62648, 62656, 62664, 62730, 62766,
+ 62830, 62890, 62924, 62974, 63032, 63050, 63082, 63118,
+ 63182, 63242, 63274, 63310, 63368, 63390,
};
- char aChar[] = {
- '\0', 'a', 'c', 'e', 'i', 'n', 'o', 'u', 'y', 'y', 'a', 'c',
- 'd', 'e', 'e', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'o', 'r',
- 's', 't', 'u', 'u', 'w', 'y', 'z', 'o', 'u', 'a', 'i', 'o',
- 'u', 'g', 'k', 'o', 'j', 'g', 'n', 'a', 'e', 'i', 'o', 'r',
- 'u', 's', 't', 'h', 'a', 'e', 'o', 'y', '\0', '\0', '\0', '\0',
- '\0', '\0', '\0', '\0', 'a', 'b', 'd', 'd', 'e', 'f', 'g', 'h',
- 'h', 'i', 'k', 'l', 'l', 'm', 'n', 'p', 'r', 'r', 's', 't',
- 'u', 'v', 'w', 'w', 'x', 'y', 'z', 'h', 't', 'w', 'y', 'a',
- 'e', 'i', 'o', 'u', 'y',
+#define HIBIT ((unsigned char)0x80)
+ unsigned char aChar[] = {
+ '\0', 'a', 'c', 'e', 'i', 'n',
+ 'o', 'u', 'y', 'y', 'a', 'c',
+ 'd', 'e', 'e', 'g', 'h', 'i',
+ 'j', 'k', 'l', 'n', 'o', 'r',
+ 's', 't', 'u', 'u', 'w', 'y',
+ 'z', 'o', 'u', 'a', 'i', 'o',
+ 'u', 'u'|HIBIT, 'a'|HIBIT, 'g', 'k', 'o',
+ 'o'|HIBIT, 'j', 'g', 'n', 'a'|HIBIT, 'a',
+ 'e', 'i', 'o', 'r', 'u', 's',
+ 't', 'h', 'a', 'e', 'o'|HIBIT, 'o',
+ 'o'|HIBIT, 'y', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', 'a', 'b',
+ 'c'|HIBIT, 'd', 'd', 'e'|HIBIT, 'e', 'e'|HIBIT,
+ 'f', 'g', 'h', 'h', 'i', 'i'|HIBIT,
+ 'k', 'l', 'l'|HIBIT, 'l', 'm', 'n',
+ 'o'|HIBIT, 'p', 'r', 'r'|HIBIT, 'r', 's',
+ 's'|HIBIT, 't', 'u', 'u'|HIBIT, 'v', 'w',
+ 'w', 'x', 'y', 'z', 'h', 't',
+ 'w', 'y', 'a', 'a'|HIBIT, 'a'|HIBIT, 'a'|HIBIT,
+ 'e', 'e'|HIBIT, 'e'|HIBIT, 'i', 'o', 'o'|HIBIT,
+ 'o'|HIBIT, 'o'|HIBIT, 'u', 'u'|HIBIT, 'u'|HIBIT, 'y',
};
unsigned int key = (((unsigned int)c)<<3) | 0x00000007;
@@ -164282,7 +186875,8 @@ static int remove_diacritic(int c){
}
}
assert( key>=aDia[iRes] );
- return ((c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : (int)aChar[iRes]);
+ if( bComplex==0 && (aChar[iRes] & 0x80) ) return c;
+ return (c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : ((int)aChar[iRes] & 0x7F);
}
@@ -164290,13 +186884,13 @@ static int remove_diacritic(int c){
** Return true if the argument interpreted as a unicode codepoint
** is a diacritical modifier character.
*/
-SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int c){
+SQLITE_PRIVATE int tdsqlite3FtsUnicodeIsdiacritic(int c){
unsigned int mask0 = 0x08029FDF;
unsigned int mask1 = 0x000361F8;
if( c<768 || c>817 ) return 0;
return (c < 768+32) ?
- (mask0 & (1 << (c-768))) :
- (mask1 & (1 << (c-768-32)));
+ (mask0 & ((unsigned int)1 << (c-768))) :
+ (mask1 & ((unsigned int)1 << (c-768-32)));
}
@@ -164309,7 +186903,7 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int c){
** The results are undefined if the value passed to this function
** is less than zero.
*/
-SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){
+SQLITE_PRIVATE int tdsqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){
/* Each entry in the following array defines a rule for folding a range
** of codepoints to lower case. The rule applies to a range of nRange
** codepoints starting at codepoint iCode.
@@ -164403,16 +186997,17 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){
int ret = c;
- assert( c>=0 );
assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 );
if( c<128 ){
if( c>='A' && c<='Z' ) ret = c + ('a' - 'A');
}else if( c<65536 ){
+ const struct TableEntry *p;
int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1;
int iLo = 0;
int iRes = -1;
+ assert( c>aEntry[0].iCode );
while( iHi>=iLo ){
int iTest = (iHi + iLo) / 2;
int cmp = (c - aEntry[iTest].iCode);
@@ -164423,17 +187018,17 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){
iHi = iTest-1;
}
}
- assert( iRes<0 || c>=aEntry[iRes].iCode );
- if( iRes>=0 ){
- const struct TableEntry *p = &aEntry[iRes];
- if( c<(p->iCode + p->nRange) && 0==(0x01 & p->flags & (p->iCode ^ c)) ){
- ret = (c + (aiOff[p->flags>>1])) & 0x0000FFFF;
- assert( ret>0 );
- }
+ assert( iRes>=0 && c>=aEntry[iRes].iCode );
+ p = &aEntry[iRes];
+ if( c<(p->iCode + p->nRange) && 0==(0x01 & p->flags & (p->iCode ^ c)) ){
+ ret = (c + (aiOff[p->flags>>1])) & 0x0000FFFF;
+ assert( ret>0 );
}
- if( bRemoveDiacritic ) ret = remove_diacritic(ret);
+ if( eRemoveDiacritic ){
+ ret = remove_diacritic(ret, eRemoveDiacritic==2);
+ }
}
else if( c>=66560 && c<66600 ){
@@ -164446,6 +187041,2634 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){
#endif /* !defined(SQLITE_DISABLE_FTS3_UNICODE) */
/************** End of fts3_unicode2.c ***************************************/
+/************** Begin file json1.c *******************************************/
+/*
+** 2015-08-12
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This SQLite extension implements JSON functions. The interface is
+** modeled after MySQL JSON functions:
+**
+** https://dev.mysql.com/doc/refman/5.7/en/json.html
+**
+** For the time being, all JSON is stored as pure text. (We might add
+** a JSONB type in the future which stores a binary encoding of JSON in
+** a BLOB, but there is no support for JSONB in the current implementation.
+** This implementation parses JSON text at 250 MB/s, so it is hard to see
+** how JSONB might improve on that.)
+*/
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_JSON1)
+#if !defined(SQLITEINT_H)
+/* #include "tdsqlite3ext.h" */
+#endif
+SQLITE_EXTENSION_INIT1
+/* #include <assert.h> */
+/* #include <string.h> */
+/* #include <stdlib.h> */
+/* #include <stdarg.h> */
+
+/* Mark a function parameter as unused, to suppress nuisance compiler
+** warnings. */
+#ifndef UNUSED_PARAM
+# define UNUSED_PARAM(X) (void)(X)
+#endif
+
+#ifndef LARGEST_INT64
+# define LARGEST_INT64 (0xffffffff|(((tdsqlite3_int64)0x7fffffff)<<32))
+# define SMALLEST_INT64 (((tdsqlite3_int64)-1) - LARGEST_INT64)
+#endif
+
+/*
+** Versions of isspace(), isalnum() and isdigit() to which it is safe
+** to pass signed char values.
+*/
+#ifdef tdsqlite3Isdigit
+ /* Use the SQLite core versions if this routine is part of the
+ ** SQLite amalgamation */
+# define safe_isdigit(x) tdsqlite3Isdigit(x)
+# define safe_isalnum(x) tdsqlite3Isalnum(x)
+# define safe_isxdigit(x) tdsqlite3Isxdigit(x)
+#else
+ /* Use the standard library for separate compilation */
+#include <ctype.h> /* amalgamator: keep */
+# define safe_isdigit(x) isdigit((unsigned char)(x))
+# define safe_isalnum(x) isalnum((unsigned char)(x))
+# define safe_isxdigit(x) isxdigit((unsigned char)(x))
+#endif
+
+/*
+** Growing our own isspace() routine this way is twice as fast as
+** the library isspace() function, resulting in a 7% overall performance
+** increase for the parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os).
+*/
+static const char jsonIsSpace[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+#define safe_isspace(x) (jsonIsSpace[(unsigned char)x])
+
+#ifndef SQLITE_AMALGAMATION
+ /* Unsigned integer types. These are already defined in the sqliteInt.h,
+ ** but the definitions need to be repeated for separate compilation. */
+ typedef tdsqlite3_uint64 u64;
+ typedef unsigned int u32;
+ typedef unsigned short int u16;
+ typedef unsigned char u8;
+#endif
+
+/* Objects */
+typedef struct JsonString JsonString;
+typedef struct JsonNode JsonNode;
+typedef struct JsonParse JsonParse;
+
+/* An instance of this object represents a JSON string
+** under construction. Really, this is a generic string accumulator
+** that can be and is used to create strings other than JSON.
+*/
+struct JsonString {
+ tdsqlite3_context *pCtx; /* Function context - put error messages here */
+ char *zBuf; /* Append JSON content here */
+ u64 nAlloc; /* Bytes of storage available in zBuf[] */
+ u64 nUsed; /* Bytes of zBuf[] currently used */
+ u8 bStatic; /* True if zBuf is static space */
+ u8 bErr; /* True if an error has been encountered */
+ char zSpace[100]; /* Initial static space */
+};
+
+/* JSON type values
+*/
+#define JSON_NULL 0
+#define JSON_TRUE 1
+#define JSON_FALSE 2
+#define JSON_INT 3
+#define JSON_REAL 4
+#define JSON_STRING 5
+#define JSON_ARRAY 6
+#define JSON_OBJECT 7
+
+/* The "subtype" set for JSON values */
+#define JSON_SUBTYPE 74 /* Ascii for "J" */
+
+/*
+** Names of the various JSON types:
+*/
+static const char * const jsonType[] = {
+ "null", "true", "false", "integer", "real", "text", "array", "object"
+};
+
+/* Bit values for the JsonNode.jnFlag field
+*/
+#define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */
+#define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */
+#define JNODE_REMOVE 0x04 /* Do not output */
+#define JNODE_REPLACE 0x08 /* Replace with JsonNode.u.iReplace */
+#define JNODE_PATCH 0x10 /* Patch with JsonNode.u.pPatch */
+#define JNODE_APPEND 0x20 /* More ARRAY/OBJECT entries at u.iAppend */
+#define JNODE_LABEL 0x40 /* Is a label of an object */
+
+
+/* A single node of parsed JSON
+*/
+struct JsonNode {
+ u8 eType; /* One of the JSON_ type values */
+ u8 jnFlags; /* JNODE flags */
+ u32 n; /* Bytes of content, or number of sub-nodes */
+ union {
+ const char *zJContent; /* Content for INT, REAL, and STRING */
+ u32 iAppend; /* More terms for ARRAY and OBJECT */
+ u32 iKey; /* Key for ARRAY objects in json_tree() */
+ u32 iReplace; /* Replacement content for JNODE_REPLACE */
+ JsonNode *pPatch; /* Node chain of patch for JNODE_PATCH */
+ } u;
+};
+
+/* A completely parsed JSON string
+*/
+struct JsonParse {
+ u32 nNode; /* Number of slots of aNode[] used */
+ u32 nAlloc; /* Number of slots of aNode[] allocated */
+ JsonNode *aNode; /* Array of nodes containing the parse */
+ const char *zJson; /* Original JSON string */
+ u32 *aUp; /* Index of parent of each node */
+ u8 oom; /* Set to true if out of memory */
+ u8 nErr; /* Number of errors seen */
+ u16 iDepth; /* Nesting depth */
+ int nJson; /* Length of the zJson string in bytes */
+ u32 iHold; /* Replace cache line with the lowest iHold value */
+};
+
+/*
+** Maximum nesting depth of JSON for this implementation.
+**
+** This limit is needed to avoid a stack overflow in the recursive
+** descent parser. A depth of 2000 is far deeper than any sane JSON
+** should go.
+*/
+#define JSON_MAX_DEPTH 2000
+
+/**************************************************************************
+** Utility routines for dealing with JsonString objects
+**************************************************************************/
+
+/* Set the JsonString object to an empty string
+*/
+static void jsonZero(JsonString *p){
+ p->zBuf = p->zSpace;
+ p->nAlloc = sizeof(p->zSpace);
+ p->nUsed = 0;
+ p->bStatic = 1;
+}
+
+/* Initialize the JsonString object
+*/
+static void jsonInit(JsonString *p, tdsqlite3_context *pCtx){
+ p->pCtx = pCtx;
+ p->bErr = 0;
+ jsonZero(p);
+}
+
+
+/* Free all allocated memory and reset the JsonString object back to its
+** initial state.
+*/
+static void jsonReset(JsonString *p){
+ if( !p->bStatic ) tdsqlite3_free(p->zBuf);
+ jsonZero(p);
+}
+
+
+/* Report an out-of-memory (OOM) condition
+*/
+static void jsonOom(JsonString *p){
+ p->bErr = 1;
+ tdsqlite3_result_error_nomem(p->pCtx);
+ jsonReset(p);
+}
+
+/* Enlarge pJson->zBuf so that it can hold at least N more bytes.
+** Return zero on success. Return non-zero on an OOM error
+*/
+static int jsonGrow(JsonString *p, u32 N){
+ u64 nTotal = N<p->nAlloc ? p->nAlloc*2 : p->nAlloc+N+10;
+ char *zNew;
+ if( p->bStatic ){
+ if( p->bErr ) return 1;
+ zNew = tdsqlite3_malloc64(nTotal);
+ if( zNew==0 ){
+ jsonOom(p);
+ return SQLITE_NOMEM;
+ }
+ memcpy(zNew, p->zBuf, (size_t)p->nUsed);
+ p->zBuf = zNew;
+ p->bStatic = 0;
+ }else{
+ zNew = tdsqlite3_realloc64(p->zBuf, nTotal);
+ if( zNew==0 ){
+ jsonOom(p);
+ return SQLITE_NOMEM;
+ }
+ p->zBuf = zNew;
+ }
+ p->nAlloc = nTotal;
+ return SQLITE_OK;
+}
+
+/* Append N bytes from zIn onto the end of the JsonString string.
+*/
+static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){
+ if( (N+p->nUsed >= p->nAlloc) && jsonGrow(p,N)!=0 ) return;
+ memcpy(p->zBuf+p->nUsed, zIn, N);
+ p->nUsed += N;
+}
+
+/* Append formatted text (not to exceed N bytes) to the JsonString.
+*/
+static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){
+ va_list ap;
+ if( (p->nUsed + N >= p->nAlloc) && jsonGrow(p, N) ) return;
+ va_start(ap, zFormat);
+ tdsqlite3_vsnprintf(N, p->zBuf+p->nUsed, zFormat, ap);
+ va_end(ap);
+ p->nUsed += (int)strlen(p->zBuf+p->nUsed);
+}
+
+/* Append a single character
+*/
+static void jsonAppendChar(JsonString *p, char c){
+ if( p->nUsed>=p->nAlloc && jsonGrow(p,1)!=0 ) return;
+ p->zBuf[p->nUsed++] = c;
+}
+
+/* Append a comma separator to the output buffer, if the previous
+** character is not '[' or '{'.
+*/
+static void jsonAppendSeparator(JsonString *p){
+ char c;
+ if( p->nUsed==0 ) return;
+ c = p->zBuf[p->nUsed-1];
+ if( c!='[' && c!='{' ) jsonAppendChar(p, ',');
+}
+
+/* Append the N-byte string in zIn to the end of the JsonString string
+** under construction. Enclose the string in "..." and escape
+** any double-quotes or backslash characters contained within the
+** string.
+*/
+static void jsonAppendString(JsonString *p, const char *zIn, u32 N){
+ u32 i;
+ if( (N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0 ) return;
+ p->zBuf[p->nUsed++] = '"';
+ for(i=0; i<N; i++){
+ unsigned char c = ((unsigned const char*)zIn)[i];
+ if( c=='"' || c=='\\' ){
+ json_simple_escape:
+ if( (p->nUsed+N+3-i > p->nAlloc) && jsonGrow(p,N+3-i)!=0 ) return;
+ p->zBuf[p->nUsed++] = '\\';
+ }else if( c<=0x1f ){
+ static const char aSpecial[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ assert( sizeof(aSpecial)==32 );
+ assert( aSpecial['\b']=='b' );
+ assert( aSpecial['\f']=='f' );
+ assert( aSpecial['\n']=='n' );
+ assert( aSpecial['\r']=='r' );
+ assert( aSpecial['\t']=='t' );
+ if( aSpecial[c] ){
+ c = aSpecial[c];
+ goto json_simple_escape;
+ }
+ if( (p->nUsed+N+7+i > p->nAlloc) && jsonGrow(p,N+7-i)!=0 ) return;
+ p->zBuf[p->nUsed++] = '\\';
+ p->zBuf[p->nUsed++] = 'u';
+ p->zBuf[p->nUsed++] = '0';
+ p->zBuf[p->nUsed++] = '0';
+ p->zBuf[p->nUsed++] = '0' + (c>>4);
+ c = "0123456789abcdef"[c&0xf];
+ }
+ p->zBuf[p->nUsed++] = c;
+ }
+ p->zBuf[p->nUsed++] = '"';
+ assert( p->nUsed<p->nAlloc );
+}
+
+/*
+** Append a function parameter value to the JSON string under
+** construction.
+*/
+static void jsonAppendValue(
+ JsonString *p, /* Append to this JSON string */
+ tdsqlite3_value *pValue /* Value to append */
+){
+ switch( tdsqlite3_value_type(pValue) ){
+ case SQLITE_NULL: {
+ jsonAppendRaw(p, "null", 4);
+ break;
+ }
+ case SQLITE_INTEGER:
+ case SQLITE_FLOAT: {
+ const char *z = (const char*)tdsqlite3_value_text(pValue);
+ u32 n = (u32)tdsqlite3_value_bytes(pValue);
+ jsonAppendRaw(p, z, n);
+ break;
+ }
+ case SQLITE_TEXT: {
+ const char *z = (const char*)tdsqlite3_value_text(pValue);
+ u32 n = (u32)tdsqlite3_value_bytes(pValue);
+ if( tdsqlite3_value_subtype(pValue)==JSON_SUBTYPE ){
+ jsonAppendRaw(p, z, n);
+ }else{
+ jsonAppendString(p, z, n);
+ }
+ break;
+ }
+ default: {
+ if( p->bErr==0 ){
+ tdsqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1);
+ p->bErr = 2;
+ jsonReset(p);
+ }
+ break;
+ }
+ }
+}
+
+
+/* Make the JSON in p the result of the SQL function.
+*/
+static void jsonResult(JsonString *p){
+ if( p->bErr==0 ){
+ tdsqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed,
+ p->bStatic ? SQLITE_TRANSIENT : tdsqlite3_free,
+ SQLITE_UTF8);
+ jsonZero(p);
+ }
+ assert( p->bStatic );
+}
+
+/**************************************************************************
+** Utility routines for dealing with JsonNode and JsonParse objects
+**************************************************************************/
+
+/*
+** Return the number of consecutive JsonNode slots need to represent
+** the parsed JSON at pNode. The minimum answer is 1. For ARRAY and
+** OBJECT types, the number might be larger.
+**
+** Appended elements are not counted. The value returned is the number
+** by which the JsonNode counter should increment in order to go to the
+** next peer value.
+*/
+static u32 jsonNodeSize(JsonNode *pNode){
+ return pNode->eType>=JSON_ARRAY ? pNode->n+1 : 1;
+}
+
+/*
+** Reclaim all memory allocated by a JsonParse object. But do not
+** delete the JsonParse object itself.
+*/
+static void jsonParseReset(JsonParse *pParse){
+ tdsqlite3_free(pParse->aNode);
+ pParse->aNode = 0;
+ pParse->nNode = 0;
+ pParse->nAlloc = 0;
+ tdsqlite3_free(pParse->aUp);
+ pParse->aUp = 0;
+}
+
+/*
+** Free a JsonParse object that was obtained from tdsqlite3_malloc().
+*/
+static void jsonParseFree(JsonParse *pParse){
+ jsonParseReset(pParse);
+ tdsqlite3_free(pParse);
+}
+
+/*
+** Convert the JsonNode pNode into a pure JSON string and
+** append to pOut. Subsubstructure is also included. Return
+** the number of JsonNode objects that are encoded.
+*/
+static void jsonRenderNode(
+ JsonNode *pNode, /* The node to render */
+ JsonString *pOut, /* Write JSON here */
+ tdsqlite3_value **aReplace /* Replacement values */
+){
+ if( pNode->jnFlags & (JNODE_REPLACE|JNODE_PATCH) ){
+ if( pNode->jnFlags & JNODE_REPLACE ){
+ jsonAppendValue(pOut, aReplace[pNode->u.iReplace]);
+ return;
+ }
+ pNode = pNode->u.pPatch;
+ }
+ switch( pNode->eType ){
+ default: {
+ assert( pNode->eType==JSON_NULL );
+ jsonAppendRaw(pOut, "null", 4);
+ break;
+ }
+ case JSON_TRUE: {
+ jsonAppendRaw(pOut, "true", 4);
+ break;
+ }
+ case JSON_FALSE: {
+ jsonAppendRaw(pOut, "false", 5);
+ break;
+ }
+ case JSON_STRING: {
+ if( pNode->jnFlags & JNODE_RAW ){
+ jsonAppendString(pOut, pNode->u.zJContent, pNode->n);
+ break;
+ }
+ /* Fall through into the next case */
+ }
+ case JSON_REAL:
+ case JSON_INT: {
+ jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n);
+ break;
+ }
+ case JSON_ARRAY: {
+ u32 j = 1;
+ jsonAppendChar(pOut, '[');
+ for(;;){
+ while( j<=pNode->n ){
+ if( (pNode[j].jnFlags & JNODE_REMOVE)==0 ){
+ jsonAppendSeparator(pOut);
+ jsonRenderNode(&pNode[j], pOut, aReplace);
+ }
+ j += jsonNodeSize(&pNode[j]);
+ }
+ if( (pNode->jnFlags & JNODE_APPEND)==0 ) break;
+ pNode = &pNode[pNode->u.iAppend];
+ j = 1;
+ }
+ jsonAppendChar(pOut, ']');
+ break;
+ }
+ case JSON_OBJECT: {
+ u32 j = 1;
+ jsonAppendChar(pOut, '{');
+ for(;;){
+ while( j<=pNode->n ){
+ if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 ){
+ jsonAppendSeparator(pOut);
+ jsonRenderNode(&pNode[j], pOut, aReplace);
+ jsonAppendChar(pOut, ':');
+ jsonRenderNode(&pNode[j+1], pOut, aReplace);
+ }
+ j += 1 + jsonNodeSize(&pNode[j+1]);
+ }
+ if( (pNode->jnFlags & JNODE_APPEND)==0 ) break;
+ pNode = &pNode[pNode->u.iAppend];
+ j = 1;
+ }
+ jsonAppendChar(pOut, '}');
+ break;
+ }
+ }
+}
+
+/*
+** Return a JsonNode and all its descendents as a JSON string.
+*/
+static void jsonReturnJson(
+ JsonNode *pNode, /* Node to return */
+ tdsqlite3_context *pCtx, /* Return value for this function */
+ tdsqlite3_value **aReplace /* Array of replacement values */
+){
+ JsonString s;
+ jsonInit(&s, pCtx);
+ jsonRenderNode(pNode, &s, aReplace);
+ jsonResult(&s);
+ tdsqlite3_result_subtype(pCtx, JSON_SUBTYPE);
+}
+
+/*
+** Translate a single byte of Hex into an integer.
+** This routine only works if h really is a valid hexadecimal
+** character: 0..9a..fA..F
+*/
+static u8 jsonHexToInt(int h){
+ assert( (h>='0' && h<='9') || (h>='a' && h<='f') || (h>='A' && h<='F') );
+#ifdef SQLITE_EBCDIC
+ h += 9*(1&~(h>>4));
+#else
+ h += 9*(1&(h>>6));
+#endif
+ return (u8)(h & 0xf);
+}
+
+/*
+** Convert a 4-byte hex string into an integer
+*/
+static u32 jsonHexToInt4(const char *z){
+ u32 v;
+ assert( safe_isxdigit(z[0]) );
+ assert( safe_isxdigit(z[1]) );
+ assert( safe_isxdigit(z[2]) );
+ assert( safe_isxdigit(z[3]) );
+ v = (jsonHexToInt(z[0])<<12)
+ + (jsonHexToInt(z[1])<<8)
+ + (jsonHexToInt(z[2])<<4)
+ + jsonHexToInt(z[3]);
+ return v;
+}
+
+/*
+** Make the JsonNode the return value of the function.
+*/
+static void jsonReturn(
+ JsonNode *pNode, /* Node to return */
+ tdsqlite3_context *pCtx, /* Return value for this function */
+ tdsqlite3_value **aReplace /* Array of replacement values */
+){
+ switch( pNode->eType ){
+ default: {
+ assert( pNode->eType==JSON_NULL );
+ tdsqlite3_result_null(pCtx);
+ break;
+ }
+ case JSON_TRUE: {
+ tdsqlite3_result_int(pCtx, 1);
+ break;
+ }
+ case JSON_FALSE: {
+ tdsqlite3_result_int(pCtx, 0);
+ break;
+ }
+ case JSON_INT: {
+ tdsqlite3_int64 i = 0;
+ const char *z = pNode->u.zJContent;
+ if( z[0]=='-' ){ z++; }
+ while( z[0]>='0' && z[0]<='9' ){
+ unsigned v = *(z++) - '0';
+ if( i>=LARGEST_INT64/10 ){
+ if( i>LARGEST_INT64/10 ) goto int_as_real;
+ if( z[0]>='0' && z[0]<='9' ) goto int_as_real;
+ if( v==9 ) goto int_as_real;
+ if( v==8 ){
+ if( pNode->u.zJContent[0]=='-' ){
+ tdsqlite3_result_int64(pCtx, SMALLEST_INT64);
+ goto int_done;
+ }else{
+ goto int_as_real;
+ }
+ }
+ }
+ i = i*10 + v;
+ }
+ if( pNode->u.zJContent[0]=='-' ){ i = -i; }
+ tdsqlite3_result_int64(pCtx, i);
+ int_done:
+ break;
+ int_as_real: /* fall through to real */;
+ }
+ case JSON_REAL: {
+ double r;
+#ifdef SQLITE_AMALGAMATION
+ const char *z = pNode->u.zJContent;
+ tdsqlite3AtoF(z, &r, tdsqlite3Strlen30(z), SQLITE_UTF8);
+#else
+ r = strtod(pNode->u.zJContent, 0);
+#endif
+ tdsqlite3_result_double(pCtx, r);
+ break;
+ }
+ case JSON_STRING: {
+#if 0 /* Never happens because JNODE_RAW is only set by json_set(),
+ ** json_insert() and json_replace() and those routines do not
+ ** call jsonReturn() */
+ if( pNode->jnFlags & JNODE_RAW ){
+ tdsqlite3_result_text(pCtx, pNode->u.zJContent, pNode->n,
+ SQLITE_TRANSIENT);
+ }else
+#endif
+ assert( (pNode->jnFlags & JNODE_RAW)==0 );
+ if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){
+ /* JSON formatted without any backslash-escapes */
+ tdsqlite3_result_text(pCtx, pNode->u.zJContent+1, pNode->n-2,
+ SQLITE_TRANSIENT);
+ }else{
+ /* Translate JSON formatted string into raw text */
+ u32 i;
+ u32 n = pNode->n;
+ const char *z = pNode->u.zJContent;
+ char *zOut;
+ u32 j;
+ zOut = tdsqlite3_malloc( n+1 );
+ if( zOut==0 ){
+ tdsqlite3_result_error_nomem(pCtx);
+ break;
+ }
+ for(i=1, j=0; i<n-1; i++){
+ char c = z[i];
+ if( c!='\\' ){
+ zOut[j++] = c;
+ }else{
+ c = z[++i];
+ if( c=='u' ){
+ u32 v = jsonHexToInt4(z+i+1);
+ i += 4;
+ if( v==0 ) break;
+ if( v<=0x7f ){
+ zOut[j++] = (char)v;
+ }else if( v<=0x7ff ){
+ zOut[j++] = (char)(0xc0 | (v>>6));
+ zOut[j++] = 0x80 | (v&0x3f);
+ }else{
+ u32 vlo;
+ if( (v&0xfc00)==0xd800
+ && i<n-6
+ && z[i+1]=='\\'
+ && z[i+2]=='u'
+ && ((vlo = jsonHexToInt4(z+i+3))&0xfc00)==0xdc00
+ ){
+ /* We have a surrogate pair */
+ v = ((v&0x3ff)<<10) + (vlo&0x3ff) + 0x10000;
+ i += 6;
+ zOut[j++] = 0xf0 | (v>>18);
+ zOut[j++] = 0x80 | ((v>>12)&0x3f);
+ zOut[j++] = 0x80 | ((v>>6)&0x3f);
+ zOut[j++] = 0x80 | (v&0x3f);
+ }else{
+ zOut[j++] = 0xe0 | (v>>12);
+ zOut[j++] = 0x80 | ((v>>6)&0x3f);
+ zOut[j++] = 0x80 | (v&0x3f);
+ }
+ }
+ }else{
+ if( c=='b' ){
+ c = '\b';
+ }else if( c=='f' ){
+ c = '\f';
+ }else if( c=='n' ){
+ c = '\n';
+ }else if( c=='r' ){
+ c = '\r';
+ }else if( c=='t' ){
+ c = '\t';
+ }
+ zOut[j++] = c;
+ }
+ }
+ }
+ zOut[j] = 0;
+ tdsqlite3_result_text(pCtx, zOut, j, tdsqlite3_free);
+ }
+ break;
+ }
+ case JSON_ARRAY:
+ case JSON_OBJECT: {
+ jsonReturnJson(pNode, pCtx, aReplace);
+ break;
+ }
+ }
+}
+
+/* Forward reference */
+static int jsonParseAddNode(JsonParse*,u32,u32,const char*);
+
+/*
+** A macro to hint to the compiler that a function should not be
+** inlined.
+*/
+#if defined(__GNUC__)
+# define JSON_NOINLINE __attribute__((noinline))
+#elif defined(_MSC_VER) && _MSC_VER>=1310
+# define JSON_NOINLINE __declspec(noinline)
+#else
+# define JSON_NOINLINE
+#endif
+
+
+static JSON_NOINLINE int jsonParseAddNodeExpand(
+ JsonParse *pParse, /* Append the node to this object */
+ u32 eType, /* Node type */
+ u32 n, /* Content size or sub-node count */
+ const char *zContent /* Content */
+){
+ u32 nNew;
+ JsonNode *pNew;
+ assert( pParse->nNode>=pParse->nAlloc );
+ if( pParse->oom ) return -1;
+ nNew = pParse->nAlloc*2 + 10;
+ pNew = tdsqlite3_realloc64(pParse->aNode, sizeof(JsonNode)*nNew);
+ if( pNew==0 ){
+ pParse->oom = 1;
+ return -1;
+ }
+ pParse->nAlloc = nNew;
+ pParse->aNode = pNew;
+ assert( pParse->nNode<pParse->nAlloc );
+ return jsonParseAddNode(pParse, eType, n, zContent);
+}
+
+/*
+** Create a new JsonNode instance based on the arguments and append that
+** instance to the JsonParse. Return the index in pParse->aNode[] of the
+** new node, or -1 if a memory allocation fails.
+*/
+static int jsonParseAddNode(
+ JsonParse *pParse, /* Append the node to this object */
+ u32 eType, /* Node type */
+ u32 n, /* Content size or sub-node count */
+ const char *zContent /* Content */
+){
+ JsonNode *p;
+ if( pParse->nNode>=pParse->nAlloc ){
+ return jsonParseAddNodeExpand(pParse, eType, n, zContent);
+ }
+ p = &pParse->aNode[pParse->nNode];
+ p->eType = (u8)eType;
+ p->jnFlags = 0;
+ p->n = n;
+ p->u.zJContent = zContent;
+ return pParse->nNode++;
+}
+
+/*
+** Return true if z[] begins with 4 (or more) hexadecimal digits
+*/
+static int jsonIs4Hex(const char *z){
+ int i;
+ for(i=0; i<4; i++) if( !safe_isxdigit(z[i]) ) return 0;
+ return 1;
+}
+
+/*
+** Parse a single JSON value which begins at pParse->zJson[i]. Return the
+** index of the first character past the end of the value parsed.
+**
+** Return negative for a syntax error. Special cases: return -2 if the
+** first non-whitespace character is '}' and return -3 if the first
+** non-whitespace character is ']'.
+*/
+static int jsonParseValue(JsonParse *pParse, u32 i){
+ char c;
+ u32 j;
+ int iThis;
+ int x;
+ JsonNode *pNode;
+ const char *z = pParse->zJson;
+ while( safe_isspace(z[i]) ){ i++; }
+ if( (c = z[i])=='{' ){
+ /* Parse object */
+ iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0);
+ if( iThis<0 ) return -1;
+ for(j=i+1;;j++){
+ while( safe_isspace(z[j]) ){ j++; }
+ if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1;
+ x = jsonParseValue(pParse, j);
+ if( x<0 ){
+ pParse->iDepth--;
+ if( x==(-2) && pParse->nNode==(u32)iThis+1 ) return j+1;
+ return -1;
+ }
+ if( pParse->oom ) return -1;
+ pNode = &pParse->aNode[pParse->nNode-1];
+ if( pNode->eType!=JSON_STRING ) return -1;
+ pNode->jnFlags |= JNODE_LABEL;
+ j = x;
+ while( safe_isspace(z[j]) ){ j++; }
+ if( z[j]!=':' ) return -1;
+ j++;
+ x = jsonParseValue(pParse, j);
+ pParse->iDepth--;
+ if( x<0 ) return -1;
+ j = x;
+ while( safe_isspace(z[j]) ){ j++; }
+ c = z[j];
+ if( c==',' ) continue;
+ if( c!='}' ) return -1;
+ break;
+ }
+ pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1;
+ return j+1;
+ }else if( c=='[' ){
+ /* Parse array */
+ iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0);
+ if( iThis<0 ) return -1;
+ for(j=i+1;;j++){
+ while( safe_isspace(z[j]) ){ j++; }
+ if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1;
+ x = jsonParseValue(pParse, j);
+ pParse->iDepth--;
+ if( x<0 ){
+ if( x==(-3) && pParse->nNode==(u32)iThis+1 ) return j+1;
+ return -1;
+ }
+ j = x;
+ while( safe_isspace(z[j]) ){ j++; }
+ c = z[j];
+ if( c==',' ) continue;
+ if( c!=']' ) return -1;
+ break;
+ }
+ pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1;
+ return j+1;
+ }else if( c=='"' ){
+ /* Parse string */
+ u8 jnFlags = 0;
+ j = i+1;
+ for(;;){
+ c = z[j];
+ if( (c & ~0x1f)==0 ){
+ /* Control characters are not allowed in strings */
+ return -1;
+ }
+ if( c=='\\' ){
+ c = z[++j];
+ if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f'
+ || c=='n' || c=='r' || c=='t'
+ || (c=='u' && jsonIs4Hex(z+j+1)) ){
+ jnFlags = JNODE_ESCAPE;
+ }else{
+ return -1;
+ }
+ }else if( c=='"' ){
+ break;
+ }
+ j++;
+ }
+ jsonParseAddNode(pParse, JSON_STRING, j+1-i, &z[i]);
+ if( !pParse->oom ) pParse->aNode[pParse->nNode-1].jnFlags = jnFlags;
+ return j+1;
+ }else if( c=='n'
+ && strncmp(z+i,"null",4)==0
+ && !safe_isalnum(z[i+4]) ){
+ jsonParseAddNode(pParse, JSON_NULL, 0, 0);
+ return i+4;
+ }else if( c=='t'
+ && strncmp(z+i,"true",4)==0
+ && !safe_isalnum(z[i+4]) ){
+ jsonParseAddNode(pParse, JSON_TRUE, 0, 0);
+ return i+4;
+ }else if( c=='f'
+ && strncmp(z+i,"false",5)==0
+ && !safe_isalnum(z[i+5]) ){
+ jsonParseAddNode(pParse, JSON_FALSE, 0, 0);
+ return i+5;
+ }else if( c=='-' || (c>='0' && c<='9') ){
+ /* Parse number */
+ u8 seenDP = 0;
+ u8 seenE = 0;
+ assert( '-' < '0' );
+ if( c<='0' ){
+ j = c=='-' ? i+1 : i;
+ if( z[j]=='0' && z[j+1]>='0' && z[j+1]<='9' ) return -1;
+ }
+ j = i+1;
+ for(;; j++){
+ c = z[j];
+ if( c>='0' && c<='9' ) continue;
+ if( c=='.' ){
+ if( z[j-1]=='-' ) return -1;
+ if( seenDP ) return -1;
+ seenDP = 1;
+ continue;
+ }
+ if( c=='e' || c=='E' ){
+ if( z[j-1]<'0' ) return -1;
+ if( seenE ) return -1;
+ seenDP = seenE = 1;
+ c = z[j+1];
+ if( c=='+' || c=='-' ){
+ j++;
+ c = z[j+1];
+ }
+ if( c<'0' || c>'9' ) return -1;
+ continue;
+ }
+ break;
+ }
+ if( z[j-1]<'0' ) return -1;
+ jsonParseAddNode(pParse, seenDP ? JSON_REAL : JSON_INT,
+ j - i, &z[i]);
+ return j;
+ }else if( c=='}' ){
+ return -2; /* End of {...} */
+ }else if( c==']' ){
+ return -3; /* End of [...] */
+ }else if( c==0 ){
+ return 0; /* End of file */
+ }else{
+ return -1; /* Syntax error */
+ }
+}
+
+/*
+** Parse a complete JSON string. Return 0 on success or non-zero if there
+** are any errors. If an error occurs, free all memory associated with
+** pParse.
+**
+** pParse is uninitialized when this routine is called.
+*/
+static int jsonParse(
+ JsonParse *pParse, /* Initialize and fill this JsonParse object */
+ tdsqlite3_context *pCtx, /* Report errors here */
+ const char *zJson /* Input JSON text to be parsed */
+){
+ int i;
+ memset(pParse, 0, sizeof(*pParse));
+ if( zJson==0 ) return 1;
+ pParse->zJson = zJson;
+ i = jsonParseValue(pParse, 0);
+ if( pParse->oom ) i = -1;
+ if( i>0 ){
+ assert( pParse->iDepth==0 );
+ while( safe_isspace(zJson[i]) ) i++;
+ if( zJson[i] ) i = -1;
+ }
+ if( i<=0 ){
+ if( pCtx!=0 ){
+ if( pParse->oom ){
+ tdsqlite3_result_error_nomem(pCtx);
+ }else{
+ tdsqlite3_result_error(pCtx, "malformed JSON", -1);
+ }
+ }
+ jsonParseReset(pParse);
+ return 1;
+ }
+ return 0;
+}
+
+/* Mark node i of pParse as being a child of iParent. Call recursively
+** to fill in all the descendants of node i.
+*/
+static void jsonParseFillInParentage(JsonParse *pParse, u32 i, u32 iParent){
+ JsonNode *pNode = &pParse->aNode[i];
+ u32 j;
+ pParse->aUp[i] = iParent;
+ switch( pNode->eType ){
+ case JSON_ARRAY: {
+ for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j)){
+ jsonParseFillInParentage(pParse, i+j, i);
+ }
+ break;
+ }
+ case JSON_OBJECT: {
+ for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j+1)+1){
+ pParse->aUp[i+j] = i;
+ jsonParseFillInParentage(pParse, i+j+1, i);
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
+
+/*
+** Compute the parentage of all nodes in a completed parse.
+*/
+static int jsonParseFindParents(JsonParse *pParse){
+ u32 *aUp;
+ assert( pParse->aUp==0 );
+ aUp = pParse->aUp = tdsqlite3_malloc64( sizeof(u32)*pParse->nNode );
+ if( aUp==0 ){
+ pParse->oom = 1;
+ return SQLITE_NOMEM;
+ }
+ jsonParseFillInParentage(pParse, 0, 0);
+ return SQLITE_OK;
+}
+
+/*
+** Magic number used for the JSON parse cache in tdsqlite3_get_auxdata()
+*/
+#define JSON_CACHE_ID (-429938) /* First cache entry */
+#define JSON_CACHE_SZ 4 /* Max number of cache entries */
+
+/*
+** Obtain a complete parse of the JSON found in the first argument
+** of the argv array. Use the tdsqlite3_get_auxdata() cache for this
+** parse if it is available. If the cache is not available or if it
+** is no longer valid, parse the JSON again and return the new parse,
+** and also register the new parse so that it will be available for
+** future tdsqlite3_get_auxdata() calls.
+*/
+static JsonParse *jsonParseCached(
+ tdsqlite3_context *pCtx,
+ tdsqlite3_value **argv,
+ tdsqlite3_context *pErrCtx
+){
+ const char *zJson = (const char*)tdsqlite3_value_text(argv[0]);
+ int nJson = tdsqlite3_value_bytes(argv[0]);
+ JsonParse *p;
+ JsonParse *pMatch = 0;
+ int iKey;
+ int iMinKey = 0;
+ u32 iMinHold = 0xffffffff;
+ u32 iMaxHold = 0;
+ if( zJson==0 ) return 0;
+ for(iKey=0; iKey<JSON_CACHE_SZ; iKey++){
+ p = (JsonParse*)tdsqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iKey);
+ if( p==0 ){
+ iMinKey = iKey;
+ break;
+ }
+ if( pMatch==0
+ && p->nJson==nJson
+ && memcmp(p->zJson,zJson,nJson)==0
+ ){
+ p->nErr = 0;
+ pMatch = p;
+ }else if( p->iHold<iMinHold ){
+ iMinHold = p->iHold;
+ iMinKey = iKey;
+ }
+ if( p->iHold>iMaxHold ){
+ iMaxHold = p->iHold;
+ }
+ }
+ if( pMatch ){
+ pMatch->nErr = 0;
+ pMatch->iHold = iMaxHold+1;
+ return pMatch;
+ }
+ p = tdsqlite3_malloc64( sizeof(*p) + nJson + 1 );
+ if( p==0 ){
+ tdsqlite3_result_error_nomem(pCtx);
+ return 0;
+ }
+ memset(p, 0, sizeof(*p));
+ p->zJson = (char*)&p[1];
+ memcpy((char*)p->zJson, zJson, nJson+1);
+ if( jsonParse(p, pErrCtx, p->zJson) ){
+ tdsqlite3_free(p);
+ return 0;
+ }
+ p->nJson = nJson;
+ p->iHold = iMaxHold+1;
+ tdsqlite3_set_auxdata(pCtx, JSON_CACHE_ID+iMinKey, p,
+ (void(*)(void*))jsonParseFree);
+ return (JsonParse*)tdsqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iMinKey);
+}
+
+/*
+** Compare the OBJECT label at pNode against zKey,nKey. Return true on
+** a match.
+*/
+static int jsonLabelCompare(JsonNode *pNode, const char *zKey, u32 nKey){
+ if( pNode->jnFlags & JNODE_RAW ){
+ if( pNode->n!=nKey ) return 0;
+ return strncmp(pNode->u.zJContent, zKey, nKey)==0;
+ }else{
+ if( pNode->n!=nKey+2 ) return 0;
+ return strncmp(pNode->u.zJContent+1, zKey, nKey)==0;
+ }
+}
+
+/* forward declaration */
+static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*,const char**);
+
+/*
+** Search along zPath to find the node specified. Return a pointer
+** to that node, or NULL if zPath is malformed or if there is no such
+** node.
+**
+** If pApnd!=0, then try to append new nodes to complete zPath if it is
+** possible to do so and if no existing node corresponds to zPath. If
+** new nodes are appended *pApnd is set to 1.
+*/
+static JsonNode *jsonLookupStep(
+ JsonParse *pParse, /* The JSON to search */
+ u32 iRoot, /* Begin the search at this node */
+ const char *zPath, /* The path to search */
+ int *pApnd, /* Append nodes to complete path if not NULL */
+ const char **pzErr /* Make *pzErr point to any syntax error in zPath */
+){
+ u32 i, j, nKey;
+ const char *zKey;
+ JsonNode *pRoot = &pParse->aNode[iRoot];
+ if( zPath[0]==0 ) return pRoot;
+ if( pRoot->jnFlags & JNODE_REPLACE ) return 0;
+ if( zPath[0]=='.' ){
+ if( pRoot->eType!=JSON_OBJECT ) return 0;
+ zPath++;
+ if( zPath[0]=='"' ){
+ zKey = zPath + 1;
+ for(i=1; zPath[i] && zPath[i]!='"'; i++){}
+ nKey = i-1;
+ if( zPath[i] ){
+ i++;
+ }else{
+ *pzErr = zPath;
+ return 0;
+ }
+ }else{
+ zKey = zPath;
+ for(i=0; zPath[i] && zPath[i]!='.' && zPath[i]!='['; i++){}
+ nKey = i;
+ }
+ if( nKey==0 ){
+ *pzErr = zPath;
+ return 0;
+ }
+ j = 1;
+ for(;;){
+ while( j<=pRoot->n ){
+ if( jsonLabelCompare(pRoot+j, zKey, nKey) ){
+ return jsonLookupStep(pParse, iRoot+j+1, &zPath[i], pApnd, pzErr);
+ }
+ j++;
+ j += jsonNodeSize(&pRoot[j]);
+ }
+ if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break;
+ iRoot += pRoot->u.iAppend;
+ pRoot = &pParse->aNode[iRoot];
+ j = 1;
+ }
+ if( pApnd ){
+ u32 iStart, iLabel;
+ JsonNode *pNode;
+ iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0);
+ iLabel = jsonParseAddNode(pParse, JSON_STRING, nKey, zKey);
+ zPath += i;
+ pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr);
+ if( pParse->oom ) return 0;
+ if( pNode ){
+ pRoot = &pParse->aNode[iRoot];
+ pRoot->u.iAppend = iStart - iRoot;
+ pRoot->jnFlags |= JNODE_APPEND;
+ pParse->aNode[iLabel].jnFlags |= JNODE_RAW;
+ }
+ return pNode;
+ }
+ }else if( zPath[0]=='[' ){
+ i = 0;
+ j = 1;
+ while( safe_isdigit(zPath[j]) ){
+ i = i*10 + zPath[j] - '0';
+ j++;
+ }
+ if( j<2 || zPath[j]!=']' ){
+ if( zPath[1]=='#' ){
+ JsonNode *pBase = pRoot;
+ int iBase = iRoot;
+ if( pRoot->eType!=JSON_ARRAY ) return 0;
+ for(;;){
+ while( j<=pBase->n ){
+ if( (pBase[j].jnFlags & JNODE_REMOVE)==0 ) i++;
+ j += jsonNodeSize(&pBase[j]);
+ }
+ if( (pBase->jnFlags & JNODE_APPEND)==0 ) break;
+ iBase += pBase->u.iAppend;
+ pBase = &pParse->aNode[iBase];
+ j = 1;
+ }
+ j = 2;
+ if( zPath[2]=='-' && safe_isdigit(zPath[3]) ){
+ unsigned int x = 0;
+ j = 3;
+ do{
+ x = x*10 + zPath[j] - '0';
+ j++;
+ }while( safe_isdigit(zPath[j]) );
+ if( x>i ) return 0;
+ i -= x;
+ }
+ if( zPath[j]!=']' ){
+ *pzErr = zPath;
+ return 0;
+ }
+ }else{
+ *pzErr = zPath;
+ return 0;
+ }
+ }
+ if( pRoot->eType!=JSON_ARRAY ) return 0;
+ zPath += j + 1;
+ j = 1;
+ for(;;){
+ while( j<=pRoot->n && (i>0 || (pRoot[j].jnFlags & JNODE_REMOVE)!=0) ){
+ if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 ) i--;
+ j += jsonNodeSize(&pRoot[j]);
+ }
+ if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break;
+ iRoot += pRoot->u.iAppend;
+ pRoot = &pParse->aNode[iRoot];
+ j = 1;
+ }
+ if( j<=pRoot->n ){
+ return jsonLookupStep(pParse, iRoot+j, zPath, pApnd, pzErr);
+ }
+ if( i==0 && pApnd ){
+ u32 iStart;
+ JsonNode *pNode;
+ iStart = jsonParseAddNode(pParse, JSON_ARRAY, 1, 0);
+ pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr);
+ if( pParse->oom ) return 0;
+ if( pNode ){
+ pRoot = &pParse->aNode[iRoot];
+ pRoot->u.iAppend = iStart - iRoot;
+ pRoot->jnFlags |= JNODE_APPEND;
+ }
+ return pNode;
+ }
+ }else{
+ *pzErr = zPath;
+ }
+ return 0;
+}
+
+/*
+** Append content to pParse that will complete zPath. Return a pointer
+** to the inserted node, or return NULL if the append fails.
+*/
+static JsonNode *jsonLookupAppend(
+ JsonParse *pParse, /* Append content to the JSON parse */
+ const char *zPath, /* Description of content to append */
+ int *pApnd, /* Set this flag to 1 */
+ const char **pzErr /* Make this point to any syntax error */
+){
+ *pApnd = 1;
+ if( zPath[0]==0 ){
+ jsonParseAddNode(pParse, JSON_NULL, 0, 0);
+ return pParse->oom ? 0 : &pParse->aNode[pParse->nNode-1];
+ }
+ if( zPath[0]=='.' ){
+ jsonParseAddNode(pParse, JSON_OBJECT, 0, 0);
+ }else if( strncmp(zPath,"[0]",3)==0 ){
+ jsonParseAddNode(pParse, JSON_ARRAY, 0, 0);
+ }else{
+ return 0;
+ }
+ if( pParse->oom ) return 0;
+ return jsonLookupStep(pParse, pParse->nNode-1, zPath, pApnd, pzErr);
+}
+
+/*
+** Return the text of a syntax error message on a JSON path. Space is
+** obtained from tdsqlite3_malloc().
+*/
+static char *jsonPathSyntaxError(const char *zErr){
+ return tdsqlite3_mprintf("JSON path error near '%q'", zErr);
+}
+
+/*
+** Do a node lookup using zPath. Return a pointer to the node on success.
+** Return NULL if not found or if there is an error.
+**
+** On an error, write an error message into pCtx and increment the
+** pParse->nErr counter.
+**
+** If pApnd!=NULL then try to append missing nodes and set *pApnd = 1 if
+** nodes are appended.
+*/
+static JsonNode *jsonLookup(
+ JsonParse *pParse, /* The JSON to search */
+ const char *zPath, /* The path to search */
+ int *pApnd, /* Append nodes to complete path if not NULL */
+ tdsqlite3_context *pCtx /* Report errors here, if not NULL */
+){
+ const char *zErr = 0;
+ JsonNode *pNode = 0;
+ char *zMsg;
+
+ if( zPath==0 ) return 0;
+ if( zPath[0]!='$' ){
+ zErr = zPath;
+ goto lookup_err;
+ }
+ zPath++;
+ pNode = jsonLookupStep(pParse, 0, zPath, pApnd, &zErr);
+ if( zErr==0 ) return pNode;
+
+lookup_err:
+ pParse->nErr++;
+ assert( zErr!=0 && pCtx!=0 );
+ zMsg = jsonPathSyntaxError(zErr);
+ if( zMsg ){
+ tdsqlite3_result_error(pCtx, zMsg, -1);
+ tdsqlite3_free(zMsg);
+ }else{
+ tdsqlite3_result_error_nomem(pCtx);
+ }
+ return 0;
+}
+
+
+/*
+** Report the wrong number of arguments for json_insert(), json_replace()
+** or json_set().
+*/
+static void jsonWrongNumArgs(
+ tdsqlite3_context *pCtx,
+ const char *zFuncName
+){
+ char *zMsg = tdsqlite3_mprintf("json_%s() needs an odd number of arguments",
+ zFuncName);
+ tdsqlite3_result_error(pCtx, zMsg, -1);
+ tdsqlite3_free(zMsg);
+}
+
+/*
+** Mark all NULL entries in the Object passed in as JNODE_REMOVE.
+*/
+static void jsonRemoveAllNulls(JsonNode *pNode){
+ int i, n;
+ assert( pNode->eType==JSON_OBJECT );
+ n = pNode->n;
+ for(i=2; i<=n; i += jsonNodeSize(&pNode[i])+1){
+ switch( pNode[i].eType ){
+ case JSON_NULL:
+ pNode[i].jnFlags |= JNODE_REMOVE;
+ break;
+ case JSON_OBJECT:
+ jsonRemoveAllNulls(&pNode[i]);
+ break;
+ }
+ }
+}
+
+
+/****************************************************************************
+** SQL functions used for testing and debugging
+****************************************************************************/
+
+#ifdef SQLITE_DEBUG
+/*
+** The json_parse(JSON) function returns a string which describes
+** a parse of the JSON provided. Or it returns NULL if JSON is not
+** well-formed.
+*/
+static void jsonParseFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonString s; /* Output string - not real JSON */
+ JsonParse x; /* The parse */
+ u32 i;
+
+ assert( argc==1 );
+ if( jsonParse(&x, ctx, (const char*)tdsqlite3_value_text(argv[0])) ) return;
+ jsonParseFindParents(&x);
+ jsonInit(&s, ctx);
+ for(i=0; i<x.nNode; i++){
+ const char *zType;
+ if( x.aNode[i].jnFlags & JNODE_LABEL ){
+ assert( x.aNode[i].eType==JSON_STRING );
+ zType = "label";
+ }else{
+ zType = jsonType[x.aNode[i].eType];
+ }
+ jsonPrintf(100, &s,"node %3u: %7s n=%-4d up=%-4d",
+ i, zType, x.aNode[i].n, x.aUp[i]);
+ if( x.aNode[i].u.zJContent!=0 ){
+ jsonAppendRaw(&s, " ", 1);
+ jsonAppendRaw(&s, x.aNode[i].u.zJContent, x.aNode[i].n);
+ }
+ jsonAppendRaw(&s, "\n", 1);
+ }
+ jsonParseReset(&x);
+ jsonResult(&s);
+}
+
+/*
+** The json_test1(JSON) function return true (1) if the input is JSON
+** text generated by another json function. It returns (0) if the input
+** is not known to be JSON.
+*/
+static void jsonTest1Func(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ UNUSED_PARAM(argc);
+ tdsqlite3_result_int(ctx, tdsqlite3_value_subtype(argv[0])==JSON_SUBTYPE);
+}
+#endif /* SQLITE_DEBUG */
+
+/****************************************************************************
+** Scalar SQL function implementations
+****************************************************************************/
+
+/*
+** Implementation of the json_QUOTE(VALUE) function. Return a JSON value
+** corresponding to the SQL value input. Mostly this means putting
+** double-quotes around strings and returning the unquoted string "null"
+** when given a NULL input.
+*/
+static void jsonQuoteFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonString jx;
+ UNUSED_PARAM(argc);
+
+ jsonInit(&jx, ctx);
+ jsonAppendValue(&jx, argv[0]);
+ jsonResult(&jx);
+ tdsqlite3_result_subtype(ctx, JSON_SUBTYPE);
+}
+
+/*
+** Implementation of the json_array(VALUE,...) function. Return a JSON
+** array that contains all values given in arguments. Or if any argument
+** is a BLOB, throw an error.
+*/
+static void jsonArrayFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ int i;
+ JsonString jx;
+
+ jsonInit(&jx, ctx);
+ jsonAppendChar(&jx, '[');
+ for(i=0; i<argc; i++){
+ jsonAppendSeparator(&jx);
+ jsonAppendValue(&jx, argv[i]);
+ }
+ jsonAppendChar(&jx, ']');
+ jsonResult(&jx);
+ tdsqlite3_result_subtype(ctx, JSON_SUBTYPE);
+}
+
+
+/*
+** json_array_length(JSON)
+** json_array_length(JSON, PATH)
+**
+** Return the number of elements in the top-level JSON array.
+** Return 0 if the input is not a well-formed JSON array.
+*/
+static void jsonArrayLengthFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonParse *p; /* The parse */
+ tdsqlite3_int64 n = 0;
+ u32 i;
+ JsonNode *pNode;
+
+ p = jsonParseCached(ctx, argv, ctx);
+ if( p==0 ) return;
+ assert( p->nNode );
+ if( argc==2 ){
+ const char *zPath = (const char*)tdsqlite3_value_text(argv[1]);
+ pNode = jsonLookup(p, zPath, 0, ctx);
+ }else{
+ pNode = p->aNode;
+ }
+ if( pNode==0 ){
+ return;
+ }
+ if( pNode->eType==JSON_ARRAY ){
+ assert( (pNode->jnFlags & JNODE_APPEND)==0 );
+ for(i=1; i<=pNode->n; n++){
+ i += jsonNodeSize(&pNode[i]);
+ }
+ }
+ tdsqlite3_result_int64(ctx, n);
+}
+
+/*
+** json_extract(JSON, PATH, ...)
+**
+** Return the element described by PATH. Return NULL if there is no
+** PATH element. If there are multiple PATHs, then return a JSON array
+** with the result from each path. Throw an error if the JSON or any PATH
+** is malformed.
+*/
+static void jsonExtractFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonParse *p; /* The parse */
+ JsonNode *pNode;
+ const char *zPath;
+ JsonString jx;
+ int i;
+
+ if( argc<2 ) return;
+ p = jsonParseCached(ctx, argv, ctx);
+ if( p==0 ) return;
+ jsonInit(&jx, ctx);
+ jsonAppendChar(&jx, '[');
+ for(i=1; i<argc; i++){
+ zPath = (const char*)tdsqlite3_value_text(argv[i]);
+ pNode = jsonLookup(p, zPath, 0, ctx);
+ if( p->nErr ) break;
+ if( argc>2 ){
+ jsonAppendSeparator(&jx);
+ if( pNode ){
+ jsonRenderNode(pNode, &jx, 0);
+ }else{
+ jsonAppendRaw(&jx, "null", 4);
+ }
+ }else if( pNode ){
+ jsonReturn(pNode, ctx, 0);
+ }
+ }
+ if( argc>2 && i==argc ){
+ jsonAppendChar(&jx, ']');
+ jsonResult(&jx);
+ tdsqlite3_result_subtype(ctx, JSON_SUBTYPE);
+ }
+ jsonReset(&jx);
+}
+
+/* This is the RFC 7396 MergePatch algorithm.
+*/
+static JsonNode *jsonMergePatch(
+ JsonParse *pParse, /* The JSON parser that contains the TARGET */
+ u32 iTarget, /* Node of the TARGET in pParse */
+ JsonNode *pPatch /* The PATCH */
+){
+ u32 i, j;
+ u32 iRoot;
+ JsonNode *pTarget;
+ if( pPatch->eType!=JSON_OBJECT ){
+ return pPatch;
+ }
+ assert( iTarget>=0 && iTarget<pParse->nNode );
+ pTarget = &pParse->aNode[iTarget];
+ assert( (pPatch->jnFlags & JNODE_APPEND)==0 );
+ if( pTarget->eType!=JSON_OBJECT ){
+ jsonRemoveAllNulls(pPatch);
+ return pPatch;
+ }
+ iRoot = iTarget;
+ for(i=1; i<pPatch->n; i += jsonNodeSize(&pPatch[i+1])+1){
+ u32 nKey;
+ const char *zKey;
+ assert( pPatch[i].eType==JSON_STRING );
+ assert( pPatch[i].jnFlags & JNODE_LABEL );
+ nKey = pPatch[i].n;
+ zKey = pPatch[i].u.zJContent;
+ assert( (pPatch[i].jnFlags & JNODE_RAW)==0 );
+ for(j=1; j<pTarget->n; j += jsonNodeSize(&pTarget[j+1])+1 ){
+ assert( pTarget[j].eType==JSON_STRING );
+ assert( pTarget[j].jnFlags & JNODE_LABEL );
+ assert( (pPatch[i].jnFlags & JNODE_RAW)==0 );
+ if( pTarget[j].n==nKey && strncmp(pTarget[j].u.zJContent,zKey,nKey)==0 ){
+ if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_PATCH) ) break;
+ if( pPatch[i+1].eType==JSON_NULL ){
+ pTarget[j+1].jnFlags |= JNODE_REMOVE;
+ }else{
+ JsonNode *pNew = jsonMergePatch(pParse, iTarget+j+1, &pPatch[i+1]);
+ if( pNew==0 ) return 0;
+ pTarget = &pParse->aNode[iTarget];
+ if( pNew!=&pTarget[j+1] ){
+ pTarget[j+1].u.pPatch = pNew;
+ pTarget[j+1].jnFlags |= JNODE_PATCH;
+ }
+ }
+ break;
+ }
+ }
+ if( j>=pTarget->n && pPatch[i+1].eType!=JSON_NULL ){
+ int iStart, iPatch;
+ iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0);
+ jsonParseAddNode(pParse, JSON_STRING, nKey, zKey);
+ iPatch = jsonParseAddNode(pParse, JSON_TRUE, 0, 0);
+ if( pParse->oom ) return 0;
+ jsonRemoveAllNulls(pPatch);
+ pTarget = &pParse->aNode[iTarget];
+ pParse->aNode[iRoot].jnFlags |= JNODE_APPEND;
+ pParse->aNode[iRoot].u.iAppend = iStart - iRoot;
+ iRoot = iStart;
+ pParse->aNode[iPatch].jnFlags |= JNODE_PATCH;
+ pParse->aNode[iPatch].u.pPatch = &pPatch[i+1];
+ }
+ }
+ return pTarget;
+}
+
+/*
+** Implementation of the json_mergepatch(JSON1,JSON2) function. Return a JSON
+** object that is the result of running the RFC 7396 MergePatch() algorithm
+** on the two arguments.
+*/
+static void jsonPatchFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonParse x; /* The JSON that is being patched */
+ JsonParse y; /* The patch */
+ JsonNode *pResult; /* The result of the merge */
+
+ UNUSED_PARAM(argc);
+ if( jsonParse(&x, ctx, (const char*)tdsqlite3_value_text(argv[0])) ) return;
+ if( jsonParse(&y, ctx, (const char*)tdsqlite3_value_text(argv[1])) ){
+ jsonParseReset(&x);
+ return;
+ }
+ pResult = jsonMergePatch(&x, 0, y.aNode);
+ assert( pResult!=0 || x.oom );
+ if( pResult ){
+ jsonReturnJson(pResult, ctx, 0);
+ }else{
+ tdsqlite3_result_error_nomem(ctx);
+ }
+ jsonParseReset(&x);
+ jsonParseReset(&y);
+}
+
+
+/*
+** Implementation of the json_object(NAME,VALUE,...) function. Return a JSON
+** object that contains all name/value given in arguments. Or if any name
+** is not a string or if any value is a BLOB, throw an error.
+*/
+static void jsonObjectFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ int i;
+ JsonString jx;
+ const char *z;
+ u32 n;
+
+ if( argc&1 ){
+ tdsqlite3_result_error(ctx, "json_object() requires an even number "
+ "of arguments", -1);
+ return;
+ }
+ jsonInit(&jx, ctx);
+ jsonAppendChar(&jx, '{');
+ for(i=0; i<argc; i+=2){
+ if( tdsqlite3_value_type(argv[i])!=SQLITE_TEXT ){
+ tdsqlite3_result_error(ctx, "json_object() labels must be TEXT", -1);
+ jsonReset(&jx);
+ return;
+ }
+ jsonAppendSeparator(&jx);
+ z = (const char*)tdsqlite3_value_text(argv[i]);
+ n = (u32)tdsqlite3_value_bytes(argv[i]);
+ jsonAppendString(&jx, z, n);
+ jsonAppendChar(&jx, ':');
+ jsonAppendValue(&jx, argv[i+1]);
+ }
+ jsonAppendChar(&jx, '}');
+ jsonResult(&jx);
+ tdsqlite3_result_subtype(ctx, JSON_SUBTYPE);
+}
+
+
+/*
+** json_remove(JSON, PATH, ...)
+**
+** Remove the named elements from JSON and return the result. malformed
+** JSON or PATH arguments result in an error.
+*/
+static void jsonRemoveFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonParse x; /* The parse */
+ JsonNode *pNode;
+ const char *zPath;
+ u32 i;
+
+ if( argc<1 ) return;
+ if( jsonParse(&x, ctx, (const char*)tdsqlite3_value_text(argv[0])) ) return;
+ assert( x.nNode );
+ for(i=1; i<(u32)argc; i++){
+ zPath = (const char*)tdsqlite3_value_text(argv[i]);
+ if( zPath==0 ) goto remove_done;
+ pNode = jsonLookup(&x, zPath, 0, ctx);
+ if( x.nErr ) goto remove_done;
+ if( pNode ) pNode->jnFlags |= JNODE_REMOVE;
+ }
+ if( (x.aNode[0].jnFlags & JNODE_REMOVE)==0 ){
+ jsonReturnJson(x.aNode, ctx, 0);
+ }
+remove_done:
+ jsonParseReset(&x);
+}
+
+/*
+** json_replace(JSON, PATH, VALUE, ...)
+**
+** Replace the value at PATH with VALUE. If PATH does not already exist,
+** this routine is a no-op. If JSON or PATH is malformed, throw an error.
+*/
+static void jsonReplaceFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonParse x; /* The parse */
+ JsonNode *pNode;
+ const char *zPath;
+ u32 i;
+
+ if( argc<1 ) return;
+ if( (argc&1)==0 ) {
+ jsonWrongNumArgs(ctx, "replace");
+ return;
+ }
+ if( jsonParse(&x, ctx, (const char*)tdsqlite3_value_text(argv[0])) ) return;
+ assert( x.nNode );
+ for(i=1; i<(u32)argc; i+=2){
+ zPath = (const char*)tdsqlite3_value_text(argv[i]);
+ pNode = jsonLookup(&x, zPath, 0, ctx);
+ if( x.nErr ) goto replace_err;
+ if( pNode ){
+ pNode->jnFlags |= (u8)JNODE_REPLACE;
+ pNode->u.iReplace = i + 1;
+ }
+ }
+ if( x.aNode[0].jnFlags & JNODE_REPLACE ){
+ tdsqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]);
+ }else{
+ jsonReturnJson(x.aNode, ctx, argv);
+ }
+replace_err:
+ jsonParseReset(&x);
+}
+
+/*
+** json_set(JSON, PATH, VALUE, ...)
+**
+** Set the value at PATH to VALUE. Create the PATH if it does not already
+** exist. Overwrite existing values that do exist.
+** If JSON or PATH is malformed, throw an error.
+**
+** json_insert(JSON, PATH, VALUE, ...)
+**
+** Create PATH and initialize it to VALUE. If PATH already exists, this
+** routine is a no-op. If JSON or PATH is malformed, throw an error.
+*/
+static void jsonSetFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonParse x; /* The parse */
+ JsonNode *pNode;
+ const char *zPath;
+ u32 i;
+ int bApnd;
+ int bIsSet = *(int*)tdsqlite3_user_data(ctx);
+
+ if( argc<1 ) return;
+ if( (argc&1)==0 ) {
+ jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert");
+ return;
+ }
+ if( jsonParse(&x, ctx, (const char*)tdsqlite3_value_text(argv[0])) ) return;
+ assert( x.nNode );
+ for(i=1; i<(u32)argc; i+=2){
+ zPath = (const char*)tdsqlite3_value_text(argv[i]);
+ bApnd = 0;
+ pNode = jsonLookup(&x, zPath, &bApnd, ctx);
+ if( x.oom ){
+ tdsqlite3_result_error_nomem(ctx);
+ goto jsonSetDone;
+ }else if( x.nErr ){
+ goto jsonSetDone;
+ }else if( pNode && (bApnd || bIsSet) ){
+ pNode->jnFlags |= (u8)JNODE_REPLACE;
+ pNode->u.iReplace = i + 1;
+ }
+ }
+ if( x.aNode[0].jnFlags & JNODE_REPLACE ){
+ tdsqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]);
+ }else{
+ jsonReturnJson(x.aNode, ctx, argv);
+ }
+jsonSetDone:
+ jsonParseReset(&x);
+}
+
+/*
+** json_type(JSON)
+** json_type(JSON, PATH)
+**
+** Return the top-level "type" of a JSON string. Throw an error if
+** either the JSON or PATH inputs are not well-formed.
+*/
+static void jsonTypeFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonParse *p; /* The parse */
+ const char *zPath;
+ JsonNode *pNode;
+
+ p = jsonParseCached(ctx, argv, ctx);
+ if( p==0 ) return;
+ if( argc==2 ){
+ zPath = (const char*)tdsqlite3_value_text(argv[1]);
+ pNode = jsonLookup(p, zPath, 0, ctx);
+ }else{
+ pNode = p->aNode;
+ }
+ if( pNode ){
+ tdsqlite3_result_text(ctx, jsonType[pNode->eType], -1, SQLITE_STATIC);
+ }
+}
+
+/*
+** json_valid(JSON)
+**
+** Return 1 if JSON is a well-formed JSON string according to RFC-7159.
+** Return 0 otherwise.
+*/
+static void jsonValidFunc(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonParse *p; /* The parse */
+ UNUSED_PARAM(argc);
+ p = jsonParseCached(ctx, argv, 0);
+ tdsqlite3_result_int(ctx, p!=0);
+}
+
+
+/****************************************************************************
+** Aggregate SQL function implementations
+****************************************************************************/
+/*
+** json_group_array(VALUE)
+**
+** Return a JSON array composed of all values in the aggregate.
+*/
+static void jsonArrayStep(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonString *pStr;
+ UNUSED_PARAM(argc);
+ pStr = (JsonString*)tdsqlite3_aggregate_context(ctx, sizeof(*pStr));
+ if( pStr ){
+ if( pStr->zBuf==0 ){
+ jsonInit(pStr, ctx);
+ jsonAppendChar(pStr, '[');
+ }else if( pStr->nUsed>1 ){
+ jsonAppendChar(pStr, ',');
+ pStr->pCtx = ctx;
+ }
+ jsonAppendValue(pStr, argv[0]);
+ }
+}
+static void jsonArrayCompute(tdsqlite3_context *ctx, int isFinal){
+ JsonString *pStr;
+ pStr = (JsonString*)tdsqlite3_aggregate_context(ctx, 0);
+ if( pStr ){
+ pStr->pCtx = ctx;
+ jsonAppendChar(pStr, ']');
+ if( pStr->bErr ){
+ if( pStr->bErr==1 ) tdsqlite3_result_error_nomem(ctx);
+ assert( pStr->bStatic );
+ }else if( isFinal ){
+ tdsqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed,
+ pStr->bStatic ? SQLITE_TRANSIENT : tdsqlite3_free);
+ pStr->bStatic = 1;
+ }else{
+ tdsqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);
+ pStr->nUsed--;
+ }
+ }else{
+ tdsqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC);
+ }
+ tdsqlite3_result_subtype(ctx, JSON_SUBTYPE);
+}
+static void jsonArrayValue(tdsqlite3_context *ctx){
+ jsonArrayCompute(ctx, 0);
+}
+static void jsonArrayFinal(tdsqlite3_context *ctx){
+ jsonArrayCompute(ctx, 1);
+}
+
+#ifndef SQLITE_OMIT_WINDOWFUNC
+/*
+** This method works for both json_group_array() and json_group_object().
+** It works by removing the first element of the group by searching forward
+** to the first comma (",") that is not within a string and deleting all
+** text through that comma.
+*/
+static void jsonGroupInverse(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ unsigned int i;
+ int inStr = 0;
+ int nNest = 0;
+ char *z;
+ char c;
+ JsonString *pStr;
+ UNUSED_PARAM(argc);
+ UNUSED_PARAM(argv);
+ pStr = (JsonString*)tdsqlite3_aggregate_context(ctx, 0);
+#ifdef NEVER
+ /* pStr is always non-NULL since jsonArrayStep() or jsonObjectStep() will
+ ** always have been called to initalize it */
+ if( NEVER(!pStr) ) return;
+#endif
+ z = pStr->zBuf;
+ for(i=1; (c = z[i])!=',' || inStr || nNest; i++){
+ if( i>=pStr->nUsed ){
+ pStr->nUsed = 1;
+ return;
+ }
+ if( c=='"' ){
+ inStr = !inStr;
+ }else if( c=='\\' ){
+ i++;
+ }else if( !inStr ){
+ if( c=='{' || c=='[' ) nNest++;
+ if( c=='}' || c==']' ) nNest--;
+ }
+ }
+ pStr->nUsed -= i;
+ memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1);
+}
+#else
+# define jsonGroupInverse 0
+#endif
+
+
+/*
+** json_group_obj(NAME,VALUE)
+**
+** Return a JSON object composed of all names and values in the aggregate.
+*/
+static void jsonObjectStep(
+ tdsqlite3_context *ctx,
+ int argc,
+ tdsqlite3_value **argv
+){
+ JsonString *pStr;
+ const char *z;
+ u32 n;
+ UNUSED_PARAM(argc);
+ pStr = (JsonString*)tdsqlite3_aggregate_context(ctx, sizeof(*pStr));
+ if( pStr ){
+ if( pStr->zBuf==0 ){
+ jsonInit(pStr, ctx);
+ jsonAppendChar(pStr, '{');
+ }else if( pStr->nUsed>1 ){
+ jsonAppendChar(pStr, ',');
+ pStr->pCtx = ctx;
+ }
+ z = (const char*)tdsqlite3_value_text(argv[0]);
+ n = (u32)tdsqlite3_value_bytes(argv[0]);
+ jsonAppendString(pStr, z, n);
+ jsonAppendChar(pStr, ':');
+ jsonAppendValue(pStr, argv[1]);
+ }
+}
+static void jsonObjectCompute(tdsqlite3_context *ctx, int isFinal){
+ JsonString *pStr;
+ pStr = (JsonString*)tdsqlite3_aggregate_context(ctx, 0);
+ if( pStr ){
+ jsonAppendChar(pStr, '}');
+ if( pStr->bErr ){
+ if( pStr->bErr==1 ) tdsqlite3_result_error_nomem(ctx);
+ assert( pStr->bStatic );
+ }else if( isFinal ){
+ tdsqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed,
+ pStr->bStatic ? SQLITE_TRANSIENT : tdsqlite3_free);
+ pStr->bStatic = 1;
+ }else{
+ tdsqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);
+ pStr->nUsed--;
+ }
+ }else{
+ tdsqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC);
+ }
+ tdsqlite3_result_subtype(ctx, JSON_SUBTYPE);
+}
+static void jsonObjectValue(tdsqlite3_context *ctx){
+ jsonObjectCompute(ctx, 0);
+}
+static void jsonObjectFinal(tdsqlite3_context *ctx){
+ jsonObjectCompute(ctx, 1);
+}
+
+
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+/****************************************************************************
+** The json_each virtual table
+****************************************************************************/
+typedef struct JsonEachCursor JsonEachCursor;
+struct JsonEachCursor {
+ tdsqlite3_vtab_cursor base; /* Base class - must be first */
+ u32 iRowid; /* The rowid */
+ u32 iBegin; /* The first node of the scan */
+ u32 i; /* Index in sParse.aNode[] of current row */
+ u32 iEnd; /* EOF when i equals or exceeds this value */
+ u8 eType; /* Type of top-level element */
+ u8 bRecursive; /* True for json_tree(). False for json_each() */
+ char *zJson; /* Input JSON */
+ char *zRoot; /* Path by which to filter zJson */
+ JsonParse sParse; /* Parse of the input JSON */
+};
+
+/* Constructor for the json_each virtual table */
+static int jsonEachConnect(
+ tdsqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ tdsqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ tdsqlite3_vtab *pNew;
+ int rc;
+
+/* Column numbers */
+#define JEACH_KEY 0
+#define JEACH_VALUE 1
+#define JEACH_TYPE 2
+#define JEACH_ATOM 3
+#define JEACH_ID 4
+#define JEACH_PARENT 5
+#define JEACH_FULLKEY 6
+#define JEACH_PATH 7
+/* The xBestIndex method assumes that the JSON and ROOT columns are
+** the last two columns in the table. Should this ever changes, be
+** sure to update the xBestIndex method. */
+#define JEACH_JSON 8
+#define JEACH_ROOT 9
+
+ UNUSED_PARAM(pzErr);
+ UNUSED_PARAM(argv);
+ UNUSED_PARAM(argc);
+ UNUSED_PARAM(pAux);
+ rc = tdsqlite3_declare_vtab(db,
+ "CREATE TABLE x(key,value,type,atom,id,parent,fullkey,path,"
+ "json HIDDEN,root HIDDEN)");
+ if( rc==SQLITE_OK ){
+ pNew = *ppVtab = tdsqlite3_malloc( sizeof(*pNew) );
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, sizeof(*pNew));
+ tdsqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
+ }
+ return rc;
+}
+
+/* destructor for json_each virtual table */
+static int jsonEachDisconnect(tdsqlite3_vtab *pVtab){
+ tdsqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/* constructor for a JsonEachCursor object for json_each(). */
+static int jsonEachOpenEach(tdsqlite3_vtab *p, tdsqlite3_vtab_cursor **ppCursor){
+ JsonEachCursor *pCur;
+
+ UNUSED_PARAM(p);
+ pCur = tdsqlite3_malloc( sizeof(*pCur) );
+ if( pCur==0 ) return SQLITE_NOMEM;
+ memset(pCur, 0, sizeof(*pCur));
+ *ppCursor = &pCur->base;
+ return SQLITE_OK;
+}
+
+/* constructor for a JsonEachCursor object for json_tree(). */
+static int jsonEachOpenTree(tdsqlite3_vtab *p, tdsqlite3_vtab_cursor **ppCursor){
+ int rc = jsonEachOpenEach(p, ppCursor);
+ if( rc==SQLITE_OK ){
+ JsonEachCursor *pCur = (JsonEachCursor*)*ppCursor;
+ pCur->bRecursive = 1;
+ }
+ return rc;
+}
+
+/* Reset a JsonEachCursor back to its original state. Free any memory
+** held. */
+static void jsonEachCursorReset(JsonEachCursor *p){
+ tdsqlite3_free(p->zJson);
+ tdsqlite3_free(p->zRoot);
+ jsonParseReset(&p->sParse);
+ p->iRowid = 0;
+ p->i = 0;
+ p->iEnd = 0;
+ p->eType = 0;
+ p->zJson = 0;
+ p->zRoot = 0;
+}
+
+/* Destructor for a jsonEachCursor object */
+static int jsonEachClose(tdsqlite3_vtab_cursor *cur){
+ JsonEachCursor *p = (JsonEachCursor*)cur;
+ jsonEachCursorReset(p);
+ tdsqlite3_free(cur);
+ return SQLITE_OK;
+}
+
+/* Return TRUE if the jsonEachCursor object has been advanced off the end
+** of the JSON object */
+static int jsonEachEof(tdsqlite3_vtab_cursor *cur){
+ JsonEachCursor *p = (JsonEachCursor*)cur;
+ return p->i >= p->iEnd;
+}
+
+/* Advance the cursor to the next element for json_tree() */
+static int jsonEachNext(tdsqlite3_vtab_cursor *cur){
+ JsonEachCursor *p = (JsonEachCursor*)cur;
+ if( p->bRecursive ){
+ if( p->sParse.aNode[p->i].jnFlags & JNODE_LABEL ) p->i++;
+ p->i++;
+ p->iRowid++;
+ if( p->i<p->iEnd ){
+ u32 iUp = p->sParse.aUp[p->i];
+ JsonNode *pUp = &p->sParse.aNode[iUp];
+ p->eType = pUp->eType;
+ if( pUp->eType==JSON_ARRAY ){
+ if( iUp==p->i-1 ){
+ pUp->u.iKey = 0;
+ }else{
+ pUp->u.iKey++;
+ }
+ }
+ }
+ }else{
+ switch( p->eType ){
+ case JSON_ARRAY: {
+ p->i += jsonNodeSize(&p->sParse.aNode[p->i]);
+ p->iRowid++;
+ break;
+ }
+ case JSON_OBJECT: {
+ p->i += 1 + jsonNodeSize(&p->sParse.aNode[p->i+1]);
+ p->iRowid++;
+ break;
+ }
+ default: {
+ p->i = p->iEnd;
+ break;
+ }
+ }
+ }
+ return SQLITE_OK;
+}
+
+/* Append the name of the path for element i to pStr
+*/
+static void jsonEachComputePath(
+ JsonEachCursor *p, /* The cursor */
+ JsonString *pStr, /* Write the path here */
+ u32 i /* Path to this element */
+){
+ JsonNode *pNode, *pUp;
+ u32 iUp;
+ if( i==0 ){
+ jsonAppendChar(pStr, '$');
+ return;
+ }
+ iUp = p->sParse.aUp[i];
+ jsonEachComputePath(p, pStr, iUp);
+ pNode = &p->sParse.aNode[i];
+ pUp = &p->sParse.aNode[iUp];
+ if( pUp->eType==JSON_ARRAY ){
+ jsonPrintf(30, pStr, "[%d]", pUp->u.iKey);
+ }else{
+ assert( pUp->eType==JSON_OBJECT );
+ if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--;
+ assert( pNode->eType==JSON_STRING );
+ assert( pNode->jnFlags & JNODE_LABEL );
+ jsonPrintf(pNode->n+1, pStr, ".%.*s", pNode->n-2, pNode->u.zJContent+1);
+ }
+}
+
+/* Return the value of a column */
+static int jsonEachColumn(
+ tdsqlite3_vtab_cursor *cur, /* The cursor */
+ tdsqlite3_context *ctx, /* First argument to tdsqlite3_result_...() */
+ int i /* Which column to return */
+){
+ JsonEachCursor *p = (JsonEachCursor*)cur;
+ JsonNode *pThis = &p->sParse.aNode[p->i];
+ switch( i ){
+ case JEACH_KEY: {
+ if( p->i==0 ) break;
+ if( p->eType==JSON_OBJECT ){
+ jsonReturn(pThis, ctx, 0);
+ }else if( p->eType==JSON_ARRAY ){
+ u32 iKey;
+ if( p->bRecursive ){
+ if( p->iRowid==0 ) break;
+ iKey = p->sParse.aNode[p->sParse.aUp[p->i]].u.iKey;
+ }else{
+ iKey = p->iRowid;
+ }
+ tdsqlite3_result_int64(ctx, (tdsqlite3_int64)iKey);
+ }
+ break;
+ }
+ case JEACH_VALUE: {
+ if( pThis->jnFlags & JNODE_LABEL ) pThis++;
+ jsonReturn(pThis, ctx, 0);
+ break;
+ }
+ case JEACH_TYPE: {
+ if( pThis->jnFlags & JNODE_LABEL ) pThis++;
+ tdsqlite3_result_text(ctx, jsonType[pThis->eType], -1, SQLITE_STATIC);
+ break;
+ }
+ case JEACH_ATOM: {
+ if( pThis->jnFlags & JNODE_LABEL ) pThis++;
+ if( pThis->eType>=JSON_ARRAY ) break;
+ jsonReturn(pThis, ctx, 0);
+ break;
+ }
+ case JEACH_ID: {
+ tdsqlite3_result_int64(ctx,
+ (tdsqlite3_int64)p->i + ((pThis->jnFlags & JNODE_LABEL)!=0));
+ break;
+ }
+ case JEACH_PARENT: {
+ if( p->i>p->iBegin && p->bRecursive ){
+ tdsqlite3_result_int64(ctx, (tdsqlite3_int64)p->sParse.aUp[p->i]);
+ }
+ break;
+ }
+ case JEACH_FULLKEY: {
+ JsonString x;
+ jsonInit(&x, ctx);
+ if( p->bRecursive ){
+ jsonEachComputePath(p, &x, p->i);
+ }else{
+ if( p->zRoot ){
+ jsonAppendRaw(&x, p->zRoot, (int)strlen(p->zRoot));
+ }else{
+ jsonAppendChar(&x, '$');
+ }
+ if( p->eType==JSON_ARRAY ){
+ jsonPrintf(30, &x, "[%d]", p->iRowid);
+ }else if( p->eType==JSON_OBJECT ){
+ jsonPrintf(pThis->n, &x, ".%.*s", pThis->n-2, pThis->u.zJContent+1);
+ }
+ }
+ jsonResult(&x);
+ break;
+ }
+ case JEACH_PATH: {
+ if( p->bRecursive ){
+ JsonString x;
+ jsonInit(&x, ctx);
+ jsonEachComputePath(p, &x, p->sParse.aUp[p->i]);
+ jsonResult(&x);
+ break;
+ }
+ /* For json_each() path and root are the same so fall through
+ ** into the root case */
+ }
+ default: {
+ const char *zRoot = p->zRoot;
+ if( zRoot==0 ) zRoot = "$";
+ tdsqlite3_result_text(ctx, zRoot, -1, SQLITE_STATIC);
+ break;
+ }
+ case JEACH_JSON: {
+ assert( i==JEACH_JSON );
+ tdsqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC);
+ break;
+ }
+ }
+ return SQLITE_OK;
+}
+
+/* Return the current rowid value */
+static int jsonEachRowid(tdsqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ JsonEachCursor *p = (JsonEachCursor*)cur;
+ *pRowid = p->iRowid;
+ return SQLITE_OK;
+}
+
+/* The query strategy is to look for an equality constraint on the json
+** column. Without such a constraint, the table cannot operate. idxNum is
+** 1 if the constraint is found, 3 if the constraint and zRoot are found,
+** and 0 otherwise.
+*/
+static int jsonEachBestIndex(
+ tdsqlite3_vtab *tab,
+ tdsqlite3_index_info *pIdxInfo
+){
+ int i; /* Loop counter or computed array index */
+ int aIdx[2]; /* Index of constraints for JSON and ROOT */
+ int unusableMask = 0; /* Mask of unusable JSON and ROOT constraints */
+ int idxMask = 0; /* Mask of usable == constraints JSON and ROOT */
+ const struct tdsqlite3_index_constraint *pConstraint;
+
+ /* This implementation assumes that JSON and ROOT are the last two
+ ** columns in the table */
+ assert( JEACH_ROOT == JEACH_JSON+1 );
+ UNUSED_PARAM(tab);
+ aIdx[0] = aIdx[1] = -1;
+ pConstraint = pIdxInfo->aConstraint;
+ for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
+ int iCol;
+ int iMask;
+ if( pConstraint->iColumn < JEACH_JSON ) continue;
+ iCol = pConstraint->iColumn - JEACH_JSON;
+ assert( iCol==0 || iCol==1 );
+ iMask = 1 << iCol;
+ if( pConstraint->usable==0 ){
+ unusableMask |= iMask;
+ }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ aIdx[iCol] = i;
+ idxMask |= iMask;
+ }
+ }
+ if( (unusableMask & ~idxMask)!=0 ){
+ /* If there are any unusable constraints on JSON or ROOT, then reject
+ ** this entire plan */
+ return SQLITE_CONSTRAINT;
+ }
+ if( aIdx[0]<0 ){
+ /* No JSON input. Leave estimatedCost at the huge value that it was
+ ** initialized to to discourage the query planner from selecting this
+ ** plan. */
+ pIdxInfo->idxNum = 0;
+ }else{
+ pIdxInfo->estimatedCost = 1.0;
+ i = aIdx[0];
+ pIdxInfo->aConstraintUsage[i].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[i].omit = 1;
+ if( aIdx[1]<0 ){
+ pIdxInfo->idxNum = 1; /* Only JSON supplied. Plan 1 */
+ }else{
+ i = aIdx[1];
+ pIdxInfo->aConstraintUsage[i].argvIndex = 2;
+ pIdxInfo->aConstraintUsage[i].omit = 1;
+ pIdxInfo->idxNum = 3; /* Both JSON and ROOT are supplied. Plan 3 */
+ }
+ }
+ return SQLITE_OK;
+}
+
+/* Start a search on a new JSON string */
+static int jsonEachFilter(
+ tdsqlite3_vtab_cursor *cur,
+ int idxNum, const char *idxStr,
+ int argc, tdsqlite3_value **argv
+){
+ JsonEachCursor *p = (JsonEachCursor*)cur;
+ const char *z;
+ const char *zRoot = 0;
+ tdsqlite3_int64 n;
+
+ UNUSED_PARAM(idxStr);
+ UNUSED_PARAM(argc);
+ jsonEachCursorReset(p);
+ if( idxNum==0 ) return SQLITE_OK;
+ z = (const char*)tdsqlite3_value_text(argv[0]);
+ if( z==0 ) return SQLITE_OK;
+ n = tdsqlite3_value_bytes(argv[0]);
+ p->zJson = tdsqlite3_malloc64( n+1 );
+ if( p->zJson==0 ) return SQLITE_NOMEM;
+ memcpy(p->zJson, z, (size_t)n+1);
+ if( jsonParse(&p->sParse, 0, p->zJson) ){
+ int rc = SQLITE_NOMEM;
+ if( p->sParse.oom==0 ){
+ tdsqlite3_free(cur->pVtab->zErrMsg);
+ cur->pVtab->zErrMsg = tdsqlite3_mprintf("malformed JSON");
+ if( cur->pVtab->zErrMsg ) rc = SQLITE_ERROR;
+ }
+ jsonEachCursorReset(p);
+ return rc;
+ }else if( p->bRecursive && jsonParseFindParents(&p->sParse) ){
+ jsonEachCursorReset(p);
+ return SQLITE_NOMEM;
+ }else{
+ JsonNode *pNode = 0;
+ if( idxNum==3 ){
+ const char *zErr = 0;
+ zRoot = (const char*)tdsqlite3_value_text(argv[1]);
+ if( zRoot==0 ) return SQLITE_OK;
+ n = tdsqlite3_value_bytes(argv[1]);
+ p->zRoot = tdsqlite3_malloc64( n+1 );
+ if( p->zRoot==0 ) return SQLITE_NOMEM;
+ memcpy(p->zRoot, zRoot, (size_t)n+1);
+ if( zRoot[0]!='$' ){
+ zErr = zRoot;
+ }else{
+ pNode = jsonLookupStep(&p->sParse, 0, p->zRoot+1, 0, &zErr);
+ }
+ if( zErr ){
+ tdsqlite3_free(cur->pVtab->zErrMsg);
+ cur->pVtab->zErrMsg = jsonPathSyntaxError(zErr);
+ jsonEachCursorReset(p);
+ return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM;
+ }else if( pNode==0 ){
+ return SQLITE_OK;
+ }
+ }else{
+ pNode = p->sParse.aNode;
+ }
+ p->iBegin = p->i = (int)(pNode - p->sParse.aNode);
+ p->eType = pNode->eType;
+ if( p->eType>=JSON_ARRAY ){
+ pNode->u.iKey = 0;
+ p->iEnd = p->i + pNode->n + 1;
+ if( p->bRecursive ){
+ p->eType = p->sParse.aNode[p->sParse.aUp[p->i]].eType;
+ if( p->i>0 && (p->sParse.aNode[p->i-1].jnFlags & JNODE_LABEL)!=0 ){
+ p->i--;
+ }
+ }else{
+ p->i++;
+ }
+ }else{
+ p->iEnd = p->i+1;
+ }
+ }
+ return SQLITE_OK;
+}
+
+/* The methods of the json_each virtual table */
+static tdsqlite3_module jsonEachModule = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ jsonEachConnect, /* xConnect */
+ jsonEachBestIndex, /* xBestIndex */
+ jsonEachDisconnect, /* xDisconnect */
+ 0, /* xDestroy */
+ jsonEachOpenEach, /* xOpen - open a cursor */
+ jsonEachClose, /* xClose - close a cursor */
+ jsonEachFilter, /* xFilter - configure scan constraints */
+ jsonEachNext, /* xNext - advance a cursor */
+ jsonEachEof, /* xEof - check for end of scan */
+ jsonEachColumn, /* xColumn - read data */
+ jsonEachRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
+};
+
+/* The methods of the json_tree virtual table. */
+static tdsqlite3_module jsonTreeModule = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ jsonEachConnect, /* xConnect */
+ jsonEachBestIndex, /* xBestIndex */
+ jsonEachDisconnect, /* xDisconnect */
+ 0, /* xDestroy */
+ jsonEachOpenTree, /* xOpen - open a cursor */
+ jsonEachClose, /* xClose - close a cursor */
+ jsonEachFilter, /* xFilter - configure scan constraints */
+ jsonEachNext, /* xNext - advance a cursor */
+ jsonEachEof, /* xEof - check for end of scan */
+ jsonEachColumn, /* xColumn - read data */
+ jsonEachRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
+};
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+/****************************************************************************
+** The following routines are the only publically visible identifiers in this
+** file. Call the following routines in order to register the various SQL
+** functions and the virtual table implemented by this file.
+****************************************************************************/
+
+SQLITE_PRIVATE int tdsqlite3Json1Init(tdsqlite3 *db){
+ int rc = SQLITE_OK;
+ unsigned int i;
+ static const struct {
+ const char *zName;
+ int nArg;
+ int flag;
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**);
+ } aFunc[] = {
+ { "json", 1, 0, jsonRemoveFunc },
+ { "json_array", -1, 0, jsonArrayFunc },
+ { "json_array_length", 1, 0, jsonArrayLengthFunc },
+ { "json_array_length", 2, 0, jsonArrayLengthFunc },
+ { "json_extract", -1, 0, jsonExtractFunc },
+ { "json_insert", -1, 0, jsonSetFunc },
+ { "json_object", -1, 0, jsonObjectFunc },
+ { "json_patch", 2, 0, jsonPatchFunc },
+ { "json_quote", 1, 0, jsonQuoteFunc },
+ { "json_remove", -1, 0, jsonRemoveFunc },
+ { "json_replace", -1, 0, jsonReplaceFunc },
+ { "json_set", -1, 1, jsonSetFunc },
+ { "json_type", 1, 0, jsonTypeFunc },
+ { "json_type", 2, 0, jsonTypeFunc },
+ { "json_valid", 1, 0, jsonValidFunc },
+
+#if SQLITE_DEBUG
+ /* DEBUG and TESTING functions */
+ { "json_parse", 1, 0, jsonParseFunc },
+ { "json_test1", 1, 0, jsonTest1Func },
+#endif
+ };
+ static const struct {
+ const char *zName;
+ int nArg;
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**);
+ void (*xFinal)(tdsqlite3_context*);
+ void (*xValue)(tdsqlite3_context*);
+ } aAgg[] = {
+ { "json_group_array", 1,
+ jsonArrayStep, jsonArrayFinal, jsonArrayValue },
+ { "json_group_object", 2,
+ jsonObjectStep, jsonObjectFinal, jsonObjectValue },
+ };
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ static const struct {
+ const char *zName;
+ tdsqlite3_module *pModule;
+ } aMod[] = {
+ { "json_each", &jsonEachModule },
+ { "json_tree", &jsonTreeModule },
+ };
+#endif
+ static const int enc =
+ SQLITE_UTF8 |
+ SQLITE_DETERMINISTIC |
+ SQLITE_INNOCUOUS;
+ for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){
+ rc = tdsqlite3_create_function(db, aFunc[i].zName, aFunc[i].nArg, enc,
+ (void*)&aFunc[i].flag,
+ aFunc[i].xFunc, 0, 0);
+ }
+#ifndef SQLITE_OMIT_WINDOWFUNC
+ for(i=0; i<sizeof(aAgg)/sizeof(aAgg[0]) && rc==SQLITE_OK; i++){
+ rc = tdsqlite3_create_window_function(db, aAgg[i].zName, aAgg[i].nArg,
+ SQLITE_SUBTYPE | enc, 0,
+ aAgg[i].xStep, aAgg[i].xFinal,
+ aAgg[i].xValue, jsonGroupInverse, 0);
+ }
+#endif
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){
+ rc = tdsqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0);
+ }
+#endif
+ return rc;
+}
+
+
+#ifndef SQLITE_CORE
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+SQLITE_API int tdsqlite3_json_init(
+ tdsqlite3 *db,
+ char **pzErrMsg,
+ const tdsqlite3_api_routines *pApi
+){
+ SQLITE_EXTENSION_INIT2(pApi);
+ (void)pzErrMsg; /* Unused parameter */
+ return tdsqlite3Json1Init(db);
+}
+#endif
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_JSON1) */
+
+/************** End of json1.c ***********************************************/
/************** Begin file rtree.c *******************************************/
/*
** 2001 September 15
@@ -164473,14 +189696,15 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){
**
** CREATE TABLE %_node(nodeno INTEGER PRIMARY KEY, data BLOB)
** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER)
-** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER)
+** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER, ...)
**
** The data for each node of the r-tree structure is stored in the %_node
** table. For each node that is not the root node of the r-tree, there is
** an entry in the %_parent table associating the node with its parent.
** And for each row of data in the table, there is an entry in the %_rowid
** table that maps from the entries rowid to the id of the node that it
-** is stored on.
+** is stored on. If the r-tree contains auxiliary columns, those are stored
+** on the end of the %_rowid table.
**
** The root node of an r-tree always exists, even if the r-tree table is
** empty. The nodeno of the root node is always 1. All other nodes in the
@@ -164501,27 +189725,36 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){
** child page.
*/
-#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RTREE)
+#if !defined(SQLITE_CORE) \
+ || (defined(SQLITE_ENABLE_RTREE) && !defined(SQLITE_OMIT_VIRTUALTABLE))
#ifndef SQLITE_CORE
-/* #include "sqlite3ext.h" */
+/* #include "tdsqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#else
/* #include "sqlite3.h" */
#endif
-
-/* #include <string.h> */
-/* #include <assert.h> */
-/* #include <stdio.h> */
+SQLITE_PRIVATE int tdsqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */
#ifndef SQLITE_AMALGAMATION
-#include "sqlite3rtree.h"
-typedef sqlite3_int64 i64;
+#include "tdsqlite3rtree.h"
+typedef tdsqlite3_int64 i64;
+typedef tdsqlite3_uint64 u64;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
+#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
+# define NDEBUG 1
+#endif
+#if defined(NDEBUG) && defined(SQLITE_DEBUG)
+# undef NDEBUG
+#endif
#endif
+/* #include <string.h> */
+/* #include <stdio.h> */
+/* #include <assert.h> */
+
/* The following macro is used to suppress compiler warnings.
*/
#ifndef UNUSED_PARAMETER
@@ -164541,6 +189774,9 @@ typedef struct RtreeSearchPoint RtreeSearchPoint;
/* The rtree may have between 1 and RTREE_MAX_DIMENSIONS dimensions. */
#define RTREE_MAX_DIMENSIONS 5
+/* Maximum number of auxiliary columns */
+#define RTREE_MAX_AUX_COLUMN 100
+
/* Size of hash table Rtree.aHash. This hash table is not expected to
** ever contain very many entries, so a fixed number of buckets is
** used.
@@ -164561,17 +189797,27 @@ typedef struct RtreeSearchPoint RtreeSearchPoint;
** An rtree virtual-table object.
*/
struct Rtree {
- sqlite3_vtab base; /* Base class. Must be first */
- sqlite3 *db; /* Host database connection */
+ tdsqlite3_vtab base; /* Base class. Must be first */
+ tdsqlite3 *db; /* Host database connection */
int iNodeSize; /* Size in bytes of each node in the node table */
u8 nDim; /* Number of dimensions */
+ u8 nDim2; /* Twice the number of dimensions */
u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */
u8 nBytesPerCell; /* Bytes consumed per cell */
+ u8 inWrTrans; /* True if inside write transaction */
+ u8 nAux; /* # of auxiliary columns in %_rowid */
+ u8 nAuxNotNull; /* Number of initial not-null aux columns */
+#ifdef SQLITE_DEBUG
+ u8 bCorrupt; /* Shadow table corruption detected */
+#endif
int iDepth; /* Current depth of the r-tree structure */
char *zDb; /* Name of database containing r-tree table */
char *zName; /* Name of r-tree table */
- int nBusy; /* Current number of users of this structure */
+ u32 nBusy; /* Current number of users of this structure */
i64 nRowEst; /* Estimated number of rows in this table */
+ u32 nCursor; /* Number of open cursors */
+ u32 nNodeRef; /* Number RtreeNodes with positive nRef */
+ char *zReadAuxSql; /* SQL for statement to read aux data */
/* List of nodes removed during a CondenseTree operation. List is
** linked together via the pointer normally used for hash chains -
@@ -164581,20 +189827,25 @@ struct Rtree {
RtreeNode *pDeleted;
int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */
+ /* Blob I/O on xxx_node */
+ tdsqlite3_blob *pNodeBlob;
+
/* Statements to read/write/delete a record from xxx_node */
- sqlite3_stmt *pReadNode;
- sqlite3_stmt *pWriteNode;
- sqlite3_stmt *pDeleteNode;
+ tdsqlite3_stmt *pWriteNode;
+ tdsqlite3_stmt *pDeleteNode;
/* Statements to read/write/delete a record from xxx_rowid */
- sqlite3_stmt *pReadRowid;
- sqlite3_stmt *pWriteRowid;
- sqlite3_stmt *pDeleteRowid;
+ tdsqlite3_stmt *pReadRowid;
+ tdsqlite3_stmt *pWriteRowid;
+ tdsqlite3_stmt *pDeleteRowid;
/* Statements to read/write/delete a record from xxx_parent */
- sqlite3_stmt *pReadParent;
- sqlite3_stmt *pWriteParent;
- sqlite3_stmt *pDeleteParent;
+ tdsqlite3_stmt *pReadParent;
+ tdsqlite3_stmt *pWriteParent;
+ tdsqlite3_stmt *pDeleteParent;
+
+ /* Statement for writing to the "aux:" fields, if there are any */
+ tdsqlite3_stmt *pWriteAux;
RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
};
@@ -164609,7 +189860,7 @@ struct Rtree {
** will be done.
*/
#ifdef SQLITE_RTREE_INT_ONLY
- typedef sqlite3_int64 RtreeDValue; /* High accuracy coordinate */
+ typedef tdsqlite3_int64 RtreeDValue; /* High accuracy coordinate */
typedef int RtreeValue; /* Low accuracy coordinate */
# define RTREE_ZERO 0
#else
@@ -164619,6 +189870,15 @@ struct Rtree {
#endif
/*
+** Set the Rtree.bCorrupt flag
+*/
+#ifdef SQLITE_DEBUG
+# define RTREE_IS_CORRUPT(X) ((X)->bCorrupt = 1)
+#else
+# define RTREE_IS_CORRUPT(X)
+#endif
+
+/*
** When doing a search of an r-tree, instances of the following structure
** record intermediate results from the tree walk.
**
@@ -164629,7 +189889,7 @@ struct Rtree {
*/
struct RtreeSearchPoint {
RtreeDValue rScore; /* The score for this node. Smallest goes first. */
- sqlite3_int64 id; /* Node ID */
+ tdsqlite3_int64 id; /* Node ID */
u8 iLevel; /* 0=entries. 1=leaf node. 2+ for higher */
u8 eWithin; /* PARTLY_WITHIN or FULLY_WITHIN */
u8 iCell; /* Cell index within the node */
@@ -164652,7 +189912,7 @@ struct RtreeSearchPoint {
** The smallest possible node-size is (512-64)==448 bytes. And the largest
** supported cell size is 48 bytes (8 byte rowid + ten 4 byte coordinates).
** Therefore all non-root nodes must contain at least 3 entries. Since
-** 2^40 is greater than 2^64, an r-tree structure always has a depth of
+** 3^40 is greater than 2^64, an r-tree structure always has a depth of
** 40 or less.
*/
#define RTREE_MAX_DEPTH 40
@@ -164669,9 +189929,10 @@ struct RtreeSearchPoint {
** An rtree cursor object.
*/
struct RtreeCursor {
- sqlite3_vtab_cursor base; /* Base class. Must be first */
+ tdsqlite3_vtab_cursor base; /* Base class. Must be first */
u8 atEOF; /* True if at end of search */
u8 bPoint; /* True if sPoint is valid */
+ u8 bAuxValid; /* True if pReadAux is valid */
int iStrategy; /* Copy of idxNum search parameter */
int nConstraint; /* Number of entries in aConstraint */
RtreeConstraint *aConstraint; /* Search constraints. */
@@ -164679,6 +189940,7 @@ struct RtreeCursor {
int nPoint; /* Number of slots used in aPoint[] */
int mxLevel; /* iLevel value for root of the tree */
RtreeSearchPoint *aPoint; /* Priority queue for search points */
+ tdsqlite3_stmt *pReadAux; /* Statement to read aux-data */
RtreeSearchPoint sPoint; /* Cached next search point */
RtreeNode *aNode[RTREE_CACHE_SZ]; /* Rtree node cache */
u32 anQueue[RTREE_MAX_DEPTH+1]; /* Number of queued entries by iLevel */
@@ -164721,10 +189983,10 @@ struct RtreeConstraint {
int op; /* Constraining operation */
union {
RtreeDValue rValue; /* Constraint value. */
- int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*);
- int (*xQueryFunc)(sqlite3_rtree_query_info*);
+ int (*xGeom)(tdsqlite3_rtree_geometry*,int,RtreeDValue*,int*);
+ int (*xQueryFunc)(tdsqlite3_rtree_query_info*);
} u;
- sqlite3_rtree_query_info *pInfo; /* xGeom and xQueryFunc argument */
+ tdsqlite3_rtree_query_info *pInfo; /* xGeom and xQueryFunc argument */
};
/* Possible values for RtreeConstraint.op */
@@ -164733,9 +189995,15 @@ struct RtreeConstraint {
#define RTREE_LT 0x43 /* C */
#define RTREE_GE 0x44 /* D */
#define RTREE_GT 0x45 /* E */
-#define RTREE_MATCH 0x46 /* F: Old-style sqlite3_rtree_geometry_callback() */
-#define RTREE_QUERY 0x47 /* G: New-style sqlite3_rtree_query_callback() */
+#define RTREE_MATCH 0x46 /* F: Old-style tdsqlite3_rtree_geometry_callback() */
+#define RTREE_QUERY 0x47 /* G: New-style tdsqlite3_rtree_query_callback() */
+/* Special operators available only on cursors. Needs to be consecutive
+** with the normal values above, but must be less than RTREE_MATCH. These
+** are used in the cursor for contraints such as x=NULL (RTREE_FALSE) or
+** x<'xyz' (RTREE_TRUE) */
+#define RTREE_TRUE 0x3f /* ? */
+#define RTREE_FALSE 0x40 /* @ */
/*
** An rtree structure node.
@@ -164762,45 +190030,37 @@ struct RtreeCell {
/*
-** This object becomes the sqlite3_user_data() for the SQL functions
-** that are created by sqlite3_rtree_geometry_callback() and
-** sqlite3_rtree_query_callback() and which appear on the right of MATCH
+** This object becomes the tdsqlite3_user_data() for the SQL functions
+** that are created by tdsqlite3_rtree_geometry_callback() and
+** tdsqlite3_rtree_query_callback() and which appear on the right of MATCH
** operators in order to constrain a search.
**
** xGeom and xQueryFunc are the callback functions. Exactly one of
** xGeom and xQueryFunc fields is non-NULL, depending on whether the
-** SQL function was created using sqlite3_rtree_geometry_callback() or
-** sqlite3_rtree_query_callback().
+** SQL function was created using tdsqlite3_rtree_geometry_callback() or
+** tdsqlite3_rtree_query_callback().
**
** This object is deleted automatically by the destructor mechanism in
-** sqlite3_create_function_v2().
+** tdsqlite3_create_function_v2().
*/
struct RtreeGeomCallback {
- int (*xGeom)(sqlite3_rtree_geometry*, int, RtreeDValue*, int*);
- int (*xQueryFunc)(sqlite3_rtree_query_info*);
+ int (*xGeom)(tdsqlite3_rtree_geometry*, int, RtreeDValue*, int*);
+ int (*xQueryFunc)(tdsqlite3_rtree_query_info*);
void (*xDestructor)(void*);
void *pContext;
};
-
-/*
-** Value for the first field of every RtreeMatchArg object. The MATCH
-** operator tests that the first field of a blob operand matches this
-** value to avoid operating on invalid blobs (which could cause a segfault).
-*/
-#define RTREE_GEOMETRY_MAGIC 0x891245AB
-
/*
** An instance of this structure (in the form of a BLOB) is returned by
-** the SQL functions that sqlite3_rtree_geometry_callback() and
-** sqlite3_rtree_query_callback() create, and is read as the right-hand
+** the SQL functions that tdsqlite3_rtree_geometry_callback() and
+** tdsqlite3_rtree_query_callback() create, and is read as the right-hand
** operand to the MATCH operator of an R-Tree.
*/
struct RtreeMatchArg {
- u32 magic; /* Always RTREE_GEOMETRY_MAGIC */
+ u32 iSize; /* Size of this object */
RtreeGeomCallback cb; /* Info about the callback functions */
int nParam; /* Number of parameters to the SQL function */
- sqlite3_value **apSqlParam; /* Original SQL parameter values */
+ tdsqlite3_value **apSqlParam; /* Original SQL parameter values */
RtreeDValue aParam[1]; /* Values for parameters to the SQL function */
};
@@ -164811,6 +190071,58 @@ struct RtreeMatchArg {
# define MIN(x,y) ((x) > (y) ? (y) : (x))
#endif
+/* What version of GCC is being used. 0 means GCC is not being used .
+** Note that the GCC_VERSION macro will also be set correctly when using
+** clang, since clang works hard to be gcc compatible. So the gcc
+** optimizations will also work when compiling with clang.
+*/
+#ifndef GCC_VERSION
+#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC)
+# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__)
+#else
+# define GCC_VERSION 0
+#endif
+#endif
+
+/* The testcase() macro should already be defined in the amalgamation. If
+** it is not, make it a no-op.
+*/
+#ifndef SQLITE_AMALGAMATION
+# define testcase(X)
+#endif
+
+/*
+** Macros to determine whether the machine is big or little endian,
+** and whether or not that determination is run-time or compile-time.
+**
+** For best performance, an attempt is made to guess at the byte-order
+** using C-preprocessor macros. If that is unsuccessful, or if
+** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined
+** at run-time.
+*/
+#ifndef SQLITE_BYTEORDER
+#if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
+ defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
+ defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
+ defined(__arm__)
+# define SQLITE_BYTEORDER 1234
+#elif defined(sparc) || defined(__ppc__)
+# define SQLITE_BYTEORDER 4321
+#else
+# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */
+#endif
+#endif
+
+
+/* What version of MSVC is being used. 0 means MSVC is not being used */
+#ifndef MSVC_VERSION
+#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC)
+# define MSVC_VERSION _MSC_VER
+#else
+# define MSVC_VERSION 0
+#endif
+#endif
+
/*
** Functions to deserialize a 16 bit integer, 32 bit real number and
** 64 bit integer. The deserialized value is returned.
@@ -164819,24 +190131,47 @@ static int readInt16(u8 *p){
return (p[0]<<8) + p[1];
}
static void readCoord(u8 *p, RtreeCoord *pCoord){
+ assert( ((((char*)p) - (char*)0)&3)==0 ); /* p is always 4-byte aligned */
+#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+ pCoord->u = _byteswap_ulong(*(u32*)p);
+#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+ pCoord->u = __builtin_bswap32(*(u32*)p);
+#elif SQLITE_BYTEORDER==4321
+ pCoord->u = *(u32*)p;
+#else
pCoord->u = (
(((u32)p[0]) << 24) +
(((u32)p[1]) << 16) +
(((u32)p[2]) << 8) +
(((u32)p[3]) << 0)
);
+#endif
}
static i64 readInt64(u8 *p){
- return (
- (((i64)p[0]) << 56) +
- (((i64)p[1]) << 48) +
- (((i64)p[2]) << 40) +
- (((i64)p[3]) << 32) +
- (((i64)p[4]) << 24) +
- (((i64)p[5]) << 16) +
- (((i64)p[6]) << 8) +
- (((i64)p[7]) << 0)
+#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+ u64 x;
+ memcpy(&x, p, 8);
+ return (i64)_byteswap_uint64(x);
+#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+ u64 x;
+ memcpy(&x, p, 8);
+ return (i64)__builtin_bswap64(x);
+#elif SQLITE_BYTEORDER==4321
+ i64 x;
+ memcpy(&x, p, 8);
+ return x;
+#else
+ return (i64)(
+ (((u64)p[0]) << 56) +
+ (((u64)p[1]) << 48) +
+ (((u64)p[2]) << 40) +
+ (((u64)p[3]) << 32) +
+ (((u64)p[4]) << 24) +
+ (((u64)p[5]) << 16) +
+ (((u64)p[6]) << 8) +
+ (((u64)p[7]) << 0)
);
+#endif
}
/*
@@ -164844,23 +190179,43 @@ static i64 readInt64(u8 *p){
** 64 bit integer. The value returned is the number of bytes written
** to the argument buffer (always 2, 4 and 8 respectively).
*/
-static int writeInt16(u8 *p, int i){
+static void writeInt16(u8 *p, int i){
p[0] = (i>> 8)&0xFF;
p[1] = (i>> 0)&0xFF;
- return 2;
}
static int writeCoord(u8 *p, RtreeCoord *pCoord){
u32 i;
+ assert( ((((char*)p) - (char*)0)&3)==0 ); /* p is always 4-byte aligned */
assert( sizeof(RtreeCoord)==4 );
assert( sizeof(u32)==4 );
+#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+ i = __builtin_bswap32(pCoord->u);
+ memcpy(p, &i, 4);
+#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+ i = _byteswap_ulong(pCoord->u);
+ memcpy(p, &i, 4);
+#elif SQLITE_BYTEORDER==4321
+ i = pCoord->u;
+ memcpy(p, &i, 4);
+#else
i = pCoord->u;
p[0] = (i>>24)&0xFF;
p[1] = (i>>16)&0xFF;
p[2] = (i>> 8)&0xFF;
p[3] = (i>> 0)&0xFF;
+#endif
return 4;
}
static int writeInt64(u8 *p, i64 i){
+#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+ i = (i64)__builtin_bswap64((u64)i);
+ memcpy(p, &i, 8);
+#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+ i = (i64)_byteswap_uint64((u64)i);
+ memcpy(p, &i, 8);
+#elif SQLITE_BYTEORDER==4321
+ memcpy(p, &i, 8);
+#else
p[0] = (i>>56)&0xFF;
p[1] = (i>>48)&0xFF;
p[2] = (i>>40)&0xFF;
@@ -164869,6 +190224,7 @@ static int writeInt64(u8 *p, i64 i){
p[5] = (i>>16)&0xFF;
p[6] = (i>> 8)&0xFF;
p[7] = (i>> 0)&0xFF;
+#endif
return 8;
}
@@ -164877,6 +190233,7 @@ static int writeInt64(u8 *p, i64 i){
*/
static void nodeReference(RtreeNode *p){
if( p ){
+ assert( p->nRef>0 );
p->nRef++;
}
}
@@ -164893,8 +190250,8 @@ static void nodeZero(Rtree *pRtree, RtreeNode *p){
** Given a node number iNode, return the corresponding key to use
** in the Rtree.aHash table.
*/
-static int nodeHash(i64 iNode){
- return iNode % HASHSIZE;
+static unsigned int nodeHash(i64 iNode){
+ return ((unsigned)iNode) % HASHSIZE;
}
/*
@@ -164939,11 +190296,12 @@ static void nodeHashDelete(Rtree *pRtree, RtreeNode *pNode){
*/
static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){
RtreeNode *pNode;
- pNode = (RtreeNode *)sqlite3_malloc(sizeof(RtreeNode) + pRtree->iNodeSize);
+ pNode = (RtreeNode *)tdsqlite3_malloc64(sizeof(RtreeNode) + pRtree->iNodeSize);
if( pNode ){
memset(pNode, 0, sizeof(RtreeNode) + pRtree->iNodeSize);
pNode->zData = (u8 *)&pNode[1];
pNode->nRef = 1;
+ pRtree->nNodeRef++;
pNode->pParent = pParent;
pNode->isDirty = 1;
nodeReference(pParent);
@@ -164952,6 +190310,29 @@ static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){
}
/*
+** Clear the Rtree.pNodeBlob object
+*/
+static void nodeBlobReset(Rtree *pRtree){
+ if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){
+ tdsqlite3_blob *pBlob = pRtree->pNodeBlob;
+ pRtree->pNodeBlob = 0;
+ tdsqlite3_blob_close(pBlob);
+ }
+}
+
+/*
+** Check to see if pNode is the same as pParent or any of the parents
+** of pParent.
+*/
+static int nodeInParentChain(const RtreeNode *pNode, const RtreeNode *pParent){
+ do{
+ if( pNode==pParent ) return 1;
+ pParent = pParent->pParent;
+ }while( pParent );
+ return 0;
+}
+
+/*
** Obtain a reference to an r-tree node.
*/
static int nodeAcquire(
@@ -164960,46 +190341,71 @@ static int nodeAcquire(
RtreeNode *pParent, /* Either the parent node or NULL */
RtreeNode **ppNode /* OUT: Acquired node */
){
- int rc;
- int rc2 = SQLITE_OK;
- RtreeNode *pNode;
+ int rc = SQLITE_OK;
+ RtreeNode *pNode = 0;
/* Check if the requested node is already in the hash table. If so,
** increase its reference count and return it.
*/
- if( (pNode = nodeHashLookup(pRtree, iNode)) ){
- assert( !pParent || !pNode->pParent || pNode->pParent==pParent );
+ if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){
if( pParent && !pNode->pParent ){
- nodeReference(pParent);
+ if( nodeInParentChain(pNode, pParent) ){
+ RTREE_IS_CORRUPT(pRtree);
+ return SQLITE_CORRUPT_VTAB;
+ }
+ pParent->nRef++;
pNode->pParent = pParent;
+ }else if( pParent && pNode->pParent && pParent!=pNode->pParent ){
+ RTREE_IS_CORRUPT(pRtree);
+ return SQLITE_CORRUPT_VTAB;
}
pNode->nRef++;
*ppNode = pNode;
return SQLITE_OK;
}
- sqlite3_bind_int64(pRtree->pReadNode, 1, iNode);
- rc = sqlite3_step(pRtree->pReadNode);
- if( rc==SQLITE_ROW ){
- const u8 *zBlob = sqlite3_column_blob(pRtree->pReadNode, 0);
- if( pRtree->iNodeSize==sqlite3_column_bytes(pRtree->pReadNode, 0) ){
- pNode = (RtreeNode *)sqlite3_malloc(sizeof(RtreeNode)+pRtree->iNodeSize);
- if( !pNode ){
- rc2 = SQLITE_NOMEM;
- }else{
- pNode->pParent = pParent;
- pNode->zData = (u8 *)&pNode[1];
- pNode->nRef = 1;
- pNode->iNode = iNode;
- pNode->isDirty = 0;
- pNode->pNext = 0;
- memcpy(pNode->zData, zBlob, pRtree->iNodeSize);
- nodeReference(pParent);
- }
+ if( pRtree->pNodeBlob ){
+ tdsqlite3_blob *pBlob = pRtree->pNodeBlob;
+ pRtree->pNodeBlob = 0;
+ rc = tdsqlite3_blob_reopen(pBlob, iNode);
+ pRtree->pNodeBlob = pBlob;
+ if( rc ){
+ nodeBlobReset(pRtree);
+ if( rc==SQLITE_NOMEM ) return SQLITE_NOMEM;
+ }
+ }
+ if( pRtree->pNodeBlob==0 ){
+ char *zTab = tdsqlite3_mprintf("%s_node", pRtree->zName);
+ if( zTab==0 ) return SQLITE_NOMEM;
+ rc = tdsqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0,
+ &pRtree->pNodeBlob);
+ tdsqlite3_free(zTab);
+ }
+ if( rc ){
+ nodeBlobReset(pRtree);
+ *ppNode = 0;
+ /* If unable to open an tdsqlite3_blob on the desired row, that can only
+ ** be because the shadow tables hold erroneous data. */
+ if( rc==SQLITE_ERROR ){
+ rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
+ }
+ }else if( pRtree->iNodeSize==tdsqlite3_blob_bytes(pRtree->pNodeBlob) ){
+ pNode = (RtreeNode *)tdsqlite3_malloc64(sizeof(RtreeNode)+pRtree->iNodeSize);
+ if( !pNode ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pNode->pParent = pParent;
+ pNode->zData = (u8 *)&pNode[1];
+ pNode->nRef = 1;
+ pRtree->nNodeRef++;
+ pNode->iNode = iNode;
+ pNode->isDirty = 0;
+ pNode->pNext = 0;
+ rc = tdsqlite3_blob_read(pRtree->pNodeBlob, pNode->zData,
+ pRtree->iNodeSize, 0);
}
}
- rc = sqlite3_reset(pRtree->pReadNode);
- if( rc==SQLITE_OK ) rc = rc2;
/* If the root node was just loaded, set pRtree->iDepth to the height
** of the r-tree structure. A height of zero means all data is stored on
@@ -165011,6 +190417,7 @@ static int nodeAcquire(
pRtree->iDepth = readInt16(pNode->zData);
if( pRtree->iDepth>RTREE_MAX_DEPTH ){
rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
}
}
@@ -165021,18 +190428,24 @@ static int nodeAcquire(
if( pNode && rc==SQLITE_OK ){
if( NCELL(pNode)>((pRtree->iNodeSize-4)/pRtree->nBytesPerCell) ){
rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
}
}
if( rc==SQLITE_OK ){
if( pNode!=0 ){
+ nodeReference(pParent);
nodeHashInsert(pRtree, pNode);
}else{
rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
}
*ppNode = pNode;
}else{
- sqlite3_free(pNode);
+ if( pNode ){
+ pRtree->nNodeRef--;
+ tdsqlite3_free(pNode);
+ }
*ppNode = 0;
}
@@ -165051,7 +190464,7 @@ static void nodeOverwriteCell(
int ii;
u8 *p = &pNode->zData[4 + pRtree->nBytesPerCell*iCell];
p += writeInt64(p, pCell->iRowid);
- for(ii=0; ii<(pRtree->nDim*2); ii++){
+ for(ii=0; ii<pRtree->nDim2; ii++){
p += writeCoord(p, &pCell->aCoord[ii]);
}
pNode->isDirty = 1;
@@ -165102,18 +190515,19 @@ static int nodeInsertCell(
static int nodeWrite(Rtree *pRtree, RtreeNode *pNode){
int rc = SQLITE_OK;
if( pNode->isDirty ){
- sqlite3_stmt *p = pRtree->pWriteNode;
+ tdsqlite3_stmt *p = pRtree->pWriteNode;
if( pNode->iNode ){
- sqlite3_bind_int64(p, 1, pNode->iNode);
+ tdsqlite3_bind_int64(p, 1, pNode->iNode);
}else{
- sqlite3_bind_null(p, 1);
+ tdsqlite3_bind_null(p, 1);
}
- sqlite3_bind_blob(p, 2, pNode->zData, pRtree->iNodeSize, SQLITE_STATIC);
- sqlite3_step(p);
+ tdsqlite3_bind_blob(p, 2, pNode->zData, pRtree->iNodeSize, SQLITE_STATIC);
+ tdsqlite3_step(p);
pNode->isDirty = 0;
- rc = sqlite3_reset(p);
+ rc = tdsqlite3_reset(p);
+ tdsqlite3_bind_null(p, 2);
if( pNode->iNode==0 && rc==SQLITE_OK ){
- pNode->iNode = sqlite3_last_insert_rowid(pRtree->db);
+ pNode->iNode = tdsqlite3_last_insert_rowid(pRtree->db);
nodeHashInsert(pRtree, pNode);
}
}
@@ -165128,8 +190542,10 @@ static int nodeRelease(Rtree *pRtree, RtreeNode *pNode){
int rc = SQLITE_OK;
if( pNode ){
assert( pNode->nRef>0 );
+ assert( pRtree->nNodeRef>0 );
pNode->nRef--;
if( pNode->nRef==0 ){
+ pRtree->nNodeRef--;
if( pNode->iNode==1 ){
pRtree->iDepth = -1;
}
@@ -165140,7 +190556,7 @@ static int nodeRelease(Rtree *pRtree, RtreeNode *pNode){
rc = nodeWrite(pRtree, pNode);
}
nodeHashDelete(pRtree, pNode);
- sqlite3_free(pNode);
+ tdsqlite3_free(pNode);
}
}
return rc;
@@ -165185,13 +190601,16 @@ static void nodeGetCell(
){
u8 *pData;
RtreeCoord *pCoord;
- int ii;
+ int ii = 0;
pCell->iRowid = nodeGetRowid(pRtree, pNode, iCell);
pData = pNode->zData + (12 + pRtree->nBytesPerCell*iCell);
pCoord = pCell->aCoord;
- for(ii=0; ii<pRtree->nDim*2; ii++){
- readCoord(&pData[ii*4], &pCoord[ii]);
- }
+ do{
+ readCoord(pData, &pCoord[ii]);
+ readCoord(pData+4, &pCoord[ii+1]);
+ pData += 8;
+ ii += 2;
+ }while( ii<pRtree->nDim2 );
}
@@ -165199,17 +190618,17 @@ static void nodeGetCell(
** the virtual table module xCreate() and xConnect() methods.
*/
static int rtreeInit(
- sqlite3 *, void *, int, const char *const*, sqlite3_vtab **, char **, int
+ tdsqlite3 *, void *, int, const char *const*, tdsqlite3_vtab **, char **, int
);
/*
** Rtree virtual table module xCreate method.
*/
static int rtreeCreate(
- sqlite3 *db,
+ tdsqlite3 *db,
void *pAux,
int argc, const char *const*argv,
- sqlite3_vtab **ppVtab,
+ tdsqlite3_vtab **ppVtab,
char **pzErr
){
return rtreeInit(db, pAux, argc, argv, ppVtab, pzErr, 1);
@@ -165219,10 +190638,10 @@ static int rtreeCreate(
** Rtree virtual table module xConnect method.
*/
static int rtreeConnect(
- sqlite3 *db,
+ tdsqlite3 *db,
void *pAux,
int argc, const char *const*argv,
- sqlite3_vtab **ppVtab,
+ tdsqlite3_vtab **ppVtab,
char **pzErr
){
return rtreeInit(db, pAux, argc, argv, ppVtab, pzErr, 0);
@@ -165242,23 +190661,28 @@ static void rtreeReference(Rtree *pRtree){
static void rtreeRelease(Rtree *pRtree){
pRtree->nBusy--;
if( pRtree->nBusy==0 ){
- sqlite3_finalize(pRtree->pReadNode);
- sqlite3_finalize(pRtree->pWriteNode);
- sqlite3_finalize(pRtree->pDeleteNode);
- sqlite3_finalize(pRtree->pReadRowid);
- sqlite3_finalize(pRtree->pWriteRowid);
- sqlite3_finalize(pRtree->pDeleteRowid);
- sqlite3_finalize(pRtree->pReadParent);
- sqlite3_finalize(pRtree->pWriteParent);
- sqlite3_finalize(pRtree->pDeleteParent);
- sqlite3_free(pRtree);
+ pRtree->inWrTrans = 0;
+ assert( pRtree->nCursor==0 );
+ nodeBlobReset(pRtree);
+ assert( pRtree->nNodeRef==0 || pRtree->bCorrupt );
+ tdsqlite3_finalize(pRtree->pWriteNode);
+ tdsqlite3_finalize(pRtree->pDeleteNode);
+ tdsqlite3_finalize(pRtree->pReadRowid);
+ tdsqlite3_finalize(pRtree->pWriteRowid);
+ tdsqlite3_finalize(pRtree->pDeleteRowid);
+ tdsqlite3_finalize(pRtree->pReadParent);
+ tdsqlite3_finalize(pRtree->pWriteParent);
+ tdsqlite3_finalize(pRtree->pDeleteParent);
+ tdsqlite3_finalize(pRtree->pWriteAux);
+ tdsqlite3_free(pRtree->zReadAuxSql);
+ tdsqlite3_free(pRtree);
}
}
/*
** Rtree virtual table module xDisconnect method.
*/
-static int rtreeDisconnect(sqlite3_vtab *pVtab){
+static int rtreeDisconnect(tdsqlite3_vtab *pVtab){
rtreeRelease((Rtree *)pVtab);
return SQLITE_OK;
}
@@ -165266,10 +190690,10 @@ static int rtreeDisconnect(sqlite3_vtab *pVtab){
/*
** Rtree virtual table module xDestroy method.
*/
-static int rtreeDestroy(sqlite3_vtab *pVtab){
+static int rtreeDestroy(tdsqlite3_vtab *pVtab){
Rtree *pRtree = (Rtree *)pVtab;
int rc;
- char *zCreate = sqlite3_mprintf(
+ char *zCreate = tdsqlite3_mprintf(
"DROP TABLE '%q'.'%q_node';"
"DROP TABLE '%q'.'%q_rowid';"
"DROP TABLE '%q'.'%q_parent';",
@@ -165280,8 +190704,9 @@ static int rtreeDestroy(sqlite3_vtab *pVtab){
if( !zCreate ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_exec(pRtree->db, zCreate, 0, 0, 0);
- sqlite3_free(zCreate);
+ nodeBlobReset(pRtree);
+ rc = tdsqlite3_exec(pRtree->db, zCreate, 0, 0, 0);
+ tdsqlite3_free(zCreate);
}
if( rc==SQLITE_OK ){
rtreeRelease(pRtree);
@@ -165293,51 +190718,64 @@ static int rtreeDestroy(sqlite3_vtab *pVtab){
/*
** Rtree virtual table module xOpen method.
*/
-static int rtreeOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+static int rtreeOpen(tdsqlite3_vtab *pVTab, tdsqlite3_vtab_cursor **ppCursor){
int rc = SQLITE_NOMEM;
+ Rtree *pRtree = (Rtree *)pVTab;
RtreeCursor *pCsr;
- pCsr = (RtreeCursor *)sqlite3_malloc(sizeof(RtreeCursor));
+ pCsr = (RtreeCursor *)tdsqlite3_malloc64(sizeof(RtreeCursor));
if( pCsr ){
memset(pCsr, 0, sizeof(RtreeCursor));
pCsr->base.pVtab = pVTab;
rc = SQLITE_OK;
+ pRtree->nCursor++;
}
- *ppCursor = (sqlite3_vtab_cursor *)pCsr;
+ *ppCursor = (tdsqlite3_vtab_cursor *)pCsr;
return rc;
}
/*
-** Free the RtreeCursor.aConstraint[] array and its contents.
+** Reset a cursor back to its initial state.
*/
-static void freeCursorConstraints(RtreeCursor *pCsr){
+static void resetCursor(RtreeCursor *pCsr){
+ Rtree *pRtree = (Rtree *)(pCsr->base.pVtab);
+ int ii;
+ tdsqlite3_stmt *pStmt;
if( pCsr->aConstraint ){
int i; /* Used to iterate through constraint array */
for(i=0; i<pCsr->nConstraint; i++){
- sqlite3_rtree_query_info *pInfo = pCsr->aConstraint[i].pInfo;
+ tdsqlite3_rtree_query_info *pInfo = pCsr->aConstraint[i].pInfo;
if( pInfo ){
if( pInfo->xDelUser ) pInfo->xDelUser(pInfo->pUser);
- sqlite3_free(pInfo);
+ tdsqlite3_free(pInfo);
}
}
- sqlite3_free(pCsr->aConstraint);
+ tdsqlite3_free(pCsr->aConstraint);
pCsr->aConstraint = 0;
}
+ for(ii=0; ii<RTREE_CACHE_SZ; ii++) nodeRelease(pRtree, pCsr->aNode[ii]);
+ tdsqlite3_free(pCsr->aPoint);
+ pStmt = pCsr->pReadAux;
+ memset(pCsr, 0, sizeof(RtreeCursor));
+ pCsr->base.pVtab = (tdsqlite3_vtab*)pRtree;
+ pCsr->pReadAux = pStmt;
+
}
/*
** Rtree virtual table module xClose method.
*/
-static int rtreeClose(sqlite3_vtab_cursor *cur){
+static int rtreeClose(tdsqlite3_vtab_cursor *cur){
Rtree *pRtree = (Rtree *)(cur->pVtab);
- int ii;
RtreeCursor *pCsr = (RtreeCursor *)cur;
- freeCursorConstraints(pCsr);
- sqlite3_free(pCsr->aPoint);
- for(ii=0; ii<RTREE_CACHE_SZ; ii++) nodeRelease(pRtree, pCsr->aNode[ii]);
- sqlite3_free(pCsr);
+ assert( pRtree->nCursor>0 );
+ resetCursor(pCsr);
+ tdsqlite3_finalize(pCsr->pReadAux);
+ tdsqlite3_free(pCsr);
+ pRtree->nCursor--;
+ nodeBlobReset(pRtree);
return SQLITE_OK;
}
@@ -165347,7 +190785,7 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){
** Return non-zero if the cursor does not currently point to a valid
** record (i.e if the scan has finished), or zero otherwise.
*/
-static int rtreeEof(sqlite3_vtab_cursor *cur){
+static int rtreeEof(tdsqlite3_vtab_cursor *cur){
RtreeCursor *pCsr = (RtreeCursor *)cur;
return pCsr->atEOF;
}
@@ -165360,34 +190798,41 @@ static int rtreeEof(sqlite3_vtab_cursor *cur){
** false. a[] is the four bytes of the on-disk record to be decoded.
** Store the results in "r".
**
-** There are three versions of this macro, one each for little-endian and
-** big-endian processors and a third generic implementation. The endian-
-** specific implementations are much faster and are preferred if the
-** processor endianness is known at compile-time. The SQLITE_BYTEORDER
-** macro is part of sqliteInt.h and hence the endian-specific
-** implementation will only be used if this module is compiled as part
-** of the amalgamation.
+** There are five versions of this macro. The last one is generic. The
+** other four are various architectures-specific optimizations.
*/
-#if defined(SQLITE_BYTEORDER) && SQLITE_BYTEORDER==1234
+#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+#define RTREE_DECODE_COORD(eInt, a, r) { \
+ RtreeCoord c; /* Coordinate decoded */ \
+ c.u = _byteswap_ulong(*(u32*)a); \
+ r = eInt ? (tdsqlite3_rtree_dbl)c.i : (tdsqlite3_rtree_dbl)c.f; \
+}
+#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+#define RTREE_DECODE_COORD(eInt, a, r) { \
+ RtreeCoord c; /* Coordinate decoded */ \
+ c.u = __builtin_bswap32(*(u32*)a); \
+ r = eInt ? (tdsqlite3_rtree_dbl)c.i : (tdsqlite3_rtree_dbl)c.f; \
+}
+#elif SQLITE_BYTEORDER==1234
#define RTREE_DECODE_COORD(eInt, a, r) { \
RtreeCoord c; /* Coordinate decoded */ \
memcpy(&c.u,a,4); \
c.u = ((c.u>>24)&0xff)|((c.u>>8)&0xff00)| \
((c.u&0xff)<<24)|((c.u&0xff00)<<8); \
- r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \
+ r = eInt ? (tdsqlite3_rtree_dbl)c.i : (tdsqlite3_rtree_dbl)c.f; \
}
-#elif defined(SQLITE_BYTEORDER) && SQLITE_BYTEORDER==4321
+#elif SQLITE_BYTEORDER==4321
#define RTREE_DECODE_COORD(eInt, a, r) { \
RtreeCoord c; /* Coordinate decoded */ \
memcpy(&c.u,a,4); \
- r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \
+ r = eInt ? (tdsqlite3_rtree_dbl)c.i : (tdsqlite3_rtree_dbl)c.f; \
}
#else
#define RTREE_DECODE_COORD(eInt, a, r) { \
RtreeCoord c; /* Coordinate decoded */ \
c.u = ((u32)a[0]<<24) + ((u32)a[1]<<16) \
+((u32)a[2]<<8) + a[3]; \
- r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \
+ r = eInt ? (tdsqlite3_rtree_dbl)c.i : (tdsqlite3_rtree_dbl)c.f; \
}
#endif
@@ -165400,14 +190845,14 @@ static int rtreeCallbackConstraint(
int eInt, /* True if RTree holding integer coordinates */
u8 *pCellData, /* Raw cell content */
RtreeSearchPoint *pSearch, /* Container of this cell */
- sqlite3_rtree_dbl *prScore, /* OUT: score for the cell */
+ tdsqlite3_rtree_dbl *prScore, /* OUT: score for the cell */
int *peWithin /* OUT: visibility of the cell */
){
- int i; /* Loop counter */
- sqlite3_rtree_query_info *pInfo = pConstraint->pInfo; /* Callback info */
+ tdsqlite3_rtree_query_info *pInfo = pConstraint->pInfo; /* Callback info */
int nCoord = pInfo->nCoord; /* No. of coordinates */
int rc; /* Callback return code */
- sqlite3_rtree_dbl aCoord[RTREE_MAX_DIMENSIONS*2]; /* Decoded coordinates */
+ RtreeCoord c; /* Translator union */
+ tdsqlite3_rtree_dbl aCoord[RTREE_MAX_DIMENSIONS*2]; /* Decoded coordinates */
assert( pConstraint->op==RTREE_MATCH || pConstraint->op==RTREE_QUERY );
assert( nCoord==2 || nCoord==4 || nCoord==6 || nCoord==8 || nCoord==10 );
@@ -165416,13 +190861,41 @@ static int rtreeCallbackConstraint(
pInfo->iRowid = readInt64(pCellData);
}
pCellData += 8;
- for(i=0; i<nCoord; i++, pCellData += 4){
- RTREE_DECODE_COORD(eInt, pCellData, aCoord[i]);
+#ifndef SQLITE_RTREE_INT_ONLY
+ if( eInt==0 ){
+ switch( nCoord ){
+ case 10: readCoord(pCellData+36, &c); aCoord[9] = c.f;
+ readCoord(pCellData+32, &c); aCoord[8] = c.f;
+ case 8: readCoord(pCellData+28, &c); aCoord[7] = c.f;
+ readCoord(pCellData+24, &c); aCoord[6] = c.f;
+ case 6: readCoord(pCellData+20, &c); aCoord[5] = c.f;
+ readCoord(pCellData+16, &c); aCoord[4] = c.f;
+ case 4: readCoord(pCellData+12, &c); aCoord[3] = c.f;
+ readCoord(pCellData+8, &c); aCoord[2] = c.f;
+ default: readCoord(pCellData+4, &c); aCoord[1] = c.f;
+ readCoord(pCellData, &c); aCoord[0] = c.f;
+ }
+ }else
+#endif
+ {
+ switch( nCoord ){
+ case 10: readCoord(pCellData+36, &c); aCoord[9] = c.i;
+ readCoord(pCellData+32, &c); aCoord[8] = c.i;
+ case 8: readCoord(pCellData+28, &c); aCoord[7] = c.i;
+ readCoord(pCellData+24, &c); aCoord[6] = c.i;
+ case 6: readCoord(pCellData+20, &c); aCoord[5] = c.i;
+ readCoord(pCellData+16, &c); aCoord[4] = c.i;
+ case 4: readCoord(pCellData+12, &c); aCoord[3] = c.i;
+ readCoord(pCellData+8, &c); aCoord[2] = c.i;
+ default: readCoord(pCellData+4, &c); aCoord[1] = c.i;
+ readCoord(pCellData, &c); aCoord[0] = c.i;
+ }
}
if( pConstraint->op==RTREE_MATCH ){
- rc = pConstraint->u.xGeom((sqlite3_rtree_geometry*)pInfo,
- nCoord, aCoord, &i);
- if( i==0 ) *peWithin = NOT_WITHIN;
+ int eWithin = 0;
+ rc = pConstraint->u.xGeom((tdsqlite3_rtree_geometry*)pInfo,
+ nCoord, aCoord, &eWithin);
+ if( eWithin==0 ) *peWithin = NOT_WITHIN;
*prScore = RTREE_ZERO;
}else{
pInfo->aCoord = aCoord;
@@ -165449,7 +190922,7 @@ static void rtreeNonleafConstraint(
u8 *pCellData, /* Raw cell content as appears on disk */
int *peWithin /* Adjust downward, as appropriate */
){
- sqlite3_rtree_dbl val; /* Coordinate value convert to a double */
+ tdsqlite3_rtree_dbl val; /* Coordinate value convert to a double */
/* p->iCoord might point to either a lower or upper bound coordinate
** in a coordinate pair. But make pCellData point to the lower bound.
@@ -165457,8 +190930,12 @@ static void rtreeNonleafConstraint(
pCellData += 8 + 4*(p->iCoord&0xfe);
assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE
- || p->op==RTREE_GT || p->op==RTREE_EQ );
+ || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
+ || p->op==RTREE_FALSE );
+ assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */
switch( p->op ){
+ case RTREE_TRUE: return; /* Always satisfied */
+ case RTREE_FALSE: break; /* Never satisfied */
case RTREE_LE:
case RTREE_LT:
case RTREE_EQ:
@@ -165496,15 +190973,19 @@ static void rtreeLeafConstraint(
RtreeDValue xN; /* Coordinate value converted to a double */
assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE
- || p->op==RTREE_GT || p->op==RTREE_EQ );
+ || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
+ || p->op==RTREE_FALSE );
pCellData += 8 + p->iCoord*4;
+ assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */
RTREE_DECODE_COORD(eInt, pCellData, xN);
switch( p->op ){
- case RTREE_LE: if( xN <= p->u.rValue ) return; break;
- case RTREE_LT: if( xN < p->u.rValue ) return; break;
- case RTREE_GE: if( xN >= p->u.rValue ) return; break;
- case RTREE_GT: if( xN > p->u.rValue ) return; break;
- default: if( xN == p->u.rValue ) return; break;
+ case RTREE_TRUE: return; /* Always satisfied */
+ case RTREE_FALSE: break; /* Never satisfied */
+ case RTREE_LE: if( xN <= p->u.rValue ) return; break;
+ case RTREE_LT: if( xN < p->u.rValue ) return; break;
+ case RTREE_GE: if( xN >= p->u.rValue ) return; break;
+ case RTREE_GT: if( xN > p->u.rValue ) return; break;
+ default: if( xN == p->u.rValue ) return; break;
}
*peWithin = NOT_WITHIN;
}
@@ -165528,6 +191009,7 @@ static int nodeRowidIndex(
return SQLITE_OK;
}
}
+ RTREE_IS_CORRUPT(pRtree);
return SQLITE_CORRUPT_VTAB;
}
@@ -165566,7 +191048,7 @@ static int rtreeSearchPointCompare(
}
/*
-** Interchange to search points in a cursor.
+** Interchange two search points in a cursor.
*/
static void rtreeSearchPointSwap(RtreeCursor *p, int i, int j){
RtreeSearchPoint t = p->aPoint[i];
@@ -165597,7 +191079,7 @@ static RtreeSearchPoint *rtreeSearchPointFirst(RtreeCursor *pCur){
** Get the RtreeNode for the search point with the lowest score.
*/
static RtreeNode *rtreeNodeOfFirstSearchPoint(RtreeCursor *pCur, int *pRC){
- sqlite3_int64 id;
+ tdsqlite3_int64 id;
int ii = 1 - pCur->bPoint;
assert( ii==0 || ii==1 );
assert( pCur->bPoint || pCur->nPoint );
@@ -165621,7 +191103,7 @@ static RtreeSearchPoint *rtreeEnqueue(
RtreeSearchPoint *pNew;
if( pCur->nPoint>=pCur->nPointAlloc ){
int nNew = pCur->nPointAlloc*2 + 8;
- pNew = sqlite3_realloc(pCur->aPoint, nNew*sizeof(pCur->aPoint[0]));
+ pNew = tdsqlite3_realloc64(pCur->aPoint, nNew*sizeof(pCur->aPoint[0]));
if( pNew==0 ) return 0;
pCur->aPoint = pNew;
pCur->nPointAlloc = nNew;
@@ -165667,7 +191149,7 @@ static RtreeSearchPoint *rtreeSearchPointNew(
if( ii<RTREE_CACHE_SZ ){
assert( pCur->aNode[ii]==0 );
pCur->aNode[ii] = pCur->aNode[0];
- }else{
+ }else{
nodeRelease(RTREE_OF_CURSOR(pCur), pCur->aNode[0]);
}
pCur->aNode[0] = 0;
@@ -165776,13 +191258,14 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){
eInt = pRtree->eCoordType==RTREE_COORD_INT32;
while( (p = rtreeSearchPointFirst(pCur))!=0 && p->iLevel>0 ){
+ u8 *pCellData;
pNode = rtreeNodeOfFirstSearchPoint(pCur, &rc);
if( rc ) return rc;
nCell = NCELL(pNode);
assert( nCell<200 );
+ pCellData = pNode->zData + (4+pRtree->nBytesPerCell*p->iCell);
while( p->iCell<nCell ){
- sqlite3_rtree_dbl rScore = (sqlite3_rtree_dbl)-1;
- u8 *pCellData = pNode->zData + (4+pRtree->nBytesPerCell*p->iCell);
+ tdsqlite3_rtree_dbl rScore = (tdsqlite3_rtree_dbl)-1;
eWithin = FULLY_WITHIN;
for(ii=0; ii<nConstraint; ii++){
RtreeConstraint *pConstraint = pCur->aConstraint + ii;
@@ -165795,13 +191278,23 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){
}else{
rtreeNonleafConstraint(pConstraint, eInt, pCellData, &eWithin);
}
- if( eWithin==NOT_WITHIN ) break;
+ if( eWithin==NOT_WITHIN ){
+ p->iCell++;
+ pCellData += pRtree->nBytesPerCell;
+ break;
+ }
}
- p->iCell++;
if( eWithin==NOT_WITHIN ) continue;
+ p->iCell++;
x.iLevel = p->iLevel - 1;
if( x.iLevel ){
x.id = readInt64(pCellData);
+ for(ii=0; ii<pCur->nPoint; ii++){
+ if( pCur->aPoint[ii].id==x.id ){
+ RTREE_IS_CORRUPT(pRtree);
+ return SQLITE_CORRUPT_VTAB;
+ }
+ }
x.iCell = 0;
}else{
x.id = p->id;
@@ -165814,7 +191307,7 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){
if( rScore<RTREE_ZERO ) rScore = RTREE_ZERO;
p = rtreeSearchPointNew(pCur, rScore, x.iLevel);
if( p==0 ) return SQLITE_NOMEM;
- p->eWithin = eWithin;
+ p->eWithin = (u8)eWithin;
p->id = x.id;
p->iCell = x.iCell;
RTREE_QUEUE_TRACE(pCur, "PUSH-S:");
@@ -165832,12 +191325,16 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){
/*
** Rtree virtual table module xNext method.
*/
-static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){
+static int rtreeNext(tdsqlite3_vtab_cursor *pVtabCursor){
RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
int rc = SQLITE_OK;
/* Move to the next entry that matches the configured constraints. */
RTREE_QUEUE_TRACE(pCsr, "POP-Nx:");
+ if( pCsr->bAuxValid ){
+ pCsr->bAuxValid = 0;
+ tdsqlite3_reset(pCsr->pReadAux);
+ }
rtreeSearchPointPop(pCsr);
rc = rtreeStepToLeaf(pCsr);
return rc;
@@ -165846,7 +191343,7 @@ static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){
/*
** Rtree virtual table module xRowid method.
*/
-static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
+static int rtreeRowid(tdsqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr);
int rc = SQLITE_OK;
@@ -165860,7 +191357,7 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
/*
** Rtree virtual table module xColumn method.
*/
-static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+static int rtreeColumn(tdsqlite3_vtab_cursor *cur, tdsqlite3_context *ctx, int i){
Rtree *pRtree = (Rtree *)cur->pVtab;
RtreeCursor *pCsr = (RtreeCursor *)cur;
RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr);
@@ -165871,20 +191368,39 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
if( rc ) return rc;
if( p==0 ) return SQLITE_OK;
if( i==0 ){
- sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
- }else{
- if( rc ) return rc;
+ tdsqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
+ }else if( i<=pRtree->nDim2 ){
nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c);
#ifndef SQLITE_RTREE_INT_ONLY
if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
- sqlite3_result_double(ctx, c.f);
+ tdsqlite3_result_double(ctx, c.f);
}else
#endif
{
assert( pRtree->eCoordType==RTREE_COORD_INT32 );
- sqlite3_result_int(ctx, c.i);
+ tdsqlite3_result_int(ctx, c.i);
}
- }
+ }else{
+ if( !pCsr->bAuxValid ){
+ if( pCsr->pReadAux==0 ){
+ rc = tdsqlite3_prepare_v3(pRtree->db, pRtree->zReadAuxSql, -1, 0,
+ &pCsr->pReadAux, 0);
+ if( rc ) return rc;
+ }
+ tdsqlite3_bind_int64(pCsr->pReadAux, 1,
+ nodeGetRowid(pRtree, pNode, p->iCell));
+ rc = tdsqlite3_step(pCsr->pReadAux);
+ if( rc==SQLITE_ROW ){
+ pCsr->bAuxValid = 1;
+ }else{
+ tdsqlite3_reset(pCsr->pReadAux);
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ return rc;
+ }
+ }
+ tdsqlite3_result_value(ctx,
+ tdsqlite3_column_value(pCsr->pReadAux, i - pRtree->nDim2 + 1));
+ }
return SQLITE_OK;
}
@@ -165899,18 +191415,18 @@ static int findLeafNode(
Rtree *pRtree, /* RTree to search */
i64 iRowid, /* The rowid searching for */
RtreeNode **ppLeaf, /* Write the node here */
- sqlite3_int64 *piNode /* Write the node-id here */
+ tdsqlite3_int64 *piNode /* Write the node-id here */
){
int rc;
*ppLeaf = 0;
- sqlite3_bind_int64(pRtree->pReadRowid, 1, iRowid);
- if( sqlite3_step(pRtree->pReadRowid)==SQLITE_ROW ){
- i64 iNode = sqlite3_column_int64(pRtree->pReadRowid, 0);
+ tdsqlite3_bind_int64(pRtree->pReadRowid, 1, iRowid);
+ if( tdsqlite3_step(pRtree->pReadRowid)==SQLITE_ROW ){
+ i64 iNode = tdsqlite3_column_int64(pRtree->pReadRowid, 0);
if( piNode ) *piNode = iNode;
rc = nodeAcquire(pRtree, iNode, 0, ppLeaf);
- sqlite3_reset(pRtree->pReadRowid);
+ tdsqlite3_reset(pRtree->pReadRowid);
}else{
- rc = sqlite3_reset(pRtree->pReadRowid);
+ rc = tdsqlite3_reset(pRtree->pReadRowid);
}
return rc;
}
@@ -165921,34 +191437,18 @@ static int findLeafNode(
** first argument to this function is the right-hand operand to the MATCH
** operator.
*/
-static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){
- RtreeMatchArg *pBlob; /* BLOB returned by geometry function */
- sqlite3_rtree_query_info *pInfo; /* Callback information */
- int nBlob; /* Size of the geometry function blob */
- int nExpected; /* Expected size of the BLOB */
-
- /* Check that value is actually a blob. */
- if( sqlite3_value_type(pValue)!=SQLITE_BLOB ) return SQLITE_ERROR;
-
- /* Check that the blob is roughly the right size. */
- nBlob = sqlite3_value_bytes(pValue);
- if( nBlob<(int)sizeof(RtreeMatchArg) ){
- return SQLITE_ERROR;
- }
+static int deserializeGeometry(tdsqlite3_value *pValue, RtreeConstraint *pCons){
+ RtreeMatchArg *pBlob, *pSrc; /* BLOB returned by geometry function */
+ tdsqlite3_rtree_query_info *pInfo; /* Callback information */
- pInfo = (sqlite3_rtree_query_info*)sqlite3_malloc( sizeof(*pInfo)+nBlob );
+ pSrc = tdsqlite3_value_pointer(pValue, "RtreeMatchArg");
+ if( pSrc==0 ) return SQLITE_ERROR;
+ pInfo = (tdsqlite3_rtree_query_info*)
+ tdsqlite3_malloc64( sizeof(*pInfo)+pSrc->iSize );
if( !pInfo ) return SQLITE_NOMEM;
memset(pInfo, 0, sizeof(*pInfo));
pBlob = (RtreeMatchArg*)&pInfo[1];
-
- memcpy(pBlob, sqlite3_value_blob(pValue), nBlob);
- nExpected = (int)(sizeof(RtreeMatchArg) +
- pBlob->nParam*sizeof(sqlite3_value*) +
- (pBlob->nParam-1)*sizeof(RtreeDValue));
- if( pBlob->magic!=RTREE_GEOMETRY_MAGIC || nBlob!=nExpected ){
- sqlite3_free(pInfo);
- return SQLITE_ERROR;
- }
+ memcpy(pBlob, pSrc, pSrc->iSize);
pInfo->pContext = pBlob->cb.pContext;
pInfo->nParam = pBlob->nParam;
pInfo->aParam = pBlob->aParam;
@@ -165968,9 +191468,9 @@ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){
** Rtree virtual table module xFilter method.
*/
static int rtreeFilter(
- sqlite3_vtab_cursor *pVtabCursor,
+ tdsqlite3_vtab_cursor *pVtabCursor,
int idxNum, const char *idxStr,
- int argc, sqlite3_value **argv
+ int argc, tdsqlite3_value **argv
){
Rtree *pRtree = (Rtree *)pVtabCursor->pVtab;
RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
@@ -165982,19 +191482,24 @@ static int rtreeFilter(
rtreeReference(pRtree);
/* Reset the cursor to the same state as rtreeOpen() leaves it in. */
- freeCursorConstraints(pCsr);
- sqlite3_free(pCsr->aPoint);
- memset(pCsr, 0, sizeof(RtreeCursor));
- pCsr->base.pVtab = (sqlite3_vtab*)pRtree;
+ resetCursor(pCsr);
pCsr->iStrategy = idxNum;
if( idxNum==1 ){
/* Special case - lookup by rowid. */
RtreeNode *pLeaf; /* Leaf on which the required cell resides */
RtreeSearchPoint *p; /* Search point for the leaf */
- i64 iRowid = sqlite3_value_int64(argv[0]);
+ i64 iRowid = tdsqlite3_value_int64(argv[0]);
i64 iNode = 0;
- rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode);
+ int eType = tdsqlite3_value_numeric_type(argv[0]);
+ if( eType==SQLITE_INTEGER
+ || (eType==SQLITE_FLOAT && tdsqlite3_value_double(argv[0])==iRowid)
+ ){
+ rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode);
+ }else{
+ rc = SQLITE_OK;
+ pLeaf = 0;
+ }
if( rc==SQLITE_OK && pLeaf!=0 ){
p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0);
assert( p!=0 ); /* Always returns pCsr->sPoint */
@@ -166002,7 +191507,7 @@ static int rtreeFilter(
p->id = iNode;
p->eWithin = PARTLY_WITHIN;
rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &iCell);
- p->iCell = iCell;
+ p->iCell = (u8)iCell;
RTREE_QUEUE_TRACE(pCsr, "PUSH-F1:");
}else{
pCsr->atEOF = 1;
@@ -166013,7 +191518,7 @@ static int rtreeFilter(
*/
rc = nodeAcquire(pRtree, 1, 0, &pRoot);
if( rc==SQLITE_OK && argc>0 ){
- pCsr->aConstraint = sqlite3_malloc(sizeof(RtreeConstraint)*argc);
+ pCsr->aConstraint = tdsqlite3_malloc64(sizeof(RtreeConstraint)*argc);
pCsr->nConstraint = argc;
if( !pCsr->aConstraint ){
rc = SQLITE_NOMEM;
@@ -166024,33 +191529,43 @@ static int rtreeFilter(
|| (idxStr && (int)strlen(idxStr)==argc*2) );
for(ii=0; ii<argc; ii++){
RtreeConstraint *p = &pCsr->aConstraint[ii];
+ int eType = tdsqlite3_value_numeric_type(argv[ii]);
p->op = idxStr[ii*2];
p->iCoord = idxStr[ii*2+1]-'0';
if( p->op>=RTREE_MATCH ){
/* A MATCH operator. The right-hand-side must be a blob that
** can be cast into an RtreeMatchArg object. One created using
- ** an sqlite3_rtree_geometry_callback() SQL user function.
+ ** an tdsqlite3_rtree_geometry_callback() SQL user function.
*/
rc = deserializeGeometry(argv[ii], p);
if( rc!=SQLITE_OK ){
break;
}
- p->pInfo->nCoord = pRtree->nDim*2;
+ p->pInfo->nCoord = pRtree->nDim2;
p->pInfo->anQueue = pCsr->anQueue;
p->pInfo->mxLevel = pRtree->iDepth + 1;
- }else{
+ }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
#ifdef SQLITE_RTREE_INT_ONLY
- p->u.rValue = sqlite3_value_int64(argv[ii]);
+ p->u.rValue = tdsqlite3_value_int64(argv[ii]);
#else
- p->u.rValue = sqlite3_value_double(argv[ii]);
+ p->u.rValue = tdsqlite3_value_double(argv[ii]);
#endif
+ }else{
+ p->u.rValue = RTREE_ZERO;
+ if( eType==SQLITE_NULL ){
+ p->op = RTREE_FALSE;
+ }else if( p->op==RTREE_LT || p->op==RTREE_LE ){
+ p->op = RTREE_TRUE;
+ }else{
+ p->op = RTREE_FALSE;
+ }
}
}
}
}
if( rc==SQLITE_OK ){
RtreeSearchPoint *pNew;
- pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, pRtree->iDepth+1);
+ pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1));
if( pNew==0 ) return SQLITE_NOMEM;
pNew->id = 1;
pNew->iCell = 0;
@@ -166069,19 +191584,6 @@ static int rtreeFilter(
}
/*
-** Set the pIdxInfo->estimatedRows variable to nRow. Unless this
-** extension is currently being used by a version of SQLite too old to
-** support estimatedRows. In that case this function is a no-op.
-*/
-static void setEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){
-#if SQLITE_VERSION_NUMBER>=3008002
- if( sqlite3_libversion_number()>=3008002 ){
- pIdxInfo->estimatedRows = nRow;
- }
-#endif
-}
-
-/*
** Rtree virtual table module xBestIndex method. There are three
** table scan strategies to choose from (in order from most to
** least desirable):
@@ -166095,7 +191597,7 @@ static void setEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){
** If strategy 1 is used, then idxStr is not meaningful. If strategy
** 2 is used, idxStr is formatted to contain 2 bytes for each
** constraint used. The first two bytes of idxStr correspond to
-** the constraint in sqlite3_index_info.aConstraintUsage[] with
+** the constraint in tdsqlite3_index_info.aConstraintUsage[] with
** (argvIndex==1) etc.
**
** The first of each pair of bytes in idxStr identifies the constraint
@@ -166115,7 +191617,7 @@ static void setEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){
** to which the constraint applies. The leftmost coordinate column
** is 'a', the second from the left 'b' etc.
*/
-static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+static int rtreeBestIndex(tdsqlite3_vtab *tab, tdsqlite3_index_info *pIdxInfo){
Rtree *pRtree = (Rtree*)tab;
int rc = SQLITE_OK;
int ii;
@@ -166138,7 +191640,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
assert( pIdxInfo->idxStr==0 );
for(ii=0; ii<pIdxInfo->nConstraint && iIdx<(int)(sizeof(zIdxStr)-1); ii++){
- struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii];
+ struct tdsqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii];
if( bMatch==0 && p->usable
&& p->iColumn==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ
@@ -166160,39 +191662,43 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
** a single row.
*/
pIdxInfo->estimatedCost = 30.0;
- setEstimatedRows(pIdxInfo, 1);
+ pIdxInfo->estimatedRows = 1;
+ pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE;
return SQLITE_OK;
}
- if( p->usable && (p->iColumn>0 || p->op==SQLITE_INDEX_CONSTRAINT_MATCH) ){
+ if( p->usable
+ && ((p->iColumn>0 && p->iColumn<=pRtree->nDim2)
+ || p->op==SQLITE_INDEX_CONSTRAINT_MATCH)
+ ){
u8 op;
switch( p->op ){
- case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break;
- case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break;
- case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break;
- case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break;
- case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break;
- default:
- assert( p->op==SQLITE_INDEX_CONSTRAINT_MATCH );
- op = RTREE_MATCH;
- break;
+ case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break;
+ case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break;
+ case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break;
+ case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break;
+ case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break;
+ case SQLITE_INDEX_CONSTRAINT_MATCH: op = RTREE_MATCH; break;
+ default: op = 0; break;
+ }
+ if( op ){
+ zIdxStr[iIdx++] = op;
+ zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0');
+ pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2);
+ pIdxInfo->aConstraintUsage[ii].omit = 1;
}
- zIdxStr[iIdx++] = op;
- zIdxStr[iIdx++] = p->iColumn - 1 + '0';
- pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2);
- pIdxInfo->aConstraintUsage[ii].omit = 1;
}
}
pIdxInfo->idxNum = 2;
pIdxInfo->needToFreeIdxStr = 1;
- if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){
+ if( iIdx>0 && 0==(pIdxInfo->idxStr = tdsqlite3_mprintf("%s", zIdxStr)) ){
return SQLITE_NOMEM;
}
nRow = pRtree->nRowEst >> (iIdx/2);
pIdxInfo->estimatedCost = (double)6.0 * (double)nRow;
- setEstimatedRows(pIdxInfo, nRow);
+ pIdxInfo->estimatedRows = nRow;
return rc;
}
@@ -166202,9 +191708,26 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
*/
static RtreeDValue cellArea(Rtree *pRtree, RtreeCell *p){
RtreeDValue area = (RtreeDValue)1;
- int ii;
- for(ii=0; ii<(pRtree->nDim*2); ii+=2){
- area = (area * (DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii])));
+ assert( pRtree->nDim>=1 && pRtree->nDim<=5 );
+#ifndef SQLITE_RTREE_INT_ONLY
+ if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
+ switch( pRtree->nDim ){
+ case 5: area = p->aCoord[9].f - p->aCoord[8].f;
+ case 4: area *= p->aCoord[7].f - p->aCoord[6].f;
+ case 3: area *= p->aCoord[5].f - p->aCoord[4].f;
+ case 2: area *= p->aCoord[3].f - p->aCoord[2].f;
+ default: area *= p->aCoord[1].f - p->aCoord[0].f;
+ }
+ }else
+#endif
+ {
+ switch( pRtree->nDim ){
+ case 5: area = (i64)p->aCoord[9].i - (i64)p->aCoord[8].i;
+ case 4: area *= (i64)p->aCoord[7].i - (i64)p->aCoord[6].i;
+ case 3: area *= (i64)p->aCoord[5].i - (i64)p->aCoord[4].i;
+ case 2: area *= (i64)p->aCoord[3].i - (i64)p->aCoord[2].i;
+ default: area *= (i64)p->aCoord[1].i - (i64)p->aCoord[0].i;
+ }
}
return area;
}
@@ -166214,11 +191737,12 @@ static RtreeDValue cellArea(Rtree *pRtree, RtreeCell *p){
** of the objects size in each dimension.
*/
static RtreeDValue cellMargin(Rtree *pRtree, RtreeCell *p){
- RtreeDValue margin = (RtreeDValue)0;
- int ii;
- for(ii=0; ii<(pRtree->nDim*2); ii+=2){
+ RtreeDValue margin = 0;
+ int ii = pRtree->nDim2 - 2;
+ do{
margin += (DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii]));
- }
+ ii -= 2;
+ }while( ii>=0 );
return margin;
}
@@ -166226,17 +191750,19 @@ static RtreeDValue cellMargin(Rtree *pRtree, RtreeCell *p){
** Store the union of cells p1 and p2 in p1.
*/
static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
- int ii;
+ int ii = 0;
if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
- for(ii=0; ii<(pRtree->nDim*2); ii+=2){
+ do{
p1->aCoord[ii].f = MIN(p1->aCoord[ii].f, p2->aCoord[ii].f);
p1->aCoord[ii+1].f = MAX(p1->aCoord[ii+1].f, p2->aCoord[ii+1].f);
- }
+ ii += 2;
+ }while( ii<pRtree->nDim2 );
}else{
- for(ii=0; ii<(pRtree->nDim*2); ii+=2){
+ do{
p1->aCoord[ii].i = MIN(p1->aCoord[ii].i, p2->aCoord[ii].i);
p1->aCoord[ii+1].i = MAX(p1->aCoord[ii+1].i, p2->aCoord[ii+1].i);
- }
+ ii += 2;
+ }while( ii<pRtree->nDim2 );
}
}
@@ -166247,7 +191773,7 @@ static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
int ii;
int isInt = (pRtree->eCoordType==RTREE_COORD_INT32);
- for(ii=0; ii<(pRtree->nDim*2); ii+=2){
+ for(ii=0; ii<pRtree->nDim2; ii+=2){
RtreeCoord *a1 = &p1->aCoord[ii];
RtreeCoord *a2 = &p2->aCoord[ii];
if( (!isInt && (a2[0].f<a1[0].f || a2[1].f>a1[1].f))
@@ -166282,7 +191808,7 @@ static RtreeDValue cellOverlap(
for(ii=0; ii<nCell; ii++){
int jj;
RtreeDValue o = (RtreeDValue)1;
- for(jj=0; jj<(pRtree->nDim*2); jj+=2){
+ for(jj=0; jj<pRtree->nDim2; jj+=2){
RtreeDValue x1, x2;
x1 = MAX(DCOORD(p->aCoord[jj]), DCOORD(aCell[ii].aCoord[jj]));
x2 = MIN(DCOORD(p->aCoord[jj+1]), DCOORD(aCell[ii].aCoord[jj+1]));
@@ -166311,12 +191837,12 @@ static int ChooseLeaf(
){
int rc;
int ii;
- RtreeNode *pNode;
+ RtreeNode *pNode = 0;
rc = nodeAcquire(pRtree, 1, 0, &pNode);
for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){
int iCell;
- sqlite3_int64 iBest = 0;
+ tdsqlite3_int64 iBest = 0;
RtreeDValue fMinGrowth = RTREE_ZERO;
RtreeDValue fMinArea = RTREE_ZERO;
@@ -166348,7 +191874,7 @@ static int ChooseLeaf(
}
}
- sqlite3_free(aCell);
+ tdsqlite3_free(aCell);
rc = nodeAcquire(pRtree, iBest, pNode, &pChild);
nodeRelease(pRtree, pNode);
pNode = pChild;
@@ -166369,12 +191895,14 @@ static int AdjustTree(
RtreeCell *pCell /* This cell was just inserted */
){
RtreeNode *p = pNode;
+ int cnt = 0;
while( p->pParent ){
RtreeNode *pParent = p->pParent;
RtreeCell cell;
int iCell;
- if( nodeParentIndex(pRtree, p, &iCell) ){
+ if( (++cnt)>1000 || nodeParentIndex(pRtree, p, &iCell) ){
+ RTREE_IS_CORRUPT(pRtree);
return SQLITE_CORRUPT_VTAB;
}
@@ -166392,21 +191920,21 @@ static int AdjustTree(
/*
** Write mapping (iRowid->iNode) to the <rtree>_rowid table.
*/
-static int rowidWrite(Rtree *pRtree, sqlite3_int64 iRowid, sqlite3_int64 iNode){
- sqlite3_bind_int64(pRtree->pWriteRowid, 1, iRowid);
- sqlite3_bind_int64(pRtree->pWriteRowid, 2, iNode);
- sqlite3_step(pRtree->pWriteRowid);
- return sqlite3_reset(pRtree->pWriteRowid);
+static int rowidWrite(Rtree *pRtree, tdsqlite3_int64 iRowid, tdsqlite3_int64 iNode){
+ tdsqlite3_bind_int64(pRtree->pWriteRowid, 1, iRowid);
+ tdsqlite3_bind_int64(pRtree->pWriteRowid, 2, iNode);
+ tdsqlite3_step(pRtree->pWriteRowid);
+ return tdsqlite3_reset(pRtree->pWriteRowid);
}
/*
** Write mapping (iNode->iPar) to the <rtree>_parent table.
*/
-static int parentWrite(Rtree *pRtree, sqlite3_int64 iNode, sqlite3_int64 iPar){
- sqlite3_bind_int64(pRtree->pWriteParent, 1, iNode);
- sqlite3_bind_int64(pRtree->pWriteParent, 2, iPar);
- sqlite3_step(pRtree->pWriteParent);
- return sqlite3_reset(pRtree->pWriteParent);
+static int parentWrite(Rtree *pRtree, tdsqlite3_int64 iNode, tdsqlite3_int64 iPar){
+ tdsqlite3_bind_int64(pRtree->pWriteParent, 1, iNode);
+ tdsqlite3_bind_int64(pRtree->pWriteParent, 2, iPar);
+ tdsqlite3_step(pRtree->pWriteParent);
+ return tdsqlite3_reset(pRtree->pWriteParent);
}
static int rtreeInsertCell(Rtree *, RtreeNode *, RtreeCell *, int);
@@ -166571,9 +192099,9 @@ static int splitNodeStartree(
int iBestSplit = 0;
RtreeDValue fBestMargin = RTREE_ZERO;
- int nByte = (pRtree->nDim+1)*(sizeof(int*)+nCell*sizeof(int));
+ tdsqlite3_int64 nByte = (pRtree->nDim+1)*(sizeof(int*)+nCell*sizeof(int));
- aaSorted = (int **)sqlite3_malloc(nByte);
+ aaSorted = (int **)tdsqlite3_malloc64(nByte);
if( !aaSorted ){
return SQLITE_NOMEM;
}
@@ -166647,7 +192175,7 @@ static int splitNodeStartree(
cellUnion(pRtree, pBbox, pCell);
}
- sqlite3_free(aaSorted);
+ tdsqlite3_free(aaSorted);
return SQLITE_OK;
}
@@ -166658,7 +192186,7 @@ static int updateMapping(
RtreeNode *pNode,
int iHeight
){
- int (*xSetMapping)(Rtree *, sqlite3_int64, sqlite3_int64);
+ int (*xSetMapping)(Rtree *, tdsqlite3_int64, tdsqlite3_int64);
xSetMapping = ((iHeight==0)?rowidWrite:parentWrite);
if( iHeight>0 ){
RtreeNode *pChild = nodeHashLookup(pRtree, iRowid);
@@ -166694,7 +192222,7 @@ static int SplitNode(
/* Allocate an array and populate it with a copy of pCell and
** all cells from node pLeft. Then zero the original node.
*/
- aCell = sqlite3_malloc((sizeof(RtreeCell)+sizeof(int))*(nCell+1));
+ aCell = tdsqlite3_malloc64((sizeof(RtreeCell)+sizeof(int))*(nCell+1));
if( !aCell ){
rc = SQLITE_NOMEM;
goto splitnode_out;
@@ -166717,7 +192245,7 @@ static int SplitNode(
}else{
pLeft = pNode;
pRight = nodeNew(pRtree, pLeft->pParent);
- nodeReference(pLeft);
+ pLeft->nRef++;
}
if( !pLeft || !pRight ){
@@ -166803,7 +192331,7 @@ static int SplitNode(
splitnode_out:
nodeRelease(pRtree, pRight);
nodeRelease(pRtree, pLeft);
- sqlite3_free(aCell);
+ tdsqlite3_free(aCell);
return rc;
}
@@ -166822,9 +192350,9 @@ static int fixLeafParent(Rtree *pRtree, RtreeNode *pLeaf){
int rc = SQLITE_OK;
RtreeNode *pChild = pLeaf;
while( rc==SQLITE_OK && pChild->iNode!=1 && pChild->pParent==0 ){
- int rc2 = SQLITE_OK; /* sqlite3_reset() return code */
- sqlite3_bind_int64(pRtree->pReadParent, 1, pChild->iNode);
- rc = sqlite3_step(pRtree->pReadParent);
+ int rc2 = SQLITE_OK; /* tdsqlite3_reset() return code */
+ tdsqlite3_bind_int64(pRtree->pReadParent, 1, pChild->iNode);
+ rc = tdsqlite3_step(pRtree->pReadParent);
if( rc==SQLITE_ROW ){
RtreeNode *pTest; /* Used to test for reference loops */
i64 iNode; /* Node number of parent node */
@@ -166834,15 +192362,18 @@ static int fixLeafParent(Rtree *pRtree, RtreeNode *pLeaf){
** want to do this as it leads to a memory leak when trying to delete
** the referenced counted node structures.
*/
- iNode = sqlite3_column_int64(pRtree->pReadParent, 0);
+ iNode = tdsqlite3_column_int64(pRtree->pReadParent, 0);
for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent);
if( !pTest ){
rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent);
}
}
- rc = sqlite3_reset(pRtree->pReadParent);
+ rc = tdsqlite3_reset(pRtree->pReadParent);
if( rc==SQLITE_OK ) rc = rc2;
- if( rc==SQLITE_OK && !pChild->pParent ) rc = SQLITE_CORRUPT_VTAB;
+ if( rc==SQLITE_OK && !pChild->pParent ){
+ RTREE_IS_CORRUPT(pRtree);
+ rc = SQLITE_CORRUPT_VTAB;
+ }
pChild = pChild->pParent;
}
return rc;
@@ -166874,16 +192405,16 @@ static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){
}
/* Remove the xxx_node entry. */
- sqlite3_bind_int64(pRtree->pDeleteNode, 1, pNode->iNode);
- sqlite3_step(pRtree->pDeleteNode);
- if( SQLITE_OK!=(rc = sqlite3_reset(pRtree->pDeleteNode)) ){
+ tdsqlite3_bind_int64(pRtree->pDeleteNode, 1, pNode->iNode);
+ tdsqlite3_step(pRtree->pDeleteNode);
+ if( SQLITE_OK!=(rc = tdsqlite3_reset(pRtree->pDeleteNode)) ){
return rc;
}
/* Remove the xxx_parent entry. */
- sqlite3_bind_int64(pRtree->pDeleteParent, 1, pNode->iNode);
- sqlite3_step(pRtree->pDeleteParent);
- if( SQLITE_OK!=(rc = sqlite3_reset(pRtree->pDeleteParent)) ){
+ tdsqlite3_bind_int64(pRtree->pDeleteParent, 1, pNode->iNode);
+ tdsqlite3_step(pRtree->pDeleteParent);
+ if( SQLITE_OK!=(rc = tdsqlite3_reset(pRtree->pDeleteParent)) ){
return rc;
}
@@ -166982,7 +192513,7 @@ static int Reinsert(
/* Allocate the buffers used by this operation. The allocation is
** relinquished before this function returns.
*/
- aCell = (RtreeCell *)sqlite3_malloc(n * (
+ aCell = (RtreeCell *)tdsqlite3_malloc64(n * (
sizeof(RtreeCell) + /* aCell array */
sizeof(int) + /* aOrder array */
sizeof(int) + /* aSpare array */
@@ -167054,7 +192585,7 @@ static int Reinsert(
}
}
- sqlite3_free(aCell);
+ tdsqlite3_free(aCell);
return rc;
}
@@ -167126,24 +192657,24 @@ static int reinsertNodeContent(Rtree *pRtree, RtreeNode *pNode){
/*
** Select a currently unused rowid for a new r-tree record.
*/
-static int newRowid(Rtree *pRtree, i64 *piRowid){
+static int rtreeNewRowid(Rtree *pRtree, i64 *piRowid){
int rc;
- sqlite3_bind_null(pRtree->pWriteRowid, 1);
- sqlite3_bind_null(pRtree->pWriteRowid, 2);
- sqlite3_step(pRtree->pWriteRowid);
- rc = sqlite3_reset(pRtree->pWriteRowid);
- *piRowid = sqlite3_last_insert_rowid(pRtree->db);
+ tdsqlite3_bind_null(pRtree->pWriteRowid, 1);
+ tdsqlite3_bind_null(pRtree->pWriteRowid, 2);
+ tdsqlite3_step(pRtree->pWriteRowid);
+ rc = tdsqlite3_reset(pRtree->pWriteRowid);
+ *piRowid = tdsqlite3_last_insert_rowid(pRtree->db);
return rc;
}
/*
** Remove the entry with rowid=iDelete from the r-tree structure.
*/
-static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
+static int rtreeDeleteRowid(Rtree *pRtree, tdsqlite3_int64 iDelete){
int rc; /* Return code */
RtreeNode *pLeaf = 0; /* Leaf node containing record iDelete */
int iCell; /* Index of iDelete cell in pLeaf */
- RtreeNode *pRoot; /* Root node of rtree structure */
+ RtreeNode *pRoot = 0; /* Root node of rtree structure */
/* Obtain a reference to the root node to initialize Rtree.iDepth */
@@ -167156,8 +192687,12 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
rc = findLeafNode(pRtree, iDelete, &pLeaf, 0);
}
+#ifdef CORRUPT_DB
+ assert( pLeaf!=0 || rc!=SQLITE_OK || CORRUPT_DB );
+#endif
+
/* Delete the cell in question from the leaf node. */
- if( rc==SQLITE_OK ){
+ if( rc==SQLITE_OK && pLeaf ){
int rc2;
rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell);
if( rc==SQLITE_OK ){
@@ -167171,9 +192706,9 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
/* Delete the corresponding entry in the <rtree>_rowid table. */
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete);
- sqlite3_step(pRtree->pDeleteRowid);
- rc = sqlite3_reset(pRtree->pDeleteRowid);
+ tdsqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete);
+ tdsqlite3_step(pRtree->pDeleteRowid);
+ rc = tdsqlite3_reset(pRtree->pDeleteRowid);
}
/* Check if the root node now has exactly one child. If so, remove
@@ -167186,7 +192721,7 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
*/
if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
int rc2;
- RtreeNode *pChild;
+ RtreeNode *pChild = 0;
i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
rc = nodeAcquire(pRtree, iChild, pRoot, &pChild);
if( rc==SQLITE_OK ){
@@ -167207,7 +192742,8 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
rc = reinsertNodeContent(pRtree, pLeaf);
}
pRtree->pDeleted = pLeaf->pNext;
- sqlite3_free(pLeaf);
+ pRtree->nNodeRef--;
+ tdsqlite3_free(pLeaf);
}
/* Release the reference to the root node. */
@@ -167228,19 +192764,19 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
#if !defined(SQLITE_RTREE_INT_ONLY)
/*
-** Convert an sqlite3_value into an RtreeValue (presumably a float)
+** Convert an tdsqlite3_value into an RtreeValue (presumably a float)
** while taking care to round toward negative or positive, respectively.
*/
-static RtreeValue rtreeValueDown(sqlite3_value *v){
- double d = sqlite3_value_double(v);
+static RtreeValue rtreeValueDown(tdsqlite3_value *v){
+ double d = tdsqlite3_value_double(v);
float f = (float)d;
if( f>d ){
f = (float)(d*(d<0 ? RNDAWAY : RNDTOWARDS));
}
return f;
}
-static RtreeValue rtreeValueUp(sqlite3_value *v){
- double d = sqlite3_value_double(v);
+static RtreeValue rtreeValueUp(tdsqlite3_value *v){
+ double d = tdsqlite3_value_double(v);
float f = (float)d;
if( f<d ){
f = (float)(d*(d<0 ? RNDTOWARDS : RNDAWAY));
@@ -167263,35 +192799,35 @@ static RtreeValue rtreeValueUp(sqlite3_value *v){
** If an OOM occurs, SQLITE_NOMEM is returned instead of SQLITE_CONSTRAINT.
*/
static int rtreeConstraintError(Rtree *pRtree, int iCol){
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
char *zSql;
int rc;
assert( iCol==0 || iCol%2 );
- zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", pRtree->zDb, pRtree->zName);
+ zSql = tdsqlite3_mprintf("SELECT * FROM %Q.%Q", pRtree->zDb, pRtree->zName);
if( zSql ){
- rc = sqlite3_prepare_v2(pRtree->db, zSql, -1, &pStmt, 0);
+ rc = tdsqlite3_prepare_v2(pRtree->db, zSql, -1, &pStmt, 0);
}else{
rc = SQLITE_NOMEM;
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
if( rc==SQLITE_OK ){
if( iCol==0 ){
- const char *zCol = sqlite3_column_name(pStmt, 0);
- pRtree->base.zErrMsg = sqlite3_mprintf(
+ const char *zCol = tdsqlite3_column_name(pStmt, 0);
+ pRtree->base.zErrMsg = tdsqlite3_mprintf(
"UNIQUE constraint failed: %s.%s", pRtree->zName, zCol
);
}else{
- const char *zCol1 = sqlite3_column_name(pStmt, iCol);
- const char *zCol2 = sqlite3_column_name(pStmt, iCol+1);
- pRtree->base.zErrMsg = sqlite3_mprintf(
+ const char *zCol1 = tdsqlite3_column_name(pStmt, iCol);
+ const char *zCol2 = tdsqlite3_column_name(pStmt, iCol+1);
+ pRtree->base.zErrMsg = tdsqlite3_mprintf(
"rtree constraint failed: %s.(%s<=%s)", pRtree->zName, zCol1, zCol2
);
}
}
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
return (rc==SQLITE_OK ? SQLITE_CONSTRAINT : rc);
}
@@ -167301,9 +192837,9 @@ static int rtreeConstraintError(Rtree *pRtree, int iCol){
** The xUpdate method for rtree module virtual tables.
*/
static int rtreeUpdate(
- sqlite3_vtab *pVtab,
+ tdsqlite3_vtab *pVtab,
int nData,
- sqlite3_value **azData,
+ tdsqlite3_value **aData,
sqlite_int64 *pRowid
){
Rtree *pRtree = (Rtree *)pVtab;
@@ -167311,6 +192847,12 @@ static int rtreeUpdate(
RtreeCell cell; /* New cell to insert if nData>1 */
int bHaveRowid = 0; /* Set to 1 after new rowid is determined */
+ if( pRtree->nNodeRef ){
+ /* Unable to write to the btree while another cursor is reading from it,
+ ** since the write might do a rebalance which would disrupt the read
+ ** cursor. */
+ return SQLITE_LOCKED_VTAB;
+ }
rtreeReference(pRtree);
assert(nData>=1);
@@ -167329,8 +192871,10 @@ static int rtreeUpdate(
*/
if( nData>1 ){
int ii;
+ int nn = nData - 4;
- /* Populate the cell.aCoord[] array. The first coordinate is azData[3].
+ if( nn > pRtree->nDim2 ) nn = pRtree->nDim2;
+ /* Populate the cell.aCoord[] array. The first coordinate is aData[3].
**
** NB: nData can only be less than nDim*2+3 if the rtree is mis-declared
** with "column" that are interpreted as table constraints.
@@ -167338,13 +192882,12 @@ static int rtreeUpdate(
** This problem was discovered after years of use, so we silently ignore
** these kinds of misdeclared tables to avoid breaking any legacy.
*/
- assert( nData<=(pRtree->nDim*2 + 3) );
#ifndef SQLITE_RTREE_INT_ONLY
if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
- for(ii=0; ii<nData-4; ii+=2){
- cell.aCoord[ii].f = rtreeValueDown(azData[ii+3]);
- cell.aCoord[ii+1].f = rtreeValueUp(azData[ii+4]);
+ for(ii=0; ii<nn; ii+=2){
+ cell.aCoord[ii].f = rtreeValueDown(aData[ii+3]);
+ cell.aCoord[ii+1].f = rtreeValueUp(aData[ii+4]);
if( cell.aCoord[ii].f>cell.aCoord[ii+1].f ){
rc = rtreeConstraintError(pRtree, ii+1);
goto constraint;
@@ -167353,9 +192896,9 @@ static int rtreeUpdate(
}else
#endif
{
- for(ii=0; ii<nData-4; ii+=2){
- cell.aCoord[ii].i = sqlite3_value_int(azData[ii+3]);
- cell.aCoord[ii+1].i = sqlite3_value_int(azData[ii+4]);
+ for(ii=0; ii<nn; ii+=2){
+ cell.aCoord[ii].i = tdsqlite3_value_int(aData[ii+3]);
+ cell.aCoord[ii+1].i = tdsqlite3_value_int(aData[ii+4]);
if( cell.aCoord[ii].i>cell.aCoord[ii+1].i ){
rc = rtreeConstraintError(pRtree, ii+1);
goto constraint;
@@ -167365,17 +192908,17 @@ static int rtreeUpdate(
/* If a rowid value was supplied, check if it is already present in
** the table. If so, the constraint has failed. */
- if( sqlite3_value_type(azData[2])!=SQLITE_NULL ){
- cell.iRowid = sqlite3_value_int64(azData[2]);
- if( sqlite3_value_type(azData[0])==SQLITE_NULL
- || sqlite3_value_int64(azData[0])!=cell.iRowid
+ if( tdsqlite3_value_type(aData[2])!=SQLITE_NULL ){
+ cell.iRowid = tdsqlite3_value_int64(aData[2]);
+ if( tdsqlite3_value_type(aData[0])==SQLITE_NULL
+ || tdsqlite3_value_int64(aData[0])!=cell.iRowid
){
int steprc;
- sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
- steprc = sqlite3_step(pRtree->pReadRowid);
- rc = sqlite3_reset(pRtree->pReadRowid);
+ tdsqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
+ steprc = tdsqlite3_step(pRtree->pReadRowid);
+ rc = tdsqlite3_reset(pRtree->pReadRowid);
if( SQLITE_ROW==steprc ){
- if( sqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){
+ if( tdsqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){
rc = rtreeDeleteRowid(pRtree, cell.iRowid);
}else{
rc = rtreeConstraintError(pRtree, 0);
@@ -167387,16 +192930,16 @@ static int rtreeUpdate(
}
}
- /* If azData[0] is not an SQL NULL value, it is the rowid of a
+ /* If aData[0] is not an SQL NULL value, it is the rowid of a
** record to delete from the r-tree table. The following block does
** just that.
*/
- if( sqlite3_value_type(azData[0])!=SQLITE_NULL ){
- rc = rtreeDeleteRowid(pRtree, sqlite3_value_int64(azData[0]));
+ if( tdsqlite3_value_type(aData[0])!=SQLITE_NULL ){
+ rc = rtreeDeleteRowid(pRtree, tdsqlite3_value_int64(aData[0]));
}
- /* If the azData[] array contains more than one element, elements
- ** (azData[2]..azData[argc-1]) contain a new record to insert into
+ /* If the aData[] array contains more than one element, elements
+ ** (aData[2]..aData[argc-1]) contain a new record to insert into
** the r-tree structure.
*/
if( rc==SQLITE_OK && nData>1 ){
@@ -167405,7 +192948,7 @@ static int rtreeUpdate(
/* Figure out the rowid of the new row. */
if( bHaveRowid==0 ){
- rc = newRowid(pRtree, &cell.iRowid);
+ rc = rtreeNewRowid(pRtree, &cell.iRowid);
}
*pRowid = cell.iRowid;
@@ -167421,6 +192964,16 @@ static int rtreeUpdate(
rc = rc2;
}
}
+ if( rc==SQLITE_OK && pRtree->nAux ){
+ tdsqlite3_stmt *pUp = pRtree->pWriteAux;
+ int jj;
+ tdsqlite3_bind_int64(pUp, 1, *pRowid);
+ for(jj=0; jj<pRtree->nAux; jj++){
+ tdsqlite3_bind_value(pUp, jj+2, aData[pRtree->nDim2+3+jj]);
+ }
+ tdsqlite3_step(pUp);
+ rc = tdsqlite3_reset(pUp);
+ }
}
constraint:
@@ -167429,12 +192982,33 @@ constraint:
}
/*
+** Called when a transaction starts.
+*/
+static int rtreeBeginTransaction(tdsqlite3_vtab *pVtab){
+ Rtree *pRtree = (Rtree *)pVtab;
+ assert( pRtree->inWrTrans==0 );
+ pRtree->inWrTrans++;
+ return SQLITE_OK;
+}
+
+/*
+** Called when a transaction completes (either by COMMIT or ROLLBACK).
+** The tdsqlite3_blob object should be released at this point.
+*/
+static int rtreeEndTransaction(tdsqlite3_vtab *pVtab){
+ Rtree *pRtree = (Rtree *)pVtab;
+ pRtree->inWrTrans = 0;
+ nodeBlobReset(pRtree);
+ return SQLITE_OK;
+}
+
+/*
** The xRename method for rtree module virtual tables.
*/
-static int rtreeRename(sqlite3_vtab *pVtab, const char *zNewName){
+static int rtreeRename(tdsqlite3_vtab *pVtab, const char *zNewName){
Rtree *pRtree = (Rtree *)pVtab;
int rc = SQLITE_NOMEM;
- char *zSql = sqlite3_mprintf(
+ char *zSql = tdsqlite3_mprintf(
"ALTER TABLE %Q.'%q_node' RENAME TO \"%w_node\";"
"ALTER TABLE %Q.'%q_parent' RENAME TO \"%w_parent\";"
"ALTER TABLE %Q.'%q_rowid' RENAME TO \"%w_rowid\";"
@@ -167443,39 +193017,64 @@ static int rtreeRename(sqlite3_vtab *pVtab, const char *zNewName){
, pRtree->zDb, pRtree->zName, zNewName
);
if( zSql ){
- rc = sqlite3_exec(pRtree->db, zSql, 0, 0, 0);
- sqlite3_free(zSql);
+ nodeBlobReset(pRtree);
+ rc = tdsqlite3_exec(pRtree->db, zSql, 0, 0, 0);
+ tdsqlite3_free(zSql);
}
return rc;
}
/*
+** The xSavepoint method.
+**
+** This module does not need to do anything to support savepoints. However,
+** it uses this hook to close any open blob handle. This is done because a
+** DROP TABLE command - which fortunately always opens a savepoint - cannot
+** succeed if there are any open blob handles. i.e. if the blob handle were
+** not closed here, the following would fail:
+**
+** BEGIN;
+** INSERT INTO rtree...
+** DROP TABLE <tablename>; -- Would fail with SQLITE_LOCKED
+** COMMIT;
+*/
+static int rtreeSavepoint(tdsqlite3_vtab *pVtab, int iSavepoint){
+ Rtree *pRtree = (Rtree *)pVtab;
+ u8 iwt = pRtree->inWrTrans;
+ UNUSED_PARAMETER(iSavepoint);
+ pRtree->inWrTrans = 0;
+ nodeBlobReset(pRtree);
+ pRtree->inWrTrans = iwt;
+ return SQLITE_OK;
+}
+
+/*
** This function populates the pRtree->nRowEst variable with an estimate
** of the number of rows in the virtual table. If possible, this is based
** on sqlite_stat1 data. Otherwise, use RTREE_DEFAULT_ROWEST.
*/
-static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){
+static int rtreeQueryStat1(tdsqlite3 *db, Rtree *pRtree){
const char *zFmt = "SELECT stat FROM %Q.sqlite_stat1 WHERE tbl = '%q_rowid'";
char *zSql;
- sqlite3_stmt *p;
+ tdsqlite3_stmt *p;
int rc;
i64 nRow = 0;
- rc = sqlite3_table_column_metadata(
+ rc = tdsqlite3_table_column_metadata(
db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0
);
if( rc!=SQLITE_OK ){
pRtree->nRowEst = RTREE_DEFAULT_ROWEST;
return rc==SQLITE_ERROR ? SQLITE_OK : rc;
}
- zSql = sqlite3_mprintf(zFmt, pRtree->zDb, pRtree->zName);
+ zSql = tdsqlite3_mprintf(zFmt, pRtree->zDb, pRtree->zName);
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare_v2(db, zSql, -1, &p, 0);
+ rc = tdsqlite3_prepare_v2(db, zSql, -1, &p, 0);
if( rc==SQLITE_OK ){
- if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0);
- rc = sqlite3_finalize(p);
+ if( tdsqlite3_step(p)==SQLITE_ROW ) nRow = tdsqlite3_column_int64(p, 0);
+ rc = tdsqlite3_finalize(p);
}else if( rc!=SQLITE_NOMEM ){
rc = SQLITE_OK;
}
@@ -167487,14 +193086,30 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){
pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST);
}
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
}
return rc;
}
-static sqlite3_module rtreeModule = {
- 0, /* iVersion */
+
+/*
+** Return true if zName is the extension on one of the shadow tables used
+** by this module.
+*/
+static int rtreeShadowName(const char *zName){
+ static const char *azName[] = {
+ "node", "parent", "rowid"
+ };
+ unsigned int i;
+ for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
+ if( tdsqlite3_stricmp(zName, azName[i])==0 ) return 1;
+ }
+ return 0;
+}
+
+static tdsqlite3_module rtreeModule = {
+ 3, /* iVersion */
rtreeCreate, /* xCreate - create a table */
rtreeConnect, /* xConnect - connect to an existing table */
rtreeBestIndex, /* xBestIndex - Determine search strategy */
@@ -167508,86 +193123,136 @@ static sqlite3_module rtreeModule = {
rtreeColumn, /* xColumn - read data */
rtreeRowid, /* xRowid - read data */
rtreeUpdate, /* xUpdate - write data */
- 0, /* xBegin - begin transaction */
- 0, /* xSync - sync transaction */
- 0, /* xCommit - commit transaction */
- 0, /* xRollback - rollback transaction */
+ rtreeBeginTransaction, /* xBegin - begin transaction */
+ rtreeEndTransaction, /* xSync - sync transaction */
+ rtreeEndTransaction, /* xCommit - commit transaction */
+ rtreeEndTransaction, /* xRollback - rollback transaction */
0, /* xFindFunction - function overloading */
rtreeRename, /* xRename - rename the table */
- 0, /* xSavepoint */
+ rtreeSavepoint, /* xSavepoint */
0, /* xRelease */
- 0 /* xRollbackTo */
+ 0, /* xRollbackTo */
+ rtreeShadowName /* xShadowName */
};
static int rtreeSqlInit(
Rtree *pRtree,
- sqlite3 *db,
+ tdsqlite3 *db,
const char *zDb,
const char *zPrefix,
int isCreate
){
int rc = SQLITE_OK;
- #define N_STATEMENT 9
+ #define N_STATEMENT 8
static const char *azSql[N_STATEMENT] = {
- /* Read and write the xxx_node table */
- "SELECT data FROM '%q'.'%q_node' WHERE nodeno = :1",
- "INSERT OR REPLACE INTO '%q'.'%q_node' VALUES(:1, :2)",
- "DELETE FROM '%q'.'%q_node' WHERE nodeno = :1",
+ /* Write the xxx_node table */
+ "INSERT OR REPLACE INTO '%q'.'%q_node' VALUES(?1, ?2)",
+ "DELETE FROM '%q'.'%q_node' WHERE nodeno = ?1",
/* Read and write the xxx_rowid table */
- "SELECT nodeno FROM '%q'.'%q_rowid' WHERE rowid = :1",
- "INSERT OR REPLACE INTO '%q'.'%q_rowid' VALUES(:1, :2)",
- "DELETE FROM '%q'.'%q_rowid' WHERE rowid = :1",
+ "SELECT nodeno FROM '%q'.'%q_rowid' WHERE rowid = ?1",
+ "INSERT OR REPLACE INTO '%q'.'%q_rowid' VALUES(?1, ?2)",
+ "DELETE FROM '%q'.'%q_rowid' WHERE rowid = ?1",
/* Read and write the xxx_parent table */
- "SELECT parentnode FROM '%q'.'%q_parent' WHERE nodeno = :1",
- "INSERT OR REPLACE INTO '%q'.'%q_parent' VALUES(:1, :2)",
- "DELETE FROM '%q'.'%q_parent' WHERE nodeno = :1"
+ "SELECT parentnode FROM '%q'.'%q_parent' WHERE nodeno = ?1",
+ "INSERT OR REPLACE INTO '%q'.'%q_parent' VALUES(?1, ?2)",
+ "DELETE FROM '%q'.'%q_parent' WHERE nodeno = ?1"
};
- sqlite3_stmt **appStmt[N_STATEMENT];
+ tdsqlite3_stmt **appStmt[N_STATEMENT];
int i;
+ const int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB;
pRtree->db = db;
if( isCreate ){
- char *zCreate = sqlite3_mprintf(
-"CREATE TABLE \"%w\".\"%w_node\"(nodeno INTEGER PRIMARY KEY, data BLOB);"
-"CREATE TABLE \"%w\".\"%w_rowid\"(rowid INTEGER PRIMARY KEY, nodeno INTEGER);"
-"CREATE TABLE \"%w\".\"%w_parent\"(nodeno INTEGER PRIMARY KEY,"
- " parentnode INTEGER);"
-"INSERT INTO '%q'.'%q_node' VALUES(1, zeroblob(%d))",
- zDb, zPrefix, zDb, zPrefix, zDb, zPrefix, zDb, zPrefix, pRtree->iNodeSize
- );
+ char *zCreate;
+ tdsqlite3_str *p = tdsqlite3_str_new(db);
+ int ii;
+ tdsqlite3_str_appendf(p,
+ "CREATE TABLE \"%w\".\"%w_rowid\"(rowid INTEGER PRIMARY KEY,nodeno",
+ zDb, zPrefix);
+ for(ii=0; ii<pRtree->nAux; ii++){
+ tdsqlite3_str_appendf(p,",a%d",ii);
+ }
+ tdsqlite3_str_appendf(p,
+ ");CREATE TABLE \"%w\".\"%w_node\"(nodeno INTEGER PRIMARY KEY,data);",
+ zDb, zPrefix);
+ tdsqlite3_str_appendf(p,
+ "CREATE TABLE \"%w\".\"%w_parent\"(nodeno INTEGER PRIMARY KEY,parentnode);",
+ zDb, zPrefix);
+ tdsqlite3_str_appendf(p,
+ "INSERT INTO \"%w\".\"%w_node\"VALUES(1,zeroblob(%d))",
+ zDb, zPrefix, pRtree->iNodeSize);
+ zCreate = tdsqlite3_str_finish(p);
if( !zCreate ){
return SQLITE_NOMEM;
}
- rc = sqlite3_exec(db, zCreate, 0, 0, 0);
- sqlite3_free(zCreate);
+ rc = tdsqlite3_exec(db, zCreate, 0, 0, 0);
+ tdsqlite3_free(zCreate);
if( rc!=SQLITE_OK ){
return rc;
}
}
- appStmt[0] = &pRtree->pReadNode;
- appStmt[1] = &pRtree->pWriteNode;
- appStmt[2] = &pRtree->pDeleteNode;
- appStmt[3] = &pRtree->pReadRowid;
- appStmt[4] = &pRtree->pWriteRowid;
- appStmt[5] = &pRtree->pDeleteRowid;
- appStmt[6] = &pRtree->pReadParent;
- appStmt[7] = &pRtree->pWriteParent;
- appStmt[8] = &pRtree->pDeleteParent;
+ appStmt[0] = &pRtree->pWriteNode;
+ appStmt[1] = &pRtree->pDeleteNode;
+ appStmt[2] = &pRtree->pReadRowid;
+ appStmt[3] = &pRtree->pWriteRowid;
+ appStmt[4] = &pRtree->pDeleteRowid;
+ appStmt[5] = &pRtree->pReadParent;
+ appStmt[6] = &pRtree->pWriteParent;
+ appStmt[7] = &pRtree->pDeleteParent;
rc = rtreeQueryStat1(db, pRtree);
for(i=0; i<N_STATEMENT && rc==SQLITE_OK; i++){
- char *zSql = sqlite3_mprintf(azSql[i], zDb, zPrefix);
+ char *zSql;
+ const char *zFormat;
+ if( i!=3 || pRtree->nAux==0 ){
+ zFormat = azSql[i];
+ }else {
+ /* An UPSERT is very slightly slower than REPLACE, but it is needed
+ ** if there are auxiliary columns */
+ zFormat = "INSERT INTO\"%w\".\"%w_rowid\"(rowid,nodeno)VALUES(?1,?2)"
+ "ON CONFLICT(rowid)DO UPDATE SET nodeno=excluded.nodeno";
+ }
+ zSql = tdsqlite3_mprintf(zFormat, zDb, zPrefix);
if( zSql ){
- rc = sqlite3_prepare_v2(db, zSql, -1, appStmt[i], 0);
+ rc = tdsqlite3_prepare_v3(db, zSql, -1, f, appStmt[i], 0);
}else{
rc = SQLITE_NOMEM;
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
+ }
+ if( pRtree->nAux ){
+ pRtree->zReadAuxSql = tdsqlite3_mprintf(
+ "SELECT * FROM \"%w\".\"%w_rowid\" WHERE rowid=?1",
+ zDb, zPrefix);
+ if( pRtree->zReadAuxSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ tdsqlite3_str *p = tdsqlite3_str_new(db);
+ int ii;
+ char *zSql;
+ tdsqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix);
+ for(ii=0; ii<pRtree->nAux; ii++){
+ if( ii ) tdsqlite3_str_append(p, ",", 1);
+ if( ii<pRtree->nAuxNotNull ){
+ tdsqlite3_str_appendf(p,"a%d=coalesce(?%d,a%d)",ii,ii+2,ii);
+ }else{
+ tdsqlite3_str_appendf(p,"a%d=?%d",ii,ii+2);
+ }
+ }
+ tdsqlite3_str_appendf(p, " WHERE rowid=?1");
+ zSql = tdsqlite3_str_finish(p);
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = tdsqlite3_prepare_v3(db, zSql, -1, f, &pRtree->pWriteAux, 0);
+ tdsqlite3_free(zSql);
+ }
+ }
}
return rc;
@@ -167600,16 +193265,16 @@ static int rtreeSqlInit(
** is written to *piVal and SQLITE_OK returned. Otherwise, an SQLite error
** code is returned and the value of *piVal after returning is not defined.
*/
-static int getIntFromStmt(sqlite3 *db, const char *zSql, int *piVal){
+static int getIntFromStmt(tdsqlite3 *db, const char *zSql, int *piVal){
int rc = SQLITE_NOMEM;
if( zSql ){
- sqlite3_stmt *pStmt = 0;
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ tdsqlite3_stmt *pStmt = 0;
+ rc = tdsqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
if( rc==SQLITE_OK ){
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- *piVal = sqlite3_column_int(pStmt, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ *piVal = tdsqlite3_column_int(pStmt, 0);
}
- rc = sqlite3_finalize(pStmt);
+ rc = tdsqlite3_finalize(pStmt);
}
}
return rc;
@@ -167631,7 +193296,7 @@ static int getIntFromStmt(sqlite3 *db, const char *zSql, int *piVal){
** would fit in a single node, use a smaller node-size.
*/
static int getNodeSize(
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
Rtree *pRtree, /* Rtree handle */
int isCreate, /* True for xCreate, false for xConnect */
char **pzErr /* OUT: Error message, if any */
@@ -167640,7 +193305,7 @@ static int getNodeSize(
char *zSql;
if( isCreate ){
int iPageSize = 0;
- zSql = sqlite3_mprintf("PRAGMA %Q.page_size", pRtree->zDb);
+ zSql = tdsqlite3_mprintf("PRAGMA %Q.page_size", pRtree->zDb);
rc = getIntFromStmt(db, zSql, &iPageSize);
if( rc==SQLITE_OK ){
pRtree->iNodeSize = iPageSize-64;
@@ -167648,23 +193313,36 @@ static int getNodeSize(
pRtree->iNodeSize = 4+pRtree->nBytesPerCell*RTREE_MAXCELLS;
}
}else{
- *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ *pzErr = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
}
}else{
- zSql = sqlite3_mprintf(
+ zSql = tdsqlite3_mprintf(
"SELECT length(data) FROM '%q'.'%q_node' WHERE nodeno = 1",
pRtree->zDb, pRtree->zName
);
rc = getIntFromStmt(db, zSql, &pRtree->iNodeSize);
if( rc!=SQLITE_OK ){
- *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ *pzErr = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
+ }else if( pRtree->iNodeSize<(512-64) ){
+ rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
+ *pzErr = tdsqlite3_mprintf("undersize RTree blobs in \"%q_node\"",
+ pRtree->zName);
}
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
return rc;
}
+/*
+** Return the length of a token
+*/
+static int rtreeTokenLength(const char *z){
+ int dummy = 0;
+ return tdsqlite3GetToken((const unsigned char*)z,&dummy);
+}
+
/*
** This function is the implementation of both the xConnect and xCreate
** methods of the r-tree virtual table.
@@ -167675,10 +193353,10 @@ static int getNodeSize(
** argv[...] -> column names...
*/
static int rtreeInit(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
void *pAux, /* One of the RTREE_COORD_* constants */
int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */
- sqlite3_vtab **ppVtab, /* OUT: New virtual table */
+ tdsqlite3_vtab **ppVtab, /* OUT: New virtual table */
char **pzErr, /* OUT: Error message, if any */
int isCreate /* True for xCreate, false for xConnect */
){
@@ -167687,26 +193365,31 @@ static int rtreeInit(
int nDb; /* Length of string argv[1] */
int nName; /* Length of string argv[2] */
int eCoordType = (pAux ? RTREE_COORD_INT32 : RTREE_COORD_REAL32);
+ tdsqlite3_str *pSql;
+ char *zSql;
+ int ii = 4;
+ int iErr;
const char *aErrMsg[] = {
0, /* 0 */
"Wrong number of columns for an rtree table", /* 1 */
"Too few columns for an rtree table", /* 2 */
- "Too many columns for an rtree table" /* 3 */
+ "Too many columns for an rtree table", /* 3 */
+ "Auxiliary rtree columns must be last" /* 4 */
};
- int iErr = (argc<6) ? 2 : argc>(RTREE_MAX_DIMENSIONS*2+4) ? 3 : argc%2;
- if( aErrMsg[iErr] ){
- *pzErr = sqlite3_mprintf("%s", aErrMsg[iErr]);
+ assert( RTREE_MAX_AUX_COLUMN<256 ); /* Aux columns counted by a u8 */
+ if( argc<6 || argc>RTREE_MAX_AUX_COLUMN+3 ){
+ *pzErr = tdsqlite3_mprintf("%s", aErrMsg[2 + (argc>=6)]);
return SQLITE_ERROR;
}
- sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+ tdsqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
- /* Allocate the sqlite3_vtab structure */
+ /* Allocate the tdsqlite3_vtab structure */
nDb = (int)strlen(argv[1]);
nName = (int)strlen(argv[2]);
- pRtree = (Rtree *)sqlite3_malloc(sizeof(Rtree)+nDb+nName+2);
+ pRtree = (Rtree *)tdsqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
if( !pRtree ){
return SQLITE_NOMEM;
}
@@ -167715,52 +193398,75 @@ static int rtreeInit(
pRtree->base.pModule = &rtreeModule;
pRtree->zDb = (char *)&pRtree[1];
pRtree->zName = &pRtree->zDb[nDb+1];
- pRtree->nDim = (argc-4)/2;
- pRtree->nBytesPerCell = 8 + pRtree->nDim*4*2;
- pRtree->eCoordType = eCoordType;
+ pRtree->eCoordType = (u8)eCoordType;
memcpy(pRtree->zDb, argv[1], nDb);
memcpy(pRtree->zName, argv[2], nName);
- /* Figure out the node size to use. */
- rc = getNodeSize(db, pRtree, isCreate, pzErr);
/* Create/Connect to the underlying relational database schema. If
- ** that is successful, call sqlite3_declare_vtab() to configure
+ ** that is successful, call tdsqlite3_declare_vtab() to configure
** the r-tree table schema.
*/
- if( rc==SQLITE_OK ){
- if( (rc = rtreeSqlInit(pRtree, db, argv[1], argv[2], isCreate)) ){
- *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ pSql = tdsqlite3_str_new(db);
+ tdsqlite3_str_appendf(pSql, "CREATE TABLE x(%.*s INT",
+ rtreeTokenLength(argv[3]), argv[3]);
+ for(ii=4; ii<argc; ii++){
+ const char *zArg = argv[ii];
+ if( zArg[0]=='+' ){
+ pRtree->nAux++;
+ tdsqlite3_str_appendf(pSql, ",%.*s", rtreeTokenLength(zArg+1), zArg+1);
+ }else if( pRtree->nAux>0 ){
+ break;
}else{
- char *zSql = sqlite3_mprintf("CREATE TABLE x(%s", argv[3]);
- char *zTmp;
- int ii;
- for(ii=4; zSql && ii<argc; ii++){
- zTmp = zSql;
- zSql = sqlite3_mprintf("%s, %s", zTmp, argv[ii]);
- sqlite3_free(zTmp);
- }
- if( zSql ){
- zTmp = zSql;
- zSql = sqlite3_mprintf("%s);", zTmp);
- sqlite3_free(zTmp);
- }
- if( !zSql ){
- rc = SQLITE_NOMEM;
- }else if( SQLITE_OK!=(rc = sqlite3_declare_vtab(db, zSql)) ){
- *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
- }
- sqlite3_free(zSql);
+ pRtree->nDim2++;
+ tdsqlite3_str_appendf(pSql, ",%.*s NUM", rtreeTokenLength(zArg), zArg);
}
}
-
- if( rc==SQLITE_OK ){
- *ppVtab = (sqlite3_vtab *)pRtree;
+ tdsqlite3_str_appendf(pSql, ");");
+ zSql = tdsqlite3_str_finish(pSql);
+ if( !zSql ){
+ rc = SQLITE_NOMEM;
+ }else if( ii<argc ){
+ *pzErr = tdsqlite3_mprintf("%s", aErrMsg[4]);
+ rc = SQLITE_ERROR;
+ }else if( SQLITE_OK!=(rc = tdsqlite3_declare_vtab(db, zSql)) ){
+ *pzErr = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
+ }
+ tdsqlite3_free(zSql);
+ if( rc ) goto rtreeInit_fail;
+ pRtree->nDim = pRtree->nDim2/2;
+ if( pRtree->nDim<1 ){
+ iErr = 2;
+ }else if( pRtree->nDim2>RTREE_MAX_DIMENSIONS*2 ){
+ iErr = 3;
+ }else if( pRtree->nDim2 % 2 ){
+ iErr = 1;
}else{
- assert( *ppVtab==0 );
- assert( pRtree->nBusy==1 );
- rtreeRelease(pRtree);
+ iErr = 0;
}
+ if( iErr ){
+ *pzErr = tdsqlite3_mprintf("%s", aErrMsg[iErr]);
+ goto rtreeInit_fail;
+ }
+ pRtree->nBytesPerCell = 8 + pRtree->nDim2*4;
+
+ /* Figure out the node size to use. */
+ rc = getNodeSize(db, pRtree, isCreate, pzErr);
+ if( rc ) goto rtreeInit_fail;
+ rc = rtreeSqlInit(pRtree, db, argv[1], argv[2], isCreate);
+ if( rc ){
+ *pzErr = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
+ goto rtreeInit_fail;
+ }
+
+ *ppVtab = (tdsqlite3_vtab *)pRtree;
+ return SQLITE_OK;
+
+rtreeInit_fail:
+ if( rc==SQLITE_OK ) rc = SQLITE_ERROR;
+ assert( *ppVtab==0 );
+ assert( pRtree->nBusy==1 );
+ rtreeRelease(pRtree);
return rc;
}
@@ -167781,49 +193487,46 @@ static int rtreeInit(
** list, containing the 8-byte rowid/pageno followed by the
** <num-dimension>*2 coordinates.
*/
-static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
- char *zText = 0;
+static void rtreenode(tdsqlite3_context *ctx, int nArg, tdsqlite3_value **apArg){
RtreeNode node;
Rtree tree;
int ii;
+ int nData;
+ int errCode;
+ tdsqlite3_str *pOut;
UNUSED_PARAMETER(nArg);
memset(&node, 0, sizeof(RtreeNode));
memset(&tree, 0, sizeof(Rtree));
- tree.nDim = sqlite3_value_int(apArg[0]);
+ tree.nDim = (u8)tdsqlite3_value_int(apArg[0]);
+ if( tree.nDim<1 || tree.nDim>5 ) return;
+ tree.nDim2 = tree.nDim*2;
tree.nBytesPerCell = 8 + 8 * tree.nDim;
- node.zData = (u8 *)sqlite3_value_blob(apArg[1]);
+ node.zData = (u8 *)tdsqlite3_value_blob(apArg[1]);
+ nData = tdsqlite3_value_bytes(apArg[1]);
+ if( nData<4 ) return;
+ if( nData<NCELL(&node)*tree.nBytesPerCell ) return;
+ pOut = tdsqlite3_str_new(0);
for(ii=0; ii<NCELL(&node); ii++){
- char zCell[512];
- int nCell = 0;
RtreeCell cell;
int jj;
nodeGetCell(&tree, &node, ii, &cell);
- sqlite3_snprintf(512-nCell,&zCell[nCell],"%lld", cell.iRowid);
- nCell = (int)strlen(zCell);
- for(jj=0; jj<tree.nDim*2; jj++){
+ if( ii>0 ) tdsqlite3_str_append(pOut, " ", 1);
+ tdsqlite3_str_appendf(pOut, "{%lld", cell.iRowid);
+ for(jj=0; jj<tree.nDim2; jj++){
#ifndef SQLITE_RTREE_INT_ONLY
- sqlite3_snprintf(512-nCell,&zCell[nCell], " %g",
- (double)cell.aCoord[jj].f);
+ tdsqlite3_str_appendf(pOut, " %g", (double)cell.aCoord[jj].f);
#else
- sqlite3_snprintf(512-nCell,&zCell[nCell], " %d",
- cell.aCoord[jj].i);
+ tdsqlite3_str_appendf(pOut, " %d", cell.aCoord[jj].i);
#endif
- nCell = (int)strlen(zCell);
- }
-
- if( zText ){
- char *zTextNew = sqlite3_mprintf("%s {%s}", zText, zCell);
- sqlite3_free(zText);
- zText = zTextNew;
- }else{
- zText = sqlite3_mprintf("{%s}", zCell);
}
+ tdsqlite3_str_append(pOut, "}", 1);
}
-
- sqlite3_result_text(ctx, zText, -1, sqlite3_free);
+ errCode = tdsqlite3_str_errcode(pOut);
+ tdsqlite3_result_text(ctx, tdsqlite3_str_finish(pOut), -1, tdsqlite3_free);
+ tdsqlite3_result_error_code(ctx, errCode);
}
/* This routine implements an SQL function that returns the "depth" parameter
@@ -167835,30 +193538,2316 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
** node always has nodeno=1, so the example above is the primary use for this
** routine. This routine is intended for testing and analysis only.
*/
-static void rtreedepth(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
+static void rtreedepth(tdsqlite3_context *ctx, int nArg, tdsqlite3_value **apArg){
UNUSED_PARAMETER(nArg);
- if( sqlite3_value_type(apArg[0])!=SQLITE_BLOB
- || sqlite3_value_bytes(apArg[0])<2
+ if( tdsqlite3_value_type(apArg[0])!=SQLITE_BLOB
+ || tdsqlite3_value_bytes(apArg[0])<2
+ ){
+ tdsqlite3_result_error(ctx, "Invalid argument to rtreedepth()", -1);
+ }else{
+ u8 *zBlob = (u8 *)tdsqlite3_value_blob(apArg[0]);
+ tdsqlite3_result_int(ctx, readInt16(zBlob));
+ }
+}
+
+/*
+** Context object passed between the various routines that make up the
+** implementation of integrity-check function rtreecheck().
+*/
+typedef struct RtreeCheck RtreeCheck;
+struct RtreeCheck {
+ tdsqlite3 *db; /* Database handle */
+ const char *zDb; /* Database containing rtree table */
+ const char *zTab; /* Name of rtree table */
+ int bInt; /* True for rtree_i32 table */
+ int nDim; /* Number of dimensions for this rtree tbl */
+ tdsqlite3_stmt *pGetNode; /* Statement used to retrieve nodes */
+ tdsqlite3_stmt *aCheckMapping[2]; /* Statements to query %_parent/%_rowid */
+ int nLeaf; /* Number of leaf cells in table */
+ int nNonLeaf; /* Number of non-leaf cells in table */
+ int rc; /* Return code */
+ char *zReport; /* Message to report */
+ int nErr; /* Number of lines in zReport */
+};
+
+#define RTREE_CHECK_MAX_ERROR 100
+
+/*
+** Reset SQL statement pStmt. If the tdsqlite3_reset() call returns an error,
+** and RtreeCheck.rc==SQLITE_OK, set RtreeCheck.rc to the error code.
+*/
+static void rtreeCheckReset(RtreeCheck *pCheck, tdsqlite3_stmt *pStmt){
+ int rc = tdsqlite3_reset(pStmt);
+ if( pCheck->rc==SQLITE_OK ) pCheck->rc = rc;
+}
+
+/*
+** The second and subsequent arguments to this function are a format string
+** and printf style arguments. This function formats the string and attempts
+** to compile it as an SQL statement.
+**
+** If successful, a pointer to the new SQL statement is returned. Otherwise,
+** NULL is returned and an error code left in RtreeCheck.rc.
+*/
+static tdsqlite3_stmt *rtreeCheckPrepare(
+ RtreeCheck *pCheck, /* RtreeCheck object */
+ const char *zFmt, ... /* Format string and trailing args */
+){
+ va_list ap;
+ char *z;
+ tdsqlite3_stmt *pRet = 0;
+
+ va_start(ap, zFmt);
+ z = tdsqlite3_vmprintf(zFmt, ap);
+
+ if( pCheck->rc==SQLITE_OK ){
+ if( z==0 ){
+ pCheck->rc = SQLITE_NOMEM;
+ }else{
+ pCheck->rc = tdsqlite3_prepare_v2(pCheck->db, z, -1, &pRet, 0);
+ }
+ }
+
+ tdsqlite3_free(z);
+ va_end(ap);
+ return pRet;
+}
+
+/*
+** The second and subsequent arguments to this function are a printf()
+** style format string and arguments. This function formats the string and
+** appends it to the report being accumuated in pCheck.
+*/
+static void rtreeCheckAppendMsg(RtreeCheck *pCheck, const char *zFmt, ...){
+ va_list ap;
+ va_start(ap, zFmt);
+ if( pCheck->rc==SQLITE_OK && pCheck->nErr<RTREE_CHECK_MAX_ERROR ){
+ char *z = tdsqlite3_vmprintf(zFmt, ap);
+ if( z==0 ){
+ pCheck->rc = SQLITE_NOMEM;
+ }else{
+ pCheck->zReport = tdsqlite3_mprintf("%z%s%z",
+ pCheck->zReport, (pCheck->zReport ? "\n" : ""), z
+ );
+ if( pCheck->zReport==0 ){
+ pCheck->rc = SQLITE_NOMEM;
+ }
+ }
+ pCheck->nErr++;
+ }
+ va_end(ap);
+}
+
+/*
+** This function is a no-op if there is already an error code stored
+** in the RtreeCheck object indicated by the first argument. NULL is
+** returned in this case.
+**
+** Otherwise, the contents of rtree table node iNode are loaded from
+** the database and copied into a buffer obtained from tdsqlite3_malloc().
+** If no error occurs, a pointer to the buffer is returned and (*pnNode)
+** is set to the size of the buffer in bytes.
+**
+** Or, if an error does occur, NULL is returned and an error code left
+** in the RtreeCheck object. The final value of *pnNode is undefined in
+** this case.
+*/
+static u8 *rtreeCheckGetNode(RtreeCheck *pCheck, i64 iNode, int *pnNode){
+ u8 *pRet = 0; /* Return value */
+
+ if( pCheck->rc==SQLITE_OK && pCheck->pGetNode==0 ){
+ pCheck->pGetNode = rtreeCheckPrepare(pCheck,
+ "SELECT data FROM %Q.'%q_node' WHERE nodeno=?",
+ pCheck->zDb, pCheck->zTab
+ );
+ }
+
+ if( pCheck->rc==SQLITE_OK ){
+ tdsqlite3_bind_int64(pCheck->pGetNode, 1, iNode);
+ if( tdsqlite3_step(pCheck->pGetNode)==SQLITE_ROW ){
+ int nNode = tdsqlite3_column_bytes(pCheck->pGetNode, 0);
+ const u8 *pNode = (const u8*)tdsqlite3_column_blob(pCheck->pGetNode, 0);
+ pRet = tdsqlite3_malloc64(nNode);
+ if( pRet==0 ){
+ pCheck->rc = SQLITE_NOMEM;
+ }else{
+ memcpy(pRet, pNode, nNode);
+ *pnNode = nNode;
+ }
+ }
+ rtreeCheckReset(pCheck, pCheck->pGetNode);
+ if( pCheck->rc==SQLITE_OK && pRet==0 ){
+ rtreeCheckAppendMsg(pCheck, "Node %lld missing from database", iNode);
+ }
+ }
+
+ return pRet;
+}
+
+/*
+** This function is used to check that the %_parent (if bLeaf==0) or %_rowid
+** (if bLeaf==1) table contains a specified entry. The schemas of the
+** two tables are:
+**
+** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER)
+** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER, ...)
+**
+** In both cases, this function checks that there exists an entry with
+** IPK value iKey and the second column set to iVal.
+**
+*/
+static void rtreeCheckMapping(
+ RtreeCheck *pCheck, /* RtreeCheck object */
+ int bLeaf, /* True for a leaf cell, false for interior */
+ i64 iKey, /* Key for mapping */
+ i64 iVal /* Expected value for mapping */
+){
+ int rc;
+ tdsqlite3_stmt *pStmt;
+ const char *azSql[2] = {
+ "SELECT parentnode FROM %Q.'%q_parent' WHERE nodeno=?1",
+ "SELECT nodeno FROM %Q.'%q_rowid' WHERE rowid=?1"
+ };
+
+ assert( bLeaf==0 || bLeaf==1 );
+ if( pCheck->aCheckMapping[bLeaf]==0 ){
+ pCheck->aCheckMapping[bLeaf] = rtreeCheckPrepare(pCheck,
+ azSql[bLeaf], pCheck->zDb, pCheck->zTab
+ );
+ }
+ if( pCheck->rc!=SQLITE_OK ) return;
+
+ pStmt = pCheck->aCheckMapping[bLeaf];
+ tdsqlite3_bind_int64(pStmt, 1, iKey);
+ rc = tdsqlite3_step(pStmt);
+ if( rc==SQLITE_DONE ){
+ rtreeCheckAppendMsg(pCheck, "Mapping (%lld -> %lld) missing from %s table",
+ iKey, iVal, (bLeaf ? "%_rowid" : "%_parent")
+ );
+ }else if( rc==SQLITE_ROW ){
+ i64 ii = tdsqlite3_column_int64(pStmt, 0);
+ if( ii!=iVal ){
+ rtreeCheckAppendMsg(pCheck,
+ "Found (%lld -> %lld) in %s table, expected (%lld -> %lld)",
+ iKey, ii, (bLeaf ? "%_rowid" : "%_parent"), iKey, iVal
+ );
+ }
+ }
+ rtreeCheckReset(pCheck, pStmt);
+}
+
+/*
+** Argument pCell points to an array of coordinates stored on an rtree page.
+** This function checks that the coordinates are internally consistent (no
+** x1>x2 conditions) and adds an error message to the RtreeCheck object
+** if they are not.
+**
+** Additionally, if pParent is not NULL, then it is assumed to point to
+** the array of coordinates on the parent page that bound the page
+** containing pCell. In this case it is also verified that the two
+** sets of coordinates are mutually consistent and an error message added
+** to the RtreeCheck object if they are not.
+*/
+static void rtreeCheckCellCoord(
+ RtreeCheck *pCheck,
+ i64 iNode, /* Node id to use in error messages */
+ int iCell, /* Cell number to use in error messages */
+ u8 *pCell, /* Pointer to cell coordinates */
+ u8 *pParent /* Pointer to parent coordinates */
+){
+ RtreeCoord c1, c2;
+ RtreeCoord p1, p2;
+ int i;
+
+ for(i=0; i<pCheck->nDim; i++){
+ readCoord(&pCell[4*2*i], &c1);
+ readCoord(&pCell[4*(2*i + 1)], &c2);
+
+ /* printf("%e, %e\n", c1.u.f, c2.u.f); */
+ if( pCheck->bInt ? c1.i>c2.i : c1.f>c2.f ){
+ rtreeCheckAppendMsg(pCheck,
+ "Dimension %d of cell %d on node %lld is corrupt", i, iCell, iNode
+ );
+ }
+
+ if( pParent ){
+ readCoord(&pParent[4*2*i], &p1);
+ readCoord(&pParent[4*(2*i + 1)], &p2);
+
+ if( (pCheck->bInt ? c1.i<p1.i : c1.f<p1.f)
+ || (pCheck->bInt ? c2.i>p2.i : c2.f>p2.f)
+ ){
+ rtreeCheckAppendMsg(pCheck,
+ "Dimension %d of cell %d on node %lld is corrupt relative to parent"
+ , i, iCell, iNode
+ );
+ }
+ }
+ }
+}
+
+/*
+** Run rtreecheck() checks on node iNode, which is at depth iDepth within
+** the r-tree structure. Argument aParent points to the array of coordinates
+** that bound node iNode on the parent node.
+**
+** If any problems are discovered, an error message is appended to the
+** report accumulated in the RtreeCheck object.
+*/
+static void rtreeCheckNode(
+ RtreeCheck *pCheck,
+ int iDepth, /* Depth of iNode (0==leaf) */
+ u8 *aParent, /* Buffer containing parent coords */
+ i64 iNode /* Node to check */
+){
+ u8 *aNode = 0;
+ int nNode = 0;
+
+ assert( iNode==1 || aParent!=0 );
+ assert( pCheck->nDim>0 );
+
+ aNode = rtreeCheckGetNode(pCheck, iNode, &nNode);
+ if( aNode ){
+ if( nNode<4 ){
+ rtreeCheckAppendMsg(pCheck,
+ "Node %lld is too small (%d bytes)", iNode, nNode
+ );
+ }else{
+ int nCell; /* Number of cells on page */
+ int i; /* Used to iterate through cells */
+ if( aParent==0 ){
+ iDepth = readInt16(aNode);
+ if( iDepth>RTREE_MAX_DEPTH ){
+ rtreeCheckAppendMsg(pCheck, "Rtree depth out of range (%d)", iDepth);
+ tdsqlite3_free(aNode);
+ return;
+ }
+ }
+ nCell = readInt16(&aNode[2]);
+ if( (4 + nCell*(8 + pCheck->nDim*2*4))>nNode ){
+ rtreeCheckAppendMsg(pCheck,
+ "Node %lld is too small for cell count of %d (%d bytes)",
+ iNode, nCell, nNode
+ );
+ }else{
+ for(i=0; i<nCell; i++){
+ u8 *pCell = &aNode[4 + i*(8 + pCheck->nDim*2*4)];
+ i64 iVal = readInt64(pCell);
+ rtreeCheckCellCoord(pCheck, iNode, i, &pCell[8], aParent);
+
+ if( iDepth>0 ){
+ rtreeCheckMapping(pCheck, 0, iVal, iNode);
+ rtreeCheckNode(pCheck, iDepth-1, &pCell[8], iVal);
+ pCheck->nNonLeaf++;
+ }else{
+ rtreeCheckMapping(pCheck, 1, iVal, iNode);
+ pCheck->nLeaf++;
+ }
+ }
+ }
+ }
+ tdsqlite3_free(aNode);
+ }
+}
+
+/*
+** The second argument to this function must be either "_rowid" or
+** "_parent". This function checks that the number of entries in the
+** %_rowid or %_parent table is exactly nExpect. If not, it adds
+** an error message to the report in the RtreeCheck object indicated
+** by the first argument.
+*/
+static void rtreeCheckCount(RtreeCheck *pCheck, const char *zTbl, i64 nExpect){
+ if( pCheck->rc==SQLITE_OK ){
+ tdsqlite3_stmt *pCount;
+ pCount = rtreeCheckPrepare(pCheck, "SELECT count(*) FROM %Q.'%q%s'",
+ pCheck->zDb, pCheck->zTab, zTbl
+ );
+ if( pCount ){
+ if( tdsqlite3_step(pCount)==SQLITE_ROW ){
+ i64 nActual = tdsqlite3_column_int64(pCount, 0);
+ if( nActual!=nExpect ){
+ rtreeCheckAppendMsg(pCheck, "Wrong number of entries in %%%s table"
+ " - expected %lld, actual %lld" , zTbl, nExpect, nActual
+ );
+ }
+ }
+ pCheck->rc = tdsqlite3_finalize(pCount);
+ }
+ }
+}
+
+/*
+** This function does the bulk of the work for the rtree integrity-check.
+** It is called by rtreecheck(), which is the SQL function implementation.
+*/
+static int rtreeCheckTable(
+ tdsqlite3 *db, /* Database handle to access db through */
+ const char *zDb, /* Name of db ("main", "temp" etc.) */
+ const char *zTab, /* Name of rtree table to check */
+ char **pzReport /* OUT: tdsqlite3_malloc'd report text */
+){
+ RtreeCheck check; /* Common context for various routines */
+ tdsqlite3_stmt *pStmt = 0; /* Used to find column count of rtree table */
+ int bEnd = 0; /* True if transaction should be closed */
+ int nAux = 0; /* Number of extra columns. */
+
+ /* Initialize the context object */
+ memset(&check, 0, sizeof(check));
+ check.db = db;
+ check.zDb = zDb;
+ check.zTab = zTab;
+
+ /* If there is not already an open transaction, open one now. This is
+ ** to ensure that the queries run as part of this integrity-check operate
+ ** on a consistent snapshot. */
+ if( tdsqlite3_get_autocommit(db) ){
+ check.rc = tdsqlite3_exec(db, "BEGIN", 0, 0, 0);
+ bEnd = 1;
+ }
+
+ /* Find the number of auxiliary columns */
+ if( check.rc==SQLITE_OK ){
+ pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab);
+ if( pStmt ){
+ nAux = tdsqlite3_column_count(pStmt) - 2;
+ tdsqlite3_finalize(pStmt);
+ }
+ check.rc = SQLITE_OK;
+ }
+
+ /* Find number of dimensions in the rtree table. */
+ pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.%Q", zDb, zTab);
+ if( pStmt ){
+ int rc;
+ check.nDim = (tdsqlite3_column_count(pStmt) - 1 - nAux) / 2;
+ if( check.nDim<1 ){
+ rtreeCheckAppendMsg(&check, "Schema corrupt or not an rtree");
+ }else if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ check.bInt = (tdsqlite3_column_type(pStmt, 1)==SQLITE_INTEGER);
+ }
+ rc = tdsqlite3_finalize(pStmt);
+ if( rc!=SQLITE_CORRUPT ) check.rc = rc;
+ }
+
+ /* Do the actual integrity-check */
+ if( check.nDim>=1 ){
+ if( check.rc==SQLITE_OK ){
+ rtreeCheckNode(&check, 0, 0, 1);
+ }
+ rtreeCheckCount(&check, "_rowid", check.nLeaf);
+ rtreeCheckCount(&check, "_parent", check.nNonLeaf);
+ }
+
+ /* Finalize SQL statements used by the integrity-check */
+ tdsqlite3_finalize(check.pGetNode);
+ tdsqlite3_finalize(check.aCheckMapping[0]);
+ tdsqlite3_finalize(check.aCheckMapping[1]);
+
+ /* If one was opened, close the transaction */
+ if( bEnd ){
+ int rc = tdsqlite3_exec(db, "END", 0, 0, 0);
+ if( check.rc==SQLITE_OK ) check.rc = rc;
+ }
+ *pzReport = check.zReport;
+ return check.rc;
+}
+
+/*
+** Usage:
+**
+** rtreecheck(<rtree-table>);
+** rtreecheck(<database>, <rtree-table>);
+**
+** Invoking this SQL function runs an integrity-check on the named rtree
+** table. The integrity-check verifies the following:
+**
+** 1. For each cell in the r-tree structure (%_node table), that:
+**
+** a) for each dimension, (coord1 <= coord2).
+**
+** b) unless the cell is on the root node, that the cell is bounded
+** by the parent cell on the parent node.
+**
+** c) for leaf nodes, that there is an entry in the %_rowid
+** table corresponding to the cell's rowid value that
+** points to the correct node.
+**
+** d) for cells on non-leaf nodes, that there is an entry in the
+** %_parent table mapping from the cell's child node to the
+** node that it resides on.
+**
+** 2. That there are the same number of entries in the %_rowid table
+** as there are leaf cells in the r-tree structure, and that there
+** is a leaf cell that corresponds to each entry in the %_rowid table.
+**
+** 3. That there are the same number of entries in the %_parent table
+** as there are non-leaf cells in the r-tree structure, and that
+** there is a non-leaf cell that corresponds to each entry in the
+** %_parent table.
+*/
+static void rtreecheck(
+ tdsqlite3_context *ctx,
+ int nArg,
+ tdsqlite3_value **apArg
+){
+ if( nArg!=1 && nArg!=2 ){
+ tdsqlite3_result_error(ctx,
+ "wrong number of arguments to function rtreecheck()", -1
+ );
+ }else{
+ int rc;
+ char *zReport = 0;
+ const char *zDb = (const char*)tdsqlite3_value_text(apArg[0]);
+ const char *zTab;
+ if( nArg==1 ){
+ zTab = zDb;
+ zDb = "main";
+ }else{
+ zTab = (const char*)tdsqlite3_value_text(apArg[1]);
+ }
+ rc = rtreeCheckTable(tdsqlite3_context_db_handle(ctx), zDb, zTab, &zReport);
+ if( rc==SQLITE_OK ){
+ tdsqlite3_result_text(ctx, zReport ? zReport : "ok", -1, SQLITE_TRANSIENT);
+ }else{
+ tdsqlite3_result_error_code(ctx, rc);
+ }
+ tdsqlite3_free(zReport);
+ }
+}
+
+/* Conditionally include the geopoly code */
+#ifdef SQLITE_ENABLE_GEOPOLY
+/************** Include geopoly.c in the middle of rtree.c *******************/
+/************** Begin file geopoly.c *****************************************/
+/*
+** 2018-05-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file implements an alternative R-Tree virtual table that
+** uses polygons to express the boundaries of 2-dimensional objects.
+**
+** This file is #include-ed onto the end of "rtree.c" so that it has
+** access to all of the R-Tree internals.
+*/
+/* #include <stdlib.h> */
+
+/* Enable -DGEOPOLY_ENABLE_DEBUG for debugging facilities */
+#ifdef GEOPOLY_ENABLE_DEBUG
+ static int geo_debug = 0;
+# define GEODEBUG(X) if(geo_debug)printf X
+#else
+# define GEODEBUG(X)
+#endif
+
+#ifndef JSON_NULL /* The following stuff repeats things found in json1 */
+/*
+** Versions of isspace(), isalnum() and isdigit() to which it is safe
+** to pass signed char values.
+*/
+#ifdef tdsqlite3Isdigit
+ /* Use the SQLite core versions if this routine is part of the
+ ** SQLite amalgamation */
+# define safe_isdigit(x) tdsqlite3Isdigit(x)
+# define safe_isalnum(x) tdsqlite3Isalnum(x)
+# define safe_isxdigit(x) tdsqlite3Isxdigit(x)
+#else
+ /* Use the standard library for separate compilation */
+#include <ctype.h> /* amalgamator: keep */
+# define safe_isdigit(x) isdigit((unsigned char)(x))
+# define safe_isalnum(x) isalnum((unsigned char)(x))
+# define safe_isxdigit(x) isxdigit((unsigned char)(x))
+#endif
+
+/*
+** Growing our own isspace() routine this way is twice as fast as
+** the library isspace() function.
+*/
+static const char geopolyIsSpace[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+#define safe_isspace(x) (geopolyIsSpace[(unsigned char)x])
+#endif /* JSON NULL - back to original code */
+
+/* Compiler and version */
+#ifndef GCC_VERSION
+#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC)
+# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__)
+#else
+# define GCC_VERSION 0
+#endif
+#endif
+#ifndef MSVC_VERSION
+#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC)
+# define MSVC_VERSION _MSC_VER
+#else
+# define MSVC_VERSION 0
+#endif
+#endif
+
+/* Datatype for coordinates
+*/
+typedef float GeoCoord;
+
+/*
+** Internal representation of a polygon.
+**
+** The polygon consists of a sequence of vertexes. There is a line
+** segment between each pair of vertexes, and one final segment from
+** the last vertex back to the first. (This differs from the GeoJSON
+** standard in which the final vertex is a repeat of the first.)
+**
+** The polygon follows the right-hand rule. The area to the right of
+** each segment is "outside" and the area to the left is "inside".
+**
+** The on-disk representation consists of a 4-byte header followed by
+** the values. The 4-byte header is:
+**
+** encoding (1 byte) 0=big-endian, 1=little-endian
+** nvertex (3 bytes) Number of vertexes as a big-endian integer
+**
+** Enough space is allocated for 4 coordinates, to work around over-zealous
+** warnings coming from some compiler (notably, clang). In reality, the size
+** of each GeoPoly memory allocate is adjusted as necessary so that the
+** GeoPoly.a[] array at the end is the appropriate size.
+*/
+typedef struct GeoPoly GeoPoly;
+struct GeoPoly {
+ int nVertex; /* Number of vertexes */
+ unsigned char hdr[4]; /* Header for on-disk representation */
+ GeoCoord a[8]; /* 2*nVertex values. X (longitude) first, then Y */
+};
+
+/* The size of a memory allocation needed for a GeoPoly object sufficient
+** to hold N coordinate pairs.
+*/
+#define GEOPOLY_SZ(N) (sizeof(GeoPoly) + sizeof(GeoCoord)*2*((N)-4))
+
+/* Macros to access coordinates of a GeoPoly.
+** We have to use these macros, rather than just say p->a[i] in order
+** to silence (incorrect) UBSAN warnings if the array index is too large.
+*/
+#define GeoX(P,I) (((GeoCoord*)(P)->a)[(I)*2])
+#define GeoY(P,I) (((GeoCoord*)(P)->a)[(I)*2+1])
+
+
+/*
+** State of a parse of a GeoJSON input.
+*/
+typedef struct GeoParse GeoParse;
+struct GeoParse {
+ const unsigned char *z; /* Unparsed input */
+ int nVertex; /* Number of vertexes in a[] */
+ int nAlloc; /* Space allocated to a[] */
+ int nErr; /* Number of errors encountered */
+ GeoCoord *a; /* Array of vertexes. From tdsqlite3_malloc64() */
+};
+
+/* Do a 4-byte byte swap */
+static void geopolySwab32(unsigned char *a){
+ unsigned char t = a[0];
+ a[0] = a[3];
+ a[3] = t;
+ t = a[1];
+ a[1] = a[2];
+ a[2] = t;
+}
+
+/* Skip whitespace. Return the next non-whitespace character. */
+static char geopolySkipSpace(GeoParse *p){
+ while( safe_isspace(p->z[0]) ) p->z++;
+ return p->z[0];
+}
+
+/* Parse out a number. Write the value into *pVal if pVal!=0.
+** return non-zero on success and zero if the next token is not a number.
+*/
+static int geopolyParseNumber(GeoParse *p, GeoCoord *pVal){
+ char c = geopolySkipSpace(p);
+ const unsigned char *z = p->z;
+ int j = 0;
+ int seenDP = 0;
+ int seenE = 0;
+ if( c=='-' ){
+ j = 1;
+ c = z[j];
+ }
+ if( c=='0' && z[j+1]>='0' && z[j+1]<='9' ) return 0;
+ for(;; j++){
+ c = z[j];
+ if( safe_isdigit(c) ) continue;
+ if( c=='.' ){
+ if( z[j-1]=='-' ) return 0;
+ if( seenDP ) return 0;
+ seenDP = 1;
+ continue;
+ }
+ if( c=='e' || c=='E' ){
+ if( z[j-1]<'0' ) return 0;
+ if( seenE ) return -1;
+ seenDP = seenE = 1;
+ c = z[j+1];
+ if( c=='+' || c=='-' ){
+ j++;
+ c = z[j+1];
+ }
+ if( c<'0' || c>'9' ) return 0;
+ continue;
+ }
+ break;
+ }
+ if( z[j-1]<'0' ) return 0;
+ if( pVal ){
+#ifdef SQLITE_AMALGAMATION
+ /* The tdsqlite3AtoF() routine is much much faster than atof(), if it
+ ** is available */
+ double r;
+ (void)tdsqlite3AtoF((const char*)p->z, &r, j, SQLITE_UTF8);
+ *pVal = r;
+#else
+ *pVal = (GeoCoord)atof((const char*)p->z);
+#endif
+ }
+ p->z += j;
+ return 1;
+}
+
+/*
+** If the input is a well-formed JSON array of coordinates with at least
+** four coordinates and where each coordinate is itself a two-value array,
+** then convert the JSON into a GeoPoly object and return a pointer to
+** that object.
+**
+** If any error occurs, return NULL.
+*/
+static GeoPoly *geopolyParseJson(const unsigned char *z, int *pRc){
+ GeoParse s;
+ int rc = SQLITE_OK;
+ memset(&s, 0, sizeof(s));
+ s.z = z;
+ if( geopolySkipSpace(&s)=='[' ){
+ s.z++;
+ while( geopolySkipSpace(&s)=='[' ){
+ int ii = 0;
+ char c;
+ s.z++;
+ if( s.nVertex>=s.nAlloc ){
+ GeoCoord *aNew;
+ s.nAlloc = s.nAlloc*2 + 16;
+ aNew = tdsqlite3_realloc64(s.a, s.nAlloc*sizeof(GeoCoord)*2 );
+ if( aNew==0 ){
+ rc = SQLITE_NOMEM;
+ s.nErr++;
+ break;
+ }
+ s.a = aNew;
+ }
+ while( geopolyParseNumber(&s, ii<=1 ? &s.a[s.nVertex*2+ii] : 0) ){
+ ii++;
+ if( ii==2 ) s.nVertex++;
+ c = geopolySkipSpace(&s);
+ s.z++;
+ if( c==',' ) continue;
+ if( c==']' && ii>=2 ) break;
+ s.nErr++;
+ rc = SQLITE_ERROR;
+ goto parse_json_err;
+ }
+ if( geopolySkipSpace(&s)==',' ){
+ s.z++;
+ continue;
+ }
+ break;
+ }
+ if( geopolySkipSpace(&s)==']'
+ && s.nVertex>=4
+ && s.a[0]==s.a[s.nVertex*2-2]
+ && s.a[1]==s.a[s.nVertex*2-1]
+ && (s.z++, geopolySkipSpace(&s)==0)
+ ){
+ GeoPoly *pOut;
+ int x = 1;
+ s.nVertex--; /* Remove the redundant vertex at the end */
+ pOut = tdsqlite3_malloc64( GEOPOLY_SZ((tdsqlite3_int64)s.nVertex) );
+ x = 1;
+ if( pOut==0 ) goto parse_json_err;
+ pOut->nVertex = s.nVertex;
+ memcpy(pOut->a, s.a, s.nVertex*2*sizeof(GeoCoord));
+ pOut->hdr[0] = *(unsigned char*)&x;
+ pOut->hdr[1] = (s.nVertex>>16)&0xff;
+ pOut->hdr[2] = (s.nVertex>>8)&0xff;
+ pOut->hdr[3] = s.nVertex&0xff;
+ tdsqlite3_free(s.a);
+ if( pRc ) *pRc = SQLITE_OK;
+ return pOut;
+ }else{
+ s.nErr++;
+ rc = SQLITE_ERROR;
+ }
+ }
+parse_json_err:
+ if( pRc ) *pRc = rc;
+ tdsqlite3_free(s.a);
+ return 0;
+}
+
+/*
+** Given a function parameter, try to interpret it as a polygon, either
+** in the binary format or JSON text. Compute a GeoPoly object and
+** return a pointer to that object. Or if the input is not a well-formed
+** polygon, put an error message in tdsqlite3_context and return NULL.
+*/
+static GeoPoly *geopolyFuncParam(
+ tdsqlite3_context *pCtx, /* Context for error messages */
+ tdsqlite3_value *pVal, /* The value to decode */
+ int *pRc /* Write error here */
+){
+ GeoPoly *p = 0;
+ int nByte;
+ if( tdsqlite3_value_type(pVal)==SQLITE_BLOB
+ && (nByte = tdsqlite3_value_bytes(pVal))>=(4+6*sizeof(GeoCoord))
){
- sqlite3_result_error(ctx, "Invalid argument to rtreedepth()", -1);
+ const unsigned char *a = tdsqlite3_value_blob(pVal);
+ int nVertex;
+ nVertex = (a[1]<<16) + (a[2]<<8) + a[3];
+ if( (a[0]==0 || a[0]==1)
+ && (nVertex*2*sizeof(GeoCoord) + 4)==(unsigned int)nByte
+ ){
+ p = tdsqlite3_malloc64( sizeof(*p) + (nVertex-1)*2*sizeof(GeoCoord) );
+ if( p==0 ){
+ if( pRc ) *pRc = SQLITE_NOMEM;
+ if( pCtx ) tdsqlite3_result_error_nomem(pCtx);
+ }else{
+ int x = 1;
+ p->nVertex = nVertex;
+ memcpy(p->hdr, a, nByte);
+ if( a[0] != *(unsigned char*)&x ){
+ int ii;
+ for(ii=0; ii<nVertex; ii++){
+ geopolySwab32((unsigned char*)&GeoX(p,ii));
+ geopolySwab32((unsigned char*)&GeoY(p,ii));
+ }
+ p->hdr[0] ^= 1;
+ }
+ }
+ }
+ if( pRc ) *pRc = SQLITE_OK;
+ return p;
+ }else if( tdsqlite3_value_type(pVal)==SQLITE_TEXT ){
+ const unsigned char *zJson = tdsqlite3_value_text(pVal);
+ if( zJson==0 ){
+ if( pRc ) *pRc = SQLITE_NOMEM;
+ return 0;
+ }
+ return geopolyParseJson(zJson, pRc);
+ }else{
+ if( pRc ) *pRc = SQLITE_ERROR;
+ return 0;
+ }
+}
+
+/*
+** Implementation of the geopoly_blob(X) function.
+**
+** If the input is a well-formed Geopoly BLOB or JSON string
+** then return the BLOB representation of the polygon. Otherwise
+** return NULL.
+*/
+static void geopolyBlobFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ tdsqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ tdsqlite3_free(p);
+ }
+}
+
+/*
+** SQL function: geopoly_json(X)
+**
+** Interpret X as a polygon and render it as a JSON array
+** of coordinates. Or, if X is not a valid polygon, return NULL.
+*/
+static void geopolyJsonFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
+ tdsqlite3_str *x = tdsqlite3_str_new(db);
+ int i;
+ tdsqlite3_str_append(x, "[", 1);
+ for(i=0; i<p->nVertex; i++){
+ tdsqlite3_str_appendf(x, "[%!g,%!g],", GeoX(p,i), GeoY(p,i));
+ }
+ tdsqlite3_str_appendf(x, "[%!g,%!g]]", GeoX(p,0), GeoY(p,0));
+ tdsqlite3_result_text(context, tdsqlite3_str_finish(x), -1, tdsqlite3_free);
+ tdsqlite3_free(p);
+ }
+}
+
+/*
+** SQL function: geopoly_svg(X, ....)
+**
+** Interpret X as a polygon and render it as a SVG <polyline>.
+** Additional arguments are added as attributes to the <polyline>.
+*/
+static void geopolySvgFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p;
+ if( argc<1 ) return;
+ p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ tdsqlite3 *db = tdsqlite3_context_db_handle(context);
+ tdsqlite3_str *x = tdsqlite3_str_new(db);
+ int i;
+ char cSep = '\'';
+ tdsqlite3_str_appendf(x, "<polyline points=");
+ for(i=0; i<p->nVertex; i++){
+ tdsqlite3_str_appendf(x, "%c%g,%g", cSep, GeoX(p,i), GeoY(p,i));
+ cSep = ' ';
+ }
+ tdsqlite3_str_appendf(x, " %g,%g'", GeoX(p,0), GeoY(p,0));
+ for(i=1; i<argc; i++){
+ const char *z = (const char*)tdsqlite3_value_text(argv[i]);
+ if( z && z[0] ){
+ tdsqlite3_str_appendf(x, " %s", z);
+ }
+ }
+ tdsqlite3_str_appendf(x, "></polyline>");
+ tdsqlite3_result_text(context, tdsqlite3_str_finish(x), -1, tdsqlite3_free);
+ tdsqlite3_free(p);
+ }
+}
+
+/*
+** SQL Function: geopoly_xform(poly, A, B, C, D, E, F)
+**
+** Transform and/or translate a polygon as follows:
+**
+** x1 = A*x0 + B*y0 + E
+** y1 = C*x0 + D*y0 + F
+**
+** For a translation:
+**
+** geopoly_xform(poly, 1, 0, 0, 1, x-offset, y-offset)
+**
+** Rotate by R around the point (0,0):
+**
+** geopoly_xform(poly, cos(R), sin(R), -sin(R), cos(R), 0, 0)
+*/
+static void geopolyXformFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ double A = tdsqlite3_value_double(argv[1]);
+ double B = tdsqlite3_value_double(argv[2]);
+ double C = tdsqlite3_value_double(argv[3]);
+ double D = tdsqlite3_value_double(argv[4]);
+ double E = tdsqlite3_value_double(argv[5]);
+ double F = tdsqlite3_value_double(argv[6]);
+ GeoCoord x1, y1, x0, y0;
+ int ii;
+ if( p ){
+ for(ii=0; ii<p->nVertex; ii++){
+ x0 = GeoX(p,ii);
+ y0 = GeoY(p,ii);
+ x1 = (GeoCoord)(A*x0 + B*y0 + E);
+ y1 = (GeoCoord)(C*x0 + D*y0 + F);
+ GeoX(p,ii) = x1;
+ GeoY(p,ii) = y1;
+ }
+ tdsqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ tdsqlite3_free(p);
+ }
+}
+
+/*
+** Compute the area enclosed by the polygon.
+**
+** This routine can also be used to detect polygons that rotate in
+** the wrong direction. Polygons are suppose to be counter-clockwise (CCW).
+** This routine returns a negative value for clockwise (CW) polygons.
+*/
+static double geopolyArea(GeoPoly *p){
+ double rArea = 0.0;
+ int ii;
+ for(ii=0; ii<p->nVertex-1; ii++){
+ rArea += (GeoX(p,ii) - GeoX(p,ii+1)) /* (x0 - x1) */
+ * (GeoY(p,ii) + GeoY(p,ii+1)) /* (y0 + y1) */
+ * 0.5;
+ }
+ rArea += (GeoX(p,ii) - GeoX(p,0)) /* (xN - x0) */
+ * (GeoY(p,ii) + GeoY(p,0)) /* (yN + y0) */
+ * 0.5;
+ return rArea;
+}
+
+/*
+** Implementation of the geopoly_area(X) function.
+**
+** If the input is a well-formed Geopoly BLOB then return the area
+** enclosed by the polygon. If the polygon circulates clockwise instead
+** of counterclockwise (as it should) then return the negative of the
+** enclosed area. Otherwise return NULL.
+*/
+static void geopolyAreaFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ tdsqlite3_result_double(context, geopolyArea(p));
+ tdsqlite3_free(p);
+ }
+}
+
+/*
+** Implementation of the geopoly_ccw(X) function.
+**
+** If the rotation of polygon X is clockwise (incorrect) instead of
+** counter-clockwise (the correct winding order according to RFC7946)
+** then reverse the order of the vertexes in polygon X.
+**
+** In other words, this routine returns a CCW polygon regardless of the
+** winding order of its input.
+**
+** Use this routine to sanitize historical inputs that that sometimes
+** contain polygons that wind in the wrong direction.
+*/
+static void geopolyCcwFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ if( geopolyArea(p)<0.0 ){
+ int ii, jj;
+ for(ii=1, jj=p->nVertex-1; ii<jj; ii++, jj--){
+ GeoCoord t = GeoX(p,ii);
+ GeoX(p,ii) = GeoX(p,jj);
+ GeoX(p,jj) = t;
+ t = GeoY(p,ii);
+ GeoY(p,ii) = GeoY(p,jj);
+ GeoY(p,jj) = t;
+ }
+ }
+ tdsqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ tdsqlite3_free(p);
+ }
+}
+
+#define GEOPOLY_PI 3.1415926535897932385
+
+/* Fast approximation for sine(X) for X between -0.5*pi and 2*pi
+*/
+static double geopolySine(double r){
+ assert( r>=-0.5*GEOPOLY_PI && r<=2.0*GEOPOLY_PI );
+ if( r>=1.5*GEOPOLY_PI ){
+ r -= 2.0*GEOPOLY_PI;
+ }
+ if( r>=0.5*GEOPOLY_PI ){
+ return -geopolySine(r-GEOPOLY_PI);
+ }else{
+ double r2 = r*r;
+ double r3 = r2*r;
+ double r5 = r3*r2;
+ return 0.9996949*r - 0.1656700*r3 + 0.0075134*r5;
+ }
+}
+
+/*
+** Function: geopoly_regular(X,Y,R,N)
+**
+** Construct a simple, convex, regular polygon centered at X, Y
+** with circumradius R and with N sides.
+*/
+static void geopolyRegularFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ double x = tdsqlite3_value_double(argv[0]);
+ double y = tdsqlite3_value_double(argv[1]);
+ double r = tdsqlite3_value_double(argv[2]);
+ int n = tdsqlite3_value_int(argv[3]);
+ int i;
+ GeoPoly *p;
+
+ if( n<3 || r<=0.0 ) return;
+ if( n>1000 ) n = 1000;
+ p = tdsqlite3_malloc64( sizeof(*p) + (n-1)*2*sizeof(GeoCoord) );
+ if( p==0 ){
+ tdsqlite3_result_error_nomem(context);
+ return;
+ }
+ i = 1;
+ p->hdr[0] = *(unsigned char*)&i;
+ p->hdr[1] = 0;
+ p->hdr[2] = (n>>8)&0xff;
+ p->hdr[3] = n&0xff;
+ for(i=0; i<n; i++){
+ double rAngle = 2.0*GEOPOLY_PI*i/n;
+ GeoX(p,i) = x - r*geopolySine(rAngle-0.5*GEOPOLY_PI);
+ GeoY(p,i) = y + r*geopolySine(rAngle);
+ }
+ tdsqlite3_result_blob(context, p->hdr, 4+8*n, SQLITE_TRANSIENT);
+ tdsqlite3_free(p);
+}
+
+/*
+** If pPoly is a polygon, compute its bounding box. Then:
+**
+** (1) if aCoord!=0 store the bounding box in aCoord, returning NULL
+** (2) otherwise, compute a GeoPoly for the bounding box and return the
+** new GeoPoly
+**
+** If pPoly is NULL but aCoord is not NULL, then compute a new GeoPoly from
+** the bounding box in aCoord and return a pointer to that GeoPoly.
+*/
+static GeoPoly *geopolyBBox(
+ tdsqlite3_context *context, /* For recording the error */
+ tdsqlite3_value *pPoly, /* The polygon */
+ RtreeCoord *aCoord, /* Results here */
+ int *pRc /* Error code here */
+){
+ GeoPoly *pOut = 0;
+ GeoPoly *p;
+ float mnX, mxX, mnY, mxY;
+ if( pPoly==0 && aCoord!=0 ){
+ p = 0;
+ mnX = aCoord[0].f;
+ mxX = aCoord[1].f;
+ mnY = aCoord[2].f;
+ mxY = aCoord[3].f;
+ goto geopolyBboxFill;
}else{
- u8 *zBlob = (u8 *)sqlite3_value_blob(apArg[0]);
- sqlite3_result_int(ctx, readInt16(zBlob));
+ p = geopolyFuncParam(context, pPoly, pRc);
+ }
+ if( p ){
+ int ii;
+ mnX = mxX = GeoX(p,0);
+ mnY = mxY = GeoY(p,0);
+ for(ii=1; ii<p->nVertex; ii++){
+ double r = GeoX(p,ii);
+ if( r<mnX ) mnX = (float)r;
+ else if( r>mxX ) mxX = (float)r;
+ r = GeoY(p,ii);
+ if( r<mnY ) mnY = (float)r;
+ else if( r>mxY ) mxY = (float)r;
+ }
+ if( pRc ) *pRc = SQLITE_OK;
+ if( aCoord==0 ){
+ geopolyBboxFill:
+ pOut = tdsqlite3_realloc64(p, GEOPOLY_SZ(4));
+ if( pOut==0 ){
+ tdsqlite3_free(p);
+ if( context ) tdsqlite3_result_error_nomem(context);
+ if( pRc ) *pRc = SQLITE_NOMEM;
+ return 0;
+ }
+ pOut->nVertex = 4;
+ ii = 1;
+ pOut->hdr[0] = *(unsigned char*)&ii;
+ pOut->hdr[1] = 0;
+ pOut->hdr[2] = 0;
+ pOut->hdr[3] = 4;
+ GeoX(pOut,0) = mnX;
+ GeoY(pOut,0) = mnY;
+ GeoX(pOut,1) = mxX;
+ GeoY(pOut,1) = mnY;
+ GeoX(pOut,2) = mxX;
+ GeoY(pOut,2) = mxY;
+ GeoX(pOut,3) = mnX;
+ GeoY(pOut,3) = mxY;
+ }else{
+ tdsqlite3_free(p);
+ aCoord[0].f = mnX;
+ aCoord[1].f = mxX;
+ aCoord[2].f = mnY;
+ aCoord[3].f = mxY;
+ }
+ }
+ return pOut;
+}
+
+/*
+** Implementation of the geopoly_bbox(X) SQL function.
+*/
+static void geopolyBBoxFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p = geopolyBBox(context, argv[0], 0, 0);
+ if( p ){
+ tdsqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ tdsqlite3_free(p);
+ }
+}
+
+/*
+** State vector for the geopoly_group_bbox() aggregate function.
+*/
+typedef struct GeoBBox GeoBBox;
+struct GeoBBox {
+ int isInit;
+ RtreeCoord a[4];
+};
+
+
+/*
+** Implementation of the geopoly_group_bbox(X) aggregate SQL function.
+*/
+static void geopolyBBoxStep(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ RtreeCoord a[4];
+ int rc = SQLITE_OK;
+ (void)geopolyBBox(context, argv[0], a, &rc);
+ if( rc==SQLITE_OK ){
+ GeoBBox *pBBox;
+ pBBox = (GeoBBox*)tdsqlite3_aggregate_context(context, sizeof(*pBBox));
+ if( pBBox==0 ) return;
+ if( pBBox->isInit==0 ){
+ pBBox->isInit = 1;
+ memcpy(pBBox->a, a, sizeof(RtreeCoord)*4);
+ }else{
+ if( a[0].f < pBBox->a[0].f ) pBBox->a[0] = a[0];
+ if( a[1].f > pBBox->a[1].f ) pBBox->a[1] = a[1];
+ if( a[2].f < pBBox->a[2].f ) pBBox->a[2] = a[2];
+ if( a[3].f > pBBox->a[3].f ) pBBox->a[3] = a[3];
+ }
+ }
+}
+static void geopolyBBoxFinal(
+ tdsqlite3_context *context
+){
+ GeoPoly *p;
+ GeoBBox *pBBox;
+ pBBox = (GeoBBox*)tdsqlite3_aggregate_context(context, 0);
+ if( pBBox==0 ) return;
+ p = geopolyBBox(context, 0, pBBox->a, 0);
+ if( p ){
+ tdsqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ tdsqlite3_free(p);
+ }
+}
+
+
+/*
+** Determine if point (x0,y0) is beneath line segment (x1,y1)->(x2,y2).
+** Returns:
+**
+** +2 x0,y0 is on the line segement
+**
+** +1 x0,y0 is beneath line segment
+**
+** 0 x0,y0 is not on or beneath the line segment or the line segment
+** is vertical and x0,y0 is not on the line segment
+**
+** The left-most coordinate min(x1,x2) is not considered to be part of
+** the line segment for the purposes of this analysis.
+*/
+static int pointBeneathLine(
+ double x0, double y0,
+ double x1, double y1,
+ double x2, double y2
+){
+ double y;
+ if( x0==x1 && y0==y1 ) return 2;
+ if( x1<x2 ){
+ if( x0<=x1 || x0>x2 ) return 0;
+ }else if( x1>x2 ){
+ if( x0<=x2 || x0>x1 ) return 0;
+ }else{
+ /* Vertical line segment */
+ if( x0!=x1 ) return 0;
+ if( y0<y1 && y0<y2 ) return 0;
+ if( y0>y1 && y0>y2 ) return 0;
+ return 2;
+ }
+ y = y1 + (y2-y1)*(x0-x1)/(x2-x1);
+ if( y0==y ) return 2;
+ if( y0<y ) return 1;
+ return 0;
+}
+
+/*
+** SQL function: geopoly_contains_point(P,X,Y)
+**
+** Return +2 if point X,Y is within polygon P.
+** Return +1 if point X,Y is on the polygon boundary.
+** Return 0 if point X,Y is outside the polygon
+*/
+static void geopolyContainsPointFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0);
+ double x0 = tdsqlite3_value_double(argv[1]);
+ double y0 = tdsqlite3_value_double(argv[2]);
+ int v = 0;
+ int cnt = 0;
+ int ii;
+ if( p1==0 ) return;
+ for(ii=0; ii<p1->nVertex-1; ii++){
+ v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii),
+ GeoX(p1,ii+1),GeoY(p1,ii+1));
+ if( v==2 ) break;
+ cnt += v;
+ }
+ if( v!=2 ){
+ v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii),
+ GeoX(p1,0), GeoY(p1,0));
+ }
+ if( v==2 ){
+ tdsqlite3_result_int(context, 1);
+ }else if( ((v+cnt)&1)==0 ){
+ tdsqlite3_result_int(context, 0);
+ }else{
+ tdsqlite3_result_int(context, 2);
+ }
+ tdsqlite3_free(p1);
+}
+
+/* Forward declaration */
+static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2);
+
+/*
+** SQL function: geopoly_within(P1,P2)
+**
+** Return +2 if P1 and P2 are the same polygon
+** Return +1 if P2 is contained within P1
+** Return 0 if any part of P2 is on the outside of P1
+**
+*/
+static void geopolyWithinFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0);
+ GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0);
+ if( p1 && p2 ){
+ int x = geopolyOverlap(p1, p2);
+ if( x<0 ){
+ tdsqlite3_result_error_nomem(context);
+ }else{
+ tdsqlite3_result_int(context, x==2 ? 1 : x==4 ? 2 : 0);
+ }
+ }
+ tdsqlite3_free(p1);
+ tdsqlite3_free(p2);
+}
+
+/* Objects used by the overlap algorihm. */
+typedef struct GeoEvent GeoEvent;
+typedef struct GeoSegment GeoSegment;
+typedef struct GeoOverlap GeoOverlap;
+struct GeoEvent {
+ double x; /* X coordinate at which event occurs */
+ int eType; /* 0 for ADD, 1 for REMOVE */
+ GeoSegment *pSeg; /* The segment to be added or removed */
+ GeoEvent *pNext; /* Next event in the sorted list */
+};
+struct GeoSegment {
+ double C, B; /* y = C*x + B */
+ double y; /* Current y value */
+ float y0; /* Initial y value */
+ unsigned char side; /* 1 for p1, 2 for p2 */
+ unsigned int idx; /* Which segment within the side */
+ GeoSegment *pNext; /* Next segment in a list sorted by y */
+};
+struct GeoOverlap {
+ GeoEvent *aEvent; /* Array of all events */
+ GeoSegment *aSegment; /* Array of all segments */
+ int nEvent; /* Number of events */
+ int nSegment; /* Number of segments */
+};
+
+/*
+** Add a single segment and its associated events.
+*/
+static void geopolyAddOneSegment(
+ GeoOverlap *p,
+ GeoCoord x0,
+ GeoCoord y0,
+ GeoCoord x1,
+ GeoCoord y1,
+ unsigned char side,
+ unsigned int idx
+){
+ GeoSegment *pSeg;
+ GeoEvent *pEvent;
+ if( x0==x1 ) return; /* Ignore vertical segments */
+ if( x0>x1 ){
+ GeoCoord t = x0;
+ x0 = x1;
+ x1 = t;
+ t = y0;
+ y0 = y1;
+ y1 = t;
+ }
+ pSeg = p->aSegment + p->nSegment;
+ p->nSegment++;
+ pSeg->C = (y1-y0)/(x1-x0);
+ pSeg->B = y1 - x1*pSeg->C;
+ pSeg->y0 = y0;
+ pSeg->side = side;
+ pSeg->idx = idx;
+ pEvent = p->aEvent + p->nEvent;
+ p->nEvent++;
+ pEvent->x = x0;
+ pEvent->eType = 0;
+ pEvent->pSeg = pSeg;
+ pEvent = p->aEvent + p->nEvent;
+ p->nEvent++;
+ pEvent->x = x1;
+ pEvent->eType = 1;
+ pEvent->pSeg = pSeg;
+}
+
+
+
+/*
+** Insert all segments and events for polygon pPoly.
+*/
+static void geopolyAddSegments(
+ GeoOverlap *p, /* Add segments to this Overlap object */
+ GeoPoly *pPoly, /* Take all segments from this polygon */
+ unsigned char side /* The side of pPoly */
+){
+ unsigned int i;
+ GeoCoord *x;
+ for(i=0; i<(unsigned)pPoly->nVertex-1; i++){
+ x = &GeoX(pPoly,i);
+ geopolyAddOneSegment(p, x[0], x[1], x[2], x[3], side, i);
+ }
+ x = &GeoX(pPoly,i);
+ geopolyAddOneSegment(p, x[0], x[1], pPoly->a[0], pPoly->a[1], side, i);
+}
+
+/*
+** Merge two lists of sorted events by X coordinate
+*/
+static GeoEvent *geopolyEventMerge(GeoEvent *pLeft, GeoEvent *pRight){
+ GeoEvent head, *pLast;
+ head.pNext = 0;
+ pLast = &head;
+ while( pRight && pLeft ){
+ if( pRight->x <= pLeft->x ){
+ pLast->pNext = pRight;
+ pLast = pRight;
+ pRight = pRight->pNext;
+ }else{
+ pLast->pNext = pLeft;
+ pLast = pLeft;
+ pLeft = pLeft->pNext;
+ }
+ }
+ pLast->pNext = pRight ? pRight : pLeft;
+ return head.pNext;
+}
+
+/*
+** Sort an array of nEvent event objects into a list.
+*/
+static GeoEvent *geopolySortEventsByX(GeoEvent *aEvent, int nEvent){
+ int mx = 0;
+ int i, j;
+ GeoEvent *p;
+ GeoEvent *a[50];
+ for(i=0; i<nEvent; i++){
+ p = &aEvent[i];
+ p->pNext = 0;
+ for(j=0; j<mx && a[j]; j++){
+ p = geopolyEventMerge(a[j], p);
+ a[j] = 0;
+ }
+ a[j] = p;
+ if( j>=mx ) mx = j+1;
+ }
+ p = 0;
+ for(i=0; i<mx; i++){
+ p = geopolyEventMerge(a[i], p);
+ }
+ return p;
+}
+
+/*
+** Merge two lists of sorted segments by Y, and then by C.
+*/
+static GeoSegment *geopolySegmentMerge(GeoSegment *pLeft, GeoSegment *pRight){
+ GeoSegment head, *pLast;
+ head.pNext = 0;
+ pLast = &head;
+ while( pRight && pLeft ){
+ double r = pRight->y - pLeft->y;
+ if( r==0.0 ) r = pRight->C - pLeft->C;
+ if( r<0.0 ){
+ pLast->pNext = pRight;
+ pLast = pRight;
+ pRight = pRight->pNext;
+ }else{
+ pLast->pNext = pLeft;
+ pLast = pLeft;
+ pLeft = pLeft->pNext;
+ }
+ }
+ pLast->pNext = pRight ? pRight : pLeft;
+ return head.pNext;
+}
+
+/*
+** Sort a list of GeoSegments in order of increasing Y and in the event of
+** a tie, increasing C (slope).
+*/
+static GeoSegment *geopolySortSegmentsByYAndC(GeoSegment *pList){
+ int mx = 0;
+ int i;
+ GeoSegment *p;
+ GeoSegment *a[50];
+ while( pList ){
+ p = pList;
+ pList = pList->pNext;
+ p->pNext = 0;
+ for(i=0; i<mx && a[i]; i++){
+ p = geopolySegmentMerge(a[i], p);
+ a[i] = 0;
+ }
+ a[i] = p;
+ if( i>=mx ) mx = i+1;
+ }
+ p = 0;
+ for(i=0; i<mx; i++){
+ p = geopolySegmentMerge(a[i], p);
+ }
+ return p;
+}
+
+/*
+** Determine the overlap between two polygons
+*/
+static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2){
+ tdsqlite3_int64 nVertex = p1->nVertex + p2->nVertex + 2;
+ GeoOverlap *p;
+ tdsqlite3_int64 nByte;
+ GeoEvent *pThisEvent;
+ double rX;
+ int rc = 0;
+ int needSort = 0;
+ GeoSegment *pActive = 0;
+ GeoSegment *pSeg;
+ unsigned char aOverlap[4];
+
+ nByte = sizeof(GeoEvent)*nVertex*2
+ + sizeof(GeoSegment)*nVertex
+ + sizeof(GeoOverlap);
+ p = tdsqlite3_malloc64( nByte );
+ if( p==0 ) return -1;
+ p->aEvent = (GeoEvent*)&p[1];
+ p->aSegment = (GeoSegment*)&p->aEvent[nVertex*2];
+ p->nEvent = p->nSegment = 0;
+ geopolyAddSegments(p, p1, 1);
+ geopolyAddSegments(p, p2, 2);
+ pThisEvent = geopolySortEventsByX(p->aEvent, p->nEvent);
+ rX = pThisEvent->x==0.0 ? -1.0 : 0.0;
+ memset(aOverlap, 0, sizeof(aOverlap));
+ while( pThisEvent ){
+ if( pThisEvent->x!=rX ){
+ GeoSegment *pPrev = 0;
+ int iMask = 0;
+ GEODEBUG(("Distinct X: %g\n", pThisEvent->x));
+ rX = pThisEvent->x;
+ if( needSort ){
+ GEODEBUG(("SORT\n"));
+ pActive = geopolySortSegmentsByYAndC(pActive);
+ needSort = 0;
+ }
+ for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){
+ if( pPrev ){
+ if( pPrev->y!=pSeg->y ){
+ GEODEBUG(("MASK: %d\n", iMask));
+ aOverlap[iMask] = 1;
+ }
+ }
+ iMask ^= pSeg->side;
+ pPrev = pSeg;
+ }
+ pPrev = 0;
+ for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){
+ double y = pSeg->C*rX + pSeg->B;
+ GEODEBUG(("Segment %d.%d %g->%g\n", pSeg->side, pSeg->idx, pSeg->y, y));
+ pSeg->y = y;
+ if( pPrev ){
+ if( pPrev->y>pSeg->y && pPrev->side!=pSeg->side ){
+ rc = 1;
+ GEODEBUG(("Crossing: %d.%d and %d.%d\n",
+ pPrev->side, pPrev->idx,
+ pSeg->side, pSeg->idx));
+ goto geopolyOverlapDone;
+ }else if( pPrev->y!=pSeg->y ){
+ GEODEBUG(("MASK: %d\n", iMask));
+ aOverlap[iMask] = 1;
+ }
+ }
+ iMask ^= pSeg->side;
+ pPrev = pSeg;
+ }
+ }
+ GEODEBUG(("%s %d.%d C=%g B=%g\n",
+ pThisEvent->eType ? "RM " : "ADD",
+ pThisEvent->pSeg->side, pThisEvent->pSeg->idx,
+ pThisEvent->pSeg->C,
+ pThisEvent->pSeg->B));
+ if( pThisEvent->eType==0 ){
+ /* Add a segment */
+ pSeg = pThisEvent->pSeg;
+ pSeg->y = pSeg->y0;
+ pSeg->pNext = pActive;
+ pActive = pSeg;
+ needSort = 1;
+ }else{
+ /* Remove a segment */
+ if( pActive==pThisEvent->pSeg ){
+ pActive = pActive->pNext;
+ }else{
+ for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){
+ if( pSeg->pNext==pThisEvent->pSeg ){
+ pSeg->pNext = pSeg->pNext->pNext;
+ break;
+ }
+ }
+ }
+ }
+ pThisEvent = pThisEvent->pNext;
+ }
+ if( aOverlap[3]==0 ){
+ rc = 0;
+ }else if( aOverlap[1]!=0 && aOverlap[2]==0 ){
+ rc = 3;
+ }else if( aOverlap[1]==0 && aOverlap[2]!=0 ){
+ rc = 2;
+ }else if( aOverlap[1]==0 && aOverlap[2]==0 ){
+ rc = 4;
+ }else{
+ rc = 1;
+ }
+
+geopolyOverlapDone:
+ tdsqlite3_free(p);
+ return rc;
+}
+
+/*
+** SQL function: geopoly_overlap(P1,P2)
+**
+** Determine whether or not P1 and P2 overlap. Return value:
+**
+** 0 The two polygons are disjoint
+** 1 They overlap
+** 2 P1 is completely contained within P2
+** 3 P2 is completely contained within P1
+** 4 P1 and P2 are the same polygon
+** NULL Either P1 or P2 or both are not valid polygons
+*/
+static void geopolyOverlapFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0);
+ GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0);
+ if( p1 && p2 ){
+ int x = geopolyOverlap(p1, p2);
+ if( x<0 ){
+ tdsqlite3_result_error_nomem(context);
+ }else{
+ tdsqlite3_result_int(context, x);
+ }
+ }
+ tdsqlite3_free(p1);
+ tdsqlite3_free(p2);
+}
+
+/*
+** Enable or disable debugging output
+*/
+static void geopolyDebugFunc(
+ tdsqlite3_context *context,
+ int argc,
+ tdsqlite3_value **argv
+){
+#ifdef GEOPOLY_ENABLE_DEBUG
+ geo_debug = tdsqlite3_value_int(argv[0]);
+#endif
+}
+
+/*
+** This function is the implementation of both the xConnect and xCreate
+** methods of the geopoly virtual table.
+**
+** argv[0] -> module name
+** argv[1] -> database name
+** argv[2] -> table name
+** argv[...] -> column names...
+*/
+static int geopolyInit(
+ tdsqlite3 *db, /* Database connection */
+ void *pAux, /* One of the RTREE_COORD_* constants */
+ int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */
+ tdsqlite3_vtab **ppVtab, /* OUT: New virtual table */
+ char **pzErr, /* OUT: Error message, if any */
+ int isCreate /* True for xCreate, false for xConnect */
+){
+ int rc = SQLITE_OK;
+ Rtree *pRtree;
+ tdsqlite3_int64 nDb; /* Length of string argv[1] */
+ tdsqlite3_int64 nName; /* Length of string argv[2] */
+ tdsqlite3_str *pSql;
+ char *zSql;
+ int ii;
+
+ tdsqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+
+ /* Allocate the tdsqlite3_vtab structure */
+ nDb = strlen(argv[1]);
+ nName = strlen(argv[2]);
+ pRtree = (Rtree *)tdsqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
+ if( !pRtree ){
+ return SQLITE_NOMEM;
+ }
+ memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
+ pRtree->nBusy = 1;
+ pRtree->base.pModule = &rtreeModule;
+ pRtree->zDb = (char *)&pRtree[1];
+ pRtree->zName = &pRtree->zDb[nDb+1];
+ pRtree->eCoordType = RTREE_COORD_REAL32;
+ pRtree->nDim = 2;
+ pRtree->nDim2 = 4;
+ memcpy(pRtree->zDb, argv[1], nDb);
+ memcpy(pRtree->zName, argv[2], nName);
+
+
+ /* Create/Connect to the underlying relational database schema. If
+ ** that is successful, call tdsqlite3_declare_vtab() to configure
+ ** the r-tree table schema.
+ */
+ pSql = tdsqlite3_str_new(db);
+ tdsqlite3_str_appendf(pSql, "CREATE TABLE x(_shape");
+ pRtree->nAux = 1; /* Add one for _shape */
+ pRtree->nAuxNotNull = 1; /* The _shape column is always not-null */
+ for(ii=3; ii<argc; ii++){
+ pRtree->nAux++;
+ tdsqlite3_str_appendf(pSql, ",%s", argv[ii]);
+ }
+ tdsqlite3_str_appendf(pSql, ");");
+ zSql = tdsqlite3_str_finish(pSql);
+ if( !zSql ){
+ rc = SQLITE_NOMEM;
+ }else if( SQLITE_OK!=(rc = tdsqlite3_declare_vtab(db, zSql)) ){
+ *pzErr = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
+ }
+ tdsqlite3_free(zSql);
+ if( rc ) goto geopolyInit_fail;
+ pRtree->nBytesPerCell = 8 + pRtree->nDim2*4;
+
+ /* Figure out the node size to use. */
+ rc = getNodeSize(db, pRtree, isCreate, pzErr);
+ if( rc ) goto geopolyInit_fail;
+ rc = rtreeSqlInit(pRtree, db, argv[1], argv[2], isCreate);
+ if( rc ){
+ *pzErr = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
+ goto geopolyInit_fail;
+ }
+
+ *ppVtab = (tdsqlite3_vtab *)pRtree;
+ return SQLITE_OK;
+
+geopolyInit_fail:
+ if( rc==SQLITE_OK ) rc = SQLITE_ERROR;
+ assert( *ppVtab==0 );
+ assert( pRtree->nBusy==1 );
+ rtreeRelease(pRtree);
+ return rc;
+}
+
+
+/*
+** GEOPOLY virtual table module xCreate method.
+*/
+static int geopolyCreate(
+ tdsqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ tdsqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ return geopolyInit(db, pAux, argc, argv, ppVtab, pzErr, 1);
+}
+
+/*
+** GEOPOLY virtual table module xConnect method.
+*/
+static int geopolyConnect(
+ tdsqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ tdsqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ return geopolyInit(db, pAux, argc, argv, ppVtab, pzErr, 0);
+}
+
+
+/*
+** GEOPOLY virtual table module xFilter method.
+**
+** Query plans:
+**
+** 1 rowid lookup
+** 2 search for objects overlapping the same bounding box
+** that contains polygon argv[0]
+** 3 search for objects overlapping the same bounding box
+** that contains polygon argv[0]
+** 4 full table scan
+*/
+static int geopolyFilter(
+ tdsqlite3_vtab_cursor *pVtabCursor, /* The cursor to initialize */
+ int idxNum, /* Query plan */
+ const char *idxStr, /* Not Used */
+ int argc, tdsqlite3_value **argv /* Parameters to the query plan */
+){
+ Rtree *pRtree = (Rtree *)pVtabCursor->pVtab;
+ RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
+ RtreeNode *pRoot = 0;
+ int rc = SQLITE_OK;
+ int iCell = 0;
+
+ rtreeReference(pRtree);
+
+ /* Reset the cursor to the same state as rtreeOpen() leaves it in. */
+ resetCursor(pCsr);
+
+ pCsr->iStrategy = idxNum;
+ if( idxNum==1 ){
+ /* Special case - lookup by rowid. */
+ RtreeNode *pLeaf; /* Leaf on which the required cell resides */
+ RtreeSearchPoint *p; /* Search point for the leaf */
+ i64 iRowid = tdsqlite3_value_int64(argv[0]);
+ i64 iNode = 0;
+ rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode);
+ if( rc==SQLITE_OK && pLeaf!=0 ){
+ p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0);
+ assert( p!=0 ); /* Always returns pCsr->sPoint */
+ pCsr->aNode[0] = pLeaf;
+ p->id = iNode;
+ p->eWithin = PARTLY_WITHIN;
+ rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &iCell);
+ p->iCell = (u8)iCell;
+ RTREE_QUEUE_TRACE(pCsr, "PUSH-F1:");
+ }else{
+ pCsr->atEOF = 1;
+ }
+ }else{
+ /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array
+ ** with the configured constraints.
+ */
+ rc = nodeAcquire(pRtree, 1, 0, &pRoot);
+ if( rc==SQLITE_OK && idxNum<=3 ){
+ RtreeCoord bbox[4];
+ RtreeConstraint *p;
+ assert( argc==1 );
+ geopolyBBox(0, argv[0], bbox, &rc);
+ if( rc ){
+ goto geopoly_filter_end;
+ }
+ pCsr->aConstraint = p = tdsqlite3_malloc(sizeof(RtreeConstraint)*4);
+ pCsr->nConstraint = 4;
+ if( p==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*4);
+ memset(pCsr->anQueue, 0, sizeof(u32)*(pRtree->iDepth + 1));
+ if( idxNum==2 ){
+ /* Overlap query */
+ p->op = 'B';
+ p->iCoord = 0;
+ p->u.rValue = bbox[1].f;
+ p++;
+ p->op = 'D';
+ p->iCoord = 1;
+ p->u.rValue = bbox[0].f;
+ p++;
+ p->op = 'B';
+ p->iCoord = 2;
+ p->u.rValue = bbox[3].f;
+ p++;
+ p->op = 'D';
+ p->iCoord = 3;
+ p->u.rValue = bbox[2].f;
+ }else{
+ /* Within query */
+ p->op = 'D';
+ p->iCoord = 0;
+ p->u.rValue = bbox[0].f;
+ p++;
+ p->op = 'B';
+ p->iCoord = 1;
+ p->u.rValue = bbox[1].f;
+ p++;
+ p->op = 'D';
+ p->iCoord = 2;
+ p->u.rValue = bbox[2].f;
+ p++;
+ p->op = 'B';
+ p->iCoord = 3;
+ p->u.rValue = bbox[3].f;
+ }
+ }
+ }
+ if( rc==SQLITE_OK ){
+ RtreeSearchPoint *pNew;
+ pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1));
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ goto geopoly_filter_end;
+ }
+ pNew->id = 1;
+ pNew->iCell = 0;
+ pNew->eWithin = PARTLY_WITHIN;
+ assert( pCsr->bPoint==1 );
+ pCsr->aNode[0] = pRoot;
+ pRoot = 0;
+ RTREE_QUEUE_TRACE(pCsr, "PUSH-Fm:");
+ rc = rtreeStepToLeaf(pCsr);
+ }
+ }
+
+geopoly_filter_end:
+ nodeRelease(pRtree, pRoot);
+ rtreeRelease(pRtree);
+ return rc;
+}
+
+/*
+** Rtree virtual table module xBestIndex method. There are three
+** table scan strategies to choose from (in order from most to
+** least desirable):
+**
+** idxNum idxStr Strategy
+** ------------------------------------------------
+** 1 "rowid" Direct lookup by rowid.
+** 2 "rtree" R-tree overlap query using geopoly_overlap()
+** 3 "rtree" R-tree within query using geopoly_within()
+** 4 "fullscan" full-table scan.
+** ------------------------------------------------
+*/
+static int geopolyBestIndex(tdsqlite3_vtab *tab, tdsqlite3_index_info *pIdxInfo){
+ int ii;
+ int iRowidTerm = -1;
+ int iFuncTerm = -1;
+ int idxNum = 0;
+
+ for(ii=0; ii<pIdxInfo->nConstraint; ii++){
+ struct tdsqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii];
+ if( !p->usable ) continue;
+ if( p->iColumn<0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ iRowidTerm = ii;
+ break;
+ }
+ if( p->iColumn==0 && p->op>=SQLITE_INDEX_CONSTRAINT_FUNCTION ){
+ /* p->op==SQLITE_INDEX_CONSTRAINT_FUNCTION for geopoly_overlap()
+ ** p->op==(SQLITE_INDEX_CONTRAINT_FUNCTION+1) for geopoly_within().
+ ** See geopolyFindFunction() */
+ iFuncTerm = ii;
+ idxNum = p->op - SQLITE_INDEX_CONSTRAINT_FUNCTION + 2;
+ }
+ }
+
+ if( iRowidTerm>=0 ){
+ pIdxInfo->idxNum = 1;
+ pIdxInfo->idxStr = "rowid";
+ pIdxInfo->aConstraintUsage[iRowidTerm].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[iRowidTerm].omit = 1;
+ pIdxInfo->estimatedCost = 30.0;
+ pIdxInfo->estimatedRows = 1;
+ pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE;
+ return SQLITE_OK;
+ }
+ if( iFuncTerm>=0 ){
+ pIdxInfo->idxNum = idxNum;
+ pIdxInfo->idxStr = "rtree";
+ pIdxInfo->aConstraintUsage[iFuncTerm].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[iFuncTerm].omit = 0;
+ pIdxInfo->estimatedCost = 300.0;
+ pIdxInfo->estimatedRows = 10;
+ return SQLITE_OK;
+ }
+ pIdxInfo->idxNum = 4;
+ pIdxInfo->idxStr = "fullscan";
+ pIdxInfo->estimatedCost = 3000000.0;
+ pIdxInfo->estimatedRows = 100000;
+ return SQLITE_OK;
+}
+
+
+/*
+** GEOPOLY virtual table module xColumn method.
+*/
+static int geopolyColumn(tdsqlite3_vtab_cursor *cur, tdsqlite3_context *ctx, int i){
+ Rtree *pRtree = (Rtree *)cur->pVtab;
+ RtreeCursor *pCsr = (RtreeCursor *)cur;
+ RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr);
+ int rc = SQLITE_OK;
+ RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
+
+ if( rc ) return rc;
+ if( p==0 ) return SQLITE_OK;
+ if( i==0 && tdsqlite3_vtab_nochange(ctx) ) return SQLITE_OK;
+ if( i<=pRtree->nAux ){
+ if( !pCsr->bAuxValid ){
+ if( pCsr->pReadAux==0 ){
+ rc = tdsqlite3_prepare_v3(pRtree->db, pRtree->zReadAuxSql, -1, 0,
+ &pCsr->pReadAux, 0);
+ if( rc ) return rc;
+ }
+ tdsqlite3_bind_int64(pCsr->pReadAux, 1,
+ nodeGetRowid(pRtree, pNode, p->iCell));
+ rc = tdsqlite3_step(pCsr->pReadAux);
+ if( rc==SQLITE_ROW ){
+ pCsr->bAuxValid = 1;
+ }else{
+ tdsqlite3_reset(pCsr->pReadAux);
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ return rc;
+ }
+ }
+ tdsqlite3_result_value(ctx, tdsqlite3_column_value(pCsr->pReadAux, i+2));
}
+ return SQLITE_OK;
}
+
+/*
+** The xUpdate method for GEOPOLY module virtual tables.
+**
+** For DELETE:
+**
+** argv[0] = the rowid to be deleted
+**
+** For INSERT:
+**
+** argv[0] = SQL NULL
+** argv[1] = rowid to insert, or an SQL NULL to select automatically
+** argv[2] = _shape column
+** argv[3] = first application-defined column....
+**
+** For UPDATE:
+**
+** argv[0] = rowid to modify. Never NULL
+** argv[1] = rowid after the change. Never NULL
+** argv[2] = new value for _shape
+** argv[3] = new value for first application-defined column....
+*/
+static int geopolyUpdate(
+ tdsqlite3_vtab *pVtab,
+ int nData,
+ tdsqlite3_value **aData,
+ sqlite_int64 *pRowid
+){
+ Rtree *pRtree = (Rtree *)pVtab;
+ int rc = SQLITE_OK;
+ RtreeCell cell; /* New cell to insert if nData>1 */
+ i64 oldRowid; /* The old rowid */
+ int oldRowidValid; /* True if oldRowid is valid */
+ i64 newRowid; /* The new rowid */
+ int newRowidValid; /* True if newRowid is valid */
+ int coordChange = 0; /* Change in coordinates */
+
+ if( pRtree->nNodeRef ){
+ /* Unable to write to the btree while another cursor is reading from it,
+ ** since the write might do a rebalance which would disrupt the read
+ ** cursor. */
+ return SQLITE_LOCKED_VTAB;
+ }
+ rtreeReference(pRtree);
+ assert(nData>=1);
+
+ oldRowidValid = tdsqlite3_value_type(aData[0])!=SQLITE_NULL;;
+ oldRowid = oldRowidValid ? tdsqlite3_value_int64(aData[0]) : 0;
+ newRowidValid = nData>1 && tdsqlite3_value_type(aData[1])!=SQLITE_NULL;
+ newRowid = newRowidValid ? tdsqlite3_value_int64(aData[1]) : 0;
+ cell.iRowid = newRowid;
+
+ if( nData>1 /* not a DELETE */
+ && (!oldRowidValid /* INSERT */
+ || !tdsqlite3_value_nochange(aData[2]) /* UPDATE _shape */
+ || oldRowid!=newRowid) /* Rowid change */
+ ){
+ geopolyBBox(0, aData[2], cell.aCoord, &rc);
+ if( rc ){
+ if( rc==SQLITE_ERROR ){
+ pVtab->zErrMsg =
+ tdsqlite3_mprintf("_shape does not contain a valid polygon");
+ }
+ goto geopoly_update_end;
+ }
+ coordChange = 1;
+
+ /* If a rowid value was supplied, check if it is already present in
+ ** the table. If so, the constraint has failed. */
+ if( newRowidValid && (!oldRowidValid || oldRowid!=newRowid) ){
+ int steprc;
+ tdsqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
+ steprc = tdsqlite3_step(pRtree->pReadRowid);
+ rc = tdsqlite3_reset(pRtree->pReadRowid);
+ if( SQLITE_ROW==steprc ){
+ if( tdsqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){
+ rc = rtreeDeleteRowid(pRtree, cell.iRowid);
+ }else{
+ rc = rtreeConstraintError(pRtree, 0);
+ }
+ }
+ }
+ }
+
+ /* If aData[0] is not an SQL NULL value, it is the rowid of a
+ ** record to delete from the r-tree table. The following block does
+ ** just that.
+ */
+ if( rc==SQLITE_OK && (nData==1 || (coordChange && oldRowidValid)) ){
+ rc = rtreeDeleteRowid(pRtree, oldRowid);
+ }
+
+ /* If the aData[] array contains more than one element, elements
+ ** (aData[2]..aData[argc-1]) contain a new record to insert into
+ ** the r-tree structure.
+ */
+ if( rc==SQLITE_OK && nData>1 && coordChange ){
+ /* Insert the new record into the r-tree */
+ RtreeNode *pLeaf = 0;
+ if( !newRowidValid ){
+ rc = rtreeNewRowid(pRtree, &cell.iRowid);
+ }
+ *pRowid = cell.iRowid;
+ if( rc==SQLITE_OK ){
+ rc = ChooseLeaf(pRtree, &cell, 0, &pLeaf);
+ }
+ if( rc==SQLITE_OK ){
+ int rc2;
+ pRtree->iReinsertHeight = -1;
+ rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
+ rc2 = nodeRelease(pRtree, pLeaf);
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ }
+ }
+
+ /* Change the data */
+ if( rc==SQLITE_OK && nData>1 ){
+ tdsqlite3_stmt *pUp = pRtree->pWriteAux;
+ int jj;
+ int nChange = 0;
+ tdsqlite3_bind_int64(pUp, 1, cell.iRowid);
+ assert( pRtree->nAux>=1 );
+ if( tdsqlite3_value_nochange(aData[2]) ){
+ tdsqlite3_bind_null(pUp, 2);
+ }else{
+ GeoPoly *p = 0;
+ if( tdsqlite3_value_type(aData[2])==SQLITE_TEXT
+ && (p = geopolyFuncParam(0, aData[2], &rc))!=0
+ && rc==SQLITE_OK
+ ){
+ tdsqlite3_bind_blob(pUp, 2, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT);
+ }else{
+ tdsqlite3_bind_value(pUp, 2, aData[2]);
+ }
+ tdsqlite3_free(p);
+ nChange = 1;
+ }
+ for(jj=1; jj<pRtree->nAux; jj++){
+ nChange++;
+ tdsqlite3_bind_value(pUp, jj+2, aData[jj+2]);
+ }
+ if( nChange ){
+ tdsqlite3_step(pUp);
+ rc = tdsqlite3_reset(pUp);
+ }
+ }
+
+geopoly_update_end:
+ rtreeRelease(pRtree);
+ return rc;
+}
+
+/*
+** Report that geopoly_overlap() is an overloaded function suitable
+** for use in xBestIndex.
+*/
+static int geopolyFindFunction(
+ tdsqlite3_vtab *pVtab,
+ int nArg,
+ const char *zName,
+ void (**pxFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void **ppArg
+){
+ if( tdsqlite3_stricmp(zName, "geopoly_overlap")==0 ){
+ *pxFunc = geopolyOverlapFunc;
+ *ppArg = 0;
+ return SQLITE_INDEX_CONSTRAINT_FUNCTION;
+ }
+ if( tdsqlite3_stricmp(zName, "geopoly_within")==0 ){
+ *pxFunc = geopolyWithinFunc;
+ *ppArg = 0;
+ return SQLITE_INDEX_CONSTRAINT_FUNCTION+1;
+ }
+ return 0;
+}
+
+
+static tdsqlite3_module geopolyModule = {
+ 3, /* iVersion */
+ geopolyCreate, /* xCreate - create a table */
+ geopolyConnect, /* xConnect - connect to an existing table */
+ geopolyBestIndex, /* xBestIndex - Determine search strategy */
+ rtreeDisconnect, /* xDisconnect - Disconnect from a table */
+ rtreeDestroy, /* xDestroy - Drop a table */
+ rtreeOpen, /* xOpen - open a cursor */
+ rtreeClose, /* xClose - close a cursor */
+ geopolyFilter, /* xFilter - configure scan constraints */
+ rtreeNext, /* xNext - advance a cursor */
+ rtreeEof, /* xEof */
+ geopolyColumn, /* xColumn - read data */
+ rtreeRowid, /* xRowid - read data */
+ geopolyUpdate, /* xUpdate - write data */
+ rtreeBeginTransaction, /* xBegin - begin transaction */
+ rtreeEndTransaction, /* xSync - sync transaction */
+ rtreeEndTransaction, /* xCommit - commit transaction */
+ rtreeEndTransaction, /* xRollback - rollback transaction */
+ geopolyFindFunction, /* xFindFunction - function overloading */
+ rtreeRename, /* xRename - rename the table */
+ rtreeSavepoint, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ rtreeShadowName /* xShadowName */
+};
+
+static int tdsqlite3_geopoly_init(tdsqlite3 *db){
+ int rc = SQLITE_OK;
+ static const struct {
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**);
+ signed char nArg;
+ unsigned char bPure;
+ const char *zName;
+ } aFunc[] = {
+ { geopolyAreaFunc, 1, 1, "geopoly_area" },
+ { geopolyBlobFunc, 1, 1, "geopoly_blob" },
+ { geopolyJsonFunc, 1, 1, "geopoly_json" },
+ { geopolySvgFunc, -1, 1, "geopoly_svg" },
+ { geopolyWithinFunc, 2, 1, "geopoly_within" },
+ { geopolyContainsPointFunc, 3, 1, "geopoly_contains_point" },
+ { geopolyOverlapFunc, 2, 1, "geopoly_overlap" },
+ { geopolyDebugFunc, 1, 0, "geopoly_debug" },
+ { geopolyBBoxFunc, 1, 1, "geopoly_bbox" },
+ { geopolyXformFunc, 7, 1, "geopoly_xform" },
+ { geopolyRegularFunc, 4, 1, "geopoly_regular" },
+ { geopolyCcwFunc, 1, 1, "geopoly_ccw" },
+ };
+ static const struct {
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**);
+ void (*xFinal)(tdsqlite3_context*);
+ const char *zName;
+ } aAgg[] = {
+ { geopolyBBoxStep, geopolyBBoxFinal, "geopoly_group_bbox" },
+ };
+ int i;
+ for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){
+ int enc;
+ if( aFunc[i].bPure ){
+ enc = SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS;
+ }else{
+ enc = SQLITE_UTF8|SQLITE_DIRECTONLY;
+ }
+ rc = tdsqlite3_create_function(db, aFunc[i].zName, aFunc[i].nArg,
+ enc, 0,
+ aFunc[i].xFunc, 0, 0);
+ }
+ for(i=0; i<sizeof(aAgg)/sizeof(aAgg[0]) && rc==SQLITE_OK; i++){
+ rc = tdsqlite3_create_function(db, aAgg[i].zName, 1,
+ SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, 0,
+ 0, aAgg[i].xStep, aAgg[i].xFinal);
+ }
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3_create_module_v2(db, "geopoly", &geopolyModule, 0, 0);
+ }
+ return rc;
+}
+
+/************** End of geopoly.c *********************************************/
+/************** Continuing where we left off in rtree.c **********************/
+#endif
+
/*
** Register the r-tree module with database handle db. This creates the
** virtual table module "rtree" and the debugging/analysis scalar
** function "rtreenode".
*/
-SQLITE_PRIVATE int sqlite3RtreeInit(sqlite3 *db){
+SQLITE_PRIVATE int tdsqlite3RtreeInit(tdsqlite3 *db){
const int utf8 = SQLITE_UTF8;
int rc;
- rc = sqlite3_create_function(db, "rtreenode", 2, utf8, 0, rtreenode, 0, 0);
+ rc = tdsqlite3_create_function(db, "rtreenode", 2, utf8, 0, rtreenode, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3_create_function(db, "rtreedepth", 1, utf8, 0,rtreedepth, 0, 0);
+ }
if( rc==SQLITE_OK ){
- rc = sqlite3_create_function(db, "rtreedepth", 1, utf8, 0,rtreedepth, 0, 0);
+ rc = tdsqlite3_create_function(db, "rtreecheck", -1, utf8, 0,rtreecheck, 0,0);
}
if( rc==SQLITE_OK ){
#ifdef SQLITE_RTREE_INT_ONLY
@@ -167866,27 +195855,32 @@ SQLITE_PRIVATE int sqlite3RtreeInit(sqlite3 *db){
#else
void *c = (void *)RTREE_COORD_REAL32;
#endif
- rc = sqlite3_create_module_v2(db, "rtree", &rtreeModule, c, 0);
+ rc = tdsqlite3_create_module_v2(db, "rtree", &rtreeModule, c, 0);
}
if( rc==SQLITE_OK ){
void *c = (void *)RTREE_COORD_INT32;
- rc = sqlite3_create_module_v2(db, "rtree_i32", &rtreeModule, c, 0);
+ rc = tdsqlite3_create_module_v2(db, "rtree_i32", &rtreeModule, c, 0);
}
+#ifdef SQLITE_ENABLE_GEOPOLY
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3_geopoly_init(db);
+ }
+#endif
return rc;
}
/*
** This routine deletes the RtreeGeomCallback object that was attached
-** one of the SQL functions create by sqlite3_rtree_geometry_callback()
-** or sqlite3_rtree_query_callback(). In other words, this routine is the
+** one of the SQL functions create by tdsqlite3_rtree_geometry_callback()
+** or tdsqlite3_rtree_query_callback(). In other words, this routine is the
** destructor for an RtreeGeomCallback objecct. This routine is called when
** the corresponding SQL function is deleted.
*/
static void rtreeFreeCallback(void *p){
RtreeGeomCallback *pInfo = (RtreeGeomCallback*)p;
if( pInfo->xDestructor ) pInfo->xDestructor(pInfo->pContext);
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
/*
@@ -167896,14 +195890,14 @@ static void rtreeMatchArgFree(void *pArg){
int i;
RtreeMatchArg *p = (RtreeMatchArg*)pArg;
for(i=0; i<p->nParam; i++){
- sqlite3_value_free(p->apSqlParam[i]);
+ tdsqlite3_value_free(p->apSqlParam[i]);
}
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
/*
-** Each call to sqlite3_rtree_geometry_callback() or
-** sqlite3_rtree_query_callback() creates an ordinary SQLite
+** Each call to tdsqlite3_rtree_geometry_callback() or
+** tdsqlite3_rtree_query_callback() creates an ordinary SQLite
** scalar function that is implemented by this routine.
**
** All this function does is construct an RtreeMatchArg object that
@@ -167915,37 +195909,37 @@ static void rtreeMatchArgFree(void *pArg){
** the RtreeMatchArg object, and use the RtreeMatchArg object to figure
** out which elements of the R-Tree should be returned by the query.
*/
-static void geomCallback(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){
- RtreeGeomCallback *pGeomCtx = (RtreeGeomCallback *)sqlite3_user_data(ctx);
+static void geomCallback(tdsqlite3_context *ctx, int nArg, tdsqlite3_value **aArg){
+ RtreeGeomCallback *pGeomCtx = (RtreeGeomCallback *)tdsqlite3_user_data(ctx);
RtreeMatchArg *pBlob;
- int nBlob;
+ tdsqlite3_int64 nBlob;
int memErr = 0;
nBlob = sizeof(RtreeMatchArg) + (nArg-1)*sizeof(RtreeDValue)
- + nArg*sizeof(sqlite3_value*);
- pBlob = (RtreeMatchArg *)sqlite3_malloc(nBlob);
+ + nArg*sizeof(tdsqlite3_value*);
+ pBlob = (RtreeMatchArg *)tdsqlite3_malloc64(nBlob);
if( !pBlob ){
- sqlite3_result_error_nomem(ctx);
+ tdsqlite3_result_error_nomem(ctx);
}else{
int i;
- pBlob->magic = RTREE_GEOMETRY_MAGIC;
+ pBlob->iSize = nBlob;
pBlob->cb = pGeomCtx[0];
- pBlob->apSqlParam = (sqlite3_value**)&pBlob->aParam[nArg];
+ pBlob->apSqlParam = (tdsqlite3_value**)&pBlob->aParam[nArg];
pBlob->nParam = nArg;
for(i=0; i<nArg; i++){
- pBlob->apSqlParam[i] = sqlite3_value_dup(aArg[i]);
+ pBlob->apSqlParam[i] = tdsqlite3_value_dup(aArg[i]);
if( pBlob->apSqlParam[i]==0 ) memErr = 1;
#ifdef SQLITE_RTREE_INT_ONLY
- pBlob->aParam[i] = sqlite3_value_int64(aArg[i]);
+ pBlob->aParam[i] = tdsqlite3_value_int64(aArg[i]);
#else
- pBlob->aParam[i] = sqlite3_value_double(aArg[i]);
+ pBlob->aParam[i] = tdsqlite3_value_double(aArg[i]);
#endif
}
if( memErr ){
- sqlite3_result_error_nomem(ctx);
+ tdsqlite3_result_error_nomem(ctx);
rtreeMatchArgFree(pBlob);
}else{
- sqlite3_result_blob(ctx, pBlob, nBlob, rtreeMatchArgFree);
+ tdsqlite3_result_pointer(ctx, pBlob, "RtreeMatchArg", rtreeMatchArgFree);
}
}
}
@@ -167953,22 +195947,22 @@ static void geomCallback(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){
/*
** Register a new geometry function for use with the r-tree MATCH operator.
*/
-SQLITE_API int sqlite3_rtree_geometry_callback(
- sqlite3 *db, /* Register SQL function on this connection */
+SQLITE_API int tdsqlite3_rtree_geometry_callback(
+ tdsqlite3 *db, /* Register SQL function on this connection */
const char *zGeom, /* Name of the new SQL function */
- int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*), /* Callback */
+ int (*xGeom)(tdsqlite3_rtree_geometry*,int,RtreeDValue*,int*), /* Callback */
void *pContext /* Extra data associated with the callback */
){
RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */
/* Allocate and populate the context object. */
- pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback));
+ pGeomCtx = (RtreeGeomCallback *)tdsqlite3_malloc(sizeof(RtreeGeomCallback));
if( !pGeomCtx ) return SQLITE_NOMEM;
pGeomCtx->xGeom = xGeom;
pGeomCtx->xQueryFunc = 0;
pGeomCtx->xDestructor = 0;
pGeomCtx->pContext = pContext;
- return sqlite3_create_function_v2(db, zGeom, -1, SQLITE_ANY,
+ return tdsqlite3_create_function_v2(db, zGeom, -1, SQLITE_ANY,
(void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback
);
}
@@ -167977,23 +195971,23 @@ SQLITE_API int sqlite3_rtree_geometry_callback(
** Register a new 2nd-generation geometry function for use with the
** r-tree MATCH operator.
*/
-SQLITE_API int sqlite3_rtree_query_callback(
- sqlite3 *db, /* Register SQL function on this connection */
+SQLITE_API int tdsqlite3_rtree_query_callback(
+ tdsqlite3 *db, /* Register SQL function on this connection */
const char *zQueryFunc, /* Name of new SQL function */
- int (*xQueryFunc)(sqlite3_rtree_query_info*), /* Callback */
+ int (*xQueryFunc)(tdsqlite3_rtree_query_info*), /* Callback */
void *pContext, /* Extra data passed into the callback */
void (*xDestructor)(void*) /* Destructor for the extra data */
){
RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */
/* Allocate and populate the context object. */
- pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback));
+ pGeomCtx = (RtreeGeomCallback *)tdsqlite3_malloc(sizeof(RtreeGeomCallback));
if( !pGeomCtx ) return SQLITE_NOMEM;
pGeomCtx->xGeom = 0;
pGeomCtx->xQueryFunc = xQueryFunc;
pGeomCtx->xDestructor = xDestructor;
pGeomCtx->pContext = pContext;
- return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY,
+ return tdsqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY,
(void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback
);
}
@@ -168002,13 +195996,13 @@ SQLITE_API int sqlite3_rtree_query_callback(
#ifdef _WIN32
__declspec(dllexport)
#endif
-SQLITE_API int sqlite3_rtree_init(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_rtree_init(
+ tdsqlite3 *db,
char **pzErrMsg,
- const sqlite3_api_routines *pApi
+ const tdsqlite3_api_routines *pApi
){
SQLITE_EXTENSION_INIT2(pApi)
- return sqlite3RtreeInit(db);
+ return tdsqlite3RtreeInit(db);
}
#endif
@@ -168046,7 +196040,9 @@ SQLITE_API int sqlite3_rtree_init(
** provide case-independent matching.
*/
-#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU)
+#if !defined(SQLITE_CORE) \
+ || defined(SQLITE_ENABLE_ICU) \
+ || defined(SQLITE_ENABLE_ICU_COLLATIONS)
/* Include ICU headers */
#include <unicode/utypes.h>
@@ -168057,13 +196053,33 @@ SQLITE_API int sqlite3_rtree_init(
/* #include <assert.h> */
#ifndef SQLITE_CORE
-/* #include "sqlite3ext.h" */
+/* #include "tdsqlite3ext.h" */
SQLITE_EXTENSION_INIT1
#else
/* #include "sqlite3.h" */
#endif
/*
+** This function is called when an ICU function called from within
+** the implementation of an SQL scalar function returns an error.
+**
+** The scalar function context passed as the first argument is
+** loaded with an error message based on the following two args.
+*/
+static void icuFunctionError(
+ tdsqlite3_context *pCtx, /* SQLite scalar function context */
+ const char *zName, /* Name of ICU function that failed */
+ UErrorCode e /* Error code returned by ICU function */
+){
+ char zBuf[128];
+ tdsqlite3_snprintf(128, zBuf, "ICU error: %s(): %s", zName, u_errorName(e));
+ zBuf[127] = '\0';
+ tdsqlite3_result_error(pCtx, zBuf, -1);
+}
+
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU)
+
+/*
** Maximum length (in bytes) of the pattern in a LIKE or GLOB
** operator.
*/
@@ -168072,10 +196088,10 @@ SQLITE_API int sqlite3_rtree_init(
#endif
/*
-** Version of sqlite3_free() that is always a function, never a macro.
+** Version of tdsqlite3_free() that is always a function, never a macro.
*/
static void xFree(void *p){
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
/*
@@ -168120,15 +196136,15 @@ static int icuLikeCompare(
const uint8_t *zString, /* The UTF-8 string to compare against */
const UChar32 uEsc /* The escape character */
){
- static const int MATCH_ONE = (UChar32)'_';
- static const int MATCH_ALL = (UChar32)'%';
+ static const uint32_t MATCH_ONE = (uint32_t)'_';
+ static const uint32_t MATCH_ALL = (uint32_t)'%';
int prevEscape = 0; /* True if the previous character was uEsc */
while( 1 ){
/* Read (and consume) the next character from the input pattern. */
- UChar32 uPattern;
+ uint32_t uPattern;
SQLITE_ICU_READ_UTF8(zPattern, uPattern);
if( uPattern==0 ) break;
@@ -168170,16 +196186,16 @@ static int icuLikeCompare(
if( *zString==0 ) return 0;
SQLITE_ICU_SKIP_UTF8(zString);
- }else if( !prevEscape && uPattern==uEsc){
+ }else if( !prevEscape && uPattern==(uint32_t)uEsc){
/* Case 3. */
prevEscape = 1;
}else{
/* Case 4. */
- UChar32 uString;
+ uint32_t uString;
SQLITE_ICU_READ_UTF8(zString, uString);
- uString = u_foldCase(uString, U_FOLD_CASE_DEFAULT);
- uPattern = u_foldCase(uPattern, U_FOLD_CASE_DEFAULT);
+ uString = (uint32_t)u_foldCase((UChar32)uString, U_FOLD_CASE_DEFAULT);
+ uPattern = (uint32_t)u_foldCase((UChar32)uPattern, U_FOLD_CASE_DEFAULT);
if( uString!=uPattern ){
return 0;
}
@@ -168204,19 +196220,19 @@ static int icuLikeCompare(
** is mapped to like(B, A, E).
*/
static void icuLikeFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- const unsigned char *zA = sqlite3_value_text(argv[0]);
- const unsigned char *zB = sqlite3_value_text(argv[1]);
+ const unsigned char *zA = tdsqlite3_value_text(argv[0]);
+ const unsigned char *zB = tdsqlite3_value_text(argv[1]);
UChar32 uEsc = 0;
/* Limit the length of the LIKE or GLOB pattern to avoid problems
** of deep recursion and N*N behavior in patternCompare().
*/
- if( sqlite3_value_bytes(argv[0])>SQLITE_MAX_LIKE_PATTERN_LENGTH ){
- sqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1);
+ if( tdsqlite3_value_bytes(argv[0])>SQLITE_MAX_LIKE_PATTERN_LENGTH ){
+ tdsqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1);
return;
}
@@ -168225,44 +196241,26 @@ static void icuLikeFunc(
/* The escape character string must consist of a single UTF-8 character.
** Otherwise, return an error.
*/
- int nE= sqlite3_value_bytes(argv[2]);
- const unsigned char *zE = sqlite3_value_text(argv[2]);
+ int nE= tdsqlite3_value_bytes(argv[2]);
+ const unsigned char *zE = tdsqlite3_value_text(argv[2]);
int i = 0;
if( zE==0 ) return;
U8_NEXT(zE, i, nE, uEsc);
if( i!=nE){
- sqlite3_result_error(context,
+ tdsqlite3_result_error(context,
"ESCAPE expression must be a single character", -1);
return;
}
}
if( zA && zB ){
- sqlite3_result_int(context, icuLikeCompare(zA, zB, uEsc));
+ tdsqlite3_result_int(context, icuLikeCompare(zA, zB, uEsc));
}
}
/*
-** This function is called when an ICU function called from within
-** the implementation of an SQL scalar function returns an error.
-**
-** The scalar function context passed as the first argument is
-** loaded with an error message based on the following two args.
-*/
-static void icuFunctionError(
- sqlite3_context *pCtx, /* SQLite scalar function context */
- const char *zName, /* Name of ICU function that failed */
- UErrorCode e /* Error code returned by ICU function */
-){
- char zBuf[128];
- sqlite3_snprintf(128, zBuf, "ICU error: %s(): %s", zName, u_errorName(e));
- zBuf[127] = '\0';
- sqlite3_result_error(pCtx, zBuf, -1);
-}
-
-/*
** Function to delete compiled regexp objects. Registered as
-** a destructor function with sqlite3_set_auxdata().
+** a destructor function with tdsqlite3_set_auxdata().
*/
static void icuRegexpDelete(void *p){
URegularExpression *pExpr = (URegularExpression *)p;
@@ -168288,11 +196286,11 @@ static void icuRegexpDelete(void *p){
** uregex_matches()
** uregex_close()
*/
-static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
+static void icuRegexpFunc(tdsqlite3_context *p, int nArg, tdsqlite3_value **apArg){
UErrorCode status = U_ZERO_ERROR;
URegularExpression *pExpr;
UBool res;
- const UChar *zString = sqlite3_value_text16(apArg[1]);
+ const UChar *zString = tdsqlite3_value_text16(apArg[1]);
(void)nArg; /* Unused parameter */
@@ -168303,16 +196301,16 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
return;
}
- pExpr = sqlite3_get_auxdata(p, 0);
+ pExpr = tdsqlite3_get_auxdata(p, 0);
if( !pExpr ){
- const UChar *zPattern = sqlite3_value_text16(apArg[0]);
+ const UChar *zPattern = tdsqlite3_value_text16(apArg[0]);
if( !zPattern ){
return;
}
pExpr = uregex_open(zPattern, -1, 0, 0, &status);
if( U_SUCCESS(status) ){
- sqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete);
+ tdsqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete);
}else{
assert(!pExpr);
icuFunctionError(p, "uregex_open", status);
@@ -168342,7 +196340,7 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
uregex_setText(pExpr, 0, 0, &status);
/* Return 1 or 0. */
- sqlite3_result_int(p, res ? 1 : 0);
+ tdsqlite3_result_int(p, res ? 1 : 0);
}
/*
@@ -168371,7 +196369,7 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
**
** http://www.icu-project.org/userguide/posix.html#case_mappings
*/
-static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
+static void icuCaseFunc16(tdsqlite3_context *p, int nArg, tdsqlite3_value **apArg){
const UChar *zInput; /* Pointer to input string */
UChar *zOutput = 0; /* Pointer to output buffer */
int nInput; /* Size of utf-16 input string in bytes */
@@ -168382,26 +196380,26 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
const char *zLocale = 0;
assert(nArg==1 || nArg==2);
- bToUpper = (sqlite3_user_data(p)!=0);
+ bToUpper = (tdsqlite3_user_data(p)!=0);
if( nArg==2 ){
- zLocale = (const char *)sqlite3_value_text(apArg[1]);
+ zLocale = (const char *)tdsqlite3_value_text(apArg[1]);
}
- zInput = sqlite3_value_text16(apArg[0]);
+ zInput = tdsqlite3_value_text16(apArg[0]);
if( !zInput ){
return;
}
- nOut = nInput = sqlite3_value_bytes16(apArg[0]);
+ nOut = nInput = tdsqlite3_value_bytes16(apArg[0]);
if( nOut==0 ){
- sqlite3_result_text16(p, "", 0, SQLITE_STATIC);
+ tdsqlite3_result_text16(p, "", 0, SQLITE_STATIC);
return;
}
for(cnt=0; cnt<2; cnt++){
- UChar *zNew = sqlite3_realloc(zOutput, nOut);
+ UChar *zNew = tdsqlite3_realloc(zOutput, nOut);
if( zNew==0 ){
- sqlite3_free(zOutput);
- sqlite3_result_error_nomem(p);
+ tdsqlite3_free(zOutput);
+ tdsqlite3_result_error_nomem(p);
return;
}
zOutput = zNew;
@@ -168413,7 +196411,7 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
}
if( U_SUCCESS(status) ){
- sqlite3_result_text16(p, zOutput, nOut, xFree);
+ tdsqlite3_result_text16(p, zOutput, nOut, xFree);
}else if( status==U_BUFFER_OVERFLOW_ERROR ){
assert( cnt==0 );
continue;
@@ -168425,6 +196423,8 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
assert( 0 ); /* Unreachable */
}
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) */
+
/*
** Collation sequence destructor function. The pCtx argument points to
** a UCollator structure previously allocated using ucol_open().
@@ -168471,21 +196471,21 @@ static int icuCollationColl(
** collation sequence to create.
*/
static void icuLoadCollation(
- sqlite3_context *p,
+ tdsqlite3_context *p,
int nArg,
- sqlite3_value **apArg
+ tdsqlite3_value **apArg
){
- sqlite3 *db = (sqlite3 *)sqlite3_user_data(p);
+ tdsqlite3 *db = (tdsqlite3 *)tdsqlite3_user_data(p);
UErrorCode status = U_ZERO_ERROR;
const char *zLocale; /* Locale identifier - (eg. "jp_JP") */
const char *zName; /* SQL Collation sequence name (eg. "japanese") */
UCollator *pUCollator; /* ICU library collation object */
- int rc; /* Return code from sqlite3_create_collation_x() */
+ int rc; /* Return code from tdsqlite3_create_collation_x() */
assert(nArg==2);
(void)nArg; /* Unused parameter */
- zLocale = (const char *)sqlite3_value_text(apArg[0]);
- zName = (const char *)sqlite3_value_text(apArg[1]);
+ zLocale = (const char *)tdsqlite3_value_text(apArg[0]);
+ zName = (const char *)tdsqlite3_value_text(apArg[1]);
if( !zLocale || !zName ){
return;
@@ -168498,51 +196498,51 @@ static void icuLoadCollation(
}
assert(p);
- rc = sqlite3_create_collation_v2(db, zName, SQLITE_UTF16, (void *)pUCollator,
+ rc = tdsqlite3_create_collation_v2(db, zName, SQLITE_UTF16, (void *)pUCollator,
icuCollationColl, icuCollationDel
);
if( rc!=SQLITE_OK ){
ucol_close(pUCollator);
- sqlite3_result_error(p, "Error registering collation function", -1);
+ tdsqlite3_result_error(p, "Error registering collation function", -1);
}
}
/*
** Register the ICU extension functions with database db.
*/
-SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db){
- struct IcuScalar {
+SQLITE_PRIVATE int tdsqlite3IcuInit(tdsqlite3 *db){
+# define SQLITEICU_EXTRAFLAGS (SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS)
+ static const struct IcuScalar {
const char *zName; /* Function name */
- int nArg; /* Number of arguments */
- int enc; /* Optimal text encoding */
- void *pContext; /* sqlite3_user_data() context */
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
+ unsigned char nArg; /* Number of arguments */
+ unsigned int enc; /* Optimal text encoding */
+ unsigned char iContext; /* tdsqlite3_user_data() context */
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**);
} scalars[] = {
- {"regexp", 2, SQLITE_ANY|SQLITE_DETERMINISTIC, 0, icuRegexpFunc},
-
- {"lower", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16},
- {"lower", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16},
- {"upper", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16},
- {"upper", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16},
-
- {"lower", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16},
- {"lower", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16},
- {"upper", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16},
- {"upper", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, (void*)1, icuCaseFunc16},
-
- {"like", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc},
- {"like", 3, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc},
-
- {"icu_load_collation", 2, SQLITE_UTF8, (void*)db, icuLoadCollation},
+ {"icu_load_collation",2,SQLITE_UTF8|SQLITE_DIRECTONLY,1, icuLoadCollation},
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU)
+ {"regexp", 2, SQLITE_ANY|SQLITEICU_EXTRAFLAGS, 0, icuRegexpFunc},
+ {"lower", 1, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16},
+ {"lower", 2, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16},
+ {"upper", 1, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16},
+ {"upper", 2, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16},
+ {"lower", 1, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16},
+ {"lower", 2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16},
+ {"upper", 1, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16},
+ {"upper", 2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16},
+ {"like", 2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuLikeFunc},
+ {"like", 3, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuLikeFunc},
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) */
};
-
int rc = SQLITE_OK;
int i;
-
+
for(i=0; rc==SQLITE_OK && i<(int)(sizeof(scalars)/sizeof(scalars[0])); i++){
- struct IcuScalar *p = &scalars[i];
- rc = sqlite3_create_function(
- db, p->zName, p->nArg, p->enc, p->pContext, p->xFunc, 0, 0
+ const struct IcuScalar *p = &scalars[i];
+ rc = tdsqlite3_create_function(
+ db, p->zName, p->nArg, p->enc,
+ p->iContext ? (void*)db : (void*)0,
+ p->xFunc, 0, 0
);
}
@@ -168553,13 +196553,13 @@ SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db){
#ifdef _WIN32
__declspec(dllexport)
#endif
-SQLITE_API int sqlite3_icu_init(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_icu_init(
+ tdsqlite3 *db,
char **pzErrMsg,
- const sqlite3_api_routines *pApi
+ const tdsqlite3_api_routines *pApi
){
SQLITE_EXTENSION_INIT2(pApi)
- return sqlite3IcuInit(db);
+ return tdsqlite3IcuInit(db);
}
#endif
@@ -168597,12 +196597,12 @@ typedef struct IcuTokenizer IcuTokenizer;
typedef struct IcuCursor IcuCursor;
struct IcuTokenizer {
- sqlite3_tokenizer base;
+ tdsqlite3_tokenizer base;
char *zLocale;
};
struct IcuCursor {
- sqlite3_tokenizer_cursor base;
+ tdsqlite3_tokenizer_cursor base;
UBreakIterator *pIter; /* ICU break-iterator object */
int nChar; /* Number of UChar elements in pInput */
@@ -168621,7 +196621,7 @@ struct IcuCursor {
static int icuCreate(
int argc, /* Number of entries in argv[] */
const char * const *argv, /* Tokenizer creation arguments */
- sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
+ tdsqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
){
IcuTokenizer *p;
int n = 0;
@@ -168629,7 +196629,7 @@ static int icuCreate(
if( argc>0 ){
n = strlen(argv[0])+1;
}
- p = (IcuTokenizer *)sqlite3_malloc(sizeof(IcuTokenizer)+n);
+ p = (IcuTokenizer *)tdsqlite3_malloc64(sizeof(IcuTokenizer)+n);
if( !p ){
return SQLITE_NOMEM;
}
@@ -168640,7 +196640,7 @@ static int icuCreate(
memcpy(p->zLocale, argv[0], n);
}
- *ppTokenizer = (sqlite3_tokenizer *)p;
+ *ppTokenizer = (tdsqlite3_tokenizer *)p;
return SQLITE_OK;
}
@@ -168648,9 +196648,9 @@ static int icuCreate(
/*
** Destroy a tokenizer
*/
-static int icuDestroy(sqlite3_tokenizer *pTokenizer){
+static int icuDestroy(tdsqlite3_tokenizer *pTokenizer){
IcuTokenizer *p = (IcuTokenizer *)pTokenizer;
- sqlite3_free(p);
+ tdsqlite3_free(p);
return SQLITE_OK;
}
@@ -168661,10 +196661,10 @@ static int icuDestroy(sqlite3_tokenizer *pTokenizer){
** *ppCursor.
*/
static int icuOpen(
- sqlite3_tokenizer *pTokenizer, /* The tokenizer */
+ tdsqlite3_tokenizer *pTokenizer, /* The tokenizer */
const char *zInput, /* Input string */
int nInput, /* Length of zInput in bytes */
- sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
+ tdsqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */
){
IcuTokenizer *p = (IcuTokenizer *)pTokenizer;
IcuCursor *pCsr;
@@ -168686,7 +196686,7 @@ static int icuOpen(
nInput = strlen(zInput);
}
nChar = nInput+1;
- pCsr = (IcuCursor *)sqlite3_malloc(
+ pCsr = (IcuCursor *)tdsqlite3_malloc64(
sizeof(IcuCursor) + /* IcuCursor */
((nChar+3)&~3) * sizeof(UChar) + /* IcuCursor.aChar[] */
(nChar+1) * sizeof(int) /* IcuCursor.aOffset[] */
@@ -168705,7 +196705,7 @@ static int icuOpen(
c = u_foldCase(c, opt);
U16_APPEND(pCsr->aChar, iOut, nChar, c, isError);
if( isError ){
- sqlite3_free(pCsr);
+ tdsqlite3_free(pCsr);
return SQLITE_ERROR;
}
pCsr->aOffset[iOut] = iInput;
@@ -168719,24 +196719,24 @@ static int icuOpen(
pCsr->pIter = ubrk_open(UBRK_WORD, p->zLocale, pCsr->aChar, iOut, &status);
if( !U_SUCCESS(status) ){
- sqlite3_free(pCsr);
+ tdsqlite3_free(pCsr);
return SQLITE_ERROR;
}
pCsr->nChar = iOut;
ubrk_first(pCsr->pIter);
- *ppCursor = (sqlite3_tokenizer_cursor *)pCsr;
+ *ppCursor = (tdsqlite3_tokenizer_cursor *)pCsr;
return SQLITE_OK;
}
/*
** Close a tokenization cursor previously opened by a call to icuOpen().
*/
-static int icuClose(sqlite3_tokenizer_cursor *pCursor){
+static int icuClose(tdsqlite3_tokenizer_cursor *pCursor){
IcuCursor *pCsr = (IcuCursor *)pCursor;
ubrk_close(pCsr->pIter);
- sqlite3_free(pCsr->zBuffer);
- sqlite3_free(pCsr);
+ tdsqlite3_free(pCsr->zBuffer);
+ tdsqlite3_free(pCsr);
return SQLITE_OK;
}
@@ -168744,7 +196744,7 @@ static int icuClose(sqlite3_tokenizer_cursor *pCursor){
** Extract the next token from a tokenization cursor.
*/
static int icuNext(
- sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */
+ tdsqlite3_tokenizer_cursor *pCursor, /* Cursor returned by simpleOpen */
const char **ppToken, /* OUT: *ppToken is the token text */
int *pnBytes, /* OUT: Number of bytes in token */
int *piStartOffset, /* OUT: Starting offset of token */
@@ -168781,7 +196781,7 @@ static int icuNext(
do {
UErrorCode status = U_ZERO_ERROR;
if( nByte ){
- char *zNew = sqlite3_realloc(pCsr->zBuffer, nByte);
+ char *zNew = tdsqlite3_realloc(pCsr->zBuffer, nByte);
if( !zNew ){
return SQLITE_NOMEM;
}
@@ -168808,7 +196808,7 @@ static int icuNext(
/*
** The set of routines that implement the simple tokenizer
*/
-static const sqlite3_tokenizer_module icuTokenizerModule = {
+static const tdsqlite3_tokenizer_module icuTokenizerModule = {
0, /* iVersion */
icuCreate, /* xCreate */
icuDestroy, /* xCreate */
@@ -168821,8 +196821,8 @@ static const sqlite3_tokenizer_module icuTokenizerModule = {
/*
** Set *ppModule to point at the implementation of the ICU tokenizer.
*/
-SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(
- sqlite3_tokenizer_module const**ppModule
+SQLITE_PRIVATE void tdsqlite3Fts3IcuTokenizerModule(
+ tdsqlite3_tokenizer_module const**ppModule
){
*ppModule = &icuTokenizerModule;
}
@@ -168831,7 +196831,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */
/************** End of fts3_icu.c ********************************************/
-/************** Begin file sqlite3rbu.c **************************************/
+/************** Begin file tdsqlite3rbu.c **************************************/
/*
** 2014 August 30
**
@@ -168849,7 +196849,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(
**
** The RBU extension requires that the RBU update be packaged as an
** SQLite database. The tables it expects to find are described in
-** sqlite3rbu.h. Essentially, for each table xyz in the target database
+** tdsqlite3rbu.h. Essentially, for each table xyz in the target database
** that the user wishes to write to, a corresponding data_xyz table is
** created in the RBU database and populated with one row for each row to
** update, insert or delete from the target table.
@@ -168882,7 +196882,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(
**
** 3) The new *-wal file is checkpointed. This proceeds in the same way
** as a regular database checkpoint, except that a single frame is
-** checkpointed each time sqlite3rbu_step() is called. If the RBU
+** checkpointed each time tdsqlite3rbu_step() is called. If the RBU
** handle is closed before the entire *-wal file is checkpointed,
** the checkpoint progress is saved in the RBU database and the
** checkpoint can be resumed by another RBU client at some point in
@@ -168921,8 +196921,8 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(
/* #include "sqlite3.h" */
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RBU)
-/************** Include sqlite3rbu.h in the middle of sqlite3rbu.c ***********/
-/************** Begin file sqlite3rbu.h **************************************/
+/************** Include tdsqlite3rbu.h in the middle of tdsqlite3rbu.c ***********/
+/************** Begin file tdsqlite3rbu.h **************************************/
/*
** 2014 August 30
**
@@ -169146,19 +197146,19 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(
** stored on disk to an existing target database. Essentially, the
** application:
**
-** 1) Opens an RBU handle using the sqlite3rbu_open() function.
+** 1) Opens an RBU handle using the tdsqlite3rbu_open() function.
**
** 2) Registers any required virtual table modules with the database
-** handle returned by sqlite3rbu_db(). Also, if required, register
+** handle returned by tdsqlite3rbu_db(). Also, if required, register
** the rbu_delta() implementation.
**
-** 3) Calls the sqlite3rbu_step() function one or more times on
-** the new handle. Each call to sqlite3rbu_step() performs a single
+** 3) Calls the tdsqlite3rbu_step() function one or more times on
+** the new handle. Each call to tdsqlite3rbu_step() performs a single
** b-tree operation, so thousands of calls may be required to apply
** a complete update.
**
-** 4) Calls sqlite3rbu_close() to close the RBU update handle. If
-** sqlite3rbu_step() has been called enough times to completely
+** 4) Calls tdsqlite3rbu_close() to close the RBU update handle. If
+** tdsqlite3rbu_step() has been called enough times to completely
** apply the update to the target database, then the RBU database
** is marked as fully applied. Otherwise, the state of the RBU
** update application is saved in the RBU database for later
@@ -169167,7 +197167,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(
** See comments below for more detail on APIs.
**
** If an update is only partially applied to the target database by the
-** time sqlite3rbu_close() is called, various state information is saved
+** time tdsqlite3rbu_close() is called, various state information is saved
** within the RBU database. This allows subsequent processes to automatically
** resume the RBU update from where it left off.
**
@@ -169198,15 +197198,15 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(
extern "C" {
#endif
-typedef struct sqlite3rbu sqlite3rbu;
+typedef struct tdsqlite3rbu tdsqlite3rbu;
/*
** Open an RBU handle.
**
** Argument zTarget is the path to the target database. Argument zRbu is
** the path to the RBU database. Each call to this function must be matched
-** by a call to sqlite3rbu_close(). When opening the databases, RBU passes
-** the SQLITE_CONFIG_URI flag to sqlite3_open_v2(). So if either zTarget
+** by a call to tdsqlite3rbu_close(). When opening the databases, RBU passes
+** the SQLITE_CONFIG_URI flag to tdsqlite3_open_v2(). So if either zTarget
** or zRbu begin with "file:", it will be interpreted as an SQLite
** database URI, not a regular file name.
**
@@ -169233,7 +197233,7 @@ typedef struct sqlite3rbu sqlite3rbu;
** not work out of the box with zipvfs. Refer to the comment describing
** the zipvfs_create_vfs() API below for details on using RBU with zipvfs.
*/
-SQLITE_API sqlite3rbu *sqlite3rbu_open(
+SQLITE_API tdsqlite3rbu *tdsqlite3rbu_open(
const char *zTarget,
const char *zRbu,
const char *zState
@@ -169246,10 +197246,10 @@ SQLITE_API sqlite3rbu *sqlite3rbu_open(
**
** The second argument to this function identifies a database in which
** to store the state of the RBU vacuum operation if it is suspended. The
-** first time sqlite3rbu_vacuum() is called, to start an RBU vacuum
+** first time tdsqlite3rbu_vacuum() is called, to start an RBU vacuum
** operation, the state database should either not exist or be empty
** (contain no tables). If an RBU vacuum is suspended by calling
-** sqlite3rbu_close() on the RBU handle before sqlite3rbu_step() has
+** tdsqlite3rbu_close() on the RBU handle before tdsqlite3rbu_step() has
** returned SQLITE_DONE, the vacuum state is stored in the state database.
** The vacuum can be resumed by calling this function to open a new RBU
** handle specifying the same target and state databases.
@@ -169258,26 +197258,52 @@ SQLITE_API sqlite3rbu *sqlite3rbu_open(
** name of the state database is "<database>-vacuum", where <database>
** is the name of the target database file. In this case, on UNIX, if the
** state database is not already present in the file-system, it is created
-** with the same permissions as the target db is made.
+** with the same permissions as the target db is made.
+**
+** With an RBU vacuum, it is an SQLITE_MISUSE error if the name of the
+** state database ends with "-vactmp". This name is reserved for internal
+** use.
**
** This function does not delete the state database after an RBU vacuum
** is completed, even if it created it. However, if the call to
-** sqlite3rbu_close() returns any value other than SQLITE_OK, the contents
+** tdsqlite3rbu_close() returns any value other than SQLITE_OK, the contents
** of the state tables within the state database are zeroed. This way,
-** the next call to sqlite3rbu_vacuum() opens a handle that starts a
+** the next call to tdsqlite3rbu_vacuum() opens a handle that starts a
** new RBU vacuum operation.
**
-** As with sqlite3rbu_open(), Zipvfs users should rever to the comment
-** describing the sqlite3rbu_create_vfs() API function below for
+** As with tdsqlite3rbu_open(), Zipvfs users should rever to the comment
+** describing the tdsqlite3rbu_create_vfs() API function below for
** a description of the complications associated with using RBU with
** zipvfs databases.
*/
-SQLITE_API sqlite3rbu *sqlite3rbu_vacuum(
+SQLITE_API tdsqlite3rbu *tdsqlite3rbu_vacuum(
const char *zTarget,
const char *zState
);
/*
+** Configure a limit for the amount of temp space that may be used by
+** the RBU handle passed as the first argument. The new limit is specified
+** in bytes by the second parameter. If it is positive, the limit is updated.
+** If the second parameter to this function is passed zero, then the limit
+** is removed entirely. If the second parameter is negative, the limit is
+** not modified (this is useful for querying the current limit).
+**
+** In all cases the returned value is the current limit in bytes (zero
+** indicates unlimited).
+**
+** If the temp space limit is exceeded during operation, an SQLITE_FULL
+** error is returned.
+*/
+SQLITE_API tdsqlite3_int64 tdsqlite3rbu_temp_size_limit(tdsqlite3rbu*, tdsqlite3_int64);
+
+/*
+** Return the current amount of temp file space, in bytes, currently used by
+** the RBU handle passed as the only argument.
+*/
+SQLITE_API tdsqlite3_int64 tdsqlite3rbu_temp_size(tdsqlite3rbu*);
+
+/*
** Internally, each RBU connection uses a separate SQLite database
** connection to access the target and rbu update databases. This
** API allows the application direct access to these database handles.
@@ -169289,26 +197315,26 @@ SQLITE_API sqlite3rbu *sqlite3rbu_vacuum(
** following scenarios:
**
** * If any target tables are virtual tables, it may be necessary to
-** call sqlite3_create_module() on the target database handle to
+** call tdsqlite3_create_module() on the target database handle to
** register the required virtual table implementations.
**
** * If the data_xxx tables in the RBU source database are virtual
-** tables, the application may need to call sqlite3_create_module() on
+** tables, the application may need to call tdsqlite3_create_module() on
** the rbu update db handle to any required virtual table
** implementations.
**
** * If the application uses the "rbu_delta()" feature described above,
-** it must use sqlite3_create_function() or similar to register the
+** it must use tdsqlite3_create_function() or similar to register the
** rbu_delta() implementation with the target database handle.
**
** If an error has occurred, either while opening or stepping the RBU object,
** this function may return NULL. The error code and message may be collected
-** when sqlite3rbu_close() is called.
+** when tdsqlite3rbu_close() is called.
**
** Database handles returned by this function remain valid until the next
-** call to any sqlite3rbu_xxx() function other than sqlite3rbu_db().
+** call to any tdsqlite3rbu_xxx() function other than tdsqlite3rbu_db().
*/
-SQLITE_API sqlite3 *sqlite3rbu_db(sqlite3rbu*, int bRbu);
+SQLITE_API tdsqlite3 *tdsqlite3rbu_db(tdsqlite3rbu*, int bRbu);
/*
** Do some work towards applying the RBU update to the target db.
@@ -169318,11 +197344,11 @@ SQLITE_API sqlite3 *sqlite3rbu_db(sqlite3rbu*, int bRbu);
** the RBU update. If an error does occur, some other error code is
** returned.
**
-** Once a call to sqlite3rbu_step() has returned a value other than
+** Once a call to tdsqlite3rbu_step() has returned a value other than
** SQLITE_OK, all subsequent calls on the same RBU handle are no-ops
** that immediately return the same value.
*/
-SQLITE_API int sqlite3rbu_step(sqlite3rbu *pRbu);
+SQLITE_API int tdsqlite3rbu_step(tdsqlite3rbu *pRbu);
/*
** Force RBU to save its state to disk.
@@ -169330,11 +197356,11 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *pRbu);
** If a power failure or application crash occurs during an update, following
** system recovery RBU may resume the update from the point at which the state
** was last saved. In other words, from the most recent successful call to
-** sqlite3rbu_close() or this function.
+** tdsqlite3rbu_close() or this function.
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
*/
-SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *pRbu);
+SQLITE_API int tdsqlite3rbu_savestate(tdsqlite3rbu *pRbu);
/*
** Close an RBU handle.
@@ -169343,25 +197369,25 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *pRbu);
** as fully applied. Otherwise, assuming no error has occurred, save the
** current state of the RBU update appliation to the RBU database.
**
-** If an error has already occurred as part of an sqlite3rbu_step()
-** or sqlite3rbu_open() call, or if one occurs within this function, an
-** SQLite error code is returned. Additionally, *pzErrmsg may be set to
-** point to a buffer containing a utf-8 formatted English language error
-** message. It is the responsibility of the caller to eventually free any
-** such buffer using sqlite3_free().
+** If an error has already occurred as part of an tdsqlite3rbu_step()
+** or tdsqlite3rbu_open() call, or if one occurs within this function, an
+** SQLite error code is returned. Additionally, if pzErrmsg is not NULL,
+** *pzErrmsg may be set to point to a buffer containing a utf-8 formatted
+** English language error message. It is the responsibility of the caller to
+** eventually free any such buffer using tdsqlite3_free().
**
** Otherwise, if no error occurs, this function returns SQLITE_OK if the
** update has been partially applied, or SQLITE_DONE if it has been
** completely applied.
*/
-SQLITE_API int sqlite3rbu_close(sqlite3rbu *pRbu, char **pzErrmsg);
+SQLITE_API int tdsqlite3rbu_close(tdsqlite3rbu *pRbu, char **pzErrmsg);
/*
** Return the total number of key-value operations (inserts, deletes or
** updates) that have been performed on the target database since the
** current RBU update was started.
*/
-SQLITE_API sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu);
+SQLITE_API tdsqlite3_int64 tdsqlite3rbu_progress(tdsqlite3rbu *pRbu);
/*
** Obtain permyriadage (permyriadage is to 10000 as percentage is to 100)
@@ -169403,7 +197429,7 @@ SQLITE_API sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu);
** table exists but is not correctly populated, the value of the *pnOne
** output variable during stage 1 is undefined.
*/
-SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int *pnTwo);
+SQLITE_API void tdsqlite3rbu_bp_progress(tdsqlite3rbu *pRbu, int *pnOne, int*pnTwo);
/*
** Obtain an indication as to the current stage of an RBU update or vacuum.
@@ -169411,28 +197437,28 @@ SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int *pnTwo)
** defined in this file. Return values should be interpreted as follows:
**
** SQLITE_RBU_STATE_OAL:
-** RBU is currently building a *-oal file. The next call to sqlite3rbu_step()
+** RBU is currently building a *-oal file. The next call to tdsqlite3rbu_step()
** may either add further data to the *-oal file, or compute data that will
** be added by a subsequent call.
**
** SQLITE_RBU_STATE_MOVE:
-** RBU has finished building the *-oal file. The next call to sqlite3rbu_step()
+** RBU has finished building the *-oal file. The next call to tdsqlite3rbu_step()
** will move the *-oal file to the equivalent *-wal path. If the current
** operation is an RBU update, then the updated version of the database
** file will become visible to ordinary SQLite clients following the next
-** call to sqlite3rbu_step().
+** call to tdsqlite3rbu_step().
**
** SQLITE_RBU_STATE_CHECKPOINT:
** RBU is currently performing an incremental checkpoint. The next call to
-** sqlite3rbu_step() will copy a page of data from the *-wal file into
+** tdsqlite3rbu_step() will copy a page of data from the *-wal file into
** the target database file.
**
** SQLITE_RBU_STATE_DONE:
-** The RBU operation has finished. Any subsequent calls to sqlite3rbu_step()
+** The RBU operation has finished. Any subsequent calls to tdsqlite3rbu_step()
** will immediately return SQLITE_DONE.
**
** SQLITE_RBU_STATE_ERROR:
-** An error has occurred. Any subsequent calls to sqlite3rbu_step() will
+** An error has occurred. Any subsequent calls to tdsqlite3rbu_step() will
** immediately return the SQLite error code associated with the error.
*/
#define SQLITE_RBU_STATE_OAL 1
@@ -169441,7 +197467,7 @@ SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int *pnTwo)
#define SQLITE_RBU_STATE_DONE 4
#define SQLITE_RBU_STATE_ERROR 5
-SQLITE_API int sqlite3rbu_state(sqlite3rbu *pRbu);
+SQLITE_API int tdsqlite3rbu_state(tdsqlite3rbu *pRbu);
/*
** Create an RBU VFS named zName that accesses the underlying file-system
@@ -169462,19 +197488,19 @@ SQLITE_API int sqlite3rbu_state(sqlite3rbu *pRbu);
** multiplexor (error checking omitted):
**
** // Create a VFS named "multiplex" (not the default).
-** sqlite3_multiplex_initialize(0, 0);
+** tdsqlite3_multiplex_initialize(0, 0);
**
** // Create an rbu VFS named "rbu" that uses multiplexor. If the
** // second argument were replaced with NULL, the "rbu" VFS would
** // access the file-system via the system default VFS, bypassing the
** // multiplexor.
-** sqlite3rbu_create_vfs("rbu", "multiplex");
+** tdsqlite3rbu_create_vfs("rbu", "multiplex");
**
** // Create a zipvfs VFS named "zipvfs" that uses rbu.
** zipvfs_create_vfs_v3("zipvfs", "rbu", 0, xCompressorAlgorithmDetector);
**
** // Make zipvfs the default VFS.
-** sqlite3_vfs_register(sqlite3_vfs_find("zipvfs"), 1);
+** tdsqlite3_vfs_register(tdsqlite3_vfs_find("zipvfs"), 1);
**
** Because the default VFS created above includes a RBU functionality, it
** may be used by RBU clients. Attempting to use RBU with a zipvfs VFS stack
@@ -169485,17 +197511,17 @@ SQLITE_API int sqlite3rbu_state(sqlite3rbu *pRbu);
** file-system via "rbu" all the time, even if it only uses RBU functionality
** occasionally.
*/
-SQLITE_API int sqlite3rbu_create_vfs(const char *zName, const char *zParent);
+SQLITE_API int tdsqlite3rbu_create_vfs(const char *zName, const char *zParent);
/*
** Deregister and destroy an RBU vfs created by an earlier call to
-** sqlite3rbu_create_vfs().
+** tdsqlite3rbu_create_vfs().
**
** VFS objects are not reference counted. If a VFS object is destroyed
** before all database handles that use it have been closed, the results
** are undefined.
*/
-SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName);
+SQLITE_API void tdsqlite3rbu_destroy_vfs(const char *zName);
#if 0
} /* end of the 'extern "C"' block */
@@ -169503,8 +197529,8 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName);
#endif /* _SQLITE3RBU_H */
-/************** End of sqlite3rbu.h ******************************************/
-/************** Continuing where we left off in sqlite3rbu.c *****************/
+/************** End of tdsqlite3rbu.h ******************************************/
+/************** Continuing where we left off in tdsqlite3rbu.c *****************/
#if defined(_WIN32_WCE)
/* #include "windows.h" */
@@ -169513,6 +197539,13 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName);
/* Maximum number of prepared UPDATE statements held by this module */
#define SQLITE_RBU_UPDATE_CACHESIZE 16
+/* Delta checksums disabled by default. Compile with -DRBU_ENABLE_DELTA_CKSUM
+** to enable checksum verification.
+*/
+#ifndef RBU_ENABLE_DELTA_CKSUM
+# define RBU_ENABLE_DELTA_CKSUM 0
+#endif
+
/*
** Swap two objects of type TYPE.
*/
@@ -169547,7 +197580,7 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName);
** table/index.
**
** RBU_STATE_PROGRESS:
-** Trbul number of sqlite3rbu_step() calls made so far as part of this
+** Trbul number of tdsqlite3rbu_step() calls made so far as part of this
** rbu update.
**
** RBU_STATE_CKPT:
@@ -169563,6 +197596,10 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName);
**
** RBU_STATE_OALSZ:
** Valid if STAGE==1. The size in bytes of the *-oal file.
+**
+** RBU_STATE_DATATBL:
+** Only valid if STAGE==1. The RBU database name of the table
+** currently being read.
*/
#define RBU_STATE_STAGE 1
#define RBU_STATE_TBL 2
@@ -169573,6 +197610,7 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName);
#define RBU_STATE_COOKIE 7
#define RBU_STATE_OALSZ 8
#define RBU_STATE_PHASEONESTEP 9
+#define RBU_STATE_DATATBL 10
#define RBU_STAGE_OAL 1
#define RBU_STAGE_MOVE 2
@@ -169587,6 +197625,7 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName);
typedef struct RbuFrame RbuFrame;
typedef struct RbuObjIter RbuObjIter;
typedef struct RbuState RbuState;
+typedef struct RbuSpan RbuSpan;
typedef struct rbu_vfs rbu_vfs;
typedef struct rbu_file rbu_file;
typedef struct RbuUpdateStmt RbuUpdateStmt;
@@ -169595,7 +197634,7 @@ typedef struct RbuUpdateStmt RbuUpdateStmt;
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
-typedef sqlite3_int64 i64;
+typedef tdsqlite3_int64 i64;
#endif
/*
@@ -169615,6 +197654,7 @@ typedef sqlite3_int64 i64;
struct RbuState {
int eStage;
char *zTbl;
+ char *zDataTbl;
char *zIdx;
i64 iWalCksum;
int nRow;
@@ -169626,10 +197666,15 @@ struct RbuState {
struct RbuUpdateStmt {
char *zMask; /* Copy of update mask used with pUpdate */
- sqlite3_stmt *pUpdate; /* Last update statement (or NULL) */
+ tdsqlite3_stmt *pUpdate; /* Last update statement (or NULL) */
RbuUpdateStmt *pNext;
};
+struct RbuSpan {
+ const char *zSpan;
+ int nSpan;
+};
+
/*
** An iterator of this type is used to iterate through all objects in
** the target database that require updating. For each such table, the
@@ -169644,11 +197689,16 @@ struct RbuUpdateStmt {
** it points to an array of flags nTblCol elements in size. The flag is
** set for each column that is either a part of the PK or a part of an
** index. Or clear otherwise.
+**
+** If there are one or more partial indexes on the table, all fields of
+** this array set set to 1. This is because in that case, the module has
+** no way to tell which fields will be required to add and remove entries
+** from the partial indexes.
**
*/
struct RbuObjIter {
- sqlite3_stmt *pTblIter; /* Iterate through tables */
- sqlite3_stmt *pIdxIter; /* Index iterator */
+ tdsqlite3_stmt *pTblIter; /* Iterate through tables */
+ tdsqlite3_stmt *pIdxIter; /* Index iterator */
int nTblCol; /* Size of azTblCol[] array */
char **azTblCol; /* Array of unquoted target column names */
char **azTblType; /* Array of target column types */
@@ -169670,10 +197720,13 @@ struct RbuObjIter {
/* Statements created by rbuObjIterPrepareAll() */
int nCol; /* Number of columns in current object */
- sqlite3_stmt *pSelect; /* Source data */
- sqlite3_stmt *pInsert; /* Statement for INSERT operations */
- sqlite3_stmt *pDelete; /* Statement for DELETE ops */
- sqlite3_stmt *pTmpInsert; /* Insert into rbu_tmp_$zDataTbl */
+ tdsqlite3_stmt *pSelect; /* Source data */
+ tdsqlite3_stmt *pInsert; /* Statement for INSERT operations */
+ tdsqlite3_stmt *pDelete; /* Statement for DELETE ops */
+ tdsqlite3_stmt *pTmpInsert; /* Insert into rbu_tmp_$zDataTbl */
+ int nIdxCol;
+ RbuSpan *aIdxCol;
+ char *zIdxSql;
/* Last UPDATE used (for PK b-tree updates only), or NULL. */
RbuUpdateStmt *pRbuUpdate;
@@ -169698,7 +197751,7 @@ struct RbuObjIter {
/*
-** Within the RBU_STAGE_OAL stage, each call to sqlite3rbu_step() performs
+** Within the RBU_STAGE_OAL stage, each call to tdsqlite3rbu_step() performs
** one of the following operations.
*/
#define RBU_INSERT 1 /* Insert on a main table b-tree */
@@ -169724,7 +197777,7 @@ struct RbuFrame {
** nPhaseOneStep:
** If the RBU database contains an rbu_count table, this value is set to
** a running estimate of the number of b-tree operations required to
-** finish populating the *-oal file. This allows the sqlite3_bp_progress()
+** finish populating the *-oal file. This allows the tdsqlite3_bp_progress()
** API to calculate the permyriadage progress of populating the *-oal file
** using the formula:
**
@@ -169758,10 +197811,10 @@ struct RbuFrame {
** first pass of each source table. The updated nPhaseOneStep value is
** stored in the rbu_state table if the RBU update is suspended.
*/
-struct sqlite3rbu {
+struct tdsqlite3rbu {
int eStage; /* Value of RBU_STATE_STAGE field */
- sqlite3 *dbMain; /* target database handle */
- sqlite3 *dbRbu; /* rbu database handle */
+ tdsqlite3 *dbMain; /* target database handle */
+ tdsqlite3 *dbRbu; /* rbu database handle */
char *zTarget; /* Path to target db */
char *zRbu; /* Path to rbu db */
char *zState; /* Path to state db (or NULL if zRbu) */
@@ -169773,6 +197826,7 @@ struct sqlite3rbu {
RbuObjIter objiter; /* Iterator for skipping through tbl/idx */
const char *zVfsName; /* Name of automatically created rbu vfs */
rbu_file *pTargetFd; /* File handle open on target db */
+ int nPagePerSector; /* Pages per sector for pTargetFd */
i64 iOalSz;
i64 nPhaseOneStep;
@@ -169787,6 +197841,8 @@ struct sqlite3rbu {
int pgsz;
u8 *aBuf;
i64 iWalCksum;
+ i64 szTemp; /* Current size of all temp files in use */
+ i64 szTempLimit; /* Total size limit for temp files */
/* Used in RBU vacuum mode only */
int nRbu; /* Number of RBU VFS in the stack */
@@ -169795,23 +197851,34 @@ struct sqlite3rbu {
/*
** An rbu VFS is implemented using an instance of this structure.
+**
+** Variable pRbu is only non-NULL for automatically created RBU VFS objects.
+** It is NULL for RBU VFS objects created explicitly using
+** tdsqlite3rbu_create_vfs(). It is used to track the total amount of temp
+** space used by the RBU handle.
*/
struct rbu_vfs {
- sqlite3_vfs base; /* rbu VFS shim methods */
- sqlite3_vfs *pRealVfs; /* Underlying VFS */
- sqlite3_mutex *mutex; /* Mutex to protect pMain */
- rbu_file *pMain; /* Linked list of main db files */
+ tdsqlite3_vfs base; /* rbu VFS shim methods */
+ tdsqlite3_vfs *pRealVfs; /* Underlying VFS */
+ tdsqlite3_mutex *mutex; /* Mutex to protect pMain */
+ tdsqlite3rbu *pRbu; /* Owner RBU object */
+ rbu_file *pMain; /* List of main db files */
+ rbu_file *pMainRbu; /* List of main db files with pRbu!=0 */
};
/*
** Each file opened by an rbu VFS is represented by an instance of
** the following structure.
+**
+** If this is a temporary file (pRbu!=0 && flags&DELETE_ON_CLOSE), variable
+** "sz" is set to the current size of the database file.
*/
struct rbu_file {
- sqlite3_file base; /* sqlite3_file methods */
- sqlite3_file *pReal; /* Underlying file handle */
+ tdsqlite3_file base; /* tdsqlite3_file methods */
+ tdsqlite3_file *pReal; /* Underlying file handle */
rbu_vfs *pRbuVfs; /* Pointer to the rbu_vfs object */
- sqlite3rbu *pRbu; /* Pointer to rbu object (rbu target only) */
+ tdsqlite3rbu *pRbu; /* Pointer to rbu object (rbu target only) */
+ i64 sz; /* Size of file in bytes (temp only) */
int openFlags; /* Flags this file was opened with */
u32 iCookie; /* Cookie value for main db files */
@@ -169825,6 +197892,7 @@ struct rbu_file {
const char *zWal; /* Wal filename for this main db file */
rbu_file *pWalFd; /* Wal file descriptor for this main db */
rbu_file *pMainNext; /* Next MAIN_DB file */
+ rbu_file *pMainRbuNext; /* Next MAIN_DB file with pRbu!=0 */
};
/*
@@ -169874,6 +197942,7 @@ static unsigned int rbuDeltaGetInt(const char **pz, int *pLen){
return v;
}
+#if RBU_ENABLE_DELTA_CKSUM
/*
** Compute a 32-bit checksum on the N-byte buffer. Return the result.
*/
@@ -169908,6 +197977,7 @@ static unsigned int rbuDeltaChecksum(const char *zIn, size_t N){
}
return sum3;
}
+#endif
/*
** Apply a delta.
@@ -169938,7 +198008,7 @@ static int rbuDeltaApply(
){
unsigned int limit;
unsigned int total = 0;
-#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST
+#if RBU_ENABLE_DELTA_CKSUM
char *zOrigOut = zOut;
#endif
@@ -169993,7 +198063,7 @@ static int rbuDeltaApply(
case ';': {
zDelta++; lenDelta--;
zOut[0] = 0;
-#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST
+#if RBU_ENABLE_DELTA_CKSUM
if( cnt!=rbuDeltaChecksum(zOrigOut, total) ){
/* ERROR: bad checksum */
return -1;
@@ -170038,9 +198108,9 @@ static int rbuDeltaOutputSize(const char *zDelta, int lenDelta){
** function returns the patched blob.
*/
static void rbuFossilDeltaFunc(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
const char *aDelta;
int nDelta;
@@ -170053,27 +198123,28 @@ static void rbuFossilDeltaFunc(
assert( argc==2 );
- nOrig = sqlite3_value_bytes(argv[0]);
- aOrig = (const char*)sqlite3_value_blob(argv[0]);
- nDelta = sqlite3_value_bytes(argv[1]);
- aDelta = (const char*)sqlite3_value_blob(argv[1]);
+ nOrig = tdsqlite3_value_bytes(argv[0]);
+ aOrig = (const char*)tdsqlite3_value_blob(argv[0]);
+ nDelta = tdsqlite3_value_bytes(argv[1]);
+ aDelta = (const char*)tdsqlite3_value_blob(argv[1]);
/* Figure out the size of the output */
nOut = rbuDeltaOutputSize(aDelta, nDelta);
if( nOut<0 ){
- sqlite3_result_error(context, "corrupt fossil delta", -1);
+ tdsqlite3_result_error(context, "corrupt fossil delta", -1);
return;
}
- aOut = sqlite3_malloc(nOut+1);
+ aOut = tdsqlite3_malloc(nOut+1);
if( aOut==0 ){
- sqlite3_result_error_nomem(context);
+ tdsqlite3_result_error_nomem(context);
}else{
nOut2 = rbuDeltaApply(aOrig, nOrig, aDelta, nDelta, aOut);
if( nOut2!=nOut ){
- sqlite3_result_error(context, "corrupt fossil delta", -1);
+ tdsqlite3_free(aOut);
+ tdsqlite3_result_error(context, "corrupt fossil delta", -1);
}else{
- sqlite3_result_blob(context, aOut, nOut, sqlite3_free);
+ tdsqlite3_result_blob(context, aOut, nOut, tdsqlite3_free);
}
}
}
@@ -170087,17 +198158,17 @@ static void rbuFossilDeltaFunc(
** Otherwise, if an error does occur, set *ppStmt to NULL and return
** an SQLite error code. Additionally, set output variable *pzErrmsg to
** point to a buffer containing an error message. It is the responsibility
-** of the caller to (eventually) free this buffer using sqlite3_free().
+** of the caller to (eventually) free this buffer using tdsqlite3_free().
*/
static int prepareAndCollectError(
- sqlite3 *db,
- sqlite3_stmt **ppStmt,
+ tdsqlite3 *db,
+ tdsqlite3_stmt **ppStmt,
char **pzErrmsg,
const char *zSql
){
- int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
+ int rc = tdsqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
if( rc!=SQLITE_OK ){
- *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ *pzErrmsg = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
*ppStmt = 0;
}
return rc;
@@ -170105,23 +198176,23 @@ static int prepareAndCollectError(
/*
** Reset the SQL statement passed as the first argument. Return a copy
-** of the value returned by sqlite3_reset().
+** of the value returned by tdsqlite3_reset().
**
** If an error has occurred, then set *pzErrmsg to point to a buffer
** containing an error message. It is the responsibility of the caller
-** to eventually free this buffer using sqlite3_free().
+** to eventually free this buffer using tdsqlite3_free().
*/
-static int resetAndCollectError(sqlite3_stmt *pStmt, char **pzErrmsg){
- int rc = sqlite3_reset(pStmt);
+static int resetAndCollectError(tdsqlite3_stmt *pStmt, char **pzErrmsg){
+ int rc = tdsqlite3_reset(pStmt);
if( rc!=SQLITE_OK ){
- *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(sqlite3_db_handle(pStmt)));
+ *pzErrmsg = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(tdsqlite3_db_handle(pStmt)));
}
return rc;
}
/*
** Unless it is NULL, argument zSql points to a buffer allocated using
-** sqlite3_malloc containing an SQL statement. This function prepares the SQL
+** tdsqlite3_malloc containing an SQL statement. This function prepares the SQL
** statement against database db and frees the buffer. If statement
** compilation is successful, *ppStmt is set to point to the new statement
** handle and SQLITE_OK is returned.
@@ -170129,14 +198200,14 @@ static int resetAndCollectError(sqlite3_stmt *pStmt, char **pzErrmsg){
** Otherwise, if an error occurs, *ppStmt is set to NULL and an error code
** returned. In this case, *pzErrmsg may also be set to point to an error
** message. It is the responsibility of the caller to free this error message
-** buffer using sqlite3_free().
+** buffer using tdsqlite3_free().
**
** If argument zSql is NULL, this function assumes that an OOM has occurred.
** In this case SQLITE_NOMEM is returned and *ppStmt set to NULL.
*/
static int prepareFreeAndCollectError(
- sqlite3 *db,
- sqlite3_stmt **ppStmt,
+ tdsqlite3 *db,
+ tdsqlite3_stmt **ppStmt,
char **pzErrmsg,
char *zSql
){
@@ -170147,7 +198218,7 @@ static int prepareFreeAndCollectError(
*ppStmt = 0;
}else{
rc = prepareAndCollectError(db, ppStmt, pzErrmsg, zSql);
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
}
return rc;
}
@@ -170159,10 +198230,10 @@ static int prepareFreeAndCollectError(
static void rbuObjIterFreeCols(RbuObjIter *pIter){
int i;
for(i=0; i<pIter->nTblCol; i++){
- sqlite3_free(pIter->azTblCol[i]);
- sqlite3_free(pIter->azTblType[i]);
+ tdsqlite3_free(pIter->azTblCol[i]);
+ tdsqlite3_free(pIter->azTblType[i]);
}
- sqlite3_free(pIter->azTblCol);
+ tdsqlite3_free(pIter->azTblCol);
pIter->azTblCol = 0;
pIter->azTblType = 0;
pIter->aiSrcOrder = 0;
@@ -170179,17 +198250,19 @@ static void rbuObjIterFreeCols(RbuObjIter *pIter){
static void rbuObjIterClearStatements(RbuObjIter *pIter){
RbuUpdateStmt *pUp;
- sqlite3_finalize(pIter->pSelect);
- sqlite3_finalize(pIter->pInsert);
- sqlite3_finalize(pIter->pDelete);
- sqlite3_finalize(pIter->pTmpInsert);
+ tdsqlite3_finalize(pIter->pSelect);
+ tdsqlite3_finalize(pIter->pInsert);
+ tdsqlite3_finalize(pIter->pDelete);
+ tdsqlite3_finalize(pIter->pTmpInsert);
pUp = pIter->pRbuUpdate;
while( pUp ){
RbuUpdateStmt *pTmp = pUp->pNext;
- sqlite3_finalize(pUp->pUpdate);
- sqlite3_free(pUp);
+ tdsqlite3_finalize(pUp->pUpdate);
+ tdsqlite3_free(pUp);
pUp = pTmp;
}
+ tdsqlite3_free(pIter->aIdxCol);
+ tdsqlite3_free(pIter->zIdxSql);
pIter->pSelect = 0;
pIter->pInsert = 0;
@@ -170197,6 +198270,9 @@ static void rbuObjIterClearStatements(RbuObjIter *pIter){
pIter->pRbuUpdate = 0;
pIter->pTmpInsert = 0;
pIter->nCol = 0;
+ pIter->nIdxCol = 0;
+ pIter->aIdxCol = 0;
+ pIter->zIdxSql = 0;
}
/*
@@ -170205,8 +198281,8 @@ static void rbuObjIterClearStatements(RbuObjIter *pIter){
*/
static void rbuObjIterFinalize(RbuObjIter *pIter){
rbuObjIterClearStatements(pIter);
- sqlite3_finalize(pIter->pTblIter);
- sqlite3_finalize(pIter->pIdxIter);
+ tdsqlite3_finalize(pIter->pTblIter);
+ tdsqlite3_finalize(pIter->pIdxIter);
rbuObjIterFreeCols(pIter);
memset(pIter, 0, sizeof(RbuObjIter));
}
@@ -170219,14 +198295,14 @@ static void rbuObjIterFinalize(RbuObjIter *pIter){
** left in the RBU handle passed as the first argument. A copy of the
** error code is returned.
*/
-static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){
+static int rbuObjIterNext(tdsqlite3rbu *p, RbuObjIter *pIter){
int rc = p->rc;
if( rc==SQLITE_OK ){
/* Free any SQLite statements used while processing the previous object */
rbuObjIterClearStatements(pIter);
if( pIter->zIdx==0 ){
- rc = sqlite3_exec(p->dbMain,
+ rc = tdsqlite3_exec(p->dbMain,
"DROP TRIGGER IF EXISTS temp.rbu_insert_tr;"
"DROP TRIGGER IF EXISTS temp.rbu_update1_tr;"
"DROP TRIGGER IF EXISTS temp.rbu_update2_tr;"
@@ -170239,30 +198315,30 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){
if( pIter->bCleanup ){
rbuObjIterFreeCols(pIter);
pIter->bCleanup = 0;
- rc = sqlite3_step(pIter->pTblIter);
+ rc = tdsqlite3_step(pIter->pTblIter);
if( rc!=SQLITE_ROW ){
rc = resetAndCollectError(pIter->pTblIter, &p->zErrmsg);
pIter->zTbl = 0;
}else{
- pIter->zTbl = (const char*)sqlite3_column_text(pIter->pTblIter, 0);
- pIter->zDataTbl = (const char*)sqlite3_column_text(pIter->pTblIter,1);
+ pIter->zTbl = (const char*)tdsqlite3_column_text(pIter->pTblIter, 0);
+ pIter->zDataTbl = (const char*)tdsqlite3_column_text(pIter->pTblIter,1);
rc = (pIter->zDataTbl && pIter->zTbl) ? SQLITE_OK : SQLITE_NOMEM;
}
}else{
if( pIter->zIdx==0 ){
- sqlite3_stmt *pIdx = pIter->pIdxIter;
- rc = sqlite3_bind_text(pIdx, 1, pIter->zTbl, -1, SQLITE_STATIC);
+ tdsqlite3_stmt *pIdx = pIter->pIdxIter;
+ rc = tdsqlite3_bind_text(pIdx, 1, pIter->zTbl, -1, SQLITE_STATIC);
}
if( rc==SQLITE_OK ){
- rc = sqlite3_step(pIter->pIdxIter);
+ rc = tdsqlite3_step(pIter->pIdxIter);
if( rc!=SQLITE_ROW ){
rc = resetAndCollectError(pIter->pIdxIter, &p->zErrmsg);
pIter->bCleanup = 1;
pIter->zIdx = 0;
}else{
- pIter->zIdx = (const char*)sqlite3_column_text(pIter->pIdxIter, 0);
- pIter->iTnum = sqlite3_column_int(pIter->pIdxIter, 1);
- pIter->bUnique = sqlite3_column_int(pIter->pIdxIter, 2);
+ pIter->zIdx = (const char*)tdsqlite3_column_text(pIter->pIdxIter, 0);
+ pIter->iTnum = tdsqlite3_column_int(pIter->pIdxIter, 1);
+ pIter->bUnique = tdsqlite3_column_int(pIter->pIdxIter, 2);
rc = pIter->zIdx ? SQLITE_OK : SQLITE_NOMEM;
}
}
@@ -170300,26 +198376,27 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){
** the second argument is either missing or 0 (not a view).
*/
static void rbuTargetNameFunc(
- sqlite3_context *pCtx,
+ tdsqlite3_context *pCtx,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
- sqlite3rbu *p = sqlite3_user_data(pCtx);
+ tdsqlite3rbu *p = tdsqlite3_user_data(pCtx);
const char *zIn;
assert( argc==1 || argc==2 );
- zIn = (const char*)sqlite3_value_text(argv[0]);
+ zIn = (const char*)tdsqlite3_value_text(argv[0]);
if( zIn ){
if( rbuIsVacuum(p) ){
- if( argc==1 || 0==sqlite3_value_int(argv[1]) ){
- sqlite3_result_text(pCtx, zIn, -1, SQLITE_STATIC);
+ assert( argc==2 || argc==1 );
+ if( argc==1 || 0==tdsqlite3_value_int(argv[1]) ){
+ tdsqlite3_result_text(pCtx, zIn, -1, SQLITE_STATIC);
}
}else{
if( strlen(zIn)>4 && memcmp("data", zIn, 4)==0 ){
int i;
for(i=4; zIn[i]>='0' && zIn[i]<='9'; i++);
if( zIn[i]=='_' && zIn[i+1] ){
- sqlite3_result_text(pCtx, &zIn[i+1], -1, SQLITE_STATIC);
+ tdsqlite3_result_text(pCtx, &zIn[i+1], -1, SQLITE_STATIC);
}
}
}
@@ -170334,12 +198411,12 @@ static void rbuTargetNameFunc(
** left in the RBU handle passed as the first argument. A copy of the
** error code is returned.
*/
-static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){
+static int rbuObjIterFirst(tdsqlite3rbu *p, RbuObjIter *pIter){
int rc;
memset(pIter, 0, sizeof(RbuObjIter));
rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg,
- sqlite3_mprintf(
+ tdsqlite3_mprintf(
"SELECT rbu_target_name(name, type='view') AS target, name "
"FROM sqlite_master "
"WHERE type IN ('table', 'view') AND target IS NOT NULL "
@@ -170361,23 +198438,23 @@ static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){
}
/*
-** This is a wrapper around "sqlite3_mprintf(zFmt, ...)". If an OOM occurs,
+** This is a wrapper around "tdsqlite3_mprintf(zFmt, ...)". If an OOM occurs,
** an error code is stored in the RBU handle passed as the first argument.
**
** If an error has already occurred (p->rc is already set to something other
** than SQLITE_OK), then this function returns NULL without modifying the
-** stored error code. In this case it still calls sqlite3_free() on any
+** stored error code. In this case it still calls tdsqlite3_free() on any
** printf() parameters associated with %z conversions.
*/
-static char *rbuMPrintf(sqlite3rbu *p, const char *zFmt, ...){
+static char *rbuMPrintf(tdsqlite3rbu *p, const char *zFmt, ...){
char *zSql = 0;
va_list ap;
va_start(ap, zFmt);
- zSql = sqlite3_vmprintf(zFmt, ap);
+ zSql = tdsqlite3_vmprintf(zFmt, ap);
if( p->rc==SQLITE_OK ){
if( zSql==0 ) p->rc = SQLITE_NOMEM;
}else{
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
zSql = 0;
}
va_end(ap);
@@ -170385,7 +198462,7 @@ static char *rbuMPrintf(sqlite3rbu *p, const char *zFmt, ...){
}
/*
-** Argument zFmt is a sqlite3_mprintf() style format string. The trailing
+** Argument zFmt is a tdsqlite3_mprintf() style format string. The trailing
** arguments are the usual subsitution values. This function performs
** the printf() style substitutions and executes the result as an SQL
** statement on the RBU handles database.
@@ -170394,19 +198471,19 @@ static char *rbuMPrintf(sqlite3rbu *p, const char *zFmt, ...){
** RBU handle. If an error has already occurred when this function is
** called, it is a no-op.
*/
-static int rbuMPrintfExec(sqlite3rbu *p, sqlite3 *db, const char *zFmt, ...){
+static int rbuMPrintfExec(tdsqlite3rbu *p, tdsqlite3 *db, const char *zFmt, ...){
va_list ap;
char *zSql;
va_start(ap, zFmt);
- zSql = sqlite3_vmprintf(zFmt, ap);
+ zSql = tdsqlite3_vmprintf(zFmt, ap);
if( p->rc==SQLITE_OK ){
if( zSql==0 ){
p->rc = SQLITE_NOMEM;
}else{
- p->rc = sqlite3_exec(db, zSql, 0, 0, &p->zErrmsg);
+ p->rc = tdsqlite3_exec(db, zSql, 0, 0, &p->zErrmsg);
}
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
va_end(ap);
return p->rc;
}
@@ -170421,11 +198498,11 @@ static int rbuMPrintfExec(sqlite3rbu *p, sqlite3 *db, const char *zFmt, ...){
** immediately without attempting the allocation or modifying the stored
** error code.
*/
-static void *rbuMalloc(sqlite3rbu *p, int nByte){
+static void *rbuMalloc(tdsqlite3rbu *p, tdsqlite3_int64 nByte){
void *pRet = 0;
if( p->rc==SQLITE_OK ){
assert( nByte>0 );
- pRet = sqlite3_malloc64(nByte);
+ pRet = tdsqlite3_malloc64(nByte);
if( pRet==0 ){
p->rc = SQLITE_NOMEM;
}else{
@@ -170441,8 +198518,8 @@ static void *rbuMalloc(sqlite3rbu *p, int nByte){
** there is room for at least nCol elements. If an OOM occurs, store an
** error code in the RBU handle passed as the first argument.
*/
-static void rbuAllocateIterArrays(sqlite3rbu *p, RbuObjIter *pIter, int nCol){
- int nByte = (2*sizeof(char*) + sizeof(int) + 3*sizeof(u8)) * nCol;
+static void rbuAllocateIterArrays(tdsqlite3rbu *p, RbuObjIter *pIter, int nCol){
+ tdsqlite3_int64 nByte = (2*sizeof(char*) + sizeof(int) + 3*sizeof(u8)) * nCol;
char **azNew;
azNew = (char**)rbuMalloc(p, nByte);
@@ -170458,9 +198535,9 @@ static void rbuAllocateIterArrays(sqlite3rbu *p, RbuObjIter *pIter, int nCol){
/*
** The first argument must be a nul-terminated string. This function
-** returns a copy of the string in memory obtained from sqlite3_malloc().
+** returns a copy of the string in memory obtained from tdsqlite3_malloc().
** It is the responsibility of the caller to eventually free this memory
-** using sqlite3_free().
+** using tdsqlite3_free().
**
** If an OOM condition is encountered when attempting to allocate memory,
** output variable (*pRc) is set to SQLITE_NOMEM before returning. Otherwise,
@@ -170469,14 +198546,15 @@ static void rbuAllocateIterArrays(sqlite3rbu *p, RbuObjIter *pIter, int nCol){
static char *rbuStrndup(const char *zStr, int *pRc){
char *zRet = 0;
- assert( *pRc==SQLITE_OK );
- if( zStr ){
- size_t nCopy = strlen(zStr) + 1;
- zRet = (char*)sqlite3_malloc64(nCopy);
- if( zRet ){
- memcpy(zRet, zStr, nCopy);
- }else{
- *pRc = SQLITE_NOMEM;
+ if( *pRc==SQLITE_OK ){
+ if( zStr ){
+ size_t nCopy = strlen(zStr) + 1;
+ zRet = (char*)tdsqlite3_malloc64(nCopy);
+ if( zRet ){
+ memcpy(zRet, zStr, nCopy);
+ }else{
+ *pRc = SQLITE_NOMEM;
+ }
}
}
@@ -170486,16 +198564,16 @@ static char *rbuStrndup(const char *zStr, int *pRc){
/*
** Finalize the statement passed as the second argument.
**
-** If the sqlite3_finalize() call indicates that an error occurs, and the
+** If the tdsqlite3_finalize() call indicates that an error occurs, and the
** rbu handle error code is not already set, set the error code and error
** message accordingly.
*/
-static void rbuFinalize(sqlite3rbu *p, sqlite3_stmt *pStmt){
- sqlite3 *db = sqlite3_db_handle(pStmt);
- int rc = sqlite3_finalize(pStmt);
+static void rbuFinalize(tdsqlite3rbu *p, tdsqlite3_stmt *pStmt){
+ tdsqlite3 *db = tdsqlite3_db_handle(pStmt);
+ int rc = tdsqlite3_finalize(pStmt);
if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){
p->rc = rc;
- p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ p->zErrmsg = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
}
}
@@ -170539,7 +198617,7 @@ static void rbuFinalize(sqlite3rbu *p, sqlite3_stmt *pStmt){
** }
*/
static void rbuTableType(
- sqlite3rbu *p,
+ tdsqlite3rbu *p,
const char *zTab,
int *peType,
int *piTnum,
@@ -170551,43 +198629,43 @@ static void rbuTableType(
** 2) SELECT count(*) FROM sqlite_master where name=%Q
** 3) PRAGMA table_info = ?
*/
- sqlite3_stmt *aStmt[4] = {0, 0, 0, 0};
+ tdsqlite3_stmt *aStmt[4] = {0, 0, 0, 0};
*peType = RBU_PK_NOTABLE;
*piPk = 0;
assert( p->rc==SQLITE_OK );
p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[0], &p->zErrmsg,
- sqlite3_mprintf(
+ tdsqlite3_mprintf(
"SELECT (sql LIKE 'create virtual%%'), rootpage"
" FROM sqlite_master"
" WHERE name=%Q", zTab
));
- if( p->rc!=SQLITE_OK || sqlite3_step(aStmt[0])!=SQLITE_ROW ){
+ if( p->rc!=SQLITE_OK || tdsqlite3_step(aStmt[0])!=SQLITE_ROW ){
/* Either an error, or no such table. */
goto rbuTableType_end;
}
- if( sqlite3_column_int(aStmt[0], 0) ){
+ if( tdsqlite3_column_int(aStmt[0], 0) ){
*peType = RBU_PK_VTAB; /* virtual table */
goto rbuTableType_end;
}
- *piTnum = sqlite3_column_int(aStmt[0], 1);
+ *piTnum = tdsqlite3_column_int(aStmt[0], 1);
p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[1], &p->zErrmsg,
- sqlite3_mprintf("PRAGMA index_list=%Q",zTab)
+ tdsqlite3_mprintf("PRAGMA index_list=%Q",zTab)
);
if( p->rc ) goto rbuTableType_end;
- while( sqlite3_step(aStmt[1])==SQLITE_ROW ){
- const u8 *zOrig = sqlite3_column_text(aStmt[1], 3);
- const u8 *zIdx = sqlite3_column_text(aStmt[1], 1);
+ while( tdsqlite3_step(aStmt[1])==SQLITE_ROW ){
+ const u8 *zOrig = tdsqlite3_column_text(aStmt[1], 3);
+ const u8 *zIdx = tdsqlite3_column_text(aStmt[1], 1);
if( zOrig && zIdx && zOrig[0]=='p' ){
p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[2], &p->zErrmsg,
- sqlite3_mprintf(
+ tdsqlite3_mprintf(
"SELECT rootpage FROM sqlite_master WHERE name = %Q", zIdx
));
if( p->rc==SQLITE_OK ){
- if( sqlite3_step(aStmt[2])==SQLITE_ROW ){
- *piPk = sqlite3_column_int(aStmt[2], 0);
+ if( tdsqlite3_step(aStmt[2])==SQLITE_ROW ){
+ *piPk = tdsqlite3_column_int(aStmt[2], 0);
*peType = RBU_PK_EXTERNAL;
}else{
*peType = RBU_PK_WITHOUT_ROWID;
@@ -170598,11 +198676,11 @@ static void rbuTableType(
}
p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[3], &p->zErrmsg,
- sqlite3_mprintf("PRAGMA table_info=%Q",zTab)
+ tdsqlite3_mprintf("PRAGMA table_info=%Q",zTab)
);
if( p->rc==SQLITE_OK ){
- while( sqlite3_step(aStmt[3])==SQLITE_ROW ){
- if( sqlite3_column_int(aStmt[3],5)>0 ){
+ while( tdsqlite3_step(aStmt[3])==SQLITE_ROW ){
+ if( tdsqlite3_column_int(aStmt[3],5)>0 ){
*peType = RBU_PK_IPK; /* explicit IPK column */
goto rbuTableType_end;
}
@@ -170622,28 +198700,35 @@ rbuTableType_end: {
** This is a helper function for rbuObjIterCacheTableInfo(). It populates
** the pIter->abIndexed[] array.
*/
-static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){
- sqlite3_stmt *pList = 0;
+static void rbuObjIterCacheIndexedCols(tdsqlite3rbu *p, RbuObjIter *pIter){
+ tdsqlite3_stmt *pList = 0;
int bIndex = 0;
if( p->rc==SQLITE_OK ){
memcpy(pIter->abIndexed, pIter->abTblPk, sizeof(u8)*pIter->nTblCol);
p->rc = prepareFreeAndCollectError(p->dbMain, &pList, &p->zErrmsg,
- sqlite3_mprintf("PRAGMA main.index_list = %Q", pIter->zTbl)
+ tdsqlite3_mprintf("PRAGMA main.index_list = %Q", pIter->zTbl)
);
}
pIter->nIndex = 0;
- while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pList) ){
- const char *zIdx = (const char*)sqlite3_column_text(pList, 1);
- sqlite3_stmt *pXInfo = 0;
+ while( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pList) ){
+ const char *zIdx = (const char*)tdsqlite3_column_text(pList, 1);
+ int bPartial = tdsqlite3_column_int(pList, 4);
+ tdsqlite3_stmt *pXInfo = 0;
if( zIdx==0 ) break;
+ if( bPartial ){
+ memset(pIter->abIndexed, 0x01, sizeof(u8)*pIter->nTblCol);
+ }
p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg,
- sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx)
+ tdsqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx)
);
- while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){
- int iCid = sqlite3_column_int(pXInfo, 1);
+ while( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pXInfo) ){
+ int iCid = tdsqlite3_column_int(pXInfo, 1);
if( iCid>=0 ) pIter->abIndexed[iCid] = 1;
+ if( iCid==-2 ){
+ memset(pIter->abIndexed, 0x01, sizeof(u8)*pIter->nTblCol);
+ }
}
rbuFinalize(p, pXInfo);
bIndex = 1;
@@ -170669,9 +198754,9 @@ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){
** an error does occur, an error code and error message are also left in
** the RBU handle.
*/
-static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){
+static int rbuObjIterCacheTableInfo(tdsqlite3rbu *p, RbuObjIter *pIter){
if( pIter->azTblCol==0 ){
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
int nCol = 0;
int i; /* for() loop iterator variable */
int bRbuRowid = 0; /* If input table has column "rbu_rowid" */
@@ -170683,7 +198768,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){
rbuTableType(p, pIter->zTbl, &pIter->eType, &iTnum, &pIter->iPkTnum);
if( p->rc==SQLITE_OK && pIter->eType==RBU_PK_NOTABLE ){
p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("no such table: %s", pIter->zTbl);
+ p->zErrmsg = tdsqlite3_mprintf("no such table: %s", pIter->zTbl);
}
if( p->rc ) return p->rc;
if( pIter->zIdx==0 ) pIter->iTnum = iTnum;
@@ -170697,24 +198782,24 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){
** of the input table. Ignore any input table columns that begin with
** "rbu_". */
p->rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg,
- sqlite3_mprintf("SELECT * FROM '%q'", pIter->zDataTbl)
+ tdsqlite3_mprintf("SELECT * FROM '%q'", pIter->zDataTbl)
);
if( p->rc==SQLITE_OK ){
- nCol = sqlite3_column_count(pStmt);
+ nCol = tdsqlite3_column_count(pStmt);
rbuAllocateIterArrays(p, pIter, nCol);
}
for(i=0; p->rc==SQLITE_OK && i<nCol; i++){
- const char *zName = (const char*)sqlite3_column_name(pStmt, i);
- if( sqlite3_strnicmp("rbu_", zName, 4) ){
+ const char *zName = (const char*)tdsqlite3_column_name(pStmt, i);
+ if( tdsqlite3_strnicmp("rbu_", zName, 4) ){
char *zCopy = rbuStrndup(zName, &p->rc);
pIter->aiSrcOrder[pIter->nTblCol] = pIter->nTblCol;
pIter->azTblCol[pIter->nTblCol++] = zCopy;
}
- else if( 0==sqlite3_stricmp("rbu_rowid", zName) ){
+ else if( 0==tdsqlite3_stricmp("rbu_rowid", zName) ){
bRbuRowid = 1;
}
}
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
pStmt = 0;
if( p->rc==SQLITE_OK
@@ -170722,7 +198807,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){
&& bRbuRowid!=(pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE)
){
p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf(
+ p->zErrmsg = tdsqlite3_mprintf(
"table %q %s rbu_rowid column", pIter->zDataTbl,
(bRbuRowid ? "may not have" : "requires")
);
@@ -170733,24 +198818,24 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){
** aiTblOrder[] arrays at the same time. */
if( p->rc==SQLITE_OK ){
p->rc = prepareFreeAndCollectError(p->dbMain, &pStmt, &p->zErrmsg,
- sqlite3_mprintf("PRAGMA table_info(%Q)", pIter->zTbl)
+ tdsqlite3_mprintf("PRAGMA table_info(%Q)", pIter->zTbl)
);
}
- while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
- const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
+ while( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ const char *zName = (const char*)tdsqlite3_column_text(pStmt, 1);
if( zName==0 ) break; /* An OOM - finalize() below returns S_NOMEM */
for(i=iOrder; i<pIter->nTblCol; i++){
if( 0==strcmp(zName, pIter->azTblCol[i]) ) break;
}
if( i==pIter->nTblCol ){
p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("column missing from %q: %s",
+ p->zErrmsg = tdsqlite3_mprintf("column missing from %q: %s",
pIter->zDataTbl, zName
);
}else{
- int iPk = sqlite3_column_int(pStmt, 5);
- int bNotNull = sqlite3_column_int(pStmt, 3);
- const char *zType = (const char*)sqlite3_column_text(pStmt, 2);
+ int iPk = tdsqlite3_column_int(pStmt, 5);
+ int bNotNull = tdsqlite3_column_int(pStmt, 3);
+ const char *zType = (const char*)tdsqlite3_column_text(pStmt, 2);
if( i!=iOrder ){
SWAP(int, pIter->aiSrcOrder[i], pIter->aiSrcOrder[iOrder]);
@@ -170758,7 +198843,8 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){
}
pIter->azTblType[iOrder] = rbuStrndup(zType, &p->rc);
- pIter->abTblPk[iOrder] = (iPk!=0);
+ assert( iPk>=0 );
+ pIter->abTblPk[iOrder] = (u8)iPk;
pIter->abNotNull[iOrder] = (u8)bNotNull || (iPk!=0);
iOrder++;
}
@@ -170779,7 +198865,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){
** column names currently stored in the pIter->azTblCol[] array.
*/
static char *rbuObjIterGetCollist(
- sqlite3rbu *p, /* RBU object */
+ tdsqlite3rbu *p, /* RBU object */
RbuObjIter *pIter /* Object iterator for column names */
){
char *zList = 0;
@@ -170794,6 +198880,213 @@ static char *rbuObjIterGetCollist(
}
/*
+** Return a comma separated list of the quoted PRIMARY KEY column names,
+** in order, for the current table. Before each column name, add the text
+** zPre. After each column name, add the zPost text. Use zSeparator as
+** the separator text (usually ", ").
+*/
+static char *rbuObjIterGetPkList(
+ tdsqlite3rbu *p, /* RBU object */
+ RbuObjIter *pIter, /* Object iterator for column names */
+ const char *zPre, /* Before each quoted column name */
+ const char *zSeparator, /* Separator to use between columns */
+ const char *zPost /* After each quoted column name */
+){
+ int iPk = 1;
+ char *zRet = 0;
+ const char *zSep = "";
+ while( 1 ){
+ int i;
+ for(i=0; i<pIter->nTblCol; i++){
+ if( (int)pIter->abTblPk[i]==iPk ){
+ const char *zCol = pIter->azTblCol[i];
+ zRet = rbuMPrintf(p, "%z%s%s\"%w\"%s", zRet, zSep, zPre, zCol, zPost);
+ zSep = zSeparator;
+ break;
+ }
+ }
+ if( i==pIter->nTblCol ) break;
+ iPk++;
+ }
+ return zRet;
+}
+
+/*
+** This function is called as part of restarting an RBU vacuum within
+** stage 1 of the process (while the *-oal file is being built) while
+** updating a table (not an index). The table may be a rowid table or
+** a WITHOUT ROWID table. It queries the target database to find the
+** largest key that has already been written to the target table and
+** constructs a WHERE clause that can be used to extract the remaining
+** rows from the source table. For a rowid table, the WHERE clause
+** is of the form:
+**
+** "WHERE _rowid_ > ?"
+**
+** and for WITHOUT ROWID tables:
+**
+** "WHERE (key1, key2) > (?, ?)"
+**
+** Instead of "?" placeholders, the actual WHERE clauses created by
+** this function contain literal SQL values.
+*/
+static char *rbuVacuumTableStart(
+ tdsqlite3rbu *p, /* RBU handle */
+ RbuObjIter *pIter, /* RBU iterator object */
+ int bRowid, /* True for a rowid table */
+ const char *zWrite /* Target table name prefix */
+){
+ tdsqlite3_stmt *pMax = 0;
+ char *zRet = 0;
+ if( bRowid ){
+ p->rc = prepareFreeAndCollectError(p->dbMain, &pMax, &p->zErrmsg,
+ tdsqlite3_mprintf(
+ "SELECT max(_rowid_) FROM \"%s%w\"", zWrite, pIter->zTbl
+ )
+ );
+ if( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pMax) ){
+ tdsqlite3_int64 iMax = tdsqlite3_column_int64(pMax, 0);
+ zRet = rbuMPrintf(p, " WHERE _rowid_ > %lld ", iMax);
+ }
+ rbuFinalize(p, pMax);
+ }else{
+ char *zOrder = rbuObjIterGetPkList(p, pIter, "", ", ", " DESC");
+ char *zSelect = rbuObjIterGetPkList(p, pIter, "quote(", "||','||", ")");
+ char *zList = rbuObjIterGetPkList(p, pIter, "", ", ", "");
+
+ if( p->rc==SQLITE_OK ){
+ p->rc = prepareFreeAndCollectError(p->dbMain, &pMax, &p->zErrmsg,
+ tdsqlite3_mprintf(
+ "SELECT %s FROM \"%s%w\" ORDER BY %s LIMIT 1",
+ zSelect, zWrite, pIter->zTbl, zOrder
+ )
+ );
+ if( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pMax) ){
+ const char *zVal = (const char*)tdsqlite3_column_text(pMax, 0);
+ zRet = rbuMPrintf(p, " WHERE (%s) > (%s) ", zList, zVal);
+ }
+ rbuFinalize(p, pMax);
+ }
+
+ tdsqlite3_free(zOrder);
+ tdsqlite3_free(zSelect);
+ tdsqlite3_free(zList);
+ }
+ return zRet;
+}
+
+/*
+** This function is called as part of restating an RBU vacuum when the
+** current operation is writing content to an index. If possible, it
+** queries the target index b-tree for the largest key already written to
+** it, then composes and returns an expression that can be used in a WHERE
+** clause to select the remaining required rows from the source table.
+** It is only possible to return such an expression if:
+**
+** * The index contains no DESC columns, and
+** * The last key written to the index before the operation was
+** suspended does not contain any NULL values.
+**
+** The expression is of the form:
+**
+** (index-field1, index-field2, ...) > (?, ?, ...)
+**
+** except that the "?" placeholders are replaced with literal values.
+**
+** If the expression cannot be created, NULL is returned. In this case,
+** the caller has to use an OFFSET clause to extract only the required
+** rows from the sourct table, just as it does for an RBU update operation.
+*/
+char *rbuVacuumIndexStart(
+ tdsqlite3rbu *p, /* RBU handle */
+ RbuObjIter *pIter /* RBU iterator object */
+){
+ char *zOrder = 0;
+ char *zLhs = 0;
+ char *zSelect = 0;
+ char *zVector = 0;
+ char *zRet = 0;
+ int bFailed = 0;
+ const char *zSep = "";
+ int iCol = 0;
+ tdsqlite3_stmt *pXInfo = 0;
+
+ p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg,
+ tdsqlite3_mprintf("PRAGMA main.index_xinfo = %Q", pIter->zIdx)
+ );
+ while( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pXInfo) ){
+ int iCid = tdsqlite3_column_int(pXInfo, 1);
+ const char *zCollate = (const char*)tdsqlite3_column_text(pXInfo, 4);
+ const char *zCol;
+ if( tdsqlite3_column_int(pXInfo, 3) ){
+ bFailed = 1;
+ break;
+ }
+
+ if( iCid<0 ){
+ if( pIter->eType==RBU_PK_IPK ){
+ int i;
+ for(i=0; pIter->abTblPk[i]==0; i++);
+ assert( i<pIter->nTblCol );
+ zCol = pIter->azTblCol[i];
+ }else{
+ zCol = "_rowid_";
+ }
+ }else{
+ zCol = pIter->azTblCol[iCid];
+ }
+
+ zLhs = rbuMPrintf(p, "%z%s \"%w\" COLLATE %Q",
+ zLhs, zSep, zCol, zCollate
+ );
+ zOrder = rbuMPrintf(p, "%z%s \"rbu_imp_%d%w\" COLLATE %Q DESC",
+ zOrder, zSep, iCol, zCol, zCollate
+ );
+ zSelect = rbuMPrintf(p, "%z%s quote(\"rbu_imp_%d%w\")",
+ zSelect, zSep, iCol, zCol
+ );
+ zSep = ", ";
+ iCol++;
+ }
+ rbuFinalize(p, pXInfo);
+ if( bFailed ) goto index_start_out;
+
+ if( p->rc==SQLITE_OK ){
+ tdsqlite3_stmt *pSel = 0;
+
+ p->rc = prepareFreeAndCollectError(p->dbMain, &pSel, &p->zErrmsg,
+ tdsqlite3_mprintf("SELECT %s FROM \"rbu_imp_%w\" ORDER BY %s LIMIT 1",
+ zSelect, pIter->zTbl, zOrder
+ )
+ );
+ if( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pSel) ){
+ zSep = "";
+ for(iCol=0; iCol<pIter->nCol; iCol++){
+ const char *zQuoted = (const char*)tdsqlite3_column_text(pSel, iCol);
+ if( zQuoted[0]=='N' ){
+ bFailed = 1;
+ break;
+ }
+ zVector = rbuMPrintf(p, "%z%s%s", zVector, zSep, zQuoted);
+ zSep = ", ";
+ }
+
+ if( !bFailed ){
+ zRet = rbuMPrintf(p, "(%s) > (%s)", zLhs, zVector);
+ }
+ }
+ rbuFinalize(p, pSel);
+ }
+
+ index_start_out:
+ tdsqlite3_free(zOrder);
+ tdsqlite3_free(zSelect);
+ tdsqlite3_free(zVector);
+ tdsqlite3_free(zLhs);
+ return zRet;
+}
+
+/*
** This function is used to create a SELECT list (the list of SQL
** expressions that follows a SELECT keyword) for a SELECT statement
** used to read from an data_xxx or rbu_tmp_xxx table while updating the
@@ -170818,7 +199111,7 @@ static char *rbuObjIterGetCollist(
** pzWhere: ...
*/
static char *rbuObjIterGetIndexCols(
- sqlite3rbu *p, /* RBU object */
+ tdsqlite3rbu *p, /* RBU object */
RbuObjIter *pIter, /* Object iterator for column names */
char **pzImposterCols, /* OUT: Columns for imposter table */
char **pzImposterPk, /* OUT: Imposter PK clause */
@@ -170826,7 +199119,7 @@ static char *rbuObjIterGetIndexCols(
int *pnBind /* OUT: Trbul number of columns */
){
int rc = p->rc; /* Error code */
- int rc2; /* sqlite3_finalize() return code */
+ int rc2; /* tdsqlite3_finalize() return code */
char *zRet = 0; /* String to return */
char *zImpCols = 0; /* String to return via *pzImposterCols */
char *zImpPK = 0; /* String to return via *pzImposterPK */
@@ -170834,52 +199127,60 @@ static char *rbuObjIterGetIndexCols(
int nBind = 0; /* Value to return via *pnBind */
const char *zCom = ""; /* Set to ", " later on */
const char *zAnd = ""; /* Set to " AND " later on */
- sqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = ? */
+ tdsqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = ? */
if( rc==SQLITE_OK ){
assert( p->zErrmsg==0 );
rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg,
- sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", pIter->zIdx)
+ tdsqlite3_mprintf("PRAGMA main.index_xinfo = %Q", pIter->zIdx)
);
}
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){
- int iCid = sqlite3_column_int(pXInfo, 1);
- int bDesc = sqlite3_column_int(pXInfo, 3);
- const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4);
- const char *zCol;
+ while( rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pXInfo) ){
+ int iCid = tdsqlite3_column_int(pXInfo, 1);
+ int bDesc = tdsqlite3_column_int(pXInfo, 3);
+ const char *zCollate = (const char*)tdsqlite3_column_text(pXInfo, 4);
+ const char *zCol = 0;
const char *zType;
- if( iCid<0 ){
- /* An integer primary key. If the table has an explicit IPK, use
- ** its name. Otherwise, use "rbu_rowid". */
- if( pIter->eType==RBU_PK_IPK ){
- int i;
- for(i=0; pIter->abTblPk[i]==0; i++);
- assert( i<pIter->nTblCol );
- zCol = pIter->azTblCol[i];
- }else if( rbuIsVacuum(p) ){
- zCol = "_rowid_";
+ if( iCid==-2 ){
+ int iSeq = tdsqlite3_column_int(pXInfo, 0);
+ zRet = tdsqlite3_mprintf("%z%s(%.*s) COLLATE %Q", zRet, zCom,
+ pIter->aIdxCol[iSeq].nSpan, pIter->aIdxCol[iSeq].zSpan, zCollate
+ );
+ zType = "";
+ }else {
+ if( iCid<0 ){
+ /* An integer primary key. If the table has an explicit IPK, use
+ ** its name. Otherwise, use "rbu_rowid". */
+ if( pIter->eType==RBU_PK_IPK ){
+ int i;
+ for(i=0; pIter->abTblPk[i]==0; i++);
+ assert( i<pIter->nTblCol );
+ zCol = pIter->azTblCol[i];
+ }else if( rbuIsVacuum(p) ){
+ zCol = "_rowid_";
+ }else{
+ zCol = "rbu_rowid";
+ }
+ zType = "INTEGER";
}else{
- zCol = "rbu_rowid";
+ zCol = pIter->azTblCol[iCid];
+ zType = pIter->azTblType[iCid];
}
- zType = "INTEGER";
- }else{
- zCol = pIter->azTblCol[iCid];
- zType = pIter->azTblType[iCid];
+ zRet = tdsqlite3_mprintf("%z%s\"%w\" COLLATE %Q", zRet, zCom,zCol,zCollate);
}
- zRet = sqlite3_mprintf("%z%s\"%w\" COLLATE %Q", zRet, zCom, zCol, zCollate);
- if( pIter->bUnique==0 || sqlite3_column_int(pXInfo, 5) ){
+ if( pIter->bUnique==0 || tdsqlite3_column_int(pXInfo, 5) ){
const char *zOrder = (bDesc ? " DESC" : "");
- zImpPK = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\"%s",
+ zImpPK = tdsqlite3_mprintf("%z%s\"rbu_imp_%d%w\"%s",
zImpPK, zCom, nBind, zCol, zOrder
);
}
- zImpCols = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\" %s COLLATE %Q",
+ zImpCols = tdsqlite3_mprintf("%z%s\"rbu_imp_%d%w\" %s COLLATE %Q",
zImpCols, zCom, nBind, zCol, zType, zCollate
);
- zWhere = sqlite3_mprintf(
+ zWhere = tdsqlite3_mprintf(
"%z%s\"rbu_imp_%d%w\" IS ?", zWhere, zAnd, nBind, zCol
);
if( zRet==0 || zImpPK==0 || zImpCols==0 || zWhere==0 ) rc = SQLITE_NOMEM;
@@ -170888,14 +199189,14 @@ static char *rbuObjIterGetIndexCols(
nBind++;
}
- rc2 = sqlite3_finalize(pXInfo);
+ rc2 = tdsqlite3_finalize(pXInfo);
if( rc==SQLITE_OK ) rc = rc2;
if( rc!=SQLITE_OK ){
- sqlite3_free(zRet);
- sqlite3_free(zImpCols);
- sqlite3_free(zImpPK);
- sqlite3_free(zWhere);
+ tdsqlite3_free(zRet);
+ tdsqlite3_free(zImpCols);
+ tdsqlite3_free(zImpPK);
+ tdsqlite3_free(zWhere);
zRet = 0;
zImpCols = 0;
zImpPK = 0;
@@ -170922,7 +199223,7 @@ static char *rbuObjIterGetIndexCols(
** the text ", old._rowid_" to the returned value.
*/
static char *rbuObjIterGetOldlist(
- sqlite3rbu *p,
+ tdsqlite3rbu *p,
RbuObjIter *pIter,
const char *zObj
){
@@ -170933,9 +199234,9 @@ static char *rbuObjIterGetOldlist(
for(i=0; i<pIter->nTblCol; i++){
if( pIter->abIndexed[i] ){
const char *zCol = pIter->azTblCol[i];
- zList = sqlite3_mprintf("%z%s%s.\"%w\"", zList, zS, zObj, zCol);
+ zList = tdsqlite3_mprintf("%z%s%s.\"%w\"", zList, zS, zObj, zCol);
}else{
- zList = sqlite3_mprintf("%z%sNULL", zList, zS);
+ zList = tdsqlite3_mprintf("%z%sNULL", zList, zS);
}
zS = ", ";
if( zList==0 ){
@@ -170963,7 +199264,7 @@ static char *rbuObjIterGetOldlist(
** "b = ?1 AND c = ?2"
*/
static char *rbuObjIterGetWhere(
- sqlite3rbu *p,
+ tdsqlite3rbu *p,
RbuObjIter *pIter
){
char *zList = 0;
@@ -171003,9 +199304,9 @@ static char *rbuObjIterGetWhere(
** stored in the (p->nCol+1)'th column. Set the error code and error message
** of the RBU handle to something reflecting this.
*/
-static void rbuBadControlError(sqlite3rbu *p){
+static void rbuBadControlError(tdsqlite3rbu *p){
p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("invalid rbu_control value");
+ p->zErrmsg = tdsqlite3_mprintf("invalid rbu_control value");
}
@@ -171016,9 +199317,9 @@ static void rbuBadControlError(sqlite3rbu *p){
** passed as the second argument currently points to if the rbu_control
** column of the data_xxx table entry is set to zMask.
**
-** The memory for the returned string is obtained from sqlite3_malloc().
+** The memory for the returned string is obtained from tdsqlite3_malloc().
** It is the responsibility of the caller to eventually free it using
-** sqlite3_free().
+** tdsqlite3_free().
**
** If an OOM error is encountered when allocating space for the new
** string, an error code is left in the rbu handle passed as the first
@@ -171027,7 +199328,7 @@ static void rbuBadControlError(sqlite3rbu *p){
** attempting the allocation or modifying the stored error code.
*/
static char *rbuObjIterGetSetlist(
- sqlite3rbu *p,
+ tdsqlite3rbu *p,
RbuObjIter *pIter,
const char *zMask
){
@@ -171070,9 +199371,9 @@ static char *rbuObjIterGetSetlist(
** "?" expressions. For example, if nByte is 3, return a pointer to
** a buffer containing the string "?,?,?".
**
-** The memory for the returned string is obtained from sqlite3_malloc().
+** The memory for the returned string is obtained from tdsqlite3_malloc().
** It is the responsibility of the caller to eventually free it using
-** sqlite3_free().
+** tdsqlite3_free().
**
** If an OOM error is encountered when allocating space for the new
** string, an error code is left in the rbu handle passed as the first
@@ -171080,9 +199381,9 @@ static char *rbuObjIterGetSetlist(
** when this function is called, NULL is returned immediately, without
** attempting the allocation or modifying the stored error code.
*/
-static char *rbuObjIterGetBindlist(sqlite3rbu *p, int nBind){
+static char *rbuObjIterGetBindlist(tdsqlite3rbu *p, int nBind){
char *zRet = 0;
- int nByte = nBind*2 + 1;
+ tdsqlite3_int64 nByte = 2*(tdsqlite3_int64)nBind + 1;
zRet = (char*)rbuMalloc(p, nByte);
if( zRet ){
@@ -171107,24 +199408,24 @@ static char *rbuObjIterGetBindlist(sqlite3rbu *p, int nBind){
**
** PRIMARY KEY("b", "a" DESC)
*/
-static char *rbuWithoutRowidPK(sqlite3rbu *p, RbuObjIter *pIter){
+static char *rbuWithoutRowidPK(tdsqlite3rbu *p, RbuObjIter *pIter){
char *z = 0;
assert( pIter->zIdx==0 );
if( p->rc==SQLITE_OK ){
const char *zSep = "PRIMARY KEY(";
- sqlite3_stmt *pXList = 0; /* PRAGMA index_list = (pIter->zTbl) */
- sqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = <pk-index> */
+ tdsqlite3_stmt *pXList = 0; /* PRAGMA index_list = (pIter->zTbl) */
+ tdsqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = <pk-index> */
p->rc = prepareFreeAndCollectError(p->dbMain, &pXList, &p->zErrmsg,
- sqlite3_mprintf("PRAGMA main.index_list = %Q", pIter->zTbl)
+ tdsqlite3_mprintf("PRAGMA main.index_list = %Q", pIter->zTbl)
);
- while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXList) ){
- const char *zOrig = (const char*)sqlite3_column_text(pXList,3);
+ while( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pXList) ){
+ const char *zOrig = (const char*)tdsqlite3_column_text(pXList,3);
if( zOrig && strcmp(zOrig, "pk")==0 ){
- const char *zIdx = (const char*)sqlite3_column_text(pXList,1);
+ const char *zIdx = (const char*)tdsqlite3_column_text(pXList,1);
if( zIdx ){
p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg,
- sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx)
+ tdsqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx)
);
}
break;
@@ -171132,11 +199433,11 @@ static char *rbuWithoutRowidPK(sqlite3rbu *p, RbuObjIter *pIter){
}
rbuFinalize(p, pXList);
- while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){
- if( sqlite3_column_int(pXInfo, 5) ){
- /* int iCid = sqlite3_column_int(pXInfo, 0); */
- const char *zCol = (const char*)sqlite3_column_text(pXInfo, 2);
- const char *zDesc = sqlite3_column_int(pXInfo, 3) ? " DESC" : "";
+ while( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pXInfo) ){
+ if( tdsqlite3_column_int(pXInfo, 5) ){
+ /* int iCid = tdsqlite3_column_int(pXInfo, 0); */
+ const char *zCol = (const char*)tdsqlite3_column_text(pXInfo, 2);
+ const char *zDesc = tdsqlite3_column_int(pXInfo, 3) ? " DESC" : "";
z = rbuMPrintf(p, "%z%s\"%w\"%s", z, zSep, zCol, zDesc);
zSep = ", ";
}
@@ -171166,12 +199467,12 @@ static char *rbuWithoutRowidPK(sqlite3rbu *p, RbuObjIter *pIter){
** CREATE TABLE rbu_imposter2(c1 TEXT, c2 REAL, id INTEGER) WITHOUT ROWID;
**
*/
-static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){
+static void rbuCreateImposterTable2(tdsqlite3rbu *p, RbuObjIter *pIter){
if( p->rc==SQLITE_OK && pIter->eType==RBU_PK_EXTERNAL ){
int tnum = pIter->iPkTnum; /* Root page of PK index */
- sqlite3_stmt *pQuery = 0; /* SELECT name ... WHERE rootpage = $tnum */
+ tdsqlite3_stmt *pQuery = 0; /* SELECT name ... WHERE rootpage = $tnum */
const char *zIdx = 0; /* Name of PK index */
- sqlite3_stmt *pXInfo = 0; /* PRAGMA main.index_xinfo = $zIdx */
+ tdsqlite3_stmt *pXInfo = 0; /* PRAGMA main.index_xinfo = $zIdx */
const char *zComma = "";
char *zCols = 0; /* Used to build up list of table cols */
char *zPk = 0; /* Used to build up table PK declaration */
@@ -171183,25 +199484,25 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){
"SELECT name FROM sqlite_master WHERE rootpage = ?"
);
if( p->rc==SQLITE_OK ){
- sqlite3_bind_int(pQuery, 1, tnum);
- if( SQLITE_ROW==sqlite3_step(pQuery) ){
- zIdx = (const char*)sqlite3_column_text(pQuery, 0);
+ tdsqlite3_bind_int(pQuery, 1, tnum);
+ if( SQLITE_ROW==tdsqlite3_step(pQuery) ){
+ zIdx = (const char*)tdsqlite3_column_text(pQuery, 0);
}
}
if( zIdx ){
p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg,
- sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx)
+ tdsqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx)
);
}
rbuFinalize(p, pQuery);
- while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){
- int bKey = sqlite3_column_int(pXInfo, 5);
+ while( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pXInfo) ){
+ int bKey = tdsqlite3_column_int(pXInfo, 5);
if( bKey ){
- int iCid = sqlite3_column_int(pXInfo, 1);
- int bDesc = sqlite3_column_int(pXInfo, 3);
- const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4);
- zCols = rbuMPrintf(p, "%z%sc%d %s COLLATE %s", zCols, zComma,
+ int iCid = tdsqlite3_column_int(pXInfo, 1);
+ int bDesc = tdsqlite3_column_int(pXInfo, 3);
+ const char *zCollate = (const char*)tdsqlite3_column_text(pXInfo, 4);
+ zCols = rbuMPrintf(p, "%z%sc%d %s COLLATE %Q", zCols, zComma,
iCid, pIter->azTblType[iCid], zCollate
);
zPk = rbuMPrintf(p, "%z%sc%d%s", zPk, zComma, iCid, bDesc?" DESC":"");
@@ -171211,12 +199512,12 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){
zCols = rbuMPrintf(p, "%z, id INTEGER", zCols);
rbuFinalize(p, pXInfo);
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum);
+ tdsqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum);
rbuMPrintfExec(p, p->dbMain,
"CREATE TABLE rbu_imposter2(%z, PRIMARY KEY(%z)) WITHOUT ROWID",
zCols, zPk
);
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0);
+ tdsqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0);
}
}
@@ -171224,7 +199525,7 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){
** If an error has already occurred when this function is called, it
** immediately returns zero (without doing any work). Or, if an error
** occurs during the execution of this function, it sets the error code
-** in the sqlite3rbu object indicated by the first argument and returns
+** in the tdsqlite3rbu object indicated by the first argument and returns
** zero.
**
** The iterator passed as the second argument is guaranteed to point to
@@ -171240,20 +199541,20 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){
** collation sequences. For tables that do not have an external PRIMARY
** KEY, it also means the same PRIMARY KEY declaration.
*/
-static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){
+static void rbuCreateImposterTable(tdsqlite3rbu *p, RbuObjIter *pIter){
if( p->rc==SQLITE_OK && pIter->eType!=RBU_PK_VTAB ){
int tnum = pIter->iTnum;
const char *zComma = "";
char *zSql = 0;
int iCol;
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1);
+ tdsqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1);
for(iCol=0; p->rc==SQLITE_OK && iCol<pIter->nTblCol; iCol++){
const char *zPk = "";
const char *zCol = pIter->azTblCol[iCol];
const char *zColl = 0;
- p->rc = sqlite3_table_column_metadata(
+ p->rc = tdsqlite3_table_column_metadata(
p->dbMain, "main", pIter->zTbl, zCol, 0, &zColl, 0, 0, 0
);
@@ -171262,7 +199563,7 @@ static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){
** "PRIMARY KEY" to the imposter table column declaration. */
zPk = "PRIMARY KEY ";
}
- zSql = rbuMPrintf(p, "%z%s\"%w\" %s %sCOLLATE %s%s",
+ zSql = rbuMPrintf(p, "%z%s\"%w\" %s %sCOLLATE %Q%s",
zSql, zComma, zCol, pIter->azTblType[iCol], zPk, zColl,
(pIter->abNotNull[iCol] ? " NOT NULL" : "")
);
@@ -171276,12 +199577,12 @@ static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){
}
}
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum);
+ tdsqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum);
rbuMPrintfExec(p, p->dbMain, "CREATE TABLE \"rbu_imp_%w\"(%z)%s",
pIter->zTbl, zSql,
(pIter->eType==RBU_PK_WITHOUT_ROWID ? " WITHOUT ROWID" : "")
);
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0);
+ tdsqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0);
}
}
@@ -171297,7 +199598,7 @@ static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){
** virtual table.
*/
static void rbuObjIterPrepareTmpInsert(
- sqlite3rbu *p,
+ tdsqlite3rbu *p,
RbuObjIter *pIter,
const char *zCollist,
const char *zRbuRowid
@@ -171307,7 +199608,7 @@ static void rbuObjIterPrepareTmpInsert(
if( zBind ){
assert( pIter->pTmpInsert==0 );
p->rc = prepareFreeAndCollectError(
- p->dbRbu, &pIter->pTmpInsert, &p->zErrmsg, sqlite3_mprintf(
+ p->dbRbu, &pIter->pTmpInsert, &p->zErrmsg, tdsqlite3_mprintf(
"INSERT INTO %s.'rbu_tmp_%q'(rbu_control,%s%s) VALUES(%z)",
p->zStateDb, pIter->zDataTbl, zCollist, zRbuRowid, zBind
));
@@ -171315,42 +199616,137 @@ static void rbuObjIterPrepareTmpInsert(
}
static void rbuTmpInsertFunc(
- sqlite3_context *pCtx,
+ tdsqlite3_context *pCtx,
int nVal,
- sqlite3_value **apVal
+ tdsqlite3_value **apVal
){
- sqlite3rbu *p = sqlite3_user_data(pCtx);
+ tdsqlite3rbu *p = tdsqlite3_user_data(pCtx);
int rc = SQLITE_OK;
int i;
- assert( sqlite3_value_int(apVal[0])!=0
+ assert( tdsqlite3_value_int(apVal[0])!=0
|| p->objiter.eType==RBU_PK_EXTERNAL
|| p->objiter.eType==RBU_PK_NONE
);
- if( sqlite3_value_int(apVal[0])!=0 ){
+ if( tdsqlite3_value_int(apVal[0])!=0 ){
p->nPhaseOneStep += p->objiter.nIndex;
}
for(i=0; rc==SQLITE_OK && i<nVal; i++){
- rc = sqlite3_bind_value(p->objiter.pTmpInsert, i+1, apVal[i]);
+ rc = tdsqlite3_bind_value(p->objiter.pTmpInsert, i+1, apVal[i]);
}
if( rc==SQLITE_OK ){
- sqlite3_step(p->objiter.pTmpInsert);
- rc = sqlite3_reset(p->objiter.pTmpInsert);
+ tdsqlite3_step(p->objiter.pTmpInsert);
+ rc = tdsqlite3_reset(p->objiter.pTmpInsert);
}
if( rc!=SQLITE_OK ){
- sqlite3_result_error_code(pCtx, rc);
+ tdsqlite3_result_error_code(pCtx, rc);
}
}
+static char *rbuObjIterGetIndexWhere(tdsqlite3rbu *p, RbuObjIter *pIter){
+ tdsqlite3_stmt *pStmt = 0;
+ int rc = p->rc;
+ char *zRet = 0;
+
+ assert( pIter->zIdxSql==0 && pIter->nIdxCol==0 && pIter->aIdxCol==0 );
+
+ if( rc==SQLITE_OK ){
+ rc = prepareAndCollectError(p->dbMain, &pStmt, &p->zErrmsg,
+ "SELECT trim(sql) FROM sqlite_master WHERE type='index' AND name=?"
+ );
+ }
+ if( rc==SQLITE_OK ){
+ int rc2;
+ rc = tdsqlite3_bind_text(pStmt, 1, pIter->zIdx, -1, SQLITE_STATIC);
+ if( rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ char *zSql = (char*)tdsqlite3_column_text(pStmt, 0);
+ if( zSql ){
+ pIter->zIdxSql = zSql = rbuStrndup(zSql, &rc);
+ }
+ if( zSql ){
+ int nParen = 0; /* Number of open parenthesis */
+ int i;
+ int iIdxCol = 0;
+ int nIdxAlloc = 0;
+ for(i=0; zSql[i]; i++){
+ char c = zSql[i];
+
+ /* If necessary, grow the pIter->aIdxCol[] array */
+ if( iIdxCol==nIdxAlloc ){
+ RbuSpan *aIdxCol = (RbuSpan*)tdsqlite3_realloc(
+ pIter->aIdxCol, (nIdxAlloc+16)*sizeof(RbuSpan)
+ );
+ if( aIdxCol==0 ){
+ rc = SQLITE_NOMEM;
+ break;
+ }
+ pIter->aIdxCol = aIdxCol;
+ nIdxAlloc += 16;
+ }
+
+ if( c=='(' ){
+ if( nParen==0 ){
+ assert( iIdxCol==0 );
+ pIter->aIdxCol[0].zSpan = &zSql[i+1];
+ }
+ nParen++;
+ }
+ else if( c==')' ){
+ nParen--;
+ if( nParen==0 ){
+ int nSpan = &zSql[i] - pIter->aIdxCol[iIdxCol].zSpan;
+ pIter->aIdxCol[iIdxCol++].nSpan = nSpan;
+ i++;
+ break;
+ }
+ }else if( c==',' && nParen==1 ){
+ int nSpan = &zSql[i] - pIter->aIdxCol[iIdxCol].zSpan;
+ pIter->aIdxCol[iIdxCol++].nSpan = nSpan;
+ pIter->aIdxCol[iIdxCol].zSpan = &zSql[i+1];
+ }else if( c=='"' || c=='\'' || c=='`' ){
+ for(i++; 1; i++){
+ if( zSql[i]==c ){
+ if( zSql[i+1]!=c ) break;
+ i++;
+ }
+ }
+ }else if( c=='[' ){
+ for(i++; 1; i++){
+ if( zSql[i]==']' ) break;
+ }
+ }else if( c=='-' && zSql[i+1]=='-' ){
+ for(i=i+2; zSql[i] && zSql[i]!='\n'; i++);
+ if( zSql[i]=='\0' ) break;
+ }else if( c=='/' && zSql[i+1]=='*' ){
+ for(i=i+2; zSql[i] && (zSql[i]!='*' || zSql[i+1]!='/'); i++);
+ if( zSql[i]=='\0' ) break;
+ i++;
+ }
+ }
+ if( zSql[i] ){
+ zRet = rbuStrndup(&zSql[i], &rc);
+ }
+ pIter->nIdxCol = iIdxCol;
+ }
+ }
+
+ rc2 = tdsqlite3_finalize(pStmt);
+ if( rc==SQLITE_OK ) rc = rc2;
+ }
+
+ p->rc = rc;
+ return zRet;
+}
+
/*
** Ensure that the SQLite statement handles required to update the
** target database object currently indicated by the iterator passed
** as the second argument are available.
*/
static int rbuObjIterPrepareAll(
- sqlite3rbu *p,
+ tdsqlite3rbu *p,
RbuObjIter *pIter,
int nOffset /* Add "LIMIT -1 OFFSET $nOffset" to SELECT */
){
@@ -171363,7 +199759,7 @@ static int rbuObjIterPrepareAll(
char *zLimit = 0;
if( nOffset ){
- zLimit = sqlite3_mprintf(" LIMIT -1 OFFSET %d", nOffset);
+ zLimit = tdsqlite3_mprintf(" LIMIT -1 OFFSET %d", nOffset);
if( !zLimit ) p->rc = SQLITE_NOMEM;
}
@@ -171373,29 +199769,31 @@ static int rbuObjIterPrepareAll(
char *zImposterPK = 0; /* Primary key declaration for imposter */
char *zWhere = 0; /* WHERE clause on PK columns */
char *zBind = 0;
+ char *zPart = 0;
int nBind = 0;
assert( pIter->eType!=RBU_PK_VTAB );
+ zPart = rbuObjIterGetIndexWhere(p, pIter);
zCollist = rbuObjIterGetIndexCols(
p, pIter, &zImposterCols, &zImposterPK, &zWhere, &nBind
);
zBind = rbuObjIterGetBindlist(p, nBind);
/* Create the imposter table used to write to this index. */
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1);
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1,tnum);
+ tdsqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1);
+ tdsqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1,tnum);
rbuMPrintfExec(p, p->dbMain,
"CREATE TABLE \"rbu_imp_%w\"( %s, PRIMARY KEY( %s ) ) WITHOUT ROWID",
zTbl, zImposterCols, zImposterPK
);
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0);
+ tdsqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0);
/* Create the statement to insert index entries */
pIter->nCol = nBind;
if( p->rc==SQLITE_OK ){
p->rc = prepareFreeAndCollectError(
p->dbMain, &pIter->pInsert, &p->zErrmsg,
- sqlite3_mprintf("INSERT INTO \"rbu_imp_%w\" VALUES(%s)", zTbl, zBind)
+ tdsqlite3_mprintf("INSERT INTO \"rbu_imp_%w\" VALUES(%s)", zTbl, zBind)
);
}
@@ -171403,7 +199801,7 @@ static int rbuObjIterPrepareAll(
if( rbuIsVacuum(p)==0 && p->rc==SQLITE_OK ){
p->rc = prepareFreeAndCollectError(
p->dbMain, &pIter->pDelete, &p->zErrmsg,
- sqlite3_mprintf("DELETE FROM \"rbu_imp_%w\" WHERE %s", zTbl, zWhere)
+ tdsqlite3_mprintf("DELETE FROM \"rbu_imp_%w\" WHERE %s", zTbl, zWhere)
);
}
@@ -171411,39 +199809,58 @@ static int rbuObjIterPrepareAll(
if( p->rc==SQLITE_OK ){
char *zSql;
if( rbuIsVacuum(p) ){
- zSql = sqlite3_mprintf(
- "SELECT %s, 0 AS rbu_control FROM '%q' ORDER BY %s%s",
+ char *zStart = 0;
+ if( nOffset ){
+ zStart = rbuVacuumIndexStart(p, pIter);
+ if( zStart ){
+ tdsqlite3_free(zLimit);
+ zLimit = 0;
+ }
+ }
+
+ zSql = tdsqlite3_mprintf(
+ "SELECT %s, 0 AS rbu_control FROM '%q' %s %s %s ORDER BY %s%s",
zCollist,
pIter->zDataTbl,
+ zPart,
+ (zStart ? (zPart ? "AND" : "WHERE") : ""), zStart,
zCollist, zLimit
);
+ tdsqlite3_free(zStart);
}else
if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){
- zSql = sqlite3_mprintf(
- "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' ORDER BY %s%s",
+ zSql = tdsqlite3_mprintf(
+ "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' %s ORDER BY %s%s",
zCollist, p->zStateDb, pIter->zDataTbl,
- zCollist, zLimit
+ zPart, zCollist, zLimit
);
}else{
- zSql = sqlite3_mprintf(
- "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' "
+ zSql = tdsqlite3_mprintf(
+ "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' %s "
"UNION ALL "
"SELECT %s, rbu_control FROM '%q' "
- "WHERE typeof(rbu_control)='integer' AND rbu_control!=1 "
+ "%s %s typeof(rbu_control)='integer' AND rbu_control!=1 "
"ORDER BY %s%s",
- zCollist, p->zStateDb, pIter->zDataTbl,
+ zCollist, p->zStateDb, pIter->zDataTbl, zPart,
zCollist, pIter->zDataTbl,
+ zPart,
+ (zPart ? "AND" : "WHERE"),
zCollist, zLimit
);
}
- p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, zSql);
+ if( p->rc==SQLITE_OK ){
+ p->rc = prepareFreeAndCollectError(p->dbRbu,&pIter->pSelect,pz,zSql);
+ }else{
+ tdsqlite3_free(zSql);
+ }
}
- sqlite3_free(zImposterCols);
- sqlite3_free(zImposterPK);
- sqlite3_free(zWhere);
- sqlite3_free(zBind);
+ tdsqlite3_free(zImposterCols);
+ tdsqlite3_free(zImposterPK);
+ tdsqlite3_free(zWhere);
+ tdsqlite3_free(zBind);
+ tdsqlite3_free(zPart);
}else{
int bRbuRowid = (pIter->eType==RBU_PK_VTAB)
||(pIter->eType==RBU_PK_NONE)
@@ -171467,7 +199884,7 @@ static int rbuObjIterPrepareAll(
/* Create the INSERT statement to write to the target PK b-tree */
if( p->rc==SQLITE_OK ){
p->rc = prepareFreeAndCollectError(p->dbMain, &pIter->pInsert, pz,
- sqlite3_mprintf(
+ tdsqlite3_mprintf(
"INSERT INTO \"%s%w\"(%s%s) VALUES(%s)",
zWrite, zTbl, zCollist, (bRbuRowid ? ", _rowid_" : ""), zBindings
)
@@ -171479,7 +199896,7 @@ static int rbuObjIterPrepareAll(
** an rbu vacuum handle. */
if( rbuIsVacuum(p)==0 && p->rc==SQLITE_OK ){
p->rc = prepareFreeAndCollectError(p->dbMain, &pIter->pDelete, pz,
- sqlite3_mprintf(
+ tdsqlite3_mprintf(
"DELETE FROM \"%s%w\" WHERE %s", zWrite, zTbl, zWhere
)
);
@@ -171536,27 +199953,51 @@ static int rbuObjIterPrepareAll(
/* Create the SELECT statement to read keys from data_xxx */
if( p->rc==SQLITE_OK ){
const char *zRbuRowid = "";
+ char *zStart = 0;
+ char *zOrder = 0;
if( bRbuRowid ){
zRbuRowid = rbuIsVacuum(p) ? ",_rowid_ " : ",rbu_rowid";
}
- p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz,
- sqlite3_mprintf(
- "SELECT %s,%s rbu_control%s FROM '%q'%s",
- zCollist,
- (rbuIsVacuum(p) ? "0 AS " : ""),
- zRbuRowid,
- pIter->zDataTbl, zLimit
- )
- );
+
+ if( rbuIsVacuum(p) ){
+ if( nOffset ){
+ zStart = rbuVacuumTableStart(p, pIter, bRbuRowid, zWrite);
+ if( zStart ){
+ tdsqlite3_free(zLimit);
+ zLimit = 0;
+ }
+ }
+ if( bRbuRowid ){
+ zOrder = rbuMPrintf(p, "_rowid_");
+ }else{
+ zOrder = rbuObjIterGetPkList(p, pIter, "", ", ", "");
+ }
+ }
+
+ if( p->rc==SQLITE_OK ){
+ p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz,
+ tdsqlite3_mprintf(
+ "SELECT %s,%s rbu_control%s FROM '%q'%s %s %s %s",
+ zCollist,
+ (rbuIsVacuum(p) ? "0 AS " : ""),
+ zRbuRowid,
+ pIter->zDataTbl, (zStart ? zStart : ""),
+ (zOrder ? "ORDER BY" : ""), zOrder,
+ zLimit
+ )
+ );
+ }
+ tdsqlite3_free(zStart);
+ tdsqlite3_free(zOrder);
}
- sqlite3_free(zWhere);
- sqlite3_free(zOldlist);
- sqlite3_free(zNewlist);
- sqlite3_free(zBindings);
+ tdsqlite3_free(zWhere);
+ tdsqlite3_free(zOldlist);
+ tdsqlite3_free(zNewlist);
+ tdsqlite3_free(zBindings);
}
- sqlite3_free(zCollist);
- sqlite3_free(zLimit);
+ tdsqlite3_free(zCollist);
+ tdsqlite3_free(zLimit);
}
return p->rc;
@@ -171572,10 +200013,10 @@ static int rbuObjIterPrepareAll(
** is not an error. Output variable *ppStmt is set to NULL in this case.
*/
static int rbuGetUpdateStmt(
- sqlite3rbu *p, /* RBU handle */
+ tdsqlite3rbu *p, /* RBU handle */
RbuObjIter *pIter, /* Object iterator */
const char *zMask, /* rbu_control value ('x.x.') */
- sqlite3_stmt **ppStmt /* OUT: UPDATE statement handle */
+ tdsqlite3_stmt **ppStmt /* OUT: UPDATE statement handle */
){
RbuUpdateStmt **pp;
RbuUpdateStmt *pUp = 0;
@@ -171604,7 +200045,7 @@ static int rbuGetUpdateStmt(
if( nUp>=SQLITE_RBU_UPDATE_CACHESIZE ){
for(pp=&pIter->pRbuUpdate; *pp!=pUp; pp=&((*pp)->pNext));
*pp = 0;
- sqlite3_finalize(pUp->pUpdate);
+ tdsqlite3_finalize(pUp->pUpdate);
pUp->pUpdate = 0;
}else{
pUp = (RbuUpdateStmt*)rbuMalloc(p, sizeof(RbuUpdateStmt)+pIter->nTblCol+1);
@@ -171624,7 +200065,7 @@ static int rbuGetUpdateStmt(
const char *zPrefix = "";
if( pIter->eType!=RBU_PK_VTAB ) zPrefix = "rbu_imp_";
- zUpdate = sqlite3_mprintf("UPDATE \"%s%w\" SET %s WHERE %s",
+ zUpdate = tdsqlite3_mprintf("UPDATE \"%s%w\" SET %s WHERE %s",
zPrefix, pIter->zTbl, zSet, zWhere
);
p->rc = prepareFreeAndCollectError(
@@ -171632,25 +200073,25 @@ static int rbuGetUpdateStmt(
);
*ppStmt = pUp->pUpdate;
}
- sqlite3_free(zWhere);
- sqlite3_free(zSet);
+ tdsqlite3_free(zWhere);
+ tdsqlite3_free(zSet);
}
return p->rc;
}
-static sqlite3 *rbuOpenDbhandle(
- sqlite3rbu *p,
+static tdsqlite3 *rbuOpenDbhandle(
+ tdsqlite3rbu *p,
const char *zName,
int bUseVfs
){
- sqlite3 *db = 0;
+ tdsqlite3 *db = 0;
if( p->rc==SQLITE_OK ){
const int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_URI;
- p->rc = sqlite3_open_v2(zName, &db, flags, bUseVfs ? p->zVfsName : 0);
+ p->rc = tdsqlite3_open_v2(zName, &db, flags, bUseVfs ? p->zVfsName : 0);
if( p->rc ){
- p->zErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
- sqlite3_close(db);
+ p->zErrmsg = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(db));
+ tdsqlite3_close(db);
db = 0;
}
}
@@ -171662,9 +200103,10 @@ static sqlite3 *rbuOpenDbhandle(
*/
static void rbuFreeState(RbuState *p){
if( p ){
- sqlite3_free(p->zTbl);
- sqlite3_free(p->zIdx);
- sqlite3_free(p);
+ tdsqlite3_free(p->zTbl);
+ tdsqlite3_free(p->zDataTbl);
+ tdsqlite3_free(p->zIdx);
+ tdsqlite3_free(p);
}
}
@@ -171672,14 +200114,14 @@ static void rbuFreeState(RbuState *p){
** Allocate an RbuState object and load the contents of the rbu_state
** table into it. Return a pointer to the new object. It is the
** responsibility of the caller to eventually free the object using
-** sqlite3_free().
+** tdsqlite3_free().
**
** If an error occurs, leave an error code and message in the rbu handle
** and return NULL.
*/
-static RbuState *rbuLoadState(sqlite3rbu *p){
+static RbuState *rbuLoadState(tdsqlite3rbu *p){
RbuState *pRet = 0;
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
int rc;
int rc2;
@@ -171687,12 +200129,12 @@ static RbuState *rbuLoadState(sqlite3rbu *p){
if( pRet==0 ) return 0;
rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg,
- sqlite3_mprintf("SELECT k, v FROM %s.rbu_state", p->zStateDb)
+ tdsqlite3_mprintf("SELECT k, v FROM %s.rbu_state", p->zStateDb)
);
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
- switch( sqlite3_column_int(pStmt, 0) ){
+ while( rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ switch( tdsqlite3_column_int(pStmt, 0) ){
case RBU_STATE_STAGE:
- pRet->eStage = sqlite3_column_int(pStmt, 1);
+ pRet->eStage = tdsqlite3_column_int(pStmt, 1);
if( pRet->eStage!=RBU_STAGE_OAL
&& pRet->eStage!=RBU_STAGE_MOVE
&& pRet->eStage!=RBU_STAGE_CKPT
@@ -171702,35 +200144,39 @@ static RbuState *rbuLoadState(sqlite3rbu *p){
break;
case RBU_STATE_TBL:
- pRet->zTbl = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc);
+ pRet->zTbl = rbuStrndup((char*)tdsqlite3_column_text(pStmt, 1), &rc);
break;
case RBU_STATE_IDX:
- pRet->zIdx = rbuStrndup((char*)sqlite3_column_text(pStmt, 1), &rc);
+ pRet->zIdx = rbuStrndup((char*)tdsqlite3_column_text(pStmt, 1), &rc);
break;
case RBU_STATE_ROW:
- pRet->nRow = sqlite3_column_int(pStmt, 1);
+ pRet->nRow = tdsqlite3_column_int(pStmt, 1);
break;
case RBU_STATE_PROGRESS:
- pRet->nProgress = sqlite3_column_int64(pStmt, 1);
+ pRet->nProgress = tdsqlite3_column_int64(pStmt, 1);
break;
case RBU_STATE_CKPT:
- pRet->iWalCksum = sqlite3_column_int64(pStmt, 1);
+ pRet->iWalCksum = tdsqlite3_column_int64(pStmt, 1);
break;
case RBU_STATE_COOKIE:
- pRet->iCookie = (u32)sqlite3_column_int64(pStmt, 1);
+ pRet->iCookie = (u32)tdsqlite3_column_int64(pStmt, 1);
break;
case RBU_STATE_OALSZ:
- pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1);
+ pRet->iOalSz = (u32)tdsqlite3_column_int64(pStmt, 1);
break;
case RBU_STATE_PHASEONESTEP:
- pRet->nPhaseOneStep = sqlite3_column_int64(pStmt, 1);
+ pRet->nPhaseOneStep = tdsqlite3_column_int64(pStmt, 1);
+ break;
+
+ case RBU_STATE_DATATBL:
+ pRet->zDataTbl = rbuStrndup((char*)tdsqlite3_column_text(pStmt, 1), &rc);
break;
default:
@@ -171738,7 +200184,7 @@ static RbuState *rbuLoadState(sqlite3rbu *p){
break;
}
}
- rc2 = sqlite3_finalize(pStmt);
+ rc2 = tdsqlite3_finalize(pStmt);
if( rc==SQLITE_OK ) rc = rc2;
p->rc = rc;
@@ -171750,7 +200196,7 @@ static RbuState *rbuLoadState(sqlite3rbu *p){
** Open the database handle and attach the RBU database as "rbu". If an
** error occurs, leave an error code and message in the RBU handle.
*/
-static void rbuOpenDatabase(sqlite3rbu *p){
+static void rbuOpenDatabase(tdsqlite3rbu *p, int *pbRetry){
assert( p->rc || (p->dbMain==0 && p->dbRbu==0) );
assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 );
@@ -171758,9 +200204,9 @@ static void rbuOpenDatabase(sqlite3rbu *p){
p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1);
if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){
- sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p);
+ tdsqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p);
if( p->zState==0 ){
- const char *zFile = sqlite3_db_filename(p->dbRbu, "main");
+ const char *zFile = tdsqlite3_db_filename(p->dbRbu, "main");
p->zState = rbuMPrintf(p, "file://%s-vacuum?modeof=%s", zFile, zFile);
}
}
@@ -171776,7 +200222,7 @@ static void rbuOpenDatabase(sqlite3rbu *p){
#if 0
if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){
- p->rc = sqlite3_exec(p->dbRbu, "BEGIN", 0, 0, 0);
+ p->rc = tdsqlite3_exec(p->dbRbu, "BEGIN", 0, 0, 0);
}
#endif
@@ -171788,26 +200234,26 @@ static void rbuOpenDatabase(sqlite3rbu *p){
if( p->rc==SQLITE_OK ){
int rc2;
int bOk = 0;
- sqlite3_stmt *pCnt = 0;
+ tdsqlite3_stmt *pCnt = 0;
p->rc = prepareAndCollectError(p->dbRbu, &pCnt, &p->zErrmsg,
"SELECT count(*) FROM stat.sqlite_master"
);
if( p->rc==SQLITE_OK
- && sqlite3_step(pCnt)==SQLITE_ROW
- && 1==sqlite3_column_int(pCnt, 0)
+ && tdsqlite3_step(pCnt)==SQLITE_ROW
+ && 1==tdsqlite3_column_int(pCnt, 0)
){
bOk = 1;
}
- rc2 = sqlite3_finalize(pCnt);
+ rc2 = tdsqlite3_finalize(pCnt);
if( p->rc==SQLITE_OK ) p->rc = rc2;
if( p->rc==SQLITE_OK && bOk==0 ){
p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("invalid state database");
+ p->zErrmsg = tdsqlite3_mprintf("invalid state database");
}
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0);
+ p->rc = tdsqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0);
}
}
}
@@ -171818,14 +200264,14 @@ static void rbuOpenDatabase(sqlite3rbu *p){
int rc;
p->nRbu = 0;
p->pRbuFd = 0;
- rc = sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p);
+ rc = tdsqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p);
if( rc!=SQLITE_NOTFOUND ) p->rc = rc;
if( p->eStage>=RBU_STAGE_MOVE ){
bOpen = 1;
}else{
RbuState *pState = rbuLoadState(p);
if( pState ){
- bOpen = (pState->eStage>RBU_STAGE_MOVE);
+ bOpen = (pState->eStage>=RBU_STAGE_MOVE);
rbuFreeState(pState);
}
}
@@ -171837,8 +200283,17 @@ static void rbuOpenDatabase(sqlite3rbu *p){
if( !rbuIsVacuum(p) ){
p->dbMain = rbuOpenDbhandle(p, p->zTarget, 1);
}else if( p->pRbuFd->pWalFd ){
+ if( pbRetry ){
+ p->pRbuFd->bNolock = 0;
+ tdsqlite3_close(p->dbRbu);
+ tdsqlite3_close(p->dbMain);
+ p->dbMain = 0;
+ p->dbRbu = 0;
+ *pbRetry = 1;
+ return;
+ }
p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("cannot vacuum wal mode database");
+ p->zErrmsg = tdsqlite3_mprintf("cannot vacuum wal mode database");
}else{
char *zTarget;
char *zExtra = 0;
@@ -171850,8 +200305,8 @@ static void rbuOpenDatabase(sqlite3rbu *p){
if( *zExtra=='\0' ) zExtra = 0;
}
- zTarget = sqlite3_mprintf("file:%s-vacuum?rbu_memory=1%s%s",
- sqlite3_db_filename(p->dbRbu, "main"),
+ zTarget = tdsqlite3_mprintf("file:%s-vactmp?rbu_memory=1%s%s",
+ tdsqlite3_db_filename(p->dbRbu, "main"),
(zExtra==0 ? "" : "&"), (zExtra==0 ? "" : zExtra)
);
@@ -171860,30 +200315,30 @@ static void rbuOpenDatabase(sqlite3rbu *p){
return;
}
p->dbMain = rbuOpenDbhandle(p, zTarget, p->nRbu<=1);
- sqlite3_free(zTarget);
+ tdsqlite3_free(zTarget);
}
}
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_create_function(p->dbMain,
+ p->rc = tdsqlite3_create_function(p->dbMain,
"rbu_tmp_insert", -1, SQLITE_UTF8, (void*)p, rbuTmpInsertFunc, 0, 0
);
}
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_create_function(p->dbMain,
+ p->rc = tdsqlite3_create_function(p->dbMain,
"rbu_fossil_delta", 2, SQLITE_UTF8, 0, rbuFossilDeltaFunc, 0, 0
);
}
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_create_function(p->dbRbu,
+ p->rc = tdsqlite3_create_function(p->dbRbu,
"rbu_target_name", -1, SQLITE_UTF8, (void*)p, rbuTargetNameFunc, 0, 0
);
}
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_RBU, (void*)p);
+ p->rc = tdsqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_RBU, (void*)p);
}
rbuMPrintfExec(p, p->dbMain, "SELECT * FROM sqlite_master");
@@ -171891,17 +200346,17 @@ static void rbuOpenDatabase(sqlite3rbu *p){
** this call returns SQLITE_NOTFOUND, then the RBU vfs is not in use.
** This is an error. */
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_RBU, (void*)p);
+ p->rc = tdsqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_RBU, (void*)p);
}
if( p->rc==SQLITE_NOTFOUND ){
p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("rbu vfs not found");
+ p->zErrmsg = tdsqlite3_mprintf("rbu vfs not found");
}
}
/*
-** This routine is a copy of the sqlite3FileSuffix3() routine from the core.
+** This routine is a copy of the tdsqlite3FileSuffix3() routine from the core.
** It is a no-op unless SQLITE_ENABLE_8_3_NAMES is defined.
**
** If SQLITE_ENABLE_8_3_NAMES is set at compile-time and if the database
@@ -171923,7 +200378,7 @@ static void rbuOpenDatabase(sqlite3rbu *p){
static void rbuFileSuffix3(const char *zBase, char *z){
#ifdef SQLITE_ENABLE_8_3_NAMES
#if SQLITE_ENABLE_8_3_NAMES<2
- if( sqlite3_uri_boolean(zBase, "8_3_names", 0) )
+ if( tdsqlite3_uri_boolean(zBase, "8_3_names", 0) )
#endif
{
int i, sz;
@@ -171941,10 +200396,10 @@ static void rbuFileSuffix3(const char *zBase, char *z){
** The checksum is store in the first page of xShmMap memory as an 8-byte
** blob starting at byte offset 40.
*/
-static i64 rbuShmChecksum(sqlite3rbu *p){
+static i64 rbuShmChecksum(tdsqlite3rbu *p){
i64 iRet = 0;
if( p->rc==SQLITE_OK ){
- sqlite3_file *pDb = p->pTargetFd->pReal;
+ tdsqlite3_file *pDb = p->pTargetFd->pReal;
u32 volatile *ptr;
p->rc = pDb->pMethods->xShmMap(pDb, 0, 32*1024, 0, (void volatile**)&ptr);
if( p->rc==SQLITE_OK ){
@@ -171958,7 +200413,7 @@ static i64 rbuShmChecksum(sqlite3rbu *p){
** This function is called as part of initializing or reinitializing an
** incremental checkpoint.
**
-** It populates the sqlite3rbu.aFrame[] array with the set of
+** It populates the tdsqlite3rbu.aFrame[] array with the set of
** (wal frame -> db page) copy operations required to checkpoint the
** current wal file, and obtains the set of shm locks required to safely
** perform the copy operations directly on the file-system.
@@ -171970,7 +200425,7 @@ static i64 rbuShmChecksum(sqlite3rbu *p){
** other client appends a transaction to the wal file in the middle of
** an incremental checkpoint.
*/
-static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){
+static void rbuSetupCheckpoint(tdsqlite3rbu *p, RbuState *pState){
/* If pState is NULL, then the wal file may not have been opened and
** recovered. Running a read-statement here to ensure that doing so
@@ -171978,12 +200433,12 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){
if( pState==0 ){
p->eStage = 0;
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_exec(p->dbMain, "SELECT * FROM sqlite_master", 0, 0, 0);
+ p->rc = tdsqlite3_exec(p->dbMain, "SELECT * FROM sqlite_master", 0, 0, 0);
}
}
/* Assuming no error has occurred, run a "restart" checkpoint with the
- ** sqlite3rbu.eStage variable set to CAPTURE. This turns on the following
+ ** tdsqlite3rbu.eStage variable set to CAPTURE. This turns on the following
** special behaviour in the rbu VFS:
**
** * If the exclusive shm WRITER or READ0 lock cannot be obtained,
@@ -171992,7 +200447,7 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){
**
** * Attempts to read from the *-wal file or write to the database file
** do not perform any IO. Instead, the frame/page combinations that
- ** would be read/written are recorded in the sqlite3rbu.aFrame[]
+ ** would be read/written are recorded in the tdsqlite3rbu.aFrame[]
** array.
**
** * Calls to xShmLock(UNLOCK) to release the exclusive shm WRITER,
@@ -172013,20 +200468,39 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){
if( p->rc==SQLITE_OK ){
int rc2;
p->eStage = RBU_STAGE_CAPTURE;
- rc2 = sqlite3_exec(p->dbMain, "PRAGMA main.wal_checkpoint=restart", 0, 0,0);
+ rc2 = tdsqlite3_exec(p->dbMain, "PRAGMA main.wal_checkpoint=restart", 0, 0,0);
if( rc2!=SQLITE_INTERNAL ) p->rc = rc2;
}
- if( p->rc==SQLITE_OK ){
+ if( p->rc==SQLITE_OK && p->nFrame>0 ){
p->eStage = RBU_STAGE_CKPT;
p->nStep = (pState ? pState->nRow : 0);
p->aBuf = rbuMalloc(p, p->pgsz);
p->iWalCksum = rbuShmChecksum(p);
}
- if( p->rc==SQLITE_OK && pState && pState->iWalCksum!=p->iWalCksum ){
- p->rc = SQLITE_DONE;
- p->eStage = RBU_STAGE_DONE;
+ if( p->rc==SQLITE_OK ){
+ if( p->nFrame==0 || (pState && pState->iWalCksum!=p->iWalCksum) ){
+ p->rc = SQLITE_DONE;
+ p->eStage = RBU_STAGE_DONE;
+ }else{
+ int nSectorSize;
+ tdsqlite3_file *pDb = p->pTargetFd->pReal;
+ tdsqlite3_file *pWal = p->pTargetFd->pWalFd->pReal;
+ assert( p->nPagePerSector==0 );
+ nSectorSize = pDb->pMethods->xSectorSize(pDb);
+ if( nSectorSize>p->pgsz ){
+ p->nPagePerSector = nSectorSize / p->pgsz;
+ }else{
+ p->nPagePerSector = 1;
+ }
+
+ /* Call xSync() on the wal file. This causes SQLite to sync the
+ ** directory in which the target database and the wal file reside, in
+ ** case it has not been synced since the rename() call in
+ ** rbuMoveOalFile(). */
+ p->rc = pWal->pMethods->xSync(pWal, SQLITE_SYNC_NORMAL);
+ }
}
}
@@ -172035,7 +200509,7 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){
** the rbu object is in capture mode. Record the frame number of the frame
** being read in the aFrame[] array.
*/
-static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){
+static int rbuCaptureWalRead(tdsqlite3rbu *pRbu, i64 iOff, int iAmt){
const u32 mReq = (1<<WAL_LOCK_WRITE)|(1<<WAL_LOCK_CKPT)|(1<<WAL_LOCK_READ0);
u32 iFrame;
@@ -172048,7 +200522,7 @@ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){
if( pRbu->nFrame==pRbu->nFrameAlloc ){
int nNew = (pRbu->nFrameAlloc ? pRbu->nFrameAlloc : 64) * 2;
RbuFrame *aNew;
- aNew = (RbuFrame*)sqlite3_realloc64(pRbu->aFrame, nNew * sizeof(RbuFrame));
+ aNew = (RbuFrame*)tdsqlite3_realloc64(pRbu->aFrame, nNew * sizeof(RbuFrame));
if( aNew==0 ) return SQLITE_NOMEM;
pRbu->aFrame = aNew;
pRbu->nFrameAlloc = nNew;
@@ -172067,7 +200541,7 @@ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){
** file while the rbu handle is in capture mode. Record the page number
** of the page being written in the aFrame[] array.
*/
-static int rbuCaptureDbWrite(sqlite3rbu *pRbu, i64 iOff){
+static int rbuCaptureDbWrite(tdsqlite3rbu *pRbu, i64 iOff){
pRbu->aFrame[pRbu->nFrame-1].iDbPage = (u32)(iOff / pRbu->pgsz) + 1;
return SQLITE_OK;
}
@@ -172077,9 +200551,9 @@ static int rbuCaptureDbWrite(sqlite3rbu *pRbu, i64 iOff){
** a single frame of data from the wal file into the database file, as
** indicated by the RbuFrame object.
*/
-static void rbuCheckpointFrame(sqlite3rbu *p, RbuFrame *pFrame){
- sqlite3_file *pWal = p->pTargetFd->pWalFd->pReal;
- sqlite3_file *pDb = p->pTargetFd->pReal;
+static void rbuCheckpointFrame(tdsqlite3rbu *p, RbuFrame *pFrame){
+ tdsqlite3_file *pWal = p->pTargetFd->pWalFd->pReal;
+ tdsqlite3_file *pDb = p->pTargetFd->pReal;
i64 iOff;
assert( p->rc==SQLITE_OK );
@@ -172095,8 +200569,8 @@ static void rbuCheckpointFrame(sqlite3rbu *p, RbuFrame *pFrame){
/*
** Take an EXCLUSIVE lock on the database file.
*/
-static void rbuLockDatabase(sqlite3rbu *p){
- sqlite3_file *pReal = p->pTargetFd->pReal;
+static void rbuLockDatabase(tdsqlite3rbu *p){
+ tdsqlite3_file *pReal = p->pTargetFd->pReal;
assert( p->rc==SQLITE_OK );
p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_SHARED);
if( p->rc==SQLITE_OK ){
@@ -172113,7 +200587,7 @@ static LPWSTR rbuWinUtf8ToUnicode(const char *zFilename){
if( nChar==0 ){
return 0;
}
- zWideFilename = sqlite3_malloc64( nChar*sizeof(zWideFilename[0]) );
+ zWideFilename = tdsqlite3_malloc64( nChar*sizeof(zWideFilename[0]) );
if( zWideFilename==0 ){
return 0;
}
@@ -172121,7 +200595,7 @@ static LPWSTR rbuWinUtf8ToUnicode(const char *zFilename){
nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename,
nChar);
if( nChar==0 ){
- sqlite3_free(zWideFilename);
+ tdsqlite3_free(zWideFilename);
zWideFilename = 0;
}
return zWideFilename;
@@ -172135,17 +200609,17 @@ static LPWSTR rbuWinUtf8ToUnicode(const char *zFilename){
** If an error occurs, leave an error code and error message in the rbu
** handle.
*/
-static void rbuMoveOalFile(sqlite3rbu *p){
- const char *zBase = sqlite3_db_filename(p->dbMain, "main");
+static void rbuMoveOalFile(tdsqlite3rbu *p){
+ const char *zBase = tdsqlite3_db_filename(p->dbMain, "main");
const char *zMove = zBase;
char *zOal;
char *zWal;
if( rbuIsVacuum(p) ){
- zMove = sqlite3_db_filename(p->dbRbu, "main");
+ zMove = tdsqlite3_db_filename(p->dbRbu, "main");
}
- zOal = sqlite3_mprintf("%s-oal", zMove);
- zWal = sqlite3_mprintf("%s-wal", zMove);
+ zOal = tdsqlite3_mprintf("%s-oal", zMove);
+ zWal = tdsqlite3_mprintf("%s-wal", zMove);
assert( p->eStage==RBU_STAGE_MOVE );
assert( p->rc==SQLITE_OK && p->zErrmsg==0 );
@@ -172166,8 +200640,8 @@ static void rbuMoveOalFile(sqlite3rbu *p){
/* Re-open the databases. */
rbuObjIterFinalize(&p->objiter);
- sqlite3_close(p->dbRbu);
- sqlite3_close(p->dbMain);
+ tdsqlite3_close(p->dbRbu);
+ tdsqlite3_close(p->dbMain);
p->dbMain = 0;
p->dbRbu = 0;
@@ -172185,11 +200659,11 @@ static void rbuMoveOalFile(sqlite3rbu *p){
}else{
p->rc = SQLITE_IOERR;
}
- sqlite3_free(zWideWal);
+ tdsqlite3_free(zWideWal);
}else{
p->rc = SQLITE_IOERR_NOMEM;
}
- sqlite3_free(zWideOal);
+ tdsqlite3_free(zWideOal);
}else{
p->rc = SQLITE_IOERR_NOMEM;
}
@@ -172199,14 +200673,14 @@ static void rbuMoveOalFile(sqlite3rbu *p){
#endif
if( p->rc==SQLITE_OK ){
- rbuOpenDatabase(p);
+ rbuOpenDatabase(p, 0);
rbuSetupCheckpoint(p, 0);
}
}
}
- sqlite3_free(zWal);
- sqlite3_free(zOal);
+ tdsqlite3_free(zWal);
+ tdsqlite3_free(zOal);
}
/*
@@ -172226,13 +200700,13 @@ static void rbuMoveOalFile(sqlite3rbu *p){
** If the rbu_control field contains an invalid value, an error code and
** message are left in the RBU handle and zero returned.
*/
-static int rbuStepType(sqlite3rbu *p, const char **pzMask){
+static int rbuStepType(tdsqlite3rbu *p, const char **pzMask){
int iCol = p->objiter.nCol; /* Index of rbu_control column */
int res = 0; /* Return value */
- switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){
+ switch( tdsqlite3_column_type(p->objiter.pSelect, iCol) ){
case SQLITE_INTEGER: {
- int iVal = sqlite3_column_int(p->objiter.pSelect, iCol);
+ int iVal = tdsqlite3_column_int(p->objiter.pSelect, iCol);
switch( iVal ){
case 0: res = RBU_INSERT; break;
case 1: res = RBU_DELETE; break;
@@ -172244,7 +200718,7 @@ static int rbuStepType(sqlite3rbu *p, const char **pzMask){
}
case SQLITE_TEXT: {
- const unsigned char *z = sqlite3_column_text(p->objiter.pSelect, iCol);
+ const unsigned char *z = tdsqlite3_column_text(p->objiter.pSelect, iCol);
if( z==0 ){
p->rc = SQLITE_NOMEM;
}else{
@@ -172269,9 +200743,9 @@ static int rbuStepType(sqlite3rbu *p, const char **pzMask){
/*
** Assert that column iCol of statement pStmt is named zName.
*/
-static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){
- const char *zCol = sqlite3_column_name(pStmt, iCol);
- assert( 0==sqlite3_stricmp(zName, zCol) );
+static void assertColumnName(tdsqlite3_stmt *pStmt, int iCol, const char *zName){
+ const char *zCol = tdsqlite3_column_name(pStmt, iCol);
+ assert( 0==tdsqlite3_stricmp(zName, zCol) );
}
#else
# define assertColumnName(x,y,z)
@@ -172280,12 +200754,12 @@ static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){
/*
** Argument eType must be one of RBU_INSERT, RBU_DELETE, RBU_IDX_INSERT or
** RBU_IDX_DELETE. This function performs the work of a single
-** sqlite3rbu_step() call for the type of operation specified by eType.
+** tdsqlite3rbu_step() call for the type of operation specified by eType.
*/
-static void rbuStepOneOp(sqlite3rbu *p, int eType){
+static void rbuStepOneOp(tdsqlite3rbu *p, int eType){
RbuObjIter *pIter = &p->objiter;
- sqlite3_value *pVal;
- sqlite3_stmt *pWriter;
+ tdsqlite3_value *pVal;
+ tdsqlite3_stmt *pWriter;
int i;
assert( p->rc==SQLITE_OK );
@@ -172314,10 +200788,10 @@ static void rbuStepOneOp(sqlite3rbu *p, int eType){
** to write a NULL into the IPK column. That is not permitted. */
if( eType==RBU_INSERT
&& pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i]
- && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
+ && tdsqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
){
p->rc = SQLITE_MISMATCH;
- p->zErrmsg = sqlite3_mprintf("datatype mismatch");
+ p->zErrmsg = tdsqlite3_mprintf("datatype mismatch");
return;
}
@@ -172325,8 +200799,8 @@ static void rbuStepOneOp(sqlite3rbu *p, int eType){
continue;
}
- pVal = sqlite3_column_value(pIter->pSelect, i);
- p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
+ pVal = tdsqlite3_column_value(pIter->pSelect, i);
+ p->rc = tdsqlite3_bind_value(pWriter, i+1, pVal);
if( p->rc ) return;
}
if( pIter->zIdx==0 ){
@@ -172344,18 +200818,18 @@ static void rbuStepOneOp(sqlite3rbu *p, int eType){
assertColumnName(pIter->pSelect, pIter->nCol+1,
rbuIsVacuum(p) ? "rowid" : "rbu_rowid"
);
- pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
- p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
+ pVal = tdsqlite3_column_value(pIter->pSelect, pIter->nCol+1);
+ p->rc = tdsqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
}
}
if( p->rc==SQLITE_OK ){
- sqlite3_step(pWriter);
+ tdsqlite3_step(pWriter);
p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
}
}
/*
-** This function does the work for an sqlite3rbu_step() call.
+** This function does the work for an tdsqlite3rbu_step() call.
**
** The object-iterator (p->objiter) currently points to a valid object,
** and the input cursor (p->objiter.pSelect) currently points to a valid
@@ -172365,7 +200839,7 @@ static void rbuStepOneOp(sqlite3rbu *p, int eType){
** and message is left in the RBU handle and a copy of the error code
** returned.
*/
-static int rbuStep(sqlite3rbu *p){
+static int rbuStep(tdsqlite3rbu *p){
RbuObjIter *pIter = &p->objiter;
const char *zMask = 0;
int eType = rbuStepType(p, &zMask);
@@ -172391,8 +200865,8 @@ static int rbuStep(sqlite3rbu *p){
rbuStepOneOp(p, eType);
}
else{
- sqlite3_value *pVal;
- sqlite3_stmt *pUpdate = 0;
+ tdsqlite3_value *pVal;
+ tdsqlite3_stmt *pUpdate = 0;
assert( eType==RBU_UPDATE );
p->nPhaseOneStep -= p->objiter.nIndex;
rbuGetUpdateStmt(p, pIter, zMask, &pUpdate);
@@ -172400,9 +200874,9 @@ static int rbuStep(sqlite3rbu *p){
int i;
for(i=0; p->rc==SQLITE_OK && i<pIter->nCol; i++){
char c = zMask[pIter->aiSrcOrder[i]];
- pVal = sqlite3_column_value(pIter->pSelect, i);
+ pVal = tdsqlite3_column_value(pIter->pSelect, i);
if( pIter->abTblPk[i] || c!='.' ){
- p->rc = sqlite3_bind_value(pUpdate, i+1, pVal);
+ p->rc = tdsqlite3_bind_value(pUpdate, i+1, pVal);
}
}
if( p->rc==SQLITE_OK
@@ -172410,11 +200884,11 @@ static int rbuStep(sqlite3rbu *p){
){
/* Bind the rbu_rowid value to column _rowid_ */
assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid");
- pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
- p->rc = sqlite3_bind_value(pUpdate, pIter->nCol+1, pVal);
+ pVal = tdsqlite3_column_value(pIter->pSelect, pIter->nCol+1);
+ p->rc = tdsqlite3_bind_value(pUpdate, pIter->nCol+1, pVal);
}
if( p->rc==SQLITE_OK ){
- sqlite3_step(pUpdate);
+ tdsqlite3_step(pUpdate);
p->rc = resetAndCollectError(pUpdate, &p->zErrmsg);
}
}
@@ -172430,23 +200904,23 @@ static int rbuStep(sqlite3rbu *p){
** opened by p->dbMain to one more than the schema cookie of the main
** db opened by p->dbRbu.
*/
-static void rbuIncrSchemaCookie(sqlite3rbu *p){
+static void rbuIncrSchemaCookie(tdsqlite3rbu *p){
if( p->rc==SQLITE_OK ){
- sqlite3 *dbread = (rbuIsVacuum(p) ? p->dbRbu : p->dbMain);
+ tdsqlite3 *dbread = (rbuIsVacuum(p) ? p->dbRbu : p->dbMain);
int iCookie = 1000000;
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
p->rc = prepareAndCollectError(dbread, &pStmt, &p->zErrmsg,
"PRAGMA schema_version"
);
if( p->rc==SQLITE_OK ){
- /* Coverage: it may be that this sqlite3_step() cannot fail. There
+ /* Coverage: it may be that this tdsqlite3_step() cannot fail. There
** is already a transaction open, so the prepared statement cannot
** throw an SQLITE_SCHEMA exception. The only database page the
** statement reads is page 1, which is guaranteed to be in the cache.
** And no memory allocations are required. */
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- iCookie = sqlite3_column_int(pStmt, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ iCookie = tdsqlite3_column_int(pStmt, 0);
}
rbuFinalize(p, pStmt);
}
@@ -172461,15 +200935,15 @@ static void rbuIncrSchemaCookie(sqlite3rbu *p){
** value stored in the RBU_STATE_STAGE column is eStage. All other values
** are determined by inspecting the rbu handle passed as the first argument.
*/
-static void rbuSaveState(sqlite3rbu *p, int eStage){
+static void rbuSaveState(tdsqlite3rbu *p, int eStage){
if( p->rc==SQLITE_OK || p->rc==SQLITE_DONE ){
- sqlite3_stmt *pInsert = 0;
+ tdsqlite3_stmt *pInsert = 0;
rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd);
int rc;
assert( p->zErrmsg==0 );
rc = prepareFreeAndCollectError(p->dbRbu, &pInsert, &p->zErrmsg,
- sqlite3_mprintf(
+ tdsqlite3_mprintf(
"INSERT OR REPLACE INTO %s.rbu_state(k, v) VALUES "
"(%d, %d), "
"(%d, %Q), "
@@ -172479,7 +200953,8 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){
"(%d, %lld), "
"(%d, %lld), "
"(%d, %lld), "
- "(%d, %lld) ",
+ "(%d, %lld), "
+ "(%d, %Q) ",
p->zStateDb,
RBU_STATE_STAGE, eStage,
RBU_STATE_TBL, p->objiter.zTbl,
@@ -172489,14 +200964,15 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){
RBU_STATE_CKPT, p->iWalCksum,
RBU_STATE_COOKIE, (i64)pFd->iCookie,
RBU_STATE_OALSZ, p->iOalSz,
- RBU_STATE_PHASEONESTEP, p->nPhaseOneStep
+ RBU_STATE_PHASEONESTEP, p->nPhaseOneStep,
+ RBU_STATE_DATATBL, p->objiter.zDataTbl
)
);
assert( pInsert==0 || rc==SQLITE_OK );
if( rc==SQLITE_OK ){
- sqlite3_step(pInsert);
- rc = sqlite3_finalize(pInsert);
+ tdsqlite3_step(pInsert);
+ rc = tdsqlite3_finalize(pInsert);
}
if( rc!=SQLITE_OK ) p->rc = rc;
}
@@ -172506,12 +200982,12 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){
/*
** The second argument passed to this function is the name of a PRAGMA
** setting - "page_size", "auto_vacuum", "user_version" or "application_id".
-** This function executes the following on sqlite3rbu.dbRbu:
+** This function executes the following on tdsqlite3rbu.dbRbu:
**
** "PRAGMA main.$zPragma"
**
** where $zPragma is the string passed as the second argument, then
-** on sqlite3rbu.dbMain:
+** on tdsqlite3rbu.dbMain:
**
** "PRAGMA main.$zPragma = $val"
**
@@ -172520,15 +200996,15 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){
** In short, it copies the value of the specified PRAGMA setting from
** dbRbu to dbMain.
*/
-static void rbuCopyPragma(sqlite3rbu *p, const char *zPragma){
+static void rbuCopyPragma(tdsqlite3rbu *p, const char *zPragma){
if( p->rc==SQLITE_OK ){
- sqlite3_stmt *pPragma = 0;
+ tdsqlite3_stmt *pPragma = 0;
p->rc = prepareFreeAndCollectError(p->dbRbu, &pPragma, &p->zErrmsg,
- sqlite3_mprintf("PRAGMA main.%s", zPragma)
+ tdsqlite3_mprintf("PRAGMA main.%s", zPragma)
);
- if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPragma) ){
+ if( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pPragma) ){
p->rc = rbuMPrintfExec(p, p->dbMain, "PRAGMA main.%s = %d",
- zPragma, sqlite3_column_int(pPragma, 0)
+ zPragma, tdsqlite3_column_int(pPragma, 0)
);
}
rbuFinalize(p, pPragma);
@@ -172540,12 +201016,12 @@ static void rbuCopyPragma(sqlite3rbu *p, const char *zPragma){
** the state database is empty. If this RBU handle was opened for an
** RBU vacuum operation, create the schema in the target db.
*/
-static void rbuCreateTargetSchema(sqlite3rbu *p){
- sqlite3_stmt *pSql = 0;
- sqlite3_stmt *pInsert = 0;
+static void rbuCreateTargetSchema(tdsqlite3rbu *p){
+ tdsqlite3_stmt *pSql = 0;
+ tdsqlite3_stmt *pInsert = 0;
assert( rbuIsVacuum(p) );
- p->rc = sqlite3_exec(p->dbMain, "PRAGMA writable_schema=1", 0,0, &p->zErrmsg);
+ p->rc = tdsqlite3_exec(p->dbMain, "PRAGMA writable_schema=1", 0,0, &p->zErrmsg);
if( p->rc==SQLITE_OK ){
p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg,
"SELECT sql FROM sqlite_master WHERE sql!='' AND rootpage!=0"
@@ -172554,9 +201030,9 @@ static void rbuCreateTargetSchema(sqlite3rbu *p){
);
}
- while( p->rc==SQLITE_OK && sqlite3_step(pSql)==SQLITE_ROW ){
- const char *zSql = (const char*)sqlite3_column_text(pSql, 0);
- p->rc = sqlite3_exec(p->dbMain, zSql, 0, 0, &p->zErrmsg);
+ while( p->rc==SQLITE_OK && tdsqlite3_step(pSql)==SQLITE_ROW ){
+ const char *zSql = (const char*)tdsqlite3_column_text(pSql, 0);
+ p->rc = tdsqlite3_exec(p->dbMain, zSql, 0, 0, &p->zErrmsg);
}
rbuFinalize(p, pSql);
if( p->rc!=SQLITE_OK ) return;
@@ -172573,16 +201049,16 @@ static void rbuCreateTargetSchema(sqlite3rbu *p){
);
}
- while( p->rc==SQLITE_OK && sqlite3_step(pSql)==SQLITE_ROW ){
+ while( p->rc==SQLITE_OK && tdsqlite3_step(pSql)==SQLITE_ROW ){
int i;
for(i=0; i<5; i++){
- sqlite3_bind_value(pInsert, i+1, sqlite3_column_value(pSql, i));
+ tdsqlite3_bind_value(pInsert, i+1, tdsqlite3_column_value(pSql, i));
}
- sqlite3_step(pInsert);
- p->rc = sqlite3_reset(pInsert);
+ tdsqlite3_step(pInsert);
+ p->rc = tdsqlite3_reset(pInsert);
}
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_exec(p->dbMain, "PRAGMA writable_schema=0",0,0,&p->zErrmsg);
+ p->rc = tdsqlite3_exec(p->dbMain, "PRAGMA writable_schema=0",0,0,&p->zErrmsg);
}
rbuFinalize(p, pSql);
@@ -172592,7 +201068,7 @@ static void rbuCreateTargetSchema(sqlite3rbu *p){
/*
** Step the RBU object.
*/
-SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){
+SQLITE_API int tdsqlite3rbu_step(tdsqlite3rbu *p){
if( p ){
switch( p->eStage ){
case RBU_STAGE_OAL: {
@@ -172622,13 +201098,13 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){
/* Advance to the next row to process. */
if( p->rc==SQLITE_OK ){
- int rc = sqlite3_step(pIter->pSelect);
+ int rc = tdsqlite3_step(pIter->pSelect);
if( rc==SQLITE_ROW ){
p->nProgress++;
p->nStep++;
return rbuStep(p);
}
- p->rc = sqlite3_reset(pIter->pSelect);
+ p->rc = tdsqlite3_reset(pIter->pSelect);
p->nStep = 0;
}
}
@@ -172641,10 +201117,10 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){
rbuSaveState(p, RBU_STAGE_MOVE);
rbuIncrSchemaCookie(p);
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_exec(p->dbMain, "COMMIT", 0, 0, &p->zErrmsg);
+ p->rc = tdsqlite3_exec(p->dbMain, "COMMIT", 0, 0, &p->zErrmsg);
}
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, &p->zErrmsg);
+ p->rc = tdsqlite3_exec(p->dbRbu, "COMMIT", 0, 0, &p->zErrmsg);
}
p->eStage = RBU_STAGE_MOVE;
}
@@ -172662,7 +201138,7 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){
case RBU_STAGE_CKPT: {
if( p->rc==SQLITE_OK ){
if( p->nStep>=p->nFrame ){
- sqlite3_file *pDb = p->pTargetFd->pReal;
+ tdsqlite3_file *pDb = p->pTargetFd->pReal;
/* Sync the db file */
p->rc = pDb->pMethods->xSync(pDb, SQLITE_SYNC_NORMAL);
@@ -172681,9 +201157,26 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){
p->rc = SQLITE_DONE;
}
}else{
- RbuFrame *pFrame = &p->aFrame[p->nStep];
- rbuCheckpointFrame(p, pFrame);
- p->nStep++;
+ /* At one point the following block copied a single frame from the
+ ** wal file to the database file. So that one call to tdsqlite3rbu_step()
+ ** checkpointed a single frame.
+ **
+ ** However, if the sector-size is larger than the page-size, and the
+ ** application calls tdsqlite3rbu_savestate() or close() immediately
+ ** after this step, then rbu_step() again, then a power failure occurs,
+ ** then the database page written here may be damaged. Work around
+ ** this by checkpointing frames until the next page in the aFrame[]
+ ** lies on a different disk sector to the current one. */
+ u32 iSector;
+ do{
+ RbuFrame *pFrame = &p->aFrame[p->nStep];
+ iSector = (pFrame->iDbPage-1) / p->nPagePerSector;
+ rbuCheckpointFrame(p, pFrame);
+ p->nStep++;
+ }while( p->nStep<p->nFrame
+ && iSector==((p->aFrame[p->nStep].iDbPage-1) / p->nPagePerSector)
+ && p->rc==SQLITE_OK
+ );
}
p->nProgress++;
}
@@ -172707,20 +201200,20 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){
static int rbuStrCompare(const char *z1, const char *z2){
if( z1==0 && z2==0 ) return 0;
if( z1==0 || z2==0 ) return 1;
- return (sqlite3_stricmp(z1, z2)!=0);
+ return (tdsqlite3_stricmp(z1, z2)!=0);
}
/*
-** This function is called as part of sqlite3rbu_open() when initializing
+** This function is called as part of tdsqlite3rbu_open() when initializing
** an rbu handle in OAL stage. If the rbu update has not started (i.e.
** the rbu_state table was empty) it is a no-op. Otherwise, it arranges
-** things so that the next call to sqlite3rbu_step() continues on from
+** things so that the next call to tdsqlite3rbu_step() continues on from
** where the previous rbu handle left off.
**
** If an error occurs, an error code and error message are left in the
** rbu handle passed as the first argument.
*/
-static void rbuSetupOal(sqlite3rbu *p, RbuState *pState){
+static void rbuSetupOal(tdsqlite3rbu *p, RbuState *pState){
assert( p->rc==SQLITE_OK );
if( pState->zTbl ){
RbuObjIter *pIter = &p->objiter;
@@ -172728,14 +201221,15 @@ static void rbuSetupOal(sqlite3rbu *p, RbuState *pState){
while( rc==SQLITE_OK && pIter->zTbl && (pIter->bCleanup
|| rbuStrCompare(pIter->zIdx, pState->zIdx)
- || rbuStrCompare(pIter->zTbl, pState->zTbl)
+ || (pState->zDataTbl==0 && rbuStrCompare(pIter->zTbl, pState->zTbl))
+ || (pState->zDataTbl && rbuStrCompare(pIter->zDataTbl, pState->zDataTbl))
)){
rc = rbuObjIterNext(p, pIter);
}
if( rc==SQLITE_OK && !pIter->zTbl ){
rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("rbu_state mismatch error");
+ p->zErrmsg = tdsqlite3_mprintf("rbu_state mismatch error");
}
if( rc==SQLITE_OK ){
@@ -172752,34 +201246,35 @@ static void rbuSetupOal(sqlite3rbu *p, RbuState *pState){
** target database in the file-system, delete it. If an error occurs,
** leave an error code and error message in the rbu handle.
*/
-static void rbuDeleteOalFile(sqlite3rbu *p){
+static void rbuDeleteOalFile(tdsqlite3rbu *p){
char *zOal = rbuMPrintf(p, "%s-oal", p->zTarget);
if( zOal ){
- sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
+ tdsqlite3_vfs *pVfs = tdsqlite3_vfs_find(0);
assert( pVfs && p->rc==SQLITE_OK && p->zErrmsg==0 );
pVfs->xDelete(pVfs, zOal, 0);
- sqlite3_free(zOal);
+ tdsqlite3_free(zOal);
}
}
/*
** Allocate a private rbu VFS for the rbu handle passed as the only
-** argument. This VFS will be used unless the call to sqlite3rbu_open()
+** argument. This VFS will be used unless the call to tdsqlite3rbu_open()
** specified a URI with a vfs=? option in place of a target database
** file name.
*/
-static void rbuCreateVfs(sqlite3rbu *p){
+static void rbuCreateVfs(tdsqlite3rbu *p){
int rnd;
char zRnd[64];
assert( p->rc==SQLITE_OK );
- sqlite3_randomness(sizeof(int), (void*)&rnd);
- sqlite3_snprintf(sizeof(zRnd), zRnd, "rbu_vfs_%d", rnd);
- p->rc = sqlite3rbu_create_vfs(zRnd, 0);
+ tdsqlite3_randomness(sizeof(int), (void*)&rnd);
+ tdsqlite3_snprintf(sizeof(zRnd), zRnd, "rbu_vfs_%d", rnd);
+ p->rc = tdsqlite3rbu_create_vfs(zRnd, 0);
if( p->rc==SQLITE_OK ){
- sqlite3_vfs *pVfs = sqlite3_vfs_find(zRnd);
+ tdsqlite3_vfs *pVfs = tdsqlite3_vfs_find(zRnd);
assert( pVfs );
p->zVfsName = pVfs->zName;
+ ((rbu_vfs*)pVfs)->pRbu = p;
}
}
@@ -172787,9 +201282,9 @@ static void rbuCreateVfs(sqlite3rbu *p){
** Destroy the private VFS created for the rbu handle passed as the only
** argument by an earlier call to rbuCreateVfs().
*/
-static void rbuDeleteVfs(sqlite3rbu *p){
+static void rbuDeleteVfs(tdsqlite3rbu *p){
if( p->zVfsName ){
- sqlite3rbu_destroy_vfs(p->zVfsName);
+ tdsqlite3rbu_destroy_vfs(p->zVfsName);
p->zVfsName = 0;
}
}
@@ -172800,42 +201295,43 @@ static void rbuDeleteVfs(sqlite3rbu *p){
** the number of auxilliary indexes on the table.
*/
static void rbuIndexCntFunc(
- sqlite3_context *pCtx,
+ tdsqlite3_context *pCtx,
int nVal,
- sqlite3_value **apVal
+ tdsqlite3_value **apVal
){
- sqlite3rbu *p = (sqlite3rbu*)sqlite3_user_data(pCtx);
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3rbu *p = (tdsqlite3rbu*)tdsqlite3_user_data(pCtx);
+ tdsqlite3_stmt *pStmt = 0;
char *zErrmsg = 0;
int rc;
+ tdsqlite3 *db = (rbuIsVacuum(p) ? p->dbRbu : p->dbMain);
assert( nVal==1 );
- rc = prepareFreeAndCollectError(p->dbMain, &pStmt, &zErrmsg,
- sqlite3_mprintf("SELECT count(*) FROM sqlite_master "
- "WHERE type='index' AND tbl_name = %Q", sqlite3_value_text(apVal[0]))
+ rc = prepareFreeAndCollectError(db, &pStmt, &zErrmsg,
+ tdsqlite3_mprintf("SELECT count(*) FROM sqlite_master "
+ "WHERE type='index' AND tbl_name = %Q", tdsqlite3_value_text(apVal[0]))
);
if( rc!=SQLITE_OK ){
- sqlite3_result_error(pCtx, zErrmsg, -1);
+ tdsqlite3_result_error(pCtx, zErrmsg, -1);
}else{
int nIndex = 0;
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- nIndex = sqlite3_column_int(pStmt, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ nIndex = tdsqlite3_column_int(pStmt, 0);
}
- rc = sqlite3_finalize(pStmt);
+ rc = tdsqlite3_finalize(pStmt);
if( rc==SQLITE_OK ){
- sqlite3_result_int(pCtx, nIndex);
+ tdsqlite3_result_int(pCtx, nIndex);
}else{
- sqlite3_result_error(pCtx, sqlite3_errmsg(p->dbMain), -1);
+ tdsqlite3_result_error(pCtx, tdsqlite3_errmsg(db), -1);
}
}
- sqlite3_free(zErrmsg);
+ tdsqlite3_free(zErrmsg);
}
/*
** If the RBU database contains the rbu_count table, use it to initialize
-** the sqlite3rbu.nPhaseOneStep variable. The schema of the rbu_count table
+** the tdsqlite3rbu.nPhaseOneStep variable. The schema of the rbu_count table
** is assumed to contain the same columns as:
**
** CREATE TABLE rbu_count(tbl TEXT PRIMARY KEY, cnt INTEGER) WITHOUT ROWID;
@@ -172844,18 +201340,18 @@ static void rbuIndexCntFunc(
** database. The 'tbl' column should contain the name of a data_xxx table,
** and the cnt column the number of rows it contains.
**
-** sqlite3rbu.nPhaseOneStep is initialized to the sum of (1 + nIndex) * cnt
+** tdsqlite3rbu.nPhaseOneStep is initialized to the sum of (1 + nIndex) * cnt
** for all rows in the rbu_count table, where nIndex is the number of
** indexes on the corresponding target database table.
*/
-static void rbuInitPhaseOneSteps(sqlite3rbu *p){
+static void rbuInitPhaseOneSteps(tdsqlite3rbu *p){
if( p->rc==SQLITE_OK ){
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
int bExists = 0; /* True if rbu_count exists */
p->nPhaseOneStep = -1;
- p->rc = sqlite3_create_function(p->dbRbu,
+ p->rc = tdsqlite3_create_function(p->dbRbu,
"rbu_index_cnt", 1, SQLITE_UTF8, (void*)p, rbuIndexCntFunc, 0, 0
);
@@ -172867,10 +201363,10 @@ static void rbuInitPhaseOneSteps(sqlite3rbu *p){
);
}
if( p->rc==SQLITE_OK ){
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
bExists = 1;
}
- p->rc = sqlite3_finalize(pStmt);
+ p->rc = tdsqlite3_finalize(pStmt);
}
if( p->rc==SQLITE_OK && bExists ){
@@ -172879,37 +201375,38 @@ static void rbuInitPhaseOneSteps(sqlite3rbu *p){
"FROM rbu_count"
);
if( p->rc==SQLITE_OK ){
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- p->nPhaseOneStep = sqlite3_column_int64(pStmt, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ p->nPhaseOneStep = tdsqlite3_column_int64(pStmt, 0);
}
- p->rc = sqlite3_finalize(pStmt);
+ p->rc = tdsqlite3_finalize(pStmt);
}
}
}
}
-static sqlite3rbu *openRbuHandle(
+static tdsqlite3rbu *openRbuHandle(
const char *zTarget,
const char *zRbu,
const char *zState
){
- sqlite3rbu *p;
+ tdsqlite3rbu *p;
size_t nTarget = zTarget ? strlen(zTarget) : 0;
size_t nRbu = strlen(zRbu);
- size_t nByte = sizeof(sqlite3rbu) + nTarget+1 + nRbu+1;
+ size_t nByte = sizeof(tdsqlite3rbu) + nTarget+1 + nRbu+1;
- p = (sqlite3rbu*)sqlite3_malloc64(nByte);
+ p = (tdsqlite3rbu*)tdsqlite3_malloc64(nByte);
if( p ){
RbuState *pState = 0;
/* Create the custom VFS. */
- memset(p, 0, sizeof(sqlite3rbu));
+ memset(p, 0, sizeof(tdsqlite3rbu));
rbuCreateVfs(p);
/* Open the target, RBU and state databases */
if( p->rc==SQLITE_OK ){
char *pCsr = (char*)&p[1];
+ int bRetry = 0;
if( zTarget ){
p->zTarget = pCsr;
memcpy(p->zTarget, zTarget, nTarget+1);
@@ -172921,7 +201418,18 @@ static sqlite3rbu *openRbuHandle(
if( zState ){
p->zState = rbuMPrintf(p, "%s", zState);
}
- rbuOpenDatabase(p);
+
+ /* If the first attempt to open the database file fails and the bRetry
+ ** flag it set, this means that the db was not opened because it seemed
+ ** to be a wal-mode db. But, this may have happened due to an earlier
+ ** RBU vacuum operation leaving an old wal file in the directory.
+ ** If this is the case, it will have been checkpointed and deleted
+ ** when the handle was closed and a second attempt to open the
+ ** database may succeed. */
+ rbuOpenDatabase(p, &bRetry);
+ if( bRetry ){
+ rbuOpenDatabase(p, 0);
+ }
}
if( p->rc==SQLITE_OK ){
@@ -172946,7 +201454,7 @@ static sqlite3rbu *openRbuHandle(
if( p->rc==SQLITE_OK && p->pTargetFd->pWalFd ){
if( p->eStage==RBU_STAGE_OAL ){
p->rc = SQLITE_ERROR;
- p->zErrmsg = sqlite3_mprintf("cannot update wal mode database");
+ p->zErrmsg = tdsqlite3_mprintf("cannot update wal mode database");
}else if( p->eStage==RBU_STAGE_MOVE ){
p->eStage = RBU_STAGE_CKPT;
p->nStep = 0;
@@ -172964,7 +201472,7 @@ static sqlite3rbu *openRbuHandle(
** transaction is committed in rollback mode) currently stored on
** page 1 of the database file. */
p->rc = SQLITE_BUSY;
- p->zErrmsg = sqlite3_mprintf("database modified during rbu %s",
+ p->zErrmsg = tdsqlite3_mprintf("database modified during rbu %s",
(rbuIsVacuum(p) ? "vacuum" : "update")
);
}
@@ -172972,8 +201480,8 @@ static sqlite3rbu *openRbuHandle(
if( p->rc==SQLITE_OK ){
if( p->eStage==RBU_STAGE_OAL ){
- sqlite3 *db = p->dbMain;
- p->rc = sqlite3_exec(p->dbRbu, "BEGIN", 0, 0, &p->zErrmsg);
+ tdsqlite3 *db = p->dbMain;
+ p->rc = tdsqlite3_exec(p->dbRbu, "BEGIN", 0, 0, &p->zErrmsg);
/* Point the object iterator at the first object */
if( p->rc==SQLITE_OK ){
@@ -172994,16 +201502,16 @@ static sqlite3rbu *openRbuHandle(
/* Open transactions both databases. The *-oal file is opened or
** created at this point. */
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, &p->zErrmsg);
+ p->rc = tdsqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, &p->zErrmsg);
}
/* Check if the main database is a zipvfs db. If it is, set the upper
** level pager to use "journal_mode=off". This prevents it from
** generating a large journal using a temp file. */
if( p->rc==SQLITE_OK ){
- int frc = sqlite3_file_control(db, "main", SQLITE_FCNTL_ZIPVFS, 0);
+ int frc = tdsqlite3_file_control(db, "main", SQLITE_FCNTL_ZIPVFS, 0);
if( frc==SQLITE_OK ){
- p->rc = sqlite3_exec(
+ p->rc = tdsqlite3_exec(
db, "PRAGMA journal_mode=off",0,0,&p->zErrmsg);
}
}
@@ -173033,11 +201541,11 @@ static sqlite3rbu *openRbuHandle(
** Allocate and return an RBU handle with all fields zeroed except for the
** error code, which is set to SQLITE_MISUSE.
*/
-static sqlite3rbu *rbuMisuseError(void){
- sqlite3rbu *pRet;
- pRet = sqlite3_malloc64(sizeof(sqlite3rbu));
+static tdsqlite3rbu *rbuMisuseError(void){
+ tdsqlite3rbu *pRet;
+ pRet = tdsqlite3_malloc64(sizeof(tdsqlite3rbu));
if( pRet ){
- memset(pRet, 0, sizeof(sqlite3rbu));
+ memset(pRet, 0, sizeof(tdsqlite3rbu));
pRet->rc = SQLITE_MISUSE;
}
return pRet;
@@ -173046,7 +201554,7 @@ static sqlite3rbu *rbuMisuseError(void){
/*
** Open and return a new RBU handle.
*/
-SQLITE_API sqlite3rbu *sqlite3rbu_open(
+SQLITE_API tdsqlite3rbu *tdsqlite3rbu_open(
const char *zTarget,
const char *zRbu,
const char *zState
@@ -173059,11 +201567,17 @@ SQLITE_API sqlite3rbu *sqlite3rbu_open(
/*
** Open a handle to begin or resume an RBU VACUUM operation.
*/
-SQLITE_API sqlite3rbu *sqlite3rbu_vacuum(
+SQLITE_API tdsqlite3rbu *tdsqlite3rbu_vacuum(
const char *zTarget,
const char *zState
){
if( zTarget==0 ){ return rbuMisuseError(); }
+ if( zState ){
+ int n = strlen(zState);
+ if( n>=7 && 0==memcmp("-vactmp", &zState[n-7], 7) ){
+ return rbuMisuseError();
+ }
+ }
/* TODO: Check that both arguments are non-NULL */
return openRbuHandle(0, zTarget, zState);
}
@@ -173071,8 +201585,8 @@ SQLITE_API sqlite3rbu *sqlite3rbu_vacuum(
/*
** Return the database handle used by pRbu.
*/
-SQLITE_API sqlite3 *sqlite3rbu_db(sqlite3rbu *pRbu, int bRbu){
- sqlite3 *db = 0;
+SQLITE_API tdsqlite3 *tdsqlite3rbu_db(tdsqlite3rbu *pRbu, int bRbu){
+ tdsqlite3 *db = 0;
if( pRbu ){
db = (bRbu ? pRbu->dbRbu : pRbu->dbMain);
}
@@ -173085,7 +201599,7 @@ SQLITE_API sqlite3 *sqlite3rbu_db(sqlite3rbu *pRbu, int bRbu){
** then edit any error message string so as to remove all occurrences of
** the pattern "rbu_imp_[0-9]*".
*/
-static void rbuEditErrmsg(sqlite3rbu *p){
+static void rbuEditErrmsg(tdsqlite3rbu *p){
if( p->rc==SQLITE_CONSTRAINT && p->zErrmsg ){
unsigned int i;
size_t nErrmsg = strlen(p->zErrmsg);
@@ -173103,19 +201617,25 @@ static void rbuEditErrmsg(sqlite3rbu *p){
/*
** Close the RBU handle.
*/
-SQLITE_API int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){
+SQLITE_API int tdsqlite3rbu_close(tdsqlite3rbu *p, char **pzErrmsg){
int rc;
if( p ){
/* Commit the transaction to the *-oal file. */
if( p->rc==SQLITE_OK && p->eStage==RBU_STAGE_OAL ){
- p->rc = sqlite3_exec(p->dbMain, "COMMIT", 0, 0, &p->zErrmsg);
+ p->rc = tdsqlite3_exec(p->dbMain, "COMMIT", 0, 0, &p->zErrmsg);
+ }
+
+ /* Sync the db file if currently doing an incremental checkpoint */
+ if( p->rc==SQLITE_OK && p->eStage==RBU_STAGE_CKPT ){
+ tdsqlite3_file *pDb = p->pTargetFd->pReal;
+ p->rc = pDb->pMethods->xSync(pDb, SQLITE_SYNC_NORMAL);
}
rbuSaveState(p, p->eStage);
if( p->rc==SQLITE_OK && p->eStage==RBU_STAGE_OAL ){
- p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, &p->zErrmsg);
+ p->rc = tdsqlite3_exec(p->dbRbu, "COMMIT", 0, 0, &p->zErrmsg);
}
/* Close any open statement handles. */
@@ -173123,26 +201643,31 @@ SQLITE_API int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){
/* If this is an RBU vacuum handle and the vacuum has either finished
** successfully or encountered an error, delete the contents of the
- ** state table. This causes the next call to sqlite3rbu_vacuum()
+ ** state table. This causes the next call to tdsqlite3rbu_vacuum()
** specifying the current target and state databases to start a new
** vacuum from scratch. */
if( rbuIsVacuum(p) && p->rc!=SQLITE_OK && p->dbRbu ){
- int rc2 = sqlite3_exec(p->dbRbu, "DELETE FROM stat.rbu_state", 0, 0, 0);
+ int rc2 = tdsqlite3_exec(p->dbRbu, "DELETE FROM stat.rbu_state", 0, 0, 0);
if( p->rc==SQLITE_DONE && rc2!=SQLITE_OK ) p->rc = rc2;
}
/* Close the open database handle and VFS object. */
- sqlite3_close(p->dbRbu);
- sqlite3_close(p->dbMain);
+ tdsqlite3_close(p->dbRbu);
+ tdsqlite3_close(p->dbMain);
+ assert( p->szTemp==0 );
rbuDeleteVfs(p);
- sqlite3_free(p->aBuf);
- sqlite3_free(p->aFrame);
+ tdsqlite3_free(p->aBuf);
+ tdsqlite3_free(p->aFrame);
rbuEditErrmsg(p);
rc = p->rc;
- *pzErrmsg = p->zErrmsg;
- sqlite3_free(p->zState);
- sqlite3_free(p);
+ if( pzErrmsg ){
+ *pzErrmsg = p->zErrmsg;
+ }else{
+ tdsqlite3_free(p->zErrmsg);
+ }
+ tdsqlite3_free(p->zState);
+ tdsqlite3_free(p);
}else{
rc = SQLITE_NOMEM;
*pzErrmsg = 0;
@@ -173155,7 +201680,7 @@ SQLITE_API int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){
** updates) that have been performed on the target database since the
** current RBU update was started.
*/
-SQLITE_API sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu){
+SQLITE_API tdsqlite3_int64 tdsqlite3rbu_progress(tdsqlite3rbu *pRbu){
return pRbu->nProgress;
}
@@ -173163,7 +201688,7 @@ SQLITE_API sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu){
** Return permyriadage progress indications for the two main stages of
** an RBU update.
*/
-SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *p, int *pnOne, int *pnTwo){
+SQLITE_API void tdsqlite3rbu_bp_progress(tdsqlite3rbu *p, int *pnOne, int *pnTwo){
const int MAX_PROGRESS = 10000;
switch( p->eStage ){
case RBU_STAGE_OAL:
@@ -173198,7 +201723,7 @@ SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *p, int *pnOne, int *pnTwo){
/*
** Return the current state of the RBU vacuum or update operation.
*/
-SQLITE_API int sqlite3rbu_state(sqlite3rbu *p){
+SQLITE_API int tdsqlite3rbu_state(tdsqlite3rbu *p){
int aRes[] = {
0, SQLITE_RBU_STATE_OAL, SQLITE_RBU_STATE_MOVE,
0, SQLITE_RBU_STATE_CHECKPOINT, SQLITE_RBU_STATE_DONE
@@ -173226,14 +201751,20 @@ SQLITE_API int sqlite3rbu_state(sqlite3rbu *p){
}
}
-SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){
+SQLITE_API int tdsqlite3rbu_savestate(tdsqlite3rbu *p){
int rc = p->rc;
if( rc==SQLITE_DONE ) return SQLITE_OK;
assert( p->eStage>=RBU_STAGE_OAL && p->eStage<=RBU_STAGE_DONE );
if( p->eStage==RBU_STAGE_OAL ){
assert( rc!=SQLITE_DONE );
- if( rc==SQLITE_OK ) rc = sqlite3_exec(p->dbMain, "COMMIT", 0, 0, 0);
+ if( rc==SQLITE_OK ) rc = tdsqlite3_exec(p->dbMain, "COMMIT", 0, 0, 0);
+ }
+
+ /* Sync the db file */
+ if( rc==SQLITE_OK && p->eStage==RBU_STAGE_CKPT ){
+ tdsqlite3_file *pDb = p->pTargetFd->pReal;
+ rc = pDb->pMethods->xSync(pDb, SQLITE_SYNC_NORMAL);
}
p->rc = rc;
@@ -173242,9 +201773,12 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){
if( p->eStage==RBU_STAGE_OAL ){
assert( rc!=SQLITE_DONE );
- if( rc==SQLITE_OK ) rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0);
- if( rc==SQLITE_OK ) rc = sqlite3_exec(p->dbRbu, "BEGIN IMMEDIATE", 0, 0, 0);
- if( rc==SQLITE_OK ) rc = sqlite3_exec(p->dbMain, "BEGIN IMMEDIATE", 0, 0,0);
+ if( rc==SQLITE_OK ) rc = tdsqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0);
+ if( rc==SQLITE_OK ){
+ const char *zBegin = rbuIsVacuum(p) ? "BEGIN" : "BEGIN IMMEDIATE";
+ rc = tdsqlite3_exec(p->dbRbu, zBegin, 0, 0, 0);
+ }
+ if( rc==SQLITE_OK ) rc = tdsqlite3_exec(p->dbMain, "BEGIN IMMEDIATE", 0, 0,0);
}
p->rc = rc;
@@ -173297,7 +201831,7 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){
** mode except RBU_STAGE_DONE (all work completed and checkpointed), it
** fails with an SQLITE_BUSY error. This is to stop RBU connections
** from automatically checkpointing a *-wal (or *-oal) file from within
-** sqlite3_close().
+** tdsqlite3_close().
**
** 3d. In RBU_STAGE_CAPTURE mode, all xRead() calls on the wal file, and
** all xWrite() calls on the target database file perform no IO.
@@ -173311,8 +201845,9 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){
*/
static void rbuUnlockShm(rbu_file *p){
+ assert( p->openFlags & SQLITE_OPEN_MAIN_DB );
if( p->pRbu ){
- int (*xShmLock)(sqlite3_file*,int,int,int) = p->pReal->pMethods->xShmLock;
+ int (*xShmLock)(tdsqlite3_file*,int,int,int) = p->pReal->pMethods->xShmLock;
int i;
for(i=0; i<SQLITE_SHM_NLOCK;i++){
if( (1<<i) & p->pRbu->mLock ){
@@ -173324,30 +201859,105 @@ static void rbuUnlockShm(rbu_file *p){
}
/*
+*/
+static int rbuUpdateTempSize(rbu_file *pFd, tdsqlite3_int64 nNew){
+ tdsqlite3rbu *pRbu = pFd->pRbu;
+ i64 nDiff = nNew - pFd->sz;
+ pRbu->szTemp += nDiff;
+ pFd->sz = nNew;
+ assert( pRbu->szTemp>=0 );
+ if( pRbu->szTempLimit && pRbu->szTemp>pRbu->szTempLimit ) return SQLITE_FULL;
+ return SQLITE_OK;
+}
+
+/*
+** Add an item to the main-db lists, if it is not already present.
+**
+** There are two main-db lists. One for all file descriptors, and one
+** for all file descriptors with rbu_file.pDb!=0. If the argument has
+** rbu_file.pDb!=0, then it is assumed to already be present on the
+** main list and is only added to the pDb!=0 list.
+*/
+static void rbuMainlistAdd(rbu_file *p){
+ rbu_vfs *pRbuVfs = p->pRbuVfs;
+ rbu_file *pIter;
+ assert( (p->openFlags & SQLITE_OPEN_MAIN_DB) );
+ tdsqlite3_mutex_enter(pRbuVfs->mutex);
+ if( p->pRbu==0 ){
+ for(pIter=pRbuVfs->pMain; pIter; pIter=pIter->pMainNext);
+ p->pMainNext = pRbuVfs->pMain;
+ pRbuVfs->pMain = p;
+ }else{
+ for(pIter=pRbuVfs->pMainRbu; pIter && pIter!=p; pIter=pIter->pMainRbuNext){}
+ if( pIter==0 ){
+ p->pMainRbuNext = pRbuVfs->pMainRbu;
+ pRbuVfs->pMainRbu = p;
+ }
+ }
+ tdsqlite3_mutex_leave(pRbuVfs->mutex);
+}
+
+/*
+** Remove an item from the main-db lists.
+*/
+static void rbuMainlistRemove(rbu_file *p){
+ rbu_file **pp;
+ tdsqlite3_mutex_enter(p->pRbuVfs->mutex);
+ for(pp=&p->pRbuVfs->pMain; *pp && *pp!=p; pp=&((*pp)->pMainNext)){}
+ if( *pp ) *pp = p->pMainNext;
+ p->pMainNext = 0;
+ for(pp=&p->pRbuVfs->pMainRbu; *pp && *pp!=p; pp=&((*pp)->pMainRbuNext)){}
+ if( *pp ) *pp = p->pMainRbuNext;
+ p->pMainRbuNext = 0;
+ tdsqlite3_mutex_leave(p->pRbuVfs->mutex);
+}
+
+/*
+** Given that zWal points to a buffer containing a wal file name passed to
+** either the xOpen() or xAccess() VFS method, search the main-db list for
+** a file-handle opened by the same database connection on the corresponding
+** database file.
+**
+** If parameter bRbu is true, only search for file-descriptors with
+** rbu_file.pDb!=0.
+*/
+static rbu_file *rbuFindMaindb(rbu_vfs *pRbuVfs, const char *zWal, int bRbu){
+ rbu_file *pDb;
+ tdsqlite3_mutex_enter(pRbuVfs->mutex);
+ if( bRbu ){
+ for(pDb=pRbuVfs->pMainRbu; pDb && pDb->zWal!=zWal; pDb=pDb->pMainRbuNext){}
+ }else{
+ for(pDb=pRbuVfs->pMain; pDb && pDb->zWal!=zWal; pDb=pDb->pMainNext){}
+ }
+ tdsqlite3_mutex_leave(pRbuVfs->mutex);
+ return pDb;
+}
+
+/*
** Close an rbu file.
*/
-static int rbuVfsClose(sqlite3_file *pFile){
+static int rbuVfsClose(tdsqlite3_file *pFile){
rbu_file *p = (rbu_file*)pFile;
int rc;
int i;
/* Free the contents of the apShm[] array. And the array itself. */
for(i=0; i<p->nShm; i++){
- sqlite3_free(p->apShm[i]);
+ tdsqlite3_free(p->apShm[i]);
}
- sqlite3_free(p->apShm);
+ tdsqlite3_free(p->apShm);
p->apShm = 0;
- sqlite3_free(p->zDel);
+ tdsqlite3_free(p->zDel);
if( p->openFlags & SQLITE_OPEN_MAIN_DB ){
- rbu_file **pp;
- sqlite3_mutex_enter(p->pRbuVfs->mutex);
- for(pp=&p->pRbuVfs->pMain; *pp!=p; pp=&((*pp)->pMainNext));
- *pp = p->pMainNext;
- sqlite3_mutex_leave(p->pRbuVfs->mutex);
+ rbuMainlistRemove(p);
rbuUnlockShm(p);
p->pReal->pMethods->xShmUnmap(p->pReal, 0);
}
+ else if( (p->openFlags & SQLITE_OPEN_DELETEONCLOSE) && p->pRbu ){
+ rbuUpdateTempSize(p, 0);
+ }
+ assert( p->pMainNext==0 && p->pRbuVfs->pMain!=p );
/* Close the underlying file handle */
rc = p->pReal->pMethods->xClose(p->pReal);
@@ -173386,13 +201996,13 @@ static void rbuPutU16(u8 *aBuf, u16 iVal){
** Read data from an rbuVfs-file.
*/
static int rbuVfsRead(
- sqlite3_file *pFile,
+ tdsqlite3_file *pFile,
void *zBuf,
int iAmt,
sqlite_int64 iOfst
){
rbu_file *p = (rbu_file*)pFile;
- sqlite3rbu *pRbu = p->pRbu;
+ tdsqlite3rbu *pRbu = p->pRbu;
int rc;
if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){
@@ -173417,7 +202027,7 @@ static int rbuVfsRead(
&& (p->openFlags & SQLITE_OPEN_MAIN_DB)
&& pRbu->rc==SQLITE_OK
){
- sqlite3_file *pFd = (sqlite3_file*)pRbu->pRbuFd;
+ tdsqlite3_file *pFd = (tdsqlite3_file*)pRbu->pRbuFd;
rc = pFd->pMethods->xRead(pFd, zBuf, iAmt, iOfst);
if( rc==SQLITE_OK ){
u8 *aBuf = (u8*)zBuf;
@@ -173452,24 +202062,32 @@ static int rbuVfsRead(
** Write data to an rbuVfs-file.
*/
static int rbuVfsWrite(
- sqlite3_file *pFile,
+ tdsqlite3_file *pFile,
const void *zBuf,
int iAmt,
sqlite_int64 iOfst
){
rbu_file *p = (rbu_file*)pFile;
- sqlite3rbu *pRbu = p->pRbu;
+ tdsqlite3rbu *pRbu = p->pRbu;
int rc;
if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){
assert( p->openFlags & SQLITE_OPEN_MAIN_DB );
rc = rbuCaptureDbWrite(p->pRbu, iOfst);
}else{
- if( pRbu && pRbu->eStage==RBU_STAGE_OAL
- && (p->openFlags & SQLITE_OPEN_WAL)
- && iOfst>=pRbu->iOalSz
- ){
- pRbu->iOalSz = iAmt + iOfst;
+ if( pRbu ){
+ if( pRbu->eStage==RBU_STAGE_OAL
+ && (p->openFlags & SQLITE_OPEN_WAL)
+ && iOfst>=pRbu->iOalSz
+ ){
+ pRbu->iOalSz = iAmt + iOfst;
+ }else if( p->openFlags & SQLITE_OPEN_DELETEONCLOSE ){
+ i64 szNew = iAmt+iOfst;
+ if( szNew>p->sz ){
+ rc = rbuUpdateTempSize(p, szNew);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ }
}
rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
if( rc==SQLITE_OK && iOfst==0 && (p->openFlags & SQLITE_OPEN_MAIN_DB) ){
@@ -173486,15 +202104,19 @@ static int rbuVfsWrite(
/*
** Truncate an rbuVfs-file.
*/
-static int rbuVfsTruncate(sqlite3_file *pFile, sqlite_int64 size){
+static int rbuVfsTruncate(tdsqlite3_file *pFile, sqlite_int64 size){
rbu_file *p = (rbu_file*)pFile;
+ if( (p->openFlags & SQLITE_OPEN_DELETEONCLOSE) && p->pRbu ){
+ int rc = rbuUpdateTempSize(p, size);
+ if( rc!=SQLITE_OK ) return rc;
+ }
return p->pReal->pMethods->xTruncate(p->pReal, size);
}
/*
** Sync an rbuVfs-file.
*/
-static int rbuVfsSync(sqlite3_file *pFile, int flags){
+static int rbuVfsSync(tdsqlite3_file *pFile, int flags){
rbu_file *p = (rbu_file *)pFile;
if( p->pRbu && p->pRbu->eStage==RBU_STAGE_CAPTURE ){
if( p->openFlags & SQLITE_OPEN_MAIN_DB ){
@@ -173508,7 +202130,7 @@ static int rbuVfsSync(sqlite3_file *pFile, int flags){
/*
** Return the current file-size of an rbuVfs-file.
*/
-static int rbuVfsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
+static int rbuVfsFileSize(tdsqlite3_file *pFile, sqlite_int64 *pSize){
rbu_file *p = (rbu_file *)pFile;
int rc;
rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
@@ -173529,9 +202151,9 @@ static int rbuVfsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
/*
** Lock an rbuVfs-file.
*/
-static int rbuVfsLock(sqlite3_file *pFile, int eLock){
+static int rbuVfsLock(tdsqlite3_file *pFile, int eLock){
rbu_file *p = (rbu_file*)pFile;
- sqlite3rbu *pRbu = p->pRbu;
+ tdsqlite3rbu *pRbu = p->pRbu;
int rc = SQLITE_OK;
assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) );
@@ -173539,7 +202161,7 @@ static int rbuVfsLock(sqlite3_file *pFile, int eLock){
&& (p->bNolock || (pRbu && pRbu->eStage!=RBU_STAGE_DONE))
){
/* Do not allow EXCLUSIVE locks. Preventing SQLite from taking this
- ** prevents it from checkpointing the database from sqlite3_close(). */
+ ** prevents it from checkpointing the database from tdsqlite3_close(). */
rc = SQLITE_BUSY;
}else{
rc = p->pReal->pMethods->xLock(p->pReal, eLock);
@@ -173551,7 +202173,7 @@ static int rbuVfsLock(sqlite3_file *pFile, int eLock){
/*
** Unlock an rbuVfs-file.
*/
-static int rbuVfsUnlock(sqlite3_file *pFile, int eLock){
+static int rbuVfsUnlock(tdsqlite3_file *pFile, int eLock){
rbu_file *p = (rbu_file *)pFile;
return p->pReal->pMethods->xUnlock(p->pReal, eLock);
}
@@ -173559,7 +202181,7 @@ static int rbuVfsUnlock(sqlite3_file *pFile, int eLock){
/*
** Check if another file-handle holds a RESERVED lock on an rbuVfs-file.
*/
-static int rbuVfsCheckReservedLock(sqlite3_file *pFile, int *pResOut){
+static int rbuVfsCheckReservedLock(tdsqlite3_file *pFile, int *pResOut){
rbu_file *p = (rbu_file *)pFile;
return p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
}
@@ -173567,16 +202189,16 @@ static int rbuVfsCheckReservedLock(sqlite3_file *pFile, int *pResOut){
/*
** File control method. For custom operations on an rbuVfs-file.
*/
-static int rbuVfsFileControl(sqlite3_file *pFile, int op, void *pArg){
+static int rbuVfsFileControl(tdsqlite3_file *pFile, int op, void *pArg){
rbu_file *p = (rbu_file *)pFile;
- int (*xControl)(sqlite3_file*,int,void*) = p->pReal->pMethods->xFileControl;
+ int (*xControl)(tdsqlite3_file*,int,void*) = p->pReal->pMethods->xFileControl;
int rc;
assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB)
|| p->openFlags & (SQLITE_OPEN_TRANSIENT_DB|SQLITE_OPEN_TEMP_JOURNAL)
);
if( op==SQLITE_FCNTL_RBU ){
- sqlite3rbu *pRbu = (sqlite3rbu*)pArg;
+ tdsqlite3rbu *pRbu = (tdsqlite3rbu*)pArg;
/* First try to find another RBU vfs lower down in the vfs stack. If
** one is found, this vfs will operate in pass-through mode. The lower
@@ -173590,10 +202212,11 @@ static int rbuVfsFileControl(sqlite3_file *pFile, int op, void *pArg){
rc = xControl(p->pReal, SQLITE_FCNTL_ZIPVFS, &dummy);
if( rc==SQLITE_OK ){
rc = SQLITE_ERROR;
- pRbu->zErrmsg = sqlite3_mprintf("rbu/zipvfs setup error");
+ pRbu->zErrmsg = tdsqlite3_mprintf("rbu/zipvfs setup error");
}else if( rc==SQLITE_NOTFOUND ){
pRbu->pTargetFd = p;
p->pRbu = pRbu;
+ rbuMainlistAdd(p);
if( p->pWalFd ) p->pWalFd->pRbu = pRbu;
rc = SQLITE_OK;
}
@@ -173601,7 +202224,7 @@ static int rbuVfsFileControl(sqlite3_file *pFile, int op, void *pArg){
return rc;
}
else if( op==SQLITE_FCNTL_RBUCNT ){
- sqlite3rbu *pRbu = (sqlite3rbu*)pArg;
+ tdsqlite3rbu *pRbu = (tdsqlite3rbu*)pArg;
pRbu->nRbu++;
pRbu->pRbuFd = p;
p->bNolock = 1;
@@ -173611,7 +202234,7 @@ static int rbuVfsFileControl(sqlite3_file *pFile, int op, void *pArg){
if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){
rbu_vfs *pRbuVfs = p->pRbuVfs;
char *zIn = *(char**)pArg;
- char *zOut = sqlite3_mprintf("rbu(%s)/%z", pRbuVfs->base.zName, zIn);
+ char *zOut = tdsqlite3_mprintf("rbu(%s)/%z", pRbuVfs->base.zName, zIn);
*(char**)pArg = zOut;
if( zOut==0 ) rc = SQLITE_NOMEM;
}
@@ -173622,7 +202245,7 @@ static int rbuVfsFileControl(sqlite3_file *pFile, int op, void *pArg){
/*
** Return the sector-size in bytes for an rbuVfs-file.
*/
-static int rbuVfsSectorSize(sqlite3_file *pFile){
+static int rbuVfsSectorSize(tdsqlite3_file *pFile){
rbu_file *p = (rbu_file *)pFile;
return p->pReal->pMethods->xSectorSize(p->pReal);
}
@@ -173630,7 +202253,7 @@ static int rbuVfsSectorSize(sqlite3_file *pFile){
/*
** Return the device characteristic flags supported by an rbuVfs-file.
*/
-static int rbuVfsDeviceCharacteristics(sqlite3_file *pFile){
+static int rbuVfsDeviceCharacteristics(tdsqlite3_file *pFile){
rbu_file *p = (rbu_file *)pFile;
return p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
}
@@ -173638,9 +202261,9 @@ static int rbuVfsDeviceCharacteristics(sqlite3_file *pFile){
/*
** Take or release a shared-memory lock.
*/
-static int rbuVfsShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
+static int rbuVfsShmLock(tdsqlite3_file *pFile, int ofst, int n, int flags){
rbu_file *p = (rbu_file*)pFile;
- sqlite3rbu *pRbu = p->pRbu;
+ tdsqlite3rbu *pRbu = p->pRbu;
int rc = SQLITE_OK;
#ifdef SQLITE_AMALGAMATION
@@ -173656,10 +202279,7 @@ static int rbuVfsShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
if( ofst==WAL_LOCK_CKPT && n==1 ) rc = SQLITE_BUSY;
}else{
int bCapture = 0;
- if( n==1 && (flags & SQLITE_SHM_EXCLUSIVE)
- && pRbu && pRbu->eStage==RBU_STAGE_CAPTURE
- && (ofst==WAL_LOCK_WRITE || ofst==WAL_LOCK_CKPT || ofst==WAL_LOCK_READ0)
- ){
+ if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){
bCapture = 1;
}
@@ -173678,7 +202298,7 @@ static int rbuVfsShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
** Obtain a pointer to a mapping of a single 32KiB page of the *-shm file.
*/
static int rbuVfsShmMap(
- sqlite3_file *pFile,
+ tdsqlite3_file *pFile,
int iRegion,
int szRegion,
int isWrite,
@@ -173692,21 +202312,25 @@ static int rbuVfsShmMap(
** rbu is in the RBU_STAGE_OAL state, use heap memory for *-shm space
** instead of a file on disk. */
assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) );
- if( eStage==RBU_STAGE_OAL || eStage==RBU_STAGE_MOVE ){
- if( iRegion<=p->nShm ){
- int nByte = (iRegion+1) * sizeof(char*);
- char **apNew = (char**)sqlite3_realloc64(p->apShm, nByte);
- if( apNew==0 ){
- rc = SQLITE_NOMEM;
- }else{
- memset(&apNew[p->nShm], 0, sizeof(char*) * (1 + iRegion - p->nShm));
- p->apShm = apNew;
- p->nShm = iRegion+1;
- }
+ if( eStage==RBU_STAGE_OAL ){
+ tdsqlite3_int64 nByte = (iRegion+1) * sizeof(char*);
+ char **apNew = (char**)tdsqlite3_realloc64(p->apShm, nByte);
+
+ /* This is an RBU connection that uses its own heap memory for the
+ ** pages of the *-shm file. Since no other process can have run
+ ** recovery, the connection must request *-shm pages in order
+ ** from start to finish. */
+ assert( iRegion==p->nShm );
+ if( apNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(&apNew[p->nShm], 0, sizeof(char*) * (1 + iRegion - p->nShm));
+ p->apShm = apNew;
+ p->nShm = iRegion+1;
}
- if( rc==SQLITE_OK && p->apShm[iRegion]==0 ){
- char *pNew = (char*)sqlite3_malloc64(szRegion);
+ if( rc==SQLITE_OK ){
+ char *pNew = (char*)tdsqlite3_malloc64(szRegion);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -173731,7 +202355,7 @@ static int rbuVfsShmMap(
/*
** Memory barrier.
*/
-static void rbuVfsShmBarrier(sqlite3_file *pFile){
+static void rbuVfsShmBarrier(tdsqlite3_file *pFile){
rbu_file *p = (rbu_file *)pFile;
p->pReal->pMethods->xShmBarrier(p->pReal);
}
@@ -173739,7 +202363,7 @@ static void rbuVfsShmBarrier(sqlite3_file *pFile){
/*
** The xShmUnmap method.
*/
-static int rbuVfsShmUnmap(sqlite3_file *pFile, int delFlag){
+static int rbuVfsShmUnmap(tdsqlite3_file *pFile, int delFlag){
rbu_file *p = (rbu_file*)pFile;
int rc = SQLITE_OK;
int eStage = (p->pRbu ? p->pRbu->eStage : 0);
@@ -173756,57 +202380,16 @@ static int rbuVfsShmUnmap(sqlite3_file *pFile, int delFlag){
}
/*
-** Given that zWal points to a buffer containing a wal file name passed to
-** either the xOpen() or xAccess() VFS method, return a pointer to the
-** file-handle opened by the same database connection on the corresponding
-** database file.
-*/
-static rbu_file *rbuFindMaindb(rbu_vfs *pRbuVfs, const char *zWal){
- rbu_file *pDb;
- sqlite3_mutex_enter(pRbuVfs->mutex);
- for(pDb=pRbuVfs->pMain; pDb && pDb->zWal!=zWal; pDb=pDb->pMainNext){}
- sqlite3_mutex_leave(pRbuVfs->mutex);
- return pDb;
-}
-
-/*
-** A main database named zName has just been opened. The following
-** function returns a pointer to a buffer owned by SQLite that contains
-** the name of the *-wal file this db connection will use. SQLite
-** happens to pass a pointer to this buffer when using xAccess()
-** or xOpen() to operate on the *-wal file.
-*/
-static const char *rbuMainToWal(const char *zName, int flags){
- int n = (int)strlen(zName);
- const char *z = &zName[n];
- if( flags & SQLITE_OPEN_URI ){
- int odd = 0;
- while( 1 ){
- if( z[0]==0 ){
- odd = 1 - odd;
- if( odd && z[1]==0 ) break;
- }
- z++;
- }
- z += 2;
- }else{
- while( *z==0 ) z++;
- }
- z += (n + 8 + 1);
- return z;
-}
-
-/*
** Open an rbu file handle.
*/
static int rbuVfsOpen(
- sqlite3_vfs *pVfs,
+ tdsqlite3_vfs *pVfs,
const char *zName,
- sqlite3_file *pFile,
+ tdsqlite3_file *pFile,
int flags,
int *pOutFlags
){
- static sqlite3_io_methods rbuvfs_io_methods = {
+ static tdsqlite3_io_methods rbuvfs_io_methods = {
2, /* iVersion */
rbuVfsClose, /* xClose */
rbuVfsRead, /* xRead */
@@ -173827,14 +202410,14 @@ static int rbuVfsOpen(
0, 0 /* xFetch, xUnfetch */
};
rbu_vfs *pRbuVfs = (rbu_vfs*)pVfs;
- sqlite3_vfs *pRealVfs = pRbuVfs->pRealVfs;
+ tdsqlite3_vfs *pRealVfs = pRbuVfs->pRealVfs;
rbu_file *pFd = (rbu_file *)pFile;
int rc = SQLITE_OK;
const char *zOpen = zName;
int oflags = flags;
memset(pFd, 0, sizeof(rbu_file));
- pFd->pReal = (sqlite3_file*)&pFd[1];
+ pFd->pReal = (tdsqlite3_file*)&pFd[1];
pFd->pRbuVfs = pRbuVfs;
pFd->openFlags = flags;
if( zName ){
@@ -173844,10 +202427,10 @@ static int rbuVfsOpen(
** the name of the *-wal file this db connection will use. SQLite
** happens to pass a pointer to this buffer when using xAccess()
** or xOpen() to operate on the *-wal file. */
- pFd->zWal = rbuMainToWal(zName, flags);
+ pFd->zWal = tdsqlite3_filename_wal(zName);
}
else if( flags & SQLITE_OPEN_WAL ){
- rbu_file *pDb = rbuFindMaindb(pRbuVfs, zName);
+ rbu_file *pDb = rbuFindMaindb(pRbuVfs, zName, 0);
if( pDb ){
if( pDb->pRbu && pDb->pRbu->eStage==RBU_STAGE_OAL ){
/* This call is to open a *-wal file. Intead, open the *-oal. This
@@ -173858,11 +202441,11 @@ static int rbuVfsOpen(
size_t nCopy;
char *zCopy;
if( rbuIsVacuum(pDb->pRbu) ){
- zBase = sqlite3_db_filename(pDb->pRbu->dbRbu, "main");
- zBase = rbuMainToWal(zBase, SQLITE_OPEN_URI);
+ zBase = tdsqlite3_db_filename(pDb->pRbu->dbRbu, "main");
+ zBase = tdsqlite3_filename_wal(zBase);
}
nCopy = strlen(zBase);
- zCopy = sqlite3_malloc64(nCopy+2);
+ zCopy = tdsqlite3_malloc64(nCopy+2);
if( zCopy ){
memcpy(zCopy, zBase, nCopy);
zCopy[nCopy-3] = 'o';
@@ -173877,10 +202460,12 @@ static int rbuVfsOpen(
pDb->pWalFd = pFd;
}
}
+ }else{
+ pFd->pRbu = pRbuVfs->pRbu;
}
if( oflags & SQLITE_OPEN_MAIN_DB
- && sqlite3_uri_boolean(zName, "rbu_memory", 0)
+ && tdsqlite3_uri_boolean(zName, "rbu_memory", 0)
){
assert( oflags & SQLITE_OPEN_MAIN_DB );
oflags = SQLITE_OPEN_TEMP_DB | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
@@ -173892,18 +202477,15 @@ static int rbuVfsOpen(
rc = pRealVfs->xOpen(pRealVfs, zOpen, pFd->pReal, oflags, pOutFlags);
}
if( pFd->pReal->pMethods ){
- /* The xOpen() operation has succeeded. Set the sqlite3_file.pMethods
+ /* The xOpen() operation has succeeded. Set the tdsqlite3_file.pMethods
** pointer and, if the file is a main database file, link it into the
** mutex protected linked list of all such files. */
pFile->pMethods = &rbuvfs_io_methods;
if( flags & SQLITE_OPEN_MAIN_DB ){
- sqlite3_mutex_enter(pRbuVfs->mutex);
- pFd->pMainNext = pRbuVfs->pMain;
- pRbuVfs->pMain = pFd;
- sqlite3_mutex_leave(pRbuVfs->mutex);
+ rbuMainlistAdd(pFd);
}
}else{
- sqlite3_free(pFd->zDel);
+ tdsqlite3_free(pFd->zDel);
}
return rc;
@@ -173912,8 +202494,8 @@ static int rbuVfsOpen(
/*
** Delete the file located at zPath.
*/
-static int rbuVfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
- sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
+static int rbuVfsDelete(tdsqlite3_vfs *pVfs, const char *zPath, int dirSync){
+ tdsqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
return pRealVfs->xDelete(pRealVfs, zPath, dirSync);
}
@@ -173922,13 +202504,13 @@ static int rbuVfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
** is available, or false otherwise.
*/
static int rbuVfsAccess(
- sqlite3_vfs *pVfs,
+ tdsqlite3_vfs *pVfs,
const char *zPath,
int flags,
int *pResOut
){
rbu_vfs *pRbuVfs = (rbu_vfs*)pVfs;
- sqlite3_vfs *pRealVfs = pRbuVfs->pRealVfs;
+ tdsqlite3_vfs *pRealVfs = pRbuVfs->pRealVfs;
int rc;
rc = pRealVfs->xAccess(pRealVfs, zPath, flags, pResOut);
@@ -173948,12 +202530,15 @@ static int rbuVfsAccess(
** file opened instead.
*/
if( rc==SQLITE_OK && flags==SQLITE_ACCESS_EXISTS ){
- rbu_file *pDb = rbuFindMaindb(pRbuVfs, zPath);
- if( pDb && pDb->pRbu && pDb->pRbu->eStage==RBU_STAGE_OAL ){
+ rbu_file *pDb = rbuFindMaindb(pRbuVfs, zPath, 1);
+ if( pDb && pDb->pRbu->eStage==RBU_STAGE_OAL ){
+ assert( pDb->pRbu );
if( *pResOut ){
rc = SQLITE_CANTOPEN;
}else{
- *pResOut = 1;
+ tdsqlite3_int64 sz = 0;
+ rc = rbuVfsFileSize(&pDb->base, &sz);
+ *pResOut = (sz>0);
}
}
}
@@ -173967,12 +202552,12 @@ static int rbuVfsAccess(
** of at least (DEVSYM_MAX_PATHNAME+1) bytes.
*/
static int rbuVfsFullPathname(
- sqlite3_vfs *pVfs,
+ tdsqlite3_vfs *pVfs,
const char *zPath,
int nOut,
char *zOut
){
- sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
+ tdsqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
return pRealVfs->xFullPathname(pRealVfs, zPath, nOut, zOut);
}
@@ -173980,8 +202565,8 @@ static int rbuVfsFullPathname(
/*
** Open the dynamic library located at zPath and return a handle.
*/
-static void *rbuVfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
- sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
+static void *rbuVfsDlOpen(tdsqlite3_vfs *pVfs, const char *zPath){
+ tdsqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
return pRealVfs->xDlOpen(pRealVfs, zPath);
}
@@ -173990,8 +202575,8 @@ static void *rbuVfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
** utf-8 string describing the most recent error encountered associated
** with dynamic libraries.
*/
-static void rbuVfsDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
- sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
+static void rbuVfsDlError(tdsqlite3_vfs *pVfs, int nByte, char *zErrMsg){
+ tdsqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
pRealVfs->xDlError(pRealVfs, nByte, zErrMsg);
}
@@ -173999,19 +202584,19 @@ static void rbuVfsDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
*/
static void (*rbuVfsDlSym(
- sqlite3_vfs *pVfs,
+ tdsqlite3_vfs *pVfs,
void *pArg,
const char *zSym
))(void){
- sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
+ tdsqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
return pRealVfs->xDlSym(pRealVfs, pArg, zSym);
}
/*
** Close the dynamic library handle pHandle.
*/
-static void rbuVfsDlClose(sqlite3_vfs *pVfs, void *pHandle){
- sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
+static void rbuVfsDlClose(tdsqlite3_vfs *pVfs, void *pHandle){
+ tdsqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
pRealVfs->xDlClose(pRealVfs, pHandle);
}
#endif /* SQLITE_OMIT_LOAD_EXTENSION */
@@ -174020,8 +202605,8 @@ static void rbuVfsDlClose(sqlite3_vfs *pVfs, void *pHandle){
** Populate the buffer pointed to by zBufOut with nByte bytes of
** random data.
*/
-static int rbuVfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
- sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
+static int rbuVfsRandomness(tdsqlite3_vfs *pVfs, int nByte, char *zBufOut){
+ tdsqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
return pRealVfs->xRandomness(pRealVfs, nByte, zBufOut);
}
@@ -174029,36 +202614,36 @@ static int rbuVfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
** Sleep for nMicro microseconds. Return the number of microseconds
** actually slept.
*/
-static int rbuVfsSleep(sqlite3_vfs *pVfs, int nMicro){
- sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
+static int rbuVfsSleep(tdsqlite3_vfs *pVfs, int nMicro){
+ tdsqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
return pRealVfs->xSleep(pRealVfs, nMicro);
}
/*
** Return the current time as a Julian Day number in *pTimeOut.
*/
-static int rbuVfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
- sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
+static int rbuVfsCurrentTime(tdsqlite3_vfs *pVfs, double *pTimeOut){
+ tdsqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs;
return pRealVfs->xCurrentTime(pRealVfs, pTimeOut);
}
/*
** No-op.
*/
-static int rbuVfsGetLastError(sqlite3_vfs *pVfs, int a, char *b){
+static int rbuVfsGetLastError(tdsqlite3_vfs *pVfs, int a, char *b){
return 0;
}
/*
** Deregister and destroy an RBU vfs created by an earlier call to
-** sqlite3rbu_create_vfs().
+** tdsqlite3rbu_create_vfs().
*/
-SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName){
- sqlite3_vfs *pVfs = sqlite3_vfs_find(zName);
+SQLITE_API void tdsqlite3rbu_destroy_vfs(const char *zName){
+ tdsqlite3_vfs *pVfs = tdsqlite3_vfs_find(zName);
if( pVfs && pVfs->xOpen==rbuVfsOpen ){
- sqlite3_mutex_free(((rbu_vfs*)pVfs)->mutex);
- sqlite3_vfs_unregister(pVfs);
- sqlite3_free(pVfs);
+ tdsqlite3_mutex_free(((rbu_vfs*)pVfs)->mutex);
+ tdsqlite3_vfs_unregister(pVfs);
+ tdsqlite3_free(pVfs);
}
}
@@ -174067,10 +202652,10 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName){
** via existing VFS zParent. The new object is registered as a non-default
** VFS with SQLite before returning.
*/
-SQLITE_API int sqlite3rbu_create_vfs(const char *zName, const char *zParent){
+SQLITE_API int tdsqlite3rbu_create_vfs(const char *zName, const char *zParent){
/* Template for VFS */
- static sqlite3_vfs vfs_template = {
+ static tdsqlite3_vfs vfs_template = {
1, /* iVersion */
0, /* szOsFile */
0, /* mxPathname */
@@ -174106,18 +202691,18 @@ SQLITE_API int sqlite3rbu_create_vfs(const char *zName, const char *zParent){
nName = strlen(zName);
nByte = sizeof(rbu_vfs) + nName + 1;
- pNew = (rbu_vfs*)sqlite3_malloc64(nByte);
+ pNew = (rbu_vfs*)tdsqlite3_malloc64(nByte);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
- sqlite3_vfs *pParent; /* Parent VFS */
+ tdsqlite3_vfs *pParent; /* Parent VFS */
memset(pNew, 0, nByte);
- pParent = sqlite3_vfs_find(zParent);
+ pParent = tdsqlite3_vfs_find(zParent);
if( pParent==0 ){
rc = SQLITE_NOTFOUND;
}else{
char *zSpace;
- memcpy(&pNew->base, &vfs_template, sizeof(sqlite3_vfs));
+ memcpy(&pNew->base, &vfs_template, sizeof(tdsqlite3_vfs));
pNew->base.mxPathname = pParent->mxPathname;
pNew->base.szOsFile = sizeof(rbu_file) + pParent->szOsFile;
pNew->pRealVfs = pParent;
@@ -174125,29 +202710,43 @@ SQLITE_API int sqlite3rbu_create_vfs(const char *zName, const char *zParent){
memcpy(zSpace, zName, nName);
/* Allocate the mutex and register the new VFS (not as the default) */
- pNew->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE);
+ pNew->mutex = tdsqlite3_mutex_alloc(SQLITE_MUTEX_RECURSIVE);
if( pNew->mutex==0 ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_vfs_register(&pNew->base, 0);
+ rc = tdsqlite3_vfs_register(&pNew->base, 0);
}
}
if( rc!=SQLITE_OK ){
- sqlite3_mutex_free(pNew->mutex);
- sqlite3_free(pNew);
+ tdsqlite3_mutex_free(pNew->mutex);
+ tdsqlite3_free(pNew);
}
}
return rc;
}
+/*
+** Configure the aggregate temp file size limit for this RBU handle.
+*/
+SQLITE_API tdsqlite3_int64 tdsqlite3rbu_temp_size_limit(tdsqlite3rbu *pRbu, tdsqlite3_int64 n){
+ if( n>=0 ){
+ pRbu->szTempLimit = n;
+ }
+ return pRbu->szTempLimit;
+}
+
+SQLITE_API tdsqlite3_int64 tdsqlite3rbu_temp_size(tdsqlite3rbu *pRbu){
+ return pRbu->szTemp;
+}
+
/**************************************************************************/
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_RBU) */
-/************** End of sqlite3rbu.c ******************************************/
+/************** End of tdsqlite3rbu.c ******************************************/
/************** Begin file dbstat.c ******************************************/
/*
** 2010 July 12
@@ -174163,9 +202762,9 @@ SQLITE_API int sqlite3rbu_create_vfs(const char *zName, const char *zParent){
**
** This file contains an implementation of the "dbstat" virtual table.
**
-** The dbstat virtual table is used to extract low-level formatting
+** The dbstat virtual table is used to extract low-level storage
** information from an SQLite database in order to implement the
-** "sqlite3_analyzer" utility. See the ../tool/spaceanal.tcl script
+** "tdsqlite3_analyzer" utility. See the ../tool/spaceanal.tcl script
** for an example implementation.
**
** Additional information is available on the "dbstat.html" page of the
@@ -174207,27 +202806,30 @@ SQLITE_API int sqlite3rbu_create_vfs(const char *zName, const char *zParent){
**
** '/1c2/000/' // Left-most child of 451st child of root
*/
-#define VTAB_SCHEMA \
- "CREATE TABLE xx( " \
- " name TEXT, /* Name of table or index */" \
- " path TEXT, /* Path to page from root */" \
- " pageno INTEGER, /* Page number */" \
- " pagetype TEXT, /* 'internal', 'leaf' or 'overflow' */" \
- " ncell INTEGER, /* Cells on page (0 for overflow) */" \
- " payload INTEGER, /* Bytes of payload on this page */" \
- " unused INTEGER, /* Bytes of unused space on this page */" \
- " mx_payload INTEGER, /* Largest payload size of all cells */" \
- " pgoffset INTEGER, /* Offset of page in file */" \
- " pgsize INTEGER, /* Size of the page */" \
- " schema TEXT HIDDEN /* Database schema being analyzed */" \
- ");"
-
-
+static const char zDbstatSchema[] =
+ "CREATE TABLE x("
+ " name TEXT," /* 0 Name of table or index */
+ " path TEXT," /* 1 Path to page from root (NULL for agg) */
+ " pageno INTEGER," /* 2 Page number (page count for aggregates) */
+ " pagetype TEXT," /* 3 'internal', 'leaf', 'overflow', or NULL */
+ " ncell INTEGER," /* 4 Cells on page (0 for overflow) */
+ " payload INTEGER," /* 5 Bytes of payload on this page */
+ " unused INTEGER," /* 6 Bytes of unused space on this page */
+ " mx_payload INTEGER," /* 7 Largest payload size of all cells */
+ " pgoffset INTEGER," /* 8 Offset of page in file (NULL for agg) */
+ " pgsize INTEGER," /* 9 Size of the page (sum for aggregate) */
+ " schema TEXT HIDDEN," /* 10 Database schema being analyzed */
+ " aggregate BOOLEAN HIDDEN" /* 11 aggregate info for each table */
+ ")"
+;
+
+/* Forward reference to data structured used in this module */
typedef struct StatTable StatTable;
typedef struct StatCursor StatCursor;
typedef struct StatPage StatPage;
typedef struct StatCell StatCell;
+/* Size information for a single cell within a btree page */
struct StatCell {
int nLocal; /* Bytes of local payload */
u32 iChildPg; /* Child node (or 0 if this is a leaf) */
@@ -174237,10 +202839,11 @@ struct StatCell {
int iOvfl; /* Iterates through aOvfl[] */
};
+/* Size information for a single btree page */
struct StatPage {
- u32 iPgno;
- DbPage *pPg;
- int iCell;
+ u32 iPgno; /* Page number */
+ DbPage *pPg; /* Page content */
+ int iCell; /* Current cell */
char *zPath; /* Path to this page */
@@ -174250,34 +202853,38 @@ struct StatPage {
int nUnused; /* Number of unused bytes on page */
StatCell *aCell; /* Array of parsed cells */
u32 iRightChildPg; /* Right-child page number (or 0) */
- int nMxPayload; /* Largest payload of any cell on this page */
+ int nMxPayload; /* Largest payload of any cell on the page */
};
+/* The cursor for scanning the dbstat virtual table */
struct StatCursor {
- sqlite3_vtab_cursor base;
- sqlite3_stmt *pStmt; /* Iterates through set of root pages */
- int isEof; /* After pStmt has returned SQLITE_DONE */
+ tdsqlite3_vtab_cursor base; /* base class. MUST BE FIRST! */
+ tdsqlite3_stmt *pStmt; /* Iterates through set of root pages */
+ u8 isEof; /* After pStmt has returned SQLITE_DONE */
+ u8 isAgg; /* Aggregate results for each table */
int iDb; /* Schema used for this query */
- StatPage aPage[32];
+ StatPage aPage[32]; /* Pages in path to current page */
int iPage; /* Current entry in aPage[] */
/* Values to return. */
+ u32 iPageno; /* Value of 'pageno' column */
char *zName; /* Value of 'name' column */
char *zPath; /* Value of 'path' column */
- u32 iPageno; /* Value of 'pageno' column */
char *zPagetype; /* Value of 'pagetype' column */
+ int nPage; /* Number of pages in current btree */
int nCell; /* Value of 'ncell' column */
- int nPayload; /* Value of 'payload' column */
- int nUnused; /* Value of 'unused' column */
int nMxPayload; /* Value of 'mx_payload' column */
+ i64 nUnused; /* Value of 'unused' column */
+ i64 nPayload; /* Value of 'payload' column */
i64 iOffset; /* Value of 'pgOffset' column */
- int szPage; /* Value of 'pgSize' column */
+ i64 szPage; /* Value of 'pgSize' column */
};
+/* An instance of the DBSTAT virtual table */
struct StatTable {
- sqlite3_vtab base;
- sqlite3 *db;
+ tdsqlite3_vtab base; /* base class. MUST BE FIRST! */
+ tdsqlite3 *db; /* Database connection that owns this vtab */
int iDb; /* Index of database to analyze */
};
@@ -174286,13 +202893,13 @@ struct StatTable {
#endif
/*
-** Connect to or create a statvfs virtual table.
+** Connect to or create a new DBSTAT virtual table.
*/
static int statConnect(
- sqlite3 *db,
+ tdsqlite3 *db,
void *pAux,
int argc, const char *const*argv,
- sqlite3_vtab **ppVtab,
+ tdsqlite3_vtab **ppVtab,
char **pzErr
){
StatTable *pTab = 0;
@@ -174301,18 +202908,19 @@ static int statConnect(
if( argc>=4 ){
Token nm;
- sqlite3TokenInit(&nm, (char*)argv[3]);
- iDb = sqlite3FindDb(db, &nm);
+ tdsqlite3TokenInit(&nm, (char*)argv[3]);
+ iDb = tdsqlite3FindDb(db, &nm);
if( iDb<0 ){
- *pzErr = sqlite3_mprintf("no such database: %s", argv[3]);
+ *pzErr = tdsqlite3_mprintf("no such database: %s", argv[3]);
return SQLITE_ERROR;
}
}else{
iDb = 0;
}
- rc = sqlite3_declare_vtab(db, VTAB_SCHEMA);
+ tdsqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
+ rc = tdsqlite3_declare_vtab(db, zDbstatSchema);
if( rc==SQLITE_OK ){
- pTab = (StatTable *)sqlite3_malloc64(sizeof(StatTable));
+ pTab = (StatTable *)tdsqlite3_malloc64(sizeof(StatTable));
if( pTab==0 ) rc = SQLITE_NOMEM_BKPT;
}
@@ -174323,29 +202931,33 @@ static int statConnect(
pTab->iDb = iDb;
}
- *ppVtab = (sqlite3_vtab*)pTab;
+ *ppVtab = (tdsqlite3_vtab*)pTab;
return rc;
}
/*
-** Disconnect from or destroy a statvfs virtual table.
+** Disconnect from or destroy the DBSTAT virtual table.
*/
-static int statDisconnect(sqlite3_vtab *pVtab){
- sqlite3_free(pVtab);
+static int statDisconnect(tdsqlite3_vtab *pVtab){
+ tdsqlite3_free(pVtab);
return SQLITE_OK;
}
/*
-** There is no "best-index". This virtual table always does a linear
-** scan. However, a schema=? constraint should cause this table to
-** operate on a different database schema, so check for it.
+** Compute the best query strategy and return the result in idxNum.
**
-** idxNum is normally 0, but will be 1 if a schema=? constraint exists.
+** idxNum-Bit Meaning
+** ---------- ----------------------------------------------
+** 0x01 There is a schema=? term in the WHERE clause
+** 0x02 There is a name=? term in the WHERE clause
+** 0x04 There is an aggregate=? term in the WHERE clause
+** 0x08 Output should be ordered by name and path
*/
-static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+static int statBestIndex(tdsqlite3_vtab *tab, tdsqlite3_index_info *pIdxInfo){
int i;
-
- pIdxInfo->estimatedCost = 1.0e6; /* Initial cost estimate */
+ int iSchema = -1;
+ int iName = -1;
+ int iAgg = -1;
/* Look for a valid schema=? constraint. If found, change the idxNum to
** 1 and request the value of that constraint be sent to xFilter. And
@@ -174353,16 +202965,40 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
** used.
*/
for(i=0; i<pIdxInfo->nConstraint; i++){
- if( pIdxInfo->aConstraint[i].usable==0 ) continue;
if( pIdxInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
- if( pIdxInfo->aConstraint[i].iColumn!=10 ) continue;
- pIdxInfo->idxNum = 1;
- pIdxInfo->estimatedCost = 1.0;
- pIdxInfo->aConstraintUsage[i].argvIndex = 1;
- pIdxInfo->aConstraintUsage[i].omit = 1;
- break;
+ if( pIdxInfo->aConstraint[i].usable==0 ){
+ /* Force DBSTAT table should always be the right-most table in a join */
+ return SQLITE_CONSTRAINT;
+ }
+ switch( pIdxInfo->aConstraint[i].iColumn ){
+ case 0: { /* name */
+ iName = i;
+ break;
+ }
+ case 10: { /* schema */
+ iSchema = i;
+ break;
+ }
+ case 11: { /* aggregate */
+ iAgg = i;
+ break;
+ }
+ }
}
-
+ i = 0;
+ if( iSchema>=0 ){
+ pIdxInfo->aConstraintUsage[iSchema].argvIndex = ++i;
+ pIdxInfo->idxNum |= 0x01;
+ }
+ if( iName>=0 ){
+ pIdxInfo->aConstraintUsage[iName].argvIndex = ++i;
+ pIdxInfo->idxNum |= 0x02;
+ }
+ if( iAgg>=0 ){
+ pIdxInfo->aConstraintUsage[iAgg].argvIndex = ++i;
+ pIdxInfo->idxNum |= 0x04;
+ }
+ pIdxInfo->estimatedCost = 1.0;
/* Records are always returned in ascending order of (name, path).
** If this will satisfy the client, set the orderByConsumed flag so that
@@ -174380,19 +203016,20 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
)
){
pIdxInfo->orderByConsumed = 1;
+ pIdxInfo->idxNum |= 0x08;
}
return SQLITE_OK;
}
/*
-** Open a new statvfs cursor.
+** Open a new DBSTAT cursor.
*/
-static int statOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+static int statOpen(tdsqlite3_vtab *pVTab, tdsqlite3_vtab_cursor **ppCursor){
StatTable *pTab = (StatTable *)pVTab;
StatCursor *pCsr;
- pCsr = (StatCursor *)sqlite3_malloc64(sizeof(StatCursor));
+ pCsr = (StatCursor *)tdsqlite3_malloc64(sizeof(StatCursor));
if( pCsr==0 ){
return SQLITE_NOMEM_BKPT;
}else{
@@ -174401,51 +203038,71 @@ static int statOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
pCsr->iDb = pTab->iDb;
}
- *ppCursor = (sqlite3_vtab_cursor *)pCsr;
+ *ppCursor = (tdsqlite3_vtab_cursor *)pCsr;
return SQLITE_OK;
}
-static void statClearPage(StatPage *p){
+static void statClearCells(StatPage *p){
int i;
if( p->aCell ){
for(i=0; i<p->nCell; i++){
- sqlite3_free(p->aCell[i].aOvfl);
+ tdsqlite3_free(p->aCell[i].aOvfl);
}
- sqlite3_free(p->aCell);
+ tdsqlite3_free(p->aCell);
}
- sqlite3PagerUnref(p->pPg);
- sqlite3_free(p->zPath);
+ p->nCell = 0;
+ p->aCell = 0;
+}
+
+static void statClearPage(StatPage *p){
+ statClearCells(p);
+ tdsqlite3PagerUnref(p->pPg);
+ tdsqlite3_free(p->zPath);
memset(p, 0, sizeof(StatPage));
}
static void statResetCsr(StatCursor *pCsr){
int i;
- sqlite3_reset(pCsr->pStmt);
+ tdsqlite3_reset(pCsr->pStmt);
for(i=0; i<ArraySize(pCsr->aPage); i++){
statClearPage(&pCsr->aPage[i]);
}
pCsr->iPage = 0;
- sqlite3_free(pCsr->zPath);
+ tdsqlite3_free(pCsr->zPath);
pCsr->zPath = 0;
pCsr->isEof = 0;
}
+/* Resize the space-used counters inside of the cursor */
+static void statResetCounts(StatCursor *pCsr){
+ pCsr->nCell = 0;
+ pCsr->nMxPayload = 0;
+ pCsr->nUnused = 0;
+ pCsr->nPayload = 0;
+ pCsr->szPage = 0;
+ pCsr->nPage = 0;
+}
+
/*
-** Close a statvfs cursor.
+** Close a DBSTAT cursor.
*/
-static int statClose(sqlite3_vtab_cursor *pCursor){
+static int statClose(tdsqlite3_vtab_cursor *pCursor){
StatCursor *pCsr = (StatCursor *)pCursor;
statResetCsr(pCsr);
- sqlite3_finalize(pCsr->pStmt);
- sqlite3_free(pCsr);
+ tdsqlite3_finalize(pCsr->pStmt);
+ tdsqlite3_free(pCsr);
return SQLITE_OK;
}
-static void getLocalPayload(
+/*
+** For a single cell on a btree page, compute the number of bytes of
+** content (payload) stored on that page. That is to say, compute the
+** number of bytes of content not found on overflow pages.
+*/
+static int getLocalPayload(
int nUsable, /* Usable bytes per page */
u8 flags, /* Page flags */
- int nTotal, /* Total record (payload) size */
- int *pnLocal /* OUT: Bytes stored locally */
+ int nTotal /* Total record (payload) size */
){
int nLocal;
int nMinLocal;
@@ -174461,9 +203118,12 @@ static void getLocalPayload(
nLocal = nMinLocal + (nTotal - nMinLocal) % (nUsable - 4);
if( nLocal>nMaxLocal ) nLocal = nMinLocal;
- *pnLocal = nLocal;
+ return nLocal;
}
+/* Populate the StatPage object with information about the all
+** cells found on the page currently under analysis.
+*/
static int statDecodePage(Btree *pBt, StatPage *p){
int nUnused;
int iOff;
@@ -174471,35 +203131,46 @@ static int statDecodePage(Btree *pBt, StatPage *p){
int isLeaf;
int szPage;
- u8 *aData = sqlite3PagerGetData(p->pPg);
+ u8 *aData = tdsqlite3PagerGetData(p->pPg);
u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0];
p->flags = aHdr[0];
+ if( p->flags==0x0A || p->flags==0x0D ){
+ isLeaf = 1;
+ nHdr = 8;
+ }else if( p->flags==0x05 || p->flags==0x02 ){
+ isLeaf = 0;
+ nHdr = 12;
+ }else{
+ goto statPageIsCorrupt;
+ }
+ if( p->iPgno==1 ) nHdr += 100;
p->nCell = get2byte(&aHdr[3]);
p->nMxPayload = 0;
-
- isLeaf = (p->flags==0x0A || p->flags==0x0D);
- nHdr = 12 - isLeaf*4 + (p->iPgno==1)*100;
+ szPage = tdsqlite3BtreeGetPageSize(pBt);
nUnused = get2byte(&aHdr[5]) - nHdr - 2*p->nCell;
nUnused += (int)aHdr[7];
iOff = get2byte(&aHdr[1]);
while( iOff ){
+ int iNext;
+ if( iOff>=szPage ) goto statPageIsCorrupt;
nUnused += get2byte(&aData[iOff+2]);
- iOff = get2byte(&aData[iOff]);
+ iNext = get2byte(&aData[iOff]);
+ if( iNext<iOff+4 && iNext>0 ) goto statPageIsCorrupt;
+ iOff = iNext;
}
p->nUnused = nUnused;
- p->iRightChildPg = isLeaf ? 0 : sqlite3Get4byte(&aHdr[8]);
- szPage = sqlite3BtreeGetPageSize(pBt);
+ p->iRightChildPg = isLeaf ? 0 : tdsqlite3Get4byte(&aHdr[8]);
if( p->nCell ){
int i; /* Used to iterate through cells */
int nUsable; /* Usable bytes per page */
- sqlite3BtreeEnter(pBt);
- nUsable = szPage - sqlite3BtreeGetReserveNoMutex(pBt);
- sqlite3BtreeLeave(pBt);
- p->aCell = sqlite3_malloc64((p->nCell+1) * sizeof(StatCell));
+ tdsqlite3BtreeEnter(pBt);
+ nUsable = szPage - tdsqlite3BtreeGetReserveNoMutex(pBt);
+ tdsqlite3BtreeLeave(pBt);
+ p->aCell = tdsqlite3_malloc64((p->nCell+1) * sizeof(StatCell));
if( p->aCell==0 ) return SQLITE_NOMEM_BKPT;
memset(p->aCell, 0, (p->nCell+1) * sizeof(StatCell));
@@ -174507,8 +203178,9 @@ static int statDecodePage(Btree *pBt, StatPage *p){
StatCell *pCell = &p->aCell[i];
iOff = get2byte(&aData[nHdr+i*2]);
+ if( iOff<nHdr || iOff>=szPage ) goto statPageIsCorrupt;
if( !isLeaf ){
- pCell->iChildPg = sqlite3Get4byte(&aData[iOff]);
+ pCell->iChildPg = tdsqlite3Get4byte(&aData[iOff]);
iOff += 4;
}
if( p->flags==0x05 ){
@@ -174519,33 +203191,34 @@ static int statDecodePage(Btree *pBt, StatPage *p){
iOff += getVarint32(&aData[iOff], nPayload);
if( p->flags==0x0D ){
u64 dummy;
- iOff += sqlite3GetVarint(&aData[iOff], &dummy);
+ iOff += tdsqlite3GetVarint(&aData[iOff], &dummy);
}
if( nPayload>(u32)p->nMxPayload ) p->nMxPayload = nPayload;
- getLocalPayload(nUsable, p->flags, nPayload, &nLocal);
+ nLocal = getLocalPayload(nUsable, p->flags, nPayload);
+ if( nLocal<0 ) goto statPageIsCorrupt;
pCell->nLocal = nLocal;
- assert( nLocal>=0 );
assert( nPayload>=(u32)nLocal );
assert( nLocal<=(nUsable-35) );
if( nPayload>(u32)nLocal ){
int j;
int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4);
+ if( iOff+nLocal>nUsable ) goto statPageIsCorrupt;
pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4);
pCell->nOvfl = nOvfl;
- pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl);
+ pCell->aOvfl = tdsqlite3_malloc64(sizeof(u32)*nOvfl);
if( pCell->aOvfl==0 ) return SQLITE_NOMEM_BKPT;
- pCell->aOvfl[0] = sqlite3Get4byte(&aData[iOff+nLocal]);
+ pCell->aOvfl[0] = tdsqlite3Get4byte(&aData[iOff+nLocal]);
for(j=1; j<nOvfl; j++){
int rc;
u32 iPrev = pCell->aOvfl[j-1];
DbPage *pPg = 0;
- rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPrev, &pPg, 0);
+ rc = tdsqlite3PagerGet(tdsqlite3BtreePager(pBt), iPrev, &pPg, 0);
if( rc!=SQLITE_OK ){
assert( pPg==0 );
return rc;
}
- pCell->aOvfl[j] = sqlite3Get4byte(sqlite3PagerGetData(pPg));
- sqlite3PagerUnref(pPg);
+ pCell->aOvfl[j] = tdsqlite3Get4byte(tdsqlite3PagerGetData(pPg));
+ tdsqlite3PagerUnref(pPg);
}
}
}
@@ -174553,6 +203226,11 @@ static int statDecodePage(Btree *pBt, StatPage *p){
}
return SQLITE_OK;
+
+statPageIsCorrupt:
+ p->flags = 0;
+ statClearCells(p);
+ return SQLITE_OK;
}
/*
@@ -174560,94 +203238,101 @@ static int statDecodePage(Btree *pBt, StatPage *p){
** the current value of pCsr->iPageno.
*/
static void statSizeAndOffset(StatCursor *pCsr){
- StatTable *pTab = (StatTable *)((sqlite3_vtab_cursor *)pCsr)->pVtab;
+ StatTable *pTab = (StatTable *)((tdsqlite3_vtab_cursor *)pCsr)->pVtab;
Btree *pBt = pTab->db->aDb[pTab->iDb].pBt;
- Pager *pPager = sqlite3BtreePager(pBt);
- sqlite3_file *fd;
- sqlite3_int64 x[2];
+ Pager *pPager = tdsqlite3BtreePager(pBt);
+ tdsqlite3_file *fd;
+ tdsqlite3_int64 x[2];
- /* The default page size and offset */
- pCsr->szPage = sqlite3BtreeGetPageSize(pBt);
- pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1);
-
- /* If connected to a ZIPVFS backend, override the page size and
- ** offset with actual values obtained from ZIPVFS.
+ /* If connected to a ZIPVFS backend, find the page size and
+ ** offset from ZIPVFS.
*/
- fd = sqlite3PagerFile(pPager);
+ fd = tdsqlite3PagerFile(pPager);
x[0] = pCsr->iPageno;
- if( fd->pMethods!=0 && sqlite3OsFileControl(fd, 230440, &x)==SQLITE_OK ){
+ if( tdsqlite3OsFileControl(fd, 230440, &x)==SQLITE_OK ){
pCsr->iOffset = x[0];
- pCsr->szPage = (int)x[1];
+ pCsr->szPage += x[1];
+ }else{
+ /* Not ZIPVFS: The default page size and offset */
+ pCsr->szPage += tdsqlite3BtreeGetPageSize(pBt);
+ pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1);
}
}
/*
-** Move a statvfs cursor to the next entry in the file.
+** Move a DBSTAT cursor to the next entry. Normally, the next
+** entry will be the next page, but in aggregated mode (pCsr->isAgg!=0),
+** the next entry is the next btree.
*/
-static int statNext(sqlite3_vtab_cursor *pCursor){
+static int statNext(tdsqlite3_vtab_cursor *pCursor){
int rc;
int nPayload;
char *z;
StatCursor *pCsr = (StatCursor *)pCursor;
StatTable *pTab = (StatTable *)pCursor->pVtab;
Btree *pBt = pTab->db->aDb[pCsr->iDb].pBt;
- Pager *pPager = sqlite3BtreePager(pBt);
+ Pager *pPager = tdsqlite3BtreePager(pBt);
- sqlite3_free(pCsr->zPath);
+ tdsqlite3_free(pCsr->zPath);
pCsr->zPath = 0;
statNextRestart:
if( pCsr->aPage[0].pPg==0 ){
- rc = sqlite3_step(pCsr->pStmt);
+ /* Start measuring space on the next btree */
+ statResetCounts(pCsr);
+ rc = tdsqlite3_step(pCsr->pStmt);
if( rc==SQLITE_ROW ){
int nPage;
- u32 iRoot = (u32)sqlite3_column_int64(pCsr->pStmt, 1);
- sqlite3PagerPagecount(pPager, &nPage);
+ u32 iRoot = (u32)tdsqlite3_column_int64(pCsr->pStmt, 1);
+ tdsqlite3PagerPagecount(pPager, &nPage);
if( nPage==0 ){
pCsr->isEof = 1;
- return sqlite3_reset(pCsr->pStmt);
+ return tdsqlite3_reset(pCsr->pStmt);
}
- rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0);
+ rc = tdsqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0);
pCsr->aPage[0].iPgno = iRoot;
pCsr->aPage[0].iCell = 0;
- pCsr->aPage[0].zPath = z = sqlite3_mprintf("/");
+ if( !pCsr->isAgg ){
+ pCsr->aPage[0].zPath = z = tdsqlite3_mprintf("/");
+ if( z==0 ) rc = SQLITE_NOMEM_BKPT;
+ }
pCsr->iPage = 0;
- if( z==0 ) rc = SQLITE_NOMEM_BKPT;
+ pCsr->nPage = 1;
}else{
pCsr->isEof = 1;
- return sqlite3_reset(pCsr->pStmt);
+ return tdsqlite3_reset(pCsr->pStmt);
}
}else{
-
- /* Page p itself has already been visited. */
+ /* Continue analyzing the btree previously started */
StatPage *p = &pCsr->aPage[pCsr->iPage];
-
+ if( !pCsr->isAgg ) statResetCounts(pCsr);
while( p->iCell<p->nCell ){
StatCell *pCell = &p->aCell[p->iCell];
- if( pCell->iOvfl<pCell->nOvfl ){
- int nUsable;
- sqlite3BtreeEnter(pBt);
- nUsable = sqlite3BtreeGetPageSize(pBt) -
- sqlite3BtreeGetReserveNoMutex(pBt);
- sqlite3BtreeLeave(pBt);
- pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0);
- pCsr->iPageno = pCell->aOvfl[pCell->iOvfl];
- pCsr->zPagetype = "overflow";
- pCsr->nCell = 0;
- pCsr->nMxPayload = 0;
- pCsr->zPath = z = sqlite3_mprintf(
- "%s%.3x+%.6x", p->zPath, p->iCell, pCell->iOvfl
- );
+ while( pCell->iOvfl<pCell->nOvfl ){
+ int nUsable, iOvfl;
+ tdsqlite3BtreeEnter(pBt);
+ nUsable = tdsqlite3BtreeGetPageSize(pBt) -
+ tdsqlite3BtreeGetReserveNoMutex(pBt);
+ tdsqlite3BtreeLeave(pBt);
+ pCsr->nPage++;
+ statSizeAndOffset(pCsr);
if( pCell->iOvfl<pCell->nOvfl-1 ){
- pCsr->nUnused = 0;
- pCsr->nPayload = nUsable - 4;
+ pCsr->nPayload += nUsable - 4;
}else{
- pCsr->nPayload = pCell->nLastOvfl;
- pCsr->nUnused = nUsable - 4 - pCsr->nPayload;
+ pCsr->nPayload += pCell->nLastOvfl;
+ pCsr->nUnused += nUsable - 4 - pCell->nLastOvfl;
}
+ iOvfl = pCell->iOvfl;
pCell->iOvfl++;
- statSizeAndOffset(pCsr);
- return z==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK;
+ if( !pCsr->isAgg ){
+ pCsr->zName = (char *)tdsqlite3_column_text(pCsr->pStmt, 0);
+ pCsr->iPageno = pCell->aOvfl[iOvfl];
+ pCsr->zPagetype = "overflow";
+ pCsr->zPath = z = tdsqlite3_mprintf(
+ "%s%.3x+%.6x", p->zPath, p->iCell, iOvfl
+ );
+ return z==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK;
+ }
}
if( p->iRightChildPg ) break;
p->iCell++;
@@ -174655,11 +203340,20 @@ statNextRestart:
if( !p->iRightChildPg || p->iCell>p->nCell ){
statClearPage(p);
- if( pCsr->iPage==0 ) return statNext(pCursor);
- pCsr->iPage--;
+ if( pCsr->iPage>0 ){
+ pCsr->iPage--;
+ }else if( pCsr->isAgg ){
+ /* label-statNext-done: When computing aggregate space usage over
+ ** an entire btree, this is the exit point from this function */
+ return SQLITE_OK;
+ }
goto statNextRestart; /* Tail recursion */
}
pCsr->iPage++;
+ if( pCsr->iPage>=ArraySize(pCsr->aPage) ){
+ statResetCsr(pCsr);
+ return SQLITE_CORRUPT_BKPT;
+ }
assert( p==&pCsr->aPage[pCsr->iPage-1] );
if( p->iCell==p->nCell ){
@@ -174667,11 +203361,14 @@ statNextRestart:
}else{
p[1].iPgno = p->aCell[p->iCell].iChildPg;
}
- rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0);
+ rc = tdsqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0);
+ pCsr->nPage++;
p[1].iCell = 0;
- p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell);
+ if( !pCsr->isAgg ){
+ p[1].zPath = z = tdsqlite3_mprintf("%s%.3x/", p->zPath, p->iCell);
+ if( z==0 ) rc = SQLITE_NOMEM_BKPT;
+ }
p->iCell++;
- if( z==0 ) rc = SQLITE_NOMEM_BKPT;
}
@@ -174681,7 +203378,7 @@ statNextRestart:
if( rc==SQLITE_OK ){
int i;
StatPage *p = &pCsr->aPage[pCsr->iPage];
- pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0);
+ pCsr->zName = (char *)tdsqlite3_column_text(pCsr->pStmt, 0);
pCsr->iPageno = p->iPgno;
rc = statDecodePage(pBt, p);
@@ -174701,64 +203398,96 @@ statNextRestart:
pCsr->zPagetype = "corrupted";
break;
}
- pCsr->nCell = p->nCell;
- pCsr->nUnused = p->nUnused;
- pCsr->nMxPayload = p->nMxPayload;
- pCsr->zPath = z = sqlite3_mprintf("%s", p->zPath);
- if( z==0 ) rc = SQLITE_NOMEM_BKPT;
+ pCsr->nCell += p->nCell;
+ pCsr->nUnused += p->nUnused;
+ if( p->nMxPayload>pCsr->nMxPayload ) pCsr->nMxPayload = p->nMxPayload;
+ if( !pCsr->isAgg ){
+ pCsr->zPath = z = tdsqlite3_mprintf("%s", p->zPath);
+ if( z==0 ) rc = SQLITE_NOMEM_BKPT;
+ }
nPayload = 0;
for(i=0; i<p->nCell; i++){
nPayload += p->aCell[i].nLocal;
}
- pCsr->nPayload = nPayload;
+ pCsr->nPayload += nPayload;
+
+ /* If computing aggregate space usage by btree, continue with the
+ ** next page. The loop will exit via the return at label-statNext-done
+ */
+ if( pCsr->isAgg ) goto statNextRestart;
}
}
return rc;
}
-static int statEof(sqlite3_vtab_cursor *pCursor){
+static int statEof(tdsqlite3_vtab_cursor *pCursor){
StatCursor *pCsr = (StatCursor *)pCursor;
return pCsr->isEof;
}
+/* Initialize a cursor according to the query plan idxNum using the
+** arguments in argv[0]. See statBestIndex() for a description of the
+** meaning of the bits in idxNum.
+*/
static int statFilter(
- sqlite3_vtab_cursor *pCursor,
+ tdsqlite3_vtab_cursor *pCursor,
int idxNum, const char *idxStr,
- int argc, sqlite3_value **argv
+ int argc, tdsqlite3_value **argv
){
StatCursor *pCsr = (StatCursor *)pCursor;
StatTable *pTab = (StatTable*)(pCursor->pVtab);
- char *zSql;
- int rc = SQLITE_OK;
- char *zMaster;
+ tdsqlite3_str *pSql; /* Query of btrees to analyze */
+ char *zSql; /* String value of pSql */
+ int iArg = 0; /* Count of argv[] parameters used so far */
+ int rc = SQLITE_OK; /* Result of this operation */
+ const char *zName = 0; /* Only provide analysis of this table */
- if( idxNum==1 ){
- const char *zDbase = (const char*)sqlite3_value_text(argv[0]);
- pCsr->iDb = sqlite3FindDbName(pTab->db, zDbase);
+ statResetCsr(pCsr);
+ tdsqlite3_finalize(pCsr->pStmt);
+ pCsr->pStmt = 0;
+ if( idxNum & 0x01 ){
+ /* schema=? constraint is present. Get its value */
+ const char *zDbase = (const char*)tdsqlite3_value_text(argv[iArg++]);
+ pCsr->iDb = tdsqlite3FindDbName(pTab->db, zDbase);
if( pCsr->iDb<0 ){
- sqlite3_free(pCursor->pVtab->zErrMsg);
- pCursor->pVtab->zErrMsg = sqlite3_mprintf("no such schema: %s", zDbase);
- return pCursor->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM_BKPT;
+ pCsr->iDb = 0;
+ pCsr->isEof = 1;
+ return SQLITE_OK;
}
}else{
pCsr->iDb = pTab->iDb;
}
- statResetCsr(pCsr);
- sqlite3_finalize(pCsr->pStmt);
- pCsr->pStmt = 0;
- zMaster = pCsr->iDb==1 ? "sqlite_temp_master" : "sqlite_master";
- zSql = sqlite3_mprintf(
- "SELECT 'sqlite_master' AS name, 1 AS rootpage, 'table' AS type"
- " UNION ALL "
- "SELECT name, rootpage, type"
- " FROM \"%w\".%s WHERE rootpage!=0"
- " ORDER BY name", pTab->db->aDb[pCsr->iDb].zDbSName, zMaster);
+ if( idxNum & 0x02 ){
+ /* name=? constraint is present */
+ zName = (const char*)tdsqlite3_value_text(argv[iArg++]);
+ }
+ if( idxNum & 0x04 ){
+ /* aggregate=? constraint is present */
+ pCsr->isAgg = tdsqlite3_value_double(argv[iArg++])!=0.0;
+ }else{
+ pCsr->isAgg = 0;
+ }
+ pSql = tdsqlite3_str_new(pTab->db);
+ tdsqlite3_str_appendf(pSql,
+ "SELECT * FROM ("
+ "SELECT 'sqlite_master' AS name,1 AS rootpage,'table' AS type"
+ " UNION ALL "
+ "SELECT name,rootpage,type"
+ " FROM \"%w\".sqlite_master WHERE rootpage!=0)",
+ pTab->db->aDb[pCsr->iDb].zDbSName);
+ if( zName ){
+ tdsqlite3_str_appendf(pSql, "WHERE name=%Q", zName);
+ }
+ if( idxNum & 0x08 ){
+ tdsqlite3_str_appendf(pSql, " ORDER BY name");
+ }
+ zSql = tdsqlite3_str_finish(pSql);
if( zSql==0 ){
return SQLITE_NOMEM_BKPT;
}else{
- rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
- sqlite3_free(zSql);
+ rc = tdsqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pStmt, 0);
+ tdsqlite3_free(zSql);
}
if( rc==SQLITE_OK ){
@@ -174768,53 +203497,67 @@ static int statFilter(
}
static int statColumn(
- sqlite3_vtab_cursor *pCursor,
- sqlite3_context *ctx,
+ tdsqlite3_vtab_cursor *pCursor,
+ tdsqlite3_context *ctx,
int i
){
StatCursor *pCsr = (StatCursor *)pCursor;
switch( i ){
case 0: /* name */
- sqlite3_result_text(ctx, pCsr->zName, -1, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(ctx, pCsr->zName, -1, SQLITE_TRANSIENT);
break;
case 1: /* path */
- sqlite3_result_text(ctx, pCsr->zPath, -1, SQLITE_TRANSIENT);
+ if( !pCsr->isAgg ){
+ tdsqlite3_result_text(ctx, pCsr->zPath, -1, SQLITE_TRANSIENT);
+ }
break;
case 2: /* pageno */
- sqlite3_result_int64(ctx, pCsr->iPageno);
+ if( pCsr->isAgg ){
+ tdsqlite3_result_int64(ctx, pCsr->nPage);
+ }else{
+ tdsqlite3_result_int64(ctx, pCsr->iPageno);
+ }
break;
case 3: /* pagetype */
- sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC);
+ if( !pCsr->isAgg ){
+ tdsqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC);
+ }
break;
case 4: /* ncell */
- sqlite3_result_int(ctx, pCsr->nCell);
+ tdsqlite3_result_int(ctx, pCsr->nCell);
break;
case 5: /* payload */
- sqlite3_result_int(ctx, pCsr->nPayload);
+ tdsqlite3_result_int(ctx, pCsr->nPayload);
break;
case 6: /* unused */
- sqlite3_result_int(ctx, pCsr->nUnused);
+ tdsqlite3_result_int(ctx, pCsr->nUnused);
break;
case 7: /* mx_payload */
- sqlite3_result_int(ctx, pCsr->nMxPayload);
+ tdsqlite3_result_int(ctx, pCsr->nMxPayload);
break;
case 8: /* pgoffset */
- sqlite3_result_int64(ctx, pCsr->iOffset);
+ if( !pCsr->isAgg ){
+ tdsqlite3_result_int64(ctx, pCsr->iOffset);
+ }
break;
case 9: /* pgsize */
- sqlite3_result_int(ctx, pCsr->szPage);
+ tdsqlite3_result_int(ctx, pCsr->szPage);
break;
- default: { /* schema */
- sqlite3 *db = sqlite3_context_db_handle(ctx);
+ case 10: { /* schema */
+ tdsqlite3 *db = tdsqlite3_context_db_handle(ctx);
int iDb = pCsr->iDb;
- sqlite3_result_text(ctx, db->aDb[iDb].zDbSName, -1, SQLITE_STATIC);
+ tdsqlite3_result_text(ctx, db->aDb[iDb].zDbSName, -1, SQLITE_STATIC);
+ break;
+ }
+ default: { /* aggregate */
+ tdsqlite3_result_int(ctx, pCsr->isAgg);
break;
}
}
return SQLITE_OK;
}
-static int statRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+static int statRowid(tdsqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
StatCursor *pCsr = (StatCursor *)pCursor;
*pRowid = pCsr->iPageno;
return SQLITE_OK;
@@ -174823,8 +203566,8 @@ static int statRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
/*
** Invoke this routine to register the "dbstat" virtual table module
*/
-SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){
- static sqlite3_module dbstat_module = {
+SQLITE_PRIVATE int tdsqlite3DbstatRegister(tdsqlite3 *db){
+ static tdsqlite3_module dbstat_module = {
0, /* iVersion */
statConnect, /* xCreate */
statConnect, /* xConnect */
@@ -174845,18 +203588,441 @@ SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
};
- return sqlite3_create_module(db, "dbstat", &dbstat_module, 0);
+ return tdsqlite3_create_module(db, "dbstat", &dbstat_module, 0);
}
#elif defined(SQLITE_ENABLE_DBSTAT_VTAB)
-SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){ return SQLITE_OK; }
+SQLITE_PRIVATE int tdsqlite3DbstatRegister(tdsqlite3 *db){ return SQLITE_OK; }
#endif /* SQLITE_ENABLE_DBSTAT_VTAB */
/************** End of dbstat.c **********************************************/
-/************** Begin file sqlite3session.c **********************************/
+/************** Begin file dbpage.c ******************************************/
+/*
+** 2017-10-11
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file contains an implementation of the "sqlite_dbpage" virtual table.
+**
+** The sqlite_dbpage virtual table is used to read or write whole raw
+** pages of the database file. The pager interface is used so that
+** uncommitted changes and changes recorded in the WAL file are correctly
+** retrieved.
+**
+** Usage example:
+**
+** SELECT data FROM sqlite_dbpage('aux1') WHERE pgno=123;
+**
+** This is an eponymous virtual table so it does not need to be created before
+** use. The optional argument to the sqlite_dbpage() table name is the
+** schema for the database file that is to be read. The default schema is
+** "main".
+**
+** The data field of sqlite_dbpage table can be updated. The new
+** value must be a BLOB which is the correct page size, otherwise the
+** update fails. Rows may not be deleted or inserted.
+*/
+
+/* #include "sqliteInt.h" ** Requires access to internal data structures ** */
+#if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \
+ && !defined(SQLITE_OMIT_VIRTUALTABLE)
+
+typedef struct DbpageTable DbpageTable;
+typedef struct DbpageCursor DbpageCursor;
+
+struct DbpageCursor {
+ tdsqlite3_vtab_cursor base; /* Base class. Must be first */
+ int pgno; /* Current page number */
+ int mxPgno; /* Last page to visit on this scan */
+ Pager *pPager; /* Pager being read/written */
+ DbPage *pPage1; /* Page 1 of the database */
+ int iDb; /* Index of database to analyze */
+ int szPage; /* Size of each page in bytes */
+};
+
+struct DbpageTable {
+ tdsqlite3_vtab base; /* Base class. Must be first */
+ tdsqlite3 *db; /* The database */
+};
+
+/* Columns */
+#define DBPAGE_COLUMN_PGNO 0
+#define DBPAGE_COLUMN_DATA 1
+#define DBPAGE_COLUMN_SCHEMA 2
+
+
+
+/*
+** Connect to or create a dbpagevfs virtual table.
+*/
+static int dbpageConnect(
+ tdsqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ tdsqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ DbpageTable *pTab = 0;
+ int rc = SQLITE_OK;
+
+ tdsqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
+ rc = tdsqlite3_declare_vtab(db,
+ "CREATE TABLE x(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)");
+ if( rc==SQLITE_OK ){
+ pTab = (DbpageTable *)tdsqlite3_malloc64(sizeof(DbpageTable));
+ if( pTab==0 ) rc = SQLITE_NOMEM_BKPT;
+ }
+
+ assert( rc==SQLITE_OK || pTab==0 );
+ if( rc==SQLITE_OK ){
+ memset(pTab, 0, sizeof(DbpageTable));
+ pTab->db = db;
+ }
+
+ *ppVtab = (tdsqlite3_vtab*)pTab;
+ return rc;
+}
+
+/*
+** Disconnect from or destroy a dbpagevfs virtual table.
+*/
+static int dbpageDisconnect(tdsqlite3_vtab *pVtab){
+ tdsqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** idxNum:
+**
+** 0 schema=main, full table scan
+** 1 schema=main, pgno=?1
+** 2 schema=?1, full table scan
+** 3 schema=?1, pgno=?2
+*/
+static int dbpageBestIndex(tdsqlite3_vtab *tab, tdsqlite3_index_info *pIdxInfo){
+ int i;
+ int iPlan = 0;
+
+ /* If there is a schema= constraint, it must be honored. Report a
+ ** ridiculously large estimated cost if the schema= constraint is
+ ** unavailable
+ */
+ for(i=0; i<pIdxInfo->nConstraint; i++){
+ struct tdsqlite3_index_constraint *p = &pIdxInfo->aConstraint[i];
+ if( p->iColumn!=DBPAGE_COLUMN_SCHEMA ) continue;
+ if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
+ if( !p->usable ){
+ /* No solution. */
+ return SQLITE_CONSTRAINT;
+ }
+ iPlan = 2;
+ pIdxInfo->aConstraintUsage[i].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[i].omit = 1;
+ break;
+ }
+
+ /* If we reach this point, it means that either there is no schema=
+ ** constraint (in which case we use the "main" schema) or else the
+ ** schema constraint was accepted. Lower the estimated cost accordingly
+ */
+ pIdxInfo->estimatedCost = 1.0e6;
+
+ /* Check for constraints against pgno */
+ for(i=0; i<pIdxInfo->nConstraint; i++){
+ struct tdsqlite3_index_constraint *p = &pIdxInfo->aConstraint[i];
+ if( p->usable && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ pIdxInfo->estimatedRows = 1;
+ pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE;
+ pIdxInfo->estimatedCost = 1.0;
+ pIdxInfo->aConstraintUsage[i].argvIndex = iPlan ? 2 : 1;
+ pIdxInfo->aConstraintUsage[i].omit = 1;
+ iPlan |= 1;
+ break;
+ }
+ }
+ pIdxInfo->idxNum = iPlan;
+
+ if( pIdxInfo->nOrderBy>=1
+ && pIdxInfo->aOrderBy[0].iColumn<=0
+ && pIdxInfo->aOrderBy[0].desc==0
+ ){
+ pIdxInfo->orderByConsumed = 1;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Open a new dbpagevfs cursor.
+*/
+static int dbpageOpen(tdsqlite3_vtab *pVTab, tdsqlite3_vtab_cursor **ppCursor){
+ DbpageCursor *pCsr;
+
+ pCsr = (DbpageCursor *)tdsqlite3_malloc64(sizeof(DbpageCursor));
+ if( pCsr==0 ){
+ return SQLITE_NOMEM_BKPT;
+ }else{
+ memset(pCsr, 0, sizeof(DbpageCursor));
+ pCsr->base.pVtab = pVTab;
+ pCsr->pgno = -1;
+ }
+
+ *ppCursor = (tdsqlite3_vtab_cursor *)pCsr;
+ return SQLITE_OK;
+}
+
+/*
+** Close a dbpagevfs cursor.
+*/
+static int dbpageClose(tdsqlite3_vtab_cursor *pCursor){
+ DbpageCursor *pCsr = (DbpageCursor *)pCursor;
+ if( pCsr->pPage1 ) tdsqlite3PagerUnrefPageOne(pCsr->pPage1);
+ tdsqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+/*
+** Move a dbpagevfs cursor to the next entry in the file.
+*/
+static int dbpageNext(tdsqlite3_vtab_cursor *pCursor){
+ int rc = SQLITE_OK;
+ DbpageCursor *pCsr = (DbpageCursor *)pCursor;
+ pCsr->pgno++;
+ return rc;
+}
+
+static int dbpageEof(tdsqlite3_vtab_cursor *pCursor){
+ DbpageCursor *pCsr = (DbpageCursor *)pCursor;
+ return pCsr->pgno > pCsr->mxPgno;
+}
+
+/*
+** idxNum:
+**
+** 0 schema=main, full table scan
+** 1 schema=main, pgno=?1
+** 2 schema=?1, full table scan
+** 3 schema=?1, pgno=?2
+**
+** idxStr is not used
+*/
+static int dbpageFilter(
+ tdsqlite3_vtab_cursor *pCursor,
+ int idxNum, const char *idxStr,
+ int argc, tdsqlite3_value **argv
+){
+ DbpageCursor *pCsr = (DbpageCursor *)pCursor;
+ DbpageTable *pTab = (DbpageTable *)pCursor->pVtab;
+ int rc;
+ tdsqlite3 *db = pTab->db;
+ Btree *pBt;
+
+ /* Default setting is no rows of result */
+ pCsr->pgno = 1;
+ pCsr->mxPgno = 0;
+
+ if( idxNum & 2 ){
+ const char *zSchema;
+ assert( argc>=1 );
+ zSchema = (const char*)tdsqlite3_value_text(argv[0]);
+ pCsr->iDb = tdsqlite3FindDbName(db, zSchema);
+ if( pCsr->iDb<0 ) return SQLITE_OK;
+ }else{
+ pCsr->iDb = 0;
+ }
+ pBt = db->aDb[pCsr->iDb].pBt;
+ if( pBt==0 ) return SQLITE_OK;
+ pCsr->pPager = tdsqlite3BtreePager(pBt);
+ pCsr->szPage = tdsqlite3BtreeGetPageSize(pBt);
+ pCsr->mxPgno = tdsqlite3BtreeLastPage(pBt);
+ if( idxNum & 1 ){
+ assert( argc>(idxNum>>1) );
+ pCsr->pgno = tdsqlite3_value_int(argv[idxNum>>1]);
+ if( pCsr->pgno<1 || pCsr->pgno>pCsr->mxPgno ){
+ pCsr->pgno = 1;
+ pCsr->mxPgno = 0;
+ }else{
+ pCsr->mxPgno = pCsr->pgno;
+ }
+ }else{
+ assert( pCsr->pgno==1 );
+ }
+ if( pCsr->pPage1 ) tdsqlite3PagerUnrefPageOne(pCsr->pPage1);
+ rc = tdsqlite3PagerGet(pCsr->pPager, 1, &pCsr->pPage1, 0);
+ return rc;
+}
+
+static int dbpageColumn(
+ tdsqlite3_vtab_cursor *pCursor,
+ tdsqlite3_context *ctx,
+ int i
+){
+ DbpageCursor *pCsr = (DbpageCursor *)pCursor;
+ int rc = SQLITE_OK;
+ switch( i ){
+ case 0: { /* pgno */
+ tdsqlite3_result_int(ctx, pCsr->pgno);
+ break;
+ }
+ case 1: { /* data */
+ DbPage *pDbPage = 0;
+ rc = tdsqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0);
+ if( rc==SQLITE_OK ){
+ tdsqlite3_result_blob(ctx, tdsqlite3PagerGetData(pDbPage), pCsr->szPage,
+ SQLITE_TRANSIENT);
+ }
+ tdsqlite3PagerUnref(pDbPage);
+ break;
+ }
+ default: { /* schema */
+ tdsqlite3 *db = tdsqlite3_context_db_handle(ctx);
+ tdsqlite3_result_text(ctx, db->aDb[pCsr->iDb].zDbSName, -1, SQLITE_STATIC);
+ break;
+ }
+ }
+ return SQLITE_OK;
+}
+
+static int dbpageRowid(tdsqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+ DbpageCursor *pCsr = (DbpageCursor *)pCursor;
+ *pRowid = pCsr->pgno;
+ return SQLITE_OK;
+}
+
+static int dbpageUpdate(
+ tdsqlite3_vtab *pVtab,
+ int argc,
+ tdsqlite3_value **argv,
+ sqlite_int64 *pRowid
+){
+ DbpageTable *pTab = (DbpageTable *)pVtab;
+ Pgno pgno;
+ DbPage *pDbPage = 0;
+ int rc = SQLITE_OK;
+ char *zErr = 0;
+ const char *zSchema;
+ int iDb;
+ Btree *pBt;
+ Pager *pPager;
+ int szPage;
+
+ if( pTab->db->flags & SQLITE_Defensive ){
+ zErr = "read-only";
+ goto update_fail;
+ }
+ if( argc==1 ){
+ zErr = "cannot delete";
+ goto update_fail;
+ }
+ pgno = tdsqlite3_value_int(argv[0]);
+ if( (Pgno)tdsqlite3_value_int(argv[1])!=pgno ){
+ zErr = "cannot insert";
+ goto update_fail;
+ }
+ zSchema = (const char*)tdsqlite3_value_text(argv[4]);
+ iDb = zSchema ? tdsqlite3FindDbName(pTab->db, zSchema) : -1;
+ if( iDb<0 ){
+ zErr = "no such schema";
+ goto update_fail;
+ }
+ pBt = pTab->db->aDb[iDb].pBt;
+ if( pgno<1 || pBt==0 || pgno>(int)tdsqlite3BtreeLastPage(pBt) ){
+ zErr = "bad page number";
+ goto update_fail;
+ }
+ szPage = tdsqlite3BtreeGetPageSize(pBt);
+ if( tdsqlite3_value_type(argv[3])!=SQLITE_BLOB
+ || tdsqlite3_value_bytes(argv[3])!=szPage
+ ){
+ zErr = "bad page value";
+ goto update_fail;
+ }
+ pPager = tdsqlite3BtreePager(pBt);
+ rc = tdsqlite3PagerGet(pPager, pgno, (DbPage**)&pDbPage, 0);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3PagerWrite(pDbPage);
+ if( rc==SQLITE_OK ){
+ memcpy(tdsqlite3PagerGetData(pDbPage),
+ tdsqlite3_value_blob(argv[3]),
+ szPage);
+ }
+ }
+ tdsqlite3PagerUnref(pDbPage);
+ return rc;
+
+update_fail:
+ tdsqlite3_free(pVtab->zErrMsg);
+ pVtab->zErrMsg = tdsqlite3_mprintf("%s", zErr);
+ return SQLITE_ERROR;
+}
+
+/* Since we do not know in advance which database files will be
+** written by the sqlite_dbpage virtual table, start a write transaction
+** on them all.
+*/
+static int dbpageBegin(tdsqlite3_vtab *pVtab){
+ DbpageTable *pTab = (DbpageTable *)pVtab;
+ tdsqlite3 *db = pTab->db;
+ int i;
+ for(i=0; i<db->nDb; i++){
+ Btree *pBt = db->aDb[i].pBt;
+ if( pBt ) tdsqlite3BtreeBeginTrans(pBt, 1, 0);
+ }
+ return SQLITE_OK;
+}
+
+
+/*
+** Invoke this routine to register the "dbpage" virtual table module
+*/
+SQLITE_PRIVATE int tdsqlite3DbpageRegister(tdsqlite3 *db){
+ static tdsqlite3_module dbpage_module = {
+ 0, /* iVersion */
+ dbpageConnect, /* xCreate */
+ dbpageConnect, /* xConnect */
+ dbpageBestIndex, /* xBestIndex */
+ dbpageDisconnect, /* xDisconnect */
+ dbpageDisconnect, /* xDestroy */
+ dbpageOpen, /* xOpen - open a cursor */
+ dbpageClose, /* xClose - close a cursor */
+ dbpageFilter, /* xFilter - configure scan constraints */
+ dbpageNext, /* xNext - advance a cursor */
+ dbpageEof, /* xEof - check for end of scan */
+ dbpageColumn, /* xColumn - read data */
+ dbpageRowid, /* xRowid - read data */
+ dbpageUpdate, /* xUpdate */
+ dbpageBegin, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0 /* xShadowName */
+ };
+ return tdsqlite3_create_module(db, "sqlite_dbpage", &dbpage_module, 0);
+}
+#elif defined(SQLITE_ENABLE_DBPAGE_VTAB)
+SQLITE_PRIVATE int tdsqlite3DbpageRegister(tdsqlite3 *db){ return SQLITE_OK; }
+#endif /* SQLITE_ENABLE_DBSTAT_VTAB */
+
+/************** End of dbpage.c **********************************************/
+/************** Begin file tdsqlite3session.c **********************************/
#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
-/* #include "sqlite3session.h" */
+/* #include "tdsqlite3session.h" */
/* #include <assert.h> */
/* #include <string.h> */
@@ -174881,11 +204047,13 @@ typedef struct SessionInput SessionInput;
# endif
#endif
+static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE;
+
typedef struct SessionHook SessionHook;
struct SessionHook {
void *pCtx;
- int (*xOld)(void*,int,sqlite3_value**);
- int (*xNew)(void*,int,sqlite3_value**);
+ int (*xOld)(void*,int,tdsqlite3_value**);
+ int (*xNew)(void*,int,tdsqlite3_value**);
int (*xCount)(void*);
int (*xDepth)(void*);
};
@@ -174893,8 +204061,8 @@ struct SessionHook {
/*
** Session handle structure.
*/
-struct sqlite3_session {
- sqlite3 *db; /* Database handle session is attached to */
+struct tdsqlite3_session {
+ tdsqlite3 *db; /* Database handle session is attached to */
char *zDb; /* Name of database session is attached to */
int bEnable; /* True if currently recording */
int bIndirect; /* True if all changes are indirect */
@@ -174902,7 +204070,8 @@ struct sqlite3_session {
int rc; /* Non-zero if an error has occurred */
void *pFilterCtx; /* First argument to pass to xTableFilter */
int (*xTableFilter)(void *pCtx, const char *zTab);
- sqlite3_session *pNext; /* Next session object on same db. */
+ tdsqlite3_value *pZeroBlob; /* Value containing X'' */
+ tdsqlite3_session *pNext; /* Next session object on same db. */
SessionTable *pTable; /* List of attached tables */
SessionHook hook; /* APIs to grab new and old data with */
};
@@ -174919,11 +204088,11 @@ struct SessionBuffer {
/*
** An object of this type is used internally as an abstraction for
** input data. Input data may be supplied either as a single large buffer
-** (e.g. sqlite3changeset_start()) or using a stream function (e.g.
-** sqlite3changeset_start_strm()).
+** (e.g. tdsqlite3changeset_start()) or using a stream function (e.g.
+** tdsqlite3changeset_start_strm()).
*/
struct SessionInput {
- int bNoDiscard; /* If true, discard no data */
+ int bNoDiscard; /* If true, do not discard in InputBuffer() */
int iCurrent; /* Offset in aData[] of current change */
int iNext; /* Offset in aData[] of next change */
u8 *aData; /* Pointer to buffer containing changeset */
@@ -174938,24 +204107,25 @@ struct SessionInput {
/*
** Structure for changeset iterators.
*/
-struct sqlite3_changeset_iter {
+struct tdsqlite3_changeset_iter {
SessionInput in; /* Input buffer or stream */
SessionBuffer tblhdr; /* Buffer to hold apValue/zTab/abPK/ */
int bPatchset; /* True if this is a patchset */
+ int bInvert; /* True to invert changeset */
int rc; /* Iterator error code */
- sqlite3_stmt *pConflict; /* Points to conflicting row, if any */
+ tdsqlite3_stmt *pConflict; /* Points to conflicting row, if any */
char *zTab; /* Current table */
int nCol; /* Number of columns in zTab */
int op; /* Current operation */
int bIndirect; /* True if current change was indirect */
u8 *abPK; /* Primary key array */
- sqlite3_value **apValue; /* old.* and new.* values */
+ tdsqlite3_value **apValue; /* old.* and new.* values */
};
/*
** Each session object maintains a set of the following structures, one
** for each table the session object is monitoring. The structures are
-** stored in a linked list starting at sqlite3_session.pTable.
+** stored in a linked list starting at tdsqlite3_session.pTable.
**
** The keys of the SessionTable.aChange[] hash table are all rows that have
** been modified in any way since the session object was attached to the
@@ -174969,6 +204139,7 @@ struct SessionTable {
SessionTable *pNext;
char *zName; /* Local name of table */
int nCol; /* Number of columns in table zName */
+ int bStat1; /* True if this is sqlite_stat1 */
const char **azCol; /* Column names */
u8 *abPK; /* Array of primary key flags */
int nEntry; /* Total number of entries in hash table */
@@ -175086,8 +204257,8 @@ struct SessionTable {
** statement.
**
** For a DELETE change, all fields within the record except those associated
-** with PRIMARY KEY columns are set to "undefined". The PRIMARY KEY fields
-** contain the values identifying the row to delete.
+** with PRIMARY KEY columns are omitted. The PRIMARY KEY fields contain the
+** values identifying the row to delete.
**
** For an UPDATE change, all fields except those associated with PRIMARY KEY
** columns and columns that are modified by the UPDATE are set to "undefined".
@@ -175097,6 +204268,42 @@ struct SessionTable {
** The records associated with INSERT changes are in the same format as for
** changesets. It is not possible for a record associated with an INSERT
** change to contain a field set to "undefined".
+**
+** REBASE BLOB FORMAT:
+**
+** A rebase blob may be output by tdsqlite3changeset_apply_v2() and its
+** streaming equivalent for use with the tdsqlite3_rebaser APIs to rebase
+** existing changesets. A rebase blob contains one entry for each conflict
+** resolved using either the OMIT or REPLACE strategies within the apply_v2()
+** call.
+**
+** The format used for a rebase blob is very similar to that used for
+** changesets. All entries related to a single table are grouped together.
+**
+** Each group of entries begins with a table header in changeset format:
+**
+** 1 byte: Constant 0x54 (capital 'T')
+** Varint: Number of columns in the table.
+** nCol bytes: 0x01 for PK columns, 0x00 otherwise.
+** N bytes: Unqualified table name (encoded using UTF-8). Nul-terminated.
+**
+** Followed by one or more entries associated with the table.
+**
+** 1 byte: Either SQLITE_INSERT (0x12), DELETE (0x09).
+** 1 byte: Flag. 0x01 for REPLACE, 0x00 for OMIT.
+** record: (in the record format defined above).
+**
+** In a rebase blob, the first field is set to SQLITE_INSERT if the change
+** that caused the conflict was an INSERT or UPDATE, or to SQLITE_DELETE if
+** it was a DELETE. The second field is set to 0x01 if the conflict
+** resolution strategy was REPLACE, or 0x00 if it was OMIT.
+**
+** If the change that caused the conflict was a DELETE, then the single
+** record is a copy of the old.* record from the original changeset. If it
+** was an INSERT, then the single record is a copy of the new.* record. If
+** the conflicting change was an UPDATE, then the single record is a copy
+** of the new.* record with the PK fields filled in based on the original
+** old.* record.
*/
/*
@@ -175123,7 +204330,7 @@ static int sessionVarintPut(u8 *aBuf, int iVal){
** Return the number of bytes required to store value iVal as a varint.
*/
static int sessionVarintLen(int iVal){
- return sqlite3VarintLen(iVal);
+ return tdsqlite3VarintLen(iVal);
}
/*
@@ -175141,17 +204348,17 @@ static int sessionVarintGet(u8 *aBuf, int *piVal){
** Read a 64-bit big-endian integer value from buffer aRec[]. Return
** the value read.
*/
-static sqlite3_int64 sessionGetI64(u8 *aRec){
+static tdsqlite3_int64 sessionGetI64(u8 *aRec){
u64 x = SESSION_UINT32(aRec);
u32 y = SESSION_UINT32(aRec+4);
x = (x<<32) + y;
- return (sqlite3_int64)x;
+ return (tdsqlite3_int64)x;
}
/*
** Write a 64-bit big-endian integer value to the buffer aBuf[].
*/
-static void sessionPutI64(u8 *aBuf, sqlite3_int64 i){
+static void sessionPutI64(u8 *aBuf, tdsqlite3_int64 i){
aBuf[0] = (i>>56) & 0xFF;
aBuf[1] = (i>>48) & 0xFF;
aBuf[2] = (i>>40) & 0xFF;
@@ -175172,20 +204379,20 @@ static void sessionPutI64(u8 *aBuf, sqlite3_int64 i){
** set *pnWrite.
**
** If no error occurs, SQLITE_OK is returned. Or, if an OOM error occurs
-** within a call to sqlite3_value_text() (may fail if the db is utf-16))
+** within a call to tdsqlite3_value_text() (may fail if the db is utf-16))
** SQLITE_NOMEM is returned.
*/
static int sessionSerializeValue(
u8 *aBuf, /* If non-NULL, write serialized value here */
- sqlite3_value *pValue, /* Value to serialize */
- int *pnWrite /* IN/OUT: Increment by bytes written */
+ tdsqlite3_value *pValue, /* Value to serialize */
+ tdsqlite3_int64 *pnWrite /* IN/OUT: Increment by bytes written */
){
int nByte; /* Size of serialized value in bytes */
if( pValue ){
int eType; /* Value type (SQLITE_NULL, TEXT etc.) */
- eType = sqlite3_value_type(pValue);
+ eType = tdsqlite3_value_type(pValue);
if( aBuf ) aBuf[0] = eType;
switch( eType ){
@@ -175201,11 +204408,11 @@ static int sessionSerializeValue(
** too. */
u64 i;
if( eType==SQLITE_INTEGER ){
- i = (u64)sqlite3_value_int64(pValue);
+ i = (u64)tdsqlite3_value_int64(pValue);
}else{
double r;
assert( sizeof(double)==8 && sizeof(u64)==8 );
- r = sqlite3_value_double(pValue);
+ r = tdsqlite3_value_double(pValue);
memcpy(&i, &r, 8);
}
sessionPutI64(&aBuf[1], i);
@@ -175220,19 +204427,17 @@ static int sessionSerializeValue(
assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
if( eType==SQLITE_TEXT ){
- z = (u8 *)sqlite3_value_text(pValue);
+ z = (u8 *)tdsqlite3_value_text(pValue);
}else{
- z = (u8 *)sqlite3_value_blob(pValue);
+ z = (u8 *)tdsqlite3_value_blob(pValue);
}
- n = sqlite3_value_bytes(pValue);
+ n = tdsqlite3_value_bytes(pValue);
if( z==0 && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
nVarint = sessionVarintLen(n);
if( aBuf ){
sessionVarintPut(&aBuf[1], n);
- memcpy(&aBuf[nVarint + 1], eType==SQLITE_TEXT ?
- sqlite3_value_text(pValue) : sqlite3_value_blob(pValue), n
- );
+ if( n ) memcpy(&aBuf[nVarint + 1], z, n);
}
nByte = 1 + nVarint + n;
@@ -175305,7 +204510,7 @@ static unsigned int sessionHashAppendType(unsigned int h, int eType){
** and the output variables are set as described above.
*/
static int sessionPreupdateHash(
- sqlite3_session *pSession, /* Session object that owns pTab */
+ tdsqlite3_session *pSession, /* Session object that owns pTab */
SessionTable *pTab, /* Session table handle */
int bNew, /* True to hash the new.* PK */
int *piHash, /* OUT: Hash value */
@@ -175320,7 +204525,7 @@ static int sessionPreupdateHash(
if( pTab->abPK[i] ){
int rc;
int eType;
- sqlite3_value *pVal;
+ tdsqlite3_value *pVal;
if( bNew ){
rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
@@ -175329,14 +204534,14 @@ static int sessionPreupdateHash(
}
if( rc!=SQLITE_OK ) return rc;
- eType = sqlite3_value_type(pVal);
+ eType = tdsqlite3_value_type(pVal);
h = sessionHashAppendType(h, eType);
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
i64 iVal;
if( eType==SQLITE_INTEGER ){
- iVal = sqlite3_value_int64(pVal);
+ iVal = tdsqlite3_value_int64(pVal);
}else{
- double rVal = sqlite3_value_double(pVal);
+ double rVal = tdsqlite3_value_double(pVal);
assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
memcpy(&iVal, &rVal, 8);
}
@@ -175345,15 +204550,16 @@ static int sessionPreupdateHash(
const u8 *z;
int n;
if( eType==SQLITE_TEXT ){
- z = (const u8 *)sqlite3_value_text(pVal);
+ z = (const u8 *)tdsqlite3_value_text(pVal);
}else{
- z = (const u8 *)sqlite3_value_blob(pVal);
+ z = (const u8 *)tdsqlite3_value_blob(pVal);
}
- n = sqlite3_value_bytes(pVal);
+ n = tdsqlite3_value_bytes(pVal);
if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
h = sessionHashAppendBlob(h, n, z);
}else{
assert( eType==SQLITE_NULL );
+ assert( pTab->bStat1==0 || i!=1 );
*pbNullPK = 1;
}
}
@@ -175371,7 +204577,7 @@ static int sessionPreupdateHash(
static int sessionSerialLen(u8 *a){
int e = *a;
int n;
- if( e==0 ) return 1;
+ if( e==0 || e==0xFF ) return 1;
if( e==SQLITE_NULL ) return 1;
if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9;
return sessionVarintGet(&a[1], &n) + 1 + n;
@@ -175451,7 +204657,7 @@ static int sessionChangeEqual(
int n1 = sessionSerialLen(a1);
int n2 = sessionSerialLen(a2);
- if( pTab->abPK[iCol] && (n1!=n2 || memcmp(a1, a2, n1)) ){
+ if( n1!=n2 || memcmp(a1, a2, n1) ){
return 0;
}
a1 += n1;
@@ -175636,7 +204842,7 @@ static int sessionMergeUpdate(
** false.
*/
static int sessionPreupdateEqual(
- sqlite3_session *pSession, /* Session object that owns SessionTable */
+ tdsqlite3_session *pSession, /* Session object that owns SessionTable */
SessionTable *pTab, /* Table associated with change */
SessionChange *pChange, /* Change to compare to */
int op /* Current pre-update operation */
@@ -175649,7 +204855,7 @@ static int sessionPreupdateEqual(
if( !pTab->abPK[iCol] ){
a += sessionSerialLen(a);
}else{
- sqlite3_value *pVal; /* Value returned by preupdate_new/old */
+ tdsqlite3_value *pVal; /* Value returned by preupdate_new/old */
int rc; /* Error code from preupdate_new/old */
int eType = *a++; /* Type of value from change record */
@@ -175666,7 +204872,7 @@ static int sessionPreupdateEqual(
rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal);
}
assert( rc==SQLITE_OK );
- if( sqlite3_value_type(pVal)!=eType ) return 0;
+ if( tdsqlite3_value_type(pVal)!=eType ) return 0;
/* A SessionChange object never has a NULL value in a PK column */
assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
@@ -175677,26 +204883,25 @@ static int sessionPreupdateEqual(
i64 iVal = sessionGetI64(a);
a += 8;
if( eType==SQLITE_INTEGER ){
- if( sqlite3_value_int64(pVal)!=iVal ) return 0;
+ if( tdsqlite3_value_int64(pVal)!=iVal ) return 0;
}else{
double rVal;
assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
memcpy(&rVal, &iVal, 8);
- if( sqlite3_value_double(pVal)!=rVal ) return 0;
+ if( tdsqlite3_value_double(pVal)!=rVal ) return 0;
}
}else{
int n;
const u8 *z;
a += sessionVarintGet(a, &n);
- if( sqlite3_value_bytes(pVal)!=n ) return 0;
+ if( tdsqlite3_value_bytes(pVal)!=n ) return 0;
if( eType==SQLITE_TEXT ){
- z = sqlite3_value_text(pVal);
+ z = tdsqlite3_value_text(pVal);
}else{
- z = sqlite3_value_blob(pVal);
+ z = tdsqlite3_value_blob(pVal);
}
- if( memcmp(a, z, n) ) return 0;
+ if( n>0 && memcmp(a, z, n) ) return 0;
a += n;
- break;
}
}
}
@@ -175719,9 +204924,9 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){
int i;
SessionChange **apNew;
- int nNew = (pTab->nChange ? pTab->nChange : 128) * 2;
+ tdsqlite3_int64 nNew = 2*(tdsqlite3_int64)(pTab->nChange ? pTab->nChange : 128);
- apNew = (SessionChange **)sqlite3_malloc(sizeof(SessionChange *) * nNew);
+ apNew = (SessionChange **)tdsqlite3_malloc64(sizeof(SessionChange *) * nNew);
if( apNew==0 ){
if( pTab->nChange==0 ){
return SQLITE_ERROR;
@@ -175742,7 +204947,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
}
}
- sqlite3_free(pTab->apChange);
+ tdsqlite3_free(pTab->apChange);
pTab->nChange = nNew;
pTab->apChange = apNew;
}
@@ -175752,9 +204957,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
/*
** This function queries the database for the names of the columns of table
-** zThis, in schema zDb. It is expected that the table has nCol columns. If
-** not, SQLITE_SCHEMA is returned and none of the output variables are
-** populated.
+** zThis, in schema zDb.
**
** Otherwise, if they are not NULL, variable *pnCol is set to the number
** of columns in the database table and variable *pzTab is set to point to a
@@ -175775,12 +204978,10 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
** *pabPK = {1, 0, 0, 1}
**
** All returned buffers are part of the same single allocation, which must
-** be freed using sqlite3_free() by the caller. If pazCol was not NULL, then
-** pointer *pazCol should be freed to release all memory. Otherwise, pointer
-** *pabPK. It is illegal for both pazCol and pabPK to be NULL.
+** be freed using tdsqlite3_free() by the caller
*/
static int sessionTableInfo(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
const char *zDb, /* Name of attached database (e.g. "main") */
const char *zThis, /* Table name */
int *pnCol, /* OUT: number of columns */
@@ -175789,9 +204990,9 @@ static int sessionTableInfo(
u8 **pabPK /* OUT: Array of booleans - true for PK col */
){
char *zPragma;
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
int rc;
- int nByte;
+ tdsqlite3_int64 nByte;
int nDbCol = 0;
int nThis;
int i;
@@ -175801,24 +205002,40 @@ static int sessionTableInfo(
assert( pazCol && pabPK );
- nThis = sqlite3Strlen30(zThis);
- zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
+ nThis = tdsqlite3Strlen30(zThis);
+ if( nThis==12 && 0==tdsqlite3_stricmp("sqlite_stat1", zThis) ){
+ rc = tdsqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0);
+ if( rc==SQLITE_OK ){
+ /* For sqlite_stat1, pretend that (tbl,idx) is the PRIMARY KEY. */
+ zPragma = tdsqlite3_mprintf(
+ "SELECT 0, 'tbl', '', 0, '', 1 UNION ALL "
+ "SELECT 1, 'idx', '', 0, '', 2 UNION ALL "
+ "SELECT 2, 'stat', '', 0, '', 0"
+ );
+ }else if( rc==SQLITE_ERROR ){
+ zPragma = tdsqlite3_mprintf("");
+ }else{
+ return rc;
+ }
+ }else{
+ zPragma = tdsqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
+ }
if( !zPragma ) return SQLITE_NOMEM;
- rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
- sqlite3_free(zPragma);
+ rc = tdsqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
+ tdsqlite3_free(zPragma);
if( rc!=SQLITE_OK ) return rc;
nByte = nThis + 1;
- while( SQLITE_ROW==sqlite3_step(pStmt) ){
- nByte += sqlite3_column_bytes(pStmt, 1);
+ while( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ nByte += tdsqlite3_column_bytes(pStmt, 1);
nDbCol++;
}
- rc = sqlite3_reset(pStmt);
+ rc = tdsqlite3_reset(pStmt);
if( rc==SQLITE_OK ){
nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
- pAlloc = sqlite3_malloc(nByte);
+ pAlloc = tdsqlite3_malloc64(nByte);
if( pAlloc==0 ){
rc = SQLITE_NOMEM;
}
@@ -175835,17 +205052,17 @@ static int sessionTableInfo(
}
i = 0;
- while( SQLITE_ROW==sqlite3_step(pStmt) ){
- int nName = sqlite3_column_bytes(pStmt, 1);
- const unsigned char *zName = sqlite3_column_text(pStmt, 1);
+ while( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ int nName = tdsqlite3_column_bytes(pStmt, 1);
+ const unsigned char *zName = tdsqlite3_column_text(pStmt, 1);
if( zName==0 ) break;
memcpy(pAlloc, zName, nName+1);
azCol[i] = (char *)pAlloc;
pAlloc += nName+1;
- abPK[i] = sqlite3_column_int(pStmt, 5);
+ abPK[i] = tdsqlite3_column_int(pStmt, 5);
i++;
}
- rc = sqlite3_reset(pStmt);
+ rc = tdsqlite3_reset(pStmt);
}
@@ -175861,9 +205078,9 @@ static int sessionTableInfo(
*pabPK = 0;
*pnCol = 0;
if( pzTab ) *pzTab = 0;
- sqlite3_free(azCol);
+ tdsqlite3_free(azCol);
}
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
return rc;
}
@@ -175873,13 +205090,13 @@ static int sessionTableInfo(
** write to this table, initalize the SessionTable.nCol, azCol[] and
** abPK[] arrays accordingly.
**
-** If an error occurs, an error code is stored in sqlite3_session.rc and
+** If an error occurs, an error code is stored in tdsqlite3_session.rc and
** non-zero returned. Or, if no error occurs but the table has no primary
-** key, sqlite3_session.rc is left set to SQLITE_OK and non-zero returned to
+** key, tdsqlite3_session.rc is left set to SQLITE_OK and non-zero returned to
** indicate that updates on this table should be ignored. SessionTable.abPK
** is set to NULL in this case.
*/
-static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
+static int sessionInitTable(tdsqlite3_session *pSession, SessionTable *pTab){
if( pTab->nCol==0 ){
u8 *abPK;
assert( pTab->azCol==0 || pTab->abPK==0 );
@@ -175894,12 +205111,56 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
break;
}
}
+ if( 0==tdsqlite3_stricmp("sqlite_stat1", pTab->zName) ){
+ pTab->bStat1 = 1;
+ }
}
}
return (pSession->rc || pTab->abPK==0);
}
/*
+** Versions of the four methods in object SessionHook for use with the
+** sqlite_stat1 table. The purpose of this is to substitute a zero-length
+** blob each time a NULL value is read from the "idx" column of the
+** sqlite_stat1 table.
+*/
+typedef struct SessionStat1Ctx SessionStat1Ctx;
+struct SessionStat1Ctx {
+ SessionHook hook;
+ tdsqlite3_session *pSession;
+};
+static int sessionStat1Old(void *pCtx, int iCol, tdsqlite3_value **ppVal){
+ SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
+ tdsqlite3_value *pVal = 0;
+ int rc = p->hook.xOld(p->hook.pCtx, iCol, &pVal);
+ if( rc==SQLITE_OK && iCol==1 && tdsqlite3_value_type(pVal)==SQLITE_NULL ){
+ pVal = p->pSession->pZeroBlob;
+ }
+ *ppVal = pVal;
+ return rc;
+}
+static int sessionStat1New(void *pCtx, int iCol, tdsqlite3_value **ppVal){
+ SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
+ tdsqlite3_value *pVal = 0;
+ int rc = p->hook.xNew(p->hook.pCtx, iCol, &pVal);
+ if( rc==SQLITE_OK && iCol==1 && tdsqlite3_value_type(pVal)==SQLITE_NULL ){
+ pVal = p->pSession->pZeroBlob;
+ }
+ *ppVal = pVal;
+ return rc;
+}
+static int sessionStat1Count(void *pCtx){
+ SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
+ return p->hook.xCount(p->hook.pCtx);
+}
+static int sessionStat1Depth(void *pCtx){
+ SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
+ return p->hook.xDepth(p->hook.pCtx);
+}
+
+
+/*
** This function is only called from with a pre-update-hook reporting a
** change on table pTab (attached to session pSession). The type of change
** (UPDATE, INSERT, DELETE) is specified by the first argument.
@@ -175909,12 +205170,13 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
*/
static void sessionPreupdateOneChange(
int op, /* One of SQLITE_UPDATE, INSERT, DELETE */
- sqlite3_session *pSession, /* Session object pTab is attached to */
+ tdsqlite3_session *pSession, /* Session object pTab is attached to */
SessionTable *pTab /* Table that change applies to */
){
int iHash;
int bNull = 0;
int rc = SQLITE_OK;
+ SessionStat1Ctx stat1 = {{0,0,0,0,0},0};
if( pSession->rc ) return;
@@ -175934,6 +205196,25 @@ static void sessionPreupdateOneChange(
return;
}
+ if( pTab->bStat1 ){
+ stat1.hook = pSession->hook;
+ stat1.pSession = pSession;
+ pSession->hook.pCtx = (void*)&stat1;
+ pSession->hook.xNew = sessionStat1New;
+ pSession->hook.xOld = sessionStat1Old;
+ pSession->hook.xCount = sessionStat1Count;
+ pSession->hook.xDepth = sessionStat1Depth;
+ if( pSession->pZeroBlob==0 ){
+ tdsqlite3_value *p = tdsqlite3ValueNew(0);
+ if( p==0 ){
+ rc = SQLITE_NOMEM;
+ goto error_out;
+ }
+ tdsqlite3ValueSetStr(p, 0, "", 0, SQLITE_STATIC);
+ pSession->pZeroBlob = p;
+ }
+ }
+
/* Calculate the hash-key for this change. If the primary key of the row
** includes a NULL value, exit early. Such changes are ignored by the
** session module. */
@@ -175952,7 +205233,7 @@ static void sessionPreupdateOneChange(
** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
** values (if this is an INSERT). */
SessionChange *pChange; /* New change object */
- int nByte; /* Number of bytes to allocate */
+ tdsqlite3_int64 nByte; /* Number of bytes to allocate */
int i; /* Used to iterate through columns */
assert( rc==SQLITE_OK );
@@ -175961,7 +205242,7 @@ static void sessionPreupdateOneChange(
/* Figure out how large an allocation is required */
nByte = sizeof(SessionChange);
for(i=0; i<pTab->nCol; i++){
- sqlite3_value *p = 0;
+ tdsqlite3_value *p = 0;
if( op!=SQLITE_INSERT ){
TESTONLY(int trc = ) pSession->hook.xOld(pSession->hook.pCtx, i, &p);
assert( trc==SQLITE_OK );
@@ -175977,7 +205258,7 @@ static void sessionPreupdateOneChange(
}
/* Allocate the change object */
- pChange = (SessionChange *)sqlite3_malloc(nByte);
+ pChange = (SessionChange *)tdsqlite3_malloc64(nByte);
if( !pChange ){
rc = SQLITE_NOMEM;
goto error_out;
@@ -175992,7 +205273,7 @@ static void sessionPreupdateOneChange(
** It is not possible for an OOM to occur in this block. */
nByte = 0;
for(i=0; i<pTab->nCol; i++){
- sqlite3_value *p = 0;
+ tdsqlite3_value *p = 0;
if( op!=SQLITE_INSERT ){
pSession->hook.xOld(pSession->hook.pCtx, i, &p);
}else if( pTab->abPK[i] ){
@@ -176023,23 +205304,26 @@ static void sessionPreupdateOneChange(
/* If an error has occurred, mark the session object as failed. */
error_out:
+ if( pTab->bStat1 ){
+ pSession->hook = stat1.hook;
+ }
if( rc!=SQLITE_OK ){
pSession->rc = rc;
}
}
static int sessionFindTable(
- sqlite3_session *pSession,
+ tdsqlite3_session *pSession,
const char *zName,
SessionTable **ppTab
){
int rc = SQLITE_OK;
- int nName = sqlite3Strlen30(zName);
+ int nName = tdsqlite3Strlen30(zName);
SessionTable *pRet;
/* Search for an existing table */
for(pRet=pSession->pTable; pRet; pRet=pRet->pNext){
- if( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) ) break;
+ if( 0==tdsqlite3_strnicmp(pRet->zName, zName, nName+1) ) break;
}
if( pRet==0 && pSession->bAutoAttach ){
@@ -176048,10 +205332,10 @@ static int sessionFindTable(
if( pSession->xTableFilter==0
|| pSession->xTableFilter(pSession->pFilterCtx, zName)
){
- rc = sqlite3session_attach(pSession, zName);
+ rc = tdsqlite3session_attach(pSession, zName);
if( rc==SQLITE_OK ){
for(pRet=pSession->pTable; pRet->pNext; pRet=pRet->pNext);
- assert( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) );
+ assert( 0==tdsqlite3_strnicmp(pRet->zName, zName, nName+1) );
}
}
}
@@ -176066,19 +205350,19 @@ static int sessionFindTable(
*/
static void xPreUpdate(
void *pCtx, /* Copy of third arg to preupdate_hook() */
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
int op, /* SQLITE_UPDATE, DELETE or INSERT */
char const *zDb, /* Database name */
char const *zName, /* Table name */
- sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */
- sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */
+ tdsqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */
+ tdsqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */
){
- sqlite3_session *pSession;
- int nDb = sqlite3Strlen30(zDb);
+ tdsqlite3_session *pSession;
+ int nDb = tdsqlite3Strlen30(zDb);
- assert( sqlite3_mutex_held(db->mutex) );
+ assert( tdsqlite3_mutex_held(db->mutex) );
- for(pSession=(sqlite3_session *)pCtx; pSession; pSession=pSession->pNext){
+ for(pSession=(tdsqlite3_session *)pCtx; pSession; pSession=pSession->pNext){
SessionTable *pTab;
/* If this session is attached to a different database ("main", "temp"
@@ -176086,7 +205370,7 @@ static void xPreUpdate(
** to the next session object attached to this database. */
if( pSession->bEnable==0 ) continue;
if( pSession->rc ) continue;
- if( sqlite3_strnicmp(zDb, pSession->zDb, nDb+1) ) continue;
+ if( tdsqlite3_strnicmp(zDb, pSession->zDb, nDb+1) ) continue;
pSession->rc = sessionFindTable(pSession, zName, &pTab);
if( pTab ){
@@ -176102,17 +205386,17 @@ static void xPreUpdate(
/*
** The pre-update hook implementations.
*/
-static int sessionPreupdateOld(void *pCtx, int iVal, sqlite3_value **ppVal){
- return sqlite3_preupdate_old((sqlite3*)pCtx, iVal, ppVal);
+static int sessionPreupdateOld(void *pCtx, int iVal, tdsqlite3_value **ppVal){
+ return tdsqlite3_preupdate_old((tdsqlite3*)pCtx, iVal, ppVal);
}
-static int sessionPreupdateNew(void *pCtx, int iVal, sqlite3_value **ppVal){
- return sqlite3_preupdate_new((sqlite3*)pCtx, iVal, ppVal);
+static int sessionPreupdateNew(void *pCtx, int iVal, tdsqlite3_value **ppVal){
+ return tdsqlite3_preupdate_new((tdsqlite3*)pCtx, iVal, ppVal);
}
static int sessionPreupdateCount(void *pCtx){
- return sqlite3_preupdate_count((sqlite3*)pCtx);
+ return tdsqlite3_preupdate_count((tdsqlite3*)pCtx);
}
static int sessionPreupdateDepth(void *pCtx){
- return sqlite3_preupdate_depth((sqlite3*)pCtx);
+ return tdsqlite3_preupdate_depth((tdsqlite3*)pCtx);
}
/*
@@ -176120,7 +205404,7 @@ static int sessionPreupdateDepth(void *pCtx){
** argument.
*/
static void sessionPreupdateHooks(
- sqlite3_session *pSession
+ tdsqlite3_session *pSession
){
pSession->hook.pCtx = (void*)pSession->db;
pSession->hook.xOld = sessionPreupdateOld;
@@ -176131,26 +205415,26 @@ static void sessionPreupdateHooks(
typedef struct SessionDiffCtx SessionDiffCtx;
struct SessionDiffCtx {
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
int nOldOff;
};
/*
** The diff hook implementations.
*/
-static int sessionDiffOld(void *pCtx, int iVal, sqlite3_value **ppVal){
+static int sessionDiffOld(void *pCtx, int iVal, tdsqlite3_value **ppVal){
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
- *ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff);
+ *ppVal = tdsqlite3_column_value(p->pStmt, iVal+p->nOldOff);
return SQLITE_OK;
}
-static int sessionDiffNew(void *pCtx, int iVal, sqlite3_value **ppVal){
+static int sessionDiffNew(void *pCtx, int iVal, tdsqlite3_value **ppVal){
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
- *ppVal = sqlite3_column_value(p->pStmt, iVal);
+ *ppVal = tdsqlite3_column_value(p->pStmt, iVal);
return SQLITE_OK;
}
static int sessionDiffCount(void *pCtx){
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
- return p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt);
+ return p->nOldOff ? p->nOldOff : tdsqlite3_column_count(p->pStmt);
}
static int sessionDiffDepth(void *pCtx){
return 0;
@@ -176161,7 +205445,7 @@ static int sessionDiffDepth(void *pCtx){
** argument.
*/
static void sessionDiffHooks(
- sqlite3_session *pSession,
+ tdsqlite3_session *pSession,
SessionDiffCtx *pDiffCtx
){
pSession->hook.pCtx = (void*)pDiffCtx;
@@ -176183,7 +205467,7 @@ static char *sessionExprComparePK(
for(i=0; i<nCol; i++){
if( abPK[i] ){
- zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"=\"%w\".\"%w\".\"%w\"",
+ zRet = tdsqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"=\"%w\".\"%w\".\"%w\"",
zRet, zSep, zDb1, zTab, azCol[i], zDb2, zTab, azCol[i]
);
zSep = " AND ";
@@ -176208,7 +205492,7 @@ static char *sessionExprCompareOther(
for(i=0; i<nCol; i++){
if( abPK[i]==0 ){
bHave = 1;
- zRet = sqlite3_mprintf(
+ zRet = tdsqlite3_mprintf(
"%z%s\"%w\".\"%w\".\"%w\" IS NOT \"%w\".\"%w\".\"%w\"",
zRet, zSep, zDb1, zTab, azCol[i], zDb2, zTab, azCol[i]
);
@@ -176219,7 +205503,7 @@ static char *sessionExprCompareOther(
if( bHave==0 ){
assert( zRet==0 );
- zRet = sqlite3_mprintf("0");
+ zRet = tdsqlite3_mprintf("0");
}
return zRet;
@@ -176232,7 +205516,7 @@ static char *sessionSelectFindNew(
const char *zTbl, /* Table name */
const char *zExpr
){
- char *zRet = sqlite3_mprintf(
+ char *zRet = tdsqlite3_mprintf(
"SELECT * FROM \"%w\".\"%w\" WHERE NOT EXISTS ("
" SELECT 1 FROM \"%w\".\"%w\" WHERE %s"
")",
@@ -176243,7 +205527,7 @@ static char *sessionSelectFindNew(
static int sessionDiffFindNew(
int op,
- sqlite3_session *pSession,
+ tdsqlite3_session *pSession,
SessionTable *pTab,
const char *zDb1,
const char *zDb2,
@@ -176255,25 +205539,25 @@ static int sessionDiffFindNew(
if( zStmt==0 ){
rc = SQLITE_NOMEM;
}else{
- sqlite3_stmt *pStmt;
- rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
+ tdsqlite3_stmt *pStmt;
+ rc = tdsqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
if( rc==SQLITE_OK ){
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
pDiffCtx->pStmt = pStmt;
pDiffCtx->nOldOff = 0;
- while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ while( SQLITE_ROW==tdsqlite3_step(pStmt) ){
sessionPreupdateOneChange(op, pSession, pTab);
}
- rc = sqlite3_finalize(pStmt);
+ rc = tdsqlite3_finalize(pStmt);
}
- sqlite3_free(zStmt);
+ tdsqlite3_free(zStmt);
}
return rc;
}
static int sessionDiffFindModified(
- sqlite3_session *pSession,
+ tdsqlite3_session *pSession,
SessionTable *pTab,
const char *zFrom,
const char *zExpr
@@ -176286,34 +205570,34 @@ static int sessionDiffFindModified(
if( zExpr2==0 ){
rc = SQLITE_NOMEM;
}else{
- char *zStmt = sqlite3_mprintf(
+ char *zStmt = tdsqlite3_mprintf(
"SELECT * FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)",
pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2
);
if( zStmt==0 ){
rc = SQLITE_NOMEM;
}else{
- sqlite3_stmt *pStmt;
- rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
+ tdsqlite3_stmt *pStmt;
+ rc = tdsqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
if( rc==SQLITE_OK ){
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
pDiffCtx->pStmt = pStmt;
pDiffCtx->nOldOff = pTab->nCol;
- while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ while( SQLITE_ROW==tdsqlite3_step(pStmt) ){
sessionPreupdateOneChange(SQLITE_UPDATE, pSession, pTab);
}
- rc = sqlite3_finalize(pStmt);
+ rc = tdsqlite3_finalize(pStmt);
}
- sqlite3_free(zStmt);
+ tdsqlite3_free(zStmt);
}
}
return rc;
}
-SQLITE_API int sqlite3session_diff(
- sqlite3_session *pSession,
+SQLITE_API int tdsqlite3session_diff(
+ tdsqlite3_session *pSession,
const char *zFrom,
const char *zTbl,
char **pzErrMsg
@@ -176325,11 +205609,11 @@ SQLITE_API int sqlite3session_diff(
memset(&d, 0, sizeof(d));
sessionDiffHooks(pSession, &d);
- sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_enter(tdsqlite3_db_mutex(pSession->db));
if( pzErrMsg ) *pzErrMsg = 0;
if( rc==SQLITE_OK ){
char *zExpr = 0;
- sqlite3 *db = pSession->db;
+ tdsqlite3 *db = pSession->db;
SessionTable *pTo; /* Table zTbl */
/* Locate and if necessary initialize the target table object */
@@ -176355,15 +205639,16 @@ SQLITE_API int sqlite3session_diff(
int i;
for(i=0; i<nCol; i++){
if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1;
- if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1;
+ if( tdsqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1;
if( abPK[i] ) bHasPk = 1;
}
}
-
}
- sqlite3_free((char*)azCol);
+ tdsqlite3_free((char*)azCol);
if( bMismatch ){
- *pzErrMsg = sqlite3_mprintf("table schemas do not match");
+ if( pzErrMsg ){
+ *pzErrMsg = tdsqlite3_mprintf("table schemas do not match");
+ }
rc = SQLITE_SCHEMA;
}
if( bHasPk==0 ){
@@ -176393,12 +205678,12 @@ SQLITE_API int sqlite3session_diff(
rc = sessionDiffFindModified(pSession, pTo, zFrom, zExpr);
}
- sqlite3_free(zExpr);
+ tdsqlite3_free(zExpr);
}
diff_out:
sessionPreupdateHooks(pSession);
- sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_leave(tdsqlite3_db_mutex(pSession->db));
return rc;
}
@@ -176406,22 +205691,22 @@ SQLITE_API int sqlite3session_diff(
** Create a session object. This session object will record changes to
** database zDb attached to connection db.
*/
-SQLITE_API int sqlite3session_create(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3session_create(
+ tdsqlite3 *db, /* Database handle */
const char *zDb, /* Name of db (e.g. "main") */
- sqlite3_session **ppSession /* OUT: New session object */
+ tdsqlite3_session **ppSession /* OUT: New session object */
){
- sqlite3_session *pNew; /* Newly allocated session object */
- sqlite3_session *pOld; /* Session object already attached to db */
- int nDb = sqlite3Strlen30(zDb); /* Length of zDb in bytes */
+ tdsqlite3_session *pNew; /* Newly allocated session object */
+ tdsqlite3_session *pOld; /* Session object already attached to db */
+ int nDb = tdsqlite3Strlen30(zDb); /* Length of zDb in bytes */
/* Zero the output value in case an error occurs. */
*ppSession = 0;
/* Allocate and populate the new session object. */
- pNew = (sqlite3_session *)sqlite3_malloc(sizeof(sqlite3_session) + nDb + 1);
+ pNew = (tdsqlite3_session *)tdsqlite3_malloc64(sizeof(tdsqlite3_session) + nDb + 1);
if( !pNew ) return SQLITE_NOMEM;
- memset(pNew, 0, sizeof(sqlite3_session));
+ memset(pNew, 0, sizeof(tdsqlite3_session));
pNew->db = db;
pNew->zDb = (char *)&pNew[1];
pNew->bEnable = 1;
@@ -176431,10 +205716,10 @@ SQLITE_API int sqlite3session_create(
/* Add the new session object to the linked list of session objects
** attached to database handle $db. Do this under the cover of the db
** handle mutex. */
- sqlite3_mutex_enter(sqlite3_db_mutex(db));
- pOld = (sqlite3_session*)sqlite3_preupdate_hook(db, xPreUpdate, (void*)pNew);
+ tdsqlite3_mutex_enter(tdsqlite3_db_mutex(db));
+ pOld = (tdsqlite3_session*)tdsqlite3_preupdate_hook(db, xPreUpdate, (void*)pNew);
pNew->pNext = pOld;
- sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ tdsqlite3_mutex_leave(tdsqlite3_db_mutex(db));
*ppSession = pNew;
return SQLITE_OK;
@@ -176456,49 +205741,50 @@ static void sessionDeleteTable(SessionTable *pList){
SessionChange *pNextChange;
for(p=pTab->apChange[i]; p; p=pNextChange){
pNextChange = p->pNext;
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
}
- sqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */
- sqlite3_free(pTab->apChange);
- sqlite3_free(pTab);
+ tdsqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */
+ tdsqlite3_free(pTab->apChange);
+ tdsqlite3_free(pTab);
}
}
/*
-** Delete a session object previously allocated using sqlite3session_create().
+** Delete a session object previously allocated using tdsqlite3session_create().
*/
-SQLITE_API void sqlite3session_delete(sqlite3_session *pSession){
- sqlite3 *db = pSession->db;
- sqlite3_session *pHead;
- sqlite3_session **pp;
+SQLITE_API void tdsqlite3session_delete(tdsqlite3_session *pSession){
+ tdsqlite3 *db = pSession->db;
+ tdsqlite3_session *pHead;
+ tdsqlite3_session **pp;
/* Unlink the session from the linked list of sessions attached to the
** database handle. Hold the db mutex while doing so. */
- sqlite3_mutex_enter(sqlite3_db_mutex(db));
- pHead = (sqlite3_session*)sqlite3_preupdate_hook(db, 0, 0);
+ tdsqlite3_mutex_enter(tdsqlite3_db_mutex(db));
+ pHead = (tdsqlite3_session*)tdsqlite3_preupdate_hook(db, 0, 0);
for(pp=&pHead; ALWAYS((*pp)!=0); pp=&((*pp)->pNext)){
if( (*pp)==pSession ){
*pp = (*pp)->pNext;
- if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void*)pHead);
+ if( pHead ) tdsqlite3_preupdate_hook(db, xPreUpdate, (void*)pHead);
break;
}
}
- sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ tdsqlite3_mutex_leave(tdsqlite3_db_mutex(db));
+ tdsqlite3ValueFree(pSession->pZeroBlob);
/* Delete all attached table objects. And the contents of their
** associated hash-tables. */
sessionDeleteTable(pSession->pTable);
/* Free the session object itself. */
- sqlite3_free(pSession);
+ tdsqlite3_free(pSession);
}
/*
** Set a table filter on a Session Object.
*/
-SQLITE_API void sqlite3session_table_filter(
- sqlite3_session *pSession,
+SQLITE_API void tdsqlite3session_table_filter(
+ tdsqlite3_session *pSession,
int(*xFilter)(void*, const char*),
void *pCtx /* First argument passed to xFilter */
){
@@ -176515,12 +205801,12 @@ SQLITE_API void sqlite3session_table_filter(
** not matter if the PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias)
** or not.
*/
-SQLITE_API int sqlite3session_attach(
- sqlite3_session *pSession, /* Session object */
+SQLITE_API int tdsqlite3session_attach(
+ tdsqlite3_session *pSession, /* Session object */
const char *zName /* Table name */
){
int rc = SQLITE_OK;
- sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_enter(tdsqlite3_db_mutex(pSession->db));
if( !zName ){
pSession->bAutoAttach = 1;
@@ -176530,14 +205816,14 @@ SQLITE_API int sqlite3session_attach(
/* First search for an existing entry. If one is found, this call is
** a no-op. Return early. */
- nName = sqlite3Strlen30(zName);
+ nName = tdsqlite3Strlen30(zName);
for(pTab=pSession->pTable; pTab; pTab=pTab->pNext){
- if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ) break;
+ if( 0==tdsqlite3_strnicmp(pTab->zName, zName, nName+1) ) break;
}
if( !pTab ){
/* Allocate new SessionTable object. */
- pTab = (SessionTable *)sqlite3_malloc(sizeof(SessionTable) + nName + 1);
+ pTab = (SessionTable *)tdsqlite3_malloc64(sizeof(SessionTable) + nName + 1);
if( !pTab ){
rc = SQLITE_NOMEM;
}else{
@@ -176556,26 +205842,26 @@ SQLITE_API int sqlite3session_attach(
}
}
- sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_leave(tdsqlite3_db_mutex(pSession->db));
return rc;
}
/*
** Ensure that there is room in the buffer to append nByte bytes of data.
-** If not, use sqlite3_realloc() to grow the buffer so that there is.
+** If not, use tdsqlite3_realloc() to grow the buffer so that there is.
**
** If successful, return zero. Otherwise, if an OOM condition is encountered,
** set *pRc to SQLITE_NOMEM and return non-zero.
*/
-static int sessionBufferGrow(SessionBuffer *p, int nByte, int *pRc){
- if( *pRc==SQLITE_OK && p->nAlloc-p->nBuf<nByte ){
+static int sessionBufferGrow(SessionBuffer *p, size_t nByte, int *pRc){
+ if( *pRc==SQLITE_OK && (size_t)(p->nAlloc-p->nBuf)<nByte ){
u8 *aNew;
- int nNew = p->nAlloc ? p->nAlloc : 128;
+ i64 nNew = p->nAlloc ? p->nAlloc : 128;
do {
nNew = nNew*2;
- }while( nNew<(p->nBuf+nByte) );
+ }while( (size_t)(nNew-p->nBuf)<nByte );
- aNew = (u8 *)sqlite3_realloc(p->aBuf, nNew);
+ aNew = (u8 *)tdsqlite3_realloc64(p->aBuf, nNew);
if( 0==aNew ){
*pRc = SQLITE_NOMEM;
}else{
@@ -176594,10 +205880,10 @@ static int sessionBufferGrow(SessionBuffer *p, int nByte, int *pRc){
** Otherwise, if an error occurs, *pRc is set to an SQLite error code
** before returning.
*/
-static void sessionAppendValue(SessionBuffer *p, sqlite3_value *pVal, int *pRc){
+static void sessionAppendValue(SessionBuffer *p, tdsqlite3_value *pVal, int *pRc){
int rc = *pRc;
if( rc==SQLITE_OK ){
- int nByte = 0;
+ tdsqlite3_int64 nByte = 0;
rc = sessionSerializeValue(0, pVal, &nByte);
sessionBufferGrow(p, nByte, &rc);
if( rc==SQLITE_OK ){
@@ -176648,7 +205934,7 @@ static void sessionAppendBlob(
int nBlob,
int *pRc
){
- if( 0==sessionBufferGrow(p, nBlob, pRc) ){
+ if( nBlob>0 && 0==sessionBufferGrow(p, nBlob, pRc) ){
memcpy(&p->aBuf[p->nBuf], aBlob, nBlob);
p->nBuf += nBlob;
}
@@ -176667,7 +205953,7 @@ static void sessionAppendStr(
const char *zStr,
int *pRc
){
- int nStr = sqlite3Strlen30(zStr);
+ int nStr = tdsqlite3Strlen30(zStr);
if( 0==sessionBufferGrow(p, nStr, pRc) ){
memcpy(&p->aBuf[p->nBuf], zStr, nStr);
p->nBuf += nStr;
@@ -176688,7 +205974,7 @@ static void sessionAppendInteger(
int *pRc /* IN/OUT: Error code */
){
char aBuf[24];
- sqlite3_snprintf(sizeof(aBuf)-1, aBuf, "%d", iVal);
+ tdsqlite3_snprintf(sizeof(aBuf)-1, aBuf, "%d", iVal);
sessionAppendStr(p, aBuf, pRc);
}
@@ -176706,7 +205992,7 @@ static void sessionAppendIdent(
const char *zStr, /* String to quote, escape and append */
int *pRc /* IN/OUT: Error code */
){
- int nStr = sqlite3Strlen30(zStr)*2 + 2 + 1;
+ int nStr = tdsqlite3Strlen30(zStr)*2 + 2 + 1;
if( 0==sessionBufferGrow(p, nStr, pRc) ){
char *zOut = (char *)&p->aBuf[p->nBuf];
const char *zIn = zStr;
@@ -176728,20 +206014,20 @@ static void sessionAppendIdent(
*/
static void sessionAppendCol(
SessionBuffer *p, /* Buffer to append to */
- sqlite3_stmt *pStmt, /* Handle pointing to row containing value */
+ tdsqlite3_stmt *pStmt, /* Handle pointing to row containing value */
int iCol, /* Column to read value from */
int *pRc /* IN/OUT: Error code */
){
if( *pRc==SQLITE_OK ){
- int eType = sqlite3_column_type(pStmt, iCol);
+ int eType = tdsqlite3_column_type(pStmt, iCol);
sessionAppendByte(p, (u8)eType, pRc);
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
- sqlite3_int64 i;
+ tdsqlite3_int64 i;
u8 aBuf[8];
if( eType==SQLITE_INTEGER ){
- i = sqlite3_column_int64(pStmt, iCol);
+ i = tdsqlite3_column_int64(pStmt, iCol);
}else{
- double r = sqlite3_column_double(pStmt, iCol);
+ double r = tdsqlite3_column_double(pStmt, iCol);
memcpy(&i, &r, 8);
}
sessionPutI64(aBuf, i);
@@ -176751,11 +206037,11 @@ static void sessionAppendCol(
u8 *z;
int nByte;
if( eType==SQLITE_BLOB ){
- z = (u8 *)sqlite3_column_blob(pStmt, iCol);
+ z = (u8 *)tdsqlite3_column_blob(pStmt, iCol);
}else{
- z = (u8 *)sqlite3_column_text(pStmt, iCol);
+ z = (u8 *)tdsqlite3_column_text(pStmt, iCol);
}
- nByte = sqlite3_column_bytes(pStmt, iCol);
+ nByte = tdsqlite3_column_bytes(pStmt, iCol);
if( z || (eType==SQLITE_BLOB && nByte==0) ){
sessionAppendVarint(p, nByte, pRc);
sessionAppendBlob(p, z, nByte, pRc);
@@ -176791,7 +206077,7 @@ static void sessionAppendCol(
static int sessionAppendUpdate(
SessionBuffer *pBuf, /* Buffer to append to */
int bPatchset, /* True for "patchset", 0 for "changeset" */
- sqlite3_stmt *pStmt, /* Statement handle pointing at new row */
+ tdsqlite3_stmt *pStmt, /* Statement handle pointing at new row */
SessionChange *p, /* Object containing old values */
u8 *abPK /* Boolean array - true for PK columns */
){
@@ -176804,14 +206090,14 @@ static int sessionAppendUpdate(
sessionAppendByte(pBuf, SQLITE_UPDATE, &rc);
sessionAppendByte(pBuf, p->bIndirect, &rc);
- for(i=0; i<sqlite3_column_count(pStmt); i++){
+ for(i=0; i<tdsqlite3_column_count(pStmt); i++){
int bChanged = 0;
int nAdvance;
int eType = *pCsr;
switch( eType ){
case SQLITE_NULL:
nAdvance = 1;
- if( sqlite3_column_type(pStmt, i)!=SQLITE_NULL ){
+ if( tdsqlite3_column_type(pStmt, i)!=SQLITE_NULL ){
bChanged = 1;
}
break;
@@ -176819,14 +206105,14 @@ static int sessionAppendUpdate(
case SQLITE_FLOAT:
case SQLITE_INTEGER: {
nAdvance = 9;
- if( eType==sqlite3_column_type(pStmt, i) ){
- sqlite3_int64 iVal = sessionGetI64(&pCsr[1]);
+ if( eType==tdsqlite3_column_type(pStmt, i) ){
+ tdsqlite3_int64 iVal = sessionGetI64(&pCsr[1]);
if( eType==SQLITE_INTEGER ){
- if( iVal==sqlite3_column_int64(pStmt, i) ) break;
+ if( iVal==tdsqlite3_column_int64(pStmt, i) ) break;
}else{
double dVal;
memcpy(&dVal, &iVal, 8);
- if( dVal==sqlite3_column_double(pStmt, i) ) break;
+ if( dVal==tdsqlite3_column_double(pStmt, i) ) break;
}
}
bChanged = 1;
@@ -176834,13 +206120,13 @@ static int sessionAppendUpdate(
}
default: {
- int nByte;
- int nHdr = 1 + sessionVarintGet(&pCsr[1], &nByte);
+ int n;
+ int nHdr = 1 + sessionVarintGet(&pCsr[1], &n);
assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
- nAdvance = nHdr + nByte;
- if( eType==sqlite3_column_type(pStmt, i)
- && nByte==sqlite3_column_bytes(pStmt, i)
- && 0==memcmp(&pCsr[nHdr], sqlite3_column_blob(pStmt, i), nByte)
+ nAdvance = nHdr + n;
+ if( eType==tdsqlite3_column_type(pStmt, i)
+ && n==tdsqlite3_column_bytes(pStmt, i)
+ && (n==0 || 0==memcmp(&pCsr[nHdr], tdsqlite3_column_blob(pStmt, i), n))
){
break;
}
@@ -176877,7 +206163,7 @@ static int sessionAppendUpdate(
}else{
sessionAppendBlob(pBuf, buf2.aBuf, buf2.nBuf, &rc);
}
- sqlite3_free(buf2.aBuf);
+ tdsqlite3_free(buf2.aBuf);
return rc;
}
@@ -176943,37 +206229,51 @@ static int sessionAppendDelete(
** SELECT * FROM zDb.zTab WHERE pk1 = ? AND pk2 = ? AND ...
*/
static int sessionSelectStmt(
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
const char *zDb, /* Database name */
const char *zTab, /* Table name */
int nCol, /* Number of columns in table */
const char **azCol, /* Names of table columns */
u8 *abPK, /* PRIMARY KEY array */
- sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */
+ tdsqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */
){
int rc = SQLITE_OK;
- int i;
- const char *zSep = "";
- SessionBuffer buf = {0, 0, 0};
+ char *zSql = 0;
+ int nSql = -1;
- sessionAppendStr(&buf, "SELECT * FROM ", &rc);
- sessionAppendIdent(&buf, zDb, &rc);
- sessionAppendStr(&buf, ".", &rc);
- sessionAppendIdent(&buf, zTab, &rc);
- sessionAppendStr(&buf, " WHERE ", &rc);
- for(i=0; i<nCol; i++){
- if( abPK[i] ){
- sessionAppendStr(&buf, zSep, &rc);
- sessionAppendIdent(&buf, azCol[i], &rc);
- sessionAppendStr(&buf, " = ?", &rc);
- sessionAppendInteger(&buf, i+1, &rc);
- zSep = " AND ";
+ if( 0==tdsqlite3_stricmp("sqlite_stat1", zTab) ){
+ zSql = tdsqlite3_mprintf(
+ "SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND "
+ "idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb
+ );
+ if( zSql==0 ) rc = SQLITE_NOMEM;
+ }else{
+ int i;
+ const char *zSep = "";
+ SessionBuffer buf = {0, 0, 0};
+
+ sessionAppendStr(&buf, "SELECT * FROM ", &rc);
+ sessionAppendIdent(&buf, zDb, &rc);
+ sessionAppendStr(&buf, ".", &rc);
+ sessionAppendIdent(&buf, zTab, &rc);
+ sessionAppendStr(&buf, " WHERE ", &rc);
+ for(i=0; i<nCol; i++){
+ if( abPK[i] ){
+ sessionAppendStr(&buf, zSep, &rc);
+ sessionAppendIdent(&buf, azCol[i], &rc);
+ sessionAppendStr(&buf, " IS ?", &rc);
+ sessionAppendInteger(&buf, i+1, &rc);
+ zSep = " AND ";
+ }
}
+ zSql = (char*)buf.aBuf;
+ nSql = buf.nBuf;
}
+
if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
+ rc = tdsqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0);
}
- sqlite3_free(buf.aBuf);
+ tdsqlite3_free(zSql);
return rc;
}
@@ -176986,7 +206286,7 @@ static int sessionSelectStmt(
** error code (e.g. SQLITE_NOMEM) otherwise.
*/
static int sessionSelectBind(
- sqlite3_stmt *pSelect, /* SELECT from sessionSelectStmt() */
+ tdsqlite3_stmt *pSelect, /* SELECT from sessionSelectStmt() */
int nCol, /* Number of columns in table */
u8 *abPK, /* PRIMARY KEY array */
SessionChange *pChange /* Change structure */
@@ -177007,7 +206307,7 @@ static int sessionSelectBind(
case SQLITE_INTEGER: {
if( abPK[i] ){
i64 iVal = sessionGetI64(a);
- rc = sqlite3_bind_int64(pSelect, i+1, iVal);
+ rc = tdsqlite3_bind_int64(pSelect, i+1, iVal);
}
a += 8;
break;
@@ -177018,7 +206318,7 @@ static int sessionSelectBind(
double rVal;
i64 iVal = sessionGetI64(a);
memcpy(&rVal, &iVal, 8);
- rc = sqlite3_bind_double(pSelect, i+1, rVal);
+ rc = tdsqlite3_bind_double(pSelect, i+1, rVal);
}
a += 8;
break;
@@ -177028,7 +206328,7 @@ static int sessionSelectBind(
int n;
a += sessionVarintGet(a, &n);
if( abPK[i] ){
- rc = sqlite3_bind_text(pSelect, i+1, (char *)a, n, SQLITE_TRANSIENT);
+ rc = tdsqlite3_bind_text(pSelect, i+1, (char *)a, n, SQLITE_TRANSIENT);
}
a += n;
break;
@@ -177039,7 +206339,7 @@ static int sessionSelectBind(
assert( eType==SQLITE_BLOB );
a += sessionVarintGet(a, &n);
if( abPK[i] ){
- rc = sqlite3_bind_blob(pSelect, i+1, a, n, SQLITE_TRANSIENT);
+ rc = tdsqlite3_bind_blob(pSelect, i+1, a, n, SQLITE_TRANSIENT);
}
a += n;
break;
@@ -177080,14 +206380,14 @@ static void sessionAppendTableHdr(
** to 0.
*/
static int sessionGenerateChangeset(
- sqlite3_session *pSession, /* Session object */
+ tdsqlite3_session *pSession, /* Session object */
int bPatchset, /* True for patchset, false for changeset */
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut, /* First argument for xOutput */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
){
- sqlite3 *db = pSession->db; /* Source database handle */
+ tdsqlite3 *db = pSession->db; /* Source database handle */
SessionTable *pTab; /* Used to iterate through attached tables */
SessionBuffer buf = {0,0,0}; /* Buffer in which to accumlate changeset */
int rc; /* Return code */
@@ -177095,7 +206395,7 @@ static int sessionGenerateChangeset(
assert( xOutput==0 || (pnChangeset==0 && ppChangeset==0 ) );
/* Zero the output variables in case an error occurs. If this session
- ** object is already in the error state (sqlite3_session.rc != SQLITE_OK),
+ ** object is already in the error state (tdsqlite3_session.rc != SQLITE_OK),
** this call will be a no-op. */
if( xOutput==0 ){
*pnChangeset = 0;
@@ -177103,10 +206403,10 @@ static int sessionGenerateChangeset(
}
if( pSession->rc ) return pSession->rc;
- rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0);
+ rc = tdsqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0);
if( rc!=SQLITE_OK ) return rc;
- sqlite3_mutex_enter(sqlite3_db_mutex(db));
+ tdsqlite3_mutex_enter(tdsqlite3_db_mutex(db));
for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
if( pTab->nEntry ){
@@ -177115,7 +206415,7 @@ static int sessionGenerateChangeset(
u8 *abPK; /* Primary key array */
const char **azCol = 0; /* Table columns */
int i; /* Used to iterate through hash buckets */
- sqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */
+ tdsqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */
int nRewind = buf.nBuf; /* Initial size of write buffer */
int nNoop; /* Size of buffer after writing tbl header */
@@ -177141,7 +206441,7 @@ static int sessionGenerateChangeset(
for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
rc = sessionSelectBind(pSel, nCol, abPK, p);
if( rc!=SQLITE_OK ) continue;
- if( sqlite3_step(pSel)==SQLITE_ROW ){
+ if( tdsqlite3_step(pSel)==SQLITE_ROW ){
if( p->op==SQLITE_INSERT ){
int iCol;
sessionAppendByte(&buf, SQLITE_INSERT, &rc);
@@ -177156,15 +206456,15 @@ static int sessionGenerateChangeset(
rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
}
if( rc==SQLITE_OK ){
- rc = sqlite3_reset(pSel);
+ rc = tdsqlite3_reset(pSel);
}
- /* If the buffer is now larger than SESSIONS_STRM_CHUNK_SIZE, pass
+ /* If the buffer is now larger than sessions_strm_chunk_size, pass
** its contents to the xOutput() callback. */
if( xOutput
&& rc==SQLITE_OK
&& buf.nBuf>nNoop
- && buf.nBuf>SESSIONS_STRM_CHUNK_SIZE
+ && buf.nBuf>sessions_strm_chunk_size
){
rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf);
nNoop = -1;
@@ -177174,11 +206474,11 @@ static int sessionGenerateChangeset(
}
}
- sqlite3_finalize(pSel);
+ tdsqlite3_finalize(pSel);
if( buf.nBuf==nNoop ){
buf.nBuf = nRewind;
}
- sqlite3_free((char*)azCol); /* cast works around VC++ bug */
+ tdsqlite3_free((char*)azCol); /* cast works around VC++ bug */
}
}
@@ -177192,9 +206492,9 @@ static int sessionGenerateChangeset(
}
}
- sqlite3_free(buf.aBuf);
- sqlite3_exec(db, "RELEASE changeset", 0, 0, 0);
- sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ tdsqlite3_free(buf.aBuf);
+ tdsqlite3_exec(db, "RELEASE changeset", 0, 0, 0);
+ tdsqlite3_mutex_leave(tdsqlite3_db_mutex(db));
return rc;
}
@@ -177203,10 +206503,10 @@ static int sessionGenerateChangeset(
** session object passed as the first argument.
**
** It is the responsibility of the caller to eventually free the buffer
-** using sqlite3_free().
+** using tdsqlite3_free().
*/
-SQLITE_API int sqlite3session_changeset(
- sqlite3_session *pSession, /* Session object */
+SQLITE_API int tdsqlite3session_changeset(
+ tdsqlite3_session *pSession, /* Session object */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
){
@@ -177214,10 +206514,10 @@ SQLITE_API int sqlite3session_changeset(
}
/*
-** Streaming version of sqlite3session_changeset().
+** Streaming version of tdsqlite3session_changeset().
*/
-SQLITE_API int sqlite3session_changeset_strm(
- sqlite3_session *pSession,
+SQLITE_API int tdsqlite3session_changeset_strm(
+ tdsqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
){
@@ -177225,10 +206525,10 @@ SQLITE_API int sqlite3session_changeset_strm(
}
/*
-** Streaming version of sqlite3session_patchset().
+** Streaming version of tdsqlite3session_patchset().
*/
-SQLITE_API int sqlite3session_patchset_strm(
- sqlite3_session *pSession,
+SQLITE_API int tdsqlite3session_patchset_strm(
+ tdsqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
){
@@ -177240,10 +206540,10 @@ SQLITE_API int sqlite3session_patchset_strm(
** session object passed as the first argument.
**
** It is the responsibility of the caller to eventually free the buffer
-** using sqlite3_free().
+** using tdsqlite3_free().
*/
-SQLITE_API int sqlite3session_patchset(
- sqlite3_session *pSession, /* Session object */
+SQLITE_API int tdsqlite3session_patchset(
+ tdsqlite3_session *pSession, /* Session object */
int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */
void **ppPatchset /* OUT: Buffer containing changeset */
){
@@ -177253,28 +206553,28 @@ SQLITE_API int sqlite3session_patchset(
/*
** Enable or disable the session object passed as the first argument.
*/
-SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable){
+SQLITE_API int tdsqlite3session_enable(tdsqlite3_session *pSession, int bEnable){
int ret;
- sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_enter(tdsqlite3_db_mutex(pSession->db));
if( bEnable>=0 ){
pSession->bEnable = bEnable;
}
ret = pSession->bEnable;
- sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_leave(tdsqlite3_db_mutex(pSession->db));
return ret;
}
/*
** Enable or disable the session object passed as the first argument.
*/
-SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect){
+SQLITE_API int tdsqlite3session_indirect(tdsqlite3_session *pSession, int bIndirect){
int ret;
- sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_enter(tdsqlite3_db_mutex(pSession->db));
if( bIndirect>=0 ){
pSession->bIndirect = bIndirect;
}
ret = pSession->bIndirect;
- sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_leave(tdsqlite3_db_mutex(pSession->db));
return ret;
}
@@ -177282,30 +206582,31 @@ SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect)
** Return true if there have been no changes to monitored tables recorded
** by the session object passed as the only argument.
*/
-SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession){
+SQLITE_API int tdsqlite3session_isempty(tdsqlite3_session *pSession){
int ret = 0;
SessionTable *pTab;
- sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_enter(tdsqlite3_db_mutex(pSession->db));
for(pTab=pSession->pTable; pTab && ret==0; pTab=pTab->pNext){
ret = (pTab->nEntry>0);
}
- sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
+ tdsqlite3_mutex_leave(tdsqlite3_db_mutex(pSession->db));
return (ret==0);
}
/*
-** Do the work for either sqlite3changeset_start() or start_strm().
+** Do the work for either tdsqlite3changeset_start() or start_strm().
*/
static int sessionChangesetStart(
- sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
+ tdsqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int nChangeset, /* Size of buffer pChangeset in bytes */
- void *pChangeset /* Pointer to buffer containing changeset */
+ void *pChangeset, /* Pointer to buffer containing changeset */
+ int bInvert /* True to invert changeset */
){
- sqlite3_changeset_iter *pRet; /* Iterator to return */
+ tdsqlite3_changeset_iter *pRet; /* Iterator to return */
int nByte; /* Number of bytes to allocate for iterator */
assert( xInput==0 || (pChangeset==0 && nChangeset==0) );
@@ -177314,15 +206615,16 @@ static int sessionChangesetStart(
*pp = 0;
/* Allocate and initialize the iterator structure. */
- nByte = sizeof(sqlite3_changeset_iter);
- pRet = (sqlite3_changeset_iter *)sqlite3_malloc(nByte);
+ nByte = sizeof(tdsqlite3_changeset_iter);
+ pRet = (tdsqlite3_changeset_iter *)tdsqlite3_malloc(nByte);
if( !pRet ) return SQLITE_NOMEM;
- memset(pRet, 0, sizeof(sqlite3_changeset_iter));
+ memset(pRet, 0, sizeof(tdsqlite3_changeset_iter));
pRet->in.aData = (u8 *)pChangeset;
pRet->in.nData = nChangeset;
pRet->in.xInput = xInput;
pRet->in.pIn = pIn;
pRet->in.bEof = (xInput ? 0 : 1);
+ pRet->bInvert = bInvert;
/* Populate the output variable and return success. */
*pp = pRet;
@@ -177332,23 +206634,41 @@ static int sessionChangesetStart(
/*
** Create an iterator used to iterate through the contents of a changeset.
*/
-SQLITE_API int sqlite3changeset_start(
- sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
+SQLITE_API int tdsqlite3changeset_start(
+ tdsqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
int nChangeset, /* Size of buffer pChangeset in bytes */
void *pChangeset /* Pointer to buffer containing changeset */
){
- return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset);
+ return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0);
+}
+SQLITE_API int tdsqlite3changeset_start_v2(
+ tdsqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
+ int nChangeset, /* Size of buffer pChangeset in bytes */
+ void *pChangeset, /* Pointer to buffer containing changeset */
+ int flags
+){
+ int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT);
+ return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert);
}
/*
-** Streaming version of sqlite3changeset_start().
+** Streaming version of tdsqlite3changeset_start().
*/
-SQLITE_API int sqlite3changeset_start_strm(
- sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
+SQLITE_API int tdsqlite3changeset_start_strm(
+ tdsqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
){
- return sessionChangesetStart(pp, xInput, pIn, 0, 0);
+ return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0);
+}
+SQLITE_API int tdsqlite3changeset_start_v2_strm(
+ tdsqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int flags
+){
+ int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT);
+ return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert);
}
/*
@@ -177356,7 +206676,7 @@ SQLITE_API int sqlite3changeset_start_strm(
** object and the buffer is full, discard some data to free up space.
*/
static void sessionDiscardData(SessionInput *pIn){
- if( pIn->bEof && pIn->xInput && pIn->iNext>=SESSIONS_STRM_CHUNK_SIZE ){
+ if( pIn->xInput && pIn->iNext>=sessions_strm_chunk_size ){
int nMove = pIn->buf.nBuf - pIn->iNext;
assert( nMove>=0 );
if( nMove>0 ){
@@ -177379,7 +206699,7 @@ static int sessionInputBuffer(SessionInput *pIn, int nByte){
int rc = SQLITE_OK;
if( pIn->xInput ){
while( !pIn->bEof && (pIn->iNext+nByte)>=pIn->nData && rc==SQLITE_OK ){
- int nNew = SESSIONS_STRM_CHUNK_SIZE;
+ int nNew = sessions_strm_chunk_size;
if( pIn->bNoDiscard==0 ) sessionDiscardData(pIn);
if( SQLITE_OK==sessionBufferGrow(&pIn->buf, nNew, &rc) ){
@@ -177424,25 +206744,25 @@ static void sessionSkipRecord(
}
/*
-** This function sets the value of the sqlite3_value object passed as the
+** This function sets the value of the tdsqlite3_value object passed as the
** first argument to a copy of the string or blob held in the aData[]
** buffer. SQLITE_OK is returned if successful, or SQLITE_NOMEM if an OOM
** error occurs.
*/
static int sessionValueSetStr(
- sqlite3_value *pVal, /* Set the value of this object */
+ tdsqlite3_value *pVal, /* Set the value of this object */
u8 *aData, /* Buffer containing string or blob data */
int nData, /* Size of buffer aData[] in bytes */
u8 enc /* String encoding (0 for blobs) */
){
/* In theory this code could just pass SQLITE_TRANSIENT as the final
- ** argument to sqlite3ValueSetStr() and have the copy created
+ ** argument to tdsqlite3ValueSetStr() and have the copy created
** automatically. But doing so makes it difficult to detect any OOM
** error. Hence the code to create the copy externally. */
- u8 *aCopy = sqlite3_malloc(nData+1);
+ u8 *aCopy = tdsqlite3_malloc64((tdsqlite3_int64)nData+1);
if( aCopy==0 ) return SQLITE_NOMEM;
memcpy(aCopy, aData, nData);
- sqlite3ValueSetStr(pVal, nData, (char*)aCopy, enc, sqlite3_free);
+ tdsqlite3ValueSetStr(pVal, nData, (char*)aCopy, enc, tdsqlite3_free);
return SQLITE_OK;
}
@@ -177458,14 +206778,14 @@ static int sessionValueSetStr(
** (in other words, it is a patchset DELETE record).
**
** If successful, each element of the apOut[] array (allocated by the caller)
-** is set to point to an sqlite3_value object containing the value read
+** is set to point to an tdsqlite3_value object containing the value read
** from the corresponding position in the record. If that value is not
** included in the record (i.e. because the record is part of an UPDATE change
** and the field was not modified), the corresponding element of apOut[] is
** set to NULL.
**
** It is the responsibility of the caller to free all sqlite_value structures
-** using sqlite3_free().
+** using tdsqlite3_free().
**
** If an error occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
** The apOut[] array may have been partially populated in this case.
@@ -177474,7 +206794,7 @@ static int sessionReadRecord(
SessionInput *pIn, /* Input data */
int nCol, /* Number of values in record */
u8 *abPK, /* Array of primary key flags, or NULL */
- sqlite3_value **apOut /* Write values to this array */
+ tdsqlite3_value **apOut /* Write values to this array */
){
int i; /* Used to iterate through columns */
int rc = SQLITE_OK;
@@ -177484,13 +206804,16 @@ static int sessionReadRecord(
if( abPK && abPK[i]==0 ) continue;
rc = sessionInputBuffer(pIn, 9);
if( rc==SQLITE_OK ){
- eType = pIn->aData[pIn->iNext++];
- }
-
- assert( apOut[i]==0 );
- if( eType ){
- apOut[i] = sqlite3ValueNew(0);
- if( !apOut[i] ) rc = SQLITE_NOMEM;
+ if( pIn->iNext>=pIn->nData ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ eType = pIn->aData[pIn->iNext++];
+ assert( apOut[i]==0 );
+ if( eType ){
+ apOut[i] = tdsqlite3ValueNew(0);
+ if( !apOut[i] ) rc = SQLITE_NOMEM;
+ }
+ }
}
if( rc==SQLITE_OK ){
@@ -177500,19 +206823,23 @@ static int sessionReadRecord(
pIn->iNext += sessionVarintGet(aVal, &nByte);
rc = sessionInputBuffer(pIn, nByte);
if( rc==SQLITE_OK ){
- u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
- rc = sessionValueSetStr(apOut[i],&pIn->aData[pIn->iNext],nByte,enc);
+ if( nByte<0 || nByte>pIn->nData-pIn->iNext ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
+ rc = sessionValueSetStr(apOut[i],&pIn->aData[pIn->iNext],nByte,enc);
+ pIn->iNext += nByte;
+ }
}
- pIn->iNext += nByte;
}
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
- sqlite3_int64 v = sessionGetI64(aVal);
+ tdsqlite3_int64 v = sessionGetI64(aVal);
if( eType==SQLITE_INTEGER ){
- sqlite3VdbeMemSetInt64(apOut[i], v);
+ tdsqlite3VdbeMemSetInt64(apOut[i], v);
}else{
double d;
memcpy(&d, &v, 8);
- sqlite3VdbeMemSetDouble(apOut[i], d);
+ tdsqlite3VdbeMemSetDouble(apOut[i], d);
}
pIn->iNext += 8;
}
@@ -177543,8 +206870,19 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){
rc = sessionInputBuffer(pIn, 9);
if( rc==SQLITE_OK ){
nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol);
- rc = sessionInputBuffer(pIn, nRead+nCol+100);
- nRead += nCol;
+ /* The hard upper limit for the number of columns in an SQLite
+ ** database table is, according to sqliteLimit.h, 32676. So
+ ** consider any table-header that purports to have more than 65536
+ ** columns to be corrupt. This is convenient because otherwise,
+ ** if the (nCol>65536) condition below were omitted, a sufficiently
+ ** large value for nCol may cause nRead to wrap around and become
+ ** negative. Leading to a crash. */
+ if( nCol<0 || nCol>65536 ){
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ rc = sessionInputBuffer(pIn, nRead+nCol+100);
+ nRead += nCol;
+ }
}
while( rc==SQLITE_OK ){
@@ -177611,7 +206949,7 @@ static int sessionChangesetBufferRecord(
** is returned and the final values of the various fields enumerated above
** are undefined.
*/
-static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
+static int sessionChangesetReadTblhdr(tdsqlite3_changeset_iter *p){
int rc;
int nCopy;
assert( p->rc==SQLITE_OK );
@@ -177621,21 +206959,25 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
int nByte;
int nVarint;
nVarint = sessionVarintGet(&p->in.aData[p->in.iNext], &p->nCol);
- nCopy -= nVarint;
- p->in.iNext += nVarint;
- nByte = p->nCol * sizeof(sqlite3_value*) * 2 + nCopy;
- p->tblhdr.nBuf = 0;
- sessionBufferGrow(&p->tblhdr, nByte, &rc);
+ if( p->nCol>0 ){
+ nCopy -= nVarint;
+ p->in.iNext += nVarint;
+ nByte = p->nCol * sizeof(tdsqlite3_value*) * 2 + nCopy;
+ p->tblhdr.nBuf = 0;
+ sessionBufferGrow(&p->tblhdr, nByte, &rc);
+ }else{
+ rc = SQLITE_CORRUPT_BKPT;
+ }
}
if( rc==SQLITE_OK ){
- int iPK = sizeof(sqlite3_value*)*p->nCol*2;
+ size_t iPK = sizeof(tdsqlite3_value*)*p->nCol*2;
memset(p->tblhdr.aBuf, 0, iPK);
memcpy(&p->tblhdr.aBuf[iPK], &p->in.aData[p->in.iNext], nCopy);
p->in.iNext += nCopy;
}
- p->apValue = (sqlite3_value**)p->tblhdr.aBuf;
+ p->apValue = (tdsqlite3_value**)p->tblhdr.aBuf;
p->abPK = (u8*)&p->apValue[p->nCol*2];
p->zTab = (char*)&p->abPK[p->nCol];
return (p->rc = rc);
@@ -177645,8 +206987,8 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
** Advance the changeset iterator to the next change.
**
** If both paRec and pnRec are NULL, then this function works like the public
-** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the
-** sqlite3changeset_new() and old() APIs may be used to query for values.
+** API tdsqlite3changeset_next(). If SQLITE_ROW is returned, then the
+** tdsqlite3changeset_new() and old() APIs may be used to query for values.
**
** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change
** record is written to *paRec before returning and the number of bytes in
@@ -177658,9 +207000,10 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
** changes in the changeset.
*/
static int sessionChangesetNext(
- sqlite3_changeset_iter *p, /* Changeset iterator */
+ tdsqlite3_changeset_iter *p, /* Changeset iterator */
u8 **paRec, /* If non-NULL, store record pointer here */
- int *pnRec /* If non-NULL, store size of record here */
+ int *pnRec, /* If non-NULL, store size of record here */
+ int *pbNew /* If non-NULL, true if new table */
){
int i;
u8 op;
@@ -177673,9 +207016,9 @@ static int sessionChangesetNext(
/* Free the current contents of p->apValue[], if any. */
if( p->apValue ){
for(i=0; i<p->nCol*2; i++){
- sqlite3ValueFree(p->apValue[i]);
+ tdsqlite3ValueFree(p->apValue[i]);
}
- memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
+ memset(p->apValue, 0, sizeof(tdsqlite3_value*)*p->nCol*2);
}
/* Make sure the buffer contains at least 10 bytes of input data, or all
@@ -177694,14 +207037,23 @@ static int sessionChangesetNext(
p->in.iCurrent = p->in.iNext;
op = p->in.aData[p->in.iNext++];
- if( op=='T' || op=='P' ){
+ while( op=='T' || op=='P' ){
+ if( pbNew ) *pbNew = 1;
p->bPatchset = (op=='P');
if( sessionChangesetReadTblhdr(p) ) return p->rc;
if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc;
p->in.iCurrent = p->in.iNext;
+ if( p->in.iNext>=p->in.nData ) return SQLITE_DONE;
op = p->in.aData[p->in.iNext++];
}
+ if( p->zTab==0 || (p->bPatchset && p->bInvert) ){
+ /* The first record in the changeset is not a table header. Must be a
+ ** corrupt changeset. */
+ assert( p->in.iNext==1 || p->zTab );
+ return (p->rc = SQLITE_CORRUPT_BKPT);
+ }
+
p->op = op;
p->bIndirect = p->in.aData[p->in.iNext++];
if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
@@ -177723,33 +207075,39 @@ static int sessionChangesetNext(
*paRec = &p->in.aData[p->in.iNext];
p->in.iNext += *pnRec;
}else{
+ tdsqlite3_value **apOld = (p->bInvert ? &p->apValue[p->nCol] : p->apValue);
+ tdsqlite3_value **apNew = (p->bInvert ? p->apValue : &p->apValue[p->nCol]);
/* If this is an UPDATE or DELETE, read the old.* record. */
if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){
u8 *abPK = p->bPatchset ? p->abPK : 0;
- p->rc = sessionReadRecord(&p->in, p->nCol, abPK, p->apValue);
+ p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld);
if( p->rc!=SQLITE_OK ) return p->rc;
}
/* If this is an INSERT or UPDATE, read the new.* record. */
if( p->op!=SQLITE_DELETE ){
- p->rc = sessionReadRecord(&p->in, p->nCol, 0, &p->apValue[p->nCol]);
+ p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew);
if( p->rc!=SQLITE_OK ) return p->rc;
}
- if( p->bPatchset && p->op==SQLITE_UPDATE ){
+ if( (p->bPatchset || p->bInvert) && p->op==SQLITE_UPDATE ){
/* If this is an UPDATE that is part of a patchset, then all PK and
** modified fields are present in the new.* record. The old.* record
** is currently completely empty. This block shifts the PK fields from
** new.* to old.*, to accommodate the code that reads these arrays. */
for(i=0; i<p->nCol; i++){
- assert( p->apValue[i]==0 );
- assert( p->abPK[i]==0 || p->apValue[i+p->nCol] );
+ assert( p->bPatchset==0 || p->apValue[i]==0 );
if( p->abPK[i] ){
+ assert( p->apValue[i]==0 );
p->apValue[i] = p->apValue[i+p->nCol];
+ if( p->apValue[i]==0 ) return (p->rc = SQLITE_CORRUPT_BKPT);
p->apValue[i+p->nCol] = 0;
}
}
+ }else if( p->bInvert ){
+ if( p->op==SQLITE_INSERT ) p->op = SQLITE_DELETE;
+ else if( p->op==SQLITE_DELETE ) p->op = SQLITE_INSERT;
}
}
@@ -177757,15 +207115,15 @@ static int sessionChangesetNext(
}
/*
-** Advance an iterator created by sqlite3changeset_start() to the next
+** Advance an iterator created by tdsqlite3changeset_start() to the next
** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE
** or SQLITE_CORRUPT.
**
** This function may not be called on iterators passed to a conflict handler
** callback by changeset_apply().
*/
-SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *p){
- return sessionChangesetNext(p, 0, 0);
+SQLITE_API int tdsqlite3changeset_next(tdsqlite3_changeset_iter *p){
+ return sessionChangesetNext(p, 0, 0, 0);
}
/*
@@ -177773,8 +207131,8 @@ SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *p){
** from a changeset iterator. It may only be called after changeset_next()
** has returned SQLITE_ROW.
*/
-SQLITE_API int sqlite3changeset_op(
- sqlite3_changeset_iter *pIter, /* Iterator handle */
+SQLITE_API int tdsqlite3changeset_op(
+ tdsqlite3_changeset_iter *pIter, /* Iterator handle */
const char **pzTab, /* OUT: Pointer to table name */
int *pnCol, /* OUT: Number of columns in table */
int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */
@@ -177793,8 +207151,8 @@ SQLITE_API int sqlite3changeset_op(
** to. This function may only be called after changeset_next() returns
** SQLITE_ROW.
*/
-SQLITE_API int sqlite3changeset_pk(
- sqlite3_changeset_iter *pIter, /* Iterator object */
+SQLITE_API int tdsqlite3changeset_pk(
+ tdsqlite3_changeset_iter *pIter, /* Iterator object */
unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */
int *pnCol /* OUT: Number of entries in output array */
){
@@ -177805,10 +207163,10 @@ SQLITE_API int sqlite3changeset_pk(
/*
** This function may only be called while the iterator is pointing to an
-** SQLITE_UPDATE or SQLITE_DELETE change (see sqlite3changeset_op()).
+** SQLITE_UPDATE or SQLITE_DELETE change (see tdsqlite3changeset_op()).
** Otherwise, SQLITE_MISUSE is returned.
**
-** It sets *ppValue to point to an sqlite3_value structure containing the
+** It sets *ppValue to point to an tdsqlite3_value structure containing the
** iVal'th value in the old.* record. Or, if that particular value is not
** included in the record (because the change is an UPDATE and the field
** was not modified and is not a PK column), set *ppValue to NULL.
@@ -177816,10 +207174,10 @@ SQLITE_API int sqlite3changeset_pk(
** If value iVal is out-of-range, SQLITE_RANGE is returned and *ppValue is
** not modified. Otherwise, SQLITE_OK.
*/
-SQLITE_API int sqlite3changeset_old(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_old(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Index of old.* value to retrieve */
- sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
+ tdsqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
){
if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_DELETE ){
return SQLITE_MISUSE;
@@ -177833,10 +207191,10 @@ SQLITE_API int sqlite3changeset_old(
/*
** This function may only be called while the iterator is pointing to an
-** SQLITE_UPDATE or SQLITE_INSERT change (see sqlite3changeset_op()).
+** SQLITE_UPDATE or SQLITE_INSERT change (see tdsqlite3changeset_op()).
** Otherwise, SQLITE_MISUSE is returned.
**
-** It sets *ppValue to point to an sqlite3_value structure containing the
+** It sets *ppValue to point to an tdsqlite3_value structure containing the
** iVal'th value in the new.* record. Or, if that particular value is not
** included in the record (because the change is an UPDATE and the field
** was not modified), set *ppValue to NULL.
@@ -177844,10 +207202,10 @@ SQLITE_API int sqlite3changeset_old(
** If value iVal is out-of-range, SQLITE_RANGE is returned and *ppValue is
** not modified. Otherwise, SQLITE_OK.
*/
-SQLITE_API int sqlite3changeset_new(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_new(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Index of new.* value to retrieve */
- sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
+ tdsqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
){
if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_INSERT ){
return SQLITE_MISUSE;
@@ -177861,7 +207219,7 @@ SQLITE_API int sqlite3changeset_new(
/*
** The following two macros are used internally. They are similar to the
-** sqlite3changeset_new() and sqlite3changeset_old() functions, except that
+** tdsqlite3changeset_new() and tdsqlite3changeset_old() functions, except that
** they omit all error checking and return a pointer to the requested value.
*/
#define sessionChangesetNew(pIter, iVal) (pIter)->apValue[(pIter)->nCol+(iVal)]
@@ -177872,24 +207230,24 @@ SQLITE_API int sqlite3changeset_new(
** passed to an SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT
** conflict-handler function. Otherwise, SQLITE_MISUSE is returned.
**
-** If successful, *ppValue is set to point to an sqlite3_value structure
+** If successful, *ppValue is set to point to an tdsqlite3_value structure
** containing the iVal'th value of the conflicting record.
**
** If value iVal is out-of-range or some other error occurs, an SQLite error
** code is returned. Otherwise, SQLITE_OK.
*/
-SQLITE_API int sqlite3changeset_conflict(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_conflict(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Index of conflict record value to fetch */
- sqlite3_value **ppValue /* OUT: Value from conflicting row */
+ tdsqlite3_value **ppValue /* OUT: Value from conflicting row */
){
if( !pIter->pConflict ){
return SQLITE_MISUSE;
}
- if( iVal<0 || iVal>=sqlite3_column_count(pIter->pConflict) ){
+ if( iVal<0 || iVal>=pIter->nCol ){
return SQLITE_RANGE;
}
- *ppValue = sqlite3_column_value(pIter->pConflict, iVal);
+ *ppValue = tdsqlite3_column_value(pIter->pConflict, iVal);
return SQLITE_OK;
}
@@ -177901,8 +207259,8 @@ SQLITE_API int sqlite3changeset_conflict(
**
** In all other cases this function returns SQLITE_MISUSE.
*/
-SQLITE_API int sqlite3changeset_fk_conflicts(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_fk_conflicts(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int *pnOut /* OUT: Number of FK violations */
){
if( pIter->pConflict || pIter->apValue ){
@@ -177914,22 +207272,22 @@ SQLITE_API int sqlite3changeset_fk_conflicts(
/*
-** Finalize an iterator allocated with sqlite3changeset_start().
+** Finalize an iterator allocated with tdsqlite3changeset_start().
**
** This function may not be called on iterators passed to a conflict handler
** callback by changeset_apply().
*/
-SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *p){
+SQLITE_API int tdsqlite3changeset_finalize(tdsqlite3_changeset_iter *p){
int rc = SQLITE_OK;
if( p ){
int i; /* Used to iterate through p->apValue[] */
rc = p->rc;
if( p->apValue ){
- for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]);
+ for(i=0; i<p->nCol*2; i++) tdsqlite3ValueFree(p->apValue[i]);
}
- sqlite3_free(p->tblhdr.aBuf);
- sqlite3_free(p->in.buf.aBuf);
- sqlite3_free(p);
+ tdsqlite3_free(p->tblhdr.aBuf);
+ tdsqlite3_free(p->in.buf.aBuf);
+ tdsqlite3_free(p);
}
return rc;
}
@@ -177945,7 +207303,7 @@ static int sessionChangesetInvert(
SessionBuffer sOut; /* Output buffer */
int nCol = 0; /* Number of cols in current table */
u8 *abPK = 0; /* PK array for current table */
- sqlite3_value **apVal = 0; /* Space for values for UPDATE inversion */
+ tdsqlite3_value **apVal = 0; /* Space for values for UPDATE inversion */
SessionBuffer sPK = {0, 0, 0}; /* PK array for current table */
/* Initialize the output buffer */
@@ -177988,7 +207346,7 @@ static int sessionChangesetInvert(
if( rc ) goto finished_invert;
pInput->iNext += nByte;
- sqlite3_free(apVal);
+ tdsqlite3_free(apVal);
apVal = 0;
abPK = sPK.aBuf;
break;
@@ -178014,7 +207372,7 @@ static int sessionChangesetInvert(
int iCol;
if( 0==apVal ){
- apVal = (sqlite3_value **)sqlite3_malloc(sizeof(apVal[0])*nCol*2);
+ apVal = (tdsqlite3_value **)tdsqlite3_malloc64(sizeof(apVal[0])*nCol*2);
if( 0==apVal ){
rc = SQLITE_NOMEM;
goto finished_invert;
@@ -178037,7 +207395,7 @@ static int sessionChangesetInvert(
** original old.* record, and the other values from the original
** new.* record. */
for(iCol=0; iCol<nCol; iCol++){
- sqlite3_value *pVal = apVal[iCol + (abPK[iCol] ? 0 : nCol)];
+ tdsqlite3_value *pVal = apVal[iCol + (abPK[iCol] ? 0 : nCol)];
sessionAppendValue(&sOut, pVal, &rc);
}
@@ -178045,12 +207403,12 @@ static int sessionChangesetInvert(
** from the original old.* record, except for the PK columns, which
** are set to "undefined". */
for(iCol=0; iCol<nCol; iCol++){
- sqlite3_value *pVal = (abPK[iCol] ? 0 : apVal[iCol]);
+ tdsqlite3_value *pVal = (abPK[iCol] ? 0 : apVal[iCol]);
sessionAppendValue(&sOut, pVal, &rc);
}
for(iCol=0; iCol<nCol*2; iCol++){
- sqlite3ValueFree(apVal[iCol]);
+ tdsqlite3ValueFree(apVal[iCol]);
}
memset(apVal, 0, sizeof(apVal[0])*nCol*2);
if( rc!=SQLITE_OK ){
@@ -178066,7 +207424,7 @@ static int sessionChangesetInvert(
}
assert( rc==SQLITE_OK );
- if( xOutput && sOut.nBuf>=SESSIONS_STRM_CHUNK_SIZE ){
+ if( xOutput && sOut.nBuf>=sessions_strm_chunk_size ){
rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
sOut.nBuf = 0;
if( rc!=SQLITE_OK ) goto finished_invert;
@@ -178083,9 +207441,9 @@ static int sessionChangesetInvert(
}
finished_invert:
- sqlite3_free(sOut.aBuf);
- sqlite3_free(apVal);
- sqlite3_free(sPK.aBuf);
+ tdsqlite3_free(sOut.aBuf);
+ tdsqlite3_free(apVal);
+ tdsqlite3_free(sPK.aBuf);
return rc;
}
@@ -178093,7 +207451,7 @@ static int sessionChangesetInvert(
/*
** Invert a changeset object.
*/
-SQLITE_API int sqlite3changeset_invert(
+SQLITE_API int tdsqlite3changeset_invert(
int nChangeset, /* Number of bytes in input */
const void *pChangeset, /* Input changeset */
int *pnInverted, /* OUT: Number of bytes in output changeset */
@@ -178110,9 +207468,9 @@ SQLITE_API int sqlite3changeset_invert(
}
/*
-** Streaming version of sqlite3changeset_invert().
+** Streaming version of tdsqlite3changeset_invert().
*/
-SQLITE_API int sqlite3changeset_invert_strm(
+SQLITE_API int tdsqlite3changeset_invert_strm(
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int (*xOutput)(void *pOut, const void *pData, int nData),
@@ -178127,23 +207485,26 @@ SQLITE_API int sqlite3changeset_invert_strm(
sInput.pIn = pIn;
rc = sessionChangesetInvert(&sInput, xOutput, pOut, 0, 0);
- sqlite3_free(sInput.buf.aBuf);
+ tdsqlite3_free(sInput.buf.aBuf);
return rc;
}
typedef struct SessionApplyCtx SessionApplyCtx;
struct SessionApplyCtx {
- sqlite3 *db;
- sqlite3_stmt *pDelete; /* DELETE statement */
- sqlite3_stmt *pUpdate; /* UPDATE statement */
- sqlite3_stmt *pInsert; /* INSERT statement */
- sqlite3_stmt *pSelect; /* SELECT statement */
+ tdsqlite3 *db;
+ tdsqlite3_stmt *pDelete; /* DELETE statement */
+ tdsqlite3_stmt *pUpdate; /* UPDATE statement */
+ tdsqlite3_stmt *pInsert; /* INSERT statement */
+ tdsqlite3_stmt *pSelect; /* SELECT statement */
int nCol; /* Size of azCol[] and abPK[] arrays */
const char **azCol; /* Array of column names */
u8 *abPK; /* Boolean array - true if column is in PK */
-
+ int bStat1; /* True if table is sqlite_stat1 */
int bDeferConstraints; /* True to defer constraints */
SessionBuffer constraints; /* Deferred constraints are stored here */
+ SessionBuffer rebase; /* Rebase information (if any) here */
+ u8 bRebaseStarted; /* If table header is already in rebase */
+ u8 bRebase; /* True to collect rebase information */
};
/*
@@ -178164,7 +207525,7 @@ struct SessionApplyCtx {
** pointing to the prepared version of the SQL statement.
*/
static int sessionDeleteRow(
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
const char *zTab, /* Table name */
SessionApplyCtx *p /* Session changeset-apply context */
){
@@ -178208,9 +207569,9 @@ static int sessionDeleteRow(
}
if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0);
+ rc = tdsqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0);
}
- sqlite3_free(buf.aBuf);
+ tdsqlite3_free(buf.aBuf);
return rc;
}
@@ -178247,7 +207608,7 @@ static int sessionDeleteRow(
** pointing to the prepared version of the SQL statement.
*/
static int sessionUpdateRow(
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
const char *zTab, /* Table name */
SessionApplyCtx *p /* Session changeset-apply context */
){
@@ -178304,13 +207665,14 @@ static int sessionUpdateRow(
sessionAppendStr(&buf, ")", &rc);
if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0);
+ rc = tdsqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0);
}
- sqlite3_free(buf.aBuf);
+ tdsqlite3_free(buf.aBuf);
return rc;
}
+
/*
** Formulate and prepare an SQL statement to query table zTab by primary
** key. Assuming the following table structure:
@@ -178325,7 +207687,7 @@ static int sessionUpdateRow(
** pointing to the prepared version of the SQL statement.
*/
static int sessionSelectRow(
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
const char *zTab, /* Table name */
SessionApplyCtx *p /* Session changeset-apply context */
){
@@ -178343,7 +207705,7 @@ static int sessionSelectRow(
** pointing to the prepared version of the SQL statement.
*/
static int sessionInsertRow(
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
const char *zTab, /* Table name */
SessionApplyCtx *p /* Session changeset-apply context */
){
@@ -178353,40 +207715,86 @@ static int sessionInsertRow(
sessionAppendStr(&buf, "INSERT INTO main.", &rc);
sessionAppendIdent(&buf, zTab, &rc);
- sessionAppendStr(&buf, " VALUES(?", &rc);
+ sessionAppendStr(&buf, "(", &rc);
+ for(i=0; i<p->nCol; i++){
+ if( i!=0 ) sessionAppendStr(&buf, ", ", &rc);
+ sessionAppendIdent(&buf, p->azCol[i], &rc);
+ }
+
+ sessionAppendStr(&buf, ") VALUES(?", &rc);
for(i=1; i<p->nCol; i++){
sessionAppendStr(&buf, ", ?", &rc);
}
sessionAppendStr(&buf, ")", &rc);
if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
+ rc = tdsqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
+ }
+ tdsqlite3_free(buf.aBuf);
+ return rc;
+}
+
+static int sessionPrepare(tdsqlite3 *db, tdsqlite3_stmt **pp, const char *zSql){
+ return tdsqlite3_prepare_v2(db, zSql, -1, pp, 0);
+}
+
+/*
+** Prepare statements for applying changes to the sqlite_stat1 table.
+** These are similar to those created by sessionSelectRow(),
+** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for
+** other tables.
+*/
+static int sessionStat1Sql(tdsqlite3 *db, SessionApplyCtx *p){
+ int rc = sessionSelectRow(db, "sqlite_stat1", p);
+ if( rc==SQLITE_OK ){
+ rc = sessionPrepare(db, &p->pInsert,
+ "INSERT INTO main.sqlite_stat1 VALUES(?1, "
+ "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, "
+ "?3)"
+ );
+ }
+ if( rc==SQLITE_OK ){
+ rc = sessionPrepare(db, &p->pUpdate,
+ "UPDATE main.sqlite_stat1 SET "
+ "tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, "
+ "idx = CASE WHEN ?5 THEN ?6 ELSE idx END, "
+ "stat = CASE WHEN ?8 THEN ?9 ELSE stat END "
+ "WHERE tbl=?1 AND idx IS "
+ "CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END "
+ "AND (?10 OR ?8=0 OR stat IS ?7)"
+ );
+ }
+ if( rc==SQLITE_OK ){
+ rc = sessionPrepare(db, &p->pDelete,
+ "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS "
+ "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END "
+ "AND (?4 OR stat IS ?3)"
+ );
}
- sqlite3_free(buf.aBuf);
return rc;
}
/*
-** A wrapper around sqlite3_bind_value() that detects an extra problem.
+** A wrapper around tdsqlite3_bind_value() that detects an extra problem.
** See comments in the body of this function for details.
*/
static int sessionBindValue(
- sqlite3_stmt *pStmt, /* Statement to bind value to */
+ tdsqlite3_stmt *pStmt, /* Statement to bind value to */
int i, /* Parameter number to bind to */
- sqlite3_value *pVal /* Value to bind */
+ tdsqlite3_value *pVal /* Value to bind */
){
- int eType = sqlite3_value_type(pVal);
+ int eType = tdsqlite3_value_type(pVal);
/* COVERAGE: The (pVal->z==0) branch is never true using current versions
- ** of SQLite. If a malloc fails in an sqlite3_value_xxx() function, either
+ ** of SQLite. If a malloc fails in an tdsqlite3_value_xxx() function, either
** the (pVal->z) variable remains as it was or the type of the value is
** set to SQLITE_NULL. */
if( (eType==SQLITE_TEXT || eType==SQLITE_BLOB) && pVal->z==0 ){
/* This condition occurs when an earlier OOM in a call to
- ** sqlite3_value_text() or sqlite3_value_blob() (perhaps from within
+ ** tdsqlite3_value_text() or tdsqlite3_value_blob() (perhaps from within
** a conflict-handler) has zeroed the pVal->z pointer. Return NOMEM. */
return SQLITE_NOMEM;
}
- return sqlite3_bind_value(pStmt, i, pVal);
+ return tdsqlite3_bind_value(pStmt, i, pVal);
}
/*
@@ -178404,26 +207812,32 @@ static int sessionBindValue(
** An SQLite error code is returned if an error occurs. Otherwise, SQLITE_OK.
*/
static int sessionBindRow(
- sqlite3_changeset_iter *pIter, /* Iterator to read values from */
- int(*xValue)(sqlite3_changeset_iter *, int, sqlite3_value **),
+ tdsqlite3_changeset_iter *pIter, /* Iterator to read values from */
+ int(*xValue)(tdsqlite3_changeset_iter *, int, tdsqlite3_value **),
int nCol, /* Number of columns */
u8 *abPK, /* If not NULL, bind only if true */
- sqlite3_stmt *pStmt /* Bind values to this statement */
+ tdsqlite3_stmt *pStmt /* Bind values to this statement */
){
int i;
int rc = SQLITE_OK;
- /* Neither sqlite3changeset_old or sqlite3changeset_new can fail if the
+ /* Neither tdsqlite3changeset_old or tdsqlite3changeset_new can fail if the
** argument iterator points to a suitable entry. Make sure that xValue
** is one of these to guarantee that it is safe to ignore the return
** in the code below. */
- assert( xValue==sqlite3changeset_old || xValue==sqlite3changeset_new );
+ assert( xValue==tdsqlite3changeset_old || xValue==tdsqlite3changeset_new );
for(i=0; rc==SQLITE_OK && i<nCol; i++){
if( !abPK || abPK[i] ){
- sqlite3_value *pVal;
+ tdsqlite3_value *pVal;
(void)xValue(pIter, i, &pVal);
- rc = sessionBindValue(pStmt, i+1, pVal);
+ if( pVal==0 ){
+ /* The value in the changeset was "undefined". This indicates a
+ ** corrupt changeset blob. */
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ rc = sessionBindValue(pStmt, i+1, pVal);
+ }
}
}
return rc;
@@ -178447,31 +207861,80 @@ static int sessionBindRow(
** UPDATE, bind values from the old.* record.
*/
static int sessionSeekToRow(
- sqlite3 *db, /* Database handle */
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+ tdsqlite3 *db, /* Database handle */
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
u8 *abPK, /* Primary key flags array */
- sqlite3_stmt *pSelect /* SELECT statement from sessionSelectRow() */
+ tdsqlite3_stmt *pSelect /* SELECT statement from sessionSelectRow() */
){
int rc; /* Return code */
int nCol; /* Number of columns in table */
int op; /* Changset operation (SQLITE_UPDATE etc.) */
const char *zDummy; /* Unused */
- sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
+ tdsqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
rc = sessionBindRow(pIter,
- op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old,
+ op==SQLITE_INSERT ? tdsqlite3changeset_new : tdsqlite3changeset_old,
nCol, abPK, pSelect
);
if( rc==SQLITE_OK ){
- rc = sqlite3_step(pSelect);
- if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect);
+ rc = tdsqlite3_step(pSelect);
+ if( rc!=SQLITE_ROW ) rc = tdsqlite3_reset(pSelect);
}
return rc;
}
/*
+** This function is called from within tdsqlite3changeset_apply_v2() when
+** a conflict is encountered and resolved using conflict resolution
+** mode eType (either SQLITE_CHANGESET_OMIT or SQLITE_CHANGESET_REPLACE)..
+** It adds a conflict resolution record to the buffer in
+** SessionApplyCtx.rebase, which will eventually be returned to the caller
+** of apply_v2() as the "rebase" buffer.
+**
+** Return SQLITE_OK if successful, or an SQLite error code otherwise.
+*/
+static int sessionRebaseAdd(
+ SessionApplyCtx *p, /* Apply context */
+ int eType, /* Conflict resolution (OMIT or REPLACE) */
+ tdsqlite3_changeset_iter *pIter /* Iterator pointing at current change */
+){
+ int rc = SQLITE_OK;
+ if( p->bRebase ){
+ int i;
+ int eOp = pIter->op;
+ if( p->bRebaseStarted==0 ){
+ /* Append a table-header to the rebase buffer */
+ const char *zTab = pIter->zTab;
+ sessionAppendByte(&p->rebase, 'T', &rc);
+ sessionAppendVarint(&p->rebase, p->nCol, &rc);
+ sessionAppendBlob(&p->rebase, p->abPK, p->nCol, &rc);
+ sessionAppendBlob(&p->rebase, (u8*)zTab, (int)strlen(zTab)+1, &rc);
+ p->bRebaseStarted = 1;
+ }
+
+ assert( eType==SQLITE_CHANGESET_REPLACE||eType==SQLITE_CHANGESET_OMIT );
+ assert( eOp==SQLITE_DELETE || eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE );
+
+ sessionAppendByte(&p->rebase,
+ (eOp==SQLITE_DELETE ? SQLITE_DELETE : SQLITE_INSERT), &rc
+ );
+ sessionAppendByte(&p->rebase, (eType==SQLITE_CHANGESET_REPLACE), &rc);
+ for(i=0; i<p->nCol; i++){
+ tdsqlite3_value *pVal = 0;
+ if( eOp==SQLITE_DELETE || (eOp==SQLITE_UPDATE && p->abPK[i]) ){
+ tdsqlite3changeset_old(pIter, i, &pVal);
+ }else{
+ tdsqlite3changeset_new(pIter, i, &pVal);
+ }
+ sessionAppendValue(&p->rebase, pVal, &rc);
+ }
+ }
+ return rc;
+}
+
+/*
** Invoke the conflict handler for the change that the changeset iterator
** currently points to.
**
@@ -178509,8 +207972,8 @@ static int sessionSeekToRow(
static int sessionConflictHandler(
int eType, /* Either CHANGESET_DATA or CONFLICT */
SessionApplyCtx *p, /* changeset_apply() context */
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
- int(*xConflict)(void *, int, sqlite3_changeset_iter*),
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
+ int(*xConflict)(void *, int, tdsqlite3_changeset_iter*),
void *pCtx, /* First argument for conflict handler */
int *pbReplace /* OUT: Set to true if PK row is found */
){
@@ -178520,7 +207983,7 @@ static int sessionConflictHandler(
int op;
const char *zDummy;
- sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
+ tdsqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA );
assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
@@ -178538,7 +208001,7 @@ static int sessionConflictHandler(
pIter->pConflict = p->pSelect;
res = xConflict(pCtx, eType, pIter);
pIter->pConflict = 0;
- rc = sqlite3_reset(p->pSelect);
+ rc = tdsqlite3_reset(p->pSelect);
}else if( rc==SQLITE_OK ){
if( p->bDeferConstraints && eType==SQLITE_CHANGESET_CONFLICT ){
/* Instead of invoking the conflict handler, append the change blob
@@ -178546,7 +208009,7 @@ static int sessionConflictHandler(
u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent];
int nBlob = pIter->in.iNext - pIter->in.iCurrent;
sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc);
- res = SQLITE_CHANGESET_OMIT;
+ return SQLITE_OK;
}else{
/* No other row with the new.* primary key. */
res = xConflict(pCtx, eType+1, pIter);
@@ -178572,6 +208035,9 @@ static int sessionConflictHandler(
rc = SQLITE_MISUSE;
break;
}
+ if( rc==SQLITE_OK ){
+ rc = sessionRebaseAdd(p, res, pIter);
+ }
}
return rc;
@@ -178602,9 +208068,9 @@ static int sessionConflictHandler(
** returned.
*/
static int sessionApplyOneOp(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
SessionApplyCtx *p, /* changeset_apply() context */
- int(*xConflict)(void *, int, sqlite3_changeset_iter *),
+ int(*xConflict)(void *, int, tdsqlite3_changeset_iter *),
void *pCtx, /* First argument for the conflict handler */
int *pbReplace, /* OUT: True to remove PK row and retry */
int *pbRetry /* OUT: True to retry. */
@@ -178618,7 +208084,7 @@ static int sessionApplyOneOp(
assert( p->azCol && p->abPK );
assert( !pbReplace || *pbReplace==0 );
- sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
+ tdsqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
if( op==SQLITE_DELETE ){
@@ -178634,15 +208100,15 @@ static int sessionApplyOneOp(
** no (nCol+1) variable to bind to).
*/
u8 *abPK = (pIter->bPatchset ? p->abPK : 0);
- rc = sessionBindRow(pIter, sqlite3changeset_old, nCol, abPK, p->pDelete);
- if( rc==SQLITE_OK && sqlite3_bind_parameter_count(p->pDelete)>nCol ){
- rc = sqlite3_bind_int(p->pDelete, nCol+1, (pbRetry==0 || abPK));
+ rc = sessionBindRow(pIter, tdsqlite3changeset_old, nCol, abPK, p->pDelete);
+ if( rc==SQLITE_OK && tdsqlite3_bind_parameter_count(p->pDelete)>nCol ){
+ rc = tdsqlite3_bind_int(p->pDelete, nCol+1, (pbRetry==0 || abPK));
}
if( rc!=SQLITE_OK ) return rc;
- sqlite3_step(p->pDelete);
- rc = sqlite3_reset(p->pDelete);
- if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
+ tdsqlite3_step(p->pDelete);
+ rc = tdsqlite3_reset(p->pDelete);
+ if( rc==SQLITE_OK && tdsqlite3_changes(p->db)==0 ){
rc = sessionConflictHandler(
SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
);
@@ -178657,10 +208123,10 @@ static int sessionApplyOneOp(
/* Bind values to the UPDATE statement. */
for(i=0; rc==SQLITE_OK && i<nCol; i++){
- sqlite3_value *pOld = sessionChangesetOld(pIter, i);
- sqlite3_value *pNew = sessionChangesetNew(pIter, i);
+ tdsqlite3_value *pOld = sessionChangesetOld(pIter, i);
+ tdsqlite3_value *pNew = sessionChangesetNew(pIter, i);
- sqlite3_bind_int(p->pUpdate, i*3+2, !!pNew);
+ tdsqlite3_bind_int(p->pUpdate, i*3+2, !!pNew);
if( pOld ){
rc = sessionBindValue(p->pUpdate, i*3+1, pOld);
}
@@ -178669,16 +208135,16 @@ static int sessionApplyOneOp(
}
}
if( rc==SQLITE_OK ){
- sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0 || pIter->bPatchset);
+ tdsqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0 || pIter->bPatchset);
}
if( rc!=SQLITE_OK ) return rc;
/* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict,
** the result will be SQLITE_OK with 0 rows modified. */
- sqlite3_step(p->pUpdate);
- rc = sqlite3_reset(p->pUpdate);
+ tdsqlite3_step(p->pUpdate);
+ rc = tdsqlite3_reset(p->pUpdate);
- if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
+ if( rc==SQLITE_OK && tdsqlite3_changes(p->db)==0 ){
/* A NOTFOUND or DATA error. Search the table to see if it contains
** a row with a matching primary key. If so, this is a DATA conflict.
** Otherwise, if there is no primary key match, it is a NOTFOUND. */
@@ -178696,11 +208162,25 @@ static int sessionApplyOneOp(
}else{
assert( op==SQLITE_INSERT );
- rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert);
- if( rc!=SQLITE_OK ) return rc;
+ if( p->bStat1 ){
+ /* Check if there is a conflicting row. For sqlite_stat1, this needs
+ ** to be done using a SELECT, as there is no PRIMARY KEY in the
+ ** database schema to throw an exception if a duplicate is inserted. */
+ rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect);
+ if( rc==SQLITE_ROW ){
+ rc = SQLITE_CONSTRAINT;
+ tdsqlite3_reset(p->pSelect);
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = sessionBindRow(pIter, tdsqlite3changeset_new, nCol, 0, p->pInsert);
+ if( rc!=SQLITE_OK ) return rc;
+
+ tdsqlite3_step(p->pInsert);
+ rc = tdsqlite3_reset(p->pInsert);
+ }
- sqlite3_step(p->pInsert);
- rc = sqlite3_reset(p->pInsert);
if( (rc&0xff)==SQLITE_CONSTRAINT ){
rc = sessionConflictHandler(
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
@@ -178722,10 +208202,10 @@ static int sessionApplyOneOp(
** retried in some manner.
*/
static int sessionApplyOneWithRetry(
- sqlite3 *db, /* Apply change to "main" db of this handle */
- sqlite3_changeset_iter *pIter, /* Changeset iterator to read change from */
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator to read change from */
SessionApplyCtx *pApply, /* Apply context */
- int(*xConflict)(void*, int, sqlite3_changeset_iter*),
+ int(*xConflict)(void*, int, tdsqlite3_changeset_iter*),
void *pCtx /* First argument passed to xConflict */
){
int bReplace = 0;
@@ -178733,42 +208213,42 @@ static int sessionApplyOneWithRetry(
int rc;
rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry);
- assert( rc==SQLITE_OK || (bRetry==0 && bReplace==0) );
-
- /* If the bRetry flag is set, the change has not been applied due to an
- ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and
- ** a row with the correct PK is present in the db, but one or more other
- ** fields do not contain the expected values) and the conflict handler
- ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation,
- ** but pass NULL as the final argument so that sessionApplyOneOp() ignores
- ** the SQLITE_CHANGESET_DATA problem. */
- if( bRetry ){
- assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE );
- rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
- }
-
- /* If the bReplace flag is set, the change is an INSERT that has not
- ** been performed because the database already contains a row with the
- ** specified primary key and the conflict handler returned
- ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row
- ** before reattempting the INSERT. */
- else if( bReplace ){
- assert( pIter->op==SQLITE_INSERT );
- rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
- if( rc==SQLITE_OK ){
- rc = sessionBindRow(pIter,
- sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete);
- sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
- }
- if( rc==SQLITE_OK ){
- sqlite3_step(pApply->pDelete);
- rc = sqlite3_reset(pApply->pDelete);
- }
- if( rc==SQLITE_OK ){
+ if( rc==SQLITE_OK ){
+ /* If the bRetry flag is set, the change has not been applied due to an
+ ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and
+ ** a row with the correct PK is present in the db, but one or more other
+ ** fields do not contain the expected values) and the conflict handler
+ ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation,
+ ** but pass NULL as the final argument so that sessionApplyOneOp() ignores
+ ** the SQLITE_CHANGESET_DATA problem. */
+ if( bRetry ){
+ assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE );
rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
}
- if( rc==SQLITE_OK ){
- rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
+
+ /* If the bReplace flag is set, the change is an INSERT that has not
+ ** been performed because the database already contains a row with the
+ ** specified primary key and the conflict handler returned
+ ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row
+ ** before reattempting the INSERT. */
+ else if( bReplace ){
+ assert( pIter->op==SQLITE_INSERT );
+ rc = tdsqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = sessionBindRow(pIter,
+ tdsqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete);
+ tdsqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
+ }
+ if( rc==SQLITE_OK ){
+ tdsqlite3_step(pApply->pDelete);
+ rc = tdsqlite3_reset(pApply->pDelete);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
+ }
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
+ }
}
}
@@ -178779,42 +208259,42 @@ static int sessionApplyOneWithRetry(
** Retry the changes accumulated in the pApply->constraints buffer.
*/
static int sessionRetryConstraints(
- sqlite3 *db,
+ tdsqlite3 *db,
int bPatchset,
const char *zTab,
SessionApplyCtx *pApply,
- int(*xConflict)(void*, int, sqlite3_changeset_iter*),
+ int(*xConflict)(void*, int, tdsqlite3_changeset_iter*),
void *pCtx /* First argument passed to xConflict */
){
int rc = SQLITE_OK;
while( pApply->constraints.nBuf ){
- sqlite3_changeset_iter *pIter2 = 0;
+ tdsqlite3_changeset_iter *pIter2 = 0;
SessionBuffer cons = pApply->constraints;
memset(&pApply->constraints, 0, sizeof(SessionBuffer));
- rc = sessionChangesetStart(&pIter2, 0, 0, cons.nBuf, cons.aBuf);
+ rc = sessionChangesetStart(&pIter2, 0, 0, cons.nBuf, cons.aBuf, 0);
if( rc==SQLITE_OK ){
- int nByte = 2*pApply->nCol*sizeof(sqlite3_value*);
+ size_t nByte = 2*pApply->nCol*sizeof(tdsqlite3_value*);
int rc2;
pIter2->bPatchset = bPatchset;
pIter2->zTab = (char*)zTab;
pIter2->nCol = pApply->nCol;
pIter2->abPK = pApply->abPK;
sessionBufferGrow(&pIter2->tblhdr, nByte, &rc);
- pIter2->apValue = (sqlite3_value**)pIter2->tblhdr.aBuf;
+ pIter2->apValue = (tdsqlite3_value**)pIter2->tblhdr.aBuf;
if( rc==SQLITE_OK ) memset(pIter2->apValue, 0, nByte);
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter2) ){
+ while( rc==SQLITE_OK && SQLITE_ROW==tdsqlite3changeset_next(pIter2) ){
rc = sessionApplyOneWithRetry(db, pIter2, pApply, xConflict, pCtx);
}
- rc2 = sqlite3changeset_finalize(pIter2);
+ rc2 = tdsqlite3changeset_finalize(pIter2);
if( rc==SQLITE_OK ) rc = rc2;
}
assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 );
- sqlite3_free(cons.aBuf);
+ tdsqlite3_free(cons.aBuf);
if( rc!=SQLITE_OK ) break;
if( pApply->constraints.nBuf>=cons.nBuf ){
/* No progress was made on the last round. */
@@ -178827,14 +208307,14 @@ static int sessionRetryConstraints(
/*
** Argument pIter is a changeset iterator that has been initialized, but
-** not yet passed to sqlite3changeset_next(). This function applies the
+** not yet passed to tdsqlite3changeset_next(). This function applies the
** changeset to the main database attached to handle "db". The supplied
** conflict handler callback is invoked to resolve any conflicts encountered
** while applying the change.
*/
static int sessionChangesetApply(
- sqlite3 *db, /* Apply change to "main" db of this handle */
- sqlite3_changeset_iter *pIter, /* Changeset to apply */
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ tdsqlite3_changeset_iter *pIter, /* Changeset to apply */
int(*xFilter)(
void *pCtx, /* Copy of sixth arg to _apply() */
const char *zTab /* Table name */
@@ -178842,14 +208322,16 @@ static int sessionChangesetApply(
int(*xConflict)(
void *pCtx, /* Copy of fifth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
- sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
),
- void *pCtx /* First argument passed to xConflict */
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase, /* OUT: Rebase information */
+ int flags /* SESSION_APPLY_XXX flags */
){
int schemaMismatch = 0;
- int rc; /* Return code */
+ int rc = SQLITE_OK; /* Return code */
const char *zTab = 0; /* Name of current table */
- int nTab = 0; /* Result of sqlite3Strlen30(zTab) */
+ int nTab = 0; /* Result of tdsqlite3Strlen30(zTab) */
SessionApplyCtx sApply; /* changeset_apply() context object */
int bPatchset;
@@ -178857,19 +208339,22 @@ static int sessionChangesetApply(
pIter->in.bNoDiscard = 1;
memset(&sApply, 0, sizeof(sApply));
- sqlite3_mutex_enter(sqlite3_db_mutex(db));
- rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
+ sApply.bRebase = (ppRebase && pnRebase);
+ tdsqlite3_mutex_enter(tdsqlite3_db_mutex(db));
+ if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){
+ rc = tdsqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
+ }
if( rc==SQLITE_OK ){
- rc = sqlite3_exec(db, "PRAGMA defer_foreign_keys = 1", 0, 0, 0);
+ rc = tdsqlite3_exec(db, "PRAGMA defer_foreign_keys = 1", 0, 0, 0);
}
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
+ while( rc==SQLITE_OK && SQLITE_ROW==tdsqlite3changeset_next(pIter) ){
int nCol;
int op;
const char *zNew;
- sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0);
+ tdsqlite3changeset_op(pIter, &zNew, &nCol, &op, 0);
- if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
+ if( zTab==0 || tdsqlite3_strnicmp(zNew, zTab, nTab+1) ){
u8 *abPK;
rc = sessionRetryConstraints(
@@ -178877,21 +208362,30 @@ static int sessionChangesetApply(
);
if( rc!=SQLITE_OK ) break;
- sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
- sqlite3_finalize(sApply.pDelete);
- sqlite3_finalize(sApply.pUpdate);
- sqlite3_finalize(sApply.pInsert);
- sqlite3_finalize(sApply.pSelect);
- memset(&sApply, 0, sizeof(sApply));
+ tdsqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
+ tdsqlite3_finalize(sApply.pDelete);
+ tdsqlite3_finalize(sApply.pUpdate);
+ tdsqlite3_finalize(sApply.pInsert);
+ tdsqlite3_finalize(sApply.pSelect);
sApply.db = db;
+ sApply.pDelete = 0;
+ sApply.pUpdate = 0;
+ sApply.pInsert = 0;
+ sApply.pSelect = 0;
+ sApply.nCol = 0;
+ sApply.azCol = 0;
+ sApply.abPK = 0;
+ sApply.bStat1 = 0;
sApply.bDeferConstraints = 1;
+ sApply.bRebaseStarted = 0;
+ memset(&sApply.constraints, 0, sizeof(SessionBuffer));
/* If an xFilter() callback was specified, invoke it now. If the
** xFilter callback returns zero, skip this table. If it returns
** non-zero, proceed. */
schemaMismatch = (xFilter && (0==xFilter(pCtx, zNew)));
if( schemaMismatch ){
- zTab = sqlite3_mprintf("%s", zNew);
+ zTab = tdsqlite3_mprintf("%s", zNew);
if( zTab==0 ){
rc = SQLITE_NOMEM;
break;
@@ -178899,40 +208393,57 @@ static int sessionChangesetApply(
nTab = (int)strlen(zTab);
sApply.azCol = (const char **)zTab;
}else{
- sqlite3changeset_pk(pIter, &abPK, 0);
+ int nMinCol = 0;
+ int i;
+
+ tdsqlite3changeset_pk(pIter, &abPK, 0);
rc = sessionTableInfo(
db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK
);
if( rc!=SQLITE_OK ) break;
+ for(i=0; i<sApply.nCol; i++){
+ if( sApply.abPK[i] ) nMinCol = i+1;
+ }
if( sApply.nCol==0 ){
schemaMismatch = 1;
- sqlite3_log(SQLITE_SCHEMA,
- "sqlite3changeset_apply(): no such table: %s", zTab
+ tdsqlite3_log(SQLITE_SCHEMA,
+ "tdsqlite3changeset_apply(): no such table: %s", zTab
);
}
- else if( sApply.nCol!=nCol ){
+ else if( sApply.nCol<nCol ){
schemaMismatch = 1;
- sqlite3_log(SQLITE_SCHEMA,
- "sqlite3changeset_apply(): table %s has %d columns, expected %d",
+ tdsqlite3_log(SQLITE_SCHEMA,
+ "tdsqlite3changeset_apply(): table %s has %d columns, "
+ "expected %d or more",
zTab, sApply.nCol, nCol
);
}
- else if( memcmp(sApply.abPK, abPK, nCol)!=0 ){
+ else if( nCol<nMinCol || memcmp(sApply.abPK, abPK, nCol)!=0 ){
schemaMismatch = 1;
- sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_apply(): "
+ tdsqlite3_log(SQLITE_SCHEMA, "tdsqlite3changeset_apply(): "
"primary key mismatch for table %s", zTab
);
}
- else if(
- (rc = sessionSelectRow(db, zTab, &sApply))
- || (rc = sessionUpdateRow(db, zTab, &sApply))
- || (rc = sessionDeleteRow(db, zTab, &sApply))
- || (rc = sessionInsertRow(db, zTab, &sApply))
- ){
- break;
+ else{
+ sApply.nCol = nCol;
+ if( 0==tdsqlite3_stricmp(zTab, "sqlite_stat1") ){
+ if( (rc = sessionStat1Sql(db, &sApply) ) ){
+ break;
+ }
+ sApply.bStat1 = 1;
+ }else{
+ if((rc = sessionSelectRow(db, zTab, &sApply))
+ || (rc = sessionUpdateRow(db, zTab, &sApply))
+ || (rc = sessionDeleteRow(db, zTab, &sApply))
+ || (rc = sessionInsertRow(db, zTab, &sApply))
+ ){
+ break;
+ }
+ sApply.bStat1 = 0;
+ }
}
- nTab = sqlite3Strlen30(zTab);
+ nTab = tdsqlite3Strlen30(zTab);
}
}
@@ -178945,9 +208456,9 @@ static int sessionChangesetApply(
bPatchset = pIter->bPatchset;
if( rc==SQLITE_OK ){
- rc = sqlite3changeset_finalize(pIter);
+ rc = tdsqlite3changeset_finalize(pIter);
}else{
- sqlite3changeset_finalize(pIter);
+ tdsqlite3changeset_finalize(pIter);
}
if( rc==SQLITE_OK ){
@@ -178956,10 +208467,10 @@ static int sessionChangesetApply(
if( rc==SQLITE_OK ){
int nFk, notUsed;
- sqlite3_db_status(db, SQLITE_DBSTATUS_DEFERRED_FKS, &nFk, &notUsed, 0);
+ tdsqlite3_db_status(db, SQLITE_DBSTATUS_DEFERRED_FKS, &nFk, &notUsed, 0);
if( nFk!=0 ){
int res = SQLITE_CHANGESET_ABORT;
- sqlite3_changeset_iter sIter;
+ tdsqlite3_changeset_iter sIter;
memset(&sIter, 0, sizeof(sIter));
sIter.nCol = nFk;
res = xConflict(pCtx, SQLITE_CHANGESET_FOREIGN_KEY, &sIter);
@@ -178968,22 +208479,63 @@ static int sessionChangesetApply(
}
}
}
- sqlite3_exec(db, "PRAGMA defer_foreign_keys = 0", 0, 0, 0);
+ tdsqlite3_exec(db, "PRAGMA defer_foreign_keys = 0", 0, 0, 0);
- if( rc==SQLITE_OK ){
- rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
- }else{
- sqlite3_exec(db, "ROLLBACK TO changeset_apply", 0, 0, 0);
- sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
+ if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
+ }else{
+ tdsqlite3_exec(db, "ROLLBACK TO changeset_apply", 0, 0, 0);
+ tdsqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
+ }
}
- sqlite3_finalize(sApply.pInsert);
- sqlite3_finalize(sApply.pDelete);
- sqlite3_finalize(sApply.pUpdate);
- sqlite3_finalize(sApply.pSelect);
- sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
- sqlite3_free((char*)sApply.constraints.aBuf);
- sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ assert( sApply.bRebase || sApply.rebase.nBuf==0 );
+ if( rc==SQLITE_OK && bPatchset==0 && sApply.bRebase ){
+ *ppRebase = (void*)sApply.rebase.aBuf;
+ *pnRebase = sApply.rebase.nBuf;
+ sApply.rebase.aBuf = 0;
+ }
+ tdsqlite3_finalize(sApply.pInsert);
+ tdsqlite3_finalize(sApply.pDelete);
+ tdsqlite3_finalize(sApply.pUpdate);
+ tdsqlite3_finalize(sApply.pSelect);
+ tdsqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */
+ tdsqlite3_free((char*)sApply.constraints.aBuf);
+ tdsqlite3_free((char*)sApply.rebase.aBuf);
+ tdsqlite3_mutex_leave(tdsqlite3_db_mutex(db));
+ return rc;
+}
+
+/*
+** Apply the changeset passed via pChangeset/nChangeset to the main
+** database attached to handle "db".
+*/
+SQLITE_API int tdsqlite3changeset_apply_v2(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ int nChangeset, /* Size of changeset in bytes */
+ void *pChangeset, /* Changeset blob */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase,
+ int flags
+){
+ tdsqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
+ int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+ int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset,bInverse);
+ if( rc==SQLITE_OK ){
+ rc = sessionChangesetApply(
+ db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
+ );
+ }
return rc;
}
@@ -178992,8 +208544,8 @@ static int sessionChangesetApply(
** attached to handle "db". Invoke the supplied conflict handler callback
** to resolve any conflicts encountered while applying the change.
*/
-SQLITE_API int sqlite3changeset_apply(
- sqlite3 *db, /* Apply change to "main" db of this handle */
+SQLITE_API int tdsqlite3changeset_apply(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset, /* Changeset blob */
int(*xFilter)(
@@ -179003,16 +208555,13 @@ SQLITE_API int sqlite3changeset_apply(
int(*xConflict)(
void *pCtx, /* Copy of fifth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
- sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx /* First argument passed to xConflict */
){
- sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
- if( rc==SQLITE_OK ){
- rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx);
- }
- return rc;
+ return tdsqlite3changeset_apply_v2(
+ db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0, 0
+ );
}
/*
@@ -179020,8 +208569,8 @@ SQLITE_API int sqlite3changeset_apply(
** attached to handle "db". Invoke the supplied conflict handler callback
** to resolve any conflicts encountered while applying the change.
*/
-SQLITE_API int sqlite3changeset_apply_strm(
- sqlite3 *db, /* Apply change to "main" db of this handle */
+SQLITE_API int tdsqlite3changeset_apply_v2_strm(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
int(*xFilter)(
@@ -179031,22 +208580,46 @@ SQLITE_API int sqlite3changeset_apply_strm(
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
- sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
),
- void *pCtx /* First argument passed to xConflict */
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase,
+ int flags
){
- sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
+ tdsqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
+ int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+ int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse);
if( rc==SQLITE_OK ){
- rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx);
+ rc = sessionChangesetApply(
+ db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
+ );
}
return rc;
}
+SQLITE_API int tdsqlite3changeset_apply_strm(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
+ void *pIn, /* First arg for xInput */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx /* First argument passed to xConflict */
+){
+ return tdsqlite3changeset_apply_v2_strm(
+ db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0, 0
+ );
+}
/*
-** sqlite3_changegroup handle.
+** tdsqlite3_changegroup handle.
*/
-struct sqlite3_changegroup {
+struct tdsqlite3_changegroup {
int rc; /* Error code */
int bPatch; /* True to accumulate patchsets */
SessionTable *pList; /* List of tables in current patch */
@@ -179054,11 +208627,12 @@ struct sqlite3_changegroup {
/*
** This function is called to merge two changes to the same row together as
-** part of an sqlite3changeset_concat() operation. A new change object is
+** part of an tdsqlite3changeset_concat() operation. A new change object is
** allocated and a pointer to it stored in *ppNew.
*/
static int sessionChangeMerge(
SessionTable *pTab, /* Table structure */
+ int bRebase, /* True for a rebase hash-table */
int bPatchset, /* True for patchsets */
SessionChange *pExist, /* Existing change */
int op2, /* Second change operation */
@@ -179068,18 +208642,76 @@ static int sessionChangeMerge(
SessionChange **ppNew /* OUT: Merged change */
){
SessionChange *pNew = 0;
+ int rc = SQLITE_OK;
if( !pExist ){
- pNew = (SessionChange *)sqlite3_malloc(sizeof(SessionChange) + nRec);
+ pNew = (SessionChange *)tdsqlite3_malloc64(sizeof(SessionChange) + nRec);
if( !pNew ){
return SQLITE_NOMEM;
}
memset(pNew, 0, sizeof(SessionChange));
pNew->op = op2;
pNew->bIndirect = bIndirect;
- pNew->nRecord = nRec;
pNew->aRecord = (u8*)&pNew[1];
- memcpy(pNew->aRecord, aRec, nRec);
+ if( bIndirect==0 || bRebase==0 ){
+ pNew->nRecord = nRec;
+ memcpy(pNew->aRecord, aRec, nRec);
+ }else{
+ int i;
+ u8 *pIn = aRec;
+ u8 *pOut = pNew->aRecord;
+ for(i=0; i<pTab->nCol; i++){
+ int nIn = sessionSerialLen(pIn);
+ if( *pIn==0 ){
+ *pOut++ = 0;
+ }else if( pTab->abPK[i]==0 ){
+ *pOut++ = 0xFF;
+ }else{
+ memcpy(pOut, pIn, nIn);
+ pOut += nIn;
+ }
+ pIn += nIn;
+ }
+ pNew->nRecord = pOut - pNew->aRecord;
+ }
+ }else if( bRebase ){
+ if( pExist->op==SQLITE_DELETE && pExist->bIndirect ){
+ *ppNew = pExist;
+ }else{
+ tdsqlite3_int64 nByte = nRec + pExist->nRecord + sizeof(SessionChange);
+ pNew = (SessionChange*)tdsqlite3_malloc64(nByte);
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int i;
+ u8 *a1 = pExist->aRecord;
+ u8 *a2 = aRec;
+ u8 *pOut;
+
+ memset(pNew, 0, nByte);
+ pNew->bIndirect = bIndirect || pExist->bIndirect;
+ pNew->op = op2;
+ pOut = pNew->aRecord = (u8*)&pNew[1];
+
+ for(i=0; i<pTab->nCol; i++){
+ int n1 = sessionSerialLen(a1);
+ int n2 = sessionSerialLen(a2);
+ if( *a1==0xFF || (pTab->abPK[i]==0 && bIndirect) ){
+ *pOut++ = 0xFF;
+ }else if( *a2==0 ){
+ memcpy(pOut, a1, n1);
+ pOut += n1;
+ }else{
+ memcpy(pOut, a2, n2);
+ pOut += n2;
+ }
+ a1 += n1;
+ a2 += n2;
+ }
+ pNew->nRecord = pOut - pNew->aRecord;
+ }
+ tdsqlite3_free(pExist);
+ }
}else{
int op1 = pExist->op;
@@ -179103,20 +208735,20 @@ static int sessionChangeMerge(
){
pNew = pExist;
}else if( op1==SQLITE_INSERT && op2==SQLITE_DELETE ){
- sqlite3_free(pExist);
+ tdsqlite3_free(pExist);
assert( pNew==0 );
}else{
u8 *aExist = pExist->aRecord;
- int nByte;
+ tdsqlite3_int64 nByte;
u8 *aCsr;
/* Allocate a new SessionChange object. Ensure that the aRecord[]
** buffer of the new object is large enough to hold any record that
** may be generated by combining the input records. */
nByte = sizeof(SessionChange) + pExist->nRecord + nRec;
- pNew = (SessionChange *)sqlite3_malloc(nByte);
+ pNew = (SessionChange *)tdsqlite3_malloc64(nByte);
if( !pNew ){
- sqlite3_free(pExist);
+ tdsqlite3_free(pExist);
return SQLITE_NOMEM;
}
memset(pNew, 0, sizeof(SessionChange));
@@ -179137,7 +208769,7 @@ static int sessionChangeMerge(
aCsr += nRec;
}else{
if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist, 0,aRec,0) ){
- sqlite3_free(pNew);
+ tdsqlite3_free(pNew);
pNew = 0;
}
}
@@ -179151,7 +208783,7 @@ static int sessionChangeMerge(
}
pNew->op = SQLITE_UPDATE;
if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aRec, aExist,a1,a2) ){
- sqlite3_free(pNew);
+ tdsqlite3_free(pNew);
pNew = 0;
}
}else{ /* UPDATE + DELETE */
@@ -179168,12 +208800,12 @@ static int sessionChangeMerge(
if( pNew ){
pNew->nRecord = (int)(aCsr - pNew->aRecord);
}
- sqlite3_free(pExist);
+ tdsqlite3_free(pExist);
}
}
*ppNew = pNew;
- return SQLITE_OK;
+ return rc;
}
/*
@@ -179181,16 +208813,16 @@ static int sessionChangeMerge(
** the first argument to the changegroup hash tables.
*/
static int sessionChangesetToHash(
- sqlite3_changeset_iter *pIter, /* Iterator to read from */
- sqlite3_changegroup *pGrp /* Changegroup object to add changeset to */
+ tdsqlite3_changeset_iter *pIter, /* Iterator to read from */
+ tdsqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */
+ int bRebase /* True if hash table is for rebasing */
){
u8 *aRec;
int nRec;
int rc = SQLITE_OK;
SessionTable *pTab = 0;
-
- while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec) ){
+ while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){
const char *zNew;
int nCol;
int op;
@@ -179207,20 +208839,20 @@ static int sessionChangesetToHash(
break;
}
- sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect);
- if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){
+ tdsqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect);
+ if( !pTab || tdsqlite3_stricmp(zNew, pTab->zName) ){
/* Search the list for a matching table */
int nNew = (int)strlen(zNew);
u8 *abPK;
- sqlite3changeset_pk(pIter, &abPK, 0);
+ tdsqlite3changeset_pk(pIter, &abPK, 0);
for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
- if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break;
+ if( 0==tdsqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break;
}
if( !pTab ){
SessionTable **ppTab;
- pTab = sqlite3_malloc(sizeof(SessionTable) + nCol + nNew+1);
+ pTab = tdsqlite3_malloc64(sizeof(SessionTable) + nCol + nNew+1);
if( !pTab ){
rc = SQLITE_NOMEM;
break;
@@ -179234,7 +208866,7 @@ static int sessionChangesetToHash(
/* The new object must be linked on to the end of the list, not
** simply added to the start of it. This is to ensure that the
- ** tables within the output of sqlite3changegroup_output() are in
+ ** tables within the output of tdsqlite3changegroup_output() are in
** the right order. */
for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext);
*ppTab = pTab;
@@ -179270,7 +208902,7 @@ static int sessionChangesetToHash(
}
}
- rc = sessionChangeMerge(pTab,
+ rc = sessionChangeMerge(pTab, bRebase,
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
);
if( rc ) break;
@@ -179297,14 +208929,14 @@ static int sessionChangesetToHash(
** buffer containing the output changeset before this function returns. In
** this case (*pnOut) is set to the size of the output buffer in bytes. It
** is the responsibility of the caller to free the output buffer using
-** sqlite3_free() when it is no longer required.
+** tdsqlite3_free() when it is no longer required.
**
** If successful, SQLITE_OK is returned. Or, if an error occurs, an SQLite
** error code. If an error occurs and xOutput is NULL, (*ppOut) and (*pnOut)
** are both set to 0 before returning.
*/
static int sessionChangegroupOutput(
- sqlite3_changegroup *pGrp,
+ tdsqlite3_changegroup *pGrp,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut,
int *pnOut,
@@ -179329,13 +208961,12 @@ static int sessionChangegroupOutput(
sessionAppendByte(&buf, p->op, &rc);
sessionAppendByte(&buf, p->bIndirect, &rc);
sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
+ if( rc==SQLITE_OK && xOutput && buf.nBuf>=sessions_strm_chunk_size ){
+ rc = xOutput(pOut, buf.aBuf, buf.nBuf);
+ buf.nBuf = 0;
+ }
}
}
-
- if( rc==SQLITE_OK && xOutput && buf.nBuf>=SESSIONS_STRM_CHUNK_SIZE ){
- rc = xOutput(pOut, buf.aBuf, buf.nBuf);
- buf.nBuf = 0;
- }
}
if( rc==SQLITE_OK ){
@@ -179347,22 +208978,22 @@ static int sessionChangegroupOutput(
buf.aBuf = 0;
}
}
- sqlite3_free(buf.aBuf);
+ tdsqlite3_free(buf.aBuf);
return rc;
}
/*
-** Allocate a new, empty, sqlite3_changegroup.
+** Allocate a new, empty, tdsqlite3_changegroup.
*/
-SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp){
+SQLITE_API int tdsqlite3changegroup_new(tdsqlite3_changegroup **pp){
int rc = SQLITE_OK; /* Return code */
- sqlite3_changegroup *p; /* New object */
- p = (sqlite3_changegroup*)sqlite3_malloc(sizeof(sqlite3_changegroup));
+ tdsqlite3_changegroup *p; /* New object */
+ p = (tdsqlite3_changegroup*)tdsqlite3_malloc(sizeof(tdsqlite3_changegroup));
if( p==0 ){
rc = SQLITE_NOMEM;
}else{
- memset(p, 0, sizeof(sqlite3_changegroup));
+ memset(p, 0, sizeof(tdsqlite3_changegroup));
}
*pp = p;
return rc;
@@ -179372,15 +209003,15 @@ SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp){
** Add the changeset currently stored in buffer pData, size nData bytes,
** to changeset-group p.
*/
-SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){
- sqlite3_changeset_iter *pIter; /* Iterator opened on pData/nData */
+SQLITE_API int tdsqlite3changegroup_add(tdsqlite3_changegroup *pGrp, int nData, void *pData){
+ tdsqlite3_changeset_iter *pIter; /* Iterator opened on pData/nData */
int rc; /* Return code */
- rc = sqlite3changeset_start(&pIter, nData, pData);
+ rc = tdsqlite3changeset_start(&pIter, nData, pData);
if( rc==SQLITE_OK ){
- rc = sessionChangesetToHash(pIter, pGrp);
+ rc = sessionChangesetToHash(pIter, pGrp, 0);
}
- sqlite3changeset_finalize(pIter);
+ tdsqlite3changeset_finalize(pIter);
return rc;
}
@@ -179388,8 +209019,8 @@ SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void
** Obtain a buffer containing a changeset representing the concatenation
** of all changesets added to the group so far.
*/
-SQLITE_API int sqlite3changegroup_output(
- sqlite3_changegroup *pGrp,
+SQLITE_API int tdsqlite3changegroup_output(
+ tdsqlite3_changegroup *pGrp,
int *pnData,
void **ppData
){
@@ -179399,27 +209030,27 @@ SQLITE_API int sqlite3changegroup_output(
/*
** Streaming versions of changegroup_add().
*/
-SQLITE_API int sqlite3changegroup_add_strm(
- sqlite3_changegroup *pGrp,
+SQLITE_API int tdsqlite3changegroup_add_strm(
+ tdsqlite3_changegroup *pGrp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
){
- sqlite3_changeset_iter *pIter; /* Iterator opened on pData/nData */
+ tdsqlite3_changeset_iter *pIter; /* Iterator opened on pData/nData */
int rc; /* Return code */
- rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
+ rc = tdsqlite3changeset_start_strm(&pIter, xInput, pIn);
if( rc==SQLITE_OK ){
- rc = sessionChangesetToHash(pIter, pGrp);
+ rc = sessionChangesetToHash(pIter, pGrp, 0);
}
- sqlite3changeset_finalize(pIter);
+ tdsqlite3changeset_finalize(pIter);
return rc;
}
/*
** Streaming versions of changegroup_output().
*/
-SQLITE_API int sqlite3changegroup_output_strm(
- sqlite3_changegroup *pGrp,
+SQLITE_API int tdsqlite3changegroup_output_strm(
+ tdsqlite3_changegroup *pGrp,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
){
@@ -179429,17 +209060,17 @@ SQLITE_API int sqlite3changegroup_output_strm(
/*
** Delete a changegroup object.
*/
-SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
+SQLITE_API void tdsqlite3changegroup_delete(tdsqlite3_changegroup *pGrp){
if( pGrp ){
sessionDeleteTable(pGrp->pList);
- sqlite3_free(pGrp);
+ tdsqlite3_free(pGrp);
}
}
/*
** Combine two changesets together.
*/
-SQLITE_API int sqlite3changeset_concat(
+SQLITE_API int tdsqlite3changeset_concat(
int nLeft, /* Number of bytes in lhs input */
void *pLeft, /* Lhs input changeset */
int nRight /* Number of bytes in rhs input */,
@@ -179447,28 +209078,28 @@ SQLITE_API int sqlite3changeset_concat(
int *pnOut, /* OUT: Number of bytes in output changeset */
void **ppOut /* OUT: changeset (left <concat> right) */
){
- sqlite3_changegroup *pGrp;
+ tdsqlite3_changegroup *pGrp;
int rc;
- rc = sqlite3changegroup_new(&pGrp);
+ rc = tdsqlite3changegroup_new(&pGrp);
if( rc==SQLITE_OK ){
- rc = sqlite3changegroup_add(pGrp, nLeft, pLeft);
+ rc = tdsqlite3changegroup_add(pGrp, nLeft, pLeft);
}
if( rc==SQLITE_OK ){
- rc = sqlite3changegroup_add(pGrp, nRight, pRight);
+ rc = tdsqlite3changegroup_add(pGrp, nRight, pRight);
}
if( rc==SQLITE_OK ){
- rc = sqlite3changegroup_output(pGrp, pnOut, ppOut);
+ rc = tdsqlite3changegroup_output(pGrp, pnOut, ppOut);
}
- sqlite3changegroup_delete(pGrp);
+ tdsqlite3changegroup_delete(pGrp);
return rc;
}
/*
-** Streaming version of sqlite3changeset_concat().
+** Streaming version of tdsqlite3changeset_concat().
*/
-SQLITE_API int sqlite3changeset_concat_strm(
+SQLITE_API int tdsqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
int (*xInputB)(void *pIn, void *pData, int *pnData),
@@ -179476,2259 +209107,391 @@ SQLITE_API int sqlite3changeset_concat_strm(
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
){
- sqlite3_changegroup *pGrp;
+ tdsqlite3_changegroup *pGrp;
int rc;
- rc = sqlite3changegroup_new(&pGrp);
+ rc = tdsqlite3changegroup_new(&pGrp);
if( rc==SQLITE_OK ){
- rc = sqlite3changegroup_add_strm(pGrp, xInputA, pInA);
+ rc = tdsqlite3changegroup_add_strm(pGrp, xInputA, pInA);
}
if( rc==SQLITE_OK ){
- rc = sqlite3changegroup_add_strm(pGrp, xInputB, pInB);
+ rc = tdsqlite3changegroup_add_strm(pGrp, xInputB, pInB);
}
if( rc==SQLITE_OK ){
- rc = sqlite3changegroup_output_strm(pGrp, xOutput, pOut);
+ rc = tdsqlite3changegroup_output_strm(pGrp, xOutput, pOut);
}
- sqlite3changegroup_delete(pGrp);
+ tdsqlite3changegroup_delete(pGrp);
return rc;
}
-#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
-
-/************** End of sqlite3session.c **************************************/
-/************** Begin file json1.c *******************************************/
-/*
-** 2015-08-12
-**
-** The author disclaims copyright to this source code. In place of
-** a legal notice, here is a blessing:
-**
-** May you do good and not evil.
-** May you find forgiveness for yourself and forgive others.
-** May you share freely, never taking more than you give.
-**
-******************************************************************************
-**
-** This SQLite extension implements JSON functions. The interface is
-** modeled after MySQL JSON functions:
-**
-** https://dev.mysql.com/doc/refman/5.7/en/json.html
-**
-** For the time being, all JSON is stored as pure text. (We might add
-** a JSONB type in the future which stores a binary encoding of JSON in
-** a BLOB, but there is no support for JSONB in the current implementation.
-** This implementation parses JSON text at 250 MB/s, so it is hard to see
-** how JSONB might improve on that.)
-*/
-#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_JSON1)
-#if !defined(_SQLITEINT_H_)
-/* #include "sqlite3ext.h" */
-#endif
-SQLITE_EXTENSION_INIT1
-/* #include <assert.h> */
-/* #include <string.h> */
-/* #include <stdlib.h> */
-/* #include <stdarg.h> */
-
-/* Mark a function parameter as unused, to suppress nuisance compiler
-** warnings. */
-#ifndef UNUSED_PARAM
-# define UNUSED_PARAM(X) (void)(X)
-#endif
-
-#ifndef LARGEST_INT64
-# define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32))
-# define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64)
-#endif
-
-/*
-** Versions of isspace(), isalnum() and isdigit() to which it is safe
-** to pass signed char values.
-*/
-#ifdef sqlite3Isdigit
- /* Use the SQLite core versions if this routine is part of the
- ** SQLite amalgamation */
-# define safe_isdigit(x) sqlite3Isdigit(x)
-# define safe_isalnum(x) sqlite3Isalnum(x)
-# define safe_isxdigit(x) sqlite3Isxdigit(x)
-#else
- /* Use the standard library for separate compilation */
-#include <ctype.h> /* amalgamator: keep */
-# define safe_isdigit(x) isdigit((unsigned char)(x))
-# define safe_isalnum(x) isalnum((unsigned char)(x))
-# define safe_isxdigit(x) isxdigit((unsigned char)(x))
-#endif
-
-/*
-** Growing our own isspace() routine this way is twice as fast as
-** the library isspace() function, resulting in a 7% overall performance
-** increase for the parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os).
-*/
-static const char jsonIsSpace[] = {
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-};
-#define safe_isspace(x) (jsonIsSpace[(unsigned char)x])
-
-#ifndef SQLITE_AMALGAMATION
- /* Unsigned integer types. These are already defined in the sqliteInt.h,
- ** but the definitions need to be repeated for separate compilation. */
- typedef sqlite3_uint64 u64;
- typedef unsigned int u32;
- typedef unsigned char u8;
-#endif
-
-/* Objects */
-typedef struct JsonString JsonString;
-typedef struct JsonNode JsonNode;
-typedef struct JsonParse JsonParse;
-
-/* An instance of this object represents a JSON string
-** under construction. Really, this is a generic string accumulator
-** that can be and is used to create strings other than JSON.
-*/
-struct JsonString {
- sqlite3_context *pCtx; /* Function context - put error messages here */
- char *zBuf; /* Append JSON content here */
- u64 nAlloc; /* Bytes of storage available in zBuf[] */
- u64 nUsed; /* Bytes of zBuf[] currently used */
- u8 bStatic; /* True if zBuf is static space */
- u8 bErr; /* True if an error has been encountered */
- char zSpace[100]; /* Initial static space */
-};
-
-/* JSON type values
-*/
-#define JSON_NULL 0
-#define JSON_TRUE 1
-#define JSON_FALSE 2
-#define JSON_INT 3
-#define JSON_REAL 4
-#define JSON_STRING 5
-#define JSON_ARRAY 6
-#define JSON_OBJECT 7
-
-/* The "subtype" set for JSON values */
-#define JSON_SUBTYPE 74 /* Ascii for "J" */
-
/*
-** Names of the various JSON types:
-*/
-static const char * const jsonType[] = {
- "null", "true", "false", "integer", "real", "text", "array", "object"
-};
-
-/* Bit values for the JsonNode.jnFlag field
-*/
-#define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */
-#define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */
-#define JNODE_REMOVE 0x04 /* Do not output */
-#define JNODE_REPLACE 0x08 /* Replace with JsonNode.iVal */
-#define JNODE_APPEND 0x10 /* More ARRAY/OBJECT entries at u.iAppend */
-#define JNODE_LABEL 0x20 /* Is a label of an object */
-
-
-/* A single node of parsed JSON
+** Changeset rebaser handle.
*/
-struct JsonNode {
- u8 eType; /* One of the JSON_ type values */
- u8 jnFlags; /* JNODE flags */
- u8 iVal; /* Replacement value when JNODE_REPLACE */
- u32 n; /* Bytes of content, or number of sub-nodes */
- union {
- const char *zJContent; /* Content for INT, REAL, and STRING */
- u32 iAppend; /* More terms for ARRAY and OBJECT */
- u32 iKey; /* Key for ARRAY objects in json_tree() */
- } u;
-};
-
-/* A completely parsed JSON string
-*/
-struct JsonParse {
- u32 nNode; /* Number of slots of aNode[] used */
- u32 nAlloc; /* Number of slots of aNode[] allocated */
- JsonNode *aNode; /* Array of nodes containing the parse */
- const char *zJson; /* Original JSON string */
- u32 *aUp; /* Index of parent of each node */
- u8 oom; /* Set to true if out of memory */
- u8 nErr; /* Number of errors seen */
+struct tdsqlite3_rebaser {
+ tdsqlite3_changegroup grp; /* Hash table */
};
-/**************************************************************************
-** Utility routines for dealing with JsonString objects
-**************************************************************************/
-
-/* Set the JsonString object to an empty string
-*/
-static void jsonZero(JsonString *p){
- p->zBuf = p->zSpace;
- p->nAlloc = sizeof(p->zSpace);
- p->nUsed = 0;
- p->bStatic = 1;
-}
-
-/* Initialize the JsonString object
-*/
-static void jsonInit(JsonString *p, sqlite3_context *pCtx){
- p->pCtx = pCtx;
- p->bErr = 0;
- jsonZero(p);
-}
-
-
-/* Free all allocated memory and reset the JsonString object back to its
-** initial state.
-*/
-static void jsonReset(JsonString *p){
- if( !p->bStatic ) sqlite3_free(p->zBuf);
- jsonZero(p);
-}
-
-
-/* Report an out-of-memory (OOM) condition
-*/
-static void jsonOom(JsonString *p){
- p->bErr = 1;
- sqlite3_result_error_nomem(p->pCtx);
- jsonReset(p);
-}
-
-/* Enlarge pJson->zBuf so that it can hold at least N more bytes.
-** Return zero on success. Return non-zero on an OOM error
-*/
-static int jsonGrow(JsonString *p, u32 N){
- u64 nTotal = N<p->nAlloc ? p->nAlloc*2 : p->nAlloc+N+10;
- char *zNew;
- if( p->bStatic ){
- if( p->bErr ) return 1;
- zNew = sqlite3_malloc64(nTotal);
- if( zNew==0 ){
- jsonOom(p);
- return SQLITE_NOMEM;
- }
- memcpy(zNew, p->zBuf, (size_t)p->nUsed);
- p->zBuf = zNew;
- p->bStatic = 0;
- }else{
- zNew = sqlite3_realloc64(p->zBuf, nTotal);
- if( zNew==0 ){
- jsonOom(p);
- return SQLITE_NOMEM;
- }
- p->zBuf = zNew;
- }
- p->nAlloc = nTotal;
- return SQLITE_OK;
-}
-
-/* Append N bytes from zIn onto the end of the JsonString string.
-*/
-static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){
- if( (N+p->nUsed >= p->nAlloc) && jsonGrow(p,N)!=0 ) return;
- memcpy(p->zBuf+p->nUsed, zIn, N);
- p->nUsed += N;
-}
-
-/* Append formatted text (not to exceed N bytes) to the JsonString.
-*/
-static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){
- va_list ap;
- if( (p->nUsed + N >= p->nAlloc) && jsonGrow(p, N) ) return;
- va_start(ap, zFormat);
- sqlite3_vsnprintf(N, p->zBuf+p->nUsed, zFormat, ap);
- va_end(ap);
- p->nUsed += (int)strlen(p->zBuf+p->nUsed);
-}
-
-/* Append a single character
-*/
-static void jsonAppendChar(JsonString *p, char c){
- if( p->nUsed>=p->nAlloc && jsonGrow(p,1)!=0 ) return;
- p->zBuf[p->nUsed++] = c;
-}
-
-/* Append a comma separator to the output buffer, if the previous
-** character is not '[' or '{'.
-*/
-static void jsonAppendSeparator(JsonString *p){
- char c;
- if( p->nUsed==0 ) return;
- c = p->zBuf[p->nUsed-1];
- if( c!='[' && c!='{' ) jsonAppendChar(p, ',');
-}
-
-/* Append the N-byte string in zIn to the end of the JsonString string
-** under construction. Enclose the string in "..." and escape
-** any double-quotes or backslash characters contained within the
-** string.
-*/
-static void jsonAppendString(JsonString *p, const char *zIn, u32 N){
- u32 i;
- if( (N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0 ) return;
- p->zBuf[p->nUsed++] = '"';
- for(i=0; i<N; i++){
- unsigned char c = ((unsigned const char*)zIn)[i];
- if( c=='"' || c=='\\' ){
- json_simple_escape:
- if( (p->nUsed+N+3-i > p->nAlloc) && jsonGrow(p,N+3-i)!=0 ) return;
- p->zBuf[p->nUsed++] = '\\';
- }else if( c<=0x1f ){
- static const char aSpecial[] = {
- 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- };
- assert( sizeof(aSpecial)==32 );
- assert( aSpecial['\b']=='b' );
- assert( aSpecial['\f']=='f' );
- assert( aSpecial['\n']=='n' );
- assert( aSpecial['\r']=='r' );
- assert( aSpecial['\t']=='t' );
- if( aSpecial[c] ){
- c = aSpecial[c];
- goto json_simple_escape;
- }
- if( (p->nUsed+N+7+i > p->nAlloc) && jsonGrow(p,N+7-i)!=0 ) return;
- p->zBuf[p->nUsed++] = '\\';
- p->zBuf[p->nUsed++] = 'u';
- p->zBuf[p->nUsed++] = '0';
- p->zBuf[p->nUsed++] = '0';
- p->zBuf[p->nUsed++] = '0' + (c>>4);
- c = "0123456789abcdef"[c&0xf];
- }
- p->zBuf[p->nUsed++] = c;
- }
- p->zBuf[p->nUsed++] = '"';
- assert( p->nUsed<p->nAlloc );
-}
-
/*
-** Append a function parameter value to the JSON string under
-** construction.
+** Buffers a1 and a2 must both contain a sessions module record nCol
+** fields in size. This function appends an nCol sessions module
+** record to buffer pBuf that is a copy of a1, except that for
+** each field that is undefined in a1[], swap in the field from a2[].
*/
-static void jsonAppendValue(
- JsonString *p, /* Append to this JSON string */
- sqlite3_value *pValue /* Value to append */
+static void sessionAppendRecordMerge(
+ SessionBuffer *pBuf, /* Buffer to append to */
+ int nCol, /* Number of columns in each record */
+ u8 *a1, int n1, /* Record 1 */
+ u8 *a2, int n2, /* Record 2 */
+ int *pRc /* IN/OUT: error code */
){
- switch( sqlite3_value_type(pValue) ){
- case SQLITE_NULL: {
- jsonAppendRaw(p, "null", 4);
- break;
- }
- case SQLITE_INTEGER:
- case SQLITE_FLOAT: {
- const char *z = (const char*)sqlite3_value_text(pValue);
- u32 n = (u32)sqlite3_value_bytes(pValue);
- jsonAppendRaw(p, z, n);
- break;
- }
- case SQLITE_TEXT: {
- const char *z = (const char*)sqlite3_value_text(pValue);
- u32 n = (u32)sqlite3_value_bytes(pValue);
- if( sqlite3_value_subtype(pValue)==JSON_SUBTYPE ){
- jsonAppendRaw(p, z, n);
+ sessionBufferGrow(pBuf, n1+n2, pRc);
+ if( *pRc==SQLITE_OK ){
+ int i;
+ u8 *pOut = &pBuf->aBuf[pBuf->nBuf];
+ for(i=0; i<nCol; i++){
+ int nn1 = sessionSerialLen(a1);
+ int nn2 = sessionSerialLen(a2);
+ if( *a1==0 || *a1==0xFF ){
+ memcpy(pOut, a2, nn2);
+ pOut += nn2;
}else{
- jsonAppendString(p, z, n);
- }
- break;
- }
- default: {
- if( p->bErr==0 ){
- sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1);
- p->bErr = 2;
- jsonReset(p);
+ memcpy(pOut, a1, nn1);
+ pOut += nn1;
}
- break;
+ a1 += nn1;
+ a2 += nn2;
}
- }
-}
-
-/* Make the JSON in p the result of the SQL function.
-*/
-static void jsonResult(JsonString *p){
- if( p->bErr==0 ){
- sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed,
- p->bStatic ? SQLITE_TRANSIENT : sqlite3_free,
- SQLITE_UTF8);
- jsonZero(p);
+ pBuf->nBuf = pOut-pBuf->aBuf;
+ assert( pBuf->nBuf<=pBuf->nAlloc );
}
- assert( p->bStatic );
}
-/**************************************************************************
-** Utility routines for dealing with JsonNode and JsonParse objects
-**************************************************************************/
-
/*
-** Return the number of consecutive JsonNode slots need to represent
-** the parsed JSON at pNode. The minimum answer is 1. For ARRAY and
-** OBJECT types, the number might be larger.
+** This function is called when rebasing a local UPDATE change against one
+** or more remote UPDATE changes. The aRec/nRec buffer contains the current
+** old.* and new.* records for the change. The rebase buffer (a single
+** record) is in aChange/nChange. The rebased change is appended to buffer
+** pBuf.
**
-** Appended elements are not counted. The value returned is the number
-** by which the JsonNode counter should increment in order to go to the
-** next peer value.
-*/
-static u32 jsonNodeSize(JsonNode *pNode){
- return pNode->eType>=JSON_ARRAY ? pNode->n+1 : 1;
-}
-
-/*
-** Reclaim all memory allocated by a JsonParse object. But do not
-** delete the JsonParse object itself.
-*/
-static void jsonParseReset(JsonParse *pParse){
- sqlite3_free(pParse->aNode);
- pParse->aNode = 0;
- pParse->nNode = 0;
- pParse->nAlloc = 0;
- sqlite3_free(pParse->aUp);
- pParse->aUp = 0;
-}
-
-/*
-** Convert the JsonNode pNode into a pure JSON string and
-** append to pOut. Subsubstructure is also included. Return
-** the number of JsonNode objects that are encoded.
-*/
-static void jsonRenderNode(
- JsonNode *pNode, /* The node to render */
- JsonString *pOut, /* Write JSON here */
- sqlite3_value **aReplace /* Replacement values */
-){
- switch( pNode->eType ){
- default: {
- assert( pNode->eType==JSON_NULL );
- jsonAppendRaw(pOut, "null", 4);
- break;
- }
- case JSON_TRUE: {
- jsonAppendRaw(pOut, "true", 4);
- break;
- }
- case JSON_FALSE: {
- jsonAppendRaw(pOut, "false", 5);
- break;
- }
- case JSON_STRING: {
- if( pNode->jnFlags & JNODE_RAW ){
- jsonAppendString(pOut, pNode->u.zJContent, pNode->n);
- break;
- }
- /* Fall through into the next case */
- }
- case JSON_REAL:
- case JSON_INT: {
- jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n);
- break;
- }
- case JSON_ARRAY: {
- u32 j = 1;
- jsonAppendChar(pOut, '[');
- for(;;){
- while( j<=pNode->n ){
- if( pNode[j].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ){
- if( pNode[j].jnFlags & JNODE_REPLACE ){
- jsonAppendSeparator(pOut);
- jsonAppendValue(pOut, aReplace[pNode[j].iVal]);
- }
- }else{
- jsonAppendSeparator(pOut);
- jsonRenderNode(&pNode[j], pOut, aReplace);
- }
- j += jsonNodeSize(&pNode[j]);
- }
- if( (pNode->jnFlags & JNODE_APPEND)==0 ) break;
- pNode = &pNode[pNode->u.iAppend];
- j = 1;
- }
- jsonAppendChar(pOut, ']');
- break;
- }
- case JSON_OBJECT: {
- u32 j = 1;
- jsonAppendChar(pOut, '{');
- for(;;){
- while( j<=pNode->n ){
- if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 ){
- jsonAppendSeparator(pOut);
- jsonRenderNode(&pNode[j], pOut, aReplace);
- jsonAppendChar(pOut, ':');
- if( pNode[j+1].jnFlags & JNODE_REPLACE ){
- jsonAppendValue(pOut, aReplace[pNode[j+1].iVal]);
- }else{
- jsonRenderNode(&pNode[j+1], pOut, aReplace);
- }
- }
- j += 1 + jsonNodeSize(&pNode[j+1]);
- }
- if( (pNode->jnFlags & JNODE_APPEND)==0 ) break;
- pNode = &pNode[pNode->u.iAppend];
- j = 1;
- }
- jsonAppendChar(pOut, '}');
- break;
- }
- }
-}
-
-/*
-** Return a JsonNode and all its descendents as a JSON string.
+** Rebasing the UPDATE involves:
+**
+** * Removing any changes to fields for which the corresponding field
+** in the rebase buffer is set to "replaced" (type 0xFF). If this
+** means the UPDATE change updates no fields, nothing is appended
+** to the output buffer.
+**
+** * For each field modified by the local change for which the
+** corresponding field in the rebase buffer is not "undefined" (0x00)
+** or "replaced" (0xFF), the old.* value is replaced by the value
+** in the rebase buffer.
*/
-static void jsonReturnJson(
- JsonNode *pNode, /* Node to return */
- sqlite3_context *pCtx, /* Return value for this function */
- sqlite3_value **aReplace /* Array of replacement values */
+static void sessionAppendPartialUpdate(
+ SessionBuffer *pBuf, /* Append record here */
+ tdsqlite3_changeset_iter *pIter, /* Iterator pointed at local change */
+ u8 *aRec, int nRec, /* Local change */
+ u8 *aChange, int nChange, /* Record to rebase against */
+ int *pRc /* IN/OUT: Return Code */
){
- JsonString s;
- jsonInit(&s, pCtx);
- jsonRenderNode(pNode, &s, aReplace);
- jsonResult(&s);
- sqlite3_result_subtype(pCtx, JSON_SUBTYPE);
-}
+ sessionBufferGrow(pBuf, 2+nRec+nChange, pRc);
+ if( *pRc==SQLITE_OK ){
+ int bData = 0;
+ u8 *pOut = &pBuf->aBuf[pBuf->nBuf];
+ int i;
+ u8 *a1 = aRec;
+ u8 *a2 = aChange;
-/*
-** Make the JsonNode the return value of the function.
-*/
-static void jsonReturn(
- JsonNode *pNode, /* Node to return */
- sqlite3_context *pCtx, /* Return value for this function */
- sqlite3_value **aReplace /* Array of replacement values */
-){
- switch( pNode->eType ){
- default: {
- assert( pNode->eType==JSON_NULL );
- sqlite3_result_null(pCtx);
- break;
- }
- case JSON_TRUE: {
- sqlite3_result_int(pCtx, 1);
- break;
- }
- case JSON_FALSE: {
- sqlite3_result_int(pCtx, 0);
- break;
- }
- case JSON_INT: {
- sqlite3_int64 i = 0;
- const char *z = pNode->u.zJContent;
- if( z[0]=='-' ){ z++; }
- while( z[0]>='0' && z[0]<='9' ){
- unsigned v = *(z++) - '0';
- if( i>=LARGEST_INT64/10 ){
- if( i>LARGEST_INT64/10 ) goto int_as_real;
- if( z[0]>='0' && z[0]<='9' ) goto int_as_real;
- if( v==9 ) goto int_as_real;
- if( v==8 ){
- if( pNode->u.zJContent[0]=='-' ){
- sqlite3_result_int64(pCtx, SMALLEST_INT64);
- goto int_done;
- }else{
- goto int_as_real;
- }
- }
- }
- i = i*10 + v;
- }
- if( pNode->u.zJContent[0]=='-' ){ i = -i; }
- sqlite3_result_int64(pCtx, i);
- int_done:
- break;
- int_as_real: /* fall through to real */;
- }
- case JSON_REAL: {
- double r;
-#ifdef SQLITE_AMALGAMATION
- const char *z = pNode->u.zJContent;
- sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8);
-#else
- r = strtod(pNode->u.zJContent, 0);
-#endif
- sqlite3_result_double(pCtx, r);
- break;
- }
- case JSON_STRING: {
-#if 0 /* Never happens because JNODE_RAW is only set by json_set(),
- ** json_insert() and json_replace() and those routines do not
- ** call jsonReturn() */
- if( pNode->jnFlags & JNODE_RAW ){
- sqlite3_result_text(pCtx, pNode->u.zJContent, pNode->n,
- SQLITE_TRANSIENT);
- }else
-#endif
- assert( (pNode->jnFlags & JNODE_RAW)==0 );
- if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){
- /* JSON formatted without any backslash-escapes */
- sqlite3_result_text(pCtx, pNode->u.zJContent+1, pNode->n-2,
- SQLITE_TRANSIENT);
+ *pOut++ = SQLITE_UPDATE;
+ *pOut++ = pIter->bIndirect;
+ for(i=0; i<pIter->nCol; i++){
+ int n1 = sessionSerialLen(a1);
+ int n2 = sessionSerialLen(a2);
+ if( pIter->abPK[i] || a2[0]==0 ){
+ if( !pIter->abPK[i] ) bData = 1;
+ memcpy(pOut, a1, n1);
+ pOut += n1;
+ }else if( a2[0]!=0xFF ){
+ bData = 1;
+ memcpy(pOut, a2, n2);
+ pOut += n2;
}else{
- /* Translate JSON formatted string into raw text */
- u32 i;
- u32 n = pNode->n;
- const char *z = pNode->u.zJContent;
- char *zOut;
- u32 j;
- zOut = sqlite3_malloc( n+1 );
- if( zOut==0 ){
- sqlite3_result_error_nomem(pCtx);
- break;
- }
- for(i=1, j=0; i<n-1; i++){
- char c = z[i];
- if( c!='\\' ){
- zOut[j++] = c;
- }else{
- c = z[++i];
- if( c=='u' ){
- u32 v = 0, k;
- for(k=0; k<4 && i<n-2; i++, k++){
- c = z[i+1];
- if( c>='0' && c<='9' ) v = v*16 + c - '0';
- else if( c>='A' && c<='F' ) v = v*16 + c - 'A' + 10;
- else if( c>='a' && c<='f' ) v = v*16 + c - 'a' + 10;
- else break;
- }
- if( v==0 ) break;
- if( v<=0x7f ){
- zOut[j++] = (char)v;
- }else if( v<=0x7ff ){
- zOut[j++] = (char)(0xc0 | (v>>6));
- zOut[j++] = 0x80 | (v&0x3f);
- }else{
- zOut[j++] = (char)(0xe0 | (v>>12));
- zOut[j++] = 0x80 | ((v>>6)&0x3f);
- zOut[j++] = 0x80 | (v&0x3f);
- }
- }else{
- if( c=='b' ){
- c = '\b';
- }else if( c=='f' ){
- c = '\f';
- }else if( c=='n' ){
- c = '\n';
- }else if( c=='r' ){
- c = '\r';
- }else if( c=='t' ){
- c = '\t';
- }
- zOut[j++] = c;
- }
- }
- }
- zOut[j] = 0;
- sqlite3_result_text(pCtx, zOut, j, sqlite3_free);
+ *pOut++ = '\0';
}
- break;
- }
- case JSON_ARRAY:
- case JSON_OBJECT: {
- jsonReturnJson(pNode, pCtx, aReplace);
- break;
- }
- }
-}
-
-/* Forward reference */
-static int jsonParseAddNode(JsonParse*,u32,u32,const char*);
-
-/*
-** A macro to hint to the compiler that a function should not be
-** inlined.
-*/
-#if defined(__GNUC__)
-# define JSON_NOINLINE __attribute__((noinline))
-#elif defined(_MSC_VER) && _MSC_VER>=1310
-# define JSON_NOINLINE __declspec(noinline)
-#else
-# define JSON_NOINLINE
-#endif
-
-
-static JSON_NOINLINE int jsonParseAddNodeExpand(
- JsonParse *pParse, /* Append the node to this object */
- u32 eType, /* Node type */
- u32 n, /* Content size or sub-node count */
- const char *zContent /* Content */
-){
- u32 nNew;
- JsonNode *pNew;
- assert( pParse->nNode>=pParse->nAlloc );
- if( pParse->oom ) return -1;
- nNew = pParse->nAlloc*2 + 10;
- pNew = sqlite3_realloc(pParse->aNode, sizeof(JsonNode)*nNew);
- if( pNew==0 ){
- pParse->oom = 1;
- return -1;
- }
- pParse->nAlloc = nNew;
- pParse->aNode = pNew;
- assert( pParse->nNode<pParse->nAlloc );
- return jsonParseAddNode(pParse, eType, n, zContent);
-}
-
-/*
-** Create a new JsonNode instance based on the arguments and append that
-** instance to the JsonParse. Return the index in pParse->aNode[] of the
-** new node, or -1 if a memory allocation fails.
-*/
-static int jsonParseAddNode(
- JsonParse *pParse, /* Append the node to this object */
- u32 eType, /* Node type */
- u32 n, /* Content size or sub-node count */
- const char *zContent /* Content */
-){
- JsonNode *p;
- if( pParse->nNode>=pParse->nAlloc ){
- return jsonParseAddNodeExpand(pParse, eType, n, zContent);
- }
- p = &pParse->aNode[pParse->nNode];
- p->eType = (u8)eType;
- p->jnFlags = 0;
- p->iVal = 0;
- p->n = n;
- p->u.zJContent = zContent;
- return pParse->nNode++;
-}
-
-/*
-** Return true if z[] begins with 4 (or more) hexadecimal digits
-*/
-static int jsonIs4Hex(const char *z){
- int i;
- for(i=0; i<4; i++) if( !safe_isxdigit(z[i]) ) return 0;
- return 1;
-}
-
-/*
-** Parse a single JSON value which begins at pParse->zJson[i]. Return the
-** index of the first character past the end of the value parsed.
-**
-** Return negative for a syntax error. Special cases: return -2 if the
-** first non-whitespace character is '}' and return -3 if the first
-** non-whitespace character is ']'.
-*/
-static int jsonParseValue(JsonParse *pParse, u32 i){
- char c;
- u32 j;
- int iThis;
- int x;
- JsonNode *pNode;
- while( safe_isspace(pParse->zJson[i]) ){ i++; }
- if( (c = pParse->zJson[i])=='{' ){
- /* Parse object */
- iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0);
- if( iThis<0 ) return -1;
- for(j=i+1;;j++){
- while( safe_isspace(pParse->zJson[j]) ){ j++; }
- x = jsonParseValue(pParse, j);
- if( x<0 ){
- if( x==(-2) && pParse->nNode==(u32)iThis+1 ) return j+1;
- return -1;
- }
- if( pParse->oom ) return -1;
- pNode = &pParse->aNode[pParse->nNode-1];
- if( pNode->eType!=JSON_STRING ) return -1;
- pNode->jnFlags |= JNODE_LABEL;
- j = x;
- while( safe_isspace(pParse->zJson[j]) ){ j++; }
- if( pParse->zJson[j]!=':' ) return -1;
- j++;
- x = jsonParseValue(pParse, j);
- if( x<0 ) return -1;
- j = x;
- while( safe_isspace(pParse->zJson[j]) ){ j++; }
- c = pParse->zJson[j];
- if( c==',' ) continue;
- if( c!='}' ) return -1;
- break;
- }
- pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1;
- return j+1;
- }else if( c=='[' ){
- /* Parse array */
- iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0);
- if( iThis<0 ) return -1;
- for(j=i+1;;j++){
- while( safe_isspace(pParse->zJson[j]) ){ j++; }
- x = jsonParseValue(pParse, j);
- if( x<0 ){
- if( x==(-3) && pParse->nNode==(u32)iThis+1 ) return j+1;
- return -1;
- }
- j = x;
- while( safe_isspace(pParse->zJson[j]) ){ j++; }
- c = pParse->zJson[j];
- if( c==',' ) continue;
- if( c!=']' ) return -1;
- break;
+ a1 += n1;
+ a2 += n2;
}
- pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1;
- return j+1;
- }else if( c=='"' ){
- /* Parse string */
- u8 jnFlags = 0;
- j = i+1;
- for(;;){
- c = pParse->zJson[j];
- if( c==0 ) return -1;
- if( c=='\\' ){
- c = pParse->zJson[++j];
- if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f'
- || c=='n' || c=='r' || c=='t'
- || (c=='u' && jsonIs4Hex(pParse->zJson+j+1)) ){
- jnFlags = JNODE_ESCAPE;
+ if( bData ){
+ a2 = aChange;
+ for(i=0; i<pIter->nCol; i++){
+ int n1 = sessionSerialLen(a1);
+ int n2 = sessionSerialLen(a2);
+ if( pIter->abPK[i] || a2[0]!=0xFF ){
+ memcpy(pOut, a1, n1);
+ pOut += n1;
}else{
- return -1;
- }
- }else if( c=='"' ){
- break;
- }
- j++;
- }
- jsonParseAddNode(pParse, JSON_STRING, j+1-i, &pParse->zJson[i]);
- if( !pParse->oom ) pParse->aNode[pParse->nNode-1].jnFlags = jnFlags;
- return j+1;
- }else if( c=='n'
- && strncmp(pParse->zJson+i,"null",4)==0
- && !safe_isalnum(pParse->zJson[i+4]) ){
- jsonParseAddNode(pParse, JSON_NULL, 0, 0);
- return i+4;
- }else if( c=='t'
- && strncmp(pParse->zJson+i,"true",4)==0
- && !safe_isalnum(pParse->zJson[i+4]) ){
- jsonParseAddNode(pParse, JSON_TRUE, 0, 0);
- return i+4;
- }else if( c=='f'
- && strncmp(pParse->zJson+i,"false",5)==0
- && !safe_isalnum(pParse->zJson[i+5]) ){
- jsonParseAddNode(pParse, JSON_FALSE, 0, 0);
- return i+5;
- }else if( c=='-' || (c>='0' && c<='9') ){
- /* Parse number */
- u8 seenDP = 0;
- u8 seenE = 0;
- j = i+1;
- for(;; j++){
- c = pParse->zJson[j];
- if( c>='0' && c<='9' ) continue;
- if( c=='.' ){
- if( pParse->zJson[j-1]=='-' ) return -1;
- if( seenDP ) return -1;
- seenDP = 1;
- continue;
- }
- if( c=='e' || c=='E' ){
- if( pParse->zJson[j-1]<'0' ) return -1;
- if( seenE ) return -1;
- seenDP = seenE = 1;
- c = pParse->zJson[j+1];
- if( c=='+' || c=='-' ){
- j++;
- c = pParse->zJson[j+1];
+ *pOut++ = '\0';
}
- if( c<'0' || c>'9' ) return -1;
- continue;
+ a1 += n1;
+ a2 += n2;
}
- break;
+ pBuf->nBuf = (pOut - pBuf->aBuf);
}
- if( pParse->zJson[j-1]<'0' ) return -1;
- jsonParseAddNode(pParse, seenDP ? JSON_REAL : JSON_INT,
- j - i, &pParse->zJson[i]);
- return j;
- }else if( c=='}' ){
- return -2; /* End of {...} */
- }else if( c==']' ){
- return -3; /* End of [...] */
- }else if( c==0 ){
- return 0; /* End of file */
- }else{
- return -1; /* Syntax error */
}
}
/*
-** Parse a complete JSON string. Return 0 on success or non-zero if there
-** are any errors. If an error occurs, free all memory associated with
-** pParse.
+** pIter is configured to iterate through a changeset. This function rebases
+** that changeset according to the current configuration of the rebaser
+** object passed as the first argument. If no error occurs and argument xOutput
+** is not NULL, then the changeset is returned to the caller by invoking
+** xOutput zero or more times and SQLITE_OK returned. Or, if xOutput is NULL,
+** then (*ppOut) is set to point to a buffer containing the rebased changeset
+** before this function returns. In this case (*pnOut) is set to the size of
+** the buffer in bytes. It is the responsibility of the caller to eventually
+** free the (*ppOut) buffer using tdsqlite3_free().
**
-** pParse is uninitialized when this routine is called.
+** If an error occurs, an SQLite error code is returned. If ppOut and
+** pnOut are not NULL, then the two output parameters are set to 0 before
+** returning.
*/
-static int jsonParse(
- JsonParse *pParse, /* Initialize and fill this JsonParse object */
- sqlite3_context *pCtx, /* Report errors here */
- const char *zJson /* Input JSON text to be parsed */
+static int sessionRebase(
+ tdsqlite3_rebaser *p, /* Rebaser hash table */
+ tdsqlite3_changeset_iter *pIter, /* Input data */
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut, /* Context for xOutput callback */
+ int *pnOut, /* OUT: Number of bytes in output changeset */
+ void **ppOut /* OUT: Inverse of pChangeset */
){
- int i;
- memset(pParse, 0, sizeof(*pParse));
- if( zJson==0 ) return 1;
- pParse->zJson = zJson;
- i = jsonParseValue(pParse, 0);
- if( pParse->oom ) i = -1;
- if( i>0 ){
- while( safe_isspace(zJson[i]) ) i++;
- if( zJson[i] ) i = -1;
- }
- if( i<=0 ){
- if( pCtx!=0 ){
- if( pParse->oom ){
- sqlite3_result_error_nomem(pCtx);
- }else{
- sqlite3_result_error(pCtx, "malformed JSON", -1);
- }
- }
- jsonParseReset(pParse);
- return 1;
- }
- return 0;
-}
+ int rc = SQLITE_OK;
+ u8 *aRec = 0;
+ int nRec = 0;
+ int bNew = 0;
+ SessionTable *pTab = 0;
+ SessionBuffer sOut = {0,0,0};
-/* Mark node i of pParse as being a child of iParent. Call recursively
-** to fill in all the descendants of node i.
-*/
-static void jsonParseFillInParentage(JsonParse *pParse, u32 i, u32 iParent){
- JsonNode *pNode = &pParse->aNode[i];
- u32 j;
- pParse->aUp[i] = iParent;
- switch( pNode->eType ){
- case JSON_ARRAY: {
- for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j)){
- jsonParseFillInParentage(pParse, i+j, i);
- }
- break;
- }
- case JSON_OBJECT: {
- for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j+1)+1){
- pParse->aUp[i+j] = i;
- jsonParseFillInParentage(pParse, i+j+1, i);
+ while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){
+ SessionChange *pChange = 0;
+ int bDone = 0;
+
+ if( bNew ){
+ const char *zTab = pIter->zTab;
+ for(pTab=p->grp.pList; pTab; pTab=pTab->pNext){
+ if( 0==tdsqlite3_stricmp(pTab->zName, zTab) ) break;
}
- break;
- }
- default: {
- break;
- }
- }
-}
+ bNew = 0;
-/*
-** Compute the parentage of all nodes in a completed parse.
-*/
-static int jsonParseFindParents(JsonParse *pParse){
- u32 *aUp;
- assert( pParse->aUp==0 );
- aUp = pParse->aUp = sqlite3_malloc( sizeof(u32)*pParse->nNode );
- if( aUp==0 ){
- pParse->oom = 1;
- return SQLITE_NOMEM;
- }
- jsonParseFillInParentage(pParse, 0, 0);
- return SQLITE_OK;
-}
+ /* A patchset may not be rebased */
+ if( pIter->bPatchset ){
+ rc = SQLITE_ERROR;
+ }
-/*
-** Compare the OBJECT label at pNode against zKey,nKey. Return true on
-** a match.
-*/
-static int jsonLabelCompare(JsonNode *pNode, const char *zKey, u32 nKey){
- if( pNode->jnFlags & JNODE_RAW ){
- if( pNode->n!=nKey ) return 0;
- return strncmp(pNode->u.zJContent, zKey, nKey)==0;
- }else{
- if( pNode->n!=nKey+2 ) return 0;
- return strncmp(pNode->u.zJContent+1, zKey, nKey)==0;
- }
-}
+ /* Append a table header to the output for this new table */
+ sessionAppendByte(&sOut, pIter->bPatchset ? 'P' : 'T', &rc);
+ sessionAppendVarint(&sOut, pIter->nCol, &rc);
+ sessionAppendBlob(&sOut, pIter->abPK, pIter->nCol, &rc);
+ sessionAppendBlob(&sOut,(u8*)pIter->zTab,(int)strlen(pIter->zTab)+1,&rc);
+ }
-/* forward declaration */
-static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*,const char**);
+ if( pTab && rc==SQLITE_OK ){
+ int iHash = sessionChangeHash(pTab, 0, aRec, pTab->nChange);
-/*
-** Search along zPath to find the node specified. Return a pointer
-** to that node, or NULL if zPath is malformed or if there is no such
-** node.
-**
-** If pApnd!=0, then try to append new nodes to complete zPath if it is
-** possible to do so and if no existing node corresponds to zPath. If
-** new nodes are appended *pApnd is set to 1.
-*/
-static JsonNode *jsonLookupStep(
- JsonParse *pParse, /* The JSON to search */
- u32 iRoot, /* Begin the search at this node */
- const char *zPath, /* The path to search */
- int *pApnd, /* Append nodes to complete path if not NULL */
- const char **pzErr /* Make *pzErr point to any syntax error in zPath */
-){
- u32 i, j, nKey;
- const char *zKey;
- JsonNode *pRoot = &pParse->aNode[iRoot];
- if( zPath[0]==0 ) return pRoot;
- if( zPath[0]=='.' ){
- if( pRoot->eType!=JSON_OBJECT ) return 0;
- zPath++;
- if( zPath[0]=='"' ){
- zKey = zPath + 1;
- for(i=1; zPath[i] && zPath[i]!='"'; i++){}
- nKey = i-1;
- if( zPath[i] ){
- i++;
- }else{
- *pzErr = zPath;
- return 0;
- }
- }else{
- zKey = zPath;
- for(i=0; zPath[i] && zPath[i]!='.' && zPath[i]!='['; i++){}
- nKey = i;
- }
- if( nKey==0 ){
- *pzErr = zPath;
- return 0;
- }
- j = 1;
- for(;;){
- while( j<=pRoot->n ){
- if( jsonLabelCompare(pRoot+j, zKey, nKey) ){
- return jsonLookupStep(pParse, iRoot+j+1, &zPath[i], pApnd, pzErr);
+ for(pChange=pTab->apChange[iHash]; pChange; pChange=pChange->pNext){
+ if( sessionChangeEqual(pTab, 0, aRec, 0, pChange->aRecord) ){
+ break;
}
- j++;
- j += jsonNodeSize(&pRoot[j]);
- }
- if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break;
- iRoot += pRoot->u.iAppend;
- pRoot = &pParse->aNode[iRoot];
- j = 1;
- }
- if( pApnd ){
- u32 iStart, iLabel;
- JsonNode *pNode;
- iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0);
- iLabel = jsonParseAddNode(pParse, JSON_STRING, i, zPath);
- zPath += i;
- pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr);
- if( pParse->oom ) return 0;
- if( pNode ){
- pRoot = &pParse->aNode[iRoot];
- pRoot->u.iAppend = iStart - iRoot;
- pRoot->jnFlags |= JNODE_APPEND;
- pParse->aNode[iLabel].jnFlags |= JNODE_RAW;
- }
- return pNode;
- }
- }else if( zPath[0]=='[' && safe_isdigit(zPath[1]) ){
- if( pRoot->eType!=JSON_ARRAY ) return 0;
- i = 0;
- j = 1;
- while( safe_isdigit(zPath[j]) ){
- i = i*10 + zPath[j] - '0';
- j++;
- }
- if( zPath[j]!=']' ){
- *pzErr = zPath;
- return 0;
- }
- zPath += j + 1;
- j = 1;
- for(;;){
- while( j<=pRoot->n && (i>0 || (pRoot[j].jnFlags & JNODE_REMOVE)!=0) ){
- if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 ) i--;
- j += jsonNodeSize(&pRoot[j]);
- }
- if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break;
- iRoot += pRoot->u.iAppend;
- pRoot = &pParse->aNode[iRoot];
- j = 1;
- }
- if( j<=pRoot->n ){
- return jsonLookupStep(pParse, iRoot+j, zPath, pApnd, pzErr);
- }
- if( i==0 && pApnd ){
- u32 iStart;
- JsonNode *pNode;
- iStart = jsonParseAddNode(pParse, JSON_ARRAY, 1, 0);
- pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr);
- if( pParse->oom ) return 0;
- if( pNode ){
- pRoot = &pParse->aNode[iRoot];
- pRoot->u.iAppend = iStart - iRoot;
- pRoot->jnFlags |= JNODE_APPEND;
}
- return pNode;
- }
- }else{
- *pzErr = zPath;
- }
- return 0;
-}
-
-/*
-** Append content to pParse that will complete zPath. Return a pointer
-** to the inserted node, or return NULL if the append fails.
-*/
-static JsonNode *jsonLookupAppend(
- JsonParse *pParse, /* Append content to the JSON parse */
- const char *zPath, /* Description of content to append */
- int *pApnd, /* Set this flag to 1 */
- const char **pzErr /* Make this point to any syntax error */
-){
- *pApnd = 1;
- if( zPath[0]==0 ){
- jsonParseAddNode(pParse, JSON_NULL, 0, 0);
- return pParse->oom ? 0 : &pParse->aNode[pParse->nNode-1];
- }
- if( zPath[0]=='.' ){
- jsonParseAddNode(pParse, JSON_OBJECT, 0, 0);
- }else if( strncmp(zPath,"[0]",3)==0 ){
- jsonParseAddNode(pParse, JSON_ARRAY, 0, 0);
- }else{
- return 0;
- }
- if( pParse->oom ) return 0;
- return jsonLookupStep(pParse, pParse->nNode-1, zPath, pApnd, pzErr);
-}
-
-/*
-** Return the text of a syntax error message on a JSON path. Space is
-** obtained from sqlite3_malloc().
-*/
-static char *jsonPathSyntaxError(const char *zErr){
- return sqlite3_mprintf("JSON path error near '%q'", zErr);
-}
-
-/*
-** Do a node lookup using zPath. Return a pointer to the node on success.
-** Return NULL if not found or if there is an error.
-**
-** On an error, write an error message into pCtx and increment the
-** pParse->nErr counter.
-**
-** If pApnd!=NULL then try to append missing nodes and set *pApnd = 1 if
-** nodes are appended.
-*/
-static JsonNode *jsonLookup(
- JsonParse *pParse, /* The JSON to search */
- const char *zPath, /* The path to search */
- int *pApnd, /* Append nodes to complete path if not NULL */
- sqlite3_context *pCtx /* Report errors here, if not NULL */
-){
- const char *zErr = 0;
- JsonNode *pNode = 0;
- char *zMsg;
-
- if( zPath==0 ) return 0;
- if( zPath[0]!='$' ){
- zErr = zPath;
- goto lookup_err;
- }
- zPath++;
- pNode = jsonLookupStep(pParse, 0, zPath, pApnd, &zErr);
- if( zErr==0 ) return pNode;
-
-lookup_err:
- pParse->nErr++;
- assert( zErr!=0 && pCtx!=0 );
- zMsg = jsonPathSyntaxError(zErr);
- if( zMsg ){
- sqlite3_result_error(pCtx, zMsg, -1);
- sqlite3_free(zMsg);
- }else{
- sqlite3_result_error_nomem(pCtx);
- }
- return 0;
-}
-
-
-/*
-** Report the wrong number of arguments for json_insert(), json_replace()
-** or json_set().
-*/
-static void jsonWrongNumArgs(
- sqlite3_context *pCtx,
- const char *zFuncName
-){
- char *zMsg = sqlite3_mprintf("json_%s() needs an odd number of arguments",
- zFuncName);
- sqlite3_result_error(pCtx, zMsg, -1);
- sqlite3_free(zMsg);
-}
-
-
-/****************************************************************************
-** SQL functions used for testing and debugging
-****************************************************************************/
-
-#ifdef SQLITE_DEBUG
-/*
-** The json_parse(JSON) function returns a string which describes
-** a parse of the JSON provided. Or it returns NULL if JSON is not
-** well-formed.
-*/
-static void jsonParseFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- JsonString s; /* Output string - not real JSON */
- JsonParse x; /* The parse */
- u32 i;
-
- assert( argc==1 );
- if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
- jsonParseFindParents(&x);
- jsonInit(&s, ctx);
- for(i=0; i<x.nNode; i++){
- const char *zType;
- if( x.aNode[i].jnFlags & JNODE_LABEL ){
- assert( x.aNode[i].eType==JSON_STRING );
- zType = "label";
- }else{
- zType = jsonType[x.aNode[i].eType];
}
- jsonPrintf(100, &s,"node %3u: %7s n=%-4d up=%-4d",
- i, zType, x.aNode[i].n, x.aUp[i]);
- if( x.aNode[i].u.zJContent!=0 ){
- jsonAppendRaw(&s, " ", 1);
- jsonAppendRaw(&s, x.aNode[i].u.zJContent, x.aNode[i].n);
- }
- jsonAppendRaw(&s, "\n", 1);
- }
- jsonParseReset(&x);
- jsonResult(&s);
-}
-/*
-** The json_test1(JSON) function return true (1) if the input is JSON
-** text generated by another json function. It returns (0) if the input
-** is not known to be JSON.
-*/
-static void jsonTest1Func(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- UNUSED_PARAM(argc);
- sqlite3_result_int(ctx, sqlite3_value_subtype(argv[0])==JSON_SUBTYPE);
-}
-#endif /* SQLITE_DEBUG */
-
-/****************************************************************************
-** Scalar SQL function implementations
-****************************************************************************/
-
-/*
-** Implementation of the json_QUOTE(VALUE) function. Return a JSON value
-** corresponding to the SQL value input. Mostly this means putting
-** double-quotes around strings and returning the unquoted string "null"
-** when given a NULL input.
-*/
-static void jsonQuoteFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- JsonString jx;
- UNUSED_PARAM(argc);
-
- jsonInit(&jx, ctx);
- jsonAppendValue(&jx, argv[0]);
- jsonResult(&jx);
- sqlite3_result_subtype(ctx, JSON_SUBTYPE);
-}
-
-/*
-** Implementation of the json_array(VALUE,...) function. Return a JSON
-** array that contains all values given in arguments. Or if any argument
-** is a BLOB, throw an error.
-*/
-static void jsonArrayFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- int i;
- JsonString jx;
-
- jsonInit(&jx, ctx);
- jsonAppendChar(&jx, '[');
- for(i=0; i<argc; i++){
- jsonAppendSeparator(&jx);
- jsonAppendValue(&jx, argv[i]);
- }
- jsonAppendChar(&jx, ']');
- jsonResult(&jx);
- sqlite3_result_subtype(ctx, JSON_SUBTYPE);
-}
-
-
-/*
-** json_array_length(JSON)
-** json_array_length(JSON, PATH)
-**
-** Return the number of elements in the top-level JSON array.
-** Return 0 if the input is not a well-formed JSON array.
-*/
-static void jsonArrayLengthFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- JsonParse x; /* The parse */
- sqlite3_int64 n = 0;
- u32 i;
- JsonNode *pNode;
-
- if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
- assert( x.nNode );
- if( argc==2 ){
- const char *zPath = (const char*)sqlite3_value_text(argv[1]);
- pNode = jsonLookup(&x, zPath, 0, ctx);
- }else{
- pNode = x.aNode;
- }
- if( pNode==0 ){
- x.nErr = 1;
- }else if( pNode->eType==JSON_ARRAY ){
- assert( (pNode->jnFlags & JNODE_APPEND)==0 );
- for(i=1; i<=pNode->n; n++){
- i += jsonNodeSize(&pNode[i]);
- }
- }
- if( x.nErr==0 ) sqlite3_result_int64(ctx, n);
- jsonParseReset(&x);
-}
+ if( pChange ){
+ assert( pChange->op==SQLITE_DELETE || pChange->op==SQLITE_INSERT );
+ switch( pIter->op ){
+ case SQLITE_INSERT:
+ if( pChange->op==SQLITE_INSERT ){
+ bDone = 1;
+ if( pChange->bIndirect==0 ){
+ sessionAppendByte(&sOut, SQLITE_UPDATE, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ sessionAppendBlob(&sOut, pChange->aRecord, pChange->nRecord, &rc);
+ sessionAppendBlob(&sOut, aRec, nRec, &rc);
+ }
+ }
+ break;
-/*
-** json_extract(JSON, PATH, ...)
-**
-** Return the element described by PATH. Return NULL if there is no
-** PATH element. If there are multiple PATHs, then return a JSON array
-** with the result from each path. Throw an error if the JSON or any PATH
-** is malformed.
-*/
-static void jsonExtractFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- JsonParse x; /* The parse */
- JsonNode *pNode;
- const char *zPath;
- JsonString jx;
- int i;
+ case SQLITE_UPDATE:
+ bDone = 1;
+ if( pChange->op==SQLITE_DELETE ){
+ if( pChange->bIndirect==0 ){
+ u8 *pCsr = aRec;
+ sessionSkipRecord(&pCsr, pIter->nCol);
+ sessionAppendByte(&sOut, SQLITE_INSERT, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ sessionAppendRecordMerge(&sOut, pIter->nCol,
+ pCsr, nRec-(pCsr-aRec),
+ pChange->aRecord, pChange->nRecord, &rc
+ );
+ }
+ }else{
+ sessionAppendPartialUpdate(&sOut, pIter,
+ aRec, nRec, pChange->aRecord, pChange->nRecord, &rc
+ );
+ }
+ break;
- if( argc<2 ) return;
- if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
- jsonInit(&jx, ctx);
- jsonAppendChar(&jx, '[');
- for(i=1; i<argc; i++){
- zPath = (const char*)sqlite3_value_text(argv[i]);
- pNode = jsonLookup(&x, zPath, 0, ctx);
- if( x.nErr ) break;
- if( argc>2 ){
- jsonAppendSeparator(&jx);
- if( pNode ){
- jsonRenderNode(pNode, &jx, 0);
- }else{
- jsonAppendRaw(&jx, "null", 4);
+ default:
+ assert( pIter->op==SQLITE_DELETE );
+ bDone = 1;
+ if( pChange->op==SQLITE_INSERT ){
+ sessionAppendByte(&sOut, SQLITE_DELETE, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ sessionAppendRecordMerge(&sOut, pIter->nCol,
+ pChange->aRecord, pChange->nRecord, aRec, nRec, &rc
+ );
+ }
+ break;
}
- }else if( pNode ){
- jsonReturn(pNode, ctx, 0);
}
- }
- if( argc>2 && i==argc ){
- jsonAppendChar(&jx, ']');
- jsonResult(&jx);
- sqlite3_result_subtype(ctx, JSON_SUBTYPE);
- }
- jsonReset(&jx);
- jsonParseReset(&x);
-}
-/*
-** Implementation of the json_object(NAME,VALUE,...) function. Return a JSON
-** object that contains all name/value given in arguments. Or if any name
-** is not a string or if any value is a BLOB, throw an error.
-*/
-static void jsonObjectFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- int i;
- JsonString jx;
- const char *z;
- u32 n;
-
- if( argc&1 ){
- sqlite3_result_error(ctx, "json_object() requires an even number "
- "of arguments", -1);
- return;
- }
- jsonInit(&jx, ctx);
- jsonAppendChar(&jx, '{');
- for(i=0; i<argc; i+=2){
- if( sqlite3_value_type(argv[i])!=SQLITE_TEXT ){
- sqlite3_result_error(ctx, "json_object() labels must be TEXT", -1);
- jsonReset(&jx);
- return;
+ if( bDone==0 ){
+ sessionAppendByte(&sOut, pIter->op, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ sessionAppendBlob(&sOut, aRec, nRec, &rc);
}
- jsonAppendSeparator(&jx);
- z = (const char*)sqlite3_value_text(argv[i]);
- n = (u32)sqlite3_value_bytes(argv[i]);
- jsonAppendString(&jx, z, n);
- jsonAppendChar(&jx, ':');
- jsonAppendValue(&jx, argv[i+1]);
+ if( rc==SQLITE_OK && xOutput && sOut.nBuf>sessions_strm_chunk_size ){
+ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
+ sOut.nBuf = 0;
+ }
+ if( rc ) break;
}
- jsonAppendChar(&jx, '}');
- jsonResult(&jx);
- sqlite3_result_subtype(ctx, JSON_SUBTYPE);
-}
-
-
-/*
-** json_remove(JSON, PATH, ...)
-**
-** Remove the named elements from JSON and return the result. malformed
-** JSON or PATH arguments result in an error.
-*/
-static void jsonRemoveFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- JsonParse x; /* The parse */
- JsonNode *pNode;
- const char *zPath;
- u32 i;
- if( argc<1 ) return;
- if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
- assert( x.nNode );
- for(i=1; i<(u32)argc; i++){
- zPath = (const char*)sqlite3_value_text(argv[i]);
- if( zPath==0 ) goto remove_done;
- pNode = jsonLookup(&x, zPath, 0, ctx);
- if( x.nErr ) goto remove_done;
- if( pNode ) pNode->jnFlags |= JNODE_REMOVE;
- }
- if( (x.aNode[0].jnFlags & JNODE_REMOVE)==0 ){
- jsonReturnJson(x.aNode, ctx, 0);
+ if( rc!=SQLITE_OK ){
+ tdsqlite3_free(sOut.aBuf);
+ memset(&sOut, 0, sizeof(sOut));
}
-remove_done:
- jsonParseReset(&x);
-}
-/*
-** json_replace(JSON, PATH, VALUE, ...)
-**
-** Replace the value at PATH with VALUE. If PATH does not already exist,
-** this routine is a no-op. If JSON or PATH is malformed, throw an error.
-*/
-static void jsonReplaceFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- JsonParse x; /* The parse */
- JsonNode *pNode;
- const char *zPath;
- u32 i;
-
- if( argc<1 ) return;
- if( (argc&1)==0 ) {
- jsonWrongNumArgs(ctx, "replace");
- return;
- }
- if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
- assert( x.nNode );
- for(i=1; i<(u32)argc; i+=2){
- zPath = (const char*)sqlite3_value_text(argv[i]);
- pNode = jsonLookup(&x, zPath, 0, ctx);
- if( x.nErr ) goto replace_err;
- if( pNode ){
- pNode->jnFlags |= (u8)JNODE_REPLACE;
- pNode->iVal = (u8)(i+1);
+ if( rc==SQLITE_OK ){
+ if( xOutput ){
+ if( sOut.nBuf>0 ){
+ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
+ }
+ }else{
+ *ppOut = (void*)sOut.aBuf;
+ *pnOut = sOut.nBuf;
+ sOut.aBuf = 0;
}
}
- if( x.aNode[0].jnFlags & JNODE_REPLACE ){
- sqlite3_result_value(ctx, argv[x.aNode[0].iVal]);
- }else{
- jsonReturnJson(x.aNode, ctx, argv);
- }
-replace_err:
- jsonParseReset(&x);
+ tdsqlite3_free(sOut.aBuf);
+ return rc;
}
-/*
-** json_set(JSON, PATH, VALUE, ...)
-**
-** Set the value at PATH to VALUE. Create the PATH if it does not already
-** exist. Overwrite existing values that do exist.
-** If JSON or PATH is malformed, throw an error.
-**
-** json_insert(JSON, PATH, VALUE, ...)
-**
-** Create PATH and initialize it to VALUE. If PATH already exists, this
-** routine is a no-op. If JSON or PATH is malformed, throw an error.
+/*
+** Create a new rebaser object.
*/
-static void jsonSetFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- JsonParse x; /* The parse */
- JsonNode *pNode;
- const char *zPath;
- u32 i;
- int bApnd;
- int bIsSet = *(int*)sqlite3_user_data(ctx);
+SQLITE_API int tdsqlite3rebaser_create(tdsqlite3_rebaser **ppNew){
+ int rc = SQLITE_OK;
+ tdsqlite3_rebaser *pNew;
- if( argc<1 ) return;
- if( (argc&1)==0 ) {
- jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert");
- return;
- }
- if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
- assert( x.nNode );
- for(i=1; i<(u32)argc; i+=2){
- zPath = (const char*)sqlite3_value_text(argv[i]);
- bApnd = 0;
- pNode = jsonLookup(&x, zPath, &bApnd, ctx);
- if( x.oom ){
- sqlite3_result_error_nomem(ctx);
- goto jsonSetDone;
- }else if( x.nErr ){
- goto jsonSetDone;
- }else if( pNode && (bApnd || bIsSet) ){
- pNode->jnFlags |= (u8)JNODE_REPLACE;
- pNode->iVal = (u8)(i+1);
- }
- }
- if( x.aNode[0].jnFlags & JNODE_REPLACE ){
- sqlite3_result_value(ctx, argv[x.aNode[0].iVal]);
+ pNew = tdsqlite3_malloc(sizeof(tdsqlite3_rebaser));
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
}else{
- jsonReturnJson(x.aNode, ctx, argv);
+ memset(pNew, 0, sizeof(tdsqlite3_rebaser));
}
-jsonSetDone:
- jsonParseReset(&x);
+ *ppNew = pNew;
+ return rc;
}
-/*
-** json_type(JSON)
-** json_type(JSON, PATH)
-**
-** Return the top-level "type" of a JSON string. Throw an error if
-** either the JSON or PATH inputs are not well-formed.
+/*
+** Call this one or more times to configure a rebaser.
*/
-static void jsonTypeFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
+SQLITE_API int tdsqlite3rebaser_configure(
+ tdsqlite3_rebaser *p,
+ int nRebase, const void *pRebase
){
- JsonParse x; /* The parse */
- const char *zPath;
- JsonNode *pNode;
-
- if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
- assert( x.nNode );
- if( argc==2 ){
- zPath = (const char*)sqlite3_value_text(argv[1]);
- pNode = jsonLookup(&x, zPath, 0, ctx);
- }else{
- pNode = x.aNode;
- }
- if( pNode ){
- sqlite3_result_text(ctx, jsonType[pNode->eType], -1, SQLITE_STATIC);
+ tdsqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */
+ int rc; /* Return code */
+ rc = tdsqlite3changeset_start(&pIter, nRebase, (void*)pRebase);
+ if( rc==SQLITE_OK ){
+ rc = sessionChangesetToHash(pIter, &p->grp, 1);
}
- jsonParseReset(&x);
+ tdsqlite3changeset_finalize(pIter);
+ return rc;
}
-/*
-** json_valid(JSON)
-**
-** Return 1 if JSON is a well-formed JSON string according to RFC-7159.
-** Return 0 otherwise.
+/*
+** Rebase a changeset according to current rebaser configuration
*/
-static void jsonValidFunc(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
+SQLITE_API int tdsqlite3rebaser_rebase(
+ tdsqlite3_rebaser *p,
+ int nIn, const void *pIn,
+ int *pnOut, void **ppOut
){
- JsonParse x; /* The parse */
- int rc = 0;
+ tdsqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */
+ int rc = tdsqlite3changeset_start(&pIter, nIn, (void*)pIn);
- UNUSED_PARAM(argc);
- if( jsonParse(&x, 0, (const char*)sqlite3_value_text(argv[0]))==0 ){
- rc = 1;
+ if( rc==SQLITE_OK ){
+ rc = sessionRebase(p, pIter, 0, 0, pnOut, ppOut);
+ tdsqlite3changeset_finalize(pIter);
}
- jsonParseReset(&x);
- sqlite3_result_int(ctx, rc);
-}
-
-/****************************************************************************
-** Aggregate SQL function implementations
-****************************************************************************/
-/*
-** json_group_array(VALUE)
-**
-** Return a JSON array composed of all values in the aggregate.
-*/
-static void jsonArrayStep(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- JsonString *pStr;
- UNUSED_PARAM(argc);
- pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr));
- if( pStr ){
- if( pStr->zBuf==0 ){
- jsonInit(pStr, ctx);
- jsonAppendChar(pStr, '[');
- }else{
- jsonAppendChar(pStr, ',');
- pStr->pCtx = ctx;
- }
- jsonAppendValue(pStr, argv[0]);
- }
-}
-static void jsonArrayFinal(sqlite3_context *ctx){
- JsonString *pStr;
- pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0);
- if( pStr ){
- pStr->pCtx = ctx;
- jsonAppendChar(pStr, ']');
- if( pStr->bErr ){
- if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx);
- assert( pStr->bStatic );
- }else{
- sqlite3_result_text(ctx, pStr->zBuf, pStr->nUsed,
- pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free);
- pStr->bStatic = 1;
- }
- }else{
- sqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC);
- }
- sqlite3_result_subtype(ctx, JSON_SUBTYPE);
+ return rc;
}
-/*
-** json_group_obj(NAME,VALUE)
-**
-** Return a JSON object composed of all names and values in the aggregate.
+/*
+** Rebase a changeset according to current rebaser configuration
*/
-static void jsonObjectStep(
- sqlite3_context *ctx,
- int argc,
- sqlite3_value **argv
-){
- JsonString *pStr;
- const char *z;
- u32 n;
- UNUSED_PARAM(argc);
- pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr));
- if( pStr ){
- if( pStr->zBuf==0 ){
- jsonInit(pStr, ctx);
- jsonAppendChar(pStr, '{');
- }else{
- jsonAppendChar(pStr, ',');
- pStr->pCtx = ctx;
- }
- z = (const char*)sqlite3_value_text(argv[0]);
- n = (u32)sqlite3_value_bytes(argv[0]);
- jsonAppendString(pStr, z, n);
- jsonAppendChar(pStr, ':');
- jsonAppendValue(pStr, argv[1]);
- }
-}
-static void jsonObjectFinal(sqlite3_context *ctx){
- JsonString *pStr;
- pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0);
- if( pStr ){
- jsonAppendChar(pStr, '}');
- if( pStr->bErr ){
- if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx);
- assert( pStr->bStatic );
- }else{
- sqlite3_result_text(ctx, pStr->zBuf, pStr->nUsed,
- pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free);
- pStr->bStatic = 1;
- }
- }else{
- sqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC);
- }
- sqlite3_result_subtype(ctx, JSON_SUBTYPE);
-}
-
-
-#ifndef SQLITE_OMIT_VIRTUALTABLE
-/****************************************************************************
-** The json_each virtual table
-****************************************************************************/
-typedef struct JsonEachCursor JsonEachCursor;
-struct JsonEachCursor {
- sqlite3_vtab_cursor base; /* Base class - must be first */
- u32 iRowid; /* The rowid */
- u32 iBegin; /* The first node of the scan */
- u32 i; /* Index in sParse.aNode[] of current row */
- u32 iEnd; /* EOF when i equals or exceeds this value */
- u8 eType; /* Type of top-level element */
- u8 bRecursive; /* True for json_tree(). False for json_each() */
- char *zJson; /* Input JSON */
- char *zRoot; /* Path by which to filter zJson */
- JsonParse sParse; /* Parse of the input JSON */
-};
-
-/* Constructor for the json_each virtual table */
-static int jsonEachConnect(
- sqlite3 *db,
- void *pAux,
- int argc, const char *const*argv,
- sqlite3_vtab **ppVtab,
- char **pzErr
+SQLITE_API int tdsqlite3rebaser_rebase_strm(
+ tdsqlite3_rebaser *p,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut
){
- sqlite3_vtab *pNew;
- int rc;
-
-/* Column numbers */
-#define JEACH_KEY 0
-#define JEACH_VALUE 1
-#define JEACH_TYPE 2
-#define JEACH_ATOM 3
-#define JEACH_ID 4
-#define JEACH_PARENT 5
-#define JEACH_FULLKEY 6
-#define JEACH_PATH 7
-#define JEACH_JSON 8
-#define JEACH_ROOT 9
+ tdsqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */
+ int rc = tdsqlite3changeset_start_strm(&pIter, xInput, pIn);
- UNUSED_PARAM(pzErr);
- UNUSED_PARAM(argv);
- UNUSED_PARAM(argc);
- UNUSED_PARAM(pAux);
- rc = sqlite3_declare_vtab(db,
- "CREATE TABLE x(key,value,type,atom,id,parent,fullkey,path,"
- "json HIDDEN,root HIDDEN)");
if( rc==SQLITE_OK ){
- pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
- if( pNew==0 ) return SQLITE_NOMEM;
- memset(pNew, 0, sizeof(*pNew));
+ rc = sessionRebase(p, pIter, xOutput, pOut, 0, 0);
+ tdsqlite3changeset_finalize(pIter);
}
- return rc;
-}
-
-/* destructor for json_each virtual table */
-static int jsonEachDisconnect(sqlite3_vtab *pVtab){
- sqlite3_free(pVtab);
- return SQLITE_OK;
-}
-
-/* constructor for a JsonEachCursor object for json_each(). */
-static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
- JsonEachCursor *pCur;
- UNUSED_PARAM(p);
- pCur = sqlite3_malloc( sizeof(*pCur) );
- if( pCur==0 ) return SQLITE_NOMEM;
- memset(pCur, 0, sizeof(*pCur));
- *ppCursor = &pCur->base;
- return SQLITE_OK;
-}
-
-/* constructor for a JsonEachCursor object for json_tree(). */
-static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
- int rc = jsonEachOpenEach(p, ppCursor);
- if( rc==SQLITE_OK ){
- JsonEachCursor *pCur = (JsonEachCursor*)*ppCursor;
- pCur->bRecursive = 1;
- }
return rc;
}
-/* Reset a JsonEachCursor back to its original state. Free any memory
-** held. */
-static void jsonEachCursorReset(JsonEachCursor *p){
- sqlite3_free(p->zJson);
- sqlite3_free(p->zRoot);
- jsonParseReset(&p->sParse);
- p->iRowid = 0;
- p->i = 0;
- p->iEnd = 0;
- p->eType = 0;
- p->zJson = 0;
- p->zRoot = 0;
-}
-
-/* Destructor for a jsonEachCursor object */
-static int jsonEachClose(sqlite3_vtab_cursor *cur){
- JsonEachCursor *p = (JsonEachCursor*)cur;
- jsonEachCursorReset(p);
- sqlite3_free(cur);
- return SQLITE_OK;
-}
-
-/* Return TRUE if the jsonEachCursor object has been advanced off the end
-** of the JSON object */
-static int jsonEachEof(sqlite3_vtab_cursor *cur){
- JsonEachCursor *p = (JsonEachCursor*)cur;
- return p->i >= p->iEnd;
-}
-
-/* Advance the cursor to the next element for json_tree() */
-static int jsonEachNext(sqlite3_vtab_cursor *cur){
- JsonEachCursor *p = (JsonEachCursor*)cur;
- if( p->bRecursive ){
- if( p->sParse.aNode[p->i].jnFlags & JNODE_LABEL ) p->i++;
- p->i++;
- p->iRowid++;
- if( p->i<p->iEnd ){
- u32 iUp = p->sParse.aUp[p->i];
- JsonNode *pUp = &p->sParse.aNode[iUp];
- p->eType = pUp->eType;
- if( pUp->eType==JSON_ARRAY ){
- if( iUp==p->i-1 ){
- pUp->u.iKey = 0;
- }else{
- pUp->u.iKey++;
- }
- }
- }
- }else{
- switch( p->eType ){
- case JSON_ARRAY: {
- p->i += jsonNodeSize(&p->sParse.aNode[p->i]);
- p->iRowid++;
- break;
- }
- case JSON_OBJECT: {
- p->i += 1 + jsonNodeSize(&p->sParse.aNode[p->i+1]);
- p->iRowid++;
- break;
- }
- default: {
- p->i = p->iEnd;
- break;
- }
- }
- }
- return SQLITE_OK;
-}
-
-/* Append the name of the path for element i to pStr
+/*
+** Destroy a rebaser object
*/
-static void jsonEachComputePath(
- JsonEachCursor *p, /* The cursor */
- JsonString *pStr, /* Write the path here */
- u32 i /* Path to this element */
-){
- JsonNode *pNode, *pUp;
- u32 iUp;
- if( i==0 ){
- jsonAppendChar(pStr, '$');
- return;
- }
- iUp = p->sParse.aUp[i];
- jsonEachComputePath(p, pStr, iUp);
- pNode = &p->sParse.aNode[i];
- pUp = &p->sParse.aNode[iUp];
- if( pUp->eType==JSON_ARRAY ){
- jsonPrintf(30, pStr, "[%d]", pUp->u.iKey);
- }else{
- assert( pUp->eType==JSON_OBJECT );
- if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--;
- assert( pNode->eType==JSON_STRING );
- assert( pNode->jnFlags & JNODE_LABEL );
- jsonPrintf(pNode->n+1, pStr, ".%.*s", pNode->n-2, pNode->u.zJContent+1);
+SQLITE_API void tdsqlite3rebaser_delete(tdsqlite3_rebaser *p){
+ if( p ){
+ sessionDeleteTable(p->grp.pList);
+ tdsqlite3_free(p);
}
}
-/* Return the value of a column */
-static int jsonEachColumn(
- sqlite3_vtab_cursor *cur, /* The cursor */
- sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
- int i /* Which column to return */
-){
- JsonEachCursor *p = (JsonEachCursor*)cur;
- JsonNode *pThis = &p->sParse.aNode[p->i];
- switch( i ){
- case JEACH_KEY: {
- if( p->i==0 ) break;
- if( p->eType==JSON_OBJECT ){
- jsonReturn(pThis, ctx, 0);
- }else if( p->eType==JSON_ARRAY ){
- u32 iKey;
- if( p->bRecursive ){
- if( p->iRowid==0 ) break;
- iKey = p->sParse.aNode[p->sParse.aUp[p->i]].u.iKey;
- }else{
- iKey = p->iRowid;
- }
- sqlite3_result_int64(ctx, (sqlite3_int64)iKey);
- }
- break;
- }
- case JEACH_VALUE: {
- if( pThis->jnFlags & JNODE_LABEL ) pThis++;
- jsonReturn(pThis, ctx, 0);
- break;
- }
- case JEACH_TYPE: {
- if( pThis->jnFlags & JNODE_LABEL ) pThis++;
- sqlite3_result_text(ctx, jsonType[pThis->eType], -1, SQLITE_STATIC);
- break;
- }
- case JEACH_ATOM: {
- if( pThis->jnFlags & JNODE_LABEL ) pThis++;
- if( pThis->eType>=JSON_ARRAY ) break;
- jsonReturn(pThis, ctx, 0);
- break;
- }
- case JEACH_ID: {
- sqlite3_result_int64(ctx,
- (sqlite3_int64)p->i + ((pThis->jnFlags & JNODE_LABEL)!=0));
- break;
- }
- case JEACH_PARENT: {
- if( p->i>p->iBegin && p->bRecursive ){
- sqlite3_result_int64(ctx, (sqlite3_int64)p->sParse.aUp[p->i]);
- }
- break;
- }
- case JEACH_FULLKEY: {
- JsonString x;
- jsonInit(&x, ctx);
- if( p->bRecursive ){
- jsonEachComputePath(p, &x, p->i);
- }else{
- if( p->zRoot ){
- jsonAppendRaw(&x, p->zRoot, (int)strlen(p->zRoot));
- }else{
- jsonAppendChar(&x, '$');
- }
- if( p->eType==JSON_ARRAY ){
- jsonPrintf(30, &x, "[%d]", p->iRowid);
- }else{
- jsonPrintf(pThis->n, &x, ".%.*s", pThis->n-2, pThis->u.zJContent+1);
- }
- }
- jsonResult(&x);
- break;
- }
- case JEACH_PATH: {
- if( p->bRecursive ){
- JsonString x;
- jsonInit(&x, ctx);
- jsonEachComputePath(p, &x, p->sParse.aUp[p->i]);
- jsonResult(&x);
- break;
+/*
+** Global configuration
+*/
+SQLITE_API int tdsqlite3session_config(int op, void *pArg){
+ int rc = SQLITE_OK;
+ switch( op ){
+ case SQLITE_SESSION_CONFIG_STRMSIZE: {
+ int *pInt = (int*)pArg;
+ if( *pInt>0 ){
+ sessions_strm_chunk_size = *pInt;
}
- /* For json_each() path and root are the same so fall through
- ** into the root case */
- }
- default: {
- const char *zRoot = p->zRoot;
- if( zRoot==0 ) zRoot = "$";
- sqlite3_result_text(ctx, zRoot, -1, SQLITE_STATIC);
+ *pInt = sessions_strm_chunk_size;
break;
}
- case JEACH_JSON: {
- assert( i==JEACH_JSON );
- sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC);
+ default:
+ rc = SQLITE_MISUSE;
break;
- }
}
- return SQLITE_OK;
-}
-
-/* Return the current rowid value */
-static int jsonEachRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
- JsonEachCursor *p = (JsonEachCursor*)cur;
- *pRowid = p->iRowid;
- return SQLITE_OK;
-}
-
-/* The query strategy is to look for an equality constraint on the json
-** column. Without such a constraint, the table cannot operate. idxNum is
-** 1 if the constraint is found, 3 if the constraint and zRoot are found,
-** and 0 otherwise.
-*/
-static int jsonEachBestIndex(
- sqlite3_vtab *tab,
- sqlite3_index_info *pIdxInfo
-){
- int i;
- int jsonIdx = -1;
- int rootIdx = -1;
- const struct sqlite3_index_constraint *pConstraint;
-
- UNUSED_PARAM(tab);
- pConstraint = pIdxInfo->aConstraint;
- for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
- if( pConstraint->usable==0 ) continue;
- if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
- switch( pConstraint->iColumn ){
- case JEACH_JSON: jsonIdx = i; break;
- case JEACH_ROOT: rootIdx = i; break;
- default: /* no-op */ break;
- }
- }
- if( jsonIdx<0 ){
- pIdxInfo->idxNum = 0;
- pIdxInfo->estimatedCost = 1e99;
- }else{
- pIdxInfo->estimatedCost = 1.0;
- pIdxInfo->aConstraintUsage[jsonIdx].argvIndex = 1;
- pIdxInfo->aConstraintUsage[jsonIdx].omit = 1;
- if( rootIdx<0 ){
- pIdxInfo->idxNum = 1;
- }else{
- pIdxInfo->aConstraintUsage[rootIdx].argvIndex = 2;
- pIdxInfo->aConstraintUsage[rootIdx].omit = 1;
- pIdxInfo->idxNum = 3;
- }
- }
- return SQLITE_OK;
-}
-
-/* Start a search on a new JSON string */
-static int jsonEachFilter(
- sqlite3_vtab_cursor *cur,
- int idxNum, const char *idxStr,
- int argc, sqlite3_value **argv
-){
- JsonEachCursor *p = (JsonEachCursor*)cur;
- const char *z;
- const char *zRoot = 0;
- sqlite3_int64 n;
-
- UNUSED_PARAM(idxStr);
- UNUSED_PARAM(argc);
- jsonEachCursorReset(p);
- if( idxNum==0 ) return SQLITE_OK;
- z = (const char*)sqlite3_value_text(argv[0]);
- if( z==0 ) return SQLITE_OK;
- n = sqlite3_value_bytes(argv[0]);
- p->zJson = sqlite3_malloc64( n+1 );
- if( p->zJson==0 ) return SQLITE_NOMEM;
- memcpy(p->zJson, z, (size_t)n+1);
- if( jsonParse(&p->sParse, 0, p->zJson) ){
- int rc = SQLITE_NOMEM;
- if( p->sParse.oom==0 ){
- sqlite3_free(cur->pVtab->zErrMsg);
- cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON");
- if( cur->pVtab->zErrMsg ) rc = SQLITE_ERROR;
- }
- jsonEachCursorReset(p);
- return rc;
- }else if( p->bRecursive && jsonParseFindParents(&p->sParse) ){
- jsonEachCursorReset(p);
- return SQLITE_NOMEM;
- }else{
- JsonNode *pNode = 0;
- if( idxNum==3 ){
- const char *zErr = 0;
- zRoot = (const char*)sqlite3_value_text(argv[1]);
- if( zRoot==0 ) return SQLITE_OK;
- n = sqlite3_value_bytes(argv[1]);
- p->zRoot = sqlite3_malloc64( n+1 );
- if( p->zRoot==0 ) return SQLITE_NOMEM;
- memcpy(p->zRoot, zRoot, (size_t)n+1);
- if( zRoot[0]!='$' ){
- zErr = zRoot;
- }else{
- pNode = jsonLookupStep(&p->sParse, 0, p->zRoot+1, 0, &zErr);
- }
- if( zErr ){
- sqlite3_free(cur->pVtab->zErrMsg);
- cur->pVtab->zErrMsg = jsonPathSyntaxError(zErr);
- jsonEachCursorReset(p);
- return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM;
- }else if( pNode==0 ){
- return SQLITE_OK;
- }
- }else{
- pNode = p->sParse.aNode;
- }
- p->iBegin = p->i = (int)(pNode - p->sParse.aNode);
- p->eType = pNode->eType;
- if( p->eType>=JSON_ARRAY ){
- pNode->u.iKey = 0;
- p->iEnd = p->i + pNode->n + 1;
- if( p->bRecursive ){
- p->eType = p->sParse.aNode[p->sParse.aUp[p->i]].eType;
- if( p->i>0 && (p->sParse.aNode[p->i-1].jnFlags & JNODE_LABEL)!=0 ){
- p->i--;
- }
- }else{
- p->i++;
- }
- }else{
- p->iEnd = p->i+1;
- }
- }
- return SQLITE_OK;
-}
-
-/* The methods of the json_each virtual table */
-static sqlite3_module jsonEachModule = {
- 0, /* iVersion */
- 0, /* xCreate */
- jsonEachConnect, /* xConnect */
- jsonEachBestIndex, /* xBestIndex */
- jsonEachDisconnect, /* xDisconnect */
- 0, /* xDestroy */
- jsonEachOpenEach, /* xOpen - open a cursor */
- jsonEachClose, /* xClose - close a cursor */
- jsonEachFilter, /* xFilter - configure scan constraints */
- jsonEachNext, /* xNext - advance a cursor */
- jsonEachEof, /* xEof - check for end of scan */
- jsonEachColumn, /* xColumn - read data */
- jsonEachRowid, /* xRowid - read data */
- 0, /* xUpdate */
- 0, /* xBegin */
- 0, /* xSync */
- 0, /* xCommit */
- 0, /* xRollback */
- 0, /* xFindMethod */
- 0, /* xRename */
- 0, /* xSavepoint */
- 0, /* xRelease */
- 0 /* xRollbackTo */
-};
-
-/* The methods of the json_tree virtual table. */
-static sqlite3_module jsonTreeModule = {
- 0, /* iVersion */
- 0, /* xCreate */
- jsonEachConnect, /* xConnect */
- jsonEachBestIndex, /* xBestIndex */
- jsonEachDisconnect, /* xDisconnect */
- 0, /* xDestroy */
- jsonEachOpenTree, /* xOpen - open a cursor */
- jsonEachClose, /* xClose - close a cursor */
- jsonEachFilter, /* xFilter - configure scan constraints */
- jsonEachNext, /* xNext - advance a cursor */
- jsonEachEof, /* xEof - check for end of scan */
- jsonEachColumn, /* xColumn - read data */
- jsonEachRowid, /* xRowid - read data */
- 0, /* xUpdate */
- 0, /* xBegin */
- 0, /* xSync */
- 0, /* xCommit */
- 0, /* xRollback */
- 0, /* xFindMethod */
- 0, /* xRename */
- 0, /* xSavepoint */
- 0, /* xRelease */
- 0 /* xRollbackTo */
-};
-#endif /* SQLITE_OMIT_VIRTUALTABLE */
-
-/****************************************************************************
-** The following routines are the only publically visible identifiers in this
-** file. Call the following routines in order to register the various SQL
-** functions and the virtual table implemented by this file.
-****************************************************************************/
-
-SQLITE_PRIVATE int sqlite3Json1Init(sqlite3 *db){
- int rc = SQLITE_OK;
- unsigned int i;
- static const struct {
- const char *zName;
- int nArg;
- int flag;
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
- } aFunc[] = {
- { "json", 1, 0, jsonRemoveFunc },
- { "json_array", -1, 0, jsonArrayFunc },
- { "json_array_length", 1, 0, jsonArrayLengthFunc },
- { "json_array_length", 2, 0, jsonArrayLengthFunc },
- { "json_extract", -1, 0, jsonExtractFunc },
- { "json_insert", -1, 0, jsonSetFunc },
- { "json_object", -1, 0, jsonObjectFunc },
- { "json_quote", 1, 0, jsonQuoteFunc },
- { "json_remove", -1, 0, jsonRemoveFunc },
- { "json_replace", -1, 0, jsonReplaceFunc },
- { "json_set", -1, 1, jsonSetFunc },
- { "json_type", 1, 0, jsonTypeFunc },
- { "json_type", 2, 0, jsonTypeFunc },
- { "json_valid", 1, 0, jsonValidFunc },
-
-#if SQLITE_DEBUG
- /* DEBUG and TESTING functions */
- { "json_parse", 1, 0, jsonParseFunc },
- { "json_test1", 1, 0, jsonTest1Func },
-#endif
- };
- static const struct {
- const char *zName;
- int nArg;
- void (*xStep)(sqlite3_context*,int,sqlite3_value**);
- void (*xFinal)(sqlite3_context*);
- } aAgg[] = {
- { "json_group_array", 1, jsonArrayStep, jsonArrayFinal },
- { "json_group_object", 2, jsonObjectStep, jsonObjectFinal },
- };
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- static const struct {
- const char *zName;
- sqlite3_module *pModule;
- } aMod[] = {
- { "json_each", &jsonEachModule },
- { "json_tree", &jsonTreeModule },
- };
-#endif
- for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){
- rc = sqlite3_create_function(db, aFunc[i].zName, aFunc[i].nArg,
- SQLITE_UTF8 | SQLITE_DETERMINISTIC,
- (void*)&aFunc[i].flag,
- aFunc[i].xFunc, 0, 0);
- }
- for(i=0; i<sizeof(aAgg)/sizeof(aAgg[0]) && rc==SQLITE_OK; i++){
- rc = sqlite3_create_function(db, aAgg[i].zName, aAgg[i].nArg,
- SQLITE_UTF8 | SQLITE_DETERMINISTIC, 0,
- 0, aAgg[i].xStep, aAgg[i].xFinal);
- }
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){
- rc = sqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0);
- }
-#endif
return rc;
}
+#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
-#ifndef SQLITE_CORE
-#ifdef _WIN32
-__declspec(dllexport)
-#endif
-SQLITE_API int sqlite3_json_init(
- sqlite3 *db,
- char **pzErrMsg,
- const sqlite3_api_routines *pApi
-){
- SQLITE_EXTENSION_INIT2(pApi);
- (void)pzErrMsg; /* Unused parameter */
- return sqlite3Json1Init(db);
-}
-#endif
-#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_JSON1) */
-
-/************** End of json1.c ***********************************************/
+/************** End of tdsqlite3session.c **************************************/
/************** Begin file fts5.c ********************************************/
@@ -181774,7 +209537,7 @@ extern "C" {
** CUSTOM AUXILIARY FUNCTIONS
**
** Virtual table implementations may overload SQL functions by implementing
-** the sqlite3_module.xFindFunction() method.
+** the tdsqlite3_module.xFindFunction() method.
*/
typedef struct Fts5ExtensionApi Fts5ExtensionApi;
@@ -181784,9 +209547,9 @@ typedef struct Fts5PhraseIter Fts5PhraseIter;
typedef void (*fts5_extension_function)(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
- sqlite3_context *pCtx, /* Context for returning result/error */
+ tdsqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
- sqlite3_value **apVal /* Array of trailing arguments */
+ tdsqlite3_value **apVal /* Array of trailing arguments */
);
struct Fts5PhraseIter {
@@ -181863,12 +209626,8 @@ struct Fts5PhraseIter {
**
** Usually, output parameter *piPhrase is set to the phrase number, *piCol
** to the column in which it occurs and *piOff the token offset of the
-** first token of the phrase. The exception is if the table was created
-** with the offsets=0 option specified. In this case *piOff is always
-** set to -1.
-**
-** Returns SQLITE_OK if successful, or an error code (i.e. SQLITE_NOMEM)
-** if an error occurs.
+** first token of the phrase. Returns SQLITE_OK if successful, or an error
+** code (i.e. SQLITE_NOMEM) if an error occurs.
**
** This API can be quite slow if used with an FTS5 table created with the
** "detail=none" or "detail=column" option.
@@ -181906,10 +209665,10 @@ struct Fts5PhraseIter {
**
** xSetAuxdata(pFts5, pAux, xDelete)
**
-** Save the pointer passed as the second argument as the extension functions
+** Save the pointer passed as the second argument as the extension function's
** "auxiliary data". The pointer may then be retrieved by the current or any
** future invocation of the same fts5 extension function made as part of
-** of the same MATCH query using the xGetAuxdata() API.
+** the same MATCH query using the xGetAuxdata() API.
**
** Each extension function is allocated a single auxiliary data slot for
** each FTS query (MATCH expression). If the extension function is invoked
@@ -181924,7 +209683,7 @@ struct Fts5PhraseIter {
** The xDelete callback, if one is specified, is also invoked on the
** auxiliary data pointer after the FTS5 query has finished.
**
-** If an error (e.g. an OOM condition) occurs within this function, an
+** If an error (e.g. an OOM condition) occurs within this function,
** the auxiliary data is set to NULL and an error code returned. If the
** xDelete parameter was not NULL, it is invoked on the auxiliary data
** pointer before returning.
@@ -182015,8 +209774,8 @@ struct Fts5ExtensionApi {
void *(*xUserData)(Fts5Context*);
int (*xColumnCount)(Fts5Context*);
- int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow);
- int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken);
+ int (*xRowCount)(Fts5Context*, tdsqlite3_int64 *pnRow);
+ int (*xColumnTotalSize)(Fts5Context*, int iCol, tdsqlite3_int64 *pnToken);
int (*xTokenize)(Fts5Context*,
const char *pText, int nText, /* Text to tokenize */
@@ -182030,7 +209789,7 @@ struct Fts5ExtensionApi {
int (*xInstCount)(Fts5Context*, int *pnInst);
int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff);
- sqlite3_int64 (*xRowid)(Fts5Context*);
+ tdsqlite3_int64 (*xRowid)(Fts5Context*);
int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn);
int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken);
@@ -182148,8 +209907,8 @@ struct Fts5ExtensionApi {
**
** There are several ways to approach this in FTS5:
**
-** <ol><li> By mapping all synonyms to a single token. In this case, the
-** In the above example, this means that the tokenizer returns the
+** <ol><li> By mapping all synonyms to a single token. In this case, using
+** the above example, this means that the tokenizer returns the
** same token for inputs "first" and "1st". Say that token is in
** fact "first", so that when the user inserts the document "I won
** 1st place" entries are added to the index for tokens "i", "won",
@@ -182157,11 +209916,11 @@ struct Fts5ExtensionApi {
** the tokenizer substitutes "first" for "1st" and the query works
** as expected.
**
-** <li> By adding multiple synonyms for a single term to the FTS index.
-** In this case, when tokenizing query text, the tokenizer may
-** provide multiple synonyms for a single term within the document.
-** FTS5 then queries the index for each synonym individually. For
-** example, faced with the query:
+** <li> By querying the index for all synonyms of each query term
+** separately. In this case, when tokenizing query text, the
+** tokenizer may provide multiple synonyms for a single term
+** within the document. FTS5 then queries the index for each
+** synonym individually. For example, faced with the query:
**
** <codeblock>
** ... MATCH 'first place'</codeblock>
@@ -182185,9 +209944,9 @@ struct Fts5ExtensionApi {
** "place".
**
** This way, even if the tokenizer does not provide synonyms
-** when tokenizing query text (it should not - to do would be
+** when tokenizing query text (it should not - to do so would be
** inefficient), it doesn't matter if the user queries for
-** 'first + place' or '1st + place', as there are entires in the
+** 'first + place' or '1st + place', as there are entries in the
** FTS index corresponding to both forms of the first token.
** </ol>
**
@@ -182215,7 +209974,7 @@ struct Fts5ExtensionApi {
** extra data to the FTS index or require FTS5 to query for multiple terms,
** so it is efficient in terms of disk space and query speed. However, it
** does not support prefix queries very well. If, as suggested above, the
-** token "first" is subsituted for "1st" by the tokenizer, then the query:
+** token "first" is substituted for "1st" by the tokenizer, then the query:
**
** <codeblock>
** ... MATCH '1s*'</codeblock>
@@ -182338,7 +210097,7 @@ struct fts5_api {
#define _FTS5INT_H
/* #include "fts5.h" */
-/* #include "sqlite3ext.h" */
+/* #include "tdsqlite3ext.h" */
SQLITE_EXTENSION_INIT1
/* #include <string.h> */
@@ -182350,10 +210109,12 @@ typedef unsigned char u8;
typedef unsigned int u32;
typedef unsigned short u16;
typedef short i16;
-typedef sqlite3_int64 i64;
-typedef sqlite3_uint64 u64;
+typedef tdsqlite3_int64 i64;
+typedef tdsqlite3_uint64 u64;
-#define ArraySize(x) ((int)(sizeof(x) / sizeof(x[0])))
+#ifndef ArraySize
+# define ArraySize(x) ((int)(sizeof(x) / sizeof(x[0])))
+#endif
#define testcase(x)
#define ALWAYS(x) 1
@@ -182382,6 +210143,11 @@ typedef sqlite3_uint64 u64;
*/
#define FTS5_MAX_PREFIX_INDEXES 31
+/*
+** Maximum segments permitted in a single index
+*/
+#define FTS5_MAX_SEGMENT 2000
+
#define FTS5_DEFAULT_NEARDIST 10
#define FTS5_DEFAULT_RANK "bm25"
@@ -182390,8 +210156,8 @@ typedef sqlite3_uint64 u64;
#define FTS5_ROWID_NAME "rowid"
#ifdef SQLITE_DEBUG
-# define FTS5_CORRUPT sqlite3Fts5Corrupt()
-static int sqlite3Fts5Corrupt(void);
+# define FTS5_CORRUPT tdsqlite3Fts5Corrupt()
+static int tdsqlite3Fts5Corrupt(void);
#else
# define FTS5_CORRUPT SQLITE_CORRUPT_VTAB
#endif
@@ -182402,12 +210168,18 @@ static int sqlite3Fts5Corrupt(void);
** guranteed that the database is not corrupt.
*/
#ifdef SQLITE_DEBUG
-SQLITE_API extern int sqlite3_fts5_may_be_corrupt;
-# define assert_nc(x) assert(sqlite3_fts5_may_be_corrupt || (x))
+SQLITE_API extern int tdsqlite3_fts5_may_be_corrupt;
+# define assert_nc(x) assert(tdsqlite3_fts5_may_be_corrupt || (x))
#else
# define assert_nc(x) assert(x)
#endif
+/*
+** A version of memcmp() that does not cause asan errors if one of the pointer
+** parameters is NULL and the number of bytes to compare is zero.
+*/
+#define fts5Memcmp(s1, s2, n) ((n)==0 ? 0 : memcmp((s1), (s2), (n)))
+
/* Mark a function parameter as unused, to suppress nuisance compiler
** warnings. */
#ifndef UNUSED_PARAM
@@ -182477,7 +210249,7 @@ typedef struct Fts5Config Fts5Config;
**
*/
struct Fts5Config {
- sqlite3 *db; /* Database handle */
+ tdsqlite3 *db; /* Database handle */
char *zDb; /* Database holding FTS index (e.g. "main") */
char *zName; /* Name of FTS index */
int nCol; /* Number of columns */
@@ -182493,6 +210265,7 @@ struct Fts5Config {
char *zContentExprlist;
Fts5Tokenizer *pTok;
fts5_tokenizer *pTokApi;
+ int bLock; /* True when table is preparing statement */
/* Values loaded from the %_config table */
int iCookie; /* Incremented when %_config is modified */
@@ -182504,7 +210277,7 @@ struct Fts5Config {
char *zRank; /* Name of rank function */
char *zRankArgs; /* Arguments to rank function */
- /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
+ /* If non-NULL, points to tdsqlite3_vtab.base.zErrmsg. Often NULL. */
char **pzErrmsg;
#ifdef SQLITE_DEBUG
@@ -182525,14 +210298,14 @@ struct Fts5Config {
-static int sqlite3Fts5ConfigParse(
- Fts5Global*, sqlite3*, int, const char **, Fts5Config**, char**
+static int tdsqlite3Fts5ConfigParse(
+ Fts5Global*, tdsqlite3*, int, const char **, Fts5Config**, char**
);
-static void sqlite3Fts5ConfigFree(Fts5Config*);
+static void tdsqlite3Fts5ConfigFree(Fts5Config*);
-static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig);
+static int tdsqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig);
-static int sqlite3Fts5Tokenize(
+static int tdsqlite3Fts5Tokenize(
Fts5Config *pConfig, /* FTS5 Configuration object */
int flags, /* FTS5_TOKENIZE_* flags */
const char *pText, int nText, /* Text to tokenize */
@@ -182540,15 +210313,15 @@ static int sqlite3Fts5Tokenize(
int (*xToken)(void*, int, const char*, int, int, int) /* Callback */
);
-static void sqlite3Fts5Dequote(char *z);
+static void tdsqlite3Fts5Dequote(char *z);
/* Load the contents of the %_config table */
-static int sqlite3Fts5ConfigLoad(Fts5Config*, int);
+static int tdsqlite3Fts5ConfigLoad(Fts5Config*, int);
/* Set the value of a single config attribute */
-static int sqlite3Fts5ConfigSetValue(Fts5Config*, const char*, sqlite3_value*, int*);
+static int tdsqlite3Fts5ConfigSetValue(Fts5Config*, const char*, tdsqlite3_value*, int*);
-static int sqlite3Fts5ConfigParseRank(const char*, char**, char**);
+static int tdsqlite3Fts5ConfigParseRank(const char*, char**, char**);
/*
** End of interface to code in fts5_config.c.
@@ -182568,38 +210341,38 @@ struct Fts5Buffer {
int nSpace;
};
-static int sqlite3Fts5BufferSize(int*, Fts5Buffer*, u32);
-static void sqlite3Fts5BufferAppendVarint(int*, Fts5Buffer*, i64);
-static void sqlite3Fts5BufferAppendBlob(int*, Fts5Buffer*, u32, const u8*);
-static void sqlite3Fts5BufferAppendString(int *, Fts5Buffer*, const char*);
-static void sqlite3Fts5BufferFree(Fts5Buffer*);
-static void sqlite3Fts5BufferZero(Fts5Buffer*);
-static void sqlite3Fts5BufferSet(int*, Fts5Buffer*, int, const u8*);
-static void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...);
+static int tdsqlite3Fts5BufferSize(int*, Fts5Buffer*, u32);
+static void tdsqlite3Fts5BufferAppendVarint(int*, Fts5Buffer*, i64);
+static void tdsqlite3Fts5BufferAppendBlob(int*, Fts5Buffer*, u32, const u8*);
+static void tdsqlite3Fts5BufferAppendString(int *, Fts5Buffer*, const char*);
+static void tdsqlite3Fts5BufferFree(Fts5Buffer*);
+static void tdsqlite3Fts5BufferZero(Fts5Buffer*);
+static void tdsqlite3Fts5BufferSet(int*, Fts5Buffer*, int, const u8*);
+static void tdsqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...);
-static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...);
+static char *tdsqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...);
-#define fts5BufferZero(x) sqlite3Fts5BufferZero(x)
-#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,c)
-#define fts5BufferFree(a) sqlite3Fts5BufferFree(a)
-#define fts5BufferAppendBlob(a,b,c,d) sqlite3Fts5BufferAppendBlob(a,b,c,d)
-#define fts5BufferSet(a,b,c,d) sqlite3Fts5BufferSet(a,b,c,d)
+#define fts5BufferZero(x) tdsqlite3Fts5BufferZero(x)
+#define fts5BufferAppendVarint(a,b,c) tdsqlite3Fts5BufferAppendVarint(a,b,c)
+#define fts5BufferFree(a) tdsqlite3Fts5BufferFree(a)
+#define fts5BufferAppendBlob(a,b,c,d) tdsqlite3Fts5BufferAppendBlob(a,b,c,d)
+#define fts5BufferSet(a,b,c,d) tdsqlite3Fts5BufferSet(a,b,c,d)
#define fts5BufferGrow(pRc,pBuf,nn) ( \
(u32)((pBuf)->n) + (u32)(nn) <= (u32)((pBuf)->nSpace) ? 0 : \
- sqlite3Fts5BufferSize((pRc),(pBuf),(nn)+(pBuf)->n) \
+ tdsqlite3Fts5BufferSize((pRc),(pBuf),(nn)+(pBuf)->n) \
)
/* Write and decode big-endian 32-bit integer values */
-static void sqlite3Fts5Put32(u8*, int);
-static int sqlite3Fts5Get32(const u8*);
+static void tdsqlite3Fts5Put32(u8*, int);
+static int tdsqlite3Fts5Get32(const u8*);
#define FTS5_POS2COLUMN(iPos) (int)(iPos >> 32)
-#define FTS5_POS2OFFSET(iPos) (int)(iPos & 0xFFFFFFFF)
+#define FTS5_POS2OFFSET(iPos) (int)(iPos & 0x7FFFFFFF)
typedef struct Fts5PoslistReader Fts5PoslistReader;
struct Fts5PoslistReader {
- /* Variables used only by sqlite3Fts5PoslistIterXXX() functions. */
+ /* Variables used only by tdsqlite3Fts5PoslistIterXXX() functions. */
const u8 *a; /* Position list to iterate through */
int n; /* Size of buffer at a[] in bytes */
int i; /* Current offset in a[] */
@@ -182610,38 +210383,38 @@ struct Fts5PoslistReader {
u8 bEof; /* Set to true at EOF */
i64 iPos; /* (iCol<<32) + iPos */
};
-static int sqlite3Fts5PoslistReaderInit(
+static int tdsqlite3Fts5PoslistReaderInit(
const u8 *a, int n, /* Poslist buffer to iterate through */
Fts5PoslistReader *pIter /* Iterator object to initialize */
);
-static int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader*);
+static int tdsqlite3Fts5PoslistReaderNext(Fts5PoslistReader*);
typedef struct Fts5PoslistWriter Fts5PoslistWriter;
struct Fts5PoslistWriter {
i64 iPrev;
};
-static int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64);
-static void sqlite3Fts5PoslistSafeAppend(Fts5Buffer*, i64*, i64);
+static int tdsqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64);
+static void tdsqlite3Fts5PoslistSafeAppend(Fts5Buffer*, i64*, i64);
-static int sqlite3Fts5PoslistNext64(
+static int tdsqlite3Fts5PoslistNext64(
const u8 *a, int n, /* Buffer containing poslist */
int *pi, /* IN/OUT: Offset within a[] */
i64 *piOff /* IN/OUT: Current offset */
);
/* Malloc utility */
-static void *sqlite3Fts5MallocZero(int *pRc, int nByte);
-static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn);
+static void *tdsqlite3Fts5MallocZero(int *pRc, tdsqlite3_int64 nByte);
+static char *tdsqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn);
/* Character set tests (like isspace(), isalpha() etc.) */
-static int sqlite3Fts5IsBareword(char t);
+static int tdsqlite3Fts5IsBareword(char t);
/* Bucket of terms object used by the integrity-check in offsets=0 mode. */
typedef struct Fts5Termset Fts5Termset;
-static int sqlite3Fts5TermsetNew(Fts5Termset**);
-static int sqlite3Fts5TermsetAdd(Fts5Termset*, int, const char*, int, int *pbPresent);
-static void sqlite3Fts5TermsetFree(Fts5Termset*);
+static int tdsqlite3Fts5TermsetNew(Fts5Termset**);
+static int tdsqlite3Fts5TermsetAdd(Fts5Termset*, int, const char*, int, int *pbPresent);
+static void tdsqlite3Fts5TermsetFree(Fts5Termset*);
/*
** End of interface to code in fts5_buffer.c.
@@ -182662,7 +210435,7 @@ struct Fts5IndexIter {
u8 bEof;
};
-#define sqlite3Fts5IterEof(x) ((x)->bEof)
+#define tdsqlite3Fts5IterEof(x) ((x)->bEof)
/*
** Values used as part of the flags argument passed to IndexQuery().
@@ -182681,13 +210454,13 @@ struct Fts5IndexIter {
/*
** Create/destroy an Fts5Index object.
*/
-static int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**);
-static int sqlite3Fts5IndexClose(Fts5Index *p);
+static int tdsqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**);
+static int tdsqlite3Fts5IndexClose(Fts5Index *p);
/*
** Return a simple checksum value based on the arguments.
*/
-static u64 sqlite3Fts5IndexEntryCksum(
+static u64 tdsqlite3Fts5IndexEntryCksum(
i64 iRowid,
int iCol,
int iPos,
@@ -182701,7 +210474,7 @@ static u64 sqlite3Fts5IndexEntryCksum(
** size. Return the number of bytes in the nChar character prefix of the
** buffer, or 0 if there are less than nChar characters in total.
*/
-static int sqlite3Fts5IndexCharlenToBytelen(
+static int tdsqlite3Fts5IndexCharlenToBytelen(
const char *p,
int nByte,
int nChar
@@ -182711,7 +210484,7 @@ static int sqlite3Fts5IndexCharlenToBytelen(
** Open a new iterator to iterate though all rowids that match the
** specified token or token prefix.
*/
-static int sqlite3Fts5IndexQuery(
+static int tdsqlite3Fts5IndexQuery(
Fts5Index *p, /* FTS index to query */
const char *pToken, int nToken, /* Token (or prefix) to query for */
int flags, /* Mask of FTS5INDEX_QUERY_X flags */
@@ -182721,21 +210494,26 @@ static int sqlite3Fts5IndexQuery(
/*
** The various operations on open token or token prefix iterators opened
-** using sqlite3Fts5IndexQuery().
+** using tdsqlite3Fts5IndexQuery().
+*/
+static int tdsqlite3Fts5IterNext(Fts5IndexIter*);
+static int tdsqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch);
+
+/*
+** Close an iterator opened by tdsqlite3Fts5IndexQuery().
*/
-static int sqlite3Fts5IterNext(Fts5IndexIter*);
-static int sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch);
+static void tdsqlite3Fts5IterClose(Fts5IndexIter*);
/*
-** Close an iterator opened by sqlite3Fts5IndexQuery().
+** Close the reader blob handle, if it is open.
*/
-static void sqlite3Fts5IterClose(Fts5IndexIter*);
+static void tdsqlite3Fts5IndexCloseReader(Fts5Index*);
/*
** This interface is used by the fts5vocab module.
*/
-static const char *sqlite3Fts5IterTerm(Fts5IndexIter*, int*);
-static int sqlite3Fts5IterNextScan(Fts5IndexIter*);
+static const char *tdsqlite3Fts5IterTerm(Fts5IndexIter*, int*);
+static int tdsqlite3Fts5IterNextScan(Fts5IndexIter*);
/*
@@ -182748,7 +210526,7 @@ static int sqlite3Fts5IterNextScan(Fts5IndexIter*);
** unique token in the document with an iCol value less than zero. The iPos
** argument is ignored for a delete.
*/
-static int sqlite3Fts5IndexWrite(
+static int tdsqlite3Fts5IndexWrite(
Fts5Index *p, /* Index to write to */
int iCol, /* Column token appears in (-ve -> delete) */
int iPos, /* Position of token within column */
@@ -182756,10 +210534,10 @@ static int sqlite3Fts5IndexWrite(
);
/*
-** Indicate that subsequent calls to sqlite3Fts5IndexWrite() pertain to
+** Indicate that subsequent calls to tdsqlite3Fts5IndexWrite() pertain to
** document iDocid.
*/
-static int sqlite3Fts5IndexBeginWrite(
+static int tdsqlite3Fts5IndexBeginWrite(
Fts5Index *p, /* Index to write to */
int bDelete, /* True if current operation is a delete */
i64 iDocid /* Docid to add or remove data from */
@@ -182767,9 +210545,9 @@ static int sqlite3Fts5IndexBeginWrite(
/*
** Flush any data stored in the in-memory hash tables to the database.
-** If the bCommit flag is true, also close any open blob handles.
+** Also close any open blob handles.
*/
-static int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit);
+static int tdsqlite3Fts5IndexSync(Fts5Index *p);
/*
** Discard any data stored in the in-memory hash tables. Do not write it
@@ -182777,39 +210555,39 @@ static int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit);
** table may have changed on disk. So any in-memory caches of %_data
** records must be invalidated.
*/
-static int sqlite3Fts5IndexRollback(Fts5Index *p);
+static int tdsqlite3Fts5IndexRollback(Fts5Index *p);
/*
** Get or set the "averages" values.
*/
-static int sqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize);
-static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int);
+static int tdsqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize);
+static int tdsqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int);
/*
** Functions called by the storage module as part of integrity-check.
*/
-static int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum);
+static int tdsqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum);
/*
** Called during virtual module initialization to register UDF
** fts5_decode() with SQLite
*/
-static int sqlite3Fts5IndexInit(sqlite3*);
+static int tdsqlite3Fts5IndexInit(tdsqlite3*);
-static int sqlite3Fts5IndexSetCookie(Fts5Index*, int);
+static int tdsqlite3Fts5IndexSetCookie(Fts5Index*, int);
/*
** Return the total number of entries read from the %_data table by
** this connection since it was created.
*/
-static int sqlite3Fts5IndexReads(Fts5Index *p);
+static int tdsqlite3Fts5IndexReads(Fts5Index *p);
-static int sqlite3Fts5IndexReinit(Fts5Index *p);
-static int sqlite3Fts5IndexOptimize(Fts5Index *p);
-static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge);
-static int sqlite3Fts5IndexReset(Fts5Index *p);
+static int tdsqlite3Fts5IndexReinit(Fts5Index *p);
+static int tdsqlite3Fts5IndexOptimize(Fts5Index *p);
+static int tdsqlite3Fts5IndexMerge(Fts5Index *p, int nMerge);
+static int tdsqlite3Fts5IndexReset(Fts5Index *p);
-static int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
+static int tdsqlite3Fts5IndexLoadConfig(Fts5Index *p);
/*
** End of interface to code in fts5_index.c.
@@ -182818,13 +210596,13 @@ static int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
/**************************************************************************
** Interface to code in fts5_varint.c.
*/
-static int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v);
-static int sqlite3Fts5GetVarintLen(u32 iVal);
-static u8 sqlite3Fts5GetVarint(const unsigned char*, u64*);
-static int sqlite3Fts5PutVarint(unsigned char *p, u64 v);
+static int tdsqlite3Fts5GetVarint32(const unsigned char *p, u32 *v);
+static int tdsqlite3Fts5GetVarintLen(u32 iVal);
+static u8 tdsqlite3Fts5GetVarint(const unsigned char*, u64*);
+static int tdsqlite3Fts5PutVarint(unsigned char *p, u64 v);
-#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&b)
-#define fts5GetVarint sqlite3Fts5GetVarint
+#define fts5GetVarint32(a,b) tdsqlite3Fts5GetVarint32(a,(u32*)&b)
+#define fts5GetVarint tdsqlite3Fts5GetVarint
#define fts5FastGetVarint32(a, iOff, nVal) { \
nVal = (a)[iOff++]; \
@@ -182841,10 +210619,20 @@ static int sqlite3Fts5PutVarint(unsigned char *p, u64 v);
/**************************************************************************
-** Interface to code in fts5.c.
+** Interface to code in fts5_main.c.
*/
-static int sqlite3Fts5GetTokenizer(
+/*
+** Virtual-table object.
+*/
+typedef struct Fts5Table Fts5Table;
+struct Fts5Table {
+ tdsqlite3_vtab base; /* Base class used by SQLite core */
+ Fts5Config *pConfig; /* Virtual table configuration */
+ Fts5Index *pIndex; /* Full-text index */
+};
+
+static int tdsqlite3Fts5GetTokenizer(
Fts5Global*,
const char **azArg,
int nArg,
@@ -182853,7 +210641,9 @@ static int sqlite3Fts5GetTokenizer(
char **pzErr
);
-static Fts5Index *sqlite3Fts5IndexFromCsrid(Fts5Global*, i64, Fts5Config **);
+static Fts5Table *tdsqlite3Fts5TableFromCsrid(Fts5Global*, i64);
+
+static int tdsqlite3Fts5FlushToDisk(Fts5Table*);
/*
** End of interface to code in fts5.c.
@@ -182867,10 +210657,10 @@ typedef struct Fts5Hash Fts5Hash;
/*
** Create a hash table, free a hash table.
*/
-static int sqlite3Fts5HashNew(Fts5Config*, Fts5Hash**, int *pnSize);
-static void sqlite3Fts5HashFree(Fts5Hash*);
+static int tdsqlite3Fts5HashNew(Fts5Config*, Fts5Hash**, int *pnSize);
+static void tdsqlite3Fts5HashFree(Fts5Hash*);
-static int sqlite3Fts5HashWrite(
+static int tdsqlite3Fts5HashWrite(
Fts5Hash*,
i64 iRowid, /* Rowid for this entry */
int iCol, /* Column token appears in (-ve -> delete) */
@@ -182882,22 +210672,23 @@ static int sqlite3Fts5HashWrite(
/*
** Empty (but do not delete) a hash table.
*/
-static void sqlite3Fts5HashClear(Fts5Hash*);
+static void tdsqlite3Fts5HashClear(Fts5Hash*);
-static int sqlite3Fts5HashQuery(
+static int tdsqlite3Fts5HashQuery(
Fts5Hash*, /* Hash table to query */
+ int nPre,
const char *pTerm, int nTerm, /* Query term */
- const u8 **ppDoclist, /* OUT: Pointer to doclist for pTerm */
+ void **ppObj, /* OUT: Pointer to doclist for pTerm */
int *pnDoclist /* OUT: Size of doclist in bytes */
);
-static int sqlite3Fts5HashScanInit(
+static int tdsqlite3Fts5HashScanInit(
Fts5Hash*, /* Hash table to query */
const char *pTerm, int nTerm /* Query prefix */
);
-static void sqlite3Fts5HashScanNext(Fts5Hash*);
-static int sqlite3Fts5HashScanEof(Fts5Hash*);
-static void sqlite3Fts5HashScanEntry(Fts5Hash *,
+static void tdsqlite3Fts5HashScanNext(Fts5Hash*);
+static int tdsqlite3Fts5HashScanEof(Fts5Hash*);
+static void tdsqlite3Fts5HashScanEntry(Fts5Hash *,
const char **pzTerm, /* OUT: term (nul-terminated) */
const u8 **ppDoclist, /* OUT: pointer to doclist */
int *pnDoclist /* OUT: size of doclist in bytes */
@@ -182919,38 +210710,38 @@ static void sqlite3Fts5HashScanEntry(Fts5Hash *,
typedef struct Fts5Storage Fts5Storage;
-static int sqlite3Fts5StorageOpen(Fts5Config*, Fts5Index*, int, Fts5Storage**, char**);
-static int sqlite3Fts5StorageClose(Fts5Storage *p);
-static int sqlite3Fts5StorageRename(Fts5Storage*, const char *zName);
+static int tdsqlite3Fts5StorageOpen(Fts5Config*, Fts5Index*, int, Fts5Storage**, char**);
+static int tdsqlite3Fts5StorageClose(Fts5Storage *p);
+static int tdsqlite3Fts5StorageRename(Fts5Storage*, const char *zName);
-static int sqlite3Fts5DropAll(Fts5Config*);
-static int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **);
+static int tdsqlite3Fts5DropAll(Fts5Config*);
+static int tdsqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **);
-static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**);
-static int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*);
-static int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64);
+static int tdsqlite3Fts5StorageDelete(Fts5Storage *p, i64, tdsqlite3_value**);
+static int tdsqlite3Fts5StorageContentInsert(Fts5Storage *p, tdsqlite3_value**, i64*);
+static int tdsqlite3Fts5StorageIndexInsert(Fts5Storage *p, tdsqlite3_value**, i64);
-static int sqlite3Fts5StorageIntegrity(Fts5Storage *p);
+static int tdsqlite3Fts5StorageIntegrity(Fts5Storage *p);
-static int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt**, char**);
-static void sqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, sqlite3_stmt*);
+static int tdsqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, tdsqlite3_stmt**, char**);
+static void tdsqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, tdsqlite3_stmt*);
-static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol);
-static int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnAvg);
-static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow);
+static int tdsqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol);
+static int tdsqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnAvg);
+static int tdsqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow);
-static int sqlite3Fts5StorageSync(Fts5Storage *p, int bCommit);
-static int sqlite3Fts5StorageRollback(Fts5Storage *p);
+static int tdsqlite3Fts5StorageSync(Fts5Storage *p);
+static int tdsqlite3Fts5StorageRollback(Fts5Storage *p);
-static int sqlite3Fts5StorageConfigValue(
- Fts5Storage *p, const char*, sqlite3_value*, int
+static int tdsqlite3Fts5StorageConfigValue(
+ Fts5Storage *p, const char*, tdsqlite3_value*, int
);
-static int sqlite3Fts5StorageDeleteAll(Fts5Storage *p);
-static int sqlite3Fts5StorageRebuild(Fts5Storage *p);
-static int sqlite3Fts5StorageOptimize(Fts5Storage *p);
-static int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge);
-static int sqlite3Fts5StorageReset(Fts5Storage *p);
+static int tdsqlite3Fts5StorageDeleteAll(Fts5Storage *p);
+static int tdsqlite3Fts5StorageRebuild(Fts5Storage *p);
+static int tdsqlite3Fts5StorageOptimize(Fts5Storage *p);
+static int tdsqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge);
+static int tdsqlite3Fts5StorageReset(Fts5Storage *p);
/*
** End of interface to code in fts5_storage.c.
@@ -182973,55 +210764,57 @@ struct Fts5Token {
};
/* Parse a MATCH expression. */
-static int sqlite3Fts5ExprNew(
+static int tdsqlite3Fts5ExprNew(
Fts5Config *pConfig,
+ int iCol, /* Column on LHS of MATCH operator */
const char *zExpr,
Fts5Expr **ppNew,
char **pzErr
);
/*
-** for(rc = sqlite3Fts5ExprFirst(pExpr, pIdx, bDesc);
-** rc==SQLITE_OK && 0==sqlite3Fts5ExprEof(pExpr);
-** rc = sqlite3Fts5ExprNext(pExpr)
+** for(rc = tdsqlite3Fts5ExprFirst(pExpr, pIdx, bDesc);
+** rc==SQLITE_OK && 0==tdsqlite3Fts5ExprEof(pExpr);
+** rc = tdsqlite3Fts5ExprNext(pExpr)
** ){
** // The document with rowid iRowid matches the expression!
-** i64 iRowid = sqlite3Fts5ExprRowid(pExpr);
+** i64 iRowid = tdsqlite3Fts5ExprRowid(pExpr);
** }
*/
-static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc);
-static int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax);
-static int sqlite3Fts5ExprEof(Fts5Expr*);
-static i64 sqlite3Fts5ExprRowid(Fts5Expr*);
+static int tdsqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc);
+static int tdsqlite3Fts5ExprNext(Fts5Expr*, i64 iMax);
+static int tdsqlite3Fts5ExprEof(Fts5Expr*);
+static i64 tdsqlite3Fts5ExprRowid(Fts5Expr*);
-static void sqlite3Fts5ExprFree(Fts5Expr*);
+static void tdsqlite3Fts5ExprFree(Fts5Expr*);
+static int tdsqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2);
/* Called during startup to register a UDF with SQLite */
-static int sqlite3Fts5ExprInit(Fts5Global*, sqlite3*);
+static int tdsqlite3Fts5ExprInit(Fts5Global*, tdsqlite3*);
-static int sqlite3Fts5ExprPhraseCount(Fts5Expr*);
-static int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase);
-static int sqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **);
+static int tdsqlite3Fts5ExprPhraseCount(Fts5Expr*);
+static int tdsqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase);
+static int tdsqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **);
typedef struct Fts5PoslistPopulator Fts5PoslistPopulator;
-static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr*, int);
-static int sqlite3Fts5ExprPopulatePoslists(
+static Fts5PoslistPopulator *tdsqlite3Fts5ExprClearPoslists(Fts5Expr*, int);
+static int tdsqlite3Fts5ExprPopulatePoslists(
Fts5Config*, Fts5Expr*, Fts5PoslistPopulator*, int, const char*, int
);
-static void sqlite3Fts5ExprCheckPoslists(Fts5Expr*, i64);
+static void tdsqlite3Fts5ExprCheckPoslists(Fts5Expr*, i64);
-static int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**);
+static int tdsqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**);
-static int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *);
+static int tdsqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *);
/*******************************************
** The fts5_expr.c API above this point is used by the other hand-written
** C code in this module. The interfaces below this point are called by
** the parser code in fts5parse.y. */
-static void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...);
+static void tdsqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...);
-static Fts5ExprNode *sqlite3Fts5ParseNode(
+static Fts5ExprNode *tdsqlite3Fts5ParseNode(
Fts5Parse *pParse,
int eType,
Fts5ExprNode *pLeft,
@@ -183029,40 +210822,42 @@ static Fts5ExprNode *sqlite3Fts5ParseNode(
Fts5ExprNearset *pNear
);
-static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
+static Fts5ExprNode *tdsqlite3Fts5ParseImplicitAnd(
Fts5Parse *pParse,
Fts5ExprNode *pLeft,
Fts5ExprNode *pRight
);
-static Fts5ExprPhrase *sqlite3Fts5ParseTerm(
+static Fts5ExprPhrase *tdsqlite3Fts5ParseTerm(
Fts5Parse *pParse,
Fts5ExprPhrase *pPhrase,
Fts5Token *pToken,
int bPrefix
);
-static Fts5ExprNearset *sqlite3Fts5ParseNearset(
+static void tdsqlite3Fts5ParseSetCaret(Fts5ExprPhrase*);
+
+static Fts5ExprNearset *tdsqlite3Fts5ParseNearset(
Fts5Parse*,
Fts5ExprNearset*,
Fts5ExprPhrase*
);
-static Fts5Colset *sqlite3Fts5ParseColset(
+static Fts5Colset *tdsqlite3Fts5ParseColset(
Fts5Parse*,
Fts5Colset*,
Fts5Token *
);
-static void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase*);
-static void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset*);
-static void sqlite3Fts5ParseNodeFree(Fts5ExprNode*);
+static void tdsqlite3Fts5ParsePhraseFree(Fts5ExprPhrase*);
+static void tdsqlite3Fts5ParseNearsetFree(Fts5ExprNearset*);
+static void tdsqlite3Fts5ParseNodeFree(Fts5ExprNode*);
-static void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*);
-static void sqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNearset*, Fts5Colset*);
-static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse*, Fts5Colset*);
-static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p);
-static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*);
+static void tdsqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*);
+static void tdsqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNode*, Fts5Colset*);
+static Fts5Colset *tdsqlite3Fts5ParseColsetInvert(Fts5Parse*, Fts5Colset*);
+static void tdsqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p);
+static void tdsqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*);
/*
** End of interface to code in fts5_expr.c.
@@ -183074,7 +210869,7 @@ static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*);
** Interface to code in fts5_aux.c.
*/
-static int sqlite3Fts5AuxInit(fts5_api*);
+static int tdsqlite3Fts5AuxInit(fts5_api*);
/*
** End of interface to code in fts5_aux.c.
**************************************************************************/
@@ -183083,7 +210878,7 @@ static int sqlite3Fts5AuxInit(fts5_api*);
** Interface to code in fts5_tokenizer.c.
*/
-static int sqlite3Fts5TokenizerInit(fts5_api*);
+static int tdsqlite3Fts5TokenizerInit(fts5_api*);
/*
** End of interface to code in fts5_tokenizer.c.
**************************************************************************/
@@ -183092,7 +210887,7 @@ static int sqlite3Fts5TokenizerInit(fts5_api*);
** Interface to code in fts5_vocab.c.
*/
-static int sqlite3Fts5VocabInit(Fts5Global*, sqlite3*);
+static int tdsqlite3Fts5VocabInit(Fts5Global*, tdsqlite3*);
/*
** End of interface to code in fts5_vocab.c.
@@ -183102,9 +210897,12 @@ static int sqlite3Fts5VocabInit(Fts5Global*, sqlite3*);
/**************************************************************************
** Interface to automatically generated code in fts5_unicode2.c.
*/
-static int sqlite3Fts5UnicodeIsalnum(int c);
-static int sqlite3Fts5UnicodeIsdiacritic(int c);
-static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic);
+static int tdsqlite3Fts5UnicodeIsdiacritic(int c);
+static int tdsqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic);
+
+static int tdsqlite3Fts5UnicodeCatParse(const char*, u8*);
+static int tdsqlite3Fts5UnicodeCategory(u32 iCode);
+static void tdsqlite3Fts5UnicodeAscii(u8*, u8*);
/*
** End of interface to code in fts5_unicode2.c.
**************************************************************************/
@@ -183116,15 +210914,16 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic);
#define FTS5_NOT 3
#define FTS5_TERM 4
#define FTS5_COLON 5
-#define FTS5_LP 6
-#define FTS5_RP 7
-#define FTS5_MINUS 8
-#define FTS5_LCP 9
-#define FTS5_RCP 10
-#define FTS5_STRING 11
-#define FTS5_COMMA 12
-#define FTS5_PLUS 13
-#define FTS5_STAR 14
+#define FTS5_MINUS 6
+#define FTS5_LCP 7
+#define FTS5_RCP 8
+#define FTS5_STRING 9
+#define FTS5_LP 10
+#define FTS5_RP 11
+#define FTS5_CARET 12
+#define FTS5_COMMA 13
+#define FTS5_PLUS 14
+#define FTS5_STAR 15
/*
** 2000-05-29
@@ -183151,6 +210950,7 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic);
** input grammar file:
*/
/* #include <stdio.h> */
+/* #include <assert.h> */
/************ Begin %include sections from the grammar ************************/
/* #include "fts5Int.h" */
@@ -183168,14 +210968,14 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic);
#define fts5yytestcase(X) testcase(X)
/*
-** Indicate that sqlite3ParserFree() will never be called with a null
+** Indicate that tdsqlite3ParserFree() will never be called with a null
** pointer.
*/
#define fts5YYPARSEFREENOTNULL 1
/*
** Alternative datatype for the argument to the malloc() routine passed
-** into sqlite3ParserAlloc(). The default is size_t.
+** into tdsqlite3ParserAlloc(). The default is size_t.
*/
#define fts5YYMALLOCARGTYPE u64
@@ -183202,7 +211002,7 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic);
** fts5YYACTIONTYPE is the data type used for "action codes" - numbers
** that indicate what to do in response to the next
** token.
-** sqlite3Fts5ParserFTS5TOKENTYPE is the data type used for minor type for terminal
+** tdsqlite3Fts5ParserFTS5TOKENTYPE is the data type used for minor type for terminal
** symbols. Background: A "minor type" is a semantic
** value associated with a terminal or non-terminal
** symbols. For example, for an "ID" terminal symbol,
@@ -183213,37 +211013,41 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic);
** symbols.
** fts5YYMINORTYPE is the data type used for all minor types.
** This is typically a union of many types, one of
-** which is sqlite3Fts5ParserFTS5TOKENTYPE. The entry in the union
+** which is tdsqlite3Fts5ParserFTS5TOKENTYPE. The entry in the union
** for terminal symbols is called "fts5yy0".
** fts5YYSTACKDEPTH is the maximum depth of the parser's stack. If
** zero the stack is dynamically sized using realloc()
-** sqlite3Fts5ParserARG_SDECL A static variable declaration for the %extra_argument
-** sqlite3Fts5ParserARG_PDECL A parameter declaration for the %extra_argument
-** sqlite3Fts5ParserARG_STORE Code to store %extra_argument into fts5yypParser
-** sqlite3Fts5ParserARG_FETCH Code to extract %extra_argument from fts5yypParser
+** tdsqlite3Fts5ParserARG_SDECL A static variable declaration for the %extra_argument
+** tdsqlite3Fts5ParserARG_PDECL A parameter declaration for the %extra_argument
+** tdsqlite3Fts5ParserARG_PARAM Code to pass %extra_argument as a subroutine parameter
+** tdsqlite3Fts5ParserARG_STORE Code to store %extra_argument into fts5yypParser
+** tdsqlite3Fts5ParserARG_FETCH Code to extract %extra_argument from fts5yypParser
+** tdsqlite3Fts5ParserCTX_* As tdsqlite3Fts5ParserARG_ except for %extra_context
** fts5YYERRORSYMBOL is the code number of the error symbol. If not
** defined, then do no error processing.
** fts5YYNSTATE the combined number of states.
** fts5YYNRULE the number of rules in the grammar
+** fts5YYNFTS5TOKEN Number of terminal symbols
** fts5YY_MAX_SHIFT Maximum value for shift actions
** fts5YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions
** fts5YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions
-** fts5YY_MIN_REDUCE Maximum value for reduce actions
** fts5YY_ERROR_ACTION The fts5yy_action[] code for syntax error
** fts5YY_ACCEPT_ACTION The fts5yy_action[] code for accept
** fts5YY_NO_ACTION The fts5yy_action[] code for no-op
+** fts5YY_MIN_REDUCE Minimum value for reduce actions
+** fts5YY_MAX_REDUCE Maximum value for reduce actions
*/
#ifndef INTERFACE
# define INTERFACE 1
#endif
/************* Begin control #defines *****************************************/
#define fts5YYCODETYPE unsigned char
-#define fts5YYNOCODE 28
+#define fts5YYNOCODE 27
#define fts5YYACTIONTYPE unsigned char
-#define sqlite3Fts5ParserFTS5TOKENTYPE Fts5Token
+#define tdsqlite3Fts5ParserFTS5TOKENTYPE Fts5Token
typedef union {
int fts5yyinit;
- sqlite3Fts5ParserFTS5TOKENTYPE fts5yy0;
+ tdsqlite3Fts5ParserFTS5TOKENTYPE fts5yy0;
int fts5yy4;
Fts5Colset* fts5yy11;
Fts5ExprNode* fts5yy24;
@@ -183253,21 +211057,30 @@ typedef union {
#ifndef fts5YYSTACKDEPTH
#define fts5YYSTACKDEPTH 100
#endif
-#define sqlite3Fts5ParserARG_SDECL Fts5Parse *pParse;
-#define sqlite3Fts5ParserARG_PDECL ,Fts5Parse *pParse
-#define sqlite3Fts5ParserARG_FETCH Fts5Parse *pParse = fts5yypParser->pParse
-#define sqlite3Fts5ParserARG_STORE fts5yypParser->pParse = pParse
-#define fts5YYNSTATE 29
-#define fts5YYNRULE 26
-#define fts5YY_MAX_SHIFT 28
-#define fts5YY_MIN_SHIFTREDUCE 45
-#define fts5YY_MAX_SHIFTREDUCE 70
-#define fts5YY_MIN_REDUCE 71
-#define fts5YY_MAX_REDUCE 96
-#define fts5YY_ERROR_ACTION 97
-#define fts5YY_ACCEPT_ACTION 98
-#define fts5YY_NO_ACTION 99
+#define tdsqlite3Fts5ParserARG_SDECL Fts5Parse *pParse;
+#define tdsqlite3Fts5ParserARG_PDECL ,Fts5Parse *pParse
+#define tdsqlite3Fts5ParserARG_PARAM ,pParse
+#define tdsqlite3Fts5ParserARG_FETCH Fts5Parse *pParse=fts5yypParser->pParse;
+#define tdsqlite3Fts5ParserARG_STORE fts5yypParser->pParse=pParse;
+#define tdsqlite3Fts5ParserCTX_SDECL
+#define tdsqlite3Fts5ParserCTX_PDECL
+#define tdsqlite3Fts5ParserCTX_PARAM
+#define tdsqlite3Fts5ParserCTX_FETCH
+#define tdsqlite3Fts5ParserCTX_STORE
+#define fts5YYNSTATE 35
+#define fts5YYNRULE 28
+#define fts5YYNRULE_WITH_ACTION 28
+#define fts5YYNFTS5TOKEN 16
+#define fts5YY_MAX_SHIFT 34
+#define fts5YY_MIN_SHIFTREDUCE 52
+#define fts5YY_MAX_SHIFTREDUCE 79
+#define fts5YY_ERROR_ACTION 80
+#define fts5YY_ACCEPT_ACTION 81
+#define fts5YY_NO_ACTION 82
+#define fts5YY_MIN_REDUCE 83
+#define fts5YY_MAX_REDUCE 110
/************* End control #defines *******************************************/
+#define fts5YY_NLOOKAHEAD ((int)(sizeof(fts5yy_lookahead)/sizeof(fts5yy_lookahead[0])))
/* Define the fts5yytestcase() macro to be a no-op if is not already defined
** otherwise.
@@ -183296,9 +211109,6 @@ typedef union {
** N between fts5YY_MIN_SHIFTREDUCE Shift to an arbitrary state then
** and fts5YY_MAX_SHIFTREDUCE reduce by rule N-fts5YY_MIN_SHIFTREDUCE.
**
-** N between fts5YY_MIN_REDUCE Reduce by rule N-fts5YY_MIN_REDUCE
-** and fts5YY_MAX_REDUCE
-**
** N == fts5YY_ERROR_ACTION A syntax error has occurred.
**
** N == fts5YY_ACCEPT_ACTION The parser accepts its input.
@@ -183306,25 +211116,22 @@ typedef union {
** N == fts5YY_NO_ACTION No such action. Denotes unused
** slots in the fts5yy_action[] table.
**
+** N between fts5YY_MIN_REDUCE Reduce by rule N-fts5YY_MIN_REDUCE
+** and fts5YY_MAX_REDUCE
+**
** The action table is constructed as a single large table named fts5yy_action[].
** Given state S and lookahead X, the action is computed as either:
**
** (A) N = fts5yy_action[ fts5yy_shift_ofst[S] + X ]
** (B) N = fts5yy_default[S]
**
-** The (A) formula is preferred. The B formula is used instead if:
-** (1) The fts5yy_shift_ofst[S]+X value is out of range, or
-** (2) fts5yy_lookahead[fts5yy_shift_ofst[S]+X] is not equal to X, or
-** (3) fts5yy_shift_ofst[S] equal fts5YY_SHIFT_USE_DFLT.
-** (Implementation note: fts5YY_SHIFT_USE_DFLT is chosen so that
-** fts5YY_SHIFT_USE_DFLT+X will be out of range for all possible lookaheads X.
-** Hence only tests (1) and (2) need to be evaluated.)
+** The (A) formula is preferred. The B formula is used instead if
+** fts5yy_lookahead[fts5yy_shift_ofst[S]+X] is not equal to X.
**
** The formulas above are 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 fts5yy_reduce_ofst[] array is used in place of
-** the fts5yy_shift_ofst[] array and fts5YY_REDUCE_USE_DFLT is used in place of
-** fts5YY_SHIFT_USE_DFLT.
+** the fts5yy_shift_ofst[] array.
**
** The following are the tables generated in this section:
**
@@ -183338,50 +211145,56 @@ typedef union {
** fts5yy_default[] Default action for each state.
**
*********** Begin parsing tables **********************************************/
-#define fts5YY_ACTTAB_COUNT (85)
+#define fts5YY_ACTTAB_COUNT (105)
static const fts5YYACTIONTYPE fts5yy_action[] = {
- /* 0 */ 98, 16, 51, 5, 53, 27, 83, 7, 26, 15,
- /* 10 */ 51, 5, 53, 27, 13, 69, 26, 48, 51, 5,
- /* 20 */ 53, 27, 19, 11, 26, 9, 20, 51, 5, 53,
- /* 30 */ 27, 13, 22, 26, 28, 51, 5, 53, 27, 68,
- /* 40 */ 1, 26, 19, 11, 17, 9, 52, 10, 53, 27,
- /* 50 */ 23, 24, 26, 54, 3, 4, 2, 26, 6, 21,
- /* 60 */ 49, 71, 3, 4, 2, 7, 56, 59, 55, 59,
- /* 70 */ 4, 2, 12, 69, 58, 60, 18, 67, 62, 69,
- /* 80 */ 25, 66, 8, 14, 2,
+ /* 0 */ 81, 20, 96, 6, 28, 99, 98, 26, 26, 18,
+ /* 10 */ 96, 6, 28, 17, 98, 56, 26, 19, 96, 6,
+ /* 20 */ 28, 14, 98, 14, 26, 31, 92, 96, 6, 28,
+ /* 30 */ 108, 98, 25, 26, 21, 96, 6, 28, 78, 98,
+ /* 40 */ 58, 26, 29, 96, 6, 28, 107, 98, 22, 26,
+ /* 50 */ 24, 16, 12, 11, 1, 13, 13, 24, 16, 23,
+ /* 60 */ 11, 33, 34, 13, 97, 8, 27, 32, 98, 7,
+ /* 70 */ 26, 3, 4, 5, 3, 4, 5, 3, 83, 4,
+ /* 80 */ 5, 3, 63, 5, 3, 62, 12, 2, 86, 13,
+ /* 90 */ 9, 30, 10, 10, 54, 57, 75, 78, 78, 53,
+ /* 100 */ 57, 15, 82, 82, 71,
};
static const fts5YYCODETYPE fts5yy_lookahead[] = {
- /* 0 */ 16, 17, 18, 19, 20, 21, 5, 6, 24, 17,
- /* 10 */ 18, 19, 20, 21, 11, 14, 24, 17, 18, 19,
- /* 20 */ 20, 21, 8, 9, 24, 11, 17, 18, 19, 20,
- /* 30 */ 21, 11, 12, 24, 17, 18, 19, 20, 21, 26,
- /* 40 */ 6, 24, 8, 9, 22, 11, 18, 11, 20, 21,
- /* 50 */ 24, 25, 24, 20, 1, 2, 3, 24, 23, 24,
- /* 60 */ 7, 0, 1, 2, 3, 6, 10, 11, 10, 11,
- /* 70 */ 2, 3, 9, 14, 11, 11, 22, 26, 7, 14,
- /* 80 */ 13, 11, 5, 11, 3,
+ /* 0 */ 16, 17, 18, 19, 20, 22, 22, 24, 24, 17,
+ /* 10 */ 18, 19, 20, 7, 22, 9, 24, 17, 18, 19,
+ /* 20 */ 20, 9, 22, 9, 24, 13, 17, 18, 19, 20,
+ /* 30 */ 26, 22, 24, 24, 17, 18, 19, 20, 15, 22,
+ /* 40 */ 9, 24, 17, 18, 19, 20, 26, 22, 21, 24,
+ /* 50 */ 6, 7, 9, 9, 10, 12, 12, 6, 7, 21,
+ /* 60 */ 9, 24, 25, 12, 18, 5, 20, 14, 22, 5,
+ /* 70 */ 24, 3, 1, 2, 3, 1, 2, 3, 0, 1,
+ /* 80 */ 2, 3, 11, 2, 3, 11, 9, 10, 5, 12,
+ /* 90 */ 23, 24, 10, 10, 8, 9, 9, 15, 15, 8,
+ /* 100 */ 9, 9, 27, 27, 11, 27, 27, 27, 27, 27,
+ /* 110 */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ /* 120 */ 27,
};
-#define fts5YY_SHIFT_USE_DFLT (85)
-#define fts5YY_SHIFT_COUNT (28)
+#define fts5YY_SHIFT_COUNT (34)
#define fts5YY_SHIFT_MIN (0)
-#define fts5YY_SHIFT_MAX (81)
+#define fts5YY_SHIFT_MAX (93)
static const unsigned char fts5yy_shift_ofst[] = {
- /* 0 */ 34, 34, 34, 34, 34, 14, 20, 3, 36, 1,
- /* 10 */ 59, 64, 64, 65, 65, 53, 61, 56, 58, 63,
- /* 20 */ 68, 67, 70, 67, 71, 72, 67, 77, 81,
+ /* 0 */ 44, 44, 44, 44, 44, 44, 51, 77, 43, 12,
+ /* 10 */ 14, 83, 82, 14, 23, 23, 31, 31, 71, 74,
+ /* 20 */ 78, 81, 86, 91, 6, 53, 53, 60, 64, 68,
+ /* 30 */ 53, 87, 92, 53, 93,
};
-#define fts5YY_REDUCE_USE_DFLT (-17)
-#define fts5YY_REDUCE_COUNT (14)
-#define fts5YY_REDUCE_MIN (-16)
-#define fts5YY_REDUCE_MAX (54)
+#define fts5YY_REDUCE_COUNT (17)
+#define fts5YY_REDUCE_MIN (-17)
+#define fts5YY_REDUCE_MAX (67)
static const signed char fts5yy_reduce_ofst[] = {
- /* 0 */ -16, -8, 0, 9, 17, 28, 26, 35, 33, 13,
- /* 10 */ 13, 22, 54, 13, 51,
+ /* 0 */ -16, -8, 0, 9, 17, 25, 46, -17, -17, 37,
+ /* 10 */ 67, 4, 4, 8, 4, 20, 27, 38,
};
static const fts5YYACTIONTYPE fts5yy_default[] = {
- /* 0 */ 97, 97, 97, 97, 97, 76, 91, 97, 97, 96,
- /* 10 */ 96, 97, 97, 96, 96, 97, 97, 97, 97, 97,
- /* 20 */ 73, 89, 97, 90, 97, 97, 87, 97, 72,
+ /* 0 */ 80, 80, 80, 80, 80, 80, 95, 80, 80, 105,
+ /* 10 */ 80, 110, 110, 80, 110, 110, 80, 80, 80, 80,
+ /* 20 */ 80, 91, 80, 80, 80, 101, 100, 80, 80, 90,
+ /* 30 */ 103, 80, 80, 104, 80,
};
/********** End of lemon-generated parsing tables *****************************/
@@ -183439,13 +211252,15 @@ struct fts5yyParser {
#ifndef fts5YYNOERRORRECOVERY
int fts5yyerrcnt; /* Shifts left before out of the error */
#endif
- sqlite3Fts5ParserARG_SDECL /* A place to hold %extra_argument */
+ tdsqlite3Fts5ParserARG_SDECL /* A place to hold %extra_argument */
+ tdsqlite3Fts5ParserCTX_SDECL /* A place to hold %extra_context */
#if fts5YYSTACKDEPTH<=0
int fts5yystksz; /* Current side of the stack */
fts5yyStackEntry *fts5yystack; /* The parser's stack */
fts5yyStackEntry fts5yystk0; /* First stack entry */
#else
fts5yyStackEntry fts5yystack[fts5YYSTACKDEPTH]; /* The parser's stack */
+ fts5yyStackEntry *fts5yystackEnd; /* Last entry in the stack */
#endif
};
typedef struct fts5yyParser fts5yyParser;
@@ -183474,7 +211289,7 @@ static char *fts5yyTracePrompt = 0;
** Outputs:
** None.
*/
-static void sqlite3Fts5ParserTrace(FILE *TraceFILE, char *zTracePrompt){
+static void tdsqlite3Fts5ParserTrace(FILE *TraceFILE, char *zTracePrompt){
fts5yyTraceFILE = TraceFILE;
fts5yyTracePrompt = zTracePrompt;
if( fts5yyTraceFILE==0 ) fts5yyTracePrompt = 0;
@@ -183482,50 +211297,72 @@ static void sqlite3Fts5ParserTrace(FILE *TraceFILE, char *zTracePrompt){
}
#endif /* NDEBUG */
-#ifndef NDEBUG
+#if defined(fts5YYCOVERAGE) || !defined(NDEBUG)
/* For tracing shifts, the names of all terminals and nonterminals
** are required. The following table supplies these names */
static const char *const fts5yyTokenName[] = {
- "$", "OR", "AND", "NOT",
- "TERM", "COLON", "LP", "RP",
- "MINUS", "LCP", "RCP", "STRING",
- "COMMA", "PLUS", "STAR", "error",
- "input", "expr", "cnearset", "exprlist",
- "nearset", "colset", "colsetlist", "nearphrases",
- "phrase", "neardist_opt", "star_opt",
+ /* 0 */ "$",
+ /* 1 */ "OR",
+ /* 2 */ "AND",
+ /* 3 */ "NOT",
+ /* 4 */ "TERM",
+ /* 5 */ "COLON",
+ /* 6 */ "MINUS",
+ /* 7 */ "LCP",
+ /* 8 */ "RCP",
+ /* 9 */ "STRING",
+ /* 10 */ "LP",
+ /* 11 */ "RP",
+ /* 12 */ "CARET",
+ /* 13 */ "COMMA",
+ /* 14 */ "PLUS",
+ /* 15 */ "STAR",
+ /* 16 */ "input",
+ /* 17 */ "expr",
+ /* 18 */ "cnearset",
+ /* 19 */ "exprlist",
+ /* 20 */ "colset",
+ /* 21 */ "colsetlist",
+ /* 22 */ "nearset",
+ /* 23 */ "nearphrases",
+ /* 24 */ "phrase",
+ /* 25 */ "neardist_opt",
+ /* 26 */ "star_opt",
};
-#endif /* NDEBUG */
+#endif /* defined(fts5YYCOVERAGE) || !defined(NDEBUG) */
#ifndef NDEBUG
/* For tracing reduce actions, the names of all rules are required.
*/
static const char *const fts5yyRuleName[] = {
/* 0 */ "input ::= expr",
- /* 1 */ "expr ::= expr AND expr",
- /* 2 */ "expr ::= expr OR expr",
- /* 3 */ "expr ::= expr NOT expr",
- /* 4 */ "expr ::= LP expr RP",
- /* 5 */ "expr ::= exprlist",
- /* 6 */ "exprlist ::= cnearset",
- /* 7 */ "exprlist ::= exprlist cnearset",
- /* 8 */ "cnearset ::= nearset",
- /* 9 */ "cnearset ::= colset COLON nearset",
- /* 10 */ "colset ::= MINUS LCP colsetlist RCP",
- /* 11 */ "colset ::= LCP colsetlist RCP",
- /* 12 */ "colset ::= STRING",
- /* 13 */ "colset ::= MINUS STRING",
- /* 14 */ "colsetlist ::= colsetlist STRING",
- /* 15 */ "colsetlist ::= STRING",
- /* 16 */ "nearset ::= phrase",
- /* 17 */ "nearset ::= STRING LP nearphrases neardist_opt RP",
- /* 18 */ "nearphrases ::= phrase",
- /* 19 */ "nearphrases ::= nearphrases phrase",
- /* 20 */ "neardist_opt ::=",
- /* 21 */ "neardist_opt ::= COMMA STRING",
- /* 22 */ "phrase ::= phrase PLUS STRING star_opt",
- /* 23 */ "phrase ::= STRING star_opt",
- /* 24 */ "star_opt ::= STAR",
- /* 25 */ "star_opt ::=",
+ /* 1 */ "colset ::= MINUS LCP colsetlist RCP",
+ /* 2 */ "colset ::= LCP colsetlist RCP",
+ /* 3 */ "colset ::= STRING",
+ /* 4 */ "colset ::= MINUS STRING",
+ /* 5 */ "colsetlist ::= colsetlist STRING",
+ /* 6 */ "colsetlist ::= STRING",
+ /* 7 */ "expr ::= expr AND expr",
+ /* 8 */ "expr ::= expr OR expr",
+ /* 9 */ "expr ::= expr NOT expr",
+ /* 10 */ "expr ::= colset COLON LP expr RP",
+ /* 11 */ "expr ::= LP expr RP",
+ /* 12 */ "expr ::= exprlist",
+ /* 13 */ "exprlist ::= cnearset",
+ /* 14 */ "exprlist ::= exprlist cnearset",
+ /* 15 */ "cnearset ::= nearset",
+ /* 16 */ "cnearset ::= colset COLON nearset",
+ /* 17 */ "nearset ::= phrase",
+ /* 18 */ "nearset ::= CARET phrase",
+ /* 19 */ "nearset ::= STRING LP nearphrases neardist_opt RP",
+ /* 20 */ "nearphrases ::= phrase",
+ /* 21 */ "nearphrases ::= nearphrases phrase",
+ /* 22 */ "neardist_opt ::=",
+ /* 23 */ "neardist_opt ::= COMMA STRING",
+ /* 24 */ "phrase ::= phrase PLUS STRING star_opt",
+ /* 25 */ "phrase ::= STRING star_opt",
+ /* 26 */ "star_opt ::= STAR",
+ /* 27 */ "star_opt ::=",
};
#endif /* NDEBUG */
@@ -183564,7 +211401,7 @@ static int fts5yyGrowStack(fts5yyParser *p){
#endif
/* Datatype of the argument to the memory allocated passed as the
-** second argument to sqlite3Fts5ParserAlloc() below. This can be changed by
+** second argument to tdsqlite3Fts5ParserAlloc() below. This can be changed by
** putting an appropriate #define in the %include section of the input
** grammar.
*/
@@ -183572,6 +211409,35 @@ static int fts5yyGrowStack(fts5yyParser *p){
# define fts5YYMALLOCARGTYPE size_t
#endif
+/* Initialize a new parser that has already been allocated.
+*/
+static void tdsqlite3Fts5ParserInit(void *fts5yypRawParser tdsqlite3Fts5ParserCTX_PDECL){
+ fts5yyParser *fts5yypParser = (fts5yyParser*)fts5yypRawParser;
+ tdsqlite3Fts5ParserCTX_STORE
+#ifdef fts5YYTRACKMAXSTACKDEPTH
+ fts5yypParser->fts5yyhwm = 0;
+#endif
+#if fts5YYSTACKDEPTH<=0
+ fts5yypParser->fts5yytos = NULL;
+ fts5yypParser->fts5yystack = NULL;
+ fts5yypParser->fts5yystksz = 0;
+ if( fts5yyGrowStack(fts5yypParser) ){
+ fts5yypParser->fts5yystack = &fts5yypParser->fts5yystk0;
+ fts5yypParser->fts5yystksz = 1;
+ }
+#endif
+#ifndef fts5YYNOERRORRECOVERY
+ fts5yypParser->fts5yyerrcnt = -1;
+#endif
+ fts5yypParser->fts5yytos = fts5yypParser->fts5yystack;
+ fts5yypParser->fts5yystack[0].stateno = 0;
+ fts5yypParser->fts5yystack[0].major = 0;
+#if fts5YYSTACKDEPTH>0
+ fts5yypParser->fts5yystackEnd = &fts5yypParser->fts5yystack[fts5YYSTACKDEPTH-1];
+#endif
+}
+
+#ifndef tdsqlite3Fts5Parser_ENGINEALWAYSONSTACK
/*
** This function allocates a new parser.
** The only argument is a pointer to a function which works like
@@ -183582,33 +211448,19 @@ static int fts5yyGrowStack(fts5yyParser *p){
**
** Outputs:
** A pointer to a parser. This pointer is used in subsequent calls
-** to sqlite3Fts5Parser and sqlite3Fts5ParserFree.
+** to tdsqlite3Fts5Parser and tdsqlite3Fts5ParserFree.
*/
-static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(fts5YYMALLOCARGTYPE)){
- fts5yyParser *pParser;
- pParser = (fts5yyParser*)(*mallocProc)( (fts5YYMALLOCARGTYPE)sizeof(fts5yyParser) );
- if( pParser ){
-#ifdef fts5YYTRACKMAXSTACKDEPTH
- pParser->fts5yyhwm = 0;
-#endif
-#if fts5YYSTACKDEPTH<=0
- pParser->fts5yytos = NULL;
- pParser->fts5yystack = NULL;
- pParser->fts5yystksz = 0;
- if( fts5yyGrowStack(pParser) ){
- pParser->fts5yystack = &pParser->fts5yystk0;
- pParser->fts5yystksz = 1;
- }
-#endif
-#ifndef fts5YYNOERRORRECOVERY
- pParser->fts5yyerrcnt = -1;
-#endif
- pParser->fts5yytos = pParser->fts5yystack;
- pParser->fts5yystack[0].stateno = 0;
- pParser->fts5yystack[0].major = 0;
+static void *tdsqlite3Fts5ParserAlloc(void *(*mallocProc)(fts5YYMALLOCARGTYPE) tdsqlite3Fts5ParserCTX_PDECL){
+ fts5yyParser *fts5yypParser;
+ fts5yypParser = (fts5yyParser*)(*mallocProc)( (fts5YYMALLOCARGTYPE)sizeof(fts5yyParser) );
+ if( fts5yypParser ){
+ tdsqlite3Fts5ParserCTX_STORE
+ tdsqlite3Fts5ParserInit(fts5yypParser tdsqlite3Fts5ParserCTX_PARAM);
}
- return pParser;
+ return (void*)fts5yypParser;
}
+#endif /* tdsqlite3Fts5Parser_ENGINEALWAYSONSTACK */
+
/* The following function deletes the "minor type" or semantic value
** associated with a symbol. The symbol can be either a terminal
@@ -183622,7 +211474,8 @@ static void fts5yy_destructor(
fts5YYCODETYPE fts5yymajor, /* Type code for object to destroy */
fts5YYMINORTYPE *fts5yypminor /* The object to be destroyed */
){
- sqlite3Fts5ParserARG_FETCH;
+ tdsqlite3Fts5ParserARG_FETCH
+ tdsqlite3Fts5ParserCTX_FETCH
switch( fts5yymajor ){
/* Here is inserted the actions which take place when a
** terminal or non-terminal is destroyed. This can happen
@@ -183644,24 +211497,24 @@ static void fts5yy_destructor(
case 18: /* cnearset */
case 19: /* exprlist */
{
- sqlite3Fts5ParseNodeFree((fts5yypminor->fts5yy24));
+ tdsqlite3Fts5ParseNodeFree((fts5yypminor->fts5yy24));
}
break;
- case 20: /* nearset */
- case 23: /* nearphrases */
+ case 20: /* colset */
+ case 21: /* colsetlist */
{
- sqlite3Fts5ParseNearsetFree((fts5yypminor->fts5yy46));
+ tdsqlite3_free((fts5yypminor->fts5yy11));
}
break;
- case 21: /* colset */
- case 22: /* colsetlist */
+ case 22: /* nearset */
+ case 23: /* nearphrases */
{
- sqlite3_free((fts5yypminor->fts5yy11));
+ tdsqlite3Fts5ParseNearsetFree((fts5yypminor->fts5yy46));
}
break;
case 24: /* phrase */
{
- sqlite3Fts5ParsePhraseFree((fts5yypminor->fts5yy53));
+ tdsqlite3Fts5ParsePhraseFree((fts5yypminor->fts5yy53));
}
break;
/********* End destructor definitions *****************************************/
@@ -183690,6 +211543,18 @@ static void fts5yy_pop_parser_stack(fts5yyParser *pParser){
fts5yy_destructor(pParser, fts5yytos->major, &fts5yytos->minor);
}
+/*
+** Clear all secondary memory allocations from the parser
+*/
+static void tdsqlite3Fts5ParserFinalize(void *p){
+ fts5yyParser *pParser = (fts5yyParser*)p;
+ while( pParser->fts5yytos>pParser->fts5yystack ) fts5yy_pop_parser_stack(pParser);
+#if fts5YYSTACKDEPTH<=0
+ if( pParser->fts5yystack!=&pParser->fts5yystk0 ) free(pParser->fts5yystack);
+#endif
+}
+
+#ifndef tdsqlite3Fts5Parser_ENGINEALWAYSONSTACK
/*
** Deallocate and destroy a parser. Destructors are called for
** all stack elements before shutting the parser down.
@@ -183698,53 +211563,95 @@ static void fts5yy_pop_parser_stack(fts5yyParser *pParser){
** is defined in a %include section of the input grammar) then it is
** assumed that the input pointer is never NULL.
*/
-static void sqlite3Fts5ParserFree(
+static void tdsqlite3Fts5ParserFree(
void *p, /* The parser to be deleted */
void (*freeProc)(void*) /* Function used to reclaim memory */
){
- fts5yyParser *pParser = (fts5yyParser*)p;
#ifndef fts5YYPARSEFREENEVERNULL
- if( pParser==0 ) return;
-#endif
- while( pParser->fts5yytos>pParser->fts5yystack ) fts5yy_pop_parser_stack(pParser);
-#if fts5YYSTACKDEPTH<=0
- if( pParser->fts5yystack!=&pParser->fts5yystk0 ) free(pParser->fts5yystack);
+ if( p==0 ) return;
#endif
- (*freeProc)((void*)pParser);
+ tdsqlite3Fts5ParserFinalize(p);
+ (*freeProc)(p);
}
+#endif /* tdsqlite3Fts5Parser_ENGINEALWAYSONSTACK */
/*
** Return the peak depth of the stack for a parser.
*/
#ifdef fts5YYTRACKMAXSTACKDEPTH
-static int sqlite3Fts5ParserStackPeak(void *p){
+static int tdsqlite3Fts5ParserStackPeak(void *p){
fts5yyParser *pParser = (fts5yyParser*)p;
return pParser->fts5yyhwm;
}
#endif
+/* This array of booleans keeps track of the parser statement
+** coverage. The element fts5yycoverage[X][Y] is set when the parser
+** is in state X and has a lookahead token Y. In a well-tested
+** systems, every element of this matrix should end up being set.
+*/
+#if defined(fts5YYCOVERAGE)
+static unsigned char fts5yycoverage[fts5YYNSTATE][fts5YYNFTS5TOKEN];
+#endif
+
+/*
+** Write into out a description of every state/lookahead combination that
+**
+** (1) has not been used by the parser, and
+** (2) is not a syntax error.
+**
+** Return the number of missed state/lookahead combinations.
+*/
+#if defined(fts5YYCOVERAGE)
+static int tdsqlite3Fts5ParserCoverage(FILE *out){
+ int stateno, iLookAhead, i;
+ int nMissed = 0;
+ for(stateno=0; stateno<fts5YYNSTATE; stateno++){
+ i = fts5yy_shift_ofst[stateno];
+ for(iLookAhead=0; iLookAhead<fts5YYNFTS5TOKEN; iLookAhead++){
+ if( fts5yy_lookahead[i+iLookAhead]!=iLookAhead ) continue;
+ if( fts5yycoverage[stateno][iLookAhead]==0 ) nMissed++;
+ if( out ){
+ fprintf(out,"State %d lookahead %s %s\n", stateno,
+ fts5yyTokenName[iLookAhead],
+ fts5yycoverage[stateno][iLookAhead] ? "ok" : "missed");
+ }
+ }
+ }
+ return nMissed;
+}
+#endif
+
/*
** Find the appropriate action for a parser given the terminal
** look-ahead token iLookAhead.
*/
-static unsigned int fts5yy_find_shift_action(
- fts5yyParser *pParser, /* The parser */
- fts5YYCODETYPE iLookAhead /* The look-ahead token */
+static fts5YYACTIONTYPE fts5yy_find_shift_action(
+ fts5YYCODETYPE iLookAhead, /* The look-ahead token */
+ fts5YYACTIONTYPE stateno /* Current state number */
){
int i;
- int stateno = pParser->fts5yytos->stateno;
-
- if( stateno>=fts5YY_MIN_REDUCE ) return stateno;
+
+ if( stateno>fts5YY_MAX_SHIFT ) return stateno;
assert( stateno <= fts5YY_SHIFT_COUNT );
+#if defined(fts5YYCOVERAGE)
+ fts5yycoverage[stateno][iLookAhead] = 1;
+#endif
do{
i = fts5yy_shift_ofst[stateno];
+ assert( i>=0 );
+ assert( i<=fts5YY_ACTTAB_COUNT );
+ assert( i+fts5YYNFTS5TOKEN<=(int)fts5YY_NLOOKAHEAD );
assert( iLookAhead!=fts5YYNOCODE );
+ assert( iLookAhead < fts5YYNFTS5TOKEN );
i += iLookAhead;
- if( i<0 || i>=fts5YY_ACTTAB_COUNT || fts5yy_lookahead[i]!=iLookAhead ){
+ assert( i<(int)fts5YY_NLOOKAHEAD );
+ if( fts5yy_lookahead[i]!=iLookAhead ){
#ifdef fts5YYFALLBACK
fts5YYCODETYPE iFallback; /* Fallback token */
- if( iLookAhead<sizeof(fts5yyFallback)/sizeof(fts5yyFallback[0])
- && (iFallback = fts5yyFallback[iLookAhead])!=0 ){
+ assert( iLookAhead<sizeof(fts5yyFallback)/sizeof(fts5yyFallback[0]) );
+ iFallback = fts5yyFallback[iLookAhead];
+ if( iFallback!=0 ){
#ifndef NDEBUG
if( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE, "%sFALLBACK %s => %s\n",
@@ -183759,15 +211666,8 @@ static unsigned int fts5yy_find_shift_action(
#ifdef fts5YYWILDCARD
{
int j = i - iLookAhead + fts5YYWILDCARD;
- if(
-#if fts5YY_SHIFT_MIN+fts5YYWILDCARD<0
- j>=0 &&
-#endif
-#if fts5YY_SHIFT_MAX+fts5YYWILDCARD>=fts5YY_ACTTAB_COUNT
- j<fts5YY_ACTTAB_COUNT &&
-#endif
- fts5yy_lookahead[j]==fts5YYWILDCARD && iLookAhead>0
- ){
+ assert( j<(int)(sizeof(fts5yy_lookahead)/sizeof(fts5yy_lookahead[0])) );
+ if( fts5yy_lookahead[j]==fts5YYWILDCARD && iLookAhead>0 ){
#ifndef NDEBUG
if( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE, "%sWILDCARD %s => %s\n",
@@ -183781,6 +211681,7 @@ static unsigned int fts5yy_find_shift_action(
#endif /* fts5YYWILDCARD */
return fts5yy_default[stateno];
}else{
+ assert( i>=0 && i<sizeof(fts5yy_action)/sizeof(fts5yy_action[0]) );
return fts5yy_action[i];
}
}while(1);
@@ -183790,8 +211691,8 @@ static unsigned int fts5yy_find_shift_action(
** Find the appropriate action for a parser given the non-terminal
** look-ahead token iLookAhead.
*/
-static int fts5yy_find_reduce_action(
- int stateno, /* Current state number */
+static fts5YYACTIONTYPE fts5yy_find_reduce_action(
+ fts5YYACTIONTYPE stateno, /* Current state number */
fts5YYCODETYPE iLookAhead /* The look-ahead token */
){
int i;
@@ -183803,7 +211704,6 @@ static int fts5yy_find_reduce_action(
assert( stateno<=fts5YY_REDUCE_COUNT );
#endif
i = fts5yy_reduce_ofst[stateno];
- assert( i!=fts5YY_REDUCE_USE_DFLT );
assert( iLookAhead!=fts5YYNOCODE );
i += iLookAhead;
#ifdef fts5YYERRORSYMBOL
@@ -183821,8 +211721,8 @@ static int fts5yy_find_reduce_action(
** The following routine is called if the stack overflows.
*/
static void fts5yyStackOverflow(fts5yyParser *fts5yypParser){
- sqlite3Fts5ParserARG_FETCH;
- fts5yypParser->fts5yytos--;
+ tdsqlite3Fts5ParserARG_FETCH
+ tdsqlite3Fts5ParserCTX_FETCH
#ifndef NDEBUG
if( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sStack Overflow!\n",fts5yyTracePrompt);
@@ -183833,29 +211733,31 @@ static void fts5yyStackOverflow(fts5yyParser *fts5yypParser){
** stack every overflows */
/******** Begin %stack_overflow code ******************************************/
- sqlite3Fts5ParseError(pParse, "fts5: parser stack overflow");
+ tdsqlite3Fts5ParseError(pParse, "fts5: parser stack overflow");
/******** End %stack_overflow code ********************************************/
- sqlite3Fts5ParserARG_STORE; /* Suppress warning about unused %extra_argument var */
+ tdsqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument var */
+ tdsqlite3Fts5ParserCTX_STORE
}
/*
** Print tracing information for a SHIFT action
*/
#ifndef NDEBUG
-static void fts5yyTraceShift(fts5yyParser *fts5yypParser, int fts5yyNewState){
+static void fts5yyTraceShift(fts5yyParser *fts5yypParser, int fts5yyNewState, const char *zTag){
if( fts5yyTraceFILE ){
if( fts5yyNewState<fts5YYNSTATE ){
- fprintf(fts5yyTraceFILE,"%sShift '%s', go to state %d\n",
- fts5yyTracePrompt,fts5yyTokenName[fts5yypParser->fts5yytos->major],
+ fprintf(fts5yyTraceFILE,"%s%s '%s', go to state %d\n",
+ fts5yyTracePrompt, zTag, fts5yyTokenName[fts5yypParser->fts5yytos->major],
fts5yyNewState);
}else{
- fprintf(fts5yyTraceFILE,"%sShift '%s'\n",
- fts5yyTracePrompt,fts5yyTokenName[fts5yypParser->fts5yytos->major]);
+ fprintf(fts5yyTraceFILE,"%s%s '%s', pending reduce %d\n",
+ fts5yyTracePrompt, zTag, fts5yyTokenName[fts5yypParser->fts5yytos->major],
+ fts5yyNewState - fts5YY_MIN_REDUCE);
}
}
}
#else
-# define fts5yyTraceShift(X,Y)
+# define fts5yyTraceShift(X,Y,Z)
#endif
/*
@@ -183863,9 +211765,9 @@ static void fts5yyTraceShift(fts5yyParser *fts5yypParser, int fts5yyNewState){
*/
static void fts5yy_shift(
fts5yyParser *fts5yypParser, /* The parser to be shifted */
- int fts5yyNewState, /* The new state to shift in */
- int fts5yyMajor, /* The major token to shift in */
- sqlite3Fts5ParserFTS5TOKENTYPE fts5yyMinor /* The minor token to shift in */
+ fts5YYACTIONTYPE fts5yyNewState, /* The new state to shift in */
+ fts5YYCODETYPE fts5yyMajor, /* The major token to shift in */
+ tdsqlite3Fts5ParserFTS5TOKENTYPE fts5yyMinor /* The minor token to shift in */
){
fts5yyStackEntry *fts5yytos;
fts5yypParser->fts5yytos++;
@@ -183876,13 +211778,15 @@ static void fts5yy_shift(
}
#endif
#if fts5YYSTACKDEPTH>0
- if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5YYSTACKDEPTH] ){
+ if( fts5yypParser->fts5yytos>fts5yypParser->fts5yystackEnd ){
+ fts5yypParser->fts5yytos--;
fts5yyStackOverflow(fts5yypParser);
return;
}
#else
if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5yypParser->fts5yystksz] ){
if( fts5yyGrowStack(fts5yypParser) ){
+ fts5yypParser->fts5yytos--;
fts5yyStackOverflow(fts5yypParser);
return;
}
@@ -183892,45 +211796,76 @@ static void fts5yy_shift(
fts5yyNewState += fts5YY_MIN_REDUCE - fts5YY_MIN_SHIFTREDUCE;
}
fts5yytos = fts5yypParser->fts5yytos;
- fts5yytos->stateno = (fts5YYACTIONTYPE)fts5yyNewState;
- fts5yytos->major = (fts5YYCODETYPE)fts5yyMajor;
+ fts5yytos->stateno = fts5yyNewState;
+ fts5yytos->major = fts5yyMajor;
fts5yytos->minor.fts5yy0 = fts5yyMinor;
- fts5yyTraceShift(fts5yypParser, fts5yyNewState);
-}
+ fts5yyTraceShift(fts5yypParser, fts5yyNewState, "Shift");
+}
+
+/* For rule J, fts5yyRuleInfoLhs[J] contains the symbol on the left-hand side
+** of that rule */
+static const fts5YYCODETYPE fts5yyRuleInfoLhs[] = {
+ 16, /* (0) input ::= expr */
+ 20, /* (1) colset ::= MINUS LCP colsetlist RCP */
+ 20, /* (2) colset ::= LCP colsetlist RCP */
+ 20, /* (3) colset ::= STRING */
+ 20, /* (4) colset ::= MINUS STRING */
+ 21, /* (5) colsetlist ::= colsetlist STRING */
+ 21, /* (6) colsetlist ::= STRING */
+ 17, /* (7) expr ::= expr AND expr */
+ 17, /* (8) expr ::= expr OR expr */
+ 17, /* (9) expr ::= expr NOT expr */
+ 17, /* (10) expr ::= colset COLON LP expr RP */
+ 17, /* (11) expr ::= LP expr RP */
+ 17, /* (12) expr ::= exprlist */
+ 19, /* (13) exprlist ::= cnearset */
+ 19, /* (14) exprlist ::= exprlist cnearset */
+ 18, /* (15) cnearset ::= nearset */
+ 18, /* (16) cnearset ::= colset COLON nearset */
+ 22, /* (17) nearset ::= phrase */
+ 22, /* (18) nearset ::= CARET phrase */
+ 22, /* (19) nearset ::= STRING LP nearphrases neardist_opt RP */
+ 23, /* (20) nearphrases ::= phrase */
+ 23, /* (21) nearphrases ::= nearphrases phrase */
+ 25, /* (22) neardist_opt ::= */
+ 25, /* (23) neardist_opt ::= COMMA STRING */
+ 24, /* (24) phrase ::= phrase PLUS STRING star_opt */
+ 24, /* (25) phrase ::= STRING star_opt */
+ 26, /* (26) star_opt ::= STAR */
+ 26, /* (27) star_opt ::= */
+};
-/* The following table contains information about every rule that
-** is used during the reduce.
-*/
-static const struct {
- fts5YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */
- unsigned char nrhs; /* Number of right-hand side symbols in the rule */
-} fts5yyRuleInfo[] = {
- { 16, 1 },
- { 17, 3 },
- { 17, 3 },
- { 17, 3 },
- { 17, 3 },
- { 17, 1 },
- { 19, 1 },
- { 19, 2 },
- { 18, 1 },
- { 18, 3 },
- { 21, 4 },
- { 21, 3 },
- { 21, 1 },
- { 21, 2 },
- { 22, 2 },
- { 22, 1 },
- { 20, 1 },
- { 20, 5 },
- { 23, 1 },
- { 23, 2 },
- { 25, 0 },
- { 25, 2 },
- { 24, 4 },
- { 24, 2 },
- { 26, 1 },
- { 26, 0 },
+/* For rule J, fts5yyRuleInfoNRhs[J] contains the negative of the number
+** of symbols on the right-hand side of that rule. */
+static const signed char fts5yyRuleInfoNRhs[] = {
+ -1, /* (0) input ::= expr */
+ -4, /* (1) colset ::= MINUS LCP colsetlist RCP */
+ -3, /* (2) colset ::= LCP colsetlist RCP */
+ -1, /* (3) colset ::= STRING */
+ -2, /* (4) colset ::= MINUS STRING */
+ -2, /* (5) colsetlist ::= colsetlist STRING */
+ -1, /* (6) colsetlist ::= STRING */
+ -3, /* (7) expr ::= expr AND expr */
+ -3, /* (8) expr ::= expr OR expr */
+ -3, /* (9) expr ::= expr NOT expr */
+ -5, /* (10) expr ::= colset COLON LP expr RP */
+ -3, /* (11) expr ::= LP expr RP */
+ -1, /* (12) expr ::= exprlist */
+ -1, /* (13) exprlist ::= cnearset */
+ -2, /* (14) exprlist ::= exprlist cnearset */
+ -1, /* (15) cnearset ::= nearset */
+ -3, /* (16) cnearset ::= colset COLON nearset */
+ -1, /* (17) nearset ::= phrase */
+ -2, /* (18) nearset ::= CARET phrase */
+ -5, /* (19) nearset ::= STRING LP nearphrases neardist_opt RP */
+ -1, /* (20) nearphrases ::= phrase */
+ -2, /* (21) nearphrases ::= nearphrases phrase */
+ 0, /* (22) neardist_opt ::= */
+ -2, /* (23) neardist_opt ::= COMMA STRING */
+ -4, /* (24) phrase ::= phrase PLUS STRING star_opt */
+ -2, /* (25) phrase ::= STRING star_opt */
+ -1, /* (26) star_opt ::= STAR */
+ 0, /* (27) star_opt ::= */
};
static void fts5yy_accept(fts5yyParser*); /* Forward Declaration */
@@ -183938,29 +211873,49 @@ static void fts5yy_accept(fts5yyParser*); /* Forward Declaration */
/*
** Perform a reduce action and the shift that must immediately
** follow the reduce.
+**
+** The fts5yyLookahead and fts5yyLookaheadToken parameters provide reduce actions
+** access to the lookahead token (if any). The fts5yyLookahead will be fts5YYNOCODE
+** if the lookahead token has already been consumed. As this procedure is
+** only called from one place, optimizing compilers will in-line it, which
+** means that the extra parameters have no performance impact.
*/
-static void fts5yy_reduce(
+static fts5YYACTIONTYPE fts5yy_reduce(
fts5yyParser *fts5yypParser, /* The parser */
- unsigned int fts5yyruleno /* Number of the rule by which to reduce */
+ unsigned int fts5yyruleno, /* Number of the rule by which to reduce */
+ int fts5yyLookahead, /* Lookahead token, or fts5YYNOCODE if none */
+ tdsqlite3Fts5ParserFTS5TOKENTYPE fts5yyLookaheadToken /* Value of the lookahead token */
+ tdsqlite3Fts5ParserCTX_PDECL /* %extra_context */
){
int fts5yygoto; /* The next state */
- int fts5yyact; /* The next action */
+ fts5YYACTIONTYPE fts5yyact; /* The next action */
fts5yyStackEntry *fts5yymsp; /* The top of the parser's stack */
int fts5yysize; /* Amount to pop the stack */
- sqlite3Fts5ParserARG_FETCH;
+ tdsqlite3Fts5ParserARG_FETCH
+ (void)fts5yyLookahead;
+ (void)fts5yyLookaheadToken;
fts5yymsp = fts5yypParser->fts5yytos;
#ifndef NDEBUG
if( fts5yyTraceFILE && fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ){
- fts5yysize = fts5yyRuleInfo[fts5yyruleno].nrhs;
- fprintf(fts5yyTraceFILE, "%sReduce [%s], go to state %d.\n", fts5yyTracePrompt,
- fts5yyRuleName[fts5yyruleno], fts5yymsp[-fts5yysize].stateno);
+ fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno];
+ if( fts5yysize ){
+ fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n",
+ fts5yyTracePrompt,
+ fts5yyruleno, fts5yyRuleName[fts5yyruleno],
+ fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action",
+ fts5yymsp[fts5yysize].stateno);
+ }else{
+ fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s.\n",
+ fts5yyTracePrompt, fts5yyruleno, fts5yyRuleName[fts5yyruleno],
+ fts5yyruleno<fts5YYNRULE_WITH_ACTION ? "" : " without external action");
+ }
}
#endif /* NDEBUG */
/* Check that the stack is large enough to grow by a single entry
** if the RHS of the rule is empty. This ensures that there is room
** enough on the stack to push the LHS value */
- if( fts5yyRuleInfo[fts5yyruleno].nrhs==0 ){
+ if( fts5yyRuleInfoNRhs[fts5yyruleno]==0 ){
#ifdef fts5YYTRACKMAXSTACKDEPTH
if( (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)>fts5yypParser->fts5yyhwm ){
fts5yypParser->fts5yyhwm++;
@@ -183968,15 +211923,21 @@ static void fts5yy_reduce(
}
#endif
#if fts5YYSTACKDEPTH>0
- if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5YYSTACKDEPTH-1] ){
+ if( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystackEnd ){
fts5yyStackOverflow(fts5yypParser);
- return;
+ /* The call to fts5yyStackOverflow() above pops the stack until it is
+ ** empty, causing the main parser loop to exit. So the return value
+ ** is never used and does not matter. */
+ return 0;
}
#else
if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5yypParser->fts5yystksz-1] ){
if( fts5yyGrowStack(fts5yypParser) ){
fts5yyStackOverflow(fts5yypParser);
- return;
+ /* The call to fts5yyStackOverflow() above pops the stack until it is
+ ** empty, causing the main parser loop to exit. So the return value
+ ** is never used and does not matter. */
+ return 0;
}
fts5yymsp = fts5yypParser->fts5yytos;
}
@@ -183995,154 +211956,167 @@ static void fts5yy_reduce(
/********** Begin reduce actions **********************************************/
fts5YYMINORTYPE fts5yylhsminor;
case 0: /* input ::= expr */
-{ sqlite3Fts5ParseFinished(pParse, fts5yymsp[0].minor.fts5yy24); }
+{ tdsqlite3Fts5ParseFinished(pParse, fts5yymsp[0].minor.fts5yy24); }
+ break;
+ case 1: /* colset ::= MINUS LCP colsetlist RCP */
+{
+ fts5yymsp[-3].minor.fts5yy11 = tdsqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11);
+}
+ break;
+ case 2: /* colset ::= LCP colsetlist RCP */
+{ fts5yymsp[-2].minor.fts5yy11 = fts5yymsp[-1].minor.fts5yy11; }
+ break;
+ case 3: /* colset ::= STRING */
+{
+ fts5yylhsminor.fts5yy11 = tdsqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0);
+}
+ fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11;
+ break;
+ case 4: /* colset ::= MINUS STRING */
+{
+ fts5yymsp[-1].minor.fts5yy11 = tdsqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0);
+ fts5yymsp[-1].minor.fts5yy11 = tdsqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11);
+}
+ break;
+ case 5: /* colsetlist ::= colsetlist STRING */
+{
+ fts5yylhsminor.fts5yy11 = tdsqlite3Fts5ParseColset(pParse, fts5yymsp[-1].minor.fts5yy11, &fts5yymsp[0].minor.fts5yy0); }
+ fts5yymsp[-1].minor.fts5yy11 = fts5yylhsminor.fts5yy11;
+ break;
+ case 6: /* colsetlist ::= STRING */
+{
+ fts5yylhsminor.fts5yy11 = tdsqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0);
+}
+ fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11;
break;
- case 1: /* expr ::= expr AND expr */
+ case 7: /* expr ::= expr AND expr */
{
- fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0);
+ fts5yylhsminor.fts5yy24 = tdsqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0);
}
fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break;
- case 2: /* expr ::= expr OR expr */
+ case 8: /* expr ::= expr OR expr */
{
- fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0);
+ fts5yylhsminor.fts5yy24 = tdsqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0);
}
fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break;
- case 3: /* expr ::= expr NOT expr */
+ case 9: /* expr ::= expr NOT expr */
{
- fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0);
+ fts5yylhsminor.fts5yy24 = tdsqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0);
}
fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break;
- case 4: /* expr ::= LP expr RP */
+ case 10: /* expr ::= colset COLON LP expr RP */
+{
+ tdsqlite3Fts5ParseSetColset(pParse, fts5yymsp[-1].minor.fts5yy24, fts5yymsp[-4].minor.fts5yy11);
+ fts5yylhsminor.fts5yy24 = fts5yymsp[-1].minor.fts5yy24;
+}
+ fts5yymsp[-4].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
+ break;
+ case 11: /* expr ::= LP expr RP */
{fts5yymsp[-2].minor.fts5yy24 = fts5yymsp[-1].minor.fts5yy24;}
break;
- case 5: /* expr ::= exprlist */
- case 6: /* exprlist ::= cnearset */ fts5yytestcase(fts5yyruleno==6);
+ case 12: /* expr ::= exprlist */
+ case 13: /* exprlist ::= cnearset */ fts5yytestcase(fts5yyruleno==13);
{fts5yylhsminor.fts5yy24 = fts5yymsp[0].minor.fts5yy24;}
fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break;
- case 7: /* exprlist ::= exprlist cnearset */
+ case 14: /* exprlist ::= exprlist cnearset */
{
- fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseImplicitAnd(pParse, fts5yymsp[-1].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24);
+ fts5yylhsminor.fts5yy24 = tdsqlite3Fts5ParseImplicitAnd(pParse, fts5yymsp[-1].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24);
}
fts5yymsp[-1].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break;
- case 8: /* cnearset ::= nearset */
+ case 15: /* cnearset ::= nearset */
{
- fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46);
+ fts5yylhsminor.fts5yy24 = tdsqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46);
}
fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break;
- case 9: /* cnearset ::= colset COLON nearset */
+ case 16: /* cnearset ::= colset COLON nearset */
{
- sqlite3Fts5ParseSetColset(pParse, fts5yymsp[0].minor.fts5yy46, fts5yymsp[-2].minor.fts5yy11);
- fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46);
+ fts5yylhsminor.fts5yy24 = tdsqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46);
+ tdsqlite3Fts5ParseSetColset(pParse, fts5yylhsminor.fts5yy24, fts5yymsp[-2].minor.fts5yy11);
}
fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24;
break;
- case 10: /* colset ::= MINUS LCP colsetlist RCP */
-{
- fts5yymsp[-3].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11);
-}
- break;
- case 11: /* colset ::= LCP colsetlist RCP */
-{ fts5yymsp[-2].minor.fts5yy11 = fts5yymsp[-1].minor.fts5yy11; }
- break;
- case 12: /* colset ::= STRING */
-{
- fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0);
-}
- fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11;
- break;
- case 13: /* colset ::= MINUS STRING */
-{
- fts5yymsp[-1].minor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0);
- fts5yymsp[-1].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11);
-}
- break;
- case 14: /* colsetlist ::= colsetlist STRING */
-{
- fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, fts5yymsp[-1].minor.fts5yy11, &fts5yymsp[0].minor.fts5yy0); }
- fts5yymsp[-1].minor.fts5yy11 = fts5yylhsminor.fts5yy11;
+ case 17: /* nearset ::= phrase */
+{ fts5yylhsminor.fts5yy46 = tdsqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); }
+ fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46;
break;
- case 15: /* colsetlist ::= STRING */
+ case 18: /* nearset ::= CARET phrase */
{
- fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0);
+ tdsqlite3Fts5ParseSetCaret(fts5yymsp[0].minor.fts5yy53);
+ fts5yymsp[-1].minor.fts5yy46 = tdsqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53);
}
- fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11;
- break;
- case 16: /* nearset ::= phrase */
-{ fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); }
- fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46;
break;
- case 17: /* nearset ::= STRING LP nearphrases neardist_opt RP */
+ case 19: /* nearset ::= STRING LP nearphrases neardist_opt RP */
{
- sqlite3Fts5ParseNear(pParse, &fts5yymsp[-4].minor.fts5yy0);
- sqlite3Fts5ParseSetDistance(pParse, fts5yymsp[-2].minor.fts5yy46, &fts5yymsp[-1].minor.fts5yy0);
+ tdsqlite3Fts5ParseNear(pParse, &fts5yymsp[-4].minor.fts5yy0);
+ tdsqlite3Fts5ParseSetDistance(pParse, fts5yymsp[-2].minor.fts5yy46, &fts5yymsp[-1].minor.fts5yy0);
fts5yylhsminor.fts5yy46 = fts5yymsp[-2].minor.fts5yy46;
}
fts5yymsp[-4].minor.fts5yy46 = fts5yylhsminor.fts5yy46;
break;
- case 18: /* nearphrases ::= phrase */
+ case 20: /* nearphrases ::= phrase */
{
- fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53);
+ fts5yylhsminor.fts5yy46 = tdsqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53);
}
fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46;
break;
- case 19: /* nearphrases ::= nearphrases phrase */
+ case 21: /* nearphrases ::= nearphrases phrase */
{
- fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, fts5yymsp[-1].minor.fts5yy46, fts5yymsp[0].minor.fts5yy53);
+ fts5yylhsminor.fts5yy46 = tdsqlite3Fts5ParseNearset(pParse, fts5yymsp[-1].minor.fts5yy46, fts5yymsp[0].minor.fts5yy53);
}
fts5yymsp[-1].minor.fts5yy46 = fts5yylhsminor.fts5yy46;
break;
- case 20: /* neardist_opt ::= */
+ case 22: /* neardist_opt ::= */
{ fts5yymsp[1].minor.fts5yy0.p = 0; fts5yymsp[1].minor.fts5yy0.n = 0; }
break;
- case 21: /* neardist_opt ::= COMMA STRING */
+ case 23: /* neardist_opt ::= COMMA STRING */
{ fts5yymsp[-1].minor.fts5yy0 = fts5yymsp[0].minor.fts5yy0; }
break;
- case 22: /* phrase ::= phrase PLUS STRING star_opt */
+ case 24: /* phrase ::= phrase PLUS STRING star_opt */
{
- fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, fts5yymsp[-3].minor.fts5yy53, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4);
+ fts5yylhsminor.fts5yy53 = tdsqlite3Fts5ParseTerm(pParse, fts5yymsp[-3].minor.fts5yy53, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4);
}
fts5yymsp[-3].minor.fts5yy53 = fts5yylhsminor.fts5yy53;
break;
- case 23: /* phrase ::= STRING star_opt */
+ case 25: /* phrase ::= STRING star_opt */
{
- fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, 0, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4);
+ fts5yylhsminor.fts5yy53 = tdsqlite3Fts5ParseTerm(pParse, 0, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4);
}
fts5yymsp[-1].minor.fts5yy53 = fts5yylhsminor.fts5yy53;
break;
- case 24: /* star_opt ::= STAR */
+ case 26: /* star_opt ::= STAR */
{ fts5yymsp[0].minor.fts5yy4 = 1; }
break;
- case 25: /* star_opt ::= */
+ case 27: /* star_opt ::= */
{ fts5yymsp[1].minor.fts5yy4 = 0; }
break;
default:
break;
/********** End reduce actions ************************************************/
};
- assert( fts5yyruleno<sizeof(fts5yyRuleInfo)/sizeof(fts5yyRuleInfo[0]) );
- fts5yygoto = fts5yyRuleInfo[fts5yyruleno].lhs;
- fts5yysize = fts5yyRuleInfo[fts5yyruleno].nrhs;
- fts5yyact = fts5yy_find_reduce_action(fts5yymsp[-fts5yysize].stateno,(fts5YYCODETYPE)fts5yygoto);
- if( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){
- if( fts5yyact>fts5YY_MAX_SHIFT ){
- fts5yyact += fts5YY_MIN_REDUCE - fts5YY_MIN_SHIFTREDUCE;
- }
- fts5yymsp -= fts5yysize-1;
- fts5yypParser->fts5yytos = fts5yymsp;
- fts5yymsp->stateno = (fts5YYACTIONTYPE)fts5yyact;
- fts5yymsp->major = (fts5YYCODETYPE)fts5yygoto;
- fts5yyTraceShift(fts5yypParser, fts5yyact);
- }else{
- assert( fts5yyact == fts5YY_ACCEPT_ACTION );
- fts5yypParser->fts5yytos -= fts5yysize;
- fts5yy_accept(fts5yypParser);
- }
+ assert( fts5yyruleno<sizeof(fts5yyRuleInfoLhs)/sizeof(fts5yyRuleInfoLhs[0]) );
+ fts5yygoto = fts5yyRuleInfoLhs[fts5yyruleno];
+ fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno];
+ fts5yyact = fts5yy_find_reduce_action(fts5yymsp[fts5yysize].stateno,(fts5YYCODETYPE)fts5yygoto);
+
+ /* There are no SHIFTREDUCE actions on nonterminals because the table
+ ** generator has simplified them to pure REDUCE actions. */
+ assert( !(fts5yyact>fts5YY_MAX_SHIFT && fts5yyact<=fts5YY_MAX_SHIFTREDUCE) );
+
+ /* It is not possible for a REDUCE to be followed by an error */
+ assert( fts5yyact!=fts5YY_ERROR_ACTION );
+
+ fts5yymsp += fts5yysize+1;
+ fts5yypParser->fts5yytos = fts5yymsp;
+ fts5yymsp->stateno = (fts5YYACTIONTYPE)fts5yyact;
+ fts5yymsp->major = (fts5YYCODETYPE)fts5yygoto;
+ fts5yyTraceShift(fts5yypParser, fts5yyact, "... then shift");
+ return fts5yyact;
}
/*
@@ -184152,7 +212126,8 @@ static void fts5yy_reduce(
static void fts5yy_parse_failed(
fts5yyParser *fts5yypParser /* The parser */
){
- sqlite3Fts5ParserARG_FETCH;
+ tdsqlite3Fts5ParserARG_FETCH
+ tdsqlite3Fts5ParserCTX_FETCH
#ifndef NDEBUG
if( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sFail!\n",fts5yyTracePrompt);
@@ -184163,7 +212138,8 @@ static void fts5yy_parse_failed(
** parser fails */
/************ Begin %parse_failure code ***************************************/
/************ End %parse_failure code *****************************************/
- sqlite3Fts5ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3Fts5ParserCTX_STORE
}
#endif /* fts5YYNOERRORRECOVERY */
@@ -184173,18 +212149,20 @@ static void fts5yy_parse_failed(
static void fts5yy_syntax_error(
fts5yyParser *fts5yypParser, /* The parser */
int fts5yymajor, /* The major type of the error token */
- sqlite3Fts5ParserFTS5TOKENTYPE fts5yyminor /* The minor type of the error token */
+ tdsqlite3Fts5ParserFTS5TOKENTYPE fts5yyminor /* The minor type of the error token */
){
- sqlite3Fts5ParserARG_FETCH;
+ tdsqlite3Fts5ParserARG_FETCH
+ tdsqlite3Fts5ParserCTX_FETCH
#define FTS5TOKEN fts5yyminor
/************ Begin %syntax_error code ****************************************/
UNUSED_PARAM(fts5yymajor); /* Silence a compiler warning */
- sqlite3Fts5ParseError(
+ tdsqlite3Fts5ParseError(
pParse, "fts5: syntax error near \"%.*s\"",FTS5TOKEN.n,FTS5TOKEN.p
);
/************ End %syntax_error code ******************************************/
- sqlite3Fts5ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3Fts5ParserCTX_STORE
}
/*
@@ -184193,7 +212171,8 @@ static void fts5yy_syntax_error(
static void fts5yy_accept(
fts5yyParser *fts5yypParser /* The parser */
){
- sqlite3Fts5ParserARG_FETCH;
+ tdsqlite3Fts5ParserARG_FETCH
+ tdsqlite3Fts5ParserCTX_FETCH
#ifndef NDEBUG
if( fts5yyTraceFILE ){
fprintf(fts5yyTraceFILE,"%sAccept!\n",fts5yyTracePrompt);
@@ -184207,12 +212186,13 @@ static void fts5yy_accept(
** parser accepts */
/*********** Begin %parse_accept code *****************************************/
/*********** End %parse_accept code *******************************************/
- sqlite3Fts5ParserARG_STORE; /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3Fts5ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
+ tdsqlite3Fts5ParserCTX_STORE
}
/* The main parser program.
** The first argument is a pointer to a structure obtained from
-** "sqlite3Fts5ParserAlloc" which describes the current state of the parser.
+** "tdsqlite3Fts5ParserAlloc" 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
@@ -184229,45 +212209,58 @@ static void fts5yy_accept(
** Outputs:
** None.
*/
-static void sqlite3Fts5Parser(
+static void tdsqlite3Fts5Parser(
void *fts5yyp, /* The parser */
int fts5yymajor, /* The major token code number */
- sqlite3Fts5ParserFTS5TOKENTYPE fts5yyminor /* The value for the token */
- sqlite3Fts5ParserARG_PDECL /* Optional %extra_argument parameter */
+ tdsqlite3Fts5ParserFTS5TOKENTYPE fts5yyminor /* The value for the token */
+ tdsqlite3Fts5ParserARG_PDECL /* Optional %extra_argument parameter */
){
fts5YYMINORTYPE fts5yyminorunion;
- unsigned int fts5yyact; /* The parser action. */
+ fts5YYACTIONTYPE fts5yyact; /* The parser action. */
#if !defined(fts5YYERRORSYMBOL) && !defined(fts5YYNOERRORRECOVERY)
int fts5yyendofinput; /* True if we are at the end of input */
#endif
#ifdef fts5YYERRORSYMBOL
int fts5yyerrorhit = 0; /* True if fts5yymajor has invoked an error */
#endif
- fts5yyParser *fts5yypParser; /* The parser */
+ fts5yyParser *fts5yypParser = (fts5yyParser*)fts5yyp; /* The parser */
+ tdsqlite3Fts5ParserCTX_FETCH
+ tdsqlite3Fts5ParserARG_STORE
- fts5yypParser = (fts5yyParser*)fts5yyp;
assert( fts5yypParser->fts5yytos!=0 );
#if !defined(fts5YYERRORSYMBOL) && !defined(fts5YYNOERRORRECOVERY)
fts5yyendofinput = (fts5yymajor==0);
#endif
- sqlite3Fts5ParserARG_STORE;
+ fts5yyact = fts5yypParser->fts5yytos->stateno;
#ifndef NDEBUG
if( fts5yyTraceFILE ){
- fprintf(fts5yyTraceFILE,"%sInput '%s'\n",fts5yyTracePrompt,fts5yyTokenName[fts5yymajor]);
+ if( fts5yyact < fts5YY_MIN_REDUCE ){
+ fprintf(fts5yyTraceFILE,"%sInput '%s' in state %d\n",
+ fts5yyTracePrompt,fts5yyTokenName[fts5yymajor],fts5yyact);
+ }else{
+ fprintf(fts5yyTraceFILE,"%sInput '%s' with pending reduce %d\n",
+ fts5yyTracePrompt,fts5yyTokenName[fts5yymajor],fts5yyact-fts5YY_MIN_REDUCE);
+ }
}
#endif
do{
- fts5yyact = fts5yy_find_shift_action(fts5yypParser,(fts5YYCODETYPE)fts5yymajor);
- if( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){
- fts5yy_shift(fts5yypParser,fts5yyact,fts5yymajor,fts5yyminor);
+ assert( fts5yyact==fts5yypParser->fts5yytos->stateno );
+ fts5yyact = fts5yy_find_shift_action((fts5YYCODETYPE)fts5yymajor,fts5yyact);
+ if( fts5yyact >= fts5YY_MIN_REDUCE ){
+ fts5yyact = fts5yy_reduce(fts5yypParser,fts5yyact-fts5YY_MIN_REDUCE,fts5yymajor,
+ fts5yyminor tdsqlite3Fts5ParserCTX_PARAM);
+ }else if( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){
+ fts5yy_shift(fts5yypParser,fts5yyact,(fts5YYCODETYPE)fts5yymajor,fts5yyminor);
#ifndef fts5YYNOERRORRECOVERY
fts5yypParser->fts5yyerrcnt--;
#endif
- fts5yymajor = fts5YYNOCODE;
- }else if( fts5yyact <= fts5YY_MAX_REDUCE ){
- fts5yy_reduce(fts5yypParser,fts5yyact-fts5YY_MIN_REDUCE);
+ break;
+ }else if( fts5yyact==fts5YY_ACCEPT_ACTION ){
+ fts5yypParser->fts5yytos--;
+ fts5yy_accept(fts5yypParser);
+ return;
}else{
assert( fts5yyact == fts5YY_ERROR_ACTION );
fts5yyminorunion.fts5yy0 = fts5yyminor;
@@ -184314,10 +212307,9 @@ static void sqlite3Fts5Parser(
fts5yymajor = fts5YYNOCODE;
}else{
while( fts5yypParser->fts5yytos >= fts5yypParser->fts5yystack
- && fts5yymx != fts5YYERRORSYMBOL
&& (fts5yyact = fts5yy_find_reduce_action(
fts5yypParser->fts5yytos->stateno,
- fts5YYERRORSYMBOL)) >= fts5YY_MIN_REDUCE
+ fts5YYERRORSYMBOL)) > fts5YY_MAX_SHIFTREDUCE
){
fts5yy_pop_parser_stack(fts5yypParser);
}
@@ -184334,6 +212326,8 @@ static void sqlite3Fts5Parser(
}
fts5yypParser->fts5yyerrcnt = 3;
fts5yyerrorhit = 1;
+ if( fts5yymajor==fts5YYNOCODE ) break;
+ fts5yyact = fts5yypParser->fts5yytos->stateno;
#elif defined(fts5YYNOERRORRECOVERY)
/* If the fts5YYNOERRORRECOVERY macro is defined, then do not attempt to
** do any kind of error recovery. Instead, simply invoke the syntax
@@ -184344,8 +212338,7 @@ static void sqlite3Fts5Parser(
*/
fts5yy_syntax_error(fts5yypParser,fts5yymajor, fts5yyminor);
fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion);
- fts5yymajor = fts5YYNOCODE;
-
+ break;
#else /* fts5YYERRORSYMBOL is not defined */
/* This is what we do if the grammar does not define ERROR:
**
@@ -184367,10 +212360,10 @@ static void sqlite3Fts5Parser(
fts5yypParser->fts5yyerrcnt = -1;
#endif
}
- fts5yymajor = fts5YYNOCODE;
+ break;
#endif
}
- }while( fts5yymajor!=fts5YYNOCODE && fts5yypParser->fts5yytos>fts5yypParser->fts5yystack );
+ }while( fts5yypParser->fts5yytos>fts5yypParser->fts5yystack );
#ifndef NDEBUG
if( fts5yyTraceFILE ){
fts5yyStackEntry *i;
@@ -184387,6 +212380,20 @@ static void sqlite3Fts5Parser(
}
/*
+** Return the fallback token corresponding to canonical token iToken, or
+** 0 if iToken has no fallback.
+*/
+static int tdsqlite3Fts5ParserFallback(int iToken){
+#ifdef fts5YYFALLBACK
+ assert( iToken<(int)(sizeof(fts5yyFallback)/sizeof(fts5yyFallback[0])) );
+ return fts5yyFallback[iToken];
+#else
+ (void)iToken;
+ return 0;
+#endif
+}
+
+/*
** 2014 May 31
**
** The author disclaims copyright to this source code. In place of
@@ -184524,9 +212531,9 @@ static void fts5HighlightAppend(
HighlightContext *p,
const char *z, int n
){
- if( *pRc==SQLITE_OK ){
+ if( *pRc==SQLITE_OK && z ){
if( n<0 ) n = (int)strlen(z);
- p->zOut = sqlite3_mprintf("%z%.*s", p->zOut, n, z);
+ p->zOut = tdsqlite3_mprintf("%z%.*s", p->zOut, n, z);
if( p->zOut==0 ) *pRc = SQLITE_NOMEM;
}
}
@@ -184591,9 +212598,9 @@ static int fts5HighlightCb(
static void fts5HighlightFunction(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
- sqlite3_context *pCtx, /* Context for returning result/error */
+ tdsqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
- sqlite3_value **apVal /* Array of trailing arguments */
+ tdsqlite3_value **apVal /* Array of trailing arguments */
){
HighlightContext ctx;
int rc;
@@ -184601,14 +212608,14 @@ static void fts5HighlightFunction(
if( nVal!=3 ){
const char *zErr = "wrong number of arguments to function highlight()";
- sqlite3_result_error(pCtx, zErr, -1);
+ tdsqlite3_result_error(pCtx, zErr, -1);
return;
}
- iCol = sqlite3_value_int(apVal[0]);
+ iCol = tdsqlite3_value_int(apVal[0]);
memset(&ctx, 0, sizeof(HighlightContext));
- ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]);
- ctx.zClose = (const char*)sqlite3_value_text(apVal[2]);
+ ctx.zOpen = (const char*)tdsqlite3_value_text(apVal[1]);
+ ctx.zClose = (const char*)tdsqlite3_value_text(apVal[2]);
rc = pApi->xColumnText(pFts, iCol, &ctx.zIn, &ctx.nIn);
if( ctx.zIn ){
@@ -184622,12 +212629,12 @@ static void fts5HighlightFunction(
fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
if( rc==SQLITE_OK ){
- sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT);
}
- sqlite3_free(ctx.zOut);
+ tdsqlite3_free(ctx.zOut);
}
if( rc!=SQLITE_OK ){
- sqlite3_result_error_code(pCtx, rc);
+ tdsqlite3_result_error_code(pCtx, rc);
}
}
/*
@@ -184656,7 +212663,7 @@ static int fts5SentenceFinderAdd(Fts5SFinder *p, int iAdd){
int nNew = p->nFirstAlloc ? p->nFirstAlloc*2 : 64;
int *aNew;
- aNew = (int*)sqlite3_realloc(p->aFirst, nNew*sizeof(int));
+ aNew = (int*)tdsqlite3_realloc64(p->aFirst, nNew*sizeof(int));
if( aNew==0 ) return SQLITE_NOMEM;
p->aFirst = aNew;
p->nFirstAlloc = nNew;
@@ -184723,11 +212730,12 @@ static int fts5SnippetScore(
int nInst;
int nScore = 0;
int iLast = 0;
+ tdsqlite3_int64 iEnd = (tdsqlite3_int64)iPos + nToken;
rc = pApi->xInstCount(pFts, &nInst);
for(i=0; i<nInst && rc==SQLITE_OK; i++){
rc = pApi->xInst(pFts, i, &ip, &ic, &iOff);
- if( rc==SQLITE_OK && ic==iCol && iOff>=iPos && iOff<(iPos+nToken) ){
+ if( rc==SQLITE_OK && ic==iCol && iOff>=iPos && iOff<iEnd ){
nScore += (aSeen[ip] ? 1 : 1000);
aSeen[ip] = 1;
if( iFirst<0 ) iFirst = iOff;
@@ -184737,24 +212745,34 @@ static int fts5SnippetScore(
*pnScore = nScore;
if( piPos ){
- int iAdj = iFirst - (nToken - (iLast-iFirst)) / 2;
+ tdsqlite3_int64 iAdj = iFirst - (nToken - (iLast-iFirst)) / 2;
if( (iAdj+nToken)>nDocsize ) iAdj = nDocsize - nToken;
if( iAdj<0 ) iAdj = 0;
- *piPos = iAdj;
+ *piPos = (int)iAdj;
}
return rc;
}
/*
+** Return the value in pVal interpreted as utf-8 text. Except, if pVal
+** contains a NULL value, return a pointer to a static string zero
+** bytes in length instead of a NULL pointer.
+*/
+static const char *fts5ValueToText(tdsqlite3_value *pVal){
+ const char *zRet = (const char*)tdsqlite3_value_text(pVal);
+ return zRet ? zRet : "";
+}
+
+/*
** Implementation of snippet() function.
*/
static void fts5SnippetFunction(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
- sqlite3_context *pCtx, /* Context for returning result/error */
+ tdsqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
- sqlite3_value **apVal /* Array of trailing arguments */
+ tdsqlite3_value **apVal /* Array of trailing arguments */
){
HighlightContext ctx;
int rc = SQLITE_OK; /* Return code */
@@ -184774,21 +212792,21 @@ static void fts5SnippetFunction(
if( nVal!=5 ){
const char *zErr = "wrong number of arguments to function snippet()";
- sqlite3_result_error(pCtx, zErr, -1);
+ tdsqlite3_result_error(pCtx, zErr, -1);
return;
}
nCol = pApi->xColumnCount(pFts);
memset(&ctx, 0, sizeof(HighlightContext));
- iCol = sqlite3_value_int(apVal[0]);
- ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]);
- ctx.zClose = (const char*)sqlite3_value_text(apVal[2]);
- zEllips = (const char*)sqlite3_value_text(apVal[3]);
- nToken = sqlite3_value_int(apVal[4]);
+ iCol = tdsqlite3_value_int(apVal[0]);
+ ctx.zOpen = fts5ValueToText(apVal[1]);
+ ctx.zClose = fts5ValueToText(apVal[2]);
+ zEllips = fts5ValueToText(apVal[3]);
+ nToken = tdsqlite3_value_int(apVal[4]);
iBestCol = (iCol>=0 ? iCol : 0);
nPhrase = pApi->xPhraseCount(pFts);
- aSeen = sqlite3_malloc(nPhrase);
+ aSeen = tdsqlite3_malloc(nPhrase);
if( aSeen==0 ){
rc = SQLITE_NOMEM;
}
@@ -184820,7 +212838,9 @@ static void fts5SnippetFunction(
int jj;
rc = pApi->xInst(pFts, ii, &ip, &ic, &io);
- if( ic!=i || rc!=SQLITE_OK ) continue;
+ if( ic!=i ) continue;
+ if( io>nDocsize ) rc = FTS5_CORRUPT;
+ if( rc!=SQLITE_OK ) continue;
memset(aSeen, 0, nPhrase);
rc = fts5SnippetScore(pApi, pFts, nDocsize, aSeen, i,
io, nToken, &nScore, &iAdj
@@ -184890,13 +212910,13 @@ static void fts5SnippetFunction(
}
}
if( rc==SQLITE_OK ){
- sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT);
}else{
- sqlite3_result_error_code(pCtx, rc);
+ tdsqlite3_result_error_code(pCtx, rc);
}
- sqlite3_free(ctx.zOut);
- sqlite3_free(aSeen);
- sqlite3_free(sFinder.aFirst);
+ tdsqlite3_free(ctx.zOut);
+ tdsqlite3_free(aSeen);
+ tdsqlite3_free(sFinder.aFirst);
}
/************************************************************************/
@@ -184920,9 +212940,9 @@ struct Fts5Bm25Data {
static int fts5CountCb(
const Fts5ExtensionApi *pApi,
Fts5Context *pFts,
- void *pUserData /* Pointer to sqlite3_int64 variable */
+ void *pUserData /* Pointer to tdsqlite3_int64 variable */
){
- sqlite3_int64 *pn = (sqlite3_int64*)pUserData;
+ tdsqlite3_int64 *pn = (tdsqlite3_int64*)pUserData;
UNUSED_PARAM2(pApi, pFts);
(*pn)++;
return SQLITE_OK;
@@ -184944,19 +212964,19 @@ static int fts5Bm25GetData(
p = pApi->xGetAuxdata(pFts, 0);
if( p==0 ){
int nPhrase; /* Number of phrases in query */
- sqlite3_int64 nRow = 0; /* Number of rows in table */
- sqlite3_int64 nToken = 0; /* Number of tokens in table */
- int nByte; /* Bytes of space to allocate */
+ tdsqlite3_int64 nRow = 0; /* Number of rows in table */
+ tdsqlite3_int64 nToken = 0; /* Number of tokens in table */
+ tdsqlite3_int64 nByte; /* Bytes of space to allocate */
int i;
/* Allocate the Fts5Bm25Data object */
nPhrase = pApi->xPhraseCount(pFts);
nByte = sizeof(Fts5Bm25Data) + nPhrase*2*sizeof(double);
- p = (Fts5Bm25Data*)sqlite3_malloc(nByte);
+ p = (Fts5Bm25Data*)tdsqlite3_malloc64(nByte);
if( p==0 ){
rc = SQLITE_NOMEM;
}else{
- memset(p, 0, nByte);
+ memset(p, 0, (size_t)nByte);
p->nPhrase = nPhrase;
p->aIDF = (double*)&p[1];
p->aFreq = &p->aIDF[nPhrase];
@@ -184964,12 +212984,13 @@ static int fts5Bm25GetData(
/* Calculate the average document length for this FTS5 table */
if( rc==SQLITE_OK ) rc = pApi->xRowCount(pFts, &nRow);
+ assert( rc!=SQLITE_OK || nRow>0 );
if( rc==SQLITE_OK ) rc = pApi->xColumnTotalSize(pFts, -1, &nToken);
if( rc==SQLITE_OK ) p->avgdl = (double)nToken / (double)nRow;
/* Calculate an IDF for each phrase in the query */
for(i=0; rc==SQLITE_OK && i<nPhrase; i++){
- sqlite3_int64 nHit = 0;
+ tdsqlite3_int64 nHit = 0;
rc = pApi->xQueryPhrase(pFts, i, (void*)&nHit, fts5CountCb);
if( rc==SQLITE_OK ){
/* Calculate the IDF (Inverse Document Frequency) for phrase i.
@@ -184992,9 +213013,9 @@ static int fts5Bm25GetData(
}
if( rc!=SQLITE_OK ){
- sqlite3_free(p);
+ tdsqlite3_free(p);
}else{
- rc = pApi->xSetAuxdata(pFts, p, sqlite3_free);
+ rc = pApi->xSetAuxdata(pFts, p, tdsqlite3_free);
}
if( rc!=SQLITE_OK ) p = 0;
}
@@ -185008,9 +213029,9 @@ static int fts5Bm25GetData(
static void fts5Bm25Function(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
- sqlite3_context *pCtx, /* Context for returning result/error */
+ tdsqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
- sqlite3_value **apVal /* Array of trailing arguments */
+ tdsqlite3_value **apVal /* Array of trailing arguments */
){
const double k1 = 1.2; /* Constant "k1" from BM25 formula */
const double b = 0.75; /* Constant "b" from BM25 formula */
@@ -185034,7 +213055,7 @@ static void fts5Bm25Function(
int ip; int ic; int io;
rc = pApi->xInst(pFts, i, &ip, &ic, &io);
if( rc==SQLITE_OK ){
- double w = (nVal > ic) ? sqlite3_value_double(apVal[ic]) : 1.0;
+ double w = (nVal > ic) ? tdsqlite3_value_double(apVal[ic]) : 1.0;
aFreq[ip] += w;
}
}
@@ -185057,13 +213078,13 @@ static void fts5Bm25Function(
/* If no error has occurred, return the calculated score. Otherwise,
** throw an SQL exception. */
if( rc==SQLITE_OK ){
- sqlite3_result_double(pCtx, -1.0 * score);
+ tdsqlite3_result_double(pCtx, -1.0 * score);
}else{
- sqlite3_result_error_code(pCtx, rc);
+ tdsqlite3_result_error_code(pCtx, rc);
}
}
-static int sqlite3Fts5AuxInit(fts5_api *pApi){
+static int tdsqlite3Fts5AuxInit(fts5_api *pApi){
struct Builtin {
const char *zFunc; /* Function name (nul-terminated) */
void *pUserData; /* User-data pointer */
@@ -185089,8 +213110,6 @@ static int sqlite3Fts5AuxInit(fts5_api *pApi){
return rc;
}
-
-
/*
** 2014 May 31
**
@@ -185108,19 +213127,19 @@ static int sqlite3Fts5AuxInit(fts5_api *pApi){
/* #include "fts5Int.h" */
-static int sqlite3Fts5BufferSize(int *pRc, Fts5Buffer *pBuf, u32 nByte){
+static int tdsqlite3Fts5BufferSize(int *pRc, Fts5Buffer *pBuf, u32 nByte){
if( (u32)pBuf->nSpace<nByte ){
- u32 nNew = pBuf->nSpace ? pBuf->nSpace : 64;
+ u64 nNew = pBuf->nSpace ? pBuf->nSpace : 64;
u8 *pNew;
while( nNew<nByte ){
nNew = nNew * 2;
}
- pNew = sqlite3_realloc(pBuf->p, nNew);
+ pNew = tdsqlite3_realloc64(pBuf->p, nNew);
if( pNew==0 ){
*pRc = SQLITE_NOMEM;
return 1;
}else{
- pBuf->nSpace = nNew;
+ pBuf->nSpace = (int)nNew;
pBuf->p = pNew;
}
}
@@ -185132,20 +213151,20 @@ static int sqlite3Fts5BufferSize(int *pRc, Fts5Buffer *pBuf, u32 nByte){
** Encode value iVal as an SQLite varint and append it to the buffer object
** pBuf. If an OOM error occurs, set the error code in p.
*/
-static void sqlite3Fts5BufferAppendVarint(int *pRc, Fts5Buffer *pBuf, i64 iVal){
+static void tdsqlite3Fts5BufferAppendVarint(int *pRc, Fts5Buffer *pBuf, i64 iVal){
if( fts5BufferGrow(pRc, pBuf, 9) ) return;
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iVal);
+ pBuf->n += tdsqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iVal);
}
-static void sqlite3Fts5Put32(u8 *aBuf, int iVal){
+static void tdsqlite3Fts5Put32(u8 *aBuf, int iVal){
aBuf[0] = (iVal>>24) & 0x00FF;
aBuf[1] = (iVal>>16) & 0x00FF;
aBuf[2] = (iVal>> 8) & 0x00FF;
aBuf[3] = (iVal>> 0) & 0x00FF;
}
-static int sqlite3Fts5Get32(const u8 *aBuf){
- return (aBuf[0] << 24) + (aBuf[1] << 16) + (aBuf[2] << 8) + aBuf[3];
+static int tdsqlite3Fts5Get32(const u8 *aBuf){
+ return (int)((((u32)aBuf[0])<<24) + (aBuf[1]<<16) + (aBuf[2]<<8) + aBuf[3]);
}
/*
@@ -185153,16 +213172,18 @@ static int sqlite3Fts5Get32(const u8 *aBuf){
** the error code in p. If an error has already occurred when this function
** is called, it is a no-op.
*/
-static void sqlite3Fts5BufferAppendBlob(
+static void tdsqlite3Fts5BufferAppendBlob(
int *pRc,
Fts5Buffer *pBuf,
u32 nData,
const u8 *pData
){
assert_nc( *pRc || nData>=0 );
- if( fts5BufferGrow(pRc, pBuf, nData) ) return;
- memcpy(&pBuf->p[pBuf->n], pData, nData);
- pBuf->n += nData;
+ if( nData ){
+ if( fts5BufferGrow(pRc, pBuf, nData) ) return;
+ memcpy(&pBuf->p[pBuf->n], pData, nData);
+ pBuf->n += nData;
+ }
}
/*
@@ -185170,13 +213191,13 @@ static void sqlite3Fts5BufferAppendBlob(
** ensures that the byte following the buffer data is set to 0x00, even
** though this byte is not included in the pBuf->n count.
*/
-static void sqlite3Fts5BufferAppendString(
+static void tdsqlite3Fts5BufferAppendString(
int *pRc,
Fts5Buffer *pBuf,
const char *zStr
){
int nStr = (int)strlen(zStr);
- sqlite3Fts5BufferAppendBlob(pRc, pBuf, nStr+1, (const u8*)zStr);
+ tdsqlite3Fts5BufferAppendBlob(pRc, pBuf, nStr+1, (const u8*)zStr);
pBuf->n--;
}
@@ -185184,11 +213205,11 @@ static void sqlite3Fts5BufferAppendString(
** Argument zFmt is a printf() style format string. This function performs
** the printf() style processing, then appends the results to buffer pBuf.
**
-** Like sqlite3Fts5BufferAppendString(), this function ensures that the byte
+** Like tdsqlite3Fts5BufferAppendString(), this function ensures that the byte
** following the buffer data is set to 0x00, even though this byte is not
** included in the pBuf->n count.
*/
-static void sqlite3Fts5BufferAppendPrintf(
+static void tdsqlite3Fts5BufferAppendPrintf(
int *pRc,
Fts5Buffer *pBuf,
char *zFmt, ...
@@ -185197,24 +213218,24 @@ static void sqlite3Fts5BufferAppendPrintf(
char *zTmp;
va_list ap;
va_start(ap, zFmt);
- zTmp = sqlite3_vmprintf(zFmt, ap);
+ zTmp = tdsqlite3_vmprintf(zFmt, ap);
va_end(ap);
if( zTmp==0 ){
*pRc = SQLITE_NOMEM;
}else{
- sqlite3Fts5BufferAppendString(pRc, pBuf, zTmp);
- sqlite3_free(zTmp);
+ tdsqlite3Fts5BufferAppendString(pRc, pBuf, zTmp);
+ tdsqlite3_free(zTmp);
}
}
}
-static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...){
+static char *tdsqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...){
char *zRet = 0;
if( *pRc==SQLITE_OK ){
va_list ap;
va_start(ap, zFmt);
- zRet = sqlite3_vmprintf(zFmt, ap);
+ zRet = tdsqlite3_vmprintf(zFmt, ap);
va_end(ap);
if( zRet==0 ){
*pRc = SQLITE_NOMEM;
@@ -185227,8 +213248,8 @@ static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...){
/*
** Free any buffer allocated by pBuf. Zero the structure before returning.
*/
-static void sqlite3Fts5BufferFree(Fts5Buffer *pBuf){
- sqlite3_free(pBuf->p);
+static void tdsqlite3Fts5BufferFree(Fts5Buffer *pBuf){
+ tdsqlite3_free(pBuf->p);
memset(pBuf, 0, sizeof(Fts5Buffer));
}
@@ -185236,7 +213257,7 @@ static void sqlite3Fts5BufferFree(Fts5Buffer *pBuf){
** Zero the contents of the buffer object. But do not free the associated
** memory allocation.
*/
-static void sqlite3Fts5BufferZero(Fts5Buffer *pBuf){
+static void tdsqlite3Fts5BufferZero(Fts5Buffer *pBuf){
pBuf->n = 0;
}
@@ -185245,17 +213266,17 @@ static void sqlite3Fts5BufferZero(Fts5Buffer *pBuf){
** the error code in p. If an error has already occurred when this function
** is called, it is a no-op.
*/
-static void sqlite3Fts5BufferSet(
+static void tdsqlite3Fts5BufferSet(
int *pRc,
Fts5Buffer *pBuf,
int nData,
const u8 *pData
){
pBuf->n = 0;
- sqlite3Fts5BufferAppendBlob(pRc, pBuf, nData, pData);
+ tdsqlite3Fts5BufferAppendBlob(pRc, pBuf, nData, pData);
}
-static int sqlite3Fts5PoslistNext64(
+static int tdsqlite3Fts5PoslistNext64(
const u8 *a, int n, /* Buffer containing poslist */
int *pi, /* IN/OUT: Offset within a[] */
i64 *piOff /* IN/OUT: Current offset */
@@ -185269,12 +213290,21 @@ static int sqlite3Fts5PoslistNext64(
i64 iOff = *piOff;
int iVal;
fts5FastGetVarint32(a, i, iVal);
- if( iVal==1 ){
+ if( iVal<=1 ){
+ if( iVal==0 ){
+ *pi = i;
+ return 0;
+ }
fts5FastGetVarint32(a, i, iVal);
iOff = ((i64)iVal) << 32;
fts5FastGetVarint32(a, i, iVal);
+ if( iVal<2 ){
+ /* This is a corrupt record. So stop parsing it here. */
+ *piOff = -1;
+ return 1;
+ }
}
- *piOff = iOff + (iVal-2);
+ *piOff = iOff + ((iVal-2) & 0x7FFFFFFF);
*pi = i;
return 0;
}
@@ -185285,21 +213315,21 @@ static int sqlite3Fts5PoslistNext64(
** Advance the iterator object passed as the only argument. Return true
** if the iterator reaches EOF, or false otherwise.
*/
-static int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader *pIter){
- if( sqlite3Fts5PoslistNext64(pIter->a, pIter->n, &pIter->i, &pIter->iPos) ){
+static int tdsqlite3Fts5PoslistReaderNext(Fts5PoslistReader *pIter){
+ if( tdsqlite3Fts5PoslistNext64(pIter->a, pIter->n, &pIter->i, &pIter->iPos) ){
pIter->bEof = 1;
}
return pIter->bEof;
}
-static int sqlite3Fts5PoslistReaderInit(
+static int tdsqlite3Fts5PoslistReaderInit(
const u8 *a, int n, /* Poslist buffer to iterate through */
Fts5PoslistReader *pIter /* Iterator object to initialize */
){
memset(pIter, 0, sizeof(*pIter));
pIter->a = a;
pIter->n = n;
- sqlite3Fts5PoslistReaderNext(pIter);
+ tdsqlite3Fts5PoslistReaderNext(pIter);
return pIter->bEof;
}
@@ -185309,7 +213339,7 @@ static int sqlite3Fts5PoslistReaderInit(
** The previous position written to this list is *piPrev. *piPrev is set
** to iPos before returning.
*/
-static void sqlite3Fts5PoslistSafeAppend(
+static void tdsqlite3Fts5PoslistSafeAppend(
Fts5Buffer *pBuf,
i64 *piPrev,
i64 iPos
@@ -185317,32 +213347,32 @@ static void sqlite3Fts5PoslistSafeAppend(
static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32;
if( (iPos & colmask) != (*piPrev & colmask) ){
pBuf->p[pBuf->n++] = 1;
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32));
+ pBuf->n += tdsqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32));
*piPrev = (iPos & colmask);
}
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2);
+ pBuf->n += tdsqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2);
*piPrev = iPos;
}
-static int sqlite3Fts5PoslistWriterAppend(
+static int tdsqlite3Fts5PoslistWriterAppend(
Fts5Buffer *pBuf,
Fts5PoslistWriter *pWriter,
i64 iPos
){
int rc = 0; /* Initialized only to suppress erroneous warning from Clang */
if( fts5BufferGrow(&rc, pBuf, 5+5+5) ) return rc;
- sqlite3Fts5PoslistSafeAppend(pBuf, &pWriter->iPrev, iPos);
+ tdsqlite3Fts5PoslistSafeAppend(pBuf, &pWriter->iPrev, iPos);
return SQLITE_OK;
}
-static void *sqlite3Fts5MallocZero(int *pRc, int nByte){
+static void *tdsqlite3Fts5MallocZero(int *pRc, tdsqlite3_int64 nByte){
void *pRet = 0;
if( *pRc==SQLITE_OK ){
- pRet = sqlite3_malloc(nByte);
- if( pRet==0 && nByte>0 ){
- *pRc = SQLITE_NOMEM;
+ pRet = tdsqlite3_malloc64(nByte);
+ if( pRet==0 ){
+ if( nByte>0 ) *pRc = SQLITE_NOMEM;
}else{
- memset(pRet, 0, nByte);
+ memset(pRet, 0, (size_t)nByte);
}
}
return pRet;
@@ -185354,15 +213384,15 @@ static void *sqlite3Fts5MallocZero(int *pRc, int nByte){
** the length of the string is determined using strlen().
**
** It is the responsibility of the caller to eventually free the returned
-** buffer using sqlite3_free(). If an OOM error occurs, NULL is returned.
+** buffer using tdsqlite3_free(). If an OOM error occurs, NULL is returned.
*/
-static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){
+static char *tdsqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){
char *zRet = 0;
if( *pRc==SQLITE_OK ){
if( nIn<0 ){
nIn = (int)strlen(pIn);
}
- zRet = (char*)sqlite3_malloc(nIn+1);
+ zRet = (char*)tdsqlite3_malloc(nIn+1);
if( zRet ){
memcpy(zRet, pIn, nIn);
zRet[nIn] = '\0';
@@ -185384,7 +213414,7 @@ static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){
** * The underscore character "_" (0x5F).
** * The unicode "subsitute" character (0x1A).
*/
-static int sqlite3Fts5IsBareword(char t){
+static int tdsqlite3Fts5IsBareword(char t){
u8 aBareword[128] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 .. 0x0F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, /* 0x10 .. 0x1F */
@@ -185414,13 +213444,13 @@ struct Fts5Termset {
Fts5TermsetEntry *apHash[512];
};
-static int sqlite3Fts5TermsetNew(Fts5Termset **pp){
+static int tdsqlite3Fts5TermsetNew(Fts5Termset **pp){
int rc = SQLITE_OK;
- *pp = sqlite3Fts5MallocZero(&rc, sizeof(Fts5Termset));
+ *pp = tdsqlite3Fts5MallocZero(&rc, sizeof(Fts5Termset));
return rc;
}
-static int sqlite3Fts5TermsetAdd(
+static int tdsqlite3Fts5TermsetAdd(
Fts5Termset *p,
int iIdx,
const char *pTerm, int nTerm,
@@ -185454,7 +213484,7 @@ static int sqlite3Fts5TermsetAdd(
}
if( pEntry==0 ){
- pEntry = sqlite3Fts5MallocZero(&rc, sizeof(Fts5TermsetEntry) + nTerm);
+ pEntry = tdsqlite3Fts5MallocZero(&rc, sizeof(Fts5TermsetEntry) + nTerm);
if( pEntry ){
pEntry->pTerm = (char*)&pEntry[1];
pEntry->nTerm = nTerm;
@@ -185469,7 +213499,7 @@ static int sqlite3Fts5TermsetAdd(
return rc;
}
-static void sqlite3Fts5TermsetFree(Fts5Termset *p){
+static void tdsqlite3Fts5TermsetFree(Fts5Termset *p){
if( p ){
u32 i;
for(i=0; i<ArraySize(p->apHash); i++){
@@ -185477,10 +213507,10 @@ static void sqlite3Fts5TermsetFree(Fts5Termset *p){
while( pEntry ){
Fts5TermsetEntry *pDel = pEntry;
pEntry = pEntry->pNext;
- sqlite3_free(pDel);
+ tdsqlite3_free(pDel);
}
}
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
}
@@ -185509,7 +213539,7 @@ static void sqlite3Fts5TermsetFree(Fts5Termset *p){
#define FTS5_DEFAULT_HASHSIZE (1024*1024)
/* Maximum allowed page size */
-#define FTS5_MAX_PAGE_SIZE (128*1024)
+#define FTS5_MAX_PAGE_SIZE (64*1024)
static int fts5_iswhitespace(char x){
return (x==' ');
@@ -185539,7 +213569,7 @@ static const char *fts5ConfigSkipWhitespace(const char *pIn){
*/
static const char *fts5ConfigSkipBareword(const char *pIn){
const char *p = pIn;
- while ( sqlite3Fts5IsBareword(*p) ) p++;
+ while ( tdsqlite3Fts5IsBareword(*p) ) p++;
if( p==pIn ) p = 0;
return p;
}
@@ -185554,7 +213584,7 @@ static const char *fts5ConfigSkipLiteral(const char *pIn){
const char *p = pIn;
switch( *p ){
case 'n': case 'N':
- if( sqlite3_strnicmp("null", p, 4)==0 ){
+ if( tdsqlite3_strnicmp("null", p, 4)==0 ){
p = &p[4];
}else{
p = 0;
@@ -185636,7 +213666,7 @@ static int fts5Dequote(char *z){
assert( q=='[' || q=='\'' || q=='"' || q=='`' );
if( q=='[' ) q = ']';
- while( ALWAYS(z[iIn]) ){
+ while( z[iIn] ){
if( z[iIn]==q ){
if( z[iIn+1]!=q ){
/* Character iIn was the close quote. */
@@ -185671,7 +213701,7 @@ static int fts5Dequote(char *z){
** [pqr] becomes pqr
** `mno` becomes mno
*/
-static void sqlite3Fts5Dequote(char *z){
+static void tdsqlite3Fts5Dequote(char *z){
char quote; /* Quote character (if any ) */
assert( 0==fts5_iswhitespace(z[0]) );
@@ -185698,7 +213728,7 @@ static int fts5ConfigSetEnum(
int iVal = -1;
for(i=0; aEnum[i].zName; i++){
- if( sqlite3_strnicmp(aEnum[i].zName, zEnum, nEnum)==0 ){
+ if( tdsqlite3_strnicmp(aEnum[i].zName, zEnum, nEnum)==0 ){
if( iVal>=0 ) return SQLITE_ERROR;
iVal = aEnum[i].eVal;
}
@@ -185715,7 +213745,7 @@ static int fts5ConfigSetEnum(
** If successful, object pConfig is updated and SQLITE_OK returned. If
** an error occurs, an SQLite error code is returned and an error message
** may be left in *pzErr. It is the responsibility of the caller to
-** eventually free any such error message using sqlite3_free().
+** eventually free any such error message using tdsqlite3_free().
*/
static int fts5ConfigParseSpecial(
Fts5Global *pGlobal,
@@ -185726,12 +213756,12 @@ static int fts5ConfigParseSpecial(
){
int rc = SQLITE_OK;
int nCmd = (int)strlen(zCmd);
- if( sqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){
+ if( tdsqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){
const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES;
const char *p;
int bFirst = 1;
if( pConfig->aPrefix==0 ){
- pConfig->aPrefix = sqlite3Fts5MallocZero(&rc, nByte);
+ pConfig->aPrefix = tdsqlite3Fts5MallocZero(&rc, nByte);
if( rc ) return rc;
}
@@ -185747,13 +213777,13 @@ static int fts5ConfigParseSpecial(
break;
}
if( p[0]<'0' || p[0]>'9' ){
- *pzErr = sqlite3_mprintf("malformed prefix=... directive");
+ *pzErr = tdsqlite3_mprintf("malformed prefix=... directive");
rc = SQLITE_ERROR;
break;
}
if( pConfig->nPrefix==FTS5_MAX_PREFIX_INDEXES ){
- *pzErr = sqlite3_mprintf(
+ *pzErr = tdsqlite3_mprintf(
"too many prefix indexes (max %d)", FTS5_MAX_PREFIX_INDEXES
);
rc = SQLITE_ERROR;
@@ -185766,7 +213796,7 @@ static int fts5ConfigParseSpecial(
}
if( nPre<=0 || nPre>=1000 ){
- *pzErr = sqlite3_mprintf("prefix length out of range (max 999)");
+ *pzErr = tdsqlite3_mprintf("prefix length out of range (max 999)");
rc = SQLITE_ERROR;
break;
}
@@ -185779,16 +213809,16 @@ static int fts5ConfigParseSpecial(
return rc;
}
- if( sqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){
+ if( tdsqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){
const char *p = (const char*)zArg;
- int nArg = (int)strlen(zArg) + 1;
- char **azArg = sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg);
- char *pDel = sqlite3Fts5MallocZero(&rc, nArg * 2);
+ tdsqlite3_int64 nArg = strlen(zArg) + 1;
+ char **azArg = tdsqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg);
+ char *pDel = tdsqlite3Fts5MallocZero(&rc, nArg * 2);
char *pSpace = pDel;
if( azArg && pSpace ){
if( pConfig->pTok ){
- *pzErr = sqlite3_mprintf("multiple tokenize=... directives");
+ *pzErr = tdsqlite3_mprintf("multiple tokenize=... directives");
rc = SQLITE_ERROR;
}else{
for(nArg=0; p && *p; nArg++){
@@ -185801,36 +213831,36 @@ static int fts5ConfigParseSpecial(
if( p ){
memcpy(pSpace, p2, p-p2);
azArg[nArg] = pSpace;
- sqlite3Fts5Dequote(pSpace);
+ tdsqlite3Fts5Dequote(pSpace);
pSpace += (p - p2) + 1;
p = fts5ConfigSkipWhitespace(p);
}
}
if( p==0 ){
- *pzErr = sqlite3_mprintf("parse error in tokenize directive");
+ *pzErr = tdsqlite3_mprintf("parse error in tokenize directive");
rc = SQLITE_ERROR;
}else{
- rc = sqlite3Fts5GetTokenizer(pGlobal,
- (const char**)azArg, nArg, &pConfig->pTok, &pConfig->pTokApi,
+ rc = tdsqlite3Fts5GetTokenizer(pGlobal,
+ (const char**)azArg, (int)nArg, &pConfig->pTok, &pConfig->pTokApi,
pzErr
);
}
}
}
- sqlite3_free(azArg);
- sqlite3_free(pDel);
+ tdsqlite3_free(azArg);
+ tdsqlite3_free(pDel);
return rc;
}
- if( sqlite3_strnicmp("content", zCmd, nCmd)==0 ){
+ if( tdsqlite3_strnicmp("content", zCmd, nCmd)==0 ){
if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){
- *pzErr = sqlite3_mprintf("multiple content=... directives");
+ *pzErr = tdsqlite3_mprintf("multiple content=... directives");
rc = SQLITE_ERROR;
}else{
if( zArg[0] ){
pConfig->eContent = FTS5_CONTENT_EXTERNAL;
- pConfig->zContent = sqlite3Fts5Mprintf(&rc, "%Q.%Q", pConfig->zDb,zArg);
+ pConfig->zContent = tdsqlite3Fts5Mprintf(&rc, "%Q.%Q", pConfig->zDb,zArg);
}else{
pConfig->eContent = FTS5_CONTENT_NONE;
}
@@ -185838,19 +213868,19 @@ static int fts5ConfigParseSpecial(
return rc;
}
- if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){
+ if( tdsqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){
if( pConfig->zContentRowid ){
- *pzErr = sqlite3_mprintf("multiple content_rowid=... directives");
+ *pzErr = tdsqlite3_mprintf("multiple content_rowid=... directives");
rc = SQLITE_ERROR;
}else{
- pConfig->zContentRowid = sqlite3Fts5Strndup(&rc, zArg, -1);
+ pConfig->zContentRowid = tdsqlite3Fts5Strndup(&rc, zArg, -1);
}
return rc;
}
- if( sqlite3_strnicmp("columnsize", zCmd, nCmd)==0 ){
+ if( tdsqlite3_strnicmp("columnsize", zCmd, nCmd)==0 ){
if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){
- *pzErr = sqlite3_mprintf("malformed columnsize=... directive");
+ *pzErr = tdsqlite3_mprintf("malformed columnsize=... directive");
rc = SQLITE_ERROR;
}else{
pConfig->bColumnsize = (zArg[0]=='1');
@@ -185858,7 +213888,7 @@ static int fts5ConfigParseSpecial(
return rc;
}
- if( sqlite3_strnicmp("detail", zCmd, nCmd)==0 ){
+ if( tdsqlite3_strnicmp("detail", zCmd, nCmd)==0 ){
const Fts5Enum aDetail[] = {
{ "none", FTS5_DETAIL_NONE },
{ "full", FTS5_DETAIL_FULL },
@@ -185867,12 +213897,12 @@ static int fts5ConfigParseSpecial(
};
if( (rc = fts5ConfigSetEnum(aDetail, zArg, &pConfig->eDetail)) ){
- *pzErr = sqlite3_mprintf("malformed detail=... directive");
+ *pzErr = tdsqlite3_mprintf("malformed detail=... directive");
}
return rc;
}
- *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd);
+ *pzErr = tdsqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd);
return SQLITE_ERROR;
}
@@ -185883,7 +213913,7 @@ static int fts5ConfigParseSpecial(
*/
static int fts5ConfigDefaultTokenizer(Fts5Global *pGlobal, Fts5Config *pConfig){
assert( pConfig->pTok==0 && pConfig->pTokApi==0 );
- return sqlite3Fts5GetTokenizer(
+ return tdsqlite3Fts5GetTokenizer(
pGlobal, 0, 0, &pConfig->pTok, &pConfig->pTokApi, 0
);
}
@@ -185911,8 +213941,8 @@ static const char *fts5ConfigGobbleWord(
){
const char *zRet = 0;
- int nIn = (int)strlen(zIn);
- char *zOut = sqlite3_malloc(nIn+1);
+ tdsqlite3_int64 nIn = strlen(zIn);
+ char *zOut = tdsqlite3_malloc64(nIn+1);
assert( *pRc==SQLITE_OK );
*pbQuoted = 0;
@@ -185921,7 +213951,7 @@ static const char *fts5ConfigGobbleWord(
if( zOut==0 ){
*pRc = SQLITE_NOMEM;
}else{
- memcpy(zOut, zIn, nIn+1);
+ memcpy(zOut, zIn, (size_t)(nIn+1));
if( fts5_isopenquote(zOut[0]) ){
int ii = fts5Dequote(zOut);
zRet = &zIn[ii];
@@ -185935,7 +213965,7 @@ static const char *fts5ConfigGobbleWord(
}
if( zRet==0 ){
- sqlite3_free(zOut);
+ tdsqlite3_free(zOut);
}else{
*pzOut = zOut;
}
@@ -185950,16 +213980,16 @@ static int fts5ConfigParseColumn(
char **pzErr
){
int rc = SQLITE_OK;
- if( 0==sqlite3_stricmp(zCol, FTS5_RANK_NAME)
- || 0==sqlite3_stricmp(zCol, FTS5_ROWID_NAME)
+ if( 0==tdsqlite3_stricmp(zCol, FTS5_RANK_NAME)
+ || 0==tdsqlite3_stricmp(zCol, FTS5_ROWID_NAME)
){
- *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zCol);
+ *pzErr = tdsqlite3_mprintf("reserved fts5 column name: %s", zCol);
rc = SQLITE_ERROR;
}else if( zArg ){
- if( 0==sqlite3_stricmp(zArg, "unindexed") ){
+ if( 0==tdsqlite3_stricmp(zArg, "unindexed") ){
p->abUnindexed[p->nCol] = 1;
}else{
- *pzErr = sqlite3_mprintf("unrecognized column option: %s", zArg);
+ *pzErr = tdsqlite3_mprintf("unrecognized column option: %s", zArg);
rc = SQLITE_ERROR;
}
}
@@ -185976,13 +214006,13 @@ static int fts5ConfigMakeExprlist(Fts5Config *p){
int rc = SQLITE_OK;
Fts5Buffer buf = {0, 0, 0};
- sqlite3Fts5BufferAppendPrintf(&rc, &buf, "T.%Q", p->zContentRowid);
+ tdsqlite3Fts5BufferAppendPrintf(&rc, &buf, "T.%Q", p->zContentRowid);
if( p->eContent!=FTS5_CONTENT_NONE ){
for(i=0; i<p->nCol; i++){
if( p->eContent==FTS5_CONTENT_EXTERNAL ){
- sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.%Q", p->azCol[i]);
+ tdsqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.%Q", p->azCol[i]);
}else{
- sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.c%d", i);
+ tdsqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.c%d", i);
}
}
}
@@ -186002,11 +214032,11 @@ static int fts5ConfigMakeExprlist(Fts5Config *p){
** new Fts5Config object. If an error occurs, an SQLite error code is
** returned, *ppOut is set to NULL and an error message may be left in
** *pzErr. It is the responsibility of the caller to eventually free any
-** such error message using sqlite3_free().
+** such error message using tdsqlite3_free().
*/
-static int sqlite3Fts5ConfigParse(
+static int tdsqlite3Fts5ConfigParse(
Fts5Global *pGlobal,
- sqlite3 *db,
+ tdsqlite3 *db,
int nArg, /* Number of arguments */
const char **azArg, /* Array of nArg CREATE VIRTUAL TABLE args */
Fts5Config **ppOut, /* OUT: Results of parse */
@@ -186015,26 +214045,26 @@ static int sqlite3Fts5ConfigParse(
int rc = SQLITE_OK; /* Return code */
Fts5Config *pRet; /* New object to return */
int i;
- int nByte;
+ tdsqlite3_int64 nByte;
- *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config));
+ *ppOut = pRet = (Fts5Config*)tdsqlite3_malloc(sizeof(Fts5Config));
if( pRet==0 ) return SQLITE_NOMEM;
memset(pRet, 0, sizeof(Fts5Config));
pRet->db = db;
pRet->iCookie = -1;
nByte = nArg * (sizeof(char*) + sizeof(u8));
- pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, nByte);
+ pRet->azCol = (char**)tdsqlite3Fts5MallocZero(&rc, nByte);
pRet->abUnindexed = (u8*)&pRet->azCol[nArg];
- pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1);
- pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1);
+ pRet->zDb = tdsqlite3Fts5Strndup(&rc, azArg[1], -1);
+ pRet->zName = tdsqlite3Fts5Strndup(&rc, azArg[2], -1);
pRet->bColumnsize = 1;
pRet->eDetail = FTS5_DETAIL_FULL;
#ifdef SQLITE_DEBUG
pRet->bPrefixIndex = 1;
#endif
- if( rc==SQLITE_OK && sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){
- *pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName);
+ if( rc==SQLITE_OK && tdsqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){
+ *pzErr = tdsqlite3_mprintf("reserved fts5 table name: %s", pRet->zName);
rc = SQLITE_ERROR;
}
@@ -186062,7 +214092,7 @@ static int sqlite3Fts5ConfigParse(
if( rc==SQLITE_OK ){
if( z==0 ){
- *pzErr = sqlite3_mprintf("parse error in \"%s\"", zOrig);
+ *pzErr = tdsqlite3_mprintf("parse error in \"%s\"", zOrig);
rc = SQLITE_ERROR;
}else{
if( bOption ){
@@ -186074,8 +214104,8 @@ static int sqlite3Fts5ConfigParse(
}
}
- sqlite3_free(zOne);
- sqlite3_free(zTwo);
+ tdsqlite3_free(zOne);
+ tdsqlite3_free(zTwo);
}
/* If a tokenizer= option was successfully parsed, the tokenizer has
@@ -186098,14 +214128,14 @@ static int sqlite3Fts5ConfigParse(
}
if( zTail ){
- pRet->zContent = sqlite3Fts5Mprintf(
+ pRet->zContent = tdsqlite3Fts5Mprintf(
&rc, "%Q.'%q_%s'", pRet->zDb, pRet->zName, zTail
);
}
}
if( rc==SQLITE_OK && pRet->zContentRowid==0 ){
- pRet->zContentRowid = sqlite3Fts5Strndup(&rc, "rowid", -1);
+ pRet->zContentRowid = tdsqlite3Fts5Strndup(&rc, "rowid", -1);
}
/* Formulate the zContentExprlist text */
@@ -186114,7 +214144,7 @@ static int sqlite3Fts5ConfigParse(
}
if( rc!=SQLITE_OK ){
- sqlite3Fts5ConfigFree(pRet);
+ tdsqlite3Fts5ConfigFree(pRet);
*ppOut = 0;
}
return rc;
@@ -186123,53 +214153,53 @@ static int sqlite3Fts5ConfigParse(
/*
** Free the configuration object passed as the only argument.
*/
-static void sqlite3Fts5ConfigFree(Fts5Config *pConfig){
+static void tdsqlite3Fts5ConfigFree(Fts5Config *pConfig){
if( pConfig ){
int i;
if( pConfig->pTok ){
pConfig->pTokApi->xDelete(pConfig->pTok);
}
- sqlite3_free(pConfig->zDb);
- sqlite3_free(pConfig->zName);
+ tdsqlite3_free(pConfig->zDb);
+ tdsqlite3_free(pConfig->zName);
for(i=0; i<pConfig->nCol; i++){
- sqlite3_free(pConfig->azCol[i]);
+ tdsqlite3_free(pConfig->azCol[i]);
}
- sqlite3_free(pConfig->azCol);
- sqlite3_free(pConfig->aPrefix);
- sqlite3_free(pConfig->zRank);
- sqlite3_free(pConfig->zRankArgs);
- sqlite3_free(pConfig->zContent);
- sqlite3_free(pConfig->zContentRowid);
- sqlite3_free(pConfig->zContentExprlist);
- sqlite3_free(pConfig);
+ tdsqlite3_free(pConfig->azCol);
+ tdsqlite3_free(pConfig->aPrefix);
+ tdsqlite3_free(pConfig->zRank);
+ tdsqlite3_free(pConfig->zRankArgs);
+ tdsqlite3_free(pConfig->zContent);
+ tdsqlite3_free(pConfig->zContentRowid);
+ tdsqlite3_free(pConfig->zContentExprlist);
+ tdsqlite3_free(pConfig);
}
}
/*
-** Call sqlite3_declare_vtab() based on the contents of the configuration
+** Call tdsqlite3_declare_vtab() based on the contents of the configuration
** object passed as the only argument. Return SQLITE_OK if successful, or
** an SQLite error code if an error occurs.
*/
-static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
+static int tdsqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
int i;
int rc = SQLITE_OK;
char *zSql;
- zSql = sqlite3Fts5Mprintf(&rc, "CREATE TABLE x(");
+ zSql = tdsqlite3Fts5Mprintf(&rc, "CREATE TABLE x(");
for(i=0; zSql && i<pConfig->nCol; i++){
const char *zSep = (i==0?"":", ");
- zSql = sqlite3Fts5Mprintf(&rc, "%z%s%Q", zSql, zSep, pConfig->azCol[i]);
+ zSql = tdsqlite3Fts5Mprintf(&rc, "%z%s%Q", zSql, zSep, pConfig->azCol[i]);
}
- zSql = sqlite3Fts5Mprintf(&rc, "%z, %Q HIDDEN, %s HIDDEN)",
+ zSql = tdsqlite3Fts5Mprintf(&rc, "%z, %Q HIDDEN, %s HIDDEN)",
zSql, pConfig->zName, FTS5_RANK_NAME
);
assert( zSql || rc==SQLITE_NOMEM );
if( zSql ){
- rc = sqlite3_declare_vtab(pConfig->db, zSql);
- sqlite3_free(zSql);
+ rc = tdsqlite3_declare_vtab(pConfig->db, zSql);
+ tdsqlite3_free(zSql);
}
-
+
return rc;
}
@@ -186179,7 +214209,7 @@ static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
** The callback is invoked once for each token in the input text. The
** arguments passed to it are, in order:
**
-** void *pCtx // Copy of 4th argument to sqlite3Fts5Tokenize()
+** void *pCtx // Copy of 4th argument to tdsqlite3Fts5Tokenize()
** const char *pToken // Pointer to buffer containing token
** int nToken // Size of token in bytes
** int iStart // Byte offset of start of token within input text
@@ -186196,7 +214226,7 @@ static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
** because the callback returned another non-zero value, it is assumed
** to be an SQLite error code and returned to the caller.
*/
-static int sqlite3Fts5Tokenize(
+static int tdsqlite3Fts5Tokenize(
Fts5Config *pConfig, /* FTS5 Configuration object */
int flags, /* FTS5_TOKENIZE_* flags */
const char *pText, int nText, /* Text to tokenize */
@@ -186242,7 +214272,7 @@ static const char *fts5ConfigSkipArgs(const char *pIn){
** + Zero or more SQL literals in a comma separated list
** + Close parenthesis - ")"
*/
-static int sqlite3Fts5ConfigParseRank(
+static int tdsqlite3Fts5ConfigParseRank(
const char *zIn, /* Input string */
char **pzRank, /* OUT: Rank function name */
char **pzRankArgs /* OUT: Rank function arguments */
@@ -186264,7 +214294,7 @@ static int sqlite3Fts5ConfigParseRank(
p = fts5ConfigSkipBareword(p);
if( p ){
- zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank);
+ zRank = tdsqlite3Fts5MallocZero(&rc, 1 + p - pRank);
if( zRank ) memcpy(zRank, pRank, p-pRank);
}else{
rc = SQLITE_ERROR;
@@ -186284,7 +214314,7 @@ static int sqlite3Fts5ConfigParseRank(
if( p==0 ){
rc = SQLITE_ERROR;
}else{
- zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs);
+ zRankArgs = tdsqlite3Fts5MallocZero(&rc, 1 + p - pArgs);
if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs);
}
}
@@ -186292,7 +214322,7 @@ static int sqlite3Fts5ConfigParseRank(
}
if( rc!=SQLITE_OK ){
- sqlite3_free(zRank);
+ tdsqlite3_free(zRank);
assert( zRankArgs==0 );
}else{
*pzRank = zRank;
@@ -186301,30 +214331,30 @@ static int sqlite3Fts5ConfigParseRank(
return rc;
}
-static int sqlite3Fts5ConfigSetValue(
+static int tdsqlite3Fts5ConfigSetValue(
Fts5Config *pConfig,
const char *zKey,
- sqlite3_value *pVal,
+ tdsqlite3_value *pVal,
int *pbBadkey
){
int rc = SQLITE_OK;
- if( 0==sqlite3_stricmp(zKey, "pgsz") ){
+ if( 0==tdsqlite3_stricmp(zKey, "pgsz") ){
int pgsz = 0;
- if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
- pgsz = sqlite3_value_int(pVal);
+ if( SQLITE_INTEGER==tdsqlite3_value_numeric_type(pVal) ){
+ pgsz = tdsqlite3_value_int(pVal);
}
- if( pgsz<=0 || pgsz>FTS5_MAX_PAGE_SIZE ){
+ if( pgsz<32 || pgsz>FTS5_MAX_PAGE_SIZE ){
*pbBadkey = 1;
}else{
pConfig->pgsz = pgsz;
}
}
- else if( 0==sqlite3_stricmp(zKey, "hashsize") ){
+ else if( 0==tdsqlite3_stricmp(zKey, "hashsize") ){
int nHashSize = -1;
- if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
- nHashSize = sqlite3_value_int(pVal);
+ if( SQLITE_INTEGER==tdsqlite3_value_numeric_type(pVal) ){
+ nHashSize = tdsqlite3_value_int(pVal);
}
if( nHashSize<=0 ){
*pbBadkey = 1;
@@ -186333,10 +214363,10 @@ static int sqlite3Fts5ConfigSetValue(
}
}
- else if( 0==sqlite3_stricmp(zKey, "automerge") ){
+ else if( 0==tdsqlite3_stricmp(zKey, "automerge") ){
int nAutomerge = -1;
- if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
- nAutomerge = sqlite3_value_int(pVal);
+ if( SQLITE_INTEGER==tdsqlite3_value_numeric_type(pVal) ){
+ nAutomerge = tdsqlite3_value_int(pVal);
}
if( nAutomerge<0 || nAutomerge>64 ){
*pbBadkey = 1;
@@ -186346,10 +214376,10 @@ static int sqlite3Fts5ConfigSetValue(
}
}
- else if( 0==sqlite3_stricmp(zKey, "usermerge") ){
+ else if( 0==tdsqlite3_stricmp(zKey, "usermerge") ){
int nUsermerge = -1;
- if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
- nUsermerge = sqlite3_value_int(pVal);
+ if( SQLITE_INTEGER==tdsqlite3_value_numeric_type(pVal) ){
+ nUsermerge = tdsqlite3_value_int(pVal);
}
if( nUsermerge<2 || nUsermerge>16 ){
*pbBadkey = 1;
@@ -186358,27 +214388,28 @@ static int sqlite3Fts5ConfigSetValue(
}
}
- else if( 0==sqlite3_stricmp(zKey, "crisismerge") ){
+ else if( 0==tdsqlite3_stricmp(zKey, "crisismerge") ){
int nCrisisMerge = -1;
- if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
- nCrisisMerge = sqlite3_value_int(pVal);
+ if( SQLITE_INTEGER==tdsqlite3_value_numeric_type(pVal) ){
+ nCrisisMerge = tdsqlite3_value_int(pVal);
}
if( nCrisisMerge<0 ){
*pbBadkey = 1;
}else{
if( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
+ if( nCrisisMerge>=FTS5_MAX_SEGMENT ) nCrisisMerge = FTS5_MAX_SEGMENT-1;
pConfig->nCrisisMerge = nCrisisMerge;
}
}
- else if( 0==sqlite3_stricmp(zKey, "rank") ){
- const char *zIn = (const char*)sqlite3_value_text(pVal);
+ else if( 0==tdsqlite3_stricmp(zKey, "rank") ){
+ const char *zIn = (const char*)tdsqlite3_value_text(pVal);
char *zRank;
char *zRankArgs;
- rc = sqlite3Fts5ConfigParseRank(zIn, &zRank, &zRankArgs);
+ rc = tdsqlite3Fts5ConfigParseRank(zIn, &zRank, &zRankArgs);
if( rc==SQLITE_OK ){
- sqlite3_free(pConfig->zRank);
- sqlite3_free(pConfig->zRankArgs);
+ tdsqlite3_free(pConfig->zRank);
+ tdsqlite3_free(pConfig->zRankArgs);
pConfig->zRank = zRank;
pConfig->zRankArgs = zRankArgs;
}else if( rc==SQLITE_ERROR ){
@@ -186394,10 +214425,10 @@ static int sqlite3Fts5ConfigSetValue(
/*
** Load the contents of the %_config table into memory.
*/
-static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
+static int tdsqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
const char *zSelect = "SELECT k, v FROM %Q.'%q_config'";
char *zSql;
- sqlite3_stmt *p = 0;
+ tdsqlite3_stmt *p = 0;
int rc = SQLITE_OK;
int iVersion = 0;
@@ -186408,32 +214439,32 @@ static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE;
- zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName);
+ zSql = tdsqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName);
if( zSql ){
- rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0);
- sqlite3_free(zSql);
+ rc = tdsqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0);
+ tdsqlite3_free(zSql);
}
assert( rc==SQLITE_OK || p==0 );
if( rc==SQLITE_OK ){
- while( SQLITE_ROW==sqlite3_step(p) ){
- const char *zK = (const char*)sqlite3_column_text(p, 0);
- sqlite3_value *pVal = sqlite3_column_value(p, 1);
- if( 0==sqlite3_stricmp(zK, "version") ){
- iVersion = sqlite3_value_int(pVal);
+ while( SQLITE_ROW==tdsqlite3_step(p) ){
+ const char *zK = (const char*)tdsqlite3_column_text(p, 0);
+ tdsqlite3_value *pVal = tdsqlite3_column_value(p, 1);
+ if( 0==tdsqlite3_stricmp(zK, "version") ){
+ iVersion = tdsqlite3_value_int(pVal);
}else{
int bDummy = 0;
- sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, &bDummy);
+ tdsqlite3Fts5ConfigSetValue(pConfig, zK, pVal, &bDummy);
}
}
- rc = sqlite3_finalize(p);
+ rc = tdsqlite3_finalize(p);
}
if( rc==SQLITE_OK && iVersion!=FTS5_CURRENT_VERSION ){
rc = SQLITE_ERROR;
if( pConfig->pzErrmsg ){
assert( 0==*pConfig->pzErrmsg );
- *pConfig->pzErrmsg = sqlite3_mprintf(
+ *pConfig->pzErrmsg = tdsqlite3_mprintf(
"invalid fts5 file format (found %d, expected %d) - run 'rebuild'",
iVersion, FTS5_CURRENT_VERSION
);
@@ -186477,13 +214508,14 @@ typedef struct Fts5ExprTerm Fts5ExprTerm;
/*
** Functions generated by lemon from fts5parse.y.
*/
-static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(u64));
-static void sqlite3Fts5ParserFree(void*, void (*freeProc)(void*));
-static void sqlite3Fts5Parser(void*, int, Fts5Token, Fts5Parse*);
+static void *tdsqlite3Fts5ParserAlloc(void *(*mallocProc)(u64));
+static void tdsqlite3Fts5ParserFree(void*, void (*freeProc)(void*));
+static void tdsqlite3Fts5Parser(void*, int, Fts5Token, Fts5Parse*);
#ifndef NDEBUG
/* #include <stdio.h> */
-static void sqlite3Fts5ParserTrace(FILE*, char*);
+static void tdsqlite3Fts5ParserTrace(FILE*, char*);
#endif
+static int tdsqlite3Fts5ParserFallback(int);
struct Fts5Expr {
@@ -186535,7 +214567,8 @@ struct Fts5ExprNode {
** or term prefix.
*/
struct Fts5ExprTerm {
- int bPrefix; /* True for a prefix term */
+ u8 bPrefix; /* True for a prefix term */
+ u8 bFirst; /* True if token must be first in column */
char *zTerm; /* nul-terminated term */
Fts5IndexIter *pIter; /* Iterator for this term */
Fts5ExprTerm *pSynonym; /* Pointer to first in list of synonyms */
@@ -186576,11 +214609,11 @@ struct Fts5Parse {
Fts5ExprNode *pExpr; /* Result of a successful parse */
};
-static void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){
+static void tdsqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){
va_list ap;
va_start(ap, zFmt);
if( pParse->rc==SQLITE_OK ){
- pParse->zErr = sqlite3_vmprintf(zFmt, ap);
+ pParse->zErr = tdsqlite3_vmprintf(zFmt, ap);
pParse->rc = SQLITE_ERROR;
}
va_end(ap);
@@ -186616,6 +214649,7 @@ static int fts5ExprGetToken(
case '+': tok = FTS5_PLUS; break;
case '*': tok = FTS5_STAR; break;
case '-': tok = FTS5_MINUS; break;
+ case '^': tok = FTS5_CARET; break;
case '\0': tok = FTS5_EOF; break;
case '"': {
@@ -186628,7 +214662,7 @@ static int fts5ExprGetToken(
if( z2[0]!='"' ) break;
}
if( z2[0]=='\0' ){
- sqlite3Fts5ParseError(pParse, "unterminated string");
+ tdsqlite3Fts5ParseError(pParse, "unterminated string");
return FTS5_EOF;
}
}
@@ -186638,12 +214672,12 @@ static int fts5ExprGetToken(
default: {
const char *z2;
- if( sqlite3Fts5IsBareword(z[0])==0 ){
- sqlite3Fts5ParseError(pParse, "fts5: syntax error near \"%.1s\"", z);
+ if( tdsqlite3Fts5IsBareword(z[0])==0 ){
+ tdsqlite3Fts5ParseError(pParse, "fts5: syntax error near \"%.1s\"", z);
return FTS5_EOF;
}
tok = FTS5_STRING;
- for(z2=&z[1]; sqlite3Fts5IsBareword(*z2); z2++);
+ for(z2=&z[1]; tdsqlite3Fts5IsBareword(*z2); z2++);
pToken->n = (z2 - z);
if( pToken->n==2 && memcmp(pToken->p, "OR", 2)==0 ) tok = FTS5_OR;
if( pToken->n==3 && memcmp(pToken->p, "NOT", 3)==0 ) tok = FTS5_NOT;
@@ -186656,11 +214690,12 @@ static int fts5ExprGetToken(
return tok;
}
-static void *fts5ParseAlloc(u64 t){ return sqlite3_malloc((int)t); }
-static void fts5ParseFree(void *p){ sqlite3_free(p); }
+static void *fts5ParseAlloc(u64 t){ return tdsqlite3_malloc64((tdsqlite3_int64)t);}
+static void fts5ParseFree(void *p){ tdsqlite3_free(p); }
-static int sqlite3Fts5ExprNew(
+static int tdsqlite3Fts5ExprNew(
Fts5Config *pConfig, /* FTS5 Configuration */
+ int iCol,
const char *zExpr, /* Expression text */
Fts5Expr **ppNew,
char **pzErr
@@ -186675,26 +214710,38 @@ static int sqlite3Fts5ExprNew(
*ppNew = 0;
*pzErr = 0;
memset(&sParse, 0, sizeof(sParse));
- pEngine = sqlite3Fts5ParserAlloc(fts5ParseAlloc);
+ pEngine = tdsqlite3Fts5ParserAlloc(fts5ParseAlloc);
if( pEngine==0 ){ return SQLITE_NOMEM; }
sParse.pConfig = pConfig;
do {
t = fts5ExprGetToken(&sParse, &z, &token);
- sqlite3Fts5Parser(pEngine, t, token, &sParse);
+ tdsqlite3Fts5Parser(pEngine, t, token, &sParse);
}while( sParse.rc==SQLITE_OK && t!=FTS5_EOF );
- sqlite3Fts5ParserFree(pEngine, fts5ParseFree);
+ tdsqlite3Fts5ParserFree(pEngine, fts5ParseFree);
+
+ /* If the LHS of the MATCH expression was a user column, apply the
+ ** implicit column-filter. */
+ if( iCol<pConfig->nCol && sParse.pExpr && sParse.rc==SQLITE_OK ){
+ int n = sizeof(Fts5Colset);
+ Fts5Colset *pColset = (Fts5Colset*)tdsqlite3Fts5MallocZero(&sParse.rc, n);
+ if( pColset ){
+ pColset->nCol = 1;
+ pColset->aiCol[0] = iCol;
+ tdsqlite3Fts5ParseSetColset(&sParse, sParse.pExpr, pColset);
+ }
+ }
assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 );
if( sParse.rc==SQLITE_OK ){
- *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr));
+ *ppNew = pNew = tdsqlite3_malloc(sizeof(Fts5Expr));
if( pNew==0 ){
sParse.rc = SQLITE_NOMEM;
- sqlite3Fts5ParseNodeFree(sParse.pExpr);
+ tdsqlite3Fts5ParseNodeFree(sParse.pExpr);
}else{
if( !sParse.pExpr ){
const int nByte = sizeof(Fts5ExprNode);
- pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&sParse.rc, nByte);
+ pNew->pRoot = (Fts5ExprNode*)tdsqlite3Fts5MallocZero(&sParse.rc, nByte);
if( pNew->pRoot ){
pNew->pRoot->bEof = 1;
}
@@ -186708,10 +214755,10 @@ static int sqlite3Fts5ExprNew(
sParse.apPhrase = 0;
}
}else{
- sqlite3Fts5ParseNodeFree(sParse.pExpr);
+ tdsqlite3Fts5ParseNodeFree(sParse.pExpr);
}
- sqlite3_free(sParse.apPhrase);
+ tdsqlite3_free(sParse.apPhrase);
*pzErr = sParse.zErr;
return sParse.rc;
}
@@ -186719,26 +214766,62 @@ static int sqlite3Fts5ExprNew(
/*
** Free the expression node object passed as the only argument.
*/
-static void sqlite3Fts5ParseNodeFree(Fts5ExprNode *p){
+static void tdsqlite3Fts5ParseNodeFree(Fts5ExprNode *p){
if( p ){
int i;
for(i=0; i<p->nChild; i++){
- sqlite3Fts5ParseNodeFree(p->apChild[i]);
+ tdsqlite3Fts5ParseNodeFree(p->apChild[i]);
}
- sqlite3Fts5ParseNearsetFree(p->pNear);
- sqlite3_free(p);
+ tdsqlite3Fts5ParseNearsetFree(p->pNear);
+ tdsqlite3_free(p);
}
}
/*
** Free the expression object passed as the only argument.
*/
-static void sqlite3Fts5ExprFree(Fts5Expr *p){
+static void tdsqlite3Fts5ExprFree(Fts5Expr *p){
if( p ){
- sqlite3Fts5ParseNodeFree(p->pRoot);
- sqlite3_free(p->apExprPhrase);
- sqlite3_free(p);
+ tdsqlite3Fts5ParseNodeFree(p->pRoot);
+ tdsqlite3_free(p->apExprPhrase);
+ tdsqlite3_free(p);
+ }
+}
+
+static int tdsqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){
+ Fts5Parse sParse;
+ memset(&sParse, 0, sizeof(sParse));
+
+ if( *pp1 ){
+ Fts5Expr *p1 = *pp1;
+ int nPhrase = p1->nPhrase + p2->nPhrase;
+
+ p1->pRoot = tdsqlite3Fts5ParseNode(&sParse, FTS5_AND, p1->pRoot, p2->pRoot,0);
+ p2->pRoot = 0;
+
+ if( sParse.rc==SQLITE_OK ){
+ Fts5ExprPhrase **ap = (Fts5ExprPhrase**)tdsqlite3_realloc(
+ p1->apExprPhrase, nPhrase * sizeof(Fts5ExprPhrase*)
+ );
+ if( ap==0 ){
+ sParse.rc = SQLITE_NOMEM;
+ }else{
+ int i;
+ memmove(&ap[p2->nPhrase], ap, p1->nPhrase*sizeof(Fts5ExprPhrase*));
+ for(i=0; i<p2->nPhrase; i++){
+ ap[i] = p2->apExprPhrase[i];
+ }
+ p1->nPhrase = nPhrase;
+ p1->apExprPhrase = ap;
+ }
+ }
+ tdsqlite3_free(p2->apExprPhrase);
+ tdsqlite3_free(p2);
+ }else{
+ *pp1 = p2;
}
+
+ return sParse.rc;
}
/*
@@ -186753,7 +214836,7 @@ static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){
assert( pTerm->pSynonym );
assert( bDesc==0 || bDesc==1 );
for(p=pTerm; p; p=p->pSynonym){
- if( 0==sqlite3Fts5IterEof(p->pIter) ){
+ if( 0==tdsqlite3Fts5IterEof(p->pIter) ){
i64 iRowid = p->pIter->iRowid;
if( bRetValid==0 || (bDesc!=(iRowid<iRet)) ){
iRet = iRowid;
@@ -186785,21 +214868,21 @@ static int fts5ExprSynonymList(
assert( pTerm->pSynonym );
for(p=pTerm; p; p=p->pSynonym){
Fts5IndexIter *pIter = p->pIter;
- if( sqlite3Fts5IterEof(pIter)==0 && pIter->iRowid==iRowid ){
+ if( tdsqlite3Fts5IterEof(pIter)==0 && pIter->iRowid==iRowid ){
if( pIter->nData==0 ) continue;
if( nIter==nAlloc ){
- int nByte = sizeof(Fts5PoslistReader) * nAlloc * 2;
- Fts5PoslistReader *aNew = (Fts5PoslistReader*)sqlite3_malloc(nByte);
+ tdsqlite3_int64 nByte = sizeof(Fts5PoslistReader) * nAlloc * 2;
+ Fts5PoslistReader *aNew = (Fts5PoslistReader*)tdsqlite3_malloc64(nByte);
if( aNew==0 ){
rc = SQLITE_NOMEM;
goto synonym_poslist_out;
}
memcpy(aNew, aIter, sizeof(Fts5PoslistReader) * nIter);
nAlloc = nAlloc*2;
- if( aIter!=aStatic ) sqlite3_free(aIter);
+ if( aIter!=aStatic ) tdsqlite3_free(aIter);
aIter = aNew;
}
- sqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &aIter[nIter]);
+ tdsqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &aIter[nIter]);
assert( aIter[nIter].bEof==0 );
nIter++;
}
@@ -186818,7 +214901,7 @@ static int fts5ExprSynonymList(
for(i=0; i<nIter; i++){
if( aIter[i].bEof==0 ){
if( aIter[i].iPos==iPrev ){
- if( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) continue;
+ if( tdsqlite3Fts5PoslistReaderNext(&aIter[i]) ) continue;
}
if( aIter[i].iPos<iMin ){
iMin = aIter[i].iPos;
@@ -186826,7 +214909,7 @@ static int fts5ExprSynonymList(
}
}
if( iMin==FTS5_LARGEST_INT64 || rc!=SQLITE_OK ) break;
- rc = sqlite3Fts5PoslistWriterAppend(pBuf, &writer, iMin);
+ rc = tdsqlite3Fts5PoslistWriterAppend(pBuf, &writer, iMin);
iPrev = iMin;
}
if( rc==SQLITE_OK ){
@@ -186836,7 +214919,7 @@ static int fts5ExprSynonymList(
}
synonym_poslist_out:
- if( aIter!=aStatic ) sqlite3_free(aIter);
+ if( aIter!=aStatic ) tdsqlite3_free(aIter);
return rc;
}
@@ -186862,14 +214945,15 @@ static int fts5ExprPhraseIsMatch(
Fts5PoslistReader *aIter = aStatic;
int i;
int rc = SQLITE_OK;
+ int bFirst = pPhrase->aTerm[0].bFirst;
fts5BufferZero(&pPhrase->poslist);
/* If the aStatic[] array is not large enough, allocate a large array
- ** using sqlite3_malloc(). This approach could be improved upon. */
+ ** using tdsqlite3_malloc(). This approach could be improved upon. */
if( pPhrase->nTerm>ArraySize(aStatic) ){
- int nByte = sizeof(Fts5PoslistReader) * pPhrase->nTerm;
- aIter = (Fts5PoslistReader*)sqlite3_malloc(nByte);
+ tdsqlite3_int64 nByte = sizeof(Fts5PoslistReader) * pPhrase->nTerm;
+ aIter = (Fts5PoslistReader*)tdsqlite3_malloc64(nByte);
if( !aIter ) return SQLITE_NOMEM;
}
memset(aIter, 0, sizeof(Fts5PoslistReader) * pPhrase->nTerm);
@@ -186884,7 +214968,7 @@ static int fts5ExprPhraseIsMatch(
Fts5Buffer buf = {0, 0, 0};
rc = fts5ExprSynonymList(pTerm, pNode->iRowid, &buf, &a, &n);
if( rc ){
- sqlite3_free(a);
+ tdsqlite3_free(a);
goto ismatch_out;
}
if( a==buf.p ) bFlag = 1;
@@ -186892,7 +214976,7 @@ static int fts5ExprPhraseIsMatch(
a = (u8*)pTerm->pIter->pData;
n = pTerm->pIter->nData;
}
- sqlite3Fts5PoslistReaderInit(a, n, &aIter[i]);
+ tdsqlite3Fts5PoslistReaderInit(a, n, &aIter[i]);
aIter[i].bFlag = (u8)bFlag;
if( aIter[i].bEof ) goto ismatch_out;
}
@@ -186908,7 +214992,7 @@ static int fts5ExprPhraseIsMatch(
if( pPos->iPos!=iAdj ){
bMatch = 0;
while( pPos->iPos<iAdj ){
- if( sqlite3Fts5PoslistReaderNext(pPos) ) goto ismatch_out;
+ if( tdsqlite3Fts5PoslistReaderNext(pPos) ) goto ismatch_out;
}
if( pPos->iPos>iAdj ) iPos = pPos->iPos-i;
}
@@ -186916,20 +215000,22 @@ static int fts5ExprPhraseIsMatch(
}while( bMatch==0 );
/* Append position iPos to the output */
- rc = sqlite3Fts5PoslistWriterAppend(&pPhrase->poslist, &writer, iPos);
- if( rc!=SQLITE_OK ) goto ismatch_out;
+ if( bFirst==0 || FTS5_POS2OFFSET(iPos)==0 ){
+ rc = tdsqlite3Fts5PoslistWriterAppend(&pPhrase->poslist, &writer, iPos);
+ if( rc!=SQLITE_OK ) goto ismatch_out;
+ }
for(i=0; i<pPhrase->nTerm; i++){
- if( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) goto ismatch_out;
+ if( tdsqlite3Fts5PoslistReaderNext(&aIter[i]) ) goto ismatch_out;
}
}
ismatch_out:
*pbMatch = (pPhrase->poslist.n>0);
for(i=0; i<pPhrase->nTerm; i++){
- if( aIter[i].bFlag ) sqlite3_free((u8*)aIter[i].a);
+ if( aIter[i].bFlag ) tdsqlite3_free((u8*)aIter[i].a);
}
- if( aIter!=aStatic ) sqlite3_free(aIter);
+ if( aIter!=aStatic ) tdsqlite3_free(aIter);
return rc;
}
@@ -186946,7 +215032,7 @@ struct Fts5LookaheadReader {
static int fts5LookaheadReaderNext(Fts5LookaheadReader *p){
p->iPos = p->iLookahead;
- if( sqlite3Fts5PoslistNext64(p->a, p->n, &p->i, &p->iLookahead) ){
+ if( tdsqlite3Fts5PoslistNext64(p->a, p->n, &p->i, &p->iLookahead) ){
p->iLookahead = FTS5_LOOKAHEAD_EOF;
}
return (p->iPos==FTS5_LOOKAHEAD_EOF);
@@ -186999,10 +215085,10 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){
assert( pNear->nPhrase>1 );
/* If the aStatic[] array is not large enough, allocate a large array
- ** using sqlite3_malloc(). This approach could be improved upon. */
+ ** using tdsqlite3_malloc(). This approach could be improved upon. */
if( pNear->nPhrase>ArraySize(aStatic) ){
- int nByte = sizeof(Fts5NearTrimmer) * pNear->nPhrase;
- a = (Fts5NearTrimmer*)sqlite3Fts5MallocZero(&rc, nByte);
+ tdsqlite3_int64 nByte = sizeof(Fts5NearTrimmer) * pNear->nPhrase;
+ a = (Fts5NearTrimmer*)tdsqlite3Fts5MallocZero(&rc, nByte);
}else{
memset(aStatic, 0, sizeof(aStatic));
}
@@ -187054,7 +215140,7 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){
i64 iPos = a[i].reader.iPos;
Fts5PoslistWriter *pWriter = &a[i].writer;
if( a[i].pOut->n==0 || iPos!=pWriter->iPrev ){
- sqlite3Fts5PoslistWriterAppend(a[i].pOut, pWriter, iPos);
+ tdsqlite3Fts5PoslistWriterAppend(a[i].pOut, pWriter, iPos);
}
}
@@ -187072,7 +215158,7 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){
ismatch_out: {
int bRet = a[0].pOut->n>0;
*pRc = rc;
- if( a!=aStatic ) sqlite3_free(a);
+ if( a!=aStatic ) tdsqlite3_free(a);
return bRet;
}
}
@@ -187098,8 +215184,8 @@ static int fts5ExprAdvanceto(
iRowid = pIter->iRowid;
if( (bDesc==0 && iLast>iRowid) || (bDesc && iLast<iRowid) ){
- int rc = sqlite3Fts5IterNextFrom(pIter, iLast);
- if( rc || sqlite3Fts5IterEof(pIter) ){
+ int rc = tdsqlite3Fts5IterNextFrom(pIter, iLast);
+ if( rc || tdsqlite3Fts5IterEof(pIter) ){
*pRc = rc;
*pbEof = 1;
return 1;
@@ -187124,10 +215210,10 @@ static int fts5ExprSynonymAdvanceto(
int bEof = 0;
for(p=pTerm; rc==SQLITE_OK && p; p=p->pSynonym){
- if( sqlite3Fts5IterEof(p->pIter)==0 ){
+ if( tdsqlite3Fts5IterEof(p->pIter)==0 ){
i64 iRowid = p->pIter->iRowid;
if( (bDesc==0 && iLast>iRowid) || (bDesc && iLast<iRowid) ){
- rc = sqlite3Fts5IterNextFrom(p->pIter, iLast);
+ rc = tdsqlite3Fts5IterNextFrom(p->pIter, iLast);
}
}
}
@@ -187156,7 +215242,7 @@ static int fts5ExprNearTest(
pPhrase->poslist.n = 0;
for(pTerm=&pPhrase->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){
Fts5IndexIter *pIter = pTerm->pIter;
- if( sqlite3Fts5IterEof(pIter)==0 ){
+ if( tdsqlite3Fts5IterEof(pIter)==0 ){
if( pIter->iRowid==pNode->iRowid && pIter->nData>0 ){
pPhrase->poslist.n = 1;
}
@@ -187171,7 +215257,9 @@ static int fts5ExprNearTest(
** phrase is not a match, break out of the loop early. */
for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){
Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
- if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym || pNear->pColset ){
+ if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym
+ || pNear->pColset || pPhrase->aTerm[0].bFirst
+ ){
int bMatch = 0;
rc = fts5ExprPhraseIsMatch(pNode, pPhrase, &bMatch);
if( bMatch==0 ) break;
@@ -187194,48 +215282,61 @@ static int fts5ExprNearTest(
** Initialize all term iterators in the pNear object. If any term is found
** to match no documents at all, return immediately without initializing any
** further iterators.
+**
+** If an error occurs, return an SQLite error code. Otherwise, return
+** SQLITE_OK. It is not considered an error if some term matches zero
+** documents.
*/
static int fts5ExprNearInitAll(
Fts5Expr *pExpr,
Fts5ExprNode *pNode
){
Fts5ExprNearset *pNear = pNode->pNear;
- int i, j;
- int rc = SQLITE_OK;
- int bEof = 1;
+ int i;
assert( pNode->bNomatch==0 );
- for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){
+ for(i=0; i<pNear->nPhrase; i++){
Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
- for(j=0; j<pPhrase->nTerm; j++){
- Fts5ExprTerm *pTerm = &pPhrase->aTerm[j];
- Fts5ExprTerm *p;
+ if( pPhrase->nTerm==0 ){
+ pNode->bEof = 1;
+ return SQLITE_OK;
+ }else{
+ int j;
+ for(j=0; j<pPhrase->nTerm; j++){
+ Fts5ExprTerm *pTerm = &pPhrase->aTerm[j];
+ Fts5ExprTerm *p;
+ int bHit = 0;
+
+ for(p=pTerm; p; p=p->pSynonym){
+ int rc;
+ if( p->pIter ){
+ tdsqlite3Fts5IterClose(p->pIter);
+ p->pIter = 0;
+ }
+ rc = tdsqlite3Fts5IndexQuery(
+ pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm),
+ (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) |
+ (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0),
+ pNear->pColset,
+ &p->pIter
+ );
+ assert( (rc==SQLITE_OK)==(p->pIter!=0) );
+ if( rc!=SQLITE_OK ) return rc;
+ if( 0==tdsqlite3Fts5IterEof(p->pIter) ){
+ bHit = 1;
+ }
+ }
- for(p=pTerm; p && rc==SQLITE_OK; p=p->pSynonym){
- if( p->pIter ){
- sqlite3Fts5IterClose(p->pIter);
- p->pIter = 0;
- }
- rc = sqlite3Fts5IndexQuery(
- pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm),
- (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) |
- (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0),
- pNear->pColset,
- &p->pIter
- );
- assert( rc==SQLITE_OK || p->pIter==0 );
- if( p->pIter && 0==sqlite3Fts5IterEof(p->pIter) ){
- bEof = 0;
+ if( bHit==0 ){
+ pNode->bEof = 1;
+ return SQLITE_OK;
}
}
-
- if( bEof ) break;
}
- if( bEof ) break;
}
- pNode->bEof = bEof;
- return rc;
+ pNode->bEof = 0;
+ return SQLITE_OK;
}
/*
@@ -187339,6 +215440,7 @@ static int fts5ExprNodeTest_STRING(
assert( pNear->nPhrase>1
|| pNear->apPhrase[0]->nTerm>1
|| pNear->apPhrase[0]->aTerm[0].pSynonym
+ || pNear->apPhrase[0]->aTerm[0].bFirst
);
/* Initialize iLast, the "lastest" rowid any iterator points to. If the
@@ -187412,18 +215514,18 @@ static int fts5ExprNodeNext_STRING(
/* Advance each iterator that currently points to iRowid. Or, if iFrom
** is valid - each iterator that points to a rowid before iFrom. */
for(p=pTerm; p; p=p->pSynonym){
- if( sqlite3Fts5IterEof(p->pIter)==0 ){
+ if( tdsqlite3Fts5IterEof(p->pIter)==0 ){
i64 ii = p->pIter->iRowid;
if( ii==iRowid
|| (bFromValid && ii!=iFrom && (ii>iFrom)==pExpr->bDesc)
){
if( bFromValid ){
- rc = sqlite3Fts5IterNextFrom(p->pIter, iFrom);
+ rc = tdsqlite3Fts5IterNextFrom(p->pIter, iFrom);
}else{
- rc = sqlite3Fts5IterNext(p->pIter);
+ rc = tdsqlite3Fts5IterNext(p->pIter);
}
if( rc!=SQLITE_OK ) break;
- if( sqlite3Fts5IterEof(p->pIter)==0 ){
+ if( tdsqlite3Fts5IterEof(p->pIter)==0 ){
bEof = 0;
}
}else{
@@ -187440,12 +215542,12 @@ static int fts5ExprNodeNext_STRING(
assert( Fts5NodeIsString(pNode) );
if( bFromValid ){
- rc = sqlite3Fts5IterNextFrom(pIter, iFrom);
+ rc = tdsqlite3Fts5IterNextFrom(pIter, iFrom);
}else{
- rc = sqlite3Fts5IterNext(pIter);
+ rc = tdsqlite3Fts5IterNext(pIter);
}
- pNode->bEof = (rc || sqlite3Fts5IterEof(pIter));
+ pNode->bEof = (rc || tdsqlite3Fts5IterEof(pIter));
}
if( pNode->bEof==0 ){
@@ -187496,11 +215598,11 @@ static int fts5ExprNodeNext_TERM(
assert( pNode->bEof==0 );
if( bFromValid ){
- rc = sqlite3Fts5IterNextFrom(pIter, iFrom);
+ rc = tdsqlite3Fts5IterNextFrom(pIter, iFrom);
}else{
- rc = sqlite3Fts5IterNext(pIter);
+ rc = tdsqlite3Fts5IterNext(pIter);
}
- if( rc==SQLITE_OK && sqlite3Fts5IterEof(pIter)==0 ){
+ if( rc==SQLITE_OK && tdsqlite3Fts5IterEof(pIter)==0 ){
rc = fts5ExprNodeTest_TERM(pExpr, pNode);
}else{
pNode->bEof = 1;
@@ -187545,7 +215647,10 @@ static int fts5ExprNodeNext_OR(
|| (bFromValid && fts5RowidCmp(pExpr, p1->iRowid, iFrom)<0)
){
int rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom);
- if( rc!=SQLITE_OK ) return rc;
+ if( rc!=SQLITE_OK ){
+ pNode->bNomatch = 0;
+ return rc;
+ }
}
}
}
@@ -187576,7 +215681,10 @@ static int fts5ExprNodeTest_AND(
if( cmp>0 ){
/* Advance pChild until it points to iLast or laster */
rc = fts5ExprNodeNext(pExpr, pChild, 1, iLast);
- if( rc!=SQLITE_OK ) return rc;
+ if( rc!=SQLITE_OK ){
+ pAnd->bNomatch = 0;
+ return rc;
+ }
}
/* If the child node is now at EOF, so is the parent AND node. Otherwise,
@@ -187615,6 +215723,8 @@ static int fts5ExprNodeNext_AND(
int rc = fts5ExprNodeNext(pExpr, pNode->apChild[0], bFromValid, iFrom);
if( rc==SQLITE_OK ){
rc = fts5ExprNodeTest_AND(pExpr, pNode);
+ }else{
+ pNode->bNomatch = 0;
}
return rc;
}
@@ -187657,6 +215767,9 @@ static int fts5ExprNodeNext_NOT(
if( rc==SQLITE_OK ){
rc = fts5ExprNodeTest_NOT(pExpr, pNode);
}
+ if( rc!=SQLITE_OK ){
+ pNode->bNomatch = 0;
+ }
return rc;
}
@@ -187769,7 +215882,7 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){
** Return SQLITE_OK if successful, or an SQLite error code otherwise. It
** is not considered an error if the query does not match any documents.
*/
-static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){
+static int tdsqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){
Fts5ExprNode *pRoot = p->pRoot;
int rc; /* Return code */
@@ -187779,7 +215892,10 @@ static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bD
/* If not at EOF but the current rowid occurs earlier than iFirst in
** the iteration order, move to document iFirst or later. */
- if( pRoot->bEof==0 && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 ){
+ if( rc==SQLITE_OK
+ && 0==pRoot->bEof
+ && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0
+ ){
rc = fts5ExprNodeNext(p, pRoot, 1, iFirst);
}
@@ -187797,7 +215913,7 @@ static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bD
** Return SQLITE_OK if successful, or an SQLite error code otherwise. It
** is not considered an error if the query does not match any documents.
*/
-static int sqlite3Fts5ExprNext(Fts5Expr *p, i64 iLast){
+static int tdsqlite3Fts5ExprNext(Fts5Expr *p, i64 iLast){
int rc;
Fts5ExprNode *pRoot = p->pRoot;
assert( pRoot->bEof==0 && pRoot->bNomatch==0 );
@@ -187811,17 +215927,17 @@ static int sqlite3Fts5ExprNext(Fts5Expr *p, i64 iLast){
return rc;
}
-static int sqlite3Fts5ExprEof(Fts5Expr *p){
+static int tdsqlite3Fts5ExprEof(Fts5Expr *p){
return p->pRoot->bEof;
}
-static i64 sqlite3Fts5ExprRowid(Fts5Expr *p){
+static i64 tdsqlite3Fts5ExprRowid(Fts5Expr *p){
return p->pRoot->iRowid;
}
static int fts5ParseStringFromToken(Fts5Token *pToken, char **pz){
int rc = SQLITE_OK;
- *pz = sqlite3Fts5Strndup(&rc, pToken->p, pToken->n);
+ *pz = tdsqlite3Fts5Strndup(&rc, pToken->p, pToken->n);
return rc;
}
@@ -187835,17 +215951,27 @@ static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){
Fts5ExprTerm *pSyn;
Fts5ExprTerm *pNext;
Fts5ExprTerm *pTerm = &pPhrase->aTerm[i];
- sqlite3_free(pTerm->zTerm);
- sqlite3Fts5IterClose(pTerm->pIter);
+ tdsqlite3_free(pTerm->zTerm);
+ tdsqlite3Fts5IterClose(pTerm->pIter);
for(pSyn=pTerm->pSynonym; pSyn; pSyn=pNext){
pNext = pSyn->pSynonym;
- sqlite3Fts5IterClose(pSyn->pIter);
+ tdsqlite3Fts5IterClose(pSyn->pIter);
fts5BufferFree((Fts5Buffer*)&pSyn[1]);
- sqlite3_free(pSyn);
+ tdsqlite3_free(pSyn);
}
}
if( pPhrase->poslist.nSpace>0 ) fts5BufferFree(&pPhrase->poslist);
- sqlite3_free(pPhrase);
+ tdsqlite3_free(pPhrase);
+ }
+}
+
+/*
+** Set the "bFirst" flag on the first token of the phrase passed as the
+** only argument.
+*/
+static void tdsqlite3Fts5ParseSetCaret(Fts5ExprPhrase *pPhrase){
+ if( pPhrase && pPhrase->nTerm ){
+ pPhrase->aTerm[0].bFirst = 1;
}
}
@@ -187857,7 +215983,7 @@ static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){
** If an OOM error occurs, both the pNear and pPhrase objects are freed and
** NULL returned.
*/
-static Fts5ExprNearset *sqlite3Fts5ParseNearset(
+static Fts5ExprNearset *tdsqlite3Fts5ParseNearset(
Fts5Parse *pParse, /* Parse context */
Fts5ExprNearset *pNear, /* Existing nearset, or NULL */
Fts5ExprPhrase *pPhrase /* Recently parsed phrase */
@@ -187870,18 +215996,20 @@ static Fts5ExprNearset *sqlite3Fts5ParseNearset(
return pNear;
}
if( pNear==0 ){
- int nByte = sizeof(Fts5ExprNearset) + SZALLOC * sizeof(Fts5ExprPhrase*);
- pRet = sqlite3_malloc(nByte);
+ tdsqlite3_int64 nByte;
+ nByte = sizeof(Fts5ExprNearset) + SZALLOC * sizeof(Fts5ExprPhrase*);
+ pRet = tdsqlite3_malloc64(nByte);
if( pRet==0 ){
pParse->rc = SQLITE_NOMEM;
}else{
- memset(pRet, 0, nByte);
+ memset(pRet, 0, (size_t)nByte);
}
}else if( (pNear->nPhrase % SZALLOC)==0 ){
int nNew = pNear->nPhrase + SZALLOC;
- int nByte = sizeof(Fts5ExprNearset) + nNew * sizeof(Fts5ExprPhrase*);
+ tdsqlite3_int64 nByte;
- pRet = (Fts5ExprNearset*)sqlite3_realloc(pNear, nByte);
+ nByte = sizeof(Fts5ExprNearset) + nNew * sizeof(Fts5ExprPhrase*);
+ pRet = (Fts5ExprNearset*)tdsqlite3_realloc64(pNear, nByte);
if( pRet==0 ){
pParse->rc = SQLITE_NOMEM;
}
@@ -187892,8 +216020,8 @@ static Fts5ExprNearset *sqlite3Fts5ParseNearset(
if( pRet==0 ){
assert( pParse->rc!=SQLITE_OK );
- sqlite3Fts5ParseNearsetFree(pNear);
- sqlite3Fts5ParsePhraseFree(pPhrase);
+ tdsqlite3Fts5ParseNearsetFree(pNear);
+ tdsqlite3Fts5ParsePhraseFree(pPhrase);
}else{
if( pRet->nPhrase>0 ){
Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1];
@@ -187945,12 +216073,12 @@ static int fts5ParseTokenize(
if( pPhrase && pPhrase->nTerm>0 && (tflags & FTS5_TOKEN_COLOCATED) ){
Fts5ExprTerm *pSyn;
- int nByte = sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer) + nToken+1;
- pSyn = (Fts5ExprTerm*)sqlite3_malloc(nByte);
+ tdsqlite3_int64 nByte = sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer) + nToken+1;
+ pSyn = (Fts5ExprTerm*)tdsqlite3_malloc64(nByte);
if( pSyn==0 ){
rc = SQLITE_NOMEM;
}else{
- memset(pSyn, 0, nByte);
+ memset(pSyn, 0, (size_t)nByte);
pSyn->zTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer);
memcpy(pSyn->zTerm, pToken, nToken);
pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym;
@@ -187962,7 +216090,7 @@ static int fts5ParseTokenize(
Fts5ExprPhrase *pNew;
int nNew = SZALLOC + (pPhrase ? pPhrase->nTerm : 0);
- pNew = (Fts5ExprPhrase*)sqlite3_realloc(pPhrase,
+ pNew = (Fts5ExprPhrase*)tdsqlite3_realloc64(pPhrase,
sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * nNew
);
if( pNew==0 ){
@@ -187977,7 +216105,7 @@ static int fts5ParseTokenize(
if( rc==SQLITE_OK ){
pTerm = &pPhrase->aTerm[pPhrase->nTerm++];
memset(pTerm, 0, sizeof(Fts5ExprTerm));
- pTerm->zTerm = sqlite3Fts5Strndup(&rc, pToken, nToken);
+ pTerm->zTerm = tdsqlite3Fts5Strndup(&rc, pToken, nToken);
}
}
@@ -187989,25 +216117,25 @@ static int fts5ParseTokenize(
/*
** Free the phrase object passed as the only argument.
*/
-static void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase *pPhrase){
+static void tdsqlite3Fts5ParsePhraseFree(Fts5ExprPhrase *pPhrase){
fts5ExprPhraseFree(pPhrase);
}
/*
** Free the phrase object passed as the second argument.
*/
-static void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset *pNear){
+static void tdsqlite3Fts5ParseNearsetFree(Fts5ExprNearset *pNear){
if( pNear ){
int i;
for(i=0; i<pNear->nPhrase; i++){
fts5ExprPhraseFree(pNear->apPhrase[i]);
}
- sqlite3_free(pNear->pColset);
- sqlite3_free(pNear);
+ tdsqlite3_free(pNear->pColset);
+ tdsqlite3_free(pNear);
}
}
-static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){
+static void tdsqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){
assert( pParse->pExpr==0 );
pParse->pExpr = p;
}
@@ -188017,7 +216145,7 @@ static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){
** string may or may not be quoted. In any case it is tokenized and a
** phrase object consisting of all tokens returned.
*/
-static Fts5ExprPhrase *sqlite3Fts5ParseTerm(
+static Fts5ExprPhrase *tdsqlite3Fts5ParseTerm(
Fts5Parse *pParse, /* Parse context */
Fts5ExprPhrase *pAppend, /* Phrase to append to */
Fts5Token *pToken, /* String to tokenize */
@@ -188033,13 +216161,13 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm(
rc = fts5ParseStringFromToken(pToken, &z);
if( rc==SQLITE_OK ){
- int flags = FTS5_TOKENIZE_QUERY | (bPrefix ? FTS5_TOKENIZE_QUERY : 0);
+ int flags = FTS5_TOKENIZE_QUERY | (bPrefix ? FTS5_TOKENIZE_PREFIX : 0);
int n;
- sqlite3Fts5Dequote(z);
+ tdsqlite3Fts5Dequote(z);
n = (int)strlen(z);
- rc = sqlite3Fts5Tokenize(pConfig, flags, z, n, &sCtx, fts5ParseTokenize);
+ rc = tdsqlite3Fts5Tokenize(pConfig, flags, z, n, &sCtx, fts5ParseTokenize);
}
- sqlite3_free(z);
+ tdsqlite3_free(z);
if( rc || (rc = sCtx.rc) ){
pParse->rc = rc;
fts5ExprPhraseFree(sCtx.pPhrase);
@@ -188048,9 +216176,9 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm(
if( pAppend==0 ){
if( (pParse->nPhrase % 8)==0 ){
- int nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8);
+ tdsqlite3_int64 nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8);
Fts5ExprPhrase **apNew;
- apNew = (Fts5ExprPhrase**)sqlite3_realloc(pParse->apPhrase, nByte);
+ apNew = (Fts5ExprPhrase**)tdsqlite3_realloc64(pParse->apPhrase, nByte);
if( apNew==0 ){
pParse->rc = SQLITE_NOMEM;
fts5ExprPhraseFree(sCtx.pPhrase);
@@ -188064,9 +216192,9 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm(
if( sCtx.pPhrase==0 ){
/* This happens when parsing a token or quoted phrase that contains
** no token characters at all. (e.g ... MATCH '""'). */
- sCtx.pPhrase = sqlite3Fts5MallocZero(&pParse->rc, sizeof(Fts5ExprPhrase));
+ sCtx.pPhrase = tdsqlite3Fts5MallocZero(&pParse->rc, sizeof(Fts5ExprPhrase));
}else if( sCtx.pPhrase->nTerm ){
- sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
+ sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = (u8)bPrefix;
}
pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase;
}
@@ -188078,7 +216206,7 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm(
** Create a new FTS5 expression by cloning phrase iPhrase of the
** expression passed as the second argument.
*/
-static int sqlite3Fts5ExprClonePhrase(
+static int tdsqlite3Fts5ExprClonePhrase(
Fts5Expr *pExpr,
int iPhrase,
Fts5Expr **ppNew
@@ -188089,26 +216217,28 @@ static int sqlite3Fts5ExprClonePhrase(
TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */
pOrig = pExpr->apExprPhrase[iPhrase];
- pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr));
+ pNew = (Fts5Expr*)tdsqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr));
if( rc==SQLITE_OK ){
- pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc,
+ pNew->apExprPhrase = (Fts5ExprPhrase**)tdsqlite3Fts5MallocZero(&rc,
sizeof(Fts5ExprPhrase*));
}
if( rc==SQLITE_OK ){
- pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&rc,
+ pNew->pRoot = (Fts5ExprNode*)tdsqlite3Fts5MallocZero(&rc,
sizeof(Fts5ExprNode));
}
if( rc==SQLITE_OK ){
- pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc,
+ pNew->pRoot->pNear = (Fts5ExprNearset*)tdsqlite3Fts5MallocZero(&rc,
sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*));
}
if( rc==SQLITE_OK ){
Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset;
if( pColsetOrig ){
- int nByte = sizeof(Fts5Colset) + (pColsetOrig->nCol-1) * sizeof(int);
- Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&rc, nByte);
+ tdsqlite3_int64 nByte;
+ Fts5Colset *pColset;
+ nByte = sizeof(Fts5Colset) + (pColsetOrig->nCol-1) * sizeof(int);
+ pColset = (Fts5Colset*)tdsqlite3Fts5MallocZero(&rc, nByte);
if( pColset ){
- memcpy(pColset, pColsetOrig, nByte);
+ memcpy(pColset, pColsetOrig, (size_t)nByte);
}
pNew->pRoot->pNear->pColset = pColset;
}
@@ -188127,12 +216257,13 @@ static int sqlite3Fts5ExprClonePhrase(
}
if( rc==SQLITE_OK ){
sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
+ sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst;
}
}
}else{
/* This happens when parsing a token or quoted phrase that contains
** no token characters at all. (e.g ... MATCH '""'). */
- sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase));
+ sCtx.pPhrase = tdsqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase));
}
if( rc==SQLITE_OK ){
@@ -188145,7 +216276,10 @@ static int sqlite3Fts5ExprClonePhrase(
pNew->pRoot->pNear->nPhrase = 1;
sCtx.pPhrase->pNode = pNew->pRoot;
- if( pOrig->nTerm==1 && pOrig->aTerm[0].pSynonym==0 ){
+ if( pOrig->nTerm==1
+ && pOrig->aTerm[0].pSynonym==0
+ && pOrig->aTerm[0].bFirst==0
+ ){
pNew->pRoot->eType = FTS5_TERM;
pNew->pRoot->xNext = fts5ExprNodeNext_TERM;
}else{
@@ -188153,7 +216287,7 @@ static int sqlite3Fts5ExprClonePhrase(
pNew->pRoot->xNext = fts5ExprNodeNext_STRING;
}
}else{
- sqlite3Fts5ExprFree(pNew);
+ tdsqlite3Fts5ExprFree(pNew);
fts5ExprPhraseFree(sCtx.pPhrase);
pNew = 0;
}
@@ -188168,15 +216302,15 @@ static int sqlite3Fts5ExprClonePhrase(
** is expected. If token pTok does not contain "NEAR", store an error
** in the pParse object.
*/
-static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token *pTok){
+static void tdsqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token *pTok){
if( pTok->n!=4 || memcmp("NEAR", pTok->p, 4) ){
- sqlite3Fts5ParseError(
+ tdsqlite3Fts5ParseError(
pParse, "fts5: syntax error near \"%.*s\"", pTok->n, pTok->p
);
}
}
-static void sqlite3Fts5ParseSetDistance(
+static void tdsqlite3Fts5ParseSetDistance(
Fts5Parse *pParse,
Fts5ExprNearset *pNear,
Fts5Token *p
@@ -188188,7 +216322,7 @@ static void sqlite3Fts5ParseSetDistance(
for(i=0; i<p->n; i++){
char c = (char)p->p[i];
if( c<'0' || c>'9' ){
- sqlite3Fts5ParseError(
+ tdsqlite3Fts5ParseError(
pParse, "expected integer, got \"%.*s\"", p->n, p->p
);
return;
@@ -188222,7 +216356,7 @@ static Fts5Colset *fts5ParseColset(
assert( pParse->rc==SQLITE_OK );
assert( iCol>=0 && iCol<pParse->pConfig->nCol );
- pNew = sqlite3_realloc(p, sizeof(Fts5Colset) + sizeof(int)*nCol);
+ pNew = tdsqlite3_realloc64(p, sizeof(Fts5Colset) + sizeof(int)*nCol);
if( pNew==0 ){
pParse->rc = SQLITE_NOMEM;
}else{
@@ -188252,11 +216386,11 @@ static Fts5Colset *fts5ParseColset(
** the colset passed as the second argument. Free the colset passed
** as the second argument before returning.
*/
-static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse *pParse, Fts5Colset *p){
+static Fts5Colset *tdsqlite3Fts5ParseColsetInvert(Fts5Parse *pParse, Fts5Colset *p){
Fts5Colset *pRet;
int nCol = pParse->pConfig->nCol;
- pRet = (Fts5Colset*)sqlite3Fts5MallocZero(&pParse->rc,
+ pRet = (Fts5Colset*)tdsqlite3Fts5MallocZero(&pParse->rc,
sizeof(Fts5Colset) + sizeof(int)*nCol
);
if( pRet ){
@@ -188271,11 +216405,11 @@ static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse *pParse, Fts5Colset *p
}
}
- sqlite3_free(p);
+ tdsqlite3_free(p);
return pRet;
}
-static Fts5Colset *sqlite3Fts5ParseColset(
+static Fts5Colset *tdsqlite3Fts5ParseColset(
Fts5Parse *pParse, /* Store SQLITE_NOMEM here if required */
Fts5Colset *pColset, /* Existing colset object */
Fts5Token *p
@@ -188284,48 +216418,133 @@ static Fts5Colset *sqlite3Fts5ParseColset(
int iCol;
char *z; /* Dequoted copy of token p */
- z = sqlite3Fts5Strndup(&pParse->rc, p->p, p->n);
+ z = tdsqlite3Fts5Strndup(&pParse->rc, p->p, p->n);
if( pParse->rc==SQLITE_OK ){
Fts5Config *pConfig = pParse->pConfig;
- sqlite3Fts5Dequote(z);
+ tdsqlite3Fts5Dequote(z);
for(iCol=0; iCol<pConfig->nCol; iCol++){
- if( 0==sqlite3_stricmp(pConfig->azCol[iCol], z) ) break;
+ if( 0==tdsqlite3_stricmp(pConfig->azCol[iCol], z) ) break;
}
if( iCol==pConfig->nCol ){
- sqlite3Fts5ParseError(pParse, "no such column: %s", z);
+ tdsqlite3Fts5ParseError(pParse, "no such column: %s", z);
}else{
pRet = fts5ParseColset(pParse, pColset, iCol);
}
- sqlite3_free(z);
+ tdsqlite3_free(z);
}
if( pRet==0 ){
assert( pParse->rc!=SQLITE_OK );
- sqlite3_free(pColset);
+ tdsqlite3_free(pColset);
}
return pRet;
}
-static void sqlite3Fts5ParseSetColset(
+/*
+** If argument pOrig is NULL, or if (*pRc) is set to anything other than
+** SQLITE_OK when this function is called, NULL is returned.
+**
+** Otherwise, a copy of (*pOrig) is made into memory obtained from
+** tdsqlite3Fts5MallocZero() and a pointer to it returned. If the allocation
+** fails, (*pRc) is set to SQLITE_NOMEM and NULL is returned.
+*/
+static Fts5Colset *fts5CloneColset(int *pRc, Fts5Colset *pOrig){
+ Fts5Colset *pRet;
+ if( pOrig ){
+ tdsqlite3_int64 nByte = sizeof(Fts5Colset) + (pOrig->nCol-1) * sizeof(int);
+ pRet = (Fts5Colset*)tdsqlite3Fts5MallocZero(pRc, nByte);
+ if( pRet ){
+ memcpy(pRet, pOrig, (size_t)nByte);
+ }
+ }else{
+ pRet = 0;
+ }
+ return pRet;
+}
+
+/*
+** Remove from colset pColset any columns that are not also in colset pMerge.
+*/
+static void fts5MergeColset(Fts5Colset *pColset, Fts5Colset *pMerge){
+ int iIn = 0; /* Next input in pColset */
+ int iMerge = 0; /* Next input in pMerge */
+ int iOut = 0; /* Next output slot in pColset */
+
+ while( iIn<pColset->nCol && iMerge<pMerge->nCol ){
+ int iDiff = pColset->aiCol[iIn] - pMerge->aiCol[iMerge];
+ if( iDiff==0 ){
+ pColset->aiCol[iOut++] = pMerge->aiCol[iMerge];
+ iMerge++;
+ iIn++;
+ }else if( iDiff>0 ){
+ iMerge++;
+ }else{
+ iIn++;
+ }
+ }
+ pColset->nCol = iOut;
+}
+
+/*
+** Recursively apply colset pColset to expression node pNode and all of
+** its decendents. If (*ppFree) is not NULL, it contains a spare copy
+** of pColset. This function may use the spare copy and set (*ppFree) to
+** zero, or it may create copies of pColset using fts5CloneColset().
+*/
+static void fts5ParseSetColset(
+ Fts5Parse *pParse,
+ Fts5ExprNode *pNode,
+ Fts5Colset *pColset,
+ Fts5Colset **ppFree
+){
+ if( pParse->rc==SQLITE_OK ){
+ assert( pNode->eType==FTS5_TERM || pNode->eType==FTS5_STRING
+ || pNode->eType==FTS5_AND || pNode->eType==FTS5_OR
+ || pNode->eType==FTS5_NOT || pNode->eType==FTS5_EOF
+ );
+ if( pNode->eType==FTS5_STRING || pNode->eType==FTS5_TERM ){
+ Fts5ExprNearset *pNear = pNode->pNear;
+ if( pNear->pColset ){
+ fts5MergeColset(pNear->pColset, pColset);
+ if( pNear->pColset->nCol==0 ){
+ pNode->eType = FTS5_EOF;
+ pNode->xNext = 0;
+ }
+ }else if( *ppFree ){
+ pNear->pColset = pColset;
+ *ppFree = 0;
+ }else{
+ pNear->pColset = fts5CloneColset(&pParse->rc, pColset);
+ }
+ }else{
+ int i;
+ assert( pNode->eType!=FTS5_EOF || pNode->nChild==0 );
+ for(i=0; i<pNode->nChild; i++){
+ fts5ParseSetColset(pParse, pNode->apChild[i], pColset, ppFree);
+ }
+ }
+ }
+}
+
+/*
+** Apply colset pColset to expression node pExpr and all of its descendents.
+*/
+static void tdsqlite3Fts5ParseSetColset(
Fts5Parse *pParse,
- Fts5ExprNearset *pNear,
+ Fts5ExprNode *pExpr,
Fts5Colset *pColset
){
+ Fts5Colset *pFree = pColset;
if( pParse->pConfig->eDetail==FTS5_DETAIL_NONE ){
pParse->rc = SQLITE_ERROR;
- pParse->zErr = sqlite3_mprintf(
+ pParse->zErr = tdsqlite3_mprintf(
"fts5: column queries are not supported (detail=none)"
);
- sqlite3_free(pColset);
- return;
- }
-
- if( pNear ){
- pNear->pColset = pColset;
}else{
- sqlite3_free(pColset);
+ fts5ParseSetColset(pParse, pExpr, pColset, &pFree);
}
+ tdsqlite3_free(pFree);
}
static void fts5ExprAssignXNext(Fts5ExprNode *pNode){
@@ -188334,6 +216553,7 @@ static void fts5ExprAssignXNext(Fts5ExprNode *pNode){
Fts5ExprNearset *pNear = pNode->pNear;
if( pNear->nPhrase==1 && pNear->apPhrase[0]->nTerm==1
&& pNear->apPhrase[0]->aTerm[0].pSynonym==0
+ && pNear->apPhrase[0]->aTerm[0].bFirst==0
){
pNode->eType = FTS5_TERM;
pNode->xNext = fts5ExprNodeNext_TERM;
@@ -188365,7 +216585,7 @@ static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){
int nByte = sizeof(Fts5ExprNode*) * pSub->nChild;
memcpy(&p->apChild[p->nChild], pSub->apChild, nByte);
p->nChild += pSub->nChild;
- sqlite3_free(pSub);
+ tdsqlite3_free(pSub);
}else{
p->apChild[p->nChild++] = pSub;
}
@@ -188375,7 +216595,7 @@ static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){
** Allocate and return a new expression object. If anything goes wrong (i.e.
** OOM error), leave an error code in pParse and return NULL.
*/
-static Fts5ExprNode *sqlite3Fts5ParseNode(
+static Fts5ExprNode *tdsqlite3Fts5ParseNode(
Fts5Parse *pParse, /* Parse context */
int eType, /* FTS5_STRING, AND, OR or NOT */
Fts5ExprNode *pLeft, /* Left hand child expression */
@@ -188386,7 +216606,7 @@ static Fts5ExprNode *sqlite3Fts5ParseNode(
if( pParse->rc==SQLITE_OK ){
int nChild = 0; /* Number of children of returned node */
- int nByte; /* Bytes of space to allocate for this node */
+ tdsqlite3_int64 nByte; /* Bytes of space to allocate for this node */
assert( (eType!=FTS5_STRING && !pNear)
|| (eType==FTS5_STRING && !pLeft && !pRight)
@@ -188404,7 +216624,7 @@ static Fts5ExprNode *sqlite3Fts5ParseNode(
}
nByte = sizeof(Fts5ExprNode) + sizeof(Fts5ExprNode*)*(nChild-1);
- pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte);
+ pRet = (Fts5ExprNode*)tdsqlite3Fts5MallocZero(&pParse->rc, nByte);
if( pRet ){
pRet->eType = eType;
@@ -188420,20 +216640,23 @@ static Fts5ExprNode *sqlite3Fts5ParseNode(
}
}
- if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL
- && (pNear->nPhrase!=1 || pNear->apPhrase[0]->nTerm>1)
- ){
- assert( pParse->rc==SQLITE_OK );
- pParse->rc = SQLITE_ERROR;
- assert( pParse->zErr==0 );
- pParse->zErr = sqlite3_mprintf(
- "fts5: %s queries are not supported (detail!=full)",
- pNear->nPhrase==1 ? "phrase": "NEAR"
- );
- sqlite3_free(pRet);
- pRet = 0;
+ if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL ){
+ Fts5ExprPhrase *pPhrase = pNear->apPhrase[0];
+ if( pNear->nPhrase!=1
+ || pPhrase->nTerm>1
+ || (pPhrase->nTerm>0 && pPhrase->aTerm[0].bFirst)
+ ){
+ assert( pParse->rc==SQLITE_OK );
+ pParse->rc = SQLITE_ERROR;
+ assert( pParse->zErr==0 );
+ pParse->zErr = tdsqlite3_mprintf(
+ "fts5: %s queries are not supported (detail!=full)",
+ pNear->nPhrase==1 ? "phrase": "NEAR"
+ );
+ tdsqlite3_free(pRet);
+ pRet = 0;
+ }
}
-
}else{
fts5ExprAddChildren(pRet, pLeft);
fts5ExprAddChildren(pRet, pRight);
@@ -188443,14 +216666,14 @@ static Fts5ExprNode *sqlite3Fts5ParseNode(
if( pRet==0 ){
assert( pParse->rc!=SQLITE_OK );
- sqlite3Fts5ParseNodeFree(pLeft);
- sqlite3Fts5ParseNodeFree(pRight);
- sqlite3Fts5ParseNearsetFree(pNear);
+ tdsqlite3Fts5ParseNodeFree(pLeft);
+ tdsqlite3Fts5ParseNodeFree(pRight);
+ tdsqlite3Fts5ParseNearsetFree(pNear);
}
return pRet;
}
-static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
+static Fts5ExprNode *tdsqlite3Fts5ParseImplicitAnd(
Fts5Parse *pParse, /* Parse context */
Fts5ExprNode *pLeft, /* Left hand child expression */
Fts5ExprNode *pRight /* Right hand child expression */
@@ -188459,8 +216682,8 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
Fts5ExprNode *pPrev;
if( pParse->rc ){
- sqlite3Fts5ParseNodeFree(pLeft);
- sqlite3Fts5ParseNodeFree(pRight);
+ tdsqlite3Fts5ParseNodeFree(pLeft);
+ tdsqlite3Fts5ParseNodeFree(pRight);
}else{
assert( pLeft->eType==FTS5_STRING
@@ -188485,7 +216708,7 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
if( pRight->eType==FTS5_EOF ){
assert( pParse->apPhrase[pParse->nPhrase-1]==pRight->pNear->apPhrase[0] );
- sqlite3Fts5ParseNodeFree(pRight);
+ tdsqlite3Fts5ParseNodeFree(pRight);
pRet = pLeft;
pParse->nPhrase--;
}
@@ -188504,10 +216727,10 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
memmove(ap, &ap[1], sizeof(Fts5ExprPhrase*)*pRight->pNear->nPhrase);
pParse->nPhrase--;
- sqlite3Fts5ParseNodeFree(pPrev);
+ tdsqlite3Fts5ParseNodeFree(pPrev);
}
else{
- pRet = sqlite3Fts5ParseNode(pParse, FTS5_AND, pLeft, pRight, 0);
+ pRet = tdsqlite3Fts5ParseNode(pParse, FTS5_AND, pLeft, pRight, 0);
}
}
@@ -188515,7 +216738,7 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
}
static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
- int nByte = 0;
+ tdsqlite3_int64 nByte = 0;
Fts5ExprTerm *p;
char *zQuoted;
@@ -188523,7 +216746,7 @@ static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
for(p=pTerm; p; p=p->pSynonym){
nByte += (int)strlen(pTerm->zTerm) * 2 + 3 + 2;
}
- zQuoted = sqlite3_malloc(nByte);
+ zQuoted = tdsqlite3_malloc64(nByte);
if( zQuoted ){
int i = 0;
@@ -188550,14 +216773,14 @@ static char *fts5PrintfAppend(char *zApp, const char *zFmt, ...){
char *zNew;
va_list ap;
va_start(ap, zFmt);
- zNew = sqlite3_vmprintf(zFmt, ap);
+ zNew = tdsqlite3_vmprintf(zFmt, ap);
va_end(ap);
if( zApp && zNew ){
- char *zNew2 = sqlite3_mprintf("%s%s", zApp, zNew);
- sqlite3_free(zNew);
+ char *zNew2 = tdsqlite3_mprintf("%s%s", zApp, zNew);
+ tdsqlite3_free(zNew);
zNew = zNew2;
}
- sqlite3_free(zApp);
+ tdsqlite3_free(zApp);
return zNew;
}
@@ -188565,7 +216788,7 @@ static char *fts5PrintfAppend(char *zApp, const char *zFmt, ...){
** Compose a tcl-readable representation of expression pExpr. Return a
** pointer to a buffer containing that representation. It is the
** responsibility of the caller to at some point free the buffer using
-** sqlite3_free().
+** tdsqlite3_free().
*/
static char *fts5ExprPrintTcl(
Fts5Config *pConfig,
@@ -188631,11 +216854,11 @@ static char *fts5ExprPrintTcl(
break;
}
- zRet = sqlite3_mprintf("%s", zOp);
+ zRet = tdsqlite3_mprintf("%s", zOp);
for(i=0; zRet && i<pExpr->nChild; i++){
char *z = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->apChild[i]);
if( !z ){
- sqlite3_free(zRet);
+ tdsqlite3_free(zRet);
zRet = 0;
}else{
zRet = fts5PrintfAppend(zRet, " [%z]", z);
@@ -188649,7 +216872,7 @@ static char *fts5ExprPrintTcl(
static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
char *zRet = 0;
if( pExpr->eType==0 ){
- return sqlite3_mprintf("\"\"");
+ return tdsqlite3_mprintf("\"\"");
}else
if( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){
Fts5ExprNearset *pNear = pExpr->pNear;
@@ -188677,10 +216900,10 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
char *zTerm = fts5ExprTermPrint(&pPhrase->aTerm[iTerm]);
if( zTerm ){
zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" + ", zTerm);
- sqlite3_free(zTerm);
+ tdsqlite3_free(zTerm);
}
if( zTerm==0 || zRet==0 ){
- sqlite3_free(zRet);
+ tdsqlite3_free(zRet);
return 0;
}
}
@@ -188707,7 +216930,7 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
for(i=0; i<pExpr->nChild; i++){
char *z = fts5ExprPrint(pConfig, pExpr->apChild[i]);
if( z==0 ){
- sqlite3_free(zRet);
+ tdsqlite3_free(zRet);
zRet = 0;
}else{
int e = pExpr->apChild[i]->eType;
@@ -188729,13 +216952,13 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
** and fts5_expr_tcl() (bTcl!=0).
*/
static void fts5ExprFunction(
- sqlite3_context *pCtx, /* Function call context */
+ tdsqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
- sqlite3_value **apVal, /* Function arguments */
+ tdsqlite3_value **apVal, /* Function arguments */
int bTcl
){
- Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx);
- sqlite3 *db = sqlite3_context_db_handle(pCtx);
+ Fts5Global *pGlobal = (Fts5Global*)tdsqlite3_user_data(pCtx);
+ tdsqlite3 *db = tdsqlite3_context_db_handle(pCtx);
const char *zExpr = 0;
char *zErr = 0;
Fts5Expr *pExpr = 0;
@@ -188749,42 +216972,44 @@ static void fts5ExprFunction(
int iArg = 1;
if( nArg<1 ){
- zErr = sqlite3_mprintf("wrong number of arguments to function %s",
+ zErr = tdsqlite3_mprintf("wrong number of arguments to function %s",
bTcl ? "fts5_expr_tcl" : "fts5_expr"
);
- sqlite3_result_error(pCtx, zErr, -1);
- sqlite3_free(zErr);
+ tdsqlite3_result_error(pCtx, zErr, -1);
+ tdsqlite3_free(zErr);
return;
}
if( bTcl && nArg>1 ){
- zNearsetCmd = (const char*)sqlite3_value_text(apVal[1]);
+ zNearsetCmd = (const char*)tdsqlite3_value_text(apVal[1]);
iArg = 2;
}
nConfig = 3 + (nArg-iArg);
- azConfig = (const char**)sqlite3_malloc(sizeof(char*) * nConfig);
+ azConfig = (const char**)tdsqlite3_malloc64(sizeof(char*) * nConfig);
if( azConfig==0 ){
- sqlite3_result_error_nomem(pCtx);
+ tdsqlite3_result_error_nomem(pCtx);
return;
}
azConfig[0] = 0;
azConfig[1] = "main";
azConfig[2] = "tbl";
for(i=3; iArg<nArg; iArg++){
- azConfig[i++] = (const char*)sqlite3_value_text(apVal[iArg]);
+ const char *z = (const char*)tdsqlite3_value_text(apVal[iArg]);
+ azConfig[i++] = (z ? z : "");
}
- zExpr = (const char*)sqlite3_value_text(apVal[0]);
+ zExpr = (const char*)tdsqlite3_value_text(apVal[0]);
+ if( zExpr==0 ) zExpr = "";
- rc = sqlite3Fts5ConfigParse(pGlobal, db, nConfig, azConfig, &pConfig, &zErr);
+ rc = tdsqlite3Fts5ConfigParse(pGlobal, db, nConfig, azConfig, &pConfig, &zErr);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5ExprNew(pConfig, zExpr, &pExpr, &zErr);
+ rc = tdsqlite3Fts5ExprNew(pConfig, pConfig->nCol, zExpr, &pExpr, &zErr);
}
if( rc==SQLITE_OK ){
char *zText;
if( pExpr->pRoot->xNext==0 ){
- zText = sqlite3_mprintf("");
+ zText = tdsqlite3_mprintf("");
}else if( bTcl ){
zText = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->pRoot);
}else{
@@ -188793,35 +217018,35 @@ static void fts5ExprFunction(
if( zText==0 ){
rc = SQLITE_NOMEM;
}else{
- sqlite3_result_text(pCtx, zText, -1, SQLITE_TRANSIENT);
- sqlite3_free(zText);
+ tdsqlite3_result_text(pCtx, zText, -1, SQLITE_TRANSIENT);
+ tdsqlite3_free(zText);
}
}
if( rc!=SQLITE_OK ){
if( zErr ){
- sqlite3_result_error(pCtx, zErr, -1);
- sqlite3_free(zErr);
+ tdsqlite3_result_error(pCtx, zErr, -1);
+ tdsqlite3_free(zErr);
}else{
- sqlite3_result_error_code(pCtx, rc);
+ tdsqlite3_result_error_code(pCtx, rc);
}
}
- sqlite3_free((void *)azConfig);
- sqlite3Fts5ConfigFree(pConfig);
- sqlite3Fts5ExprFree(pExpr);
+ tdsqlite3_free((void *)azConfig);
+ tdsqlite3Fts5ConfigFree(pConfig);
+ tdsqlite3Fts5ExprFree(pExpr);
}
static void fts5ExprFunctionHr(
- sqlite3_context *pCtx, /* Function call context */
+ tdsqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
- sqlite3_value **apVal /* Function arguments */
+ tdsqlite3_value **apVal /* Function arguments */
){
fts5ExprFunction(pCtx, nArg, apVal, 0);
}
static void fts5ExprFunctionTcl(
- sqlite3_context *pCtx, /* Function call context */
+ tdsqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
- sqlite3_value **apVal /* Function arguments */
+ tdsqlite3_value **apVal /* Function arguments */
){
fts5ExprFunction(pCtx, nArg, apVal, 1);
}
@@ -188832,36 +217057,41 @@ static void fts5ExprFunctionTcl(
** unicode code point, 1 is returned. Otherwise 0.
*/
static void fts5ExprIsAlnum(
- sqlite3_context *pCtx, /* Function call context */
+ tdsqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
- sqlite3_value **apVal /* Function arguments */
+ tdsqlite3_value **apVal /* Function arguments */
){
int iCode;
+ u8 aArr[32];
if( nArg!=1 ){
- sqlite3_result_error(pCtx,
+ tdsqlite3_result_error(pCtx,
"wrong number of arguments to function fts5_isalnum", -1
);
return;
}
- iCode = sqlite3_value_int(apVal[0]);
- sqlite3_result_int(pCtx, sqlite3Fts5UnicodeIsalnum(iCode));
+ memset(aArr, 0, sizeof(aArr));
+ tdsqlite3Fts5UnicodeCatParse("L*", aArr);
+ tdsqlite3Fts5UnicodeCatParse("N*", aArr);
+ tdsqlite3Fts5UnicodeCatParse("Co", aArr);
+ iCode = tdsqlite3_value_int(apVal[0]);
+ tdsqlite3_result_int(pCtx, aArr[tdsqlite3Fts5UnicodeCategory((u32)iCode)]);
}
static void fts5ExprFold(
- sqlite3_context *pCtx, /* Function call context */
+ tdsqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
- sqlite3_value **apVal /* Function arguments */
+ tdsqlite3_value **apVal /* Function arguments */
){
if( nArg!=1 && nArg!=2 ){
- sqlite3_result_error(pCtx,
+ tdsqlite3_result_error(pCtx,
"wrong number of arguments to function fts5_fold", -1
);
}else{
int iCode;
int bRemoveDiacritics = 0;
- iCode = sqlite3_value_int(apVal[0]);
- if( nArg==2 ) bRemoveDiacritics = sqlite3_value_int(apVal[1]);
- sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics));
+ iCode = tdsqlite3_value_int(apVal[0]);
+ if( nArg==2 ) bRemoveDiacritics = tdsqlite3_value_int(apVal[1]);
+ tdsqlite3_result_int(pCtx, tdsqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics));
}
}
@@ -188869,10 +217099,10 @@ static void fts5ExprFold(
** This is called during initialization to register the fts5_expr() scalar
** UDF with the SQLite handle passed as the only argument.
*/
-static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
+static int tdsqlite3Fts5ExprInit(Fts5Global *pGlobal, tdsqlite3 *db){
struct Fts5ExprFunc {
const char *z;
- void (*x)(sqlite3_context*,int,sqlite3_value**);
+ void (*x)(tdsqlite3_context*,int,tdsqlite3_value**);
} aFunc[] = {
{ "fts5_expr", fts5ExprFunctionHr },
{ "fts5_expr_tcl", fts5ExprFunctionTcl },
@@ -188885,13 +217115,15 @@ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
for(i=0; rc==SQLITE_OK && i<ArraySize(aFunc); i++){
struct Fts5ExprFunc *p = &aFunc[i];
- rc = sqlite3_create_function(db, p->z, -1, SQLITE_UTF8, pCtx, p->x, 0, 0);
+ rc = tdsqlite3_create_function(db, p->z, -1, SQLITE_UTF8, pCtx, p->x, 0, 0);
}
- /* Avoid a warning indicating that sqlite3Fts5ParserTrace() is unused */
+ /* Avoid warnings indicating that tdsqlite3Fts5ParserTrace() and
+ ** tdsqlite3Fts5ParserFallback() are unused */
#ifndef NDEBUG
- (void)sqlite3Fts5ParserTrace;
+ (void)tdsqlite3Fts5ParserTrace;
#endif
+ (void)tdsqlite3Fts5ParserFallback;
return rc;
}
@@ -188899,14 +217131,14 @@ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
/*
** Return the number of phrases in expression pExpr.
*/
-static int sqlite3Fts5ExprPhraseCount(Fts5Expr *pExpr){
+static int tdsqlite3Fts5ExprPhraseCount(Fts5Expr *pExpr){
return (pExpr ? pExpr->nPhrase : 0);
}
/*
** Return the number of terms in the iPhrase'th phrase in pExpr.
*/
-static int sqlite3Fts5ExprPhraseSize(Fts5Expr *pExpr, int iPhrase){
+static int tdsqlite3Fts5ExprPhraseSize(Fts5Expr *pExpr, int iPhrase){
if( iPhrase<0 || iPhrase>=pExpr->nPhrase ) return 0;
return pExpr->apExprPhrase[iPhrase]->nTerm;
}
@@ -188915,7 +217147,7 @@ static int sqlite3Fts5ExprPhraseSize(Fts5Expr *pExpr, int iPhrase){
** This function is used to access the current position list for phrase
** iPhrase.
*/
-static int sqlite3Fts5ExprPoslist(Fts5Expr *pExpr, int iPhrase, const u8 **pa){
+static int tdsqlite3Fts5ExprPoslist(Fts5Expr *pExpr, int iPhrase, const u8 **pa){
int nRet;
Fts5ExprPhrase *pPhrase = pExpr->apExprPhrase[iPhrase];
Fts5ExprNode *pNode = pPhrase->pNode;
@@ -188935,9 +217167,9 @@ struct Fts5PoslistPopulator {
int bMiss;
};
-static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int bLive){
+static Fts5PoslistPopulator *tdsqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int bLive){
Fts5PoslistPopulator *pRet;
- pRet = sqlite3_malloc(sizeof(Fts5PoslistPopulator)*pExpr->nPhrase);
+ pRet = tdsqlite3_malloc64(sizeof(Fts5PoslistPopulator)*pExpr->nPhrase);
if( pRet ){
int i;
memset(pRet, 0, sizeof(Fts5PoslistPopulator)*pExpr->nPhrase);
@@ -188999,7 +217231,7 @@ static int fts5ExprPopulatePoslistsCb(
if( (nTerm==nToken || (nTerm<nToken && pTerm->bPrefix))
&& memcmp(pTerm->zTerm, pToken, nTerm)==0
){
- int rc = sqlite3Fts5PoslistWriterAppend(
+ int rc = tdsqlite3Fts5PoslistWriterAppend(
&pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff
);
if( rc ) return rc;
@@ -189010,7 +217242,7 @@ static int fts5ExprPopulatePoslistsCb(
return SQLITE_OK;
}
-static int sqlite3Fts5ExprPopulatePoslists(
+static int tdsqlite3Fts5ExprPopulatePoslists(
Fts5Config *pConfig,
Fts5Expr *pExpr,
Fts5PoslistPopulator *aPopulator,
@@ -189035,7 +217267,7 @@ static int sqlite3Fts5ExprPopulatePoslists(
}
}
- return sqlite3Fts5Tokenize(pConfig,
+ return tdsqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT, z, n, (void*)&sCtx, fts5ExprPopulatePoslistsCb
);
}
@@ -189095,14 +217327,14 @@ static int fts5ExprCheckPoslists(Fts5ExprNode *pNode, i64 iRowid){
return 1;
}
-static void sqlite3Fts5ExprCheckPoslists(Fts5Expr *pExpr, i64 iRowid){
+static void tdsqlite3Fts5ExprCheckPoslists(Fts5Expr *pExpr, i64 iRowid){
fts5ExprCheckPoslists(pExpr->pRoot, iRowid);
}
/*
** This function is only called for detail=columns tables.
*/
-static int sqlite3Fts5ExprPhraseCollist(
+static int tdsqlite3Fts5ExprPhraseCollist(
Fts5Expr *pExpr,
int iPhrase,
const u8 **ppCollist,
@@ -189137,7 +217369,6 @@ static int sqlite3Fts5ExprPhraseCollist(
return rc;
}
-
/*
** 2014 August 11
**
@@ -189176,9 +217407,10 @@ struct Fts5Hash {
/*
** Each entry in the hash table is represented by an object of the
-** following type. Each object, its key (zKey[]) and its current data
-** are stored in a single memory allocation. The position list data
-** immediately follows the key data in memory.
+** following type. Each object, its key (a nul-terminated string) and
+** its current data are stored in a single memory allocation. The
+** key immediately follows the object in memory. The position list
+** data immediately follows the key data in memory.
**
** The data that follows the key is in a similar, but not identical format
** to the doclist data stored in the database. It is:
@@ -189202,47 +217434,47 @@ struct Fts5HashEntry {
int nAlloc; /* Total size of allocation */
int iSzPoslist; /* Offset of space for 4-byte poslist size */
int nData; /* Total bytes of data (incl. structure) */
- int nKey; /* Length of zKey[] in bytes */
+ int nKey; /* Length of key in bytes */
u8 bDel; /* Set delete-flag @ iSzPoslist */
u8 bContent; /* Set content-flag (detail=none mode) */
i16 iCol; /* Column of last value written */
int iPos; /* Position of last value written */
i64 iRowid; /* Rowid of last value written */
- char zKey[8]; /* Nul-terminated entry key */
};
/*
-** Size of Fts5HashEntry without the zKey[] array.
+** Eqivalent to:
+**
+** char *fts5EntryKey(Fts5HashEntry *pEntry){ return zKey; }
*/
-#define FTS5_HASHENTRYSIZE (sizeof(Fts5HashEntry)-8)
-
+#define fts5EntryKey(p) ( ((char *)(&(p)[1])) )
/*
** Allocate a new hash table.
*/
-static int sqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte){
+static int tdsqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte){
int rc = SQLITE_OK;
Fts5Hash *pNew;
- *ppNew = pNew = (Fts5Hash*)sqlite3_malloc(sizeof(Fts5Hash));
+ *ppNew = pNew = (Fts5Hash*)tdsqlite3_malloc(sizeof(Fts5Hash));
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
- int nByte;
+ tdsqlite3_int64 nByte;
memset(pNew, 0, sizeof(Fts5Hash));
pNew->pnByte = pnByte;
pNew->eDetail = pConfig->eDetail;
pNew->nSlot = 1024;
nByte = sizeof(Fts5HashEntry*) * pNew->nSlot;
- pNew->aSlot = (Fts5HashEntry**)sqlite3_malloc(nByte);
+ pNew->aSlot = (Fts5HashEntry**)tdsqlite3_malloc64(nByte);
if( pNew->aSlot==0 ){
- sqlite3_free(pNew);
+ tdsqlite3_free(pNew);
*ppNew = 0;
rc = SQLITE_NOMEM;
}else{
- memset(pNew->aSlot, 0, nByte);
+ memset(pNew->aSlot, 0, (size_t)nByte);
}
}
return rc;
@@ -189251,25 +217483,25 @@ static int sqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte
/*
** Free a hash table object.
*/
-static void sqlite3Fts5HashFree(Fts5Hash *pHash){
+static void tdsqlite3Fts5HashFree(Fts5Hash *pHash){
if( pHash ){
- sqlite3Fts5HashClear(pHash);
- sqlite3_free(pHash->aSlot);
- sqlite3_free(pHash);
+ tdsqlite3Fts5HashClear(pHash);
+ tdsqlite3_free(pHash->aSlot);
+ tdsqlite3_free(pHash);
}
}
/*
** Empty (but do not delete) a hash table.
*/
-static void sqlite3Fts5HashClear(Fts5Hash *pHash){
+static void tdsqlite3Fts5HashClear(Fts5Hash *pHash){
int i;
for(i=0; i<pHash->nSlot; i++){
Fts5HashEntry *pNext;
Fts5HashEntry *pSlot;
for(pSlot=pHash->aSlot[i]; pSlot; pSlot=pNext){
pNext = pSlot->pHashNext;
- sqlite3_free(pSlot);
+ tdsqlite3_free(pSlot);
}
}
memset(pHash->aSlot, 0, pHash->nSlot * sizeof(Fts5HashEntry*));
@@ -189304,57 +217536,69 @@ static int fts5HashResize(Fts5Hash *pHash){
Fts5HashEntry **apNew;
Fts5HashEntry **apOld = pHash->aSlot;
- apNew = (Fts5HashEntry**)sqlite3_malloc(nNew*sizeof(Fts5HashEntry*));
+ apNew = (Fts5HashEntry**)tdsqlite3_malloc64(nNew*sizeof(Fts5HashEntry*));
if( !apNew ) return SQLITE_NOMEM;
memset(apNew, 0, nNew*sizeof(Fts5HashEntry*));
for(i=0; i<pHash->nSlot; i++){
while( apOld[i] ){
- int iHash;
+ unsigned int iHash;
Fts5HashEntry *p = apOld[i];
apOld[i] = p->pHashNext;
- iHash = fts5HashKey(nNew, (u8*)p->zKey, (int)strlen(p->zKey));
+ iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p),
+ (int)strlen(fts5EntryKey(p)));
p->pHashNext = apNew[iHash];
apNew[iHash] = p;
}
}
- sqlite3_free(apOld);
+ tdsqlite3_free(apOld);
pHash->nSlot = nNew;
pHash->aSlot = apNew;
return SQLITE_OK;
}
-static void fts5HashAddPoslistSize(Fts5Hash *pHash, Fts5HashEntry *p){
+static int fts5HashAddPoslistSize(
+ Fts5Hash *pHash,
+ Fts5HashEntry *p,
+ Fts5HashEntry *p2
+){
+ int nRet = 0;
if( p->iSzPoslist ){
- u8 *pPtr = (u8*)p;
+ u8 *pPtr = p2 ? (u8*)p2 : (u8*)p;
+ int nData = p->nData;
if( pHash->eDetail==FTS5_DETAIL_NONE ){
- assert( p->nData==p->iSzPoslist );
+ assert( nData==p->iSzPoslist );
if( p->bDel ){
- pPtr[p->nData++] = 0x00;
+ pPtr[nData++] = 0x00;
if( p->bContent ){
- pPtr[p->nData++] = 0x00;
+ pPtr[nData++] = 0x00;
}
}
}else{
- int nSz = (p->nData - p->iSzPoslist - 1); /* Size in bytes */
+ int nSz = (nData - p->iSzPoslist - 1); /* Size in bytes */
int nPos = nSz*2 + p->bDel; /* Value of nPos field */
assert( p->bDel==0 || p->bDel==1 );
if( nPos<=127 ){
pPtr[p->iSzPoslist] = (u8)nPos;
}else{
- int nByte = sqlite3Fts5GetVarintLen((u32)nPos);
+ int nByte = tdsqlite3Fts5GetVarintLen((u32)nPos);
memmove(&pPtr[p->iSzPoslist + nByte], &pPtr[p->iSzPoslist + 1], nSz);
- sqlite3Fts5PutVarint(&pPtr[p->iSzPoslist], nPos);
- p->nData += (nByte-1);
+ tdsqlite3Fts5PutVarint(&pPtr[p->iSzPoslist], nPos);
+ nData += (nByte-1);
}
}
- p->iSzPoslist = 0;
- p->bDel = 0;
- p->bContent = 0;
+ nRet = nData - p->nData;
+ if( p2==0 ){
+ p->iSzPoslist = 0;
+ p->bDel = 0;
+ p->bContent = 0;
+ p->nData = nData;
+ }
}
+ return nRet;
}
/*
@@ -189365,7 +217609,7 @@ static void fts5HashAddPoslistSize(Fts5Hash *pHash, Fts5HashEntry *p){
**
** Or, if iCol is negative, then the value is a delete marker.
*/
-static int sqlite3Fts5HashWrite(
+static int tdsqlite3Fts5HashWrite(
Fts5Hash *pHash,
i64 iRowid, /* Rowid for this entry */
int iCol, /* Column token appears in (-ve -> delete) */
@@ -189384,9 +217628,10 @@ static int sqlite3Fts5HashWrite(
/* Attempt to locate an existing hash entry */
iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken);
for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
- if( p->zKey[0]==bByte
+ char *zKey = fts5EntryKey(p);
+ if( zKey[0]==bByte
&& p->nKey==nToken
- && memcmp(&p->zKey[1], pToken, nToken)==0
+ && memcmp(&zKey[1], pToken, nToken)==0
){
break;
}
@@ -189395,7 +217640,8 @@ static int sqlite3Fts5HashWrite(
/* If an existing hash entry cannot be found, create a new one. */
if( p==0 ){
/* Figure out how much space to allocate */
- int nByte = FTS5_HASHENTRYSIZE + (nToken+1) + 1 + 64;
+ char *zKey;
+ tdsqlite3_int64 nByte = sizeof(Fts5HashEntry) + (nToken+1) + 1 + 64;
if( nByte<128 ) nByte = 128;
/* Grow the Fts5Hash.aSlot[] array if necessary. */
@@ -189406,22 +217652,23 @@ static int sqlite3Fts5HashWrite(
}
/* Allocate new Fts5HashEntry and add it to the hash table. */
- p = (Fts5HashEntry*)sqlite3_malloc(nByte);
+ p = (Fts5HashEntry*)tdsqlite3_malloc64(nByte);
if( !p ) return SQLITE_NOMEM;
- memset(p, 0, FTS5_HASHENTRYSIZE);
- p->nAlloc = nByte;
- p->zKey[0] = bByte;
- memcpy(&p->zKey[1], pToken, nToken);
- assert( iHash==fts5HashKey(pHash->nSlot, (u8*)p->zKey, nToken+1) );
+ memset(p, 0, sizeof(Fts5HashEntry));
+ p->nAlloc = (int)nByte;
+ zKey = fts5EntryKey(p);
+ zKey[0] = bByte;
+ memcpy(&zKey[1], pToken, nToken);
+ assert( iHash==fts5HashKey(pHash->nSlot, (u8*)zKey, nToken+1) );
p->nKey = nToken;
- p->zKey[nToken+1] = '\0';
- p->nData = nToken+1 + 1 + FTS5_HASHENTRYSIZE;
+ zKey[nToken+1] = '\0';
+ p->nData = nToken+1 + 1 + sizeof(Fts5HashEntry);
p->pHashNext = pHash->aSlot[iHash];
pHash->aSlot[iHash] = p;
pHash->nEntry++;
/* Add the first rowid field to the hash-entry */
- p->nData += sqlite3Fts5PutVarint(&((u8*)p)[p->nData], iRowid);
+ p->nData += tdsqlite3Fts5PutVarint(&((u8*)p)[p->nData], iRowid);
p->iRowid = iRowid;
p->iSzPoslist = p->nData;
@@ -189444,12 +217691,12 @@ static int sqlite3Fts5HashWrite(
** + 5 bytes for the new position offset (32-bit max).
*/
if( (p->nAlloc - p->nData) < (9 + 4 + 1 + 3 + 5) ){
- int nNew = p->nAlloc * 2;
+ tdsqlite3_int64 nNew = p->nAlloc * 2;
Fts5HashEntry *pNew;
Fts5HashEntry **pp;
- pNew = (Fts5HashEntry*)sqlite3_realloc(p, nNew);
+ pNew = (Fts5HashEntry*)tdsqlite3_realloc64(p, nNew);
if( pNew==0 ) return SQLITE_NOMEM;
- pNew->nAlloc = nNew;
+ pNew->nAlloc = (int)nNew;
for(pp=&pHash->aSlot[iHash]; *pp!=p; pp=&(*pp)->pHashNext);
*pp = pNew;
p = pNew;
@@ -189463,8 +217710,8 @@ static int sqlite3Fts5HashWrite(
/* If this is a new rowid, append the 4-byte size field for the previous
** entry, and the new rowid for this entry. */
if( iRowid!=p->iRowid ){
- fts5HashAddPoslistSize(pHash, p);
- p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iRowid - p->iRowid);
+ fts5HashAddPoslistSize(pHash, p, 0);
+ p->nData += tdsqlite3Fts5PutVarint(&pPtr[p->nData], iRowid - p->iRowid);
p->iRowid = iRowid;
bNew = 1;
p->iSzPoslist = p->nData;
@@ -189484,7 +217731,7 @@ static int sqlite3Fts5HashWrite(
if( iCol!=p->iCol ){
if( pHash->eDetail==FTS5_DETAIL_FULL ){
pPtr[p->nData++] = 0x01;
- p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iCol);
+ p->nData += tdsqlite3Fts5PutVarint(&pPtr[p->nData], iCol);
p->iCol = (i16)iCol;
p->iPos = 0;
}else{
@@ -189495,7 +217742,7 @@ static int sqlite3Fts5HashWrite(
/* Append the new position offset, if necessary */
if( bNew ){
- p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iPos - p->iPos + 2);
+ p->nData += tdsqlite3Fts5PutVarint(&pPtr[p->nData], iPos - p->iPos + 2);
p->iPos = iPos;
}
}
@@ -189533,9 +217780,11 @@ static Fts5HashEntry *fts5HashEntryMerge(
p1 = 0;
}else{
int i = 0;
- while( p1->zKey[i]==p2->zKey[i] ) i++;
+ char *zKey1 = fts5EntryKey(p1);
+ char *zKey2 = fts5EntryKey(p2);
+ while( zKey1[i]==zKey2[i] ) i++;
- if( ((u8)p1->zKey[i])>((u8)p2->zKey[i]) ){
+ if( ((u8)zKey1[i])>((u8)zKey2[i]) ){
/* p2 is smaller */
*ppOut = p2;
ppOut = &p2->pScanNext;
@@ -189571,14 +217820,16 @@ static int fts5HashEntrySort(
int i;
*ppSorted = 0;
- ap = sqlite3_malloc(sizeof(Fts5HashEntry*) * nMergeSlot);
+ ap = tdsqlite3_malloc64(sizeof(Fts5HashEntry*) * nMergeSlot);
if( !ap ) return SQLITE_NOMEM;
memset(ap, 0, sizeof(Fts5HashEntry*) * nMergeSlot);
for(iSlot=0; iSlot<pHash->nSlot; iSlot++){
Fts5HashEntry *pIter;
for(pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){
- if( pTerm==0 || 0==memcmp(pIter->zKey, pTerm, nTerm) ){
+ if( pTerm==0
+ || (pIter->nKey+1>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm))
+ ){
Fts5HashEntry *pEntry = pIter;
pEntry->pScanNext = 0;
for(i=0; ap[i]; i++){
@@ -189596,7 +217847,7 @@ static int fts5HashEntrySort(
}
pHash->nEntry = 0;
- sqlite3_free(ap);
+ tdsqlite3_free(ap);
*ppSorted = pList;
return SQLITE_OK;
}
@@ -189604,48 +217855,61 @@ static int fts5HashEntrySort(
/*
** Query the hash table for a doclist associated with term pTerm/nTerm.
*/
-static int sqlite3Fts5HashQuery(
+static int tdsqlite3Fts5HashQuery(
Fts5Hash *pHash, /* Hash table to query */
+ int nPre,
const char *pTerm, int nTerm, /* Query term */
- const u8 **ppDoclist, /* OUT: Pointer to doclist for pTerm */
+ void **ppOut, /* OUT: Pointer to new object */
int *pnDoclist /* OUT: Size of doclist in bytes */
){
unsigned int iHash = fts5HashKey(pHash->nSlot, (const u8*)pTerm, nTerm);
+ char *zKey = 0;
Fts5HashEntry *p;
for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
- if( memcmp(p->zKey, pTerm, nTerm)==0 && p->zKey[nTerm]==0 ) break;
+ zKey = fts5EntryKey(p);
+ assert( p->nKey+1==(int)strlen(zKey) );
+ if( nTerm==p->nKey+1 && memcmp(zKey, pTerm, nTerm)==0 ) break;
}
if( p ){
- fts5HashAddPoslistSize(pHash, p);
- *ppDoclist = (const u8*)&p->zKey[nTerm+1];
- *pnDoclist = p->nData - (FTS5_HASHENTRYSIZE + nTerm + 1);
+ int nHashPre = sizeof(Fts5HashEntry) + nTerm + 1;
+ int nList = p->nData - nHashPre;
+ u8 *pRet = (u8*)(*ppOut = tdsqlite3_malloc64(nPre + nList + 10));
+ if( pRet ){
+ Fts5HashEntry *pFaux = (Fts5HashEntry*)&pRet[nPre-nHashPre];
+ memcpy(&pRet[nPre], &((u8*)p)[nHashPre], nList);
+ nList += fts5HashAddPoslistSize(pHash, p, pFaux);
+ *pnDoclist = nList;
+ }else{
+ *pnDoclist = 0;
+ return SQLITE_NOMEM;
+ }
}else{
- *ppDoclist = 0;
+ *ppOut = 0;
*pnDoclist = 0;
}
return SQLITE_OK;
}
-static int sqlite3Fts5HashScanInit(
+static int tdsqlite3Fts5HashScanInit(
Fts5Hash *p, /* Hash table to query */
const char *pTerm, int nTerm /* Query prefix */
){
return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan);
}
-static void sqlite3Fts5HashScanNext(Fts5Hash *p){
- assert( !sqlite3Fts5HashScanEof(p) );
+static void tdsqlite3Fts5HashScanNext(Fts5Hash *p){
+ assert( !tdsqlite3Fts5HashScanEof(p) );
p->pScan = p->pScan->pScanNext;
}
-static int sqlite3Fts5HashScanEof(Fts5Hash *p){
+static int tdsqlite3Fts5HashScanEof(Fts5Hash *p){
return (p->pScan==0);
}
-static void sqlite3Fts5HashScanEntry(
+static void tdsqlite3Fts5HashScanEntry(
Fts5Hash *pHash,
const char **pzTerm, /* OUT: term (nul-terminated) */
const u8 **ppDoclist, /* OUT: pointer to doclist */
@@ -189653,11 +217917,12 @@ static void sqlite3Fts5HashScanEntry(
){
Fts5HashEntry *p;
if( (p = pHash->pScan) ){
- int nTerm = (int)strlen(p->zKey);
- fts5HashAddPoslistSize(pHash, p);
- *pzTerm = p->zKey;
- *ppDoclist = (const u8*)&p->zKey[nTerm+1];
- *pnDoclist = p->nData - (FTS5_HASHENTRYSIZE + nTerm + 1);
+ char *zKey = fts5EntryKey(p);
+ int nTerm = (int)strlen(zKey);
+ fts5HashAddPoslistSize(pHash, p, 0);
+ *pzTerm = zKey;
+ *ppDoclist = (const u8*)&zKey[nTerm+1];
+ *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm + 1);
}else{
*pzTerm = 0;
*ppDoclist = 0;
@@ -189665,7 +217930,6 @@ static void sqlite3Fts5HashScanEntry(
}
}
-
/*
** 2014 May 31
**
@@ -189907,13 +218171,8 @@ static void sqlite3Fts5HashScanEntry(
#define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0, 0, pgno)
#define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1, height, pgno)
-/*
-** Maximum segments permitted in a single index
-*/
-#define FTS5_MAX_SEGMENT 2000
-
#ifdef SQLITE_DEBUG
-static int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; }
+static int tdsqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; }
#endif
@@ -189965,15 +218224,15 @@ struct Fts5Index {
int rc; /* Current error code */
/* State used by the fts5DataXXX() functions. */
- sqlite3_blob *pReader; /* RO incr-blob open on %_data table */
- sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */
- sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */
- sqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */
- sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */
- sqlite3_stmt *pIdxSelect;
+ tdsqlite3_blob *pReader; /* RO incr-blob open on %_data table */
+ tdsqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */
+ tdsqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */
+ tdsqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */
+ tdsqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */
+ tdsqlite3_stmt *pIdxSelect;
int nRead; /* Total number of blocks read */
- sqlite3_stmt *pDataVersion;
+ tdsqlite3_stmt *pDataVersion;
i64 iStructVersion; /* data_version when pStruct read */
Fts5Structure *pStruct; /* Current db structure (or NULL) */
};
@@ -190173,14 +218432,13 @@ struct Fts5SegIter {
** the smallest key overall. aFirst[0] is unused.
**
** poslist:
-** Used by sqlite3Fts5IterPoslist() when the poslist needs to be buffered.
+** Used by tdsqlite3Fts5IterPoslist() when the poslist needs to be buffered.
** There is no way to tell if this is populated or not.
*/
struct Fts5Iter {
Fts5IndexIter base; /* Base class containing output vars */
Fts5Index *pIndex; /* Index that owns this iterator */
- Fts5Structure *pStruct; /* Database structure for this iterator */
Fts5Buffer poslist; /* Buffer containing current poslist */
Fts5Colset *pColset; /* Restrict matches to these columns */
@@ -190241,8 +218499,8 @@ static u16 fts5GetU16(const u8 *aIn){
** If an OOM error is encountered, return NULL and set the error code in
** the Fts5Index handle passed as the first argument.
*/
-static void *fts5IdxMalloc(Fts5Index *p, int nByte){
- return sqlite3Fts5MallocZero(&p->rc, nByte);
+static void *fts5IdxMalloc(Fts5Index *p, tdsqlite3_int64 nByte){
+ return tdsqlite3Fts5MallocZero(&p->rc, nByte);
}
/*
@@ -190275,7 +218533,7 @@ static int fts5BufferCompareBlob(
*/
static int fts5BufferCompare(Fts5Buffer *pLeft, Fts5Buffer *pRight){
int nCmp = MIN(pLeft->n, pRight->n);
- int res = memcmp(pLeft->p, pRight->p, nCmp);
+ int res = fts5Memcmp(pLeft->p, pRight->p, nCmp);
return (res==0 ? (pLeft->n - pRight->n) : res);
}
@@ -190288,15 +218546,14 @@ static int fts5LeafFirstTermOff(Fts5Data *pLeaf){
/*
** Close the read-only blob handle, if it is open.
*/
-static void fts5CloseReader(Fts5Index *p){
+static void tdsqlite3Fts5IndexCloseReader(Fts5Index *p){
if( p->pReader ){
- sqlite3_blob *pReader = p->pReader;
+ tdsqlite3_blob *pReader = p->pReader;
p->pReader = 0;
- sqlite3_blob_close(pReader);
+ tdsqlite3_blob_close(pReader);
}
}
-
/*
** Retrieve a record from the %_data table.
**
@@ -190312,13 +218569,13 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){
/* This call may return SQLITE_ABORT if there has been a savepoint
** rollback since it was last used. In this case a new blob handle
** is required. */
- sqlite3_blob *pBlob = p->pReader;
+ tdsqlite3_blob *pBlob = p->pReader;
p->pReader = 0;
- rc = sqlite3_blob_reopen(pBlob, iRowid);
+ rc = tdsqlite3_blob_reopen(pBlob, iRowid);
assert( p->pReader==0 );
p->pReader = pBlob;
if( rc!=SQLITE_OK ){
- fts5CloseReader(p);
+ tdsqlite3Fts5IndexCloseReader(p);
}
if( rc==SQLITE_ABORT ) rc = SQLITE_OK;
}
@@ -190327,12 +218584,12 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){
** to the requested entry. */
if( p->pReader==0 && rc==SQLITE_OK ){
Fts5Config *pConfig = p->pConfig;
- rc = sqlite3_blob_open(pConfig->db,
+ rc = tdsqlite3_blob_open(pConfig->db,
pConfig->zDb, p->zDataTbl, "block", iRowid, 0, &p->pReader
);
}
- /* If either of the sqlite3_blob_open() or sqlite3_blob_reopen() calls
+ /* If either of the tdsqlite3_blob_open() or tdsqlite3_blob_reopen() calls
** above returned SQLITE_ERROR, return SQLITE_CORRUPT_VTAB instead.
** All the reasons those functions might return SQLITE_ERROR - missing
** table, missing row, non-blob/text in block column - indicate
@@ -190341,9 +218598,9 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){
if( rc==SQLITE_OK ){
u8 *aOut = 0; /* Read blob data into this buffer */
- int nByte = sqlite3_blob_bytes(p->pReader);
- int nAlloc = sizeof(Fts5Data) + nByte + FTS5_DATA_PADDING;
- pRet = (Fts5Data*)sqlite3_malloc(nAlloc);
+ int nByte = tdsqlite3_blob_bytes(p->pReader);
+ tdsqlite3_int64 nAlloc = sizeof(Fts5Data) + nByte + FTS5_DATA_PADDING;
+ pRet = (Fts5Data*)tdsqlite3_malloc64(nAlloc);
if( pRet ){
pRet->nn = nByte;
aOut = pRet->p = (u8*)&pRet[1];
@@ -190352,13 +218609,15 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){
}
if( rc==SQLITE_OK ){
- rc = sqlite3_blob_read(p->pReader, aOut, nByte, 0);
+ rc = tdsqlite3_blob_read(p->pReader, aOut, nByte, 0);
}
if( rc!=SQLITE_OK ){
- sqlite3_free(pRet);
+ tdsqlite3_free(pRet);
pRet = 0;
}else{
/* TODO1: Fix this */
+ pRet->p[nByte] = 0x00;
+ pRet->p[nByte+1] = 0x00;
pRet->szLeaf = fts5GetU16(&pRet->p[2]);
}
}
@@ -190375,13 +218634,13 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){
** fts5DataRead().
*/
static void fts5DataRelease(Fts5Data *pData){
- sqlite3_free(pData);
+ tdsqlite3_free(pData);
}
static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){
Fts5Data *pRet = fts5DataRead(p, iRowid);
if( pRet ){
- if( pRet->szLeaf>pRet->nn ){
+ if( pRet->nn<4 || pRet->szLeaf>pRet->nn ){
p->rc = FTS5_CORRUPT;
fts5DataRelease(pRet);
pRet = 0;
@@ -190392,17 +218651,19 @@ static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){
static int fts5IndexPrepareStmt(
Fts5Index *p,
- sqlite3_stmt **ppStmt,
+ tdsqlite3_stmt **ppStmt,
char *zSql
){
if( p->rc==SQLITE_OK ){
if( zSql ){
- p->rc = sqlite3_prepare_v2(p->pConfig->db, zSql, -1, ppStmt, 0);
+ p->rc = tdsqlite3_prepare_v3(p->pConfig->db, zSql, -1,
+ SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB,
+ ppStmt, 0);
}else{
p->rc = SQLITE_NOMEM;
}
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
return p->rc;
}
@@ -190415,17 +218676,18 @@ static void fts5DataWrite(Fts5Index *p, i64 iRowid, const u8 *pData, int nData){
if( p->pWriter==0 ){
Fts5Config *pConfig = p->pConfig;
- fts5IndexPrepareStmt(p, &p->pWriter, sqlite3_mprintf(
+ fts5IndexPrepareStmt(p, &p->pWriter, tdsqlite3_mprintf(
"REPLACE INTO '%q'.'%q_data'(id, block) VALUES(?,?)",
pConfig->zDb, pConfig->zName
));
if( p->rc ) return;
}
- sqlite3_bind_int64(p->pWriter, 1, iRowid);
- sqlite3_bind_blob(p->pWriter, 2, pData, nData, SQLITE_STATIC);
- sqlite3_step(p->pWriter);
- p->rc = sqlite3_reset(p->pWriter);
+ tdsqlite3_bind_int64(p->pWriter, 1, iRowid);
+ tdsqlite3_bind_blob(p->pWriter, 2, pData, nData, SQLITE_STATIC);
+ tdsqlite3_step(p->pWriter);
+ p->rc = tdsqlite3_reset(p->pWriter);
+ tdsqlite3_bind_null(p->pWriter, 2);
}
/*
@@ -190437,28 +218699,18 @@ static void fts5DataDelete(Fts5Index *p, i64 iFirst, i64 iLast){
if( p->rc!=SQLITE_OK ) return;
if( p->pDeleter==0 ){
- int rc;
Fts5Config *pConfig = p->pConfig;
- char *zSql = sqlite3_mprintf(
+ char *zSql = tdsqlite3_mprintf(
"DELETE FROM '%q'.'%q_data' WHERE id>=? AND id<=?",
pConfig->zDb, pConfig->zName
);
- if( zSql==0 ){
- rc = SQLITE_NOMEM;
- }else{
- rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p->pDeleter, 0);
- sqlite3_free(zSql);
- }
- if( rc!=SQLITE_OK ){
- p->rc = rc;
- return;
- }
+ if( fts5IndexPrepareStmt(p, &p->pDeleter, zSql) ) return;
}
- sqlite3_bind_int64(p->pDeleter, 1, iFirst);
- sqlite3_bind_int64(p->pDeleter, 2, iLast);
- sqlite3_step(p->pDeleter);
- p->rc = sqlite3_reset(p->pDeleter);
+ tdsqlite3_bind_int64(p->pDeleter, 1, iFirst);
+ tdsqlite3_bind_int64(p->pDeleter, 2, iLast);
+ tdsqlite3_step(p->pDeleter);
+ p->rc = tdsqlite3_reset(p->pDeleter);
}
/*
@@ -190470,15 +218722,15 @@ static void fts5DataRemoveSegment(Fts5Index *p, int iSegid){
fts5DataDelete(p, iFirst, iLast);
if( p->pIdxDeleter==0 ){
Fts5Config *pConfig = p->pConfig;
- fts5IndexPrepareStmt(p, &p->pIdxDeleter, sqlite3_mprintf(
+ fts5IndexPrepareStmt(p, &p->pIdxDeleter, tdsqlite3_mprintf(
"DELETE FROM '%q'.'%q_idx' WHERE segid=?",
pConfig->zDb, pConfig->zName
));
}
if( p->rc==SQLITE_OK ){
- sqlite3_bind_int(p->pIdxDeleter, 1, iSegid);
- sqlite3_step(p->pIdxDeleter);
- p->rc = sqlite3_reset(p->pIdxDeleter);
+ tdsqlite3_bind_int(p->pIdxDeleter, 1, iSegid);
+ tdsqlite3_step(p->pIdxDeleter);
+ p->rc = tdsqlite3_reset(p->pIdxDeleter);
}
}
@@ -190491,9 +218743,9 @@ static void fts5StructureRelease(Fts5Structure *pStruct){
int i;
assert( pStruct->nRef==0 );
for(i=0; i<pStruct->nLevel; i++){
- sqlite3_free(pStruct->aLevel[i].aSeg);
+ tdsqlite3_free(pStruct->aLevel[i].aSeg);
}
- sqlite3_free(pStruct);
+ tdsqlite3_free(pStruct);
}
}
@@ -190524,28 +218776,33 @@ static int fts5StructureDecode(
int iLvl;
int nLevel = 0;
int nSegment = 0;
- int nByte; /* Bytes of space to allocate at pRet */
+ tdsqlite3_int64 nByte; /* Bytes of space to allocate at pRet */
Fts5Structure *pRet = 0; /* Structure object to return */
/* Grab the cookie value */
- if( piCookie ) *piCookie = sqlite3Fts5Get32(pData);
+ if( piCookie ) *piCookie = tdsqlite3Fts5Get32(pData);
i = 4;
/* Read the total number of levels and segments from the start of the
** structure record. */
i += fts5GetVarint32(&pData[i], nLevel);
i += fts5GetVarint32(&pData[i], nSegment);
+ if( nLevel>FTS5_MAX_SEGMENT || nLevel<0
+ || nSegment>FTS5_MAX_SEGMENT || nSegment<0
+ ){
+ return FTS5_CORRUPT;
+ }
nByte = (
sizeof(Fts5Structure) + /* Main structure */
sizeof(Fts5StructureLevel) * (nLevel-1) /* aLevel[] array */
);
- pRet = (Fts5Structure*)sqlite3Fts5MallocZero(&rc, nByte);
+ pRet = (Fts5Structure*)tdsqlite3Fts5MallocZero(&rc, nByte);
if( pRet ){
pRet->nRef = 1;
pRet->nLevel = nLevel;
pRet->nSegment = nSegment;
- i += sqlite3Fts5GetVarint(&pData[i], &pRet->nWriteCounter);
+ i += tdsqlite3Fts5GetVarint(&pData[i], &pRet->nWriteCounter);
for(iLvl=0; rc==SQLITE_OK && iLvl<nLevel; iLvl++){
Fts5StructureLevel *pLvl = &pRet->aLevel[iLvl];
@@ -190557,25 +218814,35 @@ static int fts5StructureDecode(
}else{
i += fts5GetVarint32(&pData[i], pLvl->nMerge);
i += fts5GetVarint32(&pData[i], nTotal);
- assert( nTotal>=pLvl->nMerge );
- pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc,
+ if( nTotal<pLvl->nMerge ) rc = FTS5_CORRUPT;
+ pLvl->aSeg = (Fts5StructureSegment*)tdsqlite3Fts5MallocZero(&rc,
nTotal * sizeof(Fts5StructureSegment)
);
+ nSegment -= nTotal;
}
if( rc==SQLITE_OK ){
pLvl->nSeg = nTotal;
for(iSeg=0; iSeg<nTotal; iSeg++){
+ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
if( i>=nData ){
rc = FTS5_CORRUPT;
break;
}
- i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].iSegid);
- i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].pgnoFirst);
- i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].pgnoLast);
+ i += fts5GetVarint32(&pData[i], pSeg->iSegid);
+ i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst);
+ i += fts5GetVarint32(&pData[i], pSeg->pgnoLast);
+ if( pSeg->pgnoLast<pSeg->pgnoFirst ){
+ rc = FTS5_CORRUPT;
+ break;
+ }
}
+ if( iLvl>0 && pLvl[-1].nMerge && nTotal==0 ) rc = FTS5_CORRUPT;
+ if( iLvl==nLevel-1 && pLvl->nMerge ) rc = FTS5_CORRUPT;
}
}
+ if( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT;
+
if( rc!=SQLITE_OK ){
fts5StructureRelease(pRet);
pRet = 0;
@@ -190593,12 +218860,12 @@ static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){
if( *pRc==SQLITE_OK ){
Fts5Structure *pStruct = *ppStruct;
int nLevel = pStruct->nLevel;
- int nByte = (
+ tdsqlite3_int64 nByte = (
sizeof(Fts5Structure) + /* Main structure */
sizeof(Fts5StructureLevel) * (nLevel+1) /* aLevel[] array */
);
- pStruct = sqlite3_realloc(pStruct, nByte);
+ pStruct = tdsqlite3_realloc64(pStruct, nByte);
if( pStruct ){
memset(&pStruct->aLevel[nLevel], 0, sizeof(Fts5StructureLevel));
pStruct->nLevel++;
@@ -190623,10 +218890,10 @@ static void fts5StructureExtendLevel(
if( *pRc==SQLITE_OK ){
Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
Fts5StructureSegment *aNew;
- int nByte;
+ tdsqlite3_int64 nByte;
nByte = (pLvl->nSeg + nExtra) * sizeof(Fts5StructureSegment);
- aNew = sqlite3_realloc(pLvl->aSeg, nByte);
+ aNew = tdsqlite3_realloc64(pLvl->aSeg, nByte);
if( aNew ){
if( bInsert==0 ){
memset(&aNew[pLvl->nSeg], 0, sizeof(Fts5StructureSegment) * nExtra);
@@ -190653,8 +218920,8 @@ static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){
/* TODO: Do we need this if the leaf-index is appended? Probably... */
memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING);
p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet);
- if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){
- p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
+ if( p->rc==SQLITE_OK && (pConfig->pgsz==0 || pConfig->iCookie!=iCookie) ){
+ p->rc = tdsqlite3Fts5ConfigLoad(pConfig, iCookie);
}
fts5DataRelease(pData);
if( p->rc!=SQLITE_OK ){
@@ -190672,15 +218939,15 @@ static i64 fts5IndexDataVersion(Fts5Index *p){
if( p->rc==SQLITE_OK ){
if( p->pDataVersion==0 ){
p->rc = fts5IndexPrepareStmt(p, &p->pDataVersion,
- sqlite3_mprintf("PRAGMA %Q.data_version", p->pConfig->zDb)
+ tdsqlite3_mprintf("PRAGMA %Q.data_version", p->pConfig->zDb)
);
if( p->rc ) return 0;
}
- if( SQLITE_ROW==sqlite3_step(p->pDataVersion) ){
- iVersion = sqlite3_column_int64(p->pDataVersion, 0);
+ if( SQLITE_ROW==tdsqlite3_step(p->pDataVersion) ){
+ iVersion = tdsqlite3_column_int64(p->pDataVersion, 0);
}
- p->rc = sqlite3_reset(p->pDataVersion);
+ p->rc = tdsqlite3_reset(p->pDataVersion);
}
return iVersion;
@@ -190768,7 +219035,7 @@ static int fts5StructureCountSegments(Fts5Structure *pStruct){
}
#define fts5BufferSafeAppendVarint(pBuf, iVal) { \
- (pBuf)->n += sqlite3Fts5PutVarint(&(pBuf)->p[(pBuf)->n], (iVal)); \
+ (pBuf)->n += tdsqlite3Fts5PutVarint(&(pBuf)->p[(pBuf)->n], (iVal)); \
assert( (pBuf)->nSpace>=(pBuf)->n ); \
}
@@ -190792,8 +219059,8 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){
iCookie = p->pConfig->iCookie;
if( iCookie<0 ) iCookie = 0;
- if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, 4+9+9+9) ){
- sqlite3Fts5Put32(buf.p, iCookie);
+ if( 0==tdsqlite3Fts5BufferSize(&p->rc, &buf, 4+9+9+9) ){
+ tdsqlite3Fts5Put32(buf.p, iCookie);
buf.n = 4;
fts5BufferSafeAppendVarint(&buf, pStruct->nLevel);
fts5BufferSafeAppendVarint(&buf, pStruct->nSegment);
@@ -191125,7 +219392,7 @@ static void fts5DlidxIterFree(Fts5DlidxIter *pIter){
for(i=0; i<pIter->nLvl; i++){
fts5DataRelease(pIter->aLvl[i].pData);
}
- sqlite3_free(pIter);
+ tdsqlite3_free(pIter);
}
}
@@ -191140,10 +219407,10 @@ static Fts5DlidxIter *fts5DlidxIterInit(
int bDone = 0;
for(i=0; p->rc==SQLITE_OK && bDone==0; i++){
- int nByte = sizeof(Fts5DlidxIter) + i * sizeof(Fts5DlidxLvl);
+ tdsqlite3_int64 nByte = sizeof(Fts5DlidxIter) + i * sizeof(Fts5DlidxLvl);
Fts5DlidxIter *pNew;
- pNew = (Fts5DlidxIter*)sqlite3_realloc(pIter, nByte);
+ pNew = (Fts5DlidxIter*)tdsqlite3_realloc64(pIter, nByte);
if( pNew==0 ){
p->rc = SQLITE_NOMEM;
}else{
@@ -191288,7 +219555,7 @@ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){
iOff = 4;
a = pIter->pLeaf->p;
}
- iOff += sqlite3Fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
+ iOff += tdsqlite3Fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid);
pIter->iLeafOffset = iOff;
}
@@ -191313,12 +219580,13 @@ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){
int nNew; /* Bytes of new data */
iOff += fts5GetVarint32(&a[iOff], nNew);
- if( iOff+nNew>pIter->pLeaf->nn ){
+ if( iOff+nNew>pIter->pLeaf->szLeaf || nKeep>pIter->term.n || nNew==0 ){
p->rc = FTS5_CORRUPT;
return;
}
pIter->term.n = nKeep;
fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]);
+ assert( pIter->term.n<=pIter->term.nSpace );
iOff += nNew;
pIter->iTermLeafOffset = iOff;
pIter->iTermLeafPgno = pIter->iLeafPgno;
@@ -191383,7 +219651,7 @@ static void fts5SegIterInit(
if( p->rc==SQLITE_OK ){
pIter->iLeafOffset = 4;
assert_nc( pIter->pLeaf->nn>4 );
- assert( fts5LeafFirstTermOff(pIter->pLeaf)==4 );
+ assert_nc( fts5LeafFirstTermOff(pIter->pLeaf)==4 );
pIter->iPgidxOff = pIter->pLeaf->szLeaf+1;
fts5SegIterLoadTerm(p, pIter, 0);
fts5SegIterLoadNPos(p, pIter);
@@ -191439,7 +219707,7 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){
/* If necessary, grow the pIter->aRowidOffset[] array. */
if( iRowidOffset>=pIter->nRowidOffset ){
int nNew = pIter->nRowidOffset + 8;
- int *aNew = (int*)sqlite3_realloc(pIter->aRowidOffset, nNew*sizeof(int));
+ int *aNew = (int*)tdsqlite3_realloc64(pIter->aRowidOffset,nNew*sizeof(int));
if( aNew==0 ){
p->rc = SQLITE_NOMEM;
break;
@@ -191579,7 +219847,7 @@ static void fts5SegIterNext_None(
if( iOff<pIter->iEndofDoclist ){
/* Next entry is on the current page */
i64 iDelta;
- iOff += sqlite3Fts5GetVarint(&pIter->pLeaf->p[iOff], (u64*)&iDelta);
+ iOff += tdsqlite3Fts5GetVarint(&pIter->pLeaf->p[iOff], (u64*)&iDelta);
pIter->iLeafOffset = iOff;
pIter->iRowid += iDelta;
}else if( (pIter->flags & FTS5_SEGITER_ONETERM)==0 ){
@@ -191594,14 +219862,14 @@ static void fts5SegIterNext_None(
const u8 *pList = 0;
const char *zTerm = 0;
int nList;
- sqlite3Fts5HashScanNext(p->pHash);
- sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList);
+ tdsqlite3Fts5HashScanNext(p->pHash);
+ tdsqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList);
if( pList==0 ) goto next_none_eof;
pIter->pLeaf->p = (u8*)pList;
pIter->pLeaf->nn = nList;
pIter->pLeaf->szLeaf = nList;
pIter->iEndofDoclist = nList;
- sqlite3Fts5BufferSet(&p->rc,&pIter->term, (int)strlen(zTerm), (u8*)zTerm);
+ tdsqlite3Fts5BufferSet(&p->rc,&pIter->term, (int)strlen(zTerm), (u8*)zTerm);
pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid);
}
@@ -191658,7 +219926,7 @@ static void fts5SegIterNext(
}
}else{
u64 iDelta;
- iOff += sqlite3Fts5GetVarint(&a[iOff], &iDelta);
+ iOff += tdsqlite3Fts5GetVarint(&a[iOff], &iDelta);
pIter->iRowid += iDelta;
assert_nc( iDelta>0 );
}
@@ -191670,8 +219938,8 @@ static void fts5SegIterNext(
int nList = 0;
assert( (pIter->flags & FTS5_SEGITER_ONETERM) || pbNewTerm );
if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){
- sqlite3Fts5HashScanNext(p->pHash);
- sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList);
+ tdsqlite3Fts5HashScanNext(p->pHash);
+ tdsqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList);
}
if( pList==0 ){
fts5DataRelease(pIter->pLeaf);
@@ -191681,7 +219949,7 @@ static void fts5SegIterNext(
pIter->pLeaf->nn = nList;
pIter->pLeaf->szLeaf = nList;
pIter->iEndofDoclist = nList+1;
- sqlite3Fts5BufferSet(&p->rc, &pIter->term, (int)strlen(zTerm),
+ tdsqlite3Fts5BufferSet(&p->rc, &pIter->term, (int)strlen(zTerm),
(u8*)zTerm);
pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid);
*pbNewTerm = 1;
@@ -191695,7 +219963,7 @@ static void fts5SegIterNext(
if( pLeaf==0 ) break;
ASSERT_SZLEAF_OK(pLeaf);
if( (iOff = fts5LeafFirstRowidOff(pLeaf)) && iOff<pLeaf->szLeaf ){
- iOff += sqlite3Fts5GetVarint(&pLeaf->p[iOff], (u64*)&pIter->iRowid);
+ iOff += tdsqlite3Fts5GetVarint(&pLeaf->p[iOff], (u64*)&pIter->iRowid);
pIter->iLeafOffset = iOff;
if( pLeaf->nn>pLeaf->szLeaf ){
@@ -191707,7 +219975,7 @@ static void fts5SegIterNext(
else if( pLeaf->nn>pLeaf->szLeaf ){
pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32(
&pLeaf->p[pLeaf->szLeaf], iOff
- );
+ );
pIter->iLeafOffset = iOff;
pIter->iEndofDoclist = iOff;
bNewTerm = 1;
@@ -191741,6 +220009,7 @@ static void fts5SegIterNext(
*/
int nSz;
assert( p->rc==SQLITE_OK );
+ assert( pIter->iLeafOffset<=pIter->pLeaf->nn );
fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz);
pIter->bDel = (nSz & 0x0001);
pIter->nPos = nSz>>1;
@@ -191892,10 +220161,10 @@ static void fts5LeafSeek(
int szLeaf = pIter->pLeaf->szLeaf;
int n = pIter->pLeaf->nn;
- int nMatch = 0;
- int nKeep = 0;
- int nNew = 0;
- int iTermOff;
+ u32 nMatch = 0;
+ u32 nKeep = 0;
+ u32 nNew = 0;
+ u32 iTermOff;
int iPgidx; /* Current offset in pgidx */
int bEndOfPage = 0;
@@ -191919,15 +220188,15 @@ static void fts5LeafSeek(
assert( nKeep>=nMatch );
if( nKeep==nMatch ){
- int nCmp;
- int i;
- nCmp = MIN(nNew, nTerm-nMatch);
+ u32 nCmp;
+ u32 i;
+ nCmp = (u32)MIN(nNew, nTerm-nMatch);
for(i=0; i<nCmp; i++){
if( a[iOff+i]!=pTerm[nMatch+i] ) break;
}
nMatch += i;
- if( nTerm==nMatch ){
+ if( (u32)nTerm==nMatch ){
if( i==nNew ){
goto search_success;
}else{
@@ -191971,6 +220240,7 @@ static void fts5LeafSeek(
iPgidx += fts5GetVarint32(&pIter->pLeaf->p[iPgidx], iOff);
if( iOff<4 || iOff>=pIter->pLeaf->szLeaf ){
p->rc = FTS5_CORRUPT;
+ return;
}else{
nKeep = 0;
iTermOff = iOff;
@@ -191983,8 +220253,11 @@ static void fts5LeafSeek(
}
search_success:
-
pIter->iLeafOffset = iOff + nNew;
+ if( pIter->iLeafOffset>n || nNew<1 ){
+ p->rc = FTS5_CORRUPT;
+ return;
+ }
pIter->iTermLeafOffset = pIter->iLeafOffset;
pIter->iTermLeafPgno = pIter->iLeafPgno;
@@ -192004,10 +220277,10 @@ static void fts5LeafSeek(
fts5SegIterLoadNPos(p, pIter);
}
-static sqlite3_stmt *fts5IdxSelectStmt(Fts5Index *p){
+static tdsqlite3_stmt *fts5IdxSelectStmt(Fts5Index *p){
if( p->pIdxSelect==0 ){
Fts5Config *pConfig = p->pConfig;
- fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf(
+ fts5IndexPrepareStmt(p, &p->pIdxSelect, tdsqlite3_mprintf(
"SELECT pgno FROM '%q'.'%q_idx' WHERE "
"segid=? AND term<=? ORDER BY term DESC LIMIT 1",
pConfig->zDb, pConfig->zName
@@ -192033,7 +220306,7 @@ static void fts5SegIterSeekInit(
int iPg = 1;
int bGe = (flags & FTS5INDEX_QUERY_SCAN);
int bDlidx = 0; /* True if there is a doclist-index */
- sqlite3_stmt *pIdxSelect = 0;
+ tdsqlite3_stmt *pIdxSelect = 0;
assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 );
assert( pTerm && nTerm );
@@ -192044,14 +220317,15 @@ static void fts5SegIterSeekInit(
** contain term (pTerm/nTerm), if it is present in the segment. */
pIdxSelect = fts5IdxSelectStmt(p);
if( p->rc ) return;
- sqlite3_bind_int(pIdxSelect, 1, pSeg->iSegid);
- sqlite3_bind_blob(pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC);
- if( SQLITE_ROW==sqlite3_step(pIdxSelect) ){
- i64 val = sqlite3_column_int(pIdxSelect, 0);
+ tdsqlite3_bind_int(pIdxSelect, 1, pSeg->iSegid);
+ tdsqlite3_bind_blob(pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC);
+ if( SQLITE_ROW==tdsqlite3_step(pIdxSelect) ){
+ i64 val = tdsqlite3_column_int(pIdxSelect, 0);
iPg = (int)(val>>1);
bDlidx = (val & 0x0001);
}
- p->rc = sqlite3_reset(pIdxSelect);
+ p->rc = tdsqlite3_reset(pIdxSelect);
+ tdsqlite3_bind_null(pIdxSelect, 2);
if( iPg<pSeg->pgnoFirst ){
iPg = pSeg->pgnoFirst;
@@ -192090,7 +220364,7 @@ static void fts5SegIterSeekInit(
** 4) the FTS5INDEX_QUERY_SCAN flag was set and the iterator points
** to an entry with a term greater than or equal to (pTerm/nTerm).
*/
- assert( p->rc!=SQLITE_OK /* 1 */
+ assert_nc( p->rc!=SQLITE_OK /* 1 */
|| pIter->pLeaf==0 /* 2 */
|| fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)==0 /* 3 */
|| (bGe && fts5BufferCompareBlob(&pIter->term, pTerm, nTerm)>0) /* 4 */
@@ -192111,31 +220385,40 @@ static void fts5SegIterHashInit(
int flags, /* Mask of FTS5INDEX_XXX flags */
Fts5SegIter *pIter /* Object to populate */
){
- const u8 *pList = 0;
int nList = 0;
const u8 *z = 0;
int n = 0;
+ Fts5Data *pLeaf = 0;
assert( p->pHash );
assert( p->rc==SQLITE_OK );
if( pTerm==0 || (flags & FTS5INDEX_QUERY_SCAN) ){
- p->rc = sqlite3Fts5HashScanInit(p->pHash, (const char*)pTerm, nTerm);
- sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &pList, &nList);
+ const u8 *pList = 0;
+
+ p->rc = tdsqlite3Fts5HashScanInit(p->pHash, (const char*)pTerm, nTerm);
+ tdsqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &pList, &nList);
n = (z ? (int)strlen((const char*)z) : 0);
+ if( pList ){
+ pLeaf = fts5IdxMalloc(p, sizeof(Fts5Data));
+ if( pLeaf ){
+ pLeaf->p = (u8*)pList;
+ }
+ }
}else{
- pIter->flags |= FTS5_SEGITER_ONETERM;
- sqlite3Fts5HashQuery(p->pHash, (const char*)pTerm, nTerm, &pList, &nList);
+ p->rc = tdsqlite3Fts5HashQuery(p->pHash, sizeof(Fts5Data),
+ (const char*)pTerm, nTerm, (void**)&pLeaf, &nList
+ );
+ if( pLeaf ){
+ pLeaf->p = (u8*)&pLeaf[1];
+ }
z = pTerm;
n = nTerm;
+ pIter->flags |= FTS5_SEGITER_ONETERM;
}
- if( pList ){
- Fts5Data *pLeaf;
- sqlite3Fts5BufferSet(&p->rc, &pIter->term, n, z);
- pLeaf = fts5IdxMalloc(p, sizeof(Fts5Data));
- if( pLeaf==0 ) return;
- pLeaf->p = (u8*)pList;
+ if( pLeaf ){
+ tdsqlite3Fts5BufferSet(&p->rc, &pIter->term, n, z);
pLeaf->nn = pLeaf->szLeaf = nList;
pIter->pLeaf = pLeaf;
pIter->iLeafOffset = fts5GetVarint(pLeaf->p, (u64*)&pIter->iRowid);
@@ -192160,7 +220443,7 @@ static void fts5SegIterClear(Fts5SegIter *pIter){
fts5DataRelease(pIter->pLeaf);
fts5DataRelease(pIter->pNextLeaf);
fts5DlidxIterFree(pIter->pDlidx);
- sqlite3_free(pIter->aRowidOffset);
+ tdsqlite3_free(pIter->aRowidOffset);
memset(pIter, 0, sizeof(Fts5SegIter));
}
@@ -192188,7 +220471,7 @@ static void fts5AssertComparisonResult(
assert( pRes->iFirst==i1 );
}else{
int nMin = MIN(p1->term.n, p2->term.n);
- int res = memcmp(p1->term.p, p2->term.p, nMin);
+ int res = fts5Memcmp(p1->term.p, p2->term.p, nMin);
if( res==0 ) res = p1->term.n - p2->term.n;
if( res==0 ){
@@ -192288,8 +220571,8 @@ static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){
}else{
int res = fts5BufferCompare(&p1->term, &p2->term);
if( res==0 ){
- assert( i2>i1 );
- assert( i2!=0 );
+ assert_nc( i2>i1 );
+ assert_nc( i2!=0 );
pRes->bTermEq = 1;
if( p1->iRowid==p2->iRowid ){
p1->bDel = p2->bDel;
@@ -192411,9 +220694,8 @@ static void fts5MultiIterFree(Fts5Iter *pIter){
for(i=0; i<pIter->nSeg; i++){
fts5SegIterClear(&pIter->aSeg[i]);
}
- fts5StructureRelease(pIter->pStruct);
fts5BufferFree(&pIter->poslist);
- sqlite3_free(pIter);
+ tdsqlite3_free(pIter);
}
}
@@ -192546,7 +220828,8 @@ static void fts5MultiIterNext2(
){
assert( pIter->bSkipEmpty );
if( p->rc==SQLITE_OK ){
- do {
+ *pbNewTerm = 0;
+ do{
int iFirst = pIter->aFirst[1].iFirst;
Fts5SegIter *pSeg = &pIter->aSeg[iFirst];
int bNewTerm = 0;
@@ -192559,8 +220842,6 @@ static void fts5MultiIterNext2(
fts5MultiIterAdvanced(p, pIter, iFirst, 1);
fts5MultiIterSetEof(pIter);
*pbNewTerm = 1;
- }else{
- *pbNewTerm = 0;
}
fts5AssertMultiIterSetup(p, pIter);
@@ -192735,7 +221016,7 @@ static void fts5ChunkIterate(
break;
}else{
pgno++;
- pData = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno));
+ pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno));
if( pData==0 ) break;
pChunk = &pData->p[4];
nChunk = MIN(nRem, pData->szLeaf - 4);
@@ -192760,7 +221041,8 @@ static void fts5SegiterPoslist(
Fts5Colset *pColset,
Fts5Buffer *pBuf
){
- if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos) ){
+ if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos+FTS5_DATA_ZERO_PADDING) ){
+ memset(&pBuf->p[pBuf->n+pSeg->nPos], 0, FTS5_DATA_ZERO_PADDING);
if( pColset==0 ){
fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback);
}else{
@@ -192826,23 +221108,23 @@ static int fts5IndexExtractCol(
return p - (*pa);
}
-static int fts5IndexExtractColset (
+static void fts5IndexExtractColset(
+ int *pRc,
Fts5Colset *pColset, /* Colset to filter on */
const u8 *pPos, int nPos, /* Position list */
Fts5Buffer *pBuf /* Output buffer */
){
- int rc = SQLITE_OK;
- int i;
-
- fts5BufferZero(pBuf);
- for(i=0; i<pColset->nCol; i++){
- const u8 *pSub = pPos;
- int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]);
- if( nSub ){
- fts5BufferAppendBlob(&rc, pBuf, nSub, pSub);
+ if( *pRc==SQLITE_OK ){
+ int i;
+ fts5BufferZero(pBuf);
+ for(i=0; i<pColset->nCol; i++){
+ const u8 *pSub = pPos;
+ int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]);
+ if( nSub ){
+ fts5BufferAppendBlob(pRc, pBuf, nSub, pSub);
+ }
}
}
- return rc;
}
/*
@@ -192966,8 +221248,9 @@ static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){
pIter->base.nData = fts5IndexExtractCol(&a, pSeg->nPos,pColset->aiCol[0]);
pIter->base.pData = a;
}else{
+ int *pRc = &pIter->pIndex->rc;
fts5BufferZero(&pIter->poslist);
- fts5IndexExtractColset(pColset, a, pSeg->nPos, &pIter->poslist);
+ fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, &pIter->poslist);
pIter->base.pData = pIter->poslist.p;
pIter->base.nData = pIter->poslist.n;
}
@@ -193005,7 +221288,7 @@ static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){
assert( pConfig->eDetail==FTS5_DETAIL_COLUMNS );
if( pConfig->nCol<=100 ){
pIter->xSetOutputs = fts5IterSetOutputs_Col100;
- sqlite3Fts5BufferSize(pRc, &pIter->poslist, pConfig->nCol);
+ tdsqlite3Fts5BufferSize(pRc, &pIter->poslist, pConfig->nCol);
}else{
pIter->xSetOutputs = fts5IterSetOutputs_Col;
}
@@ -193057,9 +221340,7 @@ static void fts5MultiIterNew(
if( pNew==0 ) return;
pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC));
pNew->bSkipEmpty = (0!=(flags & FTS5INDEX_QUERY_SKIPEMPTY));
- pNew->pStruct = pStruct;
pNew->pColset = pColset;
- fts5StructureRef(pStruct);
if( (flags & FTS5INDEX_QUERY_NOOUTPUT)==0 ){
fts5IterSetOutputCb(&p->rc, pNew);
}
@@ -193237,33 +221518,34 @@ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){
for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
int iId = pStruct->aLevel[iLvl].aSeg[iSeg].iSegid;
- if( iId<=FTS5_MAX_SEGMENT ){
- aUsed[(iId-1) / 32] |= 1 << ((iId-1) % 32);
+ if( iId<=FTS5_MAX_SEGMENT && iId>0 ){
+ aUsed[(iId-1) / 32] |= (u32)1 << ((iId-1) % 32);
}
}
}
for(i=0; aUsed[i]==0xFFFFFFFF; i++);
mask = aUsed[i];
- for(iSegid=0; mask & (1 << iSegid); iSegid++);
+ for(iSegid=0; mask & ((u32)1 << iSegid); iSegid++);
iSegid += 1 + i*32;
#ifdef SQLITE_DEBUG
for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
- assert( iSegid!=pStruct->aLevel[iLvl].aSeg[iSeg].iSegid );
+ assert_nc( iSegid!=pStruct->aLevel[iLvl].aSeg[iSeg].iSegid );
}
}
- assert( iSegid>0 && iSegid<=FTS5_MAX_SEGMENT );
+ assert_nc( iSegid>0 && iSegid<=FTS5_MAX_SEGMENT );
{
- sqlite3_stmt *pIdxSelect = fts5IdxSelectStmt(p);
+ tdsqlite3_stmt *pIdxSelect = fts5IdxSelectStmt(p);
if( p->rc==SQLITE_OK ){
u8 aBlob[2] = {0xff, 0xff};
- sqlite3_bind_int(pIdxSelect, 1, iSegid);
- sqlite3_bind_blob(pIdxSelect, 2, aBlob, 2, SQLITE_STATIC);
- assert( sqlite3_step(pIdxSelect)!=SQLITE_ROW );
- p->rc = sqlite3_reset(pIdxSelect);
+ tdsqlite3_bind_int(pIdxSelect, 1, iSegid);
+ tdsqlite3_bind_blob(pIdxSelect, 2, aBlob, 2, SQLITE_STATIC);
+ assert_nc( tdsqlite3_step(pIdxSelect)!=SQLITE_ROW );
+ p->rc = tdsqlite3_reset(pIdxSelect);
+ tdsqlite3_bind_null(pIdxSelect, 2);
}
}
#endif
@@ -193279,7 +221561,7 @@ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){
static void fts5IndexDiscardData(Fts5Index *p){
assert( p->pHash || p->nPendingData==0 );
if( p->pHash ){
- sqlite3Fts5HashClear(p->pHash);
+ tdsqlite3Fts5HashClear(p->pHash);
p->nPendingData = 0;
}
}
@@ -193316,7 +221598,7 @@ static void fts5WriteDlidxClear(
pDlidx->buf.p, pDlidx->buf.n
);
}
- sqlite3Fts5BufferZero(&pDlidx->buf);
+ tdsqlite3Fts5BufferZero(&pDlidx->buf);
pDlidx->bPrevValid = 0;
}
}
@@ -193331,13 +221613,13 @@ static int fts5WriteDlidxGrow(
int nLvl
){
if( p->rc==SQLITE_OK && nLvl>=pWriter->nDlidx ){
- Fts5DlidxWriter *aDlidx = (Fts5DlidxWriter*)sqlite3_realloc(
+ Fts5DlidxWriter *aDlidx = (Fts5DlidxWriter*)tdsqlite3_realloc64(
pWriter->aDlidx, sizeof(Fts5DlidxWriter) * nLvl
);
if( aDlidx==0 ){
p->rc = SQLITE_NOMEM;
}else{
- int nByte = sizeof(Fts5DlidxWriter) * (nLvl - pWriter->nDlidx);
+ size_t nByte = sizeof(Fts5DlidxWriter) * (nLvl - pWriter->nDlidx);
memset(&aDlidx[pWriter->nDlidx], 0, nByte);
pWriter->aDlidx = aDlidx;
pWriter->nDlidx = nLvl;
@@ -193385,11 +221667,12 @@ static void fts5WriteFlushBtree(Fts5Index *p, Fts5SegWriter *pWriter){
if( p->rc==SQLITE_OK ){
const char *z = (pWriter->btterm.n>0?(const char*)pWriter->btterm.p:"");
/* The following was already done in fts5WriteInit(): */
- /* sqlite3_bind_int(p->pIdxWriter, 1, pWriter->iSegid); */
- sqlite3_bind_blob(p->pIdxWriter, 2, z, pWriter->btterm.n, SQLITE_STATIC);
- sqlite3_bind_int64(p->pIdxWriter, 3, bFlag + ((i64)pWriter->iBtPage<<1));
- sqlite3_step(p->pIdxWriter);
- p->rc = sqlite3_reset(p->pIdxWriter);
+ /* tdsqlite3_bind_int(p->pIdxWriter, 1, pWriter->iSegid); */
+ tdsqlite3_bind_blob(p->pIdxWriter, 2, z, pWriter->btterm.n, SQLITE_STATIC);
+ tdsqlite3_bind_int64(p->pIdxWriter, 3, bFlag + ((i64)pWriter->iBtPage<<1));
+ tdsqlite3_step(p->pIdxWriter);
+ p->rc = tdsqlite3_reset(p->pIdxWriter);
+ tdsqlite3_bind_null(p->pIdxWriter, 2);
}
pWriter->iBtPage = 0;
}
@@ -193409,8 +221692,10 @@ static void fts5WriteBtreeTerm(
int nTerm, const u8 *pTerm /* First term on new page */
){
fts5WriteFlushBtree(p, pWriter);
- fts5BufferSet(&p->rc, &pWriter->btterm, nTerm, pTerm);
- pWriter->iBtPage = pWriter->writer.pgno;
+ if( p->rc==SQLITE_OK ){
+ fts5BufferSet(&p->rc, &pWriter->btterm, nTerm, pTerm);
+ pWriter->iBtPage = pWriter->writer.pgno;
+ }
}
/*
@@ -193426,7 +221711,7 @@ static void fts5WriteBtreeNoTerm(
if( pWriter->bFirstRowidInPage && pWriter->aDlidx[0].buf.n>0 ){
Fts5DlidxWriter *pDlidx = &pWriter->aDlidx[0];
assert( pDlidx->bPrevValid );
- sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, 0);
+ tdsqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, 0);
}
/* Increment the "number of sequential leaves without a term" counter. */
@@ -193477,14 +221762,14 @@ static void fts5WriteDlidxAppend(
/* This was the root node. Push its first rowid up to the new root. */
pDlidx[1].pgno = pDlidx->pgno;
- sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1].buf, 0);
- sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1].buf, pDlidx->pgno);
- sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1].buf, iFirst);
+ tdsqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1].buf, 0);
+ tdsqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1].buf, pDlidx->pgno);
+ tdsqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx[1].buf, iFirst);
pDlidx[1].bPrevValid = 1;
pDlidx[1].iPrev = iFirst;
}
- sqlite3Fts5BufferZero(&pDlidx->buf);
+ tdsqlite3Fts5BufferZero(&pDlidx->buf);
pDlidx->bPrevValid = 0;
pDlidx->pgno++;
}else{
@@ -193496,12 +221781,12 @@ static void fts5WriteDlidxAppend(
}else{
i64 iPgno = (i==0 ? pWriter->writer.pgno : pDlidx[-1].pgno);
assert( pDlidx->buf.n==0 );
- sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, !bDone);
- sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, iPgno);
+ tdsqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, !bDone);
+ tdsqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, iPgno);
iVal = iRowid;
}
- sqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, iVal);
+ tdsqlite3Fts5BufferAppendVarint(&p->rc, &pDlidx->buf, iVal);
pDlidx->bPrevValid = 1;
pDlidx->iPrev = iRowid;
}
@@ -193512,9 +221797,6 @@ static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){
Fts5PageWriter *pPage = &pWriter->writer;
i64 iRowid;
-static int nCall = 0;
-nCall++;
-
assert( (pPage->pgidx.n==0)==(pWriter->bFirstTermInPage) );
/* Set the szLeaf header field. */
@@ -193564,6 +221846,7 @@ static void fts5WriteAppendTerm(
int nPrefix; /* Bytes of prefix compression for term */
Fts5PageWriter *pPage = &pWriter->writer;
Fts5Buffer *pPgidx = &pWriter->writer.pgidx;
+ int nMin = MIN(pPage->term.n, nTerm);
assert( p->rc==SQLITE_OK );
assert( pPage->buf.n>=4 );
@@ -193573,12 +221856,13 @@ static void fts5WriteAppendTerm(
if( (pPage->buf.n + pPgidx->n + nTerm + 2)>=p->pConfig->pgsz ){
if( pPage->buf.n>4 ){
fts5WriteFlushLeaf(p, pWriter);
+ if( p->rc!=SQLITE_OK ) return;
}
fts5BufferGrow(&p->rc, &pPage->buf, nTerm+FTS5_DATA_PADDING);
}
/* TODO1: Updating pgidx here. */
- pPgidx->n += sqlite3Fts5PutVarint(
+ pPgidx->n += tdsqlite3Fts5PutVarint(
&pPgidx->p[pPgidx->n], pPage->buf.n - pPage->iPrevPgidx
);
pPage->iPrevPgidx = pPage->buf.n;
@@ -193605,13 +221889,14 @@ static void fts5WriteAppendTerm(
** inefficient, but still correct. */
int n = nTerm;
if( pPage->term.n ){
- n = 1 + fts5PrefixCompress(pPage->term.n, pPage->term.p, pTerm);
+ n = 1 + fts5PrefixCompress(nMin, pPage->term.p, pTerm);
}
fts5WriteBtreeTerm(p, pWriter, n, pTerm);
+ if( p->rc!=SQLITE_OK ) return;
pPage = &pWriter->writer;
}
}else{
- nPrefix = fts5PrefixCompress(pPage->term.n, pPage->term.p, pTerm);
+ nPrefix = fts5PrefixCompress(nMin, pPage->term.p, pTerm);
fts5BufferAppendVarint(&p->rc, &pPage->buf, nPrefix);
}
@@ -193658,7 +221943,7 @@ static void fts5WriteAppendRowid(
if( pWriter->bFirstRowidInDoclist || pWriter->bFirstRowidInPage ){
fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid);
}else{
- assert( p->rc || iRowid>pWriter->iPrevRowid );
+ assert_nc( p->rc || iRowid>pWriter->iPrevRowid );
fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid - pWriter->iPrevRowid);
}
pWriter->iPrevRowid = iRowid;
@@ -193724,9 +222009,9 @@ static void fts5WriteFinish(
fts5BufferFree(&pWriter->btterm);
for(i=0; i<pWriter->nDlidx; i++){
- sqlite3Fts5BufferFree(&pWriter->aDlidx[i].buf);
+ tdsqlite3Fts5BufferFree(&pWriter->aDlidx[i].buf);
}
- sqlite3_free(pWriter->aDlidx);
+ tdsqlite3_free(pWriter->aDlidx);
}
static void fts5WriteInit(
@@ -193748,12 +222033,12 @@ static void fts5WriteInit(
assert( pWriter->writer.pgidx.n==0 );
/* Grow the two buffers to pgsz + padding bytes in size. */
- sqlite3Fts5BufferSize(&p->rc, &pWriter->writer.pgidx, nBuffer);
- sqlite3Fts5BufferSize(&p->rc, &pWriter->writer.buf, nBuffer);
+ tdsqlite3Fts5BufferSize(&p->rc, &pWriter->writer.pgidx, nBuffer);
+ tdsqlite3Fts5BufferSize(&p->rc, &pWriter->writer.buf, nBuffer);
if( p->pIdxWriter==0 ){
Fts5Config *pConfig = p->pConfig;
- fts5IndexPrepareStmt(p, &p->pIdxWriter, sqlite3_mprintf(
+ fts5IndexPrepareStmt(p, &p->pIdxWriter, tdsqlite3_mprintf(
"INSERT INTO '%q'.'%q_idx'(segid,term,pgno) VALUES(?,?,?)",
pConfig->zDb, pConfig->zName
));
@@ -193767,7 +222052,7 @@ static void fts5WriteInit(
/* Bind the current output segment id to the index-writer. This is an
** optimization over binding the same value over and over as rows are
** inserted into %_idx by the current writer. */
- sqlite3_bind_int(p->pIdxWriter, 1, pWriter->iSegid);
+ tdsqlite3_bind_int(p->pIdxWriter, 1, pWriter->iSegid);
}
}
@@ -193780,7 +222065,7 @@ static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){
int i;
Fts5Buffer buf;
memset(&buf, 0, sizeof(Fts5Buffer));
- for(i=0; i<pIter->nSeg; i++){
+ for(i=0; i<pIter->nSeg && p->rc==SQLITE_OK; i++){
Fts5SegIter *pSeg = &pIter->aSeg[i];
if( pSeg->pSeg==0 ){
/* no-op */
@@ -193798,35 +222083,44 @@ static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){
u8 aHdr[4] = {0x00, 0x00, 0x00, 0x00};
iLeafRowid = FTS5_SEGMENT_ROWID(iId, pSeg->iTermLeafPgno);
- pData = fts5DataRead(p, iLeafRowid);
+ pData = fts5LeafRead(p, iLeafRowid);
if( pData ){
- fts5BufferZero(&buf);
- fts5BufferGrow(&p->rc, &buf, pData->nn);
- fts5BufferAppendBlob(&p->rc, &buf, sizeof(aHdr), aHdr);
- fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n);
- fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p);
- fts5BufferAppendBlob(&p->rc, &buf, pData->szLeaf-iOff, &pData->p[iOff]);
- if( p->rc==SQLITE_OK ){
- /* Set the szLeaf field */
- fts5PutU16(&buf.p[2], (u16)buf.n);
- }
+ if( iOff>pData->szLeaf ){
+ /* This can occur if the pages that the segments occupy overlap - if
+ ** a single page has been assigned to more than one segment. In
+ ** this case a prior iteration of this loop may have corrupted the
+ ** segment currently being trimmed. */
+ p->rc = FTS5_CORRUPT;
+ }else{
+ fts5BufferZero(&buf);
+ fts5BufferGrow(&p->rc, &buf, pData->nn);
+ fts5BufferAppendBlob(&p->rc, &buf, sizeof(aHdr), aHdr);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n);
+ fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p);
+ fts5BufferAppendBlob(&p->rc, &buf, pData->szLeaf-iOff,&pData->p[iOff]);
+ if( p->rc==SQLITE_OK ){
+ /* Set the szLeaf field */
+ fts5PutU16(&buf.p[2], (u16)buf.n);
+ }
- /* Set up the new page-index array */
- fts5BufferAppendVarint(&p->rc, &buf, 4);
- if( pSeg->iLeafPgno==pSeg->iTermLeafPgno
- && pSeg->iEndofDoclist<pData->szLeaf
- ){
- int nDiff = pData->szLeaf - pSeg->iEndofDoclist;
- fts5BufferAppendVarint(&p->rc, &buf, buf.n - 1 - nDiff - 4);
- fts5BufferAppendBlob(&p->rc, &buf,
- pData->nn - pSeg->iPgidxOff, &pData->p[pSeg->iPgidxOff]
- );
- }
+ /* Set up the new page-index array */
+ fts5BufferAppendVarint(&p->rc, &buf, 4);
+ if( pSeg->iLeafPgno==pSeg->iTermLeafPgno
+ && pSeg->iEndofDoclist<pData->szLeaf
+ && pSeg->iPgidxOff<=pData->nn
+ ){
+ int nDiff = pData->szLeaf - pSeg->iEndofDoclist;
+ fts5BufferAppendVarint(&p->rc, &buf, buf.n - 1 - nDiff - 4);
+ fts5BufferAppendBlob(&p->rc, &buf,
+ pData->nn - pSeg->iPgidxOff, &pData->p[pSeg->iPgidxOff]
+ );
+ }
+ pSeg->pSeg->pgnoFirst = pSeg->iTermLeafPgno;
+ fts5DataDelete(p, FTS5_SEGMENT_ROWID(iId, 1), iLeafRowid);
+ fts5DataWrite(p, iLeafRowid, buf.p, buf.n);
+ }
fts5DataRelease(pData);
- pSeg->pSeg->pgnoFirst = pSeg->iTermLeafPgno;
- fts5DataDelete(p, FTS5_SEGMENT_ROWID(iId, 1), iLeafRowid);
- fts5DataWrite(p, iLeafRowid, buf.p, buf.n);
}
}
}
@@ -193863,6 +222157,7 @@ static void fts5IndexMergeLevel(
int bOldest; /* True if the output segment is the oldest */
int eDetail = p->pConfig->eDetail;
const int flags = FTS5INDEX_QUERY_NOOUTPUT;
+ int bTermWritten = 0; /* True if current term already output */
assert( iLvl<pStruct->nLevel );
assert( pLvl->nMerge<=pLvl->nSeg );
@@ -193916,18 +222211,22 @@ static void fts5IndexMergeLevel(
int nTerm;
const u8 *pTerm;
- /* Check for key annihilation. */
- if( pSegIter->nPos==0 && (bOldest || pSegIter->bDel==0) ) continue;
-
pTerm = fts5MultiIterTerm(pIter, &nTerm);
- if( nTerm!=term.n || memcmp(pTerm, term.p, nTerm) ){
+ if( nTerm!=term.n || fts5Memcmp(pTerm, term.p, nTerm) ){
if( pnRem && writer.nLeafWritten>nRem ){
break;
}
+ fts5BufferSet(&p->rc, &term, nTerm, pTerm);
+ bTermWritten =0;
+ }
+ /* Check for key annihilation. */
+ if( pSegIter->nPos==0 && (bOldest || pSegIter->bDel==0) ) continue;
+
+ if( p->rc==SQLITE_OK && bTermWritten==0 ){
/* This is a new term. Append a term to the output segment. */
fts5WriteAppendTerm(p, &writer, nTerm, pTerm);
- fts5BufferSet(&p->rc, &term, nTerm, pTerm);
+ bTermWritten = 1;
}
/* Append the rowid to the output */
@@ -194158,16 +222457,17 @@ static void fts5FlushOneHash(Fts5Index *p){
/* Begin scanning through hash table entries. This loop runs once for each
** term/doclist currently stored within the hash table. */
if( p->rc==SQLITE_OK ){
- p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0);
+ p->rc = tdsqlite3Fts5HashScanInit(pHash, 0, 0);
}
- while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){
+ while( p->rc==SQLITE_OK && 0==tdsqlite3Fts5HashScanEof(pHash) ){
const char *zTerm; /* Buffer containing term */
const u8 *pDoclist; /* Pointer to doclist for this term */
int nDoclist; /* Size of doclist in bytes */
/* Write the term for this entry to disk. */
- sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist);
+ tdsqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist);
fts5WriteAppendTerm(p, &writer, (int)strlen(zTerm), (const u8*)zTerm);
+ if( p->rc!=SQLITE_OK ) break;
assert( writer.bFirstRowidInPage==0 );
if( pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){
@@ -194187,11 +222487,12 @@ static void fts5FlushOneHash(Fts5Index *p){
if( writer.bFirstRowidInPage ){
fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid);
+ pBuf->n += tdsqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid);
writer.bFirstRowidInPage = 0;
fts5WriteDlidxAppend(p, &writer, iRowid);
+ if( p->rc!=SQLITE_OK ) break;
}else{
- pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iDelta);
+ pBuf->n += tdsqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iDelta);
}
assert( pBuf->n<=pBuf->nSpace );
@@ -194247,9 +222548,9 @@ static void fts5FlushOneHash(Fts5Index *p){
/* TODO2: Doclist terminator written here. */
/* pBuf->p[pBuf->n++] = '\0'; */
assert( pBuf->n<=pBuf->nSpace );
- sqlite3Fts5HashScanNext(pHash);
+ if( p->rc==SQLITE_OK ) tdsqlite3Fts5HashScanNext(pHash);
}
- sqlite3Fts5HashClear(pHash);
+ tdsqlite3Fts5HashClear(pHash);
fts5WriteFinish(p, &writer, &pgnoLast);
/* Update the Fts5Structure. It is written back to the database by the
@@ -194291,7 +222592,7 @@ static Fts5Structure *fts5IndexOptimizeStruct(
Fts5Structure *pStruct
){
Fts5Structure *pNew = 0;
- int nByte = sizeof(Fts5Structure);
+ tdsqlite3_int64 nByte = sizeof(Fts5Structure);
int nSeg = pStruct->nSegment;
int i;
@@ -194316,7 +222617,7 @@ static Fts5Structure *fts5IndexOptimizeStruct(
}
nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel);
- pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
+ pNew = (Fts5Structure*)tdsqlite3Fts5MallocZero(&p->rc, nByte);
if( pNew ){
Fts5StructureLevel *pLvl;
@@ -194325,7 +222626,7 @@ static Fts5Structure *fts5IndexOptimizeStruct(
pNew->nRef = 1;
pNew->nWriteCounter = pStruct->nWriteCounter;
pLvl = &pNew->aLevel[pStruct->nLevel];
- pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte);
+ pLvl->aSeg = (Fts5StructureSegment*)tdsqlite3Fts5MallocZero(&p->rc, nByte);
if( pLvl->aSeg ){
int iLvl, iSeg;
int iSegOut = 0;
@@ -194340,7 +222641,7 @@ static Fts5Structure *fts5IndexOptimizeStruct(
}
pNew->nSegment = pLvl->nSeg = nSeg;
}else{
- sqlite3_free(pNew);
+ tdsqlite3_free(pNew);
pNew = 0;
}
}
@@ -194348,7 +222649,7 @@ static Fts5Structure *fts5IndexOptimizeStruct(
return pNew;
}
-static int sqlite3Fts5IndexOptimize(Fts5Index *p){
+static int tdsqlite3Fts5IndexOptimize(Fts5Index *p){
Fts5Structure *pStruct;
Fts5Structure *pNew = 0;
@@ -194382,7 +222683,7 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){
** This is called to implement the special "VALUES('merge', $nMerge)"
** INSERT command.
*/
-static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
+static int tdsqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
Fts5Structure *pStruct = fts5StructureRead(p);
if( pStruct ){
int nMin = p->pConfig->nUsermerge;
@@ -194421,11 +222722,13 @@ static void fts5AppendPoslist(
Fts5Buffer *pBuf
){
int nData = pMulti->base.nData;
+ int nByte = nData + 9 + 9 + FTS5_DATA_ZERO_PADDING;
assert( nData>0 );
- if( p->rc==SQLITE_OK && 0==fts5BufferGrow(&p->rc, pBuf, nData+9+9) ){
+ if( p->rc==SQLITE_OK && 0==fts5BufferGrow(&p->rc, pBuf, nByte) ){
fts5BufferSafeAppendVarint(pBuf, iDelta);
fts5BufferSafeAppendVarint(pBuf, nData*2);
fts5BufferSafeAppendBlob(pBuf, pMulti->base.pData, nData);
+ memset(&pBuf->p[pBuf->n], 0, FTS5_DATA_ZERO_PADDING);
}
}
@@ -194505,7 +222808,7 @@ static void fts5NextRowid(Fts5Buffer *pBuf, int *piOff, i64 *piRowid){
*piOff = -1;
}else{
u64 iVal;
- *piOff = i + sqlite3Fts5GetVarint(&pBuf->p[i], &iVal);
+ *piOff = i + tdsqlite3Fts5GetVarint(&pBuf->p[i], &iVal);
*piRowid += iVal;
}
}
@@ -194527,7 +222830,7 @@ static void fts5MergeRowidLists(
Fts5Buffer out;
memset(&out, 0, sizeof(out));
- sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n);
+ tdsqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n);
if( p->rc ) return;
fts5NextRowid(p1, &i1, &iRowid1);
@@ -194573,7 +222876,19 @@ static void fts5MergePrefixLists(
Fts5Buffer out = {0, 0, 0};
Fts5Buffer tmp = {0, 0, 0};
- if( sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n) ) return;
+ /* The maximum size of the output is equal to the sum of the two
+ ** input sizes + 1 varint (9 bytes). The extra varint is because if the
+ ** first rowid in one input is a large negative number, and the first in
+ ** the other a non-negative number, the delta for the non-negative
+ ** number will be larger on disk than the literal integer value
+ ** was.
+ **
+ ** Or, if the input position-lists are corrupt, then the output might
+ ** include up to 2 extra 10-byte positions created by interpreting -1
+ ** (the value PoslistNext64() uses for EOF) as a position and appending
+ ** it to the output. This can happen at most once for each input
+ ** position-list, hence two 10 byte paddings. */
+ if( tdsqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n + 9+10+10) ) return;
fts5DoclistIterInit(p1, &i1);
fts5DoclistIterInit(p2, &i2);
@@ -194584,6 +222899,7 @@ static void fts5MergePrefixLists(
fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.nPoslist+i1.nSize);
fts5DoclistIterNext(&i1);
if( i1.aPoslist==0 ) break;
+ assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) );
}
else if( i2.iRowid!=i1.iRowid ){
/* Copy entry from i2 */
@@ -194591,6 +222907,7 @@ static void fts5MergePrefixLists(
fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.nPoslist+i2.nSize);
fts5DoclistIterNext(&i2);
if( i2.aPoslist==0 ) break;
+ assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) );
}
else{
/* Merge the two position lists. */
@@ -194600,40 +222917,44 @@ static void fts5MergePrefixLists(
int iOff2 = 0;
u8 *a1 = &i1.aPoslist[i1.nSize];
u8 *a2 = &i2.aPoslist[i2.nSize];
+ int nCopy;
+ u8 *aCopy;
i64 iPrev = 0;
Fts5PoslistWriter writer;
memset(&writer, 0, sizeof(writer));
+ /* See the earlier comment in this function for an explanation of why
+ ** corrupt input position lists might cause the output to consume
+ ** at most 20 bytes of unexpected space. */
fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid);
fts5BufferZero(&tmp);
- sqlite3Fts5BufferSize(&p->rc, &tmp, i1.nPoslist + i2.nPoslist);
+ tdsqlite3Fts5BufferSize(&p->rc, &tmp, i1.nPoslist + i2.nPoslist + 10 + 10);
if( p->rc ) break;
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
- assert( iPos1>=0 && iPos2>=0 );
+ tdsqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
+ tdsqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
+ assert_nc( iPos1>=0 && iPos2>=0 );
if( iPos1<iPos2 ){
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
+ tdsqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
+ tdsqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
}else{
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
+ tdsqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
+ tdsqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
}
-
if( iPos1>=0 && iPos2>=0 ){
while( 1 ){
if( iPos1<iPos2 ){
if( iPos1!=iPrev ){
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
+ tdsqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
}
- sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
+ tdsqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1);
if( iPos1<0 ) break;
}else{
- assert( iPos2!=iPrev );
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
- sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
+ assert_nc( iPos2!=iPrev );
+ tdsqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
+ tdsqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2);
if( iPos2<0 ) break;
}
}
@@ -194641,21 +222962,34 @@ static void fts5MergePrefixLists(
if( iPos1>=0 ){
if( iPos1!=iPrev ){
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
+ tdsqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1);
}
- fts5BufferSafeAppendBlob(&tmp, &a1[iOff1], i1.nPoslist-iOff1);
+ aCopy = &a1[iOff1];
+ nCopy = i1.nPoslist - iOff1;
}else{
- assert( iPos2>=0 && iPos2!=iPrev );
- sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
- fts5BufferSafeAppendBlob(&tmp, &a2[iOff2], i2.nPoslist-iOff2);
+ assert_nc( iPos2>=0 && iPos2!=iPrev );
+ tdsqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2);
+ aCopy = &a2[iOff2];
+ nCopy = i2.nPoslist - iOff2;
+ }
+ if( nCopy>0 ){
+ fts5BufferSafeAppendBlob(&tmp, aCopy, nCopy);
}
/* WRITEPOSLISTSIZE */
+ assert_nc( tmp.n<=i1.nPoslist+i2.nPoslist );
+ assert( tmp.n<=i1.nPoslist+i2.nPoslist+10+10 );
+ if( tmp.n>i1.nPoslist+i2.nPoslist ){
+ if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
+ break;
+ }
fts5BufferSafeAppendVarint(&out, tmp.n * 2);
fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n);
fts5DoclistIterNext(&i1);
fts5DoclistIterNext(&i2);
+ assert_nc( out.n<=(p1->n+p2->n+9) );
if( i1.aPoslist==0 || i2.aPoslist==0 ) break;
+ assert( out.n<=((i1.aPoslist-p1->p) + (i2.aPoslist-p2->p)+9+10+10) );
}
}
@@ -194667,6 +223001,7 @@ static void fts5MergePrefixLists(
fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid);
fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.aEof - i2.aPoslist);
}
+ assert_nc( out.n<=(p1->n+p2->n+9) );
fts5BufferSet(&p->rc, p1, out.n, out.p);
fts5BufferFree(&tmp);
@@ -194755,31 +223090,31 @@ static void fts5SetupPrefixIter(
}
fts5MultiIterFree(p1);
- pData = fts5IdxMalloc(p, sizeof(Fts5Data) + doclist.n);
+ pData = fts5IdxMalloc(p, sizeof(Fts5Data)+doclist.n+FTS5_DATA_ZERO_PADDING);
if( pData ){
pData->p = (u8*)&pData[1];
pData->nn = pData->szLeaf = doclist.n;
- memcpy(pData->p, doclist.p, doclist.n);
+ if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n);
fts5MultiIterNew2(p, pData, bDesc, ppIter);
}
fts5BufferFree(&doclist);
}
fts5StructureRelease(pStruct);
- sqlite3_free(aBuf);
+ tdsqlite3_free(aBuf);
}
/*
-** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain
+** Indicate that all subsequent calls to tdsqlite3Fts5IndexWrite() pertain
** to the document with rowid iRowid.
*/
-static int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){
+static int tdsqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){
assert( p->rc==SQLITE_OK );
/* Allocate the hash table if it has not already been allocated */
if( p->pHash==0 ){
- p->rc = sqlite3Fts5HashNew(p->pConfig, &p->pHash, &p->nPendingData);
+ p->rc = tdsqlite3Fts5HashNew(p->pConfig, &p->pHash, &p->nPendingData);
}
/* Flush the hash table to disk if required */
@@ -194798,10 +223133,10 @@ static int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){
/*
** Commit data to disk.
*/
-static int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){
+static int tdsqlite3Fts5IndexSync(Fts5Index *p){
assert( p->rc==SQLITE_OK );
fts5IndexFlush(p);
- if( bCommit ) fts5CloseReader(p);
+ tdsqlite3Fts5IndexCloseReader(p);
return fts5IndexReturn(p);
}
@@ -194811,8 +223146,8 @@ static int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){
** table may have changed on disk. So any in-memory caches of %_data
** records must be invalidated.
*/
-static int sqlite3Fts5IndexRollback(Fts5Index *p){
- fts5CloseReader(p);
+static int tdsqlite3Fts5IndexRollback(Fts5Index *p){
+ tdsqlite3Fts5IndexCloseReader(p);
fts5IndexDiscardData(p);
fts5StructureInvalidate(p);
/* assert( p->rc==SQLITE_OK ); */
@@ -194824,9 +223159,10 @@ static int sqlite3Fts5IndexRollback(Fts5Index *p){
** function populates it with the initial structure objects for each index,
** and the initial version of the "averages" record (a zero-byte blob).
*/
-static int sqlite3Fts5IndexReinit(Fts5Index *p){
+static int tdsqlite3Fts5IndexReinit(Fts5Index *p){
Fts5Structure s;
fts5StructureInvalidate(p);
+ fts5IndexDiscardData(p);
memset(&s, 0, sizeof(Fts5Structure));
fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0);
fts5StructureWrite(p, &s);
@@ -194840,7 +223176,7 @@ static int sqlite3Fts5IndexReinit(Fts5Index *p){
** If successful, set *pp to point to the new object and return SQLITE_OK.
** Otherwise, set *pp to NULL and return an SQLite error code.
*/
-static int sqlite3Fts5IndexOpen(
+static int tdsqlite3Fts5IndexOpen(
Fts5Config *pConfig,
int bCreate,
Fts5Index **pp,
@@ -194849,52 +223185,52 @@ static int sqlite3Fts5IndexOpen(
int rc = SQLITE_OK;
Fts5Index *p; /* New object */
- *pp = p = (Fts5Index*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Index));
+ *pp = p = (Fts5Index*)tdsqlite3Fts5MallocZero(&rc, sizeof(Fts5Index));
if( rc==SQLITE_OK ){
p->pConfig = pConfig;
p->nWorkUnit = FTS5_WORK_UNIT;
- p->zDataTbl = sqlite3Fts5Mprintf(&rc, "%s_data", pConfig->zName);
+ p->zDataTbl = tdsqlite3Fts5Mprintf(&rc, "%s_data", pConfig->zName);
if( p->zDataTbl && bCreate ){
- rc = sqlite3Fts5CreateTable(
+ rc = tdsqlite3Fts5CreateTable(
pConfig, "data", "id INTEGER PRIMARY KEY, block BLOB", 0, pzErr
);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5CreateTable(pConfig, "idx",
+ rc = tdsqlite3Fts5CreateTable(pConfig, "idx",
"segid, term, pgno, PRIMARY KEY(segid, term)",
1, pzErr
);
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexReinit(p);
+ rc = tdsqlite3Fts5IndexReinit(p);
}
}
}
assert( rc!=SQLITE_OK || p->rc==SQLITE_OK );
if( rc ){
- sqlite3Fts5IndexClose(p);
+ tdsqlite3Fts5IndexClose(p);
*pp = 0;
}
return rc;
}
/*
-** Close a handle opened by an earlier call to sqlite3Fts5IndexOpen().
+** Close a handle opened by an earlier call to tdsqlite3Fts5IndexOpen().
*/
-static int sqlite3Fts5IndexClose(Fts5Index *p){
+static int tdsqlite3Fts5IndexClose(Fts5Index *p){
int rc = SQLITE_OK;
if( p ){
assert( p->pReader==0 );
fts5StructureInvalidate(p);
- sqlite3_finalize(p->pWriter);
- sqlite3_finalize(p->pDeleter);
- sqlite3_finalize(p->pIdxWriter);
- sqlite3_finalize(p->pIdxDeleter);
- sqlite3_finalize(p->pIdxSelect);
- sqlite3_finalize(p->pDataVersion);
- sqlite3Fts5HashFree(p->pHash);
- sqlite3_free(p->zDataTbl);
- sqlite3_free(p);
+ tdsqlite3_finalize(p->pWriter);
+ tdsqlite3_finalize(p->pDeleter);
+ tdsqlite3_finalize(p->pIdxWriter);
+ tdsqlite3_finalize(p->pIdxDeleter);
+ tdsqlite3_finalize(p->pIdxSelect);
+ tdsqlite3_finalize(p->pDataVersion);
+ tdsqlite3Fts5HashFree(p->pHash);
+ tdsqlite3_free(p->zDataTbl);
+ tdsqlite3_free(p);
}
return rc;
}
@@ -194904,7 +223240,7 @@ static int sqlite3Fts5IndexClose(Fts5Index *p){
** size. Return the number of bytes in the nChar character prefix of the
** buffer, or 0 if there are less than nChar characters in total.
*/
-static int sqlite3Fts5IndexCharlenToBytelen(
+static int tdsqlite3Fts5IndexCharlenToBytelen(
const char *p,
int nByte,
int nChar
@@ -194914,7 +223250,14 @@ static int sqlite3Fts5IndexCharlenToBytelen(
for(i=0; i<nChar; i++){
if( n>=nByte ) return 0; /* Input contains fewer than nChar chars */
if( (unsigned char)p[n++]>=0xc0 ){
- while( (p[n] & 0xc0)==0x80 ) n++;
+ if( n>=nByte ) return 0;
+ while( (p[n] & 0xc0)==0x80 ){
+ n++;
+ if( n>=nByte ){
+ if( i+1==nChar ) break;
+ return 0;
+ }
+ }
}
}
return n;
@@ -194946,7 +223289,7 @@ static int fts5IndexCharlen(const char *pIn, int nIn){
** unique token in the document with an iCol value less than zero. The iPos
** argument is ignored for a delete.
*/
-static int sqlite3Fts5IndexWrite(
+static int tdsqlite3Fts5IndexWrite(
Fts5Index *p, /* Index to write to */
int iCol, /* Column token appears in (-ve -> delete) */
int iPos, /* Position of token within column */
@@ -194960,15 +223303,15 @@ static int sqlite3Fts5IndexWrite(
assert( (iCol<0)==p->bDelete );
/* Add the entry to the main terms index. */
- rc = sqlite3Fts5HashWrite(
+ rc = tdsqlite3Fts5HashWrite(
p->pHash, p->iWriteRowid, iCol, iPos, FTS5_MAIN_PREFIX, pToken, nToken
);
for(i=0; i<pConfig->nPrefix && rc==SQLITE_OK; i++){
const int nChar = pConfig->aPrefix[i];
- int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar);
+ int nByte = tdsqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar);
if( nByte ){
- rc = sqlite3Fts5HashWrite(p->pHash,
+ rc = tdsqlite3Fts5HashWrite(p->pHash,
p->iWriteRowid, iCol, iPos, (char)(FTS5_MAIN_PREFIX+i+1), pToken,
nByte
);
@@ -194982,7 +223325,7 @@ static int sqlite3Fts5IndexWrite(
** Open a new iterator to iterate though all rowid that match the
** specified token or token prefix.
*/
-static int sqlite3Fts5IndexQuery(
+static int tdsqlite3Fts5IndexQuery(
Fts5Index *p, /* FTS index to query */
const char *pToken, int nToken, /* Token (or prefix) to query for */
int flags, /* Mask of FTS5INDEX_QUERY_X flags */
@@ -194996,9 +223339,9 @@ static int sqlite3Fts5IndexQuery(
/* If the QUERY_SCAN flag is set, all other flags must be clear. */
assert( (flags & FTS5INDEX_QUERY_SCAN)==0 || flags==FTS5INDEX_QUERY_SCAN );
- if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){
+ if( tdsqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){
int iIdx = 0; /* Index to search */
- memcpy(&buf.p[1], pToken, nToken);
+ if( nToken ) memcpy(&buf.p[1], pToken, nToken);
/* Figure out which index to search and set iIdx accordingly. If this
** is a prefix query for which there is no prefix index, set iIdx to
@@ -195047,13 +223390,13 @@ static int sqlite3Fts5IndexQuery(
}
if( p->rc ){
- sqlite3Fts5IterClose(&pRet->base);
+ tdsqlite3Fts5IterClose((Fts5IndexIter*)pRet);
pRet = 0;
- fts5CloseReader(p);
+ tdsqlite3Fts5IndexCloseReader(p);
}
- *ppIter = &pRet->base;
- sqlite3Fts5BufferFree(&buf);
+ *ppIter = (Fts5IndexIter*)pRet;
+ tdsqlite3Fts5BufferFree(&buf);
}
return fts5IndexReturn(p);
}
@@ -195064,7 +223407,7 @@ static int sqlite3Fts5IndexQuery(
/*
** Move to the next matching rowid.
*/
-static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){
+static int tdsqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){
Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
assert( pIter->pIndex->rc==SQLITE_OK );
fts5MultiIterNext(pIter->pIndex, pIter, 0, 0);
@@ -195074,7 +223417,7 @@ static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){
/*
** Move to the next matching term/rowid. Used by the fts5vocab module.
*/
-static int sqlite3Fts5IterNextScan(Fts5IndexIter *pIndexIter){
+static int tdsqlite3Fts5IterNextScan(Fts5IndexIter *pIndexIter){
Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
Fts5Index *p = pIter->pIndex;
@@ -195098,7 +223441,7 @@ static int sqlite3Fts5IterNextScan(Fts5IndexIter *pIndexIter){
** definition of "at or after" depends on whether this iterator iterates
** in ascending or descending rowid order.
*/
-static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){
+static int tdsqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){
Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch);
return fts5IndexReturn(pIter->pIndex);
@@ -195107,7 +223450,7 @@ static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){
/*
** Return the current term.
*/
-static const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){
+static const char *tdsqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){
int n;
const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n);
*pn = n-1;
@@ -195115,14 +223458,14 @@ static const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){
}
/*
-** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery().
+** Close an iterator opened by an earlier call to tdsqlite3Fts5IndexQuery().
*/
-static void sqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){
+static void tdsqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){
if( pIndexIter ){
Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
Fts5Index *pIndex = pIter->pIndex;
fts5MultiIterFree(pIter);
- fts5CloseReader(pIndex);
+ tdsqlite3Fts5IndexCloseReader(pIndex);
}
}
@@ -195132,7 +223475,7 @@ static void sqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){
** Parameter anSize must point to an array of size nCol, where nCol is
** the number of user defined columns in the FTS table.
*/
-static int sqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize){
+static int tdsqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize){
int nCol = p->pConfig->nCol;
Fts5Data *pData;
@@ -195156,7 +223499,7 @@ static int sqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize){
** Replace the current "averages" record with the contents of the buffer
** supplied as the second argument.
*/
-static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8 *pData, int nData){
+static int tdsqlite3Fts5IndexSetAverages(Fts5Index *p, const u8 *pData, int nData){
assert( p->rc==SQLITE_OK );
fts5DataWrite(p, FTS5_AVERAGES_ROWID, pData, nData);
return fts5IndexReturn(p);
@@ -195166,7 +223509,7 @@ static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8 *pData, int nData)
** Return the total number of blocks this module has read from the %_data
** table since it was created.
*/
-static int sqlite3Fts5IndexReads(Fts5Index *p){
+static int tdsqlite3Fts5IndexReads(Fts5Index *p){
return p->nRead;
}
@@ -195177,27 +223520,27 @@ static int sqlite3Fts5IndexReads(Fts5Index *p){
** Return SQLITE_OK if successful, or an SQLite error code if an error
** occurs.
*/
-static int sqlite3Fts5IndexSetCookie(Fts5Index *p, int iNew){
+static int tdsqlite3Fts5IndexSetCookie(Fts5Index *p, int iNew){
int rc; /* Return code */
Fts5Config *pConfig = p->pConfig; /* Configuration object */
u8 aCookie[4]; /* Binary representation of iNew */
- sqlite3_blob *pBlob = 0;
+ tdsqlite3_blob *pBlob = 0;
assert( p->rc==SQLITE_OK );
- sqlite3Fts5Put32(aCookie, iNew);
+ tdsqlite3Fts5Put32(aCookie, iNew);
- rc = sqlite3_blob_open(pConfig->db, pConfig->zDb, p->zDataTbl,
+ rc = tdsqlite3_blob_open(pConfig->db, pConfig->zDb, p->zDataTbl,
"block", FTS5_STRUCTURE_ROWID, 1, &pBlob
);
if( rc==SQLITE_OK ){
- sqlite3_blob_write(pBlob, aCookie, 4, 0);
- rc = sqlite3_blob_close(pBlob);
+ tdsqlite3_blob_write(pBlob, aCookie, 4, 0);
+ rc = tdsqlite3_blob_close(pBlob);
}
return rc;
}
-static int sqlite3Fts5IndexLoadConfig(Fts5Index *p){
+static int tdsqlite3Fts5IndexLoadConfig(Fts5Index *p){
Fts5Structure *pStruct;
pStruct = fts5StructureRead(p);
fts5StructureRelease(pStruct);
@@ -195214,7 +223557,7 @@ static int sqlite3Fts5IndexLoadConfig(Fts5Index *p){
/*
** Return a simple checksum value based on the arguments.
*/
-static u64 sqlite3Fts5IndexEntryCksum(
+static u64 tdsqlite3Fts5IndexEntryCksum(
i64 iRowid,
int iCol,
int iPos,
@@ -195287,34 +223630,65 @@ static int fts5QueryCksum(
int eDetail = p->pConfig->eDetail;
u64 cksum = *pCksum;
Fts5IndexIter *pIter = 0;
- int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter);
+ int rc = tdsqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter);
- while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIter) ){
+ while( rc==SQLITE_OK && 0==tdsqlite3Fts5IterEof(pIter) ){
i64 rowid = pIter->iRowid;
if( eDetail==FTS5_DETAIL_NONE ){
- cksum ^= sqlite3Fts5IndexEntryCksum(rowid, 0, 0, iIdx, z, n);
+ cksum ^= tdsqlite3Fts5IndexEntryCksum(rowid, 0, 0, iIdx, z, n);
}else{
Fts5PoslistReader sReader;
- for(sqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &sReader);
+ for(tdsqlite3Fts5PoslistReaderInit(pIter->pData, pIter->nData, &sReader);
sReader.bEof==0;
- sqlite3Fts5PoslistReaderNext(&sReader)
+ tdsqlite3Fts5PoslistReaderNext(&sReader)
){
int iCol = FTS5_POS2COLUMN(sReader.iPos);
int iOff = FTS5_POS2OFFSET(sReader.iPos);
- cksum ^= sqlite3Fts5IndexEntryCksum(rowid, iCol, iOff, iIdx, z, n);
+ cksum ^= tdsqlite3Fts5IndexEntryCksum(rowid, iCol, iOff, iIdx, z, n);
}
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IterNext(pIter);
+ rc = tdsqlite3Fts5IterNext(pIter);
}
}
- sqlite3Fts5IterClose(pIter);
+ tdsqlite3Fts5IterClose(pIter);
*pCksum = cksum;
return rc;
}
+/*
+** Check if buffer z[], size n bytes, contains as series of valid utf-8
+** encoded codepoints. If so, return 0. Otherwise, if the buffer does not
+** contain valid utf-8, return non-zero.
+*/
+static int fts5TestUtf8(const char *z, int n){
+ assert_nc( n>0 );
+ int i = 0;
+ while( i<n ){
+ if( (z[i] & 0x80)==0x00 ){
+ i++;
+ }else
+ if( (z[i] & 0xE0)==0xC0 ){
+ if( i+1>=n || (z[i+1] & 0xC0)!=0x80 ) return 1;
+ i += 2;
+ }else
+ if( (z[i] & 0xF0)==0xE0 ){
+ if( i+2>=n || (z[i+1] & 0xC0)!=0x80 || (z[i+2] & 0xC0)!=0x80 ) return 1;
+ i += 3;
+ }else
+ if( (z[i] & 0xF8)==0xF0 ){
+ if( i+3>=n || (z[i+1] & 0xC0)!=0x80 || (z[i+2] & 0xC0)!=0x80 ) return 1;
+ if( (z[i+2] & 0xC0)!=0x80 ) return 1;
+ i += 3;
+ }else{
+ return 1;
+ }
+ }
+
+ return 0;
+}
/*
** This function is also purely an internal test. It does not contribute to
@@ -195355,8 +223729,14 @@ static void fts5TestTerm(
** This check may only be performed if the hash table is empty. This
** is because the hash table only supports a single scan query at
** a time, and the multi-iter loop from which this function is called
- ** is already performing such a scan. */
- if( p->nPendingData==0 ){
+ ** is already performing such a scan.
+ **
+ ** Also only do this if buffer zTerm contains nTerm bytes of valid
+ ** utf-8. Otherwise, the last part of the buffer contents might contain
+ ** a non-utf-8 sequence that happens to be a prefix of a valid utf-8
+ ** character stored in the main fts index, which will cause the
+ ** test to fail. */
+ if( p->nPendingData==0 && 0==fts5TestUtf8(zTerm, nTerm) ){
if( iIdx>0 && rc==SQLITE_OK ){
int f = flags|FTS5INDEX_QUERY_TEST_NOIDX;
ck2 = 0;
@@ -195471,33 +223851,34 @@ static void fts5IndexIntegrityCheckSegment(
Fts5StructureSegment *pSeg /* Segment to check internal consistency */
){
Fts5Config *pConfig = p->pConfig;
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
int rc2;
int iIdxPrevLeaf = pSeg->pgnoFirst-1;
int iDlidxPrevLeaf = pSeg->pgnoLast;
if( pSeg->pgnoFirst==0 ) return;
- fts5IndexPrepareStmt(p, &pStmt, sqlite3_mprintf(
- "SELECT segid, term, (pgno>>1), (pgno&1) FROM %Q.'%q_idx' WHERE segid=%d",
+ fts5IndexPrepareStmt(p, &pStmt, tdsqlite3_mprintf(
+ "SELECT segid, term, (pgno>>1), (pgno&1) FROM %Q.'%q_idx' WHERE segid=%d "
+ "ORDER BY 1, 2",
pConfig->zDb, pConfig->zName, pSeg->iSegid
));
/* Iterate through the b-tree hierarchy. */
- while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ while( p->rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pStmt) ){
i64 iRow; /* Rowid for this leaf */
Fts5Data *pLeaf; /* Data for this leaf */
- int nIdxTerm = sqlite3_column_bytes(pStmt, 1);
- const char *zIdxTerm = (const char*)sqlite3_column_text(pStmt, 1);
- int iIdxLeaf = sqlite3_column_int(pStmt, 2);
- int bIdxDlidx = sqlite3_column_int(pStmt, 3);
+ const char *zIdxTerm = (const char*)tdsqlite3_column_blob(pStmt, 1);
+ int nIdxTerm = tdsqlite3_column_bytes(pStmt, 1);
+ int iIdxLeaf = tdsqlite3_column_int(pStmt, 2);
+ int bIdxDlidx = tdsqlite3_column_int(pStmt, 3);
/* If the leaf in question has already been trimmed from the segment,
** ignore this b-tree entry. Otherwise, load it into memory. */
if( iIdxLeaf<pSeg->pgnoFirst ) continue;
iRow = FTS5_SEGMENT_ROWID(pSeg->iSegid, iIdxLeaf);
- pLeaf = fts5DataRead(p, iRow);
+ pLeaf = fts5LeafRead(p, iRow);
if( pLeaf==0 ) break;
/* Check that the leaf contains at least one term, and that it is equal
@@ -195514,11 +223895,11 @@ static void fts5IndexIntegrityCheckSegment(
iOff = fts5LeafFirstTermOff(pLeaf);
iRowidOff = fts5LeafFirstRowidOff(pLeaf);
- if( iRowidOff>=iOff ){
+ if( iRowidOff>=iOff || iOff>=pLeaf->szLeaf ){
p->rc = FTS5_CORRUPT;
}else{
iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm);
- res = memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm));
+ res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm));
if( res==0 ) res = nTerm - nIdxTerm;
if( res<0 ) p->rc = FTS5_CORRUPT;
}
@@ -195588,7 +223969,7 @@ static void fts5IndexIntegrityCheckSegment(
iIdxPrevLeaf = iIdxLeaf;
}
- rc2 = sqlite3_finalize(pStmt);
+ rc2 = tdsqlite3_finalize(pStmt);
if( p->rc==SQLITE_OK ) p->rc = rc2;
/* Page iter.iLeaf must now be the rightmost leaf-page in the segment */
@@ -195603,14 +223984,14 @@ static void fts5IndexIntegrityCheckSegment(
/*
** Run internal checks to ensure that the FTS index (a) is internally
** consistent and (b) contains entries for which the XOR of the checksums
-** as calculated by sqlite3Fts5IndexEntryCksum() is cksum.
+** as calculated by tdsqlite3Fts5IndexEntryCksum() is cksum.
**
** Return SQLITE_CORRUPT if any of the internal checks fail, or if the
** checksum does not match. Return SQLITE_OK if all checks pass without
** error, or some other SQLite error code if another error (e.g. OOM)
** occurs.
*/
-static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
+static int tdsqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
int eDetail = p->pConfig->eDetail;
u64 cksum2 = 0; /* Checksum based on contents of indexes */
Fts5Buffer poslist = {0,0,0}; /* Buffer used to hold a poslist */
@@ -195666,15 +224047,15 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
if( eDetail==FTS5_DETAIL_NONE ){
if( 0==fts5MultiIterIsEmpty(p, pIter) ){
- cksum2 ^= sqlite3Fts5IndexEntryCksum(iRowid, 0, 0, -1, z, n);
+ cksum2 ^= tdsqlite3Fts5IndexEntryCksum(iRowid, 0, 0, -1, z, n);
}
}else{
poslist.n = 0;
fts5SegiterPoslist(p, &pIter->aSeg[pIter->aFirst[1].iFirst], 0, &poslist);
- while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){
+ while( 0==tdsqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){
int iCol = FTS5_POS2COLUMN(iPos);
int iTokOff = FTS5_POS2OFFSET(iPos);
- cksum2 ^= sqlite3Fts5IndexEntryCksum(iRowid, iCol, iTokOff, -1, z, n);
+ cksum2 ^= tdsqlite3Fts5IndexEntryCksum(iRowid, iCol, iTokOff, -1, z, n);
}
}
}
@@ -195726,13 +224107,13 @@ static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){
if( iSegid==0 ){
if( iKey==FTS5_AVERAGES_ROWID ){
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{averages} ");
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{averages} ");
}else{
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{structure}");
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{structure}");
}
}
else{
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%ssegid=%d h=%d pgno=%d}",
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%ssegid=%d h=%d pgno=%d}",
bDlidx ? "dlidx " : "", iSegid, iHeight, iPgno
);
}
@@ -195747,16 +224128,16 @@ static void fts5DebugStructure(
for(iLvl=0; iLvl<p->nLevel; iLvl++){
Fts5StructureLevel *pLvl = &p->aLevel[iLvl];
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf,
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf,
" {lvl=%d nMerge=%d nSeg=%d", iLvl, pLvl->nMerge, pLvl->nSeg
);
for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){
Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d}",
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d}",
pSeg->iSegid, pSeg->pgnoFirst, pSeg->pgnoLast
);
}
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}");
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}");
}
}
@@ -195802,8 +224183,8 @@ static void fts5DecodeAverages(
while( i<nBlob ){
u64 iVal;
- i += sqlite3Fts5GetVarint(&pBlob[i], &iVal);
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "%s%d", zSpace, (int)iVal);
+ i += tdsqlite3Fts5GetVarint(&pBlob[i], &iVal);
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, "%s%d", zSpace, (int)iVal);
zSpace = " ";
}
}
@@ -195820,7 +224201,7 @@ static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){
while( iOff<n ){
int iVal;
iOff += fts5GetVarint32(&a[iOff], iVal);
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %d", iVal);
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %d", iVal);
}
return iOff;
}
@@ -195838,20 +224219,20 @@ static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){
int iOff = 0;
if( n>0 ){
- iOff = sqlite3Fts5GetVarint(a, (u64*)&iDocid);
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " id=%lld", iDocid);
+ iOff = tdsqlite3Fts5GetVarint(a, (u64*)&iDocid);
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, " id=%lld", iDocid);
}
while( iOff<n ){
int nPos;
int bDel;
iOff += fts5GetPoslistSize(&a[iOff], &nPos, &bDel);
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " nPos=%d%s", nPos, bDel?"*":"");
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, " nPos=%d%s", nPos, bDel?"*":"");
iOff += fts5DecodePoslist(pRc, pBuf, &a[iOff], MIN(n-iOff, nPos));
if( iOff<n ){
i64 iDelta;
- iOff += sqlite3Fts5GetVarint(&a[iOff], (u64*)&iDelta);
+ iOff += tdsqlite3Fts5GetVarint(&a[iOff], (u64*)&iDelta);
iDocid += iDelta;
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " id=%lld", iDocid);
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, " id=%lld", iDocid);
}
}
@@ -195882,7 +224263,7 @@ static void fts5DecodeRowidList(
while( i<nData ){
const char *zApp = "";
u64 iVal;
- i += sqlite3Fts5GetVarint(&pData[i], &iVal);
+ i += tdsqlite3Fts5GetVarint(&pData[i], &iVal);
iRowid += iVal;
if( i<nData && pData[i]==0x00 ){
@@ -195895,7 +224276,7 @@ static void fts5DecodeRowidList(
}
}
- sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp);
+ tdsqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp);
}
}
@@ -195903,9 +224284,9 @@ static void fts5DecodeRowidList(
** The implementation of user-defined scalar function fts5_decode().
*/
static void fts5DecodeFunction(
- sqlite3_context *pCtx, /* Function call context */
+ tdsqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args (always 2) */
- sqlite3_value **apVal /* Function arguments */
+ tdsqlite3_value **apVal /* Function arguments */
){
i64 iRowid; /* Rowid for record being decoded */
int iSegid,iHeight,iPgno,bDlidx;/* Rowid components */
@@ -195913,24 +224294,23 @@ static void fts5DecodeFunction(
u8 *a = 0;
Fts5Buffer s; /* Build up text to return here */
int rc = SQLITE_OK; /* Return code */
- int nSpace = 0;
- int eDetailNone = (sqlite3_user_data(pCtx)!=0);
+ tdsqlite3_int64 nSpace = 0;
+ int eDetailNone = (tdsqlite3_user_data(pCtx)!=0);
assert( nArg==2 );
UNUSED_PARAM(nArg);
memset(&s, 0, sizeof(Fts5Buffer));
- iRowid = sqlite3_value_int64(apVal[0]);
+ iRowid = tdsqlite3_value_int64(apVal[0]);
/* Make a copy of the second argument (a blob) in aBlob[]. The aBlob[]
** copy is followed by FTS5_DATA_ZERO_PADDING 0x00 bytes, which prevents
** buffer overreads even if the record is corrupt. */
- n = sqlite3_value_bytes(apVal[1]);
- aBlob = sqlite3_value_blob(apVal[1]);
+ n = tdsqlite3_value_bytes(apVal[1]);
+ aBlob = tdsqlite3_value_blob(apVal[1]);
nSpace = n + FTS5_DATA_ZERO_PADDING;
- a = (u8*)sqlite3Fts5MallocZero(&rc, nSpace);
+ a = (u8*)tdsqlite3Fts5MallocZero(&rc, nSpace);
if( a==0 ) goto decode_out;
- memcpy(a, aBlob, n);
-
+ if( n>0 ) memcpy(a, aBlob, n);
fts5DecodeRowid(iRowid, &iSegid, &bDlidx, &iHeight, &iPgno);
@@ -195947,7 +224327,7 @@ static void fts5DecodeFunction(
lvl.iLeafPgno = iPgno;
for(fts5DlidxLvlNext(&lvl); lvl.bEof==0; fts5DlidxLvlNext(&lvl)){
- sqlite3Fts5BufferAppendPrintf(&rc, &s,
+ tdsqlite3Fts5BufferAppendPrintf(&rc, &s,
" %d(%lld)", lvl.iLeafPgno, lvl.iRowid
);
}
@@ -195983,7 +224363,7 @@ static void fts5DecodeFunction(
iOff += fts5GetVarint32(&a[iOff], nAppend);
term.n = nKeep;
fts5BufferAppendBlob(&rc, &term, nAppend, &a[iOff]);
- sqlite3Fts5BufferAppendPrintf(
+ tdsqlite3Fts5BufferAppendPrintf(
&rc, &s, " term=%.*s", term.n, (const char*)term.p
);
iOff += nAppend;
@@ -196018,13 +224398,16 @@ static void fts5DecodeFunction(
memset(&term, 0, sizeof(Fts5Buffer));
if( n<4 ){
- sqlite3Fts5BufferSet(&rc, &s, 7, (const u8*)"corrupt");
+ tdsqlite3Fts5BufferSet(&rc, &s, 7, (const u8*)"corrupt");
goto decode_out;
}else{
iRowidOff = fts5GetU16(&a[0]);
iPgidxOff = szLeaf = fts5GetU16(&a[2]);
if( iPgidxOff<n ){
fts5GetVarint32(&a[iPgidxOff], iTermOff);
+ }else if( iPgidxOff>n ){
+ rc = FTS5_CORRUPT;
+ goto decode_out;
}
}
@@ -196036,14 +224419,22 @@ static void fts5DecodeFunction(
}else{
iOff = szLeaf;
}
+ if( iOff>n ){
+ rc = FTS5_CORRUPT;
+ goto decode_out;
+ }
fts5DecodePoslist(&rc, &s, &a[4], iOff-4);
/* Decode any more doclist data that appears on the page before the
** first term. */
nDoclist = (iTermOff ? iTermOff : szLeaf) - iOff;
+ if( nDoclist+iOff>n ){
+ rc = FTS5_CORRUPT;
+ goto decode_out;
+ }
fts5DecodeDoclist(&rc, &s, &a[iOff], nDoclist);
- while( iPgidxOff<n ){
+ while( iPgidxOff<n && rc==SQLITE_OK ){
int bFirst = (iPgidxOff==szLeaf); /* True for first term on page */
int nByte; /* Bytes of data */
int iEnd;
@@ -196058,16 +224449,28 @@ static void fts5DecodeFunction(
}else{
iEnd = szLeaf;
}
+ if( iEnd>szLeaf ){
+ rc = FTS5_CORRUPT;
+ break;
+ }
if( bFirst==0 ){
iOff += fts5GetVarint32(&a[iOff], nByte);
+ if( nByte>term.n ){
+ rc = FTS5_CORRUPT;
+ break;
+ }
term.n = nByte;
}
iOff += fts5GetVarint32(&a[iOff], nByte);
+ if( iOff+nByte>n ){
+ rc = FTS5_CORRUPT;
+ break;
+ }
fts5BufferAppendBlob(&rc, &term, nByte, &a[iOff]);
iOff += nByte;
- sqlite3Fts5BufferAppendPrintf(
+ tdsqlite3Fts5BufferAppendPrintf(
&rc, &s, " term=%.*s", term.n, (const char*)term.p
);
iOff += fts5DecodeDoclist(&rc, &s, &a[iOff], iEnd-iOff);
@@ -196077,11 +224480,11 @@ static void fts5DecodeFunction(
}
decode_out:
- sqlite3_free(a);
+ tdsqlite3_free(a);
if( rc==SQLITE_OK ){
- sqlite3_result_text(pCtx, (const char*)s.p, s.n, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(pCtx, (const char*)s.p, s.n, SQLITE_TRANSIENT);
}else{
- sqlite3_result_error_code(pCtx, rc);
+ tdsqlite3_result_error_code(pCtx, rc);
}
fts5BufferFree(&s);
}
@@ -196090,30 +224493,30 @@ static void fts5DecodeFunction(
** The implementation of user-defined scalar function fts5_rowid().
*/
static void fts5RowidFunction(
- sqlite3_context *pCtx, /* Function call context */
+ tdsqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args (always 2) */
- sqlite3_value **apVal /* Function arguments */
+ tdsqlite3_value **apVal /* Function arguments */
){
const char *zArg;
if( nArg==0 ){
- sqlite3_result_error(pCtx, "should be: fts5_rowid(subject, ....)", -1);
+ tdsqlite3_result_error(pCtx, "should be: fts5_rowid(subject, ....)", -1);
}else{
- zArg = (const char*)sqlite3_value_text(apVal[0]);
- if( 0==sqlite3_stricmp(zArg, "segment") ){
+ zArg = (const char*)tdsqlite3_value_text(apVal[0]);
+ if( 0==tdsqlite3_stricmp(zArg, "segment") ){
i64 iRowid;
int segid, pgno;
if( nArg!=3 ){
- sqlite3_result_error(pCtx,
+ tdsqlite3_result_error(pCtx,
"should be: fts5_rowid('segment', segid, pgno))", -1
);
}else{
- segid = sqlite3_value_int(apVal[1]);
- pgno = sqlite3_value_int(apVal[2]);
+ segid = tdsqlite3_value_int(apVal[1]);
+ pgno = tdsqlite3_value_int(apVal[2]);
iRowid = FTS5_SEGMENT_ROWID(segid, pgno);
- sqlite3_result_int64(pCtx, iRowid);
+ tdsqlite3_result_int64(pCtx, iRowid);
}
}else{
- sqlite3_result_error(pCtx,
+ tdsqlite3_result_error(pCtx,
"first arg to fts5_rowid() must be 'segment'" , -1
);
}
@@ -196128,20 +224531,20 @@ static void fts5RowidFunction(
** If successful, SQLITE_OK is returned. If an error occurs, some other
** SQLite error code is returned instead.
*/
-static int sqlite3Fts5IndexInit(sqlite3 *db){
- int rc = sqlite3_create_function(
+static int tdsqlite3Fts5IndexInit(tdsqlite3 *db){
+ int rc = tdsqlite3_create_function(
db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0
);
if( rc==SQLITE_OK ){
- rc = sqlite3_create_function(
+ rc = tdsqlite3_create_function(
db, "fts5_decode_none", 2,
SQLITE_UTF8, (void*)db, fts5DecodeFunction, 0, 0
);
}
if( rc==SQLITE_OK ){
- rc = sqlite3_create_function(
+ rc = tdsqlite3_create_function(
db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0
);
}
@@ -196149,7 +224552,7 @@ static int sqlite3Fts5IndexInit(sqlite3 *db){
}
-static int sqlite3Fts5IndexReset(Fts5Index *p){
+static int tdsqlite3Fts5IndexReset(Fts5Index *p){
assert( p->pStruct==0 || p->iStructVersion!=0 );
if( fts5IndexDataVersion(p)!=p->iStructVersion ){
fts5StructureInvalidate(p);
@@ -196181,14 +224584,14 @@ static int sqlite3Fts5IndexReset(Fts5Index *p){
** assert() conditions in the fts5 code are activated - conditions that are
** only true if it is guaranteed that the fts5 database is not corrupt.
*/
-SQLITE_API int sqlite3_fts5_may_be_corrupt = 1;
+SQLITE_API int tdsqlite3_fts5_may_be_corrupt = 1;
typedef struct Fts5Auxdata Fts5Auxdata;
typedef struct Fts5Auxiliary Fts5Auxiliary;
typedef struct Fts5Cursor Fts5Cursor;
+typedef struct Fts5FullTable Fts5FullTable;
typedef struct Fts5Sorter Fts5Sorter;
-typedef struct Fts5Table Fts5Table;
typedef struct Fts5TokenizerModule Fts5TokenizerModule;
/*
@@ -196234,7 +224637,7 @@ struct Fts5TransactionState {
*/
struct Fts5Global {
fts5_api api; /* User visible part of object (see fts5.h) */
- sqlite3 *db; /* Associated database connection */
+ tdsqlite3 *db; /* Associated database connection */
i64 iNextId; /* Used to allocate unique cursor ids */
Fts5Auxiliary *pAux; /* First in list of all aux. functions */
Fts5TokenizerModule *pTok; /* First in list of all tokenizer modules */
@@ -196269,13 +224672,8 @@ struct Fts5TokenizerModule {
Fts5TokenizerModule *pNext; /* Next registered tokenizer module */
};
-/*
-** Virtual-table object.
-*/
-struct Fts5Table {
- sqlite3_vtab base; /* Base class used by SQLite core */
- Fts5Config *pConfig; /* Virtual table configuration */
- Fts5Index *pIndex; /* Full-text index */
+struct Fts5FullTable {
+ Fts5Table p; /* Public class members from fts5Int.h */
Fts5Storage *pStorage; /* Document store */
Fts5Global *pGlobal; /* Global (connection wide) data */
Fts5Cursor *pSortCsr; /* Sort data from this cursor */
@@ -196299,7 +224697,7 @@ struct Fts5MatchPhrase {
** byte of the position list for the corresponding phrase.
*/
struct Fts5Sorter {
- sqlite3_stmt *pStmt;
+ tdsqlite3_stmt *pStmt;
i64 iRowid; /* Current rowid */
const u8 *aPoslist; /* Position lists for current row */
int nIdx; /* Number of entries in aIdx[] */
@@ -196327,7 +224725,7 @@ struct Fts5Sorter {
** the lower.
*/
struct Fts5Cursor {
- sqlite3_vtab_cursor base; /* Base class used by SQLite core */
+ tdsqlite3_vtab_cursor base; /* Base class used by SQLite core */
Fts5Cursor *pNext; /* Next cursor in Fts5Cursor.pCsr list */
int *aColumnSize; /* Values for xColumnSize() */
i64 iCsrId; /* Cursor id */
@@ -196337,7 +224735,7 @@ struct Fts5Cursor {
int bDesc; /* True for "ORDER BY rowid DESC" queries */
i64 iFirstRowid; /* Return no rowids earlier than this */
i64 iLastRowid; /* Return no rowids later than this */
- sqlite3_stmt *pStmt; /* Statement used to read %_content */
+ tdsqlite3_stmt *pStmt; /* Statement used to read %_content */
Fts5Expr *pExpr; /* Expression for MATCH queries */
Fts5Sorter *pSorter; /* Sorter for "ORDER BY rank" queries */
int csrflags; /* Mask of cursor flags (see below) */
@@ -196348,8 +224746,8 @@ struct Fts5Cursor {
char *zRankArgs; /* Custom rank function args */
Fts5Auxiliary *pRank; /* Rank callback (or NULL) */
int nRankArg; /* Number of trailing arguments for rank() */
- sqlite3_value **apRankArg; /* Array of trailing arguments */
- sqlite3_stmt *pRankArgStmt; /* Origin of objects in apRankArg[] */
+ tdsqlite3_value **apRankArg; /* Array of trailing arguments */
+ tdsqlite3_stmt *pRankArgStmt; /* Origin of objects in apRankArg[] */
/* Auxiliary data storage */
Fts5Auxiliary *pAux; /* Currently executing extension function */
@@ -196413,7 +224811,7 @@ struct Fts5Auxdata {
#define FTS5_SAVEPOINT 5
#define FTS5_RELEASE 6
#define FTS5_ROLLBACKTO 7
-static void fts5CheckTransactionState(Fts5Table *p, int op, int iSavepoint){
+static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){
switch( op ){
case FTS5_BEGIN:
assert( p->ts.eState==0 );
@@ -196439,7 +224837,7 @@ static void fts5CheckTransactionState(Fts5Table *p, int op, int iSavepoint){
case FTS5_SAVEPOINT:
assert( p->ts.eState==1 );
assert( iSavepoint>=0 );
- assert( iSavepoint>p->ts.iSavepoint );
+ assert( iSavepoint>=p->ts.iSavepoint );
p->ts.iSavepoint = iSavepoint;
break;
@@ -196452,8 +224850,11 @@ static void fts5CheckTransactionState(Fts5Table *p, int op, int iSavepoint){
case FTS5_ROLLBACKTO:
assert( p->ts.eState==1 );
- assert( iSavepoint>=0 );
- assert( iSavepoint<=p->ts.iSavepoint );
+ assert( iSavepoint>=-1 );
+ /* The following assert() can fail if another vtab strikes an error
+ ** within an xSavepoint() call then SQLite calls xRollbackTo() - without
+ ** having called xSavepoint() on this vtab. */
+ /* assert( iSavepoint<=p->ts.iSavepoint ); */
p->ts.iSavepoint = iSavepoint;
break;
}
@@ -196465,38 +224866,38 @@ static void fts5CheckTransactionState(Fts5Table *p, int op, int iSavepoint){
/*
** Return true if pTab is a contentless table.
*/
-static int fts5IsContentless(Fts5Table *pTab){
- return pTab->pConfig->eContent==FTS5_CONTENT_NONE;
+static int fts5IsContentless(Fts5FullTable *pTab){
+ return pTab->p.pConfig->eContent==FTS5_CONTENT_NONE;
}
/*
** Delete a virtual table handle allocated by fts5InitVtab().
*/
-static void fts5FreeVtab(Fts5Table *pTab){
+static void fts5FreeVtab(Fts5FullTable *pTab){
if( pTab ){
- sqlite3Fts5IndexClose(pTab->pIndex);
- sqlite3Fts5StorageClose(pTab->pStorage);
- sqlite3Fts5ConfigFree(pTab->pConfig);
- sqlite3_free(pTab);
+ tdsqlite3Fts5IndexClose(pTab->p.pIndex);
+ tdsqlite3Fts5StorageClose(pTab->pStorage);
+ tdsqlite3Fts5ConfigFree(pTab->p.pConfig);
+ tdsqlite3_free(pTab);
}
}
/*
** The xDisconnect() virtual table method.
*/
-static int fts5DisconnectMethod(sqlite3_vtab *pVtab){
- fts5FreeVtab((Fts5Table*)pVtab);
+static int fts5DisconnectMethod(tdsqlite3_vtab *pVtab){
+ fts5FreeVtab((Fts5FullTable*)pVtab);
return SQLITE_OK;
}
/*
** The xDestroy() virtual table method.
*/
-static int fts5DestroyMethod(sqlite3_vtab *pVtab){
+static int fts5DestroyMethod(tdsqlite3_vtab *pVtab){
Fts5Table *pTab = (Fts5Table*)pVtab;
- int rc = sqlite3Fts5DropAll(pTab->pConfig);
+ int rc = tdsqlite3Fts5DropAll(pTab->pConfig);
if( rc==SQLITE_OK ){
- fts5FreeVtab((Fts5Table*)pVtab);
+ fts5FreeVtab((Fts5FullTable*)pVtab);
}
return rc;
}
@@ -196514,53 +224915,53 @@ static int fts5DestroyMethod(sqlite3_vtab *pVtab){
*/
static int fts5InitVtab(
int bCreate, /* True for xCreate, false for xConnect */
- sqlite3 *db, /* The SQLite database connection */
+ tdsqlite3 *db, /* The SQLite database connection */
void *pAux, /* Hash table containing tokenizers */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
+ tdsqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
char **pzErr /* Write any error message here */
){
Fts5Global *pGlobal = (Fts5Global*)pAux;
const char **azConfig = (const char**)argv;
int rc = SQLITE_OK; /* Return code */
Fts5Config *pConfig = 0; /* Results of parsing argc/argv */
- Fts5Table *pTab = 0; /* New virtual table object */
+ Fts5FullTable *pTab = 0; /* New virtual table object */
/* Allocate the new vtab object and parse the configuration */
- pTab = (Fts5Table*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Table));
+ pTab = (Fts5FullTable*)tdsqlite3Fts5MallocZero(&rc, sizeof(Fts5FullTable));
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5ConfigParse(pGlobal, db, argc, azConfig, &pConfig, pzErr);
+ rc = tdsqlite3Fts5ConfigParse(pGlobal, db, argc, azConfig, &pConfig, pzErr);
assert( (rc==SQLITE_OK && *pzErr==0) || pConfig==0 );
}
if( rc==SQLITE_OK ){
- pTab->pConfig = pConfig;
+ pTab->p.pConfig = pConfig;
pTab->pGlobal = pGlobal;
}
/* Open the index sub-system */
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexOpen(pConfig, bCreate, &pTab->pIndex, pzErr);
+ rc = tdsqlite3Fts5IndexOpen(pConfig, bCreate, &pTab->p.pIndex, pzErr);
}
/* Open the storage sub-system */
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageOpen(
- pConfig, pTab->pIndex, bCreate, &pTab->pStorage, pzErr
+ rc = tdsqlite3Fts5StorageOpen(
+ pConfig, pTab->p.pIndex, bCreate, &pTab->pStorage, pzErr
);
}
- /* Call sqlite3_declare_vtab() */
+ /* Call tdsqlite3_declare_vtab() */
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5ConfigDeclareVtab(pConfig);
+ rc = tdsqlite3Fts5ConfigDeclareVtab(pConfig);
}
/* Load the initial configuration */
if( rc==SQLITE_OK ){
assert( pConfig->pzErrmsg==0 );
pConfig->pzErrmsg = pzErr;
- rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex);
- sqlite3Fts5IndexRollback(pTab->pIndex);
+ rc = tdsqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
+ tdsqlite3Fts5IndexRollback(pTab->p.pIndex);
pConfig->pzErrmsg = 0;
}
@@ -196570,7 +224971,7 @@ static int fts5InitVtab(
}else if( bCreate ){
fts5CheckTransactionState(pTab, FTS5_BEGIN, 0);
}
- *ppVTab = (sqlite3_vtab*)pTab;
+ *ppVTab = (tdsqlite3_vtab*)pTab;
return rc;
}
@@ -196579,22 +224980,22 @@ static int fts5InitVtab(
** work is done in function fts5InitVtab().
*/
static int fts5ConnectMethod(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
void *pAux, /* Pointer to tokenizer hash table */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
- char **pzErr /* OUT: sqlite3_malloc'd error message */
+ tdsqlite3_vtab **ppVtab, /* OUT: New tdsqlite3_vtab object */
+ char **pzErr /* OUT: tdsqlite3_malloc'd error message */
){
return fts5InitVtab(0, db, pAux, argc, argv, ppVtab, pzErr);
}
static int fts5CreateMethod(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
void *pAux, /* Pointer to tokenizer hash table */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
- char **pzErr /* OUT: sqlite3_malloc'd error message */
+ tdsqlite3_vtab **ppVtab, /* OUT: New tdsqlite3_vtab object */
+ char **pzErr /* OUT: tdsqlite3_malloc'd error message */
){
return fts5InitVtab(1, db, pAux, argc, argv, ppVtab, pzErr);
}
@@ -196614,10 +225015,10 @@ static int fts5CreateMethod(
** extension is currently being used by a version of SQLite too old to
** support index-info flags. In that case this function is a no-op.
*/
-static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){
+static void fts5SetUniqueFlag(tdsqlite3_index_info *pIdxInfo){
#if SQLITE_VERSION_NUMBER>=3008012
#ifndef SQLITE_CORE
- if( sqlite3_libversion_number()>=3008012 )
+ if( tdsqlite3_libversion_number()>=3008012 )
#endif
{
pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE;
@@ -196629,17 +225030,39 @@ static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){
** Implementation of the xBestIndex method for FTS5 tables. Within the
** WHERE constraint, it searches for the following:
**
-** 1. A MATCH constraint against the special column.
+** 1. A MATCH constraint against the table column.
** 2. A MATCH constraint against the "rank" column.
-** 3. An == constraint against the rowid column.
-** 4. A < or <= constraint against the rowid column.
-** 5. A > or >= constraint against the rowid column.
+** 3. A MATCH constraint against some other column.
+** 4. An == constraint against the rowid column.
+** 5. A < or <= constraint against the rowid column.
+** 6. A > or >= constraint against the rowid column.
**
-** Within the ORDER BY, either:
+** Within the ORDER BY, the following are supported:
**
** 5. ORDER BY rank [ASC|DESC]
** 6. ORDER BY rowid [ASC|DESC]
**
+** Information for the xFilter call is passed via both the idxNum and
+** idxStr variables. Specifically, idxNum is a bitmask of the following
+** flags used to encode the ORDER BY clause:
+**
+** FTS5_BI_ORDER_RANK
+** FTS5_BI_ORDER_ROWID
+** FTS5_BI_ORDER_DESC
+**
+** idxStr is used to encode data from the WHERE clause. For each argument
+** passed to the xFilter method, the following is appended to idxStr:
+**
+** Match against table column: "m"
+** Match against rank column: "r"
+** Match against other column: "<column-number>"
+** Equality constraint against the rowid: "="
+** A < or <= against the rowid: "<"
+** A > or >= against the rowid: ">"
+**
+** This function ensures that there is at most one "r" or "=". And that if
+** there exists an "=" then there is no "<" or ">".
+**
** Costs are assigned as follows:
**
** a) If an unusable MATCH operator is present in the WHERE clause, the
@@ -196662,61 +225085,109 @@ static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){
**
** Costs are not modified by the ORDER BY clause.
*/
-static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
+static int fts5BestIndexMethod(tdsqlite3_vtab *pVTab, tdsqlite3_index_info *pInfo){
Fts5Table *pTab = (Fts5Table*)pVTab;
Fts5Config *pConfig = pTab->pConfig;
+ const int nCol = pConfig->nCol;
int idxFlags = 0; /* Parameter passed through to xFilter() */
- int bHasMatch;
- int iNext;
int i;
- struct Constraint {
- int op; /* Mask against sqlite3_index_constraint.op */
- int fts5op; /* FTS5 mask for idxFlags */
- int iCol; /* 0==rowid, 1==tbl, 2==rank */
- int omit; /* True to omit this if found */
- int iConsIndex; /* Index in pInfo->aConstraint[] */
- } aConstraint[] = {
- {SQLITE_INDEX_CONSTRAINT_MATCH|SQLITE_INDEX_CONSTRAINT_EQ,
- FTS5_BI_MATCH, 1, 1, -1},
- {SQLITE_INDEX_CONSTRAINT_MATCH|SQLITE_INDEX_CONSTRAINT_EQ,
- FTS5_BI_RANK, 2, 1, -1},
- {SQLITE_INDEX_CONSTRAINT_EQ, FTS5_BI_ROWID_EQ, 0, 0, -1},
- {SQLITE_INDEX_CONSTRAINT_LT|SQLITE_INDEX_CONSTRAINT_LE,
- FTS5_BI_ROWID_LE, 0, 0, -1},
- {SQLITE_INDEX_CONSTRAINT_GT|SQLITE_INDEX_CONSTRAINT_GE,
- FTS5_BI_ROWID_GE, 0, 0, -1},
- };
+ char *idxStr;
+ int iIdxStr = 0;
+ int iCons = 0;
+
+ int bSeenEq = 0;
+ int bSeenGt = 0;
+ int bSeenLt = 0;
+ int bSeenMatch = 0;
+ int bSeenRank = 0;
+
+
+ assert( SQLITE_INDEX_CONSTRAINT_EQ<SQLITE_INDEX_CONSTRAINT_MATCH );
+ assert( SQLITE_INDEX_CONSTRAINT_GT<SQLITE_INDEX_CONSTRAINT_MATCH );
+ assert( SQLITE_INDEX_CONSTRAINT_LE<SQLITE_INDEX_CONSTRAINT_MATCH );
+ assert( SQLITE_INDEX_CONSTRAINT_GE<SQLITE_INDEX_CONSTRAINT_MATCH );
+ assert( SQLITE_INDEX_CONSTRAINT_LE<SQLITE_INDEX_CONSTRAINT_MATCH );
- int aColMap[3];
- aColMap[0] = -1;
- aColMap[1] = pConfig->nCol;
- aColMap[2] = pConfig->nCol+1;
+ if( pConfig->bLock ){
+ pTab->base.zErrMsg = tdsqlite3_mprintf(
+ "recursively defined fts5 content table"
+ );
+ return SQLITE_ERROR;
+ }
+
+ idxStr = (char*)tdsqlite3_malloc(pInfo->nConstraint * 6 + 1);
+ if( idxStr==0 ) return SQLITE_NOMEM;
+ pInfo->idxStr = idxStr;
+ pInfo->needToFreeIdxStr = 1;
- /* Set idxFlags flags for all WHERE clause terms that will be used. */
for(i=0; i<pInfo->nConstraint; i++){
- struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
- int j;
- for(j=0; j<ArraySize(aConstraint); j++){
- struct Constraint *pC = &aConstraint[j];
- if( p->iColumn==aColMap[pC->iCol] && p->op & pC->op ){
- if( p->usable ){
- pC->iConsIndex = i;
- idxFlags |= pC->fts5op;
- }else if( j==0 ){
- /* As there exists an unusable MATCH constraint this is an
- ** unusable plan. Set a prohibitively high cost. */
- pInfo->estimatedCost = 1e50;
- return SQLITE_OK;
+ struct tdsqlite3_index_constraint *p = &pInfo->aConstraint[i];
+ int iCol = p->iColumn;
+ if( p->op==SQLITE_INDEX_CONSTRAINT_MATCH
+ || (p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol>=nCol)
+ ){
+ /* A MATCH operator or equivalent */
+ if( p->usable==0 || iCol<0 ){
+ /* As there exists an unusable MATCH constraint this is an
+ ** unusable plan. Set a prohibitively high cost. */
+ pInfo->estimatedCost = 1e50;
+ assert( iIdxStr < pInfo->nConstraint*6 + 1 );
+ idxStr[iIdxStr] = 0;
+ return SQLITE_OK;
+ }else{
+ if( iCol==nCol+1 ){
+ if( bSeenRank ) continue;
+ idxStr[iIdxStr++] = 'r';
+ bSeenRank = 1;
+ }else{
+ bSeenMatch = 1;
+ idxStr[iIdxStr++] = 'm';
+ if( iCol<nCol ){
+ tdsqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol);
+ idxStr += strlen(&idxStr[iIdxStr]);
+ assert( idxStr[iIdxStr]=='\0' );
+ }
+ }
+ pInfo->aConstraintUsage[i].argvIndex = ++iCons;
+ pInfo->aConstraintUsage[i].omit = 1;
+ }
+ }
+ else if( p->usable && bSeenEq==0
+ && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0
+ ){
+ idxStr[iIdxStr++] = '=';
+ bSeenEq = 1;
+ pInfo->aConstraintUsage[i].argvIndex = ++iCons;
+ }
+ }
+
+ if( bSeenEq==0 ){
+ for(i=0; i<pInfo->nConstraint; i++){
+ struct tdsqlite3_index_constraint *p = &pInfo->aConstraint[i];
+ if( p->iColumn<0 && p->usable ){
+ int op = p->op;
+ if( op==SQLITE_INDEX_CONSTRAINT_LT || op==SQLITE_INDEX_CONSTRAINT_LE ){
+ if( bSeenLt ) continue;
+ idxStr[iIdxStr++] = '<';
+ pInfo->aConstraintUsage[i].argvIndex = ++iCons;
+ bSeenLt = 1;
+ }else
+ if( op==SQLITE_INDEX_CONSTRAINT_GT || op==SQLITE_INDEX_CONSTRAINT_GE ){
+ if( bSeenGt ) continue;
+ idxStr[iIdxStr++] = '>';
+ pInfo->aConstraintUsage[i].argvIndex = ++iCons;
+ bSeenGt = 1;
}
}
}
}
+ idxStr[iIdxStr] = '\0';
/* Set idxFlags flags for the ORDER BY clause */
if( pInfo->nOrderBy==1 ){
int iSort = pInfo->aOrderBy[0].iColumn;
- if( iSort==(pConfig->nCol+1) && BitFlagTest(idxFlags, FTS5_BI_MATCH) ){
+ if( iSort==(pConfig->nCol+1) && bSeenMatch ){
idxFlags |= FTS5_BI_ORDER_RANK;
}else if( iSort==-1 ){
idxFlags |= FTS5_BI_ORDER_ROWID;
@@ -196730,57 +225201,46 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
}
/* Calculate the estimated cost based on the flags set in idxFlags. */
- bHasMatch = BitFlagTest(idxFlags, FTS5_BI_MATCH);
- if( BitFlagTest(idxFlags, FTS5_BI_ROWID_EQ) ){
- pInfo->estimatedCost = bHasMatch ? 100.0 : 10.0;
- if( bHasMatch==0 ) fts5SetUniqueFlag(pInfo);
- }else if( BitFlagAllTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){
- pInfo->estimatedCost = bHasMatch ? 500.0 : 250000.0;
- }else if( BitFlagTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){
- pInfo->estimatedCost = bHasMatch ? 750.0 : 750000.0;
+ if( bSeenEq ){
+ pInfo->estimatedCost = bSeenMatch ? 100.0 : 10.0;
+ if( bSeenMatch==0 ) fts5SetUniqueFlag(pInfo);
+ }else if( bSeenLt && bSeenGt ){
+ pInfo->estimatedCost = bSeenMatch ? 500.0 : 250000.0;
+ }else if( bSeenLt || bSeenGt ){
+ pInfo->estimatedCost = bSeenMatch ? 750.0 : 750000.0;
}else{
- pInfo->estimatedCost = bHasMatch ? 1000.0 : 1000000.0;
- }
-
- /* Assign argvIndex values to each constraint in use. */
- iNext = 1;
- for(i=0; i<ArraySize(aConstraint); i++){
- struct Constraint *pC = &aConstraint[i];
- if( pC->iConsIndex>=0 ){
- pInfo->aConstraintUsage[pC->iConsIndex].argvIndex = iNext++;
- pInfo->aConstraintUsage[pC->iConsIndex].omit = (unsigned char)pC->omit;
- }
+ pInfo->estimatedCost = bSeenMatch ? 1000.0 : 1000000.0;
}
pInfo->idxNum = idxFlags;
return SQLITE_OK;
}
-static int fts5NewTransaction(Fts5Table *pTab){
+static int fts5NewTransaction(Fts5FullTable *pTab){
Fts5Cursor *pCsr;
for(pCsr=pTab->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){
- if( pCsr->base.pVtab==(sqlite3_vtab*)pTab ) return SQLITE_OK;
+ if( pCsr->base.pVtab==(tdsqlite3_vtab*)pTab ) return SQLITE_OK;
}
- return sqlite3Fts5StorageReset(pTab->pStorage);
+ return tdsqlite3Fts5StorageReset(pTab->pStorage);
}
/*
** Implementation of xOpen method.
*/
-static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
- Fts5Table *pTab = (Fts5Table*)pVTab;
- Fts5Config *pConfig = pTab->pConfig;
+static int fts5OpenMethod(tdsqlite3_vtab *pVTab, tdsqlite3_vtab_cursor **ppCsr){
+ Fts5FullTable *pTab = (Fts5FullTable*)pVTab;
+ Fts5Config *pConfig = pTab->p.pConfig;
Fts5Cursor *pCsr = 0; /* New cursor object */
- int nByte; /* Bytes of space to allocate */
+ tdsqlite3_int64 nByte; /* Bytes of space to allocate */
int rc; /* Return code */
rc = fts5NewTransaction(pTab);
if( rc==SQLITE_OK ){
nByte = sizeof(Fts5Cursor) + pConfig->nCol * sizeof(int);
- pCsr = (Fts5Cursor*)sqlite3_malloc(nByte);
+ pCsr = (Fts5Cursor*)tdsqlite3_malloc64(nByte);
if( pCsr ){
Fts5Global *pGlobal = pTab->pGlobal;
- memset(pCsr, 0, nByte);
+ memset(pCsr, 0, (size_t)nByte);
pCsr->aColumnSize = (int*)&pCsr[1];
pCsr->pNext = pGlobal->pCsr;
pGlobal->pCsr = pCsr;
@@ -196789,7 +225249,7 @@ static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
rc = SQLITE_NOMEM;
}
}
- *ppCsr = (sqlite3_vtab_cursor*)pCsr;
+ *ppCsr = (tdsqlite3_vtab_cursor*)pCsr;
return rc;
}
@@ -196815,40 +225275,41 @@ static void fts5CsrNewrow(Fts5Cursor *pCsr){
}
static void fts5FreeCursorComponents(Fts5Cursor *pCsr){
- Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab);
Fts5Auxdata *pData;
Fts5Auxdata *pNext;
- sqlite3_free(pCsr->aInstIter);
- sqlite3_free(pCsr->aInst);
+ tdsqlite3_free(pCsr->aInstIter);
+ tdsqlite3_free(pCsr->aInst);
if( pCsr->pStmt ){
int eStmt = fts5StmtType(pCsr);
- sqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt);
+ tdsqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt);
}
if( pCsr->pSorter ){
Fts5Sorter *pSorter = pCsr->pSorter;
- sqlite3_finalize(pSorter->pStmt);
- sqlite3_free(pSorter);
+ tdsqlite3_finalize(pSorter->pStmt);
+ tdsqlite3_free(pSorter);
}
if( pCsr->ePlan!=FTS5_PLAN_SOURCE ){
- sqlite3Fts5ExprFree(pCsr->pExpr);
+ tdsqlite3Fts5ExprFree(pCsr->pExpr);
}
for(pData=pCsr->pAuxdata; pData; pData=pNext){
pNext = pData->pNext;
if( pData->xDelete ) pData->xDelete(pData->pPtr);
- sqlite3_free(pData);
+ tdsqlite3_free(pData);
}
- sqlite3_finalize(pCsr->pRankArgStmt);
- sqlite3_free(pCsr->apRankArg);
+ tdsqlite3_finalize(pCsr->pRankArgStmt);
+ tdsqlite3_free(pCsr->apRankArg);
if( CsrFlagTest(pCsr, FTS5CSR_FREE_ZRANK) ){
- sqlite3_free(pCsr->zRank);
- sqlite3_free(pCsr->zRankArgs);
+ tdsqlite3_free(pCsr->zRank);
+ tdsqlite3_free(pCsr->zRankArgs);
}
+ tdsqlite3Fts5IndexCloseReader(pTab->p.pIndex);
memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan - (u8*)pCsr));
}
@@ -196857,9 +225318,9 @@ static void fts5FreeCursorComponents(Fts5Cursor *pCsr){
** Close the cursor. For additional information see the documentation
** on the xClose method of the virtual table interface.
*/
-static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){
+static int fts5CloseMethod(tdsqlite3_vtab_cursor *pCursor){
if( pCursor ){
- Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCursor->pVtab);
Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
Fts5Cursor **pp;
@@ -196868,7 +225329,7 @@ static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){
for(pp=&pTab->pGlobal->pCsr; (*pp)!=pCsr; pp=&(*pp)->pNext);
*pp = pCsr->pNext;
- sqlite3_free(pCsr);
+ tdsqlite3_free(pCsr);
}
return SQLITE_OK;
}
@@ -196877,7 +225338,7 @@ static int fts5SorterNext(Fts5Cursor *pCsr){
Fts5Sorter *pSorter = pCsr->pSorter;
int rc;
- rc = sqlite3_step(pSorter->pStmt);
+ rc = tdsqlite3_step(pSorter->pStmt);
if( rc==SQLITE_DONE ){
rc = SQLITE_OK;
CsrFlagSet(pCsr, FTS5CSR_EOF);
@@ -196889,9 +225350,9 @@ static int fts5SorterNext(Fts5Cursor *pCsr){
int iOff = 0;
rc = SQLITE_OK;
- pSorter->iRowid = sqlite3_column_int64(pSorter->pStmt, 0);
- nBlob = sqlite3_column_bytes(pSorter->pStmt, 1);
- aBlob = a = sqlite3_column_blob(pSorter->pStmt, 1);
+ pSorter->iRowid = tdsqlite3_column_int64(pSorter->pStmt, 0);
+ nBlob = tdsqlite3_column_bytes(pSorter->pStmt, 1);
+ aBlob = a = tdsqlite3_column_blob(pSorter->pStmt, 1);
/* nBlob==0 in detail=none mode. */
if( nBlob>0 ){
@@ -196916,11 +225377,11 @@ static int fts5SorterNext(Fts5Cursor *pCsr){
** Set the FTS5CSR_REQUIRE_RESEEK flag on all FTS5_PLAN_MATCH cursors
** open on table pTab.
*/
-static void fts5TripCursors(Fts5Table *pTab){
+static void fts5TripCursors(Fts5FullTable *pTab){
Fts5Cursor *pCsr;
for(pCsr=pTab->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){
if( pCsr->ePlan==FTS5_PLAN_MATCH
- && pCsr->base.pVtab==(sqlite3_vtab*)pTab
+ && pCsr->base.pVtab==(tdsqlite3_vtab*)pTab
){
CsrFlagSet(pCsr, FTS5CSR_REQUIRE_RESEEK);
}
@@ -196943,18 +225404,18 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){
int rc = SQLITE_OK;
assert( *pbSkip==0 );
if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_RESEEK) ){
- Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab);
int bDesc = pCsr->bDesc;
- i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr);
+ i64 iRowid = tdsqlite3Fts5ExprRowid(pCsr->pExpr);
- rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->pIndex, iRowid, bDesc);
- if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){
+ rc = tdsqlite3Fts5ExprFirst(pCsr->pExpr, pTab->p.pIndex, iRowid, bDesc);
+ if( rc==SQLITE_OK && iRowid!=tdsqlite3Fts5ExprRowid(pCsr->pExpr) ){
*pbSkip = 1;
}
CsrFlagClear(pCsr, FTS5CSR_REQUIRE_RESEEK);
fts5CsrNewrow(pCsr);
- if( sqlite3Fts5ExprEof(pCsr->pExpr) ){
+ if( tdsqlite3Fts5ExprEof(pCsr->pExpr) ){
CsrFlagSet(pCsr, FTS5CSR_EOF);
*pbSkip = 1;
}
@@ -196971,7 +225432,7 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){
** even if we reach end-of-file. The fts5EofMethod() will be called
** subsequently to determine whether or not an EOF was hit.
*/
-static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){
+static int fts5NextMethod(tdsqlite3_vtab_cursor *pCursor){
Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
int rc;
@@ -196983,8 +225444,8 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){
if( pCsr->ePlan<3 ){
int bSkip = 0;
if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc;
- rc = sqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid);
- CsrFlagSet(pCsr, sqlite3Fts5ExprEof(pCsr->pExpr));
+ rc = tdsqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid);
+ CsrFlagSet(pCsr, tdsqlite3Fts5ExprEof(pCsr->pExpr));
fts5CsrNewrow(pCsr);
}else{
switch( pCsr->ePlan ){
@@ -196999,15 +225460,24 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){
break;
}
- default:
- rc = sqlite3_step(pCsr->pStmt);
+ default: {
+ Fts5Config *pConfig = ((Fts5Table*)pCursor->pVtab)->pConfig;
+ pConfig->bLock++;
+ rc = tdsqlite3_step(pCsr->pStmt);
+ pConfig->bLock--;
if( rc!=SQLITE_ROW ){
CsrFlagSet(pCsr, FTS5CSR_EOF);
- rc = sqlite3_reset(pCsr->pStmt);
+ rc = tdsqlite3_reset(pCsr->pStmt);
+ if( rc!=SQLITE_OK ){
+ pCursor->pVtab->zErrMsg = tdsqlite3_mprintf(
+ "%s", tdsqlite3_errmsg(pConfig->db)
+ );
+ }
}else{
rc = SQLITE_OK;
}
break;
+ }
}
}
@@ -197016,26 +225486,27 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){
static int fts5PrepareStatement(
- sqlite3_stmt **ppStmt,
+ tdsqlite3_stmt **ppStmt,
Fts5Config *pConfig,
const char *zFmt,
...
){
- sqlite3_stmt *pRet = 0;
+ tdsqlite3_stmt *pRet = 0;
int rc;
char *zSql;
va_list ap;
va_start(ap, zFmt);
- zSql = sqlite3_vmprintf(zFmt, ap);
+ zSql = tdsqlite3_vmprintf(zFmt, ap);
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pRet, 0);
+ rc = tdsqlite3_prepare_v3(pConfig->db, zSql, -1,
+ SQLITE_PREPARE_PERSISTENT, &pRet, 0);
if( rc!=SQLITE_OK ){
- *pConfig->pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(pConfig->db));
+ *pConfig->pzErrmsg = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(pConfig->db));
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
}
va_end(ap);
@@ -197043,20 +225514,24 @@ static int fts5PrepareStatement(
return rc;
}
-static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){
- Fts5Config *pConfig = pTab->pConfig;
+static int fts5CursorFirstSorted(
+ Fts5FullTable *pTab,
+ Fts5Cursor *pCsr,
+ int bDesc
+){
+ Fts5Config *pConfig = pTab->p.pConfig;
Fts5Sorter *pSorter;
int nPhrase;
- int nByte;
+ tdsqlite3_int64 nByte;
int rc;
const char *zRank = pCsr->zRank;
const char *zRankArgs = pCsr->zRankArgs;
- nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
+ nPhrase = tdsqlite3Fts5ExprPhraseCount(pCsr->pExpr);
nByte = sizeof(Fts5Sorter) + sizeof(int) * (nPhrase-1);
- pSorter = (Fts5Sorter*)sqlite3_malloc(nByte);
+ pSorter = (Fts5Sorter*)tdsqlite3_malloc64(nByte);
if( pSorter==0 ) return SQLITE_NOMEM;
- memset(pSorter, 0, nByte);
+ memset(pSorter, 0, (size_t)nByte);
pSorter->nIdx = nPhrase;
/* TODO: It would be better to have some system for reusing statement
@@ -197067,7 +225542,7 @@ static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){
**
** If SQLite a built-in statement cache, this wouldn't be a problem. */
rc = fts5PrepareStatement(&pSorter->pStmt, pConfig,
- "SELECT rowid, rank FROM %Q.%Q ORDER BY %s(%s%s%s) %s",
+ "SELECT rowid, rank FROM %Q.%Q ORDER BY %s(\"%w\"%s%s) %s",
pConfig->zDb, pConfig->zName, zRank, pConfig->zName,
(zRankArgs ? ", " : ""),
(zRankArgs ? zRankArgs : ""),
@@ -197083,19 +225558,19 @@ static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){
}
if( rc!=SQLITE_OK ){
- sqlite3_finalize(pSorter->pStmt);
- sqlite3_free(pSorter);
+ tdsqlite3_finalize(pSorter->pStmt);
+ tdsqlite3_free(pSorter);
pCsr->pSorter = 0;
}
return rc;
}
-static int fts5CursorFirst(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){
+static int fts5CursorFirst(Fts5FullTable *pTab, Fts5Cursor *pCsr, int bDesc){
int rc;
Fts5Expr *pExpr = pCsr->pExpr;
- rc = sqlite3Fts5ExprFirst(pExpr, pTab->pIndex, pCsr->iFirstRowid, bDesc);
- if( sqlite3Fts5ExprEof(pExpr) ){
+ rc = tdsqlite3Fts5ExprFirst(pExpr, pTab->p.pIndex, pCsr->iFirstRowid, bDesc);
+ if( tdsqlite3Fts5ExprEof(pExpr) ){
CsrFlagSet(pCsr, FTS5CSR_EOF);
}
fts5CsrNewrow(pCsr);
@@ -197109,7 +225584,7 @@ static int fts5CursorFirst(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){
** parameters.
*/
static int fts5SpecialMatch(
- Fts5Table *pTab,
+ Fts5FullTable *pTab,
Fts5Cursor *pCsr,
const char *zQuery
){
@@ -197120,18 +225595,18 @@ static int fts5SpecialMatch(
while( z[0]==' ' ) z++;
for(n=0; z[n] && z[n]!=' '; n++);
- assert( pTab->base.zErrMsg==0 );
+ assert( pTab->p.base.zErrMsg==0 );
pCsr->ePlan = FTS5_PLAN_SPECIAL;
- if( 0==sqlite3_strnicmp("reads", z, n) ){
- pCsr->iSpecial = sqlite3Fts5IndexReads(pTab->pIndex);
+ if( n==5 && 0==tdsqlite3_strnicmp("reads", z, n) ){
+ pCsr->iSpecial = tdsqlite3Fts5IndexReads(pTab->p.pIndex);
}
- else if( 0==sqlite3_strnicmp("id", z, n) ){
+ else if( n==2 && 0==tdsqlite3_strnicmp("id", z, n) ){
pCsr->iSpecial = pCsr->iCsrId;
}
else{
/* An unrecognized directive. Return an error message. */
- pTab->base.zErrMsg = sqlite3_mprintf("unknown special query: %.*s", n, z);
+ pTab->p.base.zErrMsg = tdsqlite3_mprintf("unknown special query: %.*s", n, z);
rc = SQLITE_ERROR;
}
@@ -197143,11 +225618,11 @@ static int fts5SpecialMatch(
** pTab. If one is found, return a pointer to the corresponding Fts5Auxiliary
** structure. Otherwise, if no such function exists, return NULL.
*/
-static Fts5Auxiliary *fts5FindAuxiliary(Fts5Table *pTab, const char *zName){
+static Fts5Auxiliary *fts5FindAuxiliary(Fts5FullTable *pTab, const char *zName){
Fts5Auxiliary *pAux;
for(pAux=pTab->pGlobal->pAux; pAux; pAux=pAux->pNext){
- if( sqlite3_stricmp(zName, pAux->zFunc)==0 ) return pAux;
+ if( tdsqlite3_stricmp(zName, pAux->zFunc)==0 ) return pAux;
}
/* No function of the specified name was found. Return 0. */
@@ -197156,35 +225631,36 @@ static Fts5Auxiliary *fts5FindAuxiliary(Fts5Table *pTab, const char *zName){
static int fts5FindRankFunction(Fts5Cursor *pCsr){
- Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
- Fts5Config *pConfig = pTab->pConfig;
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab);
+ Fts5Config *pConfig = pTab->p.pConfig;
int rc = SQLITE_OK;
Fts5Auxiliary *pAux = 0;
const char *zRank = pCsr->zRank;
const char *zRankArgs = pCsr->zRankArgs;
if( zRankArgs ){
- char *zSql = sqlite3Fts5Mprintf(&rc, "SELECT %s", zRankArgs);
+ char *zSql = tdsqlite3Fts5Mprintf(&rc, "SELECT %s", zRankArgs);
if( zSql ){
- sqlite3_stmt *pStmt = 0;
- rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
+ tdsqlite3_stmt *pStmt = 0;
+ rc = tdsqlite3_prepare_v3(pConfig->db, zSql, -1,
+ SQLITE_PREPARE_PERSISTENT, &pStmt, 0);
+ tdsqlite3_free(zSql);
assert( rc==SQLITE_OK || pCsr->pRankArgStmt==0 );
if( rc==SQLITE_OK ){
- if( SQLITE_ROW==sqlite3_step(pStmt) ){
- int nByte;
- pCsr->nRankArg = sqlite3_column_count(pStmt);
- nByte = sizeof(sqlite3_value*)*pCsr->nRankArg;
- pCsr->apRankArg = (sqlite3_value**)sqlite3Fts5MallocZero(&rc, nByte);
+ if( SQLITE_ROW==tdsqlite3_step(pStmt) ){
+ tdsqlite3_int64 nByte;
+ pCsr->nRankArg = tdsqlite3_column_count(pStmt);
+ nByte = sizeof(tdsqlite3_value*)*pCsr->nRankArg;
+ pCsr->apRankArg = (tdsqlite3_value**)tdsqlite3Fts5MallocZero(&rc, nByte);
if( rc==SQLITE_OK ){
int i;
for(i=0; i<pCsr->nRankArg; i++){
- pCsr->apRankArg[i] = sqlite3_column_value(pStmt, i);
+ pCsr->apRankArg[i] = tdsqlite3_column_value(pStmt, i);
}
}
pCsr->pRankArgStmt = pStmt;
}else{
- rc = sqlite3_finalize(pStmt);
+ rc = tdsqlite3_finalize(pStmt);
assert( rc!=SQLITE_OK );
}
}
@@ -197194,8 +225670,8 @@ static int fts5FindRankFunction(Fts5Cursor *pCsr){
if( rc==SQLITE_OK ){
pAux = fts5FindAuxiliary(pTab, zRank);
if( pAux==0 ){
- assert( pTab->base.zErrMsg==0 );
- pTab->base.zErrMsg = sqlite3_mprintf("no such function: %s", zRank);
+ assert( pTab->p.base.zErrMsg==0 );
+ pTab->p.base.zErrMsg = tdsqlite3_mprintf("no such function: %s", zRank);
rc = SQLITE_ERROR;
}
}
@@ -197208,25 +225684,25 @@ static int fts5FindRankFunction(Fts5Cursor *pCsr){
static int fts5CursorParseRank(
Fts5Config *pConfig,
Fts5Cursor *pCsr,
- sqlite3_value *pRank
+ tdsqlite3_value *pRank
){
int rc = SQLITE_OK;
if( pRank ){
- const char *z = (const char*)sqlite3_value_text(pRank);
+ const char *z = (const char*)tdsqlite3_value_text(pRank);
char *zRank = 0;
char *zRankArgs = 0;
if( z==0 ){
- if( sqlite3_value_type(pRank)==SQLITE_NULL ) rc = SQLITE_ERROR;
+ if( tdsqlite3_value_type(pRank)==SQLITE_NULL ) rc = SQLITE_ERROR;
}else{
- rc = sqlite3Fts5ConfigParseRank(z, &zRank, &zRankArgs);
+ rc = tdsqlite3Fts5ConfigParseRank(z, &zRank, &zRankArgs);
}
if( rc==SQLITE_OK ){
pCsr->zRank = zRank;
pCsr->zRankArgs = zRankArgs;
CsrFlagSet(pCsr, FTS5CSR_FREE_ZRANK);
}else if( rc==SQLITE_ERROR ){
- pCsr->base.pVtab->zErrMsg = sqlite3_mprintf(
+ pCsr->base.pVtab->zErrMsg = tdsqlite3_mprintf(
"parse error in rank function: %s", z
);
}
@@ -197242,11 +225718,11 @@ static int fts5CursorParseRank(
return rc;
}
-static i64 fts5GetRowidLimit(sqlite3_value *pVal, i64 iDefault){
+static i64 fts5GetRowidLimit(tdsqlite3_value *pVal, i64 iDefault){
if( pVal ){
- int eType = sqlite3_value_numeric_type(pVal);
+ int eType = tdsqlite3_value_numeric_type(pVal);
if( eType==SQLITE_INTEGER ){
- return sqlite3_value_int64(pVal);
+ return tdsqlite3_value_int64(pVal);
}
}
return iDefault;
@@ -197264,28 +225740,34 @@ static i64 fts5GetRowidLimit(sqlite3_value *pVal, i64 iDefault){
** 3. A full-table scan.
*/
static int fts5FilterMethod(
- sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
+ tdsqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
int idxNum, /* Strategy index */
- const char *zUnused, /* Unused */
+ const char *idxStr, /* Unused */
int nVal, /* Number of elements in apVal */
- sqlite3_value **apVal /* Arguments for the indexing scheme */
+ tdsqlite3_value **apVal /* Arguments for the indexing scheme */
){
- Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
- Fts5Config *pConfig = pTab->pConfig;
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCursor->pVtab);
+ Fts5Config *pConfig = pTab->p.pConfig;
Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
int rc = SQLITE_OK; /* Error code */
- int iVal = 0; /* Counter for apVal[] */
int bDesc; /* True if ORDER BY [rank|rowid] DESC */
int bOrderByRank; /* True if ORDER BY rank */
- sqlite3_value *pMatch = 0; /* <tbl> MATCH ? expression (or NULL) */
- sqlite3_value *pRank = 0; /* rank MATCH ? expression (or NULL) */
- sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */
- sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */
- sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */
+ tdsqlite3_value *pRank = 0; /* rank MATCH ? expression (or NULL) */
+ tdsqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */
+ tdsqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */
+ tdsqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */
+ int iCol; /* Column on LHS of MATCH operator */
char **pzErrmsg = pConfig->pzErrmsg;
+ int i;
+ int iIdxStr = 0;
+ Fts5Expr *pExpr = 0;
- UNUSED_PARAM(zUnused);
- UNUSED_PARAM(nVal);
+ if( pConfig->bLock ){
+ pTab->p.base.zErrMsg = tdsqlite3_mprintf(
+ "recursively defined fts5 content table"
+ );
+ return SQLITE_ERROR;
+ }
if( pCsr->ePlan ){
fts5FreeCursorComponents(pCsr);
@@ -197298,27 +225780,66 @@ static int fts5FilterMethod(
assert( pCsr->pRank==0 );
assert( pCsr->zRank==0 );
assert( pCsr->zRankArgs==0 );
+ assert( pTab->pSortCsr==0 || nVal==0 );
- assert( pzErrmsg==0 || pzErrmsg==&pTab->base.zErrMsg );
- pConfig->pzErrmsg = &pTab->base.zErrMsg;
+ assert( pzErrmsg==0 || pzErrmsg==&pTab->p.base.zErrMsg );
+ pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
- /* Decode the arguments passed through to this function.
- **
- ** Note: The following set of if(...) statements must be in the same
- ** order as the corresponding entries in the struct at the top of
- ** fts5BestIndexMethod(). */
- if( BitFlagTest(idxNum, FTS5_BI_MATCH) ) pMatch = apVal[iVal++];
- if( BitFlagTest(idxNum, FTS5_BI_RANK) ) pRank = apVal[iVal++];
- if( BitFlagTest(idxNum, FTS5_BI_ROWID_EQ) ) pRowidEq = apVal[iVal++];
- if( BitFlagTest(idxNum, FTS5_BI_ROWID_LE) ) pRowidLe = apVal[iVal++];
- if( BitFlagTest(idxNum, FTS5_BI_ROWID_GE) ) pRowidGe = apVal[iVal++];
- assert( iVal==nVal );
+ /* Decode the arguments passed through to this function. */
+ for(i=0; i<nVal; i++){
+ switch( idxStr[iIdxStr++] ){
+ case 'r':
+ pRank = apVal[i];
+ break;
+ case 'm': {
+ const char *zText = (const char*)tdsqlite3_value_text(apVal[i]);
+ if( zText==0 ) zText = "";
+
+ if( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' ){
+ iCol = 0;
+ do{
+ iCol = iCol*10 + (idxStr[iIdxStr]-'0');
+ iIdxStr++;
+ }while( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' );
+ }else{
+ iCol = pConfig->nCol;
+ }
+
+ if( zText[0]=='*' ){
+ /* The user has issued a query of the form "MATCH '*...'". This
+ ** indicates that the MATCH expression is not a full text query,
+ ** but a request for an internal parameter. */
+ rc = fts5SpecialMatch(pTab, pCsr, &zText[1]);
+ goto filter_out;
+ }else{
+ char **pzErr = &pTab->p.base.zErrMsg;
+ rc = tdsqlite3Fts5ExprNew(pConfig, iCol, zText, &pExpr, pzErr);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr);
+ pExpr = 0;
+ }
+ if( rc!=SQLITE_OK ) goto filter_out;
+ }
+
+ break;
+ }
+ case '=':
+ pRowidEq = apVal[i];
+ break;
+ case '<':
+ pRowidLe = apVal[i];
+ break;
+ default: assert( idxStr[iIdxStr-1]=='>' );
+ pRowidGe = apVal[i];
+ break;
+ }
+ }
bOrderByRank = ((idxNum & FTS5_BI_ORDER_RANK) ? 1 : 0);
pCsr->bDesc = bDesc = ((idxNum & FTS5_BI_ORDER_DESC) ? 1 : 0);
/* Set the cursor upper and lower rowid limits. Only some strategies
** actually use them. This is ok, as the xBestIndex() method leaves the
- ** sqlite3_index_constraint.omit flag clear for range constraints
+ ** tdsqlite3_index_constraint.omit flag clear for range constraints
** on the rowid field. */
if( pRowidEq ){
pRowidLe = pRowidGe = pRowidEq;
@@ -197339,39 +225860,32 @@ static int fts5FilterMethod(
** (pCursor) is used to execute the query issued by function
** fts5CursorFirstSorted() above. */
assert( pRowidEq==0 && pRowidLe==0 && pRowidGe==0 && pRank==0 );
- assert( nVal==0 && pMatch==0 && bOrderByRank==0 && bDesc==0 );
+ assert( nVal==0 && bOrderByRank==0 && bDesc==0 );
assert( pCsr->iLastRowid==LARGEST_INT64 );
assert( pCsr->iFirstRowid==SMALLEST_INT64 );
+ if( pTab->pSortCsr->bDesc ){
+ pCsr->iLastRowid = pTab->pSortCsr->iFirstRowid;
+ pCsr->iFirstRowid = pTab->pSortCsr->iLastRowid;
+ }else{
+ pCsr->iLastRowid = pTab->pSortCsr->iLastRowid;
+ pCsr->iFirstRowid = pTab->pSortCsr->iFirstRowid;
+ }
pCsr->ePlan = FTS5_PLAN_SOURCE;
pCsr->pExpr = pTab->pSortCsr->pExpr;
rc = fts5CursorFirst(pTab, pCsr, bDesc);
- }else if( pMatch ){
- const char *zExpr = (const char*)sqlite3_value_text(apVal[0]);
- if( zExpr==0 ) zExpr = "";
-
+ }else if( pCsr->pExpr ){
rc = fts5CursorParseRank(pConfig, pCsr, pRank);
if( rc==SQLITE_OK ){
- if( zExpr[0]=='*' ){
- /* The user has issued a query of the form "MATCH '*...'". This
- ** indicates that the MATCH expression is not a full text query,
- ** but a request for an internal parameter. */
- rc = fts5SpecialMatch(pTab, pCsr, &zExpr[1]);
+ if( bOrderByRank ){
+ pCsr->ePlan = FTS5_PLAN_SORTED_MATCH;
+ rc = fts5CursorFirstSorted(pTab, pCsr, bDesc);
}else{
- char **pzErr = &pTab->base.zErrMsg;
- rc = sqlite3Fts5ExprNew(pConfig, zExpr, &pCsr->pExpr, pzErr);
- if( rc==SQLITE_OK ){
- if( bOrderByRank ){
- pCsr->ePlan = FTS5_PLAN_SORTED_MATCH;
- rc = fts5CursorFirstSorted(pTab, pCsr, bDesc);
- }else{
- pCsr->ePlan = FTS5_PLAN_MATCH;
- rc = fts5CursorFirst(pTab, pCsr, bDesc);
- }
- }
+ pCsr->ePlan = FTS5_PLAN_MATCH;
+ rc = fts5CursorFirst(pTab, pCsr, bDesc);
}
}
}else if( pConfig->zContent==0 ){
- *pConfig->pzErrmsg = sqlite3_mprintf(
+ *pConfig->pzErrmsg = tdsqlite3_mprintf(
"%s: table does not support scanning", pConfig->zName
);
rc = SQLITE_ERROR;
@@ -197379,20 +225893,22 @@ static int fts5FilterMethod(
/* This is either a full-table scan (ePlan==FTS5_PLAN_SCAN) or a lookup
** by rowid (ePlan==FTS5_PLAN_ROWID). */
pCsr->ePlan = (pRowidEq ? FTS5_PLAN_ROWID : FTS5_PLAN_SCAN);
- rc = sqlite3Fts5StorageStmt(
- pTab->pStorage, fts5StmtType(pCsr), &pCsr->pStmt, &pTab->base.zErrMsg
+ rc = tdsqlite3Fts5StorageStmt(
+ pTab->pStorage, fts5StmtType(pCsr), &pCsr->pStmt, &pTab->p.base.zErrMsg
);
if( rc==SQLITE_OK ){
if( pCsr->ePlan==FTS5_PLAN_ROWID ){
- sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
+ tdsqlite3_bind_value(pCsr->pStmt, 1, pRowidEq);
}else{
- sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iFirstRowid);
- sqlite3_bind_int64(pCsr->pStmt, 2, pCsr->iLastRowid);
+ tdsqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iFirstRowid);
+ tdsqlite3_bind_int64(pCsr->pStmt, 2, pCsr->iLastRowid);
}
rc = fts5NextMethod(pCursor);
}
}
+ filter_out:
+ tdsqlite3Fts5ExprFree(pExpr);
pConfig->pzErrmsg = pzErrmsg;
return rc;
}
@@ -197401,7 +225917,7 @@ static int fts5FilterMethod(
** This is the xEof method of the virtual table. SQLite calls this
** routine to find out if it has reached the end of a result set.
*/
-static int fts5EofMethod(sqlite3_vtab_cursor *pCursor){
+static int fts5EofMethod(tdsqlite3_vtab_cursor *pCursor){
Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
return (CsrFlagTest(pCsr, FTS5CSR_EOF) ? 1 : 0);
}
@@ -197417,7 +225933,7 @@ static i64 fts5CursorRowid(Fts5Cursor *pCsr){
if( pCsr->pSorter ){
return pCsr->pSorter->iRowid;
}else{
- return sqlite3Fts5ExprRowid(pCsr->pExpr);
+ return tdsqlite3Fts5ExprRowid(pCsr->pExpr);
}
}
@@ -197427,7 +225943,7 @@ static i64 fts5CursorRowid(Fts5Cursor *pCsr){
** exposes %_content.rowid as the rowid for the virtual table. The
** rowid should be written to *pRowid.
*/
-static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+static int fts5RowidMethod(tdsqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
int ePlan = pCsr->ePlan;
@@ -197444,7 +225960,7 @@ static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
break;
default:
- *pRowid = sqlite3_column_int64(pCsr->pStmt, 0);
+ *pRowid = tdsqlite3_column_int64(pCsr->pStmt, 0);
break;
}
@@ -197456,45 +225972,52 @@ static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
** Return SQLITE_OK if no error occurs, or an SQLite error code otherwise.
**
** If argument bErrormsg is true and an error occurs, an error message may
-** be left in sqlite3_vtab.zErrMsg.
+** be left in tdsqlite3_vtab.zErrMsg.
*/
static int fts5SeekCursor(Fts5Cursor *pCsr, int bErrormsg){
int rc = SQLITE_OK;
/* If the cursor does not yet have a statement handle, obtain one now. */
if( pCsr->pStmt==0 ){
- Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab);
int eStmt = fts5StmtType(pCsr);
- rc = sqlite3Fts5StorageStmt(
- pTab->pStorage, eStmt, &pCsr->pStmt, (bErrormsg?&pTab->base.zErrMsg:0)
+ rc = tdsqlite3Fts5StorageStmt(
+ pTab->pStorage, eStmt, &pCsr->pStmt, (bErrormsg?&pTab->p.base.zErrMsg:0)
);
- assert( rc!=SQLITE_OK || pTab->base.zErrMsg==0 );
+ assert( rc!=SQLITE_OK || pTab->p.base.zErrMsg==0 );
assert( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_CONTENT) );
}
if( rc==SQLITE_OK && CsrFlagTest(pCsr, FTS5CSR_REQUIRE_CONTENT) ){
+ Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
assert( pCsr->pExpr );
- sqlite3_reset(pCsr->pStmt);
- sqlite3_bind_int64(pCsr->pStmt, 1, fts5CursorRowid(pCsr));
- rc = sqlite3_step(pCsr->pStmt);
+ tdsqlite3_reset(pCsr->pStmt);
+ tdsqlite3_bind_int64(pCsr->pStmt, 1, fts5CursorRowid(pCsr));
+ pTab->pConfig->bLock++;
+ rc = tdsqlite3_step(pCsr->pStmt);
+ pTab->pConfig->bLock--;
if( rc==SQLITE_ROW ){
rc = SQLITE_OK;
CsrFlagClear(pCsr, FTS5CSR_REQUIRE_CONTENT);
}else{
- rc = sqlite3_reset(pCsr->pStmt);
+ rc = tdsqlite3_reset(pCsr->pStmt);
if( rc==SQLITE_OK ){
rc = FTS5_CORRUPT;
+ }else if( pTab->pConfig->pzErrmsg ){
+ *pTab->pConfig->pzErrmsg = tdsqlite3_mprintf(
+ "%s", tdsqlite3_errmsg(pTab->pConfig->db)
+ );
}
}
}
return rc;
}
-static void fts5SetVtabError(Fts5Table *p, const char *zFormat, ...){
+static void fts5SetVtabError(Fts5FullTable *p, const char *zFormat, ...){
va_list ap; /* ... printf arguments */
va_start(ap, zFormat);
- assert( p->base.zErrMsg==0 );
- p->base.zErrMsg = sqlite3_vmprintf(zFormat, ap);
+ assert( p->p.base.zErrMsg==0 );
+ p->p.base.zErrMsg = tdsqlite3_vmprintf(zFormat, ap);
va_end(ap);
}
@@ -197514,15 +226037,15 @@ static void fts5SetVtabError(Fts5Table *p, const char *zFormat, ...){
** more commands are added to this function.
*/
static int fts5SpecialInsert(
- Fts5Table *pTab, /* Fts5 table object */
+ Fts5FullTable *pTab, /* Fts5 table object */
const char *zCmd, /* Text inserted into table-name column */
- sqlite3_value *pVal /* Value inserted into rank column */
+ tdsqlite3_value *pVal /* Value inserted into rank column */
){
- Fts5Config *pConfig = pTab->pConfig;
+ Fts5Config *pConfig = pTab->p.pConfig;
int rc = SQLITE_OK;
int bError = 0;
- if( 0==sqlite3_stricmp("delete-all", zCmd) ){
+ if( 0==tdsqlite3_stricmp("delete-all", zCmd) ){
if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
fts5SetVtabError(pTab,
"'delete-all' may only be used with a "
@@ -197530,38 +226053,38 @@ static int fts5SpecialInsert(
);
rc = SQLITE_ERROR;
}else{
- rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage);
+ rc = tdsqlite3Fts5StorageDeleteAll(pTab->pStorage);
}
- }else if( 0==sqlite3_stricmp("rebuild", zCmd) ){
+ }else if( 0==tdsqlite3_stricmp("rebuild", zCmd) ){
if( pConfig->eContent==FTS5_CONTENT_NONE ){
fts5SetVtabError(pTab,
"'rebuild' may not be used with a contentless fts5 table"
);
rc = SQLITE_ERROR;
}else{
- rc = sqlite3Fts5StorageRebuild(pTab->pStorage);
+ rc = tdsqlite3Fts5StorageRebuild(pTab->pStorage);
}
- }else if( 0==sqlite3_stricmp("optimize", zCmd) ){
- rc = sqlite3Fts5StorageOptimize(pTab->pStorage);
- }else if( 0==sqlite3_stricmp("merge", zCmd) ){
- int nMerge = sqlite3_value_int(pVal);
- rc = sqlite3Fts5StorageMerge(pTab->pStorage, nMerge);
- }else if( 0==sqlite3_stricmp("integrity-check", zCmd) ){
- rc = sqlite3Fts5StorageIntegrity(pTab->pStorage);
+ }else if( 0==tdsqlite3_stricmp("optimize", zCmd) ){
+ rc = tdsqlite3Fts5StorageOptimize(pTab->pStorage);
+ }else if( 0==tdsqlite3_stricmp("merge", zCmd) ){
+ int nMerge = tdsqlite3_value_int(pVal);
+ rc = tdsqlite3Fts5StorageMerge(pTab->pStorage, nMerge);
+ }else if( 0==tdsqlite3_stricmp("integrity-check", zCmd) ){
+ rc = tdsqlite3Fts5StorageIntegrity(pTab->pStorage);
#ifdef SQLITE_DEBUG
- }else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){
- pConfig->bPrefixIndex = sqlite3_value_int(pVal);
+ }else if( 0==tdsqlite3_stricmp("prefix-index", zCmd) ){
+ pConfig->bPrefixIndex = tdsqlite3_value_int(pVal);
#endif
}else{
- rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex);
+ rc = tdsqlite3Fts5IndexLoadConfig(pTab->p.pIndex);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5ConfigSetValue(pTab->pConfig, zCmd, pVal, &bError);
+ rc = tdsqlite3Fts5ConfigSetValue(pTab->p.pConfig, zCmd, pVal, &bError);
}
if( rc==SQLITE_OK ){
if( bError ){
rc = SQLITE_ERROR;
}else{
- rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, zCmd, pVal, 0);
+ rc = tdsqlite3Fts5StorageConfigValue(pTab->pStorage, zCmd, pVal, 0);
}
}
}
@@ -197569,30 +226092,30 @@ static int fts5SpecialInsert(
}
static int fts5SpecialDelete(
- Fts5Table *pTab,
- sqlite3_value **apVal
+ Fts5FullTable *pTab,
+ tdsqlite3_value **apVal
){
int rc = SQLITE_OK;
- int eType1 = sqlite3_value_type(apVal[1]);
+ int eType1 = tdsqlite3_value_type(apVal[1]);
if( eType1==SQLITE_INTEGER ){
- sqlite3_int64 iDel = sqlite3_value_int64(apVal[1]);
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2]);
+ tdsqlite3_int64 iDel = tdsqlite3_value_int64(apVal[1]);
+ rc = tdsqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2]);
}
return rc;
}
static void fts5StorageInsert(
int *pRc,
- Fts5Table *pTab,
- sqlite3_value **apVal,
+ Fts5FullTable *pTab,
+ tdsqlite3_value **apVal,
i64 *piRowid
){
int rc = *pRc;
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, piRowid);
+ rc = tdsqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, piRowid);
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *piRowid);
+ rc = tdsqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *piRowid);
}
*pRc = rc;
}
@@ -197612,13 +226135,13 @@ static void fts5StorageInsert(
** 4. Values for the two hidden columns (<tablename> and "rank").
*/
static int fts5UpdateMethod(
- sqlite3_vtab *pVtab, /* Virtual table handle */
+ tdsqlite3_vtab *pVtab, /* Virtual table handle */
int nArg, /* Size of argument array */
- sqlite3_value **apVal, /* Array of arguments */
+ tdsqlite3_value **apVal, /* Array of arguments */
sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */
){
- Fts5Table *pTab = (Fts5Table*)pVtab;
- Fts5Config *pConfig = pTab->pConfig;
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+ Fts5Config *pConfig = pTab->p.pConfig;
int eType0; /* value_type() of apVal[0] */
int rc = SQLITE_OK; /* Return code */
@@ -197627,24 +226150,23 @@ static int fts5UpdateMethod(
assert( pVtab->zErrMsg==0 );
assert( nArg==1 || nArg==(2+pConfig->nCol+2) );
- assert( nArg==1
- || sqlite3_value_type(apVal[1])==SQLITE_INTEGER
- || sqlite3_value_type(apVal[1])==SQLITE_NULL
+ assert( tdsqlite3_value_type(apVal[0])==SQLITE_INTEGER
+ || tdsqlite3_value_type(apVal[0])==SQLITE_NULL
);
- assert( pTab->pConfig->pzErrmsg==0 );
- pTab->pConfig->pzErrmsg = &pTab->base.zErrMsg;
+ assert( pTab->p.pConfig->pzErrmsg==0 );
+ pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
/* Put any active cursors into REQUIRE_SEEK state. */
fts5TripCursors(pTab);
- eType0 = sqlite3_value_type(apVal[0]);
+ eType0 = tdsqlite3_value_type(apVal[0]);
if( eType0==SQLITE_NULL
- && sqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL
+ && tdsqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL
){
/* A "special" INSERT op. These are handled separately. */
- const char *z = (const char*)sqlite3_value_text(apVal[2+pConfig->nCol]);
+ const char *z = (const char*)tdsqlite3_value_text(apVal[2+pConfig->nCol]);
if( pConfig->eContent!=FTS5_CONTENT_NORMAL
- && 0==sqlite3_stricmp("delete", z)
+ && 0==tdsqlite3_stricmp("delete", z)
){
rc = fts5SpecialDelete(pTab, apVal);
}else{
@@ -197664,7 +226186,7 @@ static int fts5UpdateMethod(
*/
int eConflict = SQLITE_ABORT;
if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
- eConflict = sqlite3_vtab_on_conflict(pConfig->db);
+ eConflict = tdsqlite3_vtab_on_conflict(pConfig->db);
}
assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL );
@@ -197673,7 +226195,7 @@ static int fts5UpdateMethod(
/* Filter out attempts to run UPDATE or DELETE on contentless tables.
** This is not suported. */
if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){
- pTab->base.zErrMsg = sqlite3_mprintf(
+ pTab->p.base.zErrMsg = tdsqlite3_mprintf(
"cannot %s contentless fts5 table: %s",
(nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName
);
@@ -197682,73 +226204,79 @@ static int fts5UpdateMethod(
/* DELETE */
else if( nArg==1 ){
- i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0);
+ i64 iDel = tdsqlite3_value_int64(apVal[0]); /* Rowid to delete */
+ rc = tdsqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0);
}
- /* INSERT */
- else if( eType0!=SQLITE_INTEGER ){
- /* If this is a REPLACE, first remove the current entry (if any) */
- if( eConflict==SQLITE_REPLACE
- && sqlite3_value_type(apVal[1])==SQLITE_INTEGER
- ){
- i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
+ /* INSERT or UPDATE */
+ else{
+ int eType1 = tdsqlite3_value_numeric_type(apVal[1]);
+
+ if( eType1!=SQLITE_INTEGER && eType1!=SQLITE_NULL ){
+ rc = SQLITE_MISMATCH;
}
- fts5StorageInsert(&rc, pTab, apVal, pRowid);
- }
- /* UPDATE */
- else{
- i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */
- i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */
- if( iOld!=iNew ){
- if( eConflict==SQLITE_REPLACE ){
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
- if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
+ else if( eType0!=SQLITE_INTEGER ){
+ /* If this is a REPLACE, first remove the current entry (if any) */
+ if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){
+ i64 iNew = tdsqlite3_value_int64(apVal[1]); /* Rowid to delete */
+ rc = tdsqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
+ }
+ fts5StorageInsert(&rc, pTab, apVal, pRowid);
+ }
+
+ /* UPDATE */
+ else{
+ i64 iOld = tdsqlite3_value_int64(apVal[0]); /* Old rowid */
+ i64 iNew = tdsqlite3_value_int64(apVal[1]); /* New rowid */
+ if( eType1==SQLITE_INTEGER && iOld!=iNew ){
+ if( eConflict==SQLITE_REPLACE ){
+ rc = tdsqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0);
+ }
+ fts5StorageInsert(&rc, pTab, apVal, pRowid);
+ }else{
+ rc = tdsqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
+ }
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal,*pRowid);
+ }
}
- fts5StorageInsert(&rc, pTab, apVal, pRowid);
}else{
- rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid);
- if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
- }
- if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *pRowid);
- }
+ rc = tdsqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
+ fts5StorageInsert(&rc, pTab, apVal, pRowid);
}
- }else{
- rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0);
- fts5StorageInsert(&rc, pTab, apVal, pRowid);
}
}
}
- pTab->pConfig->pzErrmsg = 0;
+ pTab->p.pConfig->pzErrmsg = 0;
return rc;
}
/*
** Implementation of xSync() method.
*/
-static int fts5SyncMethod(sqlite3_vtab *pVtab){
+static int fts5SyncMethod(tdsqlite3_vtab *pVtab){
int rc;
- Fts5Table *pTab = (Fts5Table*)pVtab;
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
fts5CheckTransactionState(pTab, FTS5_SYNC, 0);
- pTab->pConfig->pzErrmsg = &pTab->base.zErrMsg;
+ pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
fts5TripCursors(pTab);
- rc = sqlite3Fts5StorageSync(pTab->pStorage, 1);
- pTab->pConfig->pzErrmsg = 0;
+ rc = tdsqlite3Fts5StorageSync(pTab->pStorage);
+ pTab->p.pConfig->pzErrmsg = 0;
return rc;
}
/*
** Implementation of xBegin() method.
*/
-static int fts5BeginMethod(sqlite3_vtab *pVtab){
- fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_BEGIN, 0);
- fts5NewTransaction((Fts5Table*)pVtab);
+static int fts5BeginMethod(tdsqlite3_vtab *pVtab){
+ fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_BEGIN, 0);
+ fts5NewTransaction((Fts5FullTable*)pVtab);
return SQLITE_OK;
}
@@ -197757,9 +226285,9 @@ static int fts5BeginMethod(sqlite3_vtab *pVtab){
** the pending-terms hash-table have already been flushed into the database
** by fts5SyncMethod().
*/
-static int fts5CommitMethod(sqlite3_vtab *pVtab){
+static int fts5CommitMethod(tdsqlite3_vtab *pVtab){
UNUSED_PARAM(pVtab); /* Call below is a no-op for NDEBUG builds */
- fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_COMMIT, 0);
+ fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_COMMIT, 0);
return SQLITE_OK;
}
@@ -197767,11 +226295,11 @@ static int fts5CommitMethod(sqlite3_vtab *pVtab){
** Implementation of xRollback(). Discard the contents of the pending-terms
** hash-table. Any changes made to the database are reverted by SQLite.
*/
-static int fts5RollbackMethod(sqlite3_vtab *pVtab){
+static int fts5RollbackMethod(tdsqlite3_vtab *pVtab){
int rc;
- Fts5Table *pTab = (Fts5Table*)pVtab;
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
fts5CheckTransactionState(pTab, FTS5_ROLLBACK, 0);
- rc = sqlite3Fts5StorageRollback(pTab->pStorage);
+ rc = tdsqlite3Fts5StorageRollback(pTab->pStorage);
return rc;
}
@@ -197790,17 +226318,17 @@ static int fts5ApiColumnCount(Fts5Context *pCtx){
static int fts5ApiColumnTotalSize(
Fts5Context *pCtx,
int iCol,
- sqlite3_int64 *pnToken
+ tdsqlite3_int64 *pnToken
){
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
- Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
- return sqlite3Fts5StorageSize(pTab->pStorage, iCol, pnToken);
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab);
+ return tdsqlite3Fts5StorageSize(pTab->pStorage, iCol, pnToken);
}
static int fts5ApiRowCount(Fts5Context *pCtx, i64 *pnRow){
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
- Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
- return sqlite3Fts5StorageRowCount(pTab->pStorage, pnRow);
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab);
+ return tdsqlite3Fts5StorageRowCount(pTab->pStorage, pnRow);
}
static int fts5ApiTokenize(
@@ -197811,19 +226339,19 @@ static int fts5ApiTokenize(
){
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
- return sqlite3Fts5Tokenize(
+ return tdsqlite3Fts5Tokenize(
pTab->pConfig, FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken
);
}
static int fts5ApiPhraseCount(Fts5Context *pCtx){
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
- return sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
+ return tdsqlite3Fts5ExprPhraseCount(pCsr->pExpr);
}
static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
- return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase);
+ return tdsqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase);
}
static int fts5ApiColumnText(
@@ -197834,14 +226362,16 @@ static int fts5ApiColumnText(
){
int rc = SQLITE_OK;
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
- if( fts5IsContentless((Fts5Table*)(pCsr->base.pVtab)) ){
+ if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab))
+ || pCsr->ePlan==FTS5_PLAN_SPECIAL
+ ){
*pz = 0;
*pn = 0;
}else{
rc = fts5SeekCursor(pCsr, 0);
if( rc==SQLITE_OK ){
- *pz = (const char*)sqlite3_column_text(pCsr->pStmt, iCol+1);
- *pn = sqlite3_column_bytes(pCsr->pStmt, iCol+1);
+ *pz = (const char*)tdsqlite3_column_text(pCsr->pStmt, iCol+1);
+ *pn = tdsqlite3_column_bytes(pCsr->pStmt, iCol+1);
}
}
return rc;
@@ -197862,21 +226392,21 @@ static int fts5CsrPoslist(
if( pConfig->eDetail!=FTS5_DETAIL_FULL ){
Fts5PoslistPopulator *aPopulator;
int i;
- aPopulator = sqlite3Fts5ExprClearPoslists(pCsr->pExpr, bLive);
+ aPopulator = tdsqlite3Fts5ExprClearPoslists(pCsr->pExpr, bLive);
if( aPopulator==0 ) rc = SQLITE_NOMEM;
for(i=0; i<pConfig->nCol && rc==SQLITE_OK; i++){
int n; const char *z;
rc = fts5ApiColumnText((Fts5Context*)pCsr, i, &z, &n);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5ExprPopulatePoslists(
+ rc = tdsqlite3Fts5ExprPopulatePoslists(
pConfig, pCsr->pExpr, aPopulator, i, z, n
);
}
}
- sqlite3_free(aPopulator);
+ tdsqlite3_free(aPopulator);
if( pCsr->pSorter ){
- sqlite3Fts5ExprCheckPoslists(pCsr->pExpr, pCsr->pSorter->iRowid);
+ tdsqlite3Fts5ExprCheckPoslists(pCsr->pExpr, pCsr->pSorter->iRowid);
}
}
CsrFlagClear(pCsr, FTS5CSR_REQUIRE_POSLIST);
@@ -197888,7 +226418,7 @@ static int fts5CsrPoslist(
*pn = pSorter->aIdx[iPhrase] - i1;
*pa = &pSorter->aPoslist[i1];
}else{
- *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa);
+ *pn = tdsqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa);
}
return rc;
@@ -197903,11 +226433,12 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){
int rc = SQLITE_OK;
Fts5PoslistReader *aIter; /* One iterator for each phrase */
int nIter; /* Number of iterators/phrases */
+ int nCol = ((Fts5Table*)pCsr->base.pVtab)->pConfig->nCol;
- nIter = sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
+ nIter = tdsqlite3Fts5ExprPhraseCount(pCsr->pExpr);
if( pCsr->aInstIter==0 ){
- int nByte = sizeof(Fts5PoslistReader) * nIter;
- pCsr->aInstIter = (Fts5PoslistReader*)sqlite3Fts5MallocZero(&rc, nByte);
+ tdsqlite3_int64 nByte = sizeof(Fts5PoslistReader) * nIter;
+ pCsr->aInstIter = (Fts5PoslistReader*)tdsqlite3Fts5MallocZero(&rc, nByte);
}
aIter = pCsr->aInstIter;
@@ -197921,7 +226452,7 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){
int n;
rc = fts5CsrPoslist(pCsr, i, &a, &n);
if( rc==SQLITE_OK ){
- sqlite3Fts5PoslistReaderInit(a, n, &aIter[i]);
+ tdsqlite3Fts5PoslistReaderInit(a, n, &aIter[i]);
}
}
@@ -197941,7 +226472,7 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){
nInst++;
if( nInst>=pCsr->nInstAlloc ){
pCsr->nInstAlloc = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32;
- aInst = (int*)sqlite3_realloc(
+ aInst = (int*)tdsqlite3_realloc64(
pCsr->aInst, pCsr->nInstAlloc*sizeof(int)*3
);
if( aInst ){
@@ -197956,7 +226487,11 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){
aInst[0] = iBest;
aInst[1] = FTS5_POS2COLUMN(aIter[iBest].iPos);
aInst[2] = FTS5_POS2OFFSET(aIter[iBest].iPos);
- sqlite3Fts5PoslistReaderNext(&aIter[iBest]);
+ if( aInst[1]<0 || aInst[1]>=nCol ){
+ rc = FTS5_CORRUPT;
+ break;
+ }
+ tdsqlite3Fts5PoslistReaderNext(&aIter[iBest]);
}
}
@@ -198005,7 +226540,7 @@ static int fts5ApiInst(
return rc;
}
-static sqlite3_int64 fts5ApiRowid(Fts5Context *pCtx){
+static tdsqlite3_int64 fts5ApiRowid(Fts5Context *pCtx){
return fts5CursorRowid((Fts5Cursor*)pCtx);
}
@@ -198028,14 +226563,14 @@ static int fts5ColumnSizeCb(
static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
- Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
- Fts5Config *pConfig = pTab->pConfig;
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab);
+ Fts5Config *pConfig = pTab->p.pConfig;
int rc = SQLITE_OK;
if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_DOCSIZE) ){
if( pConfig->bColumnsize ){
i64 iRowid = fts5CursorRowid(pCsr);
- rc = sqlite3Fts5StorageDocsize(pTab->pStorage, iRowid, pCsr->aColumnSize);
+ rc = tdsqlite3Fts5StorageDocsize(pTab->pStorage, iRowid, pCsr->aColumnSize);
}else if( pConfig->zContent==0 ){
int i;
for(i=0; i<pConfig->nCol; i++){
@@ -198052,7 +226587,7 @@ static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){
pCsr->aColumnSize[i] = 0;
rc = fts5ApiColumnText(pCtx, i, &z, &n);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5Tokenize(
+ rc = tdsqlite3Fts5Tokenize(
pConfig, FTS5_TOKENIZE_AUX, z, n, p, fts5ColumnSizeCb
);
}
@@ -198099,7 +226634,7 @@ static int fts5ApiSetAuxdata(
}
}else{
int rc = SQLITE_OK;
- pData = (Fts5Auxdata*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Auxdata));
+ pData = (Fts5Auxdata*)tdsqlite3Fts5MallocZero(&rc, sizeof(Fts5Auxdata));
if( pData==0 ){
if( xDelete ) xDelete(pPtr);
return rc;
@@ -198222,7 +226757,7 @@ static int fts5ApiPhraseFirstColumn(
n = pSorter->aIdx[iPhrase] - i1;
pIter->a = &pSorter->aPoslist[i1];
}else{
- rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n);
+ rc = tdsqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n);
}
if( rc==SQLITE_OK ){
pIter->b = &pIter->a[n];
@@ -198285,23 +226820,23 @@ static int fts5ApiQueryPhrase(
int(*xCallback)(const Fts5ExtensionApi*, Fts5Context*, void*)
){
Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
- Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab);
int rc;
Fts5Cursor *pNew = 0;
- rc = fts5OpenMethod(pCsr->base.pVtab, (sqlite3_vtab_cursor**)&pNew);
+ rc = fts5OpenMethod(pCsr->base.pVtab, (tdsqlite3_vtab_cursor**)&pNew);
if( rc==SQLITE_OK ){
pNew->ePlan = FTS5_PLAN_MATCH;
pNew->iFirstRowid = SMALLEST_INT64;
pNew->iLastRowid = LARGEST_INT64;
- pNew->base.pVtab = (sqlite3_vtab*)pTab;
- rc = sqlite3Fts5ExprClonePhrase(pCsr->pExpr, iPhrase, &pNew->pExpr);
+ pNew->base.pVtab = (tdsqlite3_vtab*)pTab;
+ rc = tdsqlite3Fts5ExprClonePhrase(pCsr->pExpr, iPhrase, &pNew->pExpr);
}
if( rc==SQLITE_OK ){
for(rc = fts5CursorFirst(pTab, pNew, 0);
rc==SQLITE_OK && CsrFlagTest(pNew, FTS5CSR_EOF)==0;
- rc = fts5NextMethod((sqlite3_vtab_cursor*)pNew)
+ rc = fts5NextMethod((tdsqlite3_vtab_cursor*)pNew)
){
rc = xCallback(&sFts5Api, (Fts5Context*)pNew, pUserData);
if( rc!=SQLITE_OK ){
@@ -198311,16 +226846,16 @@ static int fts5ApiQueryPhrase(
}
}
- fts5CloseMethod((sqlite3_vtab_cursor*)pNew);
+ fts5CloseMethod((tdsqlite3_vtab_cursor*)pNew);
return rc;
}
static void fts5ApiInvoke(
Fts5Auxiliary *pAux,
Fts5Cursor *pCsr,
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
assert( pCsr->pAux==0 );
pCsr->pAux = pAux;
@@ -198337,9 +226872,9 @@ static Fts5Cursor *fts5CursorFromCsrid(Fts5Global *pGlobal, i64 iCsrId){
}
static void fts5ApiCallback(
- sqlite3_context *context,
+ tdsqlite3_context *context,
int argc,
- sqlite3_value **argv
+ tdsqlite3_value **argv
){
Fts5Auxiliary *pAux;
@@ -198347,14 +226882,14 @@ static void fts5ApiCallback(
i64 iCsrId;
assert( argc>=1 );
- pAux = (Fts5Auxiliary*)sqlite3_user_data(context);
- iCsrId = sqlite3_value_int64(argv[0]);
+ pAux = (Fts5Auxiliary*)tdsqlite3_user_data(context);
+ iCsrId = tdsqlite3_value_int64(argv[0]);
pCsr = fts5CursorFromCsrid(pAux->pGlobal, iCsrId);
- if( pCsr==0 ){
- char *zErr = sqlite3_mprintf("no such cursor: %lld", iCsrId);
- sqlite3_result_error(context, zErr, -1);
- sqlite3_free(zErr);
+ if( pCsr==0 || pCsr->ePlan==0 ){
+ char *zErr = tdsqlite3_mprintf("no such cursor: %lld", iCsrId);
+ tdsqlite3_result_error(context, zErr, -1);
+ tdsqlite3_free(zErr);
}else{
fts5ApiInvoke(pAux, pCsr, context, argc-1, &argv[1]);
}
@@ -198362,30 +226897,24 @@ static void fts5ApiCallback(
/*
-** Given cursor id iId, return a pointer to the corresponding Fts5Index
+** Given cursor id iId, return a pointer to the corresponding Fts5Table
** object. Or NULL If the cursor id does not exist.
-**
-** If successful, set *ppConfig to point to the associated config object
-** before returning.
*/
-static Fts5Index *sqlite3Fts5IndexFromCsrid(
+static Fts5Table *tdsqlite3Fts5TableFromCsrid(
Fts5Global *pGlobal, /* FTS5 global context for db handle */
- i64 iCsrId, /* Id of cursor to find */
- Fts5Config **ppConfig /* OUT: Configuration object */
+ i64 iCsrId /* Id of cursor to find */
){
Fts5Cursor *pCsr;
- Fts5Table *pTab;
-
pCsr = fts5CursorFromCsrid(pGlobal, iCsrId);
- pTab = (Fts5Table*)pCsr->base.pVtab;
- *ppConfig = pTab->pConfig;
-
- return pTab->pIndex;
+ if( pCsr ){
+ return (Fts5Table*)pCsr->base.pVtab;
+ }
+ return 0;
}
/*
** Return a "position-list blob" corresponding to the current position of
-** cursor pCsr via sqlite3_result_blob(). A position-list blob contains
+** cursor pCsr via tdsqlite3_result_blob(). A position-list blob contains
** the current position-list for each phrase in the query associated with
** cursor pCsr.
**
@@ -198398,10 +226927,10 @@ static Fts5Index *sqlite3Fts5IndexFromCsrid(
** list 1. And so on. There is no size field for the final position list,
** as it can be derived from the total size of the blob.
*/
-static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){
+static int fts5PoslistBlob(tdsqlite3_context *pCtx, Fts5Cursor *pCsr){
int i;
int rc = SQLITE_OK;
- int nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
+ int nPhrase = tdsqlite3Fts5ExprPhraseCount(pCsr->pExpr);
Fts5Buffer val;
memset(&val, 0, sizeof(Fts5Buffer));
@@ -198411,16 +226940,16 @@ static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){
/* Append the varints */
for(i=0; i<(nPhrase-1); i++){
const u8 *dummy;
- int nByte = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &dummy);
- sqlite3Fts5BufferAppendVarint(&rc, &val, nByte);
+ int nByte = tdsqlite3Fts5ExprPoslist(pCsr->pExpr, i, &dummy);
+ tdsqlite3Fts5BufferAppendVarint(&rc, &val, nByte);
}
/* Append the position lists */
for(i=0; i<nPhrase; i++){
const u8 *pPoslist;
int nPoslist;
- nPoslist = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &pPoslist);
- sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist);
+ nPoslist = tdsqlite3Fts5ExprPoslist(pCsr->pExpr, i, &pPoslist);
+ tdsqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist);
}
break;
@@ -198430,16 +226959,16 @@ static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){
for(i=0; rc==SQLITE_OK && i<(nPhrase-1); i++){
const u8 *dummy;
int nByte;
- rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, i, &dummy, &nByte);
- sqlite3Fts5BufferAppendVarint(&rc, &val, nByte);
+ rc = tdsqlite3Fts5ExprPhraseCollist(pCsr->pExpr, i, &dummy, &nByte);
+ tdsqlite3Fts5BufferAppendVarint(&rc, &val, nByte);
}
/* Append the position lists */
for(i=0; rc==SQLITE_OK && i<nPhrase; i++){
const u8 *pPoslist;
int nPoslist;
- rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, i, &pPoslist, &nPoslist);
- sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist);
+ rc = tdsqlite3Fts5ExprPhraseCollist(pCsr->pExpr, i, &pPoslist, &nPoslist);
+ tdsqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist);
}
break;
@@ -198447,7 +226976,7 @@ static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){
break;
}
- sqlite3_result_blob(pCtx, val.p, val.n, sqlite3_free);
+ tdsqlite3_result_blob(pCtx, val.p, val.n, tdsqlite3_free);
return rc;
}
@@ -198456,12 +226985,12 @@ static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){
** the row that the supplied cursor currently points to.
*/
static int fts5ColumnMethod(
- sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
- sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
+ tdsqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
+ tdsqlite3_context *pCtx, /* Context for tdsqlite3_result_xxx() calls */
int iCol /* Index of column to read value from */
){
- Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
- Fts5Config *pConfig = pTab->pConfig;
+ Fts5FullTable *pTab = (Fts5FullTable*)(pCursor->pVtab);
+ Fts5Config *pConfig = pTab->p.pConfig;
Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
int rc = SQLITE_OK;
@@ -198469,7 +226998,7 @@ static int fts5ColumnMethod(
if( pCsr->ePlan==FTS5_PLAN_SPECIAL ){
if( iCol==pConfig->nCol ){
- sqlite3_result_int64(pCtx, pCsr->iSpecial);
+ tdsqlite3_result_int64(pCtx, pCsr->iSpecial);
}
}else
@@ -198478,7 +227007,7 @@ static int fts5ColumnMethod(
** as the table. Return the cursor integer id number. This value is only
** useful in that it may be passed as the first argument to an FTS5
** auxiliary function. */
- sqlite3_result_int64(pCtx, pCsr->iCsrId);
+ tdsqlite3_result_int64(pCtx, pCsr->iCsrId);
}else if( iCol==pConfig->nCol+1 ){
/* The value of the "rank" column. */
@@ -198493,10 +227022,12 @@ static int fts5ColumnMethod(
}
}
}else if( !fts5IsContentless(pTab) ){
+ pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
rc = fts5SeekCursor(pCsr, 1);
if( rc==SQLITE_OK ){
- sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
+ tdsqlite3_result_value(pCtx, tdsqlite3_column_value(pCsr->pStmt, iCol+1));
}
+ pConfig->pzErrmsg = 0;
}
return rc;
}
@@ -198507,13 +227038,13 @@ static int fts5ColumnMethod(
** virtual table.
*/
static int fts5FindFunctionMethod(
- sqlite3_vtab *pVtab, /* Virtual table handle */
+ tdsqlite3_vtab *pVtab, /* Virtual table handle */
int nUnused, /* Number of SQL function arguments */
const char *zName, /* Name of SQL function */
- void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */
+ void (**pxFunc)(tdsqlite3_context*,int,tdsqlite3_value**), /* OUT: Result */
void **ppArg /* OUT: User data for *pxFunc */
){
- Fts5Table *pTab = (Fts5Table*)pVtab;
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
Fts5Auxiliary *pAux;
UNUSED_PARAM(nUnused);
@@ -198532,11 +227063,16 @@ static int fts5FindFunctionMethod(
** Implementation of FTS5 xRename method. Rename an fts5 table.
*/
static int fts5RenameMethod(
- sqlite3_vtab *pVtab, /* Virtual table handle */
+ tdsqlite3_vtab *pVtab, /* Virtual table handle */
const char *zName /* New name of table */
){
- Fts5Table *pTab = (Fts5Table*)pVtab;
- return sqlite3Fts5StorageRename(pTab->pStorage, zName);
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
+ return tdsqlite3Fts5StorageRename(pTab->pStorage, zName);
+}
+
+static int tdsqlite3Fts5FlushToDisk(Fts5Table *pTab){
+ fts5TripCursors((Fts5FullTable*)pTab);
+ return tdsqlite3Fts5StorageSync(((Fts5FullTable*)pTab)->pStorage);
}
/*
@@ -198544,12 +227080,10 @@ static int fts5RenameMethod(
**
** Flush the contents of the pending-terms table to disk.
*/
-static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
- Fts5Table *pTab = (Fts5Table*)pVtab;
+static int fts5SavepointMethod(tdsqlite3_vtab *pVtab, int iSavepoint){
UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
- fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint);
- fts5TripCursors(pTab);
- return sqlite3Fts5StorageSync(pTab->pStorage, 0);
+ fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_SAVEPOINT, iSavepoint);
+ return tdsqlite3Fts5FlushToDisk((Fts5Table*)pVtab);
}
/*
@@ -198557,12 +227091,10 @@ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
**
** This is a no-op.
*/
-static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
- Fts5Table *pTab = (Fts5Table*)pVtab;
+static int fts5ReleaseMethod(tdsqlite3_vtab *pVtab, int iSavepoint){
UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
- fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint);
- fts5TripCursors(pTab);
- return sqlite3Fts5StorageSync(pTab->pStorage, 0);
+ fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_RELEASE, iSavepoint);
+ return tdsqlite3Fts5FlushToDisk((Fts5Table*)pVtab);
}
/*
@@ -198570,12 +227102,12 @@ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
**
** Discard the contents of the pending terms table.
*/
-static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
- Fts5Table *pTab = (Fts5Table*)pVtab;
+static int fts5RollbackToMethod(tdsqlite3_vtab *pVtab, int iSavepoint){
+ Fts5FullTable *pTab = (Fts5FullTable*)pVtab;
UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint);
fts5TripCursors(pTab);
- return sqlite3Fts5StorageRollback(pTab->pStorage);
+ return tdsqlite3Fts5StorageRollback(pTab->pStorage);
}
/*
@@ -198589,17 +227121,17 @@ static int fts5CreateAux(
void(*xDestroy)(void*) /* Destructor for pUserData */
){
Fts5Global *pGlobal = (Fts5Global*)pApi;
- int rc = sqlite3_overload_function(pGlobal->db, zName, -1);
+ int rc = tdsqlite3_overload_function(pGlobal->db, zName, -1);
if( rc==SQLITE_OK ){
Fts5Auxiliary *pAux;
- int nName; /* Size of zName in bytes, including \0 */
- int nByte; /* Bytes of space to allocate */
+ tdsqlite3_int64 nName; /* Size of zName in bytes, including \0 */
+ tdsqlite3_int64 nByte; /* Bytes of space to allocate */
- nName = (int)strlen(zName) + 1;
+ nName = strlen(zName) + 1;
nByte = sizeof(Fts5Auxiliary) + nName;
- pAux = (Fts5Auxiliary*)sqlite3_malloc(nByte);
+ pAux = (Fts5Auxiliary*)tdsqlite3_malloc64(nByte);
if( pAux ){
- memset(pAux, 0, nByte);
+ memset(pAux, 0, (size_t)nByte);
pAux->zFunc = (char*)&pAux[1];
memcpy(pAux->zFunc, zName, nName);
pAux->pGlobal = pGlobal;
@@ -198629,15 +227161,15 @@ static int fts5CreateTokenizer(
){
Fts5Global *pGlobal = (Fts5Global*)pApi;
Fts5TokenizerModule *pNew;
- int nName; /* Size of zName and its \0 terminator */
- int nByte; /* Bytes of space to allocate */
+ tdsqlite3_int64 nName; /* Size of zName and its \0 terminator */
+ tdsqlite3_int64 nByte; /* Bytes of space to allocate */
int rc = SQLITE_OK;
- nName = (int)strlen(zName) + 1;
+ nName = strlen(zName) + 1;
nByte = sizeof(Fts5TokenizerModule) + nName;
- pNew = (Fts5TokenizerModule*)sqlite3_malloc(nByte);
+ pNew = (Fts5TokenizerModule*)tdsqlite3_malloc64(nByte);
if( pNew ){
- memset(pNew, 0, nByte);
+ memset(pNew, 0, (size_t)nByte);
pNew->zName = (char*)&pNew[1];
memcpy(pNew->zName, zName, nName);
pNew->pUserData = pUserData;
@@ -198665,7 +227197,7 @@ static Fts5TokenizerModule *fts5LocateTokenizer(
pMod = pGlobal->pDfltTok;
}else{
for(pMod=pGlobal->pTok; pMod; pMod=pMod->pNext){
- if( sqlite3_stricmp(zName, pMod->zName)==0 ) break;
+ if( tdsqlite3_stricmp(zName, pMod->zName)==0 ) break;
}
}
@@ -198697,7 +227229,7 @@ static int fts5FindTokenizer(
return rc;
}
-static int sqlite3Fts5GetTokenizer(
+static int tdsqlite3Fts5GetTokenizer(
Fts5Global *pGlobal,
const char **azArg,
int nArg,
@@ -198712,12 +227244,12 @@ static int sqlite3Fts5GetTokenizer(
if( pMod==0 ){
assert( nArg>0 );
rc = SQLITE_ERROR;
- *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]);
+ *pzErr = tdsqlite3_mprintf("no such tokenizer: %s", azArg[0]);
}else{
rc = pMod->x.xCreate(pMod->pUserData, &azArg[1], (nArg?nArg-1:0), ppTok);
*ppTokApi = &pMod->x;
if( rc!=SQLITE_OK && pzErr ){
- *pzErr = sqlite3_mprintf("error in tokenizer constructor");
+ *pzErr = tdsqlite3_mprintf("error in tokenizer constructor");
}
}
@@ -198737,48 +227269,62 @@ static void fts5ModuleDestroy(void *pCtx){
for(pAux=pGlobal->pAux; pAux; pAux=pNextAux){
pNextAux = pAux->pNext;
if( pAux->xDestroy ) pAux->xDestroy(pAux->pUserData);
- sqlite3_free(pAux);
+ tdsqlite3_free(pAux);
}
for(pTok=pGlobal->pTok; pTok; pTok=pNextTok){
pNextTok = pTok->pNext;
if( pTok->xDestroy ) pTok->xDestroy(pTok->pUserData);
- sqlite3_free(pTok);
+ tdsqlite3_free(pTok);
}
- sqlite3_free(pGlobal);
+ tdsqlite3_free(pGlobal);
}
static void fts5Fts5Func(
- sqlite3_context *pCtx, /* Function call context */
+ tdsqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
- sqlite3_value **apUnused /* Function arguments */
+ tdsqlite3_value **apArg /* Function arguments */
){
- Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx);
- char buf[8];
- UNUSED_PARAM2(nArg, apUnused);
- assert( nArg==0 );
- assert( sizeof(buf)>=sizeof(pGlobal) );
- memcpy(buf, (void*)&pGlobal, sizeof(pGlobal));
- sqlite3_result_blob(pCtx, buf, sizeof(pGlobal), SQLITE_TRANSIENT);
+ Fts5Global *pGlobal = (Fts5Global*)tdsqlite3_user_data(pCtx);
+ fts5_api **ppApi;
+ UNUSED_PARAM(nArg);
+ assert( nArg==1 );
+ ppApi = (fts5_api**)tdsqlite3_value_pointer(apArg[0], "fts5_api_ptr");
+ if( ppApi ) *ppApi = &pGlobal->api;
}
/*
** Implementation of fts5_source_id() function.
*/
static void fts5SourceIdFunc(
- sqlite3_context *pCtx, /* Function call context */
+ tdsqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
- sqlite3_value **apUnused /* Function arguments */
+ tdsqlite3_value **apUnused /* Function arguments */
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
- sqlite3_result_text(pCtx, "fts5: 2016-11-28 19:13:37 bbd85d235f7037c6a033a9690534391ffeacecc8", -1, SQLITE_TRANSIENT);
+ tdsqlite3_result_text(pCtx, "fts5: 2020-01-22 18:38:59 f6affdd41608946fcfcea914ece149038a8b25a62bbe719ed2561c649b86d824", -1, SQLITE_TRANSIENT);
}
-static int fts5Init(sqlite3 *db){
- static const sqlite3_module fts5Mod = {
- /* iVersion */ 2,
+/*
+** Return true if zName is the extension on one of the shadow tables used
+** by this module.
+*/
+static int fts5ShadowName(const char *zName){
+ static const char *azName[] = {
+ "config", "content", "data", "docsize", "idx"
+ };
+ unsigned int i;
+ for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
+ if( tdsqlite3_stricmp(zName, azName[i])==0 ) return 1;
+ }
+ return 0;
+}
+
+static int fts5Init(tdsqlite3 *db){
+ static const tdsqlite3_module fts5Mod = {
+ /* iVersion */ 3,
/* xCreate */ fts5CreateMethod,
/* xConnect */ fts5ConnectMethod,
/* xBestIndex */ fts5BestIndexMethod,
@@ -198801,12 +227347,13 @@ static int fts5Init(sqlite3 *db){
/* xSavepoint */ fts5SavepointMethod,
/* xRelease */ fts5ReleaseMethod,
/* xRollbackTo */ fts5RollbackToMethod,
+ /* xShadowName */ fts5ShadowName
};
int rc;
Fts5Global *pGlobal = 0;
- pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global));
+ pGlobal = (Fts5Global*)tdsqlite3_malloc(sizeof(Fts5Global));
if( pGlobal==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -198817,19 +227364,19 @@ static int fts5Init(sqlite3 *db){
pGlobal->api.xCreateFunction = fts5CreateAux;
pGlobal->api.xCreateTokenizer = fts5CreateTokenizer;
pGlobal->api.xFindTokenizer = fts5FindTokenizer;
- rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy);
- if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db);
- if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(pGlobal, db);
- if( rc==SQLITE_OK ) rc = sqlite3Fts5AuxInit(&pGlobal->api);
- if( rc==SQLITE_OK ) rc = sqlite3Fts5TokenizerInit(&pGlobal->api);
- if( rc==SQLITE_OK ) rc = sqlite3Fts5VocabInit(pGlobal, db);
+ rc = tdsqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy);
+ if( rc==SQLITE_OK ) rc = tdsqlite3Fts5IndexInit(db);
+ if( rc==SQLITE_OK ) rc = tdsqlite3Fts5ExprInit(pGlobal, db);
+ if( rc==SQLITE_OK ) rc = tdsqlite3Fts5AuxInit(&pGlobal->api);
+ if( rc==SQLITE_OK ) rc = tdsqlite3Fts5TokenizerInit(&pGlobal->api);
+ if( rc==SQLITE_OK ) rc = tdsqlite3Fts5VocabInit(pGlobal, db);
if( rc==SQLITE_OK ){
- rc = sqlite3_create_function(
- db, "fts5", 0, SQLITE_UTF8, p, fts5Fts5Func, 0, 0
+ rc = tdsqlite3_create_function(
+ db, "fts5", 1, SQLITE_UTF8, p, fts5Fts5Func, 0, 0
);
}
if( rc==SQLITE_OK ){
- rc = sqlite3_create_function(
+ rc = tdsqlite3_create_function(
db, "fts5_source_id", 0, SQLITE_UTF8, p, fts5SourceIdFunc, 0, 0
);
}
@@ -198840,8 +227387,8 @@ static int fts5Init(sqlite3 *db){
** its entry point to enable the matchinfo() demo. */
#ifdef SQLITE_FTS5_ENABLE_TEST_MI
if( rc==SQLITE_OK ){
- extern int sqlite3Fts5TestRegisterMatchinfo(sqlite3*);
- rc = sqlite3Fts5TestRegisterMatchinfo(db);
+ extern int tdsqlite3Fts5TestRegisterMatchinfo(tdsqlite3*);
+ rc = tdsqlite3Fts5TestRegisterMatchinfo(db);
}
#endif
@@ -198851,20 +227398,20 @@ static int fts5Init(sqlite3 *db){
/*
** The following functions are used to register the module with SQLite. If
** this module is being built as part of the SQLite core (SQLITE_CORE is
-** defined), then sqlite3_open() will call sqlite3Fts5Init() directly.
+** defined), then tdsqlite3_open() will call tdsqlite3Fts5Init() directly.
**
** Or, if this module is being built as a loadable extension,
-** sqlite3Fts5Init() is omitted and the two standard entry points
-** sqlite3_fts_init() and sqlite3_fts5_init() defined instead.
+** tdsqlite3Fts5Init() is omitted and the two standard entry points
+** tdsqlite3_fts_init() and tdsqlite3_fts5_init() defined instead.
*/
#ifndef SQLITE_CORE
#ifdef _WIN32
__declspec(dllexport)
#endif
-SQLITE_API int sqlite3_fts_init(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_fts_init(
+ tdsqlite3 *db,
char **pzErrMsg,
- const sqlite3_api_routines *pApi
+ const tdsqlite3_api_routines *pApi
){
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
@@ -198874,17 +227421,17 @@ SQLITE_API int sqlite3_fts_init(
#ifdef _WIN32
__declspec(dllexport)
#endif
-SQLITE_API int sqlite3_fts5_init(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_fts5_init(
+ tdsqlite3 *db,
char **pzErrMsg,
- const sqlite3_api_routines *pApi
+ const tdsqlite3_api_routines *pApi
){
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
return fts5Init(db);
}
#else
-SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3 *db){
+SQLITE_PRIVATE int tdsqlite3Fts5Init(tdsqlite3 *db){
return fts5Init(db);
}
#endif
@@ -198913,7 +227460,7 @@ struct Fts5Storage {
int bTotalsValid; /* True if nTotalRow/aTotalSize[] are valid */
i64 nTotalRow; /* Total number of rows in FTS table */
i64 *aTotalSize; /* Total sizes of each column */
- sqlite3_stmt *aStmt[11];
+ tdsqlite3_stmt *aStmt[11];
};
@@ -198945,7 +227492,7 @@ struct Fts5Storage {
static int fts5StorageGetStmt(
Fts5Storage *p, /* Storage handle */
int eStmt, /* FTS5_STMT_XXX constant */
- sqlite3_stmt **ppStmt, /* OUT: Prepared statement handle */
+ tdsqlite3_stmt **ppStmt, /* OUT: Prepared statement handle */
char **pzErrMsg /* OUT: Error message (if any) */
){
int rc = SQLITE_OK;
@@ -198981,21 +227528,21 @@ static int fts5StorageGetStmt(
switch( eStmt ){
case FTS5_STMT_SCAN:
- zSql = sqlite3_mprintf(azStmt[eStmt],
+ zSql = tdsqlite3_mprintf(azStmt[eStmt],
pC->zContentExprlist, pC->zContent
);
break;
case FTS5_STMT_SCAN_ASC:
case FTS5_STMT_SCAN_DESC:
- zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist,
+ zSql = tdsqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist,
pC->zContent, pC->zContentRowid, pC->zContentRowid,
pC->zContentRowid
);
break;
case FTS5_STMT_LOOKUP:
- zSql = sqlite3_mprintf(azStmt[eStmt],
+ zSql = tdsqlite3_mprintf(azStmt[eStmt],
pC->zContentExprlist, pC->zContent, pC->zContentRowid
);
break;
@@ -199006,43 +227553,47 @@ static int fts5StorageGetStmt(
char *zBind;
int i;
- zBind = sqlite3_malloc(1 + nCol*2);
+ zBind = tdsqlite3_malloc64(1 + nCol*2);
if( zBind ){
for(i=0; i<nCol; i++){
zBind[i*2] = '?';
zBind[i*2 + 1] = ',';
}
zBind[i*2-1] = '\0';
- zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind);
- sqlite3_free(zBind);
+ zSql = tdsqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind);
+ tdsqlite3_free(zBind);
}
break;
}
default:
- zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName);
+ zSql = tdsqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName);
break;
}
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_prepare_v2(pC->db, zSql, -1, &p->aStmt[eStmt], 0);
- sqlite3_free(zSql);
+ int f = SQLITE_PREPARE_PERSISTENT;
+ if( eStmt>FTS5_STMT_LOOKUP ) f |= SQLITE_PREPARE_NO_VTAB;
+ p->pConfig->bLock++;
+ rc = tdsqlite3_prepare_v3(pC->db, zSql, -1, f, &p->aStmt[eStmt], 0);
+ p->pConfig->bLock--;
+ tdsqlite3_free(zSql);
if( rc!=SQLITE_OK && pzErrMsg ){
- *pzErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pC->db));
+ *pzErrMsg = tdsqlite3_mprintf("%s", tdsqlite3_errmsg(pC->db));
}
}
}
*ppStmt = p->aStmt[eStmt];
- sqlite3_reset(*ppStmt);
+ tdsqlite3_reset(*ppStmt);
return rc;
}
static int fts5ExecPrintf(
- sqlite3 *db,
+ tdsqlite3 *db,
char **pzErr,
const char *zFormat,
...
@@ -199052,13 +227603,13 @@ static int fts5ExecPrintf(
char *zSql;
va_start(ap, zFormat);
- zSql = sqlite3_vmprintf(zFormat, ap);
+ zSql = tdsqlite3_vmprintf(zFormat, ap);
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
- rc = sqlite3_exec(db, zSql, 0, 0, pzErr);
- sqlite3_free(zSql);
+ rc = tdsqlite3_exec(db, zSql, 0, 0, pzErr);
+ tdsqlite3_free(zSql);
}
va_end(ap);
@@ -199069,7 +227620,7 @@ static int fts5ExecPrintf(
** Drop all shadow tables. Return SQLITE_OK if successful or an SQLite error
** code otherwise.
*/
-static int sqlite3Fts5DropAll(Fts5Config *pConfig){
+static int tdsqlite3Fts5DropAll(Fts5Config *pConfig){
int rc = fts5ExecPrintf(pConfig->db, 0,
"DROP TABLE IF EXISTS %Q.'%q_data';"
"DROP TABLE IF EXISTS %Q.'%q_idx';"
@@ -199107,9 +227658,9 @@ static void fts5StorageRenameOne(
}
}
-static int sqlite3Fts5StorageRename(Fts5Storage *pStorage, const char *zName){
+static int tdsqlite3Fts5StorageRename(Fts5Storage *pStorage, const char *zName){
Fts5Config *pConfig = pStorage->pConfig;
- int rc = sqlite3Fts5StorageSync(pStorage, 1);
+ int rc = tdsqlite3Fts5StorageSync(pStorage);
fts5StorageRenameOne(pConfig, &rc, "data", zName);
fts5StorageRenameOne(pConfig, &rc, "idx", zName);
@@ -199127,7 +227678,7 @@ static int sqlite3Fts5StorageRename(Fts5Storage *pStorage, const char *zName){
** Create the shadow table named zPost, with definition zDefn. Return
** SQLITE_OK if successful, or an SQLite error code otherwise.
*/
-static int sqlite3Fts5CreateTable(
+static int tdsqlite3Fts5CreateTable(
Fts5Config *pConfig, /* FTS5 configuration */
const char *zPost, /* Shadow table to create (e.g. "content") */
const char *zDefn, /* Columns etc. for shadow table */
@@ -199145,11 +227696,11 @@ static int sqlite3Fts5CreateTable(
""
);
if( zErr ){
- *pzErr = sqlite3_mprintf(
+ *pzErr = tdsqlite3_mprintf(
"fts5: error creating shadow table %q_%s: %s",
pConfig->zName, zPost, zErr
);
- sqlite3_free(zErr);
+ tdsqlite3_free(zErr);
}
return rc;
@@ -199162,7 +227713,7 @@ static int sqlite3Fts5CreateTable(
** If successful, set *pp to point to the new object and return SQLITE_OK.
** Otherwise, set *pp to NULL and return an SQLite error code.
*/
-static int sqlite3Fts5StorageOpen(
+static int tdsqlite3Fts5StorageOpen(
Fts5Config *pConfig,
Fts5Index *pIndex,
int bCreate,
@@ -199171,14 +227722,14 @@ static int sqlite3Fts5StorageOpen(
){
int rc = SQLITE_OK;
Fts5Storage *p; /* New object */
- int nByte; /* Bytes of space to allocate */
+ tdsqlite3_int64 nByte; /* Bytes of space to allocate */
nByte = sizeof(Fts5Storage) /* Fts5Storage object */
+ pConfig->nCol * sizeof(i64); /* Fts5Storage.aTotalSize[] */
- *pp = p = (Fts5Storage*)sqlite3_malloc(nByte);
+ *pp = p = (Fts5Storage*)tdsqlite3_malloc64(nByte);
if( !p ) return SQLITE_NOMEM;
- memset(p, 0, nByte);
+ memset(p, 0, (size_t)nByte);
p->aTotalSize = (i64*)&p[1];
p->pConfig = pConfig;
p->pIndex = pIndex;
@@ -199186,59 +227737,59 @@ static int sqlite3Fts5StorageOpen(
if( bCreate ){
if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
int nDefn = 32 + pConfig->nCol*10;
- char *zDefn = sqlite3_malloc(32 + pConfig->nCol * 10);
+ char *zDefn = tdsqlite3_malloc64(32 + (tdsqlite3_int64)pConfig->nCol * 10);
if( zDefn==0 ){
rc = SQLITE_NOMEM;
}else{
int i;
int iOff;
- sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY");
+ tdsqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY");
iOff = (int)strlen(zDefn);
for(i=0; i<pConfig->nCol; i++){
- sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i);
+ tdsqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i);
iOff += (int)strlen(&zDefn[iOff]);
}
- rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr);
+ rc = tdsqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr);
}
- sqlite3_free(zDefn);
+ tdsqlite3_free(zDefn);
}
if( rc==SQLITE_OK && pConfig->bColumnsize ){
- rc = sqlite3Fts5CreateTable(
+ rc = tdsqlite3Fts5CreateTable(
pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr
);
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5CreateTable(
+ rc = tdsqlite3Fts5CreateTable(
pConfig, "config", "k PRIMARY KEY, v", 1, pzErr
);
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION);
+ rc = tdsqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION);
}
}
if( rc ){
- sqlite3Fts5StorageClose(p);
+ tdsqlite3Fts5StorageClose(p);
*pp = 0;
}
return rc;
}
/*
-** Close a handle opened by an earlier call to sqlite3Fts5StorageOpen().
+** Close a handle opened by an earlier call to tdsqlite3Fts5StorageOpen().
*/
-static int sqlite3Fts5StorageClose(Fts5Storage *p){
+static int tdsqlite3Fts5StorageClose(Fts5Storage *p){
int rc = SQLITE_OK;
if( p ){
int i;
/* Finalize all SQL statements */
for(i=0; i<ArraySize(p->aStmt); i++){
- sqlite3_finalize(p->aStmt[i]);
+ tdsqlite3_finalize(p->aStmt[i]);
}
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
return rc;
}
@@ -199268,7 +227819,7 @@ static int fts5StorageInsertCallback(
if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
pCtx->szCol++;
}
- return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken);
+ return tdsqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken);
}
/*
@@ -199279,40 +227830,40 @@ static int fts5StorageInsertCallback(
static int fts5StorageDeleteFromIndex(
Fts5Storage *p,
i64 iDel,
- sqlite3_value **apVal
+ tdsqlite3_value **apVal
){
Fts5Config *pConfig = p->pConfig;
- sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */
+ tdsqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */
int rc; /* Return code */
- int rc2; /* sqlite3_reset() return code */
+ int rc2; /* tdsqlite3_reset() return code */
int iCol;
Fts5InsertCtx ctx;
if( apVal==0 ){
rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0);
if( rc!=SQLITE_OK ) return rc;
- sqlite3_bind_int64(pSeek, 1, iDel);
- if( sqlite3_step(pSeek)!=SQLITE_ROW ){
- return sqlite3_reset(pSeek);
+ tdsqlite3_bind_int64(pSeek, 1, iDel);
+ if( tdsqlite3_step(pSeek)!=SQLITE_ROW ){
+ return tdsqlite3_reset(pSeek);
}
}
ctx.pStorage = p;
ctx.iCol = -1;
- rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
+ rc = tdsqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
if( pConfig->abUnindexed[iCol-1]==0 ){
const char *zText;
int nText;
if( pSeek ){
- zText = (const char*)sqlite3_column_text(pSeek, iCol);
- nText = sqlite3_column_bytes(pSeek, iCol);
+ zText = (const char*)tdsqlite3_column_text(pSeek, iCol);
+ nText = tdsqlite3_column_bytes(pSeek, iCol);
}else{
- zText = (const char*)sqlite3_value_text(apVal[iCol-1]);
- nText = sqlite3_value_bytes(apVal[iCol-1]);
+ zText = (const char*)tdsqlite3_value_text(apVal[iCol-1]);
+ nText = tdsqlite3_value_bytes(apVal[iCol-1]);
}
ctx.szCol = 0;
- rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT,
+ rc = tdsqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT,
zText, nText, (void*)&ctx, fts5StorageInsertCallback
);
p->aTotalSize[iCol-1] -= (i64)ctx.szCol;
@@ -199320,7 +227871,7 @@ static int fts5StorageDeleteFromIndex(
}
p->nTotalRow--;
- rc2 = sqlite3_reset(pSeek);
+ rc2 = tdsqlite3_reset(pSeek);
if( rc==SQLITE_OK ) rc = rc2;
return rc;
}
@@ -199341,13 +227892,14 @@ static int fts5StorageInsertDocsize(
){
int rc = SQLITE_OK;
if( p->pConfig->bColumnsize ){
- sqlite3_stmt *pReplace = 0;
+ tdsqlite3_stmt *pReplace = 0;
rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pReplace, 1, iRowid);
- sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
- sqlite3_step(pReplace);
- rc = sqlite3_reset(pReplace);
+ tdsqlite3_bind_int64(pReplace, 1, iRowid);
+ tdsqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
+ tdsqlite3_step(pReplace);
+ rc = tdsqlite3_reset(pReplace);
+ tdsqlite3_bind_null(pReplace, 2);
}
}
return rc;
@@ -199366,7 +227918,7 @@ static int fts5StorageInsertDocsize(
static int fts5StorageLoadTotals(Fts5Storage *p, int bCache){
int rc = SQLITE_OK;
if( p->bTotalsValid==0 ){
- rc = sqlite3Fts5IndexGetAverages(p->pIndex, &p->nTotalRow, p->aTotalSize);
+ rc = tdsqlite3Fts5IndexGetAverages(p->pIndex, &p->nTotalRow, p->aTotalSize);
p->bTotalsValid = bCache;
}
return rc;
@@ -199386,14 +227938,14 @@ static int fts5StorageSaveTotals(Fts5Storage *p){
int rc = SQLITE_OK;
memset(&buf, 0, sizeof(buf));
- sqlite3Fts5BufferAppendVarint(&rc, &buf, p->nTotalRow);
+ tdsqlite3Fts5BufferAppendVarint(&rc, &buf, p->nTotalRow);
for(i=0; i<nCol; i++){
- sqlite3Fts5BufferAppendVarint(&rc, &buf, p->aTotalSize[i]);
+ tdsqlite3Fts5BufferAppendVarint(&rc, &buf, p->aTotalSize[i]);
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexSetAverages(p->pIndex, buf.p, buf.n);
+ rc = tdsqlite3Fts5IndexSetAverages(p->pIndex, buf.p, buf.n);
}
- sqlite3_free(buf.p);
+ tdsqlite3_free(buf.p);
return rc;
}
@@ -199401,10 +227953,10 @@ static int fts5StorageSaveTotals(Fts5Storage *p){
/*
** Remove a row from the FTS table.
*/
-static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){
+static int tdsqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, tdsqlite3_value **apVal){
Fts5Config *pConfig = p->pConfig;
int rc;
- sqlite3_stmt *pDel = 0;
+ tdsqlite3_stmt *pDel = 0;
assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 );
rc = fts5StorageLoadTotals(p, 1);
@@ -199418,9 +227970,9 @@ static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **ap
if( rc==SQLITE_OK && pConfig->bColumnsize ){
rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pDel, 1, iDel);
- sqlite3_step(pDel);
- rc = sqlite3_reset(pDel);
+ tdsqlite3_bind_int64(pDel, 1, iDel);
+ tdsqlite3_step(pDel);
+ rc = tdsqlite3_reset(pDel);
}
}
@@ -199430,27 +227982,24 @@ static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **ap
rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel, 0);
}
if( rc==SQLITE_OK ){
- sqlite3_bind_int64(pDel, 1, iDel);
- sqlite3_step(pDel);
- rc = sqlite3_reset(pDel);
+ tdsqlite3_bind_int64(pDel, 1, iDel);
+ tdsqlite3_step(pDel);
+ rc = tdsqlite3_reset(pDel);
}
}
- /* Write the averages record */
- if( rc==SQLITE_OK ){
- rc = fts5StorageSaveTotals(p);
- }
-
return rc;
}
/*
** Delete all entries in the FTS5 index.
*/
-static int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){
+static int tdsqlite3Fts5StorageDeleteAll(Fts5Storage *p){
Fts5Config *pConfig = p->pConfig;
int rc;
+ p->bTotalsValid = 0;
+
/* Delete the contents of the %_data and %_docsize tables. */
rc = fts5ExecPrintf(pConfig->db, 0,
"DELETE FROM %Q.'%q_data';"
@@ -199468,24 +228017,24 @@ static int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){
/* Reinitialize the %_data table. This call creates the initial structure
** and averages records. */
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexReinit(p->pIndex);
+ rc = tdsqlite3Fts5IndexReinit(p->pIndex);
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION);
+ rc = tdsqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION);
}
return rc;
}
-static int sqlite3Fts5StorageRebuild(Fts5Storage *p){
+static int tdsqlite3Fts5StorageRebuild(Fts5Storage *p){
Fts5Buffer buf = {0,0,0};
Fts5Config *pConfig = p->pConfig;
- sqlite3_stmt *pScan = 0;
+ tdsqlite3_stmt *pScan = 0;
Fts5InsertCtx ctx;
- int rc;
+ int rc, rc2;
memset(&ctx, 0, sizeof(Fts5InsertCtx));
ctx.pStorage = p;
- rc = sqlite3Fts5StorageDeleteAll(p);
+ rc = tdsqlite3Fts5StorageDeleteAll(p);
if( rc==SQLITE_OK ){
rc = fts5StorageLoadTotals(p, 1);
}
@@ -199494,23 +228043,24 @@ static int sqlite3Fts5StorageRebuild(Fts5Storage *p){
rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0);
}
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){
- i64 iRowid = sqlite3_column_int64(pScan, 0);
+ while( rc==SQLITE_OK && SQLITE_ROW==tdsqlite3_step(pScan) ){
+ i64 iRowid = tdsqlite3_column_int64(pScan, 0);
- sqlite3Fts5BufferZero(&buf);
- rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
+ tdsqlite3Fts5BufferZero(&buf);
+ rc = tdsqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
ctx.szCol = 0;
if( pConfig->abUnindexed[ctx.iCol]==0 ){
- rc = sqlite3Fts5Tokenize(pConfig,
+ const char *zText = (const char*)tdsqlite3_column_text(pScan, ctx.iCol+1);
+ int nText = tdsqlite3_column_bytes(pScan, ctx.iCol+1);
+ rc = tdsqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT,
- (const char*)sqlite3_column_text(pScan, ctx.iCol+1),
- sqlite3_column_bytes(pScan, ctx.iCol+1),
+ zText, nText,
(void*)&ctx,
fts5StorageInsertCallback
);
}
- sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
+ tdsqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
}
p->nTotalRow++;
@@ -199519,7 +228069,9 @@ static int sqlite3Fts5StorageRebuild(Fts5Storage *p){
rc = fts5StorageInsertDocsize(p, iRowid, &buf);
}
}
- sqlite3_free(buf.p);
+ tdsqlite3_free(buf.p);
+ rc2 = tdsqlite3_reset(pScan);
+ if( rc==SQLITE_OK ) rc = rc2;
/* Write the averages record */
if( rc==SQLITE_OK ){
@@ -199528,16 +228080,16 @@ static int sqlite3Fts5StorageRebuild(Fts5Storage *p){
return rc;
}
-static int sqlite3Fts5StorageOptimize(Fts5Storage *p){
- return sqlite3Fts5IndexOptimize(p->pIndex);
+static int tdsqlite3Fts5StorageOptimize(Fts5Storage *p){
+ return tdsqlite3Fts5IndexOptimize(p->pIndex);
}
-static int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge){
- return sqlite3Fts5IndexMerge(p->pIndex, nMerge);
+static int tdsqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge){
+ return tdsqlite3Fts5IndexMerge(p->pIndex, nMerge);
}
-static int sqlite3Fts5StorageReset(Fts5Storage *p){
- return sqlite3Fts5IndexReset(p->pIndex);
+static int tdsqlite3Fts5StorageReset(Fts5Storage *p){
+ return tdsqlite3Fts5IndexReset(p->pIndex);
}
/*
@@ -199552,16 +228104,16 @@ static int sqlite3Fts5StorageReset(Fts5Storage *p){
static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){
int rc = SQLITE_MISMATCH;
if( p->pConfig->bColumnsize ){
- sqlite3_stmt *pReplace = 0;
+ tdsqlite3_stmt *pReplace = 0;
rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_null(pReplace, 1);
- sqlite3_bind_null(pReplace, 2);
- sqlite3_step(pReplace);
- rc = sqlite3_reset(pReplace);
+ tdsqlite3_bind_null(pReplace, 1);
+ tdsqlite3_bind_null(pReplace, 2);
+ tdsqlite3_step(pReplace);
+ rc = tdsqlite3_reset(pReplace);
}
if( rc==SQLITE_OK ){
- *piRowid = sqlite3_last_insert_rowid(p->pConfig->db);
+ *piRowid = tdsqlite3_last_insert_rowid(p->pConfig->db);
}
}
return rc;
@@ -199570,9 +228122,9 @@ static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){
/*
** Insert a new row into the FTS content table.
*/
-static int sqlite3Fts5StorageContentInsert(
+static int tdsqlite3Fts5StorageContentInsert(
Fts5Storage *p,
- sqlite3_value **apVal,
+ tdsqlite3_value **apVal,
i64 *piRowid
){
Fts5Config *pConfig = p->pConfig;
@@ -199580,23 +228132,23 @@ static int sqlite3Fts5StorageContentInsert(
/* Insert the new row into the %_content table. */
if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){
- if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){
- *piRowid = sqlite3_value_int64(apVal[1]);
+ if( tdsqlite3_value_type(apVal[1])==SQLITE_INTEGER ){
+ *piRowid = tdsqlite3_value_int64(apVal[1]);
}else{
rc = fts5StorageNewRowid(p, piRowid);
}
}else{
- sqlite3_stmt *pInsert = 0; /* Statement to write %_content table */
+ tdsqlite3_stmt *pInsert = 0; /* Statement to write %_content table */
int i; /* Counter variable */
rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0);
for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
- rc = sqlite3_bind_value(pInsert, i, apVal[i]);
+ rc = tdsqlite3_bind_value(pInsert, i, apVal[i]);
}
if( rc==SQLITE_OK ){
- sqlite3_step(pInsert);
- rc = sqlite3_reset(pInsert);
+ tdsqlite3_step(pInsert);
+ rc = tdsqlite3_reset(pInsert);
}
- *piRowid = sqlite3_last_insert_rowid(pConfig->db);
+ *piRowid = tdsqlite3_last_insert_rowid(pConfig->db);
}
return rc;
@@ -199605,9 +228157,9 @@ static int sqlite3Fts5StorageContentInsert(
/*
** Insert new entries into the FTS index and %_docsize table.
*/
-static int sqlite3Fts5StorageIndexInsert(
+static int tdsqlite3Fts5StorageIndexInsert(
Fts5Storage *p,
- sqlite3_value **apVal,
+ tdsqlite3_value **apVal,
i64 iRowid
){
Fts5Config *pConfig = p->pConfig;
@@ -199620,20 +228172,21 @@ static int sqlite3Fts5StorageIndexInsert(
rc = fts5StorageLoadTotals(p, 1);
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
+ rc = tdsqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
}
for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
ctx.szCol = 0;
if( pConfig->abUnindexed[ctx.iCol]==0 ){
- rc = sqlite3Fts5Tokenize(pConfig,
+ const char *zText = (const char*)tdsqlite3_value_text(apVal[ctx.iCol+2]);
+ int nText = tdsqlite3_value_bytes(apVal[ctx.iCol+2]);
+ rc = tdsqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT,
- (const char*)sqlite3_value_text(apVal[ctx.iCol+2]),
- sqlite3_value_bytes(apVal[ctx.iCol+2]),
+ zText, nText,
(void*)&ctx,
fts5StorageInsertCallback
);
}
- sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
+ tdsqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
}
p->nTotalRow++;
@@ -199642,12 +228195,7 @@ static int sqlite3Fts5StorageIndexInsert(
if( rc==SQLITE_OK ){
rc = fts5StorageInsertDocsize(p, iRowid, &buf);
}
- sqlite3_free(buf.p);
-
- /* Write the averages record */
- if( rc==SQLITE_OK ){
- rc = fts5StorageSaveTotals(p);
- }
+ tdsqlite3_free(buf.p);
return rc;
}
@@ -199657,28 +228205,28 @@ static int fts5StorageCount(Fts5Storage *p, const char *zSuffix, i64 *pnRow){
char *zSql;
int rc;
- zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'",
+ zSql = tdsqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'",
pConfig->zDb, pConfig->zName, zSuffix
);
if( zSql==0 ){
rc = SQLITE_NOMEM;
}else{
- sqlite3_stmt *pCnt = 0;
- rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pCnt, 0);
+ tdsqlite3_stmt *pCnt = 0;
+ rc = tdsqlite3_prepare_v2(pConfig->db, zSql, -1, &pCnt, 0);
if( rc==SQLITE_OK ){
- if( SQLITE_ROW==sqlite3_step(pCnt) ){
- *pnRow = sqlite3_column_int64(pCnt, 0);
+ if( SQLITE_ROW==tdsqlite3_step(pCnt) ){
+ *pnRow = tdsqlite3_column_int64(pCnt, 0);
}
- rc = sqlite3_finalize(pCnt);
+ rc = tdsqlite3_finalize(pCnt);
}
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
return rc;
}
/*
-** Context object used by sqlite3Fts5StorageIntegrity().
+** Context object used by tdsqlite3Fts5StorageIntegrity().
*/
typedef struct Fts5IntegrityCtx Fts5IntegrityCtx;
struct Fts5IntegrityCtx {
@@ -199735,20 +228283,20 @@ static int fts5StorageIntegrityCallback(
break;
}
- rc = sqlite3Fts5TermsetAdd(pTermset, 0, pToken, nToken, &bPresent);
+ rc = tdsqlite3Fts5TermsetAdd(pTermset, 0, pToken, nToken, &bPresent);
if( rc==SQLITE_OK && bPresent==0 ){
- pCtx->cksum ^= sqlite3Fts5IndexEntryCksum(
+ pCtx->cksum ^= tdsqlite3Fts5IndexEntryCksum(
pCtx->iRowid, iCol, iPos, 0, pToken, nToken
);
}
for(ii=0; rc==SQLITE_OK && ii<pCtx->pConfig->nPrefix; ii++){
const int nChar = pCtx->pConfig->aPrefix[ii];
- int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar);
+ int nByte = tdsqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar);
if( nByte ){
- rc = sqlite3Fts5TermsetAdd(pTermset, ii+1, pToken, nByte, &bPresent);
+ rc = tdsqlite3Fts5TermsetAdd(pTermset, ii+1, pToken, nByte, &bPresent);
if( bPresent==0 ){
- pCtx->cksum ^= sqlite3Fts5IndexEntryCksum(
+ pCtx->cksum ^= tdsqlite3Fts5IndexEntryCksum(
pCtx->iRowid, iCol, iPos, ii+1, pToken, nByte
);
}
@@ -199764,17 +228312,17 @@ static int fts5StorageIntegrityCallback(
** some other SQLite error code if an error occurs while attempting to
** determine this.
*/
-static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
+static int tdsqlite3Fts5StorageIntegrity(Fts5Storage *p){
Fts5Config *pConfig = p->pConfig;
int rc; /* Return code */
int *aColSize; /* Array of size pConfig->nCol */
i64 *aTotalSize; /* Array of size pConfig->nCol */
Fts5IntegrityCtx ctx;
- sqlite3_stmt *pScan;
+ tdsqlite3_stmt *pScan;
memset(&ctx, 0, sizeof(Fts5IntegrityCtx));
ctx.pConfig = p->pConfig;
- aTotalSize = (i64*)sqlite3_malloc(pConfig->nCol * (sizeof(int)+sizeof(i64)));
+ aTotalSize = (i64*)tdsqlite3_malloc64(pConfig->nCol*(sizeof(int)+sizeof(i64)));
if( !aTotalSize ) return SQLITE_NOMEM;
aColSize = (int*)&aTotalSize[pConfig->nCol];
memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol);
@@ -199784,28 +228332,29 @@ static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0);
if( rc==SQLITE_OK ){
int rc2;
- while( SQLITE_ROW==sqlite3_step(pScan) ){
+ while( SQLITE_ROW==tdsqlite3_step(pScan) ){
int i;
- ctx.iRowid = sqlite3_column_int64(pScan, 0);
+ ctx.iRowid = tdsqlite3_column_int64(pScan, 0);
ctx.szCol = 0;
if( pConfig->bColumnsize ){
- rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
+ rc = tdsqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
}
if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_NONE ){
- rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
+ rc = tdsqlite3Fts5TermsetNew(&ctx.pTermset);
}
for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
if( pConfig->abUnindexed[i] ) continue;
ctx.iCol = i;
ctx.szCol = 0;
if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
- rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
+ rc = tdsqlite3Fts5TermsetNew(&ctx.pTermset);
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5Tokenize(pConfig,
+ const char *zText = (const char*)tdsqlite3_column_text(pScan, i+1);
+ int nText = tdsqlite3_column_bytes(pScan, i+1);
+ rc = tdsqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT,
- (const char*)sqlite3_column_text(pScan, i+1),
- sqlite3_column_bytes(pScan, i+1),
+ zText, nText,
(void*)&ctx,
fts5StorageIntegrityCallback
);
@@ -199815,16 +228364,16 @@ static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
}
aTotalSize[i] += ctx.szCol;
if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
- sqlite3Fts5TermsetFree(ctx.pTermset);
+ tdsqlite3Fts5TermsetFree(ctx.pTermset);
ctx.pTermset = 0;
}
}
- sqlite3Fts5TermsetFree(ctx.pTermset);
+ tdsqlite3Fts5TermsetFree(ctx.pTermset);
ctx.pTermset = 0;
if( rc!=SQLITE_OK ) break;
}
- rc2 = sqlite3_reset(pScan);
+ rc2 = tdsqlite3_reset(pScan);
if( rc==SQLITE_OK ) rc = rc2;
}
@@ -199854,10 +228403,10 @@ static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
** verify, amongst other things, that it matches the checksum generated by
** inspecting the index itself. */
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum);
+ rc = tdsqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum);
}
- sqlite3_free(aTotalSize);
+ tdsqlite3_free(aTotalSize);
return rc;
}
@@ -199865,10 +228414,10 @@ static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
** Obtain an SQLite statement handle that may be used to read data from the
** %_content table.
*/
-static int sqlite3Fts5StorageStmt(
+static int tdsqlite3Fts5StorageStmt(
Fts5Storage *p,
int eStmt,
- sqlite3_stmt **pp,
+ tdsqlite3_stmt **pp,
char **pzErrMsg
){
int rc;
@@ -199886,23 +228435,23 @@ static int sqlite3Fts5StorageStmt(
/*
** Release an SQLite statement handle obtained via an earlier call to
-** sqlite3Fts5StorageStmt(). The eStmt parameter passed to this function
-** must match that passed to the sqlite3Fts5StorageStmt() call.
+** tdsqlite3Fts5StorageStmt(). The eStmt parameter passed to this function
+** must match that passed to the tdsqlite3Fts5StorageStmt() call.
*/
-static void sqlite3Fts5StorageStmtRelease(
+static void tdsqlite3Fts5StorageStmtRelease(
Fts5Storage *p,
int eStmt,
- sqlite3_stmt *pStmt
+ tdsqlite3_stmt *pStmt
){
assert( eStmt==FTS5_STMT_SCAN_ASC
|| eStmt==FTS5_STMT_SCAN_DESC
|| eStmt==FTS5_STMT_LOOKUP
);
if( p->aStmt[eStmt]==0 ){
- sqlite3_reset(pStmt);
+ tdsqlite3_reset(pStmt);
p->aStmt[eStmt] = pStmt;
}else{
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
}
}
@@ -199927,24 +228476,24 @@ static int fts5StorageDecodeSizeArray(
** An SQLite error code is returned if an error occurs, or SQLITE_OK
** otherwise.
*/
-static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){
+static int tdsqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){
int nCol = p->pConfig->nCol; /* Number of user columns in table */
- sqlite3_stmt *pLookup = 0; /* Statement to query %_docsize */
+ tdsqlite3_stmt *pLookup = 0; /* Statement to query %_docsize */
int rc; /* Return Code */
assert( p->pConfig->bColumnsize );
rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0);
if( rc==SQLITE_OK ){
int bCorrupt = 1;
- sqlite3_bind_int64(pLookup, 1, iRowid);
- if( SQLITE_ROW==sqlite3_step(pLookup) ){
- const u8 *aBlob = sqlite3_column_blob(pLookup, 0);
- int nBlob = sqlite3_column_bytes(pLookup, 0);
+ tdsqlite3_bind_int64(pLookup, 1, iRowid);
+ if( SQLITE_ROW==tdsqlite3_step(pLookup) ){
+ const u8 *aBlob = tdsqlite3_column_blob(pLookup, 0);
+ int nBlob = tdsqlite3_column_bytes(pLookup, 0);
if( 0==fts5StorageDecodeSizeArray(aCol, nCol, aBlob, nBlob) ){
bCorrupt = 0;
}
}
- rc = sqlite3_reset(pLookup);
+ rc = tdsqlite3_reset(pLookup);
if( bCorrupt && rc==SQLITE_OK ){
rc = FTS5_CORRUPT;
}
@@ -199953,7 +228502,7 @@ static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){
return rc;
}
-static int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){
+static int tdsqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){
int rc = fts5StorageLoadTotals(p, 0);
if( rc==SQLITE_OK ){
*pnToken = 0;
@@ -199971,10 +228520,16 @@ static int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){
return rc;
}
-static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){
+static int tdsqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){
int rc = fts5StorageLoadTotals(p, 0);
if( rc==SQLITE_OK ){
+ /* nTotalRow being zero does not necessarily indicate a corrupt
+ ** database - it might be that the FTS5 table really does contain zero
+ ** rows. However this function is only called from the xRowCount() API,
+ ** and there is no way for that API to be invoked if the table contains
+ ** no rows. Hence the FTS5_CORRUPT return. */
*pnRow = p->nTotalRow;
+ if( p->nTotalRow<=0 ) rc = FTS5_CORRUPT;
}
return rc;
}
@@ -199982,41 +228537,47 @@ static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){
/*
** Flush any data currently held in-memory to disk.
*/
-static int sqlite3Fts5StorageSync(Fts5Storage *p, int bCommit){
- if( bCommit && p->bTotalsValid ){
- int rc = fts5StorageSaveTotals(p);
+static int tdsqlite3Fts5StorageSync(Fts5Storage *p){
+ int rc = SQLITE_OK;
+ i64 iLastRowid = tdsqlite3_last_insert_rowid(p->pConfig->db);
+ if( p->bTotalsValid ){
+ rc = fts5StorageSaveTotals(p);
p->bTotalsValid = 0;
- if( rc!=SQLITE_OK ) return rc;
}
- return sqlite3Fts5IndexSync(p->pIndex, bCommit);
+ if( rc==SQLITE_OK ){
+ rc = tdsqlite3Fts5IndexSync(p->pIndex);
+ }
+ tdsqlite3_set_last_insert_rowid(p->pConfig->db, iLastRowid);
+ return rc;
}
-static int sqlite3Fts5StorageRollback(Fts5Storage *p){
+static int tdsqlite3Fts5StorageRollback(Fts5Storage *p){
p->bTotalsValid = 0;
- return sqlite3Fts5IndexRollback(p->pIndex);
+ return tdsqlite3Fts5IndexRollback(p->pIndex);
}
-static int sqlite3Fts5StorageConfigValue(
+static int tdsqlite3Fts5StorageConfigValue(
Fts5Storage *p,
const char *z,
- sqlite3_value *pVal,
+ tdsqlite3_value *pVal,
int iVal
){
- sqlite3_stmt *pReplace = 0;
+ tdsqlite3_stmt *pReplace = 0;
int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_CONFIG, &pReplace, 0);
if( rc==SQLITE_OK ){
- sqlite3_bind_text(pReplace, 1, z, -1, SQLITE_STATIC);
+ tdsqlite3_bind_text(pReplace, 1, z, -1, SQLITE_STATIC);
if( pVal ){
- sqlite3_bind_value(pReplace, 2, pVal);
+ tdsqlite3_bind_value(pReplace, 2, pVal);
}else{
- sqlite3_bind_int(pReplace, 2, iVal);
+ tdsqlite3_bind_int(pReplace, 2, iVal);
}
- sqlite3_step(pReplace);
- rc = sqlite3_reset(pReplace);
+ tdsqlite3_step(pReplace);
+ rc = tdsqlite3_reset(pReplace);
+ tdsqlite3_bind_null(pReplace, 1);
}
if( rc==SQLITE_OK && pVal ){
int iNew = p->pConfig->iCookie + 1;
- rc = sqlite3Fts5IndexSetCookie(p->pIndex, iNew);
+ rc = tdsqlite3Fts5IndexSetCookie(p->pIndex, iNew);
if( rc==SQLITE_OK ){
p->pConfig->iCookie = iNew;
}
@@ -200081,7 +228642,7 @@ static void fts5AsciiAddExceptions(
** Delete a "ascii" tokenizer.
*/
static void fts5AsciiDelete(Fts5Tokenizer *p){
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
/*
@@ -200098,7 +228659,7 @@ static int fts5AsciiCreate(
if( nArg%2 ){
rc = SQLITE_ERROR;
}else{
- p = sqlite3_malloc(sizeof(AsciiTokenizer));
+ p = tdsqlite3_malloc(sizeof(AsciiTokenizer));
if( p==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -200107,10 +228668,10 @@ static int fts5AsciiCreate(
memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar));
for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
const char *zArg = azArg[i+1];
- if( 0==sqlite3_stricmp(azArg[i], "tokenchars") ){
+ if( 0==tdsqlite3_stricmp(azArg[i], "tokenchars") ){
fts5AsciiAddExceptions(p, zArg, 1);
}else
- if( 0==sqlite3_stricmp(azArg[i], "separators") ){
+ if( 0==tdsqlite3_stricmp(azArg[i], "separators") ){
fts5AsciiAddExceptions(p, zArg, 0);
}else{
rc = SQLITE_ERROR;
@@ -200177,8 +228738,8 @@ static int fts5AsciiTokenize(
/* Fold to lower case */
nByte = ie-is;
if( nByte>nFold ){
- if( pFold!=aFold ) sqlite3_free(pFold);
- pFold = sqlite3_malloc(nByte*2);
+ if( pFold!=aFold ) tdsqlite3_free(pFold);
+ pFold = tdsqlite3_malloc64((tdsqlite3_int64)nByte*2);
if( pFold==0 ){
rc = SQLITE_NOMEM;
break;
@@ -200192,7 +228753,7 @@ static int fts5AsciiTokenize(
is = ie+1;
}
- if( pFold!=aFold ) sqlite3_free(pFold);
+ if( pFold!=aFold ) tdsqlite3_free(pFold);
if( rc==SQLITE_DONE ) rc = SQLITE_OK;
return rc;
}
@@ -200204,12 +228765,12 @@ static int fts5AsciiTokenize(
/*
** The following two macros - READ_UTF8 and WRITE_UTF8 - have been copied
-** from the sqlite3 source file utf.c. If this file is compiled as part
+** from the tdsqlite3 source file utf.c. If this file is compiled as part
** of the amalgamation, they are not required.
*/
#ifndef SQLITE_AMALGAMATION
-static const unsigned char sqlite3Utf8Trans1[] = {
+static const unsigned char tdsqlite3Utf8Trans1[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
@@ -200223,7 +228784,7 @@ static const unsigned char sqlite3Utf8Trans1[] = {
#define READ_UTF8(zIn, zTerm, c) \
c = *(zIn++); \
if( c>=0xc0 ){ \
- c = sqlite3Utf8Trans1[c-0xc0]; \
+ c = tdsqlite3Utf8Trans1[c-0xc0]; \
while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \
c = (c<<6) + (0x3f & *(zIn++)); \
} \
@@ -200260,11 +228821,18 @@ struct Unicode61Tokenizer {
unsigned char aTokenChar[128]; /* ASCII range token characters */
char *aFold; /* Buffer to fold text into */
int nFold; /* Size of aFold[] in bytes */
- int bRemoveDiacritic; /* True if remove_diacritics=1 is set */
+ int eRemoveDiacritic; /* True if remove_diacritics=1 is set */
int nException;
int *aiException;
+
+ unsigned char aCategory[32]; /* True for token char categories */
};
+/* Values for eRemoveDiacritic (must match internals of fts5_unicode2.c) */
+#define FTS5_REMOVE_DIACRITICS_NONE 0
+#define FTS5_REMOVE_DIACRITICS_SIMPLE 1
+#define FTS5_REMOVE_DIACRITICS_COMPLEX 2
+
static int fts5UnicodeAddExceptions(
Unicode61Tokenizer *p, /* Tokenizer object */
const char *z, /* Characters to treat as exceptions */
@@ -200275,25 +228843,26 @@ static int fts5UnicodeAddExceptions(
int *aNew;
if( n>0 ){
- aNew = (int*)sqlite3_realloc(p->aiException, (n+p->nException)*sizeof(int));
+ aNew = (int*)tdsqlite3_realloc64(p->aiException,
+ (n+p->nException)*sizeof(int));
if( aNew ){
int nNew = p->nException;
const unsigned char *zCsr = (const unsigned char*)z;
const unsigned char *zTerm = (const unsigned char*)&z[n];
while( zCsr<zTerm ){
- int iCode;
+ u32 iCode;
int bToken;
READ_UTF8(zCsr, zTerm, iCode);
if( iCode<128 ){
p->aTokenChar[iCode] = (unsigned char)bTokenChars;
}else{
- bToken = sqlite3Fts5UnicodeIsalnum(iCode);
+ bToken = p->aCategory[tdsqlite3Fts5UnicodeCategory(iCode)];
assert( (bToken==0 || bToken==1) );
assert( (bTokenChars==0 || bTokenChars==1) );
- if( bToken!=bTokenChars && sqlite3Fts5UnicodeIsdiacritic(iCode)==0 ){
+ if( bToken!=bTokenChars && tdsqlite3Fts5UnicodeIsdiacritic(iCode)==0 ){
int i;
for(i=0; i<nNew; i++){
- if( aNew[i]>iCode ) break;
+ if( (u32)aNew[i]>iCode ) break;
}
memmove(&aNew[i+1], &aNew[i], (nNew-i)*sizeof(int));
aNew[i] = iCode;
@@ -200341,13 +228910,28 @@ static int fts5UnicodeIsException(Unicode61Tokenizer *p, int iCode){
static void fts5UnicodeDelete(Fts5Tokenizer *pTok){
if( pTok ){
Unicode61Tokenizer *p = (Unicode61Tokenizer*)pTok;
- sqlite3_free(p->aiException);
- sqlite3_free(p->aFold);
- sqlite3_free(p);
+ tdsqlite3_free(p->aiException);
+ tdsqlite3_free(p->aFold);
+ tdsqlite3_free(p);
}
return;
}
+static int unicodeSetCategories(Unicode61Tokenizer *p, const char *zCat){
+ const char *z = zCat;
+
+ while( *z ){
+ while( *z==' ' || *z=='\t' ) z++;
+ if( *z && tdsqlite3Fts5UnicodeCatParse(z, p->aCategory) ){
+ return SQLITE_ERROR;
+ }
+ while( *z!=' ' && *z!='\t' && *z!='\0' ) z++;
+ }
+
+ tdsqlite3Fts5UnicodeAscii(p->aCategory, p->aTokenChar);
+ return SQLITE_OK;
+}
+
/*
** Create a "unicode61" tokenizer.
*/
@@ -200364,34 +228948,56 @@ static int fts5UnicodeCreate(
if( nArg%2 ){
rc = SQLITE_ERROR;
}else{
- p = (Unicode61Tokenizer*)sqlite3_malloc(sizeof(Unicode61Tokenizer));
+ p = (Unicode61Tokenizer*)tdsqlite3_malloc(sizeof(Unicode61Tokenizer));
if( p ){
+ const char *zCat = "L* N* Co";
int i;
memset(p, 0, sizeof(Unicode61Tokenizer));
- memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar));
- p->bRemoveDiacritic = 1;
+
+ p->eRemoveDiacritic = FTS5_REMOVE_DIACRITICS_SIMPLE;
p->nFold = 64;
- p->aFold = sqlite3_malloc(p->nFold * sizeof(char));
+ p->aFold = tdsqlite3_malloc64(p->nFold * sizeof(char));
if( p->aFold==0 ){
rc = SQLITE_NOMEM;
}
+
+ /* Search for a "categories" argument */
+ for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
+ if( 0==tdsqlite3_stricmp(azArg[i], "categories") ){
+ zCat = azArg[i+1];
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = unicodeSetCategories(p, zCat);
+ }
+
for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
const char *zArg = azArg[i+1];
- if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){
- if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1] ){
+ if( 0==tdsqlite3_stricmp(azArg[i], "remove_diacritics") ){
+ if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){
rc = SQLITE_ERROR;
+ }else{
+ p->eRemoveDiacritic = (zArg[0] - '0');
+ assert( p->eRemoveDiacritic==FTS5_REMOVE_DIACRITICS_NONE
+ || p->eRemoveDiacritic==FTS5_REMOVE_DIACRITICS_SIMPLE
+ || p->eRemoveDiacritic==FTS5_REMOVE_DIACRITICS_COMPLEX
+ );
}
- p->bRemoveDiacritic = (zArg[0]=='1');
}else
- if( 0==sqlite3_stricmp(azArg[i], "tokenchars") ){
+ if( 0==tdsqlite3_stricmp(azArg[i], "tokenchars") ){
rc = fts5UnicodeAddExceptions(p, zArg, 1);
}else
- if( 0==sqlite3_stricmp(azArg[i], "separators") ){
+ if( 0==tdsqlite3_stricmp(azArg[i], "separators") ){
rc = fts5UnicodeAddExceptions(p, zArg, 0);
+ }else
+ if( 0==tdsqlite3_stricmp(azArg[i], "categories") ){
+ /* no-op */
}else{
rc = SQLITE_ERROR;
}
}
+
}else{
rc = SQLITE_NOMEM;
}
@@ -200410,8 +229016,10 @@ static int fts5UnicodeCreate(
** character (not a separator).
*/
static int fts5UnicodeIsAlnum(Unicode61Tokenizer *p, int iCode){
- assert( (sqlite3Fts5UnicodeIsalnum(iCode) & 0xFFFFFFFE)==0 );
- return sqlite3Fts5UnicodeIsalnum(iCode) ^ fts5UnicodeIsException(p, iCode);
+ return (
+ p->aCategory[tdsqlite3Fts5UnicodeCategory((u32)iCode)]
+ ^ fts5UnicodeIsException(p, iCode)
+ );
}
static int fts5UnicodeTokenize(
@@ -200438,7 +229046,7 @@ static int fts5UnicodeTokenize(
/* Each iteration of this loop gobbles up a contiguous run of separators,
** then the next token. */
while( rc==SQLITE_OK ){
- int iCode; /* non-ASCII codepoint read from input */
+ u32 iCode; /* non-ASCII codepoint read from input */
char *zOut = aFold;
int is;
int ie;
@@ -200470,14 +229078,14 @@ static int fts5UnicodeTokenize(
/* Grow the output buffer so that there is sufficient space to fit the
** largest possible utf-8 character. */
if( zOut>pEnd ){
- aFold = sqlite3_malloc(nFold*2);
+ aFold = tdsqlite3_malloc64((tdsqlite3_int64)nFold*2);
if( aFold==0 ){
rc = SQLITE_NOMEM;
goto tokenize_done;
}
zOut = &aFold[zOut - p->aFold];
memcpy(aFold, p->aFold, nFold);
- sqlite3_free(p->aFold);
+ tdsqlite3_free(p->aFold);
p->aFold = aFold;
p->nFold = nFold = nFold*2;
pEnd = &aFold[nFold-6];
@@ -200487,9 +229095,9 @@ static int fts5UnicodeTokenize(
/* An non-ascii-range character. Fold it into the output buffer if
** it is a token character, or break out of the loop if it is not. */
READ_UTF8(zCsr, zTerm, iCode);
- if( fts5UnicodeIsAlnum(p,iCode)||sqlite3Fts5UnicodeIsdiacritic(iCode) ){
+ if( fts5UnicodeIsAlnum(p,iCode)||tdsqlite3Fts5UnicodeIsdiacritic(iCode) ){
non_ascii_tokenchar:
- iCode = sqlite3Fts5UnicodeFold(iCode, p->bRemoveDiacritic);
+ iCode = tdsqlite3Fts5UnicodeFold(iCode, p->eRemoveDiacritic);
if( iCode ) WRITE_UTF8(zOut, iCode);
}else{
break;
@@ -200542,7 +229150,7 @@ static void fts5PorterDelete(Fts5Tokenizer *pTok){
if( p->pTokenizer ){
p->tokenizer.xDelete(p->pTokenizer);
}
- sqlite3_free(p);
+ tdsqlite3_free(p);
}
}
@@ -200564,7 +229172,7 @@ static int fts5PorterCreate(
zBase = azArg[0];
}
- pRet = (PorterTokenizer*)sqlite3_malloc(sizeof(PorterTokenizer));
+ pRet = (PorterTokenizer*)tdsqlite3_malloc(sizeof(PorterTokenizer));
if( pRet ){
memset(pRet, 0, sizeof(PorterTokenizer));
rc = pApi->xFindTokenizer(pApi, zBase, &pUserdata, &pRet->tokenizer);
@@ -201240,7 +229848,7 @@ static int fts5PorterTokenize(
/*
** Register all built-in tokenizers with FTS5.
*/
-static int sqlite3Fts5TokenizerInit(fts5_api *pApi){
+static int tdsqlite3Fts5TokenizerInit(fts5_api *pApi){
struct BuiltinTokenizer {
const char *zName;
fts5_tokenizer x;
@@ -201265,10 +229873,8 @@ static int sqlite3Fts5TokenizerInit(fts5_api *pApi){
return rc;
}
-
-
/*
-** 2012 May 25
+** 2012-05-25
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
@@ -201287,135 +229893,6 @@ static int sqlite3Fts5TokenizerInit(fts5_api *pApi){
/* #include <assert.h> */
-/*
-** Return true if the argument corresponds to a unicode codepoint
-** classified as either a letter or a number. Otherwise false.
-**
-** The results are undefined if the value passed to this function
-** is less than zero.
-*/
-static int sqlite3Fts5UnicodeIsalnum(int c){
- /* Each unsigned integer in the following array corresponds to a contiguous
- ** range of unicode codepoints that are not either letters or numbers (i.e.
- ** codepoints for which this function should return 0).
- **
- ** The most significant 22 bits in each 32-bit value contain the first
- ** codepoint in the range. The least significant 10 bits are used to store
- ** the size of the range (always at least 1). In other words, the value
- ** ((C<<22) + N) represents a range of N codepoints starting with codepoint
- ** C. It is not possible to represent a range larger than 1023 codepoints
- ** using this format.
- */
- static const unsigned int aEntry[] = {
- 0x00000030, 0x0000E807, 0x00016C06, 0x0001EC2F, 0x0002AC07,
- 0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01,
- 0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401,
- 0x000BBC81, 0x000DD401, 0x000DF801, 0x000E1002, 0x000E1C01,
- 0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163C01,
- 0x00164437, 0x0017CC02, 0x00180005, 0x00181816, 0x00187802,
- 0x00192C15, 0x0019A804, 0x0019C001, 0x001B5001, 0x001B580F,
- 0x001B9C07, 0x001BF402, 0x001C000E, 0x001C3C01, 0x001C4401,
- 0x001CC01B, 0x001E980B, 0x001FAC09, 0x001FD804, 0x00205804,
- 0x00206C09, 0x00209403, 0x0020A405, 0x0020C00F, 0x00216403,
- 0x00217801, 0x0023901B, 0x00240004, 0x0024E803, 0x0024F812,
- 0x00254407, 0x00258804, 0x0025C001, 0x00260403, 0x0026F001,
- 0x0026F807, 0x00271C02, 0x00272C03, 0x00275C01, 0x00278802,
- 0x0027C802, 0x0027E802, 0x00280403, 0x0028F001, 0x0028F805,
- 0x00291C02, 0x00292C03, 0x00294401, 0x0029C002, 0x0029D401,
- 0x002A0403, 0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03,
- 0x002B8802, 0x002BC002, 0x002C0403, 0x002CF001, 0x002CF807,
- 0x002D1C02, 0x002D2C03, 0x002D5802, 0x002D8802, 0x002DC001,
- 0x002E0801, 0x002EF805, 0x002F1803, 0x002F2804, 0x002F5C01,
- 0x002FCC08, 0x00300403, 0x0030F807, 0x00311803, 0x00312804,
- 0x00315402, 0x00318802, 0x0031FC01, 0x00320802, 0x0032F001,
- 0x0032F807, 0x00331803, 0x00332804, 0x00335402, 0x00338802,
- 0x00340802, 0x0034F807, 0x00351803, 0x00352804, 0x00355C01,
- 0x00358802, 0x0035E401, 0x00360802, 0x00372801, 0x00373C06,
- 0x00375801, 0x00376008, 0x0037C803, 0x0038C401, 0x0038D007,
- 0x0038FC01, 0x00391C09, 0x00396802, 0x003AC401, 0x003AD006,
- 0x003AEC02, 0x003B2006, 0x003C041F, 0x003CD00C, 0x003DC417,
- 0x003E340B, 0x003E6424, 0x003EF80F, 0x003F380D, 0x0040AC14,
- 0x00412806, 0x00415804, 0x00417803, 0x00418803, 0x00419C07,
- 0x0041C404, 0x0042080C, 0x00423C01, 0x00426806, 0x0043EC01,
- 0x004D740C, 0x004E400A, 0x00500001, 0x0059B402, 0x005A0001,
- 0x005A6C02, 0x005BAC03, 0x005C4803, 0x005CC805, 0x005D4802,
- 0x005DC802, 0x005ED023, 0x005F6004, 0x005F7401, 0x0060000F,
- 0x0062A401, 0x0064800C, 0x0064C00C, 0x00650001, 0x00651002,
- 0x0066C011, 0x00672002, 0x00677822, 0x00685C05, 0x00687802,
- 0x0069540A, 0x0069801D, 0x0069FC01, 0x006A8007, 0x006AA006,
- 0x006C0005, 0x006CD011, 0x006D6823, 0x006E0003, 0x006E840D,
- 0x006F980E, 0x006FF004, 0x00709014, 0x0070EC05, 0x0071F802,
- 0x00730008, 0x00734019, 0x0073B401, 0x0073C803, 0x00770027,
- 0x0077F004, 0x007EF401, 0x007EFC03, 0x007F3403, 0x007F7403,
- 0x007FB403, 0x007FF402, 0x00800065, 0x0081A806, 0x0081E805,
- 0x00822805, 0x0082801A, 0x00834021, 0x00840002, 0x00840C04,
- 0x00842002, 0x00845001, 0x00845803, 0x00847806, 0x00849401,
- 0x00849C01, 0x0084A401, 0x0084B801, 0x0084E802, 0x00850005,
- 0x00852804, 0x00853C01, 0x00864264, 0x00900027, 0x0091000B,
- 0x0092704E, 0x00940200, 0x009C0475, 0x009E53B9, 0x00AD400A,
- 0x00B39406, 0x00B3BC03, 0x00B3E404, 0x00B3F802, 0x00B5C001,
- 0x00B5FC01, 0x00B7804F, 0x00B8C00C, 0x00BA001A, 0x00BA6C59,
- 0x00BC00D6, 0x00BFC00C, 0x00C00005, 0x00C02019, 0x00C0A807,
- 0x00C0D802, 0x00C0F403, 0x00C26404, 0x00C28001, 0x00C3EC01,
- 0x00C64002, 0x00C6580A, 0x00C70024, 0x00C8001F, 0x00C8A81E,
- 0x00C94001, 0x00C98020, 0x00CA2827, 0x00CB003F, 0x00CC0100,
- 0x01370040, 0x02924037, 0x0293F802, 0x02983403, 0x0299BC10,
- 0x029A7C01, 0x029BC008, 0x029C0017, 0x029C8002, 0x029E2402,
- 0x02A00801, 0x02A01801, 0x02A02C01, 0x02A08C09, 0x02A0D804,
- 0x02A1D004, 0x02A20002, 0x02A2D011, 0x02A33802, 0x02A38012,
- 0x02A3E003, 0x02A4980A, 0x02A51C0D, 0x02A57C01, 0x02A60004,
- 0x02A6CC1B, 0x02A77802, 0x02A8A40E, 0x02A90C01, 0x02A93002,
- 0x02A97004, 0x02A9DC03, 0x02A9EC01, 0x02AAC001, 0x02AAC803,
- 0x02AADC02, 0x02AAF802, 0x02AB0401, 0x02AB7802, 0x02ABAC07,
- 0x02ABD402, 0x02AF8C0B, 0x03600001, 0x036DFC02, 0x036FFC02,
- 0x037FFC01, 0x03EC7801, 0x03ECA401, 0x03EEC810, 0x03F4F802,
- 0x03F7F002, 0x03F8001A, 0x03F88007, 0x03F8C023, 0x03F95013,
- 0x03F9A004, 0x03FBFC01, 0x03FC040F, 0x03FC6807, 0x03FCEC06,
- 0x03FD6C0B, 0x03FF8007, 0x03FFA007, 0x03FFE405, 0x04040003,
- 0x0404DC09, 0x0405E411, 0x0406400C, 0x0407402E, 0x040E7C01,
- 0x040F4001, 0x04215C01, 0x04247C01, 0x0424FC01, 0x04280403,
- 0x04281402, 0x04283004, 0x0428E003, 0x0428FC01, 0x04294009,
- 0x0429FC01, 0x042CE407, 0x04400003, 0x0440E016, 0x04420003,
- 0x0442C012, 0x04440003, 0x04449C0E, 0x04450004, 0x04460003,
- 0x0446CC0E, 0x04471404, 0x045AAC0D, 0x0491C004, 0x05BD442E,
- 0x05BE3C04, 0x074000F6, 0x07440027, 0x0744A4B5, 0x07480046,
- 0x074C0057, 0x075B0401, 0x075B6C01, 0x075BEC01, 0x075C5401,
- 0x075CD401, 0x075D3C01, 0x075DBC01, 0x075E2401, 0x075EA401,
- 0x075F0C01, 0x07BBC002, 0x07C0002C, 0x07C0C064, 0x07C2800F,
- 0x07C2C40E, 0x07C3040F, 0x07C3440F, 0x07C4401F, 0x07C4C03C,
- 0x07C5C02B, 0x07C7981D, 0x07C8402B, 0x07C90009, 0x07C94002,
- 0x07CC0021, 0x07CCC006, 0x07CCDC46, 0x07CE0014, 0x07CE8025,
- 0x07CF1805, 0x07CF8011, 0x07D0003F, 0x07D10001, 0x07D108B6,
- 0x07D3E404, 0x07D4003E, 0x07D50004, 0x07D54018, 0x07D7EC46,
- 0x07D9140B, 0x07DA0046, 0x07DC0074, 0x38000401, 0x38008060,
- 0x380400F0,
- };
- static const unsigned int aAscii[4] = {
- 0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001,
- };
-
- if( (unsigned int)c<128 ){
- return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 );
- }else if( (unsigned int)c<(1<<22) ){
- unsigned int key = (((unsigned int)c)<<10) | 0x000003FF;
- int iRes = 0;
- int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1;
- int iLo = 0;
- while( iHi>=iLo ){
- int iTest = (iHi + iLo) / 2;
- if( key >= aEntry[iTest] ){
- iRes = iTest;
- iLo = iTest+1;
- }else{
- iHi = iTest-1;
- }
- }
- assert( aEntry[0]<key );
- assert( key>=aEntry[iRes] );
- return (((unsigned int)c) >= ((aEntry[iRes]>>10) + (aEntry[iRes]&0x3FF)));
- }
- return 1;
-}
/*
@@ -201426,32 +229903,48 @@ static int sqlite3Fts5UnicodeIsalnum(int c){
** E"). The resuls of passing a codepoint that corresponds to an
** uppercase letter are undefined.
*/
-static int fts5_remove_diacritic(int c){
+static int fts5_remove_diacritic(int c, int bComplex){
unsigned short aDia[] = {
0, 1797, 1848, 1859, 1891, 1928, 1940, 1995,
2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286,
2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732,
2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336,
- 3456, 3696, 3712, 3728, 3744, 3896, 3912, 3928,
- 3968, 4008, 4040, 4106, 4138, 4170, 4202, 4234,
- 4266, 4296, 4312, 4344, 4408, 4424, 4472, 4504,
- 6148, 6198, 6264, 6280, 6360, 6429, 6505, 6529,
- 61448, 61468, 61534, 61592, 61642, 61688, 61704, 61726,
- 61784, 61800, 61836, 61880, 61914, 61948, 61998, 62122,
- 62154, 62200, 62218, 62302, 62364, 62442, 62478, 62536,
- 62554, 62584, 62604, 62640, 62648, 62656, 62664, 62730,
- 62924, 63050, 63082, 63274, 63390,
+ 3456, 3696, 3712, 3728, 3744, 3766, 3832, 3896,
+ 3912, 3928, 3944, 3968, 4008, 4040, 4056, 4106,
+ 4138, 4170, 4202, 4234, 4266, 4296, 4312, 4344,
+ 4408, 4424, 4442, 4472, 4488, 4504, 6148, 6198,
+ 6264, 6280, 6360, 6429, 6505, 6529, 61448, 61468,
+ 61512, 61534, 61592, 61610, 61642, 61672, 61688, 61704,
+ 61726, 61784, 61800, 61816, 61836, 61880, 61896, 61914,
+ 61948, 61998, 62062, 62122, 62154, 62184, 62200, 62218,
+ 62252, 62302, 62364, 62410, 62442, 62478, 62536, 62554,
+ 62584, 62604, 62640, 62648, 62656, 62664, 62730, 62766,
+ 62830, 62890, 62924, 62974, 63032, 63050, 63082, 63118,
+ 63182, 63242, 63274, 63310, 63368, 63390,
};
- char aChar[] = {
- '\0', 'a', 'c', 'e', 'i', 'n', 'o', 'u', 'y', 'y', 'a', 'c',
- 'd', 'e', 'e', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'o', 'r',
- 's', 't', 'u', 'u', 'w', 'y', 'z', 'o', 'u', 'a', 'i', 'o',
- 'u', 'g', 'k', 'o', 'j', 'g', 'n', 'a', 'e', 'i', 'o', 'r',
- 'u', 's', 't', 'h', 'a', 'e', 'o', 'y', '\0', '\0', '\0', '\0',
- '\0', '\0', '\0', '\0', 'a', 'b', 'd', 'd', 'e', 'f', 'g', 'h',
- 'h', 'i', 'k', 'l', 'l', 'm', 'n', 'p', 'r', 'r', 's', 't',
- 'u', 'v', 'w', 'w', 'x', 'y', 'z', 'h', 't', 'w', 'y', 'a',
- 'e', 'i', 'o', 'u', 'y',
+#define HIBIT ((unsigned char)0x80)
+ unsigned char aChar[] = {
+ '\0', 'a', 'c', 'e', 'i', 'n',
+ 'o', 'u', 'y', 'y', 'a', 'c',
+ 'd', 'e', 'e', 'g', 'h', 'i',
+ 'j', 'k', 'l', 'n', 'o', 'r',
+ 's', 't', 'u', 'u', 'w', 'y',
+ 'z', 'o', 'u', 'a', 'i', 'o',
+ 'u', 'u'|HIBIT, 'a'|HIBIT, 'g', 'k', 'o',
+ 'o'|HIBIT, 'j', 'g', 'n', 'a'|HIBIT, 'a',
+ 'e', 'i', 'o', 'r', 'u', 's',
+ 't', 'h', 'a', 'e', 'o'|HIBIT, 'o',
+ 'o'|HIBIT, 'y', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', 'a', 'b',
+ 'c'|HIBIT, 'd', 'd', 'e'|HIBIT, 'e', 'e'|HIBIT,
+ 'f', 'g', 'h', 'h', 'i', 'i'|HIBIT,
+ 'k', 'l', 'l'|HIBIT, 'l', 'm', 'n',
+ 'o'|HIBIT, 'p', 'r', 'r'|HIBIT, 'r', 's',
+ 's'|HIBIT, 't', 'u', 'u'|HIBIT, 'v', 'w',
+ 'w', 'x', 'y', 'z', 'h', 't',
+ 'w', 'y', 'a', 'a'|HIBIT, 'a'|HIBIT, 'a'|HIBIT,
+ 'e', 'e'|HIBIT, 'e'|HIBIT, 'i', 'o', 'o'|HIBIT,
+ 'o'|HIBIT, 'o'|HIBIT, 'u', 'u'|HIBIT, 'u'|HIBIT, 'y',
};
unsigned int key = (((unsigned int)c)<<3) | 0x00000007;
@@ -201468,7 +229961,8 @@ static int fts5_remove_diacritic(int c){
}
}
assert( key>=aDia[iRes] );
- return ((c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : (int)aChar[iRes]);
+ if( bComplex==0 && (aChar[iRes] & 0x80) ) return c;
+ return (c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : ((int)aChar[iRes] & 0x7F);
}
@@ -201476,13 +229970,13 @@ static int fts5_remove_diacritic(int c){
** Return true if the argument interpreted as a unicode codepoint
** is a diacritical modifier character.
*/
-static int sqlite3Fts5UnicodeIsdiacritic(int c){
+static int tdsqlite3Fts5UnicodeIsdiacritic(int c){
unsigned int mask0 = 0x08029FDF;
unsigned int mask1 = 0x000361F8;
if( c<768 || c>817 ) return 0;
return (c < 768+32) ?
- (mask0 & (1 << (c-768))) :
- (mask1 & (1 << (c-768-32)));
+ (mask0 & ((unsigned int)1 << (c-768))) :
+ (mask1 & ((unsigned int)1 << (c-768-32)));
}
@@ -201495,7 +229989,7 @@ static int sqlite3Fts5UnicodeIsdiacritic(int c){
** The results are undefined if the value passed to this function
** is less than zero.
*/
-static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic){
+static int tdsqlite3Fts5UnicodeFold(int c, int eRemoveDiacritic){
/* Each entry in the following array defines a rule for folding a range
** of codepoints to lower case. The rule applies to a range of nRange
** codepoints starting at codepoint iCode.
@@ -201618,7 +230112,9 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic){
assert( ret>0 );
}
- if( bRemoveDiacritic ) ret = fts5_remove_diacritic(ret);
+ if( eRemoveDiacritic ){
+ ret = fts5_remove_diacritic(ret, eRemoveDiacritic==2);
+ }
}
else if( c>=66560 && c<66600 ){
@@ -201628,6 +230124,532 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic){
return ret;
}
+
+static int tdsqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){
+ aArray[0] = 1;
+ switch( zCat[0] ){
+ case 'C':
+ switch( zCat[1] ){
+ case 'c': aArray[1] = 1; break;
+ case 'f': aArray[2] = 1; break;
+ case 'n': aArray[3] = 1; break;
+ case 's': aArray[4] = 1; break;
+ case 'o': aArray[31] = 1; break;
+ case '*':
+ aArray[1] = 1;
+ aArray[2] = 1;
+ aArray[3] = 1;
+ aArray[4] = 1;
+ aArray[31] = 1;
+ break;
+ default: return 1; }
+ break;
+
+ case 'L':
+ switch( zCat[1] ){
+ case 'l': aArray[5] = 1; break;
+ case 'm': aArray[6] = 1; break;
+ case 'o': aArray[7] = 1; break;
+ case 't': aArray[8] = 1; break;
+ case 'u': aArray[9] = 1; break;
+ case 'C': aArray[30] = 1; break;
+ case '*':
+ aArray[5] = 1;
+ aArray[6] = 1;
+ aArray[7] = 1;
+ aArray[8] = 1;
+ aArray[9] = 1;
+ aArray[30] = 1;
+ break;
+ default: return 1; }
+ break;
+
+ case 'M':
+ switch( zCat[1] ){
+ case 'c': aArray[10] = 1; break;
+ case 'e': aArray[11] = 1; break;
+ case 'n': aArray[12] = 1; break;
+ case '*':
+ aArray[10] = 1;
+ aArray[11] = 1;
+ aArray[12] = 1;
+ break;
+ default: return 1; }
+ break;
+
+ case 'N':
+ switch( zCat[1] ){
+ case 'd': aArray[13] = 1; break;
+ case 'l': aArray[14] = 1; break;
+ case 'o': aArray[15] = 1; break;
+ case '*':
+ aArray[13] = 1;
+ aArray[14] = 1;
+ aArray[15] = 1;
+ break;
+ default: return 1; }
+ break;
+
+ case 'P':
+ switch( zCat[1] ){
+ case 'c': aArray[16] = 1; break;
+ case 'd': aArray[17] = 1; break;
+ case 'e': aArray[18] = 1; break;
+ case 'f': aArray[19] = 1; break;
+ case 'i': aArray[20] = 1; break;
+ case 'o': aArray[21] = 1; break;
+ case 's': aArray[22] = 1; break;
+ case '*':
+ aArray[16] = 1;
+ aArray[17] = 1;
+ aArray[18] = 1;
+ aArray[19] = 1;
+ aArray[20] = 1;
+ aArray[21] = 1;
+ aArray[22] = 1;
+ break;
+ default: return 1; }
+ break;
+
+ case 'S':
+ switch( zCat[1] ){
+ case 'c': aArray[23] = 1; break;
+ case 'k': aArray[24] = 1; break;
+ case 'm': aArray[25] = 1; break;
+ case 'o': aArray[26] = 1; break;
+ case '*':
+ aArray[23] = 1;
+ aArray[24] = 1;
+ aArray[25] = 1;
+ aArray[26] = 1;
+ break;
+ default: return 1; }
+ break;
+
+ case 'Z':
+ switch( zCat[1] ){
+ case 'l': aArray[27] = 1; break;
+ case 'p': aArray[28] = 1; break;
+ case 's': aArray[29] = 1; break;
+ case '*':
+ aArray[27] = 1;
+ aArray[28] = 1;
+ aArray[29] = 1;
+ break;
+ default: return 1; }
+ break;
+
+ }
+ return 0;
+}
+
+static u16 aFts5UnicodeBlock[] = {
+ 0, 1471, 1753, 1760, 1760, 1760, 1760, 1760, 1760, 1760,
+ 1760, 1760, 1760, 1760, 1760, 1763, 1765,
+ };
+static u16 aFts5UnicodeMap[] = {
+ 0, 32, 33, 36, 37, 40, 41, 42, 43, 44,
+ 45, 46, 48, 58, 60, 63, 65, 91, 92, 93,
+ 94, 95, 96, 97, 123, 124, 125, 126, 127, 160,
+ 161, 162, 166, 167, 168, 169, 170, 171, 172, 173,
+ 174, 175, 176, 177, 178, 180, 181, 182, 184, 185,
+ 186, 187, 188, 191, 192, 215, 216, 223, 247, 248,
+ 256, 312, 313, 329, 330, 377, 383, 385, 387, 388,
+ 391, 394, 396, 398, 402, 403, 405, 406, 409, 412,
+ 414, 415, 417, 418, 423, 427, 428, 431, 434, 436,
+ 437, 440, 442, 443, 444, 446, 448, 452, 453, 454,
+ 455, 456, 457, 458, 459, 460, 461, 477, 478, 496,
+ 497, 498, 499, 500, 503, 505, 506, 564, 570, 572,
+ 573, 575, 577, 580, 583, 584, 592, 660, 661, 688,
+ 706, 710, 722, 736, 741, 748, 749, 750, 751, 768,
+ 880, 884, 885, 886, 890, 891, 894, 900, 902, 903,
+ 904, 908, 910, 912, 913, 931, 940, 975, 977, 978,
+ 981, 984, 1008, 1012, 1014, 1015, 1018, 1020, 1021, 1072,
+ 1120, 1154, 1155, 1160, 1162, 1217, 1231, 1232, 1329, 1369,
+ 1370, 1377, 1417, 1418, 1423, 1425, 1470, 1471, 1472, 1473,
+ 1475, 1476, 1478, 1479, 1488, 1520, 1523, 1536, 1542, 1545,
+ 1547, 1548, 1550, 1552, 1563, 1566, 1568, 1600, 1601, 1611,
+ 1632, 1642, 1646, 1648, 1649, 1748, 1749, 1750, 1757, 1758,
+ 1759, 1765, 1767, 1769, 1770, 1774, 1776, 1786, 1789, 1791,
+ 1792, 1807, 1808, 1809, 1810, 1840, 1869, 1958, 1969, 1984,
+ 1994, 2027, 2036, 2038, 2039, 2042, 2048, 2070, 2074, 2075,
+ 2084, 2085, 2088, 2089, 2096, 2112, 2137, 2142, 2208, 2210,
+ 2276, 2304, 2307, 2308, 2362, 2363, 2364, 2365, 2366, 2369,
+ 2377, 2381, 2382, 2384, 2385, 2392, 2402, 2404, 2406, 2416,
+ 2417, 2418, 2425, 2433, 2434, 2437, 2447, 2451, 2474, 2482,
+ 2486, 2492, 2493, 2494, 2497, 2503, 2507, 2509, 2510, 2519,
+ 2524, 2527, 2530, 2534, 2544, 2546, 2548, 2554, 2555, 2561,
+ 2563, 2565, 2575, 2579, 2602, 2610, 2613, 2616, 2620, 2622,
+ 2625, 2631, 2635, 2641, 2649, 2654, 2662, 2672, 2674, 2677,
+ 2689, 2691, 2693, 2703, 2707, 2730, 2738, 2741, 2748, 2749,
+ 2750, 2753, 2759, 2761, 2763, 2765, 2768, 2784, 2786, 2790,
+ 2800, 2801, 2817, 2818, 2821, 2831, 2835, 2858, 2866, 2869,
+ 2876, 2877, 2878, 2879, 2880, 2881, 2887, 2891, 2893, 2902,
+ 2903, 2908, 2911, 2914, 2918, 2928, 2929, 2930, 2946, 2947,
+ 2949, 2958, 2962, 2969, 2972, 2974, 2979, 2984, 2990, 3006,
+ 3008, 3009, 3014, 3018, 3021, 3024, 3031, 3046, 3056, 3059,
+ 3065, 3066, 3073, 3077, 3086, 3090, 3114, 3125, 3133, 3134,
+ 3137, 3142, 3146, 3157, 3160, 3168, 3170, 3174, 3192, 3199,
+ 3202, 3205, 3214, 3218, 3242, 3253, 3260, 3261, 3262, 3263,
+ 3264, 3270, 3271, 3274, 3276, 3285, 3294, 3296, 3298, 3302,
+ 3313, 3330, 3333, 3342, 3346, 3389, 3390, 3393, 3398, 3402,
+ 3405, 3406, 3415, 3424, 3426, 3430, 3440, 3449, 3450, 3458,
+ 3461, 3482, 3507, 3517, 3520, 3530, 3535, 3538, 3542, 3544,
+ 3570, 3572, 3585, 3633, 3634, 3636, 3647, 3648, 3654, 3655,
+ 3663, 3664, 3674, 3713, 3716, 3719, 3722, 3725, 3732, 3737,
+ 3745, 3749, 3751, 3754, 3757, 3761, 3762, 3764, 3771, 3773,
+ 3776, 3782, 3784, 3792, 3804, 3840, 3841, 3844, 3859, 3860,
+ 3861, 3864, 3866, 3872, 3882, 3892, 3893, 3894, 3895, 3896,
+ 3897, 3898, 3899, 3900, 3901, 3902, 3904, 3913, 3953, 3967,
+ 3968, 3973, 3974, 3976, 3981, 3993, 4030, 4038, 4039, 4046,
+ 4048, 4053, 4057, 4096, 4139, 4141, 4145, 4146, 4152, 4153,
+ 4155, 4157, 4159, 4160, 4170, 4176, 4182, 4184, 4186, 4190,
+ 4193, 4194, 4197, 4199, 4206, 4209, 4213, 4226, 4227, 4229,
+ 4231, 4237, 4238, 4239, 4240, 4250, 4253, 4254, 4256, 4295,
+ 4301, 4304, 4347, 4348, 4349, 4682, 4688, 4696, 4698, 4704,
+ 4746, 4752, 4786, 4792, 4800, 4802, 4808, 4824, 4882, 4888,
+ 4957, 4960, 4969, 4992, 5008, 5024, 5120, 5121, 5741, 5743,
+ 5760, 5761, 5787, 5788, 5792, 5867, 5870, 5888, 5902, 5906,
+ 5920, 5938, 5941, 5952, 5970, 5984, 5998, 6002, 6016, 6068,
+ 6070, 6071, 6078, 6086, 6087, 6089, 6100, 6103, 6104, 6107,
+ 6108, 6109, 6112, 6128, 6144, 6150, 6151, 6155, 6158, 6160,
+ 6176, 6211, 6212, 6272, 6313, 6314, 6320, 6400, 6432, 6435,
+ 6439, 6441, 6448, 6450, 6451, 6457, 6464, 6468, 6470, 6480,
+ 6512, 6528, 6576, 6593, 6600, 6608, 6618, 6622, 6656, 6679,
+ 6681, 6686, 6688, 6741, 6742, 6743, 6744, 6752, 6753, 6754,
+ 6755, 6757, 6765, 6771, 6783, 6784, 6800, 6816, 6823, 6824,
+ 6912, 6916, 6917, 6964, 6965, 6966, 6971, 6972, 6973, 6978,
+ 6979, 6981, 6992, 7002, 7009, 7019, 7028, 7040, 7042, 7043,
+ 7073, 7074, 7078, 7080, 7082, 7083, 7084, 7086, 7088, 7098,
+ 7142, 7143, 7144, 7146, 7149, 7150, 7151, 7154, 7164, 7168,
+ 7204, 7212, 7220, 7222, 7227, 7232, 7245, 7248, 7258, 7288,
+ 7294, 7360, 7376, 7379, 7380, 7393, 7394, 7401, 7405, 7406,
+ 7410, 7412, 7413, 7424, 7468, 7531, 7544, 7545, 7579, 7616,
+ 7676, 7680, 7830, 7838, 7936, 7944, 7952, 7960, 7968, 7976,
+ 7984, 7992, 8000, 8008, 8016, 8025, 8027, 8029, 8031, 8033,
+ 8040, 8048, 8064, 8072, 8080, 8088, 8096, 8104, 8112, 8118,
+ 8120, 8124, 8125, 8126, 8127, 8130, 8134, 8136, 8140, 8141,
+ 8144, 8150, 8152, 8157, 8160, 8168, 8173, 8178, 8182, 8184,
+ 8188, 8189, 8192, 8203, 8208, 8214, 8216, 8217, 8218, 8219,
+ 8221, 8222, 8223, 8224, 8232, 8233, 8234, 8239, 8240, 8249,
+ 8250, 8251, 8255, 8257, 8260, 8261, 8262, 8263, 8274, 8275,
+ 8276, 8277, 8287, 8288, 8298, 8304, 8305, 8308, 8314, 8317,
+ 8318, 8319, 8320, 8330, 8333, 8334, 8336, 8352, 8400, 8413,
+ 8417, 8418, 8421, 8448, 8450, 8451, 8455, 8456, 8458, 8459,
+ 8462, 8464, 8467, 8468, 8469, 8470, 8472, 8473, 8478, 8484,
+ 8485, 8486, 8487, 8488, 8489, 8490, 8494, 8495, 8496, 8500,
+ 8501, 8505, 8506, 8508, 8510, 8512, 8517, 8519, 8522, 8523,
+ 8524, 8526, 8527, 8528, 8544, 8579, 8581, 8585, 8592, 8597,
+ 8602, 8604, 8608, 8609, 8611, 8612, 8614, 8615, 8622, 8623,
+ 8654, 8656, 8658, 8659, 8660, 8661, 8692, 8960, 8968, 8972,
+ 8992, 8994, 9001, 9002, 9003, 9084, 9085, 9115, 9140, 9180,
+ 9186, 9216, 9280, 9312, 9372, 9450, 9472, 9655, 9656, 9665,
+ 9666, 9720, 9728, 9839, 9840, 9985, 10088, 10089, 10090, 10091,
+ 10092, 10093, 10094, 10095, 10096, 10097, 10098, 10099, 10100, 10101,
+ 10102, 10132, 10176, 10181, 10182, 10183, 10214, 10215, 10216, 10217,
+ 10218, 10219, 10220, 10221, 10222, 10223, 10224, 10240, 10496, 10627,
+ 10628, 10629, 10630, 10631, 10632, 10633, 10634, 10635, 10636, 10637,
+ 10638, 10639, 10640, 10641, 10642, 10643, 10644, 10645, 10646, 10647,
+ 10648, 10649, 10712, 10713, 10714, 10715, 10716, 10748, 10749, 10750,
+ 11008, 11056, 11077, 11079, 11088, 11264, 11312, 11360, 11363, 11365,
+ 11367, 11374, 11377, 11378, 11380, 11381, 11383, 11388, 11390, 11393,
+ 11394, 11492, 11493, 11499, 11503, 11506, 11513, 11517, 11518, 11520,
+ 11559, 11565, 11568, 11631, 11632, 11647, 11648, 11680, 11688, 11696,
+ 11704, 11712, 11720, 11728, 11736, 11744, 11776, 11778, 11779, 11780,
+ 11781, 11782, 11785, 11786, 11787, 11788, 11789, 11790, 11799, 11800,
+ 11802, 11803, 11804, 11805, 11806, 11808, 11809, 11810, 11811, 11812,
+ 11813, 11814, 11815, 11816, 11817, 11818, 11823, 11824, 11834, 11904,
+ 11931, 12032, 12272, 12288, 12289, 12292, 12293, 12294, 12295, 12296,
+ 12297, 12298, 12299, 12300, 12301, 12302, 12303, 12304, 12305, 12306,
+ 12308, 12309, 12310, 12311, 12312, 12313, 12314, 12315, 12316, 12317,
+ 12318, 12320, 12321, 12330, 12334, 12336, 12337, 12342, 12344, 12347,
+ 12348, 12349, 12350, 12353, 12441, 12443, 12445, 12447, 12448, 12449,
+ 12539, 12540, 12543, 12549, 12593, 12688, 12690, 12694, 12704, 12736,
+ 12784, 12800, 12832, 12842, 12872, 12880, 12881, 12896, 12928, 12938,
+ 12977, 12992, 13056, 13312, 19893, 19904, 19968, 40908, 40960, 40981,
+ 40982, 42128, 42192, 42232, 42238, 42240, 42508, 42509, 42512, 42528,
+ 42538, 42560, 42606, 42607, 42608, 42611, 42612, 42622, 42623, 42624,
+ 42655, 42656, 42726, 42736, 42738, 42752, 42775, 42784, 42786, 42800,
+ 42802, 42864, 42865, 42873, 42878, 42888, 42889, 42891, 42896, 42912,
+ 43000, 43002, 43003, 43010, 43011, 43014, 43015, 43019, 43020, 43043,
+ 43045, 43047, 43048, 43056, 43062, 43064, 43065, 43072, 43124, 43136,
+ 43138, 43188, 43204, 43214, 43216, 43232, 43250, 43256, 43259, 43264,
+ 43274, 43302, 43310, 43312, 43335, 43346, 43359, 43360, 43392, 43395,
+ 43396, 43443, 43444, 43446, 43450, 43452, 43453, 43457, 43471, 43472,
+ 43486, 43520, 43561, 43567, 43569, 43571, 43573, 43584, 43587, 43588,
+ 43596, 43597, 43600, 43612, 43616, 43632, 43633, 43639, 43642, 43643,
+ 43648, 43696, 43697, 43698, 43701, 43703, 43705, 43710, 43712, 43713,
+ 43714, 43739, 43741, 43742, 43744, 43755, 43756, 43758, 43760, 43762,
+ 43763, 43765, 43766, 43777, 43785, 43793, 43808, 43816, 43968, 44003,
+ 44005, 44006, 44008, 44009, 44011, 44012, 44013, 44016, 44032, 55203,
+ 55216, 55243, 55296, 56191, 56319, 57343, 57344, 63743, 63744, 64112,
+ 64256, 64275, 64285, 64286, 64287, 64297, 64298, 64312, 64318, 64320,
+ 64323, 64326, 64434, 64467, 64830, 64831, 64848, 64914, 65008, 65020,
+ 65021, 65024, 65040, 65047, 65048, 65049, 65056, 65072, 65073, 65075,
+ 65077, 65078, 65079, 65080, 65081, 65082, 65083, 65084, 65085, 65086,
+ 65087, 65088, 65089, 65090, 65091, 65092, 65093, 65095, 65096, 65097,
+ 65101, 65104, 65108, 65112, 65113, 65114, 65115, 65116, 65117, 65118,
+ 65119, 65122, 65123, 65124, 65128, 65129, 65130, 65136, 65142, 65279,
+ 65281, 65284, 65285, 65288, 65289, 65290, 65291, 65292, 65293, 65294,
+ 65296, 65306, 65308, 65311, 65313, 65339, 65340, 65341, 65342, 65343,
+ 65344, 65345, 65371, 65372, 65373, 65374, 65375, 65376, 65377, 65378,
+ 65379, 65380, 65382, 65392, 65393, 65438, 65440, 65474, 65482, 65490,
+ 65498, 65504, 65506, 65507, 65508, 65509, 65512, 65513, 65517, 65529,
+ 65532, 0, 13, 40, 60, 63, 80, 128, 256, 263,
+ 311, 320, 373, 377, 394, 400, 464, 509, 640, 672,
+ 768, 800, 816, 833, 834, 842, 896, 927, 928, 968,
+ 976, 977, 1024, 1064, 1104, 1184, 2048, 2056, 2058, 2103,
+ 2108, 2111, 2135, 2136, 2304, 2326, 2335, 2336, 2367, 2432,
+ 2494, 2560, 2561, 2565, 2572, 2576, 2581, 2585, 2616, 2623,
+ 2624, 2640, 2656, 2685, 2687, 2816, 2873, 2880, 2904, 2912,
+ 2936, 3072, 3680, 4096, 4097, 4098, 4099, 4152, 4167, 4178,
+ 4198, 4224, 4226, 4227, 4272, 4275, 4279, 4281, 4283, 4285,
+ 4286, 4304, 4336, 4352, 4355, 4391, 4396, 4397, 4406, 4416,
+ 4480, 4482, 4483, 4531, 4534, 4543, 4545, 4549, 4560, 5760,
+ 5803, 5804, 5805, 5806, 5808, 5814, 5815, 5824, 8192, 9216,
+ 9328, 12288, 26624, 28416, 28496, 28497, 28559, 28563, 45056, 53248,
+ 53504, 53545, 53605, 53607, 53610, 53613, 53619, 53627, 53635, 53637,
+ 53644, 53674, 53678, 53760, 53826, 53829, 54016, 54112, 54272, 54298,
+ 54324, 54350, 54358, 54376, 54402, 54428, 54430, 54434, 54437, 54441,
+ 54446, 54454, 54459, 54461, 54469, 54480, 54506, 54532, 54535, 54541,
+ 54550, 54558, 54584, 54587, 54592, 54598, 54602, 54610, 54636, 54662,
+ 54688, 54714, 54740, 54766, 54792, 54818, 54844, 54870, 54896, 54922,
+ 54952, 54977, 54978, 55003, 55004, 55010, 55035, 55036, 55061, 55062,
+ 55068, 55093, 55094, 55119, 55120, 55126, 55151, 55152, 55177, 55178,
+ 55184, 55209, 55210, 55235, 55236, 55242, 55246, 60928, 60933, 60961,
+ 60964, 60967, 60969, 60980, 60985, 60987, 60994, 60999, 61001, 61003,
+ 61005, 61009, 61012, 61015, 61017, 61019, 61021, 61023, 61025, 61028,
+ 61031, 61036, 61044, 61049, 61054, 61056, 61067, 61089, 61093, 61099,
+ 61168, 61440, 61488, 61600, 61617, 61633, 61649, 61696, 61712, 61744,
+ 61808, 61926, 61968, 62016, 62032, 62208, 62256, 62263, 62336, 62368,
+ 62406, 62432, 62464, 62528, 62530, 62713, 62720, 62784, 62800, 62971,
+ 63045, 63104, 63232, 0, 42710, 42752, 46900, 46912, 47133, 63488,
+ 1, 32, 256, 0, 65533,
+ };
+static u16 aFts5UnicodeData[] = {
+ 1025, 61, 117, 55, 117, 54, 50, 53, 57, 53,
+ 49, 85, 333, 85, 121, 85, 841, 54, 53, 50,
+ 56, 48, 56, 837, 54, 57, 50, 57, 1057, 61,
+ 53, 151, 58, 53, 56, 58, 39, 52, 57, 34,
+ 58, 56, 58, 57, 79, 56, 37, 85, 56, 47,
+ 39, 51, 111, 53, 745, 57, 233, 773, 57, 261,
+ 1822, 37, 542, 37, 1534, 222, 69, 73, 37, 126,
+ 126, 73, 69, 137, 37, 73, 37, 105, 101, 73,
+ 37, 73, 37, 190, 158, 37, 126, 126, 73, 37,
+ 126, 94, 37, 39, 94, 69, 135, 41, 40, 37,
+ 41, 40, 37, 41, 40, 37, 542, 37, 606, 37,
+ 41, 40, 37, 126, 73, 37, 1886, 197, 73, 37,
+ 73, 69, 126, 105, 37, 286, 2181, 39, 869, 582,
+ 152, 390, 472, 166, 248, 38, 56, 38, 568, 3596,
+ 158, 38, 56, 94, 38, 101, 53, 88, 41, 53,
+ 105, 41, 73, 37, 553, 297, 1125, 94, 37, 105,
+ 101, 798, 133, 94, 57, 126, 94, 37, 1641, 1541,
+ 1118, 58, 172, 75, 1790, 478, 37, 2846, 1225, 38,
+ 213, 1253, 53, 49, 55, 1452, 49, 44, 53, 76,
+ 53, 76, 53, 44, 871, 103, 85, 162, 121, 85,
+ 55, 85, 90, 364, 53, 85, 1031, 38, 327, 684,
+ 333, 149, 71, 44, 3175, 53, 39, 236, 34, 58,
+ 204, 70, 76, 58, 140, 71, 333, 103, 90, 39,
+ 469, 34, 39, 44, 967, 876, 2855, 364, 39, 333,
+ 1063, 300, 70, 58, 117, 38, 711, 140, 38, 300,
+ 38, 108, 38, 172, 501, 807, 108, 53, 39, 359,
+ 876, 108, 42, 1735, 44, 42, 44, 39, 106, 268,
+ 138, 44, 74, 39, 236, 327, 76, 85, 333, 53,
+ 38, 199, 231, 44, 74, 263, 71, 711, 231, 39,
+ 135, 44, 39, 106, 140, 74, 74, 44, 39, 42,
+ 71, 103, 76, 333, 71, 87, 207, 58, 55, 76,
+ 42, 199, 71, 711, 231, 71, 71, 71, 44, 106,
+ 76, 76, 108, 44, 135, 39, 333, 76, 103, 44,
+ 76, 42, 295, 103, 711, 231, 71, 167, 44, 39,
+ 106, 172, 76, 42, 74, 44, 39, 71, 76, 333,
+ 53, 55, 44, 74, 263, 71, 711, 231, 71, 167,
+ 44, 39, 42, 44, 42, 140, 74, 74, 44, 44,
+ 42, 71, 103, 76, 333, 58, 39, 207, 44, 39,
+ 199, 103, 135, 71, 39, 71, 71, 103, 391, 74,
+ 44, 74, 106, 106, 44, 39, 42, 333, 111, 218,
+ 55, 58, 106, 263, 103, 743, 327, 167, 39, 108,
+ 138, 108, 140, 76, 71, 71, 76, 333, 239, 58,
+ 74, 263, 103, 743, 327, 167, 44, 39, 42, 44,
+ 170, 44, 74, 74, 76, 74, 39, 71, 76, 333,
+ 71, 74, 263, 103, 1319, 39, 106, 140, 106, 106,
+ 44, 39, 42, 71, 76, 333, 207, 58, 199, 74,
+ 583, 775, 295, 39, 231, 44, 106, 108, 44, 266,
+ 74, 53, 1543, 44, 71, 236, 55, 199, 38, 268,
+ 53, 333, 85, 71, 39, 71, 39, 39, 135, 231,
+ 103, 39, 39, 71, 135, 44, 71, 204, 76, 39,
+ 167, 38, 204, 333, 135, 39, 122, 501, 58, 53,
+ 122, 76, 218, 333, 335, 58, 44, 58, 44, 58,
+ 44, 54, 50, 54, 50, 74, 263, 1159, 460, 42,
+ 172, 53, 76, 167, 364, 1164, 282, 44, 218, 90,
+ 181, 154, 85, 1383, 74, 140, 42, 204, 42, 76,
+ 74, 76, 39, 333, 213, 199, 74, 76, 135, 108,
+ 39, 106, 71, 234, 103, 140, 423, 44, 74, 76,
+ 202, 44, 39, 42, 333, 106, 44, 90, 1225, 41,
+ 41, 1383, 53, 38, 10631, 135, 231, 39, 135, 1319,
+ 135, 1063, 135, 231, 39, 135, 487, 1831, 135, 2151,
+ 108, 309, 655, 519, 346, 2727, 49, 19847, 85, 551,
+ 61, 839, 54, 50, 2407, 117, 110, 423, 135, 108,
+ 583, 108, 85, 583, 76, 423, 103, 76, 1671, 76,
+ 42, 236, 266, 44, 74, 364, 117, 38, 117, 55,
+ 39, 44, 333, 335, 213, 49, 149, 108, 61, 333,
+ 1127, 38, 1671, 1319, 44, 39, 2247, 935, 108, 138,
+ 76, 106, 74, 44, 202, 108, 58, 85, 333, 967,
+ 167, 1415, 554, 231, 74, 333, 47, 1114, 743, 76,
+ 106, 85, 1703, 42, 44, 42, 236, 44, 42, 44,
+ 74, 268, 202, 332, 44, 333, 333, 245, 38, 213,
+ 140, 42, 1511, 44, 42, 172, 42, 44, 170, 44,
+ 74, 231, 333, 245, 346, 300, 314, 76, 42, 967,
+ 42, 140, 74, 76, 42, 44, 74, 71, 333, 1415,
+ 44, 42, 76, 106, 44, 42, 108, 74, 149, 1159,
+ 266, 268, 74, 76, 181, 333, 103, 333, 967, 198,
+ 85, 277, 108, 53, 428, 42, 236, 135, 44, 135,
+ 74, 44, 71, 1413, 2022, 421, 38, 1093, 1190, 1260,
+ 140, 4830, 261, 3166, 261, 265, 197, 201, 261, 265,
+ 261, 265, 197, 201, 261, 41, 41, 41, 94, 229,
+ 265, 453, 261, 264, 261, 264, 261, 264, 165, 69,
+ 137, 40, 56, 37, 120, 101, 69, 137, 40, 120,
+ 133, 69, 137, 120, 261, 169, 120, 101, 69, 137,
+ 40, 88, 381, 162, 209, 85, 52, 51, 54, 84,
+ 51, 54, 52, 277, 59, 60, 162, 61, 309, 52,
+ 51, 149, 80, 117, 57, 54, 50, 373, 57, 53,
+ 48, 341, 61, 162, 194, 47, 38, 207, 121, 54,
+ 50, 38, 335, 121, 54, 50, 422, 855, 428, 139,
+ 44, 107, 396, 90, 41, 154, 41, 90, 37, 105,
+ 69, 105, 37, 58, 41, 90, 57, 169, 218, 41,
+ 58, 41, 58, 41, 58, 137, 58, 37, 137, 37,
+ 135, 37, 90, 69, 73, 185, 94, 101, 58, 57,
+ 90, 37, 58, 527, 1134, 94, 142, 47, 185, 186,
+ 89, 154, 57, 90, 57, 90, 57, 250, 57, 1018,
+ 89, 90, 57, 58, 57, 1018, 8601, 282, 153, 666,
+ 89, 250, 54, 50, 2618, 57, 986, 825, 1306, 217,
+ 602, 1274, 378, 1935, 2522, 719, 5882, 57, 314, 57,
+ 1754, 281, 3578, 57, 4634, 3322, 54, 50, 54, 50,
+ 54, 50, 54, 50, 54, 50, 54, 50, 54, 50,
+ 975, 1434, 185, 54, 50, 1017, 54, 50, 54, 50,
+ 54, 50, 54, 50, 54, 50, 537, 8218, 4217, 54,
+ 50, 54, 50, 54, 50, 54, 50, 54, 50, 54,
+ 50, 54, 50, 54, 50, 54, 50, 54, 50, 54,
+ 50, 2041, 54, 50, 54, 50, 1049, 54, 50, 8281,
+ 1562, 697, 90, 217, 346, 1513, 1509, 126, 73, 69,
+ 254, 105, 37, 94, 37, 94, 165, 70, 105, 37,
+ 3166, 37, 218, 158, 108, 94, 149, 47, 85, 1221,
+ 37, 37, 1799, 38, 53, 44, 743, 231, 231, 231,
+ 231, 231, 231, 231, 231, 1036, 85, 52, 51, 52,
+ 51, 117, 52, 51, 53, 52, 51, 309, 49, 85,
+ 49, 53, 52, 51, 85, 52, 51, 54, 50, 54,
+ 50, 54, 50, 54, 50, 181, 38, 341, 81, 858,
+ 2874, 6874, 410, 61, 117, 58, 38, 39, 46, 54,
+ 50, 54, 50, 54, 50, 54, 50, 54, 50, 90,
+ 54, 50, 54, 50, 54, 50, 54, 50, 49, 54,
+ 82, 58, 302, 140, 74, 49, 166, 90, 110, 38,
+ 39, 53, 90, 2759, 76, 88, 70, 39, 49, 2887,
+ 53, 102, 39, 1319, 3015, 90, 143, 346, 871, 1178,
+ 519, 1018, 335, 986, 271, 58, 495, 1050, 335, 1274,
+ 495, 2042, 8218, 39, 39, 2074, 39, 39, 679, 38,
+ 36583, 1786, 1287, 198, 85, 8583, 38, 117, 519, 333,
+ 71, 1502, 39, 44, 107, 53, 332, 53, 38, 798,
+ 44, 2247, 334, 76, 213, 760, 294, 88, 478, 69,
+ 2014, 38, 261, 190, 350, 38, 88, 158, 158, 382,
+ 70, 37, 231, 44, 103, 44, 135, 44, 743, 74,
+ 76, 42, 154, 207, 90, 55, 58, 1671, 149, 74,
+ 1607, 522, 44, 85, 333, 588, 199, 117, 39, 333,
+ 903, 268, 85, 743, 364, 74, 53, 935, 108, 42,
+ 1511, 44, 74, 140, 74, 44, 138, 437, 38, 333,
+ 85, 1319, 204, 74, 76, 74, 76, 103, 44, 263,
+ 44, 42, 333, 149, 519, 38, 199, 122, 39, 42,
+ 1543, 44, 39, 108, 71, 76, 167, 76, 39, 44,
+ 39, 71, 38, 85, 359, 42, 76, 74, 85, 39,
+ 70, 42, 44, 199, 199, 199, 231, 231, 1127, 74,
+ 44, 74, 44, 74, 53, 42, 44, 333, 39, 39,
+ 743, 1575, 36, 68, 68, 36, 63, 63, 11719, 3399,
+ 229, 165, 39, 44, 327, 57, 423, 167, 39, 71,
+ 71, 3463, 536, 11623, 54, 50, 2055, 1735, 391, 55,
+ 58, 524, 245, 54, 50, 53, 236, 53, 81, 80,
+ 54, 50, 54, 50, 54, 50, 54, 50, 54, 50,
+ 54, 50, 54, 50, 54, 50, 85, 54, 50, 149,
+ 112, 117, 149, 49, 54, 50, 54, 50, 54, 50,
+ 117, 57, 49, 121, 53, 55, 85, 167, 4327, 34,
+ 117, 55, 117, 54, 50, 53, 57, 53, 49, 85,
+ 333, 85, 121, 85, 841, 54, 53, 50, 56, 48,
+ 56, 837, 54, 57, 50, 57, 54, 50, 53, 54,
+ 50, 85, 327, 38, 1447, 70, 999, 199, 199, 199,
+ 103, 87, 57, 56, 58, 87, 58, 153, 90, 98,
+ 90, 391, 839, 615, 71, 487, 455, 3943, 117, 1455,
+ 314, 1710, 143, 570, 47, 410, 1466, 44, 935, 1575,
+ 999, 143, 551, 46, 263, 46, 967, 53, 1159, 263,
+ 53, 174, 1289, 1285, 2503, 333, 199, 39, 1415, 71,
+ 39, 743, 53, 271, 711, 207, 53, 839, 53, 1799,
+ 71, 39, 108, 76, 140, 135, 103, 871, 108, 44,
+ 271, 309, 935, 79, 53, 1735, 245, 711, 271, 615,
+ 271, 2343, 1007, 42, 44, 42, 1703, 492, 245, 655,
+ 333, 76, 42, 1447, 106, 140, 74, 76, 85, 34,
+ 149, 807, 333, 108, 1159, 172, 42, 268, 333, 149,
+ 76, 42, 1543, 106, 300, 74, 135, 149, 333, 1383,
+ 44, 42, 44, 74, 204, 42, 44, 333, 28135, 3182,
+ 149, 34279, 18215, 2215, 39, 1482, 140, 422, 71, 7898,
+ 1274, 1946, 74, 108, 122, 202, 258, 268, 90, 236,
+ 986, 140, 1562, 2138, 108, 58, 2810, 591, 841, 837,
+ 841, 229, 581, 841, 837, 41, 73, 41, 73, 137,
+ 265, 133, 37, 229, 357, 841, 837, 73, 137, 265,
+ 233, 837, 73, 137, 169, 41, 233, 837, 841, 837,
+ 841, 837, 841, 837, 841, 837, 841, 837, 841, 901,
+ 809, 57, 805, 57, 197, 809, 57, 805, 57, 197,
+ 809, 57, 805, 57, 197, 809, 57, 805, 57, 197,
+ 809, 57, 805, 57, 197, 94, 1613, 135, 871, 71,
+ 39, 39, 327, 135, 39, 39, 39, 39, 39, 39,
+ 103, 71, 39, 39, 39, 39, 39, 39, 71, 39,
+ 135, 231, 135, 135, 39, 327, 551, 103, 167, 551,
+ 89, 1434, 3226, 506, 474, 506, 506, 367, 1018, 1946,
+ 1402, 954, 1402, 314, 90, 1082, 218, 2266, 666, 1210,
+ 186, 570, 2042, 58, 5850, 154, 2010, 154, 794, 2266,
+ 378, 2266, 3738, 39, 39, 39, 39, 39, 39, 17351,
+ 34, 3074, 7692, 63, 63,
+ };
+
+static int tdsqlite3Fts5UnicodeCategory(u32 iCode) {
+ int iRes = -1;
+ int iHi;
+ int iLo;
+ int ret;
+ u16 iKey;
+
+ if( iCode>=(1<<20) ){
+ return 0;
+ }
+ iLo = aFts5UnicodeBlock[(iCode>>16)];
+ iHi = aFts5UnicodeBlock[1+(iCode>>16)];
+ iKey = (iCode & 0xFFFF);
+ while( iHi>iLo ){
+ int iTest = (iHi + iLo) / 2;
+ assert( iTest>=iLo && iTest<iHi );
+ if( iKey>=aFts5UnicodeMap[iTest] ){
+ iRes = iTest;
+ iLo = iTest+1;
+ }else{
+ iHi = iTest;
+ }
+ }
+
+ if( iRes<0 ) return 0;
+ if( iKey>=(aFts5UnicodeMap[iRes]+(aFts5UnicodeData[iRes]>>5)) ) return 0;
+ ret = aFts5UnicodeData[iRes] & 0x1F;
+ if( ret!=30 ) return ret;
+ return ((iKey - aFts5UnicodeMap[iRes]) & 0x01) ? 5 : 9;
+}
+
+static void tdsqlite3Fts5UnicodeAscii(u8 *aArray, u8 *aAscii){
+ int i = 0;
+ int iTbl = 0;
+ while( i<128 ){
+ int bToken = aArray[ aFts5UnicodeData[iTbl] & 0x1F ];
+ int n = (aFts5UnicodeData[iTbl] >> 5) + i;
+ for(; i<128 && i<n; i++){
+ aAscii[i] = (u8)bToken;
+ }
+ iTbl++;
+ }
+}
+
/*
** 2015 May 30
**
@@ -201647,11 +230669,11 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic){
/* #include "fts5Int.h" */
/*
-** This is a copy of the sqlite3GetVarint32() routine from the SQLite core.
+** This is a copy of the tdsqlite3GetVarint32() routine from the SQLite core.
** Except, this version does handle the single byte case that the core
** version depends on being handled before its function is called.
*/
-static int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v){
+static int tdsqlite3Fts5GetVarint32(const unsigned char *p, u32 *v){
u32 a,b;
/* The 1-byte case. Overwhelmingly the most common. */
@@ -201705,8 +230727,8 @@ static int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v){
u64 v64;
u8 n;
p -= 2;
- n = sqlite3Fts5GetVarint(p, &v64);
- *v = (u32)v64;
+ n = tdsqlite3Fts5GetVarint(p, &v64);
+ *v = ((u32)v64) & 0x7FFFFFFF;
assert( n>3 && n<=9 );
return n;
}
@@ -201714,7 +230736,7 @@ static int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v){
/*
-** Bitmasks used by sqlite3GetVarint(). These precomputed constants
+** Bitmasks used by tdsqlite3GetVarint(). These precomputed constants
** are defined here rather than simply putting the constant expressions
** inline in order to work around bugs in the RVT compiler.
**
@@ -201729,7 +230751,7 @@ static int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v){
** Read a 64-bit variable-length integer from memory starting at p[0].
** Return the number of bytes read. The value is stored in *v.
*/
-static u8 sqlite3Fts5GetVarint(const unsigned char *p, u64 *v){
+static u8 tdsqlite3Fts5GetVarint(const unsigned char *p, u64 *v){
u32 a,b,s;
a = *p;
@@ -201948,7 +230970,7 @@ static int FTS5_NOINLINE fts5PutVarint64(unsigned char *p, u64 v){
return n;
}
-static int sqlite3Fts5PutVarint(unsigned char *p, u64 v){
+static int tdsqlite3Fts5PutVarint(unsigned char *p, u64 v){
if( v<=0x7f ){
p[0] = v&0x7f;
return 1;
@@ -201962,7 +230984,7 @@ static int sqlite3Fts5PutVarint(unsigned char *p, u64 v){
}
-static int sqlite3Fts5GetVarintLen(u32 iVal){
+static int tdsqlite3Fts5GetVarintLen(u32 iVal){
#if 0
if( iVal<(1 << 7 ) ) return 1;
#endif
@@ -201973,7 +230995,6 @@ static int sqlite3Fts5GetVarintLen(u32 iVal){
return 5;
}
-
/*
** 2015 May 08
**
@@ -202005,6 +231026,11 @@ static int sqlite3Fts5GetVarintLen(u32 iVal){
** the number of fts5 rows that contain at least one instance of term
** $term. Field $cnt is set to the total number of instances of term
** $term in the database.
+**
+** instance:
+** CREATE TABLE vocab(term, doc, col, offset, PRIMARY KEY(<all-fields>));
+**
+** One row for each term instance in the database.
*/
@@ -202015,18 +231041,18 @@ typedef struct Fts5VocabTable Fts5VocabTable;
typedef struct Fts5VocabCursor Fts5VocabCursor;
struct Fts5VocabTable {
- sqlite3_vtab base;
+ tdsqlite3_vtab base;
char *zFts5Tbl; /* Name of fts5 table */
char *zFts5Db; /* Db containing fts5 table */
- sqlite3 *db; /* Database handle */
+ tdsqlite3 *db; /* Database handle */
Fts5Global *pGlobal; /* FTS5 global object for this database */
- int eType; /* FTS5_VOCAB_COL or ROW */
+ int eType; /* FTS5_VOCAB_COL, ROW or INSTANCE */
};
struct Fts5VocabCursor {
- sqlite3_vtab_cursor base;
- sqlite3_stmt *pStmt; /* Statement holding lock on pIndex */
- Fts5Index *pIndex; /* Associated FTS5 index */
+ tdsqlite3_vtab_cursor base;
+ tdsqlite3_stmt *pStmt; /* Statement holding lock on pIndex */
+ Fts5Table *pFts5; /* Associated FTS5 table */
int bEof; /* True if this cursor is at EOF */
Fts5IndexIter *pIter; /* Term/rowid iterator object */
@@ -202035,21 +231061,26 @@ struct Fts5VocabCursor {
char *zLeTerm; /* (term <= $zLeTerm) paramater, or NULL */
/* These are used by 'col' tables only */
- Fts5Config *pConfig; /* Fts5 table configuration */
int iCol;
i64 *aCnt;
i64 *aDoc;
- /* Output values used by 'row' and 'col' tables */
+ /* Output values used by all tables. */
i64 rowid; /* This table's current rowid value */
Fts5Buffer term; /* Current value of 'term' column */
+
+ /* Output values Used by 'instance' tables only */
+ i64 iInstPos;
+ int iInstOff;
};
-#define FTS5_VOCAB_COL 0
-#define FTS5_VOCAB_ROW 1
+#define FTS5_VOCAB_COL 0
+#define FTS5_VOCAB_ROW 1
+#define FTS5_VOCAB_INSTANCE 2
#define FTS5_VOCAB_COL_SCHEMA "term, col, doc, cnt"
#define FTS5_VOCAB_ROW_SCHEMA "term, doc, cnt"
+#define FTS5_VOCAB_INST_SCHEMA "term, doc, col, offset"
/*
** Bits for the mask used as the idxNum value by xBestIndex/xFilter.
@@ -202067,21 +231098,24 @@ struct Fts5VocabCursor {
*/
static int fts5VocabTableType(const char *zType, char **pzErr, int *peType){
int rc = SQLITE_OK;
- char *zCopy = sqlite3Fts5Strndup(&rc, zType, -1);
+ char *zCopy = tdsqlite3Fts5Strndup(&rc, zType, -1);
if( rc==SQLITE_OK ){
- sqlite3Fts5Dequote(zCopy);
- if( sqlite3_stricmp(zCopy, "col")==0 ){
+ tdsqlite3Fts5Dequote(zCopy);
+ if( tdsqlite3_stricmp(zCopy, "col")==0 ){
*peType = FTS5_VOCAB_COL;
}else
- if( sqlite3_stricmp(zCopy, "row")==0 ){
+ if( tdsqlite3_stricmp(zCopy, "row")==0 ){
*peType = FTS5_VOCAB_ROW;
}else
+ if( tdsqlite3_stricmp(zCopy, "instance")==0 ){
+ *peType = FTS5_VOCAB_INSTANCE;
+ }else
{
- *pzErr = sqlite3_mprintf("fts5vocab: unknown table type: %Q", zCopy);
+ *pzErr = tdsqlite3_mprintf("fts5vocab: unknown table type: %Q", zCopy);
rc = SQLITE_ERROR;
}
- sqlite3_free(zCopy);
+ tdsqlite3_free(zCopy);
}
return rc;
@@ -202091,18 +231125,18 @@ static int fts5VocabTableType(const char *zType, char **pzErr, int *peType){
/*
** The xDisconnect() virtual table method.
*/
-static int fts5VocabDisconnectMethod(sqlite3_vtab *pVtab){
+static int fts5VocabDisconnectMethod(tdsqlite3_vtab *pVtab){
Fts5VocabTable *pTab = (Fts5VocabTable*)pVtab;
- sqlite3_free(pTab);
+ tdsqlite3_free(pTab);
return SQLITE_OK;
}
/*
** The xDestroy() virtual table method.
*/
-static int fts5VocabDestroyMethod(sqlite3_vtab *pVtab){
+static int fts5VocabDestroyMethod(tdsqlite3_vtab *pVtab){
Fts5VocabTable *pTab = (Fts5VocabTable*)pVtab;
- sqlite3_free(pTab);
+ tdsqlite3_free(pTab);
return SQLITE_OK;
}
@@ -202128,16 +231162,17 @@ static int fts5VocabDestroyMethod(sqlite3_vtab *pVtab){
** argv[5] -> type of fts5vocab table
*/
static int fts5VocabInitVtab(
- sqlite3 *db, /* The SQLite database connection */
+ tdsqlite3 *db, /* The SQLite database connection */
void *pAux, /* Pointer to Fts5Global object */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
+ tdsqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
char **pzErr /* Write any error message here */
){
const char *azSchema[] = {
"CREATE TABlE vocab(" FTS5_VOCAB_COL_SCHEMA ")",
- "CREATE TABlE vocab(" FTS5_VOCAB_ROW_SCHEMA ")"
+ "CREATE TABlE vocab(" FTS5_VOCAB_ROW_SCHEMA ")",
+ "CREATE TABlE vocab(" FTS5_VOCAB_INST_SCHEMA ")"
};
Fts5VocabTable *pRet = 0;
@@ -202147,7 +231182,7 @@ static int fts5VocabInitVtab(
bDb = (argc==6 && strlen(argv[1])==4 && memcmp("temp", argv[1], 4)==0);
if( argc!=5 && bDb==0 ){
- *pzErr = sqlite3_mprintf("wrong number of vtable arguments");
+ *pzErr = tdsqlite3_mprintf("wrong number of vtable arguments");
rc = SQLITE_ERROR;
}else{
int nByte; /* Bytes of space to allocate */
@@ -202161,11 +231196,11 @@ static int fts5VocabInitVtab(
rc = fts5VocabTableType(zType, pzErr, &eType);
if( rc==SQLITE_OK ){
assert( eType>=0 && eType<ArraySize(azSchema) );
- rc = sqlite3_declare_vtab(db, azSchema[eType]);
+ rc = tdsqlite3_declare_vtab(db, azSchema[eType]);
}
nByte = sizeof(Fts5VocabTable) + nDb + nTab;
- pRet = sqlite3Fts5MallocZero(&rc, nByte);
+ pRet = tdsqlite3Fts5MallocZero(&rc, nByte);
if( pRet ){
pRet->pGlobal = (Fts5Global*)pAux;
pRet->eType = eType;
@@ -202174,12 +231209,12 @@ static int fts5VocabInitVtab(
pRet->zFts5Db = &pRet->zFts5Tbl[nTab];
memcpy(pRet->zFts5Tbl, zTab, nTab);
memcpy(pRet->zFts5Db, zDb, nDb);
- sqlite3Fts5Dequote(pRet->zFts5Tbl);
- sqlite3Fts5Dequote(pRet->zFts5Db);
+ tdsqlite3Fts5Dequote(pRet->zFts5Tbl);
+ tdsqlite3Fts5Dequote(pRet->zFts5Db);
}
}
- *ppVTab = (sqlite3_vtab*)pRet;
+ *ppVTab = (tdsqlite3_vtab*)pRet;
return rc;
}
@@ -202189,32 +231224,41 @@ static int fts5VocabInitVtab(
** work is done in function fts5VocabInitVtab().
*/
static int fts5VocabConnectMethod(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
void *pAux, /* Pointer to tokenizer hash table */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
- char **pzErr /* OUT: sqlite3_malloc'd error message */
+ tdsqlite3_vtab **ppVtab, /* OUT: New tdsqlite3_vtab object */
+ char **pzErr /* OUT: tdsqlite3_malloc'd error message */
){
return fts5VocabInitVtab(db, pAux, argc, argv, ppVtab, pzErr);
}
static int fts5VocabCreateMethod(
- sqlite3 *db, /* Database connection */
+ tdsqlite3 *db, /* Database connection */
void *pAux, /* Pointer to tokenizer hash table */
int argc, /* Number of elements in argv array */
const char * const *argv, /* xCreate/xConnect argument array */
- sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
- char **pzErr /* OUT: sqlite3_malloc'd error message */
+ tdsqlite3_vtab **ppVtab, /* OUT: New tdsqlite3_vtab object */
+ char **pzErr /* OUT: tdsqlite3_malloc'd error message */
){
return fts5VocabInitVtab(db, pAux, argc, argv, ppVtab, pzErr);
}
/*
** Implementation of the xBestIndex method.
+**
+** Only constraints of the form:
+**
+** term <= ?
+** term == ?
+** term >= ?
+**
+** are interpreted. Less-than and less-than-or-equal are treated
+** identically, as are greater-than and greater-than-or-equal.
*/
static int fts5VocabBestIndexMethod(
- sqlite3_vtab *pUnused,
- sqlite3_index_info *pInfo
+ tdsqlite3_vtab *pUnused,
+ tdsqlite3_index_info *pInfo
){
int i;
int iTermEq = -1;
@@ -202226,7 +231270,7 @@ static int fts5VocabBestIndexMethod(
UNUSED_PARAM(pUnused);
for(i=0; i<pInfo->nConstraint; i++){
- struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
+ struct tdsqlite3_index_constraint *p = &pInfo->aConstraint[i];
if( p->usable==0 ) continue;
if( p->iColumn==0 ){ /* term column */
if( p->op==SQLITE_INDEX_CONSTRAINT_EQ ) iTermEq = i;
@@ -202258,7 +231302,7 @@ static int fts5VocabBestIndexMethod(
/* This virtual table always delivers results in ascending order of
** the "term" column (column 0). So if the user has requested this
** specifically - "ORDER BY term" or "ORDER BY term ASC" - set the
- ** sqlite3_index_info.orderByConsumed flag to tell the core the results
+ ** tdsqlite3_index_info.orderByConsumed flag to tell the core the results
** are already in sorted order. */
if( pInfo->nOrderBy==1
&& pInfo->aOrderBy[0].iColumn==0
@@ -202275,111 +231319,169 @@ static int fts5VocabBestIndexMethod(
** Implementation of xOpen method.
*/
static int fts5VocabOpenMethod(
- sqlite3_vtab *pVTab,
- sqlite3_vtab_cursor **ppCsr
+ tdsqlite3_vtab *pVTab,
+ tdsqlite3_vtab_cursor **ppCsr
){
Fts5VocabTable *pTab = (Fts5VocabTable*)pVTab;
- Fts5Index *pIndex = 0;
- Fts5Config *pConfig = 0;
+ Fts5Table *pFts5 = 0;
Fts5VocabCursor *pCsr = 0;
int rc = SQLITE_OK;
- sqlite3_stmt *pStmt = 0;
+ tdsqlite3_stmt *pStmt = 0;
char *zSql = 0;
- zSql = sqlite3Fts5Mprintf(&rc,
+ zSql = tdsqlite3Fts5Mprintf(&rc,
"SELECT t.%Q FROM %Q.%Q AS t WHERE t.%Q MATCH '*id'",
pTab->zFts5Tbl, pTab->zFts5Db, pTab->zFts5Tbl, pTab->zFts5Tbl
);
if( zSql ){
- rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pStmt, 0);
+ rc = tdsqlite3_prepare_v2(pTab->db, zSql, -1, &pStmt, 0);
}
- sqlite3_free(zSql);
+ tdsqlite3_free(zSql);
assert( rc==SQLITE_OK || pStmt==0 );
if( rc==SQLITE_ERROR ) rc = SQLITE_OK;
- if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
- i64 iId = sqlite3_column_int64(pStmt, 0);
- pIndex = sqlite3Fts5IndexFromCsrid(pTab->pGlobal, iId, &pConfig);
+ if( pStmt && tdsqlite3_step(pStmt)==SQLITE_ROW ){
+ i64 iId = tdsqlite3_column_int64(pStmt, 0);
+ pFts5 = tdsqlite3Fts5TableFromCsrid(pTab->pGlobal, iId);
}
- if( rc==SQLITE_OK && pIndex==0 ){
- rc = sqlite3_finalize(pStmt);
- pStmt = 0;
- if( rc==SQLITE_OK ){
- pVTab->zErrMsg = sqlite3_mprintf(
- "no such fts5 table: %s.%s", pTab->zFts5Db, pTab->zFts5Tbl
- );
- rc = SQLITE_ERROR;
+ if( rc==SQLITE_OK ){
+ if( pFts5==0 ){
+ rc = tdsqlite3_finalize(pStmt);
+ pStmt = 0;
+ if( rc==SQLITE_OK ){
+ pVTab->zErrMsg = tdsqlite3_mprintf(
+ "no such fts5 table: %s.%s", pTab->zFts5Db, pTab->zFts5Tbl
+ );
+ rc = SQLITE_ERROR;
+ }
+ }else{
+ rc = tdsqlite3Fts5FlushToDisk(pFts5);
}
}
if( rc==SQLITE_OK ){
- int nByte = pConfig->nCol * sizeof(i64) * 2 + sizeof(Fts5VocabCursor);
- pCsr = (Fts5VocabCursor*)sqlite3Fts5MallocZero(&rc, nByte);
+ int nByte = pFts5->pConfig->nCol * sizeof(i64)*2 + sizeof(Fts5VocabCursor);
+ pCsr = (Fts5VocabCursor*)tdsqlite3Fts5MallocZero(&rc, nByte);
}
if( pCsr ){
- pCsr->pIndex = pIndex;
+ pCsr->pFts5 = pFts5;
pCsr->pStmt = pStmt;
- pCsr->pConfig = pConfig;
pCsr->aCnt = (i64*)&pCsr[1];
- pCsr->aDoc = &pCsr->aCnt[pConfig->nCol];
+ pCsr->aDoc = &pCsr->aCnt[pFts5->pConfig->nCol];
}else{
- sqlite3_finalize(pStmt);
+ tdsqlite3_finalize(pStmt);
}
- *ppCsr = (sqlite3_vtab_cursor*)pCsr;
+ *ppCsr = (tdsqlite3_vtab_cursor*)pCsr;
return rc;
}
static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){
pCsr->rowid = 0;
- sqlite3Fts5IterClose(pCsr->pIter);
+ tdsqlite3Fts5IterClose(pCsr->pIter);
pCsr->pIter = 0;
- sqlite3_free(pCsr->zLeTerm);
+ tdsqlite3_free(pCsr->zLeTerm);
pCsr->nLeTerm = -1;
pCsr->zLeTerm = 0;
+ pCsr->bEof = 0;
}
/*
** Close the cursor. For additional information see the documentation
** on the xClose method of the virtual table interface.
*/
-static int fts5VocabCloseMethod(sqlite3_vtab_cursor *pCursor){
+static int fts5VocabCloseMethod(tdsqlite3_vtab_cursor *pCursor){
Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
fts5VocabResetCursor(pCsr);
- sqlite3Fts5BufferFree(&pCsr->term);
- sqlite3_finalize(pCsr->pStmt);
- sqlite3_free(pCsr);
+ tdsqlite3Fts5BufferFree(&pCsr->term);
+ tdsqlite3_finalize(pCsr->pStmt);
+ tdsqlite3_free(pCsr);
return SQLITE_OK;
}
+static int fts5VocabInstanceNewTerm(Fts5VocabCursor *pCsr){
+ int rc = SQLITE_OK;
+
+ if( tdsqlite3Fts5IterEof(pCsr->pIter) ){
+ pCsr->bEof = 1;
+ }else{
+ const char *zTerm;
+ int nTerm;
+ zTerm = tdsqlite3Fts5IterTerm(pCsr->pIter, &nTerm);
+ if( pCsr->nLeTerm>=0 ){
+ int nCmp = MIN(nTerm, pCsr->nLeTerm);
+ int bCmp = memcmp(pCsr->zLeTerm, zTerm, nCmp);
+ if( bCmp<0 || (bCmp==0 && pCsr->nLeTerm<nTerm) ){
+ pCsr->bEof = 1;
+ }
+ }
+
+ tdsqlite3Fts5BufferSet(&rc, &pCsr->term, nTerm, (const u8*)zTerm);
+ }
+ return rc;
+}
+
+static int fts5VocabInstanceNext(Fts5VocabCursor *pCsr){
+ int eDetail = pCsr->pFts5->pConfig->eDetail;
+ int rc = SQLITE_OK;
+ Fts5IndexIter *pIter = pCsr->pIter;
+ i64 *pp = &pCsr->iInstPos;
+ int *po = &pCsr->iInstOff;
+
+ assert( tdsqlite3Fts5IterEof(pIter)==0 );
+ assert( pCsr->bEof==0 );
+ while( eDetail==FTS5_DETAIL_NONE
+ || tdsqlite3Fts5PoslistNext64(pIter->pData, pIter->nData, po, pp)
+ ){
+ pCsr->iInstPos = 0;
+ pCsr->iInstOff = 0;
+
+ rc = tdsqlite3Fts5IterNextScan(pCsr->pIter);
+ if( rc==SQLITE_OK ){
+ rc = fts5VocabInstanceNewTerm(pCsr);
+ if( pCsr->bEof || eDetail==FTS5_DETAIL_NONE ) break;
+ }
+ if( rc ){
+ pCsr->bEof = 1;
+ break;
+ }
+ }
+
+ return rc;
+}
/*
** Advance the cursor to the next row in the table.
*/
-static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){
+static int fts5VocabNextMethod(tdsqlite3_vtab_cursor *pCursor){
Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
Fts5VocabTable *pTab = (Fts5VocabTable*)pCursor->pVtab;
int rc = SQLITE_OK;
- int nCol = pCsr->pConfig->nCol;
+ int nCol = pCsr->pFts5->pConfig->nCol;
pCsr->rowid++;
+ if( pTab->eType==FTS5_VOCAB_INSTANCE ){
+ return fts5VocabInstanceNext(pCsr);
+ }
+
if( pTab->eType==FTS5_VOCAB_COL ){
for(pCsr->iCol++; pCsr->iCol<nCol; pCsr->iCol++){
if( pCsr->aDoc[pCsr->iCol] ) break;
}
}
- if( pTab->eType==FTS5_VOCAB_ROW || pCsr->iCol>=nCol ){
- if( sqlite3Fts5IterEof(pCsr->pIter) ){
+ if( pTab->eType!=FTS5_VOCAB_COL || pCsr->iCol>=nCol ){
+ if( tdsqlite3Fts5IterEof(pCsr->pIter) ){
pCsr->bEof = 1;
}else{
const char *zTerm;
int nTerm;
- zTerm = sqlite3Fts5IterTerm(pCsr->pIter, &nTerm);
+ zTerm = tdsqlite3Fts5IterTerm(pCsr->pIter, &nTerm);
+ assert( nTerm>=0 );
if( pCsr->nLeTerm>=0 ){
int nCmp = MIN(nTerm, pCsr->nLeTerm);
int bCmp = memcmp(pCsr->zLeTerm, zTerm, nCmp);
@@ -202389,33 +231491,36 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){
}
}
- sqlite3Fts5BufferSet(&rc, &pCsr->term, nTerm, (const u8*)zTerm);
+ tdsqlite3Fts5BufferSet(&rc, &pCsr->term, nTerm, (const u8*)zTerm);
memset(pCsr->aCnt, 0, nCol * sizeof(i64));
memset(pCsr->aDoc, 0, nCol * sizeof(i64));
pCsr->iCol = 0;
assert( pTab->eType==FTS5_VOCAB_COL || pTab->eType==FTS5_VOCAB_ROW );
while( rc==SQLITE_OK ){
+ int eDetail = pCsr->pFts5->pConfig->eDetail;
const u8 *pPos; int nPos; /* Position list */
i64 iPos = 0; /* 64-bit position read from poslist */
int iOff = 0; /* Current offset within position list */
pPos = pCsr->pIter->pData;
nPos = pCsr->pIter->nData;
- switch( pCsr->pConfig->eDetail ){
- case FTS5_DETAIL_FULL:
- pPos = pCsr->pIter->pData;
- nPos = pCsr->pIter->nData;
- if( pTab->eType==FTS5_VOCAB_ROW ){
- while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
+
+ switch( pTab->eType ){
+ case FTS5_VOCAB_ROW:
+ if( eDetail==FTS5_DETAIL_FULL ){
+ while( 0==tdsqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
pCsr->aCnt[0]++;
}
- pCsr->aDoc[0]++;
- }else{
+ }
+ pCsr->aDoc[0]++;
+ break;
+
+ case FTS5_VOCAB_COL:
+ if( eDetail==FTS5_DETAIL_FULL ){
int iCol = -1;
- while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
+ while( 0==tdsqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
int ii = FTS5_POS2COLUMN(iPos);
- pCsr->aCnt[ii]++;
if( iCol!=ii ){
if( ii>=nCol ){
rc = FTS5_CORRUPT;
@@ -202424,15 +231529,10 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){
pCsr->aDoc[ii]++;
iCol = ii;
}
+ pCsr->aCnt[ii]++;
}
- }
- break;
-
- case FTS5_DETAIL_COLUMNS:
- if( pTab->eType==FTS5_VOCAB_ROW ){
- pCsr->aDoc[0]++;
- }else{
- while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff,&iPos) ){
+ }else if( eDetail==FTS5_DETAIL_COLUMNS ){
+ while( 0==tdsqlite3Fts5PoslistNext64(pPos, nPos, &iOff,&iPos) ){
assert_nc( iPos>=0 && iPos<nCol );
if( iPos>=nCol ){
rc = FTS5_CORRUPT;
@@ -202440,33 +231540,40 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){
}
pCsr->aDoc[iPos]++;
}
+ }else{
+ assert( eDetail==FTS5_DETAIL_NONE );
+ pCsr->aDoc[0]++;
}
break;
- default:
- assert( pCsr->pConfig->eDetail==FTS5_DETAIL_NONE );
- pCsr->aDoc[0]++;
+ default:
+ assert( pTab->eType==FTS5_VOCAB_INSTANCE );
break;
}
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IterNextScan(pCsr->pIter);
+ rc = tdsqlite3Fts5IterNextScan(pCsr->pIter);
}
+ if( pTab->eType==FTS5_VOCAB_INSTANCE ) break;
if( rc==SQLITE_OK ){
- zTerm = sqlite3Fts5IterTerm(pCsr->pIter, &nTerm);
- if( nTerm!=pCsr->term.n || memcmp(zTerm, pCsr->term.p, nTerm) ){
+ zTerm = tdsqlite3Fts5IterTerm(pCsr->pIter, &nTerm);
+ if( nTerm!=pCsr->term.n
+ || (nTerm>0 && memcmp(zTerm, pCsr->term.p, nTerm))
+ ){
break;
}
- if( sqlite3Fts5IterEof(pCsr->pIter) ) break;
+ if( tdsqlite3Fts5IterEof(pCsr->pIter) ) break;
}
}
}
}
if( rc==SQLITE_OK && pCsr->bEof==0 && pTab->eType==FTS5_VOCAB_COL ){
- while( pCsr->aDoc[pCsr->iCol]==0 ) pCsr->iCol++;
- assert( pCsr->iCol<pCsr->pConfig->nCol );
+ for(/* noop */; pCsr->iCol<nCol && pCsr->aDoc[pCsr->iCol]==0; pCsr->iCol++);
+ if( pCsr->iCol==nCol ){
+ rc = FTS5_CORRUPT;
+ }
}
return rc;
}
@@ -202475,13 +231582,15 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){
** This is the xFilter implementation for the virtual table.
*/
static int fts5VocabFilterMethod(
- sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
+ tdsqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
int idxNum, /* Strategy index */
const char *zUnused, /* Unused */
int nUnused, /* Number of elements in apVal */
- sqlite3_value **apVal /* Arguments for the indexing scheme */
+ tdsqlite3_value **apVal /* Arguments for the indexing scheme */
){
+ Fts5VocabTable *pTab = (Fts5VocabTable*)pCursor->pVtab;
Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
+ int eType = pTab->eType;
int rc = SQLITE_OK;
int iVal = 0;
@@ -202489,9 +231598,9 @@ static int fts5VocabFilterMethod(
const char *zTerm = 0;
int nTerm = 0;
- sqlite3_value *pEq = 0;
- sqlite3_value *pGe = 0;
- sqlite3_value *pLe = 0;
+ tdsqlite3_value *pEq = 0;
+ tdsqlite3_value *pGe = 0;
+ tdsqlite3_value *pLe = 0;
UNUSED_PARAM2(zUnused, nUnused);
@@ -202501,18 +231610,19 @@ static int fts5VocabFilterMethod(
if( idxNum & FTS5_VOCAB_TERM_LE ) pLe = apVal[iVal++];
if( pEq ){
- zTerm = (const char *)sqlite3_value_text(pEq);
- nTerm = sqlite3_value_bytes(pEq);
+ zTerm = (const char *)tdsqlite3_value_text(pEq);
+ nTerm = tdsqlite3_value_bytes(pEq);
f = 0;
}else{
if( pGe ){
- zTerm = (const char *)sqlite3_value_text(pGe);
- nTerm = sqlite3_value_bytes(pGe);
+ zTerm = (const char *)tdsqlite3_value_text(pGe);
+ nTerm = tdsqlite3_value_bytes(pGe);
}
if( pLe ){
- const char *zCopy = (const char *)sqlite3_value_text(pLe);
- pCsr->nLeTerm = sqlite3_value_bytes(pLe);
- pCsr->zLeTerm = sqlite3_malloc(pCsr->nLeTerm+1);
+ const char *zCopy = (const char *)tdsqlite3_value_text(pLe);
+ if( zCopy==0 ) zCopy = "";
+ pCsr->nLeTerm = tdsqlite3_value_bytes(pLe);
+ pCsr->zLeTerm = tdsqlite3_malloc(pCsr->nLeTerm+1);
if( pCsr->zLeTerm==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -202521,11 +231631,17 @@ static int fts5VocabFilterMethod(
}
}
-
if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexQuery(pCsr->pIndex, zTerm, nTerm, f, 0, &pCsr->pIter);
+ Fts5Index *pIndex = pCsr->pFts5->pIndex;
+ rc = tdsqlite3Fts5IndexQuery(pIndex, zTerm, nTerm, f, 0, &pCsr->pIter);
}
- if( rc==SQLITE_OK ){
+ if( rc==SQLITE_OK && eType==FTS5_VOCAB_INSTANCE ){
+ rc = fts5VocabInstanceNewTerm(pCsr);
+ }
+ if( rc==SQLITE_OK && !pCsr->bEof
+ && (eType!=FTS5_VOCAB_INSTANCE
+ || pCsr->pFts5->pConfig->eDetail!=FTS5_DETAIL_NONE)
+ ){
rc = fts5VocabNextMethod(pCursor);
}
@@ -202536,47 +231652,75 @@ static int fts5VocabFilterMethod(
** This is the xEof method of the virtual table. SQLite calls this
** routine to find out if it has reached the end of a result set.
*/
-static int fts5VocabEofMethod(sqlite3_vtab_cursor *pCursor){
+static int fts5VocabEofMethod(tdsqlite3_vtab_cursor *pCursor){
Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
return pCsr->bEof;
}
static int fts5VocabColumnMethod(
- sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
- sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
+ tdsqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
+ tdsqlite3_context *pCtx, /* Context for tdsqlite3_result_xxx() calls */
int iCol /* Index of column to read value from */
){
Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
- int eDetail = pCsr->pConfig->eDetail;
+ int eDetail = pCsr->pFts5->pConfig->eDetail;
int eType = ((Fts5VocabTable*)(pCursor->pVtab))->eType;
i64 iVal = 0;
if( iCol==0 ){
- sqlite3_result_text(
+ tdsqlite3_result_text(
pCtx, (const char*)pCsr->term.p, pCsr->term.n, SQLITE_TRANSIENT
);
}else if( eType==FTS5_VOCAB_COL ){
assert( iCol==1 || iCol==2 || iCol==3 );
if( iCol==1 ){
if( eDetail!=FTS5_DETAIL_NONE ){
- const char *z = pCsr->pConfig->azCol[pCsr->iCol];
- sqlite3_result_text(pCtx, z, -1, SQLITE_STATIC);
+ const char *z = pCsr->pFts5->pConfig->azCol[pCsr->iCol];
+ tdsqlite3_result_text(pCtx, z, -1, SQLITE_STATIC);
}
}else if( iCol==2 ){
iVal = pCsr->aDoc[pCsr->iCol];
}else{
iVal = pCsr->aCnt[pCsr->iCol];
}
- }else{
+ }else if( eType==FTS5_VOCAB_ROW ){
assert( iCol==1 || iCol==2 );
if( iCol==1 ){
iVal = pCsr->aDoc[0];
}else{
iVal = pCsr->aCnt[0];
}
+ }else{
+ assert( eType==FTS5_VOCAB_INSTANCE );
+ switch( iCol ){
+ case 1:
+ tdsqlite3_result_int64(pCtx, pCsr->pIter->iRowid);
+ break;
+ case 2: {
+ int ii = -1;
+ if( eDetail==FTS5_DETAIL_FULL ){
+ ii = FTS5_POS2COLUMN(pCsr->iInstPos);
+ }else if( eDetail==FTS5_DETAIL_COLUMNS ){
+ ii = (int)pCsr->iInstPos;
+ }
+ if( ii>=0 && ii<pCsr->pFts5->pConfig->nCol ){
+ const char *z = pCsr->pFts5->pConfig->azCol[ii];
+ tdsqlite3_result_text(pCtx, z, -1, SQLITE_STATIC);
+ }
+ break;
+ }
+ default: {
+ assert( iCol==3 );
+ if( eDetail==FTS5_DETAIL_FULL ){
+ int ii = FTS5_POS2OFFSET(pCsr->iInstPos);
+ tdsqlite3_result_int(pCtx, ii);
+ }
+ break;
+ }
+ }
}
- if( iVal>0 ) sqlite3_result_int64(pCtx, iVal);
+ if( iVal>0 ) tdsqlite3_result_int64(pCtx, iVal);
return SQLITE_OK;
}
@@ -202586,7 +231730,7 @@ static int fts5VocabColumnMethod(
** rowid should be written to *pRowid.
*/
static int fts5VocabRowidMethod(
- sqlite3_vtab_cursor *pCursor,
+ tdsqlite3_vtab_cursor *pCursor,
sqlite_int64 *pRowid
){
Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
@@ -202594,8 +231738,8 @@ static int fts5VocabRowidMethod(
return SQLITE_OK;
}
-static int sqlite3Fts5VocabInit(Fts5Global *pGlobal, sqlite3 *db){
- static const sqlite3_module fts5Vocab = {
+static int tdsqlite3Fts5VocabInit(Fts5Global *pGlobal, tdsqlite3 *db){
+ static const tdsqlite3_module fts5Vocab = {
/* iVersion */ 2,
/* xCreate */ fts5VocabCreateMethod,
/* xConnect */ fts5VocabConnectMethod,
@@ -202619,16 +231763,324 @@ static int sqlite3Fts5VocabInit(Fts5Global *pGlobal, sqlite3 *db){
/* xSavepoint */ 0,
/* xRelease */ 0,
/* xRollbackTo */ 0,
+ /* xShadowName */ 0
};
void *p = (void*)pGlobal;
- return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0);
+ return tdsqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0);
}
-
-
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */
/************** End of fts5.c ************************************************/
+/************** Begin file stmt.c ********************************************/
+/*
+** 2017-05-31
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** This file demonstrates an eponymous virtual table that returns information
+** about all prepared statements for the database connection.
+**
+** Usage example:
+**
+** .load ./stmt
+** .mode line
+** .header on
+** SELECT * FROM stmt;
+*/
+#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB)
+#if !defined(SQLITEINT_H)
+/* #include "tdsqlite3ext.h" */
+#endif
+SQLITE_EXTENSION_INIT1
+/* #include <assert.h> */
+/* #include <string.h> */
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+/* stmt_vtab is a subclass of tdsqlite3_vtab which will
+** serve as the underlying representation of a stmt virtual table
+*/
+typedef struct stmt_vtab stmt_vtab;
+struct stmt_vtab {
+ tdsqlite3_vtab base; /* Base class - must be first */
+ tdsqlite3 *db; /* Database connection for this stmt vtab */
+};
+
+/* stmt_cursor is a subclass of tdsqlite3_vtab_cursor which will
+** serve as the underlying representation of a cursor that scans
+** over rows of the result
+*/
+typedef struct stmt_cursor stmt_cursor;
+struct stmt_cursor {
+ tdsqlite3_vtab_cursor base; /* Base class - must be first */
+ tdsqlite3 *db; /* Database connection for this cursor */
+ tdsqlite3_stmt *pStmt; /* Statement cursor is currently pointing at */
+ tdsqlite3_int64 iRowid; /* The rowid */
+};
+
+/*
+** The stmtConnect() method is invoked to create a new
+** stmt_vtab that describes the stmt virtual table.
+**
+** Think of this routine as the constructor for stmt_vtab objects.
+**
+** All this routine needs to do is:
+**
+** (1) Allocate the stmt_vtab object and initialize all fields.
+**
+** (2) Tell SQLite (via the tdsqlite3_declare_vtab() interface) what the
+** result set of queries against stmt will look like.
+*/
+static int stmtConnect(
+ tdsqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ tdsqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ stmt_vtab *pNew;
+ int rc;
+
+/* Column numbers */
+#define STMT_COLUMN_SQL 0 /* SQL for the statement */
+#define STMT_COLUMN_NCOL 1 /* Number of result columns */
+#define STMT_COLUMN_RO 2 /* True if read-only */
+#define STMT_COLUMN_BUSY 3 /* True if currently busy */
+#define STMT_COLUMN_NSCAN 4 /* SQLITE_STMTSTATUS_FULLSCAN_STEP */
+#define STMT_COLUMN_NSORT 5 /* SQLITE_STMTSTATUS_SORT */
+#define STMT_COLUMN_NAIDX 6 /* SQLITE_STMTSTATUS_AUTOINDEX */
+#define STMT_COLUMN_NSTEP 7 /* SQLITE_STMTSTATUS_VM_STEP */
+#define STMT_COLUMN_REPREP 8 /* SQLITE_STMTSTATUS_REPREPARE */
+#define STMT_COLUMN_RUN 9 /* SQLITE_STMTSTATUS_RUN */
+#define STMT_COLUMN_MEM 10 /* SQLITE_STMTSTATUS_MEMUSED */
+
+
+ rc = tdsqlite3_declare_vtab(db,
+ "CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep,"
+ "reprep,run,mem)");
+ if( rc==SQLITE_OK ){
+ pNew = tdsqlite3_malloc( sizeof(*pNew) );
+ *ppVtab = (tdsqlite3_vtab*)pNew;
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, sizeof(*pNew));
+ pNew->db = db;
+ }
+ return rc;
+}
+
+/*
+** This method is the destructor for stmt_cursor objects.
+*/
+static int stmtDisconnect(tdsqlite3_vtab *pVtab){
+ tdsqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new stmt_cursor object.
+*/
+static int stmtOpen(tdsqlite3_vtab *p, tdsqlite3_vtab_cursor **ppCursor){
+ stmt_cursor *pCur;
+ pCur = tdsqlite3_malloc( sizeof(*pCur) );
+ if( pCur==0 ) return SQLITE_NOMEM;
+ memset(pCur, 0, sizeof(*pCur));
+ pCur->db = ((stmt_vtab*)p)->db;
+ *ppCursor = &pCur->base;
+ return SQLITE_OK;
+}
+
+/*
+** Destructor for a stmt_cursor.
+*/
+static int stmtClose(tdsqlite3_vtab_cursor *cur){
+ tdsqlite3_free(cur);
+ return SQLITE_OK;
+}
+
+
+/*
+** Advance a stmt_cursor to its next row of output.
+*/
+static int stmtNext(tdsqlite3_vtab_cursor *cur){
+ stmt_cursor *pCur = (stmt_cursor*)cur;
+ pCur->iRowid++;
+ pCur->pStmt = tdsqlite3_next_stmt(pCur->db, pCur->pStmt);
+ return SQLITE_OK;
+}
+
+/*
+** Return values of columns for the row at which the stmt_cursor
+** is currently pointing.
+*/
+static int stmtColumn(
+ tdsqlite3_vtab_cursor *cur, /* The cursor */
+ tdsqlite3_context *ctx, /* First argument to tdsqlite3_result_...() */
+ int i /* Which column to return */
+){
+ stmt_cursor *pCur = (stmt_cursor*)cur;
+ switch( i ){
+ case STMT_COLUMN_SQL: {
+ tdsqlite3_result_text(ctx, tdsqlite3_sql(pCur->pStmt), -1, SQLITE_TRANSIENT);
+ break;
+ }
+ case STMT_COLUMN_NCOL: {
+ tdsqlite3_result_int(ctx, tdsqlite3_column_count(pCur->pStmt));
+ break;
+ }
+ case STMT_COLUMN_RO: {
+ tdsqlite3_result_int(ctx, tdsqlite3_stmt_readonly(pCur->pStmt));
+ break;
+ }
+ case STMT_COLUMN_BUSY: {
+ tdsqlite3_result_int(ctx, tdsqlite3_stmt_busy(pCur->pStmt));
+ break;
+ }
+ case STMT_COLUMN_MEM: {
+ i = SQLITE_STMTSTATUS_MEMUSED +
+ STMT_COLUMN_NSCAN - SQLITE_STMTSTATUS_FULLSCAN_STEP;
+ /* Fall thru */
+ }
+ case STMT_COLUMN_NSCAN:
+ case STMT_COLUMN_NSORT:
+ case STMT_COLUMN_NAIDX:
+ case STMT_COLUMN_NSTEP:
+ case STMT_COLUMN_REPREP:
+ case STMT_COLUMN_RUN: {
+ tdsqlite3_result_int(ctx, tdsqlite3_stmt_status(pCur->pStmt,
+ i-STMT_COLUMN_NSCAN+SQLITE_STMTSTATUS_FULLSCAN_STEP, 0));
+ break;
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Return the rowid for the current row. In this implementation, the
+** rowid is the same as the output value.
+*/
+static int stmtRowid(tdsqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ stmt_cursor *pCur = (stmt_cursor*)cur;
+ *pRowid = pCur->iRowid;
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int stmtEof(tdsqlite3_vtab_cursor *cur){
+ stmt_cursor *pCur = (stmt_cursor*)cur;
+ return pCur->pStmt==0;
+}
+
+/*
+** This method is called to "rewind" the stmt_cursor object back
+** to the first row of output. This method is always called at least
+** once prior to any call to stmtColumn() or stmtRowid() or
+** stmtEof().
+*/
+static int stmtFilter(
+ tdsqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, tdsqlite3_value **argv
+){
+ stmt_cursor *pCur = (stmt_cursor *)pVtabCursor;
+ pCur->pStmt = 0;
+ pCur->iRowid = 0;
+ return stmtNext(pVtabCursor);
+}
+
+/*
+** SQLite will invoke this method one or more times while planning a query
+** that uses the stmt virtual table. This routine needs to create
+** a query plan for each invocation and compute an estimated cost for that
+** plan.
+*/
+static int stmtBestIndex(
+ tdsqlite3_vtab *tab,
+ tdsqlite3_index_info *pIdxInfo
+){
+ pIdxInfo->estimatedCost = (double)500;
+ pIdxInfo->estimatedRows = 500;
+ return SQLITE_OK;
+}
+
+/*
+** This following structure defines all the methods for the
+** stmt virtual table.
+*/
+static tdsqlite3_module stmtModule = {
+ 0, /* iVersion */
+ 0, /* xCreate */
+ stmtConnect, /* xConnect */
+ stmtBestIndex, /* xBestIndex */
+ stmtDisconnect, /* xDisconnect */
+ 0, /* xDestroy */
+ stmtOpen, /* xOpen - open a cursor */
+ stmtClose, /* xClose - close a cursor */
+ stmtFilter, /* xFilter - configure scan constraints */
+ stmtNext, /* xNext - advance a cursor */
+ stmtEof, /* xEof - check for end of scan */
+ stmtColumn, /* xColumn - read data */
+ stmtRowid, /* xRowid - read data */
+ 0, /* xUpdate */
+ 0, /* xBegin */
+ 0, /* xSync */
+ 0, /* xCommit */
+ 0, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ 0, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ 0, /* xShadowName */
+};
+
+#endif /* SQLITE_OMIT_VIRTUALTABLE */
+
+SQLITE_PRIVATE int tdsqlite3StmtVtabInit(tdsqlite3 *db){
+ int rc = SQLITE_OK;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ rc = tdsqlite3_create_module(db, "sqlite_stmt", &stmtModule, 0);
+#endif
+ return rc;
+}
+
+#ifndef SQLITE_CORE
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+SQLITE_API int tdsqlite3_stmt_init(
+ tdsqlite3 *db,
+ char **pzErrMsg,
+ const tdsqlite3_api_routines *pApi
+){
+ int rc = SQLITE_OK;
+ SQLITE_EXTENSION_INIT2(pApi);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ rc = tdsqlite3StmtVtabInit(db);
+#endif
+ return rc;
+}
+#endif /* SQLITE_CORE */
+#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
+
+/************** End of stmt.c ************************************************/
+#if __LINE__!=232867
+#undef SQLITE_SOURCE_ID
+#define SQLITE_SOURCE_ID "2020-01-22 18:38:59 f6affdd41608946fcfcea914ece149038a8b25a62bbe719ed2561c649b86alt2"
+#endif
+/* Return the source-id for this library */
+SQLITE_API const char *tdsqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
+/************************** End of sqlite3.c ******************************/
diff --git a/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.h b/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.h
index 8222b7930e..44de2ee213 100644
--- a/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.h
+++ b/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3.h
@@ -1,5 +1,5 @@
/*
-** 2001 September 15
+** 2001-09-15
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
@@ -114,20 +114,22 @@ extern "C" {
** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to
** a string which identifies a particular check-in of SQLite
** within its configuration management system. ^The SQLITE_SOURCE_ID
-** string contains the date and time of the check-in (UTC) and an SHA1
-** hash of the entire source tree.
+** string contains the date and time of the check-in (UTC) and a SHA1
+** or SHA3-256 hash of the entire source tree. If the source code has
+** been edited in any way since it was last checked in, then the last
+** four hexadecimal digits of the hash may be modified.
**
-** See also: [sqlite3_libversion()],
-** [sqlite3_libversion_number()], [sqlite3_sourceid()],
+** See also: [tdsqlite3_libversion()],
+** [tdsqlite3_libversion_number()], [tdsqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.15.2"
-#define SQLITE_VERSION_NUMBER 3015002
-#define SQLITE_SOURCE_ID "2016-11-28 19:13:37 bbd85d235f7037c6a033a9690534391ffeacecc8"
+#define SQLITE_VERSION "3.31.0"
+#define SQLITE_VERSION_NUMBER 3031000
+#define SQLITE_SOURCE_ID "2020-01-22 18:38:59 f6affdd41608946fcfcea914ece149038a8b25a62bbe719ed2561c649b86alt1"
/*
** CAPI3REF: Run-Time Library Version Numbers
-** KEYWORDS: sqlite3_version, sqlite3_sourceid
+** KEYWORDS: tdsqlite3_version tdsqlite3_sourceid
**
** These interfaces provide the same information as the [SQLITE_VERSION],
** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros
@@ -138,59 +140,64 @@ extern "C" {
** compiled with matching library and header files.
**
** <blockquote><pre>
-** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
-** assert( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)==0 );
-** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
+** assert( tdsqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
+** assert( strncmp(tdsqlite3_sourceid(),SQLITE_SOURCE_ID,80)==0 );
+** assert( strcmp(tdsqlite3_libversion(),SQLITE_VERSION)==0 );
** </pre></blockquote>)^
**
-** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION]
-** macro. ^The sqlite3_libversion() function returns a pointer to the
-** to the sqlite3_version[] string constant. The sqlite3_libversion()
+** ^The tdsqlite3_version[] string constant contains the text of [SQLITE_VERSION]
+** macro. ^The tdsqlite3_libversion() function returns a pointer to the
+** to the tdsqlite3_version[] string constant. The tdsqlite3_libversion()
** function is provided for use in DLLs since DLL users usually do not have
** direct access to string constants within the DLL. ^The
-** sqlite3_libversion_number() function returns an integer equal to
-** [SQLITE_VERSION_NUMBER]. ^The sqlite3_sourceid() function returns
+** tdsqlite3_libversion_number() function returns an integer equal to
+** [SQLITE_VERSION_NUMBER]. ^(The tdsqlite3_sourceid() function returns
** a pointer to a string constant whose value is the same as the
-** [SQLITE_SOURCE_ID] C preprocessor macro.
+** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built
+** using an edited copy of [the amalgamation], then the last four characters
+** of the hash might be different from [SQLITE_SOURCE_ID].)^
**
** See also: [sqlite_version()] and [sqlite_source_id()].
*/
-SQLITE_API SQLITE_EXTERN const char sqlite3_version[];
-SQLITE_API const char *sqlite3_libversion(void);
-SQLITE_API const char *sqlite3_sourceid(void);
-SQLITE_API int sqlite3_libversion_number(void);
+SQLITE_API SQLITE_EXTERN const char tdsqlite3_version[];
+SQLITE_API const char *tdsqlite3_libversion(void);
+SQLITE_API const char *tdsqlite3_sourceid(void);
+SQLITE_API int tdsqlite3_libversion_number(void);
/*
** CAPI3REF: Run-Time Library Compilation Options Diagnostics
**
-** ^The sqlite3_compileoption_used() function returns 0 or 1
+** ^The tdsqlite3_compileoption_used() function returns 0 or 1
** indicating whether the specified option was defined at
** compile time. ^The SQLITE_ prefix may be omitted from the
-** option name passed to sqlite3_compileoption_used().
+** option name passed to tdsqlite3_compileoption_used().
**
-** ^The sqlite3_compileoption_get() function allows iterating
+** ^The tdsqlite3_compileoption_get() function allows iterating
** over the list of options that were defined at compile time by
** returning the N-th compile time option string. ^If N is out of range,
-** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_
+** tdsqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_
** prefix is omitted from any strings returned by
-** sqlite3_compileoption_get().
+** tdsqlite3_compileoption_get().
**
-** ^Support for the diagnostic functions sqlite3_compileoption_used()
-** and sqlite3_compileoption_get() may be omitted by specifying the
+** ^Support for the diagnostic functions tdsqlite3_compileoption_used()
+** and tdsqlite3_compileoption_get() may be omitted by specifying the
** [SQLITE_OMIT_COMPILEOPTION_DIAGS] option at compile time.
**
** See also: SQL functions [sqlite_compileoption_used()] and
** [sqlite_compileoption_get()] and the [compile_options pragma].
*/
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
-SQLITE_API int sqlite3_compileoption_used(const char *zOptName);
-SQLITE_API const char *sqlite3_compileoption_get(int N);
+SQLITE_API int tdsqlite3_compileoption_used(const char *zOptName);
+SQLITE_API const char *tdsqlite3_compileoption_get(int N);
+#else
+# define tdsqlite3_compileoption_used(X) 0
+# define tdsqlite3_compileoption_get(X) ((void*)0)
#endif
/*
** CAPI3REF: Test To See If The Library Is Threadsafe
**
-** ^The sqlite3_threadsafe() function returns zero if and only if
+** ^The tdsqlite3_threadsafe() function returns zero if and only if
** SQLite was compiled with mutexing code omitted due to the
** [SQLITE_THREADSAFE] compile-time option being set to 0.
**
@@ -213,33 +220,33 @@ SQLITE_API const char *sqlite3_compileoption_get(int N);
** This interface only reports on the compile-time mutex setting
** of the [SQLITE_THREADSAFE] flag. If SQLite is compiled with
** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but
-** can be fully or partially disabled using a call to [sqlite3_config()]
+** can be fully or partially disabled using a call to [tdsqlite3_config()]
** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD],
** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the
-** sqlite3_threadsafe() function shows only the compile-time setting of
+** tdsqlite3_threadsafe() function shows only the compile-time setting of
** thread safety, not any run-time changes to that setting made by
-** sqlite3_config(). In other words, the return value from sqlite3_threadsafe()
-** is unchanged by calls to sqlite3_config().)^
+** tdsqlite3_config(). In other words, the return value from tdsqlite3_threadsafe()
+** is unchanged by calls to tdsqlite3_config().)^
**
** See the [threading mode] documentation for additional information.
*/
-SQLITE_API int sqlite3_threadsafe(void);
+SQLITE_API int tdsqlite3_threadsafe(void);
/*
** CAPI3REF: Database Connection Handle
** KEYWORDS: {database connection} {database connections}
**
** Each open SQLite database is represented by a pointer to an instance of
-** the opaque structure named "sqlite3". It is useful to think of an sqlite3
-** pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and
-** [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()]
-** and [sqlite3_close_v2()] are its destructors. There are many other
+** the opaque structure named "tdsqlite3". It is useful to think of an tdsqlite3
+** pointer as an object. The [tdsqlite3_open()], [tdsqlite3_open16()], and
+** [tdsqlite3_open_v2()] interfaces are its constructors, and [tdsqlite3_close()]
+** and [tdsqlite3_close_v2()] are its destructors. There are many other
** interfaces (such as
-** [sqlite3_prepare_v2()], [sqlite3_create_function()], and
-** [sqlite3_busy_timeout()] to name but three) that are methods on an
-** sqlite3 object.
+** [tdsqlite3_prepare_v2()], [tdsqlite3_create_function()], and
+** [tdsqlite3_busy_timeout()] to name but three) that are methods on an
+** tdsqlite3 object.
*/
-typedef struct sqlite3 sqlite3;
+typedef struct tdsqlite3 tdsqlite3;
/*
** CAPI3REF: 64-Bit Integer Types
@@ -248,18 +255,22 @@ typedef struct sqlite3 sqlite3;
** Because there is no cross-platform way to specify 64-bit integer types
** SQLite includes typedefs for 64-bit signed and unsigned integers.
**
-** The sqlite3_int64 and sqlite3_uint64 are the preferred type definitions.
+** The tdsqlite3_int64 and tdsqlite3_uint64 are the preferred type definitions.
** The sqlite_int64 and sqlite_uint64 types are supported for backwards
** compatibility only.
**
-** ^The sqlite3_int64 and sqlite_int64 types can store integer values
+** ^The tdsqlite3_int64 and sqlite_int64 types can store integer values
** between -9223372036854775808 and +9223372036854775807 inclusive. ^The
-** sqlite3_uint64 and sqlite_uint64 types can store integer values
+** tdsqlite3_uint64 and sqlite_uint64 types can store integer values
** between 0 and +18446744073709551615 inclusive.
*/
#ifdef SQLITE_INT64_TYPE
typedef SQLITE_INT64_TYPE sqlite_int64;
- typedef unsigned SQLITE_INT64_TYPE sqlite_uint64;
+# ifdef SQLITE_UINT64_TYPE
+ typedef SQLITE_UINT64_TYPE sqlite_uint64;
+# else
+ typedef unsigned SQLITE_INT64_TYPE sqlite_uint64;
+# endif
#elif defined(_MSC_VER) || defined(__BORLANDC__)
typedef __int64 sqlite_int64;
typedef unsigned __int64 sqlite_uint64;
@@ -267,116 +278,116 @@ typedef struct sqlite3 sqlite3;
typedef long long int sqlite_int64;
typedef unsigned long long int sqlite_uint64;
#endif
-typedef sqlite_int64 sqlite3_int64;
-typedef sqlite_uint64 sqlite3_uint64;
+typedef sqlite_int64 tdsqlite3_int64;
+typedef sqlite_uint64 tdsqlite3_uint64;
/*
** If compiling for a processor that lacks floating point support,
** substitute integer for floating-point.
*/
#ifdef SQLITE_OMIT_FLOATING_POINT
-# define double sqlite3_int64
+# define double tdsqlite3_int64
#endif
/*
** CAPI3REF: Closing A Database Connection
-** DESTRUCTOR: sqlite3
+** DESTRUCTOR: tdsqlite3
**
-** ^The sqlite3_close() and sqlite3_close_v2() routines are destructors
-** for the [sqlite3] object.
-** ^Calls to sqlite3_close() and sqlite3_close_v2() return [SQLITE_OK] if
-** the [sqlite3] object is successfully destroyed and all associated
+** ^The tdsqlite3_close() and tdsqlite3_close_v2() routines are destructors
+** for the [tdsqlite3] object.
+** ^Calls to tdsqlite3_close() and tdsqlite3_close_v2() return [SQLITE_OK] if
+** the [tdsqlite3] object is successfully destroyed and all associated
** resources are deallocated.
**
** ^If the database connection is associated with unfinalized prepared
-** statements or unfinished sqlite3_backup objects then sqlite3_close()
+** statements or unfinished tdsqlite3_backup objects then tdsqlite3_close()
** will leave the database connection open and return [SQLITE_BUSY].
-** ^If sqlite3_close_v2() is called with unfinalized prepared statements
-** and/or unfinished sqlite3_backups, then the database connection becomes
+** ^If tdsqlite3_close_v2() is called with unfinalized prepared statements
+** and/or unfinished tdsqlite3_backups, then the database connection becomes
** an unusable "zombie" which will automatically be deallocated when the
-** last prepared statement is finalized or the last sqlite3_backup is
-** finished. The sqlite3_close_v2() interface is intended for use with
+** last prepared statement is finalized or the last tdsqlite3_backup is
+** finished. The tdsqlite3_close_v2() interface is intended for use with
** host languages that are garbage collected, and where the order in which
** destructors are called is arbitrary.
**
-** Applications should [sqlite3_finalize | finalize] all [prepared statements],
-** [sqlite3_blob_close | close] all [BLOB handles], and
-** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated
-** with the [sqlite3] object prior to attempting to close the object. ^If
-** sqlite3_close_v2() is called on a [database connection] that still has
+** Applications should [tdsqlite3_finalize | finalize] all [prepared statements],
+** [tdsqlite3_blob_close | close] all [BLOB handles], and
+** [tdsqlite3_backup_finish | finish] all [tdsqlite3_backup] objects associated
+** with the [tdsqlite3] object prior to attempting to close the object. ^If
+** tdsqlite3_close_v2() is called on a [database connection] that still has
** outstanding [prepared statements], [BLOB handles], and/or
-** [sqlite3_backup] objects then it returns [SQLITE_OK] and the deallocation
+** [tdsqlite3_backup] objects then it returns [SQLITE_OK] and the deallocation
** of resources is deferred until all [prepared statements], [BLOB handles],
-** and [sqlite3_backup] objects are also destroyed.
+** and [tdsqlite3_backup] objects are also destroyed.
**
-** ^If an [sqlite3] object is destroyed while a transaction is open,
+** ^If an [tdsqlite3] object is destroyed while a transaction is open,
** the transaction is automatically rolled back.
**
-** The C parameter to [sqlite3_close(C)] and [sqlite3_close_v2(C)]
+** The C parameter to [tdsqlite3_close(C)] and [tdsqlite3_close_v2(C)]
** must be either a NULL
-** pointer or an [sqlite3] object pointer obtained
-** from [sqlite3_open()], [sqlite3_open16()], or
-** [sqlite3_open_v2()], and not previously closed.
-** ^Calling sqlite3_close() or sqlite3_close_v2() with a NULL pointer
+** pointer or an [tdsqlite3] object pointer obtained
+** from [tdsqlite3_open()], [tdsqlite3_open16()], or
+** [tdsqlite3_open_v2()], and not previously closed.
+** ^Calling tdsqlite3_close() or tdsqlite3_close_v2() with a NULL pointer
** argument is a harmless no-op.
*/
-SQLITE_API int sqlite3_close(sqlite3*);
-SQLITE_API int sqlite3_close_v2(sqlite3*);
+SQLITE_API int tdsqlite3_close(tdsqlite3*);
+SQLITE_API int tdsqlite3_close_v2(tdsqlite3*);
/*
** The type for a callback function.
** This is legacy and deprecated. It is included for historical
** compatibility and is not documented.
*/
-typedef int (*sqlite3_callback)(void*,int,char**, char**);
+typedef int (*tdsqlite3_callback)(void*,int,char**, char**);
/*
** CAPI3REF: One-Step Query Execution Interface
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** The sqlite3_exec() interface is a convenience wrapper around
-** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()],
+** The tdsqlite3_exec() interface is a convenience wrapper around
+** [tdsqlite3_prepare_v2()], [tdsqlite3_step()], and [tdsqlite3_finalize()],
** that allows an application to run multiple statements of SQL
** without having to use a lot of C code.
**
-** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded,
+** ^The tdsqlite3_exec() interface runs zero or more UTF-8 encoded,
** semicolon-separate SQL statements passed into its 2nd argument,
** in the context of the [database connection] passed in as its 1st
** argument. ^If the callback function of the 3rd argument to
-** sqlite3_exec() is not NULL, then it is invoked for each result row
+** tdsqlite3_exec() is not NULL, then it is invoked for each result row
** coming out of the evaluated SQL statements. ^The 4th argument to
-** sqlite3_exec() is relayed through to the 1st argument of each
-** callback invocation. ^If the callback pointer to sqlite3_exec()
+** tdsqlite3_exec() is relayed through to the 1st argument of each
+** callback invocation. ^If the callback pointer to tdsqlite3_exec()
** is NULL, then no callback is ever invoked and result rows are
** ignored.
**
** ^If an error occurs while evaluating the SQL statements passed into
-** sqlite3_exec(), then execution of the current statement stops and
-** subsequent statements are skipped. ^If the 5th parameter to sqlite3_exec()
+** tdsqlite3_exec(), then execution of the current statement stops and
+** subsequent statements are skipped. ^If the 5th parameter to tdsqlite3_exec()
** is not NULL then any error message is written into memory obtained
-** from [sqlite3_malloc()] and passed back through the 5th parameter.
-** To avoid memory leaks, the application should invoke [sqlite3_free()]
+** from [tdsqlite3_malloc()] and passed back through the 5th parameter.
+** To avoid memory leaks, the application should invoke [tdsqlite3_free()]
** on error message strings returned through the 5th parameter of
-** sqlite3_exec() after the error message string is no longer needed.
-** ^If the 5th parameter to sqlite3_exec() is not NULL and no errors
-** occur, then sqlite3_exec() sets the pointer in its 5th parameter to
+** tdsqlite3_exec() after the error message string is no longer needed.
+** ^If the 5th parameter to tdsqlite3_exec() is not NULL and no errors
+** occur, then tdsqlite3_exec() sets the pointer in its 5th parameter to
** NULL before returning.
**
-** ^If an sqlite3_exec() callback returns non-zero, the sqlite3_exec()
+** ^If an tdsqlite3_exec() callback returns non-zero, the tdsqlite3_exec()
** routine returns SQLITE_ABORT without invoking the callback again and
** without running any subsequent SQL statements.
**
-** ^The 2nd argument to the sqlite3_exec() callback function is the
-** number of columns in the result. ^The 3rd argument to the sqlite3_exec()
+** ^The 2nd argument to the tdsqlite3_exec() callback function is the
+** number of columns in the result. ^The 3rd argument to the tdsqlite3_exec()
** callback is an array of pointers to strings obtained as if from
-** [sqlite3_column_text()], one for each column. ^If an element of a
+** [tdsqlite3_column_text()], one for each column. ^If an element of a
** result row is NULL then the corresponding string pointer for the
-** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the
-** sqlite3_exec() callback is an array of pointers to strings where each
+** tdsqlite3_exec() callback is a NULL pointer. ^The 4th argument to the
+** tdsqlite3_exec() callback is an array of pointers to strings where each
** entry represents the name of corresponding result column as obtained
-** from [sqlite3_column_name()].
+** from [tdsqlite3_column_name()].
**
-** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer
+** ^If the 2nd parameter to tdsqlite3_exec() is a NULL pointer, a pointer
** to an empty string, or a pointer that contains only whitespace and/or
** SQL comments, then no SQL statements are evaluated and the database
** is not changed.
@@ -384,16 +395,16 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** Restrictions:
**
** <ul>
-** <li> The application must ensure that the 1st parameter to sqlite3_exec()
+** <li> The application must ensure that the 1st parameter to tdsqlite3_exec()
** is a valid and open [database connection].
** <li> The application must not close the [database connection] specified by
-** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running.
+** the 1st parameter to tdsqlite3_exec() while tdsqlite3_exec() is running.
** <li> The application must not modify the SQL statement text passed into
-** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running.
+** the 2nd parameter of tdsqlite3_exec() while tdsqlite3_exec() is running.
** </ul>
*/
-SQLITE_API int sqlite3_exec(
- sqlite3*, /* An open database */
+SQLITE_API int tdsqlite3_exec(
+ tdsqlite3*, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
@@ -413,7 +424,7 @@ SQLITE_API int sqlite3_exec(
*/
#define SQLITE_OK 0 /* Successful result */
/* beginning-of-error-codes */
-#define SQLITE_ERROR 1 /* SQL error or missing database */
+#define SQLITE_ERROR 1 /* Generic error */
#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */
#define SQLITE_PERM 3 /* Access permission denied */
#define SQLITE_ABORT 4 /* Callback routine requested an abort */
@@ -421,14 +432,14 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_LOCKED 6 /* A table in the database is locked */
#define SQLITE_NOMEM 7 /* A malloc() failed */
#define SQLITE_READONLY 8 /* Attempt to write a readonly database */
-#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/
+#define SQLITE_INTERRUPT 9 /* Operation terminated by tdsqlite3_interrupt()*/
#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */
#define SQLITE_CORRUPT 11 /* The database disk image is malformed */
-#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */
+#define SQLITE_NOTFOUND 12 /* Unknown opcode in tdsqlite3_file_control() */
#define SQLITE_FULL 13 /* Insertion failed because database is full */
#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
#define SQLITE_PROTOCOL 15 /* Database lock protocol error */
-#define SQLITE_EMPTY 16 /* Database is empty */
+#define SQLITE_EMPTY 16 /* Internal use only */
#define SQLITE_SCHEMA 17 /* The database schema changed */
#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */
#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
@@ -436,13 +447,13 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_MISUSE 21 /* Library used incorrectly */
#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */
#define SQLITE_AUTH 23 /* Authorization denied */
-#define SQLITE_FORMAT 24 /* Auxiliary database format error */
-#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */
+#define SQLITE_FORMAT 24 /* Not used */
+#define SQLITE_RANGE 25 /* 2nd parameter to tdsqlite3_bind out of range */
#define SQLITE_NOTADB 26 /* File opened that is not a database file */
-#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */
-#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */
-#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
-#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
+#define SQLITE_NOTICE 27 /* Notifications from tdsqlite3_log() */
+#define SQLITE_WARNING 28 /* Warnings from tdsqlite3_log() */
+#define SQLITE_ROW 100 /* tdsqlite3_step() has another row ready */
+#define SQLITE_DONE 101 /* tdsqlite3_step() has finished executing */
/* end-of-error-codes */
/*
@@ -458,10 +469,13 @@ SQLITE_API int sqlite3_exec(
** support for additional result codes that provide more detailed information
** about errors. These [extended result codes] are enabled or disabled
** on a per database connection basis using the
-** [sqlite3_extended_result_codes()] API. Or, the extended code for
+** [tdsqlite3_extended_result_codes()] API. Or, the extended code for
** the most recent error can be obtained using
-** [sqlite3_extended_errcode()].
+** [tdsqlite3_extended_errcode()].
*/
+#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8))
+#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8))
+#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8))
#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
@@ -490,18 +504,27 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8))
#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8))
#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8))
+#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8))
+#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8))
+#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
+#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8))
#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8))
#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8))
#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8))
#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8))
+#define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5<<8)) /* Not Used */
+#define SQLITE_CANTOPEN_SYMLINK (SQLITE_CANTOPEN | (6<<8))
#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8))
+#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8))
#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8))
#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8))
#define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8))
#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4<<8))
+#define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5<<8))
+#define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6<<8))
#define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8))
#define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1<<8))
#define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2<<8))
@@ -513,27 +536,29 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8))
#define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8))
#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8))
+#define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8))
#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8))
#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8))
#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8))
#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8))
#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8))
+#define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8))
/*
** CAPI3REF: Flags For File Open Operations
**
** These bit values are intended for use in the
-** 3rd parameter to the [sqlite3_open_v2()] interface and
-** in the 4th parameter to the [sqlite3_vfs.xOpen] method.
+** 3rd parameter to the [tdsqlite3_open_v2()] interface and
+** in the 4th parameter to the [tdsqlite3_vfs.xOpen] method.
*/
-#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */
+#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for tdsqlite3_open_v2() */
#define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */
#define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */
#define SQLITE_OPEN_AUTOPROXY 0x00000020 /* VFS only */
-#define SQLITE_OPEN_URI 0x00000040 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for sqlite3_open_v2() */
+#define SQLITE_OPEN_URI 0x00000040 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for tdsqlite3_open_v2() */
#define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */
#define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */
#define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */
@@ -541,21 +566,22 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */
#define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */
#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */
-#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */
-#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */
+#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for tdsqlite3_open_v2() */
+#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for tdsqlite3_open_v2() */
#define SQLITE_OPEN_WAL 0x00080000 /* VFS only */
+#define SQLITE_OPEN_NOFOLLOW 0x01000000 /* Ok for tdsqlite3_open_v2() */
/* Reserved: 0x00F00000 */
/*
** CAPI3REF: Device Characteristics
**
-** The xDeviceCharacteristics method of the [sqlite3_io_methods]
+** The xDeviceCharacteristics method of the [tdsqlite3_io_methods]
** object returns an integer which is a vector of these
** bit values expressing I/O characteristics of the mass storage
-** device that holds the file that the [sqlite3_io_methods]
+** device that holds the file that the [tdsqlite3_io_methods]
** refers to.
**
** The SQLITE_IOCAP_ATOMIC property means that all writes of
@@ -572,10 +598,15 @@ SQLITE_API int sqlite3_exec(
** file that were written at the application level might have changed
** and that adjacent bytes, even bytes within the same sector are
** guaranteed to be unchanged. The SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN
-** flag indicate that a file cannot be deleted when open. The
+** flag indicates that a file cannot be deleted when open. The
** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on
** read-only media and cannot be changed even by processes with
** elevated privileges.
+**
+** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
+** filesystem supports doing multiple write operations atomically when those
+** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
+** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
*/
#define SQLITE_IOCAP_ATOMIC 0x00000001
#define SQLITE_IOCAP_ATOMIC512 0x00000002
@@ -591,13 +622,14 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
#define SQLITE_IOCAP_IMMUTABLE 0x00002000
+#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
/*
** CAPI3REF: File Locking Levels
**
** SQLite uses one of these integer values as the second
** argument to calls it makes to the xLock() and xUnlock() methods
-** of an [sqlite3_io_methods] object.
+** of an [tdsqlite3_io_methods] object.
*/
#define SQLITE_LOCK_NONE 0
#define SQLITE_LOCK_SHARED 1
@@ -609,7 +641,7 @@ SQLITE_API int sqlite3_exec(
** CAPI3REF: Synchronization Type Flags
**
** When SQLite invokes the xSync() method of an
-** [sqlite3_io_methods] object it uses a combination of
+** [tdsqlite3_io_methods] object it uses a combination of
** these integer values as the second argument.
**
** When the SQLITE_SYNC_DATAONLY flag is used, it means that the
@@ -638,33 +670,33 @@ SQLITE_API int sqlite3_exec(
/*
** CAPI3REF: OS Interface Open File Handle
**
-** An [sqlite3_file] object represents an open file in the
-** [sqlite3_vfs | OS interface layer]. Individual OS interface
+** An [tdsqlite3_file] object represents an open file in the
+** [tdsqlite3_vfs | OS interface layer]. Individual OS interface
** implementations will
** want to subclass this object by appending additional fields
** for their own use. The pMethods entry is a pointer to an
-** [sqlite3_io_methods] object that defines methods for performing
+** [tdsqlite3_io_methods] object that defines methods for performing
** I/O operations on the open file.
*/
-typedef struct sqlite3_file sqlite3_file;
-struct sqlite3_file {
- const struct sqlite3_io_methods *pMethods; /* Methods for an open file */
+typedef struct tdsqlite3_file tdsqlite3_file;
+struct tdsqlite3_file {
+ const struct tdsqlite3_io_methods *pMethods; /* Methods for an open file */
};
/*
** CAPI3REF: OS Interface File Virtual Methods Object
**
-** Every file opened by the [sqlite3_vfs.xOpen] method populates an
-** [sqlite3_file] object (or, more commonly, a subclass of the
-** [sqlite3_file] object) with a pointer to an instance of this object.
+** Every file opened by the [tdsqlite3_vfs.xOpen] method populates an
+** [tdsqlite3_file] object (or, more commonly, a subclass of the
+** [tdsqlite3_file] object) with a pointer to an instance of this object.
** This object defines the methods used to perform various operations
-** against the open file represented by the [sqlite3_file] object.
+** against the open file represented by the [tdsqlite3_file] object.
**
-** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element
-** to a non-NULL pointer, then the sqlite3_io_methods.xClose method
-** may be invoked even if the [sqlite3_vfs.xOpen] reported that it failed. The
-** only way to prevent a call to xClose following a failed [sqlite3_vfs.xOpen]
-** is for the [sqlite3_vfs.xOpen] to set the sqlite3_file.pMethods element
+** If the [tdsqlite3_vfs.xOpen] method sets the tdsqlite3_file.pMethods element
+** to a non-NULL pointer, then the tdsqlite3_io_methods.xClose method
+** may be invoked even if the [tdsqlite3_vfs.xOpen] reported that it failed. The
+** only way to prevent a call to xClose following a failed [tdsqlite3_vfs.xOpen]
+** is for the [tdsqlite3_vfs.xOpen] to set the tdsqlite3_file.pMethods element
** to NULL.
**
** The flags argument to xSync may be one of [SQLITE_SYNC_NORMAL] or
@@ -689,7 +721,7 @@ struct sqlite3_file {
**
** The xFileControl() method is a generic interface that allows custom
** VFS implementations to directly control an open file using the
-** [sqlite3_file_control()] interface. The second "op" argument is an
+** [tdsqlite3_file_control()] interface. The second "op" argument is an
** integer opcode. The third argument is a generic pointer intended to
** point to a structure that may contain arguments or space in which to
** write return values. Potential uses for xFileControl() might be
@@ -722,6 +754,10 @@ struct sqlite3_file {
** <li> [SQLITE_IOCAP_ATOMIC64K]
** <li> [SQLITE_IOCAP_SAFE_APPEND]
** <li> [SQLITE_IOCAP_SEQUENTIAL]
+** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
+** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
+** <li> [SQLITE_IOCAP_IMMUTABLE]
+** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
** </ul>
**
** The SQLITE_IOCAP_ATOMIC property means that all writes of
@@ -741,29 +777,29 @@ struct sqlite3_file {
** failure to zero-fill short reads will eventually lead to
** database corruption.
*/
-typedef struct sqlite3_io_methods sqlite3_io_methods;
-struct sqlite3_io_methods {
+typedef struct tdsqlite3_io_methods tdsqlite3_io_methods;
+struct tdsqlite3_io_methods {
int iVersion;
- int (*xClose)(sqlite3_file*);
- int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
- int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
- int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
- int (*xSync)(sqlite3_file*, int flags);
- int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
- int (*xLock)(sqlite3_file*, int);
- int (*xUnlock)(sqlite3_file*, int);
- int (*xCheckReservedLock)(sqlite3_file*, int *pResOut);
- int (*xFileControl)(sqlite3_file*, int op, void *pArg);
- int (*xSectorSize)(sqlite3_file*);
- int (*xDeviceCharacteristics)(sqlite3_file*);
+ int (*xClose)(tdsqlite3_file*);
+ int (*xRead)(tdsqlite3_file*, void*, int iAmt, tdsqlite3_int64 iOfst);
+ int (*xWrite)(tdsqlite3_file*, const void*, int iAmt, tdsqlite3_int64 iOfst);
+ int (*xTruncate)(tdsqlite3_file*, tdsqlite3_int64 size);
+ int (*xSync)(tdsqlite3_file*, int flags);
+ int (*xFileSize)(tdsqlite3_file*, tdsqlite3_int64 *pSize);
+ int (*xLock)(tdsqlite3_file*, int);
+ int (*xUnlock)(tdsqlite3_file*, int);
+ int (*xCheckReservedLock)(tdsqlite3_file*, int *pResOut);
+ int (*xFileControl)(tdsqlite3_file*, int op, void *pArg);
+ int (*xSectorSize)(tdsqlite3_file*);
+ int (*xDeviceCharacteristics)(tdsqlite3_file*);
/* Methods above are valid for version 1 */
- int (*xShmMap)(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
- int (*xShmLock)(sqlite3_file*, int offset, int n, int flags);
- void (*xShmBarrier)(sqlite3_file*);
- int (*xShmUnmap)(sqlite3_file*, int deleteFlag);
+ int (*xShmMap)(tdsqlite3_file*, int iPg, int pgsz, int, void volatile**);
+ int (*xShmLock)(tdsqlite3_file*, int offset, int n, int flags);
+ void (*xShmBarrier)(tdsqlite3_file*);
+ int (*xShmUnmap)(tdsqlite3_file*, int deleteFlag);
/* Methods above are valid for version 2 */
- int (*xFetch)(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
- int (*xUnfetch)(sqlite3_file*, sqlite3_int64 iOfst, void *p);
+ int (*xFetch)(tdsqlite3_file*, tdsqlite3_int64 iOfst, int iAmt, void **pp);
+ int (*xUnfetch)(tdsqlite3_file*, tdsqlite3_int64 iOfst, void *p);
/* Methods above are valid for version 3 */
/* Additional methods may be added in future releases */
};
@@ -773,7 +809,7 @@ struct sqlite3_io_methods {
** KEYWORDS: {file control opcodes} {file control opcode}
**
** These integer constants are opcodes for the xFileControl method
-** of the [sqlite3_io_methods] object and for the [sqlite3_file_control()]
+** of the [tdsqlite3_io_methods] object and for the [tdsqlite3_file_control()]
** interface.
**
** <ul>
@@ -794,10 +830,19 @@ struct sqlite3_io_methods {
** file space based on this hint in order to help writes to the database
** file run faster.
**
+** <li>[[SQLITE_FCNTL_SIZE_LIMIT]]
+** The [SQLITE_FCNTL_SIZE_LIMIT] opcode is used by in-memory VFS that
+** implements [tdsqlite3_deserialize()] to set an upper bound on the size
+** of the in-memory database. The argument is a pointer to a [tdsqlite3_int64].
+** If the integer pointed to is negative, then it is filled in with the
+** current limit. Otherwise the limit is set to the larger of the value
+** of the integer pointed to and the current database size. The integer
+** pointed to is set to the new limit.
+**
** <li>[[SQLITE_FCNTL_CHUNK_SIZE]]
** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS
** extends and truncates the database file in chunks of a size specified
-** by the user. The fourth argument to [sqlite3_file_control()] should
+** by the user. The fourth argument to [tdsqlite3_file_control()] should
** point to an integer (type int) containing the new chunk-size to use
** for the nominated database. Allocating database file space in large
** chunks (say 1MB at a time), may reduce file-system fragmentation and
@@ -805,12 +850,12 @@ struct sqlite3_io_methods {
**
** <li>[[SQLITE_FCNTL_FILE_POINTER]]
** The [SQLITE_FCNTL_FILE_POINTER] opcode is used to obtain a pointer
-** to the [sqlite3_file] object associated with a particular database
+** to the [tdsqlite3_file] object associated with a particular database
** connection. See also [SQLITE_FCNTL_JOURNAL_POINTER].
**
** <li>[[SQLITE_FCNTL_JOURNAL_POINTER]]
** The [SQLITE_FCNTL_JOURNAL_POINTER] opcode is used to obtain a pointer
-** to the [sqlite3_file] object associated with the journal file (either
+** to the [tdsqlite3_file] object associated with the journal file (either
** the [rollback journal] or the [write-ahead log]) for a particular database
** connection. See also [SQLITE_FCNTL_FILE_POINTER].
**
@@ -828,7 +873,7 @@ struct sqlite3_io_methods {
** as part of a multi-database commit, the argument points to a nul-terminated
** string containing the transactions master-journal file name. VFSes that
** do not need this signal should silently ignore this opcode. Applications
-** should not call [sqlite3_file_control()] with this opcode as doing so may
+** should not call [tdsqlite3_file_control()] with this opcode as doing so may
** disrupt the operation of the specialized VFSes that do require it.
**
** <li>[[SQLITE_FCNTL_COMMIT_PHASETWO]]
@@ -836,7 +881,7 @@ struct sqlite3_io_methods {
** and sent to the VFS after a transaction has been committed immediately
** but before the database is unlocked. VFSes that do not need this signal
** should silently ignore this opcode. Applications should not call
-** [sqlite3_file_control()] with this opcode as doing so may disrupt the
+** [tdsqlite3_file_control()] with this opcode as doing so may disrupt the
** operation of the specialized VFSes that do require it.
**
** <li>[[SQLITE_FCNTL_WIN32_AV_RETRY]]
@@ -850,7 +895,7 @@ struct sqlite3_io_methods {
** opcode allows these two values (10 retries and 25 milliseconds of delay)
** to be adjusted. The values are changed for all database connections
** within the same process. The argument is a pointer to an array of two
-** integers where the first integer i the new retry count and the second
+** integers where the first integer is the new retry count and the second
** integer is the delay. If either integer is negative, then the setting
** is not changed but instead the prior value of that setting is written
** into the array entry, allowing the current retry settings to be
@@ -859,14 +904,15 @@ struct sqlite3_io_methods {
** <li>[[SQLITE_FCNTL_PERSIST_WAL]]
** ^The [SQLITE_FCNTL_PERSIST_WAL] opcode is used to set or query the
** persistent [WAL | Write Ahead Log] setting. By default, the auxiliary
-** write ahead log and shared memory files used for transaction control
+** write ahead log ([WAL file]) and shared memory
+** files used for transaction control
** are automatically deleted when the latest connection to the database
** closes. Setting persistent WAL mode causes those files to persist after
** close. Persisting the files is useful when other processes that do not
** have write permission on the directory containing the database file want
** to read the database file, as the WAL and shared memory files must exist
** in order for the database to be readable. The fourth parameter to
-** [sqlite3_file_control()] for this opcode should be a pointer to an integer.
+** [tdsqlite3_file_control()] for this opcode should be a pointer to an integer.
** That integer is 0 to disable persistent WAL mode or 1 to enable persistent
** WAL mode. If the integer is -1, then it is overwritten with the current
** WAL persistence setting.
@@ -876,7 +922,7 @@ struct sqlite3_io_methods {
** persistent "powersafe-overwrite" or "PSOW" setting. The PSOW setting
** determines the [SQLITE_IOCAP_POWERSAFE_OVERWRITE] bit of the
** xDeviceCharacteristics methods. The fourth parameter to
-** [sqlite3_file_control()] for this opcode should be a pointer to an integer.
+** [tdsqlite3_file_control()] for this opcode should be a pointer to an integer.
** That integer is 0 to disable zero-damage mode or 1 to enable zero-damage
** mode. If the integer is -1, then it is overwritten with the current
** zero-damage mode setting.
@@ -891,8 +937,8 @@ struct sqlite3_io_methods {
** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of
** all [VFSes] in the VFS stack. The names are of all VFS shims and the
** final bottom-level VFS are written into memory obtained from
-** [sqlite3_malloc()] and the result is stored in the char* variable
-** that the fourth parameter of [sqlite3_file_control()] points to.
+** [tdsqlite3_malloc()] and the result is stored in the char* variable
+** that the fourth parameter of [tdsqlite3_file_control()] points to.
** The caller is responsible for freeing the memory when done. As with
** all file-control actions, there is no guarantee that this will actually
** do anything. Callers should initialize the char* variable to a NULL
@@ -902,22 +948,22 @@ struct sqlite3_io_methods {
** <li>[[SQLITE_FCNTL_VFS_POINTER]]
** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level
** [VFSes] currently in use. ^(The argument X in
-** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be
-** of type "[sqlite3_vfs] **". This opcodes will set *X
+** tdsqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be
+** of type "[tdsqlite3_vfs] **". This opcodes will set *X
** to a pointer to the top-level VFS.)^
** ^When there are multiple VFS shims in the stack, this opcode finds the
** upper-most shim only.
**
** <li>[[SQLITE_FCNTL_PRAGMA]]
** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA]
-** file control is sent to the open [sqlite3_file] object corresponding
+** file control is sent to the open [tdsqlite3_file] object corresponding
** to the database file to which the pragma statement refers. ^The argument
** to the [SQLITE_FCNTL_PRAGMA] file control is an array of
** pointers to strings (char**) in which the second element of the array
** is the name of the pragma and the third element is the argument to the
** pragma or NULL if the pragma has no argument. ^The handler for an
** [SQLITE_FCNTL_PRAGMA] file control can optionally make the first element
-** of the char** argument point to a string obtained from [sqlite3_mprintf()]
+** of the char** argument point to a string obtained from [tdsqlite3_mprintf()]
** or the equivalent and that string will become the result of the pragma or
** the error message if the pragma fails. ^If the
** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal
@@ -937,27 +983,27 @@ struct sqlite3_io_methods {
** ^The [SQLITE_FCNTL_BUSYHANDLER]
** file-control may be invoked by SQLite on the database file handle
** shortly after it is opened in order to provide a custom VFS with access
-** to the connections busy-handler callback. The argument is of type (void **)
+** to the connection's busy-handler callback. The argument is of type (void**)
** - an array of two (void *) values. The first (void *) actually points
-** to a function of type (int (*)(void *)). In order to invoke the connections
+** to a function of type (int (*)(void *)). In order to invoke the connection's
** busy-handler, this function should be invoked with the second (void *) in
** the array as the only argument. If it returns non-zero, then the operation
** should be retried. If it returns zero, the custom VFS should abandon the
** current operation.
**
** <li>[[SQLITE_FCNTL_TEMPFILENAME]]
-** ^Application can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control
+** ^Applications can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control
** to have SQLite generate a
** temporary filename using the same algorithm that is followed to generate
** temporary filenames for TEMP tables and other internal uses. The
** argument should be a char** which will be filled with the filename
-** written into memory obtained from [sqlite3_malloc()]. The caller should
-** invoke [sqlite3_free()] on the result to avoid a memory leak.
+** written into memory obtained from [tdsqlite3_malloc()]. The caller should
+** invoke [tdsqlite3_free()] on the result to avoid a memory leak.
**
** <li>[[SQLITE_FCNTL_MMAP_SIZE]]
** The [SQLITE_FCNTL_MMAP_SIZE] file control is used to query or set the
** maximum number of bytes that will be used for memory-mapped I/O.
-** The argument is a pointer to a value of type sqlite3_int64 that
+** The argument is a pointer to a value of type tdsqlite3_int64 that
** is an advisory maximum number of bytes in the file to memory map. The
** pointer is overwritten with the old value. The limit is not changed if
** the value originally pointed to is negative, and so the current limit
@@ -1005,6 +1051,72 @@ struct sqlite3_io_methods {
** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by
** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for
** this opcode.
+**
+** <li>[[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]]
+** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then
+** the file descriptor is placed in "batch write mode", which
+** means all subsequent write operations will be deferred and done
+** atomically at the next [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. Systems
+** that do not support batch atomic writes will return SQLITE_NOTFOUND.
+** ^Following a successful SQLITE_FCNTL_BEGIN_ATOMIC_WRITE and prior to
+** the closing [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] or
+** [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE], SQLite will make
+** no VFS interface calls on the same [tdsqlite3_file] file descriptor
+** except for calls to the xWrite method and the xFileControl method
+** with [SQLITE_FCNTL_SIZE_HINT].
+**
+** <li>[[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]]
+** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write
+** operations since the previous successful call to
+** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically.
+** This file control returns [SQLITE_OK] if and only if the writes were
+** all performed successfully and have been committed to persistent storage.
+** ^Regardless of whether or not it is successful, this file control takes
+** the file descriptor out of batch write mode so that all subsequent
+** write operations are independent.
+** ^SQLite will never invoke SQLITE_FCNTL_COMMIT_ATOMIC_WRITE without
+** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE].
+**
+** <li>[[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]]
+** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write
+** operations since the previous successful call to
+** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back.
+** ^This file control takes the file descriptor out of batch write mode
+** so that all subsequent write operations are independent.
+** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without
+** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE].
+**
+** <li>[[SQLITE_FCNTL_LOCK_TIMEOUT]]
+** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode causes attempts to obtain
+** a file lock using the xLock or xShmLock methods of the VFS to wait
+** for up to M milliseconds before failing, where M is the single
+** unsigned integer parameter.
+**
+** <li>[[SQLITE_FCNTL_DATA_VERSION]]
+** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
+** a database file. The argument is a pointer to a 32-bit unsigned integer.
+** The "data version" for the pager is written into the pointer. The
+** "data version" changes whenever any change occurs to the corresponding
+** database file, either through SQL statements on the same database
+** connection or through transactions committed by separate database
+** connections possibly in other processes. The [tdsqlite3_total_changes()]
+** interface can be used to find if any database on the connection has changed,
+** but that interface responds to changes on TEMP as well as MAIN and does
+** not provide a mechanism to detect changes to MAIN only. Also, the
+** [tdsqlite3_total_changes()] interface responds to internal changes only and
+** omits changes made by other database connections. The
+** [PRAGMA data_version] command provides a mechanism to detect changes to
+** a single attached database that occur due to other database connections,
+** but omits changes implemented by the database connection on which it is
+** called. This file control is the only mechanism to detect changes that
+** happen either internally or externally and that are associated with
+** a particular attached database.
+**
+** <li>[[SQLITE_FCNTL_CKPT_DONE]]
+** The [SQLITE_FCNTL_CKPT_DONE] opcode is invoked from within a checkpoint
+** in wal mode after the client has finished copying pages from the wal
+** file to the database file, but before the *-shm file is updated to
+** record the fact that the pages have been checkpointed.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE 1
@@ -1035,6 +1147,14 @@ struct sqlite3_io_methods {
#define SQLITE_FCNTL_VFS_POINTER 27
#define SQLITE_FCNTL_JOURNAL_POINTER 28
#define SQLITE_FCNTL_WIN32_GET_HANDLE 29
+#define SQLITE_FCNTL_PDB 30
+#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31
+#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32
+#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33
+#define SQLITE_FCNTL_LOCK_TIMEOUT 34
+#define SQLITE_FCNTL_DATA_VERSION 35
+#define SQLITE_FCNTL_SIZE_LIMIT 36
+#define SQLITE_FCNTL_CKPT_DONE 37
/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
@@ -1045,61 +1165,67 @@ struct sqlite3_io_methods {
/*
** CAPI3REF: Mutex Handle
**
-** The mutex module within SQLite defines [sqlite3_mutex] to be an
+** The mutex module within SQLite defines [tdsqlite3_mutex] to be an
** abstract type for a mutex object. The SQLite core never looks
-** at the internal representation of an [sqlite3_mutex]. It only
-** deals with pointers to the [sqlite3_mutex] object.
+** at the internal representation of an [tdsqlite3_mutex]. It only
+** deals with pointers to the [tdsqlite3_mutex] object.
**
-** Mutexes are created using [sqlite3_mutex_alloc()].
+** Mutexes are created using [tdsqlite3_mutex_alloc()].
*/
-typedef struct sqlite3_mutex sqlite3_mutex;
+typedef struct tdsqlite3_mutex tdsqlite3_mutex;
/*
** CAPI3REF: Loadable Extension Thunk
**
-** A pointer to the opaque sqlite3_api_routines structure is passed as
+** A pointer to the opaque tdsqlite3_api_routines structure is passed as
** the third parameter to entry points of [loadable extensions]. This
** structure must be typedefed in order to work around compiler warnings
** on some platforms.
*/
-typedef struct sqlite3_api_routines sqlite3_api_routines;
+typedef struct tdsqlite3_api_routines tdsqlite3_api_routines;
/*
** CAPI3REF: OS Interface Object
**
-** An instance of the sqlite3_vfs object defines the interface between
+** An instance of the tdsqlite3_vfs object defines the interface between
** the SQLite core and the underlying operating system. The "vfs"
** in the name of the object stands for "virtual file system". See
** the [VFS | VFS documentation] for further information.
**
-** The value of the iVersion field is initially 1 but may be larger in
-** future versions of SQLite. Additional fields may be appended to this
-** object when the iVersion value is increased. Note that the structure
-** of the sqlite3_vfs object changes in the transaction between
-** SQLite version 3.5.9 and 3.6.0 and yet the iVersion field was not
-** modified.
-**
-** The szOsFile field is the size of the subclassed [sqlite3_file]
+** The VFS interface is sometimes extended by adding new methods onto
+** the end. Each time such an extension occurs, the iVersion field
+** is incremented. The iVersion value started out as 1 in
+** SQLite [version 3.5.0] on [dateof:3.5.0], then increased to 2
+** with SQLite [version 3.7.0] on [dateof:3.7.0], and then increased
+** to 3 with SQLite [version 3.7.6] on [dateof:3.7.6]. Additional fields
+** may be appended to the tdsqlite3_vfs object and the iVersion value
+** may increase again in future versions of SQLite.
+** Note that due to an oversight, the structure
+** of the tdsqlite3_vfs object changed in the transition from
+** SQLite [version 3.5.9] to [version 3.6.0] on [dateof:3.6.0]
+** and yet the iVersion field was not increased.
+**
+** The szOsFile field is the size of the subclassed [tdsqlite3_file]
** structure used by this VFS. mxPathname is the maximum length of
** a pathname in this VFS.
**
-** Registered sqlite3_vfs objects are kept on a linked list formed by
-** the pNext pointer. The [sqlite3_vfs_register()]
-** and [sqlite3_vfs_unregister()] interfaces manage this list
-** in a thread-safe way. The [sqlite3_vfs_find()] interface
+** Registered tdsqlite3_vfs objects are kept on a linked list formed by
+** the pNext pointer. The [tdsqlite3_vfs_register()]
+** and [tdsqlite3_vfs_unregister()] interfaces manage this list
+** in a thread-safe way. The [tdsqlite3_vfs_find()] interface
** searches the list. Neither the application code nor the VFS
** implementation should use the pNext pointer.
**
-** The pNext field is the only field in the sqlite3_vfs
+** The pNext field is the only field in the tdsqlite3_vfs
** structure that SQLite will ever modify. SQLite will only access
** or modify this field while holding a particular static mutex.
-** The application should never modify anything within the sqlite3_vfs
+** The application should never modify anything within the tdsqlite3_vfs
** object once the object has been registered.
**
** The zName field holds the name of the VFS module. The name must
** be unique across all VFS modules.
**
-** [[sqlite3_vfs.xOpen]]
+** [[tdsqlite3_vfs.xOpen]]
** ^SQLite guarantees that the zFilename parameter to xOpen
** is either a NULL pointer or string obtained
** from xFullPathname() with an optional suffix added.
@@ -1109,7 +1235,7 @@ typedef struct sqlite3_api_routines sqlite3_api_routines;
** ^SQLite further guarantees that
** the string will be valid and unchanged until xClose() is
** called. Because of the previous sentence,
-** the [sqlite3_file] can safely store a pointer to the
+** the [tdsqlite3_file] can safely store a pointer to the
** filename if it needs to remember the filename for some reason.
** If the zFilename parameter to xOpen is a NULL pointer then xOpen
** must invent its own temporary name for the file. ^Whenever the
@@ -1117,8 +1243,8 @@ typedef struct sqlite3_api_routines sqlite3_api_routines;
** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE].
**
** The flags argument to xOpen() includes all bits set in
-** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()]
-** or [sqlite3_open16()] is used, then flags includes at least
+** the flags argument to [tdsqlite3_open_v2()]. Or if [tdsqlite3_open()]
+** or [tdsqlite3_open16()] is used, then flags includes at least
** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE].
** If xOpen() opens a file read-only then it sets *pOutFlags to
** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set.
@@ -1168,21 +1294,27 @@ typedef struct sqlite3_api_routines sqlite3_api_routines;
** for exclusive access.
**
** ^At least szOsFile bytes of memory are allocated by SQLite
-** to hold the [sqlite3_file] structure passed as the third
+** to hold the [tdsqlite3_file] structure passed as the third
** argument to xOpen. The xOpen method does not have to
** allocate the structure; it should just fill it in. Note that
-** the xOpen method must set the sqlite3_file.pMethods to either
-** a valid [sqlite3_io_methods] object or to NULL. xOpen must do
-** this even if the open fails. SQLite expects that the sqlite3_file.pMethods
+** the xOpen method must set the tdsqlite3_file.pMethods to either
+** a valid [tdsqlite3_io_methods] object or to NULL. xOpen must do
+** this even if the open fails. SQLite expects that the tdsqlite3_file.pMethods
** element will be valid after xOpen returns regardless of the success
** or failure of the xOpen call.
**
-** [[sqlite3_vfs.xAccess]]
+** [[tdsqlite3_vfs.xAccess]]
** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS]
** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to
** test whether a file is readable and writable, or [SQLITE_ACCESS_READ]
-** to test whether a file is at least readable. The file can be a
-** directory.
+** to test whether a file is at least readable. The SQLITE_ACCESS_READ
+** flag is never actually used and is not implemented in the built-in
+** VFSes of SQLite. The file is named by the second argument and can be a
+** directory. The xAccess method returns [SQLITE_OK] on success or some
+** non-zero error code if there is an I/O error or if the name of
+** the file given in the second argument is illegal. If SQLITE_OK
+** is returned, then non-zero or zero is written into *pResOut to indicate
+** whether or not the file is accessible.
**
** ^SQLite will always allocate at least mxPathname+1 bytes for the
** output buffer xFullPathname. The exact size of the output buffer
@@ -1221,40 +1353,40 @@ typedef struct sqlite3_api_routines sqlite3_api_routines;
** from one release to the next. Applications must not attempt to access
** any of these methods if the iVersion of the VFS is less than 3.
*/
-typedef struct sqlite3_vfs sqlite3_vfs;
-typedef void (*sqlite3_syscall_ptr)(void);
-struct sqlite3_vfs {
+typedef struct tdsqlite3_vfs tdsqlite3_vfs;
+typedef void (*tdsqlite3_syscall_ptr)(void);
+struct tdsqlite3_vfs {
int iVersion; /* Structure version number (currently 3) */
- int szOsFile; /* Size of subclassed sqlite3_file */
+ int szOsFile; /* Size of subclassed tdsqlite3_file */
int mxPathname; /* Maximum file pathname length */
- sqlite3_vfs *pNext; /* Next registered VFS */
+ tdsqlite3_vfs *pNext; /* Next registered VFS */
const char *zName; /* Name of this virtual file system */
void *pAppData; /* Pointer to application-specific data */
- int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
+ int (*xOpen)(tdsqlite3_vfs*, const char *zName, tdsqlite3_file*,
int flags, int *pOutFlags);
- int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
- int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
- int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
- void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
- void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
- void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void);
- void (*xDlClose)(sqlite3_vfs*, void*);
- int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
- int (*xSleep)(sqlite3_vfs*, int microseconds);
- int (*xCurrentTime)(sqlite3_vfs*, double*);
- int (*xGetLastError)(sqlite3_vfs*, int, char *);
+ int (*xDelete)(tdsqlite3_vfs*, const char *zName, int syncDir);
+ int (*xAccess)(tdsqlite3_vfs*, const char *zName, int flags, int *pResOut);
+ int (*xFullPathname)(tdsqlite3_vfs*, const char *zName, int nOut, char *zOut);
+ void *(*xDlOpen)(tdsqlite3_vfs*, const char *zFilename);
+ void (*xDlError)(tdsqlite3_vfs*, int nByte, char *zErrMsg);
+ void (*(*xDlSym)(tdsqlite3_vfs*,void*, const char *zSymbol))(void);
+ void (*xDlClose)(tdsqlite3_vfs*, void*);
+ int (*xRandomness)(tdsqlite3_vfs*, int nByte, char *zOut);
+ int (*xSleep)(tdsqlite3_vfs*, int microseconds);
+ int (*xCurrentTime)(tdsqlite3_vfs*, double*);
+ int (*xGetLastError)(tdsqlite3_vfs*, int, char *);
/*
** The methods above are in version 1 of the sqlite_vfs object
** definition. Those that follow are added in version 2 or later
*/
- int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
+ int (*xCurrentTimeInt64)(tdsqlite3_vfs*, tdsqlite3_int64*);
/*
** The methods above are in versions 1 and 2 of the sqlite_vfs object.
** Those below are for version 3 and greater.
*/
- int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr);
- sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName);
- const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName);
+ int (*xSetSystemCall)(tdsqlite3_vfs*, const char *zName, tdsqlite3_syscall_ptr);
+ tdsqlite3_syscall_ptr (*xGetSystemCall)(tdsqlite3_vfs*, const char *zName);
+ const char *(*xNextSystemCall)(tdsqlite3_vfs*, const char *zName);
/*
** The methods above are in versions 1 through 3 of the sqlite_vfs object.
** New fields may be appended in future versions. The iVersion
@@ -1266,7 +1398,7 @@ struct sqlite3_vfs {
** CAPI3REF: Flags for the xAccess VFS method
**
** These integer constants can be used as the third parameter to
-** the xAccess method of an [sqlite3_vfs] object. They determine
+** the xAccess method of an [tdsqlite3_vfs] object. They determine
** what kind of permissions the xAccess method is looking for.
** With SQLITE_ACCESS_EXISTS, the xAccess method
** simply checks whether the file exists.
@@ -1290,7 +1422,7 @@ struct sqlite3_vfs {
** CAPI3REF: Flags for the xShmLock VFS method
**
** These integer constants define the various locking operations
-** allowed by the xShmLock method of [sqlite3_io_methods]. The
+** allowed by the xShmLock method of [tdsqlite3_io_methods]. The
** following are the only legal combinations of flags to the
** xShmLock method:
**
@@ -1316,7 +1448,7 @@ struct sqlite3_vfs {
/*
** CAPI3REF: Maximum xShmLock index
**
-** The xShmLock method on [sqlite3_io_methods] may use values
+** The xShmLock method on [tdsqlite3_io_methods] may use values
** between 0 and this upper bound as its "offset" argument.
** The SQLite core will never attempt to acquire or release a
** lock outside of this range
@@ -1327,134 +1459,134 @@ struct sqlite3_vfs {
/*
** CAPI3REF: Initialize The SQLite Library
**
-** ^The sqlite3_initialize() routine initializes the
-** SQLite library. ^The sqlite3_shutdown() routine
-** deallocates any resources that were allocated by sqlite3_initialize().
+** ^The tdsqlite3_initialize() routine initializes the
+** SQLite library. ^The tdsqlite3_shutdown() routine
+** deallocates any resources that were allocated by tdsqlite3_initialize().
** These routines are designed to aid in process initialization and
** shutdown on embedded systems. Workstation applications using
** SQLite normally do not need to invoke either of these routines.
**
-** A call to sqlite3_initialize() is an "effective" call if it is
-** the first time sqlite3_initialize() is invoked during the lifetime of
-** the process, or if it is the first time sqlite3_initialize() is invoked
-** following a call to sqlite3_shutdown(). ^(Only an effective call
-** of sqlite3_initialize() does any initialization. All other calls
+** A call to tdsqlite3_initialize() is an "effective" call if it is
+** the first time tdsqlite3_initialize() is invoked during the lifetime of
+** the process, or if it is the first time tdsqlite3_initialize() is invoked
+** following a call to tdsqlite3_shutdown(). ^(Only an effective call
+** of tdsqlite3_initialize() does any initialization. All other calls
** are harmless no-ops.)^
**
-** A call to sqlite3_shutdown() is an "effective" call if it is the first
-** call to sqlite3_shutdown() since the last sqlite3_initialize(). ^(Only
-** an effective call to sqlite3_shutdown() does any deinitialization.
-** All other valid calls to sqlite3_shutdown() are harmless no-ops.)^
+** A call to tdsqlite3_shutdown() is an "effective" call if it is the first
+** call to tdsqlite3_shutdown() since the last tdsqlite3_initialize(). ^(Only
+** an effective call to tdsqlite3_shutdown() does any deinitialization.
+** All other valid calls to tdsqlite3_shutdown() are harmless no-ops.)^
**
-** The sqlite3_initialize() interface is threadsafe, but sqlite3_shutdown()
-** is not. The sqlite3_shutdown() interface must only be called from a
+** The tdsqlite3_initialize() interface is threadsafe, but tdsqlite3_shutdown()
+** is not. The tdsqlite3_shutdown() interface must only be called from a
** single thread. All open [database connections] must be closed and all
** other SQLite resources must be deallocated prior to invoking
-** sqlite3_shutdown().
+** tdsqlite3_shutdown().
**
-** Among other things, ^sqlite3_initialize() will invoke
-** sqlite3_os_init(). Similarly, ^sqlite3_shutdown()
-** will invoke sqlite3_os_end().
+** Among other things, ^tdsqlite3_initialize() will invoke
+** tdsqlite3_os_init(). Similarly, ^tdsqlite3_shutdown()
+** will invoke tdsqlite3_os_end().
**
-** ^The sqlite3_initialize() routine returns [SQLITE_OK] on success.
-** ^If for some reason, sqlite3_initialize() is unable to initialize
+** ^The tdsqlite3_initialize() routine returns [SQLITE_OK] on success.
+** ^If for some reason, tdsqlite3_initialize() is unable to initialize
** the library (perhaps it is unable to allocate a needed resource such
** as a mutex) it returns an [error code] other than [SQLITE_OK].
**
-** ^The sqlite3_initialize() routine is called internally by many other
+** ^The tdsqlite3_initialize() routine is called internally by many other
** SQLite interfaces so that an application usually does not need to
-** invoke sqlite3_initialize() directly. For example, [sqlite3_open()]
-** calls sqlite3_initialize() so the SQLite library will be automatically
-** initialized when [sqlite3_open()] is called if it has not be initialized
+** invoke tdsqlite3_initialize() directly. For example, [tdsqlite3_open()]
+** calls tdsqlite3_initialize() so the SQLite library will be automatically
+** initialized when [tdsqlite3_open()] is called if it has not be initialized
** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT]
-** compile-time option, then the automatic calls to sqlite3_initialize()
-** are omitted and the application must call sqlite3_initialize() directly
+** compile-time option, then the automatic calls to tdsqlite3_initialize()
+** are omitted and the application must call tdsqlite3_initialize() directly
** prior to using any other SQLite interface. For maximum portability,
-** it is recommended that applications always invoke sqlite3_initialize()
+** it is recommended that applications always invoke tdsqlite3_initialize()
** directly prior to using any other SQLite interface. Future releases
** of SQLite may require this. In other words, the behavior exhibited
** when SQLite is compiled with [SQLITE_OMIT_AUTOINIT] might become the
** default behavior in some future release of SQLite.
**
-** The sqlite3_os_init() routine does operating-system specific
-** initialization of the SQLite library. The sqlite3_os_end()
-** routine undoes the effect of sqlite3_os_init(). Typical tasks
+** The tdsqlite3_os_init() routine does operating-system specific
+** initialization of the SQLite library. The tdsqlite3_os_end()
+** routine undoes the effect of tdsqlite3_os_init(). Typical tasks
** performed by these routines include allocation or deallocation
** of static resources, initialization of global variables,
-** setting up a default [sqlite3_vfs] module, or setting up
-** a default configuration using [sqlite3_config()].
-**
-** The application should never invoke either sqlite3_os_init()
-** or sqlite3_os_end() directly. The application should only invoke
-** sqlite3_initialize() and sqlite3_shutdown(). The sqlite3_os_init()
-** interface is called automatically by sqlite3_initialize() and
-** sqlite3_os_end() is called by sqlite3_shutdown(). Appropriate
-** implementations for sqlite3_os_init() and sqlite3_os_end()
+** setting up a default [tdsqlite3_vfs] module, or setting up
+** a default configuration using [tdsqlite3_config()].
+**
+** The application should never invoke either tdsqlite3_os_init()
+** or tdsqlite3_os_end() directly. The application should only invoke
+** tdsqlite3_initialize() and tdsqlite3_shutdown(). The tdsqlite3_os_init()
+** interface is called automatically by tdsqlite3_initialize() and
+** tdsqlite3_os_end() is called by tdsqlite3_shutdown(). Appropriate
+** implementations for tdsqlite3_os_init() and tdsqlite3_os_end()
** are built into SQLite when it is compiled for Unix, Windows, or OS/2.
** When [custom builds | built for other platforms]
** (using the [SQLITE_OS_OTHER=1] compile-time
** option) the application must supply a suitable implementation for
-** sqlite3_os_init() and sqlite3_os_end(). An application-supplied
-** implementation of sqlite3_os_init() or sqlite3_os_end()
+** tdsqlite3_os_init() and tdsqlite3_os_end(). An application-supplied
+** implementation of tdsqlite3_os_init() or tdsqlite3_os_end()
** must return [SQLITE_OK] on success and some other [error code] upon
** failure.
*/
-SQLITE_API int sqlite3_initialize(void);
-SQLITE_API int sqlite3_shutdown(void);
-SQLITE_API int sqlite3_os_init(void);
-SQLITE_API int sqlite3_os_end(void);
+SQLITE_API int tdsqlite3_initialize(void);
+SQLITE_API int tdsqlite3_shutdown(void);
+SQLITE_API int tdsqlite3_os_init(void);
+SQLITE_API int tdsqlite3_os_end(void);
/*
** CAPI3REF: Configuring The SQLite Library
**
-** The sqlite3_config() interface is used to make global configuration
+** The tdsqlite3_config() interface is used to make global configuration
** changes to SQLite in order to tune SQLite to the specific needs of
** the application. The default configuration is recommended for most
** applications and so this routine is usually not necessary. It is
** provided to support rare applications with unusual needs.
**
-** <b>The sqlite3_config() interface is not threadsafe. The application
+** <b>The tdsqlite3_config() interface is not threadsafe. The application
** must ensure that no other SQLite interfaces are invoked by other
-** threads while sqlite3_config() is running.</b>
+** threads while tdsqlite3_config() is running.</b>
**
-** The sqlite3_config() interface
+** The tdsqlite3_config() interface
** may only be invoked prior to library initialization using
-** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()].
-** ^If sqlite3_config() is called after [sqlite3_initialize()] and before
-** [sqlite3_shutdown()] then it will return SQLITE_MISUSE.
-** Note, however, that ^sqlite3_config() can be called as part of the
-** implementation of an application-defined [sqlite3_os_init()].
+** [tdsqlite3_initialize()] or after shutdown by [tdsqlite3_shutdown()].
+** ^If tdsqlite3_config() is called after [tdsqlite3_initialize()] and before
+** [tdsqlite3_shutdown()] then it will return SQLITE_MISUSE.
+** Note, however, that ^tdsqlite3_config() can be called as part of the
+** implementation of an application-defined [tdsqlite3_os_init()].
**
-** The first argument to sqlite3_config() is an integer
+** The first argument to tdsqlite3_config() is an integer
** [configuration option] that determines
** what property of SQLite is to be configured. Subsequent arguments
** vary depending on the [configuration option]
** in the first argument.
**
-** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK].
+** ^When a configuration option is set, tdsqlite3_config() returns [SQLITE_OK].
** ^If the option is unknown or SQLite is unable to set the option
** then this routine returns a non-zero [error code].
*/
-SQLITE_API int sqlite3_config(int, ...);
+SQLITE_API int tdsqlite3_config(int, ...);
/*
** CAPI3REF: Configure database connections
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** The sqlite3_db_config() interface is used to make configuration
+** The tdsqlite3_db_config() interface is used to make configuration
** changes to a [database connection]. The interface is similar to
-** [sqlite3_config()] except that the changes apply to a single
+** [tdsqlite3_config()] except that the changes apply to a single
** [database connection] (specified in the first argument).
**
-** The second argument to sqlite3_db_config(D,V,...) is the
+** The second argument to tdsqlite3_db_config(D,V,...) is the
** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code
** that indicates what aspect of the [database connection] is being configured.
** Subsequent arguments vary depending on the configuration verb.
**
-** ^Calls to sqlite3_db_config() return SQLITE_OK if and only if
+** ^Calls to tdsqlite3_db_config() return SQLITE_OK if and only if
** the call is considered successful.
*/
-SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...);
+SQLITE_API int tdsqlite3_db_config(tdsqlite3*, int op, ...);
/*
** CAPI3REF: Memory Allocation Routines
@@ -1464,10 +1596,10 @@ SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...);
**
** This object is used in only one place in the SQLite interface.
** A pointer to an instance of this object is the argument to
-** [sqlite3_config()] when the configuration option is
+** [tdsqlite3_config()] when the configuration option is
** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC].
** By creating an instance of this object
-** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC])
+** and passing it to [tdsqlite3_config]([SQLITE_CONFIG_MALLOC])
** during configuration, an application can specify an alternative
** memory allocation subsystem for SQLite to use for all of its
** dynamic memory needs.
@@ -1494,20 +1626,20 @@ SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...);
** a memory allocation given a particular requested size. Most memory
** allocators round up memory allocations at least to the next multiple
** of 8. Some allocators round up to a larger multiple or to a power of 2.
-** Every memory allocation request coming in through [sqlite3_malloc()]
-** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0,
+** Every memory allocation request coming in through [tdsqlite3_malloc()]
+** or [tdsqlite3_realloc()] first calls xRoundup. If xRoundup returns 0,
** that causes the corresponding memory allocation to fail.
**
** The xInit method initializes the memory allocator. For example,
-** it might allocate any require mutexes or initialize internal data
+** it might allocate any required mutexes or initialize internal data
** structures. The xShutdown method is invoked (indirectly) by
-** [sqlite3_shutdown()] and should deallocate any resources acquired
+** [tdsqlite3_shutdown()] and should deallocate any resources acquired
** by xInit. The pAppData pointer is used as the only parameter to
** xInit and xShutdown.
**
** SQLite holds the [SQLITE_MUTEX_STATIC_MASTER] mutex when it invokes
** the xInit method, so the xInit method need not be threadsafe. The
-** xShutdown method is only called from [sqlite3_shutdown()] so it does
+** xShutdown method is only called from [tdsqlite3_shutdown()] so it does
** not need to be threadsafe either. For all other methods, SQLite
** holds the [SQLITE_MUTEX_STATIC_MEM] mutex as long as the
** [SQLITE_CONFIG_MEMSTATUS] configuration option is turned on (which
@@ -1519,8 +1651,8 @@ SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...);
** SQLite will never invoke xInit() more than once without an intervening
** call to xShutdown().
*/
-typedef struct sqlite3_mem_methods sqlite3_mem_methods;
-struct sqlite3_mem_methods {
+typedef struct tdsqlite3_mem_methods tdsqlite3_mem_methods;
+struct tdsqlite3_mem_methods {
void *(*xMalloc)(int); /* Memory allocation function */
void (*xFree)(void*); /* Free a prior allocation */
void *(*xRealloc)(void*,int); /* Resize an allocation */
@@ -1536,12 +1668,12 @@ struct sqlite3_mem_methods {
** KEYWORDS: {configuration option}
**
** These constants are the available integer configuration options that
-** can be passed as the first argument to the [sqlite3_config()] interface.
+** can be passed as the first argument to the [tdsqlite3_config()] interface.
**
** New configuration options may be added in future releases of SQLite.
** Existing configuration options might be discontinued. Applications
-** should check the return code from [sqlite3_config()] to make sure that
-** the call worked. The [sqlite3_config()] interface will return a
+** should check the return code from [tdsqlite3_config()] to make sure that
+** the call worked. The [tdsqlite3_config()] interface will return a
** non-zero [error code] if a discontinued or unsupported configuration option
** is invoked.
**
@@ -1553,7 +1685,7 @@ struct sqlite3_mem_methods {
** by a single thread. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** it is not possible to change the [threading mode] from its default
-** value of Single-thread and so [sqlite3_config()] will return
+** value of Single-thread and so [tdsqlite3_config()] will return
** [SQLITE_ERROR] if called with the SQLITE_CONFIG_SINGLETHREAD
** configuration option.</dd>
**
@@ -1568,7 +1700,7 @@ struct sqlite3_mem_methods {
** [database connection] at the same time. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** it is not possible to set the Multi-thread [threading mode] and
-** [sqlite3_config()] will return [SQLITE_ERROR] if called with the
+** [tdsqlite3_config()] will return [SQLITE_ERROR] if called with the
** SQLITE_CONFIG_MULTITHREAD configuration option.</dd>
**
** [[SQLITE_CONFIG_SERIALIZED]] <dt>SQLITE_CONFIG_SERIALIZED</dt>
@@ -1584,37 +1716,48 @@ struct sqlite3_mem_methods {
** ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** it is not possible to set the Serialized [threading mode] and
-** [sqlite3_config()] will return [SQLITE_ERROR] if called with the
+** [tdsqlite3_config()] will return [SQLITE_ERROR] if called with the
** SQLITE_CONFIG_SERIALIZED configuration option.</dd>
**
** [[SQLITE_CONFIG_MALLOC]] <dt>SQLITE_CONFIG_MALLOC</dt>
** <dd> ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is
-** a pointer to an instance of the [sqlite3_mem_methods] structure.
+** a pointer to an instance of the [tdsqlite3_mem_methods] structure.
** The argument specifies
** alternative low-level memory allocation routines to be used in place of
** the memory allocation routines built into SQLite.)^ ^SQLite makes
-** its own private copy of the content of the [sqlite3_mem_methods] structure
-** before the [sqlite3_config()] call returns.</dd>
+** its own private copy of the content of the [tdsqlite3_mem_methods] structure
+** before the [tdsqlite3_config()] call returns.</dd>
**
** [[SQLITE_CONFIG_GETMALLOC]] <dt>SQLITE_CONFIG_GETMALLOC</dt>
** <dd> ^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which
-** is a pointer to an instance of the [sqlite3_mem_methods] structure.
-** The [sqlite3_mem_methods]
+** is a pointer to an instance of the [tdsqlite3_mem_methods] structure.
+** The [tdsqlite3_mem_methods]
** structure is filled with the currently defined memory allocation routines.)^
** This option can be used to overload the default memory allocation
** routines with a wrapper that simulations memory allocation failure or
** tracks memory usage, for example. </dd>
**
+** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt>
+** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of
+** type int, interpreted as a boolean, which if true provides a hint to
+** SQLite that it should avoid large memory allocations if possible.
+** SQLite will run faster if it is free to make large memory allocations,
+** but some application might prefer to run slower in exchange for
+** guarantees about memory fragmentation that are possible if large
+** allocations are avoided. This hint is normally off.
+** </dd>
+**
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
** interpreted as a boolean, which enables or disables the collection of
** memory allocation statistics. ^(When memory allocation statistics are
** disabled, the following SQLite interfaces become non-operational:
** <ul>
-** <li> [sqlite3_memory_used()]
-** <li> [sqlite3_memory_highwater()]
-** <li> [sqlite3_soft_heap_limit64()]
-** <li> [sqlite3_status64()]
+** <li> [tdsqlite3_hard_heap_limit64()]
+** <li> [tdsqlite3_memory_used()]
+** <li> [tdsqlite3_memory_highwater()]
+** <li> [tdsqlite3_soft_heap_limit64()]
+** <li> [tdsqlite3_status64()]
** </ul>)^
** ^Memory allocation statistics are enabled by default unless SQLite is
** compiled with [SQLITE_DEFAULT_MEMSTATUS]=0 in which case memory
@@ -1622,32 +1765,14 @@ struct sqlite3_mem_methods {
** </dd>
**
** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt>
-** <dd> ^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer
-** that SQLite can use for scratch memory. ^(There are three arguments
-** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte
-** aligned memory buffer from which the scratch allocations will be
-** drawn, the size of each scratch allocation (sz),
-** and the maximum number of scratch allocations (N).)^
-** The first argument must be a pointer to an 8-byte aligned buffer
-** of at least sz*N bytes of memory.
-** ^SQLite will not use more than one scratch buffers per thread.
-** ^SQLite will never request a scratch buffer that is more than 6
-** times the database page size.
-** ^If SQLite needs needs additional
-** scratch memory beyond what is provided by this configuration option, then
-** [sqlite3_malloc()] will be used to obtain the memory needed.<p>
-** ^When the application provides any amount of scratch memory using
-** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large
-** [sqlite3_malloc|heap allocations].
-** This can help [Robson proof|prevent memory allocation failures] due to heap
-** fragmentation in low-memory embedded systems.
+** <dd> The SQLITE_CONFIG_SCRATCH option is no longer used.
** </dd>
**
** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt>
** <dd> ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool
** that SQLite can use for the database page cache with the default page
** cache implementation.
-** This configuration option is a no-op if an application-define page
+** This configuration option is a no-op if an application-defined page
** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2].
** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to
** 8-byte aligned memory (pMem), the size of each page cache line (sz),
@@ -1662,22 +1787,21 @@ struct sqlite3_mem_methods {
** aligned block of memory of at least sz*N bytes, otherwise
** subsequent behavior is undefined.
** ^When pMem is not NULL, SQLite will strive to use the memory provided
-** to satisfy page cache needs, falling back to [sqlite3_malloc()] if
+** to satisfy page cache needs, falling back to [tdsqlite3_malloc()] if
** a page cache line is larger than sz bytes or if all of the pMem buffer
** is exhausted.
** ^If pMem is NULL and N is non-zero, then each database connection
** does an initial bulk allocation for page cache memory
-** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or
+** from [tdsqlite3_malloc()] sufficient for N cache lines if N is positive or
** of -1024*N bytes if N is negative, . ^If additional
** page cache memory is needed beyond what is provided by the initial
-** allocation, then SQLite goes to [sqlite3_malloc()] separately for each
+** allocation, then SQLite goes to [tdsqlite3_malloc()] separately for each
** additional cache line. </dd>
**
** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt>
** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer
** that SQLite will use for all of its dynamic memory allocation needs
-** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and
-** [SQLITE_CONFIG_PAGECACHE].
+** beyond those provided for by [SQLITE_CONFIG_PAGECACHE].
** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled
** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns
** [SQLITE_ERROR] if invoked otherwise.
@@ -1696,27 +1820,27 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt>
** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a
-** pointer to an instance of the [sqlite3_mutex_methods] structure.
+** pointer to an instance of the [tdsqlite3_mutex_methods] structure.
** The argument specifies alternative low-level mutex routines to be used
** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of
-** the content of the [sqlite3_mutex_methods] structure before the call to
-** [sqlite3_config()] returns. ^If SQLite is compiled with
+** the content of the [tdsqlite3_mutex_methods] structure before the call to
+** [tdsqlite3_config()] returns. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** the entire mutexing subsystem is omitted from the build and hence calls to
-** [sqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will
+** [tdsqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will
** return [SQLITE_ERROR].</dd>
**
** [[SQLITE_CONFIG_GETMUTEX]] <dt>SQLITE_CONFIG_GETMUTEX</dt>
** <dd> ^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which
-** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The
-** [sqlite3_mutex_methods]
+** is a pointer to an instance of the [tdsqlite3_mutex_methods] structure. The
+** [tdsqlite3_mutex_methods]
** structure is filled with the currently defined mutex routines.)^
** This option can be used to overload the default mutex allocation
** routines with a wrapper used to track mutex usage for performance
** profiling or testing, for example. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** the entire mutexing subsystem is omitted from the build and hence calls to
-** [sqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will
+** [tdsqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will
** return [SQLITE_ERROR].</dd>
**
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
@@ -1726,18 +1850,18 @@ struct sqlite3_mem_methods {
** size of each lookaside buffer slot and the second is the number of
** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE
** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
-** option to [sqlite3_db_config()] can be used to change the lookaside
+** option to [tdsqlite3_db_config()] can be used to change the lookaside
** configuration on individual connections.)^ </dd>
**
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
-** a pointer to an [sqlite3_pcache_methods2] object. This object specifies
+** a pointer to an [tdsqlite3_pcache_methods2] object. This object specifies
** the interface to a custom page cache implementation.)^
-** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.</dd>
+** ^SQLite makes a copy of the [tdsqlite3_pcache_methods2] object.</dd>
**
** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which
-** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of
+** is a pointer to an [tdsqlite3_pcache_methods2] object. SQLite copies of
** the current page cache implementation into that object.)^ </dd>
**
** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt>
@@ -1746,15 +1870,15 @@ struct sqlite3_mem_methods {
** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a
** function with a call signature of void(*)(void*,int,const char*),
** and a pointer to void. ^If the function pointer is not NULL, it is
-** invoked by [sqlite3_log()] to process each logging event. ^If the
-** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op.
+** invoked by [tdsqlite3_log()] to process each logging event. ^If the
+** function pointer is NULL, the [tdsqlite3_log()] interface becomes a no-op.
** ^The void pointer that is the second argument to SQLITE_CONFIG_LOG is
** passed through as the first parameter to the application-defined logger
** function whenever that function is invoked. ^The second parameter to
** the logger function is a copy of the first parameter to the corresponding
-** [sqlite3_log()] call and is intended to be a [result code] or an
+** [tdsqlite3_log()] call and is intended to be a [result code] or an
** [extended result code]. ^The third parameter passed to the logger is
-** log message after formatting via [sqlite3_snprintf()].
+** log message after formatting via [tdsqlite3_snprintf()].
** The SQLite logging interface is not reentrant; the logger function
** supplied by the application must not invoke any SQLite interface.
** In a multi-threaded application, the application-defined logger
@@ -1764,8 +1888,8 @@ struct sqlite3_mem_methods {
** <dd>^(The SQLITE_CONFIG_URI option takes a single argument of type int.
** If non-zero, then URI handling is globally enabled. If the parameter is zero,
** then URI handling is globally disabled.)^ ^If URI handling is globally
-** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()],
-** [sqlite3_open16()] or
+** enabled, all filenames passed to [tdsqlite3_open()], [tdsqlite3_open_v2()],
+** [tdsqlite3_open16()] or
** specified as part of [ATTACH] commands are interpreted as URIs, regardless
** of whether or not the [SQLITE_OPEN_URI] flag is set when the database
** connection is opened. ^If it is globally disabled, filenames are
@@ -1797,7 +1921,7 @@ struct sqlite3_mem_methods {
** <dt>SQLITE_CONFIG_SQLLOG
** <dd>This option is only available if sqlite is compiled with the
** [SQLITE_ENABLE_SQLLOG] pre-processor macro defined. The first argument should
-** be a pointer to a function of type void(*)(void*,sqlite3*,const char*, int).
+** be a pointer to a function of type void(*)(void*,tdsqlite3*,const char*, int).
** The second should be of type (void*). The callback is invoked by the library
** in three separate circumstances, identified by the value passed as the
** fourth parameter. If the fourth parameter is 0, then the database connection
@@ -1812,7 +1936,7 @@ struct sqlite3_mem_methods {
**
** [[SQLITE_CONFIG_MMAP_SIZE]]
** <dt>SQLITE_CONFIG_MMAP_SIZE
-** <dd>^SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (sqlite3_int64) values
+** <dd>^SQLITE_CONFIG_MMAP_SIZE takes two 64-bit integer (tdsqlite3_int64) values
** that are the default mmap size limit (the default setting for
** [PRAGMA mmap_size]) and the maximum allowed mmap size limit.
** ^The default setting can be overridden by each database connection using
@@ -1863,57 +1987,88 @@ struct sqlite3_mem_methods {
** I/O required to support statement rollback.
** The default value for this setting is controlled by the
** [SQLITE_STMTJRNL_SPILL] compile-time option.
+**
+** [[SQLITE_CONFIG_SORTERREF_SIZE]]
+** <dt>SQLITE_CONFIG_SORTERREF_SIZE
+** <dd>The SQLITE_CONFIG_SORTERREF_SIZE option accepts a single parameter
+** of type (int) - the new value of the sorter-reference size threshold.
+** Usually, when SQLite uses an external sort to order records according
+** to an ORDER BY clause, all fields required by the caller are present in the
+** sorted records. However, if SQLite determines based on the declared type
+** of a table column that its values are likely to be very large - larger
+** than the configured sorter-reference size threshold - then a reference
+** is stored in each sorted record and the required column values loaded
+** from the database as records are returned in sorted order. The default
+** value for this option is to never use this optimization. Specifying a
+** negative value for this option restores the default behaviour.
+** This option is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option.
+**
+** [[SQLITE_CONFIG_MEMDB_MAXSIZE]]
+** <dt>SQLITE_CONFIG_MEMDB_MAXSIZE
+** <dd>The SQLITE_CONFIG_MEMDB_MAXSIZE option accepts a single parameter
+** [tdsqlite3_int64] parameter which is the default maximum size for an in-memory
+** database created using [tdsqlite3_deserialize()]. This default maximum
+** size can be adjusted up or down for individual databases using the
+** [SQLITE_FCNTL_SIZE_LIMIT] [tdsqlite3_file_control|file-control]. If this
+** configuration setting is never used, then the default maximum is determined
+** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that
+** compile-time option is not set, then the default maximum is 1073741824.
** </dl>
*/
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
-#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
-#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
-#define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */
+#define SQLITE_CONFIG_MALLOC 4 /* tdsqlite3_mem_methods* */
+#define SQLITE_CONFIG_GETMALLOC 5 /* tdsqlite3_mem_methods* */
+#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
-#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */
-#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */
+#define SQLITE_CONFIG_MUTEX 10 /* tdsqlite3_mutex_methods* */
+#define SQLITE_CONFIG_GETMUTEX 11 /* tdsqlite3_mutex_methods* */
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */
#define SQLITE_CONFIG_PCACHE 14 /* no-op */
#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */
#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */
#define SQLITE_CONFIG_URI 17 /* int */
-#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */
-#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */
+#define SQLITE_CONFIG_PCACHE2 18 /* tdsqlite3_pcache_methods2* */
+#define SQLITE_CONFIG_GETPCACHE2 19 /* tdsqlite3_pcache_methods2* */
#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */
#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */
-#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */
+#define SQLITE_CONFIG_MMAP_SIZE 22 /* tdsqlite3_int64, tdsqlite3_int64 */
#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
+#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
+#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */
+#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* tdsqlite3_int64 */
/*
** CAPI3REF: Database Connection Configuration Options
**
** These constants are the available integer configuration options that
-** can be passed as the second argument to the [sqlite3_db_config()] interface.
+** can be passed as the second argument to the [tdsqlite3_db_config()] interface.
**
** New configuration options may be added in future releases of SQLite.
** Existing configuration options might be discontinued. Applications
-** should check the return code from [sqlite3_db_config()] to make sure that
-** the call worked. ^The [sqlite3_db_config()] interface will return a
+** should check the return code from [tdsqlite3_db_config()] to make sure that
+** the call worked. ^The [tdsqlite3_db_config()] interface will return a
** non-zero [error code] if a discontinued or unsupported configuration option
** is invoked.
**
** <dl>
+** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> ^This option takes three additional arguments that determine the
** [lookaside memory allocator] configuration for the [database connection].
-** ^The first argument (the third parameter to [sqlite3_db_config()] is a
+** ^The first argument (the third parameter to [tdsqlite3_db_config()] is a
** pointer to a memory buffer to use for lookaside memory.
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
** may be NULL in which case SQLite will allocate the
-** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the
+** lookaside buffer itself using [tdsqlite3_malloc()]. ^The second argument is the
** size of each lookaside buffer slot. ^The third argument is the number of
** slots. The size of the buffer in the first argument must be greater than
** or equal to the product of the second and third arguments. The buffer
@@ -1923,11 +2078,12 @@ struct sqlite3_mem_methods {
** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words
** when the "current value" returned by
-** [sqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero.
+** [tdsqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero.
** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].)^</dd>
**
+** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
** <dd> ^This option is used to enable or disable the enforcement of
** [foreign key constraints]. There should be two additional arguments.
@@ -1938,6 +2094,7 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in
** which case the FK enforcement setting is not reported back. </dd>
**
+** [[SQLITE_DBCONFIG_ENABLE_TRIGGER]]
** <dt>SQLITE_DBCONFIG_ENABLE_TRIGGER</dt>
** <dd> ^This option is used to enable or disable [CREATE TRIGGER | triggers].
** There should be two additional arguments.
@@ -1948,9 +2105,21 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in
** which case the trigger setting is not reported back. </dd>
**
+** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
+** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
+** <dd> ^This option is used to enable or disable [CREATE VIEW | views].
+** There should be two additional arguments.
+** The first argument is an integer which is 0 to disable views,
+** positive to enable views or negative to leave the setting unchanged.
+** The second parameter is a pointer to an integer into which
+** is written 0 or 1 to indicate whether views are disabled or enabled
+** following this call. The second parameter may be a NULL pointer, in
+** which case the view setting is not reported back. </dd>
+**
+** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
-** <dd> ^This option is used to enable or disable the two-argument
-** version of the [fts3_tokenizer()] function which is part of the
+** <dd> ^This option is used to enable or disable the
+** [fts3_tokenizer()] function which is part of the
** [FTS3] full-text search engine extension.
** There should be two additional arguments.
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
@@ -1961,11 +2130,12 @@ struct sqlite3_mem_methods {
** following this call. The second parameter may be a NULL pointer, in
** which case the new setting is not reported back. </dd>
**
+** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]]
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
-** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()]
+** <dd> ^This option is used to enable or disable the [tdsqlite3_load_extension()]
** interface independently of the [load_extension()] SQL function.
-** The [sqlite3_enable_load_extension()] API enables or disables both the
-** C-API [sqlite3_load_extension()] and the SQL function [load_extension()].
+** The [tdsqlite3_enable_load_extension()] API enables or disables both the
+** C-API [tdsqlite3_load_extension()] and the SQL function [load_extension()].
** There should be two additional arguments.
** When the first argument to this interface is 1, then only the C-API is
** enabled and the SQL function remains disabled. If the first argument to
@@ -1973,12 +2143,12 @@ struct sqlite3_mem_methods {
** If the first argument is -1, then no changes are made to state of either the
** C-API or the SQL function.
** The second parameter is a pointer to an integer into which
-** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface
+** is written 0 or 1 to indicate whether [tdsqlite3_load_extension()] interface
** is disabled or enabled following this call. The second parameter may
** be a NULL pointer, in which case the new setting is not reported back.
** </dd>
**
-** <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
+** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
** <dd> ^This option is used to change the name of the "main" database
** schema. ^The sole argument is a pointer to a constant UTF8 string
** which will become the new schema name in place of "main". ^SQLite
@@ -1987,6 +2157,163 @@ struct sqlite3_mem_methods {
** until after the database connection closes.
** </dd>
**
+** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]]
+** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt>
+** <dd> Usually, when a database in wal mode is closed or detached from a
+** database handle, SQLite checks if this will mean that there are now no
+** connections at all to the database. If so, it performs a checkpoint
+** operation before closing the connection. This option may be used to
+** override this behaviour. The first parameter passed to this operation
+** is an integer - positive to disable checkpoints-on-close, or zero (the
+** default) to enable them, and negative to leave the setting unchanged.
+** The second parameter is a pointer to an integer
+** into which is written 0 or 1 to indicate whether checkpoints-on-close
+** have been disabled - 0 if they are not disabled, 1 if they are.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_ENABLE_QPSG]] <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt>
+** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates
+** the [query planner stability guarantee] (QPSG). When the QPSG is active,
+** a single SQL query statement will always use the same algorithm regardless
+** of values of [bound parameters].)^ The QPSG disables some query optimizations
+** that look at the values of bound parameters, which can make some queries
+** slower. But the QPSG has the advantage of more predictable behavior. With
+** the QPSG active, SQLite will always use the same query plan in the field as
+** was used during testing in the lab.
+** The first argument to this setting is an integer which is 0 to disable
+** the QPSG, positive to enable QPSG, or negative to leave the setting
+** unchanged. The second parameter is a pointer to an integer into which
+** is written 0 or 1 to indicate whether the QPSG is disabled or enabled
+** following this call.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_TRIGGER_EQP]] <dt>SQLITE_DBCONFIG_TRIGGER_EQP</dt>
+** <dd> By default, the output of EXPLAIN QUERY PLAN commands does not
+** include output for any operations performed by trigger programs. This
+** option is used to set or clear (the default) a flag that governs this
+** behavior. The first parameter passed to this operation is an integer -
+** positive to enable output for trigger programs, or zero to disable it,
+** or negative to leave the setting unchanged.
+** The second parameter is a pointer to an integer into which is written
+** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if
+** it is not disabled, 1 if it is.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_RESET_DATABASE]] <dt>SQLITE_DBCONFIG_RESET_DATABASE</dt>
+** <dd> Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run
+** [VACUUM] in order to reset a database back to an empty database
+** with no schema and no content. The following process works even for
+** a badly corrupted database file:
+** <ol>
+** <li> If the database connection is newly opened, make sure it has read the
+** database schema by preparing then discarding some query against the
+** database, or calling tdsqlite3_table_column_metadata(), ignoring any
+** errors. This step is only necessary if the application desires to keep
+** the database in WAL mode after the reset if it was in WAL mode before
+** the reset.
+** <li> tdsqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
+** <li> [tdsqlite3_exec](db, "[VACUUM]", 0, 0, 0);
+** <li> tdsqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
+** </ol>
+** Because resetting a database is destructive and irreversible, the
+** process requires the use of this obscure API and multiple steps to help
+** ensure that it does not happen by accident.
+**
+** [[SQLITE_DBCONFIG_DEFENSIVE]] <dt>SQLITE_DBCONFIG_DEFENSIVE</dt>
+** <dd>The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the
+** "defensive" flag for a database connection. When the defensive
+** flag is enabled, language features that allow ordinary SQL to
+** deliberately corrupt the database file are disabled. The disabled
+** features include but are not limited to the following:
+** <ul>
+** <li> The [PRAGMA writable_schema=ON] statement.
+** <li> The [PRAGMA journal_mode=OFF] statement.
+** <li> Writes to the [sqlite_dbpage] virtual table.
+** <li> Direct writes to [shadow tables].
+** </ul>
+** </dd>
+**
+** [[SQLITE_DBCONFIG_WRITABLE_SCHEMA]] <dt>SQLITE_DBCONFIG_WRITABLE_SCHEMA</dt>
+** <dd>The SQLITE_DBCONFIG_WRITABLE_SCHEMA option activates or deactivates the
+** "writable_schema" flag. This has the same effect and is logically equivalent
+** to setting [PRAGMA writable_schema=ON] or [PRAGMA writable_schema=OFF].
+** The first argument to this setting is an integer which is 0 to disable
+** the writable_schema, positive to enable writable_schema, or negative to
+** leave the setting unchanged. The second parameter is a pointer to an
+** integer into which is written 0 or 1 to indicate whether the writable_schema
+** is enabled or disabled following this call.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]]
+** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt>
+** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates
+** the legacy behavior of the [ALTER TABLE RENAME] command such it
+** behaves as it did prior to [version 3.24.0] (2018-06-04). See the
+** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for
+** additional information. This feature can also be turned on and off
+** using the [PRAGMA legacy_alter_table] statement.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_DQS_DML]]
+** <dt>SQLITE_DBCONFIG_DQS_DML</td>
+** <dd>The SQLITE_DBCONFIG_DQS_DML option activates or deactivates
+** the legacy [double-quoted string literal] misfeature for DML statements
+** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The
+** default value of this setting is determined by the [-DSQLITE_DQS]
+** compile-time option.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_DQS_DDL]]
+** <dt>SQLITE_DBCONFIG_DQS_DDL</td>
+** <dd>The SQLITE_DBCONFIG_DQS option activates or deactivates
+** the legacy [double-quoted string literal] misfeature for DDL statements,
+** such as CREATE TABLE and CREATE INDEX. The
+** default value of this setting is determined by the [-DSQLITE_DQS]
+** compile-time option.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]]
+** <dt>SQLITE_DBCONFIG_TRUSTED_SCHEMA</td>
+** <dd>The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to
+** assume that database schemas (the contents of the [sqlite_master] tables)
+** are untainted by malicious content.
+** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite
+** takes additional defensive steps to protect the application from harm
+** including:
+** <ul>
+** <li> Prohibit the use of SQL functions inside triggers, views,
+** CHECK constraints, DEFAULT clauses, expression indexes,
+** partial indexes, or generated columns
+** unless those functions are tagged with [SQLITE_INNOCUOUS].
+** <li> Prohibit the use of virtual tables inside of triggers or views
+** unless those virtual tables are tagged with [SQLITE_VTAB_INNOCUOUS].
+** </ul>
+** This setting defaults to "on" for legacy compatibility, however
+** all applications are advised to turn it off if possible. This setting
+** can also be controlled using the [PRAGMA trusted_schema] statement.
+** </dd>
+**
+** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]]
+** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</td>
+** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
+** the legacy file format flag. When activated, this flag causes all newly
+** created database file to have a schema format version number (the 4-byte
+** integer found at offset 44 into the database header) of 1. This in turn
+** means that the resulting database file will be readable and writable by
+** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting,
+** newly created databases are generally not understandable by SQLite versions
+** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there
+** is now scarcely any need to generated database files that are compatible
+** all the way back to version 3.0.0, and so this setting is of little
+** practical use, but is provided so that SQLite can continue to claim the
+** ability to generate new database files that are compatible with version
+** 3.0.0.
+** <p>Note that when the SQLITE_DBCONFIG_LEGACY_FILE_FORMAT setting is on,
+** the [VACUUM] command will fail with an obscure error when attempting to
+** process a table with generated columns and a descending index. This is
+** not considered a bug since SQLite versions 3.3.0 and earlier do not support
+** either generated columns or decending indexes.
+** </dd>
** </dl>
*/
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
@@ -1995,21 +2322,33 @@ struct sqlite3_mem_methods {
#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */
-
+#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */
+#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */
+#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */
+#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */
+#define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */
+#define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011 /* int int* */
+#define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */
+#define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */
+#define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */
+#define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */
+#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */
+#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */
+#define SQLITE_DBCONFIG_MAX 1017 /* Largest DBCONFIG */
/*
** CAPI3REF: Enable Or Disable Extended Result Codes
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_extended_result_codes() routine enables or disables the
+** ^The tdsqlite3_extended_result_codes() routine enables or disables the
** [extended result codes] feature of SQLite. ^The extended result
** codes are disabled by default for historical compatibility.
*/
-SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff);
+SQLITE_API int tdsqlite3_extended_result_codes(tdsqlite3*, int onoff);
/*
** CAPI3REF: Last Insert Rowid
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^Each entry in most SQLite tables (except for [WITHOUT ROWID] tables)
** has a unique 64-bit signed
@@ -2019,20 +2358,30 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff);
** the table has a column of type [INTEGER PRIMARY KEY] then that column
** is another alias for the rowid.
**
-** ^The sqlite3_last_insert_rowid(D) interface returns the [rowid] of the
-** most recent successful [INSERT] into a rowid table or [virtual table]
-** on database connection D.
-** ^Inserts into [WITHOUT ROWID] tables are not recorded.
-** ^If no successful [INSERT]s into rowid tables
-** have ever occurred on the database connection D,
-** then sqlite3_last_insert_rowid(D) returns zero.
-**
-** ^(If an [INSERT] occurs within a trigger or within a [virtual table]
-** method, then this routine will return the [rowid] of the inserted
-** row as long as the trigger or virtual table method is running.
-** But once the trigger or virtual table method ends, the value returned
-** by this routine reverts to what it was before the trigger or virtual
-** table method began.)^
+** ^The tdsqlite3_last_insert_rowid(D) interface usually returns the [rowid] of
+** the most recent successful [INSERT] into a rowid table or [virtual table]
+** on database connection D. ^Inserts into [WITHOUT ROWID] tables are not
+** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred
+** on the database connection D, then tdsqlite3_last_insert_rowid(D) returns
+** zero.
+**
+** As well as being set automatically as rows are inserted into database
+** tables, the value returned by this function may be set explicitly by
+** [tdsqlite3_set_last_insert_rowid()]
+**
+** Some virtual table implementations may INSERT rows into rowid tables as
+** part of committing a transaction (e.g. to flush data accumulated in memory
+** to disk). In this case subsequent calls to this function return the rowid
+** associated with these internal INSERT operations, which leads to
+** unintuitive results. Virtual table implementations that do write to rowid
+** tables in this way can avoid this problem by restoring the original
+** rowid value using [tdsqlite3_set_last_insert_rowid()] before returning
+** control to the user.
+**
+** ^(If an [INSERT] occurs within a trigger then this routine will
+** return the [rowid] of the inserted row as long as the trigger is
+** running. Once the trigger program ends, the value returned
+** by this routine reverts to what it was before the trigger was fired.)^
**
** ^An [INSERT] that fails due to a constraint violation is not a
** successful [INSERT] and does not change the value returned by this
@@ -2051,17 +2400,27 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff);
** [last_insert_rowid() SQL function].
**
** If a separate thread performs a new [INSERT] on the same
-** database connection while the [sqlite3_last_insert_rowid()]
+** database connection while the [tdsqlite3_last_insert_rowid()]
** function is running and thus changes the last insert [rowid],
-** then the value returned by [sqlite3_last_insert_rowid()] is
+** then the value returned by [tdsqlite3_last_insert_rowid()] is
** unpredictable and might not equal either the old or the new
** last insert [rowid].
*/
-SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
+SQLITE_API tdsqlite3_int64 tdsqlite3_last_insert_rowid(tdsqlite3*);
+
+/*
+** CAPI3REF: Set the Last Insert Rowid value.
+** METHOD: tdsqlite3
+**
+** The tdsqlite3_set_last_insert_rowid(D, R) method allows the application to
+** set the value returned by calling tdsqlite3_last_insert_rowid(D) to R
+** without inserting a row into the database.
+*/
+SQLITE_API void tdsqlite3_set_last_insert_rowid(tdsqlite3*,tdsqlite3_int64);
/*
** CAPI3REF: Count The Number Of Rows Modified
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This function returns the number of rows modified, inserted or
** deleted by the most recently completed INSERT, UPDATE or DELETE
@@ -2075,24 +2434,24 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
**
** Changes to a view that are intercepted by
** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value
-** returned by sqlite3_changes() immediately after an INSERT, UPDATE or
+** returned by tdsqlite3_changes() immediately after an INSERT, UPDATE or
** DELETE statement run on a view is always zero. Only changes made to real
** tables are counted.
**
-** Things are more complicated if the sqlite3_changes() function is
+** Things are more complicated if the tdsqlite3_changes() function is
** executed while a trigger program is running. This may happen if the
** program uses the [changes() SQL function], or if some other callback
-** function invokes sqlite3_changes() directly. Essentially:
+** function invokes tdsqlite3_changes() directly. Essentially:
**
** <ul>
** <li> ^(Before entering a trigger program the value returned by
-** sqlite3_changes() function is saved. After the trigger program
+** tdsqlite3_changes() function is saved. After the trigger program
** has finished, the original value is restored.)^
**
** <li> ^(Within a trigger program each INSERT, UPDATE and DELETE
-** statement sets the value returned by sqlite3_changes()
+** statement sets the value returned by tdsqlite3_changes()
** upon completion as normal. Of course, this value will not include
-** any changes performed by sub-triggers, as the sqlite3_changes()
+** any changes performed by sub-triggers, as the tdsqlite3_changes()
** value will be saved and restored after each sub-trigger has run.)^
** </ul>
**
@@ -2103,42 +2462,60 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*);
** program, the value returned reflects the number of rows modified by the
** previous INSERT, UPDATE or DELETE statement within the same trigger.
**
-** See also the [sqlite3_total_changes()] interface, the
-** [count_changes pragma], and the [changes() SQL function].
-**
** If a separate thread makes changes on the same database connection
-** while [sqlite3_changes()] is running then the value returned
+** while [tdsqlite3_changes()] is running then the value returned
** is unpredictable and not meaningful.
+**
+** See also:
+** <ul>
+** <li> the [tdsqlite3_total_changes()] interface
+** <li> the [count_changes pragma]
+** <li> the [changes() SQL function]
+** <li> the [data_version pragma]
+** </ul>
*/
-SQLITE_API int sqlite3_changes(sqlite3*);
+SQLITE_API int tdsqlite3_changes(tdsqlite3*);
/*
** CAPI3REF: Total Number Of Rows Modified
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This function returns the total number of rows inserted, modified or
** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed
** since the database connection was opened, including those executed as
** part of trigger programs. ^Executing any other type of SQL statement
-** does not affect the value returned by sqlite3_total_changes().
+** does not affect the value returned by tdsqlite3_total_changes().
**
** ^Changes made as part of [foreign key actions] are included in the
** count, but those made as part of REPLACE constraint resolution are
** not. ^Changes to a view that are intercepted by INSTEAD OF triggers
** are not counted.
-**
-** See also the [sqlite3_changes()] interface, the
-** [count_changes pragma], and the [total_changes() SQL function].
**
+** The [tdsqlite3_total_changes(D)] interface only reports the number
+** of rows that changed due to SQL statement run against database
+** connection D. Any changes by other database connections are ignored.
+** To detect changes against a database file from other database
+** connections use the [PRAGMA data_version] command or the
+** [SQLITE_FCNTL_DATA_VERSION] [file control].
+**
** If a separate thread makes changes on the same database connection
-** while [sqlite3_total_changes()] is running then the value
+** while [tdsqlite3_total_changes()] is running then the value
** returned is unpredictable and not meaningful.
+**
+** See also:
+** <ul>
+** <li> the [tdsqlite3_changes()] interface
+** <li> the [count_changes pragma]
+** <li> the [changes() SQL function]
+** <li> the [data_version pragma]
+** <li> the [SQLITE_FCNTL_DATA_VERSION] [file control]
+** </ul>
*/
-SQLITE_API int sqlite3_total_changes(sqlite3*);
+SQLITE_API int tdsqlite3_total_changes(tdsqlite3*);
/*
** CAPI3REF: Interrupt A Long-Running Query
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This function causes any pending database operation to abort and
** return at its earliest opportunity. This routine is typically
@@ -2149,10 +2526,10 @@ SQLITE_API int sqlite3_total_changes(sqlite3*);
** ^It is safe to call this routine from a thread different from the
** thread that is currently running the database operation. But it
** is not safe to call this routine with a [database connection] that
-** is closed or might close before sqlite3_interrupt() returns.
+** is closed or might close before tdsqlite3_interrupt() returns.
**
** ^If an SQL operation is very nearly finished at the time when
-** sqlite3_interrupt() is called, then it might not have an opportunity
+** tdsqlite3_interrupt() is called, then it might not have an opportunity
** to be interrupted and might continue to completion.
**
** ^An SQL operation that is interrupted will return [SQLITE_INTERRUPT].
@@ -2160,21 +2537,18 @@ SQLITE_API int sqlite3_total_changes(sqlite3*);
** that is inside an explicit transaction, then the entire transaction
** will be rolled back automatically.
**
-** ^The sqlite3_interrupt(D) call is in effect until all currently running
+** ^The tdsqlite3_interrupt(D) call is in effect until all currently running
** SQL statements on [database connection] D complete. ^Any new SQL statements
-** that are started after the sqlite3_interrupt() call and before the
-** running statements reaches zero are interrupted as if they had been
-** running prior to the sqlite3_interrupt() call. ^New SQL statements
+** that are started after the tdsqlite3_interrupt() call and before the
+** running statement count reaches zero are interrupted as if they had been
+** running prior to the tdsqlite3_interrupt() call. ^New SQL statements
** that are started after the running statement count reaches zero are
-** not effected by the sqlite3_interrupt().
-** ^A call to sqlite3_interrupt(D) that occurs when there are no running
+** not effected by the tdsqlite3_interrupt().
+** ^A call to tdsqlite3_interrupt(D) that occurs when there are no running
** SQL statements is a no-op and has no effect on SQL statements
-** that are started after the sqlite3_interrupt() call returns.
-**
-** If the database connection closes while [sqlite3_interrupt()]
-** is running then bad things will likely happen.
+** that are started after the tdsqlite3_interrupt() call returns.
*/
-SQLITE_API void sqlite3_interrupt(sqlite3*);
+SQLITE_API void tdsqlite3_interrupt(tdsqlite3*);
/*
** CAPI3REF: Determine If An SQL Statement Is Complete
@@ -2197,40 +2571,40 @@ SQLITE_API void sqlite3_interrupt(sqlite3*);
** ^These routines do not parse the SQL statements thus
** will not detect syntactically incorrect SQL.
**
-** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior
-** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked
-** automatically by sqlite3_complete16(). If that initialization fails,
-** then the return value from sqlite3_complete16() will be non-zero
+** ^(If SQLite has not been initialized using [tdsqlite3_initialize()] prior
+** to invoking tdsqlite3_complete16() then tdsqlite3_initialize() is invoked
+** automatically by tdsqlite3_complete16(). If that initialization fails,
+** then the return value from tdsqlite3_complete16() will be non-zero
** regardless of whether or not the input SQL is complete.)^
**
-** The input to [sqlite3_complete()] must be a zero-terminated
+** The input to [tdsqlite3_complete()] must be a zero-terminated
** UTF-8 string.
**
-** The input to [sqlite3_complete16()] must be a zero-terminated
+** The input to [tdsqlite3_complete16()] must be a zero-terminated
** UTF-16 string in native byte order.
*/
-SQLITE_API int sqlite3_complete(const char *sql);
-SQLITE_API int sqlite3_complete16(const void *sql);
+SQLITE_API int tdsqlite3_complete(const char *sql);
+SQLITE_API int tdsqlite3_complete16(const void *sql);
/*
** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors
** KEYWORDS: {busy-handler callback} {busy handler}
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X
+** ^The tdsqlite3_busy_handler(D,X,P) routine sets a callback function X
** that might be invoked with argument P whenever
** an attempt is made to access a database table associated with
** [database connection] D when another thread
** or process has the table locked.
-** The sqlite3_busy_handler() interface is used to implement
-** [sqlite3_busy_timeout()] and [PRAGMA busy_timeout].
+** The tdsqlite3_busy_handler() interface is used to implement
+** [tdsqlite3_busy_timeout()] and [PRAGMA busy_timeout].
**
** ^If the busy callback is NULL, then [SQLITE_BUSY]
** is returned immediately upon encountering the lock. ^If the busy callback
** is not NULL, then the callback might be invoked with two arguments.
**
** ^The first argument to the busy handler is a copy of the void* pointer which
-** is the third argument to sqlite3_busy_handler(). ^The second argument to
+** is the third argument to tdsqlite3_busy_handler(). ^The second argument to
** the busy handler callback is the number of times that the busy handler has
** been invoked previously for the same locking event. ^If the
** busy callback returns 0, then no additional attempts are made to
@@ -2259,7 +2633,7 @@ SQLITE_API int sqlite3_complete16(const void *sql);
**
** ^(There can only be a single busy handler defined for each
** [database connection]. Setting a new busy handler clears any
-** previously set handler.)^ ^Note that calling [sqlite3_busy_timeout()]
+** previously set handler.)^ ^Note that calling [tdsqlite3_busy_timeout()]
** or evaluating [PRAGMA busy_timeout=N] will change the
** busy handler and thus clear any previously set busy handler.
**
@@ -2271,17 +2645,17 @@ SQLITE_API int sqlite3_complete16(const void *sql);
** A busy handler must not close the database connection
** or [prepared statement] that invoked the busy handler.
*/
-SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
+SQLITE_API int tdsqlite3_busy_handler(tdsqlite3*,int(*)(void*,int),void*);
/*
** CAPI3REF: Set A Busy Timeout
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^This routine sets a [sqlite3_busy_handler | busy handler] that sleeps
+** ^This routine sets a [tdsqlite3_busy_handler | busy handler] that sleeps
** for a specified amount of time when a table is locked. ^The handler
** will sleep multiple times until at least "ms" milliseconds of sleeping
** have accumulated. ^After at least "ms" milliseconds of sleeping,
-** the handler returns 0 which causes [sqlite3_step()] to return
+** the handler returns 0 which causes [tdsqlite3_step()] to return
** [SQLITE_BUSY].
**
** ^Calling this routine with an argument less than or equal to zero
@@ -2289,22 +2663,22 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
**
** ^(There can only be a single busy handler for a particular
** [database connection] at any given moment. If another busy handler
-** was defined (using [sqlite3_busy_handler()]) prior to calling
+** was defined (using [tdsqlite3_busy_handler()]) prior to calling
** this routine, that other busy handler is cleared.)^
**
** See also: [PRAGMA busy_timeout]
*/
-SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
+SQLITE_API int tdsqlite3_busy_timeout(tdsqlite3*, int ms);
/*
** CAPI3REF: Convenience Routines For Running Queries
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** This is a legacy interface that is preserved for backwards compatibility.
** Use of this interface is not recommended.
**
** Definition: A <b>result table</b> is memory data structure created by the
-** [sqlite3_get_table()] interface. A result table records the
+** [tdsqlite3_get_table()] interface. A result table records the
** complete query results from one or more queries.
**
** The table conceptually has a number of rows and columns. But
@@ -2317,11 +2691,11 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
** to zero-terminated strings that contain the names of the columns.
** The remaining entries all point to query results. NULL values result
** in NULL pointers. All other values are in their UTF-8 zero-terminated
-** string representation as returned by [sqlite3_column_text()].
+** string representation as returned by [tdsqlite3_column_text()].
**
** A result table might consist of one or more memory allocations.
-** It is not safe to pass a result table directly to [sqlite3_free()].
-** A result table should be deallocated using [sqlite3_free_table()].
+** It is not safe to pass a result table directly to [tdsqlite3_free()].
+** A result table should be deallocated using [tdsqlite3_free_table()].
**
** ^(As an example of the result table format, suppose a query result
** is as follows:
@@ -2334,9 +2708,9 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
** Cindy | 21
** </pre></blockquote>
**
-** There are two column (M==2) and three rows (N==3). Thus the
+** There are two columns (M==2) and three rows (N==3). Thus the
** result table has 8 entries. Suppose the result table is stored
-** in an array names azResult. Then azResult holds this content:
+** in an array named azResult. Then azResult holds this content:
**
** <blockquote><pre>
** azResult&#91;0] = "Name";
@@ -2349,265 +2723,188 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
** azResult&#91;7] = "21";
** </pre></blockquote>)^
**
-** ^The sqlite3_get_table() function evaluates one or more
+** ^The tdsqlite3_get_table() function evaluates one or more
** semicolon-separated SQL statements in the zero-terminated UTF-8
** string of its 2nd parameter and returns a result table to the
** pointer given in its 3rd parameter.
**
-** After the application has finished with the result from sqlite3_get_table(),
-** it must pass the result table pointer to sqlite3_free_table() in order to
+** After the application has finished with the result from tdsqlite3_get_table(),
+** it must pass the result table pointer to tdsqlite3_free_table() in order to
** release the memory that was malloced. Because of the way the
-** [sqlite3_malloc()] happens within sqlite3_get_table(), the calling
-** function must not try to call [sqlite3_free()] directly. Only
-** [sqlite3_free_table()] is able to release the memory properly and safely.
+** [tdsqlite3_malloc()] happens within tdsqlite3_get_table(), the calling
+** function must not try to call [tdsqlite3_free()] directly. Only
+** [tdsqlite3_free_table()] is able to release the memory properly and safely.
**
-** The sqlite3_get_table() interface is implemented as a wrapper around
-** [sqlite3_exec()]. The sqlite3_get_table() routine does not have access
+** The tdsqlite3_get_table() interface is implemented as a wrapper around
+** [tdsqlite3_exec()]. The tdsqlite3_get_table() routine does not have access
** to any internal data structures of SQLite. It uses only the public
** interface defined here. As a consequence, errors that occur in the
-** wrapper layer outside of the internal [sqlite3_exec()] call are not
-** reflected in subsequent calls to [sqlite3_errcode()] or
-** [sqlite3_errmsg()].
+** wrapper layer outside of the internal [tdsqlite3_exec()] call are not
+** reflected in subsequent calls to [tdsqlite3_errcode()] or
+** [tdsqlite3_errmsg()].
*/
-SQLITE_API int sqlite3_get_table(
- sqlite3 *db, /* An open database */
+SQLITE_API int tdsqlite3_get_table(
+ tdsqlite3 *db, /* An open database */
const char *zSql, /* SQL to be evaluated */
char ***pazResult, /* Results of the query */
int *pnRow, /* Number of result rows written here */
int *pnColumn, /* Number of result columns written here */
char **pzErrmsg /* Error msg written here */
);
-SQLITE_API void sqlite3_free_table(char **result);
+SQLITE_API void tdsqlite3_free_table(char **result);
/*
** CAPI3REF: Formatted String Printing Functions
**
** These routines are work-alikes of the "printf()" family of functions
** from the standard C library.
-** These routines understand most of the common K&R formatting options,
-** plus some additional non-standard formats, detailed below.
-** Note that some of the more obscure formatting options from recent
-** C-library standards are omitted from this implementation.
+** These routines understand most of the common formatting options from
+** the standard library printf()
+** plus some additional non-standard formats ([%q], [%Q], [%w], and [%z]).
+** See the [built-in printf()] documentation for details.
**
-** ^The sqlite3_mprintf() and sqlite3_vmprintf() routines write their
-** results into memory obtained from [sqlite3_malloc()].
+** ^The tdsqlite3_mprintf() and tdsqlite3_vmprintf() routines write their
+** results into memory obtained from [tdsqlite3_malloc64()].
** The strings returned by these two routines should be
-** released by [sqlite3_free()]. ^Both routines return a
-** NULL pointer if [sqlite3_malloc()] is unable to allocate enough
+** released by [tdsqlite3_free()]. ^Both routines return a
+** NULL pointer if [tdsqlite3_malloc64()] is unable to allocate enough
** memory to hold the resulting string.
**
-** ^(The sqlite3_snprintf() routine is similar to "snprintf()" from
+** ^(The tdsqlite3_snprintf() routine is similar to "snprintf()" from
** the standard C library. The result is written into the
** buffer supplied as the second parameter whose size is given by
** the first parameter. Note that the order of the
** first two parameters is reversed from snprintf().)^ This is an
** historical accident that cannot be fixed without breaking
-** backwards compatibility. ^(Note also that sqlite3_snprintf()
+** backwards compatibility. ^(Note also that tdsqlite3_snprintf()
** returns a pointer to its buffer instead of the number of
** characters actually written into the buffer.)^ We admit that
** the number of characters written would be a more useful return
-** value but we cannot change the implementation of sqlite3_snprintf()
+** value but we cannot change the implementation of tdsqlite3_snprintf()
** now without breaking compatibility.
**
-** ^As long as the buffer size is greater than zero, sqlite3_snprintf()
+** ^As long as the buffer size is greater than zero, tdsqlite3_snprintf()
** guarantees that the buffer is always zero-terminated. ^The first
** parameter "n" is the total size of the buffer, including space for
** the zero terminator. So the longest string that can be completely
** written will be n-1 characters.
**
-** ^The sqlite3_vsnprintf() routine is a varargs version of sqlite3_snprintf().
-**
-** These routines all implement some additional formatting
-** options that are useful for constructing SQL statements.
-** All of the usual printf() formatting options apply. In addition, there
-** is are "%q", "%Q", "%w" and "%z" options.
-**
-** ^(The %q option works like %s in that it substitutes a nul-terminated
-** string from the argument list. But %q also doubles every '\'' character.
-** %q is designed for use inside a string literal.)^ By doubling each '\''
-** character it escapes that character and allows it to be inserted into
-** the string.
-**
-** For example, assume the string variable zText contains text as follows:
-**
-** <blockquote><pre>
-** char *zText = "It's a happy day!";
-** </pre></blockquote>
-**
-** One can use this text in an SQL statement as follows:
-**
-** <blockquote><pre>
-** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES('%q')", zText);
-** sqlite3_exec(db, zSQL, 0, 0, 0);
-** sqlite3_free(zSQL);
-** </pre></blockquote>
-**
-** Because the %q format string is used, the '\'' character in zText
-** is escaped and the SQL generated is as follows:
-**
-** <blockquote><pre>
-** INSERT INTO table1 VALUES('It''s a happy day!')
-** </pre></blockquote>
-**
-** This is correct. Had we used %s instead of %q, the generated SQL
-** would have looked like this:
-**
-** <blockquote><pre>
-** INSERT INTO table1 VALUES('It's a happy day!');
-** </pre></blockquote>
-**
-** This second example is an SQL syntax error. As a general rule you should
-** always use %q instead of %s when inserting text into a string literal.
-**
-** ^(The %Q option works like %q except it also adds single quotes around
-** the outside of the total string. Additionally, if the parameter in the
-** argument list is a NULL pointer, %Q substitutes the text "NULL" (without
-** single quotes).)^ So, for example, one could say:
-**
-** <blockquote><pre>
-** char *zSQL = sqlite3_mprintf("INSERT INTO table VALUES(%Q)", zText);
-** sqlite3_exec(db, zSQL, 0, 0, 0);
-** sqlite3_free(zSQL);
-** </pre></blockquote>
-**
-** The code above will render a correct SQL statement in the zSQL
-** variable even if the zText variable is a NULL pointer.
+** ^The tdsqlite3_vsnprintf() routine is a varargs version of tdsqlite3_snprintf().
**
-** ^(The "%w" formatting option is like "%q" except that it expects to
-** be contained within double-quotes instead of single quotes, and it
-** escapes the double-quote character instead of the single-quote
-** character.)^ The "%w" formatting option is intended for safely inserting
-** table and column names into a constructed SQL statement.
-**
-** ^(The "%z" formatting option works like "%s" but with the
-** addition that after the string has been read and copied into
-** the result, [sqlite3_free()] is called on the input string.)^
+** See also: [built-in printf()], [printf() SQL function]
*/
-SQLITE_API char *sqlite3_mprintf(const char*,...);
-SQLITE_API char *sqlite3_vmprintf(const char*, va_list);
-SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...);
-SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
+SQLITE_API char *tdsqlite3_mprintf(const char*,...);
+SQLITE_API char *tdsqlite3_vmprintf(const char*, va_list);
+SQLITE_API char *tdsqlite3_snprintf(int,char*,const char*, ...);
+SQLITE_API char *tdsqlite3_vsnprintf(int,char*,const char*, va_list);
/*
** CAPI3REF: Memory Allocation Subsystem
**
** The SQLite core uses these three routines for all of its own
** internal memory allocation needs. "Core" in the previous sentence
-** does not include operating-system specific VFS implementation. The
+** does not include operating-system specific [VFS] implementation. The
** Windows VFS uses native malloc() and free() for some operations.
**
-** ^The sqlite3_malloc() routine returns a pointer to a block
+** ^The tdsqlite3_malloc() routine returns a pointer to a block
** of memory at least N bytes in length, where N is the parameter.
-** ^If sqlite3_malloc() is unable to obtain sufficient free
+** ^If tdsqlite3_malloc() is unable to obtain sufficient free
** memory, it returns a NULL pointer. ^If the parameter N to
-** sqlite3_malloc() is zero or negative then sqlite3_malloc() returns
+** tdsqlite3_malloc() is zero or negative then tdsqlite3_malloc() returns
** a NULL pointer.
**
-** ^The sqlite3_malloc64(N) routine works just like
-** sqlite3_malloc(N) except that N is an unsigned 64-bit integer instead
+** ^The tdsqlite3_malloc64(N) routine works just like
+** tdsqlite3_malloc(N) except that N is an unsigned 64-bit integer instead
** of a signed 32-bit integer.
**
-** ^Calling sqlite3_free() with a pointer previously returned
-** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
-** that it might be reused. ^The sqlite3_free() routine is
+** ^Calling tdsqlite3_free() with a pointer previously returned
+** by tdsqlite3_malloc() or tdsqlite3_realloc() releases that memory so
+** that it might be reused. ^The tdsqlite3_free() routine is
** a no-op if is called with a NULL pointer. Passing a NULL pointer
-** to sqlite3_free() is harmless. After being freed, memory
+** to tdsqlite3_free() is harmless. After being freed, memory
** should neither be read nor written. Even reading previously freed
** memory might result in a segmentation fault or other severe error.
** Memory corruption, a segmentation fault, or other severe error
-** might result if sqlite3_free() is called with a non-NULL pointer that
-** was not obtained from sqlite3_malloc() or sqlite3_realloc().
+** might result if tdsqlite3_free() is called with a non-NULL pointer that
+** was not obtained from tdsqlite3_malloc() or tdsqlite3_realloc().
**
-** ^The sqlite3_realloc(X,N) interface attempts to resize a
+** ^The tdsqlite3_realloc(X,N) interface attempts to resize a
** prior memory allocation X to be at least N bytes.
-** ^If the X parameter to sqlite3_realloc(X,N)
+** ^If the X parameter to tdsqlite3_realloc(X,N)
** is a NULL pointer then its behavior is identical to calling
-** sqlite3_malloc(N).
-** ^If the N parameter to sqlite3_realloc(X,N) is zero or
+** tdsqlite3_malloc(N).
+** ^If the N parameter to tdsqlite3_realloc(X,N) is zero or
** negative then the behavior is exactly the same as calling
-** sqlite3_free(X).
-** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation
+** tdsqlite3_free(X).
+** ^tdsqlite3_realloc(X,N) returns a pointer to a memory allocation
** of at least N bytes in size or NULL if insufficient memory is available.
** ^If M is the size of the prior allocation, then min(N,M) bytes
** of the prior allocation are copied into the beginning of buffer returned
-** by sqlite3_realloc(X,N) and the prior allocation is freed.
-** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the
+** by tdsqlite3_realloc(X,N) and the prior allocation is freed.
+** ^If tdsqlite3_realloc(X,N) returns NULL and N is positive, then the
** prior allocation is not freed.
**
-** ^The sqlite3_realloc64(X,N) interfaces works the same as
-** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
+** ^The tdsqlite3_realloc64(X,N) interfaces works the same as
+** tdsqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
** of a 32-bit signed integer.
**
-** ^If X is a memory allocation previously obtained from sqlite3_malloc(),
-** sqlite3_malloc64(), sqlite3_realloc(), or sqlite3_realloc64(), then
-** sqlite3_msize(X) returns the size of that memory allocation in bytes.
-** ^The value returned by sqlite3_msize(X) might be larger than the number
+** ^If X is a memory allocation previously obtained from tdsqlite3_malloc(),
+** tdsqlite3_malloc64(), tdsqlite3_realloc(), or tdsqlite3_realloc64(), then
+** tdsqlite3_msize(X) returns the size of that memory allocation in bytes.
+** ^The value returned by tdsqlite3_msize(X) might be larger than the number
** of bytes requested when X was allocated. ^If X is a NULL pointer then
-** sqlite3_msize(X) returns zero. If X points to something that is not
+** tdsqlite3_msize(X) returns zero. If X points to something that is not
** the beginning of memory allocation, or if it points to a formerly
** valid memory allocation that has now been freed, then the behavior
-** of sqlite3_msize(X) is undefined and possibly harmful.
+** of tdsqlite3_msize(X) is undefined and possibly harmful.
**
-** ^The memory returned by sqlite3_malloc(), sqlite3_realloc(),
-** sqlite3_malloc64(), and sqlite3_realloc64()
+** ^The memory returned by tdsqlite3_malloc(), tdsqlite3_realloc(),
+** tdsqlite3_malloc64(), and tdsqlite3_realloc64()
** is always aligned to at least an 8 byte boundary, or to a
** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time
** option is used.
**
-** In SQLite version 3.5.0 and 3.5.1, it was possible to define
-** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in
-** implementation of these routines to be omitted. That capability
-** is no longer provided. Only built-in memory allocators can be used.
-**
-** Prior to SQLite version 3.7.10, the Windows OS interface layer called
-** the system malloc() and free() directly when converting
-** filenames between the UTF-8 encoding used by SQLite
-** and whatever filename encoding is used by the particular Windows
-** installation. Memory allocation errors were detected, but
-** they were reported back as [SQLITE_CANTOPEN] or
-** [SQLITE_IOERR] rather than [SQLITE_NOMEM].
-**
-** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()]
+** The pointer arguments to [tdsqlite3_free()] and [tdsqlite3_realloc()]
** must be either NULL or else pointers obtained from a prior
-** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have
+** invocation of [tdsqlite3_malloc()] or [tdsqlite3_realloc()] that have
** not yet been released.
**
** The application must not read or write any part of
** a block of memory after it has been released using
-** [sqlite3_free()] or [sqlite3_realloc()].
+** [tdsqlite3_free()] or [tdsqlite3_realloc()].
*/
-SQLITE_API void *sqlite3_malloc(int);
-SQLITE_API void *sqlite3_malloc64(sqlite3_uint64);
-SQLITE_API void *sqlite3_realloc(void*, int);
-SQLITE_API void *sqlite3_realloc64(void*, sqlite3_uint64);
-SQLITE_API void sqlite3_free(void*);
-SQLITE_API sqlite3_uint64 sqlite3_msize(void*);
+SQLITE_API void *tdsqlite3_malloc(int);
+SQLITE_API void *tdsqlite3_malloc64(tdsqlite3_uint64);
+SQLITE_API void *tdsqlite3_realloc(void*, int);
+SQLITE_API void *tdsqlite3_realloc64(void*, tdsqlite3_uint64);
+SQLITE_API void tdsqlite3_free(void*);
+SQLITE_API tdsqlite3_uint64 tdsqlite3_msize(void*);
/*
** CAPI3REF: Memory Allocator Statistics
**
** SQLite provides these two interfaces for reporting on the status
-** of the [sqlite3_malloc()], [sqlite3_free()], and [sqlite3_realloc()]
+** of the [tdsqlite3_malloc()], [tdsqlite3_free()], and [tdsqlite3_realloc()]
** routines, which form the built-in memory allocation subsystem.
**
-** ^The [sqlite3_memory_used()] routine returns the number of bytes
+** ^The [tdsqlite3_memory_used()] routine returns the number of bytes
** of memory currently outstanding (malloced but not freed).
-** ^The [sqlite3_memory_highwater()] routine returns the maximum
-** value of [sqlite3_memory_used()] since the high-water mark
-** was last reset. ^The values returned by [sqlite3_memory_used()] and
-** [sqlite3_memory_highwater()] include any overhead
-** added by SQLite in its implementation of [sqlite3_malloc()],
+** ^The [tdsqlite3_memory_highwater()] routine returns the maximum
+** value of [tdsqlite3_memory_used()] since the high-water mark
+** was last reset. ^The values returned by [tdsqlite3_memory_used()] and
+** [tdsqlite3_memory_highwater()] include any overhead
+** added by SQLite in its implementation of [tdsqlite3_malloc()],
** but not overhead added by the any underlying system library
-** routines that [sqlite3_malloc()] may call.
+** routines that [tdsqlite3_malloc()] may call.
**
** ^The memory high-water mark is reset to the current value of
-** [sqlite3_memory_used()] if and only if the parameter to
-** [sqlite3_memory_highwater()] is true. ^The value returned
-** by [sqlite3_memory_highwater(1)] is the high-water mark
+** [tdsqlite3_memory_used()] if and only if the parameter to
+** [tdsqlite3_memory_highwater()] is true. ^The value returned
+** by [tdsqlite3_memory_highwater(1)] is the high-water mark
** prior to the reset.
*/
-SQLITE_API sqlite3_int64 sqlite3_memory_used(void);
-SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
+SQLITE_API tdsqlite3_int64 tdsqlite3_memory_used(void);
+SQLITE_API tdsqlite3_int64 tdsqlite3_memory_highwater(int resetFlag);
/*
** CAPI3REF: Pseudo-Random Number Generator
@@ -2615,7 +2912,7 @@ SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
** SQLite contains a high-quality pseudo-random number generator (PRNG) used to
** select random [ROWID | ROWIDs] when inserting new records into a table that
** already uses the largest possible [ROWID]. The PRNG is also used for
-** the build-in random() and randomblob() SQL functions. This interface allows
+** the built-in random() and randomblob() SQL functions. This interface allows
** applications to access the same PRNG for other purposes.
**
** ^A call to this routine stores N bytes of randomness into buffer P.
@@ -2624,23 +2921,25 @@ SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
** ^If this routine has not been previously called or if the previous
** call had N less than one or a NULL pointer for P, then the PRNG is
** seeded using randomness obtained from the xRandomness method of
-** the default [sqlite3_vfs] object.
+** the default [tdsqlite3_vfs] object.
** ^If the previous call to this routine had an N of 1 or more and a
** non-NULL P then the pseudo-randomness is generated
-** internally and without recourse to the [sqlite3_vfs] xRandomness
+** internally and without recourse to the [tdsqlite3_vfs] xRandomness
** method.
*/
-SQLITE_API void sqlite3_randomness(int N, void *P);
+SQLITE_API void tdsqlite3_randomness(int N, void *P);
/*
** CAPI3REF: Compile-Time Authorization Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
+** KEYWORDS: {authorizer callback}
**
** ^This routine registers an authorizer callback with a particular
** [database connection], supplied in the first argument.
** ^The authorizer callback is invoked as SQL statements are being compiled
-** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()],
-** [sqlite3_prepare16()] and [sqlite3_prepare16_v2()]. ^At various
+** by [tdsqlite3_prepare()] or its variants [tdsqlite3_prepare_v2()],
+** [tdsqlite3_prepare_v3()], [tdsqlite3_prepare16()], [tdsqlite3_prepare16_v2()],
+** and [tdsqlite3_prepare16_v3()]. ^At various
** points during the compilation process, as logic is being created
** to perform various actions, the authorizer callback is invoked to
** see if those actions are allowed. ^The authorizer callback should
@@ -2649,21 +2948,23 @@ SQLITE_API void sqlite3_randomness(int N, void *P);
** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be
** rejected with an error. ^If the authorizer callback returns
** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY]
-** then the [sqlite3_prepare_v2()] or equivalent call that triggered
+** then the [tdsqlite3_prepare_v2()] or equivalent call that triggered
** the authorizer will fail with an error message.
**
** When the callback returns [SQLITE_OK], that means the operation
** requested is ok. ^When the callback returns [SQLITE_DENY], the
-** [sqlite3_prepare_v2()] or equivalent call that triggered the
+** [tdsqlite3_prepare_v2()] or equivalent call that triggered the
** authorizer will fail with an error message explaining that
** access is denied.
**
** ^The first parameter to the authorizer callback is a copy of the third
-** parameter to the sqlite3_set_authorizer() interface. ^The second parameter
+** parameter to the tdsqlite3_set_authorizer() interface. ^The second parameter
** to the callback is an integer [SQLITE_COPY | action code] that specifies
** the particular action to be authorized. ^The third through sixth parameters
-** to the callback are zero-terminated strings that contain additional
-** details about the action to be authorized.
+** to the callback are either NULL pointers or zero-terminated strings
+** that contain additional details about the action to be authorized.
+** Applications must always be prepared to encounter a NULL pointer in any
+** of the third through the sixth parameters of the authorization callback.
**
** ^If the action code is [SQLITE_READ]
** and the callback returns [SQLITE_IGNORE] then the
@@ -2672,11 +2973,15 @@ SQLITE_API void sqlite3_randomness(int N, void *P);
** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE]
** return can be used to deny an untrusted user access to individual
** columns of a table.
+** ^When a table is referenced by a [SELECT] but no column values are
+** extracted from that table (for example in a query like
+** "SELECT count(*) FROM tab") then the [SQLITE_READ] authorizer callback
+** is invoked once for that table with a column name that is an empty string.
** ^If the action code is [SQLITE_DELETE] and the callback returns
** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the
** [truncate optimization] is disabled and all rows are deleted individually.
**
-** An authorizer is used when [sqlite3_prepare | preparing]
+** An authorizer is used when [tdsqlite3_prepare | preparing]
** SQL statements from an untrusted source, to ensure that the SQL statements
** do not try to access data they are not allowed to see, or that they do not
** try to execute malicious statements that damage the database. For
@@ -2684,37 +2989,37 @@ SQLITE_API void sqlite3_randomness(int N, void *P);
** SQL queries for evaluation by a database. But the application does
** not want the user to be able to make arbitrary changes to the
** database. An authorizer could then be put in place while the
-** user-entered SQL is being [sqlite3_prepare | prepared] that
+** user-entered SQL is being [tdsqlite3_prepare | prepared] that
** disallows everything except [SELECT] statements.
**
** Applications that need to process SQL from untrusted sources
-** might also consider lowering resource limits using [sqlite3_limit()]
+** might also consider lowering resource limits using [tdsqlite3_limit()]
** and limiting database size using the [max_page_count] [PRAGMA]
** in addition to using an authorizer.
**
** ^(Only a single authorizer can be in place on a database connection
-** at a time. Each call to sqlite3_set_authorizer overrides the
+** at a time. Each call to tdsqlite3_set_authorizer overrides the
** previous call.)^ ^Disable the authorizer by installing a NULL callback.
** The authorizer is disabled by default.
**
** The authorizer callback must not do anything that will modify
** the database connection that invoked the authorizer callback.
-** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their
+** Note that [tdsqlite3_prepare_v2()] and [tdsqlite3_step()] both modify their
** database connections for the meaning of "modify" in this paragraph.
**
-** ^When [sqlite3_prepare_v2()] is used to prepare a statement, the
-** statement might be re-prepared during [sqlite3_step()] due to a
+** ^When [tdsqlite3_prepare_v2()] is used to prepare a statement, the
+** statement might be re-prepared during [tdsqlite3_step()] due to a
** schema change. Hence, the application should ensure that the
-** correct authorizer callback remains in place during the [sqlite3_step()].
+** correct authorizer callback remains in place during the [tdsqlite3_step()].
**
** ^Note that the authorizer callback is invoked only during
-** [sqlite3_prepare()] or its variants. Authorization is not
-** performed during statement evaluation in [sqlite3_step()], unless
-** as stated in the previous paragraph, sqlite3_step() invokes
-** sqlite3_prepare_v2() to reprepare a statement after a schema change.
+** [tdsqlite3_prepare()] or its variants. Authorization is not
+** performed during statement evaluation in [tdsqlite3_step()], unless
+** as stated in the previous paragraph, tdsqlite3_step() invokes
+** tdsqlite3_prepare_v2() to reprepare a statement after a schema change.
*/
-SQLITE_API int sqlite3_set_authorizer(
- sqlite3*,
+SQLITE_API int tdsqlite3_set_authorizer(
+ tdsqlite3*,
int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
void *pUserData
);
@@ -2722,14 +3027,14 @@ SQLITE_API int sqlite3_set_authorizer(
/*
** CAPI3REF: Authorizer Return Codes
**
-** The [sqlite3_set_authorizer | authorizer callback function] must
+** The [tdsqlite3_set_authorizer | authorizer callback function] must
** return either [SQLITE_OK] or one of these two constants in order
** to signal SQLite whether or not the action is permitted. See the
-** [sqlite3_set_authorizer | authorizer documentation] for additional
+** [tdsqlite3_set_authorizer | authorizer documentation] for additional
** information.
**
** Note that SQLITE_IGNORE is also used as a [conflict resolution mode]
-** returned from the [sqlite3_vtab_on_conflict()] interface.
+** returned from the [tdsqlite3_vtab_on_conflict()] interface.
*/
#define SQLITE_DENY 1 /* Abort the SQL statement with an error */
#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */
@@ -2737,7 +3042,7 @@ SQLITE_API int sqlite3_set_authorizer(
/*
** CAPI3REF: Authorizer Action Codes
**
-** The [sqlite3_set_authorizer()] interface registers a callback function
+** The [tdsqlite3_set_authorizer()] interface registers a callback function
** that is invoked to authorize certain SQL statement actions. The
** second parameter to the callback is an integer code that specifies
** what action is being authorized. These are the integer action codes that
@@ -2791,48 +3096,48 @@ SQLITE_API int sqlite3_set_authorizer(
/*
** CAPI3REF: Tracing And Profiling Functions
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** These routines are deprecated. Use the [sqlite3_trace_v2()] interface
+** These routines are deprecated. Use the [tdsqlite3_trace_v2()] interface
** instead of the routines described here.
**
** These routines register callback functions that can be used for
** tracing and profiling the execution of SQL statements.
**
-** ^The callback function registered by sqlite3_trace() is invoked at
-** various times when an SQL statement is being run by [sqlite3_step()].
-** ^The sqlite3_trace() callback is invoked with a UTF-8 rendering of the
+** ^The callback function registered by tdsqlite3_trace() is invoked at
+** various times when an SQL statement is being run by [tdsqlite3_step()].
+** ^The tdsqlite3_trace() callback is invoked with a UTF-8 rendering of the
** SQL statement text as the statement first begins executing.
-** ^(Additional sqlite3_trace() callbacks might occur
+** ^(Additional tdsqlite3_trace() callbacks might occur
** as each triggered subprogram is entered. The callbacks for triggers
** contain a UTF-8 SQL comment that identifies the trigger.)^
**
** The [SQLITE_TRACE_SIZE_LIMIT] compile-time option can be used to limit
-** the length of [bound parameter] expansion in the output of sqlite3_trace().
+** the length of [bound parameter] expansion in the output of tdsqlite3_trace().
**
-** ^The callback function registered by sqlite3_profile() is invoked
+** ^The callback function registered by tdsqlite3_profile() is invoked
** as each SQL statement finishes. ^The profile callback contains
** the original statement text and an estimate of wall-clock time
** of how long that statement took to run. ^The profile callback
** time is in units of nanoseconds, however the current implementation
** is only capable of millisecond resolution so the six least significant
** digits in the time are meaningless. Future versions of SQLite
-** might provide greater resolution on the profiler callback. The
-** sqlite3_profile() function is considered experimental and is
-** subject to change in future versions of SQLite.
+** might provide greater resolution on the profiler callback. Invoking
+** either [tdsqlite3_trace()] or [tdsqlite3_trace_v2()] will cancel the
+** profile callback.
*/
-SQLITE_API SQLITE_DEPRECATED void *sqlite3_trace(sqlite3*,
+SQLITE_API SQLITE_DEPRECATED void *tdsqlite3_trace(tdsqlite3*,
void(*xTrace)(void*,const char*), void*);
-SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
- void(*xProfile)(void*,const char*,sqlite3_uint64), void*);
+SQLITE_API SQLITE_DEPRECATED void *tdsqlite3_profile(tdsqlite3*,
+ void(*xProfile)(void*,const char*,tdsqlite3_uint64), void*);
/*
** CAPI3REF: SQL Trace Event Codes
** KEYWORDS: SQLITE_TRACE
**
** These constants identify classes of events that can be monitored
-** using the [sqlite3_trace_v2()] tracing logic. The third argument
-** to [sqlite3_trace_v2()] is an OR-ed combination of one or more of
+** using the [tdsqlite3_trace_v2()] tracing logic. The M argument
+** to [tdsqlite3_trace_v2(D,M,X,P)] is an OR-ed combination of one or more of
** the following constants. ^The first argument to the trace callback
** is one of the following constants.
**
@@ -2841,7 +3146,7 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
** ^A trace callback has four arguments: xCallback(T,C,P,X).
** ^The T argument is one of the integer type codes above.
** ^The C argument is a copy of the context pointer passed in as the
-** fourth argument to [sqlite3_trace_v2()].
+** fourth argument to [tdsqlite3_trace_v2()].
** The P and X arguments are pointers whose meanings depend on T.
**
** <dl>
@@ -2853,13 +3158,13 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
** [prepared statement]. ^The X argument is a pointer to a string which
** is the unexpanded SQL text of the prepared statement or an SQL comment
** that indicates the invocation of a trigger. ^The callback can compute
-** the same text that would have been returned by the legacy [sqlite3_trace()]
+** the same text that would have been returned by the legacy [tdsqlite3_trace()]
** interface by using the X argument when X begins with "--" and invoking
-** [sqlite3_expanded_sql(P)] otherwise.
+** [tdsqlite3_expanded_sql(P)] otherwise.
**
** [[SQLITE_TRACE_PROFILE]] <dt>SQLITE_TRACE_PROFILE</dt>
** <dd>^An SQLITE_TRACE_PROFILE callback provides approximately the same
-** information as is provided by the [sqlite3_profile()] callback.
+** information as is provided by the [tdsqlite3_profile()] callback.
** ^The P argument is a pointer to the [prepared statement] and the
** X argument points to a 64-bit integer which is the estimated of
** the number of nanosecond that the prepared statement took to run.
@@ -2885,17 +3190,17 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
/*
** CAPI3REF: SQL Trace Hook
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_trace_v2(D,M,X,P) interface registers a trace callback
+** ^The tdsqlite3_trace_v2(D,M,X,P) interface registers a trace callback
** function X against [database connection] D, using property mask M
** and context pointer P. ^If the X callback is
** NULL or if the M mask is zero, then tracing is disabled. The
** M argument should be the bitwise OR-ed combination of
** zero or more [SQLITE_TRACE] constants.
**
-** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides
-** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2().
+** ^Each call to either tdsqlite3_trace() or tdsqlite3_trace_v2() overrides
+** (cancels) any prior calls to tdsqlite3_trace() or tdsqlite3_trace_v2().
**
** ^The X callback is invoked whenever any of the events identified by
** mask M occur. ^The integer return value from the callback is currently
@@ -2908,12 +3213,12 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
** ^The C argument is a copy of the context pointer.
** The P and X arguments are pointers whose meanings depend on T.
**
-** The sqlite3_trace_v2() interface is intended to replace the legacy
-** interfaces [sqlite3_trace()] and [sqlite3_profile()], both of which
+** The tdsqlite3_trace_v2() interface is intended to replace the legacy
+** interfaces [tdsqlite3_trace()] and [tdsqlite3_profile()], both of which
** are deprecated.
*/
-SQLITE_API int sqlite3_trace_v2(
- sqlite3*,
+SQLITE_API int tdsqlite3_trace_v2(
+ tdsqlite3*,
unsigned uMask,
int(*xCallback)(unsigned,void*,void*,void*),
void *pCtx
@@ -2921,11 +3226,11 @@ SQLITE_API int sqlite3_trace_v2(
/*
** CAPI3REF: Query Progress Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback
+** ^The tdsqlite3_progress_handler(D,N,X,P) interface causes the callback
** function X to be invoked periodically during long running calls to
-** [sqlite3_exec()], [sqlite3_step()] and [sqlite3_get_table()] for
+** [tdsqlite3_exec()], [tdsqlite3_step()] and [tdsqlite3_get_table()] for
** database connection D. An example use for this
** interface is to keep a GUI updated during a large query.
**
@@ -2947,44 +3252,42 @@ SQLITE_API int sqlite3_trace_v2(
**
** The progress handler callback must not do anything that will modify
** the database connection that invoked the progress handler.
-** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their
+** Note that [tdsqlite3_prepare_v2()] and [tdsqlite3_step()] both modify their
** database connections for the meaning of "modify" in this paragraph.
**
*/
-SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
+SQLITE_API void tdsqlite3_progress_handler(tdsqlite3*, int, int(*)(void*), void*);
/*
** CAPI3REF: Opening A New Database Connection
-** CONSTRUCTOR: sqlite3
+** CONSTRUCTOR: tdsqlite3
**
** ^These routines open an SQLite database file as specified by the
** filename argument. ^The filename argument is interpreted as UTF-8 for
-** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte
-** order for sqlite3_open16(). ^(A [database connection] handle is usually
+** tdsqlite3_open() and tdsqlite3_open_v2() and as UTF-16 in the native byte
+** order for tdsqlite3_open16(). ^(A [database connection] handle is usually
** returned in *ppDb, even if an error occurs. The only exception is that
-** if SQLite is unable to allocate memory to hold the [sqlite3] object,
-** a NULL will be written into *ppDb instead of a pointer to the [sqlite3]
+** if SQLite is unable to allocate memory to hold the [tdsqlite3] object,
+** a NULL will be written into *ppDb instead of a pointer to the [tdsqlite3]
** object.)^ ^(If the database is opened (and/or created) successfully, then
** [SQLITE_OK] is returned. Otherwise an [error code] is returned.)^ ^The
-** [sqlite3_errmsg()] or [sqlite3_errmsg16()] routines can be used to obtain
+** [tdsqlite3_errmsg()] or [tdsqlite3_errmsg16()] routines can be used to obtain
** an English language description of the error following a failure of any
-** of the sqlite3_open() routines.
+** of the tdsqlite3_open() routines.
**
** ^The default encoding will be UTF-8 for databases created using
-** sqlite3_open() or sqlite3_open_v2(). ^The default encoding for databases
-** created using sqlite3_open16() will be UTF-16 in the native byte order.
+** tdsqlite3_open() or tdsqlite3_open_v2(). ^The default encoding for databases
+** created using tdsqlite3_open16() will be UTF-16 in the native byte order.
**
** Whether or not an error occurs when it is opened, resources
** associated with the [database connection] handle should be released by
-** passing it to [sqlite3_close()] when it is no longer required.
+** passing it to [tdsqlite3_close()] when it is no longer required.
**
-** The sqlite3_open_v2() interface works like sqlite3_open()
+** The tdsqlite3_open_v2() interface works like tdsqlite3_open()
** except that it accepts two additional parameters for additional control
** over the new database connection. ^(The flags parameter to
-** sqlite3_open_v2() can take one of
-** the following three values, optionally combined with the
-** [SQLITE_OPEN_NOMUTEX], [SQLITE_OPEN_FULLMUTEX], [SQLITE_OPEN_SHAREDCACHE],
-** [SQLITE_OPEN_PRIVATECACHE], and/or [SQLITE_OPEN_URI] flags:)^
+** tdsqlite3_open_v2() must include, at a minimum, one of the following
+** three flag combinations:)^
**
** <dl>
** ^(<dt>[SQLITE_OPEN_READONLY]</dt>
@@ -2999,30 +3302,58 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** ^(<dt>[SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]</dt>
** <dd>The database is opened for reading and writing, and is created if
** it does not already exist. This is the behavior that is always used for
-** sqlite3_open() and sqlite3_open16().</dd>)^
+** tdsqlite3_open() and tdsqlite3_open16().</dd>)^
** </dl>
**
-** If the 3rd parameter to sqlite3_open_v2() is not one of the
-** combinations shown above optionally combined with other
+** In addition to the required flags, the following optional flags are
+** also supported:
+**
+** <dl>
+** ^(<dt>[SQLITE_OPEN_URI]</dt>
+** <dd>The filename can be interpreted as a URI if this flag is set.</dd>)^
+**
+** ^(<dt>[SQLITE_OPEN_MEMORY]</dt>
+** <dd>The database will be opened as an in-memory database. The database
+** is named by the "filename" argument for the purposes of cache-sharing,
+** if shared cache mode is enabled, but the "filename" is otherwise ignored.
+** </dd>)^
+**
+** ^(<dt>[SQLITE_OPEN_NOMUTEX]</dt>
+** <dd>The new database connection will use the "multi-thread"
+** [threading mode].)^ This means that separate threads are allowed
+** to use SQLite at the same time, as long as each thread is using
+** a different [database connection].
+**
+** ^(<dt>[SQLITE_OPEN_FULLMUTEX]</dt>
+** <dd>The new database connection will use the "serialized"
+** [threading mode].)^ This means the multiple threads can safely
+** attempt to use the same database connection at the same time.
+** (Mutexes will block any actual concurrency, but in this mode
+** there is no harm in trying.)
+**
+** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt>
+** <dd>The database is opened [shared cache] enabled, overriding
+** the default shared cache setting provided by
+** [tdsqlite3_enable_shared_cache()].)^
+**
+** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
+** <dd>The database is opened [shared cache] disabled, overriding
+** the default shared cache setting provided by
+** [tdsqlite3_enable_shared_cache()].)^
+**
+** [[OPEN_NOFOLLOW]] ^(<dt>[SQLITE_OPEN_NOFOLLOW]</dt>
+** <dd>The database filename is not allowed to be a symbolic link</dd>
+** </dl>)^
+**
+** If the 3rd parameter to tdsqlite3_open_v2() is not one of the
+** required combinations shown above optionally combined with other
** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits]
** then the behavior is undefined.
**
-** ^If the [SQLITE_OPEN_NOMUTEX] flag is set, then the database connection
-** opens in the multi-thread [threading mode] as long as the single-thread
-** mode has not been set at compile-time or start-time. ^If the
-** [SQLITE_OPEN_FULLMUTEX] flag is set then the database connection opens
-** in the serialized [threading mode] unless single-thread was
-** previously selected at compile-time or start-time.
-** ^The [SQLITE_OPEN_SHAREDCACHE] flag causes the database connection to be
-** eligible to use [shared cache mode], regardless of whether or not shared
-** cache is enabled using [sqlite3_enable_shared_cache()]. ^The
-** [SQLITE_OPEN_PRIVATECACHE] flag causes the database connection to not
-** participate in [shared cache mode] even if it is enabled.
-**
-** ^The fourth parameter to sqlite3_open_v2() is the name of the
-** [sqlite3_vfs] object that defines the operating system interface that
+** ^The fourth parameter to tdsqlite3_open_v2() is the name of the
+** [tdsqlite3_vfs] object that defines the operating system interface that
** the new database connection should use. ^If the fourth parameter is
-** a NULL pointer then the default [sqlite3_vfs] object is used.
+** a NULL pointer then the default [tdsqlite3_vfs] object is used.
**
** ^If the filename is ":memory:", then a private, temporary in-memory database
** is created for the connection. ^This in-memory database will vanish when
@@ -3036,15 +3367,15 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** on-disk database will be created. ^This private database will be
** automatically deleted as soon as the database connection is closed.
**
-** [[URI filenames in sqlite3_open()]] <h3>URI Filenames</h3>
+** [[URI filenames in tdsqlite3_open()]] <h3>URI Filenames</h3>
**
** ^If [URI filename] interpretation is enabled, and the filename argument
** begins with "file:", then the filename is interpreted as a URI. ^URI
** filename interpretation is enabled if the [SQLITE_OPEN_URI] flag is
-** set in the fourth argument to sqlite3_open_v2(), or if it has
+** set in the third argument to tdsqlite3_open_v2(), or if it has
** been enabled globally using the [SQLITE_CONFIG_URI] option with the
-** [sqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option.
-** As of SQLite version 3.7.7, URI filename interpretation is turned off
+** [tdsqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option.
+** URI filename interpretation is turned off
** by default, but future releases of SQLite might enable URI filename
** interpretation by default. See "[URI filenames]" for additional
** information.
@@ -3074,16 +3405,16 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** a VFS object that provides the operating system interface that should
** be used to access the database file on disk. ^If this option is set to
** an empty string the default VFS object is used. ^Specifying an unknown
-** VFS is an error. ^If sqlite3_open_v2() is used and the vfs option is
+** VFS is an error. ^If tdsqlite3_open_v2() is used and the vfs option is
** present, then the VFS specified by the option takes precedence over
-** the value passed as the fourth parameter to sqlite3_open_v2().
+** the value passed as the fourth parameter to tdsqlite3_open_v2().
**
** <li> <b>mode</b>: ^(The mode parameter may be set to either "ro", "rw",
** "rwc", or "memory". Attempting to set it to any other value is
** an error)^.
** ^If "ro" is specified, then the database is opened for read-only
** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the
-** third argument to sqlite3_open_v2(). ^If the mode option is set to
+** third argument to tdsqlite3_open_v2(). ^If the mode option is set to
** "rw", then the database is opened for read-write (but not create)
** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had
** been set. ^Value "rwc" is equivalent to setting both
@@ -3091,14 +3422,14 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** set to "memory" then a pure [in-memory database] that never reads
** or writes from disk is used. ^It is an error to specify a value for
** the mode parameter that is less restrictive than that specified by
-** the flags passed in the third parameter to sqlite3_open_v2().
+** the flags passed in the third parameter to tdsqlite3_open_v2().
**
** <li> <b>cache</b>: ^The cache parameter may be set to either "shared" or
** "private". ^Setting it to "shared" is equivalent to setting the
** SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed to
-** sqlite3_open_v2(). ^Setting the cache parameter to "private" is
+** tdsqlite3_open_v2(). ^Setting the cache parameter to "private" is
** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit.
-** ^If sqlite3_open_v2() is used and the "cache" parameter is present in
+** ^If tdsqlite3_open_v2() is used and the "cache" parameter is present in
** a URI filename, its value overrides any behavior requested by setting
** SQLITE_OPEN_PRIVATECACHE or SQLITE_OPEN_SHAREDCACHE flag.
**
@@ -3169,28 +3500,28 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
** the results are undefined.
**
** <b>Note to Windows users:</b> The encoding used for the filename argument
-** of sqlite3_open() and sqlite3_open_v2() must be UTF-8, not whatever
+** of tdsqlite3_open() and tdsqlite3_open_v2() must be UTF-8, not whatever
** codepage is currently defined. Filenames containing international
** characters must be converted to UTF-8 prior to passing them into
-** sqlite3_open() or sqlite3_open_v2().
+** tdsqlite3_open() or tdsqlite3_open_v2().
**
** <b>Note to Windows Runtime users:</b> The temporary directory must be set
-** prior to calling sqlite3_open() or sqlite3_open_v2(). Otherwise, various
+** prior to calling tdsqlite3_open() or tdsqlite3_open_v2(). Otherwise, various
** features that require the use of temporary files may fail.
**
-** See also: [sqlite3_temp_directory]
+** See also: [tdsqlite3_temp_directory]
*/
-SQLITE_API int sqlite3_open(
+SQLITE_API int tdsqlite3_open(
const char *filename, /* Database filename (UTF-8) */
- sqlite3 **ppDb /* OUT: SQLite db handle */
+ tdsqlite3 **ppDb /* OUT: SQLite db handle */
);
-SQLITE_API int sqlite3_open16(
+SQLITE_API int tdsqlite3_open16(
const void *filename, /* Database filename (UTF-16) */
- sqlite3 **ppDb /* OUT: SQLite db handle */
+ tdsqlite3 **ppDb /* OUT: SQLite db handle */
);
-SQLITE_API int sqlite3_open_v2(
+SQLITE_API int tdsqlite3_open_v2(
const char *filename, /* Database filename (UTF-8) */
- sqlite3 **ppDb, /* OUT: SQLite db handle */
+ tdsqlite3 **ppDb, /* OUT: SQLite db handle */
int flags, /* Flags */
const char *zVfs /* Name of VFS module to use */
);
@@ -3198,70 +3529,129 @@ SQLITE_API int sqlite3_open_v2(
/*
** CAPI3REF: Obtain Values For URI Parameters
**
-** These are utility routines, useful to VFS implementations, that check
-** to see if a database file was a URI that contained a specific query
+** These are utility routines, useful to [VFS|custom VFS implementations],
+** that check if a database file was a URI that contained a specific query
** parameter, and if so obtains the value of that query parameter.
**
** If F is the database filename pointer passed into the xOpen() method of
-** a VFS implementation when the flags parameter to xOpen() has one or
-** more of the [SQLITE_OPEN_URI] or [SQLITE_OPEN_MAIN_DB] bits set and
-** P is the name of the query parameter, then
-** sqlite3_uri_parameter(F,P) returns the value of the P
+** a VFS implementation or it is the return value of [tdsqlite3_db_filename()]
+** and if P is the name of the query parameter, then
+** tdsqlite3_uri_parameter(F,P) returns the value of the P
** parameter if it exists or a NULL pointer if P does not appear as a
-** query parameter on F. If P is a query parameter of F
-** has no explicit value, then sqlite3_uri_parameter(F,P) returns
+** query parameter on F. If P is a query parameter of F and it
+** has no explicit value, then tdsqlite3_uri_parameter(F,P) returns
** a pointer to an empty string.
**
-** The sqlite3_uri_boolean(F,P,B) routine assumes that P is a boolean
+** The tdsqlite3_uri_boolean(F,P,B) routine assumes that P is a boolean
** parameter and returns true (1) or false (0) according to the value
-** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the
+** of P. The tdsqlite3_uri_boolean(F,P,B) routine returns true (1) if the
** value of query parameter P is one of "yes", "true", or "on" in any
** case or if the value begins with a non-zero number. The
-** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of
+** tdsqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of
** query parameter P is one of "no", "false", or "off" in any case or
** if the value begins with a numeric zero. If P is not a query
-** parameter on F or if the value of P is does not match any of the
-** above, then sqlite3_uri_boolean(F,P,B) returns (B!=0).
+** parameter on F or if the value of P does not match any of the
+** above, then tdsqlite3_uri_boolean(F,P,B) returns (B!=0).
**
-** The sqlite3_uri_int64(F,P,D) routine converts the value of P into a
+** The tdsqlite3_uri_int64(F,P,D) routine converts the value of P into a
** 64-bit signed integer and returns that integer, or D if P does not
** exist. If the value of P is something other than an integer, then
** zero is returned.
+**
+** The tdsqlite3_uri_key(F,N) returns a pointer to the name (not
+** the value) of the N-th query parameter for filename F, or a NULL
+** pointer if N is less than zero or greater than the number of query
+** parameters minus 1. The N value is zero-based so N should be 0 to obtain
+** the name of the first query parameter, 1 for the second parameter, and
+** so forth.
**
-** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and
-** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and
-** is not a database file pathname pointer that SQLite passed into the xOpen
-** VFS method, then the behavior of this routine is undefined and probably
-** undesirable.
+** If F is a NULL pointer, then tdsqlite3_uri_parameter(F,P) returns NULL and
+** tdsqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and
+** is not a database file pathname pointer that the SQLite core passed
+** into the xOpen VFS method, then the behavior of this routine is undefined
+** and probably undesirable.
+**
+** Beginning with SQLite [version 3.31.0] ([dateof:3.31.0]) the input F
+** parameter can also be the name of a rollback journal file or WAL file
+** in addition to the main database file. Prior to version 3.31.0, these
+** routines would only work if F was the name of the main database file.
+** When the F parameter is the name of the rollback journal or WAL file,
+** it has access to all the same query parameters as were found on the
+** main database file.
+**
+** See the [URI filename] documentation for additional information.
*/
-SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam);
-SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault);
-SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64);
+SQLITE_API const char *tdsqlite3_uri_parameter(const char *zFilename, const char *zParam);
+SQLITE_API int tdsqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault);
+SQLITE_API tdsqlite3_int64 tdsqlite3_uri_int64(const char*, const char*, tdsqlite3_int64);
+SQLITE_API const char *tdsqlite3_uri_key(const char *zFilename, int N);
+
+/*
+** CAPI3REF: Translate filenames
+**
+** These routines are available to [VFS|custom VFS implementations] for
+** translating filenames between the main database file, the journal file,
+** and the WAL file.
+**
+** If F is the name of an sqlite database file, journal file, or WAL file
+** passed by the SQLite core into the VFS, then tdsqlite3_filename_database(F)
+** returns the name of the corresponding database file.
+**
+** If F is the name of an sqlite database file, journal file, or WAL file
+** passed by the SQLite core into the VFS, or if F is a database filename
+** obtained from [tdsqlite3_db_filename()], then tdsqlite3_filename_journal(F)
+** returns the name of the corresponding rollback journal file.
+**
+** If F is the name of an sqlite database file, journal file, or WAL file
+** that was passed by the SQLite core into the VFS, or if F is a database
+** filename obtained from [tdsqlite3_db_filename()], then
+** tdsqlite3_filename_wal(F) returns the name of the corresponding
+** WAL file.
+**
+** In all of the above, if F is not the name of a database, journal or WAL
+** filename passed into the VFS from the SQLite core and F is not the
+** return value from [tdsqlite3_db_filename()], then the result is
+** undefined and is likely a memory access violation.
+*/
+SQLITE_API const char *tdsqlite3_filename_database(const char*);
+SQLITE_API const char *tdsqlite3_filename_journal(const char*);
+SQLITE_API const char *tdsqlite3_filename_wal(const char*);
/*
** CAPI3REF: Error Codes And Messages
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^If the most recent sqlite3_* API call associated with
-** [database connection] D failed, then the sqlite3_errcode(D) interface
+** ^If the most recent tdsqlite3_* API call associated with
+** [database connection] D failed, then the tdsqlite3_errcode(D) interface
** returns the numeric [result code] or [extended result code] for that
** API call.
-** If the most recent API call was successful,
-** then the return value from sqlite3_errcode() is undefined.
-** ^The sqlite3_extended_errcode()
+** ^The tdsqlite3_extended_errcode()
** interface is the same except that it always returns the
** [extended result code] even when extended result codes are
** disabled.
**
-** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
+** The values returned by tdsqlite3_errcode() and/or
+** tdsqlite3_extended_errcode() might change with each API call.
+** Except, there are some interfaces that are guaranteed to never
+** change the value of the error code. The error-code preserving
+** interfaces are:
+**
+** <ul>
+** <li> tdsqlite3_errcode()
+** <li> tdsqlite3_extended_errcode()
+** <li> tdsqlite3_errmsg()
+** <li> tdsqlite3_errmsg16()
+** </ul>
+**
+** ^The tdsqlite3_errmsg() and tdsqlite3_errmsg16() return English-language
** text that describes the error, as either UTF-8 or UTF-16 respectively.
** ^(Memory to hold the error message string is managed internally.
** The application does not need to worry about freeing the result.
** However, the error string might be overwritten or deallocated by
** subsequent calls to other SQLite interface functions.)^
**
-** ^The sqlite3_errstr() interface returns the English-language text
+** ^The tdsqlite3_errstr() interface returns the English-language text
** that describes the [result code], as UTF-8.
** ^(Memory to hold the error message string is managed internally
** and must not be freed by the application)^.
@@ -3272,19 +3662,19 @@ SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int
** When that happens, the second error will be reported since these
** interfaces always report the most recent result. To avoid
** this, each thread can obtain exclusive use of the [database connection] D
-** by invoking [sqlite3_mutex_enter]([sqlite3_db_mutex](D)) before beginning
-** to use D and invoking [sqlite3_mutex_leave]([sqlite3_db_mutex](D)) after
+** by invoking [tdsqlite3_mutex_enter]([tdsqlite3_db_mutex](D)) before beginning
+** to use D and invoking [tdsqlite3_mutex_leave]([tdsqlite3_db_mutex](D)) after
** all calls to the interfaces listed here are completed.
**
** If an interface fails with SQLITE_MISUSE, that means the interface
** was invoked incorrectly by the application. In that case, the
** error code and message may or may not be set.
*/
-SQLITE_API int sqlite3_errcode(sqlite3 *db);
-SQLITE_API int sqlite3_extended_errcode(sqlite3 *db);
-SQLITE_API const char *sqlite3_errmsg(sqlite3*);
-SQLITE_API const void *sqlite3_errmsg16(sqlite3*);
-SQLITE_API const char *sqlite3_errstr(int);
+SQLITE_API int tdsqlite3_errcode(tdsqlite3 *db);
+SQLITE_API int tdsqlite3_extended_errcode(tdsqlite3 *db);
+SQLITE_API const char *tdsqlite3_errmsg(tdsqlite3*);
+SQLITE_API const void *tdsqlite3_errmsg16(tdsqlite3*);
+SQLITE_API const char *tdsqlite3_errstr(int);
/*
** CAPI3REF: Prepared Statement Object
@@ -3301,20 +3691,20 @@ SQLITE_API const char *sqlite3_errstr(int);
** The life-cycle of a prepared statement object usually goes like this:
**
** <ol>
-** <li> Create the prepared statement object using [sqlite3_prepare_v2()].
-** <li> Bind values to [parameters] using the sqlite3_bind_*()
+** <li> Create the prepared statement object using [tdsqlite3_prepare_v2()].
+** <li> Bind values to [parameters] using the tdsqlite3_bind_*()
** interfaces.
-** <li> Run the SQL by calling [sqlite3_step()] one or more times.
-** <li> Reset the prepared statement using [sqlite3_reset()] then go back
+** <li> Run the SQL by calling [tdsqlite3_step()] one or more times.
+** <li> Reset the prepared statement using [tdsqlite3_reset()] then go back
** to step 2. Do this zero or more times.
-** <li> Destroy the object using [sqlite3_finalize()].
+** <li> Destroy the object using [tdsqlite3_finalize()].
** </ol>
*/
-typedef struct sqlite3_stmt sqlite3_stmt;
+typedef struct tdsqlite3_stmt tdsqlite3_stmt;
/*
** CAPI3REF: Run-time Limits
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^(This interface allows the size of various constructs to be limited
** on a connection by connection basis. The first parameter is the
@@ -3333,7 +3723,7 @@ typedef struct sqlite3_stmt sqlite3_stmt;
** silently truncated to the hard upper bound.
**
** ^Regardless of whether or not the limit was changed, the
-** [sqlite3_limit()] interface returns the prior value of the limit.
+** [tdsqlite3_limit()] interface returns the prior value of the limit.
** ^Hence, to find the current value of a limit without changing it,
** simply invoke this interface with the third parameter set to -1.
**
@@ -3345,21 +3735,21 @@ typedef struct sqlite3_stmt sqlite3_stmt;
** off the Internet. The internal databases can be given the
** large, default limits. Databases managed by external sources can
** be given much smaller limits designed to prevent a denial of service
-** attack. Developers might also want to use the [sqlite3_set_authorizer()]
+** attack. Developers might also want to use the [tdsqlite3_set_authorizer()]
** interface to further control untrusted SQL. The size of the database
** created by an untrusted script can be contained using the
** [max_page_count] [PRAGMA].
**
** New run-time limit categories may be added in future releases.
*/
-SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
+SQLITE_API int tdsqlite3_limit(tdsqlite3*, int id, int newVal);
/*
** CAPI3REF: Run-Time Limit Categories
** KEYWORDS: {limit category} {*limit categories}
**
** These constants define various performance limits
-** that can be lowered at run-time using [sqlite3_limit()].
+** that can be lowered at run-time using [tdsqlite3_limit()].
** The synopsis of the meanings of the various limits is shown below.
** Additional information is available at [limits | Limits in SQLite].
**
@@ -3383,9 +3773,9 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
**
** [[SQLITE_LIMIT_VDBE_OP]] ^(<dt>SQLITE_LIMIT_VDBE_OP</dt>
** <dd>The maximum number of instructions in a virtual machine program
-** used to implement an SQL statement. This limit is not currently
-** enforced, though that might be added in some future release of
-** SQLite.</dd>)^
+** used to implement an SQL statement. If [tdsqlite3_prepare_v2()] or
+** the equivalent tries to allocate space for more than this many opcodes
+** in a single prepared statement, an SQLITE_NOMEM error is returned.</dd>)^
**
** [[SQLITE_LIMIT_FUNCTION_ARG]] ^(<dt>SQLITE_LIMIT_FUNCTION_ARG</dt>
** <dd>The maximum number of arguments on a function.</dd>)^
@@ -3424,22 +3814,73 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
#define SQLITE_LIMIT_WORKER_THREADS 11
/*
+** CAPI3REF: Prepare Flags
+**
+** These constants define various flags that can be passed into
+** "prepFlags" parameter of the [tdsqlite3_prepare_v3()] and
+** [tdsqlite3_prepare16_v3()] interfaces.
+**
+** New flags may be added in future releases of SQLite.
+**
+** <dl>
+** [[SQLITE_PREPARE_PERSISTENT]] ^(<dt>SQLITE_PREPARE_PERSISTENT</dt>
+** <dd>The SQLITE_PREPARE_PERSISTENT flag is a hint to the query planner
+** that the prepared statement will be retained for a long time and
+** probably reused many times.)^ ^Without this flag, [tdsqlite3_prepare_v3()]
+** and [tdsqlite3_prepare16_v3()] assume that the prepared statement will
+** be used just once or at most a few times and then destroyed using
+** [tdsqlite3_finalize()] relatively soon. The current implementation acts
+** on this hint by avoiding the use of [lookaside memory] so as not to
+** deplete the limited store of lookaside memory. Future versions of
+** SQLite may act on this hint differently.
+**
+** [[SQLITE_PREPARE_NORMALIZE]] <dt>SQLITE_PREPARE_NORMALIZE</dt>
+** <dd>The SQLITE_PREPARE_NORMALIZE flag is a no-op. This flag used
+** to be required for any prepared statement that wanted to use the
+** [tdsqlite3_normalized_sql()] interface. However, the
+** [tdsqlite3_normalized_sql()] interface is now available to all
+** prepared statements, regardless of whether or not they use this
+** flag.
+**
+** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
+** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
+** to return an error (error code SQLITE_ERROR) if the statement uses
+** any virtual tables.
+** </dl>
+*/
+#define SQLITE_PREPARE_PERSISTENT 0x01
+#define SQLITE_PREPARE_NORMALIZE 0x02
+#define SQLITE_PREPARE_NO_VTAB 0x04
+
+/*
** CAPI3REF: Compiling An SQL Statement
** KEYWORDS: {SQL statement compiler}
-** METHOD: sqlite3
-** CONSTRUCTOR: sqlite3_stmt
+** METHOD: tdsqlite3
+** CONSTRUCTOR: tdsqlite3_stmt
+**
+** To execute an SQL statement, it must first be compiled into a byte-code
+** program using one of these routines. Or, in other words, these routines
+** are constructors for the [prepared statement] object.
**
-** To execute an SQL query, it must first be compiled into a byte-code
-** program using one of these routines.
+** The preferred routine to use is [tdsqlite3_prepare_v2()]. The
+** [tdsqlite3_prepare()] interface is legacy and should be avoided.
+** [tdsqlite3_prepare_v3()] has an extra "prepFlags" option that is used
+** for special purposes.
+**
+** The use of the UTF-8 interfaces is preferred, as SQLite currently
+** does all parsing using UTF-8. The UTF-16 interfaces are provided
+** as a convenience. The UTF-16 interfaces work by converting the
+** input text into UTF-8, then invoking the corresponding UTF-8 interface.
**
** The first argument, "db", is a [database connection] obtained from a
-** prior successful call to [sqlite3_open()], [sqlite3_open_v2()] or
-** [sqlite3_open16()]. The database connection must not have been closed.
+** prior successful call to [tdsqlite3_open()], [tdsqlite3_open_v2()] or
+** [tdsqlite3_open16()]. The database connection must not have been closed.
**
** The second argument, "zSql", is the statement to be compiled, encoded
-** as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2()
-** interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2()
-** use UTF-16.
+** as either UTF-8 or UTF-16. The tdsqlite3_prepare(), tdsqlite3_prepare_v2(),
+** and tdsqlite3_prepare_v3()
+** interfaces use UTF-8, and tdsqlite3_prepare16(), tdsqlite3_prepare16_v2(),
+** and tdsqlite3_prepare16_v3() use UTF-16.
**
** ^If the nByte argument is negative, then zSql is read up to the
** first zero terminator. ^If nByte is positive, then it is the
@@ -3456,129 +3897,160 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal);
** what remains uncompiled.
**
** ^*ppStmt is left pointing to a compiled [prepared statement] that can be
-** executed using [sqlite3_step()]. ^If there is an error, *ppStmt is set
+** executed using [tdsqlite3_step()]. ^If there is an error, *ppStmt is set
** to NULL. ^If the input text contains no SQL (if the input is an empty
** string or a comment) then *ppStmt is set to NULL.
** The calling procedure is responsible for deleting the compiled
-** SQL statement using [sqlite3_finalize()] after it has finished with it.
+** SQL statement using [tdsqlite3_finalize()] after it has finished with it.
** ppStmt may not be NULL.
**
-** ^On success, the sqlite3_prepare() family of routines return [SQLITE_OK];
+** ^On success, the tdsqlite3_prepare() family of routines return [SQLITE_OK];
** otherwise an [error code] is returned.
**
-** The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are
-** recommended for all new programs. The two older interfaces are retained
-** for backwards compatibility, but their use is discouraged.
-** ^In the "v2" interfaces, the prepared statement
-** that is returned (the [sqlite3_stmt] object) contains a copy of the
-** original SQL text. This causes the [sqlite3_step()] interface to
+** The tdsqlite3_prepare_v2(), tdsqlite3_prepare_v3(), tdsqlite3_prepare16_v2(),
+** and tdsqlite3_prepare16_v3() interfaces are recommended for all new programs.
+** The older interfaces (tdsqlite3_prepare() and tdsqlite3_prepare16())
+** are retained for backwards compatibility, but their use is discouraged.
+** ^In the "vX" interfaces, the prepared statement
+** that is returned (the [tdsqlite3_stmt] object) contains a copy of the
+** original SQL text. This causes the [tdsqlite3_step()] interface to
** behave differently in three ways:
**
** <ol>
** <li>
** ^If the database schema changes, instead of returning [SQLITE_SCHEMA] as it
-** always used to do, [sqlite3_step()] will automatically recompile the SQL
+** always used to do, [tdsqlite3_step()] will automatically recompile the SQL
** statement and try to run it again. As many as [SQLITE_MAX_SCHEMA_RETRY]
-** retries will occur before sqlite3_step() gives up and returns an error.
+** retries will occur before tdsqlite3_step() gives up and returns an error.
** </li>
**
** <li>
-** ^When an error occurs, [sqlite3_step()] will return one of the detailed
+** ^When an error occurs, [tdsqlite3_step()] will return one of the detailed
** [error codes] or [extended error codes]. ^The legacy behavior was that
-** [sqlite3_step()] would only return a generic [SQLITE_ERROR] result code
-** and the application would have to make a second call to [sqlite3_reset()]
+** [tdsqlite3_step()] would only return a generic [SQLITE_ERROR] result code
+** and the application would have to make a second call to [tdsqlite3_reset()]
** in order to find the underlying cause of the problem. With the "v2" prepare
** interfaces, the underlying reason for the error is returned immediately.
** </li>
**
** <li>
-** ^If the specific value bound to [parameter | host parameter] in the
+** ^If the specific value bound to a [parameter | host parameter] in the
** WHERE clause might influence the choice of query plan for a statement,
** then the statement will be automatically recompiled, as if there had been
-** a schema change, on the first [sqlite3_step()] call following any change
-** to the [sqlite3_bind_text | bindings] of that [parameter].
-** ^The specific value of WHERE-clause [parameter] might influence the
+** a schema change, on the first [tdsqlite3_step()] call following any change
+** to the [tdsqlite3_bind_text | bindings] of that [parameter].
+** ^The specific value of a WHERE-clause [parameter] might influence the
** choice of query plan if the parameter is the left-hand side of a [LIKE]
** or [GLOB] operator or if the parameter is compared to an indexed column
-** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled.
+** and the [SQLITE_ENABLE_STAT4] compile-time option is enabled.
** </li>
** </ol>
+**
+** <p>^tdsqlite3_prepare_v3() differs from tdsqlite3_prepare_v2() only in having
+** the extra prepFlags parameter, which is a bit array consisting of zero or
+** more of the [SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_*] flags. ^The
+** tdsqlite3_prepare_v2() interface works exactly the same as
+** tdsqlite3_prepare_v3() with a zero prepFlags parameter.
*/
-SQLITE_API int sqlite3_prepare(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_prepare(
+ tdsqlite3 *db, /* Database handle */
+ const char *zSql, /* SQL statement, UTF-8 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const char **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+SQLITE_API int tdsqlite3_prepare_v2(
+ tdsqlite3 *db, /* Database handle */
const char *zSql, /* SQL statement, UTF-8 encoded */
int nByte, /* Maximum length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
const char **pzTail /* OUT: Pointer to unused portion of zSql */
);
-SQLITE_API int sqlite3_prepare_v2(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_prepare_v3(
+ tdsqlite3 *db, /* Database handle */
const char *zSql, /* SQL statement, UTF-8 encoded */
int nByte, /* Maximum length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
const char **pzTail /* OUT: Pointer to unused portion of zSql */
);
-SQLITE_API int sqlite3_prepare16(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_prepare16(
+ tdsqlite3 *db, /* Database handle */
const void *zSql, /* SQL statement, UTF-16 encoded */
int nByte, /* Maximum length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
const void **pzTail /* OUT: Pointer to unused portion of zSql */
);
-SQLITE_API int sqlite3_prepare16_v2(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_prepare16_v2(
+ tdsqlite3 *db, /* Database handle */
const void *zSql, /* SQL statement, UTF-16 encoded */
int nByte, /* Maximum length of zSql in bytes. */
- sqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
+ const void **pzTail /* OUT: Pointer to unused portion of zSql */
+);
+SQLITE_API int tdsqlite3_prepare16_v3(
+ tdsqlite3 *db, /* Database handle */
+ const void *zSql, /* SQL statement, UTF-16 encoded */
+ int nByte, /* Maximum length of zSql in bytes. */
+ unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */
+ tdsqlite3_stmt **ppStmt, /* OUT: Statement handle */
const void **pzTail /* OUT: Pointer to unused portion of zSql */
);
/*
** CAPI3REF: Retrieving Statement SQL
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_sql(P) interface returns a pointer to a copy of the UTF-8
+** ^The tdsqlite3_sql(P) interface returns a pointer to a copy of the UTF-8
** SQL text used to create [prepared statement] P if P was
-** created by either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()].
-** ^The sqlite3_expanded_sql(P) interface returns a pointer to a UTF-8
+** created by [tdsqlite3_prepare_v2()], [tdsqlite3_prepare_v3()],
+** [tdsqlite3_prepare16_v2()], or [tdsqlite3_prepare16_v3()].
+** ^The tdsqlite3_expanded_sql(P) interface returns a pointer to a UTF-8
** string containing the SQL text of prepared statement P with
** [bound parameters] expanded.
+** ^The tdsqlite3_normalized_sql(P) interface returns a pointer to a UTF-8
+** string containing the normalized SQL text of prepared statement P. The
+** semantics used to normalize a SQL statement are unspecified and subject
+** to change. At a minimum, literal values will be replaced with suitable
+** placeholders.
**
** ^(For example, if a prepared statement is created using the SQL
** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345
-** and parameter :xyz is unbound, then sqlite3_sql() will return
-** the original string, "SELECT $abc,:xyz" but sqlite3_expanded_sql()
+** and parameter :xyz is unbound, then tdsqlite3_sql() will return
+** the original string, "SELECT $abc,:xyz" but tdsqlite3_expanded_sql()
** will return "SELECT 2345,NULL".)^
**
-** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory
+** ^The tdsqlite3_expanded_sql() interface returns NULL if insufficient memory
** is available to hold the result, or if the result would exceed the
** the maximum string length determined by the [SQLITE_LIMIT_LENGTH].
**
** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of
** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time
-** option causes sqlite3_expanded_sql() to always return NULL.
+** option causes tdsqlite3_expanded_sql() to always return NULL.
**
-** ^The string returned by sqlite3_sql(P) is managed by SQLite and is
-** automatically freed when the prepared statement is finalized.
-** ^The string returned by sqlite3_expanded_sql(P), on the other hand,
-** is obtained from [sqlite3_malloc()] and must be free by the application
-** by passing it to [sqlite3_free()].
+** ^The strings returned by tdsqlite3_sql(P) and tdsqlite3_normalized_sql(P)
+** are managed by SQLite and are automatically freed when the prepared
+** statement is finalized.
+** ^The string returned by tdsqlite3_expanded_sql(P), on the other hand,
+** is obtained from [tdsqlite3_malloc()] and must be free by the application
+** by passing it to [tdsqlite3_free()].
*/
-SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt);
-SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt);
+SQLITE_API const char *tdsqlite3_sql(tdsqlite3_stmt *pStmt);
+SQLITE_API char *tdsqlite3_expanded_sql(tdsqlite3_stmt *pStmt);
+SQLITE_API const char *tdsqlite3_normalized_sql(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Determine If An SQL Statement Writes The Database
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_stmt_readonly(X) interface returns true (non-zero) if
+** ^The tdsqlite3_stmt_readonly(X) interface returns true (non-zero) if
** and only if the [prepared statement] X makes no direct changes to
** the content of the database file.
**
** Note that [application-defined SQL functions] or
** [virtual tables] might change the database indirectly as a side effect.
** ^(For example, if an application defines a function "eval()" that
-** calls [sqlite3_exec()], then the following SQL statement would
+** calls [tdsqlite3_exec()], then the following SQL statement would
** change the database file through side-effects:
**
** <blockquote><pre>
@@ -3586,102 +4058,119 @@ SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt);
** </pre></blockquote>
**
** But because the [SELECT] statement does not change the database file
-** directly, sqlite3_stmt_readonly() would still return true.)^
+** directly, tdsqlite3_stmt_readonly() would still return true.)^
**
** ^Transaction control statements such as [BEGIN], [COMMIT], [ROLLBACK],
-** [SAVEPOINT], and [RELEASE] cause sqlite3_stmt_readonly() to return true,
+** [SAVEPOINT], and [RELEASE] cause tdsqlite3_stmt_readonly() to return true,
** since the statements themselves do not actually modify the database but
** rather they control the timing of when other statements modify the
** database. ^The [ATTACH] and [DETACH] statements also cause
-** sqlite3_stmt_readonly() to return true since, while those statements
+** tdsqlite3_stmt_readonly() to return true since, while those statements
** change the configuration of a database connection, they do not make
** changes to the content of the database files on disk.
+** ^The tdsqlite3_stmt_readonly() interface returns true for [BEGIN] since
+** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and
+** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so
+** tdsqlite3_stmt_readonly() returns false for those commands.
*/
-SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_stmt_readonly(tdsqlite3_stmt *pStmt);
+
+/*
+** CAPI3REF: Query The EXPLAIN Setting For A Prepared Statement
+** METHOD: tdsqlite3_stmt
+**
+** ^The tdsqlite3_stmt_isexplain(S) interface returns 1 if the
+** prepared statement S is an EXPLAIN statement, or 2 if the
+** statement S is an EXPLAIN QUERY PLAN.
+** ^The tdsqlite3_stmt_isexplain(S) interface returns 0 if S is
+** an ordinary statement or a NULL pointer.
+*/
+SQLITE_API int tdsqlite3_stmt_isexplain(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Determine If A Prepared Statement Has Been Reset
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the
+** ^The tdsqlite3_stmt_busy(S) interface returns true (non-zero) if the
** [prepared statement] S has been stepped at least once using
-** [sqlite3_step(S)] but has neither run to completion (returned
-** [SQLITE_DONE] from [sqlite3_step(S)]) nor
-** been reset using [sqlite3_reset(S)]. ^The sqlite3_stmt_busy(S)
+** [tdsqlite3_step(S)] but has neither run to completion (returned
+** [SQLITE_DONE] from [tdsqlite3_step(S)]) nor
+** been reset using [tdsqlite3_reset(S)]. ^The tdsqlite3_stmt_busy(S)
** interface returns false if S is a NULL pointer. If S is not a
** NULL pointer and is not a pointer to a valid [prepared statement]
** object, then the behavior is undefined and probably undesirable.
**
-** This interface can be used in combination [sqlite3_next_stmt()]
+** This interface can be used in combination [tdsqlite3_next_stmt()]
** to locate all prepared statements associated with a database
** connection that are in need of being reset. This can be used,
** for example, in diagnostic routines to search for prepared
** statements that are holding a transaction open.
*/
-SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*);
+SQLITE_API int tdsqlite3_stmt_busy(tdsqlite3_stmt*);
/*
** CAPI3REF: Dynamically Typed Value Object
-** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value}
+** KEYWORDS: {protected tdsqlite3_value} {unprotected tdsqlite3_value}
**
-** SQLite uses the sqlite3_value object to represent all values
+** SQLite uses the tdsqlite3_value object to represent all values
** that can be stored in a database table. SQLite uses dynamic typing
-** for the values it stores. ^Values stored in sqlite3_value objects
+** for the values it stores. ^Values stored in tdsqlite3_value objects
** can be integers, floating point values, strings, BLOBs, or NULL.
**
-** An sqlite3_value object may be either "protected" or "unprotected".
-** Some interfaces require a protected sqlite3_value. Other interfaces
-** will accept either a protected or an unprotected sqlite3_value.
-** Every interface that accepts sqlite3_value arguments specifies
-** whether or not it requires a protected sqlite3_value. The
-** [sqlite3_value_dup()] interface can be used to construct a new
-** protected sqlite3_value from an unprotected sqlite3_value.
+** An tdsqlite3_value object may be either "protected" or "unprotected".
+** Some interfaces require a protected tdsqlite3_value. Other interfaces
+** will accept either a protected or an unprotected tdsqlite3_value.
+** Every interface that accepts tdsqlite3_value arguments specifies
+** whether or not it requires a protected tdsqlite3_value. The
+** [tdsqlite3_value_dup()] interface can be used to construct a new
+** protected tdsqlite3_value from an unprotected tdsqlite3_value.
**
** The terms "protected" and "unprotected" refer to whether or not
** a mutex is held. An internal mutex is held for a protected
-** sqlite3_value object but no mutex is held for an unprotected
-** sqlite3_value object. If SQLite is compiled to be single-threaded
-** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0)
+** tdsqlite3_value object but no mutex is held for an unprotected
+** tdsqlite3_value object. If SQLite is compiled to be single-threaded
+** (with [SQLITE_THREADSAFE=0] and with [tdsqlite3_threadsafe()] returning 0)
** or if SQLite is run in one of reduced mutex modes
** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD]
** then there is no distinction between protected and unprotected
-** sqlite3_value objects and they can be used interchangeably. However,
+** tdsqlite3_value objects and they can be used interchangeably. However,
** for maximum code portability it is recommended that applications
** still make the distinction between protected and unprotected
-** sqlite3_value objects even when not strictly required.
+** tdsqlite3_value objects even when not strictly required.
**
-** ^The sqlite3_value objects that are passed as parameters into the
+** ^The tdsqlite3_value objects that are passed as parameters into the
** implementation of [application-defined SQL functions] are protected.
-** ^The sqlite3_value object returned by
-** [sqlite3_column_value()] is unprotected.
-** Unprotected sqlite3_value objects may only be used with
-** [sqlite3_result_value()] and [sqlite3_bind_value()].
-** The [sqlite3_value_blob | sqlite3_value_type()] family of
-** interfaces require protected sqlite3_value objects.
+** ^The tdsqlite3_value object returned by
+** [tdsqlite3_column_value()] is unprotected.
+** Unprotected tdsqlite3_value objects may only be used as arguments
+** to [tdsqlite3_result_value()], [tdsqlite3_bind_value()], and
+** [tdsqlite3_value_dup()].
+** The [tdsqlite3_value_blob | tdsqlite3_value_type()] family of
+** interfaces require protected tdsqlite3_value objects.
*/
-typedef struct Mem sqlite3_value;
+typedef struct tdsqlite3_value tdsqlite3_value;
/*
** CAPI3REF: SQL Function Context Object
**
** The context in which an SQL function executes is stored in an
-** sqlite3_context object. ^A pointer to an sqlite3_context object
+** tdsqlite3_context object. ^A pointer to an tdsqlite3_context object
** is always first parameter to [application-defined SQL functions].
** The application-defined SQL function implementation will pass this
-** pointer through into calls to [sqlite3_result_int | sqlite3_result()],
-** [sqlite3_aggregate_context()], [sqlite3_user_data()],
-** [sqlite3_context_db_handle()], [sqlite3_get_auxdata()],
-** and/or [sqlite3_set_auxdata()].
+** pointer through into calls to [tdsqlite3_result_int | tdsqlite3_result()],
+** [tdsqlite3_aggregate_context()], [tdsqlite3_user_data()],
+** [tdsqlite3_context_db_handle()], [tdsqlite3_get_auxdata()],
+** and/or [tdsqlite3_set_auxdata()].
*/
-typedef struct sqlite3_context sqlite3_context;
+typedef struct tdsqlite3_context tdsqlite3_context;
/*
** CAPI3REF: Binding Values To Prepared Statements
** KEYWORDS: {host parameter} {host parameters} {host parameter name}
** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding}
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
+** ^(In the SQL statement text input to [tdsqlite3_prepare_v2()] and its variants,
** literals may be replaced by a [parameter] that matches one of following
** templates:
**
@@ -3696,37 +4185,37 @@ typedef struct sqlite3_context sqlite3_context;
** In the templates above, NNN represents an integer literal,
** and VVV represents an alphanumeric identifier.)^ ^The values of these
** parameters (also called "host parameter names" or "SQL parameters")
-** can be set using the sqlite3_bind_*() routines defined here.
+** can be set using the tdsqlite3_bind_*() routines defined here.
**
-** ^The first argument to the sqlite3_bind_*() routines is always
-** a pointer to the [sqlite3_stmt] object returned from
-** [sqlite3_prepare_v2()] or its variants.
+** ^The first argument to the tdsqlite3_bind_*() routines is always
+** a pointer to the [tdsqlite3_stmt] object returned from
+** [tdsqlite3_prepare_v2()] or its variants.
**
** ^The second argument is the index of the SQL parameter to be set.
** ^The leftmost SQL parameter has an index of 1. ^When the same named
** SQL parameter is used more than once, second and subsequent
** occurrences have the same index as the first occurrence.
** ^The index for named parameters can be looked up using the
-** [sqlite3_bind_parameter_index()] API if desired. ^The index
+** [tdsqlite3_bind_parameter_index()] API if desired. ^The index
** for "?NNN" parameters is the value of NNN.
-** ^The NNN value must be between 1 and the [sqlite3_limit()]
+** ^The NNN value must be between 1 and the [tdsqlite3_limit()]
** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 999).
**
** ^The third argument is the value to bind to the parameter.
-** ^If the third parameter to sqlite3_bind_text() or sqlite3_bind_text16()
-** or sqlite3_bind_blob() is a NULL pointer then the fourth parameter
-** is ignored and the end result is the same as sqlite3_bind_null().
+** ^If the third parameter to tdsqlite3_bind_text() or tdsqlite3_bind_text16()
+** or tdsqlite3_bind_blob() is a NULL pointer then the fourth parameter
+** is ignored and the end result is the same as tdsqlite3_bind_null().
**
** ^(In those routines that have a fourth argument, its value is the
** number of bytes in the parameter. To be clear: the value is the
** number of <u>bytes</u> in the value, not the number of characters.)^
-** ^If the fourth parameter to sqlite3_bind_text() or sqlite3_bind_text16()
+** ^If the fourth parameter to tdsqlite3_bind_text() or tdsqlite3_bind_text16()
** is negative, then the length of the string is
** the number of bytes up to the first zero terminator.
-** If the fourth parameter to sqlite3_bind_blob() is negative, then
+** If the fourth parameter to tdsqlite3_bind_blob() is negative, then
** the behavior is undefined.
-** If a non-negative fourth parameter is provided to sqlite3_bind_text()
-** or sqlite3_bind_text16() or sqlite3_bind_text64() then
+** If a non-negative fourth parameter is provided to tdsqlite3_bind_text()
+** or tdsqlite3_bind_text16() or tdsqlite3_bind_text64() then
** that parameter must be the byte offset
** where the NUL terminator would occur assuming the string were NUL
** terminated. If any NUL characters occur at byte offsets less than
@@ -3737,74 +4226,86 @@ typedef struct sqlite3_context sqlite3_context;
** ^The fifth argument to the BLOB and string binding interfaces
** is a destructor used to dispose of the BLOB or
** string after SQLite has finished with it. ^The destructor is called
-** to dispose of the BLOB or string even if the call to bind API fails.
+** to dispose of the BLOB or string even if the call to the bind API fails,
+** except the destructor is not called if the third parameter is a NULL
+** pointer or the fourth parameter is negative.
** ^If the fifth argument is
** the special value [SQLITE_STATIC], then SQLite assumes that the
** information is in static, unmanaged space and does not need to be freed.
** ^If the fifth argument has the value [SQLITE_TRANSIENT], then
** SQLite makes its own private copy of the data immediately, before
-** the sqlite3_bind_*() routine returns.
+** the tdsqlite3_bind_*() routine returns.
**
-** ^The sixth argument to sqlite3_bind_text64() must be one of
+** ^The sixth argument to tdsqlite3_bind_text64() must be one of
** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]
** to specify the encoding of the text in the third parameter. If
-** the sixth argument to sqlite3_bind_text64() is not one of the
+** the sixth argument to tdsqlite3_bind_text64() is not one of the
** allowed values shown above, or if the text encoding is different
** from the encoding specified by the sixth parameter, then the behavior
** is undefined.
**
-** ^The sqlite3_bind_zeroblob() routine binds a BLOB of length N that
+** ^The tdsqlite3_bind_zeroblob() routine binds a BLOB of length N that
** is filled with zeroes. ^A zeroblob uses a fixed amount of memory
** (just an integer to hold its size) while it is being processed.
** Zeroblobs are intended to serve as placeholders for BLOBs whose
** content is later written using
-** [sqlite3_blob_open | incremental BLOB I/O] routines.
+** [tdsqlite3_blob_open | incremental BLOB I/O] routines.
** ^A negative value for the zeroblob results in a zero-length BLOB.
**
-** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer
+** ^The tdsqlite3_bind_pointer(S,I,P,T,D) routine causes the I-th parameter in
+** [prepared statement] S to have an SQL value of NULL, but to also be
+** associated with the pointer P of type T. ^D is either a NULL pointer or
+** a pointer to a destructor function for P. ^SQLite will invoke the
+** destructor D with a single argument of P when it is finished using
+** P. The T parameter should be a static string, preferably a string
+** literal. The tdsqlite3_bind_pointer() routine is part of the
+** [pointer passing interface] added for SQLite 3.20.0.
+**
+** ^If any of the tdsqlite3_bind_*() routines are called with a NULL pointer
** for the [prepared statement] or with a prepared statement for which
-** [sqlite3_step()] has been called more recently than [sqlite3_reset()],
-** then the call will return [SQLITE_MISUSE]. If any sqlite3_bind_()
+** [tdsqlite3_step()] has been called more recently than [tdsqlite3_reset()],
+** then the call will return [SQLITE_MISUSE]. If any tdsqlite3_bind_()
** routine is passed a [prepared statement] that has been finalized, the
** result is undefined and probably harmful.
**
-** ^Bindings are not cleared by the [sqlite3_reset()] routine.
+** ^Bindings are not cleared by the [tdsqlite3_reset()] routine.
** ^Unbound parameters are interpreted as NULL.
**
-** ^The sqlite3_bind_* routines return [SQLITE_OK] on success or an
+** ^The tdsqlite3_bind_* routines return [SQLITE_OK] on success or an
** [error code] if anything goes wrong.
** ^[SQLITE_TOOBIG] might be returned if the size of a string or BLOB
-** exceeds limits imposed by [sqlite3_limit]([SQLITE_LIMIT_LENGTH]) or
+** exceeds limits imposed by [tdsqlite3_limit]([SQLITE_LIMIT_LENGTH]) or
** [SQLITE_MAX_LENGTH].
** ^[SQLITE_RANGE] is returned if the parameter
** index is out of range. ^[SQLITE_NOMEM] is returned if malloc() fails.
**
-** See also: [sqlite3_bind_parameter_count()],
-** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()].
+** See also: [tdsqlite3_bind_parameter_count()],
+** [tdsqlite3_bind_parameter_name()], and [tdsqlite3_bind_parameter_index()].
*/
-SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*));
-SQLITE_API int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64,
+SQLITE_API int tdsqlite3_bind_blob(tdsqlite3_stmt*, int, const void*, int n, void(*)(void*));
+SQLITE_API int tdsqlite3_bind_blob64(tdsqlite3_stmt*, int, const void*, tdsqlite3_uint64,
void(*)(void*));
-SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double);
-SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int);
-SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64);
-SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int);
-SQLITE_API int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*));
-SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*));
-SQLITE_API int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64,
+SQLITE_API int tdsqlite3_bind_double(tdsqlite3_stmt*, int, double);
+SQLITE_API int tdsqlite3_bind_int(tdsqlite3_stmt*, int, int);
+SQLITE_API int tdsqlite3_bind_int64(tdsqlite3_stmt*, int, tdsqlite3_int64);
+SQLITE_API int tdsqlite3_bind_null(tdsqlite3_stmt*, int);
+SQLITE_API int tdsqlite3_bind_text(tdsqlite3_stmt*,int,const char*,int,void(*)(void*));
+SQLITE_API int tdsqlite3_bind_text16(tdsqlite3_stmt*, int, const void*, int, void(*)(void*));
+SQLITE_API int tdsqlite3_bind_text64(tdsqlite3_stmt*, int, const char*, tdsqlite3_uint64,
void(*)(void*), unsigned char encoding);
-SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*);
-SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n);
-SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64);
+SQLITE_API int tdsqlite3_bind_value(tdsqlite3_stmt*, int, const tdsqlite3_value*);
+SQLITE_API int tdsqlite3_bind_pointer(tdsqlite3_stmt*, int, void*, const char*,void(*)(void*));
+SQLITE_API int tdsqlite3_bind_zeroblob(tdsqlite3_stmt*, int, int n);
+SQLITE_API int tdsqlite3_bind_zeroblob64(tdsqlite3_stmt*, int, tdsqlite3_uint64);
/*
** CAPI3REF: Number Of SQL Parameters
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^This routine can be used to find the number of [SQL parameters]
** in a [prepared statement]. SQL parameters are tokens of the
** form "?", "?NNN", ":AAA", "$AAA", or "@AAA" that serve as
-** placeholders for values that are [sqlite3_bind_blob | bound]
+** placeholders for values that are [tdsqlite3_bind_blob | bound]
** to the parameters at a later time.
**
** ^(This routine actually returns the index of the largest (rightmost)
@@ -3812,17 +4313,17 @@ SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64);
** number of unique parameters. If parameters of the ?NNN form are used,
** there may be gaps in the list.)^
**
-** See also: [sqlite3_bind_blob|sqlite3_bind()],
-** [sqlite3_bind_parameter_name()], and
-** [sqlite3_bind_parameter_index()].
+** See also: [tdsqlite3_bind_blob|tdsqlite3_bind()],
+** [tdsqlite3_bind_parameter_name()], and
+** [tdsqlite3_bind_parameter_index()].
*/
-SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*);
+SQLITE_API int tdsqlite3_bind_parameter_count(tdsqlite3_stmt*);
/*
** CAPI3REF: Name Of A Host Parameter
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_bind_parameter_name(P,N) interface returns
+** ^The tdsqlite3_bind_parameter_name(P,N) interface returns
** the name of the N-th [SQL parameter] in the [prepared statement] P.
** ^(SQL parameters of the form "?NNN" or ":AAA" or "@AAA" or "$AAA"
** have a name which is the string "?NNN" or ":AAA" or "@AAA" or "$AAA"
@@ -3837,73 +4338,78 @@ SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*);
** ^If the value N is out of range or if the N-th parameter is
** nameless, then NULL is returned. ^The returned string is
** always in UTF-8 encoding even if the named parameter was
-** originally specified as UTF-16 in [sqlite3_prepare16()] or
-** [sqlite3_prepare16_v2()].
+** originally specified as UTF-16 in [tdsqlite3_prepare16()],
+** [tdsqlite3_prepare16_v2()], or [tdsqlite3_prepare16_v3()].
**
-** See also: [sqlite3_bind_blob|sqlite3_bind()],
-** [sqlite3_bind_parameter_count()], and
-** [sqlite3_bind_parameter_index()].
+** See also: [tdsqlite3_bind_blob|tdsqlite3_bind()],
+** [tdsqlite3_bind_parameter_count()], and
+** [tdsqlite3_bind_parameter_index()].
*/
-SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int);
+SQLITE_API const char *tdsqlite3_bind_parameter_name(tdsqlite3_stmt*, int);
/*
** CAPI3REF: Index Of A Parameter With A Given Name
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^Return the index of an SQL parameter given its name. ^The
** index value returned is suitable for use as the second
-** parameter to [sqlite3_bind_blob|sqlite3_bind()]. ^A zero
+** parameter to [tdsqlite3_bind_blob|tdsqlite3_bind()]. ^A zero
** is returned if no matching parameter is found. ^The parameter
** name must be given in UTF-8 even if the original statement
-** was prepared from UTF-16 text using [sqlite3_prepare16_v2()].
+** was prepared from UTF-16 text using [tdsqlite3_prepare16_v2()] or
+** [tdsqlite3_prepare16_v3()].
**
-** See also: [sqlite3_bind_blob|sqlite3_bind()],
-** [sqlite3_bind_parameter_count()], and
-** [sqlite3_bind_parameter_name()].
+** See also: [tdsqlite3_bind_blob|tdsqlite3_bind()],
+** [tdsqlite3_bind_parameter_count()], and
+** [tdsqlite3_bind_parameter_name()].
*/
-SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
+SQLITE_API int tdsqlite3_bind_parameter_index(tdsqlite3_stmt*, const char *zName);
/*
** CAPI3REF: Reset All Bindings On A Prepared Statement
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^Contrary to the intuition of many, [sqlite3_reset()] does not reset
-** the [sqlite3_bind_blob | bindings] on a [prepared statement].
+** ^Contrary to the intuition of many, [tdsqlite3_reset()] does not reset
+** the [tdsqlite3_bind_blob | bindings] on a [prepared statement].
** ^Use this routine to reset all host parameters to NULL.
*/
-SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*);
+SQLITE_API int tdsqlite3_clear_bindings(tdsqlite3_stmt*);
/*
** CAPI3REF: Number Of Columns In A Result Set
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^Return the number of columns in the result set returned by the
-** [prepared statement]. ^This routine returns 0 if pStmt is an SQL
-** statement that does not return data (for example an [UPDATE]).
+** [prepared statement]. ^If this routine returns 0, that means the
+** [prepared statement] returns no data (for example an [UPDATE]).
+** ^However, just because this routine returns a positive number does not
+** mean that one or more rows of data will be returned. ^A SELECT statement
+** will always have a positive tdsqlite3_column_count() but depending on the
+** WHERE clause constraints and the table content, it might return no rows.
**
-** See also: [sqlite3_data_count()]
+** See also: [tdsqlite3_data_count()]
*/
-SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_column_count(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Column Names In A Result Set
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^These routines return the name assigned to a particular column
-** in the result set of a [SELECT] statement. ^The sqlite3_column_name()
+** in the result set of a [SELECT] statement. ^The tdsqlite3_column_name()
** interface returns a pointer to a zero-terminated UTF-8 string
-** and sqlite3_column_name16() returns a pointer to a zero-terminated
+** and tdsqlite3_column_name16() returns a pointer to a zero-terminated
** UTF-16 string. ^The first parameter is the [prepared statement]
** that implements the [SELECT] statement. ^The second parameter is the
** column number. ^The leftmost column is number 0.
**
** ^The returned string pointer is valid until either the [prepared statement]
-** is destroyed by [sqlite3_finalize()] or until the statement is automatically
-** reprepared by the first call to [sqlite3_step()] for a particular run
+** is destroyed by [tdsqlite3_finalize()] or until the statement is automatically
+** reprepared by the first call to [tdsqlite3_step()] for a particular run
** or until the next call to
-** sqlite3_column_name() or sqlite3_column_name16() on the same column.
+** tdsqlite3_column_name() or tdsqlite3_column_name16() on the same column.
**
-** ^If sqlite3_malloc() fails during the processing of either routine
+** ^If tdsqlite3_malloc() fails during the processing of either routine
** (for example during a conversion from UTF-8 to UTF-16) then a
** NULL pointer is returned.
**
@@ -3912,12 +4418,12 @@ SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt);
** then the name of the column is unspecified and may change from
** one release of SQLite to the next.
*/
-SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N);
-SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
+SQLITE_API const char *tdsqlite3_column_name(tdsqlite3_stmt*, int N);
+SQLITE_API const void *tdsqlite3_column_name16(tdsqlite3_stmt*, int N);
/*
** CAPI3REF: Source Of Data In A Query Result
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^These routines provide a means to determine the database, table, and
** table column that is the origin of a particular result column in
@@ -3927,8 +4433,8 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
** the database name, the _table_ routines return the table name, and
** the origin_ routines return the column name.
** ^The returned string is valid until the [prepared statement] is destroyed
-** using [sqlite3_finalize()] or until the statement is automatically
-** reprepared by the first call to [sqlite3_step()] for a particular run
+** using [tdsqlite3_finalize()] or until the statement is automatically
+** reprepared by the first call to [tdsqlite3_step()] for a particular run
** or until the same information is requested
** again in a different encoding.
**
@@ -3942,7 +4448,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
**
** ^If the Nth column returned by the statement is an expression or
** subquery and is not a column value, then all of these functions return
-** NULL. ^These routine might also return NULL if a memory allocation error
+** NULL. ^These routines might also return NULL if a memory allocation error
** occurs. ^Otherwise, they return the name of the attached database, table,
** or column that query result column was extracted from.
**
@@ -3952,25 +4458,21 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
** ^These APIs are only available if the library was compiled with the
** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol.
**
-** If two or more threads call one or more of these routines against the same
-** prepared statement and column at the same time then the results are
-** undefined.
-**
** If two or more threads call one or more
-** [sqlite3_column_database_name | column metadata interfaces]
+** [tdsqlite3_column_database_name | column metadata interfaces]
** for the same [prepared statement] and result column
** at the same time then the results are undefined.
*/
-SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int);
-SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int);
-SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int);
-SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int);
-SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int);
-SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int);
+SQLITE_API const char *tdsqlite3_column_database_name(tdsqlite3_stmt*,int);
+SQLITE_API const void *tdsqlite3_column_database_name16(tdsqlite3_stmt*,int);
+SQLITE_API const char *tdsqlite3_column_table_name(tdsqlite3_stmt*,int);
+SQLITE_API const void *tdsqlite3_column_table_name16(tdsqlite3_stmt*,int);
+SQLITE_API const char *tdsqlite3_column_origin_name(tdsqlite3_stmt*,int);
+SQLITE_API const void *tdsqlite3_column_origin_name16(tdsqlite3_stmt*,int);
/*
** CAPI3REF: Declared Datatype Of A Query Result
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^(The first parameter is a [prepared statement].
** If this statement is a [SELECT] statement and the Nth column of the
@@ -3998,23 +4500,25 @@ SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int);
** is associated with individual values, not with the containers
** used to hold those values.
*/
-SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int);
-SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
+SQLITE_API const char *tdsqlite3_column_decltype(tdsqlite3_stmt*,int);
+SQLITE_API const void *tdsqlite3_column_decltype16(tdsqlite3_stmt*,int);
/*
** CAPI3REF: Evaluate An SQL Statement
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** After a [prepared statement] has been prepared using either
-** [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] or one of the legacy
-** interfaces [sqlite3_prepare()] or [sqlite3_prepare16()], this function
+** After a [prepared statement] has been prepared using any of
+** [tdsqlite3_prepare_v2()], [tdsqlite3_prepare_v3()], [tdsqlite3_prepare16_v2()],
+** or [tdsqlite3_prepare16_v3()] or one of the legacy
+** interfaces [tdsqlite3_prepare()] or [tdsqlite3_prepare16()], this function
** must be called one or more times to evaluate the statement.
**
-** The details of the behavior of the sqlite3_step() interface depend
-** on whether the statement was prepared using the newer "v2" interface
-** [sqlite3_prepare_v2()] and [sqlite3_prepare16_v2()] or the older legacy
-** interface [sqlite3_prepare()] and [sqlite3_prepare16()]. The use of the
-** new "v2" interface is recommended for new applications but the legacy
+** The details of the behavior of the tdsqlite3_step() interface depend
+** on whether the statement was prepared using the newer "vX" interfaces
+** [tdsqlite3_prepare_v3()], [tdsqlite3_prepare_v2()], [tdsqlite3_prepare16_v3()],
+** [tdsqlite3_prepare16_v2()] or the older legacy
+** interfaces [tdsqlite3_prepare()] and [tdsqlite3_prepare16()]. The use of the
+** new "vX" interface is recommended for new applications but the legacy
** interface will continue to be supported.
**
** ^In the legacy interface, the return value will be either [SQLITE_BUSY],
@@ -4030,78 +4534,79 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
** continuing.
**
** ^[SQLITE_DONE] means that the statement has finished executing
-** successfully. sqlite3_step() should not be called again on this virtual
-** machine without first calling [sqlite3_reset()] to reset the virtual
+** successfully. tdsqlite3_step() should not be called again on this virtual
+** machine without first calling [tdsqlite3_reset()] to reset the virtual
** machine back to its initial state.
**
** ^If the SQL statement being executed returns any data, then [SQLITE_ROW]
** is returned each time a new row of data is ready for processing by the
** caller. The values may be accessed using the [column access functions].
-** sqlite3_step() is called again to retrieve the next row of data.
+** tdsqlite3_step() is called again to retrieve the next row of data.
**
** ^[SQLITE_ERROR] means that a run-time error (such as a constraint
-** violation) has occurred. sqlite3_step() should not be called again on
-** the VM. More information may be found by calling [sqlite3_errmsg()].
+** violation) has occurred. tdsqlite3_step() should not be called again on
+** the VM. More information may be found by calling [tdsqlite3_errmsg()].
** ^With the legacy interface, a more specific error code (for example,
** [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth)
-** can be obtained by calling [sqlite3_reset()] on the
+** can be obtained by calling [tdsqlite3_reset()] on the
** [prepared statement]. ^In the "v2" interface,
-** the more specific error code is returned directly by sqlite3_step().
+** the more specific error code is returned directly by tdsqlite3_step().
**
** [SQLITE_MISUSE] means that the this routine was called inappropriately.
** Perhaps it was called on a [prepared statement] that has
-** already been [sqlite3_finalize | finalized] or on one that had
+** already been [tdsqlite3_finalize | finalized] or on one that had
** previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could
** be the case that the same database connection is being used by two or
** more threads at the same moment in time.
**
** For all versions of SQLite up to and including 3.6.23.1, a call to
-** [sqlite3_reset()] was required after sqlite3_step() returned anything
+** [tdsqlite3_reset()] was required after tdsqlite3_step() returned anything
** other than [SQLITE_ROW] before any subsequent invocation of
-** sqlite3_step(). Failure to reset the prepared statement using
-** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
-** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
-** sqlite3_step() began
-** calling [sqlite3_reset()] automatically in this circumstance rather
+** tdsqlite3_step(). Failure to reset the prepared statement using
+** [tdsqlite3_reset()] would result in an [SQLITE_MISUSE] return from
+** tdsqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
+** tdsqlite3_step() began
+** calling [tdsqlite3_reset()] automatically in this circumstance rather
** than returning [SQLITE_MISUSE]. This is not considered a compatibility
** break because any application that ever receives an SQLITE_MISUSE error
** is broken by definition. The [SQLITE_OMIT_AUTORESET] compile-time option
** can be used to restore the legacy behavior.
**
-** <b>Goofy Interface Alert:</b> In the legacy interface, the sqlite3_step()
+** <b>Goofy Interface Alert:</b> In the legacy interface, the tdsqlite3_step()
** API always returns a generic error code, [SQLITE_ERROR], following any
** error other than [SQLITE_BUSY] and [SQLITE_MISUSE]. You must call
-** [sqlite3_reset()] or [sqlite3_finalize()] in order to find one of the
+** [tdsqlite3_reset()] or [tdsqlite3_finalize()] in order to find one of the
** specific [error codes] that better describes the error.
** We admit that this is a goofy design. The problem has been fixed
** with the "v2" interface. If you prepare all of your SQL statements
-** using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead
-** of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces,
+** using [tdsqlite3_prepare_v3()] or [tdsqlite3_prepare_v2()]
+** or [tdsqlite3_prepare16_v2()] or [tdsqlite3_prepare16_v3()] instead
+** of the legacy [tdsqlite3_prepare()] and [tdsqlite3_prepare16()] interfaces,
** then the more specific [error codes] are returned directly
-** by sqlite3_step(). The use of the "v2" interface is recommended.
+** by tdsqlite3_step(). The use of the "vX" interfaces is recommended.
*/
-SQLITE_API int sqlite3_step(sqlite3_stmt*);
+SQLITE_API int tdsqlite3_step(tdsqlite3_stmt*);
/*
** CAPI3REF: Number of columns in a result set
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_data_count(P) interface returns the number of columns in the
+** ^The tdsqlite3_data_count(P) interface returns the number of columns in the
** current row of the result set of [prepared statement] P.
** ^If prepared statement P does not have results ready to return
-** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of
-** interfaces) then sqlite3_data_count(P) returns 0.
-** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer.
-** ^The sqlite3_data_count(P) routine returns 0 if the previous call to
-** [sqlite3_step](P) returned [SQLITE_DONE]. ^The sqlite3_data_count(P)
-** will return non-zero if previous call to [sqlite3_step](P) returned
+** (via calls to the [tdsqlite3_column_int | tdsqlite3_column()] family of
+** interfaces) then tdsqlite3_data_count(P) returns 0.
+** ^The tdsqlite3_data_count(P) routine also returns 0 if P is a NULL pointer.
+** ^The tdsqlite3_data_count(P) routine returns 0 if the previous call to
+** [tdsqlite3_step](P) returned [SQLITE_DONE]. ^The tdsqlite3_data_count(P)
+** will return non-zero if previous call to [tdsqlite3_step](P) returned
** [SQLITE_ROW], except in the case of the [PRAGMA incremental_vacuum]
** where it always returns zero since each step of that multi-step
** pragma returns 0 columns of data.
**
-** See also: [sqlite3_column_count()]
+** See also: [tdsqlite3_column_count()]
*/
-SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_data_count(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Fundamental Datatypes
@@ -4138,79 +4643,118 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
/*
** CAPI3REF: Result Values From A Query
** KEYWORDS: {column access functions}
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
+**
+** <b>Summary:</b>
+** <blockquote><table border=0 cellpadding=0 cellspacing=0>
+** <tr><td><b>tdsqlite3_column_blob</b><td>&rarr;<td>BLOB result
+** <tr><td><b>tdsqlite3_column_double</b><td>&rarr;<td>REAL result
+** <tr><td><b>tdsqlite3_column_int</b><td>&rarr;<td>32-bit INTEGER result
+** <tr><td><b>tdsqlite3_column_int64</b><td>&rarr;<td>64-bit INTEGER result
+** <tr><td><b>tdsqlite3_column_text</b><td>&rarr;<td>UTF-8 TEXT result
+** <tr><td><b>tdsqlite3_column_text16</b><td>&rarr;<td>UTF-16 TEXT result
+** <tr><td><b>tdsqlite3_column_value</b><td>&rarr;<td>The result as an
+** [tdsqlite3_value|unprotected tdsqlite3_value] object.
+** <tr><td>&nbsp;<td>&nbsp;<td>&nbsp;
+** <tr><td><b>tdsqlite3_column_bytes</b><td>&rarr;<td>Size of a BLOB
+** or a UTF-8 TEXT result in bytes
+** <tr><td><b>tdsqlite3_column_bytes16&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>Size of UTF-16
+** TEXT in bytes
+** <tr><td><b>tdsqlite3_column_type</b><td>&rarr;<td>Default
+** datatype of the result
+** </table></blockquote>
+**
+** <b>Details:</b>
**
** ^These routines return information about a single column of the current
** result row of a query. ^In every case the first argument is a pointer
-** to the [prepared statement] that is being evaluated (the [sqlite3_stmt*]
-** that was returned from [sqlite3_prepare_v2()] or one of its variants)
+** to the [prepared statement] that is being evaluated (the [tdsqlite3_stmt*]
+** that was returned from [tdsqlite3_prepare_v2()] or one of its variants)
** and the second argument is the index of the column for which information
** should be returned. ^The leftmost column of the result set has the index 0.
** ^The number of columns in the result can be determined using
-** [sqlite3_column_count()].
+** [tdsqlite3_column_count()].
**
** If the SQL statement does not currently point to a valid row, or if the
** column index is out of range, the result is undefined.
** These routines may only be called when the most recent call to
-** [sqlite3_step()] has returned [SQLITE_ROW] and neither
-** [sqlite3_reset()] nor [sqlite3_finalize()] have been called subsequently.
-** If any of these routines are called after [sqlite3_reset()] or
-** [sqlite3_finalize()] or after [sqlite3_step()] has returned
+** [tdsqlite3_step()] has returned [SQLITE_ROW] and neither
+** [tdsqlite3_reset()] nor [tdsqlite3_finalize()] have been called subsequently.
+** If any of these routines are called after [tdsqlite3_reset()] or
+** [tdsqlite3_finalize()] or after [tdsqlite3_step()] has returned
** something other than [SQLITE_ROW], the results are undefined.
-** If [sqlite3_step()] or [sqlite3_reset()] or [sqlite3_finalize()]
+** If [tdsqlite3_step()] or [tdsqlite3_reset()] or [tdsqlite3_finalize()]
** are called from a different thread while any of these routines
** are pending, then the results are undefined.
**
-** ^The sqlite3_column_type() routine returns the
+** The first six interfaces (_blob, _double, _int, _int64, _text, and _text16)
+** each return the value of a result column in a specific data format. If
+** the result column is not initially in the requested format (for example,
+** if the query returns an integer but the tdsqlite3_column_text() interface
+** is used to extract the value) then an automatic type conversion is performed.
+**
+** ^The tdsqlite3_column_type() routine returns the
** [SQLITE_INTEGER | datatype code] for the initial data type
** of the result column. ^The returned value is one of [SQLITE_INTEGER],
-** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL]. The value
-** returned by sqlite3_column_type() is only meaningful if no type
-** conversions have occurred as described below. After a type conversion,
-** the value returned by sqlite3_column_type() is undefined. Future
-** versions of SQLite may change the behavior of sqlite3_column_type()
+** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL].
+** The return value of tdsqlite3_column_type() can be used to decide which
+** of the first six interface should be used to extract the column value.
+** The value returned by tdsqlite3_column_type() is only meaningful if no
+** automatic type conversions have occurred for the value in question.
+** After a type conversion, the result of calling tdsqlite3_column_type()
+** is undefined, though harmless. Future
+** versions of SQLite may change the behavior of tdsqlite3_column_type()
** following a type conversion.
**
-** ^If the result is a BLOB or UTF-8 string then the sqlite3_column_bytes()
+** If the result is a BLOB or a TEXT string, then the tdsqlite3_column_bytes()
+** or tdsqlite3_column_bytes16() interfaces can be used to determine the size
+** of that BLOB or string.
+**
+** ^If the result is a BLOB or UTF-8 string then the tdsqlite3_column_bytes()
** routine returns the number of bytes in that BLOB or string.
-** ^If the result is a UTF-16 string, then sqlite3_column_bytes() converts
+** ^If the result is a UTF-16 string, then tdsqlite3_column_bytes() converts
** the string to UTF-8 and then returns the number of bytes.
-** ^If the result is a numeric value then sqlite3_column_bytes() uses
-** [sqlite3_snprintf()] to convert that value to a UTF-8 string and returns
+** ^If the result is a numeric value then tdsqlite3_column_bytes() uses
+** [tdsqlite3_snprintf()] to convert that value to a UTF-8 string and returns
** the number of bytes in that string.
-** ^If the result is NULL, then sqlite3_column_bytes() returns zero.
+** ^If the result is NULL, then tdsqlite3_column_bytes() returns zero.
**
-** ^If the result is a BLOB or UTF-16 string then the sqlite3_column_bytes16()
+** ^If the result is a BLOB or UTF-16 string then the tdsqlite3_column_bytes16()
** routine returns the number of bytes in that BLOB or string.
-** ^If the result is a UTF-8 string, then sqlite3_column_bytes16() converts
+** ^If the result is a UTF-8 string, then tdsqlite3_column_bytes16() converts
** the string to UTF-16 and then returns the number of bytes.
-** ^If the result is a numeric value then sqlite3_column_bytes16() uses
-** [sqlite3_snprintf()] to convert that value to a UTF-16 string and returns
+** ^If the result is a numeric value then tdsqlite3_column_bytes16() uses
+** [tdsqlite3_snprintf()] to convert that value to a UTF-16 string and returns
** the number of bytes in that string.
-** ^If the result is NULL, then sqlite3_column_bytes16() returns zero.
+** ^If the result is NULL, then tdsqlite3_column_bytes16() returns zero.
**
-** ^The values returned by [sqlite3_column_bytes()] and
-** [sqlite3_column_bytes16()] do not include the zero terminators at the end
+** ^The values returned by [tdsqlite3_column_bytes()] and
+** [tdsqlite3_column_bytes16()] do not include the zero terminators at the end
** of the string. ^For clarity: the values returned by
-** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of
+** [tdsqlite3_column_bytes()] and [tdsqlite3_column_bytes16()] are the number of
** bytes in the string, not the number of characters.
**
-** ^Strings returned by sqlite3_column_text() and sqlite3_column_text16(),
+** ^Strings returned by tdsqlite3_column_text() and tdsqlite3_column_text16(),
** even empty strings, are always zero-terminated. ^The return
-** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
-**
-** <b>Warning:</b> ^The object returned by [sqlite3_column_value()] is an
-** [unprotected sqlite3_value] object. In a multithreaded environment,
-** an unprotected sqlite3_value object may only be used safely with
-** [sqlite3_bind_value()] and [sqlite3_result_value()].
-** If the [unprotected sqlite3_value] object returned by
-** [sqlite3_column_value()] is used in any other way, including calls
-** to routines like [sqlite3_value_int()], [sqlite3_value_text()],
-** or [sqlite3_value_bytes()], the behavior is not threadsafe.
-**
-** These routines attempt to convert the value where appropriate. ^For
-** example, if the internal representation is FLOAT and a text result
-** is requested, [sqlite3_snprintf()] is used internally to perform the
+** value from tdsqlite3_column_blob() for a zero-length BLOB is a NULL pointer.
+**
+** <b>Warning:</b> ^The object returned by [tdsqlite3_column_value()] is an
+** [unprotected tdsqlite3_value] object. In a multithreaded environment,
+** an unprotected tdsqlite3_value object may only be used safely with
+** [tdsqlite3_bind_value()] and [tdsqlite3_result_value()].
+** If the [unprotected tdsqlite3_value] object returned by
+** [tdsqlite3_column_value()] is used in any other way, including calls
+** to routines like [tdsqlite3_value_int()], [tdsqlite3_value_text()],
+** or [tdsqlite3_value_bytes()], the behavior is not threadsafe.
+** Hence, the tdsqlite3_column_value() interface
+** is normally only useful within the implementation of
+** [application-defined SQL functions] or [virtual tables], not within
+** top-level application code.
+**
+** The these routines may attempt to convert the datatype of the result.
+** ^For example, if the internal representation is FLOAT and a text result
+** is requested, [tdsqlite3_snprintf()] is used internally to perform the
** conversion automatically. ^(The following table details the conversions
** that are applied:
**
@@ -4238,20 +4782,20 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** </blockquote>)^
**
** Note that when type conversions occur, pointers returned by prior
-** calls to sqlite3_column_blob(), sqlite3_column_text(), and/or
-** sqlite3_column_text16() may be invalidated.
+** calls to tdsqlite3_column_blob(), tdsqlite3_column_text(), and/or
+** tdsqlite3_column_text16() may be invalidated.
** Type conversions and pointer invalidations might occur
** in the following cases:
**
** <ul>
-** <li> The initial content is a BLOB and sqlite3_column_text() or
-** sqlite3_column_text16() is called. A zero-terminator might
+** <li> The initial content is a BLOB and tdsqlite3_column_text() or
+** tdsqlite3_column_text16() is called. A zero-terminator might
** need to be added to the string.</li>
-** <li> The initial content is UTF-8 text and sqlite3_column_bytes16() or
-** sqlite3_column_text16() is called. The content must be converted
+** <li> The initial content is UTF-8 text and tdsqlite3_column_bytes16() or
+** tdsqlite3_column_text16() is called. The content must be converted
** to UTF-16.</li>
-** <li> The initial content is UTF-16 text and sqlite3_column_bytes() or
-** sqlite3_column_text() is called. The content must be converted
+** <li> The initial content is UTF-16 text and tdsqlite3_column_bytes() or
+** tdsqlite3_column_text() is called. The content must be converted
** to UTF-8.</li>
** </ul>
**
@@ -4265,62 +4809,76 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt);
** in one of the following ways:
**
** <ul>
-** <li>sqlite3_column_text() followed by sqlite3_column_bytes()</li>
-** <li>sqlite3_column_blob() followed by sqlite3_column_bytes()</li>
-** <li>sqlite3_column_text16() followed by sqlite3_column_bytes16()</li>
+** <li>tdsqlite3_column_text() followed by tdsqlite3_column_bytes()</li>
+** <li>tdsqlite3_column_blob() followed by tdsqlite3_column_bytes()</li>
+** <li>tdsqlite3_column_text16() followed by tdsqlite3_column_bytes16()</li>
** </ul>
**
-** In other words, you should call sqlite3_column_text(),
-** sqlite3_column_blob(), or sqlite3_column_text16() first to force the result
-** into the desired format, then invoke sqlite3_column_bytes() or
-** sqlite3_column_bytes16() to find the size of the result. Do not mix calls
-** to sqlite3_column_text() or sqlite3_column_blob() with calls to
-** sqlite3_column_bytes16(), and do not mix calls to sqlite3_column_text16()
-** with calls to sqlite3_column_bytes().
+** In other words, you should call tdsqlite3_column_text(),
+** tdsqlite3_column_blob(), or tdsqlite3_column_text16() first to force the result
+** into the desired format, then invoke tdsqlite3_column_bytes() or
+** tdsqlite3_column_bytes16() to find the size of the result. Do not mix calls
+** to tdsqlite3_column_text() or tdsqlite3_column_blob() with calls to
+** tdsqlite3_column_bytes16(), and do not mix calls to tdsqlite3_column_text16()
+** with calls to tdsqlite3_column_bytes().
**
** ^The pointers returned are valid until a type conversion occurs as
-** described above, or until [sqlite3_step()] or [sqlite3_reset()] or
-** [sqlite3_finalize()] is called. ^The memory space used to hold strings
-** and BLOBs is freed automatically. Do <em>not</em> pass the pointers returned
-** from [sqlite3_column_blob()], [sqlite3_column_text()], etc. into
-** [sqlite3_free()].
-**
-** ^(If a memory allocation error occurs during the evaluation of any
-** of these routines, a default value is returned. The default value
-** is either the integer 0, the floating point number 0.0, or a NULL
-** pointer. Subsequent calls to [sqlite3_errcode()] will return
-** [SQLITE_NOMEM].)^
-*/
-SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
-SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
-SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol);
-SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol);
-SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol);
-SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol);
-SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
-SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol);
-SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol);
-SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
+** described above, or until [tdsqlite3_step()] or [tdsqlite3_reset()] or
+** [tdsqlite3_finalize()] is called. ^The memory space used to hold strings
+** and BLOBs is freed automatically. Do not pass the pointers returned
+** from [tdsqlite3_column_blob()], [tdsqlite3_column_text()], etc. into
+** [tdsqlite3_free()].
+**
+** As long as the input parameters are correct, these routines will only
+** fail if an out-of-memory error occurs during a format conversion.
+** Only the following subset of interfaces are subject to out-of-memory
+** errors:
+**
+** <ul>
+** <li> tdsqlite3_column_blob()
+** <li> tdsqlite3_column_text()
+** <li> tdsqlite3_column_text16()
+** <li> tdsqlite3_column_bytes()
+** <li> tdsqlite3_column_bytes16()
+** </ul>
+**
+** If an out-of-memory error occurs, then the return value from these
+** routines is the same as if the column had contained an SQL NULL value.
+** Valid SQL NULL returns can be distinguished from out-of-memory errors
+** by invoking the [tdsqlite3_errcode()] immediately after the suspect
+** return value is obtained and before any
+** other SQLite interface is called on the same [database connection].
+*/
+SQLITE_API const void *tdsqlite3_column_blob(tdsqlite3_stmt*, int iCol);
+SQLITE_API double tdsqlite3_column_double(tdsqlite3_stmt*, int iCol);
+SQLITE_API int tdsqlite3_column_int(tdsqlite3_stmt*, int iCol);
+SQLITE_API tdsqlite3_int64 tdsqlite3_column_int64(tdsqlite3_stmt*, int iCol);
+SQLITE_API const unsigned char *tdsqlite3_column_text(tdsqlite3_stmt*, int iCol);
+SQLITE_API const void *tdsqlite3_column_text16(tdsqlite3_stmt*, int iCol);
+SQLITE_API tdsqlite3_value *tdsqlite3_column_value(tdsqlite3_stmt*, int iCol);
+SQLITE_API int tdsqlite3_column_bytes(tdsqlite3_stmt*, int iCol);
+SQLITE_API int tdsqlite3_column_bytes16(tdsqlite3_stmt*, int iCol);
+SQLITE_API int tdsqlite3_column_type(tdsqlite3_stmt*, int iCol);
/*
** CAPI3REF: Destroy A Prepared Statement Object
-** DESTRUCTOR: sqlite3_stmt
+** DESTRUCTOR: tdsqlite3_stmt
**
-** ^The sqlite3_finalize() function is called to delete a [prepared statement].
+** ^The tdsqlite3_finalize() function is called to delete a [prepared statement].
** ^If the most recent evaluation of the statement encountered no errors
-** or if the statement is never been evaluated, then sqlite3_finalize() returns
+** or if the statement is never been evaluated, then tdsqlite3_finalize() returns
** SQLITE_OK. ^If the most recent evaluation of statement S failed, then
-** sqlite3_finalize(S) returns the appropriate [error code] or
+** tdsqlite3_finalize(S) returns the appropriate [error code] or
** [extended error code].
**
-** ^The sqlite3_finalize(S) routine can be called at any point during
+** ^The tdsqlite3_finalize(S) routine can be called at any point during
** the life cycle of [prepared statement] S:
** before statement S is ever evaluated, after
-** one or more calls to [sqlite3_reset()], or after any call
-** to [sqlite3_step()] regardless of whether or not the statement has
+** one or more calls to [tdsqlite3_reset()], or after any call
+** to [tdsqlite3_step()] regardless of whether or not the statement has
** completed execution.
**
-** ^Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op.
+** ^Invoking tdsqlite3_finalize() on a NULL pointer is a harmless no-op.
**
** The application must finalize every [prepared statement] in order to avoid
** resource leaks. It is a grievous error for the application to try to use
@@ -4328,49 +4886,49 @@ SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol);
** statement after it has been finalized can result in undefined and
** undesirable behavior such as segfaults and heap corruption.
*/
-SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_finalize(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Reset A Prepared Statement Object
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** The sqlite3_reset() function is called to reset a [prepared statement]
+** The tdsqlite3_reset() function is called to reset a [prepared statement]
** object back to its initial state, ready to be re-executed.
** ^Any SQL statement variables that had values bound to them using
-** the [sqlite3_bind_blob | sqlite3_bind_*() API] retain their values.
-** Use [sqlite3_clear_bindings()] to reset the bindings.
+** the [tdsqlite3_bind_blob | tdsqlite3_bind_*() API] retain their values.
+** Use [tdsqlite3_clear_bindings()] to reset the bindings.
**
-** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S
+** ^The [tdsqlite3_reset(S)] interface resets the [prepared statement] S
** back to the beginning of its program.
**
-** ^If the most recent call to [sqlite3_step(S)] for the
+** ^If the most recent call to [tdsqlite3_step(S)] for the
** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE],
-** or if [sqlite3_step(S)] has never before been called on S,
-** then [sqlite3_reset(S)] returns [SQLITE_OK].
+** or if [tdsqlite3_step(S)] has never before been called on S,
+** then [tdsqlite3_reset(S)] returns [SQLITE_OK].
**
-** ^If the most recent call to [sqlite3_step(S)] for the
+** ^If the most recent call to [tdsqlite3_step(S)] for the
** [prepared statement] S indicated an error, then
-** [sqlite3_reset(S)] returns an appropriate [error code].
+** [tdsqlite3_reset(S)] returns an appropriate [error code].
**
-** ^The [sqlite3_reset(S)] interface does not change the values
-** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S.
+** ^The [tdsqlite3_reset(S)] interface does not change the values
+** of any [tdsqlite3_bind_blob|bindings] on the [prepared statement] S.
*/
-SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
+SQLITE_API int tdsqlite3_reset(tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Create Or Redefine SQL Functions
** KEYWORDS: {function creation routines}
-** KEYWORDS: {application-defined SQL function}
-** KEYWORDS: {application-defined SQL functions}
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^These functions (collectively known as "function creation routines")
** are used to add SQL functions or aggregates or to redefine the behavior
-** of existing SQL functions or aggregates. The only differences between
-** these routines are the text encoding expected for
-** the second parameter (the name of the function being created)
-** and the presence or absence of a destructor callback for
-** the application data pointer.
+** of existing SQL functions or aggregates. The only differences between
+** the three "tdsqlite3_create_function*" routines are the text encoding
+** expected for the second parameter (the name of the function being
+** created) and the presence or absence of a destructor callback for
+** the application data pointer. Function tdsqlite3_create_window_function()
+** is similar, but allows the user to supply the extra callback functions
+** needed by [aggregate window functions].
**
** ^The first parameter is the [database connection] to which the SQL
** function is to be added. ^If an application uses more than one database
@@ -4388,7 +4946,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** is the number of arguments that the SQL function or
** aggregate takes. ^If this parameter is -1, then the SQL function or
** aggregate may take any number of arguments between 0 and the limit
-** set by [sqlite3_limit]([SQLITE_LIMIT_FUNCTION_ARG]). If the third
+** set by [tdsqlite3_limit]([SQLITE_LIMIT_FUNCTION_ARG]). If the third
** parameter is less than -1 or greater than 127 then the behavior is
** undefined.
**
@@ -4396,9 +4954,9 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** [SQLITE_UTF8 | text encoding] this SQL function prefers for
** its parameters. The application should set this parameter to
** [SQLITE_UTF16LE] if the function implementation invokes
-** [sqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the
-** implementation invokes [sqlite3_value_text16be()] on an input, or
-** [SQLITE_UTF16] if [sqlite3_value_text16()] is used, or [SQLITE_UTF8]
+** [tdsqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the
+** implementation invokes [tdsqlite3_value_text16be()] on an input, or
+** [SQLITE_UTF16] if [tdsqlite3_value_text16()] is used, or [SQLITE_UTF8]
** otherwise. ^The same SQL function may be registered multiple times using
** different preferred text encodings, with different implementations for
** each encoding.
@@ -4413,10 +4971,28 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** perform additional optimizations on deterministic functions, so use
** of the [SQLITE_DETERMINISTIC] flag is recommended where possible.
**
+** ^The fourth parameter may also optionally include the [SQLITE_DIRECTONLY]
+** flag, which if present prevents the function from being invoked from
+** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions,
+** index expressions, or the WHERE clause of partial indexes.
+**
+** <span style="background-color:#ffff90;">
+** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
+** all application-defined SQL functions that do not need to be
+** used inside of triggers, view, CHECK constraints, or other elements of
+** the database schema. This flags is especially recommended for SQL
+** functions that have side effects or reveal internal application state.
+** Without this flag, an attacker might be able to modify the schema of
+** a database file to include invocations of the function with parameters
+** chosen by the attacker, which the application will then execute when
+** the database file is opened and read.
+** </span>
+**
** ^(The fifth parameter is an arbitrary pointer. The implementation of the
-** function can gain access to this pointer using [sqlite3_user_data()].)^
+** function can gain access to this pointer using [tdsqlite3_user_data()].)^
**
-** ^The sixth, seventh and eighth parameters, xFunc, xStep and xFinal, are
+** ^The sixth, seventh and eighth parameters passed to the three
+** "tdsqlite3_create_function*" functions, xFunc, xStep and xFinal, are
** pointers to C-language functions that implement the SQL function or
** aggregate. ^A scalar SQL function requires an implementation of the xFunc
** callback only; NULL pointers must be passed as the xStep and xFinal
@@ -4425,15 +5001,24 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** SQL function or aggregate, pass NULL pointers for all three function
** callbacks.
**
-** ^(If the ninth parameter to sqlite3_create_function_v2() is not NULL,
-** then it is destructor for the application data pointer.
-** The destructor is invoked when the function is deleted, either by being
-** overloaded or when the database connection closes.)^
-** ^The destructor is also invoked if the call to
-** sqlite3_create_function_v2() fails.
-** ^When the destructor callback of the tenth parameter is invoked, it
-** is passed a single argument which is a copy of the application data
-** pointer which was the fifth parameter to sqlite3_create_function_v2().
+** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue
+** and xInverse) passed to tdsqlite3_create_window_function are pointers to
+** C-language callbacks that implement the new function. xStep and xFinal
+** must both be non-NULL. xValue and xInverse may either both be NULL, in
+** which case a regular aggregate function is created, or must both be
+** non-NULL, in which case the new function may be used as either an aggregate
+** or aggregate window function. More details regarding the implementation
+** of aggregate window functions are
+** [user-defined window functions|available here].
+**
+** ^(If the final parameter to tdsqlite3_create_function_v2() or
+** tdsqlite3_create_window_function() is not NULL, then it is destructor for
+** the application data pointer. The destructor is invoked when the function
+** is deleted, either by being overloaded or when the database connection
+** closes.)^ ^The destructor is also invoked if the call to
+** tdsqlite3_create_function_v2() fails. ^When the destructor callback is
+** invoked, it is passed a single argument which is a copy of the application
+** data pointer which was the fifth parameter to tdsqlite3_create_function_v2().
**
** ^It is permitted to register multiple implementations of the same
** functions with the same name but with either differing numbers of
@@ -4455,35 +5040,47 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
** close the database connection nor finalize or reset the prepared
** statement in which the function is running.
*/
-SQLITE_API int sqlite3_create_function(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_create_function(
+ tdsqlite3 *db,
const char *zFunctionName,
int nArg,
int eTextRep,
void *pApp,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*)
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*)
);
-SQLITE_API int sqlite3_create_function16(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_create_function16(
+ tdsqlite3 *db,
const void *zFunctionName,
int nArg,
int eTextRep,
void *pApp,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*)
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*)
);
-SQLITE_API int sqlite3_create_function_v2(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_create_function_v2(
+ tdsqlite3 *db,
const char *zFunctionName,
int nArg,
int eTextRep,
void *pApp,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*),
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*),
+ void(*xDestroy)(void*)
+);
+SQLITE_API int tdsqlite3_create_window_function(
+ tdsqlite3 *db,
+ const char *zFunctionName,
+ int nArg,
+ int eTextRep,
+ void *pApp,
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*),
+ void (*xValue)(tdsqlite3_context*),
+ void (*xInverse)(tdsqlite3_context*,int,tdsqlite3_value**),
void(*xDestroy)(void*)
);
@@ -4498,17 +5095,77 @@ SQLITE_API int sqlite3_create_function_v2(
#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */
#define SQLITE_UTF16 4 /* Use native byte order */
#define SQLITE_ANY 5 /* Deprecated */
-#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */
+#define SQLITE_UTF16_ALIGNED 8 /* tdsqlite3_create_collation only */
/*
** CAPI3REF: Function Flags
**
** These constants may be ORed together with the
** [SQLITE_UTF8 | preferred text encoding] as the fourth argument
-** to [sqlite3_create_function()], [sqlite3_create_function16()], or
-** [sqlite3_create_function_v2()].
+** to [tdsqlite3_create_function()], [tdsqlite3_create_function16()], or
+** [tdsqlite3_create_function_v2()].
+**
+** <dl>
+** [[SQLITE_DETERMINISTIC]] <dt>SQLITE_DETERMINISTIC</dt><dd>
+** The SQLITE_DETERMINISTIC flag means that the new function always gives
+** the same output when the input parameters are the same.
+** The [abs|abs() function] is deterministic, for example, but
+** [randomblob|randomblob()] is not. Functions must
+** be deterministic in order to be used in certain contexts such as
+** with the WHERE clause of [partial indexes] or in [generated columns].
+** SQLite might also optimize deterministic functions by factoring them
+** out of inner loops.
+** </dd>
+**
+** [[SQLITE_DIRECTONLY]] <dt>SQLITE_DIRECTONLY</dt><dd>
+** The SQLITE_DIRECTONLY flag means that the function may only be invoked
+** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in
+** schema structures such as [CHECK constraints], [DEFAULT clauses],
+** [expression indexes], [partial indexes], or [generated columns].
+** The SQLITE_DIRECTONLY flags is a security feature which is recommended
+** for all [application-defined SQL functions], and especially for functions
+** that have side-effects or that could potentially leak sensitive
+** information.
+** </dd>
+**
+** [[SQLITE_INNOCUOUS]] <dt>SQLITE_INNOCUOUS</dt><dd>
+** The SQLITE_INNOCUOUS flag means that the function is unlikely
+** to cause problems even if misused. An innocuous function should have
+** no side effects and should not depend on any values other than its
+** input parameters. The [abs|abs() function] is an example of an
+** innocuous function.
+** The [load_extension() SQL function] is not innocuous because of its
+** side effects.
+** <p> SQLITE_INNOCUOUS is similar to SQLITE_DETERMINISTIC, but is not
+** exactly the same. The [random|random() function] is an example of a
+** function that is innocuous but not deterministic.
+** <p>Some heightened security settings
+** ([SQLITE_DBCONFIG_TRUSTED_SCHEMA] and [PRAGMA trusted_schema=OFF])
+** disable the use of SQL functions inside views and triggers and in
+** schema structures such as [CHECK constraints], [DEFAULT clauses],
+** [expression indexes], [partial indexes], and [generated columns] unless
+** the function is tagged with SQLITE_INNOCUOUS. Most built-in functions
+** are innocuous. Developers are advised to avoid using the
+** SQLITE_INNOCUOUS flag for application-defined functions unless the
+** function has been carefully audited and found to be free of potentially
+** security-adverse side-effects and information-leaks.
+** </dd>
+**
+** [[SQLITE_SUBTYPE]] <dt>SQLITE_SUBTYPE</dt><dd>
+** The SQLITE_SUBTYPE flag indicates to SQLite that a function may call
+** [tdsqlite3_value_subtype()] to inspect the sub-types of its arguments.
+** Specifying this flag makes no difference for scalar or aggregate user
+** functions. However, if it is not specified for a user-defined window
+** function, then any sub-types belonging to arguments passed to the window
+** function may be discarded before the window function is called (i.e.
+** tdsqlite3_value_subtype() will always return 0).
+** </dd>
+** </dl>
*/
-#define SQLITE_DETERMINISTIC 0x800
+#define SQLITE_DETERMINISTIC 0x000000800
+#define SQLITE_DIRECTONLY 0x000080000
+#define SQLITE_SUBTYPE 0x000100000
+#define SQLITE_INNOCUOUS 0x000200000
/*
** CAPI3REF: Deprecated Functions
@@ -4521,45 +5178,87 @@ SQLITE_API int sqlite3_create_function_v2(
** these functions, we will not explain what they do.
*/
#ifndef SQLITE_OMIT_DEPRECATED
-SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*);
-SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*);
-SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*);
-SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void);
-SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void);
-SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int),
- void*,sqlite3_int64);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_aggregate_count(tdsqlite3_context*);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_expired(tdsqlite3_stmt*);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_transfer_bindings(tdsqlite3_stmt*, tdsqlite3_stmt*);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_global_recover(void);
+SQLITE_API SQLITE_DEPRECATED void tdsqlite3_thread_cleanup(void);
+SQLITE_API SQLITE_DEPRECATED int tdsqlite3_memory_alarm(void(*)(void*,tdsqlite3_int64,int),
+ void*,tdsqlite3_int64);
#endif
/*
** CAPI3REF: Obtaining SQL Values
-** METHOD: sqlite3_value
-**
-** The C-language implementation of SQL functions and aggregates uses
-** this set of interface routines to access the parameter values on
-** the function or aggregate.
-**
-** The xFunc (for scalar functions) or xStep (for aggregates) parameters
-** to [sqlite3_create_function()] and [sqlite3_create_function16()]
-** define callbacks that implement the SQL functions and aggregates.
-** The 3rd parameter to these callbacks is an array of pointers to
-** [protected sqlite3_value] objects. There is one [sqlite3_value] object for
-** each parameter to the SQL function. These routines are used to
-** extract values from the [sqlite3_value] objects.
-**
-** These routines work only with [protected sqlite3_value] objects.
-** Any attempt to use these routines on an [unprotected sqlite3_value]
-** object results in undefined behavior.
+** METHOD: tdsqlite3_value
+**
+** <b>Summary:</b>
+** <blockquote><table border=0 cellpadding=0 cellspacing=0>
+** <tr><td><b>tdsqlite3_value_blob</b><td>&rarr;<td>BLOB value
+** <tr><td><b>tdsqlite3_value_double</b><td>&rarr;<td>REAL value
+** <tr><td><b>tdsqlite3_value_int</b><td>&rarr;<td>32-bit INTEGER value
+** <tr><td><b>tdsqlite3_value_int64</b><td>&rarr;<td>64-bit INTEGER value
+** <tr><td><b>tdsqlite3_value_pointer</b><td>&rarr;<td>Pointer value
+** <tr><td><b>tdsqlite3_value_text</b><td>&rarr;<td>UTF-8 TEXT value
+** <tr><td><b>tdsqlite3_value_text16</b><td>&rarr;<td>UTF-16 TEXT value in
+** the native byteorder
+** <tr><td><b>tdsqlite3_value_text16be</b><td>&rarr;<td>UTF-16be TEXT value
+** <tr><td><b>tdsqlite3_value_text16le</b><td>&rarr;<td>UTF-16le TEXT value
+** <tr><td>&nbsp;<td>&nbsp;<td>&nbsp;
+** <tr><td><b>tdsqlite3_value_bytes</b><td>&rarr;<td>Size of a BLOB
+** or a UTF-8 TEXT in bytes
+** <tr><td><b>tdsqlite3_value_bytes16&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>Size of UTF-16
+** TEXT in bytes
+** <tr><td><b>tdsqlite3_value_type</b><td>&rarr;<td>Default
+** datatype of the value
+** <tr><td><b>tdsqlite3_value_numeric_type&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>Best numeric datatype of the value
+** <tr><td><b>tdsqlite3_value_nochange&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>True if the column is unchanged in an UPDATE
+** against a virtual table.
+** <tr><td><b>tdsqlite3_value_frombind&nbsp;&nbsp;</b>
+** <td>&rarr;&nbsp;&nbsp;<td>True if value originated from a [bound parameter]
+** </table></blockquote>
+**
+** <b>Details:</b>
+**
+** These routines extract type, size, and content information from
+** [protected tdsqlite3_value] objects. Protected tdsqlite3_value objects
+** are used to pass parameter information into the functions that
+** implement [application-defined SQL functions] and [virtual tables].
+**
+** These routines work only with [protected tdsqlite3_value] objects.
+** Any attempt to use these routines on an [unprotected tdsqlite3_value]
+** is not threadsafe.
**
** ^These routines work just like the corresponding [column access functions]
-** except that these routines take a single [protected sqlite3_value] object
-** pointer instead of a [sqlite3_stmt*] pointer and an integer column number.
+** except that these routines take a single [protected tdsqlite3_value] object
+** pointer instead of a [tdsqlite3_stmt*] pointer and an integer column number.
**
-** ^The sqlite3_value_text16() interface extracts a UTF-16 string
+** ^The tdsqlite3_value_text16() interface extracts a UTF-16 string
** in the native byte-order of the host machine. ^The
-** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces
+** tdsqlite3_value_text16be() and tdsqlite3_value_text16le() interfaces
** extract UTF-16 strings as big-endian and little-endian respectively.
**
-** ^(The sqlite3_value_numeric_type() interface attempts to apply
+** ^If [tdsqlite3_value] object V was initialized
+** using [tdsqlite3_bind_pointer(S,I,P,X,D)] or [tdsqlite3_result_pointer(C,P,X,D)]
+** and if X and Y are strings that compare equal according to strcmp(X,Y),
+** then tdsqlite3_value_pointer(V,Y) will return the pointer P. ^Otherwise,
+** tdsqlite3_value_pointer(V,Y) returns a NULL. The tdsqlite3_bind_pointer()
+** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
+**
+** ^(The tdsqlite3_value_type(V) interface returns the
+** [SQLITE_INTEGER | datatype code] for the initial datatype of the
+** [tdsqlite3_value] object V. The returned value is one of [SQLITE_INTEGER],
+** [SQLITE_FLOAT], [SQLITE_TEXT], [SQLITE_BLOB], or [SQLITE_NULL].)^
+** Other interfaces might change the datatype for an tdsqlite3_value object.
+** For example, if the datatype is initially SQLITE_INTEGER and
+** tdsqlite3_value_text(V) is called to extract a text value for that
+** integer, then subsequent calls to tdsqlite3_value_type(V) might return
+** SQLITE_TEXT. Whether or not a persistent internal datatype conversion
+** occurs is undefined and may change from one release of SQLite to the next.
+**
+** ^(The tdsqlite3_value_numeric_type() interface attempts to apply
** numeric affinity to the value. This means that an attempt is
** made to convert the value to an integer or floating point. If
** such a conversion is possible without loss of information (in other
@@ -4567,136 +5266,175 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6
** then the conversion is performed. Otherwise no conversion occurs.
** The [SQLITE_INTEGER | datatype] after conversion is returned.)^
**
+** ^Within the [xUpdate] method of a [virtual table], the
+** tdsqlite3_value_nochange(X) interface returns true if and only if
+** the column corresponding to X is unchanged by the UPDATE operation
+** that the xUpdate method call was invoked to implement and if
+** and the prior [xColumn] method call that was invoked to extracted
+** the value for that column returned without setting a result (probably
+** because it queried [tdsqlite3_vtab_nochange()] and found that the column
+** was unchanging). ^Within an [xUpdate] method, any value for which
+** tdsqlite3_value_nochange(X) is true will in all other respects appear
+** to be a NULL value. If tdsqlite3_value_nochange(X) is invoked anywhere other
+** than within an [xUpdate] method call for an UPDATE statement, then
+** the return value is arbitrary and meaningless.
+**
+** ^The tdsqlite3_value_frombind(X) interface returns non-zero if the
+** value X originated from one of the [tdsqlite3_bind_int|tdsqlite3_bind()]
+** interfaces. ^If X comes from an SQL literal value, or a table column,
+** or an expression, then tdsqlite3_value_frombind(X) returns zero.
+**
** Please pay particular attention to the fact that the pointer returned
-** from [sqlite3_value_blob()], [sqlite3_value_text()], or
-** [sqlite3_value_text16()] can be invalidated by a subsequent call to
-** [sqlite3_value_bytes()], [sqlite3_value_bytes16()], [sqlite3_value_text()],
-** or [sqlite3_value_text16()].
+** from [tdsqlite3_value_blob()], [tdsqlite3_value_text()], or
+** [tdsqlite3_value_text16()] can be invalidated by a subsequent call to
+** [tdsqlite3_value_bytes()], [tdsqlite3_value_bytes16()], [tdsqlite3_value_text()],
+** or [tdsqlite3_value_text16()].
**
** These routines must be called from the same thread as
-** the SQL function that supplied the [sqlite3_value*] parameters.
-*/
-SQLITE_API const void *sqlite3_value_blob(sqlite3_value*);
-SQLITE_API int sqlite3_value_bytes(sqlite3_value*);
-SQLITE_API int sqlite3_value_bytes16(sqlite3_value*);
-SQLITE_API double sqlite3_value_double(sqlite3_value*);
-SQLITE_API int sqlite3_value_int(sqlite3_value*);
-SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*);
-SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*);
-SQLITE_API const void *sqlite3_value_text16(sqlite3_value*);
-SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*);
-SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*);
-SQLITE_API int sqlite3_value_type(sqlite3_value*);
-SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*);
+** the SQL function that supplied the [tdsqlite3_value*] parameters.
+**
+** As long as the input parameter is correct, these routines can only
+** fail if an out-of-memory error occurs during a format conversion.
+** Only the following subset of interfaces are subject to out-of-memory
+** errors:
+**
+** <ul>
+** <li> tdsqlite3_value_blob()
+** <li> tdsqlite3_value_text()
+** <li> tdsqlite3_value_text16()
+** <li> tdsqlite3_value_text16le()
+** <li> tdsqlite3_value_text16be()
+** <li> tdsqlite3_value_bytes()
+** <li> tdsqlite3_value_bytes16()
+** </ul>
+**
+** If an out-of-memory error occurs, then the return value from these
+** routines is the same as if the column had contained an SQL NULL value.
+** Valid SQL NULL returns can be distinguished from out-of-memory errors
+** by invoking the [tdsqlite3_errcode()] immediately after the suspect
+** return value is obtained and before any
+** other SQLite interface is called on the same [database connection].
+*/
+SQLITE_API const void *tdsqlite3_value_blob(tdsqlite3_value*);
+SQLITE_API double tdsqlite3_value_double(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_int(tdsqlite3_value*);
+SQLITE_API tdsqlite3_int64 tdsqlite3_value_int64(tdsqlite3_value*);
+SQLITE_API void *tdsqlite3_value_pointer(tdsqlite3_value*, const char*);
+SQLITE_API const unsigned char *tdsqlite3_value_text(tdsqlite3_value*);
+SQLITE_API const void *tdsqlite3_value_text16(tdsqlite3_value*);
+SQLITE_API const void *tdsqlite3_value_text16le(tdsqlite3_value*);
+SQLITE_API const void *tdsqlite3_value_text16be(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_bytes(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_bytes16(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_type(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_numeric_type(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_nochange(tdsqlite3_value*);
+SQLITE_API int tdsqlite3_value_frombind(tdsqlite3_value*);
/*
** CAPI3REF: Finding The Subtype Of SQL Values
-** METHOD: sqlite3_value
+** METHOD: tdsqlite3_value
**
-** The sqlite3_value_subtype(V) function returns the subtype for
+** The tdsqlite3_value_subtype(V) function returns the subtype for
** an [application-defined SQL function] argument V. The subtype
** information can be used to pass a limited amount of context from
-** one SQL function to another. Use the [sqlite3_result_subtype()]
+** one SQL function to another. Use the [tdsqlite3_result_subtype()]
** routine to set the subtype for the return value of an SQL function.
-**
-** SQLite makes no use of subtype itself. It merely passes the subtype
-** from the result of one [application-defined SQL function] into the
-** input of another.
*/
-SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
+SQLITE_API unsigned int tdsqlite3_value_subtype(tdsqlite3_value*);
/*
** CAPI3REF: Copy And Free SQL Values
-** METHOD: sqlite3_value
+** METHOD: tdsqlite3_value
**
-** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
-** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
-** is a [protected sqlite3_value] object even if the input is not.
-** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
+** ^The tdsqlite3_value_dup(V) interface makes a copy of the [tdsqlite3_value]
+** object D and returns a pointer to that copy. ^The [tdsqlite3_value] returned
+** is a [protected tdsqlite3_value] object even if the input is not.
+** ^The tdsqlite3_value_dup(V) interface returns NULL if V is NULL or if a
** memory allocation fails.
**
-** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object
-** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer
-** then sqlite3_value_free(V) is a harmless no-op.
+** ^The tdsqlite3_value_free(V) interface frees an [tdsqlite3_value] object
+** previously obtained from [tdsqlite3_value_dup()]. ^If V is a NULL pointer
+** then tdsqlite3_value_free(V) is a harmless no-op.
*/
-SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value*);
-SQLITE_API void sqlite3_value_free(sqlite3_value*);
+SQLITE_API tdsqlite3_value *tdsqlite3_value_dup(const tdsqlite3_value*);
+SQLITE_API void tdsqlite3_value_free(tdsqlite3_value*);
/*
** CAPI3REF: Obtain Aggregate Function Context
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
** Implementations of aggregate SQL functions use this
** routine to allocate memory for storing their state.
**
-** ^The first time the sqlite3_aggregate_context(C,N) routine is called
-** for a particular aggregate function, SQLite
-** allocates N of memory, zeroes out that memory, and returns a pointer
+** ^The first time the tdsqlite3_aggregate_context(C,N) routine is called
+** for a particular aggregate function, SQLite allocates
+** N bytes of memory, zeroes out that memory, and returns a pointer
** to the new memory. ^On second and subsequent calls to
-** sqlite3_aggregate_context() for the same aggregate function instance,
+** tdsqlite3_aggregate_context() for the same aggregate function instance,
** the same buffer is returned. Sqlite3_aggregate_context() is normally
** called once for each invocation of the xStep callback and then one
** last time when the xFinal callback is invoked. ^(When no rows match
** an aggregate query, the xStep() callback of the aggregate function
** implementation is never called and xFinal() is called exactly once.
-** In those cases, sqlite3_aggregate_context() might be called for the
+** In those cases, tdsqlite3_aggregate_context() might be called for the
** first time from within xFinal().)^
**
-** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer
+** ^The tdsqlite3_aggregate_context(C,N) routine returns a NULL pointer
** when first called if N is less than or equal to zero or if a memory
** allocate error occurs.
**
-** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
+** ^(The amount of space allocated by tdsqlite3_aggregate_context(C,N) is
** determined by the N parameter on first successful call. Changing the
-** value of N in subsequent call to sqlite3_aggregate_context() within
+** value of N in any subsequents call to tdsqlite3_aggregate_context() within
** the same aggregate function instance will not resize the memory
** allocation.)^ Within the xFinal callback, it is customary to set
-** N=0 in calls to sqlite3_aggregate_context(C,N) so that no
+** N=0 in calls to tdsqlite3_aggregate_context(C,N) so that no
** pointless memory allocations occur.
**
** ^SQLite automatically frees the memory allocated by
-** sqlite3_aggregate_context() when the aggregate query concludes.
+** tdsqlite3_aggregate_context() when the aggregate query concludes.
**
** The first parameter must be a copy of the
-** [sqlite3_context | SQL function context] that is the first parameter
+** [tdsqlite3_context | SQL function context] that is the first parameter
** to the xStep or xFinal callback routine that implements the aggregate
** function.
**
** This routine must be called from the same thread in which
** the aggregate SQL function is running.
*/
-SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes);
+SQLITE_API void *tdsqlite3_aggregate_context(tdsqlite3_context*, int nBytes);
/*
** CAPI3REF: User Data For Functions
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
-** ^The sqlite3_user_data() interface returns a copy of
+** ^The tdsqlite3_user_data() interface returns a copy of
** the pointer that was the pUserData parameter (the 5th parameter)
-** of the [sqlite3_create_function()]
-** and [sqlite3_create_function16()] routines that originally
+** of the [tdsqlite3_create_function()]
+** and [tdsqlite3_create_function16()] routines that originally
** registered the application defined function.
**
** This routine must be called from the same thread in which
** the application-defined function is running.
*/
-SQLITE_API void *sqlite3_user_data(sqlite3_context*);
+SQLITE_API void *tdsqlite3_user_data(tdsqlite3_context*);
/*
** CAPI3REF: Database Connection For Functions
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
-** ^The sqlite3_context_db_handle() interface returns a copy of
+** ^The tdsqlite3_context_db_handle() interface returns a copy of
** the pointer to the [database connection] (the 1st parameter)
-** of the [sqlite3_create_function()]
-** and [sqlite3_create_function16()] routines that originally
+** of the [tdsqlite3_create_function()]
+** and [tdsqlite3_create_function16()] routines that originally
** registered the application defined function.
*/
-SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
+SQLITE_API tdsqlite3 *tdsqlite3_context_db_handle(tdsqlite3_context*);
/*
** CAPI3REF: Function Auxiliary Data
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
** These functions may be used by (non-aggregate) SQL functions to
** associate metadata with argument values. If the same value is passed to
@@ -4709,52 +5447,57 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*);
** the compiled regular expression can be reused on multiple
** invocations of the same function.
**
-** ^The sqlite3_get_auxdata() interface returns a pointer to the metadata
-** associated by the sqlite3_set_auxdata() function with the Nth argument
-** value to the application-defined function. ^If there is no metadata
-** associated with the function argument, this sqlite3_get_auxdata() interface
+** ^The tdsqlite3_get_auxdata(C,N) interface returns a pointer to the metadata
+** associated by the tdsqlite3_set_auxdata(C,N,P,X) function with the Nth argument
+** value to the application-defined function. ^N is zero for the left-most
+** function argument. ^If there is no metadata
+** associated with the function argument, the tdsqlite3_get_auxdata(C,N) interface
** returns a NULL pointer.
**
-** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th
+** ^The tdsqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th
** argument of the application-defined function. ^Subsequent
-** calls to sqlite3_get_auxdata(C,N) return P from the most recent
-** sqlite3_set_auxdata(C,N,P,X) call if the metadata is still valid or
+** calls to tdsqlite3_get_auxdata(C,N) return P from the most recent
+** tdsqlite3_set_auxdata(C,N,P,X) call if the metadata is still valid or
** NULL if the metadata has been discarded.
-** ^After each call to sqlite3_set_auxdata(C,N,P,X) where X is not NULL,
+** ^After each call to tdsqlite3_set_auxdata(C,N,P,X) where X is not NULL,
** SQLite will invoke the destructor function X with parameter P exactly
** once, when the metadata is discarded.
** SQLite is free to discard the metadata at any time, including: <ul>
** <li> ^(when the corresponding function parameter changes)^, or
-** <li> ^(when [sqlite3_reset()] or [sqlite3_finalize()] is called for the
+** <li> ^(when [tdsqlite3_reset()] or [tdsqlite3_finalize()] is called for the
** SQL statement)^, or
-** <li> ^(when sqlite3_set_auxdata() is invoked again on the same
+** <li> ^(when tdsqlite3_set_auxdata() is invoked again on the same
** parameter)^, or
-** <li> ^(during the original sqlite3_set_auxdata() call when a memory
+** <li> ^(during the original tdsqlite3_set_auxdata() call when a memory
** allocation error occurs.)^ </ul>
**
** Note the last bullet in particular. The destructor X in
-** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the
-** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata()
+** tdsqlite3_set_auxdata(C,N,P,X) might be called immediately, before the
+** tdsqlite3_set_auxdata() interface even returns. Hence tdsqlite3_set_auxdata()
** should be called near the end of the function implementation and the
** function implementation should not make any use of P after
-** sqlite3_set_auxdata() has been called.
+** tdsqlite3_set_auxdata() has been called.
**
** ^(In practice, metadata is preserved between function calls for
** function parameters that are compile-time constants, including literal
** values and [parameters] and expressions composed from the same.)^
**
+** The value of the N parameter to these interfaces should be non-negative.
+** Future enhancements may make use of negative N values to define new
+** kinds of function caching behavior.
+**
** These routines must be called from the same thread in which
** the SQL function is running.
*/
-SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N);
-SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*));
+SQLITE_API void *tdsqlite3_get_auxdata(tdsqlite3_context*, int N);
+SQLITE_API void tdsqlite3_set_auxdata(tdsqlite3_context*, int N, void*, void (*)(void*));
/*
** CAPI3REF: Constants Defining Special Destructor Behavior
**
** These are special values for the destructor that is passed in as the
-** final argument to routines like [sqlite3_result_blob()]. ^If the destructor
+** final argument to routines like [tdsqlite3_result_blob()]. ^If the destructor
** argument is SQLITE_STATIC, it means that the content pointer is constant
** and will never change. It does not need to be destroyed. ^The
** SQLITE_TRANSIENT value means that the content will likely change in
@@ -4764,89 +5507,89 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
** The typedef is necessary to work around problems in certain
** C++ compilers.
*/
-typedef void (*sqlite3_destructor_type)(void*);
-#define SQLITE_STATIC ((sqlite3_destructor_type)0)
-#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
+typedef void (*tdsqlite3_destructor_type)(void*);
+#define SQLITE_STATIC ((tdsqlite3_destructor_type)0)
+#define SQLITE_TRANSIENT ((tdsqlite3_destructor_type)-1)
/*
** CAPI3REF: Setting The Result Of An SQL Function
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
** These routines are used by the xFunc or xFinal callbacks that
** implement SQL functions and aggregates. See
-** [sqlite3_create_function()] and [sqlite3_create_function16()]
+** [tdsqlite3_create_function()] and [tdsqlite3_create_function16()]
** for additional information.
**
** These functions work very much like the [parameter binding] family of
** functions used to bind values to host parameters in prepared statements.
** Refer to the [SQL parameter] documentation for additional information.
**
-** ^The sqlite3_result_blob() interface sets the result from
+** ^The tdsqlite3_result_blob() interface sets the result from
** an application-defined function to be the BLOB whose content is pointed
** to by the second parameter and which is N bytes long where N is the
** third parameter.
**
-** ^The sqlite3_result_zeroblob(C,N) and sqlite3_result_zeroblob64(C,N)
+** ^The tdsqlite3_result_zeroblob(C,N) and tdsqlite3_result_zeroblob64(C,N)
** interfaces set the result of the application-defined function to be
** a BLOB containing all zero bytes and N bytes in size.
**
-** ^The sqlite3_result_double() interface sets the result from
+** ^The tdsqlite3_result_double() interface sets the result from
** an application-defined function to be a floating point value specified
** by its 2nd argument.
**
-** ^The sqlite3_result_error() and sqlite3_result_error16() functions
+** ^The tdsqlite3_result_error() and tdsqlite3_result_error16() functions
** cause the implemented SQL function to throw an exception.
** ^SQLite uses the string pointed to by the
-** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16()
+** 2nd parameter of tdsqlite3_result_error() or tdsqlite3_result_error16()
** as the text of an error message. ^SQLite interprets the error
-** message string from sqlite3_result_error() as UTF-8. ^SQLite
-** interprets the string from sqlite3_result_error16() as UTF-16 in native
-** byte order. ^If the third parameter to sqlite3_result_error()
-** or sqlite3_result_error16() is negative then SQLite takes as the error
+** message string from tdsqlite3_result_error() as UTF-8. ^SQLite
+** interprets the string from tdsqlite3_result_error16() as UTF-16 in native
+** byte order. ^If the third parameter to tdsqlite3_result_error()
+** or tdsqlite3_result_error16() is negative then SQLite takes as the error
** message all text up through the first zero character.
-** ^If the third parameter to sqlite3_result_error() or
-** sqlite3_result_error16() is non-negative then SQLite takes that many
+** ^If the third parameter to tdsqlite3_result_error() or
+** tdsqlite3_result_error16() is non-negative then SQLite takes that many
** bytes (not characters) from the 2nd parameter as the error message.
-** ^The sqlite3_result_error() and sqlite3_result_error16()
+** ^The tdsqlite3_result_error() and tdsqlite3_result_error16()
** routines make a private copy of the error message text before
** they return. Hence, the calling function can deallocate or
** modify the text after they return without harm.
-** ^The sqlite3_result_error_code() function changes the error code
+** ^The tdsqlite3_result_error_code() function changes the error code
** returned by SQLite as a result of an error in a function. ^By default,
-** the error code is SQLITE_ERROR. ^A subsequent call to sqlite3_result_error()
-** or sqlite3_result_error16() resets the error code to SQLITE_ERROR.
+** the error code is SQLITE_ERROR. ^A subsequent call to tdsqlite3_result_error()
+** or tdsqlite3_result_error16() resets the error code to SQLITE_ERROR.
**
-** ^The sqlite3_result_error_toobig() interface causes SQLite to throw an
+** ^The tdsqlite3_result_error_toobig() interface causes SQLite to throw an
** error indicating that a string or BLOB is too long to represent.
**
-** ^The sqlite3_result_error_nomem() interface causes SQLite to throw an
+** ^The tdsqlite3_result_error_nomem() interface causes SQLite to throw an
** error indicating that a memory allocation failed.
**
-** ^The sqlite3_result_int() interface sets the return value
+** ^The tdsqlite3_result_int() interface sets the return value
** of the application-defined function to be the 32-bit signed integer
** value given in the 2nd argument.
-** ^The sqlite3_result_int64() interface sets the return value
+** ^The tdsqlite3_result_int64() interface sets the return value
** of the application-defined function to be the 64-bit signed integer
** value given in the 2nd argument.
**
-** ^The sqlite3_result_null() interface sets the return value
+** ^The tdsqlite3_result_null() interface sets the return value
** of the application-defined function to be NULL.
**
-** ^The sqlite3_result_text(), sqlite3_result_text16(),
-** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces
+** ^The tdsqlite3_result_text(), tdsqlite3_result_text16(),
+** tdsqlite3_result_text16le(), and tdsqlite3_result_text16be() interfaces
** set the return value of the application-defined function to be
** a text string which is represented as UTF-8, UTF-16 native byte order,
** UTF-16 little endian, or UTF-16 big endian, respectively.
-** ^The sqlite3_result_text64() interface sets the return value of an
+** ^The tdsqlite3_result_text64() interface sets the return value of an
** application-defined function to be a text string in an encoding
** specified by the fifth (and last) parameter, which must be one
** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE].
** ^SQLite takes the text result from the application from
-** the 2nd parameter of the sqlite3_result_text* interfaces.
-** ^If the 3rd parameter to the sqlite3_result_text* interfaces
+** the 2nd parameter of the tdsqlite3_result_text* interfaces.
+** ^If the 3rd parameter to the tdsqlite3_result_text* interfaces
** is negative, then SQLite takes result text from the 2nd parameter
** through the first zero character.
-** ^If the 3rd parameter to the sqlite3_result_text* interfaces
+** ^If the 3rd parameter to the tdsqlite3_result_text* interfaces
** is non-negative, then as many bytes (not characters) of the text
** pointed to by the 2nd parameter are taken as the application-defined
** function result. If the 3rd parameter is non-negative, then it
@@ -4855,82 +5598,94 @@ typedef void (*sqlite3_destructor_type)(void*);
** in the string at a byte offset that is less than the value of the 3rd
** parameter, then the resulting string will contain embedded NULs and the
** result of expressions operating on strings with embedded NULs is undefined.
-** ^If the 4th parameter to the sqlite3_result_text* interfaces
-** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that
+** ^If the 4th parameter to the tdsqlite3_result_text* interfaces
+** or tdsqlite3_result_blob is a non-NULL pointer, then SQLite calls that
** function as the destructor on the text or BLOB result when it has
** finished using that result.
-** ^If the 4th parameter to the sqlite3_result_text* interfaces or to
-** sqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite
+** ^If the 4th parameter to the tdsqlite3_result_text* interfaces or to
+** tdsqlite3_result_blob is the special constant SQLITE_STATIC, then SQLite
** assumes that the text or BLOB result is in constant space and does not
** copy the content of the parameter nor call a destructor on the content
** when it has finished using that result.
-** ^If the 4th parameter to the sqlite3_result_text* interfaces
-** or sqlite3_result_blob is the special constant SQLITE_TRANSIENT
-** then SQLite makes a copy of the result into space obtained from
-** from [sqlite3_malloc()] before it returns.
+** ^If the 4th parameter to the tdsqlite3_result_text* interfaces
+** or tdsqlite3_result_blob is the special constant SQLITE_TRANSIENT
+** then SQLite makes a copy of the result into space obtained
+** from [tdsqlite3_malloc()] before it returns.
**
-** ^The sqlite3_result_value() interface sets the result of
+** ^The tdsqlite3_result_value() interface sets the result of
** the application-defined function to be a copy of the
-** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The
-** sqlite3_result_value() interface makes a copy of the [sqlite3_value]
-** so that the [sqlite3_value] specified in the parameter may change or
-** be deallocated after sqlite3_result_value() returns without harm.
-** ^A [protected sqlite3_value] object may always be used where an
-** [unprotected sqlite3_value] object is required, so either
-** kind of [sqlite3_value] object can be used with this interface.
+** [unprotected tdsqlite3_value] object specified by the 2nd parameter. ^The
+** tdsqlite3_result_value() interface makes a copy of the [tdsqlite3_value]
+** so that the [tdsqlite3_value] specified in the parameter may change or
+** be deallocated after tdsqlite3_result_value() returns without harm.
+** ^A [protected tdsqlite3_value] object may always be used where an
+** [unprotected tdsqlite3_value] object is required, so either
+** kind of [tdsqlite3_value] object can be used with this interface.
+**
+** ^The tdsqlite3_result_pointer(C,P,T,D) interface sets the result to an
+** SQL NULL value, just like [tdsqlite3_result_null(C)], except that it
+** also associates the host-language pointer P or type T with that
+** NULL value such that the pointer can be retrieved within an
+** [application-defined SQL function] using [tdsqlite3_value_pointer()].
+** ^If the D parameter is not NULL, then it is a pointer to a destructor
+** for the P parameter. ^SQLite invokes D with P as its only argument
+** when SQLite is finished with P. The T parameter should be a static
+** string and preferably a string literal. The tdsqlite3_result_pointer()
+** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
**
** If these routines are called from within the different thread
** than the one containing the application-defined function that received
-** the [sqlite3_context] pointer, the results are undefined.
-*/
-SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*));
-SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*,
- sqlite3_uint64,void(*)(void*));
-SQLITE_API void sqlite3_result_double(sqlite3_context*, double);
-SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int);
-SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int);
-SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*);
-SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*);
-SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int);
-SQLITE_API void sqlite3_result_int(sqlite3_context*, int);
-SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64);
-SQLITE_API void sqlite3_result_null(sqlite3_context*);
-SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*));
-SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64,
+** the [tdsqlite3_context] pointer, the results are undefined.
+*/
+SQLITE_API void tdsqlite3_result_blob(tdsqlite3_context*, const void*, int, void(*)(void*));
+SQLITE_API void tdsqlite3_result_blob64(tdsqlite3_context*,const void*,
+ tdsqlite3_uint64,void(*)(void*));
+SQLITE_API void tdsqlite3_result_double(tdsqlite3_context*, double);
+SQLITE_API void tdsqlite3_result_error(tdsqlite3_context*, const char*, int);
+SQLITE_API void tdsqlite3_result_error16(tdsqlite3_context*, const void*, int);
+SQLITE_API void tdsqlite3_result_error_toobig(tdsqlite3_context*);
+SQLITE_API void tdsqlite3_result_error_nomem(tdsqlite3_context*);
+SQLITE_API void tdsqlite3_result_error_code(tdsqlite3_context*, int);
+SQLITE_API void tdsqlite3_result_int(tdsqlite3_context*, int);
+SQLITE_API void tdsqlite3_result_int64(tdsqlite3_context*, tdsqlite3_int64);
+SQLITE_API void tdsqlite3_result_null(tdsqlite3_context*);
+SQLITE_API void tdsqlite3_result_text(tdsqlite3_context*, const char*, int, void(*)(void*));
+SQLITE_API void tdsqlite3_result_text64(tdsqlite3_context*, const char*,tdsqlite3_uint64,
void(*)(void*), unsigned char encoding);
-SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*));
-SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*));
-SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*));
-SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*);
-SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n);
-SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n);
+SQLITE_API void tdsqlite3_result_text16(tdsqlite3_context*, const void*, int, void(*)(void*));
+SQLITE_API void tdsqlite3_result_text16le(tdsqlite3_context*, const void*, int,void(*)(void*));
+SQLITE_API void tdsqlite3_result_text16be(tdsqlite3_context*, const void*, int,void(*)(void*));
+SQLITE_API void tdsqlite3_result_value(tdsqlite3_context*, tdsqlite3_value*);
+SQLITE_API void tdsqlite3_result_pointer(tdsqlite3_context*, void*,const char*,void(*)(void*));
+SQLITE_API void tdsqlite3_result_zeroblob(tdsqlite3_context*, int n);
+SQLITE_API int tdsqlite3_result_zeroblob64(tdsqlite3_context*, tdsqlite3_uint64 n);
/*
** CAPI3REF: Setting The Subtype Of An SQL Function
-** METHOD: sqlite3_context
+** METHOD: tdsqlite3_context
**
-** The sqlite3_result_subtype(C,T) function causes the subtype of
+** The tdsqlite3_result_subtype(C,T) function causes the subtype of
** the result from the [application-defined SQL function] with
-** [sqlite3_context] C to be the value T. Only the lower 8 bits
+** [tdsqlite3_context] C to be the value T. Only the lower 8 bits
** of the subtype T are preserved in current versions of SQLite;
** higher order bits are discarded.
** The number of subtype bytes preserved by SQLite might increase
** in future releases of SQLite.
*/
-SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
+SQLITE_API void tdsqlite3_result_subtype(tdsqlite3_context*,unsigned int);
/*
** CAPI3REF: Define New Collating Sequences
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^These functions add, remove, or modify a [collation] associated
** with the [database connection] specified as the first argument.
**
** ^The name of the collation is a UTF-8 string
-** for sqlite3_create_collation() and sqlite3_create_collation_v2()
-** and a UTF-16 string in native byte order for sqlite3_create_collation16().
-** ^Collation names that compare equal according to [sqlite3_strnicmp()] are
+** for tdsqlite3_create_collation() and tdsqlite3_create_collation_v2()
+** and a UTF-16 string in native byte order for tdsqlite3_create_collation16().
+** ^Collation names that compare equal according to [tdsqlite3_strnicmp()] are
** considered to be the same name.
**
** ^(The third argument (eTextRep) must be one of the constants:
@@ -4942,7 +5697,7 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
** <li> [SQLITE_UTF16_ALIGNED].
** </ul>)^
** ^The eTextRep argument determines the encoding of strings passed
-** to the collating function callback, xCallback.
+** to the collating function callback, xCompare.
** ^The [SQLITE_UTF16] and [SQLITE_UTF16_ALIGNED] values for eTextRep
** force strings to be UTF16 with native byte order.
** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin
@@ -4951,18 +5706,19 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
** ^The fourth argument, pArg, is an application data pointer that is passed
** through as the first argument to the collating function callback.
**
-** ^The fifth argument, xCallback, is a pointer to the collating function.
+** ^The fifth argument, xCompare, is a pointer to the collating function.
** ^Multiple collating functions can be registered using the same name but
** with different eTextRep parameters and SQLite will use whichever
** function requires the least amount of data transformation.
-** ^If the xCallback argument is NULL then the collating function is
+** ^If the xCompare argument is NULL then the collating function is
** deleted. ^When all collating functions having the same name are deleted,
** that collation is no longer usable.
**
** ^The collating function callback is invoked with a copy of the pArg
** application data pointer and with two strings in the encoding specified
-** by the eTextRep argument. The collating function must return an
-** integer that is negative, zero, or positive
+** by the eTextRep argument. The two integer parameters to the collating
+** function callback are the length of the two strings, in bytes. The collating
+** function must return an integer that is negative, zero, or positive
** if the first string is less than, equal to, or greater than the second,
** respectively. A collating function must always return the same answer
** given the same inputs. If two or more collating functions are registered
@@ -4979,44 +5735,44 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int);
** </ol>
**
** If a collating function fails any of the above constraints and that
-** collating function is registered and used, then the behavior of SQLite
+** collating function is registered and used, then the behavior of SQLite
** is undefined.
**
-** ^The sqlite3_create_collation_v2() works like sqlite3_create_collation()
+** ^The tdsqlite3_create_collation_v2() works like tdsqlite3_create_collation()
** with the addition that the xDestroy callback is invoked on pArg when
** the collating function is deleted.
** ^Collating functions are deleted when they are overridden by later
** calls to the collation creation functions or when the
-** [database connection] is closed using [sqlite3_close()].
+** [database connection] is closed using [tdsqlite3_close()].
**
** ^The xDestroy callback is <u>not</u> called if the
-** sqlite3_create_collation_v2() function fails. Applications that invoke
-** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should
+** tdsqlite3_create_collation_v2() function fails. Applications that invoke
+** tdsqlite3_create_collation_v2() with a non-NULL xDestroy argument should
** check the return code and dispose of the application data pointer
** themselves rather than expecting SQLite to deal with it for them.
** This is different from every other SQLite interface. The inconsistency
** is unfortunate but cannot be changed without breaking backwards
** compatibility.
**
-** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()].
+** See also: [tdsqlite3_collation_needed()] and [tdsqlite3_collation_needed16()].
*/
-SQLITE_API int sqlite3_create_collation(
- sqlite3*,
+SQLITE_API int tdsqlite3_create_collation(
+ tdsqlite3*,
const char *zName,
int eTextRep,
void *pArg,
int(*xCompare)(void*,int,const void*,int,const void*)
);
-SQLITE_API int sqlite3_create_collation_v2(
- sqlite3*,
+SQLITE_API int tdsqlite3_create_collation_v2(
+ tdsqlite3*,
const char *zName,
int eTextRep,
void *pArg,
int(*xCompare)(void*,int,const void*,int,const void*),
void(*xDestroy)(void*)
);
-SQLITE_API int sqlite3_create_collation16(
- sqlite3*,
+SQLITE_API int tdsqlite3_create_collation16(
+ tdsqlite3*,
const void *zName,
int eTextRep,
void *pArg,
@@ -5025,56 +5781,56 @@ SQLITE_API int sqlite3_create_collation16(
/*
** CAPI3REF: Collation Needed Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^To avoid having to register all collation sequences before a database
** can be used, a single callback function may be registered with the
** [database connection] to be invoked whenever an undefined collation
** sequence is required.
**
-** ^If the function is registered using the sqlite3_collation_needed() API,
+** ^If the function is registered using the tdsqlite3_collation_needed() API,
** then it is passed the names of undefined collation sequences as strings
-** encoded in UTF-8. ^If sqlite3_collation_needed16() is used,
+** encoded in UTF-8. ^If tdsqlite3_collation_needed16() is used,
** the names are passed as UTF-16 in machine native byte order.
** ^A call to either function replaces the existing collation-needed callback.
**
** ^(When the callback is invoked, the first argument passed is a copy
-** of the second argument to sqlite3_collation_needed() or
-** sqlite3_collation_needed16(). The second argument is the database
+** of the second argument to tdsqlite3_collation_needed() or
+** tdsqlite3_collation_needed16(). The second argument is the database
** connection. The third argument is one of [SQLITE_UTF8], [SQLITE_UTF16BE],
** or [SQLITE_UTF16LE], indicating the most desirable form of the collation
** sequence function required. The fourth parameter is the name of the
** required collation sequence.)^
**
** The callback function should register the desired collation using
-** [sqlite3_create_collation()], [sqlite3_create_collation16()], or
-** [sqlite3_create_collation_v2()].
+** [tdsqlite3_create_collation()], [tdsqlite3_create_collation16()], or
+** [tdsqlite3_create_collation_v2()].
*/
-SQLITE_API int sqlite3_collation_needed(
- sqlite3*,
+SQLITE_API int tdsqlite3_collation_needed(
+ tdsqlite3*,
void*,
- void(*)(void*,sqlite3*,int eTextRep,const char*)
+ void(*)(void*,tdsqlite3*,int eTextRep,const char*)
);
-SQLITE_API int sqlite3_collation_needed16(
- sqlite3*,
+SQLITE_API int tdsqlite3_collation_needed16(
+ tdsqlite3*,
void*,
- void(*)(void*,sqlite3*,int eTextRep,const void*)
+ void(*)(void*,tdsqlite3*,int eTextRep,const void*)
);
#ifdef SQLITE_HAS_CODEC
/*
** Specify the key for an encrypted database. This routine should be
-** called right after sqlite3_open().
+** called right after tdsqlite3_open().
**
** The code to implement this API is not available in the public release
** of SQLite.
*/
-SQLITE_API int sqlite3_key(
- sqlite3 *db, /* Database to be rekeyed */
+SQLITE_API int tdsqlite3_key(
+ tdsqlite3 *db, /* Database to be rekeyed */
const void *pKey, int nKey /* The key */
);
-SQLITE_API int sqlite3_key_v2(
- sqlite3 *db, /* Database to be rekeyed */
+SQLITE_API int tdsqlite3_key_v2(
+ tdsqlite3 *db, /* Database to be rekeyed */
const char *zDbName, /* Name of the database */
const void *pKey, int nKey /* The key */
);
@@ -5087,12 +5843,28 @@ SQLITE_API int sqlite3_key_v2(
** The code to implement this API is not available in the public release
** of SQLite.
*/
-SQLITE_API int sqlite3_rekey(
- sqlite3 *db, /* Database to be rekeyed */
+/* BEGIN SQLCIPHER
+ SQLCipher usage note:
+
+ If the current database is plaintext SQLCipher will NOT encrypt it.
+ If the current database is encrypted and pNew==0 or nNew==0, SQLCipher
+ will NOT decrypt it.
+
+ This routine will ONLY work on an already encrypted database in order
+ to change the key.
+
+ Conversion from plaintext-to-encrypted or encrypted-to-plaintext should
+ use an ATTACHed database and the sqlcipher_export() convenience function
+ as per the SQLCipher Documentation.
+
+ END SQLCIPHER
+*/
+SQLITE_API int tdsqlite3_rekey(
+ tdsqlite3 *db, /* Database to be rekeyed */
const void *pKey, int nKey /* The new key */
);
-SQLITE_API int sqlite3_rekey_v2(
- sqlite3 *db, /* Database to be rekeyed */
+SQLITE_API int tdsqlite3_rekey_v2(
+ tdsqlite3 *db, /* Database to be rekeyed */
const char *zDbName, /* Name of the database */
const void *pKey, int nKey /* The new key */
);
@@ -5101,7 +5873,7 @@ SQLITE_API int sqlite3_rekey_v2(
** Specify the activation key for a SEE database. Unless
** activated, none of the SEE routines will work.
*/
-SQLITE_API void sqlite3_activate_see(
+SQLITE_API void tdsqlite3_activate_see(
const char *zPassPhrase /* Activation phrase */
);
#endif
@@ -5111,7 +5883,7 @@ SQLITE_API void sqlite3_activate_see(
** Specify the activation key for a CEROD database. Unless
** activated, none of the CEROD routines will work.
*/
-SQLITE_API void sqlite3_activate_cerod(
+SQLITE_API void tdsqlite3_activate_cerod(
const char *zPassPhrase /* Activation phrase */
);
#endif
@@ -5119,7 +5891,7 @@ SQLITE_API void sqlite3_activate_cerod(
/*
** CAPI3REF: Suspend Execution For A Short Time
**
-** The sqlite3_sleep() function causes the current thread to suspend execution
+** The tdsqlite3_sleep() function causes the current thread to suspend execution
** for at least a number of milliseconds specified in its parameter.
**
** If the operating system does not support sleep requests with
@@ -5128,19 +5900,19 @@ SQLITE_API void sqlite3_activate_cerod(
** requested from the operating system is returned.
**
** ^SQLite implements this interface by calling the xSleep()
-** method of the default [sqlite3_vfs] object. If the xSleep() method
+** method of the default [tdsqlite3_vfs] object. If the xSleep() method
** of the default VFS is not implemented correctly, or not implemented at
-** all, then the behavior of sqlite3_sleep() may deviate from the description
+** all, then the behavior of tdsqlite3_sleep() may deviate from the description
** in the previous paragraphs.
*/
-SQLITE_API int sqlite3_sleep(int);
+SQLITE_API int tdsqlite3_sleep(int);
/*
** CAPI3REF: Name Of The Folder Holding Temporary Files
**
** ^(If this global variable is made to point to a string which is
** the name of a folder (a.k.a. directory), then all temporary files
-** created by SQLite when using a built-in [sqlite3_vfs | VFS]
+** created by SQLite when using a built-in [tdsqlite3_vfs | VFS]
** will be placed in that directory.)^ ^If this variable
** is a NULL pointer, then SQLite performs a search for an appropriate
** temporary file directory.
@@ -5162,22 +5934,22 @@ SQLITE_API int sqlite3_sleep(int);
** thereafter.
**
** ^The [temp_store_directory pragma] may modify this variable and cause
-** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore,
+** it to point to memory obtained from [tdsqlite3_malloc]. ^Furthermore,
** the [temp_store_directory pragma] always assumes that any string
** that this variable points to is held in memory obtained from
-** [sqlite3_malloc] and the pragma may attempt to free that memory
-** using [sqlite3_free].
+** [tdsqlite3_malloc] and the pragma may attempt to free that memory
+** using [tdsqlite3_free].
** Hence, if this variable is modified directly, either it should be
-** made NULL or made to point to memory obtained from [sqlite3_malloc]
+** made NULL or made to point to memory obtained from [tdsqlite3_malloc]
** or else the use of the [temp_store_directory pragma] should be avoided.
** Except when requested by the [temp_store_directory pragma], SQLite
-** does not free the memory that sqlite3_temp_directory points to. If
+** does not free the memory that tdsqlite3_temp_directory points to. If
** the application wants that memory to be freed, it must do
** so itself, taking care to only do so after all [database connection]
** objects have been destroyed.
**
** <b>Note to Windows Runtime users:</b> The temporary directory must be set
-** prior to calling [sqlite3_open] or [sqlite3_open_v2]. Otherwise, various
+** prior to calling [tdsqlite3_open] or [tdsqlite3_open_v2]. Otherwise, various
** features that require the use of temporary files may fail. Here is an
** example of how to do this using C++ with the Windows Runtime:
**
@@ -5188,10 +5960,10 @@ SQLITE_API int sqlite3_sleep(int);
** memset(zPathBuf, 0, sizeof(zPathBuf));
** WideCharToMultiByte(CP_UTF8, 0, zPath, -1, zPathBuf, sizeof(zPathBuf),
** &nbsp; NULL, NULL);
-** sqlite3_temp_directory = sqlite3_mprintf("%s", zPathBuf);
+** tdsqlite3_temp_directory = tdsqlite3_mprintf("%s", zPathBuf);
** </pre></blockquote>
*/
-SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory;
+SQLITE_API SQLITE_EXTERN char *tdsqlite3_temp_directory;
/*
** CAPI3REF: Name Of The Folder Holding Database Files
@@ -5199,7 +5971,7 @@ SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory;
** ^(If this global variable is made to point to a string which is
** the name of a folder (a.k.a. directory), then all database files
** specified with a relative pathname and created or accessed by
-** SQLite when using a built-in windows [sqlite3_vfs | VFS] will be assumed
+** SQLite when using a built-in windows [tdsqlite3_vfs | VFS] will be assumed
** to be relative to that directory.)^ ^If this variable is a NULL
** pointer, then SQLite assumes that all database files specified
** with a relative pathname are relative to the current directory
@@ -5219,23 +5991,58 @@ SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory;
** thereafter.
**
** ^The [data_store_directory pragma] may modify this variable and cause
-** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore,
+** it to point to memory obtained from [tdsqlite3_malloc]. ^Furthermore,
** the [data_store_directory pragma] always assumes that any string
** that this variable points to is held in memory obtained from
-** [sqlite3_malloc] and the pragma may attempt to free that memory
-** using [sqlite3_free].
+** [tdsqlite3_malloc] and the pragma may attempt to free that memory
+** using [tdsqlite3_free].
** Hence, if this variable is modified directly, either it should be
-** made NULL or made to point to memory obtained from [sqlite3_malloc]
+** made NULL or made to point to memory obtained from [tdsqlite3_malloc]
** or else the use of the [data_store_directory pragma] should be avoided.
*/
-SQLITE_API SQLITE_EXTERN char *sqlite3_data_directory;
+SQLITE_API SQLITE_EXTERN char *tdsqlite3_data_directory;
+
+/*
+** CAPI3REF: Win32 Specific Interface
+**
+** These interfaces are available only on Windows. The
+** [tdsqlite3_win32_set_directory] interface is used to set the value associated
+** with the [tdsqlite3_temp_directory] or [tdsqlite3_data_directory] variable, to
+** zValue, depending on the value of the type parameter. The zValue parameter
+** should be NULL to cause the previous value to be freed via [tdsqlite3_free];
+** a non-NULL value will be copied into memory obtained from [tdsqlite3_malloc]
+** prior to being used. The [tdsqlite3_win32_set_directory] interface returns
+** [SQLITE_OK] to indicate success, [SQLITE_ERROR] if the type is unsupported,
+** or [SQLITE_NOMEM] if memory could not be allocated. The value of the
+** [tdsqlite3_data_directory] variable is intended to act as a replacement for
+** the current directory on the sub-platforms of Win32 where that concept is
+** not present, e.g. WinRT and UWP. The [tdsqlite3_win32_set_directory8] and
+** [tdsqlite3_win32_set_directory16] interfaces behave exactly the same as the
+** tdsqlite3_win32_set_directory interface except the string parameter must be
+** UTF-8 or UTF-16, respectively.
+*/
+SQLITE_API int tdsqlite3_win32_set_directory(
+ unsigned long type, /* Identifier for directory being set or reset */
+ void *zValue /* New value for directory being set or reset */
+);
+SQLITE_API int tdsqlite3_win32_set_directory8(unsigned long type, const char *zValue);
+SQLITE_API int tdsqlite3_win32_set_directory16(unsigned long type, const void *zValue);
+
+/*
+** CAPI3REF: Win32 Directory Types
+**
+** These macros are only available on Windows. They define the allowed values
+** for the type argument to the [tdsqlite3_win32_set_directory] interface.
+*/
+#define SQLITE_WIN32_DATA_DIRECTORY_TYPE 1
+#define SQLITE_WIN32_TEMP_DIRECTORY_TYPE 2
/*
** CAPI3REF: Test For Auto-Commit Mode
** KEYWORDS: {autocommit mode}
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_get_autocommit() interface returns non-zero or
+** ^The tdsqlite3_get_autocommit() interface returns non-zero or
** zero if the given database connection is or is not in autocommit mode,
** respectively. ^Autocommit mode is on by default.
** ^Autocommit mode is disabled by a [BEGIN] statement.
@@ -5252,51 +6059,66 @@ SQLITE_API SQLITE_EXTERN char *sqlite3_data_directory;
** connection while this routine is running, then the return value
** is undefined.
*/
-SQLITE_API int sqlite3_get_autocommit(sqlite3*);
+SQLITE_API int tdsqlite3_get_autocommit(tdsqlite3*);
/*
** CAPI3REF: Find The Database Handle Of A Prepared Statement
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^The sqlite3_db_handle interface returns the [database connection] handle
+** ^The tdsqlite3_db_handle interface returns the [database connection] handle
** to which a [prepared statement] belongs. ^The [database connection]
-** returned by sqlite3_db_handle is the same [database connection]
+** returned by tdsqlite3_db_handle is the same [database connection]
** that was the first argument
-** to the [sqlite3_prepare_v2()] call (or its variants) that was used to
+** to the [tdsqlite3_prepare_v2()] call (or its variants) that was used to
** create the statement in the first place.
*/
-SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
+SQLITE_API tdsqlite3 *tdsqlite3_db_handle(tdsqlite3_stmt*);
/*
** CAPI3REF: Return The Filename For A Database Connection
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_db_filename(D,N) interface returns a pointer to a filename
-** associated with database N of connection D. ^The main database file
-** has the name "main". If there is no attached database N on the database
+** ^The tdsqlite3_db_filename(D,N) interface returns a pointer to the filename
+** associated with database N of connection D.
+** ^If there is no attached database N on the database
** connection D, or if database N is a temporary or in-memory database, then
-** a NULL pointer is returned.
+** this function will return either a NULL pointer or an empty string.
+**
+** ^The string value returned by this routine is owned and managed by
+** the database connection. ^The value will be valid until the database N
+** is [DETACH]-ed or until the database connection closes.
**
** ^The filename returned by this function is the output of the
** xFullPathname method of the [VFS]. ^In other words, the filename
** will be an absolute pathname, even if the filename used
** to open the database originally was a URI or relative pathname.
+**
+** If the filename pointer returned by this routine is not NULL, then it
+** can be used as the filename input parameter to these routines:
+** <ul>
+** <li> [tdsqlite3_uri_parameter()]
+** <li> [tdsqlite3_uri_boolean()]
+** <li> [tdsqlite3_uri_int64()]
+** <li> [tdsqlite3_filename_database()]
+** <li> [tdsqlite3_filename_journal()]
+** <li> [tdsqlite3_filename_wal()]
+** </ul>
*/
-SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName);
+SQLITE_API const char *tdsqlite3_db_filename(tdsqlite3 *db, const char *zDbName);
/*
** CAPI3REF: Determine if a database is read-only
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_db_readonly(D,N) interface returns 1 if the database N
+** ^The tdsqlite3_db_readonly(D,N) interface returns 1 if the database N
** of connection D is read-only, 0 if it is read/write, or -1 if N is not
** the name of a database on connection D.
*/
-SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName);
+SQLITE_API int tdsqlite3_db_readonly(tdsqlite3 *db, const char *zDbName);
/*
** CAPI3REF: Find the next prepared statement
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This interface returns a pointer to the next [prepared statement] after
** pStmt associated with the [database connection] pDb. ^If pStmt is NULL
@@ -5305,28 +6127,28 @@ SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName);
** satisfies the conditions of this routine, it returns NULL.
**
** The [database connection] pointer D in a call to
-** [sqlite3_next_stmt(D,S)] must refer to an open database
+** [tdsqlite3_next_stmt(D,S)] must refer to an open database
** connection and in particular must not be a NULL pointer.
*/
-SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt);
+SQLITE_API tdsqlite3_stmt *tdsqlite3_next_stmt(tdsqlite3 *pDb, tdsqlite3_stmt *pStmt);
/*
** CAPI3REF: Commit And Rollback Notification Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_commit_hook() interface registers a callback
+** ^The tdsqlite3_commit_hook() interface registers a callback
** function to be invoked whenever a transaction is [COMMIT | committed].
-** ^Any callback set by a previous call to sqlite3_commit_hook()
+** ^Any callback set by a previous call to tdsqlite3_commit_hook()
** for the same database connection is overridden.
-** ^The sqlite3_rollback_hook() interface registers a callback
+** ^The tdsqlite3_rollback_hook() interface registers a callback
** function to be invoked whenever a transaction is [ROLLBACK | rolled back].
-** ^Any callback set by a previous call to sqlite3_rollback_hook()
+** ^Any callback set by a previous call to tdsqlite3_rollback_hook()
** for the same database connection is overridden.
** ^The pArg argument is passed through to the callback.
** ^If the callback on a commit hook function returns non-zero,
** then the commit is converted into a rollback.
**
-** ^The sqlite3_commit_hook(D,C,P) and sqlite3_rollback_hook(D,C,P) functions
+** ^The tdsqlite3_commit_hook(D,C,P) and tdsqlite3_rollback_hook(D,C,P) functions
** return the P argument from the previous call of the same function
** on the same [database connection] D, or NULL for
** the first call for each function on D.
@@ -5335,10 +6157,10 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt);
** The callback implementation must not do anything that will modify
** the database connection that invoked the callback. Any actions
** to modify the database connection must be deferred until after the
-** completion of the [sqlite3_step()] call that triggered the commit
+** completion of the [tdsqlite3_step()] call that triggered the commit
** or rollback hook in the first place.
** Note that running any other SQL statements, including SELECT statements,
-** or merely calling [sqlite3_prepare_v2()] and [sqlite3_step()] will modify
+** or merely calling [tdsqlite3_prepare_v2()] and [tdsqlite3_step()] will modify
** the database connections for the meaning of "modify" in this paragraph.
**
** ^Registering a NULL function disables the callback.
@@ -5355,16 +6177,16 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt);
** ^The rollback callback is not invoked if a transaction is
** automatically rolled back because the database connection is closed.
**
-** See also the [sqlite3_update_hook()] interface.
+** See also the [tdsqlite3_update_hook()] interface.
*/
-SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*);
-SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
+SQLITE_API void *tdsqlite3_commit_hook(tdsqlite3*, int(*)(void*), void*);
+SQLITE_API void *tdsqlite3_rollback_hook(tdsqlite3*, void(*)(void *), void*);
/*
** CAPI3REF: Data Change Notification Callbacks
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_update_hook() interface registers a callback function
+** ^The tdsqlite3_update_hook() interface registers a callback function
** with the [database connection] identified by the first argument
** to be invoked whenever a row is updated, inserted or deleted in
** a [rowid table].
@@ -5374,7 +6196,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** ^The second argument is a pointer to the function to invoke when a
** row is updated, inserted or deleted in a rowid table.
** ^The first argument to the callback is a copy of the third argument
-** to sqlite3_update_hook().
+** to tdsqlite3_update_hook().
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE],
** or [SQLITE_UPDATE], depending on the operation that caused the callback
** to be invoked.
@@ -5388,7 +6210,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified.
**
** ^In the current implementation, the update hook
-** is not invoked when duplication rows are deleted because of an
+** is not invoked when conflicting rows are deleted because of an
** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook
** invoked when rows are deleted using the [truncate optimization].
** The exceptions defined in this paragraph might change in a future
@@ -5397,21 +6219,21 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
** The update hook implementation must not do anything that will modify
** the database connection that invoked the update hook. Any actions
** to modify the database connection must be deferred until after the
-** completion of the [sqlite3_step()] call that triggered the update hook.
-** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their
+** completion of the [tdsqlite3_step()] call that triggered the update hook.
+** Note that [tdsqlite3_prepare_v2()] and [tdsqlite3_step()] both modify their
** database connections for the meaning of "modify" in this paragraph.
**
-** ^The sqlite3_update_hook(D,C,P) function
+** ^The tdsqlite3_update_hook(D,C,P) function
** returns the P argument from the previous call
** on the same [database connection] D, or NULL for
** the first call on D.
**
-** See also the [sqlite3_commit_hook()], [sqlite3_rollback_hook()],
-** and [sqlite3_preupdate_hook()] interfaces.
+** See also the [tdsqlite3_commit_hook()], [tdsqlite3_rollback_hook()],
+** and [tdsqlite3_preupdate_hook()] interfaces.
*/
-SQLITE_API void *sqlite3_update_hook(
- sqlite3*,
- void(*)(void *,int ,char const *,char const *,sqlite3_int64),
+SQLITE_API void *tdsqlite3_update_hook(
+ tdsqlite3*,
+ void(*)(void *,int ,char const *,char const *,tdsqlite3_int64),
void*
);
@@ -5429,63 +6251,70 @@ SQLITE_API void *sqlite3_update_hook(
** sharing was enabled or disabled for each thread separately.
**
** ^(The cache sharing mode set by this interface effects all subsequent
-** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()].
-** Existing database connections continue use the sharing mode
+** calls to [tdsqlite3_open()], [tdsqlite3_open_v2()], and [tdsqlite3_open16()].
+** Existing database connections continue to use the sharing mode
** that was in effect at the time they were opened.)^
**
** ^(This routine returns [SQLITE_OK] if shared cache was enabled or disabled
** successfully. An [error code] is returned otherwise.)^
**
-** ^Shared cache is disabled by default. But this might change in
-** future releases of SQLite. Applications that care about shared
-** cache setting should set it explicitly.
+** ^Shared cache is disabled by default. It is recommended that it stay
+** that way. In other words, do not use this routine. This interface
+** continues to be provided for historical compatibility, but its use is
+** discouraged. Any use of shared cache is discouraged. If shared cache
+** must be used, it is recommended that shared cache only be enabled for
+** individual database connections using the [tdsqlite3_open_v2()] interface
+** with the [SQLITE_OPEN_SHAREDCACHE] flag.
**
** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0
** and will always return SQLITE_MISUSE. On those systems,
** shared cache mode should be enabled per-database connection via
-** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE].
+** [tdsqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE].
**
** This interface is threadsafe on processors where writing a
** 32-bit integer is atomic.
**
** See Also: [SQLite Shared-Cache Mode]
*/
-SQLITE_API int sqlite3_enable_shared_cache(int);
+SQLITE_API int tdsqlite3_enable_shared_cache(int);
/*
** CAPI3REF: Attempt To Free Heap Memory
**
-** ^The sqlite3_release_memory() interface attempts to free N bytes
+** ^The tdsqlite3_release_memory() interface attempts to free N bytes
** of heap memory by deallocating non-essential memory allocations
** held by the database library. Memory used to cache database
** pages to improve performance is an example of non-essential memory.
-** ^sqlite3_release_memory() returns the number of bytes actually freed,
+** ^tdsqlite3_release_memory() returns the number of bytes actually freed,
** which might be more or less than the amount requested.
-** ^The sqlite3_release_memory() routine is a no-op returning zero
+** ^The tdsqlite3_release_memory() routine is a no-op returning zero
** if SQLite is not compiled with [SQLITE_ENABLE_MEMORY_MANAGEMENT].
**
-** See also: [sqlite3_db_release_memory()]
+** See also: [tdsqlite3_db_release_memory()]
*/
-SQLITE_API int sqlite3_release_memory(int);
+SQLITE_API int tdsqlite3_release_memory(int);
/*
** CAPI3REF: Free Memory Used By A Database Connection
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The sqlite3_db_release_memory(D) interface attempts to free as much heap
+** ^The tdsqlite3_db_release_memory(D) interface attempts to free as much heap
** memory as possible from database connection D. Unlike the
-** [sqlite3_release_memory()] interface, this interface is in effect even
+** [tdsqlite3_release_memory()] interface, this interface is in effect even
** when the [SQLITE_ENABLE_MEMORY_MANAGEMENT] compile-time option is
** omitted.
**
-** See also: [sqlite3_release_memory()]
+** See also: [tdsqlite3_release_memory()]
*/
-SQLITE_API int sqlite3_db_release_memory(sqlite3*);
+SQLITE_API int tdsqlite3_db_release_memory(tdsqlite3*);
/*
** CAPI3REF: Impose A Limit On Heap Size
**
-** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
+** These interfaces impose limits on the amount of heap memory that will be
+** by all database connections within a single process.
+**
+** ^The tdsqlite3_soft_heap_limit64() interface sets and/or queries the
** soft limit on the amount of heap memory that may be allocated by SQLite.
** ^SQLite strives to keep heap memory utilization below the soft heap
** limit by reducing the number of pages held in the page cache
@@ -5495,73 +6324,86 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
** an [SQLITE_NOMEM] error. In other words, the soft heap limit
** is advisory only.
**
-** ^The return value from sqlite3_soft_heap_limit64() is the size of
-** the soft heap limit prior to the call, or negative in the case of an
-** error. ^If the argument N is negative
-** then no change is made to the soft heap limit. Hence, the current
-** size of the soft heap limit can be determined by invoking
-** sqlite3_soft_heap_limit64() with a negative argument.
-**
-** ^If the argument N is zero then the soft heap limit is disabled.
+** ^The tdsqlite3_hard_heap_limit64(N) interface sets a hard upper bound of
+** N bytes on the amount of memory that will be allocated. ^The
+** tdsqlite3_hard_heap_limit64(N) interface is similar to
+** tdsqlite3_soft_heap_limit64(N) except that memory allocations will fail
+** when the hard heap limit is reached.
**
-** ^(The soft heap limit is not enforced in the current implementation
+** ^The return value from both tdsqlite3_soft_heap_limit64() and
+** tdsqlite3_hard_heap_limit64() is the size of
+** the heap limit prior to the call, or negative in the case of an
+** error. ^If the argument N is negative
+** then no change is made to the heap limit. Hence, the current
+** size of heap limits can be determined by invoking
+** tdsqlite3_soft_heap_limit64(-1) or tdsqlite3_hard_heap_limit(-1).
+**
+** ^Setting the heap limits to zero disables the heap limiter mechanism.
+**
+** ^The soft heap limit may not be greater than the hard heap limit.
+** ^If the hard heap limit is enabled and if tdsqlite3_soft_heap_limit(N)
+** is invoked with a value of N that is greater than the hard heap limit,
+** the the soft heap limit is set to the value of the hard heap limit.
+** ^The soft heap limit is automatically enabled whenever the hard heap
+** limit is enabled. ^When tdsqlite3_hard_heap_limit64(N) is invoked and
+** the soft heap limit is outside the range of 1..N, then the soft heap
+** limit is set to N. ^Invoking tdsqlite3_soft_heap_limit64(0) when the
+** hard heap limit is enabled makes the soft heap limit equal to the
+** hard heap limit.
+**
+** The memory allocation limits can also be adjusted using
+** [PRAGMA soft_heap_limit] and [PRAGMA hard_heap_limit].
+**
+** ^(The heap limits are not enforced in the current implementation
** if one or more of following conditions are true:
**
** <ul>
-** <li> The soft heap limit is set to zero.
+** <li> The limit value is set to zero.
** <li> Memory accounting is disabled using a combination of the
-** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and
+** [tdsqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and
** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option.
** <li> An alternative page cache implementation is specified using
-** [sqlite3_config]([SQLITE_CONFIG_PCACHE2],...).
+** [tdsqlite3_config]([SQLITE_CONFIG_PCACHE2],...).
** <li> The page cache allocates from its own memory pool supplied
-** by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than
+** by [tdsqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than
** from the heap.
** </ul>)^
**
-** Beginning with SQLite [version 3.7.3] ([dateof:3.7.3]),
-** the soft heap limit is enforced
-** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT]
-** compile-time option is invoked. With [SQLITE_ENABLE_MEMORY_MANAGEMENT],
-** the soft heap limit is enforced on every memory allocation. Without
-** [SQLITE_ENABLE_MEMORY_MANAGEMENT], the soft heap limit is only enforced
-** when memory is allocated by the page cache. Testing suggests that because
-** the page cache is the predominate memory user in SQLite, most
-** applications will achieve adequate soft heap limit enforcement without
-** the use of [SQLITE_ENABLE_MEMORY_MANAGEMENT].
-**
-** The circumstances under which SQLite will enforce the soft heap limit may
+** The circumstances under which SQLite will enforce the heap limits may
** changes in future releases of SQLite.
*/
-SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
+SQLITE_API tdsqlite3_int64 tdsqlite3_soft_heap_limit64(tdsqlite3_int64 N);
+SQLITE_API tdsqlite3_int64 tdsqlite3_hard_heap_limit64(tdsqlite3_int64 N);
/*
** CAPI3REF: Deprecated Soft Heap Limit Interface
** DEPRECATED
**
-** This is a deprecated version of the [sqlite3_soft_heap_limit64()]
+** This is a deprecated version of the [tdsqlite3_soft_heap_limit64()]
** interface. This routine is provided for historical compatibility
** only. All new applications should use the
-** [sqlite3_soft_heap_limit64()] interface rather than this one.
+** [tdsqlite3_soft_heap_limit64()] interface rather than this one.
*/
-SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
+SQLITE_API SQLITE_DEPRECATED void tdsqlite3_soft_heap_limit(int N);
/*
** CAPI3REF: Extract Metadata About A Column Of A Table
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns
+** ^(The tdsqlite3_table_column_metadata(X,D,T,C,....) routine returns
** information about column C of table T in database D
-** on [database connection] X.)^ ^The sqlite3_table_column_metadata()
+** on [database connection] X.)^ ^The tdsqlite3_table_column_metadata()
** interface returns SQLITE_OK and fills in the non-NULL pointers in
** the final five arguments with appropriate values if the specified
-** column exists. ^The sqlite3_table_column_metadata() interface returns
-** SQLITE_ERROR and if the specified column does not exist.
-** ^If the column-name parameter to sqlite3_table_column_metadata() is a
+** column exists. ^The tdsqlite3_table_column_metadata() interface returns
+** SQLITE_ERROR if the specified column does not exist.
+** ^If the column-name parameter to tdsqlite3_table_column_metadata() is a
** NULL pointer, then this routine simply checks for the existence of the
** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it
-** does not.
+** does not. If the table name parameter T in a call to
+** tdsqlite3_table_column_metadata(X,D,T,C,...) is NULL then the result is
+** undefined behavior.
**
** ^The column is identified by the second, third and fourth parameters to
** this function. ^(The second parameter is either the name of the database
@@ -5614,8 +6456,8 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
** parsed, if that has not already been done, and returns an error if
** any errors are encountered while loading the schema.
*/
-SQLITE_API int sqlite3_table_column_metadata(
- sqlite3 *db, /* Connection handle */
+SQLITE_API int tdsqlite3_table_column_metadata(
+ tdsqlite3 *db, /* Connection handle */
const char *zDbName, /* Database name or NULL */
const char *zTableName, /* Table name */
const char *zColumnName, /* Column name */
@@ -5628,11 +6470,11 @@ SQLITE_API int sqlite3_table_column_metadata(
/*
** CAPI3REF: Load An Extension
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This interface loads an SQLite extension library from the named file.
**
-** ^The sqlite3_load_extension() interface attempts to load an
+** ^The tdsqlite3_load_extension() interface attempts to load an
** [SQLite extension] library contained in the file zFile. If
** the file cannot be loaded directly, attempts are made to load
** with various operating-system specific extensions added.
@@ -5642,36 +6484,36 @@ SQLITE_API int sqlite3_table_column_metadata(
**
** ^The entry point is zProc.
** ^(zProc may be 0, in which case SQLite will try to come up with an
-** entry point name on its own. It first tries "sqlite3_extension_init".
-** If that does not work, it constructs a name "sqlite3_X_init" where the
+** entry point name on its own. It first tries "tdsqlite3_extension_init".
+** If that does not work, it constructs a name "tdsqlite3_X_init" where the
** X is consists of the lower-case equivalent of all ASCII alphabetic
** characters in the filename from the last "/" to the first following
** "." and omitting any initial "lib".)^
-** ^The sqlite3_load_extension() interface returns
+** ^The tdsqlite3_load_extension() interface returns
** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong.
** ^If an error occurs and pzErrMsg is not 0, then the
-** [sqlite3_load_extension()] interface shall attempt to
+** [tdsqlite3_load_extension()] interface shall attempt to
** fill *pzErrMsg with error message text stored in memory
-** obtained from [sqlite3_malloc()]. The calling function
-** should free this memory by calling [sqlite3_free()].
+** obtained from [tdsqlite3_malloc()]. The calling function
+** should free this memory by calling [tdsqlite3_free()].
**
** ^Extension loading must be enabled using
-** [sqlite3_enable_load_extension()] or
-** [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],1,NULL)
+** [tdsqlite3_enable_load_extension()] or
+** [tdsqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],1,NULL)
** prior to calling this API,
** otherwise an error will be returned.
**
** <b>Security warning:</b> It is recommended that the
** [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method be used to enable only this
-** interface. The use of the [sqlite3_enable_load_extension()] interface
+** interface. The use of the [tdsqlite3_enable_load_extension()] interface
** should be avoided. This will keep the SQL function [load_extension()]
** disabled and prevent SQL injections from giving attackers
** access to extension loading capabilities.
**
** See also the [load_extension() SQL function].
*/
-SQLITE_API int sqlite3_load_extension(
- sqlite3 *db, /* Load the extension into this database connection */
+SQLITE_API int tdsqlite3_load_extension(
+ tdsqlite3 *db, /* Load the extension into this database connection */
const char *zFile, /* Name of the shared library containing extension */
const char *zProc, /* Entry point. Derived from zFile if 0 */
char **pzErrMsg /* Put error message here if not 0 */
@@ -5679,30 +6521,30 @@ SQLITE_API int sqlite3_load_extension(
/*
** CAPI3REF: Enable Or Disable Extension Loading
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^So as not to open security holes in older applications that are
** unprepared to deal with [extension loading], and as a means of disabling
** [extension loading] while evaluating user-entered SQL, the following API
-** is provided to turn the [sqlite3_load_extension()] mechanism on and off.
+** is provided to turn the [tdsqlite3_load_extension()] mechanism on and off.
**
** ^Extension loading is off by default.
-** ^Call the sqlite3_enable_load_extension() routine with onoff==1
+** ^Call the tdsqlite3_enable_load_extension() routine with onoff==1
** to turn extension loading on and call it with onoff==0 to turn
** it back off again.
**
** ^This interface enables or disables both the C-API
-** [sqlite3_load_extension()] and the SQL function [load_extension()].
-** ^(Use [sqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],..)
+** [tdsqlite3_load_extension()] and the SQL function [load_extension()].
+** ^(Use [tdsqlite3_db_config](db,[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION],..)
** to enable or disable only the C-API.)^
**
** <b>Security warning:</b> It is recommended that extension loading
-** be disabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method
+** be enabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method
** rather than this interface, so the [load_extension()] SQL function
** remains disabled. This will prevent SQL injections from giving attackers
** access to extension loading capabilities.
*/
-SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
+SQLITE_API int tdsqlite3_enable_load_extension(tdsqlite3 *db, int onoff);
/*
** CAPI3REF: Automatically Load Statically Linked Extensions
@@ -5719,48 +6561,48 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
**
** <blockquote><pre>
** &nbsp; int xEntryPoint(
-** &nbsp; sqlite3 *db,
+** &nbsp; tdsqlite3 *db,
** &nbsp; const char **pzErrMsg,
-** &nbsp; const struct sqlite3_api_routines *pThunk
+** &nbsp; const struct tdsqlite3_api_routines *pThunk
** &nbsp; );
** </pre></blockquote>)^
**
** If the xEntryPoint routine encounters an error, it should make *pzErrMsg
-** point to an appropriate error message (obtained from [sqlite3_mprintf()])
+** point to an appropriate error message (obtained from [tdsqlite3_mprintf()])
** and return an appropriate [error code]. ^SQLite ensures that *pzErrMsg
** is NULL before calling the xEntryPoint(). ^SQLite will invoke
-** [sqlite3_free()] on *pzErrMsg after xEntryPoint() returns. ^If any
-** xEntryPoint() returns an error, the [sqlite3_open()], [sqlite3_open16()],
-** or [sqlite3_open_v2()] call that provoked the xEntryPoint() will fail.
+** [tdsqlite3_free()] on *pzErrMsg after xEntryPoint() returns. ^If any
+** xEntryPoint() returns an error, the [tdsqlite3_open()], [tdsqlite3_open16()],
+** or [tdsqlite3_open_v2()] call that provoked the xEntryPoint() will fail.
**
-** ^Calling sqlite3_auto_extension(X) with an entry point X that is already
+** ^Calling tdsqlite3_auto_extension(X) with an entry point X that is already
** on the list of automatic extensions is a harmless no-op. ^No entry point
** will be called more than once for each database connection that is opened.
**
-** See also: [sqlite3_reset_auto_extension()]
-** and [sqlite3_cancel_auto_extension()]
+** See also: [tdsqlite3_reset_auto_extension()]
+** and [tdsqlite3_cancel_auto_extension()]
*/
-SQLITE_API int sqlite3_auto_extension(void(*xEntryPoint)(void));
+SQLITE_API int tdsqlite3_auto_extension(void(*xEntryPoint)(void));
/*
** CAPI3REF: Cancel Automatic Extension Loading
**
-** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the
+** ^The [tdsqlite3_cancel_auto_extension(X)] interface unregisters the
** initialization routine X that was registered using a prior call to
-** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)]
+** [tdsqlite3_auto_extension(X)]. ^The [tdsqlite3_cancel_auto_extension(X)]
** routine returns 1 if initialization routine X was successfully
** unregistered and it returns 0 if X was not on the list of initialization
** routines.
*/
-SQLITE_API int sqlite3_cancel_auto_extension(void(*xEntryPoint)(void));
+SQLITE_API int tdsqlite3_cancel_auto_extension(void(*xEntryPoint)(void));
/*
** CAPI3REF: Reset Automatic Extension Loading
**
** ^This interface disables all automatic extensions previously
-** registered using [sqlite3_auto_extension()].
+** registered using [tdsqlite3_auto_extension()].
*/
-SQLITE_API void sqlite3_reset_auto_extension(void);
+SQLITE_API void tdsqlite3_reset_auto_extension(void);
/*
** The interface to the virtual-table mechanism is currently considered
@@ -5774,67 +6616,70 @@ SQLITE_API void sqlite3_reset_auto_extension(void);
/*
** Structures used by the virtual table interface
*/
-typedef struct sqlite3_vtab sqlite3_vtab;
-typedef struct sqlite3_index_info sqlite3_index_info;
-typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor;
-typedef struct sqlite3_module sqlite3_module;
+typedef struct tdsqlite3_vtab tdsqlite3_vtab;
+typedef struct tdsqlite3_index_info tdsqlite3_index_info;
+typedef struct tdsqlite3_vtab_cursor tdsqlite3_vtab_cursor;
+typedef struct tdsqlite3_module tdsqlite3_module;
/*
** CAPI3REF: Virtual Table Object
-** KEYWORDS: sqlite3_module {virtual table module}
+** KEYWORDS: tdsqlite3_module {virtual table module}
**
** This structure, sometimes called a "virtual table module",
-** defines the implementation of a [virtual tables].
+** defines the implementation of a [virtual table].
** This structure consists mostly of methods for the module.
**
** ^A virtual table module is created by filling in a persistent
** instance of this structure and passing a pointer to that instance
-** to [sqlite3_create_module()] or [sqlite3_create_module_v2()].
+** to [tdsqlite3_create_module()] or [tdsqlite3_create_module_v2()].
** ^The registration remains valid until it is replaced by a different
** module or until the [database connection] closes. The content
** of this structure must not change while it is registered with
** any database connection.
*/
-struct sqlite3_module {
+struct tdsqlite3_module {
int iVersion;
- int (*xCreate)(sqlite3*, void *pAux,
+ int (*xCreate)(tdsqlite3*, void *pAux,
int argc, const char *const*argv,
- sqlite3_vtab **ppVTab, char**);
- int (*xConnect)(sqlite3*, void *pAux,
+ tdsqlite3_vtab **ppVTab, char**);
+ int (*xConnect)(tdsqlite3*, void *pAux,
int argc, const char *const*argv,
- sqlite3_vtab **ppVTab, char**);
- int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
- int (*xDisconnect)(sqlite3_vtab *pVTab);
- int (*xDestroy)(sqlite3_vtab *pVTab);
- int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
- int (*xClose)(sqlite3_vtab_cursor*);
- int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
- int argc, sqlite3_value **argv);
- int (*xNext)(sqlite3_vtab_cursor*);
- int (*xEof)(sqlite3_vtab_cursor*);
- int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int);
- int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid);
- int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *);
- int (*xBegin)(sqlite3_vtab *pVTab);
- int (*xSync)(sqlite3_vtab *pVTab);
- int (*xCommit)(sqlite3_vtab *pVTab);
- int (*xRollback)(sqlite3_vtab *pVTab);
- int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
- void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
+ tdsqlite3_vtab **ppVTab, char**);
+ int (*xBestIndex)(tdsqlite3_vtab *pVTab, tdsqlite3_index_info*);
+ int (*xDisconnect)(tdsqlite3_vtab *pVTab);
+ int (*xDestroy)(tdsqlite3_vtab *pVTab);
+ int (*xOpen)(tdsqlite3_vtab *pVTab, tdsqlite3_vtab_cursor **ppCursor);
+ int (*xClose)(tdsqlite3_vtab_cursor*);
+ int (*xFilter)(tdsqlite3_vtab_cursor*, int idxNum, const char *idxStr,
+ int argc, tdsqlite3_value **argv);
+ int (*xNext)(tdsqlite3_vtab_cursor*);
+ int (*xEof)(tdsqlite3_vtab_cursor*);
+ int (*xColumn)(tdsqlite3_vtab_cursor*, tdsqlite3_context*, int);
+ int (*xRowid)(tdsqlite3_vtab_cursor*, tdsqlite3_int64 *pRowid);
+ int (*xUpdate)(tdsqlite3_vtab *, int, tdsqlite3_value **, tdsqlite3_int64 *);
+ int (*xBegin)(tdsqlite3_vtab *pVTab);
+ int (*xSync)(tdsqlite3_vtab *pVTab);
+ int (*xCommit)(tdsqlite3_vtab *pVTab);
+ int (*xRollback)(tdsqlite3_vtab *pVTab);
+ int (*xFindFunction)(tdsqlite3_vtab *pVtab, int nArg, const char *zName,
+ void (**pxFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
void **ppArg);
- int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
+ int (*xRename)(tdsqlite3_vtab *pVtab, const char *zNew);
/* The methods above are in version 1 of the sqlite_module object. Those
** below are for version 2 and greater. */
- int (*xSavepoint)(sqlite3_vtab *pVTab, int);
- int (*xRelease)(sqlite3_vtab *pVTab, int);
- int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
+ int (*xSavepoint)(tdsqlite3_vtab *pVTab, int);
+ int (*xRelease)(tdsqlite3_vtab *pVTab, int);
+ int (*xRollbackTo)(tdsqlite3_vtab *pVTab, int);
+ /* The methods above are in versions 1 and 2 of the sqlite_module object.
+ ** Those below are for version 3 and greater. */
+ int (*xShadowName)(const char*);
};
/*
** CAPI3REF: Virtual Table Indexing Information
-** KEYWORDS: sqlite3_index_info
+** KEYWORDS: tdsqlite3_index_info
**
-** The sqlite3_index_info structure and its substructures is used as part
+** The tdsqlite3_index_info structure and its substructures is used as part
** of the [virtual table] interface to
** pass information into and receive the reply from the [xBestIndex]
** method of a [virtual table module]. The fields under **Inputs** are the
@@ -5865,12 +6710,12 @@ struct sqlite3_module {
** The colUsed field indicates which columns of the virtual table may be
** required by the current scan. Virtual table columns are numbered from
** zero in the order in which they appear within the CREATE TABLE statement
-** passed to sqlite3_declare_vtab(). For the first 63 columns (columns 0-62),
+** passed to tdsqlite3_declare_vtab(). For the first 63 columns (columns 0-62),
** the corresponding bit is set within the colUsed mask if the column may be
** required by SQLite. If the table has at least 64 columns and any column
** to the right of the first 63 is required, then bit 63 of colUsed is also
** set. In other words, column iCol may be required if the expression
-** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to
+** (colUsed & ((tdsqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to
** non-zero.
**
** The [xBestIndex] method must fill aConstraintUsage[] with information
@@ -5878,11 +6723,17 @@ struct sqlite3_module {
** the right-hand side of the corresponding aConstraint[] is evaluated
** and becomes the argvIndex-th entry in argv. ^(If aConstraintUsage[].omit
** is true, then the constraint is assumed to be fully handled by the
-** virtual table and is not checked again by SQLite.)^
+** virtual table and might not be checked again by the byte code.)^ ^(The
+** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
+** is left in its default setting of false, the constraint will always be
+** checked separately in byte code. If the omit flag is change to true, then
+** the constraint may or may not be checked in byte code. In other words,
+** when the omit flag is true there is no guarantee that the constraint will
+** not be checked again using byte code.)^
**
** ^The idxNum and idxPtr values are recorded and passed into the
** [xFilter] method.
-** ^[sqlite3_free()] is used to free idxPtr if and only if
+** ^[tdsqlite3_free()] is used to free idxPtr if and only if
** needToFreeIdxPtr is true.
**
** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in
@@ -5913,77 +6764,87 @@ struct sqlite3_module {
** set and xUpdate returns SQLITE_CONSTRAINT, any database changes made by
** the xUpdate method are automatically rolled back by SQLite.
**
-** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info
+** IMPORTANT: The estimatedRows field was added to the tdsqlite3_index_info
** structure for SQLite [version 3.8.2] ([dateof:3.8.2]).
** If a virtual table extension is
** used with an SQLite version earlier than 3.8.2, the results of attempting
** to read or write the estimatedRows field are undefined (but are likely
-** to included crashing the application). The estimatedRows field should
-** therefore only be used if [sqlite3_libversion_number()] returns a
+** to include crashing the application). The estimatedRows field should
+** therefore only be used if [tdsqlite3_libversion_number()] returns a
** value greater than or equal to 3008002. Similarly, the idxFlags field
** was added for [version 3.9.0] ([dateof:3.9.0]).
** It may therefore only be used if
-** sqlite3_libversion_number() returns a value greater than or equal to
+** tdsqlite3_libversion_number() returns a value greater than or equal to
** 3009000.
*/
-struct sqlite3_index_info {
+struct tdsqlite3_index_info {
/* Inputs */
int nConstraint; /* Number of entries in aConstraint */
- struct sqlite3_index_constraint {
+ struct tdsqlite3_index_constraint {
int iColumn; /* Column constrained. -1 for ROWID */
unsigned char op; /* Constraint operator */
unsigned char usable; /* True if this constraint is usable */
int iTermOffset; /* Used internally - xBestIndex should ignore */
} *aConstraint; /* Table of WHERE clause constraints */
int nOrderBy; /* Number of terms in the ORDER BY clause */
- struct sqlite3_index_orderby {
+ struct tdsqlite3_index_orderby {
int iColumn; /* Column number */
unsigned char desc; /* True for DESC. False for ASC. */
} *aOrderBy; /* The ORDER BY clause */
/* Outputs */
- struct sqlite3_index_constraint_usage {
+ struct tdsqlite3_index_constraint_usage {
int argvIndex; /* if >0, constraint is part of argv to xFilter */
unsigned char omit; /* Do not code a test for this constraint */
} *aConstraintUsage;
int idxNum; /* Number used to identify the index */
- char *idxStr; /* String, possibly obtained from sqlite3_malloc */
- int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */
+ char *idxStr; /* String, possibly obtained from tdsqlite3_malloc */
+ int needToFreeIdxStr; /* Free idxStr using tdsqlite3_free() if true */
int orderByConsumed; /* True if output is already ordered */
double estimatedCost; /* Estimated cost of using this index */
/* Fields below are only available in SQLite 3.8.2 and later */
- sqlite3_int64 estimatedRows; /* Estimated number of rows returned */
+ tdsqlite3_int64 estimatedRows; /* Estimated number of rows returned */
/* Fields below are only available in SQLite 3.9.0 and later */
int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */
/* Fields below are only available in SQLite 3.10.0 and later */
- sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */
+ tdsqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */
};
/*
** CAPI3REF: Virtual Table Scan Flags
+**
+** Virtual table implementations are allowed to set the
+** [tdsqlite3_index_info].idxFlags field to some combination of
+** these bits.
*/
#define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */
/*
** CAPI3REF: Virtual Table Constraint Operator Codes
**
-** These macros defined the allowed values for the
-** [sqlite3_index_info].aConstraint[].op field. Each value represents
+** These macros define the allowed values for the
+** [tdsqlite3_index_info].aConstraint[].op field. Each value represents
** an operator that is part of a constraint term in the wHERE clause of
** a query that uses a [virtual table].
*/
-#define SQLITE_INDEX_CONSTRAINT_EQ 2
-#define SQLITE_INDEX_CONSTRAINT_GT 4
-#define SQLITE_INDEX_CONSTRAINT_LE 8
-#define SQLITE_INDEX_CONSTRAINT_LT 16
-#define SQLITE_INDEX_CONSTRAINT_GE 32
-#define SQLITE_INDEX_CONSTRAINT_MATCH 64
-#define SQLITE_INDEX_CONSTRAINT_LIKE 65
-#define SQLITE_INDEX_CONSTRAINT_GLOB 66
-#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
+#define SQLITE_INDEX_CONSTRAINT_EQ 2
+#define SQLITE_INDEX_CONSTRAINT_GT 4
+#define SQLITE_INDEX_CONSTRAINT_LE 8
+#define SQLITE_INDEX_CONSTRAINT_LT 16
+#define SQLITE_INDEX_CONSTRAINT_GE 32
+#define SQLITE_INDEX_CONSTRAINT_MATCH 64
+#define SQLITE_INDEX_CONSTRAINT_LIKE 65
+#define SQLITE_INDEX_CONSTRAINT_GLOB 66
+#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
+#define SQLITE_INDEX_CONSTRAINT_NE 68
+#define SQLITE_INDEX_CONSTRAINT_ISNOT 69
+#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70
+#define SQLITE_INDEX_CONSTRAINT_ISNULL 71
+#define SQLITE_INDEX_CONSTRAINT_IS 72
+#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150
/*
** CAPI3REF: Register A Virtual Table Implementation
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^These routines are used to register a new [virtual table module] name.
** ^Module names must be registered before
@@ -5998,32 +6859,55 @@ struct sqlite3_index_info {
** into the [xCreate] and [xConnect] methods of the virtual table module
** when a new virtual table is be being created or reinitialized.
**
-** ^The sqlite3_create_module_v2() interface has a fifth parameter which
+** ^The tdsqlite3_create_module_v2() interface has a fifth parameter which
** is a pointer to a destructor for the pClientData. ^SQLite will
** invoke the destructor function (if it is not NULL) when SQLite
** no longer needs the pClientData pointer. ^The destructor will also
-** be invoked if the call to sqlite3_create_module_v2() fails.
-** ^The sqlite3_create_module()
-** interface is equivalent to sqlite3_create_module_v2() with a NULL
+** be invoked if the call to tdsqlite3_create_module_v2() fails.
+** ^The tdsqlite3_create_module()
+** interface is equivalent to tdsqlite3_create_module_v2() with a NULL
** destructor.
+**
+** ^If the third parameter (the pointer to the tdsqlite3_module object) is
+** NULL then no new module is create and any existing modules with the
+** same name are dropped.
+**
+** See also: [tdsqlite3_drop_modules()]
*/
-SQLITE_API int sqlite3_create_module(
- sqlite3 *db, /* SQLite connection to register module with */
+SQLITE_API int tdsqlite3_create_module(
+ tdsqlite3 *db, /* SQLite connection to register module with */
const char *zName, /* Name of the module */
- const sqlite3_module *p, /* Methods for the module */
+ const tdsqlite3_module *p, /* Methods for the module */
void *pClientData /* Client data for xCreate/xConnect */
);
-SQLITE_API int sqlite3_create_module_v2(
- sqlite3 *db, /* SQLite connection to register module with */
+SQLITE_API int tdsqlite3_create_module_v2(
+ tdsqlite3 *db, /* SQLite connection to register module with */
const char *zName, /* Name of the module */
- const sqlite3_module *p, /* Methods for the module */
+ const tdsqlite3_module *p, /* Methods for the module */
void *pClientData, /* Client data for xCreate/xConnect */
void(*xDestroy)(void*) /* Module destructor function */
);
/*
+** CAPI3REF: Remove Unnecessary Virtual Table Implementations
+** METHOD: tdsqlite3
+**
+** ^The tdsqlite3_drop_modules(D,L) interface removes all virtual
+** table modules from database connection D except those named on list L.
+** The L parameter must be either NULL or a pointer to an array of pointers
+** to strings where the array is terminated by a single NULL pointer.
+** ^If the L parameter is NULL, then all virtual table modules are removed.
+**
+** See also: [tdsqlite3_create_module()]
+*/
+SQLITE_API int tdsqlite3_drop_modules(
+ tdsqlite3 *db, /* Remove modules from this connection */
+ const char **azKeep /* Except, do not remove the ones named here */
+);
+
+/*
** CAPI3REF: Virtual Table Instance Object
-** KEYWORDS: sqlite3_vtab
+** KEYWORDS: tdsqlite3_vtab
**
** Every [virtual table module] implementation uses a subclass
** of this object to describe a particular instance
@@ -6033,29 +6917,29 @@ SQLITE_API int sqlite3_create_module_v2(
** common to all module implementations.
**
** ^Virtual tables methods can set an error message by assigning a
-** string obtained from [sqlite3_mprintf()] to zErrMsg. The method should
-** take care that any prior string is freed by a call to [sqlite3_free()]
+** string obtained from [tdsqlite3_mprintf()] to zErrMsg. The method should
+** take care that any prior string is freed by a call to [tdsqlite3_free()]
** prior to assigning a new string to zErrMsg. ^After the error message
** is delivered up to the client application, the string will be automatically
-** freed by sqlite3_free() and the zErrMsg field will be zeroed.
+** freed by tdsqlite3_free() and the zErrMsg field will be zeroed.
*/
-struct sqlite3_vtab {
- const sqlite3_module *pModule; /* The module for this virtual table */
+struct tdsqlite3_vtab {
+ const tdsqlite3_module *pModule; /* The module for this virtual table */
int nRef; /* Number of open cursors */
- char *zErrMsg; /* Error message from sqlite3_mprintf() */
+ char *zErrMsg; /* Error message from tdsqlite3_mprintf() */
/* Virtual table implementations will typically add additional fields */
};
/*
** CAPI3REF: Virtual Table Cursor Object
-** KEYWORDS: sqlite3_vtab_cursor {virtual table cursor}
+** KEYWORDS: tdsqlite3_vtab_cursor {virtual table cursor}
**
** Every [virtual table module] implementation uses a subclass of the
** following structure to describe cursors that point into the
** [virtual table] and are used
** to loop through the virtual table. Cursors are created using the
-** [sqlite3_module.xOpen | xOpen] method of the module and are destroyed
-** by the [sqlite3_module.xClose | xClose] method. Cursors are used
+** [tdsqlite3_module.xOpen | xOpen] method of the module and are destroyed
+** by the [tdsqlite3_module.xClose | xClose] method. Cursors are used
** by the [xFilter], [xNext], [xEof], [xColumn], and [xRowid] methods
** of the module. Each module implementation will define
** the content of a cursor structure to suit its own needs.
@@ -6063,8 +6947,8 @@ struct sqlite3_vtab {
** This superclass exists in order to define fields of the cursor that
** are common to all implementations.
*/
-struct sqlite3_vtab_cursor {
- sqlite3_vtab *pVtab; /* Virtual table of this cursor */
+struct tdsqlite3_vtab_cursor {
+ tdsqlite3_vtab *pVtab; /* Virtual table of this cursor */
/* Virtual table implementations will typically add additional fields */
};
@@ -6076,11 +6960,11 @@ struct sqlite3_vtab_cursor {
** to declare the format (the names and datatypes of the columns) of
** the virtual tables they implement.
*/
-SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL);
+SQLITE_API int tdsqlite3_declare_vtab(tdsqlite3*, const char *zSQL);
/*
** CAPI3REF: Overload A Function For A Virtual Table
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^(Virtual tables can provide alternative implementations of functions
** using the [xFindFunction] method of the [virtual table module].
@@ -6095,7 +6979,7 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL);
** purpose is to be a placeholder function that can be overloaded
** by a [virtual table].
*/
-SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg);
+SQLITE_API int tdsqlite3_overload_function(tdsqlite3*, const char *zFuncName, int nArg);
/*
** The interface to the virtual-table mechanism defined above (back up
@@ -6112,19 +6996,19 @@ SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nA
** KEYWORDS: {BLOB handle} {BLOB handles}
**
** An instance of this object represents an open BLOB on which
-** [sqlite3_blob_open | incremental BLOB I/O] can be performed.
-** ^Objects of this type are created by [sqlite3_blob_open()]
-** and destroyed by [sqlite3_blob_close()].
-** ^The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces
+** [tdsqlite3_blob_open | incremental BLOB I/O] can be performed.
+** ^Objects of this type are created by [tdsqlite3_blob_open()]
+** and destroyed by [tdsqlite3_blob_close()].
+** ^The [tdsqlite3_blob_read()] and [tdsqlite3_blob_write()] interfaces
** can be used to read or write small subsections of the BLOB.
-** ^The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes.
+** ^The [tdsqlite3_blob_bytes()] interface returns the size of the BLOB in bytes.
*/
-typedef struct sqlite3_blob sqlite3_blob;
+typedef struct tdsqlite3_blob tdsqlite3_blob;
/*
** CAPI3REF: Open A BLOB For Incremental I/O
-** METHOD: sqlite3
-** CONSTRUCTOR: sqlite3_blob
+** METHOD: tdsqlite3
+** CONSTRUCTOR: tdsqlite3_blob
**
** ^(This interfaces opens a [BLOB handle | handle] to the BLOB located
** in row iRow, column zColumn, table zTable in database zDb;
@@ -6147,7 +7031,7 @@ typedef struct sqlite3_blob sqlite3_blob;
** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
-** the API is not misused, it is always safe to call [sqlite3_blob_close()]
+** the API is not misused, it is always safe to call [tdsqlite3_blob_close()]
** on *ppBlob after this function it returns.
**
** This function fails with SQLITE_ERROR if any of the following are true:
@@ -6168,70 +7052,80 @@ typedef struct sqlite3_blob sqlite3_blob;
**
** ^Unless it returns SQLITE_MISUSE, this function sets the
** [database connection] error code and message accessible via
-** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions.
+** [tdsqlite3_errcode()] and [tdsqlite3_errmsg()] and related functions.
**
+** A BLOB referenced by tdsqlite3_blob_open() may be read using the
+** [tdsqlite3_blob_read()] interface and modified by using
+** [tdsqlite3_blob_write()]. The [BLOB handle] can be moved to a
+** different row of the same table using the [tdsqlite3_blob_reopen()]
+** interface. However, the column, table, or database of a [BLOB handle]
+** cannot be changed after the [BLOB handle] is opened.
**
** ^(If the row that a BLOB handle points to is modified by an
** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects
** then the BLOB handle is marked as "expired".
** This is true if any column of the row is changed, even a column
** other than the one the BLOB handle is open on.)^
-** ^Calls to [sqlite3_blob_read()] and [sqlite3_blob_write()] for
+** ^Calls to [tdsqlite3_blob_read()] and [tdsqlite3_blob_write()] for
** an expired BLOB handle fail with a return code of [SQLITE_ABORT].
** ^(Changes written into a BLOB prior to the BLOB expiring are not
** rolled back by the expiration of the BLOB. Such changes will eventually
** commit if the transaction continues to completion.)^
**
-** ^Use the [sqlite3_blob_bytes()] interface to determine the size of
+** ^Use the [tdsqlite3_blob_bytes()] interface to determine the size of
** the opened blob. ^The size of a blob may not be changed by this
** interface. Use the [UPDATE] SQL command to change the size of a
** blob.
**
-** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces
+** ^The [tdsqlite3_bind_zeroblob()] and [tdsqlite3_result_zeroblob()] interfaces
** and the built-in [zeroblob] SQL function may be used to create a
** zero-filled blob to read or write using the incremental-blob interface.
**
** To avoid a resource leak, every open [BLOB handle] should eventually
-** be released by a call to [sqlite3_blob_close()].
+** be released by a call to [tdsqlite3_blob_close()].
+**
+** See also: [tdsqlite3_blob_close()],
+** [tdsqlite3_blob_reopen()], [tdsqlite3_blob_read()],
+** [tdsqlite3_blob_bytes()], [tdsqlite3_blob_write()].
*/
-SQLITE_API int sqlite3_blob_open(
- sqlite3*,
+SQLITE_API int tdsqlite3_blob_open(
+ tdsqlite3*,
const char *zDb,
const char *zTable,
const char *zColumn,
- sqlite3_int64 iRow,
+ tdsqlite3_int64 iRow,
int flags,
- sqlite3_blob **ppBlob
+ tdsqlite3_blob **ppBlob
);
/*
** CAPI3REF: Move a BLOB Handle to a New Row
-** METHOD: sqlite3_blob
+** METHOD: tdsqlite3_blob
**
-** ^This function is used to move an existing blob handle so that it points
+** ^This function is used to move an existing [BLOB handle] so that it points
** to a different row of the same database table. ^The new row is identified
** by the rowid value passed as the second argument. Only the row can be
** changed. ^The database, table and column on which the blob handle is open
-** remain the same. Moving an existing blob handle to a new row can be
+** remain the same. Moving an existing [BLOB handle] to a new row is
** faster than closing the existing handle and opening a new one.
**
-** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] -
+** ^(The new row must meet the same criteria as for [tdsqlite3_blob_open()] -
** it must exist and there must be either a blob or text value stored in
** the nominated column.)^ ^If the new row is not present in the table, or if
** it does not contain a blob or text value, or if another error occurs, an
** SQLite error code is returned and the blob handle is considered aborted.
-** ^All subsequent calls to [sqlite3_blob_read()], [sqlite3_blob_write()] or
-** [sqlite3_blob_reopen()] on an aborted blob handle immediately return
-** SQLITE_ABORT. ^Calling [sqlite3_blob_bytes()] on an aborted blob handle
+** ^All subsequent calls to [tdsqlite3_blob_read()], [tdsqlite3_blob_write()] or
+** [tdsqlite3_blob_reopen()] on an aborted blob handle immediately return
+** SQLITE_ABORT. ^Calling [tdsqlite3_blob_bytes()] on an aborted blob handle
** always returns zero.
**
** ^This function sets the database handle error code and message.
*/
-SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
+SQLITE_API int tdsqlite3_blob_reopen(tdsqlite3_blob *, tdsqlite3_int64);
/*
** CAPI3REF: Close A BLOB Handle
-** DESTRUCTOR: sqlite3_blob
+** DESTRUCTOR: tdsqlite3_blob
**
** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed
** unconditionally. Even if this routine returns an error code, the
@@ -6246,15 +7140,15 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
** Calling this function with an argument that is not a NULL pointer or an
** open blob handle results in undefined behaviour. ^Calling this routine
** with a null pointer (such as would be returned by a failed call to
-** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function
+** [tdsqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function
** is passed a valid open blob handle, the values returned by the
-** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning.
+** tdsqlite3_errcode() and tdsqlite3_errmsg() functions are set before returning.
*/
-SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
+SQLITE_API int tdsqlite3_blob_close(tdsqlite3_blob *);
/*
** CAPI3REF: Return The Size Of An Open BLOB
-** METHOD: sqlite3_blob
+** METHOD: tdsqlite3_blob
**
** ^Returns the size in bytes of the BLOB accessible via the
** successfully opened [BLOB handle] in its only argument. ^The
@@ -6262,15 +7156,15 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
** blob content; they cannot change the size of a blob.
**
** This routine only works on a [BLOB handle] which has been created
-** by a prior successful call to [sqlite3_blob_open()] and which has not
-** been closed by [sqlite3_blob_close()]. Passing any other pointer in
+** by a prior successful call to [tdsqlite3_blob_open()] and which has not
+** been closed by [tdsqlite3_blob_close()]. Passing any other pointer in
** to this routine results in undefined and probably undesirable behavior.
*/
-SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *);
+SQLITE_API int tdsqlite3_blob_bytes(tdsqlite3_blob *);
/*
** CAPI3REF: Read Data From A BLOB Incrementally
-** METHOD: sqlite3_blob
+** METHOD: tdsqlite3_blob
**
** ^(This function is used to read data from an open [BLOB handle] into a
** caller-supplied buffer. N bytes of data are copied into buffer Z
@@ -6280,39 +7174,39 @@ SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *);
** [SQLITE_ERROR] is returned and no data is read. ^If N or iOffset is
** less than zero, [SQLITE_ERROR] is returned and no data is read.
** ^The size of the blob (and hence the maximum value of N+iOffset)
-** can be determined using the [sqlite3_blob_bytes()] interface.
+** can be determined using the [tdsqlite3_blob_bytes()] interface.
**
** ^An attempt to read from an expired [BLOB handle] fails with an
** error code of [SQLITE_ABORT].
**
-** ^(On success, sqlite3_blob_read() returns SQLITE_OK.
+** ^(On success, tdsqlite3_blob_read() returns SQLITE_OK.
** Otherwise, an [error code] or an [extended error code] is returned.)^
**
** This routine only works on a [BLOB handle] which has been created
-** by a prior successful call to [sqlite3_blob_open()] and which has not
-** been closed by [sqlite3_blob_close()]. Passing any other pointer in
+** by a prior successful call to [tdsqlite3_blob_open()] and which has not
+** been closed by [tdsqlite3_blob_close()]. Passing any other pointer in
** to this routine results in undefined and probably undesirable behavior.
**
-** See also: [sqlite3_blob_write()].
+** See also: [tdsqlite3_blob_write()].
*/
-SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
+SQLITE_API int tdsqlite3_blob_read(tdsqlite3_blob *, void *Z, int N, int iOffset);
/*
** CAPI3REF: Write Data Into A BLOB Incrementally
-** METHOD: sqlite3_blob
+** METHOD: tdsqlite3_blob
**
** ^(This function is used to write data into an open [BLOB handle] from a
** caller-supplied buffer. N bytes of data are copied from the buffer Z
** into the open BLOB, starting at offset iOffset.)^
**
-** ^(On success, sqlite3_blob_write() returns SQLITE_OK.
+** ^(On success, tdsqlite3_blob_write() returns SQLITE_OK.
** Otherwise, an [error code] or an [extended error code] is returned.)^
** ^Unless SQLITE_MISUSE is returned, this function sets the
** [database connection] error code and message accessible via
-** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions.
+** [tdsqlite3_errcode()] and [tdsqlite3_errmsg()] and related functions.
**
** ^If the [BLOB handle] passed as the first argument was not opened for
-** writing (the flags parameter to [sqlite3_blob_open()] was zero),
+** writing (the flags parameter to [tdsqlite3_blob_open()] was zero),
** this function returns [SQLITE_READONLY].
**
** This function may only modify the contents of the BLOB; it is
@@ -6320,7 +7214,7 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
** ^If offset iOffset is less than N bytes from the end of the BLOB,
** [SQLITE_ERROR] is returned and no data is written. The size of the
** BLOB (and hence the maximum value of N+iOffset) can be determined
-** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less
+** using the [tdsqlite3_blob_bytes()] interface. ^If N or iOffset are less
** than zero [SQLITE_ERROR] is returned and no data is written.
**
** ^An attempt to write to an expired [BLOB handle] fails with an
@@ -6331,31 +7225,31 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
** or by other independent statements.
**
** This routine only works on a [BLOB handle] which has been created
-** by a prior successful call to [sqlite3_blob_open()] and which has not
-** been closed by [sqlite3_blob_close()]. Passing any other pointer in
+** by a prior successful call to [tdsqlite3_blob_open()] and which has not
+** been closed by [tdsqlite3_blob_close()]. Passing any other pointer in
** to this routine results in undefined and probably undesirable behavior.
**
-** See also: [sqlite3_blob_read()].
+** See also: [tdsqlite3_blob_read()].
*/
-SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset);
+SQLITE_API int tdsqlite3_blob_write(tdsqlite3_blob *, const void *z, int n, int iOffset);
/*
** CAPI3REF: Virtual File System Objects
**
-** A virtual filesystem (VFS) is an [sqlite3_vfs] object
+** A virtual filesystem (VFS) is an [tdsqlite3_vfs] object
** that SQLite uses to interact
** with the underlying operating system. Most SQLite builds come with a
** single default VFS that is appropriate for the host computer.
** New VFSes can be registered and existing VFSes can be unregistered.
** The following interfaces are provided.
**
-** ^The sqlite3_vfs_find() interface returns a pointer to a VFS given its name.
+** ^The tdsqlite3_vfs_find() interface returns a pointer to a VFS given its name.
** ^Names are case sensitive.
** ^Names are zero-terminated UTF-8 strings.
** ^If there is no match, a NULL pointer is returned.
** ^If zVfsName is NULL then the default VFS is returned.
**
-** ^New VFSes are registered with sqlite3_vfs_register().
+** ^New VFSes are registered with tdsqlite3_vfs_register().
** ^Each new VFS becomes the default VFS if the makeDflt flag is set.
** ^The same VFS can be registered multiple times without injury.
** ^To make an existing VFS into the default VFS, register it again
@@ -6364,13 +7258,13 @@ SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOff
** VFS is registered with a name that is NULL or an empty string,
** then the behavior is undefined.
**
-** ^Unregister a VFS with the sqlite3_vfs_unregister() interface.
+** ^Unregister a VFS with the tdsqlite3_vfs_unregister() interface.
** ^(If the default VFS is unregistered, another VFS is chosen as
** the default. The choice for the new VFS is arbitrary.)^
*/
-SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);
-SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);
-SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
+SQLITE_API tdsqlite3_vfs *tdsqlite3_vfs_find(const char *zVfsName);
+SQLITE_API int tdsqlite3_vfs_register(tdsqlite3_vfs*, int makeDflt);
+SQLITE_API int tdsqlite3_vfs_unregister(tdsqlite3_vfs*);
/*
** CAPI3REF: Mutexes
@@ -6401,14 +7295,14 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex
** implementation is included with the library. In this case the
** application must supply a custom mutex implementation using the
-** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function
-** before calling sqlite3_initialize() or any other public sqlite3_
-** function that calls sqlite3_initialize().
+** [SQLITE_CONFIG_MUTEX] option of the tdsqlite3_config() function
+** before calling tdsqlite3_initialize() or any other public tdsqlite3_
+** function that calls tdsqlite3_initialize().
**
-** ^The sqlite3_mutex_alloc() routine allocates a new
-** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
+** ^The tdsqlite3_mutex_alloc() routine allocates a new
+** mutex and returns a pointer to it. ^The tdsqlite3_mutex_alloc()
** routine returns NULL if it is unable to allocate the requested
-** mutex. The argument to sqlite3_mutex_alloc() must one of these
+** mutex. The argument to tdsqlite3_mutex_alloc() must one of these
** integer constants:
**
** <ul>
@@ -6429,7 +7323,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** </ul>
**
** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE)
-** cause sqlite3_mutex_alloc() to create
+** cause tdsqlite3_mutex_alloc() to create
** a new mutex. ^The new mutex is recursive when SQLITE_MUTEX_RECURSIVE
** is used but not necessarily so when SQLITE_MUTEX_FAST is used.
** The mutex implementation does not need to make a distinction
@@ -6439,7 +7333,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** implementation is available on the host platform, the mutex subsystem
** might return such a mutex in response to SQLITE_MUTEX_FAST.
**
-** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other
+** ^The other allowed parameters to tdsqlite3_mutex_alloc() (anything other
** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return
** a pointer to a static preexisting mutex. ^Nine static mutexes are
** used by the current version of SQLite. Future versions of SQLite
@@ -6449,19 +7343,19 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** SQLITE_MUTEX_RECURSIVE.
**
** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST
-** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc()
+** or SQLITE_MUTEX_RECURSIVE) is used then tdsqlite3_mutex_alloc()
** returns a different mutex on every call. ^For the static
** mutex types, the same mutex is returned on every call that has
** the same type number.
**
-** ^The sqlite3_mutex_free() routine deallocates a previously
+** ^The tdsqlite3_mutex_free() routine deallocates a previously
** allocated dynamic mutex. Attempting to deallocate a static
** mutex results in undefined behavior.
**
-** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt
+** ^The tdsqlite3_mutex_enter() and tdsqlite3_mutex_try() routines attempt
** to enter a mutex. ^If another thread is already within the mutex,
-** sqlite3_mutex_enter() will block and sqlite3_mutex_try() will return
-** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK]
+** tdsqlite3_mutex_enter() will block and tdsqlite3_mutex_try() will return
+** SQLITE_BUSY. ^The tdsqlite3_mutex_try() interface returns [SQLITE_OK]
** upon successful entry. ^(Mutexes created using
** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread.
** In such cases, the
@@ -6470,27 +7364,27 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined.
**
** ^(Some systems (for example, Windows 95) do not support the operation
-** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try()
+** implemented by tdsqlite3_mutex_try(). On those systems, tdsqlite3_mutex_try()
** will always return SQLITE_BUSY. The SQLite core only ever uses
-** sqlite3_mutex_try() as an optimization so this is acceptable
+** tdsqlite3_mutex_try() as an optimization so this is acceptable
** behavior.)^
**
-** ^The sqlite3_mutex_leave() routine exits a mutex that was
+** ^The tdsqlite3_mutex_leave() routine exits a mutex that was
** previously entered by the same thread. The behavior
** is undefined if the mutex is not currently entered by the
** calling thread or is not currently allocated.
**
-** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or
-** sqlite3_mutex_leave() is a NULL pointer, then all three routines
+** ^If the argument to tdsqlite3_mutex_enter(), tdsqlite3_mutex_try(), or
+** tdsqlite3_mutex_leave() is a NULL pointer, then all three routines
** behave as no-ops.
**
-** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()].
+** See also: [tdsqlite3_mutex_held()] and [tdsqlite3_mutex_notheld()].
*/
-SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int);
-SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*);
-SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*);
-SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*);
-SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
+SQLITE_API tdsqlite3_mutex *tdsqlite3_mutex_alloc(int);
+SQLITE_API void tdsqlite3_mutex_free(tdsqlite3_mutex*);
+SQLITE_API void tdsqlite3_mutex_enter(tdsqlite3_mutex*);
+SQLITE_API int tdsqlite3_mutex_try(tdsqlite3_mutex*);
+SQLITE_API void tdsqlite3_mutex_leave(tdsqlite3_mutex*);
/*
** CAPI3REF: Mutex Methods Object
@@ -6503,41 +7397,41 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
** implementation for specialized deployments or systems for which SQLite
** does not provide a suitable implementation. In this case, the application
** creates and populates an instance of this structure to pass
-** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option.
+** to tdsqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option.
** Additionally, an instance of this structure can be used as an
** output variable when querying the system for the current mutex
** implementation, using the [SQLITE_CONFIG_GETMUTEX] option.
**
** ^The xMutexInit method defined by this structure is invoked as
-** part of system initialization by the sqlite3_initialize() function.
+** part of system initialization by the tdsqlite3_initialize() function.
** ^The xMutexInit routine is called by SQLite exactly once for each
-** effective call to [sqlite3_initialize()].
+** effective call to [tdsqlite3_initialize()].
**
** ^The xMutexEnd method defined by this structure is invoked as
-** part of system shutdown by the sqlite3_shutdown() function. The
+** part of system shutdown by the tdsqlite3_shutdown() function. The
** implementation of this method is expected to release all outstanding
** resources obtained by the mutex methods implementation, especially
** those obtained by the xMutexInit method. ^The xMutexEnd()
-** interface is invoked exactly once for each call to [sqlite3_shutdown()].
+** interface is invoked exactly once for each call to [tdsqlite3_shutdown()].
**
** ^(The remaining seven methods defined by this structure (xMutexAlloc,
** xMutexFree, xMutexEnter, xMutexTry, xMutexLeave, xMutexHeld and
** xMutexNotheld) implement the following interfaces (respectively):
**
** <ul>
-** <li> [sqlite3_mutex_alloc()] </li>
-** <li> [sqlite3_mutex_free()] </li>
-** <li> [sqlite3_mutex_enter()] </li>
-** <li> [sqlite3_mutex_try()] </li>
-** <li> [sqlite3_mutex_leave()] </li>
-** <li> [sqlite3_mutex_held()] </li>
-** <li> [sqlite3_mutex_notheld()] </li>
+** <li> [tdsqlite3_mutex_alloc()] </li>
+** <li> [tdsqlite3_mutex_free()] </li>
+** <li> [tdsqlite3_mutex_enter()] </li>
+** <li> [tdsqlite3_mutex_try()] </li>
+** <li> [tdsqlite3_mutex_leave()] </li>
+** <li> [tdsqlite3_mutex_held()] </li>
+** <li> [tdsqlite3_mutex_notheld()] </li>
** </ul>)^
**
-** The only difference is that the public sqlite3_XXX functions enumerated
+** The only difference is that the public tdsqlite3_XXX functions enumerated
** above silently ignore any invocations that pass a NULL pointer instead
** of a valid mutex handle. The implementations of the methods defined
-** by this structure are not required to handle this case, the results
+** by this structure are not required to handle this case. The results
** of passing a NULL pointer instead of a valid mutex handle are undefined
** (i.e. it is acceptable to provide an implementation that segfaults if
** it is passed a NULL pointer).
@@ -6547,33 +7441,33 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*);
** intervening calls to xMutexEnd(). Second and subsequent calls to
** xMutexInit() must be no-ops.
**
-** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()]
+** xMutexInit() must not use SQLite memory allocation ([tdsqlite3_malloc()]
** and its associates). Similarly, xMutexAlloc() must not use SQLite memory
** allocation for a static mutex. ^However xMutexAlloc() may use SQLite
** memory allocation for a fast or recursive mutex.
**
-** ^SQLite will invoke the xMutexEnd() method when [sqlite3_shutdown()] is
+** ^SQLite will invoke the xMutexEnd() method when [tdsqlite3_shutdown()] is
** called, but only if the prior call to xMutexInit returned SQLITE_OK.
** If xMutexInit fails in any way, it is expected to clean up after itself
** prior to returning.
*/
-typedef struct sqlite3_mutex_methods sqlite3_mutex_methods;
-struct sqlite3_mutex_methods {
+typedef struct tdsqlite3_mutex_methods tdsqlite3_mutex_methods;
+struct tdsqlite3_mutex_methods {
int (*xMutexInit)(void);
int (*xMutexEnd)(void);
- sqlite3_mutex *(*xMutexAlloc)(int);
- void (*xMutexFree)(sqlite3_mutex *);
- void (*xMutexEnter)(sqlite3_mutex *);
- int (*xMutexTry)(sqlite3_mutex *);
- void (*xMutexLeave)(sqlite3_mutex *);
- int (*xMutexHeld)(sqlite3_mutex *);
- int (*xMutexNotheld)(sqlite3_mutex *);
+ tdsqlite3_mutex *(*xMutexAlloc)(int);
+ void (*xMutexFree)(tdsqlite3_mutex *);
+ void (*xMutexEnter)(tdsqlite3_mutex *);
+ int (*xMutexTry)(tdsqlite3_mutex *);
+ void (*xMutexLeave)(tdsqlite3_mutex *);
+ int (*xMutexHeld)(tdsqlite3_mutex *);
+ int (*xMutexNotheld)(tdsqlite3_mutex *);
};
/*
** CAPI3REF: Mutex Verification Routines
**
-** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines
+** The tdsqlite3_mutex_held() and tdsqlite3_mutex_notheld() routines
** are intended for use inside assert() statements. The SQLite core
** never uses these routines except inside an assert() and applications
** are advised to follow the lead of the core. The SQLite core only
@@ -6590,24 +7484,24 @@ struct sqlite3_mutex_methods {
** versions of these routines, it should at least provide stubs that always
** return true so that one does not get spurious assertion failures.
**
-** If the argument to sqlite3_mutex_held() is a NULL pointer then
+** If the argument to tdsqlite3_mutex_held() is a NULL pointer then
** the routine should return 1. This seems counter-intuitive since
** clearly the mutex cannot be held if it does not exist. But
** the reason the mutex does not exist is because the build is not
** using mutexes. And we do not want the assert() containing the
-** call to sqlite3_mutex_held() to fail, so a non-zero return is
-** the appropriate thing to do. The sqlite3_mutex_notheld()
+** call to tdsqlite3_mutex_held() to fail, so a non-zero return is
+** the appropriate thing to do. The tdsqlite3_mutex_notheld()
** interface should also return 1 when given a NULL pointer.
*/
#ifndef NDEBUG
-SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*);
-SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
+SQLITE_API int tdsqlite3_mutex_held(tdsqlite3_mutex*);
+SQLITE_API int tdsqlite3_mutex_notheld(tdsqlite3_mutex*);
#endif
/*
** CAPI3REF: Mutex Types
**
-** The [sqlite3_mutex_alloc()] interface takes a single argument
+** The [tdsqlite3_mutex_alloc()] interface takes a single argument
** which is one of these integer constants.
**
** The set of static mutexes may change from one SQLite release to the
@@ -6617,13 +7511,13 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
#define SQLITE_MUTEX_FAST 0
#define SQLITE_MUTEX_RECURSIVE 1
#define SQLITE_MUTEX_STATIC_MASTER 2
-#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */
+#define SQLITE_MUTEX_STATIC_MEM 3 /* tdsqlite3_malloc() */
#define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */
-#define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */
-#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */
+#define SQLITE_MUTEX_STATIC_OPEN 4 /* tdsqlite3BtreeOpen() */
+#define SQLITE_MUTEX_STATIC_PRNG 5 /* tdsqlite3_randomness() */
#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */
#define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */
-#define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */
+#define SQLITE_MUTEX_STATIC_PMEM 7 /* tdsqlite3PageMalloc() */
#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */
#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */
#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */
@@ -6633,22 +7527,23 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
/*
** CAPI3REF: Retrieve the mutex for a database connection
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^This interface returns a pointer the [sqlite3_mutex] object that
+** ^This interface returns a pointer the [tdsqlite3_mutex] object that
** serializes access to the [database connection] given in the argument
** when the [threading mode] is Serialized.
** ^If the [threading mode] is Single-thread or Multi-thread then this
** routine returns a NULL pointer.
*/
-SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*);
+SQLITE_API tdsqlite3_mutex *tdsqlite3_db_mutex(tdsqlite3*);
/*
** CAPI3REF: Low-Level Control Of Database Files
-** METHOD: sqlite3
+** METHOD: tdsqlite3
+** KEYWORDS: {file control}
**
-** ^The [sqlite3_file_control()] interface makes a direct call to the
-** xFileControl method for the [sqlite3_io_methods] object associated
+** ^The [tdsqlite3_file_control()] interface makes a direct call to the
+** xFileControl method for the [tdsqlite3_io_methods] object associated
** with a particular database identified by the second argument. ^The
** name of the database is "main" for the main database or "temp" for the
** TEMP database, or the name that appears after the AS keyword for
@@ -6660,28 +7555,35 @@ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*);
** the xFileControl method. ^The return value of the xFileControl
** method becomes the return value of this routine.
**
-** ^The SQLITE_FCNTL_FILE_POINTER value for the op parameter causes
-** a pointer to the underlying [sqlite3_file] object to be written into
-** the space pointed to by the 4th parameter. ^The SQLITE_FCNTL_FILE_POINTER
-** case is a short-circuit path which does not actually invoke the
-** underlying sqlite3_io_methods.xFileControl method.
+** A few opcodes for [tdsqlite3_file_control()] are handled directly
+** by the SQLite core and never invoke the
+** tdsqlite3_io_methods.xFileControl method.
+** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes
+** a pointer to the underlying [tdsqlite3_file] object to be written into
+** the space pointed to by the 4th parameter. The
+** [SQLITE_FCNTL_JOURNAL_POINTER] works similarly except that it returns
+** the [tdsqlite3_file] object associated with the journal file instead of
+** the main database. The [SQLITE_FCNTL_VFS_POINTER] opcode returns
+** a pointer to the underlying [tdsqlite3_vfs] object for the file.
+** The [SQLITE_FCNTL_DATA_VERSION] returns the data version counter
+** from the pager.
**
** ^If the second parameter (zDbName) does not match the name of any
** open database file, then SQLITE_ERROR is returned. ^This error
-** code is not remembered and will not be recalled by [sqlite3_errcode()]
-** or [sqlite3_errmsg()]. The underlying xFileControl method might
+** code is not remembered and will not be recalled by [tdsqlite3_errcode()]
+** or [tdsqlite3_errmsg()]. The underlying xFileControl method might
** also return SQLITE_ERROR. There is no way to distinguish between
** an incorrect zDbName and an SQLITE_ERROR return from the underlying
** xFileControl method.
**
-** See also: [SQLITE_FCNTL_LOCKSTATE]
+** See also: [file control opcodes]
*/
-SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*);
+SQLITE_API int tdsqlite3_file_control(tdsqlite3*, const char *zDbName, int op, void*);
/*
** CAPI3REF: Testing Interface
**
-** ^The sqlite3_test_control() interface is used to read out internal
+** ^The tdsqlite3_test_control() interface is used to read out internal
** state of SQLite and to inject faults into SQLite for testing
** purposes. ^The first parameter is an operation code that determines
** the number, meaning, and operation of all subsequent parameters.
@@ -6695,23 +7597,23 @@ SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*
** Unlike most of the SQLite API, this function is not guaranteed to
** operate consistently from one release to the next.
*/
-SQLITE_API int sqlite3_test_control(int op, ...);
+SQLITE_API int tdsqlite3_test_control(int op, ...);
/*
** CAPI3REF: Testing Interface Operation Codes
**
** These constants are the valid operation code parameters used
-** as the first argument to [sqlite3_test_control()].
+** as the first argument to [tdsqlite3_test_control()].
**
** These parameters and their meanings are subject to change
** without notice. These values are for testing purposes only.
** Applications should not use any of these parameters or the
-** [sqlite3_test_control()] interface.
+** [tdsqlite3_test_control()] interface.
*/
#define SQLITE_TESTCTRL_FIRST 5
#define SQLITE_TESTCTRL_PRNG_SAVE 5
#define SQLITE_TESTCTRL_PRNG_RESTORE 6
-#define SQLITE_TESTCTRL_PRNG_RESET 7
+#define SQLITE_TESTCTRL_PRNG_RESET 7 /* NOT USED */
#define SQLITE_TESTCTRL_BITVEC_TEST 8
#define SQLITE_TESTCTRL_FAULT_INSTALL 9
#define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10
@@ -6720,8 +7622,9 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_ALWAYS 13
#define SQLITE_TESTCTRL_RESERVE 14
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
-#define SQLITE_TESTCTRL_ISKEYWORD 16
-#define SQLITE_TESTCTRL_SCRATCHMALLOC 17
+#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
+#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
+#define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19
@@ -6731,7 +7634,194 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_ISINIT 23
#define SQLITE_TESTCTRL_SORTER_MMAP 24
#define SQLITE_TESTCTRL_IMPOSTER 25
-#define SQLITE_TESTCTRL_LAST 25
+#define SQLITE_TESTCTRL_PARSER_COVERAGE 26
+#define SQLITE_TESTCTRL_RESULT_INTREAL 27
+#define SQLITE_TESTCTRL_PRNG_SEED 28
+#define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29
+#define SQLITE_TESTCTRL_LAST 29 /* Largest TESTCTRL */
+
+/*
+** CAPI3REF: SQL Keyword Checking
+**
+** These routines provide access to the set of SQL language keywords
+** recognized by SQLite. Applications can uses these routines to determine
+** whether or not a specific identifier needs to be escaped (for example,
+** by enclosing in double-quotes) so as not to confuse the parser.
+**
+** The tdsqlite3_keyword_count() interface returns the number of distinct
+** keywords understood by SQLite.
+**
+** The tdsqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and
+** makes *Z point to that keyword expressed as UTF8 and writes the number
+** of bytes in the keyword into *L. The string that *Z points to is not
+** zero-terminated. The tdsqlite3_keyword_name(N,Z,L) routine returns
+** SQLITE_OK if N is within bounds and SQLITE_ERROR if not. If either Z
+** or L are NULL or invalid pointers then calls to
+** tdsqlite3_keyword_name(N,Z,L) result in undefined behavior.
+**
+** The tdsqlite3_keyword_check(Z,L) interface checks to see whether or not
+** the L-byte UTF8 identifier that Z points to is a keyword, returning non-zero
+** if it is and zero if not.
+**
+** The parser used by SQLite is forgiving. It is often possible to use
+** a keyword as an identifier as long as such use does not result in a
+** parsing ambiguity. For example, the statement
+** "CREATE TABLE BEGIN(REPLACE,PRAGMA,END);" is accepted by SQLite, and
+** creates a new table named "BEGIN" with three columns named
+** "REPLACE", "PRAGMA", and "END". Nevertheless, best practice is to avoid
+** using keywords as identifiers. Common techniques used to avoid keyword
+** name collisions include:
+** <ul>
+** <li> Put all identifier names inside double-quotes. This is the official
+** SQL way to escape identifier names.
+** <li> Put identifier names inside &#91;...&#93;. This is not standard SQL,
+** but it is what SQL Server does and so lots of programmers use this
+** technique.
+** <li> Begin every identifier with the letter "Z" as no SQL keywords start
+** with "Z".
+** <li> Include a digit somewhere in every identifier name.
+** </ul>
+**
+** Note that the number of keywords understood by SQLite can depend on
+** compile-time options. For example, "VACUUM" is not a keyword if
+** SQLite is compiled with the [-DSQLITE_OMIT_VACUUM] option. Also,
+** new keywords may be added to future releases of SQLite.
+*/
+SQLITE_API int tdsqlite3_keyword_count(void);
+SQLITE_API int tdsqlite3_keyword_name(int,const char**,int*);
+SQLITE_API int tdsqlite3_keyword_check(const char*,int);
+
+/*
+** CAPI3REF: Dynamic String Object
+** KEYWORDS: {dynamic string}
+**
+** An instance of the tdsqlite3_str object contains a dynamically-sized
+** string under construction.
+**
+** The lifecycle of an tdsqlite3_str object is as follows:
+** <ol>
+** <li> ^The tdsqlite3_str object is created using [tdsqlite3_str_new()].
+** <li> ^Text is appended to the tdsqlite3_str object using various
+** methods, such as [tdsqlite3_str_appendf()].
+** <li> ^The tdsqlite3_str object is destroyed and the string it created
+** is returned using the [tdsqlite3_str_finish()] interface.
+** </ol>
+*/
+typedef struct tdsqlite3_str tdsqlite3_str;
+
+/*
+** CAPI3REF: Create A New Dynamic String Object
+** CONSTRUCTOR: tdsqlite3_str
+**
+** ^The [tdsqlite3_str_new(D)] interface allocates and initializes
+** a new [tdsqlite3_str] object. To avoid memory leaks, the object returned by
+** [tdsqlite3_str_new()] must be freed by a subsequent call to
+** [tdsqlite3_str_finish(X)].
+**
+** ^The [tdsqlite3_str_new(D)] interface always returns a pointer to a
+** valid [tdsqlite3_str] object, though in the event of an out-of-memory
+** error the returned object might be a special singleton that will
+** silently reject new text, always return SQLITE_NOMEM from
+** [tdsqlite3_str_errcode()], always return 0 for
+** [tdsqlite3_str_length()], and always return NULL from
+** [tdsqlite3_str_finish(X)]. It is always safe to use the value
+** returned by [tdsqlite3_str_new(D)] as the tdsqlite3_str parameter
+** to any of the other [tdsqlite3_str] methods.
+**
+** The D parameter to [tdsqlite3_str_new(D)] may be NULL. If the
+** D parameter in [tdsqlite3_str_new(D)] is not NULL, then the maximum
+** length of the string contained in the [tdsqlite3_str] object will be
+** the value set for [tdsqlite3_limit](D,[SQLITE_LIMIT_LENGTH]) instead
+** of [SQLITE_MAX_LENGTH].
+*/
+SQLITE_API tdsqlite3_str *tdsqlite3_str_new(tdsqlite3*);
+
+/*
+** CAPI3REF: Finalize A Dynamic String
+** DESTRUCTOR: tdsqlite3_str
+**
+** ^The [tdsqlite3_str_finish(X)] interface destroys the tdsqlite3_str object X
+** and returns a pointer to a memory buffer obtained from [tdsqlite3_malloc64()]
+** that contains the constructed string. The calling application should
+** pass the returned value to [tdsqlite3_free()] to avoid a memory leak.
+** ^The [tdsqlite3_str_finish(X)] interface may return a NULL pointer if any
+** errors were encountered during construction of the string. ^The
+** [tdsqlite3_str_finish(X)] interface will also return a NULL pointer if the
+** string in [tdsqlite3_str] object X is zero bytes long.
+*/
+SQLITE_API char *tdsqlite3_str_finish(tdsqlite3_str*);
+
+/*
+** CAPI3REF: Add Content To A Dynamic String
+** METHOD: tdsqlite3_str
+**
+** These interfaces add content to an tdsqlite3_str object previously obtained
+** from [tdsqlite3_str_new()].
+**
+** ^The [tdsqlite3_str_appendf(X,F,...)] and
+** [tdsqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf]
+** functionality of SQLite to append formatted text onto the end of
+** [tdsqlite3_str] object X.
+**
+** ^The [tdsqlite3_str_append(X,S,N)] method appends exactly N bytes from string S
+** onto the end of the [tdsqlite3_str] object X. N must be non-negative.
+** S must contain at least N non-zero bytes of content. To append a
+** zero-terminated string in its entirety, use the [tdsqlite3_str_appendall()]
+** method instead.
+**
+** ^The [tdsqlite3_str_appendall(X,S)] method appends the complete content of
+** zero-terminated string S onto the end of [tdsqlite3_str] object X.
+**
+** ^The [tdsqlite3_str_appendchar(X,N,C)] method appends N copies of the
+** single-byte character C onto the end of [tdsqlite3_str] object X.
+** ^This method can be used, for example, to add whitespace indentation.
+**
+** ^The [tdsqlite3_str_reset(X)] method resets the string under construction
+** inside [tdsqlite3_str] object X back to zero bytes in length.
+**
+** These methods do not return a result code. ^If an error occurs, that fact
+** is recorded in the [tdsqlite3_str] object and can be recovered by a
+** subsequent call to [tdsqlite3_str_errcode(X)].
+*/
+SQLITE_API void tdsqlite3_str_appendf(tdsqlite3_str*, const char *zFormat, ...);
+SQLITE_API void tdsqlite3_str_vappendf(tdsqlite3_str*, const char *zFormat, va_list);
+SQLITE_API void tdsqlite3_str_append(tdsqlite3_str*, const char *zIn, int N);
+SQLITE_API void tdsqlite3_str_appendall(tdsqlite3_str*, const char *zIn);
+SQLITE_API void tdsqlite3_str_appendchar(tdsqlite3_str*, int N, char C);
+SQLITE_API void tdsqlite3_str_reset(tdsqlite3_str*);
+
+/*
+** CAPI3REF: Status Of A Dynamic String
+** METHOD: tdsqlite3_str
+**
+** These interfaces return the current status of an [tdsqlite3_str] object.
+**
+** ^If any prior errors have occurred while constructing the dynamic string
+** in tdsqlite3_str X, then the [tdsqlite3_str_errcode(X)] method will return
+** an appropriate error code. ^The [tdsqlite3_str_errcode(X)] method returns
+** [SQLITE_NOMEM] following any out-of-memory error, or
+** [SQLITE_TOOBIG] if the size of the dynamic string exceeds
+** [SQLITE_MAX_LENGTH], or [SQLITE_OK] if there have been no errors.
+**
+** ^The [tdsqlite3_str_length(X)] method returns the current length, in bytes,
+** of the dynamic string under construction in [tdsqlite3_str] object X.
+** ^The length returned by [tdsqlite3_str_length(X)] does not include the
+** zero-termination byte.
+**
+** ^The [tdsqlite3_str_value(X)] method returns a pointer to the current
+** content of the dynamic string under construction in X. The value
+** returned by [tdsqlite3_str_value(X)] is managed by the tdsqlite3_str object X
+** and might be freed or altered by any subsequent method on the same
+** [tdsqlite3_str] object. Applications must not used the pointer returned
+** [tdsqlite3_str_value(X)] after any subsequent method call on the same
+** object. ^Applications may change the content of the string returned
+** by [tdsqlite3_str_value(X)] as long as they do not write into any bytes
+** outside the range of 0 to [tdsqlite3_str_length(X)] and do not read or
+** write any byte after any subsequent tdsqlite3_str method call.
+*/
+SQLITE_API int tdsqlite3_str_errcode(tdsqlite3_str*);
+SQLITE_API int tdsqlite3_str_length(tdsqlite3_str*);
+SQLITE_API char *tdsqlite3_str_value(tdsqlite3_str*);
/*
** CAPI3REF: SQLite Runtime Status
@@ -6750,20 +7840,20 @@ SQLITE_API int sqlite3_test_control(int op, ...);
** ^(Other parameters record only the highwater mark and not the current
** value. For these latter parameters nothing is written into *pCurrent.)^
**
-** ^The sqlite3_status() and sqlite3_status64() routines return
+** ^The tdsqlite3_status() and tdsqlite3_status64() routines return
** SQLITE_OK on success and a non-zero [error code] on failure.
**
** If either the current value or the highwater mark is too large to
** be represented by a 32-bit integer, then the values returned by
-** sqlite3_status() are undefined.
+** tdsqlite3_status() are undefined.
**
-** See also: [sqlite3_db_status()]
+** See also: [tdsqlite3_db_status()]
*/
-SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag);
-SQLITE_API int sqlite3_status64(
+SQLITE_API int tdsqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag);
+SQLITE_API int tdsqlite3_status64(
int op,
- sqlite3_int64 *pCurrent,
- sqlite3_int64 *pHighwater,
+ tdsqlite3_int64 *pCurrent,
+ tdsqlite3_int64 *pHighwater,
int resetFlag
);
@@ -6773,24 +7863,23 @@ SQLITE_API int sqlite3_status64(
** KEYWORDS: {status parameters}
**
** These integer constants designate various run-time status parameters
-** that can be returned by [sqlite3_status()].
+** that can be returned by [tdsqlite3_status()].
**
** <dl>
** [[SQLITE_STATUS_MEMORY_USED]] ^(<dt>SQLITE_STATUS_MEMORY_USED</dt>
** <dd>This parameter is the current amount of memory checked out
-** using [sqlite3_malloc()], either directly or indirectly. The
-** figure includes calls made to [sqlite3_malloc()] by the application
-** and internal memory usage by the SQLite library. Scratch memory
-** controlled by [SQLITE_CONFIG_SCRATCH] and auxiliary page-cache
+** using [tdsqlite3_malloc()], either directly or indirectly. The
+** figure includes calls made to [tdsqlite3_malloc()] by the application
+** and internal memory usage by the SQLite library. Auxiliary page-cache
** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in
** this parameter. The amount returned is the sum of the allocation
-** sizes as reported by the xSize method in [sqlite3_mem_methods].</dd>)^
+** sizes as reported by the xSize method in [tdsqlite3_mem_methods].</dd>)^
**
** [[SQLITE_STATUS_MALLOC_SIZE]] ^(<dt>SQLITE_STATUS_MALLOC_SIZE</dt>
** <dd>This parameter records the largest memory allocation request
-** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their
+** handed to [tdsqlite3_malloc()] or [tdsqlite3_realloc()] (or their
** internal equivalents). Only the value returned in the
-** *pHighwater parameter to [sqlite3_status()] is of interest.
+** *pHighwater parameter to [tdsqlite3_status()] is of interest.
** The value written into the *pCurrent parameter is undefined.</dd>)^
**
** [[SQLITE_STATUS_MALLOC_COUNT]] ^(<dt>SQLITE_STATUS_MALLOC_COUNT</dt>
@@ -6807,7 +7896,7 @@ SQLITE_API int sqlite3_status64(
** ^(<dt>SQLITE_STATUS_PAGECACHE_OVERFLOW</dt>
** <dd>This parameter returns the number of bytes of page cache
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
-** buffer and where forced to overflow to [sqlite3_malloc()]. The
+** buffer and where forced to overflow to [tdsqlite3_malloc()]. The
** returned value includes allocations that overflowed because they
** where too large (they were larger than the "sz" parameter to
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
@@ -6815,33 +7904,18 @@ SQLITE_API int sqlite3_status64(
**
** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(<dt>SQLITE_STATUS_PAGECACHE_SIZE</dt>
** <dd>This parameter records the largest memory allocation request
-** handed to [pagecache memory allocator]. Only the value returned in the
-** *pHighwater parameter to [sqlite3_status()] is of interest.
+** handed to the [pagecache memory allocator]. Only the value returned in the
+** *pHighwater parameter to [tdsqlite3_status()] is of interest.
** The value written into the *pCurrent parameter is undefined.</dd>)^
**
-** [[SQLITE_STATUS_SCRATCH_USED]] ^(<dt>SQLITE_STATUS_SCRATCH_USED</dt>
-** <dd>This parameter returns the number of allocations used out of the
-** [scratch memory allocator] configured using
-** [SQLITE_CONFIG_SCRATCH]. The value returned is in allocations, not
-** in bytes. Since a single thread may only have one scratch allocation
-** outstanding at time, this parameter also reports the number of threads
-** using scratch memory at the same time.</dd>)^
+** [[SQLITE_STATUS_SCRATCH_USED]] <dt>SQLITE_STATUS_SCRATCH_USED</dt>
+** <dd>No longer used.</dd>
**
** [[SQLITE_STATUS_SCRATCH_OVERFLOW]] ^(<dt>SQLITE_STATUS_SCRATCH_OVERFLOW</dt>
-** <dd>This parameter returns the number of bytes of scratch memory
-** allocation which could not be satisfied by the [SQLITE_CONFIG_SCRATCH]
-** buffer and where forced to overflow to [sqlite3_malloc()]. The values
-** returned include overflows because the requested allocation was too
-** larger (that is, because the requested allocation was larger than the
-** "sz" parameter to [SQLITE_CONFIG_SCRATCH]) and because no scratch buffer
-** slots were available.
-** </dd>)^
+** <dd>No longer used.</dd>
**
-** [[SQLITE_STATUS_SCRATCH_SIZE]] ^(<dt>SQLITE_STATUS_SCRATCH_SIZE</dt>
-** <dd>This parameter records the largest memory allocation request
-** handed to [scratch memory allocator]. Only the value returned in the
-** *pHighwater parameter to [sqlite3_status()] is of interest.
-** The value written into the *pCurrent parameter is undefined.</dd>)^
+** [[SQLITE_STATUS_SCRATCH_SIZE]] <dt>SQLITE_STATUS_SCRATCH_SIZE</dt>
+** <dd>No longer used.</dd>
**
** [[SQLITE_STATUS_PARSER_STACK]] ^(<dt>SQLITE_STATUS_PARSER_STACK</dt>
** <dd>The *pHighwater parameter records the deepest parser stack.
@@ -6854,17 +7928,17 @@ SQLITE_API int sqlite3_status64(
#define SQLITE_STATUS_MEMORY_USED 0
#define SQLITE_STATUS_PAGECACHE_USED 1
#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2
-#define SQLITE_STATUS_SCRATCH_USED 3
-#define SQLITE_STATUS_SCRATCH_OVERFLOW 4
+#define SQLITE_STATUS_SCRATCH_USED 3 /* NOT USED */
+#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 /* NOT USED */
#define SQLITE_STATUS_MALLOC_SIZE 5
#define SQLITE_STATUS_PARSER_STACK 6
#define SQLITE_STATUS_PAGECACHE_SIZE 7
-#define SQLITE_STATUS_SCRATCH_SIZE 8
+#define SQLITE_STATUS_SCRATCH_SIZE 8 /* NOT USED */
#define SQLITE_STATUS_MALLOC_COUNT 9
/*
** CAPI3REF: Database Connection Status
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^This interface is used to retrieve runtime status information
** about a single [database connection]. ^The first argument is the
@@ -6880,24 +7954,24 @@ SQLITE_API int sqlite3_status64(
** the resetFlg is true, then the highest instantaneous value is
** reset back down to the current value.
**
-** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a
+** ^The tdsqlite3_db_status() routine returns SQLITE_OK on success and a
** non-zero [error code] on failure.
**
-** See also: [sqlite3_status()] and [sqlite3_stmt_status()].
+** See also: [tdsqlite3_status()] and [tdsqlite3_stmt_status()].
*/
-SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
+SQLITE_API int tdsqlite3_db_status(tdsqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
/*
** CAPI3REF: Status Parameters for database connections
** KEYWORDS: {SQLITE_DBSTATUS options}
**
** These constants are the available integer "verbs" that can be passed as
-** the second argument to the [sqlite3_db_status()] interface.
+** the second argument to the [tdsqlite3_db_status()] interface.
**
** New verbs may be added in future releases of SQLite. Existing verbs
** might be discontinued. Applications should check the return code from
-** [sqlite3_db_status()] to make sure that the call worked.
-** The [sqlite3_db_status()] interface will return a non-zero error code
+** [tdsqlite3_db_status()] to make sure that the call worked.
+** The [tdsqlite3_db_status()] interface will return a non-zero error code
** if a discontinued or unsupported verb is invoked.
**
** <dl>
@@ -6906,7 +7980,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** checked out.</dd>)^
**
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
-** <dd>This parameter returns the number malloc attempts that were
+** <dd>This parameter returns the number of malloc attempts that were
** satisfied using lookaside memory. Only the high-water value is meaningful;
** the current value is always zero.)^
**
@@ -6982,6 +8056,15 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
** </dd>
**
+** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt>
+** <dd>This parameter returns the number of dirty cache entries that have
+** been written to disk in the middle of a transaction due to the page
+** cache overflowing. Transactions are more efficient if they are written
+** to disk all at once. When pages spill mid-transaction, that introduces
+** additional overhead. This parameter can be used help identify
+** inefficiencies that can be resolved by increasing the cache size.
+** </dd>
+**
** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt>
** <dd>This parameter returns zero for the current value if and only if
** all foreign key constraints (deferred or immediate) have been
@@ -7001,12 +8084,13 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
#define SQLITE_DBSTATUS_CACHE_WRITE 9
#define SQLITE_DBSTATUS_DEFERRED_FKS 10
#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11
-#define SQLITE_DBSTATUS_MAX 11 /* Largest defined DBSTATUS */
+#define SQLITE_DBSTATUS_CACHE_SPILL 12
+#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */
/*
** CAPI3REF: Prepared Statement Status
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** ^(Each prepared statement maintains various
** [SQLITE_STMTSTATUS counters] that measure the number
@@ -7026,16 +8110,16 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
** ^If the resetFlg is true, then the counter is reset to zero after this
** interface call returns.
**
-** See also: [sqlite3_status()] and [sqlite3_db_status()].
+** See also: [tdsqlite3_status()] and [tdsqlite3_db_status()].
*/
-SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
+SQLITE_API int tdsqlite3_stmt_status(tdsqlite3_stmt*, int op,int resetFlg);
/*
** CAPI3REF: Status Parameters for prepared statements
** KEYWORDS: {SQLITE_STMTSTATUS counter} {SQLITE_STMTSTATUS counters}
**
** These preprocessor macros define integer codes that name counter
-** values associated with the [sqlite3_stmt_status()] interface.
+** values associated with the [tdsqlite3_stmt_status()] interface.
** The meanings of the various counters are as follows:
**
** <dl>
@@ -7064,6 +8148,24 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
** used as a proxy for the total work done by the prepared statement.
** If the number of virtual machine operations exceeds 2147483647
** then the value returned by this statement status code is undefined.
+**
+** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
+** <dd>^This is the number of times that the prepare statement has been
+** automatically regenerated due to schema changes or changes to
+** [bound parameters] that might affect the query plan.
+**
+** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
+** <dd>^This is the number of times that the prepared statement has
+** been run. A single "run" for the purposes of this counter is one
+** or more calls to [tdsqlite3_step()] followed by a call to [tdsqlite3_reset()].
+** The counter is incremented on the first [tdsqlite3_step()] call of each
+** cycle.
+**
+** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
+** <dd>^This is the approximate number of bytes of heap memory
+** used to store the prepared statement. ^This value is not actually
+** a counter, and so the resetFlg parameter to tdsqlite3_stmt_status()
+** is ignored when the opcode is SQLITE_STMTSTATUS_MEMUSED.
** </dd>
** </dl>
*/
@@ -7071,32 +8173,35 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
#define SQLITE_STMTSTATUS_SORT 2
#define SQLITE_STMTSTATUS_AUTOINDEX 3
#define SQLITE_STMTSTATUS_VM_STEP 4
+#define SQLITE_STMTSTATUS_REPREPARE 5
+#define SQLITE_STMTSTATUS_RUN 6
+#define SQLITE_STMTSTATUS_MEMUSED 99
/*
** CAPI3REF: Custom Page Cache Object
**
-** The sqlite3_pcache type is opaque. It is implemented by
+** The tdsqlite3_pcache type is opaque. It is implemented by
** the pluggable module. The SQLite core has no knowledge of
** its size or internal structure and never deals with the
-** sqlite3_pcache object except by holding and passing pointers
+** tdsqlite3_pcache object except by holding and passing pointers
** to the object.
**
-** See [sqlite3_pcache_methods2] for additional information.
+** See [tdsqlite3_pcache_methods2] for additional information.
*/
-typedef struct sqlite3_pcache sqlite3_pcache;
+typedef struct tdsqlite3_pcache tdsqlite3_pcache;
/*
** CAPI3REF: Custom Page Cache Object
**
-** The sqlite3_pcache_page object represents a single page in the
+** The tdsqlite3_pcache_page object represents a single page in the
** page cache. The page cache will allocate instances of this
** object. Various methods of the page cache use pointers to instances
** of this object as parameters or as their return value.
**
-** See [sqlite3_pcache_methods2] for additional information.
+** See [tdsqlite3_pcache_methods2] for additional information.
*/
-typedef struct sqlite3_pcache_page sqlite3_pcache_page;
-struct sqlite3_pcache_page {
+typedef struct tdsqlite3_pcache_page tdsqlite3_pcache_page;
+struct tdsqlite3_pcache_page {
void *pBuf; /* The content of the page */
void *pExtra; /* Extra information associated with the page */
};
@@ -7105,9 +8210,9 @@ struct sqlite3_pcache_page {
** CAPI3REF: Application Defined Page Cache.
** KEYWORDS: {page cache}
**
-** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can
+** ^(The [tdsqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can
** register an alternative page cache implementation by passing in an
-** instance of the sqlite3_pcache_methods2 structure.)^
+** instance of the tdsqlite3_pcache_methods2 structure.)^
** In many applications, most of the heap memory allocated by
** SQLite is used for the page cache.
** By implementing a
@@ -7121,16 +8226,16 @@ struct sqlite3_pcache_page {
** extreme measure that is only needed by the most demanding applications.
** The built-in page cache is recommended for most uses.
**
-** ^(The contents of the sqlite3_pcache_methods2 structure are copied to an
-** internal buffer by SQLite within the call to [sqlite3_config]. Hence
+** ^(The contents of the tdsqlite3_pcache_methods2 structure are copied to an
+** internal buffer by SQLite within the call to [tdsqlite3_config]. Hence
** the application may discard the parameter after the call to
-** [sqlite3_config()] returns.)^
+** [tdsqlite3_config()] returns.)^
**
** [[the xInit() page cache method]]
** ^(The xInit() method is called once for each effective
-** call to [sqlite3_initialize()])^
+** call to [tdsqlite3_initialize()])^
** (usually only once during the lifetime of the process). ^(The xInit()
-** method is passed a copy of the sqlite3_pcache_methods2.pArg value.)^
+** method is passed a copy of the tdsqlite3_pcache_methods2.pArg value.)^
** The intent of the xInit() method is to set up global data structures
** required by the custom page cache implementation.
** ^(If the xInit() method is NULL, then the
@@ -7138,14 +8243,14 @@ struct sqlite3_pcache_page {
** page cache.)^
**
** [[the xShutdown() page cache method]]
-** ^The xShutdown() method is called by [sqlite3_shutdown()].
+** ^The xShutdown() method is called by [tdsqlite3_shutdown()].
** It can be used to clean up
** any outstanding resources before process shutdown, if required.
** ^The xShutdown() method may be NULL.
**
** ^SQLite automatically serializes calls to the xInit method,
** so the xInit method need not be threadsafe. ^The
-** xShutdown method is only called from [sqlite3_shutdown()] so it does
+** xShutdown method is only called from [tdsqlite3_shutdown()] so it does
** not need to be threadsafe either. All other methods must be threadsafe
** in multithreaded applications.
**
@@ -7189,10 +8294,10 @@ struct sqlite3_pcache_page {
**
** [[the xFetch() page cache methods]]
** The xFetch() method locates a page in the cache and returns a pointer to
-** an sqlite3_pcache_page object associated with that page, or a NULL pointer.
-** The pBuf element of the returned sqlite3_pcache_page object will be a
+** an tdsqlite3_pcache_page object associated with that page, or a NULL pointer.
+** The pBuf element of the returned tdsqlite3_pcache_page object will be a
** pointer to a buffer of szPage bytes used to store the content of a
-** single database page. The pExtra element of sqlite3_pcache_page will be
+** single database page. The pExtra element of tdsqlite3_pcache_page will be
** a pointer to the szExtra bytes of extra storage that SQLite has requested
** for each entry in the page cache.
**
@@ -7217,7 +8322,7 @@ struct sqlite3_pcache_page {
**
** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite
** will only use a createFlag of 2 after a prior call with a createFlag of 1
-** failed.)^ In between the to xFetch() calls, SQLite may
+** failed.)^ In between the xFetch() calls, SQLite may
** attempt to unpin one or more cache pages by spilling the content of
** pinned pages to disk and synching the operating system disk cache.
**
@@ -7250,8 +8355,8 @@ struct sqlite3_pcache_page {
** [[the xDestroy() page cache method]]
** ^The xDestroy() method is used to delete a cache allocated by xCreate().
** All resources associated with the specified cache should be freed. ^After
-** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*]
-** handle invalid, and will not use it with any other sqlite3_pcache_methods2
+** calling the xDestroy() method, SQLite considers the [tdsqlite3_pcache*]
+** handle invalid, and will not use it with any other tdsqlite3_pcache_methods2
** functions.
**
** [[the xShrink() page cache method]]
@@ -7260,56 +8365,56 @@ struct sqlite3_pcache_page {
** is not obligated to free any memory, but well-behaved implementations should
** do their best.
*/
-typedef struct sqlite3_pcache_methods2 sqlite3_pcache_methods2;
-struct sqlite3_pcache_methods2 {
+typedef struct tdsqlite3_pcache_methods2 tdsqlite3_pcache_methods2;
+struct tdsqlite3_pcache_methods2 {
int iVersion;
void *pArg;
int (*xInit)(void*);
void (*xShutdown)(void*);
- sqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable);
- void (*xCachesize)(sqlite3_pcache*, int nCachesize);
- int (*xPagecount)(sqlite3_pcache*);
- sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag);
- void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard);
- void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*,
+ tdsqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable);
+ void (*xCachesize)(tdsqlite3_pcache*, int nCachesize);
+ int (*xPagecount)(tdsqlite3_pcache*);
+ tdsqlite3_pcache_page *(*xFetch)(tdsqlite3_pcache*, unsigned key, int createFlag);
+ void (*xUnpin)(tdsqlite3_pcache*, tdsqlite3_pcache_page*, int discard);
+ void (*xRekey)(tdsqlite3_pcache*, tdsqlite3_pcache_page*,
unsigned oldKey, unsigned newKey);
- void (*xTruncate)(sqlite3_pcache*, unsigned iLimit);
- void (*xDestroy)(sqlite3_pcache*);
- void (*xShrink)(sqlite3_pcache*);
+ void (*xTruncate)(tdsqlite3_pcache*, unsigned iLimit);
+ void (*xDestroy)(tdsqlite3_pcache*);
+ void (*xShrink)(tdsqlite3_pcache*);
};
/*
** This is the obsolete pcache_methods object that has now been replaced
-** by sqlite3_pcache_methods2. This object is not used by SQLite. It is
+** by tdsqlite3_pcache_methods2. This object is not used by SQLite. It is
** retained in the header file for backwards compatibility only.
*/
-typedef struct sqlite3_pcache_methods sqlite3_pcache_methods;
-struct sqlite3_pcache_methods {
+typedef struct tdsqlite3_pcache_methods tdsqlite3_pcache_methods;
+struct tdsqlite3_pcache_methods {
void *pArg;
int (*xInit)(void*);
void (*xShutdown)(void*);
- sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable);
- void (*xCachesize)(sqlite3_pcache*, int nCachesize);
- int (*xPagecount)(sqlite3_pcache*);
- void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag);
- void (*xUnpin)(sqlite3_pcache*, void*, int discard);
- void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey);
- void (*xTruncate)(sqlite3_pcache*, unsigned iLimit);
- void (*xDestroy)(sqlite3_pcache*);
+ tdsqlite3_pcache *(*xCreate)(int szPage, int bPurgeable);
+ void (*xCachesize)(tdsqlite3_pcache*, int nCachesize);
+ int (*xPagecount)(tdsqlite3_pcache*);
+ void *(*xFetch)(tdsqlite3_pcache*, unsigned key, int createFlag);
+ void (*xUnpin)(tdsqlite3_pcache*, void*, int discard);
+ void (*xRekey)(tdsqlite3_pcache*, void*, unsigned oldKey, unsigned newKey);
+ void (*xTruncate)(tdsqlite3_pcache*, unsigned iLimit);
+ void (*xDestroy)(tdsqlite3_pcache*);
};
/*
** CAPI3REF: Online Backup Object
**
-** The sqlite3_backup object records state information about an ongoing
-** online backup operation. ^The sqlite3_backup object is created by
-** a call to [sqlite3_backup_init()] and is destroyed by a call to
-** [sqlite3_backup_finish()].
+** The tdsqlite3_backup object records state information about an ongoing
+** online backup operation. ^The tdsqlite3_backup object is created by
+** a call to [tdsqlite3_backup_init()] and is destroyed by a call to
+** [tdsqlite3_backup_finish()].
**
** See Also: [Using the SQLite Online Backup API]
*/
-typedef struct sqlite3_backup sqlite3_backup;
+typedef struct tdsqlite3_backup tdsqlite3_backup;
/*
** CAPI3REF: Online Backup API.
@@ -7330,63 +8435,63 @@ typedef struct sqlite3_backup sqlite3_backup;
**
** ^(To perform a backup operation:
** <ol>
-** <li><b>sqlite3_backup_init()</b> is called once to initialize the
+** <li><b>tdsqlite3_backup_init()</b> is called once to initialize the
** backup,
-** <li><b>sqlite3_backup_step()</b> is called one or more times to transfer
+** <li><b>tdsqlite3_backup_step()</b> is called one or more times to transfer
** the data between the two databases, and finally
-** <li><b>sqlite3_backup_finish()</b> is called to release all resources
+** <li><b>tdsqlite3_backup_finish()</b> is called to release all resources
** associated with the backup operation.
** </ol>)^
-** There should be exactly one call to sqlite3_backup_finish() for each
-** successful call to sqlite3_backup_init().
+** There should be exactly one call to tdsqlite3_backup_finish() for each
+** successful call to tdsqlite3_backup_init().
**
-** [[sqlite3_backup_init()]] <b>sqlite3_backup_init()</b>
+** [[tdsqlite3_backup_init()]] <b>tdsqlite3_backup_init()</b>
**
-** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the
+** ^The D and N arguments to tdsqlite3_backup_init(D,N,S,M) are the
** [database connection] associated with the destination database
** and the database name, respectively.
** ^The database name is "main" for the main database, "temp" for the
** temporary database, or the name specified after the AS keyword in
** an [ATTACH] statement for an attached database.
** ^The S and M arguments passed to
-** sqlite3_backup_init(D,N,S,M) identify the [database connection]
+** tdsqlite3_backup_init(D,N,S,M) identify the [database connection]
** and database name of the source database, respectively.
** ^The source and destination [database connections] (parameters S and D)
-** must be different or else sqlite3_backup_init(D,N,S,M) will fail with
+** must be different or else tdsqlite3_backup_init(D,N,S,M) will fail with
** an error.
**
-** ^A call to sqlite3_backup_init() will fail, returning NULL, if
+** ^A call to tdsqlite3_backup_init() will fail, returning NULL, if
** there is already a read or read-write transaction open on the
** destination database.
**
-** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is
+** ^If an error occurs within tdsqlite3_backup_init(D,N,S,M), then NULL is
** returned and an error code and error message are stored in the
** destination [database connection] D.
-** ^The error code and message for the failed call to sqlite3_backup_init()
-** can be retrieved using the [sqlite3_errcode()], [sqlite3_errmsg()], and/or
-** [sqlite3_errmsg16()] functions.
-** ^A successful call to sqlite3_backup_init() returns a pointer to an
-** [sqlite3_backup] object.
-** ^The [sqlite3_backup] object may be used with the sqlite3_backup_step() and
-** sqlite3_backup_finish() functions to perform the specified backup
+** ^The error code and message for the failed call to tdsqlite3_backup_init()
+** can be retrieved using the [tdsqlite3_errcode()], [tdsqlite3_errmsg()], and/or
+** [tdsqlite3_errmsg16()] functions.
+** ^A successful call to tdsqlite3_backup_init() returns a pointer to an
+** [tdsqlite3_backup] object.
+** ^The [tdsqlite3_backup] object may be used with the tdsqlite3_backup_step() and
+** tdsqlite3_backup_finish() functions to perform the specified backup
** operation.
**
-** [[sqlite3_backup_step()]] <b>sqlite3_backup_step()</b>
+** [[tdsqlite3_backup_step()]] <b>tdsqlite3_backup_step()</b>
**
-** ^Function sqlite3_backup_step(B,N) will copy up to N pages between
-** the source and destination databases specified by [sqlite3_backup] object B.
+** ^Function tdsqlite3_backup_step(B,N) will copy up to N pages between
+** the source and destination databases specified by [tdsqlite3_backup] object B.
** ^If N is negative, all remaining source pages are copied.
-** ^If sqlite3_backup_step(B,N) successfully copies N pages and there
+** ^If tdsqlite3_backup_step(B,N) successfully copies N pages and there
** are still more pages to be copied, then the function returns [SQLITE_OK].
-** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages
+** ^If tdsqlite3_backup_step(B,N) successfully finishes copying all pages
** from source to destination, then it returns [SQLITE_DONE].
-** ^If an error occurs while running sqlite3_backup_step(B,N),
+** ^If an error occurs while running tdsqlite3_backup_step(B,N),
** then an [error code] is returned. ^As well as [SQLITE_OK] and
-** [SQLITE_DONE], a call to sqlite3_backup_step() may return [SQLITE_READONLY],
+** [SQLITE_DONE], a call to tdsqlite3_backup_step() may return [SQLITE_READONLY],
** [SQLITE_NOMEM], [SQLITE_BUSY], [SQLITE_LOCKED], or an
** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX] extended error code.
**
-** ^(The sqlite3_backup_step() might return [SQLITE_READONLY] if
+** ^(The tdsqlite3_backup_step() might return [SQLITE_READONLY] if
** <ol>
** <li> the destination database was opened read-only, or
** <li> the destination database is using write-ahead-log journaling
@@ -7395,76 +8500,76 @@ typedef struct sqlite3_backup sqlite3_backup;
** destination and source page sizes differ.
** </ol>)^
**
-** ^If sqlite3_backup_step() cannot obtain a required file-system lock, then
-** the [sqlite3_busy_handler | busy-handler function]
+** ^If tdsqlite3_backup_step() cannot obtain a required file-system lock, then
+** the [tdsqlite3_busy_handler | busy-handler function]
** is invoked (if one is specified). ^If the
** busy-handler returns non-zero before the lock is available, then
** [SQLITE_BUSY] is returned to the caller. ^In this case the call to
-** sqlite3_backup_step() can be retried later. ^If the source
+** tdsqlite3_backup_step() can be retried later. ^If the source
** [database connection]
-** is being used to write to the source database when sqlite3_backup_step()
+** is being used to write to the source database when tdsqlite3_backup_step()
** is called, then [SQLITE_LOCKED] is returned immediately. ^Again, in this
-** case the call to sqlite3_backup_step() can be retried later on. ^(If
+** case the call to tdsqlite3_backup_step() can be retried later on. ^(If
** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or
** [SQLITE_READONLY] is returned, then
-** there is no point in retrying the call to sqlite3_backup_step(). These
+** there is no point in retrying the call to tdsqlite3_backup_step(). These
** errors are considered fatal.)^ The application must accept
** that the backup operation has failed and pass the backup operation handle
-** to the sqlite3_backup_finish() to release associated resources.
+** to the tdsqlite3_backup_finish() to release associated resources.
**
-** ^The first call to sqlite3_backup_step() obtains an exclusive lock
+** ^The first call to tdsqlite3_backup_step() obtains an exclusive lock
** on the destination file. ^The exclusive lock is not released until either
-** sqlite3_backup_finish() is called or the backup operation is complete
-** and sqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to
-** sqlite3_backup_step() obtains a [shared lock] on the source database that
-** lasts for the duration of the sqlite3_backup_step() call.
+** tdsqlite3_backup_finish() is called or the backup operation is complete
+** and tdsqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to
+** tdsqlite3_backup_step() obtains a [shared lock] on the source database that
+** lasts for the duration of the tdsqlite3_backup_step() call.
** ^Because the source database is not locked between calls to
-** sqlite3_backup_step(), the source database may be modified mid-way
+** tdsqlite3_backup_step(), the source database may be modified mid-way
** through the backup process. ^If the source database is modified by an
** external process or via a database connection other than the one being
** used by the backup operation, then the backup will be automatically
-** restarted by the next call to sqlite3_backup_step(). ^If the source
+** restarted by the next call to tdsqlite3_backup_step(). ^If the source
** database is modified by the using the same database connection as is used
** by the backup operation, then the backup database is automatically
** updated at the same time.
**
-** [[sqlite3_backup_finish()]] <b>sqlite3_backup_finish()</b>
+** [[tdsqlite3_backup_finish()]] <b>tdsqlite3_backup_finish()</b>
**
-** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the
+** When tdsqlite3_backup_step() has returned [SQLITE_DONE], or when the
** application wishes to abandon the backup operation, the application
-** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish().
-** ^The sqlite3_backup_finish() interfaces releases all
-** resources associated with the [sqlite3_backup] object.
-** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any
+** should destroy the [tdsqlite3_backup] by passing it to tdsqlite3_backup_finish().
+** ^The tdsqlite3_backup_finish() interfaces releases all
+** resources associated with the [tdsqlite3_backup] object.
+** ^If tdsqlite3_backup_step() has not yet returned [SQLITE_DONE], then any
** active write-transaction on the destination database is rolled back.
-** The [sqlite3_backup] object is invalid
-** and may not be used following a call to sqlite3_backup_finish().
+** The [tdsqlite3_backup] object is invalid
+** and may not be used following a call to tdsqlite3_backup_finish().
**
-** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
-** sqlite3_backup_step() errors occurred, regardless or whether or not
-** sqlite3_backup_step() completed.
+** ^The value returned by tdsqlite3_backup_finish is [SQLITE_OK] if no
+** tdsqlite3_backup_step() errors occurred, regardless or whether or not
+** tdsqlite3_backup_step() completed.
** ^If an out-of-memory condition or IO error occurred during any prior
-** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
-** sqlite3_backup_finish() returns the corresponding [error code].
+** tdsqlite3_backup_step() call on the same [tdsqlite3_backup] object, then
+** tdsqlite3_backup_finish() returns the corresponding [error code].
**
-** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step()
+** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from tdsqlite3_backup_step()
** is not a permanent error and does not affect the return value of
-** sqlite3_backup_finish().
+** tdsqlite3_backup_finish().
**
-** [[sqlite3_backup_remaining()]] [[sqlite3_backup_pagecount()]]
-** <b>sqlite3_backup_remaining() and sqlite3_backup_pagecount()</b>
+** [[tdsqlite3_backup_remaining()]] [[tdsqlite3_backup_pagecount()]]
+** <b>tdsqlite3_backup_remaining() and tdsqlite3_backup_pagecount()</b>
**
-** ^The sqlite3_backup_remaining() routine returns the number of pages still
-** to be backed up at the conclusion of the most recent sqlite3_backup_step().
-** ^The sqlite3_backup_pagecount() routine returns the total number of pages
+** ^The tdsqlite3_backup_remaining() routine returns the number of pages still
+** to be backed up at the conclusion of the most recent tdsqlite3_backup_step().
+** ^The tdsqlite3_backup_pagecount() routine returns the total number of pages
** in the source database at the conclusion of the most recent
-** sqlite3_backup_step().
+** tdsqlite3_backup_step().
** ^(The values returned by these functions are only updated by
-** sqlite3_backup_step(). If the source database is modified in a way that
+** tdsqlite3_backup_step(). If the source database is modified in a way that
** changes the size of the source database or the number of pages remaining,
-** those changes are not reflected in the output of sqlite3_backup_pagecount()
-** and sqlite3_backup_remaining() until after the next
-** sqlite3_backup_step().)^
+** those changes are not reflected in the output of tdsqlite3_backup_pagecount()
+** and tdsqlite3_backup_remaining() until after the next
+** tdsqlite3_backup_step().)^
**
** <b>Concurrent Usage of Database Handles</b>
**
@@ -7476,8 +8581,8 @@ typedef struct sqlite3_backup sqlite3_backup;
**
** However, the application must guarantee that the destination
** [database connection] is not passed to any other API (by any thread) after
-** sqlite3_backup_init() is called and before the corresponding call to
-** sqlite3_backup_finish(). SQLite does not currently check to see
+** tdsqlite3_backup_init() is called and before the corresponding call to
+** tdsqlite3_backup_finish(). SQLite does not currently check to see
** if the application incorrectly accesses the destination [database connection]
** and so no error code is reported, but the operations may malfunction
** nevertheless. Use of the destination database connection while a
@@ -7488,29 +8593,29 @@ typedef struct sqlite3_backup sqlite3_backup;
** is not accessed while the backup is running. In practice this means
** that the application must guarantee that the disk file being
** backed up to is not accessed by any connection within the process,
-** not just the specific connection that was passed to sqlite3_backup_init().
+** not just the specific connection that was passed to tdsqlite3_backup_init().
**
-** The [sqlite3_backup] object itself is partially threadsafe. Multiple
-** threads may safely make multiple concurrent calls to sqlite3_backup_step().
-** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount()
+** The [tdsqlite3_backup] object itself is partially threadsafe. Multiple
+** threads may safely make multiple concurrent calls to tdsqlite3_backup_step().
+** However, the tdsqlite3_backup_remaining() and tdsqlite3_backup_pagecount()
** APIs are not strictly speaking threadsafe. If they are invoked at the
-** same time as another thread is invoking sqlite3_backup_step() it is
+** same time as another thread is invoking tdsqlite3_backup_step() it is
** possible that they return invalid values.
*/
-SQLITE_API sqlite3_backup *sqlite3_backup_init(
- sqlite3 *pDest, /* Destination database handle */
+SQLITE_API tdsqlite3_backup *tdsqlite3_backup_init(
+ tdsqlite3 *pDest, /* Destination database handle */
const char *zDestName, /* Destination database name */
- sqlite3 *pSource, /* Source database handle */
+ tdsqlite3 *pSource, /* Source database handle */
const char *zSourceName /* Source database name */
);
-SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage);
-SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p);
-SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p);
-SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
+SQLITE_API int tdsqlite3_backup_step(tdsqlite3_backup *p, int nPage);
+SQLITE_API int tdsqlite3_backup_finish(tdsqlite3_backup *p);
+SQLITE_API int tdsqlite3_backup_remaining(tdsqlite3_backup *p);
+SQLITE_API int tdsqlite3_backup_pagecount(tdsqlite3_backup *p);
/*
** CAPI3REF: Unlock Notification
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
** ^When running in shared-cache mode, a database operation may fail with
** an [SQLITE_LOCKED] error if the required locks on the shared-cache or
@@ -7531,17 +8636,17 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** identity of the database connection (the blocking connection) that
** has locked the required resource is stored internally. ^After an
** application receives an SQLITE_LOCKED error, it may call the
-** sqlite3_unlock_notify() method with the blocked connection handle as
+** tdsqlite3_unlock_notify() method with the blocked connection handle as
** the first argument to register for a callback that will be invoked
** when the blocking connections current transaction is concluded. ^The
-** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
-** call that concludes the blocking connections transaction.
+** callback is invoked from within the [tdsqlite3_step] or [tdsqlite3_close]
+** call that concludes the blocking connection's transaction.
**
-** ^(If sqlite3_unlock_notify() is called in a multi-threaded application,
+** ^(If tdsqlite3_unlock_notify() is called in a multi-threaded application,
** there is a chance that the blocking connection will have already
-** concluded its transaction by the time sqlite3_unlock_notify() is invoked.
+** concluded its transaction by the time tdsqlite3_unlock_notify() is invoked.
** If this happens, then the specified callback is invoked immediately,
-** from within the call to sqlite3_unlock_notify().)^
+** from within the call to tdsqlite3_unlock_notify().)^
**
** ^If the blocked connection is attempting to obtain a write-lock on a
** shared-cache table, and more than one other connection currently holds
@@ -7549,19 +8654,19 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** the other connections to use as the blocking connection.
**
** ^(There may be at most one unlock-notify callback registered by a
-** blocked connection. If sqlite3_unlock_notify() is called when the
+** blocked connection. If tdsqlite3_unlock_notify() is called when the
** blocked connection already has a registered unlock-notify callback,
-** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
+** then the new callback replaces the old.)^ ^If tdsqlite3_unlock_notify() is
** called with a NULL pointer as its second argument, then any existing
** unlock-notify callback is canceled. ^The blocked connections
** unlock-notify callback may also be canceled by closing the blocked
-** connection using [sqlite3_close()].
+** connection using [tdsqlite3_close()].
**
** The unlock-notify callback is not reentrant. If an application invokes
-** any sqlite3_xxx API functions from within an unlock-notify callback, a
+** any tdsqlite3_xxx API functions from within an unlock-notify callback, a
** crash or deadlock may be the result.
**
-** ^Unless deadlock is detected (see below), sqlite3_unlock_notify() always
+** ^Unless deadlock is detected (see below), tdsqlite3_unlock_notify() always
** returns SQLITE_OK.
**
** <b>Callback Invocation Details</b>
@@ -7573,7 +8678,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** an unlock-notify callback is a pointer to an array of void* pointers,
** and the second is the number of entries in the array.
**
-** When a blocking connections transaction is concluded, there may be
+** When a blocking connection's transaction is concluded, there may be
** more than one blocked connection that has registered for an unlock-notify
** callback. ^If two or more such blocked connections have specified the
** same callback function, then instead of invoking the callback function
@@ -7592,8 +8697,8 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
** Y is waiting on connection X's transaction, then neither connection
** will proceed and the system may remain deadlocked indefinitely.
**
-** To avoid this scenario, the sqlite3_unlock_notify() performs deadlock
-** detection. ^If a given call to sqlite3_unlock_notify() would put the
+** To avoid this scenario, the tdsqlite3_unlock_notify() performs deadlock
+** detection. ^If a given call to tdsqlite3_unlock_notify() would put the
** system in a deadlocked state, then SQLITE_LOCKED is returned and no
** unlock-notify callback is registered. The system is said to be in
** a deadlocked state if connection A has registered for an unlock-notify
@@ -7607,24 +8712,24 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
**
** <b>The "DROP TABLE" Exception</b>
**
-** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost
-** always appropriate to call sqlite3_unlock_notify(). There is however,
+** When a call to [tdsqlite3_step()] returns SQLITE_LOCKED, it is almost
+** always appropriate to call tdsqlite3_unlock_notify(). There is however,
** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement,
** SQLite checks if there are any currently executing SELECT statements
** that belong to the same connection. If there are, SQLITE_LOCKED is
** returned. In this case there is no "blocking connection", so invoking
-** sqlite3_unlock_notify() results in the unlock-notify callback being
+** tdsqlite3_unlock_notify() results in the unlock-notify callback being
** invoked immediately. If the application then re-attempts the "DROP TABLE"
** or "DROP INDEX" query, an infinite loop might be the result.
**
** One way around this problem is to check the extended error code returned
-** by an sqlite3_step() call. ^(If there is a blocking connection, then the
+** by an tdsqlite3_step() call. ^(If there is a blocking connection, then the
** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in
** the special "DROP TABLE/INDEX" case, the extended error code is just
** SQLITE_LOCKED.)^
*/
-SQLITE_API int sqlite3_unlock_notify(
- sqlite3 *pBlocked, /* Waiting connection */
+SQLITE_API int tdsqlite3_unlock_notify(
+ tdsqlite3 *pBlocked, /* Waiting connection */
void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */
void *pNotifyArg /* Argument to pass to xNotify */
);
@@ -7633,82 +8738,82 @@ SQLITE_API int sqlite3_unlock_notify(
/*
** CAPI3REF: String Comparison
**
-** ^The [sqlite3_stricmp()] and [sqlite3_strnicmp()] APIs allow applications
+** ^The [tdsqlite3_stricmp()] and [tdsqlite3_strnicmp()] APIs allow applications
** and extensions to compare the contents of two buffers containing UTF-8
** strings in a case-independent fashion, using the same definition of "case
** independence" that SQLite uses internally when comparing identifiers.
*/
-SQLITE_API int sqlite3_stricmp(const char *, const char *);
-SQLITE_API int sqlite3_strnicmp(const char *, const char *, int);
+SQLITE_API int tdsqlite3_stricmp(const char *, const char *);
+SQLITE_API int tdsqlite3_strnicmp(const char *, const char *, int);
/*
** CAPI3REF: String Globbing
*
-** ^The [sqlite3_strglob(P,X)] interface returns zero if and only if
+** ^The [tdsqlite3_strglob(P,X)] interface returns zero if and only if
** string X matches the [GLOB] pattern P.
** ^The definition of [GLOB] pattern matching used in
-** [sqlite3_strglob(P,X)] is the same as for the "X GLOB P" operator in the
-** SQL dialect understood by SQLite. ^The [sqlite3_strglob(P,X)] function
+** [tdsqlite3_strglob(P,X)] is the same as for the "X GLOB P" operator in the
+** SQL dialect understood by SQLite. ^The [tdsqlite3_strglob(P,X)] function
** is case sensitive.
**
** Note that this routine returns zero on a match and non-zero if the strings
-** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()].
+** do not match, the same as [tdsqlite3_stricmp()] and [tdsqlite3_strnicmp()].
**
-** See also: [sqlite3_strlike()].
+** See also: [tdsqlite3_strlike()].
*/
-SQLITE_API int sqlite3_strglob(const char *zGlob, const char *zStr);
+SQLITE_API int tdsqlite3_strglob(const char *zGlob, const char *zStr);
/*
** CAPI3REF: String LIKE Matching
*
-** ^The [sqlite3_strlike(P,X,E)] interface returns zero if and only if
+** ^The [tdsqlite3_strlike(P,X,E)] interface returns zero if and only if
** string X matches the [LIKE] pattern P with escape character E.
** ^The definition of [LIKE] pattern matching used in
-** [sqlite3_strlike(P,X,E)] is the same as for the "X LIKE P ESCAPE E"
+** [tdsqlite3_strlike(P,X,E)] is the same as for the "X LIKE P ESCAPE E"
** operator in the SQL dialect understood by SQLite. ^For "X LIKE P" without
-** the ESCAPE clause, set the E parameter of [sqlite3_strlike(P,X,E)] to 0.
-** ^As with the LIKE operator, the [sqlite3_strlike(P,X,E)] function is case
+** the ESCAPE clause, set the E parameter of [tdsqlite3_strlike(P,X,E)] to 0.
+** ^As with the LIKE operator, the [tdsqlite3_strlike(P,X,E)] function is case
** insensitive - equivalent upper and lower case ASCII characters match
** one another.
**
-** ^The [sqlite3_strlike(P,X,E)] function matches Unicode characters, though
+** ^The [tdsqlite3_strlike(P,X,E)] function matches Unicode characters, though
** only ASCII characters are case folded.
**
** Note that this routine returns zero on a match and non-zero if the strings
-** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()].
+** do not match, the same as [tdsqlite3_stricmp()] and [tdsqlite3_strnicmp()].
**
-** See also: [sqlite3_strglob()].
+** See also: [tdsqlite3_strglob()].
*/
-SQLITE_API int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc);
+SQLITE_API int tdsqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc);
/*
** CAPI3REF: Error Logging Interface
**
-** ^The [sqlite3_log()] interface writes a message into the [error log]
-** established by the [SQLITE_CONFIG_LOG] option to [sqlite3_config()].
+** ^The [tdsqlite3_log()] interface writes a message into the [error log]
+** established by the [SQLITE_CONFIG_LOG] option to [tdsqlite3_config()].
** ^If logging is enabled, the zFormat string and subsequent arguments are
-** used with [sqlite3_snprintf()] to generate the final output string.
+** used with [tdsqlite3_snprintf()] to generate the final output string.
**
-** The sqlite3_log() interface is intended for use by extensions such as
+** The tdsqlite3_log() interface is intended for use by extensions such as
** virtual tables, collating functions, and SQL functions. While there is
-** nothing to prevent an application from calling sqlite3_log(), doing so
+** nothing to prevent an application from calling tdsqlite3_log(), doing so
** is considered bad form.
**
** The zFormat string must not be NULL.
**
-** To avoid deadlocks and other threading problems, the sqlite3_log() routine
+** To avoid deadlocks and other threading problems, the tdsqlite3_log() routine
** will not use dynamically allocated memory. The log message is stored in
** a fixed-length buffer on the stack. If the log message is longer than
** a few hundred characters, it will be truncated to the length of the
** buffer.
*/
-SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
+SQLITE_API void tdsqlite3_log(int iErrCode, const char *zFormat, ...);
/*
** CAPI3REF: Write-Ahead Log Commit Hook
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The [sqlite3_wal_hook()] function is used to register a callback that
+** ^The [tdsqlite3_wal_hook()] function is used to register a callback that
** is invoked each time data is committed to a database in wal mode.
**
** ^(The callback is invoked by SQLite after the commit has taken place and
@@ -7716,7 +8821,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** may read, write or [checkpoint] the database as required.
**
** ^The first parameter passed to the callback function when it is invoked
-** is a copy of the third parameter passed to sqlite3_wal_hook() when
+** is a copy of the third parameter passed to tdsqlite3_wal_hook() when
** registering the callback. ^The second is a copy of the database handle.
** ^The third parameter is the name of the database that was written to -
** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter
@@ -7732,24 +8837,24 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...);
** are undefined.
**
** A single database handle may have at most a single write-ahead log callback
-** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any
+** registered at one time. ^Calling [tdsqlite3_wal_hook()] replaces any
** previously registered write-ahead log callback. ^Note that the
-** [sqlite3_wal_autocheckpoint()] interface and the
-** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will
-** overwrite any prior [sqlite3_wal_hook()] settings.
+** [tdsqlite3_wal_autocheckpoint()] interface and the
+** [wal_autocheckpoint pragma] both invoke [tdsqlite3_wal_hook()] and will
+** overwrite any prior [tdsqlite3_wal_hook()] settings.
*/
-SQLITE_API void *sqlite3_wal_hook(
- sqlite3*,
- int(*)(void *,sqlite3*,const char*,int),
+SQLITE_API void *tdsqlite3_wal_hook(
+ tdsqlite3*,
+ int(*)(void *,tdsqlite3*,const char*,int),
void*
);
/*
** CAPI3REF: Configure an auto-checkpoint
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around
-** [sqlite3_wal_hook()] that causes any database on [database connection] D
+** ^The [tdsqlite3_wal_autocheckpoint(D,N)] is a wrapper around
+** [tdsqlite3_wal_hook()] that causes any database on [database connection] D
** to automatically [checkpoint]
** after committing a transaction if there are N or
** more frames in the [write-ahead log] file. ^Passing zero or
@@ -7757,15 +8862,15 @@ SQLITE_API void *sqlite3_wal_hook(
** checkpoints entirely.
**
** ^The callback registered by this function replaces any existing callback
-** registered using [sqlite3_wal_hook()]. ^Likewise, registering a callback
-** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism
+** registered using [tdsqlite3_wal_hook()]. ^Likewise, registering a callback
+** using [tdsqlite3_wal_hook()] disables the automatic checkpoint mechanism
** configured by this function.
**
** ^The [wal_autocheckpoint pragma] can be used to invoke this interface
** from SQL.
**
** ^Checkpoints initiated by this mechanism are
-** [sqlite3_wal_checkpoint_v2|PASSIVE].
+** [tdsqlite3_wal_checkpoint_v2|PASSIVE].
**
** ^Every new [database connection] defaults to having the auto-checkpoint
** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT]
@@ -7773,35 +8878,35 @@ SQLITE_API void *sqlite3_wal_hook(
** is only necessary if the default setting is found to be suboptimal
** for a particular application.
*/
-SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);
+SQLITE_API int tdsqlite3_wal_autocheckpoint(tdsqlite3 *db, int N);
/*
** CAPI3REF: Checkpoint a database
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to
-** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^
+** ^(The tdsqlite3_wal_checkpoint(D,X) is equivalent to
+** [tdsqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^
**
-** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the
+** In brief, tdsqlite3_wal_checkpoint(D,X) causes the content in the
** [write-ahead log] for database X on [database connection] D to be
** transferred into the database file and for the write-ahead log to
** be reset. See the [checkpointing] documentation for addition
** information.
**
** This interface used to be the only way to cause a checkpoint to
-** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()]
+** occur. But then the newer and more powerful [tdsqlite3_wal_checkpoint_v2()]
** interface was added. This interface is retained for backwards
** compatibility and as a convenience for applications that need to manually
** start a callback but which do not need the full power (and corresponding
-** complication) of [sqlite3_wal_checkpoint_v2()].
+** complication) of [tdsqlite3_wal_checkpoint_v2()].
*/
-SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
+SQLITE_API int tdsqlite3_wal_checkpoint(tdsqlite3 *db, const char *zDb);
/*
** CAPI3REF: Checkpoint a database
-** METHOD: sqlite3
+** METHOD: tdsqlite3
**
-** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint
+** ^(The tdsqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint
** operation on database X of [database connection] D in mode M. Status
** information is written back into integers pointed to by L and C.)^
** ^(The M parameter must be a valid [checkpoint mode]:)^
@@ -7817,7 +8922,7 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
**
** <dt>SQLITE_CHECKPOINT_FULL<dd>
** ^This mode blocks (it invokes the
-** [sqlite3_busy_handler|busy-handler callback]) until there is no
+** [tdsqlite3_busy_handler|busy-handler callback]) until there is no
** database writer and all readers are reading from the most recent database
** snapshot. ^It then checkpoints all frames in the log file and syncs the
** database file. ^This mode blocks new database writers while it is pending,
@@ -7882,15 +8987,15 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);
** attached database, SQLITE_ERROR is returned to the caller.
**
** ^Unless it returns SQLITE_MISUSE,
-** the sqlite3_wal_checkpoint_v2() interface
+** the tdsqlite3_wal_checkpoint_v2() interface
** sets the error information that is queried by
-** [sqlite3_errcode()] and [sqlite3_errmsg()].
+** [tdsqlite3_errcode()] and [tdsqlite3_errmsg()].
**
** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface
** from SQL.
*/
-SQLITE_API int sqlite3_wal_checkpoint_v2(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3_wal_checkpoint_v2(
+ tdsqlite3 *db, /* Database handle */
const char *zDb, /* Name of attached database (or NULL) */
int eMode, /* SQLITE_CHECKPOINT_* value */
int *pnLog, /* OUT: Size of WAL log in frames */
@@ -7902,8 +9007,8 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
** KEYWORDS: {checkpoint mode}
**
** These constants define all valid values for the "checkpoint mode" passed
-** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface.
-** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the
+** as the third parameter to the [tdsqlite3_wal_checkpoint_v2()] interface.
+** See the [tdsqlite3_wal_checkpoint_v2()] documentation for details on the
** meaning of each of these checkpoint modes.
*/
#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */
@@ -7921,25 +9026,32 @@ SQLITE_API int sqlite3_wal_checkpoint_v2(
** If this interface is invoked outside the context of an xConnect or
** xCreate virtual table method then the behavior is undefined.
**
-** At present, there is only one option that may be configured using
-** this function. (See [SQLITE_VTAB_CONSTRAINT_SUPPORT].) Further options
-** may be added in the future.
+** In the call tdsqlite3_vtab_config(D,C,...) the D parameter is the
+** [database connection] in which the virtual table is being created and
+** which is passed in as the first argument to the [xConnect] or [xCreate]
+** method that is invoking tdsqlite3_vtab_config(). The C parameter is one
+** of the [virtual table configuration options]. The presence and meaning
+** of parameters after C depend on which [virtual table configuration option]
+** is used.
*/
-SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
+SQLITE_API int tdsqlite3_vtab_config(tdsqlite3*, int op, ...);
/*
** CAPI3REF: Virtual Table Configuration Options
+** KEYWORDS: {virtual table configuration options}
+** KEYWORDS: {virtual table configuration option}
**
** These macros define the various options to the
-** [sqlite3_vtab_config()] interface that [virtual table] implementations
+** [tdsqlite3_vtab_config()] interface that [virtual table] implementations
** can use to customize and optimize their behavior.
**
** <dl>
-** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT
+** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]]
+** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT</dt>
** <dd>Calls of the form
-** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported,
+** [tdsqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported,
** where X is an integer. If X is zero, then the [virtual table] whose
-** [xCreate] or [xConnect] method invoked [sqlite3_vtab_config()] does not
+** [xCreate] or [xConnect] method invoked [tdsqlite3_vtab_config()] does not
** support constraints. In this configuration (which is the default) if
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
@@ -7958,15 +9070,37 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
**
** Virtual table implementations that are required to handle OR REPLACE
** must do so within the [xUpdate] method. If a call to the
-** [sqlite3_vtab_on_conflict()] function indicates that the current ON
+** [tdsqlite3_vtab_on_conflict()] function indicates that the current ON
** CONFLICT policy is REPLACE, the virtual table implementation should
** silently replace the appropriate rows within the xUpdate callback and
** return SQLITE_OK. Or, if this is not possible, it may return
** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT
** constraint handling.
+** </dd>
+**
+** [[SQLITE_VTAB_DIRECTONLY]]<dt>SQLITE_VTAB_DIRECTONLY</dt>
+** <dd>Calls of the form
+** [tdsqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the
+** the [xConnect] or [xCreate] methods of a [virtual table] implmentation
+** prohibits that virtual table from being used from within triggers and
+** views.
+** </dd>
+**
+** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
+** <dd>Calls of the form
+** [tdsqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
+** the [xConnect] or [xCreate] methods of a [virtual table] implmentation
+** identify that virtual table as being safe to use from within triggers
+** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
+** virtual table can do no serious harm even if it is controlled by a
+** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS
+** flag unless absolutely necessary.
+** </dd>
** </dl>
*/
#define SQLITE_VTAB_CONSTRAINT_SUPPORT 1
+#define SQLITE_VTAB_INNOCUOUS 2
+#define SQLITE_VTAB_DIRECTONLY 3
/*
** CAPI3REF: Determine The Virtual Table Conflict Policy
@@ -7978,22 +9112,56 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
** of the SQL statement that triggered the call to the [xUpdate] method of the
** [virtual table].
*/
-SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
+SQLITE_API int tdsqlite3_vtab_on_conflict(tdsqlite3 *);
+
+/*
+** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE
+**
+** If the tdsqlite3_vtab_nochange(X) routine is called within the [xColumn]
+** method of a [virtual table], then it returns true if and only if the
+** column is being fetched as part of an UPDATE operation during which the
+** column value will not change. Applications might use this to substitute
+** a return value that is less expensive to compute and that the corresponding
+** [xUpdate] method understands as a "no-change" value.
+**
+** If the [xColumn] method calls tdsqlite3_vtab_nochange() and finds that
+** the column is not changed by the UPDATE statement, then the xColumn
+** method can optionally return without setting a result, without calling
+** any of the [tdsqlite3_result_int|tdsqlite3_result_xxxxx() interfaces].
+** In that case, [tdsqlite3_value_nochange(X)] will return true for the
+** same column in the [xUpdate] method.
+*/
+SQLITE_API int tdsqlite3_vtab_nochange(tdsqlite3_context*);
+
+/*
+** CAPI3REF: Determine The Collation For a Virtual Table Constraint
+**
+** This function may only be called from within a call to the [xBestIndex]
+** method of a [virtual table].
+**
+** The first argument must be the tdsqlite3_index_info object that is the
+** first parameter to the xBestIndex() method. The second argument must be
+** an index into the aConstraint[] array belonging to the tdsqlite3_index_info
+** structure passed to xBestIndex. This function returns a pointer to a buffer
+** containing the name of the collation sequence for the corresponding
+** constraint.
+*/
+SQLITE_API SQLITE_EXPERIMENTAL const char *tdsqlite3_vtab_collation(tdsqlite3_index_info*,int);
/*
** CAPI3REF: Conflict resolution modes
** KEYWORDS: {conflict resolution mode}
**
-** These constants are returned by [sqlite3_vtab_on_conflict()] to
+** These constants are returned by [tdsqlite3_vtab_on_conflict()] to
** inform a [virtual table] implementation what the [ON CONFLICT] mode
** is for the SQL statement being evaluated.
**
** Note that the [SQLITE_IGNORE] constant is also used as a potential
-** return value from the [sqlite3_set_authorizer()] callback and that
+** return value from the [tdsqlite3_set_authorizer()] callback and that
** [SQLITE_ABORT] is also a [result code].
*/
#define SQLITE_ROLLBACK 1
-/* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */
+/* #define SQLITE_IGNORE 2 // Also used by tdsqlite3_authorizer() callback */
#define SQLITE_FAIL 3
/* #define SQLITE_ABORT 4 // Also an error code */
#define SQLITE_REPLACE 5
@@ -8003,8 +9171,8 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** KEYWORDS: {scanstatus options}
**
** The following constants can be used for the T parameter to the
-** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a
-** different metric for sqlite3_stmt_scanstatus() to return.
+** [tdsqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a
+** different metric for tdsqlite3_stmt_scanstatus() to return.
**
** When the value returned to V is a string, space to hold that string is
** managed by the prepared statement S and will be automatically freed when
@@ -8012,15 +9180,15 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
**
** <dl>
** [[SQLITE_SCANSTAT_NLOOP]] <dt>SQLITE_SCANSTAT_NLOOP</dt>
-** <dd>^The [sqlite3_int64] variable pointed to by the T parameter will be
+** <dd>^The [tdsqlite3_int64] variable pointed to by the V parameter will be
** set to the total number of times that the X-th loop has run.</dd>
**
** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt>
-** <dd>^The [sqlite3_int64] variable pointed to by the T parameter will be set
+** <dd>^The [tdsqlite3_int64] variable pointed to by the V parameter will be set
** to the total number of rows examined by all iterations of the X-th loop.</dd>
**
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
-** <dd>^The "double" variable pointed to by the T parameter will be set to the
+** <dd>^The "double" variable pointed to by the V parameter will be set to the
** query planner's estimate for the average number of rows output from each
** iteration of the X-th loop. If the query planner's estimates was accurate,
** then this value will approximate the quotient NVISIT/NLOOP and the
@@ -8028,17 +9196,17 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** be the NLOOP value for the current loop.
**
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
-** <dd>^The "const char *" variable pointed to by the T parameter will be set
+** <dd>^The "const char *" variable pointed to by the V parameter will be set
** to a zero-terminated UTF-8 string containing the name of the index or table
** used for the X-th loop.
**
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
-** <dd>^The "const char *" variable pointed to by the T parameter will be set
+** <dd>^The "const char *" variable pointed to by the V parameter will be set
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
** description for the X-th loop.
**
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECT</dt>
-** <dd>^The "int" variable pointed to by the T parameter will be set to the
+** <dd>^The "int" variable pointed to by the V parameter will be set to the
** "select-id" for the X-th loop. The select-id identifies which query or
** subquery the loop is part of. The main query has a select-id of zero.
** The select-id is the same value as is output in the first column
@@ -8054,7 +9222,7 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
/*
** CAPI3REF: Prepared Statement Scan Status
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
** This interface returns information about the predicted and measured
** performance for pStmt. Advanced applications can use this
@@ -8081,10 +9249,10 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** as if the loop did not exist - it returns non-zero and leave the variable
** that pOut points to unchanged.
**
-** See also: [sqlite3_stmt_scanstatus_reset()]
+** See also: [tdsqlite3_stmt_scanstatus_reset()]
*/
-SQLITE_API int sqlite3_stmt_scanstatus(
- sqlite3_stmt *pStmt, /* Prepared statement for which info desired */
+SQLITE_API int tdsqlite3_stmt_scanstatus(
+ tdsqlite3_stmt *pStmt, /* Prepared statement for which info desired */
int idx, /* Index of loop to report on */
int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */
void *pOut /* Result written here */
@@ -8092,24 +9260,24 @@ SQLITE_API int sqlite3_stmt_scanstatus(
/*
** CAPI3REF: Zero Scan-Status Counters
-** METHOD: sqlite3_stmt
+** METHOD: tdsqlite3_stmt
**
-** ^Zero all [sqlite3_stmt_scanstatus()] related event counters.
+** ^Zero all [tdsqlite3_stmt_scanstatus()] related event counters.
**
** This API is only available if the library is built with pre-processor
** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined.
*/
-SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
+SQLITE_API void tdsqlite3_stmt_scanstatus_reset(tdsqlite3_stmt*);
/*
** CAPI3REF: Flush caches to disk mid-transaction
**
** ^If a write-transaction is open on [database connection] D when the
-** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
+** [tdsqlite3_db_cacheflush(D)] interface invoked, any dirty
** pages in the pager-cache that are not currently in use are written out
** to disk. A dirty page may be in use if a database cursor created by an
** active SQL statement is reading from it, or if it is page 1 of a database
-** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)]
+** file (page 1 is always "in use"). ^The [tdsqlite3_db_cacheflush(D)]
** interface flushes caches for all schemas - "main", "temp", and
** any [attached] databases.
**
@@ -8126,12 +9294,12 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
** example an IO error or out-of-memory condition), then processing is
** abandoned and an SQLite [error code] is returned to the caller immediately.
**
-** ^Otherwise, if no error occurs, [sqlite3_db_cacheflush()] returns SQLITE_OK.
+** ^Otherwise, if no error occurs, [tdsqlite3_db_cacheflush()] returns SQLITE_OK.
**
** ^This function does not set the database handle error code or message
-** returned by the [sqlite3_errcode()] and [sqlite3_errmsg()] functions.
+** returned by the [tdsqlite3_errcode()] and [tdsqlite3_errmsg()] functions.
*/
-SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
+SQLITE_API int tdsqlite3_db_cacheflush(tdsqlite3*);
/*
** CAPI3REF: The pre-update hook.
@@ -8139,20 +9307,20 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** ^These interfaces are only available if SQLite is compiled using the
** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option.
**
-** ^The [sqlite3_preupdate_hook()] interface registers a callback function
+** ^The [tdsqlite3_preupdate_hook()] interface registers a callback function
** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation
-** on a [rowid table].
+** on a database table.
** ^At most one preupdate hook may be registered at a time on a single
-** [database connection]; each call to [sqlite3_preupdate_hook()] overrides
+** [database connection]; each call to [tdsqlite3_preupdate_hook()] overrides
** the previous setting.
-** ^The preupdate hook is disabled by invoking [sqlite3_preupdate_hook()]
+** ^The preupdate hook is disabled by invoking [tdsqlite3_preupdate_hook()]
** with a NULL pointer as the second parameter.
-** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as
+** ^The third parameter to [tdsqlite3_preupdate_hook()] is passed through as
** the first parameter to callbacks.
**
-** ^The preupdate hook only fires for changes to [rowid tables]; the preupdate
-** hook is not invoked for changes to [virtual tables] or [WITHOUT ROWID]
-** tables.
+** ^The preupdate hook only fires for changes to real database tables; the
+** preupdate hook is not invoked for changes to [virtual tables] or to
+** system tables like sqlite_master or sqlite_stat1.
**
** ^The second parameter to the preupdate callback is a pointer to
** the [database connection] that registered the preupdate hook.
@@ -8166,15 +9334,19 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** databases.)^
** ^The fifth parameter to the preupdate callback is the name of the
** table that is being modified.
-** ^The sixth parameter to the preupdate callback is the initial [rowid] of the
-** row being changes for SQLITE_UPDATE and SQLITE_DELETE changes and is
-** undefined for SQLITE_INSERT changes.
-** ^The seventh parameter to the preupdate callback is the final [rowid] of
-** the row being changed for SQLITE_UPDATE and SQLITE_INSERT changes and is
-** undefined for SQLITE_DELETE changes.
-**
-** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()],
-** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces
+**
+** For an UPDATE or DELETE operation on a [rowid table], the sixth
+** parameter passed to the preupdate callback is the initial [rowid] of the
+** row being modified or deleted. For an INSERT operation on a rowid table,
+** or any operation on a WITHOUT ROWID table, the value of the sixth
+** parameter is undefined. For an INSERT or UPDATE on a rowid table the
+** seventh parameter is the final rowid value of the row being inserted
+** or updated. The value of the seventh parameter passed to the callback
+** function is not defined for operations on WITHOUT ROWID tables, or for
+** INSERT operations on rowid tables.
+**
+** The [tdsqlite3_preupdate_old()], [tdsqlite3_preupdate_new()],
+** [tdsqlite3_preupdate_count()], and [tdsqlite3_preupdate_depth()] interfaces
** provide additional information about a preupdate event. These routines
** may only be called from within a preupdate callback. Invoking any of
** these routines from outside of a preupdate callback or with a
@@ -8182,52 +9354,54 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
** to the preupdate callback results in undefined and probably undesirable
** behavior.
**
-** ^The [sqlite3_preupdate_count(D)] interface returns the number of columns
+** ^The [tdsqlite3_preupdate_count(D)] interface returns the number of columns
** in the row that is being inserted, updated, or deleted.
**
-** ^The [sqlite3_preupdate_old(D,N,P)] interface writes into P a pointer to
-** a [protected sqlite3_value] that contains the value of the Nth column of
+** ^The [tdsqlite3_preupdate_old(D,N,P)] interface writes into P a pointer to
+** a [protected tdsqlite3_value] that contains the value of the Nth column of
** the table row before it is updated. The N parameter must be between 0
** and one less than the number of columns or the behavior will be
** undefined. This must only be used within SQLITE_UPDATE and SQLITE_DELETE
** preupdate callbacks; if it is used by an SQLITE_INSERT callback then the
-** behavior is undefined. The [sqlite3_value] that P points to
+** behavior is undefined. The [tdsqlite3_value] that P points to
** will be destroyed when the preupdate callback returns.
**
-** ^The [sqlite3_preupdate_new(D,N,P)] interface writes into P a pointer to
-** a [protected sqlite3_value] that contains the value of the Nth column of
+** ^The [tdsqlite3_preupdate_new(D,N,P)] interface writes into P a pointer to
+** a [protected tdsqlite3_value] that contains the value of the Nth column of
** the table row after it is updated. The N parameter must be between 0
** and one less than the number of columns or the behavior will be
** undefined. This must only be used within SQLITE_INSERT and SQLITE_UPDATE
** preupdate callbacks; if it is used by an SQLITE_DELETE callback then the
-** behavior is undefined. The [sqlite3_value] that P points to
+** behavior is undefined. The [tdsqlite3_value] that P points to
** will be destroyed when the preupdate callback returns.
**
-** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate
+** ^The [tdsqlite3_preupdate_depth(D)] interface returns 0 if the preupdate
** callback was invoked as a result of a direct insert, update, or delete
** operation; or 1 for inserts, updates, or deletes invoked by top-level
** triggers; or 2 for changes resulting from triggers called by top-level
** triggers; and so forth.
**
-** See also: [sqlite3_update_hook()]
+** See also: [tdsqlite3_update_hook()]
*/
-SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook(
- sqlite3 *db,
+#if defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+SQLITE_API void *tdsqlite3_preupdate_hook(
+ tdsqlite3 *db,
void(*xPreUpdate)(
void *pCtx, /* Copy of third arg to preupdate_hook() */
- sqlite3 *db, /* Database handle */
+ tdsqlite3 *db, /* Database handle */
int op, /* SQLITE_UPDATE, DELETE or INSERT */
char const *zDb, /* Database name */
char const *zName, /* Table name */
- sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */
- sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */
+ tdsqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */
+ tdsqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */
),
void*
);
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *);
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_depth(sqlite3 *);
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **);
+SQLITE_API int tdsqlite3_preupdate_old(tdsqlite3 *, int, tdsqlite3_value **);
+SQLITE_API int tdsqlite3_preupdate_count(tdsqlite3 *);
+SQLITE_API int tdsqlite3_preupdate_depth(tdsqlite3 *);
+SQLITE_API int tdsqlite3_preupdate_new(tdsqlite3 *, int, tdsqlite3_value **);
+#endif
/*
** CAPI3REF: Low-level system error code
@@ -8235,16 +9409,15 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3
** ^Attempt to return the underlying operating system error code or error
** number that caused the most recent I/O error or failure to open a file.
** The return value is OS-dependent. For example, on unix systems, after
-** [sqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be
+** [tdsqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be
** called to get back the underlying "errno" that caused the problem, such
** as ENOSPC, EAUTH, EISDIR, and so forth.
*/
-SQLITE_API int sqlite3_system_errno(sqlite3*);
+SQLITE_API int tdsqlite3_system_errno(tdsqlite3*);
/*
** CAPI3REF: Database Snapshot
-** KEYWORDS: {snapshot}
-** EXPERIMENTAL
+** KEYWORDS: {snapshot} {tdsqlite3_snapshot}
**
** An instance of the snapshot object records the state of a [WAL mode]
** database for some specific point in history.
@@ -8257,65 +9430,96 @@ SQLITE_API int sqlite3_system_errno(sqlite3*);
** Subsequent changes to the database from other connections are not seen
** by the reader until a new read transaction is started.
**
-** The sqlite3_snapshot object records state information about an historical
+** The tdsqlite3_snapshot object records state information about an historical
** version of the database file so that it is possible to later open a new read
** transaction that sees that historical version of the database rather than
** the most recent version.
-**
-** The constructor for this object is [sqlite3_snapshot_get()]. The
-** [sqlite3_snapshot_open()] method causes a fresh read transaction to refer
-** to an historical snapshot (if possible). The destructor for
-** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
*/
-typedef struct sqlite3_snapshot sqlite3_snapshot;
+typedef struct tdsqlite3_snapshot {
+ unsigned char hidden[48];
+} tdsqlite3_snapshot;
/*
** CAPI3REF: Record A Database Snapshot
-** EXPERIMENTAL
+** CONSTRUCTOR: tdsqlite3_snapshot
**
-** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
-** new [sqlite3_snapshot] object that records the current state of
+** ^The [tdsqlite3_snapshot_get(D,S,P)] interface attempts to make a
+** new [tdsqlite3_snapshot] object that records the current state of
** schema S in database connection D. ^On success, the
-** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
-** created [sqlite3_snapshot] object into *P and returns SQLITE_OK.
-** ^If schema S of [database connection] D is not a [WAL mode] database
-** that is in a read transaction, then [sqlite3_snapshot_get(D,S,P)]
-** leaves the *P value unchanged and returns an appropriate [error code].
-**
-** The [sqlite3_snapshot] object returned from a successful call to
-** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
+** [tdsqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
+** created [tdsqlite3_snapshot] object into *P and returns SQLITE_OK.
+** If there is not already a read-transaction open on schema S when
+** this function is called, one is opened automatically.
+**
+** The following must be true for this function to succeed. If any of
+** the following statements are false when tdsqlite3_snapshot_get() is
+** called, SQLITE_ERROR is returned. The final value of *P is undefined
+** in this case.
+**
+** <ul>
+** <li> The database handle must not be in [autocommit mode].
+**
+** <li> Schema S of [database connection] D must be a [WAL mode] database.
+**
+** <li> There must not be a write transaction open on schema S of database
+** connection D.
+**
+** <li> One or more transactions must have been written to the current wal
+** file since it was created on disk (by any connection). This means
+** that a snapshot cannot be taken on a wal mode database with no wal
+** file immediately after it is first opened. At least one transaction
+** must be written to it first.
+** </ul>
+**
+** This function may also return SQLITE_NOMEM. If it is called with the
+** database handle in autocommit mode but fails for some other reason,
+** whether or not a read transaction is opened on schema S is undefined.
+**
+** The [tdsqlite3_snapshot] object returned from a successful call to
+** [tdsqlite3_snapshot_get()] must be freed using [tdsqlite3_snapshot_free()]
** to avoid a memory leak.
**
-** The [sqlite3_snapshot_get()] interface is only available when the
-** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
+** The [tdsqlite3_snapshot_get()] interface is only available when the
+** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
- sqlite3 *db,
+SQLITE_API SQLITE_EXPERIMENTAL int tdsqlite3_snapshot_get(
+ tdsqlite3 *db,
const char *zSchema,
- sqlite3_snapshot **ppSnapshot
+ tdsqlite3_snapshot **ppSnapshot
);
/*
** CAPI3REF: Start a read transaction on an historical snapshot
-** EXPERIMENTAL
-**
-** ^The [sqlite3_snapshot_open(D,S,P)] interface starts a
-** read transaction for schema S of
-** [database connection] D such that the read transaction
-** refers to historical [snapshot] P, rather than the most
-** recent change to the database.
-** ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK on success
-** or an appropriate [error code] if it fails.
-**
-** ^In order to succeed, a call to [sqlite3_snapshot_open(D,S,P)] must be
-** the first operation following the [BEGIN] that takes the schema S
-** out of [autocommit mode].
-** ^In other words, schema S must not currently be in
-** a transaction for [sqlite3_snapshot_open(D,S,P)] to work, but the
-** database connection D must be out of [autocommit mode].
-** ^A [snapshot] will fail to open if it has been overwritten by a
-** [checkpoint].
-** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the
+** METHOD: tdsqlite3_snapshot
+**
+** ^The [tdsqlite3_snapshot_open(D,S,P)] interface either starts a new read
+** transaction or upgrades an existing one for schema S of
+** [database connection] D such that the read transaction refers to
+** historical [snapshot] P, rather than the most recent change to the
+** database. ^The [tdsqlite3_snapshot_open()] interface returns SQLITE_OK
+** on success or an appropriate [error code] if it fails.
+**
+** ^In order to succeed, the database connection must not be in
+** [autocommit mode] when [tdsqlite3_snapshot_open(D,S,P)] is called. If there
+** is already a read transaction open on schema S, then the database handle
+** must have no active statements (SELECT statements that have been passed
+** to tdsqlite3_step() but not tdsqlite3_reset() or tdsqlite3_finalize()).
+** SQLITE_ERROR is returned if either of these conditions is violated, or
+** if schema S does not exist, or if the snapshot object is invalid.
+**
+** ^A call to tdsqlite3_snapshot_open() will fail to open if the specified
+** snapshot has been overwritten by a [checkpoint]. In this case
+** SQLITE_ERROR_SNAPSHOT is returned.
+**
+** If there is already a read transaction open when this function is
+** invoked, then the same read transaction remains open (on the same
+** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT
+** is returned. If another error code - for example SQLITE_PROTOCOL or an
+** SQLITE_IOERR error code - is returned, then the final state of the
+** read transaction is undefined. If SQLITE_OK is returned, then the
+** read transaction is now open on database snapshot P.
+**
+** ^(A call to [tdsqlite3_snapshot_open(D,S,P)] will fail if the
** database connection D does not know that the database file for
** schema S is in [WAL mode]. A database connection might not know
** that the database file is in [WAL mode] if there has been no prior
@@ -8324,40 +9528,40 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
** (Hint: Run "[PRAGMA application_id]" against a newly opened
** database connection in order to make it ready to use snapshots.)
**
-** The [sqlite3_snapshot_open()] interface is only available when the
-** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
+** The [tdsqlite3_snapshot_open()] interface is only available when the
+** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
- sqlite3 *db,
+SQLITE_API SQLITE_EXPERIMENTAL int tdsqlite3_snapshot_open(
+ tdsqlite3 *db,
const char *zSchema,
- sqlite3_snapshot *pSnapshot
+ tdsqlite3_snapshot *pSnapshot
);
/*
** CAPI3REF: Destroy a snapshot
-** EXPERIMENTAL
+** DESTRUCTOR: tdsqlite3_snapshot
**
-** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P.
-** The application must eventually free every [sqlite3_snapshot] object
+** ^The [tdsqlite3_snapshot_free(P)] interface destroys [tdsqlite3_snapshot] P.
+** The application must eventually free every [tdsqlite3_snapshot] object
** using this routine to avoid a memory leak.
**
-** The [sqlite3_snapshot_free()] interface is only available when the
-** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
+** The [tdsqlite3_snapshot_free()] interface is only available when the
+** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
-SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
+SQLITE_API SQLITE_EXPERIMENTAL void tdsqlite3_snapshot_free(tdsqlite3_snapshot*);
/*
** CAPI3REF: Compare the ages of two snapshot handles.
-** EXPERIMENTAL
+** METHOD: tdsqlite3_snapshot
**
-** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages
+** The tdsqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages
** of two valid snapshot handles.
**
** If the two snapshot handles are not associated with the same database
** file, the result of the comparison is undefined.
**
** Additionally, the result of the comparison is only valid if both of the
-** snapshot handles were obtained by calling sqlite3_snapshot_get() since the
+** snapshot handles were obtained by calling tdsqlite3_snapshot_get() since the
** last time the wal file was deleted. The wal file is deleted when the
** database is changed back to rollback mode or when the number of database
** clients drops to zero. If either snapshot handle was obtained before the
@@ -8367,13 +9571,163 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
** Otherwise, this API returns a negative value if P1 refers to an older
** snapshot than P2, zero if the two handles refer to the same database
** snapshot, and a positive value if P1 is a newer snapshot than P2.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_SNAPSHOT] option.
*/
-SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
- sqlite3_snapshot *p1,
- sqlite3_snapshot *p2
+SQLITE_API SQLITE_EXPERIMENTAL int tdsqlite3_snapshot_cmp(
+ tdsqlite3_snapshot *p1,
+ tdsqlite3_snapshot *p2
+);
+
+/*
+** CAPI3REF: Recover snapshots from a wal file
+** METHOD: tdsqlite3_snapshot
+**
+** If a [WAL file] remains on disk after all database connections close
+** (either through the use of the [SQLITE_FCNTL_PERSIST_WAL] [file control]
+** or because the last process to have the database opened exited without
+** calling [tdsqlite3_close()]) and a new connection is subsequently opened
+** on that database and [WAL file], the [tdsqlite3_snapshot_open()] interface
+** will only be able to open the last transaction added to the WAL file
+** even though the WAL file contains other valid transactions.
+**
+** This function attempts to scan the WAL file associated with database zDb
+** of database handle db and make all valid snapshots available to
+** tdsqlite3_snapshot_open(). It is an error if there is already a read
+** transaction open on the database, or if the database is not a WAL mode
+** database.
+**
+** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_SNAPSHOT] option.
+*/
+SQLITE_API SQLITE_EXPERIMENTAL int tdsqlite3_snapshot_recover(tdsqlite3 *db, const char *zDb);
+
+/*
+** CAPI3REF: Serialize a database
+**
+** The tdsqlite3_serialize(D,S,P,F) interface returns a pointer to memory
+** that is a serialization of the S database on [database connection] D.
+** If P is not a NULL pointer, then the size of the database in bytes
+** is written into *P.
+**
+** For an ordinary on-disk database file, the serialization is just a
+** copy of the disk file. For an in-memory database or a "TEMP" database,
+** the serialization is the same sequence of bytes which would be written
+** to disk if that database where backed up to disk.
+**
+** The usual case is that tdsqlite3_serialize() copies the serialization of
+** the database into memory obtained from [tdsqlite3_malloc64()] and returns
+** a pointer to that memory. The caller is responsible for freeing the
+** returned value to avoid a memory leak. However, if the F argument
+** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
+** are made, and the tdsqlite3_serialize() function will return a pointer
+** to the contiguous memory representation of the database that SQLite
+** is currently using for that database, or NULL if the no such contiguous
+** memory representation of the database exists. A contiguous memory
+** representation of the database will usually only exist if there has
+** been a prior call to [tdsqlite3_deserialize(D,S,...)] with the same
+** values of D and S.
+** The size of the database is written into *P even if the
+** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy
+** of the database exists.
+**
+** A call to tdsqlite3_serialize(D,S,P,F) might return NULL even if the
+** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory
+** allocation error occurs.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_DESERIALIZE] option.
+*/
+SQLITE_API unsigned char *tdsqlite3_serialize(
+ tdsqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which DB to serialize. ex: "main", "temp", ... */
+ tdsqlite3_int64 *piSize, /* Write size of the DB here, if not NULL */
+ unsigned int mFlags /* Zero or more SQLITE_SERIALIZE_* flags */
);
/*
+** CAPI3REF: Flags for tdsqlite3_serialize
+**
+** Zero or more of the following constants can be OR-ed together for
+** the F argument to [tdsqlite3_serialize(D,S,P,F)].
+**
+** SQLITE_SERIALIZE_NOCOPY means that [tdsqlite3_serialize()] will return
+** a pointer to contiguous in-memory database that it is currently using,
+** without making a copy of the database. If SQLite is not currently using
+** a contiguous in-memory database, then this option causes
+** [tdsqlite3_serialize()] to return a NULL pointer. SQLite will only be
+** using a contiguous in-memory database if it has been initialized by a
+** prior call to [tdsqlite3_deserialize()].
+*/
+#define SQLITE_SERIALIZE_NOCOPY 0x001 /* Do no memory allocations */
+
+/*
+** CAPI3REF: Deserialize a database
+**
+** The tdsqlite3_deserialize(D,S,P,N,M,F) interface causes the
+** [database connection] D to disconnect from database S and then
+** reopen S as an in-memory database based on the serialization contained
+** in P. The serialized database P is N bytes in size. M is the size of
+** the buffer P, which might be larger than N. If M is larger than N, and
+** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is
+** permitted to add content to the in-memory database as long as the total
+** size does not exceed M bytes.
+**
+** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will
+** invoke tdsqlite3_free() on the serialization buffer when the database
+** connection closes. If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then
+** SQLite will try to increase the buffer size using tdsqlite3_realloc64()
+** if writes on the database cause it to grow larger than M bytes.
+**
+** The tdsqlite3_deserialize() interface will fail with SQLITE_BUSY if the
+** database is currently in a read transaction or is involved in a backup
+** operation.
+**
+** If tdsqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the
+** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then
+** [tdsqlite3_free()] is invoked on argument P prior to returning.
+**
+** This interface is only available if SQLite is compiled with the
+** [SQLITE_ENABLE_DESERIALIZE] option.
+*/
+SQLITE_API int tdsqlite3_deserialize(
+ tdsqlite3 *db, /* The database connection */
+ const char *zSchema, /* Which DB to reopen with the deserialization */
+ unsigned char *pData, /* The serialized database content */
+ tdsqlite3_int64 szDb, /* Number bytes in the deserialization */
+ tdsqlite3_int64 szBuf, /* Total size of buffer pData[] */
+ unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
+);
+
+/*
+** CAPI3REF: Flags for tdsqlite3_deserialize()
+**
+** The following are allowed values for 6th argument (the F argument) to
+** the [tdsqlite3_deserialize(D,S,P,N,M,F)] interface.
+**
+** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
+** in the P argument is held in memory obtained from [tdsqlite3_malloc64()]
+** and that SQLite should take ownership of this memory and automatically
+** free it when it has finished using it. Without this flag, the caller
+** is responsible for freeing any dynamically allocated memory.
+**
+** The SQLITE_DESERIALIZE_RESIZEABLE flag means that SQLite is allowed to
+** grow the size of the database using calls to [tdsqlite3_realloc64()]. This
+** flag should only be used if SQLITE_DESERIALIZE_FREEONCLOSE is also used.
+** Without this flag, the deserialized database cannot increase in size beyond
+** the number of bytes specified by the M parameter.
+**
+** The SQLITE_DESERIALIZE_READONLY flag means that the deserialized database
+** should be treated as read-only.
+*/
+#define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call tdsqlite3_free() on close */
+#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using tdsqlite3_realloc64() */
+#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
+
+/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
@@ -8386,7 +9740,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
#endif
#endif /* SQLITE3_H */
-/******** Begin file sqlite3rtree.h *********/
+/******** Begin file tdsqlite3rtree.h *********/
/*
** 2010 August 30
**
@@ -8408,16 +9762,16 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
extern "C" {
#endif
-typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry;
-typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info;
+typedef struct tdsqlite3_rtree_geometry tdsqlite3_rtree_geometry;
+typedef struct tdsqlite3_rtree_query_info tdsqlite3_rtree_query_info;
/* The double-precision datatype used by RTree depends on the
** SQLITE_RTREE_INT_ONLY compile-time option.
*/
#ifdef SQLITE_RTREE_INT_ONLY
- typedef sqlite3_int64 sqlite3_rtree_dbl;
+ typedef tdsqlite3_int64 tdsqlite3_rtree_dbl;
#else
- typedef double sqlite3_rtree_dbl;
+ typedef double tdsqlite3_rtree_dbl;
#endif
/*
@@ -8426,10 +9780,10 @@ typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info;
**
** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zGeom(... params ...)
*/
-SQLITE_API int sqlite3_rtree_geometry_callback(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_rtree_geometry_callback(
+ tdsqlite3 *db,
const char *zGeom,
- int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*,int*),
+ int (*xGeom)(tdsqlite3_rtree_geometry*, int, tdsqlite3_rtree_dbl*,int*),
void *pContext
);
@@ -8438,10 +9792,10 @@ SQLITE_API int sqlite3_rtree_geometry_callback(
** A pointer to a structure of the following type is passed as the first
** argument to callbacks registered using rtree_geometry_callback().
*/
-struct sqlite3_rtree_geometry {
+struct tdsqlite3_rtree_geometry {
void *pContext; /* Copy of pContext passed to s_r_g_c() */
int nParam; /* Size of array aParam[] */
- sqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */
+ tdsqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */
void *pUser; /* Callback implementation user data */
void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */
};
@@ -8452,10 +9806,10 @@ struct sqlite3_rtree_geometry {
**
** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zQueryFunc(... params ...)
*/
-SQLITE_API int sqlite3_rtree_query_callback(
- sqlite3 *db,
+SQLITE_API int tdsqlite3_rtree_query_callback(
+ tdsqlite3 *db,
const char *zQueryFunc,
- int (*xQueryFunc)(sqlite3_rtree_query_info*),
+ int (*xQueryFunc)(tdsqlite3_rtree_query_info*),
void *pContext,
void (*xDestructor)(void*)
);
@@ -8464,34 +9818,34 @@ SQLITE_API int sqlite3_rtree_query_callback(
/*
** A pointer to a structure of the following type is passed as the
** argument to scored geometry callback registered using
-** sqlite3_rtree_query_callback().
+** tdsqlite3_rtree_query_callback().
**
** Note that the first 5 fields of this structure are identical to
-** sqlite3_rtree_geometry. This structure is a subclass of
-** sqlite3_rtree_geometry.
+** tdsqlite3_rtree_geometry. This structure is a subclass of
+** tdsqlite3_rtree_geometry.
*/
-struct sqlite3_rtree_query_info {
+struct tdsqlite3_rtree_query_info {
void *pContext; /* pContext from when function registered */
int nParam; /* Number of function parameters */
- sqlite3_rtree_dbl *aParam; /* value of function parameters */
+ tdsqlite3_rtree_dbl *aParam; /* value of function parameters */
void *pUser; /* callback can use this, if desired */
void (*xDelUser)(void*); /* function to free pUser */
- sqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */
+ tdsqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */
unsigned int *anQueue; /* Number of pending entries in the queue */
int nCoord; /* Number of coordinates */
int iLevel; /* Level of current node or entry */
int mxLevel; /* The largest iLevel value in the tree */
- sqlite3_int64 iRowid; /* Rowid for current entry */
- sqlite3_rtree_dbl rParentScore; /* Score of parent node */
+ tdsqlite3_int64 iRowid; /* Rowid for current entry */
+ tdsqlite3_rtree_dbl rParentScore; /* Score of parent node */
int eParentWithin; /* Visibility of parent node */
- int eWithin; /* OUT: Visiblity */
- sqlite3_rtree_dbl rScore; /* OUT: Write the score here */
+ int eWithin; /* OUT: Visibility */
+ tdsqlite3_rtree_dbl rScore; /* OUT: Write the score here */
/* The following fields are only available in 3.8.11 and later */
- sqlite3_value **apSqlParam; /* Original SQL values of parameters */
+ tdsqlite3_value **apSqlParam; /* Original SQL values of parameters */
};
/*
-** Allowed values for sqlite3_rtree_query.eWithin and .eParentWithin.
+** Allowed values for tdsqlite3_rtree_query.eWithin and .eParentWithin.
*/
#define NOT_WITHIN 0 /* Object completely outside of query region */
#define PARTLY_WITHIN 1 /* Object partially overlaps query region */
@@ -8504,8 +9858,8 @@ struct sqlite3_rtree_query_info {
#endif /* ifndef _SQLITE3RTREE_H_ */
-/******** End of sqlite3rtree.h *********/
-/******** Begin file sqlite3session.h *********/
+/******** End of tdsqlite3rtree.h *********/
+/******** Begin file tdsqlite3session.h *********/
#if !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION)
#define __SQLITESESSION_H_ 1
@@ -8520,16 +9874,23 @@ extern "C" {
/*
** CAPI3REF: Session Object Handle
+**
+** An instance of this object is a [session] that can be used to
+** record changes to a database.
*/
-typedef struct sqlite3_session sqlite3_session;
+typedef struct tdsqlite3_session tdsqlite3_session;
/*
** CAPI3REF: Changeset Iterator Handle
+**
+** An instance of this object acts as a cursor for iterating
+** over the elements of a [changeset] or [patchset].
*/
-typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
+typedef struct tdsqlite3_changeset_iter tdsqlite3_changeset_iter;
/*
** CAPI3REF: Create A New Session Object
+** CONSTRUCTOR: tdsqlite3_session
**
** Create a new session object attached to database handle db. If successful,
** a pointer to the new object is written to *ppSession and SQLITE_OK is
@@ -8540,13 +9901,13 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
** database handle.
**
** Session objects created using this function should be deleted using the
-** [sqlite3session_delete()] function before the database handle that they
+** [tdsqlite3session_delete()] function before the database handle that they
** are attached to is itself closed. If the database handle is closed before
** the session object is deleted, then the results of calling any session
-** module function, including [sqlite3session_delete()] on the session object
+** module function, including [tdsqlite3session_delete()] on the session object
** are undefined.
**
-** Because the session module uses the [sqlite3_preupdate_hook()] API, it
+** Because the session module uses the [tdsqlite3_preupdate_hook()] API, it
** is not possible for an application to register a pre-update hook on a
** database handle that has one or more session objects attached. Nor is
** it possible to create a session object attached to a database handle for
@@ -8558,34 +9919,36 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
** attached database. It is not an error if database zDb is not attached
** to the database when the session object is created.
*/
-int sqlite3session_create(
- sqlite3 *db, /* Database handle */
+SQLITE_API int tdsqlite3session_create(
+ tdsqlite3 *db, /* Database handle */
const char *zDb, /* Name of db (e.g. "main") */
- sqlite3_session **ppSession /* OUT: New session object */
+ tdsqlite3_session **ppSession /* OUT: New session object */
);
/*
** CAPI3REF: Delete A Session Object
+** DESTRUCTOR: tdsqlite3_session
**
** Delete a session object previously allocated using
-** [sqlite3session_create()]. Once a session object has been deleted, the
+** [tdsqlite3session_create()]. Once a session object has been deleted, the
** results of attempting to use pSession with any other session module
** function are undefined.
**
** Session objects must be deleted before the database handle to which they
** are attached is closed. Refer to the documentation for
-** [sqlite3session_create()] for details.
+** [tdsqlite3session_create()] for details.
*/
-void sqlite3session_delete(sqlite3_session *pSession);
+SQLITE_API void tdsqlite3session_delete(tdsqlite3_session *pSession);
/*
** CAPI3REF: Enable Or Disable A Session Object
+** METHOD: tdsqlite3_session
**
** Enable or disable the recording of changes by a session object. When
** enabled, a session object records changes made to the database. When
** disabled - it does not. A newly created session object is enabled.
-** Refer to the documentation for [sqlite3session_changeset()] for further
+** Refer to the documentation for [tdsqlite3session_changeset()] for further
** details regarding how enabling and disabling a session object affects
** the eventual changesets.
**
@@ -8596,10 +9959,11 @@ void sqlite3session_delete(sqlite3_session *pSession);
** The return value indicates the final state of the session object: 0 if
** the session is disabled, or 1 if it is enabled.
*/
-int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
+SQLITE_API int tdsqlite3session_enable(tdsqlite3_session *pSession, int bEnable);
/*
** CAPI3REF: Set Or Clear the Indirect Change Flag
+** METHOD: tdsqlite3_session
**
** Each change recorded by a session object is marked as either direct or
** indirect. A change is marked as indirect if either:
@@ -8625,15 +9989,16 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
** The return value indicates the final state of the indirect flag: 0 if
** it is clear, or 1 if it is set.
*/
-int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
+SQLITE_API int tdsqlite3session_indirect(tdsqlite3_session *pSession, int bIndirect);
/*
** CAPI3REF: Attach A Table To A Session Object
+** METHOD: tdsqlite3_session
**
** If argument zTab is not NULL, then it is the name of a table to attach
** to the session object passed as the first argument. All subsequent changes
** made to the table while the session object is enabled will be recorded. See
-** documentation for [sqlite3session_changeset()] for further details.
+** documentation for [tdsqlite3session_changeset()] for further details.
**
** Or, if argument zTab is NULL, then changes are recorded for all tables
** in the database. If additional tables are added to the database (by
@@ -8654,23 +10019,53 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
**
** SQLITE_OK is returned if the call completes without error. Or, if an error
** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
-*/
-int sqlite3session_attach(
- sqlite3_session *pSession, /* Session object */
+**
+** <h3>Special sqlite_stat1 Handling</h3>
+**
+** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to
+** some of the rules above. In SQLite, the schema of sqlite_stat1 is:
+** <pre>
+** &nbsp; CREATE TABLE sqlite_stat1(tbl,idx,stat)
+** </pre>
+**
+** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are
+** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes
+** are recorded for rows for which (idx IS NULL) is true. However, for such
+** rows a zero-length blob (SQL value X'') is stored in the changeset or
+** patchset instead of a NULL value. This allows such changesets to be
+** manipulated by legacy implementations of tdsqlite3changeset_invert(),
+** concat() and similar.
+**
+** The tdsqlite3changeset_apply() function automatically converts the
+** zero-length blob back to a NULL value when updating the sqlite_stat1
+** table. However, if the application calls tdsqlite3changeset_new(),
+** tdsqlite3changeset_old() or tdsqlite3changeset_conflict on a changeset
+** iterator directly (including on a changeset iterator passed to a
+** conflict-handler callback) then the X'' value is returned. The application
+** must translate X'' to NULL itself if required.
+**
+** Legacy (older than 3.22.0) versions of the sessions module cannot capture
+** changes made to the sqlite_stat1 table. Legacy versions of the
+** tdsqlite3changeset_apply() function silently ignore any modifications to the
+** sqlite_stat1 table that are part of a changeset or patchset.
+*/
+SQLITE_API int tdsqlite3session_attach(
+ tdsqlite3_session *pSession, /* Session object */
const char *zTab /* Table name */
);
/*
** CAPI3REF: Set a table filter on a Session Object.
+** METHOD: tdsqlite3_session
**
** The second argument (xFilter) is the "filter callback". For changes to rows
** in tables that are not attached to the Session object, the filter is called
** to determine whether changes to the table's rows should be tracked or not.
-** If xFilter returns 0, changes is not tracked. Note that once a table is
+** If xFilter returns 0, changes are not tracked. Note that once a table is
** attached, xFilter will not be called again.
*/
-void sqlite3session_table_filter(
- sqlite3_session *pSession, /* Session object */
+SQLITE_API void tdsqlite3session_table_filter(
+ tdsqlite3_session *pSession, /* Session object */
int(*xFilter)(
void *pCtx, /* Copy of third arg to _filter_table() */
const char *zTab /* Table name */
@@ -8680,6 +10075,7 @@ void sqlite3session_table_filter(
/*
** CAPI3REF: Generate A Changeset From A Session Object
+** METHOD: tdsqlite3_session
**
** Obtain a changeset containing changes to the tables attached to the
** session object passed as the first argument. If successful,
@@ -8709,8 +10105,8 @@ void sqlite3session_table_filter(
** DELETE change only.
**
** The contents of a changeset may be traversed using an iterator created
-** using the [sqlite3changeset_start()] API. A changeset may be applied to
-** a database with a compatible schema using the [sqlite3changeset_apply()]
+** using the [tdsqlite3changeset_start()] API. A changeset may be applied to
+** a database with a compatible schema using the [tdsqlite3changeset_apply()]
** API.
**
** Within a changeset generated by this function, all changes related to a
@@ -8718,12 +10114,12 @@ void sqlite3session_table_filter(
** a changeset or when applying a changeset to a database, all changes related
** to a single table are processed before moving on to the next table. Tables
** are sorted in the same order in which they were attached (or auto-attached)
-** to the sqlite3_session object. The order in which the changes related to
+** to the tdsqlite3_session object. The order in which the changes related to
** a single table are stored is undefined.
**
** Following a successful call to this function, it is the responsibility of
** the caller to eventually free the buffer that *ppChangeset points to using
-** [sqlite3_free()].
+** [tdsqlite3_free()].
**
** <h3>Changeset Generation</h3>
**
@@ -8771,7 +10167,7 @@ void sqlite3session_table_filter(
** active, the resulting changeset will contain an UPDATE change instead of
** a DELETE and an INSERT.
**
-** When a session object is disabled (see the [sqlite3session_enable()] API),
+** When a session object is disabled (see the [tdsqlite3session_enable()] API),
** it does not accumulate records when rows are inserted, updated or deleted.
** This may appear to have some counter-intuitive effects if a single row
** is written to more than once during a session. For example, if a row
@@ -8782,18 +10178,19 @@ void sqlite3session_table_filter(
** another field of the same row is updated while the session is enabled, the
** resulting changeset will contain an UPDATE change that updates both fields.
*/
-int sqlite3session_changeset(
- sqlite3_session *pSession, /* Session object */
+SQLITE_API int tdsqlite3session_changeset(
+ tdsqlite3_session *pSession, /* Session object */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
);
/*
-** CAPI3REF: Load The Difference Between Tables Into A Session
+** CAPI3REF: Load The Difference Between Tables Into A Session
+** METHOD: tdsqlite3_session
**
** If it is not already attached to the session object passed as the first
** argument, this function attaches table zTbl in the same manner as the
-** [sqlite3session_attach()] function. If zTbl does not exist, or if it
+** [tdsqlite3session_attach()] function. If zTbl does not exist, or if it
** does not have a primary key, this function is a no-op (but does not return
** an error).
**
@@ -8826,25 +10223,26 @@ int sqlite3session_changeset(
** the from-table, a DELETE record is added to the session object.
**
** <li> For each row (primary key) that exists in both tables, but features
-** different in each, an UPDATE record is added to the session.
+** different non-PK values in each, an UPDATE record is added to the
+** session.
** </ul>
**
** To clarify, if this function is called and then a changeset constructed
-** using [sqlite3session_changeset()], then after applying that changeset to
+** using [tdsqlite3session_changeset()], then after applying that changeset to
** database zFrom the contents of the two compatible tables would be
** identical.
**
** It an error if database zFrom does not exist or does not contain the
** required compatible table.
**
-** If the operation successful, SQLITE_OK is returned. Otherwise, an SQLite
+** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
** may be set to point to a buffer containing an English language error
** message. It is the responsibility of the caller to free this buffer using
-** sqlite3_free().
+** tdsqlite3_free().
*/
-int sqlite3session_diff(
- sqlite3_session *pSession,
+SQLITE_API int tdsqlite3session_diff(
+ tdsqlite3_session *pSession,
const char *zFromDb,
const char *zTbl,
char **pzErrMsg
@@ -8853,6 +10251,7 @@ int sqlite3session_diff(
/*
** CAPI3REF: Generate A Patchset From A Session Object
+** METHOD: tdsqlite3_session
**
** The differences between a patchset and a changeset are that:
**
@@ -8864,25 +10263,25 @@ int sqlite3session_diff(
** </ul>
**
** A patchset blob may be used with up to date versions of all
-** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(),
+** tdsqlite3changeset_xxx API functions except for tdsqlite3changeset_invert(),
** which returns SQLITE_CORRUPT if it is passed a patchset. Similarly,
** attempting to use a patchset blob with old versions of the
-** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error.
+** tdsqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error.
**
** Because the non-primary key "old.*" fields are omitted, no
** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset
-** is passed to the sqlite3changeset_apply() API. Other conflict types work
+** is passed to the tdsqlite3changeset_apply() API. Other conflict types work
** in the same way as for changesets.
**
** Changes within a patchset are ordered in the same way as for changesets
-** generated by the sqlite3session_changeset() function (i.e. all changes for
+** generated by the tdsqlite3session_changeset() function (i.e. all changes for
** a single table are grouped together, tables appear in the order in which
** they were attached to the session object).
*/
-int sqlite3session_patchset(
- sqlite3_session *pSession, /* Session object */
- int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */
- void **ppPatchset /* OUT: Buffer containing changeset */
+SQLITE_API int tdsqlite3session_patchset(
+ tdsqlite3_session *pSession, /* Session object */
+ int *pnPatchset, /* OUT: Size of buffer at *ppPatchset */
+ void **ppPatchset /* OUT: Buffer containing patchset */
);
/*
@@ -8893,17 +10292,18 @@ int sqlite3session_patchset(
** more changes have been recorded, return zero.
**
** Even if this function returns zero, it is possible that calling
-** [sqlite3session_changeset()] on the session handle may still return a
+** [tdsqlite3session_changeset()] on the session handle may still return a
** changeset that contains no changes. This can happen when a row in
** an attached table is modified and then later on the original values
** are restored. However, if this function returns non-zero, then it is
-** guaranteed that a call to sqlite3session_changeset() will return a
+** guaranteed that a call to tdsqlite3session_changeset() will return a
** changeset containing zero changes.
*/
-int sqlite3session_isempty(sqlite3_session *pSession);
+SQLITE_API int tdsqlite3session_isempty(tdsqlite3_session *pSession);
/*
** CAPI3REF: Create An Iterator To Traverse A Changeset
+** CONSTRUCTOR: tdsqlite3_changeset_iter
**
** Create an iterator used to iterate through the contents of a changeset.
** If successful, *pp is set to point to the iterator handle and SQLITE_OK
@@ -8914,49 +10314,76 @@ int sqlite3session_isempty(sqlite3_session *pSession);
** iterator created by this function:
**
** <ul>
-** <li> [sqlite3changeset_next()]
-** <li> [sqlite3changeset_op()]
-** <li> [sqlite3changeset_new()]
-** <li> [sqlite3changeset_old()]
+** <li> [tdsqlite3changeset_next()]
+** <li> [tdsqlite3changeset_op()]
+** <li> [tdsqlite3changeset_new()]
+** <li> [tdsqlite3changeset_old()]
** </ul>
**
** It is the responsibility of the caller to eventually destroy the iterator
-** by passing it to [sqlite3changeset_finalize()]. The buffer containing the
+** by passing it to [tdsqlite3changeset_finalize()]. The buffer containing the
** changeset (pChangeset) must remain valid until after the iterator is
** destroyed.
**
** Assuming the changeset blob was created by one of the
-** [sqlite3session_changeset()], [sqlite3changeset_concat()] or
-** [sqlite3changeset_invert()] functions, all changes within the changeset
+** [tdsqlite3session_changeset()], [tdsqlite3changeset_concat()] or
+** [tdsqlite3changeset_invert()] functions, all changes within the changeset
** that apply to a single table are grouped together. This means that when
** an application iterates through a changeset using an iterator created by
** this function, all changes that relate to a single table are visited
** consecutively. There is no chance that the iterator will visit a change
** the applies to table X, then one for table Y, and then later on visit
** another change for table X.
+**
+** The behavior of tdsqlite3changeset_start_v2() and its streaming equivalent
+** may be modified by passing a combination of
+** [SQLITE_CHANGESETSTART_INVERT | supported flags] as the 4th parameter.
+**
+** Note that the tdsqlite3changeset_start_v2() API is still <b>experimental</b>
+** and therefore subject to change.
*/
-int sqlite3changeset_start(
- sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
+SQLITE_API int tdsqlite3changeset_start(
+ tdsqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
int nChangeset, /* Size of changeset blob in bytes */
void *pChangeset /* Pointer to blob containing changeset */
);
+SQLITE_API int tdsqlite3changeset_start_v2(
+ tdsqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
+ int nChangeset, /* Size of changeset blob in bytes */
+ void *pChangeset, /* Pointer to blob containing changeset */
+ int flags /* SESSION_CHANGESETSTART_* flags */
+);
+
+/*
+** CAPI3REF: Flags for tdsqlite3changeset_start_v2
+**
+** The following flags may passed via the 4th parameter to
+** [tdsqlite3changeset_start_v2] and [tdsqlite3changeset_start_v2_strm]:
+**
+** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
+** Invert the changeset while iterating through it. This is equivalent to
+** inverting a changeset using tdsqlite3changeset_invert() before applying it.
+** It is an error to specify this flag with a patchset.
+*/
+#define SQLITE_CHANGESETSTART_INVERT 0x0002
/*
** CAPI3REF: Advance A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
-** This function may only be used with iterators created by function
-** [sqlite3changeset_start()]. If it is called on an iterator passed to
-** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE
+** This function may only be used with iterators created by the function
+** [tdsqlite3changeset_start()]. If it is called on an iterator passed to
+** a conflict-handler callback by [tdsqlite3changeset_apply()], SQLITE_MISUSE
** is returned and the call has no effect.
**
-** Immediately after an iterator is created by sqlite3changeset_start(), it
+** Immediately after an iterator is created by tdsqlite3changeset_start(), it
** does not point to any change in the changeset. Assuming the changeset
** is not empty, the first call to this function advances the iterator to
** point to the first change in the changeset. Each subsequent call advances
** the iterator to point to the next change in the changeset (if any). If
** no error occurs and the iterator points to a valid change after a call
-** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned.
+** to tdsqlite3changeset_next() has advanced it, SQLITE_ROW is returned.
** Otherwise, if all changes in the changeset have already been visited,
** SQLITE_DONE is returned.
**
@@ -8964,26 +10391,27 @@ int sqlite3changeset_start(
** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or
** SQLITE_NOMEM.
*/
-int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
+SQLITE_API int tdsqlite3changeset_next(tdsqlite3_changeset_iter *pIter);
/*
** CAPI3REF: Obtain The Current Operation From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
-** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
-** created by [sqlite3changeset_start()]. In the latter case, the most recent
-** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
+** passed to a conflict-handler by [tdsqlite3changeset_apply()], or an iterator
+** created by [tdsqlite3changeset_start()]. In the latter case, the most recent
+** call to [tdsqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
** is not the case, this function returns [SQLITE_MISUSE].
**
** If argument pzTab is not NULL, then *pzTab is set to point to a
** nul-terminated utf-8 encoded string containing the name of the table
** affected by the current change. The buffer remains valid until either
-** sqlite3changeset_next() is called on the iterator or until the
+** tdsqlite3changeset_next() is called on the iterator or until the
** conflict-handler function returns. If pnCol is not NULL, then *pnCol is
** set to the number of columns in the table affected by the change. If
-** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change
+** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
** is an indirect change, or false (0) otherwise. See the documentation for
-** [sqlite3session_indirect()] for a description of direct and indirect
+** [tdsqlite3session_indirect()] for a description of direct and indirect
** changes. Finally, if pOp is not NULL, then *pOp is set to one of
** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the
** type of change that the iterator currently points to.
@@ -8992,8 +10420,8 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
** SQLite error code is returned. The values of the output variables may not
** be trusted in this case.
*/
-int sqlite3changeset_op(
- sqlite3_changeset_iter *pIter, /* Iterator object */
+SQLITE_API int tdsqlite3changeset_op(
+ tdsqlite3_changeset_iter *pIter, /* Iterator object */
const char **pzTab, /* OUT: Pointer to table name */
int *pnCol, /* OUT: Number of columns in table */
int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */
@@ -9002,6 +10430,7 @@ int sqlite3changeset_op(
/*
** CAPI3REF: Obtain The Primary Key Definition Of A Table
+** METHOD: tdsqlite3_changeset_iter
**
** For each modified table, a changeset includes the following:
**
@@ -9025,19 +10454,20 @@ int sqlite3changeset_op(
** SQLITE_OK is returned and the output variables populated as described
** above.
*/
-int sqlite3changeset_pk(
- sqlite3_changeset_iter *pIter, /* Iterator object */
+SQLITE_API int tdsqlite3changeset_pk(
+ tdsqlite3_changeset_iter *pIter, /* Iterator object */
unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */
int *pnCol /* OUT: Number of entries in output array */
);
/*
** CAPI3REF: Obtain old.* Values From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
-** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
-** created by [sqlite3changeset_start()]. In the latter case, the most recent
-** call to [sqlite3changeset_next()] must have returned SQLITE_ROW.
+** passed to a conflict-handler by [tdsqlite3changeset_apply()], or an iterator
+** created by [tdsqlite3changeset_start()]. In the latter case, the most recent
+** call to [tdsqlite3changeset_next()] must have returned SQLITE_ROW.
** Furthermore, it may only be called if the type of change that the iterator
** currently points to is either [SQLITE_DELETE] or [SQLITE_UPDATE]. Otherwise,
** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL.
@@ -9047,7 +10477,7 @@ int sqlite3changeset_pk(
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
**
** If successful, this function sets *ppValue to point to a protected
-** sqlite3_value object containing the iVal'th value from the vector of
+** tdsqlite3_value object containing the iVal'th value from the vector of
** original row values stored as part of the UPDATE or DELETE change and
** returns SQLITE_OK. The name of the function comes from the fact that this
** is similar to the "old.*" columns available to update or delete triggers.
@@ -9055,19 +10485,20 @@ int sqlite3changeset_pk(
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
-int sqlite3changeset_old(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_old(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
- sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
+ tdsqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
);
/*
** CAPI3REF: Obtain new.* Values From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
-** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
-** created by [sqlite3changeset_start()]. In the latter case, the most recent
-** call to [sqlite3changeset_next()] must have returned SQLITE_ROW.
+** passed to a conflict-handler by [tdsqlite3changeset_apply()], or an iterator
+** created by [tdsqlite3changeset_start()]. In the latter case, the most recent
+** call to [tdsqlite3changeset_next()] must have returned SQLITE_ROW.
** Furthermore, it may only be called if the type of change that the iterator
** currently points to is either [SQLITE_UPDATE] or [SQLITE_INSERT]. Otherwise,
** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL.
@@ -9077,7 +10508,7 @@ int sqlite3changeset_old(
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
**
** If successful, this function sets *ppValue to point to a protected
-** sqlite3_value object containing the iVal'th value from the vector of
+** tdsqlite3_value object containing the iVal'th value from the vector of
** new row values stored as part of the UPDATE or INSERT change and
** returns SQLITE_OK. If the change is an UPDATE and does not include
** a new value for the requested column, *ppValue is set to NULL and
@@ -9088,17 +10519,18 @@ int sqlite3changeset_old(
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
-int sqlite3changeset_new(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_new(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
- sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
+ tdsqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
);
/*
** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** This function should only be used with iterator objects passed to a
-** conflict-handler callback by [sqlite3changeset_apply()] with either
+** conflict-handler callback by [tdsqlite3changeset_apply()] with either
** [SQLITE_CHANGESET_DATA] or [SQLITE_CHANGESET_CONFLICT]. If this function
** is called on any other iterator, [SQLITE_MISUSE] is returned and *ppValue
** is set to NULL.
@@ -9108,21 +10540,22 @@ int sqlite3changeset_new(
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
**
** If successful, this function sets *ppValue to point to a protected
-** sqlite3_value object containing the iVal'th value from the
+** tdsqlite3_value object containing the iVal'th value from the
** "conflicting row" associated with the current conflict-handler callback
** and returns SQLITE_OK.
**
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
-int sqlite3changeset_conflict(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_conflict(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
- sqlite3_value **ppValue /* OUT: Value from conflicting row */
+ tdsqlite3_value **ppValue /* OUT: Value from conflicting row */
);
/*
** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations
+** METHOD: tdsqlite3_changeset_iter
**
** This function may only be called with an iterator passed to an
** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case
@@ -9131,40 +10564,43 @@ int sqlite3changeset_conflict(
**
** In all other cases this function returns SQLITE_MISUSE.
*/
-int sqlite3changeset_fk_conflicts(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+SQLITE_API int tdsqlite3changeset_fk_conflicts(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int *pnOut /* OUT: Number of FK violations */
);
/*
** CAPI3REF: Finalize A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** This function is used to finalize an iterator allocated with
-** [sqlite3changeset_start()].
+** [tdsqlite3changeset_start()].
**
** This function should only be called on iterators created using the
-** [sqlite3changeset_start()] function. If an application calls this
+** [tdsqlite3changeset_start()] function. If an application calls this
** function with an iterator passed to a conflict-handler by
-** [sqlite3changeset_apply()], [SQLITE_MISUSE] is immediately returned and the
+** [tdsqlite3changeset_apply()], [SQLITE_MISUSE] is immediately returned and the
** call has no effect.
**
-** If an error was encountered within a call to an sqlite3changeset_xxx()
-** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an
-** [SQLITE_NOMEM] in [sqlite3changeset_new()]) then an error code corresponding
+** If an error was encountered within a call to an tdsqlite3changeset_xxx()
+** function (for example an [SQLITE_CORRUPT] in [tdsqlite3changeset_next()] or an
+** [SQLITE_NOMEM] in [tdsqlite3changeset_new()]) then an error code corresponding
** to that error is returned by this function. Otherwise, SQLITE_OK is
** returned. This is to allow the following pattern (pseudo-code):
**
-** sqlite3changeset_start();
-** while( SQLITE_ROW==sqlite3changeset_next() ){
+** <pre>
+** tdsqlite3changeset_start();
+** while( SQLITE_ROW==tdsqlite3changeset_next() ){
** // Do something with change.
** }
-** rc = sqlite3changeset_finalize();
+** rc = tdsqlite3changeset_finalize();
** if( rc!=SQLITE_OK ){
** // An error has occurred
** }
+** </pre>
*/
-int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
+SQLITE_API int tdsqlite3changeset_finalize(tdsqlite3_changeset_iter *pIter);
/*
** CAPI3REF: Invert A Changeset
@@ -9187,14 +10623,14 @@ int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
** SQLITE_OK is returned. If an error occurs, both *pnOut and *ppOut are
** zeroed and an SQLite error code returned.
**
-** It is the responsibility of the caller to eventually call sqlite3_free()
+** It is the responsibility of the caller to eventually call tdsqlite3_free()
** on the *ppOut pointer to free the buffer allocation following a successful
** call to this function.
**
** WARNING/TODO: This function currently assumes that the input is a valid
** changeset. If it is not, the results are undefined.
*/
-int sqlite3changeset_invert(
+SQLITE_API int tdsqlite3changeset_invert(
int nIn, const void *pIn, /* Input changeset */
int *pnOut, void **ppOut /* OUT: Inverse of input */
);
@@ -9207,23 +10643,25 @@ int sqlite3changeset_invert(
** changeset A followed by changeset B.
**
** This function combines the two input changesets using an
-** sqlite3_changegroup object. Calling it produces similar results as the
+** tdsqlite3_changegroup object. Calling it produces similar results as the
** following code fragment:
**
-** sqlite3_changegroup *pGrp;
-** rc = sqlite3_changegroup_new(&pGrp);
-** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nA, pA);
-** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nB, pB);
+** <pre>
+** tdsqlite3_changegroup *pGrp;
+** rc = tdsqlite3_changegroup_new(&pGrp);
+** if( rc==SQLITE_OK ) rc = tdsqlite3changegroup_add(pGrp, nA, pA);
+** if( rc==SQLITE_OK ) rc = tdsqlite3changegroup_add(pGrp, nB, pB);
** if( rc==SQLITE_OK ){
-** rc = sqlite3changegroup_output(pGrp, pnOut, ppOut);
+** rc = tdsqlite3changegroup_output(pGrp, pnOut, ppOut);
** }else{
** *ppOut = 0;
** *pnOut = 0;
** }
+** </pre>
**
-** Refer to the sqlite3_changegroup documentation below for details.
+** Refer to the tdsqlite3_changegroup documentation below for details.
*/
-int sqlite3changeset_concat(
+SQLITE_API int tdsqlite3changeset_concat(
int nA, /* Number of bytes in buffer pA */
void *pA, /* Pointer to buffer containing changeset A */
int nB, /* Number of bytes in buffer pB */
@@ -9235,48 +10673,53 @@ int sqlite3changeset_concat(
/*
** CAPI3REF: Changegroup Handle
+**
+** A changegroup is an object used to combine two or more
+** [changesets] or [patchsets]
*/
-typedef struct sqlite3_changegroup sqlite3_changegroup;
+typedef struct tdsqlite3_changegroup tdsqlite3_changegroup;
/*
** CAPI3REF: Create A New Changegroup Object
+** CONSTRUCTOR: tdsqlite3_changegroup
**
-** An sqlite3_changegroup object is used to combine two or more changesets
+** An tdsqlite3_changegroup object is used to combine two or more changesets
** (or patchsets) into a single changeset (or patchset). A single changegroup
** object may combine changesets or patchsets, but not both. The output is
** always in the same format as the input.
**
** If successful, this function returns SQLITE_OK and populates (*pp) with
-** a pointer to a new sqlite3_changegroup object before returning. The caller
+** a pointer to a new tdsqlite3_changegroup object before returning. The caller
** should eventually free the returned object using a call to
-** sqlite3changegroup_delete(). If an error occurs, an SQLite error code
+** tdsqlite3changegroup_delete(). If an error occurs, an SQLite error code
** (i.e. SQLITE_NOMEM) is returned and *pp is set to NULL.
**
-** The usual usage pattern for an sqlite3_changegroup object is as follows:
+** The usual usage pattern for an tdsqlite3_changegroup object is as follows:
**
** <ul>
-** <li> It is created using a call to sqlite3changegroup_new().
+** <li> It is created using a call to tdsqlite3changegroup_new().
**
** <li> Zero or more changesets (or patchsets) are added to the object
-** by calling sqlite3changegroup_add().
+** by calling tdsqlite3changegroup_add().
**
** <li> The result of combining all input changesets together is obtained
-** by the application via a call to sqlite3changegroup_output().
+** by the application via a call to tdsqlite3changegroup_output().
**
-** <li> The object is deleted using a call to sqlite3changegroup_delete().
+** <li> The object is deleted using a call to tdsqlite3changegroup_delete().
** </ul>
**
** Any number of calls to add() and output() may be made between the calls to
** new() and delete(), and in any order.
**
-** As well as the regular sqlite3changegroup_add() and
-** sqlite3changegroup_output() functions, also available are the streaming
-** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm().
+** As well as the regular tdsqlite3changegroup_add() and
+** tdsqlite3changegroup_output() functions, also available are the streaming
+** versions tdsqlite3changegroup_add_strm() and tdsqlite3changegroup_output_strm().
*/
-int sqlite3changegroup_new(sqlite3_changegroup **pp);
+SQLITE_API int tdsqlite3changegroup_new(tdsqlite3_changegroup **pp);
/*
** CAPI3REF: Add A Changeset To A Changegroup
+** METHOD: tdsqlite3_changegroup
**
** Add all changes within the changeset (or patchset) in buffer pData (size
** nData bytes) to the changegroup.
@@ -9345,23 +10788,24 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp);
** case, this function fails with SQLITE_SCHEMA. If the input changeset
** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is
** returned. Or, if an out-of-memory condition occurs during processing, this
-** function returns SQLITE_NOMEM. In all cases, if an error occurs the
-** final contents of the changegroup is undefined.
+** function returns SQLITE_NOMEM. In all cases, if an error occurs the state
+** of the final contents of the changegroup is undefined.
**
** If no error occurs, SQLITE_OK is returned.
*/
-int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
+SQLITE_API int tdsqlite3changegroup_add(tdsqlite3_changegroup*, int nData, void *pData);
/*
** CAPI3REF: Obtain A Composite Changeset From A Changegroup
+** METHOD: tdsqlite3_changegroup
**
** Obtain a buffer containing a changeset (or patchset) representing the
** current contents of the changegroup. If the inputs to the changegroup
** were themselves changesets, the output is a changeset. Or, if the
** inputs were patchsets, the output is also a patchset.
**
-** As with the output of the sqlite3session_changeset() and
-** sqlite3session_patchset() functions, all changes related to a single
+** As with the output of the tdsqlite3session_changeset() and
+** tdsqlite3session_patchset() functions, all changes related to a single
** table are grouped together in the output of this function. Tables appear
** in the same order as for the very first changeset added to the changegroup.
** If the second or subsequent changesets added to the changegroup contain
@@ -9374,35 +10818,35 @@ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
** is returned and the output variables are set to the size of and a
** pointer to the output buffer, respectively. In this case it is the
** responsibility of the caller to eventually free the buffer using a
-** call to sqlite3_free().
+** call to tdsqlite3_free().
*/
-int sqlite3changegroup_output(
- sqlite3_changegroup*,
+SQLITE_API int tdsqlite3changegroup_output(
+ tdsqlite3_changegroup*,
int *pnData, /* OUT: Size of output buffer in bytes */
void **ppData /* OUT: Pointer to output buffer */
);
/*
** CAPI3REF: Delete A Changegroup Object
+** DESTRUCTOR: tdsqlite3_changegroup
*/
-void sqlite3changegroup_delete(sqlite3_changegroup*);
+SQLITE_API void tdsqlite3changegroup_delete(tdsqlite3_changegroup*);
/*
** CAPI3REF: Apply A Changeset To A Database
**
-** Apply a changeset to a database. This function attempts to update the
-** "main" database attached to handle db with the changes found in the
-** changeset passed via the second and third arguments.
+** Apply a changeset or patchset to a database. These functions attempt to
+** update the "main" database attached to handle db with the changes found in
+** the changeset passed via the second and third arguments.
**
-** The fourth argument (xFilter) passed to this function is the "filter
+** The fourth argument (xFilter) passed to these functions is the "filter
** callback". If it is not NULL, then for each table affected by at least one
** change in the changeset, the filter callback is invoked with
** the table name as the second argument, and a copy of the context pointer
-** passed as the sixth argument to this function as the first. If the "filter
-** callback" returns zero, then no attempt is made to apply any changes to
-** the table. Otherwise, if the return value is non-zero or the xFilter
-** argument to this function is NULL, all changes related to the table are
-** attempted.
+** passed as the sixth argument as the first. If the "filter callback"
+** returns zero, then no attempt is made to apply any changes to the table.
+** Otherwise, if the return value is non-zero or the xFilter argument to
+** is NULL, all changes related to the table are attempted.
**
** For each table that is not excluded by the filter callback, this function
** tests that the target database contains a compatible table. A table is
@@ -9411,7 +10855,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** <ul>
** <li> The table has the same name as the name recorded in the
** changeset, and
-** <li> The table has the same number of columns as recorded in the
+** <li> The table has at least as many columns as recorded in the
** changeset, and
** <li> The table has primary key columns in the same position as
** recorded in the changeset.
@@ -9419,13 +10863,13 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** If there is no compatible table, it is not an error, but none of the
** changes associated with the table are applied. A warning message is issued
-** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
+** via the tdsqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
** one such warning is issued for each table in the changeset.
**
** For each change for which there is a compatible table, an attempt is made
** to modify the table contents according to the UPDATE, INSERT or DELETE
** change. If a change cannot be applied cleanly, the conflict handler
-** function passed as the fifth argument to sqlite3changeset_apply() may be
+** function passed as the fifth argument to tdsqlite3changeset_apply() may be
** invoked. A description of exactly when the conflict handler is invoked for
** each type of change is below.
**
@@ -9439,15 +10883,15 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** if the second argument passed to the conflict handler is either
** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler
** returns an illegal value, any changes already made are rolled back and
-** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different
-** actions are taken by sqlite3changeset_apply() depending on the value
+** the call to tdsqlite3changeset_apply() returns SQLITE_MISUSE. Different
+** actions are taken by tdsqlite3changeset_apply() depending on the value
** returned by each invocation of the conflict-handler function. Refer to
** the documentation for the three
** [SQLITE_CHANGESET_OMIT|available return values] for details.
**
** <dl>
** <dt>DELETE Changes<dd>
-** For each DELETE change, this function checks if the target database
+** For each DELETE change, the function checks if the target database
** contains a row with the same primary key value (or values) as the
** original row values stored in the changeset. If it does, and the values
** stored in all non-primary key columns also match the values stored in
@@ -9456,7 +10900,11 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** If a row with matching primary key values is found, but one or more of
** the non-primary key fields contains a value different from the original
** row value stored in the changeset, the conflict-handler function is
-** invoked with [SQLITE_CHANGESET_DATA] as the second argument.
+** invoked with [SQLITE_CHANGESET_DATA] as the second argument. If the
+** database table has more columns than are recorded in the changeset,
+** only the values of those non-primary key fields are compared against
+** the current database contents - any trailing database table columns
+** are ignored.
**
** If no row with matching primary key values is found in the database,
** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND]
@@ -9471,7 +10919,9 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** <dt>INSERT Changes<dd>
** For each INSERT change, an attempt is made to insert the new row into
-** the database.
+** the database. If the changeset row contains fewer fields than the
+** database table, the trailing fields are populated with their default
+** values.
**
** If the attempt to insert the row fails because the database already
** contains a row with the same primary key values, the conflict handler
@@ -9486,16 +10936,16 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** [SQLITE_CHANGESET_REPLACE].
**
** <dt>UPDATE Changes<dd>
-** For each UPDATE change, this function checks if the target database
+** For each UPDATE change, the function checks if the target database
** contains a row with the same primary key value (or values) as the
** original row values stored in the changeset. If it does, and the values
-** stored in all non-primary key columns also match the values stored in
-** the changeset the row is updated within the target database.
+** stored in all modified non-primary key columns also match the values
+** stored in the changeset the row is updated within the target database.
**
** If a row with matching primary key values is found, but one or more of
-** the non-primary key fields contains a value different from an original
-** row value stored in the changeset, the conflict-handler function is
-** invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since
+** the modified non-primary key fields contains a value different from an
+** original row value stored in the changeset, the conflict-handler function
+** is invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since
** UPDATE changes only contain values for non-primary key fields that are
** to be modified, only those fields need to match the original values to
** avoid the SQLITE_CHANGESET_DATA conflict-handler callback.
@@ -9514,17 +10964,34 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** It is safe to execute SQL statements, including those that write to the
** table that the callback related to, from within the xConflict callback.
-** This can be used to further customize the applications conflict
+** This can be used to further customize the application's conflict
** resolution strategy.
**
-** All changes made by this function are enclosed in a savepoint transaction.
+** All changes made by these functions are enclosed in a savepoint transaction.
** If any other error (aside from a constraint failure when attempting to
** write to the target database) occurs, then the savepoint transaction is
** rolled back, restoring the target database to its original state, and an
** SQLite error code returned.
-*/
-int sqlite3changeset_apply(
- sqlite3 *db, /* Apply change to "main" db of this handle */
+**
+** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
+** the input is a changeset (not a patchset), then tdsqlite3changeset_apply_v2()
+** may set (*ppRebase) to point to a "rebase" that may be used with the
+** tdsqlite3_rebaser APIs buffer before returning. In this case (*pnRebase)
+** is set to the size of the buffer in bytes. It is the responsibility of the
+** caller to eventually free any such buffer using tdsqlite3_free(). The buffer
+** is only allocated and populated if one or more conflicts were encountered
+** while applying the patchset. See comments surrounding the tdsqlite3_rebaser
+** APIs for further details.
+**
+** The behavior of tdsqlite3changeset_apply_v2() and its streaming equivalent
+** may be modified by passing a combination of
+** [SQLITE_CHANGESETAPPLY_NOSAVEPOINT | supported flags] as the 9th parameter.
+**
+** Note that the tdsqlite3changeset_apply_v2() API is still <b>experimental</b>
+** and therefore subject to change.
+*/
+SQLITE_API int tdsqlite3changeset_apply(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset, /* Changeset blob */
int(*xFilter)(
@@ -9534,10 +11001,51 @@ int sqlite3changeset_apply(
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
- sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx /* First argument passed to xConflict */
);
+SQLITE_API int tdsqlite3changeset_apply_v2(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ int nChangeset, /* Size of changeset in bytes */
+ void *pChangeset, /* Changeset blob */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase, /* OUT: Rebase data */
+ int flags /* SESSION_CHANGESETAPPLY_* flags */
+);
+
+/*
+** CAPI3REF: Flags for tdsqlite3changeset_apply_v2
+**
+** The following flags may passed via the 9th parameter to
+** [tdsqlite3changeset_apply_v2] and [tdsqlite3changeset_apply_v2_strm]:
+**
+** <dl>
+** <dt>SQLITE_CHANGESETAPPLY_NOSAVEPOINT <dd>
+** Usually, the sessions module encloses all operations performed by
+** a single call to apply_v2() or apply_v2_strm() in a [SAVEPOINT]. The
+** SAVEPOINT is committed if the changeset or patchset is successfully
+** applied, or rolled back if an error occurs. Specifying this flag
+** causes the sessions module to omit this savepoint. In this case, if the
+** caller has an open transaction or savepoint when apply_v2() is called,
+** it may revert the partially applied changeset by rolling it back.
+**
+** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
+** Invert the changeset before applying it. This is equivalent to inverting
+** a changeset using tdsqlite3changeset_invert() before applying it. It is
+** an error to specify this flag with a patchset.
+*/
+#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
+#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
/*
** CAPI3REF: Constants Passed To The Conflict Handler
@@ -9561,7 +11069,7 @@ int sqlite3changeset_apply(
** required PRIMARY KEY fields is not present in the database.
**
** There is no conflicting row in this case. The results of invoking the
-** sqlite3changeset_conflict() API are undefined.
+** tdsqlite3changeset_conflict() API are undefined.
**
** <dt>SQLITE_CHANGESET_CONFLICT<dd>
** CHANGESET_CONFLICT is passed as the second argument to the conflict
@@ -9581,8 +11089,8 @@ int sqlite3changeset_apply(
** CHANGESET_ABORT, the changeset is rolled back.
**
** No current or conflicting row information is provided. The only function
-** it is possible to call on the supplied sqlite3_changeset_iter handle
-** is sqlite3changeset_fk_conflicts().
+** it is possible to call on the supplied tdsqlite3_changeset_iter handle
+** is tdsqlite3changeset_fk_conflicts().
**
** <dt>SQLITE_CHANGESET_CONSTRAINT<dd>
** If any other constraint violation occurs while applying a change (i.e.
@@ -9590,7 +11098,7 @@ int sqlite3changeset_apply(
** invoked with CHANGESET_CONSTRAINT as the second argument.
**
** There is no conflicting row in this case. The results of invoking the
-** sqlite3changeset_conflict() API are undefined.
+** tdsqlite3changeset_conflict() API are undefined.
**
** </dl>
*/
@@ -9615,7 +11123,7 @@ int sqlite3changeset_apply(
** This value may only be returned if the second argument to the conflict
** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this
** is not the case, any changes applied so far are rolled back and the
-** call to sqlite3changeset_apply() returns SQLITE_MISUSE.
+** call to tdsqlite3changeset_apply() returns SQLITE_MISUSE.
**
** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict
** handler, then the conflicting row is either updated or deleted, depending
@@ -9628,13 +11136,168 @@ int sqlite3changeset_apply(
**
** <dt>SQLITE_CHANGESET_ABORT<dd>
** If this value is returned, any changes applied so far are rolled back
-** and the call to sqlite3changeset_apply() returns SQLITE_ABORT.
+** and the call to tdsqlite3changeset_apply() returns SQLITE_ABORT.
** </dl>
*/
#define SQLITE_CHANGESET_OMIT 0
#define SQLITE_CHANGESET_REPLACE 1
#define SQLITE_CHANGESET_ABORT 2
+/*
+** CAPI3REF: Rebasing changesets
+** EXPERIMENTAL
+**
+** Suppose there is a site hosting a database in state S0. And that
+** modifications are made that move that database to state S1 and a
+** changeset recorded (the "local" changeset). Then, a changeset based
+** on S0 is received from another site (the "remote" changeset) and
+** applied to the database. The database is then in state
+** (S1+"remote"), where the exact state depends on any conflict
+** resolution decisions (OMIT or REPLACE) made while applying "remote".
+** Rebasing a changeset is to update it to take those conflict
+** resolution decisions into account, so that the same conflicts
+** do not have to be resolved elsewhere in the network.
+**
+** For example, if both the local and remote changesets contain an
+** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)":
+**
+** local: INSERT INTO t1 VALUES(1, 'v1');
+** remote: INSERT INTO t1 VALUES(1, 'v2');
+**
+** and the conflict resolution is REPLACE, then the INSERT change is
+** removed from the local changeset (it was overridden). Or, if the
+** conflict resolution was "OMIT", then the local changeset is modified
+** to instead contain:
+**
+** UPDATE t1 SET b = 'v2' WHERE a=1;
+**
+** Changes within the local changeset are rebased as follows:
+**
+** <dl>
+** <dt>Local INSERT<dd>
+** This may only conflict with a remote INSERT. If the conflict
+** resolution was OMIT, then add an UPDATE change to the rebased
+** changeset. Or, if the conflict resolution was REPLACE, add
+** nothing to the rebased changeset.
+**
+** <dt>Local DELETE<dd>
+** This may conflict with a remote UPDATE or DELETE. In both cases the
+** only possible resolution is OMIT. If the remote operation was a
+** DELETE, then add no change to the rebased changeset. If the remote
+** operation was an UPDATE, then the old.* fields of change are updated
+** to reflect the new.* values in the UPDATE.
+**
+** <dt>Local UPDATE<dd>
+** This may conflict with a remote UPDATE or DELETE. If it conflicts
+** with a DELETE, and the conflict resolution was OMIT, then the update
+** is changed into an INSERT. Any undefined values in the new.* record
+** from the update change are filled in using the old.* values from
+** the conflicting DELETE. Or, if the conflict resolution was REPLACE,
+** the UPDATE change is simply omitted from the rebased changeset.
+**
+** If conflict is with a remote UPDATE and the resolution is OMIT, then
+** the old.* values are rebased using the new.* values in the remote
+** change. Or, if the resolution is REPLACE, then the change is copied
+** into the rebased changeset with updates to columns also updated by
+** the conflicting remote UPDATE removed. If this means no columns would
+** be updated, the change is omitted.
+** </dl>
+**
+** A local change may be rebased against multiple remote changes
+** simultaneously. If a single key is modified by multiple remote
+** changesets, they are combined as follows before the local changeset
+** is rebased:
+**
+** <ul>
+** <li> If there has been one or more REPLACE resolutions on a
+** key, it is rebased according to a REPLACE.
+**
+** <li> If there have been no REPLACE resolutions on a key, then
+** the local changeset is rebased according to the most recent
+** of the OMIT resolutions.
+** </ul>
+**
+** Note that conflict resolutions from multiple remote changesets are
+** combined on a per-field basis, not per-row. This means that in the
+** case of multiple remote UPDATE operations, some fields of a single
+** local change may be rebased for REPLACE while others are rebased for
+** OMIT.
+**
+** In order to rebase a local changeset, the remote changeset must first
+** be applied to the local database using tdsqlite3changeset_apply_v2() and
+** the buffer of rebase information captured. Then:
+**
+** <ol>
+** <li> An tdsqlite3_rebaser object is created by calling
+** tdsqlite3rebaser_create().
+** <li> The new object is configured with the rebase buffer obtained from
+** tdsqlite3changeset_apply_v2() by calling tdsqlite3rebaser_configure().
+** If the local changeset is to be rebased against multiple remote
+** changesets, then tdsqlite3rebaser_configure() should be called
+** multiple times, in the same order that the multiple
+** tdsqlite3changeset_apply_v2() calls were made.
+** <li> Each local changeset is rebased by calling tdsqlite3rebaser_rebase().
+** <li> The tdsqlite3_rebaser object is deleted by calling
+** tdsqlite3rebaser_delete().
+** </ol>
+*/
+typedef struct tdsqlite3_rebaser tdsqlite3_rebaser;
+
+/*
+** CAPI3REF: Create a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Allocate a new changeset rebaser object. If successful, set (*ppNew) to
+** point to the new object and return SQLITE_OK. Otherwise, if an error
+** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew)
+** to NULL.
+*/
+SQLITE_API int tdsqlite3rebaser_create(tdsqlite3_rebaser **ppNew);
+
+/*
+** CAPI3REF: Configure a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Configure the changeset rebaser object to rebase changesets according
+** to the conflict resolutions described by buffer pRebase (size nRebase
+** bytes), which must have been obtained from a previous call to
+** tdsqlite3changeset_apply_v2().
+*/
+SQLITE_API int tdsqlite3rebaser_configure(
+ tdsqlite3_rebaser*,
+ int nRebase, const void *pRebase
+);
+
+/*
+** CAPI3REF: Rebase a changeset
+** EXPERIMENTAL
+**
+** Argument pIn must point to a buffer containing a changeset nIn bytes
+** in size. This function allocates and populates a buffer with a copy
+** of the changeset rebased according to the configuration of the
+** rebaser object passed as the first argument. If successful, (*ppOut)
+** is set to point to the new buffer containing the rebased changeset and
+** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the
+** responsibility of the caller to eventually free the new buffer using
+** tdsqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut)
+** are set to zero and an SQLite error code returned.
+*/
+SQLITE_API int tdsqlite3rebaser_rebase(
+ tdsqlite3_rebaser*,
+ int nIn, const void *pIn,
+ int *pnOut, void **ppOut
+);
+
+/*
+** CAPI3REF: Delete a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Delete the changeset rebaser object and all associated resources. There
+** should be one call to this function for each successful invocation
+** of tdsqlite3rebaser_create().
+*/
+SQLITE_API void tdsqlite3rebaser_delete(tdsqlite3_rebaser *p);
+
/*
** CAPI3REF: Streaming Versions of API functions.
**
@@ -9643,18 +11306,19 @@ int sqlite3changeset_apply(
**
** <table border=1 style="margin-left:8ex;margin-right:8ex">
** <tr><th>Streaming function<th>Non-streaming equivalent</th>
-** <tr><td>sqlite3changeset_apply_str<td>[sqlite3changeset_apply]
-** <tr><td>sqlite3changeset_concat_str<td>[sqlite3changeset_concat]
-** <tr><td>sqlite3changeset_invert_str<td>[sqlite3changeset_invert]
-** <tr><td>sqlite3changeset_start_str<td>[sqlite3changeset_start]
-** <tr><td>sqlite3session_changeset_str<td>[sqlite3session_changeset]
-** <tr><td>sqlite3session_patchset_str<td>[sqlite3session_patchset]
+** <tr><td>tdsqlite3changeset_apply_strm<td>[tdsqlite3changeset_apply]
+** <tr><td>tdsqlite3changeset_apply_strm_v2<td>[tdsqlite3changeset_apply_v2]
+** <tr><td>tdsqlite3changeset_concat_strm<td>[tdsqlite3changeset_concat]
+** <tr><td>tdsqlite3changeset_invert_strm<td>[tdsqlite3changeset_invert]
+** <tr><td>tdsqlite3changeset_start_strm<td>[tdsqlite3changeset_start]
+** <tr><td>tdsqlite3session_changeset_strm<td>[tdsqlite3session_changeset]
+** <tr><td>tdsqlite3session_patchset_strm<td>[tdsqlite3session_patchset]
** </table>
**
** Non-streaming functions that accept changesets (or patchsets) as input
** require that the entire changeset be stored in a single buffer in memory.
** Similarly, those that return a changeset or patchset do so by returning
-** a pointer to a single large buffer allocated using sqlite3_malloc().
+** a pointer to a single large buffer allocated using tdsqlite3_malloc().
** Normally this is convenient. However, if an application running in a
** low-memory environment is required to handle very large changesets, the
** large contiguous memory allocations required can become onerous.
@@ -9687,7 +11351,7 @@ int sqlite3changeset_apply(
** an error, all processing is abandoned and the streaming API function
** returns a copy of the error code to the caller.
**
-** In the case of sqlite3changeset_start_strm(), the xInput callback may be
+** In the case of tdsqlite3changeset_start_strm(), the xInput callback may be
** invoked by the sessions module at any point during the lifetime of the
** iterator. If such an xInput callback returns an error, the iterator enters
** an error state, whereby all subsequent calls to iterator functions
@@ -9724,8 +11388,8 @@ int sqlite3changeset_apply(
** parameter set to a value less than or equal to zero. Other than this,
** no guarantees are made as to the size of the chunks of data returned.
*/
-int sqlite3changeset_apply_strm(
- sqlite3 *db, /* Apply change to "main" db of this handle */
+SQLITE_API int tdsqlite3changeset_apply_strm(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
int(*xFilter)(
@@ -9735,11 +11399,28 @@ int sqlite3changeset_apply_strm(
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
- sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx /* First argument passed to xConflict */
);
-int sqlite3changeset_concat_strm(
+SQLITE_API int tdsqlite3changeset_apply_v2_strm(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
+ void *pIn, /* First arg for xInput */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase,
+ int flags
+);
+SQLITE_API int tdsqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
int (*xInputB)(void *pIn, void *pData, int *pnData),
@@ -9747,36 +11428,88 @@ int sqlite3changeset_concat_strm(
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3changeset_invert_strm(
+SQLITE_API int tdsqlite3changeset_invert_strm(
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3changeset_start_strm(
- sqlite3_changeset_iter **pp,
+SQLITE_API int tdsqlite3changeset_start_strm(
+ tdsqlite3_changeset_iter **pp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
);
-int sqlite3session_changeset_strm(
- sqlite3_session *pSession,
+SQLITE_API int tdsqlite3changeset_start_v2_strm(
+ tdsqlite3_changeset_iter **pp,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int flags
+);
+SQLITE_API int tdsqlite3session_changeset_strm(
+ tdsqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3session_patchset_strm(
- sqlite3_session *pSession,
+SQLITE_API int tdsqlite3session_patchset_strm(
+ tdsqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3changegroup_add_strm(sqlite3_changegroup*,
+SQLITE_API int tdsqlite3changegroup_add_strm(tdsqlite3_changegroup*,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
);
-int sqlite3changegroup_output_strm(sqlite3_changegroup*,
+SQLITE_API int tdsqlite3changegroup_output_strm(tdsqlite3_changegroup*,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
+SQLITE_API int tdsqlite3rebaser_rebase_strm(
+ tdsqlite3_rebaser *pRebaser,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut
+);
+/*
+** CAPI3REF: Configure global parameters
+**
+** The tdsqlite3session_config() interface is used to make global configuration
+** changes to the sessions module in order to tune it to the specific needs
+** of the application.
+**
+** The tdsqlite3session_config() interface is not threadsafe. If it is invoked
+** while any other thread is inside any other sessions method then the
+** results are undefined. Furthermore, if it is invoked after any sessions
+** related objects have been created, the results are also undefined.
+**
+** The first argument to the tdsqlite3session_config() function must be one
+** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The
+** interpretation of the (void*) value passed as the second parameter and
+** the effect of calling this function depends on the value of the first
+** parameter.
+**
+** <dl>
+** <dt>SQLITE_SESSION_CONFIG_STRMSIZE<dd>
+** By default, the sessions module streaming interfaces attempt to input
+** and output data in approximately 1 KiB chunks. This operand may be used
+** to set and query the value of this configuration setting. The pointer
+** passed as the second argument must point to a value of type (int).
+** If this value is greater than 0, it is used as the new streaming data
+** chunk size for both input and output. Before returning, the (int) value
+** pointed to by pArg is set to the final value of the streaming interface
+** chunk size.
+** </dl>
+**
+** This function returns SQLITE_OK if successful, or an SQLite error code
+** otherwise.
+*/
+SQLITE_API int tdsqlite3session_config(int op, void *pArg);
+
+/*
+** CAPI3REF: Values for tdsqlite3session_config().
+*/
+#define SQLITE_SESSION_CONFIG_STRMSIZE 1
/*
** Make sure we can call this stuff from C++.
@@ -9787,7 +11520,7 @@ int sqlite3changegroup_output_strm(sqlite3_changegroup*,
#endif /* !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) */
-/******** End of sqlite3session.h *********/
+/******** End of tdsqlite3session.h *********/
/******** Begin file fts5.h *********/
/*
** 2014 May 31
@@ -9821,7 +11554,7 @@ extern "C" {
** CUSTOM AUXILIARY FUNCTIONS
**
** Virtual table implementations may overload SQL functions by implementing
-** the sqlite3_module.xFindFunction() method.
+** the tdsqlite3_module.xFindFunction() method.
*/
typedef struct Fts5ExtensionApi Fts5ExtensionApi;
@@ -9831,9 +11564,9 @@ typedef struct Fts5PhraseIter Fts5PhraseIter;
typedef void (*fts5_extension_function)(
const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
Fts5Context *pFts, /* First arg to pass to pApi functions */
- sqlite3_context *pCtx, /* Context for returning result/error */
+ tdsqlite3_context *pCtx, /* Context for returning result/error */
int nVal, /* Number of values in apVal[] array */
- sqlite3_value **apVal /* Array of trailing arguments */
+ tdsqlite3_value **apVal /* Array of trailing arguments */
);
struct Fts5PhraseIter {
@@ -9910,12 +11643,8 @@ struct Fts5PhraseIter {
**
** Usually, output parameter *piPhrase is set to the phrase number, *piCol
** to the column in which it occurs and *piOff the token offset of the
-** first token of the phrase. The exception is if the table was created
-** with the offsets=0 option specified. In this case *piOff is always
-** set to -1.
-**
-** Returns SQLITE_OK if successful, or an error code (i.e. SQLITE_NOMEM)
-** if an error occurs.
+** first token of the phrase. Returns SQLITE_OK if successful, or an error
+** code (i.e. SQLITE_NOMEM) if an error occurs.
**
** This API can be quite slow if used with an FTS5 table created with the
** "detail=none" or "detail=column" option.
@@ -9953,10 +11682,10 @@ struct Fts5PhraseIter {
**
** xSetAuxdata(pFts5, pAux, xDelete)
**
-** Save the pointer passed as the second argument as the extension functions
+** Save the pointer passed as the second argument as the extension function's
** "auxiliary data". The pointer may then be retrieved by the current or any
** future invocation of the same fts5 extension function made as part of
-** of the same MATCH query using the xGetAuxdata() API.
+** the same MATCH query using the xGetAuxdata() API.
**
** Each extension function is allocated a single auxiliary data slot for
** each FTS query (MATCH expression). If the extension function is invoked
@@ -9971,7 +11700,7 @@ struct Fts5PhraseIter {
** The xDelete callback, if one is specified, is also invoked on the
** auxiliary data pointer after the FTS5 query has finished.
**
-** If an error (e.g. an OOM condition) occurs within this function, an
+** If an error (e.g. an OOM condition) occurs within this function,
** the auxiliary data is set to NULL and an error code returned. If the
** xDelete parameter was not NULL, it is invoked on the auxiliary data
** pointer before returning.
@@ -10062,8 +11791,8 @@ struct Fts5ExtensionApi {
void *(*xUserData)(Fts5Context*);
int (*xColumnCount)(Fts5Context*);
- int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow);
- int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken);
+ int (*xRowCount)(Fts5Context*, tdsqlite3_int64 *pnRow);
+ int (*xColumnTotalSize)(Fts5Context*, int iCol, tdsqlite3_int64 *pnToken);
int (*xTokenize)(Fts5Context*,
const char *pText, int nText, /* Text to tokenize */
@@ -10077,7 +11806,7 @@ struct Fts5ExtensionApi {
int (*xInstCount)(Fts5Context*, int *pnInst);
int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff);
- sqlite3_int64 (*xRowid)(Fts5Context*);
+ tdsqlite3_int64 (*xRowid)(Fts5Context*);
int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn);
int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken);
@@ -10195,8 +11924,8 @@ struct Fts5ExtensionApi {
**
** There are several ways to approach this in FTS5:
**
-** <ol><li> By mapping all synonyms to a single token. In this case, the
-** In the above example, this means that the tokenizer returns the
+** <ol><li> By mapping all synonyms to a single token. In this case, using
+** the above example, this means that the tokenizer returns the
** same token for inputs "first" and "1st". Say that token is in
** fact "first", so that when the user inserts the document "I won
** 1st place" entries are added to the index for tokens "i", "won",
@@ -10204,11 +11933,11 @@ struct Fts5ExtensionApi {
** the tokenizer substitutes "first" for "1st" and the query works
** as expected.
**
-** <li> By adding multiple synonyms for a single term to the FTS index.
-** In this case, when tokenizing query text, the tokenizer may
-** provide multiple synonyms for a single term within the document.
-** FTS5 then queries the index for each synonym individually. For
-** example, faced with the query:
+** <li> By querying the index for all synonyms of each query term
+** separately. In this case, when tokenizing query text, the
+** tokenizer may provide multiple synonyms for a single term
+** within the document. FTS5 then queries the index for each
+** synonym individually. For example, faced with the query:
**
** <codeblock>
** ... MATCH 'first place'</codeblock>
@@ -10232,9 +11961,9 @@ struct Fts5ExtensionApi {
** "place".
**
** This way, even if the tokenizer does not provide synonyms
-** when tokenizing query text (it should not - to do would be
+** when tokenizing query text (it should not - to do so would be
** inefficient), it doesn't matter if the user queries for
-** 'first + place' or '1st + place', as there are entires in the
+** 'first + place' or '1st + place', as there are entries in the
** FTS index corresponding to both forms of the first token.
** </ol>
**
@@ -10262,7 +11991,7 @@ struct Fts5ExtensionApi {
** extra data to the FTS index or require FTS5 to query for multiple terms,
** so it is efficient in terms of disk space and query speed. However, it
** does not support prefix queries very well. If, as suggested above, the
-** token "first" is subsituted for "1st" by the tokenizer, then the query:
+** token "first" is substituted for "1st" by the tokenizer, then the query:
**
** <codeblock>
** ... MATCH '1s*'</codeblock>
diff --git a/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3ext.h b/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3ext.h
index ce87e74690..1b5d136d58 100644
--- a/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3ext.h
+++ b/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3ext.h
@@ -29,526 +29,616 @@
** versions of SQLite will not be able to load each other's shared
** libraries!
*/
-struct sqlite3_api_routines {
- void * (*aggregate_context)(sqlite3_context*,int nBytes);
- int (*aggregate_count)(sqlite3_context*);
- int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
- int (*bind_double)(sqlite3_stmt*,int,double);
- int (*bind_int)(sqlite3_stmt*,int,int);
- int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
- int (*bind_null)(sqlite3_stmt*,int);
- int (*bind_parameter_count)(sqlite3_stmt*);
- int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
- const char * (*bind_parameter_name)(sqlite3_stmt*,int);
- int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
- int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
- int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
- int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
- int (*busy_timeout)(sqlite3*,int ms);
- int (*changes)(sqlite3*);
- int (*close)(sqlite3*);
- int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
+struct tdsqlite3_api_routines {
+ void * (*aggregate_context)(tdsqlite3_context*,int nBytes);
+ int (*aggregate_count)(tdsqlite3_context*);
+ int (*bind_blob)(tdsqlite3_stmt*,int,const void*,int n,void(*)(void*));
+ int (*bind_double)(tdsqlite3_stmt*,int,double);
+ int (*bind_int)(tdsqlite3_stmt*,int,int);
+ int (*bind_int64)(tdsqlite3_stmt*,int,sqlite_int64);
+ int (*bind_null)(tdsqlite3_stmt*,int);
+ int (*bind_parameter_count)(tdsqlite3_stmt*);
+ int (*bind_parameter_index)(tdsqlite3_stmt*,const char*zName);
+ const char * (*bind_parameter_name)(tdsqlite3_stmt*,int);
+ int (*bind_text)(tdsqlite3_stmt*,int,const char*,int n,void(*)(void*));
+ int (*bind_text16)(tdsqlite3_stmt*,int,const void*,int,void(*)(void*));
+ int (*bind_value)(tdsqlite3_stmt*,int,const tdsqlite3_value*);
+ int (*busy_handler)(tdsqlite3*,int(*)(void*,int),void*);
+ int (*busy_timeout)(tdsqlite3*,int ms);
+ int (*changes)(tdsqlite3*);
+ int (*close)(tdsqlite3*);
+ int (*collation_needed)(tdsqlite3*,void*,void(*)(void*,tdsqlite3*,
int eTextRep,const char*));
- int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
+ int (*collation_needed16)(tdsqlite3*,void*,void(*)(void*,tdsqlite3*,
int eTextRep,const void*));
- const void * (*column_blob)(sqlite3_stmt*,int iCol);
- int (*column_bytes)(sqlite3_stmt*,int iCol);
- int (*column_bytes16)(sqlite3_stmt*,int iCol);
- int (*column_count)(sqlite3_stmt*pStmt);
- const char * (*column_database_name)(sqlite3_stmt*,int);
- const void * (*column_database_name16)(sqlite3_stmt*,int);
- const char * (*column_decltype)(sqlite3_stmt*,int i);
- const void * (*column_decltype16)(sqlite3_stmt*,int);
- double (*column_double)(sqlite3_stmt*,int iCol);
- int (*column_int)(sqlite3_stmt*,int iCol);
- sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
- const char * (*column_name)(sqlite3_stmt*,int);
- const void * (*column_name16)(sqlite3_stmt*,int);
- const char * (*column_origin_name)(sqlite3_stmt*,int);
- const void * (*column_origin_name16)(sqlite3_stmt*,int);
- const char * (*column_table_name)(sqlite3_stmt*,int);
- const void * (*column_table_name16)(sqlite3_stmt*,int);
- const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
- const void * (*column_text16)(sqlite3_stmt*,int iCol);
- int (*column_type)(sqlite3_stmt*,int iCol);
- sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
- void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
+ const void * (*column_blob)(tdsqlite3_stmt*,int iCol);
+ int (*column_bytes)(tdsqlite3_stmt*,int iCol);
+ int (*column_bytes16)(tdsqlite3_stmt*,int iCol);
+ int (*column_count)(tdsqlite3_stmt*pStmt);
+ const char * (*column_database_name)(tdsqlite3_stmt*,int);
+ const void * (*column_database_name16)(tdsqlite3_stmt*,int);
+ const char * (*column_decltype)(tdsqlite3_stmt*,int i);
+ const void * (*column_decltype16)(tdsqlite3_stmt*,int);
+ double (*column_double)(tdsqlite3_stmt*,int iCol);
+ int (*column_int)(tdsqlite3_stmt*,int iCol);
+ sqlite_int64 (*column_int64)(tdsqlite3_stmt*,int iCol);
+ const char * (*column_name)(tdsqlite3_stmt*,int);
+ const void * (*column_name16)(tdsqlite3_stmt*,int);
+ const char * (*column_origin_name)(tdsqlite3_stmt*,int);
+ const void * (*column_origin_name16)(tdsqlite3_stmt*,int);
+ const char * (*column_table_name)(tdsqlite3_stmt*,int);
+ const void * (*column_table_name16)(tdsqlite3_stmt*,int);
+ const unsigned char * (*column_text)(tdsqlite3_stmt*,int iCol);
+ const void * (*column_text16)(tdsqlite3_stmt*,int iCol);
+ int (*column_type)(tdsqlite3_stmt*,int iCol);
+ tdsqlite3_value* (*column_value)(tdsqlite3_stmt*,int iCol);
+ void * (*commit_hook)(tdsqlite3*,int(*)(void*),void*);
int (*complete)(const char*sql);
int (*complete16)(const void*sql);
- int (*create_collation)(sqlite3*,const char*,int,void*,
+ int (*create_collation)(tdsqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*));
- int (*create_collation16)(sqlite3*,const void*,int,void*,
+ int (*create_collation16)(tdsqlite3*,const void*,int,void*,
int(*)(void*,int,const void*,int,const void*));
- int (*create_function)(sqlite3*,const char*,int,int,void*,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*));
- int (*create_function16)(sqlite3*,const void*,int,int,void*,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*));
- int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
- int (*data_count)(sqlite3_stmt*pStmt);
- sqlite3 * (*db_handle)(sqlite3_stmt*);
- int (*declare_vtab)(sqlite3*,const char*);
+ int (*create_function)(tdsqlite3*,const char*,int,int,void*,
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*));
+ int (*create_function16)(tdsqlite3*,const void*,int,int,void*,
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*));
+ int (*create_module)(tdsqlite3*,const char*,const tdsqlite3_module*,void*);
+ int (*data_count)(tdsqlite3_stmt*pStmt);
+ tdsqlite3 * (*db_handle)(tdsqlite3_stmt*);
+ int (*declare_vtab)(tdsqlite3*,const char*);
int (*enable_shared_cache)(int);
- int (*errcode)(sqlite3*db);
- const char * (*errmsg)(sqlite3*);
- const void * (*errmsg16)(sqlite3*);
- int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
- int (*expired)(sqlite3_stmt*);
- int (*finalize)(sqlite3_stmt*pStmt);
+ int (*errcode)(tdsqlite3*db);
+ const char * (*errmsg)(tdsqlite3*);
+ const void * (*errmsg16)(tdsqlite3*);
+ int (*exec)(tdsqlite3*,const char*,tdsqlite3_callback,void*,char**);
+ int (*expired)(tdsqlite3_stmt*);
+ int (*finalize)(tdsqlite3_stmt*pStmt);
void (*free)(void*);
void (*free_table)(char**result);
- int (*get_autocommit)(sqlite3*);
- void * (*get_auxdata)(sqlite3_context*,int);
- int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
+ int (*get_autocommit)(tdsqlite3*);
+ void * (*get_auxdata)(tdsqlite3_context*,int);
+ int (*get_table)(tdsqlite3*,const char*,char***,int*,int*,char**);
int (*global_recover)(void);
- void (*interruptx)(sqlite3*);
- sqlite_int64 (*last_insert_rowid)(sqlite3*);
+ void (*interruptx)(tdsqlite3*);
+ sqlite_int64 (*last_insert_rowid)(tdsqlite3*);
const char * (*libversion)(void);
int (*libversion_number)(void);
void *(*malloc)(int);
char * (*mprintf)(const char*,...);
- int (*open)(const char*,sqlite3**);
- int (*open16)(const void*,sqlite3**);
- int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
- int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
- void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
- void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
+ int (*open)(const char*,tdsqlite3**);
+ int (*open16)(const void*,tdsqlite3**);
+ int (*prepare)(tdsqlite3*,const char*,int,tdsqlite3_stmt**,const char**);
+ int (*prepare16)(tdsqlite3*,const void*,int,tdsqlite3_stmt**,const void**);
+ void * (*profile)(tdsqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
+ void (*progress_handler)(tdsqlite3*,int,int(*)(void*),void*);
void *(*realloc)(void*,int);
- int (*reset)(sqlite3_stmt*pStmt);
- void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
- void (*result_double)(sqlite3_context*,double);
- void (*result_error)(sqlite3_context*,const char*,int);
- void (*result_error16)(sqlite3_context*,const void*,int);
- void (*result_int)(sqlite3_context*,int);
- void (*result_int64)(sqlite3_context*,sqlite_int64);
- void (*result_null)(sqlite3_context*);
- void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
- void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
- void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
- void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
- void (*result_value)(sqlite3_context*,sqlite3_value*);
- void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
- int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
+ int (*reset)(tdsqlite3_stmt*pStmt);
+ void (*result_blob)(tdsqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_double)(tdsqlite3_context*,double);
+ void (*result_error)(tdsqlite3_context*,const char*,int);
+ void (*result_error16)(tdsqlite3_context*,const void*,int);
+ void (*result_int)(tdsqlite3_context*,int);
+ void (*result_int64)(tdsqlite3_context*,sqlite_int64);
+ void (*result_null)(tdsqlite3_context*);
+ void (*result_text)(tdsqlite3_context*,const char*,int,void(*)(void*));
+ void (*result_text16)(tdsqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_text16be)(tdsqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_text16le)(tdsqlite3_context*,const void*,int,void(*)(void*));
+ void (*result_value)(tdsqlite3_context*,tdsqlite3_value*);
+ void * (*rollback_hook)(tdsqlite3*,void(*)(void*),void*);
+ int (*set_authorizer)(tdsqlite3*,int(*)(void*,int,const char*,const char*,
const char*,const char*),void*);
- void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
- char * (*snprintf)(int,char*,const char*,...);
- int (*step)(sqlite3_stmt*);
- int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
+ void (*set_auxdata)(tdsqlite3_context*,int,void*,void (*)(void*));
+ char * (*xsnprintf)(int,char*,const char*,...);
+ int (*step)(tdsqlite3_stmt*);
+ int (*table_column_metadata)(tdsqlite3*,const char*,const char*,const char*,
char const**,char const**,int*,int*,int*);
void (*thread_cleanup)(void);
- int (*total_changes)(sqlite3*);
- void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
- int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
- void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
+ int (*total_changes)(tdsqlite3*);
+ void * (*trace)(tdsqlite3*,void(*xTrace)(void*,const char*),void*);
+ int (*transfer_bindings)(tdsqlite3_stmt*,tdsqlite3_stmt*);
+ void * (*update_hook)(tdsqlite3*,void(*)(void*,int ,char const*,char const*,
sqlite_int64),void*);
- void * (*user_data)(sqlite3_context*);
- const void * (*value_blob)(sqlite3_value*);
- int (*value_bytes)(sqlite3_value*);
- int (*value_bytes16)(sqlite3_value*);
- double (*value_double)(sqlite3_value*);
- int (*value_int)(sqlite3_value*);
- sqlite_int64 (*value_int64)(sqlite3_value*);
- int (*value_numeric_type)(sqlite3_value*);
- const unsigned char * (*value_text)(sqlite3_value*);
- const void * (*value_text16)(sqlite3_value*);
- const void * (*value_text16be)(sqlite3_value*);
- const void * (*value_text16le)(sqlite3_value*);
- int (*value_type)(sqlite3_value*);
+ void * (*user_data)(tdsqlite3_context*);
+ const void * (*value_blob)(tdsqlite3_value*);
+ int (*value_bytes)(tdsqlite3_value*);
+ int (*value_bytes16)(tdsqlite3_value*);
+ double (*value_double)(tdsqlite3_value*);
+ int (*value_int)(tdsqlite3_value*);
+ sqlite_int64 (*value_int64)(tdsqlite3_value*);
+ int (*value_numeric_type)(tdsqlite3_value*);
+ const unsigned char * (*value_text)(tdsqlite3_value*);
+ const void * (*value_text16)(tdsqlite3_value*);
+ const void * (*value_text16be)(tdsqlite3_value*);
+ const void * (*value_text16le)(tdsqlite3_value*);
+ int (*value_type)(tdsqlite3_value*);
char *(*vmprintf)(const char*,va_list);
/* Added ??? */
- int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
+ int (*overload_function)(tdsqlite3*, const char *zFuncName, int nArg);
/* Added by 3.3.13 */
- int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
- int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
- int (*clear_bindings)(sqlite3_stmt*);
+ int (*prepare_v2)(tdsqlite3*,const char*,int,tdsqlite3_stmt**,const char**);
+ int (*prepare16_v2)(tdsqlite3*,const void*,int,tdsqlite3_stmt**,const void**);
+ int (*clear_bindings)(tdsqlite3_stmt*);
/* Added by 3.4.1 */
- int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
+ int (*create_module_v2)(tdsqlite3*,const char*,const tdsqlite3_module*,void*,
void (*xDestroy)(void *));
/* Added by 3.5.0 */
- int (*bind_zeroblob)(sqlite3_stmt*,int,int);
- int (*blob_bytes)(sqlite3_blob*);
- int (*blob_close)(sqlite3_blob*);
- int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
- int,sqlite3_blob**);
- int (*blob_read)(sqlite3_blob*,void*,int,int);
- int (*blob_write)(sqlite3_blob*,const void*,int,int);
- int (*create_collation_v2)(sqlite3*,const char*,int,void*,
+ int (*bind_zeroblob)(tdsqlite3_stmt*,int,int);
+ int (*blob_bytes)(tdsqlite3_blob*);
+ int (*blob_close)(tdsqlite3_blob*);
+ int (*blob_open)(tdsqlite3*,const char*,const char*,const char*,tdsqlite3_int64,
+ int,tdsqlite3_blob**);
+ int (*blob_read)(tdsqlite3_blob*,void*,int,int);
+ int (*blob_write)(tdsqlite3_blob*,const void*,int,int);
+ int (*create_collation_v2)(tdsqlite3*,const char*,int,void*,
int(*)(void*,int,const void*,int,const void*),
void(*)(void*));
- int (*file_control)(sqlite3*,const char*,int,void*);
- sqlite3_int64 (*memory_highwater)(int);
- sqlite3_int64 (*memory_used)(void);
- sqlite3_mutex *(*mutex_alloc)(int);
- void (*mutex_enter)(sqlite3_mutex*);
- void (*mutex_free)(sqlite3_mutex*);
- void (*mutex_leave)(sqlite3_mutex*);
- int (*mutex_try)(sqlite3_mutex*);
- int (*open_v2)(const char*,sqlite3**,int,const char*);
+ int (*file_control)(tdsqlite3*,const char*,int,void*);
+ tdsqlite3_int64 (*memory_highwater)(int);
+ tdsqlite3_int64 (*memory_used)(void);
+ tdsqlite3_mutex *(*mutex_alloc)(int);
+ void (*mutex_enter)(tdsqlite3_mutex*);
+ void (*mutex_free)(tdsqlite3_mutex*);
+ void (*mutex_leave)(tdsqlite3_mutex*);
+ int (*mutex_try)(tdsqlite3_mutex*);
+ int (*open_v2)(const char*,tdsqlite3**,int,const char*);
int (*release_memory)(int);
- void (*result_error_nomem)(sqlite3_context*);
- void (*result_error_toobig)(sqlite3_context*);
+ void (*result_error_nomem)(tdsqlite3_context*);
+ void (*result_error_toobig)(tdsqlite3_context*);
int (*sleep)(int);
void (*soft_heap_limit)(int);
- sqlite3_vfs *(*vfs_find)(const char*);
- int (*vfs_register)(sqlite3_vfs*,int);
- int (*vfs_unregister)(sqlite3_vfs*);
+ tdsqlite3_vfs *(*vfs_find)(const char*);
+ int (*vfs_register)(tdsqlite3_vfs*,int);
+ int (*vfs_unregister)(tdsqlite3_vfs*);
int (*xthreadsafe)(void);
- void (*result_zeroblob)(sqlite3_context*,int);
- void (*result_error_code)(sqlite3_context*,int);
+ void (*result_zeroblob)(tdsqlite3_context*,int);
+ void (*result_error_code)(tdsqlite3_context*,int);
int (*test_control)(int, ...);
void (*randomness)(int,void*);
- sqlite3 *(*context_db_handle)(sqlite3_context*);
- int (*extended_result_codes)(sqlite3*,int);
- int (*limit)(sqlite3*,int,int);
- sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
- const char *(*sql)(sqlite3_stmt*);
+ tdsqlite3 *(*context_db_handle)(tdsqlite3_context*);
+ int (*extended_result_codes)(tdsqlite3*,int);
+ int (*limit)(tdsqlite3*,int,int);
+ tdsqlite3_stmt *(*next_stmt)(tdsqlite3*,tdsqlite3_stmt*);
+ const char *(*sql)(tdsqlite3_stmt*);
int (*status)(int,int*,int*,int);
- int (*backup_finish)(sqlite3_backup*);
- sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
- int (*backup_pagecount)(sqlite3_backup*);
- int (*backup_remaining)(sqlite3_backup*);
- int (*backup_step)(sqlite3_backup*,int);
+ int (*backup_finish)(tdsqlite3_backup*);
+ tdsqlite3_backup *(*backup_init)(tdsqlite3*,const char*,tdsqlite3*,const char*);
+ int (*backup_pagecount)(tdsqlite3_backup*);
+ int (*backup_remaining)(tdsqlite3_backup*);
+ int (*backup_step)(tdsqlite3_backup*,int);
const char *(*compileoption_get)(int);
int (*compileoption_used)(const char*);
- int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
- void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
- void (*xStep)(sqlite3_context*,int,sqlite3_value**),
- void (*xFinal)(sqlite3_context*),
+ int (*create_function_v2)(tdsqlite3*,const char*,int,int,void*,
+ void (*xFunc)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*),
void(*xDestroy)(void*));
- int (*db_config)(sqlite3*,int,...);
- sqlite3_mutex *(*db_mutex)(sqlite3*);
- int (*db_status)(sqlite3*,int,int*,int*,int);
- int (*extended_errcode)(sqlite3*);
+ int (*db_config)(tdsqlite3*,int,...);
+ tdsqlite3_mutex *(*db_mutex)(tdsqlite3*);
+ int (*db_status)(tdsqlite3*,int,int*,int*,int);
+ int (*extended_errcode)(tdsqlite3*);
void (*log)(int,const char*,...);
- sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
+ tdsqlite3_int64 (*soft_heap_limit64)(tdsqlite3_int64);
const char *(*sourceid)(void);
- int (*stmt_status)(sqlite3_stmt*,int,int);
+ int (*stmt_status)(tdsqlite3_stmt*,int,int);
int (*strnicmp)(const char*,const char*,int);
- int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
- int (*wal_autocheckpoint)(sqlite3*,int);
- int (*wal_checkpoint)(sqlite3*,const char*);
- void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
- int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
- int (*vtab_config)(sqlite3*,int op,...);
- int (*vtab_on_conflict)(sqlite3*);
+ int (*unlock_notify)(tdsqlite3*,void(*)(void**,int),void*);
+ int (*wal_autocheckpoint)(tdsqlite3*,int);
+ int (*wal_checkpoint)(tdsqlite3*,const char*);
+ void *(*wal_hook)(tdsqlite3*,int(*)(void*,tdsqlite3*,const char*,int),void*);
+ int (*blob_reopen)(tdsqlite3_blob*,tdsqlite3_int64);
+ int (*vtab_config)(tdsqlite3*,int op,...);
+ int (*vtab_on_conflict)(tdsqlite3*);
/* Version 3.7.16 and later */
- int (*close_v2)(sqlite3*);
- const char *(*db_filename)(sqlite3*,const char*);
- int (*db_readonly)(sqlite3*,const char*);
- int (*db_release_memory)(sqlite3*);
+ int (*close_v2)(tdsqlite3*);
+ const char *(*db_filename)(tdsqlite3*,const char*);
+ int (*db_readonly)(tdsqlite3*,const char*);
+ int (*db_release_memory)(tdsqlite3*);
const char *(*errstr)(int);
- int (*stmt_busy)(sqlite3_stmt*);
- int (*stmt_readonly)(sqlite3_stmt*);
+ int (*stmt_busy)(tdsqlite3_stmt*);
+ int (*stmt_readonly)(tdsqlite3_stmt*);
int (*stricmp)(const char*,const char*);
int (*uri_boolean)(const char*,const char*,int);
- sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
+ tdsqlite3_int64 (*uri_int64)(const char*,const char*,tdsqlite3_int64);
const char *(*uri_parameter)(const char*,const char*);
- char *(*vsnprintf)(int,char*,const char*,va_list);
- int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
+ char *(*xvsnprintf)(int,char*,const char*,va_list);
+ int (*wal_checkpoint_v2)(tdsqlite3*,const char*,int,int*,int*);
/* Version 3.8.7 and later */
int (*auto_extension)(void(*)(void));
- int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
+ int (*bind_blob64)(tdsqlite3_stmt*,int,const void*,tdsqlite3_uint64,
void(*)(void*));
- int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
+ int (*bind_text64)(tdsqlite3_stmt*,int,const char*,tdsqlite3_uint64,
void(*)(void*),unsigned char);
int (*cancel_auto_extension)(void(*)(void));
- int (*load_extension)(sqlite3*,const char*,const char*,char**);
- void *(*malloc64)(sqlite3_uint64);
- sqlite3_uint64 (*msize)(void*);
- void *(*realloc64)(void*,sqlite3_uint64);
+ int (*load_extension)(tdsqlite3*,const char*,const char*,char**);
+ void *(*malloc64)(tdsqlite3_uint64);
+ tdsqlite3_uint64 (*msize)(void*);
+ void *(*realloc64)(void*,tdsqlite3_uint64);
void (*reset_auto_extension)(void);
- void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
+ void (*result_blob64)(tdsqlite3_context*,const void*,tdsqlite3_uint64,
void(*)(void*));
- void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
+ void (*result_text64)(tdsqlite3_context*,const char*,tdsqlite3_uint64,
void(*)(void*), unsigned char);
int (*strglob)(const char*,const char*);
/* Version 3.8.11 and later */
- sqlite3_value *(*value_dup)(const sqlite3_value*);
- void (*value_free)(sqlite3_value*);
- int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
- int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
+ tdsqlite3_value *(*value_dup)(const tdsqlite3_value*);
+ void (*value_free)(tdsqlite3_value*);
+ int (*result_zeroblob64)(tdsqlite3_context*,tdsqlite3_uint64);
+ int (*bind_zeroblob64)(tdsqlite3_stmt*, int, tdsqlite3_uint64);
/* Version 3.9.0 and later */
- unsigned int (*value_subtype)(sqlite3_value*);
- void (*result_subtype)(sqlite3_context*,unsigned int);
+ unsigned int (*value_subtype)(tdsqlite3_value*);
+ void (*result_subtype)(tdsqlite3_context*,unsigned int);
/* Version 3.10.0 and later */
- int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
+ int (*status64)(int,tdsqlite3_int64*,tdsqlite3_int64*,int);
int (*strlike)(const char*,const char*,unsigned int);
- int (*db_cacheflush)(sqlite3*);
+ int (*db_cacheflush)(tdsqlite3*);
/* Version 3.12.0 and later */
- int (*system_errno)(sqlite3*);
+ int (*system_errno)(tdsqlite3*);
/* Version 3.14.0 and later */
- int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
- char *(*expanded_sql)(sqlite3_stmt*);
+ int (*trace_v2)(tdsqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
+ char *(*expanded_sql)(tdsqlite3_stmt*);
+ /* Version 3.18.0 and later */
+ void (*set_last_insert_rowid)(tdsqlite3*,tdsqlite3_int64);
+ /* Version 3.20.0 and later */
+ int (*prepare_v3)(tdsqlite3*,const char*,int,unsigned int,
+ tdsqlite3_stmt**,const char**);
+ int (*prepare16_v3)(tdsqlite3*,const void*,int,unsigned int,
+ tdsqlite3_stmt**,const void**);
+ int (*bind_pointer)(tdsqlite3_stmt*,int,void*,const char*,void(*)(void*));
+ void (*result_pointer)(tdsqlite3_context*,void*,const char*,void(*)(void*));
+ void *(*value_pointer)(tdsqlite3_value*,const char*);
+ int (*vtab_nochange)(tdsqlite3_context*);
+ int (*value_nochange)(tdsqlite3_value*);
+ const char *(*vtab_collation)(tdsqlite3_index_info*,int);
+ /* Version 3.24.0 and later */
+ int (*keyword_count)(void);
+ int (*keyword_name)(int,const char**,int*);
+ int (*keyword_check)(const char*,int);
+ tdsqlite3_str *(*str_new)(tdsqlite3*);
+ char *(*str_finish)(tdsqlite3_str*);
+ void (*str_appendf)(tdsqlite3_str*, const char *zFormat, ...);
+ void (*str_vappendf)(tdsqlite3_str*, const char *zFormat, va_list);
+ void (*str_append)(tdsqlite3_str*, const char *zIn, int N);
+ void (*str_appendall)(tdsqlite3_str*, const char *zIn);
+ void (*str_appendchar)(tdsqlite3_str*, int N, char C);
+ void (*str_reset)(tdsqlite3_str*);
+ int (*str_errcode)(tdsqlite3_str*);
+ int (*str_length)(tdsqlite3_str*);
+ char *(*str_value)(tdsqlite3_str*);
+ /* Version 3.25.0 and later */
+ int (*create_window_function)(tdsqlite3*,const char*,int,int,void*,
+ void (*xStep)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void (*xFinal)(tdsqlite3_context*),
+ void (*xValue)(tdsqlite3_context*),
+ void (*xInv)(tdsqlite3_context*,int,tdsqlite3_value**),
+ void(*xDestroy)(void*));
+ /* Version 3.26.0 and later */
+ const char *(*normalized_sql)(tdsqlite3_stmt*);
+ /* Version 3.28.0 and later */
+ int (*stmt_isexplain)(tdsqlite3_stmt*);
+ int (*value_frombind)(tdsqlite3_value*);
+ /* Version 3.30.0 and later */
+ int (*drop_modules)(tdsqlite3*,const char**);
+ /* Version 3.31.0 and later */
+ tdsqlite3_int64 (*hard_heap_limit64)(tdsqlite3_int64);
+ const char *(*uri_key)(const char*,int);
+ const char *(*filename_database)(const char*);
+ const char *(*filename_journal)(const char*);
+ const char *(*filename_wal)(const char*);
};
/*
** This is the function signature used for all extension entry points. It
** is also defined in the file "loadext.c".
*/
-typedef int (*sqlite3_loadext_entry)(
- sqlite3 *db, /* Handle to the database. */
+typedef int (*tdsqlite3_loadext_entry)(
+ tdsqlite3 *db, /* Handle to the database. */
char **pzErrMsg, /* Used to set error string on failure. */
- const sqlite3_api_routines *pThunk /* Extension API function pointers. */
+ const tdsqlite3_api_routines *pThunk /* Extension API function pointers. */
);
/*
** The following macros redefine the API routines so that they are
-** redirected through the global sqlite3_api structure.
+** redirected through the global tdsqlite3_api structure.
**
** This header file is also used by the loadext.c source file
** (part of the main SQLite library - not an extension) so that
-** it can get access to the sqlite3_api_routines structure
+** it can get access to the tdsqlite3_api_routines structure
** definition. But the main library does not want to redefine
** the API. So the redefinition macros are only valid if the
** SQLITE_CORE macros is undefined.
*/
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
-#define sqlite3_aggregate_context sqlite3_api->aggregate_context
+#define tdsqlite3_aggregate_context tdsqlite3_api->aggregate_context
#ifndef SQLITE_OMIT_DEPRECATED
-#define sqlite3_aggregate_count sqlite3_api->aggregate_count
+#define tdsqlite3_aggregate_count tdsqlite3_api->aggregate_count
#endif
-#define sqlite3_bind_blob sqlite3_api->bind_blob
-#define sqlite3_bind_double sqlite3_api->bind_double
-#define sqlite3_bind_int sqlite3_api->bind_int
-#define sqlite3_bind_int64 sqlite3_api->bind_int64
-#define sqlite3_bind_null sqlite3_api->bind_null
-#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
-#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
-#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
-#define sqlite3_bind_text sqlite3_api->bind_text
-#define sqlite3_bind_text16 sqlite3_api->bind_text16
-#define sqlite3_bind_value sqlite3_api->bind_value
-#define sqlite3_busy_handler sqlite3_api->busy_handler
-#define sqlite3_busy_timeout sqlite3_api->busy_timeout
-#define sqlite3_changes sqlite3_api->changes
-#define sqlite3_close sqlite3_api->close
-#define sqlite3_collation_needed sqlite3_api->collation_needed
-#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
-#define sqlite3_column_blob sqlite3_api->column_blob
-#define sqlite3_column_bytes sqlite3_api->column_bytes
-#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
-#define sqlite3_column_count sqlite3_api->column_count
-#define sqlite3_column_database_name sqlite3_api->column_database_name
-#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
-#define sqlite3_column_decltype sqlite3_api->column_decltype
-#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
-#define sqlite3_column_double sqlite3_api->column_double
-#define sqlite3_column_int sqlite3_api->column_int
-#define sqlite3_column_int64 sqlite3_api->column_int64
-#define sqlite3_column_name sqlite3_api->column_name
-#define sqlite3_column_name16 sqlite3_api->column_name16
-#define sqlite3_column_origin_name sqlite3_api->column_origin_name
-#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
-#define sqlite3_column_table_name sqlite3_api->column_table_name
-#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
-#define sqlite3_column_text sqlite3_api->column_text
-#define sqlite3_column_text16 sqlite3_api->column_text16
-#define sqlite3_column_type sqlite3_api->column_type
-#define sqlite3_column_value sqlite3_api->column_value
-#define sqlite3_commit_hook sqlite3_api->commit_hook
-#define sqlite3_complete sqlite3_api->complete
-#define sqlite3_complete16 sqlite3_api->complete16
-#define sqlite3_create_collation sqlite3_api->create_collation
-#define sqlite3_create_collation16 sqlite3_api->create_collation16
-#define sqlite3_create_function sqlite3_api->create_function
-#define sqlite3_create_function16 sqlite3_api->create_function16
-#define sqlite3_create_module sqlite3_api->create_module
-#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
-#define sqlite3_data_count sqlite3_api->data_count
-#define sqlite3_db_handle sqlite3_api->db_handle
-#define sqlite3_declare_vtab sqlite3_api->declare_vtab
-#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
-#define sqlite3_errcode sqlite3_api->errcode
-#define sqlite3_errmsg sqlite3_api->errmsg
-#define sqlite3_errmsg16 sqlite3_api->errmsg16
-#define sqlite3_exec sqlite3_api->exec
+#define tdsqlite3_bind_blob tdsqlite3_api->bind_blob
+#define tdsqlite3_bind_double tdsqlite3_api->bind_double
+#define tdsqlite3_bind_int tdsqlite3_api->bind_int
+#define tdsqlite3_bind_int64 tdsqlite3_api->bind_int64
+#define tdsqlite3_bind_null tdsqlite3_api->bind_null
+#define tdsqlite3_bind_parameter_count tdsqlite3_api->bind_parameter_count
+#define tdsqlite3_bind_parameter_index tdsqlite3_api->bind_parameter_index
+#define tdsqlite3_bind_parameter_name tdsqlite3_api->bind_parameter_name
+#define tdsqlite3_bind_text tdsqlite3_api->bind_text
+#define tdsqlite3_bind_text16 tdsqlite3_api->bind_text16
+#define tdsqlite3_bind_value tdsqlite3_api->bind_value
+#define tdsqlite3_busy_handler tdsqlite3_api->busy_handler
+#define tdsqlite3_busy_timeout tdsqlite3_api->busy_timeout
+#define tdsqlite3_changes tdsqlite3_api->changes
+#define tdsqlite3_close tdsqlite3_api->close
+#define tdsqlite3_collation_needed tdsqlite3_api->collation_needed
+#define tdsqlite3_collation_needed16 tdsqlite3_api->collation_needed16
+#define tdsqlite3_column_blob tdsqlite3_api->column_blob
+#define tdsqlite3_column_bytes tdsqlite3_api->column_bytes
+#define tdsqlite3_column_bytes16 tdsqlite3_api->column_bytes16
+#define tdsqlite3_column_count tdsqlite3_api->column_count
+#define tdsqlite3_column_database_name tdsqlite3_api->column_database_name
+#define tdsqlite3_column_database_name16 tdsqlite3_api->column_database_name16
+#define tdsqlite3_column_decltype tdsqlite3_api->column_decltype
+#define tdsqlite3_column_decltype16 tdsqlite3_api->column_decltype16
+#define tdsqlite3_column_double tdsqlite3_api->column_double
+#define tdsqlite3_column_int tdsqlite3_api->column_int
+#define tdsqlite3_column_int64 tdsqlite3_api->column_int64
+#define tdsqlite3_column_name tdsqlite3_api->column_name
+#define tdsqlite3_column_name16 tdsqlite3_api->column_name16
+#define tdsqlite3_column_origin_name tdsqlite3_api->column_origin_name
+#define tdsqlite3_column_origin_name16 tdsqlite3_api->column_origin_name16
+#define tdsqlite3_column_table_name tdsqlite3_api->column_table_name
+#define tdsqlite3_column_table_name16 tdsqlite3_api->column_table_name16
+#define tdsqlite3_column_text tdsqlite3_api->column_text
+#define tdsqlite3_column_text16 tdsqlite3_api->column_text16
+#define tdsqlite3_column_type tdsqlite3_api->column_type
+#define tdsqlite3_column_value tdsqlite3_api->column_value
+#define tdsqlite3_commit_hook tdsqlite3_api->commit_hook
+#define tdsqlite3_complete tdsqlite3_api->complete
+#define tdsqlite3_complete16 tdsqlite3_api->complete16
+#define tdsqlite3_create_collation tdsqlite3_api->create_collation
+#define tdsqlite3_create_collation16 tdsqlite3_api->create_collation16
+#define tdsqlite3_create_function tdsqlite3_api->create_function
+#define tdsqlite3_create_function16 tdsqlite3_api->create_function16
+#define tdsqlite3_create_module tdsqlite3_api->create_module
+#define tdsqlite3_create_module_v2 tdsqlite3_api->create_module_v2
+#define tdsqlite3_data_count tdsqlite3_api->data_count
+#define tdsqlite3_db_handle tdsqlite3_api->db_handle
+#define tdsqlite3_declare_vtab tdsqlite3_api->declare_vtab
+#define tdsqlite3_enable_shared_cache tdsqlite3_api->enable_shared_cache
+#define tdsqlite3_errcode tdsqlite3_api->errcode
+#define tdsqlite3_errmsg tdsqlite3_api->errmsg
+#define tdsqlite3_errmsg16 tdsqlite3_api->errmsg16
+#define tdsqlite3_exec tdsqlite3_api->exec
#ifndef SQLITE_OMIT_DEPRECATED
-#define sqlite3_expired sqlite3_api->expired
+#define tdsqlite3_expired tdsqlite3_api->expired
#endif
-#define sqlite3_finalize sqlite3_api->finalize
-#define sqlite3_free sqlite3_api->free
-#define sqlite3_free_table sqlite3_api->free_table
-#define sqlite3_get_autocommit sqlite3_api->get_autocommit
-#define sqlite3_get_auxdata sqlite3_api->get_auxdata
-#define sqlite3_get_table sqlite3_api->get_table
+#define tdsqlite3_finalize tdsqlite3_api->finalize
+#define tdsqlite3_free tdsqlite3_api->free
+#define tdsqlite3_free_table tdsqlite3_api->free_table
+#define tdsqlite3_get_autocommit tdsqlite3_api->get_autocommit
+#define tdsqlite3_get_auxdata tdsqlite3_api->get_auxdata
+#define tdsqlite3_get_table tdsqlite3_api->get_table
#ifndef SQLITE_OMIT_DEPRECATED
-#define sqlite3_global_recover sqlite3_api->global_recover
+#define tdsqlite3_global_recover tdsqlite3_api->global_recover
#endif
-#define sqlite3_interrupt sqlite3_api->interruptx
-#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
-#define sqlite3_libversion sqlite3_api->libversion
-#define sqlite3_libversion_number sqlite3_api->libversion_number
-#define sqlite3_malloc sqlite3_api->malloc
-#define sqlite3_mprintf sqlite3_api->mprintf
-#define sqlite3_open sqlite3_api->open
-#define sqlite3_open16 sqlite3_api->open16
-#define sqlite3_prepare sqlite3_api->prepare
-#define sqlite3_prepare16 sqlite3_api->prepare16
-#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
-#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
-#define sqlite3_profile sqlite3_api->profile
-#define sqlite3_progress_handler sqlite3_api->progress_handler
-#define sqlite3_realloc sqlite3_api->realloc
-#define sqlite3_reset sqlite3_api->reset
-#define sqlite3_result_blob sqlite3_api->result_blob
-#define sqlite3_result_double sqlite3_api->result_double
-#define sqlite3_result_error sqlite3_api->result_error
-#define sqlite3_result_error16 sqlite3_api->result_error16
-#define sqlite3_result_int sqlite3_api->result_int
-#define sqlite3_result_int64 sqlite3_api->result_int64
-#define sqlite3_result_null sqlite3_api->result_null
-#define sqlite3_result_text sqlite3_api->result_text
-#define sqlite3_result_text16 sqlite3_api->result_text16
-#define sqlite3_result_text16be sqlite3_api->result_text16be
-#define sqlite3_result_text16le sqlite3_api->result_text16le
-#define sqlite3_result_value sqlite3_api->result_value
-#define sqlite3_rollback_hook sqlite3_api->rollback_hook
-#define sqlite3_set_authorizer sqlite3_api->set_authorizer
-#define sqlite3_set_auxdata sqlite3_api->set_auxdata
-#define sqlite3_snprintf sqlite3_api->snprintf
-#define sqlite3_step sqlite3_api->step
-#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
-#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
-#define sqlite3_total_changes sqlite3_api->total_changes
-#define sqlite3_trace sqlite3_api->trace
+#define tdsqlite3_interrupt tdsqlite3_api->interruptx
+#define tdsqlite3_last_insert_rowid tdsqlite3_api->last_insert_rowid
+#define tdsqlite3_libversion tdsqlite3_api->libversion
+#define tdsqlite3_libversion_number tdsqlite3_api->libversion_number
+#define tdsqlite3_malloc tdsqlite3_api->malloc
+#define tdsqlite3_mprintf tdsqlite3_api->mprintf
+#define tdsqlite3_open tdsqlite3_api->open
+#define tdsqlite3_open16 tdsqlite3_api->open16
+#define tdsqlite3_prepare tdsqlite3_api->prepare
+#define tdsqlite3_prepare16 tdsqlite3_api->prepare16
+#define tdsqlite3_prepare_v2 tdsqlite3_api->prepare_v2
+#define tdsqlite3_prepare16_v2 tdsqlite3_api->prepare16_v2
+#define tdsqlite3_profile tdsqlite3_api->profile
+#define tdsqlite3_progress_handler tdsqlite3_api->progress_handler
+#define tdsqlite3_realloc tdsqlite3_api->realloc
+#define tdsqlite3_reset tdsqlite3_api->reset
+#define tdsqlite3_result_blob tdsqlite3_api->result_blob
+#define tdsqlite3_result_double tdsqlite3_api->result_double
+#define tdsqlite3_result_error tdsqlite3_api->result_error
+#define tdsqlite3_result_error16 tdsqlite3_api->result_error16
+#define tdsqlite3_result_int tdsqlite3_api->result_int
+#define tdsqlite3_result_int64 tdsqlite3_api->result_int64
+#define tdsqlite3_result_null tdsqlite3_api->result_null
+#define tdsqlite3_result_text tdsqlite3_api->result_text
+#define tdsqlite3_result_text16 tdsqlite3_api->result_text16
+#define tdsqlite3_result_text16be tdsqlite3_api->result_text16be
+#define tdsqlite3_result_text16le tdsqlite3_api->result_text16le
+#define tdsqlite3_result_value tdsqlite3_api->result_value
+#define tdsqlite3_rollback_hook tdsqlite3_api->rollback_hook
+#define tdsqlite3_set_authorizer tdsqlite3_api->set_authorizer
+#define tdsqlite3_set_auxdata tdsqlite3_api->set_auxdata
+#define tdsqlite3_snprintf tdsqlite3_api->xsnprintf
+#define tdsqlite3_step tdsqlite3_api->step
+#define tdsqlite3_table_column_metadata tdsqlite3_api->table_column_metadata
+#define tdsqlite3_thread_cleanup tdsqlite3_api->thread_cleanup
+#define tdsqlite3_total_changes tdsqlite3_api->total_changes
+#define tdsqlite3_trace tdsqlite3_api->trace
#ifndef SQLITE_OMIT_DEPRECATED
-#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
+#define tdsqlite3_transfer_bindings tdsqlite3_api->transfer_bindings
#endif
-#define sqlite3_update_hook sqlite3_api->update_hook
-#define sqlite3_user_data sqlite3_api->user_data
-#define sqlite3_value_blob sqlite3_api->value_blob
-#define sqlite3_value_bytes sqlite3_api->value_bytes
-#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
-#define sqlite3_value_double sqlite3_api->value_double
-#define sqlite3_value_int sqlite3_api->value_int
-#define sqlite3_value_int64 sqlite3_api->value_int64
-#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
-#define sqlite3_value_text sqlite3_api->value_text
-#define sqlite3_value_text16 sqlite3_api->value_text16
-#define sqlite3_value_text16be sqlite3_api->value_text16be
-#define sqlite3_value_text16le sqlite3_api->value_text16le
-#define sqlite3_value_type sqlite3_api->value_type
-#define sqlite3_vmprintf sqlite3_api->vmprintf
-#define sqlite3_vsnprintf sqlite3_api->vsnprintf
-#define sqlite3_overload_function sqlite3_api->overload_function
-#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
-#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
-#define sqlite3_clear_bindings sqlite3_api->clear_bindings
-#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
-#define sqlite3_blob_bytes sqlite3_api->blob_bytes
-#define sqlite3_blob_close sqlite3_api->blob_close
-#define sqlite3_blob_open sqlite3_api->blob_open
-#define sqlite3_blob_read sqlite3_api->blob_read
-#define sqlite3_blob_write sqlite3_api->blob_write
-#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
-#define sqlite3_file_control sqlite3_api->file_control
-#define sqlite3_memory_highwater sqlite3_api->memory_highwater
-#define sqlite3_memory_used sqlite3_api->memory_used
-#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
-#define sqlite3_mutex_enter sqlite3_api->mutex_enter
-#define sqlite3_mutex_free sqlite3_api->mutex_free
-#define sqlite3_mutex_leave sqlite3_api->mutex_leave
-#define sqlite3_mutex_try sqlite3_api->mutex_try
-#define sqlite3_open_v2 sqlite3_api->open_v2
-#define sqlite3_release_memory sqlite3_api->release_memory
-#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
-#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
-#define sqlite3_sleep sqlite3_api->sleep
-#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
-#define sqlite3_vfs_find sqlite3_api->vfs_find
-#define sqlite3_vfs_register sqlite3_api->vfs_register
-#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
-#define sqlite3_threadsafe sqlite3_api->xthreadsafe
-#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
-#define sqlite3_result_error_code sqlite3_api->result_error_code
-#define sqlite3_test_control sqlite3_api->test_control
-#define sqlite3_randomness sqlite3_api->randomness
-#define sqlite3_context_db_handle sqlite3_api->context_db_handle
-#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes
-#define sqlite3_limit sqlite3_api->limit
-#define sqlite3_next_stmt sqlite3_api->next_stmt
-#define sqlite3_sql sqlite3_api->sql
-#define sqlite3_status sqlite3_api->status
-#define sqlite3_backup_finish sqlite3_api->backup_finish
-#define sqlite3_backup_init sqlite3_api->backup_init
-#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
-#define sqlite3_backup_remaining sqlite3_api->backup_remaining
-#define sqlite3_backup_step sqlite3_api->backup_step
-#define sqlite3_compileoption_get sqlite3_api->compileoption_get
-#define sqlite3_compileoption_used sqlite3_api->compileoption_used
-#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
-#define sqlite3_db_config sqlite3_api->db_config
-#define sqlite3_db_mutex sqlite3_api->db_mutex
-#define sqlite3_db_status sqlite3_api->db_status
-#define sqlite3_extended_errcode sqlite3_api->extended_errcode
-#define sqlite3_log sqlite3_api->log
-#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
-#define sqlite3_sourceid sqlite3_api->sourceid
-#define sqlite3_stmt_status sqlite3_api->stmt_status
-#define sqlite3_strnicmp sqlite3_api->strnicmp
-#define sqlite3_unlock_notify sqlite3_api->unlock_notify
-#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
-#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
-#define sqlite3_wal_hook sqlite3_api->wal_hook
-#define sqlite3_blob_reopen sqlite3_api->blob_reopen
-#define sqlite3_vtab_config sqlite3_api->vtab_config
-#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict
+#define tdsqlite3_update_hook tdsqlite3_api->update_hook
+#define tdsqlite3_user_data tdsqlite3_api->user_data
+#define tdsqlite3_value_blob tdsqlite3_api->value_blob
+#define tdsqlite3_value_bytes tdsqlite3_api->value_bytes
+#define tdsqlite3_value_bytes16 tdsqlite3_api->value_bytes16
+#define tdsqlite3_value_double tdsqlite3_api->value_double
+#define tdsqlite3_value_int tdsqlite3_api->value_int
+#define tdsqlite3_value_int64 tdsqlite3_api->value_int64
+#define tdsqlite3_value_numeric_type tdsqlite3_api->value_numeric_type
+#define tdsqlite3_value_text tdsqlite3_api->value_text
+#define tdsqlite3_value_text16 tdsqlite3_api->value_text16
+#define tdsqlite3_value_text16be tdsqlite3_api->value_text16be
+#define tdsqlite3_value_text16le tdsqlite3_api->value_text16le
+#define tdsqlite3_value_type tdsqlite3_api->value_type
+#define tdsqlite3_vmprintf tdsqlite3_api->vmprintf
+#define tdsqlite3_vsnprintf tdsqlite3_api->xvsnprintf
+#define tdsqlite3_overload_function tdsqlite3_api->overload_function
+#define tdsqlite3_prepare_v2 tdsqlite3_api->prepare_v2
+#define tdsqlite3_prepare16_v2 tdsqlite3_api->prepare16_v2
+#define tdsqlite3_clear_bindings tdsqlite3_api->clear_bindings
+#define tdsqlite3_bind_zeroblob tdsqlite3_api->bind_zeroblob
+#define tdsqlite3_blob_bytes tdsqlite3_api->blob_bytes
+#define tdsqlite3_blob_close tdsqlite3_api->blob_close
+#define tdsqlite3_blob_open tdsqlite3_api->blob_open
+#define tdsqlite3_blob_read tdsqlite3_api->blob_read
+#define tdsqlite3_blob_write tdsqlite3_api->blob_write
+#define tdsqlite3_create_collation_v2 tdsqlite3_api->create_collation_v2
+#define tdsqlite3_file_control tdsqlite3_api->file_control
+#define tdsqlite3_memory_highwater tdsqlite3_api->memory_highwater
+#define tdsqlite3_memory_used tdsqlite3_api->memory_used
+#define tdsqlite3_mutex_alloc tdsqlite3_api->mutex_alloc
+#define tdsqlite3_mutex_enter tdsqlite3_api->mutex_enter
+#define tdsqlite3_mutex_free tdsqlite3_api->mutex_free
+#define tdsqlite3_mutex_leave tdsqlite3_api->mutex_leave
+#define tdsqlite3_mutex_try tdsqlite3_api->mutex_try
+#define tdsqlite3_open_v2 tdsqlite3_api->open_v2
+#define tdsqlite3_release_memory tdsqlite3_api->release_memory
+#define tdsqlite3_result_error_nomem tdsqlite3_api->result_error_nomem
+#define tdsqlite3_result_error_toobig tdsqlite3_api->result_error_toobig
+#define tdsqlite3_sleep tdsqlite3_api->sleep
+#define tdsqlite3_soft_heap_limit tdsqlite3_api->soft_heap_limit
+#define tdsqlite3_vfs_find tdsqlite3_api->vfs_find
+#define tdsqlite3_vfs_register tdsqlite3_api->vfs_register
+#define tdsqlite3_vfs_unregister tdsqlite3_api->vfs_unregister
+#define tdsqlite3_threadsafe tdsqlite3_api->xthreadsafe
+#define tdsqlite3_result_zeroblob tdsqlite3_api->result_zeroblob
+#define tdsqlite3_result_error_code tdsqlite3_api->result_error_code
+#define tdsqlite3_test_control tdsqlite3_api->test_control
+#define tdsqlite3_randomness tdsqlite3_api->randomness
+#define tdsqlite3_context_db_handle tdsqlite3_api->context_db_handle
+#define tdsqlite3_extended_result_codes tdsqlite3_api->extended_result_codes
+#define tdsqlite3_limit tdsqlite3_api->limit
+#define tdsqlite3_next_stmt tdsqlite3_api->next_stmt
+#define tdsqlite3_sql tdsqlite3_api->sql
+#define tdsqlite3_status tdsqlite3_api->status
+#define tdsqlite3_backup_finish tdsqlite3_api->backup_finish
+#define tdsqlite3_backup_init tdsqlite3_api->backup_init
+#define tdsqlite3_backup_pagecount tdsqlite3_api->backup_pagecount
+#define tdsqlite3_backup_remaining tdsqlite3_api->backup_remaining
+#define tdsqlite3_backup_step tdsqlite3_api->backup_step
+#define tdsqlite3_compileoption_get tdsqlite3_api->compileoption_get
+#define tdsqlite3_compileoption_used tdsqlite3_api->compileoption_used
+#define tdsqlite3_create_function_v2 tdsqlite3_api->create_function_v2
+#define tdsqlite3_db_config tdsqlite3_api->db_config
+#define tdsqlite3_db_mutex tdsqlite3_api->db_mutex
+#define tdsqlite3_db_status tdsqlite3_api->db_status
+#define tdsqlite3_extended_errcode tdsqlite3_api->extended_errcode
+#define tdsqlite3_log tdsqlite3_api->log
+#define tdsqlite3_soft_heap_limit64 tdsqlite3_api->soft_heap_limit64
+#define tdsqlite3_sourceid tdsqlite3_api->sourceid
+#define tdsqlite3_stmt_status tdsqlite3_api->stmt_status
+#define tdsqlite3_strnicmp tdsqlite3_api->strnicmp
+#define tdsqlite3_unlock_notify tdsqlite3_api->unlock_notify
+#define tdsqlite3_wal_autocheckpoint tdsqlite3_api->wal_autocheckpoint
+#define tdsqlite3_wal_checkpoint tdsqlite3_api->wal_checkpoint
+#define tdsqlite3_wal_hook tdsqlite3_api->wal_hook
+#define tdsqlite3_blob_reopen tdsqlite3_api->blob_reopen
+#define tdsqlite3_vtab_config tdsqlite3_api->vtab_config
+#define tdsqlite3_vtab_on_conflict tdsqlite3_api->vtab_on_conflict
/* Version 3.7.16 and later */
-#define sqlite3_close_v2 sqlite3_api->close_v2
-#define sqlite3_db_filename sqlite3_api->db_filename
-#define sqlite3_db_readonly sqlite3_api->db_readonly
-#define sqlite3_db_release_memory sqlite3_api->db_release_memory
-#define sqlite3_errstr sqlite3_api->errstr
-#define sqlite3_stmt_busy sqlite3_api->stmt_busy
-#define sqlite3_stmt_readonly sqlite3_api->stmt_readonly
-#define sqlite3_stricmp sqlite3_api->stricmp
-#define sqlite3_uri_boolean sqlite3_api->uri_boolean
-#define sqlite3_uri_int64 sqlite3_api->uri_int64
-#define sqlite3_uri_parameter sqlite3_api->uri_parameter
-#define sqlite3_uri_vsnprintf sqlite3_api->vsnprintf
-#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
+#define tdsqlite3_close_v2 tdsqlite3_api->close_v2
+#define tdsqlite3_db_filename tdsqlite3_api->db_filename
+#define tdsqlite3_db_readonly tdsqlite3_api->db_readonly
+#define tdsqlite3_db_release_memory tdsqlite3_api->db_release_memory
+#define tdsqlite3_errstr tdsqlite3_api->errstr
+#define tdsqlite3_stmt_busy tdsqlite3_api->stmt_busy
+#define tdsqlite3_stmt_readonly tdsqlite3_api->stmt_readonly
+#define tdsqlite3_stricmp tdsqlite3_api->stricmp
+#define tdsqlite3_uri_boolean tdsqlite3_api->uri_boolean
+#define tdsqlite3_uri_int64 tdsqlite3_api->uri_int64
+#define tdsqlite3_uri_parameter tdsqlite3_api->uri_parameter
+#define tdsqlite3_uri_vsnprintf tdsqlite3_api->xvsnprintf
+#define tdsqlite3_wal_checkpoint_v2 tdsqlite3_api->wal_checkpoint_v2
/* Version 3.8.7 and later */
-#define sqlite3_auto_extension sqlite3_api->auto_extension
-#define sqlite3_bind_blob64 sqlite3_api->bind_blob64
-#define sqlite3_bind_text64 sqlite3_api->bind_text64
-#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension
-#define sqlite3_load_extension sqlite3_api->load_extension
-#define sqlite3_malloc64 sqlite3_api->malloc64
-#define sqlite3_msize sqlite3_api->msize
-#define sqlite3_realloc64 sqlite3_api->realloc64
-#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension
-#define sqlite3_result_blob64 sqlite3_api->result_blob64
-#define sqlite3_result_text64 sqlite3_api->result_text64
-#define sqlite3_strglob sqlite3_api->strglob
+#define tdsqlite3_auto_extension tdsqlite3_api->auto_extension
+#define tdsqlite3_bind_blob64 tdsqlite3_api->bind_blob64
+#define tdsqlite3_bind_text64 tdsqlite3_api->bind_text64
+#define tdsqlite3_cancel_auto_extension tdsqlite3_api->cancel_auto_extension
+#define tdsqlite3_load_extension tdsqlite3_api->load_extension
+#define tdsqlite3_malloc64 tdsqlite3_api->malloc64
+#define tdsqlite3_msize tdsqlite3_api->msize
+#define tdsqlite3_realloc64 tdsqlite3_api->realloc64
+#define tdsqlite3_reset_auto_extension tdsqlite3_api->reset_auto_extension
+#define tdsqlite3_result_blob64 tdsqlite3_api->result_blob64
+#define tdsqlite3_result_text64 tdsqlite3_api->result_text64
+#define tdsqlite3_strglob tdsqlite3_api->strglob
/* Version 3.8.11 and later */
-#define sqlite3_value_dup sqlite3_api->value_dup
-#define sqlite3_value_free sqlite3_api->value_free
-#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64
-#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64
+#define tdsqlite3_value_dup tdsqlite3_api->value_dup
+#define tdsqlite3_value_free tdsqlite3_api->value_free
+#define tdsqlite3_result_zeroblob64 tdsqlite3_api->result_zeroblob64
+#define tdsqlite3_bind_zeroblob64 tdsqlite3_api->bind_zeroblob64
/* Version 3.9.0 and later */
-#define sqlite3_value_subtype sqlite3_api->value_subtype
-#define sqlite3_result_subtype sqlite3_api->result_subtype
+#define tdsqlite3_value_subtype tdsqlite3_api->value_subtype
+#define tdsqlite3_result_subtype tdsqlite3_api->result_subtype
/* Version 3.10.0 and later */
-#define sqlite3_status64 sqlite3_api->status64
-#define sqlite3_strlike sqlite3_api->strlike
-#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush
+#define tdsqlite3_status64 tdsqlite3_api->status64
+#define tdsqlite3_strlike tdsqlite3_api->strlike
+#define tdsqlite3_db_cacheflush tdsqlite3_api->db_cacheflush
/* Version 3.12.0 and later */
-#define sqlite3_system_errno sqlite3_api->system_errno
+#define tdsqlite3_system_errno tdsqlite3_api->system_errno
/* Version 3.14.0 and later */
-#define sqlite3_trace_v2 sqlite3_api->trace_v2
-#define sqlite3_expanded_sql sqlite3_api->expanded_sql
+#define tdsqlite3_trace_v2 tdsqlite3_api->trace_v2
+#define tdsqlite3_expanded_sql tdsqlite3_api->expanded_sql
+/* Version 3.18.0 and later */
+#define tdsqlite3_set_last_insert_rowid tdsqlite3_api->set_last_insert_rowid
+/* Version 3.20.0 and later */
+#define tdsqlite3_prepare_v3 tdsqlite3_api->prepare_v3
+#define tdsqlite3_prepare16_v3 tdsqlite3_api->prepare16_v3
+#define tdsqlite3_bind_pointer tdsqlite3_api->bind_pointer
+#define tdsqlite3_result_pointer tdsqlite3_api->result_pointer
+#define tdsqlite3_value_pointer tdsqlite3_api->value_pointer
+/* Version 3.22.0 and later */
+#define tdsqlite3_vtab_nochange tdsqlite3_api->vtab_nochange
+#define tdsqlite3_value_nochange tdsqlite3_api->value_nochange
+#define tdsqlite3_vtab_collation tdsqlite3_api->vtab_collation
+/* Version 3.24.0 and later */
+#define tdsqlite3_keyword_count tdsqlite3_api->keyword_count
+#define tdsqlite3_keyword_name tdsqlite3_api->keyword_name
+#define tdsqlite3_keyword_check tdsqlite3_api->keyword_check
+#define tdsqlite3_str_new tdsqlite3_api->str_new
+#define tdsqlite3_str_finish tdsqlite3_api->str_finish
+#define tdsqlite3_str_appendf tdsqlite3_api->str_appendf
+#define tdsqlite3_str_vappendf tdsqlite3_api->str_vappendf
+#define tdsqlite3_str_append tdsqlite3_api->str_append
+#define tdsqlite3_str_appendall tdsqlite3_api->str_appendall
+#define tdsqlite3_str_appendchar tdsqlite3_api->str_appendchar
+#define tdsqlite3_str_reset tdsqlite3_api->str_reset
+#define tdsqlite3_str_errcode tdsqlite3_api->str_errcode
+#define tdsqlite3_str_length tdsqlite3_api->str_length
+#define tdsqlite3_str_value tdsqlite3_api->str_value
+/* Version 3.25.0 and later */
+#define tdsqlite3_create_window_function tdsqlite3_api->create_window_function
+/* Version 3.26.0 and later */
+#define tdsqlite3_normalized_sql tdsqlite3_api->normalized_sql
+/* Version 3.28.0 and later */
+#define tdsqlite3_stmt_isexplain tdsqlite3_api->isexplain
+#define tdsqlite3_value_frombind tdsqlite3_api->frombind
+/* Version 3.30.0 and later */
+#define tdsqlite3_drop_modules tdsqlite3_api->drop_modules
+/* Version 3.31.0 andn later */
+#define tdsqlite3_hard_heap_limit64 tdsqlite3_api->hard_heap_limit64
+#define tdsqlite3_uri_key tdsqlite3_api->uri_key
+#define tdsqlite3_filename_database tdsqlite3_api->filename_database
+#define tdsqlite3_filename_journal tdsqlite3_api->filename_journal
+#define tdsqlite3_filename_wal tdsqlite3_api->filename_wal
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
/* This case when the file really is being compiled as a loadable
** extension */
-# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0;
-# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v;
+# define SQLITE_EXTENSION_INIT1 const tdsqlite3_api_routines *tdsqlite3_api=0;
+# define SQLITE_EXTENSION_INIT2(v) tdsqlite3_api=v;
# define SQLITE_EXTENSION_INIT3 \
- extern const sqlite3_api_routines *sqlite3_api;
+ extern const tdsqlite3_api_routines *tdsqlite3_api;
#else
/* This case when the file is being statically linked into the
** application */
diff --git a/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3session.h b/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3session.h
index d9f806e54f..5ad10cd56e 100644
--- a/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3session.h
+++ b/protocols/Telegram/tdlib/td/sqlite/sqlite/sqlite3session.h
@@ -12,16 +12,23 @@ extern "C" {
/*
** CAPI3REF: Session Object Handle
+**
+** An instance of this object is a [session] that can be used to
+** record changes to a database.
*/
-typedef struct sqlite3_session sqlite3_session;
+typedef struct tdsqlite3_session tdsqlite3_session;
/*
** CAPI3REF: Changeset Iterator Handle
+**
+** An instance of this object acts as a cursor for iterating
+** over the elements of a [changeset] or [patchset].
*/
-typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
+typedef struct tdsqlite3_changeset_iter tdsqlite3_changeset_iter;
/*
** CAPI3REF: Create A New Session Object
+** CONSTRUCTOR: tdsqlite3_session
**
** Create a new session object attached to database handle db. If successful,
** a pointer to the new object is written to *ppSession and SQLITE_OK is
@@ -32,13 +39,13 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
** database handle.
**
** Session objects created using this function should be deleted using the
-** [sqlite3session_delete()] function before the database handle that they
+** [tdsqlite3session_delete()] function before the database handle that they
** are attached to is itself closed. If the database handle is closed before
** the session object is deleted, then the results of calling any session
-** module function, including [sqlite3session_delete()] on the session object
+** module function, including [tdsqlite3session_delete()] on the session object
** are undefined.
**
-** Because the session module uses the [sqlite3_preupdate_hook()] API, it
+** Because the session module uses the [tdsqlite3_preupdate_hook()] API, it
** is not possible for an application to register a pre-update hook on a
** database handle that has one or more session objects attached. Nor is
** it possible to create a session object attached to a database handle for
@@ -50,34 +57,36 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
** attached database. It is not an error if database zDb is not attached
** to the database when the session object is created.
*/
-int sqlite3session_create(
- sqlite3 *db, /* Database handle */
+int tdsqlite3session_create(
+ tdsqlite3 *db, /* Database handle */
const char *zDb, /* Name of db (e.g. "main") */
- sqlite3_session **ppSession /* OUT: New session object */
+ tdsqlite3_session **ppSession /* OUT: New session object */
);
/*
** CAPI3REF: Delete A Session Object
+** DESTRUCTOR: tdsqlite3_session
**
** Delete a session object previously allocated using
-** [sqlite3session_create()]. Once a session object has been deleted, the
+** [tdsqlite3session_create()]. Once a session object has been deleted, the
** results of attempting to use pSession with any other session module
** function are undefined.
**
** Session objects must be deleted before the database handle to which they
** are attached is closed. Refer to the documentation for
-** [sqlite3session_create()] for details.
+** [tdsqlite3session_create()] for details.
*/
-void sqlite3session_delete(sqlite3_session *pSession);
+void tdsqlite3session_delete(tdsqlite3_session *pSession);
/*
** CAPI3REF: Enable Or Disable A Session Object
+** METHOD: tdsqlite3_session
**
** Enable or disable the recording of changes by a session object. When
** enabled, a session object records changes made to the database. When
** disabled - it does not. A newly created session object is enabled.
-** Refer to the documentation for [sqlite3session_changeset()] for further
+** Refer to the documentation for [tdsqlite3session_changeset()] for further
** details regarding how enabling and disabling a session object affects
** the eventual changesets.
**
@@ -88,10 +97,11 @@ void sqlite3session_delete(sqlite3_session *pSession);
** The return value indicates the final state of the session object: 0 if
** the session is disabled, or 1 if it is enabled.
*/
-int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
+int tdsqlite3session_enable(tdsqlite3_session *pSession, int bEnable);
/*
** CAPI3REF: Set Or Clear the Indirect Change Flag
+** METHOD: tdsqlite3_session
**
** Each change recorded by a session object is marked as either direct or
** indirect. A change is marked as indirect if either:
@@ -117,15 +127,16 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
** The return value indicates the final state of the indirect flag: 0 if
** it is clear, or 1 if it is set.
*/
-int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
+int tdsqlite3session_indirect(tdsqlite3_session *pSession, int bIndirect);
/*
** CAPI3REF: Attach A Table To A Session Object
+** METHOD: tdsqlite3_session
**
** If argument zTab is not NULL, then it is the name of a table to attach
** to the session object passed as the first argument. All subsequent changes
** made to the table while the session object is enabled will be recorded. See
-** documentation for [sqlite3session_changeset()] for further details.
+** documentation for [tdsqlite3session_changeset()] for further details.
**
** Or, if argument zTab is NULL, then changes are recorded for all tables
** in the database. If additional tables are added to the database (by
@@ -146,23 +157,53 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
**
** SQLITE_OK is returned if the call completes without error. Or, if an error
** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
+**
+** <h3>Special sqlite_stat1 Handling</h3>
+**
+** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to
+** some of the rules above. In SQLite, the schema of sqlite_stat1 is:
+** <pre>
+** &nbsp; CREATE TABLE sqlite_stat1(tbl,idx,stat)
+** </pre>
+**
+** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are
+** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes
+** are recorded for rows for which (idx IS NULL) is true. However, for such
+** rows a zero-length blob (SQL value X'') is stored in the changeset or
+** patchset instead of a NULL value. This allows such changesets to be
+** manipulated by legacy implementations of tdsqlite3changeset_invert(),
+** concat() and similar.
+**
+** The tdsqlite3changeset_apply() function automatically converts the
+** zero-length blob back to a NULL value when updating the sqlite_stat1
+** table. However, if the application calls tdsqlite3changeset_new(),
+** tdsqlite3changeset_old() or tdsqlite3changeset_conflict on a changeset
+** iterator directly (including on a changeset iterator passed to a
+** conflict-handler callback) then the X'' value is returned. The application
+** must translate X'' to NULL itself if required.
+**
+** Legacy (older than 3.22.0) versions of the sessions module cannot capture
+** changes made to the sqlite_stat1 table. Legacy versions of the
+** tdsqlite3changeset_apply() function silently ignore any modifications to the
+** sqlite_stat1 table that are part of a changeset or patchset.
*/
-int sqlite3session_attach(
- sqlite3_session *pSession, /* Session object */
+int tdsqlite3session_attach(
+ tdsqlite3_session *pSession, /* Session object */
const char *zTab /* Table name */
);
/*
** CAPI3REF: Set a table filter on a Session Object.
+** METHOD: tdsqlite3_session
**
** The second argument (xFilter) is the "filter callback". For changes to rows
** in tables that are not attached to the Session object, the filter is called
** to determine whether changes to the table's rows should be tracked or not.
-** If xFilter returns 0, changes is not tracked. Note that once a table is
+** If xFilter returns 0, changes are not tracked. Note that once a table is
** attached, xFilter will not be called again.
*/
-void sqlite3session_table_filter(
- sqlite3_session *pSession, /* Session object */
+void tdsqlite3session_table_filter(
+ tdsqlite3_session *pSession, /* Session object */
int(*xFilter)(
void *pCtx, /* Copy of third arg to _filter_table() */
const char *zTab /* Table name */
@@ -172,6 +213,7 @@ void sqlite3session_table_filter(
/*
** CAPI3REF: Generate A Changeset From A Session Object
+** METHOD: tdsqlite3_session
**
** Obtain a changeset containing changes to the tables attached to the
** session object passed as the first argument. If successful,
@@ -201,8 +243,8 @@ void sqlite3session_table_filter(
** DELETE change only.
**
** The contents of a changeset may be traversed using an iterator created
-** using the [sqlite3changeset_start()] API. A changeset may be applied to
-** a database with a compatible schema using the [sqlite3changeset_apply()]
+** using the [tdsqlite3changeset_start()] API. A changeset may be applied to
+** a database with a compatible schema using the [tdsqlite3changeset_apply()]
** API.
**
** Within a changeset generated by this function, all changes related to a
@@ -210,12 +252,12 @@ void sqlite3session_table_filter(
** a changeset or when applying a changeset to a database, all changes related
** to a single table are processed before moving on to the next table. Tables
** are sorted in the same order in which they were attached (or auto-attached)
-** to the sqlite3_session object. The order in which the changes related to
+** to the tdsqlite3_session object. The order in which the changes related to
** a single table are stored is undefined.
**
** Following a successful call to this function, it is the responsibility of
** the caller to eventually free the buffer that *ppChangeset points to using
-** [sqlite3_free()].
+** [tdsqlite3_free()].
**
** <h3>Changeset Generation</h3>
**
@@ -263,7 +305,7 @@ void sqlite3session_table_filter(
** active, the resulting changeset will contain an UPDATE change instead of
** a DELETE and an INSERT.
**
-** When a session object is disabled (see the [sqlite3session_enable()] API),
+** When a session object is disabled (see the [tdsqlite3session_enable()] API),
** it does not accumulate records when rows are inserted, updated or deleted.
** This may appear to have some counter-intuitive effects if a single row
** is written to more than once during a session. For example, if a row
@@ -274,18 +316,19 @@ void sqlite3session_table_filter(
** another field of the same row is updated while the session is enabled, the
** resulting changeset will contain an UPDATE change that updates both fields.
*/
-int sqlite3session_changeset(
- sqlite3_session *pSession, /* Session object */
+int tdsqlite3session_changeset(
+ tdsqlite3_session *pSession, /* Session object */
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
void **ppChangeset /* OUT: Buffer containing changeset */
);
/*
-** CAPI3REF: Load The Difference Between Tables Into A Session
+** CAPI3REF: Load The Difference Between Tables Into A Session
+** METHOD: tdsqlite3_session
**
** If it is not already attached to the session object passed as the first
** argument, this function attaches table zTbl in the same manner as the
-** [sqlite3session_attach()] function. If zTbl does not exist, or if it
+** [tdsqlite3session_attach()] function. If zTbl does not exist, or if it
** does not have a primary key, this function is a no-op (but does not return
** an error).
**
@@ -318,25 +361,26 @@ int sqlite3session_changeset(
** the from-table, a DELETE record is added to the session object.
**
** <li> For each row (primary key) that exists in both tables, but features
-** different in each, an UPDATE record is added to the session.
+** different non-PK values in each, an UPDATE record is added to the
+** session.
** </ul>
**
** To clarify, if this function is called and then a changeset constructed
-** using [sqlite3session_changeset()], then after applying that changeset to
+** using [tdsqlite3session_changeset()], then after applying that changeset to
** database zFrom the contents of the two compatible tables would be
** identical.
**
** It an error if database zFrom does not exist or does not contain the
** required compatible table.
**
-** If the operation successful, SQLITE_OK is returned. Otherwise, an SQLite
+** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
** may be set to point to a buffer containing an English language error
** message. It is the responsibility of the caller to free this buffer using
-** sqlite3_free().
+** tdsqlite3_free().
*/
-int sqlite3session_diff(
- sqlite3_session *pSession,
+int tdsqlite3session_diff(
+ tdsqlite3_session *pSession,
const char *zFromDb,
const char *zTbl,
char **pzErrMsg
@@ -345,6 +389,7 @@ int sqlite3session_diff(
/*
** CAPI3REF: Generate A Patchset From A Session Object
+** METHOD: tdsqlite3_session
**
** The differences between a patchset and a changeset are that:
**
@@ -356,25 +401,25 @@ int sqlite3session_diff(
** </ul>
**
** A patchset blob may be used with up to date versions of all
-** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(),
+** tdsqlite3changeset_xxx API functions except for tdsqlite3changeset_invert(),
** which returns SQLITE_CORRUPT if it is passed a patchset. Similarly,
** attempting to use a patchset blob with old versions of the
-** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error.
+** tdsqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error.
**
** Because the non-primary key "old.*" fields are omitted, no
** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset
-** is passed to the sqlite3changeset_apply() API. Other conflict types work
+** is passed to the tdsqlite3changeset_apply() API. Other conflict types work
** in the same way as for changesets.
**
** Changes within a patchset are ordered in the same way as for changesets
-** generated by the sqlite3session_changeset() function (i.e. all changes for
+** generated by the tdsqlite3session_changeset() function (i.e. all changes for
** a single table are grouped together, tables appear in the order in which
** they were attached to the session object).
*/
-int sqlite3session_patchset(
- sqlite3_session *pSession, /* Session object */
- int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */
- void **ppPatchset /* OUT: Buffer containing changeset */
+int tdsqlite3session_patchset(
+ tdsqlite3_session *pSession, /* Session object */
+ int *pnPatchset, /* OUT: Size of buffer at *ppPatchset */
+ void **ppPatchset /* OUT: Buffer containing patchset */
);
/*
@@ -385,17 +430,18 @@ int sqlite3session_patchset(
** more changes have been recorded, return zero.
**
** Even if this function returns zero, it is possible that calling
-** [sqlite3session_changeset()] on the session handle may still return a
+** [tdsqlite3session_changeset()] on the session handle may still return a
** changeset that contains no changes. This can happen when a row in
** an attached table is modified and then later on the original values
** are restored. However, if this function returns non-zero, then it is
-** guaranteed that a call to sqlite3session_changeset() will return a
+** guaranteed that a call to tdsqlite3session_changeset() will return a
** changeset containing zero changes.
*/
-int sqlite3session_isempty(sqlite3_session *pSession);
+int tdsqlite3session_isempty(tdsqlite3_session *pSession);
/*
** CAPI3REF: Create An Iterator To Traverse A Changeset
+** CONSTRUCTOR: tdsqlite3_changeset_iter
**
** Create an iterator used to iterate through the contents of a changeset.
** If successful, *pp is set to point to the iterator handle and SQLITE_OK
@@ -406,49 +452,76 @@ int sqlite3session_isempty(sqlite3_session *pSession);
** iterator created by this function:
**
** <ul>
-** <li> [sqlite3changeset_next()]
-** <li> [sqlite3changeset_op()]
-** <li> [sqlite3changeset_new()]
-** <li> [sqlite3changeset_old()]
+** <li> [tdsqlite3changeset_next()]
+** <li> [tdsqlite3changeset_op()]
+** <li> [tdsqlite3changeset_new()]
+** <li> [tdsqlite3changeset_old()]
** </ul>
**
** It is the responsibility of the caller to eventually destroy the iterator
-** by passing it to [sqlite3changeset_finalize()]. The buffer containing the
+** by passing it to [tdsqlite3changeset_finalize()]. The buffer containing the
** changeset (pChangeset) must remain valid until after the iterator is
** destroyed.
**
** Assuming the changeset blob was created by one of the
-** [sqlite3session_changeset()], [sqlite3changeset_concat()] or
-** [sqlite3changeset_invert()] functions, all changes within the changeset
+** [tdsqlite3session_changeset()], [tdsqlite3changeset_concat()] or
+** [tdsqlite3changeset_invert()] functions, all changes within the changeset
** that apply to a single table are grouped together. This means that when
** an application iterates through a changeset using an iterator created by
** this function, all changes that relate to a single table are visited
** consecutively. There is no chance that the iterator will visit a change
** the applies to table X, then one for table Y, and then later on visit
** another change for table X.
+**
+** The behavior of tdsqlite3changeset_start_v2() and its streaming equivalent
+** may be modified by passing a combination of
+** [SQLITE_CHANGESETSTART_INVERT | supported flags] as the 4th parameter.
+**
+** Note that the tdsqlite3changeset_start_v2() API is still <b>experimental</b>
+** and therefore subject to change.
*/
-int sqlite3changeset_start(
- sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
+int tdsqlite3changeset_start(
+ tdsqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
int nChangeset, /* Size of changeset blob in bytes */
void *pChangeset /* Pointer to blob containing changeset */
);
+int tdsqlite3changeset_start_v2(
+ tdsqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
+ int nChangeset, /* Size of changeset blob in bytes */
+ void *pChangeset, /* Pointer to blob containing changeset */
+ int flags /* SESSION_CHANGESETSTART_* flags */
+);
+
+/*
+** CAPI3REF: Flags for tdsqlite3changeset_start_v2
+**
+** The following flags may passed via the 4th parameter to
+** [tdsqlite3changeset_start_v2] and [tdsqlite3changeset_start_v2_strm]:
+**
+** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
+** Invert the changeset while iterating through it. This is equivalent to
+** inverting a changeset using tdsqlite3changeset_invert() before applying it.
+** It is an error to specify this flag with a patchset.
+*/
+#define SQLITE_CHANGESETSTART_INVERT 0x0002
/*
** CAPI3REF: Advance A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
-** This function may only be used with iterators created by function
-** [sqlite3changeset_start()]. If it is called on an iterator passed to
-** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE
+** This function may only be used with iterators created by the function
+** [tdsqlite3changeset_start()]. If it is called on an iterator passed to
+** a conflict-handler callback by [tdsqlite3changeset_apply()], SQLITE_MISUSE
** is returned and the call has no effect.
**
-** Immediately after an iterator is created by sqlite3changeset_start(), it
+** Immediately after an iterator is created by tdsqlite3changeset_start(), it
** does not point to any change in the changeset. Assuming the changeset
** is not empty, the first call to this function advances the iterator to
** point to the first change in the changeset. Each subsequent call advances
** the iterator to point to the next change in the changeset (if any). If
** no error occurs and the iterator points to a valid change after a call
-** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned.
+** to tdsqlite3changeset_next() has advanced it, SQLITE_ROW is returned.
** Otherwise, if all changes in the changeset have already been visited,
** SQLITE_DONE is returned.
**
@@ -456,26 +529,27 @@ int sqlite3changeset_start(
** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or
** SQLITE_NOMEM.
*/
-int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
+int tdsqlite3changeset_next(tdsqlite3_changeset_iter *pIter);
/*
** CAPI3REF: Obtain The Current Operation From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
-** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
-** created by [sqlite3changeset_start()]. In the latter case, the most recent
-** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
+** passed to a conflict-handler by [tdsqlite3changeset_apply()], or an iterator
+** created by [tdsqlite3changeset_start()]. In the latter case, the most recent
+** call to [tdsqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
** is not the case, this function returns [SQLITE_MISUSE].
**
** If argument pzTab is not NULL, then *pzTab is set to point to a
** nul-terminated utf-8 encoded string containing the name of the table
** affected by the current change. The buffer remains valid until either
-** sqlite3changeset_next() is called on the iterator or until the
+** tdsqlite3changeset_next() is called on the iterator or until the
** conflict-handler function returns. If pnCol is not NULL, then *pnCol is
** set to the number of columns in the table affected by the change. If
-** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change
+** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change
** is an indirect change, or false (0) otherwise. See the documentation for
-** [sqlite3session_indirect()] for a description of direct and indirect
+** [tdsqlite3session_indirect()] for a description of direct and indirect
** changes. Finally, if pOp is not NULL, then *pOp is set to one of
** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the
** type of change that the iterator currently points to.
@@ -484,8 +558,8 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
** SQLite error code is returned. The values of the output variables may not
** be trusted in this case.
*/
-int sqlite3changeset_op(
- sqlite3_changeset_iter *pIter, /* Iterator object */
+int tdsqlite3changeset_op(
+ tdsqlite3_changeset_iter *pIter, /* Iterator object */
const char **pzTab, /* OUT: Pointer to table name */
int *pnCol, /* OUT: Number of columns in table */
int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */
@@ -494,6 +568,7 @@ int sqlite3changeset_op(
/*
** CAPI3REF: Obtain The Primary Key Definition Of A Table
+** METHOD: tdsqlite3_changeset_iter
**
** For each modified table, a changeset includes the following:
**
@@ -517,19 +592,20 @@ int sqlite3changeset_op(
** SQLITE_OK is returned and the output variables populated as described
** above.
*/
-int sqlite3changeset_pk(
- sqlite3_changeset_iter *pIter, /* Iterator object */
+int tdsqlite3changeset_pk(
+ tdsqlite3_changeset_iter *pIter, /* Iterator object */
unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */
int *pnCol /* OUT: Number of entries in output array */
);
/*
** CAPI3REF: Obtain old.* Values From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
-** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
-** created by [sqlite3changeset_start()]. In the latter case, the most recent
-** call to [sqlite3changeset_next()] must have returned SQLITE_ROW.
+** passed to a conflict-handler by [tdsqlite3changeset_apply()], or an iterator
+** created by [tdsqlite3changeset_start()]. In the latter case, the most recent
+** call to [tdsqlite3changeset_next()] must have returned SQLITE_ROW.
** Furthermore, it may only be called if the type of change that the iterator
** currently points to is either [SQLITE_DELETE] or [SQLITE_UPDATE]. Otherwise,
** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL.
@@ -539,7 +615,7 @@ int sqlite3changeset_pk(
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
**
** If successful, this function sets *ppValue to point to a protected
-** sqlite3_value object containing the iVal'th value from the vector of
+** tdsqlite3_value object containing the iVal'th value from the vector of
** original row values stored as part of the UPDATE or DELETE change and
** returns SQLITE_OK. The name of the function comes from the fact that this
** is similar to the "old.*" columns available to update or delete triggers.
@@ -547,19 +623,20 @@ int sqlite3changeset_pk(
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
-int sqlite3changeset_old(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+int tdsqlite3changeset_old(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
- sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
+ tdsqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
);
/*
** CAPI3REF: Obtain new.* Values From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** The pIter argument passed to this function may either be an iterator
-** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
-** created by [sqlite3changeset_start()]. In the latter case, the most recent
-** call to [sqlite3changeset_next()] must have returned SQLITE_ROW.
+** passed to a conflict-handler by [tdsqlite3changeset_apply()], or an iterator
+** created by [tdsqlite3changeset_start()]. In the latter case, the most recent
+** call to [tdsqlite3changeset_next()] must have returned SQLITE_ROW.
** Furthermore, it may only be called if the type of change that the iterator
** currently points to is either [SQLITE_UPDATE] or [SQLITE_INSERT]. Otherwise,
** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL.
@@ -569,7 +646,7 @@ int sqlite3changeset_old(
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
**
** If successful, this function sets *ppValue to point to a protected
-** sqlite3_value object containing the iVal'th value from the vector of
+** tdsqlite3_value object containing the iVal'th value from the vector of
** new row values stored as part of the UPDATE or INSERT change and
** returns SQLITE_OK. If the change is an UPDATE and does not include
** a new value for the requested column, *ppValue is set to NULL and
@@ -580,17 +657,18 @@ int sqlite3changeset_old(
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
-int sqlite3changeset_new(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+int tdsqlite3changeset_new(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
- sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
+ tdsqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
);
/*
** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** This function should only be used with iterator objects passed to a
-** conflict-handler callback by [sqlite3changeset_apply()] with either
+** conflict-handler callback by [tdsqlite3changeset_apply()] with either
** [SQLITE_CHANGESET_DATA] or [SQLITE_CHANGESET_CONFLICT]. If this function
** is called on any other iterator, [SQLITE_MISUSE] is returned and *ppValue
** is set to NULL.
@@ -600,21 +678,22 @@ int sqlite3changeset_new(
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
**
** If successful, this function sets *ppValue to point to a protected
-** sqlite3_value object containing the iVal'th value from the
+** tdsqlite3_value object containing the iVal'th value from the
** "conflicting row" associated with the current conflict-handler callback
** and returns SQLITE_OK.
**
** If some other error occurs (e.g. an OOM condition), an SQLite error code
** is returned and *ppValue is set to NULL.
*/
-int sqlite3changeset_conflict(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+int tdsqlite3changeset_conflict(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int iVal, /* Column number */
- sqlite3_value **ppValue /* OUT: Value from conflicting row */
+ tdsqlite3_value **ppValue /* OUT: Value from conflicting row */
);
/*
** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations
+** METHOD: tdsqlite3_changeset_iter
**
** This function may only be called with an iterator passed to an
** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case
@@ -623,40 +702,43 @@ int sqlite3changeset_conflict(
**
** In all other cases this function returns SQLITE_MISUSE.
*/
-int sqlite3changeset_fk_conflicts(
- sqlite3_changeset_iter *pIter, /* Changeset iterator */
+int tdsqlite3changeset_fk_conflicts(
+ tdsqlite3_changeset_iter *pIter, /* Changeset iterator */
int *pnOut /* OUT: Number of FK violations */
);
/*
** CAPI3REF: Finalize A Changeset Iterator
+** METHOD: tdsqlite3_changeset_iter
**
** This function is used to finalize an iterator allocated with
-** [sqlite3changeset_start()].
+** [tdsqlite3changeset_start()].
**
** This function should only be called on iterators created using the
-** [sqlite3changeset_start()] function. If an application calls this
+** [tdsqlite3changeset_start()] function. If an application calls this
** function with an iterator passed to a conflict-handler by
-** [sqlite3changeset_apply()], [SQLITE_MISUSE] is immediately returned and the
+** [tdsqlite3changeset_apply()], [SQLITE_MISUSE] is immediately returned and the
** call has no effect.
**
-** If an error was encountered within a call to an sqlite3changeset_xxx()
-** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an
-** [SQLITE_NOMEM] in [sqlite3changeset_new()]) then an error code corresponding
+** If an error was encountered within a call to an tdsqlite3changeset_xxx()
+** function (for example an [SQLITE_CORRUPT] in [tdsqlite3changeset_next()] or an
+** [SQLITE_NOMEM] in [tdsqlite3changeset_new()]) then an error code corresponding
** to that error is returned by this function. Otherwise, SQLITE_OK is
** returned. This is to allow the following pattern (pseudo-code):
**
-** sqlite3changeset_start();
-** while( SQLITE_ROW==sqlite3changeset_next() ){
+** <pre>
+** tdsqlite3changeset_start();
+** while( SQLITE_ROW==tdsqlite3changeset_next() ){
** // Do something with change.
** }
-** rc = sqlite3changeset_finalize();
+** rc = tdsqlite3changeset_finalize();
** if( rc!=SQLITE_OK ){
** // An error has occurred
** }
+** </pre>
*/
-int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
+int tdsqlite3changeset_finalize(tdsqlite3_changeset_iter *pIter);
/*
** CAPI3REF: Invert A Changeset
@@ -679,14 +761,14 @@ int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
** SQLITE_OK is returned. If an error occurs, both *pnOut and *ppOut are
** zeroed and an SQLite error code returned.
**
-** It is the responsibility of the caller to eventually call sqlite3_free()
+** It is the responsibility of the caller to eventually call tdsqlite3_free()
** on the *ppOut pointer to free the buffer allocation following a successful
** call to this function.
**
** WARNING/TODO: This function currently assumes that the input is a valid
** changeset. If it is not, the results are undefined.
*/
-int sqlite3changeset_invert(
+int tdsqlite3changeset_invert(
int nIn, const void *pIn, /* Input changeset */
int *pnOut, void **ppOut /* OUT: Inverse of input */
);
@@ -699,23 +781,25 @@ int sqlite3changeset_invert(
** changeset A followed by changeset B.
**
** This function combines the two input changesets using an
-** sqlite3_changegroup object. Calling it produces similar results as the
+** tdsqlite3_changegroup object. Calling it produces similar results as the
** following code fragment:
**
-** sqlite3_changegroup *pGrp;
-** rc = sqlite3_changegroup_new(&pGrp);
-** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nA, pA);
-** if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nB, pB);
+** <pre>
+** tdsqlite3_changegroup *pGrp;
+** rc = tdsqlite3_changegroup_new(&pGrp);
+** if( rc==SQLITE_OK ) rc = tdsqlite3changegroup_add(pGrp, nA, pA);
+** if( rc==SQLITE_OK ) rc = tdsqlite3changegroup_add(pGrp, nB, pB);
** if( rc==SQLITE_OK ){
-** rc = sqlite3changegroup_output(pGrp, pnOut, ppOut);
+** rc = tdsqlite3changegroup_output(pGrp, pnOut, ppOut);
** }else{
** *ppOut = 0;
** *pnOut = 0;
** }
+** </pre>
**
-** Refer to the sqlite3_changegroup documentation below for details.
+** Refer to the tdsqlite3_changegroup documentation below for details.
*/
-int sqlite3changeset_concat(
+int tdsqlite3changeset_concat(
int nA, /* Number of bytes in buffer pA */
void *pA, /* Pointer to buffer containing changeset A */
int nB, /* Number of bytes in buffer pB */
@@ -727,48 +811,53 @@ int sqlite3changeset_concat(
/*
** CAPI3REF: Changegroup Handle
+**
+** A changegroup is an object used to combine two or more
+** [changesets] or [patchsets]
*/
-typedef struct sqlite3_changegroup sqlite3_changegroup;
+typedef struct tdsqlite3_changegroup tdsqlite3_changegroup;
/*
** CAPI3REF: Create A New Changegroup Object
+** CONSTRUCTOR: tdsqlite3_changegroup
**
-** An sqlite3_changegroup object is used to combine two or more changesets
+** An tdsqlite3_changegroup object is used to combine two or more changesets
** (or patchsets) into a single changeset (or patchset). A single changegroup
** object may combine changesets or patchsets, but not both. The output is
** always in the same format as the input.
**
** If successful, this function returns SQLITE_OK and populates (*pp) with
-** a pointer to a new sqlite3_changegroup object before returning. The caller
+** a pointer to a new tdsqlite3_changegroup object before returning. The caller
** should eventually free the returned object using a call to
-** sqlite3changegroup_delete(). If an error occurs, an SQLite error code
+** tdsqlite3changegroup_delete(). If an error occurs, an SQLite error code
** (i.e. SQLITE_NOMEM) is returned and *pp is set to NULL.
**
-** The usual usage pattern for an sqlite3_changegroup object is as follows:
+** The usual usage pattern for an tdsqlite3_changegroup object is as follows:
**
** <ul>
-** <li> It is created using a call to sqlite3changegroup_new().
+** <li> It is created using a call to tdsqlite3changegroup_new().
**
** <li> Zero or more changesets (or patchsets) are added to the object
-** by calling sqlite3changegroup_add().
+** by calling tdsqlite3changegroup_add().
**
** <li> The result of combining all input changesets together is obtained
-** by the application via a call to sqlite3changegroup_output().
+** by the application via a call to tdsqlite3changegroup_output().
**
-** <li> The object is deleted using a call to sqlite3changegroup_delete().
+** <li> The object is deleted using a call to tdsqlite3changegroup_delete().
** </ul>
**
** Any number of calls to add() and output() may be made between the calls to
** new() and delete(), and in any order.
**
-** As well as the regular sqlite3changegroup_add() and
-** sqlite3changegroup_output() functions, also available are the streaming
-** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm().
+** As well as the regular tdsqlite3changegroup_add() and
+** tdsqlite3changegroup_output() functions, also available are the streaming
+** versions tdsqlite3changegroup_add_strm() and tdsqlite3changegroup_output_strm().
*/
-int sqlite3changegroup_new(sqlite3_changegroup **pp);
+int tdsqlite3changegroup_new(tdsqlite3_changegroup **pp);
/*
** CAPI3REF: Add A Changeset To A Changegroup
+** METHOD: tdsqlite3_changegroup
**
** Add all changes within the changeset (or patchset) in buffer pData (size
** nData bytes) to the changegroup.
@@ -837,23 +926,24 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp);
** case, this function fails with SQLITE_SCHEMA. If the input changeset
** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is
** returned. Or, if an out-of-memory condition occurs during processing, this
-** function returns SQLITE_NOMEM. In all cases, if an error occurs the
-** final contents of the changegroup is undefined.
+** function returns SQLITE_NOMEM. In all cases, if an error occurs the state
+** of the final contents of the changegroup is undefined.
**
** If no error occurs, SQLITE_OK is returned.
*/
-int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
+int tdsqlite3changegroup_add(tdsqlite3_changegroup*, int nData, void *pData);
/*
** CAPI3REF: Obtain A Composite Changeset From A Changegroup
+** METHOD: tdsqlite3_changegroup
**
** Obtain a buffer containing a changeset (or patchset) representing the
** current contents of the changegroup. If the inputs to the changegroup
** were themselves changesets, the output is a changeset. Or, if the
** inputs were patchsets, the output is also a patchset.
**
-** As with the output of the sqlite3session_changeset() and
-** sqlite3session_patchset() functions, all changes related to a single
+** As with the output of the tdsqlite3session_changeset() and
+** tdsqlite3session_patchset() functions, all changes related to a single
** table are grouped together in the output of this function. Tables appear
** in the same order as for the very first changeset added to the changegroup.
** If the second or subsequent changesets added to the changegroup contain
@@ -866,35 +956,35 @@ int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
** is returned and the output variables are set to the size of and a
** pointer to the output buffer, respectively. In this case it is the
** responsibility of the caller to eventually free the buffer using a
-** call to sqlite3_free().
+** call to tdsqlite3_free().
*/
-int sqlite3changegroup_output(
- sqlite3_changegroup*,
+int tdsqlite3changegroup_output(
+ tdsqlite3_changegroup*,
int *pnData, /* OUT: Size of output buffer in bytes */
void **ppData /* OUT: Pointer to output buffer */
);
/*
** CAPI3REF: Delete A Changegroup Object
+** DESTRUCTOR: tdsqlite3_changegroup
*/
-void sqlite3changegroup_delete(sqlite3_changegroup*);
+void tdsqlite3changegroup_delete(tdsqlite3_changegroup*);
/*
** CAPI3REF: Apply A Changeset To A Database
**
-** Apply a changeset to a database. This function attempts to update the
-** "main" database attached to handle db with the changes found in the
-** changeset passed via the second and third arguments.
+** Apply a changeset or patchset to a database. These functions attempt to
+** update the "main" database attached to handle db with the changes found in
+** the changeset passed via the second and third arguments.
**
-** The fourth argument (xFilter) passed to this function is the "filter
+** The fourth argument (xFilter) passed to these functions is the "filter
** callback". If it is not NULL, then for each table affected by at least one
** change in the changeset, the filter callback is invoked with
** the table name as the second argument, and a copy of the context pointer
-** passed as the sixth argument to this function as the first. If the "filter
-** callback" returns zero, then no attempt is made to apply any changes to
-** the table. Otherwise, if the return value is non-zero or the xFilter
-** argument to this function is NULL, all changes related to the table are
-** attempted.
+** passed as the sixth argument as the first. If the "filter callback"
+** returns zero, then no attempt is made to apply any changes to the table.
+** Otherwise, if the return value is non-zero or the xFilter argument to
+** is NULL, all changes related to the table are attempted.
**
** For each table that is not excluded by the filter callback, this function
** tests that the target database contains a compatible table. A table is
@@ -903,7 +993,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** <ul>
** <li> The table has the same name as the name recorded in the
** changeset, and
-** <li> The table has the same number of columns as recorded in the
+** <li> The table has at least as many columns as recorded in the
** changeset, and
** <li> The table has primary key columns in the same position as
** recorded in the changeset.
@@ -911,13 +1001,13 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** If there is no compatible table, it is not an error, but none of the
** changes associated with the table are applied. A warning message is issued
-** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
+** via the tdsqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
** one such warning is issued for each table in the changeset.
**
** For each change for which there is a compatible table, an attempt is made
** to modify the table contents according to the UPDATE, INSERT or DELETE
** change. If a change cannot be applied cleanly, the conflict handler
-** function passed as the fifth argument to sqlite3changeset_apply() may be
+** function passed as the fifth argument to tdsqlite3changeset_apply() may be
** invoked. A description of exactly when the conflict handler is invoked for
** each type of change is below.
**
@@ -931,15 +1021,15 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** if the second argument passed to the conflict handler is either
** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler
** returns an illegal value, any changes already made are rolled back and
-** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different
-** actions are taken by sqlite3changeset_apply() depending on the value
+** the call to tdsqlite3changeset_apply() returns SQLITE_MISUSE. Different
+** actions are taken by tdsqlite3changeset_apply() depending on the value
** returned by each invocation of the conflict-handler function. Refer to
** the documentation for the three
** [SQLITE_CHANGESET_OMIT|available return values] for details.
**
** <dl>
** <dt>DELETE Changes<dd>
-** For each DELETE change, this function checks if the target database
+** For each DELETE change, the function checks if the target database
** contains a row with the same primary key value (or values) as the
** original row values stored in the changeset. If it does, and the values
** stored in all non-primary key columns also match the values stored in
@@ -948,7 +1038,11 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** If a row with matching primary key values is found, but one or more of
** the non-primary key fields contains a value different from the original
** row value stored in the changeset, the conflict-handler function is
-** invoked with [SQLITE_CHANGESET_DATA] as the second argument.
+** invoked with [SQLITE_CHANGESET_DATA] as the second argument. If the
+** database table has more columns than are recorded in the changeset,
+** only the values of those non-primary key fields are compared against
+** the current database contents - any trailing database table columns
+** are ignored.
**
** If no row with matching primary key values is found in the database,
** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND]
@@ -963,7 +1057,9 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** <dt>INSERT Changes<dd>
** For each INSERT change, an attempt is made to insert the new row into
-** the database.
+** the database. If the changeset row contains fewer fields than the
+** database table, the trailing fields are populated with their default
+** values.
**
** If the attempt to insert the row fails because the database already
** contains a row with the same primary key values, the conflict handler
@@ -978,16 +1074,16 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
** [SQLITE_CHANGESET_REPLACE].
**
** <dt>UPDATE Changes<dd>
-** For each UPDATE change, this function checks if the target database
+** For each UPDATE change, the function checks if the target database
** contains a row with the same primary key value (or values) as the
** original row values stored in the changeset. If it does, and the values
-** stored in all non-primary key columns also match the values stored in
-** the changeset the row is updated within the target database.
+** stored in all modified non-primary key columns also match the values
+** stored in the changeset the row is updated within the target database.
**
** If a row with matching primary key values is found, but one or more of
-** the non-primary key fields contains a value different from an original
-** row value stored in the changeset, the conflict-handler function is
-** invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since
+** the modified non-primary key fields contains a value different from an
+** original row value stored in the changeset, the conflict-handler function
+** is invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since
** UPDATE changes only contain values for non-primary key fields that are
** to be modified, only those fields need to match the original values to
** avoid the SQLITE_CHANGESET_DATA conflict-handler callback.
@@ -1006,17 +1102,34 @@ void sqlite3changegroup_delete(sqlite3_changegroup*);
**
** It is safe to execute SQL statements, including those that write to the
** table that the callback related to, from within the xConflict callback.
-** This can be used to further customize the applications conflict
+** This can be used to further customize the application's conflict
** resolution strategy.
**
-** All changes made by this function are enclosed in a savepoint transaction.
+** All changes made by these functions are enclosed in a savepoint transaction.
** If any other error (aside from a constraint failure when attempting to
** write to the target database) occurs, then the savepoint transaction is
** rolled back, restoring the target database to its original state, and an
** SQLite error code returned.
+**
+** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
+** the input is a changeset (not a patchset), then tdsqlite3changeset_apply_v2()
+** may set (*ppRebase) to point to a "rebase" that may be used with the
+** tdsqlite3_rebaser APIs buffer before returning. In this case (*pnRebase)
+** is set to the size of the buffer in bytes. It is the responsibility of the
+** caller to eventually free any such buffer using tdsqlite3_free(). The buffer
+** is only allocated and populated if one or more conflicts were encountered
+** while applying the patchset. See comments surrounding the tdsqlite3_rebaser
+** APIs for further details.
+**
+** The behavior of tdsqlite3changeset_apply_v2() and its streaming equivalent
+** may be modified by passing a combination of
+** [SQLITE_CHANGESETAPPLY_NOSAVEPOINT | supported flags] as the 9th parameter.
+**
+** Note that the tdsqlite3changeset_apply_v2() API is still <b>experimental</b>
+** and therefore subject to change.
*/
-int sqlite3changeset_apply(
- sqlite3 *db, /* Apply change to "main" db of this handle */
+int tdsqlite3changeset_apply(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset, /* Changeset blob */
int(*xFilter)(
@@ -1026,10 +1139,51 @@ int sqlite3changeset_apply(
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
- sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx /* First argument passed to xConflict */
);
+int tdsqlite3changeset_apply_v2(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ int nChangeset, /* Size of changeset in bytes */
+ void *pChangeset, /* Changeset blob */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase, /* OUT: Rebase data */
+ int flags /* SESSION_CHANGESETAPPLY_* flags */
+);
+
+/*
+** CAPI3REF: Flags for tdsqlite3changeset_apply_v2
+**
+** The following flags may passed via the 9th parameter to
+** [tdsqlite3changeset_apply_v2] and [tdsqlite3changeset_apply_v2_strm]:
+**
+** <dl>
+** <dt>SQLITE_CHANGESETAPPLY_NOSAVEPOINT <dd>
+** Usually, the sessions module encloses all operations performed by
+** a single call to apply_v2() or apply_v2_strm() in a [SAVEPOINT]. The
+** SAVEPOINT is committed if the changeset or patchset is successfully
+** applied, or rolled back if an error occurs. Specifying this flag
+** causes the sessions module to omit this savepoint. In this case, if the
+** caller has an open transaction or savepoint when apply_v2() is called,
+** it may revert the partially applied changeset by rolling it back.
+**
+** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
+** Invert the changeset before applying it. This is equivalent to inverting
+** a changeset using tdsqlite3changeset_invert() before applying it. It is
+** an error to specify this flag with a patchset.
+*/
+#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001
+#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
/*
** CAPI3REF: Constants Passed To The Conflict Handler
@@ -1053,7 +1207,7 @@ int sqlite3changeset_apply(
** required PRIMARY KEY fields is not present in the database.
**
** There is no conflicting row in this case. The results of invoking the
-** sqlite3changeset_conflict() API are undefined.
+** tdsqlite3changeset_conflict() API are undefined.
**
** <dt>SQLITE_CHANGESET_CONFLICT<dd>
** CHANGESET_CONFLICT is passed as the second argument to the conflict
@@ -1073,8 +1227,8 @@ int sqlite3changeset_apply(
** CHANGESET_ABORT, the changeset is rolled back.
**
** No current or conflicting row information is provided. The only function
-** it is possible to call on the supplied sqlite3_changeset_iter handle
-** is sqlite3changeset_fk_conflicts().
+** it is possible to call on the supplied tdsqlite3_changeset_iter handle
+** is tdsqlite3changeset_fk_conflicts().
**
** <dt>SQLITE_CHANGESET_CONSTRAINT<dd>
** If any other constraint violation occurs while applying a change (i.e.
@@ -1082,7 +1236,7 @@ int sqlite3changeset_apply(
** invoked with CHANGESET_CONSTRAINT as the second argument.
**
** There is no conflicting row in this case. The results of invoking the
-** sqlite3changeset_conflict() API are undefined.
+** tdsqlite3changeset_conflict() API are undefined.
**
** </dl>
*/
@@ -1107,7 +1261,7 @@ int sqlite3changeset_apply(
** This value may only be returned if the second argument to the conflict
** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this
** is not the case, any changes applied so far are rolled back and the
-** call to sqlite3changeset_apply() returns SQLITE_MISUSE.
+** call to tdsqlite3changeset_apply() returns SQLITE_MISUSE.
**
** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict
** handler, then the conflicting row is either updated or deleted, depending
@@ -1120,13 +1274,168 @@ int sqlite3changeset_apply(
**
** <dt>SQLITE_CHANGESET_ABORT<dd>
** If this value is returned, any changes applied so far are rolled back
-** and the call to sqlite3changeset_apply() returns SQLITE_ABORT.
+** and the call to tdsqlite3changeset_apply() returns SQLITE_ABORT.
** </dl>
*/
#define SQLITE_CHANGESET_OMIT 0
#define SQLITE_CHANGESET_REPLACE 1
#define SQLITE_CHANGESET_ABORT 2
+/*
+** CAPI3REF: Rebasing changesets
+** EXPERIMENTAL
+**
+** Suppose there is a site hosting a database in state S0. And that
+** modifications are made that move that database to state S1 and a
+** changeset recorded (the "local" changeset). Then, a changeset based
+** on S0 is received from another site (the "remote" changeset) and
+** applied to the database. The database is then in state
+** (S1+"remote"), where the exact state depends on any conflict
+** resolution decisions (OMIT or REPLACE) made while applying "remote".
+** Rebasing a changeset is to update it to take those conflict
+** resolution decisions into account, so that the same conflicts
+** do not have to be resolved elsewhere in the network.
+**
+** For example, if both the local and remote changesets contain an
+** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)":
+**
+** local: INSERT INTO t1 VALUES(1, 'v1');
+** remote: INSERT INTO t1 VALUES(1, 'v2');
+**
+** and the conflict resolution is REPLACE, then the INSERT change is
+** removed from the local changeset (it was overridden). Or, if the
+** conflict resolution was "OMIT", then the local changeset is modified
+** to instead contain:
+**
+** UPDATE t1 SET b = 'v2' WHERE a=1;
+**
+** Changes within the local changeset are rebased as follows:
+**
+** <dl>
+** <dt>Local INSERT<dd>
+** This may only conflict with a remote INSERT. If the conflict
+** resolution was OMIT, then add an UPDATE change to the rebased
+** changeset. Or, if the conflict resolution was REPLACE, add
+** nothing to the rebased changeset.
+**
+** <dt>Local DELETE<dd>
+** This may conflict with a remote UPDATE or DELETE. In both cases the
+** only possible resolution is OMIT. If the remote operation was a
+** DELETE, then add no change to the rebased changeset. If the remote
+** operation was an UPDATE, then the old.* fields of change are updated
+** to reflect the new.* values in the UPDATE.
+**
+** <dt>Local UPDATE<dd>
+** This may conflict with a remote UPDATE or DELETE. If it conflicts
+** with a DELETE, and the conflict resolution was OMIT, then the update
+** is changed into an INSERT. Any undefined values in the new.* record
+** from the update change are filled in using the old.* values from
+** the conflicting DELETE. Or, if the conflict resolution was REPLACE,
+** the UPDATE change is simply omitted from the rebased changeset.
+**
+** If conflict is with a remote UPDATE and the resolution is OMIT, then
+** the old.* values are rebased using the new.* values in the remote
+** change. Or, if the resolution is REPLACE, then the change is copied
+** into the rebased changeset with updates to columns also updated by
+** the conflicting remote UPDATE removed. If this means no columns would
+** be updated, the change is omitted.
+** </dl>
+**
+** A local change may be rebased against multiple remote changes
+** simultaneously. If a single key is modified by multiple remote
+** changesets, they are combined as follows before the local changeset
+** is rebased:
+**
+** <ul>
+** <li> If there has been one or more REPLACE resolutions on a
+** key, it is rebased according to a REPLACE.
+**
+** <li> If there have been no REPLACE resolutions on a key, then
+** the local changeset is rebased according to the most recent
+** of the OMIT resolutions.
+** </ul>
+**
+** Note that conflict resolutions from multiple remote changesets are
+** combined on a per-field basis, not per-row. This means that in the
+** case of multiple remote UPDATE operations, some fields of a single
+** local change may be rebased for REPLACE while others are rebased for
+** OMIT.
+**
+** In order to rebase a local changeset, the remote changeset must first
+** be applied to the local database using tdsqlite3changeset_apply_v2() and
+** the buffer of rebase information captured. Then:
+**
+** <ol>
+** <li> An tdsqlite3_rebaser object is created by calling
+** tdsqlite3rebaser_create().
+** <li> The new object is configured with the rebase buffer obtained from
+** tdsqlite3changeset_apply_v2() by calling tdsqlite3rebaser_configure().
+** If the local changeset is to be rebased against multiple remote
+** changesets, then tdsqlite3rebaser_configure() should be called
+** multiple times, in the same order that the multiple
+** tdsqlite3changeset_apply_v2() calls were made.
+** <li> Each local changeset is rebased by calling tdsqlite3rebaser_rebase().
+** <li> The tdsqlite3_rebaser object is deleted by calling
+** tdsqlite3rebaser_delete().
+** </ol>
+*/
+typedef struct tdsqlite3_rebaser tdsqlite3_rebaser;
+
+/*
+** CAPI3REF: Create a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Allocate a new changeset rebaser object. If successful, set (*ppNew) to
+** point to the new object and return SQLITE_OK. Otherwise, if an error
+** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew)
+** to NULL.
+*/
+int tdsqlite3rebaser_create(tdsqlite3_rebaser **ppNew);
+
+/*
+** CAPI3REF: Configure a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Configure the changeset rebaser object to rebase changesets according
+** to the conflict resolutions described by buffer pRebase (size nRebase
+** bytes), which must have been obtained from a previous call to
+** tdsqlite3changeset_apply_v2().
+*/
+int tdsqlite3rebaser_configure(
+ tdsqlite3_rebaser*,
+ int nRebase, const void *pRebase
+);
+
+/*
+** CAPI3REF: Rebase a changeset
+** EXPERIMENTAL
+**
+** Argument pIn must point to a buffer containing a changeset nIn bytes
+** in size. This function allocates and populates a buffer with a copy
+** of the changeset rebased according to the configuration of the
+** rebaser object passed as the first argument. If successful, (*ppOut)
+** is set to point to the new buffer containing the rebased changeset and
+** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the
+** responsibility of the caller to eventually free the new buffer using
+** tdsqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut)
+** are set to zero and an SQLite error code returned.
+*/
+int tdsqlite3rebaser_rebase(
+ tdsqlite3_rebaser*,
+ int nIn, const void *pIn,
+ int *pnOut, void **ppOut
+);
+
+/*
+** CAPI3REF: Delete a changeset rebaser object.
+** EXPERIMENTAL
+**
+** Delete the changeset rebaser object and all associated resources. There
+** should be one call to this function for each successful invocation
+** of tdsqlite3rebaser_create().
+*/
+void tdsqlite3rebaser_delete(tdsqlite3_rebaser *p);
+
/*
** CAPI3REF: Streaming Versions of API functions.
**
@@ -1135,18 +1444,19 @@ int sqlite3changeset_apply(
**
** <table border=1 style="margin-left:8ex;margin-right:8ex">
** <tr><th>Streaming function<th>Non-streaming equivalent</th>
-** <tr><td>sqlite3changeset_apply_str<td>[sqlite3changeset_apply]
-** <tr><td>sqlite3changeset_concat_str<td>[sqlite3changeset_concat]
-** <tr><td>sqlite3changeset_invert_str<td>[sqlite3changeset_invert]
-** <tr><td>sqlite3changeset_start_str<td>[sqlite3changeset_start]
-** <tr><td>sqlite3session_changeset_str<td>[sqlite3session_changeset]
-** <tr><td>sqlite3session_patchset_str<td>[sqlite3session_patchset]
+** <tr><td>tdsqlite3changeset_apply_strm<td>[tdsqlite3changeset_apply]
+** <tr><td>tdsqlite3changeset_apply_strm_v2<td>[tdsqlite3changeset_apply_v2]
+** <tr><td>tdsqlite3changeset_concat_strm<td>[tdsqlite3changeset_concat]
+** <tr><td>tdsqlite3changeset_invert_strm<td>[tdsqlite3changeset_invert]
+** <tr><td>tdsqlite3changeset_start_strm<td>[tdsqlite3changeset_start]
+** <tr><td>tdsqlite3session_changeset_strm<td>[tdsqlite3session_changeset]
+** <tr><td>tdsqlite3session_patchset_strm<td>[tdsqlite3session_patchset]
** </table>
**
** Non-streaming functions that accept changesets (or patchsets) as input
** require that the entire changeset be stored in a single buffer in memory.
** Similarly, those that return a changeset or patchset do so by returning
-** a pointer to a single large buffer allocated using sqlite3_malloc().
+** a pointer to a single large buffer allocated using tdsqlite3_malloc().
** Normally this is convenient. However, if an application running in a
** low-memory environment is required to handle very large changesets, the
** large contiguous memory allocations required can become onerous.
@@ -1179,7 +1489,7 @@ int sqlite3changeset_apply(
** an error, all processing is abandoned and the streaming API function
** returns a copy of the error code to the caller.
**
-** In the case of sqlite3changeset_start_strm(), the xInput callback may be
+** In the case of tdsqlite3changeset_start_strm(), the xInput callback may be
** invoked by the sessions module at any point during the lifetime of the
** iterator. If such an xInput callback returns an error, the iterator enters
** an error state, whereby all subsequent calls to iterator functions
@@ -1216,8 +1526,8 @@ int sqlite3changeset_apply(
** parameter set to a value less than or equal to zero. Other than this,
** no guarantees are made as to the size of the chunks of data returned.
*/
-int sqlite3changeset_apply_strm(
- sqlite3 *db, /* Apply change to "main" db of this handle */
+int tdsqlite3changeset_apply_strm(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
void *pIn, /* First arg for xInput */
int(*xFilter)(
@@ -1227,11 +1537,28 @@ int sqlite3changeset_apply_strm(
int(*xConflict)(
void *pCtx, /* Copy of sixth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
- sqlite3_changeset_iter *p /* Handle describing change and conflict */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx /* First argument passed to xConflict */
);
-int sqlite3changeset_concat_strm(
+int tdsqlite3changeset_apply_v2_strm(
+ tdsqlite3 *db, /* Apply change to "main" db of this handle */
+ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
+ void *pIn, /* First arg for xInput */
+ int(*xFilter)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ const char *zTab /* Table name */
+ ),
+ int(*xConflict)(
+ void *pCtx, /* Copy of sixth arg to _apply() */
+ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ tdsqlite3_changeset_iter *p /* Handle describing change and conflict */
+ ),
+ void *pCtx, /* First argument passed to xConflict */
+ void **ppRebase, int *pnRebase,
+ int flags
+);
+int tdsqlite3changeset_concat_strm(
int (*xInputA)(void *pIn, void *pData, int *pnData),
void *pInA,
int (*xInputB)(void *pIn, void *pData, int *pnData),
@@ -1239,36 +1566,88 @@ int sqlite3changeset_concat_strm(
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3changeset_invert_strm(
+int tdsqlite3changeset_invert_strm(
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3changeset_start_strm(
- sqlite3_changeset_iter **pp,
+int tdsqlite3changeset_start_strm(
+ tdsqlite3_changeset_iter **pp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
);
-int sqlite3session_changeset_strm(
- sqlite3_session *pSession,
+int tdsqlite3changeset_start_v2_strm(
+ tdsqlite3_changeset_iter **pp,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int flags
+);
+int tdsqlite3session_changeset_strm(
+ tdsqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3session_patchset_strm(
- sqlite3_session *pSession,
+int tdsqlite3session_patchset_strm(
+ tdsqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
-int sqlite3changegroup_add_strm(sqlite3_changegroup*,
+int tdsqlite3changegroup_add_strm(tdsqlite3_changegroup*,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
);
-int sqlite3changegroup_output_strm(sqlite3_changegroup*,
+int tdsqlite3changegroup_output_strm(tdsqlite3_changegroup*,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
);
+int tdsqlite3rebaser_rebase_strm(
+ tdsqlite3_rebaser *pRebaser,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut
+);
+/*
+** CAPI3REF: Configure global parameters
+**
+** The tdsqlite3session_config() interface is used to make global configuration
+** changes to the sessions module in order to tune it to the specific needs
+** of the application.
+**
+** The tdsqlite3session_config() interface is not threadsafe. If it is invoked
+** while any other thread is inside any other sessions method then the
+** results are undefined. Furthermore, if it is invoked after any sessions
+** related objects have been created, the results are also undefined.
+**
+** The first argument to the tdsqlite3session_config() function must be one
+** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The
+** interpretation of the (void*) value passed as the second parameter and
+** the effect of calling this function depends on the value of the first
+** parameter.
+**
+** <dl>
+** <dt>SQLITE_SESSION_CONFIG_STRMSIZE<dd>
+** By default, the sessions module streaming interfaces attempt to input
+** and output data in approximately 1 KiB chunks. This operand may be used
+** to set and query the value of this configuration setting. The pointer
+** passed as the second argument must point to a value of type (int).
+** If this value is greater than 0, it is used as the new streaming data
+** chunk size for both input and output. Before returning, the (int) value
+** pointed to by pArg is set to the final value of the streaming interface
+** chunk size.
+** </dl>
+**
+** This function returns SQLITE_OK if successful, or an SQLite error code
+** otherwise.
+*/
+int tdsqlite3session_config(int op, void *pArg);
+
+/*
+** CAPI3REF: Values for tdsqlite3session_config().
+*/
+#define SQLITE_SESSION_CONFIG_STRMSIZE 1
/*
** Make sure we can call this stuff from C++.
diff --git a/protocols/Telegram/tdlib/td/src.ps1 b/protocols/Telegram/tdlib/td/src.ps1
index c5d1d1d619..7350a4eb30 100644
--- a/protocols/Telegram/tdlib/td/src.ps1
+++ b/protocols/Telegram/tdlib/td/src.ps1
@@ -1 +1 @@
-git ls-tree -r HEAD --name-only benchmark example memprof td tdactor tddb tdnet tdtl tdutils test tg_http_client | Select-String "\.cpp$|\.h$|\.hpp$" | Select-String -NotMatch "third_party"
+git ls-tree -r HEAD --name-only benchmark example memprof td tdactor tddb tdnet tdtl tdutils test tg_http_client | Select-String "\.cpp$|\.h$|\.hpp$"
diff --git a/protocols/Telegram/tdlib/td/src.sh b/protocols/Telegram/tdlib/td/src.sh
index 5b89d9d40d..d129641efc 100644
--- a/protocols/Telegram/tdlib/td/src.sh
+++ b/protocols/Telegram/tdlib/td/src.sh
@@ -1,2 +1,2 @@
#!/bin/bash
-git ls-tree -r HEAD --name-only benchmark example memprof td tdactor tddb tdnet tdtl tdutils test tg_http_client | grep -E "\.cpp$|\.h$|\.hpp$" | grep -v third_party
+git ls-tree -r HEAD --name-only benchmark example memprof td tdactor tddb tdnet tdtl tdutils test tg_http_client | grep -E "\.cpp$|\.h$|\.hpp$"
diff --git a/protocols/Telegram/tdlib/td/td/generate/CMakeLists.txt b/protocols/Telegram/tdlib/td/td/generate/CMakeLists.txt
index 0b07d61604..e7f6aea067 100644
--- a/protocols/Telegram/tdlib/td/td/generate/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/td/generate/CMakeLists.txt
@@ -1,45 +1,55 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
+
+if (NOT DEFINED CMAKE_INSTALL_BINDIR)
+ set(CMAKE_INSTALL_BINDIR "bin")
+endif()
file(MAKE_DIRECTORY auto/td/telegram)
file(MAKE_DIRECTORY auto/td/mtproto)
-
-set(TL_TD_AUTO_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/auto PARENT_SCOPE)
-
-set(TL_TD_AUTO
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/mtproto/mtproto_api.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/mtproto/mtproto_api.h
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/mtproto/mtproto_api.hpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/td_api.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/td_api.h
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/td_api.hpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/telegram_api.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/telegram_api.h
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/telegram_api.hpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/secret_api.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/secret_api.h
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/secret_api.hpp
+file(MAKE_DIRECTORY auto/tlo)
+
+set(TL_TD_AUTO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/auto PARENT_SCOPE)
+
+set(TD_AUTO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/auto/td)
+
+set(TL_TD_AUTO_SOURCE
+ ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.cpp
+ ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.h
+ ${TD_AUTO_INCLUDE_DIR}/mtproto/mtproto_api.hpp
+ ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.cpp
+ ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.h
+ ${TD_AUTO_INCLUDE_DIR}/telegram/telegram_api.hpp
+ ${TD_AUTO_INCLUDE_DIR}/telegram/secret_api.cpp
+ ${TD_AUTO_INCLUDE_DIR}/telegram/secret_api.h
+ ${TD_AUTO_INCLUDE_DIR}/telegram/secret_api.hpp
PARENT_SCOPE
)
-set(TL_TD_JSON_AUTO
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/td_api_json.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/td_api_json.h
+set(TL_TD_API_AUTO_SOURCE
+ ${TD_AUTO_INCLUDE_DIR}/telegram/td_api.cpp
+ ${TD_AUTO_INCLUDE_DIR}/telegram/td_api.h
+ ${TD_AUTO_INCLUDE_DIR}/telegram/td_api.hpp
PARENT_SCOPE
)
-set(TL_TD_API_TLO ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tlo)
-set(TL_TD_API_TLO ${TL_TD_API_TLO} PARENT_SCOPE)
+set(TL_TD_JSON_AUTO_SOURCE
+ ${TD_AUTO_INCLUDE_DIR}/telegram/td_api_json.cpp
+ ${TD_AUTO_INCLUDE_DIR}/telegram/td_api_json.h
+ PARENT_SCOPE
+)
-set(TL_C_AUTO
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/td_tdc_api.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/td_tdc_api.h
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/td_tdc_api_inner.h
+set(TL_C_AUTO_SOURCE
+ ${TD_AUTO_INCLUDE_DIR}/telegram/td_tdc_api.cpp
+ ${TD_AUTO_INCLUDE_DIR}/telegram/td_tdc_api.h
+ ${TD_AUTO_INCLUDE_DIR}/telegram/td_tdc_api_inner.h
PARENT_SCOPE
)
-set(TL_DOTNET_AUTO
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/TdDotNetApi.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/auto/td/telegram/TdDotNetApi.h
+set(TL_DOTNET_AUTO_SOURCE
+ ${TD_AUTO_INCLUDE_DIR}/telegram/TdDotNetApi.cpp
+ ${TD_AUTO_INCLUDE_DIR}/telegram/TdDotNetApi.h
PARENT_SCOPE
)
@@ -86,19 +96,46 @@ set(TL_GENERATE_JSON_SOURCE
if (NOT CMAKE_CROSSCOMPILING)
find_program(PHP_EXECUTABLE php)
+ if ((CMAKE_SYSTEM_NAME MATCHES "FreeBSD") AND (CMAKE_SYSTEM_VERSION MATCHES "HBSD"))
+ set(PHP_EXECUTABLE "PHP_EXECUTABLE-NOTFOUND")
+ endif()
+
if (PHP_EXECUTABLE AND NOT TD_ENABLE_DOTNET)
set(GENERATE_COMMON_CMD generate_common && ${PHP_EXECUTABLE} DoxygenTlDocumentationGenerator.php scheme/td_api.tl auto/td/telegram/td_api.h)
else()
set(GENERATE_COMMON_CMD generate_common)
endif()
+ add_subdirectory(tl-parser)
+
+ set(TLO_AUTO_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/auto/tlo)
+ set(TLO_FILES ${TLO_AUTO_INCLUDE_DIR}/mtproto_api.tlo ${TLO_AUTO_INCLUDE_DIR}/secret_api.tlo ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo ${TLO_AUTO_INCLUDE_DIR}/telegram_api.tlo)
+ set(TD_API_TLO_FILE ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo)
+
+ # Ninja generator uses relative paths and can't correctly handle these dependencies
+ # See https://gitlab.kitware.com/cmake/cmake/-/issues/13894
+ if (CMAKE_GENERATOR STREQUAL "Ninja")
+ set(TLO_FILES)
+ set(TD_API_TLO_FILE)
+ endif()
+
+ add_custom_target(tl_generate_tlo
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ COMMAND tl-parser -e auto/tlo/mtproto_api.tlo scheme/mtproto_api.tl
+ COMMAND tl-parser -e auto/tlo/secret_api.tlo scheme/secret_api.tl
+ COMMAND tl-parser -e auto/tlo/td_api.tlo scheme/td_api.tl
+ COMMAND tl-parser -e auto/tlo/telegram_api.tlo scheme/telegram_api.tl
+ COMMENT "Generate TLO files"
+ DEPENDS tl-parser ${CMAKE_CURRENT_SOURCE_DIR}/scheme/mtproto_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/scheme/secret_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/scheme/telegram_api.tl
+ )
+
add_executable(generate_common ${TL_GENERATE_COMMON_SOURCE})
target_link_libraries(generate_common PRIVATE tdtl)
add_custom_target(tl_generate_common
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${GENERATE_COMMON_CMD}
- COMMENT "Generate common tl source files"
- DEPENDS generate_common scheme/mtproto_api.tlo scheme/telegram_api.tlo scheme/secret_api.tlo ${TL_TD_API_TLO} DoxygenTlDocumentationGenerator.php
+ COMMENT "Generate common TL source files"
+ DEPENDS generate_common tl_generate_tlo ${TLO_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/DoxygenTlDocumentationGenerator.php
)
if (TD_ENABLE_JNI)
target_compile_definitions(generate_common PRIVATE TD_ENABLE_JNI=1)
@@ -112,8 +149,8 @@ if (NOT CMAKE_CROSSCOMPILING)
add_custom_target(tl_generate_c
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND generate_c
- COMMENT "Generate C tl source files"
- DEPENDS generate_c ${TL_TD_API_TLO}
+ COMMENT "Generate C TL source files"
+ DEPENDS generate_c tl_generate_tlo ${TD_API_TLO_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl
)
add_executable(td_generate_java_api ${TL_GENERATE_JAVA_SOURCE})
@@ -124,30 +161,30 @@ if (NOT CMAKE_CROSSCOMPILING)
add_custom_target(tl_generate_json
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND generate_json
- COMMENT "Generate JSON tl source files"
- DEPENDS generate_json ${TL_TD_API_TLO}
+ COMMENT "Generate JSON TL source files"
+ DEPENDS generate_json tl_generate_tlo ${TD_API_TLO_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl
)
if (TD_ENABLE_JNI)
- install(TARGETS td_generate_java_api RUNTIME DESTINATION bin)
- install(FILES JavadocTlDocumentationGenerator.php TlDocumentationGenerator.php DESTINATION bin/td/generate)
- install(FILES scheme/td_api.tlo scheme/td_api.tl DESTINATION bin/td/generate/scheme)
+ install(TARGETS td_generate_java_api RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
+ install(FILES JavadocTlDocumentationGenerator.php TlDocumentationGenerator.php DESTINATION "${CMAKE_INSTALL_BINDIR}/td/generate")
+ install(FILES ${TLO_AUTO_INCLUDE_DIR}/td_api.tlo scheme/td_api.tl DESTINATION "${CMAKE_INSTALL_BINDIR}/td/generate/scheme")
endif()
if (TD_ENABLE_DOTNET)
if (PHP_EXECUTABLE)
- set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TL_TD_API_TLO} && ${PHP_EXECUTABLE} DotnetTlDocumentationGenerator.php scheme/td_api.tl auto/td/telegram/TdDotNetApi.h)
+ set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TD_API_TLO_FILE} && ${PHP_EXECUTABLE} DotnetTlDocumentationGenerator.php scheme/td_api.tl auto/td/telegram/TdDotNetApi.h)
else()
- set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TL_TD_API_TLO})
+ set(GENERATE_DOTNET_API_CMD td_generate_dotnet_api ${TD_API_TLO_FILE})
endif()
add_executable(td_generate_dotnet_api generate_dotnet.cpp tl_writer_dotnet.h)
target_link_libraries(td_generate_dotnet_api PRIVATE tdtl)
add_custom_target(generate_dotnet_api
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
- COMMAND ${GENERATE_DOTNET_API_CMD} ${TL_TD_API_TLO}
+ COMMAND ${GENERATE_DOTNET_API_CMD}
COMMENT "Generate .NET API files"
- DEPENDS td_generate_dotnet_api ${TL_TD_API_TLO} DotnetTlDocumentationGenerator.php
+ DEPENDS td_generate_dotnet_api tl_generate_tlo ${TD_API_TLO_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/scheme/td_api.tl ${CMAKE_CURRENT_SOURCE_DIR}/DotnetTlDocumentationGenerator.php
)
endif()
diff --git a/protocols/Telegram/tdlib/td/td/generate/DotnetTlDocumentationGenerator.php b/protocols/Telegram/tdlib/td/td/generate/DotnetTlDocumentationGenerator.php
index 7e11cc6653..da792b3620 100644
--- a/protocols/Telegram/tdlib/td/td/generate/DotnetTlDocumentationGenerator.php
+++ b/protocols/Telegram/tdlib/td/td/generate/DotnetTlDocumentationGenerator.php
@@ -6,9 +6,13 @@ class DotnetTlDocumentationGenerator extends TlDocumentationGenerator
{
protected function escapeDocumentation($doc)
{
+ $doc = preg_replace_callback('/(?<!["A-Za-z_\/])[A-Za-z]*_[A-Za-z_]*/',
+ function ($word_matches)
+ {
+ return ucfirst(preg_replace_callback('/_([A-Za-z])/', function ($matches) {return strtoupper($matches[1]);}, $word_matches[0]));
+ }, $doc);
$doc = htmlspecialchars($doc, ENT_XML1);
$doc = str_replace('*/', '*&#47;', $doc);
- $doc = preg_replace_callback('/_([A-Za-z])/', function ($matches) {return strtoupper($matches[1]);}, $doc);
return $doc;
}
@@ -50,7 +54,7 @@ class DotnetTlDocumentationGenerator extends TlDocumentationGenerator
case 'string':
return 'String^';
case 'bytes':
- return 'Array<byte>^';
+ return 'Array<BYTE>^';
case 'bool':
case 'int':
case 'long':
@@ -154,13 +158,17 @@ EOT
);
}
- protected function addClassDocumentation($class_name, $base_class_name, $description, $return_type)
+ protected function getFunctionReturnTypeDescription($return_type, $for_constructor)
{
- $return_type_description = $return_type ? "\r\n/// <para>Returns <see cref=\"".substr($return_type, 0, -1).'"/>.</para>' : '';
+ $shift = $for_constructor ? ' ' : '';
+ return "\r\n$shift/// <para>Returns <see cref=\"".substr($return_type, 0, -1).'"/>.</para>';
+ }
+ protected function addClassDocumentation($class_name, $base_class_name, $return_type, $description)
+ {
$this->addDocumentation("public ref class $class_name sealed : $base_class_name {", <<<EOT
/// <summary>
-/// $description$return_type_description
+/// $description
/// </summary>
EOT
);
@@ -182,25 +190,28 @@ EOT
);
}
- protected function addDefaultConstructorDocumentation($class_name)
+ protected function addDefaultConstructorDocumentation($class_name, $class_description)
{
$this->addDocumentation(" $class_name();", <<<EOT
/// <summary>
- /// Default constructor.
+ /// $class_description
/// </summary>
EOT
);
}
- protected function addFullConstructorDocumentation($class_name, $known_fields, $info)
+ protected function addFullConstructorDocumentation($class_name, $class_description, $known_fields, $info)
{
$full_constructor = " $class_name(";
$colon = '';
foreach ($known_fields as $name => $type) {
$field_type = $this->getTypeName($type);
- if (substr($field_type, 0, 5) !== 'Array' && substr($field_type, 0, 6) !== 'String' &&
- ucfirst($field_type) === $field_type) {
- $field_type = '::Telegram::Td::Api::'.$field_type;
+ $pos = 0;
+ while (substr($field_type, $pos, 6) === 'Array<') {
+ $pos += 6;
+ }
+ if (substr($field_type, $pos, 4) !== 'BYTE' && substr($field_type, $pos, 6) !== 'String' && ucfirst(substr($field_type, $pos)) === substr($field_type, $pos)) {
+ $field_type = substr($field_type, 0, $pos).'::Telegram::Td::Api::'.substr($field_type, $pos);
}
$full_constructor .= $colon.$field_type.' '.$this->getParameterName($name, $class_name);
$colon = ', ';
@@ -209,11 +220,11 @@ EOT
$full_doc = <<<EOT
/// <summary>
- /// Constructor for initialization of all fields.
+ /// $class_description
/// </summary>
EOT;
foreach ($known_fields as $name => $type) {
- $full_doc .= '\r\n /// <param name="'.$this->getParameterName($name, $class_name).'">'.$info[$name]."</param>";
+ $full_doc .= "\r\n /// <param name=\"".$this->getParameterName($name, $class_name).'">'.$info[$name]."</param>";
}
$this->addDocumentation($full_constructor, $full_doc);
}
diff --git a/protocols/Telegram/tdlib/td/td/generate/DoxygenTlDocumentationGenerator.php b/protocols/Telegram/tdlib/td/td/generate/DoxygenTlDocumentationGenerator.php
index 377e3b3718..dc89536b21 100644
--- a/protocols/Telegram/tdlib/td/td/generate/DoxygenTlDocumentationGenerator.php
+++ b/protocols/Telegram/tdlib/td/td/generate/DoxygenTlDocumentationGenerator.php
@@ -10,22 +10,24 @@ class DoxygenTlDocumentationGenerator extends TlDocumentationGenerator
case 'Bool':
return 'bool ';
case 'int32':
- return 'std::int32_t ';
+ return 'int32 ';
case 'int53':
+ return 'int53 ';
case 'int64':
- return 'std::int64_t ';
+ return 'int64 ';
case 'double':
return 'double ';
case 'string':
+ return 'string const &';
case 'bytes':
- return 'std::string const &';
+ return 'bytes const &';
default:
if (substr($type, 0, 6) === 'vector') {
if ($type[6] !== '<' || $type[strlen($type) - 1] !== '>') {
return '';
}
- return 'std::vector<'.$this->getTypeName(substr($type, 7, -1)).'> &&';
+ return 'array<'.$this->getTypeName(substr($type, 7, -1)).'> &&';
}
if (preg_match('/[^A-Za-z0-9.]/', $type)) {
@@ -38,6 +40,11 @@ class DoxygenTlDocumentationGenerator extends TlDocumentationGenerator
protected function escapeDocumentation($doc)
{
$doc = htmlspecialchars($doc);
+ $doc = preg_replace_callback('/&quot;((http|https|tg):\/\/[^" ]*)&quot;/',
+ function ($quoted_link)
+ {
+ return "&quot;<a href=\"".$quoted_link[1]."\">".$quoted_link[1]."</a>&quot;";
+ }, $doc);
$doc = str_replace('*/', '*&#47;', $doc);
$doc = str_replace('#', '\#', $doc);
return $doc;
@@ -62,15 +69,17 @@ class DoxygenTlDocumentationGenerator extends TlDocumentationGenerator
case 'Bool':
return 'bool';
case 'int32':
- return 'std::int32_t';
+ return 'int32';
case 'int53':
+ return 'int53';
case 'int64':
- return 'std::int64_t';
+ return 'int64';
case 'double':
return 'double';
case 'string':
+ return 'string';
case 'bytes':
- return 'std::string';
+ return 'bytes';
case 'bool':
case 'int':
case 'long':
@@ -90,7 +99,7 @@ class DoxygenTlDocumentationGenerator extends TlDocumentationGenerator
$this->printError("Wrong vector subtype in $type");
return '';
}
- return 'std::vector<'.$this->getTypeName(substr($type, 7, -1)).'>';
+ return 'array<'.$this->getTypeName(substr($type, 7, -1)).'>';
}
if (preg_match('/[^A-Za-z0-9.]/', $type)) {
@@ -118,6 +127,7 @@ class DoxygenTlDocumentationGenerator extends TlDocumentationGenerator
return empty($tline) || $tline[0] === '}' || $tline === 'public:' || strpos($line, '#pragma ') === 0 ||
strpos($line, '#include <') === 0 || strpos($tline, 'return ') === 0 || strpos($tline, 'namespace') === 0 ||
preg_match('/class [A-Za-z0-9_]*;/', $line) || $tline === 'if (value == nullptr) {' ||
+ strpos($tline, 'result += ') === 0 || strpos($tline, 'result = ') || strpos($tline, ' : values') ||
strpos($line, 'JNIEnv') || strpos($line, 'jfieldID') || $tline === 'virtual ~Object() {' ||
$tline === 'virtual void store(TlStorerToString &s, const char *field_name) const = 0;';
}
@@ -160,6 +170,48 @@ class DoxygenTlDocumentationGenerator extends TlDocumentationGenerator
EOT
);
+ $this->addDocumentation('using int32 = std::int32_t;', <<<EOT
+/**
+ * This type is used to store 32-bit signed integers, which can be represented as Number in JSON.
+ */
+EOT
+);
+
+ $this->addDocumentation('using int53 = std::int64_t;', <<<EOT
+/**
+ * This type is used to store 53-bit signed integers, which can be represented as Number in JSON.
+ */
+EOT
+);
+
+ $this->addDocumentation('using int64 = std::int64_t;', <<<EOT
+/**
+ * This type is used to store 64-bit signed integers, which can't be represented as Number in JSON and are represented as String instead.
+ */
+EOT
+);
+
+ $this->addDocumentation('using string = std::string;', <<<EOT
+/**
+ * This type is used to store UTF-8 strings.
+ */
+EOT
+);
+
+ $this->addDocumentation('using bytes = std::string;', <<<EOT
+/**
+ * This type is used to store arbitrary sequences of bytes. In JSON interface the bytes are base64-encoded.
+ */
+EOT
+);
+
+ $this->addDocumentation('using array = std::vector<Type>;', <<<EOT
+/**
+ * This type is used to store a list of objects of any type and is represented as Array in JSON.
+ */
+EOT
+);
+
$this->addDocumentation('using BaseObject', <<<EOT
/**
* This class is a base class for all TDLib API classes and functions.
@@ -181,8 +233,8 @@ EOT
* \\code
* auto get_authorization_state_request = td::td_api::make_object<td::td_api::getAuthorizationState>();
* auto message_text = td::td_api::make_object<td::td_api::formattedText>("Hello, world!!!",
- * std::vector<td::td_api::object_ptr<td::td_api::textEntities>>());
- * auto send_message_request = td::td_api::make_object<td::td_api::sendMessage>(chat_id, 0, false, false, nullptr,
+ * td::td_api::array<td::td_api::object_ptr<td::td_api::textEntity>>());
+ * auto send_message_request = td::td_api::make_object<td::td_api::sendMessage>(chat_id, 0, 0, nullptr, nullptr,
* td::td_api::make_object<td::td_api::inputMessageText>(std::move(message_text), false, true));
* \\endcode
*
@@ -196,7 +248,7 @@ EOT
$this->addDocumentation('object_ptr<ToType> move_object_as(FromType &&from) {', <<<EOT
/**
* A function to cast a wrapped in td::td_api::object_ptr TDLib API object to its subclass or superclass.
- * Casting an object to an incorrect type will lead to undefined bejaviour.
+ * Casting an object to an incorrect type will lead to undefined behaviour.
* Usage example:
* \\code
* td::td_api::object_ptr<td::td_api::callState> call_state = ...;
@@ -243,7 +295,7 @@ EOT
$this->addDocumentation('std::string to_string(const BaseObject &value);', <<<EOT
/**
- * Returns a string representation of the TDLib API object.
+ * Returns a string representation of a TDLib API object.
* \\param[in] value The object.
* \\return Object string representation.
*/
@@ -252,7 +304,7 @@ EOT
$this->addDocumentation('std::string to_string(const object_ptr<T> &value) {', <<<EOT
/**
- * Returns a string representation of the TDLib API object.
+ * Returns a string representation of a TDLib API object.
* \\tparam T Object type, auto-deduced.
* \\param[in] value The object.
* \\return Object string representation.
@@ -260,6 +312,16 @@ EOT
EOT
);
+ $this->addDocumentation('std::string to_string(const std::vector<object_ptr<T>> &values) {', <<<EOT
+/**
+ * Returns a string representation of a list of TDLib API objects.
+ * \\tparam T Object type, auto-deduced.
+ * \\param[in] values The objects.
+ * \\return Objects string representation.
+ */
+EOT
+);
+
$this->addDocumentation(' void store(TlStorerToString &s, const char *field_name) const final;', <<<EOT
/**
* Helper function for to_string method. Appends string representation of the object to the storer.
@@ -288,14 +350,15 @@ EOT
EOT
);
- $this->addDocumentation(' virtual std::int32_t get_id() const = 0;', <<<EOT
+ $this->addDocumentation(' std::int32_t get_id() const final {', <<<EOT
/**
* Returns identifier uniquely determining a type of the object.
+ * \\return this->ID.
*/
EOT
);
- $this->addDocumentation(' std::int32_t get_id() const final {', <<<EOT
+ $this->addDocumentation(' virtual std::int32_t get_id() const = 0;', <<<EOT
/**
* Returns identifier uniquely determining a type of the object.
* \\return this->ID.
@@ -320,13 +383,17 @@ EOT
);
}
- protected function addClassDocumentation($class_name, $base_class_name, $description, $return_type)
+ protected function getFunctionReturnTypeDescription($return_type, $for_constructor)
{
- $return_type_description = $return_type ? PHP_EOL.' *'.PHP_EOL." * Returns $return_type." : '';
+ $shift = $for_constructor ? ' ' : ' ';
+ return PHP_EOL.$shift.'*'.PHP_EOL.$shift."* Returns $return_type.";
+ }
+ protected function addClassDocumentation($class_name, $base_class_name, $return_type, $description)
+ {
$this->addDocumentation("class $class_name final : public $base_class_name {", <<<EOT
/**
- * $description$return_type_description
+ * $description
*/
EOT
);
@@ -340,17 +407,17 @@ EOT
);
}
- protected function addDefaultConstructorDocumentation($class_name)
+ protected function addDefaultConstructorDocumentation($class_name, $class_description)
{
$this->addDocumentation(" $class_name();", <<<EOT
/**
- * Default constructor. All fields will be value-initilaized.
+ * $class_description
*/
EOT
);
}
- protected function addFullConstructorDocumentation($class_name, $known_fields, $info)
+ protected function addFullConstructorDocumentation($class_name, $class_description, $known_fields, $info)
{
$explicit = count($known_fields) === 1 ? 'explicit ' : '';
$full_constructor = " $explicit$class_name(";
@@ -363,7 +430,7 @@ EOT
$full_doc = <<<EOT
/**
- * Constructor for initialization of all fields.
+ * $class_description
*
EOT;
diff --git a/protocols/Telegram/tdlib/td/td/generate/JavadocTlDocumentationGenerator.php b/protocols/Telegram/tdlib/td/td/generate/JavadocTlDocumentationGenerator.php
index 6cf170694f..e9cd2d3e6e 100644
--- a/protocols/Telegram/tdlib/td/td/generate/JavadocTlDocumentationGenerator.php
+++ b/protocols/Telegram/tdlib/td/td/generate/JavadocTlDocumentationGenerator.php
@@ -10,9 +10,13 @@ class JavadocTlDocumentationGenerator extends TlDocumentationGenerator
protected function escapeDocumentation($doc)
{
+ $doc = preg_replace_callback('/(?<!["A-Za-z_\/])[A-Za-z]*_[A-Za-z_]*/',
+ function ($word_matches)
+ {
+ return preg_replace_callback('/_([A-Za-z])/', function ($matches) {return strtoupper($matches[1]);}, $word_matches[0]);
+ }, $doc);
$doc = htmlspecialchars($doc);
$doc = str_replace('*/', '*&#47;', $doc);
- $doc = preg_replace_callback('/_([A-Za-z])/', function ($matches) {return strtoupper($matches[1]);}, $doc);
return $doc;
}
@@ -88,19 +92,19 @@ class JavadocTlDocumentationGenerator extends TlDocumentationGenerator
protected function needSkipLine($line)
{
- $line = trim($line);
- return strpos($line, 'public') !== 0 && !$this->isHeaderLine($line);
+ $line = $this->fixLine(trim($line));
+ return (strpos($line, 'public') !== 0 && !$this->isHeaderLine($line)) || $line === 'public @interface Constructors {}';
}
protected function isHeaderLine($line)
{
- return trim($line) === '@Override';
+ return trim($line) === '@Override' || trim($line) === '@Constructors';
}
protected function extractClassName($line)
{
if (strpos($line, 'public static class ') > 0) {
- return explode(' ', trim($line))[3];
+ return preg_split('/( |<|>)/', trim($line))[3];
}
return '';
}
@@ -139,27 +143,45 @@ EOT
EOT
);
+ $this->addDocumentation(" public Object() {", <<<EOT
+ /**
+ * Default Object constructor.
+ */
+EOT
+);
+
$this->addDocumentation(' public abstract int getConstructor();', <<<EOT
/**
- * @return identifier uniquely determining type of the object.
+ * Returns an identifier uniquely determining type of the object.
+ *
+ * @return a unique identifier of the object type.
*/
EOT
);
$this->addDocumentation(' public native String toString();', <<<EOT
/**
- * @return string representation of the object.
+ * Returns a string representation of the object.
+ *
+ * @return a string representation of the object.
*/
EOT
);
- $this->addDocumentation(' public abstract static class Function extends Object {', <<<EOT
+ $this->addDocumentation(' public abstract static class Function<R extends Object> extends Object {', <<<EOT
/**
* This class is a base class for all TDLib interface function-classes.
*/
EOT
);
+ $this->addDocumentation(" public Function() {", <<<EOT
+ /**
+ * Default Function constructor.
+ */
+EOT
+);
+
$this->addDocumentation(' public static final int CONSTRUCTOR', <<<EOT
/**
* Identifier uniquely determining type of the object.
@@ -184,15 +206,25 @@ EOT
*/
EOT
);
+ $this->addDocumentation(" public $class_name() {", <<<EOT
+ /**
+ * Default class constructor.
+ */
+EOT
+);
}
- protected function addClassDocumentation($class_name, $base_class_name, $description, $return_type)
+ protected function getFunctionReturnTypeDescription($return_type, $for_constructor)
{
- $return_type_description = $return_type ? PHP_EOL.' *'.PHP_EOL." * <p> Returns {@link $return_type $return_type} </p>" : '';
+ $shift = $for_constructor ? ' ' : ' ';
+ return PHP_EOL.$shift.'*'.PHP_EOL.$shift."* <p> Returns {@link $return_type $return_type} </p>";
+ }
- $this->addDocumentation(" public static class $class_name extends $base_class_name {", <<<EOT
+ protected function addClassDocumentation($class_name, $base_class_name, $return_type, $description)
+ {
+ $this->addDocumentation(" public static class $class_name extends ".$base_class_name.(empty($return_type) ? "" : "<".$return_type.">")." {", <<<EOT
/**
- * $description$return_type_description
+ * $description
*/
EOT
);
@@ -208,21 +240,21 @@ EOT
EOT
);
if ($may_be_null && $this->nullable_annotation && ($this->java_version >= 8 || substr($type_name, -1) != ']')) {
- $this->addLineReplacement($full_line, " public $this->nullable_annotation $type_name $field_name;".PHP_EOL);
+ $this->addLineReplacement($full_line, " $this->nullable_annotation public $type_name $field_name;".PHP_EOL);
}
}
- protected function addDefaultConstructorDocumentation($class_name)
+ protected function addDefaultConstructorDocumentation($class_name, $class_description)
{
$this->addDocumentation(" public $class_name() {", <<<EOT
/**
- * Default constructor.
+ * $class_description
*/
EOT
);
}
- protected function addFullConstructorDocumentation($class_name, $known_fields, $info)
+ protected function addFullConstructorDocumentation($class_name, $class_description, $known_fields, $info)
{
$full_constructor = " public $class_name(";
$colon = '';
@@ -234,7 +266,7 @@ EOT
$full_doc = <<<EOT
/**
- * Constructor for initialization of all fields.
+ * $class_description
*
EOT;
diff --git a/protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php b/protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php
index 11a281ce38..f30ca382e1 100644
--- a/protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php
+++ b/protocols/Telegram/tdlib/td/td/generate/TlDocumentationGenerator.php
@@ -53,7 +53,7 @@ abstract class TlDocumentationGenerator
}
}
if ($bracket_count === 0) {
- if (ctype_upper($str[$pos + 1])) {
+ if (ord('A') <= ord($str[$pos + 1]) && ord($str[$pos + 1]) <= ord('Z')) {
return substr($str, 0, -1).'.)';
}
} else {
@@ -87,13 +87,15 @@ abstract class TlDocumentationGenerator
abstract protected function addAbstractClassDocumentation($class_name, $value);
- abstract protected function addClassDocumentation($class_name, $base_class_name, $description, $return_type);
+ abstract protected function getFunctionReturnTypeDescription($return_type, $for_constructor);
+
+ abstract protected function addClassDocumentation($class_name, $base_class_name, $return_type, $description);
abstract protected function addFieldDocumentation($class_name, $field_name, $type_name, $field_info, $may_be_null);
- abstract protected function addDefaultConstructorDocumentation($class_name);
+ abstract protected function addDefaultConstructorDocumentation($class_name, $class_description);
- abstract protected function addFullConstructorDocumentation($class_name, $known_fields, $info);
+ abstract protected function addFullConstructorDocumentation($class_name, $class_description, $known_fields, $info);
public function generate($tl_scheme_file, $source_file)
{
@@ -134,6 +136,9 @@ abstract class TlDocumentationGenerator
$this->printError('Wrong description begin');
}
+ if (preg_match('/[^ ]@/', $description)) {
+ $this->printError("Wrong documentation '@' usage: $description");
+ }
$docs = explode('@', $description);
array_shift($docs);
$info = array();
@@ -146,7 +151,7 @@ abstract class TlDocumentationGenerator
if ($key === 'description') {
$need_class_description = false;
- $value = $this->addDot($value);
+ $value = $this->escapeDocumentation($this->addDot($value));
$this->addAbstractClassDocumentation($current_class, $value);
continue;
@@ -207,43 +212,65 @@ abstract class TlDocumentationGenerator
$known_fields[$field_name] = $field_type;
continue;
}
- $this->printError("Have no info about field `$field_name`");
+ $this->printError("Have no documentation for field `$field_name`");
}
foreach ($info as $name => $value) {
if (!$value) {
- $this->printError("info[$name] for $class_name is empty");
- } elseif ($value[0] < 'A' || $value[0] > 'Z') {
- $this->printError("info[$name] for $class_name doesn't begins with capital letter");
+ $this->printError("Documentation for field $name of $class_name is empty");
+ } elseif (($value[0] < 'A' || $value[0] > 'Z') && ($value[0] < '0' || $value[0] > '9')) {
+ $this->printError("Documentation for field $name of $class_name doesn't begin with a capital letter");
}
}
- foreach (array_diff_key($info, $known_fields) as $field_name => $field_info) {
- if ($field_name !== 'description') {
- $this->printError("Have info about unexisted field `$field_name`");
- }
+ foreach ($info as &$v) {
+ $v = $this->escapeDocumentation($this->addDot($v));
}
- if (!$info['description']) {
+ $description = $info['description'];
+ unset($info['description']);
+
+ if (!$description) {
$this->printError("Have no description for class `$class_name`");
}
- foreach ($info as &$v) {
- $v = $this->escapeDocumentation($this->addDot($v));
+ foreach (array_diff_key($info, $known_fields) as $field_name => $field_info) {
+ $this->printError("Have info about nonexistent field `$field_name`");
+ }
+
+ if (array_keys($info) !== array_keys($known_fields)) {
+ $this->printError("Have wrong documentation for class `$class_name`");
}
$base_class_name = $current_class ?: $this->getBaseClassName($is_function);
- $this->addClassDocumentation($class_name, $base_class_name, $info['description'], $is_function ? $this->getTypeName($type) : '');
+ $class_description = $description;
+ $return_type = "";
+ if ($is_function) {
+ $return_type = $this->getTypeName($type);
+ $class_description .= $this->getFunctionReturnTypeDescription($return_type, false);
+ }
+ $this->addClassDocumentation($class_name, $base_class_name, $return_type, $class_description);
- foreach ($known_fields as $name => $type) {
+ foreach ($known_fields as $name => $field_type) {
$may_be_null = stripos($info[$name], 'may be null') !== false;
- $this->addFieldDocumentation($class_name, $this->getFieldName($name, $class_name), $this->getTypeName($type), $info[$name], $may_be_null);
+ $field_name = $this->getFieldName($name, $class_name);
+ $field_type_name = $this->getTypeName($field_type);
+ $this->addFieldDocumentation($class_name, $field_name, $field_type_name, $info[$name], $may_be_null);
}
- $this->addDefaultConstructorDocumentation($class_name);
+ if ($is_function) {
+ $default_constructor_prefix = 'Default constructor for a function, which ';
+ $full_constructor_prefix = 'Creates a function, which ';
+ $class_description = lcfirst($description);
+ $class_description .= $this->getFunctionReturnTypeDescription($this->getTypeName($type), true);
+ } else {
+ $default_constructor_prefix = '';
+ $full_constructor_prefix = '';
+ }
+ $this->addDefaultConstructorDocumentation($class_name, $default_constructor_prefix.$class_description);
if ($known_fields) {
- $this->addFullConstructorDocumentation($class_name, $known_fields, $info);
+ $this->addFullConstructorDocumentation($class_name, $full_constructor_prefix.$class_description, $known_fields, $info);
}
$description = '';
diff --git a/protocols/Telegram/tdlib/td/td/generate/generate_c.cpp b/protocols/Telegram/tdlib/td/td/generate/generate_c.cpp
index 841d1b366c..40bcc6a4a5 100644
--- a/protocols/Telegram/tdlib/td/td/generate/generate_c.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/generate_c.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,7 +10,7 @@
#include "td/tl/tl_generate.h"
int main() {
- td::tl::tl_config config_td = td::tl::read_tl_config_from_file("scheme/td_api.tlo");
+ td::tl::tl_config config_td = td::tl::read_tl_config_from_file("auto/tlo/td_api.tlo");
td::tl::write_tl_to_file(config_td, "auto/td/telegram/td_tdc_api.h",
td::TlWriterCCommon("TdApi", 1, "#include \"td/telegram/td_api.h\"\n"));
td::tl::write_tl_to_file(config_td, "auto/td/telegram/td_tdc_api_inner.h",
diff --git a/protocols/Telegram/tdlib/td/td/generate/generate_common.cpp b/protocols/Telegram/tdlib/td/td/generate/generate_common.cpp
index 8759c40550..85f62c465e 100644
--- a/protocols/Telegram/tdlib/td/td/generate/generate_common.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/generate_common.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -22,7 +22,7 @@ static void generate_cpp(const std::string &directory, const std::string &tl_nam
const std::string &bytes_type, const std::vector<std::string> &ext_cpp_includes,
const std::vector<std::string> &ext_h_includes) {
std::string path = directory + "/" + tl_name;
- td::tl::tl_config config = td::tl::read_tl_config_from_file("scheme/" + tl_name + ".tlo");
+ td::tl::tl_config config = td::tl::read_tl_config_from_file("auto/tlo/" + tl_name + ".tlo");
td::tl::write_tl_to_file(config, path + ".cpp", WriterCpp(tl_name, string_type, bytes_type, ext_cpp_includes));
td::tl::write_tl_to_file(config, path + ".h", WriterH(tl_name, string_type, bytes_type, ext_h_includes));
td::tl::write_tl_to_file(config, path + ".hpp", WriterHpp(tl_name, string_type, bytes_type));
@@ -36,7 +36,8 @@ int main() {
{"\"td/tl/tl_object_parse.h\"", "\"td/tl/tl_object_store.h\""}, {"\"td/utils/buffer.h\""});
generate_cpp<>("auto/td/mtproto", "mtproto_api", "Slice", "Slice",
- {"\"td/tl/tl_object_parse.h\"", "\"td/tl/tl_object_store.h\""}, {"\"td/utils/Slice.h\""});
+ {"\"td/tl/tl_object_parse.h\"", "\"td/tl/tl_object_store.h\""},
+ {"\"td/utils/Slice.h\"", "\"td/utils/UInt.h\""});
#ifdef TD_ENABLE_JNI
generate_cpp<td::TD_TL_writer_jni_cpp, td::TD_TL_writer_jni_h>(
diff --git a/protocols/Telegram/tdlib/td/td/generate/generate_dotnet.cpp b/protocols/Telegram/tdlib/td/td/generate/generate_dotnet.cpp
index 59e36778fd..9f7aca9f40 100644
--- a/protocols/Telegram/tdlib/td/td/generate/generate_dotnet.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/generate_dotnet.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -18,6 +18,5 @@ int main(int argc, char *argv[]) {
td::tl::write_tl_to_file(config_td, "auto/td/telegram/TdDotNetApi.cpp",
td::tl::TlWriterDotNet("TdApi", false, "#include \"td/telegram/TdDotNetApi.h\"\n\n"));
- td::tl::write_tl_to_file(config_td, "auto/td/telegram/TdDotNetApi.h",
- td::tl::TlWriterDotNet("TdApi", true, ""));
+ td::tl::write_tl_to_file(config_td, "auto/td/telegram/TdDotNetApi.h", td::tl::TlWriterDotNet("TdApi", true, ""));
}
diff --git a/protocols/Telegram/tdlib/td/td/generate/generate_java.cpp b/protocols/Telegram/tdlib/td/td/generate/generate_java.cpp
index 0dd968b2fa..a3dcc45a8f 100644
--- a/protocols/Telegram/tdlib/td/td/generate/generate_java.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/generate_java.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/td/generate/generate_json.cpp b/protocols/Telegram/tdlib/td/td/generate/generate_json.cpp
index f03f5eeae8..e8079d7eaa 100644
--- a/protocols/Telegram/tdlib/td/td/generate/generate_json.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/generate_json.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,5 +10,6 @@
#include "td/tl/tl_generate.h"
int main() {
- td::gen_json_converter(td::tl::read_tl_config_from_file("scheme/td_api.tlo"), "td/telegram/td_api_json");
+ td::gen_json_converter(td::tl::read_tl_config_from_file("auto/tlo/td_api.tlo"), "td/telegram/td_api_json",
+ td::tl::TL_writer::Server);
}
diff --git a/protocols/Telegram/tdlib/td/td/generate/remove_documentation.cpp b/protocols/Telegram/tdlib/td/td/generate/remove_documentation.cpp
index d690f0ba8b..8969424f21 100644
--- a/protocols/Telegram/tdlib/td/td/generate/remove_documentation.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/remove_documentation.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/td/generate/scheme/mtproto_api.tl b/protocols/Telegram/tdlib/td/td/generate/scheme/mtproto_api.tl
index b89e9bde3f..de2e94bd62 100644
--- a/protocols/Telegram/tdlib/td/td/generate/scheme/mtproto_api.tl
+++ b/protocols/Telegram/tdlib/td/td/generate/scheme/mtproto_api.tl
@@ -12,10 +12,9 @@ int256 8*[ int ] = Int256;
resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector<long> = ResPQ;
-p_q_inner_data#83c95aec pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data;
-p_q_inner_data_temp#3c6a84d4 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data;
+p_q_inner_data_dc#a9f55f95 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data;
+p_q_inner_data_temp_dc#56fddf88 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data;
-server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params;
server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params;
server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:string g_a:string server_time:int = Server_DH_inner_data;
@@ -41,8 +40,8 @@ future_salts#ae500895 req_msg_id:long now:int salts:vector<future_salt> = Future
pong#347773c5 msg_id:long ping_id:long = Pong;
-destroy_session_ok#e22045fc session_id:long = DestroySessionRes;
-destroy_session_none#62d350c9 session_id:long = DestroySessionRes;
+//destroy_session_ok#e22045fc session_id:long = DestroySessionRes;
+//destroy_session_none#62d350c9 session_id:long = DestroySessionRes;
new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession;
@@ -66,6 +65,10 @@ msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDeta
rsa_public_key n:string e:string = RSAPublicKey;
+destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes;
+destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes;
+destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
+
---functions---
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
@@ -76,16 +79,10 @@ set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:st
rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;
get_future_salts#b921bd04 num:int = FutureSalts;
-ping#7abe77ec ping_id:long = Pong;
+//ping#7abe77ec ping_id:long = Pong;
ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;
-destroy_session#e7512126 session_id:long = DestroySessionRes;
+//destroy_session#e7512126 session_id:long = DestroySessionRes;
http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait;
-//test.useGzipPacked = GzipPacked;
-//test.useServerDhInnerData = Server_DH_inner_data;
-//test.useNewSessionCreated = NewSession;
-//test.useMsgsAck = MsgsAck;
-//test.useBadMsgNotification = BadMsgNotification;
-
-//test.useOther key:rsa_public_key p_q_data:P_Q_inner_data dh_data:client_DH_inner_data = RpcError;
+destroy_auth_key#d1435160 = DestroyAuthKeyRes;
diff --git a/protocols/Telegram/tdlib/td/td/generate/scheme/mtproto_api.tlo b/protocols/Telegram/tdlib/td/td/generate/scheme/mtproto_api.tlo
deleted file mode 100644
index 6c3583ddb3..0000000000
--- a/protocols/Telegram/tdlib/td/td/generate/scheme/mtproto_api.tlo
+++ /dev/null
Binary files differ
diff --git a/protocols/Telegram/tdlib/td/td/generate/scheme/secret_api.tl b/protocols/Telegram/tdlib/td/td/generate/scheme/secret_api.tl
index 32db7251f1..25231eb26d 100644
--- a/protocols/Telegram/tdlib/td/td/generate/scheme/secret_api.tl
+++ b/protocols/Telegram/tdlib/td/td/generate/scheme/secret_api.tl
@@ -15,12 +15,12 @@ vector {t:Type} # [ t ] = Vector t;
decryptedMessage8#1f814f1f random_id:long random_bytes:bytes message:string media:DecryptedMessageMedia = DecryptedMessage;
decryptedMessageService8#aa48327d random_id:long random_bytes:bytes action:DecryptedMessageAction = DecryptedMessage;
decryptedMessageMediaEmpty#89f5c4a = DecryptedMessageMedia;
-decryptedMessageMediaPhoto23#32798a8c thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes = DecryptedMessageMedia;
+decryptedMessageMediaPhoto8#32798a8c thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes = DecryptedMessageMedia;
decryptedMessageMediaVideo8#4cee6ef3 thumb:bytes thumb_w:int thumb_h:int duration:int w:int h:int size:int key:bytes iv:bytes = DecryptedMessageMedia;
decryptedMessageMediaGeoPoint#35480a59 lat:double long:double = DecryptedMessageMedia;
decryptedMessageMediaContact#588a0a97 phone_number:string first_name:string last_name:string user_id:int = DecryptedMessageMedia;
decryptedMessageActionSetMessageTTL#a1733aec ttl_seconds:int = DecryptedMessageAction;
-decryptedMessageMediaDocument23#b095434b thumb:bytes thumb_w:int thumb_h:int file_name:string mime_type:string size:int key:bytes iv:bytes = DecryptedMessageMedia;
+decryptedMessageMediaDocument8#b095434b thumb:bytes thumb_w:int thumb_h:int file_name:string mime_type:string size:int key:bytes iv:bytes = DecryptedMessageMedia;
decryptedMessageMediaAudio8#6080758f duration:int size:int key:bytes iv:bytes = DecryptedMessageMedia;
decryptedMessageActionReadMessages#c4f40be random_ids:Vector<long> = DecryptedMessageAction;
decryptedMessageActionDeleteMessages#65614304 random_ids:Vector<long> = DecryptedMessageAction;
@@ -59,7 +59,7 @@ decryptedMessageActionNoop#a82fdd63 = DecryptedMessageAction;
documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
documentAttributeAnimated#11b58939 = DocumentAttribute;
documentAttributeSticker23#fb0a5727 = DocumentAttribute;
-documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute;
+documentAttributeVideo23#5910cccb duration:int w:int h:int = DocumentAttribute;
documentAttributeAudio23#51448e5 duration:int = DocumentAttribute;
documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
photoSizeEmpty#e17e23c type:string = PhotoSize;
@@ -78,7 +78,7 @@ documentAttributeAudio45#ded218e0 duration:int title:string performer:string = D
decryptedMessage46#36b091de flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector<MessageEntity> via_bot_name:flags.11?string reply_to_random_id:flags.3?long = DecryptedMessage;
decryptedMessageMediaPhoto#f1fa8d78 thumb:bytes thumb_w:int thumb_h:int w:int h:int size:int key:bytes iv:bytes caption:string = DecryptedMessageMedia;
decryptedMessageMediaVideo#970c8c0e thumb:bytes thumb_w:int thumb_h:int duration:int mime_type:string w:int h:int size:int key:bytes iv:bytes caption:string = DecryptedMessageMedia;
-decryptedMessageMediaDocument#7afe8ae2 thumb:bytes thumb_w:int thumb_h:int mime_type:string size:int key:bytes iv:bytes attributes:Vector<DocumentAttribute> caption:string = DecryptedMessageMedia;
+decryptedMessageMediaDocument46#7afe8ae2 thumb:bytes thumb_w:int thumb_h:int mime_type:string size:int key:bytes iv:bytes attributes:Vector<DocumentAttribute> caption:string = DecryptedMessageMedia;
documentAttributeSticker#3a556302 alt:string stickerset:InputStickerSet = DocumentAttribute;
documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity;
@@ -95,6 +95,7 @@ messageEntityTextUrl#76a6d327 offset:int length:int url:string = MessageEntity;
messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEntity;
messageEntityPhone#9b69e34b offset:int length:int = MessageEntity;
messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity;
+messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity;
inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
inputStickerSetEmpty#ffb62b95 = InputStickerSet;
decryptedMessageMediaVenue#8a0df56f lat:double long:double title:string address:string provider:string venue_id:string = DecryptedMessageMedia;
@@ -104,11 +105,26 @@ decryptedMessageMediaWebPage#e50511d8 url:string = DecryptedMessageMedia;
sendMessageRecordRoundAction#88f27fbc = SendMessageAction;
sendMessageUploadRoundAction#bb718624 = SendMessageAction;
-documentAttributeVideo66#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute;
+documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute;
// layer 73
-decryptedMessage#91cc4674 flags:# random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector<MessageEntity> via_bot_name:flags.11?string reply_to_random_id:flags.3?long grouped_id:flags.17?long = DecryptedMessage;
+decryptedMessage#91cc4674 flags:# silent:flags.5?true random_id:long ttl:int message:string media:flags.9?DecryptedMessageMedia entities:flags.7?Vector<MessageEntity> via_bot_name:flags.11?string reply_to_random_id:flags.3?long grouped_id:flags.17?long = DecryptedMessage;
+
+// layer 101
+
+messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity;
+messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity;
+messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity;
+
+// layer 143
+
+decryptedMessageMediaDocument#6abd9782 thumb:bytes thumb_w:int thumb_h:int mime_type:string size:long key:bytes iv:bytes attributes:Vector<DocumentAttribute> caption:string = DecryptedMessageMedia;
+
+// layer 144
+
+messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity;
+messageEntityCustomEmoji#c8cf05f8 offset:int length:int document_id:long = MessageEntity;
---functions---
diff --git a/protocols/Telegram/tdlib/td/td/generate/scheme/secret_api.tlo b/protocols/Telegram/tdlib/td/td/generate/scheme/secret_api.tlo
deleted file mode 100644
index d6be21615f..0000000000
--- a/protocols/Telegram/tdlib/td/td/generate/scheme/secret_api.tlo
+++ /dev/null
Binary files differ
diff --git a/protocols/Telegram/tdlib/td/td/generate/scheme/td_api.tl b/protocols/Telegram/tdlib/td/td/generate/scheme/td_api.tl
index 2e7008e47f..8cc02dd61a 100644
--- a/protocols/Telegram/tdlib/td/td/generate/scheme/td_api.tl
+++ b/protocols/Telegram/tdlib/td/td/generate/scheme/td_api.tl
@@ -22,28 +22,9 @@ error code:int32 message:string = Error;
ok = Ok;
-//@description Contains parameters for TDLib initialization
-//@use_test_dc If set to true, the Telegram test environment will be used instead of the production environment
-//@database_directory The path to the directory for the persistent database; if empty, the current working directory will be used
-//@files_directory The path to the directory for storing files; if empty, database_directory will be used
-//@use_file_database If set to true, information about downloaded and uploaded files will be saved between application restarts
-//@use_chat_info_database If set to true, the library will maintain a cache of users, basic groups, supergroups, channels and secret chats. Implies use_file_database
-//@use_message_database If set to true, the library will maintain a cache of chats and messages. Implies use_chat_info_database
-//@use_secret_chats If set to true, support for secret chats will be enabled
-//@api_id Application identifier for Telegram API access, which can be obtained at https://my.telegram.org
-//@api_hash Application identifier hash for Telegram API access, which can be obtained at https://my.telegram.org
-//@system_language_code IETF language tag of the user's operating system language; must be non-empty
-//@device_model Model of the device the application is being run on; must be non-empty
-//@system_version Version of the operating system the application is being run on; must be non-empty
-//@application_version Application version; must be non-empty
-//@enable_storage_optimizer If set to true, old files will automatically be deleted
-//@ignore_file_names If set to true, original file names will be ignored. Otherwise, downloaded files will be saved under names as close as possible to the original name
-tdlibParameters use_test_dc:Bool database_directory:string files_directory:string use_file_database:Bool use_chat_info_database:Bool use_message_database:Bool use_secret_chats:Bool api_id:int32 api_hash:string system_language_code:string device_model:string system_version:string application_version:string enable_storage_optimizer:Bool ignore_file_names:Bool = TdlibParameters;
-
-
//@class AuthenticationCodeType @description Provides information about the method by which an authentication code is delivered to the user
-//@description An authentication code is delivered via a private Telegram message, which can be viewed in another client @length Length of the code
+//@description An authentication code is delivered via a private Telegram message, which can be viewed from another active session @length Length of the code
authenticationCodeTypeTelegramMessage length:int32 = AuthenticationCodeType;
//@description An authentication code is delivered via an SMS message to the specified phone number @length Length of the code
@@ -52,29 +33,75 @@ authenticationCodeTypeSms length:int32 = AuthenticationCodeType;
//@description An authentication code is delivered via a phone call to the specified phone number @length Length of the code
authenticationCodeTypeCall length:int32 = AuthenticationCodeType;
-//@description An authentication code is delivered by an immediately cancelled call to the specified phone number. The number from which the call was made is the code @pattern Pattern of the phone number from which the call will be made
+//@description An authentication code is delivered by an immediately canceled call to the specified phone number. The phone number that calls is the code that must be entered automatically @pattern Pattern of the phone number from which the call will be made
authenticationCodeTypeFlashCall pattern:string = AuthenticationCodeType;
+//@description An authentication code is delivered by an immediately canceled call to the specified phone number. The last digits of the phone number that calls are the code that must be entered manually by the user @phone_number_prefix Prefix of the phone number from which the call will be made @length Number of digits in the code, excluding the prefix
+authenticationCodeTypeMissedCall phone_number_prefix:string length:int32 = AuthenticationCodeType;
+
-//@description Information about the authentication code that was sent @phone_number A phone number that is being authenticated @type Describes the way the code was sent to the user @next_type Describes the way the next code will be sent to the user; may be null @timeout Timeout before the code should be re-sent, in seconds
+//@description Information about the authentication code that was sent @phone_number A phone number that is being authenticated @type The way the code was sent to the user @next_type The way the next code will be sent to the user; may be null @timeout Timeout before the code can be re-sent, in seconds
authenticationCodeInfo phone_number:string type:AuthenticationCodeType next_type:AuthenticationCodeType timeout:int32 = AuthenticationCodeInfo;
+//@description Information about the email address authentication code that was sent @email_address_pattern Pattern of the email address to which an authentication code was sent @length Length of the code; 0 if unknown
+emailAddressAuthenticationCodeInfo email_address_pattern:string length:int32 = EmailAddressAuthenticationCodeInfo;
-//@class AuthorizationState @description Represents the current authorization state of the client
-//@description TDLib needs TdlibParameters for initialization
-authorizationStateWaitTdlibParameters = AuthorizationState;
+//@class EmailAddressAuthentication @description Contains authentication data for a email address
+
+//@description An authentication code delivered to a user's email address @code The code
+emailAddressAuthenticationCode code:string = EmailAddressAuthentication;
+
+//@description An authentication token received through Apple ID @token The token
+emailAddressAuthenticationAppleId token:string = EmailAddressAuthentication;
+
+//@description An authentication token received through Google ID @token The token
+emailAddressAuthenticationGoogleId token:string = EmailAddressAuthentication;
+
-//@description TDLib needs an encryption key to decrypt the local database @is_encrypted True, if the database is currently encrypted
-authorizationStateWaitEncryptionKey is_encrypted:Bool = AuthorizationState;
+//@description Represents a part of the text that needs to be formatted in some unusual way @offset Offset of the entity, in UTF-16 code units @length Length of the entity, in UTF-16 code units @type Type of the entity
+textEntity offset:int32 length:int32 type:TextEntityType = TextEntity;
-//@description TDLib needs the user's phone number to authorize
+//@description Contains a list of text entities @entities List of text entities
+textEntities entities:vector<textEntity> = TextEntities;
+
+//@description A text with some entities @text The text @entities Entities contained in the text. Entities can be nested, but must not mutually intersect with each other.
+//-Pre, Code and PreCode entities can't contain other entities. Bold, Italic, Underline, Strikethrough, and Spoiler entities can contain and can be part of any other entities. All other entities can't contain each other
+formattedText text:string entities:vector<textEntity> = FormattedText;
+
+
+//@description Contains Telegram terms of service @text Text of the terms of service @min_user_age The minimum age of a user to be able to accept the terms; 0 if age isn't restricted @show_popup True, if a blocking popup with terms of service must be shown to the user
+termsOfService text:formattedText min_user_age:int32 show_popup:Bool = TermsOfService;
+
+
+//@class AuthorizationState @description Represents the current authorization state of the TDLib client
+
+//@description Initializetion parameters are needed. Call `setTdlibParameters` to provide them
+authorizationStateWaitTdlibParameters = AuthorizationState;
+
+//@description TDLib needs the user's phone number to authorize. Call `setAuthenticationPhoneNumber` to provide the phone number, or use `requestQrCodeAuthentication`, or `checkAuthenticationBotToken` for other authentication options
authorizationStateWaitPhoneNumber = AuthorizationState;
-//@description TDLib needs the user's authentication code to finalize authorization @is_registered True, if the user is already registered @code_info Information about the authorization code that was sent
-authorizationStateWaitCode is_registered:Bool code_info:authenticationCodeInfo = AuthorizationState;
+//@description TDLib needs the user's email address to authorize. Call `setAuthenticationEmailAddress` to provide the email address, or directly call `checkAuthenticationEmailCode` with Apple ID/Google ID token if allowed
+//@allow_apple_id True, if authorization through Apple ID is allowed @allow_google_id True, if authorization through Google ID is allowed
+authorizationStateWaitEmailAddress allow_apple_id:Bool allow_google_id:Bool = AuthorizationState;
+
+//@description TDLib needs the user's authentication code sent to an email address to authorize. Call `checkAuthenticationEmailCode` to provide the code
+//@allow_apple_id True, if authorization through Apple ID is allowed @allow_google_id True, if authorization through Google ID is allowed
+//@code_info Information about the sent authentication code
+//@next_phone_number_authorization_date Point in time (Unix timestamp) when the user will be able to authorize with a code sent to the user's phone number; 0 if unknown
+authorizationStateWaitEmailCode allow_apple_id:Bool allow_google_id:Bool code_info:emailAddressAuthenticationCodeInfo next_phone_number_authorization_date:int32 = AuthorizationState;
+
+//@description TDLib needs the user's authentication code to authorize @code_info Information about the authorization code that was sent
+authorizationStateWaitCode code_info:authenticationCodeInfo = AuthorizationState;
+
+//@description The user needs to confirm authorization on another logged in device by scanning a QR code with the provided link @link A tg:// URL for the QR code. The link will be updated frequently
+authorizationStateWaitOtherDeviceConfirmation link:string = AuthorizationState;
-//@description The user has been authorized, but needs to enter a password to start using the application @password_hint Hint for the password; can be empty @has_recovery_email_address True if a recovery email address has been set up
+//@description The user is unregistered and need to accept terms of service and enter their first name and last name to finish registration @terms_of_service Telegram terms of service
+authorizationStateWaitRegistration terms_of_service:termsOfService = AuthorizationState;
+
+//@description The user has been authorized, but needs to enter a 2-step verification password to start using the application @password_hint Hint for the password; may be empty @has_recovery_email_address True, if a recovery email address has been set up
//@recovery_email_address_pattern Pattern of the email address to which the recovery email was sent; empty until a recovery email has been sent
authorizationStateWaitPassword password_hint:string has_recovery_email_address:Bool recovery_email_address_pattern:string = AuthorizationState;
@@ -88,15 +115,16 @@ authorizationStateLoggingOut = AuthorizationState;
authorizationStateClosing = AuthorizationState;
//@description TDLib client is in its final state. All databases are closed and all resources are released. No other updates will be received after this. All queries will be responded to
-//-with error code 500. To continue working, one should create a new instance of the TDLib client
+//-with error code 500. To continue working, one must create a new instance of the TDLib client
authorizationStateClosed = AuthorizationState;
-//@description Represents the current state of 2-step verification @has_password True if a 2-step verification password has been set up @password_hint Hint for the password; can be empty @has_recovery_email_address True if a recovery email has been set up @unconfirmed_recovery_email_address_pattern Pattern of the email address to which a confirmation email was sent
-passwordState has_password:Bool password_hint:string has_recovery_email_address:Bool unconfirmed_recovery_email_address_pattern:string = PasswordState;
-
-//@description Contains information available to the user after requesting password recovery @recovery_email_address_pattern Pattern of the email address to which a recovery email was sent
-passwordRecoveryInfo recovery_email_address_pattern:string = PasswordRecoveryInfo;
+//@description Represents the current state of 2-step verification @has_password True, if a 2-step verification password is set @password_hint Hint for the password; may be empty
+//@has_recovery_email_address True, if a recovery email is set @has_passport_data True, if some Telegram Passport elements were saved
+//@recovery_email_address_code_info Information about the recovery email address to which the confirmation email was sent; may be null
+//@login_email_address_pattern Pattern of the email address set up for logging in
+//@pending_reset_date If not 0, point in time (Unix timestamp) after which the 2-step verification password can be reset immediately using resetPassword
+passwordState has_password:Bool password_hint:string has_recovery_email_address:Bool has_passport_data:Bool recovery_email_address_code_info:emailAddressAuthenticationCodeInfo login_email_address_pattern:string pending_reset_date:int32 = PasswordState;
//@description Contains information about the current recovery email address @recovery_email_address Recovery email address
recoveryEmailAddress recovery_email_address:string = RecoveryEmailAddress;
@@ -108,29 +136,32 @@ temporaryPasswordState has_password:Bool valid_for:int32 = TemporaryPasswordStat
//@description Represents a local file
//@path Local path to the locally available file part; may be empty
-//@can_be_downloaded True, if it is possible to try to download or generate the file
+//@can_be_downloaded True, if it is possible to download or generate the file
//@can_be_deleted True, if the file can be deleted
//@is_downloading_active True, if the file is currently being downloaded (or a local copy is being generated by some other means)
//@is_downloading_completed True, if the local copy is fully available
-//@downloaded_prefix_size If is_downloading_completed is false, then only some prefix of the file is ready to be read. downloaded_prefix_size is the size of that prefix
-//@downloaded_size Total downloaded file bytes. Should be used only for calculating download progress. The actual file size may be bigger, and some parts of it may contain garbage
-localFile path:string can_be_downloaded:Bool can_be_deleted:Bool is_downloading_active:Bool is_downloading_completed:Bool downloaded_prefix_size:int32 downloaded_size:int32 = LocalFile;
+//@download_offset Download will be started from this offset. downloaded_prefix_size is calculated from this offset
+//@downloaded_prefix_size If is_downloading_completed is false, then only some prefix of the file starting from download_offset is ready to be read. downloaded_prefix_size is the size of that prefix in bytes
+//@downloaded_size Total downloaded file size, in bytes. Can be used only for calculating download progress. The actual file size may be bigger, and some parts of it may contain garbage
+localFile path:string can_be_downloaded:Bool can_be_deleted:Bool is_downloading_active:Bool is_downloading_completed:Bool download_offset:int53 downloaded_prefix_size:int53 downloaded_size:int53 = LocalFile;
//@description Represents a remote file
-//@id Remote file identifier, may be empty. Can be used across application restarts or even from other devices for the current user. If the ID starts with "http://" or "https://", it represents the HTTP URL of the file. TDLib is currently unable to download files if only their URL is known.
-//-If downloadFile is called on such a file or if it is sent to a secret chat, TDLib starts a file generation process by sending updateFileGenerationStart to the client with the HTTP URL in the original_path and "#url#" as the conversion string. Clients should generate the file by downloading it to the specified location
+//@id Remote file identifier; may be empty. Can be used by the current user across application restarts or even from other devices. Uniquely identifies a file, but a file can have a lot of different valid identifiers.
+//-If the ID starts with "http://" or "https://", it represents the HTTP URL of the file. TDLib is currently unable to download files if only their URL is known.
+//-If downloadFile/addFileToDownloads is called on such a file or if it is sent to a secret chat, TDLib starts a file generation process by sending updateFileGenerationStart to the application with the HTTP URL in the original_path and "#url#" as the conversion string. Application must generate the file by downloading it to the specified location
+//@unique_id Unique file identifier; may be empty if unknown. The unique file identifier which is the same for the same file even for different users and is persistent over time
//@is_uploading_active True, if the file is currently being uploaded (or a remote copy is being generated by some other means)
//@is_uploading_completed True, if a remote copy is fully available
-//@uploaded_size Size of the remote available part of the file; 0 if unknown
-remoteFile id:string is_uploading_active:Bool is_uploading_completed:Bool uploaded_size:int32 = RemoteFile;
+//@uploaded_size Size of the remote available part of the file, in bytes; 0 if unknown
+remoteFile id:string unique_id:string is_uploading_active:Bool is_uploading_completed:Bool uploaded_size:int53 = RemoteFile;
//@description Represents a file
//@id Unique file identifier
-//@size File size; 0 if unknown
-//@expected_size Expected file size in case the exact file size is unknown, but an approximate size is known. Can be used to show download/upload progress
+//@size File size, in bytes; 0 if unknown
+//@expected_size Approximate file size in bytes in case the exact file size is unknown. Can be used to show download/upload progress
//@local Information about the local copy of the file
//@remote Information about the remote copy of the file
-file id:int32 size:int32 expected_size:int32 local:localFile remote:remoteFile = File;
+file id:int32 size:int53 expected_size:int53 local:localFile remote:remoteFile = File;
//@class InputFile @description Points to a file
@@ -138,210 +169,468 @@ file id:int32 size:int32 expected_size:int32 local:localFile remote:remoteFile =
//@description A file defined by its unique ID @id Unique file identifier
inputFileId id:int32 = InputFile;
-//@description A file defined by its remote ID @id Remote file identifier
+//@description A file defined by its remote ID. The remote ID is guaranteed to be usable only if the corresponding file is still accessible to the user and known to TDLib.
+//-For example, if the file is from a message, then the message must be not deleted and accessible to the user. If the file database is disabled, then the corresponding object with the file must be preloaded by the application
+//@id Remote file identifier
inputFileRemote id:string = InputFile;
//@description A file defined by a local path @path Local path to the file
inputFileLocal path:string = InputFile;
-//@description A file generated by the client @original_path Local path to a file from which the file is generated, may be empty if there is no such file @conversion String specifying the conversion applied to the original file; should be persistent across application restarts @expected_size Expected size of the generated file; 0 if unknown
-inputFileGenerated original_path:string conversion:string expected_size:int32 = InputFile;
+//@description A file generated by the application @original_path Local path to a file from which the file is generated; may be empty if there is no such file
+//@conversion String specifying the conversion applied to the original file; must be persistent across application restarts. Conversions beginning with '#' are reserved for internal TDLib usage
+//@expected_size Expected size of the generated file, in bytes; 0 if unknown
+inputFileGenerated original_path:string conversion:string expected_size:int53 = InputFile;
+
+
+//@description Describes an image in JPEG format @type Image type (see https://core.telegram.org/constructor/photoSize)
+//@photo Information about the image file @width Image width @height Image height
+//@progressive_sizes Sizes of progressive JPEG file prefixes, which can be used to preliminarily show the image; in bytes
+photoSize type:string photo:file width:int32 height:int32 progressive_sizes:vector<int32> = PhotoSize;
+
+//@description Thumbnail image of a very poor quality and low resolution @width Thumbnail width, usually doesn't exceed 40 @height Thumbnail height, usually doesn't exceed 40 @data The thumbnail in JPEG format
+minithumbnail width:int32 height:int32 data:bytes = Minithumbnail;
+
+
+//@class ThumbnailFormat @description Describes format of a thumbnail
+
+//@description The thumbnail is in JPEG format
+thumbnailFormatJpeg = ThumbnailFormat;
+//@description The thumbnail is in static GIF format. It will be used only for some bot inline results
+thumbnailFormatGif = ThumbnailFormat;
-//@description Photo description @type Thumbnail type (see https://core.telegram.org/constructor/photoSize) @photo Information about the photo file @width Photo width @height Photo height
-photoSize type:string photo:file width:int32 height:int32 = PhotoSize;
+//@description The thumbnail is in MPEG4 format. It will be used only for some animations and videos
+thumbnailFormatMpeg4 = ThumbnailFormat;
+//@description The thumbnail is in PNG format. It will be used only for background patterns
+thumbnailFormatPng = ThumbnailFormat;
-//@class MaskPoint @description Part of the face, relative to which a mask should be placed
+//@description The thumbnail is in TGS format. It will be used only for TGS sticker sets
+thumbnailFormatTgs = ThumbnailFormat;
-//@description A mask should be placed relatively to the forehead
+//@description The thumbnail is in WEBM format. It will be used only for WEBM sticker sets
+thumbnailFormatWebm = ThumbnailFormat;
+
+//@description The thumbnail is in WEBP format. It will be used only for some stickers
+thumbnailFormatWebp = ThumbnailFormat;
+
+
+//@description Represents a thumbnail @format Thumbnail format @width Thumbnail width @height Thumbnail height @file The thumbnail
+thumbnail format:ThumbnailFormat width:int32 height:int32 file:file = Thumbnail;
+
+
+//@class MaskPoint @description Part of the face, relative to which a mask is placed
+
+//@description The mask is placed relatively to the forehead
maskPointForehead = MaskPoint;
-//@description A mask should be placed relatively to the eyes
+//@description The mask is placed relatively to the eyes
maskPointEyes = MaskPoint;
-//@description A mask should be placed relatively to the mouth
+//@description The mask is placed relatively to the mouth
maskPointMouth = MaskPoint;
-//@description A mask should be placed relatively to the chin
+//@description The mask is placed relatively to the chin
maskPointChin = MaskPoint;
-//@description Position on a photo where a mask should be placed @point Part of the face, relative to which the mask should be placed
+//@description Position on a photo where a mask is placed @point Part of the face, relative to which the mask is placed
//@x_shift Shift by X-axis measured in widths of the mask scaled to the face size, from left to right. (For example, -1.0 will place the mask just to the left of the default mask position)
//@y_shift Shift by Y-axis measured in heights of the mask scaled to the face size, from top to bottom. (For example, 1.0 will place the mask just below the default mask position)
//@scale Mask scaling coefficient. (For example, 2.0 means a doubled size)
maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPosition;
-//@description Represents a part of the text that needs to be formatted in some unusual way @offset Offset of the entity in UTF-16 code points @length Length of the entity, in UTF-16 code points @type Type of the entity
-textEntity offset:int32 length:int32 type:TextEntityType = TextEntity;
+//@class StickerFormat @description Describes format of a sticker
-//@description Contains a list of text entities @entities List of text entities
-textEntities entities:vector<textEntity> = TextEntities;
+//@description The sticker is an image in WEBP format
+stickerFormatWebp = StickerFormat;
+
+//@description The sticker is an animation in TGS format
+stickerFormatTgs = StickerFormat;
+
+//@description The sticker is a video in WEBM format
+stickerFormatWebm = StickerFormat;
+
+
+//@class StickerType @description Describes type of a sticker
+
+//@description The sticker is a regular sticker
+stickerTypeRegular = StickerType;
+
+//@description The sticker is a mask in WEBP format to be placed on photos or videos
+stickerTypeMask = StickerType;
+
+//@description The sticker is a custom emoji to be used inside message text and caption
+stickerTypeCustomEmoji = StickerType;
+
+
+//@description Represents a closed vector path. The path begins at the end point of the last command @commands List of vector path commands
+closedVectorPath commands:vector<VectorPathCommand> = ClosedVectorPath;
-//@description A text with some entities @text The text @entities Entities contained in the text
-formattedText text:string entities:vector<textEntity> = FormattedText;
+
+//@description Describes one answer option of a poll @text Option text; 1-100 characters @voter_count Number of voters for this option, available only for closed or voted polls @vote_percentage The percentage of votes for this option; 0-100
+//@is_chosen True, if the option was chosen by the user @is_being_chosen True, if the option is being chosen by a pending setPollAnswer request
+pollOption text:string voter_count:int32 vote_percentage:int32 is_chosen:Bool is_being_chosen:Bool = PollOption;
+
+
+//@class PollType @description Describes the type of a poll
+
+//@description A regular poll @allow_multiple_answers True, if multiple answer options can be chosen simultaneously
+pollTypeRegular allow_multiple_answers:Bool = PollType;
+
+//@description A poll in quiz mode, which has exactly one correct answer option and can be answered only once
+//@correct_option_id 0-based identifier of the correct answer option; -1 for a yet unanswered poll
+//@explanation Text that is shown when the user chooses an incorrect answer or taps on the lamp icon; 0-200 characters with at most 2 line feeds; empty for a yet unanswered poll
+pollTypeQuiz correct_option_id:int32 explanation:formattedText = PollType;
//@description Describes an animation file. The animation must be encoded in GIF or MPEG4 format @duration Duration of the animation, in seconds; as defined by the sender @width Width of the animation @height Height of the animation
-//@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file, usually "image/gif" or "video/mp4" @thumbnail Animation thumbnail; may be null @animation File containing the animation
-animation duration:int32 width:int32 height:int32 file_name:string mime_type:string thumbnail:photoSize animation:file = Animation;
+//@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file, usually "image/gif" or "video/mp4"
+//@has_stickers True, if stickers were added to the animation. The list of corresponding sticker set can be received using getAttachedStickerSets
+//@minithumbnail Animation minithumbnail; may be null @thumbnail Animation thumbnail in JPEG or MPEG4 format; may be null @animation File containing the animation
+animation duration:int32 width:int32 height:int32 file_name:string mime_type:string has_stickers:Bool minithumbnail:minithumbnail thumbnail:thumbnail animation:file = Animation;
-//@description Describes an audio file. Audio is usually in MP3 format @duration Duration of the audio, in seconds; as defined by the sender @title Title of the audio; as defined by the sender @performer Performer of the audio; as defined by the sender
-//@file_name Original name of the file; as defined by the sender @mime_type The MIME type of the file; as defined by the sender @album_cover_thumbnail The thumbnail of the album cover; as defined by the sender. The full size thumbnail should be extracted from the downloaded file; may be null @audio File containing the audio
-audio duration:int32 title:string performer:string file_name:string mime_type:string album_cover_thumbnail:photoSize audio:file = Audio;
+//@description Describes an audio file. Audio is usually in MP3 or M4A format @duration Duration of the audio, in seconds; as defined by the sender @title Title of the audio; as defined by the sender @performer Performer of the audio; as defined by the sender
+//@file_name Original name of the file; as defined by the sender @mime_type The MIME type of the file; as defined by the sender @album_cover_minithumbnail The minithumbnail of the album cover; may be null
+//@album_cover_thumbnail The thumbnail of the album cover in JPEG format; as defined by the sender. The full size thumbnail is supposed to be extracted from the downloaded audio file; may be null
+//@external_album_covers Album cover variants to use if the downloaded audio file contains no album cover. Provided thumbnail dimensions are approximate @audio File containing the audio
+audio duration:int32 title:string performer:string file_name:string mime_type:string album_cover_minithumbnail:minithumbnail album_cover_thumbnail:thumbnail external_album_covers:vector<thumbnail> audio:file = Audio;
//@description Describes a document of any type @file_name Original name of the file; as defined by the sender @mime_type MIME type of the file; as defined by the sender
-//@thumbnail Document thumbnail; as defined by the sender; may be null @document File containing the document
-document file_name:string mime_type:string thumbnail:photoSize document:file = Document;
+//@minithumbnail Document minithumbnail; may be null @thumbnail Document thumbnail in JPEG or PNG format (PNG will be used only for background patterns); as defined by the sender; may be null @document File containing the document
+document file_name:string mime_type:string minithumbnail:minithumbnail thumbnail:thumbnail document:file = Document;
-//@description Describes a photo @id Photo identifier; 0 for deleted photos @has_stickers True, if stickers were added to the photo @sizes Available variants of the photo, in different sizes
-photo id:int64 has_stickers:Bool sizes:vector<photoSize> = Photo;
+//@description Describes a photo @has_stickers True, if stickers were added to the photo. The list of corresponding sticker sets can be received using getAttachedStickerSets
+//@minithumbnail Photo minithumbnail; may be null @sizes Available variants of the photo, in different sizes
+photo has_stickers:Bool minithumbnail:minithumbnail sizes:vector<photoSize> = Photo;
//@description Describes a sticker @set_id The identifier of the sticker set to which the sticker belongs; 0 if none @width Sticker width; as defined by the sender @height Sticker height; as defined by the sender
-//@emoji Emoji corresponding to the sticker @is_mask True, if the sticker is a mask @mask_position Position where the mask should be placed; may be null @thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @sticker File containing the sticker
-sticker set_id:int64 width:int32 height:int32 emoji:string is_mask:Bool mask_position:maskPosition thumbnail:photoSize sticker:file = Sticker;
+//@emoji Emoji corresponding to the sticker @format Sticker format @type Sticker type @mask_position Position where the mask is placed; may be null even the sticker is a mask
+//@custom_emoji_id Identifier of the emoji if the sticker is a custom emoji
+//@outline Sticker's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner
+//@thumbnail Sticker thumbnail in WEBP or JPEG format; may be null @is_premium True, if only Premium users can use the sticker @premium_animation Premium animation of the sticker; may be null @sticker File containing the sticker
+sticker set_id:int64 width:int32 height:int32 emoji:string format:StickerFormat type:StickerType mask_position:maskPosition custom_emoji_id:int64 outline:vector<closedVectorPath> thumbnail:thumbnail is_premium:Bool premium_animation:file sticker:file = Sticker;
//@description Describes a video file @duration Duration of the video, in seconds; as defined by the sender @width Video width; as defined by the sender @height Video height; as defined by the sender
-//@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file; as defined by the sender @has_stickers True, if stickers were added to the photo
-//@supports_streaming True, if the video should be tried to be streamed @thumbnail Video thumbnail; as defined by the sender; may be null @video File containing the video
-video duration:int32 width:int32 height:int32 file_name:string mime_type:string has_stickers:Bool supports_streaming:Bool thumbnail:photoSize video:file = Video;
-
-//@description Describes a video note. The video must be equal in width and height, cropped to a circle, and stored in MPEG4 format @duration Duration of the video, in seconds; as defined by the sender @length Video width and height; as defined by the sender @thumbnail Video thumbnail; as defined by the sender; may be null @video File containing the video
-videoNote duration:int32 length:int32 thumbnail:photoSize video:file = VideoNote;
-
-//@description Describes a voice note. The voice note must be encoded with the Opus codec, and stored inside an OGG container. Voice notes can have only a single audio channel @duration Duration of the voice note, in seconds; as defined by the sender
-//@waveform A waveform representation of the voice note in 5-bit format @mime_type MIME type of the file; as defined by the sender @voice File containing the voice note
-voiceNote duration:int32 waveform:bytes mime_type:string voice:file = VoiceNote;
-
-//@description Describes a user contact @phone_number Phone number of the user @first_name First name of the user; 1-255 characters in length @last_name Last name of the user @user_id Identifier of the user, if known; otherwise 0
-contact phone_number:string first_name:string last_name:string user_id:int32 = Contact;
+//@file_name Original name of the file; as defined by the sender @mime_type MIME type of the file; as defined by the sender
+//@has_stickers True, if stickers were added to the video. The list of corresponding sticker sets can be received using getAttachedStickerSets
+//@supports_streaming True, if the video is supposed to be streamed @minithumbnail Video minithumbnail; may be null
+//@thumbnail Video thumbnail in JPEG or MPEG4 format; as defined by the sender; may be null @video File containing the video
+video duration:int32 width:int32 height:int32 file_name:string mime_type:string has_stickers:Bool supports_streaming:Bool minithumbnail:minithumbnail thumbnail:thumbnail video:file = Video;
+
+//@description Describes a video note. The video must be equal in width and height, cropped to a circle, and stored in MPEG4 format @duration Duration of the video, in seconds; as defined by the sender
+//@waveform A waveform representation of the video note's audio in 5-bit format; may be empty if unknown @length Video width and height; as defined by the sender @minithumbnail Video minithumbnail; may be null
+//@thumbnail Video thumbnail in JPEG format; as defined by the sender; may be null @speech_recognition_result Result of speech recognition in the video note; may be null @video File containing the video
+videoNote duration:int32 waveform:bytes length:int32 minithumbnail:minithumbnail thumbnail:thumbnail speech_recognition_result:SpeechRecognitionResult video:file = VideoNote;
+
+//@description Describes a voice note. The voice note must be encoded with the Opus codec, and stored inside an OGG container. Voice notes can have only a single audio channel
+//@duration Duration of the voice note, in seconds; as defined by the sender @waveform A waveform representation of the voice note in 5-bit format
+//@mime_type MIME type of the file; as defined by the sender @speech_recognition_result Result of speech recognition in the voice note; may be null @voice File containing the voice note
+voiceNote duration:int32 waveform:bytes mime_type:string speech_recognition_result:SpeechRecognitionResult voice:file = VoiceNote;
+
+//@description Describes an animated or custom representation of an emoji
+//@sticker Sticker for the emoji; may be null if yet unknown for a custom emoji. If the sticker is a custom emoji, it can have arbitrary format different from stickerFormatTgs
+//@sticker_width Expected width of the sticker, which can be used if the sticker is null
+//@sticker_height Expected height of the sticker, which can be used if the sticker is null
+//@fitzpatrick_type Emoji modifier fitzpatrick type; 0-6; 0 if none
+//@sound File containing the sound to be played when the sticker is clicked; may be null. The sound is encoded with the Opus codec, and stored inside an OGG container
+animatedEmoji sticker:sticker sticker_width:int32 sticker_height:int32 fitzpatrick_type:int32 sound:file = AnimatedEmoji;
+
+//@description Describes a user contact @phone_number Phone number of the user @first_name First name of the user; 1-255 characters in length @last_name Last name of the user @vcard Additional data about the user in a form of vCard; 0-2048 bytes in length @user_id Identifier of the user, if known; otherwise 0
+contact phone_number:string first_name:string last_name:string vcard:string user_id:int53 = Contact;
//@description Describes a location on planet Earth @latitude Latitude of the location in degrees; as defined by the sender @longitude Longitude of the location, in degrees; as defined by the sender
-location latitude:double longitude:double = Location;
+//@horizontal_accuracy The estimated horizontal accuracy of the location, in meters; as defined by the sender. 0 if unknown
+location latitude:double longitude:double horizontal_accuracy:double = Location;
-//@description Describes a venue @location Venue location; as defined by the sender @title Venue name; as defined by the sender @address Venue address; as defined by the sender @provider Provider of the venue database; as defined by the sender. Currently only "foursquare" needs to be supported
-//@id Identifier of the venue in the provider database; as defined by the sender
-venue location:location title:string address:string provider:string id:string = Venue;
+//@description Describes a venue @location Venue location; as defined by the sender @title Venue name; as defined by the sender @address Venue address; as defined by the sender @provider Provider of the venue database; as defined by the sender. Currently, only "foursquare" and "gplaces" (Google Places) need to be supported
+//@id Identifier of the venue in the provider database; as defined by the sender @type Type of the venue in the provider database; as defined by the sender
+venue location:location title:string address:string provider:string id:string type:string = Venue;
//@description Describes a game @id Game ID @short_name Game short name. To share a game use the URL https://t.me/{bot_username}?game={game_short_name} @title Game title @text Game text, usually containing scoreboards for a game
//@param_description Game description @photo Game photo @animation Game animation; may be null
game id:int64 short_name:string title:string text:formattedText description:string photo:photo animation:animation = Game;
-
-//@description Describes a user profile photo @id Photo identifier; 0 for an empty photo. Can be used to find a photo in a list of userProfilePhotos
-//@small A small (160x160) user profile photo @big A big (640x640) user profile photo
-profilePhoto id:int64 small:file big:file = ProfilePhoto;
-
-//@description Describes the photo of a chat @small A small (160x160) chat photo @big A big (640x640) chat photo
-chatPhoto small:file big:file = ChatPhoto;
+//@description Describes a poll @id Unique poll identifier @question Poll question; 1-300 characters @options List of poll answer options
+//@total_voter_count Total number of voters, participating in the poll @recent_voter_user_ids User identifiers of recent voters, if the poll is non-anonymous
+//@is_anonymous True, if the poll is anonymous @type Type of the poll
+//@open_period Amount of time the poll will be active after creation, in seconds @close_date Point in time (Unix timestamp) when the poll will automatically be closed @is_closed True, if the poll is closed
+poll id:int64 question:string options:vector<pollOption> total_voter_count:int32 recent_voter_user_ids:vector<int53> is_anonymous:Bool type:PollType open_period:int32 close_date:int32 is_closed:Bool = Poll;
-//@class LinkState @description Represents the relationship between user A and user B. For incoming_link, user A is the current user; for outgoing_link, user B is the current user
+//@description Describes a user profile photo @id Photo identifier; 0 for an empty photo. Can be used to find a photo in a list of user profile photos
+//@small A small (160x160) user profile photo. The file can be downloaded only before the photo is changed
+//@big A big (640x640) user profile photo. The file can be downloaded only before the photo is changed
+//@minithumbnail User profile photo minithumbnail; may be null
+//@has_animation True, if the photo has animated variant
+profilePhoto id:int64 small:file big:file minithumbnail:minithumbnail has_animation:Bool = ProfilePhoto;
-//@description The phone number of user A is not known to user B
-linkStateNone = LinkState;
+//@description Contains basic information about the photo of a chat
+//@small A small (160x160) chat photo variant in JPEG format. The file can be downloaded only before the photo is changed
+//@big A big (640x640) chat photo variant in JPEG format. The file can be downloaded only before the photo is changed
+//@minithumbnail Chat photo minithumbnail; may be null
+//@has_animation True, if the photo has animated variant
+chatPhotoInfo small:file big:file minithumbnail:minithumbnail has_animation:Bool = ChatPhotoInfo;
-//@description The phone number of user A is known but that number has not been saved to the contacts list of user B
-linkStateKnowsPhoneNumber = LinkState;
-//@description The phone number of user A has been saved to the contacts list of user B
-linkStateIsContact = LinkState;
-
-
-//@class UserType @description Represents the type of the user. The following types are possible: regular users, deleted users and bots
+//@class UserType @description Represents the type of a user. The following types are possible: regular users, deleted users and bots
//@description A regular user
userTypeRegular = UserType;
-//@description A deleted user or deleted bot. No information on the user besides the user_id is available. It is not possible to perform any active actions on this type of user
+//@description A deleted user or deleted bot. No information on the user besides the user identifier is available. It is not possible to perform any active actions on this type of user
userTypeDeleted = UserType;
-//@description A bot (see https://core.telegram.org/bots) @can_join_groups True, if the bot can be invited to basic group and supergroup chats
+//@description A bot (see https://core.telegram.org/bots)
+//@can_join_groups True, if the bot can be invited to basic group and supergroup chats
//@can_read_all_group_messages True, if the bot can read all messages in basic group or supergroup chats and not just those addressed to the bot. In private and channel chats a bot can always read all messages
-//@is_inline True, if the bot supports inline queries @inline_query_placeholder Placeholder for inline queries (displayed on the client input field) @need_location True, if the location of the user should be sent with every inline query to this bot
-userTypeBot can_join_groups:Bool can_read_all_group_messages:Bool is_inline:Bool inline_query_placeholder:string need_location:Bool = UserType;
+//@is_inline True, if the bot supports inline queries
+//@inline_query_placeholder Placeholder for inline queries (displayed on the application input field)
+//@need_location True, if the location of the user is expected to be sent with every inline query to this bot
+//@can_be_added_to_attachment_menu True, if the bot can be added to attachment menu
+userTypeBot can_join_groups:Bool can_read_all_group_messages:Bool is_inline:Bool inline_query_placeholder:string need_location:Bool can_be_added_to_attachment_menu:Bool = UserType;
-//@description No information on the user besides the user_id is available, yet this user has not been deleted. This object is extremely rare and must be handled like a deleted user. It is not possible to perform any actions on users of this type
+//@description No information on the user besides the user identifier is available, yet this user has not been deleted. This object is extremely rare and must be handled like a deleted user. It is not possible to perform any actions on users of this type
userTypeUnknown = UserType;
-//@description Represents commands supported by a bot @command Text of the bot command @param_description Description of the bot command
+//@description Represents a command supported by a bot @command Text of the bot command @param_description Description of the bot command
botCommand command:string description:string = BotCommand;
-//@description Provides information about a bot and its supported commands @param_description Long description shown on the user info page @commands A list of commands supported by the bot
-botInfo description:string commands:vector<botCommand> = BotInfo;
+//@description Contains a list of bot commands @bot_user_id Bot's user identifier @commands List of bot commands
+botCommands bot_user_id:int53 commands:vector<botCommand> = BotCommands;
+//@description Describes a button to be shown instead of bot commands menu button @text Text of the button @url URL to be passed to openWebApp
+botMenuButton text:string url:string = BotMenuButton;
-//@description Represents a user @id User identifier @first_name First name of the user @last_name Last name of the user @username Username of the user
-//@phone_number Phone number of the user @status Current online status of the user @profile_photo Profile photo of the user; may be null
-//@outgoing_link Relationship from the current user to the other user @incoming_link Relationship from the other user to the current user @is_verified True, if the user is verified @restriction_reason If non-empty, it contains the reason why access to this user must be restricted. The format of the string is "{type}: {description}".
-//-{type} contains the type of the restriction and at least one of the suffixes "-all", "-ios", "-android", or "-wp", which describe the platforms on which access should be restricted. (For example, "terms-ios-android". {description} contains a human-readable description of the restriction, which can be shown to the user)
-//@have_access If false, the user is inaccessible, and the only information known about the user is inside this class. It can't be passed to any method except GetUser @type Type of the user @language_code IETF language tag of the user's language; only available to bots
-user id:int32 first_name:string last_name:string username:string phone_number:string status:UserStatus profile_photo:profilePhoto outgoing_link:LinkState incoming_link:LinkState is_verified:Bool restriction_reason:string have_access:Bool type:UserType language_code:string = User;
-//@description Contains full information about a user (except the full list of profile photos) @is_blocked True, if the user is blacklisted by the current user @can_be_called True, if the user can be called @has_private_calls True, if the user can't be called due to their privacy settings
-//@bio A short user bio @share_text For bots, the text that is included with the link when users share the bot @group_in_common_count Number of group chats where both the other user and the current user are a member; 0 for the current user @bot_info If the user is a bot, information about the bot; may be null
-userFullInfo is_blocked:Bool can_be_called:Bool has_private_calls:Bool bio:string share_text:string group_in_common_count:int32 bot_info:botInfo = UserFullInfo;
+//@description Represents a location to which a chat is connected @location The location @address Location address; 1-64 characters, as defined by the chat owner
+chatLocation location:location address:string = ChatLocation;
-//@description Contains part of the list of user photos @total_count Total number of user profile photos @photos A list of photos
-userProfilePhotos total_count:int32 photos:vector<photo> = UserProfilePhotos;
-//@description Represents a list of users @total_count Approximate total count of users found @user_ids A list of user identifiers
-users total_count:int32 user_ids:vector<int32> = Users;
+//@description Animated variant of a chat photo in MPEG4 format
+//@length Animation width and height
+//@file Information about the animation file
+//@main_frame_timestamp Timestamp of the frame, used as a static chat photo
+animatedChatPhoto length:int32 file:file main_frame_timestamp:double = AnimatedChatPhoto;
-//@class ChatMemberStatus @description Provides information about the status of a member in a chat
+//@description Describes a chat or user profile photo
+//@id Unique photo identifier
+//@added_date Point in time (Unix timestamp) when the photo has been added
+//@minithumbnail Photo minithumbnail; may be null
+//@sizes Available variants of the photo in JPEG format, in different size
+//@animation A big (640x640) animated variant of the photo in MPEG4 format; may be null
+//@small_animation A small (160x160) animated variant of the photo in MPEG4 format; may be null even the big animation is available
+chatPhoto id:int64 added_date:int32 minithumbnail:minithumbnail sizes:vector<photoSize> animation:animatedChatPhoto small_animation:animatedChatPhoto = ChatPhoto;
-//@description The user is the creator of a chat and has all the administrator privileges @is_member True, if the user is a member of the chat
-chatMemberStatusCreator is_member:Bool = ChatMemberStatus;
+//@description Contains a list of chat or user profile photos @total_count Total number of photos @photos List of photos
+chatPhotos total_count:int32 photos:vector<chatPhoto> = ChatPhotos;
-//@description The user is a member of a chat and has some additional privileges. In basic groups, administrators can edit and delete messages sent by others, add new members, and ban unprivileged members. In supergroups and channels, there are more detailed options for administrator privileges
-//@can_be_edited True, if the current user can edit the administrator privileges for the called user
+
+//@class InputChatPhoto @description Describes a photo to be set as a user profile or chat photo
+
+//@description A previously used profile photo of the current user @chat_photo_id Identifier of the current user's profile photo to reuse
+inputChatPhotoPrevious chat_photo_id:int64 = InputChatPhoto;
+
+//@description A static photo in JPEG format @photo Photo to be set as profile photo. Only inputFileLocal and inputFileGenerated are allowed
+inputChatPhotoStatic photo:InputFile = InputChatPhoto;
+
+//@description An animation in MPEG4 format; must be square, at most 10 seconds long, have width between 160 and 800 and be at most 2MB in size
+//@animation Animation to be set as profile photo. Only inputFileLocal and inputFileGenerated are allowed
+//@main_frame_timestamp Timestamp of the frame, which will be used as static chat photo
+inputChatPhotoAnimation animation:InputFile main_frame_timestamp:double = InputChatPhoto;
+
+
+//@description Describes actions that a user is allowed to take in a chat
+//@can_send_messages True, if the user can send text messages, contacts, locations, and venues
+//@can_send_media_messages True, if the user can send audio files, documents, photos, videos, video notes, and voice notes. Implies can_send_messages permissions
+//@can_send_polls True, if the user can send polls. Implies can_send_messages permissions
+//@can_send_other_messages True, if the user can send animations, games, stickers, and dice and use inline bots. Implies can_send_messages permissions
+//@can_add_web_page_previews True, if the user may add a web page preview to their messages. Implies can_send_messages permissions
+//@can_change_info True, if the user can change the chat title, photo, and other settings
+//@can_invite_users True, if the user can invite new users to the chat
+//@can_pin_messages True, if the user can pin messages
+//@can_manage_topics True, if the user can manage topics
+chatPermissions can_send_messages:Bool can_send_media_messages:Bool can_send_polls:Bool can_send_other_messages:Bool can_add_web_page_previews:Bool can_change_info:Bool can_invite_users:Bool can_pin_messages:Bool can_manage_topics:Bool = ChatPermissions;
+
+//@description Describes rights of the administrator
+//@can_manage_chat True, if the administrator can get chat event log, get chat statistics, get message statistics in channels, get channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other privilege; applicable to supergroups and channels only
//@can_change_info True, if the administrator can change the chat title, photo, and other settings
//@can_post_messages True, if the administrator can create channel posts; applicable to channels only
//@can_edit_messages True, if the administrator can edit messages of other users and pin messages; applicable to channels only
//@can_delete_messages True, if the administrator can delete messages of other users
//@can_invite_users True, if the administrator can invite new users to the chat
-//@can_restrict_members True, if the administrator can restrict, ban, or unban chat members
-//@can_pin_messages True, if the administrator can pin messages; applicable to supergroups only
-//@can_promote_members True, if the administrator can add new administrators with a subset of his own privileges or demote administrators that were directly or indirectly promoted by him
-chatMemberStatusAdministrator can_be_edited:Bool can_change_info:Bool can_post_messages:Bool can_edit_messages:Bool can_delete_messages:Bool can_invite_users:Bool can_restrict_members:Bool can_pin_messages:Bool can_promote_members:Bool = ChatMemberStatus;
+//@can_restrict_members True, if the administrator can restrict, ban, or unban chat members; always true for channels
+//@can_pin_messages True, if the administrator can pin messages; applicable to basic groups and supergroups only
+//@can_manage_topics True, if the administrator can manage topics; applicable to forum supergroups only
+//@can_promote_members True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that were directly or indirectly promoted by them
+//@can_manage_video_chats True, if the administrator can manage video chats
+//@is_anonymous True, if the administrator isn't shown in the chat member list and sends messages anonymously; applicable to supergroups only
+chatAdministratorRights can_manage_chat:Bool can_change_info:Bool can_post_messages:Bool can_edit_messages:Bool can_delete_messages:Bool can_invite_users:Bool can_restrict_members:Bool can_pin_messages:Bool can_manage_topics:Bool can_promote_members:Bool can_manage_video_chats:Bool is_anonymous:Bool = ChatAdministratorRights;
+
+
+//@description Describes an option for buying Telegram Premium to a user
+//@currency ISO 4217 currency code for Telegram Premium subscription payment
+//@amount The amount to pay, in the smallest units of the currency
+//@discount_percentage The discount associated with this option, as a percentage
+//@month_count Number of month the Telegram Premium subscription will be active
+//@store_product_id Identifier of the store product associated with the option
+//@payment_link An internal link to be opened for buying Telegram Premium to the user if store payment isn't possible; may be null if direct payment isn't available
+premiumPaymentOption currency:string amount:int53 discount_percentage:int32 month_count:int32 store_product_id:string payment_link:InternalLinkType = PremiumPaymentOption;
+
+
+//@description Describes a custom emoji to be shown instead of the Telegram Premium badge @custom_emoji_id Identifier of the custom emoji in stickerFormatTgs format. If the custom emoji belongs to the sticker set GetOption("themed_emoji_statuses_sticker_set_id"), then it's color must be changed to the color of the Telegram Premium badge
+emojiStatus custom_emoji_id:int64 = EmojiStatus;
+
+//@description Contains a list of emoji statuses @emoji_statuses The list of emoji statuses
+emojiStatuses emoji_statuses:vector<emojiStatus> = EmojiStatuses;
+
+
+//@description Describes usernames assigned to a user, a supergroup, or a channel
+//@active_usernames List of active usernames; the first one must be shown as the primary username. The order of active usernames can be changed with reorderActiveUsernames or reorderSupergroupActiveUsernames
+//@disabled_usernames List of currently disabled usernames; the username can be activated with toggleUsernameIsActive/toggleSupergroupUsernameIsActive
+//@editable_username The active username, which can be changed with setUsername/setSupergroupUsername
+usernames active_usernames:vector<string> disabled_usernames:vector<string> editable_username:string = Usernames;
+
+
+//@description Represents a user
+//@id User identifier
+//@first_name First name of the user
+//@last_name Last name of the user
+//@usernames Usernames of the user; may be null
+//@phone_number Phone number of the user
+//@status Current online status of the user
+//@profile_photo Profile photo of the user; may be null
+//@emoji_status Emoji status to be shown instead of the default Telegram Premium badge; may be null. For Telegram Premium users only
+//@is_contact The user is a contact of the current user
+//@is_mutual_contact The user is a contact of the current user and the current user is a contact of the user
+//@is_verified True, if the user is verified
+//@is_premium True, if the user is a Telegram Premium user
+//@is_support True, if the user is Telegram support account
+//@restriction_reason If non-empty, it contains a human-readable description of the reason why access to this user must be restricted
+//@is_scam True, if many users reported this user as a scam
+//@is_fake True, if many users reported this user as a fake account
+//@have_access If false, the user is inaccessible, and the only information known about the user is inside this class. Identifier of the user can't be passed to any method except GetUser
+//@type Type of the user
+//@language_code IETF language tag of the user's language; only available to bots
+//@added_to_attachment_menu True, if the user added the current bot to attachment menu; only available to bots
+user id:int53 first_name:string last_name:string usernames:usernames phone_number:string status:UserStatus profile_photo:profilePhoto emoji_status:emojiStatus is_contact:Bool is_mutual_contact:Bool is_verified:Bool is_premium:Bool is_support:Bool restriction_reason:string is_scam:Bool is_fake:Bool have_access:Bool type:UserType language_code:string added_to_attachment_menu:Bool = User;
+
+
+//@description Contains information about a bot
+//@share_text The text that is shown on the bot's profile page and is sent together with the link when users share the bot
+//@param_description The text shown in the chat with the bot if the chat is empty
+//@photo Photo shown in the chat with the bot if the chat is empty; may be null
+//@animation Animation shown in the chat with the bot if the chat is empty; may be null
+//@menu_button Information about a button to show instead of the bot commands menu button; may be null if ordinary bot commands menu must be shown
+//@commands List of the bot commands
+//@default_group_administrator_rights Default administrator rights for adding the bot to basic group and supergroup chats; may be null
+//@default_channel_administrator_rights Default administrator rights for adding the bot to channels; may be null
+botInfo share_text:string description:string photo:photo animation:animation menu_button:botMenuButton commands:vector<botCommand> default_group_administrator_rights:chatAdministratorRights default_channel_administrator_rights:chatAdministratorRights = BotInfo;
+
+//@description Contains full information about a user
+//@photo User profile photo; may be null if empty or unknown. If non-null, then it is the same photo as in user.profile_photo and chat.photo
+//@is_blocked True, if the user is blocked by the current user
+//@can_be_called True, if the user can be called
+//@supports_video_calls True, if a video call can be created with the user
+//@has_private_calls True, if the user can't be called due to their privacy settings
+//@has_private_forwards True, if the user can't be linked in forwarded messages due to their privacy settings
+//@has_restricted_voice_and_video_note_messages True, if voice and video notes can't be sent or forwarded to the user
+//@need_phone_number_privacy_exception True, if the current user needs to explicitly allow to share their phone number with the user when the method addContact is used
+//@bio A short user bio; may be null for bots
+//@premium_gift_options The list of available options for gifting Telegram Premium to the user
+//@group_in_common_count Number of group chats where both the other user and the current user are a member; 0 for the current user
+//@bot_info For bots, information about the bot; may be null
+userFullInfo photo:chatPhoto is_blocked:Bool can_be_called:Bool supports_video_calls:Bool has_private_calls:Bool has_private_forwards:Bool has_restricted_voice_and_video_note_messages:Bool need_phone_number_privacy_exception:Bool bio:formattedText premium_gift_options:vector<premiumPaymentOption> group_in_common_count:int32 bot_info:botInfo = UserFullInfo;
+
+//@description Represents a list of users @total_count Approximate total number of users found @user_ids A list of user identifiers
+users total_count:int32 user_ids:vector<int53> = Users;
+
+
+//@description Contains information about a chat administrator @user_id User identifier of the administrator @custom_title Custom title of the administrator @is_owner True, if the user is the owner of the chat
+chatAdministrator user_id:int53 custom_title:string is_owner:Bool = ChatAdministrator;
+
+//@description Represents a list of chat administrators @administrators A list of chat administrators
+chatAdministrators administrators:vector<chatAdministrator> = ChatAdministrators;
+
+
+//@class ChatMemberStatus @description Provides information about the status of a member in a chat
+
+//@description The user is the owner of the chat and has all the administrator privileges
+//@custom_title A custom title of the owner; 0-16 characters without emojis; applicable to supergroups only
+//@is_anonymous True, if the creator isn't shown in the chat member list and sends messages anonymously; applicable to supergroups only
+//@is_member True, if the user is a member of the chat
+chatMemberStatusCreator custom_title:string is_anonymous:Bool is_member:Bool = ChatMemberStatus;
+
+//@description The user is a member of the chat and has some additional privileges. In basic groups, administrators can edit and delete messages sent by others, add new members, ban unprivileged members, and manage video chats. In supergroups and channels, there are more detailed options for administrator privileges
+//@custom_title A custom title of the administrator; 0-16 characters without emojis; applicable to supergroups only
+//@can_be_edited True, if the current user can edit the administrator privileges for the called user
+//@rights Rights of the administrator
+chatMemberStatusAdministrator custom_title:string can_be_edited:Bool rights:chatAdministratorRights = ChatMemberStatus;
-//@description The user is a member of a chat, without any additional privileges or restrictions
+//@description The user is a member of the chat, without any additional privileges or restrictions
chatMemberStatusMember = ChatMemberStatus;
//@description The user is under certain restrictions in the chat. Not supported in basic groups and channels
//@is_member True, if the user is a member of the chat
//@restricted_until_date Point in time (Unix timestamp) when restrictions will be lifted from the user; 0 if never. If the user is restricted for more than 366 days or for less than 30 seconds from the current time, the user is considered to be restricted forever
-//@can_send_messages True, if the user can send text messages, contacts, locations, and venues
-//@can_send_media_messages True, if the user can send audio files, documents, photos, videos, video notes, and voice notes. Implies can_send_messages permissions
-//@can_send_other_messages True, if the user can send animations, games, and stickers and use inline bots. Implies can_send_media_messages permissions
-//@can_add_web_page_previews True, if the user may add a web page preview to his messages. Implies can_send_messages permissions
-chatMemberStatusRestricted is_member:Bool restricted_until_date:int32 can_send_messages:Bool can_send_media_messages:Bool can_send_other_messages:Bool can_add_web_page_previews:Bool = ChatMemberStatus;
+//@permissions User permissions in the chat
+chatMemberStatusRestricted is_member:Bool restricted_until_date:int32 permissions:chatPermissions = ChatMemberStatus;
-//@description The user is not a chat member
+//@description The user or the chat is not a chat member
chatMemberStatusLeft = ChatMemberStatus;
-//@description The user was banned (and hence is not a member of the chat). Implies the user can't return to the chat or view messages
-//@banned_until_date Point in time (Unix timestamp) when the user will be unbanned; 0 if never. If the user is banned for more than 366 days or for less than 30 seconds from the current time, the user is considered to be banned forever
+//@description The user or the chat was banned (and hence is not a member of the chat). Implies the user can't return to the chat, view messages, or be used as a participant identifier to join a video chat of the chat
+//@banned_until_date Point in time (Unix timestamp) when the user will be unbanned; 0 if never. If the user is banned for more than 366 days or for less than 30 seconds from the current time, the user is considered to be banned forever. Always 0 in basic groups
chatMemberStatusBanned banned_until_date:int32 = ChatMemberStatus;
-//@description A user with information about joining/leaving a chat @user_id User identifier of the chat member @inviter_user_id Identifier of a user that invited/promoted/banned this member in the chat; 0 if unknown
-//@joined_chat_date Point in time (Unix timestamp) when the user joined a chat @status Status of the member in the chat @bot_info If the user is a bot, information about the bot; may be null. Can be null even for a bot if the bot is not a chat member
-chatMember user_id:int32 inviter_user_id:int32 joined_chat_date:int32 status:ChatMemberStatus bot_info:botInfo = ChatMember;
+//@description Describes a user or a chat as a member of another chat
+//@member_id Identifier of the chat member. Currently, other chats can be only Left or Banned. Only supergroups and channels can have other chats as Left or Banned members and these chats must be supergroups or channels
+//@inviter_user_id Identifier of a user that invited/promoted/banned this member in the chat; 0 if unknown
+//@joined_chat_date Point in time (Unix timestamp) when the user joined the chat
+//@status Status of the member in the chat
+chatMember member_id:MessageSender inviter_user_id:int53 joined_chat_date:int32 status:ChatMemberStatus = ChatMember;
-//@description Contains a list of chat members @total_count Approximate total count of chat members found @members A list of chat members
+//@description Contains a list of chat members @total_count Approximate total number of chat members found @members A list of chat members
chatMembers total_count:int32 members:vector<chatMember> = ChatMembers;
+//@class ChatMembersFilter @description Specifies the kind of chat members to return in searchChatMembers
+
+//@description Returns contacts of the user
+chatMembersFilterContacts = ChatMembersFilter;
+
+//@description Returns the owner and administrators
+chatMembersFilterAdministrators = ChatMembersFilter;
+
+//@description Returns all chat members, including restricted chat members
+chatMembersFilterMembers = ChatMembersFilter;
+
+//@description Returns users which can be mentioned in the chat @message_thread_id If non-zero, the identifier of the current message thread
+chatMembersFilterMention message_thread_id:int53 = ChatMembersFilter;
+
+//@description Returns users under certain restrictions in the chat; can be used only by administrators in a supergroup
+chatMembersFilterRestricted = ChatMembersFilter;
+
+//@description Returns users banned from the chat; can be used only by administrators in a supergroup or in a channel
+chatMembersFilterBanned = ChatMembersFilter;
+
+//@description Returns bot members of the chat
+chatMembersFilterBots = ChatMembersFilter;
+
+
//@class SupergroupMembersFilter @description Specifies the kind of chat members to return in getSupergroupMembers
//@description Returns recently active users in reverse chronological order
supergroupMembersFilterRecent = SupergroupMembersFilter;
-//@description Returns the creator and administrators
+//@description Returns contacts of the user, which are members of the supergroup or channel @query Query to search for
+supergroupMembersFilterContacts query:string = SupergroupMembersFilter;
+
+//@description Returns the owner and administrators
supergroupMembersFilterAdministrators = SupergroupMembersFilter;
//@description Used to search for supergroup or channel members via a (string) query @query Query to search for
@@ -353,53 +642,131 @@ supergroupMembersFilterRestricted query:string = SupergroupMembersFilter;
//@description Returns users banned from the supergroup or channel; can be used only by administrators @query Query to search for
supergroupMembersFilterBanned query:string = SupergroupMembersFilter;
+//@description Returns users which can be mentioned in the supergroup @query Query to search for @message_thread_id If non-zero, the identifier of the current message thread
+supergroupMembersFilterMention query:string message_thread_id:int53 = SupergroupMembersFilter;
+
//@description Returns bot members of the supergroup or channel
supergroupMembersFilterBots = SupergroupMembersFilter;
+//@description Contains a chat invite link
+//@invite_link Chat invite link
+//@name Name of the link
+//@creator_user_id User identifier of an administrator created the link
+//@date Point in time (Unix timestamp) when the link was created
+//@edit_date Point in time (Unix timestamp) when the link was last edited; 0 if never or unknown
+//@expiration_date Point in time (Unix timestamp) when the link will expire; 0 if never
+//@member_limit The maximum number of members, which can join the chat using the link simultaneously; 0 if not limited. Always 0 if the link requires approval
+//@member_count Number of chat members, which joined the chat using the link
+//@pending_join_request_count Number of pending join requests created using this link
+//@creates_join_request True, if the link only creates join request. If true, total number of joining members will be unlimited
+//@is_primary True, if the link is primary. Primary invite link can't have name, expiration date, or usage limit. There is exactly one primary invite link for each administrator with can_invite_users right at a given time
+//@is_revoked True, if the link was revoked
+chatInviteLink invite_link:string name:string creator_user_id:int53 date:int32 edit_date:int32 expiration_date:int32 member_limit:int32 member_count:int32 pending_join_request_count:int32 creates_join_request:Bool is_primary:Bool is_revoked:Bool = ChatInviteLink;
+
+//@description Contains a list of chat invite links @total_count Approximate total number of chat invite links found @invite_links List of invite links
+chatInviteLinks total_count:int32 invite_links:vector<chatInviteLink> = ChatInviteLinks;
+
+//@description Describes a chat administrator with a number of active and revoked chat invite links
+//@user_id Administrator's user identifier
+//@invite_link_count Number of active invite links
+//@revoked_invite_link_count Number of revoked invite links
+chatInviteLinkCount user_id:int53 invite_link_count:int32 revoked_invite_link_count:int32 = ChatInviteLinkCount;
+
+//@description Contains a list of chat invite link counts @invite_link_counts List of invite link counts
+chatInviteLinkCounts invite_link_counts:vector<chatInviteLinkCount> = ChatInviteLinkCounts;
+
+//@description Describes a chat member joined a chat via an invite link @user_id User identifier @joined_chat_date Point in time (Unix timestamp) when the user joined the chat @approver_user_id User identifier of the chat administrator, approved user join request
+chatInviteLinkMember user_id:int53 joined_chat_date:int32 approver_user_id:int53 = ChatInviteLinkMember;
+
+//@description Contains a list of chat members joined a chat via an invite link @total_count Approximate total number of chat members found @members List of chat members, joined a chat via an invite link
+chatInviteLinkMembers total_count:int32 members:vector<chatInviteLinkMember> = ChatInviteLinkMembers;
+
+//@description Contains information about a chat invite link
+//@chat_id Chat identifier of the invite link; 0 if the user has no access to the chat before joining
+//@accessible_for If non-zero, the amount of time for which read access to the chat will remain available, in seconds
+//@type Type of the chat
+//@title Title of the chat
+//@photo Chat photo; may be null
+//@param_description Chat description
+//@member_count Number of members in the chat
+//@member_user_ids User identifiers of some chat members that may be known to the current user
+//@creates_join_request True, if the link only creates join request
+//@is_public True, if the chat is a public supergroup or channel, i.e. it has a username or it is a location-based supergroup
+chatInviteLinkInfo chat_id:int53 accessible_for:int32 type:ChatType title:string photo:chatPhotoInfo description:string member_count:int32 member_user_ids:vector<int53> creates_join_request:Bool is_public:Bool = ChatInviteLinkInfo;
+
+//@description Describes a user that sent a join request and waits for administrator approval @user_id User identifier @date Point in time (Unix timestamp) when the user sent the join request @bio A short bio of the user
+chatJoinRequest user_id:int53 date:int32 bio:string = ChatJoinRequest;
+
+//@description Contains a list of requests to join a chat @total_count Approximate total number of requests found @requests List of the requests
+chatJoinRequests total_count:int32 requests:vector<chatJoinRequest> = ChatJoinRequests;
+
+//@description Contains information about pending join requests for a chat @total_count Total number of pending join requests @user_ids Identifiers of at most 3 users sent the newest pending join requests
+chatJoinRequestsInfo total_count:int32 user_ids:vector<int53> = ChatJoinRequestsInfo;
+
+
//@description Represents a basic group of 0-200 users (must be upgraded to a supergroup to accommodate more than 200 users)
//@id Group identifier
//@member_count Number of members in the group
//@status Status of the current user in the group
-//@everyone_is_administrator True, if all members have been granted administrator rights in the group
//@is_active True, if the group is active
//@upgraded_to_supergroup_id Identifier of the supergroup to which this group was upgraded; 0 if none
-basicGroup id:int32 member_count:int32 status:ChatMemberStatus everyone_is_administrator:Bool is_active:Bool upgraded_to_supergroup_id:int32 = BasicGroup;
+basicGroup id:int53 member_count:int32 status:ChatMemberStatus is_active:Bool upgraded_to_supergroup_id:int53 = BasicGroup;
-//@description Contains full information about a basic group @creator_user_id User identifier of the creator of the group; 0 if unknown @members Group members @invite_link Invite link for this group; available only for the group creator and only after it has been generated at least once
-basicGroupFullInfo creator_user_id:int32 members:vector<chatMember> invite_link:string = BasicGroupFullInfo;
+//@description Contains full information about a basic group
+//@photo Chat photo; may be null if empty or unknown. If non-null, then it is the same photo as in chat.photo
+//@param_description Group description. Updated only after the basic group is opened
+//@creator_user_id User identifier of the creator of the group; 0 if unknown
+//@members Group members
+//@invite_link Primary invite link for this group; may be null. For chat administrators with can_invite_users right only. Updated only after the basic group is opened
+//@bot_commands List of commands of bots in the group
+basicGroupFullInfo photo:chatPhoto description:string creator_user_id:int53 members:vector<chatMember> invite_link:chatInviteLink bot_commands:vector<botCommands> = BasicGroupFullInfo;
//@description Represents a supergroup or channel with zero or more members (subscribers in the case of channels). From the point of view of the system, a channel is a special kind of a supergroup: only administrators can post and see the list of members, and posts from all administrators use the name and photo of the channel instead of individual names and profile photos. Unlike supergroups, channels can have an unlimited number of subscribers
//@id Supergroup or channel identifier
-//@username Username of the supergroup or channel; empty for private supergroups or channels
+//@usernames Usernames of the supergroup or channel; may be null
//@date Point in time (Unix timestamp) when the current user joined, or the point in time when the supergroup or channel was created, in case the user is not a member
-//@status Status of the current user in the supergroup or channel
-//@member_count Member count; 0 if unknown. Currently it is guaranteed to be known only if the supergroup or channel was found through SearchPublicChats
-//@anyone_can_invite True, if any member of the supergroup can invite other members. This field has no meaning for channels
-//@sign_messages True, if messages sent to the channel should contain information about the sender. This field is only applicable to channels
+//@status Status of the current user in the supergroup or channel; custom title will always be empty
+//@member_count Number of members in the supergroup or channel; 0 if unknown. Currently, it is guaranteed to be known only if the supergroup or channel was received through searchPublicChats, searchChatsNearby, getInactiveSupergroupChats, getSuitableDiscussionChats, getGroupsInCommon, or getUserPrivacySettingRules
+//@has_linked_chat True, if the channel has a discussion group, or the supergroup is the designated discussion group for a channel
+//@has_location True, if the supergroup is connected to a location, i.e. the supergroup is a location-based supergroup
+//@sign_messages True, if messages sent to the channel need to contain information about the sender. This field is only applicable to channels
+//@join_to_send_messages True, if users need to join the supergroup before they can send messages. Always true for channels and non-discussion supergroups
+//@join_by_request True, if all users directly joining the supergroup need to be approved by supergroup administrators. Always false for channels and supergroups without username, location, or a linked chat
+//@is_slow_mode_enabled True, if the slow mode is enabled in the supergroup
//@is_channel True, if the supergroup is a channel
+//@is_broadcast_group True, if the supergroup is a broadcast group, i.e. only administrators can send messages and there is no limit on the number of members
+//@is_forum True, if the supergroup must be shown as a forum by default
//@is_verified True, if the supergroup or channel is verified
-//@restriction_reason If non-empty, contains the reason why access to this supergroup or channel must be restricted. Format of the string is "{type}: {description}".
-//-{type} Contains the type of the restriction and at least one of the suffixes "-all", "-ios", "-android", or "-wp", which describe the platforms on which access should be restricted. (For example, "terms-ios-android". {description} contains a human-readable description of the restriction, which can be shown to the user)
-supergroup id:int32 username:string date:int32 status:ChatMemberStatus member_count:int32 anyone_can_invite:Bool sign_messages:Bool is_channel:Bool is_verified:Bool restriction_reason:string = Supergroup;
+//@restriction_reason If non-empty, contains a human-readable description of the reason why access to this supergroup or channel must be restricted
+//@is_scam True, if many users reported this supergroup or channel as a scam
+//@is_fake True, if many users reported this supergroup or channel as a fake account
+supergroup id:int53 usernames:usernames date:int32 status:ChatMemberStatus member_count:int32 has_linked_chat:Bool has_location:Bool sign_messages:Bool join_to_send_messages:Bool join_by_request:Bool is_slow_mode_enabled:Bool is_channel:Bool is_broadcast_group:Bool is_forum:Bool is_verified:Bool restriction_reason:string is_scam:Bool is_fake:Bool = Supergroup;
//@description Contains full information about a supergroup or channel
+//@photo Chat photo; may be null if empty or unknown. If non-null, then it is the same photo as in chat.photo
//@param_description Supergroup or channel description
//@member_count Number of members in the supergroup or channel; 0 if unknown
//@administrator_count Number of privileged users in the supergroup or channel; 0 if unknown
//@restricted_count Number of restricted users in the supergroup; 0 if unknown
//@banned_count Number of users banned from chat; 0 if unknown
+//@linked_chat_id Chat identifier of a discussion group for the channel, or a channel, for which the supergroup is the designated discussion group; 0 if none or unknown
+//@slow_mode_delay Delay between consecutive sent messages for non-administrator supergroup members, in seconds
+//@slow_mode_delay_expires_in Time left before next message can be sent in the supergroup, in seconds. An updateSupergroupFullInfo update is not triggered when value of this field changes, but both new and old values are non-zero
//@can_get_members True, if members of the chat can be retrieved
-//@can_set_username True, if the chat can be made public
+//@can_set_username True, if the chat username can be changed
//@can_set_sticker_set True, if the supergroup sticker set can be changed
-//@is_all_history_available True, if new chat members will have access to old messages. In public supergroups and both public and private channels, old messages are always available, so this option affects only private supergroups. The value of this field is only available for chat administrators
+//@can_set_location True, if the supergroup location can be changed
+//@can_get_statistics True, if the supergroup or channel statistics are available
+//@is_all_history_available True, if new chat members will have access to old messages. In public, discussion, of forum groups and all channels, old messages are always available, so this option affects only private non-forum supergroups without a linked chat. The value of this field is only available for chat administrators
//@sticker_set_id Identifier of the supergroup sticker set; 0 if none
-//@invite_link Invite link for this chat
-//@pinned_message_id Identifier of the pinned message in the chat; 0 if none
+//@location Location to which the supergroup is connected; may be null
+//@invite_link Primary invite link for the chat; may be null. For chat administrators with can_invite_users right only
+//@bot_commands List of commands of bots in the group
//@upgraded_from_basic_group_id Identifier of the basic group from which supergroup was upgraded; 0 if none
//@upgraded_from_max_message_id Identifier of the last message in the basic group from which supergroup was upgraded; 0 if none
-supergroupFullInfo description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool is_all_history_available:Bool sticker_set_id:int64 invite_link:string pinned_message_id:int53 upgraded_from_basic_group_id:int32 upgraded_from_max_message_id:int53 = SupergroupFullInfo;
+supergroupFullInfo photo:chatPhoto description:string member_count:int32 administrator_count:int32 restricted_count:int32 banned_count:int32 linked_chat_id:int53 slow_mode_delay:int32 slow_mode_delay_expires_in:double can_get_members:Bool can_set_username:Bool can_set_sticker_set:Bool can_set_location:Bool can_get_statistics:Bool is_all_history_available:Bool sticker_set_id:int64 location:chatLocation invite_link:chatInviteLink bot_commands:vector<botCommands> upgraded_from_basic_group_id:int53 upgraded_from_max_message_id:int53 = SupergroupFullInfo;
//@class SecretChatState @description Describes the current secret chat state
@@ -419,25 +786,99 @@ secretChatStateClosed = SecretChatState;
//@user_id Identifier of the chat partner
//@state State of the secret chat
//@is_outbound True, if the chat was created by the current user; otherwise false
-//@ttl Current message Time To Live setting (self-destruct timer) for the chat, in seconds
-//@key_hash Hash of the currently used key for comparison with the hash of the chat partner's key. This is a string of 36 bytes, which must be used to make a 12x12 square image with a color depth of 4. The first 16 bytes should be used to make a central 8x8 square, while the remaining 20 bytes should be used to construct a 2-pixel-wide border around that square.
-//-Alternatively, the first 32 bytes of the hash can be converted to the hexadecimal format and printed as 32 2-digit hex numbers
-//@layer Secret chat layer; determines features supported by the other client. Video notes are supported if the layer >= 66
-secretChat id:int32 user_id:int32 state:SecretChatState is_outbound:Bool ttl:int32 key_hash:bytes layer:int32 = SecretChat;
+//@key_hash Hash of the currently used key for comparison with the hash of the chat partner's key. This is a string of 36 little-endian bytes, which must be split into groups of 2 bits, each denoting a pixel of one of 4 colors FFFFFF, D5E6F3, 2D5775, and 2F99C9.
+//-The pixels must be used to make a 12x12 square image filled from left to right, top to bottom. Alternatively, the first 32 bytes of the hash can be converted to the hexadecimal format and printed as 32 2-digit hex numbers
+//@layer Secret chat layer; determines features supported by the chat partner's application. Nested text entities and underline and strikethrough entities are supported if the layer >= 101, files bigger than 2000MB are supported if the layer >= 143, spoiler and custom emoji text entities are supported if the layer >= 144
+secretChat id:int32 user_id:int53 state:SecretChatState is_outbound:Bool key_hash:bytes layer:int32 = SecretChat;
-//@class MessageForwardInfo @description Contains information about the initial sender of a forwarded message
+//@class MessageSender @description Contains information about the sender of a message
-//@description The message was originally written by a known user @sender_user_id Identifier of the user that originally sent this message @date Point in time (Unix timestamp) when the message was originally sent
-//@forwarded_from_chat_id For messages forwarded to the chat with the current user (saved messages), the identifier of the chat from which the message was forwarded; 0 if unknown
-//@forwarded_from_message_id For messages forwarded to the chat with the current user (saved messages) the identifier of the original message from which the new message was forwarded; 0 if unknown
-messageForwardedFromUser sender_user_id:int32 date:int32 forwarded_from_chat_id:int53 forwarded_from_message_id:int53 = MessageForwardInfo;
+//@description The message was sent by a known user @user_id Identifier of the user that sent the message
+messageSenderUser user_id:int53 = MessageSender;
-//@description The message was originally a post in a channel @chat_id Identifier of the chat from which the message was forwarded @author_signature Post author signature
-//@date Point in time (Unix timestamp) when the message was originally sent @message_id Message identifier of the original message from which the new message was forwarded; 0 if unknown
-//@forwarded_from_chat_id For messages forwarded to the chat with the current user (saved messages), the identifier of the chat from which the message was forwarded; 0 if unknown
-//@forwarded_from_message_id For messages forwarded to the chat with the current user (saved messages), the identifier of the original message from which the new message was forwarded; 0 if unknown
-messageForwardedPost chat_id:int53 author_signature:string date:int32 message_id:int53 forwarded_from_chat_id:int53 forwarded_from_message_id:int53 = MessageForwardInfo;
+//@description The message was sent on behalf of a chat @chat_id Identifier of the chat that sent the message
+messageSenderChat chat_id:int53 = MessageSender;
+
+
+//@description Represents a list of message senders @total_count Approximate total number of messages senders found @senders List of message senders
+messageSenders total_count:int32 senders:vector<MessageSender> = MessageSenders;
+
+
+//@description Represents a message sender, which can be used to send messages in a chat @sender Available message senders @needs_premium True, if Telegram Premium is needed to use the message sender
+chatMessageSender sender:MessageSender needs_premium:Bool = ChatMessageSender;
+
+//@description Represents a list of message senders, which can be used to send messages in a chat @senders List of available message senders
+chatMessageSenders senders:vector<chatMessageSender> = ChatMessageSenders;
+
+
+//@class MessageForwardOrigin @description Contains information about the origin of a forwarded message
+
+//@description The message was originally sent by a known user @sender_user_id Identifier of the user that originally sent the message
+messageForwardOriginUser sender_user_id:int53 = MessageForwardOrigin;
+
+//@description The message was originally sent on behalf of a chat
+//@sender_chat_id Identifier of the chat that originally sent the message
+//@author_signature For messages originally sent by an anonymous chat administrator, original message author signature
+messageForwardOriginChat sender_chat_id:int53 author_signature:string = MessageForwardOrigin;
+
+//@description The message was originally sent by a user, which is hidden by their privacy settings @sender_name Name of the sender
+messageForwardOriginHiddenUser sender_name:string = MessageForwardOrigin;
+
+//@description The message was originally a post in a channel
+//@chat_id Identifier of the chat from which the message was originally forwarded
+//@message_id Message identifier of the original message
+//@author_signature Original post author signature
+messageForwardOriginChannel chat_id:int53 message_id:int53 author_signature:string = MessageForwardOrigin;
+
+//@description The message was imported from an exported message history @sender_name Name of the sender
+messageForwardOriginMessageImport sender_name:string = MessageForwardOrigin;
+
+
+//@class ReactionType @description Describes type of message reaction
+
+//@description A reaction with an emoji @emoji Text representation of the reaction
+reactionTypeEmoji emoji:string = ReactionType;
+
+//@description A reaction with a custom emoji @custom_emoji_id Unique identifier of the custom emoji
+reactionTypeCustomEmoji custom_emoji_id:int64 = ReactionType;
+
+
+//@description Contains information about a forwarded message
+//@origin Origin of a forwarded message
+//@date Point in time (Unix timestamp) when the message was originally sent
+//@public_service_announcement_type The type of a public service announcement for the forwarded message
+//@from_chat_id For messages forwarded to the chat with the current user (Saved Messages), to the Replies bot chat, or to the channel's discussion group, the identifier of the chat from which the message was forwarded last time; 0 if unknown
+//@from_message_id For messages forwarded to the chat with the current user (Saved Messages), to the Replies bot chat, or to the channel's discussion group, the identifier of the original message from which the new message was forwarded last time; 0 if unknown
+messageForwardInfo origin:MessageForwardOrigin date:int32 public_service_announcement_type:string from_chat_id:int53 from_message_id:int53 = MessageForwardInfo;
+
+//@description Contains information about replies to a message
+//@reply_count Number of times the message was directly or indirectly replied
+//@recent_replier_ids Identifiers of at most 3 recent repliers to the message; available in channels with a discussion supergroup. The users and chats are expected to be inaccessible: only their photo and name will be available
+//@last_read_inbox_message_id Identifier of the last read incoming reply to the message
+//@last_read_outbox_message_id Identifier of the last read outgoing reply to the message
+//@last_message_id Identifier of the last reply to the message
+messageReplyInfo reply_count:int32 recent_replier_ids:vector<MessageSender> last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 last_message_id:int53 = MessageReplyInfo;
+
+//@description Contains information about a reaction to a message
+//@type Type of the reaction
+//@total_count Number of times the reaction was added
+//@is_chosen True, if the reaction is chosen by the current user
+//@recent_sender_ids Identifiers of at most 3 recent message senders, added the reaction; available in private, basic group and supergroup chats
+messageReaction type:ReactionType total_count:int32 is_chosen:Bool recent_sender_ids:vector<MessageSender> = MessageReaction;
+
+//@description Contains information about interactions with a message
+//@view_count Number of times the message was viewed
+//@forward_count Number of times the message was forwarded
+//@reply_info Information about direct or indirect replies to the message; may be null. Currently, available only in channels with a discussion supergroup and discussion supergroups for messages, which are not replies itself
+//@reactions The list of reactions added to the message
+messageInteractionInfo view_count:int32 forward_count:int32 reply_info:messageReplyInfo reactions:vector<messageReaction> = MessageInteractionInfo;
+
+//@description Contains information about an unread reaction to a message
+//@type Type of the reaction
+//@sender_id Identifier of the sender, added the reaction
+//@is_big True, if the reaction was added with a big animation
+unreadReaction type:ReactionType sender_id:MessageSender is_big:Bool = UnreadReaction;
//@class MessageSendingState @description Contains information about the sending state of the message
@@ -445,79 +886,235 @@ messageForwardedPost chat_id:int53 author_signature:string date:int32 message_id
//@description The message is being sent now, but has not yet been delivered to the server
messageSendingStatePending = MessageSendingState;
-//@description The message failed to be sent
-messageSendingStateFailed = MessageSendingState;
+//@description The message failed to be sent @error_code An error code; 0 if unknown @error_message Error message
+//@can_retry True, if the message can be re-sent
+//@need_another_sender True, if the message can be re-sent only on behalf of a different sender
+//@retry_after Time left before the message can be re-sent, in seconds. No update is sent when this field changes
+messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool need_another_sender:Bool retry_after:double = MessageSendingState;
//@description Describes a message
-//@id Unique message identifier
-//@sender_user_id Identifier of the user who sent the message; 0 if unknown. It is unknown for channel posts
+//@id Message identifier; unique for the chat to which the message belongs
+//@sender_id Identifier of the sender of the message
//@chat_id Chat identifier
-//@sending_state Information about the sending state of the message; may be null
+//@sending_state The sending state of the message; may be null
+//@scheduling_state The scheduling state of the message; may be null
//@is_outgoing True, if the message is outgoing
-//@can_be_edited True, if the message can be edited
+//@is_pinned True, if the message is pinned
+//@can_be_edited True, if the message can be edited. For live location and poll messages this fields shows whether editMessageLiveLocation or stopPoll can be used with this message by the application
//@can_be_forwarded True, if the message can be forwarded
+//@can_be_saved True, if content of the message can be saved locally or copied
//@can_be_deleted_only_for_self True, if the message can be deleted only for the current user while other users will continue to see it
//@can_be_deleted_for_all_users True, if the message can be deleted for all users
+//@can_get_added_reactions True, if the list of added reactions is available through getMessageAddedReactions
+//@can_get_statistics True, if the message statistics are available through getMessageStatistics
+//@can_get_message_thread True, if information about the message thread is available through getMessageThread and getMessageThreadHistory
+//@can_get_viewers True, if chat members already viewed the message can be received through getMessageViewers
+//@can_get_media_timestamp_links True, if media timestamp links can be generated for media timestamp entities in the message text, caption or web page description through getMessageLink
+//@can_report_reactions True, if reactions on the message can be reported through reportMessageReactions
+//@has_timestamped_media True, if media timestamp entities refers to a media in this message as opposed to a media in the replied message
//@is_channel_post True, if the message is a channel post. All messages to channels are channel posts, all other messages are not channel posts
+//@is_topic_message True, if the message is a forum topic message
//@contains_unread_mention True, if the message contains an unread mention for the current user
//@date Point in time (Unix timestamp) when the message was sent
//@edit_date Point in time (Unix timestamp) when the message was last edited
//@forward_info Information about the initial message sender; may be null
+//@interaction_info Information about interactions with the message; may be null
+//@unread_reactions Information about unread reactions added to the message
+//@reply_in_chat_id If non-zero, the identifier of the chat to which the replied message belongs; Currently, only messages in the Replies chat can have different reply_in_chat_id and chat_id
//@reply_to_message_id If non-zero, the identifier of the message this message is replying to; can be the identifier of a deleted message
+//@message_thread_id If non-zero, the identifier of the message thread the message belongs to; unique within the chat to which the message belongs
//@ttl For self-destructing messages, the message's TTL (Time To Live), in seconds; 0 if none. TDLib will send updateDeleteMessages or updateMessageContent once the TTL expires
-//@ttl_expires_in Time left before the message expires, in seconds
+//@ttl_expires_in Time left before the message expires, in seconds. If the TTL timer isn't started yet, equals to the value of the ttl field
//@via_bot_user_id If non-zero, the user identifier of the bot through which this message was sent
-//@author_signature For channel posts, optional author signature
-//@views Number of times this message was viewed
-//@media_album_id Unique identifier of an album this message belongs to. Only photos and videos can be grouped together in albums
+//@author_signature For channel posts and anonymous group messages, optional author signature
+//@media_album_id Unique identifier of an album this message belongs to. Only audios, documents, photos and videos can be grouped together in albums
+//@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted
//@content Content of the message
//@reply_markup Reply markup for the message; may be null
-message id:int53 sender_user_id:int32 chat_id:int53 sending_state:MessageSendingState is_outgoing:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool is_channel_post:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:MessageForwardInfo reply_to_message_id:int53 ttl:int32 ttl_expires_in:double via_bot_user_id:int32 author_signature:string views:int32 media_album_id:int64 content:MessageContent reply_markup:ReplyMarkup = Message;
+message id:int53 sender_id:MessageSender chat_id:int53 sending_state:MessageSendingState scheduling_state:MessageSchedulingState is_outgoing:Bool is_pinned:Bool can_be_edited:Bool can_be_forwarded:Bool can_be_saved:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_get_added_reactions:Bool can_get_statistics:Bool can_get_message_thread:Bool can_get_viewers:Bool can_get_media_timestamp_links:Bool can_report_reactions:Bool has_timestamped_media:Bool is_channel_post:Bool is_topic_message:Bool contains_unread_mention:Bool date:int32 edit_date:int32 forward_info:messageForwardInfo interaction_info:messageInteractionInfo unread_reactions:vector<unreadReaction> reply_in_chat_id:int53 reply_to_message_id:int53 message_thread_id:int53 ttl:int32 ttl_expires_in:double via_bot_user_id:int53 author_signature:string media_album_id:int64 restriction_reason:string content:MessageContent reply_markup:ReplyMarkup = Message;
-//@description Contains a list of messages @total_count Approximate total count of messages found @messages List of messages; messages may be null
+//@description Contains a list of messages @total_count Approximate total number of messages found @messages List of messages; messages may be null
messages total_count:int32 messages:vector<message> = Messages;
-//@description Contains a list of messages found by a search @messages List of messages @next_from_search_id Value to pass as from_search_id to get more results
-foundMessages messages:vector<message> next_from_search_id:int64 = FoundMessages;
+//@description Contains a list of messages found by a search @total_count Approximate total number of messages found; -1 if unknown @messages List of messages @next_offset The offset for the next request. If empty, there are no more results
+foundMessages total_count:int32 messages:vector<message> next_offset:string = FoundMessages;
+
+//@description Contains information about a message in a specific position @position 0-based message position in the full list of suitable messages @message_id Message identifier @date Point in time (Unix timestamp) when the message was sent
+messagePosition position:int32 message_id:int53 date:int32 = MessagePosition;
+
+//@description Contains a list of message positions @total_count Total number of messages found @positions List of message positions
+messagePositions total_count:int32 positions:vector<messagePosition> = MessagePositions;
+
+//@description Contains information about found messages sent on a specific day @total_count Total number of found messages sent on the day @message First message sent on the day
+messageCalendarDay total_count:int32 message:message = MessageCalendarDay;
+
+//@description Contains information about found messages, split by days according to the option "utc_time_offset" @total_count Total number of found messages @days Information about messages sent
+messageCalendar total_count:int32 days:vector<messageCalendarDay> = MessageCalendar;
+
+
+//@description Describes a sponsored message
+//@message_id Message identifier; unique for the chat to which the sponsored message belongs among both ordinary and sponsored messages
+//@is_recommended True, if the message needs to be labeled as "recommended" instead of "sponsored"
+//@sponsor_chat_id Sponsor chat identifier; 0 if the sponsor chat is accessible through an invite link
+//@sponsor_chat_info Information about the sponsor chat; may be null unless sponsor_chat_id == 0
+//@show_chat_photo True, if the sponsor's chat photo must be shown
+//@link An internal link to be opened when the sponsored message is clicked; may be null if the sponsor chat needs to be opened instead
+//@content Content of the message. Currently, can be only of the type messageText
+sponsoredMessage message_id:int53 is_recommended:Bool sponsor_chat_id:int53 sponsor_chat_info:chatInviteLinkInfo show_chat_photo:Bool link:InternalLinkType content:MessageContent = SponsoredMessage;
+
+//@description Contains a list of sponsored messages @messages List of sponsored messages @messages_between The minimum number of messages between shown sponsored messages, or 0 if only one sponsored message must be shown after all ordinary messages
+sponsoredMessages messages:vector<sponsoredMessage> messages_between:int32 = SponsoredMessages;
-//@class NotificationSettingsScope @description Describes the types of chats for which notification settings are applied
+//@description Describes a file added to file download list
+//@file_id File identifier
+//@message The message with the file
+//@add_date Point in time (Unix timestamp) when the file was added to the download list
+//@complete_date Point in time (Unix timestamp) when the file downloading was completed; 0 if the file downloading isn't completed
+//@is_paused True, if downloading of the file is paused
+fileDownload file_id:int32 message:message add_date:int32 complete_date:int32 is_paused:Bool = FileDownload;
-//@description Notification settings applied to a particular chat @chat_id Chat identifier
-notificationSettingsScopeChat chat_id:int53 = NotificationSettingsScope;
+//@description Contains number of being downloaded and recently downloaded files found
+//@active_count Number of active file downloads found, including paused
+//@paused_count Number of paused file downloads found
+//@completed_count Number of completed file downloads found
+downloadedFileCounts active_count:int32 paused_count:int32 completed_count:int32 = DownloadedFileCounts;
-//@description Notification settings applied to all private chats
+//@description Contains a list of downloaded files, found by a search
+//@total_counts Total number of suitable files, ignoring offset
+//@files The list of files
+//@next_offset The offset for the next request. If empty, there are no more results
+foundFileDownloads total_counts:downloadedFileCounts files:vector<fileDownload> next_offset:string = FoundFileDownloads;
+
+
+//@class NotificationSettingsScope @description Describes the types of chats to which notification settings are relevant
+
+//@description Notification settings applied to all private and secret chats when the corresponding chat setting has a default value
notificationSettingsScopePrivateChats = NotificationSettingsScope;
-//@description Notification settings applied to all basic groups and channels. (Supergroups have no common settings)
-notificationSettingsScopeBasicGroupChats = NotificationSettingsScope;
+//@description Notification settings applied to all basic group and supergroup chats when the corresponding chat setting has a default value
+notificationSettingsScopeGroupChats = NotificationSettingsScope;
-//@description Notification settings applied to all chats
-notificationSettingsScopeAllChats = NotificationSettingsScope;
+//@description Notification settings applied to all channel chats when the corresponding chat setting has a default value
+notificationSettingsScopeChannelChats = NotificationSettingsScope;
-//@description Contains information about notification settings for a chat or several chats @mute_for Time left before notifications will be unmuted, in seconds @sound An audio file name for notification sounds; only applies to iOS applications @show_preview True, if message content should be displayed in notifications
-notificationSettings mute_for:int32 sound:string show_preview:Bool = NotificationSettings;
+//@description Contains information about notification settings for a chat
+//@use_default_mute_for If true, mute_for is ignored and the value for the relevant type of chat is used instead @mute_for Time left before notifications will be unmuted, in seconds
+//@use_default_sound If true, the value for the relevant type of chat is used instead of sound_id @sound_id Identifier of the notification sound to be played; 0 if sound is disabled
+//@use_default_show_preview If true, show_preview is ignored and the value for the relevant type of chat is used instead @show_preview True, if message content must be displayed in notifications
+//@use_default_disable_pinned_message_notifications If true, disable_pinned_message_notifications is ignored and the value for the relevant type of chat is used instead @disable_pinned_message_notifications If true, notifications for incoming pinned messages will be created as for an ordinary unread message
+//@use_default_disable_mention_notifications If true, disable_mention_notifications is ignored and the value for the relevant type of chat is used instead @disable_mention_notifications If true, notifications for messages with mentions will be created as for an ordinary unread message
+chatNotificationSettings use_default_mute_for:Bool mute_for:int32 use_default_sound:Bool sound_id:int64 use_default_show_preview:Bool show_preview:Bool use_default_disable_pinned_message_notifications:Bool disable_pinned_message_notifications:Bool use_default_disable_mention_notifications:Bool disable_mention_notifications:Bool = ChatNotificationSettings;
+//@description Contains information about notification settings for several chats
+//@mute_for Time left before notifications will be unmuted, in seconds
+//@sound_id Identifier of the notification sound to be played; 0 if sound is disabled
+//@show_preview True, if message content must be displayed in notifications
+//@disable_pinned_message_notifications True, if notifications for incoming pinned messages will be created as for an ordinary unread message
+//@disable_mention_notifications True, if notifications for messages with mentions will be created as for an ordinary unread message
+scopeNotificationSettings mute_for:int32 sound_id:int64 show_preview:Bool disable_pinned_message_notifications:Bool disable_mention_notifications:Bool = ScopeNotificationSettings;
-//@description Contains information about a message draft @reply_to_message_id Identifier of the message to reply to; 0 if none @input_message_text Content of the message draft; this should always be of type inputMessageText
-draftMessage reply_to_message_id:int53 input_message_text:InputMessageContent = DraftMessage;
+
+//@description Contains information about a message draft
+//@reply_to_message_id Identifier of the replied message; 0 if none
+//@date Point in time (Unix timestamp) when the draft was created
+//@input_message_text Content of the message draft; must be of the type inputMessageText
+draftMessage reply_to_message_id:int53 date:int32 input_message_text:InputMessageContent = DraftMessage;
//@class ChatType @description Describes the type of a chat
//@description An ordinary chat with a user @user_id User identifier
-chatTypePrivate user_id:int32 = ChatType;
+chatTypePrivate user_id:int53 = ChatType;
-//@description A basic group (i.e., a chat with 0-200 other users) @basic_group_id Basic group identifier
-chatTypeBasicGroup basic_group_id:int32 = ChatType;
+//@description A basic group (a chat with 0-200 other users) @basic_group_id Basic group identifier
+chatTypeBasicGroup basic_group_id:int53 = ChatType;
-//@description A supergroup (i.e. a chat with up to GetOption("supergroup_max_size") other users), or channel (with unlimited members) @supergroup_id Supergroup or channel identifier @is_channel True, if the supergroup is a channel
-chatTypeSupergroup supergroup_id:int32 is_channel:Bool = ChatType;
+//@description A supergroup or channel (with unlimited members) @supergroup_id Supergroup or channel identifier @is_channel True, if the supergroup is a channel
+chatTypeSupergroup supergroup_id:int53 is_channel:Bool = ChatType;
//@description A secret chat with a user @secret_chat_id Secret chat identifier @user_id User identifier of the secret chat peer
-chatTypeSecret secret_chat_id:int32 user_id:int32 = ChatType;
+chatTypeSecret secret_chat_id:int32 user_id:int53 = ChatType;
+
+
+//@description Represents a filter of user chats
+//@title The title of the filter; 1-12 characters without line feeds
+//@icon_name The chosen icon name for short filter representation. If non-empty, must be one of "All", "Unread", "Unmuted", "Bots", "Channels", "Groups", "Private", "Custom", "Setup", "Cat", "Crown", "Favorite", "Flower", "Game", "Home", "Love", "Mask", "Party", "Sport", "Study", "Trade", "Travel", "Work", "Airplane", "Book", "Light", "Like", "Money", "Note", "Palette".
+//-If empty, use getChatFilterDefaultIconName to get default icon name for the filter
+//@pinned_chat_ids The chat identifiers of pinned chats in the filtered chat list. There can be up to GetOption("chat_filter_chosen_chat_count_max") pinned and always included non-secret chats and the same number of secret chats, but the limit can be increased with Telegram Premium
+//@included_chat_ids The chat identifiers of always included chats in the filtered chat list. There can be up to GetOption("chat_filter_chosen_chat_count_max") pinned and always included non-secret chats and the same number of secret chats, but the limit can be increased with Telegram Premium
+//@excluded_chat_ids The chat identifiers of always excluded chats in the filtered chat list. There can be up to GetOption("chat_filter_chosen_chat_count_max") always excluded non-secret chats and the same number of secret chats, but the limit can be increased with Telegram Premium
+//@exclude_muted True, if muted chats need to be excluded
+//@exclude_read True, if read chats need to be excluded
+//@exclude_archived True, if archived chats need to be excluded
+//@include_contacts True, if contacts need to be included
+//@include_non_contacts True, if non-contact users need to be included
+//@include_bots True, if bots need to be included
+//@include_groups True, if basic groups and supergroups need to be included
+//@include_channels True, if channels need to be included
+chatFilter title:string icon_name:string pinned_chat_ids:vector<int53> included_chat_ids:vector<int53> excluded_chat_ids:vector<int53> exclude_muted:Bool exclude_read:Bool exclude_archived:Bool include_contacts:Bool include_non_contacts:Bool include_bots:Bool include_groups:Bool include_channels:Bool = ChatFilter;
+
+//@description Contains basic information about a chat filter
+//@id Unique chat filter identifier
+//@title The title of the filter; 1-12 characters without line feeds
+//@icon_name The chosen or default icon name for short filter representation. One of "All", "Unread", "Unmuted", "Bots", "Channels", "Groups", "Private", "Custom", "Setup", "Cat", "Crown", "Favorite", "Flower", "Game", "Home", "Love", "Mask", "Party", "Sport", "Study", "Trade", "Travel", "Work", "Airplane", "Book", "Light", "Like", "Money", "Note", "Palette"
+chatFilterInfo id:int32 title:string icon_name:string = ChatFilterInfo;
+
+//@description Describes a recommended chat filter @filter The chat filter @param_description Chat filter description
+recommendedChatFilter filter:chatFilter description:string = RecommendedChatFilter;
+
+//@description Contains a list of recommended chat filters @chat_filters List of recommended chat filters
+recommendedChatFilters chat_filters:vector<recommendedChatFilter> = RecommendedChatFilters;
+
+
+//@class ChatList @description Describes a list of chats
+
+//@description A main list of chats
+chatListMain = ChatList;
+
+//@description A list of chats usually located at the top of the main chat list. Unmuted chats are automatically moved from the Archive to the Main chat list when a new message arrives
+chatListArchive = ChatList;
+
+//@description A list of chats belonging to a chat filter @chat_filter_id Chat filter identifier
+chatListFilter chat_filter_id:int32 = ChatList;
+
+//@description Contains a list of chat lists @chat_lists List of chat lists
+chatLists chat_lists:vector<ChatList> = ChatLists;
+
+
+//@class ChatSource @description Describes a reason why an external chat is shown in a chat list
+
+//@description The chat is sponsored by the user's MTProxy server
+chatSourceMtprotoProxy = ChatSource;
+
+//@description The chat contains a public service announcement @type The type of the announcement @text The text of the announcement
+chatSourcePublicServiceAnnouncement type:string text:string = ChatSource;
+
+
+//@description Describes a position of a chat in a chat list
+//@list The chat list
+//@order A parameter used to determine order of the chat in the chat list. Chats must be sorted by the pair (order, chat.id) in descending order
+//@is_pinned True, if the chat is pinned in the chat list
+//@source Source of the chat in the chat list; may be null
+chatPosition list:ChatList order:int64 is_pinned:Bool source:ChatSource = ChatPosition;
+
+
+//@class ChatAvailableReactions @description Describes reactions available in the chat
+
+//@description All reactions are available in the chat
+chatAvailableReactionsAll = ChatAvailableReactions;
+
+//@description Only specific reactions are available in the chat @reactions The list of reactions
+chatAvailableReactionsSome reactions:vector<ReactionType> = ChatAvailableReactions;
+
+
+//@description Describes a video chat
+//@group_call_id Group call identifier of an active video chat; 0 if none. Full information about the video chat can be received through the method getGroupCall
+//@has_participants True, if the video chat has participants
+//@default_participant_id Default group call participant identifier to join the video chat; may be null
+videoChat group_call_id:int32 has_participants:Bool default_participant_id:MessageSender = VideoChat;
//@description A chat. (Can be a private chat, basic group, supergroup, or secret chat)
@@ -525,41 +1122,88 @@ chatTypeSecret secret_chat_id:int32 user_id:int32 = ChatType;
//@type Type of the chat
//@title Chat title
//@photo Chat photo; may be null
+//@permissions Actions that non-administrator chat members are allowed to take in the chat
//@last_message Last message in the chat; may be null
-//@order Descending parameter by which chats are sorted in the main chat list. If the order number of two chats is the same, they must be sorted in descending order by ID. If 0, the position of the chat in the list is undetermined
-//@is_pinned True, if the chat is pinned
-//@can_be_reported True, if the chat can be reported to Telegram moderators through reportChat
+//@positions Positions of the chat in chat lists
+//@message_sender_id Identifier of a user or chat that is selected to send messages in the chat; may be null if the user can't change message sender
+//@has_protected_content True, if chat content can't be saved locally, forwarded, or copied
+//@is_marked_as_unread True, if the chat is marked as unread
+//@is_blocked True, if the chat is blocked by the current user and private messages from the chat can't be received
+//@has_scheduled_messages True, if the chat has scheduled messages
+//@can_be_deleted_only_for_self True, if the chat messages can be deleted only for the current user while other users will continue to see the messages
+//@can_be_deleted_for_all_users True, if the chat messages can be deleted for all users
+//@can_be_reported True, if the chat can be reported to Telegram moderators through reportChat or reportChatPhoto
+//@default_disable_notification Default value of the disable_notification parameter, used when a message is sent to the chat
//@unread_count Number of unread messages in the chat
//@last_read_inbox_message_id Identifier of the last read incoming message
//@last_read_outbox_message_id Identifier of the last read outgoing message
//@unread_mention_count Number of unread messages with a mention/reply in the chat
-//@notification_settings Notification settings for this chat
+//@unread_reaction_count Number of messages with unread reactions in the chat
+//@notification_settings Notification settings for the chat
+//@available_reactions Types of reaction, available in the chat
+//@message_ttl Current message Time To Live setting (self-destruct timer) for the chat; 0 if not defined. TTL is counted from the time message or its content is viewed in secret chats and from the send date in other chats
+//@theme_name If non-empty, name of a theme, set for the chat
+//@action_bar Information about actions which must be possible to do through the chat action bar; may be null
+//@video_chat Information about video chat of the chat
+//@pending_join_requests Information about pending join requests; may be null
//@reply_markup_message_id Identifier of the message from which reply markup needs to be used; 0 if there is no default custom reply markup in the chat
//@draft_message A draft of a message in the chat; may be null
-//@client_data Contains client-specific data associated with the chat. (For example, the chat position or local chat notification settings can be stored here.) Persistent if a message database is used
-chat id:int53 type:ChatType title:string photo:chatPhoto last_message:message order:int64 is_pinned:Bool can_be_reported:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 notification_settings:notificationSettings reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat;
+//@client_data Application-specific data associated with the chat. (For example, the chat scroll position or local chat notification settings can be stored here.) Persistent if the message database is used
+chat id:int53 type:ChatType title:string photo:chatPhotoInfo permissions:chatPermissions last_message:message positions:vector<chatPosition> message_sender_id:MessageSender has_protected_content:Bool is_marked_as_unread:Bool is_blocked:Bool has_scheduled_messages:Bool can_be_deleted_only_for_self:Bool can_be_deleted_for_all_users:Bool can_be_reported:Bool default_disable_notification:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 unread_reaction_count:int32 notification_settings:chatNotificationSettings available_reactions:ChatAvailableReactions message_ttl:int32 theme_name:string action_bar:ChatActionBar video_chat:videoChat pending_join_requests:chatJoinRequestsInfo reply_markup_message_id:int53 draft_message:draftMessage client_data:string = Chat;
-//@description Represents a list of chats @chat_ids List of chat identifiers
-chats chat_ids:vector<int53> = Chats;
+//@description Represents a list of chats @total_count Approximate total number of chats found @chat_ids List of chat identifiers
+chats total_count:int32 chat_ids:vector<int53> = Chats;
-//@description Contains a chat invite link @invite_link Chat invite link
-chatInviteLink invite_link:string = ChatInviteLink;
+//@description Describes a chat located nearby @chat_id Chat identifier @distance Distance to the chat location, in meters
+chatNearby chat_id:int53 distance:int32 = ChatNearby;
-//@description Contains information about a chat invite link
-//@chat_id Chat identifier of the invite link; 0 if the user is not a member of this chat
-//@type Contains information about the type of the chat
-//@title Title of the chat
-//@photo Chat photo; may be null
-//@member_count Number of members
-//@member_user_ids User identifiers of some chat members that may be known to the current user
-//@is_public True, if the chat is a public supergroup or channel with a username
-chatInviteLinkInfo chat_id:int53 type:ChatType title:string photo:chatPhoto member_count:int32 member_user_ids:vector<int32> is_public:Bool = ChatInviteLinkInfo;
+//@description Represents a list of chats located nearby @users_nearby List of users nearby @supergroups_nearby List of location-based supergroups nearby
+chatsNearby users_nearby:vector<chatNearby> supergroups_nearby:vector<chatNearby> = ChatsNearby;
+
+
+//@class PublicChatType @description Describes a type of public chats
+
+//@description The chat is public, because it has an active username
+publicChatTypeHasUsername = PublicChatType;
+
+//@description The chat is public, because it is a location-based supergroup
+publicChatTypeIsLocationBased = PublicChatType;
+
+
+//@class ChatActionBar @description Describes actions which must be possible to do through a chat action bar
+
+//@description The chat can be reported as spam using the method reportChat with the reason chatReportReasonSpam. If the chat is a private chat with a user with an emoji status, then a notice about emoji status usage must be shown
+//@can_unarchive If true, the chat was automatically archived and can be moved back to the main chat list using addChatToList simultaneously with setting chat notification settings to default using setChatNotificationSettings
+chatActionBarReportSpam can_unarchive:Bool = ChatActionBar;
+
+//@description The chat is a location-based supergroup, which can be reported as having unrelated location using the method reportChat with the reason chatReportReasonUnrelatedLocation
+chatActionBarReportUnrelatedLocation = ChatActionBar;
+
+//@description The chat is a recently created group chat to which new members can be invited
+chatActionBarInviteMembers = ChatActionBar;
+
+//@description The chat is a private or secret chat, which can be reported using the method reportChat, or the other user can be blocked using the method toggleMessageSenderIsBlocked, or the other user can be added to the contact list using the method addContact. If the chat is a private chat with a user with an emoji status, then a notice about emoji status usage must be shown
+//@can_unarchive If true, the chat was automatically archived and can be moved back to the main chat list using addChatToList simultaneously with setting chat notification settings to default using setChatNotificationSettings
+//@distance If non-negative, the current user was found by the peer through searchChatsNearby and this is the distance between the users
+chatActionBarReportAddBlock can_unarchive:Bool distance:int32 = ChatActionBar;
+
+//@description The chat is a private or secret chat and the other user can be added to the contact list using the method addContact
+chatActionBarAddContact = ChatActionBar;
+
+//@description The chat is a private or secret chat with a mutual contact and the user's phone number can be shared with the other user using the method sharePhoneNumber
+chatActionBarSharePhoneNumber = ChatActionBar;
+
+//@description The chat is a private chat with an administrator of a chat to which the user sent join request
+//@title Title of the chat to which the join request was sent
+//@is_channel True, if the join request was sent to a channel chat
+//@request_date Point in time (Unix timestamp) when the join request was sent
+chatActionBarJoinRequest title:string is_channel:Bool request_date:int32 = ChatActionBar;
//@class KeyboardButtonType @description Describes a keyboard button type
-//@description A simple button, with text that should be sent when the button is pressed
+//@description A simple button, with text that must be sent when the button is pressed
keyboardButtonTypeText = KeyboardButtonType;
//@description A button that sends the user's phone number when pressed; available only in private chats
@@ -568,6 +1212,12 @@ keyboardButtonTypeRequestPhoneNumber = KeyboardButtonType;
//@description A button that sends the user's location when pressed; available only in private chats
keyboardButtonTypeRequestLocation = KeyboardButtonType;
+//@description A button that allows the user to create and send a poll when pressed; available only in private chats @force_regular If true, only regular polls must be allowed to create @force_quiz If true, only polls in quiz mode must be allowed to create
+keyboardButtonTypeRequestPoll force_regular:Bool force_quiz:Bool = KeyboardButtonType;
+
+//@description A button that opens a Web App by calling getWebAppUrl @url An HTTP URL to pass to getWebAppUrl
+keyboardButtonTypeWebApp url:string = KeyboardButtonType;
+
//@description Represents a single button in a bot keyboard @text Text of the button @type Type of the button
keyboardButton text:string type:KeyboardButtonType = KeyboardButton;
@@ -575,21 +1225,33 @@ keyboardButton text:string type:KeyboardButtonType = KeyboardButton;
//@class InlineKeyboardButtonType @description Describes the type of an inline keyboard button
-//@description A button that opens a specified URL @url URL to open
+//@description A button that opens a specified URL @url HTTP or tg:// URL to open
inlineKeyboardButtonTypeUrl url:string = InlineKeyboardButtonType;
-//@description A button that sends a special callback query to a bot @data Data to be sent to the bot via a callback query
+//@description A button that opens a specified URL and automatically authorize the current user by calling getLoginUrlInfo @url An HTTP URL to pass to getLoginUrlInfo @id Unique button identifier @forward_text If non-empty, new text of the button in forwarded messages
+inlineKeyboardButtonTypeLoginUrl url:string id:int53 forward_text:string = InlineKeyboardButtonType;
+
+//@description A button that opens a Web App by calling openWebApp @url An HTTP URL to pass to openWebApp
+inlineKeyboardButtonTypeWebApp url:string = InlineKeyboardButtonType;
+
+//@description A button that sends a callback query to a bot @data Data to be sent to the bot via a callback query
inlineKeyboardButtonTypeCallback data:bytes = InlineKeyboardButtonType;
-//@description A button with a game that sends a special callback query to a bot. This button must be in the first column and row of the keyboard and can be attached only to a message with content of the type messageGame
+//@description A button that asks for the 2-step verification password of the current user and then sends a callback query to a bot @data Data to be sent to the bot via a callback query
+inlineKeyboardButtonTypeCallbackWithPassword data:bytes = InlineKeyboardButtonType;
+
+//@description A button with a game that sends a callback query to a bot. This button must be in the first column and row of the keyboard and can be attached only to a message with content of the type messageGame
inlineKeyboardButtonTypeCallbackGame = InlineKeyboardButtonType;
-//@description A button that forces an inline query to the bot to be inserted in the input field @query Inline query to be sent to the bot @in_current_chat True, if the inline query should be sent from the current chat
+//@description A button that forces an inline query to the bot to be inserted in the input field @query Inline query to be sent to the bot @in_current_chat True, if the inline query must be sent from the current chat
inlineKeyboardButtonTypeSwitchInline query:string in_current_chat:Bool = InlineKeyboardButtonType;
//@description A button to buy something. This button must be in the first column and row of the keyboard and can be attached only to a message with content of the type messageInvoice
inlineKeyboardButtonTypeBuy = InlineKeyboardButtonType;
+//@description A button with a user reference to be handled in the same way as textEntityTypeMentionName entities @user_id User identifier
+inlineKeyboardButtonTypeUser user_id:int53 = InlineKeyboardButtonType;
+
//@description Represents a single button in an inline keyboard @text Text of the button @type Type of the button
inlineKeyboardButton text:string type:InlineKeyboardButtonType = InlineKeyboardButton;
@@ -597,26 +1259,79 @@ inlineKeyboardButton text:string type:InlineKeyboardButtonType = InlineKeyboardB
//@class ReplyMarkup @description Contains a description of a custom keyboard and actions that can be done with it to quickly reply to bots
-//@description Instructs clients to remove the keyboard once this message has been received. This kind of keyboard can't be received in an incoming message; instead, UpdateChatReplyMarkup with message_id == 0 will be sent
+//@description Instructs application to remove the keyboard once this message has been received. This kind of keyboard can't be received in an incoming message; instead, UpdateChatReplyMarkup with message_id == 0 will be sent
//@is_personal True, if the keyboard is removed only for the mentioned users or the target user of a reply
replyMarkupRemoveKeyboard is_personal:Bool = ReplyMarkup;
-//@description Instructs clients to force a reply to this message
+//@description Instructs application to force a reply to this message
//@is_personal True, if a forced reply must automatically be shown to the current user. For outgoing messages, specify true to show the forced reply only for the mentioned users and for the target user of a reply
-replyMarkupForceReply is_personal:Bool = ReplyMarkup;
+//@input_field_placeholder If non-empty, the placeholder to be shown in the input field when the reply is active; 0-64 characters
+replyMarkupForceReply is_personal:Bool input_field_placeholder:string = ReplyMarkup;
//@description Contains a custom keyboard layout to quickly reply to bots
//@rows A list of rows of bot keyboard buttons
-//@resize_keyboard True, if the client needs to resize the keyboard vertically
-//@one_time True, if the client needs to hide the keyboard after use
+//@resize_keyboard True, if the application needs to resize the keyboard vertically
+//@one_time True, if the application needs to hide the keyboard after use
//@is_personal True, if the keyboard must automatically be shown to the current user. For outgoing messages, specify true to show the keyboard only for the mentioned users and for the target user of a reply
-replyMarkupShowKeyboard rows:vector<vector<keyboardButton>> resize_keyboard:Bool one_time:Bool is_personal:Bool = ReplyMarkup;
+//@input_field_placeholder If non-empty, the placeholder to be shown in the input field when the keyboard is active; 0-64 characters
+replyMarkupShowKeyboard rows:vector<vector<keyboardButton>> resize_keyboard:Bool one_time:Bool is_personal:Bool input_field_placeholder:string = ReplyMarkup;
//@description Contains an inline keyboard layout
//@rows A list of rows of inline keyboard buttons
replyMarkupInlineKeyboard rows:vector<vector<inlineKeyboardButton>> = ReplyMarkup;
+//@class LoginUrlInfo @description Contains information about an inline button of type inlineKeyboardButtonTypeLoginUrl
+
+//@description An HTTP url needs to be open @url The URL to open @skip_confirm True, if there is no need to show an ordinary open URL confirm
+loginUrlInfoOpen url:string skip_confirm:Bool = LoginUrlInfo;
+
+//@description An authorization confirmation dialog needs to be shown to the user @url An HTTP URL to be opened @domain A domain of the URL
+//@bot_user_id User identifier of a bot linked with the website @request_write_access True, if the user needs to be requested to give the permission to the bot to send them messages
+loginUrlInfoRequestConfirmation url:string domain:string bot_user_id:int53 request_write_access:Bool = LoginUrlInfo;
+
+
+//@description Contains information about a Web App @launch_id Unique identifier for the Web App launch @url A Web App URL to open in a web view
+webAppInfo launch_id:int64 url:string = WebAppInfo;
+
+
+//@description Contains information about a message thread
+//@chat_id Identifier of the chat to which the message thread belongs
+//@message_thread_id Message thread identifier, unique within the chat
+//@reply_info Information about the message thread; may be null for forum topic threads
+//@unread_message_count Approximate number of unread messages in the message thread
+//@messages The messages from which the thread starts. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id)
+//@draft_message A draft of a message in the message thread; may be null
+messageThreadInfo chat_id:int53 message_thread_id:int53 reply_info:messageReplyInfo unread_message_count:int32 messages:vector<message> draft_message:draftMessage = MessageThreadInfo;
+
+
+//@description Describes a forum topic icon @color Color of the topic icon in RGB format @custom_emoji_id Unique identifier of the custom emoji shown on the topic icon; 0 if none
+forumTopicIcon color:int32 custom_emoji_id:int64 = ForumTopicIcon;
+
+//@description Contains basic information about a forum topic
+//@message_thread_id Message thread identifier of the topic
+//@name Name of the topic
+//@icon Icon of the topic
+//@creation_date Date the topic was created
+//@creator_id Identifier of the creator of the topic
+//@is_outgoing True, if the topic was created by the current user
+//@is_closed True, if the topic is closed
+forumTopicInfo message_thread_id:int53 name:string icon:forumTopicIcon creation_date:int32 creator_id:MessageSender is_outgoing:Bool is_closed:Bool = ForumTopicInfo;
+
+//@description Describes a forum topic
+//@info Basic information about the topic
+//@last_message Last message in the topic; may be null
+//@is_pinned True, if the topic is pinned in the topic list
+//@unread_count Number of unread messages in the topic
+//@last_read_inbox_message_id Identifier of the last read incoming message
+//@last_read_outbox_message_id Identifier of the last read outgoing message
+//@unread_mention_count Number of unread messages with a mention/reply in the topic
+//@unread_reaction_count Number of messages with unread reactions in the topic
+//@notification_settings Notification settings for the topic
+//@draft_message A draft of a message in the topic; may be null
+forumTopic info:forumTopicInfo last_message:message is_pinned:Bool unread_count:int32 last_read_inbox_message_id:int53 last_read_outbox_message_id:int53 unread_mention_count:int32 unread_reaction_count:int32 notification_settings:chatNotificationSettings draft_message:draftMessage = ForumTopic;
+
+
//@class RichText @description Describes a text object inside an instant-view web page
//@description A plain text @text Text
@@ -631,22 +1346,86 @@ richTextItalic text:RichText = RichText;
//@description An underlined rich text @text Text
richTextUnderline text:RichText = RichText;
-//@description A strike-through rich text @text Text
+//@description A strikethrough rich text @text Text
richTextStrikethrough text:RichText = RichText;
//@description A fixed-width rich text @text Text
richTextFixed text:RichText = RichText;
-//@description A rich text URL link @text Text @url URL
-richTextUrl text:RichText url:string = RichText;
+//@description A rich text URL link @text Text @url URL @is_cached True, if the URL has cached instant view server-side
+richTextUrl text:RichText url:string is_cached:Bool = RichText;
//@description A rich text email link @text Text @email_address Email address
richTextEmailAddress text:RichText email_address:string = RichText;
+//@description A subscript rich text @text Text
+richTextSubscript text:RichText = RichText;
+
+//@description A superscript rich text @text Text
+richTextSuperscript text:RichText = RichText;
+
+//@description A marked rich text @text Text
+richTextMarked text:RichText = RichText;
+
+//@description A rich text phone number @text Text @phone_number Phone number
+richTextPhoneNumber text:RichText phone_number:string = RichText;
+
+//@description A small image inside the text @document The image represented as a document. The image can be in GIF, JPEG or PNG format
+//@width Width of a bounding box in which the image must be shown; 0 if unknown
+//@height Height of a bounding box in which the image must be shown; 0 if unknown
+richTextIcon document:document width:int32 height:int32 = RichText;
+
+//@description A reference to a richTexts object on the same web page @text The text @anchor_name The name of a richTextAnchor object, which is the first element of the target richTexts object @url An HTTP URL, opening the reference
+richTextReference text:RichText anchor_name:string url:string = RichText;
+
+//@description An anchor @name Anchor name
+richTextAnchor name:string = RichText;
+
+//@description A link to an anchor on the same web page @text The link text @anchor_name The anchor name. If the name is empty, the link must bring back to top @url An HTTP URL, opening the anchor
+richTextAnchorLink text:RichText anchor_name:string url:string = RichText;
+
//@description A concatenation of rich texts @texts Texts
richTexts texts:vector<RichText> = RichText;
+//@description Contains a caption of an instant view web page block, consisting of a text and a trailing credit @text Content of the caption @credit Block credit (like HTML tag <cite>)
+pageBlockCaption text:RichText credit:RichText = PageBlockCaption;
+
+//@description Describes an item of a list page block @label Item label @page_blocks Item blocks
+pageBlockListItem label:string page_blocks:vector<PageBlock> = PageBlockListItem;
+
+//@class PageBlockHorizontalAlignment @description Describes a horizontal alignment of a table cell content
+
+//@description The content must be left-aligned
+pageBlockHorizontalAlignmentLeft = PageBlockHorizontalAlignment;
+
+//@description The content must be center-aligned
+pageBlockHorizontalAlignmentCenter = PageBlockHorizontalAlignment;
+
+//@description The content must be right-aligned
+pageBlockHorizontalAlignmentRight = PageBlockHorizontalAlignment;
+
+//@class PageBlockVerticalAlignment @description Describes a Vertical alignment of a table cell content
+
+//@description The content must be top-aligned
+pageBlockVerticalAlignmentTop = PageBlockVerticalAlignment;
+
+//@description The content must be middle-aligned
+pageBlockVerticalAlignmentMiddle = PageBlockVerticalAlignment;
+
+//@description The content must be bottom-aligned
+pageBlockVerticalAlignmentBottom = PageBlockVerticalAlignment;
+
+//@description Represents a cell of a table @text Cell text; may be null. If the text is null, then the cell must be invisible @is_header True, if it is a header cell
+//@colspan The number of columns the cell spans @rowspan The number of rows the cell spans
+//@align Horizontal cell content alignment @valign Vertical cell content alignment
+pageBlockTableCell text:RichText is_header:Bool colspan:int32 rowspan:int32 align:PageBlockHorizontalAlignment valign:PageBlockVerticalAlignment = PageBlockTableCell;
+
+//@description Contains information about a related article @url Related article URL @title Article title; may be empty @param_description Article description; may be empty
+//@photo Article photo; may be null @author Article author; may be empty @publish_date Point in time (Unix timestamp) when the article was published; 0 if unknown
+pageBlockRelatedArticle url:string title:string description:string photo:photo author:string publish_date:int32 = PageBlockRelatedArticle;
+
+
//@class PageBlock @description Describes a block of an instant view web page
//@description The title of a page @title Title
@@ -664,10 +1443,13 @@ pageBlockHeader header:RichText = PageBlock;
//@description A subheader @subheader Subheader
pageBlockSubheader subheader:RichText = PageBlock;
+//@description A kicker @kicker Kicker
+pageBlockKicker kicker:RichText = PageBlock;
+
//@description A text paragraph @text Paragraph text
pageBlockParagraph text:RichText = PageBlock;
-//@description A preformatted text paragraph @text Paragraph text @language Programming language for which the text should be formatted
+//@description A preformatted text paragraph @text Paragraph text @language Programming language for which the text needs to be formatted
pageBlockPreformatted text:RichText language:string = PageBlock;
//@description The footer of a page @footer Footer
@@ -679,53 +1461,78 @@ pageBlockDivider = PageBlock;
//@description An invisible anchor on a page, which can be used in a URL to open the page from the specified anchor @name Name of the anchor
pageBlockAnchor name:string = PageBlock;
-//@description A list of texts @items Texts @is_ordered True, if the items should be marked with numbers
-pageBlockList items:vector<RichText> is_ordered:Bool = PageBlock;
+//@description A list of data blocks @items The items of the list
+pageBlockList items:vector<pageBlockListItem> = PageBlock;
-//@description A block quote @text Quote text @caption Quote caption
-pageBlockBlockQuote text:RichText caption:RichText = PageBlock;
+//@description A block quote @text Quote text @credit Quote credit
+pageBlockBlockQuote text:RichText credit:RichText = PageBlock;
-//@description A pull quote @text Quote text @caption Quote caption
-pageBlockPullQuote text:RichText caption:RichText = PageBlock;
+//@description A pull quote @text Quote text @credit Quote credit
+pageBlockPullQuote text:RichText credit:RichText = PageBlock;
-//@description An animation @animation Animation file; may be null @caption Animation caption @need_autoplay True, if the animation should be played automatically
-pageBlockAnimation animation:animation caption:RichText need_autoplay:Bool = PageBlock;
+//@description An animation @animation Animation file; may be null @caption Animation caption @need_autoplay True, if the animation must be played automatically
+pageBlockAnimation animation:animation caption:pageBlockCaption need_autoplay:Bool = PageBlock;
//@description An audio file @audio Audio file; may be null @caption Audio file caption
-pageBlockAudio audio:audio caption:RichText = PageBlock;
+pageBlockAudio audio:audio caption:pageBlockCaption = PageBlock;
-//@description A photo @photo Photo file; may be null @caption Photo caption
-pageBlockPhoto photo:photo caption:RichText = PageBlock;
+//@description A photo @photo Photo file; may be null @caption Photo caption @url URL that needs to be opened when the photo is clicked
+pageBlockPhoto photo:photo caption:pageBlockCaption url:string = PageBlock;
-//@description A video @video Video file; may be null @caption Video caption @need_autoplay True, if the video should be played automatically @is_looped True, if the video should be looped
-pageBlockVideo video:video caption:RichText need_autoplay:Bool is_looped:Bool = PageBlock;
+//@description A video @video Video file; may be null @caption Video caption @need_autoplay True, if the video must be played automatically @is_looped True, if the video must be looped
+pageBlockVideo video:video caption:pageBlockCaption need_autoplay:Bool is_looped:Bool = PageBlock;
+
+//@description A voice note @voice_note Voice note; may be null @caption Voice note caption
+pageBlockVoiceNote voice_note:voiceNote caption:pageBlockCaption = PageBlock;
//@description A page cover @cover Cover
pageBlockCover cover:PageBlock = PageBlock;
-//@description An embedded web page @url Web page URL, if available @html HTML-markup of the embedded page @poster_photo Poster photo, if available; may be null @width Block width @height Block height @caption Block caption @is_full_width True, if the block should be full width @allow_scrolling True, if scrolling should be allowed
-pageBlockEmbedded url:string html:string poster_photo:photo width:int32 height:int32 caption:RichText is_full_width:Bool allow_scrolling:Bool = PageBlock;
+//@description An embedded web page @url Web page URL, if available @html HTML-markup of the embedded page @poster_photo Poster photo, if available; may be null @width Block width; 0 if unknown @height Block height; 0 if unknown @caption Block caption @is_full_width True, if the block must be full width @allow_scrolling True, if scrolling needs to be allowed
+pageBlockEmbedded url:string html:string poster_photo:photo width:int32 height:int32 caption:pageBlockCaption is_full_width:Bool allow_scrolling:Bool = PageBlock;
-//@description An embedded post @url Web page URL @author Post author @author_photo Post author photo @date Point in time (Unix timestamp) when the post was created; 0 if unknown @page_blocks Post content @caption Post caption
-pageBlockEmbeddedPost url:string author:string author_photo:photo date:int32 page_blocks:vector<PageBlock> caption:RichText = PageBlock;
+//@description An embedded post @url Web page URL @author Post author @author_photo Post author photo; may be null @date Point in time (Unix timestamp) when the post was created; 0 if unknown @page_blocks Post content @caption Post caption
+pageBlockEmbeddedPost url:string author:string author_photo:photo date:int32 page_blocks:vector<PageBlock> caption:pageBlockCaption = PageBlock;
//@description A collage @page_blocks Collage item contents @caption Block caption
-pageBlockCollage page_blocks:vector<PageBlock> caption:RichText = PageBlock;
+pageBlockCollage page_blocks:vector<PageBlock> caption:pageBlockCaption = PageBlock;
//@description A slideshow @page_blocks Slideshow item contents @caption Block caption
-pageBlockSlideshow page_blocks:vector<PageBlock> caption:RichText = PageBlock;
+pageBlockSlideshow page_blocks:vector<PageBlock> caption:pageBlockCaption = PageBlock;
+
+//@description A link to a chat @title Chat title @photo Chat photo; may be null @username Chat username by which all other information about the chat can be resolved
+pageBlockChatLink title:string photo:chatPhotoInfo username:string = PageBlock;
+
+//@description A table @caption Table caption @cells Table cells @is_bordered True, if the table is bordered @is_striped True, if the table is striped
+pageBlockTable caption:RichText cells:vector<vector<pageBlockTableCell>> is_bordered:Bool is_striped:Bool = PageBlock;
+
+//@description A collapsible block @header Always visible heading for the block @page_blocks Block contents @is_open True, if the block is open by default
+pageBlockDetails header:RichText page_blocks:vector<PageBlock> is_open:Bool = PageBlock;
+
+//@description Related articles @header Block header @articles List of related articles
+pageBlockRelatedArticles header:RichText articles:vector<pageBlockRelatedArticle> = PageBlock;
-//@description A link to a chat @title Chat title @photo Chat photo; may be null @username Chat username, by which all other information about the chat should be resolved
-pageBlockChatLink title:string photo:chatPhoto username:string = PageBlock;
+//@description A map @location Location of the map center @zoom Map zoom level @width Map width @height Map height @caption Block caption
+pageBlockMap location:location zoom:int32 width:int32 height:int32 caption:pageBlockCaption = PageBlock;
-//@description Describes an instant view page for a web page @page_blocks Content of the web page @is_full True, if the instant view contains the full page. A network request might be needed to get the full web page instant view
-webPageInstantView page_blocks:vector<PageBlock> is_full:Bool = WebPageInstantView;
+//@description Describes an instant view page for a web page
+//@page_blocks Content of the web page
+//@view_count Number of the instant view views; 0 if unknown
+//@version Version of the instant view; currently, can be 1 or 2
+//@is_rtl True, if the instant view must be shown from right to left
+//@is_full True, if the instant view contains the full page. A network request might be needed to get the full web page instant view
+//@feedback_link An internal link to be opened to leave feedback about the instant view
+webPageInstantView page_blocks:vector<PageBlock> view_count:int32 version:int32 is_rtl:Bool is_full:Bool feedback_link:InternalLinkType = WebPageInstantView;
-//@description Describes a web page preview @url Original URL of the link @display_url URL to display
+//@description Describes a web page preview
+//@url Original URL of the link
+//@display_url URL to display
//@type Type of the web page. Can be: article, photo, audio, video, document, profile, app, or something else
-//@site_name Short name of the site (e.g., Google Docs, App Store) @title Title of the content @param_description Description of the content
+//@site_name Short name of the site (e.g., Google Docs, App Store)
+//@title Title of the content
+//@param_description Description of the content
//@photo Image representing the content; may be null
//@embed_url URL to show in the embedded preview
//@embed_type MIME type of the embedded preview, (e.g., text/html or video/mp4)
@@ -735,66 +1542,425 @@ webPageInstantView page_blocks:vector<PageBlock> is_full:Bool = WebPageInstantVi
//@author Author of the content
//@animation Preview of the content as an animation, if available; may be null
//@audio Preview of the content as an audio file, if available; may be null
-//@document Preview of the content as a document, if available (currently only available for small PDF files and ZIP archives); may be null
+//@document Preview of the content as a document, if available; may be null
//@sticker Preview of the content as a sticker for small WEBP files, if available; may be null
//@video Preview of the content as a video, if available; may be null
//@video_note Preview of the content as a video note, if available; may be null
//@voice_note Preview of the content as a voice note, if available; may be null
-//@has_instant_view True, if the web page has an instant view
-webPage url:string display_url:string type:string site_name:string title:string description:string photo:photo embed_url:string embed_type:string embed_width:int32 embed_height:int32 duration:int32 author:string animation:animation audio:audio document:document sticker:sticker video:video video_note:videoNote voice_note:voiceNote has_instant_view:Bool = WebPage;
+//@instant_view_version Version of instant view, available for the web page (currently, can be 1 or 2), 0 if none
+webPage url:string display_url:string type:string site_name:string title:string description:formattedText photo:photo embed_url:string embed_type:string embed_width:int32 embed_height:int32 duration:int32 author:string animation:animation audio:audio document:document sticker:sticker video:video video_note:videoNote voice_note:voiceNote instant_view_version:int32 = WebPage;
-//@description Portion of the price of a product (e.g., "delivery cost", "tax amount") @label Label for this portion of the product price @amount Currency amount in minimal quantity of the currency
-labeledPricePart label:string amount:int53 = LabeledPricePart;
+//@description Contains information about a country
+//@country_code A two-letter ISO 3166-1 alpha-2 country code
+//@name Native name of the country
+//@english_name English name of the country
+//@is_hidden True, if the country must be hidden from the list of all countries
+//@calling_codes List of country calling codes
+countryInfo country_code:string name:string english_name:string is_hidden:Bool calling_codes:vector<string> = CountryInfo;
+
+//@description Contains information about countries @countries The list of countries
+countries countries:vector<countryInfo> = Countries;
+
+//@description Contains information about a phone number
+//@country Information about the country to which the phone number belongs; may be null
+//@country_calling_code The part of the phone number denoting country calling code or its part
+//@formatted_phone_number The phone number without country calling code formatted accordingly to local rules. Expected digits are returned as '-', but even more digits might be entered by the user
+phoneNumberInfo country:countryInfo country_calling_code:string formatted_phone_number:string = PhoneNumberInfo;
+
+
+//@description Describes an action associated with a bank card number @text Action text @url The URL to be opened
+bankCardActionOpenUrl text:string url:string = BankCardActionOpenUrl;
-//@description Product invoice @currency ISO 4217 currency code @price_parts A list of objects used to calculate the total price of the product @is_test True, if the payment is a test payment
-//@need_name True, if the user's name is needed for payment @need_phone_number True, if the user's phone number is needed for payment @need_email_address True, if the user's email address is needed for payment
-//@need_shipping_address True, if the user's shipping address is needed for payment @send_phone_number_to_provider True, if the user's phone number will be sent to the provider
-//@send_email_address_to_provider True, if the user's email address will be sent to the provider @is_flexible True, if the total price depends on the shipping method
-invoice currency:string price_parts:vector<labeledPricePart> is_test:Bool need_name:Bool need_phone_number:Bool need_email_address:Bool need_shipping_address:Bool send_phone_number_to_provider:Bool send_email_address_to_provider:Bool is_flexible:Bool = Invoice;
+//@description Information about a bank card @title Title of the bank card description @actions Actions that can be done with the bank card number
+bankCardInfo title:string actions:vector<bankCardActionOpenUrl> = BankCardInfo;
-//@description Describes a shipping address @country_code Two-letter ISO 3166-1 alpha-2 country code @state State, if applicable @city City @street_line1 First line of the address @street_line2 Second line of the address @postal_code Address postal code
-shippingAddress country_code:string state:string city:string street_line1:string street_line2:string postal_code:string = ShippingAddress;
+
+//@description Describes an address @country_code A two-letter ISO 3166-1 alpha-2 country code @state State, if applicable @city City @street_line1 First line of the address @street_line2 Second line of the address @postal_code Address postal code
+address country_code:string state:string city:string street_line1:string street_line2:string postal_code:string = Address;
+
+
+//@description Contains parameters of the application theme @background_color A color of the background in the RGB24 format @secondary_background_color A secondary color for the background in the RGB24 format
+//@text_color A color of text in the RGB24 format @hint_color A color of hints in the RGB24 format @link_color A color of links in the RGB24 format @button_color A color of the buttons in the RGB24 format
+//@button_text_color A color of text on the buttons in the RGB24 format
+themeParameters background_color:int32 secondary_background_color:int32 text_color:int32 hint_color:int32 link_color:int32 button_color:int32 button_text_color:int32 = ThemeParameters;
+
+
+//@description Portion of the price of a product (e.g., "delivery cost", "tax amount") @label Label for this portion of the product price @amount Currency amount in the smallest units of the currency
+labeledPricePart label:string amount:int53 = LabeledPricePart;
+
+//@description Product invoice
+//@currency ISO 4217 currency code
+//@price_parts A list of objects used to calculate the total price of the product
+//@max_tip_amount The maximum allowed amount of tip in the smallest units of the currency
+//@suggested_tip_amounts Suggested amounts of tip in the smallest units of the currency
+//@recurring_payment_terms_of_service_url An HTTP URL with terms of service for recurring payments. If non-empty, the invoice payment will result in recurring payments and the user must accept the terms of service before allowed to pay
+//@is_test True, if the payment is a test payment
+//@need_name True, if the user's name is needed for payment
+//@need_phone_number True, if the user's phone number is needed for payment
+//@need_email_address True, if the user's email address is needed for payment
+//@need_shipping_address True, if the user's shipping address is needed for payment
+//@send_phone_number_to_provider True, if the user's phone number will be sent to the provider
+//@send_email_address_to_provider True, if the user's email address will be sent to the provider
+//@is_flexible True, if the total price depends on the shipping method
+invoice currency:string price_parts:vector<labeledPricePart> max_tip_amount:int53 suggested_tip_amounts:vector<int53> recurring_payment_terms_of_service_url:string is_test:Bool need_name:Bool need_phone_number:Bool need_email_address:Bool need_shipping_address:Bool send_phone_number_to_provider:Bool send_email_address_to_provider:Bool is_flexible:Bool = Invoice;
//@description Order information @name Name of the user @phone_number Phone number of the user @email_address Email address of the user @shipping_address Shipping address for this order; may be null
-orderInfo name:string phone_number:string email_address:string shipping_address:shippingAddress = OrderInfo;
+orderInfo name:string phone_number:string email_address:string shipping_address:address = OrderInfo;
//@description One shipping option @id Shipping option identifier @title Option title @price_parts A list of objects used to calculate the total shipping costs
shippingOption id:string title:string price_parts:vector<labeledPricePart> = ShippingOption;
-//@description Contains information about saved card credentials @id Unique identifier of the saved credentials @title Title of the saved credentials
+//@description Contains information about saved payment credentials @id Unique identifier of the saved credentials @title Title of the saved credentials
savedCredentials id:string title:string = SavedCredentials;
+
//@class InputCredentials @description Contains information about the payment method chosen by the user
//@description Applies if a user chooses some previously saved payment credentials. To use their previously saved credentials, the user must have a valid temporary password @saved_credentials_id Identifier of the saved credentials
inputCredentialsSaved saved_credentials_id:string = InputCredentials;
-//@description Applies if a user enters new credentials on a payment provider website @data Contains JSON-encoded data with a credential identifier from the payment provider @allow_save True, if the credential identifier can be saved on the server side
+//@description Applies if a user enters new credentials on a payment provider website @data JSON-encoded data with the credential identifier from the payment provider @allow_save True, if the credential identifier can be saved on the server side
inputCredentialsNew data:string allow_save:Bool = InputCredentials;
-//@description Applies if a user enters new credentials using Android Pay @data JSON-encoded data with the credential identifier
-inputCredentialsAndroidPay data:string = InputCredentials;
-
//@description Applies if a user enters new credentials using Apple Pay @data JSON-encoded data with the credential identifier
inputCredentialsApplePay data:string = InputCredentials;
+//@description Applies if a user enters new credentials using Google Pay @data JSON-encoded data with the credential identifier
+inputCredentialsGooglePay data:string = InputCredentials;
+
+
+//@class PaymentProvider @description Contains information about a payment provider
+
+//@description Smart Glocal payment provider @public_token Public payment token
+paymentProviderSmartGlocal public_token:string = PaymentProvider;
+
//@description Stripe payment provider @publishable_key Stripe API publishable key @need_country True, if the user country must be provided @need_postal_code True, if the user ZIP/postal code must be provided @need_cardholder_name True, if the cardholder name must be provided
-paymentsProviderStripe publishable_key:string need_country:Bool need_postal_code:Bool need_cardholder_name:Bool = PaymentsProviderStripe;
+paymentProviderStripe publishable_key:string need_country:Bool need_postal_code:Bool need_cardholder_name:Bool = PaymentProvider;
+
+//@description Some other payment provider, for which a web payment form must be shown @url Payment form URL
+paymentProviderOther url:string = PaymentProvider;
-//@description Contains information about an invoice payment form @invoice Full information of the invoice @url Payment form URL @payments_provider Contains information about the payment provider, if available, to support it natively without the need for opening the URL; may be null
-//@saved_order_info Saved server-side order information; may be null @saved_credentials Contains information about saved card credentials; may be null @can_save_credentials True, if the user can choose to save credentials @need_password True, if the user will be able to save credentials protected by a password they set up
-paymentForm invoice:invoice url:string payments_provider:paymentsProviderStripe saved_order_info:orderInfo saved_credentials:savedCredentials can_save_credentials:Bool need_password:Bool = PaymentForm;
+
+//@description Describes an additional payment option @title Title for the payment option @url Payment form URL to be opened in a web view
+paymentOption title:string url:string = PaymentOption;
+
+
+//@description Contains information about an invoice payment form
+//@id The payment form identifier
+//@invoice Full information about the invoice
+//@seller_bot_user_id User identifier of the seller bot
+//@payment_provider_user_id User identifier of the payment provider bot
+//@payment_provider Information about the payment provider
+//@additional_payment_options The list of additional payment options
+//@saved_order_info Saved server-side order information; may be null
+//@saved_credentials The list of saved payment credentials
+//@can_save_credentials True, if the user can choose to save credentials
+//@need_password True, if the user will be able to save credentials, if sets up a 2-step verification password
+//@product_title Product title
+//@product_description Product description
+//@product_photo Product photo; may be null
+paymentForm id:int64 invoice:invoice seller_bot_user_id:int53 payment_provider_user_id:int53 payment_provider:PaymentProvider additional_payment_options:vector<paymentOption> saved_order_info:orderInfo saved_credentials:vector<savedCredentials> can_save_credentials:Bool need_password:Bool product_title:string product_description:formattedText product_photo:photo = PaymentForm;
//@description Contains a temporary identifier of validated order information, which is stored for one hour. Also contains the available shipping options @order_info_id Temporary identifier of the order information @shipping_options Available shipping options
validatedOrderInfo order_info_id:string shipping_options:vector<shippingOption> = ValidatedOrderInfo;
-//@description Contains the result of a payment request @success True, if the payment request was successful; otherwise the verification_url will be not empty @verification_url URL for additional payment credentials verification
+//@description Contains the result of a payment request @success True, if the payment request was successful; otherwise the verification_url will be non-empty @verification_url URL for additional payment credentials verification
paymentResult success:Bool verification_url:string = PaymentResult;
-//@description Contains information about a successful payment @date Point in time (Unix timestamp) when the payment was made @payments_provider_user_id User identifier of the payment provider bot @invoice Contains information about the invoice
-//@order_info Contains order information; may be null @shipping_option Chosen shipping option; may be null @credentials_title Title of the saved credentials
-paymentReceipt date:int32 payments_provider_user_id:int32 invoice:invoice order_info:orderInfo shipping_option:shippingOption credentials_title:string = PaymentReceipt;
+//@description Contains information about a successful payment
+//@title Product title
+//@param_description Product description
+//@photo Product photo; may be null
+//@date Point in time (Unix timestamp) when the payment was made
+//@seller_bot_user_id User identifier of the seller bot
+//@payment_provider_user_id User identifier of the payment provider bot
+//@invoice Information about the invoice
+//@order_info Order information; may be null
+//@shipping_option Chosen shipping option; may be null
+//@credentials_title Title of the saved credentials chosen by the buyer
+//@tip_amount The amount of tip chosen by the buyer in the smallest units of the currency
+paymentReceipt title:string description:formattedText photo:photo date:int32 seller_bot_user_id:int53 payment_provider_user_id:int53 invoice:invoice order_info:orderInfo shipping_option:shippingOption credentials_title:string tip_amount:int53 = PaymentReceipt;
+
+
+//@class InputInvoice @description Describes an invoice to process
+
+//@description An invoice from a message of the type messageInvoice @chat_id Chat identifier of the message @message_id Message identifier
+inputInvoiceMessage chat_id:int53 message_id:int53 = InputInvoice;
+
+//@description An invoice from a link of the type internalLinkTypeInvoice @name Name of the invoice
+inputInvoiceName name:string = InputInvoice;
+
+
+//@class MessageExtendedMedia @description Describes a media, which is attached to an invoice
+
+//@description The media is hidden until the invoice is paid
+//@width Media width; 0 if unknown
+//@height Media height; 0 if unknown
+//@duration Media duration; 0 if unknown
+//@minithumbnail Media minithumbnail; may be null
+//@caption Media caption
+messageExtendedMediaPreview width:int32 height:int32 duration:int32 minithumbnail:minithumbnail caption:formattedText = MessageExtendedMedia;
+
+//@description The media is a photo @photo The photo @caption Photo caption
+messageExtendedMediaPhoto photo:photo caption:formattedText = MessageExtendedMedia;
+
+//@description The media is a video @video The video @caption Photo caption
+messageExtendedMediaVideo video:video caption:formattedText = MessageExtendedMedia;
+
+//@description The media is unuspported @caption Media caption
+messageExtendedMediaUnsupported caption:formattedText = MessageExtendedMedia;
+
+
+
+//@description File with the date it was uploaded @file The file @date Point in time (Unix timestamp) when the file was uploaded
+datedFile file:file date:int32 = DatedFile;
+
+
+//@class PassportElementType @description Contains the type of a Telegram Passport element
+
+//@description A Telegram Passport element containing the user's personal details
+passportElementTypePersonalDetails = PassportElementType;
+
+//@description A Telegram Passport element containing the user's passport
+passportElementTypePassport = PassportElementType;
+
+//@description A Telegram Passport element containing the user's driver license
+passportElementTypeDriverLicense = PassportElementType;
+
+//@description A Telegram Passport element containing the user's identity card
+passportElementTypeIdentityCard = PassportElementType;
+
+//@description A Telegram Passport element containing the user's internal passport
+passportElementTypeInternalPassport = PassportElementType;
+
+//@description A Telegram Passport element containing the user's address
+passportElementTypeAddress = PassportElementType;
+
+//@description A Telegram Passport element containing the user's utility bill
+passportElementTypeUtilityBill = PassportElementType;
+
+//@description A Telegram Passport element containing the user's bank statement
+passportElementTypeBankStatement = PassportElementType;
+
+//@description A Telegram Passport element containing the user's rental agreement
+passportElementTypeRentalAgreement = PassportElementType;
+
+//@description A Telegram Passport element containing the registration page of the user's passport
+passportElementTypePassportRegistration = PassportElementType;
+
+//@description A Telegram Passport element containing the user's temporary registration
+passportElementTypeTemporaryRegistration = PassportElementType;
+
+//@description A Telegram Passport element containing the user's phone number
+passportElementTypePhoneNumber = PassportElementType;
+
+//@description A Telegram Passport element containing the user's email address
+passportElementTypeEmailAddress = PassportElementType;
+
+
+//@description Represents a date according to the Gregorian calendar @day Day of the month; 1-31 @month Month; 1-12 @year Year; 1-9999
+date day:int32 month:int32 year:int32 = Date;
+
+//@description Contains the user's personal details
+//@first_name First name of the user written in English; 1-255 characters @middle_name Middle name of the user written in English; 0-255 characters @last_name Last name of the user written in English; 1-255 characters
+//@native_first_name Native first name of the user; 1-255 characters @native_middle_name Native middle name of the user; 0-255 characters @native_last_name Native last name of the user; 1-255 characters
+//@birthdate Birthdate of the user @gender Gender of the user, "male" or "female" @country_code A two-letter ISO 3166-1 alpha-2 country code of the user's country @residence_country_code A two-letter ISO 3166-1 alpha-2 country code of the user's residence country
+personalDetails first_name:string middle_name:string last_name:string native_first_name:string native_middle_name:string native_last_name:string birthdate:date gender:string country_code:string residence_country_code:string = PersonalDetails;
+
+//@description An identity document @number Document number; 1-24 characters @expiry_date Document expiry date; may be null if not applicable @front_side Front side of the document
+//@reverse_side Reverse side of the document; only for driver license and identity card; may be null @selfie Selfie with the document; may be null @translation List of files containing a certified English translation of the document
+identityDocument number:string expiry_date:date front_side:datedFile reverse_side:datedFile selfie:datedFile translation:vector<datedFile> = IdentityDocument;
+
+//@description An identity document to be saved to Telegram Passport @number Document number; 1-24 characters @expiry_date Document expiry date; pass null if not applicable @front_side Front side of the document
+//@reverse_side Reverse side of the document; only for driver license and identity card; pass null otherwise @selfie Selfie with the document; pass null if unavailable @translation List of files containing a certified English translation of the document
+inputIdentityDocument number:string expiry_date:date front_side:InputFile reverse_side:InputFile selfie:InputFile translation:vector<InputFile> = InputIdentityDocument;
+
+//@description A personal document, containing some information about a user @files List of files containing the pages of the document @translation List of files containing a certified English translation of the document
+personalDocument files:vector<datedFile> translation:vector<datedFile> = PersonalDocument;
+
+//@description A personal document to be saved to Telegram Passport @files List of files containing the pages of the document @translation List of files containing a certified English translation of the document
+inputPersonalDocument files:vector<InputFile> translation:vector<InputFile> = InputPersonalDocument;
+
+
+//@class PassportElement @description Contains information about a Telegram Passport element
+
+//@description A Telegram Passport element containing the user's personal details @personal_details Personal details of the user
+passportElementPersonalDetails personal_details:personalDetails = PassportElement;
+
+//@description A Telegram Passport element containing the user's passport @passport Passport
+passportElementPassport passport:identityDocument = PassportElement;
+
+//@description A Telegram Passport element containing the user's driver license @driver_license Driver license
+passportElementDriverLicense driver_license:identityDocument = PassportElement;
+
+//@description A Telegram Passport element containing the user's identity card @identity_card Identity card
+passportElementIdentityCard identity_card:identityDocument = PassportElement;
+
+//@description A Telegram Passport element containing the user's internal passport @internal_passport Internal passport
+passportElementInternalPassport internal_passport:identityDocument = PassportElement;
+
+//@description A Telegram Passport element containing the user's address @address Address
+passportElementAddress address:address = PassportElement;
+
+//@description A Telegram Passport element containing the user's utility bill @utility_bill Utility bill
+passportElementUtilityBill utility_bill:personalDocument = PassportElement;
+
+//@description A Telegram Passport element containing the user's bank statement @bank_statement Bank statement
+passportElementBankStatement bank_statement:personalDocument = PassportElement;
+
+//@description A Telegram Passport element containing the user's rental agreement @rental_agreement Rental agreement
+passportElementRentalAgreement rental_agreement:personalDocument = PassportElement;
+
+//@description A Telegram Passport element containing the user's passport registration pages @passport_registration Passport registration pages
+passportElementPassportRegistration passport_registration:personalDocument = PassportElement;
+
+//@description A Telegram Passport element containing the user's temporary registration @temporary_registration Temporary registration
+passportElementTemporaryRegistration temporary_registration:personalDocument = PassportElement;
+
+//@description A Telegram Passport element containing the user's phone number @phone_number Phone number
+passportElementPhoneNumber phone_number:string = PassportElement;
+
+//@description A Telegram Passport element containing the user's email address @email_address Email address
+passportElementEmailAddress email_address:string = PassportElement;
+
+
+//@class InputPassportElement @description Contains information about a Telegram Passport element to be saved
+
+//@description A Telegram Passport element to be saved containing the user's personal details @personal_details Personal details of the user
+inputPassportElementPersonalDetails personal_details:personalDetails = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's passport @passport The passport to be saved
+inputPassportElementPassport passport:inputIdentityDocument = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's driver license @driver_license The driver license to be saved
+inputPassportElementDriverLicense driver_license:inputIdentityDocument = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's identity card @identity_card The identity card to be saved
+inputPassportElementIdentityCard identity_card:inputIdentityDocument = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's internal passport @internal_passport The internal passport to be saved
+inputPassportElementInternalPassport internal_passport:inputIdentityDocument = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's address @address The address to be saved
+inputPassportElementAddress address:address = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's utility bill @utility_bill The utility bill to be saved
+inputPassportElementUtilityBill utility_bill:inputPersonalDocument = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's bank statement @bank_statement The bank statement to be saved
+inputPassportElementBankStatement bank_statement:inputPersonalDocument = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's rental agreement @rental_agreement The rental agreement to be saved
+inputPassportElementRentalAgreement rental_agreement:inputPersonalDocument = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's passport registration @passport_registration The passport registration page to be saved
+inputPassportElementPassportRegistration passport_registration:inputPersonalDocument = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's temporary registration @temporary_registration The temporary registration document to be saved
+inputPassportElementTemporaryRegistration temporary_registration:inputPersonalDocument = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's phone number @phone_number The phone number to be saved
+inputPassportElementPhoneNumber phone_number:string = InputPassportElement;
+
+//@description A Telegram Passport element to be saved containing the user's email address @email_address The email address to be saved
+inputPassportElementEmailAddress email_address:string = InputPassportElement;
+
+
+//@description Contains information about saved Telegram Passport elements @elements Telegram Passport elements
+passportElements elements:vector<PassportElement> = PassportElements;
+
+
+//@class PassportElementErrorSource @description Contains the description of an error in a Telegram Passport element
+
+//@description The element contains an error in an unspecified place. The error will be considered resolved when new data is added
+passportElementErrorSourceUnspecified = PassportElementErrorSource;
+
+//@description One of the data fields contains an error. The error will be considered resolved when the value of the field changes @field_name Field name
+passportElementErrorSourceDataField field_name:string = PassportElementErrorSource;
+
+//@description The front side of the document contains an error. The error will be considered resolved when the file with the front side changes
+passportElementErrorSourceFrontSide = PassportElementErrorSource;
+
+//@description The reverse side of the document contains an error. The error will be considered resolved when the file with the reverse side changes
+passportElementErrorSourceReverseSide = PassportElementErrorSource;
+
+//@description The selfie with the document contains an error. The error will be considered resolved when the file with the selfie changes
+passportElementErrorSourceSelfie = PassportElementErrorSource;
+
+//@description One of files with the translation of the document contains an error. The error will be considered resolved when the file changes @file_index Index of a file with the error
+passportElementErrorSourceTranslationFile file_index:int32 = PassportElementErrorSource;
+
+//@description The translation of the document contains an error. The error will be considered resolved when the list of translation files changes
+passportElementErrorSourceTranslationFiles = PassportElementErrorSource;
+
+//@description The file contains an error. The error will be considered resolved when the file changes @file_index Index of a file with the error
+passportElementErrorSourceFile file_index:int32 = PassportElementErrorSource;
+
+//@description The list of attached files contains an error. The error will be considered resolved when the list of files changes
+passportElementErrorSourceFiles = PassportElementErrorSource;
+
+
+//@description Contains the description of an error in a Telegram Passport element @type Type of the Telegram Passport element which has the error @message Error message @source Error source
+passportElementError type:PassportElementType message:string source:PassportElementErrorSource = PassportElementError;
+
+
+//@description Contains information about a Telegram Passport element that was requested by a service @type Type of the element @is_selfie_required True, if a selfie is required with the identity document
+//@is_translation_required True, if a certified English translation is required with the document @is_native_name_required True, if personal details must include the user's name in the language of their country of residence
+passportSuitableElement type:PassportElementType is_selfie_required:Bool is_translation_required:Bool is_native_name_required:Bool = PassportSuitableElement;
+
+//@description Contains a description of the required Telegram Passport element that was requested by a service @suitable_elements List of Telegram Passport elements any of which is enough to provide
+passportRequiredElement suitable_elements:vector<passportSuitableElement> = PassportRequiredElement;
+
+//@description Contains information about a Telegram Passport authorization form that was requested @id Unique identifier of the authorization form
+//@required_elements Telegram Passport elements that must be provided to complete the form
+//@privacy_policy_url URL for the privacy policy of the service; may be empty
+passportAuthorizationForm id:int32 required_elements:vector<passportRequiredElement> privacy_policy_url:string = PassportAuthorizationForm;
+
+//@description Contains information about a Telegram Passport elements and corresponding errors @elements Telegram Passport elements @errors Errors in the elements that are already available
+passportElementsWithErrors elements:vector<PassportElement> errors:vector<passportElementError> = PassportElementsWithErrors;
+
+
+//@description Contains encrypted Telegram Passport data credentials @data The encrypted credentials @hash The decrypted data hash @secret Secret for data decryption, encrypted with the service's public key
+encryptedCredentials data:bytes hash:bytes secret:bytes = EncryptedCredentials;
+
+
+//@description Contains information about an encrypted Telegram Passport element; for bots only @type Type of Telegram Passport element @data Encrypted JSON-encoded data about the user @front_side The front side of an identity document @reverse_side The reverse side of an identity document; may be null @selfie Selfie with the document; may be null @translation List of files containing a certified English translation of the document @files List of attached files @value Unencrypted data, phone number or email address @hash Hash of the entire element
+encryptedPassportElement type:PassportElementType data:bytes front_side:datedFile reverse_side:datedFile selfie:datedFile translation:vector<datedFile> files:vector<datedFile> value:string hash:string = EncryptedPassportElement;
+
+
+//@class InputPassportElementErrorSource @description Contains the description of an error in a Telegram Passport element; for bots only
+
+//@description The element contains an error in an unspecified place. The error will be considered resolved when new data is added @element_hash Current hash of the entire element
+inputPassportElementErrorSourceUnspecified element_hash:bytes = InputPassportElementErrorSource;
+
+//@description A data field contains an error. The error is considered resolved when the field's value changes @field_name Field name @data_hash Current data hash
+inputPassportElementErrorSourceDataField field_name:string data_hash:bytes = InputPassportElementErrorSource;
+
+//@description The front side of the document contains an error. The error is considered resolved when the file with the front side of the document changes @file_hash Current hash of the file containing the front side
+inputPassportElementErrorSourceFrontSide file_hash:bytes = InputPassportElementErrorSource;
+
+//@description The reverse side of the document contains an error. The error is considered resolved when the file with the reverse side of the document changes @file_hash Current hash of the file containing the reverse side
+inputPassportElementErrorSourceReverseSide file_hash:bytes = InputPassportElementErrorSource;
+
+//@description The selfie contains an error. The error is considered resolved when the file with the selfie changes @file_hash Current hash of the file containing the selfie
+inputPassportElementErrorSourceSelfie file_hash:bytes = InputPassportElementErrorSource;
+
+//@description One of the files containing the translation of the document contains an error. The error is considered resolved when the file with the translation changes @file_hash Current hash of the file containing the translation
+inputPassportElementErrorSourceTranslationFile file_hash:bytes = InputPassportElementErrorSource;
+
+//@description The translation of the document contains an error. The error is considered resolved when the list of files changes @file_hashes Current hashes of all files with the translation
+inputPassportElementErrorSourceTranslationFiles file_hashes:vector<bytes> = InputPassportElementErrorSource;
+
+//@description The file contains an error. The error is considered resolved when the file changes @file_hash Current hash of the file which has the error
+inputPassportElementErrorSourceFile file_hash:bytes = InputPassportElementErrorSource;
+
+//@description The list of attached files contains an error. The error is considered resolved when the file list changes @file_hashes Current hashes of all attached files
+inputPassportElementErrorSourceFiles file_hashes:vector<bytes> = InputPassportElementErrorSource;
+
+
+//@description Contains the description of an error in a Telegram Passport element; for bots only @type Type of Telegram Passport element that has the error @message Error message @source Error source
+inputPassportElementError type:PassportElementType message:string source:InputPassportElementErrorSource = InputPassportElementError;
//@class MessageContent @description Contains the content of a message
@@ -802,59 +1968,88 @@ paymentReceipt date:int32 payments_provider_user_id:int32 invoice:invoice order_
//@description A text message @text Text of the message @web_page A preview of the web page that's mentioned in the text; may be null
messageText text:formattedText web_page:webPage = MessageContent;
-//@description An animation message (GIF-style). @animation Message content @caption Animation caption @is_secret True, if the animation thumbnail must be blurred and the animation must be shown only while tapped
+//@description An animation message (GIF-style). @animation The animation description @caption Animation caption @is_secret True, if the animation thumbnail must be blurred and the animation must be shown only while tapped
messageAnimation animation:animation caption:formattedText is_secret:Bool = MessageContent;
-//@description An audio message @audio Message content @caption Audio caption
+//@description An audio message @audio The audio description @caption Audio caption
messageAudio audio:audio caption:formattedText = MessageContent;
-//@description A document message (general file) @document Message content @caption Document caption
+//@description A document message (general file) @document The document description @caption Document caption
messageDocument document:document caption:formattedText = MessageContent;
-//@description A photo message @photo Message content @caption Photo caption @is_secret True, if the photo must be blurred and must be shown only while tapped
+//@description A photo message @photo The photo description @caption Photo caption @is_secret True, if the photo must be blurred and must be shown only while tapped
messagePhoto photo:photo caption:formattedText is_secret:Bool = MessageContent;
//@description An expired photo message (self-destructed after TTL has elapsed)
messageExpiredPhoto = MessageContent;
-//@description A sticker message @sticker Message content
-messageSticker sticker:sticker = MessageContent;
+//@description A sticker message @sticker The sticker description @is_premium True, if premium animation of the sticker must be played
+messageSticker sticker:sticker is_premium:Bool = MessageContent;
-//@description A video message @video Message content @caption Video caption @is_secret True, if the video thumbnail must be blurred and the video must be shown only while tapped
+//@description A video message @video The video description @caption Video caption @is_secret True, if the video thumbnail must be blurred and the video must be shown only while tapped
messageVideo video:video caption:formattedText is_secret:Bool = MessageContent;
//@description An expired video message (self-destructed after TTL has elapsed)
messageExpiredVideo = MessageContent;
-//@description A video note message @video_note Message content @is_viewed True, if at least one of the recipients has viewed the video note @is_secret True, if the video note thumbnail must be blurred and the video note must be shown only while tapped
+//@description A video note message @video_note The video note description @is_viewed True, if at least one of the recipients has viewed the video note @is_secret True, if the video note thumbnail must be blurred and the video note must be shown only while tapped
messageVideoNote video_note:videoNote is_viewed:Bool is_secret:Bool = MessageContent;
-//@description A voice note message @voice_note Message content @caption Voice note caption @is_listened True, if at least one of the recipients has listened to the voice note
+//@description A voice note message @voice_note The voice note description @caption Voice note caption @is_listened True, if at least one of the recipients has listened to the voice note
messageVoiceNote voice_note:voiceNote caption:formattedText is_listened:Bool = MessageContent;
-//@description A message with a location @location Message content @live_period Time relative to the message sent date until which the location can be updated, in seconds
+//@description A message with a location @location The location description @live_period Time relative to the message send date, for which the location can be updated, in seconds
//@expires_in Left time for which the location can be updated, in seconds. updateMessageContent is not sent when this field changes
-messageLocation location:location live_period:int32 expires_in:int32 = MessageContent;
+//@heading For live locations, a direction in which the location moves, in degrees; 1-360. If 0 the direction is unknown
+//@proximity_alert_radius For live locations, a maximum distance to another chat member for proximity alerts, in meters (0-100000). 0 if the notification is disabled. Available only for the message sender
+messageLocation location:location live_period:int32 expires_in:int32 heading:int32 proximity_alert_radius:int32 = MessageContent;
-//@description A message with information about a venue @venue Message content
+//@description A message with information about a venue @venue The venue description
messageVenue venue:venue = MessageContent;
-//@description A message with a user contact @contact Message content
+//@description A message with a user contact @contact The contact description
messageContact contact:contact = MessageContent;
-//@description A message with a game @game Game
+//@description A message with an animated emoji @animated_emoji The animated emoji @emoji The corresponding emoji
+messageAnimatedEmoji animated_emoji:animatedEmoji emoji:string = MessageContent;
+
+//@description A dice message. The dice value is randomly generated by the server
+//@initial_state The animated stickers with the initial dice animation; may be null if unknown. updateMessageContent will be sent when the sticker became known
+//@final_state The animated stickers with the final dice animation; may be null if unknown. updateMessageContent will be sent when the sticker became known
+//@emoji Emoji on which the dice throw animation is based
+//@value The dice value. If the value is 0, the dice don't have final state yet
+//@success_animation_frame_number Number of frame after which a success animation like a shower of confetti needs to be shown on updateMessageSendSucceeded
+messageDice initial_state:DiceStickers final_state:DiceStickers emoji:string value:int32 success_animation_frame_number:int32 = MessageContent;
+
+//@description A message with a game @game The game description
messageGame game:game = MessageContent;
-//@description A message with an invoice from a bot @title Product title @param_description Product description @photo Product photo; may be null @currency Currency for the product price @total_amount Product total price in the minimal quantity of the currency
+//@description A message with a poll @poll The poll description
+messagePoll poll:poll = MessageContent;
+
+//@description A message with an invoice from a bot @title Product title @param_description Product description @photo Product photo; may be null @currency Currency for the product price @total_amount Product total price in the smallest units of the currency
//@start_parameter Unique invoice bot start_parameter. To share an invoice use the URL https://t.me/{bot_username}?start={start_parameter} @is_test True, if the invoice is a test invoice
-//@need_shipping_address True, if the shipping address should be specified @receipt_message_id The identifier of the message with the receipt, after the product has been purchased
-messageInvoice title:string description:string photo:photo currency:string total_amount:int53 start_parameter:string is_test:Bool need_shipping_address:Bool receipt_message_id:int53 = MessageContent;
+//@need_shipping_address True, if the shipping address must be specified @receipt_message_id The identifier of the message with the receipt, after the product has been purchased
+//@extended_media Extended media attached to the invoice; may be null
+messageInvoice title:string description:formattedText photo:photo currency:string total_amount:int53 start_parameter:string is_test:Bool need_shipping_address:Bool receipt_message_id:int53 extended_media:MessageExtendedMedia = MessageContent;
+
+//@description A message with information about an ended call @is_video True, if the call was a video call @discard_reason Reason why the call was discarded @duration Call duration, in seconds
+messageCall is_video:Bool discard_reason:CallDiscardReason duration:int32 = MessageContent;
+
+//@description A new video chat was scheduled @group_call_id Identifier of the video chat. The video chat can be received through the method getGroupCall @start_date Point in time (Unix timestamp) when the group call is supposed to be started by an administrator
+messageVideoChatScheduled group_call_id:int32 start_date:int32 = MessageContent;
-//@description A message with information about an ended call @discard_reason Reason why the call was discarded @duration Call duration, in seconds
-messageCall discard_reason:CallDiscardReason duration:int32 = MessageContent;
+//@description A newly created video chat @group_call_id Identifier of the video chat. The video chat can be received through the method getGroupCall
+messageVideoChatStarted group_call_id:int32 = MessageContent;
+
+//@description A message with information about an ended video chat @duration Call duration, in seconds
+messageVideoChatEnded duration:int32 = MessageContent;
+
+//@description A message with information about an invite to a video chat @group_call_id Identifier of the video chat. The video chat can be received through the method getGroupCall @user_ids Invited user identifiers
+messageInviteVideoChatParticipants group_call_id:int32 user_ids:vector<int53> = MessageContent;
//@description A newly created basic group @title Title of the basic group @member_user_ids User identifiers of members in the basic group
-messageBasicGroupChatCreate title:string member_user_ids:vector<int32> = MessageContent;
+messageBasicGroupChatCreate title:string member_user_ids:vector<int53> = MessageContent;
//@description A newly created supergroup or channel @title Title of the supergroup or channel
messageSupergroupChatCreate title:string = MessageContent;
@@ -863,48 +2058,70 @@ messageSupergroupChatCreate title:string = MessageContent;
messageChatChangeTitle title:string = MessageContent;
//@description An updated chat photo @photo New chat photo
-messageChatChangePhoto photo:photo = MessageContent;
+messageChatChangePhoto photo:chatPhoto = MessageContent;
//@description A deleted chat photo
messageChatDeletePhoto = MessageContent;
//@description New chat members were added @member_user_ids User identifiers of the new members
-messageChatAddMembers member_user_ids:vector<int32> = MessageContent;
+messageChatAddMembers member_user_ids:vector<int53> = MessageContent;
-//@description A new member joined the chat by invite link
+//@description A new member joined the chat via an invite link
messageChatJoinByLink = MessageContent;
+//@description A new member was accepted to the chat by an administrator
+messageChatJoinByRequest = MessageContent;
+
//@description A chat member was deleted @user_id User identifier of the deleted chat member
-messageChatDeleteMember user_id:int32 = MessageContent;
+messageChatDeleteMember user_id:int53 = MessageContent;
//@description A basic group was upgraded to a supergroup and was deactivated as the result @supergroup_id Identifier of the supergroup to which the basic group was upgraded
-messageChatUpgradeTo supergroup_id:int32 = MessageContent;
+messageChatUpgradeTo supergroup_id:int53 = MessageContent;
//@description A supergroup has been created from a basic group @title Title of the newly created supergroup @basic_group_id The identifier of the original basic group
-messageChatUpgradeFrom title:string basic_group_id:int32 = MessageContent;
+messageChatUpgradeFrom title:string basic_group_id:int53 = MessageContent;
-//@description A message has been pinned @message_id Identifier of the pinned message, can be an identifier of a deleted message
+//@description A message has been pinned @message_id Identifier of the pinned message, can be an identifier of a deleted message or 0
messagePinMessage message_id:int53 = MessageContent;
//@description A screenshot of a message in the chat has been taken
messageScreenshotTaken = MessageContent;
-//@description The TTL (Time To Live) setting messages in a secret chat has been changed @ttl New TTL
+//@description A theme in the chat has been changed @theme_name If non-empty, name of a new theme, set for the chat. Otherwise chat theme was reset to the default one
+messageChatSetTheme theme_name:string = MessageContent;
+
+//@description The TTL (Time To Live) setting for messages in the chat has been changed @ttl New message TTL
messageChatSetTtl ttl:int32 = MessageContent;
+//@description A forum topic has been created @name Name of the topic @icon Icon of the topic
+messageForumTopicCreated name:string icon:forumTopicIcon = MessageContent;
+
+//@description A forum topic has been edited @name If non-empty, the new name of the topic @edit_icon_custom_emoji_id True, if icon's custom_emoji_id is changed @icon_custom_emoji_id New unique identifier of the custom emoji shown on the topic icon; 0 if none. Must be ignored if edit_icon_custom_emoji_id is false
+messageForumTopicEdited name:string edit_icon_custom_emoji_id:Bool icon_custom_emoji_id:int64 = MessageContent;
+
+//@description A forum topic has been closed or opened @is_closed True if the topic was closed or reopened
+messageForumTopicIsClosedToggled is_closed:Bool = MessageContent;
+
//@description A non-standard action has happened in the chat @text Message text to be shown in the chat
messageCustomServiceAction text:string = MessageContent;
-//@description A new high score was achieved in a game @game_message_id Identifier of the message with the game, can be an identifier of a deleted message @game_id Identifier of the game, may be different from the games presented in the message with the game @score New score
+//@description A new high score was achieved in a game @game_message_id Identifier of the message with the game, can be an identifier of a deleted message @game_id Identifier of the game; may be different from the games presented in the message with the game @score New score
messageGameScore game_message_id:int53 game_id:int64 score:int32 = MessageContent;
-//@description A payment has been completed @invoice_message_id Identifier of the message with the corresponding invoice; can be an identifier of a deleted message @currency Currency for the price of the product @total_amount Total price for the product, in the minimal quantity of the currency
-messagePaymentSuccessful invoice_message_id:int53 currency:string total_amount:int53 = MessageContent;
+//@description A payment has been completed @invoice_chat_id Identifier of the chat, containing the corresponding invoice message @invoice_message_id Identifier of the message with the corresponding invoice; can be 0 or an identifier of a deleted message
+//@currency Currency for the price of the product @total_amount Total price for the product, in the smallest units of the currency
+//@is_recurring True, if this is a recurring payment @is_first_recurring True, if this is the first recurring payment @invoice_name Name of the invoice; may be empty if unknown
+messagePaymentSuccessful invoice_chat_id:int53 invoice_message_id:int53 currency:string total_amount:int53 is_recurring:Bool is_first_recurring:Bool invoice_name:string = MessageContent;
-//@description A payment has been completed; for bots only @invoice_message_id Identifier of the message with the corresponding invoice; can be an identifier of a deleted message @currency Currency for price of the product
-//@total_amount Total price for the product, in the minimal quantity of the currency @invoice_payload Invoice payload @shipping_option_id Identifier of the shipping option chosen by the user, may be empty if not applicable @order_info Information about the order; may be null
+//@description A payment has been completed; for bots only @currency Currency for price of the product @total_amount Total price for the product, in the smallest units of the currency
+//@is_recurring True, if this is a recurring payment @is_first_recurring True, if this is the first recurring payment
+//@invoice_payload Invoice payload @shipping_option_id Identifier of the shipping option chosen by the user; may be empty if not applicable @order_info Information about the order; may be null
//@telegram_payment_charge_id Telegram payment identifier @provider_payment_charge_id Provider payment identifier
-messagePaymentSuccessfulBot invoice_message_id:int53 currency:string total_amount:int53 invoice_payload:bytes shipping_option_id:string order_info:orderInfo telegram_payment_charge_id:string provider_payment_charge_id:string = MessageContent;
+messagePaymentSuccessfulBot currency:string total_amount:int53 is_recurring:Bool is_first_recurring:Bool invoice_payload:bytes shipping_option_id:string order_info:orderInfo telegram_payment_charge_id:string provider_payment_charge_id:string = MessageContent;
+
+//@description Telegram Premium was gifted to the user @currency Currency for the paid amount @amount The paid amount, in the smallest units of the currency @month_count Number of month the Telegram Premium subscription will be active
+//@sticker A sticker to be shown in the message; may be null if unknown
+messageGiftedPremium currency:string amount:int53 month_count:int32 sticker:sticker = MessageContent;
//@description A contact has registered with Telegram
messageContactRegistered = MessageContent;
@@ -912,22 +2129,37 @@ messageContactRegistered = MessageContent;
//@description The current user has connected a website by logging in using Telegram Login Widget on it @domain_name Domain name of the connected website
messageWebsiteConnected domain_name:string = MessageContent;
-//@description Message content that is not supported by the client
+//@description Data from a Web App has been sent to a bot @button_text Text of the keyboardButtonTypeWebApp button, which opened the Web App
+messageWebAppDataSent button_text:string = MessageContent;
+
+//@description Data from a Web App has been received; for bots only @button_text Text of the keyboardButtonTypeWebApp button, which opened the Web App @data Received data
+messageWebAppDataReceived button_text:string data:string = MessageContent;
+
+//@description Telegram Passport data has been sent to a bot @types List of Telegram Passport element types sent
+messagePassportDataSent types:vector<PassportElementType> = MessageContent;
+
+//@description Telegram Passport data has been received; for bots only @elements List of received Telegram Passport elements @credentials Encrypted data credentials
+messagePassportDataReceived elements:vector<encryptedPassportElement> credentials:encryptedCredentials = MessageContent;
+
+//@description A user in the chat came within proximity alert range @traveler_id The identifier of a user or chat that triggered the proximity alert @watcher_id The identifier of a user or chat that subscribed for the proximity alert @distance The distance between the users
+messageProximityAlertTriggered traveler_id:MessageSender watcher_id:MessageSender distance:int32 = MessageContent;
+
+//@description Message content that is not supported in the current TDLib version
messageUnsupported = MessageContent;
//@class TextEntityType @description Represents a part of the text which must be formatted differently
-//@description A mention of a user by their username
+//@description A mention of a user, a supergroup, or a channel by their username
textEntityTypeMention = TextEntityType;
//@description A hashtag text, beginning with "#"
textEntityTypeHashtag = TextEntityType;
-//@description A cashtag text, beginning with "$" and consisting of capital english letters (i.e. "$USD")
+//@description A cashtag text, beginning with "$" and consisting of capital English letters (e.g., "$USD")
textEntityTypeCashtag = TextEntityType;
-//@description A bot command, beginning with "/". This shouldn't be highlighted if there are no bots in the chat
+//@description A bot command, beginning with "/"
textEntityTypeBotCommand = TextEntityType;
//@description An HTTP URL
@@ -936,12 +2168,27 @@ textEntityTypeUrl = TextEntityType;
//@description An email address
textEntityTypeEmailAddress = TextEntityType;
+//@description A phone number
+textEntityTypePhoneNumber = TextEntityType;
+
+//@description A bank card number. The getBankCardInfo method can be used to get information about the bank card
+textEntityTypeBankCardNumber = TextEntityType;
+
//@description A bold text
textEntityTypeBold = TextEntityType;
//@description An italic text
textEntityTypeItalic = TextEntityType;
+//@description An underlined text
+textEntityTypeUnderline = TextEntityType;
+
+//@description A strikethrough text
+textEntityTypeStrikethrough = TextEntityType;
+
+//@description A spoiler text
+textEntityTypeSpoiler = TextEntityType;
+
//@description Text that must be formatted as if inside a code HTML tag
textEntityTypeCode = TextEntityType;
@@ -951,57 +2198,89 @@ textEntityTypePre = TextEntityType;
//@description Text that must be formatted as if inside pre, and code HTML tags @language Programming language of the code; as defined by the sender
textEntityTypePreCode language:string = TextEntityType;
-//@description A text description shown instead of a raw URL @url URL to be opened when the link is clicked
+//@description A text description shown instead of a raw URL @url HTTP or tg:// URL to be opened when the link is clicked
textEntityTypeTextUrl url:string = TextEntityType;
//@description A text shows instead of a raw mention of the user (e.g., when the user has no username) @user_id Identifier of the mentioned user
-textEntityTypeMentionName user_id:int32 = TextEntityType;
+textEntityTypeMentionName user_id:int53 = TextEntityType;
-//@description A phone number
-textEntityTypePhoneNumber = TextEntityType;
+//@description A custom emoji. The text behind a custom emoji must be an emoji. Only premium users can use premium custom emoji @custom_emoji_id Unique identifier of the custom emoji
+textEntityTypeCustomEmoji custom_emoji_id:int64 = TextEntityType;
+//@description A media timestamp @media_timestamp Timestamp from which a video/audio/video note/voice note playing must start, in seconds. The media can be in the content or the web page preview of the current message, or in the same places in the replied message
+textEntityTypeMediaTimestamp media_timestamp:int32 = TextEntityType;
-//@description A thumbnail to be sent along with a file; should be in JPEG or WEBP format for stickers, and less than 200 kB in size @thumbnail Thumbnail file to send. Sending thumbnails by file_id is currently not supported
-//@width Thumbnail width, usually shouldn't exceed 90. Use 0 if unknown @height Thumbnail height, usually shouldn't exceed 90. Use 0 if unknown
+
+//@description A thumbnail to be sent along with a file; must be in JPEG or WEBP format for stickers, and less than 200 KB in size
+//@thumbnail Thumbnail file to send. Sending thumbnails by file_id is currently not supported
+//@width Thumbnail width, usually shouldn't exceed 320. Use 0 if unknown
+//@height Thumbnail height, usually shouldn't exceed 320. Use 0 if unknown
inputThumbnail thumbnail:InputFile width:int32 height:int32 = InputThumbnail;
+//@class MessageSchedulingState @description Contains information about the time when a scheduled message will be sent
+
+//@description The message will be sent at the specified date @send_date Date the message will be sent. The date must be within 367 days in the future
+messageSchedulingStateSendAtDate send_date:int32 = MessageSchedulingState;
+
+//@description The message will be sent when the peer will be online. Applicable to private chats only and when the exact online status of the peer is known
+messageSchedulingStateSendWhenOnline = MessageSchedulingState;
+
+
+//@description Options to be used when a message is sent
+//@disable_notification Pass true to disable notification for the message
+//@from_background Pass true if the message is sent from the background
+//@protect_content Pass true if the content of the message must be protected from forwarding and saving; for bots only
+//@update_order_of_installed_sticker_sets Pass true if the user explicitly chosen a sticker or a custom emoji from an installed sticker set; applicable only to sendMessage and sendMessageAlbum
+//@scheduling_state Message scheduling state; pass null to send message immediately. Messages sent to a secret chat, live location messages and self-destructing messages can't be scheduled
+messageSendOptions disable_notification:Bool from_background:Bool protect_content:Bool update_order_of_installed_sticker_sets:Bool scheduling_state:MessageSchedulingState = MessageSendOptions;
+
+//@description Options to be used when a message content is copied without reference to the original sender. Service messages and messageInvoice can't be copied
+//@send_copy True, if content of the message needs to be copied without reference to the original sender. Always true if the message is forwarded to a secret chat or is local
+//@replace_caption True, if media caption of the message copy needs to be replaced. Ignored if send_copy is false
+//@new_caption New message caption; pass null to copy message without caption. Ignored if replace_caption is false
+messageCopyOptions send_copy:Bool replace_caption:Bool new_caption:formattedText = MessageCopyOptions;
+
+
//@class InputMessageContent @description The content of a message to send
-//@description A text message @text Formatted text to be sent. Only Bold, Italic, Code, Pre, PreCode and TextUrl entities are allowed to be specified manually
-//@disable_web_page_preview True, if rich web page previews for URLs in the message text should be disabled @clear_draft True, if a chat message draft should be deleted
+//@description A text message @text Formatted text to be sent; 1-GetOption("message_text_length_max") characters. Only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, Code, Pre, PreCode, TextUrl and MentionName entities are allowed to be specified manually
+//@disable_web_page_preview True, if rich web page previews for URLs in the message text must be disabled @clear_draft True, if a chat message draft must be deleted
inputMessageText text:formattedText disable_web_page_preview:Bool clear_draft:Bool = InputMessageContent;
-//@description An animation message (GIF-style). @animation Animation file to be sent @thumbnail Animation thumbnail, if available @duration Duration of the animation, in seconds @width Width of the animation; may be replaced by the server @height Height of the animation; may be replaced by the server @caption Animation caption; 0-200 characters
-inputMessageAnimation animation:InputFile thumbnail:inputThumbnail duration:int32 width:int32 height:int32 caption:formattedText = InputMessageContent;
+//@description An animation message (GIF-style). @animation Animation file to be sent @thumbnail Animation thumbnail; pass null to skip thumbnail uploading @added_sticker_file_ids File identifiers of the stickers added to the animation, if applicable
+//@duration Duration of the animation, in seconds @width Width of the animation; may be replaced by the server @height Height of the animation; may be replaced by the server @caption Animation caption; pass null to use an empty caption; 0-GetOption("message_caption_length_max") characters
+inputMessageAnimation animation:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector<int32> duration:int32 width:int32 height:int32 caption:formattedText = InputMessageContent;
-//@description An audio message @audio Audio file to be sent @album_cover_thumbnail Thumbnail of the cover for the album, if available @duration Duration of the audio, in seconds; may be replaced by the server @title Title of the audio; 0-64 characters; may be replaced by the server
-//@performer Performer of the audio; 0-64 characters, may be replaced by the server @caption Audio caption; 0-200 characters
+//@description An audio message @audio Audio file to be sent @album_cover_thumbnail Thumbnail of the cover for the album; pass null to skip thumbnail uploading @duration Duration of the audio, in seconds; may be replaced by the server @title Title of the audio; 0-64 characters; may be replaced by the server
+//@performer Performer of the audio; 0-64 characters, may be replaced by the server @caption Audio caption; pass null to use an empty caption; 0-GetOption("message_caption_length_max") characters
inputMessageAudio audio:InputFile album_cover_thumbnail:inputThumbnail duration:int32 title:string performer:string caption:formattedText = InputMessageContent;
-//@description A document message (general file) @document Document to be sent @thumbnail Document thumbnail, if available @caption Document caption; 0-200 characters
-inputMessageDocument document:InputFile thumbnail:inputThumbnail caption:formattedText = InputMessageContent;
+//@description A document message (general file) @document Document to be sent @thumbnail Document thumbnail; pass null to skip thumbnail uploading @disable_content_type_detection If true, automatic file type detection will be disabled and the document will always be sent as file. Always true for files sent to secret chats @caption Document caption; pass null to use an empty caption; 0-GetOption("message_caption_length_max") characters
+inputMessageDocument document:InputFile thumbnail:inputThumbnail disable_content_type_detection:Bool caption:formattedText = InputMessageContent;
-//@description A photo message @photo Photo to send @thumbnail Photo thumbnail to be sent, this is sent to the other party in secret chats only @added_sticker_file_ids File identifiers of the stickers added to the photo, if applicable @width Photo width @height Photo height @caption Photo caption; 0-200 characters
+//@description A photo message @photo Photo to send. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20 @thumbnail Photo thumbnail to be sent; pass null to skip thumbnail uploading. The thumbnail is sent to the other party only in secret chats @added_sticker_file_ids File identifiers of the stickers added to the photo, if applicable @width Photo width @height Photo height @caption Photo caption; pass null to use an empty caption; 0-GetOption("message_caption_length_max") characters
//@ttl Photo TTL (Time To Live), in seconds (0-60). A non-zero TTL can be specified only in private chats
inputMessagePhoto photo:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector<int32> width:int32 height:int32 caption:formattedText ttl:int32 = InputMessageContent;
-//@description A sticker message @sticker Sticker to be sent @thumbnail Sticker thumbnail, if available @width Sticker width @height Sticker height
-inputMessageSticker sticker:InputFile thumbnail:inputThumbnail width:int32 height:int32 = InputMessageContent;
+//@description A sticker message @sticker Sticker to be sent @thumbnail Sticker thumbnail; pass null to skip thumbnail uploading @width Sticker width @height Sticker height @emoji Emoji used to choose the sticker
+inputMessageSticker sticker:InputFile thumbnail:inputThumbnail width:int32 height:int32 emoji:string = InputMessageContent;
-//@description A video message @video Video to be sent @thumbnail Video thumbnail, if available @added_sticker_file_ids File identifiers of the stickers added to the video, if applicable
-//@duration Duration of the video, in seconds @width Video width @height Video height @supports_streaming True, if the video should be tried to be streamed
-//@caption Video caption; 0-200 characters @ttl Video TTL (Time To Live), in seconds (0-60). A non-zero TTL can be specified only in private chats
+//@description A video message @video Video to be sent @thumbnail Video thumbnail; pass null to skip thumbnail uploading @added_sticker_file_ids File identifiers of the stickers added to the video, if applicable
+//@duration Duration of the video, in seconds @width Video width @height Video height @supports_streaming True, if the video is supposed to be streamed
+//@caption Video caption; pass null to use an empty caption; 0-GetOption("message_caption_length_max") characters @ttl Video TTL (Time To Live), in seconds (0-60). A non-zero TTL can be specified only in private chats
inputMessageVideo video:InputFile thumbnail:inputThumbnail added_sticker_file_ids:vector<int32> duration:int32 width:int32 height:int32 supports_streaming:Bool caption:formattedText ttl:int32 = InputMessageContent;
-//@description A video note message @video_note Video note to be sent @thumbnail Video thumbnail, if available @duration Duration of the video, in seconds @length Video width and height; must be positive and not greater than 640
+//@description A video note message @video_note Video note to be sent @thumbnail Video thumbnail; pass null to skip thumbnail uploading @duration Duration of the video, in seconds @length Video width and height; must be positive and not greater than 640
inputMessageVideoNote video_note:InputFile thumbnail:inputThumbnail duration:int32 length:int32 = InputMessageContent;
-//@description A voice note message @voice_note Voice note to be sent @duration Duration of the voice note, in seconds @waveform Waveform representation of the voice note, in 5-bit format @caption Voice note caption; 0-200 characters
+//@description A voice note message @voice_note Voice note to be sent @duration Duration of the voice note, in seconds @waveform Waveform representation of the voice note in 5-bit format @caption Voice note caption; pass null to use an empty caption; 0-GetOption("message_caption_length_max") characters
inputMessageVoiceNote voice_note:InputFile duration:int32 waveform:bytes caption:formattedText = InputMessageContent;
-//@description A message with a location @location Location to be sent @live_period Period for which the location can be updated, in seconds; should bebetween 60 and 86400 for a live location and 0 otherwise
-inputMessageLocation location:location live_period:int32 = InputMessageContent;
+//@description A message with a location @location Location to be sent @live_period Period for which the location can be updated, in seconds; must be between 60 and 86400 for a live location and 0 otherwise
+//@heading For live locations, a direction in which the location moves, in degrees; 1-360. Pass 0 if unknown
+//@proximity_alert_radius For live locations, a maximum distance to another chat member for proximity alerts, in meters (0-100000). Pass 0 if the notification is disabled. Can't be enabled in channels and Saved Messages
+inputMessageLocation location:location live_period:int32 heading:int32 proximity_alert_radius:int32 = InputMessageContent;
//@description A message with information about a venue @venue Venue to send
inputMessageVenue venue:venue = InputMessageContent;
@@ -1009,15 +2288,30 @@ inputMessageVenue venue:venue = InputMessageContent;
//@description A message containing a user contact @contact Contact to send
inputMessageContact contact:contact = InputMessageContent;
+//@description A dice message @emoji Emoji on which the dice throw animation is based @clear_draft True, if the chat message draft must be deleted
+inputMessageDice emoji:string clear_draft:Bool = InputMessageContent;
+
//@description A message with a game; not supported for channels or secret chats @bot_user_id User identifier of the bot that owns the game @game_short_name Short name of the game
-inputMessageGame bot_user_id:int32 game_short_name:string = InputMessageContent;
+inputMessageGame bot_user_id:int53 game_short_name:string = InputMessageContent;
+
+//@description A message with an invoice; can be used only by bots @invoice Invoice @title Product title; 1-32 characters @param_description Product description; 0-255 characters
+//@photo_url Product photo URL; optional @photo_size Product photo size @photo_width Product photo width @photo_height Product photo height
+//@payload The invoice payload @provider_token Payment provider token @provider_data JSON-encoded data about the invoice, which will be shared with the payment provider
+//@start_parameter Unique invoice bot deep link parameter for the generation of this invoice. If empty, it would be possible to pay directly from forwards of the invoice message
+//@extended_media_content The content of extended media attached to the invoice. The content of the message to be sent. Must be one of the following types: inputMessagePhoto, inputMessageVideo
+inputMessageInvoice invoice:invoice title:string description:string photo_url:string photo_size:int32 photo_width:int32 photo_height:int32 payload:bytes provider_token:string provider_data:string start_parameter:string extended_media_content:InputMessageContent = InputMessageContent;
-//@description A message with an invoice; can be used only by bots and only in private chats @invoice Invoice @title Product title; 1-32 characters @param_description Product description; 0-255 characters @photo_url Product photo URL; optional @photo_size Product photo size @photo_width Product photo width @photo_height Product photo height
-//@payload The invoice payload @provider_token Payment provider token @provider_data JSON-encoded data about the invoice, which will be shared with the payment provider @start_parameter Unique invoice bot start_parameter for the generation of this invoice
-inputMessageInvoice invoice:invoice title:string description:string photo_url:string photo_size:int32 photo_width:int32 photo_height:int32 payload:bytes provider_token:string provider_data:string start_parameter:string = InputMessageContent;
+//@description A message with a poll. Polls can't be sent to secret chats. Polls can be sent only to a private chat with a bot @question Poll question; 1-255 characters (up to 300 characters for bots) @options List of poll answer options, 2-10 strings 1-100 characters each
+//@is_anonymous True, if the poll voters are anonymous. Non-anonymous polls can't be sent or forwarded to channels @type Type of the poll
+//@open_period Amount of time the poll will be active after creation, in seconds; for bots only
+//@close_date Point in time (Unix timestamp) when the poll will automatically be closed; for bots only
+//@is_closed True, if the poll needs to be sent already closed; for bots only
+inputMessagePoll question:string options:vector<string> is_anonymous:Bool type:PollType open_period:int32 close_date:int32 is_closed:Bool = InputMessageContent;
-//@description A forwarded message @from_chat_id Identifier for the chat this forwarded message came from @message_id Identifier of the message to forward @in_game_share True, if a game message should be shared within a launched game; applies only to game messages
-inputMessageForwarded from_chat_id:int53 message_id:int53 in_game_share:Bool = InputMessageContent;
+//@description A forwarded message @from_chat_id Identifier for the chat this forwarded message came from @message_id Identifier of the message to forward
+//@in_game_share True, if a game message is being shared from a launched game; applies only to game messages
+//@copy_options Options to be used to copy content of the message without reference to the original sender; pass null to forward the message as usual
+inputMessageForwarded from_chat_id:int53 message_id:int53 in_game_share:Bool copy_options:messageCopyOptions = InputMessageContent;
//@class SearchMessagesFilter @description Represents a filter for message search results
@@ -1052,12 +2346,6 @@ searchMessagesFilterUrl = SearchMessagesFilter;
//@description Returns only messages containing chat photos
searchMessagesFilterChatPhoto = SearchMessagesFilter;
-//@description Returns only call messages
-searchMessagesFilterCall = SearchMessagesFilter;
-
-//@description Returns only incoming call messages with missed/declined discard reasons
-searchMessagesFilterMissedCall = SearchMessagesFilter;
-
//@description Returns only video note messages
searchMessagesFilterVideoNote = SearchMessagesFilter;
@@ -1067,37 +2355,64 @@ searchMessagesFilterVoiceAndVideoNote = SearchMessagesFilter;
//@description Returns only messages with mentions of the current user, or messages that are replies to their messages
searchMessagesFilterMention = SearchMessagesFilter;
-//@description Returns only messages with unread mentions of the current user or messages that are replies to their messages. When using this filter the results can't be additionally filtered by a query or by the sending user
+//@description Returns only messages with unread mentions of the current user, or messages that are replies to their messages. When using this filter the results can't be additionally filtered by a query, a message thread or by the sending user
searchMessagesFilterUnreadMention = SearchMessagesFilter;
+//@description Returns only messages with unread reactions for the current user. When using this filter the results can't be additionally filtered by a query, a message thread or by the sending user
+searchMessagesFilterUnreadReaction = SearchMessagesFilter;
+
+//@description Returns only failed to send messages. This filter can be used only if the message database is used
+searchMessagesFilterFailedToSend = SearchMessagesFilter;
+
+//@description Returns only pinned messages
+searchMessagesFilterPinned = SearchMessagesFilter;
+
//@class ChatAction @description Describes the different types of activity in a chat
//@description The user is typing a message
chatActionTyping = ChatAction;
+
//@description The user is recording a video
chatActionRecordingVideo = ChatAction;
+
//@description The user is uploading a video @progress Upload progress, as a percentage
chatActionUploadingVideo progress:int32 = ChatAction;
+
//@description The user is recording a voice note
chatActionRecordingVoiceNote = ChatAction;
+
//@description The user is uploading a voice note @progress Upload progress, as a percentage
chatActionUploadingVoiceNote progress:int32 = ChatAction;
+
//@description The user is uploading a photo @progress Upload progress, as a percentage
chatActionUploadingPhoto progress:int32 = ChatAction;
+
//@description The user is uploading a document @progress Upload progress, as a percentage
chatActionUploadingDocument progress:int32 = ChatAction;
+
+//@description The user is picking a sticker to send
+chatActionChoosingSticker = ChatAction;
+
//@description The user is picking a location or venue to send
chatActionChoosingLocation = ChatAction;
+
//@description The user is picking a contact to send
chatActionChoosingContact = ChatAction;
+
//@description The user has started to play a game
chatActionStartPlayingGame = ChatAction;
+
//@description The user is recording a video note
chatActionRecordingVideoNote = ChatAction;
+
//@description The user is uploading a video note @progress Upload progress, as a percentage
chatActionUploadingVideoNote progress:int32 = ChatAction;
-//@description The user has cancelled the previous action
+
+//@description The user is watching animations sent by the other party by clicking on an animated emoji @emoji The animated emoji
+chatActionWatchingAnimations emoji:string = ChatAction;
+
+//@description The user has canceled the previous action
chatActionCancel = ChatAction;
@@ -1125,29 +2440,38 @@ userStatusLastMonth = UserStatus;
//@description Represents a list of stickers @stickers List of stickers
stickers stickers:vector<sticker> = Stickers;
-//@description Represents a list of all emoji corresponding to a sticker in a sticker set. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object @emojis List of emojis
-stickerEmojis emojis:vector<string> = StickerEmojis;
-
-//@description Represents a sticker set @id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @is_installed True, if the sticker set has been installed by the current user
-//@is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously @is_official True, if the sticker set is official @is_masks True, if the stickers in the set are masks
-//@is_viewed True for already viewed trending sticker sets @stickers List of stickers in this set @emojis A list of emoji corresponding to the stickers in the same order
-stickerSet id:int64 title:string name:string is_installed:Bool is_archived:Bool is_official:Bool is_masks:Bool is_viewed:Bool stickers:vector<sticker> emojis:vector<stickerEmojis> = StickerSet;
-
-//@description Represents short information about a sticker set @id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @is_installed True, if the sticker set has been installed by current user
-//@is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously @is_official True, if the sticker set is official @is_masks True, if the stickers in the set are masks
-//@is_viewed True for already viewed trending sticker sets @size Total number of stickers in the set @covers Contains up to the first 5 stickers from the set, depending on the context. If the client needs more stickers the full set should be requested
-stickerSetInfo id:int64 title:string name:string is_installed:Bool is_archived:Bool is_official:Bool is_masks:Bool is_viewed:Bool size:int32 covers:vector<sticker> = StickerSetInfo;
+//@description Represents a list of emoji @emojis List of emojis
+emojis emojis:vector<string> = Emojis;
+
+//@description Represents a sticker set
+//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed
+//@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner
+//@is_installed True, if the sticker set has been installed by the current user @is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously
+//@is_official True, if the sticker set is official @sticker_format Format of the stickers in the set @sticker_type Type of the stickers in the set @is_viewed True for already viewed trending sticker sets
+//@stickers List of stickers in this set @emojis A list of emoji corresponding to the stickers in the same order. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object
+stickerSet id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector<closedVectorPath> is_installed:Bool is_archived:Bool is_official:Bool sticker_format:StickerFormat sticker_type:StickerType is_viewed:Bool stickers:vector<sticker> emojis:vector<emojis> = StickerSet;
+
+//@description Represents short information about a sticker set
+//@id Identifier of the sticker set @title Title of the sticker set @name Name of the sticker set @thumbnail Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null
+//@thumbnail_outline Sticker set thumbnail's outline represented as a list of closed vector paths; may be empty. The coordinate system origin is in the upper-left corner
+//@is_installed True, if the sticker set has been installed by the current user @is_archived True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously
+//@is_official True, if the sticker set is official @sticker_format Format of the stickers in the set @sticker_type Type of the stickers in the set @is_viewed True for already viewed trending sticker sets
+//@size Total number of stickers in the set @covers Up to the first 5 stickers from the set, depending on the context. If the application needs more stickers the full sticker set needs to be requested
+stickerSetInfo id:int64 title:string name:string thumbnail:thumbnail thumbnail_outline:vector<closedVectorPath> is_installed:Bool is_archived:Bool is_official:Bool sticker_format:StickerFormat sticker_type:StickerType is_viewed:Bool size:int32 covers:vector<sticker> = StickerSetInfo;
//@description Represents a list of sticker sets @total_count Approximate total number of sticker sets found @sets List of sticker sets
stickerSets total_count:int32 sets:vector<stickerSetInfo> = StickerSets;
+//@description Represents a list of trending sticker sets @total_count Approximate total number of trending sticker sets @sets List of trending sticker sets @is_premium True, if the list contains sticker sets with premium stickers
+trendingStickerSets total_count:int32 sets:vector<stickerSetInfo> is_premium:Bool = TrendingStickerSets;
+
//@class CallDiscardReason @description Describes the reason why a call was discarded
//@description The call wasn't discarded, or the reason is unknown
callDiscardReasonEmpty = CallDiscardReason;
-//@description The call was ended before the conversation started. It was cancelled by the caller or missed by the other party
+//@description The call was ended before the conversation started. It was canceled by the caller or missed by the other party
callDiscardReasonMissed = CallDiscardReason;
//@description The call was ended before the conversation started. It was declined by the other party
@@ -1160,16 +2484,34 @@ callDiscardReasonDisconnected = CallDiscardReason;
callDiscardReasonHungUp = CallDiscardReason;
-//@description Specifies the supported call protocols @udp_p2p True, if UDP peer-to-peer connections are supported @udp_reflector True, if connection through UDP reflectors is supported @min_layer Minimum supported API layer; use 65 @max_layer Maximum supported API layer; use 65
-callProtocol udp_p2p:Bool udp_reflector:Bool min_layer:int32 max_layer:int32 = CallProtocol;
+//@description Specifies the supported call protocols
+//@udp_p2p True, if UDP peer-to-peer connections are supported
+//@udp_reflector True, if connection through UDP reflectors is supported
+//@min_layer The minimum supported API layer; use 65
+//@max_layer The maximum supported API layer; use 65
+//@library_versions List of supported tgcalls versions
+callProtocol udp_p2p:Bool udp_reflector:Bool min_layer:int32 max_layer:int32 library_versions:vector<string> = CallProtocol;
+
+
+//@class CallServerType @description Describes the type of a call server
+
+//@description A Telegram call reflector @peer_tag A peer tag to be used with the reflector @is_tcp True, if the server uses TCP instead of UDP
+callServerTypeTelegramReflector peer_tag:bytes is_tcp:Bool = CallServerType;
+
+//@description A WebRTC server @username Username to be used for authentication @password Authentication password @supports_turn True, if the server supports TURN @supports_stun True, if the server supports STUN
+callServerTypeWebrtc username:string password:string supports_turn:Bool supports_stun:Bool = CallServerType;
-//@description Describes the address of UDP reflectors @id Reflector identifier @ip IPv4 reflector address @ipv6 IPv6 reflector address @port Reflector port number @peer_tag Connection peer tag
-callConnection id:int64 ip:string ipv6:string port:int32 peer_tag:bytes = CallConnection;
+
+//@description Describes a server for relaying call data @id Server identifier @ip_address Server IPv4 address @ipv6_address Server IPv6 address @port Server port number @type Server type
+callServer id:int64 ip_address:string ipv6_address:string port:int32 type:CallServerType = CallServer;
//@description Contains the call identifier @id Call identifier
callId id:int32 = CallId;
+//@description Contains the group call identifier @id Group call identifier
+groupCallId id:int32 = GroupCallId;
+
//@class CallState @description Describes the current call state
@@ -1179,122 +2521,327 @@ callStatePending is_created:Bool is_received:Bool = CallState;
//@description The call has been answered and encryption keys are being exchanged
callStateExchangingKeys = CallState;
-//@description The call is ready to use @protocol Call protocols supported by the peer @connections Available UDP reflectors @config A JSON-encoded call config @encryption_key Call encryption key @emojis Encryption key emojis fingerprint
-callStateReady protocol:callProtocol connections:vector<callConnection> config:string encryption_key:bytes emojis:vector<string> = CallState;
+//@description The call is ready to use @protocol Call protocols supported by the peer @servers List of available call servers @config A JSON-encoded call config @encryption_key Call encryption key @emojis Encryption key emojis fingerprint @allow_p2p True, if peer-to-peer connection is allowed by users privacy settings
+callStateReady protocol:callProtocol servers:vector<callServer> config:string encryption_key:bytes emojis:vector<string> allow_p2p:Bool = CallState;
//@description The call is hanging up after discardCall has been called
callStateHangingUp = CallState;
-//@description The call has ended successfully @reason The reason, why the call has ended @need_rating True, if the call rating should be sent to the server @need_debug_information True, if the call debug information should be sent to the server
-callStateDiscarded reason:CallDiscardReason need_rating:Bool need_debug_information:Bool = CallState;
+//@description The call has ended successfully @reason The reason, why the call has ended @need_rating True, if the call rating must be sent to the server @need_debug_information True, if the call debug information must be sent to the server @need_log True, if the call log must be sent to the server
+callStateDiscarded reason:CallDiscardReason need_rating:Bool need_debug_information:Bool need_log:Bool = CallState;
//@description The call has ended with an error @error Error. An error with the code 4005000 will be returned if an outgoing call is missed because of an expired timeout
callStateError error:error = CallState;
-//@description Describes a call @id Call identifier, not persistent @user_id Peer user identifier @is_outgoing True, if the call is outgoing @state Call state
-call id:int32 user_id:int32 is_outgoing:Bool state:CallState = Call;
+//@class GroupCallVideoQuality @description Describes the quality of a group call video
+
+//@description The worst available video quality
+groupCallVideoQualityThumbnail = GroupCallVideoQuality;
+
+//@description The medium video quality
+groupCallVideoQualityMedium = GroupCallVideoQuality;
+
+//@description The best available video quality
+groupCallVideoQualityFull = GroupCallVideoQuality;
+
+
+//@description Describes an available stream in a group call
+//@channel_id Identifier of an audio/video channel
+//@scale Scale of segment durations in the stream. The duration is 1000/(2**scale) milliseconds
+//@time_offset Point in time when the stream currently ends; Unix timestamp in milliseconds
+groupCallStream channel_id:int32 scale:int32 time_offset:int53 = GroupCallStream;
+
+//@description Represents a list of group call streams @streams A list of group call streams
+groupCallStreams streams:vector<groupCallStream> = GroupCallStreams;
+
+//@description Represents an RTMP url @url The URL @stream_key Stream key
+rtmpUrl url:string stream_key:string = RtmpUrl;
+
+
+//@description Describes a recently speaking participant in a group call @participant_id Group call participant identifier @is_speaking True, is the user has spoken recently
+groupCallRecentSpeaker participant_id:MessageSender is_speaking:Bool = GroupCallRecentSpeaker;
+
+//@description Describes a group call
+//@id Group call identifier
+//@title Group call title
+//@scheduled_start_date Point in time (Unix timestamp) when the group call is supposed to be started by an administrator; 0 if it is already active or was ended
+//@enabled_start_notification True, if the group call is scheduled and the current user will receive a notification when the group call will start
+//@is_active True, if the call is active
+//@is_rtmp_stream True, if the chat is an RTMP stream instead of an ordinary video chat
+//@is_joined True, if the call is joined
+//@need_rejoin True, if user was kicked from the call because of network loss and the call needs to be rejoined
+//@can_be_managed True, if the current user can manage the group call
+//@participant_count Number of participants in the group call
+//@has_hidden_listeners True, if group call participants, which are muted, aren't returned in participant list
+//@loaded_all_participants True, if all group call participants are loaded
+//@recent_speakers At most 3 recently speaking users in the group call
+//@is_my_video_enabled True, if the current user's video is enabled
+//@is_my_video_paused True, if the current user's video is paused
+//@can_enable_video True, if the current user can broadcast video or share screen
+//@mute_new_participants True, if only group call administrators can unmute new participants
+//@can_toggle_mute_new_participants True, if the current user can enable or disable mute_new_participants setting
+//@record_duration Duration of the ongoing group call recording, in seconds; 0 if none. An updateGroupCall update is not triggered when value of this field changes, but the same recording goes on
+//@is_video_recorded True, if a video file is being recorded for the call
+//@duration Call duration, in seconds; for ended calls only
+groupCall id:int32 title:string scheduled_start_date:int32 enabled_start_notification:Bool is_active:Bool is_rtmp_stream:Bool is_joined:Bool need_rejoin:Bool can_be_managed:Bool participant_count:int32 has_hidden_listeners:Bool loaded_all_participants:Bool recent_speakers:vector<groupCallRecentSpeaker> is_my_video_enabled:Bool is_my_video_paused:Bool can_enable_video:Bool mute_new_participants:Bool can_toggle_mute_new_participants:Bool record_duration:int32 is_video_recorded:Bool duration:int32 = GroupCall;
+
+//@description Describes a group of video synchronization source identifiers @semantics The semantics of sources, one of "SIM" or "FID" @source_ids The list of synchronization source identifiers
+groupCallVideoSourceGroup semantics:string source_ids:vector<int32> = GroupCallVideoSourceGroup;
+
+//@description Contains information about a group call participant's video channel @source_groups List of synchronization source groups of the video @endpoint_id Video channel endpoint identifier
+//@is_paused True if the video is paused. This flag needs to be ignored, if new video frames are received
+groupCallParticipantVideoInfo source_groups:vector<groupCallVideoSourceGroup> endpoint_id:string is_paused:Bool = GroupCallParticipantVideoInfo;
+
+//@description Represents a group call participant
+//@participant_id Identifier of the group call participant
+//@audio_source_id User's audio channel synchronization source identifier
+//@screen_sharing_audio_source_id User's screen sharing audio channel synchronization source identifier
+//@video_info Information about user's video channel; may be null if there is no active video
+//@screen_sharing_video_info Information about user's screen sharing video channel; may be null if there is no active screen sharing video
+//@bio The participant user's bio or the participant chat's description
+//@is_current_user True, if the participant is the current user
+//@is_speaking True, if the participant is speaking as set by setGroupCallParticipantIsSpeaking
+//@is_hand_raised True, if the participant hand is raised
+//@can_be_muted_for_all_users True, if the current user can mute the participant for all other group call participants
+//@can_be_unmuted_for_all_users True, if the current user can allow the participant to unmute themselves or unmute the participant (if the participant is the current user)
+//@can_be_muted_for_current_user True, if the current user can mute the participant only for self
+//@can_be_unmuted_for_current_user True, if the current user can unmute the participant for self
+//@is_muted_for_all_users True, if the participant is muted for all users
+//@is_muted_for_current_user True, if the participant is muted for the current user
+//@can_unmute_self True, if the participant is muted for all users, but can unmute themselves
+//@volume_level Participant's volume level; 1-20000 in hundreds of percents
+//@order User's order in the group call participant list. Orders must be compared lexicographically. The bigger is order, the higher is user in the list. If order is empty, the user must be removed from the participant list
+groupCallParticipant participant_id:MessageSender audio_source_id:int32 screen_sharing_audio_source_id:int32 video_info:groupCallParticipantVideoInfo screen_sharing_video_info:groupCallParticipantVideoInfo bio:string is_current_user:Bool is_speaking:Bool is_hand_raised:Bool can_be_muted_for_all_users:Bool can_be_unmuted_for_all_users:Bool can_be_muted_for_current_user:Bool can_be_unmuted_for_current_user:Bool is_muted_for_all_users:Bool is_muted_for_current_user:Bool can_unmute_self:Bool volume_level:int32 order:string = GroupCallParticipant;
+
+
+//@class CallProblem @description Describes the exact type of a problem with a call
+
+//@description The user heard their own voice
+callProblemEcho = CallProblem;
+
+//@description The user heard background noise
+callProblemNoise = CallProblem;
+
+//@description The other side kept disappearing
+callProblemInterruptions = CallProblem;
+
+//@description The speech was distorted
+callProblemDistortedSpeech = CallProblem;
+
+//@description The user couldn't hear the other side
+callProblemSilentLocal = CallProblem;
+
+//@description The other side couldn't hear the user
+callProblemSilentRemote = CallProblem;
+
+//@description The call ended unexpectedly
+callProblemDropped = CallProblem;
+
+//@description The video was distorted
+callProblemDistortedVideo = CallProblem;
+
+//@description The video was pixelated
+callProblemPixelatedVideo = CallProblem;
+
+
+//@description Describes a call @id Call identifier, not persistent @user_id Peer user identifier @is_outgoing True, if the call is outgoing @is_video True, if the call is a video call @state Call state
+call id:int32 user_id:int53 is_outgoing:Bool is_video:Bool state:CallState = Call;
+
+
+//@description Contains settings for the authentication of the user's phone number
+//@allow_flash_call Pass true if the authentication code may be sent via a flash call to the specified phone number
+//@allow_missed_call Pass true if the authentication code may be sent via a missed call to the specified phone number
+//@is_current_phone_number Pass true if the authenticated phone number is used on the current device
+//@allow_sms_retriever_api For official applications only. True, if the application can use Android SMS Retriever API (requires Google Play Services >= 10.2) to automatically receive the authentication code from the SMS. See https://developers.google.com/identity/sms-retriever/ for more details
+//@authentication_tokens List of up to 20 authentication tokens, recently received in updateOption("authentication_token") in previously logged out sessions
+phoneNumberAuthenticationSettings allow_flash_call:Bool allow_missed_call:Bool is_current_phone_number:Bool allow_sms_retriever_api:Bool authentication_tokens:vector<string> = PhoneNumberAuthenticationSettings;
+
+
+//@description Represents a reaction applied to a message @type Type of the reaction @sender_id Identifier of the chat member, applied the reaction
+addedReaction type:ReactionType sender_id:MessageSender = AddedReaction;
+
+//@description Represents a list of reactions added to a message @total_count The total number of found reactions @reactions The list of added reactions @next_offset The offset for the next request. If empty, there are no more results
+addedReactions total_count:int32 reactions:vector<addedReaction> next_offset:string = AddedReactions;
+
+//@description Represents an available reaction @type Type of the reaction @needs_premium True, if Telegram Premium is needed to send the reaction
+availableReaction type:ReactionType needs_premium:Bool = AvailableReaction;
+
+//@description Represents a list of reactions that can be added to a message
+//@top_reactions List of reactions to be shown at the top
+//@recent_reactions List of recently used reactions
+//@popular_reactions List of popular reactions
+//@allow_custom_emoji True, if custom emoji reactions could be added by Telegram Premium subscribers
+availableReactions top_reactions:vector<availableReaction> recent_reactions:vector<availableReaction> popular_reactions:vector<availableReaction> allow_custom_emoji:Bool = AvailableReactions;
+
+//@description Contains information about a emoji reaction
+//@emoji Text representation of the reaction
+//@title Reaction title
+//@is_active True, if the reaction can be added to new messages and enabled in chats
+//@static_icon Static icon for the reaction
+//@appear_animation Appear animation for the reaction
+//@select_animation Select animation for the reaction
+//@activate_animation Activate animation for the reaction
+//@effect_animation Effect animation for the reaction
+//@around_animation Around animation for the reaction; may be null
+//@center_animation Center animation for the reaction; may be null
+emojiReaction emoji:string title:string is_active:Bool static_icon:sticker appear_animation:sticker select_animation:sticker activate_animation:sticker effect_animation:sticker around_animation:sticker center_animation:sticker = EmojiReaction;
//@description Represents a list of animations @animations List of animations
animations animations:vector<animation> = Animations;
+//@class DiceStickers @description Contains animated stickers which must be used for dice animation rendering
+
+//@description A regular animated sticker @sticker The animated sticker with the dice animation
+diceStickersRegular sticker:sticker = DiceStickers;
+
+//@description Animated stickers to be combined into a slot machine
+//@background The animated sticker with the slot machine background. The background animation must start playing after all reel animations finish
+//@lever The animated sticker with the lever animation. The lever animation must play once in the initial dice state
+//@left_reel The animated sticker with the left reel
+//@center_reel The animated sticker with the center reel
+//@right_reel The animated sticker with the right reel
+diceStickersSlotMachine background:sticker lever:sticker left_reel:sticker center_reel:sticker right_reel:sticker = DiceStickers;
+
+
//@description Represents the result of an ImportContacts request @user_ids User identifiers of the imported contacts in the same order as they were specified in the request; 0 if the contact is not yet a registered user
//@importer_count The number of users that imported the corresponding contact; 0 for already registered users or if unavailable
-importedContacts user_ids:vector<int32> importer_count:vector<int32> = ImportedContacts;
+importedContacts user_ids:vector<int53> importer_count:vector<int32> = ImportedContacts;
-//@class InputInlineQueryResult @description Represents a single result of an inline query; for bots only
+//@class SpeechRecognitionResult @description Describes result of speech recognition in a voice note
+
+//@description The speech recognition is ongoing @partial_text Partially recognized text
+speechRecognitionResultPending partial_text:string = SpeechRecognitionResult;
+
+//@description The speech recognition successfully finished @text Recognized text
+speechRecognitionResultText text:string = SpeechRecognitionResult;
+
+//@description The speech recognition failed @error Received error
+speechRecognitionResultError error:error = SpeechRecognitionResult;
+
+
+//@description Describes a color to highlight a bot added to attachment menu @light_color Color in the RGB24 format for light themes @dark_color Color in the RGB24 format for dark themes
+attachmentMenuBotColor light_color:int32 dark_color:int32 = AttachmentMenuBotColor;
-//@description Represents a link to an animated GIF @id Unique identifier of the query result @title Title of the query result @thumbnail_url URL of the static result thumbnail (JPEG or GIF), if it exists
-//@gif_url The URL of the GIF-file (file size must not exceed 1MB) @gif_duration Duration of the GIF, in seconds @gif_width Width of the GIF @gif_height Height of the GIF
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageAnimation, InputMessageLocation, InputMessageVenue or InputMessageContact
-inputInlineQueryResultAnimatedGif id:string title:string thumbnail_url:string gif_url:string gif_duration:int32 gif_width:int32 gif_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
+//@description Represents a bot added to attachment menu
+//@bot_user_id User identifier of the bot added to attachment menu
+//@supports_self_chat True, if the bot supports opening from attachment menu in the chat with the bot
+//@supports_user_chats True, if the bot supports opening from attachment menu in private chats with ordinary users
+//@supports_bot_chats True, if the bot supports opening from attachment menu in private chats with other bots
+//@supports_group_chats True, if the bot supports opening from attachment menu in basic group and supergroup chats
+//@supports_channel_chats True, if the bot supports opening from attachment menu in channel chats
+//@supports_settings True, if the bot supports "settings_button_pressed" event
+//@name Name for the bot in attachment menu
+//@name_color Color to highlight selected name of the bot if appropriate; may be null
+//@default_icon Default attachment menu icon for the bot in SVG format; may be null
+//@ios_static_icon Attachment menu icon for the bot in SVG format for the official iOS app; may be null
+//@ios_animated_icon Attachment menu icon for the bot in TGS format for the official iOS app; may be null
+//@android_icon Attachment menu icon for the bot in TGS format for the official Android app; may be null
+//@macos_icon Attachment menu icon for the bot in TGS format for the official native macOS app; may be null
+//@icon_color Color to highlight selected icon of the bot if appropriate; may be null
+//@web_app_placeholder Default placeholder for opened Web Apps in SVG format; may be null
+attachmentMenuBot bot_user_id:int53 supports_self_chat:Bool supports_user_chats:Bool supports_bot_chats:Bool supports_group_chats:Bool supports_channel_chats:Bool supports_settings:Bool name:string name_color:attachmentMenuBotColor default_icon:file ios_static_icon:file ios_animated_icon:file android_icon:file macos_icon:file icon_color:attachmentMenuBotColor web_app_placeholder:file = AttachmentMenuBot;
-//@description Represents a link to an animated (i.e. without sound) H.264/MPEG-4 AVC video @id Unique identifier of the query result @title Title of the result @thumbnail_url URL of the static result thumbnail (JPEG or GIF), if it exists
-//@mpeg4_url The URL of the MPEG4-file (file size must not exceed 1MB) @mpeg4_duration Duration of the video, in seconds @mpeg4_width Width of the video @mpeg4_height Height of the video
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageAnimation, InputMessageLocation, InputMessageVenue or InputMessageContact
-inputInlineQueryResultAnimatedMpeg4 id:string title:string thumbnail_url:string mpeg4_url:string mpeg4_duration:int32 mpeg4_width:int32 mpeg4_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
+//@description Information about the message sent by answerWebAppQuery @inline_message_id Identifier of the sent inline message, if known
+sentWebAppMessage inline_message_id:string = SentWebAppMessage;
+
+
+//@description Contains an HTTP URL @url The URL
+httpUrl url:string = HttpUrl;
+
+
+//@class InputInlineQueryResult @description Represents a single result of an inline query; for bots only
+
+//@description Represents a link to an animated GIF or an animated (i.e., without sound) H.264/MPEG-4 AVC video
+//@id Unique identifier of the query result @title Title of the query result
+//@thumbnail_url URL of the result thumbnail (JPEG, GIF, or MPEG4), if it exists @thumbnail_mime_type MIME type of the video thumbnail. If non-empty, must be one of "image/jpeg", "image/gif" and "video/mp4"
+//@video_url The URL of the video file (file size must not exceed 1MB) @video_mime_type MIME type of the video file. Must be one of "image/gif" and "video/mp4"
+//@video_duration Duration of the video, in seconds @video_width Width of the video @video_height Height of the video
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageAnimation, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
+inputInlineQueryResultAnimation id:string title:string thumbnail_url:string thumbnail_mime_type:string video_url:string video_mime_type:string video_duration:int32 video_width:int32 video_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@description Represents a link to an article or web page @id Unique identifier of the query result @url URL of the result, if it exists @hide_url True, if the URL must be not shown @title Title of the result
//@param_description A short description of the result @thumbnail_url URL of the result thumbnail, if it exists @thumbnail_width Thumbnail width, if known @thumbnail_height Thumbnail height, if known
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultArticle id:string url:string hide_url:Bool title:string description:string thumbnail_url:string thumbnail_width:int32 thumbnail_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@description Represents a link to an MP3 audio file @id Unique identifier of the query result @title Title of the audio file @performer Performer of the audio file
//@audio_url The URL of the audio file @audio_duration Audio file duration, in seconds
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageAudio, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageAudio, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultAudio id:string title:string performer:string audio_url:string audio_duration:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@description Represents a user contact @id Unique identifier of the query result @contact User contact @thumbnail_url URL of the result thumbnail, if it exists @thumbnail_width Thumbnail width, if known @thumbnail_height Thumbnail height, if known
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultContact id:string contact:contact thumbnail_url:string thumbnail_width:int32 thumbnail_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@description Represents a link to a file @id Unique identifier of the query result @title Title of the resulting file @param_description Short description of the result, if known @document_url URL of the file @mime_type MIME type of the file content; only "application/pdf" and "application/zip" are currently allowed
//@thumbnail_url The URL of the file thumbnail, if it exists @thumbnail_width Width of the thumbnail @thumbnail_height Height of the thumbnail
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageDocument, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageDocument, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultDocument id:string title:string description:string document_url:string mime_type:string thumbnail_url:string thumbnail_width:int32 thumbnail_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
-//@description Represents a game @id Unique identifier of the query result @game_short_name Short name of the game @reply_markup Message reply markup. Must be of type replyMarkupInlineKeyboard or null
+//@description Represents a game @id Unique identifier of the query result @game_short_name Short name of the game @reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
inputInlineQueryResultGame id:string game_short_name:string reply_markup:ReplyMarkup = InputInlineQueryResult;
-//@description Represents a point on the map @id Unique identifier of the query result @location Location result @live_period Amount of time relative to the message sent time until the location can be updated, in seconds @title Title of the result @thumbnail_url URL of the result thumbnail, if it exists @thumbnail_width Thumbnail width, if known @thumbnail_height Thumbnail height, if known
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@description Represents a point on the map @id Unique identifier of the query result @location Location result
+//@live_period Amount of time relative to the message sent time until the location can be updated, in seconds
+//@title Title of the result @thumbnail_url URL of the result thumbnail, if it exists @thumbnail_width Thumbnail width, if known @thumbnail_height Thumbnail height, if known
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultLocation id:string location:location live_period:int32 title:string thumbnail_url:string thumbnail_width:int32 thumbnail_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@description Represents link to a JPEG image @id Unique identifier of the query result @title Title of the result, if known @param_description A short description of the result, if known @thumbnail_url URL of the photo thumbnail, if it exists
//@photo_url The URL of the JPEG photo (photo size must not exceed 5MB) @photo_width Width of the photo @photo_height Height of the photo
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessagePhoto, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessagePhoto, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultPhoto id:string title:string description:string thumbnail_url:string photo_url:string photo_width:int32 photo_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
-//@description Represents a link to a WEBP sticker @id Unique identifier of the query result @thumbnail_url URL of the sticker thumbnail, if it exists
-//@sticker_url The URL of the WEBP sticker (sticker file size must not exceed 5MB) @sticker_width Width of the sticker @sticker_height Height of the sticker
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, inputMessageSticker, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@description Represents a link to a WEBP, TGS, or WEBM sticker @id Unique identifier of the query result @thumbnail_url URL of the sticker thumbnail, if it exists
+//@sticker_url The URL of the WEBP, TGS, or WEBM sticker (sticker file size must not exceed 5MB) @sticker_width Width of the sticker @sticker_height Height of the sticker
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageSticker, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultSticker id:string thumbnail_url:string sticker_url:string sticker_width:int32 sticker_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@description Represents information about a venue @id Unique identifier of the query result @venue Venue result @thumbnail_url URL of the result thumbnail, if it exists @thumbnail_width Thumbnail width, if known @thumbnail_height Thumbnail height, if known
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultVenue id:string venue:venue thumbnail_url:string thumbnail_width:int32 thumbnail_height:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@description Represents a link to a page containing an embedded video player or a video file @id Unique identifier of the query result @title Title of the result @param_description A short description of the result, if known
//@thumbnail_url The URL of the video thumbnail (JPEG), if it exists @video_url URL of the embedded video player or video file @mime_type MIME type of the content of the video URL, only "text/html" or "video/mp4" are currently supported
//@video_width Width of the video @video_height Height of the video @video_duration Video duration, in seconds
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageVideo, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageVideo, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultVideo id:string title:string description:string thumbnail_url:string video_url:string mime_type:string video_width:int32 video_height:int32 video_duration:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@description Represents a link to an opus-encoded audio file within an OGG container, single channel audio @id Unique identifier of the query result @title Title of the voice note
//@voice_note_url The URL of the voice note file @voice_note_duration Duration of the voice note, in seconds
-//@reply_markup The message reply markup. Must be of type replyMarkupInlineKeyboard or null
-//@input_message_content The content of the message to be sent. Must be one of the following types: InputMessageText, InputMessageVoiceNote, InputMessageLocation, InputMessageVenue or InputMessageContact
+//@reply_markup The message reply markup; pass null if none. Must be of type replyMarkupInlineKeyboard or null
+//@input_message_content The content of the message to be sent. Must be one of the following types: inputMessageText, inputMessageVoiceNote, inputMessageInvoice, inputMessageLocation, inputMessageVenue or inputMessageContact
inputInlineQueryResultVoiceNote id:string title:string voice_note_url:string voice_note_duration:int32 reply_markup:ReplyMarkup input_message_content:InputMessageContent = InputInlineQueryResult;
//@class InlineQueryResult @description Represents a single result of an inline query
//@description Represents a link to an article or web page @id Unique identifier of the query result @url URL of the result, if it exists @hide_url True, if the URL must be not shown @title Title of the result
-//@param_description A short description of the result @thumbnail Result thumbnail; may be null
-inlineQueryResultArticle id:string url:string hide_url:Bool title:string description:string thumbnail:photoSize = InlineQueryResult;
+//@param_description A short description of the result @thumbnail Result thumbnail in JPEG format; may be null
+inlineQueryResultArticle id:string url:string hide_url:Bool title:string description:string thumbnail:thumbnail = InlineQueryResult;
-//@description Represents a user contact @id Unique identifier of the query result @contact A user contact @thumbnail Result thumbnail; may be null
-inlineQueryResultContact id:string contact:contact thumbnail:photoSize = InlineQueryResult;
+//@description Represents a user contact @id Unique identifier of the query result @contact A user contact @thumbnail Result thumbnail in JPEG format; may be null
+inlineQueryResultContact id:string contact:contact thumbnail:thumbnail = InlineQueryResult;
-//@description Represents a point on the map @id Unique identifier of the query result @location Location result @title Title of the result @thumbnail Result thumbnail; may be null
-inlineQueryResultLocation id:string location:location title:string thumbnail:photoSize = InlineQueryResult;
+//@description Represents a point on the map @id Unique identifier of the query result @location Location result @title Title of the result @thumbnail Result thumbnail in JPEG format; may be null
+inlineQueryResultLocation id:string location:location title:string thumbnail:thumbnail = InlineQueryResult;
-//@description Represents information about a venue @id Unique identifier of the query result @venue Venue result @thumbnail Result thumbnail; may be null
-inlineQueryResultVenue id:string venue:venue thumbnail:photoSize = InlineQueryResult;
+//@description Represents information about a venue @id Unique identifier of the query result @venue Venue result @thumbnail Result thumbnail in JPEG format; may be null
+inlineQueryResultVenue id:string venue:venue thumbnail:thumbnail = InlineQueryResult;
//@description Represents information about a game @id Unique identifier of the query result @game Game result
inlineQueryResultGame id:string game:game = InlineQueryResult;
@@ -1322,20 +2869,23 @@ inlineQueryResultVoiceNote id:string voice_note:voiceNote title:string = InlineQ
//@description Represents the results of the inline query. Use sendInlineQueryResultMessage to send the result of the query @inline_query_id Unique identifier of the inline query @next_offset The offset for the next request. If empty, there are no more results @results Results of the query
-//@switch_pm_text If non-empty, this text should be shown on the button, which opens a private chat with the bot and sends the bot a start message with the switch_pm_parameter @switch_pm_parameter Parameter for the bot start message
+//@switch_pm_text If non-empty, this text must be shown on the button, which opens a private chat with the bot and sends the bot a start message with the switch_pm_parameter @switch_pm_parameter Parameter for the bot start message
inlineQueryResults inline_query_id:int64 next_offset:string results:vector<InlineQueryResult> switch_pm_text:string switch_pm_parameter:string = InlineQueryResults;
//@class CallbackQueryPayload @description Represents a payload of a callback query
-//@description The payload from a general callback button @data Data that was attached to the callback button
+//@description The payload for a general callback button @data Data that was attached to the callback button
callbackQueryPayloadData data:bytes = CallbackQueryPayload;
-//@description The payload from a game callback button @game_short_name A short name of the game that was attached to the callback button
+//@description The payload for a callback button requiring password @password The 2-step verification password for the current user @data Data that was attached to the callback button
+callbackQueryPayloadDataWithPassword password:string data:bytes = CallbackQueryPayload;
+
+//@description The payload for a game callback button @game_short_name A short name of the game that was attached to the callback button
callbackQueryPayloadGame game_short_name:string = CallbackQueryPayload;
-//@description Contains a bot's answer to a callback query @text Text of the answer @show_alert True, if an alert should be shown to the user instead of a toast notification @url URL to be opened
+//@description Contains a bot's answer to a callback query @text Text of the answer @show_alert True, if an alert must be shown to the user instead of a toast notification @url URL to be opened
callbackQueryAnswer text:string show_alert:Bool url:string = CallbackQueryAnswer;
@@ -1344,7 +2894,7 @@ customRequestResult result:string = CustomRequestResult;
//@description Contains one row of the game high score table @position Position in the high score table @user_id User identifier @score User score
-gameHighScore position:int32 user_id:int32 score:int32 = GameHighScore;
+gameHighScore position:int32 user_id:int53 score:int32 = GameHighScore;
//@description Contains a list of game high scores @scores A list of game high scores
gameHighScores scores:vector<gameHighScore> = GameHighScores;
@@ -1361,116 +2911,430 @@ chatEventMessageDeleted message:message = ChatEventAction;
//@description A message was pinned @message Pinned message
chatEventMessagePinned message:message = ChatEventAction;
-//@description A message was unpinned
-chatEventMessageUnpinned = ChatEventAction;
+//@description A message was unpinned @message Unpinned message
+chatEventMessageUnpinned message:message = ChatEventAction;
+
+//@description A poll in a message was stopped @message The message with the poll
+chatEventPollStopped message:message = ChatEventAction;
//@description A new member joined the chat
chatEventMemberJoined = ChatEventAction;
-//@description A member left the chat
-chatEventMemberLeft = ChatEventAction;
+//@description A new member joined the chat via an invite link @invite_link Invite link used to join the chat
+chatEventMemberJoinedByInviteLink invite_link:chatInviteLink = ChatEventAction;
+
+//@description A new member was accepted to the chat by an administrator @approver_user_id User identifier of the chat administrator, approved user join request @invite_link Invite link used to join the chat; may be null
+chatEventMemberJoinedByRequest approver_user_id:int53 invite_link:chatInviteLink = ChatEventAction;
//@description A new chat member was invited @user_id New member user identifier @status New member status
-chatEventMemberInvited user_id:int32 status:ChatMemberStatus = ChatEventAction;
+chatEventMemberInvited user_id:int53 status:ChatMemberStatus = ChatEventAction;
-//@description A chat member has gained/lost administrator status, or the list of their administrator privileges has changed @user_id Chat member user identifier @old_status Previous status of the chat member @new_status New status of the chat member
-chatEventMemberPromoted user_id:int32 old_status:ChatMemberStatus new_status:ChatMemberStatus = ChatEventAction;
+//@description A member left the chat
+chatEventMemberLeft = ChatEventAction;
-//@description A chat member was restricted/unrestricted or banned/unbanned, or the list of their restrictions has changed @user_id Chat member user identifier @old_status Previous status of the chat member @new_status New status of the chat member
-chatEventMemberRestricted user_id:int32 old_status:ChatMemberStatus new_status:ChatMemberStatus = ChatEventAction;
+//@description A chat member has gained/lost administrator status, or the list of their administrator privileges has changed @user_id Affected chat member user identifier @old_status Previous status of the chat member @new_status New status of the chat member
+chatEventMemberPromoted user_id:int53 old_status:ChatMemberStatus new_status:ChatMemberStatus = ChatEventAction;
-//@description The chat title was changed @old_title Previous chat title @new_title New chat title
-chatEventTitleChanged old_title:string new_title:string = ChatEventAction;
+//@description A chat member was restricted/unrestricted or banned/unbanned, or the list of their restrictions has changed @member_id Affected chat member identifier @old_status Previous status of the chat member @new_status New status of the chat member
+chatEventMemberRestricted member_id:MessageSender old_status:ChatMemberStatus new_status:ChatMemberStatus = ChatEventAction;
+
+//@description The chat available reactions were changed @old_available_reactions Previous chat available reactions @new_available_reactions New chat available reactions
+chatEventAvailableReactionsChanged old_available_reactions:ChatAvailableReactions new_available_reactions:ChatAvailableReactions = ChatEventAction;
//@description The chat description was changed @old_description Previous chat description @new_description New chat description
chatEventDescriptionChanged old_description:string new_description:string = ChatEventAction;
-//@description The chat username was changed @old_username Previous chat username @new_username New chat username
-chatEventUsernameChanged old_username:string new_username:string = ChatEventAction;
+//@description The linked chat of a supergroup was changed @old_linked_chat_id Previous supergroup linked chat identifier @new_linked_chat_id New supergroup linked chat identifier
+chatEventLinkedChatChanged old_linked_chat_id:int53 new_linked_chat_id:int53 = ChatEventAction;
+
+//@description The supergroup location was changed @old_location Previous location; may be null @new_location New location; may be null
+chatEventLocationChanged old_location:chatLocation new_location:chatLocation = ChatEventAction;
+
+//@description The message TTL was changed @old_message_ttl Previous value of message_ttl @new_message_ttl New value of message_ttl
+chatEventMessageTtlChanged old_message_ttl:int32 new_message_ttl:int32 = ChatEventAction;
+
+//@description The chat permissions was changed @old_permissions Previous chat permissions @new_permissions New chat permissions
+chatEventPermissionsChanged old_permissions:chatPermissions new_permissions:chatPermissions = ChatEventAction;
//@description The chat photo was changed @old_photo Previous chat photo value; may be null @new_photo New chat photo value; may be null
chatEventPhotoChanged old_photo:chatPhoto new_photo:chatPhoto = ChatEventAction;
-//@description The anyone_can_invite setting of a supergroup chat was toggled @anyone_can_invite New value of anyone_can_invite
-chatEventInvitesToggled anyone_can_invite:Bool = ChatEventAction;
-
-//@description The sign_messages setting of a channel was toggled @sign_messages New value of sign_messages
-chatEventSignMessagesToggled sign_messages:Bool = ChatEventAction;
+//@description The slow_mode_delay setting of a supergroup was changed @old_slow_mode_delay Previous value of slow_mode_delay, in seconds @new_slow_mode_delay New value of slow_mode_delay, in seconds
+chatEventSlowModeDelayChanged old_slow_mode_delay:int32 new_slow_mode_delay:int32 = ChatEventAction;
//@description The supergroup sticker set was changed @old_sticker_set_id Previous identifier of the chat sticker set; 0 if none @new_sticker_set_id New identifier of the chat sticker set; 0 if none
chatEventStickerSetChanged old_sticker_set_id:int64 new_sticker_set_id:int64 = ChatEventAction;
+//@description The chat title was changed @old_title Previous chat title @new_title New chat title
+chatEventTitleChanged old_title:string new_title:string = ChatEventAction;
+
+//@description The chat editable username was changed @old_username Previous chat username @new_username New chat username
+chatEventUsernameChanged old_username:string new_username:string = ChatEventAction;
+
+//@description The chat active usernames were changed @old_usernames Previous list of active usernames @new_usernames New list of active usernames
+chatEventActiveUsernamesChanged old_usernames:vector<string> new_usernames:vector<string> = ChatEventAction;
+
+//@description The has_protected_content setting of a channel was toggled @has_protected_content New value of has_protected_content
+chatEventHasProtectedContentToggled has_protected_content:Bool = ChatEventAction;
+
+//@description The can_invite_users permission of a supergroup chat was toggled @can_invite_users New value of can_invite_users permission
+chatEventInvitesToggled can_invite_users:Bool = ChatEventAction;
+
//@description The is_all_history_available setting of a supergroup was toggled @is_all_history_available New value of is_all_history_available
chatEventIsAllHistoryAvailableToggled is_all_history_available:Bool = ChatEventAction;
-//@description Represents a chat event @id Chat event identifier @date Point in time (Unix timestamp) when the event happened @user_id Identifier of the user who performed the action that triggered the event @action Action performed by the user
-chatEvent id:int64 date:int32 user_id:int32 action:ChatEventAction = ChatEvent;
+//@description The sign_messages setting of a channel was toggled @sign_messages New value of sign_messages
+chatEventSignMessagesToggled sign_messages:Bool = ChatEventAction;
+
+//@description A chat invite link was edited @old_invite_link Previous information about the invite link @new_invite_link New information about the invite link
+chatEventInviteLinkEdited old_invite_link:chatInviteLink new_invite_link:chatInviteLink = ChatEventAction;
+
+//@description A chat invite link was revoked @invite_link The invite link
+chatEventInviteLinkRevoked invite_link:chatInviteLink = ChatEventAction;
+
+//@description A revoked chat invite link was deleted @invite_link The invite link
+chatEventInviteLinkDeleted invite_link:chatInviteLink = ChatEventAction;
+
+//@description A video chat was created @group_call_id Identifier of the video chat. The video chat can be received through the method getGroupCall
+chatEventVideoChatCreated group_call_id:int32 = ChatEventAction;
+
+//@description A video chat was ended @group_call_id Identifier of the video chat. The video chat can be received through the method getGroupCall
+chatEventVideoChatEnded group_call_id:int32 = ChatEventAction;
+
+//@description The mute_new_participants setting of a video chat was toggled @mute_new_participants New value of the mute_new_participants setting
+chatEventVideoChatMuteNewParticipantsToggled mute_new_participants:Bool = ChatEventAction;
+
+//@description A video chat participant was muted or unmuted @participant_id Identifier of the affected group call participant @is_muted New value of is_muted
+chatEventVideoChatParticipantIsMutedToggled participant_id:MessageSender is_muted:Bool = ChatEventAction;
+
+//@description A video chat participant volume level was changed @participant_id Identifier of the affected group call participant @volume_level New value of volume_level; 1-20000 in hundreds of percents
+chatEventVideoChatParticipantVolumeLevelChanged participant_id:MessageSender volume_level:int32 = ChatEventAction;
+
+//@description The is_forum setting of a channel was toggled @is_forum New value of is_forum
+chatEventIsForumToggled is_forum:Bool = ChatEventAction;
+
+//@description A new forum topic was created @topic_info Information about the topic
+chatEventForumTopicCreated topic_info:forumTopicInfo = ChatEventAction;
+
+//@description A forum topic was edited @old_topic_info Old information about the topic @new_topic_info New information about the topic
+chatEventForumTopicEdited old_topic_info:forumTopicInfo new_topic_info:forumTopicInfo = ChatEventAction;
+
+//@description A forum topic was closed or reopened @topic_info New information about the topic
+chatEventForumTopicToggleIsClosed topic_info:forumTopicInfo = ChatEventAction;
+
+//@description A forum topic was deleted @topic_info Information about the topic
+chatEventForumTopicDeleted topic_info:forumTopicInfo = ChatEventAction;
+
+//@description A pinned forum topic was changed @old_topic_info Information about the old pinned topic; may be null @new_topic_info Information about the new pinned topic; may be null
+chatEventForumTopicPinned old_topic_info:forumTopicInfo new_topic_info:forumTopicInfo = ChatEventAction;
+
+//@description Represents a chat event @id Chat event identifier @date Point in time (Unix timestamp) when the event happened @member_id Identifier of the user or chat who performed the action @action The action
+chatEvent id:int64 date:int32 member_id:MessageSender action:ChatEventAction = ChatEvent;
//@description Contains a list of chat events @events List of events
chatEvents events:vector<chatEvent> = ChatEvents;
//@description Represents a set of filters used to obtain a chat event log
-//@message_edits True, if message edits should be returned
-//@message_deletions True, if message deletions should be returned
-//@message_pins True, if pin/unpin events should be returned
-//@member_joins True, if members joining events should be returned
-//@member_leaves True, if members leaving events should be returned
-//@member_invites True, if invited member events should be returned
-//@member_promotions True, if member promotion/demotion events should be returned
-//@member_restrictions True, if member restricted/unrestricted/banned/unbanned events should be returned
-//@info_changes True, if changes in chat information should be returned
-//@setting_changes True, if changes in chat settings should be returned
-chatEventLogFilters message_edits:Bool message_deletions:Bool message_pins:Bool member_joins:Bool member_leaves:Bool member_invites:Bool member_promotions:Bool member_restrictions:Bool info_changes:Bool setting_changes:Bool = ChatEventLogFilters;
+//@message_edits True, if message edits need to be returned
+//@message_deletions True, if message deletions need to be returned
+//@message_pins True, if pin/unpin events need to be returned
+//@member_joins True, if members joining events need to be returned
+//@member_leaves True, if members leaving events need to be returned
+//@member_invites True, if invited member events need to be returned
+//@member_promotions True, if member promotion/demotion events need to be returned
+//@member_restrictions True, if member restricted/unrestricted/banned/unbanned events need to be returned
+//@info_changes True, if changes in chat information need to be returned
+//@setting_changes True, if changes in chat settings need to be returned
+//@invite_link_changes True, if changes to invite links need to be returned
+//@video_chat_changes True, if video chat actions need to be returned
+//@forum_changes True, if forum-related actions need to be returned
+chatEventLogFilters message_edits:Bool message_deletions:Bool message_pins:Bool member_joins:Bool member_leaves:Bool member_invites:Bool member_promotions:Bool member_restrictions:Bool info_changes:Bool setting_changes:Bool invite_link_changes:Bool video_chat_changes:Bool forum_changes:Bool = ChatEventLogFilters;
+
+
+//@class LanguagePackStringValue @description Represents the value of a string in a language pack
+
+//@description An ordinary language pack string @value String value
+languagePackStringValueOrdinary value:string = LanguagePackStringValue;
+
+//@description A language pack string which has different forms based on the number of some object it mentions. See https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html for more information
+//@zero_value Value for zero objects @one_value Value for one object @two_value Value for two objects
+//@few_value Value for few objects @many_value Value for many objects @other_value Default value
+languagePackStringValuePluralized zero_value:string one_value:string two_value:string few_value:string many_value:string other_value:string = LanguagePackStringValue;
+
+//@description A deleted language pack string, the value must be taken from the built-in English language pack
+languagePackStringValueDeleted = LanguagePackStringValue;
+
+
+//@description Represents one language pack string @key String key @value String value; pass null if the string needs to be taken from the built-in English language pack
+languagePackString key:string value:LanguagePackStringValue = LanguagePackString;
+
+//@description Contains a list of language pack strings @strings A list of language pack strings
+languagePackStrings strings:vector<languagePackString> = LanguagePackStrings;
+
+//@description Contains information about a language pack @id Unique language pack identifier
+//@base_language_pack_id Identifier of a base language pack; may be empty. If a string is missed in the language pack, then it must be fetched from base language pack. Unsupported in custom language packs
+//@name Language name @native_name Name of the language in that language
+//@plural_code A language code to be used to apply plural forms. See https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html for more information
+//@is_official True, if the language pack is official @is_rtl True, if the language pack strings are RTL @is_beta True, if the language pack is a beta language pack
+//@is_installed True, if the language pack is installed by the current user
+//@total_string_count Total number of non-deleted strings from the language pack @translated_string_count Total number of translated strings from the language pack
+//@local_string_count Total number of non-deleted strings from the language pack available locally @translation_url Link to language translation interface; empty for custom local language packs
+languagePackInfo id:string base_language_pack_id:string name:string native_name:string plural_code:string is_official:Bool is_rtl:Bool is_beta:Bool is_installed:Bool total_string_count:int32 translated_string_count:int32 local_string_count:int32 translation_url:string = LanguagePackInfo;
+
+//@description Contains information about the current localization target @language_packs List of available language packs for this application
+localizationTargetInfo language_packs:vector<languagePackInfo> = LocalizationTargetInfo;
+
+
+//@class PremiumLimitType @description Describes type of a limit, increased for Premium users
+
+//@description The maximum number of joined supergroups and channels
+premiumLimitTypeSupergroupCount = PremiumLimitType;
+
+//@description The maximum number of pinned chats in the main chat list
+premiumLimitTypePinnedChatCount = PremiumLimitType;
+
+//@description The maximum number of created public chats
+premiumLimitTypeCreatedPublicChatCount = PremiumLimitType;
+
+//@description The maximum number of saved animations
+premiumLimitTypeSavedAnimationCount = PremiumLimitType;
+
+//@description The maximum number of favorite stickers
+premiumLimitTypeFavoriteStickerCount = PremiumLimitType;
+
+//@description The maximum number of chat filters
+premiumLimitTypeChatFilterCount = PremiumLimitType;
+
+//@description The maximum number of pinned and always included, or always excluded chats in a chat filter
+premiumLimitTypeChatFilterChosenChatCount = PremiumLimitType;
+
+//@description The maximum number of pinned chats in the archive chat list
+premiumLimitTypePinnedArchivedChatCount = PremiumLimitType;
+
+//@description The maximum length of sent media caption
+premiumLimitTypeCaptionLength = PremiumLimitType;
+
+//@description The maximum length of the user's bio
+premiumLimitTypeBioLength = PremiumLimitType;
+
+
+//@class PremiumFeature @description Describes a feature available to Premium users
+
+//@description Increased limits
+premiumFeatureIncreasedLimits = PremiumFeature;
+
+//@description Increased maximum upload file size
+premiumFeatureIncreasedUploadFileSize = PremiumFeature;
+
+//@description Improved download speed
+premiumFeatureImprovedDownloadSpeed = PremiumFeature;
+
+//@description The ability to convert voice notes to text
+premiumFeatureVoiceRecognition = PremiumFeature;
+
+//@description Disabled ads
+premiumFeatureDisabledAds = PremiumFeature;
+
+//@description Allowed to use more reactions
+premiumFeatureUniqueReactions = PremiumFeature;
+
+//@description Allowed to use premium stickers with unique effects
+premiumFeatureUniqueStickers = PremiumFeature;
+
+//@description Allowed to use custom emoji stickers in message texts and captions
+premiumFeatureCustomEmoji = PremiumFeature;
+
+//@description Ability to change position of the main chat list, archive and mute all new chats from non-contacts, and completely disable notifications about the user's contacts joined Telegram
+premiumFeatureAdvancedChatManagement = PremiumFeature;
+
+//@description A badge in the user's profile
+premiumFeatureProfileBadge = PremiumFeature;
+
+//@description A emoji status shown along with the user's name
+premiumFeatureEmojiStatus = PremiumFeature;
+
+//@description Profile photo animation on message and chat screens
+premiumFeatureAnimatedProfilePhoto = PremiumFeature;
+
+//@description The ability to set a custom emoji as a forum topic icon
+premiumFeatureForumTopicIcon = PremiumFeature;
+
+//@description Allowed to set a premium appllication icons
+premiumFeatureAppIcons = PremiumFeature;
+
+
+//@description Contains information about a limit, increased for Premium users @type The type of the limit @default_value Default value of the limit @premium_value Value of the limit for Premium users
+premiumLimit type:PremiumLimitType default_value:int32 premium_value:int32 = PremiumLimit;
+
+//@description Contains information about features, available to Premium users @features The list of available features @limits The list of limits, increased for Premium users
+//@payment_link An internal link to be opened to pay for Telegram Premium if store payment isn't possible; may be null if direct payment isn't available
+premiumFeatures features:vector<PremiumFeature> limits:vector<premiumLimit> payment_link:InternalLinkType = PremiumFeatures;
+
+
+//@class PremiumSource @description Describes a source from which the Premium features screen is opened
+
+//@description A limit was exceeded @limit_type Type of the exceeded limit
+premiumSourceLimitExceeded limit_type:PremiumLimitType = PremiumSource;
+
+//@description A user tried to use a Premium feature @feature The used feature
+premiumSourceFeature feature:PremiumFeature = PremiumSource;
+
+//@description A user opened an internal link of the type internalLinkTypePremiumFeatures @referrer The referrer from the link
+premiumSourceLink referrer:string = PremiumSource;
+
+//@description A user opened the Premium features screen from settings
+premiumSourceSettings = PremiumSource;
+
+//@description Describes a promotion animation for a Premium feature @feature Premium feature @animation Promotion animation for the feature
+premiumFeaturePromotionAnimation feature:PremiumFeature animation:animation = PremiumFeaturePromotionAnimation;
-//@class DeviceToken @description Represents a data needed to subscribe for push notifications. To use specific push notification service, you must specify the correct application platform and upload valid server authentication data at https://my.telegram.org
+//@description Contains state of Telegram Premium subscription and promotion videos for Premium features
+//@state Text description of the state of the current Premium subscription; may be empty if the current user has no Telegram Premium subscription
+//@payment_options The list of available options for buying Telegram Premium
+//@animations The list of available promotion animations for Premium features
+premiumState state:formattedText payment_options:vector<premiumPaymentOption> animations:vector<premiumFeaturePromotionAnimation> = PremiumState;
-//@description A token for Google Cloud Messaging @token Device registration token, may be empty to de-register a device
-deviceTokenGoogleCloudMessaging token:string = DeviceToken;
-//@description A token for Apple Push Notification service @device_token Device token, may be empty to de-register a device @is_app_sandbox True, if App Sandbox is enabled
+//@class StorePaymentPurpose @description Describes a purpose of an in-store payment
+
+//@description The user subscribed to Telegram Premium @is_restore Pass true if this is a restore of a Telegram Premium purchase; only for App Store
+storePaymentPurposePremiumSubscription is_restore:Bool = StorePaymentPurpose;
+
+//@description The user gifted Telegram Premium to another user @user_id Identifier of the user for which Premium was gifted @currency ISO 4217 currency code of the payment currency @amount Paid amount, in the smallest units of the currency
+storePaymentPurposeGiftedPremium user_id:int53 currency:string amount:int53 = StorePaymentPurpose;
+
+
+//@class DeviceToken @description Represents a data needed to subscribe for push notifications through registerDevice method. To use specific push notification service, the correct application platform must be specified and a valid server authentication data must be uploaded at https://my.telegram.org
+
+//@description A token for Firebase Cloud Messaging @token Device registration token; may be empty to deregister a device @encrypt True, if push notifications must be additionally encrypted
+deviceTokenFirebaseCloudMessaging token:string encrypt:Bool = DeviceToken;
+
+//@description A token for Apple Push Notification service @device_token Device token; may be empty to deregister a device @is_app_sandbox True, if App Sandbox is enabled
deviceTokenApplePush device_token:string is_app_sandbox:Bool = DeviceToken;
-//@description A token for Apple Push Notification service VoIP notifications @device_token Device token, may be empty to de-register a device @is_app_sandbox True, if App Sandbox is enabled
-deviceTokenApplePushVoIP device_token:string is_app_sandbox:Bool = DeviceToken;
+//@description A token for Apple Push Notification service VoIP notifications @device_token Device token; may be empty to deregister a device @is_app_sandbox True, if App Sandbox is enabled @encrypt True, if push notifications must be additionally encrypted
+deviceTokenApplePushVoIP device_token:string is_app_sandbox:Bool encrypt:Bool = DeviceToken;
-//@description A token for Windows Push Notification Services @access_token The access token that will be used to send notifications, may be empty to de-register a device
+//@description A token for Windows Push Notification Services @access_token The access token that will be used to send notifications; may be empty to deregister a device
deviceTokenWindowsPush access_token:string = DeviceToken;
-//@description A token for Microsoft Push Notification Service @channel_uri Push notification channel URI, may be empty to de-register a device
+//@description A token for Microsoft Push Notification Service @channel_uri Push notification channel URI; may be empty to deregister a device
deviceTokenMicrosoftPush channel_uri:string = DeviceToken;
-//@description A token for Microsoft Push Notification Service VoIP channel @channel_uri Push notification channel URI, may be empty to de-register a device
+//@description A token for Microsoft Push Notification Service VoIP channel @channel_uri Push notification channel URI; may be empty to deregister a device
deviceTokenMicrosoftPushVoIP channel_uri:string = DeviceToken;
-//@description A token for web Push API @endpoint Absolute URL exposed by the push service where the application server can send push messages, may be empty to de-register a device
+//@description A token for web Push API @endpoint Absolute URL exposed by the push service where the application server can send push messages; may be empty to deregister a device
//@p256dh_base64url Base64url-encoded P-256 elliptic curve Diffie-Hellman public key @auth_base64url Base64url-encoded authentication secret
deviceTokenWebPush endpoint:string p256dh_base64url:string auth_base64url:string = DeviceToken;
-//@description A token for Simple Push API for Firefox OS @endpoint Absolute URL exposed by the push service where the application server can send push messages, may be empty to de-register a device
+//@description A token for Simple Push API for Firefox OS @endpoint Absolute URL exposed by the push service where the application server can send push messages; may be empty to deregister a device
deviceTokenSimplePush endpoint:string = DeviceToken;
-//@description A token for Ubuntu Push Client service @token Token, may be empty to de-register a device
+//@description A token for Ubuntu Push Client service @token Token; may be empty to deregister a device
deviceTokenUbuntuPush token:string = DeviceToken;
-//@description A token for BlackBerry Push Service @token Token, may be empty to de-register a device
+//@description A token for BlackBerry Push Service @token Token; may be empty to deregister a device
deviceTokenBlackBerryPush token:string = DeviceToken;
-//@description A token for Tizen Push Service @reg_id Push service registration identifier, may be empty to de-register a device
+//@description A token for Tizen Push Service @reg_id Push service registration identifier; may be empty to deregister a device
deviceTokenTizenPush reg_id:string = DeviceToken;
-//@description Contains information about a wallpaper @id Unique persistent wallpaper identifier @sizes Available variants of the wallpaper in different sizes. These photos can only be downloaded; they can't be sent in a message @color Main color of the wallpaper in RGB24 format; should be treated as background color if no photos are specified
-wallpaper id:int32 sizes:vector<photoSize> color:int32 = Wallpaper;
+//@description Contains a globally unique push receiver identifier, which can be used to identify which account has received a push notification @id The globally unique identifier of push notification subscription
+pushReceiverId id:int64 = PushReceiverId;
+
+
+//@class BackgroundFill @description Describes a fill of a background
+
+//@description Describes a solid fill of a background @color A color of the background in the RGB24 format
+backgroundFillSolid color:int32 = BackgroundFill;
+
+//@description Describes a gradient fill of a background @top_color A top color of the background in the RGB24 format @bottom_color A bottom color of the background in the RGB24 format
+//@rotation_angle Clockwise rotation angle of the gradient, in degrees; 0-359. Must always be divisible by 45
+backgroundFillGradient top_color:int32 bottom_color:int32 rotation_angle:int32 = BackgroundFill;
+
+//@description Describes a freeform gradient fill of a background @colors A list of 3 or 4 colors of the freeform gradients in the RGB24 format
+backgroundFillFreeformGradient colors:vector<int32> = BackgroundFill;
+
+
+//@class BackgroundType @description Describes the type of a background
+
+//@description A wallpaper in JPEG format
+//@is_blurred True, if the wallpaper must be downscaled to fit in 450x450 square and then box-blurred with radius 12
+//@is_moving True, if the background needs to be slightly moved when device is tilted
+backgroundTypeWallpaper is_blurred:Bool is_moving:Bool = BackgroundType;
+
+//@description A PNG or TGV (gzipped subset of SVG with MIME type "application/x-tgwallpattern") pattern to be combined with the background fill chosen by the user
+//@fill Fill of the background
+//@intensity Intensity of the pattern when it is shown above the filled background; 0-100.
+//@is_inverted True, if the background fill must be applied only to the pattern itself. All other pixels are black in this case. For dark themes only
+//@is_moving True, if the background needs to be slightly moved when device is tilted
+backgroundTypePattern fill:BackgroundFill intensity:int32 is_inverted:Bool is_moving:Bool = BackgroundType;
+
+//@description A filled background @fill The background fill
+backgroundTypeFill fill:BackgroundFill = BackgroundType;
+
+
+//@description Describes a chat background
+//@id Unique background identifier
+//@is_default True, if this is one of default backgrounds
+//@is_dark True, if the background is dark and is recommended to be used with dark theme
+//@name Unique background name
+//@document Document with the background; may be null. Null only for filled backgrounds
+//@type Type of the background
+background id:int64 is_default:Bool is_dark:Bool name:string document:document type:BackgroundType = Background;
+
+//@description Contains a list of backgrounds @backgrounds A list of backgrounds
+backgrounds backgrounds:vector<background> = Backgrounds;
-//@description Contains a list of wallpapers @wallpapers A list of wallpapers
-wallpapers wallpapers:vector<wallpaper> = Wallpapers;
+
+//@class InputBackground @description Contains information about background to set
+
+//@description A background from a local file
+//@background Background file to use. Only inputFileLocal and inputFileGenerated are supported. The file must be in JPEG format for wallpapers and in PNG format for patterns
+inputBackgroundLocal background:InputFile = InputBackground;
+
+//@description A background from the server @background_id The background identifier
+inputBackgroundRemote background_id:int64 = InputBackground;
+
+
+//@description Describes theme settings
+//@accent_color Theme accent color in ARGB format
+//@background The background to be used in chats; may be null
+//@outgoing_message_fill The fill to be used as a background for outgoing messages
+//@animate_outgoing_message_fill If true, the freeform gradient fill needs to be animated on every sent message
+//@outgoing_message_accent_color Accent color of outgoing messages in ARGB format
+themeSettings accent_color:int32 background:background outgoing_message_fill:BackgroundFill animate_outgoing_message_fill:Bool outgoing_message_accent_color:int32 = ThemeSettings;
+
+
+//@description Describes a chat theme
+//@name Theme name
+//@light_settings Theme settings for a light chat theme
+//@dark_settings Theme settings for a dark chat theme
+chatTheme name:string light_settings:themeSettings dark_settings:themeSettings = ChatTheme;
//@description Contains a list of hashtags @hashtags A list of hashtags
hashtags hashtags:vector<string> = Hashtags;
+//@class CanTransferOwnershipResult @description Represents result of checking whether the current session can be used to transfer a chat ownership to another user
+
+//@description The session can be used
+canTransferOwnershipResultOk = CanTransferOwnershipResult;
+
+//@description The 2-step verification needs to be enabled first
+canTransferOwnershipResultPasswordNeeded = CanTransferOwnershipResult;
+
+//@description The 2-step verification was enabled recently, user needs to wait @retry_after Time left before the session can be used to transfer ownership of a chat, in seconds
+canTransferOwnershipResultPasswordTooFresh retry_after:int32 = CanTransferOwnershipResult;
+
+//@description The session was created recently, user needs to wait @retry_after Time left before the session can be used to transfer ownership of a chat, in seconds
+canTransferOwnershipResultSessionTooFresh retry_after:int32 = CanTransferOwnershipResult;
+
+
//@class CheckChatUsernameResult @description Represents result of checking whether a username can be set for a chat
//@description The username can be set
@@ -1482,28 +3346,239 @@ checkChatUsernameResultUsernameInvalid = CheckChatUsernameResult;
//@description The username is occupied
checkChatUsernameResultUsernameOccupied = CheckChatUsernameResult;
-//@description The user has too much public chats, one of them should be made private first
+//@description The user has too many chats with username, one of them must be made private first
checkChatUsernameResultPublicChatsTooMuch = CheckChatUsernameResult;
//@description The user can't be a member of a public supergroup
checkChatUsernameResultPublicGroupsUnavailable = CheckChatUsernameResult;
+//@class CheckStickerSetNameResult @description Represents result of checking whether a name can be used for a new sticker set
+
+//@description The name can be set
+checkStickerSetNameResultOk = CheckStickerSetNameResult;
+
+//@description The name is invalid
+checkStickerSetNameResultNameInvalid = CheckStickerSetNameResult;
+
+//@description The name is occupied
+checkStickerSetNameResultNameOccupied = CheckStickerSetNameResult;
+
+
+//@class ResetPasswordResult @description Represents result of 2-step verification password reset
+
+//@description The password was reset
+resetPasswordResultOk = ResetPasswordResult;
+
+//@description The password reset request is pending @pending_reset_date Point in time (Unix timestamp) after which the password can be reset immediately using resetPassword
+resetPasswordResultPending pending_reset_date:int32 = ResetPasswordResult;
+
+//@description The password reset request was declined @retry_date Point in time (Unix timestamp) when the password reset can be retried
+resetPasswordResultDeclined retry_date:int32 = ResetPasswordResult;
+
+
+//@class MessageFileType @description Contains information about a file with messages exported from another app
+
+//@description The messages was exported from a private chat @name Name of the other party; may be empty if unrecognized
+messageFileTypePrivate name:string = MessageFileType;
+
+//@description The messages was exported from a group chat @title Title of the group chat; may be empty if unrecognized
+messageFileTypeGroup title:string = MessageFileType;
+
+//@description The messages was exported from a chat of unknown type
+messageFileTypeUnknown = MessageFileType;
+
+
+//@class PushMessageContent @description Contains content of a push message notification
+
+//@description A general message with hidden content @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentHidden is_pinned:Bool = PushMessageContent;
+
+//@description An animation message (GIF-style). @animation Message content; may be null @caption Animation caption @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentAnimation animation:animation caption:string is_pinned:Bool = PushMessageContent;
+
+//@description An audio message @audio Message content; may be null @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentAudio audio:audio is_pinned:Bool = PushMessageContent;
+
+//@description A message with a user contact @name Contact's name @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentContact name:string is_pinned:Bool = PushMessageContent;
+
+//@description A contact has registered with Telegram
+pushMessageContentContactRegistered = PushMessageContent;
+
+//@description A document message (a general file) @document Message content; may be null @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentDocument document:document is_pinned:Bool = PushMessageContent;
+
+//@description A message with a game @title Game title, empty for pinned game message @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentGame title:string is_pinned:Bool = PushMessageContent;
+
+//@description A new high score was achieved in a game @title Game title, empty for pinned message @score New score, 0 for pinned message @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentGameScore title:string score:int32 is_pinned:Bool = PushMessageContent;
+
+//@description A message with an invoice from a bot @price Product price @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentInvoice price:string is_pinned:Bool = PushMessageContent;
+
+//@description A message with a location @is_live True, if the location is live @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentLocation is_live:Bool is_pinned:Bool = PushMessageContent;
+
+//@description A photo message @photo Message content; may be null @caption Photo caption @is_secret True, if the photo is secret @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentPhoto photo:photo caption:string is_secret:Bool is_pinned:Bool = PushMessageContent;
+
+//@description A message with a poll @question Poll question @is_regular True, if the poll is regular and not in quiz mode @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentPoll question:string is_regular:Bool is_pinned:Bool = PushMessageContent;
+
+//@description A screenshot of a message in the chat has been taken
+pushMessageContentScreenshotTaken = PushMessageContent;
+
+//@description A message with a sticker @sticker Message content; may be null @emoji Emoji corresponding to the sticker; may be empty @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentSticker sticker:sticker emoji:string is_pinned:Bool = PushMessageContent;
+
+//@description A text message @text Message text @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentText text:string is_pinned:Bool = PushMessageContent;
+
+//@description A video message @video Message content; may be null @caption Video caption @is_secret True, if the video is secret @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentVideo video:video caption:string is_secret:Bool is_pinned:Bool = PushMessageContent;
+
+//@description A video note message @video_note Message content; may be null @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentVideoNote video_note:videoNote is_pinned:Bool = PushMessageContent;
+
+//@description A voice note message @voice_note Message content; may be null @is_pinned True, if the message is a pinned message with the specified content
+pushMessageContentVoiceNote voice_note:voiceNote is_pinned:Bool = PushMessageContent;
+
+//@description A newly created basic group
+pushMessageContentBasicGroupChatCreate = PushMessageContent;
+
+//@description New chat members were invited to a group @member_name Name of the added member @is_current_user True, if the current user was added to the group
+//@is_returned True, if the user has returned to the group themselves
+pushMessageContentChatAddMembers member_name:string is_current_user:Bool is_returned:Bool = PushMessageContent;
+
+//@description A chat photo was edited
+pushMessageContentChatChangePhoto = PushMessageContent;
+
+//@description A chat title was edited @title New chat title
+pushMessageContentChatChangeTitle title:string = PushMessageContent;
+
+//@description A chat theme was edited @theme_name If non-empty, name of a new theme, set for the chat. Otherwise chat theme was reset to the default one
+pushMessageContentChatSetTheme theme_name:string = PushMessageContent;
+
+//@description A chat member was deleted @member_name Name of the deleted member @is_current_user True, if the current user was deleted from the group
+//@is_left True, if the user has left the group themselves
+pushMessageContentChatDeleteMember member_name:string is_current_user:Bool is_left:Bool = PushMessageContent;
+
+//@description A new member joined the chat via an invite link
+pushMessageContentChatJoinByLink = PushMessageContent;
+
+//@description A new member was accepted to the chat by an administrator
+pushMessageContentChatJoinByRequest = PushMessageContent;
+
+//@description A new recurrent payment was made by the current user @amount The paid amount
+pushMessageContentRecurringPayment amount:string = PushMessageContent;
+
+//@description A forwarded messages @total_count Number of forwarded messages
+pushMessageContentMessageForwards total_count:int32 = PushMessageContent;
+
+//@description A media album @total_count Number of messages in the album @has_photos True, if the album has at least one photo @has_videos True, if the album has at least one video
+//@has_audios True, if the album has at least one audio file @has_documents True, if the album has at least one document
+pushMessageContentMediaAlbum total_count:int32 has_photos:Bool has_videos:Bool has_audios:Bool has_documents:Bool = PushMessageContent;
+
+
+//@class NotificationType @description Contains detailed information about a notification
+
+//@description New message was received @message The message @show_preview True, if message content must be displayed in notifications
+notificationTypeNewMessage message:message show_preview:Bool = NotificationType;
+
+//@description New secret chat was created
+notificationTypeNewSecretChat = NotificationType;
+
+//@description New call was received @call_id Call identifier
+notificationTypeNewCall call_id:int32 = NotificationType;
+
+//@description New message was received through a push notification
+//@message_id The message identifier. The message will not be available in the chat history, but the ID can be used in viewMessages, or as reply_to_message_id
+//@sender_id Identifier of the sender of the message. Corresponding user or chat may be inaccessible
+//@sender_name Name of the sender
+//@is_outgoing True, if the message is outgoing
+//@content Push message content
+notificationTypeNewPushMessage message_id:int53 sender_id:MessageSender sender_name:string is_outgoing:Bool content:PushMessageContent = NotificationType;
+
+
+//@class NotificationGroupType @description Describes the type of notifications in a notification group
+
+//@description A group containing notifications of type notificationTypeNewMessage and notificationTypeNewPushMessage with ordinary unread messages
+notificationGroupTypeMessages = NotificationGroupType;
+
+//@description A group containing notifications of type notificationTypeNewMessage and notificationTypeNewPushMessage with unread mentions of the current user, replies to their messages, or a pinned message
+notificationGroupTypeMentions = NotificationGroupType;
+
+//@description A group containing a notification of type notificationTypeNewSecretChat
+notificationGroupTypeSecretChat = NotificationGroupType;
+
+//@description A group containing notifications of type notificationTypeNewCall
+notificationGroupTypeCalls = NotificationGroupType;
+
+
+//@description Describes a notification sound in MP3 format
+//@id Unique identifier of the notification sound
+//@duration Duration of the sound, in seconds
+//@date Point in time (Unix timestamp) when the sound was created
+//@title Title of the notification sound
+//@data Arbitrary data, defined while the sound was uploaded
+//@sound File containing the sound
+notificationSound id:int64 duration:int32 date:int32 title:string data:string sound:file = NotificationSound;
+
+//@description Contains a list of notification sounds @notification_sounds A list of notification sounds
+notificationSounds notification_sounds:vector<notificationSound> = NotificationSounds;
+
+
+//@description Contains information about a notification @id Unique persistent identifier of this notification @date Notification date
+//@is_silent True, if the notification was explicitly sent without sound @type Notification type
+notification id:int32 date:int32 is_silent:Bool type:NotificationType = Notification;
+
+//@description Describes a group of notifications @id Unique persistent auto-incremented from 1 identifier of the notification group @type Type of the group
+//@chat_id Identifier of a chat to which all notifications in the group belong
+//@total_count Total number of active notifications in the group @notifications The list of active notifications
+notificationGroup id:int32 type:NotificationGroupType chat_id:int53 total_count:int32 notifications:vector<notification> = NotificationGroup;
+
+
//@class OptionValue @description Represents the value of an option
-//@description Boolean option @value The value of the option
+//@description Represents a boolean option @value The value of the option
optionValueBoolean value:Bool = OptionValue;
-//@description An unknown option or an option which has a default value
+//@description Represents an unknown option or an option which has a default value
optionValueEmpty = OptionValue;
-//@description An integer option @value The value of the option
-optionValueInteger value:int32 = OptionValue;
+//@description Represents an integer option @value The value of the option
+optionValueInteger value:int64 = OptionValue;
-//@description A string option @value The value of the option
+//@description Represents a string option @value The value of the option
optionValueString value:string = OptionValue;
+//@description Represents one member of a JSON object @key Member's key @value Member's value
+jsonObjectMember key:string value:JsonValue = JsonObjectMember;
+
+//@class JsonValue @description Represents a JSON value
+
+//@description Represents a null JSON value
+jsonValueNull = JsonValue;
+
+//@description Represents a boolean JSON value @value The value
+jsonValueBoolean value:Bool = JsonValue;
+
+//@description Represents a numeric JSON value @value The value
+jsonValueNumber value:double = JsonValue;
+
+//@description Represents a string JSON value @value The value
+jsonValueString value:string = JsonValue;
+
+//@description Represents a JSON array @values The list of array elements
+jsonValueArray values:vector<JsonValue> = JsonValue;
+
+//@description Represents a JSON object @members The list of object members
+jsonValueObject members:vector<jsonObjectMember> = JsonValue;
+
+
//@class UserPrivacySettingRule @description Represents a single rule for managing privacy settings
//@description A rule to allow all users to do something
@@ -1512,8 +3587,11 @@ userPrivacySettingRuleAllowAll = UserPrivacySettingRule;
//@description A rule to allow all of a user's contacts to do something
userPrivacySettingRuleAllowContacts = UserPrivacySettingRule;
-//@description A rule to allow certain specified users to do something @user_ids The user identifiers
-userPrivacySettingRuleAllowUsers user_ids:vector<int32> = UserPrivacySettingRule;
+//@description A rule to allow certain specified users to do something @user_ids The user identifiers, total number of users in all rules must not exceed 1000
+userPrivacySettingRuleAllowUsers user_ids:vector<int53> = UserPrivacySettingRule;
+
+//@description A rule to allow all members of certain specified basic groups and supergroups to doing something @chat_ids The chat identifiers, total number of chats in all rules must not exceed 20
+userPrivacySettingRuleAllowChatMembers chat_ids:vector<int53> = UserPrivacySettingRule;
//@description A rule to restrict all users from doing something
userPrivacySettingRuleRestrictAll = UserPrivacySettingRule;
@@ -1521,8 +3599,11 @@ userPrivacySettingRuleRestrictAll = UserPrivacySettingRule;
//@description A rule to restrict all contacts of a user from doing something
userPrivacySettingRuleRestrictContacts = UserPrivacySettingRule;
-//@description A rule to restrict all specified users from doing something @user_ids The user identifiers
-userPrivacySettingRuleRestrictUsers user_ids:vector<int32> = UserPrivacySettingRule;
+//@description A rule to restrict all specified users from doing something @user_ids The user identifiers, total number of users in all rules must not exceed 1000
+userPrivacySettingRuleRestrictUsers user_ids:vector<int53> = UserPrivacySettingRule;
+
+//@description A rule to restrict all members of specified basic groups and supergroups from doing something @chat_ids The chat identifiers, total number of chats in all rules must not exceed 20
+userPrivacySettingRuleRestrictChatMembers chat_ids:vector<int53> = UserPrivacySettingRule;
//@description A list of privacy rules. Rules are matched in the specified order. The first matched rule defines the privacy setting for a given user. If no rule matches, the action is not allowed @rules A list of rules
userPrivacySettingRules rules:vector<UserPrivacySettingRule> = UserPrivacySettingRules;
@@ -1532,28 +3613,105 @@ userPrivacySettingRules rules:vector<UserPrivacySettingRule> = UserPrivacySettin
//@description A privacy setting for managing whether the user's online status is visible
userPrivacySettingShowStatus = UserPrivacySetting;
+//@description A privacy setting for managing whether the user's profile photo is visible
+userPrivacySettingShowProfilePhoto = UserPrivacySetting;
+
+//@description A privacy setting for managing whether a link to the user's account is included in forwarded messages
+userPrivacySettingShowLinkInForwardedMessages = UserPrivacySetting;
+
+//@description A privacy setting for managing whether the user's phone number is visible
+userPrivacySettingShowPhoneNumber = UserPrivacySetting;
+
//@description A privacy setting for managing whether the user can be invited to chats
userPrivacySettingAllowChatInvites = UserPrivacySetting;
//@description A privacy setting for managing whether the user can be called
userPrivacySettingAllowCalls = UserPrivacySetting;
+//@description A privacy setting for managing whether peer-to-peer connections can be used for calls
+userPrivacySettingAllowPeerToPeerCalls = UserPrivacySetting;
+
+//@description A privacy setting for managing whether the user can be found by their phone number. Checked only if the phone number is not known to the other user. Can be set only to "Allow contacts" or "Allow all"
+userPrivacySettingAllowFindingByPhoneNumber = UserPrivacySetting;
+
+//@description A privacy setting for managing whether the user can receive voice and video messages in private chats
+userPrivacySettingAllowPrivateVoiceAndVideoNoteMessages = UserPrivacySetting;
-//@description Contains information about the period of inactivity after which the current user's account will automatically be deleted @days Number of days of inactivity before the account will be flagged for deletion; should range from 30-366 days
+
+//@description Contains information about the period of inactivity after which the current user's account will automatically be deleted @days Number of days of inactivity before the account will be flagged for deletion; 30-366 days
accountTtl days:int32 = AccountTtl;
-//@description Contains information about one session in a Telegram application used by the current user @id Session identifier @is_current True, if this session is the current session
+//@class SessionType @description Represents the type of a session
+
+//@description The session is running on an Android device
+sessionTypeAndroid = SessionType;
+
+//@description The session is running on a generic Apple device
+sessionTypeApple = SessionType;
+
+//@description The session is running on the Brave browser
+sessionTypeBrave = SessionType;
+
+//@description The session is running on the Chrome browser
+sessionTypeChrome = SessionType;
+
+//@description The session is running on the Edge browser
+sessionTypeEdge = SessionType;
+
+//@description The session is running on the Firefox browser
+sessionTypeFirefox = SessionType;
+
+//@description The session is running on an iPad device
+sessionTypeIpad = SessionType;
+
+//@description The session is running on an iPhone device
+sessionTypeIphone = SessionType;
+
+//@description The session is running on a Linux device
+sessionTypeLinux = SessionType;
+
+//@description The session is running on a Mac device
+sessionTypeMac = SessionType;
+
+//@description The session is running on the Opera browser
+sessionTypeOpera = SessionType;
+
+//@description The session is running on the Safari browser
+sessionTypeSafari = SessionType;
+
+//@description The session is running on an Ubuntu device
+sessionTypeUbuntu = SessionType;
+
+//@description The session is running on an unknown type of device
+sessionTypeUnknown = SessionType;
+
+//@description The session is running on the Vivaldi browser
+sessionTypeVivaldi = SessionType;
+
+//@description The session is running on a Windows device
+sessionTypeWindows = SessionType;
+
+//@description The session is running on an Xbox console
+sessionTypeXbox = SessionType;
+
+
+//@description Contains information about one session in a Telegram application used by the current user. Sessions must be shown to the user in the returned order
+//@id Session identifier @is_current True, if this session is the current session
+//@is_password_pending True, if a 2-step verification password is needed to complete authorization of the session
+//@can_accept_secret_chats True, if incoming secret chats can be accepted by the session
+//@can_accept_calls True, if incoming calls can be accepted by the session
+//@type Session type based on the system and application version, which can be used to display a corresponding icon
//@api_id Telegram API identifier, as provided by the application @application_name Name of the application, as provided by the application
//@application_version The version of the application, as provided by the application @is_official_application True, if the application is an official application or uses the api_id of an official application
//@device_model Model of the device the application has been run or is running on, as provided by the application @platform Operating system the application has been run or is running on, as provided by the application
//@system_version Version of the operating system the application has been run or is running on, as provided by the application @log_in_date Point in time (Unix timestamp) when the user has logged in
//@last_active_date Point in time (Unix timestamp) when the session was last used @ip IP address from which the session was created, in human-readable format
//@country A two-letter country code for the country from which the session was created, based on the IP address @region Region code from which the session was created, based on the IP address
-session id:int64 is_current:Bool api_id:int32 application_name:string application_version:string is_official_application:Bool device_model:string platform:string system_version:string log_in_date:int32 last_active_date:int32 ip:string country:string region:string = Session;
+session id:int64 is_current:Bool is_password_pending:Bool can_accept_secret_chats:Bool can_accept_calls:Bool type:SessionType api_id:int32 application_name:string application_version:string is_official_application:Bool device_model:string platform:string system_version:string log_in_date:int32 last_active_date:int32 ip:string country:string region:string = Session;
-//@description Contains a list of sessions @sessions List of sessions
-sessions sessions:vector<session> = Sessions;
+//@description Contains a list of sessions @sessions List of sessions @inactive_session_ttl_days Number of days of inactivity before sessions will automatically be terminated; 1-366 days
+sessions sessions:vector<session> inactive_session_ttl_days:int32 = Sessions;
//@description Contains information about one website the current user is logged in with Telegram
@@ -1565,16 +3723,13 @@ sessions sessions:vector<session> = Sessions;
//@log_in_date Point in time (Unix timestamp) when the user was logged in
//@last_active_date Point in time (Unix timestamp) when obtained authorization was last used
//@ip IP address from which the user was logged in, in human-readable format
-//@location Human-readable description of a country and a region, from which the user was logged in, based on the IP address
-connectedWebsite id:int64 domain_name:string bot_user_id:int32 browser:string platform:string log_in_date:int32 last_active_date:int32 ip:string location:string = ConnectedWebsite;
+//@location Human-readable description of a country and a region from which the user was logged in, based on the IP address
+connectedWebsite id:int64 domain_name:string bot_user_id:int53 browser:string platform:string log_in_date:int32 last_active_date:int32 ip:string location:string = ConnectedWebsite;
//@description Contains a list of websites the current user is logged in with Telegram @websites List of connected websites
connectedWebsites websites:vector<connectedWebsite> = ConnectedWebsites;
-//@description Contains information about the availability of the "Report spam" action for a chat @can_report_spam True, if a prompt with the "Report spam" action should be shown to the user
-chatReportSpamState can_report_spam:Bool = ChatReportSpamState;
-
//@class ChatReportReason @description Describes the reason why a chat is reported
//@description The chat contains spam messages
@@ -1586,12 +3741,186 @@ chatReportReasonViolence = ChatReportReason;
//@description The chat contains pornographic messages
chatReportReasonPornography = ChatReportReason;
-//@description A custom reason provided by the user @text Report text
-chatReportReasonCustom text:string = ChatReportReason;
+//@description The chat has child abuse related content
+chatReportReasonChildAbuse = ChatReportReason;
+
+//@description The chat contains copyrighted content
+chatReportReasonCopyright = ChatReportReason;
+
+//@description The location-based chat is unrelated to its stated location
+chatReportReasonUnrelatedLocation = ChatReportReason;
+
+//@description The chat represents a fake account
+chatReportReasonFake = ChatReportReason;
+
+//@description The chat has illegal drugs related content
+chatReportReasonIllegalDrugs = ChatReportReason;
+
+//@description The chat contains messages with personal details
+chatReportReasonPersonalDetails = ChatReportReason;
+
+//@description A custom reason provided by the user
+chatReportReasonCustom = ChatReportReason;
+
+
+//@class TargetChat @description Describes the target chat to be opened
+
+//@description The currently opened chat needs to be kept
+targetChatCurrent = TargetChat;
+
+//@description The chat needs to be chosen by the user among chats of the specified types
+//@allow_user_chats True, if private chats with ordinary users are allowed
+//@allow_bot_chats True, if private chats with other bots are allowed
+//@allow_group_chats True, if basic group and supergroup chats are allowed
+//@allow_channel_chats True, if channel chats are allowed
+targetChatChosen allow_user_chats:Bool allow_bot_chats:Bool allow_group_chats:Bool allow_channel_chats:Bool = TargetChat;
+
+//@description The chat needs to be open with the provided internal link @link An internal link pointing to the chat
+targetChatInternalLink link:InternalLinkType = TargetChat;
+
+
+//@class InternalLinkType @description Describes an internal https://t.me or tg: link, which must be processed by the application in a special way
+
+//@description The link is a link to the active sessions section of the application. Use getActiveSessions to handle the link
+internalLinkTypeActiveSessions = InternalLinkType;
+
+//@description The link is a link to an attachment menu bot to be opened in the specified or a chosen chat. Process given target_chat to open the chat.
+//-Then call searchPublicChat with the given bot username, check that the user is a bot and can be added to attachment menu. Then use getAttachmentMenuBot to receive information about the bot.
+//-If the bot isn't added to attachment menu, then user needs to confirm adding the bot to attachment menu. If user confirms adding, then use toggleBotIsAddedToAttachmentMenu to add it.
+//-If the attachment menu bot can't be used in the opened chat, show an error to the user. If the bot is added to attachment menu and can be used in the chat, then use openWebApp with the given URL
+//@target_chat Target chat to be opened @bot_username Username of the bot @url URL to be passed to openWebApp
+internalLinkTypeAttachmentMenuBot target_chat:TargetChat bot_username:string url:string = InternalLinkType;
+
+//@description The link contains an authentication code. Call checkAuthenticationCode with the code if the current authorization state is authorizationStateWaitCode @code The authentication code
+internalLinkTypeAuthenticationCode code:string = InternalLinkType;
+
+//@description The link is a link to a background. Call searchBackground with the given background name to process the link @background_name Name of the background
+internalLinkTypeBackground background_name:string = InternalLinkType;
+//@description The link is a link to a chat with a Telegram bot. Call searchPublicChat with the given bot username, check that the user is a bot, show START button in the chat with the bot,
+//-and then call sendBotStartMessage with the given start parameter after the button is pressed
+//@bot_username Username of the bot @start_parameter The parameter to be passed to sendBotStartMessage
+//@autostart True, if sendBotStartMessage must be called automatically without showing the START button
+internalLinkTypeBotStart bot_username:string start_parameter:string autostart:Bool = InternalLinkType;
-//@description Contains a public HTTPS link to a message in a public supergroup or channel @link Message link @html HTML-code for embedding the message
-publicMessageLink link:string html:string = PublicMessageLink;
+//@description The link is a link to a Telegram bot, which is supposed to be added to a group chat. Call searchPublicChat with the given bot username, check that the user is a bot and can be added to groups,
+//-ask the current user to select a basic group or a supergroup chat to add the bot to, taking into account that bots can be added to a public supergroup only by administrators of the supergroup.
+//-If administrator rights are provided by the link, call getChatMember to receive the current bot rights in the chat and if the bot already is an administrator,
+//-check that the current user can edit its administrator rights, combine received rights with the requested administrator rights, show confirmation box to the user,
+//-and call setChatMemberStatus with the chosen chat and confirmed administrator rights. Before call to setChatMemberStatus it may be required to upgrade the chosen basic group chat to a supergroup chat.
+//-Then if start_parameter isn't empty, call sendBotStartMessage with the given start parameter and the chosen chat, otherwise just send /start message with bot's username added to the chat.
+//@bot_username Username of the bot @start_parameter The parameter to be passed to sendBotStartMessage @administrator_rights Expected administrator rights for the bot; may be null
+internalLinkTypeBotStartInGroup bot_username:string start_parameter:string administrator_rights:chatAdministratorRights = InternalLinkType;
+
+//@description The link is a link to a Telegram bot, which is supposed to be added to a channel chat as an administrator. Call searchPublicChat with the given bot username and check that the user is a bot,
+//-ask the current user to select a channel chat to add the bot to as an administrator. Then call getChatMember to receive the current bot rights in the chat and if the bot already is an administrator,
+//-check that the current user can edit its administrator rights and combine received rights with the requested administrator rights. Then show confirmation box to the user, and call setChatMemberStatus with the chosen chat and confirmed rights
+//@bot_username Username of the bot @administrator_rights Expected administrator rights for the bot
+internalLinkTypeBotAddToChannel bot_username:string administrator_rights:chatAdministratorRights = InternalLinkType;
+
+//@description The link is a link to the change phone number section of the app
+internalLinkTypeChangePhoneNumber = InternalLinkType;
+
+//@description The link is a chat invite link. Call checkChatInviteLink with the given invite link to process the link @invite_link Internal representation of the invite link
+internalLinkTypeChatInvite invite_link:string = InternalLinkType;
+
+//@description The link is a link to the filter settings section of the app
+internalLinkTypeFilterSettings = InternalLinkType;
+
+//@description The link is a link to a game. Call searchPublicChat with the given bot username, check that the user is a bot, ask the current user to select a chat to send the game, and then call sendMessage with inputMessageGame
+//@bot_username Username of the bot that owns the game @game_short_name Short name of the game
+internalLinkTypeGame bot_username:string game_short_name:string = InternalLinkType;
+
+//@description The link must be opened in an Instant View. Call getWebPageInstantView with the given URL to process the link @url URL to be passed to getWebPageInstantView @fallback_url An URL to open if getWebPageInstantView fails
+internalLinkTypeInstantView url:string fallback_url:string = InternalLinkType;
+
+//@description The link is a link to an invoice. Call getPaymentForm with the given invoice name to process the link @invoice_name Name of the invoice
+internalLinkTypeInvoice invoice_name:string = InternalLinkType;
+
+//@description The link is a link to a language pack. Call getLanguagePackInfo with the given language pack identifier to process the link @language_pack_id Language pack identifier
+internalLinkTypeLanguagePack language_pack_id:string = InternalLinkType;
+
+//@description The link is a link to the language settings section of the app
+internalLinkTypeLanguageSettings = InternalLinkType;
+
+//@description The link is a link to a Telegram message. Call getMessageLinkInfo with the given URL to process the link @url URL to be passed to getMessageLinkInfo
+internalLinkTypeMessage url:string = InternalLinkType;
+
+//@description The link contains a message draft text. A share screen needs to be shown to the user, then the chosen chat must be opened and the text is added to the input field
+//@text Message draft text @contains_link True, if the first line of the text contains a link. If true, the input field needs to be focused and the text after the link must be selected
+internalLinkTypeMessageDraft text:formattedText contains_link:Bool = InternalLinkType;
+
+//@description The link contains a request of Telegram passport data. Call getPassportAuthorizationForm with the given parameters to process the link if the link was received from outside of the application, otherwise ignore it
+//@bot_user_id User identifier of the service's bot @scope Telegram Passport element types requested by the service @public_key Service's public key @nonce Unique request identifier provided by the service
+//@callback_url An HTTP URL to open once the request is finished or canceled with the parameter tg_passport=success or tg_passport=cancel respectively. If empty, then the link tgbot{bot_user_id}://passport/success or tgbot{bot_user_id}://passport/cancel needs to be opened instead
+internalLinkTypePassportDataRequest bot_user_id:int53 scope:string public_key:string nonce:string callback_url:string = InternalLinkType;
+
+//@description The link can be used to confirm ownership of a phone number to prevent account deletion. Call sendPhoneNumberConfirmationCode with the given hash and phone number to process the link
+//@hash Hash value from the link @phone_number Phone number value from the link
+internalLinkTypePhoneNumberConfirmation hash:string phone_number:string = InternalLinkType;
+
+//@description The link is a link to the Premium features screen of the applcation from which the user can subscribe to Telegram Premium. Call getPremiumFeatures with the given referrer to process the link @referrer Referrer specified in the link
+internalLinkTypePremiumFeatures referrer:string = InternalLinkType;
+
+//@description The link is a link to the privacy and security settings section of the app
+internalLinkTypePrivacyAndSecuritySettings = InternalLinkType;
+
+//@description The link is a link to a proxy. Call addProxy with the given parameters to process the link and add the proxy
+//@server Proxy server IP address @port Proxy server port @type Type of the proxy
+internalLinkTypeProxy server:string port:int32 type:ProxyType = InternalLinkType;
+
+//@description The link is a link to a chat by its username. Call searchPublicChat with the given chat username to process the link @chat_username Username of the chat
+internalLinkTypePublicChat chat_username:string = InternalLinkType;
+
+//@description The link can be used to login the current user on another device, but it must be scanned from QR-code using in-app camera. An alert similar to
+//-"This code can be used to allow someone to log in to your Telegram account. To confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code" needs to be shown
+internalLinkTypeQrCodeAuthentication = InternalLinkType;
+
+//@description The link forces restore of App Store purchases when opened. For official iOS application only
+internalLinkTypeRestorePurchases = InternalLinkType;
+
+//@description The link is a link to application settings
+internalLinkTypeSettings = InternalLinkType;
+
+//@description The link is a link to a sticker set. Call searchStickerSet with the given sticker set name to process the link and show the sticker set @sticker_set_name Name of the sticker set
+internalLinkTypeStickerSet sticker_set_name:string = InternalLinkType;
+
+//@description The link is a link to a theme. TDLib has no theme support yet @theme_name Name of the theme
+internalLinkTypeTheme theme_name:string = InternalLinkType;
+
+//@description The link is a link to the theme settings section of the app
+internalLinkTypeThemeSettings = InternalLinkType;
+
+//@description The link is an unknown tg: link. Call getDeepLinkInfo to process the link @link Link to be passed to getDeepLinkInfo
+internalLinkTypeUnknownDeepLink link:string = InternalLinkType;
+
+//@description The link is a link to an unsupported proxy. An alert can be shown to the user
+internalLinkTypeUnsupportedProxy = InternalLinkType;
+
+//@description The link is a link to a user by its phone number. Call searchUserByPhoneNumber with the given phone number to process the link @phone_number Phone number of the user
+internalLinkTypeUserPhoneNumber phone_number:string = InternalLinkType;
+
+//@description The link is a link to a video chat. Call searchPublicChat with the given chat username, and then joinGroupCall with the given invite hash to process the link
+//@chat_username Username of the chat with the video chat @invite_hash If non-empty, invite hash to be used to join the video chat without being muted by administrators
+//@is_live_stream True, if the video chat is expected to be a live stream in a channel or a broadcast group
+internalLinkTypeVideoChat chat_username:string invite_hash:string is_live_stream:Bool = InternalLinkType;
+
+
+//@description Contains an HTTPS link to a message in a supergroup or channel @link Message link @is_public True, if the link will work for non-members of the chat
+messageLink link:string is_public:Bool = MessageLink;
+
+//@description Contains information about a link to a message in a chat
+//@is_public True, if the link is a public link for a message in a chat
+//@chat_id If found, identifier of the chat to which the message belongs, 0 otherwise
+//@message_thread_id If found, identifier of the message thread in which to open the message, or which to open in case of a missing message
+//@message If found, the linked message; may be null
+//@media_timestamp Timestamp from which the video/audio/video note/voice note playing must start, in seconds; 0 if not specified. The media can be in the message content or in its web page preview
+//@for_album True, if the whole media album to which the message belongs is linked
+messageLinkInfo is_public:Bool chat_id:int53 message_thread_id:int53 message:message media_timestamp:int32 for_album:Bool = MessageLinkInfo;
+
+
+//@description Contains a part of a file @data File bytes
+filePart data:bytes = FilePart;
//@class FileType @description Represents the type of a file
@@ -1608,6 +3937,9 @@ fileTypeAudio = FileType;
//@description The file is a document
fileTypeDocument = FileType;
+//@description The file is a notification sound
+fileTypeNotificationSound = FileType;
+
//@description The file is a photo
fileTypePhoto = FileType;
@@ -1617,6 +3949,12 @@ fileTypeProfilePhoto = FileType;
//@description The file was sent to a secret chat (the file type is not known to the server)
fileTypeSecret = FileType;
+//@description The file is a thumbnail of a file from a secret chat
+fileTypeSecretThumbnail = FileType;
+
+//@description The file is a file from Secure storage used for storing Telegram Passport files
+fileTypeSecure = FileType;
+
//@description The file is a sticker
fileTypeSticker = FileType;
@@ -1635,24 +3973,26 @@ fileTypeVideoNote = FileType;
//@description The file is a voice note
fileTypeVoiceNote = FileType;
-//@description The file is a wallpaper
+//@description The file is a wallpaper or a background pattern
fileTypeWallpaper = FileType;
-//@description The file is a thumbnail of a file from a secret chat
-fileTypeSecretThumbnail = FileType;
-
-//@description Contains the storage usage statistics for a specific file type @file_type File type @size Total size of the files @count Total number of files
+//@description Contains the storage usage statistics for a specific file type @file_type File type @size Total size of the files, in bytes @count Total number of files
storageStatisticsByFileType file_type:FileType size:int53 count:int32 = StorageStatisticsByFileType;
-//@description Contains the storage usage statistics for a specific chat @chat_id Chat identifier; 0 if none @size Total size of the files in the chat @count Total number of files in the chat @by_file_type Statistics split by file types
+//@description Contains the storage usage statistics for a specific chat @chat_id Chat identifier; 0 if none @size Total size of the files in the chat, in bytes @count Total number of files in the chat @by_file_type Statistics split by file types
storageStatisticsByChat chat_id:int53 size:int53 count:int32 by_file_type:vector<storageStatisticsByFileType> = StorageStatisticsByChat;
-//@description Contains the exact storage usage statistics split by chats and file type @size Total size of files @count Total number of files @by_chat Statistics split by chats
+//@description Contains the exact storage usage statistics split by chats and file type @size Total size of files, in bytes @count Total number of files @by_chat Statistics split by chats
storageStatistics size:int53 count:int32 by_chat:vector<storageStatisticsByChat> = StorageStatistics;
-//@description Contains approximate storage usage statistics, excluding files of unknown file type @files_size Approximate total size of files @file_count Approximate number of files @database_size Size of the database
-storageStatisticsFast files_size:int53 file_count:int32 database_size:int53 = StorageStatisticsFast;
+//@description Contains approximate storage usage statistics, excluding files of unknown file type @files_size Approximate total size of files, in bytes @file_count Approximate number of files
+//@database_size Size of the database @language_pack_database_size Size of the language pack database @log_size Size of the TDLib internal log
+storageStatisticsFast files_size:int53 file_count:int32 database_size:int53 language_pack_database_size:int53 log_size:int53 = StorageStatisticsFast;
+
+//@description Contains database statistics
+//@statistics Database statistics in an unspecified human-readable format
+databaseStatistics statistics:string = DatabaseStatistics;
//@class NetworkType @description Represents the type of a network
@@ -1675,21 +4015,44 @@ networkTypeOther = NetworkType;
//@class NetworkStatisticsEntry @description Contains statistics about network usage
-//@description Contains information about the total amount of data that was used to send and receive files @file_type Type of the file the data is part of @network_type Type of the network the data was sent through. Call setNetworkType to maintain the actual network type
+//@description Contains information about the total amount of data that was used to send and receive files
+//@file_type Type of the file the data is part of; pass null if the data isn't related to files
+//@network_type Type of the network the data was sent through. Call setNetworkType to maintain the actual network type
//@sent_bytes Total number of bytes sent @received_bytes Total number of bytes received
networkStatisticsEntryFile file_type:FileType network_type:NetworkType sent_bytes:int53 received_bytes:int53 = NetworkStatisticsEntry;
-//@description Contains information about the total amount of data that was used for calls @network_type Type of the network the data was sent through. Call setNetworkType to maintain the actual network type
-//@sent_bytes Total number of bytes sent @received_bytes Total number of bytes received @duration Total call duration, in seconds
+//@description Contains information about the total amount of data that was used for calls
+//@network_type Type of the network the data was sent through. Call setNetworkType to maintain the actual network type
+//@sent_bytes Total number of bytes sent
+//@received_bytes Total number of bytes received
+//@duration Total call duration, in seconds
networkStatisticsEntryCall network_type:NetworkType sent_bytes:int53 received_bytes:int53 duration:double = NetworkStatisticsEntry;
-//@description A full list of available network statistic entries @since_date Point in time (Unix timestamp) when the app began collecting statistics @entries Network statistics entries
+//@description A full list of available network statistic entries @since_date Point in time (Unix timestamp) from which the statistics are collected @entries Network statistics entries
networkStatistics since_date:int32 entries:vector<NetworkStatisticsEntry> = NetworkStatistics;
+//@description Contains auto-download settings
+//@is_auto_download_enabled True, if the auto-download is enabled
+//@max_photo_file_size The maximum size of a photo file to be auto-downloaded, in bytes
+//@max_video_file_size The maximum size of a video file to be auto-downloaded, in bytes
+//@max_other_file_size The maximum size of other file types to be auto-downloaded, in bytes
+//@video_upload_bitrate The maximum suggested bitrate for uploaded videos, in kbit/s
+//@preload_large_videos True, if the beginning of video files needs to be preloaded for instant playback
+//@preload_next_audio True, if the next audio track needs to be preloaded while the user is listening to an audio file
+//@use_less_data_for_calls True, if "use less data for calls" option needs to be enabled
+autoDownloadSettings is_auto_download_enabled:Bool max_photo_file_size:int32 max_video_file_size:int53 max_other_file_size:int53 video_upload_bitrate:int32 preload_large_videos:Bool preload_next_audio:Bool use_less_data_for_calls:Bool = AutoDownloadSettings;
+
+//@description Contains auto-download settings presets for the current user
+//@low Preset with lowest settings; supposed to be used by default when roaming
+//@medium Preset with medium settings; supposed to be used by default when using mobile data
+//@high Preset with highest settings; supposed to be used by default when connected on Wi-Fi
+autoDownloadSettingsPresets low:autoDownloadSettings medium:autoDownloadSettings high:autoDownloadSettings = AutoDownloadSettingsPresets;
+
+
//@class ConnectionState @description Describes the current state of the connection to Telegram servers
-//@description Currently waiting for the network to become available. Use SetNetworkType to change the available network type
+//@description Currently waiting for the network to become available. Use setNetworkType to change the available network type
connectionStateWaitingForNetwork = ConnectionState;
//@description Currently establishing a connection with a proxy server
@@ -1698,7 +4061,7 @@ connectionStateConnectingToProxy = ConnectionState;
//@description Currently establishing a connection to the Telegram servers
connectionStateConnecting = ConnectionState;
-//@description Downloading data received while the client was offline
+//@description Downloading data received while the application was offline
connectionStateUpdating = ConnectionState;
//@description There is a working connection to the Telegram servers
@@ -1725,16 +4088,19 @@ topChatCategoryInlineBots = TopChatCategory;
//@description A category containing frequently used chats used for calls
topChatCategoryCalls = TopChatCategory;
+//@description A category containing frequently used chats used to forward messages
+topChatCategoryForwardChats = TopChatCategory;
+
//@class TMeUrlType @description Describes the type of a URL linking to an internal Telegram entity
//@description A URL linking to a user @user_id Identifier of the user
-tMeUrlTypeUser user_id:int32 = TMeUrlType;
+tMeUrlTypeUser user_id:int53 = TMeUrlType;
//@description A URL linking to a public supergroup or channel @supergroup_id Identifier of the supergroup or channel
tMeUrlTypeSupergroup supergroup_id:int53 = TMeUrlType;
-//@description A chat invite link @info Chat invite link info
+//@description A chat invite link @info Information about the chat invite link
tMeUrlTypeChatInvite info:chatInviteLinkInfo = TMeUrlType;
//@description A URL linking to a sticker set @sticker_set_id Identifier of the sticker set
@@ -1747,33 +4113,205 @@ tMeUrl url:string type:TMeUrlType = TMeUrl;
tMeUrls urls:vector<tMeUrl> = TMeUrls;
+//@class SuggestedAction @description Describes an action suggested to the current user
+
+//@description Suggests the user to enable "archive_and_mute_new_chats_from_unknown_users" option
+suggestedActionEnableArchiveAndMuteNewChats = SuggestedAction;
+
+//@description Suggests the user to check whether they still remember their 2-step verification password
+suggestedActionCheckPassword = SuggestedAction;
+
+//@description Suggests the user to check whether authorization phone number is correct and change the phone number if it is inaccessible
+suggestedActionCheckPhoneNumber = SuggestedAction;
+
+//@description Suggests the user to view a hint about the meaning of one and two check marks on sent messages
+suggestedActionViewChecksHint = SuggestedAction;
+
+//@description Suggests the user to convert specified supergroup to a broadcast group @supergroup_id Supergroup identifier
+suggestedActionConvertToBroadcastGroup supergroup_id:int53 = SuggestedAction;
+
+//@description Suggests the user to set a 2-step verification password to be able to log in again @authorization_delay The number of days to pass between consecutive authorizations if the user declines to set password
+suggestedActionSetPassword authorization_delay:int32 = SuggestedAction;
+
+
//@description Contains a counter @count Count
count count:int32 = Count;
//@description Contains some text @text Text
text text:string = Text;
+//@description Contains a value representing a number of seconds @seconds Number of seconds
+seconds seconds:double = Seconds;
+
+//@description Contains size of downloaded prefix of a file @size The prefix size, in bytes
+fileDownloadedPrefixSize size:int53 = FileDownloadedPrefixSize;
-//@class TextParseMode @description Describes the way the text should be parsed for TextEntities
-//@description The text should be parsed in markdown-style
-textParseModeMarkdown = TextParseMode;
+//@description Contains information about a tg: deep link @text Text to be shown to the user @need_update_application True, if the user must be asked to update the application
+deepLinkInfo text:formattedText need_update_application:Bool = DeepLinkInfo;
-//@description The text should be parsed in HTML-style
+
+//@class TextParseMode @description Describes the way the text needs to be parsed for TextEntities
+
+//@description The text uses Markdown-style formatting
+//@version Version of the parser: 0 or 1 - Telegram Bot API "Markdown" parse mode, 2 - Telegram Bot API "MarkdownV2" parse mode
+textParseModeMarkdown version:int32 = TextParseMode;
+
+//@description The text uses HTML-style formatting. The same as Telegram Bot API "HTML" parse mode
textParseModeHTML = TextParseMode;
-//@class Proxy @description Contains information about a proxy server
+//@class ProxyType @description Describes the type of a proxy server
+
+//@description A SOCKS5 proxy server @username Username for logging in; may be empty @password Password for logging in; may be empty
+proxyTypeSocks5 username:string password:string = ProxyType;
+
+//@description A HTTP transparent proxy server @username Username for logging in; may be empty @password Password for logging in; may be empty @http_only Pass true if the proxy supports only HTTP requests and doesn't support transparent TCP connections via HTTP CONNECT method
+proxyTypeHttp username:string password:string http_only:Bool = ProxyType;
+
+//@description An MTProto proxy server @secret The proxy's secret in hexadecimal encoding
+proxyTypeMtproto secret:string = ProxyType;
+
+
+//@description Contains information about a proxy server @id Unique identifier of the proxy @server Proxy server IP address @port Proxy server port @last_used_date Point in time (Unix timestamp) when the proxy was last used; 0 if never @is_enabled True, if the proxy is enabled now @type Type of the proxy
+proxy id:int32 server:string port:int32 last_used_date:int32 is_enabled:Bool type:ProxyType = Proxy;
+
+//@description Represents a list of proxy servers @proxies List of proxy servers
+proxies proxies:vector<proxy> = Proxies;
+
+
+//@description A sticker to be added to a sticker set
+//@sticker File with the sticker; must fit in a 512x512 square. For WEBP stickers and masks the file must be in PNG format, which will be converted to WEBP server-side. Otherwise, the file must be local or uploaded within a week. See https://core.telegram.org/animated_stickers#technical-requirements for technical requirements
+//@emojis Emojis corresponding to the sticker
+//@format Sticker format
+//@mask_position Position where the mask is placed; pass null if not specified
+inputSticker sticker:InputFile emojis:string format:StickerFormat mask_position:maskPosition = InputSticker;
+
+
+//@description Represents a date range @start_date Point in time (Unix timestamp) at which the date range begins @end_date Point in time (Unix timestamp) at which the date range ends
+dateRange start_date:int32 end_date:int32 = DateRange;
+
+
+//@description A value with information about its recent changes @value The current value @previous_value The value for the previous day @growth_rate_percentage The growth rate of the value, as a percentage
+statisticalValue value:double previous_value:double growth_rate_percentage:double = StatisticalValue;
+
+
+//@class StatisticalGraph @description Describes a statistical graph
+
+//@description A graph data @json_data Graph data in JSON format @zoom_token If non-empty, a token which can be used to receive a zoomed in graph
+statisticalGraphData json_data:string zoom_token:string = StatisticalGraph;
+
+//@description The graph data to be asynchronously loaded through getStatisticalGraph @token The token to use for data loading
+statisticalGraphAsync token:string = StatisticalGraph;
+
+//@description An error message to be shown to the user instead of the graph @error_message The error message
+statisticalGraphError error_message:string = StatisticalGraph;
+
+
+//@description Contains statistics about interactions with a message
+//@message_id Message identifier
+//@view_count Number of times the message was viewed
+//@forward_count Number of times the message was forwarded
+chatStatisticsMessageInteractionInfo message_id:int53 view_count:int32 forward_count:int32 = ChatStatisticsMessageInteractionInfo;
+
+//@description Contains statistics about messages sent by a user
+//@user_id User identifier
+//@sent_message_count Number of sent messages
+//@average_character_count Average number of characters in sent messages; 0 if unknown
+chatStatisticsMessageSenderInfo user_id:int53 sent_message_count:int32 average_character_count:int32 = ChatStatisticsMessageSenderInfo;
-//@description An empty proxy server
-proxyEmpty = Proxy;
+//@description Contains statistics about administrator actions done by a user
+//@user_id Administrator user identifier
+//@deleted_message_count Number of messages deleted by the administrator
+//@banned_user_count Number of users banned by the administrator
+//@restricted_user_count Number of users restricted by the administrator
+chatStatisticsAdministratorActionsInfo user_id:int53 deleted_message_count:int32 banned_user_count:int32 restricted_user_count:int32 = ChatStatisticsAdministratorActionsInfo;
-//@description A SOCKS5 proxy server @server Proxy server IP address @port Proxy server port @username Username for logging in @password Password for logging in
-proxySocks5 server:string port:int32 username:string password:string = Proxy;
+//@description Contains statistics about number of new members invited by a user
+//@user_id User identifier
+//@added_member_count Number of new members invited by the user
+chatStatisticsInviterInfo user_id:int53 added_member_count:int32 = ChatStatisticsInviterInfo;
-//@description Describes a sticker that should be added to a sticker set @png_sticker PNG image with the sticker; must be up to 512 kB in size and fit in a 512x512 square @emojis Emoji corresponding to the sticker @mask_position For masks, position where the mask should be placed; may be null
-inputSticker png_sticker:InputFile emojis:string mask_position:maskPosition = InputSticker;
+//@class ChatStatistics @description Contains a detailed statistics about a chat
+
+//@description A detailed statistics about a supergroup chat
+//@period A period to which the statistics applies
+//@member_count Number of members in the chat
+//@message_count Number of messages sent to the chat
+//@viewer_count Number of users who viewed messages in the chat
+//@sender_count Number of users who sent messages to the chat
+//@member_count_graph A graph containing number of members in the chat
+//@join_graph A graph containing number of members joined and left the chat
+//@join_by_source_graph A graph containing number of new member joins per source
+//@language_graph A graph containing distribution of active users per language
+//@message_content_graph A graph containing distribution of sent messages by content type
+//@action_graph A graph containing number of different actions in the chat
+//@day_graph A graph containing distribution of message views per hour
+//@week_graph A graph containing distribution of message views per day of week
+//@top_senders List of users sent most messages in the last week
+//@top_administrators List of most active administrators in the last week
+//@top_inviters List of most active inviters of new members in the last week
+chatStatisticsSupergroup period:dateRange member_count:statisticalValue message_count:statisticalValue viewer_count:statisticalValue sender_count:statisticalValue member_count_graph:StatisticalGraph join_graph:StatisticalGraph join_by_source_graph:StatisticalGraph language_graph:StatisticalGraph message_content_graph:StatisticalGraph action_graph:StatisticalGraph day_graph:StatisticalGraph week_graph:StatisticalGraph top_senders:vector<chatStatisticsMessageSenderInfo> top_administrators:vector<chatStatisticsAdministratorActionsInfo> top_inviters:vector<chatStatisticsInviterInfo> = ChatStatistics;
+
+//@description A detailed statistics about a channel chat
+//@period A period to which the statistics applies
+//@member_count Number of members in the chat
+//@mean_view_count Mean number of times the recently sent messages was viewed
+//@mean_share_count Mean number of times the recently sent messages was shared
+//@enabled_notifications_percentage A percentage of users with enabled notifications for the chat
+//@member_count_graph A graph containing number of members in the chat
+//@join_graph A graph containing number of members joined and left the chat
+//@mute_graph A graph containing number of members muted and unmuted the chat
+//@view_count_by_hour_graph A graph containing number of message views in a given hour in the last two weeks
+//@view_count_by_source_graph A graph containing number of message views per source
+//@join_by_source_graph A graph containing number of new member joins per source
+//@language_graph A graph containing number of users viewed chat messages per language
+//@message_interaction_graph A graph containing number of chat message views and shares
+//@instant_view_interaction_graph A graph containing number of views of associated with the chat instant views
+//@recent_message_interactions Detailed statistics about number of views and shares of recently sent messages
+chatStatisticsChannel period:dateRange member_count:statisticalValue mean_view_count:statisticalValue mean_share_count:statisticalValue enabled_notifications_percentage:double member_count_graph:StatisticalGraph join_graph:StatisticalGraph mute_graph:StatisticalGraph view_count_by_hour_graph:StatisticalGraph view_count_by_source_graph:StatisticalGraph join_by_source_graph:StatisticalGraph language_graph:StatisticalGraph message_interaction_graph:StatisticalGraph instant_view_interaction_graph:StatisticalGraph recent_message_interactions:vector<chatStatisticsMessageInteractionInfo> = ChatStatistics;
+
+
+//@description A detailed statistics about a message @message_interaction_graph A graph containing number of message views and shares
+messageStatistics message_interaction_graph:StatisticalGraph = MessageStatistics;
+
+
+//@description A point on a Cartesian plane @x The point's first coordinate @y The point's second coordinate
+point x:double y:double = Point;
+
+
+//@class VectorPathCommand @description Represents a vector path command
+
+//@description A straight line to a given point @end_point The end point of the straight line
+vectorPathCommandLine end_point:point = VectorPathCommand;
+
+//@description A cubic Bézier curve to a given point @start_control_point The start control point of the curve @end_control_point The end control point of the curve @end_point The end point of the curve
+vectorPathCommandCubicBezierCurve start_control_point:point end_control_point:point end_point:point = VectorPathCommand;
+
+
+//@class BotCommandScope @description Represents the scope to which bot commands are relevant
+
+//@description A scope covering all users
+botCommandScopeDefault = BotCommandScope;
+
+//@description A scope covering all private chats
+botCommandScopeAllPrivateChats = BotCommandScope;
+
+//@description A scope covering all group and supergroup chats
+botCommandScopeAllGroupChats = BotCommandScope;
+
+//@description A scope covering all group and supergroup chat administrators
+botCommandScopeAllChatAdministrators = BotCommandScope;
+
+//@description A scope covering all members of a chat @chat_id Chat identifier
+botCommandScopeChat chat_id:int53 = BotCommandScope;
+
+//@description A scope covering all administrators of a chat @chat_id Chat identifier
+botCommandScopeChatAdministrators chat_id:int53 = BotCommandScope;
+
+//@description A scope covering a member of a chat @chat_id Chat identifier @user_id User identifier
+botCommandScopeChatMember chat_id:int53 user_id:int53 = BotCommandScope;
//@class Update @description Contains notifications about data changes
@@ -1781,18 +4319,18 @@ inputSticker png_sticker:InputFile emojis:string mask_position:maskPosition = In
//@description The user authorization state has changed @authorization_state New authorization state
updateAuthorizationState authorization_state:AuthorizationState = Update;
-//@description A new message was received; can also be an outgoing message @message The new message @disable_notification True, if this message must not generate a notification @contains_mention True, if the message contains a mention of the current user
-updateNewMessage message:message disable_notification:Bool contains_mention:Bool = Update;
+//@description A new message was received; can also be an outgoing message @message The new message
+updateNewMessage message:message = Update;
//@description A request to send a message has reached the Telegram server. This doesn't mean that the message will be sent successfully or even that the send message request will be processed. This update will be sent only if the option "use_quick_ack" is set to true. This update may be sent multiple times for the same message
//@chat_id The chat identifier of the sent message @message_id A temporary message identifier
updateMessageSendAcknowledged chat_id:int53 message_id:int53 = Update;
-//@description A message has been successfully sent @message Information about the sent message. Usually only the message identifier, date, and content are changed, but almost all other fields can also change @old_message_id The previous temporary message identifier
+//@description A message has been successfully sent @message The sent message. Usually only the message identifier, date, and content are changed, but almost all other fields can also change @old_message_id The previous temporary message identifier
updateMessageSendSucceeded message:message old_message_id:int53 = Update;
//@description A message failed to send. Be aware that some messages being sent can be irrecoverably deleted, in which case updateDeleteMessages will be received instead of this update
-//@message Contains information about the message that failed to send @old_message_id The previous temporary message identifier @error_code An error code @error_message Error message
+//@message The failed to send message @old_message_id The previous temporary message identifier @error_code An error code @error_message Error message
updateMessageSendFailed message:message old_message_id:int53 error_code:int32 error_message:string = Update;
//@description The message content has changed @chat_id Chat identifier @message_id Message identifier @new_content New message content
@@ -1801,8 +4339,11 @@ updateMessageContent chat_id:int53 message_id:int53 new_content:MessageContent =
//@description A message was edited. Changes in the message content will come in a separate updateMessageContent @chat_id Chat identifier @message_id Message identifier @edit_date Point in time (Unix timestamp) when the message was edited @reply_markup New message reply markup; may be null
updateMessageEdited chat_id:int53 message_id:int53 edit_date:int32 reply_markup:ReplyMarkup = Update;
-//@description The view count of the message has changed @chat_id Chat identifier @message_id Message identifier @views New value of the view count
-updateMessageViews chat_id:int53 message_id:int53 views:int32 = Update;
+//@description The message pinned state was changed @chat_id Chat identifier @message_id The message identifier @is_pinned True, if the message is pinned
+updateMessageIsPinned chat_id:int53 message_id:int53 is_pinned:Bool = Update;
+
+//@description The information about interactions with a message has changed @chat_id Chat identifier @message_id Message identifier @interaction_info New information about interactions with the message; may be null
+updateMessageInteractionInfo chat_id:int53 message_id:int53 interaction_info:messageInteractionInfo = Update;
//@description The message content was opened. Updates voice note messages to "listened", video note messages to "viewed" and starts the TTL timer for self-destructing messages @chat_id Chat identifier @message_id Message identifier
updateMessageContentOpened chat_id:int53 message_id:int53 = Update;
@@ -1810,108 +4351,228 @@ updateMessageContentOpened chat_id:int53 message_id:int53 = Update;
//@description A message with an unread mention was read @chat_id Chat identifier @message_id Message identifier @unread_mention_count The new number of unread mention messages left in the chat
updateMessageMentionRead chat_id:int53 message_id:int53 unread_mention_count:int32 = Update;
-//@description A new chat has been loaded/created. This update is guaranteed to come before the chat identifier is returned to the client. The chat field changes will be reported through separate updates @chat The chat
+//@description The list of unread reactions added to a message was changed @chat_id Chat identifier @message_id Message identifier @unread_reactions The new list of unread reactions @unread_reaction_count The new number of messages with unread reactions left in the chat
+updateMessageUnreadReactions chat_id:int53 message_id:int53 unread_reactions:vector<unreadReaction> unread_reaction_count:int32 = Update;
+
+//@description A message with a live location was viewed. When the update is received, the application is supposed to update the live location
+//@chat_id Identifier of the chat with the live location message @message_id Identifier of the message with live location
+updateMessageLiveLocationViewed chat_id:int53 message_id:int53 = Update;
+
+//@description A new chat has been loaded/created. This update is guaranteed to come before the chat identifier is returned to the application. The chat field changes will be reported through separate updates @chat The chat
updateNewChat chat:chat = Update;
//@description The title of a chat was changed @chat_id Chat identifier @title The new chat title
updateChatTitle chat_id:int53 title:string = Update;
//@description A chat photo was changed @chat_id Chat identifier @photo The new chat photo; may be null
-updateChatPhoto chat_id:int53 photo:chatPhoto = Update;
+updateChatPhoto chat_id:int53 photo:chatPhotoInfo = Update;
-//@description The last message of a chat was changed. If last_message is null then the last message in the chat became unknown. Some new unknown messages might be added to the chat in this case @chat_id Chat identifier @last_message The new last message in the chat; may be null @order New value of the chat order
-updateChatLastMessage chat_id:int53 last_message:message order:int64 = Update;
+//@description Chat permissions was changed @chat_id Chat identifier @permissions The new chat permissions
+updateChatPermissions chat_id:int53 permissions:chatPermissions = Update;
-//@description The order of the chat in the chats list has changed. Instead of this update updateChatLastMessage, updateChatIsPinned or updateChatDraftMessage might be sent @chat_id Chat identifier @order New value of the order
-updateChatOrder chat_id:int53 order:int64 = Update;
+//@description The last message of a chat was changed. If last_message is null, then the last message in the chat became unknown. Some new unknown messages might be added to the chat in this case @chat_id Chat identifier @last_message The new last message in the chat; may be null @positions The new chat positions in the chat lists
+updateChatLastMessage chat_id:int53 last_message:message positions:vector<chatPosition> = Update;
-//@description A chat was pinned or unpinned @chat_id Chat identifier @is_pinned New value of is_pinned @order New value of the chat order
-updateChatIsPinned chat_id:int53 is_pinned:Bool order:int64 = Update;
+//@description The position of a chat in a chat list has changed. Instead of this update updateChatLastMessage or updateChatDraftMessage might be sent @chat_id Chat identifier @position New chat position. If new order is 0, then the chat needs to be removed from the list
+updateChatPosition chat_id:int53 position:chatPosition = Update;
-//@description Incoming messages were read or number of unread messages has been changed @chat_id Chat identifier @last_read_inbox_message_id Identifier of the last read incoming message @unread_count The number of unread messages left in the chat
+//@description Incoming messages were read or the number of unread messages has been changed @chat_id Chat identifier @last_read_inbox_message_id Identifier of the last read incoming message @unread_count The number of unread messages left in the chat
updateChatReadInbox chat_id:int53 last_read_inbox_message_id:int53 unread_count:int32 = Update;
//@description Outgoing messages were read @chat_id Chat identifier @last_read_outbox_message_id Identifier of last read outgoing message
updateChatReadOutbox chat_id:int53 last_read_outbox_message_id:int53 = Update;
-//@description The chat unread_mention_count has changed @chat_id Chat identifier @unread_mention_count The number of unread mention messages left in the chat
-updateChatUnreadMentionCount chat_id:int53 unread_mention_count:int32 = Update;
+//@description The chat action bar was changed @chat_id Chat identifier @action_bar The new value of the action bar; may be null
+updateChatActionBar chat_id:int53 action_bar:ChatActionBar = Update;
+
+//@description The chat available reactions were changed @chat_id Chat identifier @available_reactions The new reactions, available in the chat
+updateChatAvailableReactions chat_id:int53 available_reactions:ChatAvailableReactions = Update;
-//@description Notification settings for some chats were updated @scope Types of chats for which notification settings were updated @notification_settings The new notification settings
-updateNotificationSettings scope:NotificationSettingsScope notification_settings:notificationSettings = Update;
+//@description A chat draft has changed. Be aware that the update may come in the currently opened chat but with old content of the draft. If the user has changed the content of the draft, this update mustn't be applied @chat_id Chat identifier @draft_message The new draft message; may be null @positions The new chat positions in the chat lists
+updateChatDraftMessage chat_id:int53 draft_message:draftMessage positions:vector<chatPosition> = Update;
+
+//@description The message sender that is selected to send messages in a chat has changed @chat_id Chat identifier @message_sender_id New value of message_sender_id; may be null if the user can't change message sender
+updateChatMessageSender chat_id:int53 message_sender_id:MessageSender = Update;
+
+//@description The message Time To Live setting for a chat was changed @chat_id Chat identifier @message_ttl New value of message_ttl
+updateChatMessageTtl chat_id:int53 message_ttl:int32 = Update;
+
+//@description Notification settings for a chat were changed @chat_id Chat identifier @notification_settings The new notification settings
+updateChatNotificationSettings chat_id:int53 notification_settings:chatNotificationSettings = Update;
+
+//@description The chat pending join requests were changed @chat_id Chat identifier @pending_join_requests The new data about pending join requests; may be null
+updateChatPendingJoinRequests chat_id:int53 pending_join_requests:chatJoinRequestsInfo = Update;
//@description The default chat reply markup was changed. Can occur because new messages with reply markup were received or because an old reply markup was hidden by the user
//@chat_id Chat identifier @reply_markup_message_id Identifier of the message from which reply markup needs to be used; 0 if there is no default custom reply markup in the chat
updateChatReplyMarkup chat_id:int53 reply_markup_message_id:int53 = Update;
-//@description A draft has changed. Be aware that the update may come in the currently opened chat but with old content of the draft. If the user has changed the content of the draft, this update shouldn't be applied @chat_id Chat identifier @draft_message The new draft message; may be null @order New value of the chat order
-updateChatDraftMessage chat_id:int53 draft_message:draftMessage order:int64 = Update;
+//@description The chat theme was changed @chat_id Chat identifier @theme_name The new name of the chat theme; may be empty if theme was reset to default
+updateChatTheme chat_id:int53 theme_name:string = Update;
+
+//@description The chat unread_mention_count has changed @chat_id Chat identifier @unread_mention_count The number of unread mention messages left in the chat
+updateChatUnreadMentionCount chat_id:int53 unread_mention_count:int32 = Update;
+
+//@description The chat unread_reaction_count has changed @chat_id Chat identifier @unread_reaction_count The number of messages with unread reactions left in the chat
+updateChatUnreadReactionCount chat_id:int53 unread_reaction_count:int32 = Update;
+
+//@description A chat video chat state has changed @chat_id Chat identifier @video_chat New value of video_chat
+updateChatVideoChat chat_id:int53 video_chat:videoChat = Update;
+
+//@description The value of the default disable_notification parameter, used when a message is sent to the chat, was changed @chat_id Chat identifier @default_disable_notification The new default_disable_notification value
+updateChatDefaultDisableNotification chat_id:int53 default_disable_notification:Bool = Update;
+
+//@description A chat content was allowed or restricted for saving @chat_id Chat identifier @has_protected_content New value of has_protected_content
+updateChatHasProtectedContent chat_id:int53 has_protected_content:Bool = Update;
+
+//@description A chat's has_scheduled_messages field has changed @chat_id Chat identifier @has_scheduled_messages New value of has_scheduled_messages
+updateChatHasScheduledMessages chat_id:int53 has_scheduled_messages:Bool = Update;
+
+//@description A chat was blocked or unblocked @chat_id Chat identifier @is_blocked New value of is_blocked
+updateChatIsBlocked chat_id:int53 is_blocked:Bool = Update;
+
+//@description A chat was marked as unread or was read @chat_id Chat identifier @is_marked_as_unread New value of is_marked_as_unread
+updateChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Update;
+
+//@description The list of chat filters or a chat filter has changed @chat_filters The new list of chat filters @main_chat_list_position Position of the main chat list among chat filters, 0-based
+updateChatFilters chat_filters:vector<chatFilterInfo> main_chat_list_position:int32 = Update;
+
+//@description The number of online group members has changed. This update with non-zero number of online group members is sent only for currently opened chats. There is no guarantee that it will be sent just after the number of online users has changed @chat_id Identifier of the chat @online_member_count New number of online members in the chat, or 0 if unknown
+updateChatOnlineMemberCount chat_id:int53 online_member_count:int32 = Update;
+
+//@description Basic information about a topic in a forum chat was changed @chat_id Chat identifier @info New information about the topic
+updateForumTopicInfo chat_id:int53 info:forumTopicInfo = Update;
+
+//@description Notification settings for some type of chats were updated @scope Types of chats for which notification settings were updated @notification_settings The new notification settings
+updateScopeNotificationSettings scope:NotificationSettingsScope notification_settings:scopeNotificationSettings = Update;
+
+//@description A notification was changed @notification_group_id Unique notification group identifier @notification Changed notification
+updateNotification notification_group_id:int32 notification:notification = Update;
+
+//@description A list of active notifications in a notification group has changed
+//@notification_group_id Unique notification group identifier
+//@type New type of the notification group
+//@chat_id Identifier of a chat to which all notifications in the group belong
+//@notification_settings_chat_id Chat identifier, which notification settings must be applied to the added notifications
+//@notification_sound_id Identifier of the notification sound to be played; 0 if sound is disabled
+//@total_count Total number of unread notifications in the group, can be bigger than number of active notifications
+//@added_notifications List of added group notifications, sorted by notification ID @removed_notification_ids Identifiers of removed group notifications, sorted by notification ID
+updateNotificationGroup notification_group_id:int32 type:NotificationGroupType chat_id:int53 notification_settings_chat_id:int53 notification_sound_id:int64 total_count:int32 added_notifications:vector<notification> removed_notification_ids:vector<int32> = Update;
+
+//@description Contains active notifications that was shown on previous application launches. This update is sent only if the message database is used. In that case it comes once before any updateNotification and updateNotificationGroup update @groups Lists of active notification groups
+updateActiveNotifications groups:vector<notificationGroup> = Update;
+
+//@description Describes whether there are some pending notification updates. Can be used to prevent application from killing, while there are some pending notifications
+//@have_delayed_notifications True, if there are some delayed notification updates, which will be sent soon
+//@have_unreceived_notifications True, if there can be some yet unreceived notifications, which are being fetched from the server
+updateHavePendingNotifications have_delayed_notifications:Bool have_unreceived_notifications:Bool = Update;
//@description Some messages were deleted @chat_id Chat identifier @message_ids Identifiers of the deleted messages
-//@is_permanent True, if the messages are permanently deleted by a user (as opposed to just becoming unaccessible)
+//@is_permanent True, if the messages are permanently deleted by a user (as opposed to just becoming inaccessible)
//@from_cache True, if the messages are deleted only from the cache and can possibly be retrieved again in the future
updateDeleteMessages chat_id:int53 message_ids:vector<int53> is_permanent:Bool from_cache:Bool = Update;
-//@description User activity in the chat has changed @chat_id Chat identifier @user_id Identifier of a user performing an action @action The action description
-updateUserChatAction chat_id:int53 user_id:int32 action:ChatAction = Update;
+//@description A message sender activity in the chat has changed @chat_id Chat identifier @message_thread_id If not 0, a message thread identifier in which the action was performed @sender_id Identifier of a message sender performing the action @action The action
+updateChatAction chat_id:int53 message_thread_id:int53 sender_id:MessageSender action:ChatAction = Update;
//@description The user went online or offline @user_id User identifier @status New status of the user
-updateUserStatus user_id:int32 status:UserStatus = Update;
+updateUserStatus user_id:int53 status:UserStatus = Update;
-//@description Some data of a user has changed. This update is guaranteed to come before the user identifier is returned to the client @user New data about the user
+//@description Some data of a user has changed. This update is guaranteed to come before the user identifier is returned to the application @user New data about the user
updateUser user:user = Update;
-//@description Some data of a basic group has changed. This update is guaranteed to come before the basic group identifier is returned to the client @basic_group New data about the group
+//@description Some data of a basic group has changed. This update is guaranteed to come before the basic group identifier is returned to the application @basic_group New data about the group
updateBasicGroup basic_group:basicGroup = Update;
-//@description Some data of a supergroup or a channel has changed. This update is guaranteed to come before the supergroup identifier is returned to the client @supergroup New data about the supergroup
+//@description Some data of a supergroup or a channel has changed. This update is guaranteed to come before the supergroup identifier is returned to the application @supergroup New data about the supergroup
updateSupergroup supergroup:supergroup = Update;
-//@description Some data of a secret chat has changed. This update is guaranteed to come before the secret chat identifier is returned to the client @secret_chat New data about the secret chat
+//@description Some data of a secret chat has changed. This update is guaranteed to come before the secret chat identifier is returned to the application @secret_chat New data about the secret chat
updateSecretChat secret_chat:secretChat = Update;
-//@description Some data from userFullInfo has been changed @user_id User identifier @user_full_info New full information about the user
-updateUserFullInfo user_id:int32 user_full_info:userFullInfo = Update;
+//@description Some data in userFullInfo has been changed @user_id User identifier @user_full_info New full information about the user
+updateUserFullInfo user_id:int53 user_full_info:userFullInfo = Update;
-//@description Some data from basicGroupFullInfo has been changed @basic_group_id Identifier of a basic group @basic_group_full_info New full information about the group
-updateBasicGroupFullInfo basic_group_id:int32 basic_group_full_info:basicGroupFullInfo = Update;
+//@description Some data in basicGroupFullInfo has been changed @basic_group_id Identifier of a basic group @basic_group_full_info New full information about the group
+updateBasicGroupFullInfo basic_group_id:int53 basic_group_full_info:basicGroupFullInfo = Update;
-//@description Some data from supergroupFullInfo has been changed @supergroup_id Identifier of the supergroup or channel @supergroup_full_info New full information about the supergroup
-updateSupergroupFullInfo supergroup_id:int32 supergroup_full_info:supergroupFullInfo = Update;
+//@description Some data in supergroupFullInfo has been changed @supergroup_id Identifier of the supergroup or channel @supergroup_full_info New full information about the supergroup
+updateSupergroupFullInfo supergroup_id:int53 supergroup_full_info:supergroupFullInfo = Update;
-//@description Service notification from the server. Upon receiving this the client must show a popup with the content of the notification @type Notification type @content Notification content
+//@description A service notification from the server was received. Upon receiving this the application must show a popup with the content of the notification
+//@type Notification type. If type begins with "AUTH_KEY_DROP_", then two buttons "Cancel" and "Log out" must be shown under notification; if user presses the second, all local data must be destroyed using Destroy method
+//@content Notification content
updateServiceNotification type:string content:MessageContent = Update;
//@description Information about a file was updated @file New data about the file
updateFile file:file = Update;
-//@description The file generation process needs to be started by the client
+//@description The file generation process needs to be started by the application
//@generation_id Unique identifier for the generation process
-//@original_path The path to a file from which a new file is generated, may be empty
-//@destination_path The path to a file that should be created and where the new file should be generated
-//@conversion String specifying the conversion applied to the original file. If conversion is "#url#" than original_path contains a HTTP/HTTPS URL of a file, which should be downloaded by the client
+//@original_path The path to a file from which a new file is generated; may be empty
+//@destination_path The path to a file that must be created and where the new file is generated
+//@conversion String specifying the conversion applied to the original file. If conversion is "#url#" than original_path contains an HTTP/HTTPS URL of a file, which must be downloaded by the application
updateFileGenerationStart generation_id:int64 original_path:string destination_path:string conversion:string = Update;
//@description File generation is no longer needed @generation_id Unique identifier for the generation process
updateFileGenerationStop generation_id:int64 = Update;
+//@description The state of the file download list has changed
+//@total_size Total size of files in the file download list, in bytes
+//@total_count Total number of files in the file download list
+//@downloaded_size Total downloaded size of files in the file download list, in bytes
+updateFileDownloads total_size:int53 total_count:int32 downloaded_size:int53 = Update;
+
+//@description A file was added to the file download list. This update is sent only after file download list is loaded for the first time @file_download The added file download @counts New number of being downloaded and recently downloaded files found
+updateFileAddedToDownloads file_download:fileDownload counts:downloadedFileCounts = Update;
+
+//@description A file download was changed. This update is sent only after file download list is loaded for the first time @file_id File identifier
+//@complete_date Point in time (Unix timestamp) when the file downloading was completed; 0 if the file downloading isn't completed
+//@is_paused True, if downloading of the file is paused
+//@counts New number of being downloaded and recently downloaded files found
+updateFileDownload file_id:int32 complete_date:int32 is_paused:Bool counts:downloadedFileCounts = Update;
+
+//@description A file was removed from the file download list. This update is sent only after file download list is loaded for the first time @file_id File identifier @counts New number of being downloaded and recently downloaded files found
+updateFileRemovedFromDownloads file_id:int32 counts:downloadedFileCounts = Update;
+
//@description New call was created or information about a call was updated @call New data about a call
updateCall call:call = Update;
+//@description Information about a group call was updated @group_call New data about a group call
+updateGroupCall group_call:groupCall = Update;
+
+//@description Information about a group call participant was changed. The updates are sent only after the group call is received through getGroupCall and only if the call is joined or being joined
+//@group_call_id Identifier of group call @participant New data about a participant
+updateGroupCallParticipant group_call_id:int32 participant:groupCallParticipant = Update;
+
+//@description New call signaling data arrived @call_id The call identifier @data The data
+updateNewCallSignalingData call_id:int32 data:bytes = Update;
+
//@description Some privacy setting rules have been changed @setting The privacy setting @rules New privacy rules
updateUserPrivacySettingRules setting:UserPrivacySetting rules:userPrivacySettingRules = Update;
-//@description Number of unread messages has changed. This update is sent only if a message database is used @unread_count Total number of unread messages @unread_unmuted_count Total number of unread messages in unmuted chats
-updateUnreadMessageCount unread_count:int32 unread_unmuted_count:int32 = Update;
+//@description Number of unread messages in a chat list has changed. This update is sent only if the message database is used @chat_list The chat list with changed number of unread messages
+//@unread_count Total number of unread messages @unread_unmuted_count Total number of unread messages in unmuted chats
+updateUnreadMessageCount chat_list:ChatList unread_count:int32 unread_unmuted_count:int32 = Update;
+
+//@description Number of unread chats, i.e. with unread messages or marked as unread, has changed. This update is sent only if the message database is used
+//@chat_list The chat list with changed number of unread messages
+//@total_count Approximate total number of chats in the chat list
+//@unread_count Total number of unread chats @unread_unmuted_count Total number of unread unmuted chats
+//@marked_as_unread_count Total number of chats marked as unread @marked_as_unread_unmuted_count Total number of unmuted chats marked as unread
+updateUnreadChatCount chat_list:ChatList total_count:int32 unread_count:int32 unread_unmuted_count:int32 marked_as_unread_count:int32 marked_as_unread_unmuted_count:int32 = Update;
//@description An option changed its value @name The option name @value The new option value
updateOption name:string value:OptionValue = Update;
-//@description The list of installed sticker sets was updated @is_masks True, if the list of installed mask sticker sets was updated @sticker_set_ids The new list of installed ordinary sticker sets
-updateInstalledStickerSets is_masks:Bool sticker_set_ids:vector<int64> = Update;
+//@description A sticker set has changed @sticker_set The sticker set
+updateStickerSet sticker_set:stickerSet = Update;
+
+//@description The list of installed sticker sets was updated @sticker_type Type of the affected stickers @sticker_set_ids The new list of installed ordinary sticker sets
+updateInstalledStickerSets sticker_type:StickerType sticker_set_ids:vector<int64> = Update;
-//@description The list of trending sticker sets was updated or some of them were viewed @sticker_sets The new list of trending sticker sets
-updateTrendingStickerSets sticker_sets:stickerSets = Update;
+//@description The list of trending sticker sets was updated or some of them were viewed @sticker_type Type of the affected stickers @sticker_sets The prefix of the list of trending sticker sets with the newest trending sticker sets
+updateTrendingStickerSets sticker_type:StickerType sticker_sets:trendingStickerSets = Update;
//@description The list of recently used stickers was updated @is_attached True, if the list of stickers attached to photo or video files was updated, otherwise the list of sent stickers is updated @sticker_ids The new list of file identifiers of recently used stickers
updateRecentStickers is_attached:Bool sticker_ids:vector<int32> = Update;
@@ -1922,29 +4583,75 @@ updateFavoriteStickers sticker_ids:vector<int32> = Update;
//@description The list of saved animations was updated @animation_ids The new list of file identifiers of saved animations
updateSavedAnimations animation_ids:vector<int32> = Update;
-//@description The connection state has changed @state The new connection state
+//@description The list of saved notifications sounds was updated. This update may not be sent until information about a notification sound was requested for the first time @notification_sound_ids The new list of identifiers of saved notification sounds
+updateSavedNotificationSounds notification_sound_ids:vector<int64> = Update;
+
+//@description The selected background has changed @for_dark_theme True, if background for dark theme has changed @background The new selected background; may be null
+updateSelectedBackground for_dark_theme:Bool background:background = Update;
+
+//@description The list of available chat themes has changed @chat_themes The new list of chat themes
+updateChatThemes chat_themes:vector<chatTheme> = Update;
+
+//@description Some language pack strings have been updated @localization_target Localization target to which the language pack belongs @language_pack_id Identifier of the updated language pack @strings List of changed language pack strings
+updateLanguagePackStrings localization_target:string language_pack_id:string strings:vector<languagePackString> = Update;
+
+//@description The connection state has changed. This update must be used only to show a human-readable description of the connection state @state The new connection state
updateConnectionState state:ConnectionState = Update;
-//@description A new incoming inline query; for bots only @id Unique query identifier @sender_user_id Identifier of the user who sent the query @user_location User location, provided by the client; may be null @query Text of the query @offset Offset of the first entry to return
-updateNewInlineQuery id:int64 sender_user_id:int32 user_location:location query:string offset:string = Update;
+//@description New terms of service must be accepted by the user. If the terms of service are declined, then the deleteAccount method must be called with the reason "Decline ToS update" @terms_of_service_id Identifier of the terms of service @terms_of_service The new terms of service
+updateTermsOfService terms_of_service_id:string terms_of_service:termsOfService = Update;
+
+//@description The list of users nearby has changed. The update is guaranteed to be sent only 60 seconds after a successful searchChatsNearby request @users_nearby The new list of users nearby
+updateUsersNearby users_nearby:vector<chatNearby> = Update;
+
+//@description The list of bots added to attachment menu has changed @bots The new list of bots added to attachment menu. The bots must not be shown on scheduled messages screen
+updateAttachmentMenuBots bots:vector<attachmentMenuBot> = Update;
+
+//@description A message was sent by an opened Web App, so the Web App needs to be closed @web_app_launch_id Identifier of Web App launch
+updateWebAppMessageSent web_app_launch_id:int64 = Update;
+
+//@description The list of active emoji reactions has changed @emojis The new list of active emoji reactions
+updateActiveEmojiReactions emojis:vector<string> = Update;
+
+//@description The type of default reaction has changed @reaction_type The new type of the default reaction
+updateDefaultReactionType reaction_type:ReactionType = Update;
+
+//@description The list of supported dice emojis has changed @emojis The new list of supported dice emojis
+updateDiceEmojis emojis:vector<string> = Update;
+
+//@description Some animated emoji message was clicked and a big animated sticker must be played if the message is visible on the screen. chatActionWatchingAnimations with the text of the message needs to be sent if the sticker is played
+//@chat_id Chat identifier @message_id Message identifier @sticker The animated sticker to be played
+updateAnimatedEmojiMessageClicked chat_id:int53 message_id:int53 sticker:sticker = Update;
+
+//@description The parameters of animation search through GetOption("animation_search_bot_username") bot has changed @provider Name of the animation search provider @emojis The new list of emojis suggested for searching
+updateAnimationSearchParameters provider:string emojis:vector<string> = Update;
-//@description The user has chosen a result of an inline query; for bots only @sender_user_id Identifier of the user who sent the query @user_location User location, provided by the client; may be null @query Text of the query @result_id Identifier of the chosen result @inline_message_id Identifier of the sent inline message, if known
-updateNewChosenInlineResult sender_user_id:int32 user_location:location query:string result_id:string inline_message_id:string = Update;
+//@description The list of suggested to the user actions has changed @added_actions Added suggested actions @removed_actions Removed suggested actions
+updateSuggestedActions added_actions:vector<SuggestedAction> removed_actions:vector<SuggestedAction> = Update;
-//@description A new incoming callback query; for bots only @id Unique query identifier @sender_user_id Identifier of the user who sent the query @chat_id Identifier of the chat, in which the query was sent
-//@message_id Identifier of the message, from which the query originated @chat_instance Identifier that uniquely corresponds to the chat to which the message was sent @payload Query payload
-updateNewCallbackQuery id:int64 sender_user_id:int32 chat_id:int53 message_id:int53 chat_instance:int64 payload:CallbackQueryPayload = Update;
+//@description A new incoming inline query; for bots only @id Unique query identifier @sender_user_id Identifier of the user who sent the query @user_location User location; may be null
+//@chat_type The type of the chat from which the query originated; may be null if unknown @query Text of the query @offset Offset of the first entry to return
+updateNewInlineQuery id:int64 sender_user_id:int53 user_location:location chat_type:ChatType query:string offset:string = Update;
-//@description A new incoming callback query from a message sent via a bot; for bots only @id Unique query identifier @sender_user_id Identifier of the user who sent the query @inline_message_id Identifier of the inline message, from which the query originated
+//@description The user has chosen a result of an inline query; for bots only @sender_user_id Identifier of the user who sent the query @user_location User location; may be null
+//@query Text of the query @result_id Identifier of the chosen result @inline_message_id Identifier of the sent inline message, if known
+updateNewChosenInlineResult sender_user_id:int53 user_location:location query:string result_id:string inline_message_id:string = Update;
+
+//@description A new incoming callback query; for bots only @id Unique query identifier @sender_user_id Identifier of the user who sent the query
+//@chat_id Identifier of the chat where the query was sent @message_id Identifier of the message from which the query originated
+//@chat_instance Identifier that uniquely corresponds to the chat to which the message was sent @payload Query payload
+updateNewCallbackQuery id:int64 sender_user_id:int53 chat_id:int53 message_id:int53 chat_instance:int64 payload:CallbackQueryPayload = Update;
+
+//@description A new incoming callback query from a message sent via a bot; for bots only @id Unique query identifier @sender_user_id Identifier of the user who sent the query @inline_message_id Identifier of the inline message from which the query originated
//@chat_instance An identifier uniquely corresponding to the chat a message was sent to @payload Query payload
-updateNewInlineCallbackQuery id:int64 sender_user_id:int32 inline_message_id:string chat_instance:int64 payload:CallbackQueryPayload = Update;
+updateNewInlineCallbackQuery id:int64 sender_user_id:int53 inline_message_id:string chat_instance:int64 payload:CallbackQueryPayload = Update;
//@description A new incoming shipping query; for bots only. Only for invoices with flexible price @id Unique query identifier @sender_user_id Identifier of the user who sent the query @invoice_payload Invoice payload @shipping_address User shipping address
-updateNewShippingQuery id:int64 sender_user_id:int32 invoice_payload:string shipping_address:shippingAddress = Update;
+updateNewShippingQuery id:int64 sender_user_id:int53 invoice_payload:string shipping_address:address = Update;
-//@description A new incoming pre-checkout query; for bots only. Contains full information about a checkout @id Unique query identifier @sender_user_id Identifier of the user who sent the query @currency Currency for the product price @total_amount Total price for the product, in the minimal quantity of the currency
+//@description A new incoming pre-checkout query; for bots only. Contains full information about a checkout @id Unique query identifier @sender_user_id Identifier of the user who sent the query @currency Currency for the product price @total_amount Total price for the product, in the smallest units of the currency
//@invoice_payload Invoice payload @shipping_option_id Identifier of a shipping option chosen by the user; may be empty if not applicable @order_info Information about the order; may be null
-updateNewPreCheckoutQuery id:int64 sender_user_id:int32 currency:string total_amount:int53 invoice_payload:bytes shipping_option_id:string order_info:orderInfo = Update;
+updateNewPreCheckoutQuery id:int64 sender_user_id:int53 currency:string total_amount:int53 invoice_payload:bytes shipping_option_id:string order_info:orderInfo = Update;
//@description A new incoming event; for bots only @event A JSON-serialized event
updateNewCustomEvent event:string = Update;
@@ -1952,6 +4659,50 @@ updateNewCustomEvent event:string = Update;
//@description A new incoming query; for bots only @id The query identifier @data JSON-serialized query data @timeout Query timeout
updateNewCustomQuery id:int64 data:string timeout:int32 = Update;
+//@description A poll was updated; for bots only @poll New data about the poll
+updatePoll poll:poll = Update;
+
+//@description A user changed the answer to a poll; for bots only @poll_id Unique poll identifier @user_id The user, who changed the answer to the poll @option_ids 0-based identifiers of answer options, chosen by the user
+updatePollAnswer poll_id:int64 user_id:int53 option_ids:vector<int32> = Update;
+
+//@description User rights changed in a chat; for bots only @chat_id Chat identifier @actor_user_id Identifier of the user, changing the rights
+//@date Point in time (Unix timestamp) when the user rights was changed @invite_link If user has joined the chat using an invite link, the invite link; may be null
+//@old_chat_member Previous chat member @new_chat_member New chat member
+updateChatMember chat_id:int53 actor_user_id:int53 date:int32 invite_link:chatInviteLink old_chat_member:chatMember new_chat_member:chatMember = Update;
+
+//@description A user sent a join request to a chat; for bots only @chat_id Chat identifier @request Join request @invite_link The invite link, which was used to send join request; may be null
+updateNewChatJoinRequest chat_id:int53 request:chatJoinRequest invite_link:chatInviteLink = Update;
+
+
+//@description Contains a list of updates @updates List of updates
+updates updates:vector<Update> = Updates;
+
+
+//@class LogStream @description Describes a stream to which TDLib internal log is written
+
+//@description The log is written to stderr or an OS specific log
+logStreamDefault = LogStream;
+
+//@description The log is written to a file
+//@path Path to the file to where the internal TDLib log will be written
+//@max_file_size The maximum size of the file to where the internal TDLib log is written before the file will automatically be rotated, in bytes
+//@redirect_stderr Pass true to additionally redirect stderr to the log file. Ignored on Windows
+logStreamFile path:string max_file_size:int53 redirect_stderr:Bool = LogStream;
+
+//@description The log is written nowhere
+logStreamEmpty = LogStream;
+
+
+//@description Contains a TDLib internal log verbosity level @verbosity_level Log verbosity level
+logVerbosityLevel verbosity_level:int32 = LogVerbosityLevel;
+
+//@description Contains a list of available TDLib internal log tags @tags List of log tags
+logTags tags:vector<string> = LogTags;
+
+
+//@description Contains custom information about the user @message Information message @author Information author @date Information change date
+userSupportInfo message:formattedText author:string date:int32 = UserSupportInfo;
+
//@description A simple object containing a number; for testing only @value Number
testInt value:int32 = TestInt;
@@ -1970,35 +4721,67 @@ testVectorStringObject value:vector<testString> = TestVectorStringObject;
---functions---
-//@description Returns the current authorization state; this is an offline request. For informational purposes only. Use updateAuthorizationState instead to maintain the current authorization state
+//@description Returns the current authorization state; this is an offline request. For informational purposes only. Use updateAuthorizationState instead to maintain the current authorization state. Can be called before initialization
getAuthorizationState = AuthorizationState;
-//@description Sets the parameters for TDLib initialization. Works only when the current authorization state is authorizationStateWaitTdlibParameters @parameters Parameters
-setTdlibParameters parameters:tdlibParameters = Ok;
+//@description Sets the parameters for TDLib initialization. Works only when the current authorization state is authorizationStateWaitTdlibParameters
+//@use_test_dc Pass true to use Telegram test environment instead of the production environment
+//@database_directory The path to the directory for the persistent database; if empty, the current working directory will be used
+//@files_directory The path to the directory for storing files; if empty, database_directory will be used
+//@database_encryption_key Encryption key for the database
+//@use_file_database Pass true to keep information about downloaded and uploaded files between application restarts
+//@use_chat_info_database Pass true to keep cache of users, basic groups, supergroups, channels and secret chats between restarts. Implies use_file_database
+//@use_message_database Pass true to keep cache of chats and messages between restarts. Implies use_chat_info_database
+//@use_secret_chats Pass true to enable support for secret chats
+//@api_id Application identifier for Telegram API access, which can be obtained at https://my.telegram.org
+//@api_hash Application identifier hash for Telegram API access, which can be obtained at https://my.telegram.org
+//@system_language_code IETF language tag of the user's operating system language; must be non-empty
+//@device_model Model of the device the application is being run on; must be non-empty
+//@system_version Version of the operating system the application is being run on. If empty, the version is automatically detected by TDLib
+//@application_version Application version; must be non-empty
+//@enable_storage_optimizer Pass true to automatically delete old files in background
+//@ignore_file_names Pass true to ignore original file names for downloaded files. Otherwise, downloaded files are saved under names as close as possible to the original name
+setTdlibParameters use_test_dc:Bool database_directory:string files_directory:string database_encryption_key:bytes use_file_database:Bool use_chat_info_database:Bool use_message_database:Bool use_secret_chats:Bool api_id:int32 api_hash:string system_language_code:string device_model:string system_version:string application_version:string enable_storage_optimizer:Bool ignore_file_names:Bool = Ok;
-//@description Checks the database encryption key for correctness. Works only when the current authorization state is authorizationStateWaitEncryptionKey @encryption_key Encryption key to check or set up
-checkDatabaseEncryptionKey encryption_key:bytes = Ok;
+//@description Sets the phone number of the user and sends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitPhoneNumber,
+//-or if there is no pending authentication query and the current authorization state is authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword
+//@phone_number The phone number of the user, in international format @settings Settings for the authentication of the user's phone number; pass null to use default settings
+setAuthenticationPhoneNumber phone_number:string settings:phoneNumberAuthenticationSettings = Ok;
-//@description Sets the phone number of the user and sends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitPhoneNumber
-//@phone_number The phone number of the user, in international format @allow_flash_call Pass true if the authentication code may be sent via flash call to the specified phone number @is_current_phone_number Pass true if the phone number is used on the current device. Ignored if allow_flash_call is false
-setAuthenticationPhoneNumber phone_number:string allow_flash_call:Bool is_current_phone_number:Bool = Ok;
+//@description Sets the email address of the user and sends an authentication code to the email address. Works only when the current authorization state is authorizationStateWaitEmailAddress @email_address The email address of the user
+setAuthenticationEmailAddress email_address:string = Ok;
-//@description Re-sends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitCode and the next_code_type of the result is not null
+//@description Resends an authentication code to the user. Works only when the current authorization state is authorizationStateWaitCode, the next_code_type of the result is not null and the server-specified timeout has passed, or when the current authorization state is authorizationStateWaitEmailCode
resendAuthenticationCode = Ok;
-//@description Checks the authentication code. Works only when the current authorization state is authorizationStateWaitCode @code The verification code received via SMS, Telegram message, phone call, or flash call
-//@first_name If the user is not yet registered, the first name of the user; 1-255 characters @last_name If the user is not yet registered; the last name of the user; optional; 0-255 characters
-checkAuthenticationCode code:string first_name:string last_name:string = Ok;
+//@description Checks the authentication of a email address. Works only when the current authorization state is authorizationStateWaitEmailCode @code Email address authentication to check
+checkAuthenticationEmailCode code:EmailAddressAuthentication = Ok;
+
+//@description Checks the authentication code. Works only when the current authorization state is authorizationStateWaitCode @code Authentication code to check
+checkAuthenticationCode code:string = Ok;
+
+//@description Requests QR code authentication by scanning a QR code on another logged in device. Works only when the current authorization state is authorizationStateWaitPhoneNumber,
+//-or if there is no pending authentication query and the current authorization state is authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword
+//@other_user_ids List of user identifiers of other users currently using the application
+requestQrCodeAuthentication other_user_ids:vector<int53> = Ok;
+
+//@description Finishes user registration. Works only when the current authorization state is authorizationStateWaitRegistration
+//@first_name The first name of the user; 1-64 characters @last_name The last name of the user; 0-64 characters
+registerUser first_name:string last_name:string = Ok;
-//@description Checks the authentication password for correctness. Works only when the current authorization state is authorizationStateWaitPassword @password The password to check
+//@description Checks the 2-step verification password for correctness. Works only when the current authorization state is authorizationStateWaitPassword @password The 2-step verification password to check
checkAuthenticationPassword password:string = Ok;
-//@description Requests to send a password recovery code to an email address that was previously set up. Works only when the current authorization state is authorizationStateWaitPassword
+//@description Requests to send a 2-step verification password recovery code to an email address that was previously set up. Works only when the current authorization state is authorizationStateWaitPassword
requestAuthenticationPasswordRecovery = Ok;
-//@description Recovers the password with a password recovery code sent to an email address that was previously set up. Works only when the current authorization state is authorizationStateWaitPassword @recovery_code Recovery code to check
-recoverAuthenticationPassword recovery_code:string = Ok;
+//@description Checks whether a 2-step verification password recovery code sent to an email address is valid. Works only when the current authorization state is authorizationStateWaitPassword @recovery_code Recovery code to check
+checkAuthenticationPasswordRecoveryCode recovery_code:string = Ok;
+
+//@description Recovers the 2-step verification password with a password recovery code sent to an email address that was previously set up. Works only when the current authorization state is authorizationStateWaitPassword
+//@recovery_code Recovery code to check @new_password New 2-step verification password of the user; may be empty to remove the password @new_hint New password hint; may be empty
+recoverAuthenticationPassword recovery_code:string new_password:string new_hint:string = Ok;
//@description Checks the authentication token of a bot; to log in as a bot. Works only when the current authorization state is authorizationStateWaitPhoneNumber. Can be used instead of setAuthenticationPhoneNumber and checkAuthenticationCode to log in @token The bot token
checkAuthenticationBotToken token:string = Ok;
@@ -2006,13 +4789,21 @@ checkAuthenticationBotToken token:string = Ok;
//@description Closes the TDLib instance after a proper logout. Requires an available network connection. All local data will be destroyed. After the logout completes, updateAuthorizationState with authorizationStateClosed will be sent
logOut = Ok;
-//@description Closes the TDLib instance. All databases will be flushed to disk and properly closed. After the close completes, updateAuthorizationState with authorizationStateClosed will be sent
+//@description Closes the TDLib instance. All databases will be flushed to disk and properly closed. After the close completes, updateAuthorizationState with authorizationStateClosed will be sent. Can be called before initialization
close = Ok;
-//@description Closes the TDLib instance, destroying all local data without a proper logout. The current user session will remain in the list of all active sessions. All local data will be destroyed. After the destruction completes updateAuthorizationState with authorizationStateClosed will be sent
+//@description Closes the TDLib instance, destroying all local data without a proper logout. The current user session will remain in the list of all active sessions. All local data will be destroyed. After the destruction completes updateAuthorizationState with authorizationStateClosed will be sent. Can be called before authorization
destroy = Ok;
+//@description Confirms QR code authentication on another device. Returns created session on success @link A link from a QR code. The link must be scanned by the in-app camera
+confirmQrCodeAuthentication link:string = Session;
+
+
+//@description Returns all updates needed to restore current TDLib state, i.e. all actual UpdateAuthorizationState/UpdateUser/UpdateNewChat and others. This is especially useful if TDLib is run in a separate process. Can be called before initialization
+getCurrentState = Updates;
+
+
//@description Changes the database encryption key. Usually the encryption key is never changed and is stored in some OS keychain @new_encryption_key New encryption key
setDatabaseEncryptionKey new_encryption_key:bytes = Ok;
@@ -2020,54 +4811,75 @@ setDatabaseEncryptionKey new_encryption_key:bytes = Ok;
//@description Returns the current state of 2-step verification
getPasswordState = PasswordState;
-//@description Changes the password for the user. If a new recovery email address is specified, then the error EMAIL_UNCONFIRMED is returned and the password change will not be applied until the new recovery email address has been confirmed. The application should periodically call getPasswordState to check whether the new email address has been confirmed
-//@old_password Previous password of the user @new_password New password of the user; may be empty to remove the password @new_hint New password hint; may be empty @set_recovery_email_address Pass true if the recovery email address should be changed @new_recovery_email_address New recovery email address; may be empty
+//@description Changes the 2-step verification password for the current user. If a new recovery email address is specified, then the change will not be applied until the new recovery email address is confirmed
+//@old_password Previous 2-step verification password of the user @new_password New 2-step verification password of the user; may be empty to remove the password @new_hint New password hint; may be empty @set_recovery_email_address Pass true to change also the recovery email address @new_recovery_email_address New recovery email address; may be empty
setPassword old_password:string new_password:string new_hint:string set_recovery_email_address:Bool new_recovery_email_address:string = PasswordState;
-//@description Returns a recovery email address that was previously set up. This method can be used to verify a password provided by the user @password The password for the current user
+//@description Changes the login email address of the user. The change will not be applied until the new login email address is confirmed with `checkLoginEmailAddressCode`. To use Apple ID/Google ID instead of a email address, call `checkLoginEmailAddressCode` directly @new_login_email_address New login email address
+setLoginEmailAddress new_login_email_address:string = EmailAddressAuthenticationCodeInfo;
+
+//@description Resends the login email address verification code
+resendLoginEmailAddressCode = EmailAddressAuthenticationCodeInfo;
+
+//@description Checks the login email address authentication @code Email address authentication to check
+checkLoginEmailAddressCode code:EmailAddressAuthentication = Ok;
+
+//@description Returns a 2-step verification recovery email address that was previously set up. This method can be used to verify a password provided by the user @password The 2-step verification password for the current user
getRecoveryEmailAddress password:string = RecoveryEmailAddress;
-//@description Changes the recovery email address of the user. If a new recovery email address is specified, then the error EMAIL_UNCONFIRMED is returned and the email address will not be changed until the new email has been confirmed. The application should periodically call getPasswordState to check whether the email address has been confirmed.
-//-If new_recovery_email_address is the same as the email address that is currently set up, this call succeeds immediately and aborts all other requests waiting for an email confirmation @password Password of the current user @new_recovery_email_address New recovery email address
+//@description Changes the 2-step verification recovery email address of the user. If a new recovery email address is specified, then the change will not be applied until the new recovery email address is confirmed.
+//-If new_recovery_email_address is the same as the email address that is currently set up, this call succeeds immediately and aborts all other requests waiting for an email confirmation @password The 2-step verification password of the current user @new_recovery_email_address New recovery email address
setRecoveryEmailAddress password:string new_recovery_email_address:string = PasswordState;
-//@description Requests to send a password recovery code to an email address that was previously set up
-requestPasswordRecovery = PasswordRecoveryInfo;
+//@description Checks the 2-step verification recovery email address verification code @code Verification code to check
+checkRecoveryEmailAddressCode code:string = PasswordState;
+
+//@description Resends the 2-step verification recovery email address verification code
+resendRecoveryEmailAddressCode = PasswordState;
+
+//@description Requests to send a 2-step verification password recovery code to an email address that was previously set up
+requestPasswordRecovery = EmailAddressAuthenticationCodeInfo;
-//@description Recovers the password using a recovery code sent to an email address that was previously set up @recovery_code Recovery code to check
-recoverPassword recovery_code:string = PasswordState;
+//@description Checks whether a 2-step verification password recovery code sent to an email address is valid @recovery_code Recovery code to check
+checkPasswordRecoveryCode recovery_code:string = Ok;
-//@description Creates a new temporary password for processing payments @password Persistent user password @valid_for Time during which the temporary password will be valid, in seconds; should be between 60 and 86400
+//@description Recovers the 2-step verification password using a recovery code sent to an email address that was previously set up
+//@recovery_code Recovery code to check @new_password New 2-step verification password of the user; may be empty to remove the password @new_hint New password hint; may be empty
+recoverPassword recovery_code:string new_password:string new_hint:string = PasswordState;
+
+//@description Removes 2-step verification password without previous password and access to recovery email address. The password can't be reset immediately and the request needs to be repeated after the specified time
+resetPassword = ResetPasswordResult;
+
+//@description Cancels reset of 2-step verification password. The method can be called if passwordState.pending_reset_date > 0
+cancelPasswordReset = Ok;
+
+//@description Creates a new temporary password for processing payments @password The 2-step verification password of the current user @valid_for Time during which the temporary password will be valid, in seconds; must be between 60 and 86400
createTemporaryPassword password:string valid_for:int32 = TemporaryPasswordState;
//@description Returns information about the current temporary password
getTemporaryPasswordState = TemporaryPasswordState;
-//@description Handles a DC_UPDATE push service notification. Can be called before authorization @dc Value of the "dc" parameter of the notification @addr Value of the "addr" parameter of the notification
-processDcUpdate dc:string addr:string = Ok;
-
-
//@description Returns the current user
getMe = User;
//@description Returns information about a user by their identifier. This is an offline request if the current user is not a bot @user_id User identifier
-getUser user_id:int32 = User;
+getUser user_id:int53 = User;
//@description Returns full information about a user by their identifier @user_id User identifier
-getUserFullInfo user_id:int32 = UserFullInfo;
+getUserFullInfo user_id:int53 = UserFullInfo;
//@description Returns information about a basic group by its identifier. This is an offline request if the current user is not a bot @basic_group_id Basic group identifier
-getBasicGroup basic_group_id:int32 = BasicGroup;
+getBasicGroup basic_group_id:int53 = BasicGroup;
//@description Returns full information about a basic group by its identifier @basic_group_id Basic group identifier
-getBasicGroupFullInfo basic_group_id:int32 = BasicGroupFullInfo;
+getBasicGroupFullInfo basic_group_id:int53 = BasicGroupFullInfo;
-//@description Returns information about a supergroup or channel by its identifier. This is an offline request if the current user is not a bot @supergroup_id Supergroup or channel identifier
-getSupergroup supergroup_id:int32 = Supergroup;
+//@description Returns information about a supergroup or a channel by its identifier. This is an offline request if the current user is not a bot @supergroup_id Supergroup or channel identifier
+getSupergroup supergroup_id:int53 = Supergroup;
-//@description Returns full information about a supergroup or channel by its identifier, cached for up to 1 minute @supergroup_id Supergroup or channel identifier
-getSupergroupFullInfo supergroup_id:int32 = SupergroupFullInfo;
+//@description Returns full information about a supergroup or a channel by its identifier, cached for up to 1 minute @supergroup_id Supergroup or channel identifier
+getSupergroupFullInfo supergroup_id:int53 = SupergroupFullInfo;
//@description Returns information about a secret chat by its identifier. This is an offline request @secret_chat_id Secret chat identifier
getSecretChat secret_chat_id:int32 = SecretChat;
@@ -2078,39 +4890,62 @@ getChat chat_id:int53 = Chat;
//@description Returns information about a message @chat_id Identifier of the chat the message belongs to @message_id Identifier of the message to get
getMessage chat_id:int53 message_id:int53 = Message;
-//@description Returns information about a message that is replied by given message @chat_id Identifier of the chat the message belongs to @message_id Identifier of the message reply to which get
+//@description Returns information about a message, if it is available without sending network request. This is an offline request @chat_id Identifier of the chat the message belongs to @message_id Identifier of the message to get
+getMessageLocally chat_id:int53 message_id:int53 = Message;
+
+//@description Returns information about a message that is replied by a given message. Also returns the pinned message, the game message, the invoice message, and the topic creation message for messages of the types messagePinMessage, messageGameScore, messagePaymentSuccessful, and topic messages without replied message respectively
+//@chat_id Identifier of the chat the message belongs to @message_id Identifier of the reply message
getRepliedMessage chat_id:int53 message_id:int53 = Message;
-//@description Returns information about a pinned chat message @chat_id Identifier of the chat the message belongs to
+//@description Returns information about a newest pinned message in the chat @chat_id Identifier of the chat the message belongs to
getChatPinnedMessage chat_id:int53 = Message;
+//@description Returns information about a message with the callback button that originated a callback query; for bots only @chat_id Identifier of the chat the message belongs to @message_id Message identifier @callback_query_id Identifier of the callback query
+getCallbackQueryMessage chat_id:int53 message_id:int53 callback_query_id:int64 = Message;
+
//@description Returns information about messages. If a message is not found, returns null on the corresponding position of the result @chat_id Identifier of the chat the messages belong to @message_ids Identifiers of the messages to get
getMessages chat_id:int53 message_ids:vector<int53> = Messages;
+//@description Returns information about a message thread. Can be used only if message.can_get_message_thread == true @chat_id Chat identifier @message_id Identifier of the message
+getMessageThread chat_id:int53 message_id:int53 = MessageThreadInfo;
+
+//@description Returns viewers of a recent outgoing message in a basic group or a supergroup chat. For video notes and voice notes only users, opened content of the message, are returned. The method can be called if message.can_get_viewers == true @chat_id Chat identifier @message_id Identifier of the message
+getMessageViewers chat_id:int53 message_id:int53 = Users;
+
//@description Returns information about a file; this is an offline request @file_id Identifier of the file to get
getFile file_id:int32 = File;
-//@description Returns information about a file by its remote ID; this is an offline request. Can be used to register a URL as a file for further uploading, or sending as a message @remote_file_id Remote identifier of the file to get @file_type File type, if known
+//@description Returns information about a file by its remote ID; this is an offline request. Can be used to register a URL as a file for further uploading, or sending as a message. Even the request succeeds, the file can be used only if it is still accessible to the user.
+//-For example, if the file is from a message, then the message must be not deleted and accessible to the user. If the file database is disabled, then the corresponding object with the file must be preloaded by the application
+//@remote_file_id Remote identifier of the file to get @file_type File type; pass null if unknown
getRemoteFile remote_file_id:string file_type:FileType = File;
-//@description Returns an ordered list of chats. Chats are sorted by the pair (order, chat_id) in decreasing order. (For example, to get a list of chats from the beginning, the offset_order should be equal to 2^63 - 1).
-//-For optimal performance the number of returned chats is chosen by the library. @offset_order Chat order to return chats from @offset_chat_id Chat identifier to return chats from
-//@limit The maximum number of chats to be returned. It is possible that fewer chats than the limit are returned even if the end of the list is not reached
-getChats offset_order:int64 offset_chat_id:int53 limit:int32 = Chats;
+//@description Loads more chats from a chat list. The loaded chats and their positions in the chat list will be sent through updates. Chats are sorted by the pair (chat.position.order, chat.id) in descending order. Returns a 404 error if all chats have been loaded
+//@chat_list The chat list in which to load chats; pass null to load chats from the main chat list
+//@limit The maximum number of chats to be loaded. For optimal performance, the number of loaded chats is chosen by TDLib and can be smaller than the specified limit, even if the end of the list is not reached
+loadChats chat_list:ChatList limit:int32 = Ok;
-//@description Searches a public chat by its username. Currently only private chats, supergroups and channels can be public. Returns the chat if found; otherwise an error is returned @username Username to be resolved
+//@description Returns an ordered list of chats from the beginning of a chat list. For informational purposes only. Use loadChats and updates processing instead to maintain chat lists in a consistent state
+//@chat_list The chat list in which to return chats; pass null to get chats from the main chat list @limit The maximum number of chats to be returned
+getChats chat_list:ChatList limit:int32 = Chats;
+
+//@description Searches a public chat by its username. Currently, only private chats, supergroups and channels can be public. Returns the chat if found; otherwise an error is returned @username Username to be resolved
searchPublicChat username:string = Chat;
-//@description Searches public chats by looking for specified query in their username and title. Currently only private chats, supergroups and channels can be public. Returns a meaningful number of results. Returns nothing if the length of the searched username prefix is less than 5. Excludes private chats with contacts and chats from the chat list from the results @query Query to search for
+//@description Searches public chats by looking for specified query in their username and title. Currently, only private chats, supergroups and channels can be public. Returns a meaningful number of results.
+//-Excludes private chats with contacts and chats from the chat list from the results @query Query to search for
searchPublicChats query:string = Chats;
-//@description Searches for the specified query in the title and username of already known chats, this is an offline request. Returns chats in the order seen in the chat list @query Query to search for. If the query is empty, returns up to 20 recently found chats @limit Maximum number of chats to be returned
+//@description Searches for the specified query in the title and username of already known chats, this is an offline request. Returns chats in the order seen in the main chat list @query Query to search for. If the query is empty, returns up to 50 recently found chats @limit The maximum number of chats to be returned
searchChats query:string limit:int32 = Chats;
-//@description Searches for the specified query in the title and username of already known chats via request to the server. Returns chats in the order seen in the chat list @query Query to search for @limit Maximum number of chats to be returned
+//@description Searches for the specified query in the title and username of already known chats via request to the server. Returns chats in the order seen in the main chat list @query Query to search for @limit The maximum number of chats to be returned
searchChatsOnServer query:string limit:int32 = Chats;
-//@description Returns a list of frequently used chats. Supported only if the chat info database is enabled @category Category of chats to be returned @limit Maximum number of chats to be returned; up to 30
+//@description Returns a list of users and location-based supergroups nearby. The list of users nearby will be updated for 60 seconds after the request by the updates updateUsersNearby. The request must be sent again every 25 seconds with adjusted location to not miss new chats @location Current user location
+searchChatsNearby location:location = ChatsNearby;
+
+//@description Returns a list of frequently used chats. Supported only if the chat info database is enabled @category Category of chats to be returned @limit The maximum number of chats to be returned; up to 30
getTopChats category:TopChatCategory limit:int32 = Chats;
//@description Removes a chat from the list of frequently used chats. Supported only if the chat info database is enabled @category Category of frequently used chats @chat_id Chat identifier
@@ -2125,172 +4960,534 @@ removeRecentlyFoundChat chat_id:int53 = Ok;
//@description Clears the list of recently found chats
clearRecentlyFoundChats = Ok;
-//@description Checks whether a username can be set for a chat @chat_id Chat identifier; should be identifier of a supergroup chat, or a channel chat, or a private chat with self, or zero if chat is being created @username Username to be checked
-checkChatUsername chat_id:int64 username:string = CheckChatUsernameResult;
+//@description Returns recently opened chats, this is an offline request. Returns chats in the order of last opening @limit The maximum number of chats to be returned
+getRecentlyOpenedChats limit:int32 = Chats;
+
+//@description Checks whether a username can be set for a chat @chat_id Chat identifier; must be identifier of a supergroup chat, or a channel chat, or a private chat with self, or zero if the chat is being created @username Username to be checked
+checkChatUsername chat_id:int53 username:string = CheckChatUsernameResult;
+
+//@description Returns a list of public chats of the specified type, owned by the user @type Type of the public chats to return
+getCreatedPublicChats type:PublicChatType = Chats;
+
+//@description Checks whether the maximum number of owned public chats has been reached. Returns corresponding error if the limit was reached. The limit can be increased with Telegram Premium @type Type of the public chats, for which to check the limit
+checkCreatedPublicChatsLimit type:PublicChatType = Ok;
-//@description Returns a list of public chats created by the user
-getCreatedPublicChats = Chats;
+//@description Returns a list of basic group and supergroup chats, which can be used as a discussion group for a channel. Returned basic group chats must be first upgraded to supergroups before they can be set as a discussion group. To set a returned supergroup as a discussion group, access to its old messages must be enabled using toggleSupergroupIsAllHistoryAvailable first
+getSuitableDiscussionChats = Chats;
+//@description Returns a list of recently inactive supergroups and channels. Can be used when user reaches limit on the number of joined supergroups and channels and receives CHANNELS_TOO_MUCH error. Also, the limit can be increased with Telegram Premium
+getInactiveSupergroupChats = Chats;
-//@description Returns a list of common chats with a given user. Chats are sorted by their type and creation date @user_id User identifier @offset_chat_id Chat identifier starting from which to return chats; use 0 for the first request @limit Maximum number of chats to be returned; up to 100
-getGroupsInCommon user_id:int32 offset_chat_id:int53 limit:int32 = Chats;
+
+//@description Returns a list of common group chats with a given user. Chats are sorted by their type and creation date @user_id User identifier @offset_chat_id Chat identifier starting from which to return chats; use 0 for the first request @limit The maximum number of chats to be returned; up to 100
+getGroupsInCommon user_id:int53 offset_chat_id:int53 limit:int32 = Chats;
//@description Returns messages in a chat. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id).
-//-For optimal performance the number of returned messages is chosen by the library. This is an offline request if only_local is true
+//-For optimal performance, the number of returned messages is chosen by TDLib. This is an offline request if only_local is true
//@chat_id Chat identifier
-//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the beginning (i.e., from oldest to newest)
-//@offset Specify 0 to get results from exactly the from_message_id or a negative offset to get the specified message and some newer messages
-//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than -offset. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached
-//@only_local If true, returns only messages that are available locally without sending network requests
+//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message
+//@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages
+//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
+//@only_local Pass true to get only messages that are available without sending network requests
getChatHistory chat_id:int53 from_message_id:int53 offset:int32 limit:int32 only_local:Bool = Messages;
-//@description Deletes all messages in the chat only for the user. Cannot be used in channels and public supergroups @chat_id Chat identifier @remove_from_chat_list Pass true if the chat should be removed from the chats list
-deleteChatHistory chat_id:int53 remove_from_chat_list:Bool = Ok;
+//@description Returns messages in a message thread of a message. Can be used only if message.can_get_message_thread == true. Message thread of a channel message is in the channel's linked supergroup.
+//-The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib
+//@chat_id Chat identifier
+//@message_id Message identifier, which thread history needs to be returned
+//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message
+//@offset Specify 0 to get results from exactly the from_message_id or a negative offset up to 99 to get additionally some newer messages
+//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than or equal to -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
+getMessageThreadHistory chat_id:int53 message_id:int53 from_message_id:int53 offset:int32 limit:int32 = Messages;
+
+//@description Deletes all messages in the chat. Use chat.can_be_deleted_only_for_self and chat.can_be_deleted_for_all_users fields to find whether and how the method can be applied to the chat
+//@chat_id Chat identifier @remove_from_chat_list Pass true to remove the chat from all chat lists @revoke Pass true to delete chat history for all users
+deleteChatHistory chat_id:int53 remove_from_chat_list:Bool revoke:Bool = Ok;
+
+//@description Deletes a chat along with all messages in the corresponding chat for all chat members. For group chats this will release the usernames and remove all members. Use the field chat.can_be_deleted_for_all_users to find whether the method can be applied to the chat @chat_id Chat identifier
+deleteChat chat_id:int53 = Ok;
//@description Searches for messages with given words in the chat. Returns the results in reverse chronological order, i.e. in order of decreasing message_id. Cannot be used in secret chats with a non-empty query
-//-(searchSecretMessages should be used instead), or without an enabled message database. For optimal performance the number of returned messages is chosen by the library
+//-(searchSecretMessages must be used instead), or without an enabled message database. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit.
+//-A combination of query, sender_id, filter and message_thread_id search criteria is expected to be supported, only if it is required for Telegram official application implementation
//@chat_id Identifier of the chat in which to search messages
//@query Query to search for
-//@sender_user_id If not 0, only messages sent by the specified user will be returned. Not supported in secret chats
-//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the beginning
+//@sender_id Identifier of the sender of messages to search for; pass null to search for messages from any sender. Not supported in secret chats
+//@from_message_id Identifier of the message starting from which history must be fetched; use 0 to get results from the last message
//@offset Specify 0 to get results from exactly the from_message_id or a negative offset to get the specified message and some newer messages
-//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than -offset. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached
-//@filter Filter for message content in the search results
-searchChatMessages chat_id:int53 query:string sender_user_id:int32 from_message_id:int53 offset:int32 limit:int32 filter:SearchMessagesFilter = Messages;
+//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, the limit must be greater than -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
+//@filter Additional filter for messages to search; pass null to search for all messages
+//@message_thread_id If not 0, only messages in the specified thread will be returned; supergroups only
+searchChatMessages chat_id:int53 query:string sender_id:MessageSender from_message_id:int53 offset:int32 limit:int32 filter:SearchMessagesFilter message_thread_id:int53 = Messages;
//@description Searches for messages in all chats except secret chats. Returns the results in reverse chronological order (i.e., in order of decreasing (date, chat_id, message_id)).
-//-For optimal performance the number of returned messages is chosen by the library
+//-For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
+//@chat_list Chat list in which to search messages; pass null to search in all chats regardless of their chat list. Only Main and Archive chat lists are supported
//@query Query to search for
-//@offset_date The date of the message starting from which the results should be fetched. Use 0 or any date in the future to get results from the beginning
+//@offset_date The date of the message starting from which the results need to be fetched. Use 0 or any date in the future to get results from the last message
//@offset_chat_id The chat identifier of the last found message, or 0 for the first request
//@offset_message_id The message identifier of the last found message, or 0 for the first request
-//@limit The maximum number of messages to be returned, up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached
-searchMessages query:string offset_date:int32 offset_chat_id:int53 offset_message_id:int53 limit:int32 = Messages;
-
-//@description Searches for messages in secret chats. Returns the results in reverse chronological order. For optimal performance the number of returned messages is chosen by the library
-//@chat_id Identifier of the chat in which to search. Specify 0 to search in all secret chats @query Query to search for. If empty, searchChatMessages should be used instead
-//@from_search_id The identifier from the result of a previous request, use 0 to get results from the beginning
-//@limit Maximum number of messages to be returned; up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached
-//@filter A filter for the content of messages in the search results
-searchSecretMessages chat_id:int53 query:string from_search_id:int64 limit:int32 filter:SearchMessagesFilter = FoundMessages;
-
-//@description Searches for call messages. Returns the results in reverse chronological order (i. e., in order of decreasing message_id). For optimal performance the number of returned messages is chosen by the library
-//@from_message_id Identifier of the message from which to search; use 0 to get results from the beginning
-//@limit The maximum number of messages to be returned; up to 100. Fewer messages may be returned than specified by the limit, even if the end of the message history has not been reached @only_missed If true, returns only messages with missed calls
+//@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
+//@filter Additional filter for messages to search; pass null to search for all messages. Filters searchMessagesFilterMention, searchMessagesFilterUnreadMention, searchMessagesFilterUnreadReaction, searchMessagesFilterFailedToSend, and searchMessagesFilterPinned are unsupported in this function
+//@min_date If not 0, the minimum date of the messages to return
+//@max_date If not 0, the maximum date of the messages to return
+searchMessages chat_list:ChatList query:string offset_date:int32 offset_chat_id:int53 offset_message_id:int53 limit:int32 filter:SearchMessagesFilter min_date:int32 max_date:int32 = Messages;
+
+//@description Searches for messages in secret chats. Returns the results in reverse chronological order. For optimal performance, the number of returned messages is chosen by TDLib
+//@chat_id Identifier of the chat in which to search. Specify 0 to search in all secret chats
+//@query Query to search for. If empty, searchChatMessages must be used instead
+//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results
+//@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
+//@filter Additional filter for messages to search; pass null to search for all messages
+searchSecretMessages chat_id:int53 query:string offset:string limit:int32 filter:SearchMessagesFilter = FoundMessages;
+
+//@description Searches for call messages. Returns the results in reverse chronological order (i.e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib
+//@from_message_id Identifier of the message from which to search; use 0 to get results from the last message
+//@limit The maximum number of messages to be returned; up to 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
+//@only_missed Pass true to search only for messages with missed/declined calls
searchCallMessages from_message_id:int53 limit:int32 only_missed:Bool = Messages;
-//@description Returns information about the recent locations of chat members that were sent to the chat. Returns up to 1 location message per user @chat_id Chat identifier @limit Maximum number of messages to be returned
+//@description Searches for outgoing messages with content of the type messageDocument in all chats except secret chats. Returns the results in reverse chronological order
+//@query Query to search for in document file name and message caption
+//@limit The maximum number of messages to be returned; up to 100
+searchOutgoingDocumentMessages query:string limit:int32 = FoundMessages;
+
+//@description Deletes all call messages @revoke Pass true to delete the messages for all users
+deleteAllCallMessages revoke:Bool = Ok;
+
+//@description Returns information about the recent locations of chat members that were sent to the chat. Returns up to 1 location message per user @chat_id Chat identifier @limit The maximum number of messages to be returned
searchChatRecentLocationMessages chat_id:int53 limit:int32 = Messages;
-//@description Returns all active live locations that should be updated by the client. The list is persistent across application restarts only if the message database is used
+//@description Returns all active live locations that need to be updated by the application. The list is persistent across application restarts only if the message database is used
getActiveLiveLocationMessages = Messages;
//@description Returns the last message sent in a chat no later than the specified date @chat_id Chat identifier @date Point in time (Unix timestamp) relative to which to search for messages
getChatMessageByDate chat_id:int53 date:int32 = Message;
+//@description Returns sparse positions of messages of the specified type in the chat to be used for shared media scroll implementation. Returns the results in reverse chronological order (i.e., in order of decreasing message_id).
+//-Cannot be used in secret chats or with searchMessagesFilterFailedToSend filter without an enabled message database
+//@chat_id Identifier of the chat in which to return information about message positions
+//@filter Filter for message content. Filters searchMessagesFilterEmpty, searchMessagesFilterMention, searchMessagesFilterUnreadMention, and searchMessagesFilterUnreadReaction are unsupported in this function
+//@from_message_id The message identifier from which to return information about message positions
+//@limit The expected number of message positions to be returned; 50-2000. A smaller number of positions can be returned, if there are not enough appropriate messages
+getChatSparseMessagePositions chat_id:int53 filter:SearchMessagesFilter from_message_id:int53 limit:int32 = MessagePositions;
+
+//@description Returns information about the next messages of the specified type in the chat split by days. Returns the results in reverse chronological order. Can return partial result for the last returned day. Behavior of this method depends on the value of the option "utc_time_offset"
+//@chat_id Identifier of the chat in which to return information about messages
+//@filter Filter for message content. Filters searchMessagesFilterEmpty, searchMessagesFilterMention, searchMessagesFilterUnreadMention, and searchMessagesFilterUnreadReaction are unsupported in this function
+//@from_message_id The message identifier from which to return information about messages; use 0 to get results from the last message
+getChatMessageCalendar chat_id:int53 filter:SearchMessagesFilter from_message_id:int53 = MessageCalendar;
+
+//@description Returns approximate number of messages of the specified type in the chat @chat_id Identifier of the chat in which to count messages @filter Filter for message content; searchMessagesFilterEmpty is unsupported in this function @return_local Pass true to get the number of messages without sending network requests, or -1 if the number of messages is unknown locally
+getChatMessageCount chat_id:int53 filter:SearchMessagesFilter return_local:Bool = Count;
+
+//@description Returns approximate 1-based position of a message among messages, which can be found by the specified filter in the chat. Cannot be used in secret chats
+//@chat_id Identifier of the chat in which to find message position @message_id Message identifier
+//@filter Filter for message content; searchMessagesFilterEmpty, searchMessagesFilterUnreadMention, searchMessagesFilterUnreadReaction, and searchMessagesFilterFailedToSend are unsupported in this function
+//@message_thread_id If not 0, only messages in the specified thread will be considered; supergroups only
+getChatMessagePosition chat_id:int53 message_id:int53 filter:SearchMessagesFilter message_thread_id:int53 = Count;
+
+//@description Returns all scheduled messages in a chat. The messages are returned in a reverse chronological order (i.e., in order of decreasing message_id) @chat_id Chat identifier
+getChatScheduledMessages chat_id:int53 = Messages;
+
+//@description Returns forwarded copies of a channel message to different public channels. For optimal performance, the number of returned messages is chosen by TDLib
+//@chat_id Chat identifier of the message
+//@message_id Message identifier
+//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results
+//@limit The maximum number of messages to be returned; must be positive and can't be greater than 100. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit
+getMessagePublicForwards chat_id:int53 message_id:int53 offset:string limit:int32 = FoundMessages;
+
+//@description Returns sponsored messages to be shown in a chat; for channel chats only @chat_id Identifier of the chat
+getChatSponsoredMessages chat_id:int53 = SponsoredMessages;
+
-//@description Returns a public HTTPS link to a message. Available only for messages in public supergroups and channels
+//@description Removes an active notification from notification list. Needs to be called only if the notification is removed by the current user @notification_group_id Identifier of notification group to which the notification belongs @notification_id Identifier of removed notification
+removeNotification notification_group_id:int32 notification_id:int32 = Ok;
+
+//@description Removes a group of active notifications. Needs to be called only if the notification group is removed by the current user @notification_group_id Notification group identifier @max_notification_id The maximum identifier of removed notifications
+removeNotificationGroup notification_group_id:int32 max_notification_id:int32 = Ok;
+
+
+//@description Returns an HTTPS link to a message in a chat. Available only for already sent messages in supergroups and channels, or if message.can_get_media_timestamp_links and a media timestamp link is generated. This is an offline request
+//@chat_id Identifier of the chat to which the message belongs
+//@message_id Identifier of the message
+//@media_timestamp If not 0, timestamp from which the video/audio/video note/voice note playing must start, in seconds. The media can be in the message content or in its web page preview
+//@for_album Pass true to create a link for the whole media album
+//@in_message_thread Pass true to create a link to the message as a channel post comment, in a message thread, or a forum topic
+getMessageLink chat_id:int53 message_id:int53 media_timestamp:int32 for_album:Bool in_message_thread:Bool = MessageLink;
+
+//@description Returns an HTML code for embedding the message. Available only for messages in supergroups and channels with a username
//@chat_id Identifier of the chat to which the message belongs
//@message_id Identifier of the message
-//@for_album Pass true if a link for a whole media album should be returned
-getPublicMessageLink chat_id:int53 message_id:int53 for_album:Bool = PublicMessageLink;
+//@for_album Pass true to return an HTML code for embedding of the whole media album
+getMessageEmbeddingCode chat_id:int53 message_id:int53 for_album:Bool = Text;
+//@description Returns information about a public or private message link. Can be called for any internal link of the type internalLinkTypeMessage @url The message link
+getMessageLinkInfo url:string = MessageLinkInfo;
-//@description Sends a message. Returns the sent message @chat_id Target chat @reply_to_message_id Identifier of the message to reply to or 0
-//@disable_notification Pass true to disable notification for the message. Not supported in secret chats @from_background Pass true if the message is sent from the background
-//@reply_markup Markup for replying to the message; for bots only @input_message_content The content of the message to be sent
-sendMessage chat_id:int53 reply_to_message_id:int53 disable_notification:Bool from_background:Bool reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message;
-//@description Sends messages grouped together into an album. Currently only photo and video messages can be grouped into an album. Returns sent messages @chat_id Target chat @reply_to_message_id Identifier of a message to reply to or 0
-//@disable_notification Pass true to disable notification for the messages. Not supported in secret chats @from_background Pass true if the messages are sent from the background
-//@input_message_contents Contents of messages to be sent
-sendMessageAlbum chat_id:int53 reply_to_message_id:int53 disable_notification:Bool from_background:Bool input_message_contents:vector<InputMessageContent> = Messages;
+//@description Translates a text to the given language. Returns a 404 error if the translation can't be performed
+//@text Text to translate
+//@from_language_code A two-letter ISO 639-1 language code of the language from which the message is translated. If empty, the language will be detected automatically
+//@to_language_code A two-letter ISO 639-1 language code of the language to which the message is translated
+translateText text:string from_language_code:string to_language_code:string = Text;
-//@description Invites a bot to a chat (if it is not yet a member) and sends it the /start command. Bots can't be invited to a private chat other than the chat with the bot. Bots can't be invited to channels (although they can be added as admins) and secret chats. Returns the sent message
-//@bot_user_id Identifier of the bot @chat_id Identifier of the target chat @parameter A hidden parameter sent to the bot for deep linking purposes (https://api.telegram.org/bots#deep-linking)
-sendBotStartMessage bot_user_id:int32 chat_id:int53 parameter:string = Message;
+//@description Recognizes speech in a video note or a voice note message. The message must be successfully sent and must not be scheduled. May return an error with a message "MSG_VOICE_TOO_LONG" if media duration is too big to be recognized
+//@chat_id Identifier of the chat to which the message belongs
+//@message_id Identifier of the message
+recognizeSpeech chat_id:int53 message_id:int53 = Ok;
-//@description Sends the result of an inline query as a message. Returns the sent message. Always clears a chat draft message @chat_id Target chat @reply_to_message_id Identifier of a message to reply to or 0
-//@disable_notification Pass true to disable notification for the message. Not supported in secret chats @from_background Pass true if the message is sent from background
-//@query_id Identifier of the inline query @result_id Identifier of the inline result
-sendInlineQueryResultMessage chat_id:int53 reply_to_message_id:int53 disable_notification:Bool from_background:Bool query_id:int64 result_id:string = Message;
+//@description Rates recognized speech in a video note or a voice note message @chat_id Identifier of the chat to which the message belongs @message_id Identifier of the message @is_good Pass true if the speech recognition is good
+rateSpeechRecognition chat_id:int53 message_id:int53 is_good:Bool = Ok;
-//@description Forwards previously sent messages. Returns the forwarded messages in the same order as the message identifiers passed in message_ids. If a message can't be forwarded, null will be returned instead of the message
-//@chat_id Identifier of the chat to which to forward messages @from_chat_id Identifier of the chat from which to forward messages @message_ids Identifiers of the messages to forward
-//@disable_notification Pass true to disable notification for the message, doesn't work if messages are forwarded to a secret chat @from_background Pass true if the message is sent from the background
-//@as_album True, if the messages should be grouped into an album after forwarding. For this to work, no more than 10 messages may be forwarded, and all of them must be photo or video messages
-forwardMessages chat_id:int53 from_chat_id:int53 message_ids:vector<int53> disable_notification:Bool from_background:Bool as_album:Bool = Messages;
-//@description Changes the current TTL setting (sets a new self-destruct timer) in a secret chat and sends the corresponding message @chat_id Chat identifier @ttl New TTL value, in seconds
-sendChatSetTtlMessage chat_id:int53 ttl:int32 = Message;
+//@description Returns list of message sender identifiers, which can be used to send messages in a chat @chat_id Chat identifier
+getChatAvailableMessageSenders chat_id:int53 = ChatMessageSenders;
+
+//@description Selects a message sender to send messages in a chat @chat_id Chat identifier @message_sender_id New message sender for the chat
+setChatMessageSender chat_id:int53 message_sender_id:MessageSender = Ok;
+
+//@description Sends a message. Returns the sent message
+//@chat_id Target chat
+//@message_thread_id If not 0, a message thread identifier in which the message will be sent
+//@reply_to_message_id Identifier of the replied message; 0 if none
+//@options Options to be used to send the message; pass null to use default options
+//@reply_markup Markup for replying to the message; pass null if none; for bots only
+//@input_message_content The content of the message to be sent
+sendMessage chat_id:int53 message_thread_id:int53 reply_to_message_id:int53 options:messageSendOptions reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message;
+
+//@description Sends 2-10 messages grouped together into an album. Currently, only audio, document, photo and video messages can be grouped into an album. Documents and audio files can be only grouped in an album with messages of the same type. Returns sent messages
+//@chat_id Target chat
+//@message_thread_id If not 0, a message thread identifier in which the messages will be sent
+//@reply_to_message_id Identifier of a replied message; 0 if none
+//@options Options to be used to send the messages; pass null to use default options
+//@input_message_contents Contents of messages to be sent. At most 10 messages can be added to an album
+//@only_preview Pass true to get fake messages instead of actually sending them
+sendMessageAlbum chat_id:int53 message_thread_id:int53 reply_to_message_id:int53 options:messageSendOptions input_message_contents:vector<InputMessageContent> only_preview:Bool = Messages;
+
+//@description Invites a bot to a chat (if it is not yet a member) and sends it the /start command. Bots can't be invited to a private chat other than the chat with the bot. Bots can't be invited to channels (although they can be added as admins) and secret chats. Returns the sent message
+//@bot_user_id Identifier of the bot @chat_id Identifier of the target chat @parameter A hidden parameter sent to the bot for deep linking purposes (https://core.telegram.org/bots#deep-linking)
+sendBotStartMessage bot_user_id:int53 chat_id:int53 parameter:string = Message;
+
+//@description Sends the result of an inline query as a message. Returns the sent message. Always clears a chat draft message
+//@chat_id Target chat
+//@message_thread_id If not 0, a message thread identifier in which the message will be sent
+//@reply_to_message_id Identifier of a replied message; 0 if none
+//@options Options to be used to send the message; pass null to use default options
+//@query_id Identifier of the inline query
+//@result_id Identifier of the inline result
+//@hide_via_bot Pass true to hide the bot, via which the message is sent. Can be used only for bots GetOption("animation_search_bot_username"), GetOption("photo_search_bot_username"), and GetOption("venue_search_bot_username")
+sendInlineQueryResultMessage chat_id:int53 message_thread_id:int53 reply_to_message_id:int53 options:messageSendOptions query_id:int64 result_id:string hide_via_bot:Bool = Message;
+
+//@description Forwards previously sent messages. Returns the forwarded messages in the same order as the message identifiers passed in message_ids. If a message can't be forwarded, null will be returned instead of the message
+//@chat_id Identifier of the chat to which to forward messages
+//@message_thread_id If not 0, a message thread identifier in which the message will be sent; for forum threads only
+//@from_chat_id Identifier of the chat from which to forward messages
+//@message_ids Identifiers of the messages to forward. Message identifiers must be in a strictly increasing order. At most 100 messages can be forwarded simultaneously
+//@options Options to be used to send the messages; pass null to use default options
+//@send_copy Pass true to copy content of the messages without reference to the original sender. Always true if the messages are forwarded to a secret chat or are local
+//@remove_caption Pass true to remove media captions of message copies. Ignored if send_copy is false
+//@only_preview Pass true to get fake messages instead of actually forwarding them
+forwardMessages chat_id:int53 message_thread_id:int53 from_chat_id:int53 message_ids:vector<int53> options:messageSendOptions send_copy:Bool remove_caption:Bool only_preview:Bool = Messages;
+
+//@description Resends messages which failed to send. Can be called only for messages for which messageSendingStateFailed.can_retry is true and after specified in messageSendingStateFailed.retry_after time passed.
+//-If a message is re-sent, the corresponding failed to send message is deleted. Returns the sent messages in the same order as the message identifiers passed in message_ids. If a message can't be re-sent, null will be returned instead of the message
+//@chat_id Identifier of the chat to send messages @message_ids Identifiers of the messages to resend. Message identifiers must be in a strictly increasing order
+resendMessages chat_id:int53 message_ids:vector<int53> = Messages;
//@description Sends a notification about a screenshot taken in a chat. Supported only in private and secret chats @chat_id Chat identifier
sendChatScreenshotTakenNotification chat_id:int53 = Ok;
-//@description Deletes messages @chat_id Chat identifier @message_ids Identifiers of the messages to be deleted @revoke Pass true to try to delete outgoing messages for all chat members (may fail if messages are too old). Always true for supergroups, channels and secret chats
+//@description Adds a local message to a chat. The message is persistent across application restarts only if the message database is used. Returns the added message
+//@chat_id Target chat
+//@sender_id Identifier of the sender of the message
+//@reply_to_message_id Identifier of the replied message; 0 if none
+//@disable_notification Pass true to disable notification for the message
+//@input_message_content The content of the message to be added
+addLocalMessage chat_id:int53 sender_id:MessageSender reply_to_message_id:int53 disable_notification:Bool input_message_content:InputMessageContent = Message;
+
+//@description Deletes messages @chat_id Chat identifier @message_ids Identifiers of the messages to be deleted @revoke Pass true to delete messages for all chat members. Always true for supergroups, channels and secret chats
deleteMessages chat_id:int53 message_ids:vector<int53> revoke:Bool = Ok;
-//@description Deletes all messages sent by the specified user to a chat. Supported only in supergroups; requires can_delete_messages administrator privileges @chat_id Chat identifier @user_id User identifier
-deleteChatMessagesFromUser chat_id:int53 user_id:int32 = Ok;
+//@description Deletes all messages sent by the specified message sender in a chat. Supported only for supergroups; requires can_delete_messages administrator privileges @chat_id Chat identifier @sender_id Identifier of the sender of messages to delete
+deleteChatMessagesBySender chat_id:int53 sender_id:MessageSender = Ok;
+
+//@description Deletes all messages between the specified dates in a chat. Supported only for private chats and basic groups. Messages sent in the last 30 seconds will not be deleted
+//@chat_id Chat identifier @min_date The minimum date of the messages to delete @max_date The maximum date of the messages to delete @revoke Pass true to delete chat messages for all users; private chats only
+deleteChatMessagesByDate chat_id:int53 min_date:int32 max_date:int32 revoke:Bool = Ok;
-//@description Edits the text of a message (or a text of a game message). Non-bot users can edit messages for a limited period of time. Returns the edited message after the edit is completed on the server side
-//@chat_id The chat the message belongs to @message_id Identifier of the message @reply_markup The new message reply markup; for bots only @input_message_content New text content of the message. Should be of type InputMessageText
+//@description Edits the text of a message (or a text of a game message). Returns the edited message after the edit is completed on the server side
+//@chat_id The chat the message belongs to
+//@message_id Identifier of the message
+//@reply_markup The new message reply markup; pass null if none; for bots only
+//@input_message_content New text content of the message. Must be of type inputMessageText
editMessageText chat_id:int53 message_id:int53 reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message;
-//@description Edits the message content of a live location. Messages can be edited for a limited period of time specified in the live location. Returns the edited message after the edit is completed server-side
-//@chat_id The chat the message belongs to @message_id Identifier of the message @reply_markup Tew message reply markup; for bots only @location New location content of the message; may be null. Pass null to stop sharing the live location
-editMessageLiveLocation chat_id:int53 message_id:int53 reply_markup:ReplyMarkup location:location = Message;
+//@description Edits the message content of a live location. Messages can be edited for a limited period of time specified in the live location. Returns the edited message after the edit is completed on the server side
+//@chat_id The chat the message belongs to
+//@message_id Identifier of the message
+//@reply_markup The new message reply markup; pass null if none; for bots only
+//@location New location content of the message; pass null to stop sharing the live location
+//@heading The new direction in which the location moves, in degrees; 1-360. Pass 0 if unknown
+//@proximity_alert_radius The new maximum distance for proximity alerts, in meters (0-100000). Pass 0 if the notification is disabled
+editMessageLiveLocation chat_id:int53 message_id:int53 reply_markup:ReplyMarkup location:location heading:int32 proximity_alert_radius:int32 = Message;
+
+//@description Edits the content of a message with an animation, an audio, a document, a photo or a video, including message caption. If only the caption needs to be edited, use editMessageCaption instead.
+//-The media can't be edited if the message was set to self-destruct or to a self-destructing media. The type of message content in an album can't be changed with exception of replacing a photo with a video or vice versa. Returns the edited message after the edit is completed on the server side
+//@chat_id The chat the message belongs to
+//@message_id Identifier of the message
+//@reply_markup The new message reply markup; pass null if none; for bots only
+//@input_message_content New content of the message. Must be one of the following types: inputMessageAnimation, inputMessageAudio, inputMessageDocument, inputMessagePhoto or inputMessageVideo
+editMessageMedia chat_id:int53 message_id:int53 reply_markup:ReplyMarkup input_message_content:InputMessageContent = Message;
-//@description Edits the message content caption. Non-bots can edit messages for a limited period of time. Returns the edited message after the edit is completed server-side
-//@chat_id The chat the message belongs to @message_id Identifier of the message @reply_markup The new message reply markup; for bots only @caption New message content caption; 0-200 characters
+//@description Edits the message content caption. Returns the edited message after the edit is completed on the server side
+//@chat_id The chat the message belongs to
+//@message_id Identifier of the message
+//@reply_markup The new message reply markup; pass null if none; for bots only
+//@caption New message content caption; 0-GetOption("message_caption_length_max") characters; pass null to remove caption
editMessageCaption chat_id:int53 message_id:int53 reply_markup:ReplyMarkup caption:formattedText = Message;
-//@description Edits the message reply markup; for bots only. Returns the edited message after the edit is completed server-side
-//@chat_id The chat the message belongs to @message_id Identifier of the message @reply_markup New message reply markup
+//@description Edits the message reply markup; for bots only. Returns the edited message after the edit is completed on the server side
+//@chat_id The chat the message belongs to
+//@message_id Identifier of the message
+//@reply_markup The new message reply markup; pass null if none
editMessageReplyMarkup chat_id:int53 message_id:int53 reply_markup:ReplyMarkup = Message;
-//@description Edits the text of an inline text or game message sent via a bot; for bots only @inline_message_id Inline message identifier @reply_markup New message reply markup @input_message_content New text content of the message. Should be of type InputMessageText
+//@description Edits the text of an inline text or game message sent via a bot; for bots only
+//@inline_message_id Inline message identifier
+//@reply_markup The new message reply markup; pass null if none
+//@input_message_content New text content of the message. Must be of type inputMessageText
editInlineMessageText inline_message_id:string reply_markup:ReplyMarkup input_message_content:InputMessageContent = Ok;
-//@description Edits the content of a live location in an inline message sent via a bot; for bots only @inline_message_id Inline message identifier @reply_markup New message reply markup @location New location content of the message; may be null. Pass null to stop sharing the live location
-editInlineMessageLiveLocation inline_message_id:string reply_markup:ReplyMarkup location:location = Ok;
-
-//@description Edits the caption of an inline message sent via a bot; for bots only @inline_message_id Inline message identifier @reply_markup New message reply markup @caption New message content caption; 0-200 characters
+//@description Edits the content of a live location in an inline message sent via a bot; for bots only
+//@inline_message_id Inline message identifier
+//@reply_markup The new message reply markup; pass null if none
+//@location New location content of the message; pass null to stop sharing the live location
+//@heading The new direction in which the location moves, in degrees; 1-360. Pass 0 if unknown
+//@proximity_alert_radius The new maximum distance for proximity alerts, in meters (0-100000). Pass 0 if the notification is disabled
+editInlineMessageLiveLocation inline_message_id:string reply_markup:ReplyMarkup location:location heading:int32 proximity_alert_radius:int32 = Ok;
+
+//@description Edits the content of a message with an animation, an audio, a document, a photo or a video in an inline message sent via a bot; for bots only
+//@inline_message_id Inline message identifier
+//@reply_markup The new message reply markup; pass null if none; for bots only
+//@input_message_content New content of the message. Must be one of the following types: inputMessageAnimation, inputMessageAudio, inputMessageDocument, inputMessagePhoto or inputMessageVideo
+editInlineMessageMedia inline_message_id:string reply_markup:ReplyMarkup input_message_content:InputMessageContent = Ok;
+
+//@description Edits the caption of an inline message sent via a bot; for bots only
+//@inline_message_id Inline message identifier
+//@reply_markup The new message reply markup; pass null if none
+//@caption New message content caption; pass null to remove caption; 0-GetOption("message_caption_length_max") characters
editInlineMessageCaption inline_message_id:string reply_markup:ReplyMarkup caption:formattedText = Ok;
-//@description Edits the reply markup of an inline message sent via a bot; for bots only @inline_message_id Inline message identifier @reply_markup New message reply markup
+//@description Edits the reply markup of an inline message sent via a bot; for bots only
+//@inline_message_id Inline message identifier
+//@reply_markup The new message reply markup; pass null if none
editInlineMessageReplyMarkup inline_message_id:string reply_markup:ReplyMarkup = Ok;
+//@description Edits the time when a scheduled message will be sent. Scheduling state of all messages in the same album or forwarded together with the message will be also changed
+//@chat_id The chat the message belongs to
+//@message_id Identifier of the message
+//@scheduling_state The new message scheduling state; pass null to send the message immediately
+editMessageSchedulingState chat_id:int53 message_id:int53 scheduling_state:MessageSchedulingState = Ok;
+
+
+//@description Returns list of custom emojis, which can be used as forum topic icon by all users
+getForumTopicDefaultIcons = Stickers;
+
+//@description Creates a topic in a forum supergroup chat; requires can_manage_topics rights in the supergroup
+//@chat_id Identifier of the chat
+//@name Name of the topic; 1-128 characters
+//@icon Icon of the topic. Icon color must be one of 0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, or 0xFB6F5F. Telegram Premium users can use any custom emoji as topic icon, other users can use only a custom emoji returned by getForumTopicDefaultIcons
+createForumTopic chat_id:int53 name:string icon:forumTopicIcon = ForumTopicInfo;
+
+//@description Edits title and icon of a topic in a forum supergroup chat; requires can_manage_topics administrator rights in the supergroup unless the user is creator of the topic
+//@chat_id Identifier of the chat
+//@message_thread_id Message thread identifier of the forum topic
+//@name New name of the topic; 1-128 characters
+//@icon_custom_emoji_id Identifier of the new custom emoji for topic icon. Telegram Premium users can use any custom emoji, other users can use only a custom emoji returned by getForumTopicDefaultIcons
+editForumTopic chat_id:int53 message_thread_id:int53 name:string icon_custom_emoji_id:int64 = Ok;
+
+//@description Toggles whether a topic is closed in a forum supergroup chat; requires can_manage_topics administrator rights in the supergroup unless the user is creator of the topic
+//@chat_id Identifier of the chat
+//@message_thread_id Message thread identifier of the forum topic
+//@is_closed Pass true to close the topic; pass false to reopen it
+toggleForumTopicIsClosed chat_id:int53 message_thread_id:int53 is_closed:Bool = Ok;
+
+//@description Deletes all messages in a forum topic; requires can_delete_messages administrator rights in the supergroup unless the user is creator of the topic, the topic has no messages from other users and has at most 11 messages
+//@chat_id Identifier of the chat
+//@message_thread_id Message thread identifier of the forum topic
+deleteForumTopic chat_id:int53 message_thread_id:int53 = Ok;
+
+
+//@description Returns information about a emoji reaction. Returns a 404 error if the reaction is not found @emoji Text representation of the reaction
+getEmojiReaction emoji:string = EmojiReaction;
+
+//@description Returns TGS stickers with generic animations for custom emoji reactions
+getCustomEmojiReactionAnimations = Stickers;
+
+//@description Returns reactions, which can be added to a message. The list can change after updateActiveEmojiReactions, updateChatAvailableReactions for the chat, or updateMessageInteractionInfo for the message
+//@chat_id Identifier of the chat to which the message belongs
+//@message_id Identifier of the message
+//@row_size Number of reaction per row, 5-25
+getMessageAvailableReactions chat_id:int53 message_id:int53 row_size:int32 = AvailableReactions;
+
+//@description Clears the list of recently used reactions
+clearRecentReactions = Ok;
-//@description Returns all entities (mentions, hashtags, cashtags, bot commands, URLs, and email addresses) contained in the text. This is an offline method. Can be called before authorization. Can be called synchronously @text The text in which to look for entites
+//@description Adds a reaction to a message. Use getMessageAvailableReactions to receive the list of available reactions for the message
+//@chat_id Identifier of the chat to which the message belongs
+//@message_id Identifier of the message
+//@reaction_type Type of the reaction to add
+//@is_big Pass true if the reaction is added with a big animation
+//@update_recent_reactions Pass true if the reaction needs to be added to recent reactions
+addMessageReaction chat_id:int53 message_id:int53 reaction_type:ReactionType is_big:Bool update_recent_reactions:Bool = Ok;
+
+//@description Removes a reaction from a message. A chosen reaction can always be removed
+//@chat_id Identifier of the chat to which the message belongs
+//@message_id Identifier of the message
+//@reaction_type Type of the reaction to remove
+removeMessageReaction chat_id:int53 message_id:int53 reaction_type:ReactionType = Ok;
+
+//@description Returns reactions added for a message, along with their sender
+//@chat_id Identifier of the chat to which the message belongs
+//@message_id Identifier of the message
+//@reaction_type Type of the reactions to return; pass null to return all added reactions
+//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results
+//@limit The maximum number of reactions to be returned; must be positive and can't be greater than 100
+getMessageAddedReactions chat_id:int53 message_id:int53 reaction_type:ReactionType offset:string limit:int32 = AddedReactions;
+
+//@description Changes type of default reaction for the current user @reaction_type New type of the default reaction
+setDefaultReactionType reaction_type:ReactionType = Ok;
+
+
+//@description Returns all entities (mentions, hashtags, cashtags, bot commands, bank card numbers, URLs, and email addresses) found in the text. Can be called synchronously @text The text in which to look for entites
getTextEntities text:string = TextEntities;
-//@description Parses Bold, Italic, Code, Pre, PreCode and TextUrl entities contained in the text. This is an offline method. Can be called before authorization. Can be called synchronously @text The text which should be parsed @parse_mode Text parse mode
+//@description Parses Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, Code, Pre, PreCode, TextUrl and MentionName entities from a marked-up text. Can be called synchronously @text The text to parse @parse_mode Text parse mode
parseTextEntities text:string parse_mode:TextParseMode = FormattedText;
-//@description Returns the MIME type of a file, guessed by its extension. Returns an empty string on failure. This is an offline method. Can be called before authorization. Can be called synchronously @file_name The name of the file or path to the file
+//@description Parses Markdown entities in a human-friendly format, ignoring markup errors. Can be called synchronously
+//@text The text to parse. For example, "__italic__ ~~strikethrough~~ ||spoiler|| **bold** `code` ```pre``` __[italic__ text_url](telegram.org) __italic**bold italic__bold**"
+parseMarkdown text:formattedText = FormattedText;
+
+//@description Replaces text entities with Markdown formatting in a human-friendly format. Entities that can't be represented in Markdown unambiguously are kept as is. Can be called synchronously @text The text
+getMarkdownText text:formattedText = FormattedText;
+
+//@description Returns the MIME type of a file, guessed by its extension. Returns an empty string on failure. Can be called synchronously @file_name The name of the file or path to the file
getFileMimeType file_name:string = Text;
-//@description Returns the extension of a file, guessed by its MIME type. Returns an empty string on failure. This is an offline method. Can be called before authorization. Can be called synchronously @mime_type The MIME type of the file
+//@description Returns the extension of a file, guessed by its MIME type. Returns an empty string on failure. Can be called synchronously @mime_type The MIME type of the file
getFileExtension mime_type:string = Text;
+//@description Removes potentially dangerous characters from the name of a file. The encoding of the file name is supposed to be UTF-8. Returns an empty string on failure. Can be called synchronously @file_name File name or path to the file
+cleanFileName file_name:string = Text;
+
+//@description Returns a string stored in the local database from the specified localization target and language pack by its key. Returns a 404 error if the string is not found. Can be called synchronously
+//@language_pack_database_path Path to the language pack database in which strings are stored @localization_target Localization target to which the language pack belongs @language_pack_id Language pack identifier @key Language pack key of the string to be returned
+getLanguagePackString language_pack_database_path:string localization_target:string language_pack_id:string key:string = LanguagePackStringValue;
+
+//@description Converts a JSON-serialized string to corresponding JsonValue object. Can be called synchronously @json The JSON-serialized string
+getJsonValue json:string = JsonValue;
+
+//@description Converts a JsonValue object to corresponding JSON-serialized string. Can be called synchronously @json_value The JsonValue object
+getJsonString json_value:JsonValue = Text;
+
+//@description Converts a themeParameters object to corresponding JSON-serialized string. Can be called synchronously @theme Theme parameters to convert to JSON
+getThemeParametersJsonString theme:themeParameters = Text;
+
+
+//@description Changes the user answer to a poll. A poll in quiz mode can be answered only once
+//@chat_id Identifier of the chat to which the poll belongs
+//@message_id Identifier of the message containing the poll
+//@option_ids 0-based identifiers of answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers
+setPollAnswer chat_id:int53 message_id:int53 option_ids:vector<int32> = Ok;
-//@description Sends an inline query to a bot and returns its results. Returns an error with code 502 if the bot fails to answer the query before the query timeout expires @bot_user_id The identifier of the target bot
-//@chat_id Identifier of the chat, where the query was sent @user_location Location of the user, only if needed @query Text of the query @offset Offset of the first entry to return
-getInlineQueryResults bot_user_id:int32 chat_id:int53 user_location:location query:string offset:string = InlineQueryResults;
+//@description Returns users voted for the specified option in a non-anonymous polls. For optimal performance, the number of returned users is chosen by TDLib
+//@chat_id Identifier of the chat to which the poll belongs
+//@message_id Identifier of the message containing the poll
+//@option_id 0-based identifier of the answer option
+//@offset Number of users to skip in the result; must be non-negative
+//@limit The maximum number of users to be returned; must be positive and can't be greater than 50. For optimal performance, the number of returned users is chosen by TDLib and can be smaller than the specified limit, even if the end of the voter list has not been reached
+getPollVoters chat_id:int53 message_id:int53 option_id:int32 offset:int32 limit:int32 = Users;
-//@description Sets the result of an inline query; for bots only @inline_query_id Identifier of the inline query @is_personal True, if the result of the query can be cached for the specified user
-//@results The results of the query @cache_time Allowed time to cache the results of the query, in seconds @next_offset Offset for the next inline query; pass an empty string if there are no more results
-//@switch_pm_text If non-empty, this text should be shown on the button that opens a private chat with the bot and sends a start message to the bot with the parameter switch_pm_parameter @switch_pm_parameter The parameter for the bot start message
+//@description Stops a poll. A poll in a message can be stopped when the message has can_be_edited flag set
+//@chat_id Identifier of the chat to which the poll belongs
+//@message_id Identifier of the message containing the poll
+//@reply_markup The new message reply markup; pass null if none; for bots only
+stopPoll chat_id:int53 message_id:int53 reply_markup:ReplyMarkup = Ok;
+
+
+//@description Hides a suggested action @action Suggested action to hide
+hideSuggestedAction action:SuggestedAction = Ok;
+
+
+//@description Returns information about a button of type inlineKeyboardButtonTypeLoginUrl. The method needs to be called when the user presses the button
+//@chat_id Chat identifier of the message with the button @message_id Message identifier of the message with the button @button_id Button identifier
+getLoginUrlInfo chat_id:int53 message_id:int53 button_id:int53 = LoginUrlInfo;
+
+//@description Returns an HTTP URL which can be used to automatically authorize the user on a website after clicking an inline button of type inlineKeyboardButtonTypeLoginUrl.
+//-Use the method getLoginUrlInfo to find whether a prior user confirmation is needed. If an error is returned, then the button must be handled as an ordinary URL button
+//@chat_id Chat identifier of the message with the button @message_id Message identifier of the message with the button @button_id Button identifier
+//@allow_write_access Pass true to allow the bot to send messages to the current user
+getLoginUrl chat_id:int53 message_id:int53 button_id:int53 allow_write_access:Bool = HttpUrl;
+
+
+//@description Sends an inline query to a bot and returns its results. Returns an error with code 502 if the bot fails to answer the query before the query timeout expires
+//@bot_user_id The identifier of the target bot
+//@chat_id Identifier of the chat where the query was sent
+//@user_location Location of the user; pass null if unknown or the bot doesn't need user's location
+//@query Text of the query
+//@offset Offset of the first entry to return
+getInlineQueryResults bot_user_id:int53 chat_id:int53 user_location:location query:string offset:string = InlineQueryResults;
+
+//@description Sets the result of an inline query; for bots only
+//@inline_query_id Identifier of the inline query
+//@is_personal Pass true if results may be cached and returned only for the user that sent the query. By default, results may be returned to any user who sends the same query
+//@results The results of the query
+//@cache_time Allowed time to cache the results of the query, in seconds
+//@next_offset Offset for the next inline query; pass an empty string if there are no more results
+//@switch_pm_text If non-empty, this text must be shown on the button that opens a private chat with the bot and sends a start message to the bot with the parameter switch_pm_parameter
+//@switch_pm_parameter The parameter for the bot start message
answerInlineQuery inline_query_id:int64 is_personal:Bool results:vector<InputInlineQueryResult> cache_time:int32 next_offset:string switch_pm_text:string switch_pm_parameter:string = Ok;
+//@description Returns an HTTPS URL of a Web App to open after keyboardButtonTypeWebApp button is pressed
+//@bot_user_id Identifier of the target bot
+//@url The URL from the keyboardButtonTypeWebApp button
+//@theme Preferred Web App theme; pass null to use the default theme
+//@application_name Short name of the application; 0-64 English letters, digits, and underscores
+getWebAppUrl bot_user_id:int53 url:string theme:themeParameters application_name:string = HttpUrl;
+
+//@description Sends data received from a keyboardButtonTypeWebApp Web App to a bot
+//@bot_user_id Identifier of the target bot @button_text Text of the keyboardButtonTypeWebApp button, which opened the Web App @data Received data
+sendWebAppData bot_user_id:int53 button_text:string data:string = Ok;
+
+//@description Informs TDLib that a Web App is being opened from attachment menu, a botMenuButton button, an internalLinkTypeAttachmentMenuBot link, or an inlineKeyboardButtonTypeWebApp button.
+//-For each bot, a confirmation alert about data sent to the bot must be shown once
+//@chat_id Identifier of the chat in which the Web App is opened
+//@bot_user_id Identifier of the bot, providing the Web App
+//@url The URL from an inlineKeyboardButtonTypeWebApp button, a botMenuButton button, or an internalLinkTypeAttachmentMenuBot link, or an empty string otherwise
+//@theme Preferred Web App theme; pass null to use the default theme
+//@application_name Short name of the application; 0-64 English letters, digits, and underscores
+//@message_thread_id If not 0, a message thread identifier in which the message will be sent
+//@reply_to_message_id Identifier of the replied message for the message sent by the Web App; 0 if none
+openWebApp chat_id:int53 bot_user_id:int53 url:string theme:themeParameters application_name:string message_thread_id:int53 reply_to_message_id:int53 = WebAppInfo;
+
+//@description Informs TDLib that a previously opened Web App was closed @web_app_launch_id Identifier of Web App launch, received from openWebApp
+closeWebApp web_app_launch_id:int64 = Ok;
+
+//@description Sets the result of interaction with a Web App and sends corresponding message on behalf of the user to the chat from which the query originated; for bots only
+//@web_app_query_id Identifier of the Web App query
+//@result The result of the query
+answerWebAppQuery web_app_query_id:string result:InputInlineQueryResult = SentWebAppMessage;
+
+
//@description Sends a callback query to a bot and returns an answer. Returns an error with code 502 if the bot fails to answer the query before the query timeout expires @chat_id Identifier of the chat with the message @message_id Identifier of the message from which the query originated @payload Query payload
getCallbackQueryAnswer chat_id:int53 message_id:int53 payload:CallbackQueryPayload = CallbackQueryAnswer;
-//@description Sets the result of a callback query; for bots only @callback_query_id Identifier of the callback query @text Text of the answer @show_alert If true, an alert should be shown to the user instead of a toast notification @url URL to be opened @cache_time Time during which the result of the query can be cached, in seconds
+//@description Sets the result of a callback query; for bots only @callback_query_id Identifier of the callback query @text Text of the answer @show_alert Pass true to show an alert to the user instead of a toast notification @url URL to be opened @cache_time Time during which the result of the query can be cached, in seconds
answerCallbackQuery callback_query_id:int64 text:string show_alert:Bool url:string cache_time:int32 = Ok;
@@ -2301,224 +5498,714 @@ answerShippingQuery shipping_query_id:int64 shipping_options:vector<shippingOpti
answerPreCheckoutQuery pre_checkout_query_id:int64 error_message:string = Ok;
-//@description Updates the game score of the specified user in the game; for bots only @chat_id The chat to which the message with the game @message_id Identifier of the message @edit_message True, if the message should be edited @user_id User identifier @score The new score
+//@description Updates the game score of the specified user in the game; for bots only @chat_id The chat to which the message with the game belongs @message_id Identifier of the message
+//@edit_message Pass true to edit the game message to include the current scoreboard @user_id User identifier @score The new score
//@force Pass true to update the score even if it decreases. If the score is 0, the user will be deleted from the high score table
-setGameScore chat_id:int53 message_id:int53 edit_message:Bool user_id:int32 score:int32 force:Bool = Message;
+setGameScore chat_id:int53 message_id:int53 edit_message:Bool user_id:int53 score:int32 force:Bool = Message;
-//@description Updates the game score of the specified user in a game; for bots only @inline_message_id Inline message identifier @edit_message True, if the message should be edited @user_id User identifier @score The new score
+//@description Updates the game score of the specified user in a game; for bots only @inline_message_id Inline message identifier @edit_message Pass true to edit the game message to include the current scoreboard @user_id User identifier @score The new score
//@force Pass true to update the score even if it decreases. If the score is 0, the user will be deleted from the high score table
-setInlineGameScore inline_message_id:string edit_message:Bool user_id:int32 score:int32 force:Bool = Ok;
+setInlineGameScore inline_message_id:string edit_message:Bool user_id:int53 score:int32 force:Bool = Ok;
//@description Returns the high scores for a game and some part of the high score table in the range of the specified user; for bots only @chat_id The chat that contains the message with the game @message_id Identifier of the message @user_id User identifier
-getGameHighScores chat_id:int53 message_id:int53 user_id:int32 = GameHighScores;
+getGameHighScores chat_id:int53 message_id:int53 user_id:int53 = GameHighScores;
//@description Returns game high scores and some part of the high score table in the range of the specified user; for bots only @inline_message_id Inline message identifier @user_id User identifier
-getInlineGameHighScores inline_message_id:string user_id:int32 = GameHighScores;
+getInlineGameHighScores inline_message_id:string user_id:int53 = GameHighScores;
-//@description Deletes the default reply markup from a chat. Must be called after a one-time keyboard or a ForceReply reply markup has been used. UpdateChatReplyMarkup will be sent if the reply markup will be changed @chat_id Chat identifier
+//@description Deletes the default reply markup from a chat. Must be called after a one-time keyboard or a ForceReply reply markup has been used. UpdateChatReplyMarkup will be sent if the reply markup is changed
+//@chat_id Chat identifier
//@message_id The message identifier of the used keyboard
deleteChatReplyMarkup chat_id:int53 message_id:int53 = Ok;
-//@description Sends a notification about user activity in a chat @chat_id Chat identifier @action The action description
-sendChatAction chat_id:int53 action:ChatAction = Ok;
+//@description Sends a notification about user activity in a chat @chat_id Chat identifier @message_thread_id If not 0, a message thread identifier in which the action was performed @action The action description; pass null to cancel the currently active action
+sendChatAction chat_id:int53 message_thread_id:int53 action:ChatAction = Ok;
-//@description This method should be called if the chat is opened by the user. Many useful activities depend on the chat being opened or closed (e.g., in supergroups and channels all updates are received only for opened chats) @chat_id Chat identifier
+//@description Informs TDLib that the chat is opened by the user. Many useful activities depend on the chat being opened or closed (e.g., in supergroups and channels all updates are received only for opened chats) @chat_id Chat identifier
openChat chat_id:int53 = Ok;
-//@description This method should be called if the chat is closed by the user. Many useful activities depend on the chat being opened or closed @chat_id Chat identifier
+//@description Informs TDLib that the chat is closed by the user. Many useful activities depend on the chat being opened or closed @chat_id Chat identifier
closeChat chat_id:int53 = Ok;
-//@description This method should be called if messages are being viewed by the user. Many useful activities depend on whether the messages are currently being viewed or not (e.g., marking messages as read, incrementing a view counter, updating a view counter, removing deleted messages in supergroups and channels) @chat_id Chat identifier @message_ids The identifiers of the messages being viewed
-//@force_read True, if messages in closed chats should be marked as read
-viewMessages chat_id:int53 message_ids:vector<int53> force_read:Bool = Ok;
+//@description Informs TDLib that messages are being viewed by the user. Sponsored messages must be marked as viewed only when the entire text of the message is shown on the screen (excluding the button). Many useful activities depend on whether the messages are currently being viewed or not (e.g., marking messages as read, incrementing a view counter, updating a view counter, removing deleted messages in supergroups and channels)
+//@chat_id Chat identifier
+//@message_thread_id If not 0, a message thread identifier in which the messages are being viewed
+//@message_ids The identifiers of the messages being viewed
+//@force_read Pass true to mark as read the specified messages even the chat is closed
+viewMessages chat_id:int53 message_thread_id:int53 message_ids:vector<int53> force_read:Bool = Ok;
-//@description This method should be called if the message content has been opened (e.g., the user has opened a photo, video, document, location or venue, or has listened to an audio file or voice note message). An updateMessageContentOpened update will be generated if something has changed @chat_id Chat identifier of the message @message_id Identifier of the message with the opened content
+//@description Informs TDLib that the message content has been opened (e.g., the user has opened a photo, video, document, location or venue, or has listened to an audio file or voice note message). An updateMessageContentOpened update will be generated if something has changed @chat_id Chat identifier of the message @message_id Identifier of the message with the opened content
openMessageContent chat_id:int53 message_id:int53 = Ok;
+//@description Informs TDLib that a message with an animated emoji was clicked by the user. Returns a big animated sticker to be played or a 404 error if usual animation needs to be played @chat_id Chat identifier of the message @message_id Identifier of the clicked message
+clickAnimatedEmojiMessage chat_id:int53 message_id:int53 = Sticker;
+
+//@description Returns information about the type of an internal link. Returns a 404 error if the link is not internal. Can be called before authorization @link The link
+getInternalLinkType link:string = InternalLinkType;
+
+//@description Returns information about an action to be done when the current user clicks an external link. Don't use this method for links from secret chats if web page preview is disabled in secret chats @link The link
+getExternalLinkInfo link:string = LoginUrlInfo;
+
+//@description Returns an HTTP URL which can be used to automatically authorize the current user on a website after clicking an HTTP link. Use the method getExternalLinkInfo to find whether a prior user confirmation is needed
+//@link The HTTP link @allow_write_access Pass true if the current user allowed the bot, returned in getExternalLinkInfo, to send them messages
+getExternalLink link:string allow_write_access:Bool = HttpUrl;
+
//@description Marks all mentions in a chat as read @chat_id Chat identifier
readAllChatMentions chat_id:int53 = Ok;
+//@description Marks all mentions in a forum topic as read @chat_id Chat identifier @message_thread_id Message thread identifier in which mentions are marked as read
+readAllMessageThreadMentions chat_id:int53 message_thread_id:int53 = Ok;
+
+//@description Marks all reactions in a chat or a forum topic as read @chat_id Chat identifier
+readAllChatReactions chat_id:int53 = Ok;
+
+//@description Marks all reactions in a forum topic as read @chat_id Chat identifier @message_thread_id Message thread identifier in which reactions are marked as read
+readAllMessageThreadReactions chat_id:int53 message_thread_id:int53 = Ok;
-//@description Returns an existing chat corresponding to a given user @user_id User identifier @force If true, the chat will be created without network request. In this case all information about the chat except its type, title and photo can be incorrect
-createPrivateChat user_id:int32 force:Bool = Chat;
-//@description Returns an existing chat corresponding to a known basic group @basic_group_id Basic group identifier @force If true, the chat will be created without network request. In this case all information about the chat except its type, title and photo can be incorrect
-createBasicGroupChat basic_group_id:int32 force:Bool = Chat;
+//@description Returns an existing chat corresponding to a given user @user_id User identifier @force Pass true to create the chat without a network request. In this case all information about the chat except its type, title and photo can be incorrect
+createPrivateChat user_id:int53 force:Bool = Chat;
-//@description Returns an existing chat corresponding to a known supergroup or channel @supergroup_id Supergroup or channel identifier @force If true, the chat will be created without network request. In this case all information about the chat except its type, title and photo can be incorrect
-createSupergroupChat supergroup_id:int32 force:Bool = Chat;
+//@description Returns an existing chat corresponding to a known basic group @basic_group_id Basic group identifier @force Pass true to create the chat without a network request. In this case all information about the chat except its type, title and photo can be incorrect
+createBasicGroupChat basic_group_id:int53 force:Bool = Chat;
+
+//@description Returns an existing chat corresponding to a known supergroup or channel @supergroup_id Supergroup or channel identifier @force Pass true to create the chat without a network request. In this case all information about the chat except its type, title and photo can be incorrect
+createSupergroupChat supergroup_id:int53 force:Bool = Chat;
//@description Returns an existing chat corresponding to a known secret chat @secret_chat_id Secret chat identifier
createSecretChat secret_chat_id:int32 = Chat;
-//@description Creates a new basic group and sends a corresponding messageBasicGroupChatCreate. Returns the newly created chat @user_ids Identifiers of users to be added to the basic group @title Title of the new basic group; 1-255 characters
-createNewBasicGroupChat user_ids:vector<int32> title:string = Chat;
+//@description Creates a new basic group and sends a corresponding messageBasicGroupChatCreate. Returns the newly created chat @user_ids Identifiers of users to be added to the basic group @title Title of the new basic group; 1-128 characters
+createNewBasicGroupChat user_ids:vector<int53> title:string = Chat;
-//@description Creates a new supergroup or channel and sends a corresponding messageSupergroupChatCreate. Returns the newly created chat @title Title of the new chat; 1-255 characters @is_channel True, if a channel chat should be created @param_description Chat description; 0-255 characters
-createNewSupergroupChat title:string is_channel:Bool description:string = Chat;
+//@description Creates a new supergroup or channel and sends a corresponding messageSupergroupChatCreate. Returns the newly created chat
+//@title Title of the new chat; 1-128 characters
+//@is_channel Pass true to create a channel chat
+//@param_description Chat description; 0-255 characters
+//@location Chat location if a location-based supergroup is being created; pass null to create an ordinary supergroup chat
+//@for_import Pass true to create a supergroup for importing messages using importMessage
+createNewSupergroupChat title:string is_channel:Bool description:string location:chatLocation for_import:Bool = Chat;
//@description Creates a new secret chat. Returns the newly created chat @user_id Identifier of the target user
-createNewSecretChat user_id:int32 = Chat;
+createNewSecretChat user_id:int53 = Chat;
-//@description Creates a new supergroup from an existing basic group and sends a corresponding messageChatUpgradeTo and messageChatUpgradeFrom. Deactivates the original basic group @chat_id Identifier of the chat to upgrade
+//@description Creates a new supergroup from an existing basic group and sends a corresponding messageChatUpgradeTo and messageChatUpgradeFrom; requires creator privileges. Deactivates the original basic group @chat_id Identifier of the chat to upgrade
upgradeBasicGroupChatToSupergroupChat chat_id:int53 = Chat;
-//@description Changes the chat title. Supported only for basic groups, supergroups and channels. Requires administrator rights in basic groups and the appropriate administrator rights in supergroups and channels. The title will not be changed until the request to the server has been completed
-//@chat_id Chat identifier @title New title of the chat; 1-255 characters
+//@description Returns chat lists to which the chat can be added. This is an offline request @chat_id Chat identifier
+getChatListsToAddChat chat_id:int53 = ChatLists;
+
+//@description Adds a chat to a chat list. A chat can't be simultaneously in Main and Archive chat lists, so it is automatically removed from another one if needed
+//@chat_id Chat identifier @chat_list The chat list. Use getChatListsToAddChat to get suitable chat lists
+addChatToList chat_id:int53 chat_list:ChatList = Ok;
+
+//@description Returns information about a chat filter by its identifier @chat_filter_id Chat filter identifier
+getChatFilter chat_filter_id:int32 = ChatFilter;
+
+//@description Creates new chat filter. Returns information about the created chat filter. There can be up to GetOption("chat_filter_count_max") chat filters, but the limit can be increased with Telegram Premium @filter Chat filter
+createChatFilter filter:chatFilter = ChatFilterInfo;
+
+//@description Edits existing chat filter. Returns information about the edited chat filter @chat_filter_id Chat filter identifier @filter The edited chat filter
+editChatFilter chat_filter_id:int32 filter:chatFilter = ChatFilterInfo;
+
+//@description Deletes existing chat filter @chat_filter_id Chat filter identifier
+deleteChatFilter chat_filter_id:int32 = Ok;
+
+//@description Changes the order of chat filters @chat_filter_ids Identifiers of chat filters in the new correct order @main_chat_list_position Position of the main chat list among chat filters, 0-based. Can be non-zero only for Premium users
+reorderChatFilters chat_filter_ids:vector<int32> main_chat_list_position:int32 = Ok;
+
+//@description Returns recommended chat filters for the current user
+getRecommendedChatFilters = RecommendedChatFilters;
+
+//@description Returns default icon name for a filter. Can be called synchronously @filter Chat filter
+getChatFilterDefaultIconName filter:chatFilter = Text;
+
+
+//@description Changes the chat title. Supported only for basic groups, supergroups and channels. Requires can_change_info administrator right
+//@chat_id Chat identifier @title New title of the chat; 1-128 characters
setChatTitle chat_id:int53 title:string = Ok;
-//@description Changes the photo of a chat. Supported only for basic groups, supergroups and channels. Requires administrator rights in basic groups and the appropriate administrator rights in supergroups and channels. The photo will not be changed before request to the server has been completed
-//@chat_id Chat identifier @photo New chat photo. You can use a zero InputFileId to delete the chat photo. Files that are accessible only by HTTP URL are not acceptable
-setChatPhoto chat_id:int53 photo:InputFile = Ok;
+//@description Changes the photo of a chat. Supported only for basic groups, supergroups and channels. Requires can_change_info administrator right
+//@chat_id Chat identifier @photo New chat photo; pass null to delete the chat photo
+setChatPhoto chat_id:int53 photo:InputChatPhoto = Ok;
+
+//@description Changes the message TTL in a chat. Requires can_delete_messages administrator right in basic groups, supergroups and channels
+//-Message TTL can't be changed in a chat with the current user (Saved Messages) and the chat 777000 (Telegram).
+//@chat_id Chat identifier @ttl New TTL value, in seconds; unless the chat is secret, it must be from 0 up to 365 * 86400 and be divisible by 86400
+setChatMessageTtl chat_id:int53 ttl:int32 = Ok;
+
+//@description Changes the chat members permissions. Supported only for basic groups and supergroups. Requires can_restrict_members administrator right
+//@chat_id Chat identifier @permissions New non-administrator members permissions in the chat
+setChatPermissions chat_id:int53 permissions:chatPermissions = Ok;
+
+//@description Changes the chat theme. Supported only in private and secret chats @chat_id Chat identifier @theme_name Name of the new chat theme; pass an empty string to return the default theme
+setChatTheme chat_id:int53 theme_name:string = Ok;
+
+//@description Changes the draft message in a chat @chat_id Chat identifier @message_thread_id If not 0, a message thread identifier in which the draft was changed @draft_message New draft message; pass null to remove the draft
+setChatDraftMessage chat_id:int53 message_thread_id:int53 draft_message:draftMessage = Ok;
+
+//@description Changes the notification settings of a chat. Notification settings of a chat with the current user (Saved Messages) can't be changed
+//@chat_id Chat identifier @notification_settings New notification settings for the chat. If the chat is muted for more than 366 days, it is considered to be muted forever
+setChatNotificationSettings chat_id:int53 notification_settings:chatNotificationSettings = Ok;
-//@description Changes the draft message in a chat @chat_id Chat identifier @draft_message New draft message; may be null
-setChatDraftMessage chat_id:int53 draft_message:draftMessage = Ok;
+//@description Changes the ability of users to save, forward, or copy chat content. Supported only for basic groups, supergroups and channels. Requires owner privileges
+//@chat_id Chat identifier @has_protected_content New value of has_protected_content
+toggleChatHasProtectedContent chat_id:int53 has_protected_content:Bool = Ok;
-//@description Changes the pinned state of a chat. You can pin up to GetOption("pinned_chat_count_max") non-secret chats and the same number of secret chats @chat_id Chat identifier @is_pinned New value of is_pinned
-toggleChatIsPinned chat_id:int53 is_pinned:Bool = Ok;
+//@description Changes the marked as unread state of a chat @chat_id Chat identifier @is_marked_as_unread New value of is_marked_as_unread
+toggleChatIsMarkedAsUnread chat_id:int53 is_marked_as_unread:Bool = Ok;
-//@description Changes client data associated with a chat @chat_id Chat identifier @client_data New value of client_data
+//@description Changes the value of the default disable_notification parameter, used when a message is sent to a chat @chat_id Chat identifier @default_disable_notification New value of default_disable_notification
+toggleChatDefaultDisableNotification chat_id:int53 default_disable_notification:Bool = Ok;
+
+//@description Changes reactions, available in a chat. Available for basic groups, supergroups, and channels. Requires can_change_info administrator right @chat_id Identifier of the chat @available_reactions Reactions available in the chat. All emoji reactions must be active
+setChatAvailableReactions chat_id:int53 available_reactions:ChatAvailableReactions = Ok;
+
+//@description Changes application-specific data associated with a chat @chat_id Chat identifier @client_data New value of client_data
setChatClientData chat_id:int53 client_data:string = Ok;
-//@description Adds a new member to a chat. Members can't be added to private or secret chats. Members will not be added until the chat state has been synchronized with the server
-//@chat_id Chat identifier @user_id Identifier of the user @forward_limit The number of earlier messages from the chat to be forwarded to the new member; up to 300. Ignored for supergroups and channels
-addChatMember chat_id:int53 user_id:int32 forward_limit:int32 = Ok;
+//@description Changes information about a chat. Available for basic groups, supergroups, and channels. Requires can_change_info administrator right @chat_id Identifier of the chat @param_description New chat description; 0-255 characters
+setChatDescription chat_id:int53 description:string = Ok;
+
+//@description Changes the discussion group of a channel chat; requires can_change_info administrator right in the channel if it is specified @chat_id Identifier of the channel chat. Pass 0 to remove a link from the supergroup passed in the second argument to a linked channel chat (requires can_pin_messages rights in the supergroup) @discussion_chat_id Identifier of a new channel's discussion group. Use 0 to remove the discussion group.
+//-Use the method getSuitableDiscussionChats to find all suitable groups. Basic group chats must be first upgraded to supergroup chats. If new chat members don't have access to old messages in the supergroup, then toggleSupergroupIsAllHistoryAvailable must be used first to change that
+setChatDiscussionGroup chat_id:int53 discussion_chat_id:int53 = Ok;
+
+//@description Changes the location of a chat. Available only for some location-based supergroups, use supergroupFullInfo.can_set_location to check whether the method is allowed to use @chat_id Chat identifier @location New location for the chat; must be valid and not null
+setChatLocation chat_id:int53 location:chatLocation = Ok;
+
+//@description Changes the slow mode delay of a chat. Available only for supergroups; requires can_restrict_members rights @chat_id Chat identifier @slow_mode_delay New slow mode delay for the chat, in seconds; must be one of 0, 10, 30, 60, 300, 900, 3600
+setChatSlowModeDelay chat_id:int53 slow_mode_delay:int32 = Ok;
+
+//@description Pins a message in a chat; requires can_pin_messages rights or can_edit_messages rights in the channel
+//@chat_id Identifier of the chat
+//@message_id Identifier of the new pinned message
+//@disable_notification Pass true to disable notification about the pinned message. Notifications are always disabled in channels and private chats
+//@only_for_self Pass true to pin the message only for self; private chats only
+pinChatMessage chat_id:int53 message_id:int53 disable_notification:Bool only_for_self:Bool = Ok;
+
+//@description Removes a pinned message from a chat; requires can_pin_messages rights in the group or can_edit_messages rights in the channel @chat_id Identifier of the chat @message_id Identifier of the removed pinned message
+unpinChatMessage chat_id:int53 message_id:int53 = Ok;
+
+//@description Removes all pinned messages from a chat; requires can_pin_messages rights in the group or can_edit_messages rights in the channel @chat_id Identifier of the chat
+unpinAllChatMessages chat_id:int53 = Ok;
+
+//@description Removes all pinned messages from a forum topic; requires can_pin_messages rights in the supergroup @chat_id Identifier of the chat
+//@message_thread_id Message thread identifier in which messages will be unpinned
+unpinAllMessageThreadMessages chat_id:int53 message_thread_id:int53 = Ok;
+
+
+//@description Adds the current user as a new member to a chat. Private and secret chats can't be joined using this method. May return an error with a message "INVITE_REQUEST_SENT" if only a join request was created @chat_id Chat identifier
+joinChat chat_id:int53 = Ok;
+
+//@description Removes the current user from chat members. Private and secret chats can't be left using this method @chat_id Chat identifier
+leaveChat chat_id:int53 = Ok;
+
+//@description Adds a new member to a chat. Members can't be added to private or secret chats
+//@chat_id Chat identifier @user_id Identifier of the user @forward_limit The number of earlier messages from the chat to be forwarded to the new member; up to 100. Ignored for supergroups and channels, or if the added user is a bot
+addChatMember chat_id:int53 user_id:int53 forward_limit:int32 = Ok;
+
+//@description Adds multiple new members to a chat. Currently, this method is only available for supergroups and channels. This method can't be used to join a chat. Members can't be added to a channel if it has more than 200 members
+//@chat_id Chat identifier @user_ids Identifiers of the users to be added to the chat. The maximum number of added users is 20 for supergroups and 100 for channels
+addChatMembers chat_id:int53 user_ids:vector<int53> = Ok;
+
+//@description Changes the status of a chat member, needs appropriate privileges. This function is currently not suitable for transferring chat ownership; use transferChatOwnership instead. Use addChatMember or banChatMember if some additional parameters needs to be passed
+//@chat_id Chat identifier @member_id Member identifier. Chats can be only banned and unbanned in supergroups and channels @status The new status of the member in the chat
+setChatMemberStatus chat_id:int53 member_id:MessageSender status:ChatMemberStatus = Ok;
+
+//@description Bans a member in a chat. Members can't be banned in private or secret chats. In supergroups and channels, the user will not be able to return to the group on their own using invite links, etc., unless unbanned first
+//@chat_id Chat identifier
+//@member_id Member identifier
+//@banned_until_date Point in time (Unix timestamp) when the user will be unbanned; 0 if never. If the user is banned for more than 366 days or for less than 30 seconds from the current time, the user is considered to be banned forever. Ignored in basic groups and if a chat is banned
+//@revoke_messages Pass true to delete all messages in the chat for the user that is being removed. Always true for supergroups and channels
+banChatMember chat_id:int53 member_id:MessageSender banned_until_date:int32 revoke_messages:Bool = Ok;
+
+//@description Checks whether the current session can be used to transfer a chat ownership to another user
+canTransferOwnership = CanTransferOwnershipResult;
+
+//@description Changes the owner of a chat. The current user must be a current owner of the chat. Use the method canTransferOwnership to check whether the ownership can be transferred from the current session. Available only for supergroups and channel chats
+//@chat_id Chat identifier @user_id Identifier of the user to which transfer the ownership. The ownership can't be transferred to a bot or to a deleted user @password The 2-step verification password of the current user
+transferChatOwnership chat_id:int53 user_id:int53 password:string = Ok;
+
+//@description Returns information about a single member of a chat @chat_id Chat identifier @member_id Member identifier
+getChatMember chat_id:int53 member_id:MessageSender = ChatMember;
+
+//@description Searches for a specified query in the first name, last name and usernames of the members of a specified chat. Requires administrator rights in channels
+//@chat_id Chat identifier
+//@query Query to search for
+//@limit The maximum number of users to be returned; up to 200
+//@filter The type of users to search for; pass null to search among all chat members
+searchChatMembers chat_id:int53 query:string limit:int32 filter:ChatMembersFilter = ChatMembers;
+
+//@description Returns a list of administrators of the chat with their custom titles @chat_id Chat identifier
+getChatAdministrators chat_id:int53 = ChatAdministrators;
+
+
+//@description Clears message drafts in all chats @exclude_secret_chats Pass true to keep local message drafts in secret chats
+clearAllDraftMessages exclude_secret_chats:Bool = Ok;
+
+
+//@description Returns saved notification sound by its identifier. Returns a 404 error if there is no saved notification sound with the specified identifier @notification_sound_id Identifier of the notification sound
+getSavedNotificationSound notification_sound_id:int64 = NotificationSounds;
+
+//@description Returns list of saved notification sounds. If a sound isn't in the list, then default sound needs to be used
+getSavedNotificationSounds = NotificationSounds;
+
+//@description Adds a new notification sound to the list of saved notification sounds. The new notification sound is added to the top of the list. If it is already in the list, its position isn't changed @sound Notification sound file to add
+addSavedNotificationSound sound:InputFile = NotificationSound;
+
+//@description Removes a notification sound from the list of saved notification sounds @notification_sound_id Identifier of the notification sound
+removeSavedNotificationSound notification_sound_id:int64 = Ok;
+
+
+//@description Returns list of chats with non-default notification settings
+//@scope If specified, only chats from the scope will be returned; pass null to return chats from all scopes
+//@compare_sound Pass true to include in the response chats with only non-default sound
+getChatNotificationSettingsExceptions scope:NotificationSettingsScope compare_sound:Bool = Chats;
+
+//@description Returns the notification settings for chats of a given type @scope Types of chats for which to return the notification settings information
+getScopeNotificationSettings scope:NotificationSettingsScope = ScopeNotificationSettings;
+
+//@description Changes notification settings for chats of a given type @scope Types of chats for which to change the notification settings @notification_settings The new notification settings for the given scope
+setScopeNotificationSettings scope:NotificationSettingsScope notification_settings:scopeNotificationSettings = Ok;
+
+//@description Resets all notification settings to their default values. By default, all chats are unmuted and message previews are shown
+resetAllNotificationSettings = Ok;
+
+
+//@description Changes the pinned state of a chat. There can be up to GetOption("pinned_chat_count_max")/GetOption("pinned_archived_chat_count_max") pinned non-secret chats and the same number of secret chats in the main/archive chat list. The limit can be increased with Telegram Premium
+//@chat_list Chat list in which to change the pinned state of the chat @chat_id Chat identifier @is_pinned Pass true to pin the chat; pass false to unpin it
+toggleChatIsPinned chat_list:ChatList chat_id:int53 is_pinned:Bool = Ok;
-//@description Adds multiple new members to a chat. Currently this option is only available for supergroups and channels. This option can't be used to join a chat. Members can't be added to a channel if it has more than 200 members. Members will not be added until the chat state has been synchronized with the server
-//@chat_id Chat identifier @user_ids Identifiers of the users to be added to the chat
-addChatMembers chat_id:int53 user_ids:vector<int32> = Ok;
+//@description Changes the order of pinned chats @chat_list Chat list in which to change the order of pinned chats @chat_ids The new list of pinned chats
+setPinnedChats chat_list:ChatList chat_ids:vector<int53> = Ok;
-//@description Changes the status of a chat member, needs appropriate privileges. This function is currently not suitable for adding new members to the chat; instead, use addChatMember. The chat member status will not be changed until it has been synchronized with the server
-//@chat_id Chat identifier @user_id User identifier @status The new status of the member in the chat
-setChatMemberStatus chat_id:int53 user_id:int32 status:ChatMemberStatus = Ok;
-//@description Returns information about a single member of a chat @chat_id Chat identifier @user_id User identifier
-getChatMember chat_id:int53 user_id:int32 = ChatMember;
+//@description Returns information about a bot that can be added to attachment menu @bot_user_id Bot's user identifier
+getAttachmentMenuBot bot_user_id:int53 = AttachmentMenuBot;
-//@description Searches for a specified query in the first name, last name and username of the members of a specified chat. Requires administrator rights in channels @chat_id Chat identifier @query Query to search for @limit The maximum number of users to be returned
-searchChatMembers chat_id:int53 query:string limit:int32 = ChatMembers;
+//@description Adds or removes a bot to attachment menu. Bot can be added to attachment menu, only if userTypeBot.can_be_added_to_attachment_menu == true @bot_user_id Bot's user identifier @is_added Pass true to add the bot to attachment menu; pass false to remove the bot from attachment menu
+toggleBotIsAddedToAttachmentMenu bot_user_id:int53 is_added:Bool = Ok;
-//@description Returns a list of users who are administrators of the chat @chat_id Chat identifier
-getChatAdministrators chat_id:int53 = Users;
+//@description Returns up to 8 themed emoji statuses, which color must be changed to the color of the Telegram Premium badge
+getThemedEmojiStatuses = EmojiStatuses;
-//@description Changes the order of pinned chats @chat_ids The new list of pinned chats
-setPinnedChats chat_ids:vector<int53> = Ok;
+//@description Returns recent emoji statuses
+getRecentEmojiStatuses = EmojiStatuses;
+//@description Returns default emoji statuses
+getDefaultEmojiStatuses = EmojiStatuses;
-//@description Asynchronously downloads a file from the cloud. updateFile will be used to notify about the download progress and successful completion of the download. Returns file state just after the download has been started
+//@description Clears the list of recently used emoji statuses
+clearRecentEmojiStatuses = Ok;
+
+
+//@description Downloads a file from the cloud. Download progress and completion of the download will be notified through updateFile updates
//@file_id Identifier of the file to download
-//@priority Priority of the download (1-32). The higher the priority, the earlier the file will be downloaded. If the priorities of two files are equal, then the last one for which downloadFile was called will be downloaded first
-downloadFile file_id:int32 priority:int32 = File;
+//@priority Priority of the download (1-32). The higher the priority, the earlier the file will be downloaded. If the priorities of two files are equal, then the last one for which downloadFile/addFileToDownloads was called will be downloaded first
+//@offset The starting position from which the file needs to be downloaded
+//@limit Number of bytes which need to be downloaded starting from the "offset" position before the download will automatically be canceled; use 0 to download without a limit
+//@synchronous Pass true to return response only after the file download has succeeded, has failed, has been canceled, or a new downloadFile request with different offset/limit parameters was sent; pass false to return file state immediately, just after the download has been started
+downloadFile file_id:int32 priority:int32 offset:int53 limit:int53 synchronous:Bool = File;
+
+//@description Returns file downloaded prefix size from a given offset, in bytes @file_id Identifier of the file @offset Offset from which downloaded prefix size needs to be calculated
+getFileDownloadedPrefixSize file_id:int32 offset:int53 = FileDownloadedPrefixSize;
//@description Stops the downloading of a file. If a file has already been downloaded, does nothing @file_id Identifier of a file to stop downloading @only_if_pending Pass true to stop downloading only if it hasn't been started, i.e. request hasn't been sent to server
cancelDownloadFile file_id:int32 only_if_pending:Bool = Ok;
-//@description Asynchronously uploads a file to the cloud without sending it in a message. updateFile will be used to notify about upload progress and successful completion of the upload. The file will not have a persistent remote identifier until it will be sent in a message @file File to upload @file_type File type
-//@priority Priority of the upload (1-32). The higher the priority, the earlier the file will be uploaded. If the priorities of two files are equal, then the first one for which uploadFile was called will be uploaded first
-uploadFile file:InputFile file_type:FileType priority:int32 = File;
+//@description Returns suggested name for saving a file in a given directory @file_id Identifier of the file @directory Directory in which the file is supposed to be saved
+getSuggestedFileName file_id:int32 directory:string = Text;
+
+//@description Preliminary uploads a file to the cloud before sending it in a message, which can be useful for uploading of being recorded voice and video notes. Updates updateFile will be used to notify about upload progress and successful completion of the upload. The file will not have a persistent remote identifier until it will be sent in a message
+//@file File to upload
+//@file_type File type; pass null if unknown
+//@priority Priority of the upload (1-32). The higher the priority, the earlier the file will be uploaded. If the priorities of two files are equal, then the first one for which preliminaryUploadFile was called will be uploaded first
+preliminaryUploadFile file:InputFile file_type:FileType priority:int32 = File;
+
+//@description Stops the preliminary uploading of a file. Supported only for files uploaded by using preliminaryUploadFile. For other files the behavior is undefined @file_id Identifier of the file to stop uploading
+cancelPreliminaryUploadFile file_id:int32 = Ok;
-//@description Stops the uploading of a file. Supported only for files uploaded by using uploadFile. For other files the behavior is undefined @file_id Identifier of the file to stop uploading
-cancelUploadFile file_id:int32 = Ok;
+//@description Writes a part of a generated file. This method is intended to be used only if the application has no direct access to TDLib's file system, because it is usually slower than a direct write to the destination file
+//@generation_id The identifier of the generation process @offset The offset from which to write the data to the file @data The data to write
+writeGeneratedFilePart generation_id:int64 offset:int53 data:bytes = Ok;
-//@description The next part of a file was generated
+//@description Informs TDLib on a file generation progress
//@generation_id The identifier of the generation process
//@expected_size Expected size of the generated file, in bytes; 0 if unknown
//@local_prefix_size The number of bytes already generated
-setFileGenerationProgress generation_id:int64 expected_size:int32 local_prefix_size:int32 = Ok;
+setFileGenerationProgress generation_id:int64 expected_size:int53 local_prefix_size:int53 = Ok;
//@description Finishes the file generation
//@generation_id The identifier of the generation process
-//@error If set, means that file generation has failed and should be terminated
+//@error If passed, the file generation has failed and must be terminated; pass null if the file generation succeeded
finishFileGeneration generation_id:int64 error:error = Ok;
+//@description Reads a part of a file from the TDLib file cache and returns read bytes. This method is intended to be used only if the application has no direct access to TDLib's file system, because it is usually slower than a direct read from the file
+//@file_id Identifier of the file. The file must be located in the TDLib file cache
+//@offset The offset from which to read the file
+//@count Number of bytes to read. An error will be returned if there are not enough bytes available in the file from the specified position. Pass 0 to read all available data from the specified position
+readFilePart file_id:int32 offset:int53 count:int53 = FilePart;
+
//@description Deletes a file from the TDLib file cache @file_id Identifier of the file to delete
deleteFile file_id:int32 = Ok;
+//@description Adds a file from a message to the list of file downloads. Download progress and completion of the download will be notified through updateFile updates.
+//-If message database is used, the list of file downloads is persistent across application restarts. The downloading is independent from download using downloadFile, i.e. it continues if downloadFile is canceled or is used to download a part of the file
+//@file_id Identifier of the file to download
+//@chat_id Chat identifier of the message with the file
+//@message_id Message identifier
+//@priority Priority of the download (1-32). The higher the priority, the earlier the file will be downloaded. If the priorities of two files are equal, then the last one for which downloadFile/addFileToDownloads was called will be downloaded first
+addFileToDownloads file_id:int32 chat_id:int53 message_id:int53 priority:int32 = File;
+
+//@description Changes pause state of a file in the file download list
+//@file_id Identifier of the downloaded file
+//@is_paused Pass true if the download is paused
+toggleDownloadIsPaused file_id:int32 is_paused:Bool = Ok;
+
+//@description Changes pause state of all files in the file download list @are_paused Pass true to pause all downloads; pass false to unpause them
+toggleAllDownloadsArePaused are_paused:Bool = Ok;
+
+//@description Removes a file from the file download list @file_id Identifier of the downloaded file @delete_from_cache Pass true to delete the file from the TDLib file cache
+removeFileFromDownloads file_id:int32 delete_from_cache:Bool = Ok;
+
+//@description Removes all files from the file download list
+//@only_active Pass true to remove only active downloads, including paused
+//@only_completed Pass true to remove only completed downloads
+//@delete_from_cache Pass true to delete the file from the TDLib file cache
+removeAllFilesFromDownloads only_active:Bool only_completed:Bool delete_from_cache:Bool = Ok;
+
+//@description Searches for files in the file download list or recently downloaded files from the list
+//@query Query to search for; may be empty to return all downloaded files
+//@only_active Pass true to search only for active downloads, including paused
+//@only_completed Pass true to search only for completed downloads
+//@offset Offset of the first entry to return as received from the previous request; use empty string to get the first chunk of results
+//@limit The maximum number of files to be returned
+searchFileDownloads query:string only_active:Bool only_completed:Bool offset:string limit:int32 = FoundFileDownloads;
+
+
+//@description Returns information about a file with messages exported from another application @message_file_head Beginning of the message file; up to 100 first lines
+getMessageFileType message_file_head:string = MessageFileType;
+
+//@description Returns a confirmation text to be shown to the user before starting message import
+//@chat_id Identifier of a chat to which the messages will be imported. It must be an identifier of a private chat with a mutual contact or an identifier of a supergroup chat with can_change_info administrator right
+getMessageImportConfirmationText chat_id:int53 = Text;
+
+//@description Imports messages exported from another app
+//@chat_id Identifier of a chat to which the messages will be imported. It must be an identifier of a private chat with a mutual contact or an identifier of a supergroup chat with can_change_info administrator right
+//@message_file File with messages to import. Only inputFileLocal and inputFileGenerated are supported. The file must not be previously uploaded
+//@attached_files Files used in the imported messages. Only inputFileLocal and inputFileGenerated are supported. The files must not be previously uploaded
+importMessages chat_id:int53 message_file:InputFile attached_files:vector<InputFile> = Ok;
+
+
+//@description Replaces current primary invite link for a chat with a new primary invite link. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right @chat_id Chat identifier
+replacePrimaryChatInviteLink chat_id:int53 = ChatInviteLink;
+
+//@description Creates a new invite link for a chat. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right in the chat
+//@chat_id Chat identifier
+//@name Invite link name; 0-32 characters
+//@expiration_date Point in time (Unix timestamp) when the link will expire; pass 0 if never
+//@member_limit The maximum number of chat members that can join the chat via the link simultaneously; 0-99999; pass 0 if not limited
+//@creates_join_request Pass true if users joining the chat via the link need to be approved by chat administrators. In this case, member_limit must be 0
+createChatInviteLink chat_id:int53 name:string expiration_date:int32 member_limit:int32 creates_join_request:Bool = ChatInviteLink;
+
+//@description Edits a non-primary invite link for a chat. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links
+//@chat_id Chat identifier
+//@invite_link Invite link to be edited
+//@name Invite link name; 0-32 characters
+//@expiration_date Point in time (Unix timestamp) when the link will expire; pass 0 if never
+//@member_limit The maximum number of chat members that can join the chat via the link simultaneously; 0-99999; pass 0 if not limited
+//@creates_join_request Pass true if users joining the chat via the link need to be approved by chat administrators. In this case, member_limit must be 0
+editChatInviteLink chat_id:int53 invite_link:string name:string expiration_date:int32 member_limit:int32 creates_join_request:Bool = ChatInviteLink;
+
+//@description Returns information about an invite link. Requires administrator privileges and can_invite_users right in the chat to get own links and owner privileges to get other links
+//@chat_id Chat identifier
+//@invite_link Invite link to get
+getChatInviteLink chat_id:int53 invite_link:string = ChatInviteLink;
+
+//@description Returns list of chat administrators with number of their invite links. Requires owner privileges in the chat @chat_id Chat identifier
+getChatInviteLinkCounts chat_id:int53 = ChatInviteLinkCounts;
-//@description Generates a new invite link for a chat; the previously generated link is revoked. Available for basic groups, supergroups, and channels. In basic groups this can be called only by the group's creator; in supergroups and channels this requires appropriate administrator rights @chat_id Chat identifier
-generateChatInviteLink chat_id:int53 = ChatInviteLink;
+//@description Returns invite links for a chat created by specified administrator. Requires administrator privileges and can_invite_users right in the chat to get own links and owner privileges to get other links
+//@chat_id Chat identifier
+//@creator_user_id User identifier of a chat administrator. Must be an identifier of the current user for non-owner
+//@is_revoked Pass true if revoked links needs to be returned instead of active or expired
+//@offset_date Creation date of an invite link starting after which to return invite links; use 0 to get results from the beginning
+//@offset_invite_link Invite link starting after which to return invite links; use empty string to get results from the beginning
+//@limit The maximum number of invite links to return; up to 100
+getChatInviteLinks chat_id:int53 creator_user_id:int53 is_revoked:Bool offset_date:int32 offset_invite_link:string limit:int32 = ChatInviteLinks;
+
+//@description Returns chat members joined a chat via an invite link. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links @chat_id Chat identifier @invite_link Invite link for which to return chat members
+//@offset_member A chat member from which to return next chat members; pass null to get results from the beginning @limit The maximum number of chat members to return; up to 100
+getChatInviteLinkMembers chat_id:int53 invite_link:string offset_member:chatInviteLinkMember limit:int32 = ChatInviteLinkMembers;
+
+//@description Revokes invite link for a chat. Available for basic groups, supergroups, and channels. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links.
+//-If a primary link is revoked, then additionally to the revoked link returns new primary link
+//@chat_id Chat identifier
+//@invite_link Invite link to be revoked
+revokeChatInviteLink chat_id:int53 invite_link:string = ChatInviteLinks;
+
+//@description Deletes revoked chat invite links. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links @chat_id Chat identifier @invite_link Invite link to revoke
+deleteRevokedChatInviteLink chat_id:int53 invite_link:string = Ok;
+
+//@description Deletes all revoked chat invite links created by a given chat administrator. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links
+//@chat_id Chat identifier
+//@creator_user_id User identifier of a chat administrator, which links will be deleted. Must be an identifier of the current user for non-owner
+deleteAllRevokedChatInviteLinks chat_id:int53 creator_user_id:int53 = Ok;
-//@description Checks the validity of an invite link for a chat and returns information about the corresponding chat @invite_link Invite link to be checked; should begin with "https://t.me/joinchat/", "https://telegram.me/joinchat/", or "https://telegram.dog/joinchat/"
+//@description Checks the validity of an invite link for a chat and returns information about the corresponding chat @invite_link Invite link to be checked
checkChatInviteLink invite_link:string = ChatInviteLinkInfo;
-//@description Uses an invite link to add the current user to the chat if possible. The new member will not be added until the chat state has been synchronized with the server
-//@invite_link Invite link to import; should begin with "https://t.me/joinchat/", "https://telegram.me/joinchat/", or "https://telegram.dog/joinchat/"
+//@description Uses an invite link to add the current user to the chat if possible. May return an error with a message "INVITE_REQUEST_SENT" if only a join request was created @invite_link Invite link to use
joinChatByInviteLink invite_link:string = Chat;
+//@description Returns pending join requests in a chat
+//@chat_id Chat identifier
+//@invite_link Invite link for which to return join requests. If empty, all join requests will be returned. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links
+//@query A query to search for in the first names, last names and usernames of the users to return
+//@offset_request A chat join request from which to return next requests; pass null to get results from the beginning
+//@limit The maximum number of requests to join the chat to return
+getChatJoinRequests chat_id:int53 invite_link:string query:string offset_request:chatJoinRequest limit:int32 = ChatJoinRequests;
+
+//@description Handles a pending join request in a chat @chat_id Chat identifier @user_id Identifier of the user that sent the request @approve Pass true to approve the request; pass false to decline it
+processChatJoinRequest chat_id:int53 user_id:int53 approve:Bool = Ok;
+
+//@description Handles all pending join requests for a given link in a chat
+//@chat_id Chat identifier
+//@invite_link Invite link for which to process join requests. If empty, all join requests will be processed. Requires administrator privileges and can_invite_users right in the chat for own links and owner privileges for other links
+//@approve Pass true to approve all requests; pass false to decline them
+processChatJoinRequests chat_id:int53 invite_link:string approve:Bool = Ok;
+
-//@description Creates a new call @user_id Identifier of the user to be called @protocol Description of the call protocols supported by the client
-createCall user_id:int32 protocol:callProtocol = CallId;
+//@description Creates a new call @user_id Identifier of the user to be called @protocol The call protocols supported by the application @is_video Pass true to create a video call
+createCall user_id:int53 protocol:callProtocol is_video:Bool = CallId;
-//@description Accepts an incoming call @call_id Call identifier @protocol Description of the call protocols supported by the client
+//@description Accepts an incoming call @call_id Call identifier @protocol The call protocols supported by the application
acceptCall call_id:int32 protocol:callProtocol = Ok;
-//@description Discards a call @call_id Call identifier @is_disconnected True, if the user was disconnected @duration The call duration, in seconds @connection_id Identifier of the connection used during the call
-discardCall call_id:int32 is_disconnected:Bool duration:int32 connection_id:int64 = Ok;
+//@description Sends call signaling data @call_id Call identifier @data The data
+sendCallSignalingData call_id:int32 data:bytes = Ok;
-//@description Sends a call rating @call_id Call identifier @rating Call rating; 1-5 @comment An optional user comment if the rating is less than 5
-sendCallRating call_id:int32 rating:int32 comment:string = Ok;
+//@description Discards a call @call_id Call identifier @is_disconnected Pass true if the user was disconnected @duration The call duration, in seconds @is_video Pass true if the call was a video call @connection_id Identifier of the connection used during the call
+discardCall call_id:int32 is_disconnected:Bool duration:int32 is_video:Bool connection_id:int64 = Ok;
-//@description Sends debug information for a call @call_id Call identifier @debug_information Debug information in application-specific format
+//@description Sends a call rating @call_id Call identifier @rating Call rating; 1-5 @comment An optional user comment if the rating is less than 5 @problems List of the exact types of problems with the call, specified by the user
+sendCallRating call_id:int32 rating:int32 comment:string problems:vector<CallProblem> = Ok;
+
+//@description Sends debug information for a call to Telegram servers @call_id Call identifier @debug_information Debug information in application-specific format
sendCallDebugInformation call_id:int32 debug_information:string = Ok;
+//@description Sends log file for a call to Telegram servers @call_id Call identifier @log_file Call log file. Only inputFileLocal and inputFileGenerated are supported
+sendCallLog call_id:int32 log_file:InputFile = Ok;
+
+
+//@description Returns list of participant identifiers, on whose behalf a video chat in the chat can be joined @chat_id Chat identifier
+getVideoChatAvailableParticipants chat_id:int53 = MessageSenders;
+
+//@description Changes default participant identifier, on whose behalf a video chat in the chat will be joined @chat_id Chat identifier @default_participant_id Default group call participant identifier to join the video chats
+setVideoChatDefaultParticipant chat_id:int53 default_participant_id:MessageSender = Ok;
+//@description Creates a video chat (a group call bound to a chat). Available only for basic groups, supergroups and channels; requires can_manage_video_chats rights
+//@chat_id Identifier of a chat in which the video chat will be created
+//@title Group call title; if empty, chat title will be used
+//@start_date Point in time (Unix timestamp) when the group call is supposed to be started by an administrator; 0 to start the video chat immediately. The date must be at least 10 seconds and at most 8 days in the future
+//@is_rtmp_stream Pass true to create an RTMP stream instead of an ordinary video chat; requires creator privileges
+createVideoChat chat_id:int53 title:string start_date:int32 is_rtmp_stream:Bool = GroupCallId;
-//@description Adds a user to the blacklist @user_id User identifier
-blockUser user_id:int32 = Ok;
+//@description Returns RTMP URL for streaming to the chat; requires creator privileges @chat_id Chat identifier
+getVideoChatRtmpUrl chat_id:int53 = RtmpUrl;
-//@description Removes a user from the blacklist @user_id User identifier
-unblockUser user_id:int32 = Ok;
+//@description Replaces the current RTMP URL for streaming to the chat; requires creator privileges @chat_id Chat identifier
+replaceVideoChatRtmpUrl chat_id:int53 = RtmpUrl;
-//@description Returns users that were blocked by the current user @offset Number of users to skip in the result; must be non-negative @limit Maximum number of users to return; up to 100
-getBlockedUsers offset:int32 limit:int32 = Users;
+//@description Returns information about a group call @group_call_id Group call identifier
+getGroupCall group_call_id:int32 = GroupCall;
+//@description Starts a scheduled group call @group_call_id Group call identifier
+startScheduledGroupCall group_call_id:int32 = Ok;
-//@description Adds new contacts or edits existing contacts; contacts' user identifiers are ignored @contacts The list of contacts to import or edit
+//@description Toggles whether the current user will receive a notification when the group call will start; scheduled group calls only
+//@group_call_id Group call identifier @enabled_start_notification New value of the enabled_start_notification setting
+toggleGroupCallEnabledStartNotification group_call_id:int32 enabled_start_notification:Bool = Ok;
+
+//@description Joins an active group call. Returns join response payload for tgcalls
+//@group_call_id Group call identifier
+//@participant_id Identifier of a group call participant, which will be used to join the call; pass null to join as self; video chats only
+//@audio_source_id Caller audio channel synchronization source identifier; received from tgcalls
+//@payload Group call join payload; received from tgcalls
+//@is_muted Pass true to join the call with muted microphone
+//@is_my_video_enabled Pass true if the user's video is enabled
+//@invite_hash If non-empty, invite hash to be used to join the group call without being muted by administrators
+joinGroupCall group_call_id:int32 participant_id:MessageSender audio_source_id:int32 payload:string is_muted:Bool is_my_video_enabled:Bool invite_hash:string = Text;
+
+//@description Starts screen sharing in a joined group call. Returns join response payload for tgcalls
+//@group_call_id Group call identifier
+//@audio_source_id Screen sharing audio channel synchronization source identifier; received from tgcalls
+//@payload Group call join payload; received from tgcalls
+startGroupCallScreenSharing group_call_id:int32 audio_source_id:int32 payload:string = Text;
+
+//@description Pauses or unpauses screen sharing in a joined group call @group_call_id Group call identifier @is_paused True if screen sharing is paused
+toggleGroupCallScreenSharingIsPaused group_call_id:int32 is_paused:Bool = Ok;
+
+//@description Ends screen sharing in a joined group call @group_call_id Group call identifier
+endGroupCallScreenSharing group_call_id:int32 = Ok;
+
+//@description Sets group call title. Requires groupCall.can_be_managed group call flag @group_call_id Group call identifier @title New group call title; 1-64 characters
+setGroupCallTitle group_call_id:int32 title:string = Ok;
+
+//@description Toggles whether new participants of a group call can be unmuted only by administrators of the group call. Requires groupCall.can_toggle_mute_new_participants group call flag
+//@group_call_id Group call identifier @mute_new_participants New value of the mute_new_participants setting
+toggleGroupCallMuteNewParticipants group_call_id:int32 mute_new_participants:Bool = Ok;
+
+//@description Invites users to an active group call. Sends a service message of type messageInviteToGroupCall for video chats
+//@group_call_id Group call identifier @user_ids User identifiers. At most 10 users can be invited simultaneously
+inviteGroupCallParticipants group_call_id:int32 user_ids:vector<int53> = Ok;
+
+//@description Returns invite link to a video chat in a public chat
+//@group_call_id Group call identifier
+//@can_self_unmute Pass true if the invite link needs to contain an invite hash, passing which to joinGroupCall would allow the invited user to unmute themselves. Requires groupCall.can_be_managed group call flag
+getGroupCallInviteLink group_call_id:int32 can_self_unmute:Bool = HttpUrl;
+
+//@description Revokes invite link for a group call. Requires groupCall.can_be_managed group call flag @group_call_id Group call identifier
+revokeGroupCallInviteLink group_call_id:int32 = Ok;
+
+//@description Starts recording of an active group call. Requires groupCall.can_be_managed group call flag @group_call_id Group call identifier @title Group call recording title; 0-64 characters
+//@record_video Pass true to record a video file instead of an audio file @use_portrait_orientation Pass true to use portrait orientation for video instead of landscape one
+startGroupCallRecording group_call_id:int32 title:string record_video:Bool use_portrait_orientation:Bool = Ok;
+
+//@description Ends recording of an active group call. Requires groupCall.can_be_managed group call flag @group_call_id Group call identifier
+endGroupCallRecording group_call_id:int32 = Ok;
+
+//@description Toggles whether current user's video is paused @group_call_id Group call identifier @is_my_video_paused Pass true if the current user's video is paused
+toggleGroupCallIsMyVideoPaused group_call_id:int32 is_my_video_paused:Bool = Ok;
+
+//@description Toggles whether current user's video is enabled @group_call_id Group call identifier @is_my_video_enabled Pass true if the current user's video is enabled
+toggleGroupCallIsMyVideoEnabled group_call_id:int32 is_my_video_enabled:Bool = Ok;
+
+//@description Informs TDLib that speaking state of a participant of an active group has changed @group_call_id Group call identifier
+//@audio_source Group call participant's synchronization audio source identifier, or 0 for the current user @is_speaking Pass true if the user is speaking
+setGroupCallParticipantIsSpeaking group_call_id:int32 audio_source:int32 is_speaking:Bool = Ok;
+
+//@description Toggles whether a participant of an active group call is muted, unmuted, or allowed to unmute themselves
+//@group_call_id Group call identifier @participant_id Participant identifier @is_muted Pass true to mute the user; pass false to unmute the them
+toggleGroupCallParticipantIsMuted group_call_id:int32 participant_id:MessageSender is_muted:Bool = Ok;
+
+//@description Changes volume level of a participant of an active group call. If the current user can manage the group call, then the participant's volume level will be changed for all users with the default volume level
+//@group_call_id Group call identifier @participant_id Participant identifier @volume_level New participant's volume level; 1-20000 in hundreds of percents
+setGroupCallParticipantVolumeLevel group_call_id:int32 participant_id:MessageSender volume_level:int32 = Ok;
+
+//@description Toggles whether a group call participant hand is rased
+//@group_call_id Group call identifier @participant_id Participant identifier
+//@is_hand_raised Pass true if the user's hand needs to be raised. Only self hand can be raised. Requires groupCall.can_be_managed group call flag to lower other's hand
+toggleGroupCallParticipantIsHandRaised group_call_id:int32 participant_id:MessageSender is_hand_raised:Bool = Ok;
+
+//@description Loads more participants of a group call. The loaded participants will be received through updates. Use the field groupCall.loaded_all_participants to check whether all participants have already been loaded
+//@group_call_id Group call identifier. The group call must be previously received through getGroupCall and must be joined or being joined
+//@limit The maximum number of participants to load; up to 100
+loadGroupCallParticipants group_call_id:int32 limit:int32 = Ok;
+
+//@description Leaves a group call @group_call_id Group call identifier
+leaveGroupCall group_call_id:int32 = Ok;
+
+//@description Ends a group call. Requires groupCall.can_be_managed @group_call_id Group call identifier
+endGroupCall group_call_id:int32 = Ok;
+
+//@description Returns information about available group call streams @group_call_id Group call identifier
+getGroupCallStreams group_call_id:int32 = GroupCallStreams;
+
+//@description Returns a file with a segment of a group call stream in a modified OGG format for audio or MPEG-4 format for video
+//@group_call_id Group call identifier
+//@time_offset Point in time when the stream segment begins; Unix timestamp in milliseconds
+//@scale Segment duration scale; 0-1. Segment's duration is 1000/(2**scale) milliseconds
+//@channel_id Identifier of an audio/video channel to get as received from tgcalls
+//@video_quality Video quality as received from tgcalls; pass null to get the worst available quality
+getGroupCallStreamSegment group_call_id:int32 time_offset:int53 scale:int32 channel_id:int32 video_quality:GroupCallVideoQuality = FilePart;
+
+
+//@description Changes the block state of a message sender. Currently, only users and supergroup chats can be blocked @sender_id Identifier of a message sender to block/unblock @is_blocked New value of is_blocked
+toggleMessageSenderIsBlocked sender_id:MessageSender is_blocked:Bool = Ok;
+
+//@description Blocks an original sender of a message in the Replies chat
+//@message_id The identifier of an incoming message in the Replies chat
+//@delete_message Pass true to delete the message
+//@delete_all_messages Pass true to delete all messages from the same sender
+//@report_spam Pass true to report the sender to the Telegram moderators
+blockMessageSenderFromReplies message_id:int53 delete_message:Bool delete_all_messages:Bool report_spam:Bool = Ok;
+
+//@description Returns users and chats that were blocked by the current user @offset Number of users and chats to skip in the result; must be non-negative @limit The maximum number of users and chats to return; up to 100
+getBlockedMessageSenders offset:int32 limit:int32 = MessageSenders;
+
+
+//@description Adds a user to the contact list or edits an existing contact by their user identifier @contact The contact to add or edit; phone number may be empty and needs to be specified only if known, vCard is ignored
+//@share_phone_number Pass true to share the current user's phone number with the new contact. A corresponding rule to userPrivacySettingShowPhoneNumber will be added if needed. Use the field userFullInfo.need_phone_number_privacy_exception to check whether the current user needs to be asked to share their phone number
+addContact contact:contact share_phone_number:Bool = Ok;
+
+//@description Adds new contacts or edits existing contacts by their phone numbers; contacts' user identifiers are ignored @contacts The list of contacts to import or edit; contacts' vCard are ignored and are not imported
importContacts contacts:vector<contact> = ImportedContacts;
-//@description Searches for the specified query in the first names, last names and usernames of the known user contacts @query Query to search for; can be empty to return all contacts @limit Maximum number of users to be returned
+//@description Returns all user contacts
+getContacts = Users;
+
+//@description Searches for the specified query in the first names, last names and usernames of the known user contacts @query Query to search for; may be empty to return all contacts @limit The maximum number of users to be returned
searchContacts query:string limit:int32 = Users;
-//@description Removes users from the contacts list @user_ids Identifiers of users to be deleted
-removeContacts user_ids:vector<int32> = Ok;
+//@description Removes users from the contact list @user_ids Identifiers of users to be deleted
+removeContacts user_ids:vector<int53> = Ok;
//@description Returns the total number of imported contacts
getImportedContactCount = Count;
-//@description Changes imported contacts using the list of current user contacts saved on the device. Imports newly added contacts and, if at least the file database is enabled, deletes recently deleted contacts.
-//-Query result depends on the result of the previous query, so only one query is possible at the same time @contacts The new list of contacts
+//@description Changes imported contacts using the list of contacts saved on the device. Imports newly added contacts and, if at least the file database is enabled, deletes recently deleted contacts.
+//-Query result depends on the result of the previous query, so only one query is possible at the same time @contacts The new list of contacts, contact's vCard are ignored and are not imported
changeImportedContacts contacts:vector<contact> = ImportedContacts;
-//@description Clears all imported contacts
+//@description Clears all imported contacts, contact list remains unchanged
clearImportedContacts = Ok;
-//@description Returns the profile photos of a user. The result of this query may be outdated: some photos might have been deleted already @user_id User identifier @offset The number of photos to skip; must be non-negative @limit Maximum number of photos to be returned; up to 100
-getUserProfilePhotos user_id:int32 offset:int32 limit:int32 = UserProfilePhotos;
+//@description Searches a user by their phone number. Returns a 404 error if the user can't be found @phone_number Phone number to search for
+searchUserByPhoneNumber phone_number:string = User;
+//@description Shares the phone number of the current user with a mutual contact. Supposed to be called when the user clicks on chatActionBarSharePhoneNumber @user_id Identifier of the user with whom to share the phone number. The user must be a mutual contact
+sharePhoneNumber user_id:int53 = Ok;
-//@description Returns stickers from the installed sticker sets that correspond to a given emoji. If the emoji is not empty, favorite and recently used stickers may also be returned @emoji String representation of emoji. If empty, returns all known installed stickers @limit Maximum number of stickers to be returned
-getStickers emoji:string limit:int32 = Stickers;
-//@description Searches for stickers from public sticker sets that correspond to a given emoji @emoji String representation of emoji; must be non-empty @limit Maximum number of stickers to be returned
+//@description Returns the profile photos of a user. The result of this query may be outdated: some photos might have been deleted already @user_id User identifier @offset The number of photos to skip; must be non-negative @limit The maximum number of photos to be returned; up to 100
+getUserProfilePhotos user_id:int53 offset:int32 limit:int32 = ChatPhotos;
+
+
+//@description Returns stickers from the installed sticker sets that correspond to a given emoji or can be found by sticker-specific keywords. If the query is non-empty, then favorite, recently used or trending stickers may also be returned
+//@sticker_type Type of the stickers to return
+//@query Search query; an emoji or a keyword prefix. If empty, returns all known installed stickers
+//@limit The maximum number of stickers to be returned
+//@chat_id Chat identifier for which to return stickers. Available custom emoji stickers may be different for different chats
+getStickers sticker_type:StickerType query:string limit:int32 chat_id:int53 = Stickers;
+
+//@description Searches for stickers from public sticker sets that correspond to a given emoji @emoji String representation of emoji; must be non-empty @limit The maximum number of stickers to be returned; 0-100
searchStickers emoji:string limit:int32 = Stickers;
-//@description Returns a list of installed sticker sets @is_masks Pass true to return mask sticker sets; pass false to return ordinary sticker sets
-getInstalledStickerSets is_masks:Bool = StickerSets;
+//@description Returns premium stickers from regular sticker sets @limit The maximum number of stickers to be returned; 0-100
+getPremiumStickers limit:int32 = Stickers;
-//@description Returns a list of archived sticker sets @is_masks Pass true to return mask stickers sets; pass false to return ordinary sticker sets @offset_sticker_set_id Identifier of the sticker set from which to return the result @limit Maximum number of sticker sets to return
-getArchivedStickerSets is_masks:Bool offset_sticker_set_id:int64 limit:int32 = StickerSets;
+//@description Returns a list of installed sticker sets @sticker_type Type of the sticker sets to return
+getInstalledStickerSets sticker_type:StickerType = StickerSets;
-//@description Returns a list of trending sticker sets
-getTrendingStickerSets = StickerSets;
+//@description Returns a list of archived sticker sets @sticker_type Type of the sticker sets to return @offset_sticker_set_id Identifier of the sticker set from which to return the result @limit The maximum number of sticker sets to return; up to 100
+getArchivedStickerSets sticker_type:StickerType offset_sticker_set_id:int64 limit:int32 = StickerSets;
-//@description Returns a list of sticker sets attached to a file. Currently only photos and videos can have attached sticker sets @file_id File identifier
+//@description Returns a list of trending sticker sets. For optimal performance, the number of returned sticker sets is chosen by TDLib
+//@sticker_type Type of the sticker sets to return
+//@offset The offset from which to return the sticker sets; must be non-negative
+//@limit The maximum number of sticker sets to be returned; up to 100. For optimal performance, the number of returned sticker sets is chosen by TDLib and can be smaller than the specified limit, even if the end of the list has not been reached
+getTrendingStickerSets sticker_type:StickerType offset:int32 limit:int32 = TrendingStickerSets;
+
+//@description Returns a list of sticker sets attached to a file. Currently, only photos and videos can have attached sticker sets @file_id File identifier
getAttachedStickerSets file_id:int32 = StickerSets;
//@description Returns information about a sticker set by its identifier @set_id Identifier of the sticker set
@@ -2527,8 +6214,8 @@ getStickerSet set_id:int64 = StickerSet;
//@description Searches for a sticker set by its name @name Name of the sticker set
searchStickerSet name:string = StickerSet;
-//@description Searches for installed sticker sets by looking for specified query in their title and name @is_masks Pass true to return mask sticker sets; pass false to return ordinary sticker sets @query Query to search for @limit Maximum number of sticker sets to return
-searchInstalledStickerSets is_masks:Bool query:string limit:int32 = StickerSets;
+//@description Searches for installed sticker sets by looking for specified query in their title and name @sticker_type Type of the sticker sets to search for @query Query to search for @limit The maximum number of sticker sets to return
+searchInstalledStickerSets sticker_type:StickerType query:string limit:int32 = StickerSets;
//@description Searches for ordinary sticker sets by looking for specified query in their title and name. Excludes installed sticker sets from the results @query Query to search for
searchStickerSets query:string = StickerSets;
@@ -2539,13 +6226,13 @@ changeStickerSet set_id:int64 is_installed:Bool is_archived:Bool = Ok;
//@description Informs the server that some trending sticker sets have been viewed by the user @sticker_set_ids Identifiers of viewed trending sticker sets
viewTrendingStickerSets sticker_set_ids:vector<int64> = Ok;
-//@description Changes the order of installed sticker sets @is_masks Pass true to change the order of mask sticker sets; pass false to change the order of ordinary sticker sets @sticker_set_ids Identifiers of installed sticker sets in the new correct order
-reorderInstalledStickerSets is_masks:Bool sticker_set_ids:vector<int64> = Ok;
+//@description Changes the order of installed sticker sets @sticker_type Type of the sticker sets to reorder @sticker_set_ids Identifiers of installed sticker sets in the new correct order
+reorderInstalledStickerSets sticker_type:StickerType sticker_set_ids:vector<int64> = Ok;
//@description Returns a list of recently used stickers @is_attached Pass true to return stickers and masks that were recently attached to photos or video files; pass false to return recently sent stickers
getRecentStickers is_attached:Bool = Stickers;
-//@description Manually adds a new sticker to the list of recently used stickers. The new sticker is added to the top of the list. If the sticker was already in the list, it is removed from the list first. Only stickers belonging to a sticker set can be added to this list
+//@description Manually adds a new sticker to the list of recently used stickers. The new sticker is added to the top of the list. If the sticker was already in the list, it is removed from the list first. Only stickers belonging to a sticker set can be added to this list. Emoji stickers can't be added to recent stickers
//@is_attached Pass true to add the sticker to the list of stickers recently attached to photo or video files; pass false to add the sticker to the list of recently sent stickers @sticker Sticker file to add
addRecentSticker is_attached:Bool sticker:InputFile = Stickers;
@@ -2558,22 +6245,34 @@ clearRecentStickers is_attached:Bool = Ok;
//@description Returns favorite stickers
getFavoriteStickers = Stickers;
-//@description Adds a new sticker to the list of favorite stickers. The new sticker is added to the top of the list. If the sticker was already in the list, it is removed from the list first. Only stickers belonging to a sticker set can be added to this list
+//@description Adds a new sticker to the list of favorite stickers. The new sticker is added to the top of the list. If the sticker was already in the list, it is removed from the list first. Only stickers belonging to a sticker set can be added to this list. Emoji stickers can't be added to favorite stickers
//@sticker Sticker file to add
addFavoriteSticker sticker:InputFile = Ok;
//@description Removes a sticker from the list of favorite stickers @sticker Sticker file to delete from the list
removeFavoriteSticker sticker:InputFile = Ok;
-//@description Returns emoji corresponding to a sticker @sticker Sticker file identifier
-getStickerEmojis sticker:InputFile = StickerEmojis;
+//@description Returns emoji corresponding to a sticker. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object @sticker Sticker file identifier
+getStickerEmojis sticker:InputFile = Emojis;
+
+//@description Searches for emojis by keywords. Supported only if the file database is enabled @text Text to search for @exact_match Pass true if only emojis, which exactly match the text, needs to be returned @input_language_codes List of possible IETF language tags of the user's input language; may be empty if unknown
+searchEmojis text:string exact_match:Bool input_language_codes:vector<string> = Emojis;
+
+//@description Returns an animated emoji corresponding to a given emoji. Returns a 404 error if the emoji has no animated emoji @emoji The emoji
+getAnimatedEmoji emoji:string = AnimatedEmoji;
+
+//@description Returns an HTTP URL which can be used to automatically log in to the translation platform and suggest new emoji replacements. The URL will be valid for 30 seconds after generation @language_code Language code for which the emoji replacements will be suggested
+getEmojiSuggestionsUrl language_code:string = HttpUrl;
+
+//@description Returns list of custom emoji stickers by their identifiers. Stickers are returned in arbitrary order. Only found stickers are returned @custom_emoji_ids Identifiers of custom emoji stickers. At most 200 custom emoji stickers can be received simultaneously
+getCustomEmojiStickers custom_emoji_ids:vector<int64> = Stickers;
//@description Returns saved animations
getSavedAnimations = Animations;
//@description Manually adds a new animation to the list of saved animations. The new animation is added to the beginning of the list. If the animation was already in the list, it is removed first. Only non-secret video animations with MIME type "video/mp4" can be added to the list
-//@animation The animation file to be added. Only animations known to the server (i.e. successfully sent via a message) can be added to the list
+//@animation The animation file to be added. Only animations known to the server (i.e., successfully sent via a message) can be added to the list
addSavedAnimation animation:InputFile = Ok;
//@description Removes an animation from the list of saved animations @animation Animation file to be removed
@@ -2584,7 +6283,7 @@ removeSavedAnimation animation:InputFile = Ok;
getRecentInlineBots = Users;
-//@description Searches for recently used hashtags by their prefix @prefix Hashtag prefix to search for @limit Maximum number of hashtags to be returned
+//@description Searches for recently used hashtags by their prefix @prefix Hashtag prefix to search for @limit The maximum number of hashtags to be returned
searchHashtags prefix:string limit:int32 = Hashtags;
//@description Removes a hashtag from the list of recently used hashtags @hashtag Hashtag to delete
@@ -2594,47 +6293,81 @@ removeRecentHashtag hashtag:string = Ok;
//@description Returns a web page preview by the text of the message. Do not call this function too often. Returns a 404 error if the web page has no preview @text Message text with formatting
getWebPagePreview text:formattedText = WebPage;
-//@description Returns an instant view version of a web page if available. Returns a 404 error if the web page has no instant view page @url The web page URL @force_full If true, the full instant view for the web page will be returned
+//@description Returns an instant view version of a web page if available. Returns a 404 error if the web page has no instant view page @url The web page URL @force_full Pass true to get full instant view for the web page
getWebPageInstantView url:string force_full:Bool = WebPageInstantView;
-//@description Returns the notification settings for a given scope @scope Scope for which to return the notification settings information
-getNotificationSettings scope:NotificationSettingsScope = NotificationSettings;
-
-//@description Changes notification settings for a given scope @scope Scope for which to change the notification settings
-//@notification_settings The new notification settings for the given scope
-setNotificationSettings scope:NotificationSettingsScope notification_settings:notificationSettings = Ok;
+//@description Changes a profile photo for the current user @photo Profile photo to set
+setProfilePhoto photo:InputChatPhoto = Ok;
-//@description Resets all notification settings to their default values. By default, the only muted chats are supergroups, the sound is set to "default" and message previews are shown
-resetAllNotificationSettings = Ok;
-
-
-//@description Uploads a new profile photo for the current user. If something changes, updateUser will be sent @photo Profile photo to set. inputFileId and inputFileRemote may still be unsupported
-setProfilePhoto photo:InputFile = Ok;
-
-//@description Deletes a profile photo. If something changes, updateUser will be sent @profile_photo_id Identifier of the profile photo to delete
+//@description Deletes a profile photo @profile_photo_id Identifier of the profile photo to delete
deleteProfilePhoto profile_photo_id:int64 = Ok;
-//@description Changes the first and last name of the current user. If something changes, updateUser will be sent @first_name The new value of the first name for the user; 1-255 characters @last_name The new value of the optional last name for the user; 0-255 characters
+//@description Changes the first and last name of the current user @first_name The new value of the first name for the current user; 1-64 characters @last_name The new value of the optional last name for the current user; 0-64 characters
setName first_name:string last_name:string = Ok;
-//@description Changes the bio of the current user @bio The new value of the user bio; 0-70 characters without line feeds
+//@description Changes the bio of the current user @bio The new value of the user bio; 0-GetOption("bio_length_max") characters without line feeds
setBio bio:string = Ok;
-//@description Changes the username of the current user. If something changes, updateUser will be sent @username The new value of the username. Use an empty string to remove the username
+//@description Changes the editable username of the current user @username The new value of the username. Use an empty string to remove the username. The username can't be completely removed if there is another active or disabled username
setUsername username:string = Ok;
+//@description Changes active state for a username of the current user. The editable username can't be disabled. May return an error with a message "USERNAMES_ACTIVE_TOO_MUCH" if the maximum number of active usernames has been reached @username The username to change @is_active Pass true to activate the username; pass false to disable it
+toggleUsernameIsActive username:string is_active:Bool = Ok;
+
+//@description Changes order of active usernames of the current user @usernames The new order of active usernames. All currently active usernames must be specified
+reorderActiveUsernames usernames:vector<string> = Ok;
+
+//@description Changes the emoji status of the current user; for Telegram Premium users only
+//@emoji_status New emoji status; pass null to switch to the default badge
+//@duration Duration of the status, in seconds; pass 0 to keep the status active until it will be changed manually
+setEmojiStatus emoji_status:emojiStatus duration:int32 = Ok;
+
+//@description Changes the location of the current user. Needs to be called if GetOption("is_location_visible") is true and location changes for more than 1 kilometer @location The new location of the user
+setLocation location:location = Ok;
+
//@description Changes the phone number of the user and sends an authentication code to the user's new phone number. On success, returns information about the sent code
-//@phone_number The new phone number of the user in international format @allow_flash_call Pass true if the code can be sent via flash call to the specified phone number @is_current_phone_number Pass true if the phone number is used on the current device. Ignored if allow_flash_call is false
-changePhoneNumber phone_number:string allow_flash_call:Bool is_current_phone_number:Bool = AuthenticationCodeInfo;
+//@phone_number The new phone number of the user in international format @settings Settings for the authentication of the user's phone number; pass null to use default settings
+changePhoneNumber phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo;
-//@description Re-sends the authentication code sent to confirm a new phone number for the user. Works only if the previously received authenticationCodeInfo next_code_type was not null
+//@description Resends the authentication code sent to confirm a new phone number for the current user. Works only if the previously received authenticationCodeInfo next_code_type was not null and the server-specified timeout has passed
resendChangePhoneNumberCode = AuthenticationCodeInfo;
-//@description Checks the authentication code sent to confirm a new phone number of the user @code Verification code received by SMS, phone call or flash call
+//@description Checks the authentication code sent to confirm a new phone number of the user @code Authentication code to check
checkChangePhoneNumberCode code:string = Ok;
+//@description Sets the list of commands supported by the bot for the given user scope and language; for bots only
+//@scope The scope to which the commands are relevant; pass null to change commands in the default bot command scope
+//@language_code A two-letter ISO 639-1 language code. If empty, the commands will be applied to all users from the given scope, for which language there are no dedicated commands
+//@commands List of the bot's commands
+setCommands scope:BotCommandScope language_code:string commands:vector<botCommand> = Ok;
+
+//@description Deletes commands supported by the bot for the given user scope and language; for bots only
+//@scope The scope to which the commands are relevant; pass null to delete commands in the default bot command scope
+//@language_code A two-letter ISO 639-1 language code or an empty string
+deleteCommands scope:BotCommandScope language_code:string = Ok;
+
+//@description Returns the list of commands supported by the bot for the given user scope and language; for bots only
+//@scope The scope to which the commands are relevant; pass null to get commands in the default bot command scope
+//@language_code A two-letter ISO 639-1 language code or an empty string
+getCommands scope:BotCommandScope language_code:string = BotCommands;
+
+//@description Sets menu button for the given user or for all users; for bots only
+//@user_id Identifier of the user or 0 to set menu button for all users
+//@menu_button New menu button
+setMenuButton user_id:int53 menu_button:botMenuButton = Ok;
+
+//@description Returns menu button set by the bot for the given user; for bots only @user_id Identifier of the user or 0 to get the default menu button
+getMenuButton user_id:int53 = BotMenuButton;
+
+//@description Sets default administrator rights for adding the bot to basic group and supergroup chats; for bots only @default_group_administrator_rights Default administrator rights for adding the bot to basic group and supergroup chats; may be null
+setDefaultGroupAdministratorRights default_group_administrator_rights:chatAdministratorRights = Ok;
+
+//@description Sets default administrator rights for adding the bot to channel chats; for bots only @default_channel_administrator_rights Default administrator rights for adding the bot to channels; may be null
+setDefaultChannelAdministratorRights default_channel_administrator_rights:chatAdministratorRights = Ok;
+
+
//@description Returns all active sessions of the current user
getActiveSessions = Sessions;
@@ -2644,6 +6377,15 @@ terminateSession session_id:int64 = Ok;
//@description Terminates all other sessions of the current user
terminateAllOtherSessions = Ok;
+//@description Toggles whether a session can accept incoming calls @session_id Session identifier @can_accept_calls Pass true to allow accepting incoming calls by the session; pass false otherwise
+toggleSessionCanAcceptCalls session_id:int64 can_accept_calls:Bool = Ok;
+
+//@description Toggles whether a session can accept incoming secret chats @session_id Session identifier @can_accept_secret_chats Pass true to allow accepring secret chats by the session; pass false otherwise
+toggleSessionCanAcceptSecretChats session_id:int64 can_accept_secret_chats:Bool = Ok;
+
+//@description Changes the period of inactivity after which sessions will automatically be terminated @inactive_session_ttl_days New number of days of inactivity before sessions will be automatically terminated; 1-366 days
+setInactiveSessionTtl inactive_session_ttl_days:int32 = Ok;
+
//@description Returns all website where the current user used Telegram to log in
getConnectedWebsites = ConnectedWebsites;
@@ -2655,87 +6397,153 @@ disconnectWebsite website_id:int64 = Ok;
disconnectAllWebsites = Ok;
-//@description Toggles the "All members are admins" setting in basic groups; requires creator privileges in the group @basic_group_id Identifier of the basic group @everyone_is_administrator New value of everyone_is_administrator
-toggleBasicGroupAdministrators basic_group_id:int32 everyone_is_administrator:Bool = Ok;
+//@description Changes the editable username of a supergroup or channel, requires owner privileges in the supergroup or channel @supergroup_id Identifier of the supergroup or channel @username New value of the username. Use an empty string to remove the username. The username can't be completely removed if there is another active or disabled username
+setSupergroupUsername supergroup_id:int53 username:string = Ok;
+//@description Changes active state for a username of a supergroup or channel, requires owner privileges in the supergroup or channel. The editable username can't be disabled. May return an error with a message "USERNAMES_ACTIVE_TOO_MUCH" if the maximum number of active usernames has been reached @supergroup_id Identifier of the supergroup or channel @username The username to change @is_active Pass true to activate the username; pass false to disable it
+toggleSupergroupUsernameIsActive supergroup_id:int53 username:string is_active:Bool = Ok;
-//@description Changes the username of a supergroup or channel, requires creator privileges in the supergroup or channel @supergroup_id Identifier of the supergroup or channel @username New value of the username. Use an empty string to remove the username
-setSupergroupUsername supergroup_id:int32 username:string = Ok;
+//@description Disables all active non-editable usernames of a supergroup or channel, requires owner privileges in the supergroup or channel @supergroup_id Identifier of the supergroup or channel
+disableAllSupergroupUsernames supergroup_id:int53 = Ok;
-//@description Changes the sticker set of a supergroup; requires appropriate rights in the supergroup @supergroup_id Identifier of the supergroup @sticker_set_id New value of the supergroup sticker set identifier. Use 0 to remove the supergroup sticker set
-setSupergroupStickerSet supergroup_id:int32 sticker_set_id:int64 = Ok;
+//@description Changes order of active usernames of a supergroup or channel, requires owner privileges in the supergroup or channel @supergroup_id Identifier of the supergroup or channel @usernames The new order of active usernames. All currently active usernames must be specified
+reorderSupergroupActiveUsernames supergroup_id:int53 usernames:vector<string> = Ok;
-//@description Toggles whether all members of a supergroup can add new members; requires appropriate administrator rights in the supergroup. @supergroup_id Identifier of the supergroup @anyone_can_invite New value of anyone_can_invite
-toggleSupergroupInvites supergroup_id:int32 anyone_can_invite:Bool = Ok;
+//@description Changes the sticker set of a supergroup; requires can_change_info administrator right @supergroup_id Identifier of the supergroup @sticker_set_id New value of the supergroup sticker set identifier. Use 0 to remove the supergroup sticker set
+setSupergroupStickerSet supergroup_id:int53 sticker_set_id:int64 = Ok;
-//@description Toggles sender signatures messages sent in a channel; requires appropriate administrator rights in the channel. @supergroup_id Identifier of the channel @sign_messages New value of sign_messages
-toggleSupergroupSignMessages supergroup_id:int32 sign_messages:Bool = Ok;
+//@description Toggles whether sender signature is added to sent messages in a channel; requires can_change_info administrator right @supergroup_id Identifier of the channel @sign_messages New value of sign_messages
+toggleSupergroupSignMessages supergroup_id:int53 sign_messages:Bool = Ok;
-//@description Toggles whether the message history of a supergroup is available to new members; requires appropriate administrator rights in the supergroup. @supergroup_id The identifier of the supergroup @is_all_history_available The new value of is_all_history_available
-toggleSupergroupIsAllHistoryAvailable supergroup_id:int32 is_all_history_available:Bool = Ok;
+//@description Toggles whether joining is mandatory to send messages to a discussion supergroup; requires can_restrict_members administrator right @supergroup_id Identifier of the supergroup @join_to_send_messages New value of join_to_send_messages
+toggleSupergroupJoinToSendMessages supergroup_id:int53 join_to_send_messages:Bool = Ok;
-//@description Changes information about a supergroup or channel; requires appropriate administrator rights @supergroup_id Identifier of the supergroup or channel @param_description New supergroup or channel description; 0-255 characters
-setSupergroupDescription supergroup_id:int32 description:string = Ok;
+//@description Toggles whether all users directly joining the supergroup need to be approved by supergroup administrators; requires can_restrict_members administrator right @supergroup_id Identifier of the channel @join_by_request New value of join_by_request
+toggleSupergroupJoinByRequest supergroup_id:int53 join_by_request:Bool = Ok;
-//@description Pins a message in a supergroup or channel; requires appropriate administrator rights in the supergroup or channel @supergroup_id Identifier of the supergroup or channel @message_id Identifier of the new pinned message @disable_notification True, if there should be no notification about the pinned message
-pinSupergroupMessage supergroup_id:int32 message_id:int53 disable_notification:Bool = Ok;
+//@description Toggles whether the message history of a supergroup is available to new members; requires can_change_info administrator right @supergroup_id The identifier of the supergroup @is_all_history_available The new value of is_all_history_available
+toggleSupergroupIsAllHistoryAvailable supergroup_id:int53 is_all_history_available:Bool = Ok;
-//@description Removes the pinned message from a supergroup or channel; requires appropriate administrator rights in the supergroup or channel @supergroup_id Identifier of the supergroup or channel
-unpinSupergroupMessage supergroup_id:int32 = Ok;
+//@description Toggles whether the supergroup is a forum; requires owner privileges in the supergroup @supergroup_id Identifier of the supergroup @is_forum New value of is_forum. A supergroup can be converted to a forum, only if it has at least GetOption("forum_member_count_min") members
+toggleSupergroupIsForum supergroup_id:int53 is_forum:Bool = Ok;
-//@description Reports some messages from a user in a supergroup as spam @supergroup_id Supergroup identifier @user_id User identifier @message_ids Identifiers of messages sent in the supergroup by the user. This list must be non-empty
-reportSupergroupSpam supergroup_id:int32 user_id:int32 message_ids:vector<int53> = Ok;
+//@description Upgrades supergroup to a broadcast group; requires owner privileges in the supergroup @supergroup_id Identifier of the supergroup
+toggleSupergroupIsBroadcastGroup supergroup_id:int53 = Ok;
-//@description Returns information about members or banned users in a supergroup or channel. Can be used only if SupergroupFullInfo.can_get_members == true; additionally, administrator privileges may be required for some filters @supergroup_id Identifier of the supergroup or channel
-//@filter The type of users to return. By default, supergroupMembersRecent @offset Number of users to skip @limit The maximum number of users be returned; up to 200
-getSupergroupMembers supergroup_id:int32 filter:SupergroupMembersFilter offset:int32 limit:int32 = ChatMembers;
+//@description Reports messages in a supergroup as spam; requires administrator rights in the supergroup @supergroup_id Supergroup identifier @message_ids Identifiers of messages to report
+reportSupergroupSpam supergroup_id:int53 message_ids:vector<int53> = Ok;
-//@description Deletes a supergroup or channel along with all messages in the corresponding chat. This will release the supergroup or channel username and remove all members; requires creator privileges in the supergroup or channel. Chats with more than 1000 members can't be deleted using this method @supergroup_id Identifier of the supergroup or channel
-deleteSupergroup supergroup_id:int32 = Ok;
+//@description Returns information about members or banned users in a supergroup or channel. Can be used only if supergroupFullInfo.can_get_members == true; additionally, administrator privileges may be required for some filters @supergroup_id Identifier of the supergroup or channel
+//@filter The type of users to return; pass null to use supergroupMembersFilterRecent @offset Number of users to skip @limit The maximum number of users be returned; up to 200
+getSupergroupMembers supergroup_id:int53 filter:SupergroupMembersFilter offset:int32 limit:int32 = ChatMembers;
-//@description Closes a secret chat, effectively transfering its state to secretChatStateClosed @secret_chat_id Secret chat identifier
+//@description Closes a secret chat, effectively transferring its state to secretChatStateClosed @secret_chat_id Secret chat identifier
closeSecretChat secret_chat_id:int32 = Ok;
-//@description Returns a list of service actions taken by chat members and administrators in the last 48 hours. Available only in supergroups and channels. Requires administrator rights. Returns results in reverse chronological order (i. e., in order of decreasing event_id)
-//@chat_id Chat identifier @query Search query by which to filter events @from_event_id Identifier of an event from which to return results. Use 0 to get results from the latest events @limit Maximum number of events to return; up to 100
-//@filters The types of events to return. By default, all types will be returned @user_ids User identifiers by which to filter events. By default, events relating to all users will be returned
-getChatEventLog chat_id:int53 query:string from_event_id:int64 limit:int32 filters:chatEventLogFilters user_ids:vector<int32> = ChatEvents;
+//@description Returns a list of service actions taken by chat members and administrators in the last 48 hours. Available only for supergroups and channels. Requires administrator rights. Returns results in reverse chronological order (i.e., in order of decreasing event_id)
+//@chat_id Chat identifier @query Search query by which to filter events @from_event_id Identifier of an event from which to return results. Use 0 to get results from the latest events @limit The maximum number of events to return; up to 100
+//@filters The types of events to return; pass null to get chat events of all types @user_ids User identifiers by which to filter events. By default, events relating to all users will be returned
+getChatEventLog chat_id:int53 query:string from_event_id:int64 limit:int32 filters:chatEventLogFilters user_ids:vector<int53> = ChatEvents;
-//@description Returns an invoice payment form. This method should be called when the user presses inlineKeyboardButtonBuy @chat_id Chat identifier of the Invoice message @message_id Message identifier
-getPaymentForm chat_id:int53 message_id:int53 = PaymentForm;
+//@description Returns an invoice payment form. This method must be called when the user presses inlineKeyboardButtonBuy
+//@input_invoice The invoice
+//@theme Preferred payment form theme; pass null to use the default theme
+getPaymentForm input_invoice:InputInvoice theme:themeParameters = PaymentForm;
-//@description Validates the order information provided by a user and returns the available shipping options for a flexible invoice @chat_id Chat identifier of the Invoice message @message_id Message identifier @order_info The order information, provided by the user @allow_save True, if the order information can be saved
-validateOrderInfo chat_id:int53 message_id:int53 order_info:orderInfo allow_save:Bool = ValidatedOrderInfo;
+//@description Validates the order information provided by a user and returns the available shipping options for a flexible invoice
+//@input_invoice The invoice
+//@order_info The order information, provided by the user; pass null if empty
+//@allow_save Pass true to save the order information
+validateOrderInfo input_invoice:InputInvoice order_info:orderInfo allow_save:Bool = ValidatedOrderInfo;
-//@description Sends a filled-out payment form to the bot for final verification @chat_id Chat identifier of the Invoice message @message_id Message identifier @order_info_id Identifier returned by ValidateOrderInfo, or an empty string @shipping_option_id Identifier of a chosen shipping option, if applicable
-//@credentials The credentials chosen by user for payment
-sendPaymentForm chat_id:int53 message_id:int53 order_info_id:string shipping_option_id:string credentials:InputCredentials = PaymentResult;
+//@description Sends a filled-out payment form to the bot for final verification @input_invoice The invoice
+//@payment_form_id Payment form identifier returned by getPaymentForm @order_info_id Identifier returned by validateOrderInfo, or an empty string @shipping_option_id Identifier of a chosen shipping option, if applicable
+//@credentials The credentials chosen by user for payment @tip_amount Chosen by the user amount of tip in the smallest units of the currency
+sendPaymentForm input_invoice:InputInvoice payment_form_id:int64 order_info_id:string shipping_option_id:string credentials:InputCredentials tip_amount:int53 = PaymentResult;
//@description Returns information about a successful payment @chat_id Chat identifier of the PaymentSuccessful message @message_id Message identifier
getPaymentReceipt chat_id:int53 message_id:int53 = PaymentReceipt;
-//@description Returns saved order info, if any
+//@description Returns saved order information. Returns a 404 error if there is no saved order information
getSavedOrderInfo = OrderInfo;
-//@description Deletes saved order info
+//@description Deletes saved order information
deleteSavedOrderInfo = Ok;
//@description Deletes saved credentials for all payment provider bots
deleteSavedCredentials = Ok;
+//@description Creates a link for the given invoice; for bots only @invoice Information about the invoice of the type inputMessageInvoice
+createInvoiceLink invoice:InputMessageContent = HttpUrl;
+
+
//@description Returns a user that can be contacted to get support
getSupportUser = User;
-//@description Returns background wallpapers
-getWallpapers = Wallpapers;
+//@description Returns backgrounds installed by the user @for_dark_theme Pass true to order returned backgrounds for a dark theme
+getBackgrounds for_dark_theme:Bool = Backgrounds;
+
+//@description Constructs a persistent HTTP URL for a background @name Background name @type Background type
+getBackgroundUrl name:string type:BackgroundType = HttpUrl;
+
+//@description Searches for a background by its name @name The name of the background
+searchBackground name:string = Background;
+
+//@description Changes the background selected by the user; adds background to the list of installed backgrounds
+//@background The input background to use; pass null to create a new filled backgrounds or to remove the current background
+//@type Background type; pass null to use the default type of the remote background or to remove the current background
+//@for_dark_theme Pass true if the background is changed for a dark theme
+setBackground background:InputBackground type:BackgroundType for_dark_theme:Bool = Background;
+
+//@description Removes background from the list of installed backgrounds @background_id The background identifier
+removeBackground background_id:int64 = Ok;
+
+//@description Resets list of installed backgrounds to its default value
+resetBackgrounds = Ok;
+
+
+//@description Returns information about the current localization target. This is an offline request if only_local is true. Can be called before authorization @only_local Pass true to get only locally available information without sending network requests
+getLocalizationTargetInfo only_local:Bool = LocalizationTargetInfo;
+
+//@description Returns information about a language pack. Returned language pack identifier may be different from a provided one. Can be called before authorization @language_pack_id Language pack identifier
+getLanguagePackInfo language_pack_id:string = LanguagePackInfo;
+
+//@description Returns strings from a language pack in the current localization target by their keys. Can be called before authorization @language_pack_id Language pack identifier of the strings to be returned @keys Language pack keys of the strings to be returned; leave empty to request all available strings
+getLanguagePackStrings language_pack_id:string keys:vector<string> = LanguagePackStrings;
+
+//@description Fetches the latest versions of all strings from a language pack in the current localization target from the server. This method doesn't need to be called explicitly for the current used/base language packs. Can be called before authorization @language_pack_id Language pack identifier
+synchronizeLanguagePack language_pack_id:string = Ok;
+
+//@description Adds a custom server language pack to the list of installed language packs in current localization target. Can be called before authorization @language_pack_id Identifier of a language pack to be added; may be different from a name that is used in an "https://t.me/setlanguage/" link
+addCustomServerLanguagePack language_pack_id:string = Ok;
+
+//@description Adds or changes a custom local language pack to the current localization target @info Information about the language pack. Language pack ID must start with 'X', consist only of English letters, digits and hyphens, and must not exceed 64 characters. Can be called before authorization @strings Strings of the new language pack
+setCustomLanguagePack info:languagePackInfo strings:vector<languagePackString> = Ok;
+
+//@description Edits information about a custom local language pack in the current localization target. Can be called before authorization @info New information about the custom local language pack
+editCustomLanguagePackInfo info:languagePackInfo = Ok;
+
+//@description Adds, edits or deletes a string in a custom local language pack. Can be called before authorization @language_pack_id Identifier of a previously added custom local language pack in the current localization target @new_string New language pack string
+setCustomLanguagePackString language_pack_id:string new_string:languagePackString = Ok;
+
+//@description Deletes all information about a language pack in the current localization target. The language pack which is currently in use (including base language pack) or is being synchronized can't be deleted. Can be called before authorization @language_pack_id Identifier of the language pack to delete
+deleteLanguagePack language_pack_id:string = Ok;
+
+
+//@description Registers the currently used device for receiving push notifications. Returns a globally unique identifier of the push notification subscription @device_token Device token @other_user_ids List of user identifiers of other users currently using the application
+registerDevice device_token:DeviceToken other_user_ids:vector<int53> = PushReceiverId;
-//@description Registers the currently used device for receiving push notifications @device_token Device token @other_user_ids List of at most 100 user identifiers of other users currently using the client
-registerDevice device_token:DeviceToken other_user_ids:vector<int32> = Ok;
+//@description Handles a push notification. Returns error with code 406 if the push notification is not supported and connection to the server is required to fetch new data. Can be called before authorization
+//@payload JSON-encoded push notification payload with all fields sent by the server, and "google.sent_time" and "google.notification.sound" fields added
+processPushNotification payload:string = Ok;
+
+//@description Returns a globally unique push notification subscription identifier for identification of an account, which has received a push notification. Can be called synchronously @payload JSON-encoded push notification payload
+getPushReceiverId payload:string = PushReceiverId;
//@description Returns t.me URLs recently visited by a newly registered user @referrer Google Play referrer to identify the user
@@ -2749,12 +6557,12 @@ setUserPrivacySettingRules setting:UserPrivacySetting rules:userPrivacySettingRu
getUserPrivacySettingRules setting:UserPrivacySetting = UserPrivacySettingRules;
-//@description Returns the value of an option by its name. (Check the list of available options on https://core.telegram.org/tdlib/options.) Can be called before authorization
+//@description Returns the value of an option by its name. (Check the list of available options on https://core.telegram.org/tdlib/options.) Can be called before authorization. Can be called synchronously for options "version" and "commit_hash"
//@name The name of the option
getOption name:string = OptionValue;
//@description Sets the value of an option. (Check the list of available options on https://core.telegram.org/tdlib/options.) Only writable options can be set. Can be called before authorization
-//@name The name of the option @value The new value of the option
+//@name The name of the option @value The new value of the option; pass null to reset option value to a default value
setOption name:string value:OptionValue = Ok;
@@ -2764,43 +6572,63 @@ setAccountTtl ttl:accountTtl = Ok;
//@description Returns the period of inactivity after which the account of the current user will automatically be deleted
getAccountTtl = AccountTtl;
-//@description Deletes the account of the current user, deleting all information associated with the user from the server. The phone number of the account can be used to create a new account @reason The reason why the account was deleted; optional
-deleteAccount reason:string = Ok;
+//@description Deletes the account of the current user, deleting all information associated with the user from the server. The phone number of the account can be used to create a new account. Can be called before authorization when the current authorization state is authorizationStateWaitPassword @reason The reason why the account was deleted; optional @password The 2-step verification password of the current user. If not specified, account deletion can be canceled within one week
+deleteAccount reason:string password:string = Ok;
+
+
+//@description Removes a chat action bar without any other action @chat_id Chat identifier
+removeChatActionBar chat_id:int53 = Ok;
+
+//@description Reports a chat to the Telegram moderators. A chat can be reported only from the chat action bar, or if chat.can_be_reported
+//@chat_id Chat identifier @message_ids Identifiers of reported messages; may be empty to report the whole chat @reason The reason for reporting the chat @text Additional report details; 0-1024 characters
+reportChat chat_id:int53 message_ids:vector<int53> reason:ChatReportReason text:string = Ok;
+
+//@description Reports a chat photo to the Telegram moderators. A chat photo can be reported only if chat.can_be_reported
+//@chat_id Chat identifier @file_id Identifier of the photo to report. Only full photos from chatPhoto can be reported @reason The reason for reporting the chat photo @text Additional report details; 0-1024 characters
+reportChatPhoto chat_id:int53 file_id:int32 reason:ChatReportReason text:string = Ok;
+//@description Reports reactions set on a message to the Telegram moderators. Reactions on a message can be reported only if message.can_report_reactions
+//@chat_id Chat identifier @message_id Message identifier @sender_id Identifier of the sender, which added the reaction
+reportMessageReactions chat_id:int53 message_id:int53 sender_id:MessageSender = Ok;
-//@description Returns information on whether the current chat can be reported as spam @chat_id Chat identifier
-getChatReportSpamState chat_id:int53 = ChatReportSpamState;
-//@description Used to let the server know whether a chat is spam or not. Can be used only if ChatReportSpamState.can_report_spam is true. After this request, ChatReportSpamState.can_report_spam becomes false forever @chat_id Chat identifier @is_spam_chat If true, the chat will be reported as spam; otherwise it will be marked as not spam
-changeChatReportSpamState chat_id:int53 is_spam_chat:Bool = Ok;
+//@description Returns detailed statistics about a chat. Currently, this method can be used only for supergroups and channels. Can be used only if supergroupFullInfo.can_get_statistics == true @chat_id Chat identifier @is_dark Pass true if a dark theme is used by the application
+getChatStatistics chat_id:int53 is_dark:Bool = ChatStatistics;
-//@description Reports a chat to the Telegram moderators. Supported only for supergroups, channels, or private chats with bots, since other chats can't be checked by moderators @chat_id Chat identifier @reason The reason for reporting the chat @message_ids Identifiers of reported messages, if any
-reportChat chat_id:int53 reason:ChatReportReason message_ids:vector<int53> = Ok;
+//@description Returns detailed statistics about a message. Can be used only if message.can_get_statistics == true @chat_id Chat identifier @message_id Message identifier @is_dark Pass true if a dark theme is used by the application
+getMessageStatistics chat_id:int53 message_id:int53 is_dark:Bool = MessageStatistics;
+//@description Loads an asynchronous or a zoomed in statistical graph @chat_id Chat identifier @token The token for graph loading @x X-value for zoomed in graph or 0 otherwise
+getStatisticalGraph chat_id:int53 token:string x:int53 = StatisticalGraph;
-//@description Returns storage usage statistics @chat_limit Maximum number of chats with the largest storage usage for which separate statistics should be returned. All other chats will be grouped in entries with chat_id == 0. If the chat info database is not used, the chat_limit is ignored and is always set to 0
+
+//@description Returns storage usage statistics. Can be called before authorization @chat_limit The maximum number of chats with the largest storage usage for which separate statistics need to be returned. All other chats will be grouped in entries with chat_id == 0. If the chat info database is not used, the chat_limit is ignored and is always set to 0
getStorageStatistics chat_limit:int32 = StorageStatistics;
-//@description Quickly returns approximate storage usage statistics
+//@description Quickly returns approximate storage usage statistics. Can be called before authorization
getStorageStatisticsFast = StorageStatisticsFast;
+//@description Returns database statistics
+getDatabaseStatistics = DatabaseStatistics;
+
//@description Optimizes storage usage, i.e. deletes some files and returns new storage usage statistics. Secret thumbnails can't be deleted
-//@size Limit on the total size of files after deletion. Pass -1 to use the default limit
+//@size Limit on the total size of files after deletion, in bytes. Pass -1 to use the default limit
//@ttl Limit on the time that has passed since the last time a file was accessed (or creation time for some filesystems). Pass -1 to use the default limit
-//@count Limit on the total count of files after deletion. Pass -1 to use the default limit
+//@count Limit on the total number of files after deletion. Pass -1 to use the default limit
//@immunity_delay The amount of time after the creation of a file during which it can't be deleted, in seconds. Pass -1 to use the default value
-//@file_types If not empty, only files with the given type(s) are considered. By default, all types except thumbnails, profile photos, stickers and wallpapers are deleted
-//@chat_ids If not empty, only files from the given chats are considered. Use 0 as chat identifier to delete files not belonging to any chat (e.g., profile photos)
-//@exclude_chat_ids If not empty, files from the given chats are excluded. Use 0 as chat identifier to exclude all files not belonging to any chat (e.g., profile photos)
+//@file_types If non-empty, only files with the given types are considered. By default, all types except thumbnails, profile photos, stickers and wallpapers are deleted
+//@chat_ids If non-empty, only files from the given chats are considered. Use 0 as chat identifier to delete files not belonging to any chat (e.g., profile photos)
+//@exclude_chat_ids If non-empty, files from the given chats are excluded. Use 0 as chat identifier to exclude all files not belonging to any chat (e.g., profile photos)
+//@return_deleted_file_statistics Pass true if statistics about the files that were deleted must be returned instead of the whole storage usage statistics. Affects only returned statistics
//@chat_limit Same as in getStorageStatistics. Affects only returned statistics
-optimizeStorage size:int53 ttl:int32 count:int32 immunity_delay:int32 file_types:vector<FileType> chat_ids:vector<int53> exclude_chat_ids:vector<int53> chat_limit:int32 = StorageStatistics;
+optimizeStorage size:int53 ttl:int32 count:int32 immunity_delay:int32 file_types:vector<FileType> chat_ids:vector<int53> exclude_chat_ids:vector<int53> return_deleted_file_statistics:Bool chat_limit:int32 = StorageStatistics;
-//@description Sets the current network type. Can be called before authorization. Calling this method forces all network connections to reopen, mitigating the delay in switching between different networks, so it should be called whenever the network is changed, even if the network type remains the same.
-//-Network type is used to check whether the library can use the network at all and also for collecting detailed network data usage statistics @type The new network type. By default, networkTypeOther
+//@description Sets the current network type. Can be called before authorization. Calling this method forces all network connections to reopen, mitigating the delay in switching between different networks, so it must be called whenever the network is changed, even if the network type remains the same.
+//-Network type is used to check whether the library can use the network at all and also for collecting detailed network data usage statistics @type The new network type; pass null to set network type to networkTypeOther
setNetworkType type:NetworkType = Ok;
-//@description Returns network data usage statistics. Can be called before authorization @only_current If true, returns only data for the current library launch
+//@description Returns network data usage statistics. Can be called before authorization @only_current Pass true to get statistics only for the current library launch
getNetworkStatistics only_current:Bool = NetworkStatistics;
//@description Adds the specified data to data usage statistics. Can be called before authorization @entry The network statistics entry with the data to be added to statistics
@@ -2809,28 +6637,154 @@ addNetworkStatistics entry:NetworkStatisticsEntry = Ok;
//@description Resets all network data usage statistics to zero. Can be called before authorization
resetNetworkStatistics = Ok;
+//@description Returns auto-download settings presets for the current user
+getAutoDownloadSettingsPresets = AutoDownloadSettingsPresets;
+
+//@description Sets auto-download settings @settings New user auto-download settings @type Type of the network for which the new settings are relevant
+setAutoDownloadSettings settings:autoDownloadSettings type:NetworkType = Ok;
+
+
+//@description Returns information about a bank card @bank_card_number The bank card number
+getBankCardInfo bank_card_number:string = BankCardInfo;
+
+
+//@description Returns one of the available Telegram Passport elements @type Telegram Passport element type @password The 2-step verification password of the current user
+getPassportElement type:PassportElementType password:string = PassportElement;
+
+//@description Returns all available Telegram Passport elements @password The 2-step verification password of the current user
+getAllPassportElements password:string = PassportElements;
+
+//@description Adds an element to the user's Telegram Passport. May return an error with a message "PHONE_VERIFICATION_NEEDED" or "EMAIL_VERIFICATION_NEEDED" if the chosen phone number or the chosen email address must be verified first @element Input Telegram Passport element @password The 2-step verification password of the current user
+setPassportElement element:InputPassportElement password:string = PassportElement;
+
+//@description Deletes a Telegram Passport element @type Element type
+deletePassportElement type:PassportElementType = Ok;
+
+//@description Informs the user that some of the elements in their Telegram Passport contain errors; for bots only. The user will not be able to resend the elements, until the errors are fixed @user_id User identifier @errors The errors
+setPassportElementErrors user_id:int53 errors:vector<inputPassportElementError> = Ok;
+
+
+//@description Returns an IETF language tag of the language preferred in the country, which must be used to fill native fields in Telegram Passport personal details. Returns a 404 error if unknown @country_code A two-letter ISO 3166-1 alpha-2 country code
+getPreferredCountryLanguage country_code:string = Text;
+
+
+//@description Sends a code to verify a phone number to be added to a user's Telegram Passport
+//@phone_number The phone number of the user, in international format @settings Settings for the authentication of the user's phone number; pass null to use default settings
+sendPhoneNumberVerificationCode phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo;
+
+//@description Resends the code to verify a phone number to be added to a user's Telegram Passport
+resendPhoneNumberVerificationCode = AuthenticationCodeInfo;
+
+//@description Checks the phone number verification code for Telegram Passport @code Verification code to check
+checkPhoneNumberVerificationCode code:string = Ok;
+
+
+//@description Sends a code to verify an email address to be added to a user's Telegram Passport @email_address Email address
+sendEmailAddressVerificationCode email_address:string = EmailAddressAuthenticationCodeInfo;
+
+//@description Resends the code to verify an email address to be added to a user's Telegram Passport
+resendEmailAddressVerificationCode = EmailAddressAuthenticationCodeInfo;
+
+//@description Checks the email address verification code for Telegram Passport @code Verification code to check
+checkEmailAddressVerificationCode code:string = Ok;
+
+
+//@description Returns a Telegram Passport authorization form for sharing data with a service @bot_user_id User identifier of the service's bot @scope Telegram Passport element types requested by the service @public_key Service's public key @nonce Unique request identifier provided by the service
+getPassportAuthorizationForm bot_user_id:int53 scope:string public_key:string nonce:string = PassportAuthorizationForm;
+
+//@description Returns already available Telegram Passport elements suitable for completing a Telegram Passport authorization form. Result can be received only once for each authorization form @autorization_form_id Authorization form identifier @password The 2-step verification password of the current user
+getPassportAuthorizationFormAvailableElements autorization_form_id:int32 password:string = PassportElementsWithErrors;
+
+//@description Sends a Telegram Passport authorization form, effectively sharing data with the service. This method must be called after getPassportAuthorizationFormAvailableElements if some previously available elements are going to be reused
+//@autorization_form_id Authorization form identifier @types Types of Telegram Passport elements chosen by user to complete the authorization form
+sendPassportAuthorizationForm autorization_form_id:int32 types:vector<PassportElementType> = Ok;
+
+
+//@description Sends phone number confirmation code to handle links of the type internalLinkTypePhoneNumberConfirmation @hash Hash value from the link @phone_number Phone number value from the link @settings Settings for the authentication of the user's phone number; pass null to use default settings
+sendPhoneNumberConfirmationCode hash:string phone_number:string settings:phoneNumberAuthenticationSettings = AuthenticationCodeInfo;
+
+//@description Resends phone number confirmation code
+resendPhoneNumberConfirmationCode = AuthenticationCodeInfo;
+
+//@description Checks phone number confirmation code @code Confirmation code to check
+checkPhoneNumberConfirmationCode code:string = Ok;
+
//@description Informs the server about the number of pending bot updates if they haven't been processed for a long time; for bots only @pending_update_count The number of pending updates @error_message The last error message
setBotUpdatesStatus pending_update_count:int32 error_message:string = Ok;
-//@description Uploads a PNG image with a sticker; for bots only; returns the uploaded file @user_id Sticker file owner @png_sticker PNG image with the sticker; must be up to 512 kB in size and fit in 512x512 square
-uploadStickerFile user_id:int32 png_sticker:InputFile = File;
+//@description Uploads a file with a sticker; returns the uploaded file @user_id Sticker file owner; ignored for regular users @sticker Sticker file to upload
+uploadStickerFile user_id:int53 sticker:inputSticker = File;
+
+//@description Returns a suggested name for a new sticker set with a given title @title Sticker set title; 1-64 characters
+getSuggestedStickerSetName title:string = Text;
+
+//@description Checks whether a name can be used for a new sticker set @name Name to be checked
+checkStickerSetName name:string = CheckStickerSetNameResult;
+
+//@description Creates a new sticker set. Returns the newly created sticker set
+//@user_id Sticker set owner; ignored for regular users
+//@title Sticker set title; 1-64 characters
+//@name Sticker set name. Can contain only English letters, digits and underscores. Must end with *"_by_<bot username>"* (*<bot_username>* is case insensitive) for bots; 1-64 characters
+//@sticker_type Type of the stickers in the set
+//@stickers List of stickers to be added to the set; must be non-empty. All stickers must have the same format. For TGS stickers, uploadStickerFile must be used before the sticker is shown
+//@source Source of the sticker set; may be empty if unknown
+createNewStickerSet user_id:int53 title:string name:string sticker_type:StickerType stickers:vector<inputSticker> source:string = StickerSet;
-//@description Creates a new sticker set; for bots only. Returns the newly created sticker set @user_id Sticker set owner @title Sticker set title; 1-64 characters @name Sticker set name. Can contain only English letters, digits and underscores. Must end with *"_by_<bot username>"* (*<bot_username>* is case insensitive); 1-64 characters
-//@is_masks True, if stickers are masks @stickers List of stickers to be added to the set
-createNewStickerSet user_id:int32 title:string name:string is_masks:Bool stickers:vector<inputSticker> = StickerSet;
+//@description Adds a new sticker to a set; for bots only. Returns the sticker set
+//@user_id Sticker set owner @name Sticker set name @sticker Sticker to add to the set
+addStickerToSet user_id:int53 name:string sticker:inputSticker = StickerSet;
-//@description Adds a new sticker to a set; for bots only. Returns the sticker set @user_id Sticker set owner @name Sticker set name @sticker Sticker to add to the set
-addStickerToSet user_id:int32 name:string sticker:inputSticker = StickerSet;
+//@description Sets a sticker set thumbnail; for bots only. Returns the sticker set
+//@user_id Sticker set owner @name Sticker set name
+//@thumbnail Thumbnail to set in PNG, TGS, or WEBM format; pass null to remove the sticker set thumbnail. Thumbnail format must match the format of stickers in the set
+setStickerSetThumbnail user_id:int53 name:string thumbnail:InputFile = StickerSet;
-//@description Changes the position of a sticker in the set to which it belongs; for bots only. The sticker set must have been created by the bot @sticker Sticker @position New position of the sticker in the set, zero-based
+//@description Changes the position of a sticker in the set to which it belongs; for bots only. The sticker set must have been created by the bot
+//@sticker Sticker @position New position of the sticker in the set, 0-based
setStickerPositionInSet sticker:InputFile position:int32 = Ok;
//@description Removes a sticker from the set to which it belongs; for bots only. The sticker set must have been created by the bot @sticker Sticker
removeStickerFromSet sticker:InputFile = Ok;
+//@description Returns information about a file with a map thumbnail in PNG format. Only map thumbnail files with size less than 1MB can be downloaded @location Location of the map center @zoom Map zoom level; 13-20 @width Map width in pixels before applying scale; 16-1024 @height Map height in pixels before applying scale; 16-1024 @scale Map scale; 1-3 @chat_id Identifier of a chat in which the thumbnail will be shown. Use 0 if unknown
+getMapThumbnailFile location:location zoom:int32 width:int32 height:int32 scale:int32 chat_id:int53 = File;
+
+
+//@description Returns information about a limit, increased for Premium users. Returns a 404 error if the limit is unknown @limit_type Type of the limit
+getPremiumLimit limit_type:PremiumLimitType = PremiumLimit;
+
+//@description Returns information about features, available to Premium users @source Source of the request; pass null if the method is called from some non-standard source
+getPremiumFeatures source:PremiumSource = PremiumFeatures;
+
+//@description Returns examples of premium stickers for demonstration purposes
+getPremiumStickerExamples = Stickers;
+
+//@description Informs TDLib that the user viewed detailed information about a Premium feature on the Premium features screen @feature The viewed premium feature
+viewPremiumFeature feature:PremiumFeature = Ok;
+
+//@description Informs TDLib that the user clicked Premium subscription button on the Premium features screen
+clickPremiumSubscriptionButton = Ok;
+
+//@description Returns state of Telegram Premium subscription and promotion videos for Premium features
+getPremiumState = PremiumState;
+
+//@description Checks whether Telegram Premium purchase is possible. Must be called before in-store Premium purchase @purpose Transaction purpose
+canPurchasePremium purpose:StorePaymentPurpose = Ok;
+
+//@description Informs server about a purchase through App Store. For official applications only @receipt App Store receipt @purpose Transaction purpose
+assignAppStoreTransaction receipt:bytes purpose:StorePaymentPurpose = Ok;
+
+//@description Informs server about a purchase through Google Play. For official applications only @package_name Application package name @store_product_id Identifier of the purchased store product @purchase_token Google Play purchase token @purpose Transaction purpose
+assignGooglePlayTransaction package_name:string store_product_id:string purchase_token:string purpose:StorePaymentPurpose = Ok;
+
+
+//@description Accepts Telegram terms of services @terms_of_service_id Terms of service identifier
+acceptTermsOfService terms_of_service_id:string = Ok;
+
+
//@description Sends a custom request; for bots only @method The method name @parameters JSON-serialized method parameters
sendCustomRequest method:string parameters:string = CustomRequestResult;
@@ -2838,48 +6792,121 @@ sendCustomRequest method:string parameters:string = CustomRequestResult;
answerCustomQuery custom_query_id:int64 data:string = Ok;
-//@description Succeeds after a specified amount of time has passed. Can be called before authorization @seconds Number of seconds before the function returns
+//@description Succeeds after a specified amount of time has passed. Can be called before initialization @seconds Number of seconds before the function returns
setAlarm seconds:double = Ok;
-//@description Uses current user IP to found his country. Returns two-letter ISO 3166-1 alpha-2 country code. Can be called before authorization
+//@description Returns information about existing countries. Can be called before authorization
+getCountries = Countries;
+
+//@description Uses the current IP address to find the current country. Returns two-letter ISO 3166-1 alpha-2 country code. Can be called before authorization
getCountryCode = Text;
-//@description Returns the default text for invitation messages to be used as a placeholder when the current user invites friends to Telegram
-getInviteText = Text;
+//@description Returns information about a phone number by its prefix. Can be called before authorization @phone_number_prefix The phone number prefix
+getPhoneNumberInfo phone_number_prefix:string = PhoneNumberInfo;
+
+//@description Returns information about a phone number by its prefix synchronously. getCountries must be called at least once after changing localization to the specified language if properly localized country information is expected. Can be called synchronously
+//@language_code A two-letter ISO 639-1 language code for country information localization @phone_number_prefix The phone number prefix
+getPhoneNumberInfoSync language_code:string phone_number_prefix:string = PhoneNumberInfo;
+
+//@description Returns the link for downloading official Telegram application to be used when the current user invites friends to Telegram
+getApplicationDownloadLink = HttpUrl;
+
+//@description Returns information about a tg:// deep link. Use "tg://need_update_for_some_feature" or "tg:some_unsupported_feature" for testing. Returns a 404 error for unknown links. Can be called before authorization @link The link
+getDeepLinkInfo link:string = DeepLinkInfo;
+
+
+//@description Returns application config, provided by the server. Can be called before authorization
+getApplicationConfig = JsonValue;
+
+//@description Saves application log event on the server. Can be called before authorization @type Event type @chat_id Optional chat identifier, associated with the event @data The log event data
+saveApplicationLogEvent type:string chat_id:int53 data:JsonValue = Ok;
+
+
+//@description Adds a proxy server for network requests. Can be called before authorization @server Proxy server IP address @port Proxy server port @enable Pass true to immediately enable the proxy @type Proxy type
+addProxy server:string port:int32 enable:Bool type:ProxyType = Proxy;
+
+//@description Edits an existing proxy server for network requests. Can be called before authorization @proxy_id Proxy identifier @server Proxy server IP address @port Proxy server port @enable Pass true to immediately enable the proxy @type Proxy type
+editProxy proxy_id:int32 server:string port:int32 enable:Bool type:ProxyType = Proxy;
+
+//@description Enables a proxy. Only one proxy can be enabled at a time. Can be called before authorization @proxy_id Proxy identifier
+enableProxy proxy_id:int32 = Ok;
+
+//@description Disables the currently enabled proxy. Can be called before authorization
+disableProxy = Ok;
+
+//@description Removes a proxy server. Can be called before authorization @proxy_id Proxy identifier
+removeProxy proxy_id:int32 = Ok;
+
+//@description Returns list of proxies that are currently set up. Can be called before authorization
+getProxies = Proxies;
+
+//@description Returns an HTTPS link, which can be used to add a proxy. Available only for SOCKS5 and MTProto proxies. Can be called before authorization @proxy_id Proxy identifier
+getProxyLink proxy_id:int32 = HttpUrl;
+
+//@description Computes time needed to receive a response from a Telegram server through a proxy. Can be called before authorization @proxy_id Proxy identifier. Use 0 to ping a Telegram server without a proxy
+pingProxy proxy_id:int32 = Seconds;
+
+
+//@description Sets new log stream for internal logging of TDLib. Can be called synchronously @log_stream New log stream
+setLogStream log_stream:LogStream = Ok;
+
+//@description Returns information about currently used log stream for internal logging of TDLib. Can be called synchronously
+getLogStream = LogStream;
+
+//@description Sets the verbosity level of the internal logging of TDLib. Can be called synchronously
+//@new_verbosity_level New value of the verbosity level for logging. Value 0 corresponds to fatal errors, value 1 corresponds to errors, value 2 corresponds to warnings and debug warnings, value 3 corresponds to informational, value 4 corresponds to debug, value 5 corresponds to verbose debug, value greater than 5 and up to 1023 can be used to enable even more logging
+setLogVerbosityLevel new_verbosity_level:int32 = Ok;
+
+//@description Returns current verbosity level of the internal logging of TDLib. Can be called synchronously
+getLogVerbosityLevel = LogVerbosityLevel;
+
+//@description Returns list of available TDLib internal log tags, for example, ["actor", "binlog", "connections", "notifications", "proxy"]. Can be called synchronously
+getLogTags = LogTags;
+
+//@description Sets the verbosity level for a specified TDLib internal log tag. Can be called synchronously
+//@tag Logging tag to change verbosity level @new_verbosity_level New verbosity level; 1-1024
+setLogTagVerbosityLevel tag:string new_verbosity_level:int32 = Ok;
+
+//@description Returns current verbosity level for a specified TDLib internal log tag. Can be called synchronously @tag Logging tag to change verbosity level
+getLogTagVerbosityLevel tag:string = LogVerbosityLevel;
-//@description Returns the terms of service. Can be called before authorization
-getTermsOfService = Text;
+//@description Adds a message to TDLib internal log. Can be called synchronously
+//@verbosity_level The minimum verbosity level needed for the message to be logged; 0-1023 @text Text of a message to log
+addLogMessage verbosity_level:int32 text:string = Ok;
-//@description Sets the proxy server for network requests. Can be called before authorization @proxy Proxy server to use. Specify null to remove the proxy server
-setProxy proxy:Proxy = Ok;
+//@description Returns support information for the given user; for Telegram support only @user_id User identifier
+getUserSupportInfo user_id:int53 = UserSupportInfo;
-//@description Returns the proxy that is currently set up. Can be called before authorization
-getProxy = Proxy;
+//@description Sets support information for the given user; for Telegram support only @user_id User identifier @message New information message
+setUserSupportInfo user_id:int53 message:formattedText = UserSupportInfo;
-//@description Does nothing; for testing only
+//@description Does nothing; for testing only. This is an offline method. Can be called before authorization
testCallEmpty = Ok;
-//@description Returns the received string; for testing only @x String to return
+//@description Returns the received string; for testing only. This is an offline method. Can be called before authorization @x String to return
testCallString x:string = TestString;
-//@description Returns the received bytes; for testing only @x Bytes to return
+//@description Returns the received bytes; for testing only. This is an offline method. Can be called before authorization @x Bytes to return
testCallBytes x:bytes = TestBytes;
-//@description Returns the received vector of numbers; for testing only @x Vector of numbers to return
+//@description Returns the received vector of numbers; for testing only. This is an offline method. Can be called before authorization @x Vector of numbers to return
testCallVectorInt x:vector<int32> = TestVectorInt;
-//@description Returns the received vector of objects containing a number; for testing only @x Vector of objects to return
+//@description Returns the received vector of objects containing a number; for testing only. This is an offline method. Can be called before authorization @x Vector of objects to return
testCallVectorIntObject x:vector<testInt> = TestVectorIntObject;
-//@description For testing only request. Returns the received vector of strings; for testing only @x Vector of strings to return
+//@description Returns the received vector of strings; for testing only. This is an offline method. Can be called before authorization @x Vector of strings to return
testCallVectorString x:vector<string> = TestVectorString;
-//@description Returns the received vector of objects containing a string; for testing only @x Vector of objects to return
+//@description Returns the received vector of objects containing a string; for testing only. This is an offline method. Can be called before authorization @x Vector of objects to return
testCallVectorStringObject x:vector<testString> = TestVectorStringObject;
-//@description Returns the squared received number; for testing only @x Number to square
+//@description Returns the squared received number; for testing only. This is an offline method. Can be called before authorization @x Number to square
testSquareInt x:int32 = TestInt;
-//@description Sends a simple network request to the Telegram servers; for testing only
+//@description Sends a simple network request to the Telegram servers; for testing only. Can be called before authorization
testNetwork = Ok;
+//@description Sends a simple network request to the Telegram servers via proxy; for testing only. Can be called before authorization @server Proxy server IP address @port Proxy server port @type Proxy type
+//@dc_id Identifier of a datacenter with which to test connection @timeout The maximum overall timeout for the request
+testProxy server:string port:int32 type:ProxyType dc_id:int32 timeout:double = Ok;
//@description Forces an updates.getDifference call to the Telegram servers; for testing only
testGetDifference = Ok;
-//@description Does nothing and ensures that the Update object is used; for testing only
+//@description Does nothing and ensures that the Update object is used; for testing only. This is an offline method. Can be called before authorization
testUseUpdate = Update;
-//@description Does nothing and ensures that the Error object is used; for testing only
-testUseError = Error;
+//@description Returns the specified error and ensures that the Error object is used; for testing only. Can be called synchronously @error The error to be returned
+testReturnError error:error = Error;
diff --git a/protocols/Telegram/tdlib/td/td/generate/scheme/td_api.tlo b/protocols/Telegram/tdlib/td/td/generate/scheme/td_api.tlo
deleted file mode 100644
index 4b7b647b2f..0000000000
--- a/protocols/Telegram/tdlib/td/td/generate/scheme/td_api.tlo
+++ /dev/null
Binary files differ
diff --git a/protocols/Telegram/tdlib/td/td/generate/scheme/telegram_api.tl b/protocols/Telegram/tdlib/td/td/generate/scheme/telegram_api.tl
index 11e5f7f727..2abe99df15 100644
--- a/protocols/Telegram/tdlib/td/td/generate/scheme/telegram_api.tl
+++ b/protocols/Telegram/tdlib/td/td/generate/scheme/telegram_api.tl
@@ -14,25 +14,34 @@ vector#1cb5c415 {t:Type} # [ t ] = Vector t;
error#c4b9f9bb code:int text:string = Error;
-ipPort ipv4:int port:int = IpPort;
-help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<ipPort> = help.ConfigSimple;
+ipPort#d433ad73 ipv4:int port:int = IpPort;
+ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort;
+accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector<IpPort> = AccessPointRule;
+help.configSimple#5a592a6c date:int expires:int rules:vector<AccessPointRule> = help.ConfigSimple;
+
+inputPeerPhotoFileLocationLegacy#27d69997 flags:# big:flags.0?true peer:InputPeer volume_id:long local_id:int = InputFileLocation;
+inputStickerSetThumbLegacy#dbaeae9 stickerset:InputStickerSet volume_id:long local_id:int = InputFileLocation;
---functions---
test.useError = Error;
test.useConfigSimple = help.ConfigSimple;
+test.parseInputAppEvent = InputAppEvent;
---types---
inputPeerEmpty#7f3b18ea = InputPeer;
inputPeerSelf#7da07ec9 = InputPeer;
-inputPeerChat#179be863 chat_id:int = InputPeer;
-inputPeerUser#7b8e7de6 user_id:int access_hash:long = InputPeer;
-inputPeerChannel#20adaef8 channel_id:int access_hash:long = InputPeer;
+inputPeerChat#35a95cb9 chat_id:long = InputPeer;
+inputPeerUser#dde8a54c user_id:long access_hash:long = InputPeer;
+inputPeerChannel#27bcbbfc channel_id:long access_hash:long = InputPeer;
+inputPeerUserFromMessage#a87b0a1c peer:InputPeer msg_id:int user_id:long = InputPeer;
+inputPeerChannelFromMessage#bd2a0840 peer:InputPeer msg_id:int channel_id:long = InputPeer;
inputUserEmpty#b98886cf = InputUser;
inputUserSelf#f7c1b13f = InputUser;
-inputUser#d8292816 user_id:int access_hash:long = InputUser;
+inputUser#f21158c6 user_id:long access_hash:long = InputUser;
+inputUserFromMessage#1da448e2 peer:InputPeer msg_id:int user_id:long = InputUser;
inputPhoneContact#f392b7f4 client_id:long phone:string first_name:string last_name:string = InputContact;
@@ -43,36 +52,42 @@ inputMediaEmpty#9664f57f = InputMedia;
inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia;
inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
-inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia;
-inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
-inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia;
+inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia;
+inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
+inputMediaDocument#33473058 flags:# id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia;
inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia;
-inputMediaGifExternal#4843b0fd url:string q:string = InputMedia;
inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
-inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
-inputMediaGeoLive#7b1a118f geo_point:InputGeoPoint period:int = InputMedia;
+inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia;
+inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia;
+inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> solution:flags.1?string solution_entities:flags.1?Vector<MessageEntity> = InputMedia;
+inputMediaDice#e66fbf7b emoticon:string = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
-inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
+inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = InputChatPhoto;
inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto;
inputGeoPointEmpty#e4c123d6 = InputGeoPoint;
-inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint;
+inputGeoPoint#48222faf flags:# lat:double long:double accuracy_radius:flags.0?int = InputGeoPoint;
inputPhotoEmpty#1cd7bf0d = InputPhoto;
-inputPhoto#fb95c6c4 id:long access_hash:long = InputPhoto;
+inputPhoto#3bb3b94a id:long access_hash:long file_reference:bytes = InputPhoto;
-inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation;
+inputFileLocation#dfdaabe1 volume_id:long local_id:int secret:long file_reference:bytes = InputFileLocation;
inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation;
-inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation;
-
-inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent;
-
-peerUser#9db1bc6d user_id:int = Peer;
-peerChat#bad0e5bb chat_id:int = Peer;
-peerChannel#bddde532 channel_id:int = Peer;
+inputDocumentFileLocation#bad07584 id:long access_hash:long file_reference:bytes thumb_size:string = InputFileLocation;
+inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation;
+inputTakeoutFileLocation#29be5899 = InputFileLocation;
+inputPhotoFileLocation#40181ffe id:long access_hash:long file_reference:bytes thumb_size:string = InputFileLocation;
+inputPhotoLegacyFileLocation#d83466f3 id:long access_hash:long file_reference:bytes volume_id:long local_id:int secret:long = InputFileLocation;
+inputPeerPhotoFileLocation#37257e99 flags:# big:flags.0?true peer:InputPeer photo_id:long = InputFileLocation;
+inputStickerSetThumb#9d84f3db stickerset:InputStickerSet thumb_version:int = InputFileLocation;
+inputGroupCallStream#598a92a flags:# call:InputGroupCall time_ms:long scale:int video_channel:flags.0?int video_quality:flags.0?int = InputFileLocation;
+
+peerUser#59511722 user_id:long = Peer;
+peerChat#36c6019a chat_id:long = Peer;
+peerChannel#a2a5371e channel_id:long = Peer;
storage.fileUnknown#aa963b05 = storage.FileType;
storage.filePartial#40bc6f52 = storage.FileType;
@@ -85,14 +100,11 @@ storage.fileMov#4b09ebbc = storage.FileType;
storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;
-fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileLocation;
-fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation;
-
-userEmpty#200250ba id:int = User;
-user#2e13f4c3 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?string bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
+userEmpty#d3bc4b7a id:long = User;
+user#8f97c628 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector<Username> = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
-userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto;
+userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
userStatusEmpty#9d05049 = UserStatus;
userStatusOnline#edb93949 expires:int = UserStatus;
@@ -101,134 +113,151 @@ userStatusRecently#e26f42f1 = UserStatus;
userStatusLastWeek#7bf09fc = UserStatus;
userStatusLastMonth#77ebc742 = UserStatus;
-chatEmpty#9ba2d800 id:int = Chat;
-chat#d91cdd54 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true admins_enabled:flags.3?true admin:flags.4?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel = Chat;
-chatForbidden#7328bdb id:int title:string = Chat;
-channel#450b7115 flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights participants_count:flags.17?int = Chat;
-channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
+chatEmpty#29562865 id:long = Chat;
+chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
+chatForbidden#6592a1a7 id:long title:string = Chat;
+channel#83259464 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector<Username> = Chat;
+channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
-chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull;
-channelFull#76af5481 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int = ChatFull;
+chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?ChatReactions = ChatFull;
+channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull;
-chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
-chatParticipantCreator#da13538a user_id:int = ChatParticipant;
-chatParticipantAdmin#e2d6e436 user_id:int inviter_id:int date:int = ChatParticipant;
+chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
+chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
+chatParticipantAdmin#a0933f5b user_id:long inviter_id:long date:int = ChatParticipant;
-chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0?ChatParticipant = ChatParticipants;
-chatParticipants#3f460fed chat_id:int participants:Vector<ChatParticipant> version:int = ChatParticipants;
+chatParticipantsForbidden#8763d3e1 flags:# chat_id:long self_participant:flags.0?ChatParticipant = ChatParticipants;
+chatParticipants#3cbc93f8 chat_id:long participants:Vector<ChatParticipant> version:int = ChatParticipants;
chatPhotoEmpty#37c1011c = ChatPhoto;
-chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto;
+chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto;
-messageEmpty#83e5de54 id:int = Message;
-message#44f9b43d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long = Message;
-messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message;
+messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message;
+message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int = Message;
+messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message;
messageMediaEmpty#3ded6320 = MessageMedia;
messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia;
messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia;
-messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia;
+messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia;
messageMediaUnsupported#9f84f49e = MessageMedia;
-messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia;
+messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia;
messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia;
messageMediaGame#fdb19008 game:Game = MessageMedia;
-messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
-messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia;
+messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string extended_media:flags.4?MessageExtendedMedia = MessageMedia;
+messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia;
+messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
+messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
messageActionEmpty#b6aef7b0 = MessageAction;
-messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
+messageActionChatCreate#bd47cbad title:string users:Vector<long> = MessageAction;
messageActionChatEditTitle#b5a1ce5a title:string = MessageAction;
messageActionChatEditPhoto#7fcb13a8 photo:Photo = MessageAction;
messageActionChatDeletePhoto#95e3fbef = MessageAction;
-messageActionChatAddUser#488a7337 users:Vector<int> = MessageAction;
-messageActionChatDeleteUser#b2ae9b0c user_id:int = MessageAction;
-messageActionChatJoinedByLink#f89cf5e8 inviter_id:int = MessageAction;
+messageActionChatAddUser#15cefd00 users:Vector<long> = MessageAction;
+messageActionChatDeleteUser#a43f30cc user_id:long = MessageAction;
+messageActionChatJoinedByLink#31224c3 inviter_id:long = MessageAction;
messageActionChannelCreate#95d2ac92 title:string = MessageAction;
-messageActionChatMigrateTo#51bdb021 channel_id:int = MessageAction;
-messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageAction;
+messageActionChatMigrateTo#e1037f92 channel_id:long = MessageAction;
+messageActionChannelMigrateFrom#ea3948e9 title:string chat_id:long = MessageAction;
messageActionPinMessage#94bd38ed = MessageAction;
messageActionHistoryClear#9fbab604 = MessageAction;
messageActionGameScore#92a72876 game_id:long score:int = MessageAction;
-messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction;
-messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction;
-messageActionPhoneCall#80e11a7f flags:# call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction;
+messageActionPaymentSentMe#8f31b327 flags:# recurring_init:flags.2?true recurring_used:flags.3?true currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction;
+messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_used:flags.3?true currency:string total_amount:long invoice_slug:flags.0?string = MessageAction;
+messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction;
messageActionScreenshotTaken#4792929b = MessageAction;
messageActionCustomAction#fae69f56 message:string = MessageAction;
messageActionBotAllowed#abe9affe domain:string = MessageAction;
-
-dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog;
+messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction;
+messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAction;
+messageActionContactSignUp#f3f25f76 = MessageAction;
+messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction;
+messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction;
+messageActionInviteToGroupCall#502f92f7 call:InputGroupCall users:Vector<long> = MessageAction;
+messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction;
+messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction;
+messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
+messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
+messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction;
+messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction;
+messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = MessageAction;
+messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_id:flags.0?long = MessageAction;
+messageActionTopicEdit#b18a431c flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool = MessageAction;
+
+dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
+dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
photoEmpty#2331b22d id:long = Photo;
-photo#9288dd29 flags:# has_stickers:flags.0?true id:long access_hash:long date:int sizes:Vector<PhotoSize> = Photo;
+photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> video_sizes:flags.1?Vector<VideoSize> dc_id:int = Photo;
photoSizeEmpty#e17e23c type:string = PhotoSize;
-photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
-photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
+photoSize#75c78e60 type:string w:int h:int size:int = PhotoSize;
+photoCachedSize#21e1ad6 type:string w:int h:int bytes:bytes = PhotoSize;
+photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize;
+photoSizeProgressive#fa3efb95 type:string w:int h:int sizes:Vector<int> = PhotoSize;
+photoPathSize#d8214d41 type:string bytes:bytes = PhotoSize;
geoPointEmpty#1117dd5f = GeoPoint;
-geoPoint#2049d70c long:double lat:double = GeoPoint;
-
-auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone;
+geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radius:flags.0?int = GeoPoint;
-auth.sentCode#5e002502 flags:# phone_registered:flags.0?true type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode;
+auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode;
-auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization;
+auth.authorization#33fb7bb8 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int user:User = auth.Authorization;
+auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization;
-auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization;
+auth.exportedAuthorization#b434e2b8 id:long bytes:bytes = auth.ExportedAuthorization;
inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer;
inputNotifyUsers#193b4417 = InputNotifyPeer;
inputNotifyChats#4a95e84e = InputNotifyPeer;
-inputNotifyAll#a429b886 = InputNotifyPeer;
-
-inputPeerNotifyEventsEmpty#f03064d8 = InputPeerNotifyEvents;
-inputPeerNotifyEventsAll#e86a2c74 = InputPeerNotifyEvents;
+inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer;
+inputNotifyForumTopic#5c467992 peer:InputPeer top_msg_id:int = InputNotifyPeer;
-inputPeerNotifySettings#38935eb2 flags:# show_previews:flags.0?true silent:flags.1?true mute_until:int sound:string = InputPeerNotifySettings;
+inputPeerNotifySettings#df1f002b flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?NotificationSound = InputPeerNotifySettings;
-peerNotifyEventsEmpty#add53cb3 = PeerNotifyEvents;
-peerNotifyEventsAll#6d1ded88 = PeerNotifyEvents;
+peerNotifySettings#a83b0426 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound = PeerNotifySettings;
-peerNotifySettingsEmpty#70a68512 = PeerNotifySettings;
-peerNotifySettings#9acda4c0 flags:# show_previews:flags.0?true silent:flags.1?true mute_until:int sound:string = PeerNotifySettings;
+peerSettings#a518110d flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int = PeerSettings;
-peerSettings#818426cd flags:# report_spam:flags.0?true = PeerSettings;
-
-wallPaper#ccb03657 id:int title:string sizes:Vector<PhotoSize> color:int = WallPaper;
-wallPaperSolid#63117f24 id:int title:string bg_color:int color:int = WallPaper;
+wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
+wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
inputReportReasonSpam#58dbcab8 = ReportReason;
inputReportReasonViolence#1e22c78d = ReportReason;
inputReportReasonPornography#2e59d922 = ReportReason;
-inputReportReasonOther#e1746d0a text:string = ReportReason;
-
-userFull#f220f3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo common_chats_count:int = UserFull;
-
-contact#f911c994 user_id:int mutual:Bool = Contact;
+inputReportReasonChildAbuse#adf44ee3 = ReportReason;
+inputReportReasonOther#c1e4a2b1 = ReportReason;
+inputReportReasonCopyright#9b89f93a = ReportReason;
+inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
+inputReportReasonFake#f5ddd6e7 = ReportReason;
+inputReportReasonIllegalDrugs#a8eb2be = ReportReason;
+inputReportReasonPersonalDetails#9ec7863d = ReportReason;
-importedContact#d0028438 user_id:int client_id:long = ImportedContact;
+userFull#c4b1fc3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector<PremiumGiftOption> = UserFull;
-contactBlocked#561bc879 user_id:int date:int = ContactBlocked;
+contact#145ade0b user_id:long mutual:Bool = Contact;
-contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus;
+importedContact#c13e3c50 user_id:long client_id:long = ImportedContact;
-contacts.link#3ace484c my_link:ContactLink foreign_link:ContactLink user:User = contacts.Link;
+contactStatus#16d9703b user_id:long status:UserStatus = ContactStatus;
contacts.contactsNotModified#b74ba9d2 = contacts.Contacts;
contacts.contacts#eae87e42 contacts:Vector<Contact> saved_count:int users:Vector<User> = contacts.Contacts;
contacts.importedContacts#77d01c3b imported:Vector<ImportedContact> popular_invites:Vector<PopularContact> retry_contacts:Vector<long> users:Vector<User> = contacts.ImportedContacts;
-contacts.blocked#1c138d15 blocked:Vector<ContactBlocked> users:Vector<User> = contacts.Blocked;
-contacts.blockedSlice#900802a1 count:int blocked:Vector<ContactBlocked> users:Vector<User> = contacts.Blocked;
+contacts.blocked#ade1591 blocked:Vector<PeerBlocked> chats:Vector<Chat> users:Vector<User> = contacts.Blocked;
+contacts.blockedSlice#e1664194 count:int blocked:Vector<PeerBlocked> chats:Vector<Chat> users:Vector<User> = contacts.Blocked;
messages.dialogs#15ba6c40 dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
messages.dialogsSlice#71e094f3 count:int dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
+messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs;
messages.messages#8c718e87 messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
-messages.messagesSlice#b446ae3 count:int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
-messages.channelMessages#99262e37 flags:# pts:int count:int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
+messages.messagesSlice#3a54685e flags:# inexact:flags.1?true count:int next_rate:flags.0?int offset_id_offset:flags.2?int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
+messages.channelMessages#64479808 flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
messages.messagesNotModified#74535f21 count:int = messages.Messages;
messages.chats#64ff9fd5 chats:Vector<Chat> = messages.Chats;
@@ -254,73 +283,116 @@ inputMessagesFilterRoundVideo#b549da53 = MessagesFilter;
inputMessagesFilterMyMentions#c1f8e69a = MessagesFilter;
inputMessagesFilterGeo#e7026d0d = MessagesFilter;
inputMessagesFilterContacts#e062db83 = MessagesFilter;
+inputMessagesFilterPinned#1bb00451 = MessagesFilter;
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
updateMessageID#4e90bfd6 id:int random_id:long = Update;
updateDeleteMessages#a20db0e5 messages:Vector<int> pts:int pts_count:int = Update;
-updateUserTyping#5c486927 user_id:int action:SendMessageAction = Update;
-updateChatUserTyping#9a65ea1f chat_id:int user_id:int action:SendMessageAction = Update;
+updateUserTyping#c01e857f user_id:long action:SendMessageAction = Update;
+updateChatUserTyping#83487af0 chat_id:long from_id:Peer action:SendMessageAction = Update;
updateChatParticipants#7761198 participants:ChatParticipants = Update;
-updateUserStatus#1bfbd823 user_id:int status:UserStatus = Update;
-updateUserName#a7332b73 user_id:int first_name:string last_name:string username:string = Update;
-updateUserPhoto#95313b0c user_id:int date:int photo:UserProfilePhoto previous:Bool = Update;
-updateContactRegistered#2575bbb9 user_id:int date:int = Update;
-updateContactLink#9d2e67c5 user_id:int my_link:ContactLink foreign_link:ContactLink = Update;
+updateUserStatus#e5bdf8de user_id:long status:UserStatus = Update;
+updateUserName#a7848924 user_id:long first_name:string last_name:string usernames:Vector<Username> = Update;
+updateUserPhoto#f227868c user_id:long date:int photo:UserProfilePhoto previous:Bool = Update;
updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update;
updateEncryptedChatTyping#1710f156 chat_id:int = Update;
updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update;
updateEncryptedMessagesRead#38fe25b7 chat_id:int max_date:int date:int = Update;
-updateChatParticipantAdd#ea4b0e5c chat_id:int user_id:int inviter_id:int date:int version:int = Update;
-updateChatParticipantDelete#6e5f8c22 chat_id:int user_id:int version:int = Update;
+updateChatParticipantAdd#3dda5451 chat_id:long user_id:long inviter_id:long date:int version:int = Update;
+updateChatParticipantDelete#e32f3d77 chat_id:long user_id:long version:int = Update;
updateDcOptions#8e5e9873 dc_options:Vector<DcOption> = Update;
-updateUserBlocked#80ece81a user_id:int blocked:Bool = Update;
updateNotifySettings#bec268ef peer:NotifyPeer notify_settings:PeerNotifySettings = Update;
updateServiceNotification#ebe46819 flags:# popup:flags.0?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector<MessageEntity> = Update;
updatePrivacy#ee3b272a key:PrivacyKey rules:Vector<PrivacyRule> = Update;
-updateUserPhone#12b9417b user_id:int phone:string = Update;
-updateReadHistoryInbox#9961fd5c peer:Peer max_id:int pts:int pts_count:int = Update;
+updateUserPhone#5492a13 user_id:long phone:string = Update;
+updateReadHistoryInbox#9c974fdf flags:# folder_id:flags.0?int peer:Peer max_id:int still_unread_count:int pts:int pts_count:int = Update;
updateReadHistoryOutbox#2f2f21bf peer:Peer max_id:int pts:int pts_count:int = Update;
updateWebPage#7f891213 webpage:WebPage pts:int pts_count:int = Update;
updateReadMessagesContents#68c13933 messages:Vector<int> pts:int pts_count:int = Update;
-updateChannelTooLong#eb0467fb flags:# channel_id:int pts:flags.0?int = Update;
-updateChannel#b6d45656 channel_id:int = Update;
+updateChannelTooLong#108d941f flags:# channel_id:long pts:flags.0?int = Update;
+updateChannel#635b4c09 channel_id:long = Update;
updateNewChannelMessage#62ba04d9 message:Message pts:int pts_count:int = Update;
-updateReadChannelInbox#4214f37f channel_id:int max_id:int = Update;
-updateDeleteChannelMessages#c37521c9 channel_id:int messages:Vector<int> pts:int pts_count:int = Update;
-updateChannelMessageViews#98a12b4b channel_id:int id:int views:int = Update;
-updateChatAdmins#6e947941 chat_id:int enabled:Bool version:int = Update;
-updateChatParticipantAdmin#b6901959 chat_id:int user_id:int is_admin:Bool version:int = Update;
+updateReadChannelInbox#922e6e10 flags:# folder_id:flags.0?int channel_id:long max_id:int still_unread_count:int pts:int = Update;
+updateDeleteChannelMessages#c32d5b12 channel_id:long messages:Vector<int> pts:int pts_count:int = Update;
+updateChannelMessageViews#f226ac08 channel_id:long id:int views:int = Update;
+updateChatParticipantAdmin#d7ca61a2 chat_id:long user_id:long is_admin:Bool version:int = Update;
updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update;
-updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector<long> = Update;
-updateStickerSets#43ae3dec = Update;
+updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true emojis:flags.1?true order:Vector<long> = Update;
+updateStickerSets#31c24808 flags:# masks:flags.0?true emojis:flags.1?true = Update;
updateSavedGifs#9375341e = Update;
-updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update;
-updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update;
+updateBotInlineQuery#496f379c flags:# query_id:long user_id:long query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update;
+updateBotInlineSend#12f12a07 flags:# user_id:long query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update;
updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update;
-updateChannelPinnedMessage#98592475 channel_id:int id:int = Update;
-updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
+updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update;
-updateInlineBotCallbackQuery#f9d27a5a flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
-updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update;
-updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update;
+updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
+updateReadChannelOutbox#b75f99a9 channel_id:long max_id:int = Update;
+updateDraftMessage#1b49ec6d flags:# peer:Peer top_msg_id:flags.0?int draft:DraftMessage = Update;
updateReadFeaturedStickers#571d2742 = Update;
updateRecentStickers#9a422c20 = Update;
updateConfig#a229dd06 = Update;
updatePtsChanged#3354678f = Update;
-updateChannelWebPage#40771900 channel_id:int webpage:WebPage pts:int pts_count:int = Update;
-updateDialogPinned#19d27f3c flags:# pinned:flags.0?true peer:DialogPeer = Update;
-updatePinnedDialogs#ea4cb65b flags:# order:flags.0?Vector<DialogPeer> = Update;
+updateChannelWebPage#2f2ba99f channel_id:long webpage:WebPage pts:int pts_count:int = Update;
+updateDialogPinned#6e6fe51c flags:# pinned:flags.0?true folder_id:flags.1?int peer:DialogPeer = Update;
+updatePinnedDialogs#fa0f3ca2 flags:# folder_id:flags.1?int order:flags.0?Vector<DialogPeer> = Update;
updateBotWebhookJSON#8317c0c3 data:DataJSON = Update;
updateBotWebhookJSONQuery#9b9240a6 query_id:long data:DataJSON timeout:int = Update;
-updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update;
-updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update;
+updateBotShippingQuery#b5aefd7d query_id:long user_id:long payload:bytes shipping_address:PostAddress = Update;
+updateBotPrecheckoutQuery#8caa9a96 flags:# query_id:long user_id:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update;
updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update;
-updateLangPackTooLong#10c2404b = Update;
+updateLangPackTooLong#46560264 lang_code:string = Update;
updateLangPack#56022f4d difference:LangPackDifference = Update;
updateFavedStickers#e511996d = Update;
-updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector<int> = Update;
+updateChannelReadMessagesContents#ea29055d flags:# channel_id:long top_msg_id:flags.0?int messages:Vector<int> = Update;
updateContactsReset#7084a7be = Update;
-updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update;
+updateChannelAvailableMessages#b23fc698 channel_id:long available_min_id:int = Update;
+updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update;
+updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update;
+updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update;
+updateFolderPeers#19360dc0 folder_peers:Vector<FolderPeer> pts:int pts_count:int = Update;
+updatePeerSettings#6a7e7366 peer:Peer settings:PeerSettings = Update;
+updatePeerLocated#b4afcfb0 peers:Vector<PeerLocated> = Update;
+updateNewScheduledMessage#39a51dfb message:Message = Update;
+updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector<int> = Update;
+updateTheme#8216fba3 theme:Theme = Update;
+updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
+updateLoginToken#564fe691 = Update;
+updateMessagePollVote#106395c9 poll_id:long user_id:long options:Vector<bytes> qts:int = Update;
+updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update;
+updateDialogFilterOrder#a5d72105 order:Vector<int> = Update;
+updateDialogFilters#3504914f = Update;
+updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update;
+updateChannelMessageForwards#d29a27f4 channel_id:long id:int forwards:int = Update;
+updateReadChannelDiscussionInbox#d6b19546 flags:# channel_id:long top_msg_id:int read_max_id:int broadcast_id:flags.0?long broadcast_post:flags.0?int = Update;
+updateReadChannelDiscussionOutbox#695c9e7c channel_id:long top_msg_id:int read_max_id:int = Update;
+updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update;
+updateChannelUserTyping#8c88c923 flags:# channel_id:long top_msg_id:flags.0?int from_id:Peer action:SendMessageAction = Update;
+updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector<int> pts:int pts_count:int = Update;
+updatePinnedChannelMessages#5bb98608 flags:# pinned:flags.0?true channel_id:long messages:Vector<int> pts:int pts_count:int = Update;
+updateChat#f89a6a4e chat_id:long = Update;
+updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector<GroupCallParticipant> version:int = Update;
+updateGroupCall#14b24500 chat_id:long call:GroupCall = Update;
+updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update;
+updateChatParticipant#d087663a flags:# chat_id:long date:int actor_id:long user_id:long prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update;
+updateChannelParticipant#985d3abb flags:# channel_id:long date:int actor_id:long user_id:long prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update;
+updateBotStopped#c4870a49 user_id:long date:int stopped:Bool qts:int = Update;
+updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update;
+updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector<BotCommand> = Update;
+updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector<long> = Update;
+updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
+updateMessageReactions#5e1b3cb8 flags:# peer:Peer msg_id:int top_msg_id:flags.0?int reactions:MessageReactions = Update;
+updateAttachMenuBots#17b7a20b = Update;
+updateWebViewResultSent#1592b79d query_id:long = Update;
+updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
+updateSavedRingtones#74d8be99 = Update;
+updateTranscribedAudio#84cd5a flags:# pending:flags.0?true peer:Peer msg_id:int transcription_id:long text:string = Update;
+updateReadFeaturedEmojiStickers#fb4c496c = Update;
+updateUserEmojiStatus#28373599 user_id:long emoji_status:EmojiStatus = Update;
+updateRecentEmojiStatuses#30f443db = Update;
+updateRecentReactions#6f7863f4 = Update;
+updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update;
+updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update;
+updateChannelPinnedTopic#f694b0ae flags:# channel_id:long topic_id:flags.0?int = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@@ -330,12 +402,12 @@ updates.differenceSlice#a8fb1981 new_messages:Vector<Message> new_encrypted_mess
updates.differenceTooLong#4afe8f6d pts:int = updates.Difference;
updatesTooLong#e317af7e = Updates;
-updateShortMessage#914fbf11 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector<MessageEntity> = Updates;
-updateShortChatMessage#16812688 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector<MessageEntity> = Updates;
+updateShortMessage#313bc7f8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:long message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates;
+updateShortChatMessage#4d6deea5 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:long chat_id:long message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates;
updateShort#78d4dec1 update:Update date:int = Updates;
updatesCombined#725b04c3 updates:Vector<Update> users:Vector<User> chats:Vector<Chat> date:int seq_start:int seq:int = Updates;
updates#74ae4240 updates:Vector<Update> users:Vector<User> chats:Vector<Chat> date:int seq:int = Updates;
-updateShortSentMessage#11f1331c flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector<MessageEntity> = Updates;
+updateShortSentMessage#9015e101 flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates;
photos.photos#8dca6aa5 photos:Vector<Photo> users:Vector<User> = photos.Photos;
photos.photosSlice#15051f54 count:int photos:Vector<Photo> users:Vector<User> = photos.Photos;
@@ -345,27 +417,27 @@ photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector<FileHash> = upload.File;
-dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int = DcOption;
+dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
-config#86b5778e flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config;
+config#232566ac flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
-help.appUpdate#8987f311 id:int critical:Bool url:string text:string = help.AppUpdate;
+help.appUpdate#ccbbce30 flags:# can_not_skip:flags.0?true id:int version:string text:string entities:Vector<MessageEntity> document:flags.1?Document url:flags.2?string sticker:flags.3?Document = help.AppUpdate;
help.noAppUpdate#c45a6536 = help.AppUpdate;
help.inviteText#18cb9f78 message:string = help.InviteText;
encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat;
-encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat;
-encryptedChatRequested#c878527e id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat;
-encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat;
-encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat;
+encryptedChatWaiting#66b25953 id:int access_hash:long date:int admin_id:long participant_id:long = EncryptedChat;
+encryptedChatRequested#48f1d94c flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:long participant_id:long g_a:bytes = EncryptedChat;
+encryptedChat#61f0d4c7 id:int access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long = EncryptedChat;
+encryptedChatDiscarded#1e1c7c45 flags:# history_deleted:flags.0?true id:int = EncryptedChat;
inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat;
encryptedFileEmpty#c21f497e = EncryptedFile;
-encryptedFile#4a70994c id:long access_hash:long size:int dc_id:int key_fingerprint:int = EncryptedFile;
+encryptedFile#a8008cd8 id:long access_hash:long size:long dc_id:int key_fingerprint:int = EncryptedFile;
inputEncryptedFileEmpty#1837c364 = InputEncryptedFile;
inputEncryptedFileUploaded#64bd0306 id:long parts:int md5_checksum:string key_fingerprint:int = InputEncryptedFile;
@@ -382,17 +454,18 @@ messages.sentEncryptedMessage#560f8935 date:int = messages.SentEncryptedMessage;
messages.sentEncryptedFile#9493ff32 date:int file:EncryptedFile = messages.SentEncryptedMessage;
inputDocumentEmpty#72f0eaae = InputDocument;
-inputDocument#18798952 id:long access_hash:long = InputDocument;
+inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument;
documentEmpty#36f8c871 id:long = Document;
-document#87232bc7 id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int version:int attributes:Vector<DocumentAttribute> = Document;
+document#8fd4c4d8 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:long thumbs:flags.0?Vector<PhotoSize> video_thumbs:flags.1?Vector<VideoSize> dc_id:int attributes:Vector<DocumentAttribute> = Document;
help.support#17c6b5f6 phone_number:string user:User = help.Support;
notifyPeer#9fd40bd8 peer:Peer = NotifyPeer;
notifyUsers#b4c83b4c = NotifyPeer;
notifyChats#c007cec3 = NotifyPeer;
-notifyAll#74d07c60 = NotifyPeer;
+notifyBroadcasts#d612e8ef = NotifyPeer;
+notifyForumTopic#226e6308 peer:Peer top_msg_id:int = NotifyPeer;
sendMessageTypingAction#16bf744e = SendMessageAction;
sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
@@ -407,16 +480,33 @@ sendMessageChooseContactAction#628cbc6f = SendMessageAction;
sendMessageGamePlayAction#dd6a8f48 = SendMessageAction;
sendMessageRecordRoundAction#88f27fbc = SendMessageAction;
sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction;
+speakingInGroupCallAction#d92c2285 = SendMessageAction;
+sendMessageHistoryImportAction#dbda9246 progress:int = SendMessageAction;
+sendMessageChooseStickerAction#b05ac6b1 = SendMessageAction;
+sendMessageEmojiInteraction#25972bcb emoticon:string msg_id:int interaction:DataJSON = SendMessageAction;
+sendMessageEmojiInteractionSeen#b665902e emoticon:string = SendMessageAction;
contacts.found#b3134d9d my_results:Vector<Peer> results:Vector<Peer> chats:Vector<Chat> users:Vector<User> = contacts.Found;
inputPrivacyKeyStatusTimestamp#4f96cb18 = InputPrivacyKey;
inputPrivacyKeyChatInvite#bdfb0426 = InputPrivacyKey;
inputPrivacyKeyPhoneCall#fabadc5f = InputPrivacyKey;
+inputPrivacyKeyPhoneP2P#db9e70d2 = InputPrivacyKey;
+inputPrivacyKeyForwards#a4dd4c08 = InputPrivacyKey;
+inputPrivacyKeyProfilePhoto#5719bacc = InputPrivacyKey;
+inputPrivacyKeyPhoneNumber#352dafa = InputPrivacyKey;
+inputPrivacyKeyAddedByPhone#d1219bdd = InputPrivacyKey;
+inputPrivacyKeyVoiceMessages#aee69d68 = InputPrivacyKey;
privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey;
privacyKeyChatInvite#500e6dfa = PrivacyKey;
privacyKeyPhoneCall#3d662b7b = PrivacyKey;
+privacyKeyPhoneP2P#39491cc8 = PrivacyKey;
+privacyKeyForwards#69ec56a3 = PrivacyKey;
+privacyKeyProfilePhoto#96151fed = PrivacyKey;
+privacyKeyPhoneNumber#d19ae46d = PrivacyKey;
+privacyKeyAddedByPhone#42ffd42b = PrivacyKey;
+privacyKeyVoiceMessages#697f414 = PrivacyKey;
inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule;
inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule;
@@ -424,15 +514,19 @@ inputPrivacyValueAllowUsers#131cc67f users:Vector<InputUser> = InputPrivacyRule;
inputPrivacyValueDisallowContacts#ba52007 = InputPrivacyRule;
inputPrivacyValueDisallowAll#d66b66c9 = InputPrivacyRule;
inputPrivacyValueDisallowUsers#90110467 users:Vector<InputUser> = InputPrivacyRule;
+inputPrivacyValueAllowChatParticipants#840649cf chats:Vector<long> = InputPrivacyRule;
+inputPrivacyValueDisallowChatParticipants#e94f0f86 chats:Vector<long> = InputPrivacyRule;
privacyValueAllowContacts#fffe1bac = PrivacyRule;
privacyValueAllowAll#65427b82 = PrivacyRule;
-privacyValueAllowUsers#4d5bbe0c users:Vector<int> = PrivacyRule;
+privacyValueAllowUsers#b8905fb2 users:Vector<long> = PrivacyRule;
privacyValueDisallowContacts#f888fa1a = PrivacyRule;
privacyValueDisallowAll#8b73e763 = PrivacyRule;
-privacyValueDisallowUsers#c7f49b7 users:Vector<int> = PrivacyRule;
+privacyValueDisallowUsers#e4621141 users:Vector<long> = PrivacyRule;
+privacyValueAllowChatParticipants#6b134e8e chats:Vector<long> = PrivacyRule;
+privacyValueDisallowChatParticipants#41c87565 chats:Vector<long> = PrivacyRule;
-account.privacyRules#554abb6f rules:Vector<PrivacyRule> users:Vector<User> = account.PrivacyRules;
+account.privacyRules#50a04e45 rules:Vector<PrivacyRule> chats:Vector<Chat> users:Vector<User> = account.PrivacyRules;
accountDaysTTL#b8d0afdf days:int = AccountDaysTTL;
@@ -443,74 +537,85 @@ documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_strea
documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
+documentAttributeCustomEmoji#fd149899 flags:# free:flags.0?true alt:string stickerset:InputStickerSet = DocumentAttribute;
messages.stickersNotModified#f1749a22 = messages.Stickers;
-messages.stickers#8a8ecd32 hash:string stickers:Vector<Document> = messages.Stickers;
+messages.stickers#30a6ec7e hash:long stickers:Vector<Document> = messages.Stickers;
stickerPack#12b299d4 emoticon:string documents:Vector<long> = StickerPack;
messages.allStickersNotModified#e86602c3 = messages.AllStickers;
-messages.allStickers#edfd405f hash:int sets:Vector<StickerSet> = messages.AllStickers;
+messages.allStickers#cdbbcebb hash:long sets:Vector<StickerSet> = messages.AllStickers;
messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages;
-contactLinkUnknown#5f4f9247 = ContactLink;
-contactLinkNone#feedd3ad = ContactLink;
-contactLinkHasPhone#268f3f59 = ContactLink;
-contactLinkContact#d502c2d0 = ContactLink;
-
webPageEmpty#eb1477e8 id:long = WebPage;
webPagePending#c586da1c id:long date:int = WebPage;
-webPage#5f07b4bc flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page = WebPage;
-webPageNotModified#85849473 = WebPage;
+webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector<WebPageAttribute> = WebPage;
+webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage;
-authorization#7bf2e6f6 hash:long flags:int device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization;
+authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true encrypted_requests_disabled:flags.3?true call_requests_disabled:flags.4?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization;
-account.authorizations#1250abde authorizations:Vector<Authorization> = account.Authorizations;
+account.authorizations#4bff8ea0 authorization_ttl_days:int authorizations:Vector<Authorization> = account.Authorizations;
-account.noPassword#96dabc18 new_salt:bytes email_unconfirmed_pattern:string = account.Password;
-account.password#7c18141c current_salt:bytes new_salt:bytes hint:string has_recovery:Bool email_unconfirmed_pattern:string = account.Password;
+account.password#957b50fb flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int login_email_pattern:flags.6?string = account.Password;
-account.passwordSettings#b7b72ab3 email:string = account.PasswordSettings;
+account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings;
-account.passwordInputSettings#86916deb flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string = account.PasswordInputSettings;
+account.passwordInputSettings#c23727c9 flags:# new_algo:flags.0?PasswordKdfAlgo new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_settings:flags.2?SecureSecretSettings = account.PasswordInputSettings;
auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage;
-chatInviteEmpty#69df3769 = ExportedChatInvite;
-chatInviteExported#fc2e05bc link:string = ExportedChatInvite;
+chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int title:flags.8?string = ExportedChatInvite;
+chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite;
chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
-chatInvite#db74f558 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:ChatPhoto participants_count:int participants:flags.4?Vector<User> = ChatInvite;
+chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
+chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite;
inputStickerSetEmpty#ffb62b95 = InputStickerSet;
inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
+inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
+inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
+inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
+inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet;
+inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet;
+inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet;
+inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet;
-stickerSet#5585a139 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string count:int hash:int = StickerSet;
+stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet;
-messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
+messages.stickerSet#6e153f16 set:StickerSet packs:Vector<StickerPack> keywords:Vector<StickerKeyword> documents:Vector<Document> = messages.StickerSet;
+messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
botCommand#c27ac8c7 command:string description:string = BotCommand;
-botInfo#98e81d3a user_id:int description:string commands:Vector<BotCommand> = BotInfo;
+botInfo#8f300b57 flags:# user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector<BotCommand> menu_button:flags.3?BotMenuButton = BotInfo;
keyboardButton#a2fa4880 text:string = KeyboardButton;
keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
-keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton;
+keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton;
keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton;
keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton;
keyboardButtonGame#50f41ccf text:string = KeyboardButton;
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
+keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;
+inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;
+keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
+inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = KeyboardButton;
+keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton;
+keyboardButtonWebView#13767230 text:string url:string = KeyboardButton;
+keyboardButtonSimpleWebView#a0c0505c text:string url:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup;
-replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup;
-replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> = ReplyMarkup;
+replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup;
+replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> placeholder:flags.3?string = ReplyMarkup;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity;
@@ -524,30 +629,38 @@ messageEntityItalic#826f8b60 offset:int length:int = MessageEntity;
messageEntityCode#28a20571 offset:int length:int = MessageEntity;
messageEntityPre#73924be0 offset:int length:int language:string = MessageEntity;
messageEntityTextUrl#76a6d327 offset:int length:int url:string = MessageEntity;
-messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEntity;
+messageEntityMentionName#dc7b1140 offset:int length:int user_id:long = MessageEntity;
inputMessageEntityMentionName#208e68c9 offset:int length:int user_id:InputUser = MessageEntity;
messageEntityPhone#9b69e34b offset:int length:int = MessageEntity;
messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity;
+messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity;
+messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity;
+messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity;
+messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity;
+messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity;
+messageEntityCustomEmoji#c8cf05f8 offset:int length:int document_id:long = MessageEntity;
inputChannelEmpty#ee8c1e86 = InputChannel;
-inputChannel#afeb712e channel_id:int access_hash:long = InputChannel;
+inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel;
+inputChannelFromMessage#5b934f9d peer:InputPeer msg_id:int channel_id:long = InputChannel;
contacts.resolvedPeer#7f077ad9 peer:Peer chats:Vector<Chat> users:Vector<User> = contacts.ResolvedPeer;
messageRange#ae30253 min_id:int max_id:int = MessageRange;
updates.channelDifferenceEmpty#3e11affb flags:# final:flags.0?true pts:int timeout:flags.1?int = updates.ChannelDifference;
-updates.channelDifferenceTooLong#6a9d7b35 flags:# final:flags.0?true pts:int timeout:flags.1?int top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = updates.ChannelDifference;
+updates.channelDifferenceTooLong#a4bcc6fe flags:# final:flags.0?true timeout:flags.1?int dialog:Dialog messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = updates.ChannelDifference;
updates.channelDifference#2064674e flags:# final:flags.0?true pts:int timeout:flags.1?int new_messages:Vector<Message> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> = updates.ChannelDifference;
channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter;
channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector<MessageRange> = ChannelMessagesFilter;
-channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant;
-channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant;
-channelParticipantCreator#e3e2e1f9 user_id:int = ChannelParticipant;
-channelParticipantAdmin#a82fa898 flags:# can_edit:flags.0?true user_id:int inviter_id:int promoted_by:int date:int admin_rights:ChannelAdminRights = ChannelParticipant;
-channelParticipantBanned#222c1886 flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChannelBannedRights = ChannelParticipant;
+channelParticipant#c00c07c0 user_id:long date:int = ChannelParticipant;
+channelParticipantSelf#35a8bfa7 flags:# via_request:flags.0?true user_id:long inviter_id:long date:int = ChannelParticipant;
+channelParticipantCreator#2fe601d3 flags:# user_id:long admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant;
+channelParticipantAdmin#34c3bb53 flags:# can_edit:flags.0?true self:flags.1?true user_id:long inviter_id:flags.1?long promoted_by:long date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant;
+channelParticipantBanned#6df8014e flags:# left:flags.0?true peer:Peer kicked_by:long date:int banned_rights:ChatBannedRights = ChannelParticipant;
+channelParticipantLeft#1b03f006 peer:Peer = ChannelParticipant;
channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter;
channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter;
@@ -555,28 +668,26 @@ channelParticipantsKicked#a3b54985 q:string = ChannelParticipantsFilter;
channelParticipantsBots#b0d1865b = ChannelParticipantsFilter;
channelParticipantsBanned#1427a5e1 q:string = ChannelParticipantsFilter;
channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter;
+channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter;
+channelParticipantsMentions#e04b5ceb flags:# q:flags.0?string top_msg_id:flags.1?int = ChannelParticipantsFilter;
-channels.channelParticipants#f56ee2a8 count:int participants:Vector<ChannelParticipant> users:Vector<User> = channels.ChannelParticipants;
+channels.channelParticipants#9ab0feaf count:int participants:Vector<ChannelParticipant> chats:Vector<Chat> users:Vector<User> = channels.ChannelParticipants;
channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
-channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector<User> = channels.ChannelParticipant;
-
-help.termsOfService#f1ee3e90 text:string = help.TermsOfService;
-
-foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif;
-foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif;
+channels.channelParticipant#dfb80317 participant:ChannelParticipant chats:Vector<Chat> users:Vector<User> = channels.ChannelParticipant;
-messages.foundGifs#450a1c0a next_offset:int results:Vector<FoundGif> = messages.FoundGifs;
+help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService;
messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs;
-messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs;
+messages.savedGifs#84a02a0d hash:long gifs:Vector<Document> = messages.SavedGifs;
inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
-inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
-inputBotInlineMessageMediaVenue#aaafadc8 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
-inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
+inputBotInlineMessageMediaInvoice#d7e78225 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult;
inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult;
@@ -585,9 +696,10 @@ inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:Input
botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
-botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
-botInlineMessageMediaVenue#4366232e flags:# geo:GeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
-botInlineMessageMediaContact#35edb4d4 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
+botInlineMessageMediaInvoice#354a9b09 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument currency:string total_amount:long reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult;
botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult;
@@ -596,22 +708,27 @@ messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_off
exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink;
-messageFwdHeader#559ebe6d flags:# from_id:flags.0?int date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int = MessageFwdHeader;
+messageFwdHeader#5f777dce flags:# imported:flags.7?true from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader;
auth.codeTypeSms#72a3158c = auth.CodeType;
auth.codeTypeCall#741cd3e3 = auth.CodeType;
auth.codeTypeFlashCall#226ccefb = auth.CodeType;
+auth.codeTypeMissedCall#d61ad6ee = auth.CodeType;
auth.sentCodeTypeApp#3dbb5986 length:int = auth.SentCodeType;
auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType;
auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType;
auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType;
+auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType;
+auth.sentCodeTypeEmailCode#5a159841 flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int next_phone_login_date:flags.2?int = auth.SentCodeType;
+auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType;
messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer;
messages.messageEditData#26b5dde6 flags:# caption:flags.0?true = messages.MessageEditData;
inputBotInlineMessageID#890c3d89 dc_id:int id:long access_hash:long = InputBotInlineMessageID;
+inputBotInlineMessageID64#b6d915d7 dc_id:int owner_id:long id:int access_hash:long = InputBotInlineMessageID;
inlineBotSwitchPM#3c20629f text:string start_param:string = InlineBotSwitchPM;
@@ -625,20 +742,23 @@ topPeerCategoryCorrespondents#637b7ed = TopPeerCategory;
topPeerCategoryGroups#bd17a14a = TopPeerCategory;
topPeerCategoryChannels#161d9628 = TopPeerCategory;
topPeerCategoryPhoneCalls#1e76a78c = TopPeerCategory;
+topPeerCategoryForwardUsers#a8406ca9 = TopPeerCategory;
+topPeerCategoryForwardChats#fbeec0f0 = TopPeerCategory;
topPeerCategoryPeers#fb834291 category:TopPeerCategory count:int peers:Vector<TopPeer> = TopPeerCategoryPeers;
contacts.topPeersNotModified#de266ef5 = contacts.TopPeers;
contacts.topPeers#70b772a8 categories:Vector<TopPeerCategoryPeers> chats:Vector<Chat> users:Vector<User> = contacts.TopPeers;
+contacts.topPeersDisabled#b52c939d = contacts.TopPeers;
-draftMessageEmpty#ba4baec5 = DraftMessage;
+draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage;
draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
-messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers;
-messages.featuredStickers#f89d88e5 hash:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
+messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
+messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
messages.recentStickersNotModified#b17f890 = messages.RecentStickers;
-messages.recentStickers#22f3afb3 hash:int packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers;
+messages.recentStickers#88d37c56 hash:long packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers;
messages.archivedStickers#4fcba9c8 count:int sets:Vector<StickerSetCovered> = messages.ArchivedStickers;
@@ -647,6 +767,7 @@ messages.stickerSetInstallResultArchive#35e410a8 sets:Vector<StickerSetCovered>
stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered;
stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector<Document> = StickerSetCovered;
+stickerSetFullCovered#40d13c0e set:StickerSet packs:Vector<StickerPack> keywords:Vector<StickerKeyword> documents:Vector<Document> = StickerSetCovered;
maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords;
@@ -658,7 +779,7 @@ game#bdf9653b flags:# id:long access_hash:long short_name:string title:string de
inputGameID#32c3e77 id:long access_hash:long = InputGame;
inputGameShortName#c331e80a bot_id:InputUser short_name:string = InputGame;
-highScore#58fffcd0 pos:int user_id:int score:int = HighScore;
+highScore#73a379eb pos:int user_id:long score:int = HighScore;
messages.highScores#9a3bfd99 scores:Vector<HighScore> users:Vector<User> = messages.HighScores;
@@ -672,6 +793,12 @@ textFixed#6c3f19b9 text:RichText = RichText;
textUrl#3c2884c1 text:RichText url:string webpage_id:long = RichText;
textEmail#de5a0dd6 text:RichText email:string = RichText;
textConcat#7e6260d7 texts:Vector<RichText> = RichText;
+textSubscript#ed6a8504 text:RichText = RichText;
+textSuperscript#c7fb5e01 text:RichText = RichText;
+textMarked#34b8621 text:RichText = RichText;
+textPhone#1ccb966a text:RichText phone:string = RichText;
+textImage#81ccf4f document_id:long w:int h:int = RichText;
+textAnchor#35553762 text:RichText name:string = RichText;
pageBlockUnsupported#13567e8a = PageBlock;
pageBlockTitle#70abc3fd text:RichText = PageBlock;
@@ -684,21 +811,24 @@ pageBlockPreformatted#c070d93e text:RichText language:string = PageBlock;
pageBlockFooter#48870999 text:RichText = PageBlock;
pageBlockDivider#db20b188 = PageBlock;
pageBlockAnchor#ce0d37b0 name:string = PageBlock;
-pageBlockList#3a58c7f4 ordered:Bool items:Vector<RichText> = PageBlock;
+pageBlockList#e4e88011 items:Vector<PageListItem> = PageBlock;
pageBlockBlockquote#263d7c26 text:RichText caption:RichText = PageBlock;
pageBlockPullquote#4f4456d3 text:RichText caption:RichText = PageBlock;
-pageBlockPhoto#e9c69982 photo_id:long caption:RichText = PageBlock;
-pageBlockVideo#d9d71866 flags:# autoplay:flags.0?true loop:flags.1?true video_id:long caption:RichText = PageBlock;
+pageBlockPhoto#1759c560 flags:# photo_id:long caption:PageCaption url:flags.0?string webpage_id:flags.0?long = PageBlock;
+pageBlockVideo#7c8fe7b6 flags:# autoplay:flags.0?true loop:flags.1?true video_id:long caption:PageCaption = PageBlock;
pageBlockCover#39f23300 cover:PageBlock = PageBlock;
-pageBlockEmbed#cde200d1 flags:# full_width:flags.0?true allow_scrolling:flags.3?true url:flags.1?string html:flags.2?string poster_photo_id:flags.4?long w:int h:int caption:RichText = PageBlock;
-pageBlockEmbedPost#292c7be9 url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector<PageBlock> caption:RichText = PageBlock;
-pageBlockCollage#8b31c4f items:Vector<PageBlock> caption:RichText = PageBlock;
-pageBlockSlideshow#130c8963 items:Vector<PageBlock> caption:RichText = PageBlock;
+pageBlockEmbed#a8718dc5 flags:# full_width:flags.0?true allow_scrolling:flags.3?true url:flags.1?string html:flags.2?string poster_photo_id:flags.4?long w:flags.5?int h:flags.5?int caption:PageCaption = PageBlock;
+pageBlockEmbedPost#f259a80b url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector<PageBlock> caption:PageCaption = PageBlock;
+pageBlockCollage#65a0fa4d items:Vector<PageBlock> caption:PageCaption = PageBlock;
+pageBlockSlideshow#31f9590 items:Vector<PageBlock> caption:PageCaption = PageBlock;
pageBlockChannel#ef1751b5 channel:Chat = PageBlock;
-pageBlockAudio#31b81a7f audio_id:long caption:RichText = PageBlock;
-
-pagePart#8e3f9ebe blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> = Page;
-pageFull#556ec7aa blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> = Page;
+pageBlockAudio#804361ea audio_id:long caption:PageCaption = PageBlock;
+pageBlockKicker#1e148390 text:RichText = PageBlock;
+pageBlockTable#bf4dea82 flags:# bordered:flags.0?true striped:flags.1?true title:RichText rows:Vector<PageTableRow> = PageBlock;
+pageBlockOrderedList#9a8ae1e1 items:Vector<PageListOrderedItem> = PageBlock;
+pageBlockDetails#76768bed flags:# open:flags.0?true blocks:Vector<PageBlock> title:RichText = PageBlock;
+pageBlockRelatedArticles#16115a96 title:RichText articles:Vector<PageRelatedArticle> = PageBlock;
+pageBlockMap#a44f3ef6 geo:GeoPoint zoom:int w:int h:int caption:PageCaption = PageBlock;
phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason;
phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason;
@@ -709,7 +839,7 @@ dataJSON#7d748d04 data:string = DataJSON;
labeledPrice#cb296bf8 label:string amount:long = LabeledPrice;
-invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;
+invoice#3e85a91b flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true recurring:flags.9?true currency:string prices:Vector<LabeledPrice> max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector<long> recurring_terms_url:flags.9?string = Invoice;
paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge;
@@ -719,30 +849,32 @@ paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string e
paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;
-webDocument#c61acbd8 url:string access_hash:long size:int mime_type:string attributes:Vector<DocumentAttribute> dc_id:int = WebDocument;
+webDocument#1c570ed1 url:string access_hash:long size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument;
inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation;
+inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w:int h:int zoom:int scale:int = InputWebFileLocation;
+inputWebFileAudioAlbumThumbLocation#f46fe924 flags:# small:flags.2?true document:flags.0?InputDocument title:flags.1?string performer:flags.1?string = InputWebFileLocation;
upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile;
-payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
+payments.paymentForm#a0058751 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON additional_methods:flags.6?Vector<PaymentFormMethod> saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?Vector<PaymentSavedCredentials> users:Vector<User> = payments.PaymentForm;
payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = payments.ValidatedRequestedInfo;
payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult;
-payments.paymentVerficationNeeded#6b56b921 url:string = payments.PaymentResult;
+payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult;
-payments.paymentReceipt#500911e1 flags:# date:int bot_id:int invoice:Invoice provider_id:int info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption currency:string total_amount:long credentials_title:string users:Vector<User> = payments.PaymentReceipt;
+payments.paymentReceipt#70c4fe03 flags:# date:int bot_id:long provider_id:long title:string description:string photo:flags.2?WebDocument invoice:Invoice info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption tip_amount:flags.3?long currency:string total_amount:long credentials_title:string users:Vector<User> = payments.PaymentReceipt;
payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo;
inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;
-inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;
+inputPaymentCredentialsGooglePay#8ac32801 payment_token:DataJSON = InputPaymentCredentials;
account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword;
@@ -753,15 +885,16 @@ inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_co
inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall;
phoneCallEmpty#5366c915 id:long = PhoneCall;
-phoneCallWaiting#1b8f4ad1 flags:# id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
-phoneCallRequested#83761ce4 id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
-phoneCallAccepted#6d003d3f id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
-phoneCall#ffe6ab67 id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connection:PhoneConnection alternative_connections:Vector<PhoneConnection> start_date:int = PhoneCall;
-phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
+phoneCallWaiting#c5226f17 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
+phoneCallRequested#14b0ed0c flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
+phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
+phoneCall#967f7c67 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector<PhoneConnection> start_date:int = PhoneCall;
+phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
-phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
+phoneConnection#9cc123c7 flags:# tcp:flags.0?true id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
+phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection;
-phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int = PhoneCallProtocol;
+phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector<string> = PhoneCallProtocol;
phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector<User> = phone.PhoneCall;
@@ -778,16 +911,12 @@ langPackStringDeleted#2979eeb2 key:string = LangPackString;
langPackDifference#f385c1f6 lang_code:string from_version:int version:int strings:Vector<LangPackString> = LangPackDifference;
-langPackLanguage#117698f1 name:string native_name:string lang_code:string = LangPackLanguage;
-
-channelAdminRights#5d7ceba5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true invite_link:flags.6?true pin_messages:flags.7?true add_admins:flags.9?true = ChannelAdminRights;
-
-channelBannedRights#58cf4249 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true until_date:int = ChannelBannedRights;
+langPackLanguage#eeca5ce3 flags:# official:flags.0?true rtl:flags.2?true beta:flags.3?true name:string native_name:string lang_code:string base_lang_code:flags.1?string plural_code:string strings_count:int translated_count:int translations_url:string = LangPackLanguage;
channelAdminLogEventActionChangeTitle#e6dfb825 prev_value:string new_value:string = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeAbout#55188a2e prev_value:string new_value:string = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeUsername#6a4afc38 prev_value:string new_value:string = ChannelAdminLogEventAction;
-channelAdminLogEventActionChangePhoto#b82f55c3 prev_photo:ChatPhoto new_photo:ChatPhoto = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangePhoto#434bd2af prev_photo:Photo new_photo:Photo = ChannelAdminLogEventAction;
channelAdminLogEventActionToggleInvites#1b7907ae new_value:Bool = ChannelAdminLogEventAction;
channelAdminLogEventActionToggleSignatures#26ae0971 new_value:Bool = ChannelAdminLogEventAction;
channelAdminLogEventActionUpdatePinned#e9e82c18 message:Message = ChannelAdminLogEventAction;
@@ -800,21 +929,47 @@ channelAdminLogEventActionParticipantToggleBan#e6d83d7e prev_participant:Channel
channelAdminLogEventActionParticipantToggleAdmin#d5676710 prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeStickerSet#b1c3caa7 prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction;
channelAdminLogEventActionTogglePreHistoryHidden#5f5c95f1 new_value:Bool = ChannelAdminLogEventAction;
-
-channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
+channelAdminLogEventActionDefaultBannedRights#2df5fc0a prev_banned_rights:ChatBannedRights new_banned_rights:ChatBannedRights = ChannelAdminLogEventAction;
+channelAdminLogEventActionStopPoll#8f079643 message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeLinkedChat#50c7ac8 prev_value:long new_value:long = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeLocation#e6b76ae prev_value:ChannelLocation new_value:ChannelLocation = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleSlowMode#53909779 prev_value:int new_value:int = ChannelAdminLogEventAction;
+channelAdminLogEventActionStartGroupCall#23209745 call:InputGroupCall = ChannelAdminLogEventAction;
+channelAdminLogEventActionDiscardGroupCall#db9f9140 call:InputGroupCall = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantMute#f92424d2 participant:GroupCallParticipant = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantUnmute#e64429c0 participant:GroupCallParticipant = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleGroupCallSetting#56d6a247 join_muted:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantJoinByInvite#5cdada77 invite:ExportedChatInvite = ChannelAdminLogEventAction;
+channelAdminLogEventActionExportedInviteDelete#5a50fca4 invite:ExportedChatInvite = ChannelAdminLogEventAction;
+channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvite = ChannelAdminLogEventAction;
+channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction;
+channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeUsernames#f04fb3a9 prev_value:Vector<string> new_value:Vector<string> = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleForum#2cc6383 new_value:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionCreateTopic#58707d28 topic:ForumTopic = ChannelAdminLogEventAction;
+channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:ForumTopic = ChannelAdminLogEventAction;
+channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction;
+channelAdminLogEventActionPinTopic#5d8d353b flags:# prev_topic:flags.0?ForumTopic new_topic:flags.1?ForumTopic = ChannelAdminLogEventAction;
+
+channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
channels.adminLogResults#ed8af74d events:Vector<ChannelAdminLogEvent> chats:Vector<Chat> users:Vector<User> = channels.AdminLogResults;
-channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true = ChannelAdminLogEventsFilter;
+channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true forums:flags.17?true = ChannelAdminLogEventsFilter;
popularContact#5ce14175 client_id:long importers:int = PopularContact;
messages.favedStickersNotModified#9e8fa6d3 = messages.FavedStickers;
-messages.favedStickers#f37f2f16 hash:int packs:Vector<StickerPack> stickers:Vector<Document> = messages.FavedStickers;
+messages.favedStickers#2cb51097 hash:long packs:Vector<StickerPack> stickers:Vector<Document> = messages.FavedStickers;
recentMeUrlUnknown#46e1d13d url:string = RecentMeUrl;
-recentMeUrlUser#8dbc3336 url:string user_id:int = RecentMeUrl;
-recentMeUrlChat#a01b22f9 url:string chat_id:int = RecentMeUrl;
+recentMeUrlUser#b92c09e2 url:string user_id:long = RecentMeUrl;
+recentMeUrlChat#b2da71d2 url:string chat_id:long = RecentMeUrl;
recentMeUrlChatInvite#eb49081d url:string chat_invite:ChatInvite = RecentMeUrl;
recentMeUrlStickerSet#bc0a57dc url:string set:StickerSetCovered = RecentMeUrl;
@@ -822,238 +977,859 @@ help.recentMeUrls#e0310d7 urls:Vector<RecentMeUrl> chats:Vector<Chat> users:Vect
inputSingleMedia#1cc6e91f flags:# media:InputMedia random_id:long message:string entities:flags.0?Vector<MessageEntity> = InputSingleMedia;
-webAuthorization#cac943f2 hash:long bot_id:int domain:string browser:string platform:string date_created:int date_active:int ip:string region:string = WebAuthorization;
+webAuthorization#a6f8f452 hash:long bot_id:long domain:string browser:string platform:string date_created:int date_active:int ip:string region:string = WebAuthorization;
account.webAuthorizations#ed56c9fc authorizations:Vector<WebAuthorization> users:Vector<User> = account.WebAuthorizations;
inputMessageID#a676a322 id:int = InputMessage;
inputMessageReplyTo#bad88395 id:int = InputMessage;
inputMessagePinned#86872538 = InputMessage;
+inputMessageCallbackQuery#acfa1a7e id:int query_id:long = InputMessage;
inputDialogPeer#fcaafeb7 peer:InputPeer = InputDialogPeer;
+inputDialogPeerFolder#64600527 folder_id:int = InputDialogPeer;
dialogPeer#e56dbf05 peer:Peer = DialogPeer;
+dialogPeerFolder#514519e2 folder_id:int = DialogPeer;
messages.foundStickerSetsNotModified#d54b65d = messages.FoundStickerSets;
-messages.foundStickerSets#5108d648 hash:int sets:Vector<StickerSetCovered> = messages.FoundStickerSets;
+messages.foundStickerSets#8af09dd2 hash:long sets:Vector<StickerSetCovered> = messages.FoundStickerSets;
+
+fileHash#f39b035c offset:long limit:int hash:bytes = FileHash;
+
+inputClientProxy#75588b3f address:string port:int = InputClientProxy;
+
+help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate;
+help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate;
+
+inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash:bytes secret:bytes = InputSecureFile;
+inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile;
+
+secureFileEmpty#64199744 = SecureFile;
+secureFile#7d09c27e id:long access_hash:long size:long dc_id:int date:int file_hash:bytes secret:bytes = SecureFile;
+
+secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData;
+
+securePlainPhone#7d6099dd phone:string = SecurePlainData;
+securePlainEmail#21ec5a5f email:string = SecurePlainData;
+
+secureValueTypePersonalDetails#9d2a81e3 = SecureValueType;
+secureValueTypePassport#3dac6a00 = SecureValueType;
+secureValueTypeDriverLicense#6e425c4 = SecureValueType;
+secureValueTypeIdentityCard#a0d0744b = SecureValueType;
+secureValueTypeInternalPassport#99a48f23 = SecureValueType;
+secureValueTypeAddress#cbe31e26 = SecureValueType;
+secureValueTypeUtilityBill#fc36954e = SecureValueType;
+secureValueTypeBankStatement#89137c0d = SecureValueType;
+secureValueTypeRentalAgreement#8b883488 = SecureValueType;
+secureValueTypePassportRegistration#99e3806a = SecureValueType;
+secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType;
+secureValueTypePhone#b320aadb = SecureValueType;
+secureValueTypeEmail#8e3ca7ee = SecureValueType;
+
+secureValue#187fa0ca flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile translation:flags.6?Vector<SecureFile> files:flags.4?Vector<SecureFile> plain_data:flags.5?SecurePlainData hash:bytes = SecureValue;
+
+inputSecureValue#db21d0a7 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile translation:flags.6?Vector<InputSecureFile> files:flags.4?Vector<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;
+
+secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
+
+secureValueErrorData#e8a40bd9 type:SecureValueType data_hash:bytes field:string text:string = SecureValueError;
+secureValueErrorFrontSide#be3dfa type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
+secureValueError#869d758f type:SecureValueType hash:bytes text:string = SecureValueError;
+secureValueErrorTranslationFile#a1144770 type:SecureValueType file_hash:bytes text:string = SecureValueError;
+secureValueErrorTranslationFiles#34636dd8 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
+
+secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
+
+account.authorizationForm#ad2e1cd8 flags:# required_types:Vector<SecureRequiredType> values:Vector<SecureValue> errors:Vector<SecureValueError> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
+
+account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode;
+
+help.deepLinkInfoEmpty#66afa166 = help.DeepLinkInfo;
+help.deepLinkInfo#6a4ee832 flags:# update_app:flags.0?true message:string entities:flags.1?Vector<MessageEntity> = help.DeepLinkInfo;
+
+savedPhoneContact#1142bd56 phone:string first_name:string last_name:string date:int = SavedContact;
+
+account.takeout#4dba4501 id:long = account.Takeout;
+
+passwordKdfAlgoUnknown#d45ab096 = PasswordKdfAlgo;
+passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow#3a912d4a salt1:bytes salt2:bytes g:int p:bytes = PasswordKdfAlgo;
+
+securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo;
+securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo;
+securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo;
+
+secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:bytes secure_secret_id:long = SecureSecretSettings;
+
+inputCheckPasswordEmpty#9880f658 = InputCheckPasswordSRP;
+inputCheckPasswordSRP#d27ff082 srp_id:long A:bytes M1:bytes = InputCheckPasswordSRP;
+
+secureRequiredType#829d99da flags:# native_names:flags.0?true selfie_required:flags.1?true translation_required:flags.2?true type:SecureValueType = SecureRequiredType;
+secureRequiredTypeOneOf#27477b4 types:Vector<SecureRequiredType> = SecureRequiredType;
+
+help.passportConfigNotModified#bfb9f457 = help.PassportConfig;
+help.passportConfig#a098d6af hash:int countries_langs:DataJSON = help.PassportConfig;
+
+inputAppEvent#1d1b1245 time:double type:string peer:long data:JSONValue = InputAppEvent;
+
+jsonObjectValue#c0de1bd9 key:string value:JSONValue = JSONObjectValue;
+
+jsonNull#3f6d7b68 = JSONValue;
+jsonBool#c7345e6a value:Bool = JSONValue;
+jsonNumber#2be0dfa4 value:double = JSONValue;
+jsonString#b71e767a value:string = JSONValue;
+jsonArray#f7444763 value:Vector<JSONValue> = JSONValue;
+jsonObject#99c1d49d value:Vector<JSONObjectValue> = JSONValue;
+
+pageTableCell#34566b6a flags:# header:flags.0?true align_center:flags.3?true align_right:flags.4?true valign_middle:flags.5?true valign_bottom:flags.6?true text:flags.7?RichText colspan:flags.1?int rowspan:flags.2?int = PageTableCell;
+
+pageTableRow#e0c0c5e5 cells:Vector<PageTableCell> = PageTableRow;
+
+pageCaption#6f747657 text:RichText credit:RichText = PageCaption;
+
+pageListItemText#b92fb6cd text:RichText = PageListItem;
+pageListItemBlocks#25e073fc blocks:Vector<PageBlock> = PageListItem;
+
+pageListOrderedItemText#5e068047 num:string text:RichText = PageListOrderedItem;
+pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector<PageBlock> = PageListOrderedItem;
+
+pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long title:flags.0?string description:flags.1?string photo_id:flags.2?long author:flags.3?string published_date:flags.4?int = PageRelatedArticle;
+
+page#98657f0d flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> views:flags.3?int = Page;
+
+help.supportName#8c05f1c9 name:string = help.SupportName;
+
+help.userInfoEmpty#f3ae2eed = help.UserInfo;
+help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:string date:int = help.UserInfo;
+
+pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
+
+poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> close_period:flags.4?int close_date:flags.5?int = Poll;
+
+pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
+
+pollResults#dcb82ea3 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<long> solution:flags.4?string solution_entities:flags.4?Vector<MessageEntity> = PollResults;
+
+chatOnlines#f041e250 onlines:int = ChatOnlines;
+
+statsURL#47a971e0 url:string = StatsURL;
+
+chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true = ChatAdminRights;
+
+chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true until_date:int = ChatBannedRights;
+
+inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper;
+inputWallPaperSlug#72091c80 slug:string = InputWallPaper;
+inputWallPaperNoFile#967a462e id:long = InputWallPaper;
+
+account.wallPapersNotModified#1c199183 = account.WallPapers;
+account.wallPapers#cdc3858c hash:long wallpapers:Vector<WallPaper> = account.WallPapers;
+
+codeSettings#8a6469c2 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true logout_tokens:flags.6?Vector<bytes> = CodeSettings;
+
+wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings;
+
+autoDownloadSettings#8efab953 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:long file_size_max:long video_upload_maxbitrate:int = AutoDownloadSettings;
+
+account.autoDownloadSettings#63cacf26 low:AutoDownloadSettings medium:AutoDownloadSettings high:AutoDownloadSettings = account.AutoDownloadSettings;
+
+emojiKeyword#d5b3b9f9 keyword:string emoticons:Vector<string> = EmojiKeyword;
+emojiKeywordDeleted#236df622 keyword:string emoticons:Vector<string> = EmojiKeyword;
+
+emojiKeywordsDifference#5cc761bd lang_code:string from_version:int version:int keywords:Vector<EmojiKeyword> = EmojiKeywordsDifference;
+
+emojiURL#a575739d url:string = EmojiURL;
+
+emojiLanguage#b3fb5361 lang_code:string = EmojiLanguage;
+
+folder#ff544e65 flags:# autofill_new_broadcasts:flags.0?true autofill_public_groups:flags.1?true autofill_new_correspondents:flags.2?true id:int title:string photo:flags.3?ChatPhoto = Folder;
+
+inputFolderPeer#fbd2c296 peer:InputPeer folder_id:int = InputFolderPeer;
+
+folderPeer#e9baa668 peer:Peer folder_id:int = FolderPeer;
+
+messages.searchCounter#e844ebff flags:# inexact:flags.1?true filter:MessagesFilter count:int = messages.SearchCounter;
+
+urlAuthResultRequest#92d33a0e flags:# request_write_access:flags.0?true bot:User domain:string = UrlAuthResult;
+urlAuthResultAccepted#8f8c0e4e url:string = UrlAuthResult;
+urlAuthResultDefault#a9d6db1f = UrlAuthResult;
+
+channelLocationEmpty#bfb5ad8b = ChannelLocation;
+channelLocation#209b82db geo_point:GeoPoint address:string = ChannelLocation;
+
+peerLocated#ca461b5d peer:Peer expires:int distance:int = PeerLocated;
+peerSelfLocated#f8ec284b expires:int = PeerLocated;
+
+restrictionReason#d072acb4 platform:string reason:string text:string = RestrictionReason;
+
+inputTheme#3c5693e9 id:long access_hash:long = InputTheme;
+inputThemeSlug#f5890df1 slug:string = InputTheme;
+
+theme#a00e67d6 flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?Vector<ThemeSettings> emoticon:flags.6?string installs_count:flags.4?int = Theme;
+
+account.themesNotModified#f41eb622 = account.Themes;
+account.themes#9a3d8c6d hash:long themes:Vector<Theme> = account.Themes;
+
+auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken;
+auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken;
+auth.loginTokenSuccess#390d5c5e authorization:auth.Authorization = auth.LoginToken;
+
+account.contentSettings#57e28221 flags:# sensitive_enabled:flags.0?true sensitive_can_change:flags.1?true = account.ContentSettings;
+
+messages.inactiveChats#a927fec5 dates:Vector<int> chats:Vector<Chat> users:Vector<User> = messages.InactiveChats;
+
+baseThemeClassic#c3a12462 = BaseTheme;
+baseThemeDay#fbd81688 = BaseTheme;
+baseThemeNight#b7b31ea8 = BaseTheme;
+baseThemeTinted#6d5f77ee = BaseTheme;
+baseThemeArctic#5b11125a = BaseTheme;
+
+inputThemeSettings#8fde504f flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int outbox_accent_color:flags.3?int message_colors:flags.0?Vector<int> wallpaper:flags.1?InputWallPaper wallpaper_settings:flags.1?WallPaperSettings = InputThemeSettings;
+
+themeSettings#fa58b6d4 flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int outbox_accent_color:flags.3?int message_colors:flags.0?Vector<int> wallpaper:flags.1?WallPaper = ThemeSettings;
+
+webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector<Document> settings:flags.1?ThemeSettings = WebPageAttribute;
+
+messageUserVote#34d247b4 user_id:long option:bytes date:int = MessageUserVote;
+messageUserVoteInputOption#3ca5b0ec user_id:long date:int = MessageUserVote;
+messageUserVoteMultiple#8a65e557 user_id:long options:Vector<bytes> date:int = MessageUserVote;
+
+messages.votesList#823f649 flags:# count:int votes:Vector<MessageUserVote> users:Vector<User> next_offset:flags.0?string = messages.VotesList;
+
+bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl;
+
+payments.bankCardData#3e24e573 title:string open_urls:Vector<BankCardOpenUrl> = payments.BankCardData;
+
+dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector<InputPeer> include_peers:Vector<InputPeer> exclude_peers:Vector<InputPeer> = DialogFilter;
+dialogFilterDefault#363293ae = DialogFilter;
+
+dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested;
+
+statsDateRangeDays#b637edaf min_date:int max_date:int = StatsDateRangeDays;
+
+statsAbsValueAndPrev#cb43acde current:double previous:double = StatsAbsValueAndPrev;
+
+statsPercentValue#cbce2fe0 part:double total:double = StatsPercentValue;
+
+statsGraphAsync#4a27eb2d token:string = StatsGraph;
+statsGraphError#bedc9822 error:string = StatsGraph;
+statsGraph#8ea464b6 flags:# json:DataJSON zoom_token:flags.0?string = StatsGraph;
+
+messageInteractionCounters#ad4fc9bd msg_id:int views:int forwards:int = MessageInteractionCounters;
-fileHash#6242c773 offset:int limit:int hash:bytes = FileHash;
+stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph recent_message_interactions:Vector<MessageInteractionCounters> = stats.BroadcastStats;
+
+help.promoDataEmpty#98f6ac75 expires:int = help.PromoData;
+help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector<Chat> users:Vector<User> psa_type:flags.1?string psa_message:flags.2?string = help.PromoData;
+
+videoSize#de33b094 flags:# type:string w:int h:int size:int video_start_ts:flags.0?double = VideoSize;
+
+statsGroupTopPoster#9d04af9b user_id:long messages:int avg_chars:int = StatsGroupTopPoster;
+
+statsGroupTopAdmin#d7584c87 user_id:long deleted:int kicked:int banned:int = StatsGroupTopAdmin;
+
+statsGroupTopInviter#535f779d user_id:long invitations:int = StatsGroupTopInviter;
+
+stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector<StatsGroupTopPoster> top_admins:Vector<StatsGroupTopAdmin> top_inviters:Vector<StatsGroupTopInviter> users:Vector<User> = stats.MegagroupStats;
+
+globalPrivacySettings#bea2f424 flags:# archive_and_mute_new_noncontact_peers:flags.0?Bool = GlobalPrivacySettings;
+
+help.countryCode#4203c5ef flags:# country_code:string prefixes:flags.0?Vector<string> patterns:flags.1?Vector<string> = help.CountryCode;
+
+help.country#c3878e23 flags:# hidden:flags.0?true iso2:string default_name:string name:flags.1?string country_codes:Vector<help.CountryCode> = help.Country;
+
+help.countriesListNotModified#93cc1f32 = help.CountriesList;
+help.countriesList#87d0759e countries:Vector<help.Country> hash:int = help.CountriesList;
+
+messageViews#455b853d flags:# views:flags.0?int forwards:flags.1?int replies:flags.2?MessageReplies = MessageViews;
+
+messages.messageViews#b6c4f543 views:Vector<MessageViews> chats:Vector<Chat> users:Vector<User> = messages.MessageViews;
+
+messages.discussionMessage#a6341782 flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
+
+messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader;
+
+messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector<Peer> channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies;
+
+peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
+
+stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
+
+groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall;
+groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall;
+
+inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
+
+groupCallParticipant#eba636fe flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo = GroupCallParticipant;
+
+phone.groupCall#9e727aad call:GroupCall participants:Vector<GroupCallParticipant> participants_next_offset:string chats:Vector<Chat> users:Vector<User> = phone.GroupCall;
+
+phone.groupParticipants#f47751b6 count:int participants:Vector<GroupCallParticipant> next_offset:string chats:Vector<Chat> users:Vector<User> version:int = phone.GroupParticipants;
+
+inlineQueryPeerTypeSameBotPM#3081ed9d = InlineQueryPeerType;
+inlineQueryPeerTypePM#833c0fac = InlineQueryPeerType;
+inlineQueryPeerTypeChat#d766c50a = InlineQueryPeerType;
+inlineQueryPeerTypeMegagroup#5ec4be43 = InlineQueryPeerType;
+inlineQueryPeerTypeBroadcast#6334ee9a = InlineQueryPeerType;
+
+messages.historyImport#1662af0b id:long = messages.HistoryImport;
+
+messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true title:flags.2?string = messages.HistoryImportParsed;
+
+messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector<int> = messages.AffectedFoundMessages;
+
+chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter;
+
+messages.exportedChatInvites#bdc62dcc count:int invites:Vector<ExportedChatInvite> users:Vector<User> = messages.ExportedChatInvites;
+
+messages.exportedChatInvite#1871be50 invite:ExportedChatInvite users:Vector<User> = messages.ExportedChatInvite;
+messages.exportedChatInviteReplaced#222600ef invite:ExportedChatInvite new_invite:ExportedChatInvite users:Vector<User> = messages.ExportedChatInvite;
+
+messages.chatInviteImporters#81b6b00a count:int importers:Vector<ChatInviteImporter> users:Vector<User> = messages.ChatInviteImporters;
+
+chatAdminWithInvites#f2ecef23 admin_id:long invites_count:int revoked_invites_count:int = ChatAdminWithInvites;
+
+messages.chatAdminsWithInvites#b69b72d7 admins:Vector<ChatAdminWithInvites> users:Vector<User> = messages.ChatAdminsWithInvites;
+
+messages.checkedHistoryImportPeer#a24de717 confirm_text:string = messages.CheckedHistoryImportPeer;
+
+phone.joinAsPeers#afe5623f peers:Vector<Peer> chats:Vector<Chat> users:Vector<User> = phone.JoinAsPeers;
+
+phone.exportedGroupCallInvite#204bd158 link:string = phone.ExportedGroupCallInvite;
+
+groupCallParticipantVideoSourceGroup#dcb118b7 semantics:string sources:Vector<int> = GroupCallParticipantVideoSourceGroup;
+
+groupCallParticipantVideo#67753ac8 flags:# paused:flags.0?true endpoint:string source_groups:Vector<GroupCallParticipantVideoSourceGroup> audio_source:flags.1?int = GroupCallParticipantVideo;
+
+stickers.suggestedShortName#85fea03f short_name:string = stickers.SuggestedShortName;
+
+botCommandScopeDefault#2f6cb2ab = BotCommandScope;
+botCommandScopeUsers#3c4f04d8 = BotCommandScope;
+botCommandScopeChats#6fe1a881 = BotCommandScope;
+botCommandScopeChatAdmins#b9aa606a = BotCommandScope;
+botCommandScopePeer#db9d897d peer:InputPeer = BotCommandScope;
+botCommandScopePeerAdmins#3fd863d1 peer:InputPeer = BotCommandScope;
+botCommandScopePeerUser#a1321f3 peer:InputPeer user_id:InputUser = BotCommandScope;
+
+account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordResult;
+account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
+account.resetPasswordOk#e926d63e = account.ResetPasswordResult;
+
+sponsoredMessage#3a836df8 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector<MessageEntity> = SponsoredMessage;
+
+messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
+messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages;
+
+searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod;
+
+messages.searchResultsCalendar#147ee23c flags:# inexact:flags.0?true count:int min_date:int min_msg_id:int offset_id_offset:flags.1?int periods:Vector<SearchResultsCalendarPeriod> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SearchResultsCalendar;
+
+searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosition;
+
+messages.searchResultsPositions#53b22baf count:int positions:Vector<SearchResultsPosition> = messages.SearchResultsPositions;
+
+channels.sendAsPeers#f496b0c6 peers:Vector<SendAsPeer> chats:Vector<Chat> users:Vector<User> = channels.SendAsPeers;
+
+users.userFull#3b6d152e full_user:UserFull chats:Vector<Chat> users:Vector<User> = users.UserFull;
+
+messages.peerSettings#6880b94d settings:PeerSettings chats:Vector<Chat> users:Vector<User> = messages.PeerSettings;
+
+auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut;
+
+reactionCount#a3d1cb80 flags:# chosen_order:flags.0?int reaction:Reaction count:int = ReactionCount;
+
+messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector<ReactionCount> recent_reactions:flags.1?Vector<MessagePeerReaction> = MessageReactions;
+
+messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector<MessagePeerReaction> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = messages.MessageReactionsList;
+
+availableReaction#c077ec01 flags:# inactive:flags.0?true premium:flags.2?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction;
+
+messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions;
+messages.availableReactions#768e3aad hash:int reactions:Vector<AvailableReaction> = messages.AvailableReactions;
+
+messages.translateNoResult#67ca4737 = messages.TranslatedText;
+messages.translateResultText#a214f7d0 text:string = messages.TranslatedText;
+
+messagePeerReaction#b156fe9c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:Reaction = MessagePeerReaction;
+
+groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel;
+
+phone.groupCallStreamChannels#d0e482b2 channels:Vector<GroupCallStreamChannel> = phone.GroupCallStreamChannels;
+
+phone.groupCallStreamRtmpUrl#2dbf3432 url:string key:string = phone.GroupCallStreamRtmpUrl;
+
+attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor;
+
+attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector<AttachMenuBotIconColor> = AttachMenuBotIcon;
+
+attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true bot_id:long short_name:string peer_types:Vector<AttachMenuPeerType> icons:Vector<AttachMenuBotIcon> = AttachMenuBot;
+
+attachMenuBotsNotModified#f1d88a5c = AttachMenuBots;
+attachMenuBots#3c4301c0 hash:long bots:Vector<AttachMenuBot> users:Vector<User> = AttachMenuBots;
+
+attachMenuBotsBot#93bf667f bot:AttachMenuBot users:Vector<User> = AttachMenuBotsBot;
+
+webViewResultUrl#c14557c query_id:long url:string = WebViewResult;
+
+simpleWebViewResultUrl#882f76bb url:string = SimpleWebViewResult;
+
+webViewMessageSent#c94511c flags:# msg_id:flags.0?InputBotInlineMessageID = WebViewMessageSent;
+
+botMenuButtonDefault#7533a588 = BotMenuButton;
+botMenuButtonCommands#4258c205 = BotMenuButton;
+botMenuButton#c7b57ce6 text:string url:string = BotMenuButton;
+
+account.savedRingtonesNotModified#fbf6e8b1 = account.SavedRingtones;
+account.savedRingtones#c1e92cc5 hash:long ringtones:Vector<Document> = account.SavedRingtones;
+
+notificationSoundDefault#97e8bebe = NotificationSound;
+notificationSoundNone#6f0c34df = NotificationSound;
+notificationSoundLocal#830b9ae4 title:string data:string = NotificationSound;
+notificationSoundRingtone#ff6c8049 id:long = NotificationSound;
+
+account.savedRingtone#b7263f6d = account.SavedRingtone;
+account.savedRingtoneConverted#1f307eb7 document:Document = account.SavedRingtone;
+
+attachMenuPeerTypeSameBotPM#7d6be90e = AttachMenuPeerType;
+attachMenuPeerTypeBotPM#c32bfa1a = AttachMenuPeerType;
+attachMenuPeerTypePM#f146d31f = AttachMenuPeerType;
+attachMenuPeerTypeChat#509113f = AttachMenuPeerType;
+attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType;
+
+inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice;
+inputInvoiceSlug#c326caef slug:string = InputInvoice;
+
+payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice;
+
+messages.transcribedAudio#93752c52 flags:# pending:flags.0?true transcription_id:long text:string = messages.TranscribedAudio;
+
+help.premiumPromo#5334759c status_text:string status_entities:Vector<MessageEntity> video_sections:Vector<string> videos:Vector<Document> period_options:Vector<PremiumSubscriptionOption> users:Vector<User> = help.PremiumPromo;
+
+inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true = InputStorePaymentPurpose;
+inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose;
+
+premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption;
+
+paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod;
+
+emojiStatusEmpty#2de11aae = EmojiStatus;
+emojiStatus#929b619d document_id:long = EmojiStatus;
+emojiStatusUntil#fa30a8c7 document_id:long until:int = EmojiStatus;
+
+account.emojiStatusesNotModified#d08ce645 = account.EmojiStatuses;
+account.emojiStatuses#90c467d1 hash:long statuses:Vector<EmojiStatus> = account.EmojiStatuses;
+
+reactionEmpty#79f5d419 = Reaction;
+reactionEmoji#1b2286b8 emoticon:string = Reaction;
+reactionCustomEmoji#8935fc73 document_id:long = Reaction;
+
+chatReactionsNone#eafc32bc = ChatReactions;
+chatReactionsAll#52928bca flags:# allow_custom:flags.0?true = ChatReactions;
+chatReactionsSome#661d4037 reactions:Vector<Reaction> = ChatReactions;
+
+messages.reactionsNotModified#b06fdbdf = messages.Reactions;
+messages.reactions#eafdf716 hash:long reactions:Vector<Reaction> = messages.Reactions;
+
+emailVerifyPurposeLoginSetup#4345be73 phone_number:string phone_code_hash:string = EmailVerifyPurpose;
+emailVerifyPurposeLoginChange#527d22eb = EmailVerifyPurpose;
+emailVerifyPurposePassport#bbf51685 = EmailVerifyPurpose;
+
+emailVerificationCode#922e55a9 code:string = EmailVerification;
+emailVerificationGoogle#db909ec2 token:string = EmailVerification;
+emailVerificationApple#96d074fd token:string = EmailVerification;
+
+account.emailVerified#2b96cd1b email:string = account.EmailVerified;
+account.emailVerifiedLogin#e1bb0d61 email:string sent_code:auth.SentCode = account.EmailVerified;
+
+premiumSubscriptionOption#b6f11ebe flags:# current:flags.1?true can_purchase_upgrade:flags.2?true months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption;
+
+sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer;
+
+messageExtendedMediaPreview#ad628cc8 flags:# w:flags.0?int h:flags.0?int thumb:flags.1?PhotoSize video_duration:flags.2?int = MessageExtendedMedia;
+messageExtendedMedia#ee479c64 media:MessageMedia = MessageExtendedMedia;
+
+stickerKeyword#fcfeb29c document_id:long keyword:Vector<string> = StickerKeyword;
+
+username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username;
+
+forumTopicDeleted#23f109b id:int = ForumTopic;
+forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic;
+
+messages.forumTopics#367617d3 flags:# order_by_create_date:flags.0?true count:int topics:Vector<ForumTopic> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> pts:int = messages.ForumTopics;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
-initConnection#c7481da6 {X:Type} api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string query:!X = X;
+initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
+invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X;
+invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X;
-auth.checkPhone#6fe51dfb phone_number:string = auth.CheckedPhone;
-auth.sendCode#86aef0ec flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool api_id:int api_hash:string = auth.SentCode;
-auth.signUp#1b067634 phone_number:string phone_code_hash:string phone_code:string first_name:string last_name:string = auth.Authorization;
-auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization;
-auth.logOut#5717da40 = Bool;
+auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode;
+auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization;
+auth.signIn#8d52a951 flags:# phone_number:string phone_code_hash:string phone_code:flags.0?string email_verification:flags.1?EmailVerification = auth.Authorization;
+auth.logOut#3e72ba19 = auth.LoggedOut;
auth.resetAuthorizations#9fab0d1a = Bool;
-auth.sendInvites#771c1d97 phone_numbers:Vector<string> message:string = Bool;
auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization;
-auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization;
+auth.importAuthorization#a57a7dad id:long bytes:bytes = auth.Authorization;
auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool;
auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization;
-auth.checkPassword#a63011e password_hash:bytes = auth.Authorization;
+auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization;
auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery;
-auth.recoverPassword#4ea56e92 code:string = auth.Authorization;
+auth.recoverPassword#37096c70 flags:# code:string new_settings:flags.0?account.PasswordInputSettings = auth.Authorization;
auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode;
auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool;
auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector<long> = Bool;
+auth.exportLoginToken#b7e085fe api_id:int api_hash:string except_ids:Vector<long> = auth.LoginToken;
+auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken;
+auth.acceptLoginToken#e894ad4d token:bytes = Authorization;
+auth.checkRecoveryPassword#d36bf79 code:string = Bool;
-account.registerDevice#5cbea590 token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector<int> = Bool;
-account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector<int> = Bool;
+account.registerDevice#ec86017a flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector<long> = Bool;
+account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector<long> = Bool;
account.updateNotifySettings#84be5b93 peer:InputNotifyPeer settings:InputPeerNotifySettings = Bool;
account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings;
account.resetNotifySettings#db7e1747 = Bool;
account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User;
account.updateStatus#6628562c offline:Bool = Bool;
-account.getWallPapers#c04cfac2 = Vector<WallPaper>;
-account.reportPeer#ae189d5f peer:InputPeer reason:ReportReason = Bool;
+account.getWallPapers#7967d36 hash:long = account.WallPapers;
+account.reportPeer#c5ba3d86 peer:InputPeer reason:ReportReason message:string = Bool;
account.checkUsername#2714d86c username:string = Bool;
account.updateUsername#3e0bdd7c username:string = User;
account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules;
account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector<InputPrivacyRule> = account.PrivacyRules;
-account.deleteAccount#418d4e0b reason:string = Bool;
+account.deleteAccount#a2c0cf74 flags:# reason:string password:flags.0?InputCheckPasswordSRP = Bool;
account.getAccountTTL#8fc711d = AccountDaysTTL;
account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool;
-account.sendChangePhoneCode#8e57deb flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode;
+account.sendChangePhoneCode#82574ae5 phone_number:string settings:CodeSettings = auth.SentCode;
account.changePhone#70c32edb phone_number:string phone_code_hash:string phone_code:string = User;
account.updateDeviceLocked#38df3532 period:int = Bool;
account.getAuthorizations#e320c158 = account.Authorizations;
account.resetAuthorization#df77f3bc hash:long = Bool;
account.getPassword#548a30f5 = account.Password;
-account.getPasswordSettings#bc8d11bb current_password_hash:bytes = account.PasswordSettings;
-account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings:account.PasswordInputSettings = Bool;
-account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode;
+account.getPasswordSettings#9cd4eaf9 password:InputCheckPasswordSRP = account.PasswordSettings;
+account.updatePasswordSettings#a59b102f password:InputCheckPasswordSRP new_settings:account.PasswordInputSettings = Bool;
+account.sendConfirmPhoneCode#1b3faa88 hash:string settings:CodeSettings = auth.SentCode;
account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
-account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword;
+account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword;
account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
account.resetWebAuthorization#2d01b9ef hash:long = Bool;
account.resetWebAuthorizations#682d2594 = Bool;
+account.getAllSecureValues#b288bc7d = Vector<SecureValue>;
+account.getSecureValue#73665bc2 types:Vector<SecureValueType> = Vector<SecureValue>;
+account.saveSecureValue#899fe31d value:InputSecureValue secure_secret_id:long = SecureValue;
+account.deleteSecureValue#b880bc4b types:Vector<SecureValueType> = Bool;
+account.getAuthorizationForm#a929597a bot_id:long scope:string public_key:string = account.AuthorizationForm;
+account.acceptAuthorization#f3ed4c73 bot_id:long scope:string public_key:string value_hashes:Vector<SecureValueHash> credentials:SecureCredentialsEncrypted = Bool;
+account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode;
+account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
+account.sendVerifyEmailCode#98e037bb purpose:EmailVerifyPurpose email:string = account.SentEmailCode;
+account.verifyEmail#32da4cf purpose:EmailVerifyPurpose verification:EmailVerification = account.EmailVerified;
+account.initTakeoutSession#8ef3eab0 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?long = account.Takeout;
+account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool;
+account.confirmPasswordEmail#8fdf1920 code:string = Bool;
+account.resendPasswordEmail#7a7f2a15 = Bool;
+account.cancelPasswordEmail#c1cbd5b6 = Bool;
+account.getContactSignUpNotification#9f07c728 = Bool;
+account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
+account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates;
+account.getWallPaper#fc8ddbea wallpaper:InputWallPaper = WallPaper;
+account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper;
+account.saveWallPaper#6c5a5b37 wallpaper:InputWallPaper unsave:Bool settings:WallPaperSettings = Bool;
+account.installWallPaper#feed5769 wallpaper:InputWallPaper settings:WallPaperSettings = Bool;
+account.resetWallPapers#bb3b9804 = Bool;
+account.getAutoDownloadSettings#56da0b3f = account.AutoDownloadSettings;
+account.saveAutoDownloadSettings#76f36233 flags:# low:flags.0?true high:flags.1?true settings:AutoDownloadSettings = Bool;
+account.uploadTheme#1c3db333 flags:# file:InputFile thumb:flags.0?InputFile file_name:string mime_type:string = Document;
+account.createTheme#652e4400 flags:# slug:string title:string document:flags.2?InputDocument settings:flags.3?Vector<InputThemeSettings> = Theme;
+account.updateTheme#2bf40ccc flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?Vector<InputThemeSettings> = Theme;
+account.saveTheme#f257106c theme:InputTheme unsave:Bool = Bool;
+account.installTheme#c727bb3b flags:# dark:flags.0?true theme:flags.1?InputTheme format:flags.2?string base_theme:flags.3?BaseTheme = Bool;
+account.getTheme#3a5869ec format:string theme:InputTheme = Theme;
+account.getThemes#7206e458 format:string hash:long = account.Themes;
+account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
+account.getContentSettings#8b9b4dae = account.ContentSettings;
+account.getMultiWallPapers#65ad71dc wallpapers:Vector<InputWallPaper> = Vector<WallPaper>;
+account.getGlobalPrivacySettings#eb2b4cf6 = GlobalPrivacySettings;
+account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = GlobalPrivacySettings;
+account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool;
+account.resetPassword#9308ce1b = account.ResetPasswordResult;
+account.declinePasswordReset#4c9409f6 = Bool;
+account.getChatThemes#d638de89 hash:long = account.Themes;
+account.setAuthorizationTTL#bf899aa0 authorization_ttl_days:int = Bool;
+account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool;
+account.getSavedRingtones#e1902288 hash:long = account.SavedRingtones;
+account.saveRingtone#3dea5b03 id:InputDocument unsave:Bool = account.SavedRingtone;
+account.uploadRingtone#831a83a2 file:InputFile file_name:string mime_type:string = Document;
+account.updateEmojiStatus#fbd3de6b emoji_status:EmojiStatus = Bool;
+account.getDefaultEmojiStatuses#d6753386 hash:long = account.EmojiStatuses;
+account.getRecentEmojiStatuses#f578105 hash:long = account.EmojiStatuses;
+account.clearRecentEmojiStatuses#18201aae = Bool;
+account.reorderUsernames#ef500eab order:Vector<string> = Bool;
+account.toggleUsername#58d6b376 username:string active:Bool = Bool;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
-users.getFullUser#ca30a5b1 id:InputUser = UserFull;
+users.getFullUser#b60f5918 id:InputUser = users.UserFull;
+users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector<SecureValueError> = Bool;
+contacts.getContactIDs#7adc669d hash:long = Vector<int>;
contacts.getStatuses#c4a353ee = Vector<ContactStatus>;
-contacts.getContacts#c023849f hash:int = contacts.Contacts;
+contacts.getContacts#5dd69e12 hash:long = contacts.Contacts;
contacts.importContacts#2c800be5 contacts:Vector<InputContact> = contacts.ImportedContacts;
-contacts.deleteContact#8e953744 id:InputUser = contacts.Link;
-contacts.deleteContacts#59ab389e id:Vector<InputUser> = Bool;
-contacts.block#332b49fc id:InputUser = Bool;
-contacts.unblock#e54100bd id:InputUser = Bool;
+contacts.deleteContacts#96a0e00 id:Vector<InputUser> = Updates;
+contacts.deleteByPhones#1013fd9e phones:Vector<string> = Bool;
+contacts.block#68cc1411 id:InputPeer = Bool;
+contacts.unblock#bea65d50 id:InputPeer = Bool;
contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked;
-contacts.exportCard#84e53737 = Vector<int>;
-contacts.importCard#4fe196fe export_card:Vector<int> = User;
contacts.search#11f812d8 q:string limit:int = contacts.Found;
contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer;
-contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers;
+contacts.getTopPeers#973478b6 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:long = contacts.TopPeers;
contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool;
contacts.resetSaved#879537f1 = Bool;
+contacts.getSaved#82f1e39f = Vector<SavedContact>;
+contacts.toggleTopPeers#8514bdda enabled:Bool = Bool;
+contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates;
+contacts.acceptContact#f831a20f id:InputUser = Updates;
+contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates;
+contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_history:flags.1?true report_spam:flags.2?true msg_id:int = Updates;
+contacts.resolvePhone#8af94344 phone:string = contacts.ResolvedPeer;
messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages;
-messages.getDialogs#191ba9c5 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs;
-messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
-messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
+messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs;
+messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
+messages.search#a0fda762 flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
-messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true peer:InputPeer max_id:int = messages.AffectedHistory;
+messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
-messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool;
-messages.sendMessage#fa88427a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Updates;
-messages.sendMedia#b8d1262b flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Updates;
-messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer = Updates;
+messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool;
+messages.sendMessage#1cc20387 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.sendMedia#7547c966 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.forwardMessages#c661bbc4 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.reportSpam#cf1592db peer:InputPeer = Bool;
-messages.hideReportSpam#a8f1709b peer:InputPeer = Bool;
-messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings;
-messages.report#bd82b658 peer:InputPeer id:Vector<int> reason:ReportReason = Bool;
-messages.getChats#3c6aa187 id:Vector<int> = messages.Chats;
-messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull;
-messages.editChatTitle#dc452855 chat_id:int title:string = Updates;
-messages.editChatPhoto#ca4c79d8 chat_id:int photo:InputChatPhoto = Updates;
-messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Updates;
-messages.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates;
+messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings;
+messages.report#8953ab4e peer:InputPeer id:Vector<int> reason:ReportReason message:string = Bool;
+messages.getChats#49e9528f id:Vector<long> = messages.Chats;
+messages.getFullChat#aeb00b34 chat_id:long = messages.ChatFull;
+messages.editChatTitle#73783ffd chat_id:long title:string = Updates;
+messages.editChatPhoto#35ddd674 chat_id:long photo:InputChatPhoto = Updates;
+messages.addChatUser#f24753e3 chat_id:long user_id:InputUser fwd_limit:int = Updates;
+messages.deleteChatUser#a2185cab flags:# revoke_history:flags.0?true chat_id:long user_id:InputUser = Updates;
messages.createChat#9cb126e users:Vector<InputUser> title:string = Updates;
messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig;
messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat;
messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat;
-messages.discardEncryption#edd923c5 chat_id:int = Bool;
+messages.discardEncryption#f393aea0 flags:# delete_history:flags.0?true chat_id:int = Bool;
messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool;
messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool;
-messages.sendEncrypted#a9776773 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage;
-messages.sendEncryptedFile#9a901b66 peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage;
+messages.sendEncrypted#44fa7a15 flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage;
+messages.sendEncryptedFile#5559481d flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage;
messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage;
messages.receivedQueue#55a5bb66 max_qts:int = Vector<long>;
messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool;
messages.readMessageContents#36a73f77 id:Vector<int> = messages.AffectedMessages;
-messages.getStickers#85cb5182 flags:# exclude_featured:flags.0?true emoticon:string hash:string = messages.Stickers;
-messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers;
+messages.getStickers#d5a5d3a1 emoticon:string hash:long = messages.Stickers;
+messages.getAllStickers#b8a0a1a8 hash:long = messages.AllStickers;
messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector<MessageEntity> = MessageMedia;
-messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite;
+messages.exportChatInvite#a02ce5d5 flags:# legacy_revoke_permanent:flags.2?true request_needed:flags.3?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int title:flags.4?string = ExportedChatInvite;
messages.checkChatInvite#3eadb1bb hash:string = ChatInvite;
messages.importChatInvite#6c50051c hash:string = Updates;
-messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
+messages.getStickerSet#c8a0ec74 stickerset:InputStickerSet hash:int = messages.StickerSet;
messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult;
messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool;
messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates;
-messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector<int> increment:Bool = Vector<int>;
-messages.toggleChatAdmins#ec8bd9e1 chat_id:int enabled:Bool = Updates;
-messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool;
-messages.migrateChat#15a3b8e3 chat_id:int = Updates;
-messages.searchGlobal#9e3cacb0 q:string offset_date:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
-messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector<long> = Bool;
-messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document;
-messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs;
-messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs;
+messages.getMessagesViews#5784d3e1 peer:InputPeer id:Vector<int> increment:Bool = messages.MessageViews;
+messages.editChatAdmin#a85bd1c2 chat_id:long user_id:InputUser is_admin:Bool = Bool;
+messages.migrateChat#a2875319 chat_id:long = Updates;
+messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
+messages.reorderStickerSets#78337739 flags:# masks:flags.0?true emojis:flags.1?true order:Vector<long> = Bool;
+messages.getDocumentByHash#b1f2061f sha256:bytes size:long mime_type:string = Document;
+messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs;
messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool;
messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults;
messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector<InputBotInlineResult> cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool;
-messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates;
+messages.sendInlineBotResult#d3fbdccb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
-messages.editMessage#5d1b8dd flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Updates;
-messages.editInlineBotMessage#b0e08243 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Bool;
-messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer;
+messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.15?int = Updates;
+messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Bool;
+messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer;
messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
messages.getPeerDialogs#e470bcfd peers:Vector<InputDialogPeer> = messages.PeerDialogs;
-messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector<MessageEntity> = Bool;
+messages.saveDraft#b4331e3f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int top_msg_id:flags.2?int peer:InputPeer message:string entities:flags.3?Vector<MessageEntity> = Bool;
messages.getAllDrafts#6a3f8d65 = Updates;
-messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers;
+messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers;
messages.readFeaturedStickers#5b118126 id:Vector<long> = Bool;
-messages.getRecentStickers#5ea192c9 flags:# attached:flags.0?true hash:int = messages.RecentStickers;
+messages.getRecentStickers#9da9403b flags:# attached:flags.0?true hash:long = messages.RecentStickers;
messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool;
messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool;
-messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true offset_id:long limit:int = messages.ArchivedStickers;
-messages.getMaskStickers#65b8c79f hash:int = messages.AllStickers;
+messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true emojis:flags.1?true offset_id:long limit:int = messages.ArchivedStickers;
+messages.getMaskStickers#640f82b8 hash:long = messages.AllStickers;
messages.getAttachedStickers#cc5b67cc media:InputStickeredMedia = Vector<StickerSetCovered>;
messages.setGameScore#8ef8ecc0 flags:# edit_message:flags.0?true force:flags.1?true peer:InputPeer id:int user_id:InputUser score:int = Updates;
messages.setInlineGameScore#15ad9f64 flags:# edit_message:flags.0?true force:flags.1?true id:InputBotInlineMessageID user_id:InputUser score:int = Bool;
messages.getGameHighScores#e822649d peer:InputPeer id:int user_id:InputUser = messages.HighScores;
messages.getInlineGameHighScores#f635e1b id:InputBotInlineMessageID user_id:InputUser = messages.HighScores;
-messages.getCommonChats#d0a48c4 user_id:InputUser max_id:int limit:int = messages.Chats;
-messages.getAllChats#eba80ff0 except_ids:Vector<int> = messages.Chats;
+messages.getCommonChats#e40ca104 user_id:InputUser max_id:long limit:int = messages.Chats;
+messages.getAllChats#875f74be except_ids:Vector<long> = messages.Chats;
messages.getWebPage#32ca8f91 url:string hash:int = WebPage;
messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
-messages.reorderPinnedDialogs#5b51d63f flags:# force:flags.0?true order:Vector<InputDialogPeer> = Bool;
-messages.getPinnedDialogs#e254d64e = messages.PeerDialogs;
+messages.reorderPinnedDialogs#3b1adf37 flags:# force:flags.0?true folder_id:int order:Vector<InputDialogPeer> = Bool;
+messages.getPinnedDialogs#d6b94df2 folder_id:int = messages.PeerDialogs;
messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = Bool;
messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool;
messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia;
messages.sendScreenshotNotification#c97df020 peer:InputPeer reply_to_msg_id:int random_id:long = Updates;
-messages.getFavedStickers#21ce0b0e hash:int = messages.FavedStickers;
+messages.getFavedStickers#4f1aaa9 hash:long = messages.FavedStickers;
messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool;
-messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
-messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory;
-messages.getRecentLocations#bbc45b09 peer:InputPeer limit:int hash:int = messages.Messages;
-messages.sendMultiMedia#2095512f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector<InputSingleMedia> = Updates;
+messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
+messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages;
+messages.sendMultiMedia#b6f11a1c flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int multi_media:Vector<InputSingleMedia> schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
-messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:string hash:int = messages.FoundStickerSets;
+messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets;
+messages.getSplitRanges#1cff7e08 = Vector<MessageRange>;
+messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool;
+messages.getDialogUnreadMarks#22e24e22 = Vector<DialogPeer>;
+messages.clearAllDrafts#7e58ee9c = Bool;
+messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1?true pm_oneside:flags.2?true peer:InputPeer id:int = Updates;
+messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector<bytes> = Updates;
+messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates;
+messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
+messages.editChatAbout#def60797 peer:InputPeer about:string = Bool;
+messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates;
+messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference;
+messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = EmojiKeywordsDifference;
+messages.getEmojiKeywordsLanguages#4e9963b2 lang_codes:Vector<string> = Vector<EmojiLanguage>;
+messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL;
+messages.getSearchCounters#ae7cc1 flags:# peer:InputPeer top_msg_id:flags.0?int filters:Vector<MessagesFilter> = Vector<messages.SearchCounter>;
+messages.requestUrlAuth#198fb446 flags:# peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult;
+messages.acceptUrlAuth#b12c7125 flags:# write_allowed:flags.0?true peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult;
+messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool;
+messages.getScheduledHistory#f516760b peer:InputPeer hash:long = messages.Messages;
+messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector<int> = messages.Messages;
+messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
+messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates;
+messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList;
+messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector<InputStickerSet> = Bool;
+messages.getDialogFilters#f19ed96d = Vector<DialogFilter>;
+messages.getSuggestedDialogFilters#a29cd42c = Vector<DialogFilterSuggested>;
+messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool;
+messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
+messages.getOldFeaturedStickers#7ed094a1 offset:int limit:int hash:long = messages.FeaturedStickers;
+messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
+messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
+messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
+messages.unpinAllMessages#ee22b9a8 flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
+messages.deleteChat#5bd0ee50 chat_id:long = Bool;
+messages.deletePhoneCallHistory#f9cbe409 flags:# revoke:flags.0?true = messages.AffectedFoundMessages;
+messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImportParsed;
+messages.initHistoryImport#34090c3b peer:InputPeer file:InputFile media_count:int = messages.HistoryImport;
+messages.uploadImportedMedia#2a862092 peer:InputPeer import_id:long file_name:string media:InputMedia = MessageMedia;
+messages.startHistoryImport#b43df344 peer:InputPeer import_id:long = Bool;
+messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites;
+messages.getExportedChatInvite#73746f5c peer:InputPeer link:string = messages.ExportedChatInvite;
+messages.editExportedChatInvite#bdca2f75 flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int request_needed:flags.3?Bool title:flags.4?string = messages.ExportedChatInvite;
+messages.deleteRevokedExportedChatInvites#56987bd5 peer:InputPeer admin_id:InputUser = Bool;
+messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool;
+messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithInvites;
+messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true peer:InputPeer link:flags.1?string q:flags.2?string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters;
+messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates;
+messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer;
+messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates;
+messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector<long>;
+messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar;
+messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions;
+messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates;
+messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates;
+messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates;
+messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
+messages.sendReaction#d30d78d4 flags:# big:flags.1?true add_to_recent:flags.2?true peer:InputPeer msg_id:int reaction:flags.0?Vector<Reaction> = Updates;
+messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector<int> = Updates;
+messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = messages.MessageReactionsList;
+messages.setChatAvailableReactions#feb16771 peer:InputPeer available_reactions:ChatReactions = Updates;
+messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
+messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool;
+messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText;
+messages.getUnreadReactions#3223495b flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+messages.readReactions#54aa7f8e flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
+messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages;
+messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
+messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot;
+messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool;
+messages.requestWebView#178b480b flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = WebViewResult;
+messages.prolongWebView#7ff34309 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = Bool;
+messages.requestSimpleWebView#299bec8e flags:# bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult;
+messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent;
+messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates;
+messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio;
+messages.rateTranscribedAudio#7f1d072f peer:InputPeer msg_id:int transcription_id:long good:Bool = Bool;
+messages.getCustomEmojiDocuments#d9ab0f54 document_id:Vector<long> = Vector<Document>;
+messages.getEmojiStickers#fbfca18f hash:long = messages.AllStickers;
+messages.getFeaturedEmojiStickers#ecf6736 hash:long = messages.FeaturedStickers;
+messages.reportReaction#3f64c076 peer:InputPeer id:int reaction_peer:InputPeer = Bool;
+messages.getTopReactions#bb8125ba limit:int hash:long = messages.Reactions;
+messages.getRecentReactions#39461db2 limit:int hash:long = messages.Reactions;
+messages.clearRecentReactions#9dfeefb4 = Bool;
+messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector<int> = Updates;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
-photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto;
-photos.uploadProfilePhoto#4f32c098 file:InputFile = photos.Photo;
+photos.updateProfilePhoto#72d4742c id:InputPhoto = photos.Photo;
+photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo;
photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;
-upload.getFile#e3a6cfb5 location:InputFileLocation offset:int limit:int = upload.File;
+upload.getFile#be5335be flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:long limit:int = upload.File;
upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool;
upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile;
-upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile;
+upload.getCdnFile#395f69da file_token:bytes offset:long limit:int = upload.CdnFile;
upload.reuploadCdnFile#9b2754a8 file_token:bytes request_token:bytes = Vector<FileHash>;
-upload.getCdnFileHashes#4da54231 file_token:bytes offset:int = Vector<FileHash>;
-upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector<FileHash>;
+upload.getCdnFileHashes#91dc3f31 file_token:bytes offset:long = Vector<FileHash>;
+upload.getFileHashes#9156982a location:InputFileLocation offset:long = Vector<FileHash>;
help.getConfig#c4f9186b = Config;
help.getNearestDc#1fb33026 = NearestDc;
-help.getAppUpdate#ae2de196 = help.AppUpdate;
-help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
+help.getAppUpdate#522d5a7d source:string = help.AppUpdate;
help.getInviteText#4d392343 = help.InviteText;
help.getSupport#9cdf08cd = help.Support;
help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
-help.getTermsOfService#350170f3 = help.TermsOfService;
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
help.getCdnConfig#52029342 = CdnConfig;
help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
+help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate;
+help.acceptTermsOfService#ee72f79a id:DataJSON = Bool;
+help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo;
+help.getAppConfig#98914110 = JSONValue;
+help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
+help.getPassportConfig#c661ad08 hash:int = help.PassportConfig;
+help.getSupportName#d360e72c = help.SupportName;
+help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo;
+help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector<MessageEntity> = help.UserInfo;
+help.getPromoData#c0977421 = help.PromoData;
+help.hidePromoData#1e251c95 peer:InputPeer = Bool;
+help.dismissSuggestion#f50dbaa1 peer:InputPeer suggestion:string = Bool;
+help.getCountriesList#735787a8 lang_code:string hash:int = help.CountriesList;
+help.getPremiumPromo#b81b93d4 = help.PremiumPromo;
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
-channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory;
-channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector<int> = Bool;
+channels.reportSpam#f44a8315 channel:InputChannel participant:InputPeer id:Vector<int> = Bool;
channels.getMessages#ad8c9a23 channel:InputChannel id:Vector<InputMessage> = messages.Messages;
-channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants;
-channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
+channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:long = channels.ChannelParticipants;
+channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant;
channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
-channels.createChannel#f4893d7f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string = Updates;
-channels.editAbout#13e27f1e channel:InputChannel about:string = Bool;
-channels.editAdmin#20b88214 channel:InputChannel user_id:InputUser admin_rights:ChannelAdminRights = Updates;
+channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates;
+channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates;
channels.editTitle#566decd0 channel:InputChannel title:string = Updates;
channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates;
channels.checkUsername#10e6bd2c channel:InputChannel username:string = Bool;
@@ -1061,45 +1837,114 @@ channels.updateUsername#3514b3de channel:InputChannel username:string = Bool;
channels.joinChannel#24b524c5 channel:InputChannel = Updates;
channels.leaveChannel#f836aa95 channel:InputChannel = Updates;
channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector<InputUser> = Updates;
-channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite;
channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
-channels.toggleInvites#49609307 channel:InputChannel enabled:Bool = Updates;
-channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink;
+channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink;
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
-channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates;
-channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats;
-channels.editBanned#bfd915cd channel:InputChannel user_id:InputUser banned_rights:ChannelBannedRights = Updates;
+channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats;
+channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_rights:ChatBannedRights = Updates;
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
-channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool;
+channels.deleteHistory#9baa9647 flags:# for_everyone:flags.0?true channel:InputChannel max_id:int = Updates;
channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
+channels.getLeftChannels#8341ecc0 offset:int = messages.Chats;
+channels.getGroupsForDiscussion#f5dad378 = messages.Chats;
+channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel = Bool;
+channels.editCreator#8f38cd1f channel:InputChannel user_id:InputUser password:InputCheckPasswordSRP = Updates;
+channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint address:string = Bool;
+channels.toggleSlowMode#edd49ef0 channel:InputChannel seconds:int = Updates;
+channels.getInactiveChannels#11e831ee = messages.InactiveChats;
+channels.convertToGigagroup#b290c69 channel:InputChannel = Updates;
+channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bool;
+channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages;
+channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers;
+channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory;
+channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates;
+channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
+channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector<string> = Bool;
+channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = Bool;
+channels.deactivateAllUsernames#a245dd3 channel:InputChannel = Bool;
+channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates;
+channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string icon_color:flags.0?int icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates;
+channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics;
+channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector<int> = messages.ForumTopics;
+channels.editForumTopic#6c883e2d flags:# channel:InputChannel topic_id:int title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool = Updates;
+channels.updatePinnedForumTopic#6c2d9026 channel:InputChannel topic_id:int pinned:Bool = Updates;
+channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messages.AffectedHistory;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
-
-payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm;
-payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt;
-payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
-payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult;
+bots.setBotCommands#517165a scope:BotCommandScope lang_code:string commands:Vector<BotCommand> = Bool;
+bots.resetBotCommands#3d8de0f9 scope:BotCommandScope lang_code:string = Bool;
+bots.getBotCommands#e34c0dd6 scope:BotCommandScope lang_code:string = Vector<BotCommand>;
+bots.setBotMenuButton#4504d54f user_id:InputUser button:BotMenuButton = Bool;
+bots.getBotMenuButton#9c60eb28 user_id:InputUser = BotMenuButton;
+bots.setBotBroadcastDefaultAdminRights#788464e1 admin_rights:ChatAdminRights = Bool;
+bots.setBotGroupDefaultAdminRights#925ec9ea admin_rights:ChatAdminRights = Bool;
+
+payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
+payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
+payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
+payments.sendPaymentForm#2d03522f flags:# form_id:long invoice:InputInvoice requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult;
payments.getSavedInfo#227d824b = payments.SavedInfo;
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
+payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
+payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice;
+payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaymentPurpose = Updates;
+payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates;
+payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool;
-stickers.createStickerSet#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector<InputStickerSetItem> = messages.StickerSet;
+stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
+stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet;
+stickers.checkShortName#284b3639 short_name:string = Bool;
+stickers.suggestShortName#4dafc503 title:string = stickers.SuggestedShortName;
phone.getCallConfig#55451fa9 = DataJSON;
-phone.requestCall#5b95b3d4 user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
+phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool;
-phone.discardCall#78d413a6 peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates;
-phone.setCallRating#1c536a34 peer:InputPhoneCall rating:int comment:string = Updates;
+phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates;
+phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
-
-langpack.getLangPack#9ab5c58e lang_code:string = LangPackDifference;
-langpack.getStrings#2e1ee318 lang_code:string keys:Vector<string> = Vector<LangPackString>;
-langpack.getDifference#b2e4d7d from_version:int = LangPackDifference;
-langpack.getLanguages#800fd57d = Vector<LangPackLanguage>;
+phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
+phone.createGroupCall#48cdc6d8 flags:# rtmp_stream:flags.2?true peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates;
+phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates;
+phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates;
+phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector<InputUser> = Updates;
+phone.discardGroupCall#7a777135 call:InputGroupCall = Updates;
+phone.toggleGroupCallSettings#74bbb43d flags:# reset_invite_hash:flags.1?true call:InputGroupCall join_muted:flags.0?Bool = Updates;
+phone.getGroupCall#41845db call:InputGroupCall limit:int = phone.GroupCall;
+phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector<InputPeer> sources:Vector<int> offset:string limit:int = phone.GroupParticipants;
+phone.checkGroupCall#b59cf977 call:InputGroupCall sources:Vector<int> = Vector<int>;
+phone.toggleGroupCallRecord#f128c708 flags:# start:flags.0?true video:flags.2?true call:InputGroupCall title:flags.1?string video_portrait:flags.2?Bool = Updates;
+phone.editGroupCallParticipant#a5273abf flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_stopped:flags.3?Bool video_paused:flags.4?Bool presentation_paused:flags.5?Bool = Updates;
+phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates;
+phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers;
+phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite;
+phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates;
+phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates;
+phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool;
+phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates;
+phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates;
+phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels;
+phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl;
+phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool;
+
+langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
+langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
+langpack.getDifference#cd984aa5 lang_pack:string lang_code:string from_version:int = LangPackDifference;
+langpack.getLanguages#42c6978f lang_pack:string = Vector<LangPackLanguage>;
+langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage;
+
+folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
+folders.deleteFolder#1c295881 folder_id:int = Updates;
+
+stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats;
+stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
+stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats;
+stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
+stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
diff --git a/protocols/Telegram/tdlib/td/td/generate/scheme/telegram_api.tlo b/protocols/Telegram/tdlib/td/td/generate/scheme/telegram_api.tlo
deleted file mode 100644
index 6557f4c0fd..0000000000
--- a/protocols/Telegram/tdlib/td/td/generate/scheme/telegram_api.tlo
+++ /dev/null
Binary files differ
diff --git a/protocols/Telegram/tdlib/td/td/generate/scheme/update-tlo.sh b/protocols/Telegram/tdlib/td/td/generate/scheme/update-tlo.sh
deleted file mode 100644
index cfcb71f35c..0000000000
--- a/protocols/Telegram/tdlib/td/td/generate/scheme/update-tlo.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-cd $(dirname $0)
-tl-parser -e td_api.tlo td_api.tl
-tl-parser -e telegram_api.tlo telegram_api.tl
-tl-parser -e mtproto_api.tlo mtproto_api.tl
-tl-parser -e secret_api.tlo secret_api.tl
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/CMakeLists.txt b/protocols/Telegram/tdlib/td/td/generate/tl-parser/CMakeLists.txt
new file mode 100644
index 0000000000..6cf5331f4b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/CMakeLists.txt
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
+
+if (POLICY CMP0065)
+ # do not export symbols from executables
+ # affects compiler checks in project(), so must be set before it
+ cmake_policy(SET CMP0065 NEW)
+endif()
+
+project(tl-parser LANGUAGES C)
+
+set(SOURCES crc32.h crc32.c tlc.c tl-parser.c tl-parser.h tl-parser-tree.h tl-tl.h portable_endian.h)
+
+if (WIN32)
+ add_definitions("-D_CRT_SECURE_NO_WARNINGS")
+ list(APPEND SOURCES wgetopt.c wgetopt.h)
+ if (MSVC)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8 /wd4101 /wd4244 /wd4267")
+ endif()
+endif()
+
+add_executable(${PROJECT_NAME} ${SOURCES})
+
+if (NOT WIN32)
+ target_link_libraries(${PROJECT_NAME} PRIVATE m)
+endif()
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/LICENSE b/protocols/Telegram/tdlib/td/td/generate/tl-parser/LICENSE
new file mode 100644
index 0000000000..d159169d10
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 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.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) 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
+this service 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 make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. 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.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+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 Program or any portion
+of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+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 Program, 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 Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) 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; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, 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 executable. However, as a
+special exception, the source code 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.
+
+If distribution of executable or 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 counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program 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.
+
+ 5. 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 Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program 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 to
+this License.
+
+ 7. 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 Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program 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 Program.
+
+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.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program 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.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the 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 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 Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, 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
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
+
+ 12. 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 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.
+
+ 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
+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 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 2 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, 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.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision 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, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This 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.
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/crc32.c b/protocols/Telegram/tdlib/td/td/generate/tl-parser/crc32.c
new file mode 100644
index 0000000000..b7e02181b2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/crc32.c
@@ -0,0 +1,345 @@
+/*
+ This file is part of VK/KittenPHP-DB-Engine Library.
+
+ VK/KittenPHP-DB-Engine 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 of the License, or
+ (at your option) any later version.
+
+ VK/KittenPHP-DB-Engine 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 VK/KittenPHP-DB-Engine Library. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009-2012 Vkontakte Ltd
+ 2009-2012 Nikolai Durov
+ 2009-2012 Andrei Lopatin
+ 2012 Anton Maydell
+*/
+
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+
+#include "crc32.h"
+
+unsigned int crc32_table[256] =
+{
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+ 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+ 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+ 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+ 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+ 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+ 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+ 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+ 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+ 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+ 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+ 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+ 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+ 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+ 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+ 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+ 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+ 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+ 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+ 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+ 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+ 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+unsigned int crc32_table2[256] =
+{
+ 0x00000000, 0x191b3141, 0x32366282, 0x2b2d53c3,
+ 0x646cc504, 0x7d77f445, 0x565aa786, 0x4f4196c7,
+ 0xc8d98a08, 0xd1c2bb49, 0xfaefe88a, 0xe3f4d9cb,
+ 0xacb54f0c, 0xb5ae7e4d, 0x9e832d8e, 0x87981ccf,
+ 0x4ac21251, 0x53d92310, 0x78f470d3, 0x61ef4192,
+ 0x2eaed755, 0x37b5e614, 0x1c98b5d7, 0x05838496,
+ 0x821b9859, 0x9b00a918, 0xb02dfadb, 0xa936cb9a,
+ 0xe6775d5d, 0xff6c6c1c, 0xd4413fdf, 0xcd5a0e9e,
+ 0x958424a2, 0x8c9f15e3, 0xa7b24620, 0xbea97761,
+ 0xf1e8e1a6, 0xe8f3d0e7, 0xc3de8324, 0xdac5b265,
+ 0x5d5daeaa, 0x44469feb, 0x6f6bcc28, 0x7670fd69,
+ 0x39316bae, 0x202a5aef, 0x0b07092c, 0x121c386d,
+ 0xdf4636f3, 0xc65d07b2, 0xed705471, 0xf46b6530,
+ 0xbb2af3f7, 0xa231c2b6, 0x891c9175, 0x9007a034,
+ 0x179fbcfb, 0x0e848dba, 0x25a9de79, 0x3cb2ef38,
+ 0x73f379ff, 0x6ae848be, 0x41c51b7d, 0x58de2a3c,
+ 0xf0794f05, 0xe9627e44, 0xc24f2d87, 0xdb541cc6,
+ 0x94158a01, 0x8d0ebb40, 0xa623e883, 0xbf38d9c2,
+ 0x38a0c50d, 0x21bbf44c, 0x0a96a78f, 0x138d96ce,
+ 0x5ccc0009, 0x45d73148, 0x6efa628b, 0x77e153ca,
+ 0xbabb5d54, 0xa3a06c15, 0x888d3fd6, 0x91960e97,
+ 0xded79850, 0xc7cca911, 0xece1fad2, 0xf5facb93,
+ 0x7262d75c, 0x6b79e61d, 0x4054b5de, 0x594f849f,
+ 0x160e1258, 0x0f152319, 0x243870da, 0x3d23419b,
+ 0x65fd6ba7, 0x7ce65ae6, 0x57cb0925, 0x4ed03864,
+ 0x0191aea3, 0x188a9fe2, 0x33a7cc21, 0x2abcfd60,
+ 0xad24e1af, 0xb43fd0ee, 0x9f12832d, 0x8609b26c,
+ 0xc94824ab, 0xd05315ea, 0xfb7e4629, 0xe2657768,
+ 0x2f3f79f6, 0x362448b7, 0x1d091b74, 0x04122a35,
+ 0x4b53bcf2, 0x52488db3, 0x7965de70, 0x607eef31,
+ 0xe7e6f3fe, 0xfefdc2bf, 0xd5d0917c, 0xcccba03d,
+ 0x838a36fa, 0x9a9107bb, 0xb1bc5478, 0xa8a76539,
+ 0x3b83984b, 0x2298a90a, 0x09b5fac9, 0x10aecb88,
+ 0x5fef5d4f, 0x46f46c0e, 0x6dd93fcd, 0x74c20e8c,
+ 0xf35a1243, 0xea412302, 0xc16c70c1, 0xd8774180,
+ 0x9736d747, 0x8e2de606, 0xa500b5c5, 0xbc1b8484,
+ 0x71418a1a, 0x685abb5b, 0x4377e898, 0x5a6cd9d9,
+ 0x152d4f1e, 0x0c367e5f, 0x271b2d9c, 0x3e001cdd,
+ 0xb9980012, 0xa0833153, 0x8bae6290, 0x92b553d1,
+ 0xddf4c516, 0xc4eff457, 0xefc2a794, 0xf6d996d5,
+ 0xae07bce9, 0xb71c8da8, 0x9c31de6b, 0x852aef2a,
+ 0xca6b79ed, 0xd37048ac, 0xf85d1b6f, 0xe1462a2e,
+ 0x66de36e1, 0x7fc507a0, 0x54e85463, 0x4df36522,
+ 0x02b2f3e5, 0x1ba9c2a4, 0x30849167, 0x299fa026,
+ 0xe4c5aeb8, 0xfdde9ff9, 0xd6f3cc3a, 0xcfe8fd7b,
+ 0x80a96bbc, 0x99b25afd, 0xb29f093e, 0xab84387f,
+ 0x2c1c24b0, 0x350715f1, 0x1e2a4632, 0x07317773,
+ 0x4870e1b4, 0x516bd0f5, 0x7a468336, 0x635db277,
+ 0xcbfad74e, 0xd2e1e60f, 0xf9ccb5cc, 0xe0d7848d,
+ 0xaf96124a, 0xb68d230b, 0x9da070c8, 0x84bb4189,
+ 0x03235d46, 0x1a386c07, 0x31153fc4, 0x280e0e85,
+ 0x674f9842, 0x7e54a903, 0x5579fac0, 0x4c62cb81,
+ 0x8138c51f, 0x9823f45e, 0xb30ea79d, 0xaa1596dc,
+ 0xe554001b, 0xfc4f315a, 0xd7626299, 0xce7953d8,
+ 0x49e14f17, 0x50fa7e56, 0x7bd72d95, 0x62cc1cd4,
+ 0x2d8d8a13, 0x3496bb52, 0x1fbbe891, 0x06a0d9d0,
+ 0x5e7ef3ec, 0x4765c2ad, 0x6c48916e, 0x7553a02f,
+ 0x3a1236e8, 0x230907a9, 0x0824546a, 0x113f652b,
+ 0x96a779e4, 0x8fbc48a5, 0xa4911b66, 0xbd8a2a27,
+ 0xf2cbbce0, 0xebd08da1, 0xc0fdde62, 0xd9e6ef23,
+ 0x14bce1bd, 0x0da7d0fc, 0x268a833f, 0x3f91b27e,
+ 0x70d024b9, 0x69cb15f8, 0x42e6463b, 0x5bfd777a,
+ 0xdc656bb5, 0xc57e5af4, 0xee530937, 0xf7483876,
+ 0xb809aeb1, 0xa1129ff0, 0x8a3fcc33, 0x9324fd72,
+};
+
+unsigned int crc32_table1[256] =
+{
+ 0x00000000, 0x01c26a37, 0x0384d46e, 0x0246be59,
+ 0x0709a8dc, 0x06cbc2eb, 0x048d7cb2, 0x054f1685,
+ 0x0e1351b8, 0x0fd13b8f, 0x0d9785d6, 0x0c55efe1,
+ 0x091af964, 0x08d89353, 0x0a9e2d0a, 0x0b5c473d,
+ 0x1c26a370, 0x1de4c947, 0x1fa2771e, 0x1e601d29,
+ 0x1b2f0bac, 0x1aed619b, 0x18abdfc2, 0x1969b5f5,
+ 0x1235f2c8, 0x13f798ff, 0x11b126a6, 0x10734c91,
+ 0x153c5a14, 0x14fe3023, 0x16b88e7a, 0x177ae44d,
+ 0x384d46e0, 0x398f2cd7, 0x3bc9928e, 0x3a0bf8b9,
+ 0x3f44ee3c, 0x3e86840b, 0x3cc03a52, 0x3d025065,
+ 0x365e1758, 0x379c7d6f, 0x35dac336, 0x3418a901,
+ 0x3157bf84, 0x3095d5b3, 0x32d36bea, 0x331101dd,
+ 0x246be590, 0x25a98fa7, 0x27ef31fe, 0x262d5bc9,
+ 0x23624d4c, 0x22a0277b, 0x20e69922, 0x2124f315,
+ 0x2a78b428, 0x2bbade1f, 0x29fc6046, 0x283e0a71,
+ 0x2d711cf4, 0x2cb376c3, 0x2ef5c89a, 0x2f37a2ad,
+ 0x709a8dc0, 0x7158e7f7, 0x731e59ae, 0x72dc3399,
+ 0x7793251c, 0x76514f2b, 0x7417f172, 0x75d59b45,
+ 0x7e89dc78, 0x7f4bb64f, 0x7d0d0816, 0x7ccf6221,
+ 0x798074a4, 0x78421e93, 0x7a04a0ca, 0x7bc6cafd,
+ 0x6cbc2eb0, 0x6d7e4487, 0x6f38fade, 0x6efa90e9,
+ 0x6bb5866c, 0x6a77ec5b, 0x68315202, 0x69f33835,
+ 0x62af7f08, 0x636d153f, 0x612bab66, 0x60e9c151,
+ 0x65a6d7d4, 0x6464bde3, 0x662203ba, 0x67e0698d,
+ 0x48d7cb20, 0x4915a117, 0x4b531f4e, 0x4a917579,
+ 0x4fde63fc, 0x4e1c09cb, 0x4c5ab792, 0x4d98dda5,
+ 0x46c49a98, 0x4706f0af, 0x45404ef6, 0x448224c1,
+ 0x41cd3244, 0x400f5873, 0x4249e62a, 0x438b8c1d,
+ 0x54f16850, 0x55330267, 0x5775bc3e, 0x56b7d609,
+ 0x53f8c08c, 0x523aaabb, 0x507c14e2, 0x51be7ed5,
+ 0x5ae239e8, 0x5b2053df, 0x5966ed86, 0x58a487b1,
+ 0x5deb9134, 0x5c29fb03, 0x5e6f455a, 0x5fad2f6d,
+ 0xe1351b80, 0xe0f771b7, 0xe2b1cfee, 0xe373a5d9,
+ 0xe63cb35c, 0xe7fed96b, 0xe5b86732, 0xe47a0d05,
+ 0xef264a38, 0xeee4200f, 0xeca29e56, 0xed60f461,
+ 0xe82fe2e4, 0xe9ed88d3, 0xebab368a, 0xea695cbd,
+ 0xfd13b8f0, 0xfcd1d2c7, 0xfe976c9e, 0xff5506a9,
+ 0xfa1a102c, 0xfbd87a1b, 0xf99ec442, 0xf85cae75,
+ 0xf300e948, 0xf2c2837f, 0xf0843d26, 0xf1465711,
+ 0xf4094194, 0xf5cb2ba3, 0xf78d95fa, 0xf64fffcd,
+ 0xd9785d60, 0xd8ba3757, 0xdafc890e, 0xdb3ee339,
+ 0xde71f5bc, 0xdfb39f8b, 0xddf521d2, 0xdc374be5,
+ 0xd76b0cd8, 0xd6a966ef, 0xd4efd8b6, 0xd52db281,
+ 0xd062a404, 0xd1a0ce33, 0xd3e6706a, 0xd2241a5d,
+ 0xc55efe10, 0xc49c9427, 0xc6da2a7e, 0xc7184049,
+ 0xc25756cc, 0xc3953cfb, 0xc1d382a2, 0xc011e895,
+ 0xcb4dafa8, 0xca8fc59f, 0xc8c97bc6, 0xc90b11f1,
+ 0xcc440774, 0xcd866d43, 0xcfc0d31a, 0xce02b92d,
+ 0x91af9640, 0x906dfc77, 0x922b422e, 0x93e92819,
+ 0x96a63e9c, 0x976454ab, 0x9522eaf2, 0x94e080c5,
+ 0x9fbcc7f8, 0x9e7eadcf, 0x9c381396, 0x9dfa79a1,
+ 0x98b56f24, 0x99770513, 0x9b31bb4a, 0x9af3d17d,
+ 0x8d893530, 0x8c4b5f07, 0x8e0de15e, 0x8fcf8b69,
+ 0x8a809dec, 0x8b42f7db, 0x89044982, 0x88c623b5,
+ 0x839a6488, 0x82580ebf, 0x801eb0e6, 0x81dcdad1,
+ 0x8493cc54, 0x8551a663, 0x8717183a, 0x86d5720d,
+ 0xa9e2d0a0, 0xa820ba97, 0xaa6604ce, 0xaba46ef9,
+ 0xaeeb787c, 0xaf29124b, 0xad6fac12, 0xacadc625,
+ 0xa7f18118, 0xa633eb2f, 0xa4755576, 0xa5b73f41,
+ 0xa0f829c4, 0xa13a43f3, 0xa37cfdaa, 0xa2be979d,
+ 0xb5c473d0, 0xb40619e7, 0xb640a7be, 0xb782cd89,
+ 0xb2cddb0c, 0xb30fb13b, 0xb1490f62, 0xb08b6555,
+ 0xbbd72268, 0xba15485f, 0xb853f606, 0xb9919c31,
+ 0xbcde8ab4, 0xbd1ce083, 0xbf5a5eda, 0xbe9834ed,
+};
+
+unsigned int crc32_table0[256] = {
+ 0x00000000, 0xb8bc6765, 0xaa09c88b, 0x12b5afee,
+ 0x8f629757, 0x37def032, 0x256b5fdc, 0x9dd738b9,
+ 0xc5b428ef, 0x7d084f8a, 0x6fbde064, 0xd7018701,
+ 0x4ad6bfb8, 0xf26ad8dd, 0xe0df7733, 0x58631056,
+ 0x5019579f, 0xe8a530fa, 0xfa109f14, 0x42acf871,
+ 0xdf7bc0c8, 0x67c7a7ad, 0x75720843, 0xcdce6f26,
+ 0x95ad7f70, 0x2d111815, 0x3fa4b7fb, 0x8718d09e,
+ 0x1acfe827, 0xa2738f42, 0xb0c620ac, 0x087a47c9,
+ 0xa032af3e, 0x188ec85b, 0x0a3b67b5, 0xb28700d0,
+ 0x2f503869, 0x97ec5f0c, 0x8559f0e2, 0x3de59787,
+ 0x658687d1, 0xdd3ae0b4, 0xcf8f4f5a, 0x7733283f,
+ 0xeae41086, 0x525877e3, 0x40edd80d, 0xf851bf68,
+ 0xf02bf8a1, 0x48979fc4, 0x5a22302a, 0xe29e574f,
+ 0x7f496ff6, 0xc7f50893, 0xd540a77d, 0x6dfcc018,
+ 0x359fd04e, 0x8d23b72b, 0x9f9618c5, 0x272a7fa0,
+ 0xbafd4719, 0x0241207c, 0x10f48f92, 0xa848e8f7,
+ 0x9b14583d, 0x23a83f58, 0x311d90b6, 0x89a1f7d3,
+ 0x1476cf6a, 0xaccaa80f, 0xbe7f07e1, 0x06c36084,
+ 0x5ea070d2, 0xe61c17b7, 0xf4a9b859, 0x4c15df3c,
+ 0xd1c2e785, 0x697e80e0, 0x7bcb2f0e, 0xc377486b,
+ 0xcb0d0fa2, 0x73b168c7, 0x6104c729, 0xd9b8a04c,
+ 0x446f98f5, 0xfcd3ff90, 0xee66507e, 0x56da371b,
+ 0x0eb9274d, 0xb6054028, 0xa4b0efc6, 0x1c0c88a3,
+ 0x81dbb01a, 0x3967d77f, 0x2bd27891, 0x936e1ff4,
+ 0x3b26f703, 0x839a9066, 0x912f3f88, 0x299358ed,
+ 0xb4446054, 0x0cf80731, 0x1e4da8df, 0xa6f1cfba,
+ 0xfe92dfec, 0x462eb889, 0x549b1767, 0xec277002,
+ 0x71f048bb, 0xc94c2fde, 0xdbf98030, 0x6345e755,
+ 0x6b3fa09c, 0xd383c7f9, 0xc1366817, 0x798a0f72,
+ 0xe45d37cb, 0x5ce150ae, 0x4e54ff40, 0xf6e89825,
+ 0xae8b8873, 0x1637ef16, 0x048240f8, 0xbc3e279d,
+ 0x21e91f24, 0x99557841, 0x8be0d7af, 0x335cb0ca,
+ 0xed59b63b, 0x55e5d15e, 0x47507eb0, 0xffec19d5,
+ 0x623b216c, 0xda874609, 0xc832e9e7, 0x708e8e82,
+ 0x28ed9ed4, 0x9051f9b1, 0x82e4565f, 0x3a58313a,
+ 0xa78f0983, 0x1f336ee6, 0x0d86c108, 0xb53aa66d,
+ 0xbd40e1a4, 0x05fc86c1, 0x1749292f, 0xaff54e4a,
+ 0x322276f3, 0x8a9e1196, 0x982bbe78, 0x2097d91d,
+ 0x78f4c94b, 0xc048ae2e, 0xd2fd01c0, 0x6a4166a5,
+ 0xf7965e1c, 0x4f2a3979, 0x5d9f9697, 0xe523f1f2,
+ 0x4d6b1905, 0xf5d77e60, 0xe762d18e, 0x5fdeb6eb,
+ 0xc2098e52, 0x7ab5e937, 0x680046d9, 0xd0bc21bc,
+ 0x88df31ea, 0x3063568f, 0x22d6f961, 0x9a6a9e04,
+ 0x07bda6bd, 0xbf01c1d8, 0xadb46e36, 0x15080953,
+ 0x1d724e9a, 0xa5ce29ff, 0xb77b8611, 0x0fc7e174,
+ 0x9210d9cd, 0x2aacbea8, 0x38191146, 0x80a57623,
+ 0xd8c66675, 0x607a0110, 0x72cfaefe, 0xca73c99b,
+ 0x57a4f122, 0xef189647, 0xfdad39a9, 0x45115ecc,
+ 0x764dee06, 0xcef18963, 0xdc44268d, 0x64f841e8,
+ 0xf92f7951, 0x41931e34, 0x5326b1da, 0xeb9ad6bf,
+ 0xb3f9c6e9, 0x0b45a18c, 0x19f00e62, 0xa14c6907,
+ 0x3c9b51be, 0x842736db, 0x96929935, 0x2e2efe50,
+ 0x2654b999, 0x9ee8defc, 0x8c5d7112, 0x34e11677,
+ 0xa9362ece, 0x118a49ab, 0x033fe645, 0xbb838120,
+ 0xe3e09176, 0x5b5cf613, 0x49e959fd, 0xf1553e98,
+ 0x6c820621, 0xd43e6144, 0xc68bceaa, 0x7e37a9cf,
+ 0xd67f4138, 0x6ec3265d, 0x7c7689b3, 0xc4caeed6,
+ 0x591dd66f, 0xe1a1b10a, 0xf3141ee4, 0x4ba87981,
+ 0x13cb69d7, 0xab770eb2, 0xb9c2a15c, 0x017ec639,
+ 0x9ca9fe80, 0x241599e5, 0x36a0360b, 0x8e1c516e,
+ 0x866616a7, 0x3eda71c2, 0x2c6fde2c, 0x94d3b949,
+ 0x090481f0, 0xb1b8e695, 0xa30d497b, 0x1bb12e1e,
+ 0x43d23e48, 0xfb6e592d, 0xe9dbf6c3, 0x516791a6,
+ 0xccb0a91f, 0x740cce7a, 0x66b96194, 0xde0506f1,
+};
+
+static unsigned int crc32_partial (const void *data, int len, unsigned crc) {
+ const int *p = (const int *) data;
+ int x;
+#define DO_ONE(v) crc ^= v; crc = crc32_table0[crc & 0xff] ^ crc32_table1[(crc & 0xff00) >> 8] ^ crc32_table2[(crc & 0xff0000) >> 16] ^ crc32_table[crc >> 24];
+#define DO_FOUR(p) DO_ONE((p)[0]); DO_ONE((p)[1]); DO_ONE((p)[2]); DO_ONE((p)[3]);
+
+ for (x = (len >> 5); x > 0; x--) {
+ DO_FOUR (p);
+ DO_FOUR (p + 4);
+ p += 8;
+ }
+ if (len & 16) {
+ DO_FOUR (p);
+ p += 4;
+ }
+ if (len & 8) {
+ DO_ONE (p[0]);
+ DO_ONE (p[1]);
+ p += 2;
+ }
+ if (len & 4) {
+ DO_ONE (*p++);
+ }
+ /*
+ for (x = (len >> 2) & 7; x > 0; x--) {
+ DO_ONE (*p++);
+ }
+ */
+#undef DO_ONE
+#undef DO_FOUR
+ const char *q = (const char *) p;
+ if (len & 2) {
+ crc = crc32_table[(crc ^ q[0]) & 0xff] ^ (crc >> 8);
+ crc = crc32_table[(crc ^ q[1]) & 0xff] ^ (crc >> 8);
+ q += 2;
+ }
+ if (len & 1) {
+ crc = crc32_table[(crc ^ *q++) & 0xff] ^ (crc >> 8);
+ }
+ return crc;
+}
+
+unsigned int compute_crc32 (const void *data, int len) {
+ return crc32_partial (data, len, -1) ^ -1;
+}
+
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/crc32.h b/protocols/Telegram/tdlib/td/td/generate/tl-parser/crc32.h
new file mode 100644
index 0000000000..b461784b31
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/crc32.h
@@ -0,0 +1,37 @@
+/*
+ This file is part of VK/KittenPHP-DB-Engine Library.
+
+ VK/KittenPHP-DB-Engine 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 of the License, or
+ (at your option) any later version.
+
+ VK/KittenPHP-DB-Engine 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 VK/KittenPHP-DB-Engine Library. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009-2012 Vkontakte Ltd
+ 2009-2012 Nikolai Durov
+ 2009-2012 Andrei Lopatin
+ 2012 Anton Maydell
+*/
+
+#ifndef __CRC32_H__
+#define __CRC32_H__
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+unsigned int compute_crc32 (const void *data, int len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/portable_endian.h b/protocols/Telegram/tdlib/td/td/generate/tl-parser/portable_endian.h
new file mode 100644
index 0000000000..a815547e39
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/portable_endian.h
@@ -0,0 +1,88 @@
+// "License": Public Domain
+// I, Mathias Panzenböck, place this file hereby into the public domain. Use it at your own risk for whatever you like.
+// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to
+// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it
+// an example on how to get the endian conversion functions on different platforms.
+
+/* Originally cloned from https://gist.github.com/PkmX/63dd23f28ba885be53a5
+ * Commit was: 1eca2ab34f2301b9641aa73d1016b951fff3fc39
+ * Re-published at https://github.com/BenWiederhake/portable-endian.h to provide a means to submit patches and report issues. */
+
+#ifndef PORTABLE_ENDIAN_H__
+#define PORTABLE_ENDIAN_H__
+
+#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
+
+# define __WINDOWS__
+
+#endif
+
+#if defined(__linux__) || defined(__CYGWIN__) || defined(__sun)
+
+# include <endian.h>
+
+#elif defined(__APPLE__)
+
+# include <libkern/OSByteOrder.h>
+
+# define htobe32(x) OSSwapHostToBigInt32(x)
+# define htole32(x) OSSwapHostToLittleInt32(x)
+
+# define htobe64(x) OSSwapHostToBigInt64(x)
+# define htole64(x) OSSwapHostToLittleInt64(x)
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#elif defined(__OpenBSD__)
+
+# include <sys/endian.h>
+
+#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
+
+# include <sys/endian.h>
+
+#elif defined(__WINDOWS__)
+
+# include <winsock2.h>
+#ifdef __MINGW32__
+# include <sys/param.h>
+#endif
+
+# if BYTE_ORDER == LITTLE_ENDIAN
+
+# define htobe32(x) htonl(x)
+# define htole32(x) (x)
+
+# define htobe64(x) htonll(x)
+# define htole64(x) (x)
+
+# elif BYTE_ORDER == BIG_ENDIAN
+
+ /* that would be xbox 360 */
+# define htobe32(x) (x)
+# define htole32(x) __builtin_bswap32(x)
+
+# define htobe64(x) (x)
+# define htole64(x) __builtin_bswap64(x)
+
+# else
+
+# error byte order not supported
+
+# endif
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#else
+
+# error platform not supported
+
+#endif
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser-tree.h b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser-tree.h
new file mode 100644
index 0000000000..fba7c67521
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser-tree.h
@@ -0,0 +1,178 @@
+/*
+ This file is part of tgl-library
+
+ 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
+
+ Copyright Vitaly Valtman 2013-2014
+*/
+#ifndef __TREE_H__
+#define __TREE_H__
+#include <stdio.h>
+
+#include <memory.h>
+#include <assert.h>
+
+#pragma pack(push,4)
+#define DEFINE_TREE(X_NAME, X_TYPE, X_CMP, X_UNSET) \
+struct tree_ ## X_NAME { \
+ struct tree_ ## X_NAME *left, *right;\
+ X_TYPE x;\
+ int y;\
+};\
+\
+static struct tree_ ## X_NAME *new_tree_node_ ## X_NAME (X_TYPE x, int y) {\
+ struct tree_ ## X_NAME *T = malloc (sizeof (*T));\
+ T->x = x;\
+ T->y = y;\
+ T->left = T->right = 0;\
+ return T;\
+}\
+\
+static void delete_tree_node_ ## X_NAME (struct tree_ ## X_NAME *T) {\
+ free (T);\
+}\
+\
+static void tree_split_ ## X_NAME (struct tree_ ## X_NAME *T, X_TYPE x, struct tree_ ## X_NAME **L, struct tree_ ## X_NAME **R) {\
+ if (!T) {\
+ *L = *R = 0;\
+ } else {\
+ int c = X_CMP (x, T->x);\
+ if (c < 0) {\
+ tree_split_ ## X_NAME (T->left, x, L, &T->left);\
+ *R = T;\
+ } else {\
+ tree_split_ ## X_NAME (T->right, x, &T->right, R);\
+ *L = T;\
+ }\
+ }\
+}\
+\
+static struct tree_ ## X_NAME *tree_insert_ ## X_NAME (struct tree_ ## X_NAME *T, X_TYPE x, int y) __attribute__ ((warn_unused_result,unused));\
+static struct tree_ ## X_NAME *tree_insert_ ## X_NAME (struct tree_ ## X_NAME *T, X_TYPE x, int y) {\
+ if (!T) {\
+ return new_tree_node_ ## X_NAME (x, y);\
+ } else {\
+ if (y > T->y) {\
+ struct tree_ ## X_NAME *N = new_tree_node_ ## X_NAME (x, y);\
+ tree_split_ ## X_NAME (T, x, &N->left, &N->right);\
+ return N;\
+ } else {\
+ int c = X_CMP (x, T->x);\
+ assert (c);\
+ if (c < 0) { \
+ T->left = tree_insert_ ## X_NAME (T->left, x, y);\
+ } else { \
+ T->right = tree_insert_ ## X_NAME (T->right, x, y);\
+ } \
+ return T; \
+ }\
+ }\
+}\
+\
+static struct tree_ ## X_NAME *tree_merge_ ## X_NAME (struct tree_ ## X_NAME *L, struct tree_ ## X_NAME *R) {\
+ if (!L || !R) {\
+ return L ? L : R;\
+ } else {\
+ if (L->y > R->y) {\
+ L->right = tree_merge_ ## X_NAME (L->right, R);\
+ return L;\
+ } else {\
+ R->left = tree_merge_ ## X_NAME (L, R->left);\
+ return R;\
+ }\
+ }\
+}\
+\
+static struct tree_ ## X_NAME *tree_delete_ ## X_NAME (struct tree_ ## X_NAME *T, X_TYPE x) __attribute__ ((warn_unused_result,unused));\
+static struct tree_ ## X_NAME *tree_delete_ ## X_NAME (struct tree_ ## X_NAME *T, X_TYPE x) {\
+ assert (T);\
+ int c = X_CMP (x, T->x);\
+ if (!c) {\
+ struct tree_ ## X_NAME *N = tree_merge_ ## X_NAME (T->left, T->right);\
+ delete_tree_node_ ## X_NAME (T);\
+ return N;\
+ } else {\
+ if (c < 0) { \
+ T->left = tree_delete_ ## X_NAME (T->left, x); \
+ } else { \
+ T->right = tree_delete_ ## X_NAME (T->right, x); \
+ } \
+ return T; \
+ }\
+}\
+\
+static X_TYPE tree_get_min_ ## X_NAME (struct tree_ ## X_NAME *t) __attribute__ ((unused));\
+static X_TYPE tree_get_min_ ## X_NAME (struct tree_ ## X_NAME *T) {\
+ if (!T) { return X_UNSET; } \
+ while (T->left) { T = T->left; }\
+ return T->x; \
+} \
+\
+static X_TYPE tree_lookup_ ## X_NAME (struct tree_ ## X_NAME *T, X_TYPE x) __attribute__ ((unused));\
+static X_TYPE tree_lookup_ ## X_NAME (struct tree_ ## X_NAME *T, X_TYPE x) {\
+ int c;\
+ while (T && (c = X_CMP (x, T->x))) {\
+ T = (c < 0 ? T->left : T->right);\
+ }\
+ return T ? T->x : X_UNSET;\
+}\
+\
+static void tree_act_ ## X_NAME (struct tree_ ## X_NAME *T, void (*act)(X_TYPE)) __attribute__ ((unused));\
+static void tree_act_ ## X_NAME (struct tree_ ## X_NAME *T, void (*act)(X_TYPE)) {\
+ if (!T) { return; } \
+ tree_act_ ## X_NAME (T->left, act); \
+ act (T->x); \
+ tree_act_ ## X_NAME (T->right, act); \
+}\
+\
+static void tree_act_ex_ ## X_NAME (struct tree_ ## X_NAME *T, void (*act)(X_TYPE, void *), void *extra) __attribute__ ((unused));\
+static void tree_act_ex_ ## X_NAME (struct tree_ ## X_NAME *T, void (*act)(X_TYPE, void *), void *extra) {\
+ if (!T) { return; } \
+ tree_act_ex_ ## X_NAME (T->left, act, extra); \
+ act (T->x, extra); \
+ tree_act_ex_ ## X_NAME (T->right, act, extra); \
+}\
+\
+static int tree_count_ ## X_NAME (struct tree_ ## X_NAME *T) __attribute__ ((unused));\
+static int tree_count_ ## X_NAME (struct tree_ ## X_NAME *T) { \
+ if (!T) { return 0; }\
+ return 1 + tree_count_ ## X_NAME (T->left) + tree_count_ ## X_NAME (T->right); \
+}\
+static void tree_check_ ## X_NAME (struct tree_ ## X_NAME *T) __attribute__ ((unused));\
+static void tree_check_ ## X_NAME (struct tree_ ## X_NAME *T) { \
+ if (!T) { return; }\
+ if (T->left) { \
+ assert (T->left->y <= T->y);\
+ assert (X_CMP (T->left->x, T->x) < 0); \
+ }\
+ if (T->right) { \
+ assert (T->right->y <= T->y);\
+ assert (X_CMP (T->right->x, T->x) > 0); \
+ }\
+ tree_check_ ## X_NAME (T->left); \
+ tree_check_ ## X_NAME (T->right); \
+}\
+static struct tree_ ## X_NAME *tree_clear_ ## X_NAME (struct tree_ ## X_NAME *T) __attribute__ ((unused));\
+static struct tree_ ## X_NAME *tree_clear_ ## X_NAME (struct tree_ ## X_NAME *T) { \
+ if (!T) { return 0; }\
+ tree_clear_ ## X_NAME (T->left); \
+ tree_clear_ ## X_NAME (T->right); \
+ delete_tree_node_ ## X_NAME (T); \
+ return 0; \
+} \
+
+#define int_cmp(a,b) ((a) - (b))
+#pragma pack(pop)
+#endif
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser.c b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser.c
new file mode 100644
index 0000000000..66276ba012
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser.c
@@ -0,0 +1,3078 @@
+/*
+ This file is part of tl-parser
+
+ tl-parser 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 2 of the License, or
+ (at your option) any later version.
+
+ tl-parser 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 tl-parser. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright Vitaly Valtman 2014
+
+ It is derivative work of VK/KittenPHP-DB-Engine (https://github.com/vk-com/kphp-kdb/)
+ Copyright 2012-2013 Vkontakte Ltd
+ 2012-2013 Vitaliy Valtman
+
+*/
+
+#define _FILE_OFFSET_BITS 64
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+#include "portable_endian.h"
+#include "tl-parser-tree.h"
+#include "tl-parser.h"
+#include "crc32.h"
+#include "tl-tl.h"
+
+extern int verbosity;
+extern int schema_version;
+extern int output_expressions;
+
+
+int total_types_num;
+int total_constructors_num;
+int total_functions_num;
+
+
+/*char *tstrdup (const char *s) {
+ assert (s);
+ char *r = talloc (strlen (s) + 1);
+ memcpy (r, s, strlen (s) + 1);
+ return r;
+}*/
+
+#define talloc(a) malloc(a)
+#define tfree(a,b) free (a)
+#define talloc0(a) calloc(a,1)
+#define tstrdup(a) strdup(a)
+
+typedef char error_int_must_be_4_byte[(sizeof (int) == 4) ? 1 : -1];
+typedef char error_long_long_must_be_8_byte[(sizeof (long long) == 8) ? 1 : -1];
+
+char curch;
+struct parse parse;
+
+struct tree *tree;
+
+struct tree *tree_alloc (void) {
+ struct tree *T = talloc (sizeof (*T));
+ assert (T);
+ memset (T, 0, sizeof (*T));
+ return T;
+}
+
+void tree_add_child (struct tree *P, struct tree *C) {
+ if (P->nc == P->size) {
+ void **t = talloc (sizeof (void *) * (++P->size));
+ memcpy (t, P->c, sizeof (void *) * (P->size - 1));
+ if (P->c) {
+ tfree (P->c, sizeof (void *) * (P->size - 1));
+ }
+ P->c = (void *)t;
+ assert (P->c);
+ }
+ P->c[P->nc ++] = C;
+}
+
+void tree_delete (struct tree *T) {
+ assert (T);
+ int i;
+ for (i = 0; i < T->nc; i++) {
+ assert (T->c[i]);
+ tree_delete (T->c[i]);
+ }
+ if (T->c) {
+ tfree (T->c, sizeof (void *) * T->nc);
+ }
+ tfree (T, sizeof (*T));
+}
+
+void tree_del_child (struct tree *P) {
+ assert (P->nc);
+ tree_delete (P->c[--P->nc]);
+}
+
+
+char nextch (void) {
+ if (parse.pos < parse.len - 1) {
+ curch = parse.text[++parse.pos];
+ } else {
+ curch = 0;
+ }
+ if (curch == 10) {
+ parse.line ++;
+ parse.line_pos = 0;
+ } else {
+ if (curch) {
+ parse.line_pos ++;
+ }
+ }
+ return curch;
+}
+
+
+struct parse save_parse (void) {
+ return parse;
+}
+
+void load_parse (struct parse _parse) {
+ parse = _parse;
+ curch = parse.pos > parse.len ? 0: parse.text[parse.pos] ;
+}
+
+int is_whitespace (char c) {
+ return (c <= 32);
+}
+
+int is_uletter (char c) {
+ return (c >= 'A' && c <= 'Z');
+}
+
+int is_lletter (char c) {
+ return (c >= 'a' && c <= 'z');
+}
+
+int is_letter (char c) {
+ return is_uletter (c) || is_lletter (c);
+}
+
+int is_digit (char c) {
+ return (c >= '0' && c <= '9');
+}
+
+int is_hexdigit (char c) {
+ return is_digit (c) || (c >= 'a' && c <= 'f');
+}
+
+int is_ident_char (char c) {
+ return is_digit (c) || is_letter (c) || c == '_';
+}
+
+int last_error_pos;
+int last_error_line;
+int last_error_line_pos;
+char *last_error;
+
+void parse_error (const char *e) {
+ if (parse.pos > last_error_pos) {
+ last_error_pos = parse.pos;
+ last_error_line = parse.line;
+ last_error_line_pos = parse.line_pos;
+ if (last_error) {
+ tfree (last_error, strlen (last_error) + 1);
+ }
+ last_error = tstrdup (e);
+ }
+}
+
+void tl_print_parse_error (void) {
+ fprintf (stderr, "Error near line %d pos %d: `%s`\n", last_error_line + 1, last_error_line_pos + 1, last_error);
+}
+
+char *parse_lex (void) {
+ while (1) {
+ while (curch && is_whitespace (curch)) { nextch (); }
+ if (curch == '/' && nextch () == '/') {
+ while (nextch () != 10);
+ nextch ();
+ } else {
+ break;
+ }
+ }
+ if (!curch) {
+ parse.lex.len = 0;
+ parse.lex.type = lex_eof;
+ return (parse.lex.ptr = 0);
+ }
+ char *p = parse.text + parse.pos;
+ parse.lex.flags = 0;
+ switch (curch) {
+ case '-':
+ if (nextch () != '-' || nextch () != '-') {
+ parse_error ("Can not parse triple minus");
+ parse.lex.type = lex_error;
+ return (parse.lex.ptr = (void *)-1);
+ } else {
+ parse.lex.len = 3;
+ parse.lex.type = lex_triple_minus;
+ nextch ();
+ return (parse.lex.ptr = p);
+ }
+ case ':':
+ case ';':
+ case '(':
+ case ')':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ case '=':
+ case '#':
+ case '?':
+ case '%':
+ case '<':
+ case '>':
+ case '+':
+ case ',':
+ case '*':
+ case '_':
+ case '!':
+ case '.':
+ nextch ();
+ parse.lex.len = 1;
+ parse.lex.type = lex_char;
+ return (parse.lex.ptr = p);
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ parse.lex.flags = 0;
+ if (is_uletter (curch)) {
+ while (is_ident_char (nextch ()));
+ parse.lex.len = parse.text + parse.pos - p;
+ parse.lex.ptr = p;
+ if (parse.lex.len == 5 && !memcmp (parse.lex.ptr, "Final", 5)) {
+ parse.lex.type = lex_final;
+ } else if (parse.lex.len == 3 && !memcmp (parse.lex.ptr, "New", 3)) {
+ parse.lex.type = lex_new;
+ } else if (parse.lex.len == 5 && !memcmp (parse.lex.ptr, "Empty", 5)) {
+ parse.lex.type = lex_empty;
+ } else {
+ parse.lex.type = lex_uc_ident;
+ }
+ return (parse.lex.ptr = p);
+ }
+ while (is_ident_char (nextch ()));
+ if (curch == '.' && !is_letter (parse.text[parse.pos + 1])) {
+ parse.lex.len = parse.text + parse.pos - p;
+ parse.lex.type = lex_lc_ident;
+ return (parse.lex.ptr = p);
+ }
+ while (curch == '.') {
+ parse.lex.flags |= 1;
+ nextch ();
+ if (is_uletter (curch)) {
+ while (is_ident_char (nextch ()));
+ parse.lex.len = parse.text + parse.pos - p;
+ parse.lex.type = lex_uc_ident;
+ return (parse.lex.ptr = p);
+ }
+ if (is_lletter (curch)) {
+ while (is_ident_char (nextch ()));
+ } else {
+ parse_error ("Expected letter");
+ parse.lex.type = lex_error;
+ return (parse.lex.ptr = (void *)-1);
+ }
+ }
+ if (curch == '#') {
+ parse.lex.flags |= 2;
+ int i;
+ int ok = 1;
+ for (i = 0; i < 8; i++) {
+ if (!is_hexdigit (nextch())) {
+ if (curch == ' ' && i >= 5) {
+ ok = 2;
+ break;
+ } else {
+ parse_error ("Hex digit expected");
+ parse.lex.type = lex_error;
+ return (parse.lex.ptr = (void *)-1);
+ }
+ }
+ }
+ if (ok == 1) {
+ nextch ();
+ }
+ }
+ parse.lex.len = parse.text + parse.pos - p;
+ parse.lex.type = lex_lc_ident;
+ return (parse.lex.ptr = p);
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ while (is_digit (nextch ()));
+ parse.lex.len = parse.text + parse.pos - p;
+ parse.lex.type = lex_num;
+ return (parse.lex.ptr = p);
+ default:
+ parse_error ("Unknown lexem");
+ parse.lex.type = lex_error;
+ return (parse.lex.ptr = (void *)-1);
+ }
+
+}
+
+int expect (char *s) {
+ if (!parse.lex.ptr || parse.lex.ptr == (void *)-1 || parse.lex.type == lex_error || parse.lex.type == lex_none || parse.lex.len != (int)strlen (s) || memcmp (s, parse.lex.ptr, parse.lex.len)) {
+ static char buf[1000];
+ sprintf (buf, "Expected %s", s);
+ parse_error (buf);
+ return -1;
+ } else {
+ parse_lex ();
+ }
+ return 1;
+}
+
+struct parse *tl_init_parse_file (const char *fname) {
+ FILE *f = fopen (fname, "rb");
+ if (f == NULL) {
+ fprintf (stderr, "Failed to open the input file.\n");
+ return NULL;
+ }
+ if (fseek (f, 0, SEEK_END) != 0) {
+ fprintf (stderr, "Can't seek to the end of the input file.\n");
+ return NULL;
+ }
+ long size = ftell (f);
+ if (size <= 0 || size > INT_MAX) {
+ fprintf (stderr, "Size is %ld. Too small or too big.\n", size);
+ return NULL;
+ }
+ fseek (f, 0, SEEK_SET);
+
+ static struct parse save;
+ save.text = talloc ((size_t)size);
+ save.len = fread (save.text, 1, (size_t)size, f);
+ assert (save.len == size);
+ fclose (f);
+ save.pos = 0;
+ save.line = 0;
+ save.line_pos = 0;
+ save.lex.ptr = save.text;
+ save.lex.len = 0;
+ save.lex.type = lex_none;
+ return &save;
+}
+
+#define PARSE_INIT(_type) struct parse save = save_parse (); struct tree *T = tree_alloc (); T->type = (_type); T->lex_line = parse.line; T->lex_line_pos = parse.line_pos; struct tree *S __attribute__ ((unused));
+#define PARSE_FAIL load_parse (save); tree_delete (T); return 0;
+#define PARSE_OK return T;
+#define PARSE_TRY_PES(x) if (!(S = x ())) { PARSE_FAIL; } { tree_add_child (T, S); }
+#define PARSE_TRY_OPT(x) if ((S = x ())) { tree_add_child (T, S); PARSE_OK }
+#define PARSE_TRY(x) S = x ();
+#define PARSE_ADD(_type) S = tree_alloc (); S->type = _type; tree_add_child (T, S);
+#define EXPECT(s) if (expect (s) < 0) { PARSE_FAIL; }
+#define LEX_CHAR(c) (parse.lex.type == lex_char && *parse.lex.ptr == c)
+struct tree *parse_args (void);
+struct tree *parse_expr (void);
+
+struct tree *parse_boxed_type_ident (void) {
+ PARSE_INIT (type_boxed_type_ident);
+ if (parse.lex.type != lex_uc_ident) {
+ parse_error ("Can not parse boxed type");
+ PARSE_FAIL;
+ } else {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ }
+}
+
+struct tree *parse_full_combinator_id (void) {
+ PARSE_INIT (type_full_combinator_id);
+ if (parse.lex.type == lex_lc_ident || LEX_CHAR('_')) {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ } else {
+ parse_error ("Can not parse full combinator id");
+ PARSE_FAIL;
+ }
+}
+
+struct tree *parse_combinator_id (void) {
+ PARSE_INIT (type_combinator_id);
+ if (parse.lex.type == lex_lc_ident && !(parse.lex.flags & 2)) {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ } else {
+ parse_error ("Can not parse combinator id");
+ PARSE_FAIL;
+ }
+}
+
+struct tree *parse_var_ident (void) {
+ PARSE_INIT (type_var_ident);
+ if ((parse.lex.type == lex_lc_ident || parse.lex.type == lex_uc_ident) && !(parse.lex.flags & 3)) {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ } else {
+ parse_error ("Can not parse var ident");
+ PARSE_FAIL;
+ }
+}
+
+struct tree *parse_var_ident_opt (void) {
+ PARSE_INIT (type_var_ident_opt);
+ if ((parse.lex.type == lex_lc_ident || parse.lex.type == lex_uc_ident)&& !(parse.lex.flags & 3)) {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ } else if (LEX_CHAR ('_')) {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ } else {
+ parse_error ("Can not parse var ident opt");
+ PARSE_FAIL;
+ }
+}
+
+struct tree *parse_nat_const (void) {
+ PARSE_INIT (type_nat_const);
+ if (parse.lex.type == lex_num) {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ } else {
+ parse_error ("Can not parse nat const");
+ PARSE_FAIL;
+ }
+}
+
+struct tree *parse_type_ident (void) {
+ PARSE_INIT (type_type_ident);
+ if (parse.lex.type == lex_uc_ident && !(parse.lex.flags & 2)) {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ } else if (parse.lex.type == lex_lc_ident && !(parse.lex.flags & 2)) {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ } else if (LEX_CHAR ('#')) {
+ T->text = parse.lex.ptr;
+ T->len = parse.lex.len;
+ T->flags = parse.lex.flags;
+ parse_lex ();
+ PARSE_OK;
+ } else {
+ parse_error ("Can not parse type ident");
+ PARSE_FAIL;
+ }
+}
+
+struct tree *parse_term (void) {
+ PARSE_INIT (type_term);
+ while (LEX_CHAR ('%')) {
+ EXPECT ("%")
+ PARSE_ADD (type_percent);
+ }
+ if (LEX_CHAR ('(')) {
+ EXPECT ("(");
+ PARSE_TRY_PES (parse_expr);
+ EXPECT (")");
+ PARSE_OK;
+ }
+ PARSE_TRY (parse_type_ident);
+ if (S) {
+ tree_add_child (T, S);
+ if (LEX_CHAR ('<')) {
+ EXPECT ("<");
+ while (1) {
+ PARSE_TRY_PES (parse_expr);
+ if (LEX_CHAR ('>')) { break; }
+ EXPECT (",");
+ }
+ EXPECT (">");
+ }
+ PARSE_OK;
+ }
+ PARSE_TRY_OPT (parse_type_ident);
+ PARSE_TRY_OPT (parse_var_ident);
+ PARSE_TRY_OPT (parse_nat_const);
+ PARSE_FAIL;
+}
+
+struct tree *parse_nat_term (void) {
+ PARSE_INIT (type_nat_term);
+ PARSE_TRY_PES (parse_term);
+ PARSE_OK;
+}
+
+struct tree *parse_subexpr (void) {
+ PARSE_INIT (type_subexpr);
+ int was_term = 0;
+ int cc = 0;
+
+ while (1) {
+ PARSE_TRY (parse_nat_const);
+ if (S) {
+ tree_add_child (T, S);
+ } else if (!was_term) {
+ was_term = 1;
+ PARSE_TRY (parse_term);
+ if (S) {
+ tree_add_child (T, S);
+ } else {
+ break;
+ }
+ }
+ cc ++;
+ if (!LEX_CHAR ('+')) {
+ break;
+ }
+ EXPECT ("+");
+ }
+ if (!cc) {
+ PARSE_FAIL;
+ } else {
+ PARSE_OK;
+ }
+}
+
+struct tree *parse_expr (void) {
+ PARSE_INIT (type_expr);
+ int cc = 0;
+ while (1) {
+ PARSE_TRY (parse_subexpr);
+ if (S) {
+ tree_add_child (T, S);
+ cc ++;
+ } else {
+ if (cc < 1) { PARSE_FAIL; }
+ else { PARSE_OK; }
+ }
+ }
+}
+
+
+
+struct tree *parse_final_empty (void) {
+ PARSE_INIT (type_final_empty);
+ EXPECT ("Empty");
+ PARSE_TRY_PES (parse_boxed_type_ident);
+ PARSE_OK;
+}
+
+struct tree *parse_final_new (void) {
+ PARSE_INIT (type_final_new);
+ EXPECT ("New");
+ PARSE_TRY_PES (parse_boxed_type_ident);
+ PARSE_OK;
+}
+
+struct tree *parse_final_final (void) {
+ PARSE_INIT (type_final_final);
+ EXPECT ("Final");
+ PARSE_TRY_PES (parse_boxed_type_ident);
+ PARSE_OK;
+}
+
+struct tree *parse_partial_comb_app_decl (void) {
+ PARSE_INIT (type_partial_comb_app_decl);
+ PARSE_TRY_PES (parse_combinator_id);
+ while (1) {
+ PARSE_TRY_PES (parse_subexpr);
+ if (LEX_CHAR (';')) { break; }
+ }
+ PARSE_OK;
+}
+
+struct tree *parse_partial_type_app_decl (void) {
+ PARSE_INIT (type_partial_type_app_decl);
+ PARSE_TRY_PES (parse_boxed_type_ident);
+ if (LEX_CHAR ('<')) {
+ EXPECT ("<");
+ while (1) {
+ PARSE_TRY_PES (parse_expr);
+ if (LEX_CHAR ('>')) { break; }
+ EXPECT (",");
+ }
+ EXPECT (">");
+ PARSE_OK;
+ } else {
+ while (1) {
+ PARSE_TRY_PES (parse_subexpr);
+ if (LEX_CHAR (';')) { break; }
+ }
+ PARSE_OK;
+ }
+}
+
+
+
+
+struct tree *parse_multiplicity (void) {
+ PARSE_INIT (type_multiplicity);
+ PARSE_TRY_PES (parse_nat_term);
+ PARSE_OK;
+}
+
+
+struct tree *parse_type_term (void) {
+ PARSE_INIT (type_type_term);
+ PARSE_TRY_PES (parse_term);
+ PARSE_OK;
+}
+
+struct tree *parse_optional_arg_def (void) {
+ PARSE_INIT (type_optional_arg_def);
+ PARSE_TRY_PES (parse_var_ident);
+ EXPECT (".");
+ PARSE_TRY_PES (parse_nat_const);
+ EXPECT ("?");
+ PARSE_OK;
+}
+
+struct tree *parse_args4 (void) {
+ PARSE_INIT (type_args4);
+ struct parse so = save_parse ();
+ PARSE_TRY (parse_optional_arg_def);
+ if (S) {
+ tree_add_child (T, S);
+ } else {
+ load_parse (so);
+ }
+ if (LEX_CHAR ('!')) {
+ PARSE_ADD (type_exclam);
+ EXPECT ("!");
+ }
+ PARSE_TRY_PES (parse_type_term);
+ PARSE_OK;
+}
+
+struct tree *parse_args3 (void) {
+ PARSE_INIT (type_args3);
+ PARSE_TRY_PES (parse_var_ident_opt);
+ EXPECT (":");
+ struct parse so = save_parse ();
+ PARSE_TRY (parse_optional_arg_def);
+ if (S) {
+ tree_add_child (T, S);
+ } else {
+ load_parse (so);
+ }
+ if (LEX_CHAR ('!')) {
+ PARSE_ADD (type_exclam);
+ EXPECT ("!");
+ }
+ PARSE_TRY_PES (parse_type_term);
+ PARSE_OK;
+}
+
+struct tree *parse_args2 (void) {
+ PARSE_INIT (type_args2);
+ PARSE_TRY (parse_var_ident_opt);
+ if (S && LEX_CHAR (':')) {
+ tree_add_child (T, S);
+ EXPECT (":");
+ } else {
+ load_parse (save);
+ }
+ struct parse so = save_parse ();
+ PARSE_TRY (parse_optional_arg_def);
+ if (S) {
+ tree_add_child (T, S);
+ } else {
+ load_parse (so);
+ }
+ struct parse save2 = save_parse ();
+ PARSE_TRY (parse_multiplicity);
+ if (S && LEX_CHAR ('*')) {
+ tree_add_child (T, S);
+ EXPECT ("*");
+ } else {
+ load_parse (save2);
+ }
+ EXPECT ("[");
+ while (1) {
+ if (LEX_CHAR (']')) { break; }
+ PARSE_TRY_PES (parse_args);
+ }
+ EXPECT ("]");
+ PARSE_OK;
+}
+
+struct tree *parse_args1 (void) {
+ PARSE_INIT (type_args1);
+ EXPECT ("(");
+ while (1) {
+ PARSE_TRY_PES (parse_var_ident_opt);
+ if (LEX_CHAR(':')) { break; }
+ }
+ EXPECT (":");
+ struct parse so = save_parse ();
+ PARSE_TRY (parse_optional_arg_def);
+ if (S) {
+ tree_add_child (T, S);
+ } else {
+ load_parse (so);
+ }
+ if (LEX_CHAR ('!')) {
+ PARSE_ADD (type_exclam);
+ EXPECT ("!");
+ }
+ PARSE_TRY_PES (parse_type_term);
+ EXPECT (")");
+ PARSE_OK;
+}
+
+struct tree *parse_args (void) {
+ PARSE_INIT (type_args);
+ PARSE_TRY_OPT (parse_args1);
+ PARSE_TRY_OPT (parse_args2);
+ PARSE_TRY_OPT (parse_args3);
+ PARSE_TRY_OPT (parse_args4);
+ PARSE_FAIL;
+}
+
+struct tree *parse_opt_args (void) {
+ PARSE_INIT (type_opt_args);
+ while (1) {
+ PARSE_TRY_PES (parse_var_ident);
+ if (parse.lex.type == lex_char && *parse.lex.ptr == ':') { break;}
+ }
+ EXPECT (":");
+ PARSE_TRY_PES (parse_type_term);
+ PARSE_OK;
+}
+
+struct tree *parse_final_decl (void) {
+ PARSE_INIT (type_final_decl);
+ PARSE_TRY_OPT (parse_final_new);
+ PARSE_TRY_OPT (parse_final_final);
+ PARSE_TRY_OPT (parse_final_empty);
+ PARSE_FAIL;
+}
+
+struct tree *parse_partial_app_decl (void) {
+ PARSE_INIT (type_partial_app_decl);
+ PARSE_TRY_OPT (parse_partial_type_app_decl);
+ PARSE_TRY_OPT (parse_partial_comb_app_decl);
+ PARSE_FAIL;
+}
+
+struct tree *parse_result_type (void) {
+ PARSE_INIT (type_result_type);
+ PARSE_TRY_PES (parse_boxed_type_ident);
+ if (LEX_CHAR ('<')) {
+ EXPECT ("<");
+ while (1) {
+ PARSE_TRY_PES (parse_expr);
+ if (LEX_CHAR ('>')) { break; }
+ EXPECT (",");
+ }
+ EXPECT (">");
+ PARSE_OK;
+ } else {
+ while (1) {
+ if (LEX_CHAR (';')) { PARSE_OK; }
+ PARSE_TRY_PES (parse_subexpr);
+ }
+ }
+}
+
+struct tree *parse_combinator_decl (void) {
+ PARSE_INIT (type_combinator_decl);
+ PARSE_TRY_PES (parse_full_combinator_id)
+ while (1) {
+ if (LEX_CHAR ('{')) {
+ parse_lex ();
+ PARSE_TRY_PES (parse_opt_args);
+ EXPECT ("}");
+ } else {
+ break;
+ }
+ }
+ while (1) {
+ if (LEX_CHAR ('=')) { break; }
+ PARSE_TRY_PES (parse_args);
+ }
+ EXPECT ("=");
+ PARSE_ADD (type_equals);
+
+ PARSE_TRY_PES (parse_result_type);
+ PARSE_OK;
+}
+
+struct tree *parse_builtin_combinator_decl (void) {
+ PARSE_INIT (type_builtin_combinator_decl);
+ PARSE_TRY_PES (parse_full_combinator_id)
+ EXPECT ("?");
+ EXPECT ("=");
+ PARSE_TRY_PES (parse_boxed_type_ident);
+ PARSE_OK;
+}
+
+struct tree *parse_declaration (void) {
+ PARSE_INIT (type_declaration);
+ PARSE_TRY_OPT (parse_combinator_decl);
+ PARSE_TRY_OPT (parse_partial_app_decl);
+ PARSE_TRY_OPT (parse_final_decl);
+ PARSE_TRY_OPT (parse_builtin_combinator_decl);
+ PARSE_FAIL;
+}
+
+struct tree *parse_constr_declarations (void) {
+ PARSE_INIT (type_constr_declarations);
+ if (parse.lex.type == lex_triple_minus || parse.lex.type == lex_eof) { PARSE_OK; }
+ while (1) {
+ PARSE_TRY_PES (parse_declaration);
+ EXPECT (";");
+ if (parse.lex.type == lex_eof || parse.lex.type == lex_triple_minus) { PARSE_OK; }
+ }
+}
+
+struct tree *parse_fun_declarations (void) {
+ PARSE_INIT (type_fun_declarations);
+ if (parse.lex.type == lex_triple_minus || parse.lex.type == lex_eof) { PARSE_OK; }
+ while (1) {
+ PARSE_TRY_PES (parse_declaration);
+ EXPECT (";");
+ if (parse.lex.type == lex_eof || parse.lex.type == lex_triple_minus) { PARSE_OK; }
+ }
+}
+
+struct tree *parse_program (void) {
+ PARSE_INIT (type_tl_program);
+ while (1) {
+ PARSE_TRY_PES (parse_constr_declarations);
+ if (parse.lex.type == lex_eof) { PARSE_OK; }
+ if (parse.lex.type == lex_error || expect ("---") < 0 || expect ("functions") < 0 || expect ("---") < 0) { PARSE_FAIL; }
+
+ PARSE_TRY_PES (parse_fun_declarations);
+ if (parse.lex.type == lex_eof) { PARSE_OK; }
+ if (parse.lex.type == lex_error || expect ("---") < 0 || expect ("types") < 0 || expect ("---") < 0) { PARSE_FAIL; }
+ }
+}
+
+struct tree *tl_parse_lex (struct parse *_parse) {
+ assert (_parse);
+ load_parse (*_parse);
+ if (parse.lex.type == lex_none) {
+ parse_lex ();
+ }
+ if (parse.lex.type == lex_error) {
+ return 0;
+ }
+ return parse_program ();
+}
+
+int mystrcmp2 (const char *b, int len, const char *a) {
+ int c = strncmp (b, a, len);
+ return c ? a[len] ? -1 : 0 : c;
+}
+
+char *mystrdup (const char *a, int len) {
+ char *z = talloc (len + 1);
+ memcpy (z, a, len);
+ z[len] = 0;
+ return z;
+}
+
+struct tl_program *tl_program_cur;
+#define TL_TRY_PES(x) if (!(x)) { return 0; }
+
+#define tl_type_cmp(a,b) (strcmp (a->id, b->id))
+DEFINE_TREE (tl_type,struct tl_type *,tl_type_cmp,0)
+struct tree_tl_type *tl_type_tree;
+
+DEFINE_TREE (tl_constructor,struct tl_constructor *,tl_type_cmp,0)
+struct tree_tl_constructor *tl_constructor_tree;
+struct tree_tl_constructor *tl_function_tree;
+
+DEFINE_TREE (tl_var,struct tl_var *,tl_type_cmp,0)
+
+struct tl_var_value {
+ struct tl_combinator_tree *ptr;
+ struct tl_combinator_tree *val;
+ int num_val;
+};
+
+#define tl_var_value_cmp(a,b) (((char *)a.ptr) - ((char *)b.ptr))
+struct tl_var_value empty;
+DEFINE_TREE (var_value, struct tl_var_value, tl_var_value_cmp, empty)
+//tree_tl_var_t *tl_var_tree;
+
+DEFINE_TREE (tl_field,char *,strcmp, 0)
+//tree_tl_field_t *tl_field_tree;
+#define TL_FAIL return 0;
+#define TL_INIT(x) struct tl_combinator_tree *x = 0;
+#define TL_TRY(f,x) { struct tl_combinator_tree *_t = f; if (!_t) { TL_FAIL;} x = tl_union (x, _t); if (!x) { TL_FAIL; }}
+#define TL_ERROR(...) fprintf (stderr, __VA_ARGS__);
+#define TL_WARNING(...) fprintf (stderr, __VA_ARGS__);
+
+void tl_set_var_value (struct tree_var_value **T, struct tl_combinator_tree *var, struct tl_combinator_tree *value) {
+ struct tl_var_value t = {.ptr = var, .val = value, .num_val = 0};
+ if (tree_lookup_var_value (*T, t).ptr) {
+ *T = tree_delete_var_value (*T, t);
+ }
+ *T = tree_insert_var_value (*T, t, lrand48 ());
+}
+
+void tl_set_var_value_num (struct tree_var_value **T, struct tl_combinator_tree *var, struct tl_combinator_tree *value, long long num_value) {
+ struct tl_var_value t = {.ptr = var, .val = value, .num_val = num_value};
+ if (tree_lookup_var_value (*T, t).ptr) {
+ *T = tree_delete_var_value (*T, t);
+ }
+ *T = tree_insert_var_value (*T, t, lrand48 ());
+}
+
+struct tl_combinator_tree *tl_get_var_value (struct tree_var_value **T, struct tl_combinator_tree *var) {
+ struct tl_var_value t = {.ptr = var, .val = 0, .num_val = 0};
+ struct tl_var_value r = tree_lookup_var_value (*T, t);
+ return r.ptr ? r.val : 0;
+}
+
+int tl_get_var_value_num (struct tree_var_value **T, struct tl_combinator_tree *var) {
+ struct tl_var_value t = {.ptr = var, .val = 0};
+ struct tl_var_value r = tree_lookup_var_value (*T, t);
+ return r.ptr ? r.num_val : 0;
+}
+
+int namespace_level;
+
+struct tree_tl_var *vars[10];
+struct tree_tl_field *fields[10];
+struct tl_var *last_num_var[10];
+
+int tl_is_type_name (const char *id, int len) {
+ if (len == 1 && *id == '#') { return 1;}
+ int ok = id[0] >= 'A' && id[0] <= 'Z';
+ int i;
+ for (i = 0; i < len - 1; i++) if (id[i] == '.') {
+ ok = id[i + 1] >= 'A' && id[i + 1] <= 'Z';
+ }
+ return ok;
+}
+
+int tl_add_field (char *id) {
+ assert (namespace_level < 10);
+ assert (namespace_level >= 0);
+ if (tree_lookup_tl_field (fields[namespace_level], id)) {
+ return 0;
+ }
+ fields[namespace_level] = tree_insert_tl_field (fields[namespace_level], id, lrand48 ());
+ return 1;
+}
+
+void tl_clear_fields (void) {
+// tree_act_tl_field (fields[namespace_level], (void *)free);
+ fields[namespace_level] = tree_clear_tl_field (fields[namespace_level]);
+}
+
+struct tl_var *tl_add_var (char *id, struct tl_combinator_tree *ptr, int type) {
+ struct tl_var *v = talloc (sizeof (*v));
+ v->id = tstrdup (id);
+ v->type = type;
+ v->ptr = ptr;
+ v->flags = 0;
+ if (tree_lookup_tl_var (vars[namespace_level], v)) {
+ return 0;
+ }
+ vars[namespace_level] = tree_insert_tl_var (vars[namespace_level], v, lrand48 ());
+ if (type) {
+ last_num_var[namespace_level] = v;
+ }
+ return v;
+}
+
+void tl_del_var (struct tl_var *v) {
+// free (v->id);
+ tfree (v, sizeof (*v));
+}
+
+void tl_clear_vars (void) {
+ tree_act_tl_var (vars[namespace_level], tl_del_var);
+ vars[namespace_level] = tree_clear_tl_var (vars[namespace_level]);
+ last_num_var[namespace_level] = 0;
+}
+
+struct tl_var *tl_get_last_num_var (void) {
+ return last_num_var[namespace_level];
+}
+
+struct tl_var *tl_get_var (char *_id, int len) {
+ char *id = mystrdup (_id, len);
+ struct tl_var v = {.id = id};
+ int i;
+ for (i = namespace_level; i >= 0; i--) {
+ struct tl_var *w = tree_lookup_tl_var (vars[i], &v);
+ if (w) {
+ tfree (id, len + 1);
+ return w;
+ }
+ }
+ tfree (id, len + 1);
+ return 0;
+}
+
+void namespace_push (void) {
+ namespace_level ++;
+ assert (namespace_level < 10);
+ tl_clear_vars ();
+ tl_clear_fields ();
+}
+
+void namespace_pop (void) {
+ namespace_level --;
+ assert (namespace_level >= 0);
+}
+
+struct tl_type *tl_get_type (const char *_id, int len) {
+ char *id = mystrdup (_id, len);
+ struct tl_type _t = {.id = id};
+ struct tl_type *r = tree_lookup_tl_type (tl_type_tree, &_t);
+ tfree (id, len + 1);
+ return r;
+}
+
+struct tl_type *tl_add_type (const char *_id, int len, int params_num, long long params_types) {
+ char *id = talloc (len + 1);
+ memcpy (id, _id, len);
+ id[len] = 0;
+ struct tl_type _t = {.id = id};
+ struct tl_type *_r = 0;
+ if ((_r = tree_lookup_tl_type (tl_type_tree, &_t))) {
+ tfree (id, len + 1);
+ if (params_num >= 0 && (_r->params_num != params_num || _r->params_types != params_types)) {
+ TL_ERROR ("Wrong params_num or types for type %s\n", _r->id);
+ return 0;
+ }
+ return _r;
+ }
+ struct tl_type *t = talloc (sizeof (*t));
+ t->id = id;
+ t->print_id = tstrdup (t->id);
+ int i;
+ for (i = 0; i < len; i++) if (t->print_id[i] == '.' || t->print_id[i] == '#' || t->print_id[i] == ' ') {
+ t->print_id[i] = '$';
+ }
+ t->name = 0;
+ t->constructors_num = 0;
+ t->constructors = 0;
+ t->flags = 0;
+ t->real_id = 0;
+ if (params_num >= 0) {
+ assert (params_num <= 64);
+ t->params_num = params_num;
+ t->params_types = params_types;
+ } else {
+ t->flags |= 4;
+ t->params_num = -1;
+ }
+ tl_type_tree = tree_insert_tl_type (tl_type_tree, t, lrand48 ());
+ total_types_num ++;
+ return t;
+}
+
+void tl_add_type_param (struct tl_type *t, int x) {
+ assert (t->flags & 4);
+ assert (t->params_num <= 64);
+ if (x) {
+ t->params_types |= (1ull << (t->params_num ++));
+ } else {
+ t->params_num ++;
+ }
+}
+
+int tl_type_set_params (struct tl_type *t, int x, long long y) {
+ if (t->flags & 4) {
+ t->params_num = x;
+ t->params_types = y;
+ t->flags &= ~4;
+ } else {
+ if (t->params_num != x || t->params_types != y) {
+ fprintf (stderr, "Wrong num of params (type %s)\n", t->id);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+void tl_type_finalize (struct tl_type *t) {
+ t->flags &= ~4;
+}
+
+struct tl_constructor *tl_get_constructor (const char *_id, int len) {
+ char *id = mystrdup (_id, len);
+ struct tl_constructor _t = {.id = id};
+ struct tl_constructor *r = tree_lookup_tl_constructor (tl_constructor_tree, &_t);
+ tfree (id, len + 1);
+ return r;
+}
+
+struct tl_constructor *tl_add_constructor (struct tl_type *a, const char *_id, int len, int force_magic) {
+ assert (a);
+ if (a->flags & 1) {
+ TL_ERROR ("New constructor for type `%s` after final statement\n", a->id);
+ return 0;
+ }
+ int x = 0;
+ while (x < len && (_id[x] != '#' || force_magic)) { x++; }
+ char *id = talloc (x + 1);
+ memcpy (id, _id, x);
+ id[x] = 0;
+
+ unsigned magic = 0;
+ if (x < len) {
+ assert (len - x >= 6 && len - x <= 9);
+ int i;
+ for (i = 1; i < len - x; i++) {
+ magic = (magic << 4) + (_id[x + i] <= '9' ? _id[x + i] - '0' : _id[x + i] - 'a' + 10);
+ }
+ assert (magic && magic != (unsigned)-1);
+ }
+
+ len = x;
+ if (*id != '_') {
+ struct tl_constructor _t = {.id = id};
+ if (tree_lookup_tl_constructor (tl_constructor_tree, &_t)) {
+ TL_ERROR ("Duplicate constructor id `%s`\n", id);
+ tfree (id, len + 1);
+ return 0;
+ }
+ } else {
+ assert (len == 1);
+ }
+
+ struct tl_constructor *t = talloc (sizeof (*t));
+ t->type = a;
+ t->name = magic;
+ t->id = id;
+ t->print_id = tstrdup (id);
+ t->real_id = 0;
+
+ int i;
+ for (i = 0; i < len; i++) if (t->print_id[i] == '.' || t->print_id[i] == '#' || t->print_id[i] == ' ') {
+ t->print_id[i] = '$';
+ }
+
+ t->left = t->right = 0;
+ a->constructors = realloc (a->constructors, sizeof (void *) * (a->constructors_num + 1));
+ assert (a->constructors);
+ a->constructors[a->constructors_num ++] = t;
+ if (*id != '_') {
+ tl_constructor_tree = tree_insert_tl_constructor (tl_constructor_tree, t, lrand48 ());
+ } else {
+ a->flags |= FLAG_DEFAULT_CONSTRUCTOR;
+ }
+ total_constructors_num ++;
+ return t;
+}
+
+struct tl_constructor *tl_get_function (const char *_id, int len) {
+ char *id = mystrdup (_id, len);
+ struct tl_constructor _t = {.id = id};
+ struct tl_constructor *r = tree_lookup_tl_constructor (tl_function_tree, &_t);
+ tfree (id, len + 1);
+ return r;
+}
+
+struct tl_constructor *tl_add_function (struct tl_type *a, const char *_id, int len, int force_magic) {
+// assert (a);
+ int x = 0;
+ while (x < len && ((_id[x] != '#') || force_magic)) { x++; }
+ char *id = talloc (x + 1);
+ memcpy (id, _id, x);
+ id[x] = 0;
+
+ unsigned magic = 0;
+ if (x < len) {
+ assert (len - x >= 6 && len - x <= 9);
+ int i;
+ for (i = 1; i < len - x; i++) {
+ magic = (magic << 4) + (_id[x + i] <= '9' ? _id[x + i] - '0' : _id[x + i] - 'a' + 10);
+ }
+ assert (magic && magic != (unsigned)-1);
+ }
+
+ len = x;
+
+ struct tl_constructor _t = {.id = id};
+ if (tree_lookup_tl_constructor (tl_function_tree, &_t)) {
+ TL_ERROR ("Duplicate function id `%s`\n", id);
+ tfree (id, len + 1);
+ return 0;
+ }
+
+ struct tl_constructor *t = talloc (sizeof (*t));
+ t->type = a;
+ t->name = magic;
+ t->id = id;
+ t->print_id = tstrdup (id);
+ t->real_id = 0;
+
+ int i;
+ for (i = 0; i < len; i++) if (t->print_id[i] == '.' || t->print_id[i] == '#' || t->print_id[i] == ' ') {
+ t->print_id[i] = '$';
+ }
+
+ t->left = t->right = 0;
+ tl_function_tree = tree_insert_tl_constructor (tl_function_tree, t, lrand48 ());
+ total_functions_num ++;
+ return t;
+}
+
+static char buf[(1 << 20)];
+int buf_pos;
+
+struct tl_combinator_tree *alloc_ctree_node (void) {
+ struct tl_combinator_tree *T = talloc (sizeof (*T));
+ assert (T);
+ memset (T, 0, sizeof (*T));
+ return T;
+}
+
+struct tl_combinator_tree *tl_tree_dup (struct tl_combinator_tree *T) {
+ if (!T) { return 0; }
+ struct tl_combinator_tree *S = talloc (sizeof (*S));
+ memcpy (S, T, sizeof (*S));
+ S->left = tl_tree_dup (T->left);
+ S->right = tl_tree_dup (T->right);
+ return S;
+}
+
+struct tl_type *tl_tree_get_type (struct tl_combinator_tree *T) {
+ assert (T->type == type_type);
+ if (T->act == act_array) { return 0;}
+ while (T->left) {
+ T = T->left;
+ if (T->act == act_array) { return 0;}
+ assert (T->type == type_type);
+ }
+ assert (T->act == act_type || T->act == act_var || T->act == act_array);
+ return T->act == act_type ? T->data : 0;
+}
+
+void tl_tree_set_len (struct tl_combinator_tree *T) {
+ TL_INIT (H);
+ H = T;
+ while (H->left) {
+ H->left->type_len = H->type_len + 1;
+ H = H->left;
+ }
+ assert (H->type == type_type);
+ struct tl_type *t = H->data;
+ assert (t);
+ assert (H->type_len == t->params_num);
+}
+
+void tl_buf_reset (void) {
+ buf_pos = 0;
+}
+
+void tl_buf_add_string (char *s, int len) {
+ if (len < 0) { len = strlen (s); }
+ buf[buf_pos ++] = ' ';
+ memcpy (buf + buf_pos, s, len); buf_pos += len;
+ buf[buf_pos] = 0;
+}
+
+void tl_buf_add_string_nospace (char *s, int len) {
+ if (len < 0) { len = strlen (s); }
+// if (buf_pos) { buf[buf_pos ++] = ' '; }
+ memcpy (buf + buf_pos, s, len); buf_pos += len;
+ buf[buf_pos] = 0;
+}
+
+void tl_buf_add_string_q (char *s, int len, int x) {
+ if (x) {
+ tl_buf_add_string (s, len);
+ } else {
+ tl_buf_add_string_nospace (s, len);
+ }
+}
+
+
+void tl_buf_add_tree (struct tl_combinator_tree *T, int x) {
+ if (!T) { return; }
+ assert (T != (void *)-1l && T != (void *)-2l);
+ switch (T->act) {
+ case act_question_mark:
+ tl_buf_add_string_q ("?", -1, x);
+ return;
+ case act_type:
+ if ((T->flags & 1) && !(T->flags & 4)) {
+ tl_buf_add_string_q ("%", -1, x);
+ x = 0;
+ }
+ if (T->flags & 2) {
+ tl_buf_add_string_q ((char *)T->data, -1, x);
+ } else {
+ struct tl_type *t = T->data;
+ if (T->flags & 4) {
+ assert (t->constructors_num == 1);
+ tl_buf_add_string_q (t->constructors[0]->real_id ? t->constructors[0]->real_id : t->constructors[0]->id, -1, x);
+ } else {
+ tl_buf_add_string_q (t->real_id ? t->real_id : t->id, -1, x);
+ }
+ }
+ return;
+ case act_field:
+ if (T->data) {
+ tl_buf_add_string_q ((char *)T->data, -1, x);
+ x = 0;
+ tl_buf_add_string_q (":", -1, 0);
+ }
+ tl_buf_add_tree (T->left, x);
+ tl_buf_add_tree (T->right, 1);
+ return;
+ case act_union:
+ tl_buf_add_tree (T->left, x);
+ tl_buf_add_tree (T->right, 1);
+ return;
+ case act_var:
+ {
+ if (T->data == (void *)-1l) { return; }
+ struct tl_combinator_tree *v = T->data;
+ tl_buf_add_string_q ((char *)v->data, -1, x);
+ if (T->type == type_num && T->type_flags) {
+ static char _buf[30];
+ sprintf (_buf, "+%lld", T->type_flags);
+ tl_buf_add_string_q (_buf, -1, 0);
+ }
+ }
+ return;
+ case act_arg:
+ tl_buf_add_tree (T->left, x);
+ tl_buf_add_tree (T->right, 1);
+ return;
+ case act_array:
+ if (T->left && !(T->left->flags & 128)) {
+ tl_buf_add_tree (T->left, x);
+ x = 0;
+ tl_buf_add_string_q ("*", -1, x);
+ }
+ tl_buf_add_string_q ("[", -1, x);
+ tl_buf_add_tree (T->right, 1);
+ tl_buf_add_string_q ("]", -1, 1);
+ return;
+ case act_plus:
+ tl_buf_add_tree (T->left, x);
+ tl_buf_add_string_q ("+", -1, 0);
+ tl_buf_add_tree (T->right, 0);
+ return;
+ case act_nat_const:
+ {
+ static char _buf[30];
+ snprintf (_buf, 29, "%lld", T->type_flags);
+ tl_buf_add_string_q (_buf, -1, x);
+ return;
+ }
+ case act_opt_field:
+ {
+ struct tl_combinator_tree *v = T->left->data;
+ tl_buf_add_string_q ((char *)v->data, -1, x);
+ tl_buf_add_string_q (".", -1, 0);
+ static char _buf[30];
+ sprintf (_buf, "%lld", T->left->type_flags);
+ tl_buf_add_string_q (_buf, -1, 0);
+ tl_buf_add_string_q ("?", -1, 0);
+ tl_buf_add_tree (T->right, 0);
+ return;
+ }
+
+ default:
+ fprintf (stderr, "%s %s\n", TL_ACT (T->act), TL_TYPE (T->type));
+ assert (0);
+ return;
+ }
+}
+
+int tl_count_combinator_name (struct tl_constructor *c) {
+ assert (c);
+ tl_buf_reset ();
+ tl_buf_add_string_nospace (c->real_id ? c->real_id : c->id, -1);
+ tl_buf_add_tree (c->left, 1);
+ tl_buf_add_string ("=", -1);
+ tl_buf_add_tree (c->right, 1);
+ //fprintf (stderr, "%.*s\n", buf_pos, buf);
+ if (!c->name) {
+ c->name = compute_crc32 (buf, buf_pos);
+ }
+ return c->name;
+}
+
+int tl_print_combinator (struct tl_constructor *c) {
+ tl_buf_reset ();
+ tl_buf_add_string_nospace (c->real_id ? c->real_id : c->id, -1);
+ static char _buf[10];
+ sprintf (_buf, "#%08x", c->name);
+ tl_buf_add_string_nospace (_buf, -1);
+ tl_buf_add_tree (c->left, 1);
+ tl_buf_add_string ("=", -1);
+ tl_buf_add_tree (c->right, 1);
+ if (output_expressions >= 1) {
+ fprintf (stderr, "%.*s\n", buf_pos, buf);
+ }
+/* if (!c->name) {
+ c->name = compute_crc32 (buf, buf_pos);
+ }*/
+ return c->name;
+}
+
+int _tl_finish_subtree (struct tl_combinator_tree *R, int x, long long y) {
+ assert (R->type == type_type);
+ assert (R->type_len < 0);
+ assert (R->act == act_arg || R->act == act_type);
+ R->type_len = x;
+ R->type_flags = y;
+ if (R->act == act_type) {
+ struct tl_type *t = R->data;
+ assert (t);
+ return tl_type_set_params (t, x, y);
+ }
+ assert ((R->right->type == type_type && R->right->type_len == 0) || R->right->type == type_num || R->right->type == type_num_value);
+ return _tl_finish_subtree (R->left, x + 1, y * 2 + (R->right->type == type_num || R->right->type == type_num_value));
+}
+
+int tl_finish_subtree (struct tl_combinator_tree *R) {
+ assert (R);
+ if (R->type != type_type) {
+ return 1;
+ }
+ if (R->type_len >= 0) {
+ if (R->type_len > 0) {
+ TL_ERROR ("Not enough params\n");
+ return 0;
+ }
+ return 1;
+ }
+ return _tl_finish_subtree (R, 0, 0);
+}
+
+struct tl_combinator_tree *tl_union (struct tl_combinator_tree *L, struct tl_combinator_tree *R) {
+ if (!L) { return R; }
+ if (!R) { return L; }
+ TL_INIT (v);
+ v = alloc_ctree_node ();
+ v->left = L;
+ v->right = R;
+ switch (L->type) {
+ case type_num:
+ if (R->type != type_num_value) {
+ TL_ERROR ("Union: type mistmatch\n");
+ return 0;
+ }
+ tfree (v, sizeof (*v));
+ L->type_flags += R->type_flags;
+ return L;
+ case type_num_value:
+ if (R->type != type_num_value && R->type != type_num) {
+ TL_ERROR ("Union: type mistmatch\n");
+ return 0;
+ }
+ tfree (v, sizeof (*v));
+ R->type_flags += L->type_flags;
+ return R;
+ case type_list_item:
+ case type_list:
+ if (R->type != type_list_item) {
+ TL_ERROR ("Union: type mistmatch\n");
+ return 0;
+ }
+ v->type = type_list;
+ v->act = act_union;
+ return v;
+ case type_type:
+ if (L->type_len == 0) {
+ TL_ERROR ("Arguments number exceeds type arity\n");
+ return 0;
+ }
+ if (R->type != type_num && R->type != type_type && R->type != type_num_value) {
+ TL_ERROR ("Union: type mistmatch\n");
+ return 0;
+ }
+ if (R->type_len < 0) {
+ if (!tl_finish_subtree (R)) {
+ return 0;
+ }
+ }
+ if (R->type_len > 0) {
+ TL_ERROR ("Argument type must have full number of arguments\n");
+ return 0;
+ }
+ if (L->type_len > 0 && ((L->type_flags & 1) != (R->type == type_num || R->type == type_num_value))) {
+ TL_ERROR ("Argument types mistmatch: L->type_flags = %lld, R->type = %s\n", L->flags, TL_TYPE (R->type));
+ return 0;
+ }
+ v->type = type_type;
+ v->act = act_arg;
+ v->type_len = L->type_len > 0 ? L->type_len - 1 : -1;
+ v->type_flags = L->type_flags >> 1;
+ return v;
+ default:
+ assert (0);
+ return 0;
+ }
+}
+
+struct tl_combinator_tree *tl_parse_any_term (struct tree *T, int s);
+struct tl_combinator_tree *tl_parse_term (struct tree *T, int s) {
+ assert (T->type == type_term);
+ int i = 0;
+ while (i < T->nc && T->c[i]->type == type_percent) { i ++; s ++; }
+ assert (i < T->nc);
+ TL_INIT (L);
+ while (i < T->nc) {
+ TL_TRY (tl_parse_any_term (T->c[i], s), L);
+ s = 0;
+ i ++;
+ }
+ return L;
+}
+
+
+struct tl_combinator_tree *tl_parse_type_term (struct tree *T, int s) {
+ assert (T->type == type_type_term);
+ assert (T->nc == 1);
+ struct tl_combinator_tree *Z = tl_parse_term (T->c[0], s);
+ if (!Z || Z->type != type_type) { if (Z) { TL_ERROR ("type_term: found type %s\n", TL_TYPE (Z->type)); } TL_FAIL; }
+ return Z;
+}
+
+struct tl_combinator_tree *tl_parse_nat_term (struct tree *T, int s) {
+ assert (T->type == type_nat_term);
+ assert (T->nc == 1);
+ struct tl_combinator_tree *Z = tl_parse_term (T->c[0], s);
+ if (!Z || (Z->type != type_num && Z->type != type_num_value)) { if (Z) { TL_ERROR ("nat_term: found type %s\n", TL_TYPE (Z->type)); }TL_FAIL; }
+ return Z;
+}
+
+struct tl_combinator_tree *tl_parse_subexpr (struct tree *T, int s) {
+ assert (T->type == type_subexpr);
+ assert (T->nc >= 1);
+ int i;
+ TL_INIT (L);
+ for (i = 0; i < T->nc; i++) {
+ TL_TRY (tl_parse_any_term (T->c[i], s), L);
+ s = 0;
+ }
+ return L;
+}
+
+struct tl_combinator_tree *tl_parse_expr (struct tree *T, int s) {
+ assert (T->type == type_expr);
+ assert (T->nc >= 1);
+ int i;
+ TL_INIT (L);
+ for (i = 0; i < T->nc; i++) {
+ TL_TRY (tl_parse_subexpr (T->c[i], s), L);
+ s = 0;
+ }
+ return L;
+}
+
+struct tl_combinator_tree *tl_parse_nat_const (struct tree *T, int s) {
+ assert (T->type == type_nat_const);
+ assert (!T->nc);
+ if (s > 0) {
+ TL_ERROR ("Nat const can not preceed with %%\n");
+ TL_FAIL;
+ }
+ assert (T->type == type_nat_const);
+ assert (!T->nc);
+ TL_INIT (L);
+ L = alloc_ctree_node ();
+ L->act = act_nat_const;
+ L->type = type_num_value;
+ int i;
+ long long x = 0;
+ for (i = 0; i < T->len; i++) {
+ x = x * 10 + T->text[i] - '0';
+ }
+ L->type_flags = x;
+ return L;
+}
+
+struct tl_combinator_tree *tl_parse_ident (struct tree *T, int s) {
+ assert (T->type == type_type_ident || T->type == type_var_ident || T->type == type_boxed_type_ident);
+ assert (!T->nc);
+ struct tl_var *v = tl_get_var (T->text, T->len);
+ TL_INIT (L);
+ if (v) {
+ L = alloc_ctree_node ();
+ L->act = act_var;
+ L->type = v->type ? type_num : type_type;
+ if (L->type == type_num && s) {
+ TL_ERROR ("Nat var can not preceed with %%\n");
+ TL_FAIL;
+ } else {
+ if (s) {
+ L->flags |= 1;
+ }
+ }
+ L->type_len = 0;
+ L->type_flags = 0;
+ L->data = v->ptr;
+ return L;
+ }
+
+/* if (!mystrcmp2 (T->text, T->len, "#") || !mystrcmp2 (T->text, T->len, "Type")) {
+ L = alloc_ctree_node ();
+ L->act = act_type;
+ L->flags |= 2;
+ L->data = tl_get_type (T->text, T->len);
+ assert (L->data);
+ L->type = type_type;
+ L->type_len = 0;
+ L->type_flags = 0;
+ return L;
+ }*/
+
+ struct tl_constructor *c = tl_get_constructor (T->text, T->len);
+ if (c) {
+ assert (c->type);
+ if (c->type->constructors_num != 1) {
+ TL_ERROR ("Constructor can be used only if it is the only constructor of the type\n");
+ return 0;
+ }
+ c->type->flags |= 1;
+ L = alloc_ctree_node ();
+ L->act = act_type;
+ L->flags |= 5;
+ L->data = c->type;
+ L->type = type_type;
+ L->type_len = c->type->params_num;
+ L->type_flags = c->type->params_types;
+ return L;
+ }
+ int x = tl_is_type_name (T->text, T->len);
+ if (x) {
+ struct tl_type *t = tl_add_type (T->text, T->len, -1, 0);
+ L = alloc_ctree_node ();
+ if (s) {
+ L->flags |= 1;
+ t->flags |= 8;
+ }
+ L->act = act_type;
+ L->data = t;
+ L->type = type_type;
+ L->type_len = t->params_num;
+ L->type_flags = t->params_types;
+ return L;
+ } else {
+ TL_ERROR ("Not a type/var ident `%.*s`\n", T->len, T->text);
+ return 0;
+ }
+}
+
+struct tl_combinator_tree *tl_parse_any_term (struct tree *T, int s) {
+ switch (T->type) {
+ case type_type_term:
+ return tl_parse_type_term (T, s);
+ case type_nat_term:
+ return tl_parse_nat_term (T, s);
+ case type_term:
+ return tl_parse_term (T, s);
+ case type_expr:
+ return tl_parse_expr (T, s);
+ case type_subexpr:
+ return tl_parse_subexpr (T, s);
+ case type_nat_const:
+ return tl_parse_nat_const (T, s);
+ case type_type_ident:
+ case type_var_ident:
+ return tl_parse_ident (T, s);
+ default:
+ fprintf (stderr, "type = %d\n", T->type);
+ assert (0);
+ return 0;
+ }
+}
+
+struct tl_combinator_tree *tl_parse_multiplicity (struct tree *T) {
+ assert (T->type == type_multiplicity);
+ assert (T->nc == 1);
+ return tl_parse_nat_term (T->c[0], 0);
+}
+
+struct tl_combinator_tree *tl_parse_opt_args (struct tree *T) {
+ assert (T);
+ assert (T->type == type_opt_args);
+ assert (T->nc >= 2);
+ TL_INIT (R);
+ TL_TRY (tl_parse_type_term (T->c[T->nc - 1], 0), R);
+ assert (R->type == type_type && !R->type_len);
+ assert (tl_finish_subtree (R));
+ struct tl_type *t = tl_tree_get_type (R);
+ //assert (t);
+ int tt = -1;
+ if (t && !strcmp (t->id, "#")) {
+ tt = 1;
+ } else if (t && !strcmp (t->id, "Type")) {
+ tt = 0;
+ }
+ if (tt < 0) {
+ TL_ERROR ("Optargs can be only of type # or Type\n");
+ TL_FAIL;
+ }
+
+ int i;
+ for (i = 0; i < T->nc - 1; i++) {
+ if (T->c[i]->type != type_var_ident) {
+ TL_ERROR ("Variable name expected\n");
+ TL_FAIL;
+ }
+ if (T->c[i]->len == 1 && *T->c[i]->text == '_') {
+ TL_ERROR ("Variables can not be unnamed\n");
+ TL_FAIL;
+ }
+ }
+ TL_INIT (H);
+// for (i = T->nc - 2; i >= (T->nc >= 2 ? 0 : -1); i--) {
+ for (i = 0; i <= T->nc - 2; i++) {
+ TL_INIT (S); S = alloc_ctree_node ();
+ S->left = (i == T->nc - 2) ? R : tl_tree_dup (R) ; S->right = 0;
+ S->type = type_list_item;
+ S->type_len = 0;
+ S->act = act_field;
+ S->data = i >= 0 ? mystrdup (T->c[i]->text, T->c[i]->len) : 0;
+ if (tt >= 0) {
+ assert (S->data);
+ tl_add_var (S->data, S, tt);
+ }
+ S->flags = 33;
+ H = tl_union (H, S);
+ }
+ return H;
+}
+
+struct tl_combinator_tree *tl_parse_args (struct tree *T);
+struct tl_combinator_tree *tl_parse_args2 (struct tree *T) {
+ assert (T);
+ assert (T->type == type_args2);
+ assert (T->nc >= 1);
+ TL_INIT (R);
+ TL_INIT (L);
+ int x = 0;
+ char *field_name = 0;
+ if (T->c[x]->type == type_var_ident_opt || T->c[x]->type == type_var_ident) {
+ field_name = mystrdup (T->c[x]->text, T->c[x]->len);
+ if (!tl_add_field (field_name)) {
+ TL_ERROR ("Duplicate field name %s\n", field_name);
+ TL_FAIL;
+ }
+ x ++;
+ }
+ //fprintf (stderr, "%d %d\n", x, T->nc);
+ if (T->c[x]->type == type_multiplicity) {
+ L = tl_parse_multiplicity (T->c[x]);
+ if (!L) { TL_FAIL;}
+ x ++;
+ } else {
+ struct tl_var *v = tl_get_last_num_var ();
+ if (!v) {
+ TL_ERROR ("Expected multiplicity or nat var\n");
+ TL_FAIL;
+ }
+ L = alloc_ctree_node ();
+ L->act = act_var;
+ L->type = type_num;
+ L->flags |= 128;
+ L->type_len = 0;
+ L->type_flags = 0;
+ L->data = v->ptr;
+ ((struct tl_combinator_tree *)(v->ptr))->flags |= 256;
+ }
+ namespace_push ();
+ while (x < T->nc) {
+ TL_TRY (tl_parse_args (T->c[x]), R);
+ x ++;
+ }
+ namespace_pop ();
+ struct tl_combinator_tree *S = alloc_ctree_node ();
+ S->type = type_type;
+ S->type_len = 0;
+ S->act = act_array;
+ S->left = L;
+ S->right = R;
+ //S->data = field_name;
+
+ struct tl_combinator_tree *H = alloc_ctree_node ();
+ H->type = type_list_item;
+ H->act = act_field;
+ H->left = S;
+ H->right = 0;
+ H->data = field_name;
+ H->type_len = 0;
+
+ return H;
+}
+
+void tl_mark_vars (struct tl_combinator_tree *T);
+struct tl_combinator_tree *tl_parse_args134 (struct tree *T) {
+ assert (T);
+ assert (T->type == type_args1 || T->type == type_args3 || T->type == type_args4);
+ assert (T->nc >= 1);
+ TL_INIT (R);
+ TL_TRY (tl_parse_type_term (T->c[T->nc - 1], 0), R);
+ assert (tl_finish_subtree (R));
+ assert (R->type == type_type && !R->type_len);
+ struct tl_type *t = tl_tree_get_type (R);
+ //assert (t);
+ int tt = -1;
+ if (t && !strcmp (t->id, "#")) {
+ tt = 1;
+ } else if (t && !strcmp (t->id, "Type")) {
+ tt = 0;
+ }
+
+/* if (tt >= 0 && T->nc == 1) {
+ TL_ERROR ("Variables can not be unnamed (type %d)\n", tt);
+ }*/
+ int last = T->nc - 2;
+ int excl = 0;
+ if (last >= 0 && T->c[last]->type == type_exclam) {
+ excl ++;
+ tl_mark_vars (R);
+ last --;
+ }
+ if (last >= 0 && T->c[last]->type == type_optional_arg_def) {
+ assert (T->c[last]->nc == 2);
+ TL_INIT (E); E = alloc_ctree_node ();
+ E->type = type_type;
+ E->act = act_opt_field;
+ E->left = tl_parse_ident (T->c[last]->c[0], 0);
+ int i;
+ long long x = 0;
+ for (i = 0; i < T->c[last]->c[1]->len; i++) {
+ x = x * 10 + T->c[last]->c[1]->text[i] - '0';
+ }
+ E->left->type_flags = x;
+ E->type_flags = R->type_flags;
+ E->type_len = R->type_len;
+ E->right = R;
+ R = E;
+ last --;
+ }
+ int i;
+ for (i = 0; i < last; i++) {
+ if (T->c[i]->type != type_var_ident && T->c[i]->type != type_var_ident_opt) {
+ TL_ERROR ("Variable name expected\n");
+ TL_FAIL;
+ }
+/* if (tt >= 0 && (T->nc == 1 || (T->c[i]->len == 1 && *T->c[i]->text == '_'))) {
+ TL_ERROR ("Variables can not be unnamed\n");
+ TL_FAIL;
+ }*/
+ }
+ TL_INIT (H);
+// for (i = T->nc - 2; i >= (T->nc >= 2 ? 0 : -1); i--) {
+ for (i = (last >= 0 ? 0 : -1); i <= last; i++) {
+ TL_INIT (S); S = alloc_ctree_node ();
+ S->left = (i == last) ? R : tl_tree_dup (R) ; S->right = 0;
+ S->type = type_list_item;
+ S->type_len = 0;
+ S->act = act_field;
+ S->data = i >= 0 ? mystrdup (T->c[i]->text, T->c[i]->len) : 0;
+ if (excl) {
+ S->flags |= FLAG_EXCL;
+ }
+ if (S->data && (T->c[i]->len >= 2 || *T->c[i]->text != '_')) {
+ if (!tl_add_field (S->data)) {
+ TL_ERROR ("Duplicate field name %s\n", (char *)S->data);
+ TL_FAIL;
+ }
+ }
+ if (tt >= 0) {
+ //assert (S->data);
+ char *name = S->data;
+ if (!name) {
+ static char s[20];
+ sprintf (s, "%lld", lrand48 () * (1ll << 32) + lrand48 ());
+ name = s;
+ }
+ struct tl_var *v = tl_add_var (name, S, tt);
+ if (!v) {TL_FAIL;}
+ v->flags |= 2;
+ }
+
+ H = tl_union (H, S);
+ }
+ return H;
+}
+
+
+struct tl_combinator_tree *tl_parse_args (struct tree *T) {
+ assert (T->type == type_args);
+ assert (T->nc == 1);
+ switch (T->c[0]->type) {
+ case type_args1:
+ return tl_parse_args134 (T->c[0]);
+ case type_args2:
+ return tl_parse_args2 (T->c[0]);
+ case type_args3:
+ return tl_parse_args134 (T->c[0]);
+ case type_args4:
+ return tl_parse_args134 (T->c[0]);
+ default:
+ assert (0);
+ return 0;
+ }
+}
+
+void tl_mark_vars (struct tl_combinator_tree *T) {
+ if (!T) { return; }
+ if (T->act == act_var) {
+ char *id = ((struct tl_combinator_tree *)(T->data))->data;
+ struct tl_var *v = tl_get_var (id, strlen (id));
+ assert (v);
+ v->flags |= 1;
+ }
+ tl_mark_vars (T->left);
+ tl_mark_vars (T->right);
+}
+
+struct tl_combinator_tree *tl_parse_result_type (struct tree *T) {
+ assert (T->type == type_result_type);
+ assert (T->nc >= 1);
+ assert (T->nc <= 64);
+
+ TL_INIT (L);
+
+ if (tl_get_var (T->c[0]->text, T->c[0]->len)) {
+ if (T->nc != 1) {
+ TL_ERROR ("Variable can not take params\n");
+ TL_FAIL;
+ }
+ L = alloc_ctree_node ();
+ L->act = act_var;
+ L->type = type_type;
+ struct tl_var *v = tl_get_var (T->c[0]->text, T->c[0]->len);
+ if (v->type) {
+ TL_ERROR ("Type mistmatch\n");
+ TL_FAIL;
+ }
+ L->data = v->ptr;
+// assert (v->ptr);
+ } else {
+ L = alloc_ctree_node ();
+ L->act = act_type;
+ L->type = type_type;
+ struct tl_type *t = tl_add_type (T->c[0]->text, T->c[0]->len, -1, 0);
+ assert (t);
+ L->type_len = t->params_num;
+ L->type_flags = t->params_types;
+ L->data = t;
+
+ int i;
+ for (i = 1; i < T->nc; i++) {
+ TL_TRY (tl_parse_any_term (T->c[i], 0), L);
+ assert (L->right);
+ assert (L->right->type == type_num || L->right->type == type_num_value || (L->right->type == type_type && L->right->type_len == 0));
+ }
+ }
+
+ if (!tl_finish_subtree (L)) {
+ TL_FAIL;
+ }
+
+ tl_mark_vars (L);
+ return L;
+}
+
+int __ok;
+void tl_var_check_used (struct tl_var *v) {
+ __ok = __ok && (v->flags & 3);
+}
+
+int tl_parse_combinator_decl (struct tree *T, int fun) {
+ assert (T->type == type_combinator_decl);
+ assert (T->nc >= 3);
+ namespace_level = 0;
+ tl_clear_vars ();
+ tl_clear_fields ();
+ TL_INIT (L);
+ TL_INIT (R);
+
+ int i = 1;
+ while (i < T->nc - 2 && T->c[i]->type == type_opt_args) {
+ TL_TRY (tl_parse_opt_args (T->c[i]), L);
+ i++;
+ }
+ while (i < T->nc - 2 && T->c[i]->type == type_args) {
+ TL_TRY (tl_parse_args (T->c[i]), L);
+ i++;
+ }
+ assert (i == T->nc - 2 && T->c[i]->type == type_equals);
+ i ++;
+
+ R = tl_parse_result_type (T->c[i]);
+ if (!R) { TL_FAIL; }
+
+ struct tl_type *t = tl_tree_get_type (R);
+ if (!fun && !t) {
+ TL_ERROR ("Only functions can return variables\n");
+ }
+ assert (t || fun);
+
+ assert (namespace_level == 0);
+ __ok = 1;
+ tree_act_tl_var (vars[0], tl_var_check_used);
+ if (!__ok) {
+ TL_ERROR ("Not all variables are used in right side\n");
+ TL_FAIL;
+ }
+
+ if (tl_get_constructor (T->c[0]->text, T->c[0]->len) || tl_get_function (T->c[0]->text, T->c[0]->len)) {
+ TL_ERROR ("Duplicate combinator id %.*s\n", T->c[0]->len, T->c[0]->text);
+ return 0;
+ }
+ struct tl_constructor *c = !fun ? tl_add_constructor (t, T->c[0]->text, T->c[0]->len, 0) : tl_add_function (t, T->c[0]->text, T->c[0]->len, 0);
+ if (!c) { TL_FAIL; }
+ c->left = L;
+ c->right = R;
+
+ if (!c->name) {
+ tl_count_combinator_name (c);
+ }
+ tl_print_combinator (c);
+
+ return 1;
+}
+
+void change_var_ptrs (struct tl_combinator_tree *O, struct tl_combinator_tree *D, struct tree_var_value **V) {
+ if (!O || !D) {
+ assert (!O && !D);
+ return;
+ }
+ if (O->act == act_field) {
+ struct tl_type *t = tl_tree_get_type (O->left);
+ if (t && (!strcmp (t->id, "#") || !strcmp (t->id, "Type"))) {
+ tl_set_var_value (V, O, D);
+ }
+ }
+ if (O->act == act_var) {
+ assert (D->data == O->data);
+ D->data = tl_get_var_value (V, O->data);
+ assert (D->data);
+ }
+ change_var_ptrs (O->left, D->left, V);
+ change_var_ptrs (O->right, D->right, V);
+}
+
+struct tl_combinator_tree *change_first_var (struct tl_combinator_tree *O, struct tl_combinator_tree **X, struct tl_combinator_tree *Y) {
+ if (!O) { return (void *)-2l; };
+ if (O->act == act_field && !*X) {
+ struct tl_type *t = tl_tree_get_type (O->left);
+ if (t && !strcmp (t->id, "#")) {
+ if (Y->type != type_num && Y->type != type_num_value) {
+ TL_ERROR ("change_var: Type mistmatch\n");
+ return 0;
+ } else {
+ *X = O;
+ return (void *)-1l;
+ }
+ }
+ if (t && !strcmp (t->id, "Type")) {
+ if (Y->type != type_type || Y->type_len != 0) {
+ TL_ERROR ("change_var: Type mistmatch\n");
+ return 0;
+ } else {
+ *X = O;
+ return (void *)-1l;
+ }
+ }
+ }
+ if (O->act == act_var) {
+ if (O->data == *X) {
+ struct tl_combinator_tree *R = tl_tree_dup (Y);
+ if (O->type == type_num || O->type == type_num_value) { R->type_flags += O->type_flags; }
+ return R;
+ }
+ }
+ struct tl_combinator_tree *t;
+ t = change_first_var (O->left, X, Y);
+ if (!t) { return 0;}
+ if (t == (void *)-1l) {
+ t = change_first_var (O->right, X, Y);
+ if (!t) { return 0;}
+ if (t == (void *)-1l) { return (void *)-1l; }
+ if (t != (void *)-2l) { return t;}
+ return (void *)-1l;
+ }
+ if (t != (void *)-2l) {
+ O->left = t;
+ }
+ t = change_first_var (O->right, X, Y);
+ if (!t) { return 0;}
+ if (t == (void *)-1l) {
+ return O->left;
+ }
+ if (t != (void *)-2l) {
+ O->right = t;
+ }
+ return O;
+}
+
+
+int uniformize (struct tl_combinator_tree *L, struct tl_combinator_tree *R, struct tree_var_value **T);
+struct tree_var_value **_T;
+int __tok;
+void check_nat_val (struct tl_var_value v) {
+ if (!__tok) { return; }
+ long long x = v.num_val;
+ struct tl_combinator_tree *L = v.val;
+ if (L->type == type_type) { return;}
+ while (1) {
+ if (L->type == type_num_value) {
+ if (x + L->type_flags < 0) {
+ __tok = 0;
+ return;
+ } else {
+ return;
+ }
+ }
+ assert (L->type == type_num);
+ x += L->type_flags;
+ x += tl_get_var_value_num (_T, L->data);
+ L = tl_get_var_value (_T, L->data);
+ if (!L) { return;}
+ }
+}
+
+int check_constructors_equal (struct tl_combinator_tree *L, struct tl_combinator_tree *R, struct tree_var_value **T) {
+ if (!uniformize (L, R, T)) { return 0; }
+ __tok = 1;
+ _T = T;
+ tree_act_var_value (*T, check_nat_val);
+ return __tok;
+}
+
+struct tl_combinator_tree *reduce_type (struct tl_combinator_tree *A, struct tl_type *t) {
+ assert (A);
+ if (A->type_len == t->params_num) {
+ assert (A->type_flags == t->params_types);
+ A->act = act_type;
+ A->type = type_type;
+ A->left = A->right = 0;
+ A->data = t;
+ return A;
+ }
+ A->left = reduce_type (A->left, t);
+ return A;
+}
+
+struct tl_combinator_tree *change_value_var (struct tl_combinator_tree *O, struct tree_var_value **X) {
+ if (!O) { return (void *)-2l; };
+ while (O->act == act_var) {
+ assert (O->data);
+ if (!tl_get_var_value (X, O->data)) {
+ break;
+ }
+ if (O->type == type_type) {
+ O = tl_tree_dup (tl_get_var_value (X, O->data));
+ } else {
+ long long n = tl_get_var_value_num (X, O->data);
+ struct tl_combinator_tree *T = tl_get_var_value (X, O->data);
+ O->data = T->data;
+ O->type = T->type;
+ O->act = T->act;
+ O->type_flags = O->type_flags + n + T->type_flags;
+ }
+ }
+ if (O->act == act_field) {
+ if (tl_get_var_value (X, O)) { return (void *)-1l; }
+ }
+ struct tl_combinator_tree *t;
+ t = change_value_var (O->left, X);
+ if (!t) { return 0;}
+ if (t == (void *)-1l) {
+ t = change_value_var (O->right, X);
+ if (!t) { return 0;}
+ if (t == (void *)-1l) { return (void *)-1l; }
+ if (t != (void *)-2l) { return t;}
+ return (void *)-1l;
+ }
+ if (t != (void *)-2l) {
+ O->left = t;
+ }
+ t = change_value_var (O->right, X);
+ if (!t) { return 0;}
+ if (t == (void *)-1l) {
+ return O->left;
+ }
+ if (t != (void *)-2l) {
+ O->right = t;
+ }
+ return O;
+}
+
+int tl_parse_partial_type_app_decl (struct tree *T) {
+ assert (T->type == type_partial_type_app_decl);
+ assert (T->nc >= 1);
+
+ assert (T->c[0]->type == type_boxed_type_ident);
+ struct tl_type *t = tl_get_type (T->c[0]->text, T->c[0]->len);
+ if (!t) {
+ TL_ERROR ("Can not make partial app for unknown type\n");
+ return 0;
+ }
+
+ tl_type_finalize (t);
+
+ struct tl_combinator_tree *L = tl_parse_ident (T->c[0], 0);
+ assert (L);
+ int i;
+ tl_buf_reset ();
+ int cc = T->nc - 1;
+ for (i = 1; i < T->nc; i++) {
+ TL_TRY (tl_parse_any_term (T->c[i], 0), L);
+ tl_buf_add_tree (L->right, 1);
+ }
+
+ while (L->type_len) {
+ struct tl_combinator_tree *C = alloc_ctree_node ();
+ C->act = act_var;
+ C->type = (L->type_flags & 1) ? type_num : type_type;
+ C->type_len = 0;
+ C->type_flags = 0;
+ C->data = (void *)-1l;
+ L = tl_union (L, C);
+ if (!L) { return 0; }
+ }
+
+
+ static char _buf[100000];
+ snprintf (_buf, 100000, "%s%.*s", t->id, buf_pos, buf);
+ struct tl_type *nt = tl_add_type (_buf, strlen (_buf), t->params_num - cc, t->params_types >> cc);
+ assert (nt);
+ //snprintf (_buf, 100000, "%s #", t->id);
+ //nt->real_id = strdup (_buf);
+
+ for (i = 0; i < t->constructors_num; i++) {
+ struct tl_constructor *c = t->constructors[i];
+ struct tree_var_value *V = 0;
+ TL_INIT (A);
+ TL_INIT (B);
+ A = tl_tree_dup (c->left);
+ B = tl_tree_dup (c->right);
+
+ struct tree_var_value *W = 0;
+ change_var_ptrs (c->left, A, &W);
+ change_var_ptrs (c->right, B, &W);
+
+
+ if (!check_constructors_equal (B, L, &V)) { continue; }
+ B = reduce_type (B, nt);
+ A = change_value_var (A, &V);
+ if (A == (void *)-1l) { A = 0;}
+ B = change_value_var (B, &V);
+ assert (B != (void *)-1l);
+ snprintf (_buf, 100000, "%s%.*s", c->id, buf_pos, buf);
+
+ struct tl_constructor *r = tl_add_constructor (nt, _buf, strlen (_buf), 1);
+ snprintf (_buf, 100000, "%s", c->id);
+ r->real_id = tstrdup (_buf);
+
+ r->left = A;
+ r->right = B;
+ if (!r->name) {
+ tl_count_combinator_name (r);
+ }
+ tl_print_combinator (r);
+ }
+
+ return 1;
+}
+
+int tl_parse_partial_comb_app_decl (struct tree *T, int fun) {
+ assert (T->type == type_partial_comb_app_decl);
+
+ struct tl_constructor *c = !fun ? tl_get_constructor (T->c[0]->text, T->c[0]->len) : tl_get_function (T->c[0]->text, T->c[0]->len);
+ if (!c) {
+ TL_ERROR ("Can not make partial app for undefined combinator\n");
+ return 0;
+ }
+
+ //TL_INIT (K);
+ //static char buf[1000];
+ //int x = sprintf (buf, "%s", c->id);
+ TL_INIT (L);
+ TL_INIT (R);
+ L = tl_tree_dup (c->left);
+ R = tl_tree_dup (c->right);
+
+
+ struct tree_var_value *V = 0;
+ change_var_ptrs (c->left, L, &V);
+ change_var_ptrs (c->right, R, &V);
+ V = tree_clear_var_value (V);
+
+ int i;
+ tl_buf_reset ();
+ for (i = 1; i < T->nc; i++) {
+ TL_INIT (X);
+ TL_INIT (Z);
+ X = tl_parse_any_term (T->c[i], 0);
+ struct tl_combinator_tree *K = 0;
+ if (!(Z = change_first_var (L, &K, X))) {
+ TL_FAIL;
+ }
+ L = Z;
+ if (!K) {
+ TL_ERROR ("Partial app: not enougth variables (i = %d)\n", i);
+ TL_FAIL;
+ }
+ if (!(Z = change_first_var (R, &K, X))) {
+ TL_FAIL;
+ }
+ assert (Z == R);
+ tl_buf_add_tree (X, 1);
+ }
+
+ static char _buf[100000];
+ snprintf (_buf, 100000, "%s%.*s", c->id, buf_pos, buf);
+// fprintf (stderr, "Local id: %s\n", _buf);
+
+ struct tl_constructor *r = !fun ? tl_add_constructor (c->type, _buf, strlen (_buf), 1) : tl_add_function (c->type, _buf, strlen (_buf), 1);
+ r->left = L;
+ r->right = R;
+ snprintf (_buf, 100000, "%s", c->id);
+ r->real_id = tstrdup (_buf);
+ if (!r->name) {
+ tl_count_combinator_name (r);
+ }
+ tl_print_combinator (r);
+ return 1;
+}
+
+
+int tl_parse_partial_app_decl (struct tree *T, int fun) {
+ assert (T->type == type_partial_app_decl);
+ assert (T->nc == 1);
+ if (T->c[0]->type == type_partial_comb_app_decl) {
+ return tl_parse_partial_comb_app_decl (T->c[0], fun);
+ } else {
+ if (fun) {
+ TL_ERROR ("Partial type app in functions block\n");
+ TL_FAIL;
+ }
+ return tl_parse_partial_type_app_decl (T->c[0]);
+ }
+}
+
+int tl_parse_final_final (struct tree *T) {
+ assert (T->type == type_final_final);
+ assert (T->nc == 1);
+ struct tl_type *R;
+ if ((R = tl_get_type (T->c[0]->text, T->c[0]->len))) {
+ R->flags |= 1;
+ return 1;
+ } else {
+ TL_ERROR ("Final statement for type `%.*s` before declaration\n", T->c[0]->len, T->c[0]->text);
+ TL_FAIL;
+ }
+}
+
+int tl_parse_final_new (struct tree *T) {
+ assert (T->type == type_final_new);
+ assert (T->nc == 1);
+ if (tl_get_type (T->c[0]->text, T->c[0]->len)) {
+ TL_ERROR ("New statement: type `%.*s` already declared\n", T->c[0]->len, T->c[0]->text);
+ TL_FAIL;
+ } else {
+ return 1;
+ }
+}
+
+int tl_parse_final_empty (struct tree *T) {
+ assert (T->type == type_final_empty);
+ assert (T->nc == 1);
+ if (tl_get_type (T->c[0]->text, T->c[0]->len)) {
+ TL_ERROR ("New statement: type `%.*s` already declared\n", T->c[0]->len, T->c[0]->text);
+ TL_FAIL;
+ }
+ struct tl_type *t = tl_add_type (T->c[0]->text, T->c[0]->len, 0, 0);
+ assert (t);
+ t->flags |= 1 | FLAG_EMPTY;
+ return 1;
+}
+
+int tl_parse_final_decl (struct tree *T, int fun) {
+ assert (T->type == type_final_decl);
+ assert (!fun);
+ assert (T->nc == 1);
+ switch (T->c[0]->type) {
+ case type_final_new:
+ return tl_parse_final_new (T->c[0]);
+ case type_final_final:
+ return tl_parse_final_final (T->c[0]);
+ case type_final_empty:
+ return tl_parse_final_empty (T->c[0]);
+ default:
+ assert (0);
+ return 0;
+ }
+}
+
+int tl_parse_builtin_combinator_decl (struct tree *T, int fun) {
+ if (fun) {
+ TL_ERROR ("Builtin type can not be described in function block\n");
+ return -1;
+ }
+ assert (T->type == type_builtin_combinator_decl);
+ assert (T->nc == 2);
+ assert (T->c[0]->type == type_full_combinator_id);
+ assert (T->c[1]->type == type_boxed_type_ident);
+
+
+ if ((!mystrcmp2 (T->c[0]->text, T->c[0]->len, "int") && !mystrcmp2 (T->c[1]->text, T->c[1]->len, "Int")) ||
+ (!mystrcmp2 (T->c[0]->text, T->c[0]->len, "long") && !mystrcmp2 (T->c[1]->text, T->c[1]->len, "Long")) ||
+ (!mystrcmp2 (T->c[0]->text, T->c[0]->len, "double") && !mystrcmp2 (T->c[1]->text, T->c[1]->len, "Double")) ||
+ (!mystrcmp2 (T->c[0]->text, T->c[0]->len, "object") && !mystrcmp2 (T->c[1]->text, T->c[1]->len, "Object")) ||
+ (!mystrcmp2 (T->c[0]->text, T->c[0]->len, "function") && !mystrcmp2 (T->c[1]->text, T->c[1]->len, "Function")) ||
+ (!mystrcmp2 (T->c[0]->text, T->c[0]->len, "string") && !mystrcmp2 (T->c[1]->text, T->c[1]->len, "String"))) {
+ struct tl_type *t = tl_add_type (T->c[1]->text, T->c[1]->len, 0, 0);
+ if (!t) {
+ return 0;
+ }
+ struct tl_constructor *c = tl_add_constructor (t, T->c[0]->text, T->c[0]->len, 0);
+ if (!c) {
+ return 0;
+ }
+
+ c->left = alloc_ctree_node ();
+ c->left->act = act_question_mark;
+ c->left->type = type_list_item;
+
+ c->right = alloc_ctree_node ();
+ c->right->act = act_type;
+ c->right->data = t;
+ c->right->type = type_type;
+
+ if (!c->name) {
+ tl_count_combinator_name (c);
+ }
+ tl_print_combinator (c);
+ } else {
+ TL_ERROR ("Unknown builting type `%.*s`\n", T->c[0]->len, T->c[0]->text);
+ return 0;
+ }
+
+ return 1;
+}
+
+int tl_parse_declaration (struct tree *T, int fun) {
+ assert (T->type == type_declaration);
+ assert (T->nc == 1);
+ switch (T->c[0]->type) {
+ case type_combinator_decl:
+ return tl_parse_combinator_decl (T->c[0], fun);
+ case type_partial_app_decl:
+ return tl_parse_partial_app_decl (T->c[0], fun);
+ case type_final_decl:
+ return tl_parse_final_decl (T->c[0], fun);
+ case type_builtin_combinator_decl:
+ return tl_parse_builtin_combinator_decl (T->c[0], fun);
+ default:
+ assert (0);
+ return 0;
+ }
+}
+
+int tl_parse_constr_declarations (struct tree *T) {
+ assert (T->type == type_constr_declarations);
+ int i;
+ for (i = 0; i < T->nc; i++) {
+ TL_TRY_PES (tl_parse_declaration (T->c[i], 0));
+ }
+ return 1;
+}
+
+int tl_parse_fun_declarations (struct tree *T) {
+ assert (T->type == type_fun_declarations);
+ int i;
+ for (i = 0; i < T->nc; i++) {
+ TL_TRY_PES (tl_parse_declaration (T->c[i], 1));
+ }
+ return 1;
+}
+
+int tl_tree_lookup_value (struct tl_combinator_tree *L, void *var, struct tree_var_value **T) {
+ if (!L) {
+ return -1;
+ }
+ if (L->act == act_var && L->data == var) {
+ return 0;
+ }
+ if (L->act == act_var) {
+ struct tl_combinator_tree *E = tl_get_var_value (T, L->data);
+ if (!E) { return -1;}
+ else { return tl_tree_lookup_value (E, var, T); }
+ }
+ if (tl_tree_lookup_value (L->left, var, T) >= 0) { return 1; }
+ if (tl_tree_lookup_value (L->right, var, T) >= 0) { return 1; }
+ return -1;
+}
+
+int tl_tree_lookup_value_nat (struct tl_combinator_tree *L, void *var, long long x, struct tree_var_value **T) {
+ assert (L);
+ if (L->type == type_num_value) { return -1; }
+ assert (L->type == type_num);
+ assert (L->act == act_var);
+ if (L->data == var) {
+ return x == L->type_flags ? 0 : 1;
+ } else {
+ if (!tl_get_var_value (T, L->data)) {
+ return -1;
+ }
+ return tl_tree_lookup_value_nat (tl_get_var_value (T, L->data), var, x + tl_get_var_value_num (T, L->data), T);
+ }
+
+}
+
+int uniformize (struct tl_combinator_tree *L, struct tl_combinator_tree *R, struct tree_var_value **T) {
+ if (!L || !R) {
+ assert (!L && !R);
+ return 1;
+ }
+ if (R->act == act_var) {
+ struct tl_combinator_tree *_ = R; R = L; L = _;
+ }
+
+ if (L->type == type_type) {
+ if (R->type != type_type || L->type_len != R->type_len || L->type_flags != R->type_flags) {
+ return 0;
+ }
+ if (R->data == (void *)-1l || L->data == (void *)-1l) { return 1;}
+ if (L->act == act_var) {
+ int x = tl_tree_lookup_value (R, L->data, T);
+ if (x > 0) {
+// if (tl_tree_lookup_value (R, L->data, T) > 0) {
+ return 0;
+ }
+ if (x == 0) {
+ return 1;
+ }
+ struct tl_combinator_tree *E = tl_get_var_value (T, L->data);
+ if (!E) {
+ tl_set_var_value (T, L->data, R);
+ return 1;
+ } else {
+ return uniformize (E, R, T);
+ }
+ } else {
+ if (L->act != R->act || L->data != R->data) {
+ return 0;
+ }
+ return uniformize (L->left, R->left, T) && uniformize (L->right, R->right, T);
+ }
+ } else {
+ assert (L->type == type_num || L->type == type_num_value);
+ if (R->type != type_num && R->type != type_num_value) {
+ return 0;
+ }
+ assert (R->type == type_num || R->type == type_num_value);
+ if (R->data == (void *)-1l || L->data == (void *)-1l) { return 1;}
+ long long x = 0;
+ struct tl_combinator_tree *K = L;
+ while (1) {
+ x += K->type_flags;
+ if (K->type == type_num_value) {
+ break;
+ }
+ if (!tl_get_var_value (T, K->data)) {
+ int s = tl_tree_lookup_value_nat (R, K->data, K->type_flags, T);
+ if (s > 0) {
+ return 0;
+ }
+ if (s == 0) {
+ return 1;
+ }
+ /*tl_set_var_value_num (T, K->data, R, -x);
+ return 1;*/
+ break;
+ }
+ x += tl_get_var_value_num (T, K->data);
+ K = tl_get_var_value (T, K->data);
+ }
+ long long y = 0;
+ struct tl_combinator_tree *M = R;
+ while (1) {
+ y += M->type_flags;
+ if (M->type == type_num_value) {
+ break;
+ }
+ if (!tl_get_var_value (T, M->data)) {
+ int s = tl_tree_lookup_value_nat (L, M->data, M->type_flags, T);
+ if (s > 0) {
+ return 0;
+ }
+ if (s == 0) {
+ return 1;
+ }
+ /*tl_set_var_value_num (T, M->data, L, -y);
+ return 1;*/
+ break;
+ }
+ y += tl_get_var_value_num (T, M->data);
+ M = tl_get_var_value (T, M->data);
+ }
+ if (K->type == type_num_value && M->type == type_num_value) {
+ return x == y;
+ }
+ if (M->type == type_num_value) {
+ tl_set_var_value_num (T, K->data, M, -(x - y + M->type_flags));
+ return 1;
+ } else if (K->type == type_num_value) {
+ tl_set_var_value_num (T, M->data, K, -(y - x + K->type_flags));
+ return 1;
+ } else {
+ if (x >= y) {
+ tl_set_var_value_num (T, K->data, M, -(x - y + M->type_flags));
+ } else {
+ tl_set_var_value_num (T, M->data, K, -(y - x + K->type_flags));
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+void tl_type_check (struct tl_type *t) {
+ if (!__ok) return;
+ if (!strcmp (t->id, "#")) { t->name = 0x70659eff; return; }
+ if (!strcmp (t->id, "Type")) { t->name = 0x2cecf817; return; }
+ if (t->constructors_num <= 0 && !(t->flags & FLAG_EMPTY)) {
+ TL_ERROR ("Type %s has no constructors\n", t->id);
+ __ok = 0;
+ return;
+ }
+ int i, j;
+ t->name = 0;
+ for (i = 0; i < t->constructors_num; i++) {
+ t->name ^= t->constructors[i]->name;
+ }
+ for (i = 0; i < t->constructors_num; i++) {
+ for (j = i + 1; j < t->constructors_num; j++) {
+ struct tree_var_value *v = 0;
+ if (check_constructors_equal (t->constructors[i]->right, t->constructors[j]->right, &v)) {
+ t->flags |= 16;
+ }
+ }
+ }
+ if ((t->flags & 24) == 24) {
+ TL_WARNING ("Warning: Type %s has overlapping costructors, but it is used with `%%`\n", t->id);
+ }
+ int z = 0;
+ int sid = 0;
+ for (i = 0; i < t->constructors_num; i++) if (*t->constructors[i]->id == '_') {
+ z ++;
+ sid = i;
+ }
+ if (z > 1) {
+ TL_ERROR ("Type %s has %d default constructors\n", t->id, z);
+ __ok = 0;
+ return;
+ }
+ if (z == 1 && (t->flags & 8)) {
+ TL_ERROR ("Type %s has default constructors and used bare\n", t->id);
+ __ok = 0;
+ return;
+ }
+ if (z) {
+ struct tl_constructor *c;
+ c = t->constructors[sid];
+ t->constructors[sid] = t->constructors[t->constructors_num - 1];
+ t->constructors[t->constructors_num - 1] = c;
+ }
+}
+
+struct tl_program *tl_parse (struct tree *T) {
+ assert (T);
+ assert (T->type == type_tl_program);
+ int i;
+ tl_program_cur = talloc (sizeof (*tl_program_cur));
+ tl_add_type ("#", 1, 0, 0);
+ tl_add_type ("Type", 4, 0, 0);
+ for (i = 0; i < T->nc; i++) {
+ if (T->c[i]->type == type_constr_declarations) { TL_TRY_PES (tl_parse_constr_declarations (T->c[i])); }
+ else { TL_TRY_PES (tl_parse_fun_declarations (T->c[i])) }
+ }
+ __ok = 1;
+ tree_act_tl_type (tl_type_tree, tl_type_check);
+ if (!__ok) {
+ return 0;
+ }
+ return tl_program_cur;
+}
+
+FILE *__f;
+int num = 0;
+
+void wint (int a) {
+// printf ("%d ", a);
+ a = htole32 (a);
+ assert (fwrite (&a, 1, 4, __f) == 4);
+}
+
+void wdata (const void *x, int len) {
+ assert (fwrite (x, 1, len, __f) == len);
+}
+
+void wstr (const char *s) {
+ if (s) {
+// printf ("\"%s\" ", s);
+ int x = strlen (s);
+ if (x <= 254) {
+ unsigned char x_c = (unsigned char)x;
+ assert (fwrite (&x_c, 1, 1, __f) == 1);
+ } else {
+ fprintf (stderr, "String is too big...\n");
+ assert (0);
+ }
+ wdata (s, x);
+ x ++; // The header, containing the length, which is 1 byte
+ int t = 0;
+ if (x & 3) {
+ // Let's hope it's truly zero on every platform
+ wdata (&t, 4 - (x & 3));
+ }
+ } else {
+// printf ("<none> ");
+ wint (0);
+ }
+}
+
+void wll (long long a) {
+// printf ("%lld ", a);
+ a = htole64 (a);
+ assert (fwrite (&a, 1, 8, __f) == 8);
+}
+
+int count_list_size (struct tl_combinator_tree *T) {
+ assert (T->type == type_list || T->type == type_list_item);
+ if (T->type == type_list_item) {
+ return 1;
+ } else {
+ return count_list_size (T->left) + count_list_size (T->right);
+ }
+}
+
+void write_type_flags (long long flags) {
+ int new_flags = 0;
+ if (flags & 1) {
+ new_flags |= FLAG_BARE;
+ }
+ if (flags & FLAG_DEFAULT_CONSTRUCTOR) {
+ new_flags |= FLAG_DEFAULT_CONSTRUCTOR;
+ }
+ wint (new_flags);
+}
+
+void write_field_flags (long long flags) {
+ int new_flags = 0;
+ //fprintf (stderr, "%lld\n", flags);
+ if (flags & 1) {
+ new_flags |= FLAG_BARE;
+ }
+ if (flags & 32) {
+ new_flags |= FLAG_OPT_VAR;
+ }
+ if (flags & FLAG_EXCL) {
+ new_flags |= FLAG_EXCL;
+ }
+ if (flags & FLAG_OPT_FIELD) {
+ // new_flags |= FLAG_OPT_FIELD;
+ new_flags |= 2;
+ }
+ if (flags & (1 << 21)) {
+ new_flags |= 4;
+ }
+ wint (new_flags);
+}
+
+void write_var_type_flags (long long flags) {
+ int new_flags = 0;
+ if (flags & 1) {
+ new_flags |= FLAG_BARE;
+ }
+ if (new_flags & FLAG_BARE) {
+ TL_ERROR ("Sorry, bare vars are not (yet ?) supported.\n");
+ assert (!(new_flags & FLAG_BARE));
+ }
+ wint (new_flags);
+}
+
+void write_tree (struct tl_combinator_tree *T, int extra, struct tree_var_value **v, int *last_var);
+void write_args (struct tl_combinator_tree *T, struct tree_var_value **v, int *last_var) {
+ assert (T->type == type_list || T->type == type_list_item);
+ if (T->type == type_list) {
+ assert (T->act == act_union);
+ assert (T->left);
+ assert (T->right);
+ write_args (T->left, v, last_var);
+ write_args (T->right, v, last_var);
+ return;
+ }
+ wint (TLS_ARG_V2);
+ assert (T->act == act_field);
+ assert (T->left);
+ wstr (T->data && strcmp (T->data, "_") ? T->data : 0);
+ long long f = T->flags;
+ if (T->left->act == act_opt_field) {
+ f |= (1 << 20);
+ }
+ if (T->left->act == act_type && T->left->data && (!strcmp (((struct tl_type *)T->left->data)->id, "#") || !strcmp (((struct tl_type *)T->left->data)->id, "Type"))) {
+ write_field_flags (f | (1 << 21));
+ wint (*last_var);
+ *last_var = (*last_var) + 1;
+ tl_set_var_value_num (v, T, 0, (*last_var) - 1);
+ } else {
+ write_field_flags (f);
+ }
+ write_tree (T->left, 0, v, last_var);
+}
+
+void write_array (struct tl_combinator_tree *T, struct tree_var_value **v, int *last_var) {
+ wint (TLS_ARRAY);
+ write_tree (T->left, 0, v, last_var);
+ write_tree (T->right, 0, v, last_var);
+}
+
+void write_type_rec (struct tl_combinator_tree *T, int cc, struct tree_var_value **v, int *last_var) {
+ if (T->act == act_arg) {
+ write_type_rec (T->left, cc + 1, v, last_var);
+ if (T->right->type == type_num_value || T->right->type == type_num) {
+ wint (TLS_EXPR_NAT);
+ } else {
+ wint (TLS_EXPR_TYPE);
+ }
+ write_tree (T->right, 0, v, last_var);
+ } else {
+ assert (T->act == act_var || T->act == act_type);
+ if (T->act == act_var) {
+ assert (!cc);
+ wint (TLS_TYPE_VAR);
+ wint (tl_get_var_value_num (v, T->data));
+ write_var_type_flags (T->flags);
+ //wint (T->flags);
+ } else {
+ wint (TLS_TYPE_EXPR);
+ struct tl_type *t = T->data;
+ wint (t->name);
+ write_type_flags (T->flags);
+// wint (T->flags);
+ wint (cc);
+// fprintf (stderr, "cc = %d\n", cc);
+ }
+ }
+}
+
+void write_opt_type (struct tl_combinator_tree *T, struct tree_var_value **v, int *last_var) {
+ wint (tl_get_var_value_num (v, T->left->data));
+ wint (T->left->type_flags);
+// write_tree (T->right, 0, v, last_var);
+ assert (T);
+ T = T->right;
+ switch (T->type) {
+ case type_type:
+ if (T->act == act_array) {
+ write_array (T, v, last_var);
+ } else if (T->act == act_type || T->act == act_var || T->act == act_arg) {
+ write_type_rec (T, 0, v, last_var);
+ } else {
+ assert (0);
+ }
+ break;
+ default:
+ assert (0);
+ }
+}
+
+void write_tree (struct tl_combinator_tree *T, int extra, struct tree_var_value **v, int *last_var) {
+ assert (T);
+ switch (T->type) {
+ case type_list_item:
+ case type_list:
+ if (extra) {
+ wint (TLS_COMBINATOR_RIGHT_V2);
+ }
+ wint (count_list_size (T));
+ write_args (T, v, last_var);
+ break;
+ case type_num_value:
+ wint ((int)TLS_NAT_CONST);
+ wint (T->type_flags);
+ break;
+ case type_num:
+ wint ((int)TLS_NAT_VAR);
+ wint (T->type_flags);
+ wint (tl_get_var_value_num (v, T->data));
+ break;
+ case type_type:
+ if (T->act == act_array) {
+ write_array (T, v, last_var);
+ } else if (T->act == act_type || T->act == act_var || T->act == act_arg) {
+ write_type_rec (T, 0, v, last_var);
+ } else {
+ assert (T->act == act_opt_field);
+ write_opt_type (T, v, last_var);
+ }
+ break;
+ default:
+ assert (0);
+ }
+}
+
+void write_type (struct tl_type *t) {
+ wint (TLS_TYPE);
+ wint (t->name);
+ wstr (t->id);
+ wint (t->constructors_num);
+ wint (t->flags);
+ wint (t->params_num);
+ wll (t->params_types);
+}
+
+int is_builtin_type (const char *id) {
+ return !strcmp (id, "int") || !strcmp (id, "long") || !strcmp (id, "double") || !strcmp (id, "string")
+ || !strcmp(id, "object") || !strcmp(id, "function");
+}
+
+void write_combinator (struct tl_constructor *c) {
+ wint (c->name);
+ wstr (c->id);
+ wint (c->type ? c->type->name : 0);
+ struct tree_var_value *T = 0;
+ int x = 0;
+ assert (c->right);
+ if (c->left) {
+ if (is_builtin_type (c->id)) {
+ wint (TLS_COMBINATOR_LEFT_BUILTIN);
+ } else {
+ wint (TLS_COMBINATOR_LEFT);
+ // FIXME: What is that?
+// wint (count_list_size (c->left));
+ write_tree (c->left, 0, &T, &x);
+ }
+ } else {
+ wint (TLS_COMBINATOR_LEFT);
+ wint (0);
+ }
+ wint (TLS_COMBINATOR_RIGHT_V2);
+ write_tree (c->right, 1, &T, &x);
+}
+
+void write_constructor (struct tl_constructor *c) {
+ wint (TLS_COMBINATOR);
+ write_combinator (c);
+}
+
+void write_function (struct tl_constructor *c) {
+ wint (TLS_COMBINATOR);
+ write_combinator (c);
+}
+
+void write_type_constructors (struct tl_type *t) {
+ int i;
+ for (i = 0; i < t->constructors_num; i++) {
+ write_constructor (t->constructors[i]);
+ }
+}
+
+void write_types (FILE *f) {
+ __f = f;
+ wint (TLS_SCHEMA_V2);
+ wint (0);
+#ifdef TL_PARSER_NEED_TIME
+ wint (time (0));
+#else
+ /* Make the tlo reproducible by default. Rationale: https://wiki.debian.org/ReproducibleBuilds/Howto#Introduction */
+ wint (0);
+#endif
+ num = 0;
+ wint (total_types_num);
+ tree_act_tl_type (tl_type_tree, write_type);
+ wint (total_constructors_num);
+ tree_act_tl_type (tl_type_tree, write_type_constructors);
+ wint (total_functions_num);
+ tree_act_tl_constructor (tl_function_tree, write_function);
+}
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser.h b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser.h
new file mode 100644
index 0000000000..59209d4a55
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-parser.h
@@ -0,0 +1,223 @@
+/*
+ This file is part of tgl-libary/tlc
+
+ Tgl-library/tlc 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 2 of the License, or
+ (at your option) any later version.
+
+ Tgl-library/tlc 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 tgl-library/tlc. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright Vitaly Valtman 2014
+
+ It is derivative work of VK/KittenPHP-DB-Engine (https://github.com/vk-com/kphp-kdb/)
+ Copyright 2012-2013 Vkontakte Ltd
+ 2012-2013 Vitaliy Valtman
+
+*/
+
+#ifndef __TL_PARSER_NEW_H__
+#define __TL_PARSER_NEW_H__
+
+#include <stdio.h>
+
+enum lex_type {
+ lex_error,
+ lex_char,
+ lex_triple_minus,
+ lex_uc_ident,
+ lex_lc_ident,
+ lex_eof,
+ lex_final,
+ lex_new,
+ lex_none,
+ lex_num,
+ lex_empty
+};
+
+
+struct curlex {
+ char *ptr;
+ int len;
+ enum lex_type type;
+ int flags;
+};
+
+struct parse {
+ char *text;
+ int pos;
+ int len;
+ int line;
+ int line_pos;
+ struct curlex lex;
+};
+
+
+enum tree_type {
+ type_tl_program,
+ type_fun_declarations,
+ type_constr_declarations,
+ type_declaration,
+ type_combinator_decl,
+ type_equals,
+ type_partial_app_decl,
+ type_final_decl,
+ type_full_combinator_id,
+ type_opt_args,
+ type_args,
+ type_args1,
+ type_args2,
+ type_args3,
+ type_args4,
+ type_boxed_type_ident,
+ type_subexpr,
+ type_partial_comb_app_decl,
+ type_partial_type_app_decl,
+ type_final_new,
+ type_final_final,
+ type_final_empty,
+// type_type,
+ type_var_ident,
+ type_var_ident_opt,
+ type_multiplicity,
+ type_type_term,
+ type_term,
+ type_percent,
+ type_result_type,
+ type_expr,
+ type_nat_term,
+ type_combinator_id,
+ type_nat_const,
+ type_type_ident,
+ type_builtin_combinator_decl,
+ type_exclam,
+ type_optional_arg_def
+};
+
+struct tree {
+ char *text;
+ int len;
+ enum tree_type type;
+ int lex_line;
+ int lex_line_pos;
+ int flags;
+ int size;
+ int nc;
+ struct tree **c;
+};
+
+
+#define TL_ACT(x) (x == act_var ? "act_var" : x == act_field ? "act_field" : x == act_plus ? "act_plus" : x == act_type ? "act_type" : x == act_nat_const ? "act_nat_const" : x == act_array ? "act_array" : x == act_question_mark ? "act_question_mark" : \
+ x == act_union ? "act_union" : x == act_arg ? "act_arg" : x == act_opt_field ? "act_opt_field" : "act_unknown")
+
+#define TL_TYPE(x) (x == type_num ? "type_num" : x == type_type ? "type_type" : x == type_list_item ? "type_list_item" : x == type_list ? "type_list" : x == type_num_value ? "type_num_value" : "type_unknown")
+enum combinator_tree_action {
+ act_var,
+ act_field,
+ act_plus,
+ act_type,
+ act_nat_const,
+ act_array,
+ act_question_mark,
+ act_union,
+ act_arg,
+ act_opt_field
+};
+
+enum combinator_tree_type {
+ type_num,
+ type_num_value,
+ type_type,
+ type_list_item,
+ type_list
+};
+
+struct tl_combinator_tree {
+ enum combinator_tree_action act;
+ struct tl_combinator_tree *left, *right;
+ char *name;
+ void *data;
+ long long flags;
+ enum combinator_tree_type type;
+ int type_len;
+ long long type_flags;
+};
+
+
+struct tl_program {
+ int types_num;
+ int functions_num;
+ int constructors_num;
+ struct tl_type **types;
+ struct tl_function **functions;
+// struct tl_constuctor **constructors;
+};
+
+struct tl_type {
+ char *id;
+ char *print_id;
+ char *real_id;
+ unsigned name;
+ int flags;
+
+ int params_num;
+ long long params_types;
+
+ int constructors_num;
+ struct tl_constructor **constructors;
+};
+
+struct tl_constructor {
+ char *id;
+ char *print_id;
+ char *real_id;
+ unsigned name;
+ struct tl_type *type;
+
+ struct tl_combinator_tree *left;
+ struct tl_combinator_tree *right;
+};
+
+struct tl_var {
+ char *id;
+ struct tl_combinator_tree *ptr;
+ int type;
+ int flags;
+};
+
+struct parse *tl_init_parse_file (const char *fname);
+struct tree *tl_parse_lex (struct parse *P);
+void tl_print_parse_error (void);
+struct tl_program *tl_parse (struct tree *T);
+
+void write_types (FILE *f);
+
+#define FLAG_BARE 1
+#define FLAG_OPT_VAR (1 << 17)
+#define FLAG_EXCL (1 << 18)
+#define FLAG_OPT_FIELD (1 << 20)
+#define FLAG_IS_VAR (1 << 21)
+#define FLAG_DEFAULT_CONSTRUCTOR (1 << 25)
+#define FLAG_EMPTY (1 << 10)
+
+#ifdef NDEBUG
+#undef assert
+#define assert(x) if (!(x)) { fprintf(stderr, "Assertion error!\n"); abort(); }
+#endif
+
+#ifdef _WIN32
+#include "wgetopt.h"
+
+#define __attribute__(x)
+
+#define lrand48() rand()
+#define strdup _strdup
+#endif
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-tl.h b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-tl.h
new file mode 100644
index 0000000000..8bc0a707bc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tl-tl.h
@@ -0,0 +1,55 @@
+/*
+ This file is part of VK/KittenPHP-DB-Engine.
+
+ VK/KittenPHP-DB-Engine 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 2 of the License, or
+ (at your option) any later version.
+
+ VK/KittenPHP-DB-Engine 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 VK/KittenPHP-DB-Engine. If not, see <http://www.gnu.org/licenses/>.
+
+ This program is released under the GPL with the additional exemption
+ that compiling, linking, and/or using OpenSSL is allowed.
+ You are free to remove this exemption from derived works.
+
+ Copyright 2012-2013 Vkontakte Ltd
+ 2012-2013 Vitaliy Valtman
+*/
+
+#ifndef __TL_TL_H__
+#define __TL_TL_H__
+
+// Current tl-tl schema is V2
+// See https://core.telegram.org/mtproto/TL-tl
+
+#define TLS_SCHEMA_V2 0x3a2f9be2
+#define TLS_TYPE 0x12eb4386
+#define TLS_COMBINATOR 0x5c0a1ed5
+#define TLS_COMBINATOR_LEFT_BUILTIN 0xcd211f63
+#define TLS_COMBINATOR_LEFT 0x4c12c6d9
+#define TLS_COMBINATOR_RIGHT_V2 0x2c064372
+#define TLS_ARG_V2 0x29dfe61b
+
+#define TLS_EXPR_TYPE 0xecc9da78
+#define TLS_EXPR_NAT 0xdcb49bd8
+
+#define TLS_NAT_CONST 0xdcb49bd8
+#define TLS_NAT_VAR 0x4e8a14f0
+#define TLS_TYPE_VAR 0x0142ceae
+#define TLS_ARRAY 0xd9fb20de
+#define TLS_TYPE_EXPR 0xc1863d08
+
+/* Deprecated (old versions), read-only */
+#define TLS_TREE_NAT_CONST 0xc09f07d7
+#define TLS_TREE_NAT_VAR 0x90ea6f58
+#define TLS_TREE_TYPE_VAR 0x1caa237a
+#define TLS_TREE_ARRAY 0x80479360
+#define TLS_TREE_TYPE 0x10f32190
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/tlc.c b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tlc.c
new file mode 100644
index 0000000000..75c7db2f85
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/tlc.c
@@ -0,0 +1,165 @@
+/*
+ This file is part of tl-parser
+
+ tl-parser 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 2 of the License, or
+ (at your option) any later version.
+
+ tl-parser 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 tl-parser. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright Vitaly Valtman 2014
+
+ It is derivative work of VK/KittenPHP-DB-Engine (https://github.com/vk-com/kphp-kdb/)
+ Copyright 2012-2013 Vkontakte Ltd
+ 2012-2013 Vitaliy Valtman
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "tl-parser.h"
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <signal.h>
+
+#ifdef HAVE_EXECINFO_H
+#include <execinfo.h>
+#endif
+#include <stdarg.h>
+
+int verbosity;
+int output_expressions;
+void usage (void) {
+ printf ("usage: tl-parser [-v] [-h] <TL-schema-file>\n"
+ "\tTL compiler\n"
+ "\t-v\toutput statistical and debug information into stderr\n"
+ "\t-E\twhenever is possible output to stdout expressions\n"
+ "\t-e <file>\texport serialized schema to file\n"
+ );
+ exit (2);
+}
+
+int vkext_write (const char *filename) {
+ FILE *f = fopen(filename, "wb");
+ assert (f != NULL);
+ write_types (f);
+ fclose (f);
+ return 0;
+}
+
+void logprintf (const char *format, ...) __attribute__ ((format (printf, 1, 2)));
+void logprintf (const char *format __attribute__ ((unused)), ...) {
+ va_list ap;
+ va_start (ap, format);
+ vfprintf (stderr, format, ap);
+ va_end (ap);
+}
+
+void hexdump (int *in_ptr, int *in_end) {
+ int *ptr = in_ptr;
+ while (ptr < in_end) { printf (" %08x", *(ptr ++)); }
+ printf ("\n");
+}
+
+#ifdef HAVE_EXECINFO_H
+void print_backtrace (void) {
+ void *buffer[255];
+ const int calls = backtrace (buffer, sizeof (buffer) / sizeof (void *));
+ backtrace_symbols_fd (buffer, calls, 1);
+}
+#else
+void print_backtrace (void) {
+ if (fwrite ("No libexec. Backtrace disabled\n", 32, 1, stderr) != 1) {
+ // Sad thing
+ }
+}
+#endif
+
+void sig_segv_handler (int signum __attribute__ ((unused))) {
+ if (fwrite ("SIGSEGV received\n", 18, 1, stderr) != 1) {
+ // Sad thing
+ }
+ print_backtrace ();
+ exit (EXIT_FAILURE);
+}
+
+void sig_abrt_handler (int signum __attribute__ ((unused))) {
+ if (fwrite ("SIGABRT received\n", 18, 1, stderr) != 1) {
+ // Sad thing
+ }
+ print_backtrace ();
+ exit (EXIT_FAILURE);
+}
+
+int main (int argc, char **argv) {
+ signal (SIGSEGV, sig_segv_handler);
+ signal (SIGABRT, sig_abrt_handler);
+ int i;
+ char *vkext_file = 0;
+ while ((i = getopt (argc, argv, "Ehve:w:")) != -1) {
+ switch (i) {
+ case 'E':
+ output_expressions++;
+ break;
+ case 'h':
+ usage ();
+ return 2;
+ case 'e':
+ vkext_file = optarg;
+ break;
+ case 'v':
+ verbosity++;
+ break;
+ }
+ }
+
+ if (argc != optind + 1) {
+ usage ();
+ }
+
+
+ struct parse *P = tl_init_parse_file (argv[optind]);
+ if (!P) {
+ return 1;
+ }
+ struct tree *T;
+ if (!(T = tl_parse_lex (P))) {
+ fprintf (stderr, "Error in parse:\n");
+ tl_print_parse_error ();
+ return 1;
+ } else {
+ if (verbosity) {
+ fprintf (stderr, "Parse ok\n");
+ }
+ if (!tl_parse (T)) {
+ if (verbosity) {
+ fprintf (stderr, "Fail\n");
+ }
+ return 1;
+ } else {
+ if (verbosity) {
+ fprintf (stderr, "Ok\n");
+ }
+ }
+ }
+ if (vkext_file) {
+ vkext_write (vkext_file);
+ }
+ return 0;
+}
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/wgetopt.c b/protocols/Telegram/tdlib/td/td/generate/tl-parser/wgetopt.c
new file mode 100644
index 0000000000..4557b92112
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/wgetopt.c
@@ -0,0 +1,1274 @@
+/* Getopt for GNU.
+NOTE: getopt is now part of the C library, so if you don't know what
+"Keep this file name-space clean" means, talk to drepper@gnu.org
+before changing it!
+Copyright (C) 1987,88,89,90,91,92,93,94,95,96,98,99,2000,2001
+Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C 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.
+
+The GNU C 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 the GNU C Library; if not, write to the Free
+Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA. */
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+Ditto for AIX 3.2 and <stdlib.h>. */
+#ifndef _NO_PROTO
+# define _NO_PROTO
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if !defined __STDC__ || !__STDC__
+/* This is a separate conditional since some stdc systems
+reject `defined (const)'. */
+# ifndef const
+# define const
+# endif
+#endif
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+actually compiling the library itself. This code is part of the GNU C
+Library, but also included in many other GNU distributions. Compiling
+and linking in this code is a waste when using the GNU C library
+(especially if it is a shared library). Rather than having every GNU
+program understand `configure --with-gnu-libc' and omit the object files,
+it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
+# include <gnu-versions.h>
+# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+# define ELIDE_CODE
+# endif
+#endif
+
+#ifndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+contain conflicting prototypes for getopt. */
+# include <stdlib.h>
+# include <unistd.h>
+#endif /* GNU C library. */
+
+#ifdef VMS
+# include <unixlib.h>
+# if HAVE_STRING_H - 0
+# include <string.h>
+# endif
+#endif
+
+#ifndef _
+/* This is for other GNU distributions with internationalized messages. */
+# if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC
+# include <libintl.h>
+# ifndef _
+# define _(msgid) gettext (msgid)
+# endif
+# else
+# define _(msgid) (msgid)
+# endif
+# if defined _LIBC && defined USE_IN_LIBIO
+# include <wchar.h>
+# endif
+#endif
+
+/* This version of `getopt' appears to the caller like standard Unix `getopt'
+but it behaves differently for the user, since it allows the user
+to intersperse the options with the other arguments.
+
+As `getopt' works, it permutes the elements of ARGV so that,
+when it is done, all the options precede everything else. Thus
+all application programs are extended to handle flexible argument order.
+
+Setting the environment variable POSIXLY_CORRECT disables permutation.
+Then the behavior is completely standard.
+
+GNU application programs can use a third alternative mode in which
+they can distinguish the relative order of options and other arguments. */
+
+#include "wgetopt.h"
+
+/* For communication from `getopt' to the caller.
+When `getopt' finds an option that takes an argument,
+the argument value is returned here.
+Also, when `ordering' is RETURN_IN_ORDER,
+each non-option ARGV-element is returned here. */
+
+char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+This is used for communication to and from the caller
+and for communication between successive calls to `getopt'.
+
+On entry to `getopt', zero means this is the first call; initialize.
+
+When `getopt' returns -1, this is the index of the first of the
+non-option elements that the caller should itself scan.
+
+Otherwise, `optind' communicates from one call to the next
+how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int optind = 1;
+
+/* Formerly, initialization of getopt depended on optind==0, which
+causes problems with re-calling getopt as programs generally don't
+know that. */
+
+int __getopt_initialized;
+
+/* The next char to be scanned in the option-element
+in which the last option character we returned was found.
+This allows us to pick up the scan where we left off.
+
+If this is zero, or a null string, it means resume the scan
+by advancing to the next ARGV-element. */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+This must be initialized on some systems to avoid linking in the
+system's own getopt implementation. */
+
+int optopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+If the caller did not specify anything,
+the default is REQUIRE_ORDER if the environment variable
+POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+REQUIRE_ORDER means don't recognize them as options;
+stop option processing when the first non-option is seen.
+This is what Unix does.
+This mode of operation is selected by either setting the environment
+variable POSIXLY_CORRECT, or using `+' as the first character
+of the list of option characters.
+
+PERMUTE is the default. We permute the contents of ARGV as we scan,
+so that eventually all the non-options are at the end. This allows options
+to be given in any order, even with programs that were not written to
+expect this.
+
+RETURN_IN_ORDER is an option available to programs that were written
+to expect options and other ARGV-elements in any order and that care about
+the ordering of the two. We describe each non-option ARGV-element
+as if it were the argument of an option with character code 1.
+Using `-' as the first character of the list of option characters
+selects this mode of operation.
+
+The special argument `--' forces an end of option-scanning regardless
+of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+`--' can cause `getopt' to return -1 with `optind' != ARGC. */
+
+static enum
+{
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} ordering;
+
+/* Value of POSIXLY_CORRECT environment variable. */
+static char *posixly_correct;
+
+#ifdef __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+because there are many ways it can cause trouble.
+On some systems, it contains special magic macros that don't work
+in GCC. */
+# include <string.h>
+# define my_index strchr
+#else
+
+#define HAVE_STRING_H 1
+# if HAVE_STRING_H
+# include <string.h>
+# else
+# include <strings.h>
+# endif
+
+/* Avoid depending on library functions or files
+whose names are inconsistent. */
+
+#ifndef getenv
+extern char *getenv();
+#endif
+
+static char *
+my_index(str, chr)
+const char *str;
+int chr;
+{
+ while (*str)
+ {
+ if (*str == chr)
+ return (char *)str;
+ str++;
+ }
+ return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+If not using GCC, it is ok not to declare it. */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+That was relevant to code that was here before. */
+# if (!defined __STDC__ || !__STDC__) && !defined strlen
+/* gcc with -traditional declares the built-in strlen to return int,
+and has done so at least since version 2.4.5. -- rms. */
+extern int strlen(const char *);
+# endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+
+/* Handle permutation of arguments. */
+
+/* Describe the part of ARGV that contains non-options that have
+been skipped. `first_nonopt' is the index in ARGV of the first of them;
+`last_nonopt' is the index after the last of them. */
+
+static int first_nonopt;
+static int last_nonopt;
+
+#ifdef _LIBC
+/* Stored original parameters.
+XXX This is no good solution. We should rather copy the args so
+that we can compare them later. But we must not use malloc(3). */
+extern int __libc_argc;
+extern char **__libc_argv;
+
+/* Bash 2.0 gives us an environment variable containing flags
+indicating ARGV elements that should not be considered arguments. */
+
+# ifdef USE_NONOPTION_FLAGS
+/* Defined in getopt_init.c */
+extern char *__getopt_nonoption_flags;
+
+static int nonoption_flags_max_len;
+static int nonoption_flags_len;
+# endif
+
+# ifdef USE_NONOPTION_FLAGS
+# define SWAP_FLAGS(ch1, ch2) \
+if (nonoption_flags_len > 0) \
+{ \
+ char __tmp = __getopt_nonoption_flags[ch1]; \
+ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \
+ __getopt_nonoption_flags[ch2] = __tmp; \
+}
+# else
+# define SWAP_FLAGS(ch1, ch2)
+# endif
+#else /* !_LIBC */
+# define SWAP_FLAGS(ch1, ch2)
+#endif /* _LIBC */
+
+/* Exchange two adjacent subsequences of ARGV.
+One subsequence is elements [first_nonopt,last_nonopt)
+which contains all the non-options that have been skipped so far.
+The other is elements [last_nonopt,optind), which contains all
+the options processed since those non-options were skipped.
+
+`first_nonopt' and `last_nonopt' are relocated so that they describe
+the new indices of the non-options in ARGV after they are moved. */
+
+#if defined __STDC__ && __STDC__
+static void exchange(char **);
+#endif
+
+static void
+exchange(argv)
+char **argv;
+{
+ int bottom = first_nonopt;
+ int middle = last_nonopt;
+ int top = optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+ /* First make sure the handling of the `__getopt_nonoption_flags'
+ string can work normally. Our top argument must be in the range
+ of the string. */
+ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len)
+ {
+ /* We must extend the array. The user plays games with us and
+ presents new arguments. */
+ char *new_str = malloc(top + 1);
+ if (new_str == NULL)
+ nonoption_flags_len = nonoption_flags_max_len = 0;
+ else
+ {
+ memset(__mempcpy(new_str, __getopt_nonoption_flags,
+ nonoption_flags_max_len),
+ '\0', top + 1 - nonoption_flags_max_len);
+ nonoption_flags_max_len = top + 1;
+ __getopt_nonoption_flags = new_str;
+ }
+ }
+#endif
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ SWAP_FLAGS(bottom + i, top - (middle - bottom) + i);
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ SWAP_FLAGS(bottom + i, middle + i);
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ first_nonopt += (optind - last_nonopt);
+ last_nonopt = optind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+#if defined __STDC__ && __STDC__
+static const char *_getopt_initialize(int, char *const *, const char *);
+#endif
+static const char *
+_getopt_initialize(argc, argv, optstring)
+int argc;
+char *const *argv;
+const char *optstring;
+{
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ first_nonopt = last_nonopt = optind;
+
+ nextchar = NULL;
+
+ posixly_correct = getenv("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-')
+ {
+ ordering = RETURN_IN_ORDER;
+ ++optstring;
+ }
+ else if (optstring[0] == '+')
+ {
+ ordering = REQUIRE_ORDER;
+ ++optstring;
+ }
+ else if (posixly_correct != NULL)
+ ordering = REQUIRE_ORDER;
+ else
+ ordering = PERMUTE;
+
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+ if (posixly_correct == NULL
+ && argc == __libc_argc && argv == __libc_argv)
+ {
+ if (nonoption_flags_max_len == 0)
+ {
+ if (__getopt_nonoption_flags == NULL
+ || __getopt_nonoption_flags[0] == '\0')
+ nonoption_flags_max_len = -1;
+ else
+ {
+ const char *orig_str = __getopt_nonoption_flags;
+ int len = nonoption_flags_max_len = strlen(orig_str);
+ if (nonoption_flags_max_len < argc)
+ nonoption_flags_max_len = argc;
+ __getopt_nonoption_flags =
+ (char *)malloc(nonoption_flags_max_len);
+ if (__getopt_nonoption_flags == NULL)
+ nonoption_flags_max_len = -1;
+ else
+ memset(__mempcpy(__getopt_nonoption_flags, orig_str, len),
+ '\0', nonoption_flags_max_len - len);
+ }
+ }
+ nonoption_flags_len = nonoption_flags_max_len;
+ }
+ else
+ nonoption_flags_len = 0;
+#endif
+
+ return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+given in OPTSTRING.
+
+If an element of ARGV starts with '-', and is not exactly "-" or "--",
+then it is an option element. The characters of this element
+(aside from the initial '-') are option characters. If `getopt'
+is called repeatedly, it returns successively each of the option characters
+from each of the option elements.
+
+If `getopt' finds another option character, it returns that character,
+updating `optind' and `nextchar' so that the next call to `getopt' can
+resume the scan with the following option character or ARGV-element.
+
+If there are no more option characters, `getopt' returns -1.
+Then `optind' is the index in ARGV of the first ARGV-element
+that is not an option. (The ARGV-elements have been permuted
+so that those that are not options now come last.)
+
+OPTSTRING is a string containing the legitimate option characters.
+If an option character is seen that is not listed in OPTSTRING,
+return '?' after printing an error message. If you set `opterr' to
+zero, the error message is suppressed but we still return '?'.
+
+If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+so the following text in the same ARGV-element, or the text of the following
+ARGV-element, is returned in `optarg'. Two colons mean an option that
+wants an optional arg; if there is text in the current ARGV-element,
+it is returned in `optarg', otherwise `optarg' is set to zero.
+
+If OPTSTRING starts with `-' or `+', it requests different methods of
+handling the non-option ARGV-elements.
+See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+Long-named options begin with `--' instead of `-'.
+Their names may be abbreviated as long as the abbreviation is unique
+or is an exact match for some defined option. If they have an
+argument, it follows the option name in the same ARGV-element, separated
+from the option name by a `=', or else the in next ARGV-element.
+When `getopt' finds a long-named option, it returns 0 if that option's
+`flag' field is nonzero, the value of the option's `val' field
+if the `flag' field is zero.
+
+The elements of ARGV aren't really const, because we permute them.
+But we pretend they're const in the prototype to be compatible
+with other systems.
+
+LONGOPTS is a vector of `struct option' terminated by an
+element containing a name which is zero.
+
+LONGIND returns the index in LONGOPT of the long-named option found.
+It is only valid when a long-named option has been found by the most
+recent call.
+
+If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+long-named options. */
+
+int
+_getopt_internal(argc, argv, optstring, longopts, longind, long_only)
+int argc;
+char *const *argv;
+const char *optstring;
+const struct option *longopts;
+int *longind;
+int long_only;
+{
+ int print_errors = opterr;
+ if (optstring[0] == ':')
+ print_errors = 0;
+
+ if (argc < 1)
+ return -1;
+
+ optarg = NULL;
+
+ if (optind == 0 || !__getopt_initialized)
+ {
+ if (optind == 0)
+ optind = 1; /* Don't scan ARGV[0], the program name. */
+ optstring = _getopt_initialize(argc, argv, optstring);
+ __getopt_initialized = 1;
+ }
+
+ /* Test whether ARGV[optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+#if defined _LIBC && defined USE_NONOPTION_FLAGS
+# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \
+ || (optind < nonoption_flags_len \
+ && __getopt_nonoption_flags[optind] == '1'))
+#else
+# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0')
+#endif
+
+ if (nextchar == NULL || *nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ moved back by the user (who may also have changed the arguments). */
+ if (last_nonopt > optind)
+ last_nonopt = optind;
+ if (first_nonopt > optind)
+ first_nonopt = optind;
+
+ if (ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (first_nonopt != last_nonopt && last_nonopt != optind)
+ exchange((char **)argv);
+ else if (last_nonopt != optind)
+ first_nonopt = optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (optind < argc && NONOPTION_P)
+ optind++;
+ last_nonopt = optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (optind != argc && !strcmp(argv[optind], "--"))
+ {
+ optind++;
+
+ if (first_nonopt != last_nonopt && last_nonopt != optind)
+ exchange((char **)argv);
+ else if (first_nonopt == last_nonopt)
+ first_nonopt = optind;
+ last_nonopt = argc;
+
+ optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (optind == argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (first_nonopt != last_nonopt)
+ optind = first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if (NONOPTION_P)
+ {
+ if (ordering == REQUIRE_ORDER)
+ return -1;
+ optarg = argv[optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ nextchar = (argv[optind] + 1
+ + (longopts != NULL && argv[optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[optind][1] == '-'
+ || (long_only && (argv[optind][2] || !my_index(optstring, argv[optind][1])))))
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp(p->name, nextchar, nameend - nextchar))
+ {
+ if ((unsigned int)(nameend - nextchar)
+ == (unsigned int)strlen(p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else if (long_only
+ || pfound->has_arg != p->has_arg
+ || pfound->flag != p->flag
+ || pfound->val != p->val)
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ __asprintf(&buf, _("%s: option `%s' is ambiguous\n"),
+ argv[0], argv[optind]);
+
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#else
+ fprintf(stderr, _("%s: option `%s' is ambiguous\n"),
+ argv[0], argv[optind]);
+#endif
+ }
+ nextchar += strlen(nextchar);
+ optind++;
+ optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ optind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ optarg = nameend + 1;
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+#endif
+
+ if (argv[optind - 1][1] == '-')
+ {
+ /* --option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ __asprintf(&buf, _("\
+ %s: option `--%s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+#else
+ fprintf(stderr, _("\
+ %s: option `--%s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+#endif
+ }
+ else
+ {
+ /* +option or -option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ __asprintf(&buf, _("\
+ %s: option `%c%s' doesn't allow an argument\n"),
+ argv[0], argv[optind - 1][0],
+ pfound->name);
+#else
+ fprintf(stderr, _("\
+ %s: option `%c%s' doesn't allow an argument\n"),
+ argv[0], argv[optind - 1][0], pfound->name);
+#endif
+ }
+
+#if defined _LIBC && defined USE_IN_LIBIO
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#endif
+ }
+
+ nextchar += strlen(nextchar);
+
+ optopt = pfound->val;
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (optind < argc)
+ optarg = argv[optind++];
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ __asprintf(&buf,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0], argv[optind - 1]);
+
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#else
+ fprintf(stderr,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0], argv[optind - 1]);
+#endif
+ }
+ nextchar += strlen(nextchar);
+ optopt = pfound->val;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen(nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[optind][1] == '-'
+ || my_index(optstring, *nextchar) == NULL)
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+#endif
+
+ if (argv[optind][1] == '-')
+ {
+ /* --option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ __asprintf(&buf, _("%s: unrecognized option `--%s'\n"),
+ argv[0], nextchar);
+#else
+ fprintf(stderr, _("%s: unrecognized option `--%s'\n"),
+ argv[0], nextchar);
+#endif
+ }
+ else
+ {
+ /* +option or -option */
+#if defined _LIBC && defined USE_IN_LIBIO
+ __asprintf(&buf, _("%s: unrecognized option `%c%s'\n"),
+ argv[0], argv[optind][0], nextchar);
+#else
+ fprintf(stderr, _("%s: unrecognized option `%c%s'\n"),
+ argv[0], argv[optind][0], nextchar);
+#endif
+ }
+
+#if defined _LIBC && defined USE_IN_LIBIO
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#endif
+ }
+ nextchar = (char *) "";
+ optind++;
+ optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *nextchar++;
+ char *temp = my_index(optstring, c);
+
+ /* Increment `optind' when we start to process its last character. */
+ if (*nextchar == '\0')
+ ++optind;
+
+ if (temp == NULL || c == ':')
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+#endif
+
+ if (posixly_correct)
+ {
+ /* 1003.2 specifies the format of this message. */
+#if defined _LIBC && defined USE_IN_LIBIO
+ __asprintf(&buf, _("%s: illegal option -- %c\n"),
+ argv[0], c);
+#else
+ fprintf(stderr, _("%s: illegal option -- %c\n"), argv[0], c);
+#endif
+ }
+ else
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ __asprintf(&buf, _("%s: invalid option -- %c\n"),
+ argv[0], c);
+#else
+ fprintf(stderr, _("%s: invalid option -- %c\n"), argv[0], c);
+#endif
+ }
+
+#if defined _LIBC && defined USE_IN_LIBIO
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#endif
+ }
+ optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';')
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0')
+ {
+ optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ optind++;
+ }
+ else if (optind == argc)
+ {
+ if (print_errors)
+ {
+ /* 1003.2 specifies the format of this message. */
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ __asprintf(&buf, _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#else
+ fprintf(stderr, _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+#endif
+ }
+ optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ optarg = argv[optind++];
+
+ /* optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp(p->name, nextchar, nameend - nextchar))
+ {
+ if ((unsigned int)(nameend - nextchar) == strlen(p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact)
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ __asprintf(&buf, _("%s: option `-W %s' is ambiguous\n"),
+ argv[0], argv[optind]);
+
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#else
+ fprintf(stderr, _("%s: option `-W %s' is ambiguous\n"),
+ argv[0], argv[optind]);
+#endif
+ }
+ nextchar += strlen(nextchar);
+ optind++;
+ return '?';
+ }
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ optarg = nameend + 1;
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ __asprintf(&buf, _("\
+ %s: option `-W %s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#else
+ fprintf(stderr, _("\
+ %s: option `-W %s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+#endif
+ }
+
+ nextchar += strlen(nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (optind < argc)
+ optarg = argv[optind++];
+ else
+ {
+ if (print_errors)
+ {
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ __asprintf(&buf, _("\
+ %s: option `%s' requires an argument\n"),
+ argv[0], argv[optind - 1]);
+
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#else
+ fprintf(stderr,
+ _("%s: option `%s' requires an argument\n"),
+ argv[0], argv[optind - 1]);
+#endif
+ }
+ nextchar += strlen(nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen(nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*nextchar != '\0')
+ {
+ optarg = nextchar;
+ optind++;
+ }
+ else
+ optarg = NULL;
+ nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0')
+ {
+ optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ optind++;
+ }
+ else if (optind == argc)
+ {
+ if (print_errors)
+ {
+ /* 1003.2 specifies the format of this message. */
+#if defined _LIBC && defined USE_IN_LIBIO
+ char *buf;
+
+ __asprintf(&buf,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+
+ if (_IO_fwide(stderr, 0) > 0)
+ __fwprintf(stderr, L"%s", buf);
+ else
+ fputs(buf, stderr);
+
+ free(buf);
+#else
+ fprintf(stderr,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+#endif
+ }
+ optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ optarg = argv[optind++];
+ nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int
+getopt(argc, argv, optstring)
+int argc;
+char *const *argv;
+const char *optstring;
+{
+ return _getopt_internal(argc, argv, optstring,
+ (const struct option *) 0,
+ (int *)0,
+ 0);
+}
+
+
+
+
+int
+getopt_long(int argc, char *const *argv, const char *options,
+const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal(argc, argv, options, long_options, opt_index, 0, 0);
+}
+
+int
+getopt_long_only(int argc, char *const *argv, const char *options,
+const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal(argc, argv, options, long_options, opt_index, 1, 0);
+}
+
+
+
+
+
+#endif /* Not ELIDE_CODE. */
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+the above definition of `getopt'. */
+
+int
+main(argc, argv)
+int argc;
+char **argv;
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+
+ c = getopt(argc, argv, "abc:d:0123456789");
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf("option %c\n", c);
+ break;
+
+ case 'a':
+ printf("option a\n");
+ break;
+
+ case 'b':
+ printf("option b\n");
+ break;
+
+ case 'c':
+ printf("option c with value `%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf("%s ", argv[optind++]);
+ printf("\n");
+ }
+
+ exit(0);
+}
+
+#endif /* TEST */
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl-parser/wgetopt.h b/protocols/Telegram/tdlib/td/td/generate/tl-parser/wgetopt.h
new file mode 100644
index 0000000000..6e2fa27180
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/generate/tl-parser/wgetopt.h
@@ -0,0 +1,193 @@
+/* Declarations for getopt.
+ Copyright (C) 1989-1994,1996-1999,2001,2003,2004,2009,2010
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C 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.
+
+ The GNU C 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 the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+
+#ifndef _GETOPT_H
+
+#ifndef __need_getopt
+# define _GETOPT_H 1
+#endif
+
+/* If __GNU_LIBRARY__ is not already defined, either we are being used
+ standalone, or this is the first header included in the source file.
+ If we are being used with glibc, we need to include <features.h>, but
+ that does not exist if we are standalone. So: if __GNU_LIBRARY__ is
+ not defined, include <ctype.h>, which will pull in <features.h> for us
+ if it's from glibc. (Why ctype.h? It's guaranteed to exist and it
+ doesn't flood the namespace with stuff the way some other headers do.) */
+#if !defined __GNU_LIBRARY__
+# include <ctype.h>
+#endif
+
+#ifndef __THROW
+# ifndef __GNUC_PREREQ
+# define __GNUC_PREREQ(maj, min) (0)
+# endif
+# if defined __cplusplus && __GNUC_PREREQ (2,8)
+# define __THROW throw ()
+# else
+# define __THROW
+# endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+extern int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+extern int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+extern int optopt;
+
+#ifndef __need_getopt
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+struct option
+{
+ const char *name;
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+# define no_argument 0
+# define required_argument 1
+# define optional_argument 2
+#endif /* need getopt */
+
+
+/* Get definitions and prototypes for functions to process the
+ arguments in ARGV (ARGC of them, minus the program name) for
+ options given in OPTS.
+
+ Return the option character from OPTS just read. Return -1 when
+ there are no more options. For unrecognized options, or options
+ missing arguments, `optopt' is set to the option letter, and '?' is
+ returned.
+
+ The OPTS string is a list of characters which are recognized option
+ letters, optionally followed by colons, specifying that that letter
+ takes an argument, to be placed in `optarg'.
+
+ If a letter in OPTS is followed by two colons, its argument is
+ optional. This behavior is specific to the GNU `getopt'.
+
+ The argument `--' causes premature termination of argument
+ scanning, explicitly telling `getopt' that there are no more
+ options.
+
+ If OPTS begins with `--', then non-option arguments are treated as
+ arguments to the option '\0'. This behavior is specific to the GNU
+ `getopt'. */
+
+#ifdef __GNU_LIBRARY__
+/* Many other libraries have conflicting prototypes for getopt, with
+ differences in the consts, in stdlib.h. To avoid compilation
+ errors, only prototype getopt for the GNU C library. */
+extern int getopt (int ___argc, char *const *___argv, const char *__shortopts)
+ __THROW;
+
+# if defined __need_getopt && defined __USE_POSIX2 \
+ && !defined __USE_POSIX_IMPLICITLY && !defined __USE_GNU
+/* The GNU getopt has more functionality than the standard version. The
+ additional functionality can be disable at runtime. This redirection
+ helps to also do this at runtime. */
+# ifdef __REDIRECT
+ extern int __REDIRECT_NTH (getopt, (int ___argc, char *const *___argv,
+ const char *__shortopts),
+ __posix_getopt);
+# else
+extern int __posix_getopt (int ___argc, char *const *___argv,
+ const char *__shortopts) __THROW;
+# define getopt __posix_getopt
+# endif
+# endif
+#else /* not __GNU_LIBRARY__ */
+extern int getopt ();
+#endif /* __GNU_LIBRARY__ */
+
+#ifndef __need_getopt
+extern int getopt_long (int ___argc, char *const *___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind)
+ __THROW;
+extern int getopt_long_only (int ___argc, char *const *___argv,
+ const char *__shortopts,
+ const struct option *__longopts, int *__longind)
+ __THROW;
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+/* Make sure we later can get all the definitions and declarations. */
+#undef __need_getopt
+
+#endif /* getopt.h */
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_json_converter.cpp b/protocols/Telegram/tdlib/td/td/generate/tl_json_converter.cpp
index 3fda1152cd..673306aedf 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_json_converter.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_json_converter.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,70 +9,86 @@
#include "td/tl/tl_simple.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/filesystem.h"
-#include "td/utils/logging.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/StringBuilder.h"
#include <utility>
namespace td {
+using Mode = tl::TL_writer::Mode;
+
template <class T>
void gen_to_json_constructor(StringBuilder &sb, const T *constructor, bool is_header) {
sb << "void to_json(JsonValueScope &jv, "
<< "const td_api::" << tl::simple::gen_cpp_name(constructor->name) << " &object)";
if (is_header) {
- sb << ";\n";
+ sb << ";\n\n";
return;
}
sb << " {\n";
sb << " auto jo = jv.enter_object();\n";
- sb << " jo << ctie(\"@type\", \"" << tl::simple::gen_cpp_name(constructor->name) << "\");\n";
+ sb << " jo(\"@type\", \"" << tl::simple::gen_cpp_name(constructor->name) << "\");\n";
for (auto &arg : constructor->args) {
- auto field = tl::simple::gen_cpp_field_name(arg.name);
- // TODO: or as null
+ auto field_name = tl::simple::gen_cpp_field_name(arg.name);
bool is_custom = arg.type->type == tl::simple::Type::Custom;
+ auto object = PSTRING() << "object." << field_name;
if (is_custom) {
- sb << " if (object." << field << ") {\n ";
+ sb << " if (" << object << ") {\n ";
}
- auto object = PSTRING() << "object." << tl::simple::gen_cpp_field_name(arg.name);
if (arg.type->type == tl::simple::Type::Bytes) {
object = PSTRING() << "base64_encode(" << object << ")";
+ } else if (arg.type->type == tl::simple::Type::Bool) {
+ object = PSTRING() << "JsonBool{" << object << "}";
} else if (arg.type->type == tl::simple::Type::Int64) {
object = PSTRING() << "JsonInt64{" << object << "}";
} else if (arg.type->type == tl::simple::Type::Vector &&
arg.type->vector_value_type->type == tl::simple::Type::Int64) {
object = PSTRING() << "JsonVectorInt64{" << object << "}";
}
- sb << " jo << ctie(\"" << arg.name << "\", ToJson(" << object << "));\n";
+ if (is_custom) {
+ sb << " jo(\"" << arg.name << "\", ToJson(*" << object << "));\n";
+ } else if (arg.type->type == tl::simple::Type::Int64 || arg.type->type == tl::simple::Type::Vector) {
+ sb << " jo(\"" << arg.name << "\", ToJson(" << object << "));\n";
+ } else {
+ sb << " jo(\"" << arg.name << "\", " << object << ");\n";
+ }
if (is_custom) {
sb << " }\n";
}
}
- sb << "}\n";
+ sb << "}\n\n";
}
-void gen_to_json(StringBuilder &sb, const tl::simple::Schema &schema, bool is_header) {
+void gen_to_json(StringBuilder &sb, const tl::simple::Schema &schema, bool is_header, Mode mode) {
for (auto *custom_type : schema.custom_types) {
+ if (!((custom_type->is_query_ && mode != Mode::Server) || (custom_type->is_result_ && mode != Mode::Client))) {
+ continue;
+ }
if (custom_type->constructors.size() > 1) {
auto type_name = tl::simple::gen_cpp_name(custom_type->name);
sb << "void to_json(JsonValueScope &jv, const td_api::" << type_name << " &object)";
if (is_header) {
- sb << ";\n";
+ sb << ";\n\n";
} else {
sb << " {\n"
<< " td_api::downcast_call(const_cast<td_api::" << type_name
<< " &>(object), [&jv](const auto &object) { "
"to_json(jv, object); });\n"
- << "}\n";
+ << "}\n\n";
}
}
for (auto *constructor : custom_type->constructors) {
gen_to_json_constructor(sb, constructor, is_header);
}
}
+ if (mode == Mode::Server) {
+ return;
+ }
for (auto *function : schema.functions) {
gen_to_json_constructor(sb, function, is_header);
}
@@ -82,33 +98,31 @@ template <class T>
void gen_from_json_constructor(StringBuilder &sb, const T *constructor, bool is_header) {
sb << "Status from_json(td_api::" << tl::simple::gen_cpp_name(constructor->name) << " &to, JsonObject &from)";
if (is_header) {
- sb << ";\n";
+ sb << ";\n\n";
} else {
sb << " {\n";
for (auto &arg : constructor->args) {
- sb << " {\n";
- sb << " TRY_RESULT(value, get_json_object_field(from, \"" << tl::simple::gen_cpp_name(arg.name)
- << "\", JsonValue::Type::Null, true));\n";
- sb << " if (value.type() != JsonValue::Type::Null) {\n";
- if (arg.type->type == tl::simple::Type::Bytes) {
- sb << " TRY_STATUS(from_json_bytes(to." << tl::simple::gen_cpp_field_name(arg.name) << ", value));\n";
- } else {
- sb << " TRY_STATUS(from_json(to." << tl::simple::gen_cpp_field_name(arg.name) << ", value));\n";
- }
- sb << " }\n";
- sb << " }\n";
+ sb << " TRY_STATUS(from_json" << (arg.type->type == tl::simple::Type::Bytes ? "_bytes" : "") << "(to."
+ << tl::simple::gen_cpp_field_name(arg.name) << ", get_json_object_field_force(from, \""
+ << tl::simple::gen_cpp_name(arg.name) << "\")));\n";
}
sb << " return Status::OK();\n";
- sb << "}\n";
+ sb << "}\n\n";
}
}
-void gen_from_json(StringBuilder &sb, const tl::simple::Schema &schema, bool is_header) {
+void gen_from_json(StringBuilder &sb, const tl::simple::Schema &schema, bool is_header, Mode mode) {
for (auto *custom_type : schema.custom_types) {
+ if (!((custom_type->is_query_ && mode != Mode::Client) || (custom_type->is_result_ && mode != Mode::Server))) {
+ continue;
+ }
for (auto *constructor : custom_type->constructors) {
gen_from_json_constructor(sb, constructor, is_header);
}
}
+ if (mode == Mode::Client) {
+ return;
+ }
for (auto *function : schema.functions) {
gen_from_json_constructor(sb, function, is_header);
}
@@ -118,11 +132,11 @@ using Vec = std::vector<std::pair<int32, std::string>>;
void gen_tl_constructor_from_string(StringBuilder &sb, Slice name, const Vec &vec, bool is_header) {
sb << "Result<int32> tl_constructor_from_string(td_api::" << name << " *object, const std::string &str)";
if (is_header) {
- sb << ";\n";
+ sb << ";\n\n";
return;
}
sb << " {\n";
- sb << " static const std::unordered_map<Slice, int32, SliceHash> m = {\n";
+ sb << " static const FlatHashMap<Slice, int32, SliceHash> m = {\n";
bool is_first = true;
for (auto &p : vec) {
@@ -136,18 +150,21 @@ void gen_tl_constructor_from_string(StringBuilder &sb, Slice name, const Vec &ve
sb << "\n };\n";
sb << " auto it = m.find(str);\n";
sb << " if (it == m.end()) {\n"
- << " return Status::Error(\"Unknown class\");\n"
+ << " return Status::Error(PSLICE() << \"Unknown class \\\"\" << str << \"\\\"\");\n"
<< " }\n"
<< " return it->second;\n";
- sb << "}\n";
+ sb << "}\n\n";
}
-void gen_tl_constructor_from_string(StringBuilder &sb, const tl::simple::Schema &schema, bool is_header) {
+void gen_tl_constructor_from_string(StringBuilder &sb, const tl::simple::Schema &schema, bool is_header, Mode mode) {
Vec vec_for_nullary;
for (auto *custom_type : schema.custom_types) {
+ if (!((custom_type->is_query_ && mode != Mode::Client) || (custom_type->is_result_ && mode != Mode::Server))) {
+ continue;
+ }
Vec vec;
for (auto *constructor : custom_type->constructors) {
- vec.push_back(std::make_pair(constructor->id, constructor->name));
+ vec.emplace_back(constructor->id, constructor->name);
vec_for_nullary.push_back(vec.back());
}
@@ -157,14 +174,18 @@ void gen_tl_constructor_from_string(StringBuilder &sb, const tl::simple::Schema
}
gen_tl_constructor_from_string(sb, "Object", vec_for_nullary, is_header);
+ if (mode == Mode::Client) {
+ return;
+ }
Vec vec_for_function;
for (auto *function : schema.functions) {
- vec_for_function.push_back(std::make_pair(function->id, function->name));
+ vec_for_function.emplace_back(function->id, function->name);
}
gen_tl_constructor_from_string(sb, "Function", vec_for_function, is_header);
}
-void gen_json_converter_file(const tl::simple::Schema &schema, const std::string &file_name_base, bool is_header) {
+void gen_json_converter_file(const tl::simple::Schema &schema, const std::string &file_name_base, bool is_header,
+ Mode mode) {
auto file_name = is_header ? file_name_base + ".h" : file_name_base + ".cpp";
file_name = "auto/" + file_name;
auto old_file_content = [&] {
@@ -195,30 +216,76 @@ void gen_json_converter_file(const tl::simple::Schema &schema, const std::string
sb << "#include \"td/utils/base64.h\"\n";
sb << "#include \"td/utils/common.h\"\n";
+ sb << "#include \"td/utils/FlatHashMap.h\"\n";
sb << "#include \"td/utils/Slice.h\"\n\n";
- sb << "#include <unordered_map>\n\n";
+ sb << "#include <functional>\n\n";
}
sb << "namespace td {\n";
- sb << "namespace td_api{\n";
- gen_tl_constructor_from_string(sb, schema, is_header);
- gen_from_json(sb, schema, is_header);
- gen_to_json(sb, schema, is_header);
+ sb << "namespace td_api {\n";
+ if (is_header) {
+ sb << "\nvoid to_json(JsonValueScope &jv, const tl_object_ptr<Object> &value);\n";
+ sb << "\nStatus from_json(tl_object_ptr<Function> &to, td::JsonValue from);\n";
+ sb << "\nvoid to_json(JsonValueScope &jv, const Object &object);\n";
+ sb << "\nvoid to_json(JsonValueScope &jv, const Function &object);\n\n";
+ } else {
+ sb << R"ABCD(
+void to_json(JsonValueScope &jv, const tl_object_ptr<Object> &value) {
+ td::to_json(jv, std::move(value));
+}
+
+Status from_json(tl_object_ptr<Function> &to, td::JsonValue from) {
+ return td::from_json(to, std::move(from));
+}
+
+template <class T>
+auto lazy_to_json(JsonValueScope &jv, const T &t) -> decltype(td_api::to_json(jv, t)) {
+ return td_api::to_json(jv, t);
+}
+
+template <class T>
+void lazy_to_json(std::reference_wrapper<JsonValueScope>, const T &t) {
+ UNREACHABLE();
+}
+
+void to_json(JsonValueScope &jv, const Object &object) {
+ downcast_call(const_cast<Object &>(object), [&jv](const auto &object) { lazy_to_json(jv, object); });
+}
+
+void to_json(JsonValueScope &jv, const Function &object) {
+ downcast_call(const_cast<Function &>(object), [&jv](const auto &object) { lazy_to_json(jv, object); });
+}
+
+)ABCD";
+ }
+ gen_tl_constructor_from_string(sb, schema, is_header, mode);
+ gen_from_json(sb, schema, is_header, mode);
+ gen_to_json(sb, schema, is_header, mode);
sb << "} // namespace td_api\n";
sb << "} // namespace td\n";
CHECK(!sb.is_error());
buf.resize(sb.as_cslice().size());
+#if TD_WINDOWS
+ string new_file_content;
+ for (auto c : buf) {
+ if (c == '\n') {
+ new_file_content += '\r';
+ }
+ new_file_content += c;
+ }
+#else
auto new_file_content = std::move(buf);
+#endif
if (new_file_content != old_file_content.as_slice()) {
write_file(file_name, new_file_content).ensure();
}
}
-void gen_json_converter(const tl::tl_config &config, const std::string &file_name) {
+void gen_json_converter(const tl::tl_config &config, const std::string &file_name, Mode mode) {
tl::simple::Schema schema(config);
- gen_json_converter_file(schema, file_name, true);
- gen_json_converter_file(schema, file_name, false);
+ gen_json_converter_file(schema, file_name, true, mode);
+ gen_json_converter_file(schema, file_name, false, mode);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_json_converter.h b/protocols/Telegram/tdlib/td/td/generate/tl_json_converter.h
index 719ff451f0..2263bbf5e9 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_json_converter.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_json_converter.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,11 +7,12 @@
#pragma once
#include "td/tl/tl_config.h"
+#include "td/tl/tl_writer.h"
#include <string>
namespace td {
-void gen_json_converter(const tl::tl_config &config, const std::string &file_name);
+void gen_json_converter(const tl::tl_config &config, const std::string &file_name, tl::TL_writer::Mode mode);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_c.h b/protocols/Telegram/tdlib/td/td/generate/tl_writer_c.h
index 9402bae72f..7edb052b44 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_c.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_c.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -16,7 +16,7 @@
namespace td {
-class TlWriterCCommon : public tl::TL_writer {
+class TlWriterCCommon final : public tl::TL_writer {
public:
int is_header_;
std::string prefix_;
@@ -24,47 +24,47 @@ class TlWriterCCommon : public tl::TL_writer {
: TL_writer(name), is_header_(is_header), prefix_(prefix) {
}
- int get_max_arity() const override {
+ int get_max_arity() const final {
return 0;
}
- bool is_built_in_simple_type(const std::string &name) const override {
+ bool is_built_in_simple_type(const std::string &name) const final {
return name == "Bool" || name == "Int32" || name == "Int53" || name == "Int64" || name == "Double" ||
name == "String" || name == "Bytes";
}
- bool is_built_in_complex_type(const std::string &name) const override {
+ bool is_built_in_complex_type(const std::string &name) const final {
return name == "Vector";
}
- bool is_type_bare(const tl::tl_type *t) const override {
+ bool is_type_bare(const tl::tl_type *t) const final {
return t->simple_constructors <= 1 || (is_built_in_simple_type(t->name) && t->name != "Bool") ||
is_built_in_complex_type(t->name);
}
- std::vector<std::string> get_parsers() const override {
+ std::vector<std::string> get_parsers() const final {
return {};
}
- int get_parser_type(const tl::tl_combinator *t, const std::string &name) const override {
+ int get_parser_type(const tl::tl_combinator *t, const std::string &name) const final {
return 0;
}
- std::vector<std::string> get_storers() const override {
+ std::vector<std::string> get_storers() const final {
return {};
}
- std::vector<std::string> get_additional_functions() const override {
+ std::vector<std::string> get_additional_functions() const final {
return {"TdConvertToInternal", "TdConvertFromInternal", "TdSerialize", "TdToString",
"TdDestroyObject", "TdStackStorer", "TdStackFetcher", "enum"};
}
- int get_storer_type(const tl::tl_combinator *t, const std::string &name) const override {
+ int get_storer_type(const tl::tl_combinator *t, const std::string &name) const final {
return name == "to_string" || name == "to_cpp_string";
}
- std::string gen_base_tl_class_name() const override {
+ std::string gen_base_tl_class_name() const final {
return "Object";
}
- std::string gen_base_type_class_name(int arity) const override {
+ std::string gen_base_type_class_name(int arity) const final {
assert(arity == 0);
return "Object";
}
- std::string gen_base_function_class_name() const override {
+ std::string gen_base_function_class_name() const final {
return "Function";
}
@@ -118,13 +118,13 @@ class TlWriterCCommon : public tl::TL_writer {
return name;
}
- std::string gen_class_name(std::string name) const override {
+ std::string gen_class_name(std::string name) const final {
if (name == "Object" || name == "#") {
assert(false);
}
return to_CamelCase(name);
}
- std::string gen_field_name(std::string name) const override {
+ std::string gen_field_name(std::string name) const final {
return gen_native_field_name(name);
}
@@ -220,15 +220,19 @@ class TlWriterCCommon : public tl::TL_writer {
return !force ? ("struct Td" + gen_main_class_name(t) + " *") : gen_main_class_name(t);
}
- std::string gen_type_name(const tl::tl_tree_type *tree_type) const override {
+ std::string gen_type_name(const tl::tl_tree_type *tree_type) const final {
return gen_type_name(tree_type, false);
}
- std::string gen_output_begin() const override {
+ std::string gen_output_begin() const final {
if (is_header_ == 1) {
return "#pragma once\n"
"#ifdef __cplusplus\n"
"extern \"C\" {\n"
"#endif\n"
+ "struct TdBytes {\n"
+ " unsigned char *data;\n"
+ " int len;\n"
+ "};\n"
"#define TDC_VECTOR(tdc_type_name,tdc_type) \\\n"
" struct TdVector ## tdc_type_name { \\\n"
" int len;\\\n"
@@ -238,6 +242,7 @@ class TlWriterCCommon : public tl::TL_writer {
"TDC_VECTOR(Int,int)\n"
"TDC_VECTOR(Long,long long)\n"
"TDC_VECTOR(String,char *)\n"
+ "TDC_VECTOR(Bytes,struct TdBytes)\n"
"struct TdStackStorerMethods {\n"
" void (*pack_string)(const char *s);\n"
" void (*pack_bytes)(const unsigned char *s, int len);\n"
@@ -259,10 +264,6 @@ class TlWriterCCommon : public tl::TL_writer {
" void (*get_arr_field)(int idx);\n"
" int (*get_arr_size)(void);\n"
" int (*is_nil)(void);\n"
- "};\n"
- "struct TdBytes {\n"
- " unsigned char *data;\n"
- " int len;\n"
"};\n";
}
if (is_header_ == -1) {
@@ -275,10 +276,9 @@ class TlWriterCCommon : public tl::TL_writer {
"#include \"td/utils/logging.h\"\n"
"#include \"td/utils/misc.h\"\n"
"#include \"td/utils/Slice.h\"\n"
- "#include \"td/utils/tl_storers.h\"\n"
"\n";
}
- std::string gen_output_end() const override {
+ std::string gen_output_end() const final {
if (is_header_ == 1) {
return "#ifdef __cplusplus\n"
"}\n"
@@ -289,7 +289,7 @@ class TlWriterCCommon : public tl::TL_writer {
return "";
}
- std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const override {
+ std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const final {
if (is_header_ != 1 || class_name == "") {
return "";
}
@@ -302,8 +302,8 @@ class TlWriterCCommon : public tl::TL_writer {
class_name + ", struct TdVector" + class_name + " *);\n";
}
- std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const override {
+ std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy,
+ const tl::tl_tree *result) const final {
if (is_header_ != 1 || class_name == "") {
return "";
}
@@ -314,12 +314,12 @@ class TlWriterCCommon : public tl::TL_writer {
}
return "struct Td" + class_name + " {\n" + " int ID;\n int refcnt;\n" + tail;
}
- std::string gen_class_end() const override {
+ std::string gen_class_end() const final {
return "";
}
std::string gen_field_definition(const std::string &class_name, const std::string &type_name,
- const std::string &field_name) const override {
+ const std::string &field_name) const final {
if (is_header_ != 1 || class_name == "") {
return "";
}
@@ -327,14 +327,14 @@ class TlWriterCCommon : public tl::TL_writer {
}
std::string gen_store_function_begin(const std::string &storer_name, const std::string &class_name, int arity,
- std::vector<tl::var_description> &vars, int storer_type) const override {
+ std::vector<tl::var_description> &vars, int storer_type) const final {
return "";
}
- std::string gen_store_function_end(const std::vector<tl::var_description> &vars, int storer_type) const override {
+ std::string gen_store_function_end(const std::vector<tl::var_description> &vars, int storer_type) const final {
return "";
}
- std::string gen_constructor_begin(int fields_num, const std::string &class_name, bool is_default) const override {
+ std::string gen_constructor_begin(int field_count, const std::string &class_name, bool is_default) const final {
if (!is_default || is_header_ == -1 || class_name == "") {
return "";
}
@@ -343,11 +343,11 @@ class TlWriterCCommon : public tl::TL_writer {
ss << "};\n";
}
ss << "struct Td" + gen_class_name(class_name) + " *TdCreateObject" + gen_class_name(class_name) + " (" +
- (fields_num ? "" : "void");
+ (field_count ? "" : "void");
return ss.str();
}
std::string gen_constructor_parameter(int field_num, const std::string &class_name, const tl::arg &a,
- bool is_default) const override {
+ bool is_default) const final {
if (!is_default || is_header_ == -1) {
return "";
}
@@ -358,10 +358,10 @@ class TlWriterCCommon : public tl::TL_writer {
return ss.str();
}
std::string gen_constructor_field_init(int field_num, const std::string &class_name, const tl::arg &a,
- bool is_default) const override {
+ bool is_default) const final {
return "";
}
- std::string gen_constructor_end(const tl::tl_combinator *t, int fields_num, bool is_default) const override {
+ std::string gen_constructor_end(const tl::tl_combinator *t, int field_count, bool is_default) const final {
if (!is_default || is_header_ == -1) {
return "";
}
@@ -388,7 +388,7 @@ class TlWriterCCommon : public tl::TL_writer {
return ss.str();
}
std::string gen_additional_function(const std::string &function_name, const tl::tl_combinator *t,
- bool is_function) const override {
+ bool is_function) const final {
std::stringstream ss;
if (function_name == "enum") {
return ss.str();
@@ -546,11 +546,11 @@ class TlWriterCCommon : public tl::TL_writer {
}
};
- struct file_store_methods_to_td : public file_store_methods {
+ struct file_store_methods_to_td final : public file_store_methods {
explicit file_store_methods_to_td(const class TlWriterCCommon *cl) : cl(cl) {
}
void store_simple_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string type_name) const override {
+ std::string type_name) const final {
if (type_name == "String") {
ss << offset << res_var << " = (" << var << ") ? " << var << ": \"\";\n";
} else if (type_name == "Bytes") {
@@ -562,35 +562,35 @@ class TlWriterCCommon : public tl::TL_writer {
}
}
void store_common_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string type_name) const override {
+ std::string type_name) const final {
ss << offset << res_var << " = TdConvertToInternal (" << var << ");\n";
}
void store_array_start(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
}
void store_array_el(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string idx) const override {
+ std::string idx) const final {
ss << offset << res_var << ".push_back (std::move (" << var << "));\n";
}
void store_array_finish(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
}
- void store_nil(std::stringstream &ss, std::string offset) const override {
+ void store_nil(std::stringstream &ss, std::string offset) const final {
ss << offset << "return nullptr;\n";
}
std::string store_field_start(std::stringstream &ss, std::string offset, int depth,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
std::string res_var = "v" + int_to_string(depth);
ss << offset << cl->gen_native_type_name(tree_type, true) << " " << res_var << ";\n";
return res_var;
}
- void store_field_finish(std::stringstream &ss, std::string offset, std::string res_var) const override {
+ void store_field_finish(std::stringstream &ss, std::string offset, std::string res_var) const final {
}
void store_arg_finish(std::stringstream &ss, std::string offset, std::string arg_name,
- std::string res_var) const override {
+ std::string res_var) const final {
}
void store_constructor_finish(std::stringstream &ss, std::string offset, const tl::tl_combinator *t,
- std::vector<std::string> res_var) const override {
+ std::vector<std::string> res_var) const final {
auto native_class_name = cl->gen_native_class_name(t->name);
ss << offset << "return td::td_api::make_object<td::td_api::" << native_class_name << ">(";
bool is_first = true;
@@ -608,11 +608,11 @@ class TlWriterCCommon : public tl::TL_writer {
const class TlWriterCCommon *cl;
};
- struct file_store_methods_destroy : public file_store_methods {
+ struct file_store_methods_destroy final : public file_store_methods {
explicit file_store_methods_destroy(const class TlWriterCCommon *cl) : cl(cl) {
}
void store_simple_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string type_name) const override {
+ std::string type_name) const final {
if (type_name == "String") {
ss << offset << "free (" << var << ");\n";
} else if (type_name == "Bytes") {
@@ -620,32 +620,32 @@ class TlWriterCCommon : public tl::TL_writer {
}
}
void store_common_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string type_name) const override {
+ std::string type_name) const final {
ss << offset << "TdDestroyObject (" << var << ");\n";
}
void store_array_start(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
}
void store_array_el(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string idx) const override {
+ std::string idx) const final {
}
void store_array_finish(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
ss << offset << "delete[] " << var << "->data;\n" << offset << "delete " << var << ";\n";
}
- void store_nil(std::stringstream &ss, std::string offset) const override {
+ void store_nil(std::stringstream &ss, std::string offset) const final {
ss << offset << "return;\n";
}
std::string store_field_start(std::stringstream &ss, std::string offset, int depth,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
return "";
}
- void store_field_finish(std::stringstream &ss, std::string offset, std::string res_var) const override {
+ void store_field_finish(std::stringstream &ss, std::string offset, std::string res_var) const final {
}
void store_arg_finish(std::stringstream &ss, std::string offset, std::string arg_name,
- std::string res_var) const override {
+ std::string res_var) const final {
}
- void store_constructor_start(std::stringstream &ss, std::string offset, const tl::tl_combinator *t) const override {
+ void store_constructor_start(std::stringstream &ss, std::string offset, const tl::tl_combinator *t) const final {
ss << "#if TD_MSVC\n";
ss << offset << "static_assert (sizeof (long) == sizeof (var->refcnt), \"Illegal InterlockedDecrement\");\n";
ss << offset << "int ref = InterlockedDecrement (reinterpret_cast<long *>(&var->refcnt));\n";
@@ -660,16 +660,16 @@ class TlWriterCCommon : public tl::TL_writer {
ss << offset << "}\n";
}
void store_constructor_finish(std::stringstream &ss, std::string offset, const tl::tl_combinator *t,
- std::vector<std::string> res_var) const override {
+ std::vector<std::string> res_var) const final {
ss << offset << "delete var;\n";
}
const class TlWriterCCommon *cl;
};
- struct file_store_methods_stack : public file_store_methods {
+ struct file_store_methods_stack final : public file_store_methods {
explicit file_store_methods_stack(const class TlWriterCCommon *cl) : cl(cl) {
}
void store_simple_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string type_name) const override {
+ std::string type_name) const final {
if (type_name == "String") {
ss << offset << "M->pack_string (" << var << ");\n";
} else if (type_name == "Bytes") {
@@ -685,41 +685,41 @@ class TlWriterCCommon : public tl::TL_writer {
}
}
void store_common_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string type_name) const override {
+ std::string type_name) const final {
ss << offset << "TdStackStorer (" << var << ", M);\n";
}
void store_array_start(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
ss << offset << "M->new_array ();\n";
}
void store_array_el(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string idx) const override {
+ std::string idx) const final {
ss << offset << "M->new_arr_field (" << idx << ");\n";
}
void store_array_finish(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
}
- void store_nil(std::stringstream &ss, std::string offset) const override {
+ void store_nil(std::stringstream &ss, std::string offset) const final {
ss << offset << "M->pack_bool (0);\n" << offset << "return;\n";
}
std::string store_field_start(std::stringstream &ss, std::string offset, int depth,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
return "";
}
- void store_field_finish(std::stringstream &ss, std::string offset, std::string res_var) const override {
+ void store_field_finish(std::stringstream &ss, std::string offset, std::string res_var) const final {
}
void store_arg_finish(std::stringstream &ss, std::string offset, std::string arg_name,
- std::string res_var) const override {
+ std::string res_var) const final {
ss << offset << "M->new_field (\"" << arg_name << "\");\n";
}
- void store_constructor_start(std::stringstream &ss, std::string offset, const tl::tl_combinator *t) const override {
+ void store_constructor_start(std::stringstream &ss, std::string offset, const tl::tl_combinator *t) const final {
ss << offset << "M->new_table ();\n";
auto class_name = cl->gen_class_name(t->name);
ss << offset << "M->pack_string (\"" << class_name << "\");\n";
ss << offset << "M->new_field (\"ID\");\n";
}
void store_constructor_finish(std::stringstream &ss, std::string offset, const tl::tl_combinator *t,
- std::vector<std::string> res_var) const override {
+ std::vector<std::string> res_var) const final {
}
const class TlWriterCCommon *cl;
};
@@ -766,15 +766,15 @@ class TlWriterCCommon : public tl::TL_writer {
}
};
- struct file_fetch_methods_from_td : public file_fetch_methods {
+ struct file_fetch_methods_from_td final : public file_fetch_methods {
explicit file_fetch_methods_from_td(const class TlWriterCCommon *cl) : cl(cl) {
}
std::string fetch_field_start(std::stringstream &ss, std::string offset, int depth,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
return "";
}
void fetch_simple_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string type_name) const override {
+ std::string type_name) const final {
if (type_name == "String") {
ss << offset << res_var << " = (" << var << ".length ()) ? td::str_dup (" << var << ") : nullptr;\n";
} else if (type_name == "Bytes") {
@@ -790,7 +790,7 @@ class TlWriterCCommon : public tl::TL_writer {
}
}
void fetch_common_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
auto native_type_name = cl->gen_native_type_name(tree_type, false);
ss << offset << "if (!" << var << ") {\n"
<< offset << " " << res_var << " = nullptr;\n"
@@ -800,32 +800,32 @@ class TlWriterCCommon : public tl::TL_writer {
<< offset << "}\n";
}
void fetch_array_size(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
ss << offset << res_var << " = (int)" << var << ".size ();\n";
}
std::string fetch_array_field_start(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string idx, const tl::tl_tree_type *tree_type) const override {
+ std::string idx, const tl::tl_tree_type *tree_type) const final {
return var + "[" + idx + "]";
}
std::string fetch_dict_field_start(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string key, const tl::tl_tree_type *tree_type) const override {
+ std::string key, const tl::tl_tree_type *tree_type) const final {
return var + "." + key;
}
void fetch_field_finish(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
}
const class TlWriterCCommon *cl;
};
- struct file_fetch_methods_stack : public file_fetch_methods {
+ struct file_fetch_methods_stack final : public file_fetch_methods {
explicit file_fetch_methods_stack(const class TlWriterCCommon *cl) : cl(cl) {
}
std::string fetch_field_start(std::stringstream &ss, std::string offset, int depth,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
return "";
}
void fetch_simple_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string type_name) const override {
+ std::string type_name) const final {
if (type_name == "String") {
ss << offset << res_var << " = M->get_string ();\n";
} else if (type_name == "Bytes") {
@@ -841,7 +841,7 @@ class TlWriterCCommon : public tl::TL_writer {
}
}
void fetch_common_type(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
auto class_name = cl->gen_main_class_name(tree_type->type);
ss << offset << "if (M->is_nil ()) {\n"
<< offset << " " << res_var << " = nullptr;\n"
@@ -850,21 +850,21 @@ class TlWriterCCommon : public tl::TL_writer {
<< offset << "}\n";
}
void fetch_array_size(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
ss << offset << res_var << " = M->get_arr_size ();\n";
}
std::string fetch_array_field_start(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string idx, const tl::tl_tree_type *tree_type) const override {
+ std::string idx, const tl::tl_tree_type *tree_type) const final {
ss << offset << " M->get_arr_field (" << idx << ");\n";
return "";
}
std::string fetch_dict_field_start(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- std::string key, const tl::tl_tree_type *tree_type) const override {
+ std::string key, const tl::tl_tree_type *tree_type) const final {
ss << offset << "M->get_field (\"" << key << "\");\n";
return "";
}
void fetch_field_finish(std::stringstream &ss, std::string offset, std::string res_var, std::string var,
- const tl::tl_tree_type *tree_type) const override {
+ const tl::tl_tree_type *tree_type) const final {
ss << offset << "M->pop ();\n";
}
const class TlWriterCCommon *cl;
@@ -948,74 +948,74 @@ class TlWriterCCommon : public tl::TL_writer {
<< "}\n";
}
- std::string gen_array_type_name(const tl::tl_tree_array *arr, const std::string &field_name) const override {
+ std::string gen_array_type_name(const tl::tl_tree_array *arr, const std::string &field_name) const final {
assert(false);
return std::string();
}
- std::string gen_var_type_name() const override {
+ std::string gen_var_type_name() const final {
assert(false);
return std::string();
}
- std::string gen_int_const(const tl::tl_tree *tree_c, const std::vector<tl::var_description> &vars) const override {
+ std::string gen_int_const(const tl::tl_tree *tree_c, const std::vector<tl::var_description> &vars) const final {
assert(false);
return std::string();
}
- std::string gen_var_name(const tl::var_description &desc) const override {
+ std::string gen_var_name(const tl::var_description &desc) const final {
assert(false);
return "";
}
- std::string gen_parameter_name(int index) const override {
+ std::string gen_parameter_name(int index) const final {
assert(false);
return "";
}
- std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const override {
+ std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const final {
return "";
}
std::string gen_vars(const tl::tl_combinator *t, const tl::tl_tree_type *result_type,
- std::vector<tl::var_description> &vars) const override {
+ std::vector<tl::var_description> &vars) const final {
assert(vars.empty());
return "";
}
- std::string gen_function_vars(const tl::tl_combinator *t, std::vector<tl::var_description> &vars) const override {
+ std::string gen_function_vars(const tl::tl_combinator *t, std::vector<tl::var_description> &vars) const final {
assert(vars.empty());
return "";
}
std::string gen_uni(const tl::tl_tree_type *result_type, std::vector<tl::var_description> &vars,
- bool check_negative) const override {
+ bool check_negative) const final {
assert(result_type->children.empty());
return "";
}
- std::string gen_constructor_id_store(std::int32_t id, int storer_type) const override {
+ std::string gen_constructor_id_store(std::int32_t id, int storer_type) const final {
return "";
}
std::string gen_field_fetch(int field_num, const tl::arg &a, std::vector<tl::var_description> &vars, bool flat,
- int parser_type) const override {
+ int parser_type) const final {
return "";
}
std::string gen_field_store(const tl::arg &a, std::vector<tl::var_description> &vars, bool flat,
- int storer_type) const override {
+ int storer_type) const final {
return "";
}
std::string gen_type_fetch(const std::string &field_name, const tl::tl_tree_type *tree_type,
- const std::vector<tl::var_description> &vars, int parser_type) const override {
+ const std::vector<tl::var_description> &vars, int parser_type) const final {
assert(vars.empty());
return "";
}
std::string gen_type_store(const std::string &field_name, const tl::tl_tree_type *tree_type,
- const std::vector<tl::var_description> &vars, int storer_type) const override {
+ const std::vector<tl::var_description> &vars, int storer_type) const final {
return "";
}
- std::string gen_var_type_fetch(const tl::arg &a) const override {
+ std::string gen_var_type_fetch(const tl::arg &a) const final {
assert(false);
return "";
}
- std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const override {
+ std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const final {
if (is_proxy || is_header_ != 1) {
return "";
}
@@ -1023,46 +1023,47 @@ class TlWriterCCommon : public tl::TL_writer {
return "";
}
- std::string gen_function_result_type(const tl::tl_tree *result) const override {
+ std::string gen_function_result_type(const tl::tl_tree *result) const final {
return "";
}
- std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name, int arity,
- std::vector<tl::var_description> &vars, int parser_type) const override {
+ std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
+ const std::string &parent_class_name, int arity, int field_count,
+ std::vector<tl::var_description> &vars, int parser_type) const final {
return "";
}
- std::string gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
- int parser_type) const override {
+ std::string gen_fetch_function_end(bool has_parent, int field_count, const std::vector<tl::var_description> &vars,
+ int parser_type) const final {
return "";
}
std::string gen_fetch_function_result_begin(const std::string &parser_name, const std::string &class_name,
- const tl::tl_tree *result) const override {
+ const tl::tl_tree *result) const final {
return "";
}
- std::string gen_fetch_function_result_end() const override {
+ std::string gen_fetch_function_result_end() const final {
return "";
}
std::string gen_fetch_function_result_any_begin(const std::string &parser_name, const std::string &class_name,
- bool is_proxy) const override {
+ bool is_proxy) const final {
return "";
}
- std::string gen_fetch_function_result_any_end(bool is_proxy) const override {
+ std::string gen_fetch_function_result_any_end(bool is_proxy) const final {
return "";
}
- std::string gen_fetch_switch_begin() const override {
+ std::string gen_fetch_switch_begin() const final {
return "";
}
- std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const override {
+ std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const final {
return "";
}
- std::string gen_fetch_switch_end() const override {
+ std::string gen_fetch_switch_end() const final {
return "";
}
std::string gen_additional_proxy_function_begin(const std::string &function_name, const tl::tl_type *type,
- const std::string &name, int arity, bool is_function) const override {
+ const std::string &name, int arity, bool is_function) const final {
std::stringstream ss;
std::string class_name;
std::string native_class_name;
@@ -1206,7 +1207,7 @@ class TlWriterCCommon : public tl::TL_writer {
}
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type,
- const std::string &class_name, int arity) const override {
+ const std::string &class_name, int arity) const final {
if (is_header_ != (function_name == "enum" ? 1 : 0)) {
return "";
}
@@ -1241,8 +1242,7 @@ class TlWriterCCommon : public tl::TL_writer {
}
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type,
- const tl::tl_combinator *t, int arity,
- bool is_function) const override {
+ const tl::tl_combinator *t, int arity, bool is_function) const final {
if (is_header_ != (function_name == "enum" ? 1 : 0)) {
return "";
}
@@ -1319,7 +1319,7 @@ class TlWriterCCommon : public tl::TL_writer {
}
}
std::string gen_additional_proxy_function_end(const std::string &function_name, const tl::tl_type *type,
- bool is_function) const override {
+ bool is_function) const final {
if (is_header_ != (function_name == "enum" ? 1 : 0)) {
return "";
}
@@ -1350,7 +1350,7 @@ class TlWriterCCommon : public tl::TL_writer {
}
}
- int get_additional_function_type(const std::string &additional_function_name) const override {
+ int get_additional_function_type(const std::string &additional_function_name) const final {
return 2;
}
};
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.cpp b/protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.cpp
index 56a682c64b..9fae767d33 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -23,8 +23,10 @@ std::string TD_TL_writer_cpp::gen_output_begin() const {
"#include \"td/utils/common.h\"\n"
"#include \"td/utils/format.h\"\n"
"#include \"td/utils/logging.h\"\n"
+ "#include \"td/utils/SliceBuilder.h\"\n"
"#include \"td/utils/tl_parsers.h\"\n"
- "#include \"td/utils/tl_storers.h\"\n\n"
+ "#include \"td/utils/tl_storers.h\"\n"
+ "#include \"td/utils/TlStorerToString.h\"\n\n"
"namespace td {\n"
"namespace " +
tl_name +
@@ -32,7 +34,7 @@ std::string TD_TL_writer_cpp::gen_output_begin() const {
"std::string to_string(const BaseObject &value) {\n"
" TlStorerToString storer;\n"
" value.store(storer, \"\");\n"
- " return storer.str();\n"
+ " return storer.move_as_string();\n"
"}\n";
}
@@ -147,10 +149,10 @@ std::string TD_TL_writer_cpp::gen_fetch_class_name(const tl::tl_tree_type *tree_
return "TlFetch" + name;
}
if (name == "String") {
- return "TlFetchString<" + string_type + ">";
+ return "TlFetchString<string>";
}
if (name == "Bytes") {
- return "TlFetchBytes<" + bytes_type + ">";
+ return "TlFetchBytes<bytes>";
}
if (name == "Vector") {
@@ -282,7 +284,7 @@ std::string TD_TL_writer_cpp::gen_var_type_fetch(const tl::arg &a) const {
}
std::string TD_TL_writer_cpp::get_pretty_field_name(std::string field_name) const {
- if (!field_name.empty() && field_name.back() == ']') {
+ if (!field_name.empty() && field_name[0] == '_') {
return "";
}
auto equals_pos = field_name.find('=');
@@ -299,21 +301,22 @@ std::string TD_TL_writer_cpp::get_pretty_field_name(std::string field_name) cons
}
std::string TD_TL_writer_cpp::get_pretty_class_name(std::string class_name) const {
+ if (tl_name != "mtproto_api") {
+ for (std::size_t i = 0; i < class_name.size(); i++) {
+ if (class_name[i] == '_') {
+ class_name[i] = '.';
+ }
+ }
+ }
return class_name;
}
std::string TD_TL_writer_cpp::gen_vector_store(const std::string &field_name, const tl::tl_tree_type *t,
const std::vector<tl::var_description> &vars, int storer_type) const {
- std::string num = field_name.back() == ']' ? "2" : "";
- return "{ const std::vector<" + gen_type_name(t) + "> &v" + num + " = " + field_name +
- "; const std::uint32_t multiplicity" + num + " = static_cast<std::uint32_t>(v" + num +
- ".size()); const auto vector_name" + num + " = \"" + get_pretty_class_name("vector") +
- "[\" + td::to_string(multiplicity" + num + ")+ \"]\"; s.store_class_begin(\"" +
- get_pretty_field_name(field_name) + "\", vector_name" + num +
- ".c_str()); "
- "for (std::uint32_t i" +
- num + " = 0; i" + num + " < multiplicity" + num + "; i" + num + "++) { " +
- gen_type_store("v" + num + "[i" + num + "]", t, vars, storer_type) + " } s.store_class_end(); }";
+ std::string num = !field_name.empty() && field_name[0] == '_' ? "2" : "";
+ return "{ s.store_vector_begin(\"" + get_pretty_field_name(field_name) + "\", " + field_name +
+ ".size()); for (const auto &_value" + num + " : " + field_name + ") { " +
+ gen_type_store("_value" + num, t, vars, storer_type) + " } s.store_class_end(); }";
}
std::string TD_TL_writer_cpp::gen_store_class_name(const tl::tl_tree_type *tree_type) const {
@@ -328,7 +331,8 @@ std::string TD_TL_writer_cpp::gen_store_class_name(const tl::tl_tree_type *tree_
return "TlStoreBool";
}
if (name == "True") {
- return "TlStoreTrue";
+ assert(false);
+ return "";
}
if (name == "String" || name == "Bytes") {
return "TlStoreString";
@@ -403,8 +407,8 @@ std::string TD_TL_writer_cpp::gen_type_store(const std::string &field_name, cons
return gen_vector_store(field_name, child, vars, storer_type);
} else {
assert(tree_type->children.empty());
- return "if (" + field_name + " == nullptr) { s.store_field(\"" + get_pretty_field_name(field_name) +
- "\", \"null\"); } else { " + field_name + "->store(s, \"" + get_pretty_field_name(field_name) + "\"); }";
+ return "s.store_object_field(\"" + get_pretty_field_name(field_name) + "\", static_cast<const BaseObject *>(" +
+ field_name + ".get()));";
}
}
@@ -442,6 +446,13 @@ std::string TD_TL_writer_cpp::gen_field_store(const tl::arg &a, std::vector<tl::
return "";
}
+ if (a.exist_var_num >= 0 && a.var_num < 0 && a.type->get_type() == tl::NODE_TYPE_TYPE) {
+ const tl::tl_tree_type *tree_type = static_cast<tl::tl_tree_type *>(a.type);
+ if (tree_type->type->name == "True") {
+ return "";
+ }
+ }
+
if (a.exist_var_num >= 0) {
assert(a.exist_var_num < static_cast<int>(vars.size()));
assert(vars[a.exist_var_num].is_stored);
@@ -482,7 +493,7 @@ std::string TD_TL_writer_cpp::gen_forward_class_declaration(const std::string &c
}
std::string TD_TL_writer_cpp::gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const {
+ bool is_proxy, const tl::tl_tree *result) const {
return "";
}
@@ -506,37 +517,53 @@ std::string TD_TL_writer_cpp::gen_function_result_type(const tl::tl_tree *result
}
std::string TD_TL_writer_cpp::gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
- int arity, std::vector<tl::var_description> &vars,
- int parser_type) const {
+ const std::string &parent_class_name, int arity, int field_count,
+ std::vector<tl::var_description> &vars, int parser_type) const {
for (std::size_t i = 0; i < vars.size(); i++) {
assert(vars[i].is_stored == false);
}
std::string fetched_type = "object_ptr<" + class_name + "> ";
+ std::string returned_type = "object_ptr<" + parent_class_name + "> ";
assert(arity == 0);
if (parser_type == 0) {
- return "\n" + class_name + "::" + class_name + "(" + parser_name +
- " &p)\n"
- "#define FAIL(error) p.set_error(error)\n";
+ std::string result = "\n" + returned_type + class_name + "::fetch(" + parser_name +
+ " &p) {\n"
+ " return make_tl_object<" +
+ class_name + ">(";
+ if (field_count == 0) {
+ result += ");\n";
+ } else {
+ result +=
+ "p);\n"
+ "}\n\n" +
+ class_name + "::" + class_name + "(" + parser_name +
+ " &p)\n"
+ "#define FAIL(error) p.set_error(error)\n";
+ }
+ return result;
}
- return "\n" + fetched_type + class_name + "::fetch(" + parser_name +
+ return "\n" + returned_type + class_name + "::fetch(" + parser_name +
" &p) {\n"
"#define FAIL(error) p.set_error(error); return nullptr;\n" +
(parser_type == -1 ? "" : " " + fetched_type + "res = make_tl_object<" + class_name + ">();\n");
}
-std::string TD_TL_writer_cpp::gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
+std::string TD_TL_writer_cpp::gen_fetch_function_end(bool has_parent, int field_count,
+ const std::vector<tl::var_description> &vars,
int parser_type) const {
for (std::size_t i = 0; i < vars.size(); i++) {
assert(vars[i].is_stored);
}
if (parser_type == 0) {
+ if (field_count == 0) {
+ return "}\n";
+ }
return "#undef FAIL\n"
- "{" +
- (field_num == 0 ? "\n (void)p;\n" : std::string()) + "}\n";
+ "{}\n";
}
if (parser_type == -1) {
@@ -545,7 +572,9 @@ std::string TD_TL_writer_cpp::gen_fetch_function_end(int field_num, const std::v
}
return " if (p.get_error()) { FAIL(\"\"); }\n"
- " return res;\n"
+ " return " +
+ std::string(has_parent ? "std::move(res)" : "res") +
+ ";\n"
"#undef FAIL\n"
"}\n";
}
@@ -631,7 +660,7 @@ std::string TD_TL_writer_cpp::gen_fetch_switch_end() const {
" }\n";
}
-std::string TD_TL_writer_cpp::gen_constructor_begin(int fields_num, const std::string &class_name,
+std::string TD_TL_writer_cpp::gen_constructor_begin(int field_count, const std::string &class_name,
bool is_default) const {
return "\n" + class_name + "::" + class_name + "(";
}
@@ -644,7 +673,7 @@ std::string TD_TL_writer_cpp::gen_constructor_field_init(int field_num, const st
}
std::string move_begin;
std::string move_end;
- if ((field_type == bytes_type || field_type.compare(0, 11, "std::vector") == 0 ||
+ if ((field_type == "bytes" || field_type.compare(0, 5, "array") == 0 ||
field_type.compare(0, 10, "object_ptr") == 0) &&
!is_default) {
move_begin = "std::move(";
@@ -655,8 +684,8 @@ std::string TD_TL_writer_cpp::gen_constructor_field_init(int field_num, const st
(is_default ? "" : gen_field_name(a.name)) + move_end + ")\n";
}
-std::string TD_TL_writer_cpp::gen_constructor_end(const tl::tl_combinator *t, int fields_num, bool is_default) const {
- if (fields_num == 0) {
+std::string TD_TL_writer_cpp::gen_constructor_end(const tl::tl_combinator *t, int field_count, bool is_default) const {
+ if (field_count == 0) {
return ") {\n"
"}\n";
}
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.h b/protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.h
index eb64d18a88..a4fbe31cec 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_cpp.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -46,8 +46,8 @@ class TD_TL_writer_cpp : public TD_TL_writer {
std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const override;
- std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const override;
+ std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy,
+ const tl::tl_tree *result) const override;
std::string gen_class_end() const override;
std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const override;
@@ -76,9 +76,10 @@ class TD_TL_writer_cpp : public TD_TL_writer {
std::string gen_function_result_type(const tl::tl_tree *result) const override;
- std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name, int arity,
+ std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
+ const std::string &parent_class_name, int arity, int field_count,
std::vector<tl::var_description> &vars, int parser_type) const override;
- std::string gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
+ std::string gen_fetch_function_end(bool has_parent, int field_count, const std::vector<tl::var_description> &vars,
int parser_type) const override;
std::string gen_fetch_function_result_begin(const std::string &parser_name, const std::string &class_name,
@@ -96,10 +97,10 @@ class TD_TL_writer_cpp : public TD_TL_writer {
std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const override;
std::string gen_fetch_switch_end() const override;
- std::string gen_constructor_begin(int fields_num, const std::string &class_name, bool is_default) const override;
+ std::string gen_constructor_begin(int field_count, const std::string &class_name, bool is_default) const override;
std::string gen_constructor_field_init(int field_num, const std::string &class_name, const tl::arg &a,
bool is_default) const override;
- std::string gen_constructor_end(const tl::tl_combinator *t, int fields_num, bool is_default) const override;
+ std::string gen_constructor_end(const tl::tl_combinator *t, int field_count, bool is_default) const override;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_dotnet.h b/protocols/Telegram/tdlib/td/td/generate/tl_writer_dotnet.h
index 8228fa2334..2e7f11ccad 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_dotnet.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_dotnet.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -17,58 +17,59 @@
namespace td {
namespace tl {
-class TlWriterDotNet : public TL_writer {
+class TlWriterDotNet final : public TL_writer {
public:
bool is_header_;
std::string prefix_;
TlWriterDotNet(const std::string &name, bool is_header, const std::string &prefix = "")
: TL_writer(name), is_header_(is_header), prefix_(prefix) {
}
- int get_max_arity(void) const override {
+ int get_max_arity(void) const final {
return 0;
}
- bool is_built_in_simple_type(const std::string &name) const override {
- return name == "Bool" || name == "Int32" || name == "Int53" || name == "Int64" || name == "Double" || name == "String" || name == "Bytes";
+ bool is_built_in_simple_type(const std::string &name) const final {
+ return name == "Bool" || name == "Int32" || name == "Int53" || name == "Int64" || name == "Double" ||
+ name == "String" || name == "Bytes";
}
- bool is_built_in_complex_type(const std::string &name) const override {
+ bool is_built_in_complex_type(const std::string &name) const final {
return name == "Vector";
}
- bool is_type_bare(const tl_type *t) const override {
+ bool is_type_bare(const tl_type *t) const final {
return t->simple_constructors <= 1 || (is_built_in_simple_type(t->name) && t->name != "Bool") ||
is_built_in_complex_type(t->name);
}
- std::vector<std::string> get_parsers(void) const override {
+ std::vector<std::string> get_parsers(void) const final {
return {"FromUnmanaged"};
}
- int get_parser_type(const tl_combinator *t, const std::string &name) const override {
+ int get_parser_type(const tl_combinator *t, const std::string &name) const final {
return 0;
}
- Mode get_parser_mode(int type) const override {
+ Mode get_parser_mode(int type) const final {
return All; // Server;
}
- std::vector<std::string> get_storers(void) const override {
+ std::vector<std::string> get_storers(void) const final {
return {"ToUnmanaged", "ToString"};
}
- std::vector<std::string> get_additional_functions(void) const override {
+ std::vector<std::string> get_additional_functions(void) const final {
return {"ToUnmanaged", "FromUnmanaged"};
}
- int get_storer_type(const tl_combinator *t, const std::string &name) const override {
+ int get_storer_type(const tl_combinator *t, const std::string &name) const final {
return name == "ToString";
}
- Mode get_storer_mode(int type) const override {
+ Mode get_storer_mode(int type) const final {
return type <= 1 ? All : Server;
}
- std::string gen_base_tl_class_name(void) const override {
+ std::string gen_base_tl_class_name(void) const final {
return "BaseObject";
}
- std::string gen_base_type_class_name(int arity) const override {
+ std::string gen_base_type_class_name(int arity) const final {
assert(arity == 0);
return "Object";
}
- std::string gen_base_function_class_name(void) const override {
+ std::string gen_base_function_class_name(void) const final {
return "Function";
}
@@ -81,13 +82,13 @@ class TlWriterDotNet : public TL_writer {
static std::string to_cCamelCase(const std::string &name, bool flag) {
bool next_to_upper = flag;
std::string result;
- for (int i = 0; i < (int)name.size(); i++) {
+ for (std::size_t i = 0; i < name.size(); i++) {
if (!is_alnum(name[i])) {
next_to_upper = true;
continue;
}
if (next_to_upper) {
- result += (char)to_upper(name[i]);
+ result += to_upper(name[i]);
next_to_upper = false;
} else {
result += name[i];
@@ -97,7 +98,7 @@ class TlWriterDotNet : public TL_writer {
}
std::string gen_native_field_name(std::string name) const {
- for (int i = 0; i < (int)name.size(); i++) {
+ for (std::size_t i = 0; i < name.size(); i++) {
if (!is_alnum(name[i])) {
name[i] = '_';
}
@@ -114,7 +115,7 @@ class TlWriterDotNet : public TL_writer {
if (name == "#") {
return "int32_t";
}
- for (int i = 0; i < (int)name.size(); i++) {
+ for (std::size_t i = 0; i < name.size(); i++) {
if (!is_alnum(name[i])) {
name[i] = '_';
}
@@ -122,19 +123,19 @@ class TlWriterDotNet : public TL_writer {
return name;
}
- std::string gen_class_name(std::string name) const override {
+ std::string gen_class_name(std::string name) const final {
if (name == "Object" || name == "#") {
assert(0);
}
return to_CamelCase(name);
}
- std::string gen_field_name(std::string name) const override {
+ std::string gen_field_name(std::string name) const final {
assert(name.size() > 0);
assert(is_alnum(name.back()));
return to_CamelCase(name);
}
- std::string gen_type_name(const tl_tree_type *tree_type) const override {
+ std::string gen_type_name(const tl_tree_type *tree_type) const final {
const tl_type *t = tree_type->type;
const std::string &name = t->name;
@@ -157,12 +158,12 @@ class TlWriterDotNet : public TL_writer {
return "String^";
}
if (name == "Bytes") {
- return "Array<byte>^";
+ return "Array<BYTE>^";
}
if (name == "Vector") {
assert(t->arity == 1);
- assert((int)tree_type->children.size() == 1);
+ assert(tree_type->children.size() == 1);
assert(tree_type->children[0]->get_type() == NODE_TYPE_TYPE);
const tl_tree_type *child = static_cast<const tl_tree_type *>(tree_type->children[0]);
@@ -171,26 +172,26 @@ class TlWriterDotNet : public TL_writer {
assert(!is_built_in_simple_type(name) && !is_built_in_complex_type(name));
- for (int i = 0; i < (int)tree_type->children.size(); i++) {
+ for (std::size_t i = 0; i < tree_type->children.size(); i++) {
assert(tree_type->children[i]->get_type() == NODE_TYPE_NAT_CONST);
}
return gen_main_class_name(t) + "^";
}
- std::string gen_output_begin(void) const override {
+ std::string gen_output_begin(void) const final {
return prefix_ +
"#include \"td/tl/tl_dotnet_object.h\"\n\n"
"namespace Telegram {\n"
"namespace Td {\n"
"namespace Api {\n";
}
- std::string gen_output_end() const override {
+ std::string gen_output_end() const final {
return "}\n"
"}\n"
"}\n";
}
- std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const override {
+ std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const final {
if (!is_header_) {
return "";
}
@@ -199,8 +200,8 @@ class TlWriterDotNet : public TL_writer {
return ss.str();
}
- std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const override {
+ std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy,
+ const tl::tl_tree *result) const final {
if (!is_header_) {
return "";
}
@@ -211,12 +212,12 @@ class TlWriterDotNet : public TL_writer {
<< " public:\n";
return ss.str();
}
- std::string gen_class_end(void) const override {
+ std::string gen_class_end(void) const final {
return "";
}
std::string gen_field_definition(const std::string &class_name, const std::string &type_name,
- const std::string &field_name) const override {
+ const std::string &field_name) const final {
if (!is_header_) {
return "";
}
@@ -244,7 +245,7 @@ class TlWriterDotNet : public TL_writer {
}
std::string gen_store_function_begin(const std::string &storer_name, const std::string &class_name, int arity,
- std::vector<var_description> &vars, int storer_type) const override {
+ std::vector<var_description> &vars, int storer_type) const final {
if (storer_type < 0) {
return "";
}
@@ -252,42 +253,50 @@ class TlWriterDotNet : public TL_writer {
ss << "\n";
if (storer_type) {
ss << (is_header_ ? " virtual " : "") << "String^ " << (is_header_ ? "" : gen_class_name(class_name) + "::")
- << "ToString()" << (is_header_ ? " override;" : " {\n return ::Telegram::Td::Api::ToString(this);\n}") << "\n";
+ << "ToString()" << (is_header_ ? " override;" : " {\n return ::Telegram::Td::Api::ToString(this);\n}")
+ << "\n";
} else {
ss << (is_header_ ? " virtual " : "") << "NativeObject^ "
<< (is_header_ ? "" : gen_class_name(class_name) + "::") << "ToUnmanaged()";
if (is_header_) {
ss << ";\n";
} else {
- ss << "{\n return REF_NEW NativeObject(::Telegram::Td::Api::ToUnmanaged(this).release());\n}\n";
+ ss << " {\n return REF_NEW NativeObject(::Telegram::Td::Api::ToUnmanaged(this).release());\n}\n";
}
}
return ss.str();
}
- std::string gen_store_function_end(const std::vector<var_description> &vars, int storer_type) const override {
+ std::string gen_store_function_end(const std::vector<var_description> &vars, int storer_type) const final {
return "";
}
- std::string gen_constructor_begin(int fields_num, const std::string &class_name, bool is_default) const override {
+ std::string gen_constructor_begin(int field_count, const std::string &class_name, bool is_default) const final {
std::stringstream ss;
ss << "\n";
ss << (is_header_ ? " " : gen_class_name(class_name) + "::") << gen_class_name(class_name) << "(";
return ss.str();
}
- std::string gen_constructor_parameter(int field_num, const std::string &class_name, const arg &a, bool is_default) const override {
+ std::string gen_constructor_parameter(int field_num, const std::string &class_name, const arg &a,
+ bool is_default) const final {
if (is_default) {
return "";
}
std::stringstream ss;
ss << (field_num == 0 ? "" : ", ");
auto field_type = gen_field_type(a);
- if (field_type.substr(0, 5) != "Array" && field_type.substr(0, 6) != "String" && to_upper(field_type[0]) == field_type[0]) {
- field_type = "::Telegram::Td::Api::" + field_type;
+ auto pos = 0;
+ while (field_type.substr(pos, 6) == "Array<") {
+ pos += 6;
+ }
+ if (field_type.substr(pos, 4) != "BYTE" && field_type.substr(pos, 6) != "String" &&
+ to_upper(field_type[pos]) == field_type[pos]) {
+ field_type = field_type.substr(0, pos) + "::Telegram::Td::Api::" + field_type.substr(pos);
}
ss << field_type << " " << to_camelCase(a.name);
return ss.str();
}
- std::string gen_constructor_field_init(int field_num, const std::string &class_name, const arg &a, bool is_default) const override {
+ std::string gen_constructor_field_init(int field_num, const std::string &class_name, const arg &a,
+ bool is_default) const final {
if (is_default || is_header_) {
return "";
}
@@ -304,19 +313,19 @@ class TlWriterDotNet : public TL_writer {
return ss.str();
}
- std::string gen_constructor_end(const tl_combinator *t, int fields_num, bool is_default) const override {
+ std::string gen_constructor_end(const tl_combinator *t, int field_count, bool is_default) const final {
if (is_header_) {
return ");\n";
}
std::stringstream ss;
- if (fields_num == 0) {
+ if (field_count == 0) {
ss << ") {\n";
}
ss << "}\n";
return ss.str();
}
std::string gen_additional_function(const std::string &function_name, const tl_combinator *t,
- bool is_function) const override {
+ bool is_function) const final {
std::stringstream ss;
if (is_header_ && function_name == "ToUnmanaged") {
ss << "};\n";
@@ -332,13 +341,12 @@ class TlWriterDotNet : public TL_writer {
void gen_to_unmanaged(std::stringstream &ss, const tl_combinator *t) const {
auto native_class_name = gen_native_class_name(t->name);
auto class_name = gen_class_name(t->name);
- ss << "td::td_api::object_ptr<td::td_api::" << native_class_name << "> ToUnmanaged(" << class_name
- << "^ from)";
+ ss << "td::td_api::object_ptr<td::td_api::" << native_class_name << "> ToUnmanaged(" << class_name << "^ from)";
if (is_header_) {
ss << ";\n";
return;
}
- ss << "{\n"
+ ss << " {\n"
<< " if (!from) {\n"
<< " return nullptr;\n"
<< " }\n"
@@ -367,7 +375,7 @@ class TlWriterDotNet : public TL_writer {
ss << ";\n";
return;
}
- ss << "{\n"
+ ss << " {\n"
<< " return REF_NEW " << class_name << "(";
bool is_first = true;
for (auto &it : t->args) {
@@ -376,128 +384,130 @@ class TlWriterDotNet : public TL_writer {
} else {
ss << ", ";
}
- ss << (gen_field_type(it) == "Array<byte>^" ? "Bytes" : "") << "FromUnmanaged(from." << gen_native_field_name(it.name) << ")";
+ bool need_bytes = gen_field_type(it) == "Array<BYTE>^" || gen_field_type(it) == "Array<Array<BYTE>^>^";
+ ss << (need_bytes ? "Bytes" : "") << "FromUnmanaged(from." << gen_native_field_name(it.name) << ")";
}
ss << ");\n}\n";
}
- std::string gen_array_type_name(const tl_tree_array *arr, const std::string &field_name) const override {
+ std::string gen_array_type_name(const tl_tree_array *arr, const std::string &field_name) const final {
assert(0);
return std::string();
}
- std::string gen_var_type_name(void) const override {
+ std::string gen_var_type_name(void) const final {
assert(0);
return std::string();
}
- std::string gen_int_const(const tl_tree *tree_c, const std::vector<var_description> &vars) const override {
+ std::string gen_int_const(const tl_tree *tree_c, const std::vector<var_description> &vars) const final {
assert(0);
return std::string();
}
- std::string gen_var_name(const var_description &desc) const override {
+ std::string gen_var_name(const var_description &desc) const final {
assert(0);
return "";
}
- std::string gen_parameter_name(int index) const override {
+ std::string gen_parameter_name(int index) const final {
assert(0);
return "";
}
- std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const override {
+ std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const final {
return "";
}
std::string gen_vars(const tl_combinator *t, const tl_tree_type *result_type,
- std::vector<var_description> &vars) const override {
+ std::vector<var_description> &vars) const final {
assert(vars.empty());
return "";
}
- std::string gen_function_vars(const tl_combinator *t, std::vector<var_description> &vars) const override {
+ std::string gen_function_vars(const tl_combinator *t, std::vector<var_description> &vars) const final {
assert(vars.empty());
return "";
}
std::string gen_uni(const tl_tree_type *result_type, std::vector<var_description> &vars,
- bool check_negative) const override {
+ bool check_negative) const final {
assert(result_type->children.empty());
return "";
}
- std::string gen_constructor_id_store(std::int32_t id, int storer_type) const override {
+ std::string gen_constructor_id_store(std::int32_t id, int storer_type) const final {
return "";
}
std::string gen_field_fetch(int field_num, const arg &a, std::vector<var_description> &vars, bool flat,
- int parser_type) const override {
+ int parser_type) const final {
return "";
// std::stringstream ss;
// ss << gen_field_name(a.name) << " = from_unmanaged(from->" <<
// gen_native_field_name(a.name) << ");\n"; return ss.str();
}
std::string gen_field_store(const arg &a, std::vector<var_description> &vars, bool flat,
- int storer_type) const override {
+ int storer_type) const final {
return "";
// std::stringstream ss;
// ss << "to_unmanaged(" << gen_field_name(a.name) << ")";
// return ss.str();
}
std::string gen_type_fetch(const std::string &field_name, const tl_tree_type *tree_type,
- const std::vector<var_description> &vars, int parser_type) const override {
+ const std::vector<var_description> &vars, int parser_type) const final {
assert(vars.empty());
return "";
}
std::string gen_type_store(const std::string &field_name, const tl_tree_type *tree_type,
- const std::vector<var_description> &vars, int storer_type) const override {
+ const std::vector<var_description> &vars, int storer_type) const final {
return "";
}
- std::string gen_var_type_fetch(const arg &a) const override {
+ std::string gen_var_type_fetch(const arg &a) const final {
assert(0);
return "";
}
- std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const override {
+ std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const final {
return "";
}
- std::string gen_function_result_type(const tl_tree *result) const override {
+ std::string gen_function_result_type(const tl_tree *result) const final {
return "";
}
- std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name, int arity,
- std::vector<var_description> &vars, int parser_type) const override {
+ std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
+ const std::string &parent_class_name, int arity, int field_count,
+ std::vector<var_description> &vars, int parser_type) const final {
return "";
}
- std::string gen_fetch_function_end(int field_num, const std::vector<var_description> &vars,
- int parser_type) const override {
+ std::string gen_fetch_function_end(bool has_parent, int field_count, const std::vector<var_description> &vars,
+ int parser_type) const final {
return "";
}
std::string gen_fetch_function_result_begin(const std::string &parser_name, const std::string &class_name,
- const tl_tree *result) const override {
+ const tl_tree *result) const final {
return "";
}
- std::string gen_fetch_function_result_end(void) const override {
+ std::string gen_fetch_function_result_end(void) const final {
return "";
}
std::string gen_fetch_function_result_any_begin(const std::string &parser_name, const std::string &class_name,
- bool is_proxy) const override {
+ bool is_proxy) const final {
return "";
}
- std::string gen_fetch_function_result_any_end(bool is_proxy) const override {
+ std::string gen_fetch_function_result_any_end(bool is_proxy) const final {
return "";
}
- std::string gen_fetch_switch_begin(void) const override {
+ std::string gen_fetch_switch_begin(void) const final {
return "";
}
- std::string gen_fetch_switch_case(const tl_combinator *t, int arity) const override {
+ std::string gen_fetch_switch_case(const tl_combinator *t, int arity) const final {
return "";
}
- std::string gen_fetch_switch_end(void) const override {
+ std::string gen_fetch_switch_end(void) const final {
return "";
}
std::string gen_additional_proxy_function_begin(const std::string &function_name, const tl_type *type,
- const std::string &name, int arity, bool is_function) const override {
+ const std::string &name, int arity, bool is_function) const final {
std::stringstream ss;
if (is_header_ && function_name == "ToUnmanaged") {
ss << "};\n";
@@ -508,41 +518,39 @@ class TlWriterDotNet : public TL_writer {
auto native_class_name = gen_native_class_name(type->name);
auto class_name = gen_class_name(type->name);
if (function_name == "ToUnmanaged") {
- ss << "td::td_api::object_ptr<td::td_api::" << native_class_name << "> ToUnmanaged(" << class_name
- << "^ from)";
+ ss << "td::td_api::object_ptr<td::td_api::" << native_class_name << "> ToUnmanaged(" << class_name << "^ from)";
if (is_header_) {
ss << ";\n";
return ss.str();
}
- ss << "{\n"
+ ss << " {\n"
<< " if (!from) {\n"
<< " return nullptr;\n"
<< " }\n"
- << " return td::td_api::move_object_as<td::td_api::" << native_class_name << ">(from->ToUnmanaged()->get_object_ptr());\n}\n";
+ << " return td::td_api::move_object_as<td::td_api::" << native_class_name
+ << ">(from->ToUnmanaged()->get_object_ptr());\n}\n";
} else {
ss << class_name << "^ FromUnmanaged(td::td_api::" << native_class_name << " &from)";
if (is_header_) {
ss << ";\n";
return ss.str();
}
- ss << "{\n";
- ss << " CallFromUnmanaged<" << class_name << "^> res;\n";
- ss << " downcast_call(from, res);\n";
- ss << " return CallFromUnmanagedRes<" << class_name << "^>::res;\n";
+ ss << " {\n";
+ ss << " return DoFromUnmanaged<" << class_name << "^>(from);\n";
ss << "}\n";
}
return ss.str();
}
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl_type *type,
- const std::string &class_name, int arity) const override {
+ const std::string &class_name, int arity) const final {
return "";
}
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl_type *type,
- const tl_combinator *t, int arity, bool is_function) const override {
+ const tl_combinator *t, int arity, bool is_function) const final {
return "";
}
std::string gen_additional_proxy_function_end(const std::string &function_name, const tl_type *type,
- bool is_function) const override {
+ bool is_function) const final {
return "";
}
};
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_h.cpp b/protocols/Telegram/tdlib/td/td/generate/tl_writer_h.cpp
index 49eaf4982a..a7209abc03 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_h.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_h.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -50,13 +50,27 @@ std::string TD_TL_writer_h::gen_output_begin() const {
"#include \"td/tl/TlObject.h\"\n\n" +
ext_include_str +
"#include <cstdint>\n"
- "#include <memory>\n"
"#include <utility>\n"
"#include <vector>\n\n"
"namespace td {\n" +
ext_forward_declaration + "namespace " + tl_name +
" {\n\n"
+ "using int32 = std::int32_t;\n"
+ "using int53 = std::int64_t;\n"
+ "using int64 = std::int64_t;\n\n"
+
+ "using string = " +
+ string_type +
+ ";\n\n"
+
+ "using bytes = " +
+ bytes_type +
+ ";\n\n"
+
+ "template <class Type>\n"
+ "using array = std::vector<Type>;\n\n"
+
"using BaseObject = ::td::TlObject;\n\n"
"template <class Type>\n"
@@ -80,6 +94,20 @@ std::string TD_TL_writer_h::gen_output_begin() const {
" }\n"
"\n"
" return to_string(*value);\n"
+ "}\n\n"
+
+ "template <class T>\n"
+ "std::string to_string(const std::vector<object_ptr<T>> &values) {\n"
+ " std::string result = \"{\\n\";\n"
+ " for (const auto &value : values) {\n"
+ " if (value == nullptr) {\n"
+ " result += \"null\\n\";\n"
+ " } else {\n"
+ " result += to_string(*value);\n"
+ " }\n"
+ " }\n"
+ " result += \"}\\n\";\n"
+ " return result;\n"
"}\n\n";
}
@@ -136,23 +164,44 @@ std::string TD_TL_writer_h::gen_function_vars(const tl::tl_combinator *t,
return res;
}
-std::string TD_TL_writer_h::gen_flags_definitions(const tl::tl_combinator *t) const {
+bool TD_TL_writer_h::need_arg_mask(const tl::arg &a, bool can_be_stored) const {
+ if (a.exist_var_num == -1) {
+ return false;
+ }
+
+ if (can_be_stored) {
+ return true;
+ }
+
+ if (a.type->get_type() != tl::NODE_TYPE_TYPE) {
+ return true;
+ }
+ const tl::tl_tree_type *tree_type = static_cast<tl::tl_tree_type *>(a.type);
+ const std::string &name = tree_type->type->name;
+
+ if (!is_built_in_simple_type(name) || name == "True") {
+ return false;
+ }
+ return true;
+}
+
+std::string TD_TL_writer_h::gen_flags_definitions(const tl::tl_combinator *t, bool can_be_stored) const {
std::vector<std::pair<std::string, std::int32_t>> flags;
for (std::size_t i = 0; i < t->args.size(); i++) {
const tl::arg &a = t->args[i];
- if (a.exist_var_num != -1) {
+ if (need_arg_mask(a, can_be_stored)) {
auto name = a.name;
for (auto &c : name) {
c = to_upper(c);
}
- flags.push_back(std::make_pair(name, a.exist_var_bit));
+ flags.emplace_back(name, a.exist_var_bit);
}
}
std::string res;
if (!flags.empty()) {
- res += " enum Flags : std::int32_t {";
+ res += " enum Flags : std::int32_t { ";
bool first = true;
for (auto &p : flags) {
if (first) {
@@ -162,7 +211,7 @@ std::string TD_TL_writer_h::gen_flags_definitions(const tl::tl_combinator *t) co
}
res += p.first + "_MASK = " + int_to_string(1 << p.second);
}
- res += "};\n";
+ res += " };\n";
}
return res;
}
@@ -206,7 +255,7 @@ std::string TD_TL_writer_h::gen_forward_class_declaration(const std::string &cla
}
std::string TD_TL_writer_h::gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const {
+ bool is_proxy, const tl::tl_tree *result) const {
return "class " + class_name + (!is_proxy ? " final " : "") + ": public " + base_class_name +
" {\n"
" public:\n";
@@ -254,29 +303,32 @@ std::string TD_TL_writer_h::gen_function_result_type(const tl::tl_tree *result)
}
std::string TD_TL_writer_h::gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
- int arity, std::vector<tl::var_description> &vars,
- int parser_type) const {
- std::string fetched_type = "object_ptr<" + class_name + "> ";
+ const std::string &parent_class_name, int arity, int field_count,
+ std::vector<tl::var_description> &vars, int parser_type) const {
+ std::string returned_type = "object_ptr<" + parent_class_name + "> ";
if (parser_type == 0) {
- return "\n"
- " static " +
- fetched_type + "fetch(" + parser_name +
- " &p) {\n"
- " return make_tl_object<" +
- class_name +
- ">(p);\n"
- " }\n\n" +
- " explicit " + class_name + "(" + parser_name + " &p);\n";
+ std::string result =
+ "\n"
+ " static " +
+ returned_type + "fetch(" + parser_name + " &p);\n";
+ if (field_count != 0) {
+ result +=
+ "\n"
+ " explicit " +
+ class_name + "(" + parser_name + " &p);\n";
+ }
+ return result;
}
assert(arity == 0);
return "\n"
" static " +
- fetched_type + "fetch(" + parser_name + " &p);\n";
+ returned_type + "fetch(" + parser_name + " &p);\n";
}
-std::string TD_TL_writer_h::gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
+std::string TD_TL_writer_h::gen_fetch_function_end(bool has_parent, int field_count,
+ const std::vector<tl::var_description> &vars,
int parser_type) const {
return "";
}
@@ -331,11 +383,11 @@ std::string TD_TL_writer_h::gen_fetch_switch_end() const {
return "";
}
-std::string TD_TL_writer_h::gen_constructor_begin(int fields_num, const std::string &class_name,
+std::string TD_TL_writer_h::gen_constructor_begin(int field_count, const std::string &class_name,
bool is_default) const {
return "\n"
" " +
- std::string(fields_num == 1 ? "explicit " : "") + class_name + "(";
+ std::string(field_count == 1 ? "explicit " : "") + class_name + "(";
}
std::string TD_TL_writer_h::gen_constructor_field_init(int field_num, const std::string &class_name, const tl::arg &a,
@@ -343,7 +395,7 @@ std::string TD_TL_writer_h::gen_constructor_field_init(int field_num, const std:
return "";
}
-std::string TD_TL_writer_h::gen_constructor_end(const tl::tl_combinator *t, int fields_num, bool is_default) const {
+std::string TD_TL_writer_h::gen_constructor_end(const tl::tl_combinator *t, int field_count, bool is_default) const {
return ");\n";
}
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_h.h b/protocols/Telegram/tdlib/td/td/generate/tl_writer_h.h
index e763ea47e3..2f2ced49e1 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_h.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_h.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -20,6 +20,8 @@ class TD_TL_writer_h : public TD_TL_writer {
static std::string forward_declaration(std::string type);
+ bool need_arg_mask(const tl::arg &a, bool can_be_stored) const;
+
public:
TD_TL_writer_h(const std::string &tl_name, const std::string &string_type, const std::string &bytes_type,
const std::vector<std::string> &ext_include)
@@ -31,8 +33,8 @@ class TD_TL_writer_h : public TD_TL_writer {
std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const override;
- std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const override;
+ std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy,
+ const tl::tl_tree *result) const override;
std::string gen_class_end() const override;
std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const override;
@@ -40,7 +42,7 @@ class TD_TL_writer_h : public TD_TL_writer {
std::string gen_field_definition(const std::string &class_name, const std::string &type_name,
const std::string &field_name) const override;
- std::string gen_flags_definitions(const tl::tl_combinator *t) const override;
+ std::string gen_flags_definitions(const tl::tl_combinator *t, bool can_be_stored) const override;
std::string gen_vars(const tl::tl_combinator *t, const tl::tl_tree_type *result_type,
std::vector<tl::var_description> &vars) const override;
std::string gen_function_vars(const tl::tl_combinator *t, std::vector<tl::var_description> &vars) const override;
@@ -62,9 +64,10 @@ class TD_TL_writer_h : public TD_TL_writer {
std::string gen_function_result_type(const tl::tl_tree *result) const override;
- std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name, int arity,
+ std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
+ const std::string &parent_class_name, int arity, int field_count,
std::vector<tl::var_description> &vars, int parser_type) const override;
- std::string gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
+ std::string gen_fetch_function_end(bool has_parent, int field_count, const std::vector<tl::var_description> &vars,
int parser_type) const override;
std::string gen_fetch_function_result_begin(const std::string &parser_name, const std::string &class_name,
@@ -82,10 +85,10 @@ class TD_TL_writer_h : public TD_TL_writer {
std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const override;
std::string gen_fetch_switch_end() const override;
- std::string gen_constructor_begin(int fields_num, const std::string &class_name, bool is_default) const override;
+ std::string gen_constructor_begin(int field_count, const std::string &class_name, bool is_default) const override;
std::string gen_constructor_field_init(int field_num, const std::string &class_name, const tl::arg &a,
bool is_default) const override;
- std::string gen_constructor_end(const tl::tl_combinator *t, int fields_num, bool is_default) const override;
+ std::string gen_constructor_end(const tl::tl_combinator *t, int field_count, bool is_default) const override;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.cpp b/protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.cpp
index 56e8e31ea0..56dc0e842f 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -117,7 +117,7 @@ std::string TD_TL_writer_hpp::gen_forward_class_declaration(const std::string &c
}
std::string TD_TL_writer_hpp::gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const {
+ bool is_proxy, const tl::tl_tree *result) const {
return "";
}
@@ -138,12 +138,13 @@ std::string TD_TL_writer_hpp::gen_function_result_type(const tl::tl_tree *result
}
std::string TD_TL_writer_hpp::gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
- int arity, std::vector<tl::var_description> &vars,
- int parser_type) const {
+ const std::string &parent_class_name, int arity, int field_count,
+ std::vector<tl::var_description> &vars, int parser_type) const {
return "";
}
-std::string TD_TL_writer_hpp::gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
+std::string TD_TL_writer_hpp::gen_fetch_function_end(bool has_parent, int field_count,
+ const std::vector<tl::var_description> &vars,
int parser_type) const {
return "";
}
@@ -246,7 +247,7 @@ std::string TD_TL_writer_hpp::gen_additional_proxy_function_end(const std::strin
"}\n\n";
}
-std::string TD_TL_writer_hpp::gen_constructor_begin(int fields_num, const std::string &class_name,
+std::string TD_TL_writer_hpp::gen_constructor_begin(int field_count, const std::string &class_name,
bool is_default) const {
return "";
}
@@ -261,7 +262,7 @@ std::string TD_TL_writer_hpp::gen_constructor_field_init(int field_num, const st
return "";
}
-std::string TD_TL_writer_hpp::gen_constructor_end(const tl::tl_combinator *t, int fields_num, bool is_default) const {
+std::string TD_TL_writer_hpp::gen_constructor_end(const tl::tl_combinator *t, int field_count, bool is_default) const {
return "";
}
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.h b/protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.h
index 119fa87a5c..1510b11cde 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_hpp.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,94 +14,94 @@
namespace td {
-class TD_TL_writer_hpp : public TD_TL_writer {
+class TD_TL_writer_hpp final : public TD_TL_writer {
public:
TD_TL_writer_hpp(const std::string &tl_name, const std::string &string_type, const std::string &bytes_type)
: TD_TL_writer(tl_name, string_type, bytes_type) {
}
- bool is_documentation_generated() const override;
+ bool is_documentation_generated() const final;
- int get_additional_function_type(const std::string &additional_function_name) const override;
- std::vector<std::string> get_additional_functions() const override;
+ int get_additional_function_type(const std::string &additional_function_name) const final;
+ std::vector<std::string> get_additional_functions() const final;
- std::string gen_base_type_class_name(int arity) const override;
- std::string gen_base_tl_class_name() const override;
+ std::string gen_base_type_class_name(int arity) const final;
+ std::string gen_base_tl_class_name() const final;
- std::string gen_output_begin() const override;
- std::string gen_output_end() const override;
+ std::string gen_output_begin() const final;
+ std::string gen_output_end() const final;
- std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const override;
+ std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const final;
- std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const override;
- std::string gen_class_end() const override;
+ std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy,
+ const tl::tl_tree *result) const final;
+ std::string gen_class_end() const final;
- std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const override;
+ std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const final;
std::string gen_field_definition(const std::string &class_name, const std::string &type_name,
- const std::string &field_name) const override;
+ const std::string &field_name) const final;
std::string gen_vars(const tl::tl_combinator *t, const tl::tl_tree_type *result_type,
- std::vector<tl::var_description> &vars) const override;
- std::string gen_function_vars(const tl::tl_combinator *t, std::vector<tl::var_description> &vars) const override;
+ std::vector<tl::var_description> &vars) const final;
+ std::string gen_function_vars(const tl::tl_combinator *t, std::vector<tl::var_description> &vars) const final;
std::string gen_uni(const tl::tl_tree_type *result_type, std::vector<tl::var_description> &vars,
- bool check_negative) const override;
- std::string gen_constructor_id_store(std::int32_t id, int storer_type) const override;
+ bool check_negative) const final;
+ std::string gen_constructor_id_store(std::int32_t id, int storer_type) const final;
std::string gen_field_fetch(int field_num, const tl::arg &a, std::vector<tl::var_description> &vars, bool flat,
- int parser_type) const override;
+ int parser_type) const final;
std::string gen_field_store(const tl::arg &a, std::vector<tl::var_description> &vars, bool flat,
- int storer_type) const override;
+ int storer_type) const final;
std::string gen_type_fetch(const std::string &field_name, const tl::tl_tree_type *tree_type,
- const std::vector<tl::var_description> &vars, int parser_type) const override;
+ const std::vector<tl::var_description> &vars, int parser_type) const final;
std::string gen_type_store(const std::string &field_name, const tl::tl_tree_type *tree_type,
- const std::vector<tl::var_description> &vars, int storer_type) const override;
- std::string gen_var_type_fetch(const tl::arg &a) const override;
+ const std::vector<tl::var_description> &vars, int storer_type) const final;
+ std::string gen_var_type_fetch(const tl::arg &a) const final;
- std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const override;
+ std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const final;
- std::string gen_function_result_type(const tl::tl_tree *result) const override;
+ std::string gen_function_result_type(const tl::tl_tree *result) const final;
- std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name, int arity,
- std::vector<tl::var_description> &vars, int parser_type) const override;
- std::string gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
- int parser_type) const override;
+ std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
+ const std::string &parent_class_name, int arity, int field_count,
+ std::vector<tl::var_description> &vars, int parser_type) const final;
+ std::string gen_fetch_function_end(bool has_parent, int field_count, const std::vector<tl::var_description> &vars,
+ int parser_type) const final;
std::string gen_fetch_function_result_begin(const std::string &parser_name, const std::string &class_name,
- const tl::tl_tree *result) const override;
- std::string gen_fetch_function_result_end() const override;
+ const tl::tl_tree *result) const final;
+ std::string gen_fetch_function_result_end() const final;
std::string gen_fetch_function_result_any_begin(const std::string &parser_name, const std::string &class_name,
- bool is_proxy) const override;
- std::string gen_fetch_function_result_any_end(bool is_proxy) const override;
+ bool is_proxy) const final;
+ std::string gen_fetch_function_result_any_end(bool is_proxy) const final;
std::string gen_store_function_begin(const std::string &storer_name, const std::string &class_name, int arity,
- std::vector<tl::var_description> &vars, int storer_type) const override;
- std::string gen_store_function_end(const std::vector<tl::var_description> &vars, int storer_type) const override;
+ std::vector<tl::var_description> &vars, int storer_type) const final;
+ std::string gen_store_function_end(const std::vector<tl::var_description> &vars, int storer_type) const final;
- std::string gen_fetch_switch_begin() const override;
- std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const override;
- std::string gen_fetch_switch_end() const override;
+ std::string gen_fetch_switch_begin() const final;
+ std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const final;
+ std::string gen_fetch_switch_end() const final;
- std::string gen_constructor_begin(int fields_num, const std::string &class_name, bool is_default) const override;
+ std::string gen_constructor_begin(int field_count, const std::string &class_name, bool is_default) const final;
std::string gen_constructor_parameter(int field_num, const std::string &class_name, const tl::arg &a,
- bool is_default) const override;
+ bool is_default) const final;
std::string gen_constructor_field_init(int field_num, const std::string &class_name, const tl::arg &a,
- bool is_default) const override;
- std::string gen_constructor_end(const tl::tl_combinator *t, int fields_num, bool is_default) const override;
+ bool is_default) const final;
+ std::string gen_constructor_end(const tl::tl_combinator *t, int field_count, bool is_default) const final;
std::string gen_additional_function(const std::string &function_name, const tl::tl_combinator *t,
- bool is_function) const override;
+ bool is_function) const final;
std::string gen_additional_proxy_function_begin(const std::string &function_name, const tl::tl_type *type,
const std::string &class_name, int arity,
- bool is_function) const override;
+ bool is_function) const final;
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type,
- const std::string &class_name, int arity) const override;
+ const std::string &class_name, int arity) const final;
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type,
- const tl::tl_combinator *t, int arity,
- bool is_function) const override;
+ const tl::tl_combinator *t, int arity, bool is_function) const final;
std::string gen_additional_proxy_function_end(const std::string &function_name, const tl::tl_type *type,
- bool is_function) const override;
+ bool is_function) const final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_java.cpp b/protocols/Telegram/tdlib/td/td/generate/tl_writer_java.cpp
index e243384eae..7c61de3189 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_java.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_java.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -194,7 +194,19 @@ std::string TD_TL_writer_java::gen_output_begin() const {
return "package " + package_name +
";\n\n"
"public class " +
- tl_name + " {\n";
+ tl_name +
+ " {\n"
+ " static {\n"
+ " try {\n"
+ " System.loadLibrary(\"tdjni\");\n"
+ " } catch (UnsatisfiedLinkError e) {\n"
+ " e.printStackTrace();\n" +
+ " }\n"
+ " }\n\n"
+ " private " +
+ tl_name +
+ "() {\n"
+ " }\n\n";
}
std::string TD_TL_writer_java::gen_output_end() const {
@@ -206,14 +218,31 @@ std::string TD_TL_writer_java::gen_forward_class_declaration(const std::string &
}
std::string TD_TL_writer_java::gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const {
+ bool is_proxy, const tl::tl_tree *result_tl) const {
std::string full_class_name = "static class " + class_name;
+ if (class_name == "Function") {
+ full_class_name += "<R extends " + gen_base_tl_class_name() + ">";
+ }
if (class_name != gen_base_tl_class_name()) {
full_class_name += " extends " + base_class_name;
}
+ if (result_tl != nullptr && base_class_name == "Function") {
+ assert(result_tl->get_type() == tl::NODE_TYPE_TYPE);
+ const tl::tl_tree_type *result_type = static_cast<const tl::tl_tree_type *>(result_tl);
+ std::string fetched_type = gen_type_name(result_type);
+
+ if (!fetched_type.empty() && fetched_type[fetched_type.size() - 1] == ' ') {
+ fetched_type.pop_back();
+ }
+
+ full_class_name += "<" + fetched_type + ">";
+ }
std::string result = " public " + std::string(is_proxy ? "abstract " : "") + full_class_name + " {\n";
+ if (is_proxy) {
+ result += " public " + class_name + "() {\n }\n";
+ }
if (class_name == gen_base_tl_class_name() || class_name == gen_base_function_class_name()) {
- result += " public native String toString();\n";
+ result += "\n public native String toString();\n";
}
return result;
@@ -337,9 +366,7 @@ std::string TD_TL_writer_java::gen_get_id(const std::string &class_name, std::in
";\n\n"
" @Override\n"
" public int getConstructor() {\n"
- " return " +
- int_to_string(id) +
- ";\n"
+ " return CONSTRUCTOR;\n"
" }\n";
}
@@ -348,12 +375,14 @@ std::string TD_TL_writer_java::gen_function_result_type(const tl::tl_tree *resul
}
std::string TD_TL_writer_java::gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
- int arity, std::vector<tl::var_description> &vars,
+ const std::string &parent_class_name, int arity,
+ int field_count, std::vector<tl::var_description> &vars,
int parser_type) const {
return "";
}
-std::string TD_TL_writer_java::gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
+std::string TD_TL_writer_java::gen_fetch_function_end(bool has_parent, int field_count,
+ const std::vector<tl::var_description> &vars,
int parser_type) const {
return "";
}
@@ -403,7 +432,7 @@ std::string TD_TL_writer_java::gen_fetch_switch_end() const {
return "";
}
-std::string TD_TL_writer_java::gen_constructor_begin(int fields_num, const std::string &class_name,
+std::string TD_TL_writer_java::gen_constructor_begin(int field_count, const std::string &class_name,
bool is_default) const {
return "\n"
" public " +
@@ -443,8 +472,8 @@ std::string TD_TL_writer_java::gen_constructor_field_init(int field_num, const s
gen_field_name(a.name) + ";\n";
}
-std::string TD_TL_writer_java::gen_constructor_end(const tl::tl_combinator *t, int fields_num, bool is_default) const {
- if (fields_num == 0) {
+std::string TD_TL_writer_java::gen_constructor_end(const tl::tl_combinator *t, int field_count, bool is_default) const {
+ if (field_count == 0) {
return ") {\n"
" }\n";
}
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_java.h b/protocols/Telegram/tdlib/td/td/generate/tl_writer_java.h
index cb8a161f02..f85e782290 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_java.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_java.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,7 +14,7 @@
namespace td {
-class TD_TL_writer_java : public tl::TL_writer {
+class TD_TL_writer_java final : public tl::TL_writer {
static const int MAX_ARITY = 0;
static const std::string base_type_class_names[MAX_ARITY + 1];
@@ -28,91 +28,92 @@ class TD_TL_writer_java : public tl::TL_writer {
: TL_writer(tl_name), package_name(package_name) {
}
- int get_max_arity() const override;
+ int get_max_arity() const final;
- bool is_built_in_simple_type(const std::string &name) const override;
- bool is_built_in_complex_type(const std::string &name) const override;
- bool is_type_bare(const tl::tl_type *t) const override;
- bool is_combinator_supported(const tl::tl_combinator *constructor) const override;
+ bool is_built_in_simple_type(const std::string &name) const final;
+ bool is_built_in_complex_type(const std::string &name) const final;
+ bool is_type_bare(const tl::tl_type *t) const final;
+ bool is_combinator_supported(const tl::tl_combinator *constructor) const final;
- int get_parser_type(const tl::tl_combinator *t, const std::string &parser_name) const override;
- int get_storer_type(const tl::tl_combinator *t, const std::string &storer_name) const override;
- std::vector<std::string> get_parsers() const override;
- std::vector<std::string> get_storers() const override;
+ int get_parser_type(const tl::tl_combinator *t, const std::string &parser_name) const final;
+ int get_storer_type(const tl::tl_combinator *t, const std::string &storer_name) const final;
+ std::vector<std::string> get_parsers() const final;
+ std::vector<std::string> get_storers() const final;
- std::string gen_base_tl_class_name() const override;
- std::string gen_base_type_class_name(int arity) const override;
- std::string gen_base_function_class_name() const override;
- std::string gen_class_name(std::string name) const override;
- std::string gen_field_name(std::string name) const override;
- std::string gen_var_name(const tl::var_description &desc) const override;
- std::string gen_parameter_name(int index) const override;
- std::string gen_type_name(const tl::tl_tree_type *tree_type) const override;
- std::string gen_array_type_name(const tl::tl_tree_array *arr, const std::string &field_name) const override;
- std::string gen_var_type_name() const override;
+ std::string gen_base_tl_class_name() const final;
+ std::string gen_base_type_class_name(int arity) const final;
+ std::string gen_base_function_class_name() const final;
+ std::string gen_class_name(std::string name) const final;
+ std::string gen_field_name(std::string name) const final;
+ std::string gen_var_name(const tl::var_description &desc) const final;
+ std::string gen_parameter_name(int index) const final;
+ std::string gen_type_name(const tl::tl_tree_type *tree_type) const final;
+ std::string gen_array_type_name(const tl::tl_tree_array *arr, const std::string &field_name) const final;
+ std::string gen_var_type_name() const final;
- std::string gen_int_const(const tl::tl_tree *tree_c, const std::vector<tl::var_description> &vars) const override;
+ std::string gen_int_const(const tl::tl_tree *tree_c, const std::vector<tl::var_description> &vars) const final;
- std::string gen_output_begin() const override;
- std::string gen_output_end() const override;
+ std::string gen_output_begin() const final;
+ std::string gen_output_end() const final;
- std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const override;
+ std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const final;
- std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const override;
- std::string gen_class_end() const override;
+ std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy,
+ const tl::tl_tree *result) const final;
+ std::string gen_class_end() const final;
- std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const override;
+ std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const final;
std::string gen_field_definition(const std::string &class_name, const std::string &type_name,
- const std::string &field_name) const override;
+ const std::string &field_name) const final;
std::string gen_vars(const tl::tl_combinator *t, const tl::tl_tree_type *result_type,
- std::vector<tl::var_description> &vars) const override;
- std::string gen_function_vars(const tl::tl_combinator *t, std::vector<tl::var_description> &vars) const override;
+ std::vector<tl::var_description> &vars) const final;
+ std::string gen_function_vars(const tl::tl_combinator *t, std::vector<tl::var_description> &vars) const final;
std::string gen_uni(const tl::tl_tree_type *result_type, std::vector<tl::var_description> &vars,
- bool check_negative) const override;
- std::string gen_constructor_id_store(std::int32_t id, int storer_type) const override;
+ bool check_negative) const final;
+ std::string gen_constructor_id_store(std::int32_t id, int storer_type) const final;
std::string gen_field_fetch(int field_num, const tl::arg &a, std::vector<tl::var_description> &vars, bool flat,
- int parser_type) const override;
+ int parser_type) const final;
std::string gen_field_store(const tl::arg &a, std::vector<tl::var_description> &vars, bool flat,
- int storer_type) const override;
+ int storer_type) const final;
std::string gen_type_fetch(const std::string &field_name, const tl::tl_tree_type *tree_type,
- const std::vector<tl::var_description> &vars, int parser_type) const override;
+ const std::vector<tl::var_description> &vars, int parser_type) const final;
std::string gen_type_store(const std::string &field_name, const tl::tl_tree_type *tree_type,
- const std::vector<tl::var_description> &vars, int storer_type) const override;
- std::string gen_var_type_fetch(const tl::arg &a) const override;
+ const std::vector<tl::var_description> &vars, int storer_type) const final;
+ std::string gen_var_type_fetch(const tl::arg &a) const final;
- std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const override;
+ std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const final;
- std::string gen_function_result_type(const tl::tl_tree *result) const override;
+ std::string gen_function_result_type(const tl::tl_tree *result) const final;
- std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name, int arity,
- std::vector<tl::var_description> &vars, int parser_type) const override;
- std::string gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
- int parser_type) const override;
+ std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
+ const std::string &parent_class_name, int arity, int field_count,
+ std::vector<tl::var_description> &vars, int parser_type) const final;
+ std::string gen_fetch_function_end(bool has_parent, int field_count, const std::vector<tl::var_description> &vars,
+ int parser_type) const final;
std::string gen_fetch_function_result_begin(const std::string &parser_name, const std::string &class_name,
- const tl::tl_tree *result) const override;
- std::string gen_fetch_function_result_end() const override;
+ const tl::tl_tree *result) const final;
+ std::string gen_fetch_function_result_end() const final;
std::string gen_fetch_function_result_any_begin(const std::string &parser_name, const std::string &class_name,
- bool is_proxy) const override;
- std::string gen_fetch_function_result_any_end(bool is_proxy) const override;
+ bool is_proxy) const final;
+ std::string gen_fetch_function_result_any_end(bool is_proxy) const final;
std::string gen_store_function_begin(const std::string &storer_name, const std::string &class_name, int arity,
- std::vector<tl::var_description> &vars, int storer_type) const override;
- std::string gen_store_function_end(const std::vector<tl::var_description> &vars, int storer_type) const override;
+ std::vector<tl::var_description> &vars, int storer_type) const final;
+ std::string gen_store_function_end(const std::vector<tl::var_description> &vars, int storer_type) const final;
- std::string gen_fetch_switch_begin() const override;
- std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const override;
- std::string gen_fetch_switch_end() const override;
+ std::string gen_fetch_switch_begin() const final;
+ std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const final;
+ std::string gen_fetch_switch_end() const final;
- std::string gen_constructor_begin(int fields_num, const std::string &class_name, bool is_default) const override;
+ std::string gen_constructor_begin(int field_count, const std::string &class_name, bool is_default) const final;
std::string gen_constructor_parameter(int field_num, const std::string &class_name, const tl::arg &a,
- bool is_default) const override;
+ bool is_default) const final;
std::string gen_constructor_field_init(int field_num, const std::string &class_name, const tl::arg &a,
- bool is_default) const override;
- std::string gen_constructor_end(const tl::tl_combinator *t, int fields_num, bool is_default) const override;
+ bool is_default) const final;
+ std::string gen_constructor_end(const tl::tl_combinator *t, int field_count, bool is_default) const final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.cpp b/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.cpp
index b8c1c9e324..18e6ace218 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -57,7 +57,7 @@ std::string TD_TL_writer_jni_cpp::gen_base_tl_class_name() const {
}
std::string TD_TL_writer_jni_cpp::gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const {
+ bool is_proxy, const tl::tl_tree *result) const {
return "\n"
"jclass " +
class_name + "::Class;\n";
@@ -83,10 +83,10 @@ std::string TD_TL_writer_jni_cpp::gen_vector_fetch(std::string field_name, const
std::string fetch_object = "jni::fetch_object(env, p, " + field_name + "fieldID)";
std::string array_type;
- if (vector_type == "std::int32_t") {
+ if (vector_type == "int32") {
array_type = "jintArray";
}
- if (vector_type == "std::int64_t") {
+ if (vector_type == "int53" || vector_type == "int64") {
array_type = "jlongArray";
}
if (vector_type == "double") {
@@ -94,27 +94,26 @@ std::string TD_TL_writer_jni_cpp::gen_vector_fetch(std::string field_name, const
}
if (!array_type.empty()) {
- return "jni::fetch_vector(env, (" + array_type + ")" + fetch_object + ");";
+ return "jni::fetch_vector(env, (" + array_type + ")" + fetch_object + ")";
}
std::string template_type;
- if (vector_type == string_type) {
- template_type = "std::string";
- } else if (vector_type.compare(0, 11, "std::vector") == 0) {
+ if (vector_type == "string") {
+ template_type = "string";
+ } else if (vector_type.compare(0, 5, "array") == 0) {
const tl::tl_tree_type *child = static_cast<const tl::tl_tree_type *>(t->children[0]);
template_type = gen_type_name(child);
if (template_type.compare(0, 10, "object_ptr") == 0) {
template_type = gen_main_class_name(child->type);
}
- template_type = "std::vector<" + template_type + ">";
- } else if (vector_type == bytes_type) {
- std::fprintf(stderr, "Vector of Bytes is not supported\n");
- assert(false);
+ template_type = "array<" + template_type + ">";
+ } else if (vector_type == "bytes") {
+ template_type = "jbyteArray";
} else {
assert(vector_type.compare(0, 10, "object_ptr") == 0);
template_type = gen_main_class_name(t->type);
}
- return "jni::FetchVector<" + template_type + ">::fetch(env, (jobjectArray)" + fetch_object + ");";
+ return "jni::FetchVector<" + template_type + ">::fetch(env, (jobjectArray)" + fetch_object + ")";
}
std::string TD_TL_writer_jni_cpp::gen_type_fetch(const std::string &field_name, const tl::tl_tree_type *tree_type,
@@ -127,7 +126,7 @@ std::string TD_TL_writer_jni_cpp::gen_type_fetch(const std::string &field_name,
if (!(tree_type->flags & tl::FLAG_BARE)) {
if (is_type_bare(t)) {
- if (field_name != "") {
+ if (!field_name.empty()) {
std::fprintf(stderr, "Do not use non-bare fields with bare type %s\n", name.c_str());
// assert(false);
}
@@ -175,11 +174,11 @@ std::string TD_TL_writer_jni_cpp::gen_type_fetch(const std::string &field_name,
const tl::tl_tree_type *child = static_cast<const tl::tl_tree_type *>(tree_type->children[0]);
res = gen_vector_fetch(field_name, child, vars, parser_type);
} else {
- if (field_name == "") {
+ if (field_name.empty()) {
return gen_main_class_name(tree_type->type) + "::fetch(env, p)";
}
res = "jni::fetch_tl_object<" + gen_main_class_name(tree_type->type) + ">(env, jni::fetch_object(env, p, " +
- field_name + "fieldID));";
+ field_name + "fieldID))";
}
return res_begin + res;
}
@@ -248,8 +247,8 @@ std::string TD_TL_writer_jni_cpp::gen_vector_store(const std::string &field_name
if (vector_type == "bool") {
assert(false); // TODO
}
- if (vector_type == "std::int32_t" || vector_type == "std::int64_t" || vector_type == "double" ||
- vector_type == string_type || vector_type.compare(0, 11, "std::vector") == 0 ||
+ if (vector_type == "int32" || vector_type == "int53" || vector_type == "int64" || vector_type == "double" ||
+ vector_type == "string" || vector_type.compare(0, 5, "array") == 0 ||
vector_type.compare(0, 10, "object_ptr") == 0) {
return "{ "
"auto arr_tmp_ = jni::store_vector(env, " +
@@ -262,7 +261,7 @@ std::string TD_TL_writer_jni_cpp::gen_vector_store(const std::string &field_name
"env->DeleteLocalRef(arr_tmp_); "
"} }";
}
- if (vector_type == bytes_type) {
+ if (vector_type == "bytes") {
std::fprintf(stderr, "Vector of Bytes is not supported\n");
assert(false);
}
@@ -323,8 +322,8 @@ std::string TD_TL_writer_jni_cpp::gen_type_store(const std::string &field_name,
res = gen_vector_store(field_name, child, vars, storer_type);
} else {
if (storer_type == 1) {
- res = "if (" + field_name + " == nullptr) { s.store_field(\"" + get_pretty_field_name(field_name) +
- "\", \"null\"); } else { " + field_name + "->store(s, \"" + get_pretty_field_name(field_name) + "\"); }";
+ res = "s.store_object_field(\"" + get_pretty_field_name(field_name) + "\", static_cast<const BaseObject *>(" +
+ field_name + ".get()));";
} else {
res = "if (" + field_name + " != nullptr) { jobject next; " + field_name +
"->store(env, next); if (next) { env->SetObjectField(s, " + field_name +
@@ -378,26 +377,29 @@ std::string TD_TL_writer_jni_cpp::gen_get_id(const std::string &class_name, std:
}
std::string TD_TL_writer_jni_cpp::gen_fetch_function_begin(const std::string &parser_name,
- const std::string &class_name, int arity,
- std::vector<tl::var_description> &vars,
+ const std::string &class_name,
+ const std::string &parent_class_name, int arity,
+ int field_count, std::vector<tl::var_description> &vars,
int parser_type) const {
for (std::size_t i = 0; i < vars.size(); i++) {
assert(vars[i].is_stored == false);
}
std::string fetched_type = "object_ptr<" + class_name + "> ";
+ std::string returned_type = "object_ptr<" + parent_class_name + "> ";
assert(arity == 0);
assert(parser_type != 0);
- return "\n" + fetched_type + class_name + "::fetch(" + parser_name + " &p) {\n" +
+ return "\n" + returned_type + class_name + "::fetch(" + parser_name + " &p) {\n" +
(parser_type == -1 ? ""
: " if (p == nullptr) return nullptr;\n"
" " +
fetched_type + "res = make_object<" + class_name + ">();\n");
}
-std::string TD_TL_writer_jni_cpp::gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
+std::string TD_TL_writer_jni_cpp::gen_fetch_function_end(bool has_parent, int field_count,
+ const std::vector<tl::var_description> &vars,
int parser_type) const {
for (std::size_t i = 0; i < vars.size(); i++) {
assert(vars[i].is_stored);
@@ -409,7 +411,8 @@ std::string TD_TL_writer_jni_cpp::gen_fetch_function_end(int field_num, const st
return "}\n";
}
- return " return res;\n"
+ return " return " + std::string(has_parent ? "std::move(res)" : "res") +
+ ";\n"
"}\n";
}
@@ -562,14 +565,14 @@ std::string TD_TL_writer_jni_cpp::gen_additional_function(const std::string &fun
" " +
class_name_class + " = jni::get_jclass(env, " + gen_java_class_name(gen_class_name(t->name)) + ");\n";
- if (t->args.size()) {
+ if (!t->args.empty()) {
for (std::size_t i = 0; i < t->args.size(); i++) {
const tl::arg &a = t->args[i];
assert(a.type->get_type() == tl::NODE_TYPE_TYPE);
const tl::tl_tree_type *tree_type = static_cast<tl::tl_tree_type *>(a.type);
std::string field_name = gen_field_name(a.name);
- assert(field_name.size());
+ assert(!field_name.empty());
std::string java_field_name = gen_java_field_name(std::string(field_name, 0, field_name.size() - 1));
std::string type_signature = gen_type_signature(tree_type);
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.h b/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.h
index 414cb4c64c..c389bf2eee 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_cpp.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,7 +15,7 @@
namespace td {
-class TD_TL_writer_jni_cpp : public TD_TL_writer_cpp {
+class TD_TL_writer_jni_cpp final : public TD_TL_writer_cpp {
std::string gen_vector_fetch(std::string field_name, const tl::tl_tree_type *t,
const std::vector<tl::var_description> &vars, int parser_type) const;
@@ -32,9 +32,9 @@ class TD_TL_writer_jni_cpp : public TD_TL_writer_cpp {
std::string gen_type_signature(const tl::tl_tree_type *tree_type) const;
- std::string get_pretty_field_name(std::string field_name) const override;
+ std::string get_pretty_field_name(std::string field_name) const final;
- std::string get_pretty_class_name(std::string class_name) const override;
+ std::string get_pretty_class_name(std::string class_name) const final;
public:
TD_TL_writer_jni_cpp(const std::string &tl_name, const std::string &string_type, const std::string &bytes_type,
@@ -42,68 +42,68 @@ class TD_TL_writer_jni_cpp : public TD_TL_writer_cpp {
: TD_TL_writer_cpp(tl_name, string_type, bytes_type, ext_include) {
}
- bool is_built_in_simple_type(const std::string &name) const override;
- bool is_built_in_complex_type(const std::string &name) const override;
+ bool is_built_in_simple_type(const std::string &name) const final;
+ bool is_built_in_complex_type(const std::string &name) const final;
- int get_parser_type(const tl::tl_combinator *t, const std::string &parser_name) const override;
- int get_additional_function_type(const std::string &additional_function_name) const override;
- std::vector<std::string> get_parsers() const override;
- std::vector<std::string> get_storers() const override;
- std::vector<std::string> get_additional_functions() const override;
+ int get_parser_type(const tl::tl_combinator *t, const std::string &parser_name) const final;
+ int get_additional_function_type(const std::string &additional_function_name) const final;
+ std::vector<std::string> get_parsers() const final;
+ std::vector<std::string> get_storers() const final;
+ std::vector<std::string> get_additional_functions() const final;
- std::string gen_base_type_class_name(int arity) const override;
- std::string gen_base_tl_class_name() const override;
+ std::string gen_base_type_class_name(int arity) const final;
+ std::string gen_base_tl_class_name() const final;
- std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const override;
+ std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy,
+ const tl::tl_tree *result) const final;
std::string gen_field_definition(const std::string &class_name, const std::string &type_name,
- const std::string &field_name) const override;
+ const std::string &field_name) const final;
- std::string gen_constructor_id_store(std::int32_t id, int storer_type) const override;
+ std::string gen_constructor_id_store(std::int32_t id, int storer_type) const final;
std::string gen_field_fetch(int field_num, const tl::arg &a, std::vector<tl::var_description> &vars, bool flat,
- int parser_type) const override;
+ int parser_type) const final;
std::string gen_field_store(const tl::arg &a, std::vector<tl::var_description> &vars, bool flat,
- int storer_type) const override;
+ int storer_type) const final;
std::string gen_type_fetch(const std::string &field_name, const tl::tl_tree_type *tree_type,
- const std::vector<tl::var_description> &vars, int parser_type) const override;
+ const std::vector<tl::var_description> &vars, int parser_type) const final;
std::string gen_type_store(const std::string &field_name, const tl::tl_tree_type *tree_type,
- const std::vector<tl::var_description> &vars, int storer_type) const override;
+ const std::vector<tl::var_description> &vars, int storer_type) const final;
- std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const override;
+ std::string gen_get_id(const std::string &class_name, std::int32_t id, bool is_proxy) const final;
- std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name, int arity,
- std::vector<tl::var_description> &vars, int parser_type) const override;
- std::string gen_fetch_function_end(int field_num, const std::vector<tl::var_description> &vars,
- int parser_type) const override;
+ std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
+ const std::string &parent_class_name, int arity, int field_count,
+ std::vector<tl::var_description> &vars, int parser_type) const final;
+ std::string gen_fetch_function_end(bool has_parent, int field_count, const std::vector<tl::var_description> &vars,
+ int parser_type) const final;
std::string gen_fetch_function_result_begin(const std::string &parser_name, const std::string &class_name,
- const tl::tl_tree *result) const override;
- std::string gen_fetch_function_result_end() const override;
+ const tl::tl_tree *result) const final;
+ std::string gen_fetch_function_result_end() const final;
std::string gen_fetch_function_result_any_begin(const std::string &parser_name, const std::string &class_name,
- bool is_proxy) const override;
- std::string gen_fetch_function_result_any_end(bool is_proxy) const override;
+ bool is_proxy) const final;
+ std::string gen_fetch_function_result_any_end(bool is_proxy) const final;
std::string gen_store_function_begin(const std::string &storer_name, const std::string &class_name, int arity,
- std::vector<tl::var_description> &vars, int storer_type) const override;
+ std::vector<tl::var_description> &vars, int storer_type) const final;
- std::string gen_fetch_switch_begin() const override;
- std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const override;
- std::string gen_fetch_switch_end() const override;
+ std::string gen_fetch_switch_begin() const final;
+ std::string gen_fetch_switch_case(const tl::tl_combinator *t, int arity) const final;
+ std::string gen_fetch_switch_end() const final;
std::string gen_additional_function(const std::string &function_name, const tl::tl_combinator *t,
- bool is_function) const override;
+ bool is_function) const final;
std::string gen_additional_proxy_function_begin(const std::string &function_name, const tl::tl_type *type,
const std::string &class_name, int arity,
- bool is_function) const override;
+ bool is_function) const final;
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type,
- const std::string &class_name, int arity) const override;
+ const std::string &class_name, int arity) const final;
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type,
- const tl::tl_combinator *t, int arity,
- bool is_function) const override;
+ const tl::tl_combinator *t, int arity, bool is_function) const final;
std::string gen_additional_proxy_function_end(const std::string &function_name, const tl::tl_type *type,
- bool is_function) const override;
+ bool is_function) const final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.cpp b/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.cpp
index d974d454d9..0f860a2ee1 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -66,7 +66,6 @@ std::string TD_TL_writer_jni_h::gen_output_begin() const {
return "#pragma once\n\n"
"#include \"td/tl/TlObject.h\"\n\n"
"#include <cstdint>\n"
- "#include <memory>\n"
"#include <utility>\n"
"#include <vector>\n\n"
"#include <jni.h>\n\n" +
@@ -80,6 +79,21 @@ std::string TD_TL_writer_jni_h::gen_output_begin() const {
tl_name +
" {\n\n"
+ "using int32 = std::int32_t;\n"
+ "using int53 = std::int64_t;\n"
+ "using int64 = std::int64_t;\n\n"
+
+ "using string = " +
+ string_type +
+ ";\n\n"
+
+ "using bytes = " +
+ bytes_type +
+ ";\n\n"
+
+ "template <class Type>\n"
+ "using array = std::vector<Type>;\n\n"
+
"class " +
gen_base_tl_class_name() +
";\n"
@@ -112,7 +126,7 @@ std::string TD_TL_writer_jni_h::gen_output_begin() const {
}
std::string TD_TL_writer_jni_h::gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const {
+ bool is_proxy, const tl::tl_tree *result) const {
if (class_name == gen_base_tl_class_name()) {
return "class " + class_name +
" {\n"
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.h b/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.h
index 6834937858..bed0ed91d8 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_jni_h.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -13,45 +13,44 @@
namespace td {
-class TD_TL_writer_jni_h : public TD_TL_writer_h {
+class TD_TL_writer_jni_h final : public TD_TL_writer_h {
public:
TD_TL_writer_jni_h(const std::string &tl_name, const std::string &string_type, const std::string &bytes_type,
const std::vector<std::string> &ext_include)
: TD_TL_writer_h(tl_name, string_type, bytes_type, ext_include) {
}
- bool is_built_in_simple_type(const std::string &name) const override;
- bool is_built_in_complex_type(const std::string &name) const override;
+ bool is_built_in_simple_type(const std::string &name) const final;
+ bool is_built_in_complex_type(const std::string &name) const final;
- int get_parser_type(const tl::tl_combinator *t, const std::string &parser_name) const override;
- int get_additional_function_type(const std::string &additional_function_name) const override;
- std::vector<std::string> get_parsers() const override;
- std::vector<std::string> get_storers() const override;
- std::vector<std::string> get_additional_functions() const override;
+ int get_parser_type(const tl::tl_combinator *t, const std::string &parser_name) const final;
+ int get_additional_function_type(const std::string &additional_function_name) const final;
+ std::vector<std::string> get_parsers() const final;
+ std::vector<std::string> get_storers() const final;
+ std::vector<std::string> get_additional_functions() const final;
- std::string gen_base_type_class_name(int arity) const override;
- std::string gen_base_tl_class_name() const override;
+ std::string gen_base_type_class_name(int arity) const final;
+ std::string gen_base_tl_class_name() const final;
- std::string gen_output_begin() const override;
+ std::string gen_output_begin() const final;
- std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const override;
+ std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy,
+ const tl::tl_tree *result) const final;
std::string gen_field_definition(const std::string &class_name, const std::string &type_name,
- const std::string &field_name) const override;
+ const std::string &field_name) const final;
std::string gen_additional_function(const std::string &function_name, const tl::tl_combinator *t,
- bool is_function) const override;
+ bool is_function) const final;
std::string gen_additional_proxy_function_begin(const std::string &function_name, const tl::tl_type *type,
const std::string &class_name, int arity,
- bool is_function) const override;
+ bool is_function) const final;
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type,
- const std::string &class_name, int arity) const override;
+ const std::string &class_name, int arity) const final;
std::string gen_additional_proxy_function_case(const std::string &function_name, const tl::tl_type *type,
- const tl::tl_combinator *t, int arity,
- bool is_function) const override;
+ const tl::tl_combinator *t, int arity, bool is_function) const final;
std::string gen_additional_proxy_function_end(const std::string &function_name, const tl::tl_type *type,
- bool is_function) const override;
+ bool is_function) const final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_td.cpp b/protocols/Telegram/tdlib/td/td/generate/tl_writer_td.cpp
index 4db4284626..757918a553 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_td.cpp
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_td.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -49,6 +49,23 @@ bool TD_TL_writer::is_combinator_supported(const tl::tl_combinator *constructor)
return true;
}
+bool TD_TL_writer::is_default_constructor_generated(const tl::tl_combinator *t, bool can_be_parsed,
+ bool can_be_stored) const {
+ return tl_name == "td_api" || tl_name == "TdApi" || (t->var_count > 0 && can_be_parsed) || t->name == "updates";
+}
+
+bool TD_TL_writer::is_full_constructor_generated(const tl::tl_combinator *t, bool can_be_parsed,
+ bool can_be_stored) const {
+ return tl_name == "td_api" || tl_name == "TdApi" || can_be_stored || t->name == "phone.groupParticipants" ||
+ t->name == "user" || t->name == "userProfilePhoto" || t->name == "channelForbidden" || t->name == "message" ||
+ t->name == "photoSizeEmpty" || t->name == "photoSize" || t->name == "photoCachedSize" ||
+ t->name == "document" || t->name == "updateDeleteMessages" || t->name == "updateEditChannelMessage" ||
+ t->name == "encryptedChatWaiting" || t->name == "encryptedChatRequested" || t->name == "encryptedChat" ||
+ t->name == "langPackString" || t->name == "langPackStringPluralized" || t->name == "langPackStringDeleted" ||
+ t->name == "peerUser" || t->name == "peerChat" || t->name == "updateServiceNotification" ||
+ t->name == "updateNewMessage" || t->name == "updateChannelTooLong" || t->name == "messages.stickerSet";
+}
+
int TD_TL_writer::get_storer_type(const tl::tl_combinator *t, const std::string &storer_name) const {
return storer_name == "TlStorerToString";
}
@@ -117,7 +134,7 @@ std::string TD_TL_writer::gen_class_name(std::string name) const {
assert(false);
}
if (name == "#") {
- return "std::int32_t";
+ return "int32";
}
for (std::size_t i = 0; i < name.size(); i++) {
if (!is_alnum(name[i])) {
@@ -133,9 +150,9 @@ std::string TD_TL_writer::gen_field_name(std::string name) const {
name[i] = '_';
}
}
- assert(name.size() > 0);
+ assert(!name.empty());
assert(name[name.size() - 1] != '_');
- return name + "_";
+ return name + '_';
}
std::string TD_TL_writer::gen_var_name(const tl::var_description &desc) const {
@@ -157,7 +174,7 @@ std::string TD_TL_writer::gen_type_name(const tl::tl_tree_type *tree_type) const
const std::string &name = t->name;
if (name == "#") {
- return "std::int32_t";
+ return "int32";
}
if (name == "True") {
return "bool";
@@ -166,16 +183,19 @@ std::string TD_TL_writer::gen_type_name(const tl::tl_tree_type *tree_type) const
return "bool";
}
if (name == "Int" || name == "Int32") {
- return "std::int32_t";
+ return "int32";
+ }
+ if (name == "Int53") {
+ return "int53";
}
- if (name == "Long" || name == "Int53" || name == "Int64") {
- return "std::int64_t";
+ if (name == "Long" || name == "Int64") {
+ return "int64";
}
if (name == "Double") {
return "double";
}
if (name == "String") {
- return string_type;
+ return "string";
}
if (name == "Int128") {
return "UInt128";
@@ -184,7 +204,7 @@ std::string TD_TL_writer::gen_type_name(const tl::tl_tree_type *tree_type) const
return "UInt256";
}
if (name == "Bytes") {
- return bytes_type;
+ return "bytes";
}
if (name == "Vector") {
@@ -193,7 +213,7 @@ std::string TD_TL_writer::gen_type_name(const tl::tl_tree_type *tree_type) const
assert(tree_type->children[0]->get_type() == tl::NODE_TYPE_TYPE);
const tl::tl_tree_type *child = static_cast<const tl::tl_tree_type *>(tree_type->children[0]);
- return "std::vector<" + gen_type_name(child) + ">";
+ return "array<" + gen_type_name(child) + ">";
}
assert(!is_built_in_simple_type(name) && !is_built_in_complex_type(name));
@@ -235,14 +255,14 @@ std::string TD_TL_writer::gen_constructor_parameter(int field_num, const std::st
}
std::string res = (field_num == 0 ? "" : ", ");
- if (field_type == "bool " || field_type == "std::int32_t " || field_type == "std::int64_t " ||
+ if (field_type == "bool " || field_type == "int32 " || field_type == "int53 " || field_type == "int64 " ||
field_type == "double ") {
res += field_type;
- } else if (field_type == "UInt128 " || field_type == "UInt256 " || field_type == string_type + " ") {
+ } else if (field_type == "UInt128 " || field_type == "UInt256 " || field_type == "string " ||
+ (string_type == bytes_type && field_type == "bytes ")) {
res += field_type + "const &";
- } else if (field_type.compare(0, 11, "std::vector") == 0 || field_type == bytes_type + " ") {
- res += field_type + "&&";
- } else if (field_type.compare(0, 10, "object_ptr") == 0) {
+ } else if (field_type.compare(0, 5, "array") == 0 || field_type == "bytes " ||
+ field_type.compare(0, 10, "object_ptr") == 0) {
res += field_type + "&&";
} else {
assert(false && "unreachable");
diff --git a/protocols/Telegram/tdlib/td/td/generate/tl_writer_td.h b/protocols/Telegram/tdlib/td/td/generate/tl_writer_td.h
index b9f4d11b0a..5df6f7da75 100644
--- a/protocols/Telegram/tdlib/td/td/generate/tl_writer_td.h
+++ b/protocols/Telegram/tdlib/td/td/generate/tl_writer_td.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -35,6 +35,9 @@ class TD_TL_writer : public tl::TL_writer {
bool is_built_in_complex_type(const std::string &name) const override;
bool is_type_bare(const tl::tl_type *t) const override;
bool is_combinator_supported(const tl::tl_combinator *constructor) const override;
+ bool is_default_constructor_generated(const tl::tl_combinator *t, bool can_be_parsed,
+ bool can_be_stored) const override;
+ bool is_full_constructor_generated(const tl::tl_combinator *t, bool can_be_parsed, bool can_be_stored) const override;
int get_storer_type(const tl::tl_combinator *t, const std::string &storer_name) const override;
Mode get_parser_mode(int type) const override;
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/AuthData.cpp b/protocols/Telegram/tdlib/td/td/mtproto/AuthData.cpp
index aa769d1d00..c87ba6ee81 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/AuthData.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/AuthData.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,9 @@
#include "td/mtproto/AuthData.h"
#include "td/utils/format.h"
+#include "td/utils/logging.h"
#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
#include <algorithm>
@@ -15,26 +17,31 @@
namespace td {
namespace mtproto {
-Status MessageIdDuplicateChecker::check(int64 message_id) {
+Status check_message_id_duplicates(int64 *saved_message_ids, size_t max_size, size_t &end_pos, int64 message_id) {
// In addition, the identifiers (msg_id) of the last N messages received from the other side must be stored, and if
// a message comes in with msg_id lower than all or equal to any of the stored values, that message is to be
// ignored. Otherwise, the new message msg_id is added to the set, and, if the number of stored msg_id values is
- // greater than N, the oldest (i. e. the lowest) is forgotten.
- if (saved_message_ids_.size() == MAX_SAVED_MESSAGE_IDS) {
- auto oldest_message_id = *saved_message_ids_.begin();
- if (message_id < oldest_message_id) {
- return Status::Error(1, PSLICE() << "Ignore very old message_id " << tag("oldest message_id", oldest_message_id)
- << tag("got message_id", message_id));
- }
+ // greater than N, the oldest (i.e. the lowest) is forgotten.
+ if (end_pos == 2 * max_size) {
+ std::copy_n(&saved_message_ids[max_size], max_size, &saved_message_ids[0]);
+ end_pos = max_size;
}
- if (saved_message_ids_.count(message_id) != 0) {
- return Status::Error(1, PSLICE() << "Ignore duplicated_message id " << tag("message_id", message_id));
+ if (end_pos == 0 || message_id > saved_message_ids[end_pos - 1]) {
+ // fast path
+ saved_message_ids[end_pos++] = message_id;
+ return Status::OK();
}
-
- saved_message_ids_.insert(message_id);
- if (saved_message_ids_.size() > MAX_SAVED_MESSAGE_IDS) {
- saved_message_ids_.erase(saved_message_ids_.begin());
+ if (end_pos >= max_size && message_id < saved_message_ids[0]) {
+ return Status::Error(2, PSLICE() << "Ignore very old message_id " << tag("oldest message_id", saved_message_ids[0])
+ << tag("got message_id", message_id));
+ }
+ auto it = std::lower_bound(&saved_message_ids[0], &saved_message_ids[end_pos], message_id);
+ if (*it == message_id) {
+ return Status::Error(1, PSLICE() << "Ignore duplicated message_id " << tag("message_id", message_id));
}
+ std::copy_backward(it, &saved_message_ids[end_pos], &saved_message_ids[end_pos + 1]);
+ *it = message_id;
+ ++end_pos;
return Status::OK();
}
@@ -54,7 +61,7 @@ bool AuthData::is_ready(double now) {
return false;
}
if (!has_salt(now)) {
- LOG(INFO) << "no salt";
+ LOG(INFO) << "Need salt";
return false;
}
return true;
@@ -71,7 +78,7 @@ bool AuthData::update_server_time_difference(double diff) {
} else {
return false;
}
- LOG(DEBUG) << "SERVER_TIME: " << format::as_hex(static_cast<int>(get_server_time(Time::now_cached())));
+ LOG(DEBUG) << "SERVER_TIME: " << format::as_hex(static_cast<int32>(get_server_time(Time::now_cached())));
return true;
}
@@ -93,7 +100,7 @@ std::vector<ServerSalt> AuthData::get_future_salts() const {
int64 AuthData::next_message_id(double now) {
double server_time = get_server_time(now);
- int64 t = static_cast<int64>(server_time * (1ll << 32));
+ auto t = static_cast<int64>(server_time * (static_cast<int64>(1) << 32));
// randomize lower bits for clocks with low precision
// TODO(perf) do not do this for systems with good precision?..
@@ -110,14 +117,15 @@ int64 AuthData::next_message_id(double now) {
return result;
}
-bool AuthData::is_valid_outbound_msg_id(int64 id, double now) {
+bool AuthData::is_valid_outbound_msg_id(int64 id, double now) const {
double server_time = get_server_time(now);
- auto id_time = static_cast<double>(id / (1ll << 32));
- return server_time - 300 / 2 < id_time && id_time < server_time + 60 / 2;
+ auto id_time = static_cast<double>(id) / static_cast<double>(static_cast<int64>(1) << 32);
+ return server_time - 150 < id_time && id_time < server_time + 30;
}
-bool AuthData::is_valid_inbound_msg_id(int64 id, double now) {
+
+bool AuthData::is_valid_inbound_msg_id(int64 id, double now) const {
double server_time = get_server_time(now);
- auto id_time = static_cast<double>(id / (1ll << 32));
+ auto id_time = static_cast<double>(id) / static_cast<double>(static_cast<int64>(1) << 32);
return server_time - 300 < id_time && id_time < server_time + 30;
}
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/AuthData.h b/protocols/Telegram/tdlib/td/td/mtproto/AuthData.h
index 7293ffd1c6..b1aea0b4fa 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/AuthData.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/AuthData.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,11 +8,11 @@
#include "td/mtproto/AuthKey.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
-#include <set>
+#include <array>
namespace td {
namespace mtproto {
@@ -37,19 +37,24 @@ void parse(ServerSalt &salt, ParserT &parser) {
salt.valid_until = parser.fetch_double();
}
+Status check_message_id_duplicates(int64 *saved_message_ids, size_t max_size, size_t &end_pos, int64 message_id);
+
+template <size_t max_size>
class MessageIdDuplicateChecker {
public:
- Status check(int64 message_id);
+ Status check(int64 message_id) {
+ return check_message_id_duplicates(&saved_message_ids_[0], max_size, end_pos_, message_id);
+ }
private:
- static constexpr size_t MAX_SAVED_MESSAGE_IDS = 1000;
- std::set<int64> saved_message_ids_;
+ std::array<int64, 2 * max_size> saved_message_ids_;
+ size_t end_pos_ = 0;
};
class AuthData {
public:
AuthData();
- AuthData(const AuthData &) = delete;
+ AuthData(const AuthData &) = default;
AuthData &operator=(const AuthData &) = delete;
AuthData(AuthData &&) = delete;
AuthData &operator=(AuthData &&) = delete;
@@ -57,10 +62,12 @@ class AuthData {
bool is_ready(double now);
- uint64 session_id_;
void set_main_auth_key(AuthKey auth_key) {
main_auth_key_ = std::move(auth_key);
}
+ void break_main_auth_key() {
+ main_auth_key_.break_key();
+ }
const AuthKey &get_main_auth_key() const {
// CHECK(has_main_auth_key());
return main_auth_key_;
@@ -90,7 +97,7 @@ class AuthData {
if (tmp_auth_key_.empty()) {
return true;
}
- if (now > tmp_auth_key_.expire_at() - 60 * 60 * 2 /*2 hours*/) {
+ if (now > tmp_auth_key_.expires_at() - 60 * 60 * 2 /*2 hours*/) {
return true;
}
if (!has_tmp_auth_key(now)) {
@@ -111,7 +118,7 @@ class AuthData {
if (tmp_auth_key_.empty()) {
return false;
}
- if (now > tmp_auth_key_.expire_at() - 60 * 60 /*1 hour*/) {
+ if (now > tmp_auth_key_.expires_at() - 60 * 60 /*1 hour*/) {
return false;
}
return true;
@@ -136,7 +143,7 @@ class AuthData {
void set_auth_flag(bool auth_flag) {
main_auth_key_.set_auth_flag(auth_flag);
if (!auth_flag) {
- tmp_auth_key_.set_auth_flag(auth_flag);
+ drop_tmp_auth_key();
}
}
@@ -148,29 +155,39 @@ class AuthData {
tmp_auth_key_.set_auth_flag(true);
}
- Slice header() {
+ Slice get_header() const {
if (use_pfs()) {
return tmp_auth_key_.need_header() ? Slice(header_) : Slice();
} else {
return main_auth_key_.need_header() ? Slice(header_) : Slice();
}
}
+
void set_header(std::string header) {
header_ = std::move(header);
}
+
void on_api_response() {
if (use_pfs()) {
- if (tmp_auth_key_.auth_flag()) {
- tmp_auth_key_.set_need_header(false);
- }
+ tmp_auth_key_.remove_header();
} else {
- if (main_auth_key_.auth_flag()) {
- main_auth_key_.set_need_header(false);
- }
+ main_auth_key_.remove_header();
}
}
+ void on_connection_not_inited() {
+ if (use_pfs()) {
+ tmp_auth_key_.restore_header();
+ } else {
+ main_auth_key_.restore_header();
+ }
+ }
+
+ void set_session_id(uint64 session_id) {
+ session_id_ = session_id;
+ }
uint64 get_session_id() const {
+ CHECK(session_id_ != 0);
return session_id_;
}
@@ -204,7 +221,7 @@ class AuthData {
future_salts_.clear();
}
- bool is_server_salt_valid(double now) {
+ bool is_server_salt_valid(double now) const {
return server_salt_.valid_until > get_server_time(now) + 60;
}
@@ -224,9 +241,9 @@ class AuthData {
int64 next_message_id(double now);
- bool is_valid_outbound_msg_id(int64 id, double now);
+ bool is_valid_outbound_msg_id(int64 id, double now) const;
- bool is_valid_inbound_msg_id(int64 id, double now);
+ bool is_valid_inbound_msg_id(int64 id, double now) const;
Status check_packet(int64 session_id, int64 message_id, double now, bool &time_difference_was_updated);
@@ -234,6 +251,10 @@ class AuthData {
return updates_duplicate_checker_.check(message_id);
}
+ Status recheck_update(int64 message_id) {
+ return updates_duplicate_rechecker_.check(message_id);
+ }
+
int32 next_seq_no(bool is_content_related) {
int32 res = seq_no_;
if (is_content_related) {
@@ -264,11 +285,13 @@ class AuthData {
int64 last_message_id_ = 0;
int32 seq_no_ = 0;
std::string header_;
+ uint64 session_id_ = 0;
std::vector<ServerSalt> future_salts_;
- MessageIdDuplicateChecker duplicate_checker_;
- MessageIdDuplicateChecker updates_duplicate_checker_;
+ MessageIdDuplicateChecker<1000> duplicate_checker_;
+ MessageIdDuplicateChecker<1000> updates_duplicate_checker_;
+ MessageIdDuplicateChecker<100> updates_duplicate_rechecker_;
void update_salt(double now);
};
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/AuthKey.h b/protocols/Telegram/tdlib/td/td/mtproto/AuthKey.h
index d71afc88e3..52d159df96 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/AuthKey.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/AuthKey.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,11 +11,16 @@
namespace td {
namespace mtproto {
+
class AuthKey {
public:
AuthKey() = default;
AuthKey(uint64 auth_key_id, string &&auth_key) : auth_key_id_(auth_key_id), auth_key_(auth_key) {
}
+ void break_key() {
+ auth_key_id_++;
+ auth_key_[0]++;
+ }
bool empty() const {
return auth_key_.empty();
@@ -29,41 +34,53 @@ class AuthKey {
bool auth_flag() const {
return auth_flag_;
}
- bool was_auth_flag() const {
- return was_auth_flag_;
- }
void set_auth_flag(bool new_auth_flag) {
- if (new_auth_flag == false) {
- clear();
- } else {
- was_auth_flag_ = true;
- }
auth_flag_ = new_auth_flag;
}
bool need_header() const {
- return need_header_;
+ return have_header_ || Time::now() < header_expires_at_;
}
- void set_need_header(bool need_header) {
- need_header_ = need_header;
+ void remove_header() {
+ if (auth_flag_ && have_header_) {
+ have_header_ = false;
+ header_expires_at_ = Time::now() + 3;
+ }
+ }
+ void restore_header() {
+ have_header_ = true;
}
- double expire_at() const {
- return expire_at_;
+
+ double expires_at() const {
+ return expires_at_;
}
- void set_expire_at(double expire_at) {
- expire_at_ = expire_at;
- // expire_at_ = Time::now() + 60 * 60 + 10 * 60;
+ double created_at() const {
+ return created_at_;
+ }
+
+ void set_expires_at(double expires_at) {
+ expires_at_ = expires_at;
+ // expires_at_ = Time::now() + 60 * 60 + 10 * 60;
+ }
+ void set_created_at(double created_at) {
+ created_at_ = created_at;
}
void clear() {
auth_key_.clear();
}
- enum { AUTH_FLAG = 1, WAS_AUTH_FLAG = 2 };
+ static constexpr int32 AUTH_FLAG = 1;
+ static constexpr int32 HAS_CREATED_AT = 4;
+
template <class StorerT>
void store(StorerT &storer) const {
storer.store_binary(auth_key_id_);
- storer.store_binary((auth_flag_ ? AUTH_FLAG : 0) | (was_auth_flag_ ? WAS_AUTH_FLAG : 0));
+ bool has_created_at = created_at_ != 0;
+ storer.store_binary(static_cast<int32>((auth_flag_ ? AUTH_FLAG : 0) | (has_created_at ? HAS_CREATED_AT : 0)));
storer.store_string(auth_key_);
+ if (has_created_at) {
+ storer.store_binary(created_at_);
+ }
}
template <class ParserT>
@@ -71,19 +88,22 @@ class AuthKey {
auth_key_id_ = parser.fetch_long();
auto flags = parser.fetch_int();
auth_flag_ = (flags & AUTH_FLAG) != 0;
- was_auth_flag_ = (flags & WAS_AUTH_FLAG) != 0 || auth_flag_;
auth_key_ = parser.template fetch_string<string>();
+ if ((flags & HAS_CREATED_AT) != 0) {
+ created_at_ = parser.fetch_double();
+ }
// just in case
- need_header_ = true;
+ have_header_ = true;
}
private:
- uint64 auth_key_id_ = 0;
+ uint64 auth_key_id_{0};
string auth_key_;
- bool auth_flag_ = false;
- bool was_auth_flag_ = false;
- bool need_header_ = true;
- double expire_at_ = 0;
+ bool auth_flag_{false};
+ bool have_header_{true};
+ double header_expires_at_{0};
+ double expires_at_{0};
+ double created_at_{0};
};
} // namespace mtproto
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/ConnectionManager.cpp b/protocols/Telegram/tdlib/td/td/mtproto/ConnectionManager.cpp
new file mode 100644
index 0000000000..2cab7158c5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/ConnectionManager.cpp
@@ -0,0 +1,37 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/ConnectionManager.h"
+
+namespace td {
+namespace mtproto {
+
+void ConnectionManager::inc_connect() {
+ auto &cnt = get_link_token() == 1 ? connect_cnt_ : connect_proxy_cnt_;
+ cnt++;
+ if (cnt == 1) {
+ loop();
+ }
+}
+
+void ConnectionManager::dec_connect() {
+ auto &cnt = get_link_token() == 1 ? connect_cnt_ : connect_proxy_cnt_;
+ CHECK(cnt > 0);
+ cnt--;
+ if (cnt == 0) {
+ loop();
+ }
+}
+
+ConnectionManager::ConnectionToken ConnectionManager::connection_impl(ActorId<ConnectionManager> connection_manager,
+ int mode) {
+ auto actor = ActorShared<ConnectionManager>(connection_manager, mode);
+ send_closure(actor, &ConnectionManager::inc_connect);
+ return ConnectionToken(std::move(actor));
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/ConnectionManager.h b/protocols/Telegram/tdlib/td/td/mtproto/ConnectionManager.h
new file mode 100644
index 0000000000..e0d96768a2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/ConnectionManager.h
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+namespace mtproto {
+
+class ConnectionManager : public Actor {
+ public:
+ class ConnectionToken {
+ public:
+ ConnectionToken() = default;
+ explicit ConnectionToken(ActorShared<ConnectionManager> connection_manager)
+ : connection_manager_(std::move(connection_manager)) {
+ }
+ ConnectionToken(const ConnectionToken &) = delete;
+ ConnectionToken &operator=(const ConnectionToken &) = delete;
+ ConnectionToken(ConnectionToken &&) = default;
+ ConnectionToken &operator=(ConnectionToken &&other) noexcept {
+ reset();
+ connection_manager_ = std::move(other.connection_manager_);
+ return *this;
+ }
+ ~ConnectionToken() {
+ reset();
+ }
+
+ void reset() {
+ if (!connection_manager_.empty()) {
+ send_closure(connection_manager_, &ConnectionManager::dec_connect);
+ connection_manager_.reset();
+ }
+ }
+
+ bool empty() const {
+ return connection_manager_.empty();
+ }
+
+ private:
+ ActorShared<ConnectionManager> connection_manager_;
+ };
+
+ static ConnectionToken connection(ActorId<ConnectionManager> connection_manager) {
+ return connection_impl(connection_manager, 1);
+ }
+ static ConnectionToken connection_proxy(ActorId<ConnectionManager> connection_manager) {
+ return connection_impl(connection_manager, 2);
+ }
+
+ protected:
+ uint32 connect_cnt_ = 0;
+ uint32 connect_proxy_cnt_ = 0;
+
+ private:
+ void inc_connect();
+ void dec_connect();
+
+ static ConnectionToken connection_impl(ActorId<ConnectionManager> connection_manager, int mode);
+};
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/CryptoStorer.h b/protocols/Telegram/tdlib/td/td/mtproto/CryptoStorer.h
index 08c1268493..6b7c3ccfdd 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/CryptoStorer.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/CryptoStorer.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,17 +7,29 @@
#pragma once
#include "td/mtproto/AuthData.h"
+#include "td/mtproto/MtprotoQuery.h"
#include "td/mtproto/PacketStorer.h"
#include "td/mtproto/utils.h"
#include "td/mtproto/mtproto_api.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
#include "td/utils/Slice.h"
+#include "td/utils/Span.h"
+#include "td/utils/StorerBase.h"
#include "td/utils/Time.h"
namespace td {
+namespace mtproto_api {
+class msg_container {
+ public:
+ static const int32 ID = 0x73f1f8dc;
+};
+} // namespace mtproto_api
+
namespace mtproto {
+
template <class Object, class ObjectStorer>
class ObjectImpl {
public:
@@ -29,8 +41,8 @@ class ObjectImpl {
message_id_ = auth_data->next_message_id(Time::now_cached());
seq_no_ = auth_data->next_seq_no(need_ack);
}
- template <class T>
- void do_store(T &storer) const {
+ template <class StorerT>
+ void do_store(StorerT &storer) const {
if (empty()) {
return;
}
@@ -64,6 +76,7 @@ using GetFutureSaltsImpl = ObjectImpl<mtproto_api::get_future_salts, TLStorer<mt
using ResendImpl = ObjectImpl<mtproto_api::msg_resend_req, TLObjectStorer<mtproto_api::msg_resend_req>>;
using CancelImpl = ObjectImpl<mtproto_api::rpc_drop_answer, TLStorer<mtproto_api::rpc_drop_answer>>;
using GetInfoImpl = ObjectImpl<mtproto_api::msgs_state_req, TLObjectStorer<mtproto_api::msgs_state_req>>;
+using DestroyAuthKeyImpl = ObjectImpl<mtproto_api::destroy_auth_key, TLStorer<mtproto_api::destroy_auth_key>>;
class CancelVectorImpl {
public:
@@ -74,8 +87,8 @@ class CancelVectorImpl {
}
}
- template <class T>
- void do_store(T &storer) const {
+ template <class StorerT>
+ void do_store(StorerT &storer) const {
for (auto &s : storers_) {
storer.store_storer(s);
}
@@ -92,33 +105,45 @@ class CancelVectorImpl {
vector<PacketStorer<CancelImpl>> storers_;
};
+class InvokeAfter {
+ public:
+ explicit InvokeAfter(Span<uint64> ids) : ids_(ids) {
+ }
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ if (ids_.empty()) {
+ return;
+ }
+ if (ids_.size() == 1) {
+ storer.store_int(static_cast<int32>(0xcb9f372d));
+ storer.store_long(static_cast<int64>(ids_[0]));
+ return;
+ }
+ // invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
+ storer.store_int(static_cast<int32>(0x3dc4b4f0));
+ storer.store_int(static_cast<int32>(0x1cb5c415));
+ storer.store_int(narrow_cast<int32>(ids_.size()));
+ for (auto id : ids_) {
+ storer.store_long(static_cast<int64>(id));
+ }
+ }
+
+ private:
+ Span<uint64> ids_;
+};
+
class QueryImpl {
public:
- QueryImpl(const Query &query, Slice header) : query_(query), header_(header) {
+ QueryImpl(const MtprotoQuery &query, Slice header) : query_(query), header_(header) {
}
- template <class T>
- void do_store(T &storer) const {
+ template <class StorerT>
+ void do_store(StorerT &storer) const {
storer.store_binary(query_.message_id);
storer.store_binary(query_.seq_no);
- Slice header = this->header_;
- Slice invoke_header = Slice();
-
-// TODO(refactor):
-// invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
-// This code makes me very sad.
-// InvokeAfterMsg is not even in mtproto_api. It is in telegram_api.
-#pragma pack(push, 4)
- struct {
- uint32 constructor_id;
- uint64 invoke_after_id;
- } invoke_data;
-#pragma pack(pop)
- if (query_.invoke_after_id != 0) {
- invoke_data.constructor_id = 0xcb9f372d;
- invoke_data.invoke_after_id = query_.invoke_after_id;
- invoke_header = Slice(reinterpret_cast<const uint8 *>(&invoke_data), sizeof(invoke_data));
- }
+
+ InvokeAfter invoke_after(query_.invoke_after_ids);
+ auto invoke_after_storer = create_default_storer(invoke_after);
Slice data = query_.packet.as_slice();
mtproto_api::gzip_packed packed(data);
@@ -126,9 +151,8 @@ class QueryImpl {
auto gzip_storer = create_storer(packed);
const Storer &data_storer =
query_.gzip_flag ? static_cast<const Storer &>(gzip_storer) : static_cast<const Storer &>(plain_storer);
- auto invoke_header_storer = create_storer(invoke_header);
- auto header_storer = create_storer(header);
- auto suff_storer = create_storer(invoke_header_storer, data_storer);
+ auto header_storer = create_storer(header_);
+ auto suff_storer = create_storer(invoke_after_storer, data_storer);
auto all_storer = create_storer(header_storer, suff_storer);
storer.store_binary(static_cast<uint32>(all_storer.size()));
@@ -136,17 +160,17 @@ class QueryImpl {
}
private:
- const Query &query_;
+ const MtprotoQuery &query_;
Slice header_;
};
class QueryVectorImpl {
public:
- QueryVectorImpl(const vector<Query> &to_send, Slice header) : to_send_(to_send), header_(header) {
+ QueryVectorImpl(const vector<MtprotoQuery> &to_send, Slice header) : to_send_(to_send), header_(header) {
}
- template <class T>
- void do_store(T &storer) const {
+ template <class StorerT>
+ void do_store(StorerT &storer) const {
if (to_send_.empty()) {
return;
}
@@ -156,7 +180,7 @@ class QueryVectorImpl {
}
private:
- const vector<Query> &to_send_;
+ const vector<MtprotoQuery> &to_send_;
Slice header_;
};
@@ -165,8 +189,8 @@ class ContainerImpl {
ContainerImpl(int32 cnt, Storer &storer) : cnt_(cnt), storer_(storer) {
}
- template <class T>
- void do_store(T &storer) const {
+ template <class StorerT>
+ void do_store(StorerT &storer) const {
storer.store_binary(mtproto_api::msg_container::ID);
storer.store_binary(cnt_);
storer.store_storer(storer_);
@@ -179,10 +203,11 @@ class ContainerImpl {
class CryptoImpl {
public:
- CryptoImpl(const vector<Query> &to_send, Slice header, vector<int64> &&to_ack, int64 ping_id, int ping_timeout,
+ CryptoImpl(const vector<MtprotoQuery> &to_send, Slice header, vector<int64> &&to_ack, int64 ping_id, int ping_timeout,
int max_delay, int max_after, int max_wait, int future_salt_n, vector<int64> get_info,
- vector<int64> resend, vector<int64> cancel, AuthData *auth_data, uint64 *container_id, uint64 *get_info_id,
- uint64 *resend_id, uint64 *ping_message_id, uint64 *parent_message_id)
+ vector<int64> resend, const vector<int64> &cancel, bool destroy_key, AuthData *auth_data,
+ uint64 *container_id, uint64 *get_info_id, uint64 *resend_id, uint64 *ping_message_id,
+ uint64 *parent_message_id)
: query_storer_(to_send, header)
, ack_empty_(to_ack.empty())
, ack_storer_(!ack_empty_, mtproto_api::msgs_ack(std::move(to_ack)), auth_data)
@@ -195,17 +220,19 @@ class CryptoImpl {
, resend_storer_(resend_not_empty_, mtproto_api::msg_resend_req(std::move(resend)), auth_data, true)
, cancel_not_empty_(!cancel.empty())
, cancel_cnt_(static_cast<int32>(cancel.size()))
- , cancel_storer_(cancel_not_empty_, std::move(cancel), auth_data, true)
+ , cancel_storer_(cancel_not_empty_, cancel, auth_data, true)
+ , destroy_key_storer_(destroy_key, mtproto_api::destroy_auth_key(), auth_data, true)
, tmp_storer_(query_storer_, ack_storer_)
, tmp2_storer_(tmp_storer_, http_wait_storer_)
, tmp3_storer_(tmp2_storer_, get_future_salts_storer_)
, tmp4_storer_(tmp3_storer_, get_info_storer_)
, tmp5_storer_(tmp4_storer_, resend_storer_)
, tmp6_storer_(tmp5_storer_, cancel_storer_)
- , concat_storer_(tmp6_storer_, ping_storer_)
+ , tmp7_storer_(tmp6_storer_, destroy_key_storer_)
+ , concat_storer_(tmp7_storer_, ping_storer_)
, cnt_(static_cast<int32>(to_send.size()) + ack_storer_.not_empty() + ping_storer_.not_empty() +
http_wait_storer_.not_empty() + get_future_salts_storer_.not_empty() + get_info_storer_.not_empty() +
- resend_storer_.not_empty() + cancel_cnt_)
+ resend_storer_.not_empty() + cancel_cnt_ + destroy_key_storer_.not_empty())
, container_storer_(cnt_, concat_storer_) {
CHECK(cnt_ != 0);
if (get_info_storer_.not_empty() && get_info_id) {
@@ -251,13 +278,16 @@ class CryptoImpl {
} else if (cancel_storer_.not_empty()) {
type_ = OnlyCancel;
*parent_message_id = cancel_storer_.get_message_id();
+ } else if (destroy_key_storer_.not_empty()) {
+ type_ = OnlyDestroyKey;
+ *parent_message_id = destroy_key_storer_.get_message_id();
} else {
UNREACHABLE();
}
}
- template <class T>
- void do_store(T &storer) const {
+ template <class StorerT>
+ void do_store(StorerT &storer) const {
switch (type_) {
case OnlyAck:
return storer.store_storer(ack_storer_);
@@ -283,6 +313,9 @@ class CryptoImpl {
case OnlyGetInfo:
return storer.store_storer(get_info_storer_);
+ case OnlyDestroyKey:
+ return storer.store_storer(destroy_key_storer_);
+
default:
storer.store_binary(message_id_);
storer.store_binary(seq_no_);
@@ -305,12 +338,14 @@ class CryptoImpl {
bool cancel_not_empty_;
int32 cancel_cnt_;
PacketStorer<CancelVectorImpl> cancel_storer_;
+ PacketStorer<DestroyAuthKeyImpl> destroy_key_storer_;
ConcatStorer tmp_storer_;
ConcatStorer tmp2_storer_;
ConcatStorer tmp3_storer_;
ConcatStorer tmp4_storer_;
ConcatStorer tmp5_storer_;
ConcatStorer tmp6_storer_;
+ ConcatStorer tmp7_storer_;
ConcatStorer concat_storer_;
int32 cnt_;
PacketStorer<ContainerImpl> container_storer_;
@@ -323,11 +358,13 @@ class CryptoImpl {
OnlyResend,
OnlyCancel,
OnlyGetInfo,
+ OnlyDestroyKey,
Mixed
};
Type type_;
uint64 message_id_;
int32 seq_no_;
};
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/DhCallback.h b/protocols/Telegram/tdlib/td/td/mtproto/DhCallback.h
new file mode 100644
index 0000000000..4f5e303526
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/DhCallback.h
@@ -0,0 +1,29 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/Slice.h"
+
+namespace td {
+namespace mtproto {
+
+class DhCallback {
+ public:
+ DhCallback() = default;
+ DhCallback(const DhCallback &) = delete;
+ DhCallback &operator=(const DhCallback &) = delete;
+ DhCallback(DhCallback &&) = delete;
+ DhCallback &operator=(DhCallback &&) = delete;
+ virtual ~DhCallback() = default;
+
+ virtual int is_good_prime(Slice prime_str) const = 0;
+ virtual void add_good_prime(Slice prime_str) const = 0;
+ virtual void add_bad_prime(Slice prime_str) const = 0;
+};
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/DhHandshake.cpp b/protocols/Telegram/tdlib/td/td/mtproto/DhHandshake.cpp
new file mode 100644
index 0000000000..c753d718c1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/DhHandshake.cpp
@@ -0,0 +1,232 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/DhHandshake.h"
+
+#include "td/mtproto/DhCallback.h"
+
+#include "td/utils/as.h"
+#include "td/utils/crypto.h"
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/UInt.h"
+
+namespace td {
+namespace mtproto {
+
+Status DhHandshake::check_config(Slice prime_str, const BigNum &prime, int32 g_int, BigNumContext &ctx,
+ DhCallback *callback) {
+ // check that 2^2047 <= p < 2^2048
+ if (prime.get_num_bits() != 2048) {
+ return Status::Error("p is not 2048-bit number");
+ }
+
+ // g generates a cyclic subgroup of prime order (p - 1) / 2, i.e. is a quadratic residue mod p.
+ // Since g is always equal to 2, 3, 4, 5, 6 or 7, this is easily done using quadratic reciprocity law,
+ // yielding a simple condition on
+ // * p mod 4g - namely, p mod 8 = 7 for g = 2; p mod 3 = 2 for g = 3;
+ // * no extra condition for g = 4;
+ // * p mod 5 = 1 or 4 for g = 5;
+ // * p mod 24 = 19 or 23 for g = 6;
+ // * p mod 7 = 3, 5 or 6 for g = 7.
+
+ bool mod_ok;
+ uint32 mod_r;
+ switch (g_int) {
+ case 2:
+ mod_ok = prime % 8 == 7u;
+ break;
+ case 3:
+ mod_ok = prime % 3 == 2u;
+ break;
+ case 4:
+ mod_ok = true;
+ break;
+ case 5:
+ mod_ok = (mod_r = prime % 5) == 1u || mod_r == 4u;
+ break;
+ case 6:
+ mod_ok = (mod_r = prime % 24) == 19u || mod_r == 23u;
+ break;
+ case 7:
+ mod_ok = (mod_r = prime % 7) == 3u || mod_r == 5u || mod_r == 6u;
+ break;
+ default:
+ mod_ok = false;
+ }
+ if (!mod_ok) {
+ return Status::Error("Bad prime mod 4g");
+ }
+
+ // check whether p is a safe prime (meaning that both p and (p - 1) / 2 are prime)
+ int is_good_prime = -1;
+ if (callback) {
+ is_good_prime = callback->is_good_prime(prime_str);
+ }
+ if (is_good_prime != -1) {
+ return is_good_prime ? Status::OK() : Status::Error("p or (p - 1) / 2 is not a prime number");
+ }
+ if (!prime.is_prime(ctx)) {
+ if (callback) {
+ callback->add_bad_prime(prime_str);
+ }
+ return Status::Error("p is not a prime number");
+ }
+
+ BigNum half_prime = prime;
+ half_prime -= 1;
+ half_prime /= 2;
+ if (!half_prime.is_prime(ctx)) {
+ if (callback) {
+ callback->add_bad_prime(prime_str);
+ }
+ return Status::Error("(p - 1) / 2 is not a prime number");
+ }
+ if (callback) {
+ callback->add_good_prime(prime_str);
+ }
+ return Status::OK();
+}
+
+Status DhHandshake::dh_check(const BigNum &prime, const BigNum &g_a, const BigNum &g_b) {
+ // IMPORTANT: Apart from the conditions on the Diffie-Hellman prime dh_prime and generator g, both sides are
+ // to check that g, g_a and g_b are greater than 1 and less than dh_prime - 1.
+ // We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well.
+
+ CHECK(prime.get_num_bits() == 2048);
+ BigNum left;
+ left.set_value(0);
+ left.set_bit(2048 - 64);
+
+ BigNum right;
+ BigNum::sub(right, prime, left);
+
+ if (BigNum::compare(left, g_a) > 0 || BigNum::compare(g_a, right) > 0 || BigNum::compare(left, g_b) > 0 ||
+ BigNum::compare(g_b, right) > 0) {
+ std::string x(2048, '0');
+ std::string y(2048, '0');
+ for (int i = 0; i < 2048; i++) {
+ if (g_a.is_bit_set(i)) {
+ x[i] = '1';
+ }
+ if (g_b.is_bit_set(i)) {
+ y[i] = '1';
+ }
+ }
+ LOG(ERROR) << x;
+ LOG(ERROR) << y;
+ return Status::Error("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}");
+ }
+
+ return Status::OK();
+}
+
+void DhHandshake::set_config(int32 g_int, Slice prime_str) {
+ has_config_ = true;
+ prime_ = BigNum::from_binary(prime_str);
+ prime_str_ = prime_str.str();
+
+ b_ = BigNum();
+ g_b_ = BigNum();
+
+ BigNum::random(b_, 2048, -1, 0);
+
+ // g^b
+ g_int_ = g_int;
+ g_.set_value(g_int_);
+
+ BigNum::mod_exp(g_b_, g_, b_, prime_, ctx_);
+}
+
+Status DhHandshake::check_config(int32 g_int, Slice prime_str, DhCallback *callback) {
+ BigNumContext ctx;
+ auto prime = BigNum::from_binary(prime_str);
+ return check_config(prime_str, prime, g_int, ctx, callback);
+}
+
+void DhHandshake::set_g_a_hash(Slice g_a_hash) {
+ has_g_a_hash_ = true;
+ ok_g_a_hash_ = false;
+ CHECK(!has_g_a_);
+ g_a_hash_ = g_a_hash.str();
+}
+
+void DhHandshake::set_g_a(Slice g_a_str) {
+ has_g_a_ = true;
+ if (has_g_a_hash_) {
+ string g_a_hash(32, ' ');
+ sha256(g_a_str, g_a_hash);
+ ok_g_a_hash_ = g_a_hash == g_a_hash_;
+ }
+ g_a_ = BigNum::from_binary(g_a_str);
+}
+
+string DhHandshake::get_g_a() const {
+ CHECK(has_g_a_);
+ return g_a_.to_binary();
+}
+
+string DhHandshake::get_g_b() const {
+ CHECK(has_config_);
+ return g_b_.to_binary();
+}
+string DhHandshake::get_g_b_hash() const {
+ string g_b_hash(32, ' ');
+ sha256(get_g_b(), g_b_hash);
+ return g_b_hash;
+}
+
+Status DhHandshake::run_checks(bool skip_config_check, DhCallback *callback) {
+ CHECK(has_g_a_ && has_config_);
+
+ if (has_g_a_hash_ && !ok_g_a_hash_) {
+ return Status::Error("g_a_hash mismatch");
+ }
+
+ if (!skip_config_check) {
+ TRY_STATUS(check_config(prime_str_, prime_, g_int_, ctx_, callback));
+ }
+
+ return dh_check(prime_, g_a_, g_b_);
+}
+
+BigNum DhHandshake::get_g() const {
+ CHECK(has_config_);
+ return g_;
+}
+
+BigNum DhHandshake::get_p() const {
+ CHECK(has_config_);
+ return prime_;
+}
+
+BigNum DhHandshake::get_b() const {
+ CHECK(has_config_);
+ return b_;
+}
+
+BigNum DhHandshake::get_g_ab() {
+ CHECK(has_g_a_ && has_config_);
+ BigNum g_ab;
+ BigNum::mod_exp(g_ab, g_a_, b_, prime_, ctx_);
+ return g_ab;
+}
+
+std::pair<int64, string> DhHandshake::gen_key() {
+ string key = get_g_ab().to_binary(2048 / 8);
+ auto key_id = calc_key_id(key);
+ return std::pair<int64, string>(key_id, std::move(key));
+}
+
+int64 DhHandshake::calc_key_id(Slice auth_key) {
+ UInt<160> auth_key_sha1;
+ sha1(auth_key, auth_key_sha1.raw);
+ return as<int64>(auth_key_sha1.raw + 12);
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/crypto.h b/protocols/Telegram/tdlib/td/td/mtproto/DhHandshake.h
index 1e411c7c73..4926cf7fbf 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/crypto.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/DhHandshake.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,51 +14,16 @@
#include <utility>
namespace td {
+namespace mtproto {
-/*** RSA ***/
-class RSA {
- public:
- RSA clone() const;
- int64 get_fingerprint() const;
- size_t size() const;
- size_t encrypt(unsigned char *from, size_t from_len, unsigned char *to) const;
-
- void decrypt(Slice from, MutableSlice to) const;
-
- static Result<RSA> from_pem(Slice pem);
-
- private:
- RSA(BigNum n, BigNum e);
- BigNum n_;
- BigNum e_;
-};
+class DhCallback;
-/*** PublicRsaKeyInterface ***/
-class PublicRsaKeyInterface {
- public:
- virtual ~PublicRsaKeyInterface() = default;
- virtual Result<std::pair<RSA, int64>> get_rsa(const vector<int64> &fingerprints) = 0;
- virtual void drop_keys() = 0;
-};
-
-/*** DH ***/
-class DhCallback {
- public:
- DhCallback() = default;
- DhCallback(const DhCallback &) = delete;
- DhCallback &operator=(const DhCallback &) = delete;
- DhCallback(DhCallback &&) = delete;
- DhCallback &operator=(DhCallback &&) = delete;
- virtual ~DhCallback() = default;
-
- virtual int is_good_prime(Slice prime_str) const = 0;
- virtual void add_good_prime(Slice prime_str) const = 0;
- virtual void add_bad_prime(Slice prime_str) const = 0;
-};
class DhHandshake {
public:
void set_config(int32 g_int, Slice prime_str);
+ static Status check_config(int32 g_int, Slice prime_str, DhCallback *callback);
+
bool has_config() const {
return has_config_;
}
@@ -70,11 +35,16 @@ class DhHandshake {
string get_g_a() const;
string get_g_b() const;
string get_g_b_hash() const;
- Status run_checks(DhCallback *callback) TD_WARN_UNUSED_RESULT;
+ Status run_checks(bool skip_config_check, DhCallback *callback) TD_WARN_UNUSED_RESULT;
+
+ BigNum get_g() const;
+ BigNum get_p() const;
+ BigNum get_b() const;
+ BigNum get_g_ab();
std::pair<int64, string> gen_key();
- static int64 calc_key_id(const string &auth_key);
+ static int64 calc_key_id(Slice auth_key);
enum Flags { HasConfig = 1, HasGA = 2 };
@@ -127,13 +97,15 @@ class DhHandshake {
}
private:
- static Status dh_check(Slice prime_str, const BigNum &prime, int32 g_int, const BigNum &g_a, const BigNum &g_b,
- BigNumContext &ctx, DhCallback *callback) TD_WARN_UNUSED_RESULT;
+ static Status check_config(Slice prime_str, const BigNum &prime, int32 g_int, BigNumContext &ctx,
+ DhCallback *callback) TD_WARN_UNUSED_RESULT;
+
+ static Status dh_check(const BigNum &prime, const BigNum &g_a, const BigNum &g_b) TD_WARN_UNUSED_RESULT;
string prime_str_;
BigNum prime_;
BigNum g_;
- int32 g_int_;
+ int32 g_int_ = 0;
BigNum b_;
BigNum g_b_;
BigNum g_a_;
@@ -148,13 +120,5 @@ class DhHandshake {
BigNumContext ctx_;
};
-// TODO: remove this legacy functions
-Status dh_handshake(int g_int, Slice prime_str, Slice g_a_str, string *g_b_str, string *g_ab_str,
- DhCallback *callback) TD_WARN_UNUSED_RESULT;
-int64 dh_auth_key_id(const string &auth_key);
-
-/*** KDF ***/
-void KDF(const string &auth_key, const UInt128 &msg_key, int X, UInt256 *aes_key, UInt256 *aes_iv);
-void tmp_KDF(const UInt128 &server_nonce, const UInt256 &new_nonce, UInt256 *tmp_aes_key, UInt256 *tmp_aes_iv);
-void KDF2(Slice auth_key, const UInt128 &msg_key, int X, UInt256 *aes_key, UInt256 *aes_iv);
+} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/Handshake.cpp b/protocols/Telegram/tdlib/td/td/mtproto/Handshake.cpp
index e5ed75994a..054816f100 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/Handshake.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/Handshake.cpp
@@ -1,114 +1,160 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/mtproto/Handshake.h"
-#include "td/mtproto/utils.h"
-
+#include "td/mtproto/DhCallback.h"
+#include "td/mtproto/DhHandshake.h"
+#include "td/mtproto/KDF.h"
#include "td/mtproto/mtproto_api.h"
+#include "td/mtproto/utils.h"
+#include "td/utils/as.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/Time.h"
#include "td/utils/tl_parsers.h"
-#include "td/utils/tl_storers.h"
+
+#include <algorithm>
namespace td {
namespace mtproto {
+template <class T>
+static Result<typename T::ReturnType> fetch_result(Slice message, bool check_end = true) {
+ TlParser parser(message);
+ auto result = T::fetch_result(parser);
+
+ if (check_end) {
+ parser.fetch_end();
+ }
+ const char *error = parser.get_error();
+ if (error != nullptr) {
+ LOG(ERROR) << "Can't parse: " << format::as_hex_dump<4>(message);
+ return Status::Error(500, Slice(error));
+ }
+
+ return std::move(result);
+}
+
+AuthKeyHandshake::AuthKeyHandshake(int32 dc_id, int32 expires_in)
+ : mode_(expires_in == 0 ? Mode::Main : Mode::Temp)
+ , dc_id_(dc_id)
+ , expires_in_(expires_in)
+ , start_time_(Time::now())
+ , timeout_in_(1e9) {
+}
+
+void AuthKeyHandshake::set_timeout_in(double timeout_in) {
+ start_time_ = Time::now();
+ timeout_in_ = timeout_in;
+}
+
void AuthKeyHandshake::clear() {
last_query_ = BufferSlice();
state_ = Start;
+ start_time_ = Time::now();
+ timeout_in_ = 1e9;
}
-bool AuthKeyHandshake::is_ready_for_start() {
- return state_ == Start;
-}
-bool AuthKeyHandshake::is_ready_for_message(const UInt128 &message_nonce) {
- return state_ != Finish && state_ != Start && nonce == message_nonce;
-}
-bool AuthKeyHandshake::is_ready_for_finish() {
+bool AuthKeyHandshake::is_ready_for_finish() const {
return state_ == Finish;
}
+
void AuthKeyHandshake::on_finish() {
clear();
}
-template <class DataT>
-Result<size_t> AuthKeyHandshake::fill_data_with_hash(uint8 *data_with_hash, const DataT &data) {
- // data_with_hash := SHA1(data) + data + (any random bytes); such that the length equal 255 bytes;
- uint8 *data_ptr = data_with_hash + 20;
- size_t data_size = tl_calc_length(data);
- if (data_size + 20 + 4 > 255) {
- return Status::Error("Too big data");
- }
- as<int32>(data_ptr) = data.get_id();
- tl_store_unsafe(data, data_ptr + 4);
- sha1(Slice(data_ptr, data_size + 4), data_with_hash);
- return data_size + 20 + 4;
+string AuthKeyHandshake::store_object(const mtproto_api::Object &object) {
+ auto storer = create_storer(object);
+ size_t size = storer.size();
+ string result(size, '\0');
+ auto real_size = storer.store(MutableSlice(result).ubegin());
+ CHECK(real_size == size);
+ return result;
}
Status AuthKeyHandshake::on_res_pq(Slice message, Callback *connection, PublicRsaKeyInterface *public_rsa_key) {
- TRY_RESULT(res_pq, fetch_result<mtproto_api::req_pq_multi>(message));
- if (res_pq->nonce_ != nonce) {
+ if (Time::now() >= start_time_ + timeout_in_ * 0.6) {
+ return Status::Error("Handshake ResPQ timeout expired");
+ }
+
+ TRY_RESULT(res_pq, fetch_result<mtproto_api::req_pq_multi>(message, false));
+ if (res_pq->nonce_ != nonce_) {
return Status::Error("Nonce mismatch");
}
- server_nonce = res_pq->server_nonce_;
+ server_nonce_ = res_pq->server_nonce_;
- auto r_rsa = public_rsa_key->get_rsa(res_pq->server_public_key_fingerprints_);
- if (r_rsa.is_error()) {
+ auto r_rsa_key = public_rsa_key->get_rsa_key(res_pq->server_public_key_fingerprints_);
+ if (r_rsa_key.is_error()) {
public_rsa_key->drop_keys();
- return r_rsa.move_as_error();
+ return r_rsa_key.move_as_error();
}
- int64 rsa_fingerprint = r_rsa.ok().second;
- RSA rsa = std::move(r_rsa.ok_ref().first);
+ auto rsa_key = r_rsa_key.move_as_ok();
- string p, q;
+ string p;
+ string q;
if (pq_factorize(res_pq->pq_, &p, &q) == -1) {
return Status::Error("Failed to factorize");
}
- Random::secure_bytes(new_nonce.raw, sizeof(new_nonce));
+ Random::secure_bytes(new_nonce_.raw, sizeof(new_nonce_));
- alignas(8) uint8 data_with_hash[255];
- Result<size_t> r_data_size = 0;
+ string data;
switch (mode_) {
case Mode::Main:
- r_data_size = fill_data_with_hash(data_with_hash,
- mtproto_api::p_q_inner_data(res_pq->pq_, p, q, nonce, server_nonce, new_nonce));
+ data = store_object(mtproto_api::p_q_inner_data_dc(res_pq->pq_, p, q, nonce_, server_nonce_, new_nonce_, dc_id_));
break;
case Mode::Temp:
- r_data_size = fill_data_with_hash(
- data_with_hash,
- mtproto_api::p_q_inner_data_temp(res_pq->pq_, p, q, nonce, server_nonce, new_nonce, expire_in_));
- expire_at_ = Time::now() + expire_in_;
+ data = store_object(mtproto_api::p_q_inner_data_temp_dc(res_pq->pq_, p, q, nonce_, server_nonce_, new_nonce_,
+ dc_id_, expires_in_));
+ expires_at_ = Time::now() + expires_in_;
break;
- case Mode::Unknown:
default:
UNREACHABLE();
- r_data_size = Status::Error(500, "Unreachable");
}
- if (r_data_size.is_error()) {
- return r_data_size.move_as_error();
+
+ string encrypted_data(256, '\0');
+ auto data_size = data.size();
+ if (data_size > 144) {
+ return Status::Error("Too big data");
}
- size_t size = r_data_size.ok();
- // encrypted_data := RSA (data_with_hash, server_public_key); a 255-byte long number (big endian)
- // is raised to the requisite power over the requisite modulus, and the result is stored as a 256-byte number.
- string encrypted_data(256, 0);
- rsa.encrypt(data_with_hash, size, reinterpret_cast<unsigned char *>(&encrypted_data[0]));
+ data.resize(192);
+ Random::secure_bytes(MutableSlice(data).substr(data_size));
+
+ while (true) {
+ string aes_key(32, '\0');
+ Random::secure_bytes(MutableSlice(aes_key));
+
+ string data_with_hash = PSTRING() << data << sha256(aes_key + data);
+ std::reverse(data_with_hash.begin(), data_with_hash.begin() + data.size());
- // req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long
- // encrypted_data:string = Server_DH_Params
- mtproto_api::req_DH_params req_dh_params(nonce, server_nonce, p, q, rsa_fingerprint, std::move(encrypted_data));
+ string decrypted_data(256, '\0');
+ string aes_iv(32, '\0');
+ aes_ige_encrypt(aes_key, aes_iv, data_with_hash, MutableSlice(decrypted_data).substr(32));
+
+ auto hash = sha256(MutableSlice(decrypted_data).substr(32));
+ for (size_t i = 0; i < 32; i++) {
+ decrypted_data[i] = static_cast<char>(aes_key[i] ^ hash[i]);
+ }
+
+ if (rsa_key.rsa.encrypt(decrypted_data, encrypted_data)) {
+ break;
+ }
+ }
+
+ mtproto_api::req_DH_params req_dh_params(nonce_, server_nonce_, p, q, rsa_key.fingerprint, encrypted_data);
send(connection, create_storer(req_dh_params));
state_ = ServerDHParams;
@@ -116,34 +162,30 @@ Status AuthKeyHandshake::on_res_pq(Slice message, Callback *connection, PublicRs
}
Status AuthKeyHandshake::on_server_dh_params(Slice message, Callback *connection, DhCallback *dh_callback) {
- TRY_RESULT(server_dh_params, fetch_result<mtproto_api::req_DH_params>(message));
- switch (server_dh_params->get_id()) {
- case mtproto_api::server_DH_params_ok::ID:
- break;
- case mtproto_api::server_DH_params_fail::ID:
- return Status::Error("Server dh params fail");
- default:
- return Status::Error("Unknown result");
+ if (Time::now() >= start_time_ + timeout_in_ * 0.8) {
+ return Status::Error("Handshake DH params timeout expired");
}
- auto dh_params = move_tl_object_as<mtproto_api::server_DH_params_ok>(server_dh_params);
+ TRY_RESULT(dh_params, fetch_result<mtproto_api::req_DH_params>(message, false));
// server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params;
- if (dh_params->nonce_ != nonce) {
+ if (dh_params->nonce_ != nonce_) {
return Status::Error("Nonce mismatch");
}
- if (dh_params->server_nonce_ != server_nonce) {
+ if (dh_params->server_nonce_ != server_nonce_) {
return Status::Error("Server nonce mismatch");
}
if (dh_params->encrypted_answer_.size() & 15) {
return Status::Error("Bad padding for encrypted part");
}
- tmp_KDF(server_nonce, new_nonce, &tmp_aes_key, &tmp_aes_iv);
+ UInt256 tmp_aes_key;
+ UInt256 tmp_aes_iv;
+ tmp_KDF(server_nonce_, new_nonce_, &tmp_aes_key, &tmp_aes_iv);
auto save_tmp_aes_iv = tmp_aes_iv;
// encrypted_answer := AES256_ige_encrypt (answer_with_hash, tmp_aes_key, tmp_aes_iv);
MutableSlice answer(const_cast<char *>(dh_params->encrypted_answer_.begin()), dh_params->encrypted_answer_.size());
- aes_ige_decrypt(tmp_aes_key, &tmp_aes_iv, answer, answer);
+ aes_ige_decrypt(as_slice(tmp_aes_key), as_slice(tmp_aes_iv), answer, answer);
tmp_aes_iv = save_tmp_aes_iv;
// answer_with_hash := SHA1(answer) + answer + (0-15 random bytes)
@@ -165,91 +207,98 @@ Status AuthKeyHandshake::on_server_dh_params(Slice message, Callback *connection
size_t dh_inner_data_size = answer.size() - pad - 20;
UInt<160> answer_real_sha1;
- sha1(Slice(answer.ubegin() + 20, dh_inner_data_size), answer_real_sha1.raw);
+ sha1(answer.substr(20, dh_inner_data_size), answer_real_sha1.raw);
if (answer_sha1 != answer_real_sha1) {
return Status::Error("SHA1 mismatch");
}
- // server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:string g_a:string server_time:int =
- // Server_DH_inner_data;
- if (dh_inner_data.nonce_ != nonce) {
+ if (dh_inner_data.nonce_ != nonce_) {
return Status::Error("Nonce mismatch");
}
- if (dh_inner_data.server_nonce_ != server_nonce) {
+ if (dh_inner_data.server_nonce_ != server_nonce_) {
return Status::Error("Server nonce mismatch");
}
- server_time_diff = dh_inner_data.server_time_ - Time::now();
+ server_time_diff_ = dh_inner_data.server_time_ - Time::now();
- string g_b;
- string auth_key_str;
- TRY_STATUS(
- dh_handshake(dh_inner_data.g_, dh_inner_data.dh_prime_, dh_inner_data.g_a_, &g_b, &auth_key_str, dh_callback));
+ DhHandshake handshake;
+ handshake.set_config(dh_inner_data.g_, dh_inner_data.dh_prime_);
+ handshake.set_g_a(dh_inner_data.g_a_);
+ TRY_STATUS(handshake.run_checks(false, dh_callback));
+ string g_b = handshake.get_g_b();
+ auto auth_key_params = handshake.gen_key();
- mtproto_api::client_DH_inner_data data(nonce, server_nonce, 0, g_b);
- size_t data_size = 4 + tl_calc_length(data);
- size_t encrypted_data_size = 20 + data_size;
+ auto data = store_object(mtproto_api::client_DH_inner_data(nonce_, server_nonce_, 0, g_b));
+ size_t encrypted_data_size = 20 + data.size();
size_t encrypted_data_size_with_pad = (encrypted_data_size + 15) & -16;
- string encrypted_data_str(encrypted_data_size_with_pad, 0);
+ string encrypted_data_str(encrypted_data_size_with_pad, '\0');
MutableSlice encrypted_data = encrypted_data_str;
- as<int32>(encrypted_data.begin() + 20) = data.get_id();
- tl_store_unsafe(data, encrypted_data.begin() + 20 + 4);
- sha1(Slice(encrypted_data.ubegin() + 20, data_size), encrypted_data.ubegin());
+ sha1(data, encrypted_data.ubegin());
+ encrypted_data.substr(20, data.size()).copy_from(data);
Random::secure_bytes(encrypted_data.ubegin() + encrypted_data_size,
encrypted_data_size_with_pad - encrypted_data_size);
- tmp_KDF(server_nonce, new_nonce, &tmp_aes_key, &tmp_aes_iv);
- aes_ige_encrypt(tmp_aes_key, &tmp_aes_iv, encrypted_data, encrypted_data);
+ tmp_KDF(server_nonce_, new_nonce_, &tmp_aes_key, &tmp_aes_iv);
+ aes_ige_encrypt(as_slice(tmp_aes_key), as_slice(tmp_aes_iv), encrypted_data, encrypted_data);
- mtproto_api::set_client_DH_params set_client_dh_params(nonce, server_nonce, std::move(encrypted_data_str));
+ mtproto_api::set_client_DH_params set_client_dh_params(nonce_, server_nonce_, encrypted_data);
send(connection, create_storer(set_client_dh_params));
- auth_key = AuthKey(dh_auth_key_id(auth_key_str), std::move(auth_key_str));
+ auth_key_ = AuthKey(auth_key_params.first, std::move(auth_key_params.second));
if (mode_ == Mode::Temp) {
- auth_key.set_expire_at(expire_at_);
+ auth_key_.set_expires_at(expires_at_);
}
+ auth_key_.set_created_at(dh_inner_data.server_time_);
- server_salt = as<int64>(new_nonce.raw) ^ as<int64>(server_nonce.raw);
+ server_salt_ = as<int64>(new_nonce_.raw) ^ as<int64>(server_nonce_.raw);
state_ = DHGenResponse;
return Status::OK();
}
Status AuthKeyHandshake::on_dh_gen_response(Slice message, Callback *connection) {
- TRY_RESULT(answer, fetch_result<mtproto_api::set_client_DH_params>(message));
+ TRY_RESULT(answer, fetch_result<mtproto_api::set_client_DH_params>(message, false));
switch (answer->get_id()) {
- case mtproto_api::dh_gen_ok::ID:
+ case mtproto_api::dh_gen_ok::ID: {
+ auto dh_gen_ok = move_tl_object_as<mtproto_api::dh_gen_ok>(answer);
+ if (dh_gen_ok->nonce_ != nonce_) {
+ return Status::Error("Nonce mismatch");
+ }
+ if (dh_gen_ok->server_nonce_ != server_nonce_) {
+ return Status::Error("Server nonce mismatch");
+ }
+
+ UInt<160> auth_key_sha1;
+ sha1(auth_key_.key(), auth_key_sha1.raw);
+ auto new_nonce_hash = sha1(PSLICE() << new_nonce_.as_slice() << '\x01' << auth_key_sha1.as_slice().substr(0, 8));
+ if (dh_gen_ok->new_nonce_hash1_.as_slice() != Slice(new_nonce_hash).substr(4)) {
+ return Status::Error("New nonce hash mismatch");
+ }
state_ = Finish;
- break;
+ return Status::OK();
+ }
case mtproto_api::dh_gen_fail::ID:
return Status::Error("DhGenFail");
case mtproto_api::dh_gen_retry::ID:
return Status::Error("DhGenRetry");
default:
+ UNREACHABLE();
return Status::Error("Unknown set_client_DH_params response");
}
- return Status::OK();
}
+
void AuthKeyHandshake::send(Callback *connection, const Storer &storer) {
- auto writer = BufferWriter{storer.size(), 0, 0};
- storer.store(writer.as_slice().ubegin());
+ auto size = storer.size();
+ auto writer = BufferWriter{size, 0, 0};
+ auto real_size = storer.store(writer.as_slice().ubegin());
+ CHECK(real_size == size);
last_query_ = writer.as_buffer_slice();
return do_send(connection, create_storer(last_query_.as_slice()));
}
+
void AuthKeyHandshake::do_send(Callback *connection, const Storer &storer) {
return connection->send_no_crypto(storer);
}
-Status AuthKeyHandshake::start_main(Callback *connection) {
- mode_ = Mode::Main;
- return on_start(connection);
-}
-
-Status AuthKeyHandshake::start_tmp(Callback *connection, int32 expire_in) {
- mode_ = Mode::Temp;
- expire_in_ = expire_in;
- return on_start(connection);
-}
-
void AuthKeyHandshake::resume(Callback *connection) {
if (state_ == Start) {
return on_start(connection).ignore();
@@ -262,7 +311,7 @@ void AuthKeyHandshake::resume(Callback *connection) {
LOG(ERROR) << "Last query empty! UNREACHABLE " << state_;
return clear();
}
- LOG(INFO) << "RESUME";
+ LOG(INFO) << "Resume handshake";
do_send(connection, create_storer(last_query_.as_slice()));
}
@@ -271,14 +320,14 @@ Status AuthKeyHandshake::on_start(Callback *connection) {
clear();
return Status::Error(PSLICE() << "on_start called after start " << tag("state", state_));
}
- Random::secure_bytes(nonce.raw, sizeof(nonce));
- send(connection, create_storer(mtproto_api::req_pq_multi(nonce)));
+ Random::secure_bytes(nonce_.raw, sizeof(nonce_));
+ send(connection, create_storer(mtproto_api::req_pq_multi(nonce_)));
state_ = ResPQ;
return Status::OK();
}
-Status AuthKeyHandshake::on_message(Slice message, Callback *connection, Context *context) {
+Status AuthKeyHandshake::on_message(Slice message, Callback *connection, AuthKeyHandshakeContext *context) {
Status status = [&] {
switch (state_) {
case ResPQ:
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/Handshake.h b/protocols/Telegram/tdlib/td/td/mtproto/Handshake.h
index 8c7ae7baac..e53eda6f61 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/Handshake.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/Handshake.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,22 +7,32 @@
#pragma once
#include "td/mtproto/AuthKey.h"
-#include "td/mtproto/crypto.h"
+#include "td/mtproto/RSA.h"
#include "td/utils/buffer.h"
-#include "td/utils/int_types.h"
+#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/StorerBase.h"
+#include "td/utils/UInt.h"
namespace td {
-class Storer;
+
+namespace mtproto_api {
+class Object;
+} // namespace mtproto_api
+
namespace mtproto {
+
+class DhCallback;
+
class AuthKeyHandshakeContext {
public:
virtual ~AuthKeyHandshakeContext() = default;
virtual DhCallback *get_dh_callback() = 0;
virtual PublicRsaKeyInterface *get_public_rsa_key_interface() = 0;
};
+
class AuthKeyHandshake {
public:
class Callback {
@@ -33,70 +43,69 @@ class AuthKeyHandshake {
virtual ~Callback() = default;
virtual void send_no_crypto(const Storer &storer) = 0;
};
- using Context = AuthKeyHandshakeContext;
- enum class Mode { Unknown, Main, Temp };
- AuthKey auth_key;
- double server_time_diff = 0;
- uint64 server_salt = 0;
- bool is_ready_for_start();
- Status start_main(Callback *connection) TD_WARN_UNUSED_RESULT;
- Status start_tmp(Callback *connection, int32 expire_in) TD_WARN_UNUSED_RESULT;
+ AuthKeyHandshake(int32 dc_id, int32 expires_in);
- bool is_ready_for_message(const UInt128 &message_nonce);
+ void set_timeout_in(double timeout_in);
+
+ bool is_ready_for_finish() const;
- bool is_ready_for_finish();
void on_finish();
- explicit AuthKeyHandshake(int32 expire_in = 0) {
- if (expire_in == 0) {
- mode_ = Mode::Main;
- } else {
- mode_ = Mode::Temp;
- expire_in_ = expire_in;
- }
+ void resume(Callback *connection);
+
+ Status on_message(Slice message, Callback *connection, AuthKeyHandshakeContext *context) TD_WARN_UNUSED_RESULT;
+
+ void clear();
+
+ const AuthKey &get_auth_key() const {
+ return auth_key_;
}
- void init_main() {
- clear();
- mode_ = Mode::Main;
+
+ AuthKey release_auth_key() {
+ return std::move(auth_key_);
}
- void init_temp(int32 expire_in) {
- clear();
- mode_ = Mode::Temp;
- expire_in_ = expire_in;
+
+ double get_server_time_diff() const {
+ return server_time_diff_;
}
- void resume(Callback *connection);
- Status on_message(Slice message, Callback *connection, Context *context) TD_WARN_UNUSED_RESULT;
- bool is_ready() {
- return is_ready_for_finish();
+
+ uint64 get_server_salt() const {
+ return server_salt_;
}
- void clear();
private:
- using State = enum { Start, ResPQ, ServerDHParams, DHGenResponse, Finish };
+ enum State : int32 { Start, ResPQ, ServerDHParams, DHGenResponse, Finish };
State state_ = Start;
- Mode mode_ = Mode::Unknown;
- int32 expire_in_;
- double expire_at_ = 0;
+ enum class Mode : int32 { Main, Temp };
+ Mode mode_ = Mode::Main;
+ int32 dc_id_ = 0;
+ int32 expires_in_ = 0;
+ double expires_at_ = 0;
+
+ double start_time_ = 0;
+ double timeout_in_ = 0;
- UInt128 nonce;
- UInt128 server_nonce;
- UInt256 new_nonce;
- UInt256 tmp_aes_key;
- UInt256 tmp_aes_iv;
+ AuthKey auth_key_;
+ double server_time_diff_ = 0;
+ uint64 server_salt_ = 0;
+
+ UInt128 nonce_;
+ UInt128 server_nonce_;
+ UInt256 new_nonce_;
BufferSlice last_query_;
- template <class DataT>
- Result<size_t> fill_data_with_hash(uint8 *data_with_hash, const DataT &data) TD_WARN_UNUSED_RESULT;
+ static string store_object(const mtproto_api::Object &object);
void send(Callback *connection, const Storer &storer);
- void do_send(Callback *connection, const Storer &storer);
+ static void do_send(Callback *connection, const Storer &storer);
Status on_start(Callback *connection) TD_WARN_UNUSED_RESULT;
Status on_res_pq(Slice message, Callback *connection, PublicRsaKeyInterface *public_rsa_key) TD_WARN_UNUSED_RESULT;
Status on_server_dh_params(Slice message, Callback *connection, DhCallback *dh_callback) TD_WARN_UNUSED_RESULT;
Status on_dh_gen_response(Slice message, Callback *connection) TD_WARN_UNUSED_RESULT;
};
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.cpp b/protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.cpp
index 6eb888cd76..0921bb0a2e 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.cpp
@@ -1,40 +1,40 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/mtproto/HandshakeActor.h"
+
#include "td/mtproto/HandshakeConnection.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
namespace td {
namespace mtproto {
-HandshakeActor::HandshakeActor(std::unique_ptr<AuthKeyHandshake> handshake,
- std::unique_ptr<RawConnection> raw_connection,
- std::unique_ptr<AuthKeyHandshakeContext> context, double timeout,
- Promise<std::unique_ptr<RawConnection>> raw_connection_promise,
- Promise<std::unique_ptr<AuthKeyHandshake>> handshake_promise)
+
+HandshakeActor::HandshakeActor(unique_ptr<AuthKeyHandshake> handshake, unique_ptr<RawConnection> raw_connection,
+ unique_ptr<AuthKeyHandshakeContext> context, double timeout,
+ Promise<unique_ptr<RawConnection>> raw_connection_promise,
+ Promise<unique_ptr<AuthKeyHandshake>> handshake_promise)
: handshake_(std::move(handshake))
- , connection_(
- std::make_unique<HandshakeConnection>(std::move(raw_connection), handshake_.get(), std::move(context)))
+ , connection_(make_unique<HandshakeConnection>(std::move(raw_connection), handshake_.get(), std::move(context)))
, timeout_(timeout)
, raw_connection_promise_(std::move(raw_connection_promise))
, handshake_promise_(std::move(handshake_promise)) {
}
void HandshakeActor::close() {
- finish(Status::Error("Cancelled"));
+ finish(Status::Error("Canceled"));
stop();
}
void HandshakeActor::start_up() {
- connection_->get_pollable().set_observer(this);
- subscribe(connection_->get_pollable());
+ Scheduler::subscribe(connection_->get_poll_info().extract_pollable_fd(this));
set_timeout_in(timeout_);
+ handshake_->set_timeout_in(timeout_);
yield();
}
@@ -50,17 +50,36 @@ void HandshakeActor::loop() {
}
}
+void HandshakeActor::hangup() {
+ finish(Status::Error(1, "Canceled"));
+ stop();
+}
+
+void HandshakeActor::timeout_expired() {
+ finish(Status::Error("Timeout expired"));
+ stop();
+}
+
+void HandshakeActor::tear_down() {
+ finish(Status::OK());
+}
+
+void HandshakeActor::finish(Status status) {
+ // NB: order may be important for parent
+ return_connection(std::move(status));
+ return_handshake();
+}
+
void HandshakeActor::return_connection(Status status) {
auto raw_connection = connection_->move_as_raw_connection();
if (!raw_connection) {
CHECK(!raw_connection_promise_);
return;
}
- if (status.is_error()) {
- status = Status::Error(status.code(), PSLICE() << status.message() << " : " << raw_connection->debug_str_);
+ if (status.is_error() && !raw_connection->extra().debug_str.empty()) {
+ status = status.move_as_error_suffix(PSLICE() << " : " << raw_connection->extra().debug_str);
}
- unsubscribe(raw_connection->get_pollable());
- raw_connection->get_pollable().set_observer(nullptr);
+ Scheduler::unsubscribe(raw_connection->get_poll_info().get_pollable_fd_ref());
if (raw_connection_promise_) {
if (status.is_error()) {
if (raw_connection->stats_callback()) {
@@ -89,5 +108,6 @@ void HandshakeActor::return_handshake() {
}
handshake_promise_.set_value(std::move(handshake_));
}
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.h b/protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.h
index 296dae62ca..cab4ae54c1 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/HandshakeActor.h
@@ -1,58 +1,56 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/mtproto/Handshake.h"
+#include "td/mtproto/HandshakeConnection.h"
+#include "td/mtproto/RawConnection.h"
+
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
namespace td {
-class DhCallback;
namespace mtproto {
-class AuthKeyHandshake;
-class AuthKeyHandshakeContext;
-class RawConnection;
-class HandshakeConnection;
-// Has Raw connection. Generates new auth key. And returns it and raw_connection. Or error...
-class HandshakeActor : public Actor {
+
+// Owns RawConnection. Generates new auth key. And returns it and RawConnection. Or error...
+class HandshakeActor final : public Actor {
public:
- HandshakeActor(std::unique_ptr<AuthKeyHandshake> handshake, std::unique_ptr<RawConnection> raw_connection,
- std::unique_ptr<AuthKeyHandshakeContext> context, double timeout,
- Promise<std::unique_ptr<RawConnection>> raw_connection_promise,
- Promise<std::unique_ptr<AuthKeyHandshake>> handshake_promise);
+ HandshakeActor(unique_ptr<AuthKeyHandshake> handshake, unique_ptr<RawConnection> raw_connection,
+ unique_ptr<AuthKeyHandshakeContext> context, double timeout,
+ Promise<unique_ptr<RawConnection>> raw_connection_promise,
+ Promise<unique_ptr<AuthKeyHandshake>> handshake_promise);
void close();
private:
- std::unique_ptr<AuthKeyHandshake> handshake_;
- std::unique_ptr<HandshakeConnection> connection_;
+ unique_ptr<AuthKeyHandshake> handshake_;
+ unique_ptr<HandshakeConnection> connection_;
double timeout_;
- Promise<std::unique_ptr<RawConnection>> raw_connection_promise_;
- Promise<std::unique_ptr<AuthKeyHandshake>> handshake_promise_;
-
- void start_up() override;
- void tear_down() override {
- finish(Status::OK());
- }
- void timeout_expired() override {
- finish(Status::Error("Timeout expired"));
- stop();
- }
- void loop() override;
-
- void finish(Status status) {
- // NB: order may be important for parent
- return_connection(std::move(status));
- return_handshake();
- }
+ Promise<unique_ptr<RawConnection>> raw_connection_promise_;
+ Promise<unique_ptr<AuthKeyHandshake>> handshake_promise_;
+
+ void start_up() final;
+
+ void tear_down() final;
+
+ void hangup() final;
+
+ void timeout_expired() final;
+
+ void loop() final;
+
+ void finish(Status status);
void return_connection(Status status);
+
void return_handshake();
};
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/HandshakeConnection.h b/protocols/Telegram/tdlib/td/td/mtproto/HandshakeConnection.h
index 5c9341ddc9..b81df49a4f 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/HandshakeConnection.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/HandshakeConnection.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,34 +9,36 @@
#include "td/mtproto/AuthKey.h"
#include "td/mtproto/Handshake.h"
#include "td/mtproto/NoCryptoStorer.h"
+#include "td/mtproto/PacketInfo.h"
+#include "td/mtproto/PacketStorer.h"
#include "td/mtproto/RawConnection.h"
-#include "td/mtproto/Transport.h"
-#include "td/mtproto/utils.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/Status.h"
+#include "td/utils/StorerBase.h"
namespace td {
namespace mtproto {
-class HandshakeConnection
+
+class HandshakeConnection final
: private RawConnection::Callback
, private AuthKeyHandshake::Callback {
public:
- HandshakeConnection(std::unique_ptr<RawConnection> raw_connection, AuthKeyHandshake *handshake,
- std::unique_ptr<AuthKeyHandshakeContext> context)
+ HandshakeConnection(unique_ptr<RawConnection> raw_connection, AuthKeyHandshake *handshake,
+ unique_ptr<AuthKeyHandshakeContext> context)
: raw_connection_(std::move(raw_connection)), handshake_(handshake), context_(std::move(context)) {
handshake_->resume(this);
}
- Fd &get_pollable() {
- return raw_connection_->get_pollable();
+ PollableFdInfo &get_poll_info() {
+ return raw_connection_->get_poll_info();
}
- std::unique_ptr<RawConnection> move_as_raw_connection() {
+ unique_ptr<RawConnection> move_as_raw_connection() {
return std::move(raw_connection_);
}
@@ -54,16 +56,16 @@ class HandshakeConnection
}
private:
- std::unique_ptr<RawConnection> raw_connection_;
+ unique_ptr<RawConnection> raw_connection_;
AuthKeyHandshake *handshake_;
- std::unique_ptr<AuthKeyHandshakeContext> context_;
+ unique_ptr<AuthKeyHandshakeContext> context_;
- void send_no_crypto(const Storer &storer) override {
+ void send_no_crypto(const Storer &storer) final {
raw_connection_->send_no_crypto(PacketStorer<NoCryptoImpl>(0, storer));
}
- Status on_raw_packet(const PacketInfo &packet_info, BufferSlice packet) override {
- if (packet_info.no_crypto_flag == false) {
+ Status on_raw_packet(const PacketInfo &packet_info, BufferSlice packet) final {
+ if (!packet_info.no_crypto_flag) {
return Status::Error("Expected not encrypted packet");
}
@@ -73,9 +75,11 @@ class HandshakeConnection
}
packet.confirm_read(12);
- TRY_STATUS(handshake_->on_message(packet.as_slice(), this, context_.get()));
+ auto fixed_packet_size = packet.size() & ~3; // remove some padded data
+ TRY_STATUS(handshake_->on_message(packet.as_slice().truncate(fixed_packet_size), this, context_.get()));
return Status::OK();
}
};
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.cpp b/protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.cpp
index 2af6624e21..4052b6192f 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,13 +9,16 @@
#include "td/net/HttpHeaderCreator.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
// TODO: do I need \r\n as delimiter?
-#include <cstring>
+#include <tuple>
namespace td {
namespace mtproto {
@@ -27,8 +30,8 @@ Result<size_t> Transport::read_next(BufferSlice *message, uint32 *quick_ack) {
if (r_size.is_error() || r_size.ok() != 0) {
return r_size;
}
- if (http_query_.type_ != HttpQuery::Type::RESPONSE) {
- return Status::Error("Unexpected http query type");
+ if (http_query_.type_ != HttpQuery::Type::Response) {
+ return Status::Error("Unexpected HTTP query type");
}
if (http_query_.container_.size() != 2u) {
return Status::Error("Wrong response");
@@ -47,18 +50,30 @@ void Transport::write(BufferWriter &&message, bool quick_ack) {
* Host: url
*/
HttpHeaderCreator hc;
- hc.init_post("/api");
- hc.add_header("Host", "");
- hc.set_keep_alive();
+ Slice host;
+ Slice proxy_authorizarion;
+ std::tie(host, proxy_authorizarion) = split(Slice(secret_), '|');
+ if (host.empty()) {
+ hc.init_post("/api");
+ hc.add_header("Host", "");
+ hc.set_keep_alive();
+ } else {
+ hc.init_post(PSLICE() << "HTTP://" << host << ":80/api");
+ hc.add_header("Host", host);
+ hc.add_header("User-Agent", "curl/7.35.0");
+ hc.add_header("Accept", "*/*");
+ hc.add_header("Proxy-Connection", "keep-alive");
+ if (!proxy_authorizarion.empty()) {
+ hc.add_header("Proxy-Authorization", proxy_authorizarion);
+ }
+ }
hc.set_content_size(message.size());
auto r_head = hc.finish();
- if (r_head.is_error()) {
- UNREACHABLE();
- }
+ CHECK(r_head.is_ok());
Slice src = r_head.ok();
+ // LOG(DEBUG) << src;
MutableSlice dst = message.prepare_prepend();
- CHECK(dst.size() >= src.size()) << dst.size() << " >= " << src.size();
- std::memcpy(dst.end() - src.size(), src.begin(), src.size());
+ dst.substr(dst.size() - src.size()).copy_from(src);
message.confirm_prepend(src.size());
output_->append(message.as_buffer_slice());
turn_ = Read;
@@ -73,7 +88,19 @@ bool Transport::can_write() const {
}
size_t Transport::max_prepend_size() const {
- return MAX_PREPEND_SIZE;
+ if (secret_.empty()) {
+ return 96;
+ } else {
+ return (secret_.size() + 1) / 2 * 4 + 156;
+ }
+}
+
+size_t Transport::max_append_size() const {
+ return 0;
+}
+
+bool Transport::use_random_padding() const {
+ return false;
}
} // namespace http
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.h b/protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.h
index 8e3ed41a7b..ea8d5a90f8 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/HttpTransport.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,43 +7,50 @@
#pragma once
#include "td/mtproto/IStreamTransport.h"
+#include "td/mtproto/ProxySecret.h"
+#include "td/mtproto/TransportType.h"
#include "td/net/HttpQuery.h"
#include "td/net/HttpReader.h"
#include "td/utils/buffer.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/Status.h"
namespace td {
namespace mtproto {
namespace http {
-class Transport : public IStreamTransport {
+
+class Transport final : public IStreamTransport {
public:
- Result<size_t> read_next(BufferSlice *message, uint32 *quick_ack) override TD_WARN_UNUSED_RESULT;
- bool support_quick_ack() const override {
+ explicit Transport(string secret) : secret_(std::move(secret)) {
+ }
+
+ Result<size_t> read_next(BufferSlice *message, uint32 *quick_ack) final TD_WARN_UNUSED_RESULT;
+ bool support_quick_ack() const final {
return false;
}
- void write(BufferWriter &&message, bool quick_ack) override;
- bool can_read() const override;
- bool can_write() const override;
- void init(ChainBufferReader *input, ChainBufferWriter *output) override {
+ void write(BufferWriter &&message, bool quick_ack) final;
+ bool can_read() const final;
+ bool can_write() const final;
+ void init(ChainBufferReader *input, ChainBufferWriter *output) final {
reader_.init(input);
output_ = output;
}
- size_t max_prepend_size() const override;
- TransportType get_type() const override {
- return TransportType::Http;
+ size_t max_prepend_size() const final;
+ size_t max_append_size() const final;
+ TransportType get_type() const final {
+ return {TransportType::Http, 0, ProxySecret::from_raw(secret_)};
}
+ bool use_random_padding() const final;
private:
+ string secret_;
HttpReader reader_;
HttpQuery http_query_;
- ChainBufferWriter *output_;
+ ChainBufferWriter *output_ = nullptr;
enum { Write, Read } turn_ = Write;
-
- enum { MAX_PREPEND_SIZE = 96 };
};
} // namespace http
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.cpp b/protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.cpp
index 8440108c71..b9f1754f3a 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,20 +9,20 @@
#include "td/mtproto/HttpTransport.h"
#include "td/mtproto/TcpTransport.h"
-#include "td/utils/logging.h"
-
namespace td {
namespace mtproto {
-std::unique_ptr<IStreamTransport> create_transport(TransportType type) {
- switch (type) {
+
+unique_ptr<IStreamTransport> create_transport(TransportType type) {
+ switch (type.type) {
case TransportType::ObfuscatedTcp:
- return std::make_unique<tcp::ObfuscatedTransport>();
+ return td::make_unique<tcp::ObfuscatedTransport>(type.dc_id, std::move(type.secret));
case TransportType::Tcp:
- return std::make_unique<tcp::OldTransport>();
+ return td::make_unique<tcp::OldTransport>();
case TransportType::Http:
- return std::make_unique<http::Transport>();
+ return td::make_unique<http::Transport>(type.secret.get_raw_secret().str());
}
UNREACHABLE();
}
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.h b/protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.h
index 6796d457fc..93f81f7aeb 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/IStreamTransport.h
@@ -1,18 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/mtproto/TransportType.h"
+
#include "td/utils/buffer.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/common.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/Status.h"
namespace td {
namespace mtproto {
-enum class TransportType { Tcp, ObfuscatedTcp, Http };
+
class IStreamTransport {
public:
IStreamTransport() = default;
@@ -26,9 +29,12 @@ class IStreamTransport {
virtual bool can_write() const = 0;
virtual void init(ChainBufferReader *input, ChainBufferWriter *output) = 0;
virtual size_t max_prepend_size() const = 0;
+ virtual size_t max_append_size() const = 0;
virtual TransportType get_type() const = 0;
+ virtual bool use_random_padding() const = 0;
};
-std::unique_ptr<IStreamTransport> create_transport(TransportType type);
+unique_ptr<IStreamTransport> create_transport(TransportType type);
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/KDF.cpp b/protocols/Telegram/tdlib/td/td/mtproto/KDF.cpp
new file mode 100644
index 0000000000..f99947c9d2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/KDF.cpp
@@ -0,0 +1,107 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/KDF.h"
+
+#include "td/utils/as.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/logging.h"
+
+namespace td {
+namespace mtproto {
+
+void KDF(Slice auth_key, const UInt128 &msg_key, int X, UInt256 *aes_key, UInt256 *aes_iv) {
+ LOG_CHECK(auth_key.size() == 2048 / 8) << auth_key.size();
+ const char *auth_key_raw = auth_key.data();
+ uint8 buf[48];
+ as<UInt128>(buf) = msg_key;
+ as<UInt256>(buf + 16) = as<UInt256>(auth_key_raw + X);
+ uint8 sha1_a[20];
+ sha1(Slice(buf, 48), sha1_a);
+
+ as<UInt128>(buf) = as<UInt128>(auth_key_raw + X + 32);
+ as<UInt128>(buf + 16) = msg_key;
+ as<UInt128>(buf + 32) = as<UInt128>(auth_key_raw + X + 48);
+ uint8 sha1_b[20];
+ sha1(Slice(buf, 48), sha1_b);
+
+ as<UInt256>(buf) = as<UInt256>(auth_key_raw + 64 + X);
+ as<UInt128>(buf + 32) = msg_key;
+ uint8 sha1_c[20];
+ sha1(Slice(buf, 48), sha1_c);
+
+ as<UInt128>(buf) = msg_key;
+ as<UInt256>(buf + 16) = as<UInt256>(auth_key_raw + 96 + X);
+ uint8 sha1_d[20];
+ sha1(Slice(buf, 48), sha1_d);
+
+ as<uint64>(aes_key->raw) = as<uint64>(sha1_a);
+ as<UInt<96>>(aes_key->raw + 8) = as<UInt<96>>(sha1_b + 8);
+ as<UInt<96>>(aes_key->raw + 20) = as<UInt<96>>(sha1_c + 4);
+
+ as<UInt<96>>(aes_iv->raw) = as<UInt<96>>(sha1_a + 8);
+ as<uint64>(aes_iv->raw + 12) = as<uint64>(sha1_b);
+ as<uint32>(aes_iv->raw + 20) = as<uint32>(sha1_c + 16);
+ as<uint64>(aes_iv->raw + 24) = as<uint64>(sha1_d);
+}
+
+void tmp_KDF(const UInt128 &server_nonce, const UInt256 &new_nonce, UInt256 *tmp_aes_key, UInt256 *tmp_aes_iv) {
+ // tmp_aes_key := SHA1(new_nonce + server_nonce) + substr(SHA1(server_nonce + new_nonce), 0, 12);
+ uint8 buf[512 / 8];
+ as<UInt256>(buf) = new_nonce;
+ as<UInt128>(buf + 32) = server_nonce;
+ sha1(Slice(buf, 48), tmp_aes_key->raw);
+
+ as<UInt128>(buf) = server_nonce;
+ as<UInt256>(buf + 16) = new_nonce;
+ uint8 sha1_server_new[20];
+ sha1(Slice(buf, 48), sha1_server_new);
+ as<UInt<96>>(tmp_aes_key->raw + 20) = as<UInt<96>>(sha1_server_new);
+
+ // tmp_aes_iv := substr(SHA1(server_nonce + new_nonce), 12, 8) + SHA1(new_nonce + new_nonce) + substr(new_nonce, 0, 4)
+ as<uint64>(tmp_aes_iv->raw) = as<uint64>(sha1_server_new + 12);
+
+ as<UInt256>(buf) = new_nonce;
+ as<UInt256>(buf + 32) = new_nonce;
+ sha1(Slice(buf, 64), tmp_aes_iv->raw + 8);
+ as<uint32>(tmp_aes_iv->raw + 28) = as<uint32>(new_nonce.raw);
+}
+
+void KDF2(Slice auth_key, const UInt128 &msg_key, int X, UInt256 *aes_key, UInt256 *aes_iv) {
+ uint8 buf_raw[36 + 16];
+ MutableSlice buf(buf_raw, 36 + 16);
+ Slice msg_key_slice = as_slice(msg_key);
+
+ // sha256_a = SHA256 (msg_key + substr(auth_key, x, 36));
+ buf.copy_from(msg_key_slice);
+ buf.substr(16, 36).copy_from(auth_key.substr(X, 36));
+ uint8 sha256_a_raw[32];
+ MutableSlice sha256_a(sha256_a_raw, 32);
+ sha256(buf, sha256_a);
+
+ // sha256_b = SHA256 (substr(auth_key, 40+x, 36) + msg_key);
+ buf.copy_from(auth_key.substr(40 + X, 36));
+ buf.substr(36).copy_from(msg_key_slice);
+ uint8 sha256_b_raw[32];
+ MutableSlice sha256_b(sha256_b_raw, 32);
+ sha256(buf, sha256_b);
+
+ // aes_key = substr(sha256_a, 0, 8) + substr(sha256_b, 8, 16) + substr(sha256_a, 24, 8);
+ MutableSlice aes_key_slice(aes_key->raw, sizeof(aes_key->raw));
+ aes_key_slice.copy_from(sha256_a.substr(0, 8));
+ aes_key_slice.substr(8).copy_from(sha256_b.substr(8, 16));
+ aes_key_slice.substr(24).copy_from(sha256_a.substr(24, 8));
+
+ // aes_iv = substr(sha256_b, 0, 8) + substr(sha256_a, 8, 16) + substr(sha256_b, 24, 8);
+ MutableSlice aes_iv_slice(aes_iv->raw, sizeof(aes_iv->raw));
+ aes_iv_slice.copy_from(sha256_b.substr(0, 8));
+ aes_iv_slice.substr(8).copy_from(sha256_a.substr(8, 16));
+ aes_iv_slice.substr(24).copy_from(sha256_b.substr(24, 8));
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/KDF.h b/protocols/Telegram/tdlib/td/td/mtproto/KDF.h
new file mode 100644
index 0000000000..8f0ab1aff2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/KDF.h
@@ -0,0 +1,22 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/Slice.h"
+#include "td/utils/UInt.h"
+
+namespace td {
+namespace mtproto {
+
+void KDF(Slice auth_key, const UInt128 &msg_key, int X, UInt256 *aes_key, UInt256 *aes_iv);
+
+void tmp_KDF(const UInt128 &server_nonce, const UInt256 &new_nonce, UInt256 *tmp_aes_key, UInt256 *tmp_aes_iv);
+
+void KDF2(Slice auth_key, const UInt128 &msg_key, int X, UInt256 *aes_key, UInt256 *aes_iv);
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/MtprotoQuery.h b/protocols/Telegram/tdlib/td/td/mtproto/MtprotoQuery.h
new file mode 100644
index 0000000000..b04c3c61e1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/MtprotoQuery.h
@@ -0,0 +1,25 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+
+namespace td {
+namespace mtproto {
+
+struct MtprotoQuery {
+ int64 message_id;
+ int32 seq_no;
+ BufferSlice packet;
+ bool gzip_flag;
+ std::vector<uint64> invoke_after_ids;
+ bool use_quick_ack;
+};
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/NoCryptoStorer.h b/protocols/Telegram/tdlib/td/td/mtproto/NoCryptoStorer.h
index 2dc92abf24..dbfa71bd35 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/NoCryptoStorer.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/NoCryptoStorer.h
@@ -1,28 +1,41 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/mtproto/PacketStorer.h"
+
+#include "td/utils/Random.h"
+#include "td/utils/StorerBase.h"
namespace td {
namespace mtproto {
+
class NoCryptoImpl {
public:
- NoCryptoImpl(uint64 message_id, const Storer &data) : message_id(message_id), data(data) {
+ NoCryptoImpl(uint64 message_id, const Storer &data, bool need_pad = true) : message_id_(message_id), data_(data) {
+ if (need_pad) {
+ size_t pad_size = -static_cast<int>(data_.size()) & 15;
+ pad_size += 16 * (static_cast<size_t>(Random::secure_int32()) % 16);
+ pad_.resize(pad_size);
+ Random::secure_bytes(pad_);
+ }
}
- template <class T>
- void do_store(T &storer) const {
- storer.store_binary(message_id);
- storer.store_binary(static_cast<int32>(data.size()));
- storer.store_storer(data);
+
+ template <class StorerT>
+ void do_store(StorerT &storer) const {
+ storer.store_binary(message_id_);
+ storer.store_binary(static_cast<int32>(data_.size() + pad_.size()));
+ storer.store_storer(data_);
+ storer.store_slice(pad_);
}
private:
- uint64 message_id;
- const Storer &data;
+ uint64 message_id_;
+ const Storer &data_;
+ std::string pad_;
};
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/PacketInfo.h b/protocols/Telegram/tdlib/td/td/mtproto/PacketInfo.h
new file mode 100644
index 0000000000..8de096b19e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/PacketInfo.h
@@ -0,0 +1,35 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/UInt.h"
+
+namespace td {
+namespace mtproto {
+
+struct PacketInfo {
+ enum { Common, EndToEnd } type = Common;
+ uint64 auth_key_id{0};
+ uint32 message_ack{0};
+ UInt128 message_key;
+
+ uint64 salt{0};
+ uint64 session_id{0};
+
+ uint64 message_id{0};
+ int32 seq_no{0};
+ int32 version{1};
+ bool no_crypto_flag{false};
+ bool is_creator{false};
+ bool check_mod4{true};
+ bool use_random_padding{false};
+ uint32 size{0};
+};
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/PacketStorer.h b/protocols/Telegram/tdlib/td/td/mtproto/PacketStorer.h
index 63c0322ef2..934eccce3b 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/PacketStorer.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/PacketStorer.h
@@ -1,12 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/Storer.h"
+#include "td/utils/StorerBase.h"
#include "td/utils/tl_storers.h"
#include <limits>
@@ -15,11 +15,11 @@ namespace td {
namespace mtproto {
template <class Impl>
-class PacketStorer
+class PacketStorer final
: public Storer
, public Impl {
public:
- size_t size() const override {
+ size_t size() const final {
if (size_ != std::numeric_limits<size_t>::max()) {
return size_;
}
@@ -28,11 +28,10 @@ class PacketStorer
return size_ = storer.get_length();
}
- size_t store(uint8 *ptr) const override {
- char *start = reinterpret_cast<char *>(ptr);
- TlStorerUnsafe storer(start);
+ size_t store(uint8 *ptr) const final {
+ TlStorerUnsafe storer(ptr);
this->do_store(storer);
- return storer.get_buf() - start;
+ return static_cast<size_t>(storer.get_buf() - ptr);
}
using Impl::Impl;
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/Ping.cpp b/protocols/Telegram/tdlib/td/td/mtproto/Ping.cpp
new file mode 100644
index 0000000000..c8e2a1a6c2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/Ping.cpp
@@ -0,0 +1,104 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/Ping.h"
+
+#include "td/mtproto/AuthData.h"
+#include "td/mtproto/PingConnection.h"
+#include "td/mtproto/RawConnection.h"
+
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+
+namespace td {
+namespace mtproto {
+
+ActorOwn<> create_ping_actor(Slice actor_name, unique_ptr<RawConnection> raw_connection, unique_ptr<AuthData> auth_data,
+ Promise<unique_ptr<RawConnection>> promise, ActorShared<> parent) {
+ class PingActor final : public Actor {
+ public:
+ PingActor(unique_ptr<RawConnection> raw_connection, unique_ptr<AuthData> auth_data,
+ Promise<unique_ptr<RawConnection>> promise, ActorShared<> parent)
+ : promise_(std::move(promise)), parent_(std::move(parent)) {
+ if (auth_data) {
+ ping_connection_ = PingConnection::create_ping_pong(std::move(raw_connection), std::move(auth_data));
+ } else {
+ ping_connection_ = PingConnection::create_req_pq(std::move(raw_connection), 2);
+ }
+ }
+
+ private:
+ unique_ptr<PingConnection> ping_connection_;
+ Promise<unique_ptr<RawConnection>> promise_;
+ ActorShared<> parent_;
+
+ void start_up() final {
+ Scheduler::subscribe(ping_connection_->get_poll_info().extract_pollable_fd(this));
+ set_timeout_in(10);
+ yield();
+ }
+
+ void hangup() final {
+ finish(Status::Error("Canceled"));
+ stop();
+ }
+
+ void tear_down() final {
+ finish(Status::OK());
+ }
+
+ void loop() final {
+ auto status = ping_connection_->flush();
+ if (status.is_error()) {
+ finish(std::move(status));
+ return stop();
+ }
+ if (ping_connection_->was_pong()) {
+ finish(Status::OK());
+ return stop();
+ }
+ }
+
+ void timeout_expired() final {
+ finish(Status::Error("Pong timeout expired"));
+ stop();
+ }
+
+ void finish(Status status) {
+ auto raw_connection = ping_connection_->move_as_raw_connection();
+ if (!raw_connection) {
+ CHECK(!promise_);
+ return;
+ }
+ Scheduler::unsubscribe(raw_connection->get_poll_info().get_pollable_fd_ref());
+ if (promise_) {
+ if (status.is_error()) {
+ if (raw_connection->stats_callback()) {
+ raw_connection->stats_callback()->on_error();
+ }
+ raw_connection->close();
+ promise_.set_error(std::move(status));
+ } else {
+ raw_connection->extra().rtt = ping_connection_->rtt();
+ if (raw_connection->stats_callback()) {
+ raw_connection->stats_callback()->on_pong();
+ }
+ promise_.set_value(std::move(raw_connection));
+ }
+ } else {
+ if (raw_connection->stats_callback()) {
+ raw_connection->stats_callback()->on_error();
+ }
+ raw_connection->close();
+ }
+ }
+ };
+ return ActorOwn<>(create_actor<PingActor>(PSLICE() << "PingActor<" << actor_name << ">", std::move(raw_connection),
+ std::move(auth_data), std::move(promise), std::move(parent)));
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/Ping.h b/protocols/Telegram/tdlib/td/td/mtproto/Ping.h
new file mode 100644
index 0000000000..09bc356100
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/Ping.h
@@ -0,0 +1,25 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/mtproto/AuthData.h"
+#include "td/mtproto/RawConnection.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+namespace mtproto {
+
+ActorOwn<> create_ping_actor(Slice actor_name, unique_ptr<RawConnection> raw_connection, unique_ptr<AuthData> auth_data,
+ Promise<unique_ptr<RawConnection>> promise, ActorShared<> parent);
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/PingConnection.cpp b/protocols/Telegram/tdlib/td/td/mtproto/PingConnection.cpp
new file mode 100644
index 0000000000..df6ec24bad
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/PingConnection.cpp
@@ -0,0 +1,201 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/PingConnection.h"
+
+#include "td/mtproto/AuthData.h"
+#include "td/mtproto/AuthKey.h"
+#include "td/mtproto/mtproto_api.h"
+#include "td/mtproto/NoCryptoStorer.h"
+#include "td/mtproto/PacketInfo.h"
+#include "td/mtproto/PacketStorer.h"
+#include "td/mtproto/PingConnection.h"
+#include "td/mtproto/RawConnection.h"
+#include "td/mtproto/SessionConnection.h"
+#include "td/mtproto/utils.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/Random.h"
+#include "td/utils/Time.h"
+#include "td/utils/UInt.h"
+
+namespace td {
+namespace mtproto {
+namespace detail {
+
+class PingConnectionReqPQ final
+ : public PingConnection
+ , private RawConnection::Callback {
+ public:
+ PingConnectionReqPQ(unique_ptr<RawConnection> raw_connection, size_t ping_count)
+ : raw_connection_(std::move(raw_connection)), ping_count_(ping_count) {
+ }
+
+ PollableFdInfo &get_poll_info() final {
+ return raw_connection_->get_poll_info();
+ }
+
+ unique_ptr<RawConnection> move_as_raw_connection() final {
+ return std::move(raw_connection_);
+ }
+
+ Status flush() final {
+ if (!was_ping_) {
+ UInt128 nonce;
+ Random::secure_bytes(nonce.raw, sizeof(nonce));
+ raw_connection_->send_no_crypto(PacketStorer<NoCryptoImpl>(1, create_storer(mtproto_api::req_pq_multi(nonce))));
+ was_ping_ = true;
+ if (ping_count_ == 1) {
+ start_time_ = Time::now();
+ }
+ }
+ return raw_connection_->flush(AuthKey(), *this);
+ }
+ bool was_pong() const final {
+ return finish_time_ > 0;
+ }
+ double rtt() const final {
+ return finish_time_ - start_time_;
+ }
+
+ Status on_raw_packet(const PacketInfo &packet_info, BufferSlice packet) final {
+ if (packet.size() < 12) {
+ return Status::Error("Result is too small");
+ }
+ packet.confirm_read(12);
+ // TODO: fetch_result
+
+ if (--ping_count_ > 0) {
+ was_ping_ = false;
+ return flush();
+ } else {
+ finish_time_ = Time::now();
+ return Status::OK();
+ }
+ }
+
+ private:
+ unique_ptr<RawConnection> raw_connection_;
+ size_t ping_count_ = 1;
+ double start_time_ = 0.0;
+ double finish_time_ = 0.0;
+ bool was_ping_ = false;
+};
+
+class PingConnectionPingPong final
+ : public PingConnection
+ , private SessionConnection::Callback {
+ public:
+ PingConnectionPingPong(unique_ptr<RawConnection> raw_connection, unique_ptr<AuthData> auth_data)
+ : auth_data_(std::move(auth_data)) {
+ auth_data_->set_header("");
+ auth_data_->clear_seq_no();
+ connection_ =
+ make_unique<SessionConnection>(SessionConnection::Mode::Tcp, std::move(raw_connection), auth_data_.get());
+ }
+
+ private:
+ unique_ptr<AuthData> auth_data_;
+ unique_ptr<SessionConnection> connection_;
+ int pong_cnt_{0};
+ double rtt_;
+ bool is_closed_{false};
+ Status status_;
+ void on_connected() final {
+ }
+ void on_closed(Status status) final {
+ is_closed_ = true;
+ CHECK(status.is_error());
+ status_ = std::move(status);
+ }
+
+ void on_auth_key_updated() final {
+ }
+ void on_tmp_auth_key_updated() final {
+ }
+ void on_server_salt_updated() final {
+ }
+ void on_server_time_difference_updated() final {
+ }
+
+ void on_session_created(uint64 unique_id, uint64 first_id) final {
+ }
+ void on_session_failed(Status status) final {
+ }
+
+ void on_container_sent(uint64 container_id, vector<uint64> msgs_id) final {
+ }
+ Status on_pong() final {
+ pong_cnt_++;
+ if (pong_cnt_ == 1) {
+ rtt_ = Time::now();
+ connection_->set_online(false, false);
+ } else if (pong_cnt_ == 2) {
+ rtt_ = Time::now() - rtt_;
+ }
+ return Status::OK();
+ }
+
+ Status on_update(BufferSlice packet) final {
+ return Status::OK();
+ }
+ void on_message_ack(uint64 id) final {
+ }
+ Status on_message_result_ok(uint64 id, BufferSlice packet, size_t original_size) final {
+ LOG(ERROR) << "Unexpected message";
+ return Status::OK();
+ }
+ void on_message_result_error(uint64 id, int code, string message) final {
+ }
+ void on_message_failed(uint64 id, Status status) final {
+ }
+ void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size) final {
+ }
+
+ Status on_destroy_auth_key() final {
+ LOG(ERROR) << "Destroy auth key";
+ return Status::OK();
+ }
+ PollableFdInfo &get_poll_info() final {
+ return connection_->get_poll_info();
+ }
+ unique_ptr<RawConnection> move_as_raw_connection() final {
+ return connection_->move_as_raw_connection();
+ }
+ Status flush() final {
+ if (was_pong()) {
+ return Status::OK();
+ }
+ CHECK(!is_closed_);
+ connection_->flush(this);
+ if (is_closed_) {
+ CHECK(status_.is_error());
+ return std::move(status_);
+ }
+ return Status::OK();
+ }
+ bool was_pong() const final {
+ return pong_cnt_ >= 2;
+ }
+ double rtt() const final {
+ return rtt_;
+ }
+};
+
+} // namespace detail
+
+unique_ptr<PingConnection> PingConnection::create_req_pq(unique_ptr<RawConnection> raw_connection, size_t ping_count) {
+ return make_unique<detail::PingConnectionReqPQ>(std::move(raw_connection), ping_count);
+}
+
+unique_ptr<PingConnection> PingConnection::create_ping_pong(unique_ptr<RawConnection> raw_connection,
+ unique_ptr<AuthData> auth_data) {
+ return make_unique<detail::PingConnectionPingPong>(std::move(raw_connection), std::move(auth_data));
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/PingConnection.h b/protocols/Telegram/tdlib/td/td/mtproto/PingConnection.h
index b726e48d60..2165bbd184 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/PingConnection.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/PingConnection.h
@@ -1,69 +1,34 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/mtproto/AuthKey.h"
-#include "td/mtproto/NoCryptoStorer.h"
+#include "td/mtproto/AuthData.h"
#include "td/mtproto/RawConnection.h"
-#include "td/mtproto/utils.h"
-#include "td/utils/buffer.h"
-#include "td/utils/port/Fd.h"
-#include "td/utils/Random.h"
+#include "td/utils/common.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/Status.h"
-#include "td/mtproto/mtproto_api.h"
-
namespace td {
namespace mtproto {
-class PingConnection : private RawConnection::Callback {
- public:
- explicit PingConnection(std::unique_ptr<RawConnection> raw_connection) : raw_connection_(std::move(raw_connection)) {
- }
-
- Fd &get_pollable() {
- return raw_connection_->get_pollable();
- }
-
- std::unique_ptr<RawConnection> move_as_raw_connection() {
- return std::move(raw_connection_);
- }
-
- void close() {
- raw_connection_->close();
- }
- Status flush() {
- if (!was_ping_) {
- UInt128 nonce;
- Random::secure_bytes(nonce.raw, sizeof(nonce));
- raw_connection_->send_no_crypto(PacketStorer<NoCryptoImpl>(1, create_storer(mtproto_api::req_pq_multi(nonce))));
- was_ping_ = true;
- }
- return raw_connection_->flush(AuthKey(), *this);
- }
- bool was_pong() const {
- return was_pong_;
- }
-
- Status on_raw_packet(const PacketInfo &packet_info, BufferSlice packet) override {
- if (packet.size() < 12) {
- return Status::Error("Result is too small");
- }
- packet.confirm_read(12);
- // TODO: fetch_result
- was_pong_ = true;
- return Status::OK();
- }
-
- private:
- std::unique_ptr<RawConnection> raw_connection_;
- bool was_ping_ = false;
- bool was_pong_ = false;
+class PingConnection {
+ public:
+ virtual ~PingConnection() = default;
+ virtual PollableFdInfo &get_poll_info() = 0;
+ virtual unique_ptr<RawConnection> move_as_raw_connection() = 0;
+ virtual Status flush() = 0;
+ virtual bool was_pong() const = 0;
+ virtual double rtt() const = 0;
+
+ static unique_ptr<PingConnection> create_req_pq(unique_ptr<RawConnection> raw_connection, size_t ping_count);
+ static unique_ptr<PingConnection> create_ping_pong(unique_ptr<RawConnection> raw_connection,
+ unique_ptr<AuthData> auth_data);
};
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/ProxySecret.cpp b/protocols/Telegram/tdlib/td/td/mtproto/ProxySecret.cpp
new file mode 100644
index 0000000000..f159cd6081
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/ProxySecret.cpp
@@ -0,0 +1,53 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/ProxySecret.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/misc.h"
+
+namespace td {
+namespace mtproto {
+
+Result<ProxySecret> ProxySecret::from_link(Slice encoded_secret, bool truncate_if_needed) {
+ auto r_decoded = hex_decode(encoded_secret);
+ if (r_decoded.is_error()) {
+ r_decoded = base64url_decode(encoded_secret);
+ }
+ if (r_decoded.is_error()) {
+ return Status::Error(400, "Wrong proxy secret");
+ }
+ return from_binary(r_decoded.ok(), truncate_if_needed);
+}
+
+Result<ProxySecret> ProxySecret::from_binary(Slice raw_unchecked_secret, bool truncate_if_needed) {
+ if (raw_unchecked_secret.size() > 17 + MAX_DOMAIN_LENGTH) {
+ if (truncate_if_needed) {
+ raw_unchecked_secret.truncate(17 + MAX_DOMAIN_LENGTH);
+ } else {
+ return Status::Error(400, "Too long secret");
+ }
+ }
+ if (raw_unchecked_secret.size() == 16 ||
+ (raw_unchecked_secret.size() == 17 && static_cast<unsigned char>(raw_unchecked_secret[0]) == 0xdd) ||
+ (raw_unchecked_secret.size() >= 18 && static_cast<unsigned char>(raw_unchecked_secret[0]) == 0xee)) {
+ return from_raw(raw_unchecked_secret);
+ }
+ if (raw_unchecked_secret.size() < 16) {
+ return Status::Error(400, "Wrong proxy secret");
+ }
+ return Status::Error(400, "Unsupported proxy secret");
+}
+
+string ProxySecret::get_encoded_secret() const {
+ if (emulate_tls()) {
+ return base64url_encode(secret_);
+ }
+ return hex_encode(secret_);
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/ProxySecret.h b/protocols/Telegram/tdlib/td/td/mtproto/ProxySecret.h
new file mode 100644
index 0000000000..3dc476a8b2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/ProxySecret.h
@@ -0,0 +1,69 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+namespace mtproto {
+
+class ProxySecret {
+ public:
+ static constexpr size_t MAX_DOMAIN_LENGTH = 182; // must be small enough to not overflow TLS-hello length
+
+ static Result<ProxySecret> from_link(Slice encoded_secret, bool truncate_if_needed = false);
+
+ static Result<ProxySecret> from_binary(Slice raw_unchecked_secret, bool truncate_if_needed = false);
+
+ static ProxySecret from_raw(Slice raw_secret) {
+ ProxySecret result;
+ result.secret_ = raw_secret.str();
+ return result;
+ }
+
+ Slice get_raw_secret() const {
+ return secret_;
+ }
+
+ Slice get_proxy_secret() const {
+ Slice proxy_secret(secret_);
+ if (proxy_secret.size() >= 17) {
+ return proxy_secret.substr(1, 16);
+ }
+ return proxy_secret;
+ }
+
+ string get_encoded_secret() const;
+
+ bool use_random_padding() const {
+ return secret_.size() >= 17;
+ }
+
+ bool emulate_tls() const {
+ return secret_.size() >= 17 && static_cast<unsigned char>(secret_[0]) == 0xee;
+ }
+
+ string get_domain() const {
+ CHECK(emulate_tls());
+ return secret_.substr(17);
+ }
+
+ private:
+ friend bool operator==(const ProxySecret &lhs, const ProxySecret &rhs) {
+ return lhs.secret_ == rhs.secret_;
+ }
+ string secret_;
+};
+
+inline bool operator!=(const ProxySecret &lhs, const ProxySecret &rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/RSA.cpp b/protocols/Telegram/tdlib/td/td/mtproto/RSA.cpp
new file mode 100644
index 0000000000..9f32887261
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/RSA.cpp
@@ -0,0 +1,158 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/RSA.h"
+
+#include "td/mtproto/mtproto_api.h"
+
+#include "td/utils/as.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/misc.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/tl_storers.h"
+
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/opensslv.h>
+#include <openssl/pem.h>
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || defined(LIBRESSL_VERSION_NUMBER)
+#include <openssl/rsa.h>
+#endif
+
+namespace td {
+namespace mtproto {
+
+RSA::RSA(BigNum n, BigNum e) : n_(std::move(n)), e_(std::move(e)) {
+}
+
+RSA RSA::clone() const {
+ return RSA(n_.clone(), e_.clone());
+}
+
+Result<RSA> RSA::from_pem_public_key(Slice pem) {
+ init_crypto();
+
+ auto *bio =
+ BIO_new_mem_buf(const_cast<void *>(static_cast<const void *>(pem.ubegin())), narrow_cast<int32>(pem.size()));
+ if (bio == nullptr) {
+ return Status::Error("Cannot create BIO");
+ }
+ SCOPE_EXIT {
+ BIO_free(bio);
+ };
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ EVP_PKEY *rsa = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr);
+#else
+ auto rsa = PEM_read_bio_RSAPublicKey(bio, nullptr, nullptr, nullptr);
+#endif
+ if (rsa == nullptr) {
+ return Status::Error("Error while reading RSA public key");
+ }
+ SCOPE_EXIT {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ EVP_PKEY_free(rsa);
+#else
+ RSA_free(rsa);
+#endif
+ };
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ if (!EVP_PKEY_is_a(rsa, "RSA")) {
+ return Status::Error("Key is not an RSA key");
+ }
+ if (EVP_PKEY_size(rsa) != 256) {
+ return Status::Error("EVP_PKEY_size != 256");
+ }
+#else
+ if (RSA_size(rsa) != 256) {
+ return Status::Error("RSA_size != 256");
+ }
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ BIGNUM *n_num = nullptr;
+ BIGNUM *e_num = nullptr;
+
+ int res = EVP_PKEY_get_bn_param(rsa, "n", &n_num);
+ CHECK(res == 1 && n_num != nullptr);
+ res = EVP_PKEY_get_bn_param(rsa, "e", &e_num);
+ CHECK(res == 1 && e_num != nullptr);
+
+ auto n = static_cast<void *>(n_num);
+ auto e = static_cast<void *>(e_num);
+#else
+ const BIGNUM *n_num;
+ const BIGNUM *e_num;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ RSA_get0_key(rsa, &n_num, &e_num, nullptr);
+#else
+ n_num = rsa->n;
+ e_num = rsa->e;
+#endif
+
+ auto n = static_cast<void *>(BN_dup(n_num));
+ auto e = static_cast<void *>(BN_dup(e_num));
+ if (n == nullptr || e == nullptr) {
+ return Status::Error("Cannot dup BIGNUM");
+ }
+#endif
+
+ return RSA(BigNum::from_raw(n), BigNum::from_raw(e));
+}
+
+int64 RSA::get_fingerprint() const {
+ // string objects are necessary, because mtproto_api::rsa_public_key contains Slice inside
+ string n_str = n_.to_binary();
+ string e_str = e_.to_binary();
+ mtproto_api::rsa_public_key public_key(n_str, e_str);
+ size_t size = tl_calc_length(public_key);
+ std::vector<unsigned char> tmp(size);
+ size = tl_store_unsafe(public_key, tmp.data());
+ CHECK(size == tmp.size());
+ unsigned char key_sha1[20];
+ sha1(Slice(tmp.data(), tmp.size()), key_sha1);
+ return as<int64>(key_sha1 + 12);
+}
+
+size_t RSA::size() const {
+ // Checked in RSA::from_pem_public_key step
+ return 256;
+}
+
+bool RSA::encrypt(Slice from, MutableSlice to) const {
+ CHECK(from.size() == 256)
+ CHECK(to.size() == 256)
+ int bits = n_.get_num_bits();
+ CHECK(bits >= 2041 && bits <= 2048);
+
+ BigNum x = BigNum::from_binary(from);
+ if (BigNum::compare(x, n_) >= 0) {
+ return false;
+ }
+
+ BigNumContext ctx;
+ BigNum y;
+ BigNum::mod_exp(y, x, e_, n_, ctx);
+ to.copy_from(y.to_binary(256));
+ return true;
+}
+
+void RSA::decrypt_signature(Slice from, MutableSlice to) const {
+ CHECK(from.size() == 256);
+ BigNumContext ctx;
+ BigNum x = BigNum::from_binary(from);
+ BigNum y;
+ BigNum::mod_exp(y, x, e_, n_, ctx);
+ to.copy_from(y.to_binary(256));
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/RSA.h b/protocols/Telegram/tdlib/td/td/mtproto/RSA.h
new file mode 100644
index 0000000000..489818f1dd
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/RSA.h
@@ -0,0 +1,49 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/BigNum.h"
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+namespace mtproto {
+
+class RSA {
+ public:
+ RSA clone() const;
+ int64 get_fingerprint() const;
+ size_t size() const;
+
+ bool encrypt(Slice from, MutableSlice to) const;
+
+ void decrypt_signature(Slice from, MutableSlice to) const;
+
+ static Result<RSA> from_pem_public_key(Slice pem);
+
+ private:
+ RSA(BigNum n, BigNum e);
+ BigNum n_;
+ BigNum e_;
+};
+
+class PublicRsaKeyInterface {
+ public:
+ virtual ~PublicRsaKeyInterface() = default;
+
+ struct RsaKey {
+ RSA rsa;
+ int64 fingerprint;
+ };
+ virtual Result<RsaKey> get_rsa_key(const vector<int64> &fingerprints) = 0;
+
+ virtual void drop_keys() = 0;
+};
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/RawConnection.cpp b/protocols/Telegram/tdlib/td/td/mtproto/RawConnection.cpp
index 3595b4bb03..d69211e7b1 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/RawConnection.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/RawConnection.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,119 +7,471 @@
#include "td/mtproto/RawConnection.h"
#include "td/mtproto/AuthKey.h"
+#include "td/mtproto/IStreamTransport.h"
+#include "td/mtproto/ProxySecret.h"
#include "td/mtproto/Transport.h"
+#if TD_DARWIN_WATCH_OS
+#include "td/net/DarwinHttp.h"
+#endif
+
+#include "td/utils/FlatHashMap.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/MpscPollableQueue.h"
+#include "td/utils/port/EventFd.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
+#include "td/utils/StorerBase.h"
+#include <memory>
#include <utility>
namespace td {
namespace mtproto {
-void RawConnection::send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key,
- uint64 quick_ack_token) {
- mtproto::PacketInfo info;
- info.version = 2;
- info.no_crypto_flag = false;
- info.salt = salt;
- info.session_id = session_id;
+class RawConnectionDefault final : public RawConnection {
+ public:
+ RawConnectionDefault(BufferedFd<SocketFd> buffered_socket_fd, TransportType transport_type,
+ unique_ptr<StatsCallback> stats_callback)
+ : socket_fd_(std::move(buffered_socket_fd))
+ , transport_(create_transport(std::move(transport_type)))
+ , stats_callback_(std::move(stats_callback)) {
+ transport_->init(&socket_fd_.input_buffer(), &socket_fd_.output_buffer());
+ }
+
+ void set_connection_token(ConnectionManager::ConnectionToken connection_token) final {
+ connection_token_ = std::move(connection_token);
+ }
+
+ bool can_send() const final {
+ return transport_->can_write();
+ }
- auto packet = BufferWriter{mtproto::Transport::write(storer, auth_key, &info), transport_->max_prepend_size(), 0};
- mtproto::Transport::write(storer, auth_key, &info, packet.as_slice());
+ TransportType get_transport_type() const final {
+ return transport_->get_type();
+ }
- bool use_quick_ack = false;
- if (quick_ack_token != 0 && transport_->support_quick_ack()) {
- auto tmp = quick_ack_to_token_.insert(std::make_pair(info.message_ack, quick_ack_token));
- if (tmp.second) {
- use_quick_ack = true;
- } else {
- LOG(ERROR) << "quick_ack collision " << tag("quick_ack", info.message_ack);
+ size_t send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key,
+ uint64 quick_ack_token) final {
+ PacketInfo info;
+ info.version = 2;
+ info.no_crypto_flag = false;
+ info.salt = salt;
+ info.session_id = session_id;
+ info.use_random_padding = transport_->use_random_padding();
+
+ auto packet = BufferWriter{Transport::write(storer, auth_key, &info), transport_->max_prepend_size(),
+ transport_->max_append_size()};
+ Transport::write(storer, auth_key, &info, packet.as_slice());
+
+ bool use_quick_ack = false;
+ if (quick_ack_token != 0 && transport_->support_quick_ack()) {
+ CHECK(info.message_ack & (1u << 31));
+ auto tmp = quick_ack_to_token_.emplace(info.message_ack, quick_ack_token);
+ if (tmp.second) {
+ use_quick_ack = true;
+ } else {
+ LOG(ERROR) << "Quick ack " << info.message_ack << " collision";
+ }
}
+
+ auto packet_size = packet.size();
+ transport_->write(std::move(packet), use_quick_ack);
+ return packet_size;
}
- transport_->write(std::move(packet), use_quick_ack);
-}
+ uint64 send_no_crypto(const Storer &storer) final {
+ PacketInfo info;
-uint64 RawConnection::send_no_crypto(const Storer &storer) {
- mtproto::PacketInfo info;
+ info.no_crypto_flag = true;
+ auto packet = BufferWriter{Transport::write(storer, AuthKey(), &info), transport_->max_prepend_size(),
+ transport_->max_append_size()};
+ Transport::write(storer, AuthKey(), &info, packet.as_slice());
+ LOG(INFO) << "Send handshake packet: " << format::as_hex_dump<4>(packet.as_slice());
+ transport_->write(std::move(packet), false);
+ return info.message_id;
+ }
- info.no_crypto_flag = true;
- auto packet =
- BufferWriter{mtproto::Transport::write(storer, mtproto::AuthKey(), &info), transport_->max_prepend_size(), 0};
- mtproto::Transport::write(storer, mtproto::AuthKey(), &info, packet.as_slice());
- LOG(INFO) << "Send handshake packet: " << format::as_hex_dump<4>(packet.as_slice());
- transport_->write(std::move(packet), false);
- return info.message_id;
-}
+ PollableFdInfo &get_poll_info() final {
+ return socket_fd_.get_poll_info();
+ }
+
+ StatsCallback *stats_callback() final {
+ return stats_callback_.get();
+ }
-Status RawConnection::flush_read(const AuthKey &auth_key, Callback &callback) {
- auto r = socket_fd_.flush_read();
- if (r.is_ok() && stats_callback_) {
- stats_callback_->on_read(r.ok());
+ // NB: After first returned error, all subsequent calls will return error too.
+ Status flush(const AuthKey &auth_key, Callback &callback) final {
+ auto status = do_flush(auth_key, callback);
+ if (status.is_error()) {
+ if (stats_callback_ && status.code() != 2) {
+ stats_callback_->on_error();
+ }
+ has_error_ = true;
+ }
+ return status;
}
- while (transport_->can_read()) {
- BufferSlice packet;
- uint32 quick_ack = 0;
- TRY_RESULT(wait_size, transport_->read_next(&packet, &quick_ack));
- if (wait_size != 0) {
- break;
+
+ bool has_error() const final {
+ return has_error_;
+ }
+
+ void close() final {
+ transport_.reset();
+ socket_fd_.close();
+ }
+
+ PublicFields &extra() final {
+ return extra_;
+ }
+ const PublicFields &extra() const final {
+ return extra_;
+ }
+
+ private:
+ PublicFields extra_;
+ BufferedFd<SocketFd> socket_fd_;
+ unique_ptr<IStreamTransport> transport_;
+ FlatHashMap<uint32, uint64> quick_ack_to_token_;
+ bool has_error_{false};
+
+ unique_ptr<StatsCallback> stats_callback_;
+
+ ConnectionManager::ConnectionToken connection_token_;
+
+ Status flush_read(const AuthKey &auth_key, Callback &callback) {
+ auto r = socket_fd_.flush_read();
+ if (r.is_ok()) {
+ if (stats_callback_) {
+ stats_callback_->on_read(r.ok());
+ }
+ callback.on_read(r.ok());
}
+ while (transport_->can_read()) {
+ BufferSlice packet;
+ uint32 quick_ack = 0;
+ TRY_RESULT(wait_size, transport_->read_next(&packet, &quick_ack));
+ if (!is_aligned_pointer<4>(packet.as_slice().ubegin())) {
+ BufferSlice new_packet(packet.size());
+ new_packet.as_slice().copy_from(packet.as_slice());
+ packet = std::move(new_packet);
+ }
+ LOG_CHECK(is_aligned_pointer<4>(packet.as_slice().ubegin()))
+ << packet.as_slice().ubegin() << ' ' << packet.size() << ' ' << wait_size;
+ if (wait_size != 0) {
+ constexpr size_t MAX_PACKET_SIZE = (1 << 22) + 1024;
+ if (wait_size > MAX_PACKET_SIZE) {
+ return Status::Error(PSLICE() << "Expected packet size is too big: " << wait_size);
+ }
+ break;
+ }
- if (quick_ack != 0) {
- auto it = quick_ack_to_token_.find(quick_ack);
- if (it == quick_ack_to_token_.end()) {
- LOG(WARNING) << Status::Error(PSLICE() << "Unknown " << tag("quick_ack", quick_ack));
+ if (quick_ack != 0) {
+ TRY_STATUS(on_quick_ack(quick_ack, callback));
continue;
- // TODO: return Status::Error(PSLICE() << "Unknown " << tag("quick_ack", quick_ack));
}
- auto token = it->second;
- quick_ack_to_token_.erase(it);
- callback.on_quick_ack(token);
- continue;
+
+ PacketInfo info;
+ info.version = 2;
+
+ TRY_RESULT(read_result, Transport::read(packet.as_slice(), auth_key, &info));
+ switch (read_result.type()) {
+ case Transport::ReadResult::Quickack: {
+ TRY_STATUS(on_quick_ack(read_result.quick_ack(), callback));
+ break;
+ }
+ case Transport::ReadResult::Error: {
+ TRY_STATUS(on_read_mtproto_error(read_result.error()));
+ break;
+ }
+ case Transport::ReadResult::Packet: {
+ // If a packet was successfully decrypted, then it is ok to assume that the connection is alive
+ if (!auth_key.empty()) {
+ if (stats_callback_) {
+ stats_callback_->on_pong();
+ }
+ }
+
+ TRY_STATUS(callback.on_raw_packet(info, packet.from_slice(read_result.packet())));
+ break;
+ }
+ case Transport::ReadResult::Nop:
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ TRY_STATUS(std::move(r));
+ return Status::OK();
+ }
+
+ Status on_read_mtproto_error(int32 error_code) {
+ if (error_code == -429) {
+ if (stats_callback_) {
+ stats_callback_->on_mtproto_error();
+ }
+ return Status::Error(500, PSLICE() << "MTProto error: " << error_code);
+ }
+ if (error_code == -404) {
+ return Status::Error(-404, PSLICE() << "MTProto error: " << error_code);
+ }
+ return Status::Error(PSLICE() << "MTProto error: " << error_code);
+ }
+
+ Status on_quick_ack(uint32 quick_ack, Callback &callback) {
+ if ((quick_ack & (1u << 31)) == 0) {
+ LOG(ERROR) << "Receive invalid quick_ack " << quick_ack;
+ return Status::OK();
+ }
+
+ auto it = quick_ack_to_token_.find(quick_ack);
+ if (it == quick_ack_to_token_.end()) {
+ LOG(WARNING) << "Receive unknown quick_ack " << quick_ack;
+ return Status::OK();
+ }
+ auto token = it->second;
+ quick_ack_to_token_.erase(it);
+ callback.on_quick_ack(token).ignore();
+ return Status::OK();
+ }
+
+ Status flush_write() {
+ TRY_RESULT(size, socket_fd_.flush_write());
+ if (size > 0 && stats_callback_) {
+ stats_callback_->on_write(size);
}
+ return Status::OK();
+ }
+
+ Status do_flush(const AuthKey &auth_key, Callback &callback) TD_WARN_UNUSED_RESULT {
+ if (has_error_) {
+ return Status::Error("Connection has already failed");
+ }
+ sync_with_poll(socket_fd_);
+
+ // read/write
+ // EINVAL can be returned in Linux kernel < 2.6.28. And on some new kernels too.
+ // just close connection and hope that read or write will not return this error too.
+ TRY_STATUS(socket_fd_.get_pending_error());
+
+ TRY_STATUS(flush_read(auth_key, callback));
+ TRY_STATUS(callback.before_write());
+ TRY_STATUS(flush_write());
+ if (can_close_local(socket_fd_)) {
+ return Status::Error("Connection closed");
+ }
+ return Status::OK();
+ }
+};
+
+#if TD_DARWIN_WATCH_OS
+class RawConnectionHttp final : public RawConnection {
+ public:
+ RawConnectionHttp(IPAddress ip_address, unique_ptr<StatsCallback> stats_callback)
+ : ip_address_(std::move(ip_address)), stats_callback_(std::move(stats_callback)) {
+ answers_ = std::make_shared<MpscPollableQueue<Result<BufferSlice>>>();
+ answers_->init();
+ }
+
+ void set_connection_token(ConnectionManager::ConnectionToken connection_token) final {
+ connection_token_ = std::move(connection_token);
+ }
+
+ bool can_send() const final {
+ return mode_ == Send;
+ }
+
+ TransportType get_transport_type() const final {
+ return mtproto::TransportType{mtproto::TransportType::Http, 0, mtproto::ProxySecret()};
+ }
- MutableSlice data = packet.as_slice();
+ size_t send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key,
+ uint64 quick_ack_token) final {
PacketInfo info;
info.version = 2;
+ info.no_crypto_flag = false;
+ info.salt = salt;
+ info.session_id = session_id;
+ info.use_random_padding = false;
+
+ auto packet = BufferWriter{Transport::write(storer, auth_key, &info), 0, 0};
+ Transport::write(storer, auth_key, &info, packet.as_slice());
+
+ auto packet_size = packet.size();
+ send_packet(packet.as_buffer_slice());
+ return packet_size;
+ }
+
+ uint64 send_no_crypto(const Storer &storer) final {
+ PacketInfo info;
+
+ info.no_crypto_flag = true;
+ auto packet = BufferWriter{Transport::write(storer, AuthKey(), &info), 0, 0};
+ Transport::write(storer, AuthKey(), &info, packet.as_slice());
+ LOG(INFO) << "Send handshake packet: " << format::as_hex_dump<4>(packet.as_slice());
+ send_packet(packet.as_buffer_slice());
+ return info.message_id;
+ }
+
+ PollableFdInfo &get_poll_info() final {
+ return answers_->reader_get_event_fd().get_poll_info();
+ }
+
+ StatsCallback *stats_callback() final {
+ return stats_callback_.get();
+ }
+
+ // NB: After first returned error, all subsequent calls will return error too.
+ Status flush(const AuthKey &auth_key, Callback &callback) final {
+ auto status = do_flush(auth_key, callback);
+ if (status.is_error()) {
+ if (stats_callback_ && status.code() != 2) {
+ stats_callback_->on_error();
+ }
+ has_error_ = true;
+ }
+ return status;
+ }
- int32 error_code = 0;
- TRY_STATUS(mtproto::Transport::read(data, auth_key, &info, &data, &error_code));
+ bool has_error() const final {
+ return has_error_;
+ }
+
+ void close() final {
+ }
+
+ PublicFields &extra() final {
+ return extra_;
+ }
+ const PublicFields &extra() const final {
+ return extra_;
+ }
+
+ private:
+ PublicFields extra_;
+ IPAddress ip_address_;
+ bool has_error_{false};
+ EventFd event_fd_;
+
+ enum Mode { Send, Receive } mode_{Send};
- if (error_code) {
- if (error_code == -429) {
+ unique_ptr<StatsCallback> stats_callback_;
+
+ ConnectionManager::ConnectionToken connection_token_;
+ std::shared_ptr<MpscPollableQueue<Result<BufferSlice>>> answers_;
+ std::vector<BufferSlice> to_send_;
+
+ void send_packet(BufferSlice packet) {
+ CHECK(mode_ == Send);
+ mode_ = Receive;
+ to_send_.push_back(std::move(packet));
+ }
+
+ Status flush_read(const AuthKey &auth_key, Callback &callback) {
+ while (true) {
+ auto packets_n = answers_->reader_wait_nonblock();
+ if (packets_n == 0) {
+ break;
+ }
+ for (int i = 0; i < packets_n; i++) {
+ TRY_RESULT(packet, answers_->reader_get_unsafe());
if (stats_callback_) {
- stats_callback_->on_mtproto_error();
+ stats_callback_->on_read(packet.size());
+ }
+ callback.on_read(packet.size());
+ CHECK(mode_ == Receive);
+ mode_ = Send;
+
+ PacketInfo info;
+ info.version = 2;
+
+ TRY_RESULT(read_result, Transport::read(packet.as_slice(), auth_key, &info));
+ switch (read_result.type()) {
+ case Transport::ReadResult::Quickack: {
+ break;
+ }
+ case Transport::ReadResult::Error: {
+ TRY_STATUS(on_read_mtproto_error(read_result.error()));
+ break;
+ }
+ case Transport::ReadResult::Packet: {
+ // If a packet was successfully decrypted, then it is ok to assume that the connection is alive
+ if (!auth_key.empty()) {
+ if (stats_callback_) {
+ stats_callback_->on_pong();
+ }
+ }
+
+ TRY_STATUS(callback.on_raw_packet(info, packet.from_slice(read_result.packet())));
+ break;
+ }
+ case Transport::ReadResult::Nop:
+ break;
+ default:
+ UNREACHABLE();
}
- return Status::Error(500, PSLICE() << "Mtproto error: " << error_code);
- }
- if (error_code == -404) {
- return Status::Error(-404, PSLICE() << "Mtproto error: " << error_code);
}
- return Status::Error(PSLICE() << "Mtproto error: " << error_code);
}
- // If a packet was successfully decrypted, then it is ok to assume that the connection is alive
- if (!auth_key.empty()) {
+ return Status::OK();
+ }
+
+ Status on_read_mtproto_error(int32 error_code) {
+ if (error_code == -429) {
if (stats_callback_) {
- stats_callback_->on_pong();
+ stats_callback_->on_mtproto_error();
}
+ return Status::Error(500, PSLICE() << "MTProto error: " << error_code);
+ }
+ if (error_code == -404) {
+ return Status::Error(-404, PSLICE() << "MTProto error: " << error_code);
}
+ return Status::Error(PSLICE() << "MTProto error: " << error_code);
+ }
- TRY_STATUS(callback.on_raw_packet(info, packet.from_slice(data)));
+ Status flush_write() {
+ for (auto &packet : to_send_) {
+ TRY_STATUS(do_send(packet.as_slice()));
+ if (packet.size() > 0 && stats_callback_) {
+ stats_callback_->on_write(packet.size());
+ }
+ }
+ to_send_.clear();
+ return Status::OK();
}
- TRY_STATUS(std::move(r));
- return Status::OK();
-}
-Status RawConnection::flush_write() {
- TRY_RESULT(size, socket_fd_.flush_write());
- if (size > 0 && stats_callback_) {
- stats_callback_->on_write(size);
+ Status do_send(Slice data) {
+ DarwinHttp::post(PSLICE() << "http://" << ip_address_.get_ip_str() << ":" << ip_address_.get_port() << "/api", data,
+ [answers = answers_](auto res) { answers->writer_put(std::move(res)); });
+ return Status::OK();
}
- return Status::OK();
+
+ Status do_flush(const AuthKey &auth_key, Callback &callback) TD_WARN_UNUSED_RESULT {
+ if (has_error_) {
+ return Status::Error("Connection has already failed");
+ }
+
+ TRY_STATUS(flush_read(auth_key, callback));
+ TRY_STATUS(callback.before_write());
+ TRY_STATUS(flush_write());
+ return Status::OK();
+ }
+};
+#endif
+
+unique_ptr<RawConnection> RawConnection::create(IPAddress ip_address, BufferedFd<SocketFd> buffered_socket_fd,
+ TransportType transport_type,
+ unique_ptr<StatsCallback> stats_callback) {
+#if TD_DARWIN_WATCH_OS
+ return td::make_unique<RawConnectionHttp>(std::move(ip_address), std::move(stats_callback));
+#else
+ return td::make_unique<RawConnectionDefault>(std::move(buffered_socket_fd), std::move(transport_type),
+ std::move(stats_callback));
+#endif
}
} // namespace mtproto
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/RawConnection.h b/protocols/Telegram/tdlib/td/td/mtproto/RawConnection.h
index 3c989c4122..6582d46e77 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/RawConnection.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/RawConnection.h
@@ -1,33 +1,29 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/mtproto/IStreamTransport.h"
+
+#include "td/mtproto/ConnectionManager.h"
+#include "td/mtproto/PacketInfo.h"
+#include "td/mtproto/TransportType.h"
#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
#include "td/utils/common.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/IPAddress.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Status.h"
-
-#include "td/telegram/StateManager.h"
-
-#include <map>
+#include "td/utils/StorerBase.h"
namespace td {
-class Storer;
namespace mtproto {
+
class AuthKey;
-struct PacketInfo;
-} // namespace mtproto
-} // namespace td
-namespace td {
-namespace mtproto {
class RawConnection {
public:
class StatsCallback {
@@ -37,37 +33,27 @@ class RawConnection {
virtual void on_write(uint64 bytes) = 0;
virtual void on_pong() = 0; // called when we know that connection is alive
- virtual void on_error() = 0; // called on RawConnectin error. Such error should be very rare on good connections.
+ virtual void on_error() = 0; // called on RawConnection error. Such error should be very rare on good connections.
virtual void on_mtproto_error() = 0;
};
RawConnection() = default;
- RawConnection(SocketFd socket_fd, TransportType transport_type, std::unique_ptr<StatsCallback> stats_callback)
- : socket_fd_(std::move(socket_fd))
- , transport_(create_transport(transport_type))
- , stats_callback_(std::move(stats_callback)) {
- transport_->init(&socket_fd_.input_buffer(), &socket_fd_.output_buffer());
- }
-
- void set_connection_token(StateManager::ConnectionToken connection_token) {
- connection_token_ = std::move(connection_token);
- }
-
- bool can_send() const {
- return transport_->can_write();
- }
- TransportType get_transport_type() const {
- return transport_->get_type();
- }
- void send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key,
- uint64 quick_ack_token = 0);
- uint64 send_no_crypto(const Storer &storer);
-
- Fd &get_pollable() {
- return socket_fd_.get_fd();
- }
- StatsCallback *stats_callback() {
- return stats_callback_.get();
- }
+ RawConnection(const RawConnection &) = delete;
+ RawConnection &operator=(const RawConnection &) = delete;
+ virtual ~RawConnection() = default;
+
+ static unique_ptr<RawConnection> create(IPAddress ip_address, BufferedFd<SocketFd> buffered_socket_fd,
+ TransportType transport_type, unique_ptr<StatsCallback> stats_callback);
+
+ virtual void set_connection_token(ConnectionManager::ConnectionToken connection_token) = 0;
+
+ virtual bool can_send() const = 0;
+ virtual TransportType get_transport_type() const = 0;
+ virtual size_t send_crypto(const Storer &storer, int64 session_id, int64 salt, const AuthKey &auth_key,
+ uint64 quick_ack_token) = 0;
+ virtual uint64 send_no_crypto(const Storer &storer) = 0;
+
+ virtual PollableFdInfo &get_poll_info() = 0;
+ virtual StatsCallback *stats_callback() = 0;
class Callback {
public:
@@ -77,69 +63,29 @@ class RawConnection {
virtual ~Callback() = default;
virtual Status on_raw_packet(const PacketInfo &info, BufferSlice packet) = 0;
virtual Status on_quick_ack(uint64 quick_ack_token) {
- return Status::Error("quick acks unsupported fully, but still used");
+ return Status::Error("Quick acknowledgements are unsupported by the callback");
}
virtual Status before_write() {
return Status::OK();
}
+ virtual void on_read(size_t size) {
+ }
};
// NB: After first returned error, all subsequent calls will return error too.
- Status flush(const AuthKey &auth_key, Callback &callback) TD_WARN_UNUSED_RESULT {
- auto status = do_flush(auth_key, callback);
- if (status.is_error()) {
- if (stats_callback_ && status.code() != 2) {
- stats_callback_->on_error();
- }
- has_error_ = true;
- }
- return status;
- }
-
- bool has_error() const {
- return has_error_;
- }
-
- void close() {
- transport_.reset();
- socket_fd_.close();
- }
-
- uint32 extra_{0};
- string debug_str_;
- double rtt_{0};
-
- private:
- BufferedFd<SocketFd> socket_fd_;
- unique_ptr<IStreamTransport> transport_;
- std::map<uint32, uint64> quick_ack_to_token_;
- bool has_error_{false};
+ virtual Status flush(const AuthKey &auth_key, Callback &callback) TD_WARN_UNUSED_RESULT = 0;
+ virtual bool has_error() const = 0;
- std::unique_ptr<StatsCallback> stats_callback_;
+ virtual void close() = 0;
- StateManager::ConnectionToken connection_token_;
-
- Status flush_read(const AuthKey &auth_key, Callback &callback);
- Status flush_write();
-
- Status do_flush(const AuthKey &auth_key, Callback &callback) TD_WARN_UNUSED_RESULT {
- if (has_error_) {
- return Status::Error("Connection has already failed");
- }
-
- // read/write
- // EINVAL may be returned in linux kernel < 2.6.28. And on some new kernels too.
- // just close connection and hope that read or write will not return this error too.
- TRY_STATUS(socket_fd_.get_pending_error());
+ struct PublicFields {
+ uint32 extra{0};
+ string debug_str;
+ double rtt{0};
+ };
- TRY_STATUS(flush_read(auth_key, callback));
- TRY_STATUS(callback.before_write());
- TRY_STATUS(flush_write());
- if (can_close(socket_fd_)) {
- return Status::Error("Connection closed");
- }
- return Status::OK();
- }
+ virtual PublicFields &extra() = 0;
+ virtual const PublicFields &extra() const = 0;
};
} // namespace mtproto
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.cpp b/protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.cpp
index 4b78d783f5..c9be563b7c 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,30 +9,34 @@
#include "td/mtproto/AuthData.h"
#include "td/mtproto/AuthKey.h"
#include "td/mtproto/CryptoStorer.h"
-#include "td/mtproto/Handshake.h"
-#include "td/mtproto/NoCryptoStorer.h"
-
-#include "td/mtproto/HttpTransport.h"
-#include "td/mtproto/TcpTransport.h"
+#include "td/mtproto/mtproto_api.h"
+#include "td/mtproto/mtproto_api.hpp"
+#include "td/mtproto/PacketStorer.h"
#include "td/mtproto/Transport.h"
+#include "td/mtproto/utils.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/as.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/Gzip.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Random.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
#include "td/utils/tl_parsers.h"
-
-#include "td/mtproto/mtproto_api.h"
-#include "td/mtproto/mtproto_api.hpp"
+#include "td/utils/TlDowncastHelper.h"
#include <algorithm>
#include <iterator>
#include <type_traits>
namespace td {
+
+int VERBOSITY_NAME(mtproto) = VERBOSITY_NAME(DEBUG) + 7;
+
namespace mtproto_api {
const int32 msg_container::ID;
@@ -84,7 +88,7 @@ namespace mtproto {
* - ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;
*
* 6. New session creation
- * Notification about new session.
+ * A notification about new session.
* It is reasonable to store unique_id with current session, in order to process duplicated notifications once.
*
* Causes all older than first_msg_id to be re-sent.
@@ -168,25 +172,13 @@ namespace mtproto {
*
*/
-class OnPacket {
- const MsgInfo &info_;
- SessionConnection *connection_;
- Status *status_;
-
- public:
- OnPacket(const MsgInfo &info, SessionConnection *connection, Status *status)
- : info_(info), connection_(connection), status_(status) {
- }
-
- template <class T>
- void operator()(const T &func) const {
- *status_ = connection_->on_packet(info_, func);
- }
-};
+unique_ptr<RawConnection> SessionConnection::move_as_raw_connection() {
+ was_moved_ = true;
+ return std::move(raw_connection_);
+}
-/*** SessionConnection ***/
BufferSlice SessionConnection::as_buffer_slice(Slice packet) {
- return current_buffer_slice->from_slice(packet);
+ return current_buffer_slice_->from_slice(packet);
}
Status SessionConnection::parse_message(TlParser &parser, MsgInfo *info, Slice *packet, bool crypto_flag) {
@@ -224,7 +216,6 @@ Status SessionConnection::on_packet_container(const MsgInfo &info, Slice packet)
};
TlParser parser(packet);
- parser.fetch_int();
int32 size = parser.fetch_int();
if (parser.get_error()) {
return Status::Error(PSLICE() << "Failed to parse mtproto_api::rpc_container: " << parser.get_error());
@@ -237,32 +228,40 @@ Status SessionConnection::on_packet_container(const MsgInfo &info, Slice packet)
Status SessionConnection::on_packet_rpc_result(const MsgInfo &info, Slice packet) {
TlParser parser(packet);
- parser.fetch_int();
uint64 req_msg_id = parser.fetch_long();
if (parser.get_error()) {
return Status::Error(PSLICE() << "Failed to parse mtproto_api::rpc_result: " << parser.get_error());
}
+ if (req_msg_id == 0) {
+ LOG(ERROR) << "Receive an update in rpc_result: message_id = " << info.message_id << ", seq_no = " << info.seq_no;
+ return Status::Error("Receive an update in rpc_result");
+ }
- auto object_begin_pos = packet.size() - parser.get_left_len();
- int32 id = parser.fetch_int();
- if (id == mtproto_api::rpc_error::ID) {
- mtproto_api::rpc_error rpc_error(parser);
- if (parser.get_error()) {
- return Status::Error(PSLICE() << "Failed to parse mtproto_api::rpc_error: " << parser.get_error());
+ switch (parser.fetch_int()) {
+ case mtproto_api::rpc_error::ID: {
+ mtproto_api::rpc_error rpc_error(parser);
+ if (parser.get_error()) {
+ return Status::Error(PSLICE() << "Failed to parse mtproto_api::rpc_error: " << parser.get_error());
+ }
+ VLOG(mtproto) << "ERROR " << tag("code", rpc_error.error_code_) << tag("message", rpc_error.error_message_)
+ << tag("req_msg_id", req_msg_id);
+ callback_->on_message_result_error(req_msg_id, rpc_error.error_code_, rpc_error.error_message_.str());
+ return Status::OK();
}
- return on_packet(info, req_msg_id, rpc_error);
- } else if (id == mtproto_api::gzip_packed::ID) {
- mtproto_api::gzip_packed gzip(parser);
- if (parser.get_error()) {
- return Status::Error(PSLICE() << "Failed to parse mtproto_api::gzip_packed: " << parser.get_error());
+ case mtproto_api::gzip_packed::ID: {
+ mtproto_api::gzip_packed gzip(parser);
+ if (parser.get_error()) {
+ return Status::Error(PSLICE() << "Failed to parse mtproto_api::gzip_packed: " << parser.get_error());
+ }
+ // yep, gzip in rpc_result
+ BufferSlice object = gzdecode(gzip.packed_data_);
+ // send header no more optimization
+ return callback_->on_message_result_ok(req_msg_id, std::move(object), info.size);
}
- // yep, gzip in rpc_result
- BufferSlice object = gzdecode(gzip.packed_data_);
- // send header no more optimization
- return callback_->on_message_result_ok(req_msg_id, std::move(object), info.size);
+ default:
+ packet.remove_prefix(sizeof(req_msg_id));
+ return callback_->on_message_result_ok(req_msg_id, as_buffer_slice(packet), info.size);
}
-
- return callback_->on_message_result_ok(req_msg_id, as_buffer_slice(packet.substr(object_begin_pos)), info.size);
}
template <class T>
@@ -271,18 +270,26 @@ Status SessionConnection::on_packet(const MsgInfo &info, const T &packet) {
return Status::OK();
}
-Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::rpc_error &rpc_error) {
- return on_packet(info, 0, rpc_error);
+Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_ok &destroy_auth_key) {
+ return on_destroy_auth_key(destroy_auth_key);
}
-Status SessionConnection::on_packet(const MsgInfo &info, uint64 req_msg_id, const mtproto_api::rpc_error &rpc_error) {
- VLOG(mtproto) << "ERROR [code:" << rpc_error.error_code_ << "] [msg:" << rpc_error.error_message_.str().c_str()
- << "]";
- if (req_msg_id != 0) {
- callback_->on_message_result_error(req_msg_id, rpc_error.error_code_, as_buffer_slice(rpc_error.error_message_));
- } else {
- LOG(WARNING) << "rpc_error as update: [" << rpc_error.error_code_ << "][" << rpc_error.error_message_ << "]";
- }
+Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_none &destroy_auth_key) {
+ return on_destroy_auth_key(destroy_auth_key);
+}
+
+Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_fail &destroy_auth_key) {
+ return on_destroy_auth_key(destroy_auth_key);
+}
+
+Status SessionConnection::on_destroy_auth_key(const mtproto_api::DestroyAuthKeyRes &destroy_auth_key) {
+ LOG_CHECK(need_destroy_auth_key_) << static_cast<int32>(mode_);
+ LOG(INFO) << to_string(destroy_auth_key);
+ return callback_->on_destroy_auth_key();
+}
+
+Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::rpc_error &rpc_error) {
+ LOG(ERROR) << "Receive rpc_error as update: [" << rpc_error.error_code_ << "][" << rpc_error.error_message_ << "]";
return Status::OK();
}
@@ -312,7 +319,7 @@ Status SessionConnection::on_packet(const MsgInfo &info,
InvalidContainer = 64
};
- Slice common = " BUG! CALL FOR A DEVELOPER! Session will be closed";
+ Slice common = ". BUG! CALL FOR A DEVELOPER! Session will be closed";
switch (bad_msg_notification.error_code_) {
case MsgIdTooLow: {
LOG(WARNING) << bad_info << ": MessageId is too low. Message will be re-sent";
@@ -321,18 +328,18 @@ Status SessionConnection::on_packet(const MsgInfo &info,
break;
}
case MsgIdTooHigh: {
- LOG(ERROR) << bad_info << ": MessageId is too high. Session will be closed";
+ LOG(WARNING) << bad_info << ": MessageId is too high. Session will be closed";
// All this queries will be re-sent by parent
to_send_.clear();
callback_->on_session_failed(Status::Error("MessageId is too high"));
return Status::Error("MessageId is too high");
}
case MsgIdMod4: {
- LOG(ERROR) << bad_info << ": MessageId is not divisible by 4." << common;
+ LOG(ERROR) << bad_info << ": MessageId is not divisible by 4" << common;
return Status::Error("MessageId is not divisible by 4");
}
case MsgIdCollision: {
- LOG(ERROR) << bad_info << ": Container and older message MessageId collision." << common;
+ LOG(ERROR) << bad_info << ": Container and older message MessageId collision" << common;
return Status::Error("Container and older message MessageId collision");
}
@@ -343,29 +350,29 @@ Status SessionConnection::on_packet(const MsgInfo &info,
}
case SeqNoTooLow: {
- LOG(ERROR) << bad_info << ": SeqNo is too low." << common;
+ LOG(ERROR) << bad_info << ": SeqNo is too low" << common;
return Status::Error("SeqNo is too low");
}
case SeqNoTooHigh: {
- LOG(ERROR) << bad_info << ": SeqNo is too high." << common;
+ LOG(ERROR) << bad_info << ": SeqNo is too high" << common;
return Status::Error("SeqNo is too high");
}
case SeqNoNotEven: {
- LOG(ERROR) << bad_info << ": SeqNo is not even for an irrelevant message." << common;
+ LOG(ERROR) << bad_info << ": SeqNo is not even for an irrelevant message" << common;
return Status::Error("SeqNo is not even for an irrelevant message");
}
case SeqNoNotOdd: {
- LOG(ERROR) << bad_info << ": SeqNo is not odd for an irrelevant message." << common;
+ LOG(ERROR) << bad_info << ": SeqNo is not odd for an irrelevant message" << common;
return Status::Error("SeqNo is not odd for an irrelevant message");
}
case InvalidContainer: {
- LOG(ERROR) << bad_info << ": Invalid Contailer." << common;
+ LOG(ERROR) << bad_info << ": Invalid Contailer" << common;
return Status::Error("Invalid Contailer");
}
default: {
- LOG(ERROR) << bad_info << ": Unknown error [code:" << bad_msg_notification.error_code_ << "]." << common;
+ LOG(ERROR) << bad_info << ": Unknown error [code:" << bad_msg_notification.error_code_ << "]" << common;
return Status::Error("Unknown error code");
}
}
@@ -402,7 +409,7 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::pong
}
Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::future_salts &salts) {
VLOG(mtproto) << "FUTURE_SALTS";
- std::vector<ServerSalt> new_salts;
+ vector<ServerSalt> new_salts;
for (auto &it : salts.salts_) {
new_salts.push_back(
ServerSalt{it->salt_, static_cast<double>(it->valid_since_), static_cast<double>(it->valid_until_)});
@@ -413,9 +420,9 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::futu
return Status::OK();
}
-Status SessionConnection::on_msgs_state_info(const std::vector<int64> &ids, Slice info) {
+Status SessionConnection::on_msgs_state_info(const vector<int64> &ids, Slice info) {
if (ids.size() != info.size()) {
- return Status::Error(PSLICE() << tag("ids.size()", ids.size()) << "!=" << tag("info.size()", info.size()));
+ return Status::Error(PSLICE() << tag("ids.size()", ids.size()) << " != " << tag("info.size()", info.size()));
}
size_t i = 0;
for (auto id : ids) {
@@ -430,13 +437,13 @@ Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::msgs
if (it == service_queries_.end()) {
return Status::Error("Unknown msgs_state_info");
}
- SCOPE_EXIT {
- service_queries_.erase(it);
- };
- if (it->second.type != ServiceQuery::GetStateInfo) {
- return Status::Error("Got msg_state_info in response not to GetStateInfo");
+ auto query = std::move(it->second);
+ service_queries_.erase(it);
+
+ if (query.type != ServiceQuery::GetStateInfo) {
+ return Status::Error("Receive msg_state_info in response not to GetStateInfo");
}
- return on_msgs_state_info(it->second.message_ids, msgs_state_info.info_);
+ return on_msgs_state_info(query.message_ids, msgs_state_info.info_);
}
Status SessionConnection::on_packet(const MsgInfo &info, const mtproto_api::msgs_all_info &msgs_all_info) {
@@ -459,35 +466,66 @@ Status SessionConnection::on_slice_packet(const MsgInfo &info, Slice packet) {
if (info.seq_no & 1) {
send_ack(info.message_id);
}
- TlParser parser(packet);
- tl_object_ptr<mtproto_api::Object> object = mtproto_api::Object::fetch(parser);
- if (parser.get_error()) {
- // msg_container is not real tl object
- if (packet.size() >= 4 && as<int32>(packet.begin()) == mtproto_api::msg_container::ID) {
- return on_packet_container(info, packet);
- }
- if (packet.size() >= 4 && as<int32>(packet.begin()) == mtproto_api::rpc_result::ID) {
- return on_packet_rpc_result(info, packet);
- }
+ if (packet.size() < 4) {
+ callback_->on_session_failed(Status::Error("Receive too small packet"));
+ return Status::Error(PSLICE() << "Receive packet of size " << packet.size());
+ }
- // It is an update... I hope.
- auto status = auth_data_->check_update(info.message_id);
- if (status.is_error()) {
- VLOG(mtproto) << "Skip update " << info.message_id << " from " << get_name() << " created in "
- << (Time::now() - created_at_) << ": " << status;
- return Status::OK();
+ int32 constructor_id = as<int32>(packet.begin());
+ if (constructor_id == mtproto_api::msg_container::ID) {
+ return on_packet_container(info, packet.substr(4));
+ }
+ if (constructor_id == mtproto_api::rpc_result::ID) {
+ return on_packet_rpc_result(info, packet.substr(4));
+ }
+
+ TlDowncastHelper<mtproto_api::Object> helper(constructor_id);
+ Status status;
+ bool is_mtproto_api = downcast_call(static_cast<mtproto_api::Object &>(helper), [&](auto &dummy) {
+ // a constructor from mtproto_api
+ using Type = std::decay_t<decltype(dummy)>;
+ TlParser parser(packet.substr(4));
+ auto object = Type::fetch(parser);
+ parser.fetch_end();
+ if (parser.get_error()) {
+ status = parser.get_status();
} else {
- VLOG(mtproto) << "Got update from " << get_name() << " created in " << (Time::now() - created_at_)
- << " in container " << container_id_ << " from session " << auth_data_->session_id_
- << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_
- << ", seq_no = " << info.seq_no << " and original size " << info.size;
- return callback_->on_message_result_ok(0, as_buffer_slice(packet), info.size);
+ status = this->on_packet(info, static_cast<const Type &>(*object));
}
+ });
+ if (is_mtproto_api) {
+ return status;
}
- Status status;
- downcast_call(*object, OnPacket(info, this, &status));
- return status;
+ // It is an update... I hope.
+ status = auth_data_->check_update(info.message_id);
+ auto recheck_status = auth_data_->recheck_update(info.message_id);
+ if (recheck_status.is_error() && recheck_status.code() == 2) {
+ LOG(WARNING) << "Receive very old update from " << get_name() << " created in " << (Time::now() - created_at_)
+ << " in container " << container_id_ << " from session " << auth_data_->get_session_id()
+ << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_
+ << ", seq_no = " << info.seq_no << " and original size " << info.size << ": " << status << ' '
+ << recheck_status;
+ }
+ if (status.is_error()) {
+ if (status.code() == 2) {
+ LOG(WARNING) << "Receive too old update from " << get_name() << " created in " << (Time::now() - created_at_)
+ << " in container " << container_id_ << " from session " << auth_data_->get_session_id()
+ << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_
+ << ", seq_no = " << info.seq_no << " and original size " << info.size << ": " << status;
+ callback_->on_session_failed(Status::Error("Receive too old update"));
+ return status;
+ }
+ VLOG(mtproto) << "Skip update " << info.message_id << " of size " << info.size << " with seq_no " << info.seq_no
+ << " from " << get_name() << " created in " << (Time::now() - created_at_) << ": " << status;
+ return Status::OK();
+ } else {
+ VLOG(mtproto) << "Got update from " << get_name() << " created in " << (Time::now() - created_at_)
+ << " in container " << container_id_ << " from session " << auth_data_->get_session_id()
+ << " with message_id " << info.message_id << ", main_message_id = " << main_message_id_
+ << ", seq_no = " << info.seq_no << " and original size " << info.size;
+ return callback_->on_update(as_buffer_slice(packet));
+ }
}
Status SessionConnection::parse_packet(TlParser &parser) {
@@ -506,8 +544,8 @@ Status SessionConnection::on_main_packet(const PacketInfo &info, Slice packet) {
callback_->on_connected();
}
- VLOG(raw_mtproto) << "Got packet: [session_id:" << format::as_hex(info.session_id) << "] "
- << format::as_hex_dump<4>(packet);
+ VLOG(raw_mtproto) << "Got packet of size " << packet.size() << " from session " << format::as_hex(info.session_id)
+ << ":" << format::as_hex_dump<4>(packet);
if (info.no_crypto_flag) {
return Status::Error("Unencrypted packet");
}
@@ -524,6 +562,8 @@ Status SessionConnection::on_main_packet(const PacketInfo &info, Slice packet) {
void SessionConnection::on_message_failed(uint64 id, Status status) {
callback_->on_message_failed(id, std::move(status));
+ sent_destroy_auth_key_ = false;
+
if (id == last_ping_message_id_ || id == last_ping_container_id_) {
// restart ping immediately
last_ping_at_ = 0;
@@ -533,33 +573,39 @@ void SessionConnection::on_message_failed(uint64 id, Status status) {
auto cit = container_to_service_msg_.find(id);
if (cit != container_to_service_msg_.end()) {
- for (auto nid : cit->second) {
- on_message_failed_inner(nid);
+ auto message_ids = cit->second;
+ for (auto message_id : message_ids) {
+ on_message_failed_inner(message_id);
}
} else {
on_message_failed_inner(id);
}
}
+
void SessionConnection::on_message_failed_inner(uint64 id) {
auto it = service_queries_.find(id);
if (it == service_queries_.end()) {
return;
}
- switch (it->second.type) {
+ auto query = std::move(it->second);
+ service_queries_.erase(it);
+
+ switch (query.type) {
case ServiceQuery::ResendAnswer: {
- for (auto message_id : it->second.message_ids) {
+ for (auto message_id : query.message_ids) {
resend_answer(message_id);
}
break;
}
case ServiceQuery::GetStateInfo: {
- for (auto message_id : it->second.message_ids) {
+ for (auto message_id : query.message_ids) {
get_state_info(message_id);
}
break;
}
+ default:
+ UNREACHABLE();
}
- service_queries_.erase(id);
}
bool SessionConnection::must_flush_packet() {
@@ -603,6 +649,9 @@ bool SessionConnection::must_flush_packet() {
}
// get_future_salt
if (!has_salt) {
+ if (last_get_future_salt_at_ == 0) {
+ return true;
+ }
auto get_future_salts_at = last_get_future_salt_at_ + 60;
if (get_future_salts_at < Time::now_cached()) {
return true;
@@ -610,17 +659,22 @@ bool SessionConnection::must_flush_packet() {
relax_timeout_at(&flush_packet_at_, get_future_salts_at);
}
+ if (has_salt && need_destroy_auth_key_ && !sent_destroy_auth_key_) {
+ return true;
+ }
+
return false;
}
Status SessionConnection::before_write() {
+ CHECK(raw_connection_);
while (must_flush_packet()) {
flush_packet();
}
return Status::OK();
}
-Status SessionConnection::on_raw_packet(const td::mtproto::PacketInfo &info, BufferSlice packet) {
+Status SessionConnection::on_raw_packet(const PacketInfo &info, BufferSlice packet) {
auto old_main_message_id = main_message_id_;
main_message_id_ = info.message_id;
SCOPE_EXIT {
@@ -639,9 +693,13 @@ Status SessionConnection::on_raw_packet(const td::mtproto::PacketInfo &info, Buf
}
if (status.is_error()) {
if (status.code() == 1) {
- LOG(WARNING) << "Packet ignored " << status;
+ LOG(INFO) << "Packet ignored: " << status;
send_ack(info.message_id);
return Status::OK();
+ } else if (status.code() == 2) {
+ LOG(WARNING) << "Receive too old packet: " << status;
+ callback_->on_session_failed(Status::Error("Receive too old packet"));
+ return status;
} else {
return status;
}
@@ -656,31 +714,46 @@ Status SessionConnection::on_quick_ack(uint64 quick_ack_token) {
callback_->on_message_ack(quick_ack_token);
return Status::OK();
}
-SessionConnection::SessionConnection(Mode mode, std::unique_ptr<RawConnection> raw_connection, AuthData *auth_data,
- DhCallback *dh_callback)
- : raw_connection_(std::move(raw_connection)), auth_data_(auth_data), dh_callback_(dh_callback) {
- state_ = Init;
- mode_ = mode;
- created_at_ = Time::now();
+
+void SessionConnection::on_read(size_t size) {
+ last_read_at_ = Time::now_cached();
+ last_read_size_ += size;
}
-Fd &SessionConnection::get_pollable() {
- return raw_connection_->get_pollable();
+SessionConnection::SessionConnection(Mode mode, unique_ptr<RawConnection> raw_connection, AuthData *auth_data)
+ : random_delay_(Random::fast(0, 5000000) * 1e-6)
+ , state_(Init)
+ , mode_(mode)
+ , created_at_(Time::now())
+ , raw_connection_(std::move(raw_connection))
+ , auth_data_(auth_data) {
+ CHECK(raw_connection_);
+}
+
+PollableFdInfo &SessionConnection::get_poll_info() {
+ CHECK(raw_connection_);
+ return raw_connection_->get_poll_info();
}
Status SessionConnection::init() {
CHECK(state_ == Init);
last_pong_at_ = Time::now_cached();
+ last_read_at_ = Time::now_cached();
state_ = Run;
return Status::OK();
}
-void SessionConnection::set_online(bool online_flag) {
+void SessionConnection::set_online(bool online_flag, bool is_main) {
+ bool need_ping = online_flag || !online_flag_;
online_flag_ = online_flag;
- if (online_flag_) {
- last_pong_at_ = Time::now_cached() - ping_disconnect_delay() + rtt(); // disconnect if no ping in 1 second
+ is_main_ = is_main;
+ auto now = Time::now();
+ if (need_ping) {
+ last_pong_at_ = now - ping_disconnect_delay() + rtt();
+ last_read_at_ = now - read_disconnect_delay() + rtt();
} else {
- last_pong_at_ = Time::now_cached();
+ last_pong_at_ = now;
+ last_read_at_ = now;
}
last_ping_at_ = 0;
last_ping_message_id_ = 0;
@@ -689,21 +762,20 @@ void SessionConnection::set_online(bool online_flag) {
void SessionConnection::do_close(Status status) {
state_ = Closed;
- callback_->on_before_close();
- raw_connection_->close();
// NB: this could be destroyed after on_closed
callback_->on_closed(std::move(status));
}
void SessionConnection::send_crypto(const Storer &storer, uint64 quick_ack_token) {
CHECK(state_ != Closed);
- raw_connection_->send_crypto(storer, auth_data_->get_session_id(), auth_data_->get_server_salt(Time::now_cached()),
- auth_data_->get_auth_key(), quick_ack_token);
+ last_write_size_ += raw_connection_->send_crypto(storer, auth_data_->get_session_id(),
+ auth_data_->get_server_salt(Time::now_cached()),
+ auth_data_->get_auth_key(), quick_ack_token);
}
Result<uint64> SessionConnection::send_query(BufferSlice buffer, bool gzip_flag, int64 message_id,
- uint64 invoke_after_id, bool use_quick_ack) {
- CHECK(mode_ != Mode::HttpLongPoll) << "LongPoll connection is only for http_wait";
+ vector<uint64> invoke_after_ids, bool use_quick_ack) {
+ CHECK(mode_ != Mode::HttpLongPoll); // "LongPoll connection is only for http_wait"
if (message_id == 0) {
message_id = auth_data_->next_message_id(Time::now_cached());
}
@@ -711,7 +783,10 @@ Result<uint64> SessionConnection::send_query(BufferSlice buffer, bool gzip_flag,
if (to_send_.empty()) {
send_before(Time::now_cached() + QUERY_DELAY);
}
- to_send_.push_back(Query{message_id, seq_no, std::move(buffer), gzip_flag, invoke_after_id, use_quick_ack});
+ to_send_.push_back(
+ MtprotoQuery{message_id, seq_no, std::move(buffer), gzip_flag, std::move(invoke_after_ids), use_quick_ack});
+ VLOG(mtproto) << "Invoke query " << message_id << " of size " << to_send_.back().packet.size() << " with seq_no "
+ << seq_no << " after " << invoke_after_ids << (use_quick_ack ? " with quick ack" : "");
return message_id;
}
@@ -736,28 +811,43 @@ void SessionConnection::cancel_answer(int64 message_id) {
to_cancel_answer_.push_back(message_id);
}
-std::pair<uint64, BufferSlice> SessionConnection::encrypted_bind(int64 perm_key, int64 nonce, int32 expire_at) {
+void SessionConnection::destroy_key() {
+ LOG(INFO) << "Set need_destroy_auth_key to true";
+ need_destroy_auth_key_ = true;
+}
+
+std::pair<uint64, BufferSlice> SessionConnection::encrypted_bind(int64 perm_key, int64 nonce, int32 expires_at) {
int64 temp_key = auth_data_->get_tmp_auth_key().id();
- mtproto_api::bind_auth_key_inner object(nonce, temp_key, perm_key, auth_data_->session_id_, expire_at);
+ mtproto_api::bind_auth_key_inner object(nonce, temp_key, perm_key, auth_data_->get_session_id(), expires_at);
auto object_storer = create_storer(object);
- auto object_packet = BufferWriter{object_storer.size(), 0, 0};
- object_storer.store(object_packet.as_slice().ubegin());
+ auto size = object_storer.size();
+ auto object_packet = BufferWriter{size, 0, 0};
+ auto real_size = object_storer.store(object_packet.as_slice().ubegin());
+ CHECK(size == real_size);
- Query query{auth_data_->next_message_id(Time::now_cached()), 0, object_packet.as_buffer_slice(), false, 0, false};
+ MtprotoQuery query{
+ auth_data_->next_message_id(Time::now_cached()), 0, object_packet.as_buffer_slice(), false, {}, false};
PacketStorer<QueryImpl> query_storer(query, Slice());
- mtproto::PacketInfo info;
+ PacketInfo info;
info.version = 1;
info.no_crypto_flag = false;
info.salt = Random::secure_int64();
info.session_id = Random::secure_int64();
- auto packet = BufferWriter{mtproto::Transport::write(query_storer, auth_data_->get_main_auth_key(), &info), 0, 0};
- mtproto::Transport::write(query_storer, auth_data_->get_main_auth_key(), &info, packet.as_slice());
+ const AuthKey &main_auth_key = auth_data_->get_main_auth_key();
+ auto packet = BufferWriter{Transport::write(query_storer, main_auth_key, &info), 0, 0};
+ Transport::write(query_storer, main_auth_key, &info, packet.as_slice());
return std::make_pair(query.message_id, packet.as_buffer_slice());
}
+void SessionConnection::force_ack() {
+ if (!to_ack_.empty()) {
+ send_before(Time::now_cached());
+ }
+}
+
void SessionConnection::send_ack(uint64 message_id) {
VLOG(mtproto) << "Send ack: [msg_id:" << format::as_hex(message_id) << "]";
if (to_ack_.empty()) {
@@ -767,6 +857,11 @@ void SessionConnection::send_ack(uint64 message_id) {
// an easiest way to eliminate duplicated acks for gzipped packets
if (to_ack_.empty() || to_ack_.back() != ack) {
to_ack_.push_back(ack);
+
+ constexpr size_t MAX_UNACKED_PACKETS = 100;
+ if (to_ack_.size() >= MAX_UNACKED_PACKETS) {
+ send_before(Time::now_cached());
+ }
}
}
@@ -796,8 +891,9 @@ void SessionConnection::flush_packet() {
if (mode_ == Mode::HttpLongPoll) {
max_delay = HTTP_MAX_DELAY;
max_after = HTTP_MAX_AFTER;
- max_wait = min(http_max_wait(),
- static_cast<int>(1000 * max(0.1, ping_disconnect_delay() + last_pong_at_ - Time::now_cached() - 1)));
+ auto time_to_disconnect =
+ min(ping_disconnect_delay() + last_pong_at_, read_disconnect_delay() + last_read_at_) - Time::now_cached();
+ max_wait = static_cast<int>(1000 * clamp(time_to_disconnect - rtt(), 0.1, http_max_wait()));
} else if (mode_ == Mode::Http) {
max_delay = HTTP_MAX_DELAY;
max_after = HTTP_MAX_AFTER;
@@ -807,13 +903,15 @@ void SessionConnection::flush_packet() {
// future salts
int future_salt_n = 0;
if (mode_ != Mode::HttpLongPoll) {
- if (auth_data_->need_future_salts(Time::now_cached()) && last_get_future_salt_at_ + 60 < Time::now_cached()) {
+ if (auth_data_->need_future_salts(Time::now_cached()) &&
+ (last_get_future_salt_at_ == 0 || last_get_future_salt_at_ + 60 < Time::now_cached())) {
last_get_future_salt_at_ = Time::now_cached();
future_salt_n = 64;
}
}
- size_t send_till = 0, send_size = 0;
+ size_t send_till = 0;
+ size_t send_size = 0;
// send at most 1020 queries, of total size 2^15
// don't send anything if have no salt
if (has_salt) {
@@ -822,7 +920,7 @@ void SessionConnection::flush_packet() {
send_till++;
}
}
- std::vector<Query> queries;
+ vector<MtprotoQuery> queries;
if (send_till == to_send_.size()) {
queries = std::move(to_send_);
} else if (send_till != 0) {
@@ -831,26 +929,32 @@ void SessionConnection::flush_packet() {
to_send_.erase(to_send_.begin(), to_send_.begin() + send_till);
}
+ bool destroy_auth_key = need_destroy_auth_key_ && !sent_destroy_auth_key_;
+
if (queries.empty() && to_ack_.empty() && ping_id == 0 && max_delay < 0 && future_salt_n == 0 &&
- to_resend_answer_.empty() && to_cancel_answer_.empty() && to_get_state_info_.empty()) {
+ to_resend_answer_.empty() && to_cancel_answer_.empty() && to_get_state_info_.empty() && !destroy_auth_key) {
force_send_at_ = 0;
return;
}
+ sent_destroy_auth_key_ |= destroy_auth_key;
+
VLOG(mtproto) << "Sent packet: " << tag("query_count", queries.size()) << tag("ack_cnt", to_ack_.size())
<< tag("ping", ping_id != 0) << tag("http_wait", max_delay >= 0)
<< tag("future_salt", future_salt_n > 0) << tag("get_info", to_get_state_info_.size())
<< tag("resend", to_resend_answer_.size()) << tag("cancel", to_cancel_answer_.size())
- << tag("auth_id", auth_data_->get_auth_key().id());
+ << tag("destroy_key", destroy_auth_key) << tag("auth_id", auth_data_->get_auth_key().id());
- auto cut_tail = [](auto &v, size_t size, Slice name) {
+ auto cut_tail = [](vector<int64> &v, size_t size, Slice name) {
if (size >= v.size()) {
- return std::move(v);
+ auto result = std::move(v);
+ v.clear();
+ return result;
}
- LOG(WARNING) << "Too much ids in container: " << v.size() << " " << name;
- std::decay_t<decltype(v)> res(std::make_move_iterator(v.end() - size), std::make_move_iterator(v.end()));
+ LOG(WARNING) << "Too many message identifiers in container " << name << ": " << v.size() << " instead of " << size;
+ vector<int64> result(v.end() - size, v.end());
v.resize(v.size() - size);
- return res;
+ return result;
};
// no more than 8192 ids per container..
@@ -867,22 +971,23 @@ void SessionConnection::flush_packet() {
std::any_of(queries.begin(), queries.end(), [](const auto &query) { return query.use_quick_ack; });
{
+ // LOG(ERROR) << (auth_data_->get_header().empty() ? '-' : '+');
uint64 parent_message_id = 0;
- auto storer = PacketStorer<CryptoImpl>(
- queries, auth_data_->header(), std::move(to_ack), ping_id, ping_disconnect_delay() + 2, max_delay, max_after,
- max_wait, future_salt_n, to_get_state_info, to_resend_answer, to_cancel_answer, auth_data_, &container_id,
- &get_state_info_id, &resend_answer_id, &ping_message_id, &parent_message_id);
+ auto storer = PacketStorer<CryptoImpl>(queries, auth_data_->get_header(), std::move(to_ack), ping_id,
+ static_cast<int>(ping_disconnect_delay() + 2.0), max_delay, max_after,
+ max_wait, future_salt_n, to_get_state_info, to_resend_answer,
+ to_cancel_answer, destroy_auth_key, auth_data_, &container_id,
+ &get_state_info_id, &resend_answer_id, &ping_message_id, &parent_message_id);
auto quick_ack_token = use_quick_ack ? parent_message_id : 0;
send_crypto(storer, quick_ack_token);
}
if (resend_answer_id) {
- service_queries_.insert({resend_answer_id, ServiceQuery{ServiceQuery::ResendAnswer, std::move(to_resend_answer)}});
+ service_queries_.emplace(resend_answer_id, ServiceQuery{ServiceQuery::ResendAnswer, std::move(to_resend_answer)});
}
if (get_state_info_id) {
- service_queries_.insert(
- {get_state_info_id, ServiceQuery{ServiceQuery::GetStateInfo, std::move(to_get_state_info)}});
+ service_queries_.emplace(get_state_info_id, ServiceQuery{ServiceQuery::GetStateInfo, std::move(to_get_state_info)});
}
if (ping_id != 0) {
last_ping_container_id_ = container_id;
@@ -890,7 +995,7 @@ void SessionConnection::flush_packet() {
}
if (container_id != 0) {
- vector<uint64> ids = transform(queries, [](const Query &x) { return static_cast<uint64>(x.message_id); });
+ vector<uint64> ids = transform(queries, [](const MtprotoQuery &x) { return static_cast<uint64>(x.message_id); });
// some acks may be lost here. Nobody will resend them if something goes wrong with query.
// It is mostly problem for server. We will just drop this answers in next connection
@@ -907,8 +1012,8 @@ void SessionConnection::flush_packet() {
}
}
- to_ack_.clear();
- if (to_send_.empty()) {
+ if (to_send_.empty() && to_ack_.empty() && to_get_state_info_.empty() && to_resend_answer_.empty() &&
+ to_cancel_answer_.empty()) {
force_send_at_ = 0;
}
}
@@ -920,6 +1025,10 @@ void SessionConnection::send_before(double tm) {
}
Status SessionConnection::do_flush() {
+ LOG_CHECK(raw_connection_) << was_moved_ << ' ' << state_ << ' ' << static_cast<int32>(mode_) << ' '
+ << connected_flag_ << ' ' << is_main_ << ' ' << need_destroy_auth_key_ << ' '
+ << sent_destroy_auth_key_ << ' ' << callback_ << ' ' << (Time::now() - created_at_) << ' '
+ << (Time::now() - last_read_at_);
CHECK(state_ != Closed);
if (state_ == Init) {
TRY_STATUS(init());
@@ -928,12 +1037,33 @@ Status SessionConnection::do_flush() {
return Status::Error("No auth key");
}
- TRY_STATUS(raw_connection_->flush(auth_data_->get_auth_key(), *this));
+ last_read_size_ = 0;
+ last_write_size_ = 0;
+ auto start_time = Time::now();
+ auto result = raw_connection_->flush(auth_data_->get_auth_key(), *this);
+ auto elapsed_time = Time::now() - start_time;
+ if (elapsed_time >= 0.1) {
+ LOG(ERROR) << "RawConnection::flush took " << elapsed_time << " seconds, written " << last_write_size_
+ << " bytes, read " << last_read_size_ << " bytes and returned " << result;
+ }
+ if (result.is_error()) {
+ return result;
+ }
+
+ if (last_pong_at_ + ping_disconnect_delay() < Time::now_cached()) {
+ auto stats_callback = raw_connection_->stats_callback();
+ if (stats_callback != nullptr) {
+ stats_callback->on_error();
+ }
+ return Status::Error(PSLICE() << "Ping timeout of " << ping_disconnect_delay() << " seconds expired");
+ }
- // check last pong
- if (last_pong_at_ != 0 && last_pong_at_ + ping_disconnect_delay() < Time::now_cached()) {
- raw_connection_->stats_callback()->on_error();
- return Status::Error("No pong :(");
+ if (last_read_at_ + read_disconnect_delay() < Time::now_cached()) {
+ auto stats_callback = raw_connection_->stats_callback();
+ if (stats_callback != nullptr) {
+ stats_callback->on_error();
+ }
+ return Status::Error(PSLICE() << "Read timeout of " << read_disconnect_delay() << " seconds expired");
}
return Status::OK();
@@ -954,6 +1084,7 @@ double SessionConnection::flush(SessionConnection::Callback *callback) {
// 1. close connection after PING_DISCONNECT_DELAY after last_pong.
// 2. the one returned by must_flush_packet
relax_timeout_at(&wakeup_at_, last_pong_at_ + ping_disconnect_delay() + 0.002);
+ relax_timeout_at(&wakeup_at_, last_read_at_ + read_disconnect_delay() + 0.002);
// CHECK(wakeup_at > Time::now_cached());
relax_timeout_at(&wakeup_at_, flush_packet_at_);
@@ -965,5 +1096,6 @@ void SessionConnection::force_close(SessionConnection::Callback *callback) {
callback_ = callback;
do_close(Status::OK());
}
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.h b/protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.h
index be3c1de895..ead20e1b13 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/SessionConnection.h
@@ -1,36 +1,35 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/mtproto/crypto.h"
+#include "td/mtproto/MtprotoQuery.h"
+#include "td/mtproto/PacketInfo.h"
#include "td/mtproto/RawConnection.h"
-#include "td/mtproto/utils.h"
-
-#include "td/actor/actor.h"
#include "td/utils/buffer.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/format.h"
+#include "td/utils/logging.h"
#include "td/utils/Named.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/ScopeGuard.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/StorerBase.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_parsers.h"
-#include <unordered_map>
#include <utility>
namespace td {
-namespace mtproto_api {
-class msg_container {
- public:
- static const int32 ID = 0x73f1f8dc;
-};
+extern int VERBOSITY_NAME(mtproto);
+
+namespace mtproto_api {
class rpc_error;
class new_session_created;
@@ -44,11 +43,15 @@ class msgs_state_info;
class msgs_all_info;
class msg_detailed_info;
class msg_new_detailed_info;
+class DestroyAuthKeyRes;
+class destroy_auth_key_ok;
+class destroy_auth_key_fail;
+class destroy_auth_key_none;
} // namespace mtproto_api
namespace mtproto {
+
class AuthData;
-struct PacketInfo;
struct MsgInfo {
uint64 session_id;
@@ -62,28 +65,34 @@ inline StringBuilder &operator<<(StringBuilder &stream, const MsgInfo &id) {
<< "] [seq_no:" << format::as_hex(id.seq_no) << "]";
}
-class SessionConnection
+class SessionConnection final
: public Named
, private RawConnection::Callback {
public:
- enum class Mode { Tcp, Http, HttpLongPoll };
- SessionConnection(Mode mode, std::unique_ptr<RawConnection> raw_connection, AuthData *auth_data,
- DhCallback *dh_callback);
+ enum class Mode : int32 { Tcp, Http, HttpLongPoll };
+ SessionConnection(Mode mode, unique_ptr<RawConnection> raw_connection, AuthData *auth_data);
+ SessionConnection(const SessionConnection &) = delete;
+ SessionConnection &operator=(const SessionConnection &) = delete;
+ SessionConnection(SessionConnection &&) = delete;
+ SessionConnection &operator=(SessionConnection &&) = delete;
+ ~SessionConnection() = default;
- Fd &get_pollable();
+ PollableFdInfo &get_poll_info();
+ unique_ptr<RawConnection> move_as_raw_connection();
// Interface
Result<uint64> TD_WARN_UNUSED_RESULT send_query(BufferSlice buffer, bool gzip_flag, int64 message_id = 0,
- uint64 invoke_after_id = 0, bool use_quick_ack = false);
- std::pair<uint64, BufferSlice> encrypted_bind(int64 perm_key, int64 nonce, int32 expire_at);
+ std::vector<uint64> invoke_after_id = {}, bool use_quick_ack = false);
+ std::pair<uint64, BufferSlice> encrypted_bind(int64 perm_key, int64 nonce, int32 expires_at);
void get_state_info(int64 message_id);
void resend_answer(int64 message_id);
void cancel_answer(int64 message_id);
+ void destroy_key();
- void set_online(bool online_flag);
+ void set_online(bool online_flag, bool is_main);
+ void force_ack();
- // Callback
class Callback {
public:
Callback() = default;
@@ -92,7 +101,6 @@ class SessionConnection
virtual ~Callback() = default;
virtual void on_connected() = 0;
- virtual void on_before_close() = 0;
virtual void on_closed(Status status) = 0;
virtual void on_auth_key_updated() = 0;
@@ -106,11 +114,15 @@ class SessionConnection
virtual void on_container_sent(uint64 container_id, vector<uint64> msgs_id) = 0;
virtual Status on_pong() = 0;
+ virtual Status on_update(BufferSlice packet) = 0;
+
virtual void on_message_ack(uint64 id) = 0;
virtual Status on_message_result_ok(uint64 id, BufferSlice packet, size_t original_size) = 0;
- virtual void on_message_result_error(uint64 id, int code, BufferSlice descr) = 0;
+ virtual void on_message_result_error(uint64 id, int code, string message) = 0;
virtual void on_message_failed(uint64 id, Status status) = 0;
virtual void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size) = 0;
+
+ virtual Status on_destroy_auth_key() = 0;
};
double flush(SessionConnection::Callback *callback);
@@ -124,52 +136,65 @@ class SessionConnection
static constexpr double RESEND_ANSWER_DELAY = 0.001; // 0.001s
bool online_flag_ = false;
+ bool is_main_ = false;
+ bool was_moved_ = false;
- int rtt() const {
- return max(2, static_cast<int>(raw_connection_->rtt_ * 1.5));
+ double rtt() const {
+ return max(2.0, raw_connection_->extra().rtt * 1.5 + 1);
}
- int32 ping_disconnect_delay() const {
- return online_flag_ ? rtt() * 5 / 2 : 135;
+ double read_disconnect_delay() const {
+ return online_flag_ ? rtt() * 3.5 : 135 + random_delay_;
}
- int32 ping_may_delay() const {
- return online_flag_ ? rtt() / 2 : 30;
+ double ping_disconnect_delay() const {
+ return online_flag_ && is_main_ ? rtt() * 2.5 : 135 + random_delay_;
}
- int32 ping_must_delay() const {
- return online_flag_ ? rtt() : 60;
+ double ping_may_delay() const {
+ return online_flag_ ? rtt() * 0.5 : 30 + random_delay_;
}
- int http_max_wait() const {
- return 25 * 1000; // 25s. Longer could be closed by proxy
+ double ping_must_delay() const {
+ return online_flag_ ? rtt() : 60 + random_delay_;
}
- static constexpr int HTTP_MAX_AFTER = 10; // 0.001s
- static constexpr int HTTP_MAX_DELAY = 30; // 0.003s
- static constexpr int TEMP_KEY_TIMEOUT = 60 * 60 * 24; // one day
- vector<Query> to_send_;
+ double http_max_wait() const {
+ return 25.0; // 25s. Longer could be closed by proxy
+ }
+ static constexpr int HTTP_MAX_AFTER = 10; // 0.01s
+ static constexpr int HTTP_MAX_DELAY = 30; // 0.03s
+
+ vector<MtprotoQuery> to_send_;
vector<int64> to_ack_;
double force_send_at_ = 0;
struct ServiceQuery {
enum Type { GetStateInfo, ResendAnswer } type;
- std::vector<int64> message_ids;
+ vector<int64> message_ids;
};
- std::vector<int64> to_resend_answer_;
- std::vector<int64> to_cancel_answer_;
- std::vector<int64> to_get_state_info_;
- std::unordered_map<uint64, ServiceQuery> service_queries_;
+ vector<int64> to_resend_answer_;
+ vector<int64> to_cancel_answer_;
+ vector<int64> to_get_state_info_;
+ FlatHashMap<uint64, ServiceQuery> service_queries_;
// nobody cleans up this map. But it should be really small.
- std::unordered_map<uint64, std::vector<uint64>> container_to_service_msg_;
+ FlatHashMap<uint64, vector<uint64>> container_to_service_msg_;
+ double random_delay_ = 0;
+ double last_read_at_ = 0;
double last_ping_at_ = 0;
double last_pong_at_ = 0;
int64 cur_ping_id_ = 0;
uint64 last_ping_message_id_ = 0;
uint64 last_ping_container_id_ = 0;
+ uint64 last_read_size_ = 0;
+ uint64 last_write_size_ = 0;
+
+ bool need_destroy_auth_key_ = false;
+ bool sent_destroy_auth_key_ = false;
+
double wakeup_at_ = 0;
double flush_packet_at_ = 0;
@@ -182,27 +207,25 @@ class SessionConnection
int64 main_message_id_ = 0;
double created_at_ = 0;
- std::unique_ptr<RawConnection> raw_connection_;
+ unique_ptr<RawConnection> raw_connection_;
AuthData *auth_data_;
SessionConnection::Callback *callback_ = nullptr;
- DhCallback *dh_callback_;
- BufferSlice *current_buffer_slice;
-
- friend class OnPacket;
+ BufferSlice *current_buffer_slice_ = nullptr;
BufferSlice as_buffer_slice(Slice packet);
auto set_buffer_slice(BufferSlice *buffer_slice) TD_WARN_UNUSED_RESULT {
- auto old_buffer_slice = current_buffer_slice;
- current_buffer_slice = buffer_slice;
- return ScopeExit() + [&to = current_buffer_slice, from = old_buffer_slice] { to = from; };
+ auto old_buffer_slice = current_buffer_slice_;
+ current_buffer_slice_ = buffer_slice;
+ return ScopeExit() + [&to = current_buffer_slice_, from = old_buffer_slice] {
+ to = from;
+ };
}
- Status parse_message(TlParser &parser, MsgInfo *info, Slice *packet, bool crypto_flag = true) TD_WARN_UNUSED_RESULT;
+ static Status parse_message(TlParser &parser, MsgInfo *info, Slice *packet,
+ bool crypto_flag = true) TD_WARN_UNUSED_RESULT;
Status parse_packet(TlParser &parser) TD_WARN_UNUSED_RESULT;
Status on_packet_container(const MsgInfo &info, Slice packet) TD_WARN_UNUSED_RESULT;
Status on_packet_rpc_result(const MsgInfo &info, Slice packet) TD_WARN_UNUSED_RESULT;
- Status on_packet(const MsgInfo &info, uint64 req_msg_id,
- const mtproto_api::rpc_error &rpc_error) TD_WARN_UNUSED_RESULT;
template <class T>
Status on_packet(const MsgInfo &info, const T &packet) TD_WARN_UNUSED_RESULT;
@@ -218,12 +241,18 @@ class SessionConnection
Status on_packet(const MsgInfo &info, const mtproto_api::pong &pong) TD_WARN_UNUSED_RESULT;
Status on_packet(const MsgInfo &info, const mtproto_api::future_salts &salts) TD_WARN_UNUSED_RESULT;
- Status on_msgs_state_info(const std::vector<int64> &ids, Slice info) TD_WARN_UNUSED_RESULT;
+ Status on_msgs_state_info(const vector<int64> &ids, Slice info) TD_WARN_UNUSED_RESULT;
Status on_packet(const MsgInfo &info, const mtproto_api::msgs_state_info &msgs_state_info) TD_WARN_UNUSED_RESULT;
Status on_packet(const MsgInfo &info, const mtproto_api::msgs_all_info &msgs_all_info) TD_WARN_UNUSED_RESULT;
Status on_packet(const MsgInfo &info, const mtproto_api::msg_detailed_info &msg_detailed_info) TD_WARN_UNUSED_RESULT;
Status on_packet(const MsgInfo &info,
const mtproto_api::msg_new_detailed_info &msg_new_detailed_info) TD_WARN_UNUSED_RESULT;
+ Status on_packet(const MsgInfo &info, const mtproto_api::destroy_auth_key_ok &destroy_auth_key) TD_WARN_UNUSED_RESULT;
+ Status on_packet(const MsgInfo &info,
+ const mtproto_api::destroy_auth_key_none &destroy_auth_key) TD_WARN_UNUSED_RESULT;
+ Status on_packet(const MsgInfo &info,
+ const mtproto_api::destroy_auth_key_fail &destroy_auth_key) TD_WARN_UNUSED_RESULT;
+ Status on_destroy_auth_key(const mtproto_api::DestroyAuthKeyRes &destroy_auth_key);
Status on_slice_packet(const MsgInfo &info, Slice packet) TD_WARN_UNUSED_RESULT;
Status on_main_packet(const PacketInfo &info, Slice packet) TD_WARN_UNUSED_RESULT;
@@ -241,13 +270,13 @@ class SessionConnection
void flush_packet();
Status init() TD_WARN_UNUSED_RESULT;
- Status process_packet(const PacketInfo &info, Slice packet) TD_WARN_UNUSED_RESULT;
- Status flush_read() TD_WARN_UNUSED_RESULT;
Status do_flush() TD_WARN_UNUSED_RESULT;
- Status before_write() override TD_WARN_UNUSED_RESULT;
- Status on_raw_packet(const td::mtproto::PacketInfo &info, BufferSlice packet) override;
- Status on_quick_ack(uint64 quick_ack_token) override;
+ Status before_write() final TD_WARN_UNUSED_RESULT;
+ Status on_raw_packet(const PacketInfo &info, BufferSlice packet) final;
+ Status on_quick_ack(uint64 quick_ack_token) final;
+ void on_read(size_t size) final;
};
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.cpp b/protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.cpp
index e7613acab0..d46baddf39 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.cpp
@@ -1,12 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/mtproto/TcpTransport.h"
-#include "td/utils/logging.h"
+#include "td/utils/as.h"
+#include "td/utils/common.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
@@ -15,6 +16,7 @@
namespace td {
namespace mtproto {
namespace tcp {
+
size_t IntermediateTransport::read_from_stream(ChainBufferReader *stream, BufferSlice *message, uint32 *quick_ack) {
CHECK(message);
size_t stream_size = stream->size();
@@ -58,11 +60,20 @@ void IntermediateTransport::write_prepare_inplace(BufferWriter *message, bool qu
CHECK(prepend.size() >= prepend_size);
message->confirm_prepend(prepend_size);
- as<uint32>(message->as_slice().begin()) = static_cast<uint32>(size);
+ size_t append_size = 0;
+ if (with_padding()) {
+ append_size = Random::secure_uint32() % 16;
+ MutableSlice append = message->prepare_append().substr(0, append_size);
+ CHECK(append.size() == append_size);
+ Random::secure_bytes(append);
+ message->confirm_append(append.size());
+ }
+
+ as<uint32>(message->as_slice().begin()) = static_cast<uint32>(size + append_size);
}
void IntermediateTransport::init_output_stream(ChainBufferWriter *stream) {
- const uint32 magic = 0xeeeeeeee;
+ const uint32 magic = with_padding() ? 0xdddddddd : 0xeeeeeeee;
stream->append(Slice(reinterpret_cast<const char *>(&magic), 4));
}
@@ -121,8 +132,7 @@ void AbridgedTransport::write_prepare_inplace(BufferWriter *message, bool quick_
}
void AbridgedTransport::init_output_stream(ChainBufferWriter *stream) {
- const uint8 magic = 0xef;
- stream->append(Slice(&magic, 1));
+ stream->append("\xef");
}
void ObfuscatedTransport::init(ChainBufferReader *input, ChainBufferWriter *output) {
@@ -137,15 +147,18 @@ void ObfuscatedTransport::init(ChainBufferReader *input, ChainBufferWriter *outp
try_cnt++;
CHECK(try_cnt < 10);
Random::secure_bytes(header_slice.ubegin(), header.size());
+ if (secret_.emulate_tls()) {
+ break;
+ }
if (as<uint8>(header.data()) == 0xef) {
continue;
}
- auto first_int = as<uint32>(header.data());
+ uint32 first_int = as<uint32>(header.data());
if (first_int == 0x44414548 || first_int == 0x54534f50 || first_int == 0x20544547 || first_int == 0x4954504f ||
- first_int == 0xeeeeeeee) {
+ first_int == 0xdddddddd || first_int == 0xeeeeeeee || first_int == 0x02010316) {
continue;
}
- auto second_int = as<uint32>(header.data() + sizeof(uint32));
+ uint32 second_int = as<uint32>(header.data() + sizeof(uint32));
if (second_int == 0) {
continue;
}
@@ -153,19 +166,114 @@ void ObfuscatedTransport::init(ChainBufferReader *input, ChainBufferWriter *outp
}
// TODO: It is actually IntermediateTransport::init_output_stream, so it will work only with
// TransportImpl==IntermediateTransport
- as<uint32>(header_slice.begin() + 56) = 0xeeeeeeee;
+ as<uint32>(header_slice.begin() + 56) = impl_.with_padding() ? 0xdddddddd : 0xeeeeeeee;
+ if (dc_id_ != 0) {
+ as<int16>(header_slice.begin() + 60) = dc_id_;
+ }
string rheader = header;
std::reverse(rheader.begin(), rheader.end());
- aes_ctr_byte_flow_.init(as<UInt256>(rheader.data() + 8), as<UInt128>(rheader.data() + 8 + 32));
- aes_ctr_byte_flow_.set_input(input_);
+ UInt256 key = as<UInt256>(rheader.data() + 8);
+ Slice proxy_secret = secret_.get_proxy_secret();
+ auto fix_key = [&](UInt256 &key) {
+ if (!proxy_secret.empty()) {
+ Sha256State state;
+ state.init();
+ state.feed(as_slice(key));
+ state.feed(proxy_secret);
+ state.extract(as_slice(key));
+ }
+ };
+ fix_key(key);
+ aes_ctr_byte_flow_.init(key, as<UInt128>(rheader.data() + 8 + 32));
+ if (secret_.emulate_tls()) {
+ tls_reader_byte_flow_.set_input(input_);
+ tls_reader_byte_flow_ >> aes_ctr_byte_flow_;
+ } else {
+ aes_ctr_byte_flow_.set_input(input_);
+ }
aes_ctr_byte_flow_ >> byte_flow_sink_;
output_key_ = as<UInt256>(header.data() + 8);
- output_state_.init(output_key_, as<UInt128>(header.data() + 8 + 32));
- output_->append(header_slice.substr(0, 56));
+ fix_key(output_key_);
+ output_state_.init(as_slice(output_key_), Slice(header.data() + 8 + 32, 16));
+ header_ = header;
output_state_.encrypt(header_slice, header_slice);
- output_->append(header_slice.substr(56, 8));
+ MutableSlice(header_).substr(56).copy_from(header_slice.substr(56));
+}
+
+Result<size_t> ObfuscatedTransport::read_next(BufferSlice *message, uint32 *quick_ack) {
+ if (secret_.emulate_tls()) {
+ tls_reader_byte_flow_.wakeup();
+ } else {
+ aes_ctr_byte_flow_.wakeup();
+ }
+ return impl_.read_from_stream(byte_flow_sink_.get_output(), message, quick_ack);
+}
+
+void ObfuscatedTransport::write(BufferWriter &&message, bool quick_ack) {
+ impl_.write_prepare_inplace(&message, quick_ack);
+ output_state_.encrypt(message.as_slice(), message.as_slice());
+ if (secret_.emulate_tls()) {
+ do_write_tls(std::move(message));
+ } else {
+ do_write_main(std::move(message));
+ }
+}
+
+void ObfuscatedTransport::do_write_main(BufferWriter &&message) {
+ BufferBuilder builder(std::move(message));
+ if (!header_.empty()) {
+ builder.prepend(header_);
+ header_ = {};
+ }
+ do_write(builder.extract());
+}
+
+void ObfuscatedTransport::do_write_tls(BufferWriter &&message) {
+ CHECK(header_.size() <= MAX_TLS_PACKET_LENGTH);
+ if (message.size() + header_.size() > MAX_TLS_PACKET_LENGTH) {
+ auto buffer_slice = message.as_buffer_slice();
+ auto slice = buffer_slice.as_slice();
+ while (!slice.empty()) {
+ auto buf = buffer_slice.from_slice(slice.substr(0, MAX_TLS_PACKET_LENGTH - header_.size()));
+ slice.remove_prefix(buf.size());
+ BufferBuilder builder;
+ builder.append(std::move(buf));
+ do_write_tls(std::move(builder));
+ }
+ return;
+ }
+
+ BufferBuilder builder(std::move(message));
+ do_write_tls(std::move(builder));
+}
+
+void ObfuscatedTransport::do_write_tls(BufferBuilder &&builder) {
+ if (!header_.empty()) {
+ builder.prepend(header_);
+ header_ = {};
+ }
+
+ size_t size = builder.size();
+ CHECK(size <= MAX_TLS_PACKET_LENGTH);
+
+ char buf[] = "\x17\x03\x03\x00\x00";
+ buf[3] = static_cast<char>((size >> 8) & 0xff);
+ buf[4] = static_cast<char>(size & 0xff);
+ builder.prepend(Slice(buf, 5));
+
+ if (is_first_tls_packet_) {
+ is_first_tls_packet_ = false;
+ Slice first_prefix("\x14\x03\x03\x00\x01\x01");
+ builder.prepend(first_prefix);
+ }
+
+ do_write(builder.extract());
+}
+
+void ObfuscatedTransport::do_write(BufferSlice &&message) {
+ output_->append(std::move(message));
}
} // namespace tcp
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.h b/protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.h
index d53048478e..2af5da0a0d 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/TcpTransport.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,18 +7,23 @@
#pragma once
#include "td/mtproto/IStreamTransport.h"
+#include "td/mtproto/ProxySecret.h"
+#include "td/mtproto/TlsReaderByteFlow.h"
+#include "td/mtproto/TransportType.h"
#include "td/utils/AesCtrByteFlow.h"
#include "td/utils/buffer.h"
#include "td/utils/ByteFlow.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/Status.h"
+#include "td/utils/UInt.h"
namespace td {
namespace mtproto {
namespace tcp {
+
class ITransport {
// Writes packet into message.
// Returns 0 if everything is ok, and [expected_size] otherwise.
@@ -44,115 +49,158 @@ class ITransport {
virtual ~ITransport() = default;
};
-class AbridgedTransport : public ITransport {
+class AbridgedTransport final : public ITransport {
public:
- size_t read_from_stream(ChainBufferReader *stream, BufferSlice *message, uint32 *quick_ack) override;
- void write_prepare_inplace(BufferWriter *message, bool quick_ack) override;
- void init_output_stream(ChainBufferWriter *stream) override;
- bool support_quick_ack() const override {
+ size_t read_from_stream(ChainBufferReader *stream, BufferSlice *message, uint32 *quick_ack) final;
+ void write_prepare_inplace(BufferWriter *message, bool quick_ack) final;
+ void init_output_stream(ChainBufferWriter *stream) final;
+ bool support_quick_ack() const final {
return false;
}
};
-class IntermediateTransport : ITransport {
+class IntermediateTransport final : public ITransport {
public:
- size_t read_from_stream(ChainBufferReader *stream, BufferSlice *message, uint32 *quick_ack) override;
- void write_prepare_inplace(BufferWriter *message, bool quick_ack) override;
- void init_output_stream(ChainBufferWriter *stream) override;
- bool support_quick_ack() const override {
+ explicit IntermediateTransport(bool with_padding) : with_padding_(with_padding) {
+ }
+ size_t read_from_stream(ChainBufferReader *stream, BufferSlice *message, uint32 *quick_ack) final;
+ void write_prepare_inplace(BufferWriter *message, bool quick_ack) final;
+ void init_output_stream(ChainBufferWriter *stream) final;
+ bool support_quick_ack() const final {
return true;
}
+ bool with_padding() const {
+ return with_padding_;
+ }
+
+ private:
+ bool with_padding_;
};
using TransportImpl = IntermediateTransport;
-class OldTransport : public IStreamTransport {
+class OldTransport final : public IStreamTransport {
public:
OldTransport() = default;
- Result<size_t> read_next(BufferSlice *message, uint32 *quick_ack) override TD_WARN_UNUSED_RESULT {
+ Result<size_t> read_next(BufferSlice *message, uint32 *quick_ack) final TD_WARN_UNUSED_RESULT {
return impl_.read_from_stream(input_, message, quick_ack);
}
- bool support_quick_ack() const override {
+ bool support_quick_ack() const final {
return impl_.support_quick_ack();
}
- void write(BufferWriter &&message, bool quick_ack) override {
+ void write(BufferWriter &&message, bool quick_ack) final {
impl_.write_prepare_inplace(&message, quick_ack);
output_->append(message.as_buffer_slice());
}
- void init(ChainBufferReader *input, ChainBufferWriter *output) override {
+ void init(ChainBufferReader *input, ChainBufferWriter *output) final {
input_ = input;
output_ = output;
impl_.init_output_stream(output_);
}
- bool can_read() const override {
+ bool can_read() const final {
return true;
}
- bool can_write() const override {
+ bool can_write() const final {
return true;
}
- size_t max_prepend_size() const override {
+ size_t max_prepend_size() const final {
return 4;
}
- TransportType get_type() const override {
- return TransportType::Tcp;
+
+ size_t max_append_size() const final {
+ return 15;
+ }
+
+ TransportType get_type() const final {
+ return TransportType{TransportType::Tcp, 0, ProxySecret()};
+ }
+
+ bool use_random_padding() const final {
+ return false;
}
private:
- TransportImpl impl_;
- ChainBufferReader *input_;
- ChainBufferWriter *output_;
+ TransportImpl impl_{false};
+ ChainBufferReader *input_{nullptr};
+ ChainBufferWriter *output_{nullptr};
};
-class ObfuscatedTransport : public IStreamTransport {
+class ObfuscatedTransport final : public IStreamTransport {
public:
- ObfuscatedTransport() = default;
- Result<size_t> read_next(BufferSlice *message, uint32 *quick_ack) override TD_WARN_UNUSED_RESULT {
- aes_ctr_byte_flow_.wakeup();
- return impl_.read_from_stream(byte_flow_sink_.get_output(), message, quick_ack);
+ ObfuscatedTransport(int16 dc_id, ProxySecret secret)
+ : dc_id_(dc_id), secret_(std::move(secret)), impl_(secret_.use_random_padding()) {
}
- bool support_quick_ack() const override {
+ Result<size_t> read_next(BufferSlice *message, uint32 *quick_ack) final TD_WARN_UNUSED_RESULT;
+
+ bool support_quick_ack() const final {
return impl_.support_quick_ack();
}
- void write(BufferWriter &&message, bool quick_ack) override {
- impl_.write_prepare_inplace(&message, quick_ack);
- auto slice = message.as_buffer_slice();
- output_state_.encrypt(slice.as_slice(), slice.as_slice());
- output_->append(std::move(slice));
- }
+ void write(BufferWriter &&message, bool quick_ack) final;
- void init(ChainBufferReader *input, ChainBufferWriter *output) override;
+ void init(ChainBufferReader *input, ChainBufferWriter *output) final;
- bool can_read() const override {
+ bool can_read() const final {
return true;
}
- bool can_write() const override {
+ bool can_write() const final {
return true;
}
- size_t max_prepend_size() const override {
- return 4;
+ size_t max_prepend_size() const final {
+ size_t res = 4;
+ if (secret_.emulate_tls()) {
+ res += 5;
+ if (is_first_tls_packet_) {
+ res += 6;
+ }
+ }
+ res += header_.size();
+ if (res & 3) {
+ res += 4 - (res & 3);
+ }
+ return res;
}
- TransportType get_type() const override {
- return TransportType::ObfuscatedTcp;
+ size_t max_append_size() const final {
+ return 15;
+ }
+
+ TransportType get_type() const final {
+ return TransportType{TransportType::ObfuscatedTcp, dc_id_, secret_};
+ }
+
+ bool use_random_padding() const final {
+ return secret_.use_random_padding();
}
private:
+ int16 dc_id_;
+ bool is_first_tls_packet_{true};
+ ProxySecret secret_;
+ std::string header_;
TransportImpl impl_;
+ TlsReaderByteFlow tls_reader_byte_flow_;
AesCtrByteFlow aes_ctr_byte_flow_;
ByteFlowSink byte_flow_sink_;
- ChainBufferReader *input_;
+ ChainBufferReader *input_ = nullptr;
+
+ static constexpr int32 MAX_TLS_PACKET_LENGTH = 2878;
// TODO: use ByteFlow?
// One problem is that BufferedFd owns output_buffer_
// The other problem is that first 56 bytes must be sent unencrypted.
UInt256 output_key_;
AesCtrState output_state_;
- ChainBufferWriter *output_;
+ ChainBufferWriter *output_ = nullptr;
+
+ void do_write_tls(BufferWriter &&message);
+ void do_write_tls(BufferBuilder &&builder);
+ void do_write_main(BufferWriter &&message);
+ void do_write(BufferSlice &&message);
};
using Transport = ObfuscatedTransport;
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/TlsInit.cpp b/protocols/Telegram/tdlib/td/td/mtproto/TlsInit.cpp
new file mode 100644
index 0000000000..694023e1a3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/TlsInit.cpp
@@ -0,0 +1,517 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/TlsInit.h"
+
+#include "td/mtproto/ProxySecret.h"
+
+#include "td/utils/as.h"
+#include "td/utils/BigNum.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Span.h"
+#include "td/utils/Time.h"
+
+#include <algorithm>
+#include <cstring>
+
+namespace td {
+namespace mtproto {
+
+void Grease::init(MutableSlice res) {
+ Random::secure_bytes(res);
+ for (auto &c : res) {
+ c = static_cast<char>((c & 0xF0) + 0x0A);
+ }
+ for (size_t i = 1; i < res.size(); i += 2) {
+ if (res[i] == res[i - 1]) {
+ res[i] ^= 0x10;
+ }
+ }
+}
+
+class TlsHello {
+ public:
+ struct Op {
+ enum class Type { String, Random, Zero, Domain, Grease, Key, BeginScope, EndScope };
+ Type type;
+ int length;
+ int seed;
+ std::string data;
+
+ static Op string(Slice str) {
+ Op res;
+ res.type = Type::String;
+ res.data = str.str();
+ return res;
+ }
+ static Op random(int length) {
+ Op res;
+ res.type = Type::Random;
+ res.length = length;
+ return res;
+ }
+ static Op zero(int length) {
+ Op res;
+ res.type = Type::Zero;
+ res.length = length;
+ return res;
+ }
+ static Op domain() {
+ Op res;
+ res.type = Type::Domain;
+ return res;
+ }
+ static Op grease(int seed) {
+ Op res;
+ res.type = Type::Grease;
+ res.seed = seed;
+ return res;
+ }
+ static Op begin_scope() {
+ Op res;
+ res.type = Type::BeginScope;
+ return res;
+ }
+ static Op end_scope() {
+ Op res;
+ res.type = Type::EndScope;
+ return res;
+ }
+ static Op key() {
+ Op res;
+ res.type = Type::Key;
+ return res;
+ }
+ };
+
+ static const TlsHello &get_default() {
+ static TlsHello result = [] {
+ TlsHello res;
+#if TD_DARWIN
+ res.ops_ = {
+ Op::string("\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03"),
+ Op::zero(32),
+ Op::string("\x20"),
+ Op::random(32),
+ Op::string("\x00\x36"),
+ Op::grease(0),
+ Op::string("\x13\x01\x13\x02\x13\x03\xc0\x2c\xc0\x2b\xcc\xa9\xc0\x30\xc0\x2f\xcc\xa8\xc0\x24\xc0\x23\xc0\x0a"
+ "\xc0\x09\xc0\x28\xc0\x27\xc0\x14\xc0\x13\x00\x9d\x00\x9c\x00\x3d\x00\x3c\x00\x35\x00\x2f\xc0\x08"
+ "\xc0\x12\x00\x0a\x01\x00\x01\x7d"),
+ Op::grease(2),
+ Op::string("\x00\x00\x00\x00"),
+ Op::begin_scope(),
+ Op::begin_scope(),
+ Op::string("\x00"),
+ Op::begin_scope(),
+ Op::domain(),
+ Op::end_scope(),
+ Op::end_scope(),
+ Op::end_scope(),
+ Op::string("\x00\x17\x00\x00\xff\x01\x00\x01\x00\x00\x0a\x00\x0c\x00\x0a"),
+ Op::grease(4),
+ Op::string("\x00\x1d\x00\x17\x00\x18\x00\x19\x00\x0b\x00\x02\x01\x00\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08"
+ "\x68\x74\x74\x70\x2f\x31\x2e\x31\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x0d\x00\x18\x00\x16\x04"
+ "\x03\x08\x04\x04\x01\x05\x03\x02\x03\x08\x05\x08\x05\x05\x01\x08\x06\x06\x01\x02\x01\x00\x12\x00"
+ "\x00\x00\x33\x00\x2b\x00\x29"),
+ Op::grease(4),
+ Op::string("\x00\x01\x00\x00\x1d\x00\x20"),
+ Op::key(),
+ Op::string("\x00\x2d\x00\x02\x01\x01\x00\x2b\x00\x0b\x0a"),
+ Op::grease(6),
+ Op::string("\x03\x04\x03\x03\x03\x02\x03\x01"),
+ Op::grease(3),
+ Op::string("\x00\x01\x00\x00\x15")};
+#else
+ res.ops_ = {
+ Op::string("\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03"),
+ Op::zero(32),
+ Op::string("\x20"),
+ Op::random(32),
+ Op::string("\x00\x20"),
+ Op::grease(0),
+ Op::string("\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9\xcc\xa8\xc0\x13\xc0\x14\x00\x9c"
+ "\x00\x9d\x00\x2f\x00\x35\x01\x00\x01\x93"),
+ Op::grease(2),
+ Op::string("\x00\x00\x00\x00"),
+ Op::begin_scope(),
+ Op::begin_scope(),
+ Op::string("\x00"),
+ Op::begin_scope(),
+ Op::domain(),
+ Op::end_scope(),
+ Op::end_scope(),
+ Op::end_scope(),
+ Op::string("\x00\x17\x00\x00\xff\x01\x00\x01\x00\x00\x0a\x00\x0a\x00\x08"),
+ Op::grease(4),
+ Op::string(
+ "\x00\x1d\x00\x17\x00\x18\x00\x0b\x00\x02\x01\x00\x00\x23\x00\x00\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08"
+ "\x68\x74\x74\x70\x2f\x31\x2e\x31\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x0d\x00\x12\x00\x10\x04\x03\x08"
+ "\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06\x01\x00\x12\x00\x00\x00\x33\x00\x2b\x00\x29"),
+ Op::grease(4),
+ Op::string("\x00\x01\x00\x00\x1d\x00\x20"),
+ Op::key(),
+ Op::string("\x00\x2d\x00\x02\x01\x01\x00\x2b\x00\x0b\x0a"),
+ Op::grease(6),
+ Op::string("\x03\x04\x03\x03\x03\x02\x03\x01\x00\x1b\x00\x03\x02\x00\x02"),
+ Op::grease(3),
+ Op::string("\x00\x01\x00\x00\x15")};
+#endif
+ return res;
+ }();
+ return result;
+ }
+
+ Span<Op> get_ops() const {
+ return ops_;
+ }
+
+ size_t get_grease_size() const {
+ return grease_size_;
+ }
+
+ private:
+ std::vector<Op> ops_;
+ size_t grease_size_ = 7;
+};
+
+class TlsHelloContext {
+ public:
+ TlsHelloContext(size_t grease_size, std::string domain) : grease_(grease_size, '\0'), domain_(std::move(domain)) {
+ Grease::init(grease_);
+ }
+
+ char get_grease(size_t i) const {
+ CHECK(i < grease_.size());
+ return grease_[i];
+ }
+ size_t get_grease_size() const {
+ return grease_.size();
+ }
+ Slice get_domain() const {
+ return Slice(domain_).substr(0, ProxySecret::MAX_DOMAIN_LENGTH);
+ }
+
+ private:
+ std::string grease_;
+ std::string domain_;
+};
+
+class TlsHelloCalcLength {
+ public:
+ void do_op(const TlsHello::Op &op, const TlsHelloContext *context) {
+ if (status_.is_error()) {
+ return;
+ }
+ using Type = TlsHello::Op::Type;
+ switch (op.type) {
+ case Type::String:
+ size_ += op.data.size();
+ break;
+ case Type::Random:
+ if (op.length <= 0 || op.length > 1024) {
+ return on_error(Status::Error("Invalid random length"));
+ }
+ size_ += op.length;
+ break;
+ case Type::Zero:
+ if (op.length <= 0 || op.length > 1024) {
+ return on_error(Status::Error("Invalid zero length"));
+ }
+ size_ += op.length;
+ break;
+ case Type::Domain:
+ CHECK(context);
+ size_ += context->get_domain().size();
+ break;
+ case Type::Grease:
+ CHECK(context);
+ if (op.seed < 0 || static_cast<size_t>(op.seed) >= context->get_grease_size()) {
+ return on_error(Status::Error("Invalid grease seed"));
+ }
+ size_ += 2;
+ break;
+ case Type::Key:
+ size_ += 32;
+ break;
+ case Type::BeginScope:
+ size_ += 2;
+ scope_offset_.push_back(size_);
+ break;
+ case Type::EndScope: {
+ if (scope_offset_.empty()) {
+ return on_error(Status::Error("Unbalanced scopes"));
+ }
+ auto begin_offset = scope_offset_.back();
+ scope_offset_.pop_back();
+ auto end_offset = size_;
+ auto size = end_offset - begin_offset;
+ if (size >= (1 << 14)) {
+ return on_error(Status::Error("Scope is too big"));
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ Result<size_t> finish() {
+ if (size_ > 514) {
+ on_error(Status::Error("Too long for zero padding"));
+ }
+ if (size_ < 11 + 32) {
+ on_error(Status::Error("Too small for hash"));
+ }
+ int zero_pad = 515 - static_cast<int>(size_);
+ using Op = TlsHello::Op;
+ do_op(Op::begin_scope(), nullptr);
+ do_op(Op::zero(zero_pad), nullptr);
+ do_op(Op::end_scope(), nullptr);
+ if (!scope_offset_.empty()) {
+ on_error(Status::Error("Unbalanced scopes"));
+ }
+ TRY_STATUS(std::move(status_));
+ return size_;
+ }
+
+ private:
+ size_t size_{0};
+ Status status_;
+ std::vector<size_t> scope_offset_;
+
+ void on_error(Status error) {
+ if (status_.is_ok()) {
+ status_ = std::move(error);
+ }
+ }
+};
+
+class TlsHelloStore {
+ public:
+ explicit TlsHelloStore(MutableSlice dest) : data_(dest), dest_(dest) {
+ }
+ void do_op(const TlsHello::Op &op, const TlsHelloContext *context) {
+ using Type = TlsHello::Op::Type;
+ switch (op.type) {
+ case Type::String:
+ dest_.copy_from(op.data);
+ dest_.remove_prefix(op.data.size());
+ break;
+ case Type::Random:
+ Random::secure_bytes(dest_.substr(0, op.length));
+ dest_.remove_prefix(op.length);
+ break;
+ case Type::Zero:
+ std::memset(dest_.begin(), 0, op.length);
+ dest_.remove_prefix(op.length);
+ break;
+ case Type::Domain: {
+ CHECK(context);
+ auto domain = context->get_domain();
+ dest_.copy_from(domain);
+ dest_.remove_prefix(domain.size());
+ break;
+ }
+ case Type::Grease: {
+ CHECK(context)
+ auto grease = context->get_grease(op.seed);
+ dest_[0] = grease;
+ dest_[1] = grease;
+ dest_.remove_prefix(2);
+ break;
+ }
+ case Type::Key: {
+ BigNum mod = BigNum::from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed").move_as_ok();
+ BigNumContext big_num_context;
+ auto key = dest_.substr(0, 32);
+ while (true) {
+ Random::secure_bytes(key);
+ key[31] = static_cast<char>(key[31] & 127);
+
+ BigNum x = BigNum::from_binary(key);
+ BigNum y = get_y2(x, mod, big_num_context);
+ if (is_quadratic_residue(y)) {
+ for (int i = 0; i < 3; i++) {
+ x = get_double_x(x, mod, big_num_context);
+ }
+ key.copy_from(x.to_le_binary(32));
+ break;
+ }
+ }
+ dest_.remove_prefix(32);
+ break;
+ }
+ case Type::BeginScope:
+ scope_offset_.push_back(get_offset());
+ dest_.remove_prefix(2);
+ break;
+ case Type::EndScope: {
+ CHECK(!scope_offset_.empty());
+ auto begin_offset = scope_offset_.back();
+ scope_offset_.pop_back();
+ auto end_offset = get_offset();
+ size_t size = end_offset - begin_offset - 2;
+ CHECK(size < (1 << 14));
+ data_[begin_offset] = static_cast<char>((size >> 8) & 0xff);
+ data_[begin_offset + 1] = static_cast<char>(size & 0xff);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ void finish(Slice secret, int32 unix_time) {
+ int zero_pad = 515 - static_cast<int>(get_offset());
+ using Op = TlsHello::Op;
+ do_op(Op::begin_scope(), nullptr);
+ do_op(Op::zero(zero_pad), nullptr);
+ do_op(Op::end_scope(), nullptr);
+
+ auto hash_dest = data_.substr(11, 32);
+ hmac_sha256(secret, data_, hash_dest);
+ int32 old = as<int32>(hash_dest.substr(28).data());
+ as<int32>(hash_dest.substr(28).data()) = old ^ unix_time;
+ CHECK(dest_.empty());
+ }
+
+ private:
+ MutableSlice data_;
+ MutableSlice dest_;
+ std::vector<size_t> scope_offset_;
+
+ static BigNum get_y2(BigNum &x, const BigNum &mod, BigNumContext &big_num_context) {
+ // returns y = x^3 + 486662 * x^2 + x
+ BigNum y = x.clone();
+ BigNum coef = BigNum::from_decimal("486662").move_as_ok();
+ BigNum::mod_add(y, y, coef, mod, big_num_context);
+ BigNum::mod_mul(y, y, x, mod, big_num_context);
+ BigNum one = BigNum::from_decimal("1").move_as_ok();
+ BigNum::mod_add(y, y, one, mod, big_num_context);
+ BigNum::mod_mul(y, y, x, mod, big_num_context);
+ return y;
+ }
+
+ static BigNum get_double_x(BigNum &x, const BigNum &mod, BigNumContext &big_num_context) {
+ // returns x_2 = (x^2 - 1)^2/(4*y^2)
+ BigNum denominator = get_y2(x, mod, big_num_context);
+ BigNum coef = BigNum::from_decimal("4").move_as_ok();
+ BigNum::mod_mul(denominator, denominator, coef, mod, big_num_context);
+
+ BigNum numerator;
+ BigNum::mod_mul(numerator, x, x, mod, big_num_context);
+ BigNum one = BigNum::from_decimal("1").move_as_ok();
+ BigNum::mod_sub(numerator, numerator, one, mod, big_num_context);
+ BigNum::mod_mul(numerator, numerator, numerator, mod, big_num_context);
+
+ BigNum::mod_inverse(denominator, denominator, mod, big_num_context);
+ BigNum::mod_mul(numerator, numerator, denominator, mod, big_num_context);
+ return numerator;
+ }
+
+ static bool is_quadratic_residue(const BigNum &a) {
+ // 2^255 - 19
+ BigNum mod = BigNum::from_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed").move_as_ok();
+ // (mod - 1) / 2 = 2^254 - 10
+ BigNum pow = BigNum::from_hex("3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6").move_as_ok();
+
+ BigNumContext context;
+ BigNum r;
+ BigNum::mod_exp(r, a, pow, mod, context);
+
+ return r.to_decimal() == "1";
+ }
+
+ size_t get_offset() const {
+ return data_.size() - dest_.size();
+ }
+};
+
+class TlsObfusaction {
+ public:
+ static std::string generate_header(std::string domain, Slice secret, int32 unix_time) {
+ CHECK(!domain.empty());
+ CHECK(secret.size() == 16);
+
+ auto &hello = TlsHello::get_default();
+ TlsHelloContext context(hello.get_grease_size(), std::move(domain));
+ TlsHelloCalcLength calc_length;
+ for (auto &op : hello.get_ops()) {
+ calc_length.do_op(op, &context);
+ }
+ auto length = calc_length.finish().move_as_ok();
+ std::string data(length, '\0');
+ TlsHelloStore storer(data);
+ for (auto &op : hello.get_ops()) {
+ storer.do_op(op, &context);
+ }
+ storer.finish(secret, unix_time);
+ return data;
+ }
+};
+
+void TlsInit::send_hello() {
+ auto hello =
+ TlsObfusaction::generate_header(username_, password_, static_cast<int32>(Time::now() + server_time_difference_));
+ hello_rand_ = hello.substr(11, 32);
+ fd_.output_buffer().append(hello);
+ state_ = State::WaitHelloResponse;
+}
+
+Status TlsInit::wait_hello_response() {
+ auto it = fd_.input_buffer().clone();
+ for (auto first : {Slice("\x16\x03\x03"), Slice("\x14\x03\x03\x00\x01\x01\x17\x03\x03")}) {
+ if (it.size() < first.size() + 2) {
+ return Status::OK();
+ }
+
+ std::string got_first(first.size(), '\0');
+ it.advance(first.size(), got_first);
+ if (first != got_first) {
+ return Status::Error("First part of response to hello is invalid");
+ }
+
+ uint8 tmp[2];
+ it.advance(2, MutableSlice(tmp, 2));
+ size_t skip_size = (tmp[0] << 8) + tmp[1];
+ if (it.size() < skip_size) {
+ return Status::OK();
+ }
+ it.advance(skip_size);
+ }
+
+ auto response = fd_.input_buffer().cut_head(it.begin().clone()).move_as_buffer_slice();
+ auto response_rand_slice = response.as_slice().substr(11, 32);
+ auto response_rand = response_rand_slice.str();
+ std::fill(response_rand_slice.begin(), response_rand_slice.end(), '\0');
+ std::string hash_dest(32, '\0');
+ hmac_sha256(password_, PSLICE() << hello_rand_ << response.as_slice(), hash_dest);
+ if (hash_dest != response_rand) {
+ return Status::Error("Response hash mismatch");
+ }
+
+ stop();
+ return Status::OK();
+}
+
+Status TlsInit::loop_impl() {
+ switch (state_) {
+ case State::SendHello:
+ send_hello();
+ break;
+ case State::WaitHelloResponse:
+ TRY_STATUS(wait_hello_response());
+ break;
+ }
+ return Status::OK();
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/TlsInit.h b/protocols/Telegram/tdlib/td/td/mtproto/TlsInit.h
new file mode 100644
index 0000000000..9547ec65ab
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/TlsInit.h
@@ -0,0 +1,50 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/net/TransparentProxy.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/port/IPAddress.h"
+#include "td/utils/port/SocketFd.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+namespace mtproto {
+
+class Grease {
+ public:
+ static void init(MutableSlice res);
+};
+
+class TlsInit final : public TransparentProxy {
+ public:
+ TlsInit(SocketFd socket_fd, string domain, string secret, unique_ptr<Callback> callback, ActorShared<> parent,
+ double server_time_difference)
+ : TransparentProxy(std::move(socket_fd), IPAddress(), std::move(domain), std::move(secret), std::move(callback),
+ std::move(parent))
+ , server_time_difference_(server_time_difference) {
+ }
+
+ private:
+ double server_time_difference_{0};
+ enum class State {
+ SendHello,
+ WaitHelloResponse,
+ } state_ = State::SendHello;
+ std::string hello_rand_;
+
+ void send_hello();
+ Status wait_hello_response();
+
+ Status loop_impl() final;
+};
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/TlsReaderByteFlow.cpp b/protocols/Telegram/tdlib/td/td/mtproto/TlsReaderByteFlow.cpp
new file mode 100644
index 0000000000..8b545b90cc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/TlsReaderByteFlow.cpp
@@ -0,0 +1,40 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/TlsReaderByteFlow.h"
+
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+namespace mtproto {
+
+bool TlsReaderByteFlow::loop() {
+ if (input_->size() < 5) {
+ set_need_size(5);
+ return false;
+ }
+
+ auto it = input_->clone();
+ uint8 buf[5];
+ it.advance(5, MutableSlice(buf, 5));
+ if (Slice(buf, 3) != Slice("\x17\x03\x03")) {
+ close_input(Status::Error("Invalid bytes at the beginning of a packet (emulated tls)"));
+ return false;
+ }
+ size_t len = (buf[3] << 8) | buf[4];
+ if (it.size() < len) {
+ set_need_size(5 + len);
+ return false;
+ }
+
+ output_.append(it.cut_head(len));
+ *input_ = std::move(it);
+ return true;
+}
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/TlsReaderByteFlow.h b/protocols/Telegram/tdlib/td/td/mtproto/TlsReaderByteFlow.h
new file mode 100644
index 0000000000..c229c28fea
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/TlsReaderByteFlow.h
@@ -0,0 +1,20 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/ByteFlow.h"
+
+namespace td {
+namespace mtproto {
+
+class TlsReaderByteFlow final : public ByteFlowBase {
+ public:
+ bool loop() final;
+};
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp b/protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp
index 0e65c82ee7..82247af84e 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,26 +7,134 @@
#include "td/mtproto/Transport.h"
#include "td/mtproto/AuthKey.h"
-#include "td/mtproto/crypto.h"
+#include "td/mtproto/KDF.h"
+#include "td/utils/as.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include <array>
+#include <tuple>
namespace td {
+
+int VERBOSITY_NAME(raw_mtproto) = VERBOSITY_NAME(DEBUG) + 10;
+
namespace mtproto {
-// mtproto v1.0
+#pragma pack(push, 4)
+#if TD_MSVC
+#pragma warning(push)
+#pragma warning(disable : 4200)
+#endif
+
+struct CryptoHeader {
+ uint64 auth_key_id;
+ UInt128 message_key;
+
+ // encrypted part
+ uint64 salt;
+ uint64 session_id;
+
+ // It is weird to generate message_id and seq_no while writing a packet.
+ //
+ // uint64 message_id;
+ // uint32 seq_no;
+ // uint32 message_data_length;
+ uint8 data[0]; // use compiler extension
+
+ static size_t encrypted_header_size() {
+ return sizeof(salt) + sizeof(session_id);
+ }
+
+ uint8 *encrypt_begin() {
+ return reinterpret_cast<uint8 *>(&salt);
+ }
+
+ const uint8 *encrypt_begin() const {
+ return reinterpret_cast<const uint8 *>(&salt);
+ }
+
+ CryptoHeader() = delete;
+ CryptoHeader(const CryptoHeader &) = delete;
+ CryptoHeader(CryptoHeader &&) = delete;
+ CryptoHeader &operator=(const CryptoHeader &) = delete;
+ CryptoHeader &operator=(CryptoHeader &&) = delete;
+ ~CryptoHeader() = delete;
+};
+
+struct CryptoPrefix {
+ uint64 message_id;
+ uint32 seq_no;
+ uint32 message_data_length;
+};
+
+struct EndToEndHeader {
+ uint64 auth_key_id;
+ UInt128 message_key;
+
+ // encrypted part
+ // uint32 message_data_length;
+ uint8 data[0]; // use compiler extension
+
+ static size_t encrypted_header_size() {
+ return 0;
+ }
+
+ uint8 *encrypt_begin() {
+ return reinterpret_cast<uint8 *>(&data);
+ }
+
+ const uint8 *encrypt_begin() const {
+ return reinterpret_cast<const uint8 *>(&data);
+ }
+
+ EndToEndHeader() = delete;
+ EndToEndHeader(const EndToEndHeader &) = delete;
+ EndToEndHeader(EndToEndHeader &&) = delete;
+ EndToEndHeader &operator=(const EndToEndHeader &) = delete;
+ EndToEndHeader &operator=(EndToEndHeader &&) = delete;
+ ~EndToEndHeader() = delete;
+};
+
+struct EndToEndPrefix {
+ uint32 message_data_length;
+};
+
+struct NoCryptoHeader {
+ uint64 auth_key_id;
+
+ // message_id is removed from CryptoHeader. Should be removed from here too.
+ //
+ // int64 message_id;
+ // uint32 message_data_length;
+ uint8 data[0]; // use compiler extension
+
+ NoCryptoHeader() = delete;
+ NoCryptoHeader(const NoCryptoHeader &) = delete;
+ NoCryptoHeader(NoCryptoHeader &&) = delete;
+ NoCryptoHeader &operator=(const NoCryptoHeader &) = delete;
+ NoCryptoHeader &operator=(NoCryptoHeader &&) = delete;
+ ~NoCryptoHeader() = delete;
+};
+
+#if TD_MSVC
+#pragma warning(pop)
+#endif
+#pragma pack(pop)
+
+// MTProto v1.0
template <class HeaderT>
-std::tuple<uint32, UInt128> Transport::calc_message_ack_and_key(const HeaderT &head, size_t data_size) {
+std::pair<uint32, UInt128> Transport::calc_message_ack_and_key(const HeaderT &head, size_t data_size) {
Slice part(head.encrypt_begin(), head.data + data_size);
UInt<160> message_sha1;
sha1(part, message_sha1.raw);
- return std::make_tuple(as<uint32>(message_sha1.raw) | (1u << 31), as<UInt128>(message_sha1.raw + 4));
+ return std::make_pair(as<uint32>(message_sha1.raw) | (1u << 31), as<UInt128>(message_sha1.raw + 4));
}
template <class HeaderT>
@@ -36,31 +144,29 @@ size_t Transport::calc_crypto_size(size_t data_size) {
return raw_size + ((enc_size + data_size + 15) & ~15);
}
-// mtproto v2.0
-std::tuple<uint32, UInt128> Transport::calc_message_key2(const AuthKey &auth_key, int X, Slice to_encrypt) {
+// MTProto v2.0
+std::pair<uint32, UInt128> Transport::calc_message_key2(const AuthKey &auth_key, int X, Slice to_encrypt) {
// msg_key_large = SHA256 (substr (auth_key, 88+x, 32) + plaintext + random_padding);
Sha256State state;
- sha256_init(&state);
- sha256_update(Slice(auth_key.key()).substr(88 + X, 32), &state);
- sha256_update(to_encrypt, &state);
+ state.init();
+ state.feed(Slice(auth_key.key()).substr(88 + X, 32));
+ state.feed(to_encrypt);
uint8 msg_key_large_raw[32];
MutableSlice msg_key_large(msg_key_large_raw, sizeof(msg_key_large_raw));
- sha256_final(&state, msg_key_large);
+ state.extract(msg_key_large, true);
// msg_key = substr (msg_key_large, 8, 16);
- UInt128 res_raw;
- MutableSlice res(res_raw.raw, sizeof(res_raw.raw));
- res.copy_from(msg_key_large.substr(8, 16));
+ UInt128 res;
+ as_slice(res).copy_from(msg_key_large.substr(8, 16));
- return std::make_tuple(as<uint32>(msg_key_large_raw) | (1u << 31), res_raw);
+ return std::make_pair(as<uint32>(msg_key_large_raw) | (1u << 31), res);
}
-template <class HeaderT>
-size_t Transport::calc_crypto_size2(size_t data_size) {
- size_t enc_size = HeaderT::encrypted_header_size();
- size_t raw_size = sizeof(HeaderT) - enc_size;
+namespace {
+size_t do_calc_crypto_size2_basic(size_t data_size, size_t enc_size, size_t raw_size) {
size_t encrypted_size = (enc_size + data_size + 12 + 15) & ~15;
+
std::array<size_t, 10> sizes{{64, 128, 192, 256, 384, 512, 768, 1024, 1280}};
for (auto size : sizes) {
if (encrypted_size <= size) {
@@ -72,19 +178,41 @@ size_t Transport::calc_crypto_size2(size_t data_size) {
return raw_size + encrypted_size;
}
+size_t do_calc_crypto_size2_rand(size_t data_size, size_t enc_size, size_t raw_size) {
+ size_t rand_data_size = Random::secure_uint32() & 0xff;
+ size_t encrypted_size = (enc_size + data_size + rand_data_size + 12 + 15) & ~15;
+ return raw_size + encrypted_size;
+}
+} // namespace
+
+template <class HeaderT>
+size_t Transport::calc_crypto_size2(size_t data_size, PacketInfo *info) {
+ if (info->size != 0) {
+ return info->size;
+ }
+
+ size_t enc_size = HeaderT::encrypted_header_size();
+ size_t raw_size = sizeof(HeaderT) - enc_size;
+ if (info->use_random_padding) {
+ info->size = narrow_cast<uint32>(do_calc_crypto_size2_rand(data_size, enc_size, raw_size));
+ } else {
+ info->size = narrow_cast<uint32>(do_calc_crypto_size2_basic(data_size, enc_size, raw_size));
+ }
+ return info->size;
+}
+
size_t Transport::calc_no_crypto_size(size_t data_size) {
return sizeof(NoCryptoHeader) + data_size;
}
Status Transport::read_no_crypto(MutableSlice message, PacketInfo *info, MutableSlice *data) {
if (message.size() < sizeof(NoCryptoHeader)) {
- return Status::Error(PSLICE() << "Invalid mtproto message: too small [message.size()=" << message.size()
+ return Status::Error(PSLICE() << "Invalid MTProto message: too small [message.size() = " << message.size()
<< "] < [sizeof(NoCryptoHeader) = " << sizeof(NoCryptoHeader) << "]");
}
- auto &header = as<NoCryptoHeader>(message.begin());
size_t data_size = message.size() - sizeof(NoCryptoHeader);
CHECK(message.size() == calc_no_crypto_size(data_size));
- *data = MutableSlice(header.data, data_size);
+ *data = MutableSlice(message.begin() + sizeof(NoCryptoHeader), data_size);
return Status::OK();
}
@@ -92,21 +220,19 @@ template <class HeaderT, class PrefixT>
Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &auth_key, HeaderT **header_ptr,
PrefixT **prefix_ptr, MutableSlice *data, PacketInfo *info) {
if (message.size() < sizeof(HeaderT)) {
- return Status::Error(PSLICE() << "Invalid mtproto message: too small [message.size()=" << message.size()
+ return Status::Error(PSLICE() << "Invalid MTProto message: too small [message.size() = " << message.size()
<< "] < [sizeof(HeaderT) = " << sizeof(HeaderT) << "]");
}
- auto *header = &as<HeaderT>(message.begin());
+ //FIXME: rewrite without reinterpret cast
+ auto *header = reinterpret_cast<HeaderT *>(message.begin());
*header_ptr = header;
auto to_decrypt = MutableSlice(header->encrypt_begin(), message.uend());
- if (to_decrypt.size() % 16 != 0) {
- return Status::Error(PSLICE() << "Invalid mtproto message: size of encrypted part is not multiple of 16 [size="
- << to_decrypt.size() << "]");
- }
+ to_decrypt.remove_suffix(to_decrypt.size() & 15);
if (header->auth_key_id != auth_key.id()) {
- return Status::Error(PSLICE() << "Invalid mtproto message: auth_key_id mismatch [found="
+ return Status::Error(PSLICE() << "Invalid MTProto message: auth_key_id mismatch [found = "
<< format::as_hex(header->auth_key_id)
- << "] [expected=" << format::as_hex(auth_key.id()) << "]");
+ << "] [expected = " << format::as_hex(auth_key.id()) << "]");
}
UInt256 aes_key;
@@ -117,43 +243,61 @@ Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &a
KDF2(auth_key.key(), header->message_key, X, &aes_key, &aes_iv);
}
- aes_ige_decrypt(aes_key, &aes_iv, to_decrypt, to_decrypt);
+ aes_ige_decrypt(as_slice(aes_key), as_slice(aes_iv), to_decrypt, to_decrypt);
size_t tail_size = message.end() - reinterpret_cast<char *>(header->data);
if (tail_size < sizeof(PrefixT)) {
return Status::Error("Too small encrypted part");
}
- auto *prefix = &as<PrefixT>(header->data);
+ //FIXME: rewrite without reinterpret cast
+ auto *prefix = reinterpret_cast<PrefixT *>(header->data);
*prefix_ptr = prefix;
size_t data_size = prefix->message_data_length + sizeof(PrefixT);
- bool is_length_ok = prefix->message_data_length % 4 == 0;
+ bool is_length_bad = false;
UInt128 real_message_key;
if (info->version == 1) {
+ is_length_bad |= info->check_mod4 && prefix->message_data_length % 4 != 0;
auto expected_size = calc_crypto_size<HeaderT>(data_size);
- is_length_ok = (is_length_ok & (expected_size == message.size())) != 0;
- auto check_size = data_size * is_length_ok + tail_size * (1 - is_length_ok);
+ is_length_bad |= expected_size != message.size();
+ auto check_size = data_size * (1 - is_length_bad) + tail_size * is_length_bad;
std::tie(info->message_ack, real_message_key) = calc_message_ack_and_key(*header, check_size);
} else {
- size_t pad_size = tail_size - data_size;
- is_length_ok = (is_length_ok & (tail_size - sizeof(PrefixT) >= prefix->message_data_length) & (12 <= pad_size) &
- (pad_size <= 1024)) != 0;
std::tie(info->message_ack, real_message_key) = calc_message_key2(auth_key, X, to_decrypt);
}
- bool is_key_ok = true;
+ int is_key_bad = false;
for (size_t i = 0; i < sizeof(real_message_key.raw); i++) {
- is_key_ok &= real_message_key.raw[i] == header->message_key.raw[i];
+ is_key_bad |= real_message_key.raw[i] ^ header->message_key.raw[i];
}
-
- if (!is_key_ok) {
- return Status::Error(PSLICE() << "Invalid mtproto message: message_key mismatch [found="
+ if (is_key_bad != 0) {
+ return Status::Error(PSLICE() << "Invalid MTProto message: message_key mismatch [found = "
<< format::as_hex_dump(header->message_key)
- << "] [expected=" << format::as_hex_dump(real_message_key) << "]");
+ << "] [expected = " << format::as_hex_dump(real_message_key) << "]");
}
- if (!is_length_ok) {
- return Status::Error(PSLICE() << "Invalid mtproto message: invalid length " << tag("total_size", message.size())
- << tag("message_data_length", prefix->message_data_length));
+
+ if (info->version == 2) {
+ if (info->check_mod4 && prefix->message_data_length % 4 != 0) {
+ return Status::Error(PSLICE() << "Invalid MTProto message: invalid length (not divisible by four)"
+ << tag("total_size", message.size())
+ << tag("message_data_length", prefix->message_data_length));
+ }
+ if (tail_size - sizeof(PrefixT) < prefix->message_data_length) {
+ return Status::Error(PSLICE() << "Invalid MTProto message: invalid length (message_data_length is too big)"
+ << tag("total_size", message.size())
+ << tag("message_data_length", prefix->message_data_length));
+ }
+ size_t pad_size = tail_size - data_size;
+ if (pad_size < 12 || pad_size > 1024) {
+ return Status::Error(PSLICE() << "Invalid MTProto message: invalid length (invalid padding length)"
+ << tag("padding_size", pad_size) << tag("total_size", message.size())
+ << tag("message_data_length", prefix->message_data_length));
+ }
+ } else {
+ if (is_length_bad) {
+ return Status::Error(PSLICE() << "Invalid MTProto message: invalid length " << tag("total_size", message.size())
+ << tag("message_data_length", prefix->message_data_length));
+ }
}
*data = MutableSlice(header->data, data_size);
@@ -191,24 +335,26 @@ size_t Transport::write_no_crypto(const Storer &storer, PacketInfo *info, Mutabl
if (size > dest.size()) {
return size;
}
- auto &header = as<NoCryptoHeader>(dest.begin());
- header.auth_key_id = 0;
- storer.store(header.data);
+ // NoCryptoHeader
+ as<uint64>(dest.begin()) = 0;
+ auto real_size = storer.store(dest.ubegin() + sizeof(uint64));
+ CHECK(real_size == storer.size());
return size;
}
template <class HeaderT>
void Transport::write_crypto_impl(int X, const Storer &storer, const AuthKey &auth_key, PacketInfo *info,
HeaderT *header, size_t data_size) {
- storer.store(header->data);
- VLOG(raw_mtproto) << "SEND" << format::as_hex_dump<4>(Slice(header->data, data_size));
- // LOG(ERROR) << "SEND" << format::as_hex_dump<4>(Slice(header->data, data_size)) << info->version;
+ auto real_data_size = storer.store(header->data);
+ CHECK(real_data_size == data_size);
+ VLOG(raw_mtproto) << "Send packet of size " << data_size << " to session " << format::as_hex(info->session_id) << ":"
+ << format::as_hex_dump<4>(Slice(header->data, data_size));
size_t size = 0;
if (info->version == 1) {
size = calc_crypto_size<HeaderT>(data_size);
} else {
- size = calc_crypto_size2<HeaderT>(data_size);
+ size = calc_crypto_size2<HeaderT>(data_size, info);
}
size_t pad_size = size - (sizeof(HeaderT) + data_size);
@@ -232,7 +378,7 @@ void Transport::write_crypto_impl(int X, const Storer &storer, const AuthKey &au
KDF2(auth_key.key(), header->message_key, X, &aes_key, &aes_iv);
}
- aes_ige_encrypt(aes_key, &aes_iv, to_encrypt, to_encrypt);
+ aes_ige_encrypt(as_slice(aes_key), as_slice(aes_iv), to_encrypt, to_encrypt);
}
size_t Transport::write_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest) {
@@ -241,13 +387,14 @@ size_t Transport::write_crypto(const Storer &storer, const AuthKey &auth_key, Pa
if (info->version == 1) {
size = calc_crypto_size<CryptoHeader>(data_size);
} else {
- size = calc_crypto_size2<CryptoHeader>(data_size);
+ size = calc_crypto_size2<CryptoHeader>(data_size, info);
}
if (size > dest.size()) {
return size;
}
- auto &header = as<CryptoHeader>(dest.begin());
+ //FIXME: rewrite without reinterpret cast
+ auto &header = *reinterpret_cast<CryptoHeader *>(dest.begin());
header.auth_key_id = auth_key.id();
header.salt = info->salt;
header.session_id = info->session_id;
@@ -263,13 +410,14 @@ size_t Transport::write_e2e_crypto(const Storer &storer, const AuthKey &auth_key
if (info->version == 1) {
size = calc_crypto_size<EndToEndHeader>(data_size);
} else {
- size = calc_crypto_size2<EndToEndHeader>(data_size);
+ size = calc_crypto_size2<EndToEndHeader>(data_size, info);
}
if (size > dest.size()) {
return size;
}
- auto &header = as<EndToEndHeader>(dest.begin());
+ //FIXME: rewrite without reinterpret cast
+ auto &header = *reinterpret_cast<EndToEndHeader *>(dest.begin());
header.auth_key_id = auth_key.id();
write_crypto_impl(info->is_creator || info->version == 1 ? 0 : 8, storer, auth_key, info, &header, data_size);
@@ -279,33 +427,42 @@ size_t Transport::write_e2e_crypto(const Storer &storer, const AuthKey &auth_key
Result<uint64> Transport::read_auth_key_id(Slice message) {
if (message.size() < 8) {
- return Status::Error(PSLICE() << "Invalid mtproto message: smaller than 8 bytes [size=" << message.size() << "]");
+ return Status::Error(PSLICE() << "Invalid MTProto message: smaller than 8 bytes [size = " << message.size() << "]");
}
return as<uint64>(message.begin());
}
-Status Transport::read(MutableSlice message, const AuthKey &auth_key, PacketInfo *info, MutableSlice *data,
- int32 *error_code) {
- if (message.size() < 8) {
- if (message.size() == 4) {
- *error_code = as<int32>(message.begin());
- return Status::OK();
+Result<Transport::ReadResult> Transport::read(MutableSlice message, const AuthKey &auth_key, PacketInfo *info) {
+ if (message.size() < 12) {
+ if (message.size() < 4) {
+ return Status::Error(PSLICE() << "Invalid MTProto message: smaller than 4 bytes [size = " << message.size()
+ << "]");
+ }
+
+ int32 code = as<int32>(message.begin());
+ if (code == 0) {
+ return ReadResult::make_nop();
+ } else if (code == -1 && message.size() >= 8) {
+ return ReadResult::make_quick_ack(as<uint32>(message.begin() + 4));
+ } else {
+ return ReadResult::make_error(code);
}
- return Status::Error(PSLICE() << "Invalid mtproto message: smaller than 8 bytes [size=" << message.size() << "]");
}
+
info->auth_key_id = as<int64>(message.begin());
info->no_crypto_flag = info->auth_key_id == 0;
+ MutableSlice data;
if (info->type == PacketInfo::EndToEnd) {
- return read_e2e_crypto(message, auth_key, info, data);
- }
- if (info->no_crypto_flag) {
- return read_no_crypto(message, info, data);
+ TRY_STATUS(read_e2e_crypto(message, auth_key, info, &data));
+ } else if (info->no_crypto_flag) {
+ TRY_STATUS(read_no_crypto(message, info, &data));
} else {
if (auth_key.empty()) {
- return Status::Error("Failed to decrypt mtproto message: auth key is empty");
+ return Status::Error("Failed to decrypt MTProto message: auth key is empty");
}
- return read_crypto(message, auth_key, info, data);
+ TRY_STATUS(read_crypto(message, auth_key, info, &data));
}
+ return ReadResult::make_packet(data);
}
size_t Transport::write(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest) {
@@ -319,5 +476,6 @@ size_t Transport::write(const Storer &storer, const AuthKey &auth_key, PacketInf
return write_crypto(storer, auth_key, info, dest);
}
}
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/Transport.h b/protocols/Telegram/tdlib/td/td/mtproto/Transport.h
index 9184d86ecd..8674bd0e61 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/Transport.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/Transport.h
@@ -1,167 +1,107 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/mtproto/utils.h"
+
+#include "td/mtproto/PacketInfo.h"
#include "td/utils/common.h"
-#include "td/utils/int_types.h"
+#include "td/utils/logging.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/StorerBase.h"
+#include "td/utils/UInt.h"
-#include <tuple>
+#include <utility>
namespace td {
-namespace mtproto {
-class AuthKey;
-
-#pragma pack(push, 4)
-#if TD_MSVC
-#pragma warning(push)
-#pragma warning(disable : 4200)
-#endif
-
-struct CryptoHeader {
- uint64 auth_key_id;
- UInt128 message_key;
-
- // encrypted part
- uint64 salt;
- uint64 session_id;
-
- // It is weird to generate message_id and seq_no while writing a packet.
- //
- // uint64 message_id;
- // uint32 seq_no;
- // uint32 message_data_length;
- uint8 data[0]; // use compiler extension
-
- static size_t encrypted_header_size() {
- return sizeof(salt) + sizeof(session_id);
- }
-
- uint8 *encrypt_begin() {
- return reinterpret_cast<uint8 *>(&salt);
- }
-
- const uint8 *encrypt_begin() const {
- return reinterpret_cast<const uint8 *>(&salt);
- }
-
- CryptoHeader() = delete;
- CryptoHeader(const CryptoHeader &) = delete;
- CryptoHeader(CryptoHeader &&) = delete;
- CryptoHeader &operator=(const CryptoHeader &) = delete;
- CryptoHeader &operator=(CryptoHeader &&) = delete;
- ~CryptoHeader() = delete;
-};
-
-struct CryptoPrefix {
- uint64 message_id;
- uint32 seq_no;
- uint32 message_data_length;
-};
-
-struct EndToEndHeader {
- uint64 auth_key_id;
- UInt128 message_key;
-
- // encrypted part
- // uint32 message_data_length;
- uint8 data[0]; // use compiler extension
- static size_t encrypted_header_size() {
- return 0;
- }
+extern int VERBOSITY_NAME(raw_mtproto);
- uint8 *encrypt_begin() {
- return reinterpret_cast<uint8 *>(&data);
- }
-
- const uint8 *encrypt_begin() const {
- return reinterpret_cast<const uint8 *>(&data);
- }
-
- EndToEndHeader() = delete;
- EndToEndHeader(const EndToEndHeader &) = delete;
- EndToEndHeader(EndToEndHeader &&) = delete;
- EndToEndHeader &operator=(const EndToEndHeader &) = delete;
- EndToEndHeader &operator=(EndToEndHeader &&) = delete;
- ~EndToEndHeader() = delete;
-};
-
-struct EndToEndPrefix {
- uint32 message_data_length;
-};
-
-struct NoCryptoHeader {
- uint64 auth_key_id;
-
- // message_id is removed from CryptoHeader. Should be removed from here too.
- //
- // int64 message_id;
- // uint32 message_data_length;
- uint8 data[0]; // use compiler extension
-
- NoCryptoHeader() = delete;
- NoCryptoHeader(const NoCryptoHeader &) = delete;
- NoCryptoHeader(NoCryptoHeader &&) = delete;
- NoCryptoHeader &operator=(const NoCryptoHeader &) = delete;
- NoCryptoHeader &operator=(NoCryptoHeader &&) = delete;
- ~NoCryptoHeader() = delete;
-};
+namespace mtproto {
-#if TD_MSVC
-#pragma warning(pop)
-#endif
-#pragma pack(pop)
-
-struct PacketInfo {
- enum { Common, EndToEnd } type = Common;
- uint64 auth_key_id;
- uint32 message_ack;
- UInt128 message_key;
-
- uint64 salt;
- uint64 session_id;
-
- uint64 message_id;
- int32 seq_no;
- int32 version = 1;
- bool no_crypto_flag;
- bool is_creator = false;
-};
+class AuthKey;
class Transport {
public:
+ class ReadResult {
+ public:
+ enum Type { Packet, Nop, Error, Quickack };
+
+ static ReadResult make_nop() {
+ return {};
+ }
+ static ReadResult make_error(int32 error_code) {
+ ReadResult res;
+ res.type_ = Error;
+ res.error_code_ = error_code;
+ return res;
+ }
+ static ReadResult make_packet(MutableSlice packet) {
+ CHECK(!packet.empty());
+ ReadResult res;
+ res.type_ = Packet;
+ res.packet_ = packet;
+ return res;
+ }
+ static ReadResult make_quick_ack(uint32 quick_ack) {
+ ReadResult res;
+ res.type_ = Quickack;
+ res.quick_ack_ = quick_ack;
+ return res;
+ }
+
+ Type type() const {
+ return type_;
+ }
+
+ MutableSlice packet() const {
+ CHECK(type_ == Packet);
+ return packet_;
+ }
+ uint32 quick_ack() const {
+ CHECK(type_ == Quickack);
+ return quick_ack_;
+ }
+ int32 error() const {
+ CHECK(type_ == Error);
+ return error_code_;
+ }
+
+ private:
+ Type type_ = Nop;
+ MutableSlice packet_;
+ int32 error_code_ = 0;
+ uint32 quick_ack_ = 0;
+ };
+
static Result<uint64> read_auth_key_id(Slice message);
- // Reads mtproto packet from [message] and saves into [data].
+ // Reads MTProto packet from [message] and saves it into [data].
// If message is encrypted, [auth_key] is used.
// Decryption and unpacking is made inplace, so [data] will be subslice of [message].
- // Returns size of mtproto packet.
+ // Returns size of MTProto packet.
// If dest.size() >= size, the packet is also written into [dest].
// If auth_key is nonempty, encryption will be used.
- static Status read(MutableSlice message, const AuthKey &auth_key, PacketInfo *info, MutableSlice *data,
- int32 *error_code) TD_WARN_UNUSED_RESULT;
+ static Result<ReadResult> read(MutableSlice message, const AuthKey &auth_key, PacketInfo *info) TD_WARN_UNUSED_RESULT;
static size_t write(const Storer &storer, const AuthKey &auth_key, PacketInfo *info,
MutableSlice dest = MutableSlice());
+ static std::pair<uint32, UInt128> calc_message_key2(const AuthKey &auth_key, int X, Slice to_encrypt);
+
private:
template <class HeaderT>
- static std::tuple<uint32, UInt128> calc_message_ack_and_key(const HeaderT &head, size_t data_size);
-
- static std::tuple<uint32, UInt128> calc_message_key2(const AuthKey &auth_key, int X, Slice to_encrypt);
+ static std::pair<uint32, UInt128> calc_message_ack_and_key(const HeaderT &head, size_t data_size);
template <class HeaderT>
static size_t calc_crypto_size(size_t data_size);
template <class HeaderT>
- static size_t calc_crypto_size2(size_t data_size);
+ static size_t calc_crypto_size2(size_t data_size, PacketInfo *info);
static size_t calc_no_crypto_size(size_t data_size);
@@ -183,5 +123,6 @@ class Transport {
static void write_crypto_impl(int X, const Storer &storer, const AuthKey &auth_key, PacketInfo *info, HeaderT *header,
size_t data_size);
};
+
} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/TransportType.h b/protocols/Telegram/tdlib/td/td/mtproto/TransportType.h
new file mode 100644
index 0000000000..c2da5217b9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/TransportType.h
@@ -0,0 +1,27 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/mtproto/ProxySecret.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+namespace mtproto {
+
+struct TransportType {
+ enum Type { Tcp, ObfuscatedTcp, Http } type = Tcp;
+ int16 dc_id{0};
+ ProxySecret secret;
+
+ TransportType() = default;
+ TransportType(Type type, int16 dc_id, ProxySecret secret) : type(type), dc_id(dc_id), secret(std::move(secret)) {
+ }
+};
+
+} // namespace mtproto
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/crypto.cpp b/protocols/Telegram/tdlib/td/td/mtproto/crypto.cpp
deleted file mode 100644
index 38f7a9b640..0000000000
--- a/protocols/Telegram/tdlib/td/td/mtproto/crypto.cpp
+++ /dev/null
@@ -1,441 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#include "td/mtproto/crypto.h"
-
-#include "td/mtproto/mtproto_api.h"
-
-#include "td/utils/crypto.h"
-#include "td/utils/int_types.h" // for UInt256, UInt128, etc
-#include "td/utils/logging.h"
-#include "td/utils/misc.h"
-#include "td/utils/Random.h"
-#include "td/utils/ScopeGuard.h"
-#include "td/utils/Slice.h"
-#include "td/utils/Status.h"
-#include "td/utils/tl_storers.h"
-
-#include <openssl/bio.h>
-#include <openssl/bn.h>
-#include <openssl/pem.h>
-#include <openssl/rsa.h>
-
-#include <cstring>
-
-namespace td {
-
-/*** RSA ***/
-RSA::RSA(BigNum n, BigNum e) : n_(std::move(n)), e_(std::move(e)) {
- e_.ensure_const_time();
-}
-
-RSA RSA::clone() const {
- return RSA(n_.clone(), e_.clone());
-}
-
-Result<RSA> RSA::from_pem(Slice pem) {
- init_crypto();
-
- auto *bio =
- BIO_new_mem_buf(const_cast<void *>(static_cast<const void *>(pem.ubegin())), narrow_cast<int32>(pem.size()));
- if (bio == nullptr) {
- return Status::Error("Cannot create BIO");
- }
- SCOPE_EXIT {
- BIO_free(bio);
- };
-
- auto *rsa = RSA_new();
- if (rsa == nullptr) {
- return Status::Error("Cannot create RSA");
- }
- SCOPE_EXIT {
- RSA_free(rsa);
- };
-
- if (!PEM_read_bio_RSAPublicKey(bio, &rsa, nullptr, nullptr)) {
- return Status::Error("Error while reading rsa pubkey");
- }
-
- if (RSA_size(rsa) != 256) {
- return Status::Error("RSA_size != 256");
- }
-
- const BIGNUM *n_num;
- const BIGNUM *e_num;
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
- n_num = rsa->n;
- e_num = rsa->e;
-#else
- RSA_get0_key(rsa, &n_num, &e_num, nullptr);
-#endif
-
- auto n = static_cast<void *>(BN_dup(n_num));
- auto e = static_cast<void *>(BN_dup(e_num));
- if (n == nullptr || e == nullptr) {
- return Status::Error("Cannot dup BIGNUM");
- }
-
- return RSA(BigNum::from_raw(n), BigNum::from_raw(e));
-}
-
-int64 RSA::get_fingerprint() const {
- mtproto_api::rsa_public_key public_key;
- // string objects are necessary, because mtproto_api::rsa_public_key contains Slice inside
- string n_str = n_.to_binary();
- string e_str = e_.to_binary();
- public_key.n_ = n_str;
- public_key.e_ = e_str;
- size_t size = tl_calc_length(public_key);
- std::vector<unsigned char> tmp(size);
- size = tl_store_unsafe(public_key, tmp.data());
- CHECK(size == tmp.size());
- unsigned char key_sha1[20];
- sha1(Slice(tmp.data(), tmp.size()), key_sha1);
- return as<int64>(key_sha1 + 12);
-}
-
-size_t RSA::size() const {
- // Checked in RSA::from_pem step
- return 256;
-}
-
-size_t RSA::encrypt(unsigned char *from, size_t from_len, unsigned char *to) const {
- CHECK(from_len > 0 && from_len <= 2550);
- size_t pad = (25500 - from_len - 32) % 255 + 32;
- size_t chunks = (from_len + pad) / 255;
- int bits = n_.get_num_bits();
- CHECK(bits >= 2041 && bits <= 2048);
- CHECK(chunks * 255 == from_len + pad);
- Random::secure_bytes(from + from_len, pad);
-
- BigNumContext ctx;
- BigNum y;
- while (chunks-- > 0) {
- BigNum x = BigNum::from_binary(Slice(from, 255));
- BigNum::mod_exp(y, x, e_, n_, ctx);
- string result = y.to_binary(256);
- std::memcpy(to, result.c_str(), 256);
- to += 256;
- }
- return chunks * 256;
-}
-
-void RSA::decrypt(Slice from, MutableSlice to) const {
- CHECK(from.size() == 256);
- BigNumContext ctx;
- BigNum x = BigNum::from_binary(from);
- BigNum y;
- BigNum::mod_exp(y, x, e_, n_, ctx);
- string result = y.to_binary(256);
- std::memcpy(to.data(), result.c_str(), 256);
-}
-
-/*** DH ***/
-Status DhHandshake::dh_check(Slice prime_str, const BigNum &prime, int32 g_int, const BigNum &g_a, const BigNum &g_b,
- BigNumContext &ctx, DhCallback *callback) {
- // 2. g generates a cyclic subgroup of prime order (p - 1) / 2, i.e. is a quadratic residue mod p.
- // Since g is always equal to 2, 3, 4, 5, 6 or 7, this is easily done using quadratic reciprocity law,
- // yielding a simple condition on
- // * p mod 4g - namely, p mod 8 = 7 for g = 2; p mod 3 = 2 for g = 3;
- // * no extra condition for g = 4;
- // * p mod 5 = 1 or 4 for g = 5;
- // * p mod 24 = 19 or 23 for g = 6;
- // * p mod 7 = 3, 5 or 6 for g = 7.
-
- bool mod_ok;
- uint32 mod_r;
- switch (g_int) {
- case 2:
- mod_ok = prime % 8 == 7u;
- break;
- case 3:
- mod_ok = prime % 3 == 2u;
- break;
- case 4:
- mod_ok = true;
- break;
- case 5:
- mod_ok = (mod_r = prime % 5) == 1u || mod_r == 4u;
- break;
- case 6:
- mod_ok = (mod_r = prime % 24) == 19u || mod_r == 23u;
- break;
- case 7:
- mod_ok = (mod_r = prime % 7) == 3u || mod_r == 5u || mod_r == 6u;
- break;
- default:
- mod_ok = false;
- }
- if (!mod_ok) {
- return Status::Error("Bad prime mod 4g");
- }
-
- // IMPORTANT: Apart from the conditions on the Diffie-Hellman prime dh_prime and generator g, both sides are
- // to check that g, g_a and g_b are greater than 1 and less than dh_prime - 1.
- // We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well.
-
- // check that 2^2047 <= p < 2^2048
- if (prime.get_num_bits() != 2048) {
- return Status::Error("p is not 2048-bit number");
- }
-
- BigNum left;
- left.set_value(0);
- left.set_bit(2048 - 64);
-
- BigNum right;
- BigNum::sub(right, prime, left);
-
- if (BigNum::compare(left, g_a) > 0 || BigNum::compare(g_a, right) > 0 || BigNum::compare(left, g_b) > 0 ||
- BigNum::compare(g_b, right) > 0) {
- std::string x(2048, '0');
- std::string y(2048, '0');
- for (int i = 0; i < 2048; i++) {
- if (g_a.is_bit_set(i)) {
- x[i] = '1';
- }
- if (g_b.is_bit_set(i)) {
- y[i] = '1';
- }
- }
- LOG(ERROR) << x;
- LOG(ERROR) << y;
- return Status::Error("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}");
- }
-
- // check whether p = dh_prime is a safe 2048-bit prime (meaning that both p and (p - 1) / 2 are prime)
- int is_good_prime = -1;
- if (callback) {
- is_good_prime = callback->is_good_prime(prime_str);
- }
- if (is_good_prime != -1) {
- return is_good_prime ? Status::OK() : Status::Error("p or (p - 1) / 2 is not a prime number");
- }
- if (!prime.is_prime(ctx)) {
- if (callback) {
- callback->add_bad_prime(prime_str);
- }
- return Status::Error("p is not a prime number");
- }
-
- BigNum half_prime = prime;
- half_prime -= 1;
- half_prime /= 2;
- if (!half_prime.is_prime(ctx)) {
- if (callback) {
- callback->add_bad_prime(prime_str);
- }
- return Status::Error("(p - 1) / 2 is not a prime number");
- }
- if (callback) {
- callback->add_good_prime(prime_str);
- }
-
- // TODO(perf):
- // Checks:
- // After g and p have been checked by the client, it makes sense to cache the result,
- // so as not to repeat lengthy computations in future.
-
- // If the verification takes too long time (which is the case for older mobile devices),
- // one might initially run only 15 Miller-Rabin iterations for verifying primeness of p and (p - 1)/2
- // with error probability not exceeding one billionth, and do more iterations later in the background.
-
- // Another optimization is to embed into the client application code a small table with some known "good"
- // couples (g,p) (or just known safe primes p, since the condition on g is easily verified during execution),
- // checked during code generation phase, so as to avoid doing such verification during runtime altogether.
- // Server changes these values rarely, thus one usually has to put the current value of server's dh_prime
- // into such a table. For example, current value of dh_prime equals (in big-endian byte order) ...
-
- return Status::OK();
-}
-
-int64 dh_auth_key_id(const string &auth_key) {
- UInt<160> auth_key_sha1;
- sha1(auth_key, auth_key_sha1.raw);
- return as<int64>(auth_key_sha1.raw + 12);
-}
-
-void DhHandshake::set_config(int32 g_int, Slice prime_str) {
- has_config_ = true;
- prime_ = BigNum::from_binary(prime_str);
- prime_str_ = prime_str.str();
-
- b_ = BigNum();
- g_b_ = BigNum();
-
- BigNum::random(b_, 2048, -1, 0);
-
- // g^b
- g_int_ = g_int;
- g_.set_value(g_int_);
-
- BigNum::mod_exp(g_b_, g_, b_, prime_, ctx_);
-}
-
-void DhHandshake::set_g_a_hash(Slice g_a_hash) {
- has_g_a_hash_ = true;
- ok_g_a_hash_ = false;
- CHECK(!has_g_a_);
- g_a_hash_ = g_a_hash.str();
-}
-
-void DhHandshake::set_g_a(Slice g_a_str) {
- has_g_a_ = true;
- if (has_g_a_hash_) {
- string g_a_hash(32, ' ');
- sha256(g_a_str, g_a_hash);
- ok_g_a_hash_ = g_a_hash == g_a_hash_;
- }
- g_a_ = BigNum::from_binary(g_a_str);
-}
-
-string DhHandshake::get_g_a() const {
- CHECK(has_g_a_);
- return g_a_.to_binary();
-}
-
-string DhHandshake::get_g_b() const {
- CHECK(has_config_);
- return g_b_.to_binary();
-}
-string DhHandshake::get_g_b_hash() const {
- string g_b_hash(32, ' ');
- sha256(get_g_b(), g_b_hash);
- return g_b_hash;
-}
-
-Status DhHandshake::run_checks(DhCallback *callback) {
- CHECK(has_g_a_ && has_config_);
-
- if (has_g_a_hash_ && !ok_g_a_hash_) {
- return Status::Error("g_a_hash mismatch");
- }
-
- return dh_check(prime_str_, prime_, g_int_, g_a_, g_b_, ctx_, callback);
-}
-
-std::pair<int64, string> DhHandshake::gen_key() {
- CHECK(has_g_a_ && has_config_);
- BigNum g_ab;
- BigNum::mod_exp(g_ab, g_a_, b_, prime_, ctx_);
- string key = g_ab.to_binary(2048 / 8);
- auto key_id = calc_key_id(key);
- return std::pair<int64, string>(key_id, std::move(key));
-}
-
-int64 DhHandshake::calc_key_id(const string &auth_key) {
- UInt<160> auth_key_sha1;
- sha1(auth_key, auth_key_sha1.raw);
- return as<int64>(auth_key_sha1.raw + 12);
-}
-
-Status dh_handshake(int g_int, Slice prime_str, Slice g_a_str, string *g_b_str, string *g_ab_str,
- DhCallback *callback) {
- DhHandshake handshake;
- handshake.set_config(g_int, prime_str);
- handshake.set_g_a(g_a_str);
- TRY_STATUS(handshake.run_checks(callback));
- *g_b_str = handshake.get_g_b();
- *g_ab_str = handshake.gen_key().second;
- return Status::OK();
-}
-
-/*** KDF ***/
-void KDF(const string &auth_key, const UInt128 &msg_key, int X, UInt256 *aes_key, UInt256 *aes_iv) {
- CHECK(auth_key.size() == 2048 / 8);
- const char *auth_key_raw = auth_key.c_str();
- uint8 buf[48];
- as<UInt128>(buf) = msg_key;
- as<UInt256>(buf + 16) = as<UInt256>(auth_key_raw + X);
- uint8 sha1_a[20];
- sha1(Slice(buf, 48), sha1_a);
-
- as<UInt128>(buf) = as<UInt128>(auth_key_raw + X + 32);
- as<UInt128>(buf + 16) = msg_key;
- as<UInt128>(buf + 32) = as<UInt128>(auth_key_raw + X + 48);
- uint8 sha1_b[20];
- sha1(Slice(buf, 48), sha1_b);
-
- as<UInt256>(buf) = as<UInt256>(auth_key_raw + 64 + X);
- as<UInt128>(buf + 32) = msg_key;
- uint8 sha1_c[20];
- sha1(Slice(buf, 48), sha1_c);
-
- as<UInt128>(buf) = msg_key;
- as<UInt256>(buf + 16) = as<UInt256>(auth_key_raw + 96 + X);
- uint8 sha1_d[20];
- sha1(Slice(buf, 48), sha1_d);
-
- as<uint64>(aes_key->raw) = as<uint64>(sha1_a);
- as<UInt<96>>(aes_key->raw + 8) = as<UInt<96>>(sha1_b + 8);
- as<UInt<96>>(aes_key->raw + 20) = as<UInt<96>>(sha1_c + 4);
-
- as<UInt<96>>(aes_iv->raw) = as<UInt<96>>(sha1_a + 8);
- as<uint64>(aes_iv->raw + 12) = as<uint64>(sha1_b);
- as<uint32>(aes_iv->raw + 20) = as<uint32>(sha1_c + 16);
- as<uint64>(aes_iv->raw + 24) = as<uint64>(sha1_d);
-}
-
-void tmp_KDF(const UInt128 &server_nonce, const UInt256 &new_nonce, UInt256 *tmp_aes_key, UInt256 *tmp_aes_iv) {
- // tmp_aes_key := SHA1(new_nonce + server_nonce) + substr (SHA1(server_nonce + new_nonce), 0, 12);
- uint8 buf[512 / 8];
- as<UInt256>(buf) = new_nonce;
- as<UInt128>(buf + 32) = server_nonce;
- sha1(Slice(buf, 48), tmp_aes_key->raw);
-
- as<UInt128>(buf) = server_nonce;
- as<UInt256>(buf + 16) = new_nonce;
- uint8 sha1_server_new[20];
- sha1(Slice(buf, 48), sha1_server_new);
- as<UInt<96>>(tmp_aes_key->raw + 20) = as<UInt<96>>(sha1_server_new);
-
- // tmp_aes_iv := substr (SHA1(server_nonce + new_nonce), 12, 8) + SHA1(new_nonce + new_nonce) + substr (new_nonce,
- // 0,
- // 4);
- as<uint64>(tmp_aes_iv->raw) = as<uint64>(sha1_server_new + 12);
-
- as<UInt256>(buf) = new_nonce;
- as<UInt256>(buf + 32) = new_nonce;
- sha1(Slice(buf, 64), tmp_aes_iv->raw + 8);
- as<uint32>(tmp_aes_iv->raw + 28) = as<uint32>(new_nonce.raw);
-}
-
-// msg_key_large = SHA256 (substr (auth_key, 88+x, 32) + plaintext + random_padding);
-// msg_key = substr (msg_key_large, 8, 16);
-
-void KDF2(Slice auth_key, const UInt128 &msg_key, int X, UInt256 *aes_key, UInt256 *aes_iv) {
- uint8 buf_raw[36 + 16];
- MutableSlice buf(buf_raw, 36 + 16);
- Slice msg_key_slice(msg_key.raw, sizeof(msg_key.raw));
-
- // sha256_a = SHA256 (msg_key + substr (auth_key, x, 36));
- buf.copy_from(msg_key_slice);
- buf.substr(16, 36).copy_from(auth_key.substr(X, 36));
- uint8 sha256_a_raw[32];
- MutableSlice sha256_a(sha256_a_raw, 32);
- sha256(buf, sha256_a);
-
- // sha256_b = SHA256 (substr (auth_key, 40+x, 36) + msg_key);
- buf.copy_from(auth_key.substr(40 + X, 36));
- buf.substr(36).copy_from(msg_key_slice);
- uint8 sha256_b_raw[32];
- MutableSlice sha256_b(sha256_b_raw, 32);
- sha256(buf, sha256_b);
-
- // aes_key = substr (sha256_a, 0, 8) + substr (sha256_b, 8, 16) + substr (sha256_a, 24, 8);
- MutableSlice aes_key_slice(aes_key->raw, sizeof(aes_key->raw));
- aes_key_slice.copy_from(sha256_a.substr(0, 8));
- aes_key_slice.substr(8).copy_from(sha256_b.substr(8, 16));
- aes_key_slice.substr(24).copy_from(sha256_a.substr(24, 8));
-
- // aes_iv = substr (sha256_b, 0, 8) + substr (sha256_a, 8, 16) + substr (sha256_b, 24, 8);
- MutableSlice aes_iv_slice(aes_iv->raw, sizeof(aes_iv->raw));
- aes_iv_slice.copy_from(sha256_b.substr(0, 8));
- aes_iv_slice.substr(8).copy_from(sha256_a.substr(8, 16));
- aes_iv_slice.substr(24).copy_from(sha256_b.substr(24, 8));
-}
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/utils.cpp b/protocols/Telegram/tdlib/td/td/mtproto/utils.cpp
index c63a9f32b1..40e4e57050 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/utils.cpp
+++ b/protocols/Telegram/tdlib/td/td/mtproto/utils.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,27 +7,17 @@
#include "td/mtproto/utils.h"
#include "td/mtproto/mtproto_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/utils/logging.h"
namespace td {
+namespace mtproto {
TLStorer<mtproto_api::Function> create_storer(const mtproto_api::Function &function) {
return TLStorer<mtproto_api::Function>(function);
}
-TLStorer<telegram_api::Function> create_storer(const telegram_api::Function &function) {
- LOG(DEBUG) << "Create storer for " << to_string(function);
- return TLStorer<telegram_api::Function>(function);
-}
-
TLObjectStorer<mtproto_api::Object> create_storer(const mtproto_api::Object &object) {
return TLObjectStorer<mtproto_api::Object>(object);
}
-TLObjectStorer<telegram_api::Object> create_storer(const telegram_api::Object &object) {
- return TLObjectStorer<telegram_api::Object>(object);
-}
-
+} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/utils.h b/protocols/Telegram/tdlib/td/td/mtproto/utils.h
index 9bbb706dfb..b40af749c3 100644
--- a/protocols/Telegram/tdlib/td/td/mtproto/utils.h
+++ b/protocols/Telegram/tdlib/td/td/mtproto/utils.h
@@ -1,70 +1,24 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/buffer.h"
-#include "td/utils/format.h"
-#include "td/utils/logging.h"
-#include "td/utils/Slice.h"
-#include "td/utils/Status.h"
#include "td/utils/Storer.h"
-#include "td/utils/tl_parsers.h"
+#include "td/utils/StorerBase.h"
#include "td/utils/tl_storers.h"
#include <limits>
namespace td {
-namespace mtproto {
-struct Query {
- int64 message_id;
- int32 seq_no;
- BufferSlice packet;
- bool gzip_flag;
- uint64 invoke_after_id;
- bool use_quick_ack;
-};
-} // namespace mtproto
-
-template <class T>
-Result<typename T::ReturnType> fetch_result(Slice message) {
- TlParser parser(message);
- auto result = T::fetch_result(parser);
-
- parser.fetch_end();
- const char *error = parser.get_error();
- if (error != nullptr) {
- LOG(ERROR) << "Can't parse: " << format::as_hex_dump<4>(message);
- return Status::Error(500, Slice(error));
- }
-
- return std::move(result);
-}
-
-template <class T>
-Result<typename T::ReturnType> fetch_result(const BufferSlice &message) {
- TlBufferParser parser(&message);
- auto result = T::fetch_result(parser);
-
- parser.fetch_end();
- const char *error = parser.get_error();
- if (error != nullptr) {
- LOG(ERROR) << "Can't parse: " << format::as_hex_dump<4>(message.as_slice());
- return Status::Error(500, Slice(error));
- }
-
- return std::move(result);
-}
-
template <class T>
using TLStorer = DefaultStorer<T>;
template <class T>
-class TLObjectStorer : public Storer {
+class TLObjectStorer final : public Storer {
mutable size_t size_ = std::numeric_limits<size_t>::max();
const T &object_;
@@ -72,7 +26,7 @@ class TLObjectStorer : public Storer {
explicit TLObjectStorer(const T &object) : object_(object) {
}
- size_t size() const override {
+ size_t size() const final {
if (size_ == std::numeric_limits<size_t>::max()) {
TlStorerCalcLength storer;
storer.store_binary(object_.get_id());
@@ -81,12 +35,11 @@ class TLObjectStorer : public Storer {
}
return size_;
}
- size_t store(uint8 *ptr) const override {
- char *p = reinterpret_cast<char *>(ptr);
- TlStorerUnsafe storer(p);
+ size_t store(uint8 *ptr) const final {
+ TlStorerUnsafe storer(ptr);
storer.store_binary(object_.get_id());
object_.store(storer);
- return storer.get_buf() - p;
+ return static_cast<size_t>(storer.get_buf() - ptr);
}
};
@@ -95,17 +48,11 @@ class Object;
class Function;
} // namespace mtproto_api
-namespace telegram_api {
-class Object;
-class Function;
-} // namespace telegram_api
+namespace mtproto {
TLStorer<mtproto_api::Function> create_storer(const mtproto_api::Function &function);
-TLStorer<telegram_api::Function> create_storer(const telegram_api::Function &function);
-
TLObjectStorer<mtproto_api::Object> create_storer(const mtproto_api::Object &object);
-TLObjectStorer<telegram_api::Object> create_storer(const telegram_api::Object &object);
-
+} // namespace mtproto
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AccessRights.h b/protocols/Telegram/tdlib/td/td/telegram/AccessRights.h
index fd0833ead7..25c690a448 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AccessRights.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/AccessRights.h
@@ -1,13 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/utils/common.h"
+
namespace td {
-enum class AccessRights { Read, Edit, Write };
+enum class AccessRights : int32 { Know, Read, Edit, Write };
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Account.cpp b/protocols/Telegram/tdlib/td/td/telegram/Account.cpp
new file mode 100644
index 0000000000..94921c8459
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Account.cpp
@@ -0,0 +1,605 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Account.h"
+
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DeviceTokenManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/net/NetQueryCreator.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+#include <algorithm>
+
+namespace td {
+
+static td_api::object_ptr<td_api::SessionType> get_session_type_object(
+ const tl_object_ptr<telegram_api::authorization> &authorization) {
+ auto contains = [](const string &str, const char *substr) {
+ return str.find(substr) != string::npos;
+ };
+
+ const string &app_name = authorization->app_name_;
+ auto device_model = to_lower(authorization->device_model_);
+ auto platform = to_lower(authorization->platform_);
+ auto system_version = to_lower(authorization->system_version_);
+
+ if (device_model.find("xbox") != string::npos) {
+ return td_api::make_object<td_api::sessionTypeXbox>();
+ }
+
+ bool is_web = [&] {
+ CSlice web_name("Web");
+ auto pos = app_name.find(web_name.c_str());
+ if (pos == string::npos) {
+ return false;
+ }
+
+ auto next_character = app_name[pos + web_name.size()];
+ return !('a' <= next_character && next_character <= 'z');
+ }();
+
+ if (is_web) {
+ if (contains(device_model, "brave")) {
+ return td_api::make_object<td_api::sessionTypeBrave>();
+ } else if (contains(device_model, "vivaldi")) {
+ return td_api::make_object<td_api::sessionTypeVivaldi>();
+ } else if (contains(device_model, "opera") || contains(device_model, "opr")) {
+ return td_api::make_object<td_api::sessionTypeOpera>();
+ } else if (contains(device_model, "edg")) {
+ return td_api::make_object<td_api::sessionTypeEdge>();
+ } else if (contains(device_model, "chrome")) {
+ return td_api::make_object<td_api::sessionTypeChrome>();
+ } else if (contains(device_model, "firefox") || contains(device_model, "fxios")) {
+ return td_api::make_object<td_api::sessionTypeFirefox>();
+ } else if (contains(device_model, "safari")) {
+ return td_api::make_object<td_api::sessionTypeSafari>();
+ }
+ }
+
+ if (begins_with(platform, "android") || contains(system_version, "android")) {
+ return td_api::make_object<td_api::sessionTypeAndroid>();
+ } else if (begins_with(platform, "windows") || contains(system_version, "windows")) {
+ return td_api::make_object<td_api::sessionTypeWindows>();
+ } else if (begins_with(platform, "ubuntu") || contains(system_version, "ubuntu")) {
+ return td_api::make_object<td_api::sessionTypeUbuntu>();
+ } else if (begins_with(platform, "linux") || contains(system_version, "linux")) {
+ return td_api::make_object<td_api::sessionTypeLinux>();
+ }
+
+ auto is_ios = begins_with(platform, "ios") || contains(system_version, "ios");
+ auto is_macos = begins_with(platform, "macos") || contains(system_version, "macos");
+ if (is_ios && contains(device_model, "iphone")) {
+ return td_api::make_object<td_api::sessionTypeIphone>();
+ } else if (is_ios && contains(device_model, "ipad")) {
+ return td_api::make_object<td_api::sessionTypeIpad>();
+ } else if (is_macos && contains(device_model, "mac")) {
+ return td_api::make_object<td_api::sessionTypeMac>();
+ } else if (is_ios || is_macos) {
+ return td_api::make_object<td_api::sessionTypeApple>();
+ }
+
+ return td_api::make_object<td_api::sessionTypeUnknown>();
+}
+
+static td_api::object_ptr<td_api::session> convert_authorization_object(
+ tl_object_ptr<telegram_api::authorization> &&authorization) {
+ CHECK(authorization != nullptr);
+ return td_api::make_object<td_api::session>(
+ authorization->hash_, authorization->current_, authorization->password_pending_,
+ !authorization->encrypted_requests_disabled_, !authorization->call_requests_disabled_,
+ get_session_type_object(authorization), authorization->api_id_, authorization->app_name_,
+ authorization->app_version_, authorization->official_app_, authorization->device_model_, authorization->platform_,
+ authorization->system_version_, authorization->date_created_, authorization->date_active_, authorization->ip_,
+ authorization->country_, authorization->region_);
+}
+
+class SetAccountTtlQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SetAccountTtlQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(int32 account_ttl) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::account_setAccountTTL(make_tl_object<telegram_api::accountDaysTTL>(account_ttl)), {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_setAccountTTL>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ if (!result) {
+ return on_error(Status::Error(500, "Internal Server Error: failed to set account TTL"));
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetAccountTtlQuery final : public Td::ResultHandler {
+ Promise<int32> promise_;
+
+ public:
+ explicit GetAccountTtlQuery(Promise<int32> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_getAccountTTL()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getAccountTTL>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetAccountTtlQuery: " << to_string(ptr);
+
+ promise_.set_value(std::move(ptr->days_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class AcceptLoginTokenQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::session>> promise_;
+
+ public:
+ explicit AcceptLoginTokenQuery(Promise<td_api::object_ptr<td_api::session>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(const string &login_token) {
+ send_query(G()->net_query_creator().create(telegram_api::auth_acceptLoginToken(BufferSlice(login_token))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::auth_acceptLoginToken>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ LOG(DEBUG) << "Receive result for AcceptLoginTokenQuery: " << to_string(result_ptr.ok());
+ promise_.set_value(convert_authorization_object(result_ptr.move_as_ok()));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetAuthorizationsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::sessions>> promise_;
+
+ public:
+ explicit GetAuthorizationsQuery(Promise<td_api::object_ptr<td_api::sessions>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_getAuthorizations()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getAuthorizations>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetAuthorizationsQuery: " << to_string(ptr);
+
+ auto ttl_days = ptr->authorization_ttl_days_;
+ if (ttl_days <= 0 || ttl_days > 366) {
+ LOG(ERROR) << "Receive invalid inactive sessions TTL " << ttl_days;
+ ttl_days = 180;
+ }
+
+ auto results = td_api::make_object<td_api::sessions>(
+ transform(std::move(ptr->authorizations_), convert_authorization_object), ttl_days);
+ std::sort(results->sessions_.begin(), results->sessions_.end(),
+ [](const td_api::object_ptr<td_api::session> &lhs, const td_api::object_ptr<td_api::session> &rhs) {
+ if (lhs->is_current_ != rhs->is_current_) {
+ return lhs->is_current_;
+ }
+ if (lhs->is_password_pending_ != rhs->is_password_pending_) {
+ return lhs->is_password_pending_;
+ }
+ return lhs->last_active_date_ > rhs->last_active_date_;
+ });
+
+ promise_.set_value(std::move(results));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ResetAuthorizationQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ResetAuthorizationQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(int64 authorization_id) {
+ send_query(G()->net_query_creator().create(telegram_api::account_resetAuthorization(authorization_id)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_resetAuthorization>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(WARNING, !result) << "Failed to terminate session";
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ResetAuthorizationsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ResetAuthorizationsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::auth_resetAuthorizations()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::auth_resetAuthorizations>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(WARNING, !result) << "Failed to terminate all sessions";
+ send_closure(td_->device_token_manager_, &DeviceTokenManager::reregister_device);
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ChangeAuthorizationSettingsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ChangeAuthorizationSettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(int64 hash, bool set_encrypted_requests_disabled, bool encrypted_requests_disabled,
+ bool set_call_requests_disabled, bool call_requests_disabled) {
+ int32 flags = 0;
+ if (set_encrypted_requests_disabled) {
+ flags |= telegram_api::account_changeAuthorizationSettings::ENCRYPTED_REQUESTS_DISABLED_MASK;
+ }
+ if (set_call_requests_disabled) {
+ flags |= telegram_api::account_changeAuthorizationSettings::CALL_REQUESTS_DISABLED_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::account_changeAuthorizationSettings(
+ flags, hash, encrypted_requests_disabled, call_requests_disabled),
+ {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_changeAuthorizationSettings>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(WARNING, !result) << "Failed to change session settings";
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetAuthorizationTtlQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SetAuthorizationTtlQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(int32 authorization_ttl_days) {
+ send_query(
+ G()->net_query_creator().create(telegram_api::account_setAuthorizationTTL(authorization_ttl_days), {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_setAuthorizationTTL>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(WARNING, !result) << "Failed to set inactive session TTL";
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetWebAuthorizationsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::connectedWebsites>> promise_;
+
+ public:
+ explicit GetWebAuthorizationsQuery(Promise<td_api::object_ptr<td_api::connectedWebsites>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_getWebAuthorizations()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getWebAuthorizations>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetWebAuthorizationsQuery: " << to_string(ptr);
+
+ td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetWebAuthorizationsQuery");
+
+ auto results = td_api::make_object<td_api::connectedWebsites>();
+ results->websites_.reserve(ptr->authorizations_.size());
+ for (auto &authorization : ptr->authorizations_) {
+ CHECK(authorization != nullptr);
+ UserId bot_user_id(authorization->bot_id_);
+ if (!bot_user_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid bot " << bot_user_id;
+ bot_user_id = UserId();
+ }
+
+ results->websites_.push_back(td_api::make_object<td_api::connectedWebsite>(
+ authorization->hash_, authorization->domain_,
+ td_->contacts_manager_->get_user_id_object(bot_user_id, "GetWebAuthorizationsQuery"), authorization->browser_,
+ authorization->platform_, authorization->date_created_, authorization->date_active_, authorization->ip_,
+ authorization->region_));
+ }
+
+ promise_.set_value(std::move(results));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ResetWebAuthorizationQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ResetWebAuthorizationQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(int64 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::account_resetWebAuthorization(hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_resetWebAuthorization>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(WARNING, !result) << "Failed to disconnect website";
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ResetWebAuthorizationsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ResetWebAuthorizationsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_resetWebAuthorizations()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_resetWebAuthorizations>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(WARNING, !result) << "Failed to disconnect all websites";
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetBotGroupDefaultAdminRightsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SetBotGroupDefaultAdminRightsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(AdministratorRights administrator_rights) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::bots_setBotGroupDefaultAdminRights(administrator_rights.get_chat_admin_rights()), {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::bots_setBotGroupDefaultAdminRights>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(WARNING, !result) << "Failed to set group default administrator rights";
+ td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id());
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "RIGHTS_NOT_MODIFIED") {
+ return promise_.set_value(Unit());
+ }
+ td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id());
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetBotBroadcastDefaultAdminRightsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SetBotBroadcastDefaultAdminRightsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(AdministratorRights administrator_rights) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::bots_setBotBroadcastDefaultAdminRights(administrator_rights.get_chat_admin_rights()), {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::bots_setBotBroadcastDefaultAdminRights>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(WARNING, !result) << "Failed to set channel default administrator rights";
+ td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id());
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "RIGHTS_NOT_MODIFIED") {
+ return promise_.set_value(Unit());
+ }
+ td_->contacts_manager_->invalidate_user_full(td_->contacts_manager_->get_my_id());
+ promise_.set_error(std::move(status));
+ }
+};
+
+void set_account_ttl(Td *td, int32 account_ttl, Promise<Unit> &&promise) {
+ td->create_handler<SetAccountTtlQuery>(std::move(promise))->send(account_ttl);
+}
+
+void get_account_ttl(Td *td, Promise<int32> &&promise) {
+ td->create_handler<GetAccountTtlQuery>(std::move(promise))->send();
+}
+
+void confirm_qr_code_authentication(Td *td, const string &link,
+ Promise<td_api::object_ptr<td_api::session>> &&promise) {
+ Slice prefix("tg://login?token=");
+ if (!begins_with(to_lower(link), prefix)) {
+ return promise.set_error(Status::Error(400, "AUTH_TOKEN_INVALID"));
+ }
+ auto r_token = base64url_decode(Slice(link).substr(prefix.size()));
+ if (r_token.is_error()) {
+ return promise.set_error(Status::Error(400, "AUTH_TOKEN_INVALID"));
+ }
+ td->create_handler<AcceptLoginTokenQuery>(std::move(promise))->send(r_token.ok());
+}
+
+void get_active_sessions(Td *td, Promise<td_api::object_ptr<td_api::sessions>> &&promise) {
+ td->create_handler<GetAuthorizationsQuery>(std::move(promise))->send();
+}
+
+void terminate_session(Td *td, int64 session_id, Promise<Unit> &&promise) {
+ td->create_handler<ResetAuthorizationQuery>(std::move(promise))->send(session_id);
+}
+
+void terminate_all_other_sessions(Td *td, Promise<Unit> &&promise) {
+ td->create_handler<ResetAuthorizationsQuery>(std::move(promise))->send();
+}
+
+void toggle_session_can_accept_calls(Td *td, int64 session_id, bool can_accept_calls, Promise<Unit> &&promise) {
+ td->create_handler<ChangeAuthorizationSettingsQuery>(std::move(promise))
+ ->send(session_id, false, false, true, !can_accept_calls);
+}
+
+void toggle_session_can_accept_secret_chats(Td *td, int64 session_id, bool can_accept_secret_chats,
+ Promise<Unit> &&promise) {
+ td->create_handler<ChangeAuthorizationSettingsQuery>(std::move(promise))
+ ->send(session_id, true, !can_accept_secret_chats, false, false);
+}
+
+void set_inactive_session_ttl_days(Td *td, int32 authorization_ttl_days, Promise<Unit> &&promise) {
+ td->create_handler<SetAuthorizationTtlQuery>(std::move(promise))->send(authorization_ttl_days);
+}
+
+void get_connected_websites(Td *td, Promise<td_api::object_ptr<td_api::connectedWebsites>> &&promise) {
+ td->create_handler<GetWebAuthorizationsQuery>(std::move(promise))->send();
+}
+
+void disconnect_website(Td *td, int64 website_id, Promise<Unit> &&promise) {
+ td->create_handler<ResetWebAuthorizationQuery>(std::move(promise))->send(website_id);
+}
+
+void disconnect_all_websites(Td *td, Promise<Unit> &&promise) {
+ td->create_handler<ResetWebAuthorizationsQuery>(std::move(promise))->send();
+}
+
+void set_default_group_administrator_rights(Td *td, AdministratorRights administrator_rights, Promise<Unit> &&promise) {
+ td->contacts_manager_->invalidate_user_full(td->contacts_manager_->get_my_id());
+ td->create_handler<SetBotGroupDefaultAdminRightsQuery>(std::move(promise))->send(administrator_rights);
+}
+
+void set_default_channel_administrator_rights(Td *td, AdministratorRights administrator_rights,
+ Promise<Unit> &&promise) {
+ td->contacts_manager_->invalidate_user_full(td->contacts_manager_->get_my_id());
+ td->create_handler<SetBotBroadcastDefaultAdminRightsQuery>(std::move(promise))->send(administrator_rights);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Account.h b/protocols/Telegram/tdlib/td/td/telegram/Account.h
new file mode 100644
index 0000000000..cca9d9f373
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Account.h
@@ -0,0 +1,49 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+
+namespace td {
+
+class Td;
+
+void set_account_ttl(Td *td, int32 account_ttl, Promise<Unit> &&promise);
+
+void get_account_ttl(Td *td, Promise<int32> &&promise);
+
+void confirm_qr_code_authentication(Td *td, const string &link, Promise<td_api::object_ptr<td_api::session>> &&promise);
+
+void get_active_sessions(Td *td, Promise<td_api::object_ptr<td_api::sessions>> &&promise);
+
+void terminate_session(Td *td, int64 session_id, Promise<Unit> &&promise);
+
+void terminate_all_other_sessions(Td *td, Promise<Unit> &&promise);
+
+void toggle_session_can_accept_calls(Td *td, int64 session_id, bool can_accept_calls, Promise<Unit> &&promise);
+
+void toggle_session_can_accept_secret_chats(Td *td, int64 session_id, bool can_accept_secret_chats,
+ Promise<Unit> &&promise);
+
+void set_inactive_session_ttl_days(Td *td, int32 authorization_ttl_days, Promise<Unit> &&promise);
+
+void get_connected_websites(Td *td, Promise<td_api::object_ptr<td_api::connectedWebsites>> &&promise);
+
+void disconnect_website(Td *td, int64 website_id, Promise<Unit> &&promise);
+
+void disconnect_all_websites(Td *td, Promise<Unit> &&promise);
+
+void set_default_group_administrator_rights(Td *td, AdministratorRights administrator_rights, Promise<Unit> &&promise);
+
+void set_default_channel_administrator_rights(Td *td, AdministratorRights administrator_rights,
+ Promise<Unit> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AffectedHistory.h b/protocols/Telegram/tdlib/td/td/telegram/AffectedHistory.h
new file mode 100644
index 0000000000..850b1066c2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/AffectedHistory.h
@@ -0,0 +1,33 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+struct AffectedHistory {
+ int32 pts_;
+ int32 pts_count_;
+ bool is_final_;
+
+ explicit AffectedHistory(tl_object_ptr<telegram_api::messages_affectedHistory> &&affected_history)
+ : pts_(affected_history->pts_)
+ , pts_count_(affected_history->pts_count_)
+ , is_final_(affected_history->offset_ <= 0) {
+ }
+
+ explicit AffectedHistory(tl_object_ptr<telegram_api::messages_affectedFoundMessages> &&affected_history)
+ : pts_(affected_history->pts_)
+ , pts_count_(affected_history->pts_count_)
+ , is_final_(affected_history->offset_ <= 0) {
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.cpp
index 6a372b1fc4..c3c1619aad 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.cpp
@@ -1,26 +1,33 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/AnimationsManager.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/AuthManager.h"
#include "td/telegram/DialogId.h"
+#include "td/telegram/Document.h"
#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/FileReferenceManager.h"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/misc.h"
-#include "td/telegram/Td.h"
-
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/PhotoFormat.h"
#include "td/telegram/secret_api.h"
+#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
#include "td/telegram/telegram_api.h"
+#include "td/db/SqliteKeyValueAsync.h"
+
+#include "td/utils/algorithm.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Random.h"
@@ -31,129 +38,162 @@
namespace td {
-class GetSavedGifsQuery : public Td::ResultHandler {
+class GetSavedGifsQuery final : public Td::ResultHandler {
+ bool is_repair_ = false;
+
public:
- void send(int32 hash) {
- LOG(INFO) << "Send get saved animations request with hash = " << hash;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getSavedGifs(hash))));
+ void send(bool is_repair, int64 hash) {
+ is_repair_ = is_repair;
+ send_query(G()->net_query_creator().create(telegram_api::messages_getSavedGifs(hash)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getSavedGifs>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- td->animations_manager_->on_get_saved_animations(std::move(ptr));
+ td_->animations_manager_->on_get_saved_animations(is_repair_, std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for get saved animations: " << status;
- td->animations_manager_->on_get_saved_animations_failed(std::move(status));
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for get saved animations: " << status;
+ }
+ td_->animations_manager_->on_get_saved_animations_failed(is_repair_, std::move(status));
}
};
-class SaveGifQuery : public Td::ResultHandler {
+class SaveGifQuery final : public Td::ResultHandler {
+ FileId file_id_;
+ string file_reference_;
+ bool unsave_ = false;
+
Promise<Unit> promise_;
public:
explicit SaveGifQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(tl_object_ptr<telegram_api::InputDocument> &&input_document, bool unsave) {
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_saveGif(std::move(input_document), unsave))));
+ void send(FileId file_id, tl_object_ptr<telegram_api::inputDocument> &&input_document, bool unsave) {
+ CHECK(input_document != nullptr);
+ CHECK(file_id.is_valid());
+ file_id_ = file_id;
+ file_reference_ = input_document->file_reference_.as_slice().str();
+ unsave_ = unsave;
+ send_query(G()->net_query_creator().create(telegram_api::messages_saveGif(std::move(input_document), unsave)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_saveGif>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for save gif: " << result;
+ LOG(INFO) << "Receive result for save GIF: " << result;
if (!result) {
- td->animations_manager_->reload_saved_animations(true);
+ td_->animations_manager_->reload_saved_animations(true);
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for save gif: " << status;
- td->animations_manager_->reload_saved_animations(true);
+ void on_error(Status status) final {
+ if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ VLOG(file_references) << "Receive " << status << " for " << file_id_;
+ td_->file_manager_->delete_file_reference(file_id_, file_reference_);
+ td_->file_reference_manager_->repair_file_reference(
+ file_id_, PromiseCreator::lambda([animation_id = file_id_, unsave = unsave_,
+ promise = std::move(promise_)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(Status::Error(400, "Failed to find the animation"));
+ }
+
+ send_closure(G()->animations_manager(), &AnimationsManager::send_save_gif_query, animation_id, unsave,
+ std::move(promise));
+ }));
+ return;
+ }
+
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for save GIF: " << status;
+ }
+ td_->animations_manager_->reload_saved_animations(true);
promise_.set_error(std::move(status));
}
};
AnimationsManager::AnimationsManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
- auto limit_string = G()->td_db()->get_binlog_pmc()->get("saved_animations_limit");
- if (!limit_string.empty()) {
- auto new_limit = to_integer<int32>(limit_string);
- if (new_limit > 0) {
- LOG(DEBUG) << "Load saved animations limit = " << new_limit;
- saved_animations_limit_ = new_limit;
- } else {
- LOG(ERROR) << "Wrong saved animations limit = \"" << limit_string << "\" stored in database";
- }
- }
+ on_update_animation_search_emojis();
+ on_update_animation_search_provider();
+ on_update_saved_animations_limit();
+
+ next_saved_animations_load_time_ = Time::now();
+
+ G()->td_db()->get_binlog_pmc()->erase("saved_animations_limit"); // legacy
+}
+
+AnimationsManager::~AnimationsManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), animations_);
}
void AnimationsManager::tear_down() {
parent_.reset();
}
-int32 AnimationsManager::get_animation_duration(FileId file_id) {
- auto &animation = animations_[file_id];
+int32 AnimationsManager::get_animation_duration(FileId file_id) const {
+ const auto *animation = get_animation(file_id);
CHECK(animation != nullptr);
return animation->duration;
}
-tl_object_ptr<td_api::animation> AnimationsManager::get_animation_object(FileId file_id, const char *source) {
+tl_object_ptr<td_api::animation> AnimationsManager::get_animation_object(FileId file_id) const {
if (!file_id.is_valid()) {
return nullptr;
}
- auto &animation = animations_[file_id];
- CHECK(animation != nullptr) << source << " " << file_id << " "
- << static_cast<int32>(td_->file_manager_->get_file_view(file_id).get_type());
- // TODO can we make that function const?
- animation->is_changed = false;
+ auto animation = get_animation(file_id);
+ CHECK(animation != nullptr);
+ auto thumbnail =
+ animation->animated_thumbnail.file_id.is_valid()
+ ? get_thumbnail_object(td_->file_manager_.get(), animation->animated_thumbnail, PhotoFormat::Mpeg4)
+ : get_thumbnail_object(td_->file_manager_.get(), animation->thumbnail, PhotoFormat::Jpeg);
return make_tl_object<td_api::animation>(animation->duration, animation->dimensions.width,
animation->dimensions.height, animation->file_name, animation->mime_type,
- get_photo_size_object(td_->file_manager_.get(), &animation->thumbnail),
- td_->file_manager_->get_file_object(file_id));
+ animation->has_stickers, get_minithumbnail_object(animation->minithumbnail),
+ std::move(thumbnail), td_->file_manager_->get_file_object(file_id));
}
-FileId AnimationsManager::on_get_animation(std::unique_ptr<Animation> new_animation, bool replace) {
+FileId AnimationsManager::on_get_animation(unique_ptr<Animation> new_animation, bool replace) {
auto file_id = new_animation->file_id;
- LOG(INFO) << (replace ? "Replace" : "Add") << " animation " << file_id << " of size " << new_animation->dimensions;
+ CHECK(file_id.is_valid());
auto &a = animations_[file_id];
+ LOG(INFO) << (a == nullptr ? "Add" : (replace ? "Replace" : "Ignore")) << " animation " << file_id << " of size "
+ << new_animation->dimensions;
if (a == nullptr) {
a = std::move(new_animation);
} else if (replace) {
CHECK(a->file_id == file_id);
if (a->mime_type != new_animation->mime_type) {
LOG(DEBUG) << "Animation " << file_id << " info has changed";
- a->mime_type = new_animation->mime_type;
- a->is_changed = true;
+ a->mime_type = std::move(new_animation->mime_type);
}
if (a->file_name != new_animation->file_name) {
LOG(DEBUG) << "Animation " << file_id << " file name has changed";
a->file_name = std::move(new_animation->file_name);
- a->is_changed = true;
}
if (a->dimensions != new_animation->dimensions) {
- LOG(DEBUG) << "Animation " << file_id << " dimensions has changed";
+ LOG(DEBUG) << "Animation " << file_id << " dimensions have changed";
a->dimensions = new_animation->dimensions;
- a->is_changed = true;
}
if (a->duration != new_animation->duration) {
LOG(DEBUG) << "Animation " << file_id << " duration has changed";
a->duration = new_animation->duration;
- a->is_changed = true;
+ }
+ if (a->minithumbnail != new_animation->minithumbnail) {
+ a->minithumbnail = std::move(new_animation->minithumbnail);
}
if (a->thumbnail != new_animation->thumbnail) {
if (!a->thumbnail.file_id.is_valid()) {
@@ -162,8 +202,22 @@ FileId AnimationsManager::on_get_animation(std::unique_ptr<Animation> new_animat
LOG(INFO) << "Animation " << file_id << " thumbnail has changed from " << a->thumbnail << " to "
<< new_animation->thumbnail;
}
- a->thumbnail = new_animation->thumbnail;
- a->is_changed = true;
+ a->thumbnail = std::move(new_animation->thumbnail);
+ }
+ if (a->animated_thumbnail != new_animation->animated_thumbnail) {
+ if (!a->animated_thumbnail.file_id.is_valid()) {
+ LOG(DEBUG) << "Animation " << file_id << " animated thumbnail has changed";
+ } else {
+ LOG(INFO) << "Animation " << file_id << " animated thumbnail has changed from " << a->animated_thumbnail
+ << " to " << new_animation->animated_thumbnail;
+ }
+ a->animated_thumbnail = std::move(new_animation->animated_thumbnail);
+ }
+ if (a->has_stickers != new_animation->has_stickers && new_animation->has_stickers) {
+ a->has_stickers = new_animation->has_stickers;
+ }
+ if (a->sticker_file_ids != new_animation->sticker_file_ids && !new_animation->sticker_file_ids.empty()) {
+ a->sticker_file_ids = std::move(new_animation->sticker_file_ids);
}
}
@@ -171,13 +225,7 @@ FileId AnimationsManager::on_get_animation(std::unique_ptr<Animation> new_animat
}
const AnimationsManager::Animation *AnimationsManager::get_animation(FileId file_id) const {
- auto animation = animations_.find(file_id);
- if (animation == animations_.end()) {
- return nullptr;
- }
-
- CHECK(animation->second->file_id == file_id);
- return animation->second.get();
+ return animations_.get_pointer(file_id);
}
FileId AnimationsManager::get_animation_thumbnail_file_id(FileId file_id) const {
@@ -186,10 +234,17 @@ FileId AnimationsManager::get_animation_thumbnail_file_id(FileId file_id) const
return animation->thumbnail.file_id;
}
+FileId AnimationsManager::get_animation_animated_thumbnail_file_id(FileId file_id) const {
+ auto animation = get_animation(file_id);
+ CHECK(animation != nullptr);
+ return animation->animated_thumbnail.file_id;
+}
+
void AnimationsManager::delete_animation_thumbnail(FileId file_id) {
auto &animation = animations_[file_id];
CHECK(animation != nullptr);
animation->thumbnail = PhotoSize();
+ animation->animated_thumbnail = AnimationSize();
}
FileId AnimationsManager::dup_animation(FileId new_id, FileId old_id) {
@@ -197,53 +252,43 @@ FileId AnimationsManager::dup_animation(FileId new_id, FileId old_id) {
const Animation *old_animation = get_animation(old_id);
CHECK(old_animation != nullptr);
auto &new_animation = animations_[new_id];
- CHECK(!new_animation);
- new_animation = std::make_unique<Animation>(*old_animation);
+ CHECK(new_animation == nullptr);
+ new_animation = make_unique<Animation>(*old_animation);
new_animation->file_id = new_id;
- new_animation->thumbnail.file_id = td_->file_manager_->dup_file_id(new_animation->thumbnail.file_id);
+ new_animation->thumbnail.file_id = td_->file_manager_->dup_file_id(new_animation->thumbnail.file_id, "dup_animation");
+ new_animation->animated_thumbnail.file_id =
+ td_->file_manager_->dup_file_id(new_animation->animated_thumbnail.file_id, "dup_animation");
return new_id;
}
-bool AnimationsManager::merge_animations(FileId new_id, FileId old_id, bool can_delete_old) {
- if (!old_id.is_valid()) {
- LOG(ERROR) << "Old file id is invalid";
- return true;
- }
+void AnimationsManager::merge_animations(FileId new_id, FileId old_id) {
+ CHECK(old_id.is_valid() && new_id.is_valid());
+ CHECK(new_id != old_id);
LOG(INFO) << "Merge animations " << new_id << " and " << old_id;
const Animation *old_ = get_animation(old_id);
CHECK(old_ != nullptr);
- if (old_id == new_id) {
- return old_->is_changed;
- }
- auto new_it = animations_.find(new_id);
- if (new_it == animations_.end()) {
- auto &old = animations_[old_id];
- old->is_changed = true;
- if (!can_delete_old) {
- dup_animation(new_id, old_id);
- } else {
- old->file_id = new_id;
- animations_.emplace(new_id, std::move(old));
- }
+ bool need_merge = true;
+ const auto *new_ = get_animation(new_id);
+ if (new_ == nullptr) {
+ dup_animation(new_id, old_id);
} else {
- Animation *new_ = new_it->second.get();
- CHECK(new_ != nullptr);
-
- new_->is_changed = true;
if (old_->thumbnail != new_->thumbnail) {
// LOG_STATUS(td_->file_manager_->merge(new_->thumbnail.file_id, old_->thumbnail.file_id));
}
+ if (new_->file_name.size() == old_->file_name.size() + 4 && new_->file_name == old_->file_name + ".mp4") {
+ need_merge = false;
+ }
}
- LOG_STATUS(td_->file_manager_->merge(new_id, old_id));
- if (can_delete_old) {
- animations_.erase(old_id);
+ if (need_merge) {
+ LOG_STATUS(td_->file_manager_->merge(new_id, old_id));
}
- return true;
}
-void AnimationsManager::create_animation(FileId file_id, PhotoSize thumbnail, string file_name, string mime_type,
+void AnimationsManager::create_animation(FileId file_id, string minithumbnail, PhotoSize thumbnail,
+ AnimationSize animated_thumbnail, bool has_stickers,
+ vector<FileId> &&sticker_file_ids, string file_name, string mime_type,
int32 duration, Dimensions dimensions, bool replace) {
auto a = make_unique<Animation>();
a->file_id = file_id;
@@ -251,7 +296,13 @@ void AnimationsManager::create_animation(FileId file_id, PhotoSize thumbnail, st
a->mime_type = std::move(mime_type);
a->duration = max(duration, 0);
a->dimensions = dimensions;
+ if (!td_->auth_manager_->is_bot()) {
+ a->minithumbnail = std::move(minithumbnail);
+ }
a->thumbnail = std::move(thumbnail);
+ a->animated_thumbnail = std::move(animated_thumbnail);
+ a->has_stickers = has_stickers;
+ a->sticker_file_ids = std::move(sticker_file_ids);
on_get_animation(std::move(a), replace);
}
@@ -262,15 +313,15 @@ tl_object_ptr<telegram_api::InputMedia> AnimationsManager::get_input_media(
if (file_view.is_encrypted()) {
return nullptr;
}
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
- return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.remote_location().as_input_document(), 0);
+ if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
+ return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.main_remote_location().as_input_document(), 0,
+ string());
}
if (file_view.has_url()) {
return make_tl_object<telegram_api::inputMediaDocumentExternal>(0, file_view.url(), 0);
}
- CHECK(!file_view.has_remote_location());
- if (input_file) {
+ if (input_file != nullptr) {
const Animation *animation = get_animation(file_id);
CHECK(animation != nullptr);
@@ -279,7 +330,7 @@ tl_object_ptr<telegram_api::InputMedia> AnimationsManager::get_input_media(
attributes.push_back(make_tl_object<telegram_api::documentAttributeFilename>(animation->file_name));
}
string mime_type = animation->mime_type;
- if (animation->mime_type == "video/mp4") {
+ if (mime_type == "video/mp4") {
attributes.push_back(make_tl_object<telegram_api::documentAttributeVideo>(
0, false /*ignored*/, false /*ignored*/, animation->duration, animation->dimensions.width,
animation->dimensions.height));
@@ -291,28 +342,36 @@ tl_object_ptr<telegram_api::InputMedia> AnimationsManager::get_input_media(
animation->dimensions.height));
}
int32 flags = 0;
+ vector<tl_object_ptr<telegram_api::InputDocument>> added_stickers;
+ if (animation->has_stickers) {
+ flags |= telegram_api::inputMediaUploadedDocument::STICKERS_MASK;
+ added_stickers = td_->file_manager_->get_input_documents(animation->sticker_file_ids);
+ }
if (input_thumbnail != nullptr) {
flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK;
}
return make_tl_object<telegram_api::inputMediaUploadedDocument>(
- flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type, std::move(attributes),
- vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type,
+ std::move(attributes), std::move(added_stickers), 0);
+ } else {
+ CHECK(!file_view.has_remote_location());
}
return nullptr;
}
+
SecretInputMedia AnimationsManager::get_secret_input_media(FileId animation_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption, BufferSlice thumbnail) const {
+ const string &caption, BufferSlice thumbnail,
+ int32 layer) const {
auto *animation = get_animation(animation_file_id);
CHECK(animation != nullptr);
auto file_view = td_->file_manager_->get_file_view(animation_file_id);
- auto &encryption_key = file_view.encryption_key();
- if (encryption_key.empty()) {
+ if (!file_view.is_encrypted_secret() || file_view.encryption_key().empty()) {
return SecretInputMedia{};
}
if (file_view.has_remote_location()) {
- input_file = file_view.remote_location().as_input_encrypted_file();
+ input_file = file_view.main_remote_location().as_input_encrypted_file();
}
if (!input_file) {
return SecretInputMedia{};
@@ -326,7 +385,7 @@ SecretInputMedia AnimationsManager::get_secret_input_media(FileId animation_file
}
if (animation->duration != 0 && animation->mime_type == "video/mp4") {
attributes.push_back(make_tl_object<secret_api::documentAttributeVideo>(
- animation->duration, animation->dimensions.width, animation->dimensions.height));
+ 0, false, animation->duration, animation->dimensions.width, animation->dimensions.height));
}
if (animation->dimensions.width != 0 && animation->dimensions.height != 0) {
attributes.push_back(make_tl_object<secret_api::documentAttributeImageSize>(animation->dimensions.width,
@@ -334,19 +393,63 @@ SecretInputMedia AnimationsManager::get_secret_input_media(FileId animation_file
}
attributes.push_back(make_tl_object<secret_api::documentAttributeAnimated>());
- return SecretInputMedia{
- std::move(input_file),
- make_tl_object<secret_api::decryptedMessageMediaDocument>(
- std::move(thumbnail), animation->thumbnail.dimensions.width, animation->thumbnail.dimensions.height,
- animation->mime_type, narrow_cast<int32>(file_view.size()), BufferSlice(encryption_key.key_slice()),
- BufferSlice(encryption_key.iv_slice()), std::move(attributes), caption)};
+ return {std::move(input_file),
+ std::move(thumbnail),
+ animation->thumbnail.dimensions,
+ animation->mime_type,
+ file_view,
+ std::move(attributes),
+ caption,
+ layer};
+}
+
+void AnimationsManager::on_update_animation_search_emojis() {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ td_->option_manager_->set_option_empty("animation_search_emojis");
+ return;
+ }
+
+ auto animation_search_emojis = td_->option_manager_->get_option_string("animation_search_emojis");
+ is_animation_search_emojis_inited_ = true;
+ if (animation_search_emojis_ == animation_search_emojis) {
+ return;
+ }
+ animation_search_emojis_ = std::move(animation_search_emojis);
+
+ try_send_update_animation_search_parameters();
}
-void AnimationsManager::on_update_saved_animations_limit(int32 saved_animations_limit) {
+void AnimationsManager::on_update_animation_search_provider() {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ td_->option_manager_->set_option_empty("animation_search_provider");
+ return;
+ }
+
+ string animation_search_provider = td_->option_manager_->get_option_string("animation_search_provider");
+ is_animation_search_provider_inited_ = true;
+ if (animation_search_provider_ == animation_search_provider) {
+ return;
+ }
+ animation_search_provider_ = std::move(animation_search_provider);
+
+ try_send_update_animation_search_parameters();
+}
+
+void AnimationsManager::on_update_saved_animations_limit() {
+ if (G()->close_flag()) {
+ return;
+ }
+ auto saved_animations_limit =
+ narrow_cast<int32>(td_->option_manager_->get_option_integer("saved_animations_limit", 200));
if (saved_animations_limit != saved_animations_limit_) {
if (saved_animations_limit > 0) {
LOG(INFO) << "Update saved animations limit to " << saved_animations_limit;
- G()->td_db()->get_binlog_pmc()->set("saved_animations_limit", to_string(saved_animations_limit));
saved_animations_limit_ = saved_animations_limit;
if (static_cast<int32>(saved_animation_ids_.size()) > saved_animations_limit_) {
saved_animation_ids_.resize(saved_animations_limit_);
@@ -388,11 +491,26 @@ class AnimationsManager::AnimationListLogEvent {
};
void AnimationsManager::reload_saved_animations(bool force) {
- if (!td_->auth_manager_->is_bot() && next_saved_animations_load_time_ >= 0 &&
+ if (G()->close_flag()) {
+ return;
+ }
+
+ if (!td_->auth_manager_->is_bot() && !are_saved_animations_being_loaded_ &&
(next_saved_animations_load_time_ < Time::now() || force)) {
LOG_IF(INFO, force) << "Reload saved animations";
- next_saved_animations_load_time_ = -1;
- td_->create_handler<GetSavedGifsQuery>()->send(get_saved_animations_hash());
+ are_saved_animations_being_loaded_ = true;
+ td_->create_handler<GetSavedGifsQuery>()->send(false, get_saved_animations_hash("reload_saved_animations"));
+ }
+}
+
+void AnimationsManager::repair_saved_animations(Promise<Unit> &&promise) {
+ if (td_->auth_manager_->is_bot()) {
+ return promise.set_error(Status::Error(400, "Bots have no saved animations"));
+ }
+
+ repair_saved_animations_queries_.push_back(std::move(promise));
+ if (repair_saved_animations_queries_.size() == 1u) {
+ td_->create_handler<GetSavedGifsQuery>()->send(true, 0);
}
}
@@ -432,6 +550,9 @@ void AnimationsManager::load_saved_animations(Promise<Unit> &&promise) {
}
void AnimationsManager::on_load_saved_animations_from_database(const string &value) {
+ if (G()->close_flag()) {
+ return;
+ }
if (value.empty()) {
LOG(INFO) << "Saved animations aren't found in database";
reload_saved_animations(true);
@@ -453,21 +574,23 @@ void AnimationsManager::on_load_saved_animations_finished(vector<FileId> &&saved
saved_animation_ids_ = std::move(saved_animation_ids);
are_saved_animations_loaded_ = true;
send_update_saved_animations(from_database);
- auto promises = std::move(load_saved_animations_queries_);
- load_saved_animations_queries_.clear();
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(load_saved_animations_queries_);
}
void AnimationsManager::on_get_saved_animations(
- tl_object_ptr<telegram_api::messages_SavedGifs> &&saved_animations_ptr) {
+ bool is_repair, tl_object_ptr<telegram_api::messages_SavedGifs> &&saved_animations_ptr) {
CHECK(!td_->auth_manager_->is_bot());
- next_saved_animations_load_time_ = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
+ if (!is_repair) {
+ are_saved_animations_being_loaded_ = false;
+ next_saved_animations_load_time_ = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
+ }
CHECK(saved_animations_ptr != nullptr);
int32 constructor_id = saved_animations_ptr->get_id();
if (constructor_id == telegram_api::messages_savedGifsNotModified::ID) {
+ if (is_repair) {
+ return on_get_saved_animations_failed(true, Status::Error(500, "Failed to reload saved animations"));
+ }
LOG(INFO) << "Saved animations are not modified";
return;
}
@@ -480,57 +603,60 @@ void AnimationsManager::on_get_saved_animations(
for (auto &document_ptr : saved_animations->gifs_) {
int32 document_constructor_id = document_ptr->get_id();
if (document_constructor_id == telegram_api::documentEmpty::ID) {
- LOG(ERROR) << "Empty gif document received";
+ LOG(ERROR) << "Empty saved animation document received";
continue;
}
CHECK(document_constructor_id == telegram_api::document::ID);
auto document =
td_->documents_manager_->on_get_document(move_tl_object_as<telegram_api::document>(document_ptr), DialogId());
- if (document.first != DocumentsManager::DocumentType::Animation) {
- LOG(ERROR) << "Receive " << static_cast<int>(document.first) << " instead of animation as saved animation";
+ if (document.type != Document::Type::Animation) {
+ LOG(ERROR) << "Receive " << document << " instead of animation as saved animation";
continue;
}
- saved_animation_ids.push_back(document.second);
+ if (!is_repair) {
+ saved_animation_ids.push_back(document.file_id);
+ }
}
- on_load_saved_animations_finished(std::move(saved_animation_ids));
+ if (is_repair) {
+ set_promises(repair_saved_animations_queries_);
+ } else {
+ on_load_saved_animations_finished(std::move(saved_animation_ids));
- LOG_IF(ERROR, get_saved_animations_hash() != saved_animations->hash_)
- << "Saved animations hash mismatch: " << saved_animations->hash_ << " vs " << get_saved_animations_hash();
+ LOG_IF(ERROR, get_saved_animations_hash("on_get_saved_animations") != saved_animations->hash_)
+ << "Saved animations hash mismatch: " << saved_animations->hash_ << " vs "
+ << get_saved_animations_hash("on_get_saved_animations 2");
+ }
}
-void AnimationsManager::on_get_saved_animations_failed(Status error) {
+void AnimationsManager::on_get_saved_animations_failed(bool is_repair, Status error) {
CHECK(error.is_error());
- next_saved_animations_load_time_ = Time::now_cached() + Random::fast(5, 10);
- auto promises = std::move(load_saved_animations_queries_);
- load_saved_animations_queries_.clear();
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+ if (!is_repair) {
+ are_saved_animations_being_loaded_ = false;
+ next_saved_animations_load_time_ = Time::now_cached() + Random::fast(5, 10);
}
+ fail_promises(is_repair ? repair_saved_animations_queries_ : load_saved_animations_queries_, std::move(error));
}
-int32 AnimationsManager::get_saved_animations_hash() const {
- vector<uint32> numbers;
- numbers.reserve(saved_animation_ids_.size() * 2);
+int64 AnimationsManager::get_saved_animations_hash(const char *source) const {
+ vector<uint64> numbers;
+ numbers.reserve(saved_animation_ids_.size());
for (auto animation_id : saved_animation_ids_) {
auto animation = get_animation(animation_id);
CHECK(animation != nullptr);
auto file_view = td_->file_manager_->get_file_view(animation_id);
CHECK(file_view.has_remote_location());
- CHECK(!file_view.remote_location().is_encrypted());
- CHECK(!file_view.remote_location().is_web());
- auto id = static_cast<uint64>(file_view.remote_location().get_id());
- numbers.push_back(static_cast<uint32>(id >> 32));
- numbers.push_back(static_cast<uint32>(id & 0xFFFFFFFF));
+ if (!file_view.remote_location().is_document()) {
+ LOG(ERROR) << "Saved animation remote location is not document: " << source << " " << file_view.remote_location();
+ continue;
+ }
+ numbers.push_back(file_view.remote_location().get_id());
}
return get_vector_hash(numbers);
}
void AnimationsManager::add_saved_animation(const tl_object_ptr<td_api::InputFile> &input_file,
Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(7, "Method is not available for bots"));
- }
if (!are_saved_animations_loaded_) {
load_saved_animations(std::move(promise));
return;
@@ -538,76 +664,91 @@ void AnimationsManager::add_saved_animation(const tl_object_ptr<td_api::InputFil
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Animation, input_file, DialogId(), false, false);
if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(7, r_file_id.error().message())); // TODO do not drop error code
+ return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code
}
- add_saved_animation_inner(r_file_id.ok(), std::move(promise));
+ add_saved_animation_impl(r_file_id.ok(), true, std::move(promise));
}
-void AnimationsManager::add_saved_animation_inner(FileId animation_id, Promise<Unit> &&promise) {
- if (add_saved_animation_impl(animation_id, promise)) {
- // TODO invokeAfter and log event
- auto file_view = td_->file_manager_->get_file_view(animation_id);
- td_->create_handler<SaveGifQuery>(std::move(promise))->send(file_view.remote_location().as_input_document(), false);
- }
+void AnimationsManager::send_save_gif_query(FileId animation_id, bool unsave, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ // TODO invokeAfter and log event
+ auto file_view = td_->file_manager_->get_file_view(animation_id);
+ CHECK(file_view.has_remote_location());
+ LOG_CHECK(file_view.remote_location().is_document()) << file_view.remote_location();
+ CHECK(!file_view.remote_location().is_web());
+ td_->create_handler<SaveGifQuery>(std::move(promise))
+ ->send(animation_id, file_view.remote_location().as_input_document(), unsave);
}
void AnimationsManager::add_saved_animation_by_id(FileId animation_id) {
+ auto animation = get_animation(animation_id);
+ CHECK(animation != nullptr);
+ if (animation->has_stickers) {
+ return;
+ }
+
// TODO log event
- Promise<Unit> promise;
- add_saved_animation_impl(animation_id, promise);
+ add_saved_animation_impl(animation_id, false, Auto());
}
-bool AnimationsManager::add_saved_animation_impl(FileId animation_id, Promise<Unit> &promise) {
+void AnimationsManager::add_saved_animation_impl(FileId animation_id, bool add_on_server, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
+ auto file_view = td_->file_manager_->get_file_view(animation_id);
+ if (file_view.empty()) {
+ return promise.set_error(Status::Error(400, "Animation file not found"));
+ }
+
+ LOG(INFO) << "Add saved animation " << animation_id << " with main file " << file_view.get_main_file_id();
if (!are_saved_animations_loaded_) {
- load_saved_animations(PromiseCreator::lambda([animation_id, promise = std::move(promise)](Result<> result) mutable {
- if (result.is_ok()) {
- send_closure(G()->animations_manager(), &AnimationsManager::add_saved_animation_inner, animation_id,
- std::move(promise));
- } else {
- promise.set_error(result.move_as_error());
- }
- }));
- return false;
+ load_saved_animations(
+ PromiseCreator::lambda([animation_id, add_on_server, promise = std::move(promise)](Result<> result) mutable {
+ if (result.is_ok()) {
+ send_closure(G()->animations_manager(), &AnimationsManager::add_saved_animation_impl, animation_id,
+ add_on_server, std::move(promise));
+ } else {
+ promise.set_error(result.move_as_error());
+ }
+ }));
+ return;
}
- if (!saved_animation_ids_.empty() && saved_animation_ids_[0] == animation_id) {
+ auto is_equal = [animation_id](FileId file_id) {
+ return file_id == animation_id ||
+ (file_id.get_remote() == animation_id.get_remote() && animation_id.get_remote() != 0);
+ };
+
+ if (!saved_animation_ids_.empty() && is_equal(saved_animation_ids_[0])) {
+ // fast path
if (saved_animation_ids_[0].get_remote() == 0 && animation_id.get_remote() != 0) {
saved_animation_ids_[0] = animation_id;
save_saved_animations_to_database();
}
- promise.set_value(Unit());
- return false;
+ return promise.set_value(Unit());
}
auto animation = get_animation(animation_id);
if (animation == nullptr) {
- promise.set_error(Status::Error(7, "Animation not found"));
- return false;
+ return promise.set_error(Status::Error(400, "Animation not found"));
}
if (animation->mime_type != "video/mp4") {
- promise.set_error(Status::Error(7, "Only MPEG4 animations can be saved"));
- return false;
+ return promise.set_error(Status::Error(400, "Only MPEG4 animations can be saved"));
}
- auto file_view = td_->file_manager_->get_file_view(animation_id);
if (!file_view.has_remote_location()) {
- promise.set_error(Status::Error(7, "Can save only sent animations"));
- return false;
- }
- if (file_view.remote_location().is_encrypted()) {
- promise.set_error(Status::Error(7, "Can't save encrypted animations"));
- return false;
+ return promise.set_error(Status::Error(400, "Can save only sent animations"));
}
if (file_view.remote_location().is_web()) {
- promise.set_error(Status::Error(7, "Can't save web animations"));
- return false;
+ return promise.set_error(Status::Error(400, "Can't save web animations"));
+ }
+ if (!file_view.remote_location().is_document()) {
+ return promise.set_error(Status::Error(400, "Can't save encrypted animations"));
}
- auto it = std::find(saved_animation_ids_.begin(), saved_animation_ids_.end(), animation_id);
+ auto it = std::find_if(saved_animation_ids_.begin(), saved_animation_ids_.end(), is_equal);
if (it == saved_animation_ids_.end()) {
if (static_cast<int32>(saved_animation_ids_.size()) == saved_animations_limit_) {
saved_animation_ids_.back() = animation_id;
@@ -617,19 +758,19 @@ bool AnimationsManager::add_saved_animation_impl(FileId animation_id, Promise<Un
it = saved_animation_ids_.end() - 1;
}
std::rotate(saved_animation_ids_.begin(), it, it + 1);
+ CHECK(is_equal(saved_animation_ids_[0]));
if (saved_animation_ids_[0].get_remote() == 0 && animation_id.get_remote() != 0) {
saved_animation_ids_[0] = animation_id;
}
send_update_saved_animations();
- return true;
+ if (add_on_server) {
+ send_save_gif_query(animation_id, false, std::move(promise));
+ }
}
void AnimationsManager::remove_saved_animation(const tl_object_ptr<td_api::InputFile> &input_file,
Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(7, "Method is not available for bots"));
- }
if (!are_saved_animations_loaded_) {
load_saved_animations(std::move(promise));
return;
@@ -637,40 +778,70 @@ void AnimationsManager::remove_saved_animation(const tl_object_ptr<td_api::Input
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Animation, input_file, DialogId(), false, false);
if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(7, r_file_id.error().message())); // TODO do not drop error code
+ return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code
}
FileId file_id = r_file_id.ok();
- auto it = std::find(saved_animation_ids_.begin(), saved_animation_ids_.end(), file_id);
- if (it == saved_animation_ids_.end()) {
+ auto is_equal = [animation_id = file_id](FileId file_id) {
+ return file_id == animation_id ||
+ (file_id.get_remote() == animation_id.get_remote() && animation_id.get_remote() != 0);
+ };
+ if (!td::remove_if(saved_animation_ids_, is_equal)) {
return promise.set_value(Unit());
}
auto animation = get_animation(file_id);
if (animation == nullptr) {
- return promise.set_error(Status::Error(7, "Animation not found"));
+ return promise.set_error(Status::Error(400, "Animation not found"));
}
- // TODO invokeAfter
- auto file_view = td_->file_manager_->get_file_view(file_id);
- CHECK(file_view.has_remote_location());
- CHECK(!file_view.remote_location().is_encrypted());
- CHECK(!file_view.remote_location().is_web());
- td_->create_handler<SaveGifQuery>(std::move(promise))->send(file_view.remote_location().as_input_document(), true);
-
- saved_animation_ids_.erase(it);
+ send_save_gif_query(file_id, true, std::move(promise));
send_update_saved_animations();
}
+void AnimationsManager::try_send_update_animation_search_parameters() const {
+ auto update_animation_search_parameters = get_update_animation_search_parameters_object();
+ if (update_animation_search_parameters != nullptr) {
+ send_closure(G()->td(), &Td::send_update, std::move(update_animation_search_parameters));
+ }
+}
+
+td_api::object_ptr<td_api::updateAnimationSearchParameters>
+AnimationsManager::get_update_animation_search_parameters_object() const {
+ if (!is_animation_search_emojis_inited_ || !is_animation_search_provider_inited_) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::updateAnimationSearchParameters>(animation_search_provider_,
+ full_split(animation_search_emojis_, ','));
+}
+
+td_api::object_ptr<td_api::updateSavedAnimations> AnimationsManager::get_update_saved_animations_object() const {
+ return td_api::make_object<td_api::updateSavedAnimations>(
+ td_->file_manager_->get_file_ids_object(saved_animation_ids_));
+}
+
void AnimationsManager::send_update_saved_animations(bool from_database) {
if (are_saved_animations_loaded_) {
- vector<int32> animations;
- animations.reserve(saved_animation_ids_.size());
- for (auto animation_id : saved_animation_ids_) {
- animations.push_back(animation_id.get());
+ vector<FileId> new_saved_animation_file_ids = saved_animation_ids_;
+ for (auto &animation_id : saved_animation_ids_) {
+ auto animation = get_animation(animation_id);
+ CHECK(animation != nullptr);
+ if (animation->thumbnail.file_id.is_valid()) {
+ new_saved_animation_file_ids.push_back(animation->thumbnail.file_id);
+ }
+ if (animation->animated_thumbnail.file_id.is_valid()) {
+ new_saved_animation_file_ids.push_back(animation->animated_thumbnail.file_id);
+ }
+ }
+ std::sort(new_saved_animation_file_ids.begin(), new_saved_animation_file_ids.end());
+ if (new_saved_animation_file_ids != saved_animation_file_ids_) {
+ td_->file_manager_->change_files_source(get_saved_animations_file_source_id(), saved_animation_file_ids_,
+ new_saved_animation_file_ids);
+ saved_animation_file_ids_ = std::move(new_saved_animation_file_ids);
}
- send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateSavedAnimations>(std::move(animations)));
+
+ send_closure(G()->td(), &Td::send_update, get_update_saved_animations_object());
if (!from_database) {
save_saved_animations_to_database();
@@ -686,10 +857,31 @@ void AnimationsManager::save_saved_animations_to_database() {
}
}
+FileSourceId AnimationsManager::get_saved_animations_file_source_id() {
+ if (!saved_animations_file_source_id_.is_valid()) {
+ saved_animations_file_source_id_ = td_->file_reference_manager_->create_saved_animations_file_source();
+ }
+ return saved_animations_file_source_id_;
+}
+
string AnimationsManager::get_animation_search_text(FileId file_id) const {
auto animation = get_animation(file_id);
CHECK(animation != nullptr);
return animation->file_name;
}
+void AnimationsManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (are_saved_animations_loaded_) {
+ updates.push_back(get_update_saved_animations_object());
+ }
+ auto update_animation_search_parameters = get_update_animation_search_parameters_object();
+ if (update_animation_search_parameters != nullptr) {
+ updates.push_back(std::move(update_animation_search_parameters));
+ }
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.h b/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.h
index 3282d70521..728f5e273e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.h
@@ -1,44 +1,47 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/SecretInputMedia.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/actor/actor.h"
-#include "td/telegram/files/FileId.h"
-#include "td/telegram/Photo.h"
-#include "td/telegram/SecretInputMedia.h"
-
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
-
-#include <unordered_map>
+#include "td/utils/WaitFreeHashMap.h"
namespace td {
-class Td;
-template <class T>
-class Promise;
-} // namespace td
-namespace td {
+class Td;
-class AnimationsManager : public Actor {
+class AnimationsManager final : public Actor {
public:
AnimationsManager(Td *td, ActorShared<> parent);
+ AnimationsManager(const AnimationsManager &) = delete;
+ AnimationsManager &operator=(const AnimationsManager &) = delete;
+ AnimationsManager(AnimationsManager &&) = delete;
+ AnimationsManager &operator=(AnimationsManager &&) = delete;
+ ~AnimationsManager() final;
- int32 get_animation_duration(FileId file_id);
+ int32 get_animation_duration(FileId file_id) const;
- tl_object_ptr<td_api::animation> get_animation_object(FileId file_id, const char *source);
+ tl_object_ptr<td_api::animation> get_animation_object(FileId file_id) const;
- void create_animation(FileId file_id, PhotoSize thumbnail, string file_name, string mime_type, int32 duration,
- Dimensions dimensions, bool replace);
+ void create_animation(FileId file_id, string minithumbnail, PhotoSize thumbnail, AnimationSize animated_thumbnail,
+ bool has_stickers, vector<FileId> &&sticker_file_ids, string file_name, string mime_type,
+ int32 duration, Dimensions dimensions, bool replace);
tl_object_ptr<telegram_api::InputMedia> get_input_media(FileId file_id,
tl_object_ptr<telegram_api::InputFile> input_file,
@@ -46,40 +49,54 @@ class AnimationsManager : public Actor {
SecretInputMedia get_secret_input_media(FileId animation_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption, BufferSlice thumbnail) const;
+ const string &caption, BufferSlice thumbnail, int32 layer) const;
FileId get_animation_thumbnail_file_id(FileId file_id) const;
+ FileId get_animation_animated_thumbnail_file_id(FileId file_id) const;
+
void delete_animation_thumbnail(FileId file_id);
FileId dup_animation(FileId new_id, FileId old_id);
- bool merge_animations(FileId new_id, FileId old_id, bool can_delete_old);
+ void merge_animations(FileId new_id, FileId old_id);
+
+ void on_update_animation_search_emojis();
- void on_update_saved_animations_limit(int32 saved_animations_limit);
+ void on_update_animation_search_provider();
+
+ void on_update_saved_animations_limit();
void reload_saved_animations(bool force);
- void on_get_saved_animations(tl_object_ptr<telegram_api::messages_SavedGifs> &&saved_animations_ptr);
+ void repair_saved_animations(Promise<Unit> &&promise);
+
+ void on_get_saved_animations(bool is_repair, tl_object_ptr<telegram_api::messages_SavedGifs> &&saved_animations_ptr);
- void on_get_saved_animations_failed(Status error);
+ void on_get_saved_animations_failed(bool is_repair, Status error);
vector<FileId> get_saved_animations(Promise<Unit> &&promise);
+ FileSourceId get_saved_animations_file_source_id();
+
+ void send_save_gif_query(FileId animation_id, bool unsave, Promise<Unit> &&promise);
+
void add_saved_animation(const tl_object_ptr<td_api::InputFile> &input_file, Promise<Unit> &&promise);
void add_saved_animation_by_id(FileId animation_id);
void remove_saved_animation(const tl_object_ptr<td_api::InputFile> &input_file, Promise<Unit> &&promise);
- template <class T>
- void store_animation(FileId file_id, T &storer) const;
+ template <class StorerT>
+ void store_animation(FileId file_id, StorerT &storer) const;
- template <class T>
- FileId parse_animation(T &parser);
+ template <class ParserT>
+ FileId parse_animation(ParserT &parser);
string get_animation_search_text(FileId file_id) const;
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
private:
class Animation {
public:
@@ -87,22 +104,23 @@ class AnimationsManager : public Actor {
string mime_type;
int32 duration = 0;
Dimensions dimensions;
+ string minithumbnail;
PhotoSize thumbnail;
+ AnimationSize animated_thumbnail;
- FileId file_id;
+ bool has_stickers = false;
+ vector<FileId> sticker_file_ids;
- bool is_changed = true;
+ FileId file_id;
};
const Animation *get_animation(FileId file_id) const;
- FileId on_get_animation(std::unique_ptr<Animation> new_animation, bool replace);
-
- int32 get_saved_animations_hash() const;
+ FileId on_get_animation(unique_ptr<Animation> new_animation, bool replace);
- void add_saved_animation_inner(FileId animation_id, Promise<Unit> &&promise);
+ int64 get_saved_animations_hash(const char *source) const;
- bool add_saved_animation_impl(FileId animation_id, Promise<Unit> &promise);
+ void add_saved_animation_impl(FileId animation_id, bool add_on_server, Promise<Unit> &&promise);
void load_saved_animations(Promise<Unit> &&promise);
@@ -110,24 +128,39 @@ class AnimationsManager : public Actor {
void on_load_saved_animations_finished(vector<FileId> &&saved_animation_ids, bool from_database = false);
+ void try_send_update_animation_search_parameters() const;
+
+ td_api::object_ptr<td_api::updateAnimationSearchParameters> get_update_animation_search_parameters_object() const;
+
+ td_api::object_ptr<td_api::updateSavedAnimations> get_update_saved_animations_object() const;
+
void send_update_saved_animations(bool from_database = false);
void save_saved_animations_to_database();
- void tear_down() override;
+ void tear_down() final;
class AnimationListLogEvent;
Td *td_;
ActorShared<> parent_;
- std::unordered_map<FileId, unique_ptr<Animation>, FileIdHash> animations_;
+ WaitFreeHashMap<FileId, unique_ptr<Animation>, FileIdHash> animations_;
int32 saved_animations_limit_ = 200;
vector<FileId> saved_animation_ids_;
+ vector<FileId> saved_animation_file_ids_;
double next_saved_animations_load_time_ = 0;
+ bool are_saved_animations_being_loaded_ = false;
bool are_saved_animations_loaded_ = false;
vector<Promise<Unit>> load_saved_animations_queries_;
+ vector<Promise<Unit>> repair_saved_animations_queries_;
+ FileSourceId saved_animations_file_source_id_;
+
+ string animation_search_emojis_;
+ string animation_search_provider_;
+ bool is_animation_search_emojis_inited_ = false;
+ bool is_animation_search_provider_inited_ = false;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.hpp
index 1e45039333..d34becc5c7 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/AnimationsManager.hpp
@@ -1,45 +1,76 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/telegram/AnimationsManager.h"
#include "td/telegram/files/FileId.hpp"
-#include "td/telegram/Photo.hpp"
+#include "td/telegram/PhotoSize.hpp"
#include "td/telegram/Version.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/tl_helpers.h"
namespace td {
-template <class T>
-void AnimationsManager::store_animation(FileId file_id, T &storer) const {
- auto it = animations_.find(file_id);
- CHECK(it != animations_.end());
- const Animation *animation = it->second.get();
+template <class StorerT>
+void AnimationsManager::store_animation(FileId file_id, StorerT &storer) const {
+ const Animation *animation = get_animation(file_id);
+ CHECK(animation != nullptr);
+ bool has_animated_thumbnail = animation->animated_thumbnail.file_id.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(animation->has_stickers);
+ STORE_FLAG(has_animated_thumbnail);
+ END_STORE_FLAGS();
store(animation->duration, storer);
store(animation->dimensions, storer);
store(animation->file_name, storer);
store(animation->mime_type, storer);
+ store(animation->minithumbnail, storer);
store(animation->thumbnail, storer);
store(file_id, storer);
+ if (animation->has_stickers) {
+ store(animation->sticker_file_ids, storer);
+ }
+ if (has_animated_thumbnail) {
+ store(animation->animated_thumbnail, storer);
+ }
}
-template <class T>
-FileId AnimationsManager::parse_animation(T &parser) {
+template <class ParserT>
+FileId AnimationsManager::parse_animation(ParserT &parser) {
auto animation = make_unique<Animation>();
+ bool has_animated_thumbnail = false;
+ if (parser.version() >= static_cast<int32>(Version::AddAnimationStickers)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(animation->has_stickers);
+ PARSE_FLAG(has_animated_thumbnail);
+ END_PARSE_FLAGS();
+ }
if (parser.version() >= static_cast<int32>(Version::AddDurationToAnimation)) {
parse(animation->duration, parser);
}
parse(animation->dimensions, parser);
parse(animation->file_name, parser);
parse(animation->mime_type, parser);
+ if (parser.version() >= static_cast<int32>(Version::SupportMinithumbnails)) {
+ parse(animation->minithumbnail, parser);
+ }
parse(animation->thumbnail, parser);
parse(animation->file_id, parser);
+ if (animation->has_stickers) {
+ parse(animation->sticker_file_ids, parser);
+ }
+ if (has_animated_thumbnail) {
+ parse(animation->animated_thumbnail, parser);
+ }
+ if (parser.get_error() != nullptr || !animation->file_id.is_valid()) {
+ return FileId();
+ }
return on_get_animation(std::move(animation), false);
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Application.cpp b/protocols/Telegram/tdlib/td/td/telegram/Application.cpp
new file mode 100644
index 0000000000..ca0ac61a2e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Application.cpp
@@ -0,0 +1,135 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Application.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/logevent/LogEventHelper.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/Status.h"
+#include "td/utils/tl_parsers.h"
+
+namespace td {
+
+class GetInviteTextQuery final : public Td::ResultHandler {
+ Promise<string> promise_;
+
+ public:
+ explicit GetInviteTextQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::help_getInviteText()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_getInviteText>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ promise_.set_value(std::move(result->message_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SaveAppLogQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SaveAppLogQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(telegram_api::object_ptr<telegram_api::inputAppEvent> &&input_app_event) {
+ vector<telegram_api::object_ptr<telegram_api::inputAppEvent>> input_app_events;
+ input_app_events.push_back(std::move(input_app_event));
+ send_query(G()->net_query_creator().create_unauth(telegram_api::help_saveAppLog(std::move(input_app_events))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_saveAppLog>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(ERROR, !result) << "Receive false from help.saveAppLog";
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+void get_invite_text(Td *td, Promise<string> &&promise) {
+ td->create_handler<GetInviteTextQuery>(std::move(promise))->send();
+}
+
+class SaveAppLogLogEvent {
+ public:
+ const telegram_api::inputAppEvent *input_app_event_in_ = nullptr;
+ telegram_api::object_ptr<telegram_api::inputAppEvent> input_app_event_out_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ input_app_event_in_->store(storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ auto buffer = parser.template fetch_string_raw<BufferSlice>(parser.get_left_len());
+ TlBufferParser buffer_parser{&buffer};
+ input_app_event_out_ = telegram_api::make_object<telegram_api::inputAppEvent>(buffer_parser);
+ }
+};
+
+static void save_app_log_impl(Td *td, telegram_api::object_ptr<telegram_api::inputAppEvent> input_app_event,
+ uint64 log_event_id, Promise<Unit> &&promise) {
+ if (log_event_id == 0) {
+ SaveAppLogLogEvent log_event;
+ log_event.input_app_event_in_ = input_app_event.get();
+ log_event_id =
+ binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SaveAppLog, get_log_event_storer(log_event));
+ }
+
+ td->create_handler<SaveAppLogQuery>(get_erase_log_event_promise(log_event_id, std::move(promise)))
+ ->send(std::move(input_app_event));
+}
+
+void save_app_log(Td *td, const string &type, DialogId dialog_id, tl_object_ptr<telegram_api::JSONValue> &&data,
+ Promise<Unit> &&promise) {
+ CHECK(data != nullptr);
+ auto input_app_event = telegram_api::make_object<telegram_api::inputAppEvent>(G()->server_time_cached(), type,
+ dialog_id.get(), std::move(data));
+ save_app_log_impl(td, std::move(input_app_event), 0, std::move(promise));
+}
+
+void on_save_app_log_binlog_event(Td *td, BinlogEvent &&event) {
+ if (G()->close_flag()) {
+ return;
+ }
+ CHECK(event.id_ != 0);
+ CHECK(event.type_ == LogEvent::HandlerType::SaveAppLog);
+ SaveAppLogLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ save_app_log_impl(td, std::move(log_event.input_app_event_out_), event.id_, Promise<Unit>());
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Application.h b/protocols/Telegram/tdlib/td/td/telegram/Application.h
new file mode 100644
index 0000000000..e6d7926891
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Application.h
@@ -0,0 +1,28 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+
+namespace td {
+
+struct BinlogEvent;
+
+class Td;
+
+void get_invite_text(Td *td, Promise<string> &&promise);
+
+void save_app_log(Td *td, const string &type, DialogId dialog_id, tl_object_ptr<telegram_api::JSONValue> &&data,
+ Promise<Unit> &&promise);
+
+void on_save_app_log_binlog_event(Td *td, BinlogEvent &&event);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AttachMenuManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/AttachMenuManager.cpp
new file mode 100644
index 0000000000..840e47e6d3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/AttachMenuManager.cpp
@@ -0,0 +1,1099 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/AttachMenuManager.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Dependencies.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileId.hpp"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/StateManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/telegram/ThemeManager.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class RequestWebViewQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::webAppInfo>> promise_;
+ DialogId dialog_id_;
+ UserId bot_user_id_;
+ MessageId top_thread_message_id_;
+ MessageId reply_to_message_id_;
+ DialogId as_dialog_id_;
+ bool from_attach_menu_ = false;
+
+ public:
+ explicit RequestWebViewQuery(Promise<td_api::object_ptr<td_api::webAppInfo>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, UserId bot_user_id, tl_object_ptr<telegram_api::InputUser> &&input_user, string &&url,
+ td_api::object_ptr<td_api::themeParameters> &&theme, string &&platform, MessageId top_thread_message_id,
+ MessageId reply_to_message_id, bool silent, DialogId as_dialog_id) {
+ dialog_id_ = dialog_id;
+ bot_user_id_ = bot_user_id;
+ reply_to_message_id_ = reply_to_message_id;
+ as_dialog_id_ = as_dialog_id;
+
+ int32 flags = 0;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ CHECK(input_peer != nullptr);
+
+ string start_parameter;
+ if (begins_with(url, "start://")) {
+ start_parameter = url.substr(8);
+ url = string();
+
+ flags |= telegram_api::messages_requestWebView::START_PARAM_MASK;
+ } else if (begins_with(url, "menu://")) {
+ url = url.substr(7);
+
+ flags |= telegram_api::messages_requestWebView::FROM_BOT_MENU_MASK;
+ flags |= telegram_api::messages_requestWebView::URL_MASK;
+ } else if (!url.empty()) {
+ flags |= telegram_api::messages_requestWebView::URL_MASK;
+ } else {
+ from_attach_menu_ = true;
+ }
+
+ tl_object_ptr<telegram_api::dataJSON> theme_parameters;
+ if (theme != nullptr) {
+ theme_parameters = make_tl_object<telegram_api::dataJSON>(string());
+ theme_parameters->data_ = ThemeManager::get_theme_parameters_json_string(theme, false);
+
+ flags |= telegram_api::messages_requestWebView::THEME_PARAMS_MASK;
+ }
+
+ if (top_thread_message_id.is_valid()) {
+ flags |= telegram_api::messages_requestWebView::TOP_MSG_ID_MASK;
+ }
+
+ if (reply_to_message_id.is_valid()) {
+ flags |= telegram_api::messages_requestWebView::REPLY_TO_MSG_ID_MASK;
+ }
+
+ if (silent) {
+ flags |= telegram_api::messages_requestWebView::SILENT_MASK;
+ }
+
+ tl_object_ptr<telegram_api::InputPeer> as_input_peer;
+ if (as_dialog_id.is_valid()) {
+ as_input_peer = td_->messages_manager_->get_input_peer(as_dialog_id, AccessRights::Write);
+ if (as_input_peer != nullptr) {
+ flags |= telegram_api::messages_requestWebView::SEND_AS_MASK;
+ }
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_requestWebView(
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), std::move(input_user), url, start_parameter,
+ std::move(theme_parameters), platform, reply_to_message_id.get_server_message_id().get(),
+ top_thread_message_id.get_server_message_id().get(), std::move(as_input_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_requestWebView>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ td_->attach_menu_manager_->open_web_view(ptr->query_id_, dialog_id_, bot_user_id_, top_thread_message_id_,
+ reply_to_message_id_, as_dialog_id_);
+ promise_.set_value(td_api::make_object<td_api::webAppInfo>(ptr->query_id_, ptr->url_));
+ }
+
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "RequestWebViewQuery")) {
+ if (from_attach_menu_) {
+ td_->attach_menu_manager_->reload_attach_menu_bots(Promise<Unit>());
+ }
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ProlongWebViewQuery final : public Td::ResultHandler {
+ DialogId dialog_id_;
+
+ public:
+ void send(DialogId dialog_id, UserId bot_user_id, int64 query_id, MessageId top_thread_message_id,
+ MessageId reply_to_message_id, bool silent, DialogId as_dialog_id) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ auto r_input_user = td_->contacts_manager_->get_input_user(bot_user_id);
+ if (input_peer == nullptr || r_input_user.is_error()) {
+ return;
+ }
+
+ int32 flags = 0;
+ if (reply_to_message_id.is_valid()) {
+ flags |= telegram_api::messages_prolongWebView::REPLY_TO_MSG_ID_MASK;
+ }
+
+ if (top_thread_message_id.is_valid()) {
+ flags |= telegram_api::messages_prolongWebView::TOP_MSG_ID_MASK;
+ }
+
+ if (silent) {
+ flags |= telegram_api::messages_prolongWebView::SILENT_MASK;
+ }
+
+ tl_object_ptr<telegram_api::InputPeer> as_input_peer;
+ if (as_dialog_id.is_valid()) {
+ as_input_peer = td_->messages_manager_->get_input_peer(as_dialog_id, AccessRights::Write);
+ if (as_input_peer != nullptr) {
+ flags |= telegram_api::messages_prolongWebView::SEND_AS_MASK;
+ }
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_prolongWebView(
+ flags, false /*ignored*/, std::move(input_peer), r_input_user.move_as_ok(), query_id,
+ reply_to_message_id.get_server_message_id().get(), top_thread_message_id.get_server_message_id().get(),
+ std::move(as_input_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_prolongWebView>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool ptr = result_ptr.ok();
+ if (!ptr) {
+ LOG(ERROR) << "Failed to prolong a web view";
+ }
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ProlongWebViewQuery");
+ }
+};
+
+class GetAttachMenuBotsQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::AttachMenuBots>> promise_;
+
+ public:
+ explicit GetAttachMenuBotsQuery(Promise<telegram_api::object_ptr<telegram_api::AttachMenuBots>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(int64 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getAttachMenuBots(hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getAttachMenuBots>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetAttachMenuBotsQuery: " << to_string(ptr);
+
+ promise_.set_value(std::move(ptr));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetAttachMenuBotQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::attachMenuBotsBot>> promise_;
+
+ public:
+ explicit GetAttachMenuBotQuery(Promise<telegram_api::object_ptr<telegram_api::attachMenuBotsBot>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(tl_object_ptr<telegram_api::InputUser> &&input_user) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getAttachMenuBot(std::move(input_user))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getAttachMenuBot>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetAttachMenuBotQuery: " << to_string(ptr);
+ promise_.set_value(std::move(ptr));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleBotInAttachMenuQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ToggleBotInAttachMenuQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(tl_object_ptr<telegram_api::InputUser> &&input_user, bool is_added) {
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_toggleBotInAttachMenu(std::move(input_user), is_added)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_toggleBotInAttachMenu>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ if (!result) {
+ LOG(ERROR) << "Failed to add a bot to attachment menu";
+ }
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ td_->attach_menu_manager_->reload_attach_menu_bots(Promise<Unit>());
+ promise_.set_error(std::move(status));
+ }
+};
+
+bool operator==(const AttachMenuManager::AttachMenuBotColor &lhs, const AttachMenuManager::AttachMenuBotColor &rhs) {
+ return lhs.light_color_ == rhs.light_color_ && lhs.dark_color_ == rhs.dark_color_;
+}
+
+bool operator!=(const AttachMenuManager::AttachMenuBotColor &lhs, const AttachMenuManager::AttachMenuBotColor &rhs) {
+ return !(lhs == rhs);
+}
+
+template <class StorerT>
+void AttachMenuManager::AttachMenuBotColor::store(StorerT &storer) const {
+ td::store(light_color_, storer);
+ td::store(dark_color_, storer);
+}
+
+template <class ParserT>
+void AttachMenuManager::AttachMenuBotColor::parse(ParserT &parser) {
+ td::parse(light_color_, parser);
+ td::parse(dark_color_, parser);
+}
+
+bool operator==(const AttachMenuManager::AttachMenuBot &lhs, const AttachMenuManager::AttachMenuBot &rhs) {
+ return lhs.user_id_ == rhs.user_id_ && lhs.supports_self_dialog_ == rhs.supports_self_dialog_ &&
+ lhs.supports_user_dialogs_ == rhs.supports_user_dialogs_ &&
+ lhs.supports_bot_dialogs_ == rhs.supports_bot_dialogs_ &&
+ lhs.supports_group_dialogs_ == rhs.supports_group_dialogs_ &&
+ lhs.supports_broadcast_dialogs_ == rhs.supports_broadcast_dialogs_ &&
+ lhs.supports_settings_ == rhs.supports_settings_ && lhs.name_ == rhs.name_ &&
+ lhs.default_icon_file_id_ == rhs.default_icon_file_id_ &&
+ lhs.ios_static_icon_file_id_ == rhs.ios_static_icon_file_id_ &&
+ lhs.ios_animated_icon_file_id_ == rhs.ios_animated_icon_file_id_ &&
+ lhs.android_icon_file_id_ == rhs.android_icon_file_id_ && lhs.macos_icon_file_id_ == rhs.macos_icon_file_id_ &&
+ lhs.is_added_ == rhs.is_added_ && lhs.name_color_ == rhs.name_color_ && lhs.icon_color_ == rhs.icon_color_ &&
+ lhs.placeholder_file_id_ == rhs.placeholder_file_id_;
+}
+
+bool operator!=(const AttachMenuManager::AttachMenuBot &lhs, const AttachMenuManager::AttachMenuBot &rhs) {
+ return !(lhs == rhs);
+}
+
+template <class StorerT>
+void AttachMenuManager::AttachMenuBot::store(StorerT &storer) const {
+ bool has_ios_static_icon_file_id = ios_static_icon_file_id_.is_valid();
+ bool has_ios_animated_icon_file_id = ios_animated_icon_file_id_.is_valid();
+ bool has_android_icon_file_id = android_icon_file_id_.is_valid();
+ bool has_macos_icon_file_id = macos_icon_file_id_.is_valid();
+ bool has_name_color = name_color_ != AttachMenuBotColor();
+ bool has_icon_color = icon_color_ != AttachMenuBotColor();
+ bool has_support_flags = true;
+ bool has_placeholder_file_id = placeholder_file_id_.is_valid();
+ bool has_cache_version = cache_version_ != 0;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_ios_static_icon_file_id);
+ STORE_FLAG(has_ios_animated_icon_file_id);
+ STORE_FLAG(has_android_icon_file_id);
+ STORE_FLAG(has_macos_icon_file_id);
+ STORE_FLAG(is_added_);
+ STORE_FLAG(has_name_color);
+ STORE_FLAG(has_icon_color);
+ STORE_FLAG(has_support_flags);
+ STORE_FLAG(supports_self_dialog_);
+ STORE_FLAG(supports_user_dialogs_);
+ STORE_FLAG(supports_bot_dialogs_);
+ STORE_FLAG(supports_group_dialogs_);
+ STORE_FLAG(supports_broadcast_dialogs_);
+ STORE_FLAG(supports_settings_);
+ STORE_FLAG(has_placeholder_file_id);
+ STORE_FLAG(has_cache_version);
+ END_STORE_FLAGS();
+ td::store(user_id_, storer);
+ td::store(name_, storer);
+ td::store(default_icon_file_id_, storer);
+ if (has_ios_static_icon_file_id) {
+ td::store(ios_static_icon_file_id_, storer);
+ }
+ if (has_ios_animated_icon_file_id) {
+ td::store(ios_animated_icon_file_id_, storer);
+ }
+ if (has_android_icon_file_id) {
+ td::store(android_icon_file_id_, storer);
+ }
+ if (has_macos_icon_file_id) {
+ td::store(macos_icon_file_id_, storer);
+ }
+ if (has_name_color) {
+ td::store(name_color_, storer);
+ }
+ if (has_icon_color) {
+ td::store(icon_color_, storer);
+ }
+ if (has_placeholder_file_id) {
+ td::store(placeholder_file_id_, storer);
+ }
+ if (has_cache_version) {
+ td::store(cache_version_, storer);
+ }
+}
+
+template <class ParserT>
+void AttachMenuManager::AttachMenuBot::parse(ParserT &parser) {
+ bool has_ios_static_icon_file_id;
+ bool has_ios_animated_icon_file_id;
+ bool has_android_icon_file_id;
+ bool has_macos_icon_file_id;
+ bool has_name_color;
+ bool has_icon_color;
+ bool has_support_flags;
+ bool has_placeholder_file_id;
+ bool has_cache_version;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_ios_static_icon_file_id);
+ PARSE_FLAG(has_ios_animated_icon_file_id);
+ PARSE_FLAG(has_android_icon_file_id);
+ PARSE_FLAG(has_macos_icon_file_id);
+ PARSE_FLAG(is_added_);
+ PARSE_FLAG(has_name_color);
+ PARSE_FLAG(has_icon_color);
+ PARSE_FLAG(has_support_flags);
+ PARSE_FLAG(supports_self_dialog_);
+ PARSE_FLAG(supports_user_dialogs_);
+ PARSE_FLAG(supports_bot_dialogs_);
+ PARSE_FLAG(supports_group_dialogs_);
+ PARSE_FLAG(supports_broadcast_dialogs_);
+ PARSE_FLAG(supports_settings_);
+ PARSE_FLAG(has_placeholder_file_id);
+ PARSE_FLAG(has_cache_version);
+ END_PARSE_FLAGS();
+ td::parse(user_id_, parser);
+ td::parse(name_, parser);
+ td::parse(default_icon_file_id_, parser);
+ if (has_ios_static_icon_file_id) {
+ td::parse(ios_static_icon_file_id_, parser);
+ }
+ if (has_ios_animated_icon_file_id) {
+ td::parse(ios_animated_icon_file_id_, parser);
+ }
+ if (has_android_icon_file_id) {
+ td::parse(android_icon_file_id_, parser);
+ }
+ if (has_macos_icon_file_id) {
+ td::parse(macos_icon_file_id_, parser);
+ }
+ if (has_name_color) {
+ td::parse(name_color_, parser);
+ }
+ if (has_icon_color) {
+ td::parse(icon_color_, parser);
+ }
+ if (has_placeholder_file_id) {
+ td::parse(placeholder_file_id_, parser);
+ }
+ if (has_cache_version) {
+ td::parse(cache_version_, parser);
+ }
+
+ if (!has_support_flags) {
+ supports_self_dialog_ = true;
+ supports_user_dialogs_ = true;
+ supports_bot_dialogs_ = true;
+ }
+}
+
+class AttachMenuManager::AttachMenuBotsLogEvent {
+ public:
+ int64 hash_ = 0;
+ vector<AttachMenuBot> attach_menu_bots_;
+
+ AttachMenuBotsLogEvent() = default;
+
+ AttachMenuBotsLogEvent(int64 hash, vector<AttachMenuBot> attach_menu_bots)
+ : hash_(hash), attach_menu_bots_(std::move(attach_menu_bots)) {
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(hash_, storer);
+ td::store(attach_menu_bots_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(hash_, parser);
+ td::parse(attach_menu_bots_, parser);
+ }
+};
+
+AttachMenuManager::AttachMenuManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+}
+
+void AttachMenuManager::tear_down() {
+ parent_.reset();
+}
+
+void AttachMenuManager::start_up() {
+ init();
+}
+
+void AttachMenuManager::init() {
+ if (!is_active()) {
+ return;
+ }
+ if (is_inited_) {
+ return;
+ }
+ is_inited_ = true;
+
+ if (!G()->parameters().use_chat_info_db) {
+ G()->td_db()->get_binlog_pmc()->erase(get_attach_menu_bots_database_key());
+ } else {
+ auto attach_menu_bots_string = G()->td_db()->get_binlog_pmc()->get(get_attach_menu_bots_database_key());
+
+ if (!attach_menu_bots_string.empty()) {
+ AttachMenuBotsLogEvent attach_menu_bots_log_event;
+ bool is_valid = true;
+ is_valid &= log_event_parse(attach_menu_bots_log_event, attach_menu_bots_string).is_ok();
+
+ Dependencies dependencies;
+ for (auto &attach_menu_bot : attach_menu_bots_log_event.attach_menu_bots_) {
+ if (!attach_menu_bot.user_id_.is_valid() || !attach_menu_bot.default_icon_file_id_.is_valid()) {
+ is_valid = false;
+ }
+ if (!is_valid) {
+ break;
+ }
+ dependencies.add(attach_menu_bot.user_id_);
+ }
+ if (is_valid && dependencies.resolve_force(td_, "AttachMenuBotsLogEvent")) {
+ bool is_cache_outdated = false;
+ for (auto &bot : attach_menu_bots_log_event.attach_menu_bots_) {
+ if (bot.cache_version_ != AttachMenuBot::CACHE_VERSION) {
+ is_cache_outdated = true;
+ }
+ }
+ hash_ = is_cache_outdated ? 0 : attach_menu_bots_log_event.hash_;
+ attach_menu_bots_ = std::move(attach_menu_bots_log_event.attach_menu_bots_);
+
+ for (auto attach_menu_bot : attach_menu_bots_) {
+ auto file_source_id = get_attach_menu_bot_file_source_id(attach_menu_bot.user_id_);
+ auto register_file_source = [&](FileId file_id) {
+ if (file_id.is_valid()) {
+ td_->file_manager_->add_file_source(file_id, file_source_id);
+ }
+ };
+ register_file_source(attach_menu_bot.default_icon_file_id_);
+ register_file_source(attach_menu_bot.ios_static_icon_file_id_);
+ register_file_source(attach_menu_bot.ios_animated_icon_file_id_);
+ register_file_source(attach_menu_bot.android_icon_file_id_);
+ register_file_source(attach_menu_bot.macos_icon_file_id_);
+ register_file_source(attach_menu_bot.placeholder_file_id_);
+ }
+ } else {
+ LOG(ERROR) << "Ignore invalid attachment menu bots log event";
+ }
+ }
+ }
+
+ class StateCallback final : public StateManager::Callback {
+ public:
+ explicit StateCallback(ActorId<AttachMenuManager> parent) : parent_(std::move(parent)) {
+ }
+ bool on_online(bool is_online) final {
+ if (is_online) {
+ send_closure(parent_, &AttachMenuManager::on_online, is_online);
+ }
+ return parent_.is_alive();
+ }
+
+ private:
+ ActorId<AttachMenuManager> parent_;
+ };
+ send_closure(G()->state_manager(), &StateManager::add_callback, make_unique<StateCallback>(actor_id(this)));
+
+ send_update_attach_menu_bots();
+ reload_attach_menu_bots(Promise<Unit>());
+}
+
+void AttachMenuManager::timeout_expired() {
+ if (!is_active()) {
+ return;
+ }
+
+ reload_attach_menu_bots(Promise<Unit>());
+}
+
+bool AttachMenuManager::is_active() const {
+ return !G()->close_flag() && td_->auth_manager_->is_authorized() && !td_->auth_manager_->is_bot();
+}
+
+void AttachMenuManager::on_online(bool is_online) {
+ if (is_online) {
+ ping_web_view();
+ } else {
+ ping_web_view_timeout_.cancel_timeout();
+ }
+}
+
+void AttachMenuManager::ping_web_view_static(void *td_void) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(td_void != nullptr);
+ auto td = static_cast<Td *>(td_void);
+
+ td->attach_menu_manager_->ping_web_view();
+}
+
+void AttachMenuManager::ping_web_view() {
+ if (G()->close_flag() || opened_web_views_.empty()) {
+ return;
+ }
+
+ for (const auto &it : opened_web_views_) {
+ const auto &opened_web_view = it.second;
+ bool silent = td_->messages_manager_->get_dialog_silent_send_message(opened_web_view.dialog_id_);
+ td_->create_handler<ProlongWebViewQuery>()->send(
+ opened_web_view.dialog_id_, opened_web_view.bot_user_id_, it.first, opened_web_view.top_thread_message_id_,
+ opened_web_view.reply_to_message_id_, silent, opened_web_view.as_dialog_id_);
+ }
+
+ schedule_ping_web_view();
+}
+
+void AttachMenuManager::schedule_ping_web_view() {
+ ping_web_view_timeout_.set_callback(ping_web_view_static);
+ ping_web_view_timeout_.set_callback_data(static_cast<void *>(td_));
+ ping_web_view_timeout_.set_timeout_in(PING_WEB_VIEW_TIMEOUT);
+}
+
+void AttachMenuManager::request_web_view(DialogId dialog_id, UserId bot_user_id, MessageId top_thread_message_id,
+ MessageId reply_to_message_id, string &&url,
+ td_api::object_ptr<td_api::themeParameters> &&theme, string &&platform,
+ Promise<td_api::object_ptr<td_api::webAppInfo>> &&promise) {
+ TRY_STATUS_PROMISE(promise, td_->contacts_manager_->get_bot_data(bot_user_id));
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id));
+
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "request_web_view")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::Chat:
+ case DialogType::Channel:
+ // ok
+ break;
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Web Apps can't be opened in secret chats"));
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Write)) {
+ return promise.set_error(Status::Error(400, "Have no write access to the chat"));
+ }
+
+ if (!reply_to_message_id.is_valid() || !reply_to_message_id.is_server() ||
+ !td_->messages_manager_->have_message_force({dialog_id, reply_to_message_id}, "request_web_view")) {
+ reply_to_message_id = MessageId();
+ }
+ if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server() ||
+ dialog_id.get_type() != DialogType::Channel ||
+ !td_->contacts_manager_->is_megagroup_channel(dialog_id.get_channel_id())) {
+ top_thread_message_id = MessageId();
+ }
+
+ bool silent = td_->messages_manager_->get_dialog_silent_send_message(dialog_id);
+ DialogId as_dialog_id = td_->messages_manager_->get_dialog_default_send_message_as_dialog_id(dialog_id);
+
+ td_->create_handler<RequestWebViewQuery>(std::move(promise))
+ ->send(dialog_id, bot_user_id, std::move(input_user), std::move(url), std::move(theme), std::move(platform),
+ top_thread_message_id, reply_to_message_id, silent, as_dialog_id);
+}
+
+void AttachMenuManager::open_web_view(int64 query_id, DialogId dialog_id, UserId bot_user_id,
+ MessageId top_thread_message_id, MessageId reply_to_message_id,
+ DialogId as_dialog_id) {
+ if (query_id == 0) {
+ LOG(ERROR) << "Receive Web App query identifier == 0";
+ return;
+ }
+
+ if (opened_web_views_.empty()) {
+ schedule_ping_web_view();
+ }
+ OpenedWebView opened_web_view;
+ opened_web_view.dialog_id_ = dialog_id;
+ opened_web_view.bot_user_id_ = bot_user_id;
+ opened_web_view.top_thread_message_id_ = top_thread_message_id;
+ opened_web_view.reply_to_message_id_ = reply_to_message_id;
+ opened_web_view.as_dialog_id_ = as_dialog_id;
+ opened_web_views_.emplace(query_id, std::move(opened_web_view));
+}
+
+void AttachMenuManager::close_web_view(int64 query_id, Promise<Unit> &&promise) {
+ opened_web_views_.erase(query_id);
+ if (opened_web_views_.empty()) {
+ ping_web_view_timeout_.cancel_timeout();
+ }
+ promise.set_value(Unit());
+}
+
+Result<AttachMenuManager::AttachMenuBot> AttachMenuManager::get_attach_menu_bot(
+ tl_object_ptr<telegram_api::attachMenuBot> &&bot) {
+ UserId user_id(bot->bot_id_);
+ if (!td_->contacts_manager_->have_user(user_id)) {
+ return Status::Error(PSLICE() << "Have no information about " << user_id);
+ }
+
+ auto file_source_id = get_attach_menu_bot_file_source_id(user_id);
+
+ AttachMenuBot attach_menu_bot;
+ attach_menu_bot.is_added_ = !bot->inactive_;
+ attach_menu_bot.user_id_ = user_id;
+ attach_menu_bot.name_ = std::move(bot->short_name_);
+ for (auto &icon : bot->icons_) {
+ Slice name = icon->name_;
+ int32 document_id = icon->icon_->get_id();
+ if (document_id == telegram_api::documentEmpty::ID) {
+ return Status::Error(PSLICE() << "Have no icon for " << user_id << " with name " << name);
+ }
+ CHECK(document_id == telegram_api::document::ID);
+
+ if (name != "default_static" && name != "ios_static" && name != "ios_animated" && name != "android_animated" &&
+ name != "macos_animated" && name != "placeholder_static") {
+ LOG(ERROR) << "Have icon for " << user_id << " with name " << name;
+ continue;
+ }
+
+ auto expected_document_type = ends_with(name, "_static") ? Document::Type::General : Document::Type::Sticker;
+ auto parsed_document =
+ td_->documents_manager_->on_get_document(move_tl_object_as<telegram_api::document>(icon->icon_), DialogId());
+ if (parsed_document.type != expected_document_type) {
+ LOG(ERROR) << "Receive wrong attachment menu bot icon \"" << name << "\" for " << user_id;
+ continue;
+ }
+ bool expect_colors = false;
+ switch (name[5]) {
+ case 'l':
+ attach_menu_bot.default_icon_file_id_ = parsed_document.file_id;
+ break;
+ case 't':
+ attach_menu_bot.ios_static_icon_file_id_ = parsed_document.file_id;
+ break;
+ case 'n':
+ attach_menu_bot.ios_animated_icon_file_id_ = parsed_document.file_id;
+ break;
+ case 'i':
+ attach_menu_bot.android_icon_file_id_ = parsed_document.file_id;
+ expect_colors = true;
+ break;
+ case '_':
+ attach_menu_bot.macos_icon_file_id_ = parsed_document.file_id;
+ break;
+ case 'h':
+ attach_menu_bot.placeholder_file_id_ = parsed_document.file_id;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ td_->file_manager_->add_file_source(parsed_document.file_id, file_source_id);
+ if (expect_colors) {
+ if (icon->colors_.empty()) {
+ LOG(ERROR) << "Have no colors for attachment menu bot icon for " << user_id;
+ } else {
+ for (auto &color : icon->colors_) {
+ if (color->name_ != "light_icon" && color->name_ != "light_text" && color->name_ != "dark_icon" &&
+ color->name_ != "dark_text") {
+ LOG(ERROR) << "Receive unexpected attachment menu color " << color->name_ << " for " << user_id;
+ continue;
+ }
+ auto alpha = (color->color_ >> 24) & 0xFF;
+ if (alpha != 0 && alpha != 0xFF) {
+ LOG(ERROR) << "Receive alpha in attachment menu color " << color->name_ << " for " << user_id;
+ }
+ auto c = color->color_ & 0xFFFFFF;
+ switch (color->name_[6]) {
+ case 'i':
+ attach_menu_bot.icon_color_.light_color_ = c;
+ break;
+ case 't':
+ attach_menu_bot.name_color_.light_color_ = c;
+ break;
+ case 'c':
+ attach_menu_bot.icon_color_.dark_color_ = c;
+ break;
+ case 'e':
+ attach_menu_bot.name_color_.dark_color_ = c;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+ if (attach_menu_bot.icon_color_.light_color_ == -1 || attach_menu_bot.icon_color_.dark_color_ == -1) {
+ LOG(ERROR) << "Receive wrong icon_color for " << user_id;
+ attach_menu_bot.icon_color_ = AttachMenuBotColor();
+ }
+ if (attach_menu_bot.name_color_.light_color_ == -1 || attach_menu_bot.name_color_.dark_color_ == -1) {
+ LOG(ERROR) << "Receive wrong name_color for " << user_id;
+ attach_menu_bot.name_color_ = AttachMenuBotColor();
+ }
+ }
+ } else {
+ if (!icon->colors_.empty()) {
+ LOG(ERROR) << "Have unexpected colors for attachment menu bot icon for " << user_id << " with name " << name;
+ }
+ }
+ }
+ for (auto &peer_type : bot->peer_types_) {
+ switch (peer_type->get_id()) {
+ case telegram_api::attachMenuPeerTypeSameBotPM::ID:
+ attach_menu_bot.supports_self_dialog_ = true;
+ break;
+ case telegram_api::attachMenuPeerTypeBotPM::ID:
+ attach_menu_bot.supports_bot_dialogs_ = true;
+ break;
+ case telegram_api::attachMenuPeerTypePM::ID:
+ attach_menu_bot.supports_user_dialogs_ = true;
+ break;
+ case telegram_api::attachMenuPeerTypeChat::ID:
+ attach_menu_bot.supports_group_dialogs_ = true;
+ break;
+ case telegram_api::attachMenuPeerTypeBroadcast::ID:
+ attach_menu_bot.supports_broadcast_dialogs_ = true;
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+ attach_menu_bot.supports_settings_ = bot->has_settings_;
+ if (!attach_menu_bot.default_icon_file_id_.is_valid()) {
+ return Status::Error(PSLICE() << "Have no default icon for " << user_id);
+ }
+ attach_menu_bot.cache_version_ = AttachMenuBot::CACHE_VERSION;
+
+ return std::move(attach_menu_bot);
+}
+
+void AttachMenuManager::reload_attach_menu_bots(Promise<Unit> &&promise) {
+ if (!is_active()) {
+ return;
+ }
+
+ reload_attach_menu_bots_queries_.push_back(std::move(promise));
+ if (reload_attach_menu_bots_queries_.size() == 1) {
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<telegram_api::object_ptr<telegram_api::AttachMenuBots>> &&result) {
+ send_closure(actor_id, &AttachMenuManager::on_reload_attach_menu_bots, std::move(result));
+ });
+ td_->create_handler<GetAttachMenuBotsQuery>(std::move(query_promise))->send(hash_);
+ }
+}
+
+void AttachMenuManager::on_reload_attach_menu_bots(
+ Result<telegram_api::object_ptr<telegram_api::AttachMenuBots>> &&result) {
+ if (!is_active()) {
+ return set_promises(reload_attach_menu_bots_queries_);
+ }
+ if (result.is_error()) {
+ set_timeout_in(Random::fast(60, 120));
+ return set_promises(reload_attach_menu_bots_queries_);
+ }
+
+ is_inited_ = true;
+
+ set_timeout_in(Random::fast(3600, 4800));
+
+ auto attach_menu_bots_ptr = result.move_as_ok();
+ auto constructor_id = attach_menu_bots_ptr->get_id();
+ if (constructor_id == telegram_api::attachMenuBotsNotModified::ID) {
+ return set_promises(reload_attach_menu_bots_queries_);
+ }
+ CHECK(constructor_id == telegram_api::attachMenuBots::ID);
+ auto attach_menu_bots = move_tl_object_as<telegram_api::attachMenuBots>(attach_menu_bots_ptr);
+
+ td_->contacts_manager_->on_get_users(std::move(attach_menu_bots->users_), "on_reload_attach_menu_bots");
+
+ auto new_hash = attach_menu_bots->hash_;
+ vector<AttachMenuBot> new_attach_menu_bots;
+
+ for (auto &bot : attach_menu_bots->bots_) {
+ auto r_attach_menu_bot = get_attach_menu_bot(std::move(bot));
+ if (r_attach_menu_bot.is_error()) {
+ LOG(ERROR) << r_attach_menu_bot.error().message();
+ new_hash = 0;
+ continue;
+ }
+ if (!r_attach_menu_bot.ok().is_added_) {
+ LOG(ERROR) << "Receive non-added attachment menu bot " << r_attach_menu_bot.ok().user_id_;
+ new_hash = 0;
+ continue;
+ }
+
+ new_attach_menu_bots.push_back(r_attach_menu_bot.move_as_ok());
+ }
+
+ bool need_update = new_attach_menu_bots != attach_menu_bots_;
+ if (need_update || hash_ != new_hash) {
+ hash_ = new_hash;
+ attach_menu_bots_ = std::move(new_attach_menu_bots);
+
+ if (need_update) {
+ send_update_attach_menu_bots();
+ }
+
+ save_attach_menu_bots();
+ }
+ set_promises(reload_attach_menu_bots_queries_);
+}
+
+void AttachMenuManager::remove_bot_from_attach_menu(UserId user_id) {
+ for (auto it = attach_menu_bots_.begin(); it != attach_menu_bots_.end(); ++it) {
+ if (it->user_id_ == user_id) {
+ hash_ = 0;
+ attach_menu_bots_.erase(it);
+
+ send_update_attach_menu_bots();
+ save_attach_menu_bots();
+ return;
+ }
+ }
+}
+
+void AttachMenuManager::get_attach_menu_bot(UserId user_id,
+ Promise<td_api::object_ptr<td_api::attachmentMenuBot>> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id));
+
+ TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(user_id));
+ if (!bot_data.can_be_added_to_attach_menu) {
+ return promise.set_error(Status::Error(400, "The bot can't be added to attachment menu"));
+ }
+
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), user_id, promise = std::move(promise)](
+ Result<telegram_api::object_ptr<telegram_api::attachMenuBotsBot>> &&result) mutable {
+ send_closure(actor_id, &AttachMenuManager::on_get_attach_menu_bot, user_id, std::move(result),
+ std::move(promise));
+ });
+ td_->create_handler<GetAttachMenuBotQuery>(std::move(query_promise))->send(std::move(input_user));
+}
+
+void AttachMenuManager::reload_attach_menu_bot(UserId user_id, Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id));
+
+ auto wrapped_promise = PromiseCreator::lambda(
+ [promise = std::move(promise)](Result<td_api::object_ptr<td_api::attachmentMenuBot>> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(Unit());
+ }
+ });
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), user_id, promise = std::move(wrapped_promise)](
+ Result<telegram_api::object_ptr<telegram_api::attachMenuBotsBot>> &&result) mutable {
+ send_closure(actor_id, &AttachMenuManager::on_get_attach_menu_bot, user_id, std::move(result),
+ std::move(promise));
+ });
+ td_->create_handler<GetAttachMenuBotQuery>(std::move(query_promise))->send(std::move(input_user));
+}
+
+void AttachMenuManager::on_get_attach_menu_bot(
+ UserId user_id, Result<telegram_api::object_ptr<telegram_api::attachMenuBotsBot>> &&result,
+ Promise<td_api::object_ptr<td_api::attachmentMenuBot>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, bot, std::move(result));
+
+ td_->contacts_manager_->on_get_users(std::move(bot->users_), "on_get_attach_menu_bot");
+
+ auto r_attach_menu_bot = get_attach_menu_bot(std::move(bot->bot_));
+ if (r_attach_menu_bot.is_error()) {
+ LOG(ERROR) << r_attach_menu_bot.error().message();
+ return promise.set_error(Status::Error(500, "Receive invalid response"));
+ }
+ auto attach_menu_bot = r_attach_menu_bot.move_as_ok();
+ if (attach_menu_bot.user_id_ != user_id) {
+ return promise.set_error(Status::Error(500, "Receive wrong bot"));
+ }
+ if (attach_menu_bot.is_added_) {
+ bool is_found = false;
+ for (auto &old_bot : attach_menu_bots_) {
+ if (old_bot.user_id_ == user_id) {
+ is_found = true;
+ break;
+ }
+ }
+ if (!is_found) {
+ LOG(INFO) << "Add missing attachment menu bot " << user_id;
+ }
+ hash_ = 0;
+ attach_menu_bots_.insert(attach_menu_bots_.begin(), attach_menu_bot);
+
+ send_update_attach_menu_bots();
+ save_attach_menu_bots();
+ } else {
+ remove_bot_from_attach_menu(user_id);
+ }
+ promise.set_value(get_attachment_menu_bot_object(attach_menu_bot));
+}
+
+FileSourceId AttachMenuManager::get_attach_menu_bot_file_source_id(UserId user_id) {
+ if (!user_id.is_valid() || !is_active()) {
+ return FileSourceId();
+ }
+
+ auto &source_id = attach_menu_bot_file_source_ids_[user_id];
+ if (!source_id.is_valid()) {
+ source_id = td_->file_reference_manager_->create_attach_menu_bot_file_source(user_id);
+ }
+ VLOG(file_references) << "Return " << source_id << " for attach menu bot " << user_id;
+ return source_id;
+}
+
+void AttachMenuManager::toggle_bot_is_added_to_attach_menu(UserId user_id, bool is_added, Promise<Unit> &&promise) {
+ CHECK(is_active());
+
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id));
+
+ bool is_found = false;
+ for (auto &bot : attach_menu_bots_) {
+ if (bot.user_id_ == user_id) {
+ is_found = true;
+ break;
+ }
+ }
+ if (is_added == is_found) {
+ return promise.set_value(Unit());
+ }
+
+ if (is_added) {
+ TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(user_id));
+ if (!bot_data.can_be_added_to_attach_menu) {
+ return promise.set_error(Status::Error(400, "The bot can't be added to attachment menu"));
+ }
+ } else {
+ remove_bot_from_attach_menu(user_id);
+ }
+
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &AttachMenuManager::reload_attach_menu_bots, std::move(promise));
+ }
+ });
+
+ td_->create_handler<ToggleBotInAttachMenuQuery>(std::move(query_promise))->send(std::move(input_user), is_added);
+}
+
+td_api::object_ptr<td_api::attachmentMenuBot> AttachMenuManager::get_attachment_menu_bot_object(
+ const AttachMenuBot &bot) const {
+ auto get_file = [td = td_](FileId file_id) -> td_api::object_ptr<td_api::file> {
+ if (!file_id.is_valid()) {
+ return nullptr;
+ }
+ return td->file_manager_->get_file_object(file_id);
+ };
+ auto get_attach_menu_bot_color_object =
+ [](const AttachMenuBotColor &color) -> td_api::object_ptr<td_api::attachmentMenuBotColor> {
+ if (color == AttachMenuBotColor()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::attachmentMenuBotColor>(color.light_color_, color.dark_color_);
+ };
+
+ return td_api::make_object<td_api::attachmentMenuBot>(
+ td_->contacts_manager_->get_user_id_object(bot.user_id_, "get_attachment_menu_bot_object"),
+ bot.supports_self_dialog_, bot.supports_user_dialogs_, bot.supports_bot_dialogs_, bot.supports_group_dialogs_,
+ bot.supports_broadcast_dialogs_, bot.supports_settings_, bot.name_,
+ get_attach_menu_bot_color_object(bot.name_color_), get_file(bot.default_icon_file_id_),
+ get_file(bot.ios_static_icon_file_id_), get_file(bot.ios_animated_icon_file_id_),
+ get_file(bot.android_icon_file_id_), get_file(bot.macos_icon_file_id_),
+ get_attach_menu_bot_color_object(bot.icon_color_), get_file(bot.placeholder_file_id_));
+}
+
+td_api::object_ptr<td_api::updateAttachmentMenuBots> AttachMenuManager::get_update_attachment_menu_bots_object() const {
+ CHECK(is_active());
+ CHECK(is_inited_);
+ auto bots =
+ transform(attach_menu_bots_, [this](const AttachMenuBot &bot) { return get_attachment_menu_bot_object(bot); });
+ return td_api::make_object<td_api::updateAttachmentMenuBots>(std::move(bots));
+}
+
+void AttachMenuManager::send_update_attach_menu_bots() const {
+ send_closure(G()->td(), &Td::send_update, get_update_attachment_menu_bots_object());
+}
+
+string AttachMenuManager::get_attach_menu_bots_database_key() {
+ return "attach_bots";
+}
+
+void AttachMenuManager::save_attach_menu_bots() {
+ if (!G()->parameters().use_chat_info_db) {
+ return;
+ }
+
+ if (attach_menu_bots_.empty()) {
+ G()->td_db()->get_binlog_pmc()->erase(get_attach_menu_bots_database_key());
+ } else {
+ AttachMenuBotsLogEvent attach_menu_bots_log_event{hash_, attach_menu_bots_};
+ G()->td_db()->get_binlog_pmc()->set(get_attach_menu_bots_database_key(),
+ log_event_store(attach_menu_bots_log_event).as_slice().str());
+ }
+}
+
+void AttachMenuManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ if (!is_active()) {
+ return;
+ }
+
+ updates.push_back(get_update_attachment_menu_bots_object());
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AttachMenuManager.h b/protocols/Telegram/tdlib/td/td/telegram/AttachMenuManager.h
new file mode 100644
index 0000000000..197dfbae83
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/AttachMenuManager.h
@@ -0,0 +1,166 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/Timeout.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class Td;
+
+class AttachMenuManager final : public Actor {
+ public:
+ AttachMenuManager(Td *td, ActorShared<> parent);
+
+ void init();
+
+ void request_web_view(DialogId dialog_id, UserId bot_user_id, MessageId top_thread_message_id,
+ MessageId reply_to_message_id, string &&url,
+ td_api::object_ptr<td_api::themeParameters> &&theme, string &&platform,
+ Promise<td_api::object_ptr<td_api::webAppInfo>> &&promise);
+
+ void open_web_view(int64 query_id, DialogId dialog_id, UserId bot_user_id, MessageId top_thread_message_id,
+ MessageId reply_to_message_id, DialogId as_dialog_id);
+
+ void close_web_view(int64 query_id, Promise<Unit> &&promise);
+
+ void reload_attach_menu_bots(Promise<Unit> &&promise);
+
+ void get_attach_menu_bot(UserId user_id, Promise<td_api::object_ptr<td_api::attachmentMenuBot>> &&promise);
+
+ void reload_attach_menu_bot(UserId user_id, Promise<Unit> &&promise);
+
+ FileSourceId get_attach_menu_bot_file_source_id(UserId user_id);
+
+ void toggle_bot_is_added_to_attach_menu(UserId user_id, bool is_added, Promise<Unit> &&promise);
+
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
+ private:
+ static const int32 PING_WEB_VIEW_TIMEOUT = 60;
+
+ void start_up() final;
+
+ void timeout_expired() final;
+
+ void tear_down() final;
+
+ struct AttachMenuBotColor {
+ int32 light_color_ = -1;
+ int32 dark_color_ = -1;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ friend bool operator==(const AttachMenuBotColor &lhs, const AttachMenuBotColor &rhs);
+
+ friend bool operator!=(const AttachMenuBotColor &lhs, const AttachMenuBotColor &rhs);
+
+ struct AttachMenuBot {
+ bool is_added_ = false;
+ UserId user_id_;
+ bool supports_self_dialog_ = false;
+ bool supports_user_dialogs_ = false;
+ bool supports_bot_dialogs_ = false;
+ bool supports_group_dialogs_ = false;
+ bool supports_broadcast_dialogs_ = false;
+ bool supports_settings_ = false;
+ string name_;
+ AttachMenuBotColor name_color_;
+ FileId default_icon_file_id_;
+ FileId ios_static_icon_file_id_;
+ FileId ios_animated_icon_file_id_;
+ FileId android_icon_file_id_;
+ FileId macos_icon_file_id_;
+ AttachMenuBotColor icon_color_;
+ FileId placeholder_file_id_;
+
+ static constexpr uint32 CACHE_VERSION = 1;
+ uint32 cache_version_ = 0;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ class AttachMenuBotsLogEvent;
+
+ friend bool operator==(const AttachMenuBot &lhs, const AttachMenuBot &rhs);
+
+ friend bool operator!=(const AttachMenuBot &lhs, const AttachMenuBot &rhs);
+
+ bool is_active() const;
+
+ void on_online(bool is_online);
+
+ static void ping_web_view_static(void *td_void);
+
+ void ping_web_view();
+
+ void schedule_ping_web_view();
+
+ Result<AttachMenuBot> get_attach_menu_bot(tl_object_ptr<telegram_api::attachMenuBot> &&bot);
+
+ td_api::object_ptr<td_api::attachmentMenuBot> get_attachment_menu_bot_object(const AttachMenuBot &bot) const;
+
+ td_api::object_ptr<td_api::updateAttachmentMenuBots> get_update_attachment_menu_bots_object() const;
+
+ void remove_bot_from_attach_menu(UserId user_id);
+
+ void send_update_attach_menu_bots() const;
+
+ static string get_attach_menu_bots_database_key();
+
+ void save_attach_menu_bots();
+
+ void on_reload_attach_menu_bots(Result<telegram_api::object_ptr<telegram_api::AttachMenuBots>> &&result);
+
+ void on_get_attach_menu_bot(UserId user_id,
+ Result<telegram_api::object_ptr<telegram_api::attachMenuBotsBot>> &&result,
+ Promise<td_api::object_ptr<td_api::attachmentMenuBot>> &&promise);
+
+ Td *td_;
+ ActorShared<> parent_;
+
+ bool is_inited_ = false;
+ int64 hash_ = 0;
+ vector<AttachMenuBot> attach_menu_bots_;
+ FlatHashMap<UserId, FileSourceId, UserIdHash> attach_menu_bot_file_source_ids_;
+ vector<Promise<Unit>> reload_attach_menu_bots_queries_;
+
+ struct OpenedWebView {
+ DialogId dialog_id_;
+ UserId bot_user_id_;
+ MessageId top_thread_message_id_;
+ MessageId reply_to_message_id_;
+ DialogId as_dialog_id_;
+ };
+ FlatHashMap<int64, OpenedWebView> opened_web_views_;
+ Timeout ping_web_view_timeout_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.cpp
index 248ea17e7b..e1128d610c 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.cpp
@@ -1,24 +1,26 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/AudiosManager.h"
-#include "td/actor/PromiseFuture.h"
-
-#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/DialogId.h"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/secret_api.h"
#include "td/telegram/Td.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
+#include "td/actor/actor.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/PathView.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
namespace td {
@@ -26,27 +28,71 @@ namespace td {
AudiosManager::AudiosManager(Td *td) : td_(td) {
}
-int32 AudiosManager::get_audio_duration(FileId file_id) {
- auto &audio = audios_[file_id];
- CHECK(audio != nullptr);
+AudiosManager::~AudiosManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), audios_);
+}
+
+int32 AudiosManager::get_audio_duration(FileId file_id) const {
+ const auto *audio = get_audio(file_id);
+ if (audio == nullptr) {
+ return 0;
+ }
return audio->duration;
}
-tl_object_ptr<td_api::audio> AudiosManager::get_audio_object(FileId file_id) {
+tl_object_ptr<td_api::audio> AudiosManager::get_audio_object(FileId file_id) const {
if (!file_id.is_valid()) {
return nullptr;
}
- auto &audio = audios_[file_id];
+ auto audio = get_audio(file_id);
CHECK(audio != nullptr);
- audio->is_changed = false;
+
+ vector<td_api::object_ptr<td_api::thumbnail>> album_covers;
+ if (!td_->auth_manager_->is_bot()) {
+ auto add_album_cover = [&](bool is_small, int32 width, int32 height) {
+ auto r_file_id =
+ td_->file_manager_->get_audio_thumbnail_file_id(audio->title, audio->performer, is_small, DialogId());
+ if (r_file_id.is_ok()) {
+ album_covers.emplace_back(
+ td_api::make_object<td_api::thumbnail>(td_api::make_object<td_api::thumbnailFormatJpeg>(), width, height,
+ td_->file_manager_->get_file_object(r_file_id.move_as_ok())));
+ }
+ };
+
+ add_album_cover(true, 100, 100);
+ add_album_cover(false, 600, 600);
+ }
return make_tl_object<td_api::audio>(
audio->duration, audio->title, audio->performer, audio->file_name, audio->mime_type,
- get_photo_size_object(td_->file_manager_.get(), &audio->thumbnail), td_->file_manager_->get_file_object(file_id));
+ get_minithumbnail_object(audio->minithumbnail),
+ get_thumbnail_object(td_->file_manager_.get(), audio->thumbnail, PhotoFormat::Jpeg), std::move(album_covers),
+ td_->file_manager_->get_file_object(file_id));
+}
+
+td_api::object_ptr<td_api::notificationSound> AudiosManager::get_notification_sound_object(FileId file_id) const {
+ if (!file_id.is_valid()) {
+ return nullptr;
+ }
+
+ auto audio = get_audio(file_id);
+ CHECK(audio != nullptr);
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.empty());
+ CHECK(file_view.get_type() == FileType::Ringtone);
+ CHECK(file_view.has_remote_location());
+ auto document_id = file_view.remote_location().get_id();
+ auto title = audio->title;
+ if (title.empty() && !audio->file_name.empty()) {
+ title = PathView(audio->file_name).file_name_without_extension().str();
+ }
+ return td_api::make_object<td_api::notificationSound>(document_id, audio->duration, audio->date, title,
+ audio->performer, td_->file_manager_->get_file_object(file_id));
}
-FileId AudiosManager::on_get_audio(std::unique_ptr<Audio> new_audio, bool replace) {
+FileId AudiosManager::on_get_audio(unique_ptr<Audio> new_audio, bool replace) {
auto file_id = new_audio->file_id;
+ CHECK(file_id.is_valid());
LOG(INFO) << "Receive audio " << file_id;
auto &a = audios_[file_id];
if (a == nullptr) {
@@ -55,20 +101,23 @@ FileId AudiosManager::on_get_audio(std::unique_ptr<Audio> new_audio, bool replac
CHECK(a->file_id == new_audio->file_id);
if (a->mime_type != new_audio->mime_type) {
LOG(DEBUG) << "Audio " << file_id << " info has changed";
- a->mime_type = new_audio->mime_type;
- a->is_changed = true;
+ a->mime_type = std::move(new_audio->mime_type);
}
if (a->duration != new_audio->duration || a->title != new_audio->title || a->performer != new_audio->performer) {
LOG(DEBUG) << "Audio " << file_id << " info has changed";
a->duration = new_audio->duration;
- a->title = new_audio->title;
- a->performer = new_audio->performer;
- a->is_changed = true;
+ a->title = std::move(new_audio->title);
+ a->performer = std::move(new_audio->performer);
}
if (a->file_name != new_audio->file_name) {
LOG(DEBUG) << "Audio " << file_id << " file name has changed";
a->file_name = std::move(new_audio->file_name);
- a->is_changed = true;
+ }
+ if (a->date != new_audio->date) {
+ a->date = new_audio->date;
+ }
+ if (a->minithumbnail != new_audio->minithumbnail) {
+ a->minithumbnail = std::move(new_audio->minithumbnail);
}
if (a->thumbnail != new_audio->thumbnail) {
if (!a->thumbnail.file_id.is_valid()) {
@@ -77,8 +126,7 @@ FileId AudiosManager::on_get_audio(std::unique_ptr<Audio> new_audio, bool replac
LOG(INFO) << "Audio " << file_id << " thumbnail has changed from " << a->thumbnail << " to "
<< new_audio->thumbnail;
}
- a->thumbnail = new_audio->thumbnail;
- a->is_changed = true;
+ a->thumbnail = std::move(new_audio->thumbnail);
}
}
@@ -86,67 +134,41 @@ FileId AudiosManager::on_get_audio(std::unique_ptr<Audio> new_audio, bool replac
}
const AudiosManager::Audio *AudiosManager::get_audio(FileId file_id) const {
- auto audio = audios_.find(file_id);
- if (audio == audios_.end()) {
- return nullptr;
- }
-
- CHECK(audio->second->file_id == file_id);
- return audio->second.get();
+ return audios_.get_pointer(file_id);
}
FileId AudiosManager::dup_audio(FileId new_id, FileId old_id) {
const Audio *old_audio = get_audio(old_id);
CHECK(old_audio != nullptr);
auto &new_audio = audios_[new_id];
- CHECK(!new_audio);
- new_audio = std::make_unique<Audio>(*old_audio);
+ CHECK(new_audio == nullptr);
+ new_audio = make_unique<Audio>(*old_audio);
new_audio->file_id = new_id;
- new_audio->thumbnail.file_id = td_->file_manager_->dup_file_id(new_audio->thumbnail.file_id);
+ new_audio->thumbnail.file_id = td_->file_manager_->dup_file_id(new_audio->thumbnail.file_id, "dup_audio");
return new_id;
}
-bool AudiosManager::merge_audios(FileId new_id, FileId old_id, bool can_delete_old) {
- if (!old_id.is_valid()) {
- LOG(ERROR) << "Old file id is invalid";
- return true;
- }
+void AudiosManager::merge_audios(FileId new_id, FileId old_id) {
+ CHECK(old_id.is_valid() && new_id.is_valid());
+ CHECK(new_id != old_id);
LOG(INFO) << "Merge audios " << new_id << " and " << old_id;
const Audio *old_ = get_audio(old_id);
CHECK(old_ != nullptr);
- if (old_id == new_id) {
- return old_->is_changed;
- }
- auto new_it = audios_.find(new_id);
- if (new_it == audios_.end()) {
- auto &old = audios_[old_id];
- old->is_changed = true;
- if (!can_delete_old) {
- dup_audio(new_id, old_id);
- } else {
- old->file_id = new_id;
- audios_.emplace(new_id, std::move(old));
- }
+ const auto *new_ = get_audio(new_id);
+ if (new_ == nullptr) {
+ dup_audio(new_id, old_id);
} else {
- Audio *new_ = new_it->second.get();
- CHECK(new_ != nullptr);
-
if (!old_->mime_type.empty() && old_->mime_type != new_->mime_type) {
LOG(INFO) << "Audio has changed: mime_type = (" << old_->mime_type << ", " << new_->mime_type << ")";
}
- new_->is_changed = true;
if (old_->thumbnail != new_->thumbnail) {
// LOG_STATUS(td_->file_manager_->merge(new_->thumbnail.file_id, old_->thumbnail.file_id));
}
}
LOG_STATUS(td_->file_manager_->merge(new_id, old_id));
- if (can_delete_old) {
- audios_.erase(old_id);
- }
- return true;
}
string AudiosManager::get_audio_search_text(FileId file_id) const {
@@ -161,37 +183,61 @@ FileId AudiosManager::get_audio_thumbnail_file_id(FileId file_id) const {
return audio->thumbnail.file_id;
}
+void AudiosManager::append_audio_album_cover_file_ids(FileId file_id, vector<FileId> &file_ids) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ auto audio = get_audio(file_id);
+ CHECK(audio != nullptr);
+
+ auto append_album_cover = [&](bool is_small) {
+ auto r_file_id =
+ td_->file_manager_->get_audio_thumbnail_file_id(audio->title, audio->performer, is_small, DialogId());
+ if (r_file_id.is_ok()) {
+ file_ids.push_back(r_file_id.ok());
+ }
+ };
+
+ append_album_cover(true);
+ append_album_cover(false);
+}
+
void AudiosManager::delete_audio_thumbnail(FileId file_id) {
auto &audio = audios_[file_id];
CHECK(audio != nullptr);
audio->thumbnail = PhotoSize();
}
-void AudiosManager::create_audio(FileId file_id, PhotoSize thumbnail, string file_name, string mime_type,
- int32 duration, string title, string performer, bool replace) {
- auto a = std::make_unique<Audio>();
+void AudiosManager::create_audio(FileId file_id, string minithumbnail, PhotoSize thumbnail, string file_name,
+ string mime_type, int32 duration, string title, string performer, int32 date,
+ bool replace) {
+ auto a = make_unique<Audio>();
a->file_id = file_id;
a->file_name = std::move(file_name);
a->mime_type = std::move(mime_type);
a->duration = max(duration, 0);
a->title = std::move(title);
a->performer = std::move(performer);
+ a->date = date;
+ if (!td_->auth_manager_->is_bot()) {
+ a->minithumbnail = std::move(minithumbnail);
+ }
a->thumbnail = std::move(thumbnail);
on_get_audio(std::move(a), replace);
}
SecretInputMedia AudiosManager::get_secret_input_media(FileId audio_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption, BufferSlice thumbnail) const {
+ const string &caption, BufferSlice thumbnail,
+ int32 layer) const {
auto *audio = get_audio(audio_file_id);
CHECK(audio != nullptr);
auto file_view = td_->file_manager_->get_file_view(audio_file_id);
- auto &encryption_key = file_view.encryption_key();
- if (encryption_key.empty()) {
+ if (!file_view.is_encrypted_secret() || file_view.encryption_key().empty()) {
return SecretInputMedia{};
}
if (file_view.has_remote_location()) {
- input_file = file_view.remote_location().as_input_encrypted_file();
+ input_file = file_view.main_remote_location().as_input_encrypted_file();
}
if (!input_file) {
return SecretInputMedia{};
@@ -204,15 +250,17 @@ SecretInputMedia AudiosManager::get_secret_input_media(FileId audio_file_id,
attributes.push_back(make_tl_object<secret_api::documentAttributeFilename>(audio->file_name));
}
attributes.push_back(make_tl_object<secret_api::documentAttributeAudio>(
- secret_api::documentAttributeAudio::Flags::TITLE_MASK | secret_api::documentAttributeAudio::Flags::PERFORMER_MASK,
+ secret_api::documentAttributeAudio::TITLE_MASK | secret_api::documentAttributeAudio::PERFORMER_MASK,
false /*ignored*/, audio->duration, audio->title, audio->performer, BufferSlice()));
- return SecretInputMedia{
- std::move(input_file),
- make_tl_object<secret_api::decryptedMessageMediaDocument>(
- std::move(thumbnail), audio->thumbnail.dimensions.width, audio->thumbnail.dimensions.height, audio->mime_type,
- narrow_cast<int32>(file_view.size()), BufferSlice(encryption_key.key_slice()),
- BufferSlice(encryption_key.iv_slice()), std::move(attributes), caption)};
+ return {std::move(input_file),
+ std::move(thumbnail),
+ audio->thumbnail.dimensions,
+ audio->mime_type,
+ file_view,
+ std::move(attributes),
+ caption,
+ layer};
}
tl_object_ptr<telegram_api::InputMedia> AudiosManager::get_input_media(
@@ -222,13 +270,13 @@ tl_object_ptr<telegram_api::InputMedia> AudiosManager::get_input_media(
if (file_view.is_encrypted()) {
return nullptr;
}
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
- return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.remote_location().as_input_document(), 0);
+ if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
+ return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.main_remote_location().as_input_document(), 0,
+ string());
}
if (file_view.has_url()) {
return make_tl_object<telegram_api::inputMediaDocumentExternal>(0, file_view.url(), 0);
}
- CHECK(!file_view.has_remote_location());
if (input_file != nullptr) {
const Audio *audio = get_audio(file_id);
@@ -250,8 +298,10 @@ tl_object_ptr<telegram_api::InputMedia> AudiosManager::get_input_media(
flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK;
}
return make_tl_object<telegram_api::inputMediaUploadedDocument>(
- flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type, std::move(attributes),
- vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type,
+ std::move(attributes), vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ } else {
+ CHECK(!file_view.has_remote_location());
}
return nullptr;
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.h b/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.h
index 68624cf451..c6c3b44398 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.h
@@ -1,39 +1,42 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
#include "td/telegram/files/FileId.h"
-#include "td/telegram/Photo.h"
+#include "td/telegram/PhotoSize.h"
#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
-
-#include <unordered_map>
+#include "td/utils/WaitFreeHashMap.h"
namespace td {
-class Td;
-} // namespace td
-namespace td {
+class Td;
class AudiosManager {
public:
explicit AudiosManager(Td *td);
+ AudiosManager(const AudiosManager &) = delete;
+ AudiosManager &operator=(const AudiosManager &) = delete;
+ AudiosManager(AudiosManager &&) = delete;
+ AudiosManager &operator=(AudiosManager &&) = delete;
+ ~AudiosManager();
- int32 get_audio_duration(FileId file_id);
+ int32 get_audio_duration(FileId file_id) const;
- tl_object_ptr<td_api::audio> get_audio_object(FileId file_id);
+ tl_object_ptr<td_api::audio> get_audio_object(FileId file_id) const;
- void create_audio(FileId file_id, PhotoSize thumbnail, string file_name, string mime_type, int32 duration,
- string title, string performer, bool replace);
+ td_api::object_ptr<td_api::notificationSound> get_notification_sound_object(FileId file_id) const;
+
+ void create_audio(FileId file_id, string minithumbnail, PhotoSize thumbnail, string file_name, string mime_type,
+ int32 duration, string title, string performer, int32 date, bool replace);
tl_object_ptr<telegram_api::InputMedia> get_input_media(FileId file_id,
tl_object_ptr<telegram_api::InputFile> input_file,
@@ -41,21 +44,23 @@ class AudiosManager {
SecretInputMedia get_secret_input_media(FileId audio_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption, BufferSlice thumbnail) const;
+ const string &caption, BufferSlice thumbnail, int32 layer) const;
FileId get_audio_thumbnail_file_id(FileId file_id) const;
+ void append_audio_album_cover_file_ids(FileId file_id, vector<FileId> &file_ids) const;
+
void delete_audio_thumbnail(FileId file_id);
FileId dup_audio(FileId new_id, FileId old_id);
- bool merge_audios(FileId new_id, FileId old_id, bool can_delete_old);
+ void merge_audios(FileId new_id, FileId old_id);
- template <class T>
- void store_audio(FileId file_id, T &storer) const;
+ template <class StorerT>
+ void store_audio(FileId file_id, StorerT &storer) const;
- template <class T>
- FileId parse_audio(T &parser);
+ template <class ParserT>
+ FileId parse_audio(ParserT &parser);
string get_audio_search_text(FileId file_id) const;
@@ -65,21 +70,21 @@ class AudiosManager {
string file_name;
string mime_type;
int32 duration = 0;
+ int32 date = 0;
string title;
string performer;
+ string minithumbnail;
PhotoSize thumbnail;
FileId file_id;
-
- bool is_changed = true;
};
const Audio *get_audio(FileId file_id) const;
- FileId on_get_audio(std::unique_ptr<Audio> new_audio, bool replace);
+ FileId on_get_audio(unique_ptr<Audio> new_audio, bool replace);
Td *td_;
- std::unordered_map<FileId, unique_ptr<Audio>, FileIdHash> audios_;
+ WaitFreeHashMap<FileId, unique_ptr<Audio>, FileIdHash> audios_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.hpp
index d9813352ab..b28b42be27 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/AudiosManager.hpp
@@ -1,45 +1,132 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/telegram/AudiosManager.h"
#include "td/telegram/files/FileId.hpp"
-#include "td/telegram/Photo.hpp"
+#include "td/telegram/PhotoSize.hpp"
+#include "td/telegram/Version.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/tl_helpers.h"
namespace td {
-template <class T>
-void AudiosManager::store_audio(FileId file_id, T &storer) const {
- auto it = audios_.find(file_id);
- CHECK(it != audios_.end());
- const Audio *audio = it->second.get();
- store(audio->file_name, storer);
- store(audio->mime_type, storer);
- store(audio->duration, storer);
- store(audio->title, storer);
- store(audio->performer, storer);
- store(audio->thumbnail, storer);
+template <class StorerT>
+void AudiosManager::store_audio(FileId file_id, StorerT &storer) const {
+ const Audio *audio = get_audio(file_id);
+ CHECK(audio != nullptr);
+ bool has_file_name = !audio->file_name.empty();
+ bool has_mime_type = !audio->mime_type.empty();
+ bool has_duration = audio->duration != 0;
+ bool has_title = !audio->title.empty();
+ bool has_performer = !audio->performer.empty();
+ bool has_minithumbnail = !audio->minithumbnail.empty();
+ bool has_thumbnail = audio->thumbnail.file_id.is_valid();
+ bool has_date = audio->date != 0;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_file_name);
+ STORE_FLAG(has_mime_type);
+ STORE_FLAG(has_duration);
+ STORE_FLAG(has_title);
+ STORE_FLAG(has_performer);
+ STORE_FLAG(has_minithumbnail);
+ STORE_FLAG(has_thumbnail);
+ STORE_FLAG(has_date);
+ END_STORE_FLAGS();
+ if (has_file_name) {
+ store(audio->file_name, storer);
+ }
+ if (has_mime_type) {
+ store(audio->mime_type, storer);
+ }
+ if (has_duration) {
+ store(audio->duration, storer);
+ }
+ if (has_title) {
+ store(audio->title, storer);
+ }
+ if (has_performer) {
+ store(audio->performer, storer);
+ }
+ if (has_minithumbnail) {
+ store(audio->minithumbnail, storer);
+ }
+ if (has_thumbnail) {
+ store(audio->thumbnail, storer);
+ }
+ if (has_date) {
+ store(audio->date, storer);
+ }
store(file_id, storer);
}
-template <class T>
-FileId AudiosManager::parse_audio(T &parser) {
+template <class ParserT>
+FileId AudiosManager::parse_audio(ParserT &parser) {
auto audio = make_unique<Audio>();
- parse(audio->file_name, parser);
- parse(audio->mime_type, parser);
- parse(audio->duration, parser);
- parse(audio->title, parser);
- parse(audio->performer, parser);
- parse(audio->thumbnail, parser);
+ bool has_file_name;
+ bool has_mime_type;
+ bool has_duration;
+ bool has_title;
+ bool has_performer;
+ bool has_minithumbnail;
+ bool has_thumbnail;
+ bool has_date;
+ if (parser.version() >= static_cast<int32>(Version::AddAudioFlags)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_file_name);
+ PARSE_FLAG(has_mime_type);
+ PARSE_FLAG(has_duration);
+ PARSE_FLAG(has_title);
+ PARSE_FLAG(has_performer);
+ PARSE_FLAG(has_minithumbnail);
+ PARSE_FLAG(has_thumbnail);
+ PARSE_FLAG(has_date);
+ END_PARSE_FLAGS();
+ } else {
+ has_file_name = true;
+ has_mime_type = true;
+ has_duration = true;
+ has_title = true;
+ has_performer = true;
+ has_minithumbnail = parser.version() >= static_cast<int32>(Version::SupportMinithumbnails);
+ has_thumbnail = true;
+ has_date = false;
+ }
+ if (has_file_name) {
+ parse(audio->file_name, parser);
+ }
+ if (has_mime_type) {
+ parse(audio->mime_type, parser);
+ }
+ if (has_duration) {
+ parse(audio->duration, parser);
+ }
+ if (has_title) {
+ parse(audio->title, parser);
+ }
+ if (has_performer) {
+ parse(audio->performer, parser);
+ }
+ if (has_minithumbnail) {
+ parse(audio->minithumbnail, parser);
+ }
+ if (has_thumbnail) {
+ parse(audio->thumbnail, parser);
+ }
+ if (has_date) {
+ parse(audio->date, parser);
+ }
parse(audio->file_id, parser);
- return on_get_audio(std::move(audio), true);
+ if (parser.get_error() != nullptr || !audio->file_id.is_valid()) {
+ return FileId();
+ }
+ return on_get_audio(std::move(audio), false);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp
index 64915de36b..78e5e05937 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/AuthManager.cpp
@@ -1,313 +1,44 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/AuthManager.h"
-#include "td/telegram/AuthManager.hpp"
-
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
+#include "td/telegram/AttachMenuManager.h"
+#include "td/telegram/AuthManager.hpp"
#include "td/telegram/ConfigManager.h"
-#include "td/telegram/ConfigShared.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/MessagesManager.h"
#include "td/telegram/misc.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/NewPasswordState.h"
+#include "td/telegram/NotificationManager.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/PasswordManager.h"
+#include "td/telegram/StateManager.h"
+#include "td/telegram/StickersManager.h"
#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/ThemeManager.h"
+#include "td/telegram/TopDialogManager.h"
#include "td/telegram/UpdatesManager.h"
-#include "td/telegram/logevent/LogEvent.h"
-
-#include "td/actor/PromiseFuture.h"
-
-#include "td/utils/buffer.h"
-#include "td/utils/crypto.h"
+#include "td/utils/base64.h"
+#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Promise.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/Slice.h"
#include "td/utils/Time.h"
namespace td {
-// SendCodeHelper
-void SendCodeHelper::on_sent_code(telegram_api::object_ptr<telegram_api::auth_sentCode> sent_code) {
- phone_registered_ = (sent_code->flags_ & SENT_CODE_FLAG_IS_USER_REGISTERED) != 0;
- phone_code_hash_ = sent_code->phone_code_hash_;
- sent_code_info_ = get_authentication_code_info(std::move(sent_code->type_));
- next_code_info_ = get_authentication_code_info(std::move(sent_code->next_type_));
- next_code_timestamp_ = Timestamp::in((sent_code->flags_ & SENT_CODE_FLAG_HAS_TIMEOUT) != 0 ? sent_code->timeout_ : 0);
-}
-
-td_api::object_ptr<td_api::authorizationStateWaitCode> SendCodeHelper::get_authorization_state_wait_code() const {
- return make_tl_object<td_api::authorizationStateWaitCode>(phone_registered_, get_authentication_code_info_object());
-}
-
-td_api::object_ptr<td_api::authenticationCodeInfo> SendCodeHelper::get_authentication_code_info_object() const {
- return make_tl_object<td_api::authenticationCodeInfo>(
- phone_number_, get_authentication_code_type_object(sent_code_info_),
- get_authentication_code_type_object(next_code_info_),
- max(static_cast<int32>(next_code_timestamp_.in() + 1 - 1e-9), 0));
-}
-
-Result<telegram_api::auth_resendCode> SendCodeHelper::resend_code() {
- if (next_code_info_.type == AuthenticationCodeInfo::Type::None) {
- return Status::Error(8, "Authentication code can't be resend");
- }
- sent_code_info_ = next_code_info_;
- next_code_info_ = {};
- next_code_timestamp_ = {};
- return telegram_api::auth_resendCode(phone_number_, phone_code_hash_);
-}
-
-Result<telegram_api::auth_sendCode> SendCodeHelper::send_code(Slice phone_number, bool allow_flash_call,
- bool is_current_phone_number, int32 api_id,
- const string &api_hash) {
- if (!phone_number_.empty()) {
- return Status::Error(8, "Can't change phone");
- }
- phone_number_ = phone_number.str();
- int32 flags = 0;
- if (allow_flash_call) {
- flags |= AUTH_SEND_CODE_FLAG_ALLOW_FLASH_CALL;
- }
- return telegram_api::auth_sendCode(flags, false /*ignored*/, phone_number_, is_current_phone_number, api_id,
- api_hash);
-}
-
-Result<telegram_api::account_sendChangePhoneCode> SendCodeHelper::send_change_phone_code(Slice phone_number,
- bool allow_flash_call,
- bool is_current_phone_number) {
- phone_number_ = phone_number.str();
- int32 flags = 0;
- if (allow_flash_call) {
- flags |= AUTH_SEND_CODE_FLAG_ALLOW_FLASH_CALL;
- }
- return telegram_api::account_sendChangePhoneCode(flags, false /*ignored*/, phone_number_, is_current_phone_number);
-}
-
-SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_authentication_code_info(
- tl_object_ptr<telegram_api::auth_CodeType> &&code_type_ptr) {
- if (code_type_ptr == nullptr) {
- return AuthenticationCodeInfo();
- }
-
- switch (code_type_ptr->get_id()) {
- case telegram_api::auth_codeTypeSms::ID:
- return {AuthenticationCodeInfo::Type::Sms, 0, ""};
- case telegram_api::auth_codeTypeCall::ID:
- return {AuthenticationCodeInfo::Type::Call, 0, ""};
- case telegram_api::auth_codeTypeFlashCall::ID:
- return {AuthenticationCodeInfo::Type::FlashCall, 0, ""};
- default:
- UNREACHABLE();
- return AuthenticationCodeInfo();
- }
-}
-
-SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_authentication_code_info(
- tl_object_ptr<telegram_api::auth_SentCodeType> &&sent_code_type_ptr) {
- CHECK(sent_code_type_ptr != nullptr);
- switch (sent_code_type_ptr->get_id()) {
- case telegram_api::auth_sentCodeTypeApp::ID: {
- auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeApp>(sent_code_type_ptr);
- return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Message, code_type->length_, ""};
- }
- case telegram_api::auth_sentCodeTypeSms::ID: {
- auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeSms>(sent_code_type_ptr);
- return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Sms, code_type->length_, ""};
- }
- case telegram_api::auth_sentCodeTypeCall::ID: {
- auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeCall>(sent_code_type_ptr);
- return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Call, code_type->length_, ""};
- }
- case telegram_api::auth_sentCodeTypeFlashCall::ID: {
- auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeFlashCall>(sent_code_type_ptr);
- return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::FlashCall, 0, code_type->pattern_};
- }
- default:
- UNREACHABLE();
- return AuthenticationCodeInfo();
- }
-}
-
-tl_object_ptr<td_api::AuthenticationCodeType> SendCodeHelper::get_authentication_code_type_object(
- const AuthenticationCodeInfo &authentication_code_info) {
- switch (authentication_code_info.type) {
- case AuthenticationCodeInfo::Type::None:
- return nullptr;
- case AuthenticationCodeInfo::Type::Message:
- return make_tl_object<td_api::authenticationCodeTypeTelegramMessage>(authentication_code_info.length);
- case AuthenticationCodeInfo::Type::Sms:
- return make_tl_object<td_api::authenticationCodeTypeSms>(authentication_code_info.length);
- case AuthenticationCodeInfo::Type::Call:
- return make_tl_object<td_api::authenticationCodeTypeCall>(authentication_code_info.length);
- case AuthenticationCodeInfo::Type::FlashCall:
- return make_tl_object<td_api::authenticationCodeTypeFlashCall>(authentication_code_info.pattern);
- default:
- UNREACHABLE();
- return nullptr;
- }
-}
-
-// ChangePhoneNumberManager
-void ChangePhoneNumberManager::get_state(uint64 query_id) {
- tl_object_ptr<td_api::Object> obj;
- switch (state_) {
- case State::Ok:
- obj = make_tl_object<td_api::ok>();
- break;
- case State::WaitCode:
- obj = send_code_helper_.get_authentication_code_info_object();
- break;
- }
- CHECK(obj);
- send_closure(G()->td(), &Td::send_result, query_id, std::move(obj));
-}
-
-ChangePhoneNumberManager::ChangePhoneNumberManager(ActorShared<> parent) : parent_(std::move(parent)) {
-}
-void ChangePhoneNumberManager::change_phone_number(uint64 query_id, string phone_number, bool allow_flash_call,
- bool is_current_phone_number) {
- if (phone_number.empty()) {
- return on_query_error(query_id, Status::Error(8, "Phone number can't be empty"));
- }
- auto r_send_code = send_code_helper_.send_change_phone_code(phone_number, allow_flash_call, is_current_phone_number);
- if (r_send_code.is_error()) {
- return on_query_error(query_id, r_send_code.move_as_error());
- }
-
- on_new_query(query_id);
-
- start_net_query(NetQueryType::SendCode, G()->net_query_creator().create(create_storer(r_send_code.move_as_ok())));
-}
-
-void ChangePhoneNumberManager::resend_authentication_code(uint64 query_id) {
- if (state_ != State::WaitCode) {
- return on_query_error(query_id, Status::Error(8, "resendAuthenticationCode unexpected"));
- }
-
- auto r_resend_code = send_code_helper_.resend_code();
- if (r_resend_code.is_error()) {
- return on_query_error(query_id, r_resend_code.move_as_error());
- }
-
- on_new_query(query_id);
-
- start_net_query(NetQueryType::SendCode,
- G()->net_query_creator().create(create_storer(r_resend_code.move_as_ok()), DcId::main(),
- NetQuery::Type::Common, NetQuery::AuthFlag::Off));
-}
-
-void ChangePhoneNumberManager::check_code(uint64 query_id, string code) {
- if (state_ != State::WaitCode) {
- return on_query_error(query_id, Status::Error(8, "checkAuthenticationCode unexpected"));
- }
-
- on_new_query(query_id);
- start_net_query(NetQueryType::ChangePhone,
- G()->net_query_creator().create(create_storer(telegram_api::account_changePhone(
- send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code))));
-}
-
-void ChangePhoneNumberManager::on_new_query(uint64 query_id) {
- if (query_id_ != 0) {
- on_query_error(Status::Error(9, "Another authorization query has started"));
- }
- net_query_id_ = 0;
- net_query_type_ = NetQueryType::None;
- query_id_ = query_id;
- // TODO: cancel older net_query
-}
-
-void ChangePhoneNumberManager::on_query_error(Status status) {
- CHECK(query_id_ != 0);
- auto id = query_id_;
- query_id_ = 0;
- net_query_id_ = 0;
- net_query_type_ = NetQueryType::None;
- on_query_error(id, std::move(status));
-}
-
-void ChangePhoneNumberManager::on_query_error(uint64 id, Status status) {
- send_closure(G()->td(), &Td::send_error, id, std::move(status));
-}
-
-void ChangePhoneNumberManager::on_query_ok() {
- CHECK(query_id_ != 0);
- auto id = query_id_;
- net_query_id_ = 0;
- net_query_type_ = NetQueryType::None;
- query_id_ = 0;
- get_state(id);
-}
-
-void ChangePhoneNumberManager::start_net_query(NetQueryType net_query_type, NetQueryPtr net_query) {
- // TODO: cancel old net_query?
- net_query_type_ = net_query_type;
- net_query_id_ = net_query->id();
- G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this));
-}
-
-void ChangePhoneNumberManager::on_change_phone_result(NetQueryPtr &result) {
- auto r_change_phone = fetch_result<telegram_api::account_changePhone>(result->ok());
- if (r_change_phone.is_error()) {
- return on_query_error(r_change_phone.move_as_error());
- }
- state_ = State::Ok;
- on_query_ok();
-}
-
-void ChangePhoneNumberManager::on_send_code_result(NetQueryPtr &result) {
- auto r_sent_code = fetch_result<telegram_api::account_sendChangePhoneCode>(result->ok());
- if (r_sent_code.is_error()) {
- return on_query_error(r_sent_code.move_as_error());
- }
- auto sent_code = r_sent_code.move_as_ok();
-
- LOG(INFO) << "Receive " << to_string(sent_code);
-
- send_code_helper_.on_sent_code(std::move(sent_code));
-
- state_ = State::WaitCode;
- on_query_ok();
-}
-
-void ChangePhoneNumberManager::on_result(NetQueryPtr result) {
- SCOPE_EXIT {
- result->clear();
- };
- NetQueryType type = NetQueryType::None;
- if (result->id() == net_query_id_) {
- net_query_id_ = 0;
- type = net_query_type_;
- net_query_type_ = NetQueryType::None;
- if (result->is_error()) {
- if (query_id_ != 0) {
- on_query_error(std::move(result->error()));
- }
- return;
- }
- }
- switch (type) {
- case NetQueryType::None:
- result->ignore();
- break;
- case NetQueryType::SendCode:
- on_send_code_result(result);
- break;
- case NetQueryType::ChangePhone:
- on_change_phone_result(result);
- break;
- }
-}
-
-void ChangePhoneNumberManager::tear_down() {
- parent_.reset();
-}
-
-// AuthManager
AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> parent)
: parent_(std::move(parent)), api_id_(api_id), api_hash_(api_hash) {
string auth_str = G()->td_db()->get_binlog_pmc()->get("auth");
@@ -319,16 +50,21 @@ AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> par
auto my_id = ContactsManager::load_my_id();
if (my_id.is_valid()) {
// just in case
- G()->shared_config().set_option_integer("my_id", my_id.get());
+ LOG(INFO) << "Logged in as " << my_id;
+ td_->option_manager_->set_option_integer("my_id", my_id.get());
update_state(State::Ok);
} else {
LOG(ERROR) << "Restore unknown my_id";
ContactsManager::send_get_me_query(
- G()->td().get_actor_unsafe(),
- PromiseCreator::lambda([this](Result<Unit> result) { update_state(State::Ok); }));
+ td_, PromiseCreator::lambda([this](Result<Unit> result) { update_state(State::Ok); }));
}
+ G()->net_query_dispatcher().check_authorization_is_ok();
} else if (auth_str == "logout") {
+ LOG(WARNING) << "Continue to log out";
update_state(State::LoggingOut);
+ } else if (auth_str == "destroy") {
+ LOG(WARNING) << "Continue to destroy auth keys";
+ update_state(State::DestroyingKeys);
} else {
if (!load_state()) {
update_state(State::WaitPhoneNumber);
@@ -338,7 +74,13 @@ AuthManager::AuthManager(int32 api_id, const string &api_hash, ActorShared<> par
void AuthManager::start_up() {
if (state_ == State::LoggingOut) {
- start_net_query(NetQueryType::LogOut, G()->net_query_creator().create(create_storer(telegram_api::auth_logOut())));
+ send_log_out_query();
+ } else if (state_ == State::DestroyingKeys) {
+ G()->net_query_dispatcher().destroy_auth_keys(PromiseCreator::lambda([](Result<Unit> result) {
+ if (result.is_ok()) {
+ send_closure_later(G()->td(), &Td::destroy);
+ }
+ }));
}
}
void AuthManager::tear_down() {
@@ -346,15 +88,15 @@ void AuthManager::tear_down() {
}
bool AuthManager::is_bot() const {
- return is_authorized() && is_bot_;
+ if (net_query_id_ != 0 && net_query_type_ == NetQueryType::BotAuthentication) {
+ return true;
+ }
+ return is_bot_ && was_authorized();
}
-void AuthManager::set_is_bot(bool is_bot) {
- if (!is_bot_ && is_bot && api_id_ == 23818) {
- LOG(ERROR) << "Fix is_bot to " << is_bot;
- G()->td_db()->get_binlog_pmc()->set("auth_is_bot", "true");
- is_bot_ = true;
- }
+bool AuthManager::was_authorized() const {
+ return state_ == State::Ok || state_ == State::LoggingOut || state_ == State::DestroyingKeys ||
+ state_ == State::Closing;
}
bool AuthManager::is_authorized() const {
@@ -363,16 +105,29 @@ bool AuthManager::is_authorized() const {
tl_object_ptr<td_api::AuthorizationState> AuthManager::get_authorization_state_object(State authorization_state) const {
switch (authorization_state) {
- case State::Ok:
- return make_tl_object<td_api::authorizationStateReady>();
- case State::WaitCode:
- return send_code_helper_.get_authorization_state_wait_code();
case State::WaitPhoneNumber:
return make_tl_object<td_api::authorizationStateWaitPhoneNumber>();
+ case State::WaitEmailAddress:
+ return make_tl_object<td_api::authorizationStateWaitEmailAddress>(allow_apple_id_, allow_google_id_);
+ case State::WaitEmailCode:
+ return make_tl_object<td_api::authorizationStateWaitEmailCode>(
+ allow_apple_id_, allow_google_id_, email_code_info_.get_email_address_authentication_code_info_object(),
+ next_phone_number_login_date_);
+ case State::WaitCode:
+ return send_code_helper_.get_authorization_state_wait_code();
+ case State::WaitQrCodeConfirmation:
+ return make_tl_object<td_api::authorizationStateWaitOtherDeviceConfirmation>("tg://login?token=" +
+ base64url_encode(login_token_));
case State::WaitPassword:
return make_tl_object<td_api::authorizationStateWaitPassword>(
wait_password_state_.hint_, wait_password_state_.has_recovery_, wait_password_state_.email_address_pattern_);
+ case State::WaitRegistration:
+ return make_tl_object<td_api::authorizationStateWaitRegistration>(
+ terms_of_service_.get_terms_of_service_object());
+ case State::Ok:
+ return make_tl_object<td_api::authorizationStateReady>();
case State::LoggingOut:
+ case State::DestroyingKeys:
return make_tl_object<td_api::authorizationStateLoggingOut>();
case State::Closing:
return make_tl_object<td_api::authorizationStateClosing>();
@@ -383,6 +138,14 @@ tl_object_ptr<td_api::AuthorizationState> AuthManager::get_authorization_state_o
}
}
+tl_object_ptr<td_api::AuthorizationState> AuthManager::get_current_authorization_state_object() const {
+ if (state_ == State::None) {
+ return nullptr;
+ } else {
+ return get_authorization_state_object(state_);
+ }
+}
+
void AuthManager::get_state(uint64 query_id) {
if (state_ == State::None) {
pending_get_authorization_state_requests_.push_back(query_id);
@@ -392,76 +155,165 @@ void AuthManager::get_state(uint64 query_id) {
}
void AuthManager::check_bot_token(uint64 query_id, string bot_token) {
- if (state_ != State::WaitPhoneNumber && state_ != State::Ok) {
- // TODO do not allow State::Ok
- return on_query_error(query_id, Status::Error(8, "checkAuthenticationBotToken unexpected"));
+ if (state_ == State::WaitPhoneNumber && net_query_id_ == 0) {
+ // can ignore previous checks
+ was_check_bot_token_ = false; // TODO can we remove was_check_bot_token_?
+ }
+ if (state_ != State::WaitPhoneNumber) {
+ return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationBotToken unexpected"));
}
- if (!send_code_helper_.phone_number().empty()) {
+ if (!send_code_helper_.phone_number().empty() || was_qr_code_request_) {
return on_query_error(
- query_id, Status::Error(8, "Cannot set bot token after authentication beginning. You need to log out first"));
+ query_id, Status::Error(400, "Cannot set bot token after authentication began. You need to log out first"));
}
if (was_check_bot_token_ && bot_token_ != bot_token) {
- return on_query_error(query_id, Status::Error(8, "Cannot change bot token. You need to log out first"));
- }
- if (state_ == State::Ok) {
- if (!is_bot_) {
- // fix old bots
- const int32 AUTH_IS_BOT_FIXED_DATE = 1500940800;
- if (G()->shared_config().get_option_integer("authorization_date") < AUTH_IS_BOT_FIXED_DATE) {
- G()->td_db()->get_binlog_pmc()->set("auth_is_bot", "true");
- is_bot_ = true;
- }
- }
- return send_ok(query_id);
+ return on_query_error(query_id, Status::Error(400, "Cannot change bot token. You need to log out first"));
}
on_new_query(query_id);
- bot_token_ = bot_token;
+ bot_token_ = std::move(bot_token);
was_check_bot_token_ = true;
start_net_query(NetQueryType::BotAuthentication,
- G()->net_query_creator().create(
- create_storer(telegram_api::auth_importBotAuthorization(0, api_id_, api_hash_, bot_token_)),
- DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
+ G()->net_query_creator().create_unauth(
+ telegram_api::auth_importBotAuthorization(0, api_id_, api_hash_, bot_token_)));
+}
+
+void AuthManager::request_qr_code_authentication(uint64 query_id, vector<UserId> other_user_ids) {
+ if (state_ != State::WaitPhoneNumber) {
+ if ((state_ == State::WaitEmailAddress || state_ == State::WaitEmailCode || state_ == State::WaitCode ||
+ state_ == State::WaitPassword || state_ == State::WaitRegistration) &&
+ net_query_id_ == 0) {
+ // ok
+ } else {
+ return on_query_error(query_id, Status::Error(400, "Call to requestQrCodeAuthentication unexpected"));
+ }
+ }
+ if (was_check_bot_token_) {
+ return on_query_error(
+ query_id,
+ Status::Error(400,
+ "Cannot request QR code authentication after bot token was entered. You need to log out first"));
+ }
+ for (auto &other_user_id : other_user_ids) {
+ if (!other_user_id.is_valid()) {
+ return on_query_error(query_id, Status::Error(400, "Invalid user_id among other user_ids"));
+ }
+ }
+
+ other_user_ids_ = std::move(other_user_ids);
+ send_code_helper_ = SendCodeHelper();
+ terms_of_service_ = TermsOfService();
+ was_qr_code_request_ = true;
+
+ on_new_query(query_id);
+
+ send_export_login_token_query();
+}
+
+void AuthManager::send_export_login_token_query() {
+ poll_export_login_code_timeout_.cancel_timeout();
+ start_net_query(NetQueryType::RequestQrCode,
+ G()->net_query_creator().create_unauth(telegram_api::auth_exportLoginToken(
+ api_id_, api_hash_, UserId::get_input_user_ids(other_user_ids_))));
+}
+
+void AuthManager::set_login_token_expires_at(double login_token_expires_at) {
+ login_token_expires_at_ = login_token_expires_at;
+ poll_export_login_code_timeout_.cancel_timeout();
+ poll_export_login_code_timeout_.set_callback(std::move(on_update_login_token_static));
+ poll_export_login_code_timeout_.set_callback_data(static_cast<void *>(td_));
+ poll_export_login_code_timeout_.set_timeout_at(login_token_expires_at_);
+}
+
+void AuthManager::on_update_login_token_static(void *td) {
+ if (G()->close_flag()) {
+ return;
+ }
+ static_cast<Td *>(td)->auth_manager_->on_update_login_token();
}
-void AuthManager::set_phone_number(uint64 query_id, string phone_number, bool allow_flash_call,
- bool is_current_phone_number) {
+void AuthManager::on_update_login_token() {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (state_ != State::WaitQrCodeConfirmation) {
+ return;
+ }
+
+ send_export_login_token_query();
+}
+
+void AuthManager::set_phone_number(uint64 query_id, string phone_number,
+ td_api::object_ptr<td_api::phoneNumberAuthenticationSettings> settings) {
if (state_ != State::WaitPhoneNumber) {
- if ((state_ == State::WaitCode || state_ == State::WaitPassword) && net_query_id_ == 0) {
+ if ((state_ == State::WaitEmailAddress || state_ == State::WaitEmailCode || state_ == State::WaitCode ||
+ state_ == State::WaitPassword || state_ == State::WaitRegistration) &&
+ net_query_id_ == 0) {
// ok
} else {
- return on_query_error(query_id, Status::Error(8, "setAuthenticationPhoneNumber unexpected"));
+ return on_query_error(query_id, Status::Error(400, "Call to setAuthenticationPhoneNumber unexpected"));
}
}
if (was_check_bot_token_) {
return on_query_error(
- query_id, Status::Error(8, "Cannot set phone number after bot token was entered. You need to log out first"));
+ query_id, Status::Error(400, "Cannot set phone number after bot token was entered. You need to log out first"));
}
if (phone_number.empty()) {
- return on_query_error(query_id, Status::Error(8, "Phone number can't be empty"));
+ return on_query_error(query_id, Status::Error(400, "Phone number must be non-empty"));
}
- auto r_send_code =
- send_code_helper_.send_code(phone_number, allow_flash_call, is_current_phone_number, api_id_, api_hash_);
- if (r_send_code.is_error()) {
+ other_user_ids_.clear();
+ was_qr_code_request_ = false;
+
+ allow_apple_id_ = false;
+ allow_google_id_ = false;
+ email_address_ = {};
+ email_code_info_ = {};
+ next_phone_number_login_date_ = 0;
+ code_ = string();
+ email_code_ = {};
+
+ if (send_code_helper_.phone_number() != phone_number) {
send_code_helper_ = SendCodeHelper();
- r_send_code =
- send_code_helper_.send_code(phone_number, allow_flash_call, is_current_phone_number, api_id_, api_hash_);
- if (r_send_code.is_error()) {
- return on_query_error(query_id, r_send_code.move_as_error());
+ terms_of_service_ = TermsOfService();
+ }
+
+ on_new_query(query_id);
+
+ start_net_query(NetQueryType::SendCode, G()->net_query_creator().create_unauth(send_code_helper_.send_code(
+ std::move(phone_number), settings, api_id_, api_hash_)));
+}
+
+void AuthManager::set_email_address(uint64 query_id, string email_address) {
+ if (state_ != State::WaitEmailAddress) {
+ if (state_ == State::WaitEmailCode && net_query_id_ == 0) {
+ // ok
+ } else {
+ return on_query_error(query_id, Status::Error(400, "Call to setAuthenticationEmailAddress unexpected"));
}
}
+ if (email_address.empty()) {
+ return on_query_error(query_id, Status::Error(400, "Email address must be non-empty"));
+ }
+
+ email_address_ = std::move(email_address);
on_new_query(query_id);
- start_net_query(NetQueryType::SendCode,
- G()->net_query_creator().create(create_storer(r_send_code.move_as_ok()), DcId::main(),
- NetQuery::Type::Common, NetQuery::AuthFlag::Off));
+ start_net_query(NetQueryType::SendEmailCode,
+ G()->net_query_creator().create_unauth(send_code_helper_.send_verify_email_code(email_address_)));
}
void AuthManager::resend_authentication_code(uint64 query_id) {
- if (state_ != State::WaitCode || was_check_bot_token_) {
- return on_query_error(query_id, Status::Error(8, "resendAuthenticationCode unexpected"));
+ if (state_ != State::WaitCode) {
+ if (state_ == State::WaitEmailCode) {
+ on_new_query(query_id);
+ start_net_query(NetQueryType::SendEmailCode,
+ G()->net_query_creator().create_unauth(send_code_helper_.send_verify_email_code(email_address_)));
+ return;
+ }
+
+ return on_query_error(query_id, Status::Error(400, "Call to resendAuthenticationCode unexpected"));
}
auto r_resend_code = send_code_helper_.resend_code();
@@ -471,114 +323,198 @@ void AuthManager::resend_authentication_code(uint64 query_id) {
on_new_query(query_id);
- start_net_query(NetQueryType::SendCode,
- G()->net_query_creator().create(create_storer(r_resend_code.move_as_ok()), DcId::main(),
- NetQuery::Type::Common, NetQuery::AuthFlag::Off));
+ start_net_query(NetQueryType::SendCode, G()->net_query_creator().create_unauth(r_resend_code.move_as_ok()));
}
-void AuthManager::check_code(uint64 query_id, string code, string first_name, string last_name) {
- if (state_ != State::WaitCode) {
- return on_query_error(query_id, Status::Error(8, "checkAuthenticationCode unexpected"));
+void AuthManager::send_auth_sign_in_query() {
+ bool is_email = !email_code_.is_empty();
+ int32 flags =
+ is_email ? telegram_api::auth_signIn::EMAIL_VERIFICATION_MASK : telegram_api::auth_signIn::PHONE_CODE_MASK;
+ start_net_query(NetQueryType::SignIn,
+ G()->net_query_creator().create_unauth(telegram_api::auth_signIn(
+ flags, send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code_,
+ is_email ? email_code_.get_input_email_verification() : nullptr)));
+}
+
+void AuthManager::check_email_code(uint64 query_id, EmailVerification &&code) {
+ if (code.is_empty()) {
+ return on_query_error(query_id, Status::Error(400, "Code must be non-empty"));
}
- first_name = clean_name(first_name, MAX_NAME_LENGTH);
- if (!send_code_helper_.phone_registered() && first_name.empty()) {
- return on_query_error(query_id, Status::Error(8, "First name can't be empty"));
+ if (state_ != State::WaitEmailCode && !(state_ == State::WaitEmailAddress && code.is_email_code())) {
+ return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationEmailCode unexpected"));
}
+ code_ = string();
+ email_code_ = std::move(code);
+
on_new_query(query_id);
- if (send_code_helper_.phone_registered()) {
- start_net_query(NetQueryType::SignIn,
- G()->net_query_creator().create(
- create_storer(telegram_api::auth_signIn(send_code_helper_.phone_number().str(),
- send_code_helper_.phone_code_hash().str(), code)),
- DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
+ if (email_address_.empty()) {
+ send_auth_sign_in_query();
} else {
- last_name = clean_name(last_name, MAX_NAME_LENGTH);
start_net_query(
- NetQueryType::SignUp,
- G()->net_query_creator().create(create_storer(telegram_api::auth_signUp(
- send_code_helper_.phone_number().str(),
- send_code_helper_.phone_code_hash().str(), code, first_name, last_name)),
- DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
+ NetQueryType::VerifyEmailAddress,
+ G()->net_query_creator().create_unauth(telegram_api::account_verifyEmail(
+ send_code_helper_.get_email_verify_purpose_login_setup(), email_code_.get_input_email_verification())));
}
}
+void AuthManager::check_code(uint64 query_id, string code) {
+ if (state_ != State::WaitCode) {
+ return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationCode unexpected"));
+ }
+
+ code_ = std::move(code);
+ email_code_ = {};
+
+ on_new_query(query_id);
+ send_auth_sign_in_query();
+}
+
+void AuthManager::register_user(uint64 query_id, string first_name, string last_name) {
+ if (state_ != State::WaitRegistration) {
+ return on_query_error(query_id, Status::Error(400, "Call to registerUser unexpected"));
+ }
+
+ on_new_query(query_id);
+ first_name = clean_name(first_name, MAX_NAME_LENGTH);
+ if (first_name.empty()) {
+ return on_query_error(Status::Error(400, "First name must be non-empty"));
+ }
+
+ last_name = clean_name(last_name, MAX_NAME_LENGTH);
+ start_net_query(NetQueryType::SignUp, G()->net_query_creator().create_unauth(telegram_api::auth_signUp(
+ send_code_helper_.phone_number().str(),
+ send_code_helper_.phone_code_hash().str(), first_name, last_name)));
+}
+
void AuthManager::check_password(uint64 query_id, string password) {
if (state_ != State::WaitPassword) {
- return on_query_error(query_id, Status::Error(8, "checkAuthenticationPassword unexpected"));
+ return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationPassword unexpected"));
}
- BufferSlice buf(32);
- password = wait_password_state_.current_salt_ + password + wait_password_state_.current_salt_;
- sha256(password, buf.as_slice());
+ LOG(INFO) << "Have SRP ID " << wait_password_state_.srp_id_;
on_new_query(query_id);
- start_net_query(NetQueryType::CheckPassword,
- G()->net_query_creator().create(create_storer(telegram_api::auth_checkPassword(std::move(buf))),
- DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
+ password_ = std::move(password);
+ recovery_code_.clear();
+ new_password_.clear();
+ new_hint_.clear();
+ start_net_query(NetQueryType::GetPassword,
+ G()->net_query_creator().create_unauth(telegram_api::account_getPassword()));
}
void AuthManager::request_password_recovery(uint64 query_id) {
if (state_ != State::WaitPassword) {
- return on_query_error(query_id, Status::Error(8, "requestAuthenticationPasswordRecovery unexpected"));
+ return on_query_error(query_id, Status::Error(400, "Call to requestAuthenticationPasswordRecovery unexpected"));
}
on_new_query(query_id);
start_net_query(NetQueryType::RequestPasswordRecovery,
- G()->net_query_creator().create(create_storer(telegram_api::auth_requestPasswordRecovery()),
- DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
+ G()->net_query_creator().create_unauth(telegram_api::auth_requestPasswordRecovery()));
+}
+
+void AuthManager::check_password_recovery_code(uint64 query_id, string code) {
+ if (state_ != State::WaitPassword) {
+ return on_query_error(query_id, Status::Error(400, "Call to checkAuthenticationPasswordRecoveryCode unexpected"));
+ }
+
+ on_new_query(query_id);
+ start_net_query(NetQueryType::CheckPasswordRecoveryCode,
+ G()->net_query_creator().create_unauth(telegram_api::auth_checkRecoveryPassword(code)));
}
-void AuthManager::recover_password(uint64 query_id, string code) {
+void AuthManager::recover_password(uint64 query_id, string code, string new_password, string new_hint) {
if (state_ != State::WaitPassword) {
- return on_query_error(query_id, Status::Error(8, "recoverAuthenticationPassword unexpected"));
+ return on_query_error(query_id, Status::Error(400, "Call to recoverAuthenticationPassword unexpected"));
}
on_new_query(query_id);
+ if (!new_password.empty()) {
+ password_.clear();
+ recovery_code_ = std::move(code);
+ new_password_ = std::move(new_password);
+ new_hint_ = std::move(new_hint);
+ start_net_query(NetQueryType::GetPassword,
+ G()->net_query_creator().create_unauth(telegram_api::account_getPassword()));
+ return;
+ }
start_net_query(NetQueryType::RecoverPassword,
- G()->net_query_creator().create(create_storer(telegram_api::auth_recoverPassword(code)), DcId::main(),
- NetQuery::Type::Common, NetQuery::AuthFlag::Off));
+ G()->net_query_creator().create_unauth(telegram_api::auth_recoverPassword(0, code, nullptr)));
}
-void AuthManager::logout(uint64 query_id) {
+void AuthManager::log_out(uint64 query_id) {
if (state_ == State::Closing) {
- return on_query_error(query_id, Status::Error(8, "Already logged out"));
+ return on_query_error(query_id, Status::Error(400, "Already logged out"));
}
- if (state_ == State::LoggingOut) {
- return on_query_error(query_id, Status::Error(8, "Already logging out"));
+ if (state_ == State::LoggingOut || state_ == State::DestroyingKeys) {
+ return on_query_error(query_id, Status::Error(400, "Already logging out"));
}
on_new_query(query_id);
if (state_ != State::Ok) {
- update_state(State::LoggingOut);
// TODO: could skip full logout if still no authorization
// TODO: send auth.cancelCode if state_ == State::WaitCode
- send_closure_later(G()->td(), &Td::destroy);
+ LOG(WARNING) << "Destroying auth keys by user request";
+ destroy_auth_keys();
on_query_ok();
} else {
- LOG(INFO) << "Logging out";
+ LOG(WARNING) << "Logging out by user request";
G()->td_db()->get_binlog_pmc()->set("auth", "logout");
update_state(State::LoggingOut);
- start_net_query(NetQueryType::LogOut, G()->net_query_creator().create(create_storer(telegram_api::auth_logOut())));
+ send_log_out_query();
}
}
-void AuthManager::delete_account(uint64 query_id, const string &reason) {
- if (state_ != State::Ok) {
- return on_query_error(query_id, Status::Error(8, "Need to log in first"));
+void AuthManager::send_log_out_query() {
+ // we can lose authorization while logging out, but still may need to resend the request,
+ // so we pretend that it doesn't require authorization
+ auto query = G()->net_query_creator().create_unauth(telegram_api::auth_logOut());
+ query->set_priority(1);
+ start_net_query(NetQueryType::LogOut, std::move(query));
+}
+
+void AuthManager::delete_account(uint64 query_id, string reason, string password) {
+ if (state_ != State::Ok && state_ != State::WaitPassword) {
+ return on_query_error(query_id, Status::Error(400, "Need to log in first"));
}
+ if (password.empty() || state_ != State::Ok) {
+ on_new_query(query_id);
+ LOG(INFO) << "Deleting account";
+ start_net_query(NetQueryType::DeleteAccount,
+ G()->net_query_creator().create_unauth(telegram_api::account_deleteAccount(0, reason, nullptr)));
+ } else {
+ send_closure(G()->password_manager(), &PasswordManager::get_input_check_password_srp, password,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), query_id, reason = std::move(reason)](
+ Result<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> r_input_password) mutable {
+ send_closure(actor_id, &AuthManager::do_delete_account, query_id, std::move(reason),
+ std::move(r_input_password));
+ }));
+ }
+}
+
+void AuthManager::do_delete_account(uint64 query_id, string reason,
+ Result<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> r_input_password) {
+ if (r_input_password.is_error()) {
+ return on_query_error(query_id, r_input_password.move_as_error());
+ }
+
on_new_query(query_id);
- LOG(INFO) << "Deleting account";
- update_state(State::LoggingOut);
- start_net_query(NetQueryType::DeleteAccount,
- G()->net_query_creator().create(create_storer(telegram_api::account_deleteAccount(reason))));
+ LOG(INFO) << "Deleting account with password";
+ int32 flags = telegram_api::account_deleteAccount::PASSWORD_MASK;
+ start_net_query(NetQueryType::DeleteAccount, G()->net_query_creator().create(telegram_api::account_deleteAccount(
+ flags, reason, r_input_password.move_as_ok())));
}
-void AuthManager::on_closing() {
- update_state(State::Closing);
+void AuthManager::on_closing(bool destroy_flag) {
+ if (destroy_flag) {
+ update_state(State::LoggingOut);
+ } else {
+ update_state(State::Closing);
+ }
}
void AuthManager::on_new_query(uint64 query_id) {
if (query_id_ != 0) {
- on_query_error(Status::Error(9, "Another authorization query has started"));
+ on_query_error(Status::Error(400, "Another authorization query has started"));
}
net_query_id_ = 0;
net_query_type_ = NetQueryType::None;
@@ -595,8 +531,8 @@ void AuthManager::on_query_error(Status status) {
on_query_error(id, std::move(status));
}
-void AuthManager::on_query_error(uint64 id, Status status) {
- send_closure(G()->td(), &Td::send_error, id, std::move(status));
+void AuthManager::on_query_error(uint64 query_id, Status status) {
+ send_closure(G()->td(), &Td::send_error, query_id, std::move(status));
}
void AuthManager::on_query_ok() {
@@ -619,6 +555,33 @@ void AuthManager::start_net_query(NetQueryType net_query_type, NetQueryPtr net_q
G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this));
}
+void AuthManager::on_sent_code(telegram_api::object_ptr<telegram_api::auth_sentCode> &&sent_code) {
+ auto code_type_id = sent_code->type_->get_id();
+ if (code_type_id == telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID) {
+ auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeSetUpEmailRequired>(std::move(sent_code->type_));
+ send_code_helper_.on_phone_code_hash(std::move(sent_code->phone_code_hash_));
+ allow_apple_id_ = code_type->apple_signin_allowed_;
+ allow_google_id_ = code_type->google_signin_allowed_;
+ update_state(State::WaitEmailAddress, true);
+ } else if (code_type_id == telegram_api::auth_sentCodeTypeEmailCode::ID) {
+ auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeEmailCode>(std::move(sent_code->type_));
+ send_code_helper_.on_phone_code_hash(std::move(sent_code->phone_code_hash_));
+ allow_apple_id_ = code_type->apple_signin_allowed_;
+ allow_google_id_ = code_type->google_signin_allowed_;
+ email_address_.clear();
+ email_code_info_ = SentEmailCode(std::move(code_type->email_pattern_), code_type->length_);
+ next_phone_number_login_date_ = td::max(static_cast<int32>(0), code_type->next_phone_login_date_);
+ if (email_code_info_.is_empty()) {
+ email_code_info_ = SentEmailCode("<unknown>", code_type->length_);
+ CHECK(!email_code_info_.is_empty());
+ }
+ update_state(State::WaitEmailCode, true);
+ } else {
+ send_code_helper_.on_sent_code(std::move(sent_code));
+ update_state(State::WaitCode, true);
+ }
+}
+
void AuthManager::on_send_code_result(NetQueryPtr &result) {
auto r_sent_code = fetch_result<telegram_api::auth_sendCode>(result->ok());
if (r_sent_code.is_error()) {
@@ -627,33 +590,209 @@ void AuthManager::on_send_code_result(NetQueryPtr &result) {
auto sent_code = r_sent_code.move_as_ok();
LOG(INFO) << "Receive " << to_string(sent_code);
+ on_sent_code(std::move(sent_code));
+ on_query_ok();
+}
- send_code_helper_.on_sent_code(std::move(sent_code));
+void AuthManager::on_send_email_code_result(NetQueryPtr &result) {
+ auto r_sent_code = fetch_result<telegram_api::account_sendVerifyEmailCode>(result->ok());
+ if (r_sent_code.is_error()) {
+ return on_query_error(r_sent_code.move_as_error());
+ }
+ auto sent_code = r_sent_code.move_as_ok();
+
+ LOG(INFO) << "Receive " << to_string(sent_code);
- update_state(State::WaitCode, true);
+ email_code_info_ = SentEmailCode(std::move(sent_code));
+ if (email_code_info_.is_empty()) {
+ return on_query_error(Status::Error(500, "Receive invalid response"));
+ }
+ next_phone_number_login_date_ = 0;
+
+ update_state(State::WaitEmailCode, true);
on_query_ok();
}
+void AuthManager::on_verify_email_address_result(NetQueryPtr &result) {
+ auto r_email_verified = fetch_result<telegram_api::account_verifyEmail>(result->ok());
+ if (r_email_verified.is_error()) {
+ return on_query_error(r_email_verified.move_as_error());
+ }
+ auto email_verified = r_email_verified.move_as_ok();
+
+ LOG(INFO) << "Receive " << to_string(email_verified);
+ if (email_verified->get_id() != telegram_api::account_emailVerifiedLogin::ID) {
+ return on_query_error(Status::Error(500, "Receive invalid response"));
+ }
+
+ auto verified_login = telegram_api::move_object_as<telegram_api::account_emailVerifiedLogin>(email_verified);
+ on_sent_code(std::move(verified_login->sent_code_));
+ on_query_ok();
+}
+
+void AuthManager::on_request_qr_code_result(NetQueryPtr &result, bool is_import) {
+ Status status;
+ if (result->is_ok()) {
+ auto r_login_token = fetch_result<telegram_api::auth_exportLoginToken>(result->ok());
+ if (r_login_token.is_ok()) {
+ auto login_token = r_login_token.move_as_ok();
+
+ if (is_import) {
+ CHECK(DcId::is_valid(imported_dc_id_));
+ G()->net_query_dispatcher().set_main_dc_id(imported_dc_id_);
+ imported_dc_id_ = -1;
+ }
+
+ on_get_login_token(std::move(login_token));
+ return;
+ }
+
+ status = r_login_token.move_as_error();
+ } else {
+ status = std::move(result->error());
+ }
+ CHECK(status.is_error());
+
+ LOG(INFO) << "Receive " << status << " for login token " << (is_import ? "import" : "export");
+ if (is_import) {
+ imported_dc_id_ = -1;
+ }
+ if (query_id_ != 0) {
+ on_query_error(std::move(status));
+ } else {
+ login_code_retry_delay_ = clamp(2 * login_code_retry_delay_, 1, 60);
+ set_login_token_expires_at(Time::now() + login_code_retry_delay_);
+ }
+}
+
+void AuthManager::on_get_login_token(tl_object_ptr<telegram_api::auth_LoginToken> login_token) {
+ LOG(INFO) << "Receive " << to_string(login_token);
+
+ login_code_retry_delay_ = 0;
+
+ CHECK(login_token != nullptr);
+ switch (login_token->get_id()) {
+ case telegram_api::auth_loginToken::ID: {
+ auto token = move_tl_object_as<telegram_api::auth_loginToken>(login_token);
+ login_token_ = token->token_.as_slice().str();
+ set_login_token_expires_at(Time::now() + td::max(token->expires_ - G()->server_time(), 1.0));
+ update_state(State::WaitQrCodeConfirmation, true);
+ if (query_id_ != 0) {
+ on_query_ok();
+ }
+ break;
+ }
+ case telegram_api::auth_loginTokenMigrateTo::ID: {
+ auto token = move_tl_object_as<telegram_api::auth_loginTokenMigrateTo>(login_token);
+ if (!DcId::is_valid(token->dc_id_)) {
+ LOG(ERROR) << "Receive wrong DC " << token->dc_id_;
+ return;
+ }
+ if (query_id_ != 0) {
+ on_query_ok();
+ }
+
+ imported_dc_id_ = token->dc_id_;
+ start_net_query(NetQueryType::ImportQrCode, G()->net_query_creator().create_unauth(
+ telegram_api::auth_importLoginToken(std::move(token->token_)),
+ DcId::internal(token->dc_id_)));
+ break;
+ }
+ case telegram_api::auth_loginTokenSuccess::ID: {
+ auto token = move_tl_object_as<telegram_api::auth_loginTokenSuccess>(login_token);
+ on_get_authorization(std::move(token->authorization_));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
void AuthManager::on_get_password_result(NetQueryPtr &result) {
- auto r_password = fetch_result<telegram_api::account_getPassword>(result->ok());
- if (r_password.is_error()) {
+ Result<telegram_api::object_ptr<telegram_api::account_password>> r_password;
+ if (result->is_error()) {
+ r_password = std::move(result->error());
+ } else {
+ r_password = fetch_result<telegram_api::account_getPassword>(result->ok());
+ }
+ if (r_password.is_error() && query_id_ != 0) {
return on_query_error(r_password.move_as_error());
}
- auto password = r_password.move_as_ok();
+ auto password = r_password.is_ok() ? r_password.move_as_ok() : nullptr;
+ LOG(INFO) << "Receive password info: " << to_string(password);
+
wait_password_state_ = WaitPasswordState();
- if (password->get_id() == telegram_api::account_noPassword::ID) {
- auto no_password = move_tl_object_as<telegram_api::account_noPassword>(password);
- wait_password_state_.new_salt_ = no_password->new_salt_.as_slice().str();
+ Result<NewPasswordState> r_new_password_state;
+ if (password != nullptr && password->current_algo_ != nullptr) {
+ switch (password->current_algo_->get_id()) {
+ case telegram_api::passwordKdfAlgoUnknown::ID:
+ return on_query_error(Status::Error(400, "Application update is needed to log in"));
+ case telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow::ID: {
+ auto algo = move_tl_object_as<telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow>(
+ password->current_algo_);
+ wait_password_state_.current_client_salt_ = algo->salt1_.as_slice().str();
+ wait_password_state_.current_server_salt_ = algo->salt2_.as_slice().str();
+ wait_password_state_.srp_g_ = algo->g_;
+ wait_password_state_.srp_p_ = algo->p_.as_slice().str();
+ wait_password_state_.srp_B_ = password->srp_B_.as_slice().str();
+ wait_password_state_.srp_id_ = password->srp_id_;
+ wait_password_state_.hint_ = std::move(password->hint_);
+ wait_password_state_.has_recovery_ = password->has_recovery_;
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ r_new_password_state =
+ get_new_password_state(std::move(password->new_algo_), std::move(password->new_secure_algo_));
+ } else if (was_qr_code_request_) {
+ imported_dc_id_ = -1;
+ login_code_retry_delay_ = clamp(2 * login_code_retry_delay_, 1, 60);
+ set_login_token_expires_at(Time::now() + login_code_retry_delay_);
+ return;
} else {
- CHECK(password->get_id() == telegram_api::account_password::ID);
- auto password_info = move_tl_object_as<telegram_api::account_password>(password);
- wait_password_state_.current_salt_ = password_info->current_salt_.as_slice().str();
- wait_password_state_.new_salt_ = password_info->new_salt_.as_slice().str();
- wait_password_state_.hint_ = password_info->hint_;
- wait_password_state_.has_recovery_ = password_info->has_recovery_;
- }
- update_state(State::WaitPassword);
- on_query_ok();
+ send_auth_sign_in_query();
+ return;
+ }
+
+ if (imported_dc_id_ != -1) {
+ G()->net_query_dispatcher().set_main_dc_id(imported_dc_id_);
+ imported_dc_id_ = -1;
+ }
+
+ if (state_ == State::WaitPassword) {
+ if (!new_password_.empty()) {
+ if (r_new_password_state.is_error()) {
+ return on_query_error(r_new_password_state.move_as_error());
+ }
+
+ auto r_new_settings = PasswordManager::get_password_input_settings(std::move(new_password_), std::move(new_hint_),
+ r_new_password_state.ok());
+ if (r_new_settings.is_error()) {
+ return on_query_error(r_new_settings.move_as_error());
+ }
+
+ int32 flags = telegram_api::auth_recoverPassword::NEW_SETTINGS_MASK;
+ start_net_query(NetQueryType::RecoverPassword,
+ G()->net_query_creator().create_unauth(
+ telegram_api::auth_recoverPassword(flags, recovery_code_, r_new_settings.move_as_ok())));
+ return;
+ }
+ LOG(INFO) << "Have SRP ID " << wait_password_state_.srp_id_;
+ auto hash = PasswordManager::get_input_check_password(password_, wait_password_state_.current_client_salt_,
+ wait_password_state_.current_server_salt_,
+ wait_password_state_.srp_g_, wait_password_state_.srp_p_,
+ wait_password_state_.srp_B_, wait_password_state_.srp_id_);
+
+ start_net_query(NetQueryType::CheckPassword,
+ G()->net_query_creator().create_unauth(telegram_api::auth_checkPassword(std::move(hash))));
+ } else {
+ update_state(State::WaitPassword);
+ if (query_id_ != 0) {
+ on_query_ok();
+ }
+ }
}
void AuthManager::on_request_password_recovery_result(NetQueryPtr &result) {
@@ -663,22 +802,31 @@ void AuthManager::on_request_password_recovery_result(NetQueryPtr &result) {
}
auto email_address_pattern = r_email_address_pattern.move_as_ok();
CHECK(email_address_pattern->get_id() == telegram_api::auth_passwordRecovery::ID);
- wait_password_state_.email_address_pattern_ = email_address_pattern->email_pattern_;
+ wait_password_state_.email_address_pattern_ = std::move(email_address_pattern->email_pattern_);
update_state(State::WaitPassword, true);
on_query_ok();
}
-void AuthManager::on_authentication_result(NetQueryPtr &result, bool expected_flag) {
+void AuthManager::on_check_password_recovery_code_result(NetQueryPtr &result) {
+ auto r_success = fetch_result<telegram_api::auth_checkRecoveryPassword>(result->ok());
+ if (r_success.is_error()) {
+ return on_query_error(r_success.move_as_error());
+ }
+ if (!r_success.ok()) {
+ return on_query_error(Status::Error(400, "Invalid recovery code"));
+ }
+ on_query_ok();
+}
+
+void AuthManager::on_authentication_result(NetQueryPtr &result, bool is_from_current_query) {
auto r_sign_in = fetch_result<telegram_api::auth_signIn>(result->ok());
if (r_sign_in.is_error()) {
- if (expected_flag && query_id_ != 0) {
+ if (is_from_current_query && query_id_ != 0) {
return on_query_error(r_sign_in.move_as_error());
}
return;
}
- auto sign_in = r_sign_in.move_as_ok();
- CHECK(sign_in->get_id() == telegram_api::auth_authorization::ID);
- on_authorization(std::move(sign_in));
+ on_get_authorization(r_sign_in.move_as_ok());
}
void AuthManager::on_log_out_result(NetQueryPtr &result) {
@@ -686,8 +834,10 @@ void AuthManager::on_log_out_result(NetQueryPtr &result) {
if (result->is_ok()) {
auto r_log_out = fetch_result<telegram_api::auth_logOut>(result->ok());
if (r_log_out.is_ok()) {
- if (!r_log_out.ok()) {
- status = Status::Error(500, "auth.logOut returned false!");
+ auto logged_out = r_log_out.move_as_ok();
+ if (!logged_out->future_auth_token_.empty()) {
+ td_->option_manager_->set_option_string("authentication_token",
+ base64url_encode(logged_out->future_auth_token_.as_slice()));
}
} else {
status = r_log_out.move_as_error();
@@ -695,13 +845,43 @@ void AuthManager::on_log_out_result(NetQueryPtr &result) {
} else {
status = std::move(result->error());
}
- LOG_IF(ERROR, status.is_error()) << "auth.logOut failed: " << status;
- // state_ will stay logout, so no queries will work.
- send_closure_later(G()->td(), &Td::destroy);
+ LOG_IF(ERROR, status.is_error() && status.code() != 401) << "Receive error for auth.logOut: " << status;
+ // state_ will stay LoggingOut, so no queries will work.
+ destroy_auth_keys();
if (query_id_ != 0) {
on_query_ok();
}
}
+void AuthManager::on_authorization_lost(string source) {
+ if (state_ == State::LoggingOut && net_query_type_ == NetQueryType::LogOut) {
+ LOG(INFO) << "Ignore authorization loss because of " << source << ", while logging out";
+ return;
+ }
+ if (state_ == State::Closing || state_ == State::DestroyingKeys) {
+ LOG(INFO) << "Ignore duplicate authorization loss because of " << source;
+ return;
+ }
+ LOG(WARNING) << "Lost authorization because of " << source;
+ destroy_auth_keys();
+}
+
+void AuthManager::destroy_auth_keys() {
+ if (state_ == State::Closing || state_ == State::DestroyingKeys) {
+ return;
+ }
+ update_state(State::DestroyingKeys);
+ auto promise = PromiseCreator::lambda([](Result<Unit> result) {
+ if (result.is_ok()) {
+ G()->net_query_dispatcher().destroy_auth_keys(PromiseCreator::lambda([](Result<Unit> result) {
+ if (result.is_ok()) {
+ send_closure_later(G()->td(), &Td::destroy);
+ }
+ }));
+ }
+ });
+ G()->td_db()->get_binlog_pmc()->set("auth", "destroy");
+ G()->td_db()->get_binlog_pmc()->force_sync(std::move(promise));
+}
void AuthManager::on_delete_account_result(NetQueryPtr &result) {
Status status;
@@ -709,7 +889,7 @@ void AuthManager::on_delete_account_result(NetQueryPtr &result) {
auto r_delete_account = fetch_result<telegram_api::account_deleteAccount>(result->ok());
if (r_delete_account.is_ok()) {
if (!r_delete_account.ok()) {
- status = Status::Error(500, "Receive false as result of the request");
+ // status = Status::Error(500, "Receive false as result of the request");
}
} else {
status = r_delete_account.move_as_error();
@@ -717,45 +897,84 @@ void AuthManager::on_delete_account_result(NetQueryPtr &result) {
} else {
status = std::move(result->error());
}
- if (status.is_error() && status.error().message() != "USER_DEACTIVATED") {
- update_state(State::Ok);
- LOG(WARNING) << "account.deleteAccount failed: " << status;
+ if (status.is_error() && status.message() != "USER_DEACTIVATED") {
+ LOG(WARNING) << "Request account.deleteAccount failed: " << status;
// TODO handle some errors
if (query_id_ != 0) {
on_query_error(std::move(status));
}
} else {
- send_closure_later(G()->td(), &Td::destroy);
+ destroy_auth_keys();
if (query_id_ != 0) {
on_query_ok();
}
}
}
-void AuthManager::on_authorization(tl_object_ptr<telegram_api::auth_authorization> auth) {
- G()->shared_config().set_option_integer("authorization_date", G()->unix_time());
+void AuthManager::on_get_authorization(tl_object_ptr<telegram_api::auth_Authorization> auth_ptr) {
+ if (state_ == State::Ok) {
+ LOG(WARNING) << "Ignore duplicated auth.Authorization";
+ if (query_id_ != 0) {
+ on_query_ok();
+ }
+ return;
+ }
+ CHECK(auth_ptr != nullptr);
+ if (auth_ptr->get_id() == telegram_api::auth_authorizationSignUpRequired::ID) {
+ auto sign_up_required = telegram_api::move_object_as<telegram_api::auth_authorizationSignUpRequired>(auth_ptr);
+ terms_of_service_ = TermsOfService(std::move(sign_up_required->terms_of_service_));
+ update_state(State::WaitRegistration);
+ if (query_id_ != 0) {
+ on_query_ok();
+ }
+ return;
+ }
+ auto auth = telegram_api::move_object_as<telegram_api::auth_authorization>(auth_ptr);
+
+ td_->option_manager_->set_option_integer("authorization_date", G()->unix_time());
if (was_check_bot_token_) {
is_bot_ = true;
G()->td_db()->get_binlog_pmc()->set("auth_is_bot", "true");
}
G()->td_db()->get_binlog_pmc()->set("auth", "ok");
+ code_.clear();
+ password_.clear();
+ recovery_code_.clear();
+ new_password_.clear();
+ new_hint_.clear();
state_ = State::Ok;
- td->contacts_manager_->on_get_user(std::move(auth->user_), true);
+ td_->contacts_manager_->on_get_user(std::move(auth->user_), "on_get_authorization", true);
update_state(State::Ok, true);
- if (!td->contacts_manager_->get_my_id("on_authorization").is_valid()) {
+ if (!td_->contacts_manager_->get_my_id().is_valid()) {
LOG(ERROR) << "Server doesn't send proper authorization";
if (query_id_ != 0) {
on_query_error(Status::Error(500, "Server doesn't send proper authorization"));
}
- logout(0);
+ log_out(0);
return;
}
if ((auth->flags_ & telegram_api::auth_authorization::TMP_SESSIONS_MASK) != 0) {
- G()->shared_config().set_option_integer("session_count", auth->tmp_sessions_);
+ td_->option_manager_->set_option_integer("session_count", auth->tmp_sessions_);
+ }
+ if (auth->setup_password_required_ && auth->otherwise_relogin_days_ > 0) {
+ td_->option_manager_->set_option_integer("otherwise_relogin_days", auth->otherwise_relogin_days_);
+ }
+ td_->attach_menu_manager_->init();
+ td_->messages_manager_->on_authorization_success();
+ td_->notification_manager_->init();
+ td_->stickers_manager_->init();
+ td_->theme_manager_->init();
+ td_->top_dialog_manager_->init();
+ td_->updates_manager_->get_difference("on_get_authorization");
+ td_->on_online_updated(false, true);
+ if (!is_bot()) {
+ td_->schedule_get_terms_of_service(0);
+ td_->schedule_get_promo_data(0);
+ G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1");
+ } else {
+ td_->set_is_bot_online(true);
}
- td->updates_manager_->get_difference("on_authorization");
- td->on_online_updated(true, true);
- send_closure(G()->config_manager(), &ConfigManager::request_config);
+ send_closure(G()->config_manager(), &ConfigManager::request_config, false);
if (query_id_ != 0) {
on_query_ok();
}
@@ -766,26 +985,48 @@ void AuthManager::on_result(NetQueryPtr result) {
result->clear();
};
NetQueryType type = NetQueryType::None;
+ LOG(INFO) << "Receive result of query " << result->id() << ", expecting " << net_query_id_ << " with type "
+ << static_cast<int32>(net_query_type_);
if (result->id() == net_query_id_) {
net_query_id_ = 0;
type = net_query_type_;
net_query_type_ = NetQueryType::None;
if (result->is_error()) {
- if (type == NetQueryType::SignIn && result->error().code() == 401 &&
- result->error().message() == CSlice("SESSION_PASSWORD_NEEDED")) {
+ if ((type == NetQueryType::SendCode || type == NetQueryType::SendEmailCode ||
+ type == NetQueryType::VerifyEmailAddress || type == NetQueryType::SignIn ||
+ type == NetQueryType::RequestQrCode || type == NetQueryType::ImportQrCode) &&
+ result->error().code() == 401 && result->error().message() == CSlice("SESSION_PASSWORD_NEEDED")) {
+ auto dc_id = DcId::main();
+ if (type == NetQueryType::ImportQrCode) {
+ CHECK(DcId::is_valid(imported_dc_id_));
+ dc_id = DcId::internal(imported_dc_id_);
+ }
start_net_query(NetQueryType::GetPassword,
- G()->net_query_creator().create(create_storer(telegram_api::account_getPassword()),
- DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::Off));
+ G()->net_query_creator().create_unauth(telegram_api::account_getPassword(), dc_id));
return;
}
- if (type != NetQueryType::LogOut) {
+ if (result->error().message() == CSlice("PHONE_NUMBER_BANNED")) {
+ LOG(PLAIN)
+ << "Your phone number was banned for suspicious activity. If you think that this is a mistake, please "
+ "write to recover@telegram.org your phone number and other details to recover the account.";
+ }
+ if (type != NetQueryType::LogOut && type != NetQueryType::DeleteAccount) {
if (query_id_ != 0) {
if (state_ == State::WaitPhoneNumber) {
+ other_user_ids_.clear();
send_code_helper_ = SendCodeHelper();
+ terms_of_service_ = TermsOfService();
+ was_qr_code_request_ = false;
+ was_check_bot_token_ = false;
}
on_query_error(std::move(result->error()));
+ return;
+ }
+ if (type != NetQueryType::RequestQrCode && type != NetQueryType::ImportQrCode &&
+ type != NetQueryType::GetPassword) {
+ LOG(INFO) << "Ignore error for net query of type " << static_cast<int32>(net_query_type_);
+ return;
}
- return;
}
}
} else if (result->is_ok() && result->ok_tl_constructor() == telegram_api::auth_authorization::ID) {
@@ -808,12 +1049,27 @@ void AuthManager::on_result(NetQueryPtr result) {
case NetQueryType::SendCode:
on_send_code_result(result);
break;
+ case NetQueryType::SendEmailCode:
+ on_send_email_code_result(result);
+ break;
+ case NetQueryType::VerifyEmailAddress:
+ on_verify_email_address_result(result);
+ break;
+ case NetQueryType::RequestQrCode:
+ on_request_qr_code_result(result, false);
+ break;
+ case NetQueryType::ImportQrCode:
+ on_request_qr_code_result(result, true);
+ break;
case NetQueryType::GetPassword:
on_get_password_result(result);
break;
case NetQueryType::RequestPasswordRecovery:
on_request_password_recovery_result(result);
break;
+ case NetQueryType::CheckPasswordRecoveryCode:
+ on_check_password_recovery_code_result(result);
+ break;
case NetQueryType::LogOut:
on_log_out_result(result);
break;
@@ -827,12 +1083,19 @@ void AuthManager::update_state(State new_state, bool force, bool should_save_sta
if (state_ == new_state && !force) {
return;
}
+ bool skip_update = (state_ == State::LoggingOut || state_ == State::DestroyingKeys) &&
+ (new_state == State::LoggingOut || new_state == State::DestroyingKeys);
state_ = new_state;
if (should_save_state) {
save_state();
}
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateAuthorizationState>(get_authorization_state_object(state_)));
+ if (new_state == State::LoggingOut || new_state == State::DestroyingKeys) {
+ send_closure(G()->state_manager(), &StateManager::on_logging_out, true);
+ }
+ if (!skip_update) {
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateAuthorizationState>(get_authorization_state_object(state_)));
+ }
if (!pending_get_authorization_state_requests_.empty()) {
auto query_ids = std::move(pending_get_authorization_state_requests_);
@@ -844,6 +1107,10 @@ void AuthManager::update_state(State new_state, bool force, bool should_save_sta
bool AuthManager::load_state() {
auto data = G()->td_db()->get_binlog_pmc()->get("auth_state");
+ if (data.empty()) {
+ LOG(INFO) << "Have no saved auth_state. Waiting for phone number";
+ return false;
+ }
DbState db_state;
auto status = log_event_parse(db_state, data);
if (status.is_error()) {
@@ -854,20 +1121,34 @@ bool AuthManager::load_state() {
LOG(INFO) << "Ignore auth_state: api_id or api_hash changed";
return false;
}
- if (!db_state.state_timestamp_.is_in_past()) {
- LOG(INFO) << "Ignore auth_state: timestamp in future";
- return false;
- }
- if (Timestamp::at(db_state.state_timestamp_.at() + 5 * 60).is_in_past()) {
- LOG(INFO) << "Ignore auth_state: expired " << db_state.state_timestamp_.in();
+ if (db_state.expires_at_ <= Time::now()) {
+ LOG(INFO) << "Ignore auth_state: expired";
return false;
}
- LOG(INFO) << "Load auth_state from db: " << tag("state", static_cast<int32>(db_state.state_));
- if (db_state.state_ == State::WaitCode) {
+ LOG(INFO) << "Load auth_state from database: " << tag("state", static_cast<int32>(db_state.state_));
+ if (db_state.state_ == State::WaitEmailAddress) {
+ allow_apple_id_ = db_state.allow_apple_id_;
+ allow_google_id_ = db_state.allow_google_id_;
send_code_helper_ = std::move(db_state.send_code_helper_);
+ } else if (db_state.state_ == State::WaitEmailCode) {
+ allow_apple_id_ = db_state.allow_apple_id_;
+ allow_google_id_ = db_state.allow_google_id_;
+ email_address_ = std::move(db_state.email_address_);
+ email_code_info_ = std::move(db_state.email_code_info_);
+ next_phone_number_login_date_ = db_state.next_phone_number_login_date_;
+ send_code_helper_ = std::move(db_state.send_code_helper_);
+ } else if (db_state.state_ == State::WaitCode) {
+ send_code_helper_ = std::move(db_state.send_code_helper_);
+ } else if (db_state.state_ == State::WaitQrCodeConfirmation) {
+ other_user_ids_ = std::move(db_state.other_user_ids_);
+ login_token_ = std::move(db_state.login_token_);
+ set_login_token_expires_at(db_state.login_token_expires_at_);
} else if (db_state.state_ == State::WaitPassword) {
wait_password_state_ = std::move(db_state.wait_password_state_);
+ } else if (db_state.state_ == State::WaitRegistration) {
+ send_code_helper_ = std::move(db_state.send_code_helper_);
+ terms_of_service_ = std::move(db_state.terms_of_service_);
} else {
UNREACHABLE();
}
@@ -876,21 +1157,32 @@ bool AuthManager::load_state() {
}
void AuthManager::save_state() {
- if (state_ != State::WaitCode && state_ != State::WaitPassword) {
+ if (state_ != State::WaitEmailAddress && state_ != State::WaitEmailCode && state_ != State::WaitCode &&
+ state_ != State::WaitQrCodeConfirmation && state_ != State::WaitPassword && state_ != State::WaitRegistration) {
if (state_ != State::Closing) {
G()->td_db()->get_binlog_pmc()->erase("auth_state");
}
return;
}
- DbState db_state;
- if (state_ == State::WaitCode) {
- db_state = DbState::wait_code(api_id_, api_hash_, send_code_helper_);
- } else if (state_ == State::WaitPassword) {
- db_state = DbState::wait_password(api_id_, api_hash_, wait_password_state_);
- } else {
- UNREACHABLE();
- }
+ DbState db_state = [&] {
+ if (state_ == State::WaitEmailAddress) {
+ return DbState::wait_email_address(api_id_, api_hash_, allow_apple_id_, allow_google_id_, send_code_helper_);
+ } else if (state_ == State::WaitEmailCode) {
+ return DbState::wait_email_code(api_id_, api_hash_, allow_apple_id_, allow_google_id_, email_address_,
+ email_code_info_, next_phone_number_login_date_, send_code_helper_);
+ } else if (state_ == State::WaitCode) {
+ return DbState::wait_code(api_id_, api_hash_, send_code_helper_);
+ } else if (state_ == State::WaitQrCodeConfirmation) {
+ return DbState::wait_qr_code_confirmation(api_id_, api_hash_, other_user_ids_, login_token_,
+ login_token_expires_at_);
+ } else if (state_ == State::WaitPassword) {
+ return DbState::wait_password(api_id_, api_hash_, wait_password_state_);
+ } else {
+ CHECK(state_ == State::WaitRegistration);
+ return DbState::wait_registration(api_id_, api_hash_, send_code_helper_, terms_of_service_);
+ }
+ }();
G()->td_db()->get_binlog_pmc()->set("auth_state", log_event_store(db_state).as_slice().str());
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AuthManager.h b/protocols/Telegram/tdlib/td/td/telegram/AuthManager.h
index d5e9cbf3d9..0ae4d2d161 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AuthManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/AuthManager.h
@@ -1,163 +1,94 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/EmailVerification.h"
#include "td/telegram/net/NetActor.h"
#include "td/telegram/net/NetQuery.h"
-
+#include "td/telegram/SendCodeHelper.h"
+#include "td/telegram/SentEmailCode.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/TermsOfService.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/Timeout.h"
#include "td/utils/common.h"
-#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/Time.h"
namespace td {
-class SendCodeHelper {
- public:
- void on_sent_code(telegram_api::object_ptr<telegram_api::auth_sentCode> sent_code);
- td_api::object_ptr<td_api::authorizationStateWaitCode> get_authorization_state_wait_code() const;
- td_api::object_ptr<td_api::authenticationCodeInfo> get_authentication_code_info_object() const;
- Result<telegram_api::auth_resendCode> resend_code();
- Result<telegram_api::auth_sendCode> send_code(Slice phone_number, bool allow_flash_call, bool is_current_phone_number,
- int32 api_id, const string &api_hash);
- Result<telegram_api::account_sendChangePhoneCode> send_change_phone_code(Slice phone_number, bool allow_flash_call,
- bool is_current_phone_number);
-
- Slice phone_number() const {
- return phone_number_;
- }
- Slice phone_code_hash() const {
- return phone_code_hash_;
- }
- bool phone_registered() const {
- return phone_registered_;
- }
-
- template <class T>
- void store(T &storer) const;
- template <class T>
- void parse(T &parser);
-
- private:
- static constexpr int32 AUTH_SEND_CODE_FLAG_ALLOW_FLASH_CALL = 1 << 0;
-
- static constexpr int32 SENT_CODE_FLAG_IS_USER_REGISTERED = 1 << 0;
- static constexpr int32 SENT_CODE_FLAG_HAS_NEXT_TYPE = 1 << 1;
- static constexpr int32 SENT_CODE_FLAG_HAS_TIMEOUT = 1 << 2;
-
- struct AuthenticationCodeInfo {
- enum class Type : int32 { None, Message, Sms, Call, FlashCall };
- Type type = Type::None;
- int32 length = 0;
- string pattern;
-
- AuthenticationCodeInfo() = default;
- AuthenticationCodeInfo(Type type, int length, string pattern)
- : type(type), length(length), pattern(std::move(pattern)) {
- }
-
- template <class T>
- void store(T &storer) const;
- template <class T>
- void parse(T &parser);
- };
-
- string phone_number_;
- bool phone_registered_;
- string phone_code_hash_;
-
- SendCodeHelper::AuthenticationCodeInfo sent_code_info_;
- SendCodeHelper::AuthenticationCodeInfo next_code_info_;
- Timestamp next_code_timestamp_;
-
- static AuthenticationCodeInfo get_authentication_code_info(
- tl_object_ptr<telegram_api::auth_CodeType> &&code_type_ptr);
- static AuthenticationCodeInfo get_authentication_code_info(
- tl_object_ptr<telegram_api::auth_SentCodeType> &&sent_code_type_ptr);
-
- static tl_object_ptr<td_api::AuthenticationCodeType> get_authentication_code_type_object(
- const AuthenticationCodeInfo &authentication_code_info);
-};
-
-class ChangePhoneNumberManager : public NetActor {
- public:
- explicit ChangePhoneNumberManager(ActorShared<> parent);
- void get_state(uint64 query_id);
-
- void change_phone_number(uint64 query_id, string phone_number, bool allow_flash_call, bool is_current_phone_number);
- void resend_authentication_code(uint64 query_id);
- void check_code(uint64 query_id, string code);
-
- private:
- enum class State { Ok, WaitCode } state_ = State::Ok;
- enum class NetQueryType { None, SendCode, ChangePhone };
-
- ActorShared<> parent_;
- uint64 query_id_ = 0;
- uint64 net_query_id_ = 0;
- NetQueryType net_query_type_;
-
- SendCodeHelper send_code_helper_;
-
- void on_new_query(uint64 query_id);
- void on_query_error(Status status);
- void on_query_error(uint64 id, Status status);
- void on_query_ok();
- void start_net_query(NetQueryType net_query_type, NetQueryPtr net_query);
-
- void on_change_phone_result(NetQueryPtr &result);
- void on_send_code_result(NetQueryPtr &result);
- void on_result(NetQueryPtr result) override;
- void tear_down() override;
-};
-class AuthManager : public NetActor {
+class AuthManager final : public NetActor {
public:
AuthManager(int32 api_id, const string &api_hash, ActorShared<> parent);
bool is_bot() const;
- void set_is_bot(bool is_bot);
bool is_authorized() const;
+ bool was_authorized() const;
void get_state(uint64 query_id);
- void set_phone_number(uint64 query_id, string phone_number, bool allow_flash_call, bool is_current_phone_number);
+ void set_phone_number(uint64 query_id, string phone_number,
+ td_api::object_ptr<td_api::phoneNumberAuthenticationSettings> settings);
+ void set_email_address(uint64 query_id, string email_address);
void resend_authentication_code(uint64 query_id);
- void check_code(uint64 query_id, string code, string first_name, string last_name);
+ void check_email_code(uint64 query_id, EmailVerification &&code);
+ void check_code(uint64 query_id, string code);
+ void register_user(uint64 query_id, string first_name, string last_name);
+ void request_qr_code_authentication(uint64 query_id, vector<UserId> other_user_ids);
void check_bot_token(uint64 query_id, string bot_token);
void check_password(uint64 query_id, string password);
void request_password_recovery(uint64 query_id);
- void recover_password(uint64 query_id, string code);
- void logout(uint64 query_id);
- void delete_account(uint64 query_id, const string &reason);
+ void check_password_recovery_code(uint64 query_id, string code);
+ void recover_password(uint64 query_id, string code, string new_password, string new_hint);
+ void log_out(uint64 query_id);
+ void delete_account(uint64 query_id, string reason, string password);
+
+ void on_update_login_token();
+
+ void on_authorization_lost(string source);
+ void on_closing(bool destroy_flag);
- void on_closing();
+ // can return nullptr if state isn't initialized yet
+ tl_object_ptr<td_api::AuthorizationState> get_current_authorization_state_object() const;
private:
- static constexpr size_t MAX_NAME_LENGTH = 255; // server side limit
+ static constexpr size_t MAX_NAME_LENGTH = 64; // server side limit
enum class State : int32 {
None,
WaitPhoneNumber,
WaitCode,
+ WaitQrCodeConfirmation,
WaitPassword,
+ WaitRegistration,
+ WaitEmailAddress,
+ WaitEmailCode,
Ok,
LoggingOut,
+ DestroyingKeys,
Closing
} state_ = State::None;
- enum class NetQueryType {
+ enum class NetQueryType : int32 {
None,
SignIn,
SignUp,
SendCode,
+ SendEmailCode,
+ VerifyEmailAddress,
+ RequestQrCode,
+ ImportQrCode,
GetPassword,
CheckPassword,
RequestPasswordRecovery,
+ CheckPasswordRecoveryCode,
RecoverPassword,
BotAuthentication,
Authentication,
@@ -166,54 +97,129 @@ class AuthManager : public NetActor {
};
struct WaitPasswordState {
- string current_salt_;
- string new_salt_;
+ string current_client_salt_;
+ string current_server_salt_;
+ int32 srp_g_ = 0;
+ string srp_p_;
+ string srp_B_;
+ int64 srp_id_ = 0;
string hint_;
- bool has_recovery_;
+ bool has_recovery_ = false;
string email_address_pattern_;
- template <class T>
- void store(T &storer) const;
- template <class T>
- void parse(T &parser);
+ template <class StorerT>
+ void store(StorerT &storer) const;
+ template <class ParserT>
+ void parse(ParserT &parser);
};
struct DbState {
State state_;
int32 api_id_;
string api_hash_;
- Timestamp state_timestamp_;
+ double expires_at_;
+
+ // WaitEmailAddress and WaitEmailCode
+ bool allow_apple_id_ = false;
+ bool allow_google_id_ = false;
+
+ // WaitEmailCode
+ string email_address_;
+ SentEmailCode email_code_info_;
+ int32 next_phone_number_login_date_ = 0;
- // WaitCode
+ // WaitEmailAddress, WaitEmailCode, WaitCode and WaitRegistration
SendCodeHelper send_code_helper_;
- //WaitPassword
+ // WaitQrCodeConfirmation
+ vector<UserId> other_user_ids_;
+ string login_token_;
+ double login_token_expires_at_ = 0;
+
+ // WaitPassword
WaitPasswordState wait_password_state_;
+ // WaitRegistration
+ TermsOfService terms_of_service_;
+
+ DbState() = default;
+
+ static DbState wait_email_address(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id,
+ SendCodeHelper send_code_helper) {
+ DbState state(State::WaitEmailAddress, api_id, std::move(api_hash));
+ state.send_code_helper_ = std::move(send_code_helper);
+ state.allow_apple_id_ = allow_apple_id;
+ state.allow_google_id_ = allow_google_id;
+ return state;
+ }
+
+ static DbState wait_email_code(int32 api_id, string api_hash, bool allow_apple_id, bool allow_google_id,
+ string email_address, SentEmailCode email_code_info,
+ int32 next_phone_number_login_date, SendCodeHelper send_code_helper) {
+ DbState state(State::WaitEmailCode, api_id, std::move(api_hash));
+ state.send_code_helper_ = std::move(send_code_helper);
+ state.allow_apple_id_ = allow_apple_id;
+ state.allow_google_id_ = allow_google_id;
+ state.email_address_ = std::move(email_address);
+ state.email_code_info_ = std::move(email_code_info);
+ state.next_phone_number_login_date_ = next_phone_number_login_date;
+ return state;
+ }
+
static DbState wait_code(int32 api_id, string api_hash, SendCodeHelper send_code_helper) {
- DbState state;
- state.state_ = State::WaitCode;
- state.api_id_ = api_id;
- state.api_hash_ = api_hash;
+ DbState state(State::WaitCode, api_id, std::move(api_hash));
state.send_code_helper_ = std::move(send_code_helper);
- state.state_timestamp_ = Timestamp::now();
+ return state;
+ }
+
+ static DbState wait_qr_code_confirmation(int32 api_id, string api_hash, vector<UserId> other_user_ids,
+ string login_token, double login_token_expires_at) {
+ DbState state(State::WaitQrCodeConfirmation, api_id, std::move(api_hash));
+ state.other_user_ids_ = std::move(other_user_ids);
+ state.login_token_ = std::move(login_token);
+ state.login_token_expires_at_ = login_token_expires_at;
return state;
}
static DbState wait_password(int32 api_id, string api_hash, WaitPasswordState wait_password_state) {
- DbState state;
- state.state_ = State::WaitPassword;
- state.api_id_ = api_id;
- state.api_hash_ = api_hash;
+ DbState state(State::WaitPassword, api_id, std::move(api_hash));
state.wait_password_state_ = std::move(wait_password_state);
- state.state_timestamp_ = Timestamp::now();
return state;
}
- template <class T>
- void store(T &storer) const;
- template <class T>
- void parse(T &parser);
+ static DbState wait_registration(int32 api_id, string api_hash, SendCodeHelper send_code_helper,
+ TermsOfService terms_of_service) {
+ DbState state(State::WaitRegistration, api_id, std::move(api_hash));
+ state.send_code_helper_ = std::move(send_code_helper);
+ state.terms_of_service_ = std::move(terms_of_service);
+ return state;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+ template <class ParserT>
+ void parse(ParserT &parser);
+
+ private:
+ DbState(State state, int32 api_id, string &&api_hash)
+ : state_(state), api_id_(api_id), api_hash_(std::move(api_hash)) {
+ auto state_timeout = [state] {
+ switch (state) {
+ case State::WaitPassword:
+ case State::WaitRegistration:
+ return 86400;
+ case State::WaitEmailAddress:
+ case State::WaitEmailCode:
+ case State::WaitCode:
+ case State::WaitQrCodeConfirmation:
+ return 5 * 60;
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+ }();
+ expires_at_ = Time::now() + state_timeout;
+ }
};
bool load_state();
@@ -226,43 +232,95 @@ class AuthManager : public NetActor {
int32 api_id_;
string api_hash_;
+ // State::WaitEmailAddress
+ bool allow_apple_id_ = false;
+ bool allow_google_id_ = false;
+
+ // State::WaitEmailCode
+ string email_address_;
+ SentEmailCode email_code_info_;
+ int32 next_phone_number_login_date_ = 0;
+ EmailVerification email_code_;
+
// State::WaitCode
SendCodeHelper send_code_helper_;
+ string code_;
+
+ // State::WaitQrCodeConfirmation
+ vector<UserId> other_user_ids_;
+ string login_token_;
+ double login_token_expires_at_ = 0.0;
+ int32 imported_dc_id_ = -1;
+
+ // State::WaitPassword
+ string password_;
+
+ // State::WaitRegistration
+ TermsOfService terms_of_service_;
// for bots
string bot_token_;
+
uint64 query_id_ = 0;
WaitPasswordState wait_password_state_;
+ string recovery_code_;
+ string new_password_;
+ string new_hint_;
+
+ int32 login_code_retry_delay_ = 0;
+ Timeout poll_export_login_code_timeout_;
+
+ bool was_qr_code_request_ = false;
bool was_check_bot_token_ = false;
bool is_bot_ = false;
uint64 net_query_id_ = 0;
- NetQueryType net_query_type_;
+ NetQueryType net_query_type_ = NetQueryType::None;
vector<uint64> pending_get_authorization_state_requests_;
void on_new_query(uint64 query_id);
void on_query_error(Status status);
- void on_query_error(uint64 id, Status status);
void on_query_ok();
void start_net_query(NetQueryType net_query_type, NetQueryPtr net_query);
+ static void on_update_login_token_static(void *td);
+ void send_export_login_token_query();
+ void set_login_token_expires_at(double login_token_expires_at);
+
+ void do_delete_account(uint64 query_id, string reason,
+ Result<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> r_input_password);
+
+ void send_auth_sign_in_query();
+ void send_log_out_query();
+ void destroy_auth_keys();
+
+ void on_sent_code(telegram_api::object_ptr<telegram_api::auth_sentCode> &&sent_code);
+
void on_send_code_result(NetQueryPtr &result);
+ void on_send_email_code_result(NetQueryPtr &result);
+ void on_verify_email_address_result(NetQueryPtr &result);
+ void on_request_qr_code_result(NetQueryPtr &result, bool is_import);
void on_get_password_result(NetQueryPtr &result);
void on_request_password_recovery_result(NetQueryPtr &result);
- void on_authentication_result(NetQueryPtr &result, bool expected_flag);
+ void on_check_password_recovery_code_result(NetQueryPtr &result);
+ void on_authentication_result(NetQueryPtr &result, bool is_from_current_query);
void on_log_out_result(NetQueryPtr &result);
void on_delete_account_result(NetQueryPtr &result);
- void on_authorization(tl_object_ptr<telegram_api::auth_authorization> auth);
+ void on_get_login_token(tl_object_ptr<telegram_api::auth_LoginToken> login_token);
+ void on_get_authorization(tl_object_ptr<telegram_api::auth_Authorization> auth_ptr);
- void on_result(NetQueryPtr result) override;
+ void on_result(NetQueryPtr result) final;
void update_state(State new_state, bool force = false, bool should_save_state = true);
tl_object_ptr<td_api::AuthorizationState> get_authorization_state_object(State authorization_state) const;
- void send_ok(uint64 query_id);
- void start_up() override;
- void tear_down() override;
+ static void send_ok(uint64 query_id);
+ static void on_query_error(uint64 query_id, Status status);
+
+ void start_up() final;
+ void tear_down() final;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AuthManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/AuthManager.hpp
index 89027abd61..c6cf496d13 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/AuthManager.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/AuthManager.hpp
@@ -1,100 +1,164 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#pragma once
+
#include "td/telegram/AuthManager.h"
+#include "td/telegram/logevent/LogEventHelper.h"
+#include "td/telegram/SendCodeHelper.hpp"
+#include "td/telegram/Version.h"
+
+#include "td/utils/format.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/tl_helpers.h"
namespace td {
-template <class T>
-void SendCodeHelper::AuthenticationCodeInfo::store(T &storer) const {
- using td::store;
- store(type, storer);
- store(length, storer);
- store(pattern, storer);
-}
-template <class T>
-void SendCodeHelper::AuthenticationCodeInfo::parse(T &parser) {
- using td::parse;
- parse(type, parser);
- parse(length, parser);
- parse(pattern, parser);
-}
-template <class T>
-void SendCodeHelper::store(T &storer) const {
+template <class StorerT>
+void AuthManager::WaitPasswordState::store(StorerT &storer) const {
using td::store;
- store(phone_number_, storer);
- store(phone_registered_, storer);
- store(phone_code_hash_, storer);
- store(sent_code_info_, storer);
- store(next_code_info_, storer);
- store(next_code_timestamp_, storer);
-}
-
-template <class T>
-void SendCodeHelper::parse(T &parser) {
- using td::parse;
- parse(phone_number_, parser);
- parse(phone_registered_, parser);
- parse(phone_code_hash_, parser);
- parse(sent_code_info_, parser);
- parse(next_code_info_, parser);
- parse(next_code_timestamp_, parser);
-}
-template <class T>
-void AuthManager::WaitPasswordState::store(T &storer) const {
- using td::store;
- store(current_salt_, storer);
- store(new_salt_, storer);
+ store(current_client_salt_, storer);
+ store(current_server_salt_, storer);
+ store(srp_g_, storer);
+ store(srp_p_, storer);
+ store(srp_B_, storer);
+ store(srp_id_, storer);
store(hint_, storer);
store(has_recovery_, storer);
store(email_address_pattern_, storer);
}
-template <class T>
-void AuthManager::WaitPasswordState::parse(T &parser) {
+template <class ParserT>
+void AuthManager::WaitPasswordState::parse(ParserT &parser) {
using td::parse;
- parse(current_salt_, parser);
- parse(new_salt_, parser);
+ parse(current_client_salt_, parser);
+ parse(current_server_salt_, parser);
+ parse(srp_g_, parser);
+ parse(srp_p_, parser);
+ parse(srp_B_, parser);
+ parse(srp_id_, parser);
parse(hint_, parser);
parse(has_recovery_, parser);
parse(email_address_pattern_, parser);
}
-template <class T>
-void AuthManager::DbState::store(T &storer) const {
+template <class StorerT>
+void AuthManager::DbState::store(StorerT &storer) const {
using td::store;
+ bool has_terms_of_service = !terms_of_service_.get_id().empty();
+ bool is_pbkdf2_supported = true;
+ bool is_srp_supported = true;
+ bool is_wait_registration_supported = true;
+ bool is_wait_registration_stores_phone_number = true;
+ bool is_wait_qr_code_confirmation_supported = true;
+ bool is_time_store_supported = true;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_terms_of_service);
+ STORE_FLAG(is_pbkdf2_supported);
+ STORE_FLAG(is_srp_supported);
+ STORE_FLAG(is_wait_registration_supported);
+ STORE_FLAG(is_wait_registration_stores_phone_number);
+ STORE_FLAG(is_wait_qr_code_confirmation_supported);
+ STORE_FLAG(allow_apple_id_);
+ STORE_FLAG(allow_google_id_);
+ STORE_FLAG(is_time_store_supported);
+ END_STORE_FLAGS();
store(state_, storer);
store(api_id_, storer);
store(api_hash_, storer);
- store(state_timestamp_, storer);
+ store_time(expires_at_, storer);
+
+ if (has_terms_of_service) {
+ store(terms_of_service_, storer);
+ }
- if (state_ == State::WaitCode) {
+ if (state_ == State::WaitEmailAddress) {
store(send_code_helper_, storer);
+ } else if (state_ == State::WaitEmailCode) {
+ store(send_code_helper_, storer);
+ store(email_address_, storer);
+ store(email_code_info_, storer);
+ store(next_phone_number_login_date_, storer);
+ } else if (state_ == State::WaitCode) {
+ store(send_code_helper_, storer);
+ } else if (state_ == State::WaitQrCodeConfirmation) {
+ store(other_user_ids_, storer);
+ store(login_token_, storer);
+ store_time(login_token_expires_at_, storer);
} else if (state_ == State::WaitPassword) {
store(wait_password_state_, storer);
+ } else if (state_ == State::WaitRegistration) {
+ store(send_code_helper_, storer);
} else {
UNREACHABLE();
}
}
-template <class T>
-void AuthManager::DbState::parse(T &parser) {
+
+template <class ParserT>
+void AuthManager::DbState::parse(ParserT &parser) {
using td::parse;
+ bool has_terms_of_service = false;
+ bool is_pbkdf2_supported = false;
+ bool is_srp_supported = false;
+ bool is_wait_registration_supported = false;
+ bool is_wait_registration_stores_phone_number = false;
+ bool is_wait_qr_code_confirmation_supported = false;
+ bool is_time_store_supported = false;
+ if (parser.version() >= static_cast<int32>(Version::AddTermsOfService)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_terms_of_service);
+ PARSE_FLAG(is_pbkdf2_supported);
+ PARSE_FLAG(is_srp_supported);
+ PARSE_FLAG(is_wait_registration_supported);
+ PARSE_FLAG(is_wait_registration_stores_phone_number);
+ PARSE_FLAG(is_wait_qr_code_confirmation_supported);
+ PARSE_FLAG(allow_apple_id_);
+ PARSE_FLAG(allow_google_id_);
+ PARSE_FLAG(is_time_store_supported);
+ END_PARSE_FLAGS();
+ }
+ if (!is_time_store_supported) {
+ return parser.set_error("Have no time store support");
+ }
+ CHECK(is_pbkdf2_supported);
+ CHECK(is_srp_supported);
+ CHECK(is_wait_registration_supported);
+ CHECK(is_wait_registration_stores_phone_number);
+ CHECK(is_wait_qr_code_confirmation_supported);
+
parse(state_, parser);
parse(api_id_, parser);
parse(api_hash_, parser);
- parse(state_timestamp_, parser);
+ parse_time(expires_at_, parser);
- if (state_ == State::WaitCode) {
+ if (has_terms_of_service) {
+ parse(terms_of_service_, parser);
+ }
+
+ if (state_ == State::WaitEmailAddress) {
+ parse(send_code_helper_, parser);
+ } else if (state_ == State::WaitEmailCode) {
parse(send_code_helper_, parser);
+ parse(email_address_, parser);
+ parse(email_code_info_, parser);
+ parse(next_phone_number_login_date_, parser);
+ } else if (state_ == State::WaitCode) {
+ parse(send_code_helper_, parser);
+ } else if (state_ == State::WaitQrCodeConfirmation) {
+ parse(other_user_ids_, parser);
+ parse(login_token_, parser);
+ parse_time(login_token_expires_at_, parser);
} else if (state_ == State::WaitPassword) {
parse(wait_password_state_, parser);
+ } else if (state_ == State::WaitRegistration) {
+ parse(send_code_helper_, parser);
} else {
parser.set_error(PSTRING() << "Unexpected " << tag("state", static_cast<int32>(state_)));
}
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AutoDownloadSettings.cpp b/protocols/Telegram/tdlib/td/td/telegram/AutoDownloadSettings.cpp
new file mode 100644
index 0000000000..071bb8c674
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/AutoDownloadSettings.cpp
@@ -0,0 +1,144 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/AutoDownloadSettings.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/net/NetQueryCreator.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+static td_api::object_ptr<td_api::autoDownloadSettings> convert_auto_download_settings(
+ const telegram_api::object_ptr<telegram_api::autoDownloadSettings> &settings) {
+ CHECK(settings != nullptr);
+ auto flags = settings->flags_;
+ auto disabled = (flags & telegram_api::autoDownloadSettings::DISABLED_MASK) != 0;
+ auto video_preload_large = (flags & telegram_api::autoDownloadSettings::VIDEO_PRELOAD_LARGE_MASK) != 0;
+ auto audio_preload_next = (flags & telegram_api::autoDownloadSettings::AUDIO_PRELOAD_NEXT_MASK) != 0;
+ auto phonecalls_less_data = (flags & telegram_api::autoDownloadSettings::PHONECALLS_LESS_DATA_MASK) != 0;
+ constexpr int32 MAX_PHOTO_SIZE = 10 * (1 << 20) /* 10 MB */;
+ constexpr int64 MAX_DOCUMENT_SIZE = (static_cast<int64>(1) << 52);
+ return td_api::make_object<td_api::autoDownloadSettings>(
+ !disabled, clamp(settings->photo_size_max_, static_cast<int32>(0), MAX_PHOTO_SIZE),
+ clamp(settings->video_size_max_, static_cast<int64>(0), MAX_DOCUMENT_SIZE),
+ clamp(settings->file_size_max_, static_cast<int64>(0), MAX_DOCUMENT_SIZE), settings->video_upload_maxbitrate_,
+ video_preload_large, audio_preload_next, phonecalls_less_data);
+}
+
+class GetAutoDownloadSettingsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::autoDownloadSettingsPresets>> promise_;
+
+ public:
+ explicit GetAutoDownloadSettingsQuery(Promise<td_api::object_ptr<td_api::autoDownloadSettingsPresets>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_getAutoDownloadSettings()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getAutoDownloadSettings>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto settings = result_ptr.move_as_ok();
+ promise_.set_value(td_api::make_object<td_api::autoDownloadSettingsPresets>(
+ convert_auto_download_settings(settings->low_), convert_auto_download_settings(settings->medium_),
+ convert_auto_download_settings(settings->high_)));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+telegram_api::object_ptr<telegram_api::autoDownloadSettings> get_input_auto_download_settings(
+ const AutoDownloadSettings &settings) {
+ int32 flags = 0;
+ if (!settings.is_enabled) {
+ flags |= telegram_api::autoDownloadSettings::DISABLED_MASK;
+ }
+ if (settings.preload_large_videos) {
+ flags |= telegram_api::autoDownloadSettings::VIDEO_PRELOAD_LARGE_MASK;
+ }
+ if (settings.preload_next_audio) {
+ flags |= telegram_api::autoDownloadSettings::AUDIO_PRELOAD_NEXT_MASK;
+ }
+ if (settings.use_less_data_for_calls) {
+ flags |= telegram_api::autoDownloadSettings::PHONECALLS_LESS_DATA_MASK;
+ }
+ return telegram_api::make_object<telegram_api::autoDownloadSettings>(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, settings.max_photo_file_size,
+ settings.max_video_file_size, settings.max_other_file_size, settings.video_upload_bitrate);
+}
+
+class SaveAutoDownloadSettingsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SaveAutoDownloadSettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(NetType type, const AutoDownloadSettings &settings) {
+ int32 flags = 0;
+ if (type == NetType::MobileRoaming) {
+ flags |= telegram_api::account_saveAutoDownloadSettings::LOW_MASK;
+ }
+ if (type == NetType::WiFi) {
+ flags |= telegram_api::account_saveAutoDownloadSettings::HIGH_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::account_saveAutoDownloadSettings(
+ flags, false /*ignored*/, false /*ignored*/, get_input_auto_download_settings(settings))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_saveAutoDownloadSettings>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ LOG(INFO) << "Receive result for SaveAutoDownloadSettingsQuery: " << result_ptr.ok();
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+AutoDownloadSettings get_auto_download_settings(const td_api::object_ptr<td_api::autoDownloadSettings> &settings) {
+ CHECK(settings != nullptr);
+ AutoDownloadSettings result;
+ result.max_photo_file_size = settings->max_photo_file_size_;
+ result.max_video_file_size = settings->max_video_file_size_;
+ result.max_other_file_size = settings->max_other_file_size_;
+ result.video_upload_bitrate = settings->video_upload_bitrate_;
+ result.is_enabled = settings->is_auto_download_enabled_;
+ result.preload_large_videos = settings->preload_large_videos_;
+ result.preload_next_audio = settings->preload_next_audio_;
+ result.use_less_data_for_calls = settings->use_less_data_for_calls_;
+ return result;
+}
+
+void get_auto_download_settings_presets(Td *td,
+ Promise<td_api::object_ptr<td_api::autoDownloadSettingsPresets>> &&promise) {
+ td->create_handler<GetAutoDownloadSettingsQuery>(std::move(promise))->send();
+}
+
+void set_auto_download_settings(Td *td, NetType type, AutoDownloadSettings settings, Promise<Unit> &&promise) {
+ td->create_handler<SaveAutoDownloadSettingsQuery>(std::move(promise))->send(type, settings);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/AutoDownloadSettings.h b/protocols/Telegram/tdlib/td/td/telegram/AutoDownloadSettings.h
new file mode 100644
index 0000000000..c77185829f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/AutoDownloadSettings.h
@@ -0,0 +1,38 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/net/NetType.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+
+namespace td {
+
+class Td;
+
+class AutoDownloadSettings {
+ public:
+ int32 max_photo_file_size = 0;
+ int64 max_video_file_size = 0;
+ int64 max_other_file_size = 0;
+ int32 video_upload_bitrate = 0;
+ bool is_enabled = false;
+ bool preload_large_videos = false;
+ bool preload_next_audio = false;
+ bool use_less_data_for_calls = false;
+};
+
+AutoDownloadSettings get_auto_download_settings(const td_api::object_ptr<td_api::autoDownloadSettings> &settings);
+
+void get_auto_download_settings_presets(Td *td,
+ Promise<td_api::object_ptr<td_api::autoDownloadSettingsPresets>> &&promise);
+
+void set_auto_download_settings(Td *td, NetType type, AutoDownloadSettings settings, Promise<Unit> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BackgroundId.h b/protocols/Telegram/tdlib/td/td/telegram/BackgroundId.h
new file mode 100644
index 0000000000..b44a466d4e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BackgroundId.h
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+#include <type_traits>
+
+namespace td {
+
+class BackgroundId {
+ int64 id = 0;
+
+ public:
+ BackgroundId() = default;
+
+ explicit BackgroundId(int64 background_id) : id(background_id) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
+ BackgroundId(T background_id) = delete;
+
+ int64 get() const {
+ return id;
+ }
+
+ bool operator==(const BackgroundId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const BackgroundId &other) const {
+ return id != other.id;
+ }
+
+ bool is_valid() const {
+ return id != 0;
+ }
+
+ bool is_local() const {
+ return 0 < id && id <= 0x7FFFFFFF;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(id, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(id, parser);
+ }
+};
+
+struct BackgroundIdHash {
+ uint32 operator()(BackgroundId background_id) const {
+ return Hash<int64>()(background_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, BackgroundId background_id) {
+ return string_builder << "background " << background_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BackgroundManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/BackgroundManager.cpp
new file mode 100644
index 0000000000..653751203a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BackgroundManager.cpp
@@ -0,0 +1,1273 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/BackgroundManager.h"
+
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/BackgroundType.hpp"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/DocumentsManager.hpp"
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+
+#include "td/db/SqliteKeyValueAsync.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+#include <algorithm>
+
+namespace td {
+
+class GetBackgroundQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ BackgroundId background_id_;
+ string background_name_;
+
+ public:
+ explicit GetBackgroundQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(BackgroundId background_id, const string &background_name,
+ telegram_api::object_ptr<telegram_api::InputWallPaper> &&input_wallpaper) {
+ background_id_ = background_id;
+ background_name_ = background_name;
+ send_query(G()->net_query_creator().create(telegram_api::account_getWallPaper(std::move(input_wallpaper))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getWallPaper>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ td_->background_manager_->on_get_background(background_id_, background_name_, result_ptr.move_as_ok(), true);
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for GetBackgroundQuery for " << background_id_ << "/" << background_name_ << ": "
+ << status;
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetBackgroundsQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::account_WallPapers>> promise_;
+
+ public:
+ explicit GetBackgroundsQuery(Promise<telegram_api::object_ptr<telegram_api::account_WallPapers>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_getWallPapers(0)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getWallPapers>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class InstallBackgroundQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit InstallBackgroundQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(telegram_api::object_ptr<telegram_api::InputWallPaper> input_wallpaper, const BackgroundType &type) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::account_installWallPaper(std::move(input_wallpaper), type.get_input_wallpaper_settings())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_installWallPaper>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ LOG_IF(INFO, !result_ptr.ok()) << "Receive false from account.installWallPaper";
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class UploadBackgroundQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::background>> promise_;
+ FileId file_id_;
+ BackgroundType type_;
+ bool for_dark_theme_;
+
+ public:
+ explicit UploadBackgroundQuery(Promise<td_api::object_ptr<td_api::background>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(FileId file_id, tl_object_ptr<telegram_api::InputFile> &&input_file, const BackgroundType &type,
+ bool for_dark_theme) {
+ CHECK(input_file != nullptr);
+ file_id_ = file_id;
+ type_ = type;
+ for_dark_theme_ = for_dark_theme;
+ send_query(G()->net_query_creator().create(telegram_api::account_uploadWallPaper(
+ std::move(input_file), type_.get_mime_type(), type_.get_input_wallpaper_settings())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_uploadWallPaper>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ td_->background_manager_->on_uploaded_background_file(file_id_, type_, for_dark_theme_, result_ptr.move_as_ok(),
+ std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ CHECK(status.is_error());
+ CHECK(file_id_.is_valid());
+ if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
+ // TODO td_->background_manager_->on_upload_background_file_part_missing(file_id_, to_integer<int32>(status.message().substr(10)));
+ // return;
+ } else {
+ if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) {
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+ }
+ }
+ td_->file_manager_->cancel_upload(file_id_);
+ promise_.set_error(std::move(status));
+ }
+};
+
+class UnsaveBackgroundQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit UnsaveBackgroundQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(telegram_api::object_ptr<telegram_api::InputWallPaper> input_wallpaper) {
+ send_query(G()->net_query_creator().create(telegram_api::account_saveWallPaper(
+ std::move(input_wallpaper), true, telegram_api::make_object<telegram_api::wallPaperSettings>())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_saveWallPaper>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for save background: " << result;
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for save background: " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ResetBackgroundsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ResetBackgroundsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_resetWallPapers()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_resetWallPapers>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for reset backgrounds: " << result;
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for reset backgrounds: " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class BackgroundManager::UploadBackgroundFileCallback final : public FileManager::UploadCallback {
+ public:
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
+ send_closure_later(G()->background_manager(), &BackgroundManager::on_upload_background_file, file_id,
+ std::move(input_file));
+ }
+
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
+ UNREACHABLE();
+ }
+
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
+ UNREACHABLE();
+ }
+
+ void on_upload_error(FileId file_id, Status error) final {
+ send_closure_later(G()->background_manager(), &BackgroundManager::on_upload_background_file_error, file_id,
+ std::move(error));
+ }
+};
+
+BackgroundManager::BackgroundManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+ upload_background_file_callback_ = std::make_shared<UploadBackgroundFileCallback>();
+}
+
+template <class StorerT>
+void BackgroundManager::Background::store(StorerT &storer) const {
+ bool has_file_id = file_id.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_creator);
+ STORE_FLAG(is_default);
+ STORE_FLAG(is_dark);
+ STORE_FLAG(has_file_id);
+ STORE_FLAG(has_new_local_id);
+ END_STORE_FLAGS();
+ td::store(id, storer);
+ td::store(access_hash, storer);
+ td::store(name, storer);
+ if (has_file_id) {
+ storer.context()->td().get_actor_unsafe()->documents_manager_->store_document(file_id, storer);
+ }
+ td::store(type, storer);
+}
+
+template <class ParserT>
+void BackgroundManager::Background::parse(ParserT &parser) {
+ bool has_file_id;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_creator);
+ PARSE_FLAG(is_default);
+ PARSE_FLAG(is_dark);
+ PARSE_FLAG(has_file_id);
+ PARSE_FLAG(has_new_local_id);
+ END_PARSE_FLAGS();
+ td::parse(id, parser);
+ td::parse(access_hash, parser);
+ td::parse(name, parser);
+ if (has_file_id) {
+ file_id = parser.context()->td().get_actor_unsafe()->documents_manager_->parse_document(parser);
+ } else {
+ file_id = FileId();
+ }
+ td::parse(type, parser);
+}
+
+class BackgroundManager::BackgroundLogEvent {
+ public:
+ Background background_;
+ BackgroundType set_type_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(background_, storer);
+ td::store(set_type_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(background_, parser);
+ td::parse(set_type_, parser);
+ }
+};
+
+class BackgroundManager::BackgroundsLogEvent {
+ public:
+ vector<Background> backgrounds_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(backgrounds_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(backgrounds_, parser);
+ }
+};
+
+void BackgroundManager::start_up() {
+ max_local_background_id_ = BackgroundId(to_integer<int64>(G()->td_db()->get_binlog_pmc()->get("max_bg_id")));
+
+ // first parse all log events and fix max_local_background_id_ value
+ bool has_selected_background[2] = {false, false};
+ BackgroundLogEvent selected_background_log_event[2];
+ for (int i = 0; i < 2; i++) {
+ bool for_dark_theme = i != 0;
+ auto log_event_string = G()->td_db()->get_binlog_pmc()->get(get_background_database_key(for_dark_theme));
+ if (!log_event_string.empty()) {
+ has_selected_background[i] = true;
+ log_event_parse(selected_background_log_event[i], log_event_string).ensure();
+ const Background &background = selected_background_log_event[i].background_;
+ if (background.has_new_local_id && background.id.is_local() && !background.type.has_file() &&
+ background.id.get() > max_local_background_id_.get()) {
+ set_max_local_background_id(background.id);
+ }
+ }
+ }
+
+ for (int i = 0; i < 2; i++) {
+ bool for_dark_theme = i != 0;
+ auto log_event_string = G()->td_db()->get_binlog_pmc()->get(get_local_backgrounds_database_key(for_dark_theme));
+ if (!log_event_string.empty()) {
+ BackgroundsLogEvent log_event;
+ log_event_parse(log_event, log_event_string).ensure();
+ for (const auto &background : log_event.backgrounds_) {
+ CHECK(background.has_new_local_id);
+ CHECK(background.id.is_valid());
+ CHECK(background.id.is_local());
+ CHECK(!background.type.has_file());
+ CHECK(!background.file_id.is_valid());
+ if (background.id.get() > max_local_background_id_.get()) {
+ set_max_local_background_id(background.id);
+ }
+ add_background(background, true);
+ local_background_ids_[for_dark_theme].push_back(background.id);
+ }
+ }
+ }
+
+ // then add selected backgrounds fixing their ID
+ for (int i = 0; i < 2; i++) {
+ bool for_dark_theme = i != 0;
+ if (has_selected_background[i]) {
+ Background &background = selected_background_log_event[i].background_;
+
+ bool need_resave = false;
+ if (!background.has_new_local_id && !background.type.has_file()) {
+ background.has_new_local_id = true;
+ background.id = get_next_local_background_id();
+ need_resave = true;
+ }
+
+ CHECK(background.id.is_valid());
+ if (background.file_id.is_valid() != background.type.has_file()) {
+ LOG(ERROR) << "Failed to load " << background.id << " of " << background.type;
+ need_resave = true;
+ } else {
+ set_background_id_[for_dark_theme] = background.id;
+ set_background_type_[for_dark_theme] = selected_background_log_event[i].set_type_;
+
+ add_background(background, false);
+ }
+
+ if (need_resave) {
+ save_background_id(for_dark_theme);
+ }
+ }
+
+ send_update_selected_background(for_dark_theme);
+ }
+}
+
+void BackgroundManager::tear_down() {
+ parent_.reset();
+}
+
+void BackgroundManager::store_background(BackgroundId background_id, LogEventStorerCalcLength &storer) {
+ const auto *background = get_background(background_id);
+ CHECK(background != nullptr);
+ store(*background, storer);
+}
+
+void BackgroundManager::store_background(BackgroundId background_id, LogEventStorerUnsafe &storer) {
+ const auto *background = get_background(background_id);
+ CHECK(background != nullptr);
+ store(*background, storer);
+}
+
+void BackgroundManager::parse_background(BackgroundId &background_id, LogEventParser &parser) {
+ Background background;
+ parse(background, parser);
+ CHECK(background.has_new_local_id);
+ if (background.file_id.is_valid() != background.type.has_file() || !background.id.is_valid()) {
+ parser.set_error(PSTRING() << "Failed to load " << background.id);
+ background_id = BackgroundId();
+ return;
+ }
+ if (background.id.is_local() && !background.type.has_file() && background.id.get() > max_local_background_id_.get()) {
+ set_max_local_background_id(background.id);
+ }
+ background_id = background.id;
+ add_background(background, false);
+}
+
+void BackgroundManager::get_backgrounds(bool for_dark_theme,
+ Promise<td_api::object_ptr<td_api::backgrounds>> &&promise) {
+ pending_get_backgrounds_queries_.emplace_back(for_dark_theme, std::move(promise));
+ if (pending_get_backgrounds_queries_.size() == 1) {
+ auto request_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<telegram_api::object_ptr<telegram_api::account_WallPapers>> result) {
+ send_closure(actor_id, &BackgroundManager::on_get_backgrounds, std::move(result));
+ });
+
+ td_->create_handler<GetBackgroundsQuery>(std::move(request_promise))->send();
+ }
+}
+
+void BackgroundManager::reload_background_from_server(
+ BackgroundId background_id, const string &background_name,
+ telegram_api::object_ptr<telegram_api::InputWallPaper> &&input_wallpaper, Promise<Unit> &&promise) const {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ td_->create_handler<GetBackgroundQuery>(std::move(promise))
+ ->send(background_id, background_name, std::move(input_wallpaper));
+}
+
+void BackgroundManager::reload_background(BackgroundId background_id, int64 access_hash, Promise<Unit> &&promise) {
+ reload_background_from_server(
+ background_id, string(),
+ telegram_api::make_object<telegram_api::inputWallPaper>(background_id.get(), access_hash), std::move(promise));
+}
+
+static bool is_background_name_local(Slice name) {
+ return name.size() <= 13u || name.find('?') <= 13u || !is_base64url_characters(name.substr(0, name.find('?')));
+}
+
+std::pair<BackgroundId, BackgroundType> BackgroundManager::search_background(const string &name,
+ Promise<Unit> &&promise) {
+ auto params_pos = name.find('?');
+ string slug = params_pos >= name.size() ? name : name.substr(0, params_pos);
+ auto it = name_to_background_id_.find(slug);
+ if (it != name_to_background_id_.end()) {
+ CHECK(!is_background_name_local(slug));
+
+ const auto *background = get_background(it->second);
+ CHECK(background != nullptr);
+ promise.set_value(Unit());
+ BackgroundType type = background->type;
+ type.apply_parameters_from_link(name);
+ return {it->second, std::move(type)};
+ }
+
+ if (slug.empty()) {
+ promise.set_error(Status::Error(400, "Background name must be non-empty"));
+ return {};
+ }
+
+ if (is_background_name_local(slug)) {
+ auto r_type = BackgroundType::get_local_background_type(name);
+ if (r_type.is_error()) {
+ promise.set_error(r_type.move_as_error());
+ return {};
+ }
+ auto background_id = add_local_background(r_type.ok());
+ promise.set_value(Unit());
+ return {background_id, r_type.ok()};
+ }
+
+ if (G()->parameters().use_file_db && loaded_from_database_backgrounds_.count(slug) == 0) {
+ auto &queries = being_loaded_from_database_backgrounds_[slug];
+ queries.push_back(std::move(promise));
+ if (queries.size() == 1) {
+ LOG(INFO) << "Trying to load background " << slug << " from database";
+ G()->td_db()->get_sqlite_pmc()->get(
+ get_background_name_database_key(slug), PromiseCreator::lambda([slug](string value) mutable {
+ send_closure(G()->background_manager(), &BackgroundManager::on_load_background_from_database,
+ std::move(slug), std::move(value));
+ }));
+ }
+ return {};
+ }
+
+ reload_background_from_server(BackgroundId(), slug, telegram_api::make_object<telegram_api::inputWallPaperSlug>(slug),
+ std::move(promise));
+ return {};
+}
+
+void BackgroundManager::on_load_background_from_database(string name, string value) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto promises_it = being_loaded_from_database_backgrounds_.find(name);
+ CHECK(promises_it != being_loaded_from_database_backgrounds_.end());
+ auto promises = std::move(promises_it->second);
+ CHECK(!promises.empty());
+ being_loaded_from_database_backgrounds_.erase(promises_it);
+
+ loaded_from_database_backgrounds_.insert(name);
+
+ CHECK(!is_background_name_local(name));
+ if (name_to_background_id_.count(name) == 0 && !value.empty()) {
+ LOG(INFO) << "Successfully loaded background " << name << " of size " << value.size() << " from database";
+ Background background;
+ auto status = log_event_parse(background, value);
+ if (status.is_error() || !background.type.has_file() || !background.file_id.is_valid() ||
+ !background.id.is_valid()) {
+ LOG(ERROR) << "Can't load background " << name << ": " << status << ' ' << format::as_hex_dump<4>(Slice(value));
+ } else {
+ if (background.name != name) {
+ LOG(ERROR) << "Expected background " << name << ", but received " << background.name;
+ name_to_background_id_.emplace(std::move(name), background.id);
+ }
+ add_background(background, false);
+ }
+ }
+
+ set_promises(promises);
+}
+
+td_api::object_ptr<td_api::updateSelectedBackground> BackgroundManager::get_update_selected_background_object(
+ bool for_dark_theme) const {
+ return td_api::make_object<td_api::updateSelectedBackground>(
+ for_dark_theme,
+ get_background_object(set_background_id_[for_dark_theme], for_dark_theme, &set_background_type_[for_dark_theme]));
+}
+
+void BackgroundManager::send_update_selected_background(bool for_dark_theme) const {
+ send_closure(G()->td(), &Td::send_update, get_update_selected_background_object(for_dark_theme));
+}
+
+Result<FileId> BackgroundManager::prepare_input_file(const tl_object_ptr<td_api::InputFile> &input_file) {
+ auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Background, input_file, {}, false, false);
+ if (r_file_id.is_error()) {
+ return Status::Error(400, r_file_id.error().message());
+ }
+ auto file_id = r_file_id.move_as_ok();
+
+ FileView file_view = td_->file_manager_->get_file_view(file_id);
+ if (file_view.is_encrypted()) {
+ return Status::Error(400, "Can't use encrypted file");
+ }
+ if (!file_view.has_local_location() && !file_view.has_generate_location()) {
+ return Status::Error(400, "Need local or generate location to upload background");
+ }
+ return std::move(file_id);
+}
+
+void BackgroundManager::set_max_local_background_id(BackgroundId background_id) {
+ CHECK(background_id.is_local());
+ CHECK(background_id.get() > max_local_background_id_.get());
+ max_local_background_id_ = background_id;
+ G()->td_db()->get_binlog_pmc()->set("max_bg_id", to_string(max_local_background_id_.get()));
+}
+
+BackgroundId BackgroundManager::get_next_local_background_id() {
+ set_max_local_background_id(BackgroundId(max_local_background_id_.get() + 1));
+ return max_local_background_id_;
+}
+
+BackgroundId BackgroundManager::add_local_background(const BackgroundType &type) {
+ Background background;
+ background.id = get_next_local_background_id();
+ background.is_creator = true;
+ background.is_default = false;
+ background.is_dark = type.is_dark();
+ background.type = type;
+ background.name = type.get_link();
+ add_background(background, true);
+
+ return background.id;
+}
+
+void BackgroundManager::set_background(const td_api::InputBackground *input_background,
+ const td_api::BackgroundType *background_type, bool for_dark_theme,
+ Promise<td_api::object_ptr<td_api::background>> &&promise) {
+ BackgroundType type;
+ if (background_type != nullptr) {
+ auto r_type = BackgroundType::get_background_type(background_type);
+ if (r_type.is_error()) {
+ return promise.set_error(r_type.move_as_error());
+ }
+ type = r_type.move_as_ok();
+ } else {
+ CHECK(!type.has_file());
+ }
+
+ if (input_background == nullptr) {
+ if (background_type == nullptr) {
+ set_background_id(BackgroundId(), BackgroundType(), for_dark_theme);
+ return promise.set_value(nullptr);
+ }
+ if (type.has_file()) {
+ return promise.set_error(Status::Error(400, "Input background must be non-empty for the background type"));
+ }
+
+ auto background_id = add_local_background(type);
+ set_background_id(background_id, type, for_dark_theme);
+
+ local_background_ids_[for_dark_theme].insert(local_background_ids_[for_dark_theme].begin(), background_id);
+ save_local_backgrounds(for_dark_theme);
+
+ return promise.set_value(get_background_object(background_id, for_dark_theme, nullptr));
+ }
+
+ switch (input_background->get_id()) {
+ case td_api::inputBackgroundLocal::ID: {
+ if (!type.has_file()) {
+ return promise.set_error(Status::Error(400, "Can't specify local file for the background type"));
+ }
+ CHECK(background_type != nullptr);
+
+ auto background_local = static_cast<const td_api::inputBackgroundLocal *>(input_background);
+ auto r_file_id = prepare_input_file(background_local->background_);
+ if (r_file_id.is_error()) {
+ return promise.set_error(r_file_id.move_as_error());
+ }
+ auto file_id = r_file_id.move_as_ok();
+ LOG(INFO) << "Receive file " << file_id << " for input background";
+ CHECK(file_id.is_valid());
+
+ auto it = file_id_to_background_id_.find(file_id);
+ if (it != file_id_to_background_id_.end()) {
+ return set_background(it->second, type, for_dark_theme, std::move(promise));
+ }
+
+ upload_background_file(file_id, type, for_dark_theme, std::move(promise));
+ break;
+ }
+ case td_api::inputBackgroundRemote::ID: {
+ auto background_remote = static_cast<const td_api::inputBackgroundRemote *>(input_background);
+ return set_background(BackgroundId(background_remote->background_id_), std::move(type), for_dark_theme,
+ std::move(promise));
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+void BackgroundManager::set_background(BackgroundId background_id, BackgroundType type, bool for_dark_theme,
+ Promise<td_api::object_ptr<td_api::background>> &&promise) {
+ LOG(INFO) << "Set " << background_id << " with " << type;
+ const auto *background = get_background(background_id);
+ if (background == nullptr) {
+ return promise.set_error(Status::Error(400, "Background to set not found"));
+ }
+ if (!type.has_file()) {
+ type = background->type;
+ } else if (!background->type.has_equal_type(type)) {
+ return promise.set_error(Status::Error(400, "Background type mismatch"));
+ }
+ if (set_background_id_[for_dark_theme] == background_id && set_background_type_[for_dark_theme] == type) {
+ return promise.set_value(get_background_object(background_id, for_dark_theme, nullptr));
+ }
+
+ LOG(INFO) << "Install " << background_id << " with " << type;
+
+ if (!type.has_file()) {
+ set_background_id(background_id, type, for_dark_theme);
+ return promise.set_value(get_background_object(background_id, for_dark_theme, nullptr));
+ }
+
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), background_id, type, for_dark_theme,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ send_closure(actor_id, &BackgroundManager::on_installed_background, background_id, type, for_dark_theme,
+ std::move(result), std::move(promise));
+ });
+ td_->create_handler<InstallBackgroundQuery>(std::move(query_promise))
+ ->send(telegram_api::make_object<telegram_api::inputWallPaper>(background_id.get(), background->access_hash),
+ type);
+}
+
+void BackgroundManager::on_installed_background(BackgroundId background_id, BackgroundType type, bool for_dark_theme,
+ Result<Unit> &&result,
+ Promise<td_api::object_ptr<td_api::background>> &&promise) {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+
+ size_t i;
+ for (i = 0; i < installed_backgrounds_.size(); i++) {
+ if (installed_backgrounds_[i].first == background_id) {
+ installed_backgrounds_[i].second = type;
+ break;
+ }
+ }
+ if (i == installed_backgrounds_.size()) {
+ installed_backgrounds_.insert(installed_backgrounds_.begin(), {background_id, type});
+ }
+ set_background_id(background_id, type, for_dark_theme);
+ promise.set_value(get_background_object(background_id, for_dark_theme, nullptr));
+}
+
+string BackgroundManager::get_background_database_key(bool for_dark_theme) {
+ return for_dark_theme ? "bgd" : "bg";
+}
+
+string BackgroundManager::get_local_backgrounds_database_key(bool for_dark_theme) {
+ return for_dark_theme ? "bgsd" : "bgs";
+}
+
+void BackgroundManager::save_background_id(bool for_dark_theme) {
+ string key = get_background_database_key(for_dark_theme);
+ auto background_id = set_background_id_[for_dark_theme];
+ if (background_id.is_valid()) {
+ const Background *background = get_background(background_id);
+ CHECK(background != nullptr);
+ BackgroundLogEvent log_event{*background, set_background_type_[for_dark_theme]};
+ G()->td_db()->get_binlog_pmc()->set(key, log_event_store(log_event).as_slice().str());
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase(key);
+ }
+}
+
+void BackgroundManager::set_background_id(BackgroundId background_id, const BackgroundType &type, bool for_dark_theme) {
+ if (background_id == set_background_id_[for_dark_theme] && set_background_type_[for_dark_theme] == type) {
+ return;
+ }
+
+ set_background_id_[for_dark_theme] = background_id;
+ set_background_type_[for_dark_theme] = type;
+
+ save_background_id(for_dark_theme);
+ send_update_selected_background(for_dark_theme);
+}
+
+void BackgroundManager::save_local_backgrounds(bool for_dark_theme) {
+ string key = get_local_backgrounds_database_key(for_dark_theme);
+ auto &background_ids = local_background_ids_[for_dark_theme];
+ const size_t MAX_LOCAL_BACKGROUNDS = 100;
+ while (background_ids.size() > MAX_LOCAL_BACKGROUNDS) {
+ background_ids.pop_back();
+ }
+ if (!background_ids.empty()) {
+ BackgroundsLogEvent log_event;
+ log_event.backgrounds_ = transform(background_ids, [&](BackgroundId background_id) {
+ const Background *background = get_background(background_id);
+ CHECK(background != nullptr);
+ return *background;
+ });
+ G()->td_db()->get_binlog_pmc()->set(key, log_event_store(log_event).as_slice().str());
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase(key);
+ }
+}
+
+void BackgroundManager::upload_background_file(FileId file_id, const BackgroundType &type, bool for_dark_theme,
+ Promise<td_api::object_ptr<td_api::background>> &&promise) {
+ auto upload_file_id = td_->file_manager_->dup_file_id(file_id, "upload_background_file");
+ bool is_inserted =
+ being_uploaded_files_.emplace(upload_file_id, UploadedFileInfo(type, for_dark_theme, std::move(promise))).second;
+ CHECK(is_inserted);
+ LOG(INFO) << "Ask to upload background file " << upload_file_id;
+ td_->file_manager_->upload(upload_file_id, upload_background_file_callback_, 1, 0);
+}
+
+void BackgroundManager::on_upload_background_file(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) {
+ LOG(INFO) << "Background file " << file_id << " has been uploaded";
+
+ auto it = being_uploaded_files_.find(file_id);
+ CHECK(it != being_uploaded_files_.end());
+
+ auto type = it->second.type_;
+ auto for_dark_theme = it->second.for_dark_theme_;
+ auto promise = std::move(it->second.promise_);
+
+ being_uploaded_files_.erase(it);
+
+ do_upload_background_file(file_id, type, for_dark_theme, std::move(input_file), std::move(promise));
+}
+
+void BackgroundManager::on_upload_background_file_error(FileId file_id, Status status) {
+ if (G()->close_flag()) {
+ // do not fail upload if closing
+ return;
+ }
+
+ LOG(WARNING) << "Background file " << file_id << " has upload error " << status;
+ CHECK(status.is_error());
+
+ auto it = being_uploaded_files_.find(file_id);
+ CHECK(it != being_uploaded_files_.end());
+
+ auto promise = std::move(it->second.promise_);
+
+ being_uploaded_files_.erase(it);
+
+ promise.set_error(Status::Error(status.code() > 0 ? status.code() : 500,
+ status.message())); // TODO CHECK that status has always a code
+}
+
+void BackgroundManager::do_upload_background_file(FileId file_id, const BackgroundType &type, bool for_dark_theme,
+ tl_object_ptr<telegram_api::InputFile> &&input_file,
+ Promise<td_api::object_ptr<td_api::background>> &&promise) {
+ if (input_file == nullptr) {
+ FileView file_view = td_->file_manager_->get_file_view(file_id);
+ file_id = file_view.get_main_file_id();
+ auto it = file_id_to_background_id_.find(file_id);
+ if (it != file_id_to_background_id_.end()) {
+ return set_background(it->second, type, for_dark_theme, std::move(promise));
+ }
+ return promise.set_error(Status::Error(500, "Failed to reupload background"));
+ }
+
+ td_->create_handler<UploadBackgroundQuery>(std::move(promise))
+ ->send(file_id, std::move(input_file), type, for_dark_theme);
+}
+
+void BackgroundManager::on_uploaded_background_file(FileId file_id, const BackgroundType &type, bool for_dark_theme,
+ telegram_api::object_ptr<telegram_api::WallPaper> wallpaper,
+ Promise<td_api::object_ptr<td_api::background>> &&promise) {
+ CHECK(wallpaper != nullptr);
+
+ auto added_background = on_get_background(BackgroundId(), string(), std::move(wallpaper), true);
+ auto background_id = added_background.first;
+ if (!background_id.is_valid()) {
+ td_->file_manager_->cancel_upload(file_id);
+ return promise.set_error(Status::Error(500, "Receive wrong uploaded background"));
+ }
+ LOG_IF(ERROR, added_background.second != type)
+ << "Type of uploaded background has changed from " << type << " to " << added_background.second;
+
+ const auto *background = get_background(background_id);
+ CHECK(background != nullptr);
+ if (!background->file_id.is_valid()) {
+ td_->file_manager_->cancel_upload(file_id);
+ return promise.set_error(Status::Error(500, "Receive wrong uploaded background without file"));
+ }
+ LOG_STATUS(td_->file_manager_->merge(background->file_id, file_id));
+ set_background_id(background_id, type, for_dark_theme);
+ promise.set_value(get_background_object(background_id, for_dark_theme, nullptr));
+}
+
+void BackgroundManager::remove_background(BackgroundId background_id, Promise<Unit> &&promise) {
+ const auto *background = get_background(background_id);
+ if (background == nullptr) {
+ return promise.set_error(Status::Error(400, "Background not found"));
+ }
+
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), background_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ send_closure(actor_id, &BackgroundManager::on_removed_background, background_id, std::move(result),
+ std::move(promise));
+ });
+
+ if (!background->type.has_file()) {
+ if (!background->id.is_local()) {
+ return td_->create_handler<UnsaveBackgroundQuery>(std::move(query_promise))
+ ->send(telegram_api::make_object<telegram_api::inputWallPaperNoFile>(background_id.get()));
+ } else {
+ return query_promise.set_value(Unit());
+ }
+ }
+
+ td_->create_handler<UnsaveBackgroundQuery>(std::move(query_promise))
+ ->send(telegram_api::make_object<telegram_api::inputWallPaper>(background_id.get(), background->access_hash));
+}
+
+void BackgroundManager::on_removed_background(BackgroundId background_id, Result<Unit> &&result,
+ Promise<Unit> &&promise) {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ td::remove_if(installed_backgrounds_,
+ [background_id](const auto &background) { return background.first == background_id; });
+ if (background_id == set_background_id_[0]) {
+ set_background_id(BackgroundId(), BackgroundType(), false);
+ }
+ if (background_id == set_background_id_[1]) {
+ set_background_id(BackgroundId(), BackgroundType(), true);
+ }
+ if (background_id.is_local()) {
+ if (td::remove(local_background_ids_[0], background_id)) {
+ save_local_backgrounds(false);
+ }
+ if (td::remove(local_background_ids_[1], background_id)) {
+ save_local_backgrounds(true);
+ }
+ }
+ promise.set_value(Unit());
+}
+
+void BackgroundManager::reset_backgrounds(Promise<Unit> &&promise) {
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ send_closure(actor_id, &BackgroundManager::on_reset_background, std::move(result), std::move(promise));
+ });
+
+ td_->create_handler<ResetBackgroundsQuery>(std::move(query_promise))->send();
+}
+
+void BackgroundManager::on_reset_background(Result<Unit> &&result, Promise<Unit> &&promise) {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ installed_backgrounds_.clear();
+ set_background_id(BackgroundId(), BackgroundType(), false);
+ set_background_id(BackgroundId(), BackgroundType(), true);
+ if (!local_background_ids_[0].empty()) {
+ local_background_ids_[0].clear();
+ save_local_backgrounds(false);
+ }
+ if (!local_background_ids_[1].empty()) {
+ local_background_ids_[1].clear();
+ save_local_backgrounds(true);
+ }
+
+ promise.set_value(Unit());
+}
+
+void BackgroundManager::add_background(const Background &background, bool replace_type) {
+ LOG(INFO) << "Add " << background.id << " of " << background.type;
+
+ CHECK(background.id.is_valid());
+ auto &result_ptr = backgrounds_[background.id];
+ if (result_ptr == nullptr) {
+ result_ptr = make_unique<Background>();
+ }
+ auto *result = result_ptr.get();
+
+ FileSourceId file_source_id;
+ auto it = background_id_to_file_source_id_.find(background.id);
+ if (it != background_id_to_file_source_id_.end()) {
+ CHECK(!result->id.is_valid());
+ file_source_id = it->second.second;
+ background_id_to_file_source_id_.erase(it);
+ }
+
+ if (!result->id.is_valid()) {
+ result->id = background.id;
+ result->type = background.type;
+ } else {
+ CHECK(result->id == background.id);
+ if (replace_type) {
+ result->type = background.type;
+ }
+ }
+ result->access_hash = background.access_hash;
+ result->is_creator = background.is_creator;
+ result->is_default = background.is_default;
+ result->is_dark = background.is_dark;
+
+ if (result->name != background.name) {
+ if (!result->name.empty()) {
+ LOG(ERROR) << "Background name has changed from " << result->name << " to " << background.name;
+ // keep correspondence from previous name to background ID
+ // it will not harm, because background names can't be reassigned
+ // name_to_background_id_.erase(result->name);
+ }
+
+ result->name = background.name;
+
+ if (!is_background_name_local(result->name)) {
+ name_to_background_id_.emplace(result->name, result->id);
+ loaded_from_database_backgrounds_.erase(result->name); // don't needed anymore
+ }
+ }
+
+ if (result->file_id != background.file_id) {
+ if (result->file_id.is_valid()) {
+ if (!background.file_id.is_valid() ||
+ td_->file_manager_->get_file_view(result->file_id).get_main_file_id() !=
+ td_->file_manager_->get_file_view(background.file_id).get_main_file_id()) {
+ LOG(ERROR) << "Background file has changed from " << result->file_id << " to " << background.file_id;
+ file_id_to_background_id_.erase(result->file_id);
+ result->file_source_id = FileSourceId();
+ }
+ CHECK(!file_source_id.is_valid());
+ }
+ if (file_source_id.is_valid()) {
+ result->file_source_id = file_source_id;
+ }
+
+ result->file_id = background.file_id;
+
+ if (result->file_id.is_valid()) {
+ if (!result->file_source_id.is_valid()) {
+ result->file_source_id =
+ td_->file_reference_manager_->create_background_file_source(result->id, result->access_hash);
+ }
+ for (auto file_id : Document(Document::Type::General, result->file_id).get_file_ids(td_)) {
+ td_->file_manager_->add_file_source(file_id, result->file_source_id);
+ }
+
+ file_id_to_background_id_.emplace(result->file_id, result->id);
+ }
+ } else {
+ // if file_source_id is valid, then this is a new background with result->file_id == FileId()
+ // then background.file_id == FileId(), then this is a fill background, which can't have file_source_id
+ CHECK(!file_source_id.is_valid());
+ }
+}
+
+BackgroundManager::Background *BackgroundManager::get_background_ref(BackgroundId background_id) {
+ auto p = backgrounds_.find(background_id);
+ if (p == backgrounds_.end()) {
+ return nullptr;
+ } else {
+ return p->second.get();
+ }
+}
+
+const BackgroundManager::Background *BackgroundManager::get_background(BackgroundId background_id) const {
+ auto p = backgrounds_.find(background_id);
+ if (p == backgrounds_.end()) {
+ return nullptr;
+ } else {
+ return p->second.get();
+ }
+}
+
+string BackgroundManager::get_background_name_database_key(const string &name) {
+ return PSTRING() << "bgn" << name;
+}
+
+std::pair<BackgroundId, BackgroundType> BackgroundManager::on_get_background(
+ BackgroundId expected_background_id, const string &expected_background_name,
+ telegram_api::object_ptr<telegram_api::WallPaper> wallpaper_ptr, bool replace_type) {
+ if (wallpaper_ptr == nullptr) {
+ return {};
+ }
+
+ if (wallpaper_ptr->get_id() == telegram_api::wallPaperNoFile::ID) {
+ auto wallpaper = move_tl_object_as<telegram_api::wallPaperNoFile>(wallpaper_ptr);
+
+ if (wallpaper->settings_ == nullptr) {
+ LOG(ERROR) << "Receive wallPaperNoFile without settings: " << to_string(wallpaper);
+ return {};
+ }
+
+ auto background_id = BackgroundId(wallpaper->id_);
+ if (background_id.is_local()) {
+ LOG(ERROR) << "Receive " << to_string(wallpaper);
+ return {};
+ }
+ if (!background_id.is_valid()) {
+ background_id = get_next_local_background_id();
+ }
+
+ Background background;
+ background.id = background_id;
+ background.is_creator = false;
+ background.is_default = wallpaper->default_;
+ background.is_dark = wallpaper->dark_;
+ background.type = BackgroundType(true, false, std::move(wallpaper->settings_));
+ background.name = background.type.get_link();
+ add_background(background, replace_type);
+
+ return {background_id, background.type};
+ }
+
+ auto wallpaper = move_tl_object_as<telegram_api::wallPaper>(wallpaper_ptr);
+ auto background_id = BackgroundId(wallpaper->id_);
+ if (!background_id.is_valid() || background_id.is_local() || is_background_name_local(wallpaper->slug_)) {
+ LOG(ERROR) << "Receive " << to_string(wallpaper);
+ return {};
+ }
+ if (expected_background_id.is_valid() && background_id != expected_background_id) {
+ LOG(ERROR) << "Expected " << expected_background_id << ", but receive " << to_string(wallpaper);
+ }
+
+ int32 document_id = wallpaper->document_->get_id();
+ if (document_id == telegram_api::documentEmpty::ID) {
+ LOG(ERROR) << "Receive " << to_string(wallpaper);
+ return {};
+ }
+ CHECK(document_id == telegram_api::document::ID);
+
+ bool is_pattern = wallpaper->pattern_;
+
+ Document document = td_->documents_manager_->on_get_document(
+ telegram_api::move_object_as<telegram_api::document>(wallpaper->document_), DialogId(), nullptr,
+ Document::Type::General, true, is_pattern);
+ if (!document.file_id.is_valid()) {
+ LOG(ERROR) << "Receive wrong document in " << to_string(wallpaper);
+ return {};
+ }
+ CHECK(document.type == Document::Type::General); // guaranteed by is_background parameter to on_get_document
+
+ Background background;
+ background.id = background_id;
+ background.access_hash = wallpaper->access_hash_;
+ background.is_creator = wallpaper->creator_;
+ background.is_default = wallpaper->default_;
+ background.is_dark = wallpaper->dark_;
+ background.type = BackgroundType(false, is_pattern, std::move(wallpaper->settings_));
+ background.name = std::move(wallpaper->slug_);
+ background.file_id = document.file_id;
+ add_background(background, replace_type);
+
+ if (!expected_background_name.empty() && background.name != expected_background_name) {
+ LOG(ERROR) << "Expected background " << expected_background_name << ", but receive " << background.name;
+ name_to_background_id_.emplace(expected_background_name, background_id);
+ }
+
+ if (G()->parameters().use_file_db) {
+ LOG(INFO) << "Save " << background_id << " to database with name " << background.name;
+ CHECK(!is_background_name_local(background.name));
+ G()->td_db()->get_sqlite_pmc()->set(get_background_name_database_key(background.name),
+ log_event_store(background).as_slice().str(), Auto());
+ }
+
+ return {background_id, background.type};
+}
+
+void BackgroundManager::on_get_backgrounds(Result<telegram_api::object_ptr<telegram_api::account_WallPapers>> result) {
+ auto promises = std::move(pending_get_backgrounds_queries_);
+ CHECK(!promises.empty());
+ reset_to_empty(pending_get_backgrounds_queries_);
+
+ if (result.is_error()) {
+ // do not clear installed_backgrounds_
+
+ auto error = result.move_as_error();
+ for (auto &promise : promises) {
+ promise.second.set_error(error.clone());
+ }
+ return;
+ }
+
+ auto wallpapers_ptr = result.move_as_ok();
+ LOG(INFO) << "Receive " << to_string(wallpapers_ptr);
+ if (wallpapers_ptr->get_id() == telegram_api::account_wallPapersNotModified::ID) {
+ for (auto &promise : promises) {
+ promise.second.set_value(get_backgrounds_object(promise.first));
+ }
+ return;
+ }
+
+ installed_backgrounds_.clear();
+ auto wallpapers = telegram_api::move_object_as<telegram_api::account_wallPapers>(wallpapers_ptr);
+ for (auto &wallpaper : wallpapers->wallpapers_) {
+ auto background = on_get_background(BackgroundId(), string(), std::move(wallpaper), false);
+ if (background.first.is_valid()) {
+ installed_backgrounds_.push_back(std::move(background));
+ }
+ }
+
+ for (auto &promise : promises) {
+ promise.second.set_value(get_backgrounds_object(promise.first));
+ }
+}
+
+td_api::object_ptr<td_api::background> BackgroundManager::get_background_object(BackgroundId background_id,
+ bool for_dark_theme,
+ const BackgroundType *type) const {
+ const auto *background = get_background(background_id);
+ if (background == nullptr) {
+ return nullptr;
+ }
+ if (type == nullptr) {
+ type = &background->type;
+ // first check another set_background_id to get correct type if both backgrounds are the same
+ if (background_id == set_background_id_[1 - static_cast<int>(for_dark_theme)]) {
+ type = &set_background_type_[1 - static_cast<int>(for_dark_theme)];
+ }
+ if (background_id == set_background_id_[for_dark_theme]) {
+ type = &set_background_type_[for_dark_theme];
+ }
+ }
+ return td_api::make_object<td_api::background>(
+ background->id.get(), background->is_default, background->is_dark, background->name,
+ td_->documents_manager_->get_document_object(background->file_id, PhotoFormat::Png),
+ type->get_background_type_object());
+}
+
+td_api::object_ptr<td_api::backgrounds> BackgroundManager::get_backgrounds_object(bool for_dark_theme) const {
+ auto backgrounds = transform(installed_backgrounds_,
+ [this, for_dark_theme](const std::pair<BackgroundId, BackgroundType> &background) {
+ return get_background_object(background.first, for_dark_theme, &background.second);
+ });
+ auto background_id = set_background_id_[for_dark_theme];
+ bool have_background = false;
+ for (const auto &background : installed_backgrounds_) {
+ if (background_id == background.first) {
+ have_background = true;
+ break;
+ }
+ }
+ if (background_id.is_valid() && !have_background) {
+ backgrounds.push_back(get_background_object(background_id, for_dark_theme, nullptr));
+ }
+ for (auto local_background_id : local_background_ids_[for_dark_theme]) {
+ if (local_background_id != background_id) {
+ backgrounds.push_back(get_background_object(local_background_id, for_dark_theme, nullptr));
+ }
+ }
+ std::stable_sort(backgrounds.begin(), backgrounds.end(),
+ [background_id, for_dark_theme](const td_api::object_ptr<td_api::background> &lhs,
+ const td_api::object_ptr<td_api::background> &rhs) {
+ auto get_order = [background_id,
+ for_dark_theme](const td_api::object_ptr<td_api::background> &background) {
+ if (background->id_ == background_id.get()) {
+ return 0;
+ }
+ int theme_score = background->is_dark_ == for_dark_theme ? 0 : 1;
+ int local_score = BackgroundId(background->id_).is_local() ? 0 : 2;
+ return 1 + local_score + theme_score;
+ };
+ return get_order(lhs) < get_order(rhs);
+ });
+ return td_api::make_object<td_api::backgrounds>(std::move(backgrounds));
+}
+
+FileSourceId BackgroundManager::get_background_file_source_id(BackgroundId background_id, int64 access_hash) {
+ if (!background_id.is_valid()) {
+ return FileSourceId();
+ }
+
+ Background *background = get_background_ref(background_id);
+ if (background != nullptr) {
+ if (!background->file_source_id.is_valid()) {
+ background->file_source_id =
+ td_->file_reference_manager_->create_background_file_source(background_id, background->access_hash);
+ }
+ return background->file_source_id;
+ }
+
+ auto &result = background_id_to_file_source_id_[background_id];
+ if (result.first == 0) {
+ result.first = access_hash;
+ }
+ if (!result.second.is_valid()) {
+ result.second = td_->file_reference_manager_->create_background_file_source(background_id, result.first);
+ }
+ return result.second;
+}
+
+void BackgroundManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ updates.push_back(get_update_selected_background_object(false));
+ updates.push_back(get_update_selected_background_object(true));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BackgroundManager.h b/protocols/Telegram/tdlib/td/td/telegram/BackgroundManager.h
new file mode 100644
index 0000000000..6f44144d90
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BackgroundManager.h
@@ -0,0 +1,200 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/BackgroundId.h"
+#include "td/telegram/BackgroundType.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+#include <memory>
+#include <utility>
+
+namespace td {
+
+class Td;
+
+class BackgroundManager final : public Actor {
+ public:
+ BackgroundManager(Td *td, ActorShared<> parent);
+
+ void get_backgrounds(bool for_dark_theme, Promise<td_api::object_ptr<td_api::backgrounds>> &&promise);
+
+ void reload_background(BackgroundId background_id, int64 access_hash, Promise<Unit> &&promise);
+
+ std::pair<BackgroundId, BackgroundType> search_background(const string &name, Promise<Unit> &&promise);
+
+ void set_background(const td_api::InputBackground *input_background, const td_api::BackgroundType *background_type,
+ bool for_dark_theme, Promise<td_api::object_ptr<td_api::background>> &&promise);
+
+ void remove_background(BackgroundId background_id, Promise<Unit> &&promise);
+
+ void reset_backgrounds(Promise<Unit> &&promise);
+
+ td_api::object_ptr<td_api::background> get_background_object(BackgroundId background_id, bool for_dark_theme,
+ const BackgroundType *type) const;
+
+ std::pair<BackgroundId, BackgroundType> on_get_background(
+ BackgroundId expected_background_id, const string &expected_background_name,
+ telegram_api::object_ptr<telegram_api::WallPaper> wallpaper_ptr, bool replace_type);
+
+ FileSourceId get_background_file_source_id(BackgroundId background_id, int64 access_hash);
+
+ void on_uploaded_background_file(FileId file_id, const BackgroundType &type, bool for_dark_theme,
+ telegram_api::object_ptr<telegram_api::WallPaper> wallpaper,
+ Promise<td_api::object_ptr<td_api::background>> &&promise);
+
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
+ void store_background(BackgroundId background_id, LogEventStorerCalcLength &storer);
+
+ void store_background(BackgroundId background_id, LogEventStorerUnsafe &storer);
+
+ void parse_background(BackgroundId &background_id, LogEventParser &parser);
+
+ private:
+ struct Background {
+ BackgroundId id;
+ int64 access_hash = 0;
+ string name;
+ FileId file_id;
+ bool is_creator = false;
+ bool is_default = false;
+ bool is_dark = false;
+ bool has_new_local_id = true;
+ BackgroundType type;
+ FileSourceId file_source_id;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ class BackgroundLogEvent;
+ class BackgroundsLogEvent;
+
+ class UploadBackgroundFileCallback;
+
+ void start_up() final;
+
+ void tear_down() final;
+
+ static string get_background_database_key(bool for_dark_theme);
+
+ static string get_local_backgrounds_database_key(bool for_dark_theme);
+
+ void save_background_id(bool for_dark_theme);
+
+ void save_local_backgrounds(bool for_dark_theme);
+
+ void reload_background_from_server(BackgroundId background_id, const string &background_name,
+ telegram_api::object_ptr<telegram_api::InputWallPaper> &&input_wallpaper,
+ Promise<Unit> &&promise) const;
+
+ td_api::object_ptr<td_api::updateSelectedBackground> get_update_selected_background_object(bool for_dark_theme) const;
+
+ td_api::object_ptr<td_api::backgrounds> get_backgrounds_object(bool for_dark_theme) const;
+
+ void send_update_selected_background(bool for_dark_theme) const;
+
+ void set_max_local_background_id(BackgroundId background_id);
+
+ BackgroundId get_next_local_background_id();
+
+ BackgroundId add_local_background(const BackgroundType &type);
+
+ void add_background(const Background &background, bool replace_type);
+
+ Background *get_background_ref(BackgroundId background_id);
+
+ const Background *get_background(BackgroundId background_id) const;
+
+ static string get_background_name_database_key(const string &name);
+
+ void on_load_background_from_database(string name, string value);
+
+ void on_get_backgrounds(Result<telegram_api::object_ptr<telegram_api::account_WallPapers>> result);
+
+ Result<FileId> prepare_input_file(const tl_object_ptr<td_api::InputFile> &input_file);
+
+ void set_background(BackgroundId background_id, BackgroundType type, bool for_dark_theme,
+ Promise<td_api::object_ptr<td_api::background>> &&promise);
+
+ void on_installed_background(BackgroundId background_id, BackgroundType type, bool for_dark_theme,
+ Result<Unit> &&result, Promise<td_api::object_ptr<td_api::background>> &&promise);
+
+ void set_background_id(BackgroundId background_id, const BackgroundType &type, bool for_dark_theme);
+
+ void on_removed_background(BackgroundId background_id, Result<Unit> &&result, Promise<Unit> &&promise);
+
+ void on_reset_background(Result<Unit> &&result, Promise<Unit> &&promise);
+
+ void upload_background_file(FileId file_id, const BackgroundType &type, bool for_dark_theme,
+ Promise<td_api::object_ptr<td_api::background>> &&promise);
+
+ void on_upload_background_file(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file);
+
+ void on_upload_background_file_error(FileId file_id, Status status);
+
+ void do_upload_background_file(FileId file_id, const BackgroundType &type, bool for_dark_theme,
+ tl_object_ptr<telegram_api::InputFile> &&input_file,
+ Promise<td_api::object_ptr<td_api::background>> &&promise);
+
+ FlatHashMap<BackgroundId, unique_ptr<Background>, BackgroundIdHash> backgrounds_;
+
+ FlatHashMap<BackgroundId, std::pair<int64, FileSourceId>, BackgroundIdHash>
+ background_id_to_file_source_id_; // id -> [access_hash, file_source_id]
+
+ FlatHashMap<string, BackgroundId> name_to_background_id_;
+
+ FlatHashMap<FileId, BackgroundId, FileIdHash> file_id_to_background_id_;
+
+ FlatHashSet<string> loaded_from_database_backgrounds_;
+ FlatHashMap<string, vector<Promise<Unit>>> being_loaded_from_database_backgrounds_;
+
+ BackgroundId set_background_id_[2];
+ BackgroundType set_background_type_[2];
+
+ vector<std::pair<BackgroundId, BackgroundType>> installed_backgrounds_;
+
+ vector<std::pair<bool, Promise<td_api::object_ptr<td_api::backgrounds>>>> pending_get_backgrounds_queries_;
+
+ std::shared_ptr<UploadBackgroundFileCallback> upload_background_file_callback_;
+
+ struct UploadedFileInfo {
+ BackgroundType type_;
+ bool for_dark_theme_;
+ Promise<td_api::object_ptr<td_api::background>> promise_;
+
+ UploadedFileInfo(BackgroundType type, bool for_dark_theme,
+ Promise<td_api::object_ptr<td_api::background>> &&promise)
+ : type_(type), for_dark_theme_(for_dark_theme), promise_(std::move(promise)) {
+ }
+ };
+ FlatHashMap<FileId, UploadedFileInfo, FileIdHash> being_uploaded_files_;
+
+ BackgroundId max_local_background_id_;
+ vector<BackgroundId> local_background_ids_[2];
+
+ Td *td_;
+ ActorShared<> parent_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BackgroundType.cpp b/protocols/Telegram/tdlib/td/td/telegram/BackgroundType.cpp
new file mode 100644
index 0000000000..29961128a6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BackgroundType.cpp
@@ -0,0 +1,468 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/BackgroundType.h"
+
+#include "td/utils/HttpUrl.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+
+namespace td {
+
+static string get_color_hex_string(int32 color) {
+ string result;
+ for (int i = 20; i >= 0; i -= 4) {
+ result += "0123456789abcdef"[(color >> i) & 0xF];
+ }
+ return result;
+}
+
+static bool is_valid_color(int32 color) {
+ return 0 <= color && color <= 0xFFFFFF;
+}
+
+static bool validate_alpha_color(int32 &color) {
+ if (-0x1000000 <= color && color <= 0xFFFFFF) {
+ color &= 0xFFFFFF;
+ return true;
+ }
+ color = 0;
+ return false;
+}
+
+static bool is_valid_rotation_angle(int32 rotation_angle) {
+ return 0 <= rotation_angle && rotation_angle < 360 && rotation_angle % 45 == 0;
+}
+
+static bool is_valid_intensity(int32 intensity, bool allow_negative) {
+ return (allow_negative ? -100 : 0) <= intensity && intensity <= 100;
+}
+
+BackgroundFill::BackgroundFill(const telegram_api::wallPaperSettings *settings) {
+ if (settings == nullptr) {
+ return;
+ }
+
+ auto flags = settings->flags_;
+ if ((flags & telegram_api::wallPaperSettings::BACKGROUND_COLOR_MASK) != 0) {
+ top_color_ = settings->background_color_;
+ if (!validate_alpha_color(top_color_)) {
+ LOG(ERROR) << "Receive " << to_string(*settings);
+ }
+ }
+ if ((flags & telegram_api::wallPaperSettings::FOURTH_BACKGROUND_COLOR_MASK) != 0 ||
+ (flags & telegram_api::wallPaperSettings::THIRD_BACKGROUND_COLOR_MASK) != 0) {
+ bottom_color_ = settings->second_background_color_;
+ if (!validate_alpha_color(bottom_color_)) {
+ LOG(ERROR) << "Receive " << to_string(*settings);
+ }
+ third_color_ = settings->third_background_color_;
+ if (!validate_alpha_color(third_color_)) {
+ LOG(ERROR) << "Receive " << to_string(*settings);
+ }
+ if ((flags & telegram_api::wallPaperSettings::FOURTH_BACKGROUND_COLOR_MASK) != 0) {
+ fourth_color_ = settings->fourth_background_color_;
+ if (!validate_alpha_color(fourth_color_)) {
+ LOG(ERROR) << "Receive " << to_string(*settings);
+ }
+ }
+ } else if ((flags & telegram_api::wallPaperSettings::SECOND_BACKGROUND_COLOR_MASK) != 0) {
+ bottom_color_ = settings->second_background_color_;
+ if (!validate_alpha_color(bottom_color_)) {
+ LOG(ERROR) << "Receive " << to_string(*settings);
+ }
+
+ rotation_angle_ = settings->rotation_;
+ if (!is_valid_rotation_angle(rotation_angle_)) {
+ LOG(ERROR) << "Receive " << to_string(*settings);
+ rotation_angle_ = 0;
+ }
+ } else {
+ bottom_color_ = top_color_;
+ }
+}
+
+Result<BackgroundFill> BackgroundFill::get_background_fill(const td_api::BackgroundFill *fill) {
+ if (fill == nullptr) {
+ return Status::Error(400, "Background fill info must be non-empty");
+ }
+ switch (fill->get_id()) {
+ case td_api::backgroundFillSolid::ID: {
+ auto solid = static_cast<const td_api::backgroundFillSolid *>(fill);
+ if (!is_valid_color(solid->color_)) {
+ return Status::Error(400, "Invalid solid fill color value");
+ }
+ return BackgroundFill(solid->color_);
+ }
+ case td_api::backgroundFillGradient::ID: {
+ auto gradient = static_cast<const td_api::backgroundFillGradient *>(fill);
+ if (!is_valid_color(gradient->top_color_)) {
+ return Status::Error(400, "Invalid top gradient color value");
+ }
+ if (!is_valid_color(gradient->bottom_color_)) {
+ return Status::Error(400, "Invalid bottom gradient color value");
+ }
+ if (!is_valid_rotation_angle(gradient->rotation_angle_)) {
+ return Status::Error(400, "Invalid rotation angle value");
+ }
+ return BackgroundFill(gradient->top_color_, gradient->bottom_color_, gradient->rotation_angle_);
+ }
+ case td_api::backgroundFillFreeformGradient::ID: {
+ auto freeform = static_cast<const td_api::backgroundFillFreeformGradient *>(fill);
+ if (freeform->colors_.size() != 3 && freeform->colors_.size() != 4) {
+ return Status::Error(400, "Wrong number of gradient colors");
+ }
+ for (auto &color : freeform->colors_) {
+ if (!is_valid_color(color)) {
+ return Status::Error(400, "Invalid freeform gradient color value");
+ }
+ }
+ return BackgroundFill(freeform->colors_[0], freeform->colors_[1], freeform->colors_[2],
+ freeform->colors_.size() == 3 ? -1 : freeform->colors_[3]);
+ }
+ default:
+ UNREACHABLE();
+ return {};
+ }
+}
+
+Result<BackgroundFill> BackgroundFill::get_background_fill(Slice name) {
+ name = name.substr(0, name.find('#'));
+
+ Slice parameters;
+ auto parameters_pos = name.find('?');
+ if (parameters_pos != Slice::npos) {
+ parameters = name.substr(parameters_pos + 1);
+ name = name.substr(0, parameters_pos);
+ }
+
+ auto get_color = [](Slice color_string) -> Result<int32> {
+ auto r_color = hex_to_integer_safe<uint32>(color_string);
+ if (r_color.is_error() || color_string.size() > 6) {
+ return Status::Error(400, "WALLPAPER_INVALID");
+ }
+ return static_cast<int32>(r_color.ok());
+ };
+
+ size_t hyphen_pos = name.find('-');
+ if (name.find('~') < name.size()) {
+ vector<Slice> color_strings = full_split(name, '~');
+ CHECK(color_strings.size() >= 2);
+ if (color_strings.size() == 2) {
+ hyphen_pos = color_strings[0].size();
+ } else {
+ if (color_strings.size() > 4) {
+ return Status::Error(400, "WALLPAPER_INVALID");
+ }
+
+ TRY_RESULT(first_color, get_color(color_strings[0]));
+ TRY_RESULT(second_color, get_color(color_strings[1]));
+ TRY_RESULT(third_color, get_color(color_strings[2]));
+ int32 fourth_color = -1;
+ if (color_strings.size() == 4) {
+ TRY_RESULT_ASSIGN(fourth_color, get_color(color_strings[3]));
+ }
+ return BackgroundFill(first_color, second_color, third_color, fourth_color);
+ }
+ }
+
+ if (hyphen_pos < name.size()) {
+ TRY_RESULT(top_color, get_color(name.substr(0, hyphen_pos)));
+ TRY_RESULT(bottom_color, get_color(name.substr(hyphen_pos + 1)));
+ int32 rotation_angle = 0;
+
+ Slice prefix("rotation=");
+ if (begins_with(parameters, prefix)) {
+ rotation_angle = to_integer<int32>(parameters.substr(prefix.size()));
+ if (!is_valid_rotation_angle(rotation_angle)) {
+ rotation_angle = 0;
+ }
+ }
+
+ return BackgroundFill(top_color, bottom_color, rotation_angle);
+ }
+
+ TRY_RESULT(color, get_color(name));
+ return BackgroundFill(color);
+}
+
+string BackgroundFill::get_link(bool is_first) const {
+ switch (get_type()) {
+ case BackgroundFill::Type::Solid:
+ return get_color_hex_string(top_color_);
+ case BackgroundFill::Type::Gradient:
+ return PSTRING() << get_color_hex_string(top_color_) << '-' << get_color_hex_string(bottom_color_)
+ << (is_first ? '?' : '&') << "rotation=" << rotation_angle_;
+ case BackgroundFill::Type::FreeformGradient: {
+ SliceBuilder sb;
+ sb << get_color_hex_string(top_color_) << '~' << get_color_hex_string(bottom_color_) << '~'
+ << get_color_hex_string(third_color_);
+ if (fourth_color_ != -1) {
+ sb << '~' << get_color_hex_string(fourth_color_);
+ }
+ return sb.as_cslice().str();
+ }
+ default:
+ UNREACHABLE();
+ return string();
+ }
+}
+
+bool BackgroundFill::is_dark() const {
+ switch (get_type()) {
+ case Type::Solid:
+ return (top_color_ & 0x808080) == 0;
+ case Type::Gradient:
+ return (top_color_ & 0x808080) == 0 && (bottom_color_ & 0x808080) == 0;
+ case Type::FreeformGradient:
+ return (top_color_ & 0x808080) == 0 && (bottom_color_ & 0x808080) == 0 && (third_color_ & 0x808080) == 0 &&
+ (fourth_color_ == -1 || (fourth_color_ & 0x808080) == 0);
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs) {
+ return lhs.top_color_ == rhs.top_color_ && lhs.bottom_color_ == rhs.bottom_color_ &&
+ lhs.rotation_angle_ == rhs.rotation_angle_ && lhs.third_color_ == rhs.third_color_ &&
+ lhs.fourth_color_ == rhs.fourth_color_;
+}
+
+string BackgroundType::get_mime_type() const {
+ CHECK(has_file());
+ return type_ == Type::Pattern ? "image/png" : "image/jpeg";
+}
+
+void BackgroundType::apply_parameters_from_link(Slice name) {
+ const auto query = parse_url_query(name);
+
+ is_blurred_ = false;
+ is_moving_ = false;
+ auto modes = full_split(query.get_arg("mode"), ' ');
+ for (auto &mode : modes) {
+ if (type_ != Type::Pattern && to_lower(mode) == "blur") {
+ is_blurred_ = true;
+ }
+ if (to_lower(mode) == "motion") {
+ is_moving_ = true;
+ }
+ }
+
+ if (type_ == Type::Pattern) {
+ intensity_ = -101;
+ auto intensity_arg = query.get_arg("intensity");
+ if (!intensity_arg.empty()) {
+ intensity_ = to_integer<int32>(intensity_arg);
+ }
+ if (!is_valid_intensity(intensity_, true)) {
+ intensity_ = 50;
+ }
+
+ auto bg_color = query.get_arg("bg_color");
+ if (!bg_color.empty()) {
+ auto r_fill = BackgroundFill::get_background_fill(
+ PSLICE() << url_encode(bg_color) << "?rotation=" << url_encode(query.get_arg("rotation")));
+ if (r_fill.is_ok()) {
+ fill_ = r_fill.move_as_ok();
+ }
+ }
+ }
+}
+
+string BackgroundType::get_link() const {
+ string mode;
+ if (is_blurred_) {
+ mode = "blur";
+ }
+ if (is_moving_) {
+ if (!mode.empty()) {
+ mode += '+';
+ }
+ mode += "motion";
+ }
+
+ switch (type_) {
+ case Type::Wallpaper: {
+ if (!mode.empty()) {
+ return PSTRING() << "mode=" << mode;
+ }
+ return string();
+ }
+ case Type::Pattern: {
+ string link = PSTRING() << "intensity=" << intensity_ << "&bg_color=" << fill_.get_link(false);
+ if (!mode.empty()) {
+ link += "&mode=";
+ link += mode;
+ }
+ return link;
+ }
+ case Type::Fill:
+ return fill_.get_link(true);
+ default:
+ UNREACHABLE();
+ return string();
+ }
+}
+
+bool operator==(const BackgroundType &lhs, const BackgroundType &rhs) {
+ return lhs.type_ == rhs.type_ && lhs.is_blurred_ == rhs.is_blurred_ && lhs.is_moving_ == rhs.is_moving_ &&
+ lhs.intensity_ == rhs.intensity_ && lhs.fill_ == rhs.fill_;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundType &type) {
+ string_builder << "type ";
+ switch (type.type_) {
+ case BackgroundType::Type::Wallpaper:
+ string_builder << "Wallpaper";
+ break;
+ case BackgroundType::Type::Pattern:
+ string_builder << "Pattern";
+ break;
+ case BackgroundType::Type::Fill:
+ string_builder << "Fill";
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ return string_builder << '[' << type.get_link() << ']';
+}
+
+Result<BackgroundType> BackgroundType::get_background_type(const td_api::BackgroundType *background_type) {
+ if (background_type == nullptr) {
+ return Status::Error(400, "Type must be non-empty");
+ }
+
+ switch (background_type->get_id()) {
+ case td_api::backgroundTypeWallpaper::ID: {
+ auto wallpaper_type = static_cast<const td_api::backgroundTypeWallpaper *>(background_type);
+ return BackgroundType(wallpaper_type->is_blurred_, wallpaper_type->is_moving_);
+ }
+ case td_api::backgroundTypePattern::ID: {
+ auto pattern_type = static_cast<const td_api::backgroundTypePattern *>(background_type);
+ TRY_RESULT(background_fill, BackgroundFill::get_background_fill(pattern_type->fill_.get()));
+ if (!is_valid_intensity(pattern_type->intensity_, false)) {
+ return Status::Error(400, "Wrong intensity value");
+ }
+ auto intensity = pattern_type->is_inverted_ ? -max(pattern_type->intensity_, 1) : pattern_type->intensity_;
+ return BackgroundType(pattern_type->is_moving_, std::move(background_fill), intensity);
+ }
+ case td_api::backgroundTypeFill::ID: {
+ auto fill_type = static_cast<const td_api::backgroundTypeFill *>(background_type);
+ TRY_RESULT(background_fill, BackgroundFill::get_background_fill(fill_type->fill_.get()));
+ return BackgroundType(std::move(background_fill));
+ }
+ default:
+ UNREACHABLE();
+ return BackgroundType();
+ }
+}
+
+Result<BackgroundType> BackgroundType::get_local_background_type(Slice name) {
+ TRY_RESULT(fill, BackgroundFill::get_background_fill(name));
+ return BackgroundType(fill);
+}
+
+BackgroundType::BackgroundType(bool is_fill, bool is_pattern,
+ telegram_api::object_ptr<telegram_api::wallPaperSettings> settings) {
+ if (is_fill) {
+ type_ = Type::Fill;
+ CHECK(settings != nullptr);
+ fill_ = BackgroundFill(settings.get());
+ } else if (is_pattern) {
+ type_ = Type::Pattern;
+ if (settings) {
+ fill_ = BackgroundFill(settings.get());
+ is_moving_ = (settings->flags_ & telegram_api::wallPaperSettings::MOTION_MASK) != 0;
+ if ((settings->flags_ & telegram_api::wallPaperSettings::INTENSITY_MASK) != 0) {
+ intensity_ = settings->intensity_;
+ if (!is_valid_intensity(intensity_, true)) {
+ LOG(ERROR) << "Receive " << to_string(settings);
+ intensity_ = 50;
+ }
+ }
+ }
+ } else {
+ type_ = Type::Wallpaper;
+ if (settings) {
+ is_blurred_ = (settings->flags_ & telegram_api::wallPaperSettings::BLUR_MASK) != 0;
+ is_moving_ = (settings->flags_ & telegram_api::wallPaperSettings::MOTION_MASK) != 0;
+ }
+ }
+}
+
+td_api::object_ptr<td_api::BackgroundFill> BackgroundFill::get_background_fill_object() const {
+ switch (get_type()) {
+ case BackgroundFill::Type::Solid:
+ return td_api::make_object<td_api::backgroundFillSolid>(top_color_);
+ case BackgroundFill::Type::Gradient:
+ return td_api::make_object<td_api::backgroundFillGradient>(top_color_, bottom_color_, rotation_angle_);
+ case BackgroundFill::Type::FreeformGradient: {
+ vector<int32> colors{top_color_, bottom_color_, third_color_, fourth_color_};
+ if (colors.back() == -1) {
+ colors.pop_back();
+ }
+ return td_api::make_object<td_api::backgroundFillFreeformGradient>(std::move(colors));
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+td_api::object_ptr<td_api::BackgroundType> BackgroundType::get_background_type_object() const {
+ switch (type_) {
+ case Type::Wallpaper:
+ return td_api::make_object<td_api::backgroundTypeWallpaper>(is_blurred_, is_moving_);
+ case Type::Pattern:
+ return td_api::make_object<td_api::backgroundTypePattern>(
+ fill_.get_background_fill_object(), intensity_ < 0 ? -intensity_ : intensity_, intensity_ < 0, is_moving_);
+ case Type::Fill:
+ return td_api::make_object<td_api::backgroundTypeFill>(fill_.get_background_fill_object());
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+telegram_api::object_ptr<telegram_api::wallPaperSettings> BackgroundType::get_input_wallpaper_settings() const {
+ CHECK(has_file());
+
+ int32 flags = 0;
+ if (is_blurred_) {
+ flags |= telegram_api::wallPaperSettings::BLUR_MASK;
+ }
+ if (is_moving_) {
+ flags |= telegram_api::wallPaperSettings::MOTION_MASK;
+ }
+ switch (fill_.get_type()) {
+ case BackgroundFill::Type::FreeformGradient:
+ if (fill_.fourth_color_ != -1) {
+ flags |= telegram_api::wallPaperSettings::FOURTH_BACKGROUND_COLOR_MASK;
+ }
+ flags |= telegram_api::wallPaperSettings::THIRD_BACKGROUND_COLOR_MASK;
+ // fallthrough
+ case BackgroundFill::Type::Gradient:
+ flags |= telegram_api::wallPaperSettings::SECOND_BACKGROUND_COLOR_MASK;
+ // fallthrough
+ case BackgroundFill::Type::Solid:
+ flags |= telegram_api::wallPaperSettings::BACKGROUND_COLOR_MASK;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ if (intensity_ != 0) {
+ flags |= telegram_api::wallPaperSettings::INTENSITY_MASK;
+ }
+ return telegram_api::make_object<telegram_api::wallPaperSettings>(
+ flags, false /*ignored*/, false /*ignored*/, fill_.top_color_, fill_.bottom_color_, fill_.third_color_,
+ fill_.fourth_color_, intensity_, fill_.rotation_angle_);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BackgroundType.h b/protocols/Telegram/tdlib/td/td/telegram/BackgroundType.h
new file mode 100644
index 0000000000..656e96481b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BackgroundType.h
@@ -0,0 +1,134 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class BackgroundFill {
+ int32 top_color_ = 0;
+ int32 bottom_color_ = 0;
+ int32 rotation_angle_ = 0;
+ int32 third_color_ = -1;
+ int32 fourth_color_ = -1;
+
+ BackgroundFill() = default;
+ explicit BackgroundFill(int32 solid_color) : top_color_(solid_color), bottom_color_(solid_color) {
+ }
+ BackgroundFill(int32 top_color, int32 bottom_color, int32 rotation_angle)
+ : top_color_(top_color), bottom_color_(bottom_color), rotation_angle_(rotation_angle) {
+ }
+ BackgroundFill(int32 first_color, int32 second_color, int32 third_color, int32 fourth_color)
+ : top_color_(first_color), bottom_color_(second_color), third_color_(third_color), fourth_color_(fourth_color) {
+ }
+
+ explicit BackgroundFill(const telegram_api::wallPaperSettings *settings);
+
+ static Result<BackgroundFill> get_background_fill(const td_api::BackgroundFill *fill);
+
+ string get_link(bool is_first) const;
+
+ td_api::object_ptr<td_api::BackgroundFill> get_background_fill_object() const;
+
+ enum class Type : int32 { Solid, Gradient, FreeformGradient };
+ Type get_type() const {
+ if (third_color_ != -1) {
+ return Type::FreeformGradient;
+ }
+ if (top_color_ == bottom_color_) {
+ return Type::Solid;
+ }
+ return Type::Gradient;
+ }
+
+ friend bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs);
+
+ friend class BackgroundType;
+
+ static Result<BackgroundFill> get_background_fill(Slice name);
+
+ bool is_dark() const;
+};
+
+bool operator==(const BackgroundFill &lhs, const BackgroundFill &rhs);
+
+class BackgroundType {
+ enum class Type : int32 { Wallpaper, Pattern, Fill };
+ Type type_ = Type::Fill;
+ bool is_blurred_ = false;
+ bool is_moving_ = false;
+ int32 intensity_ = 0;
+ BackgroundFill fill_;
+
+ friend bool operator==(const BackgroundType &lhs, const BackgroundType &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundType &type);
+
+ BackgroundType(bool is_blurred, bool is_moving)
+ : type_(Type::Wallpaper), is_blurred_(is_blurred), is_moving_(is_moving) {
+ }
+ BackgroundType(bool is_moving, const BackgroundFill &fill, int32 intensity)
+ : type_(Type::Pattern), is_moving_(is_moving), intensity_(intensity), fill_(fill) {
+ }
+ explicit BackgroundType(BackgroundFill fill) : type_(Type::Fill), fill_(fill) {
+ }
+
+ public:
+ BackgroundType() = default;
+
+ BackgroundType(bool is_fill, bool is_pattern, telegram_api::object_ptr<telegram_api::wallPaperSettings> settings);
+
+ static Result<BackgroundType> get_background_type(const td_api::BackgroundType *background_type);
+
+ static Result<BackgroundType> get_local_background_type(Slice name);
+
+ bool has_file() const {
+ return type_ == Type::Wallpaper || type_ == Type::Pattern;
+ }
+
+ string get_mime_type() const;
+
+ void apply_parameters_from_link(Slice name);
+
+ string get_link() const;
+
+ bool has_equal_type(const BackgroundType &other) const {
+ return type_ == other.type_;
+ }
+
+ td_api::object_ptr<td_api::BackgroundType> get_background_type_object() const;
+
+ telegram_api::object_ptr<telegram_api::wallPaperSettings> get_input_wallpaper_settings() const;
+
+ bool is_dark() const {
+ CHECK(type_ == Type::Fill);
+ return fill_.is_dark();
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+bool operator==(const BackgroundType &lhs, const BackgroundType &rhs);
+
+inline bool operator!=(const BackgroundType &lhs, const BackgroundType &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const BackgroundType &type);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BackgroundType.hpp b/protocols/Telegram/tdlib/td/td/telegram/BackgroundType.hpp
new file mode 100644
index 0000000000..d480be97f5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BackgroundType.hpp
@@ -0,0 +1,84 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/BackgroundType.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void BackgroundType::store(StorerT &storer) const {
+ using td::store;
+ bool has_fill = fill_.top_color_ != 0 || fill_.bottom_color_ != 0;
+ bool has_intensity = intensity_ != 0;
+ auto fill_type = fill_.get_type();
+ bool is_gradient = fill_type == BackgroundFill::Type::Gradient;
+ bool is_freeform_gradient = fill_type == BackgroundFill::Type::FreeformGradient;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_blurred_);
+ STORE_FLAG(is_moving_);
+ STORE_FLAG(has_fill);
+ STORE_FLAG(has_intensity);
+ STORE_FLAG(is_gradient);
+ STORE_FLAG(is_freeform_gradient);
+ END_STORE_FLAGS();
+ store(type_, storer);
+ if (is_freeform_gradient) {
+ store(fill_.top_color_, storer);
+ store(fill_.bottom_color_, storer);
+ store(fill_.third_color_, storer);
+ store(fill_.fourth_color_, storer);
+ } else if (has_fill) {
+ store(fill_.top_color_, storer);
+ if (is_gradient) {
+ store(fill_.bottom_color_, storer);
+ store(fill_.rotation_angle_, storer);
+ }
+ }
+ if (has_intensity) {
+ store(intensity_, storer);
+ }
+}
+
+template <class ParserT>
+void BackgroundType::parse(ParserT &parser) {
+ using td::parse;
+ bool has_fill;
+ bool has_intensity;
+ bool is_gradient;
+ bool is_freeform_gradient;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_blurred_);
+ PARSE_FLAG(is_moving_);
+ PARSE_FLAG(has_fill);
+ PARSE_FLAG(has_intensity);
+ PARSE_FLAG(is_gradient);
+ PARSE_FLAG(is_freeform_gradient);
+ END_PARSE_FLAGS();
+ parse(type_, parser);
+ if (is_freeform_gradient) {
+ parse(fill_.top_color_, parser);
+ parse(fill_.bottom_color_, parser);
+ parse(fill_.third_color_, parser);
+ parse(fill_.fourth_color_, parser);
+ } else if (has_fill) {
+ parse(fill_.top_color_, parser);
+ if (is_gradient) {
+ parse(fill_.bottom_color_, parser);
+ parse(fill_.rotation_angle_, parser);
+ } else {
+ fill_.bottom_color_ = fill_.top_color_;
+ }
+ }
+ if (has_intensity) {
+ parse(intensity_, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BotCommand.cpp b/protocols/Telegram/tdlib/td/td/telegram/BotCommand.cpp
new file mode 100644
index 0000000000..f960ef8ba9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BotCommand.cpp
@@ -0,0 +1,226 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/BotCommand.h"
+
+#include "td/telegram/BotCommandScope.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+#include "td/utils/utf8.h"
+
+namespace td {
+
+class SetBotCommandsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SetBotCommandsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(BotCommandScope scope, const string &language_code, vector<BotCommand> &&commands) {
+ send_query(G()->net_query_creator().create(telegram_api::bots_setBotCommands(
+ scope.get_input_bot_command_scope(td_), language_code,
+ transform(commands, [](const BotCommand &command) { return command.get_input_bot_command(); }))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::bots_setBotCommands>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ if (!result_ptr.ok()) {
+ LOG(ERROR) << "Set bot commands request failed";
+ }
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ResetBotCommandsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ResetBotCommandsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(BotCommandScope scope, const string &language_code) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::bots_resetBotCommands(scope.get_input_bot_command_scope(td_), language_code)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::bots_resetBotCommands>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetBotCommandsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::botCommands>> promise_;
+
+ public:
+ explicit GetBotCommandsQuery(Promise<td_api::object_ptr<td_api::botCommands>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(BotCommandScope scope, const string &language_code) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::bots_getBotCommands(scope.get_input_bot_command_scope(td_), language_code)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::bots_getBotCommands>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ BotCommands commands(td_->contacts_manager_->get_my_id(), result_ptr.move_as_ok());
+ promise_.set_value(commands.get_bot_commands_object(td_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+BotCommand::BotCommand(telegram_api::object_ptr<telegram_api::botCommand> &&bot_command) {
+ CHECK(bot_command != nullptr);
+ command_ = std::move(bot_command->command_);
+ description_ = std::move(bot_command->description_);
+}
+
+td_api::object_ptr<td_api::botCommand> BotCommand::get_bot_command_object() const {
+ return td_api::make_object<td_api::botCommand>(command_, description_);
+}
+
+telegram_api::object_ptr<telegram_api::botCommand> BotCommand::get_input_bot_command() const {
+ return telegram_api::make_object<telegram_api::botCommand>(command_, description_);
+}
+
+bool operator==(const BotCommand &lhs, const BotCommand &rhs) {
+ return lhs.command_ == rhs.command_ && lhs.description_ == rhs.description_;
+}
+
+BotCommands::BotCommands(UserId bot_user_id, vector<telegram_api::object_ptr<telegram_api::botCommand>> &&bot_commands)
+ : bot_user_id_(bot_user_id) {
+ commands_ = transform(std::move(bot_commands), [](telegram_api::object_ptr<telegram_api::botCommand> &&bot_command) {
+ return BotCommand(std::move(bot_command));
+ });
+}
+
+td_api::object_ptr<td_api::botCommands> BotCommands::get_bot_commands_object(Td *td) const {
+ auto commands = transform(commands_, [](const auto &command) { return command.get_bot_command_object(); });
+ return td_api::make_object<td_api::botCommands>(
+ td->contacts_manager_->get_user_id_object(bot_user_id_, "get_bot_commands_object"), std::move(commands));
+}
+
+bool operator==(const BotCommands &lhs, const BotCommands &rhs) {
+ return lhs.bot_user_id_ == rhs.bot_user_id_ && lhs.commands_ == rhs.commands_;
+}
+
+static bool is_valid_language_code(const string &language_code) {
+ if (language_code.empty()) {
+ return true;
+ }
+ if (language_code.size() != 2) {
+ return false;
+ }
+ return 'a' <= language_code[0] && language_code[0] <= 'z' && 'a' <= language_code[1] && language_code[1] <= 'z';
+}
+
+void set_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
+ vector<td_api::object_ptr<td_api::botCommand>> &&commands, Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, scope, BotCommandScope::get_bot_command_scope(td, std::move(scope_ptr)));
+
+ if (!is_valid_language_code(language_code)) {
+ return promise.set_error(Status::Error(400, "Invalid language code specified"));
+ }
+
+ vector<BotCommand> new_commands;
+ for (auto &command : commands) {
+ if (command == nullptr) {
+ return promise.set_error(Status::Error(400, "Command must be non-empty"));
+ }
+ if (!clean_input_string(command->command_)) {
+ return promise.set_error(Status::Error(400, "Command must be encoded in UTF-8"));
+ }
+ if (!clean_input_string(command->description_)) {
+ return promise.set_error(Status::Error(400, "Command description must be encoded in UTF-8"));
+ }
+
+ const size_t MAX_COMMAND_TEXT_LENGTH = 32;
+ command->command_ = trim(command->command_);
+ if (command->command_[0] == '/') {
+ command->command_ = command->command_.substr(1);
+ }
+ if (command->command_.empty()) {
+ return promise.set_error(Status::Error(400, "Command must be non-empty"));
+ }
+ if (utf8_length(command->command_) > MAX_COMMAND_TEXT_LENGTH) {
+ return promise.set_error(
+ Status::Error(400, PSLICE() << "Command length must not exceed " << MAX_COMMAND_TEXT_LENGTH));
+ }
+
+ const size_t MAX_COMMAND_DESCRIPTION_LENGTH = 256;
+ command->description_ = trim(command->description_);
+ auto description_length = utf8_length(command->description_);
+ if (command->description_.empty()) {
+ return promise.set_error(Status::Error(400, "Command description must be non-empty"));
+ }
+ if (description_length > MAX_COMMAND_DESCRIPTION_LENGTH) {
+ return promise.set_error(Status::Error(
+ 400, PSLICE() << "Command description length must not exceed " << MAX_COMMAND_DESCRIPTION_LENGTH));
+ }
+
+ new_commands.emplace_back(std::move(command->command_), std::move(command->description_));
+ }
+
+ td->create_handler<SetBotCommandsQuery>(std::move(promise))->send(scope, language_code, std::move(new_commands));
+}
+
+void delete_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
+ Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, scope, BotCommandScope::get_bot_command_scope(td, std::move(scope_ptr)));
+
+ if (!is_valid_language_code(language_code)) {
+ return promise.set_error(Status::Error(400, "Invalid language code specified"));
+ }
+
+ td->create_handler<ResetBotCommandsQuery>(std::move(promise))->send(scope, language_code);
+}
+
+void get_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
+ Promise<td_api::object_ptr<td_api::botCommands>> &&promise) {
+ TRY_RESULT_PROMISE(promise, scope, BotCommandScope::get_bot_command_scope(td, std::move(scope_ptr)));
+
+ if (!is_valid_language_code(language_code)) {
+ return promise.set_error(Status::Error(400, "Invalid language code specified"));
+ }
+
+ td->create_handler<GetBotCommandsQuery>(std::move(promise))->send(scope, language_code);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BotCommand.h b/protocols/Telegram/tdlib/td/td/telegram/BotCommand.h
new file mode 100644
index 0000000000..23d683b11f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BotCommand.h
@@ -0,0 +1,100 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class Td;
+
+class BotCommand {
+ string command_;
+ string description_;
+
+ friend bool operator==(const BotCommand &lhs, const BotCommand &rhs);
+
+ public:
+ BotCommand() = default;
+ BotCommand(string command, string description) : command_(std::move(command)), description_(std::move(description)) {
+ }
+ explicit BotCommand(telegram_api::object_ptr<telegram_api::botCommand> &&bot_command);
+
+ td_api::object_ptr<td_api::botCommand> get_bot_command_object() const;
+
+ telegram_api::object_ptr<telegram_api::botCommand> get_input_bot_command() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(command_, storer);
+ td::store(description_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(command_, parser);
+ td::parse(description_, parser);
+ }
+};
+
+bool operator==(const BotCommand &lhs, const BotCommand &rhs);
+
+inline bool operator!=(const BotCommand &lhs, const BotCommand &rhs) {
+ return !(lhs == rhs);
+}
+
+class BotCommands {
+ UserId bot_user_id_;
+ vector<BotCommand> commands_;
+
+ friend bool operator==(const BotCommands &lhs, const BotCommands &rhs);
+
+ public:
+ BotCommands() = default;
+ BotCommands(UserId bot_user_id, vector<telegram_api::object_ptr<telegram_api::botCommand>> &&bot_commands);
+
+ td_api::object_ptr<td_api::botCommands> get_bot_commands_object(Td *td) const;
+
+ UserId get_bot_user_id() const {
+ return bot_user_id_;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(bot_user_id_, storer);
+ td::store(commands_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(bot_user_id_, parser);
+ td::parse(commands_, parser);
+ }
+};
+
+bool operator==(const BotCommands &lhs, const BotCommands &rhs);
+
+inline bool operator!=(const BotCommands &lhs, const BotCommands &rhs) {
+ return !(lhs == rhs);
+}
+
+void set_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
+ vector<td_api::object_ptr<td_api::botCommand>> &&commands, Promise<Unit> &&promise);
+
+void delete_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
+ Promise<Unit> &&promise);
+
+void get_commands(Td *td, td_api::object_ptr<td_api::BotCommandScope> &&scope_ptr, string &&language_code,
+ Promise<td_api::object_ptr<td_api::botCommands>> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BotCommandScope.cpp b/protocols/Telegram/tdlib/td/td/telegram/BotCommandScope.cpp
new file mode 100644
index 0000000000..dca973e329
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BotCommandScope.cpp
@@ -0,0 +1,125 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/BotCommandScope.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/Td.h"
+
+namespace td {
+
+BotCommandScope::BotCommandScope(Type type, DialogId dialog_id, UserId user_id)
+ : type_(type), dialog_id_(dialog_id), user_id_(user_id) {
+}
+
+Result<BotCommandScope> BotCommandScope::get_bot_command_scope(Td *td,
+ td_api::object_ptr<td_api::BotCommandScope> scope_ptr) {
+ if (scope_ptr == nullptr) {
+ return BotCommandScope(Type::Default);
+ }
+
+ CHECK(td->auth_manager_->is_bot());
+ Type type;
+ DialogId dialog_id;
+ UserId user_id;
+ switch (scope_ptr->get_id()) {
+ case td_api::botCommandScopeDefault::ID:
+ return BotCommandScope(Type::Default);
+ case td_api::botCommandScopeAllPrivateChats::ID:
+ return BotCommandScope(Type::AllUsers);
+ case td_api::botCommandScopeAllGroupChats::ID:
+ return BotCommandScope(Type::AllChats);
+ case td_api::botCommandScopeAllChatAdministrators::ID:
+ return BotCommandScope(Type::AllChatAdministrators);
+ case td_api::botCommandScopeChat::ID: {
+ auto scope = td_api::move_object_as<td_api::botCommandScopeChat>(scope_ptr);
+ type = Type::Dialog;
+ dialog_id = DialogId(scope->chat_id_);
+ break;
+ }
+ case td_api::botCommandScopeChatAdministrators::ID: {
+ auto scope = td_api::move_object_as<td_api::botCommandScopeChatAdministrators>(scope_ptr);
+ type = Type::DialogAdministrators;
+ dialog_id = DialogId(scope->chat_id_);
+ break;
+ }
+ case td_api::botCommandScopeChatMember::ID: {
+ auto scope = td_api::move_object_as<td_api::botCommandScopeChatMember>(scope_ptr);
+ type = Type::DialogParticipant;
+ dialog_id = DialogId(scope->chat_id_);
+ user_id = UserId(scope->user_id_);
+ TRY_STATUS(td->contacts_manager_->get_input_user(user_id));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ return BotCommandScope(Type::Default);
+ }
+
+ if (!td->messages_manager_->have_dialog_force(dialog_id, "get_bot_command_scope")) {
+ return Status::Error(400, "Chat not found");
+ }
+ if (!td->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ return Status::Error(400, "Can't access the chat");
+ }
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ if (type != Type::Dialog) {
+ return Status::Error(400, "Can't use specified scope in private chats");
+ }
+ break;
+ case DialogType::Chat:
+ // ok
+ break;
+ case DialogType::Channel:
+ if (td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) {
+ return Status::Error(400, "Can't change commands in channel chats");
+ }
+ break;
+ case DialogType::SecretChat:
+ default:
+ return Status::Error(400, "Can't access the chat");
+ }
+
+ return BotCommandScope(type, dialog_id, user_id);
+}
+
+telegram_api::object_ptr<telegram_api::BotCommandScope> BotCommandScope::get_input_bot_command_scope(
+ const Td *td) const {
+ auto input_peer =
+ dialog_id_.is_valid() ? td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read) : nullptr;
+ auto r_input_user = td->contacts_manager_->get_input_user(user_id_);
+ auto input_user = r_input_user.is_ok() ? r_input_user.move_as_ok() : nullptr;
+ switch (type_) {
+ case Type::Default:
+ return telegram_api::make_object<telegram_api::botCommandScopeDefault>();
+ case Type::AllUsers:
+ return telegram_api::make_object<telegram_api::botCommandScopeUsers>();
+ case Type::AllChats:
+ return telegram_api::make_object<telegram_api::botCommandScopeChats>();
+ case Type::AllChatAdministrators:
+ return telegram_api::make_object<telegram_api::botCommandScopeChatAdmins>();
+ case Type::Dialog:
+ CHECK(input_peer != nullptr);
+ return telegram_api::make_object<telegram_api::botCommandScopePeer>(std::move(input_peer));
+ case Type::DialogAdministrators:
+ CHECK(input_peer != nullptr);
+ return telegram_api::make_object<telegram_api::botCommandScopePeerAdmins>(std::move(input_peer));
+ case Type::DialogParticipant:
+ CHECK(input_peer != nullptr);
+ CHECK(input_user != nullptr);
+ return telegram_api::make_object<telegram_api::botCommandScopePeerUser>(std::move(input_peer),
+ std::move(input_user));
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BotCommandScope.h b/protocols/Telegram/tdlib/td/td/telegram/BotCommandScope.h
new file mode 100644
index 0000000000..c33a6314b7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BotCommandScope.h
@@ -0,0 +1,43 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class Td;
+
+class BotCommandScope {
+ enum class Type : int32 {
+ Default,
+ AllUsers,
+ AllChats,
+ AllChatAdministrators,
+ Dialog,
+ DialogAdministrators,
+ DialogParticipant
+ };
+ Type type_ = Type::Default;
+ DialogId dialog_id_;
+ UserId user_id_;
+
+ explicit BotCommandScope(Type type, DialogId dialog_id = DialogId(), UserId user_id = UserId());
+
+ public:
+ static Result<BotCommandScope> get_bot_command_scope(Td *td, td_api::object_ptr<td_api::BotCommandScope> scope_ptr);
+
+ telegram_api::object_ptr<telegram_api::BotCommandScope> get_input_bot_command_scope(const Td *td) const;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BotMenuButton.cpp b/protocols/Telegram/tdlib/td/td/telegram/BotMenuButton.cpp
new file mode 100644
index 0000000000..3b997e06be
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BotMenuButton.cpp
@@ -0,0 +1,164 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/BotMenuButton.h"
+
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/LinkManager.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class SetBotMenuButtonQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SetBotMenuButtonQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(UserId user_id, telegram_api::object_ptr<telegram_api::BotMenuButton> input_bot_menu_button) {
+ auto input_user = user_id.is_valid() ? td_->contacts_manager_->get_input_user(user_id).move_as_ok()
+ : make_tl_object<telegram_api::inputUserEmpty>();
+ send_query(G()->net_query_creator().create(
+ telegram_api::bots_setBotMenuButton(std::move(input_user), std::move(input_bot_menu_button))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::bots_setBotMenuButton>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ if (!result_ptr.ok()) {
+ LOG(ERROR) << "Receive false as result of SetBotMenuButtonQuery";
+ }
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetBotMenuButtonQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::botMenuButton>> promise_;
+
+ public:
+ explicit GetBotMenuButtonQuery(Promise<td_api::object_ptr<td_api::botMenuButton>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(UserId user_id) {
+ auto input_user = user_id.is_valid() ? td_->contacts_manager_->get_input_user(user_id).move_as_ok()
+ : make_tl_object<telegram_api::inputUserEmpty>();
+ send_query(G()->net_query_creator().create(telegram_api::bots_getBotMenuButton(std::move(input_user))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::bots_getBotMenuButton>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetBotMenuButtonQuery: " << to_string(ptr);
+ auto bot_menu_button = get_bot_menu_button(std::move(ptr));
+ promise_.set_value(bot_menu_button == nullptr ? td_api::make_object<td_api::botMenuButton>()
+ : bot_menu_button->get_bot_menu_button_object(td_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+unique_ptr<BotMenuButton> get_bot_menu_button(telegram_api::object_ptr<telegram_api::BotMenuButton> &&bot_menu_button) {
+ if (bot_menu_button == nullptr) {
+ return nullptr;
+ }
+ switch (bot_menu_button->get_id()) {
+ case telegram_api::botMenuButtonCommands::ID:
+ return nullptr;
+ case telegram_api::botMenuButtonDefault::ID:
+ return td::make_unique<BotMenuButton>(string(), "default");
+ case telegram_api::botMenuButton::ID: {
+ auto button = telegram_api::move_object_as<telegram_api::botMenuButton>(bot_menu_button);
+ if (button->text_.empty()) {
+ LOG(ERROR) << "Receive bot menu button with empty text: " << to_string(button);
+ return nullptr;
+ }
+ return td::make_unique<BotMenuButton>(std::move(button->text_), std::move(button->url_));
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+td_api::object_ptr<td_api::botMenuButton> BotMenuButton::get_bot_menu_button_object(Td *td) const {
+ bool is_bot = td->auth_manager_->is_bot();
+ return td_api::make_object<td_api::botMenuButton>(text_, is_bot ? url_ : "menu://" + url_);
+}
+
+bool operator==(const BotMenuButton &lhs, const BotMenuButton &rhs) {
+ return lhs.text_ == rhs.text_ && lhs.url_ == rhs.url_;
+}
+
+td_api::object_ptr<td_api::botMenuButton> get_bot_menu_button_object(Td *td, const BotMenuButton *bot_menu_button) {
+ if (bot_menu_button == nullptr) {
+ return nullptr;
+ }
+ return bot_menu_button->get_bot_menu_button_object(td);
+}
+
+void set_menu_button(Td *td, UserId user_id, td_api::object_ptr<td_api::botMenuButton> &&menu_button,
+ Promise<Unit> &&promise) {
+ if (!user_id.is_valid() && user_id != UserId()) {
+ return promise.set_error(Status::Error(400, "User not found"));
+ }
+
+ telegram_api::object_ptr<telegram_api::BotMenuButton> input_bot_menu_button;
+ if (menu_button == nullptr) {
+ input_bot_menu_button = telegram_api::make_object<telegram_api::botMenuButtonCommands>();
+ } else if (menu_button->text_.empty()) {
+ if (menu_button->url_ != "default") {
+ return promise.set_error(Status::Error(400, "Menu button text must be non-empty"));
+ }
+ input_bot_menu_button = telegram_api::make_object<telegram_api::botMenuButtonDefault>();
+ } else {
+ if (!clean_input_string(menu_button->text_)) {
+ return promise.set_error(Status::Error(400, "Menu button text must be encoded in UTF-8"));
+ }
+ if (!clean_input_string(menu_button->url_)) {
+ return promise.set_error(Status::Error(400, "Menu button URL must be encoded in UTF-8"));
+ }
+ auto r_url = LinkManager::check_link(menu_button->url_, true, !G()->is_test_dc());
+ if (r_url.is_error()) {
+ return promise.set_error(Status::Error(400, PSLICE() << "Menu button Web App " << r_url.error().message()));
+ }
+ input_bot_menu_button = telegram_api::make_object<telegram_api::botMenuButton>(menu_button->text_, r_url.ok());
+ }
+
+ td->create_handler<SetBotMenuButtonQuery>(std::move(promise))->send(user_id, std::move(input_bot_menu_button));
+}
+
+void get_menu_button(Td *td, UserId user_id, Promise<td_api::object_ptr<td_api::botMenuButton>> &&promise) {
+ if (!user_id.is_valid() && user_id != UserId()) {
+ return promise.set_error(Status::Error(400, "User not found"));
+ }
+
+ td->create_handler<GetBotMenuButtonQuery>(std::move(promise))->send(user_id);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/BotMenuButton.h b/protocols/Telegram/tdlib/td/td/telegram/BotMenuButton.h
new file mode 100644
index 0000000000..79f4b54647
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/BotMenuButton.h
@@ -0,0 +1,83 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class Td;
+
+class BotMenuButton {
+ string text_;
+ string url_;
+
+ friend bool operator==(const BotMenuButton &lhs, const BotMenuButton &rhs);
+
+ public:
+ BotMenuButton() = default;
+
+ BotMenuButton(string &&text, string &&url) : text_(std::move(text)), url_(std::move(url)) {
+ }
+
+ td_api::object_ptr<td_api::botMenuButton> get_bot_menu_button_object(Td *td) const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ bool has_text = !text_.empty();
+ bool has_url = !url_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_text);
+ STORE_FLAG(has_url);
+ END_STORE_FLAGS();
+ if (has_text) {
+ td::store(text_, storer);
+ }
+ if (has_url) {
+ td::store(url_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ bool has_text;
+ bool has_url;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_text);
+ PARSE_FLAG(has_url);
+ END_PARSE_FLAGS();
+ if (has_text) {
+ td::parse(text_, parser);
+ }
+ if (has_url) {
+ td::parse(url_, parser);
+ }
+ }
+};
+
+bool operator==(const BotMenuButton &lhs, const BotMenuButton &rhs);
+
+inline bool operator!=(const BotMenuButton &lhs, const BotMenuButton &rhs) {
+ return !(lhs == rhs);
+}
+
+unique_ptr<BotMenuButton> get_bot_menu_button(telegram_api::object_ptr<telegram_api::BotMenuButton> &&bot_menu_button);
+
+td_api::object_ptr<td_api::botMenuButton> get_bot_menu_button_object(Td *td, const BotMenuButton *bot_menu_button);
+
+void set_menu_button(Td *td, UserId user_id, td_api::object_ptr<td_api::botMenuButton> &&menu_button,
+ Promise<Unit> &&promise);
+
+void get_menu_button(Td *td, UserId user_id, Promise<td_api::object_ptr<td_api::botMenuButton>> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CallActor.cpp b/protocols/Telegram/tdlib/td/td/telegram/CallActor.cpp
index 2dd70218e0..e3f3e4de18 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/CallActor.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/CallActor.cpp
@@ -1,104 +1,136 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/CallActor.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-#include "td/telegram/telegram_api.hpp"
-
-#include "td/mtproto/crypto.h"
-
-#include "td/telegram/ConfigShared.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/DhCache.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
#include "td/telegram/misc.h"
#include "td/telegram/net/NetQueryCreator.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/NotificationManager.h"
#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.hpp"
#include "td/telegram/UpdatesManager.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/as.h"
+#include "td/utils/bits.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/crypto.h"
+#include "td/utils/FlatHashSet.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
#include <tuple>
namespace td {
-// CallProtocol
-CallProtocol CallProtocol::from_telegram_api(const telegram_api::phoneCallProtocol &protocol) {
- CallProtocol res;
- res.udp_p2p = protocol.udp_p2p_;
- res.udp_reflector = protocol.udp_reflector_;
- res.min_layer = protocol.min_layer_;
- res.max_layer = protocol.max_layer_;
- return res;
+
+CallProtocol::CallProtocol(const telegram_api::phoneCallProtocol &protocol)
+ : udp_p2p(protocol.udp_p2p_)
+ , udp_reflector(protocol.udp_reflector_)
+ , min_layer(protocol.min_layer_)
+ , max_layer(protocol.max_layer_)
+ , library_versions(protocol.library_versions_) {
}
-tl_object_ptr<telegram_api::phoneCallProtocol> CallProtocol::as_telegram_api() const {
+tl_object_ptr<telegram_api::phoneCallProtocol> CallProtocol::get_input_phone_call_protocol() const {
int32 flags = 0;
if (udp_p2p) {
- flags |= telegram_api::phoneCallProtocol::Flags::UDP_P2P_MASK;
+ flags |= telegram_api::phoneCallProtocol::UDP_P2P_MASK;
}
if (udp_reflector) {
- flags |= telegram_api::phoneCallProtocol::Flags::UDP_REFLECTOR_MASK;
+ flags |= telegram_api::phoneCallProtocol::UDP_REFLECTOR_MASK;
+ }
+ return make_tl_object<telegram_api::phoneCallProtocol>(flags, udp_p2p, udp_reflector, min_layer, max_layer,
+ vector<string>(library_versions));
+}
+
+CallProtocol::CallProtocol(const td_api::callProtocol &protocol)
+ : udp_p2p(protocol.udp_p2p_)
+ , udp_reflector(protocol.udp_reflector_)
+ , min_layer(protocol.min_layer_)
+ , max_layer(protocol.max_layer_)
+ , library_versions(protocol.library_versions_) {
+}
+
+CallConnection::CallConnection(const telegram_api::PhoneConnection &connection) {
+ switch (connection.get_id()) {
+ case telegram_api::phoneConnection::ID: {
+ auto &conn = static_cast<const telegram_api::phoneConnection &>(connection);
+ type = Type::Telegram;
+ id = conn.id_;
+ ip = conn.ip_;
+ ipv6 = conn.ipv6_;
+ port = conn.port_;
+ peer_tag = conn.peer_tag_.as_slice().str();
+ is_tcp = conn.tcp_;
+ break;
+ }
+ case telegram_api::phoneConnectionWebrtc::ID: {
+ auto &conn = static_cast<const telegram_api::phoneConnectionWebrtc &>(connection);
+ type = Type::Webrtc;
+ id = conn.id_;
+ ip = conn.ip_;
+ ipv6 = conn.ipv6_;
+ port = conn.port_;
+ username = conn.username_;
+ password = conn.password_;
+ supports_turn = conn.turn_;
+ supports_stun = conn.stun_;
+ break;
+ }
+ default:
+ UNREACHABLE();
}
- return make_tl_object<telegram_api::phoneCallProtocol>(flags, udp_p2p, udp_reflector, min_layer, max_layer);
}
-CallProtocol CallProtocol::from_td_api(const td_api::callProtocol &protocol) {
- CallProtocol res;
- res.udp_p2p = protocol.udp_p2p_;
- res.udp_reflector = protocol.udp_reflector_;
- res.min_layer = protocol.min_layer_;
- res.max_layer = protocol.max_layer_;
- return res;
-}
-tl_object_ptr<td_api::callProtocol> CallProtocol::as_td_api() const {
- return make_tl_object<td_api::callProtocol>(udp_p2p, udp_reflector, min_layer, max_layer);
+tl_object_ptr<td_api::callProtocol> CallProtocol::get_call_protocol_object() const {
+ return make_tl_object<td_api::callProtocol>(udp_p2p, udp_reflector, min_layer, max_layer,
+ vector<string>(library_versions));
}
-CallConnection CallConnection::from_telegram_api(const telegram_api::phoneConnection &connection) {
- CallConnection res;
- res.id = connection.id_;
- res.ip = connection.ip_;
- res.ipv6 = connection.ipv6_;
- res.port = connection.port_;
- res.peer_tag = connection.peer_tag_.as_slice().str();
- return res;
-}
-tl_object_ptr<telegram_api::phoneConnection> CallConnection::as_telegram_api() const {
- return make_tl_object<telegram_api::phoneConnection>(id, ip, ipv6, port, BufferSlice(peer_tag));
-}
-tl_object_ptr<td_api::callConnection> CallConnection::as_td_api() const {
- return make_tl_object<td_api::callConnection>(id, ip, ipv6, port, peer_tag);
+tl_object_ptr<td_api::callServer> CallConnection::get_call_server_object() const {
+ auto server_type = [&]() -> tl_object_ptr<td_api::CallServerType> {
+ switch (type) {
+ case Type::Telegram:
+ return make_tl_object<td_api::callServerTypeTelegramReflector>(peer_tag, is_tcp);
+ case Type::Webrtc:
+ return make_tl_object<td_api::callServerTypeWebrtc>(username, password, supports_turn, supports_stun);
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+ }();
+ return make_tl_object<td_api::callServer>(id, ip, ipv6, port, std::move(server_type));
}
-// CallState
-tl_object_ptr<td_api::CallState> CallState::as_td_api() const {
+tl_object_ptr<td_api::CallState> CallState::get_call_state_object() const {
switch (type) {
case Type::Pending:
return make_tl_object<td_api::callStatePending>(is_created, is_received);
case Type::ExchangingKey:
return make_tl_object<td_api::callStateExchangingKeys>();
case Type::Ready: {
- std::vector<tl_object_ptr<td_api::callConnection>> v;
- for (auto &c : connections) {
- v.push_back(c.as_td_api());
- }
- return make_tl_object<td_api::callStateReady>(protocol.as_td_api(), std::move(v), config, key,
- vector<string>(emojis_fingerprint));
+ auto call_connections = transform(connections, [](auto &c) { return c.get_call_server_object(); });
+ return make_tl_object<td_api::callStateReady>(protocol.get_call_protocol_object(), std::move(call_connections),
+ config, key, vector<string>(emojis_fingerprint), allow_p2p);
}
case Type::HangingUp:
return make_tl_object<td_api::callStateHangingUp>();
case Type::Discarded:
return make_tl_object<td_api::callStateDiscarded>(get_call_discard_reason_object(discard_reason), need_rating,
- need_debug_information);
+ need_debug_information, need_log);
case Type::Error:
CHECK(error.is_error());
return make_tl_object<td_api::callStateError>(make_tl_object<td_api::error>(error.code(), error.message().str()));
@@ -109,16 +141,16 @@ tl_object_ptr<td_api::CallState> CallState::as_td_api() const {
}
}
-// CallActor
CallActor::CallActor(CallId call_id, ActorShared<> parent, Promise<int64> promise)
: parent_(std::move(parent)), call_id_promise_(std::move(promise)), local_call_id_(call_id) {
}
void CallActor::create_call(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user,
- CallProtocol &&protocol, Promise<CallId> &&promise) {
+ CallProtocol &&protocol, bool is_video, Promise<CallId> &&promise) {
CHECK(state_ == State::Empty);
state_ = State::SendRequestQuery;
is_outgoing_ = true;
+ is_video_ = is_video;
user_id_ = user_id;
input_user_ = std::move(input_user);
call_state_.protocol = std::move(protocol);
@@ -129,11 +161,57 @@ void CallActor::create_call(UserId user_id, tl_object_ptr<telegram_api::InputUse
promise.set_value(CallId(local_call_id_));
}
-void CallActor::discard_call(bool is_disconnected, int32 duration, int64 connection_id, Promise<> promise) {
+void CallActor::accept_call(CallProtocol &&protocol, Promise<Unit> promise) {
+ if (state_ != State::SendAcceptQuery) {
+ return promise.set_error(Status::Error(400, "Unexpected acceptCall"));
+ }
+ is_accepted_ = true;
+ call_state_.protocol = std::move(protocol);
+ promise.set_value(Unit());
+ loop();
+}
+
+void CallActor::update_call_signaling_data(string data) {
+ if (call_state_.type != CallState::Type::Ready) {
+ return;
+ }
+
+ auto update = td_api::make_object<td_api::updateNewCallSignalingData>();
+ update->call_id_ = local_call_id_.get();
+ update->data_ = std::move(data);
+ send_closure(G()->td(), &Td::send_update, std::move(update));
+}
+
+void CallActor::send_call_signaling_data(string &&data, Promise<Unit> promise) {
+ if (call_state_.type != CallState::Type::Ready) {
+ return promise.set_error(Status::Error(400, "Call is not active"));
+ }
+
+ auto query = G()->net_query_creator().create(
+ telegram_api::phone_sendSignalingData(get_input_phone_call("send_call_signaling_data"), BufferSlice(data)));
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_net_query) mutable {
+ auto res = fetch_result<telegram_api::phone_sendSignalingData>(std::move(r_net_query));
+ if (res.is_error()) {
+ promise.set_error(res.move_as_error());
+ } else {
+ promise.set_value(Unit());
+ }
+ }));
+}
+
+void CallActor::discard_call(bool is_disconnected, int32 duration, bool is_video, int64 connection_id,
+ Promise<Unit> promise) {
promise.set_value(Unit());
if (state_ == State::Discarded || state_ == State::WaitDiscardResult || state_ == State::SendDiscardQuery) {
return;
}
+ is_video_ |= is_video;
+
+ if (state_ == State::WaitRequestResult && !request_query_ref_.empty()) {
+ LOG(INFO) << "Cancel request call query";
+ cancel_query(request_query_ref_);
+ }
switch (call_state_.type) {
case CallState::Type::Empty:
@@ -167,77 +245,251 @@ void CallActor::discard_call(bool is_disconnected, int32 duration, int64 connect
loop();
}
-void CallActor::accept_call(CallProtocol &&protocol, Promise<> promise) {
- if (state_ != State::SendAcceptQuery) {
- return promise.set_error(Status::Error(400, "Unexpected acceptCall"));
- }
- is_accepted_ = true;
- call_state_.protocol = std::move(protocol);
- promise.set_value(Unit());
- loop();
-}
-
-void CallActor::rate_call(int32 rating, string comment, Promise<> promise) {
+void CallActor::rate_call(int32 rating, string comment, vector<td_api::object_ptr<td_api::CallProblem>> &&problems,
+ Promise<Unit> promise) {
if (!call_state_.need_rating) {
return promise.set_error(Status::Error(400, "Unexpected sendCallRating"));
}
promise.set_value(Unit());
- auto tl_query = telegram_api::phone_setCallRating(get_input_phone_call(), rating, std::move(comment));
- auto query = G()->net_query_creator().create(create_storer(tl_query));
- send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) {
- send_closure(actor_id, &CallActor::on_set_rating_query_result, std::move(net_query));
+
+ if (rating == 5) {
+ comment.clear();
+ }
+
+ FlatHashSet<string> tags;
+ for (auto &problem : problems) {
+ if (problem == nullptr) {
+ continue;
+ }
+
+ const char *tag = [problem_id = problem->get_id()] {
+ switch (problem_id) {
+ case td_api::callProblemEcho::ID:
+ return "echo";
+ case td_api::callProblemNoise::ID:
+ return "noise";
+ case td_api::callProblemInterruptions::ID:
+ return "interruptions";
+ case td_api::callProblemDistortedSpeech::ID:
+ return "distorted_speech";
+ case td_api::callProblemSilentLocal::ID:
+ return "silent_local";
+ case td_api::callProblemSilentRemote::ID:
+ return "silent_remote";
+ case td_api::callProblemDropped::ID:
+ return "dropped";
+ case td_api::callProblemDistortedVideo::ID:
+ return "distorted_video";
+ case td_api::callProblemPixelatedVideo::ID:
+ return "pixelated_video";
+ default:
+ UNREACHABLE();
+ return "";
+ }
+ }();
+ if (tags.insert(tag).second) {
+ if (!comment.empty()) {
+ comment += ' ';
+ }
+ comment += '#';
+ comment += tag;
+ }
+ }
+
+ auto tl_query = telegram_api::phone_setCallRating(0, false /*ignored*/, get_input_phone_call("rate_call"), rating,
+ std::move(comment));
+ auto query = G()->net_query_creator().create(tl_query);
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<NetQueryPtr> r_net_query) {
+ send_closure(actor_id, &CallActor::on_set_rating_query_result, std::move(r_net_query));
}));
loop();
}
-void CallActor::on_set_rating_query_result(NetQueryPtr net_query) {
- auto res = fetch_result<telegram_api::phone_setCallRating>(std::move(net_query));
+void CallActor::on_set_rating_query_result(Result<NetQueryPtr> r_net_query) {
+ auto res = fetch_result<telegram_api::phone_setCallRating>(std::move(r_net_query));
if (res.is_error()) {
return on_error(res.move_as_error());
}
- call_state_.need_rating = false;
- send_closure(G()->updates_manager(), &UpdatesManager::on_get_updates, res.move_as_ok());
+ if (call_state_.need_rating) {
+ call_state_.need_rating = false;
+ call_state_need_flush_ = true;
+ loop();
+ }
+ send_closure(G()->updates_manager(), &UpdatesManager::on_get_updates, res.move_as_ok(), Promise<Unit>());
}
-void CallActor::send_call_debug_information(string data, Promise<> promise) {
+void CallActor::send_call_debug_information(string data, Promise<Unit> promise) {
if (!call_state_.need_debug_information) {
return promise.set_error(Status::Error(400, "Unexpected sendCallDebugInformation"));
}
promise.set_value(Unit());
- auto tl_query = telegram_api::phone_saveCallDebug(get_input_phone_call(),
+ auto tl_query = telegram_api::phone_saveCallDebug(get_input_phone_call("send_call_debug_information"),
make_tl_object<telegram_api::dataJSON>(std::move(data)));
- auto query = G()->net_query_creator().create(create_storer(tl_query));
- send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) {
- send_closure(actor_id, &CallActor::on_set_debug_query_result, std::move(net_query));
+ auto query = G()->net_query_creator().create(tl_query);
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<NetQueryPtr> r_net_query) {
+ send_closure(actor_id, &CallActor::on_save_debug_query_result, std::move(r_net_query));
}));
loop();
}
-void CallActor::on_set_debug_query_result(NetQueryPtr net_query) {
- auto res = fetch_result<telegram_api::phone_saveCallDebug>(std::move(net_query));
+void CallActor::on_save_debug_query_result(Result<NetQueryPtr> r_net_query) {
+ auto res = fetch_result<telegram_api::phone_saveCallDebug>(std::move(r_net_query));
if (res.is_error()) {
return on_error(res.move_as_error());
}
- call_state_.need_debug_information = false;
+ if (!res.ok() && !call_state_.need_log) {
+ call_state_.need_log = true;
+ call_state_need_flush_ = true;
+ }
+ if (call_state_.need_debug_information) {
+ call_state_.need_debug_information = false;
+ call_state_need_flush_ = true;
+ }
+ loop();
+}
+
+void CallActor::send_call_log(td_api::object_ptr<td_api::InputFile> log_file, Promise<Unit> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (!call_state_.need_log) {
+ return promise.set_error(Status::Error(400, "Unexpected sendCallLog"));
+ }
+
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ auto r_file_id = file_manager->get_input_file_id(FileType::CallLog, log_file, DialogId(), false, false);
+ if (r_file_id.is_error()) {
+ return promise.set_error(Status::Error(400, r_file_id.error().message()));
+ }
+ auto file_id = r_file_id.move_as_ok();
+
+ FileView file_view = file_manager->get_file_view(file_id);
+ if (file_view.is_encrypted()) {
+ return promise.set_error(Status::Error(400, "Can't use encrypted file"));
+ }
+ if (!file_view.has_local_location() && !file_view.has_generate_location()) {
+ return promise.set_error(Status::Error(400, "Need local or generate location to upload call log"));
+ }
+
+ upload_log_file(file_id, std::move(promise));
+}
+
+void CallActor::upload_log_file(FileId file_id, Promise<Unit> &&promise) {
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ auto upload_file_id = file_manager->dup_file_id(file_id, "upload_log_file");
+ LOG(INFO) << "Ask to upload call log file " << upload_file_id;
+
+ class UploadLogFileCallback final : public FileManager::UploadCallback {
+ ActorId<CallActor> actor_id_;
+ FileId file_id_;
+ Promise<Unit> promise_;
+
+ public:
+ UploadLogFileCallback(ActorId<CallActor> actor_id, FileId file_id, Promise<Unit> &&promise)
+ : actor_id_(actor_id), file_id_(file_id), promise_(std::move(promise)) {
+ }
+
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
+ CHECK(file_id == file_id_);
+ send_closure_later(actor_id_, &CallActor::on_upload_log_file, file_id, std::move(promise_),
+ std::move(input_file));
+ }
+
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
+ UNREACHABLE();
+ }
+
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
+ UNREACHABLE();
+ }
+
+ void on_upload_error(FileId file_id, Status error) final {
+ CHECK(file_id == file_id_);
+ send_closure_later(actor_id_, &CallActor::on_upload_log_file_error, file_id, std::move(promise_),
+ std::move(error));
+ }
+ };
+
+ file_manager->upload(upload_file_id,
+ std::make_shared<UploadLogFileCallback>(actor_id(this), upload_file_id, std::move(promise)), 1,
+ 0);
+}
+
+void CallActor::on_upload_log_file(FileId file_id, Promise<Unit> &&promise,
+ tl_object_ptr<telegram_api::InputFile> input_file) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ LOG(INFO) << "Log file " << file_id << " has been uploaded";
+
+ do_upload_log_file(file_id, std::move(input_file), std::move(promise));
+}
+
+void CallActor::on_upload_log_file_error(FileId file_id, Promise<Unit> &&promise, Status status) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ LOG(WARNING) << "Log file " << file_id << " has upload error " << status;
+ CHECK(status.is_error());
+
+ promise.set_error(Status::Error(status.code() > 0 ? status.code() : 500,
+ status.message())); // TODO CHECK that status has always a code
+}
+
+void CallActor::do_upload_log_file(FileId file_id, tl_object_ptr<telegram_api::InputFile> &&input_file,
+ Promise<Unit> &&promise) {
+ if (input_file == nullptr) {
+ return promise.set_error(Status::Error(500, "Failed to reupload call log"));
+ }
+
+ auto tl_query = telegram_api::phone_saveCallLog(get_input_phone_call("do_upload_log_file"), std::move(input_file));
+ send_with_promise(G()->net_query_creator().create(tl_query),
+ PromiseCreator::lambda([actor_id = actor_id(this), file_id,
+ promise = std::move(promise)](Result<NetQueryPtr> r_net_query) mutable {
+ send_closure(actor_id, &CallActor::on_save_log_query_result, file_id, std::move(promise),
+ std::move(r_net_query));
+ }));
+ loop();
}
-//Updates
-//phoneCallEmpty#5366c915 id:long = PhoneCall;
-//phoneCallWaiting#1b8f4ad1 flags:# id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
+void CallActor::on_save_log_query_result(FileId file_id, Promise<Unit> promise, Result<NetQueryPtr> r_net_query) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ file_manager->delete_partial_remote_location(file_id);
+ file_manager->cancel_upload(file_id);
+
+ auto res = fetch_result<telegram_api::phone_saveCallLog>(std::move(r_net_query));
+ if (res.is_error()) {
+ auto error = res.move_as_error();
+ if (begins_with(error.message(), "FILE_PART_") && ends_with(error.message(), "_MISSING")) {
+ // TODO on_upload_log_file_part_missing(file_id, to_integer<int32>(error.message().substr(10)));
+ // return;
+ }
+ return promise.set_error(std::move(error));
+ }
+ if (call_state_.need_log) {
+ call_state_.need_log = false;
+ call_state_need_flush_ = true;
+ }
+ loop();
+ promise.set_value(Unit());
+}
// Requests
-//phone.discardCall#78d413a6 peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates;
void CallActor::update_call(tl_object_ptr<telegram_api::PhoneCall> call) {
+ LOG(INFO) << "Receive " << to_string(call);
Status status;
downcast_call(*call, [&](auto &call) { status = this->do_update_call(call); });
if (status.is_error()) {
+ LOG(INFO) << "Receive error " << status << ", while handling update " << to_string(call);
on_error(std::move(status));
}
loop();
}
void CallActor::update_call_inner(tl_object_ptr<telegram_api::phone_phoneCall> call) {
- send_closure(G()->contacts_manager(), &ContactsManager::on_get_users, std::move(call->users_));
+ LOG(INFO) << "Update call with " << to_string(call);
+ send_closure(G()->contacts_manager(), &ContactsManager::on_get_users, std::move(call->users_), "UpdatePhoneCall");
update_call(std::move(call->phone_call_));
}
@@ -245,29 +497,30 @@ Status CallActor::do_update_call(telegram_api::phoneCallEmpty &call) {
return Status::Error(400, "Call is finished");
}
-//phoneCallWaiting#1b8f4ad1 flags:# id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
Status CallActor::do_update_call(telegram_api::phoneCallWaiting &call) {
if (state_ != State::WaitRequestResult && state_ != State::WaitAcceptResult) {
return Status::Error(500, PSLICE() << "Drop unexpected " << to_string(call));
}
if (state_ == State::WaitAcceptResult) {
- call_state_.type = CallState::Type::ExchangingKey;
- call_state_need_flush_ = true;
- cancel_timeout();
+ LOG(DEBUG) << "Do update call to Waiting";
+ on_begin_exchanging_key();
} else {
+ LOG(DEBUG) << "Do update call to Waiting";
if ((call.flags_ & telegram_api::phoneCallWaiting::RECEIVE_DATE_MASK) != 0) {
call_state_.is_received = true;
call_state_need_flush_ = true;
- int32 call_ring_timeout_ms = G()->shared_config().get_option_integer("call_ring_timeout_ms", 90000);
- set_timeout_in(call_ring_timeout_ms * 0.001);
+ int64 call_ring_timeout_ms = G()->get_option_integer("call_ring_timeout_ms", 90000);
+ set_timeout_in(static_cast<double>(call_ring_timeout_ms) * 0.001);
}
}
call_id_ = call.id_;
call_access_hash_ = call.access_hash_;
- call_admin_id_ = call.admin_id_;
- call_participant_id_ = call.participant_id_;
+ is_call_id_inited_ = true;
+ is_video_ |= call.video_;
+ call_admin_user_id_ = UserId(call.admin_id_);
+ // call_participant_user_id_ = UserId(call.participant_id_);
if (call_id_promise_) {
call_id_promise_.set_value(std::move(call.id_));
}
@@ -280,15 +533,17 @@ Status CallActor::do_update_call(telegram_api::phoneCallWaiting &call) {
return Status::OK();
}
-//phoneCallRequested#83761ce4 id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
Status CallActor::do_update_call(telegram_api::phoneCallRequested &call) {
if (state_ != State::Empty) {
return Status::Error(500, PSLICE() << "Drop unexpected " << to_string(call));
}
+ LOG(DEBUG) << "Do update call to Requested";
call_id_ = call.id_;
call_access_hash_ = call.access_hash_;
- call_admin_id_ = call.admin_id_;
- call_participant_id_ = call.participant_id_;
+ is_call_id_inited_ = true;
+ is_video_ |= call.video_;
+ call_admin_user_id_ = UserId(call.admin_id_);
+ // call_participant_user_id_ = UserId(call.participant_id_);
if (call_id_promise_) {
call_id_promise_.set_value(std::move(call.id_));
}
@@ -305,36 +560,57 @@ Status CallActor::do_update_call(telegram_api::phoneCallRequested &call) {
return Status::OK();
}
-tl_object_ptr<telegram_api::inputPhoneCall> CallActor::get_input_phone_call() {
- CHECK(call_id_ != 0);
+tl_object_ptr<telegram_api::inputPhoneCall> CallActor::get_input_phone_call(const char *source) {
+ LOG_CHECK(is_call_id_inited_) << source;
return make_tl_object<telegram_api::inputPhoneCall>(call_id_, call_access_hash_);
}
-//phoneCallAccepted#6d003d3f id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
Status CallActor::do_update_call(telegram_api::phoneCallAccepted &call) {
if (state_ != State::WaitRequestResult) {
return Status::Error(500, PSLICE() << "Drop unexpected " << to_string(call));
}
+ LOG(DEBUG) << "Do update call to Accepted";
+ if (!is_call_id_inited_) {
+ call_id_ = call.id_;
+ call_access_hash_ = call.access_hash_;
+ is_call_id_inited_ = true;
+ call_admin_user_id_ = UserId(call.admin_id_);
+ // call_participant_user_id_ = UserId(call.participant_id_);
+ if (call_id_promise_) {
+ call_id_promise_.set_value(std::move(call.id_));
+ }
+ }
+ is_video_ |= call.video_;
dh_handshake_.set_g_a(call.g_b_.as_slice());
- TRY_STATUS(dh_handshake_.run_checks(DhCache::instance()));
+ TRY_STATUS(dh_handshake_.run_checks(true, DhCache::instance()));
std::tie(call_state_.key_fingerprint, call_state_.key) = dh_handshake_.gen_key();
state_ = State::SendConfirmQuery;
+ on_begin_exchanging_key();
+ return Status::OK();
+}
+
+void CallActor::on_begin_exchanging_key() {
call_state_.type = CallState::Type::ExchangingKey;
call_state_need_flush_ = true;
- cancel_timeout();
- return Status::OK();
+ int64 call_receive_timeout_ms = G()->get_option_integer("call_receive_timeout_ms", 20000);
+ auto timeout = static_cast<double>(call_receive_timeout_ms) * 0.001;
+ LOG(INFO) << "Set call timeout to " << timeout;
+ set_timeout_in(timeout);
}
-//phoneCall#ffe6ab67 id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connection:PhoneConnection alternative_connections:Vector<PhoneConnection> start_date:int = PhoneCall;
Status CallActor::do_update_call(telegram_api::phoneCall &call) {
if (state_ != State::WaitAcceptResult && state_ != State::WaitConfirmResult) {
return Status::Error(500, PSLICE() << "Drop unexpected " << to_string(call));
}
+ cancel_timeout();
+ is_video_ |= call.video_;
+
+ LOG(DEBUG) << "Do update call to Ready from state " << static_cast<int32>(state_);
if (state_ == State::WaitAcceptResult) {
dh_handshake_.set_g_a(call.g_a_or_b_.as_slice());
- TRY_STATUS(dh_handshake_.run_checks(DhCache::instance()));
+ TRY_STATUS(dh_handshake_.run_checks(true, DhCache::instance()));
std::tie(call_state_.key_fingerprint, call_state_.key) = dh_handshake_.gen_key();
}
if (call_state_.key_fingerprint != call.key_fingerprint_) {
@@ -344,36 +620,41 @@ Status CallActor::do_update_call(telegram_api::phoneCall &call) {
call_state_.emojis_fingerprint =
get_emojis_fingerprint(call_state_.key, is_outgoing_ ? dh_handshake_.get_g_b() : dh_handshake_.get_g_a());
- call_state_.connections.push_back(CallConnection::from_telegram_api(*call.connection_));
- for (auto &connection : call.alternative_connections_) {
- call_state_.connections.push_back(CallConnection::from_telegram_api(*connection));
+ for (auto &connection : call.connections_) {
+ call_state_.connections.emplace_back(*connection);
}
- call_state_.protocol = CallProtocol::from_telegram_api(*call.protocol_);
+ call_state_.protocol = CallProtocol(*call.protocol_);
+ call_state_.allow_p2p = call.p2p_allowed_;
call_state_.type = CallState::Type::Ready;
call_state_need_flush_ = true;
return Status::OK();
}
-//phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
Status CallActor::do_update_call(telegram_api::phoneCallDiscarded &call) {
+ LOG(DEBUG) << "Do update call to Discarded";
+ on_call_discarded(get_call_discard_reason(call.reason_), call.need_rating_, call.need_debug_, call.video_);
+ return Status::OK();
+}
+
+void CallActor::on_call_discarded(CallDiscardReason reason, bool need_rating, bool need_debug, bool is_video) {
state_ = State::Discarded;
+ is_video_ |= is_video;
- auto reason = get_call_discard_reason(call.reason_);
if (call_state_.discard_reason == CallDiscardReason::Empty || reason != CallDiscardReason::Empty) {
call_state_.discard_reason = reason;
}
if (call_state_.type != CallState::Type::Error) {
- call_state_.need_rating = call.need_rating_;
- call_state_.need_debug_information = call.need_debug_;
+ call_state_.need_rating = need_rating;
+ call_state_.need_debug_information = need_debug;
call_state_.type = CallState::Type::Discarded;
call_state_need_flush_ = true;
}
- return Status::OK();
}
bool CallActor::load_dh_config() {
if (dh_config_ready_) {
+ LOG(DEBUG) << "Dh config is ready";
return true;
}
if (!dh_config_query_sent_) {
@@ -382,12 +663,18 @@ bool CallActor::load_dh_config() {
send_closure(actor_id, &CallActor::on_dh_config, std::move(dh_config), false);
}));
}
+ LOG(INFO) << "Dh config is not loaded";
return false;
}
void CallActor::on_error(Status status) {
CHECK(status.is_error());
+ LOG(INFO) << "Receive error " << status;
+ if (state_ == State::WaitRequestResult && !request_query_ref_.empty()) {
+ LOG(INFO) << "Cancel request call query";
+ cancel_query(request_query_ref_);
+ }
if (state_ == State::WaitDiscardResult || state_ == State::Discarded) {
state_ = State::Discarded;
} else {
@@ -405,8 +692,14 @@ void CallActor::on_dh_config(Result<std::shared_ptr<DhConfig>> r_dh_config, bool
if (r_dh_config.is_error()) {
return on_error(r_dh_config.move_as_error());
}
- dh_config_ready_ = true;
+
dh_config_ = r_dh_config.move_as_ok();
+ auto check_result = mtproto::DhHandshake::check_config(dh_config_->g, dh_config_->prime, DhCache::instance());
+ if (check_result.is_error()) {
+ return on_error(std::move(check_result));
+ }
+
+ dh_config_ready_ = true;
yield();
}
@@ -419,7 +712,7 @@ void CallActor::do_load_dh_config(Promise<std::shared_ptr<DhConfig>> promise) {
}
int random_length = 0;
telegram_api::messages_getDhConfig tl_query(version, random_length);
- auto query = G()->net_query_creator().create(create_storer(tl_query));
+ auto query = G()->net_query_creator().create(tl_query);
send_with_promise(std::move(query),
PromiseCreator::lambda([actor_id = actor_id(this), old_dh_config = std::move(dh_config),
promise = std::move(promise)](Result<NetQueryPtr> result_query) mutable {
@@ -432,8 +725,12 @@ void CallActor::do_load_dh_config(Promise<std::shared_ptr<DhConfig>> promise) {
dh_config->version = dh->version_;
dh_config->prime = dh->p_.as_slice().str();
dh_config->g = dh->g_;
+ Random::add_seed(dh->random_.as_slice());
G()->set_dh_config(dh_config);
return std::move(dh_config);
+ } else if (new_dh_config->get_id() == telegram_api::messages_dhConfigNotModified::ID) {
+ auto dh = move_tl_object_as<telegram_api::messages_dhConfigNotModified>(new_dh_config);
+ Random::add_seed(dh->random_.as_slice());
}
if (old_dh_config) {
return std::move(old_dh_config);
@@ -444,41 +741,53 @@ void CallActor::do_load_dh_config(Promise<std::shared_ptr<DhConfig>> promise) {
}
void CallActor::send_received_query() {
- auto tl_query = telegram_api::phone_receivedCall(get_input_phone_call());
- auto query = G()->net_query_creator().create(create_storer(tl_query));
- send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) {
- send_closure(actor_id, &CallActor::on_received_query_result, std::move(net_query));
+ auto tl_query = telegram_api::phone_receivedCall(get_input_phone_call("send_received_query"));
+ auto query = G()->net_query_creator().create(tl_query);
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<NetQueryPtr> r_net_query) {
+ send_closure(actor_id, &CallActor::on_received_query_result, std::move(r_net_query));
}));
}
-void CallActor::on_received_query_result(NetQueryPtr net_query) {
- auto res = fetch_result<telegram_api::phone_receivedCall>(std::move(net_query));
+void CallActor::on_received_query_result(Result<NetQueryPtr> r_net_query) {
+ auto res = fetch_result<telegram_api::phone_receivedCall>(std::move(r_net_query));
if (res.is_error()) {
return on_error(res.move_as_error());
}
}
-//phone.requestCall#5b95b3d4 user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
void CallActor::try_send_request_query() {
+ LOG(INFO) << "Trying to send request query";
if (!load_dh_config()) {
return;
}
dh_handshake_.set_config(dh_config_->g, dh_config_->prime);
CHECK(input_user_ != nullptr);
- auto tl_query = telegram_api::phone_requestCall(std::move(input_user_), Random::secure_int32(),
- BufferSlice(dh_handshake_.get_g_b_hash()),
- call_state_.protocol.as_telegram_api());
- auto query = G()->net_query_creator().create(create_storer(tl_query));
+ int32 flags = 0;
+ if (is_video_) {
+ flags |= telegram_api::phone_requestCall::VIDEO_MASK;
+ }
+ auto tl_query = telegram_api::phone_requestCall(flags, false /*ignored*/, std::move(input_user_),
+ Random::secure_int32(), BufferSlice(dh_handshake_.get_g_b_hash()),
+ call_state_.protocol.get_input_phone_call_protocol());
+ auto query = G()->net_query_creator().create(tl_query);
state_ = State::WaitRequestResult;
- send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) {
- send_closure(actor_id, &CallActor::on_request_query_result, std::move(net_query));
+ int64 call_receive_timeout_ms = G()->get_option_integer("call_receive_timeout_ms", 20000);
+ auto timeout = static_cast<double>(call_receive_timeout_ms) * 0.001;
+ LOG(INFO) << "Set call timeout to " << timeout;
+ set_timeout_in(timeout);
+ query->total_timeout_limit_ =
+ static_cast<int32>(clamp(call_receive_timeout_ms + 999, static_cast<int64>(10000), static_cast<int64>(100000))) /
+ 1000;
+ request_query_ref_ = query.get_weak();
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<NetQueryPtr> r_net_query) {
+ send_closure(actor_id, &CallActor::on_request_query_result, std::move(r_net_query));
}));
- int32 call_receive_timeout_ms = G()->shared_config().get_option_integer("call_receive_timeout_ms", 20000);
- set_timeout_in(call_receive_timeout_ms * 0.001);
}
-void CallActor::on_request_query_result(NetQueryPtr net_query) {
- auto res = fetch_result<telegram_api::phone_requestCall>(std::move(net_query));
+void CallActor::on_request_query_result(Result<NetQueryPtr> r_net_query) {
+ auto res = fetch_result<telegram_api::phone_requestCall>(std::move(r_net_query));
if (res.is_error()) {
return on_error(res.move_as_error());
}
@@ -487,24 +796,28 @@ void CallActor::on_request_query_result(NetQueryPtr net_query) {
//phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
void CallActor::try_send_accept_query() {
+ LOG(INFO) << "Trying to send accept query";
if (!load_dh_config()) {
return;
}
if (!is_accepted_) {
+ LOG(DEBUG) << "Call is not accepted";
return;
}
dh_handshake_.set_config(dh_config_->g, dh_config_->prime);
- auto tl_query = telegram_api::phone_acceptCall(get_input_phone_call(), BufferSlice(dh_handshake_.get_g_b()),
- call_state_.protocol.as_telegram_api());
- auto query = G()->net_query_creator().create(create_storer(tl_query));
+ auto tl_query = telegram_api::phone_acceptCall(get_input_phone_call("try_send_accept_query"),
+ BufferSlice(dh_handshake_.get_g_b()),
+ call_state_.protocol.get_input_phone_call_protocol());
+ auto query = G()->net_query_creator().create(tl_query);
state_ = State::WaitAcceptResult;
- send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) {
- send_closure(actor_id, &CallActor::on_accept_query_result, std::move(net_query));
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<NetQueryPtr> r_net_query) {
+ send_closure(actor_id, &CallActor::on_accept_query_result, std::move(r_net_query));
}));
}
-void CallActor::on_accept_query_result(NetQueryPtr net_query) {
- auto res = fetch_result<telegram_api::phone_acceptCall>(std::move(net_query));
+void CallActor::on_accept_query_result(Result<NetQueryPtr> r_net_query) {
+ auto res = fetch_result<telegram_api::phone_acceptCall>(std::move(r_net_query));
if (res.is_error()) {
return on_error(res.move_as_error());
}
@@ -513,20 +826,23 @@ void CallActor::on_accept_query_result(NetQueryPtr net_query) {
//phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
void CallActor::try_send_confirm_query() {
+ LOG(INFO) << "Trying to send confirm query";
if (!load_dh_config()) {
return;
}
- auto tl_query = telegram_api::phone_confirmCall(get_input_phone_call(), BufferSlice(dh_handshake_.get_g_b()),
- call_state_.key_fingerprint, call_state_.protocol.as_telegram_api());
- auto query = G()->net_query_creator().create(create_storer(tl_query));
+ auto tl_query = telegram_api::phone_confirmCall(get_input_phone_call("try_send_confirm_query"),
+ BufferSlice(dh_handshake_.get_g_b()), call_state_.key_fingerprint,
+ call_state_.protocol.get_input_phone_call_protocol());
+ auto query = G()->net_query_creator().create(tl_query);
state_ = State::WaitConfirmResult;
- send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) {
- send_closure(actor_id, &CallActor::on_confirm_query_result, std::move(net_query));
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<NetQueryPtr> r_net_query) {
+ send_closure(actor_id, &CallActor::on_confirm_query_result, std::move(r_net_query));
}));
}
-void CallActor::on_confirm_query_result(NetQueryPtr net_query) {
- auto res = fetch_result<telegram_api::phone_confirmCall>(std::move(net_query));
+void CallActor::on_confirm_query_result(Result<NetQueryPtr> r_net_query) {
+ auto res = fetch_result<telegram_api::phone_confirmCall>(std::move(r_net_query));
if (res.is_error()) {
return on_error(res.move_as_error());
}
@@ -535,54 +851,78 @@ void CallActor::on_confirm_query_result(NetQueryPtr net_query) {
void CallActor::try_send_discard_query() {
if (call_id_ == 0) {
- state_ = State::Discarded;
+ LOG(INFO) << "Failed to send discard query, because call_id_ is unknown";
+ on_call_discarded(CallDiscardReason::Missed, false, false, is_video_);
yield();
return;
}
- auto tl_query =
- telegram_api::phone_discardCall(get_input_phone_call(), duration_,
- get_input_phone_call_discard_reason(call_state_.discard_reason), connection_id_);
- auto query = G()->net_query_creator().create(create_storer(tl_query));
+ LOG(INFO) << "Trying to send discard query";
+ int32 flags = 0;
+ if (is_video_) {
+ flags |= telegram_api::phone_discardCall::VIDEO_MASK;
+ }
+ auto tl_query = telegram_api::phone_discardCall(
+ flags, false /*ignored*/, get_input_phone_call("try_send_discard_query"), duration_,
+ get_input_phone_call_discard_reason(call_state_.discard_reason), connection_id_);
+ auto query = G()->net_query_creator().create(tl_query);
state_ = State::WaitDiscardResult;
- send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) {
- send_closure(actor_id, &CallActor::on_discard_query_result, std::move(net_query));
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<NetQueryPtr> r_net_query) {
+ send_closure(actor_id, &CallActor::on_discard_query_result, std::move(r_net_query));
}));
}
-void CallActor::on_discard_query_result(NetQueryPtr net_query) {
- auto res = fetch_result<telegram_api::phone_discardCall>(std::move(net_query));
+void CallActor::on_discard_query_result(Result<NetQueryPtr> r_net_query) {
+ auto res = fetch_result<telegram_api::phone_discardCall>(std::move(r_net_query));
if (res.is_error()) {
return on_error(res.move_as_error());
}
- send_closure(G()->updates_manager(), &UpdatesManager::on_get_updates, res.move_as_ok());
+ send_closure(G()->updates_manager(), &UpdatesManager::on_get_updates, res.move_as_ok(), Promise<Unit>());
}
void CallActor::flush_call_state() {
if (call_state_need_flush_) {
+ if (!is_outgoing_) {
+ if (call_state_.type == CallState::Type::Pending) {
+ if (!has_notification_) {
+ has_notification_ = true;
+ send_closure(G()->notification_manager(), &NotificationManager::add_call_notification,
+ DialogId(call_admin_user_id_), local_call_id_);
+ }
+ } else {
+ if (has_notification_) {
+ has_notification_ = false;
+ send_closure(G()->notification_manager(), &NotificationManager::remove_call_notification,
+ DialogId(call_admin_user_id_), local_call_id_);
+ }
+ }
+ }
+
if (call_state_.type == CallState::Type::Ready && !call_state_has_config_) {
return;
}
call_state_need_flush_ = false;
- // can't call const function
+ // TODO can't call const function
// send_closure(G()->contacts_manager(), &ContactsManager::get_user_id_object, user_id_, "flush_call_state");
send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateCall>(
- make_tl_object<td_api::call>(local_call_id_.get(), is_outgoing_ ? user_id_.get() : call_admin_id_,
- is_outgoing_, call_state_.as_td_api())));
+ make_tl_object<td_api::updateCall>(make_tl_object<td_api::call>(
+ local_call_id_.get(), is_outgoing_ ? user_id_.get() : call_admin_user_id_.get(), is_outgoing_,
+ is_video_, call_state_.get_call_state_object())));
}
}
void CallActor::start_up() {
auto tl_query = telegram_api::phone_getCallConfig();
- auto query = G()->net_query_creator().create(create_storer(tl_query));
- send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this)](NetQueryPtr net_query) {
- send_closure(actor_id, &CallActor::on_get_call_config_result, std::move(net_query));
+ auto query = G()->net_query_creator().create(tl_query);
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<NetQueryPtr> r_net_query) {
+ send_closure(actor_id, &CallActor::on_get_call_config_result, std::move(r_net_query));
}));
}
-void CallActor::on_get_call_config_result(NetQueryPtr net_query) {
- auto res = fetch_result<telegram_api::phone_getCallConfig>(std::move(net_query));
+void CallActor::on_get_call_config_result(Result<NetQueryPtr> r_net_query) {
+ auto res = fetch_result<telegram_api::phone_getCallConfig>(std::move(r_net_query));
if (res.is_error()) {
return on_error(res.move_as_error());
}
@@ -591,6 +931,8 @@ void CallActor::on_get_call_config_result(NetQueryPtr net_query) {
}
void CallActor::loop() {
+ LOG(DEBUG) << "Enter loop for " << call_id_ << " in state " << static_cast<int32>(state_) << '/'
+ << static_cast<int32>(call_state_.type);
flush_call_state();
switch (state_) {
case State::SendRequestQuery:
@@ -607,10 +949,12 @@ void CallActor::loop() {
break;
case State::Discarded: {
if (call_state_.type == CallState::Type::Discarded &&
- (call_state_.need_rating || call_state_.need_debug_information)) {
+ (call_state_.need_rating || call_state_.need_debug_information || call_state_.need_log)) {
break;
}
- LOG(INFO) << "Close call " << local_call_id_;
+ LOG(INFO) << "Close " << local_call_id_;
+ container_.for_each(
+ [](auto id, Promise<NetQueryPtr> &promise) { promise.set_error(Global::request_aborted_error()); });
stop();
break;
}
@@ -635,6 +979,12 @@ void CallActor::send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> promis
G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, id));
}
+void CallActor::hangup() {
+ container_.for_each(
+ [](auto id, Promise<NetQueryPtr> &promise) { promise.set_error(Global::request_aborted_error()); });
+ stop();
+}
+
vector<string> CallActor::get_emojis_fingerprint(const string &key, const string &g_a) {
string str = key + g_a;
unsigned char sha256_buf[32];
@@ -643,11 +993,7 @@ vector<string> CallActor::get_emojis_fingerprint(const string &key, const string
vector<string> result;
result.reserve(4);
for (int i = 0; i < 4; i++) {
- uint64 num =
- (static_cast<uint64>(sha256_buf[8 * i + 0]) << 56) | (static_cast<uint64>(sha256_buf[8 * i + 1]) << 48) |
- (static_cast<uint64>(sha256_buf[8 * i + 2]) << 40) | (static_cast<uint64>(sha256_buf[8 * i + 3]) << 32) |
- (static_cast<uint64>(sha256_buf[8 * i + 4]) << 24) | (static_cast<uint64>(sha256_buf[8 * i + 5]) << 16) |
- (static_cast<uint64>(sha256_buf[8 * i + 6]) << 8) | (static_cast<uint64>(sha256_buf[8 * i + 7]));
+ uint64 num = big_endian_to_host64(as<uint64>(sha256_buf + 8 * i));
result.push_back(get_emoji_fingerprint(num));
}
return result;
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CallActor.h b/protocols/Telegram/tdlib/td/td/telegram/CallActor.h
index 02960bb3f1..cb674ac55c 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/CallActor.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/CallActor.h
@@ -1,86 +1,111 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/mtproto/crypto.h"
-
#include "td/telegram/CallDiscardReason.h"
#include "td/telegram/CallId.h"
#include "td/telegram/DhConfig.h"
+#include "td/telegram/files/FileId.h"
#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
+#include "td/mtproto/DhHandshake.h"
+
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/utils/common.h"
#include "td/utils/Container.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
#include <memory>
namespace td {
+
struct CallProtocol {
bool udp_p2p{true};
bool udp_reflector{true};
int32 min_layer{65};
int32 max_layer{65};
+ vector<string> library_versions;
+
+ CallProtocol() = default;
+
+ explicit CallProtocol(const td_api::callProtocol &protocol);
- static CallProtocol from_telegram_api(const telegram_api::phoneCallProtocol &protocol);
- tl_object_ptr<telegram_api::phoneCallProtocol> as_telegram_api() const;
- static CallProtocol from_td_api(const td_api::callProtocol &protocol);
- tl_object_ptr<td_api::callProtocol> as_td_api() const;
+ explicit CallProtocol(const telegram_api::phoneCallProtocol &protocol);
+
+ tl_object_ptr<telegram_api::phoneCallProtocol> get_input_phone_call_protocol() const;
+
+ tl_object_ptr<td_api::callProtocol> get_call_protocol_object() const;
};
struct CallConnection {
+ enum class Type : int32 { Telegram, Webrtc };
+ Type type;
int64 id;
string ip;
string ipv6;
int32 port;
+
+ // Telegram
string peer_tag;
+ bool is_tcp = false;
- static CallConnection from_telegram_api(const telegram_api::phoneConnection &connection);
- tl_object_ptr<telegram_api::phoneConnection> as_telegram_api() const;
- tl_object_ptr<td_api::callConnection> as_td_api() const;
+ // WebRTC
+ string username;
+ string password;
+ bool supports_turn = false;
+ bool supports_stun = false;
+
+ explicit CallConnection(const telegram_api::PhoneConnection &connection);
+
+ tl_object_ptr<td_api::callServer> get_call_server_object() const;
};
struct CallState {
- enum class Type { Empty, Pending, ExchangingKey, Ready, HangingUp, Discarded, Error } type{Type::Empty};
+ enum class Type : int32 { Empty, Pending, ExchangingKey, Ready, HangingUp, Discarded, Error } type{Type::Empty};
CallProtocol protocol;
- std::vector<CallConnection> connections;
+ vector<CallConnection> connections;
CallDiscardReason discard_reason{CallDiscardReason::Empty};
bool is_created{false};
bool is_received{false};
bool need_debug_information{false};
bool need_rating{false};
+ bool need_log{false};
int64 key_fingerprint{0};
string key;
string config;
vector<string> emojis_fingerprint;
+ bool allow_p2p{false};
Status error;
- tl_object_ptr<td_api::CallState> as_td_api() const;
+ tl_object_ptr<td_api::CallState> get_call_state_object() const;
};
-class CallActor : public NetQueryCallback {
+class CallActor final : public NetQueryCallback {
public:
CallActor(CallId call_id, ActorShared<> parent, Promise<int64> promise);
void create_call(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user, CallProtocol &&protocol,
- Promise<CallId> &&promise);
- void discard_call(bool is_disconnected, int32 duration, int64 connection_id, Promise<> promise);
- void accept_call(CallProtocol &&protocol, Promise<> promise);
- void rate_call(int32 rating, string comment, Promise<> promise);
- void send_call_debug_information(string data, Promise<> promise);
+ bool is_video, Promise<CallId> &&promise);
+ void accept_call(CallProtocol &&protocol, Promise<Unit> promise);
+ void update_call_signaling_data(string data);
+ void send_call_signaling_data(string &&data, Promise<Unit> promise);
+ void discard_call(bool is_disconnected, int32 duration, bool is_video, int64 connection_id, Promise<Unit> promise);
+ void rate_call(int32 rating, string comment, vector<td_api::object_ptr<td_api::CallProblem>> &&problems,
+ Promise<Unit> promise);
+ void send_call_debug_information(string data, Promise<Unit> promise);
+ void send_call_log(td_api::object_ptr<td_api::InputFile> log_file, Promise<Unit> promise);
void update_call(tl_object_ptr<telegram_api::PhoneCall> call);
@@ -89,7 +114,7 @@ class CallActor : public NetQueryCallback {
ActorShared<> parent_;
Promise<int64> call_id_promise_;
- DhHandshake dh_handshake_;
+ mtproto::DhHandshake dh_handshake_;
std::shared_ptr<DhConfig> dh_config_;
bool dh_config_query_sent_{false};
bool dh_config_ready_{false};
@@ -97,7 +122,7 @@ class CallActor : public NetQueryCallback {
int32 duration_{0};
int64 connection_id_{0};
- enum class State {
+ enum class State : int32 {
Empty,
SendRequestQuery,
WaitRequestResult,
@@ -112,20 +137,25 @@ class CallActor : public NetQueryCallback {
bool is_accepted_{false};
bool is_outgoing_{false};
+ bool is_video_{false};
UserId user_id_;
tl_object_ptr<telegram_api::InputUser> input_user_;
CallId local_call_id_;
int64 call_id_{0};
+ bool is_call_id_inited_{false};
+ bool has_notification_{false};
int64 call_access_hash_{0};
- int32 call_admin_id_{0};
- int32 call_participant_id_{0};
+ UserId call_admin_user_id_;
+ // UserId call_participant_user_id_;
CallState call_state_;
bool call_state_need_flush_{false};
bool call_state_has_config_{false};
- tl_object_ptr<telegram_api::inputPhoneCall> get_input_phone_call();
+ NetQueryRef request_query_ref_;
+
+ tl_object_ptr<telegram_api::inputPhoneCall> get_input_phone_call(const char *source);
bool load_dh_config();
void on_dh_config(Result<std::shared_ptr<DhConfig>> r_dh_config, bool dummy);
void do_load_dh_config(Promise<std::shared_ptr<DhConfig>> promise);
@@ -138,38 +168,55 @@ class CallActor : public NetQueryCallback {
Status do_update_call(telegram_api::phoneCallDiscarded &call);
void send_received_query();
- void on_received_query_result(NetQueryPtr net_query);
+ void on_received_query_result(Result<NetQueryPtr> r_net_query);
void try_send_request_query();
- void on_request_query_result(NetQueryPtr net_query);
+ void on_request_query_result(Result<NetQueryPtr> r_net_query);
void try_send_accept_query();
- void on_accept_query_result(NetQueryPtr net_query);
+ void on_accept_query_result(Result<NetQueryPtr> r_net_query);
void try_send_confirm_query();
- void on_confirm_query_result(NetQueryPtr net_query);
+ void on_confirm_query_result(Result<NetQueryPtr> r_net_query);
void try_send_discard_query();
- void on_discard_query_result(NetQueryPtr net_query);
+ void on_discard_query_result(Result<NetQueryPtr> r_net_query);
+
+ void on_begin_exchanging_key();
+
+ void on_call_discarded(CallDiscardReason reason, bool need_rating, bool need_debug, bool is_video);
+
+ void on_set_rating_query_result(Result<NetQueryPtr> r_net_query);
- void on_set_rating_query_result(NetQueryPtr net_query);
- void on_set_debug_query_result(NetQueryPtr net_query);
+ void on_save_debug_query_result(Result<NetQueryPtr> r_net_query);
- void on_get_call_config_result(NetQueryPtr net_query);
+ void upload_log_file(FileId file_id, Promise<Unit> &&promise);
+
+ void on_upload_log_file(FileId file_id, Promise<Unit> &&promise, tl_object_ptr<telegram_api::InputFile> input_file);
+
+ void on_upload_log_file_error(FileId file_id, Promise<Unit> &&promise, Status status);
+
+ void do_upload_log_file(FileId file_id, tl_object_ptr<telegram_api::InputFile> &&input_file, Promise<Unit> &&promise);
+
+ void on_save_log_query_result(FileId file_id, Promise<Unit> promise, Result<NetQueryPtr> r_net_query);
+
+ void on_get_call_config_result(Result<NetQueryPtr> r_net_query);
void flush_call_state();
static vector<string> get_emojis_fingerprint(const string &key, const string &g_a);
- void start_up() override;
- void loop() override;
+ void start_up() final;
+ void loop() final;
Container<Promise<NetQueryPtr>> container_;
- void on_result(NetQueryPtr query) override;
+ void on_result(NetQueryPtr query) final;
void send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> promise);
- void timeout_expired() override;
+ void timeout_expired() final;
+ void hangup() final;
void on_error(Status status);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.cpp b/protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.cpp
index 2b6fbbc5d7..0171aa1b51 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.cpp
@@ -1,15 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/CallDiscardReason.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
namespace td {
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.h b/protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.h
index d4c00e5214..8725dbb299 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/CallDiscardReason.h
@@ -1,25 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/tl/TlObject.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/utils/int_types.h"
namespace td {
-namespace td_api {
-class CallDiscardReason;
-} // namespace td_api
-
-namespace telegram_api {
-class PhoneCallDiscardReason;
-} // namespace telegram_api
-
enum class CallDiscardReason : int32 { Empty, Missed, Disconnected, HungUp, Declined };
CallDiscardReason get_call_discard_reason(const tl_object_ptr<telegram_api::PhoneCallDiscardReason> &reason);
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CallId.h b/protocols/Telegram/tdlib/td/td/telegram/CallId.h
index 227e2240d9..643b28c877 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/CallId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/CallId.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,12 +9,13 @@
#include "td/telegram/td_api.h"
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/StringBuilder.h"
-#include <functional>
#include <type_traits>
namespace td {
+
class CallId {
public:
CallId() = default;
@@ -33,8 +34,8 @@ class CallId {
return id;
}
- auto as_td_api() const {
- return make_tl_object<td_api::callId>(id);
+ auto get_call_id_object() const {
+ return td_api::make_object<td_api::callId>(id);
}
bool operator==(const CallId &other) const {
@@ -46,12 +47,13 @@ class CallId {
};
struct CallIdHash {
- std::size_t operator()(CallId call_id) const {
- return std::hash<int32>()(call_id.get());
+ uint32 operator()(CallId call_id) const {
+ return Hash<int32>()(call_id.get());
}
};
inline StringBuilder &operator<<(StringBuilder &sb, const CallId call_id) {
- return sb << "CallId(" << call_id.get() << ")";
+ return sb << "call " << call_id.get();
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CallManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/CallManager.cpp
index 4b17b26de4..08cb4e0ff1 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/CallManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/CallManager.cpp
@@ -1,18 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/CallManager.h"
-#include "td/telegram/Global.h"
+#include "td/telegram/telegram_api.hpp"
-#include "td/utils/format.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
-
-#include "td/telegram/telegram_api.hpp"
+#include "td/utils/SliceBuilder.h"
#include <limits>
@@ -24,6 +23,7 @@ CallManager::CallManager(ActorShared<> parent) : parent_(std::move(parent)) {
void CallManager::update_call(Update call) {
int64 call_id = 0;
downcast_call(*call->phone_call_, [&](auto &update) { call_id = update.id_; });
+ LOG(DEBUG) << "Receive UpdateCall for " << call_id;
auto &info = call_info_[call_id];
@@ -32,57 +32,99 @@ void CallManager::update_call(Update call) {
}
if (!info.call_id.is_valid()) {
+ LOG(INFO) << "Call_id is not valid for " << call_id << ", postpone update " << to_string(call);
info.updates.push_back(std::move(call));
return;
}
auto actor = get_call_actor(info.call_id);
if (actor.empty()) {
- LOG(WARNING) << "Drop update: " << to_string(call);
+ LOG(INFO) << "Drop update: " << to_string(call);
}
send_closure(actor, &CallActor::update_call, std::move(call->phone_call_));
}
+void CallManager::update_call_signaling_data(int64 call_id, string data) {
+ auto info_it = call_info_.find(call_id);
+ if (info_it == call_info_.end() || !info_it->second.call_id.is_valid()) {
+ LOG(INFO) << "Ignore signaling data for " << call_id;
+ return;
+ }
+
+ auto actor = get_call_actor(info_it->second.call_id);
+ if (actor.empty()) {
+ LOG(INFO) << "Ignore signaling data for " << info_it->second.call_id;
+ return;
+ }
+ send_closure(actor, &CallActor::update_call_signaling_data, std::move(data));
+}
+
void CallManager::create_call(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user,
- CallProtocol &&protocol, Promise<CallId> promise) {
+ CallProtocol &&protocol, bool is_video, Promise<CallId> promise) {
LOG(INFO) << "Create call with " << user_id;
auto call_id = create_call_actor();
auto actor = get_call_actor(call_id);
CHECK(!actor.empty());
- send_closure(actor, &CallActor::create_call, user_id, std::move(input_user), std::move(protocol), std::move(promise));
+ auto safe_promise = SafePromise<CallId>(std::move(promise), Status::Error(400, "Call not found"));
+ send_closure(actor, &CallActor::create_call, user_id, std::move(input_user), std::move(protocol), is_video,
+ std::move(safe_promise));
+}
+
+void CallManager::accept_call(CallId call_id, CallProtocol &&protocol, Promise<Unit> promise) {
+ auto actor = get_call_actor(call_id);
+ if (actor.empty()) {
+ return promise.set_error(Status::Error(400, "Call not found"));
+ }
+ auto safe_promise = SafePromise<Unit>(std::move(promise), Status::Error(400, "Call not found"));
+ send_closure(actor, &CallActor::accept_call, std::move(protocol), std::move(safe_promise));
}
-void CallManager::discard_call(CallId call_id, bool is_disconnected, int32 duration, int64 connection_id,
- Promise<> promise) {
+void CallManager::send_call_signaling_data(CallId call_id, string &&data, Promise<Unit> promise) {
auto actor = get_call_actor(call_id);
if (actor.empty()) {
return promise.set_error(Status::Error(400, "Call not found"));
}
- send_closure(actor, &CallActor::discard_call, is_disconnected, duration, connection_id, std::move(promise));
+ auto safe_promise = SafePromise<Unit>(std::move(promise), Status::Error(400, "Call not found"));
+ send_closure(actor, &CallActor::send_call_signaling_data, std::move(data), std::move(safe_promise));
}
-void CallManager::accept_call(CallId call_id, CallProtocol &&protocol, Promise<> promise) {
+void CallManager::discard_call(CallId call_id, bool is_disconnected, int32 duration, bool is_video, int64 connection_id,
+ Promise<Unit> promise) {
auto actor = get_call_actor(call_id);
if (actor.empty()) {
return promise.set_error(Status::Error(400, "Call not found"));
}
- send_closure(actor, &CallActor::accept_call, std::move(protocol), std::move(promise));
+ auto safe_promise = SafePromise<Unit>(std::move(promise), Status::Error(400, "Call not found"));
+ send_closure(actor, &CallActor::discard_call, is_disconnected, duration, is_video, connection_id,
+ std::move(safe_promise));
}
-void CallManager::rate_call(CallId call_id, int32 rating, string comment, Promise<> promise) {
+void CallManager::rate_call(CallId call_id, int32 rating, string comment,
+ vector<td_api::object_ptr<td_api::CallProblem>> &&problems, Promise<Unit> promise) {
auto actor = get_call_actor(call_id);
if (actor.empty()) {
return promise.set_error(Status::Error(400, "Call not found"));
}
- send_closure(actor, &CallActor::rate_call, rating, std::move(comment), std::move(promise));
+ auto safe_promise = SafePromise<Unit>(std::move(promise), Status::Error(400, "Call not found"));
+ send_closure(actor, &CallActor::rate_call, rating, std::move(comment), std::move(problems), std::move(safe_promise));
}
-void CallManager::send_call_debug_information(CallId call_id, string data, Promise<> promise) {
+void CallManager::send_call_debug_information(CallId call_id, string data, Promise<Unit> promise) {
auto actor = get_call_actor(call_id);
if (actor.empty()) {
return promise.set_error(Status::Error(400, "Call not found"));
}
- send_closure(actor, &CallActor::send_call_debug_information, std::move(data), std::move(promise));
+ auto safe_promise = SafePromise<Unit>(std::move(promise), Status::Error(400, "Call not found"));
+ send_closure(actor, &CallActor::send_call_debug_information, std::move(data), std::move(safe_promise));
+}
+
+void CallManager::send_call_log(CallId call_id, td_api::object_ptr<td_api::InputFile> log_file, Promise<Unit> promise) {
+ auto actor = get_call_actor(call_id);
+ if (actor.empty()) {
+ return promise.set_error(Status::Error(400, "Call not found"));
+ }
+ auto safe_promise = SafePromise<Unit>(std::move(promise), Status::Error(400, "Call not found"));
+ send_closure(actor, &CallActor::send_call_log, std::move(log_file), std::move(safe_promise));
}
CallId CallManager::create_call_actor() {
@@ -94,7 +136,7 @@ CallId CallManager::create_call_actor() {
auto it_flag = id_to_actor_.emplace(id, ActorOwn<CallActor>());
CHECK(it_flag.second);
LOG(INFO) << "Create CallActor: " << id;
- auto main_promise = PromiseCreator::lambda([actor_id = actor_id(this), id](Result<int64> call_id) mutable {
+ auto main_promise = PromiseCreator::lambda([actor_id = actor_id(this), id](Result<int64> call_id) {
send_closure(actor_id, &CallManager::set_call_id, id, std::move(call_id));
});
it_flag.first->second = create_actor<CallActor>(PSLICE() << "Call " << id.get(), id, actor_shared(this, id.get()),
@@ -131,25 +173,24 @@ ActorId<CallActor> CallManager::get_call_actor(CallId call_id) {
void CallManager::hangup() {
close_flag_ = true;
for (auto &it : id_to_actor_) {
- LOG(INFO) << "Ask close CallActor " << it.first;
+ LOG(INFO) << "Ask to close CallActor " << it.first.get();
it.second.reset();
}
if (id_to_actor_.empty()) {
stop();
}
}
+
void CallManager::hangup_shared() {
auto token = narrow_cast<int32>(get_link_token());
auto it = id_to_actor_.find(CallId(token));
- if (it != id_to_actor_.end()) {
- LOG(INFO) << "Close CallActor " << tag("id", it->first);
- it->second.release();
- id_to_actor_.erase(it);
- } else {
- LOG(FATAL) << "Unknown CallActor hangup " << tag("id", static_cast<int32>(token));
- }
+ CHECK(it != id_to_actor_.end());
+ LOG(INFO) << "Closed CallActor " << it->first.get();
+ it->second.release();
+ id_to_actor_.erase(it);
if (close_flag_ && id_to_actor_.empty()) {
stop();
}
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CallManager.h b/protocols/Telegram/tdlib/td/td/telegram/CallManager.h
index dcfede8824..10d3076be6 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/CallManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/CallManager.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,31 +8,44 @@
#include "td/telegram/CallActor.h"
#include "td/telegram/CallId.h"
-
+#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
#include <map>
-#include <unordered_map>
namespace td {
-class CallManager : public Actor {
+class CallManager final : public Actor {
public:
using Update = telegram_api::object_ptr<telegram_api::updatePhoneCall>;
explicit CallManager(ActorShared<> parent);
void update_call(Update call);
+ void update_call_signaling_data(int64 call_id, string data);
void create_call(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user, CallProtocol &&protocol,
- Promise<CallId> promise);
- void discard_call(CallId call_id, bool is_disconnected, int32 duration, int64 connection_id, Promise<> promise);
- void accept_call(CallId call_id, CallProtocol &&protocol, Promise<> promise);
- void rate_call(CallId call_id, int32 rating, string comment, Promise<> promise);
- void send_call_debug_information(CallId call_id, string data, Promise<> promise);
+ bool is_video, Promise<CallId> promise);
+
+ void accept_call(CallId call_id, CallProtocol &&protocol, Promise<Unit> promise);
+
+ void send_call_signaling_data(CallId call_id, string &&data, Promise<Unit> promise);
+
+ void discard_call(CallId call_id, bool is_disconnected, int32 duration, bool is_video, int64 connection_id,
+ Promise<Unit> promise);
+
+ void rate_call(CallId call_id, int32 rating, string comment,
+ vector<td_api::object_ptr<td_api::CallProblem>> &&problems, Promise<Unit> promise);
+
+ void send_call_debug_information(CallId call_id, string data, Promise<Unit> promise);
+
+ void send_call_log(CallId call_id, td_api::object_ptr<td_api::InputFile> log_file, Promise<Unit> promise);
private:
bool close_flag_ = false;
@@ -44,13 +57,13 @@ class CallManager : public Actor {
};
std::map<int64, CallInfo> call_info_;
int32 next_call_id_{1};
- std::unordered_map<CallId, ActorOwn<CallActor>, CallIdHash> id_to_actor_;
+ FlatHashMap<CallId, ActorOwn<CallActor>, CallIdHash> id_to_actor_;
ActorId<CallActor> get_call_actor(CallId call_id);
CallId create_call_actor();
void set_call_id(CallId call_id, Result<int64> r_server_call_id);
- void hangup() override;
- void hangup_shared() override;
+ void hangup() final;
+ void hangup_shared() final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.cpp
index 46bd5e1c44..8d82a2b0d6 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.cpp
@@ -1,46 +1,46 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/CallbackQueriesManager.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/AccessRights.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/InlineQueriesManager.h"
#include "td/telegram/MessagesManager.h"
+#include "td/telegram/PasswordManager.h"
#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
-#include "td/utils/Random.h"
#include "td/utils/Status.h"
namespace td {
-class GetBotCallbackAnswerQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
- int64 result_id_;
+class GetBotCallbackAnswerQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> promise_;
DialogId dialog_id_;
+ MessageId message_id_;
public:
- explicit GetBotCallbackAnswerQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit GetBotCallbackAnswerQuery(Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> &&promise)
+ : promise_(std::move(promise)) {
}
void send(DialogId dialog_id, MessageId message_id, const tl_object_ptr<td_api::CallbackQueryPayload> &payload,
- int64 result_id) {
- result_id_ = result_id;
+ tl_object_ptr<telegram_api::InputCheckPasswordSRP> &&password) {
dialog_id_ = dialog_id;
+ message_id_ = message_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
int32 flags = 0;
@@ -51,6 +51,12 @@ class GetBotCallbackAnswerQuery : public Td::ResultHandler {
flags = telegram_api::messages_getBotCallbackAnswer::DATA_MASK;
data = BufferSlice(static_cast<const td_api::callbackQueryPayloadData *>(payload.get())->data_);
break;
+ case td_api::callbackQueryPayloadDataWithPassword::ID:
+ CHECK(password != nullptr);
+ flags = telegram_api::messages_getBotCallbackAnswer::DATA_MASK |
+ telegram_api::messages_getBotCallbackAnswer::PASSWORD_MASK;
+ data = BufferSlice(static_cast<const td_api::callbackQueryPayloadDataWithPassword *>(payload.get())->data_);
+ break;
case td_api::callbackQueryPayloadGame::ID:
flags = telegram_api::messages_getBotCallbackAnswer::GAME_MASK;
break;
@@ -58,30 +64,40 @@ class GetBotCallbackAnswerQuery : public Td::ResultHandler {
UNREACHABLE();
}
- auto net_query = G()->net_query_creator().create(create_storer(telegram_api::messages_getBotCallbackAnswer(
- flags, false /*ignored*/, std::move(input_peer), message_id.get_server_message_id().get(), std::move(data))));
- net_query->need_resend_on_503 = false;
+ auto net_query = G()->net_query_creator().create(telegram_api::messages_getBotCallbackAnswer(
+ flags, false /*ignored*/, std::move(input_peer), message_id.get_server_message_id().get(), std::move(data),
+ std::move(password)));
+ net_query->need_resend_on_503_ = false;
send_query(std::move(net_query));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getBotCallbackAnswer>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->callback_queries_manager_->on_get_callback_query_answer(result_id_, result_ptr.move_as_ok());
- promise_.set_value(Unit());
+ auto answer = result_ptr.move_as_ok();
+ promise_.set_value(
+ td_api::make_object<td_api::callbackQueryAnswer>(answer->message_, answer->alert_, answer->url_));
}
- void on_error(uint64 id, Status status) override {
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetBotCallbackAnswerQuery");
- td->callback_queries_manager_->on_get_callback_query_answer(result_id_, nullptr);
+ void on_error(Status status) final {
+ if (status.message() == "DATA_INVALID" || status.message() == "MESSAGE_ID_INVALID") {
+ td_->messages_manager_->get_message_from_server({dialog_id_, message_id_}, Auto(), "GetBotCallbackAnswerQuery");
+ } else if (status.message() == "BOT_RESPONSE_TIMEOUT") {
+ status = Status::Error(502, "The bot is not responding");
+ }
+ if (status.code() == 502 && td_->messages_manager_->is_message_edited_recently({dialog_id_, message_id_}, 31)) {
+ return promise_.set_value(td_api::make_object<td_api::callbackQueryAnswer>());
+ }
+
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetBotCallbackAnswerQuery");
promise_.set_error(std::move(status));
}
};
-class SetBotCallbackAnswerQuery : public Td::ResultHandler {
+class SetBotCallbackAnswerQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -89,14 +105,14 @@ class SetBotCallbackAnswerQuery : public Td::ResultHandler {
}
void send(int32 flags, int64 callback_query_id, const string &text, const string &url, int32 cache_time) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_setBotCallbackAnswer(
- flags, false /*ignored*/, callback_query_id, text, url, cache_time))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_setBotCallbackAnswer(
+ flags, false /*ignored*/, callback_query_id, text, url, cache_time)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setBotCallbackAnswer>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
@@ -106,7 +122,7 @@ class SetBotCallbackAnswerQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
@@ -160,7 +176,7 @@ void CallbackQueriesManager::on_new_query(int32 flags, int64 callback_query_id,
LOG(ERROR) << "Receive new callback query from invalid " << sender_user_id << " in " << dialog_id;
return;
}
- LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Have no info about " << sender_user_id;
+ LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id;
if (!td_->auth_manager_->is_bot()) {
LOG(ERROR) << "Receive new callback query";
return;
@@ -176,7 +192,7 @@ void CallbackQueriesManager::on_new_query(int32 flags, int64 callback_query_id,
return;
}
- td_->messages_manager_->force_create_dialog(dialog_id, "on_new_callback_query");
+ td_->messages_manager_->force_create_dialog(dialog_id, "on_new_callback_query", true);
send_closure(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateNewCallbackQuery>(
@@ -186,13 +202,13 @@ void CallbackQueriesManager::on_new_query(int32 flags, int64 callback_query_id,
void CallbackQueriesManager::on_new_inline_query(
int32 flags, int64 callback_query_id, UserId sender_user_id,
- tl_object_ptr<telegram_api::inputBotInlineMessageID> &&inline_message_id, BufferSlice &&data, int64 chat_instance,
+ tl_object_ptr<telegram_api::InputBotInlineMessageID> &&inline_message_id, BufferSlice &&data, int64 chat_instance,
string &&game_short_name) {
if (!sender_user_id.is_valid()) {
LOG(ERROR) << "Receive new callback query from invalid " << sender_user_id;
return;
}
- LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Have no info about " << sender_user_id;
+ LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id;
if (!td_->auth_manager_->is_bot()) {
LOG(ERROR) << "Receive new callback query";
return;
@@ -211,70 +227,65 @@ void CallbackQueriesManager::on_new_inline_query(
std::move(payload)));
}
-int64 CallbackQueriesManager::send_callback_query(FullMessageId full_message_id,
- const tl_object_ptr<td_api::CallbackQueryPayload> &payload,
- Promise<Unit> &&promise) {
+void CallbackQueriesManager::send_callback_query(FullMessageId full_message_id,
+ tl_object_ptr<td_api::CallbackQueryPayload> &&payload,
+ Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> &&promise) {
if (td_->auth_manager_->is_bot()) {
- promise.set_error(Status::Error(5, "Bot can't send callback queries to other bot"));
- return 0;
+ return promise.set_error(Status::Error(400, "Bot can't send callback queries to other bot"));
}
if (payload == nullptr) {
- promise.set_error(Status::Error(5, "Payload should not be empty"));
- return 0;
+ return promise.set_error(Status::Error(400, "Payload must be non-empty"));
}
auto dialog_id = full_message_id.get_dialog_id();
+ td_->messages_manager_->have_dialog_force(dialog_id, "send_callback_query");
if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
- promise.set_error(Status::Error(5, "Can't access the chat"));
- return 0;
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
}
- if (!td_->messages_manager_->have_message(full_message_id)) {
- promise.set_error(Status::Error(5, "Message not found"));
- return 0;
+ if (!td_->messages_manager_->have_message_force(full_message_id, "send_callback_query")) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ if (full_message_id.get_message_id().is_valid_scheduled()) {
+ return promise.set_error(Status::Error(400, "Can't send callback queries from scheduled messages"));
}
if (!full_message_id.get_message_id().is_server()) {
- promise.set_error(Status::Error(5, "Bad message identifier"));
- return 0;
+ return promise.set_error(Status::Error(400, "Bad message identifier"));
}
- int64 result_id;
- do {
- result_id = Random::secure_int64();
- } while (callback_query_answers_.find(result_id) != callback_query_answers_.end());
- callback_query_answers_[result_id]; // reserve place for result
-
- td_->create_handler<GetBotCallbackAnswerQuery>(std::move(promise))
- ->send(dialog_id, full_message_id.get_message_id(), payload, result_id);
-
- return result_id;
+ if (payload->get_id() == td_api::callbackQueryPayloadDataWithPassword::ID) {
+ auto password = static_cast<const td_api::callbackQueryPayloadDataWithPassword *>(payload.get())->password_;
+ send_closure(
+ td_->password_manager_, &PasswordManager::get_input_check_password_srp, std::move(password),
+ PromiseCreator::lambda([this, full_message_id, payload = std::move(payload), promise = std::move(promise)](
+ Result<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ send_get_callback_answer_query(full_message_id, std::move(payload), result.move_as_ok(), std::move(promise));
+ }));
+ } else {
+ send_get_callback_answer_query(full_message_id, std::move(payload), nullptr, std::move(promise));
+ }
}
-void CallbackQueriesManager::on_get_callback_query_answer(
- int64 result_id, tl_object_ptr<telegram_api::messages_botCallbackAnswer> &&answer) {
- LOG(INFO) << "Receive answer for callback query " << result_id;
- auto it = callback_query_answers_.find(result_id);
- CHECK(it != callback_query_answers_.end());
- CHECK(it->second.text.empty());
- if (answer == nullptr) {
- callback_query_answers_.erase(it);
- return;
- }
+void CallbackQueriesManager::send_get_callback_answer_query(
+ FullMessageId full_message_id, tl_object_ptr<td_api::CallbackQueryPayload> &&payload,
+ tl_object_ptr<telegram_api::InputCheckPasswordSRP> &&password,
+ Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
- LOG(INFO) << to_string(answer);
- it->second = CallbackQueryAnswer{(answer->flags_ & BOT_CALLBACK_ANSWER_FLAG_NEED_SHOW_ALERT) != 0, answer->message_,
- answer->url_};
-}
+ auto dialog_id = full_message_id.get_dialog_id();
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+ if (!td_->messages_manager_->have_message_force(full_message_id, "send_callback_query")) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
-tl_object_ptr<td_api::callbackQueryAnswer> CallbackQueriesManager::get_callback_query_answer_object(int64 result_id) {
- auto it = callback_query_answers_.find(result_id);
- CHECK(it != callback_query_answers_.end());
- bool show_alert = it->second.show_alert;
- auto text = std::move(it->second.text);
- auto url = std::move(it->second.url);
- callback_query_answers_.erase(it);
- return make_tl_object<td_api::callbackQueryAnswer>(text, show_alert, url);
+ td_->create_handler<GetBotCallbackAnswerQuery>(std::move(promise))
+ ->send(dialog_id, full_message_id.get_message_id(), payload, std::move(password));
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.h b/protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.h
index f2a0645088..e59e1b7785 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/CallbackQueriesManager.h
@@ -1,30 +1,26 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
#include "td/telegram/DialogId.h"
+#include "td/telegram/FullMessageId.h"
#include "td/telegram/MessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
-
-#include <unordered_map>
+#include "td/utils/Promise.h"
namespace td {
class Td;
-template <class T>
-class Promise;
-
class CallbackQueriesManager {
public:
explicit CallbackQueriesManager(Td *td);
@@ -36,31 +32,24 @@ class CallbackQueriesManager {
MessageId message_id, BufferSlice &&data, int64 chat_instance, string &&game_short_name);
void on_new_inline_query(int32 flags, int64 callback_query_id, UserId sender_user_id,
- tl_object_ptr<telegram_api::inputBotInlineMessageID> &&inline_message_id, BufferSlice &&data,
+ tl_object_ptr<telegram_api::InputBotInlineMessageID> &&inline_message_id, BufferSlice &&data,
int64 chat_instance, string &&game_short_name);
- int64 send_callback_query(FullMessageId full_message_id, const tl_object_ptr<td_api::CallbackQueryPayload> &payload,
- Promise<Unit> &&promise);
-
- void on_get_callback_query_answer(int64 result_id, tl_object_ptr<telegram_api::messages_botCallbackAnswer> &&answer);
-
- tl_object_ptr<td_api::callbackQueryAnswer> get_callback_query_answer_object(int64 result_id);
+ void send_callback_query(FullMessageId full_message_id, tl_object_ptr<td_api::CallbackQueryPayload> &&payload,
+ Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> &&promise);
private:
static constexpr int32 BOT_CALLBACK_ANSWER_FLAG_HAS_MESSAGE = 1 << 0;
static constexpr int32 BOT_CALLBACK_ANSWER_FLAG_NEED_SHOW_ALERT = 1 << 1;
static constexpr int32 BOT_CALLBACK_ANSWER_FLAG_HAS_URL = 1 << 2;
- struct CallbackQueryAnswer {
- bool show_alert;
- string text;
- string url;
- };
-
- tl_object_ptr<td_api::CallbackQueryPayload> get_query_payload(int32 flags, BufferSlice &&data,
- string &&game_short_name);
+ static tl_object_ptr<td_api::CallbackQueryPayload> get_query_payload(int32 flags, BufferSlice &&data,
+ string &&game_short_name);
- std::unordered_map<int64, CallbackQueryAnswer> callback_query_answers_;
+ void send_get_callback_answer_query(FullMessageId full_message_id,
+ tl_object_ptr<td_api::CallbackQueryPayload> &&payload,
+ tl_object_ptr<telegram_api::InputCheckPasswordSRP> &&password,
+ Promise<td_api::object_ptr<td_api::callbackQueryAnswer>> &&promise);
Td *td_;
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ChainId.h b/protocols/Telegram/tdlib/td/td/telegram/ChainId.h
new file mode 100644
index 0000000000..01ec8eb434
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ChainId.h
@@ -0,0 +1,57 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/FolderId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/MessageContentType.h"
+#include "td/telegram/PollId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+
+namespace td {
+
+class ChainId {
+ uint64 id = 0;
+
+ public:
+ ChainId(ChannelId channel_id) : ChainId(DialogId(channel_id)) {
+ }
+
+ ChainId(ChatId chat_id) : ChainId(DialogId(chat_id)) {
+ }
+
+ ChainId(DialogId dialog_id, MessageContentType message_content_type)
+ : id((static_cast<uint64>(dialog_id.get()) << 10) + get_message_content_chain_id(message_content_type)) {
+ }
+
+ ChainId(DialogId dialog_id) : id((static_cast<uint64>(dialog_id.get()) << 10) + 10) {
+ }
+
+ ChainId(FullMessageId full_message_id) : ChainId(full_message_id.get_dialog_id()) {
+ id += static_cast<uint64>(full_message_id.get_message_id().get()) << 10;
+ }
+
+ ChainId(FolderId folder_id) : id((static_cast<uint64>(folder_id.get() + (1 << 30)) << 10)) {
+ }
+
+ ChainId(PollId poll_id) : id(static_cast<uint64>(poll_id.get())) {
+ }
+
+ ChainId(const string &str) : id(Hash<string>()(str)) {
+ }
+
+ uint64 get() const {
+ return id;
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ChannelId.h b/protocols/Telegram/tdlib/td/td/telegram/ChannelId.h
index 0924de59bc..96802a7084 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ChannelId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/ChannelId.h
@@ -1,35 +1,40 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/Version.h"
+
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/StringBuilder.h"
-#include <functional>
#include <type_traits>
namespace td {
class ChannelId {
- int32 id = 0;
+ int64 id = 0;
public:
+ // the last (1 << 31) - 1 identifiers will be used for secret chat dialog identifiers
+ static constexpr int64 MAX_CHANNEL_ID = 1000000000000ll - (1ll << 31);
+
ChannelId() = default;
- explicit ChannelId(int32 channel_id) : id(channel_id) {
+ explicit ChannelId(int64 channel_id) : id(channel_id) {
}
- template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
ChannelId(T channel_id) = delete;
bool is_valid() const {
- return id > 0; // TODO better is_valid
+ return 0 < id && id < MAX_CHANNEL_ID;
}
- int32 get() const {
+ int64 get() const {
return id;
}
@@ -43,18 +48,22 @@ class ChannelId {
template <class StorerT>
void store(StorerT &storer) const {
- storer.store_int(id);
+ storer.store_long(id);
}
template <class ParserT>
void parse(ParserT &parser) {
- id = parser.fetch_int();
+ if (parser.version() >= static_cast<int32>(Version::Support64BitIds)) {
+ id = parser.fetch_long();
+ } else {
+ id = parser.fetch_int();
+ }
}
};
struct ChannelIdHash {
- std::size_t operator()(ChannelId channel_id) const {
- return std::hash<int32>()(channel_id.get());
+ uint32 operator()(ChannelId channel_id) const {
+ return Hash<int64>()(channel_id.get());
}
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ChannelParticipantFilter.cpp b/protocols/Telegram/tdlib/td/td/telegram/ChannelParticipantFilter.cpp
new file mode 100644
index 0000000000..39b0358671
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ChannelParticipantFilter.cpp
@@ -0,0 +1,116 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ChannelParticipantFilter.h"
+
+namespace td {
+
+tl_object_ptr<telegram_api::ChannelParticipantsFilter> ChannelParticipantFilter::get_input_channel_participants_filter()
+ const {
+ switch (type_) {
+ case Type::Recent:
+ return make_tl_object<telegram_api::channelParticipantsRecent>();
+ case Type::Contacts:
+ return make_tl_object<telegram_api::channelParticipantsContacts>(query_);
+ case Type::Administrators:
+ return make_tl_object<telegram_api::channelParticipantsAdmins>();
+ case Type::Search:
+ return make_tl_object<telegram_api::channelParticipantsSearch>(query_);
+ case Type::Mention: {
+ int32 flags = 0;
+ if (!query_.empty()) {
+ flags |= telegram_api::channelParticipantsMentions::Q_MASK;
+ }
+ if (top_thread_message_id_.is_valid()) {
+ flags |= telegram_api::channelParticipantsMentions::TOP_MSG_ID_MASK;
+ }
+ return make_tl_object<telegram_api::channelParticipantsMentions>(
+ flags, query_, top_thread_message_id_.get_server_message_id().get());
+ }
+ case Type::Restricted:
+ return make_tl_object<telegram_api::channelParticipantsBanned>(query_);
+ case Type::Banned:
+ return make_tl_object<telegram_api::channelParticipantsKicked>(query_);
+ case Type::Bots:
+ return make_tl_object<telegram_api::channelParticipantsBots>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+ChannelParticipantFilter::ChannelParticipantFilter(const tl_object_ptr<td_api::SupergroupMembersFilter> &filter) {
+ if (filter == nullptr) {
+ type_ = Type::Recent;
+ return;
+ }
+ switch (filter->get_id()) {
+ case td_api::supergroupMembersFilterRecent::ID:
+ type_ = Type::Recent;
+ return;
+ case td_api::supergroupMembersFilterContacts::ID:
+ type_ = Type::Contacts;
+ query_ = static_cast<const td_api::supergroupMembersFilterContacts *>(filter.get())->query_;
+ return;
+ case td_api::supergroupMembersFilterAdministrators::ID:
+ type_ = Type::Administrators;
+ return;
+ case td_api::supergroupMembersFilterSearch::ID:
+ type_ = Type::Search;
+ query_ = static_cast<const td_api::supergroupMembersFilterSearch *>(filter.get())->query_;
+ return;
+ case td_api::supergroupMembersFilterMention::ID: {
+ auto mention_filter = static_cast<const td_api::supergroupMembersFilterMention *>(filter.get());
+ type_ = Type::Mention;
+ query_ = mention_filter->query_;
+ top_thread_message_id_ = MessageId(mention_filter->message_thread_id_);
+ if (!top_thread_message_id_.is_valid() || !top_thread_message_id_.is_server()) {
+ top_thread_message_id_ = MessageId();
+ }
+ return;
+ }
+ case td_api::supergroupMembersFilterRestricted::ID:
+ type_ = Type::Restricted;
+ query_ = static_cast<const td_api::supergroupMembersFilterRestricted *>(filter.get())->query_;
+ return;
+ case td_api::supergroupMembersFilterBanned::ID:
+ type_ = Type::Banned;
+ query_ = static_cast<const td_api::supergroupMembersFilterBanned *>(filter.get())->query_;
+ return;
+ case td_api::supergroupMembersFilterBots::ID:
+ type_ = Type::Bots;
+ return;
+ default:
+ UNREACHABLE();
+ type_ = Type::Recent;
+ }
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ChannelParticipantFilter &filter) {
+ switch (filter.type_) {
+ case ChannelParticipantFilter::Type::Recent:
+ return string_builder << "Recent";
+ case ChannelParticipantFilter::Type::Contacts:
+ return string_builder << "Contacts \"" << filter.query_ << '"';
+ case ChannelParticipantFilter::Type::Administrators:
+ return string_builder << "Administrators";
+ case ChannelParticipantFilter::Type::Search:
+ return string_builder << "Search \"" << filter.query_ << '"';
+ case ChannelParticipantFilter::Type::Mention:
+ return string_builder << "Mention \"" << filter.query_ << "\" in thread of " << filter.top_thread_message_id_;
+ case ChannelParticipantFilter::Type::Restricted:
+ return string_builder << "Restricted \"" << filter.query_ << '"';
+ case ChannelParticipantFilter::Type::Banned:
+ return string_builder << "Banned \"" << filter.query_ << '"';
+ case ChannelParticipantFilter::Type::Bots:
+ return string_builder << "Bots";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ChannelParticipantFilter.h b/protocols/Telegram/tdlib/td/td/telegram/ChannelParticipantFilter.h
new file mode 100644
index 0000000000..8e17c79e4f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ChannelParticipantFilter.h
@@ -0,0 +1,62 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class ChannelParticipantFilter {
+ enum class Type : int32 { Recent, Contacts, Administrators, Search, Mention, Restricted, Banned, Bots };
+ Type type_;
+ string query_;
+ MessageId top_thread_message_id_;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const ChannelParticipantFilter &filter);
+
+ public:
+ explicit ChannelParticipantFilter(const td_api::object_ptr<td_api::SupergroupMembersFilter> &filter);
+
+ tl_object_ptr<telegram_api::ChannelParticipantsFilter> get_input_channel_participants_filter() const;
+
+ bool is_administrators() const {
+ return type_ == Type::Administrators;
+ }
+
+ bool is_bots() const {
+ return type_ == Type::Bots;
+ }
+
+ bool is_recent() const {
+ return type_ == Type::Recent;
+ }
+
+ bool is_contacts() const {
+ return type_ == Type::Contacts;
+ }
+
+ bool is_search() const {
+ return type_ == Type::Search;
+ }
+
+ bool is_restricted() const {
+ return type_ == Type::Restricted;
+ }
+
+ bool is_banned() const {
+ return type_ == Type::Banned;
+ }
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ChannelParticipantFilter &filter);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/test/TestsRunner.h b/protocols/Telegram/tdlib/td/td/telegram/ChannelType.h
index a5bc66d855..7c66e67a59 100644
--- a/protocols/Telegram/tdlib/td/test/TestsRunner.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/ChannelType.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,10 +10,6 @@
namespace td {
-class TestsRunner {
- public:
- static void init(string dir);
- static void run_all_tests();
-};
+enum class ChannelType : uint8 { Broadcast, Megagroup, Unknown };
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ChatId.h b/protocols/Telegram/tdlib/td/td/telegram/ChatId.h
index 89b1483024..7dc8808891 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ChatId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/ChatId.h
@@ -1,35 +1,39 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/Version.h"
+
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/StringBuilder.h"
-#include <functional>
#include <type_traits>
namespace td {
class ChatId {
- int32 id = 0;
+ int64 id = 0;
public:
+ static constexpr int64 MAX_CHAT_ID = 999999999999ll;
+
ChatId() = default;
- explicit ChatId(int32 chat_id) : id(chat_id) {
+ explicit ChatId(int64 chat_id) : id(chat_id) {
}
- template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
ChatId(T chat_id) = delete;
bool is_valid() const {
- return id > 0;
+ return 0 < id && id <= MAX_CHAT_ID;
}
- int32 get() const {
+ int64 get() const {
return id;
}
@@ -43,18 +47,22 @@ class ChatId {
template <class StorerT>
void store(StorerT &storer) const {
- storer.store_int(id);
+ storer.store_long(id);
}
template <class ParserT>
void parse(ParserT &parser) {
- id = parser.fetch_int();
+ if (parser.version() >= static_cast<int32>(Version::Support64BitIds)) {
+ id = parser.fetch_long();
+ } else {
+ id = parser.fetch_int();
+ }
}
};
struct ChatIdHash {
- std::size_t operator()(ChatId chat_id) const {
- return std::hash<int32>()(chat_id.get());
+ uint32 operator()(ChatId chat_id) const {
+ return Hash<int64>()(chat_id.get());
}
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ChatReactions.cpp b/protocols/Telegram/tdlib/td/td/telegram/ChatReactions.cpp
new file mode 100644
index 0000000000..93a0c77e1a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ChatReactions.cpp
@@ -0,0 +1,118 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ChatReactions.h"
+
+#include "td/telegram/MessageReaction.h"
+
+#include "td/utils/algorithm.h"
+
+namespace td {
+
+ChatReactions::ChatReactions(telegram_api::object_ptr<telegram_api::ChatReactions> &&chat_reactions_ptr) {
+ if (chat_reactions_ptr == nullptr) {
+ return;
+ }
+ switch (chat_reactions_ptr->get_id()) {
+ case telegram_api::chatReactionsNone::ID:
+ break;
+ case telegram_api::chatReactionsAll::ID: {
+ auto chat_reactions = move_tl_object_as<telegram_api::chatReactionsAll>(chat_reactions_ptr);
+ allow_all_ = true;
+ allow_custom_ = chat_reactions->allow_custom_;
+ break;
+ }
+ case telegram_api::chatReactionsSome::ID: {
+ auto chat_reactions = move_tl_object_as<telegram_api::chatReactionsSome>(chat_reactions_ptr);
+ reactions_ =
+ transform(chat_reactions->reactions_, [](const telegram_api::object_ptr<telegram_api::Reaction> &reaction) {
+ return get_message_reaction_string(reaction);
+ });
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+ChatReactions::ChatReactions(td_api::object_ptr<td_api::ChatAvailableReactions> &&chat_reactions_ptr,
+ bool allow_custom) {
+ if (chat_reactions_ptr == nullptr) {
+ return;
+ }
+ switch (chat_reactions_ptr->get_id()) {
+ case td_api::chatAvailableReactionsAll::ID:
+ allow_all_ = true;
+ allow_custom_ = allow_custom;
+ break;
+ case td_api::chatAvailableReactionsSome::ID: {
+ auto chat_reactions = move_tl_object_as<td_api::chatAvailableReactionsSome>(chat_reactions_ptr);
+ reactions_ = transform(chat_reactions->reactions_, [](const td_api::object_ptr<td_api::ReactionType> &reaction) {
+ return get_message_reaction_string(reaction);
+ });
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+ChatReactions ChatReactions::get_active_reactions(const FlatHashMap<string, size_t> &active_reaction_pos) const {
+ ChatReactions result = *this;
+ if (!reactions_.empty()) {
+ CHECK(!allow_all_);
+ CHECK(!allow_custom_);
+ td::remove_if(result.reactions_,
+ [&](const string &reaction) { return !is_active_reaction(reaction, active_reaction_pos); });
+ }
+ return result;
+}
+
+bool ChatReactions::is_allowed_reaction(const string &reaction) const {
+ CHECK(!allow_all_);
+ if (allow_custom_ && is_custom_reaction(reaction)) {
+ return true;
+ }
+ return td::contains(reactions_, reaction);
+}
+
+td_api::object_ptr<td_api::ChatAvailableReactions> ChatReactions::get_chat_available_reactions_object() const {
+ if (allow_all_) {
+ return td_api::make_object<td_api::chatAvailableReactionsAll>();
+ }
+ return td_api::make_object<td_api::chatAvailableReactionsSome>(transform(reactions_, get_reaction_type_object));
+}
+
+telegram_api::object_ptr<telegram_api::ChatReactions> ChatReactions::get_input_chat_reactions() const {
+ if (allow_all_) {
+ int32 flags = 0;
+ if (allow_custom_) {
+ flags |= telegram_api::chatReactionsAll::ALLOW_CUSTOM_MASK;
+ }
+ return telegram_api::make_object<telegram_api::chatReactionsAll>(flags, false /*ignored*/);
+ }
+ if (!reactions_.empty()) {
+ return telegram_api::make_object<telegram_api::chatReactionsSome>(transform(reactions_, get_input_reaction));
+ }
+ return telegram_api::make_object<telegram_api::chatReactionsNone>();
+}
+
+bool operator==(const ChatReactions &lhs, const ChatReactions &rhs) {
+ // don't compare allow_custom_
+ return lhs.reactions_ == rhs.reactions_ && lhs.allow_all_ == rhs.allow_all_;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ChatReactions &reactions) {
+ if (reactions.allow_all_) {
+ if (reactions.allow_custom_) {
+ return string_builder << "AllReactions";
+ }
+ return string_builder << "AllRegularReactions";
+ }
+ return string_builder << '[' << reactions.reactions_ << ']';
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ChatReactions.h b/protocols/Telegram/tdlib/td/td/telegram/ChatReactions.h
new file mode 100644
index 0000000000..78594fd371
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ChatReactions.h
@@ -0,0 +1,83 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+struct ChatReactions {
+ vector<string> reactions_;
+ bool allow_all_ = false; // implies empty reactions
+ bool allow_custom_ = false; // implies allow_all
+
+ ChatReactions() = default;
+
+ explicit ChatReactions(vector<string> &&reactions) : reactions_(std::move(reactions)) {
+ }
+
+ explicit ChatReactions(telegram_api::object_ptr<telegram_api::ChatReactions> &&chat_reactions_ptr);
+
+ ChatReactions(td_api::object_ptr<td_api::ChatAvailableReactions> &&chat_reactions_ptr, bool allow_custom);
+
+ ChatReactions(bool allow_all, bool allow_custom) : allow_all_(allow_all), allow_custom_(allow_custom) {
+ }
+
+ ChatReactions get_active_reactions(const FlatHashMap<string, size_t> &active_reaction_pos) const;
+
+ bool is_allowed_reaction(const string &reaction) const;
+
+ telegram_api::object_ptr<telegram_api::ChatReactions> get_input_chat_reactions() const;
+
+ td_api::object_ptr<td_api::ChatAvailableReactions> get_chat_available_reactions_object() const;
+
+ bool empty() const {
+ return reactions_.empty() && !allow_all_;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ bool has_reactions = !reactions_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(allow_all_);
+ STORE_FLAG(allow_custom_);
+ STORE_FLAG(has_reactions);
+ END_STORE_FLAGS();
+ if (has_reactions) {
+ td::store(reactions_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ bool has_reactions;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(allow_all_);
+ PARSE_FLAG(allow_custom_);
+ PARSE_FLAG(has_reactions);
+ END_PARSE_FLAGS();
+ if (has_reactions) {
+ td::parse(reactions_, parser);
+ }
+ }
+};
+
+bool operator==(const ChatReactions &lhs, const ChatReactions &rhs);
+
+inline bool operator!=(const ChatReactions &lhs, const ChatReactions &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ChatReactions &reactions);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Client.cpp b/protocols/Telegram/tdlib/td/td/telegram/Client.cpp
index d646932872..f9fcb58947 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Client.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Client.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,266 +7,635 @@
#include "td/telegram/Client.h"
#include "td/telegram/Td.h"
+#include "td/telegram/TdCallback.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
#include "td/utils/crypto.h"
+#include "td/utils/ExitGuard.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/MpscPollableQueue.h"
-#include "td/utils/Observer.h"
-#include "td/utils/port/Fd.h"
-#include "td/utils/port/Poll.h"
+#include "td/utils/port/RwMutex.h"
#include "td/utils/port/thread.h"
-
-#include <deque>
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/utf8.h"
+
+#include <algorithm>
+#include <atomic>
+#include <limits>
+#include <memory>
+#include <mutex>
+#include <queue>
namespace td {
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
+class TdReceiver {
+ public:
+ ClientManager::Response receive(double timeout) {
+ if (!responses_.empty()) {
+ auto result = std::move(responses_.front());
+ responses_.pop();
+ return result;
+ }
+ return {0, 0, nullptr};
+ }
-class Client::Impl final {
+ unique_ptr<TdCallback> create_callback(ClientManager::ClientId client_id) {
+ class Callback final : public TdCallback {
+ public:
+ Callback(ClientManager::ClientId client_id, TdReceiver *impl) : client_id_(client_id), impl_(impl) {
+ }
+ void on_result(uint64 id, td_api::object_ptr<td_api::Object> result) final {
+ impl_->responses_.push({client_id_, id, std::move(result)});
+ }
+ void on_error(uint64 id, td_api::object_ptr<td_api::error> error) final {
+ impl_->responses_.push({client_id_, id, std::move(error)});
+ }
+ Callback(const Callback &) = delete;
+ Callback &operator=(const Callback &) = delete;
+ Callback(Callback &&) = delete;
+ Callback &operator=(Callback &&) = delete;
+ ~Callback() final {
+ impl_->responses_.push({client_id_, 0, nullptr});
+ }
+
+ private:
+ ClientManager::ClientId client_id_;
+ TdReceiver *impl_;
+ };
+ return td::make_unique<Callback>(client_id, this);
+ }
+
+ void add_response(ClientManager::ClientId client_id, uint64 id, td_api::object_ptr<td_api::Object> result) {
+ responses_.push({client_id, id, std::move(result)});
+ }
+
+ private:
+ std::queue<ClientManager::Response> responses_;
+};
+
+class ClientManager::Impl final {
public:
- Impl() {
- init();
+ ClientId create_client_id() {
+ CHECK(client_id_ != std::numeric_limits<ClientId>::max());
+ auto client_id = ++client_id_;
+ pending_clients_.insert(client_id);
+ return client_id;
}
- void send(Request request) {
- if (request.id == 0 || request.function == nullptr) {
- LOG(ERROR) << "Drop wrong request " << request.id;
- return;
+ void send(ClientId client_id, RequestId request_id, td_api::object_ptr<td_api::Function> &&request) {
+ if (pending_clients_.erase(client_id) != 0) {
+ if (tds_.empty()) {
+ CHECK(concurrent_scheduler_ == nullptr);
+ CHECK(options_.net_query_stats == nullptr);
+ options_.net_query_stats = std::make_shared<NetQueryStats>();
+ concurrent_scheduler_ = make_unique<ConcurrentScheduler>(0, 0);
+ concurrent_scheduler_->start();
+ }
+ tds_[client_id] =
+ concurrent_scheduler_->create_actor_unsafe<Td>(0, "Td", receiver_.create_callback(client_id), options_);
}
-
- requests_.push_back(std::move(request));
+ requests_.push_back({client_id, request_id, std::move(request)});
}
Response receive(double timeout) {
if (!requests_.empty()) {
- auto guard = scheduler_->get_current_guard();
- for (auto &request : requests_) {
- send_closure_later(td_, &Td::request, request.id, std::move(request.function));
+ for (size_t i = 0; i < requests_.size(); i++) {
+ auto &request = requests_[i];
+ if (request.client_id <= 0 || request.client_id > client_id_) {
+ receiver_.add_response(request.client_id, request.id,
+ td_api::make_object<td_api::error>(400, "Invalid TDLib instance specified"));
+ continue;
+ }
+ auto it = tds_.find(request.client_id);
+ if (it == tds_.end() || it->second.empty()) {
+ receiver_.add_response(request.client_id, request.id,
+ td_api::make_object<td_api::error>(500, "Request aborted"));
+ continue;
+ }
+
+ CHECK(concurrent_scheduler_ != nullptr);
+ auto guard = concurrent_scheduler_->get_main_guard();
+ send_closure_later(it->second, &Td::request, request.id, std::move(request.request));
}
requests_.clear();
}
- if (responses_.empty()) {
- scheduler_->run_main(0);
+ auto response = receiver_.receive(0);
+ if (response.client_id == 0 && response.request_id == 0 && concurrent_scheduler_ != nullptr) {
+ concurrent_scheduler_->run_main(0);
+ response = receiver_.receive(0);
+ } else {
+ ConcurrentScheduler::emscripten_clear_main_timeout();
}
- if (!responses_.empty()) {
- auto result = std::move(responses_.front());
- responses_.pop_front();
- return result;
+ if (response.request_id == 0 && response.object != nullptr &&
+ response.object->get_id() == td_api::updateAuthorizationState::ID &&
+ static_cast<const td_api::updateAuthorizationState *>(response.object.get())->authorization_state_->get_id() ==
+ td_api::authorizationStateClosed::ID) {
+ CHECK(concurrent_scheduler_ != nullptr);
+ auto guard = concurrent_scheduler_->get_main_guard();
+ auto it = tds_.find(response.client_id);
+ CHECK(it != tds_.end());
+ it->second.reset();
+
+ response.client_id = 0;
+ response.object = nullptr;
}
- return {0, nullptr};
+ if (response.object == nullptr && response.client_id != 0 && response.request_id == 0) {
+ auto it = tds_.find(response.client_id);
+ CHECK(it != tds_.end());
+ CHECK(it->second.empty());
+ tds_.erase(it);
+
+ response.object = td_api::make_object<td_api::updateAuthorizationState>(
+ td_api::make_object<td_api::authorizationStateClosed>());
+
+ if (tds_.empty()) {
+ CHECK(options_.net_query_stats.use_count() == 1);
+ CHECK(options_.net_query_stats->get_count() == 0);
+ options_.net_query_stats = nullptr;
+ concurrent_scheduler_->finish();
+ concurrent_scheduler_ = nullptr;
+ reset_to_empty(tds_);
+ }
+ }
+ return response;
}
+ Impl() = default;
+ Impl(const Impl &) = delete;
+ Impl &operator=(const Impl &) = delete;
+ Impl(Impl &&) = delete;
+ Impl &operator=(Impl &&) = delete;
~Impl() {
+ if (concurrent_scheduler_ == nullptr) {
+ return;
+ }
+
{
- auto guard = scheduler_->get_current_guard();
- td_.reset();
+ auto guard = concurrent_scheduler_->get_main_guard();
+ for (auto &td : tds_) {
+ td.second.reset();
+ }
+ }
+ while (!tds_.empty() && !ExitGuard::is_exited()) {
+ receive(0.1);
}
- while (!closed_) {
- scheduler_->run_main(0);
+ if (concurrent_scheduler_ != nullptr) {
+ concurrent_scheduler_->finish();
}
- scheduler_.reset();
}
private:
- std::deque<Response> responses_;
- std::vector<Request> requests_;
- int output_queue_ready_cnt_{0};
- std::unique_ptr<ConcurrentScheduler> scheduler_;
- ActorOwn<Td> td_;
- bool closed_ = false;
-
- void init() {
- scheduler_ = std::make_unique<ConcurrentScheduler>();
- scheduler_->init(0);
- class Callback : public TdCallback {
- public:
- Callback(Impl *client) : client_(client) {
- }
- void on_result(std::uint64_t id, td_api::object_ptr<td_api::Object> result) override {
- client_->responses_.push_back({id, std::move(result)});
- }
- void on_error(std::uint64_t id, td_api::object_ptr<td_api::error> error) override {
- client_->responses_.push_back({id, std::move(error)});
- }
- void on_closed() override {
- client_->closed_ = true;
- Scheduler::instance()->yield();
- }
+ TdReceiver receiver_;
+ struct Request {
+ ClientId client_id;
+ RequestId id;
+ td_api::object_ptr<td_api::Function> request;
+ };
+ vector<Request> requests_;
+ unique_ptr<ConcurrentScheduler> concurrent_scheduler_;
+ ClientId client_id_{0};
+ Td::Options options_;
+ FlatHashSet<int32> pending_clients_;
+ FlatHashMap<int32, ActorOwn<Td>> tds_;
+};
- private:
- Impl *client_;
- };
- td_ = scheduler_->create_actor_unsafe<Td>(0, "Td", make_unique<Callback>(this));
- scheduler_->start();
+class Client::Impl final {
+ public:
+ Impl() : client_id_(impl_.create_client_id()) {
+ }
+
+ void send(Request request) {
+ impl_.send(client_id_, request.id, std::move(request.function));
+ }
+
+ Response receive(double timeout) {
+ auto response = impl_.receive(timeout);
+
+ Response old_response;
+ old_response.id = response.request_id;
+ old_response.object = std::move(response.object);
+ return old_response;
}
+
+ private:
+ ClientManager::Impl impl_;
+ ClientManager::ClientId client_id_;
};
#else
-/*** TdProxy ***/
-using InputQueue = MpscPollableQueue<Client::Request>;
-using OutputQueue = MpscPollableQueue<Client::Response>;
-class TdProxy : public Actor {
+class MultiTd final : public Actor {
public:
- TdProxy(std::shared_ptr<InputQueue> input_queue, std::shared_ptr<OutputQueue> output_queue)
- : input_queue_(std::move(input_queue)), output_queue_(std::move(output_queue)) {
+ explicit MultiTd(Td::Options options) : options_(std::move(options)) {
+ }
+ void create(int32 td_id, unique_ptr<TdCallback> callback) {
+ auto &td = tds_[td_id];
+ CHECK(td.empty());
+
+ string name = "Td";
+ auto context = std::make_shared<ActorContext>();
+ auto old_context = set_context(context);
+ auto old_tag = set_tag(to_string(td_id));
+ td = create_actor<Td>("Td", std::move(callback), options_);
+ set_context(std::move(old_context));
+ set_tag(std::move(old_tag));
+ }
+
+ void send(ClientManager::ClientId client_id, ClientManager::RequestId request_id,
+ td_api::object_ptr<td_api::Function> &&request) {
+ auto &td = tds_[client_id];
+ CHECK(!td.empty());
+ send_closure(td, &Td::request, request_id, std::move(request));
+ }
+
+ void close(int32 td_id) {
+ size_t erased_count = tds_.erase(td_id);
+ CHECK(erased_count > 0);
}
private:
- std::shared_ptr<InputQueue> input_queue_;
- std::shared_ptr<OutputQueue> output_queue_;
- bool is_td_closed_ = false;
- bool was_hangup_ = false;
- ActorOwn<Td> td_;
+ Td::Options options_;
+ FlatHashMap<int32, ActorOwn<Td>> tds_;
+};
- void start_up() override {
- auto &fd = input_queue_->reader_get_event_fd();
- fd.get_fd().set_observer(this);
- ::td::subscribe(fd.get_fd(), Fd::Read);
+class TdReceiver {
+ public:
+ TdReceiver() {
+ output_queue_ = std::make_shared<OutputQueue>();
+ output_queue_->init();
+ }
+
+ ClientManager::Response receive(double timeout, bool from_manager) {
+ VLOG(td_requests) << "Begin to wait for updates with timeout " << timeout;
+ auto is_locked = receive_lock_.exchange(true);
+ if (is_locked) {
+ if (from_manager) {
+ LOG(FATAL) << "Receive must not be called simultaneously from two different threads, but this has just "
+ "happened. Call it from a fixed thread, dedicated for updates and response processing.";
+ } else {
+ LOG(FATAL) << "Receive is called after Client destroy, or simultaneously from different threads";
+ }
+ }
+ auto response = receive_unlocked(clamp(timeout, 0.0, 1000000.0));
+ is_locked = receive_lock_.exchange(false);
+ CHECK(is_locked);
+ VLOG(td_requests) << "End to wait for updates, returning object " << response.request_id << ' '
+ << response.object.get();
+ return response;
+ }
- class Callback : public TdCallback {
+ unique_ptr<TdCallback> create_callback(ClientManager::ClientId client_id) {
+ class Callback final : public TdCallback {
public:
- Callback(ActorId<TdProxy> parent, std::shared_ptr<OutputQueue> output_queue)
- : parent_(parent), output_queue_(std::move(output_queue)) {
+ explicit Callback(ClientManager::ClientId client_id, std::shared_ptr<OutputQueue> output_queue)
+ : client_id_(client_id), output_queue_(std::move(output_queue)) {
}
- void on_result(std::uint64_t id, td_api::object_ptr<td_api::Object> result) override {
- output_queue_->writer_put({id, std::move(result)});
+ void on_result(uint64 id, td_api::object_ptr<td_api::Object> result) final {
+ output_queue_->writer_put({client_id_, id, std::move(result)});
}
- void on_error(std::uint64_t id, td_api::object_ptr<td_api::error> error) override {
- output_queue_->writer_put({id, std::move(error)});
+ void on_error(uint64 id, td_api::object_ptr<td_api::error> error) final {
+ output_queue_->writer_put({client_id_, id, std::move(error)});
}
- void on_closed() override {
- send_closure(parent_, &TdProxy::on_closed);
+ Callback(const Callback &) = delete;
+ Callback &operator=(const Callback &) = delete;
+ Callback(Callback &&) = delete;
+ Callback &operator=(Callback &&) = delete;
+ ~Callback() final {
+ output_queue_->writer_put({client_id_, 0, nullptr});
}
private:
- ActorId<TdProxy> parent_;
+ ClientManager::ClientId client_id_;
std::shared_ptr<OutputQueue> output_queue_;
};
- td_ = create_actor<Td>("Td", make_unique<Callback>(actor_id(this), std::move(output_queue_)));
- yield();
+ return td::make_unique<Callback>(client_id, output_queue_);
}
- void on_closed() {
- is_td_closed_ = true;
- try_stop();
+ void add_response(ClientManager::ClientId client_id, uint64 id, td_api::object_ptr<td_api::Object> result) {
+ output_queue_->writer_put({client_id, id, std::move(result)});
}
- void try_stop() {
- if (!is_td_closed_ || !was_hangup_) {
- return;
+ private:
+ using OutputQueue = MpscPollableQueue<ClientManager::Response>;
+ std::shared_ptr<OutputQueue> output_queue_;
+ int output_queue_ready_cnt_{0};
+ std::atomic<bool> receive_lock_{false};
+
+ ClientManager::Response receive_unlocked(double timeout) {
+ if (output_queue_ready_cnt_ == 0) {
+ output_queue_ready_cnt_ = output_queue_->reader_wait_nonblock();
}
- Scheduler::instance()->finish();
- stop();
+ if (output_queue_ready_cnt_ > 0) {
+ output_queue_ready_cnt_--;
+ return output_queue_->reader_get_unsafe();
+ }
+ if (timeout != 0) {
+ output_queue_->reader_get_event_fd().wait(static_cast<int>(timeout * 1000));
+ return receive_unlocked(0);
+ }
+ return {0, 0, nullptr};
}
+};
- void loop() override {
- while (true) {
- int size = input_queue_->reader_wait_nonblock();
- if (size == 0) {
- return;
- }
- for (int i = 0; i < size; i++) {
- auto request = input_queue_->reader_get_unsafe();
- if (request.id == 0 && request.function == nullptr) {
- was_hangup_ = true;
- td_.reset();
- return try_stop();
- }
- send_closure_later(td_, &Td::request, request.id, std::move(request.function));
+class MultiImpl {
+ public:
+ static constexpr int32 ADDITIONAL_THREAD_COUNT = 3;
+
+ explicit MultiImpl(std::shared_ptr<NetQueryStats> net_query_stats) {
+ concurrent_scheduler_ = std::make_shared<ConcurrentScheduler>(ADDITIONAL_THREAD_COUNT, 0);
+ concurrent_scheduler_->start();
+
+ {
+ auto guard = concurrent_scheduler_->get_main_guard();
+ Td::Options options;
+ options.net_query_stats = std::move(net_query_stats);
+ multi_td_ = create_actor<MultiTd>("MultiTd", std::move(options));
+ }
+
+ scheduler_thread_ = thread([concurrent_scheduler = concurrent_scheduler_] {
+ while (concurrent_scheduler->run_main(10)) {
}
+ });
+ }
+ MultiImpl(const MultiImpl &) = delete;
+ MultiImpl &operator=(const MultiImpl &) = delete;
+ MultiImpl(MultiImpl &&) = delete;
+ MultiImpl &operator=(MultiImpl &&) = delete;
+
+ static int32 create_id() {
+ auto result = current_id_.fetch_add(1);
+ CHECK(result <= static_cast<uint32>(std::numeric_limits<int32>::max()));
+ return static_cast<int32>(result);
+ }
+
+ void create(int32 td_id, unique_ptr<TdCallback> callback) {
+ auto guard = concurrent_scheduler_->get_send_guard();
+ send_closure(multi_td_, &MultiTd::create, td_id, std::move(callback));
+ }
+
+ static bool is_valid_client_id(int32 client_id) {
+ return client_id > 0 && static_cast<uint32>(client_id) < current_id_.load();
+ }
+
+ void send(ClientManager::ClientId client_id, ClientManager::RequestId request_id,
+ td_api::object_ptr<td_api::Function> &&request) {
+ auto guard = concurrent_scheduler_->get_send_guard();
+ send_closure(multi_td_, &MultiTd::send, client_id, request_id, std::move(request));
+ }
+
+ void close(ClientManager::ClientId client_id) {
+ auto guard = concurrent_scheduler_->get_send_guard();
+ send_closure(multi_td_, &MultiTd::close, client_id);
+ }
+
+ ~MultiImpl() {
+ {
+ auto guard = concurrent_scheduler_->get_send_guard();
+ multi_td_.reset();
+ Scheduler::instance()->finish();
}
+ if (!ExitGuard::is_exited()) {
+ scheduler_thread_.join();
+ } else {
+ scheduler_thread_.detach();
+ }
+ concurrent_scheduler_->finish();
}
- void hangup() override {
- UNREACHABLE();
+ private:
+ std::shared_ptr<ConcurrentScheduler> concurrent_scheduler_;
+ thread scheduler_thread_;
+ ActorOwn<MultiTd> multi_td_;
+
+ static std::atomic<uint32> current_id_;
+};
+
+constexpr int32 MultiImpl::ADDITIONAL_THREAD_COUNT;
+std::atomic<uint32> MultiImpl::current_id_{1};
+
+class MultiImplPool {
+ public:
+ std::shared_ptr<MultiImpl> get() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (impls_.empty()) {
+ init_openssl_threads();
+
+ auto max_client_threads = clamp(thread::hardware_concurrency(), 8u, 20u) * 5 / 4;
+#if TD_OPENBSD
+ max_client_threads = td::min(max_client_threads, 4u);
+#endif
+ impls_.resize(max_client_threads);
+ CHECK(impls_.size() * (1 + MultiImpl::ADDITIONAL_THREAD_COUNT + 1 /* IOCP */) < 128);
+
+ net_query_stats_ = std::make_shared<NetQueryStats>();
+ }
+ auto &impl = *std::min_element(impls_.begin(), impls_.end(),
+ [](auto &a, auto &b) { return a.lock().use_count() < b.lock().use_count(); });
+ auto result = impl.lock();
+ if (!result) {
+ result = std::make_shared<MultiImpl>(net_query_stats_);
+ impl = result;
+ }
+ return result;
}
- void tear_down() override {
- auto &fd = input_queue_->reader_get_event_fd();
- ::td::unsubscribe(fd.get_fd());
- fd.get_fd().set_observer(nullptr);
+ void try_clear() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ if (impls_.empty()) {
+ return;
+ }
+
+ for (auto &impl : impls_) {
+ if (impl.lock().use_count() != 0) {
+ return;
+ }
+ }
+ reset_to_empty(impls_);
+
+ CHECK(net_query_stats_.use_count() == 1);
+ CHECK(net_query_stats_->get_count() == 0);
+ net_query_stats_ = nullptr;
}
+
+ private:
+ std::mutex mutex_;
+ std::vector<std::weak_ptr<MultiImpl>> impls_;
+ std::shared_ptr<NetQueryStats> net_query_stats_;
};
-/*** Client::Impl ***/
-class Client::Impl final : ObserverBase {
+class ClientManager::Impl final {
public:
- Impl() {
- init();
+ ClientId create_client_id() {
+ auto client_id = MultiImpl::create_id();
+ {
+ auto lock = impls_mutex_.lock_write().move_as_ok();
+ impls_[client_id]; // create empty MultiImplInfo
+ }
+ return client_id;
}
- void send(Request request) {
- if (request.id == 0 || request.function == nullptr) {
- LOG(ERROR) << "Drop wrong request " << request.id;
+ void send(ClientId client_id, RequestId request_id, td_api::object_ptr<td_api::Function> &&request) {
+ auto lock = impls_mutex_.lock_read().move_as_ok();
+ if (!MultiImpl::is_valid_client_id(client_id)) {
+ receiver_.add_response(client_id, request_id,
+ td_api::make_object<td_api::error>(400, "Invalid TDLib instance specified"));
return;
}
- input_queue_->writer_put(std::move(request));
+ auto it = impls_.find(client_id);
+ if (it != impls_.end() && it->second.impl == nullptr) {
+ lock.reset();
+
+ auto write_lock = impls_mutex_.lock_write().move_as_ok();
+ it = impls_.find(client_id);
+ if (it != impls_.end() && it->second.impl == nullptr) {
+ it->second.impl = pool_.get();
+ it->second.impl->create(client_id, receiver_.create_callback(client_id));
+ }
+ write_lock.reset();
+
+ lock = impls_mutex_.lock_read().move_as_ok();
+ it = impls_.find(client_id);
+ }
+ if (it == impls_.end() || it->second.is_closed) {
+ receiver_.add_response(client_id, request_id, td_api::make_object<td_api::error>(500, "Request aborted"));
+ return;
+ }
+ it->second.impl->send(client_id, request_id, std::move(request));
}
Response receive(double timeout) {
- if (output_queue_ready_cnt_ == 0) {
- output_queue_ready_cnt_ = output_queue_->reader_wait_nonblock();
+ auto response = receiver_.receive(timeout, true);
+ if (response.request_id == 0 && response.object != nullptr &&
+ response.object->get_id() == td_api::updateAuthorizationState::ID &&
+ static_cast<const td_api::updateAuthorizationState *>(response.object.get())->authorization_state_->get_id() ==
+ td_api::authorizationStateClosed::ID) {
+ auto lock = impls_mutex_.lock_write().move_as_ok();
+ close_impl(response.client_id);
+
+ response.client_id = 0;
+ response.object = nullptr;
}
- if (output_queue_ready_cnt_ > 0) {
- output_queue_ready_cnt_--;
- return output_queue_->reader_get_unsafe();
+ if (response.object == nullptr && response.client_id != 0 && response.request_id == 0) {
+ auto lock = impls_mutex_.lock_write().move_as_ok();
+ auto it = impls_.find(response.client_id);
+ CHECK(it != impls_.end());
+ CHECK(it->second.is_closed);
+ impls_.erase(it);
+
+ response.object = td_api::make_object<td_api::updateAuthorizationState>(
+ td_api::make_object<td_api::authorizationStateClosed>());
+
+ if (impls_.empty()) {
+ reset_to_empty(impls_);
+ pool_.try_clear();
+ }
}
- if (timeout != 0) {
- poll_.run(static_cast<int>(timeout * 1000));
- return receive(0);
+ return response;
+ }
+
+ void close_impl(ClientId client_id) {
+ auto it = impls_.find(client_id);
+ CHECK(it != impls_.end());
+ if (!it->second.is_closed) {
+ it->second.is_closed = true;
+ if (it->second.impl == nullptr) {
+ receiver_.add_response(client_id, 0, nullptr);
+ } else {
+ it->second.impl->close(client_id);
+ }
}
- return {0, nullptr};
}
+ Impl() = default;
+ Impl(const Impl &) = delete;
+ Impl &operator=(const Impl &) = delete;
+ Impl(Impl &&) = delete;
+ Impl &operator=(Impl &&) = delete;
~Impl() {
- input_queue_->writer_put({0, nullptr});
- scheduler_thread_.join();
+ if (ExitGuard::is_exited()) {
+ return;
+ }
+ for (auto &it : impls_) {
+ close_impl(it.first);
+ }
+ while (!impls_.empty() && !ExitGuard::is_exited()) {
+ receive(0.1);
+ }
}
private:
- Poll poll_;
- std::shared_ptr<InputQueue> input_queue_;
- std::shared_ptr<OutputQueue> output_queue_;
- std::shared_ptr<ConcurrentScheduler> scheduler_;
- int output_queue_ready_cnt_{0};
- thread scheduler_thread_;
- bool notify_flag_{false};
+ MultiImplPool pool_;
+ RwMutex impls_mutex_;
+ struct MultiImplInfo {
+ std::shared_ptr<MultiImpl> impl;
+ bool is_closed = false;
+ };
+ FlatHashMap<ClientId, MultiImplInfo> impls_;
+ TdReceiver receiver_;
+};
- void init() {
- input_queue_ = std::make_shared<InputQueue>();
- input_queue_->init();
- output_queue_ = std::make_shared<OutputQueue>();
- output_queue_->init();
- scheduler_ = std::make_shared<ConcurrentScheduler>();
- scheduler_->init(3);
- scheduler_->create_actor_unsafe<TdProxy>(0, "TdProxy", input_queue_, output_queue_).release();
- scheduler_->start();
+class Client::Impl final {
+ public:
+ Impl() {
+ static MultiImplPool pool;
+ multi_impl_ = pool.get();
+ td_id_ = MultiImpl::create_id();
+ multi_impl_->create(td_id_, receiver_.create_callback(td_id_));
+ }
- scheduler_thread_ = thread([scheduler = scheduler_] {
- while (scheduler->run_main(10)) {
- }
- scheduler->finish();
- });
+ void send(Request request) {
+ if (request.id == 0 || request.function == nullptr) {
+ LOG(ERROR) << "Drop wrong request " << request.id;
+ return;
+ }
- poll_.init();
- auto &event_fd = output_queue_->reader_get_event_fd();
- event_fd.get_fd().set_observer(this);
- poll_.subscribe(event_fd.get_fd(), Fd::Read);
+ multi_impl_->send(td_id_, request.id, std::move(request.function));
}
- void notify() override {
- notify_flag_ = true;
+ Response receive(double timeout) {
+ auto response = receiver_.receive(timeout, false);
+
+ Response old_response;
+ old_response.id = response.request_id;
+ old_response.object = std::move(response.object);
+ return old_response;
+ }
+
+ Impl(const Impl &) = delete;
+ Impl &operator=(const Impl &) = delete;
+ Impl(Impl &&) = delete;
+ Impl &operator=(Impl &&) = delete;
+ ~Impl() {
+ multi_impl_->close(td_id_);
+ while (!ExitGuard::is_exited()) {
+ auto response = receiver_.receive(0.1, false);
+ if (response.object == nullptr && response.client_id != 0 && response.request_id == 0) {
+ break;
+ }
+ }
}
+
+ private:
+ std::shared_ptr<MultiImpl> multi_impl_;
+ TdReceiver receiver_;
+
+ int32 td_id_;
};
#endif
-/*** Client ***/
-Client::Client() : impl_(make_unique<Impl>()) {
- // At least it should be enough for everybody who uses TDLib
- init_openssl_threads();
+Client::Client() : impl_(std::make_unique<Impl>()) {
}
void Client::send(Request &&request) {
@@ -284,8 +653,67 @@ Client::Response Client::execute(Request &&request) {
return response;
}
+Client::Client(Client &&other) noexcept = default;
+Client &Client::operator=(Client &&other) noexcept = default;
Client::~Client() = default;
-Client::Client(Client &&other) = default;
-Client &Client::operator=(Client &&other) = default;
+
+ClientManager::ClientManager() : impl_(std::make_unique<Impl>()) {
+}
+
+ClientManager::ClientId ClientManager::create_client_id() {
+ return impl_->create_client_id();
+}
+
+void ClientManager::send(ClientId client_id, RequestId request_id, td_api::object_ptr<td_api::Function> &&request) {
+ impl_->send(client_id, request_id, std::move(request));
+}
+
+ClientManager::Response ClientManager::receive(double timeout) {
+ return impl_->receive(timeout);
+}
+
+td_api::object_ptr<td_api::Object> ClientManager::execute(td_api::object_ptr<td_api::Function> &&request) {
+ return Td::static_request(std::move(request));
+}
+
+static std::atomic<ClientManager::LogMessageCallbackPtr> log_message_callback;
+
+static void log_message_callback_wrapper(int verbosity_level, CSlice message) {
+ auto callback = log_message_callback.load(std::memory_order_relaxed);
+ if (callback != nullptr) {
+ if (check_utf8(message)) {
+ callback(verbosity_level, message.c_str());
+ } else {
+ size_t pos = 0;
+ while (1 <= message[pos] && message[pos] <= 126) {
+ pos++;
+ }
+ CHECK(pos + 1 < message.size());
+ auto utf8_message = PSTRING() << message.substr(0, pos)
+ << url_encode(message.substr(pos, message.size() - pos - 1)) << '\n';
+ callback(verbosity_level, utf8_message.c_str());
+ }
+ }
+}
+
+void ClientManager::set_log_message_callback(int max_verbosity_level, LogMessageCallbackPtr callback) {
+ if (callback == nullptr) {
+ ::td::set_log_message_callback(max_verbosity_level, nullptr);
+ log_message_callback = nullptr;
+ } else {
+ log_message_callback = callback;
+ ::td::set_log_message_callback(max_verbosity_level, log_message_callback_wrapper);
+ }
+}
+
+ClientManager::ClientManager(ClientManager &&other) noexcept = default;
+ClientManager &ClientManager::operator=(ClientManager &&other) noexcept = default;
+ClientManager::~ClientManager() = default;
+
+ClientManager *ClientManager::get_manager_singleton() {
+ static ClientManager client_manager;
+ static ExitGuard exit_guard;
+ return &client_manager;
+}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Client.h b/protocols/Telegram/tdlib/td/td/telegram/Client.h
index ccd891b009..01dc8ac020 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Client.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Client.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -17,15 +17,170 @@
namespace td {
/**
- * Native C++ interface for interaction with TDLib.
+ * The native C++ interface for interaction with TDLib.
+ *
+ * A TDLib client instance can be created through the method ClientManager::create_client_id.
+ * Requests can be sent using the method ClientManager::send from any thread.
+ * New updates and responses to requests can be received using the method ClientManager::receive from any thread after
+ * the first request has been sent to the client instance. ClientManager::receive must not be called simultaneously from
+ * two different threads. Also note that all updates and responses to requests should be applied in the same order as
+ * they were received, to ensure consistency.
+ * Some TDLib requests can be executed synchronously from any thread using the method ClientManager::execute.
+ *
+ * General pattern of usage:
+ * \code
+ * td::ClientManager manager;
+ * auto client_id = manager.create_client_id();
+ * // somehow share the manager and the client_id with other threads,
+ * // which will be able to send requests via manager.send(client_id, ...)
+ *
+ * // send some dummy requests to the new instance to activate it
+ * manager.send(client_id, ...);
+ *
+ * const double WAIT_TIMEOUT = 10.0; // seconds
+ * while (true) {
+ * auto response = manager.receive(WAIT_TIMEOUT);
+ * if (response.object == nullptr) {
+ * continue;
+ * }
+ *
+ * if (response.request_id == 0) {
+ * // process response.object as an incoming update of the type td_api::Update for the client response.client_id
+ * } else {
+ * // process response.object as an answer to a request response.request_id for the client response.client_id
+ * }
+ * }
+ * \endcode
+ */
+class ClientManager final {
+ public:
+ /**
+ * Creates a new TDLib client manager.
+ */
+ ClientManager();
+
+ /**
+ * Opaque TDLib client instance identifier.
+ */
+ using ClientId = std::int32_t;
+
+ /**
+ * Request identifier.
+ * Responses to TDLib requests will have the same request id as the corresponding request.
+ * Updates from TDLib will have the request_id == 0, incoming requests are thus not allowed to have request_id == 0.
+ */
+ using RequestId = std::uint64_t;
+
+ /**
+ * Returns an opaque identifier of a new TDLib instance.
+ * The TDLib instance will not send updates until the first request is sent to it.
+ * \return Opaque identifier of a new TDLib instance.
+ */
+ ClientId create_client_id();
+
+ /**
+ * Sends request to TDLib. May be called from any thread.
+ * \param[in] client_id TDLib client instance identifier.
+ * \param[in] request_id Request identifier. Must be non-zero.
+ * \param[in] request Request to TDLib.
+ */
+ void send(ClientId client_id, RequestId request_id, td_api::object_ptr<td_api::Function> &&request);
+
+ /**
+ * A response to a request, or an incoming update from TDLib.
+ */
+ struct Response {
+ /**
+ * TDLib client instance identifier, for which the response was received.
+ */
+ ClientId client_id;
+
+ /**
+ * Request identifier to which the response corresponds, or 0 for incoming updates from TDLib.
+ */
+ RequestId request_id;
+
+ /**
+ * TDLib API object representing a response to a TDLib request or an incoming update.
+ */
+ td_api::object_ptr<td_api::Object> object;
+ };
+
+ /**
+ * Receives incoming updates and responses to requests from TDLib. May be called from any thread, but must not be
+ * called simultaneously from two different threads.
+ * \param[in] timeout The maximum number of seconds allowed for this function to wait for new data.
+ * \return An incoming update or response to a request. The object returned in the response may be a nullptr
+ * if the timeout expires.
+ */
+ Response receive(double timeout);
+
+ /**
+ * Synchronously executes a TDLib request.
+ * A request can be executed synchronously, only if it is documented with "Can be called synchronously".
+ * \param[in] request Request to the TDLib.
+ * \return The request response.
+ */
+ static td_api::object_ptr<td_api::Object> execute(td_api::object_ptr<td_api::Function> &&request);
+
+ /**
+ * A type of callback function that will be called when a message is added to the internal TDLib log.
+ *
+ * \param verbosity_level Log verbosity level with which the message was added from -1 up to 1024.
+ * If 0, then TDLib will crash as soon as the callback returns.
+ * None of the TDLib methods can be called from the callback.
+ * \param message Null-terminated UTF-8-encoded string with the message added to the log.
+ */
+ using LogMessageCallbackPtr = void (*)(int verbosity_level, const char *message);
+
+ /**
+ * Sets the callback that will be called when a message is added to the internal TDLib log.
+ * None of the TDLib methods can be called from the callback.
+ * By default the callback is not set.
+ *
+ * \param[in] max_verbosity_level The maximum verbosity level of messages for which the callback will be called.
+ * \param[in] callback Callback that will be called when a message is added to the internal TDLib log.
+ * Pass nullptr to remove the callback.
+ */
+ static void set_log_message_callback(int max_verbosity_level, LogMessageCallbackPtr callback);
+
+ /**
+ * Destroys the client manager and all TDLib client instances managed by it.
+ */
+ ~ClientManager();
+
+ /**
+ * Move constructor.
+ */
+ ClientManager(ClientManager &&other) noexcept;
+
+ /**
+ * Move assignment operator.
+ */
+ ClientManager &operator=(ClientManager &&other) noexcept;
+
+ /**
+ * Returns a pointer to a singleton ClientManager instance.
+ * \return A unique singleton ClientManager instance.
+ */
+ static ClientManager *get_manager_singleton();
+
+ private:
+ friend class Client;
+ class Impl;
+ std::unique_ptr<Impl> impl_;
+};
+
+/**
+ * Old native C++ interface for interaction with TDLib to be removed in TDLib 2.0.0.
*
* The TDLib instance is created for the lifetime of the Client object.
* Requests to TDLib can be sent using the Client::send method from any thread.
* New updates and responses to requests can be received using the Client::receive method from any thread,
- * this function shouldn't be called simultaneously from two different threads. Also note that all updates and
+ * this function must not be called simultaneously from two different threads. Also note that all updates and
* responses to requests should be applied in the same order as they were received, to ensure consistency.
* Given this information, it's advisable to call this function from a dedicated thread.
- * Some service TDLib requests can be executed synchronously from any thread by using the Client::execute method.
+ * Some service TDLib requests can be executed synchronously from any thread using the Client::execute method.
*
* General pattern of usage:
* \code
@@ -84,7 +239,7 @@ class Client final {
*/
struct Response {
/**
- * TDLib request identifier, which corresponds to the response or 0 for incoming updates from TDLib.
+ * TDLib request identifier, which corresponds to the response, or 0 for incoming updates from TDLib.
*/
std::uint64_t id;
@@ -97,7 +252,7 @@ class Client final {
/**
* Receives incoming updates and request responses from TDLib. May be called from any thread, but shouldn't be
* called simultaneously from two different threads.
- * \param[in] timeout Maximum number of seconds allowed for this function to wait for new data.
+ * \param[in] timeout The maximum number of seconds allowed for this function to wait for new data.
* \return An incoming update or request response. The object returned in the response may be a nullptr
* if the timeout expires.
*/
@@ -119,12 +274,12 @@ class Client final {
/**
* Move constructor.
*/
- Client(Client &&other);
+ Client(Client &&other) noexcept;
/**
* Move assignment operator.
*/
- Client &operator=(Client &&other);
+ Client &operator=(Client &&other) noexcept;
private:
class Impl;
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ClientActor.cpp b/protocols/Telegram/tdlib/td/td/telegram/ClientActor.cpp
index 22652924a4..b9ea5ec5ed 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ClientActor.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/ClientActor.cpp
@@ -1,19 +1,25 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/ClientActor.h"
-#include "td/telegram/td_api.h"
-
#include "td/telegram/net/NetQueryCounter.h"
+#include "td/telegram/net/NetQueryStats.h"
#include "td/telegram/Td.h"
namespace td {
-ClientActor::ClientActor(unique_ptr<TdCallback> callback) {
- td_ = create_actor<Td>("Td", std::move(callback));
+
+ClientActor::ClientActor(unique_ptr<TdCallback> callback, Options options)
+ : callback_(std::move(callback)), options_(std::move(options)) {
+}
+
+void ClientActor::start_up() {
+ Td::Options td_options;
+ td_options.net_query_stats = std::move(options_.net_query_stats);
+ td_ = create_actor<Td>("Td", std::move(callback_), std::move(td_options));
}
void ClientActor::request(uint64 id, td_api::object_ptr<td_api::Function> request) {
@@ -22,16 +28,24 @@ void ClientActor::request(uint64 id, td_api::object_ptr<td_api::Function> reques
ClientActor::~ClientActor() = default;
-ClientActor::ClientActor(ClientActor &&other) = default;
+ClientActor::ClientActor(ClientActor &&other) noexcept = default;
-ClientActor &ClientActor::operator=(ClientActor &&other) = default;
+ClientActor &ClientActor::operator=(ClientActor &&other) noexcept = default;
td_api::object_ptr<td_api::Object> ClientActor::execute(td_api::object_ptr<td_api::Function> request) {
return Td::static_request(std::move(request));
}
-uint64 get_pending_network_query_count() {
- return NetQueryCounter::get_count();
+std::shared_ptr<NetQueryStats> create_net_query_stats() {
+ return std::make_shared<NetQueryStats>();
+}
+
+void dump_pending_network_queries(NetQueryStats &stats) {
+ stats.dump_pending_network_queries();
+}
+
+uint64 get_pending_network_query_count(NetQueryStats &stats) {
+ return stats.get_count();
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ClientActor.h b/protocols/Telegram/tdlib/td/td/telegram/ClientActor.h
index 37d6629b6d..de164cb71f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ClientActor.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/ClientActor.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,30 +8,43 @@
///\file
-#include "td/actor/actor.h"
-
#include "td/telegram/td_api.h"
#include "td/telegram/td_api.hpp"
-
#include "td/telegram/TdCallback.h"
+#include "td/actor/actor.h"
+
#include "td/utils/common.h"
+#include <memory>
+
namespace td {
+class NetQueryStats;
class Td;
/**
* This is a low-level Actor interface for interaction with TDLib. The interface is a lot more flexible than
- * the Client interface, however, for most usages the Client interface should be sufficient.
+ * the ClientManager interface, however, for most usages the ClientManager interface should be sufficient.
*/
-class ClientActor : public Actor {
+class ClientActor final : public Actor {
public:
+ /// Options for ClientActor creation.
+ struct Options {
+ /// NetQueryStats object for this client.
+ std::shared_ptr<NetQueryStats> net_query_stats;
+
+ /// Default constructor.
+ Options() {
+ }
+ };
+
/**
* Creates a ClientActor using the specified callback.
* \param[in] callback Callback for outgoing notifications from TDLib.
+ * \param[in] options Options to create the TDLib.
*/
- explicit ClientActor(unique_ptr<TdCallback> callback);
+ explicit ClientActor(unique_ptr<TdCallback> callback, Options options = {});
/**
* Sends one request to TDLib. The answer will be received via callback.
@@ -43,7 +56,7 @@ class ClientActor : public Actor {
/**
* Synchronously executes a TDLib request. Only a few requests can be executed synchronously.
* May be called from any thread.
- * \param[in] request Request to the TDLib.
+ * \param[in] request Request to the TDLib instance.
* \return The request response.
*/
static td_api::object_ptr<td_api::Object> execute(td_api::object_ptr<td_api::Function> request);
@@ -51,35 +64,45 @@ class ClientActor : public Actor {
/**
* Destroys the ClientActor and the TDLib instance.
*/
- ~ClientActor();
+ ~ClientActor() final;
/**
* Move constructor.
*/
- ClientActor(ClientActor &&other);
+ ClientActor(ClientActor &&other) noexcept;
/**
* Move assignment operator.
*/
- ClientActor &operator=(ClientActor &&other);
+ ClientActor &operator=(ClientActor &&other) noexcept;
ClientActor(const ClientActor &other) = delete;
+
ClientActor &operator=(const ClientActor &other) = delete;
private:
+ void start_up() final;
+
ActorOwn<Td> td_;
+ unique_ptr<TdCallback> callback_;
+ Options options_;
};
/**
+ * Creates NetQueryStats object, which can be shared between different clients.
+ */
+std::shared_ptr<NetQueryStats> create_net_query_stats();
+
+/**
* Dumps information about all pending network queries to the internal TDLib log.
* This is useful for library debugging.
*/
-void dump_pending_network_queries();
+void dump_pending_network_queries(NetQueryStats &stats);
/**
* Returns the current number of pending network queries. Useful for library debugging.
* \return Number of currently pending network queries.
*/
-uint64 get_pending_network_query_count();
+uint64 get_pending_network_query_count(NetQueryStats &stats);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ClientDotNet.cpp b/protocols/Telegram/tdlib/td/td/telegram/ClientDotNet.cpp
index 62ab095b0b..44195978e7 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ClientDotNet.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/ClientDotNet.cpp
@@ -1,22 +1,37 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#pragma managed(push, off)
#include "td/telegram/Client.h"
+#pragma managed(pop)
#include "td/telegram/TdDotNetApi.h"
#include "td/utils/port/CxCli.h"
+#pragma managed(push, off)
#include <cstdint>
+#pragma managed(pop)
namespace Telegram {
namespace Td {
using namespace CxCli;
+#if !TD_CLI
+/// <summary>
+/// A type of callback function that will be called when a message is added to the internal TDLib log.
+/// </summary>
+/// <param name="verbosityLevel">Log verbosity level with which the message was added from -1 up to 1024.
+/// If 0, then TDLib will crash as soon as the callback returns.
+/// None of the TDLib methods can be called from the callback.</param>
+/// <param name="message">The message added to the log.</param>
+public delegate void LogMessageCallback(int verbosityLevel, String^ message);
+#endif
+
/// <summary>
/// Interface for handler for results of queries to TDLib and incoming updates from TDLib.
/// </summary>
@@ -41,18 +56,12 @@ public:
/// of the query or with Telegram.Td.Api.Error as parameter. If it is null, nothing will be called.</param>
/// <exception cref="NullReferenceException">Thrown when query is null.</exception>
void Send(Api::Function^ function, ClientResultHandler^ handler) {
- if (function == nullptr) {
- throw REF_NEW NullReferenceException("Function can't be null");
- }
-
- std::uint64_t queryId = Increment(currentId);
+ std::uint64_t requestId = Increment(currentRequestId);
if (handler != nullptr) {
- handlers[queryId] = handler;
+ handlers[requestId] = handler;
}
- td::Client::Request request;
- request.id = queryId;
- request.function = td::td_api::move_object_as<td::td_api::Function>(ToUnmanaged(function)->get_object_ptr());
- client->send(std::move(request));
+ auto request = td::td_api::move_object_as<td::td_api::Function>(ToUnmanaged(function)->get_object_ptr());
+ td::ClientManager::get_manager_singleton()->send(clientId, requestId, std::move(request));
}
/// <summary>
@@ -61,40 +70,33 @@ public:
/// <param name="function">Object representing a query to the TDLib.</param>
/// <returns>Returns request result.</returns>
/// <exception cref="NullReferenceException">Thrown when query is null.</exception>
- Api::BaseObject^ Execute(Api::Function^ function) {
- if (function == nullptr) {
- throw REF_NEW NullReferenceException("Function can't be null");
- }
-
- td::Client::Request request;
- request.id = 0;
- request.function = td::td_api::move_object_as<td::td_api::Function>(ToUnmanaged(function)->get_object_ptr());
- return Api::FromUnmanaged(*client->execute(std::move(request)).object);
- }
-
- /// <summary>
- /// Replaces handler for incoming updates from the TDLib.
- /// </summary>
- /// <param name="updatesHandler">Handler with OnResult method which will be called for every incoming update from the TDLib.</param>
- void SetUpdatesHandler(ClientResultHandler^ updatesHandler) {
- handlers[0] = updatesHandler;
+ static Api::BaseObject^ Execute(Api::Function^ function) {
+ auto request = td::td_api::move_object_as<td::td_api::Function>(ToUnmanaged(function)->get_object_ptr());
+ return Api::FromUnmanaged(*td::ClientManager::execute(std::move(request)));
}
/// <summary>
/// Launches a cycle which will fetch all results of queries to TDLib and incoming updates from TDLib.
- /// Must be called once on a separate dedicated thread, on which all updates and query results will be handled.
- /// Returns only when TDLib instance is closed.
+ /// Must be called once on a separate dedicated thread on which all updates and query results from all Clients will be handled.
+ /// Never returns.
/// </summary>
- void Run() {
+ static void Run() {
while (true) {
- auto response = client->receive(10.0);
+ auto response = td::ClientManager::get_manager_singleton()->receive(300.0);
if (response.object != nullptr) {
- ProcessResult(response.id, Api::FromUnmanaged(*response.object));
-
- if (response.object->get_id() == td::td_api::updateAuthorizationState::ID &&
+ bool isClosed = response.object->get_id() == td::td_api::updateAuthorizationState::ID &&
static_cast<td::td_api::updateAuthorizationState &>(*response.object).authorization_state_->get_id() ==
- td::td_api::authorizationStateClosed::ID) {
- break;
+ td::td_api::authorizationStateClosed::ID && response.request_id == 0;
+
+ ClientResultHandler^ handler;
+ if (response.request_id == 0 ? updateHandlers.TryGetValue(response.client_id, handler) :
+ handlers.TryRemove(response.request_id, handler)) {
+ // TODO try/catch
+ handler->OnResult(Api::FromUnmanaged(*response.object));
+ }
+
+ if (isClosed) {
+ updateHandlers.TryRemove(response.client_id, handler);
}
}
}
@@ -103,35 +105,71 @@ public:
/// <summary>
/// Creates new Client.
/// </summary>
- /// <param name="updatesHandler">Handler for incoming updates.</param>
+ /// <param name="updateHandler">Handler for incoming updates.</param>
/// <returns>Returns created Client.</returns>
- static Client^ Create(ClientResultHandler^ updatesHandler) {
- return REF_NEW Client(updatesHandler);
+ static Client^ Create(ClientResultHandler^ updateHandler) {
+ return REF_NEW Client(updateHandler);
}
-private:
- Client(ClientResultHandler^ updatesHandler) {
- client = new td::Client();
- handlers[0] = updatesHandler;
+#if !TD_CLI
+ /// <summary>
+ /// Sets the callback that will be called when a message is added to the internal TDLib log.
+ /// None of the TDLib methods can be called from the callback.
+ /// </summary>
+ /// <param name="max_verbosity_level">The maximum verbosity level of messages for which the callback will be called.</param>
+ /// <param name="callback">Callback that will be called when a message is added to the internal TDLib log.
+ /// Pass null to remove the callback.</param>
+ static void SetLogMessageCallback(std::int32_t max_verbosity_level, LogMessageCallback^ callback) {
+ std::lock_guard<std::mutex> lock(logMutex);
+ if (callback == nullptr) {
+ td::ClientManager::set_log_message_callback(max_verbosity_level, nullptr);
+ logMessageCallback = nullptr;
+ } else {
+ logMessageCallback = callback;
+ td::ClientManager::set_log_message_callback(max_verbosity_level, LogMessageCallbackWrapper);
+ }
}
+#endif
- ~Client() {
- delete client;
+private:
+ Client(ClientResultHandler^ updateHandler) {
+ clientId = td::ClientManager::get_manager_singleton()->create_client_id();
+ if (updateHandler != nullptr) {
+ updateHandlers[clientId] = updateHandler;
+ }
+ Send(REF_NEW Api::GetOption("version"), nullptr);
}
- std::int64_t currentId = 0;
- ConcurrentDictionary<std::uint64_t, ClientResultHandler^> handlers;
- td::Client *client = nullptr;
-
- void ProcessResult(std::uint64_t id, Api::BaseObject^ object) {
- ClientResultHandler^ handler;
- // update handler stays forever
- if (id == 0 ? handlers.TryGetValue(id, handler) : handlers.TryRemove(id, handler)) {
- // TODO try/catch
- handler->OnResult(object);
+#if !TD_CLI
+ static std::int64_t currentRequestId;
+#else
+ static std::int64_t currentRequestId = 0;
+#endif
+ static ConcurrentDictionary<std::uint64_t, ClientResultHandler^> handlers;
+ static ConcurrentDictionary<std::int32_t, ClientResultHandler^> updateHandlers;
+ std::int32_t clientId;
+
+#if !TD_CLI
+ static std::mutex logMutex;
+ static LogMessageCallback^ logMessageCallback;
+
+ static void LogMessageCallbackWrapper(int verbosity_level, const char *message) {
+ auto callback = logMessageCallback;
+ if (callback != nullptr) {
+ callback(verbosity_level, string_from_unmanaged(message));
}
}
+#endif
};
+#if !TD_CLI
+std::int64_t Client::currentRequestId = 0;
+ConcurrentDictionary<std::uint64_t, ClientResultHandler^> Client::handlers;
+ConcurrentDictionary<std::int32_t, ClientResultHandler^> Client::updateHandlers;
+
+std::mutex Client::logMutex;
+LogMessageCallback^ Client::logMessageCallback;
+#endif
+
} // namespace Td
} // namespace Telegram
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ClientJson.cpp b/protocols/Telegram/tdlib/td/td/telegram/ClientJson.cpp
index f5adc09eca..c2ab41774e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ClientJson.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/ClientJson.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,38 +9,94 @@
#include "td/telegram/td_api.h"
#include "td/telegram/td_api_json.h"
-#include "td/tl/tl_json.h"
-
-#include "td/utils/format.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/JsonBuilder.h"
-#include "td/utils/logging.h"
-#include "td/utils/Status.h"
+#include "td/utils/port/thread_local.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StackAllocator.h"
+#include "td/utils/StringBuilder.h"
+
+#include <utility>
namespace td {
-Result<Client::Request> ClientJson::to_request(Slice request) {
+static td_api::object_ptr<td_api::Function> get_return_error_function(Slice error_message) {
+ auto error = td_api::make_object<td_api::error>(400, error_message.str());
+ return td_api::make_object<td_api::testReturnError>(std::move(error));
+}
+
+static std::pair<td_api::object_ptr<td_api::Function>, string> to_request(Slice request) {
auto request_str = request.str();
- TRY_RESULT(json_value, json_decode(request_str));
+ auto r_json_value = json_decode(request_str);
+ if (r_json_value.is_error()) {
+ return {get_return_error_function(PSLICE()
+ << "Failed to parse request as JSON object: " << r_json_value.error().message()),
+ string()};
+ }
+ auto json_value = r_json_value.move_as_ok();
if (json_value.type() != JsonValue::Type::Object) {
- return Status::Error("Expected an object");
+ return {get_return_error_function("Expected a JSON object"), string()};
}
- TRY_RESULT(extra_field, get_json_object_field(json_value.get_object(), "@extra", JsonValue::Type::Null, true));
- std::uint64_t extra_id = extra_id_.fetch_add(1, std::memory_order_relaxed);
- auto extra_str = json_encode<string>(extra_field);
- if (!extra_str.empty()) {
- std::lock_guard<std::mutex> guard(mutex_);
- extra_[extra_id] = std::move(extra_str);
+
+ string extra;
+ if (has_json_object_field(json_value.get_object(), "@extra")) {
+ extra = json_encode<string>(
+ get_json_object_field(json_value.get_object(), "@extra", JsonValue::Type::Null).move_as_ok());
}
td_api::object_ptr<td_api::Function> func;
- TRY_STATUS(from_json(func, json_value));
- return Client::Request{extra_id, std::move(func)};
+ auto status = from_json(func, std::move(json_value));
+ if (status.is_error()) {
+ return {get_return_error_function(PSLICE() << "Failed to parse JSON object as TDLib request: " << status.message()),
+ std::move(extra)};
+ }
+ return std::make_pair(std::move(func), std::move(extra));
+}
+
+static string from_response(const td_api::Object &object, const string &extra, int client_id) {
+ auto buf = StackAllocator::alloc(1 << 18);
+ JsonBuilder jb(StringBuilder(buf.as_slice(), true), -1);
+ jb.enter_value() << ToJson(object);
+ auto &sb = jb.string_builder();
+ auto slice = sb.as_cslice();
+ CHECK(!slice.empty() && slice.back() == '}');
+ sb.pop_back();
+ if (!extra.empty()) {
+ sb << ",\"@extra\":" << extra;
+ }
+ if (client_id != 0) {
+ sb << ",\"@client_id\":" << client_id;
+ }
+ sb << '}';
+ return sb.as_cslice().str();
+}
+
+static TD_THREAD_LOCAL string *current_output;
+
+static const char *store_string(string str) {
+ init_thread_local<string>(current_output);
+ *current_output = std::move(str);
+ return current_output->c_str();
+}
+
+void ClientJson::send(Slice request) {
+ auto parsed_request = to_request(request);
+ std::uint64_t extra_id = extra_id_.fetch_add(1, std::memory_order_relaxed);
+ if (!parsed_request.second.empty()) {
+ std::lock_guard<std::mutex> guard(mutex_);
+ extra_[extra_id] = std::move(parsed_request.second);
+ }
+ client_.send(Client::Request{extra_id, std::move(parsed_request.first)});
}
-std::string ClientJson::from_response(Client::Response response) {
- auto str = json_encode<string>(ToJson(static_cast<td_api::Object &>(*response.object)));
- CHECK(!str.empty() && str.back() == '}');
- std::string extra;
+const char *ClientJson::receive(double timeout) {
+ auto response = client_.receive(timeout);
+ if (response.object == nullptr) {
+ return nullptr;
+ }
+
+ string extra;
if (response.id != 0) {
std::lock_guard<std::mutex> guard(mutex_);
auto it = extra_.find(response.id);
@@ -49,49 +105,59 @@ std::string ClientJson::from_response(Client::Response response) {
extra_.erase(it);
}
}
- if (!extra.empty()) {
- str.pop_back();
- str.reserve(str.size() + 10 + extra.size());
- str += ",\"@extra\":";
- str += extra;
- str += "}";
- }
- return str;
+ return store_string(from_response(*response.object, extra, 0));
}
-TD_THREAD_LOCAL std::string *ClientJson::current_output_;
-CSlice ClientJson::store_string(std::string str) {
- init_thread_local<std::string>(ClientJson::current_output_);
- *current_output_ = std::move(str);
- return *current_output_;
+const char *ClientJson::execute(Slice request) {
+ auto parsed_request = to_request(request);
+ return store_string(from_response(*Client::execute(Client::Request{0, std::move(parsed_request.first)}).object,
+ parsed_request.second, 0));
}
-void ClientJson::send(Slice request) {
- auto status = [&] {
- TRY_RESULT(client_request, to_request(request));
- client_.send(std::move(client_request));
- return Status::OK();
- }();
+static ClientManager *get_manager() {
+ return ClientManager::get_manager_singleton();
+}
+
+static std::mutex extra_mutex;
+static FlatHashMap<int64, string> extra;
+static std::atomic<uint64> extra_id{1};
- LOG_IF(ERROR, status.is_error()) << "Failed to parse " << tag("request", format::escaped(request)) << " " << status;
+int json_create_client_id() {
+ return static_cast<int>(get_manager()->create_client_id());
}
-CSlice ClientJson::receive(double timeout) {
- auto response = client_.receive(timeout);
- if (!response.object) {
- return {};
+void json_send(int client_id, Slice request) {
+ auto parsed_request = to_request(request);
+ auto request_id = extra_id.fetch_add(1, std::memory_order_relaxed);
+ if (!parsed_request.second.empty()) {
+ std::lock_guard<std::mutex> guard(extra_mutex);
+ extra[request_id] = std::move(parsed_request.second);
}
- return store_string(from_response(std::move(response)));
+ get_manager()->send(client_id, request_id, std::move(parsed_request.first));
}
-CSlice ClientJson::execute(Slice request) {
- auto r_request = to_request(request);
- if (r_request.is_error()) {
- LOG(ERROR) << "Failed to parse " << tag("request", format::escaped(request)) << " " << r_request.error();
- return {};
+const char *json_receive(double timeout) {
+ auto response = get_manager()->receive(timeout);
+ if (!response.object) {
+ return nullptr;
}
- return store_string(from_response(Client::execute(r_request.move_as_ok())));
+ string extra_str;
+ if (response.request_id != 0) {
+ std::lock_guard<std::mutex> guard(extra_mutex);
+ auto it = extra.find(response.request_id);
+ if (it != extra.end()) {
+ extra_str = std::move(it->second);
+ extra.erase(it);
+ }
+ }
+ return store_string(from_response(*response.object, extra_str, response.client_id));
+}
+
+const char *json_execute(Slice request) {
+ auto parsed_request = to_request(request);
+ return store_string(
+ from_response(*ClientManager::execute(std::move(parsed_request.first)), parsed_request.second, 0));
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ClientJson.h b/protocols/Telegram/tdlib/td/td/telegram/ClientJson.h
index 880f144d94..1f86ed05af 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ClientJson.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/ClientJson.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,37 +8,38 @@
#include "td/telegram/Client.h"
-#include "td/utils/port/thread_local.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/Slice.h"
-#include "td/utils/Status.h"
#include <atomic>
#include <cstdint>
#include <mutex>
#include <string>
-#include <unordered_map>
-#include <vector>
namespace td {
+// TODO can be removed in TDLib 2.0
class ClientJson final {
public:
void send(Slice request);
- CSlice receive(double timeout);
+ const char *receive(double timeout);
- CSlice execute(Slice request);
+ static const char *execute(Slice request);
private:
Client client_;
std::mutex mutex_; // for extra_
- std::unordered_map<std::int64_t, std::string> extra_;
+ FlatHashMap<std::int64_t, std::string> extra_;
std::atomic<std::uint64_t> extra_id_{1};
- static TD_THREAD_LOCAL std::string *current_output_;
+};
- CSlice store_string(std::string str);
+int json_create_client_id();
+
+void json_send(int client_id, Slice request);
+
+const char *json_receive(double timeout);
+
+const char *json_execute(Slice request);
- Result<Client::Request> to_request(Slice request);
- std::string from_response(Client::Response response);
-};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ConfigManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/ConfigManager.cpp
index c8a16d638e..6425ca2c38 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ConfigManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/ConfigManager.cpp
@@ -1,53 +1,155 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/ConfigManager.h"
-#include "td/telegram/ConfigShared.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ConnectionState.h"
#include "td/telegram/Global.h"
+#include "td/telegram/JsonValue.h"
+#include "td/telegram/LinkManager.h"
#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/MessageReaction.h"
+#include "td/telegram/net/AuthDataShared.h"
#include "td/telegram/net/ConnectionCreator.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/DcOptions.h"
#include "td/telegram/net/NetQuery.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/net/NetType.h"
+#include "td/telegram/net/PublicRsaKeyShared.h"
#include "td/telegram/net/Session.h"
+#include "td/telegram/Premium.h"
+#include "td/telegram/StateManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/mtproto/AuthData.h"
+#include "td/mtproto/AuthKey.h"
+#include "td/mtproto/RawConnection.h"
+#include "td/mtproto/RSA.h"
+#include "td/mtproto/TransportType.h"
#if !TD_EMSCRIPTEN //FIXME
-#include "td/net/HttpQuery.h"
-#include "td/net/SslFd.h"
+#include "td/net/SslCtx.h"
#include "td/net/Wget.h"
#endif
-#include "td/actor/actor.h"
+#include "td/net/HttpQuery.h"
-#include "td/telegram/telegram_api.h"
+#include "td/actor/actor.h"
+#include "td/utils/algorithm.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
+#include "td/utils/emoji.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/format.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/Parser.h"
#include "td/utils/port/Clocks.h"
#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
+#include "td/utils/tl_helpers.h"
#include "td/utils/tl_parsers.h"
+#include "td/utils/UInt.h"
-#include <algorithm>
+#include <functional>
#include <memory>
#include <utility>
namespace td {
-static int VERBOSITY_NAME(config_recoverer) = VERBOSITY_NAME(INFO);
+int VERBOSITY_NAME(config_recoverer) = VERBOSITY_NAME(INFO);
+
+Result<int32> HttpDate::to_unix_time(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second) {
+ if (year < 1970 || year > 2037) {
+ return Status::Error("Invalid year");
+ }
+ if (month < 1 || month > 12) {
+ return Status::Error("Invalid month");
+ }
+ if (day < 1 || day > days_in_month(year, month)) {
+ return Status::Error("Invalid day");
+ }
+ if (hour < 0 || hour >= 24) {
+ return Status::Error("Invalid hour");
+ }
+ if (minute < 0 || minute >= 60) {
+ return Status::Error("Invalid minute");
+ }
+ if (second < 0 || second > 60) {
+ return Status::Error("Invalid second");
+ }
+
+ int32 res = 0;
+ for (int32 y = 1970; y < year; y++) {
+ res += (is_leap(y) + 365) * seconds_in_day();
+ }
+ for (int32 m = 1; m < month; m++) {
+ res += days_in_month(year, m) * seconds_in_day();
+ }
+ res += (day - 1) * seconds_in_day();
+ res += hour * 60 * 60;
+ res += minute * 60;
+ res += second;
+ return res;
+}
+
+Result<int32> HttpDate::parse_http_date(string slice) {
+ Parser p(slice);
+ p.read_till(','); // ignore week day
+ p.skip(',');
+ p.skip_whitespaces();
+ p.skip_nofail('0');
+ TRY_RESULT(day, to_integer_safe<int32>(p.read_word()));
+ auto month_name = p.read_word();
+ to_lower_inplace(month_name);
+ TRY_RESULT(year, to_integer_safe<int32>(p.read_word()));
+ p.skip_whitespaces();
+ p.skip_nofail('0');
+ TRY_RESULT(hour, to_integer_safe<int32>(p.read_till(':')));
+ p.skip(':');
+ p.skip_nofail('0');
+ TRY_RESULT(minute, to_integer_safe<int32>(p.read_till(':')));
+ p.skip(':');
+ p.skip_nofail('0');
+ TRY_RESULT(second, to_integer_safe<int32>(p.read_word()));
+ auto gmt = p.read_word();
+ TRY_STATUS(std::move(p.status()));
+ if (gmt != "GMT") {
+ return Status::Error("Timezone must be GMT");
+ }
+
+ static Slice month_names[12] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};
+
+ int month = 0;
+
+ for (int m = 1; m <= 12; m++) {
+ if (month_names[m - 1] == month_name) {
+ month = m;
+ break;
+ }
+ }
+
+ if (month == 0) {
+ return Status::Error("Unknown month name");
+ }
+
+ return HttpDate::to_unix_time(year, month, day, hour, minute, second);
+}
Result<SimpleConfig> decode_config(Slice input) {
- static auto rsa = td::RSA::from_pem(
+ static auto rsa = mtproto::RSA::from_pem_public_key(
"-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAyr+18Rex2ohtVy8sroGP\n"
"BwXD3DOoKCSpjDqYoXgCqB7ioln4eDCFfOBUlfXUEvM/fnKCpF46VkAftlb4VuPD\n"
@@ -73,125 +175,238 @@ Result<SimpleConfig> decode_config(Slice input) {
}
MutableSlice data_rsa_slice(data_rsa);
- rsa.decrypt(data_rsa_slice, data_rsa_slice);
+ rsa.decrypt_signature(data_rsa_slice, data_rsa_slice);
MutableSlice data_cbc = data_rsa_slice.substr(32);
UInt256 key;
UInt128 iv;
- MutableSlice(key.raw, sizeof(key.raw)).copy_from(data_rsa_slice.substr(0, 32));
- MutableSlice(iv.raw, sizeof(iv.raw)).copy_from(data_rsa_slice.substr(16, 16));
- aes_cbc_decrypt(key, &iv, data_cbc, data_cbc);
+ as_slice(key).copy_from(data_rsa_slice.substr(0, 32));
+ as_slice(iv).copy_from(data_rsa_slice.substr(16, 16));
+ aes_cbc_decrypt(as_slice(key), as_slice(iv), data_cbc, data_cbc);
CHECK(data_cbc.size() == 224);
string hash(32, ' ');
sha256(data_cbc.substr(0, 208), MutableSlice(hash));
if (data_cbc.substr(208) != Slice(hash).substr(0, 16)) {
- return Status::Error("sha256 mismatch");
+ return Status::Error("SHA256 mismatch");
}
TlParser len_parser{data_cbc};
int len = len_parser.fetch_int();
- if (len < 0 || len > 204) {
+ if (len < 8 || len > 208) {
return Status::Error(PSLICE() << "Invalid " << tag("data length", len) << " after aes_cbc_decrypt");
}
int constructor_id = len_parser.fetch_int();
if (constructor_id != telegram_api::help_configSimple::ID) {
return Status::Error(PSLICE() << "Wrong " << tag("constructor", format::as_hex(constructor_id)));
}
- BufferSlice raw_config(data_cbc.substr(8, len));
+ BufferSlice raw_config(data_cbc.substr(8, len - 8));
TlBufferParser parser{&raw_config};
auto config = telegram_api::help_configSimple::fetch(parser);
+ parser.fetch_end();
TRY_STATUS(parser.get_status());
return std::move(config);
}
-static ActorOwn<> get_simple_config_impl(Promise<SimpleConfig> promise, int32 scheduler_id, string url, string host) {
+static ActorOwn<> get_simple_config_impl(Promise<SimpleConfigResult> promise, int32 scheduler_id, string url,
+ string host, std::vector<std::pair<string, string>> headers, bool prefer_ipv6,
+ std::function<Result<string>(HttpQuery &)> get_config,
+ string content = string(), string content_type = string()) {
VLOG(config_recoverer) << "Request simple config from " << url;
#if TD_EMSCRIPTEN // FIXME
return ActorOwn<>();
#else
+ const int timeout = 10;
+ const int ttl = 3;
+ headers.emplace_back("Host", std::move(host));
+ headers.emplace_back("User-Agent",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/77.0.3865.90 Safari/537.36");
return ActorOwn<>(create_actor_on_scheduler<Wget>(
"Wget", scheduler_id,
- PromiseCreator::lambda([promise = std::move(promise)](Result<HttpQueryPtr> r_query) mutable {
- promise.set_result([&]() -> Result<SimpleConfig> {
+ PromiseCreator::lambda([get_config = std::move(get_config),
+ promise = std::move(promise)](Result<unique_ptr<HttpQuery>> r_query) mutable {
+ promise.set_result([&]() -> Result<SimpleConfigResult> {
TRY_RESULT(http_query, std::move(r_query));
- return decode_config(http_query->content_);
+ SimpleConfigResult res;
+ res.r_http_date = HttpDate::parse_http_date(http_query->get_header("date").str());
+ auto r_config = get_config(*http_query);
+ if (r_config.is_error()) {
+ res.r_config = r_config.move_as_error();
+ } else {
+ res.r_config = decode_config(r_config.ok());
+ }
+ return std::move(res);
}());
}),
- std::move(url), std::vector<std::pair<string, string>>({{"Host", std::move(host)}}), 10 /*timeout*/, 3 /*ttl*/,
- SslFd::VerifyPeer::Off));
+ std::move(url), std::move(headers), timeout, ttl, prefer_ipv6, SslCtx::VerifyPeer::Off, std::move(content),
+ std::move(content_type)));
#endif
}
-ActorOwn<> get_simple_config_azure(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id) {
- string url = PSTRING() << "https://software-download.microsoft.com/" << (is_test ? "test" : "prod") << "/config.txt";
- return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "tcdnb.azureedge.net");
+ActorOwn<> get_simple_config_azure(Promise<SimpleConfigResult> promise, bool prefer_ipv6, Slice domain_name,
+ bool is_test, int32 scheduler_id) {
+ string url = PSTRING() << "https://software-download.microsoft.com/" << (is_test ? "test" : "prod")
+ << "v2/config.txt";
+ return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "tcdnb.azureedge.net", {},
+ prefer_ipv6,
+ [](HttpQuery &http_query) -> Result<string> { return http_query.content_.str(); });
}
-ActorOwn<> get_simple_config_google_app(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id) {
- string url = PSTRING() << "https://www.google.com/" << (is_test ? "test/" : "");
- return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "dns-telegram.appspot.com");
+static ActorOwn<> get_simple_config_dns(Slice address, Slice host, Promise<SimpleConfigResult> promise,
+ bool prefer_ipv6, Slice domain_name, bool is_test, int32 scheduler_id) {
+ if (domain_name.empty()) {
+ domain_name = is_test ? Slice("tapv3.stel.com") : Slice("apv3.stel.com");
+ }
+ auto get_config = [](HttpQuery &http_query) -> Result<string> {
+ auto get_data = [](JsonValue &answer) -> Result<string> {
+ auto &answer_array = answer.get_array();
+ vector<string> parts;
+ for (auto &answer_part : answer_array) {
+ if (answer_part.type() != JsonValue::Type::Object) {
+ return Status::Error("Expected JSON object");
+ }
+ auto &data_object = answer_part.get_object();
+ TRY_RESULT(part, get_json_object_string_field(data_object, "data", false));
+ parts.push_back(std::move(part));
+ }
+ if (parts.size() != 2) {
+ return Status::Error("Expected data in two parts");
+ }
+ string data;
+ if (parts[0].size() < parts[1].size()) {
+ data = parts[1] + parts[0];
+ } else {
+ data = parts[0] + parts[1];
+ }
+ return data;
+ };
+ if (!http_query.get_arg("Answer").empty()) {
+ VLOG(config_recoverer) << "Receive DNS response " << http_query.get_arg("Answer");
+ TRY_RESULT(answer, json_decode(http_query.get_arg("Answer")));
+ if (answer.type() != JsonValue::Type::Array) {
+ return Status::Error("Expected JSON array");
+ }
+ return get_data(answer);
+ } else {
+ VLOG(config_recoverer) << "Receive DNS response " << http_query.content_;
+ TRY_RESULT(json, json_decode(http_query.content_));
+ if (json.type() != JsonValue::Type::Object) {
+ return Status::Error("Expected JSON object");
+ }
+ auto &answer_object = json.get_object();
+ TRY_RESULT(answer, get_json_object_field(answer_object, "Answer", JsonValue::Type::Array, false));
+ return get_data(answer);
+ }
+ };
+ return get_simple_config_impl(
+ std::move(promise), scheduler_id,
+ PSTRING() << "https://" << address << "?name=" << url_encode(domain_name) << "&type=TXT", host.str(),
+ {{"Accept", "application/dns-json"}}, prefer_ipv6, std::move(get_config));
}
-ActorOwn<> get_simple_config_google_dns(Promise<SimpleConfig> promise, bool is_test, int32 scheduler_id) {
- VLOG(config_recoverer) << "Request simple config from Google DNS";
-#if TD_EMSCRIPTEN // FIXME
- return ActorOwn<>();
-#else
- return ActorOwn<>(create_actor_on_scheduler<Wget>(
- "Wget", scheduler_id,
- PromiseCreator::lambda([promise = std::move(promise)](Result<HttpQueryPtr> r_query) mutable {
- promise.set_result([&]() -> Result<SimpleConfig> {
- TRY_RESULT(http_query, std::move(r_query));
- TRY_RESULT(json, json_decode(http_query->content_));
- if (json.type() != JsonValue::Type::Object) {
- return Status::Error("json error");
- }
- auto &answer_object = json.get_object();
- TRY_RESULT(answer, get_json_object_field(answer_object, "Answer", JsonValue::Type::Array));
- auto &answer_array = answer.get_array();
- vector<string> parts;
- for (auto &v : answer_array) {
- if (v.type() != JsonValue::Type::Object) {
- return Status::Error("json error");
- }
- auto &data_object = v.get_object();
- TRY_RESULT(part, get_json_object_string_field(data_object, "data"));
- parts.push_back(std::move(part));
- }
- if (parts.size() != 2) {
- return Status::Error("Expected data in two parts");
- }
- string data;
- if (parts[0].size() < parts[1].size()) {
- data = parts[1] + parts[0];
- } else {
- data = parts[0] + parts[1];
- }
- return decode_config(data);
- }());
- }),
- PSTRING() << "https://google.com/resolve?name=" << (is_test ? "t" : "") << "ap.stel.com&type=16",
- std::vector<std::pair<string, string>>({{"Host", "dns.google.com"}}), 10 /*timeout*/, 3 /*ttl*/,
- SslFd::VerifyPeer::Off));
-#endif
+ActorOwn<> get_simple_config_google_dns(Promise<SimpleConfigResult> promise, bool prefer_ipv6, Slice domain_name,
+ bool is_test, int32 scheduler_id) {
+ return get_simple_config_dns("dns.google/resolve", "dns.google", std::move(promise), prefer_ipv6, domain_name,
+ is_test, scheduler_id);
+}
+
+ActorOwn<> get_simple_config_mozilla_dns(Promise<SimpleConfigResult> promise, bool prefer_ipv6, Slice domain_name,
+ bool is_test, int32 scheduler_id) {
+ return get_simple_config_dns("mozilla.cloudflare-dns.com/dns-query", "mozilla.cloudflare-dns.com", std::move(promise),
+ prefer_ipv6, domain_name, is_test, scheduler_id);
+}
+
+static string generate_firebase_remote_config_payload() {
+ unsigned char buf[17];
+ Random::secure_bytes(buf, sizeof(buf));
+ buf[0] = static_cast<unsigned char>((buf[0] & 0xF0) | 0x07);
+ auto app_instance_id = base64url_encode(Slice(buf, sizeof(buf)));
+ app_instance_id.resize(22);
+ return PSTRING() << "{\"app_id\":\"1:560508485281:web:4ee13a6af4e84d49e67ae0\",\"app_instance_id\":\""
+ << app_instance_id << "\"}";
+}
+
+ActorOwn<> get_simple_config_firebase_remote_config(Promise<SimpleConfigResult> promise, bool prefer_ipv6,
+ Slice domain_name, bool is_test, int32 scheduler_id) {
+ if (is_test) {
+ promise.set_error(Status::Error(400, "Test config is not supported"));
+ return ActorOwn<>();
+ }
+
+ static const string payload = generate_firebase_remote_config_payload();
+ string url =
+ "https://firebaseremoteconfig.googleapis.com/v1/projects/peak-vista-421/namespaces/"
+ "firebase:fetch?key=AIzaSyC2-kAkpDsroixRXw-sTw-Wfqo4NxjMwwM";
+ auto get_config = [](HttpQuery &http_query) -> Result<string> {
+ TRY_RESULT(json, json_decode(http_query.get_arg("entries")));
+ if (json.type() != JsonValue::Type::Object) {
+ return Status::Error("Expected JSON object");
+ }
+ auto &entries_object = json.get_object();
+ TRY_RESULT(config, get_json_object_string_field(entries_object, "ipconfigv3", false));
+ return std::move(config);
+ };
+ return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "firebaseremoteconfig.googleapis.com",
+ {}, prefer_ipv6, std::move(get_config), payload, "application/json");
}
-ActorOwn<> get_full_config(IPAddress ip_address, Promise<FullConfig> promise) {
- class SessionCallback : public Session::Callback {
+ActorOwn<> get_simple_config_firebase_realtime(Promise<SimpleConfigResult> promise, bool prefer_ipv6, Slice domain_name,
+ bool is_test, int32 scheduler_id) {
+ if (is_test) {
+ promise.set_error(Status::Error(400, "Test config is not supported"));
+ return ActorOwn<>();
+ }
+
+ string url = "https://reserve-5a846.firebaseio.com/ipconfigv3.json";
+ auto get_config = [](HttpQuery &http_query) -> Result<string> {
+ return http_query.get_arg("content").str();
+ };
+ return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "reserve-5a846.firebaseio.com", {},
+ prefer_ipv6, std::move(get_config));
+}
+
+ActorOwn<> get_simple_config_firebase_firestore(Promise<SimpleConfigResult> promise, bool prefer_ipv6,
+ Slice domain_name, bool is_test, int32 scheduler_id) {
+ if (is_test) {
+ promise.set_error(Status::Error(400, "Test config is not supported"));
+ return ActorOwn<>();
+ }
+
+ string url = "https://www.google.com/v1/projects/reserve-5a846/databases/(default)/documents/ipconfig/v3";
+ auto get_config = [](HttpQuery &http_query) -> Result<string> {
+ TRY_RESULT(json, json_decode(http_query.get_arg("fields")));
+ if (json.type() != JsonValue::Type::Object) {
+ return Status::Error("Expected JSON object");
+ }
+ TRY_RESULT(data, get_json_object_field(json.get_object(), "data", JsonValue::Type::Object, false));
+ TRY_RESULT(config, get_json_object_string_field(data.get_object(), "stringValue", false));
+ return std::move(config);
+ };
+ return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "firestore.googleapis.com", {},
+ prefer_ipv6, std::move(get_config));
+}
+
+static ActorOwn<> get_full_config(DcOption option, Promise<tl_object_ptr<telegram_api::config>> promise,
+ ActorShared<> parent) {
+ class SessionCallback final : public Session::Callback {
public:
- SessionCallback(ActorShared<> parent, IPAddress address)
- : parent_(std::move(parent)), address_(std::move(address)) {
+ SessionCallback(ActorShared<> parent, DcOption option) : parent_(std::move(parent)), option_(std::move(option)) {
}
void on_failed() final {
}
void on_closed() final {
}
- void request_raw_connection(Promise<std::unique_ptr<mtproto::RawConnection>> promise) final {
+ void request_raw_connection(unique_ptr<mtproto::AuthData> auth_data,
+ Promise<unique_ptr<mtproto::RawConnection>> promise) final {
request_raw_connection_cnt_++;
- VLOG(config_recoverer) << "Request full config from " << address_ << ", try = " << request_raw_connection_cnt_;
- if (request_raw_connection_cnt_ <= 1) {
- send_closure(G()->connection_creator(), &ConnectionCreator::request_raw_connection_by_ip, address_,
+ VLOG(config_recoverer) << "Request full config from " << option_.get_ip_address()
+ << ", try = " << request_raw_connection_cnt_;
+ if (request_raw_connection_cnt_ <= 2) {
+ send_closure(G()->connection_creator(), &ConnectionCreator::request_raw_connection_by_ip,
+ option_.get_ip_address(),
+ mtproto::TransportType{mtproto::TransportType::ObfuscatedTcp,
+ narrow_cast<int16>(option_.get_dc_id().get_raw_id()), option_.get_secret()},
std::move(promise));
} else {
// Delay all queries except first forever
@@ -201,125 +416,157 @@ ActorOwn<> get_full_config(IPAddress ip_address, Promise<FullConfig> promise) {
void on_tmp_auth_key_updated(mtproto::AuthKey auth_key) final {
// nop
}
+ void on_server_salt_updated(std::vector<mtproto::ServerSalt> server_salts) final {
+ // nop
+ }
+ void on_update(BufferSlice &&update) final {
+ // nop
+ }
+ void on_result(NetQueryPtr net_query) final {
+ G()->net_query_dispatcher().dispatch(std::move(net_query));
+ }
private:
ActorShared<> parent_;
- IPAddress address_;
+ DcOption option_;
size_t request_raw_connection_cnt_{0};
- std::vector<Promise<std::unique_ptr<mtproto::RawConnection>>> delay_forever_;
+ std::vector<Promise<unique_ptr<mtproto::RawConnection>>> delay_forever_;
};
- class SimpleAuthData : public AuthDataShared {
+ class SimpleAuthData final : public AuthDataShared {
public:
- DcId dc_id() const override {
- return DcId::empty();
+ explicit SimpleAuthData(DcId dc_id) : dc_id_(dc_id) {
+ }
+ DcId dc_id() const final {
+ return dc_id_;
}
- const std::shared_ptr<PublicRsaKeyShared> &public_rsa_key() override {
+ const std::shared_ptr<PublicRsaKeyShared> &public_rsa_key() final {
return public_rsa_key_;
}
- mtproto::AuthKey get_auth_key() override {
- return auth_key_;
+ mtproto::AuthKey get_auth_key() final {
+ string dc_key = G()->td_db()->get_binlog_pmc()->get(auth_key_key());
+
+ mtproto::AuthKey res;
+ if (!dc_key.empty()) {
+ unserialize(res, dc_key).ensure();
+ }
+ return res;
}
- std::pair<AuthState, bool> get_auth_state() override {
- auto auth_key = get_auth_key();
- AuthState state = AuthDataShared::get_auth_state(auth_key);
- return std::make_pair(state, auth_key.was_auth_flag());
+ AuthKeyState get_auth_key_state() final {
+ return AuthDataShared::get_auth_key_state(get_auth_key());
}
- void set_auth_key(const mtproto::AuthKey &auth_key) override {
- auth_key_ = auth_key;
+ void set_auth_key(const mtproto::AuthKey &auth_key) final {
+ G()->td_db()->get_binlog_pmc()->set(auth_key_key(), serialize(auth_key));
+
+ //notify();
}
- void update_server_time_difference(double diff) override {
- if (!has_server_time_difference_ || server_time_difference_ < diff) {
- has_server_time_difference_ = true;
- server_time_difference_ = diff;
- }
+ void update_server_time_difference(double diff) final {
+ G()->update_server_time_difference(diff);
}
- double get_server_time_difference() override {
- return server_time_difference_;
+ double get_server_time_difference() final {
+ return G()->get_server_time_difference();
}
- void add_auth_key_listener(unique_ptr<Listener> listener) override {
+ void add_auth_key_listener(unique_ptr<Listener> listener) final {
if (listener->notify()) {
auth_key_listeners_.push_back(std::move(listener));
}
}
- void set_future_salts(const std::vector<mtproto::ServerSalt> &future_salts) override {
- future_salts_ = future_salts;
+ void set_future_salts(const std::vector<mtproto::ServerSalt> &future_salts) final {
+ G()->td_db()->get_binlog_pmc()->set(future_salts_key(), serialize(future_salts));
}
- std::vector<mtproto::ServerSalt> get_future_salts() override {
- return future_salts_;
+
+ std::vector<mtproto::ServerSalt> get_future_salts() final {
+ string future_salts = G()->td_db()->get_binlog_pmc()->get(future_salts_key());
+ std::vector<mtproto::ServerSalt> res;
+ if (!future_salts.empty()) {
+ unserialize(res, future_salts).ensure();
+ }
+ return res;
}
private:
- std::shared_ptr<PublicRsaKeyShared> public_rsa_key_ = std::make_shared<PublicRsaKeyShared>(DcId::empty());
- mtproto::AuthKey auth_key_;
- bool has_server_time_difference_ = false;
- double server_time_difference_ = 0;
+ DcId dc_id_;
+ std::shared_ptr<PublicRsaKeyShared> public_rsa_key_ =
+ std::make_shared<PublicRsaKeyShared>(DcId::empty(), G()->is_test_dc());
- std::vector<mtproto::ServerSalt> future_salts_;
-
- std::vector<std::unique_ptr<Listener>> auth_key_listeners_;
+ std::vector<unique_ptr<Listener>> auth_key_listeners_;
void notify() {
- auto it = std::remove_if(auth_key_listeners_.begin(), auth_key_listeners_.end(),
- [&](auto &listener) { return !listener->notify(); });
- auth_key_listeners_.erase(it, auth_key_listeners_.end());
+ td::remove_if(auth_key_listeners_, [&](auto &listener) { return !listener->notify(); });
+ }
+
+ string auth_key_key() const {
+ return PSTRING() << "config_recovery_auth" << dc_id().get_raw_id();
+ }
+ string future_salts_key() const {
+ return PSTRING() << "config_recovery_salt" << dc_id().get_raw_id();
}
};
- class GetConfigActor : public NetQueryCallback {
+ class GetConfigActor final : public NetQueryCallback {
public:
- GetConfigActor(IPAddress ip_address, Promise<FullConfig> promise)
- : ip_address_(std::move(ip_address)), promise_(std::move(promise)) {
+ GetConfigActor(DcOption option, Promise<tl_object_ptr<telegram_api::config>> promise, ActorShared<> parent)
+ : option_(std::move(option)), promise_(std::move(promise)), parent_(std::move(parent)) {
}
private:
- void start_up() override {
- auto session_callback = std::make_unique<SessionCallback>(actor_shared(this, 1), std::move(ip_address_));
-
- auto auth_data = std::make_shared<SimpleAuthData>();
- session_ = create_actor<Session>("ConfigSession", std::move(session_callback), std::move(auth_data),
- false /*is_main*/, false /*use_pfs*/, true /*is_cdn*/, mtproto::AuthKey());
- auto query = G()->net_query_creator().create(create_storer(telegram_api::help_getConfig()), DcId::empty(),
- NetQuery::Type::Common, NetQuery::AuthFlag::Off,
- NetQuery::GzipFlag::On, 60 * 60 * 24);
+ void start_up() final {
+ auto auth_data = std::make_shared<SimpleAuthData>(option_.get_dc_id());
+ int32 raw_dc_id = option_.get_dc_id().get_raw_id();
+ auto session_callback = make_unique<SessionCallback>(actor_shared(this, 1), std::move(option_));
+
+ int32 int_dc_id = raw_dc_id;
+ if (G()->is_test_dc()) {
+ int_dc_id += 10000;
+ }
+ session_ = create_actor<Session>("ConfigSession", std::move(session_callback), std::move(auth_data), raw_dc_id,
+ int_dc_id, false /*is_main*/, true /*use_pfs*/, false /*is_cdn*/,
+ false /*need_destroy_auth_key*/, mtproto::AuthKey(),
+ std::vector<mtproto::ServerSalt>());
+ auto query = G()->net_query_creator().create_unauth(telegram_api::help_getConfig(), DcId::empty());
+ query->total_timeout_limit_ = 60 * 60 * 24;
query->set_callback(actor_shared(this));
- query->dispatch_ttl = 0;
+ query->dispatch_ttl_ = 0;
send_closure(session_, &Session::send, std::move(query));
set_timeout_in(10);
}
- void on_result(NetQueryPtr query) override {
+ void on_result(NetQueryPtr query) final {
promise_.set_result(fetch_result<telegram_api::help_getConfig>(std::move(query)));
- stop();
}
- void hangup_shared() override {
+ void hangup_shared() final {
if (get_link_token() == 1) {
- promise_.set_error(Status::Error("Failed"));
+ if (promise_) {
+ promise_.set_error(Status::Error("Failed"));
+ }
stop();
}
}
- void hangup() override {
+ void hangup() final {
session_.reset();
}
- void timeout_expired() override {
+ void timeout_expired() final {
promise_.set_error(Status::Error("Timeout expired"));
- stop();
+ session_.reset();
}
- IPAddress ip_address_;
+ DcOption option_;
ActorOwn<Session> session_;
- Promise<FullConfig> promise_;
+ Promise<tl_object_ptr<telegram_api::config>> promise_;
+ ActorShared<> parent_;
};
- return ActorOwn<>(create_actor<GetConfigActor>("GetConfigActor", std::move(ip_address), std::move(promise)));
+ return ActorOwn<>(
+ create_actor<GetConfigActor>("GetConfigActor", std::move(option), std::move(promise), std::move(parent)));
}
-class ConfigRecoverer : public Actor {
+class ConfigRecoverer final : public Actor {
public:
explicit ConfigRecoverer(ActorShared<> parent) : parent_(std::move(parent)) {
+ connecting_since_ = Time::now();
}
void on_dc_options_update(DcOptions dc_options) {
- dc_options_update_ = dc_options;
+ dc_options_update_ = std::move(dc_options);
update_dc_options();
loop();
}
@@ -335,11 +582,23 @@ class ConfigRecoverer : public Actor {
loop();
}
void on_online(bool is_online) {
+ if (is_online_ == is_online) {
+ return;
+ }
+
is_online_ = is_online;
+ if (is_online) {
+ if (simple_config_.dc_options.empty()) {
+ simple_config_expires_at_ = 0;
+ }
+ if (full_config_ == nullptr) {
+ full_config_expires_at_ = 0;
+ }
+ }
loop();
}
void on_connecting(bool is_connecting) {
- VLOG(config_recoverer) << "ON CONNECTING " << is_connecting;
+ VLOG(config_recoverer) << "On connecting " << is_connecting;
if (is_connecting && !is_connecting_) {
connecting_since_ = Time::now_cached();
}
@@ -347,47 +606,117 @@ class ConfigRecoverer : public Actor {
loop();
}
- void on_simple_config(Result<SimpleConfig> r_simple_config, bool dummy) {
- simple_config_query_.reset();
- auto r_dc_options = [&]() -> Result<DcOptions> {
- if (r_simple_config.is_error()) {
- return r_simple_config.move_as_error();
+ static bool check_phone_number_rules(Slice phone_number, Slice rules) {
+ if (rules.empty() || phone_number.empty()) {
+ return true;
+ }
+
+ bool found = false;
+ for (auto prefix : full_split(rules, ',')) {
+ if (prefix.empty()) {
+ found = true;
+ } else if (prefix[0] == '+' && begins_with(phone_number, prefix.substr(1))) {
+ found = true;
+ } else if (prefix[0] == '-' && begins_with(phone_number, prefix.substr(1))) {
+ return false;
+ } else {
+ LOG(ERROR) << "Invalid prefix rule " << prefix;
}
- return DcOptions(*r_simple_config.ok());
- }();
+ }
+ return found;
+ }
+
+ void on_simple_config(Result<SimpleConfigResult> r_simple_config_result, bool dummy) {
+ simple_config_query_.reset();
dc_options_i_ = 0;
- if (r_dc_options.is_ok()) {
- simple_config_ = r_dc_options.move_as_ok();
- VLOG(config_recoverer) << "Got SimpleConfig " << simple_config_;
- simple_config_expire_at_ = Time::now_cached() + Random::fast(20 * 60, 30 * 60);
+
+ SimpleConfigResult cfg;
+ if (r_simple_config_result.is_error()) {
+ cfg.r_http_date = r_simple_config_result.error().clone();
+ cfg.r_config = r_simple_config_result.move_as_error();
+ } else {
+ cfg = r_simple_config_result.move_as_ok();
+ }
+
+ if (cfg.r_http_date.is_ok() && (date_option_i_ == 0 || cfg.r_config.is_error())) {
+ G()->update_dns_time_difference(cfg.r_http_date.ok() - Time::now());
+ } else if (cfg.r_config.is_ok()) {
+ G()->update_dns_time_difference(cfg.r_config.ok()->date_ - Time::now());
+ }
+ date_option_i_ = (date_option_i_ + 1) % 2;
+
+ do_on_simple_config(std::move(cfg.r_config));
+ update_dc_options();
+ loop();
+ }
+
+ void do_on_simple_config(Result<SimpleConfig> r_simple_config) {
+ if (r_simple_config.is_ok()) {
+ auto config = r_simple_config.move_as_ok();
+ VLOG(config_recoverer) << "Receive raw " << to_string(config);
+ if (config->expires_ >= G()->unix_time()) {
+ string phone_number = G()->get_option_string("my_phone_number");
+ simple_config_.dc_options.clear();
+
+ for (auto &rule : config->rules_) {
+ if (check_phone_number_rules(phone_number, rule->phone_prefix_rules_) && DcId::is_valid(rule->dc_id_)) {
+ DcId dc_id = DcId::internal(rule->dc_id_);
+ for (auto &ip_port : rule->ips_) {
+ DcOption option(dc_id, *ip_port);
+ if (option.is_valid()) {
+ simple_config_.dc_options.push_back(std::move(option));
+ }
+ }
+ }
+ }
+ VLOG(config_recoverer) << "Got SimpleConfig " << simple_config_;
+ } else {
+ VLOG(config_recoverer) << "Config has expired at " << config->expires_;
+ }
+
+ simple_config_expires_at_ = get_config_expire_time();
simple_config_at_ = Time::now_cached();
for (size_t i = 1; i < simple_config_.dc_options.size(); i++) {
std::swap(simple_config_.dc_options[i], simple_config_.dc_options[Random::fast(0, static_cast<int>(i))]);
}
} else {
- VLOG(config_recoverer) << "Get SimpleConfig error " << r_dc_options.error();
+ VLOG(config_recoverer) << "Get SimpleConfig error " << r_simple_config.error();
simple_config_ = DcOptions();
- simple_config_expire_at_ = Time::now_cached() + Random::fast(15, 30);
+ simple_config_expires_at_ = get_failed_config_expire_time();
}
- update_dc_options();
- loop();
}
- void on_full_config(Result<FullConfig> r_full_config, bool dummy) {
+ void on_full_config(Result<tl_object_ptr<telegram_api::config>> r_full_config, bool dummy) {
full_config_query_.reset();
if (r_full_config.is_ok()) {
full_config_ = r_full_config.move_as_ok();
- VLOG(config_recoverer) << "Got FullConfig " << to_string(full_config_);
- full_config_expire_at_ = Time::now() + Random::fast(20 * 60, 30 * 60);
+ VLOG(config_recoverer) << "Receive " << to_string(full_config_);
+ full_config_expires_at_ = get_config_expire_time();
send_closure(G()->connection_creator(), &ConnectionCreator::on_dc_options, DcOptions(full_config_->dc_options_));
} else {
- VLOG(config_recoverer) << "Get FullConfig error " << r_full_config.error();
- full_config_ = FullConfig();
- full_config_expire_at_ = Time::now() + Random::fast(15, 30);
+ VLOG(config_recoverer) << "Failed to get config: " << r_full_config.error();
+ full_config_ = nullptr;
+ full_config_expires_at_ = get_failed_config_expire_time();
}
loop();
}
+ static bool expect_blocking() {
+ return G()->get_option_boolean("expect_blocking", true);
+ }
+
+ double get_config_expire_time() const {
+ auto offline_delay = is_online_ ? 0 : 5 * 60;
+ auto expire_time = expect_blocking() ? Random::fast(2 * 60, 3 * 60) : Random::fast(20 * 60, 30 * 60);
+ return Time::now() + offline_delay + expire_time;
+ }
+
+ double get_failed_config_expire_time() const {
+ auto offline_delay = is_online_ ? 0 : 5 * 60;
+ auto expire_time = expect_blocking() ? Random::fast(5, 7) : Random::fast(15, 30);
+ return Time::now() + offline_delay + expire_time;
+ }
+
bool is_connecting_{false};
double connecting_since_{0};
@@ -398,7 +727,7 @@ class ConfigRecoverer : public Actor {
uint32 network_generation_{0};
DcOptions simple_config_;
- double simple_config_expire_at_{-1};
+ double simple_config_expires_at_{0};
double simple_config_at_{0};
ActorOwn<> simple_config_query_;
@@ -406,23 +735,25 @@ class ConfigRecoverer : public Actor {
DcOptions dc_options_; // dc_options_update_ + simple_config_
double dc_options_at_{0};
- size_t dc_options_i_;
+ size_t dc_options_i_{0};
+
+ size_t date_option_i_{0};
- FullConfig full_config_;
- double full_config_expire_at_{0};
+ tl_object_ptr<telegram_api::config> full_config_;
+ double full_config_expires_at_{0};
ActorOwn<> full_config_query_;
uint32 ref_cnt_{1};
bool close_flag_{false};
- uint8 simple_config_turn_{0};
+ uint32 simple_config_turn_{0};
ActorShared<> parent_;
- void hangup_shared() override {
+ void hangup_shared() final {
ref_cnt_--;
try_stop();
}
- void hangup() override {
+ void hangup() final {
ref_cnt_--;
close_flag_ = true;
full_config_query_.reset();
@@ -437,17 +768,23 @@ class ConfigRecoverer : public Actor {
}
double max_connecting_delay() const {
- return 20;
+ return expect_blocking() ? 5 : 20;
}
- void loop() override {
+
+ void loop() final {
if (close_flag_) {
return;
}
+ if (Session::is_high_loaded()) {
+ VLOG(config_recoverer) << "Skip config recoverer under high load";
+ set_timeout_in(Random::fast(200, 300));
+ return;
+ }
if (is_connecting_) {
- VLOG(config_recoverer) << "Failed to connect for " << Time::now_cached() - connecting_since_;
+ VLOG(config_recoverer) << "Failed to connect for " << Time::now() - connecting_since_;
} else {
- VLOG(config_recoverer) << "Successfully connected";
+ VLOG(config_recoverer) << "Successfully connected in " << Time::now() - connecting_since_;
}
Timestamp wakeup_timestamp;
@@ -461,45 +798,60 @@ class ConfigRecoverer : public Actor {
bool has_connecting_problem =
is_connecting_ && check_timeout(Timestamp::at(connecting_since_ + max_connecting_delay()));
- bool is_valid_simple_config = !check_timeout(Timestamp::at(simple_config_expire_at_));
+ bool is_valid_simple_config = !check_timeout(Timestamp::at(simple_config_expires_at_));
if (!is_valid_simple_config && !simple_config_.dc_options.empty()) {
simple_config_ = DcOptions();
update_dc_options();
}
bool need_simple_config = has_connecting_problem && !is_valid_simple_config && simple_config_query_.empty();
bool has_dc_options = !dc_options_.dc_options.empty();
- bool is_valid_full_config = !check_timeout(Timestamp::at(full_config_expire_at_));
+ bool is_valid_full_config = !check_timeout(Timestamp::at(full_config_expires_at_));
bool need_full_config = has_connecting_problem && has_dc_options && !is_valid_full_config &&
- full_config_query_.empty() && check_timeout(Timestamp::at(dc_options_at_ + 10));
+ full_config_query_.empty() &&
+ check_timeout(Timestamp::at(dc_options_at_ + (expect_blocking() ? 5 : 10)));
if (need_simple_config) {
ref_cnt_++;
- VLOG(config_recoverer) << "ASK SIMPLE CONFIG";
- auto promise = PromiseCreator::lambda([actor_id = actor_shared(this)](Result<SimpleConfig> r_simple_config) {
- send_closure(actor_id, &ConfigRecoverer::on_simple_config, std::move(r_simple_config), false);
+ VLOG(config_recoverer) << "Ask simple config with turn " << simple_config_turn_;
+ auto promise = PromiseCreator::lambda([self = actor_shared(this)](Result<SimpleConfigResult> r_simple_config) {
+ send_closure(self, &ConfigRecoverer::on_simple_config, std::move(r_simple_config), false);
});
- auto get_dimple_config = [&]() {
- switch (simple_config_turn_ % 3) {
- case 0:
+ auto get_simple_config = [&] {
+ switch (simple_config_turn_ % 10) {
+ case 6:
return get_simple_config_azure;
- case 1:
- return get_simple_config_google_app;
case 2:
- default:
+ return get_simple_config_firebase_remote_config;
+ case 4:
+ return get_simple_config_firebase_realtime;
+ case 9:
+ return get_simple_config_firebase_firestore;
+ case 0:
+ case 3:
+ case 8:
return get_simple_config_google_dns;
+ case 1:
+ case 5:
+ case 7:
+ default:
+ return get_simple_config_mozilla_dns;
}
}();
- simple_config_query_ = get_dimple_config(std::move(promise), G()->is_test_dc(), G()->get_gc_scheduler_id());
+ simple_config_query_ = get_simple_config(std::move(promise), G()->get_option_boolean("prefer_ipv6"),
+ G()->get_option_string("dc_txt_domain_name"), G()->is_test_dc(),
+ G()->get_gc_scheduler_id());
simple_config_turn_++;
}
if (need_full_config) {
ref_cnt_++;
- VLOG(config_recoverer) << "ASK FULL CONFIG";
- full_config_query_ =
- get_full_config(dc_options_.dc_options[dc_options_i_].get_ip_address(),
- PromiseCreator::lambda([actor_id = actor_shared(this)](Result<FullConfig> r_full_config) {
- send_closure(actor_id, &ConfigRecoverer::on_full_config, std::move(r_full_config), false);
- }));
+ VLOG(config_recoverer) << "Ask full config with dc_options_i_ = " << dc_options_i_;
+ full_config_query_ = get_full_config(
+ dc_options_.dc_options[dc_options_i_],
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<tl_object_ptr<telegram_api::config>> r_full_config) {
+ send_closure(actor_id, &ConfigRecoverer::on_full_config, std::move(r_full_config), false);
+ }),
+ actor_shared(this));
dc_options_i_ = (dc_options_i_ + 1) % dc_options_.dc_options.size();
}
@@ -507,24 +859,24 @@ class ConfigRecoverer : public Actor {
VLOG(config_recoverer) << "Wakeup in " << format::as_time(wakeup_timestamp.in());
set_timeout_at(wakeup_timestamp.at());
} else {
- VLOG(config_recoverer) << "Wakeup NEVER";
+ VLOG(config_recoverer) << "Wakeup never";
}
}
- void start_up() override {
- class StateCallback : public StateManager::Callback {
+ void start_up() final {
+ class StateCallback final : public StateManager::Callback {
public:
explicit StateCallback(ActorId<ConfigRecoverer> parent) : parent_(std::move(parent)) {
}
- bool on_state(StateManager::State state) override {
- send_closure(parent_, &ConfigRecoverer::on_connecting, state == StateManager::State::Connecting);
+ bool on_state(ConnectionState state) final {
+ send_closure(parent_, &ConfigRecoverer::on_connecting, state == ConnectionState::Connecting);
return parent_.is_alive();
}
- bool on_network(NetType network_type, uint32 network_generation) override {
+ bool on_network(NetType network_type, uint32 network_generation) final {
send_closure(parent_, &ConfigRecoverer::on_network, network_type != NetType::None, network_generation);
return parent_.is_alive();
}
- bool on_online(bool online_flag) override {
+ bool on_online(bool online_flag) final {
send_closure(parent_, &ConfigRecoverer::on_online, online_flag);
return parent_.is_alive();
}
@@ -536,10 +888,11 @@ class ConfigRecoverer : public Actor {
}
void update_dc_options() {
- auto v = simple_config_.dc_options;
- v.insert(v.begin(), dc_options_update_.dc_options.begin(), dc_options_update_.dc_options.end());
- if (v != dc_options_.dc_options) {
- dc_options_.dc_options = std::move(v);
+ auto new_dc_options = simple_config_.dc_options;
+ new_dc_options.insert(new_dc_options.begin(), dc_options_update_.dc_options.begin(),
+ dc_options_update_.dc_options.end());
+ if (new_dc_options != dc_options_.dc_options) {
+ dc_options_.dc_options = std::move(new_dc_options);
dc_options_i_ = 0;
dc_options_at_ = Time::now();
}
@@ -547,85 +900,384 @@ class ConfigRecoverer : public Actor {
};
ConfigManager::ConfigManager(ActorShared<> parent) : parent_(std::move(parent)) {
+ lazy_request_flood_control_.add_limit(20, 1);
}
void ConfigManager::start_up() {
- // TODO there are some problems when many ConfigRecoverers starts at the same time
- // if (G()->parameters().use_file_db) {
- ref_cnt_++;
- config_recoverer_ = create_actor<ConfigRecoverer>("Recoverer", actor_shared());
+ config_recoverer_ = create_actor<ConfigRecoverer>("Recoverer", create_reference());
send_closure(config_recoverer_, &ConfigRecoverer::on_dc_options_update, load_dc_options_update());
- // }
- auto expire = load_config_expire();
- if (expire.is_in_past()) {
- request_config();
+
+ auto expire_time = load_config_expire_time();
+ if (expire_time.is_in_past() || true) {
+ request_config(false);
} else {
- expire_ = expire;
- set_timeout_in(expire_.in());
+ expire_time_ = expire_time;
+ set_timeout_in(expire_time_.in());
}
}
+ActorShared<> ConfigManager::create_reference() {
+ ref_cnt_++;
+ return actor_shared(this, REFCNT_TOKEN);
+}
+
void ConfigManager::hangup_shared() {
+ LOG_CHECK(get_link_token() == REFCNT_TOKEN) << "Expected REFCNT_TOKEN, got " << get_link_token();
ref_cnt_--;
try_stop();
}
+
void ConfigManager::hangup() {
ref_cnt_--;
config_recoverer_.reset();
try_stop();
}
+
void ConfigManager::loop() {
- if (expire_ && expire_.is_in_past()) {
- request_config();
- expire_ = {};
+ if (expire_time_ && expire_time_.is_in_past()) {
+ request_config(reopen_sessions_after_get_config_);
+ expire_time_ = {};
}
}
+
void ConfigManager::try_stop() {
if (ref_cnt_ == 0) {
stop();
}
}
-void ConfigManager::request_config() {
+
+void ConfigManager::request_config(bool reopen_sessions) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ if (config_sent_cnt_ != 0 && !reopen_sessions) {
+ return;
+ }
+
+ lazy_request_flood_control_.add_event(Time::now());
+ request_config_from_dc_impl(DcId::main(), reopen_sessions);
+}
+
+void ConfigManager::lazy_request_config() {
+ if (G()->close_flag()) {
+ return;
+ }
+
if (config_sent_cnt_ != 0) {
return;
}
- request_config_from_dc_impl(DcId::main());
+
+ expire_time_.relax(Timestamp::at(lazy_request_flood_control_.get_wakeup_at()));
+ set_timeout_at(expire_time_.at());
+}
+
+void ConfigManager::try_request_app_config() {
+ if (get_app_config_queries_.size() + reget_app_config_queries_.size() != 1) {
+ return;
+ }
+
+ auto query = G()->net_query_creator().create_unauth(telegram_api::help_getAppConfig());
+ query->total_timeout_limit_ = 60 * 60 * 24;
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, 1));
+}
+
+void ConfigManager::get_app_config(Promise<td_api::object_ptr<td_api::JsonValue>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto auth_manager = G()->td().get_actor_unsafe()->auth_manager_.get();
+ if (auth_manager != nullptr && auth_manager->is_bot()) {
+ return promise.set_value(nullptr);
+ }
+
+ get_app_config_queries_.push_back(std::move(promise));
+ try_request_app_config();
+}
+
+void ConfigManager::reget_app_config(Promise<Unit> &&promise) {
+ if (G()->close_flag()) {
+ return promise.set_error(Status::Error(500, "Request aborted"));
+ }
+
+ auto auth_manager = G()->td().get_actor_unsafe()->auth_manager_.get();
+ if (auth_manager != nullptr && auth_manager->is_bot()) {
+ return promise.set_value(Unit());
+ }
+
+ reget_app_config_queries_.push_back(std::move(promise));
+ try_request_app_config();
+}
+
+void ConfigManager::get_content_settings(Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto auth_manager = G()->td().get_actor_unsafe()->auth_manager_.get();
+ if (auth_manager == nullptr || !auth_manager->is_authorized() || auth_manager->is_bot()) {
+ return promise.set_value(Unit());
+ }
+
+ get_content_settings_queries_.push_back(std::move(promise));
+ if (get_content_settings_queries_.size() == 1) {
+ G()->net_query_dispatcher().dispatch_with_callback(
+ G()->net_query_creator().create(telegram_api::account_getContentSettings()), actor_shared(this, 2));
+ }
+}
+
+void ConfigManager::set_content_settings(bool ignore_sensitive_content_restrictions, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ last_set_content_settings_ = ignore_sensitive_content_restrictions;
+ auto &queries = set_content_settings_queries_[ignore_sensitive_content_restrictions];
+ queries.push_back(std::move(promise));
+ if (!is_set_content_settings_request_sent_) {
+ is_set_content_settings_request_sent_ = true;
+ int32 flags = 0;
+ if (ignore_sensitive_content_restrictions) {
+ flags |= telegram_api::account_setContentSettings::SENSITIVE_ENABLED_MASK;
+ }
+ G()->net_query_dispatcher().dispatch_with_callback(
+ G()->net_query_creator().create(telegram_api::account_setContentSettings(flags, false /*ignored*/)),
+ actor_shared(this, 3 + static_cast<uint64>(ignore_sensitive_content_restrictions)));
+ }
+}
+
+void ConfigManager::get_global_privacy_settings(Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto auth_manager = G()->td().get_actor_unsafe()->auth_manager_.get();
+ if (auth_manager == nullptr || !auth_manager->is_authorized() || auth_manager->is_bot()) {
+ return promise.set_value(Unit());
+ }
+
+ get_global_privacy_settings_queries_.push_back(std::move(promise));
+ if (get_global_privacy_settings_queries_.size() == 1) {
+ G()->net_query_dispatcher().dispatch_with_callback(
+ G()->net_query_creator().create(telegram_api::account_getGlobalPrivacySettings()), actor_shared(this, 5));
+ }
+}
+
+void ConfigManager::set_archive_and_mute(bool archive_and_mute, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (archive_and_mute) {
+ remove_suggested_action(suggested_actions_, SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats});
+ }
+
+ last_set_archive_and_mute_ = archive_and_mute;
+ auto &queries = set_archive_and_mute_queries_[archive_and_mute];
+ queries.push_back(std::move(promise));
+ if (!is_set_archive_and_mute_request_sent_) {
+ is_set_archive_and_mute_request_sent_ = true;
+ int32 flags = telegram_api::globalPrivacySettings::ARCHIVE_AND_MUTE_NEW_NONCONTACT_PEERS_MASK;
+ auto settings = make_tl_object<telegram_api::globalPrivacySettings>(flags, archive_and_mute);
+ G()->net_query_dispatcher().dispatch_with_callback(
+ G()->net_query_creator().create(telegram_api::account_setGlobalPrivacySettings(std::move(settings))),
+ actor_shared(this, 6 + static_cast<uint64>(archive_and_mute)));
+ }
}
void ConfigManager::on_dc_options_update(DcOptions dc_options) {
save_dc_options_update(dc_options);
- send_closure(config_recoverer_, &ConfigRecoverer::on_dc_options_update, std::move(dc_options));
- if (dc_options.dc_options.empty()) {
- return;
+ if (!dc_options.dc_options.empty()) {
+ expire_time_ = Timestamp::now();
+ save_config_expire(expire_time_);
+ set_timeout_in(expire_time_.in());
}
- expire_ = Timestamp::now();
- save_config_expire(expire_);
- set_timeout_in(expire_.in());
+ send_closure(config_recoverer_, &ConfigRecoverer::on_dc_options_update, std::move(dc_options));
}
-void ConfigManager::request_config_from_dc_impl(DcId dc_id) {
+void ConfigManager::request_config_from_dc_impl(DcId dc_id, bool reopen_sessions) {
config_sent_cnt_++;
- G()->net_query_dispatcher().dispatch_with_callback(
- G()->net_query_creator().create(create_storer(telegram_api::help_getConfig()), dc_id, NetQuery::Type::Common,
- NetQuery::AuthFlag::Off, NetQuery::GzipFlag::On, 60 * 60 * 24),
- actor_shared(this));
+ reopen_sessions_after_get_config_ |= reopen_sessions;
+ auto query = G()->net_query_creator().create_unauth(telegram_api::help_getConfig(), dc_id);
+ query->total_timeout_limit_ = 60 * 60 * 24;
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query),
+ actor_shared(this, 8 + static_cast<uint64>(reopen_sessions)));
+}
+
+void ConfigManager::do_set_ignore_sensitive_content_restrictions(bool ignore_sensitive_content_restrictions) {
+ G()->set_option_boolean("ignore_sensitive_content_restrictions", ignore_sensitive_content_restrictions);
+ bool have_ignored_restriction_reasons = G()->have_option("ignored_restriction_reasons");
+ if (have_ignored_restriction_reasons != ignore_sensitive_content_restrictions) {
+ reget_app_config(Auto());
+ }
+}
+
+void ConfigManager::do_set_archive_and_mute(bool archive_and_mute) {
+ if (archive_and_mute) {
+ remove_suggested_action(suggested_actions_, SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats});
+ }
+ G()->set_option_boolean("archive_and_mute_new_chats_from_unknown_users", archive_and_mute);
+}
+
+void ConfigManager::hide_suggested_action(SuggestedAction suggested_action) {
+ remove_suggested_action(suggested_actions_, suggested_action);
+}
+
+void ConfigManager::dismiss_suggested_action(SuggestedAction suggested_action, Promise<Unit> &&promise) {
+ auto action_str = suggested_action.get_suggested_action_str();
+ if (action_str.empty()) {
+ return promise.set_value(Unit());
+ }
+
+ if (!td::contains(suggested_actions_, suggested_action)) {
+ return promise.set_value(Unit());
+ }
+
+ dismiss_suggested_action_request_count_++;
+ auto type = static_cast<int32>(suggested_action.type_);
+ auto &queries = dismiss_suggested_action_queries_[type];
+ queries.push_back(std::move(promise));
+ if (queries.size() == 1) {
+ G()->net_query_dispatcher().dispatch_with_callback(
+ G()->net_query_creator().create(
+ telegram_api::help_dismissSuggestion(make_tl_object<telegram_api::inputPeerEmpty>(), action_str)),
+ actor_shared(this, 100 + type));
+ }
}
void ConfigManager::on_result(NetQueryPtr res) {
+ auto token = get_link_token();
+ if (token >= 100 && token <= 200) {
+ auto type = static_cast<int32>(token - 100);
+ SuggestedAction suggested_action{static_cast<SuggestedAction::Type>(type)};
+ auto promises = std::move(dismiss_suggested_action_queries_[type]);
+ dismiss_suggested_action_queries_.erase(type);
+ CHECK(!promises.empty());
+ CHECK(dismiss_suggested_action_request_count_ >= promises.size());
+ dismiss_suggested_action_request_count_ -= promises.size();
+
+ auto result_ptr = fetch_result<telegram_api::help_dismissSuggestion>(std::move(res));
+ if (result_ptr.is_error()) {
+ fail_promises(promises, result_ptr.move_as_error());
+ return;
+ }
+ remove_suggested_action(suggested_actions_, suggested_action);
+ reget_app_config(Auto());
+
+ set_promises(promises);
+ return;
+ }
+ if (token == 6 || token == 7) {
+ is_set_archive_and_mute_request_sent_ = false;
+ bool archive_and_mute = (token == 7);
+ auto result_ptr = fetch_result<telegram_api::account_setGlobalPrivacySettings>(std::move(res));
+ if (result_ptr.is_error()) {
+ fail_promises(set_archive_and_mute_queries_[archive_and_mute], result_ptr.move_as_error());
+ } else {
+ if (last_set_archive_and_mute_ == archive_and_mute) {
+ do_set_archive_and_mute(archive_and_mute);
+ }
+
+ set_promises(set_archive_and_mute_queries_[archive_and_mute]);
+ }
+
+ if (!set_archive_and_mute_queries_[!archive_and_mute].empty()) {
+ if (archive_and_mute == last_set_archive_and_mute_) {
+ set_promises(set_archive_and_mute_queries_[!archive_and_mute]);
+ } else {
+ set_archive_and_mute(!archive_and_mute, Auto());
+ }
+ }
+ return;
+ }
+ if (token == 5) {
+ auto result_ptr = fetch_result<telegram_api::account_getGlobalPrivacySettings>(std::move(res));
+ if (result_ptr.is_error()) {
+ fail_promises(get_global_privacy_settings_queries_, result_ptr.move_as_error());
+ return;
+ }
+
+ auto result = result_ptr.move_as_ok();
+ if ((result->flags_ & telegram_api::globalPrivacySettings::ARCHIVE_AND_MUTE_NEW_NONCONTACT_PEERS_MASK) != 0) {
+ do_set_archive_and_mute(result->archive_and_mute_new_noncontact_peers_);
+ } else {
+ LOG(ERROR) << "Receive wrong response: " << to_string(result);
+ }
+
+ set_promises(get_global_privacy_settings_queries_);
+ return;
+ }
+ if (token == 3 || token == 4) {
+ is_set_content_settings_request_sent_ = false;
+ bool ignore_sensitive_content_restrictions = (token == 4);
+ auto result_ptr = fetch_result<telegram_api::account_setContentSettings>(std::move(res));
+ if (result_ptr.is_error()) {
+ fail_promises(set_content_settings_queries_[ignore_sensitive_content_restrictions], result_ptr.move_as_error());
+ } else {
+ if (G()->get_option_boolean("can_ignore_sensitive_content_restrictions") &&
+ last_set_content_settings_ == ignore_sensitive_content_restrictions) {
+ do_set_ignore_sensitive_content_restrictions(ignore_sensitive_content_restrictions);
+ }
+
+ set_promises(set_content_settings_queries_[ignore_sensitive_content_restrictions]);
+ }
+
+ if (!set_content_settings_queries_[!ignore_sensitive_content_restrictions].empty()) {
+ if (ignore_sensitive_content_restrictions == last_set_content_settings_) {
+ set_promises(set_content_settings_queries_[!ignore_sensitive_content_restrictions]);
+ } else {
+ set_content_settings(!ignore_sensitive_content_restrictions, Auto());
+ }
+ }
+ return;
+ }
+ if (token == 2) {
+ auto result_ptr = fetch_result<telegram_api::account_getContentSettings>(std::move(res));
+ if (result_ptr.is_error()) {
+ fail_promises(get_content_settings_queries_, result_ptr.move_as_error());
+ return;
+ }
+
+ auto result = result_ptr.move_as_ok();
+ do_set_ignore_sensitive_content_restrictions(result->sensitive_enabled_);
+ G()->set_option_boolean("can_ignore_sensitive_content_restrictions", result->sensitive_can_change_);
+
+ set_promises(get_content_settings_queries_);
+ return;
+ }
+ if (token == 1) {
+ auto promises = std::move(get_app_config_queries_);
+ get_app_config_queries_.clear();
+ auto unit_promises = std::move(reget_app_config_queries_);
+ reget_app_config_queries_.clear();
+ CHECK(!promises.empty() || !unit_promises.empty());
+ auto result_ptr = fetch_result<telegram_api::help_getAppConfig>(std::move(res));
+ if (result_ptr.is_error()) {
+ fail_promises(promises, result_ptr.error().clone());
+ fail_promises(unit_promises, result_ptr.move_as_error());
+ return;
+ }
+
+ auto result = result_ptr.move_as_ok();
+ process_app_config(result);
+ for (auto &promise : promises) {
+ promise.set_value(convert_json_value_object(result));
+ }
+ set_promises(unit_promises);
+ return;
+ }
+
+ CHECK(token == 8 || token == 9);
CHECK(config_sent_cnt_ > 0);
config_sent_cnt_--;
auto r_config = fetch_result<telegram_api::help_getConfig>(std::move(res));
if (r_config.is_error()) {
- LOG(ERROR) << "TODO: getConfig failed: " << r_config.error();
- expire_ = Timestamp::in(60.0); // try again in a minute
- set_timeout_in(expire_.in());
+ if (!G()->close_flag()) {
+ LOG(WARNING) << "Failed to get config: " << r_config.error();
+ expire_time_ = Timestamp::in(60.0); // try again in a minute
+ set_timeout_in(expire_time_.in());
+ }
} else {
on_dc_options_update(DcOptions());
process_config(r_config.move_as_ok());
+ if (token == 9) {
+ G()->net_query_dispatcher().update_mtproto_header();
+ }
}
}
-void ConfigManager::save_dc_options_update(DcOptions dc_options) {
+void ConfigManager::save_dc_options_update(const DcOptions &dc_options) {
if (dc_options.dc_options.empty()) {
G()->td_db()->get_binlog_pmc()->erase("dc_options_update");
return;
@@ -642,110 +1294,704 @@ DcOptions ConfigManager::load_dc_options_update() {
return dc_options;
}
-Timestamp ConfigManager::load_config_expire() {
- auto expire_in = to_integer<int32>(G()->td_db()->get_binlog_pmc()->get("config_expire")) - Clocks::system();
+Timestamp ConfigManager::load_config_expire_time() {
+ auto expires_in = to_integer<int32>(G()->td_db()->get_binlog_pmc()->get("config_expire")) - Clocks::system();
- if (expire_in < 0 || expire_in > 60 * 60 /* 1 hour */) {
+ if (expires_in < 0 || expires_in > 60 * 60 /* 1 hour */) {
return Timestamp::now();
} else {
- return Timestamp::in(expire_in);
+ return Timestamp::in(expires_in);
}
}
void ConfigManager::save_config_expire(Timestamp timestamp) {
- G()->td_db()->get_binlog_pmc()->set("config_expire", to_string(static_cast<int>(Clocks::system() + expire_.in())));
+ G()->td_db()->get_binlog_pmc()->set("config_expire", to_string(static_cast<int>(Clocks::system() + timestamp.in())));
}
void ConfigManager::process_config(tl_object_ptr<telegram_api::config> config) {
- bool is_from_main_dc = G()->net_query_dispatcher().main_dc_id().get_value() == config->this_dc_;
+ bool is_from_main_dc = G()->net_query_dispatcher().get_main_dc_id().get_value() == config->this_dc_;
LOG(INFO) << to_string(config);
- auto reload_in = max(60 /* at least 60 seconds*/, config->expires_ - config->date_);
+ auto reload_in = clamp(config->expires_ - config->date_, 60, 86400);
save_config_expire(Timestamp::in(reload_in));
reload_in -= Random::fast(0, reload_in / 5);
if (!is_from_main_dc) {
reload_in = 0;
}
- expire_ = Timestamp::in(reload_in);
- set_timeout_at(expire_.at());
+ expire_time_ = Timestamp::in(reload_in);
+ set_timeout_at(expire_time_.at());
LOG_IF(ERROR, config->test_mode_ != G()->is_test_dc()) << "Wrong parameter is_test";
- ConfigShared &shared_config = G()->shared_config();
+ Global &options = *G();
// Do not save dc_options in config, because it will be interpreted and saved by ConnectionCreator.
send_closure(G()->connection_creator(), &ConnectionCreator::on_dc_options, DcOptions(config->dc_options_));
- shared_config.set_option_integer("recent_stickers_limit", config->stickers_recent_limit_);
- shared_config.set_option_integer("favorite_stickers_limit", config->stickers_faved_limit_);
- shared_config.set_option_integer("saved_animations_limit", config->saved_gifs_limit_);
- shared_config.set_option_integer("channels_read_media_period", config->channels_read_media_period_);
-
- shared_config.set_option_boolean("test_mode", config->test_mode_);
- shared_config.set_option_integer("forwarded_message_count_max", config->forwarded_count_max_);
- shared_config.set_option_integer("basic_group_size_max", config->chat_size_max_);
- shared_config.set_option_integer("supergroup_size_max", config->megagroup_size_max_);
- shared_config.set_option_integer("pinned_chat_count_max", config->pinned_dialogs_count_max_);
- if (is_from_main_dc || !shared_config.have_option("t_me_url")) {
- shared_config.set_option_string("t_me_url", config->me_url_prefix_);
+ options.set_option_integer("recent_stickers_limit", config->stickers_recent_limit_);
+ options.set_option_integer("favorite_stickers_limit", config->stickers_faved_limit_);
+ options.set_option_integer("saved_animations_limit", config->saved_gifs_limit_);
+ options.set_option_integer("channels_read_media_period", config->channels_read_media_period_);
+
+ options.set_option_boolean("test_mode", config->test_mode_);
+ options.set_option_integer("forwarded_message_count_max", config->forwarded_count_max_);
+ options.set_option_integer("basic_group_size_max", config->chat_size_max_);
+ options.set_option_integer("supergroup_size_max", config->megagroup_size_max_);
+ options.set_option_integer("pinned_chat_count_max", config->pinned_dialogs_count_max_);
+ options.set_option_integer("pinned_archived_chat_count_max", config->pinned_infolder_count_max_);
+ if (is_from_main_dc || !options.have_option("expect_blocking")) {
+ options.set_option_boolean("expect_blocking", config->blocked_mode_);
+ }
+ if (is_from_main_dc || !options.have_option("dc_txt_domain_name")) {
+ options.set_option_string("dc_txt_domain_name", config->dc_txt_domain_name_);
+ }
+ if (is_from_main_dc || !options.have_option("t_me_url")) {
+ auto url = config->me_url_prefix_;
+ if (!url.empty()) {
+ if (url.back() != '/') {
+ url.push_back('/');
+ }
+ options.set_option_string("t_me_url", url);
+ }
}
if (is_from_main_dc) {
+ options.set_option_integer("webfile_dc_id", config->webfile_dc_id_);
if ((config->flags_ & telegram_api::config::TMP_SESSIONS_MASK) != 0) {
- G()->shared_config().set_option_integer("session_count", config->tmp_sessions_);
+ options.set_option_integer("session_count", config->tmp_sessions_);
+ } else {
+ options.set_option_empty("session_count");
+ }
+ if ((config->flags_ & telegram_api::config::SUGGESTED_LANG_CODE_MASK) != 0) {
+ options.set_option_string("suggested_language_pack_id", config->suggested_lang_code_);
+ options.set_option_integer("language_pack_version", config->lang_pack_version_);
+ options.set_option_integer("base_language_pack_version", config->base_lang_pack_version_);
} else {
- G()->shared_config().set_option_empty("session_count");
+ options.set_option_empty("suggested_language_pack_id");
+ options.set_option_empty("language_pack_version");
+ options.set_option_empty("base_language_pack_version");
}
}
if (is_from_main_dc) {
- shared_config.set_option_integer("edit_time_limit", config->edit_time_limit_);
- shared_config.set_option_boolean("revoke_pm_inbox",
- (config->flags_ & telegram_api::config::REVOKE_PM_INBOX_MASK) != 0);
- shared_config.set_option_integer("revoke_time_limit", config->revoke_time_limit_);
- shared_config.set_option_integer("revoke_pm_time_limit", config->revoke_pm_time_limit_);
+ options.set_option_integer("edit_time_limit", config->edit_time_limit_);
+ options.set_option_boolean("revoke_pm_inbox", config->revoke_pm_inbox_);
+ options.set_option_integer("revoke_time_limit", config->revoke_time_limit_);
+ options.set_option_integer("revoke_pm_time_limit", config->revoke_pm_time_limit_);
- shared_config.set_option_integer("rating_e_decay", config->rating_e_decay_);
+ options.set_option_integer("rating_e_decay", config->rating_e_decay_);
- shared_config.set_option_boolean("calls_enabled", config->phonecalls_enabled_);
+ options.set_option_boolean("calls_enabled", config->phonecalls_enabled_);
+ }
+ options.set_option_integer("call_ring_timeout_ms", config->call_ring_timeout_ms_);
+ options.set_option_integer("call_connect_timeout_ms", config->call_connect_timeout_ms_);
+ options.set_option_integer("call_packet_timeout_ms", config->call_packet_timeout_ms_);
+ options.set_option_integer("call_receive_timeout_ms", config->call_receive_timeout_ms_);
+
+ options.set_option_integer("message_text_length_max", clamp(config->message_length_max_, 4096, 1000000));
+ options.set_option_integer("message_caption_length_max", clamp(config->caption_length_max_, 1024, 1000000));
+
+ if (config->gif_search_username_.empty()) {
+ options.set_option_empty("animation_search_bot_username");
+ } else {
+ options.set_option_string("animation_search_bot_username", config->gif_search_username_);
+ }
+ if (config->venue_search_username_.empty()) {
+ options.set_option_empty("venue_search_bot_username");
+ } else {
+ options.set_option_string("venue_search_bot_username", config->venue_search_username_);
+ }
+ if (config->img_search_username_.empty()) {
+ options.set_option_empty("photo_search_bot_username");
+ } else {
+ options.set_option_string("photo_search_bot_username", config->img_search_username_);
+ }
+
+ auto fix_timeout_ms = [](int32 timeout_ms) {
+ return clamp(timeout_ms, 1000, 86400 * 1000);
+ };
+
+ options.set_option_integer("online_update_period_ms", fix_timeout_ms(config->online_update_period_ms_));
+
+ options.set_option_integer("online_cloud_timeout_ms", fix_timeout_ms(config->online_cloud_timeout_ms_));
+ options.set_option_integer("notification_cloud_delay_ms", fix_timeout_ms(config->notify_cloud_delay_ms_));
+ options.set_option_integer("notification_default_delay_ms", fix_timeout_ms(config->notify_default_delay_ms_));
+
+ if (is_from_main_dc && !options.have_option("default_reaction_need_sync")) {
+ auto reaction_str = get_message_reaction_string(config->reactions_default_);
+ if (!reaction_str.empty()) {
+ options.set_option_string("default_reaction", reaction_str);
+ }
}
- shared_config.set_option_integer("call_ring_timeout_ms", config->call_ring_timeout_ms_);
- shared_config.set_option_integer("call_connect_timeout_ms", config->call_connect_timeout_ms_);
- shared_config.set_option_integer("call_packet_timeout_ms", config->call_packet_timeout_ms_);
- shared_config.set_option_integer("call_receive_timeout_ms", config->call_receive_timeout_ms_);
// delete outdated options
- shared_config.set_option_empty("chat_big_size");
- shared_config.set_option_empty("group_size_max");
- shared_config.set_option_empty("saved_gifs_limit");
- shared_config.set_option_empty("sessions_count");
- shared_config.set_option_empty("forwarded_messages_count_max");
- shared_config.set_option_empty("broadcast_size_max");
- shared_config.set_option_empty("group_chat_size_max");
- shared_config.set_option_empty("chat_size_max");
- shared_config.set_option_empty("megagroup_size_max");
- shared_config.set_option_empty("online_update_period_ms");
- shared_config.set_option_empty("offline_blur_timeout_ms");
- shared_config.set_option_empty("offline_idle_timeout_ms");
- shared_config.set_option_empty("online_cloud_timeout_ms");
- shared_config.set_option_empty("notify_cloud_delay_ms");
- shared_config.set_option_empty("notify_default_delay_ms");
- shared_config.set_option_empty("large_chat_size");
+ options.set_option_empty("suggested_language_code");
+ options.set_option_empty("chat_big_size");
+ options.set_option_empty("group_size_max");
+ options.set_option_empty("saved_gifs_limit");
+ options.set_option_empty("sessions_count");
+ options.set_option_empty("forwarded_messages_count_max");
+ options.set_option_empty("broadcast_size_max");
+ options.set_option_empty("group_chat_size_max");
+ options.set_option_empty("chat_size_max");
+ options.set_option_empty("megagroup_size_max");
+ options.set_option_empty("offline_blur_timeout_ms");
+ options.set_option_empty("offline_idle_timeout_ms");
+ options.set_option_empty("notify_cloud_delay_ms");
+ options.set_option_empty("notify_default_delay_ms");
+ options.set_option_empty("large_chat_size");
+
+ // TODO implement online status updates
+ // options.set_option_integer("offline_blur_timeout_ms", config->offline_blur_timeout_ms_);
+ // options.set_option_integer("offline_idle_timeout_ms", config->offline_idle_timeout_ms_);
+
+ // options.set_option_integer("push_chat_period_ms", config->push_chat_period_ms_);
+ // options.set_option_integer("push_chat_limit", config->push_chat_limit_);
if (is_from_main_dc) {
- for (auto &feature : shared_config.get_options("disabled_")) {
- shared_config.set_option_empty(feature.first);
+ reget_app_config(Auto());
+ if (!options.have_option("can_ignore_sensitive_content_restrictions") ||
+ !options.have_option("ignore_sensitive_content_restrictions")) {
+ get_content_settings(Auto());
+ }
+ if (!options.have_option("archive_and_mute_new_chats_from_unknown_users")) {
+ get_global_privacy_settings(Auto());
}
}
+}
- // TODO implement online status updates
- // shared_config.set_option_integer("online_update_period_ms", config->online_update_period_ms_);
- // shared_config.set_option_integer("offline_blur_timeout_ms", config->offline_blur_timeout_ms_);
- // shared_config.set_option_integer("offline_idle_timeout_ms", config->offline_idle_timeout_ms_);
- // shared_config.set_option_integer("online_cloud_timeout_ms", config->online_cloud_timeout_ms_);
- // shared_config.set_option_integer("notify_cloud_delay_ms", config->notify_cloud_delay_ms_);
- // shared_config.set_option_integer("notify_default_delay_ms", config->notify_default_delay_ms_);
-
- // shared_config.set_option_integer("push_chat_period_ms", config->push_chat_period_ms_);
- // shared_config.set_option_integer("push_chat_limit", config->push_chat_limit_);
+void ConfigManager::process_app_config(tl_object_ptr<telegram_api::JSONValue> &config) {
+ CHECK(config != nullptr);
+ LOG(INFO) << "Receive app config " << to_string(config);
+
+ const bool archive_and_mute = G()->get_option_boolean("archive_and_mute_new_chats_from_unknown_users");
+
+ string autologin_token;
+ vector<string> autologin_domains;
+ vector<string> url_auth_domains;
+ vector<string> whitelisted_domains;
+
+ vector<tl_object_ptr<telegram_api::jsonObjectValue>> new_values;
+ string ignored_restriction_reasons;
+ string restriction_add_platforms;
+ vector<string> dice_emojis;
+ FlatHashMap<string, size_t> dice_emoji_index;
+ FlatHashMap<string, string> dice_emoji_success_value;
+ vector<string> emoji_sounds;
+ string animation_search_provider;
+ string animation_search_emojis;
+ vector<SuggestedAction> suggested_actions;
+ bool can_archive_and_mute_new_chats_from_unknown_users = false;
+ int32 chat_read_mark_expire_period = 0;
+ int32 chat_read_mark_size_threshold = 0;
+ double animated_emoji_zoom = 0.0;
+ int32 reactions_uniq_max = 0;
+ vector<string> premium_features;
+ auto &premium_limit_keys = get_premium_limit_keys();
+ string premium_bot_username;
+ string premium_invoice_slug;
+ bool is_premium_available = false;
+ int32 stickers_premium_by_emoji_num = 0;
+ int32 stickers_normal_by_emoji_per_premium_num = 2;
+ int32 forum_upgrade_participants_min = 200;
+ if (config->get_id() == telegram_api::jsonObject::ID) {
+ for (auto &key_value : static_cast<telegram_api::jsonObject *>(config.get())->value_) {
+ Slice key = key_value->key_;
+ telegram_api::JSONValue *value = key_value->value_.get();
+ if (key == "getfile_experimental_params" || key == "message_animated_emoji_max" ||
+ key == "stickers_emoji_cache_time" || key == "test" || key == "upload_max_fileparts_default" ||
+ key == "upload_max_fileparts_premium" || key == "wallet_blockchain_name" || key == "wallet_config" ||
+ key == "wallet_enabled") {
+ continue;
+ }
+ if (key == "ignore_restriction_reasons") {
+ if (value->get_id() == telegram_api::jsonArray::ID) {
+ auto reasons = std::move(static_cast<telegram_api::jsonArray *>(value)->value_);
+ for (auto &reason : reasons) {
+ auto reason_name = get_json_value_string(std::move(reason), key);
+ if (!reason_name.empty() && reason_name.find(',') == string::npos) {
+ if (!ignored_restriction_reasons.empty()) {
+ ignored_restriction_reasons += ',';
+ }
+ ignored_restriction_reasons += reason_name;
+ } else {
+ LOG(ERROR) << "Receive unexpected restriction reason " << reason_name;
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected ignore_restriction_reasons " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "restriction_add_platforms") {
+ if (value->get_id() == telegram_api::jsonArray::ID) {
+ auto platforms = std::move(static_cast<telegram_api::jsonArray *>(value)->value_);
+ for (auto &platform : platforms) {
+ auto platform_name = get_json_value_string(std::move(platform), key);
+ if (!platform_name.empty() && platform_name.find(',') == string::npos) {
+ if (!restriction_add_platforms.empty()) {
+ restriction_add_platforms += ',';
+ }
+ restriction_add_platforms += platform_name;
+ } else {
+ LOG(ERROR) << "Receive unexpected restriction platform " << platform_name;
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected restriction_add_platforms " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "emojies_animated_zoom") {
+ animated_emoji_zoom = get_json_value_double(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "emojies_send_dice") {
+ if (value->get_id() == telegram_api::jsonArray::ID) {
+ auto emojis = std::move(static_cast<telegram_api::jsonArray *>(value)->value_);
+ for (auto &emoji : emojis) {
+ auto emoji_text = get_json_value_string(std::move(emoji), key);
+ if (!emoji_text.empty()) {
+ dice_emoji_index[emoji_text] = dice_emojis.size();
+ dice_emojis.push_back(emoji_text);
+ } else {
+ LOG(ERROR) << "Receive empty dice emoji";
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected emojies_send_dice " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "emojies_send_dice_success") {
+ if (value->get_id() == telegram_api::jsonObject::ID) {
+ auto success_values = std::move(static_cast<telegram_api::jsonObject *>(value)->value_);
+ for (auto &success_value : success_values) {
+ CHECK(success_value != nullptr);
+ if (!success_value->key_.empty() && success_value->value_->get_id() == telegram_api::jsonObject::ID) {
+ int32 dice_value = -1;
+ int32 frame_start = -1;
+ for (auto &dice_key_value :
+ static_cast<telegram_api::jsonObject *>(success_value->value_.get())->value_) {
+ if (dice_key_value->value_->get_id() != telegram_api::jsonNumber::ID) {
+ continue;
+ }
+ auto current_value = get_json_value_int(std::move(dice_key_value->value_), Slice());
+ if (dice_key_value->key_ == "value") {
+ dice_value = current_value;
+ }
+ if (dice_key_value->key_ == "frame_start") {
+ frame_start = current_value;
+ }
+ }
+ if (dice_value < 0 || frame_start < 0) {
+ LOG(ERROR) << "Receive unexpected dice success value " << to_string(success_value);
+ } else {
+ dice_emoji_success_value[success_value->key_] = PSTRING() << dice_value << ':' << frame_start;
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected dice success value " << to_string(success_value);
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected emojies_send_dice_success " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "emojies_sounds") {
+ if (value->get_id() == telegram_api::jsonObject::ID) {
+ auto sounds = std::move(static_cast<telegram_api::jsonObject *>(value)->value_);
+ for (auto &sound : sounds) {
+ CHECK(sound != nullptr);
+ if (sound->value_->get_id() == telegram_api::jsonObject::ID) {
+ string id;
+ string access_hash;
+ string file_reference_base64;
+ for (auto &sound_key_value : static_cast<telegram_api::jsonObject *>(sound->value_.get())->value_) {
+ if (sound_key_value->value_->get_id() != telegram_api::jsonString::ID) {
+ continue;
+ }
+ auto current_value = get_json_value_string(std::move(sound_key_value->value_), Slice());
+ if (sound_key_value->key_ == "id") {
+ id = std::move(current_value);
+ } else if (sound_key_value->key_ == "access_hash") {
+ access_hash = std::move(current_value);
+ } else if (sound_key_value->key_ == "file_reference_base64") {
+ file_reference_base64 = std::move(current_value);
+ }
+ }
+ if (to_integer_safe<int64>(id).is_error() || to_integer_safe<int64>(access_hash).is_error() ||
+ !is_base64url(file_reference_base64) || !is_emoji(sound->key_)) {
+ LOG(ERROR) << "Receive unexpected sound value " << to_string(sound);
+ } else {
+ emoji_sounds.push_back(sound->key_);
+ emoji_sounds.push_back(PSTRING() << id << ':' << access_hash << ':' << file_reference_base64);
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected emoji sound " << to_string(sound);
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected emojies_sounds " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "gif_search_branding") {
+ animation_search_provider = get_json_value_string(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "gif_search_emojies") {
+ if (value->get_id() == telegram_api::jsonArray::ID) {
+ auto emojis = std::move(static_cast<telegram_api::jsonArray *>(value)->value_);
+ for (auto &emoji : emojis) {
+ auto emoji_str = get_json_value_string(std::move(emoji), key);
+ if (!emoji_str.empty() && emoji_str.find(',') == string::npos) {
+ if (!animation_search_emojis.empty()) {
+ animation_search_emojis += ',';
+ }
+ animation_search_emojis += emoji_str;
+ } else {
+ LOG(ERROR) << "Receive unexpected animation search emoji " << emoji_str;
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected gif_search_emojies " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "pending_suggestions") {
+ if (value->get_id() == telegram_api::jsonArray::ID) {
+ auto actions = std::move(static_cast<telegram_api::jsonArray *>(value)->value_);
+ for (auto &action : actions) {
+ auto action_str = get_json_value_string(std::move(action), key);
+ SuggestedAction suggested_action(action_str);
+ if (!suggested_action.is_empty()) {
+ if (archive_and_mute &&
+ suggested_action == SuggestedAction{SuggestedAction::Type::EnableArchiveAndMuteNewChats}) {
+ LOG(INFO) << "Skip EnableArchiveAndMuteNewChats suggested action";
+ } else {
+ suggested_actions.push_back(suggested_action);
+ }
+ } else {
+ LOG(ERROR) << "Receive unsupported suggested action " << action_str;
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected pending_suggestions " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "autoarchive_setting_available") {
+ can_archive_and_mute_new_chats_from_unknown_users = get_json_value_bool(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "autologin_token") {
+ autologin_token = get_json_value_string(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "autologin_domains") {
+ if (value->get_id() == telegram_api::jsonArray::ID) {
+ auto domains = std::move(static_cast<telegram_api::jsonArray *>(value)->value_);
+ for (auto &domain : domains) {
+ autologin_domains.push_back(get_json_value_string(std::move(domain), key));
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected autologin_domains " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "url_auth_domains") {
+ if (value->get_id() == telegram_api::jsonArray::ID) {
+ auto domains = std::move(static_cast<telegram_api::jsonArray *>(value)->value_);
+ for (auto &domain : domains) {
+ url_auth_domains.push_back(get_json_value_string(std::move(domain), key));
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected url_auth_domains " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "whitelisted_domains") {
+ if (value->get_id() == telegram_api::jsonArray::ID) {
+ auto domains = std::move(static_cast<telegram_api::jsonArray *>(value)->value_);
+ for (auto &domain : domains) {
+ whitelisted_domains.push_back(get_json_value_string(std::move(domain), key));
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected whitelisted_domains " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "round_video_encoding") {
+ if (value->get_id() == telegram_api::jsonObject::ID) {
+ auto video_note_settings = std::move(static_cast<telegram_api::jsonObject *>(value)->value_);
+ for (auto &video_note_setting : video_note_settings) {
+ CHECK(video_note_setting != nullptr);
+ if (video_note_setting->key_ != "diameter" && video_note_setting->key_ != "video_bitrate" &&
+ video_note_setting->key_ != "audio_bitrate" && video_note_setting->key_ != "max_size") {
+ continue;
+ }
+ if (video_note_setting->value_->get_id() == telegram_api::jsonNumber::ID) {
+ auto setting_value = get_json_value_int(std::move(video_note_setting->value_), Slice());
+ if (setting_value > 0) {
+ if (video_note_setting->key_ == "diameter") {
+ G()->set_option_integer("suggested_video_note_length", setting_value);
+ }
+ if (video_note_setting->key_ == "video_bitrate") {
+ G()->set_option_integer("suggested_video_note_video_bitrate", setting_value);
+ }
+ if (video_note_setting->key_ == "audio_bitrate") {
+ G()->set_option_integer("suggested_video_note_audio_bitrate", setting_value);
+ }
+ if (video_note_setting->key_ == "max_size") {
+ G()->set_option_integer("video_note_size_max", setting_value);
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected video note setting " << to_string(video_note_setting);
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected round_video_encoding " << to_string(*value);
+ }
+ continue;
+ }
+ if (key == "chat_read_mark_expire_period") {
+ chat_read_mark_expire_period = get_json_value_int(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "chat_read_mark_size_threshold") {
+ chat_read_mark_size_threshold = get_json_value_int(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "reactions_uniq_max") {
+ reactions_uniq_max = get_json_value_int(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "ringtone_duration_max") {
+ auto setting_value = get_json_value_int(std::move(key_value->value_), key);
+ G()->set_option_integer("notification_sound_duration_max", setting_value);
+ continue;
+ }
+ if (key == "ringtone_size_max") {
+ auto setting_value = get_json_value_int(std::move(key_value->value_), key);
+ G()->set_option_integer("notification_sound_size_max", setting_value);
+ continue;
+ }
+ if (key == "ringtone_saved_count_max") {
+ auto setting_value = get_json_value_int(std::move(key_value->value_), key);
+ G()->set_option_integer("notification_sound_count_max", setting_value);
+ continue;
+ }
+ if (key == "premium_promo_order") {
+ if (value->get_id() == telegram_api::jsonArray::ID) {
+ auto features = std::move(static_cast<telegram_api::jsonArray *>(value)->value_);
+ for (auto &feature : features) {
+ auto premium_feature = get_json_value_string(std::move(feature), key);
+ if (!td::contains(premium_feature, ',')) {
+ premium_features.push_back(std::move(premium_feature));
+ }
+ }
+ } else {
+ LOG(ERROR) << "Receive unexpected premium_promo_order " << to_string(*value);
+ }
+ continue;
+ }
+ bool is_premium_limit_key = false;
+ for (auto premium_limit_key : premium_limit_keys) {
+ if (begins_with(key, premium_limit_key)) {
+ auto suffix = key.substr(premium_limit_key.size());
+ if (suffix == "_limit_default" || suffix == "_limit_premium") {
+ auto setting_value = get_json_value_int(std::move(key_value->value_), key);
+ if (setting_value > 0) {
+ G()->set_option_integer(key, setting_value);
+ } else {
+ LOG(ERROR) << "Receive invalid value " << setting_value << " for " << key;
+ }
+ is_premium_limit_key = true;
+ break;
+ }
+ }
+ }
+ if (is_premium_limit_key) {
+ continue;
+ }
+ if (key == "premium_bot_username") {
+ premium_bot_username = get_json_value_string(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "premium_invoice_slug") {
+ premium_invoice_slug = get_json_value_string(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "premium_purchase_blocked") {
+ is_premium_available = !get_json_value_bool(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "stickers_premium_by_emoji_num") {
+ stickers_premium_by_emoji_num = get_json_value_int(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "stickers_normal_by_emoji_per_premium_num") {
+ stickers_normal_by_emoji_per_premium_num = get_json_value_int(std::move(key_value->value_), key);
+ continue;
+ }
+ if (key == "default_emoji_statuses_stickerset_id") {
+ auto setting_value = get_json_value_long(std::move(key_value->value_), key);
+ G()->set_option_integer("themed_emoji_statuses_sticker_set_id", setting_value);
+ continue;
+ }
+ if (key == "reactions_user_max_default" || key == "reactions_user_max_premium") {
+ auto setting_value = get_json_value_int(std::move(key_value->value_), key);
+ G()->set_option_integer(key, setting_value);
+ continue;
+ }
+ if (key == "forum_upgrade_participants_min") {
+ forum_upgrade_participants_min = get_json_value_int(std::move(key_value->value_), key);
+ continue;
+ }
+
+ new_values.push_back(std::move(key_value));
+ }
+ } else {
+ LOG(ERROR) << "Receive wrong app config " << to_string(config);
+ }
+ config = make_tl_object<telegram_api::jsonObject>(std::move(new_values));
+
+ send_closure(G()->link_manager(), &LinkManager::update_autologin_domains, std::move(autologin_token),
+ std::move(autologin_domains), std::move(url_auth_domains), std::move(whitelisted_domains));
+
+ Global &options = *G();
+
+ if (ignored_restriction_reasons.empty()) {
+ options.set_option_empty("ignored_restriction_reasons");
+
+ if (options.get_option_boolean("ignore_sensitive_content_restrictions", true) ||
+ options.get_option_boolean("can_ignore_sensitive_content_restrictions", true)) {
+ get_content_settings(Auto());
+ }
+ } else {
+ options.set_option_string("ignored_restriction_reasons", ignored_restriction_reasons);
+
+ if (!options.get_option_boolean("can_ignore_sensitive_content_restrictions") ||
+ !options.get_option_boolean("ignore_sensitive_content_restrictions")) {
+ get_content_settings(Auto());
+ }
+ }
+ if (restriction_add_platforms.empty()) {
+ options.set_option_empty("restriction_add_platforms");
+ } else {
+ options.set_option_string("restriction_add_platforms", restriction_add_platforms);
+ }
+
+ if (!dice_emojis.empty()) {
+ vector<string> dice_success_values(dice_emojis.size());
+ for (auto &it : dice_emoji_success_value) {
+ auto dice_emoji_it = dice_emoji_index.find(it.first);
+ if (dice_emoji_it == dice_emoji_index.end()) {
+ LOG(ERROR) << "Can't find emoji " << it.first;
+ continue;
+ }
+ dice_success_values[dice_emoji_it->second] = it.second;
+ }
+ options.set_option_string("dice_success_values", implode(dice_success_values, ','));
+ options.set_option_string("dice_emojis", implode(dice_emojis, '\x01'));
+ }
+
+ options.set_option_string("emoji_sounds", implode(emoji_sounds, ','));
+
+ if (animated_emoji_zoom <= 0 || animated_emoji_zoom > 2.0) {
+ options.set_option_empty("animated_emoji_zoom");
+ } else {
+ options.set_option_integer("animated_emoji_zoom", static_cast<int64>(animated_emoji_zoom * 1e9));
+ }
+ if (animation_search_provider.empty()) {
+ options.set_option_empty("animation_search_provider");
+ } else {
+ options.set_option_string("animation_search_provider", animation_search_provider);
+ }
+ if (animation_search_emojis.empty()) {
+ options.set_option_empty("animation_search_emojis");
+ } else {
+ options.set_option_string("animation_search_emojis", animation_search_emojis);
+ }
+ if (!can_archive_and_mute_new_chats_from_unknown_users) {
+ options.set_option_empty("can_archive_and_mute_new_chats_from_unknown_users");
+ } else {
+ options.set_option_boolean("can_archive_and_mute_new_chats_from_unknown_users",
+ can_archive_and_mute_new_chats_from_unknown_users);
+ }
+ if (chat_read_mark_expire_period <= 0) {
+ options.set_option_empty("chat_read_mark_expire_period");
+ } else {
+ options.set_option_integer("chat_read_mark_expire_period", chat_read_mark_expire_period);
+ }
+ if (chat_read_mark_size_threshold <= 0) {
+ options.set_option_empty("chat_read_mark_size_threshold");
+ } else {
+ options.set_option_integer("chat_read_mark_size_threshold", chat_read_mark_size_threshold);
+ }
+ if (reactions_uniq_max <= 0 || reactions_uniq_max == 11) {
+ options.set_option_empty("reactions_uniq_max");
+ } else {
+ options.set_option_integer("reactions_uniq_max", reactions_uniq_max);
+ }
+ if (forum_upgrade_participants_min < 0) {
+ options.set_option_empty("forum_member_count_min");
+ } else {
+ options.set_option_integer("forum_member_count_min", forum_upgrade_participants_min);
+ }
+
+ bool is_premium = options.get_option_boolean("is_premium");
+
+ auto chat_filter_count_max = options.get_option_integer(
+ is_premium ? Slice("dialog_filters_limit_premium") : Slice("dialog_filters_limit_default"), is_premium ? 20 : 10);
+ options.set_option_integer("chat_filter_count_max", static_cast<int32>(chat_filter_count_max));
+
+ auto chat_filter_chosen_chat_count_max = options.get_option_integer(
+ is_premium ? Slice("dialog_filters_chats_limit_premium") : Slice("dialog_filters_chats_limit_default"),
+ is_premium ? 200 : 100);
+ options.set_option_integer("chat_filter_chosen_chat_count_max",
+ static_cast<int32>(chat_filter_chosen_chat_count_max));
+
+ auto bio_length_max = options.get_option_integer(
+ is_premium ? Slice("about_length_limit_premium") : Slice("about_length_limit_default"), is_premium ? 140 : 70);
+ options.set_option_integer("bio_length_max", bio_length_max);
+
+ if (!is_premium_available) {
+ premium_bot_username.clear(); // just in case
+ premium_invoice_slug.clear(); // just in case
+ premium_features.clear(); // just in case
+ options.set_option_empty("is_premium_available");
+ } else {
+ options.set_option_boolean("is_premium_available", is_premium_available);
+ }
+ options.set_option_string("premium_features", implode(premium_features, ','));
+ if (premium_bot_username.empty()) {
+ options.set_option_empty("premium_bot_username");
+ } else {
+ options.set_option_string("premium_bot_username", premium_bot_username);
+ }
+ if (premium_invoice_slug.empty()) {
+ options.set_option_empty("premium_invoice_slug");
+ } else {
+ options.set_option_string("premium_invoice_slug", premium_invoice_slug);
+ }
+
+ options.set_option_integer("stickers_premium_by_emoji_num", stickers_premium_by_emoji_num);
+ options.set_option_integer("stickers_normal_by_emoji_per_premium_num", stickers_normal_by_emoji_per_premium_num);
+
+ options.set_option_empty("default_ton_blockchain_config");
+ options.set_option_empty("default_ton_blockchain_name");
+
+ // do not update suggested actions while changing content settings or dismissing an action
+ if (!is_set_content_settings_request_sent_ && dismiss_suggested_action_request_count_ == 0) {
+ update_suggested_actions(suggested_actions_, std::move(suggested_actions));
+ }
+}
+
+void ConfigManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ if (!suggested_actions_.empty()) {
+ updates.push_back(get_update_suggested_actions_object(suggested_actions_, {}));
+ }
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ConfigManager.h b/protocols/Telegram/tdlib/td/td/telegram/ConfigManager.h
index 3f2c7f05cf..4805fc8e63 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ConfigManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/ConfigManager.h
@@ -1,69 +1,161 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/DcOptions.h"
#include "td/telegram/net/NetQuery.h"
-
+#include "td/telegram/SuggestedAction.h"
+#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
#include "td/actor/actor.h"
-#include "td/utils/port/IPAddress.h"
+#include "td/utils/common.h"
+#include "td/utils/FloodControlStrict.h"
+#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/Time.h"
+#include <limits>
+#include <map>
+
namespace td {
+extern int VERBOSITY_NAME(config_recoverer);
+
using SimpleConfig = tl_object_ptr<telegram_api::help_configSimple>;
+struct SimpleConfigResult {
+ Result<SimpleConfig> r_config;
+ Result<int32> r_http_date;
+};
Result<SimpleConfig> decode_config(Slice input);
-ActorOwn<> get_simple_config_azure(Promise<SimpleConfig> promise, bool is_test = false, int32 scheduler_id = -1);
+ActorOwn<> get_simple_config_azure(Promise<SimpleConfigResult> promise, bool prefer_ipv6, Slice domain_name,
+ bool is_test, int32 scheduler_id);
-ActorOwn<> get_simple_config_google_app(Promise<SimpleConfig> promise, bool is_test = false, int32 scheduler_id = -1);
+ActorOwn<> get_simple_config_google_dns(Promise<SimpleConfigResult> promise, bool prefer_ipv6, Slice domain_name,
+ bool is_test, int32 scheduler_id);
-ActorOwn<> get_simple_config_google_dns(Promise<SimpleConfig> promise, bool is_test = false, int32 scheduler_id = -1);
+ActorOwn<> get_simple_config_mozilla_dns(Promise<SimpleConfigResult> promise, bool prefer_ipv6, Slice domain_name,
+ bool is_test, int32 scheduler_id);
-using FullConfig = tl_object_ptr<telegram_api::config>;
+ActorOwn<> get_simple_config_firebase_remote_config(Promise<SimpleConfigResult> promise, bool prefer_ipv6,
+ Slice domain_name, bool is_test, int32 scheduler_id);
-ActorOwn<> get_full_config(IPAddress ip_address, Promise<FullConfig> promise);
+ActorOwn<> get_simple_config_firebase_realtime(Promise<SimpleConfigResult> promise, bool prefer_ipv6, Slice domain_name,
+ bool is_test, int32 scheduler_id);
+
+ActorOwn<> get_simple_config_firebase_firestore(Promise<SimpleConfigResult> promise, bool prefer_ipv6,
+ Slice domain_name, bool is_test, int32 scheduler_id);
+
+class HttpDate {
+ static bool is_leap(int32 year) {
+ return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+ }
+ static int32 days_in_month(int32 year, int32 month) {
+ static int cnt[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ return cnt[month - 1] + (month == 2 && is_leap(year));
+ }
+ static int32 seconds_in_day() {
+ return 24 * 60 * 60;
+ }
+
+ public:
+ static Result<int32> to_unix_time(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second);
+ static Result<int32> parse_http_date(std::string slice);
+};
class ConfigRecoverer;
-class ConfigManager : public NetQueryCallback {
+class ConfigManager final : public NetQueryCallback {
public:
explicit ConfigManager(ActorShared<> parent);
- void request_config();
+ void request_config(bool reopen_sessions);
+
+ void lazy_request_config();
+
+ void get_app_config(Promise<td_api::object_ptr<td_api::JsonValue>> &&promise);
+
+ void reget_app_config(Promise<Unit> &&promise);
+
+ void get_content_settings(Promise<Unit> &&promise);
+
+ void set_content_settings(bool ignore_sensitive_content_restrictions, Promise<Unit> &&promise);
+
+ void get_global_privacy_settings(Promise<Unit> &&promise);
+
+ void set_archive_and_mute(bool archive_and_mute, Promise<Unit> &&promise);
+
+ void hide_suggested_action(SuggestedAction suggested_action);
+
+ void dismiss_suggested_action(SuggestedAction suggested_action, Promise<Unit> &&promise);
void on_dc_options_update(DcOptions dc_options);
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
private:
ActorShared<> parent_;
int32 config_sent_cnt_{0};
+ bool reopen_sessions_after_get_config_{false};
ActorOwn<ConfigRecoverer> config_recoverer_;
int ref_cnt_{1};
- Timestamp expire_;
+ Timestamp expire_time_;
+
+ FloodControlStrict lazy_request_flood_control_;
+
+ vector<Promise<td_api::object_ptr<td_api::JsonValue>>> get_app_config_queries_;
+ vector<Promise<Unit>> reget_app_config_queries_;
+
+ vector<Promise<Unit>> get_content_settings_queries_;
+ vector<Promise<Unit>> set_content_settings_queries_[2];
+ bool is_set_content_settings_request_sent_ = false;
+ bool last_set_content_settings_ = false;
+
+ vector<Promise<Unit>> get_global_privacy_settings_queries_;
+ vector<Promise<Unit>> set_archive_and_mute_queries_[2];
+ bool is_set_archive_and_mute_request_sent_ = false;
+ bool last_set_archive_and_mute_ = false;
+
+ vector<SuggestedAction> suggested_actions_;
+ size_t dismiss_suggested_action_request_count_ = 0;
+ std::map<int32, vector<Promise<Unit>>> dismiss_suggested_action_queries_;
- void start_up() override;
- void hangup_shared() override;
- void hangup() override;
- void loop() override;
+ static constexpr uint64 REFCNT_TOKEN = std::numeric_limits<uint64>::max() - 2;
+
+ void start_up() final;
+ void hangup_shared() final;
+ void hangup() final;
+ void loop() final;
void try_stop();
- void on_result(NetQueryPtr res) override;
+ void on_result(NetQueryPtr res) final;
- void request_config_from_dc_impl(DcId dc_id);
+ void request_config_from_dc_impl(DcId dc_id, bool reopen_sessions);
void process_config(tl_object_ptr<telegram_api::config> config);
- Timestamp load_config_expire();
- void save_config_expire(Timestamp timestamp);
- void save_dc_options_update(DcOptions dc_options);
- DcOptions load_dc_options_update();
+ void try_request_app_config();
+
+ void process_app_config(tl_object_ptr<telegram_api::JSONValue> &config);
+
+ void do_set_ignore_sensitive_content_restrictions(bool ignore_sensitive_content_restrictions);
+
+ void do_set_archive_and_mute(bool archive_and_mute);
+
+ static Timestamp load_config_expire_time();
+ static void save_config_expire(Timestamp timestamp);
+ static void save_dc_options_update(const DcOptions &dc_options);
+ static DcOptions load_dc_options_update();
+
+ ActorShared<> create_reference();
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ConfigShared.cpp b/protocols/Telegram/tdlib/td/td/telegram/ConfigShared.cpp
deleted file mode 100644
index 637d9fcdbc..0000000000
--- a/protocols/Telegram/tdlib/td/td/telegram/ConfigShared.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#include "td/telegram/ConfigShared.h"
-
-#include "td/telegram/td_api.h"
-
-#include "td/utils/logging.h"
-#include "td/utils/misc.h"
-
-namespace td {
-ConfigShared::ConfigShared(BinlogPmcPtr config_pmc, unique_ptr<Callback> callback)
- : config_pmc_(config_pmc), callback_(std::move(callback)) {
- for (auto key_value : config_pmc_->get_all()) {
- on_option_updated(key_value.first);
- }
-}
-void ConfigShared::set_option_boolean(Slice name, bool value) {
- if (set_option(name, value ? Slice("Btrue") : Slice("Bfalse"))) {
- on_option_updated(name);
- }
-}
-
-void ConfigShared::set_option_empty(Slice name) {
- if (set_option(name, Slice())) {
- on_option_updated(name);
- }
-}
-
-void ConfigShared::set_option_integer(Slice name, int32 value) {
- if (set_option(name, PSLICE() << "I" << value)) {
- on_option_updated(name);
- }
-}
-
-void ConfigShared::set_option_string(Slice name, Slice value) {
- if (set_option(name, PSLICE() << "S" << value)) {
- on_option_updated(name);
- }
-}
-
-bool ConfigShared::have_option(Slice name) const {
- return config_pmc_->isset(name.str());
-}
-
-string ConfigShared::get_option(Slice name) const {
- return config_pmc_->get(name.str());
-}
-
-std::unordered_map<string, string> ConfigShared::get_options(Slice prefix) const {
- return config_pmc_->prefix_get(prefix);
-}
-
-std::unordered_map<string, string> ConfigShared::get_options() const {
- return config_pmc_->get_all();
-}
-
-bool ConfigShared::get_option_boolean(Slice name) const {
- auto value = get_option(name);
- if (value.empty()) {
- return false;
- }
- if (value == "Btrue") {
- return true;
- }
- if (value == "Bfalse") {
- return false;
- }
- LOG(ERROR) << "Found \"" << value << "\" instead of boolean option";
- return false;
-}
-
-int32 ConfigShared::get_option_integer(Slice name, int32 default_value) const {
- auto str_value = get_option(name);
- if (str_value.empty()) {
- return default_value;
- }
- if (str_value[0] != 'I') {
- LOG(ERROR) << "Found \"" << str_value << "\" instead of integer option";
- return default_value;
- }
- return to_integer<int32>(str_value.substr(1));
-}
-
-tl_object_ptr<td_api::OptionValue> ConfigShared::get_option_value(Slice value) const {
- return get_option_value_object(get_option(value));
-}
-
-bool ConfigShared::set_option(Slice name, Slice value) {
- if (value.empty()) {
- return config_pmc_->erase(name.str()) != 0;
- } else {
- return config_pmc_->set(name.str(), value.str()) != 0;
- }
-}
-
-tl_object_ptr<td_api::OptionValue> ConfigShared::get_option_value_object(Slice value) {
- if (value.empty()) {
- return make_tl_object<td_api::optionValueEmpty>();
- }
-
- switch (value[0]) {
- case 'B':
- if (value == "Btrue") {
- return make_tl_object<td_api::optionValueBoolean>(true);
- }
- if (value == "Bfalse") {
- return make_tl_object<td_api::optionValueBoolean>(false);
- }
- break;
- case 'I':
- return make_tl_object<td_api::optionValueInteger>(to_integer<int32>(value.substr(1)));
- case 'S':
- return make_tl_object<td_api::optionValueString>(value.substr(1).str());
- }
-
- return make_tl_object<td_api::optionValueString>(value.str());
-}
-
-void ConfigShared::on_option_updated(Slice name) {
- callback_->on_option_updated(name.str());
-}
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ConfigShared.h b/protocols/Telegram/tdlib/td/td/telegram/ConfigShared.h
deleted file mode 100644
index 03f3ca2807..0000000000
--- a/protocols/Telegram/tdlib/td/td/telegram/ConfigShared.h
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/db/Pmc.h"
-
-#include "td/telegram/td_api.h"
-
-#include "td/utils/common.h"
-#include "td/utils/Slice.h"
-
-#include <unordered_map>
-
-namespace td {
-class ConfigShared {
- public:
- class Callback {
- public:
- Callback() = default;
- Callback(const Callback &) = delete;
- Callback &operator=(const Callback &) = delete;
- virtual ~Callback() = default;
- virtual void on_option_updated(const string &name) = 0;
- };
-
- ConfigShared(BinlogPmcPtr config_pmc, unique_ptr<Callback> callback);
-
- void set_option_boolean(Slice name, bool value);
- void set_option_empty(Slice name);
- void set_option_integer(Slice name, int32 value);
- void set_option_string(Slice name, Slice value);
-
- bool have_option(Slice name) const;
- string get_option(Slice name) const;
- std::unordered_map<string, string> get_options(Slice prefix) const;
- std::unordered_map<string, string> get_options() const;
-
- bool get_option_boolean(Slice name) const;
- int32 get_option_integer(Slice name, int32 default_value = 0) const;
-
- tl_object_ptr<td_api::OptionValue> get_option_value(Slice value) const;
-
- private:
- BinlogPmcPtr config_pmc_;
- unique_ptr<Callback> callback_;
-
- bool set_option(Slice name, Slice value);
- static tl_object_ptr<td_api::OptionValue> get_option_value_object(Slice value);
-
- void on_option_updated(Slice name);
-};
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ConnectionState.cpp b/protocols/Telegram/tdlib/td/td/telegram/ConnectionState.cpp
new file mode 100644
index 0000000000..27268720d9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ConnectionState.cpp
@@ -0,0 +1,38 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ConnectionState.h"
+
+#include "td/utils/logging.h"
+
+namespace td {
+
+static td_api::object_ptr<td_api::ConnectionState> get_connection_state_object(ConnectionState state) {
+ switch (state) {
+ case ConnectionState::Empty:
+ UNREACHABLE();
+ return nullptr;
+ case ConnectionState::WaitingForNetwork:
+ return td_api::make_object<td_api::connectionStateWaitingForNetwork>();
+ case ConnectionState::ConnectingToProxy:
+ return td_api::make_object<td_api::connectionStateConnectingToProxy>();
+ case ConnectionState::Connecting:
+ return td_api::make_object<td_api::connectionStateConnecting>();
+ case ConnectionState::Updating:
+ return td_api::make_object<td_api::connectionStateUpdating>();
+ case ConnectionState::Ready:
+ return td_api::make_object<td_api::connectionStateReady>();
+ default:
+ LOG(FATAL) << "State = " << static_cast<int32>(state);
+ return nullptr;
+ }
+}
+
+td_api::object_ptr<td_api::updateConnectionState> get_update_connection_state_object(ConnectionState state) {
+ return td_api::make_object<td_api::updateConnectionState>(get_connection_state_object(state));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ConnectionState.h b/protocols/Telegram/tdlib/td/td/telegram/ConnectionState.h
new file mode 100644
index 0000000000..95a6daccd9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ConnectionState.h
@@ -0,0 +1,19 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+enum class ConnectionState : int32 { WaitingForNetwork, ConnectingToProxy, Connecting, Updating, Ready, Empty };
+
+td_api::object_ptr<td_api::updateConnectionState> get_update_connection_state_object(ConnectionState state);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Contact.cpp b/protocols/Telegram/tdlib/td/td/telegram/Contact.cpp
index dd50281554..b54d654b33 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Contact.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Contact.cpp
@@ -1,11 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Contact.h"
+#include "td/telegram/misc.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
@@ -16,10 +17,11 @@
namespace td {
-Contact::Contact(string phone_number, string first_name, string last_name, int32 user_id)
+Contact::Contact(string phone_number, string first_name, string last_name, string vcard, UserId user_id)
: phone_number_(std::move(phone_number))
, first_name_(std::move(first_name))
, last_name_(std::move(last_name))
+ , vcard_(std::move(vcard))
, user_id_(user_id) {
if (!user_id_.is_valid()) {
user_id_ = UserId();
@@ -34,21 +36,29 @@ UserId Contact::get_user_id() const {
return user_id_;
}
-string Contact::get_phone_number() const {
+const string &Contact::get_phone_number() const {
return phone_number_;
}
+const string &Contact::get_first_name() const {
+ return first_name_;
+}
+
+const string &Contact::get_last_name() const {
+ return last_name_;
+}
+
tl_object_ptr<td_api::contact> Contact::get_contact_object() const {
- return make_tl_object<td_api::contact>(phone_number_, first_name_, last_name_, user_id_.get());
+ return make_tl_object<td_api::contact>(phone_number_, first_name_, last_name_, vcard_, user_id_.get());
}
tl_object_ptr<telegram_api::inputMediaContact> Contact::get_input_media_contact() const {
- return make_tl_object<telegram_api::inputMediaContact>(phone_number_, first_name_, last_name_);
+ return make_tl_object<telegram_api::inputMediaContact>(phone_number_, first_name_, last_name_, vcard_);
}
SecretInputMedia Contact::get_secret_input_media_contact() const {
return SecretInputMedia{nullptr, make_tl_object<secret_api::decryptedMessageMediaContact>(
- phone_number_, first_name_, last_name_, user_id_.get())};
+ phone_number_, first_name_, last_name_, static_cast<int32>(0))};
}
tl_object_ptr<telegram_api::inputPhoneContact> Contact::get_input_phone_contact(int64 client_id) const {
@@ -56,14 +66,18 @@ tl_object_ptr<telegram_api::inputPhoneContact> Contact::get_input_phone_contact(
}
tl_object_ptr<telegram_api::inputBotInlineMessageMediaContact> Contact::get_input_bot_inline_message_media_contact(
- int32 flags, tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) const {
+ tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) const {
+ int32 flags = 0;
+ if (reply_markup != nullptr) {
+ flags |= telegram_api::inputBotInlineMessageMediaContact::REPLY_MARKUP_MASK;
+ }
return make_tl_object<telegram_api::inputBotInlineMessageMediaContact>(flags, phone_number_, first_name_, last_name_,
- std::move(reply_markup));
+ vcard_, std::move(reply_markup));
}
bool operator==(const Contact &lhs, const Contact &rhs) {
- return std::tie(lhs.phone_number_, lhs.first_name_, lhs.last_name_, lhs.user_id_) ==
- std::tie(rhs.phone_number_, rhs.first_name_, rhs.last_name_, rhs.user_id_);
+ return std::tie(lhs.phone_number_, lhs.first_name_, lhs.last_name_, lhs.vcard_, lhs.user_id_) ==
+ std::tie(rhs.phone_number_, rhs.first_name_, rhs.last_name_, rhs.vcard_, rhs.user_id_);
}
bool operator!=(const Contact &lhs, const Contact &rhs) {
@@ -72,8 +86,36 @@ bool operator!=(const Contact &lhs, const Contact &rhs) {
StringBuilder &operator<<(StringBuilder &string_builder, const Contact &contact) {
return string_builder << "Contact[phone_number = " << contact.phone_number_
- << ", first_name = " << contact.first_name_ << ", last_name = " << contact.last_name_ << ", "
- << contact.user_id_ << "]";
+ << ", first_name = " << contact.first_name_ << ", last_name = " << contact.last_name_
+ << ", vCard size = " << contact.vcard_.size() << contact.user_id_ << "]";
+}
+
+Result<Contact> get_contact(td_api::object_ptr<td_api::contact> &&contact) {
+ if (contact == nullptr) {
+ return Status::Error(400, "Contact must be non-empty");
+ }
+
+ if (!clean_input_string(contact->phone_number_)) {
+ return Status::Error(400, "Phone number must be encoded in UTF-8");
+ }
+ if (!clean_input_string(contact->first_name_)) {
+ return Status::Error(400, "First name must be encoded in UTF-8");
+ }
+ if (!clean_input_string(contact->last_name_)) {
+ return Status::Error(400, "Last name must be encoded in UTF-8");
+ }
+ if (!clean_input_string(contact->vcard_)) {
+ return Status::Error(400, "vCard must be encoded in UTF-8");
+ }
+
+ return Contact(std::move(contact->phone_number_), std::move(contact->first_name_), std::move(contact->last_name_),
+ std::move(contact->vcard_), UserId(contact->user_id_));
+}
+
+Result<Contact> process_input_message_contact(tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
+ CHECK(input_message_content != nullptr);
+ CHECK(input_message_content->get_id() == td_api::inputMessageContact::ID);
+ return get_contact(std::move(static_cast<td_api::inputMessageContact *>(input_message_content.get())->contact_));
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Contact.h b/protocols/Telegram/tdlib/td/td/telegram/Contact.h
index 98254cebd6..cf1993119e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Contact.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Contact.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,16 +7,17 @@
#pragma once
#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
+#include "td/telegram/Version.h"
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_helpers.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include <functional>
#include <tuple>
namespace td {
@@ -25,6 +26,7 @@ class Contact {
string phone_number_;
string first_name_;
string last_name_;
+ string vcard_;
UserId user_id_;
friend bool operator==(const Contact &lhs, const Contact &rhs);
@@ -38,13 +40,17 @@ class Contact {
public:
Contact() = default;
- Contact(string phone_number, string first_name, string last_name, int32 user_id);
+ Contact(string phone_number, string first_name, string last_name, string vcard, UserId user_id);
void set_user_id(UserId user_id);
UserId get_user_id() const;
- string get_phone_number() const;
+ const string &get_phone_number() const;
+
+ const string &get_first_name() const;
+
+ const string &get_last_name() const;
tl_object_ptr<td_api::contact> get_contact_object() const;
@@ -54,26 +60,65 @@ class Contact {
tl_object_ptr<telegram_api::inputPhoneContact> get_input_phone_contact(int64 client_id) const;
- // TODO very strange function
tl_object_ptr<telegram_api::inputBotInlineMessageMediaContact> get_input_bot_inline_message_media_contact(
- int32 flags, tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) const;
+ tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) const;
template <class StorerT>
void store(StorerT &storer) const {
using td::store;
+ bool has_first_name = !first_name_.empty();
+ bool has_last_name = !last_name_.empty();
+ bool has_vcard = !vcard_.empty();
+ bool has_user_id = user_id_.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_first_name);
+ STORE_FLAG(has_last_name);
+ STORE_FLAG(has_vcard);
+ STORE_FLAG(has_user_id);
+ END_STORE_FLAGS();
store(phone_number_, storer);
- store(first_name_, storer);
- store(last_name_, storer);
- store(user_id_, storer);
+ if (has_first_name) {
+ store(first_name_, storer);
+ }
+ if (has_last_name) {
+ store(last_name_, storer);
+ }
+ if (has_vcard) {
+ store(vcard_, storer);
+ }
+ if (has_user_id) {
+ store(user_id_, storer);
+ }
}
template <class ParserT>
void parse(ParserT &parser) {
using td::parse;
+ bool has_first_name = true;
+ bool has_last_name = true;
+ bool has_vcard = false;
+ bool has_user_id = true;
+ if (parser.version() >= static_cast<int32>(Version::AddContactVcard)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_first_name);
+ PARSE_FLAG(has_last_name);
+ PARSE_FLAG(has_vcard);
+ PARSE_FLAG(has_user_id);
+ END_PARSE_FLAGS();
+ }
parse(phone_number_, parser);
- parse(first_name_, parser);
- parse(last_name_, parser);
- parse(user_id_, parser);
+ if (has_first_name) {
+ parse(first_name_, parser);
+ }
+ if (has_last_name) {
+ parse(last_name_, parser);
+ }
+ if (has_vcard) {
+ parse(vcard_, parser);
+ }
+ if (has_user_id) {
+ parse(user_id_, parser);
+ }
}
};
@@ -83,19 +128,22 @@ bool operator!=(const Contact &lhs, const Contact &rhs);
StringBuilder &operator<<(StringBuilder &string_builder, const Contact &contact);
struct ContactEqual {
- std::size_t operator()(const Contact &lhs, const Contact &rhs) const {
+ bool operator()(const Contact &lhs, const Contact &rhs) const {
return std::tie(lhs.phone_number_, lhs.first_name_, lhs.last_name_) ==
std::tie(rhs.phone_number_, rhs.first_name_, rhs.last_name_);
}
};
struct ContactHash {
- std::size_t operator()(const Contact &contact) const {
- return (std::hash<std::string>()(contact.phone_number_) * 2023654985u +
- std::hash<std::string>()(contact.first_name_)) *
- 2023654985u +
- std::hash<std::string>()(contact.last_name_);
+ uint32 operator()(const Contact &contact) const {
+ return (Hash<string>()(contact.phone_number_) * 2023654985u + Hash<string>()(contact.first_name_)) * 2023654985u +
+ Hash<string>()(contact.last_name_);
}
};
+Result<Contact> get_contact(td_api::object_ptr<td_api::contact> &&contact) TD_WARN_UNUSED_RESULT;
+
+Result<Contact> process_input_message_contact(tl_object_ptr<td_api::InputMessageContent> &&input_message_content)
+ TD_WARN_UNUSED_RESULT;
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ContactsManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/ContactsManager.cpp
index 44faa4096d..d7632b3c77 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ContactsManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/ContactsManager.cpp
@@ -1,629 +1,440 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/ContactsManager.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-#include "td/telegram/telegram_api.hpp"
-
-#include "td/actor/PromiseFuture.h"
-#include "td/actor/SleepActor.h"
-
-#include "td/db/binlog/BinlogHelper.h"
-
+#include "td/telegram/AnimationsManager.h"
#include "td/telegram/AuthManager.h"
-#include "td/telegram/ConfigShared.h"
+#include "td/telegram/BotMenuButton.h"
+#include "td/telegram/ChannelParticipantFilter.h"
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/Dependencies.h"
+#include "td/telegram/DialogInviteLink.h"
+#include "td/telegram/DialogLocation.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/FileReferenceManager.h"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/FolderId.h"
#include "td/telegram/Global.h"
+#include "td/telegram/GroupCallManager.h"
#include "td/telegram/InlineQueriesManager.h"
+#include "td/telegram/InputGroupCallId.h"
+#include "td/telegram/LinkManager.h"
#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/logevent/LogEventHelper.h"
+#include "td/telegram/MessageSender.h"
#include "td/telegram/MessagesManager.h"
+#include "td/telegram/MessageTtl.h"
+#include "td/telegram/MinChannel.h"
#include "td/telegram/misc.h"
#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/NotificationManager.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/PasswordManager.h"
#include "td/telegram/Photo.h"
#include "td/telegram/Photo.hpp"
-#include "td/telegram/SecretChatActor.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/PremiumGiftOption.hpp"
+#include "td/telegram/SecretChatLayer.h"
+#include "td/telegram/SecretChatsManager.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/StickerSetId.hpp"
#include "td/telegram/StickersManager.h"
#include "td/telegram/Td.h"
-#include "td/telegram/TopDialogManager.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/telegram/telegram_api.hpp"
#include "td/telegram/UpdatesManager.h"
+#include "td/telegram/Version.h"
+
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/SqliteKeyValue.h"
+#include "td/db/SqliteKeyValueAsync.h"
+
+#include "td/actor/SleepActor.h"
+#include "td/utils/algorithm.h"
#include "td/utils/buffer.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Time.h"
#include "td/utils/tl_helpers.h"
+#include "td/utils/utf8.h"
#include <algorithm>
#include <limits>
#include <tuple>
+#include <unordered_map>
+#include <unordered_set>
#include <utility>
namespace td {
-class SetAccountTtlQuery : public Td::ResultHandler {
+class DismissSuggestionQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
+ DialogId dialog_id_;
public:
- explicit SetAccountTtlQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit DismissSuggestionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(int32 account_ttl) {
+ void send(SuggestedAction action) {
+ dialog_id_ = action.dialog_id_;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::account_setAccountTTL(make_tl_object<telegram_api::accountDaysTTL>(account_ttl)))));
+ telegram_api::help_dismissSuggestion(std::move(input_peer), action.get_suggested_action_str())));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_setAccountTTL>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_dismissSuggestion>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
-
- bool result = result_ptr.move_as_ok();
- if (!result) {
- return on_error(id, Status::Error(500, "Internal Server Error"));
+ return on_error(result_ptr.move_as_error());
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DismissSuggestionQuery");
promise_.set_error(std::move(status));
}
};
-class GetAccountTtlQuery : public Td::ResultHandler {
- Promise<int32> promise_;
-
+class GetContactsQuery final : public Td::ResultHandler {
public:
- explicit GetAccountTtlQuery(Promise<int32> &&promise) : promise_(std::move(promise)) {
- }
-
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_getAccountTTL())));
+ void send(int64 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::contacts_getContacts(hash)));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_getAccountTTL>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_getContacts>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetAccountTtlQuery: " << to_string(ptr);
-
- promise_.set_value(std::move(ptr->days_));
+ LOG(INFO) << "Receive result for GetContactsQuery: " << to_string(ptr);
+ td_->contacts_manager_->on_get_contacts(std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- promise_.set_error(std::move(status));
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_contacts_failed(std::move(status));
+ td_->updates_manager_->get_difference("GetContactsQuery");
}
};
-class GetAuthorizationsQuery : public Td::ResultHandler {
- Promise<tl_object_ptr<td_api::sessions>> promise_;
- static constexpr int32 AUTHORIZATION_FLAG_IS_CURRENT = 1 << 0;
- static constexpr int32 AUTHORIZATION_FLAG_IS_OFFICIAL_APPLICATION = 1 << 1;
-
+class GetContactsStatusesQuery final : public Td::ResultHandler {
public:
- explicit GetAuthorizationsQuery(Promise<tl_object_ptr<td_api::sessions>> &&promise) : promise_(std::move(promise)) {
- }
-
void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_getAuthorizations())));
+ send_query(G()->net_query_creator().create(telegram_api::contacts_getStatuses()));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_getAuthorizations>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_getStatuses>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetAuthorizationsQuery: " << to_string(ptr);
-
- auto results = make_tl_object<td_api::sessions>();
- results->sessions_.reserve(ptr->authorizations_.size());
- for (auto &authorization : ptr->authorizations_) {
- CHECK(authorization != nullptr);
- bool is_current = (authorization->flags_ & AUTHORIZATION_FLAG_IS_CURRENT) != 0;
- bool is_official_application = (authorization->flags_ & AUTHORIZATION_FLAG_IS_OFFICIAL_APPLICATION) != 0;
-
- results->sessions_.push_back(make_tl_object<td_api::session>(
- authorization->hash_, is_current, authorization->api_id_, authorization->app_name_,
- authorization->app_version_, is_official_application, authorization->device_model_, authorization->platform_,
- authorization->system_version_, authorization->date_created_, authorization->date_active_, authorization->ip_,
- authorization->country_, authorization->region_));
- }
-
- promise_.set_value(std::move(results));
- }
-
- void on_error(uint64 id, Status status) override {
- promise_.set_error(std::move(status));
- }
-};
-
-class ResetAuthorizationQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
-
- public:
- explicit ResetAuthorizationQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
- }
-
- void send(int64 authorization_id) {
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::account_resetAuthorization(authorization_id))));
+ td_->contacts_manager_->on_get_contacts_statuses(result_ptr.move_as_ok());
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_resetAuthorization>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for GetContactsStatusesQuery: " << status;
}
-
- bool result = result_ptr.move_as_ok();
- LOG_IF(WARNING, !result) << "Failed to terminate session";
- promise_.set_value(Unit());
- }
-
- void on_error(uint64 id, Status status) override {
- promise_.set_error(std::move(status));
}
};
-class ResetAuthorizationsQuery : public Td::ResultHandler {
+class AddContactQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
+ UserId user_id_;
public:
- explicit ResetAuthorizationsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit AddContactQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::auth_resetAuthorizations())));
+ void send(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user, const Contact &contact,
+ bool share_phone_number) {
+ user_id_ = user_id;
+ int32 flags = 0;
+ if (share_phone_number) {
+ flags |= telegram_api::contacts_addContact::ADD_PHONE_PRIVACY_EXCEPTION_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::contacts_addContact(flags, false /*ignored*/, std::move(input_user), contact.get_first_name(),
+ contact.get_last_name(), contact.get_phone_number())));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::auth_resetAuthorizations>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_addContact>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- bool result = result_ptr.move_as_ok();
- LOG_IF(WARNING, !result) << "Failed to terminate all sessions";
- promise_.set_value(Unit());
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for AddContactQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
+ td_->contacts_manager_->reload_contacts(true);
+ td_->messages_manager_->reget_dialog_action_bar(DialogId(user_id_), "AddContactQuery");
}
};
-class GetWebAuthorizationsQuery : public Td::ResultHandler {
- Promise<tl_object_ptr<td_api::connectedWebsites>> promise_;
+class ResolvePhoneQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ string phone_number_;
public:
- explicit GetWebAuthorizationsQuery(Promise<tl_object_ptr<td_api::connectedWebsites>> &&promise)
- : promise_(std::move(promise)) {
+ explicit ResolvePhoneQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_getWebAuthorizations())));
+ void send(const string &phone_number) {
+ phone_number_ = phone_number;
+ send_query(G()->net_query_creator().create(telegram_api::contacts_resolvePhone(phone_number)));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_getWebAuthorizations>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_resolvePhone>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetWebAuthorizationsQuery: " << to_string(ptr);
+ LOG(DEBUG) << "Receive result for ResolvePhoneQuery: " << to_string(ptr);
+ td_->contacts_manager_->on_get_users(std::move(ptr->users_), "ResolvePhoneQuery");
+ td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "ResolvePhoneQuery");
- td->contacts_manager_->on_get_users(std::move(ptr->users_));
-
- auto results = make_tl_object<td_api::connectedWebsites>();
- results->websites_.reserve(ptr->authorizations_.size());
- for (auto &authorization : ptr->authorizations_) {
- CHECK(authorization != nullptr);
- UserId bot_user_id(authorization->bot_id_);
- if (!bot_user_id.is_valid()) {
- LOG(ERROR) << "Receive invalid bot " << bot_user_id;
- bot_user_id = UserId();
- }
-
- results->websites_.push_back(make_tl_object<td_api::connectedWebsite>(
- authorization->hash_, authorization->domain_,
- td->contacts_manager_->get_user_id_object(bot_user_id, "GetWebAuthorizationsQuery"), authorization->browser_,
- authorization->platform_, authorization->date_created_, authorization->date_active_, authorization->ip_,
- authorization->region_));
+ DialogId dialog_id(ptr->peer_);
+ if (dialog_id.get_type() != DialogType::User) {
+ LOG(ERROR) << "Receive " << dialog_id << " by " << phone_number_;
+ return on_error(Status::Error(500, "Receive invalid response"));
}
- promise_.set_value(std::move(results));
- }
-
- void on_error(uint64 id, Status status) override {
- promise_.set_error(std::move(status));
- }
-};
-
-class ResetWebAuthorizationQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
-
- public:
- explicit ResetWebAuthorizationQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
- }
-
- void send(int64 hash) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_resetWebAuthorization(hash))));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_resetWebAuthorization>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
+ td_->contacts_manager_->on_resolved_phone_number(phone_number_, dialog_id.get_user_id());
- bool result = result_ptr.move_as_ok();
- LOG_IF(WARNING, !result) << "Failed to disconnect website";
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ if (status.message() == Slice("PHONE_NOT_OCCUPIED")) {
+ td_->contacts_manager_->on_resolved_phone_number(phone_number_, UserId());
+ return promise_.set_value(Unit());
+ }
promise_.set_error(std::move(status));
}
};
-class ResetWebAuthorizationsQuery : public Td::ResultHandler {
+class AcceptContactQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
+ UserId user_id_;
public:
- explicit ResetWebAuthorizationsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit AcceptContactQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_resetWebAuthorizations())));
+ void send(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user) {
+ user_id_ = user_id;
+ send_query(G()->net_query_creator().create(telegram_api::contacts_acceptContact(std::move(input_user))));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_resetWebAuthorizations>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_acceptContact>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- bool result = result_ptr.move_as_ok();
- LOG_IF(WARNING, !result) << "Failed to disconnect all websites";
- promise_.set_value(Unit());
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for AcceptContactQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
+ td_->contacts_manager_->reload_contacts(true);
+ td_->messages_manager_->reget_dialog_action_bar(DialogId(user_id_), "AcceptContactQuery");
}
};
-class BlockUserQuery : public Td::ResultHandler {
- public:
- void send(tl_object_ptr<telegram_api::InputUser> &&user) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::contacts_block(std::move(user)))));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::contacts_block>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
+class ImportContactsQuery final : public Td::ResultHandler {
+ int64 random_id_ = 0;
+ size_t sent_size_ = 0;
- bool result = result_ptr.ok();
- LOG_IF(WARNING, !result) << "Block user has failed";
- }
-
- void on_error(uint64 id, Status status) override {
- LOG(WARNING) << "Receive error for blockUser: " << status;
- status.ignore();
- }
-};
-
-class UnblockUserQuery : public Td::ResultHandler {
public:
- void send(tl_object_ptr<telegram_api::InputUser> &&user) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::contacts_unblock(std::move(user)))));
+ void send(vector<tl_object_ptr<telegram_api::inputPhoneContact>> &&input_phone_contacts, int64 random_id) {
+ random_id_ = random_id;
+ sent_size_ = input_phone_contacts.size();
+ send_query(G()->net_query_creator().create(telegram_api::contacts_importContacts(std::move(input_phone_contacts))));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::contacts_unblock>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_importContacts>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- bool result = result_ptr.ok();
- LOG_IF(WARNING, !result) << "Unblock user has failed";
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for ImportContactsQuery: " << to_string(ptr);
+ if (sent_size_ == ptr->retry_contacts_.size()) {
+ return on_error(Status::Error(429, "Too Many Requests: retry after 3600"));
+ }
+ td_->contacts_manager_->on_imported_contacts(random_id_, std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- LOG(WARNING) << "Receive error for unblockUser: " << status;
- status.ignore();
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_imported_contacts(random_id_, std::move(status));
}
};
-class GetBlockedUsersQuery : public Td::ResultHandler {
+class DeleteContactsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- int32 offset_;
- int32 limit_;
- int64 random_id_;
public:
- explicit GetBlockedUsersQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit DeleteContactsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(int32 offset, int32 limit, int64 random_id) {
- offset_ = offset;
- limit_ = limit;
- random_id_ = random_id;
-
- send_query(G()->net_query_creator().create(create_storer(telegram_api::contacts_getBlocked(offset, limit))));
+ void send(vector<tl_object_ptr<telegram_api::InputUser>> &&input_users) {
+ send_query(G()->net_query_creator().create(telegram_api::contacts_deleteContacts(std::move(input_users))));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::contacts_getBlocked>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_deleteContacts>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetBlockedUsersQuery: " << to_string(ptr);
-
- int32 constructor_id = ptr->get_id();
- switch (constructor_id) {
- case telegram_api::contacts_blocked::ID: {
- auto blocked_users = move_tl_object_as<telegram_api::contacts_blocked>(ptr);
-
- td->contacts_manager_->on_get_users(std::move(blocked_users->users_));
- td->contacts_manager_->on_get_blocked_users_result(offset_, limit_, random_id_,
- narrow_cast<int32>(blocked_users->blocked_.size()),
- std::move(blocked_users->blocked_));
- break;
- }
- case telegram_api::contacts_blockedSlice::ID: {
- auto blocked_users = move_tl_object_as<telegram_api::contacts_blockedSlice>(ptr);
-
- td->contacts_manager_->on_get_users(std::move(blocked_users->users_));
- td->contacts_manager_->on_get_blocked_users_result(offset_, limit_, random_id_, blocked_users->count_,
- std::move(blocked_users->blocked_));
- break;
- }
- default:
- UNREACHABLE();
- }
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for DeleteContactsQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_failed_get_blocked_users(random_id_);
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
+ td_->contacts_manager_->reload_contacts(true);
}
};
-class GetContactsQuery : public Td::ResultHandler {
- public:
- void send(int32 hash) {
- LOG(INFO) << "Reload contacts with hash " << hash;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::contacts_getContacts(hash))));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::contacts_getContacts>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
-
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for getContacts: " << to_string(ptr);
- td->contacts_manager_->on_get_contacts(std::move(ptr));
- }
-
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_contacts_failed(std::move(status));
- td->updates_manager_->get_difference("GetContactsQuery");
- }
-};
-
-class GetContactsStatusesQuery : public Td::ResultHandler {
- public:
- void send() {
- LOG(INFO) << "Reload contacts statuses";
- send_query(G()->net_query_creator().create(create_storer(telegram_api::contacts_getStatuses())));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::contacts_getStatuses>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
-
- td->contacts_manager_->on_get_contacts_statuses(result_ptr.move_as_ok());
- }
-
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for getContactsStatuses: " << status;
- }
-};
-
-class ImportContactsQuery : public Td::ResultHandler {
+class DeleteContactsByPhoneNumberQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- vector<Contact> input_contacts_;
- vector<UserId> imported_user_ids_;
- vector<int32> unimported_contact_invites_;
- int64 random_id_;
+ vector<UserId> user_ids_;
public:
- explicit ImportContactsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit DeleteContactsByPhoneNumberQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(vector<Contact> input_contacts, int64 random_id) {
- random_id_ = random_id;
-
- size_t size = input_contacts.size();
- if (size == 0) {
- td->contacts_manager_->on_imported_contacts(random_id, std::move(imported_user_ids_),
- std::move(unimported_contact_invites_));
- promise_.set_value(Unit());
- return;
- }
-
- imported_user_ids_.resize(size);
- unimported_contact_invites_.resize(size);
- input_contacts_ = std::move(input_contacts);
-
- vector<tl_object_ptr<telegram_api::inputPhoneContact>> contacts;
- contacts.reserve(size);
- for (size_t i = 0; i < size; i++) {
- contacts.push_back(input_contacts_[i].get_input_phone_contact(static_cast<int64>(i)));
+ void send(vector<string> &&user_phone_numbers, vector<UserId> &&user_ids) {
+ if (user_phone_numbers.empty()) {
+ return promise_.set_value(Unit());
}
-
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::contacts_importContacts(std::move(contacts)))));
+ user_ids_ = std::move(user_ids);
+ send_query(G()->net_query_creator().create(telegram_api::contacts_deleteByPhones(std::move(user_phone_numbers))));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::contacts_importContacts>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_deleteByPhones>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for importContacts: " << to_string(ptr);
-
- td->contacts_manager_->on_get_users(std::move(ptr->users_));
- for (auto &imported_contact : ptr->imported_) {
- int64 client_id = imported_contact->client_id_;
- if (client_id < 0 || client_id >= static_cast<int64>(imported_user_ids_.size())) {
- LOG(ERROR) << "Wrong client_id " << client_id << " returned";
- continue;
- }
-
- imported_user_ids_[static_cast<size_t>(client_id)] = UserId(imported_contact->user_id_);
- }
- for (auto &popular_contact : ptr->popular_invites_) {
- int64 client_id = popular_contact->client_id_;
- if (client_id < 0 || client_id >= static_cast<int64>(unimported_contact_invites_.size())) {
- LOG(ERROR) << "Wrong client_id " << client_id << " returned";
- continue;
- }
- if (popular_contact->importers_ < 0) {
- LOG(ERROR) << "Wrong number of importers " << popular_contact->importers_ << " returned";
- continue;
- }
-
- unimported_contact_invites_[static_cast<size_t>(client_id)] = popular_contact->importers_;
- }
-
- if (!ptr->retry_contacts_.empty()) {
- int64 total_size = static_cast<int64>(input_contacts_.size());
- vector<tl_object_ptr<telegram_api::inputPhoneContact>> contacts;
- contacts.reserve(ptr->retry_contacts_.size());
- for (auto &client_id : ptr->retry_contacts_) {
- if (client_id < 0 || client_id >= total_size) {
- LOG(ERROR) << "Wrong client_id " << client_id << " returned";
- continue;
- }
- size_t i = static_cast<size_t>(client_id);
- contacts.push_back(input_contacts_[i].get_input_phone_contact(client_id));
- }
-
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::contacts_importContacts(std::move(contacts)))));
- return;
+ bool result = result_ptr.ok();
+ if (!result) {
+ return on_error(Status::Error(500, "Some contacts can't be deleted"));
}
- td->contacts_manager_->on_imported_contacts(random_id_, std::move(imported_user_ids_),
- std::move(unimported_contact_invites_));
+ td_->contacts_manager_->on_deleted_contacts(user_ids_);
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
- td->contacts_manager_->reload_contacts(true);
+ td_->contacts_manager_->reload_contacts(true);
}
};
-class DeleteContactsQuery : public Td::ResultHandler {
+class ResetContactsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- vector<UserId> user_ids_;
public:
- explicit DeleteContactsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ResetContactsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(vector<UserId> &&user_ids, vector<tl_object_ptr<telegram_api::InputUser>> &&input_users) {
- user_ids_ = std::move(user_ids);
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::contacts_deleteContacts(std::move(input_users)))));
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::contacts_resetSaved()));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::contacts_deleteContacts>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_resetSaved>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
if (!result) {
- return on_error(id, Status::Error(500, "Some contacts can't be deleted"));
+ LOG(ERROR) << "Failed to delete imported contacts";
+ td_->contacts_manager_->reload_contacts(true);
+ } else {
+ td_->contacts_manager_->on_update_contacts_reset();
}
- td->contacts_manager_->on_deleted_contacts(user_ids_);
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
- td->contacts_manager_->reload_contacts(true);
+ td_->contacts_manager_->reload_contacts(true);
}
};
-class ResetContactsQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class SearchDialogsNearbyQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::Updates>> promise_;
public:
- explicit ResetContactsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit SearchDialogsNearbyQuery(Promise<tl_object_ptr<telegram_api::Updates>> &&promise)
+ : promise_(std::move(promise)) {
}
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::contacts_resetSaved())));
+ void send(const Location &location, bool from_background, int32 expire_date) {
+ int32 flags = 0;
+ if (from_background) {
+ flags |= telegram_api::contacts_getLocated::BACKGROUND_MASK;
+ }
+ if (expire_date != -1) {
+ flags |= telegram_api::contacts_getLocated::SELF_EXPIRES_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::contacts_getLocated(flags, false /*ignored*/, location.get_input_geo_point(), expire_date)));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::contacts_resetSaved>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_getLocated>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- bool result = result_ptr.ok();
- if (!result) {
- LOG(ERROR) << "Failed to delete imported contacts";
- td->contacts_manager_->reload_contacts(true);
- } else {
- td->contacts_manager_->on_update_contacts_reset();
- }
-
- promise_.set_value(Unit());
+ promise_.set_value(result_ptr.move_as_ok());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
- td->contacts_manager_->reload_contacts(true);
}
};
-class UploadProfilePhotoQuery : public Td::ResultHandler {
+class UploadProfilePhotoQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
FileId file_id_;
@@ -631,69 +442,109 @@ class UploadProfilePhotoQuery : public Td::ResultHandler {
explicit UploadProfilePhotoQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(FileId file_id, tl_object_ptr<telegram_api::InputFile> &&input_file) {
+ void send(FileId file_id, tl_object_ptr<telegram_api::InputFile> &&input_file, bool is_animation,
+ double main_frame_timestamp) {
CHECK(input_file != nullptr);
CHECK(file_id.is_valid());
file_id_ = file_id;
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::photos_uploadProfilePhoto(std::move(input_file)))));
+ int32 flags = 0;
+ tl_object_ptr<telegram_api::InputFile> photo_input_file;
+ tl_object_ptr<telegram_api::InputFile> video_input_file;
+ if (is_animation) {
+ flags |= telegram_api::photos_uploadProfilePhoto::VIDEO_MASK;
+ video_input_file = std::move(input_file);
+
+ if (main_frame_timestamp != 0.0) {
+ flags |= telegram_api::photos_uploadProfilePhoto::VIDEO_START_TS_MASK;
+ }
+ } else {
+ flags |= telegram_api::photos_uploadProfilePhoto::FILE_MASK;
+ photo_input_file = std::move(input_file);
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::photos_uploadProfilePhoto(flags, std::move(photo_input_file), std::move(video_input_file),
+ main_frame_timestamp),
+ {{"me"}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::photos_uploadProfilePhoto>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for uploadProfilePhoto: " << to_string(ptr);
- td->contacts_manager_->on_get_users(std::move(ptr->users_));
+ td_->contacts_manager_->on_set_profile_photo(result_ptr.move_as_ok(), 0);
- td->file_manager_->delete_partial_remote_location(file_id_);
+ td_->file_manager_->delete_partial_remote_location(file_id_);
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
- td->file_manager_->delete_partial_remote_location(file_id_);
- td->updates_manager_->get_difference("UploadProfilePhotoQuery");
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+ td_->updates_manager_->get_difference("UploadProfilePhotoQuery");
}
};
-class UpdateProfilePhotoQuery : public Td::ResultHandler {
+class UpdateProfilePhotoQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
+ FileId file_id_;
+ int64 old_photo_id_;
+ string file_reference_;
public:
explicit UpdateProfilePhotoQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(tl_object_ptr<telegram_api::InputPhoto> &&input_photo) {
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::photos_updateProfilePhoto(std::move(input_photo)))));
+ void send(FileId file_id, int64 old_photo_id, tl_object_ptr<telegram_api::InputPhoto> &&input_photo) {
+ CHECK(input_photo != nullptr);
+ file_id_ = file_id;
+ old_photo_id_ = old_photo_id;
+ file_reference_ = FileManager::extract_file_reference(input_photo);
+ send_query(
+ G()->net_query_creator().create(telegram_api::photos_updateProfilePhoto(std::move(input_photo)), {{"me"}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::photos_updateProfilePhoto>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- LOG(DEBUG) << "Receive result for updateProfilePhoto " << to_string(result_ptr.ok());
- td->contacts_manager_->on_update_user_photo(td->contacts_manager_->get_my_id("UpdateProfilePhotoQuery"),
- result_ptr.move_as_ok());
+ td_->contacts_manager_->on_set_profile_photo(result_ptr.move_as_ok(), old_photo_id_);
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ if (file_id_.is_valid()) {
+ VLOG(file_references) << "Receive " << status << " for " << file_id_;
+ td_->file_manager_->delete_file_reference(file_id_, file_reference_);
+ td_->file_reference_manager_->repair_file_reference(
+ file_id_, PromiseCreator::lambda([file_id = file_id_, old_photo_id = old_photo_id_,
+ promise = std::move(promise_)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(Status::Error(400, "Can't find the photo"));
+ }
+
+ send_closure(G()->contacts_manager(), &ContactsManager::send_update_profile_photo_query, file_id,
+ old_photo_id, std::move(promise));
+ }));
+ return;
+ } else {
+ LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_;
+ }
+ }
+
promise_.set_error(std::move(status));
}
};
-class DeleteProfilePhotoQuery : public Td::ResultHandler {
+class DeleteProfilePhotoQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
int64 profile_photo_id_;
@@ -704,33 +555,32 @@ class DeleteProfilePhotoQuery : public Td::ResultHandler {
void send(int64 profile_photo_id) {
profile_photo_id_ = profile_photo_id;
vector<tl_object_ptr<telegram_api::InputPhoto>> input_photo_ids;
- input_photo_ids.push_back(make_tl_object<telegram_api::inputPhoto>(profile_photo_id, 0));
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::photos_deletePhotos(std::move(input_photo_ids)))));
+ input_photo_ids.push_back(make_tl_object<telegram_api::inputPhoto>(profile_photo_id, 0, BufferSlice()));
+ send_query(G()->net_query_creator().create(telegram_api::photos_deletePhotos(std::move(input_photo_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::photos_deletePhotos>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for deleteProfilePhoto " << format::as_array(result);
+ LOG(INFO) << "Receive result for DeleteProfilePhotoQuery: " << format::as_array(result);
if (result.size() != 1u) {
LOG(WARNING) << "Photo can't be deleted";
- return on_error(id, Status::Error(7, "Photo can't be deleted"));
+ return on_error(Status::Error(400, "Photo can't be deleted"));
}
- td->contacts_manager_->on_delete_profile_photo(profile_photo_id_, std::move(promise_));
+ td_->contacts_manager_->on_delete_profile_photo(profile_photo_id_, std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class UpdateProfileQuery : public Td::ResultHandler {
+class UpdateProfileQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
int32 flags_;
string first_name_;
@@ -746,29 +596,29 @@ class UpdateProfileQuery : public Td::ResultHandler {
first_name_ = first_name;
last_name_ = last_name;
about_ = about;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::account_updateProfile(flags, first_name, last_name, about))));
+ send_query(G()->net_query_creator().create(telegram_api::account_updateProfile(flags, first_name, last_name, about),
+ {{"me"}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::account_updateProfile>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- LOG(DEBUG) << "Receive result for updateProfile " << to_string(result_ptr.ok());
- td->contacts_manager_->on_get_user(result_ptr.move_as_ok());
- td->contacts_manager_->on_update_profile_success(flags_, first_name_, last_name_, about_);
+ LOG(DEBUG) << "Receive result for UpdateProfileQuery: " << to_string(result_ptr.ok());
+ td_->contacts_manager_->on_get_user(result_ptr.move_as_ok(), "UpdateProfileQuery");
+ td_->contacts_manager_->on_update_profile_success(flags_, first_name_, last_name_, about_);
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class CheckUsernameQuery : public Td::ResultHandler {
+class CheckUsernameQuery final : public Td::ResultHandler {
Promise<bool> promise_;
public:
@@ -776,24 +626,24 @@ class CheckUsernameQuery : public Td::ResultHandler {
}
void send(const string &username) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_checkUsername(username))));
+ send_query(G()->net_query_creator().create(telegram_api::account_checkUsername(username), {{"me"}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::account_checkUsername>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
promise_.set_value(result_ptr.move_as_ok());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class UpdateUsernameQuery : public Td::ResultHandler {
+class UpdateUsernameQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -801,22 +651,22 @@ class UpdateUsernameQuery : public Td::ResultHandler {
}
void send(const string &username) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_updateUsername(username))));
+ send_query(G()->net_query_creator().create(telegram_api::account_updateUsername(username), {{"me"}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::account_updateUsername>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- LOG(DEBUG) << "Receive result for updateUsername " << to_string(result_ptr.ok());
- td->contacts_manager_->on_get_user(result_ptr.move_as_ok());
+ LOG(DEBUG) << "Receive result for UpdateUsernameQuery: " << to_string(result_ptr.ok());
+ td_->contacts_manager_->on_get_user(result_ptr.move_as_ok(), "UpdateUsernameQuery");
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- if (status.message() == "USERNAME_NOT_MODIFIED" && !td->auth_manager_->is_bot()) {
+ void on_error(Status status) final {
+ if (status.message() == "USERNAME_NOT_MODIFIED" && !td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
@@ -824,41 +674,111 @@ class UpdateUsernameQuery : public Td::ResultHandler {
}
};
-class ToggleChatAdminsQuery : public Td::ResultHandler {
+class ToggleUsernameQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
+ string username_;
+ bool is_active_;
public:
- explicit ToggleChatAdminsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ToggleUsernameQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChatId chat_id, bool everyone_is_administrator) {
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_toggleChatAdmins(chat_id.get(), !everyone_is_administrator))));
+ void send(string &&username, bool is_active) {
+ username_ = std::move(username);
+ is_active_ = is_active;
+ send_query(G()->net_query_creator().create(telegram_api::account_toggleUsername(username_, is_active_), {{"me"}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_toggleChatAdmins>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_toggleUsername>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for toggleChatAdmins: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
+ bool result = result_ptr.ok();
+ LOG(DEBUG) << "Receive result for ToggleUsernameQuery: " << result;
+ td_->contacts_manager_->on_update_username_is_active(std::move(username_), is_active_, std::move(promise_));
+ }
- promise_.set_value(Unit());
+ void on_error(Status status) final {
+ if (status.message() == "USERNAME_NOT_MODIFIED") {
+ td_->contacts_manager_->on_update_username_is_active(std::move(username_), is_active_, std::move(promise_));
+ return;
+ }
+ promise_.set_error(std::move(status));
}
+};
- void on_error(uint64 id, Status status) override {
- if (status.message() == "CHAT_NOT_MODIFIED" && !td->auth_manager_->is_bot()) {
- promise_.set_value(Unit());
+class ReorderUsernamesQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ vector<string> usernames_;
+
+ public:
+ explicit ReorderUsernamesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(vector<string> &&usernames) {
+ usernames_ = usernames;
+ send_query(G()->net_query_creator().create(telegram_api::account_reorderUsernames(std::move(usernames)), {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_reorderUsernames>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ LOG(DEBUG) << "Receive result for ReorderUsernamesQuery: " << result;
+ if (!result) {
+ return on_error(Status::Error(500, "Usernames weren't updated"));
+ }
+
+ td_->contacts_manager_->on_update_active_usernames_order(std::move(usernames_), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "USERNAME_NOT_MODIFIED") {
+ td_->contacts_manager_->on_update_active_usernames_order(std::move(usernames_), std::move(promise_));
return;
}
promise_.set_error(std::move(status));
}
};
-class CheckChannelUsernameQuery : public Td::ResultHandler {
+class UpdateEmojiStatusQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit UpdateEmojiStatusQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(EmojiStatus emoji_status) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::account_updateEmojiStatus(emoji_status.get_input_emoji_status()), {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_updateEmojiStatus>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ LOG(DEBUG) << "Receive result for UpdateEmojiStatusQuery: " << result_ptr.ok();
+ if (result_ptr.ok()) {
+ promise_.set_value(Unit());
+ } else {
+ promise_.set_error(Status::Error(400, "Failed to change Premium badge"));
+ }
+ }
+
+ void on_error(Status status) final {
+ get_recent_emoji_statuses(td_, Auto());
+ promise_.set_error(std::move(status));
+ }
+};
+
+class CheckChannelUsernameQuery final : public Td::ResultHandler {
Promise<bool> promise_;
ChannelId channel_id_;
string username_;
@@ -871,33 +791,33 @@ class CheckChannelUsernameQuery : public Td::ResultHandler {
channel_id_ = channel_id;
tl_object_ptr<telegram_api::InputChannel> input_channel;
if (channel_id.is_valid()) {
- input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ input_channel = td_->contacts_manager_->get_input_channel(channel_id);
} else {
input_channel = make_tl_object<telegram_api::inputChannelEmpty>();
}
CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_checkUsername(std::move(input_channel), username))));
+ send_query(
+ G()->net_query_creator().create(telegram_api::channels_checkUsername(std::move(input_channel), username)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_checkUsername>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
promise_.set_value(result_ptr.move_as_ok());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (channel_id_.is_valid()) {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "CheckChannelUsernameQuery");
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "CheckChannelUsernameQuery");
}
promise_.set_error(std::move(status));
}
};
-class UpdateChannelUsernameQuery : public Td::ResultHandler {
+class UpdateChannelUsernameQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
string username_;
@@ -909,362 +829,739 @@ class UpdateChannelUsernameQuery : public Td::ResultHandler {
void send(ChannelId channel_id, const string &username) {
channel_id_ = channel_id;
username_ = username;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_updateUsername(std::move(input_channel), username))));
+ telegram_api::channels_updateUsername(std::move(input_channel), username), {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_updateUsername>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
- LOG(DEBUG) << "Receive result for updateChannelUsername " << result;
+ LOG(DEBUG) << "Receive result for UpdateChannelUsernameQuery: " << result;
if (!result) {
- return on_error(id, Status::Error(500, "Supergroup username is not updated"));
+ return on_error(Status::Error(500, "Supergroup username is not updated"));
}
- td->contacts_manager_->on_update_channel_username(channel_id_, std::move(username_));
+ td_->contacts_manager_->on_update_channel_editable_username(channel_id_, std::move(username_));
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") {
- td->contacts_manager_->on_update_channel_username(channel_id_, std::move(username_));
- if (!td->auth_manager_->is_bot()) {
+ td_->contacts_manager_->on_update_channel_editable_username(channel_id_, std::move(username_));
+ if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelUsernameQuery");
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelUsernameQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleChannelUsernameQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ ChannelId channel_id_;
+ string username_;
+ bool is_active_;
+
+ public:
+ explicit ToggleChannelUsernameQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, string &&username, bool is_active) {
+ channel_id_ = channel_id;
+ username_ = std::move(username);
+ is_active_ = is_active;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_toggleUsername(std::move(input_channel), username_, is_active_), {{channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_toggleUsername>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ LOG(DEBUG) << "Receive result for ToggleChannelUsernameQuery: " << result;
+ td_->contacts_manager_->on_update_channel_username_is_active(channel_id_, std::move(username_), is_active_,
+ std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") {
+ td_->contacts_manager_->on_update_channel_username_is_active(channel_id_, std::move(username_), is_active_,
+ std::move(promise_));
+ return;
+ } else {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelUsernameQuery");
}
promise_.set_error(std::move(status));
}
};
-class SetChannelStickerSetQuery : public Td::ResultHandler {
+class DeactivateAllChannelUsernamesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
- int64 sticker_set_id_;
+
+ public:
+ explicit DeactivateAllChannelUsernamesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id) {
+ channel_id_ = channel_id;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::channels_deactivateAllUsernames(std::move(input_channel)),
+ {{channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_deactivateAllUsernames>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ LOG(DEBUG) << "Receive result for DeactivateAllChannelUsernamesQuery: " << result;
+ td_->contacts_manager_->on_deactivate_channel_usernames(channel_id_, std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") {
+ td_->contacts_manager_->on_deactivate_channel_usernames(channel_id_, std::move(promise_));
+ return;
+ } else {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeactivateAllChannelUsernamesQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ReorderChannelUsernamesQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ ChannelId channel_id_;
+ vector<string> usernames_;
+
+ public:
+ explicit ReorderChannelUsernamesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, vector<string> &&usernames) {
+ channel_id_ = channel_id;
+ usernames_ = usernames;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_reorderUsernames(std::move(input_channel), std::move(usernames)), {{channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_reorderUsernames>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ LOG(DEBUG) << "Receive result for ReorderChannelUsernamesQuery: " << result;
+ if (!result) {
+ return on_error(Status::Error(500, "Supergroup usernames weren't updated"));
+ }
+
+ td_->contacts_manager_->on_update_channel_active_usernames_order(channel_id_, std::move(usernames_),
+ std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "USERNAME_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") {
+ td_->contacts_manager_->on_update_channel_active_usernames_order(channel_id_, std::move(usernames_),
+ std::move(promise_));
+ return;
+ } else {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReorderChannelUsernamesQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetChannelStickerSetQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ ChannelId channel_id_;
+ StickerSetId sticker_set_id_;
public:
explicit SetChannelStickerSetQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, int64 sticker_set_id,
+ void send(ChannelId channel_id, StickerSetId sticker_set_id,
telegram_api::object_ptr<telegram_api::InputStickerSet> &&input_sticker_set) {
channel_id_ = channel_id;
sticker_set_id_ = sticker_set_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_setStickers(std::move(input_channel), std::move(input_sticker_set)))));
+ telegram_api::channels_setStickers(std::move(input_channel), std::move(input_sticker_set)), {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_setStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
- LOG(DEBUG) << "Receive result for setChannelStickerSet " << result;
+ LOG(DEBUG) << "Receive result for SetChannelStickerSetQuery: " << result;
if (!result) {
- return on_error(id, Status::Error(500, "Supergroup sticker set not updated"));
+ return on_error(Status::Error(500, "Supergroup sticker set not updated"));
}
- td->contacts_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_);
+ td_->contacts_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_);
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
- td->contacts_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_);
- if (!td->auth_manager_->is_bot()) {
+ td_->contacts_manager_->on_update_channel_sticker_set(channel_id_, sticker_set_id_);
+ if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "SetChannelStickerSetQuery");
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "SetChannelStickerSetQuery");
}
promise_.set_error(std::move(status));
}
};
-class ToggleChannelInvitesQuery : public Td::ResultHandler {
+class ToggleChannelSignaturesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
public:
- explicit ToggleChannelInvitesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ToggleChannelSignaturesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, bool anyone_can_invite) {
+ void send(ChannelId channel_id, bool sign_messages) {
channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_toggleInvites(std::move(input_channel), anyone_can_invite))));
+ telegram_api::channels_toggleSignatures(std::move(input_channel), sign_messages), {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::channels_toggleInvites>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_toggleSignatures>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for toggleChannelInvites: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for ToggleChannelSignaturesQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
- if (!td->auth_manager_->is_bot()) {
+ if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelInvitesQuery");
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelSignaturesQuery");
}
promise_.set_error(std::move(status));
}
};
-class ToggleChannelSignaturesQuery : public Td::ResultHandler {
+class ToggleChannelJoinToSendQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
public:
- explicit ToggleChannelSignaturesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ToggleChannelJoinToSendQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, bool sign_messages) {
+ void send(ChannelId channel_id, bool join_to_send) {
channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_toggleSignatures(std::move(input_channel), sign_messages))));
+ telegram_api::channels_toggleJoinToSend(std::move(input_channel), join_to_send), {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::channels_toggleSignatures>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_toggleJoinToSend>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for toggleChannelSignatures: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
+ LOG(INFO) << "Receive result for ToggleChannelJoinToSendQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
- promise_.set_value(Unit());
+ void on_error(Status status) final {
+ if (status.message() == "CHAT_NOT_MODIFIED") {
+ if (!td_->auth_manager_->is_bot()) {
+ promise_.set_value(Unit());
+ return;
+ }
+ } else {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinToSendQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleChannelJoinRequestQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ ChannelId channel_id_;
+
+ public:
+ explicit ToggleChannelJoinRequestQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, bool join_request) {
+ channel_id_ = channel_id;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_toggleJoinRequest(std::move(input_channel), join_request), {{channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_toggleJoinRequest>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for ToggleChannelJoinRequestQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
- if (!td->auth_manager_->is_bot()) {
+ if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelSignaturesQuery");
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelJoinRequestQuery");
}
promise_.set_error(std::move(status));
}
};
-class ToggleChannelIsAllHistoryAvailableQuery : public Td::ResultHandler {
+class TogglePrehistoryHiddenQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
bool is_all_history_available_;
public:
- explicit ToggleChannelIsAllHistoryAvailableQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit TogglePrehistoryHiddenQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(ChannelId channel_id, bool is_all_history_available) {
channel_id_ = channel_id;
is_all_history_available_ = is_all_history_available;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::channels_togglePreHistoryHidden(std::move(input_channel), !is_all_history_available))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_togglePreHistoryHidden(std::move(input_channel), !is_all_history_available),
+ {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_togglePreHistoryHidden>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for togglePreHistoryHidden: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
- td->contacts_manager_->on_update_channel_is_all_history_available(channel_id_, is_all_history_available_);
+ LOG(INFO) << "Receive result for TogglePrehistoryHiddenQuery: " << to_string(ptr);
+
+ td_->updates_manager_->on_get_updates(
+ std::move(ptr),
+ PromiseCreator::lambda([actor_id = G()->contacts_manager(), promise = std::move(promise_),
+ channel_id = channel_id_,
+ is_all_history_available = is_all_history_available_](Unit result) mutable {
+ send_closure(actor_id, &ContactsManager::on_update_channel_is_all_history_available, channel_id,
+ is_all_history_available, std::move(promise));
+ }));
+ }
- promise_.set_value(Unit());
+ void on_error(Status status) final {
+ if (status.message() == "CHAT_NOT_MODIFIED") {
+ if (!td_->auth_manager_->is_bot()) {
+ promise_.set_value(Unit());
+ return;
+ }
+ } else {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "TogglePrehistoryHiddenQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleForumQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ ChannelId channel_id_;
+
+ public:
+ explicit ToggleForumQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, bool is_forum) {
+ channel_id_ = channel_id;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::channels_toggleForum(std::move(input_channel), is_forum),
+ {{channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_toggleForum>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for ToggleForumQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
- if (!td->auth_manager_->is_bot()) {
+ if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleChannelIsAllHistoryAvailableQuery");
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleForumQuery");
}
promise_.set_error(std::move(status));
}
};
-class EditChannelAboutQuery : public Td::ResultHandler {
+class ConvertToGigagroupQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
- string about_;
public:
- explicit EditChannelAboutQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ConvertToGigagroupQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, const string &about) {
+ void send(ChannelId channel_id) {
channel_id_ = channel_id;
- about_ = about;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_editAbout(std::move(input_channel), about))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_convertToGigagroup(std::move(input_channel)),
+ {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::channels_editAbout>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_convertToGigagroup>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for ConvertToGigagroupQuery: " << to_string(ptr);
+
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "CHAT_NOT_MODIFIED") {
+ promise_.set_value(Unit());
+ return;
+ } else {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ConvertToGigagroupQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class EditChatAboutQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+ string about_;
+
+ void on_success() {
+ switch (dialog_id_.get_type()) {
+ case DialogType::Chat:
+ return td_->contacts_manager_->on_update_chat_description(dialog_id_.get_chat_id(), std::move(about_));
+ case DialogType::Channel:
+ return td_->contacts_manager_->on_update_channel_description(dialog_id_.get_channel_id(), std::move(about_));
+ case DialogType::User:
+ case DialogType::SecretChat:
+ case DialogType::None:
+ UNREACHABLE();
+ }
+ }
+
+ public:
+ explicit EditChatAboutQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &about) {
+ dialog_id_ = dialog_id;
+ about_ = about;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_editChatAbout(std::move(input_peer), about),
+ {{dialog_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_editChatAbout>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
- LOG(DEBUG) << "Receive result for editChannelAbout " << result;
+ LOG(DEBUG) << "Receive result for EditChatAboutQuery: " << result;
if (!result) {
- return on_error(id, Status::Error(500, "Supergroup description is not updated"));
+ return on_error(Status::Error(500, "Chat description is not updated"));
}
- td->contacts_manager_->on_update_channel_description(channel_id_, std::move(about_));
+ on_success();
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "CHAT_ABOUT_NOT_MODIFIED" || status.message() == "CHAT_NOT_MODIFIED") {
- td->contacts_manager_->on_update_channel_description(channel_id_, std::move(about_));
- if (!td->auth_manager_->is_bot()) {
+ on_success();
+ if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "EditChannelAboutQuery");
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditChatAboutQuery");
}
promise_.set_error(std::move(status));
}
};
-class UpdateChannelPinnedMessageQuery : public Td::ResultHandler {
+class SetDiscussionGroupQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ ChannelId broadcast_channel_id_;
+ ChannelId group_channel_id_;
+
+ public:
+ explicit SetDiscussionGroupQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId broadcast_channel_id,
+ telegram_api::object_ptr<telegram_api::InputChannel> broadcast_input_channel, ChannelId group_channel_id,
+ telegram_api::object_ptr<telegram_api::InputChannel> group_input_channel) {
+ broadcast_channel_id_ = broadcast_channel_id;
+ group_channel_id_ = group_channel_id;
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_setDiscussionGroup(std::move(broadcast_input_channel), std::move(group_input_channel)),
+ {{broadcast_channel_id}, {group_channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_setDiscussionGroup>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(INFO, !result) << "Set discussion group has failed";
+
+ td_->contacts_manager_->on_update_channel_linked_channel_id(broadcast_channel_id_, group_channel_id_);
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "LINK_NOT_MODIFIED") {
+ return promise_.set_value(Unit());
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class EditLocationQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
- MessageId message_id_;
+ DialogLocation location_;
public:
- explicit UpdateChannelPinnedMessageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit EditLocationQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, MessageId message_id, bool disable_notification) {
+ void send(ChannelId channel_id, const DialogLocation &location) {
channel_id_ = channel_id;
- message_id_ = message_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ location_ = location;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- int32 flags = 0;
- if (disable_notification) {
- flags |= telegram_api::channels_updatePinnedMessage::SILENT_MASK;
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_editLocation(std::move(input_channel), location_.get_input_geo_point(),
+ location_.get_address()),
+ {{channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_editLocation>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_updatePinnedMessage(
- flags, false /*ignored*/, std::move(input_channel), message_id.get_server_message_id().get()))));
+ bool result = result_ptr.move_as_ok();
+ LOG_IF(INFO, !result) << "Edit chat location has failed";
+
+ td_->contacts_manager_->on_update_channel_location(channel_id_, location_);
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditLocationQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleSlowModeQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ ChannelId channel_id_;
+ int32 slow_mode_delay_ = 0;
+
+ public:
+ explicit ToggleSlowModeQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, int32 slow_mode_delay) {
+ channel_id_ = channel_id;
+ slow_mode_delay_ = slow_mode_delay;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_toggleSlowMode(std::move(input_channel), slow_mode_delay), {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::channels_updatePinnedMessage>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_toggleSlowMode>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for updateChannelPinnedMessage: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for ToggleSlowModeQuery: " << to_string(ptr);
+
+ td_->updates_manager_->on_get_updates(
+ std::move(ptr),
+ PromiseCreator::lambda([actor_id = G()->contacts_manager(), promise = std::move(promise_),
+ channel_id = channel_id_, slow_mode_delay = slow_mode_delay_](Unit result) mutable {
+ send_closure(actor_id, &ContactsManager::on_update_channel_slow_mode_delay, channel_id, slow_mode_delay,
+ std::move(promise));
+ }));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
- td->contacts_manager_->on_update_channel_pinned_message(channel_id_, message_id_);
- if (!td->auth_manager_->is_bot()) {
+ td_->contacts_manager_->on_update_channel_slow_mode_delay(channel_id_, slow_mode_delay_, Promise<Unit>());
+ if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "UpdateChannelPinnedMessageQuery");
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ToggleSlowModeQuery");
}
promise_.set_error(std::move(status));
}
};
-class ReportChannelSpamQuery : public Td::ResultHandler {
+class ReportChannelSpamQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
+ DialogId sender_dialog_id_;
public:
explicit ReportChannelSpamQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, UserId user_id, const vector<MessageId> &message_ids) {
- LOG(INFO) << "Send reportChannelSpamQuery in " << channel_id << " with messages " << format::as_array(message_ids)
- << " and " << user_id;
+ void send(ChannelId channel_id, DialogId sender_dialog_id, const vector<MessageId> &message_ids) {
channel_id_ = channel_id;
+ sender_dialog_id_ = sender_dialog_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- auto input_user = td->contacts_manager_->get_input_user(user_id);
- CHECK(input_user != nullptr);
+ auto input_peer = td_->messages_manager_->get_input_peer(sender_dialog_id, AccessRights::Know);
+ CHECK(input_peer != nullptr);
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_reportSpam(
- std::move(input_channel), std::move(input_user), MessagesManager::get_server_message_ids(message_ids)))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_reportSpam(
+ std::move(input_channel), std::move(input_peer), MessagesManager::get_server_message_ids(message_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_reportSpam>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
- LOG_IF(INFO, !result) << "Report spam has failed";
+ LOG_IF(INFO, !result) << "Report spam has failed in " << channel_id_;
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "ReportChannelSpamQuery");
- status.ignore();
+ void on_error(Status status) final {
+ if (sender_dialog_id_.get_type() != DialogType::Channel) {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReportChannelSpamQuery");
+ }
+ promise_.set_error(std::move(status));
}
};
-class DeleteChannelQuery : public Td::ResultHandler {
+class DeleteChatQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit DeleteChatQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChatId chat_id) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_deleteChat(chat_id.get()), {{chat_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_deleteChat>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ LOG(INFO) << "Receive result for DeleteChatQuery: " << result_ptr.ok();
+ td_->updates_manager_->get_difference("DeleteChatQuery");
+ td_->updates_manager_->on_get_updates(make_tl_object<telegram_api::updates>(), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class DeleteChannelQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
@@ -1274,32 +1571,30 @@ class DeleteChannelQuery : public Td::ResultHandler {
void send(ChannelId channel_id) {
channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::channels_deleteChannel(std::move(input_channel)))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_deleteChannel(std::move(input_channel)),
+ {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_deleteChannel>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for deleteChannel: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for DeleteChannelQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelQuery");
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelQuery");
promise_.set_error(std::move(status));
}
};
-class AddChatUserQuery : public Td::ResultHandler {
+class AddChatUserQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -1308,29 +1603,27 @@ class AddChatUserQuery : public Td::ResultHandler {
void send(ChatId chat_id, tl_object_ptr<telegram_api::InputUser> &&input_user, int32 forward_limit) {
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_addChatUser(chat_id.get(), std::move(input_user), forward_limit))));
+ telegram_api::messages_addChatUser(chat_id.get(), std::move(input_user), forward_limit)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_addChatUser>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for addChatUser: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for AddChatUserQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("AddChatUserQuery");
+ td_->updates_manager_->get_difference("AddChatUserQuery");
}
};
-class EditChatAdminQuery : public Td::ResultHandler {
+class EditChatAdminQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChatId chat_id_;
@@ -1341,204 +1634,807 @@ class EditChatAdminQuery : public Td::ResultHandler {
void send(ChatId chat_id, tl_object_ptr<telegram_api::InputUser> &&input_user, bool is_administrator) {
chat_id_ = chat_id;
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_editChatAdmin(chat_id.get(), std::move(input_user), is_administrator))));
+ telegram_api::messages_editChatAdmin(chat_id.get(), std::move(input_user), is_administrator)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_editChatAdmin>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
if (!result) {
LOG(ERROR) << "Receive false as result of messages.editChatAdmin";
- return on_error(id, Status::Error(400, "Can't edit chat administrators"));
+ return on_error(Status::Error(400, "Can't edit chat administrators"));
}
// result will come in the updates
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("EditChatAdminQuery");
+ td_->updates_manager_->get_difference("EditChatAdminQuery");
}
};
-class ExportChatInviteLinkQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
- ChatId chat_id_;
+class ExportChatInviteQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> promise_;
+ DialogId dialog_id_;
public:
- explicit ExportChatInviteLinkQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ExportChatInviteQuery(Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise)
+ : promise_(std::move(promise)) {
}
- void send(ChatId chat_id) {
- chat_id_ = chat_id;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_exportChatInvite(chat_id.get()))));
+ void send(DialogId dialog_id, const string &title, int32 expire_date, int32 usage_limit, bool creates_join_request,
+ bool is_permanent) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = 0;
+ if (expire_date > 0) {
+ flags |= telegram_api::messages_exportChatInvite::EXPIRE_DATE_MASK;
+ }
+ if (usage_limit > 0) {
+ flags |= telegram_api::messages_exportChatInvite::USAGE_LIMIT_MASK;
+ }
+ if (creates_join_request) {
+ flags |= telegram_api::messages_exportChatInvite::REQUEST_NEEDED_MASK;
+ }
+ if (is_permanent) {
+ flags |= telegram_api::messages_exportChatInvite::LEGACY_REVOKE_PERMANENT_MASK;
+ }
+ if (!title.empty()) {
+ flags |= telegram_api::messages_exportChatInvite::TITLE_MASK;
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_exportChatInvite(
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), expire_date, usage_limit, title)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_exportChatInvite>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for exportChatInvite: " << to_string(ptr);
+ LOG(INFO) << "Receive result for ExportChatInviteQuery: " << to_string(ptr);
- td->contacts_manager_->on_get_chat_invite_link(chat_id_, std::move(ptr));
- promise_.set_value(Unit());
+ DialogInviteLink invite_link(std::move(ptr), "ExportChatInviteQuery");
+ if (!invite_link.is_valid()) {
+ return on_error(Status::Error(500, "Receive invalid invite link"));
+ }
+ if (invite_link.get_creator_user_id() != td_->contacts_manager_->get_my_id()) {
+ return on_error(Status::Error(500, "Receive invalid invite link creator"));
+ }
+ if (invite_link.is_permanent()) {
+ td_->contacts_manager_->on_get_permanent_dialog_invite_link(dialog_id_, invite_link);
+ }
+ promise_.set_value(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ExportChatInviteQuery");
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("ExportChatInviteLinkQuery");
}
};
-class ExportChannelInviteLinkQuery : public Td::ResultHandler {
+class EditChatInviteLinkQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit EditChatInviteLinkQuery(Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &invite_link, const string &title, int32 expire_date, int32 usage_limit,
+ bool creates_join_request) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = telegram_api::messages_editExportedChatInvite::EXPIRE_DATE_MASK |
+ telegram_api::messages_editExportedChatInvite::USAGE_LIMIT_MASK |
+ telegram_api::messages_editExportedChatInvite::REQUEST_NEEDED_MASK |
+ telegram_api::messages_editExportedChatInvite::TITLE_MASK;
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_editExportedChatInvite(flags, false /*ignored*/, std::move(input_peer), invite_link,
+ expire_date, usage_limit, creates_join_request, title)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_editExportedChatInvite>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for EditChatInviteLinkQuery: " << to_string(result);
+
+ if (result->get_id() != telegram_api::messages_exportedChatInvite::ID) {
+ return on_error(Status::Error(500, "Receive unexpected response from server"));
+ }
+
+ auto invite = move_tl_object_as<telegram_api::messages_exportedChatInvite>(result);
+
+ td_->contacts_manager_->on_get_users(std::move(invite->users_), "EditChatInviteLinkQuery");
+
+ DialogInviteLink invite_link(std::move(invite->invite_), "EditChatInviteLinkQuery");
+ if (!invite_link.is_valid()) {
+ return on_error(Status::Error(500, "Receive invalid invite link"));
+ }
+ promise_.set_value(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditChatInviteLinkQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetExportedChatInviteQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetExportedChatInviteQuery(Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &invite_link) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getExportedChatInvite(std::move(input_peer), invite_link)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getExportedChatInvite>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ if (result_ptr.ok()->get_id() != telegram_api::messages_exportedChatInvite::ID) {
+ LOG(ERROR) << "Receive wrong result for GetExportedChatInviteQuery: " << to_string(result_ptr.ok());
+ return on_error(Status::Error(500, "Receive unexpected response"));
+ }
+
+ auto result = move_tl_object_as<telegram_api::messages_exportedChatInvite>(result_ptr.ok_ref());
+ LOG(INFO) << "Receive result for GetExportedChatInviteQuery: " << to_string(result);
+
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetExportedChatInviteQuery");
+
+ DialogInviteLink invite_link(std::move(result->invite_), "GetExportedChatInviteQuery");
+ if (!invite_link.is_valid()) {
+ LOG(ERROR) << "Receive invalid invite link in " << dialog_id_;
+ return on_error(Status::Error(500, "Receive invalid invite link"));
+ }
+ promise_.set_value(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetExportedChatInviteQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetExportedChatInvitesQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::chatInviteLinks>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetExportedChatInvitesQuery(Promise<td_api::object_ptr<td_api::chatInviteLinks>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, tl_object_ptr<telegram_api::InputUser> &&input_user, bool is_revoked, int32 offset_date,
+ const string &offset_invite_link, int32 limit) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = 0;
+ if (!offset_invite_link.empty() || offset_date != 0) {
+ flags |= telegram_api::messages_getExportedChatInvites::OFFSET_DATE_MASK;
+ flags |= telegram_api::messages_getExportedChatInvites::OFFSET_LINK_MASK;
+ }
+ if (is_revoked) {
+ flags |= telegram_api::messages_getExportedChatInvites::REVOKED_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getExportedChatInvites(flags, false /*ignored*/, std::move(input_peer),
+ std::move(input_user), offset_date, offset_invite_link, limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getExportedChatInvites>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetExportedChatInvitesQuery: " << to_string(result);
+
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetExportedChatInvitesQuery");
+
+ int32 total_count = result->count_;
+ if (total_count < static_cast<int32>(result->invites_.size())) {
+ LOG(ERROR) << "Receive wrong total count of invite links " << total_count << " in " << dialog_id_;
+ total_count = static_cast<int32>(result->invites_.size());
+ }
+ vector<td_api::object_ptr<td_api::chatInviteLink>> invite_links;
+ for (auto &invite : result->invites_) {
+ DialogInviteLink invite_link(std::move(invite), "GetExportedChatInvitesQuery");
+ if (!invite_link.is_valid()) {
+ LOG(ERROR) << "Receive invalid invite link in " << dialog_id_;
+ total_count--;
+ continue;
+ }
+ invite_links.push_back(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()));
+ }
+ promise_.set_value(td_api::make_object<td_api::chatInviteLinks>(total_count, std::move(invite_links)));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetExportedChatInvitesQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetChatAdminWithInvitesQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::chatInviteLinkCounts>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetChatAdminWithInvitesQuery(Promise<td_api::object_ptr<td_api::chatInviteLinkCounts>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_getAdminsWithInvites(std::move(input_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getAdminsWithInvites>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetChatAdminWithInvitesQuery: " << to_string(result);
+
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetChatAdminWithInvitesQuery");
+
+ vector<td_api::object_ptr<td_api::chatInviteLinkCount>> invite_link_counts;
+ for (auto &admin : result->admins_) {
+ UserId user_id(admin->admin_id_);
+ if (!user_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid invite link creator " << user_id << " in " << dialog_id_;
+ continue;
+ }
+ invite_link_counts.push_back(td_api::make_object<td_api::chatInviteLinkCount>(
+ td_->contacts_manager_->get_user_id_object(user_id, "chatInviteLinkCount"), admin->invites_count_,
+ admin->revoked_invites_count_));
+ }
+ promise_.set_value(td_api::make_object<td_api::chatInviteLinkCounts>(std::move(invite_link_counts)));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChatAdminWithInvitesQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetChatInviteImportersQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::chatInviteLinkMembers>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetChatInviteImportersQuery(Promise<td_api::object_ptr<td_api::chatInviteLinkMembers>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &invite_link, int32 offset_date, UserId offset_user_id, int32 limit) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ auto r_input_user = td_->contacts_manager_->get_input_user(offset_user_id);
+ if (r_input_user.is_error()) {
+ r_input_user = make_tl_object<telegram_api::inputUserEmpty>();
+ }
+
+ int32 flags = telegram_api::messages_getChatInviteImporters::LINK_MASK;
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getChatInviteImporters(flags, false /*ignored*/, std::move(input_peer), invite_link,
+ string(), offset_date, r_input_user.move_as_ok(), limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getChatInviteImporters>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetChatInviteImportersQuery: " << to_string(result);
+
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetChatInviteImportersQuery");
+
+ int32 total_count = result->count_;
+ if (total_count < static_cast<int32>(result->importers_.size())) {
+ LOG(ERROR) << "Receive wrong total count of invite link users " << total_count << " in " << dialog_id_;
+ total_count = static_cast<int32>(result->importers_.size());
+ }
+ vector<td_api::object_ptr<td_api::chatInviteLinkMember>> invite_link_members;
+ for (auto &importer : result->importers_) {
+ UserId user_id(importer->user_id_);
+ UserId approver_user_id(importer->approved_by_);
+ if (!user_id.is_valid() || (!approver_user_id.is_valid() && approver_user_id != UserId()) ||
+ importer->requested_) {
+ LOG(ERROR) << "Receive invalid invite link importer: " << to_string(importer);
+ total_count--;
+ continue;
+ }
+ invite_link_members.push_back(td_api::make_object<td_api::chatInviteLinkMember>(
+ td_->contacts_manager_->get_user_id_object(user_id, "chatInviteLinkMember"), importer->date_,
+ td_->contacts_manager_->get_user_id_object(approver_user_id, "chatInviteLinkMember")));
+ }
+ promise_.set_value(td_api::make_object<td_api::chatInviteLinkMembers>(total_count, std::move(invite_link_members)));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChatInviteImportersQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetChatJoinRequestsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::chatJoinRequests>> promise_;
+ DialogId dialog_id_;
+ bool is_full_list_ = false;
+
+ public:
+ explicit GetChatJoinRequestsQuery(Promise<td_api::object_ptr<td_api::chatJoinRequests>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &invite_link, const string &query, int32 offset_date,
+ UserId offset_user_id, int32 limit) {
+ dialog_id_ = dialog_id;
+ is_full_list_ =
+ invite_link.empty() && query.empty() && offset_date == 0 && !offset_user_id.is_valid() && limit >= 3;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ auto r_input_user = td_->contacts_manager_->get_input_user(offset_user_id);
+ if (r_input_user.is_error()) {
+ r_input_user = make_tl_object<telegram_api::inputUserEmpty>();
+ }
+
+ int32 flags = telegram_api::messages_getChatInviteImporters::REQUESTED_MASK;
+ if (!invite_link.empty()) {
+ flags |= telegram_api::messages_getChatInviteImporters::LINK_MASK;
+ }
+ if (!query.empty()) {
+ flags |= telegram_api::messages_getChatInviteImporters::Q_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getChatInviteImporters(flags, false /*ignored*/, std::move(input_peer), invite_link,
+ query, offset_date, r_input_user.move_as_ok(), limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getChatInviteImporters>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetChatJoinRequestsQuery: " << to_string(result);
+
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetChatJoinRequestsQuery");
+
+ int32 total_count = result->count_;
+ if (total_count < static_cast<int32>(result->importers_.size())) {
+ LOG(ERROR) << "Receive wrong total count of join requests " << total_count << " in " << dialog_id_;
+ total_count = static_cast<int32>(result->importers_.size());
+ }
+ vector<td_api::object_ptr<td_api::chatJoinRequest>> join_requests;
+ vector<int64> recent_requesters;
+ for (auto &request : result->importers_) {
+ UserId user_id(request->user_id_);
+ UserId approver_user_id(request->approved_by_);
+ if (!user_id.is_valid() || approver_user_id.is_valid() || !request->requested_) {
+ LOG(ERROR) << "Receive invalid join request: " << to_string(request);
+ total_count--;
+ continue;
+ }
+ if (recent_requesters.size() < 3) {
+ recent_requesters.push_back(user_id.get());
+ }
+ join_requests.push_back(td_api::make_object<td_api::chatJoinRequest>(
+ td_->contacts_manager_->get_user_id_object(user_id, "chatJoinRequest"), request->date_, request->about_));
+ }
+ if (is_full_list_) {
+ td_->messages_manager_->on_update_dialog_pending_join_requests(dialog_id_, total_count,
+ std::move(recent_requesters));
+ }
+ promise_.set_value(td_api::make_object<td_api::chatJoinRequests>(total_count, std::move(join_requests)));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChatJoinRequestsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class HideChatJoinRequestQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- ChannelId channel_id_;
+ DialogId dialog_id_;
public:
- explicit ExportChannelInviteLinkQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit HideChatJoinRequestQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id) {
- channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
- CHECK(input_channel != nullptr);
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::channels_exportInvite(std::move(input_channel)))));
+ void send(DialogId dialog_id, UserId user_id, bool approve) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return on_error(r_input_user.move_as_error());
+ }
+
+ int32 flags = 0;
+ if (approve) {
+ flags |= telegram_api::messages_hideChatJoinRequest::APPROVED_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_hideChatJoinRequest(
+ flags, false /*ignored*/, std::move(input_peer), r_input_user.move_as_ok())));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::channels_exportInvite>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_hideChatJoinRequest>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for exportChannelInvite: " << to_string(ptr);
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for HideChatJoinRequestQuery: " << to_string(result);
+ td_->updates_manager_->on_get_updates(std::move(result), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "HideChatJoinRequestQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class HideAllChatJoinRequestsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit HideAllChatJoinRequestsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &invite_link, bool approve) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = 0;
+ if (approve) {
+ flags |= telegram_api::messages_hideAllChatJoinRequests::APPROVED_MASK;
+ }
+ if (!invite_link.empty()) {
+ flags |= telegram_api::messages_hideAllChatJoinRequests::LINK_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_hideAllChatJoinRequests(flags, false /*ignored*/, std::move(input_peer), invite_link)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_hideAllChatJoinRequests>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for HideAllChatJoinRequestsQuery: " << to_string(result);
+ td_->updates_manager_->on_get_updates(std::move(result), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "HideAllChatJoinRequestsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class RevokeChatInviteLinkQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::chatInviteLinks>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit RevokeChatInviteLinkQuery(Promise<td_api::object_ptr<td_api::chatInviteLinks>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &invite_link) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = telegram_api::messages_editExportedChatInvite::REVOKED_MASK;
+ send_query(G()->net_query_creator().create(telegram_api::messages_editExportedChatInvite(
+ flags, false /*ignored*/, std::move(input_peer), invite_link, 0, 0, false, string())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_editExportedChatInvite>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for RevokeChatInviteLinkQuery: " << to_string(result);
+
+ vector<td_api::object_ptr<td_api::chatInviteLink>> links;
+ switch (result->get_id()) {
+ case telegram_api::messages_exportedChatInvite::ID: {
+ auto invite = move_tl_object_as<telegram_api::messages_exportedChatInvite>(result);
+
+ td_->contacts_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery");
+
+ DialogInviteLink invite_link(std::move(invite->invite_), "RevokeChatInviteLinkQuery");
+ if (!invite_link.is_valid()) {
+ return on_error(Status::Error(500, "Receive invalid invite link"));
+ }
+ links.push_back(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()));
+ break;
+ }
+ case telegram_api::messages_exportedChatInviteReplaced::ID: {
+ auto invite = move_tl_object_as<telegram_api::messages_exportedChatInviteReplaced>(result);
+
+ td_->contacts_manager_->on_get_users(std::move(invite->users_), "RevokeChatInviteLinkQuery replaced");
+
+ DialogInviteLink invite_link(std::move(invite->invite_), "RevokeChatInviteLinkQuery replaced");
+ DialogInviteLink new_invite_link(std::move(invite->new_invite_), "RevokeChatInviteLinkQuery new replaced");
+ if (!invite_link.is_valid() || !new_invite_link.is_valid()) {
+ return on_error(Status::Error(500, "Receive invalid invite link"));
+ }
+ if (new_invite_link.get_creator_user_id() == td_->contacts_manager_->get_my_id() &&
+ new_invite_link.is_permanent()) {
+ td_->contacts_manager_->on_get_permanent_dialog_invite_link(dialog_id_, new_invite_link);
+ }
+ links.push_back(invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()));
+ links.push_back(new_invite_link.get_chat_invite_link_object(td_->contacts_manager_.get()));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ auto total_count = static_cast<int32>(links.size());
+ promise_.set_value(td_api::make_object<td_api::chatInviteLinks>(total_count, std::move(links)));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "RevokeChatInviteLinkQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class DeleteExportedChatInviteQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit DeleteExportedChatInviteQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &invite_link) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_deleteExportedChatInvite(std::move(input_peer), invite_link)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_deleteExportedChatInvite>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteExportedChatInviteQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class DeleteRevokedExportedChatInvitesQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit DeleteRevokedExportedChatInvitesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, tl_object_ptr<telegram_api::InputUser> &&input_user) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_deleteRevokedExportedChatInvites(std::move(input_peer), std::move(input_user))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_deleteRevokedExportedChatInvites>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
- td->contacts_manager_->on_get_channel_invite_link(channel_id_, std::move(ptr));
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "ExportChannelInviteLinkQuery");
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteRevokedExportedChatInvitesQuery");
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("ExportChannelInviteLinkQuery");
}
};
-class CheckDialogInviteLinkQuery : public Td::ResultHandler {
+class CheckChatInviteQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
string invite_link_;
public:
- explicit CheckDialogInviteLinkQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit CheckChatInviteQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(const string &invite_link) {
invite_link_ = invite_link;
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::messages_checkChatInvite(ContactsManager::get_dialog_invite_link_hash(invite_link_).str()))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_checkChatInvite(LinkManager::get_dialog_invite_link_hash(invite_link_))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_checkChatInvite>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for checkChatInvite: " << to_string(ptr);
+ LOG(INFO) << "Receive result for CheckChatInviteQuery: " << to_string(ptr);
- td->contacts_manager_->on_get_dialog_invite_link_info(invite_link_, std::move(ptr));
- promise_.set_value(Unit());
+ td_->contacts_manager_->on_get_dialog_invite_link_info(invite_link_, std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class ImportDialogInviteLinkQuery : public Td::ResultHandler {
+class ImportChatInviteQuery final : public Td::ResultHandler {
Promise<DialogId> promise_;
string invite_link_;
public:
- explicit ImportDialogInviteLinkQuery(Promise<DialogId> &&promise) : promise_(std::move(promise)) {
+ explicit ImportChatInviteQuery(Promise<DialogId> &&promise) : promise_(std::move(promise)) {
}
void send(const string &invite_link) {
invite_link_ = invite_link;
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::messages_importChatInvite(ContactsManager::get_dialog_invite_link_hash(invite_link).str()))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_importChatInvite(LinkManager::get_dialog_invite_link_hash(invite_link_))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_importChatInvite>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for importChatInvite: " << to_string(ptr);
+ LOG(INFO) << "Receive result for ImportChatInviteQuery: " << to_string(ptr);
- auto dialog_ids = td->updates_manager_->get_chats(ptr.get());
+ auto dialog_ids = UpdatesManager::get_chat_dialog_ids(ptr.get());
if (dialog_ids.size() != 1u) {
- LOG(ERROR) << "Receive wrong result for ImportDialogInviteLinkQuery: " << to_string(ptr);
- return on_error(id, Status::Error(500, "Internal Server Error"));
+ LOG(ERROR) << "Receive wrong result for ImportChatInviteQuery: " << to_string(ptr);
+ return on_error(Status::Error(500, "Internal Server Error: failed to join chat via invite link"));
}
+ auto dialog_id = dialog_ids[0];
- td->updates_manager_->on_get_updates(std::move(ptr));
- td->contacts_manager_->invalidate_invite_link(invite_link_);
- promise_.set_value(std::move(dialog_ids[0]));
+ td_->contacts_manager_->invalidate_invite_link_info(invite_link_);
+ td_->updates_manager_->on_get_updates(
+ std::move(ptr), PromiseCreator::lambda([promise = std::move(promise_), dialog_id](Unit) mutable {
+ promise.set_value(std::move(dialog_id));
+ }));
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->invalidate_invite_link(invite_link_);
+ void on_error(Status status) final {
+ td_->contacts_manager_->invalidate_invite_link_info(invite_link_);
promise_.set_error(std::move(status));
}
};
-class DeleteChatUserQuery : public Td::ResultHandler {
+class DeleteChatUserQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit DeleteChatUserQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChatId chat_id, tl_object_ptr<telegram_api::InputUser> &&input_user) {
+ void send(ChatId chat_id, tl_object_ptr<telegram_api::InputUser> &&input_user, bool revoke_messages) {
+ int32 flags = 0;
+ if (revoke_messages) {
+ flags |= telegram_api::messages_deleteChatUser::REVOKE_HISTORY_MASK;
+ }
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_deleteChatUser(chat_id.get(), std::move(input_user)))));
+ telegram_api::messages_deleteChatUser(flags, false /*ignored*/, chat_id.get(), std::move(input_user))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_deleteChatUser>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for deleteChatUser: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for DeleteChatUserQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("DeleteChatUserQuery");
+ td_->updates_manager_->get_difference("DeleteChatUserQuery");
}
};
-class JoinChannelQuery : public Td::ResultHandler {
+class JoinChannelQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
@@ -1548,33 +2444,31 @@ class JoinChannelQuery : public Td::ResultHandler {
void send(ChannelId channel_id) {
channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(
- G()->net_query_creator().create(create_storer(telegram_api::channels_joinChannel(std::move(input_channel)))));
+ G()->net_query_creator().create(telegram_api::channels_joinChannel(std::move(input_channel)), {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_joinChannel>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for joinChannel: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for JoinChannelQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "JoinChannelQuery");
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "JoinChannelQuery");
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("JoinChannelQuery");
+ td_->updates_manager_->get_difference("JoinChannelQuery");
}
};
-class InviteToChannelQuery : public Td::ResultHandler {
+class InviteToChannelQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
@@ -1584,108 +2478,116 @@ class InviteToChannelQuery : public Td::ResultHandler {
void send(ChannelId channel_id, vector<tl_object_ptr<telegram_api::InputUser>> &&input_users) {
channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_inviteToChannel(std::move(input_channel), std::move(input_users)))));
+ telegram_api::channels_inviteToChannel(std::move(input_channel), std::move(input_users))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_inviteToChannel>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for inviteToChannel: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
- td->contacts_manager_->invalidate_channel_full(channel_id_);
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for InviteToChannelQuery: " << to_string(ptr);
+ td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "InviteToChannelQuery");
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "InviteToChannelQuery");
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "InviteToChannelQuery");
+ td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "InviteToChannelQuery");
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("InviteToChannelQuery");
}
};
-class EditChannelAdminQuery : public Td::ResultHandler {
+class EditChannelAdminQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
+ UserId user_id_;
+ DialogParticipantStatus status_ = DialogParticipantStatus::Left();
public:
explicit EditChannelAdminQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, tl_object_ptr<telegram_api::InputUser> &&input_user, DialogParticipantStatus status) {
+ void send(ChannelId channel_id, UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user,
+ const DialogParticipantStatus &status) {
channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ user_id_ = user_id;
+ status_ = status;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_editAdmin(
- std::move(input_channel), std::move(input_user), status.get_channel_admin_rights()))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_editAdmin(
+ std::move(input_channel), std::move(input_user), status.get_chat_admin_rights(), status.get_rank())));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_editAdmin>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for editChannelAdmin: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
- td->contacts_manager_->invalidate_channel_full(channel_id_);
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for EditChannelAdminQuery: " << to_string(ptr);
+ td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelAdminQuery");
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ td_->contacts_manager_->on_set_channel_participant_status(channel_id_, DialogId(user_id_), status_);
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "EditChannelAdminQuery");
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditChannelAdminQuery");
+ td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelAdminQuery");
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("EditChannelAdminQuery");
}
};
-class EditChannelBannedQuery : public Td::ResultHandler {
+class EditChannelBannedQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
+ DialogId participant_dialog_id_;
+ DialogParticipantStatus status_ = DialogParticipantStatus::Left();
public:
explicit EditChannelBannedQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, tl_object_ptr<telegram_api::InputUser> &&input_user, DialogParticipantStatus status) {
+ void send(ChannelId channel_id, DialogId participant_dialog_id, tl_object_ptr<telegram_api::InputPeer> &&input_peer,
+ const DialogParticipantStatus &status) {
channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ participant_dialog_id_ = participant_dialog_id;
+ status_ = status;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_editBanned(
- std::move(input_channel), std::move(input_user), status.get_channel_banned_rights()))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_editBanned(
+ std::move(input_channel), std::move(input_peer), status.get_chat_banned_rights())));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_editBanned>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for editChannelBanned: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
- td->contacts_manager_->invalidate_channel_full(channel_id_);
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for EditChannelBannedQuery: " << to_string(ptr);
+ td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelBannedQuery");
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ td_->contacts_manager_->on_set_channel_participant_status(channel_id_, participant_dialog_id_, status_);
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "EditChannelBannedQuery");
+ void on_error(Status status) final {
+ if (participant_dialog_id_.get_type() != DialogType::Channel) {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditChannelBannedQuery");
+ }
+ td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelBannedQuery");
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("EditChannelBannedQuery");
}
};
-class LeaveChannelQuery : public Td::ResultHandler {
+class LeaveChannelQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
@@ -1695,33 +2597,106 @@ class LeaveChannelQuery : public Td::ResultHandler {
void send(ChannelId channel_id) {
channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(
- G()->net_query_creator().create(create_storer(telegram_api::channels_leaveChannel(std::move(input_channel)))));
+ G()->net_query_creator().create(telegram_api::channels_leaveChannel(std::move(input_channel)), {{channel_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_leaveChannel>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for leaveChannel: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
+ LOG(INFO) << "Receive result for LeaveChannelQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
- promise_.set_value(Unit());
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "LeaveChannelQuery");
+ promise_.set_error(std::move(status));
+ td_->updates_manager_->get_difference("LeaveChannelQuery");
}
+};
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "LeaveChannelQuery");
+class CanEditChannelCreatorQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit CanEditChannelCreatorQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ auto r_input_user = td_->contacts_manager_->get_input_user(td_->contacts_manager_->get_my_id());
+ CHECK(r_input_user.is_ok());
+ send_query(G()->net_query_creator().create(telegram_api::channels_editCreator(
+ telegram_api::make_object<telegram_api::inputChannelEmpty>(), r_input_user.move_as_ok(),
+ make_tl_object<telegram_api::inputCheckPasswordEmpty>())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_editCreator>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(ERROR) << "Receive result for CanEditChannelCreatorQuery: " << to_string(ptr);
+ promise_.set_error(Status::Error(500, "Server doesn't returned error"));
+ }
+
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("LeaveChannelQuery");
}
};
-class MigrateChatQuery : public Td::ResultHandler {
+class EditChannelCreatorQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ ChannelId channel_id_;
+
+ public:
+ explicit EditChannelCreatorQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, UserId user_id,
+ tl_object_ptr<telegram_api::InputCheckPasswordSRP> input_check_password) {
+ channel_id_ = channel_id;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ if (input_channel == nullptr) {
+ return promise_.set_error(Status::Error(400, "Have no access to the chat"));
+ }
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise_.set_error(r_input_user.move_as_error());
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_editCreator(std::move(input_channel), r_input_user.move_as_ok(),
+ std::move(input_check_password)),
+ {{channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_editCreator>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for EditChannelCreatorQuery: " << to_string(ptr);
+ td_->contacts_manager_->invalidate_channel_full(channel_id_, false, "EditChannelCreatorQuery");
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditChannelCreatorQuery");
+ promise_.set_error(std::move(status));
+ td_->updates_manager_->get_difference("EditChannelCreatorQuery");
+ }
+};
+
+class MigrateChatQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -1729,58 +2704,66 @@ class MigrateChatQuery : public Td::ResultHandler {
}
void send(ChatId chat_id) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_migrateChat(chat_id.get()))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_migrateChat(chat_id.get()), {{chat_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_migrateChat>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for migrateChat: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for MigrateChatQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
- td->updates_manager_->get_difference("MigrateChatQuery");
+ td_->updates_manager_->get_difference("MigrateChatQuery");
}
};
-class GetCreatedPublicChannelsQuery : public Td::ResultHandler {
+class GetCreatedPublicChannelsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
+ PublicDialogType type_;
public:
explicit GetCreatedPublicChannelsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_getAdminedPublicChannels())));
+ void send(PublicDialogType type, bool check_limit) {
+ type_ = type;
+ int32 flags = 0;
+ if (type_ == PublicDialogType::IsLocationBased) {
+ flags |= telegram_api::channels_getAdminedPublicChannels::BY_LOCATION_MASK;
+ }
+ if (check_limit) {
+ flags |= telegram_api::channels_getAdminedPublicChannels::CHECK_LIMIT_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_getAdminedPublicChannels(flags, false /*ignored*/, false /*ignored*/)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_getAdminedPublicChannels>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto chats_ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetCreatedPublicChannelsQuery " << to_string(chats_ptr);
+ LOG(INFO) << "Receive result for GetCreatedPublicChannelsQuery: " << to_string(chats_ptr);
int32 constructor_id = chats_ptr->get_id();
switch (constructor_id) {
case telegram_api::messages_chats::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chats>(chats_ptr);
- td->contacts_manager_->on_get_created_public_channels(std::move(chats->chats_));
+ td_->contacts_manager_->on_get_created_public_channels(type_, std::move(chats->chats_));
break;
}
case telegram_api::messages_chatsSlice::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chatsSlice>(chats_ptr);
LOG(ERROR) << "Receive chatsSlice in result of GetCreatedPublicChannelsQuery";
- td->contacts_manager_->on_get_created_public_channels(std::move(chats->chats_));
+ td_->contacts_manager_->on_get_created_public_channels(type_, std::move(chats->chats_));
break;
}
default:
@@ -1790,12 +2773,85 @@ class GetCreatedPublicChannelsQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class GetUsersQuery : public Td::ResultHandler {
+class GetGroupsForDiscussionQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit GetGroupsForDiscussionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::channels_getGroupsForDiscussion()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_getGroupsForDiscussion>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto chats_ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetGroupsForDiscussionQuery: " << to_string(chats_ptr);
+ int32 constructor_id = chats_ptr->get_id();
+ switch (constructor_id) {
+ case telegram_api::messages_chats::ID: {
+ auto chats = move_tl_object_as<telegram_api::messages_chats>(chats_ptr);
+ td_->contacts_manager_->on_get_dialogs_for_discussion(std::move(chats->chats_));
+ break;
+ }
+ case telegram_api::messages_chatsSlice::ID: {
+ auto chats = move_tl_object_as<telegram_api::messages_chatsSlice>(chats_ptr);
+ LOG(ERROR) << "Receive chatsSlice in result of GetGroupsForDiscussionQuery";
+ td_->contacts_manager_->on_get_dialogs_for_discussion(std::move(chats->chats_));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetInactiveChannelsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit GetInactiveChannelsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::channels_getInactiveChannels()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_getInactiveChannels>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetInactiveChannelsQuery: " << to_string(result);
+ // don't need to use result->dates_, because chat.last_message.date is more reliable
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetInactiveChannelsQuery");
+ td_->contacts_manager_->on_get_inactive_channels(std::move(result->chats_), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetUsersQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -1803,51 +2859,56 @@ class GetUsersQuery : public Td::ResultHandler {
}
void send(vector<tl_object_ptr<telegram_api::InputUser>> &&input_users) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::users_getUsers(std::move(input_users)))));
+ send_query(G()->net_query_creator().create(telegram_api::users_getUsers(std::move(input_users))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::users_getUsers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->contacts_manager_->on_get_users(result_ptr.move_as_ok());
+ td_->contacts_manager_->on_get_users(result_ptr.move_as_ok(), "GetUsersQuery");
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class GetFullUserQuery : public Td::ResultHandler {
- UserId user_id_;
+class GetFullUserQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
public:
- void send(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user) {
- user_id_ = user_id;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::users_getFullUser(std::move(input_user)))));
+ explicit GetFullUserQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void send(tl_object_ptr<telegram_api::InputUser> &&input_user) {
+ send_query(G()->net_query_creator().create(telegram_api::users_getFullUser(std::move(input_user))));
+ }
+
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::users_getFullUser>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- LOG(DEBUG) << "Receive result for getFullUser " << to_string(result_ptr.ok());
- td->contacts_manager_->on_get_user_full(result_ptr.move_as_ok());
- td->contacts_manager_->on_get_user_full_success(user_id_);
+ auto ptr = result_ptr.move_as_ok();
+ LOG(DEBUG) << "Receive result for GetFullUserQuery: " << to_string(ptr);
+ td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetFullUserQuery");
+ td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetFullUserQuery");
+ td_->contacts_manager_->on_get_user_full(std::move(ptr->full_user_));
+ promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_user_full_fail(user_id_, std::move(status));
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
}
};
-class GetUserPhotosQuery : public Td::ResultHandler {
+class GetUserPhotosQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
UserId user_id_;
int32 offset_;
@@ -1857,62 +2918,62 @@ class GetUserPhotosQuery : public Td::ResultHandler {
explicit GetUserPhotosQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user, int32 offset, int32 limit) {
+ void send(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user, int32 offset, int32 limit,
+ int64 photo_id) {
user_id_ = user_id;
offset_ = offset;
limit_ = limit;
- LOG(INFO) << "Get " << user_id << " profile photos with offset " << offset << " and limit " << limit;
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::photos_getUserPhotos(std::move(input_user), offset, 0, limit))));
+ telegram_api::photos_getUserPhotos(std::move(input_user), offset, photo_id, limit)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::photos_getUserPhotos>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetUserPhotosQuery " << to_string(ptr);
+ LOG(INFO) << "Receive result for GetUserPhotosQuery: " << to_string(ptr);
int32 constructor_id = ptr->get_id();
if (constructor_id == telegram_api::photos_photos::ID) {
auto photos = move_tl_object_as<telegram_api::photos_photos>(ptr);
- td->contacts_manager_->on_get_users(std::move(photos->users_));
- int32 photos_size = narrow_cast<int32>(photos->photos_.size());
- td->contacts_manager_->on_get_user_photos(user_id_, offset_, limit_, photos_size, std::move(photos->photos_));
+ td_->contacts_manager_->on_get_users(std::move(photos->users_), "GetUserPhotosQuery");
+ auto photos_size = narrow_cast<int32>(photos->photos_.size());
+ td_->contacts_manager_->on_get_user_photos(user_id_, offset_, limit_, photos_size, std::move(photos->photos_));
} else {
CHECK(constructor_id == telegram_api::photos_photosSlice::ID);
auto photos = move_tl_object_as<telegram_api::photos_photosSlice>(ptr);
- td->contacts_manager_->on_get_users(std::move(photos->users_));
- td->contacts_manager_->on_get_user_photos(user_id_, offset_, limit_, photos->count_, std::move(photos->photos_));
+ td_->contacts_manager_->on_get_users(std::move(photos->users_), "GetUserPhotosQuery slice");
+ td_->contacts_manager_->on_get_user_photos(user_id_, offset_, limit_, photos->count_, std::move(photos->photos_));
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class GetChatsQuery : public Td::ResultHandler {
+class GetChatsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit GetChatsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(vector<int32> &&chat_ids) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getChats(std::move(chat_ids)))));
+ void send(vector<int64> &&chat_ids) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getChats(std::move(chat_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getChats>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto chats_ptr = result_ptr.move_as_ok();
@@ -1920,13 +2981,13 @@ class GetChatsQuery : public Td::ResultHandler {
switch (constructor_id) {
case telegram_api::messages_chats::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chats>(chats_ptr);
- td->contacts_manager_->on_get_chats(std::move(chats->chats_));
+ td_->contacts_manager_->on_get_chats(std::move(chats->chats_), "GetChatsQuery");
break;
}
case telegram_api::messages_chatsSlice::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chatsSlice>(chats_ptr);
LOG(ERROR) << "Receive chatsSlice in result of GetChatsQuery";
- td->contacts_manager_->on_get_chats(std::move(chats->chats_));
+ td_->contacts_manager_->on_get_chats(std::move(chats->chats_), "GetChatsQuery slice");
break;
}
default:
@@ -1936,42 +2997,42 @@ class GetChatsQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class GetFullChatQuery : public Td::ResultHandler {
+class GetFullChatQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
ChatId chat_id_;
public:
+ explicit GetFullChatQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
void send(ChatId chat_id) {
- chat_id_ = chat_id;
- LOG(INFO) << "Send getFullChat query to get " << chat_id;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getFullChat(chat_id.get()))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_getFullChat(chat_id.get())));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getFullChat>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- // LOG(INFO) << "Receive result for getFullChat query: " << to_string(ptr);
- td->contacts_manager_->on_get_users(std::move(ptr->users_));
- td->contacts_manager_->on_get_chats(std::move(ptr->chats_));
- td->contacts_manager_->on_get_chat_full(std::move(ptr->full_chat_));
-
- td->contacts_manager_->on_get_chat_full_success(chat_id_);
+ td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetFullChatQuery");
+ td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetFullChatQuery");
+ td_->contacts_manager_->on_get_chat_full(std::move(ptr->full_chat_), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_chat_full_fail(chat_id_, std::move(status));
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_chat_full_failed(chat_id_);
+ promise_.set_error(std::move(status));
}
};
-class GetChannelsQuery : public Td::ResultHandler {
+class GetChannelsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
@@ -1987,29 +3048,28 @@ class GetChannelsQuery : public Td::ResultHandler {
vector<tl_object_ptr<telegram_api::InputChannel>> input_channels;
input_channels.push_back(std::move(input_channel));
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::channels_getChannels(std::move(input_channels)))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_getChannels(std::move(input_channels))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_getChannels>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- // LOG(INFO) << "Receive result for getChannels query: " << to_string(result_ptr.ok());
+ // LOG(INFO) << "Receive result for GetChannelsQuery: " << to_string(result_ptr.ok());
auto chats_ptr = result_ptr.move_as_ok();
int32 constructor_id = chats_ptr->get_id();
switch (constructor_id) {
case telegram_api::messages_chats::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chats>(chats_ptr);
- td->contacts_manager_->on_get_chats(std::move(chats->chats_));
+ td_->contacts_manager_->on_get_chats(std::move(chats->chats_), "GetChannelsQuery");
break;
}
case telegram_api::messages_chatsSlice::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chatsSlice>(chats_ptr);
LOG(ERROR) << "Receive chatsSlice in result of GetChannelsQuery";
- td->contacts_manager_->on_get_chats(std::move(chats->chats_));
+ td_->contacts_manager_->on_get_chats(std::move(chats->chats_), "GetChannelsQuery slice");
break;
}
default:
@@ -2019,155 +3079,148 @@ class GetChannelsQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelsQuery");
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelsQuery");
promise_.set_error(std::move(status));
}
};
-class GetFullChannelQuery : public Td::ResultHandler {
+class GetFullChannelQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
ChannelId channel_id_;
public:
+ explicit GetFullChannelQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
void send(ChannelId channel_id, tl_object_ptr<telegram_api::InputChannel> &&input_channel) {
channel_id_ = channel_id;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_getFullChannel(std::move(input_channel)))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_getFullChannel(std::move(input_channel))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_getFullChannel>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- // LOG(INFO) << "Receive result for getFullChannel query: " << to_string(ptr);
- td->contacts_manager_->on_get_users(std::move(ptr->users_));
- td->contacts_manager_->on_get_chats(std::move(ptr->chats_));
- td->contacts_manager_->on_get_chat_full(std::move(ptr->full_chat_));
-
- td->contacts_manager_->on_get_channel_full_success(channel_id_);
+ td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetFullChannelQuery");
+ td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetFullChannelQuery");
+ td_->contacts_manager_->on_get_chat_full(std::move(ptr->full_chat_), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetFullChannelQuery");
- td->contacts_manager_->on_get_channel_full_fail(channel_id_, std::move(status));
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetFullChannelQuery");
+ td_->contacts_manager_->on_get_channel_full_failed(channel_id_);
+ promise_.set_error(std::move(status));
}
};
-class GetChannelParticipantQuery : public Td::ResultHandler {
+class GetChannelParticipantQuery final : public Td::ResultHandler {
Promise<DialogParticipant> promise_;
ChannelId channel_id_;
- UserId user_id_;
+ DialogId participant_dialog_id_;
public:
explicit GetChannelParticipantQuery(Promise<DialogParticipant> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user) {
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ void send(ChannelId channel_id, DialogId participant_dialog_id, tl_object_ptr<telegram_api::InputPeer> &&input_peer) {
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
if (input_channel == nullptr) {
- return promise_.set_error(Status::Error(3, "Supergroup not found"));
+ return promise_.set_error(Status::Error(400, "Supergroup not found"));
}
- CHECK(input_user != nullptr);
+ CHECK(input_peer != nullptr);
channel_id_ = channel_id;
- user_id_ = user_id;
+ participant_dialog_id_ = participant_dialog_id;
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_getParticipant(std::move(input_channel), std::move(input_user)))));
+ telegram_api::channels_getParticipant(std::move(input_channel), std::move(input_peer))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_getParticipant>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto participant = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetChannelParticipantQuery: " << to_string(participant);
- td->contacts_manager_->on_get_users(std::move(participant->users_));
- promise_.set_value(
- td->contacts_manager_->get_dialog_participant(channel_id_, std::move(participant->participant_)));
+ td_->contacts_manager_->on_get_users(std::move(participant->users_), "GetChannelParticipantQuery");
+ td_->contacts_manager_->on_get_chats(std::move(participant->chats_), "GetChannelParticipantQuery");
+ DialogParticipant result(std::move(participant->participant_),
+ td_->contacts_manager_->get_channel_type(channel_id_));
+ if (!result.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << result;
+ return promise_.set_error(Status::Error(500, "Receive invalid chat member"));
+ }
+ promise_.set_value(std::move(result));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "USER_NOT_PARTICIPANT") {
- promise_.set_value({user_id_, UserId(), 0, DialogParticipantStatus::Left()});
+ promise_.set_value(DialogParticipant::left(participant_dialog_id_));
return;
}
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelParticipantQuery");
+ if (participant_dialog_id_.get_type() != DialogType::Channel) {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelParticipantQuery");
+ }
promise_.set_error(std::move(status));
}
};
-class GetChannelParticipantsQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class GetChannelParticipantsQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::channels_channelParticipants>> promise_;
ChannelId channel_id_;
- ChannelParticipantsFilter filter_{nullptr};
- int32 offset_;
- int32 limit_;
- int64 random_id_;
public:
- explicit GetChannelParticipantsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit GetChannelParticipantsQuery(Promise<tl_object_ptr<telegram_api::channels_channelParticipants>> &&promise)
+ : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, int32 limit, int64 random_id) {
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ void send(ChannelId channel_id, const ChannelParticipantFilter &filter, int32 offset, int32 limit) {
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
if (input_channel == nullptr) {
- return promise_.set_error(Status::Error(3, "Supergroup not found"));
+ return promise_.set_error(Status::Error(400, "Supergroup not found"));
}
channel_id_ = channel_id;
- filter_ = std::move(filter);
- offset_ = offset;
- limit_ = limit;
- random_id_ = random_id;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_getParticipants(
- std::move(input_channel), filter_.get_input_channel_participants_filter(), offset, limit, 0))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_getParticipants(
+ std::move(input_channel), filter.get_input_channel_participants_filter(), offset, limit, 0)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_getParticipants>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto participants_ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetChannelParticipantsQuery with filter "
- << to_string(filter_.get_input_channel_participants_filter()) << ": " << to_string(participants_ptr);
+ LOG(INFO) << "Receive result for GetChannelParticipantsQuery: " << to_string(participants_ptr);
switch (participants_ptr->get_id()) {
case telegram_api::channels_channelParticipants::ID: {
- auto participants = telegram_api::move_object_as<telegram_api::channels_channelParticipants>(participants_ptr);
- td->contacts_manager_->on_get_users(std::move(participants->users_));
- td->contacts_manager_->on_get_channel_participants_success(channel_id_, std::move(filter_), offset_, limit_,
- random_id_, participants->count_,
- std::move(participants->participants_));
+ promise_.set_value(telegram_api::move_object_as<telegram_api::channels_channelParticipants>(participants_ptr));
break;
}
case telegram_api::channels_channelParticipantsNotModified::ID:
LOG(ERROR) << "Receive channelParticipantsNotModified";
- break;
+ return on_error(Status::Error(500, "Receive channelParticipantsNotModified"));
default:
UNREACHABLE();
}
-
- promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelParticipantsQuery");
- td->contacts_manager_->on_get_channel_participants_fail(channel_id_, std::move(filter_), offset_, limit_,
- random_id_);
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelParticipantsQuery");
promise_.set_error(std::move(status));
}
};
-class GetChannelAdministratorsQuery : public Td::ResultHandler {
+class GetChannelAdministratorsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
@@ -2175,22 +3228,24 @@ class GetChannelAdministratorsQuery : public Td::ResultHandler {
explicit GetChannelAdministratorsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, int32 hash) {
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ void send(ChannelId channel_id, int64 hash) {
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
if (input_channel == nullptr) {
- return promise_.set_error(Status::Error(3, "Supergroup not found"));
+ return promise_.set_error(Status::Error(400, "Supergroup not found"));
}
+ hash = 0; // to load even only ranks or creator changed
+
channel_id_ = channel_id;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_getParticipants(
+ send_query(G()->net_query_creator().create(telegram_api::channels_getParticipants(
std::move(input_channel), telegram_api::make_object<telegram_api::channelParticipantsAdmins>(), 0,
- std::numeric_limits<int32>::max(), hash))));
+ std::numeric_limits<int32>::max(), hash)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_getParticipants>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto participants_ptr = result_ptr.move_as_ok();
@@ -2198,19 +3253,28 @@ class GetChannelAdministratorsQuery : public Td::ResultHandler {
switch (participants_ptr->get_id()) {
case telegram_api::channels_channelParticipants::ID: {
auto participants = telegram_api::move_object_as<telegram_api::channels_channelParticipants>(participants_ptr);
- td->contacts_manager_->on_get_users(std::move(participants->users_));
- vector<UserId> administrator_user_ids;
- administrator_user_ids.reserve(participants->participants_.size());
+ td_->contacts_manager_->on_get_users(std::move(participants->users_), "GetChannelAdministratorsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(participants->chats_), "GetChannelAdministratorsQuery");
+
+ auto channel_type = td_->contacts_manager_->get_channel_type(channel_id_);
+ vector<DialogAdministrator> administrators;
+ administrators.reserve(participants->participants_.size());
for (auto &participant : participants->participants_) {
- UserId user_id;
- downcast_call(*participant, [&user_id](auto &participant) { user_id = UserId(participant.user_id_); });
- if (user_id.is_valid()) {
- administrator_user_ids.push_back(user_id);
+ DialogParticipant dialog_participant(std::move(participant), channel_type);
+ if (!dialog_participant.is_valid() || !dialog_participant.status_.is_administrator() ||
+ dialog_participant.dialog_id_.get_type() != DialogType::User) {
+ LOG(ERROR) << "Receive " << dialog_participant << " as an administrator of " << channel_id_;
+ continue;
}
+ administrators.emplace_back(dialog_participant.dialog_id_.get_user_id(),
+ dialog_participant.status_.get_rank(), dialog_participant.status_.is_creator());
}
- td->contacts_manager_->on_update_dialog_administrators(DialogId(channel_id_), std::move(administrator_user_ids),
- true);
+ td_->contacts_manager_->on_update_channel_administrator_count(channel_id_,
+ narrow_cast<int32>(administrators.size()));
+ td_->contacts_manager_->on_update_dialog_administrators(DialogId(channel_id_), std::move(administrators), true,
+ false);
+
break;
}
case telegram_api::channels_channelParticipantsNotModified::ID:
@@ -2222,108 +3286,345 @@ class GetChannelAdministratorsQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdministratorsQuery");
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdministratorsQuery");
promise_.set_error(std::move(status));
}
};
-class GetSupportUserQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class GetSupportUserQuery final : public Td::ResultHandler {
+ Promise<UserId> promise_;
public:
- explicit GetSupportUserQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit GetSupportUserQuery(Promise<UserId> &&promise) : promise_(std::move(promise)) {
}
void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::help_getSupport())));
+ send_query(G()->net_query_creator().create(telegram_api::help_getSupport()));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::help_getSupport>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for GetSupportUserQuery: " << to_string(ptr);
- td->contacts_manager_->on_get_user(std::move(ptr->user_), false, true);
+ auto user_id = ContactsManager::get_user_id(ptr->user_);
+ td_->contacts_manager_->on_get_user(std::move(ptr->user_), "GetSupportUserQuery", false);
- promise_.set_value(Unit());
+ promise_.set_value(std::move(user_id));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-StringBuilder &operator<<(StringBuilder &string_builder, ContactsManager::LinkState link_state) {
- switch (link_state) {
- case ContactsManager::LinkState::Unknown:
- string_builder << "unknown";
- break;
- case ContactsManager::LinkState::None:
- string_builder << "none";
- break;
- case ContactsManager::LinkState::KnowsPhoneNumber:
- string_builder << "knows phone number";
- break;
- case ContactsManager::LinkState::Contact:
- string_builder << "contact";
- break;
+tl_object_ptr<td_api::dateRange> ContactsManager::convert_date_range(
+ const tl_object_ptr<telegram_api::statsDateRangeDays> &obj) {
+ return make_tl_object<td_api::dateRange>(obj->min_date_, obj->max_date_);
+}
+
+tl_object_ptr<td_api::StatisticalGraph> ContactsManager::convert_stats_graph(
+ tl_object_ptr<telegram_api::StatsGraph> obj) {
+ CHECK(obj != nullptr);
+
+ switch (obj->get_id()) {
+ case telegram_api::statsGraphAsync::ID: {
+ auto graph = move_tl_object_as<telegram_api::statsGraphAsync>(obj);
+ return make_tl_object<td_api::statisticalGraphAsync>(std::move(graph->token_));
+ }
+ case telegram_api::statsGraphError::ID: {
+ auto graph = move_tl_object_as<telegram_api::statsGraphError>(obj);
+ return make_tl_object<td_api::statisticalGraphError>(std::move(graph->error_));
+ }
+ case telegram_api::statsGraph::ID: {
+ auto graph = move_tl_object_as<telegram_api::statsGraph>(obj);
+ return make_tl_object<td_api::statisticalGraphData>(std::move(graph->json_->data_),
+ std::move(graph->zoom_token_));
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
}
- return string_builder;
}
-bool ContactsManager::UserFull::is_bot_info_expired(int32 bot_info_version) const {
- return bot_info_version != -1 && (bot_info == nullptr || bot_info->version != bot_info_version);
+double ContactsManager::get_percentage_value(double part, double total) {
+ if (total < 1e-6 && total > -1e-6) {
+ if (part < 1e-6 && part > -1e-6) {
+ return 0.0;
+ }
+ return 100.0;
+ }
+ if (part > 1e20) {
+ return 100.0;
+ }
+ return part / total * 100;
}
-bool ContactsManager::UserFull::is_expired() const {
- return expires_at < Time::now();
+tl_object_ptr<td_api::statisticalValue> ContactsManager::convert_stats_absolute_value(
+ const tl_object_ptr<telegram_api::statsAbsValueAndPrev> &obj) {
+ return make_tl_object<td_api::statisticalValue>(obj->current_, obj->previous_,
+ get_percentage_value(obj->current_ - obj->previous_, obj->previous_));
}
-bool ContactsManager::ChannelFull::is_expired() const {
- return expires_at < Time::now();
+tl_object_ptr<td_api::chatStatisticsSupergroup> ContactsManager::convert_megagroup_stats(
+ tl_object_ptr<telegram_api::stats_megagroupStats> obj) {
+ CHECK(obj != nullptr);
+
+ on_get_users(std::move(obj->users_), "convert_megagroup_stats");
+
+ // just in case
+ td::remove_if(obj->top_posters_, [](auto &obj) {
+ return !UserId(obj->user_id_).is_valid() || obj->messages_ < 0 || obj->avg_chars_ < 0;
+ });
+ td::remove_if(obj->top_admins_, [](auto &obj) {
+ return !UserId(obj->user_id_).is_valid() || obj->deleted_ < 0 || obj->kicked_ < 0 || obj->banned_ < 0;
+ });
+ td::remove_if(obj->top_inviters_,
+ [](auto &obj) { return !UserId(obj->user_id_).is_valid() || obj->invitations_ < 0; });
+
+ auto top_senders =
+ transform(std::move(obj->top_posters_), [this](tl_object_ptr<telegram_api::statsGroupTopPoster> &&top_poster) {
+ return td_api::make_object<td_api::chatStatisticsMessageSenderInfo>(
+ get_user_id_object(UserId(top_poster->user_id_), "get_top_senders"), top_poster->messages_,
+ top_poster->avg_chars_);
+ });
+ auto top_administrators =
+ transform(std::move(obj->top_admins_), [this](tl_object_ptr<telegram_api::statsGroupTopAdmin> &&top_admin) {
+ return td_api::make_object<td_api::chatStatisticsAdministratorActionsInfo>(
+ get_user_id_object(UserId(top_admin->user_id_), "get_top_administrators"), top_admin->deleted_,
+ top_admin->kicked_, top_admin->banned_);
+ });
+ auto top_inviters =
+ transform(std::move(obj->top_inviters_), [this](tl_object_ptr<telegram_api::statsGroupTopInviter> &&top_inviter) {
+ return td_api::make_object<td_api::chatStatisticsInviterInfo>(
+ get_user_id_object(UserId(top_inviter->user_id_), "get_top_inviters"), top_inviter->invitations_);
+ });
+
+ return make_tl_object<td_api::chatStatisticsSupergroup>(
+ convert_date_range(obj->period_), convert_stats_absolute_value(obj->members_),
+ convert_stats_absolute_value(obj->messages_), convert_stats_absolute_value(obj->viewers_),
+ convert_stats_absolute_value(obj->posters_), convert_stats_graph(std::move(obj->growth_graph_)),
+ convert_stats_graph(std::move(obj->members_graph_)),
+ convert_stats_graph(std::move(obj->new_members_by_source_graph_)),
+ convert_stats_graph(std::move(obj->languages_graph_)), convert_stats_graph(std::move(obj->messages_graph_)),
+ convert_stats_graph(std::move(obj->actions_graph_)), convert_stats_graph(std::move(obj->top_hours_graph_)),
+ convert_stats_graph(std::move(obj->weekdays_graph_)), std::move(top_senders), std::move(top_administrators),
+ std::move(top_inviters));
+}
+
+tl_object_ptr<td_api::chatStatisticsChannel> ContactsManager::convert_broadcast_stats(
+ tl_object_ptr<telegram_api::stats_broadcastStats> obj) {
+ CHECK(obj != nullptr);
+
+ auto recent_message_interactions = transform(std::move(obj->recent_message_interactions_), [](auto &&interaction) {
+ return make_tl_object<td_api::chatStatisticsMessageInteractionInfo>(
+ MessageId(ServerMessageId(interaction->msg_id_)).get(), interaction->views_, interaction->forwards_);
+ });
+
+ return make_tl_object<td_api::chatStatisticsChannel>(
+ convert_date_range(obj->period_), convert_stats_absolute_value(obj->followers_),
+ convert_stats_absolute_value(obj->views_per_post_), convert_stats_absolute_value(obj->shares_per_post_),
+ get_percentage_value(obj->enabled_notifications_->part_, obj->enabled_notifications_->total_),
+ convert_stats_graph(std::move(obj->growth_graph_)), convert_stats_graph(std::move(obj->followers_graph_)),
+ convert_stats_graph(std::move(obj->mute_graph_)), convert_stats_graph(std::move(obj->top_hours_graph_)),
+ convert_stats_graph(std::move(obj->views_by_source_graph_)),
+ convert_stats_graph(std::move(obj->new_followers_by_source_graph_)),
+ convert_stats_graph(std::move(obj->languages_graph_)), convert_stats_graph(std::move(obj->interactions_graph_)),
+ convert_stats_graph(std::move(obj->iv_interactions_graph_)), std::move(recent_message_interactions));
}
-class ContactsManager::OnChatUpdate {
- ContactsManager *manager_;
+class GetMegagroupStatsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::ChatStatistics>> promise_;
+ ChannelId channel_id_;
public:
- explicit OnChatUpdate(ContactsManager *manager) : manager_(manager) {
+ explicit GetMegagroupStatsQuery(Promise<td_api::object_ptr<td_api::ChatStatistics>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, bool is_dark, DcId dc_id) {
+ channel_id_ = channel_id;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+
+ int32 flags = 0;
+ if (is_dark) {
+ flags |= telegram_api::stats_getMegagroupStats::DARK_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::stats_getMegagroupStats(flags, false /*ignored*/, std::move(input_channel)), {}, dc_id));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::stats_getMegagroupStats>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(td_->contacts_manager_->convert_megagroup_stats(result_ptr.move_as_ok()));
+ }
+
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetMegagroupStatsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetBroadcastStatsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::ChatStatistics>> promise_;
+ ChannelId channel_id_;
+
+ public:
+ explicit GetBroadcastStatsQuery(Promise<td_api::object_ptr<td_api::ChatStatistics>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, bool is_dark, DcId dc_id) {
+ channel_id_ = channel_id;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+
+ int32 flags = 0;
+ if (is_dark) {
+ flags |= telegram_api::stats_getBroadcastStats::DARK_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::stats_getBroadcastStats(flags, false /*ignored*/, std::move(input_channel)), {}, dc_id));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::stats_getBroadcastStats>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = ContactsManager::convert_broadcast_stats(result_ptr.move_as_ok());
+ for (auto &info : result->recent_message_interactions_) {
+ td_->messages_manager_->on_update_message_interaction_info({DialogId(channel_id_), MessageId(info->message_id_)},
+ info->view_count_, info->forward_count_, false,
+ nullptr);
+ }
+ promise_.set_value(std::move(result));
+ }
+
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetBroadcastStatsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+tl_object_ptr<td_api::messageStatistics> ContactsManager::convert_message_stats(
+ tl_object_ptr<telegram_api::stats_messageStats> obj) {
+ return make_tl_object<td_api::messageStatistics>(convert_stats_graph(std::move(obj->views_graph_)));
+}
+
+class GetMessageStatsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::messageStatistics>> promise_;
+ ChannelId channel_id_;
+
+ public:
+ explicit GetMessageStatsQuery(Promise<td_api::object_ptr<td_api::messageStatistics>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, MessageId message_id, bool is_dark, DcId dc_id) {
+ channel_id_ = channel_id;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+
+ int32 flags = 0;
+ if (is_dark) {
+ flags |= telegram_api::stats_getMessageStats::DARK_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::stats_getMessageStats(flags, false /*ignored*/, std::move(input_channel),
+ message_id.get_server_message_id().get()),
+ {}, dc_id));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::stats_getMessageStats>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(td_->contacts_manager_->convert_message_stats(result_ptr.move_as_ok()));
+ }
+
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetMessageStatsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class LoadAsyncGraphQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::StatisticalGraph>> promise_;
+
+ public:
+ explicit LoadAsyncGraphQuery(Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(const string &token, int64 x, DcId dc_id) {
+ int32 flags = 0;
+ if (x != 0) {
+ flags |= telegram_api::stats_loadAsyncGraph::X_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::stats_loadAsyncGraph(flags, token, x), {}, dc_id));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::stats_loadAsyncGraph>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ promise_.set_value(ContactsManager::convert_stats_graph(std::move(result)));
}
- template <class T>
- void operator()(T &func) const {
- manager_->on_chat_update(func);
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
}
};
-class ContactsManager::UploadProfilePhotoCallback : public FileManager::UploadCallback {
+class ContactsManager::UploadProfilePhotoCallback final : public FileManager::UploadCallback {
public:
- void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) override {
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->contacts_manager(), &ContactsManager::on_upload_profile_photo, file_id,
std::move(input_file));
}
- void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) override {
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
+ UNREACHABLE();
+ }
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
UNREACHABLE();
}
- void on_upload_error(FileId file_id, Status error) override {
+ void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->contacts_manager(), &ContactsManager::on_upload_profile_photo_error, file_id,
std::move(error));
}
};
-const CSlice ContactsManager::INVITE_LINK_URLS[3] = {"t.me/joinchat/", "telegram.me/joinchat/",
- "telegram.dog/joinchat/"};
-
ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
upload_profile_photo_callback_ = std::make_shared<UploadProfilePhotoCallback>();
my_id_ = load_my_id();
+ td_->option_manager_->set_option_integer("telegram_service_notifications_chat_id",
+ DialogId(get_service_notifications_user_id()).get());
+ td_->option_manager_->set_option_integer("replies_bot_chat_id", DialogId(get_replies_bot_user_id()).get());
+ td_->option_manager_->set_option_integer("group_anonymous_bot_user_id", get_anonymous_bot_user_id().get());
+ td_->option_manager_->set_option_integer("channel_bot_user_id", get_channel_bot_user_id().get());
+
if (G()->parameters().use_chat_info_db) {
auto next_contacts_sync_date_string = G()->td_db()->get_binlog_pmc()->get("next_contacts_sync_date");
if (!next_contacts_sync_date_string.empty()) {
@@ -2338,113 +3639,336 @@ ContactsManager::ContactsManager(Td *td, ActorShared<> parent) : td_(td), parent
G()->td_db()->get_binlog_pmc()->erase("next_contacts_sync_date");
G()->td_db()->get_binlog_pmc()->erase("saved_contact_count");
}
+ if (G()->parameters().use_file_db) {
+ G()->td_db()->get_sqlite_pmc()->erase_by_prefix("us_bot_info", Auto());
+ }
+
+ was_online_local_ = to_integer<int32>(G()->td_db()->get_binlog_pmc()->get("my_was_online_local"));
+ was_online_remote_ = to_integer<int32>(G()->td_db()->get_binlog_pmc()->get("my_was_online_remote"));
+ if (was_online_local_ >= G()->unix_time_cached() && !td_->is_online()) {
+ was_online_local_ = G()->unix_time_cached() - 1;
+ }
+
+ location_visibility_expire_date_ =
+ to_integer<int32>(G()->td_db()->get_binlog_pmc()->get("location_visibility_expire_date"));
+ if (location_visibility_expire_date_ != 0 && location_visibility_expire_date_ <= G()->unix_time()) {
+ location_visibility_expire_date_ = 0;
+ G()->td_db()->get_binlog_pmc()->erase("location_visibility_expire_date");
+ }
+ auto pending_location_visibility_expire_date_string =
+ G()->td_db()->get_binlog_pmc()->get("pending_location_visibility_expire_date");
+ if (!pending_location_visibility_expire_date_string.empty()) {
+ pending_location_visibility_expire_date_ = to_integer<int32>(pending_location_visibility_expire_date_string);
+ }
+ update_is_location_visible();
+ LOG(INFO) << "Loaded location_visibility_expire_date = " << location_visibility_expire_date_
+ << " and pending_location_visibility_expire_date = " << pending_location_visibility_expire_date_;
user_online_timeout_.set_callback(on_user_online_timeout_callback);
user_online_timeout_.set_callback_data(static_cast<void *>(this));
+ user_emoji_status_timeout_.set_callback(on_user_emoji_status_timeout_callback);
+ user_emoji_status_timeout_.set_callback_data(static_cast<void *>(this));
+
channel_unban_timeout_.set_callback(on_channel_unban_timeout_callback);
channel_unban_timeout_.set_callback_data(static_cast<void *>(this));
+
+ user_nearby_timeout_.set_callback(on_user_nearby_timeout_callback);
+ user_nearby_timeout_.set_callback_data(static_cast<void *>(this));
+
+ slow_mode_delay_timeout_.set_callback(on_slow_mode_delay_timeout_callback);
+ slow_mode_delay_timeout_.set_callback_data(static_cast<void *>(this));
+
+ invite_link_info_expire_timeout_.set_callback(on_invite_link_info_expire_timeout_callback);
+ invite_link_info_expire_timeout_.set_callback_data(static_cast<void *>(this));
+
+ channel_participant_cache_timeout_.set_callback(on_channel_participant_cache_timeout_callback);
+ channel_participant_cache_timeout_.set_callback_data(static_cast<void *>(this));
+}
+
+ContactsManager::~ContactsManager() {
+ Scheduler::instance()->destroy_on_scheduler(
+ G()->get_gc_scheduler_id(), users_, users_full_, user_photos_, unknown_users_, pending_user_photos_,
+ user_profile_photo_file_source_ids_, my_photo_file_id_, user_full_file_source_ids_, chats_, chats_full_,
+ unknown_chats_, chat_full_file_source_ids_, min_channels_, channels_, channels_full_, unknown_channels_,
+ invalidated_channels_full_, channel_full_file_source_ids_, secret_chats_, unknown_secret_chats_,
+ secret_chats_with_user_, invite_link_infos_, dialog_access_by_invite_link_, loaded_from_database_users_,
+ unavailable_user_fulls_, loaded_from_database_chats_, unavailable_chat_fulls_, loaded_from_database_channels_,
+ unavailable_channel_fulls_, loaded_from_database_secret_chats_, dialog_administrators_,
+ cached_channel_participants_, resolved_phone_numbers_, channel_participants_, all_imported_contacts_,
+ linked_channel_ids_, restricted_user_ids_, restricted_channel_ids_);
+}
+
+void ContactsManager::start_up() {
+ if (!pending_location_visibility_expire_date_) {
+ try_send_set_location_visibility_query();
+ }
}
void ContactsManager::tear_down() {
parent_.reset();
+
+ LOG(DEBUG) << "Have " << users_.calc_size() << " users, " << chats_.calc_size() << " basic groups, "
+ << channels_.calc_size() << " supergroups and " << secret_chats_.calc_size() << " secret chats to free";
+ LOG(DEBUG) << "Have " << users_full_.calc_size() << " full users, " << chats_full_.calc_size()
+ << " full basic groups and " << channels_full_.calc_size() << " full supergroups to free";
}
UserId ContactsManager::load_my_id() {
auto id_string = G()->td_db()->get_binlog_pmc()->get("my_id");
if (!id_string.empty()) {
- UserId my_id(to_integer<int32>(id_string));
+ UserId my_id(to_integer<int64>(id_string));
if (my_id.is_valid()) {
return my_id;
}
- my_id = UserId(to_integer<int32>(Slice(id_string).substr(5)));
+ my_id = UserId(to_integer<int64>(Slice(id_string).substr(5)));
if (my_id.is_valid()) {
G()->td_db()->get_binlog_pmc()->set("my_id", to_string(my_id.get()));
return my_id;
}
- LOG(ERROR) << "Wrong my id = \"" << id_string << "\" stored in database";
+ LOG(ERROR) << "Wrong my ID = \"" << id_string << "\" stored in database";
}
return UserId();
}
void ContactsManager::on_user_online_timeout_callback(void *contacts_manager_ptr, int64 user_id_long) {
+ if (G()->close_flag()) {
+ return;
+ }
+
auto contacts_manager = static_cast<ContactsManager *>(contacts_manager_ptr);
- UserId user_id(narrow_cast<int32>(user_id_long));
- auto u = contacts_manager->get_user(user_id);
+ send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_user_online_timeout,
+ UserId(user_id_long));
+}
+
+void ContactsManager::on_user_online_timeout(UserId user_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto u = get_user(user_id);
CHECK(u != nullptr);
+ CHECK(u->is_update_user_sent);
LOG(INFO) << "Update " << user_id << " online status to offline";
- send_closure_later(
- G()->td(), &Td::send_update,
- make_tl_object<td_api::updateUserStatus>(user_id.get(), contacts_manager->get_user_status_object(user_id, u)));
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateUserStatus>(user_id.get(), get_user_status_object(user_id, u)));
+
+ update_user_online_member_count(u);
+}
+
+void ContactsManager::on_user_emoji_status_timeout_callback(void *contacts_manager_ptr, int64 user_id_long) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto contacts_manager = static_cast<ContactsManager *>(contacts_manager_ptr);
+ send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_user_emoji_status_timeout,
+ UserId(user_id_long));
+}
+
+void ContactsManager::on_user_emoji_status_timeout(UserId user_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto u = get_user(user_id);
+ CHECK(u != nullptr);
+ CHECK(u->is_update_user_sent);
+
+ update_user(u, user_id);
}
void ContactsManager::on_channel_unban_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long) {
- auto td = static_cast<ContactsManager *>(contacts_manager_ptr)->td_;
- send_closure_later(td->actor_id(td), &Td::on_channel_unban_timeout, channel_id_long);
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto contacts_manager = static_cast<ContactsManager *>(contacts_manager_ptr);
+ send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_channel_unban_timeout,
+ ChannelId(channel_id_long));
}
void ContactsManager::on_channel_unban_timeout(ChannelId channel_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
auto c = get_channel(channel_id);
CHECK(c != nullptr);
auto old_status = c->status;
c->status.update_restrictions();
- LOG_IF(ERROR, c->status == old_status && (c->status.is_restricted() || c->status.is_banned()))
- << "Status of " << channel_id << " wasn't updated: " << c->status;
+ if (c->status == old_status) {
+ LOG_IF(ERROR, c->status.is_restricted() || c->status.is_banned())
+ << "Status of " << channel_id << " wasn't updated: " << c->status;
+ } else {
+ c->is_changed = true;
+ }
LOG(INFO) << "Update " << channel_id << " status";
c->is_status_changed = true;
- invalidate_channel_full(channel_id);
+ invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_unban_timeout");
update_channel(c, channel_id); // always call, because in case of failure we need to reactivate timeout
}
-template <class StorerT>
-void ContactsManager::store_link_state(const LinkState &link_state, StorerT &storer) {
- store(static_cast<uint32>(link_state), storer);
+void ContactsManager::on_user_nearby_timeout_callback(void *contacts_manager_ptr, int64 user_id_long) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto contacts_manager = static_cast<ContactsManager *>(contacts_manager_ptr);
+ send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_user_nearby_timeout,
+ UserId(user_id_long));
}
-template <class ParserT>
-void ContactsManager::parse_link_state(LinkState &link_state, ParserT &parser) {
- uint32 link_state_uint32;
- parse(link_state_uint32, parser);
- link_state = static_cast<LinkState>(static_cast<uint8>(link_state_uint32));
+void ContactsManager::on_user_nearby_timeout(UserId user_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto u = get_user(user_id);
+ CHECK(u != nullptr);
+
+ LOG(INFO) << "Remove " << user_id << " from nearby list";
+ DialogId dialog_id(user_id);
+ for (size_t i = 0; i < users_nearby_.size(); i++) {
+ if (users_nearby_[i].dialog_id == dialog_id) {
+ users_nearby_.erase(users_nearby_.begin() + i);
+ send_update_users_nearby();
+ return;
+ }
+ }
+}
+
+void ContactsManager::on_slow_mode_delay_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto contacts_manager = static_cast<ContactsManager *>(contacts_manager_ptr);
+ send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_slow_mode_delay_timeout,
+ ChannelId(channel_id_long));
+}
+
+void ContactsManager::on_slow_mode_delay_timeout(ChannelId channel_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ on_update_channel_slow_mode_next_send_date(channel_id, 0);
+}
+
+void ContactsManager::on_invite_link_info_expire_timeout_callback(void *contacts_manager_ptr, int64 dialog_id_long) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto contacts_manager = static_cast<ContactsManager *>(contacts_manager_ptr);
+ send_closure_later(contacts_manager->actor_id(contacts_manager), &ContactsManager::on_invite_link_info_expire_timeout,
+ DialogId(dialog_id_long));
+}
+
+void ContactsManager::on_invite_link_info_expire_timeout(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto access_it = dialog_access_by_invite_link_.find(dialog_id);
+ if (access_it == dialog_access_by_invite_link_.end()) {
+ return;
+ }
+ auto expires_in = access_it->second.accessible_before - G()->unix_time() - 1;
+ if (expires_in >= 3) {
+ invite_link_info_expire_timeout_.set_timeout_in(dialog_id.get(), expires_in);
+ return;
+ }
+
+ remove_dialog_access_by_invite_link(dialog_id);
+}
+
+void ContactsManager::on_channel_participant_cache_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto contacts_manager = static_cast<ContactsManager *>(contacts_manager_ptr);
+ send_closure_later(contacts_manager->actor_id(contacts_manager),
+ &ContactsManager::on_channel_participant_cache_timeout, ChannelId(channel_id_long));
+}
+
+void ContactsManager::on_channel_participant_cache_timeout(ChannelId channel_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto channel_participants_it = channel_participants_.find(channel_id);
+ if (channel_participants_it == channel_participants_.end()) {
+ return;
+ }
+
+ auto &participants = channel_participants_it->second.participants_;
+ auto min_access_date = G()->unix_time() - CHANNEL_PARTICIPANT_CACHE_TIME;
+ table_remove_if(participants,
+ [min_access_date](const auto &it) { return it.second.last_access_date_ < min_access_date; });
+
+ if (participants.empty()) {
+ channel_participants_.erase(channel_participants_it);
+ } else {
+ channel_participant_cache_timeout_.set_timeout_in(channel_id.get(), CHANNEL_PARTICIPANT_CACHE_TIME);
+ }
}
template <class StorerT>
void ContactsManager::User::store(StorerT &storer) const {
using td::store;
bool has_last_name = !last_name.empty();
- bool has_username = !username.empty();
+ bool legacy_has_username = false;
bool has_photo = photo.small_file_id.is_valid();
- bool is_restricted = !restriction_reason.empty();
bool has_language_code = !language_code.empty();
bool have_access_hash = access_hash != -1;
+ bool has_cache_version = cache_version != 0;
+ bool has_is_contact = true;
+ bool has_restriction_reasons = !restriction_reasons.empty();
+ bool has_emoji_status = !emoji_status.is_empty();
+ bool has_usernames = !usernames.is_empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(is_received);
STORE_FLAG(is_verified);
STORE_FLAG(is_deleted);
STORE_FLAG(is_bot);
STORE_FLAG(can_join_groups);
- STORE_FLAG(can_read_all_group_messages);
+ STORE_FLAG(can_read_all_group_messages); // 5
STORE_FLAG(is_inline_bot);
STORE_FLAG(need_location_bot);
STORE_FLAG(has_last_name);
- STORE_FLAG(has_username);
- STORE_FLAG(has_photo);
- STORE_FLAG(is_restricted);
+ STORE_FLAG(legacy_has_username);
+ STORE_FLAG(has_photo); // 10
+ STORE_FLAG(false); // legacy is_restricted
STORE_FLAG(has_language_code);
STORE_FLAG(have_access_hash);
+ STORE_FLAG(is_support);
+ STORE_FLAG(is_min_access_hash); // 15
+ STORE_FLAG(is_scam);
+ STORE_FLAG(has_cache_version);
+ STORE_FLAG(has_is_contact);
+ STORE_FLAG(is_contact);
+ STORE_FLAG(is_mutual_contact); // 20
+ STORE_FLAG(has_restriction_reasons);
+ STORE_FLAG(need_apply_min_photo);
+ STORE_FLAG(is_fake);
+ STORE_FLAG(can_be_added_to_attach_menu);
+ STORE_FLAG(is_premium); // 25
+ STORE_FLAG(attach_menu_enabled);
+ STORE_FLAG(has_emoji_status);
+ STORE_FLAG(has_usernames);
END_STORE_FLAGS();
store(first_name, storer);
if (has_last_name) {
store(last_name, storer);
}
- if (has_username) {
- store(username, storer);
- }
store(phone_number, storer);
if (have_access_hash) {
store(access_hash, storer);
@@ -2452,11 +3976,9 @@ void ContactsManager::User::store(StorerT &storer) const {
if (has_photo) {
store(photo, storer);
}
- store_link_state(inbound, storer);
- store_link_state(outbound, storer);
store(was_online, storer);
- if (is_restricted) {
- store(restriction_reason, storer);
+ if (has_restriction_reasons) {
+ store(restriction_reasons, storer);
}
if (is_inline_bot) {
store(inline_query_placeholder, storer);
@@ -2467,17 +3989,31 @@ void ContactsManager::User::store(StorerT &storer) const {
if (has_language_code) {
store(language_code, storer);
}
+ if (has_cache_version) {
+ store(cache_version, storer);
+ }
+ if (has_emoji_status) {
+ store(emoji_status, storer);
+ }
+ if (has_usernames) {
+ store(usernames, storer);
+ }
}
template <class ParserT>
void ContactsManager::User::parse(ParserT &parser) {
using td::parse;
bool has_last_name;
- bool has_username;
+ bool legacy_has_username;
bool has_photo;
- bool is_restricted;
+ bool legacy_is_restricted;
bool has_language_code;
bool have_access_hash;
+ bool has_cache_version;
+ bool has_is_contact;
+ bool has_restriction_reasons;
+ bool has_emoji_status;
+ bool has_usernames;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_received);
PARSE_FLAG(is_verified);
@@ -2488,18 +4024,36 @@ void ContactsManager::User::parse(ParserT &parser) {
PARSE_FLAG(is_inline_bot);
PARSE_FLAG(need_location_bot);
PARSE_FLAG(has_last_name);
- PARSE_FLAG(has_username);
+ PARSE_FLAG(legacy_has_username);
PARSE_FLAG(has_photo);
- PARSE_FLAG(is_restricted);
+ PARSE_FLAG(legacy_is_restricted);
PARSE_FLAG(has_language_code);
PARSE_FLAG(have_access_hash);
+ PARSE_FLAG(is_support);
+ PARSE_FLAG(is_min_access_hash);
+ PARSE_FLAG(is_scam);
+ PARSE_FLAG(has_cache_version);
+ PARSE_FLAG(has_is_contact);
+ PARSE_FLAG(is_contact);
+ PARSE_FLAG(is_mutual_contact);
+ PARSE_FLAG(has_restriction_reasons);
+ PARSE_FLAG(need_apply_min_photo);
+ PARSE_FLAG(is_fake);
+ PARSE_FLAG(can_be_added_to_attach_menu);
+ PARSE_FLAG(is_premium);
+ PARSE_FLAG(attach_menu_enabled);
+ PARSE_FLAG(has_emoji_status);
+ PARSE_FLAG(has_usernames);
END_PARSE_FLAGS();
parse(first_name, parser);
if (has_last_name) {
parse(last_name, parser);
}
- if (has_username) {
+ if (legacy_has_username) {
+ CHECK(!has_usernames);
+ string username;
parse(username, parser);
+ usernames = Usernames(std::move(username), vector<telegram_api::object_ptr<telegram_api::username>>());
}
parse(phone_number, parser);
if (parser.version() < static_cast<int32>(Version::FixMinUsers)) {
@@ -2507,15 +4061,30 @@ void ContactsManager::User::parse(ParserT &parser) {
}
if (have_access_hash) {
parse(access_hash, parser);
+ } else {
+ is_min_access_hash = true;
}
if (has_photo) {
parse(photo, parser);
}
- parse_link_state(inbound, parser);
- parse_link_state(outbound, parser);
+ if (!has_is_contact) {
+ // enum class LinkState : uint8 { Unknown, None, KnowsPhoneNumber, Contact };
+
+ uint32 link_state_inbound;
+ uint32 link_state_outbound;
+ parse(link_state_inbound, parser);
+ parse(link_state_outbound, parser);
+
+ is_contact = link_state_outbound == 3;
+ is_mutual_contact = is_contact && link_state_inbound == 3;
+ }
parse(was_online, parser);
- if (is_restricted) {
+ if (legacy_is_restricted) {
+ string restriction_reason;
parse(restriction_reason, parser);
+ restriction_reasons = get_restriction_reasons(restriction_reason);
+ } else if (has_restriction_reasons) {
+ parse(restriction_reasons, parser);
}
if (is_inline_bot) {
parse(inline_query_placeholder, parser);
@@ -2526,24 +4095,205 @@ void ContactsManager::User::parse(ParserT &parser) {
if (has_language_code) {
parse(language_code, parser);
}
- if (first_name.empty()) {
+ if (has_cache_version) {
+ parse(cache_version, parser);
+ }
+ if (has_emoji_status) {
+ parse(emoji_status, parser);
+ }
+ if (has_usernames) {
+ CHECK(!legacy_has_username);
+ parse(usernames, parser);
+ }
+
+ if (!check_utf8(first_name)) {
+ LOG(ERROR) << "Have invalid first name \"" << first_name << '"';
+ first_name.clear();
+ cache_version = 0;
+ }
+ if (!check_utf8(last_name)) {
+ LOG(ERROR) << "Have invalid last name \"" << last_name << '"';
+ last_name.clear();
+ cache_version = 0;
+ }
+
+ clean_phone_number(phone_number);
+ if (first_name.empty() && last_name.empty()) {
first_name = phone_number;
}
+ if (!is_contact && is_mutual_contact) {
+ LOG(ERROR) << "Have invalid flag is_mutual_contact";
+ is_mutual_contact = false;
+ cache_version = 0;
+ }
+}
+
+template <class StorerT>
+void ContactsManager::UserFull::store(StorerT &storer) const {
+ using td::store;
+ bool has_about = !about.empty();
+ bool has_photo = !photo.is_empty();
+ bool has_description = !description.empty();
+ bool has_commands = !commands.empty();
+ bool has_private_forward_name = !private_forward_name.empty();
+ bool has_group_administrator_rights = group_administrator_rights != AdministratorRights();
+ bool has_broadcast_administrator_rights = broadcast_administrator_rights != AdministratorRights();
+ bool has_menu_button = menu_button != nullptr;
+ bool has_description_photo = !description_photo.is_empty();
+ bool has_description_animation = description_animation_file_id.is_valid();
+ bool has_premium_gift_options = !premium_gift_options.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_about);
+ STORE_FLAG(is_blocked);
+ STORE_FLAG(can_be_called);
+ STORE_FLAG(has_private_calls);
+ STORE_FLAG(can_pin_messages);
+ STORE_FLAG(need_phone_number_privacy_exception);
+ STORE_FLAG(has_photo);
+ STORE_FLAG(supports_video_calls);
+ STORE_FLAG(has_description);
+ STORE_FLAG(has_commands);
+ STORE_FLAG(has_private_forward_name);
+ STORE_FLAG(has_group_administrator_rights);
+ STORE_FLAG(has_broadcast_administrator_rights);
+ STORE_FLAG(has_menu_button);
+ STORE_FLAG(has_description_photo);
+ STORE_FLAG(has_description_animation);
+ STORE_FLAG(has_premium_gift_options);
+ STORE_FLAG(voice_messages_forbidden);
+ END_STORE_FLAGS();
+ if (has_about) {
+ store(about, storer);
+ }
+ store(common_chat_count, storer);
+ store_time(expires_at, storer);
+ if (has_photo) {
+ store(photo, storer);
+ }
+ if (has_description) {
+ store(description, storer);
+ }
+ if (has_commands) {
+ store(commands, storer);
+ }
+ if (has_private_forward_name) {
+ store(private_forward_name, storer);
+ }
+ if (has_group_administrator_rights) {
+ store(group_administrator_rights, storer);
+ }
+ if (has_broadcast_administrator_rights) {
+ store(broadcast_administrator_rights, storer);
+ }
+ if (has_menu_button) {
+ store(menu_button, storer);
+ }
+ if (has_description_photo) {
+ store(description_photo, storer);
+ }
+ if (has_description_animation) {
+ storer.context()->td().get_actor_unsafe()->animations_manager_->store_animation(description_animation_file_id,
+ storer);
+ }
+ if (has_premium_gift_options) {
+ store(premium_gift_options, storer);
+ }
+}
+
+template <class ParserT>
+void ContactsManager::UserFull::parse(ParserT &parser) {
+ using td::parse;
+ bool has_about;
+ bool has_photo;
+ bool has_description;
+ bool has_commands;
+ bool has_private_forward_name;
+ bool has_group_administrator_rights;
+ bool has_broadcast_administrator_rights;
+ bool has_menu_button;
+ bool has_description_photo;
+ bool has_description_animation;
+ bool has_premium_gift_options;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_about);
+ PARSE_FLAG(is_blocked);
+ PARSE_FLAG(can_be_called);
+ PARSE_FLAG(has_private_calls);
+ PARSE_FLAG(can_pin_messages);
+ PARSE_FLAG(need_phone_number_privacy_exception);
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(supports_video_calls);
+ PARSE_FLAG(has_description);
+ PARSE_FLAG(has_commands);
+ PARSE_FLAG(has_private_forward_name);
+ PARSE_FLAG(has_group_administrator_rights);
+ PARSE_FLAG(has_broadcast_administrator_rights);
+ PARSE_FLAG(has_menu_button);
+ PARSE_FLAG(has_description_photo);
+ PARSE_FLAG(has_description_animation);
+ PARSE_FLAG(has_premium_gift_options);
+ PARSE_FLAG(voice_messages_forbidden);
+ END_PARSE_FLAGS();
+ if (has_about) {
+ parse(about, parser);
+ }
+ parse(common_chat_count, parser);
+ parse_time(expires_at, parser);
+ if (has_photo) {
+ parse(photo, parser);
+ }
+ if (has_description) {
+ parse(description, parser);
+ }
+ if (has_commands) {
+ parse(commands, parser);
+ }
+ if (has_private_forward_name) {
+ parse(private_forward_name, parser);
+ }
+ if (has_group_administrator_rights) {
+ parse(group_administrator_rights, parser);
+ }
+ if (has_broadcast_administrator_rights) {
+ parse(broadcast_administrator_rights, parser);
+ }
+ if (has_menu_button) {
+ parse(menu_button, parser);
+ }
+ if (has_description_photo) {
+ parse(description_photo, parser);
+ }
+ if (has_description_animation) {
+ description_animation_file_id =
+ parser.context()->td().get_actor_unsafe()->animations_manager_->parse_animation(parser);
+ }
+ if (has_premium_gift_options) {
+ parse(premium_gift_options, parser);
+ }
}
template <class StorerT>
void ContactsManager::Chat::store(StorerT &storer) const {
using td::store;
bool has_photo = photo.small_file_id.is_valid();
+ bool use_new_rights = true;
+ bool has_default_permissions_version = default_permissions_version != -1;
+ bool has_pinned_message_version = pinned_message_version != -1;
+ bool has_cache_version = cache_version != 0;
BEGIN_STORE_FLAGS();
- STORE_FLAG(left);
- STORE_FLAG(kicked);
- STORE_FLAG(is_creator);
- STORE_FLAG(is_administrator);
- STORE_FLAG(everyone_is_administrator);
- STORE_FLAG(can_edit);
+ STORE_FLAG(false);
+ STORE_FLAG(false);
+ STORE_FLAG(false);
+ STORE_FLAG(false);
+ STORE_FLAG(false);
+ STORE_FLAG(false);
STORE_FLAG(is_active);
STORE_FLAG(has_photo);
+ STORE_FLAG(use_new_rights);
+ STORE_FLAG(has_default_permissions_version);
+ STORE_FLAG(has_pinned_message_version);
+ STORE_FLAG(has_cache_version);
+ STORE_FLAG(noforwards);
END_STORE_FLAGS();
store(title, storer);
@@ -2554,12 +4304,33 @@ void ContactsManager::Chat::store(StorerT &storer) const {
store(date, storer);
store(migrated_to_channel_id, storer);
store(version, storer);
+ store(status, storer);
+ store(default_permissions, storer);
+ if (has_default_permissions_version) {
+ store(default_permissions_version, storer);
+ }
+ if (has_pinned_message_version) {
+ store(pinned_message_version, storer);
+ }
+ if (has_cache_version) {
+ store(cache_version, storer);
+ }
}
template <class ParserT>
void ContactsManager::Chat::parse(ParserT &parser) {
using td::parse;
bool has_photo;
+ bool left;
+ bool kicked;
+ bool is_creator;
+ bool is_administrator;
+ bool everyone_is_administrator;
+ bool can_edit;
+ bool use_new_rights;
+ bool has_default_permissions_version;
+ bool has_pinned_message_version;
+ bool has_cache_version;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(left);
PARSE_FLAG(kicked);
@@ -2569,6 +4340,11 @@ void ContactsManager::Chat::parse(ParserT &parser) {
PARSE_FLAG(can_edit);
PARSE_FLAG(is_active);
PARSE_FLAG(has_photo);
+ PARSE_FLAG(use_new_rights);
+ PARSE_FLAG(has_default_permissions_version);
+ PARSE_FLAG(has_pinned_message_version);
+ PARSE_FLAG(has_cache_version);
+ PARSE_FLAG(noforwards);
END_PARSE_FLAGS();
parse(title, parser);
@@ -2579,32 +4355,169 @@ void ContactsManager::Chat::parse(ParserT &parser) {
parse(date, parser);
parse(migrated_to_channel_id, parser);
parse(version, parser);
+ if (use_new_rights) {
+ parse(status, parser);
+ parse(default_permissions, parser);
+ } else {
+ if (can_edit != (is_creator || is_administrator || everyone_is_administrator)) {
+ LOG(ERROR) << "Have wrong can_edit flag";
+ }
+
+ if (kicked || !is_active) {
+ status = DialogParticipantStatus::Banned(0);
+ } else if (left) {
+ status = DialogParticipantStatus::Left();
+ } else if (is_creator) {
+ status = DialogParticipantStatus::Creator(true, false, string());
+ } else if (is_administrator && !everyone_is_administrator) {
+ status = DialogParticipantStatus::GroupAdministrator(false);
+ } else {
+ status = DialogParticipantStatus::Member();
+ }
+ default_permissions = RestrictedRights(true, true, true, true, true, true, true, true, everyone_is_administrator,
+ everyone_is_administrator, everyone_is_administrator, false);
+ }
+ if (has_default_permissions_version) {
+ parse(default_permissions_version, parser);
+ }
+ if (has_pinned_message_version) {
+ parse(pinned_message_version, parser);
+ }
+ if (has_cache_version) {
+ parse(cache_version, parser);
+ }
+
+ if (!check_utf8(title)) {
+ LOG(ERROR) << "Have invalid title \"" << title << '"';
+ title.clear();
+ cache_version = 0;
+ }
+
+ if (status.is_administrator() && !status.is_creator()) {
+ status = DialogParticipantStatus::GroupAdministrator(false);
+ }
+}
+
+template <class StorerT>
+void ContactsManager::ChatFull::store(StorerT &storer) const {
+ using td::store;
+ bool has_description = !description.empty();
+ bool has_legacy_invite_link = false;
+ bool has_photo = !photo.is_empty();
+ bool has_invite_link = invite_link.is_valid();
+ bool has_bot_commands = !bot_commands.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_description);
+ STORE_FLAG(has_legacy_invite_link);
+ STORE_FLAG(can_set_username);
+ STORE_FLAG(has_photo);
+ STORE_FLAG(has_invite_link);
+ STORE_FLAG(has_bot_commands);
+ END_STORE_FLAGS();
+ store(version, storer);
+ store(creator_user_id, storer);
+ store(participants, storer);
+ if (has_description) {
+ store(description, storer);
+ }
+ if (has_photo) {
+ store(photo, storer);
+ }
+ if (has_invite_link) {
+ store(invite_link, storer);
+ }
+ if (has_bot_commands) {
+ store(bot_commands, storer);
+ }
+}
+
+template <class ParserT>
+void ContactsManager::ChatFull::parse(ParserT &parser) {
+ using td::parse;
+ bool has_description;
+ bool legacy_has_invite_link;
+ bool has_photo;
+ bool has_invite_link;
+ bool has_bot_commands;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_description);
+ PARSE_FLAG(legacy_has_invite_link);
+ PARSE_FLAG(can_set_username);
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(has_invite_link);
+ PARSE_FLAG(has_bot_commands);
+ END_PARSE_FLAGS();
+ parse(version, parser);
+ parse(creator_user_id, parser);
+ parse(participants, parser);
+ if (has_description) {
+ parse(description, parser);
+ }
+ if (legacy_has_invite_link) {
+ string legacy_invite_link;
+ parse(legacy_invite_link, parser);
+ }
+ if (has_photo) {
+ parse(photo, parser);
+ }
+ if (has_invite_link) {
+ parse(invite_link, parser);
+ }
+ if (has_bot_commands) {
+ parse(bot_commands, parser);
+ }
}
template <class StorerT>
void ContactsManager::Channel::store(StorerT &storer) const {
using td::store;
bool has_photo = photo.small_file_id.is_valid();
- bool has_username = !username.empty();
- bool is_restricted = !restriction_reason.empty();
+ bool legacy_has_username = false;
bool use_new_rights = true;
- bool have_participant_count = participant_count != 0;
+ bool has_participant_count = participant_count != 0;
+ bool have_default_permissions = true;
+ bool has_cache_version = cache_version != 0;
+ bool has_restriction_reasons = !restriction_reasons.empty();
+ bool legacy_has_active_group_call = false;
+ bool has_usernames = !usernames.is_empty();
+ bool has_flags2 = true;
BEGIN_STORE_FLAGS();
STORE_FLAG(false);
STORE_FLAG(false);
- STORE_FLAG(anyone_can_invite);
- STORE_FLAG(sign_messages);
STORE_FLAG(false);
+ STORE_FLAG(sign_messages);
STORE_FLAG(false);
+ STORE_FLAG(false); // 5
STORE_FLAG(false);
STORE_FLAG(is_megagroup);
STORE_FLAG(is_verified);
STORE_FLAG(has_photo);
- STORE_FLAG(has_username);
- STORE_FLAG(is_restricted);
+ STORE_FLAG(legacy_has_username); // 10
+ STORE_FLAG(false);
STORE_FLAG(use_new_rights);
- STORE_FLAG(have_participant_count);
+ STORE_FLAG(has_participant_count);
+ STORE_FLAG(have_default_permissions);
+ STORE_FLAG(is_scam); // 15
+ STORE_FLAG(has_cache_version);
+ STORE_FLAG(has_linked_channel);
+ STORE_FLAG(has_location);
+ STORE_FLAG(is_slow_mode_enabled);
+ STORE_FLAG(has_restriction_reasons); // 20
+ STORE_FLAG(legacy_has_active_group_call);
+ STORE_FLAG(is_fake);
+ STORE_FLAG(is_gigagroup);
+ STORE_FLAG(noforwards);
+ STORE_FLAG(can_be_deleted); // 25
+ STORE_FLAG(join_to_send);
+ STORE_FLAG(join_request);
+ STORE_FLAG(has_usernames);
+ STORE_FLAG(has_flags2);
END_STORE_FLAGS();
+ if (has_flags2) {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_forum);
+ END_STORE_FLAGS();
+ }
store(status, storer);
store(access_hash, storer);
@@ -2612,31 +4525,44 @@ void ContactsManager::Channel::store(StorerT &storer) const {
if (has_photo) {
store(photo, storer);
}
- if (has_username) {
- store(username, storer);
- }
store(date, storer);
- if (is_restricted) {
- store(restriction_reason, storer);
+ if (has_restriction_reasons) {
+ store(restriction_reasons, storer);
}
- if (have_participant_count) {
+ if (has_participant_count) {
store(participant_count, storer);
}
+ if (is_megagroup) {
+ store(default_permissions, storer);
+ }
+ if (has_cache_version) {
+ store(cache_version, storer);
+ }
+ if (has_usernames) {
+ store(usernames, storer);
+ }
}
template <class ParserT>
void ContactsManager::Channel::parse(ParserT &parser) {
using td::parse;
bool has_photo;
- bool has_username;
- bool is_restricted;
+ bool legacy_has_username;
+ bool legacy_is_restricted;
bool left;
bool kicked;
bool is_creator;
bool can_edit;
bool can_moderate;
+ bool anyone_can_invite;
bool use_new_rights;
- bool have_participant_count;
+ bool has_participant_count;
+ bool have_default_permissions;
+ bool has_cache_version;
+ bool has_restriction_reasons;
+ bool legacy_has_active_group_call;
+ bool has_usernames;
+ bool has_flags2;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(left);
PARSE_FLAG(kicked);
@@ -2648,11 +4574,32 @@ void ContactsManager::Channel::parse(ParserT &parser) {
PARSE_FLAG(is_megagroup);
PARSE_FLAG(is_verified);
PARSE_FLAG(has_photo);
- PARSE_FLAG(has_username);
- PARSE_FLAG(is_restricted);
+ PARSE_FLAG(legacy_has_username);
+ PARSE_FLAG(legacy_is_restricted);
PARSE_FLAG(use_new_rights);
- PARSE_FLAG(have_participant_count);
+ PARSE_FLAG(has_participant_count);
+ PARSE_FLAG(have_default_permissions);
+ PARSE_FLAG(is_scam);
+ PARSE_FLAG(has_cache_version);
+ PARSE_FLAG(has_linked_channel);
+ PARSE_FLAG(has_location);
+ PARSE_FLAG(is_slow_mode_enabled);
+ PARSE_FLAG(has_restriction_reasons);
+ PARSE_FLAG(legacy_has_active_group_call);
+ PARSE_FLAG(is_fake);
+ PARSE_FLAG(is_gigagroup);
+ PARSE_FLAG(noforwards);
+ PARSE_FLAG(can_be_deleted);
+ PARSE_FLAG(join_to_send);
+ PARSE_FLAG(join_request);
+ PARSE_FLAG(has_usernames);
+ PARSE_FLAG(has_flags2);
END_PARSE_FLAGS();
+ if (has_flags2) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_forum);
+ END_PARSE_FLAGS();
+ }
if (use_new_rights) {
parse(status, parser);
@@ -2662,7 +4609,7 @@ void ContactsManager::Channel::parse(ParserT &parser) {
} else if (left) {
status = DialogParticipantStatus::Left();
} else if (is_creator) {
- status = DialogParticipantStatus::Creator(true);
+ status = DialogParticipantStatus::Creator(true, false, string());
} else if (can_edit || can_moderate) {
status = DialogParticipantStatus::ChannelAdministrator(false, is_megagroup);
} else {
@@ -2674,25 +4621,278 @@ void ContactsManager::Channel::parse(ParserT &parser) {
if (has_photo) {
parse(photo, parser);
}
- if (has_username) {
+ if (legacy_has_username) {
+ CHECK(!has_usernames);
+ string username;
parse(username, parser);
+ usernames = Usernames(std::move(username), vector<telegram_api::object_ptr<telegram_api::username>>());
}
parse(date, parser);
- if (is_restricted) {
+ if (legacy_is_restricted) {
+ string restriction_reason;
parse(restriction_reason, parser);
+ restriction_reasons = get_restriction_reasons(restriction_reason);
+ } else if (has_restriction_reasons) {
+ parse(restriction_reasons, parser);
}
- if (have_participant_count) {
+ if (has_participant_count) {
parse(participant_count, parser);
}
+ if (is_megagroup) {
+ if (have_default_permissions) {
+ parse(default_permissions, parser);
+ } else {
+ default_permissions =
+ RestrictedRights(true, true, true, true, true, true, true, true, false, anyone_can_invite, false, false);
+ }
+ }
+ if (has_cache_version) {
+ parse(cache_version, parser);
+ }
+ if (has_usernames) {
+ CHECK(!legacy_has_username);
+ parse(usernames, parser);
+ }
+
+ if (!check_utf8(title)) {
+ LOG(ERROR) << "Have invalid title \"" << title << '"';
+ title.clear();
+ cache_version = 0;
+ }
+ if (legacy_has_active_group_call) {
+ cache_version = 0;
+ }
+}
+
+template <class StorerT>
+void ContactsManager::ChannelFull::store(StorerT &storer) const {
+ using td::store;
+ bool has_description = !description.empty();
+ bool has_administrator_count = administrator_count != 0;
+ bool has_restricted_count = restricted_count != 0;
+ bool has_banned_count = banned_count != 0;
+ bool legacy_has_invite_link = false;
+ bool has_sticker_set = sticker_set_id.is_valid();
+ bool has_linked_channel_id = linked_channel_id.is_valid();
+ bool has_migrated_from_max_message_id = migrated_from_max_message_id.is_valid();
+ bool has_migrated_from_chat_id = migrated_from_chat_id.is_valid();
+ bool has_location = !location.empty();
+ bool has_bot_user_ids = !bot_user_ids.empty();
+ bool is_slow_mode_enabled = slow_mode_delay != 0;
+ bool is_slow_mode_delay_active = slow_mode_next_send_date != 0;
+ bool has_stats_dc_id = stats_dc_id.is_exact();
+ bool has_photo = !photo.is_empty();
+ bool legacy_has_active_group_call_id = false;
+ bool has_invite_link = invite_link.is_valid();
+ bool has_bot_commands = !bot_commands.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_description);
+ STORE_FLAG(has_administrator_count);
+ STORE_FLAG(has_restricted_count);
+ STORE_FLAG(has_banned_count);
+ STORE_FLAG(legacy_has_invite_link);
+ STORE_FLAG(has_sticker_set); // 5
+ STORE_FLAG(has_linked_channel_id);
+ STORE_FLAG(has_migrated_from_max_message_id);
+ STORE_FLAG(has_migrated_from_chat_id);
+ STORE_FLAG(can_get_participants);
+ STORE_FLAG(can_set_username); // 10
+ STORE_FLAG(can_set_sticker_set);
+ STORE_FLAG(false); // legacy_can_view_statistics
+ STORE_FLAG(is_all_history_available);
+ STORE_FLAG(can_set_location);
+ STORE_FLAG(has_location); // 15
+ STORE_FLAG(has_bot_user_ids);
+ STORE_FLAG(is_slow_mode_enabled);
+ STORE_FLAG(is_slow_mode_delay_active);
+ STORE_FLAG(has_stats_dc_id);
+ STORE_FLAG(has_photo); // 20
+ STORE_FLAG(is_can_view_statistics_inited);
+ STORE_FLAG(can_view_statistics);
+ STORE_FLAG(legacy_has_active_group_call_id);
+ STORE_FLAG(has_invite_link);
+ STORE_FLAG(has_bot_commands); // 25
+ STORE_FLAG(can_be_deleted);
+ END_STORE_FLAGS();
+ if (has_description) {
+ store(description, storer);
+ }
+ store(participant_count, storer);
+ if (has_administrator_count) {
+ store(administrator_count, storer);
+ }
+ if (has_restricted_count) {
+ store(restricted_count, storer);
+ }
+ if (has_banned_count) {
+ store(banned_count, storer);
+ }
+ if (has_sticker_set) {
+ store(sticker_set_id, storer);
+ }
+ if (has_linked_channel_id) {
+ store(linked_channel_id, storer);
+ }
+ if (has_location) {
+ store(location, storer);
+ }
+ if (has_bot_user_ids) {
+ store(bot_user_ids, storer);
+ }
+ if (has_migrated_from_max_message_id) {
+ store(migrated_from_max_message_id, storer);
+ }
+ if (has_migrated_from_chat_id) {
+ store(migrated_from_chat_id, storer);
+ }
+ if (is_slow_mode_enabled) {
+ store(slow_mode_delay, storer);
+ }
+ if (is_slow_mode_delay_active) {
+ store(slow_mode_next_send_date, storer);
+ }
+ store_time(expires_at, storer);
+ if (has_stats_dc_id) {
+ store(stats_dc_id.get_raw_id(), storer);
+ }
+ if (has_photo) {
+ store(photo, storer);
+ }
+ if (has_invite_link) {
+ store(invite_link, storer);
+ }
+ if (has_bot_commands) {
+ store(bot_commands, storer);
+ }
+}
+
+template <class ParserT>
+void ContactsManager::ChannelFull::parse(ParserT &parser) {
+ using td::parse;
+ bool has_description;
+ bool has_administrator_count;
+ bool has_restricted_count;
+ bool has_banned_count;
+ bool legacy_has_invite_link;
+ bool has_sticker_set;
+ bool has_linked_channel_id;
+ bool has_migrated_from_max_message_id;
+ bool has_migrated_from_chat_id;
+ bool legacy_can_view_statistics;
+ bool has_location;
+ bool has_bot_user_ids;
+ bool is_slow_mode_enabled;
+ bool is_slow_mode_delay_active;
+ bool has_stats_dc_id;
+ bool has_photo;
+ bool legacy_has_active_group_call_id;
+ bool has_invite_link;
+ bool has_bot_commands;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_description);
+ PARSE_FLAG(has_administrator_count);
+ PARSE_FLAG(has_restricted_count);
+ PARSE_FLAG(has_banned_count);
+ PARSE_FLAG(legacy_has_invite_link);
+ PARSE_FLAG(has_sticker_set);
+ PARSE_FLAG(has_linked_channel_id);
+ PARSE_FLAG(has_migrated_from_max_message_id);
+ PARSE_FLAG(has_migrated_from_chat_id);
+ PARSE_FLAG(can_get_participants);
+ PARSE_FLAG(can_set_username);
+ PARSE_FLAG(can_set_sticker_set);
+ PARSE_FLAG(legacy_can_view_statistics);
+ PARSE_FLAG(is_all_history_available);
+ PARSE_FLAG(can_set_location);
+ PARSE_FLAG(has_location);
+ PARSE_FLAG(has_bot_user_ids);
+ PARSE_FLAG(is_slow_mode_enabled);
+ PARSE_FLAG(is_slow_mode_delay_active);
+ PARSE_FLAG(has_stats_dc_id);
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(is_can_view_statistics_inited);
+ PARSE_FLAG(can_view_statistics);
+ PARSE_FLAG(legacy_has_active_group_call_id);
+ PARSE_FLAG(has_invite_link);
+ PARSE_FLAG(has_bot_commands);
+ PARSE_FLAG(can_be_deleted);
+ END_PARSE_FLAGS();
+ if (has_description) {
+ parse(description, parser);
+ }
+ parse(participant_count, parser);
+ if (has_administrator_count) {
+ parse(administrator_count, parser);
+ }
+ if (has_restricted_count) {
+ parse(restricted_count, parser);
+ }
+ if (has_banned_count) {
+ parse(banned_count, parser);
+ }
+ if (legacy_has_invite_link) {
+ string legacy_invite_link;
+ parse(legacy_invite_link, parser);
+ }
+ if (has_sticker_set) {
+ parse(sticker_set_id, parser);
+ }
+ if (has_linked_channel_id) {
+ parse(linked_channel_id, parser);
+ }
+ if (has_location) {
+ parse(location, parser);
+ }
+ if (has_bot_user_ids) {
+ parse(bot_user_ids, parser);
+ }
+ if (has_migrated_from_max_message_id) {
+ parse(migrated_from_max_message_id, parser);
+ }
+ if (has_migrated_from_chat_id) {
+ parse(migrated_from_chat_id, parser);
+ }
+ if (is_slow_mode_enabled) {
+ parse(slow_mode_delay, parser);
+ }
+ if (is_slow_mode_delay_active) {
+ parse(slow_mode_next_send_date, parser);
+ }
+ parse_time(expires_at, parser);
+ if (has_stats_dc_id) {
+ stats_dc_id = DcId::create(parser.fetch_int());
+ }
+ if (has_photo) {
+ parse(photo, parser);
+ }
+ if (legacy_has_active_group_call_id) {
+ InputGroupCallId input_group_call_id;
+ parse(input_group_call_id, parser);
+ }
+ if (has_invite_link) {
+ parse(invite_link, parser);
+ }
+ if (has_bot_commands) {
+ parse(bot_commands, parser);
+ }
+
+ if (legacy_can_view_statistics) {
+ LOG(DEBUG) << "Ignore legacy can view statistics flag";
+ }
+ if (!is_can_view_statistics_inited) {
+ can_view_statistics = stats_dc_id.is_exact();
+ }
}
template <class StorerT>
void ContactsManager::SecretChat::store(StorerT &storer) const {
using td::store;
- bool has_layer = layer > SecretChatActor::DEFAULT_LAYER;
+ bool has_layer = layer > static_cast<int32>(SecretChatLayer::Default);
+ bool has_initial_folder_id = initial_folder_id != FolderId();
BEGIN_STORE_FLAGS();
STORE_FLAG(is_outbound);
STORE_FLAG(has_layer);
+ STORE_FLAG(has_initial_folder_id);
END_STORE_FLAGS();
store(access_hash, storer);
@@ -2704,15 +4904,20 @@ void ContactsManager::SecretChat::store(StorerT &storer) const {
if (has_layer) {
store(layer, storer);
}
+ if (has_initial_folder_id) {
+ store(initial_folder_id, storer);
+ }
}
template <class ParserT>
void ContactsManager::SecretChat::parse(ParserT &parser) {
using td::parse;
bool has_layer;
+ bool has_initial_folder_id;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_outbound);
PARSE_FLAG(has_layer);
+ PARSE_FLAG(has_initial_folder_id);
END_PARSE_FLAGS();
if (parser.version() >= static_cast<int32>(Version::AddAccessHashToSecretChat)) {
@@ -2728,40 +4933,31 @@ void ContactsManager::SecretChat::parse(ParserT &parser) {
if (has_layer) {
parse(layer, parser);
} else {
- layer = SecretChatActor::DEFAULT_LAYER;
+ layer = static_cast<int32>(SecretChatLayer::Default);
+ }
+ if (has_initial_folder_id) {
+ parse(initial_folder_id, parser);
}
}
-tl_object_ptr<telegram_api::InputUser> ContactsManager::get_input_user(UserId user_id) const {
- if (user_id == get_my_id("get_input_user")) {
+Result<tl_object_ptr<telegram_api::InputUser>> ContactsManager::get_input_user(UserId user_id) const {
+ if (user_id == get_my_id()) {
return make_tl_object<telegram_api::inputUserSelf>();
}
const User *u = get_user(user_id);
- if (u == nullptr || u->access_hash == -1) {
+ if (u == nullptr || u->access_hash == -1 || u->is_min_access_hash) {
if (td_->auth_manager_->is_bot() && user_id.is_valid()) {
return make_tl_object<telegram_api::inputUser>(user_id.get(), 0);
}
- return nullptr;
- }
-
- return make_tl_object<telegram_api::inputUser>(user_id.get(), u->access_hash);
-}
-
-bool ContactsManager::have_input_user(UserId user_id) const {
- if (user_id == get_my_id("have_input_user")) {
- return true;
- }
-
- const User *u = get_user(user_id);
- if (u == nullptr || u->access_hash == -1) {
- if (td_->auth_manager_->is_bot() && user_id.is_valid()) {
- return true;
+ if (u == nullptr) {
+ return Status::Error(400, "User not found");
+ } else {
+ return Status::Error(400, "Have no access to the user");
}
- return false;
}
- return true;
+ return make_tl_object<telegram_api::inputUser>(user_id.get(), u->access_hash);
}
tl_object_ptr<telegram_api::InputChannel> ContactsManager::get_input_channel(ChannelId channel_id) const {
@@ -2777,23 +4973,29 @@ tl_object_ptr<telegram_api::InputChannel> ContactsManager::get_input_channel(Cha
}
bool ContactsManager::have_input_peer_user(UserId user_id, AccessRights access_rights) const {
- if (user_id == get_my_id("have_input_peer_user")) {
+ if (user_id == get_my_id()) {
return true;
}
return have_input_peer_user(get_user(user_id), access_rights);
}
-bool ContactsManager::have_input_peer_user(const User *user, AccessRights access_rights) {
- if (user == nullptr) {
+bool ContactsManager::have_input_peer_user(const User *u, AccessRights access_rights) {
+ if (u == nullptr) {
+ LOG(DEBUG) << "Have no user";
return false;
}
- if (user->access_hash == -1) {
+ if (u->access_hash == -1 || u->is_min_access_hash) {
+ LOG(DEBUG) << "Have user without access hash";
return false;
}
+ if (access_rights == AccessRights::Know) {
+ return true;
+ }
if (access_rights == AccessRights::Read) {
return true;
}
- if (user->is_deleted) {
+ if (u->is_deleted) {
+ LOG(DEBUG) << "Have a deleted user";
return false;
}
return true;
@@ -2801,11 +5003,15 @@ bool ContactsManager::have_input_peer_user(const User *user, AccessRights access
tl_object_ptr<telegram_api::InputPeer> ContactsManager::get_input_peer_user(UserId user_id,
AccessRights access_rights) const {
- if (user_id == get_my_id("get_input_peer_user")) {
+ if (user_id == get_my_id()) {
return make_tl_object<telegram_api::inputPeerSelf>();
}
const User *u = get_user(user_id);
if (!have_input_peer_user(u, access_rights)) {
+ if ((u == nullptr || u->access_hash == -1 || u->is_min_access_hash) && td_->auth_manager_->is_bot() &&
+ user_id.is_valid()) {
+ return make_tl_object<telegram_api::inputPeerUser>(user_id.get(), 0);
+ }
return nullptr;
}
@@ -2816,17 +5022,23 @@ bool ContactsManager::have_input_peer_chat(ChatId chat_id, AccessRights access_r
return have_input_peer_chat(get_chat(chat_id), access_rights);
}
-bool ContactsManager::have_input_peer_chat(const Chat *chat, AccessRights access_rights) {
- if (chat == nullptr) {
+bool ContactsManager::have_input_peer_chat(const Chat *c, AccessRights access_rights) {
+ if (c == nullptr) {
+ LOG(DEBUG) << "Have no basic group";
return false;
}
+ if (access_rights == AccessRights::Know) {
+ return true;
+ }
if (access_rights == AccessRights::Read) {
return true;
}
- if (chat->left) {
+ if (c->status.is_left()) {
+ LOG(DEBUG) << "Have left basic group";
return false;
}
- if (access_rights == AccessRights::Write && !chat->is_active) {
+ if (access_rights == AccessRights::Write && !c->is_active) {
+ LOG(DEBUG) << "Have inactive basic group";
return false;
}
return true;
@@ -2844,36 +5056,73 @@ tl_object_ptr<telegram_api::InputPeer> ContactsManager::get_input_peer_chat(Chat
bool ContactsManager::have_input_peer_channel(ChannelId channel_id, AccessRights access_rights) const {
const Channel *c = get_channel(channel_id);
- return have_input_peer_channel(c, access_rights);
+ return have_input_peer_channel(c, channel_id, access_rights);
}
tl_object_ptr<telegram_api::InputPeer> ContactsManager::get_input_peer_channel(ChannelId channel_id,
AccessRights access_rights) const {
const Channel *c = get_channel(channel_id);
- if (!have_input_peer_channel(c, access_rights)) {
+ if (!have_input_peer_channel(c, channel_id, access_rights)) {
+ if (c == nullptr && td_->auth_manager_->is_bot() && channel_id.is_valid()) {
+ return make_tl_object<telegram_api::inputPeerChannel>(channel_id.get(), 0);
+ }
return nullptr;
}
return make_tl_object<telegram_api::inputPeerChannel>(channel_id.get(), c->access_hash);
}
-bool ContactsManager::have_input_peer_channel(const Channel *c, AccessRights access_rights) {
+bool ContactsManager::have_input_peer_channel(const Channel *c, ChannelId channel_id, AccessRights access_rights,
+ bool from_linked) const {
if (c == nullptr) {
+ LOG(DEBUG) << "Have no " << channel_id;
return false;
}
- if (c->status.is_creator()) {
+ if (access_rights == AccessRights::Know) {
+ return true;
+ }
+ if (c->status.is_administrator()) {
return true;
}
if (c->status.is_banned()) {
+ LOG(DEBUG) << "Was banned in " << channel_id;
return false;
}
- if (!c->username.empty() && access_rights == AccessRights::Read) {
+ if (c->status.is_member()) {
return true;
}
- if (!c->status.is_member()) {
- return false;
+
+ bool is_public = is_channel_public(c);
+ if (access_rights == AccessRights::Read) {
+ if (is_public) {
+ return true;
+ }
+ if (!from_linked && c->has_linked_channel) {
+ auto linked_channel_id = get_linked_channel_id(channel_id);
+ if (linked_channel_id.is_valid() && have_channel(linked_channel_id)) {
+ if (have_input_peer_channel(get_channel(linked_channel_id), linked_channel_id, access_rights, true)) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ if (!from_linked && dialog_access_by_invite_link_.count(DialogId(channel_id))) {
+ return true;
+ }
+ } else {
+ if (!from_linked && c->is_megagroup && !td_->auth_manager_->is_bot() && c->has_linked_channel) {
+ auto linked_channel_id = get_linked_channel_id(channel_id);
+ if (linked_channel_id.is_valid() && (is_public || have_channel(linked_channel_id))) {
+ return is_public ||
+ have_input_peer_channel(get_channel(linked_channel_id), linked_channel_id, AccessRights::Read, true);
+ } else {
+ return true;
+ }
+ }
}
- return true;
+ LOG(DEBUG) << "Have no access to " << channel_id;
+ return false;
}
bool ContactsManager::have_input_encrypted_peer(SecretChatId secret_chat_id, AccessRights access_rights) const {
@@ -2882,8 +5131,12 @@ bool ContactsManager::have_input_encrypted_peer(SecretChatId secret_chat_id, Acc
bool ContactsManager::have_input_encrypted_peer(const SecretChat *secret_chat, AccessRights access_rights) {
if (secret_chat == nullptr) {
+ LOG(DEBUG) << "Have no secret chat";
return false;
}
+ if (access_rights == AccessRights::Know) {
+ return true;
+ }
if (access_rights == AccessRights::Read) {
return true;
}
@@ -2900,11 +5153,25 @@ tl_object_ptr<telegram_api::inputEncryptedChat> ContactsManager::get_input_encry
return make_tl_object<telegram_api::inputEncryptedChat>(secret_chat_id.get(), sc->access_hash);
}
-const DialogPhoto *ContactsManager::get_user_dialog_photo(UserId user_id) const {
+void ContactsManager::apply_pending_user_photo(User *u, UserId user_id) {
+ if (u == nullptr || u->is_photo_inited) {
+ return;
+ }
+
+ if (pending_user_photos_.count(user_id) > 0) {
+ do_update_user_photo(u, user_id, std::move(pending_user_photos_[user_id]), "apply_pending_user_photo");
+ pending_user_photos_.erase(user_id);
+ update_user(u, user_id);
+ }
+}
+
+const DialogPhoto *ContactsManager::get_user_dialog_photo(UserId user_id) {
auto u = get_user(user_id);
if (u == nullptr) {
return nullptr;
}
+
+ apply_pending_user_photo(u, user_id);
return &u->photo;
}
@@ -2919,12 +5186,16 @@ const DialogPhoto *ContactsManager::get_chat_dialog_photo(ChatId chat_id) const
const DialogPhoto *ContactsManager::get_channel_dialog_photo(ChannelId channel_id) const {
auto c = get_channel(channel_id);
if (c == nullptr) {
+ auto min_channel = get_min_channel(channel_id);
+ if (min_channel != nullptr) {
+ return &min_channel->photo_;
+ }
return nullptr;
}
return &c->photo;
}
-const DialogPhoto *ContactsManager::get_secret_chat_dialog_photo(SecretChatId secret_chat_id) const {
+const DialogPhoto *ContactsManager::get_secret_chat_dialog_photo(SecretChatId secret_chat_id) {
auto c = get_secret_chat(secret_chat_id);
if (c == nullptr) {
return nullptr;
@@ -2937,7 +5208,13 @@ string ContactsManager::get_user_title(UserId user_id) const {
if (u == nullptr) {
return string();
}
- return u->last_name.empty() ? u->first_name : u->first_name + " " + u->last_name;
+ if (u->last_name.empty()) {
+ return u->first_name;
+ }
+ if (u->first_name.empty()) {
+ return u->last_name;
+ }
+ return PSTRING() << u->first_name << ' ' << u->last_name;
}
string ContactsManager::get_chat_title(ChatId chat_id) const {
@@ -2951,6 +5228,10 @@ string ContactsManager::get_chat_title(ChatId chat_id) const {
string ContactsManager::get_channel_title(ChannelId channel_id) const {
auto c = get_channel(channel_id);
if (c == nullptr) {
+ auto min_channel = get_min_channel(channel_id);
+ if (min_channel != nullptr) {
+ return min_channel->title_;
+ }
return string();
}
return c->title;
@@ -2964,67 +5245,189 @@ string ContactsManager::get_secret_chat_title(SecretChatId secret_chat_id) const
return get_user_title(c->user_id);
}
-int32 ContactsManager::get_secret_chat_date(SecretChatId secret_chat_id) const {
- auto c = get_secret_chat(secret_chat_id);
+RestrictedRights ContactsManager::get_user_default_permissions(UserId user_id) const {
+ auto u = get_user(user_id);
+ if (u == nullptr || user_id == get_replies_bot_user_id()) {
+ return RestrictedRights(false, false, false, false, false, false, false, false, false, false, u != nullptr, false);
+ }
+ return RestrictedRights(true, true, true, true, true, true, true, true, false, false, true, false);
+}
+
+RestrictedRights ContactsManager::get_chat_default_permissions(ChatId chat_id) const {
+ auto c = get_chat(chat_id);
if (c == nullptr) {
- return 0;
+ return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false);
}
- return c->date;
+ return c->default_permissions;
}
-int32 ContactsManager::get_secret_chat_ttl(SecretChatId secret_chat_id) const {
- auto c = get_secret_chat(secret_chat_id);
+RestrictedRights ContactsManager::get_channel_default_permissions(ChannelId channel_id) const {
+ auto c = get_channel(channel_id);
if (c == nullptr) {
- return 0;
+ return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false);
}
- return c->ttl;
+ return c->default_permissions;
}
-bool ContactsManager::default_can_report_spam_in_secret_chat(SecretChatId secret_chat_id) const {
+RestrictedRights ContactsManager::get_secret_chat_default_permissions(SecretChatId secret_chat_id) const {
auto c = get_secret_chat(secret_chat_id);
if (c == nullptr) {
- return true;
+ return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false);
}
- if (c->is_outbound) {
+ return RestrictedRights(true, true, true, true, true, true, true, true, false, false, false, false);
+}
+
+bool ContactsManager::get_chat_has_protected_content(ChatId chat_id) const {
+ auto c = get_chat(chat_id);
+ if (c == nullptr) {
return false;
}
- auto u = get_user(c->user_id);
- if (u == nullptr) {
- return true;
+ return c->noforwards;
+}
+
+bool ContactsManager::get_channel_has_protected_content(ChannelId channel_id) const {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return false;
}
- if (u->outbound == LinkState::Contact) {
+ return c->noforwards;
+}
+
+string ContactsManager::get_user_private_forward_name(UserId user_id) {
+ auto user_full = get_user_full_force(user_id);
+ if (user_full != nullptr) {
+ return user_full->private_forward_name;
+ }
+ return string();
+}
+
+bool ContactsManager::get_user_voice_messages_forbidden(UserId user_id) const {
+ if (!is_user_premium(user_id)) {
return false;
}
- return true;
+ auto user_full = get_user_full(user_id);
+ if (user_full != nullptr) {
+ return user_full->voice_messages_forbidden;
+ }
+ return false;
}
-string ContactsManager::get_user_username(UserId user_id) const {
- if (!user_id.is_valid()) {
- return string();
+string ContactsManager::get_dialog_about(DialogId dialog_id) {
+ switch (dialog_id.get_type()) {
+ case DialogType::User: {
+ auto user_full = get_user_full_force(dialog_id.get_user_id());
+ if (user_full != nullptr) {
+ return user_full->about;
+ }
+ break;
+ }
+ case DialogType::Chat: {
+ auto chat_full = get_chat_full_force(dialog_id.get_chat_id(), "get_dialog_about");
+ if (chat_full != nullptr) {
+ return chat_full->description;
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ auto channel_full = get_channel_full_force(dialog_id.get_channel_id(), false, "get_dialog_about");
+ if (channel_full != nullptr) {
+ return channel_full->description;
+ }
+ break;
+ }
+ case DialogType::SecretChat: {
+ auto user_full = get_user_full_force(get_secret_chat_user_id(dialog_id.get_secret_chat_id()));
+ if (user_full != nullptr) {
+ return user_full->about;
+ }
+ break;
+ }
+ case DialogType::None:
+ default:
+ UNREACHABLE();
}
+ return string();
+}
+string ContactsManager::get_dialog_search_text(DialogId dialog_id) const {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return get_user_search_text(dialog_id.get_user_id());
+ case DialogType::Chat:
+ return get_chat_title(dialog_id.get_chat_id());
+ case DialogType::Channel:
+ return get_channel_search_text(dialog_id.get_channel_id());
+ case DialogType::SecretChat:
+ return get_user_search_text(get_secret_chat_user_id(dialog_id.get_secret_chat_id()));
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+ return string();
+}
+
+string ContactsManager::get_user_search_text(UserId user_id) const {
auto u = get_user(user_id);
if (u == nullptr) {
return string();
}
- return u->username;
+ return get_user_search_text(u);
+}
+
+string ContactsManager::get_user_search_text(const User *u) {
+ CHECK(u != nullptr);
+ return PSTRING() << u->first_name << ' ' << u->last_name << ' ' << implode(u->usernames.get_active_usernames());
+}
+
+string ContactsManager::get_channel_search_text(ChannelId channel_id) const {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return get_channel_title(channel_id);
+ }
+ return get_channel_search_text(c);
+}
+
+string ContactsManager::get_channel_search_text(const Channel *c) {
+ CHECK(c != nullptr);
+ return PSTRING() << c->title << ' ' << implode(c->usernames.get_active_usernames());
+}
+
+int32 ContactsManager::get_secret_chat_date(SecretChatId secret_chat_id) const {
+ auto c = get_secret_chat(secret_chat_id);
+ if (c == nullptr) {
+ return 0;
+ }
+ return c->date;
}
-string ContactsManager::get_secret_chat_username(SecretChatId secret_chat_id) const {
+int32 ContactsManager::get_secret_chat_ttl(SecretChatId secret_chat_id) const {
auto c = get_secret_chat(secret_chat_id);
if (c == nullptr) {
+ return 0;
+ }
+ return c->ttl;
+}
+
+string ContactsManager::get_user_first_username(UserId user_id) const {
+ if (!user_id.is_valid()) {
return string();
}
- return get_user_username(c->user_id);
+
+ auto u = get_user(user_id);
+ if (u == nullptr) {
+ return string();
+ }
+ return u->usernames.get_first_username();
}
-string ContactsManager::get_channel_username(ChannelId channel_id) const {
+string ContactsManager::get_channel_first_username(ChannelId channel_id) const {
auto c = get_channel(channel_id);
if (c == nullptr) {
return string();
}
- return c->username;
+ return c->usernames.get_first_username();
}
+
UserId ContactsManager::get_secret_chat_user_id(SecretChatId secret_chat_id) const {
auto c = get_secret_chat(secret_chat_id);
if (c == nullptr) {
@@ -3033,6 +5436,14 @@ UserId ContactsManager::get_secret_chat_user_id(SecretChatId secret_chat_id) con
return c->user_id;
}
+bool ContactsManager::get_secret_chat_is_outbound(SecretChatId secret_chat_id) const {
+ auto c = get_secret_chat(secret_chat_id);
+ if (c == nullptr) {
+ return false;
+ }
+ return c->is_outbound;
+}
+
SecretChatState ContactsManager::get_secret_chat_state(SecretChatId secret_chat_id) const {
auto c = get_secret_chat(secret_chat_id);
if (c == nullptr) {
@@ -3049,8 +5460,16 @@ int32 ContactsManager::get_secret_chat_layer(SecretChatId secret_chat_id) const
return c->layer;
}
-UserId ContactsManager::get_my_id(const char *source) const {
- LOG_IF(ERROR, !my_id_.is_valid()) << "Wrong or unknown my id returned to " << source;
+FolderId ContactsManager::get_secret_chat_initial_folder_id(SecretChatId secret_chat_id) const {
+ auto c = get_secret_chat(secret_chat_id);
+ if (c == nullptr) {
+ return FolderId::main();
+ }
+ return c->initial_folder_id;
+}
+
+UserId ContactsManager::get_my_id() const {
+ LOG_IF(ERROR, !my_id_.is_valid()) << "Wrong or unknown my ID returned";
return my_id_;
}
@@ -3060,18 +5479,23 @@ void ContactsManager::set_my_id(UserId my_id) {
LOG(ERROR) << "Already know that me is " << my_old_id << " but received userSelf with " << my_id;
}
if (!my_id.is_valid()) {
- LOG(ERROR) << "Receive invalid my id " << my_id;
+ LOG(ERROR) << "Receive invalid my ID " << my_id;
return;
}
if (my_old_id != my_id) {
my_id_ = my_id;
G()->td_db()->get_binlog_pmc()->set("my_id", to_string(my_id.get()));
- G()->shared_config().set_option_integer("my_id", my_id_.get());
+ td_->option_manager_->set_option_integer("my_id", my_id_.get());
+ G()->td_db()->get_binlog_pmc()->force_sync(Promise<Unit>());
}
}
void ContactsManager::set_my_online_status(bool is_online, bool send_update, bool is_local) {
- auto my_id = get_my_id("set_my_online_status");
+ if (td_->auth_manager_->is_bot()) {
+ return; // just in case
+ }
+
+ auto my_id = get_my_id();
User *u = get_user_force(my_id);
if (u != nullptr) {
int32 new_online;
@@ -3090,6 +5514,7 @@ void ContactsManager::set_my_online_status(bool is_online, bool send_update, boo
if (new_online != my_was_online_local_) {
my_was_online_local_ = new_online;
u->is_status_changed = true;
+ u->is_online_status_changed = true;
}
} else {
if (my_was_online_local_ != 0 || new_online != u->was_online) {
@@ -3097,38 +5522,95 @@ void ContactsManager::set_my_online_status(bool is_online, bool send_update, boo
my_was_online_local_ = 0;
u->was_online = new_online;
u->is_status_changed = true;
+ u->is_online_status_changed = true;
}
}
+ if (was_online_local_ != new_online) {
+ was_online_local_ = new_online;
+ VLOG(notifications) << "Set was_online_local to " << was_online_local_;
+ G()->td_db()->get_binlog_pmc()->set("my_was_online_local", to_string(was_online_local_));
+ }
+
if (send_update) {
update_user(u, my_id);
}
}
}
+ContactsManager::MyOnlineStatusInfo ContactsManager::get_my_online_status() const {
+ MyOnlineStatusInfo status_info;
+ status_info.is_online_local = td_->is_online();
+ status_info.is_online_remote = was_online_remote_ > G()->unix_time_cached();
+ status_info.was_online_local = was_online_local_;
+ status_info.was_online_remote = was_online_remote_;
+
+ return status_info;
+}
+
+UserId ContactsManager::get_service_notifications_user_id() {
+ return UserId(static_cast<int64>(777000));
+}
+
+UserId ContactsManager::add_service_notifications_user() {
+ auto user_id = get_service_notifications_user_id();
+ if (!have_user_force(user_id)) {
+ LOG(FATAL) << "Failed to load service notification user";
+ }
+ return user_id;
+}
+
+UserId ContactsManager::get_replies_bot_user_id() {
+ return UserId(static_cast<int64>(G()->is_test_dc() ? 708513 : 1271266957));
+}
+
+UserId ContactsManager::get_anonymous_bot_user_id() {
+ return UserId(static_cast<int64>(G()->is_test_dc() ? 552888 : 1087968824));
+}
+
+UserId ContactsManager::get_channel_bot_user_id() {
+ return UserId(static_cast<int64>(G()->is_test_dc() ? 936174 : 136817688));
+}
+
+UserId ContactsManager::add_anonymous_bot_user() {
+ auto user_id = get_anonymous_bot_user_id();
+ if (!have_user_force(user_id)) {
+ LOG(FATAL) << "Failed to load anonymous bot user";
+ }
+ return user_id;
+}
+
+UserId ContactsManager::add_channel_bot_user() {
+ auto user_id = get_channel_bot_user_id();
+ if (!have_user_force(user_id)) {
+ LOG(FATAL) << "Failed to load channel bot user";
+ }
+ return user_id;
+}
+
void ContactsManager::check_dialog_username(DialogId dialog_id, const string &username,
Promise<CheckDialogUsernameResult> &&promise) {
if (dialog_id != DialogId() && !dialog_id.is_valid()) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
switch (dialog_id.get_type()) {
case DialogType::User: {
- if (dialog_id.get_user_id() != get_my_id("check_dialog_username")) {
- return promise.set_error(Status::Error(3, "Can't check username for private chat with other user"));
+ if (dialog_id.get_user_id() != get_my_id()) {
+ return promise.set_error(Status::Error(400, "Can't check username for private chat with other user"));
}
break;
}
case DialogType::Channel: {
auto c = get_channel(dialog_id.get_channel_id());
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!get_channel_status(c).is_creator()) {
- return promise.set_error(Status::Error(6, "Not enough rights to change username"));
+ return promise.set_error(Status::Error(400, "Not enough rights to change username"));
}
- if (username == c->username) {
+ if (username == c->usernames.get_editable_username()) {
return promise.set_value(CheckDialogUsernameResult::Ok);
}
break;
@@ -3140,7 +5622,7 @@ void ContactsManager::check_dialog_username(DialogId dialog_id, const string &us
if (username.empty()) {
return promise.set_value(CheckDialogUsernameResult::Ok);
}
- return promise.set_error(Status::Error(3, "Chat can't have username"));
+ return promise.set_error(Status::Error(400, "Chat can't have username"));
default:
UNREACHABLE();
return;
@@ -3149,7 +5631,7 @@ void ContactsManager::check_dialog_username(DialogId dialog_id, const string &us
if (username.empty()) {
return promise.set_value(CheckDialogUsernameResult::Ok);
}
- if (!is_valid_username(username)) {
+ if (!is_allowed_username(username)) {
return promise.set_value(CheckDialogUsernameResult::Invalid);
}
@@ -3165,7 +5647,7 @@ void ContactsManager::check_dialog_username(DialogId dialog_id, const string &us
if (error.message() == "USERNAME_INVALID") {
return promise.set_value(CheckDialogUsernameResult::Invalid);
}
- promise.set_error(std::move(error));
+ return promise.set_error(std::move(error));
}
promise.set_value(result.ok() ? CheckDialogUsernameResult::Ok : CheckDialogUsernameResult::Occupied);
@@ -3205,153 +5687,39 @@ td_api::object_ptr<td_api::CheckChatUsernameResult> ContactsManager::get_check_c
}
}
-void ContactsManager::set_account_ttl(int32 account_ttl, Promise<Unit> &&promise) const {
- td_->create_handler<SetAccountTtlQuery>(std::move(promise))->send(account_ttl);
-}
-
-void ContactsManager::get_account_ttl(Promise<int32> &&promise) const {
- td_->create_handler<GetAccountTtlQuery>(std::move(promise))->send();
-}
-
-void ContactsManager::get_active_sessions(Promise<tl_object_ptr<td_api::sessions>> &&promise) const {
- td_->create_handler<GetAuthorizationsQuery>(std::move(promise))->send();
-}
-
-void ContactsManager::terminate_session(int64 session_id, Promise<Unit> &&promise) const {
- td_->create_handler<ResetAuthorizationQuery>(std::move(promise))->send(session_id);
-}
-
-void ContactsManager::terminate_all_other_sessions(Promise<Unit> &&promise) const {
- td_->create_handler<ResetAuthorizationsQuery>(std::move(promise))->send();
-}
-
-void ContactsManager::get_connected_websites(Promise<tl_object_ptr<td_api::connectedWebsites>> &&promise) const {
- td_->create_handler<GetWebAuthorizationsQuery>(std::move(promise))->send();
-}
-
-void ContactsManager::disconnect_website(int64 website_id, Promise<Unit> &&promise) const {
- td_->create_handler<ResetWebAuthorizationQuery>(std::move(promise))->send(website_id);
-}
-
-void ContactsManager::disconnect_all_websites(Promise<Unit> &&promise) const {
- td_->create_handler<ResetWebAuthorizationsQuery>(std::move(promise))->send();
-}
-
-Status ContactsManager::block_user(UserId user_id) {
- if (user_id == get_my_id("block_user")) {
- return Status::Error(5, "Can't block self");
- }
-
- auto user = get_input_user(user_id);
- if (user == nullptr) {
- return Status::Error(5, "User not found");
- }
-
- td_->create_handler<BlockUserQuery>()->send(std::move(user));
-
- on_update_user_blocked(user_id, true);
- return Status::OK();
-}
-
-Status ContactsManager::unblock_user(UserId user_id) {
- if (user_id == get_my_id("unblock_user")) {
- return Status::Error(5, "Can't unblock self");
- }
-
- auto user = get_input_user(user_id);
- if (user == nullptr) {
- return Status::Error(5, "User not found");
- }
-
- td_->create_handler<UnblockUserQuery>()->send(std::move(user));
-
- on_update_user_blocked(user_id, false);
- return Status::OK();
-}
-
-bool ContactsManager::is_valid_username(const string &username) {
- if (username.size() < 5 || username.size() > 32) {
+bool ContactsManager::is_allowed_username(const string &username) {
+ if (!is_valid_username(username)) {
return false;
}
- if (!is_alpha(username[0])) {
+ if (username.size() < 5) {
return false;
}
- for (auto c : username) {
- if (!is_alpha(c) && !is_digit(c) && c != '_') {
- return false;
- }
- }
- if (username.back() == '_') {
- return false;
- }
- for (size_t i = 1; i < username.size(); i++) {
- if (username[i - 1] == '_' && username[i] == '_') {
- return false;
- }
- }
- if (username.find("admin") == 0 || username.find("telegram") == 0 || username.find("support") == 0 ||
- username.find("security") == 0 || username.find("settings") == 0 || username.find("contacts") == 0 ||
- username.find("service") == 0 || username.find("telegraph") == 0) {
+ auto username_lowered = to_lower(username);
+ if (username_lowered.find("admin") == 0 || username_lowered.find("telegram") == 0 ||
+ username_lowered.find("support") == 0 || username_lowered.find("security") == 0 ||
+ username_lowered.find("settings") == 0 || username_lowered.find("contacts") == 0 ||
+ username_lowered.find("service") == 0 || username_lowered.find("telegraph") == 0) {
return false;
}
return true;
}
-int64 ContactsManager::get_blocked_users(int32 offset, int32 limit, Promise<Unit> &&promise) {
- LOG(INFO) << "Get blocked users with offset = " << offset << " and limit = " << limit;
-
- if (offset < 0) {
- promise.set_error(Status::Error(3, "Parameter offset must be non-negative"));
+int32 ContactsManager::get_user_was_online(const User *u, UserId user_id) const {
+ if (u == nullptr || u->is_deleted) {
return 0;
}
- if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
- return 0;
- }
-
- int64 random_id;
- do {
- random_id = Random::secure_int64();
- } while (random_id == 0 || found_blocked_users_.find(random_id) != found_blocked_users_.end());
- found_blocked_users_[random_id]; // reserve place for result
-
- td_->create_handler<GetBlockedUsersQuery>(std::move(promise))->send(offset, limit, random_id);
- return random_id;
-}
-
-void ContactsManager::on_get_blocked_users_result(int32 offset, int32 limit, int64 random_id, int32 total_count,
- vector<tl_object_ptr<telegram_api::contactBlocked>> &&blocked_users) {
- LOG(INFO) << "Receive " << blocked_users.size() << " blocked users out of " << total_count;
- auto it = found_blocked_users_.find(random_id);
- CHECK(it != found_blocked_users_.end());
-
- auto &result = it->second.second;
- CHECK(result.empty());
- for (auto &blocked_user : blocked_users) {
- CHECK(blocked_user != nullptr);
- UserId user_id(blocked_user->user_id_);
- if (have_user(user_id)) {
- result.push_back(user_id);
- } else {
- LOG(ERROR) << "Have no info about " << user_id;
+ int32 was_online = u->was_online;
+ if (user_id == get_my_id()) {
+ if (my_was_online_local_ != 0) {
+ was_online = my_was_online_local_;
+ }
+ } else {
+ if (u->local_was_online > 0 && u->local_was_online > was_online && u->local_was_online > G()->unix_time_cached()) {
+ was_online = u->local_was_online;
}
}
- it->second.first = total_count;
-}
-
-void ContactsManager::on_failed_get_blocked_users(int64 random_id) {
- auto it = found_blocked_users_.find(random_id);
- CHECK(it != found_blocked_users_.end());
- found_blocked_users_.erase(it);
-}
-
-tl_object_ptr<td_api::users> ContactsManager::get_blocked_users_object(int64 random_id) {
- auto it = found_blocked_users_.find(random_id);
- CHECK(it != found_blocked_users_.end());
- auto result = get_users_object(it->second.first, it->second.second);
- found_blocked_users_.erase(it);
- return result;
+ return was_online;
}
void ContactsManager::load_contacts(Promise<Unit> &&promise) {
@@ -3381,24 +5749,24 @@ void ContactsManager::load_contacts(Promise<Unit> &&promise) {
}
}
-int32 ContactsManager::get_contacts_hash() {
+int64 ContactsManager::get_contacts_hash() {
if (!are_contacts_loaded_) {
return 0;
}
vector<int64> user_ids = contacts_hints_.search_empty(100000).second;
CHECK(std::is_sorted(user_ids.begin(), user_ids.end()));
- auto my_id = get_my_id("get_contacts_hash");
+ auto my_id = get_my_id();
const User *u = get_user_force(my_id);
- if (u != nullptr && u->outbound == LinkState::Contact) {
+ if (u != nullptr && u->is_contact) {
user_ids.insert(std::upper_bound(user_ids.begin(), user_ids.end(), my_id.get()), my_id.get());
}
- vector<uint32> numbers;
+ vector<uint64> numbers;
numbers.reserve(user_ids.size() + 1);
numbers.push_back(saved_contact_count_);
for (auto user_id : user_ids) {
- numbers.push_back(narrow_cast<uint32>(user_id));
+ numbers.push_back(user_id);
}
return get_vector_hash(numbers);
}
@@ -3411,8 +5779,31 @@ void ContactsManager::reload_contacts(bool force) {
}
}
-std::pair<vector<UserId>, vector<int32>> ContactsManager::import_contacts(
- const vector<tl_object_ptr<td_api::contact>> &contacts, int64 &random_id, Promise<Unit> &&promise) {
+void ContactsManager::add_contact(Contact contact, bool share_phone_number, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (!are_contacts_loaded_) {
+ load_contacts(PromiseCreator::lambda([actor_id = actor_id(this), contact = std::move(contact), share_phone_number,
+ promise = std::move(promise)](Result<Unit> &&) mutable {
+ send_closure(actor_id, &ContactsManager::add_contact, std::move(contact), share_phone_number, std::move(promise));
+ }));
+ return;
+ }
+
+ LOG(INFO) << "Add " << contact << " with share_phone_number = " << share_phone_number;
+
+ auto user_id = contact.get_user_id();
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
+
+ td_->create_handler<AddContactQuery>(std::move(promise))
+ ->send(user_id, r_input_user.move_as_ok(), contact, share_phone_number);
+}
+
+std::pair<vector<UserId>, vector<int32>> ContactsManager::import_contacts(const vector<Contact> &contacts,
+ int64 &random_id, Promise<Unit> &&promise) {
if (!are_contacts_loaded_) {
load_contacts(std::move(promise));
return {};
@@ -3429,28 +5820,104 @@ std::pair<vector<UserId>, vector<int32>> ContactsManager::import_contacts(
promise.set_value(Unit());
return result;
}
- for (auto &contact : contacts) {
- if (contact == nullptr) {
- promise.set_error(Status::Error(400, "Imported contacts should not be empty"));
- return {};
- }
- }
do {
random_id = Random::secure_int64();
- } while (random_id == 0 || imported_contacts_.find(random_id) != imported_contacts_.end());
+ } while (random_id == 0 || random_id == 1 || imported_contacts_.count(random_id) > 0);
imported_contacts_[random_id]; // reserve place for result
- td_->create_handler<ImportContactsQuery>(std::move(promise))
- ->send(transform(contacts,
- [](const tl_object_ptr<td_api::contact> &contact) {
- return Contact(contact->phone_number_, contact->first_name_, contact->last_name_, 0);
- }),
- random_id);
+ do_import_contacts(contacts, random_id, std::move(promise));
return {};
}
-void ContactsManager::remove_contacts(vector<UserId> user_ids, Promise<Unit> &&promise) {
+void ContactsManager::do_import_contacts(vector<Contact> contacts, int64 random_id, Promise<Unit> &&promise) {
+ size_t size = contacts.size();
+ if (size == 0) {
+ on_import_contacts_finished(random_id, {}, {});
+ return promise.set_value(Unit());
+ }
+
+ vector<tl_object_ptr<telegram_api::inputPhoneContact>> input_phone_contacts;
+ input_phone_contacts.reserve(size);
+ for (size_t i = 0; i < size; i++) {
+ input_phone_contacts.push_back(contacts[i].get_input_phone_contact(static_cast<int64>(i)));
+ }
+
+ auto task = make_unique<ImportContactsTask>();
+ task->promise_ = std::move(promise);
+ task->input_contacts_ = std::move(contacts);
+ task->imported_user_ids_.resize(size);
+ task->unimported_contact_invites_.resize(size);
+
+ bool is_added = import_contact_tasks_.emplace(random_id, std::move(task)).second;
+ CHECK(is_added);
+
+ td_->create_handler<ImportContactsQuery>()->send(std::move(input_phone_contacts), random_id);
+}
+
+void ContactsManager::on_imported_contacts(int64 random_id,
+ Result<tl_object_ptr<telegram_api::contacts_importedContacts>> result) {
+ auto it = import_contact_tasks_.find(random_id);
+ CHECK(it != import_contact_tasks_.end());
+ CHECK(it->second != nullptr);
+
+ auto task = it->second.get();
+ if (result.is_error()) {
+ auto promise = std::move(task->promise_);
+ import_contact_tasks_.erase(it);
+ return promise.set_error(result.move_as_error());
+ }
+
+ auto imported_contacts = result.move_as_ok();
+ on_get_users(std::move(imported_contacts->users_), "on_imported_contacts");
+
+ for (auto &imported_contact : imported_contacts->imported_) {
+ int64 client_id = imported_contact->client_id_;
+ if (client_id < 0 || client_id >= static_cast<int64>(task->imported_user_ids_.size())) {
+ LOG(ERROR) << "Wrong client_id " << client_id << " returned";
+ continue;
+ }
+
+ task->imported_user_ids_[static_cast<size_t>(client_id)] = UserId(imported_contact->user_id_);
+ }
+ for (auto &popular_contact : imported_contacts->popular_invites_) {
+ int64 client_id = popular_contact->client_id_;
+ if (client_id < 0 || client_id >= static_cast<int64>(task->unimported_contact_invites_.size())) {
+ LOG(ERROR) << "Wrong client_id " << client_id << " returned";
+ continue;
+ }
+ if (popular_contact->importers_ < 0) {
+ LOG(ERROR) << "Wrong number of importers " << popular_contact->importers_ << " returned";
+ continue;
+ }
+
+ task->unimported_contact_invites_[static_cast<size_t>(client_id)] = popular_contact->importers_;
+ }
+
+ if (!imported_contacts->retry_contacts_.empty()) {
+ auto total_size = static_cast<int64>(task->input_contacts_.size());
+ vector<tl_object_ptr<telegram_api::inputPhoneContact>> input_phone_contacts;
+ input_phone_contacts.reserve(imported_contacts->retry_contacts_.size());
+ for (auto &client_id : imported_contacts->retry_contacts_) {
+ if (client_id < 0 || client_id >= total_size) {
+ LOG(ERROR) << "Wrong client_id " << client_id << " returned";
+ continue;
+ }
+ auto i = static_cast<size_t>(client_id);
+ input_phone_contacts.push_back(task->input_contacts_[i].get_input_phone_contact(client_id));
+ }
+ td_->create_handler<ImportContactsQuery>()->send(std::move(input_phone_contacts), random_id);
+ return;
+ }
+
+ auto promise = std::move(task->promise_);
+ on_import_contacts_finished(random_id, std::move(task->imported_user_ids_),
+ std::move(task->unimported_contact_invites_));
+ import_contact_tasks_.erase(it);
+ promise.set_value(Unit());
+}
+
+void ContactsManager::remove_contacts(const vector<UserId> &user_ids, Promise<Unit> &&promise) {
LOG(INFO) << "Delete contacts: " << format::as_array(user_ids);
if (!are_contacts_loaded_) {
load_contacts(std::move(promise));
@@ -3461,11 +5928,11 @@ void ContactsManager::remove_contacts(vector<UserId> user_ids, Promise<Unit> &&p
vector<tl_object_ptr<telegram_api::InputUser>> input_users;
for (auto &user_id : user_ids) {
const User *u = get_user(user_id);
- if (u != nullptr && u->outbound == LinkState::Contact) {
- auto input_user = get_input_user(user_id);
- if (input_user != nullptr) {
+ if (u != nullptr && u->is_contact) {
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_ok()) {
to_delete_user_ids.push_back(user_id);
- input_users.push_back(std::move(input_user));
+ input_users.push_back(r_input_user.move_as_ok());
}
}
}
@@ -3474,9 +5941,19 @@ void ContactsManager::remove_contacts(vector<UserId> user_ids, Promise<Unit> &&p
return promise.set_value(Unit());
}
- // TODO DeleteContactQuery
- td_->create_handler<DeleteContactsQuery>(std::move(promise))
- ->send(std::move(to_delete_user_ids), std::move(input_users));
+ td_->create_handler<DeleteContactsQuery>(std::move(promise))->send(std::move(input_users));
+}
+
+void ContactsManager::remove_contacts_by_phone_number(vector<string> user_phone_numbers, vector<UserId> user_ids,
+ Promise<Unit> &&promise) {
+ LOG(INFO) << "Delete contacts by phone number: " << format::as_array(user_phone_numbers);
+ if (!are_contacts_loaded_) {
+ load_contacts(std::move(promise));
+ return;
+ }
+
+ td_->create_handler<DeleteContactsByPhoneNumberQuery>(std::move(promise))
+ ->send(std::move(user_phone_numbers), std::move(user_ids));
}
int32 ContactsManager::get_imported_contact_count(Promise<Unit> &&promise) {
@@ -3489,7 +5966,7 @@ int32 ContactsManager::get_imported_contact_count(Promise<Unit> &&promise) {
reload_contacts(false);
promise.set_value(Unit());
- return saved_contact_count_ + static_cast<int32>(contacts_hints_.size());
+ return saved_contact_count_;
}
void ContactsManager::load_imported_contacts(Promise<Unit> &&promise) {
@@ -3520,7 +5997,15 @@ void ContactsManager::load_imported_contacts(Promise<Unit> &&promise) {
}
void ContactsManager::on_load_imported_contacts_from_database(string value) {
+ if (G()->close_flag()) {
+ return;
+ }
+
CHECK(!are_imported_contacts_loaded_);
+ if (need_clear_imported_contacts_) {
+ need_clear_imported_contacts_ = false;
+ value.clear();
+ }
if (value.empty()) {
CHECK(all_imported_contacts_.empty());
} else {
@@ -3528,11 +6013,12 @@ void ContactsManager::on_load_imported_contacts_from_database(string value) {
LOG(INFO) << "Successfully loaded " << all_imported_contacts_.size() << " imported contacts from database";
}
- load_imported_contact_users_multipromise_.add_promise(PromiseCreator::lambda([](Result<> result) {
- if (result.is_ok()) {
- send_closure_later(G()->contacts_manager(), &ContactsManager::on_load_imported_contacts_finished);
- }
- }));
+ load_imported_contact_users_multipromise_.add_promise(
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<Unit> result) {
+ if (result.is_ok()) {
+ send_closure_later(actor_id, &ContactsManager::on_load_imported_contacts_finished);
+ }
+ }));
auto lock_promise = load_imported_contact_users_multipromise_.get_promise();
@@ -3553,16 +6039,17 @@ void ContactsManager::on_load_imported_contacts_finished() {
get_user_id_object(contact.get_user_id(), "on_load_imported_contacts_finished"); // to ensure updateUser
}
- are_imported_contacts_loaded_ = true;
- auto promises = std::move(load_imported_contacts_queries_);
- load_imported_contacts_queries_.clear();
- for (auto &promise : promises) {
- promise.set_value(Unit());
+ if (need_clear_imported_contacts_) {
+ need_clear_imported_contacts_ = false;
+ all_imported_contacts_.clear();
}
+ are_imported_contacts_loaded_ = true;
+ set_promises(load_imported_contacts_queries_);
}
-std::pair<vector<UserId>, vector<int32>> ContactsManager::change_imported_contacts(
- vector<tl_object_ptr<td_api::contact>> &&contacts, int64 &random_id, Promise<Unit> &&promise) {
+std::pair<vector<UserId>, vector<int32>> ContactsManager::change_imported_contacts(vector<Contact> &contacts,
+ int64 &random_id,
+ Promise<Unit> &&promise) {
if (!are_contacts_loaded_) {
load_contacts(std::move(promise));
return {};
@@ -3576,6 +6063,15 @@ std::pair<vector<UserId>, vector<int32>> ContactsManager::change_imported_contac
<< " contacts with random_id = " << random_id;
if (random_id != 0) {
// request has already been sent before
+ if (need_clear_imported_contacts_) {
+ need_clear_imported_contacts_ = false;
+ all_imported_contacts_.clear();
+ if (G()->parameters().use_chat_info_db) {
+ G()->td_db()->get_sqlite_pmc()->erase("user_imported_contacts", Auto());
+ }
+ reload_contacts(true);
+ }
+
CHECK(are_imported_contacts_changing_);
are_imported_contacts_changing_ = false;
@@ -3594,26 +6090,14 @@ std::pair<vector<UserId>, vector<int32>> ContactsManager::change_imported_contac
return {};
}
- for (auto &contact : contacts) {
- if (contact == nullptr) {
- promise.set_error(Status::Error(400, "Contacts should not be empty"));
- return {};
- }
- }
-
- auto new_contacts = transform(std::move(contacts), [](tl_object_ptr<td_api::contact> &&contact) {
- return Contact(std::move(contact->phone_number_), std::move(contact->first_name_), std::move(contact->last_name_),
- 0);
- });
-
- vector<size_t> new_contacts_unique_id(new_contacts.size());
+ vector<size_t> new_contacts_unique_id(contacts.size());
vector<Contact> unique_new_contacts;
- unique_new_contacts.reserve(new_contacts.size());
+ unique_new_contacts.reserve(contacts.size());
std::unordered_map<Contact, size_t, ContactHash, ContactEqual> different_new_contacts;
- std::unordered_set<string> different_new_phone_numbers;
+ std::unordered_set<string, Hash<string>> different_new_phone_numbers;
size_t unique_size = 0;
- for (size_t i = 0; i < new_contacts.size(); i++) {
- auto it_success = different_new_contacts.emplace(std::move(new_contacts[i]), unique_size);
+ for (size_t i = 0; i < contacts.size(); i++) {
+ auto it_success = different_new_contacts.emplace(std::move(contacts[i]), unique_size);
new_contacts_unique_id[i] = it_success.first->second;
if (it_success.second) {
unique_new_contacts.push_back(it_success.first->first);
@@ -3622,13 +6106,18 @@ std::pair<vector<UserId>, vector<int32>> ContactsManager::change_imported_contac
}
}
- vector<UserId> to_delete;
+ vector<string> to_delete;
+ vector<UserId> to_delete_user_ids;
for (auto &old_contact : all_imported_contacts_) {
auto user_id = old_contact.get_user_id();
auto it = different_new_contacts.find(old_contact);
if (it == different_new_contacts.end()) {
- if (user_id.is_valid() && different_new_phone_numbers.count(old_contact.get_phone_number()) == 0) {
- to_delete.push_back(user_id);
+ auto phone_number = old_contact.get_phone_number();
+ if (different_new_phone_numbers.count(phone_number) == 0) {
+ to_delete.push_back(std::move(phone_number));
+ if (user_id.is_valid()) {
+ to_delete_user_ids.push_back(user_id);
+ }
}
} else {
unique_new_contacts[it->second].set_user_id(user_id);
@@ -3638,25 +6127,25 @@ std::pair<vector<UserId>, vector<int32>> ContactsManager::change_imported_contac
std::pair<vector<size_t>, vector<Contact>> to_add;
for (auto &new_contact : different_new_contacts) {
to_add.first.push_back(new_contact.second);
- to_add.second.push_back(std::move(new_contact.first));
+ to_add.second.push_back(new_contact.first);
}
if (to_add.first.empty() && to_delete.empty()) {
- for (size_t i = 0; i < new_contacts.size(); i++) {
+ for (size_t i = 0; i < contacts.size(); i++) {
auto unique_id = new_contacts_unique_id[i];
- new_contacts[i].set_user_id(unique_new_contacts[unique_id].get_user_id());
+ contacts[i].set_user_id(unique_new_contacts[unique_id].get_user_id());
}
promise.set_value(Unit());
- return {transform(new_contacts, [&](const Contact &contact) { return contact.get_user_id(); }),
- vector<int32>(new_contacts.size())};
+ return {transform(contacts, [&](const Contact &contact) { return contact.get_user_id(); }),
+ vector<int32>(contacts.size())};
}
are_imported_contacts_changing_ = true;
random_id = 1;
- remove_contacts(
- std::move(to_delete),
+ remove_contacts_by_phone_number(
+ std::move(to_delete), std::move(to_delete_user_ids),
PromiseCreator::lambda([new_contacts = std::move(unique_new_contacts),
new_contacts_unique_id = std::move(new_contacts_unique_id), to_add = std::move(to_add),
promise = std::move(promise)](Result<> result) mutable {
@@ -3679,18 +6168,13 @@ void ContactsManager::on_clear_imported_contacts(vector<Contact> &&contacts, vec
imported_contacts_unique_id_ = std::move(contacts_unique_id);
imported_contacts_pos_ = std::move(to_add.first);
- td_->create_handler<ImportContactsQuery>(std::move(promise))->send(std::move(to_add.second), 0);
+ do_import_contacts(std::move(to_add.second), 1, std::move(promise));
}
void ContactsManager::clear_imported_contacts(Promise<Unit> &&promise) {
LOG(INFO) << "Delete imported contacts";
- if (!are_contacts_loaded_ || saved_contact_count_ == -1) {
- load_contacts(std::move(promise));
- return;
- }
-
- if (contacts_hints_.size() == 0 && saved_contact_count_ == 0) {
+ if (saved_contact_count_ == 0) {
promise.set_value(Unit());
return;
}
@@ -3699,26 +6183,49 @@ void ContactsManager::clear_imported_contacts(Promise<Unit> &&promise) {
}
void ContactsManager::on_update_contacts_reset() {
- UserId my_id = get_my_id("on_update_contacts_reset");
- for (auto &p : users_) {
- UserId user_id = p.first;
- User &u = p.second;
- bool is_contact = u.outbound == LinkState::Contact;
- if (is_contact) {
+ /*
+ UserId my_id = get_my_id();
+ users_.foreach([&](const UserId &user_id, unique_ptr<User> &u) {
+ if (u->is_contact) {
LOG(INFO) << "Drop contact with " << user_id;
if (user_id != my_id) {
CHECK(contacts_hints_.has_key(user_id.get()));
}
- on_update_user_links(&u, user_id, LinkState::KnowsPhoneNumber, u.inbound);
- update_user(&u, user_id);
- CHECK(u.outbound != LinkState::Contact);
+ on_update_user_is_contact(u, user_id, false, false);
+ CHECK(u->is_is_contact_changed);
+ u->cache_version = 0;
+ u->is_repaired = false;
+ update_user(u, user_id);
+ CHECK(!u->is_contact);
if (user_id != my_id) {
CHECK(!contacts_hints_.has_key(user_id.get()));
}
}
- }
+ });
+ */
saved_contact_count_ = 0;
+ if (G()->parameters().use_chat_info_db) {
+ G()->td_db()->get_binlog_pmc()->set("saved_contact_count", "0");
+ G()->td_db()->get_sqlite_pmc()->erase("user_imported_contacts", Auto());
+ }
+ if (!are_imported_contacts_loaded_) {
+ if (load_imported_contacts_queries_.empty()) {
+ CHECK(all_imported_contacts_.empty());
+ LOG(INFO) << "Imported contacts was never loaded, just clear them";
+ } else {
+ LOG(INFO) << "Imported contacts are being loaded, clear them after they will be loaded";
+ need_clear_imported_contacts_ = true;
+ }
+ } else {
+ if (!are_imported_contacts_changing_) {
+ LOG(INFO) << "Imported contacts was loaded, but aren't changing now, just clear them";
+ all_imported_contacts_.clear();
+ } else {
+ LOG(INFO) << "Imported contacts are changing now, clear them after they will be changed";
+ need_clear_imported_contacts_ = true;
+ }
+ }
reload_contacts(true);
}
@@ -3747,44 +6254,592 @@ std::pair<int32, vector<UserId>> ContactsManager::search_contacts(const string &
vector<UserId> user_ids;
user_ids.reserve(result.second.size());
for (auto key : result.second) {
- user_ids.emplace_back(narrow_cast<int32>(key));
+ user_ids.emplace_back(key);
}
promise.set_value(Unit());
return {narrow_cast<int32>(result.first), std::move(user_ids)};
}
-void ContactsManager::set_profile_photo(const tl_object_ptr<td_api::InputFile> &input_photo, Promise<Unit> &&promise) {
- auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Photo, input_photo,
- DialogId(get_my_id("set_profile_photo")), false, false);
+UserId ContactsManager::search_user_by_phone_number(string phone_number, Promise<Unit> &&promise) {
+ clean_phone_number(phone_number);
+ if (phone_number.empty()) {
+ promise.set_error(Status::Error(200, "Phone number is invalid"));
+ return UserId();
+ }
+
+ auto it = resolved_phone_numbers_.find(phone_number);
+ if (it != resolved_phone_numbers_.end()) {
+ promise.set_value(Unit());
+ return it->second;
+ }
+
+ td_->create_handler<ResolvePhoneQuery>(std::move(promise))->send(phone_number);
+ return UserId();
+}
+
+void ContactsManager::on_resolved_phone_number(const string &phone_number, UserId user_id) {
+ if (!user_id.is_valid()) {
+ resolved_phone_numbers_.emplace(phone_number, UserId()); // negative cache
+ return;
+ }
+
+ auto it = resolved_phone_numbers_.find(phone_number);
+ if (it != resolved_phone_numbers_.end()) {
+ if (it->second != user_id) {
+ LOG(WARNING) << "Resolve phone number \"" << phone_number << "\" to " << user_id << ", but have it in "
+ << it->second;
+ it->second = user_id;
+ }
+ return;
+ }
+
+ LOG(ERROR) << "Resolve phone number \"" << phone_number << "\" to " << user_id << ", but doesn't have it";
+ resolved_phone_numbers_[phone_number] = user_id; // always update cached value
+}
+
+void ContactsManager::share_phone_number(UserId user_id, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (!are_contacts_loaded_) {
+ load_contacts(PromiseCreator::lambda(
+ [actor_id = actor_id(this), user_id, promise = std::move(promise)](Result<Unit> &&) mutable {
+ send_closure(actor_id, &ContactsManager::share_phone_number, user_id, std::move(promise));
+ }));
+ return;
+ }
+
+ LOG(INFO) << "Share phone number with " << user_id;
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
+
+ td_->messages_manager_->hide_dialog_action_bar(DialogId(user_id));
+
+ td_->create_handler<AcceptContactQuery>(std::move(promise))->send(user_id, r_input_user.move_as_ok());
+}
+
+void ContactsManager::search_dialogs_nearby(const Location &location,
+ Promise<td_api::object_ptr<td_api::chatsNearby>> &&promise) {
+ if (location.empty()) {
+ return promise.set_error(Status::Error(400, "Invalid location specified"));
+ }
+ last_user_location_ = location;
+ try_send_set_location_visibility_query();
+
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
+ Result<tl_object_ptr<telegram_api::Updates>> result) mutable {
+ send_closure(actor_id, &ContactsManager::on_get_dialogs_nearby, std::move(result), std::move(promise));
+ });
+ td_->create_handler<SearchDialogsNearbyQuery>(std::move(query_promise))->send(location, false, -1);
+}
+
+vector<td_api::object_ptr<td_api::chatNearby>> ContactsManager::get_chats_nearby_object(
+ const vector<DialogNearby> &dialogs_nearby) {
+ return transform(dialogs_nearby, [](const DialogNearby &dialog_nearby) {
+ return td_api::make_object<td_api::chatNearby>(dialog_nearby.dialog_id.get(), dialog_nearby.distance);
+ });
+}
+
+void ContactsManager::send_update_users_nearby() const {
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateUsersNearby>(get_chats_nearby_object(users_nearby_)));
+}
+
+void ContactsManager::on_get_dialogs_nearby(Result<tl_object_ptr<telegram_api::Updates>> result,
+ Promise<td_api::object_ptr<td_api::chatsNearby>> &&promise) {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+
+ auto updates_ptr = result.move_as_ok();
+ if (updates_ptr->get_id() != telegram_api::updates::ID) {
+ LOG(ERROR) << "Receive " << oneline(to_string(*updates_ptr)) << " instead of updates";
+ return promise.set_error(Status::Error(500, "Receive unsupported response from the server"));
+ }
+
+ auto update = telegram_api::move_object_as<telegram_api::updates>(updates_ptr);
+ LOG(INFO) << "Receive chats nearby in " << to_string(update);
+
+ on_get_users(std::move(update->users_), "on_get_dialogs_nearby");
+ on_get_chats(std::move(update->chats_), "on_get_dialogs_nearby");
+
+ for (auto &dialog_nearby : users_nearby_) {
+ user_nearby_timeout_.cancel_timeout(dialog_nearby.dialog_id.get_user_id().get());
+ }
+ auto old_users_nearby = std::move(users_nearby_);
+ users_nearby_.clear();
+ channels_nearby_.clear();
+ int32 location_visibility_expire_date = 0;
+ for (auto &update_ptr : update->updates_) {
+ if (update_ptr->get_id() != telegram_api::updatePeerLocated::ID) {
+ LOG(ERROR) << "Receive unexpected " << to_string(update);
+ continue;
+ }
+
+ auto expire_date = on_update_peer_located(
+ std::move(static_cast<telegram_api::updatePeerLocated *>(update_ptr.get())->peers_), false);
+ if (expire_date != -1) {
+ location_visibility_expire_date = expire_date;
+ }
+ }
+ if (location_visibility_expire_date != location_visibility_expire_date_) {
+ set_location_visibility_expire_date(location_visibility_expire_date);
+ update_is_location_visible();
+ }
+
+ std::sort(users_nearby_.begin(), users_nearby_.end());
+ if (old_users_nearby != users_nearby_) {
+ send_update_users_nearby(); // for other clients connected to the same TDLib instance
+ }
+ promise.set_value(td_api::make_object<td_api::chatsNearby>(get_chats_nearby_object(users_nearby_),
+ get_chats_nearby_object(channels_nearby_)));
+}
+
+void ContactsManager::set_location(const Location &location, Promise<Unit> &&promise) {
+ if (location.empty()) {
+ return promise.set_error(Status::Error(400, "Invalid location specified"));
+ }
+ last_user_location_ = location;
+ try_send_set_location_visibility_query();
+
+ auto query_promise = PromiseCreator::lambda(
+ [promise = std::move(promise)](Result<tl_object_ptr<telegram_api::Updates>> result) mutable {
+ promise.set_value(Unit());
+ });
+ td_->create_handler<SearchDialogsNearbyQuery>(std::move(query_promise))->send(location, true, -1);
+}
+
+void ContactsManager::set_location_visibility(Td *td) {
+ bool is_location_visible = td->option_manager_->get_option_boolean("is_location_visible");
+ auto pending_location_visibility_expire_date = is_location_visible ? std::numeric_limits<int32>::max() : 0;
+ if (td->contacts_manager_ == nullptr) {
+ G()->td_db()->get_binlog_pmc()->set("pending_location_visibility_expire_date",
+ to_string(pending_location_visibility_expire_date));
+ return;
+ }
+ if (td->contacts_manager_->pending_location_visibility_expire_date_ == -1 &&
+ pending_location_visibility_expire_date == td->contacts_manager_->location_visibility_expire_date_) {
+ return;
+ }
+ if (td->contacts_manager_->pending_location_visibility_expire_date_ != pending_location_visibility_expire_date) {
+ td->contacts_manager_->pending_location_visibility_expire_date_ = pending_location_visibility_expire_date;
+ G()->td_db()->get_binlog_pmc()->set("pending_location_visibility_expire_date",
+ to_string(pending_location_visibility_expire_date));
+ }
+ td->contacts_manager_->try_send_set_location_visibility_query();
+}
+
+void ContactsManager::try_send_set_location_visibility_query() {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (pending_location_visibility_expire_date_ == -1) {
+ return;
+ }
+
+ LOG(INFO) << "Trying to send set location visibility query";
+ if (is_set_location_visibility_request_sent_) {
+ return;
+ }
+ if (pending_location_visibility_expire_date_ != 0 && last_user_location_.empty()) {
+ return;
+ }
+
+ is_set_location_visibility_request_sent_ = true;
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), set_expire_date = pending_location_visibility_expire_date_](
+ Result<tl_object_ptr<telegram_api::Updates>> result) {
+ send_closure(actor_id, &ContactsManager::on_set_location_visibility_expire_date, set_expire_date,
+ result.is_ok() ? 0 : result.error().code());
+ });
+ td_->create_handler<SearchDialogsNearbyQuery>(std::move(query_promise))
+ ->send(last_user_location_, true, pending_location_visibility_expire_date_);
+}
+
+void ContactsManager::on_set_location_visibility_expire_date(int32 set_expire_date, int32 error_code) {
+ bool success = error_code == 0;
+ is_set_location_visibility_request_sent_ = false;
+
+ if (set_expire_date != pending_location_visibility_expire_date_) {
+ try_send_set_location_visibility_query();
+ return;
+ }
+
+ if (success) {
+ set_location_visibility_expire_date(pending_location_visibility_expire_date_);
+ } else {
+ if (G()->close_flag()) {
+ // request will be re-sent after restart
+ return;
+ }
+ if (error_code != 406) {
+ LOG(ERROR) << "Failed to set location visibility expire date to " << pending_location_visibility_expire_date_;
+ }
+ }
+ G()->td_db()->get_binlog_pmc()->erase("pending_location_visibility_expire_date");
+ pending_location_visibility_expire_date_ = -1;
+ update_is_location_visible();
+}
+
+void ContactsManager::get_is_location_visible(Promise<Unit> &&promise) {
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
+ Result<tl_object_ptr<telegram_api::Updates>> result) mutable {
+ send_closure(actor_id, &ContactsManager::on_get_is_location_visible, std::move(result), std::move(promise));
+ });
+ td_->create_handler<SearchDialogsNearbyQuery>(std::move(query_promise))->send(Location(), true, -1);
+}
+
+void ContactsManager::on_get_is_location_visible(Result<tl_object_ptr<telegram_api::Updates>> &&result,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ if (result.is_error()) {
+ if (result.error().message() == "GEO_POINT_INVALID" && pending_location_visibility_expire_date_ == -1 &&
+ location_visibility_expire_date_ > 0) {
+ set_location_visibility_expire_date(0);
+ update_is_location_visible();
+ }
+ return promise.set_value(Unit());
+ }
+
+ auto updates_ptr = result.move_as_ok();
+ if (updates_ptr->get_id() != telegram_api::updates::ID) {
+ LOG(ERROR) << "Receive " << oneline(to_string(*updates_ptr)) << " instead of updates";
+ return promise.set_value(Unit());
+ }
+
+ auto updates = std::move(telegram_api::move_object_as<telegram_api::updates>(updates_ptr)->updates_);
+ if (updates.size() != 1 || updates[0]->get_id() != telegram_api::updatePeerLocated::ID) {
+ LOG(ERROR) << "Receive unexpected " << to_string(updates);
+ return promise.set_value(Unit());
+ }
+
+ auto peers = std::move(static_cast<telegram_api::updatePeerLocated *>(updates[0].get())->peers_);
+ if (peers.size() != 1 || peers[0]->get_id() != telegram_api::peerSelfLocated::ID) {
+ LOG(ERROR) << "Receive unexpected " << to_string(peers);
+ return promise.set_value(Unit());
+ }
+
+ auto location_visibility_expire_date = static_cast<telegram_api::peerSelfLocated *>(peers[0].get())->expires_;
+ if (location_visibility_expire_date != location_visibility_expire_date_) {
+ set_location_visibility_expire_date(location_visibility_expire_date);
+ update_is_location_visible();
+ }
+
+ promise.set_value(Unit());
+}
+
+int32 ContactsManager::on_update_peer_located(vector<tl_object_ptr<telegram_api::PeerLocated>> &&peers,
+ bool from_update) {
+ auto now = G()->unix_time();
+ bool need_update = false;
+ int32 location_visibility_expire_date = -1;
+ for (auto &peer_located_ptr : peers) {
+ if (peer_located_ptr->get_id() == telegram_api::peerSelfLocated::ID) {
+ auto peer_self_located = telegram_api::move_object_as<telegram_api::peerSelfLocated>(peer_located_ptr);
+ if (peer_self_located->expires_ == 0 || peer_self_located->expires_ > G()->unix_time()) {
+ location_visibility_expire_date = peer_self_located->expires_;
+ }
+ continue;
+ }
+
+ CHECK(peer_located_ptr->get_id() == telegram_api::peerLocated::ID);
+ auto peer_located = telegram_api::move_object_as<telegram_api::peerLocated>(peer_located_ptr);
+ DialogId dialog_id(peer_located->peer_);
+ int32 expires_at = peer_located->expires_;
+ int32 distance = peer_located->distance_;
+ if (distance < 0 || distance > 50000000) {
+ LOG(ERROR) << "Receive wrong distance to " << to_string(peer_located);
+ continue;
+ }
+ if (expires_at <= now) {
+ LOG(INFO) << "Skip expired result " << to_string(peer_located);
+ continue;
+ }
+
+ auto dialog_type = dialog_id.get_type();
+ if (dialog_type == DialogType::User) {
+ auto user_id = dialog_id.get_user_id();
+ if (!have_user(user_id)) {
+ LOG(ERROR) << "Can't find " << user_id;
+ continue;
+ }
+ if (expires_at < now + 86400) {
+ user_nearby_timeout_.set_timeout_in(user_id.get(), expires_at - now + 1);
+ }
+ } else if (dialog_type == DialogType::Channel) {
+ auto channel_id = dialog_id.get_channel_id();
+ if (!have_channel(channel_id)) {
+ LOG(ERROR) << "Can't find " << channel_id;
+ continue;
+ }
+ if (expires_at != std::numeric_limits<int32>::max()) {
+ LOG(ERROR) << "Receive expiring at " << expires_at << " group location in " << to_string(peer_located);
+ }
+ if (from_update) {
+ LOG(ERROR) << "Receive nearby " << channel_id << " from update";
+ continue;
+ }
+ } else {
+ LOG(ERROR) << "Receive chat of wrong type in " << to_string(peer_located);
+ continue;
+ }
+
+ td_->messages_manager_->force_create_dialog(dialog_id, "on_update_peer_located");
+
+ if (from_update) {
+ CHECK(dialog_type == DialogType::User);
+ bool is_found = false;
+ for (auto &dialog_nearby : users_nearby_) {
+ if (dialog_nearby.dialog_id == dialog_id) {
+ if (dialog_nearby.distance != distance) {
+ dialog_nearby.distance = distance;
+ need_update = true;
+ }
+ is_found = true;
+ break;
+ }
+ }
+ if (!is_found) {
+ users_nearby_.emplace_back(dialog_id, distance);
+ all_users_nearby_.insert(dialog_id.get_user_id());
+ need_update = true;
+ }
+ } else {
+ if (dialog_type == DialogType::User) {
+ users_nearby_.emplace_back(dialog_id, distance);
+ all_users_nearby_.insert(dialog_id.get_user_id());
+ } else {
+ channels_nearby_.emplace_back(dialog_id, distance);
+ }
+ }
+ }
+ if (need_update) {
+ std::sort(users_nearby_.begin(), users_nearby_.end());
+ send_update_users_nearby();
+ }
+ return location_visibility_expire_date;
+}
+
+void ContactsManager::set_location_visibility_expire_date(int32 expire_date) {
+ if (location_visibility_expire_date_ == expire_date) {
+ return;
+ }
+
+ LOG(INFO) << "Set set_location_visibility_expire_date to " << expire_date;
+ location_visibility_expire_date_ = expire_date;
+ if (expire_date == 0) {
+ G()->td_db()->get_binlog_pmc()->erase("location_visibility_expire_date");
+ } else {
+ G()->td_db()->get_binlog_pmc()->set("location_visibility_expire_date", to_string(expire_date));
+ }
+ // the caller must call update_is_location_visible() itself
+}
+
+void ContactsManager::update_is_location_visible() {
+ auto expire_date = pending_location_visibility_expire_date_ != -1 ? pending_location_visibility_expire_date_
+ : location_visibility_expire_date_;
+ td_->option_manager_->set_option_boolean("is_location_visible", expire_date != 0);
+}
+
+void ContactsManager::on_update_bot_commands(DialogId dialog_id, UserId bot_user_id,
+ vector<tl_object_ptr<telegram_api::botCommand>> &&bot_commands) {
+ if (!bot_user_id.is_valid()) {
+ LOG(ERROR) << "Receive updateBotCOmmands about invalid " << bot_user_id;
+ return;
+ }
+ if (!have_user(bot_user_id) || !is_user_bot(bot_user_id)) {
+ return;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto is_from_bot = [bot_user_id](const BotCommands &commands) {
+ return commands.get_bot_user_id() == bot_user_id;
+ };
+
+ switch (dialog_id.get_type()) {
+ case DialogType::User: {
+ UserId user_id(dialog_id.get_user_id());
+ auto user_full = get_user_full(user_id);
+ if (user_full != nullptr) {
+ on_update_user_full_commands(user_full, user_id, std::move(bot_commands));
+ update_user_full(user_full, user_id, "on_update_bot_commands");
+ }
+ break;
+ }
+ case DialogType::Chat: {
+ ChatId chat_id(dialog_id.get_chat_id());
+ auto chat_full = get_chat_full(chat_id);
+ if (chat_full != nullptr) {
+ if (bot_commands.empty()) {
+ if (td::remove_if(chat_full->bot_commands, is_from_bot)) {
+ chat_full->is_changed = true;
+ }
+ } else {
+ BotCommands commands(bot_user_id, std::move(bot_commands));
+ auto it = std::find_if(chat_full->bot_commands.begin(), chat_full->bot_commands.end(), is_from_bot);
+ if (it != chat_full->bot_commands.end()) {
+ if (*it != commands) {
+ *it = std::move(commands);
+ chat_full->is_changed = true;
+ }
+ } else {
+ chat_full->bot_commands.push_back(std::move(commands));
+ chat_full->is_changed = true;
+ }
+ }
+ update_chat_full(chat_full, chat_id, "on_update_bot_commands");
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ ChannelId channel_id(dialog_id.get_channel_id());
+ auto channel_full = get_channel_full(channel_id, true, "on_update_bot_commands");
+ if (channel_full != nullptr) {
+ if (bot_commands.empty()) {
+ if (td::remove_if(channel_full->bot_commands, is_from_bot)) {
+ channel_full->is_changed = true;
+ }
+ } else {
+ BotCommands commands(bot_user_id, std::move(bot_commands));
+ auto it = std::find_if(channel_full->bot_commands.begin(), channel_full->bot_commands.end(), is_from_bot);
+ if (it != channel_full->bot_commands.end()) {
+ if (*it != commands) {
+ *it = std::move(commands);
+ channel_full->is_changed = true;
+ }
+ } else {
+ channel_full->bot_commands.push_back(std::move(commands));
+ channel_full->is_changed = true;
+ }
+ }
+ update_channel_full(channel_full, channel_id, "on_update_bot_commands");
+ }
+ break;
+ }
+ case DialogType::SecretChat:
+ default:
+ LOG(ERROR) << "Receive updateBotCommands in " << dialog_id;
+ break;
+ }
+}
+
+void ContactsManager::on_update_bot_menu_button(UserId bot_user_id,
+ tl_object_ptr<telegram_api::BotMenuButton> &&bot_menu_button) {
+ if (!bot_user_id.is_valid()) {
+ LOG(ERROR) << "Receive updateBotCOmmands about invalid " << bot_user_id;
+ return;
+ }
+ if (!have_user_force(bot_user_id) || !is_user_bot(bot_user_id)) {
+ return;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto user_full = get_user_full_force(bot_user_id);
+ if (user_full != nullptr) {
+ on_update_user_full_menu_button(user_full, bot_user_id, std::move(bot_menu_button));
+ update_user_full(user_full, bot_user_id, "on_update_bot_menu_button");
+ }
+}
+
+FileId ContactsManager::get_profile_photo_file_id(int64 photo_id) const {
+ auto it = my_photo_file_id_.find(photo_id);
+ if (it == my_photo_file_id_.end()) {
+ return FileId();
+ }
+ return it->second;
+}
+
+void ContactsManager::set_profile_photo(const td_api::object_ptr<td_api::InputChatPhoto> &input_photo,
+ Promise<Unit> &&promise) {
+ if (input_photo == nullptr) {
+ return promise.set_error(Status::Error(400, "New profile photo must be non-empty"));
+ }
+
+ const td_api::object_ptr<td_api::InputFile> *input_file = nullptr;
+ double main_frame_timestamp = 0.0;
+ bool is_animation = false;
+ switch (input_photo->get_id()) {
+ case td_api::inputChatPhotoPrevious::ID: {
+ auto photo = static_cast<const td_api::inputChatPhotoPrevious *>(input_photo.get());
+ auto photo_id = photo->chat_photo_id_;
+ auto *u = get_user(get_my_id());
+ if (u != nullptr && u->photo.id > 0 && photo_id == u->photo.id) {
+ return promise.set_value(Unit());
+ }
+
+ auto file_id = get_profile_photo_file_id(photo_id);
+ if (!file_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Unknown profile photo ID specified"));
+ }
+ return send_update_profile_photo_query(td_->file_manager_->dup_file_id(file_id, "set_profile_photo"), photo_id,
+ std::move(promise));
+ }
+ case td_api::inputChatPhotoStatic::ID: {
+ auto photo = static_cast<const td_api::inputChatPhotoStatic *>(input_photo.get());
+ input_file = &photo->photo_;
+ break;
+ }
+ case td_api::inputChatPhotoAnimation::ID: {
+ auto photo = static_cast<const td_api::inputChatPhotoAnimation *>(input_photo.get());
+ input_file = &photo->animation_;
+ main_frame_timestamp = photo->main_frame_timestamp_;
+ is_animation = true;
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+
+ const double MAX_ANIMATION_DURATION = 10.0;
+ if (main_frame_timestamp < 0.0 || main_frame_timestamp > MAX_ANIMATION_DURATION) {
+ return promise.set_error(Status::Error(400, "Wrong main frame timestamp specified"));
+ }
+
+ auto file_type = is_animation ? FileType::Animation : FileType::Photo;
+ auto r_file_id = td_->file_manager_->get_input_file_id(file_type, *input_file, DialogId(get_my_id()), false, false);
if (r_file_id.is_error()) {
// TODO promise.set_error(std::move(status));
- return promise.set_error(Status::Error(7, r_file_id.error().message()));
+ return promise.set_error(Status::Error(400, r_file_id.error().message()));
}
FileId file_id = r_file_id.ok();
CHECK(file_id.is_valid());
+ upload_profile_photo(td_->file_manager_->dup_file_id(file_id, "set_profile_photo"), is_animation,
+ main_frame_timestamp, std::move(promise));
+}
+
+void ContactsManager::send_update_profile_photo_query(FileId file_id, int64 old_photo_id, Promise<Unit> &&promise) {
FileView file_view = td_->file_manager_->get_file_view(file_id);
- CHECK(!file_view.is_encrypted());
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
- td_->create_handler<UpdateProfilePhotoQuery>(std::move(promise))
- ->send(file_view.remote_location().as_input_photo());
- return;
- }
+ td_->create_handler<UpdateProfilePhotoQuery>(std::move(promise))
+ ->send(file_id, old_photo_id, file_view.main_remote_location().as_input_photo());
+}
- auto upload_file_id = td_->file_manager_->dup_file_id(file_id);
- CHECK(upload_file_id.is_valid());
- CHECK(uploaded_profile_photos_.find(upload_file_id) == uploaded_profile_photos_.end());
- uploaded_profile_photos_.emplace(upload_file_id, std::move(promise));
- LOG(INFO) << "Ask to upload profile photo " << upload_file_id;
- td_->file_manager_->upload(upload_file_id, upload_profile_photo_callback_, 1, 0);
+void ContactsManager::upload_profile_photo(FileId file_id, bool is_animation, double main_frame_timestamp,
+ Promise<Unit> &&promise, int reupload_count, vector<int> bad_parts) {
+ CHECK(file_id.is_valid());
+ bool is_inserted = uploaded_profile_photos_
+ .emplace(file_id, UploadedProfilePhoto{main_frame_timestamp, is_animation, reupload_count,
+ std::move(promise)})
+ .second;
+ CHECK(is_inserted);
+ LOG(INFO) << "Ask to upload " << (is_animation ? "animated" : "static") << " profile photo " << file_id
+ << " with bad parts " << bad_parts;
+ // TODO use force_reupload if reupload_count >= 1, replace reupload_count with is_reupload
+ td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_profile_photo_callback_, 32, 0);
}
void ContactsManager::delete_profile_photo(int64 profile_photo_id, Promise<Unit> &&promise) {
- const User *u = get_user(get_my_id("delete_profile_photo"));
+ const User *u = get_user(get_my_id());
if (u != nullptr && u->photo.id == profile_photo_id) {
td_->create_handler<UpdateProfilePhotoQuery>(std::move(promise))
- ->send(make_tl_object<telegram_api::inputPhotoEmpty>());
+ ->send(FileId(), profile_photo_id, make_tl_object<telegram_api::inputPhotoEmpty>());
return;
}
@@ -3795,10 +6850,10 @@ void ContactsManager::set_name(const string &first_name, const string &last_name
auto new_first_name = clean_name(first_name, MAX_NAME_LENGTH);
auto new_last_name = clean_name(last_name, MAX_NAME_LENGTH);
if (new_first_name.empty()) {
- return promise.set_error(Status::Error(7, "First name must be non-empty"));
+ return promise.set_error(Status::Error(400, "First name must be non-empty"));
}
- const User *u = get_user(get_my_id("set_name"));
+ const User *u = get_user(get_my_id());
int32 flags = 0;
// TODO we can already send request for changing first_name and last_name and wanting to set initial values
// TODO need to be rewritten using invoke after and cancelling previous request
@@ -3816,14 +6871,15 @@ void ContactsManager::set_name(const string &first_name, const string &last_name
}
void ContactsManager::set_bio(const string &bio, Promise<Unit> &&promise) {
- auto new_bio = strip_empty_characters(bio, MAX_BIO_LENGTH);
+ auto max_bio_length = static_cast<size_t>(td_->option_manager_->get_option_integer("bio_length_max"));
+ auto new_bio = strip_empty_characters(bio, max_bio_length);
for (auto &c : new_bio) {
if (c == '\n') {
c = ' ';
}
}
- const UserFull *user_full = get_user_full(get_my_id("set_bio"));
+ const UserFull *user_full = get_user_full(get_my_id());
int32 flags = 0;
// TODO we can already send request for changing bio and wanting to set initial values
// TODO need to be rewritten using invoke after and cancelling previous request
@@ -3841,7 +6897,7 @@ void ContactsManager::on_update_profile_success(int32 flags, const string &first
const string &about) {
CHECK(flags != 0);
- auto my_user_id = get_my_id("on_update_profile_success");
+ auto my_user_id = get_my_id();
const User *u = get_user(my_user_id);
if (u == nullptr) {
LOG(ERROR) << "Doesn't receive info about me during update profile";
@@ -3853,300 +6909,824 @@ void ContactsManager::on_update_profile_success(int32 flags, const string &first
<< "Wrong last name \"" << u->last_name << "\", expected \"" << last_name << '"';
if ((flags & ACCOUNT_UPDATE_ABOUT) != 0) {
- UserFull *user_full = get_user_full(my_user_id);
- if (user_full != nullptr && user_full->is_inited) {
+ UserFull *user_full = get_user_full_force(my_user_id);
+ if (user_full != nullptr) {
user_full->about = about;
user_full->is_changed = true;
- update_user_full(user_full, my_user_id);
+ update_user_full(user_full, my_user_id, "on_update_profile_success");
+ td_->group_call_manager_->on_update_dialog_about(DialogId(my_user_id), user_full->about, true);
}
}
}
void ContactsManager::set_username(const string &username, Promise<Unit> &&promise) {
- if (!username.empty() && !is_valid_username(username)) {
+ if (!username.empty() && !is_allowed_username(username)) {
return promise.set_error(Status::Error(400, "Username is invalid"));
}
td_->create_handler<UpdateUsernameQuery>(std::move(promise))->send(username);
}
-void ContactsManager::toggle_chat_administrators(ChatId chat_id, bool everyone_is_administrator,
- Promise<Unit> &&promise) {
+void ContactsManager::toggle_username_is_active(string &&username, bool is_active, Promise<Unit> &&promise) {
+ get_me(PromiseCreator::lambda([actor_id = actor_id(this), username = std::move(username), is_active,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &ContactsManager::toggle_username_is_active_impl, std::move(username), is_active,
+ std::move(promise));
+ }
+ }));
+}
+
+void ContactsManager::toggle_username_is_active_impl(string &&username, bool is_active, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ const User *u = get_user(get_my_id());
+ CHECK(u != nullptr);
+ if (!u->usernames.can_toggle(username)) {
+ return promise.set_error(Status::Error(400, "Wrong username specified"));
+ }
+ td_->create_handler<ToggleUsernameQuery>(std::move(promise))->send(std::move(username), is_active);
+}
+
+void ContactsManager::reorder_usernames(vector<string> &&usernames, Promise<Unit> &&promise) {
+ get_me(PromiseCreator::lambda([actor_id = actor_id(this), usernames = std::move(usernames),
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &ContactsManager::reorder_usernames_impl, std::move(usernames), std::move(promise));
+ }
+ }));
+}
+
+void ContactsManager::reorder_usernames_impl(vector<string> &&usernames, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ const User *u = get_user(get_my_id());
+ CHECK(u != nullptr);
+ if (!u->usernames.can_reorder_to(usernames)) {
+ return promise.set_error(Status::Error(400, "Invalid username order specified"));
+ }
+ if (usernames.size() <= 1) {
+ return promise.set_value(Unit());
+ }
+ td_->create_handler<ReorderUsernamesQuery>(std::move(promise))->send(std::move(usernames));
+}
+
+void ContactsManager::on_update_username_is_active(string &&username, bool is_active, Promise<Unit> &&promise) {
+ auto user_id = get_my_id();
+ User *u = get_user(user_id);
+ CHECK(u != nullptr);
+ if (!u->usernames.can_toggle(username)) {
+ return reload_user(user_id, std::move(promise));
+ }
+ on_update_user_usernames(u, user_id, u->usernames.toggle(username, is_active));
+ update_user(u, user_id);
+ promise.set_value(Unit());
+}
+
+void ContactsManager::on_update_active_usernames_order(vector<string> &&usernames, Promise<Unit> &&promise) {
+ auto user_id = get_my_id();
+ User *u = get_user(user_id);
+ CHECK(u != nullptr);
+ if (!u->usernames.can_reorder_to(usernames)) {
+ return reload_user(user_id, std::move(promise));
+ }
+ on_update_user_usernames(u, user_id, u->usernames.reorder_to(std::move(usernames)));
+ update_user(u, user_id);
+ promise.set_value(Unit());
+}
+
+void ContactsManager::set_emoji_status(EmojiStatus emoji_status, Promise<Unit> &&promise) {
+ if (!td_->option_manager_->get_option_boolean("is_premium")) {
+ return promise.set_error(Status::Error(400, "The method is available only for Telegram Premium users"));
+ }
+ add_recent_emoji_status(td_, emoji_status);
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), emoji_status, promise = std::move(promise)](Result<Unit> result) mutable {
+ if (result.is_ok()) {
+ send_closure(actor_id, &ContactsManager::on_set_emoji_status, emoji_status, std::move(promise));
+ } else {
+ promise.set_error(result.move_as_error());
+ }
+ });
+ td_->create_handler<UpdateEmojiStatusQuery>(std::move(query_promise))->send(emoji_status);
+}
+
+void ContactsManager::on_set_emoji_status(EmojiStatus emoji_status, Promise<Unit> &&promise) {
+ auto user_id = get_my_id();
+ User *u = get_user(user_id);
+ if (u != nullptr) {
+ on_update_user_emoji_status(u, user_id, emoji_status);
+ update_user(u, user_id);
+ }
+ promise.set_value(Unit());
+}
+
+void ContactsManager::set_chat_description(ChatId chat_id, const string &description, Promise<Unit> &&promise) {
+ auto new_description = strip_empty_characters(description, MAX_DESCRIPTION_LENGTH);
auto c = get_chat(chat_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Group not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
- if (!get_chat_status(c).is_creator()) {
- return promise.set_error(Status::Error(6, "Not enough rights to toggle basic group administrators"));
+ if (!get_chat_permissions(c).can_change_info_and_settings()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to set chat description"));
}
- td_->create_handler<ToggleChatAdminsQuery>(std::move(promise))->send(chat_id, everyone_is_administrator);
+ td_->create_handler<EditChatAboutQuery>(std::move(promise))->send(DialogId(chat_id), new_description);
}
void ContactsManager::set_channel_username(ChannelId channel_id, const string &username, Promise<Unit> &&promise) {
- auto c = get_channel(channel_id);
+ const auto *c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
}
if (!get_channel_status(c).is_creator()) {
- return promise.set_error(Status::Error(6, "Not enough rights to change supergroup username"));
+ return promise.set_error(Status::Error(400, "Not enough rights to change supergroup username"));
}
- if (!username.empty() && !is_valid_username(username)) {
+ if (!username.empty() && !is_allowed_username(username)) {
return promise.set_error(Status::Error(400, "Username is invalid"));
}
- if (!username.empty() && c->username.empty()) {
- auto channel_full = get_channel_full(channel_id);
+ if (!username.empty() && !c->usernames.has_editable_username()) {
+ auto channel_full = get_channel_full(channel_id, false, "set_channel_username");
if (channel_full != nullptr && !channel_full->can_set_username) {
- return promise.set_error(Status::Error(3, "Can't set supergroup username"));
+ return promise.set_error(Status::Error(400, "Can't set supergroup username"));
}
}
td_->create_handler<UpdateChannelUsernameQuery>(std::move(promise))->send(channel_id, username);
}
-void ContactsManager::set_channel_sticker_set(ChannelId channel_id, int64 sticker_set_id, Promise<Unit> &&promise) {
+void ContactsManager::toggle_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active,
+ Promise<Unit> &&promise) {
+ const auto *c = get_channel(channel_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
+ }
+ if (!get_channel_status(c).is_creator()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to change username"));
+ }
+ if (!c->usernames.can_toggle(username)) {
+ return promise.set_error(Status::Error(400, "Wrong username specified"));
+ }
+ td_->create_handler<ToggleChannelUsernameQuery>(std::move(promise))->send(channel_id, std::move(username), is_active);
+}
+
+void ContactsManager::disable_all_channel_usernames(ChannelId channel_id, Promise<Unit> &&promise) {
+ const auto *c = get_channel(channel_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
+ }
+ if (!get_channel_status(c).is_creator()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to disable usernames"));
+ }
+ td_->create_handler<DeactivateAllChannelUsernamesQuery>(std::move(promise))->send(channel_id);
+}
+
+void ContactsManager::reorder_channel_usernames(ChannelId channel_id, vector<string> &&usernames,
+ Promise<Unit> &&promise) {
+ const auto *c = get_channel(channel_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
+ }
+ if (!get_channel_status(c).is_creator()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to reorder usernames"));
+ }
+ if (!c->usernames.can_reorder_to(usernames)) {
+ return promise.set_error(Status::Error(400, "Invalid username order specified"));
+ }
+ if (usernames.size() <= 1) {
+ return promise.set_value(Unit());
+ }
+ td_->create_handler<ReorderChannelUsernamesQuery>(std::move(promise))->send(channel_id, std::move(usernames));
+}
+
+void ContactsManager::on_update_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active,
+ Promise<Unit> &&promise) {
+ auto *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+ if (!c->usernames.can_toggle(username)) {
+ return reload_channel(channel_id, std::move(promise));
+ }
+ on_update_channel_usernames(c, channel_id, c->usernames.toggle(username, is_active));
+ update_channel(c, channel_id);
+ promise.set_value(Unit());
+}
+
+void ContactsManager::on_deactivate_channel_usernames(ChannelId channel_id, Promise<Unit> &&promise) {
+ auto *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+ on_update_channel_usernames(c, channel_id, c->usernames.deactivate_all());
+ update_channel(c, channel_id);
+ promise.set_value(Unit());
+}
+
+void ContactsManager::on_update_channel_active_usernames_order(ChannelId channel_id, vector<string> &&usernames,
+ Promise<Unit> &&promise) {
+ auto *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+ if (!c->usernames.can_reorder_to(usernames)) {
+ return reload_channel(channel_id, std::move(promise));
+ }
+ on_update_channel_usernames(c, channel_id, c->usernames.reorder_to(std::move(usernames)));
+ update_channel(c, channel_id);
+ promise.set_value(Unit());
+}
+
+void ContactsManager::set_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id,
+ Promise<Unit> &&promise) {
auto c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
}
if (!c->is_megagroup) {
- return promise.set_error(Status::Error(6, "Chat sticker set can be set only for supergroups"));
+ return promise.set_error(Status::Error(400, "Chat sticker set can be set only for supergroups"));
}
- if (!get_channel_status(c).can_change_info_and_settings()) {
- return promise.set_error(Status::Error(6, "Not enough rights to change supergroup sticker set"));
+ if (!get_channel_permissions(c).can_change_info_and_settings()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to change supergroup sticker set"));
}
telegram_api::object_ptr<telegram_api::InputStickerSet> input_sticker_set;
- if (sticker_set_id == 0) {
+ if (!sticker_set_id.is_valid()) {
input_sticker_set = telegram_api::make_object<telegram_api::inputStickerSetEmpty>();
} else {
input_sticker_set = td_->stickers_manager_->get_input_sticker_set(sticker_set_id);
if (input_sticker_set == nullptr) {
- return promise.set_error(Status::Error(3, "Sticker set not found"));
+ return promise.set_error(Status::Error(400, "Sticker set not found"));
}
}
- auto channel_full = get_channel_full(channel_id);
+ auto channel_full = get_channel_full(channel_id, false, "set_channel_sticker_set");
if (channel_full != nullptr && !channel_full->can_set_sticker_set) {
- return promise.set_error(Status::Error(3, "Can't set supergroup sticker set"));
+ return promise.set_error(Status::Error(400, "Can't set supergroup sticker set"));
}
td_->create_handler<SetChannelStickerSetQuery>(std::move(promise))
->send(channel_id, sticker_set_id, std::move(input_sticker_set));
}
-void ContactsManager::toggle_channel_invites(ChannelId channel_id, bool anyone_can_invite, Promise<Unit> &&promise) {
+void ContactsManager::toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise<Unit> &&promise) {
auto c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
}
- if (!get_channel_status(c).can_change_info_and_settings()) {
- return promise.set_error(Status::Error(6, "Not enough rights to toggle supergroup invites"));
+ if (get_channel_type(c) == ChannelType::Megagroup) {
+ return promise.set_error(Status::Error(400, "Message signatures can't be toggled in supergroups"));
}
- if (get_channel_type(c) != ChannelType::Megagroup) {
- return promise.set_error(Status::Error(6, "Invites by any member can be enabled in the supergroups only"));
+ if (!get_channel_permissions(c).can_change_info_and_settings()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to toggle channel sign messages"));
}
- td_->create_handler<ToggleChannelInvitesQuery>(std::move(promise))->send(channel_id, anyone_can_invite);
+ td_->create_handler<ToggleChannelSignaturesQuery>(std::move(promise))->send(channel_id, sign_messages);
}
-void ContactsManager::toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise<Unit> &&promise) {
+void ContactsManager::toggle_channel_join_to_send(ChannelId channel_id, bool join_to_send, Promise<Unit> &&promise) {
auto c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
}
- if (get_channel_type(c) == ChannelType::Megagroup) {
- return promise.set_error(Status::Error(6, "Message signatures can't be toggled in supergroups"));
+ if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) {
+ return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups"));
}
- if (!get_channel_status(c).can_change_info_and_settings()) {
- return promise.set_error(Status::Error(6, "Not enough rights to toggle channel sign messages"));
+ if (!get_channel_permissions(c).can_restrict_members()) {
+ return promise.set_error(Status::Error(400, "Not enough rights"));
}
- td_->create_handler<ToggleChannelSignaturesQuery>(std::move(promise))->send(channel_id, sign_messages);
+ td_->create_handler<ToggleChannelJoinToSendQuery>(std::move(promise))->send(channel_id, join_to_send);
+}
+
+void ContactsManager::toggle_channel_join_request(ChannelId channel_id, bool join_request, Promise<Unit> &&promise) {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
+ }
+ if (get_channel_type(c) == ChannelType::Broadcast || c->is_gigagroup) {
+ return promise.set_error(Status::Error(400, "The method can be called only for ordinary supergroups"));
+ }
+ if (!get_channel_permissions(c).can_restrict_members()) {
+ return promise.set_error(Status::Error(400, "Not enough rights"));
+ }
+
+ td_->create_handler<ToggleChannelJoinRequestQuery>(std::move(promise))->send(channel_id, join_request);
}
void ContactsManager::toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available,
Promise<Unit> &&promise) {
auto c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
}
- if (!get_channel_status(c).can_change_info_and_settings()) {
- return promise.set_error(Status::Error(6, "Not enough rights to toggle all supergroup history availability"));
+ if (!get_channel_permissions(c).can_change_info_and_settings()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to toggle all supergroup history availability"));
}
if (get_channel_type(c) != ChannelType::Megagroup) {
- return promise.set_error(Status::Error(6, "Message history can be hidden in the supergroups only"));
+ return promise.set_error(Status::Error(400, "Message history can be hidden in supergroups only"));
}
+ if (c->is_forum && !is_all_history_available) {
+ return promise.set_error(Status::Error(400, "Message history can't be hidden in forum supergroups"));
+ }
+ if (c->has_linked_channel && !is_all_history_available) {
+ return promise.set_error(Status::Error(400, "Message history can't be hidden in discussion supergroups"));
+ }
+ // it can be toggled in public chats, but will not affect them
- td_->create_handler<ToggleChannelIsAllHistoryAvailableQuery>(std::move(promise))
- ->send(channel_id, is_all_history_available);
+ td_->create_handler<TogglePrehistoryHiddenQuery>(std::move(promise))->send(channel_id, is_all_history_available);
}
-void ContactsManager::set_channel_description(ChannelId channel_id, const string &description,
- Promise<Unit> &&promise) {
- auto new_description = strip_empty_characters(description, MAX_NAME_LENGTH);
+void ContactsManager::toggle_channel_is_forum(ChannelId channel_id, bool is_forum, Promise<Unit> &&promise) {
auto c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
+ }
+ if (c->is_forum == is_forum) {
+ return promise.set_value(Unit());
+ }
+ if (!get_channel_permissions(c).is_creator()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to convert the group to a forum"));
}
- if (!get_channel_status(c).can_change_info_and_settings()) {
- return promise.set_error(Status::Error(6, "Not enough rights to set supergroup description"));
+ if (get_channel_type(c) != ChannelType::Megagroup) {
+ return promise.set_error(Status::Error(400, "Forums can be enabled in supergroups only"));
}
- td_->create_handler<EditChannelAboutQuery>(std::move(promise))->send(channel_id, new_description);
+ td_->create_handler<ToggleForumQuery>(std::move(promise))->send(channel_id, is_forum);
}
-void ContactsManager::pin_channel_message(ChannelId channel_id, MessageId message_id, bool disable_notification,
- Promise<Unit> &&promise) {
+void ContactsManager::convert_channel_to_gigagroup(ChannelId channel_id, Promise<Unit> &&promise) {
auto c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
}
- auto channel_status = get_channel_status(c);
- bool can_pin = c->is_megagroup ? channel_status.can_pin_messages() : channel_status.can_edit_messages();
- if (!can_pin) {
- return promise.set_error(Status::Error(6, "Not enough rights to pin a message"));
+ if (!get_channel_permissions(c).is_creator()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to convert group to broadcast group"));
}
+ if (get_channel_type(c) != ChannelType::Megagroup) {
+ return promise.set_error(Status::Error(400, "Chat must be a supergroup"));
+ }
+
+ remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)});
- if (!td_->messages_manager_->have_message({DialogId(channel_id), message_id})) {
- return promise.set_error(Status::Error(6, "Message not found"));
+ td_->create_handler<ConvertToGigagroupQuery>(std::move(promise))->send(channel_id);
+}
+
+void ContactsManager::set_channel_description(ChannelId channel_id, const string &description,
+ Promise<Unit> &&promise) {
+ auto new_description = strip_empty_characters(description, MAX_DESCRIPTION_LENGTH);
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat info not found"));
+ }
+ if (!get_channel_permissions(c).can_change_info_and_settings()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to set chat description"));
}
- if (!message_id.is_server()) {
- return promise.set_error(Status::Error(6, "Message can't be pinned"));
+ td_->create_handler<EditChatAboutQuery>(std::move(promise))->send(DialogId(channel_id), new_description);
+}
+
+void ContactsManager::set_channel_discussion_group(DialogId dialog_id, DialogId discussion_dialog_id,
+ Promise<Unit> &&promise) {
+ if (!dialog_id.is_valid() && !discussion_dialog_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid chat identifiers specified"));
+ }
+
+ ChannelId broadcast_channel_id;
+ telegram_api::object_ptr<telegram_api::InputChannel> broadcast_input_channel;
+ if (dialog_id.is_valid()) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "set_channel_discussion_group 1")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return promise.set_error(Status::Error(400, "Chat is not a channel"));
+ }
+
+ broadcast_channel_id = dialog_id.get_channel_id();
+ const Channel *c = get_channel(broadcast_channel_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat info not found"));
+ }
+
+ if (c->is_megagroup) {
+ return promise.set_error(Status::Error(400, "Chat is not a channel"));
+ }
+ if (!c->status.is_administrator() || !c->status.can_change_info_and_settings()) {
+ return promise.set_error(Status::Error(400, "Not enough rights in the channel"));
+ }
+
+ broadcast_input_channel = get_input_channel(broadcast_channel_id);
+ CHECK(broadcast_input_channel != nullptr);
+ } else {
+ broadcast_input_channel = telegram_api::make_object<telegram_api::inputChannelEmpty>();
+ }
+
+ ChannelId group_channel_id;
+ telegram_api::object_ptr<telegram_api::InputChannel> group_input_channel;
+ if (discussion_dialog_id.is_valid()) {
+ if (!td_->messages_manager_->have_dialog_force(discussion_dialog_id, "set_channel_discussion_group 2")) {
+ return promise.set_error(Status::Error(400, "Discussion chat not found"));
+ }
+ if (discussion_dialog_id.get_type() != DialogType::Channel) {
+ return promise.set_error(Status::Error(400, "Discussion chat is not a supergroup"));
+ }
+
+ group_channel_id = discussion_dialog_id.get_channel_id();
+ const Channel *c = get_channel(group_channel_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Discussion chat info not found"));
+ }
+
+ if (!c->is_megagroup) {
+ return promise.set_error(Status::Error(400, "Discussion chat is not a supergroup"));
+ }
+ if (!c->status.is_administrator() || !c->status.can_pin_messages()) {
+ return promise.set_error(Status::Error(400, "Not enough rights in the supergroup"));
+ }
+
+ group_input_channel = get_input_channel(group_channel_id);
+ CHECK(group_input_channel != nullptr);
+ } else {
+ group_input_channel = telegram_api::make_object<telegram_api::inputChannelEmpty>();
}
- td_->create_handler<UpdateChannelPinnedMessageQuery>(std::move(promise))
- ->send(channel_id, message_id, disable_notification);
+ td_->create_handler<SetDiscussionGroupQuery>(std::move(promise))
+ ->send(broadcast_channel_id, std::move(broadcast_input_channel), group_channel_id,
+ std::move(group_input_channel));
}
-void ContactsManager::unpin_channel_message(ChannelId channel_id, Promise<Unit> &&promise) {
- auto c = get_channel(channel_id);
+void ContactsManager::set_channel_location(DialogId dialog_id, const DialogLocation &location,
+ Promise<Unit> &&promise) {
+ if (location.empty()) {
+ return promise.set_error(Status::Error(400, "Invalid chat location specified"));
+ }
+
+ if (!dialog_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
+ }
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "set_channel_location")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return promise.set_error(Status::Error(400, "Chat is not a supergroup"));
+ }
+
+ auto channel_id = dialog_id.get_channel_id();
+ const Channel *c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
- auto channel_status = get_channel_status(c);
- bool can_unpin = c->is_megagroup ? channel_status.can_pin_messages() : channel_status.can_edit_messages();
- if (!can_unpin) {
- return promise.set_error(Status::Error(6, "Not enough rights to unpin a message"));
+ if (!c->is_megagroup) {
+ return promise.set_error(Status::Error(400, "Chat is not a supergroup"));
+ }
+ if (!c->status.is_creator()) {
+ return promise.set_error(Status::Error(400, "Not enough rights in the supergroup"));
}
- td_->create_handler<UpdateChannelPinnedMessageQuery>(std::move(promise))->send(channel_id, MessageId(), false);
+ td_->create_handler<EditLocationQuery>(std::move(promise))->send(channel_id, location);
}
-void ContactsManager::report_channel_spam(ChannelId channel_id, UserId user_id, const vector<MessageId> &message_ids,
- Promise<Unit> &&promise) {
- auto c = get_channel(channel_id);
+void ContactsManager::set_channel_slow_mode_delay(DialogId dialog_id, int32 slow_mode_delay, Promise<Unit> &&promise) {
+ vector<int32> allowed_slow_mode_delays{0, 10, 30, 60, 300, 900, 3600};
+ if (!td::contains(allowed_slow_mode_delays, slow_mode_delay)) {
+ return promise.set_error(Status::Error(400, "Invalid new value for slow mode delay"));
+ }
+
+ if (!dialog_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
+ }
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "set_channel_slow_mode_delay")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return promise.set_error(Status::Error(400, "Chat is not a supergroup"));
+ }
+
+ auto channel_id = dialog_id.get_channel_id();
+ const Channel *c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
if (!c->is_megagroup) {
- return promise.set_error(Status::Error(6, "Spam can be reported only in supergroups"));
+ return promise.set_error(Status::Error(400, "Chat is not a supergroup"));
+ }
+ if (!get_channel_permissions(c).can_restrict_members()) {
+ return promise.set_error(Status::Error(400, "Not enough rights in the supergroup"));
+ }
+
+ td_->create_handler<ToggleSlowModeQuery>(std::move(promise))->send(channel_id, slow_mode_delay);
+}
+
+void ContactsManager::get_channel_statistics_dc_id(DialogId dialog_id, bool for_full_statistics,
+ Promise<DcId> &&promise) {
+ if (!dialog_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
+ }
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_channel_statistics_dc_id")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return promise.set_error(Status::Error(400, "Chat is not a channel"));
+ }
+
+ auto channel_id = dialog_id.get_channel_id();
+ const Channel *c = get_channel(channel_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat info not found"));
+ }
+
+ auto channel_full = get_channel_full_force(channel_id, false, "get_channel_statistics_dc_id");
+ if (channel_full == nullptr || !channel_full->stats_dc_id.is_exact() ||
+ (for_full_statistics && !channel_full->can_view_statistics)) {
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id, for_full_statistics,
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ send_closure(actor_id, &ContactsManager::get_channel_statistics_dc_id_impl, channel_id, for_full_statistics,
+ std::move(promise));
+ });
+ send_get_channel_full_query(channel_full, channel_id, std::move(query_promise), "get_channel_statistics_dc_id");
+ return;
+ }
+
+ promise.set_value(DcId(channel_full->stats_dc_id));
+}
+
+void ContactsManager::get_channel_statistics_dc_id_impl(ChannelId channel_id, bool for_full_statistics,
+ Promise<DcId> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto channel_full = get_channel_full(channel_id, false, "get_channel_statistics_dc_id_impl");
+ if (channel_full == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat full info not found"));
+ }
+
+ if (!channel_full->stats_dc_id.is_exact() || (for_full_statistics && !channel_full->can_view_statistics)) {
+ return promise.set_error(Status::Error(400, "Chat statistics is not available"));
+ }
+
+ promise.set_value(DcId(channel_full->stats_dc_id));
+}
+
+void ContactsManager::get_channel_statistics(DialogId dialog_id, bool is_dark,
+ Promise<td_api::object_ptr<td_api::ChatStatistics>> &&promise) {
+ auto dc_id_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_id, is_dark, promise = std::move(promise)](Result<DcId> r_dc_id) mutable {
+ if (r_dc_id.is_error()) {
+ return promise.set_error(r_dc_id.move_as_error());
+ }
+ send_closure(actor_id, &ContactsManager::send_get_channel_stats_query, r_dc_id.move_as_ok(),
+ dialog_id.get_channel_id(), is_dark, std::move(promise));
+ });
+ get_channel_statistics_dc_id(dialog_id, true, std::move(dc_id_promise));
+}
+
+void ContactsManager::send_get_channel_stats_query(DcId dc_id, ChannelId channel_id, bool is_dark,
+ Promise<td_api::object_ptr<td_api::ChatStatistics>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ const Channel *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+ if (c->is_megagroup) {
+ td_->create_handler<GetMegagroupStatsQuery>(std::move(promise))->send(channel_id, is_dark, dc_id);
+ } else {
+ td_->create_handler<GetBroadcastStatsQuery>(std::move(promise))->send(channel_id, is_dark, dc_id);
+ }
+}
+
+bool ContactsManager::can_get_channel_message_statistics(DialogId dialog_id) const {
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return false;
}
- if (!have_input_user(user_id)) {
- return promise.set_error(Status::Error(6, "Have no access to the user"));
+ auto channel_id = dialog_id.get_channel_id();
+ const Channel *c = get_channel(channel_id);
+ if (c == nullptr || c->is_megagroup) {
+ return false;
}
- if (user_id == get_my_id("report_channel_spam")) {
- return promise.set_error(Status::Error(6, "Can't report self"));
+
+ if (td_->auth_manager_->is_bot()) {
+ return false;
}
- if (message_ids.empty()) {
- return promise.set_error(Status::Error(6, "Message list is empty"));
+ auto channel_full = get_channel_full(channel_id);
+ if (channel_full != nullptr) {
+ return channel_full->stats_dc_id.is_exact();
}
- vector<MessageId> server_message_ids;
+ return c->status.is_administrator();
+}
+
+void ContactsManager::get_channel_message_statistics(FullMessageId full_message_id, bool is_dark,
+ Promise<td_api::object_ptr<td_api::messageStatistics>> &&promise) {
+ auto dc_id_promise = PromiseCreator::lambda([actor_id = actor_id(this), full_message_id, is_dark,
+ promise = std::move(promise)](Result<DcId> r_dc_id) mutable {
+ if (r_dc_id.is_error()) {
+ return promise.set_error(r_dc_id.move_as_error());
+ }
+ send_closure(actor_id, &ContactsManager::send_get_channel_message_stats_query, r_dc_id.move_as_ok(),
+ full_message_id, is_dark, std::move(promise));
+ });
+ get_channel_statistics_dc_id(full_message_id.get_dialog_id(), false, std::move(dc_id_promise));
+}
+
+void ContactsManager::send_get_channel_message_stats_query(
+ DcId dc_id, FullMessageId full_message_id, bool is_dark,
+ Promise<td_api::object_ptr<td_api::messageStatistics>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto dialog_id = full_message_id.get_dialog_id();
+ if (!td_->messages_manager_->have_message_force(full_message_id, "send_get_channel_message_stats_query")) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ if (!td_->messages_manager_->can_get_message_statistics(full_message_id)) {
+ return promise.set_error(Status::Error(400, "Message statistics is inaccessible"));
+ }
+ CHECK(dialog_id.get_type() == DialogType::Channel);
+ td_->create_handler<GetMessageStatsQuery>(std::move(promise))
+ ->send(dialog_id.get_channel_id(), full_message_id.get_message_id(), is_dark, dc_id);
+}
+
+void ContactsManager::load_statistics_graph(DialogId dialog_id, string token, int64 x,
+ Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise) {
+ auto dc_id_promise = PromiseCreator::lambda([actor_id = actor_id(this), token = std::move(token), x,
+ promise = std::move(promise)](Result<DcId> r_dc_id) mutable {
+ if (r_dc_id.is_error()) {
+ return promise.set_error(r_dc_id.move_as_error());
+ }
+ send_closure(actor_id, &ContactsManager::send_load_async_graph_query, r_dc_id.move_as_ok(), std::move(token), x,
+ std::move(promise));
+ });
+ get_channel_statistics_dc_id(dialog_id, false, std::move(dc_id_promise));
+}
+
+void ContactsManager::send_load_async_graph_query(DcId dc_id, string token, int64 x,
+ Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ td_->create_handler<LoadAsyncGraphQuery>(std::move(promise))->send(token, x, dc_id);
+}
+
+void ContactsManager::report_channel_spam(ChannelId channel_id, const vector<MessageId> &message_ids,
+ Promise<Unit> &&promise) {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
+ }
+ if (!c->is_megagroup) {
+ return promise.set_error(Status::Error(400, "Spam can be reported only in supergroups"));
+ }
+ if (!c->status.is_administrator()) {
+ return promise.set_error(Status::Error(400, "Spam can be reported only by chat administrators"));
+ }
+
+ FlatHashMap<DialogId, vector<MessageId>, DialogIdHash> server_message_ids;
for (auto &message_id : message_ids) {
+ if (message_id.is_valid_scheduled()) {
+ return promise.set_error(Status::Error(400, "Can't report scheduled messages"));
+ }
+
if (!message_id.is_valid()) {
- return promise.set_error(Status::Error(6, "Message not found"));
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ if (!message_id.is_server()) {
+ continue;
}
- if (message_id.is_server()) {
- server_message_ids.push_back(message_id);
+ auto sender_dialog_id = td_->messages_manager_->get_dialog_message_sender({DialogId(channel_id), message_id});
+ if (sender_dialog_id.is_valid() && sender_dialog_id != DialogId(get_my_id()) &&
+ td_->messages_manager_->have_input_peer(sender_dialog_id, AccessRights::Know)) {
+ server_message_ids[sender_dialog_id].push_back(message_id);
}
}
if (server_message_ids.empty()) {
return promise.set_value(Unit());
}
- td_->create_handler<ReportChannelSpamQuery>(std::move(promise))->send(channel_id, user_id, server_message_ids);
+ MultiPromiseActorSafe mpas{"ReportSupergroupSpamMultiPromiseActor"};
+ mpas.add_promise(std::move(promise));
+ auto lock_promise = mpas.get_promise();
+
+ for (auto &it : server_message_ids) {
+ td_->create_handler<ReportChannelSpamQuery>(mpas.get_promise())->send(channel_id, it.first, it.second);
+ }
+
+ lock_promise.set_value(Unit());
+}
+
+void ContactsManager::delete_chat(ChatId chat_id, Promise<Unit> &&promise) {
+ auto c = get_chat(chat_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat info not found"));
+ }
+ if (!get_chat_status(c).is_creator()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to delete the chat"));
+ }
+ if (!c->is_active) {
+ return promise.set_error(Status::Error(400, "Chat is already deactivated"));
+ }
+
+ td_->create_handler<DeleteChatQuery>(std::move(promise))->send(chat_id);
}
void ContactsManager::delete_channel(ChannelId channel_id, Promise<Unit> &&promise) {
auto c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Supergroup not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
- if (!get_channel_status(c).is_creator()) {
- return promise.set_error(Status::Error(6, "Not enough rights to delete the supergroup"));
+ if (!get_channel_can_be_deleted(c)) {
+ return promise.set_error(Status::Error(400, "The chat can't be deleted"));
}
td_->create_handler<DeleteChannelQuery>(std::move(promise))->send(channel_id);
}
+void ContactsManager::delete_dialog(DialogId dialog_id, Promise<Unit> &&promise) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "delete_dialog")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return td_->messages_manager_->delete_dialog_history(dialog_id, true, true, std::move(promise));
+ case DialogType::Chat:
+ return delete_chat(dialog_id.get_chat_id(), std::move(promise));
+ case DialogType::Channel:
+ return delete_channel(dialog_id.get_channel_id(), std::move(promise));
+ case DialogType::SecretChat:
+ send_closure(td_->secret_chats_manager_, &SecretChatsManager::cancel_chat, dialog_id.get_secret_chat_id(), true,
+ std::move(promise));
+ return;
+ default:
+ UNREACHABLE();
+ }
+}
+
void ContactsManager::add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit,
Promise<Unit> &&promise) {
const Chat *c = get_chat(chat_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(3, "Chat info not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
if (!c->is_active) {
- return promise.set_error(Status::Error(3, "Chat is deactivated"));
+ return promise.set_error(Status::Error(400, "Chat is deactivated"));
}
if (forward_limit < 0) {
- return promise.set_error(Status::Error(3, "Can't forward negative number of messages"));
+ return promise.set_error(Status::Error(400, "Can't forward negative number of messages"));
}
- if (user_id != get_my_id("add_chat_participant")) {
- if (!get_chat_status(c).can_invite_users()) {
- return promise.set_error(Status::Error(3, "Not enough rights to invite members to the group chat"));
+ if (user_id != get_my_id()) {
+ if (!get_chat_permissions(c).can_invite_users()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to invite members to the group chat"));
}
- } else if (c->kicked) {
- return promise.set_error(Status::Error(3, "User was kicked from the chat"));
+ } else if (c->status.is_banned()) {
+ return promise.set_error(Status::Error(400, "User was kicked from the chat"));
}
// TODO upper bound on forward_limit
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(3, "User not found"));
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
}
// TODO invoke after
- td_->create_handler<AddChatUserQuery>(std::move(promise))->send(chat_id, std::move(input_user), forward_limit);
+ td_->create_handler<AddChatUserQuery>(std::move(promise))->send(chat_id, r_input_user.move_as_ok(), forward_limit);
}
-void ContactsManager::add_channel_participant(ChannelId channel_id, UserId user_id, Promise<Unit> &&promise,
- DialogParticipantStatus old_status) {
+void ContactsManager::add_channel_participant(ChannelId channel_id, UserId user_id,
+ const DialogParticipantStatus &old_status, Promise<Unit> &&promise) {
if (td_->auth_manager_->is_bot()) {
return promise.set_error(Status::Error(400, "Bots can't add new chat members"));
}
const Channel *c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(3, "Chat info not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(3, "User not found"));
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
}
- if (user_id == get_my_id("add_channel_participant")) {
+ if (user_id == get_my_id()) {
// join the channel
if (get_channel_status(c).is_banned()) {
- return promise.set_error(Status::Error(3, "Can't return to kicked from chat"));
+ return promise.set_error(Status::Error(400, "Can't return to kicked from chat"));
}
+ if (!get_channel_join_request(c)) {
+ speculative_add_channel_user(channel_id, user_id, DialogParticipantStatus::Member(), c->status);
+ }
td_->create_handler<JoinChannelQuery>(std::move(promise))->send(channel_id);
return;
}
- if (!(c->anyone_can_invite && get_channel_status(c).is_member()) && !get_channel_status(c).can_invite_users()) {
- return promise.set_error(Status::Error(3, "Not enough rights to invite members to the supergroup chat"));
+ if (!get_channel_permissions(c).can_invite_users()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to invite members to the supergroup chat"));
}
- speculative_add_channel_users(channel_id, DialogParticipantStatus::Member(), old_status);
+ speculative_add_channel_user(channel_id, user_id, DialogParticipantStatus::Member(), old_status);
vector<tl_object_ptr<telegram_api::InputUser>> input_users;
- input_users.push_back(std::move(input_user));
+ input_users.push_back(r_input_user.move_as_ok());
td_->create_handler<InviteToChannelQuery>(std::move(promise))->send(channel_id, std::move(input_users));
}
@@ -4158,25 +7738,28 @@ void ContactsManager::add_channel_participants(ChannelId channel_id, const vecto
const Channel *c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(3, "Chat info not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
- if (!(c->anyone_can_invite && get_channel_status(c).is_member()) && !get_channel_status(c).can_invite_users()) {
- return promise.set_error(Status::Error(3, "Not enough rights to invite members to the supergroup chat"));
+ if (!get_channel_permissions(c).can_invite_users()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to invite members to the supergroup chat"));
}
vector<tl_object_ptr<telegram_api::InputUser>> input_users;
for (auto user_id : user_ids) {
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(3, "User not found"));
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
}
- if (user_id == get_my_id("add_channel_participants")) {
+ if (user_id == get_my_id()) {
// can't invite self
continue;
}
- input_users.push_back(std::move(input_user));
+ input_users.push_back(r_input_user.move_as_ok());
+
+ speculative_add_channel_user(channel_id, user_id, DialogParticipantStatus::Member(),
+ DialogParticipantStatus::Left());
}
if (input_users.empty()) {
@@ -4186,77 +7769,101 @@ void ContactsManager::add_channel_participants(ChannelId channel_id, const vecto
td_->create_handler<InviteToChannelQuery>(std::move(promise))->send(channel_id, std::move(input_users));
}
-void ContactsManager::change_channel_participant_status(ChannelId channel_id, UserId user_id,
- DialogParticipantStatus status, Promise<Unit> &&promise) {
+void ContactsManager::set_channel_participant_status(ChannelId channel_id, DialogId participant_dialog_id,
+ td_api::object_ptr<td_api::ChatMemberStatus> &&chat_member_status,
+ Promise<Unit> &&promise) {
auto c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Chat info not found"));
- }
-
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(6, "User not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
+ auto status = get_dialog_participant_status(chat_member_status, get_channel_type(c));
- if (user_id == get_my_id("change_channel_participant_status")) {
+ if (participant_dialog_id == DialogId(get_my_id())) {
// fast path is needed, because get_channel_status may return Creator, while GetChannelParticipantQuery returning Left
- return change_channel_participant_status_impl(channel_id, user_id, std::move(status), get_channel_status(c),
- std::move(promise));
+ return set_channel_participant_status_impl(channel_id, participant_dialog_id, std::move(status),
+ get_channel_status(c), std::move(promise));
+ }
+ if (participant_dialog_id.get_type() != DialogType::User) {
+ if (status.is_administrator() || status.is_member() || status.is_restricted()) {
+ return promise.set_error(Status::Error(400, "Other chats can be only banned or unbanned"));
+ }
+ // always pretend that old_status is different
+ return restrict_channel_participant(
+ channel_id, participant_dialog_id, std::move(status),
+ status.is_banned() ? DialogParticipantStatus::Left() : DialogParticipantStatus::Banned(0), std::move(promise));
}
auto on_result_promise =
- PromiseCreator::lambda([actor_id = actor_id(this), channel_id, user_id, status,
+ PromiseCreator::lambda([actor_id = actor_id(this), channel_id, participant_dialog_id, status,
promise = std::move(promise)](Result<DialogParticipant> r_dialog_participant) mutable {
// ResultHandlers are cleared before managers, so it is safe to capture this
if (r_dialog_participant.is_error()) {
return promise.set_error(r_dialog_participant.move_as_error());
}
- send_closure(actor_id, &ContactsManager::change_channel_participant_status_impl, channel_id, user_id,
- std::move(status), r_dialog_participant.ok().status, std::move(promise));
+ send_closure(actor_id, &ContactsManager::set_channel_participant_status_impl, channel_id, participant_dialog_id,
+ std::move(status), r_dialog_participant.ok().status_, std::move(promise));
});
- td_->create_handler<GetChannelParticipantQuery>(std::move(on_result_promise))
- ->send(channel_id, user_id, std::move(input_user));
+ get_channel_participant(channel_id, participant_dialog_id, std::move(on_result_promise));
}
-void ContactsManager::change_channel_participant_status_impl(ChannelId channel_id, UserId user_id,
- DialogParticipantStatus status,
- DialogParticipantStatus old_status,
- Promise<Unit> &&promise) {
- if (old_status == status) {
+void ContactsManager::set_channel_participant_status_impl(ChannelId channel_id, DialogId participant_dialog_id,
+ DialogParticipantStatus new_status,
+ DialogParticipantStatus old_status, Promise<Unit> &&promise) {
+ if (old_status == new_status && !old_status.is_creator()) {
return promise.set_value(Unit());
}
+ CHECK(participant_dialog_id.get_type() == DialogType::User);
- LOG(INFO) << "Change status of " << user_id << " in " << channel_id << " from " << old_status << " to " << status;
+ LOG(INFO) << "Change status of " << participant_dialog_id << " in " << channel_id << " from " << old_status << " to "
+ << new_status;
bool need_add = false;
bool need_promote = false;
bool need_restrict = false;
- if (status.is_creator() || old_status.is_creator()) {
+ if (new_status.is_creator() || old_status.is_creator()) {
if (!old_status.is_creator()) {
- return promise.set_error(Status::Error(3, "Can't add creator to the chat"));
+ return promise.set_error(Status::Error(400, "Can't add another owner to the chat"));
}
- if (status.is_member()) {
- // creator member -> not creator member
+ if (!new_status.is_creator()) {
+ return promise.set_error(Status::Error(400, "Can't remove chat owner"));
+ }
+ auto user_id = get_my_id();
+ if (participant_dialog_id != DialogId(user_id)) {
+ return promise.set_error(Status::Error(400, "Not enough rights to edit chat owner rights"));
+ }
+ if (new_status.is_member() == old_status.is_member()) {
+ // change rank and is_anonymous
+ auto r_input_user = get_input_user(user_id);
+ CHECK(r_input_user.is_ok());
+ td_->create_handler<EditChannelAdminQuery>(std::move(promise))
+ ->send(channel_id, user_id, r_input_user.move_as_ok(), new_status);
+ return;
+ }
+ if (new_status.is_member()) {
// creator not member -> creator member
- // creator not member -> not creator member
- if (old_status.is_member()) {
- return promise.set_error(Status::Error(3, "Can't demote chat creator"));
- }
need_add = true;
} else {
// creator member -> creator not member
- // creator member -> not creator not member
- // creator not member -> not creator not member
- if (!old_status.is_member()) {
- return promise.set_error(Status::Error(3, "Can't restrict chat creator"));
- }
need_restrict = true;
}
- } else if (status.is_administrator()) {
+ } else if (new_status.is_administrator()) {
need_promote = true;
- } else if (!status.is_member() || status.is_restricted()) {
- need_restrict = true;
+ } else if (!new_status.is_member() || new_status.is_restricted()) {
+ if (new_status.is_member() && !old_status.is_member()) {
+ // TODO there is no way in server API to invite someone and change restrictions
+ // we need to first add user and change restrictions again after that
+ // but if restrictions aren't changed, then adding is enough
+ auto copy_old_status = old_status;
+ copy_old_status.set_is_member(true);
+ if (copy_old_status == new_status) {
+ need_add = true;
+ } else {
+ need_restrict = true;
+ }
+ } else {
+ need_restrict = true;
+ }
} else {
// regular member
if (old_status.is_administrator()) {
@@ -4270,256 +7877,590 @@ void ContactsManager::change_channel_participant_status_impl(ChannelId channel_i
}
if (need_promote) {
- return promote_channel_participant(channel_id, user_id, std::move(status), std::move(old_status),
+ if (participant_dialog_id.get_type() != DialogType::User) {
+ return promise.set_error(Status::Error(400, "Can't promote chats to chat administrators"));
+ }
+ return promote_channel_participant(channel_id, participant_dialog_id.get_user_id(), new_status, old_status,
std::move(promise));
} else if (need_restrict) {
- return restrict_channel_participant(channel_id, user_id, std::move(status), std::move(old_status),
+ return restrict_channel_participant(channel_id, participant_dialog_id, std::move(new_status), std::move(old_status),
std::move(promise));
} else {
CHECK(need_add);
- return add_channel_participant(channel_id, user_id, std::move(promise), std::move(old_status));
+ if (participant_dialog_id.get_type() != DialogType::User) {
+ return promise.set_error(Status::Error(400, "Can't add chats as chat members"));
+ }
+ return add_channel_participant(channel_id, participant_dialog_id.get_user_id(), old_status, std::move(promise));
}
}
-void ContactsManager::promote_channel_participant(ChannelId channel_id, UserId user_id, DialogParticipantStatus status,
- DialogParticipantStatus old_status, Promise<Unit> &&promise) {
- LOG(INFO) << "Promote " << user_id << " in " << channel_id << " from " << old_status << " to " << status;
+void ContactsManager::promote_channel_participant(ChannelId channel_id, UserId user_id,
+ const DialogParticipantStatus &new_status,
+ const DialogParticipantStatus &old_status, Promise<Unit> &&promise) {
+ LOG(INFO) << "Promote " << user_id << " in " << channel_id << " from " << old_status << " to " << new_status;
const Channel *c = get_channel(channel_id);
CHECK(c != nullptr);
- if (user_id == get_my_id("change_channel_participant_status")) {
- if (status.is_administrator()) {
- return promise.set_error(Status::Error(3, "Can't promote self"));
+ if (user_id == get_my_id()) {
+ if (new_status.is_administrator()) {
+ return promise.set_error(Status::Error(400, "Can't promote self"));
}
- CHECK(status.is_member());
+ CHECK(new_status.is_member());
// allow to demote self. TODO is it allowed server-side?
} else {
- if (!get_channel_status(c).can_promote_members()) {
- return promise.set_error(Status::Error(3, "Not enough rights"));
+ if (!get_channel_permissions(c).can_promote_members()) {
+ return promise.set_error(Status::Error(400, "Not enough rights"));
}
- }
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(3, "User not found"));
+ CHECK(!old_status.is_creator());
+ CHECK(!new_status.is_creator());
}
- speculative_add_channel_users(channel_id, status, old_status);
- td_->create_handler<EditChannelAdminQuery>(std::move(promise))->send(channel_id, std::move(input_user), status);
+ TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id));
+
+ speculative_add_channel_user(channel_id, user_id, new_status, old_status);
+ td_->create_handler<EditChannelAdminQuery>(std::move(promise))
+ ->send(channel_id, user_id, std::move(input_user), new_status);
}
-void ContactsManager::change_chat_participant_status(ChatId chat_id, UserId user_id, DialogParticipantStatus status,
- Promise<Unit> &&promise) {
+void ContactsManager::set_chat_participant_status(ChatId chat_id, UserId user_id, DialogParticipantStatus status,
+ Promise<Unit> &&promise) {
if (!status.is_member()) {
- return delete_chat_participant(chat_id, user_id, std::move(promise));
+ return delete_chat_participant(chat_id, user_id, false, std::move(promise));
+ }
+ if (status.is_creator()) {
+ return promise.set_error(Status::Error(400, "Can't change owner in basic group chats"));
+ }
+ if (status.is_restricted()) {
+ return promise.set_error(Status::Error(400, "Can't restrict users in basic group chats"));
}
auto c = get_chat(chat_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(6, "Chat info not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
+ }
+ if (!c->is_active) {
+ return promise.set_error(Status::Error(400, "Chat is deactivated"));
}
- if (!get_chat_status(c).can_promote_members()) {
- return promise.set_error(Status::Error(3, "Need creator rights in the group chat"));
+ auto chat_full = get_chat_full(chat_id);
+ if (chat_full == nullptr) {
+ auto load_chat_full_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), chat_id, user_id, status = std::move(status),
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &ContactsManager::set_chat_participant_status, chat_id, user_id, status,
+ std::move(promise));
+ }
+ });
+ return load_chat_full(chat_id, false, std::move(load_chat_full_promise), "set_chat_participant_status");
}
- if (c->everyone_is_administrator) {
- return promise.set_error(Status::Error(3, "Administrators editing is disabled in the group chat"));
+ auto participant = get_chat_full_participant(chat_full, DialogId(user_id));
+ if (participant == nullptr && !status.is_administrator()) {
+ // the user isn't a member, but needs to be added
+ return add_chat_participant(chat_id, user_id, 0, std::move(promise));
}
- if (user_id == get_my_id("change_chat_participant_status")) {
- return promise.set_error(Status::Error(3, "Can't change chat member status of self"));
+ if (!get_chat_permissions(c).can_promote_members()) {
+ return promise.set_error(Status::Error(400, "Need owner rights in the group chat"));
}
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(3, "User not found"));
+ if (user_id == get_my_id()) {
+ return promise.set_error(Status::Error(400, "Can't promote or demote self"));
}
- if (status.is_creator()) {
- return promise.set_error(Status::Error(3, "Can't add creator to the group chat"));
+ if (participant == nullptr) {
+ // the user must be added first
+ CHECK(status.is_administrator());
+ auto add_chat_participant_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), chat_id, user_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &ContactsManager::send_edit_chat_admin_query, chat_id, user_id, true,
+ std::move(promise));
+ }
+ });
+ return add_chat_participant(chat_id, user_id, 0, std::move(add_chat_participant_promise));
}
- if (status.is_restricted()) {
- return promise.set_error(Status::Error(3, "Can't restrict users in a basic group chat"));
+
+ send_edit_chat_admin_query(chat_id, user_id, status.is_administrator(), std::move(promise));
+}
+
+void ContactsManager::send_edit_chat_admin_query(ChatId chat_id, UserId user_id, bool is_administrator,
+ Promise<Unit> &&promise) {
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
}
td_->create_handler<EditChatAdminQuery>(std::move(promise))
- ->send(chat_id, std::move(input_user), status.is_administrator());
+ ->send(chat_id, r_input_user.move_as_ok(), is_administrator);
}
-void ContactsManager::export_chat_invite_link(ChatId chat_id, Promise<Unit> &&promise) {
- const Chat *c = get_chat(chat_id);
- if (c == nullptr) {
- return promise.set_error(Status::Error(3, "Chat info not found"));
+void ContactsManager::can_transfer_ownership(Promise<CanTransferOwnershipResult> &&promise) {
+ auto request_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<Unit> r_result) mutable {
+ CHECK(r_result.is_error());
+
+ auto error = r_result.move_as_error();
+ CanTransferOwnershipResult result;
+ if (error.message() == "PASSWORD_HASH_INVALID") {
+ return promise.set_value(std::move(result));
+ }
+ if (error.message() == "PASSWORD_MISSING") {
+ result.type = CanTransferOwnershipResult::Type::PasswordNeeded;
+ return promise.set_value(std::move(result));
+ }
+ if (begins_with(error.message(), "PASSWORD_TOO_FRESH_")) {
+ result.type = CanTransferOwnershipResult::Type::PasswordTooFresh;
+ result.retry_after = to_integer<int32>(error.message().substr(Slice("PASSWORD_TOO_FRESH_").size()));
+ if (result.retry_after < 0) {
+ result.retry_after = 0;
+ }
+ return promise.set_value(std::move(result));
+ }
+ if (begins_with(error.message(), "SESSION_TOO_FRESH_")) {
+ result.type = CanTransferOwnershipResult::Type::SessionTooFresh;
+ result.retry_after = to_integer<int32>(error.message().substr(Slice("SESSION_TOO_FRESH_").size()));
+ if (result.retry_after < 0) {
+ result.retry_after = 0;
+ }
+ return promise.set_value(std::move(result));
+ }
+ promise.set_error(std::move(error));
+ });
+
+ td_->create_handler<CanEditChannelCreatorQuery>(std::move(request_promise))->send();
+}
+
+td_api::object_ptr<td_api::CanTransferOwnershipResult> ContactsManager::get_can_transfer_ownership_result_object(
+ CanTransferOwnershipResult result) {
+ switch (result.type) {
+ case CanTransferOwnershipResult::Type::Ok:
+ return td_api::make_object<td_api::canTransferOwnershipResultOk>();
+ case CanTransferOwnershipResult::Type::PasswordNeeded:
+ return td_api::make_object<td_api::canTransferOwnershipResultPasswordNeeded>();
+ case CanTransferOwnershipResult::Type::PasswordTooFresh:
+ return td_api::make_object<td_api::canTransferOwnershipResultPasswordTooFresh>(result.retry_after);
+ case CanTransferOwnershipResult::Type::SessionTooFresh:
+ return td_api::make_object<td_api::canTransferOwnershipResultSessionTooFresh>(result.retry_after);
+ default:
+ UNREACHABLE();
+ return nullptr;
}
- if (!c->is_active) {
- return promise.set_error(Status::Error(3, "Chat is deactivated"));
+}
+
+void ContactsManager::transfer_dialog_ownership(DialogId dialog_id, UserId user_id, const string &password,
+ Promise<Unit> &&promise) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "transfer_dialog_ownership")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!have_user_force(user_id)) {
+ return promise.set_error(Status::Error(400, "User not found"));
+ }
+ if (is_user_bot(user_id)) {
+ return promise.set_error(Status::Error(400, "User is a bot"));
+ }
+ if (is_user_deleted(user_id)) {
+ return promise.set_error(Status::Error(400, "User is deleted"));
+ }
+ if (password.empty()) {
+ return promise.set_error(Status::Error(400, "PASSWORD_HASH_INVALID"));
}
- if (!get_chat_status(c).can_export_dialog_invite_link()) {
- return promise.set_error(Status::Error(3, "Not enough rights to export chat invite link"));
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::Chat:
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Can't transfer chat ownership"));
+ case DialogType::Channel:
+ send_closure(
+ td_->password_manager_, &PasswordManager::get_input_check_password_srp, password,
+ PromiseCreator::lambda([actor_id = actor_id(this), channel_id = dialog_id.get_channel_id(), user_id,
+ promise = std::move(promise)](
+ Result<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ send_closure(actor_id, &ContactsManager::transfer_channel_ownership, channel_id, user_id,
+ result.move_as_ok(), std::move(promise));
+ }));
+ break;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
}
+}
+
+void ContactsManager::transfer_channel_ownership(
+ ChannelId channel_id, UserId user_id, tl_object_ptr<telegram_api::InputCheckPasswordSRP> input_check_password,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
- td_->create_handler<ExportChatInviteLinkQuery>(std::move(promise))->send(chat_id);
+ td_->create_handler<EditChannelCreatorQuery>(std::move(promise))
+ ->send(channel_id, user_id, std::move(input_check_password));
}
-void ContactsManager::export_channel_invite_link(ChannelId channel_id, Promise<Unit> &&promise) {
- const Channel *c = get_channel(channel_id);
- if (c == nullptr) {
- return promise.set_error(Status::Error(3, "Chat info not found"));
+Status ContactsManager::can_manage_dialog_invite_links(DialogId dialog_id, bool creator_only) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "can_manage_dialog_invite_links")) {
+ return Status::Error(400, "Chat not found");
}
- if (!get_channel_status(c).can_export_dialog_invite_link()) {
- return promise.set_error(Status::Error(3, "Not enough rights to export chat invite link"));
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return Status::Error(400, "Can't invite members to a private chat");
+ case DialogType::Chat: {
+ const Chat *c = get_chat(dialog_id.get_chat_id());
+ if (c == nullptr) {
+ return Status::Error(400, "Chat info not found");
+ }
+ if (!c->is_active) {
+ return Status::Error(400, "Chat is deactivated");
+ }
+ bool have_rights = creator_only ? c->status.is_creator() : c->status.can_manage_invite_links();
+ if (!have_rights) {
+ return Status::Error(400, "Not enough rights to manage chat invite link");
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ const Channel *c = get_channel(dialog_id.get_channel_id());
+ if (c == nullptr) {
+ return Status::Error(400, "Chat info not found");
+ }
+ bool have_rights = creator_only ? c->status.is_creator() : c->status.can_manage_invite_links();
+ if (!have_rights) {
+ return Status::Error(400, "Not enough rights to manage chat invite link");
+ }
+ break;
+ }
+ case DialogType::SecretChat:
+ return Status::Error(400, "Can't invite members to a secret chat");
+ case DialogType::None:
+ default:
+ UNREACHABLE();
}
+ return Status::OK();
+}
- td_->create_handler<ExportChannelInviteLinkQuery>(std::move(promise))->send(channel_id);
+void ContactsManager::export_dialog_invite_link(DialogId dialog_id, string title, int32 expire_date, int32 usage_limit,
+ bool creates_join_request, bool is_permanent,
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise) {
+ get_me(PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, title = std::move(title), expire_date,
+ usage_limit, creates_join_request, is_permanent,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &ContactsManager::export_dialog_invite_link_impl, dialog_id, std::move(title), expire_date,
+ usage_limit, creates_join_request, is_permanent, std::move(promise));
+ }
+ }));
}
-void ContactsManager::check_dialog_invite_link(const string &invite_link, Promise<Unit> &&promise) const {
- auto it = invite_link_infos_.find(invite_link);
- if (it != invite_link_infos_.end()) {
- return promise.set_value(Unit());
+void ContactsManager::export_dialog_invite_link_impl(DialogId dialog_id, string title, int32 expire_date,
+ int32 usage_limit, bool creates_join_request, bool is_permanent,
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id));
+ if (creates_join_request && usage_limit > 0) {
+ return promise.set_error(
+ Status::Error(400, "Member limit can't be specified for links requiring administrator approval"));
}
- if (!is_valid_invite_link(invite_link)) {
- return promise.set_error(Status::Error(3, "Wrong invite link"));
+ auto new_title = clean_name(std::move(title), MAX_INVITE_LINK_TITLE_LENGTH);
+ td_->create_handler<ExportChatInviteQuery>(std::move(promise))
+ ->send(dialog_id, new_title, expire_date, usage_limit, creates_join_request, is_permanent);
+}
+
+void ContactsManager::edit_dialog_invite_link(DialogId dialog_id, const string &invite_link, string title,
+ int32 expire_date, int32 usage_limit, bool creates_join_request,
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id));
+ if (creates_join_request && usage_limit > 0) {
+ return promise.set_error(
+ Status::Error(400, "Member limit can't be specified for links requiring administrator approval"));
}
- td_->create_handler<CheckDialogInviteLinkQuery>(std::move(promise))->send(invite_link);
+ if (invite_link.empty()) {
+ return promise.set_error(Status::Error(400, "Invite link must be non-empty"));
+ }
+
+ auto new_title = clean_name(std::move(title), MAX_INVITE_LINK_TITLE_LENGTH);
+ td_->create_handler<EditChatInviteLinkQuery>(std::move(promise))
+ ->send(dialog_id, invite_link, new_title, expire_date, usage_limit, creates_join_request);
}
-void ContactsManager::import_dialog_invite_link(const string &invite_link, Promise<DialogId> &&promise) {
- if (!is_valid_invite_link(invite_link)) {
- return promise.set_error(Status::Error(3, "Wrong invite link"));
+void ContactsManager::get_dialog_invite_link(DialogId dialog_id, const string &invite_link,
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id, false));
+
+ if (invite_link.empty()) {
+ return promise.set_error(Status::Error(400, "Invite link must be non-empty"));
}
- td_->create_handler<ImportDialogInviteLinkQuery>(std::move(promise))->send(invite_link);
+ td_->create_handler<GetExportedChatInviteQuery>(std::move(promise))->send(dialog_id, invite_link);
}
-string ContactsManager::get_chat_invite_link(ChatId chat_id) const {
- auto chat_full = get_chat_full(chat_id);
- if (chat_full == nullptr) {
- auto it = chat_invite_links_.find(chat_id);
- return it == chat_invite_links_.end() ? string() : it->second;
+void ContactsManager::get_dialog_invite_link_counts(
+ DialogId dialog_id, Promise<td_api::object_ptr<td_api::chatInviteLinkCounts>> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id, true));
+
+ td_->create_handler<GetChatAdminWithInvitesQuery>(std::move(promise))->send(dialog_id);
+}
+
+void ContactsManager::get_dialog_invite_links(DialogId dialog_id, UserId creator_user_id, bool is_revoked,
+ int32 offset_date, const string &offset_invite_link, int32 limit,
+ Promise<td_api::object_ptr<td_api::chatInviteLinks>> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id, creator_user_id != get_my_id()));
+ TRY_RESULT_PROMISE(promise, input_user, get_input_user(creator_user_id));
+
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
- return chat_full->invite_link;
+
+ td_->create_handler<GetExportedChatInvitesQuery>(std::move(promise))
+ ->send(dialog_id, std::move(input_user), is_revoked, offset_date, offset_invite_link, limit);
}
-string ContactsManager::get_channel_invite_link(
- ChannelId channel_id) { // should be non-const to update ChannelFull cache
- auto channel_full = get_channel_full(channel_id);
- if (channel_full == nullptr) {
- auto it = channel_invite_links_.find(channel_id);
- return it == channel_invite_links_.end() ? string() : it->second;
+void ContactsManager::get_dialog_invite_link_users(
+ DialogId dialog_id, const string &invite_link, td_api::object_ptr<td_api::chatInviteLinkMember> offset_member,
+ int32 limit, Promise<td_api::object_ptr<td_api::chatInviteLinkMembers>> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id));
+
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ }
+
+ if (invite_link.empty()) {
+ return promise.set_error(Status::Error(400, "Invite link must be non-empty"));
+ }
+
+ UserId offset_user_id;
+ int32 offset_date = 0;
+ if (offset_member != nullptr) {
+ offset_user_id = UserId(offset_member->user_id_);
+ offset_date = offset_member->joined_chat_date_;
}
- return channel_full->invite_link;
+
+ td_->create_handler<GetChatInviteImportersQuery>(std::move(promise))
+ ->send(dialog_id, invite_link, offset_date, offset_user_id, limit);
}
-MessageId ContactsManager::get_channel_pinned_message_id(
- ChannelId channel_id) { // should be non-const to update ChannelFull cache
- auto channel_full = get_channel_full(channel_id);
- if (channel_full == nullptr) {
- return MessageId();
+void ContactsManager::get_dialog_join_requests(DialogId dialog_id, const string &invite_link, const string &query,
+ td_api::object_ptr<td_api::chatJoinRequest> offset_request, int32 limit,
+ Promise<td_api::object_ptr<td_api::chatJoinRequests>> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id));
+
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
- return channel_full->pinned_message_id;
+
+ UserId offset_user_id;
+ int32 offset_date = 0;
+ if (offset_request != nullptr) {
+ offset_user_id = UserId(offset_request->user_id_);
+ offset_date = offset_request->date_;
+ }
+
+ td_->create_handler<GetChatJoinRequestsQuery>(std::move(promise))
+ ->send(dialog_id, invite_link, query, offset_date, offset_user_id, limit);
}
-void ContactsManager::delete_chat_participant(ChatId chat_id, UserId user_id, Promise<Unit> &&promise) {
+void ContactsManager::process_dialog_join_request(DialogId dialog_id, UserId user_id, bool approve,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id));
+ td_->create_handler<HideChatJoinRequestQuery>(std::move(promise))->send(dialog_id, user_id, approve);
+}
+
+void ContactsManager::process_dialog_join_requests(DialogId dialog_id, const string &invite_link, bool approve,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id));
+ td_->create_handler<HideAllChatJoinRequestsQuery>(std::move(promise))->send(dialog_id, invite_link, approve);
+}
+
+void ContactsManager::revoke_dialog_invite_link(DialogId dialog_id, const string &invite_link,
+ Promise<td_api::object_ptr<td_api::chatInviteLinks>> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id));
+
+ if (invite_link.empty()) {
+ return promise.set_error(Status::Error(400, "Invite link must be non-empty"));
+ }
+
+ td_->create_handler<RevokeChatInviteLinkQuery>(std::move(promise))->send(dialog_id, invite_link);
+}
+
+void ContactsManager::delete_revoked_dialog_invite_link(DialogId dialog_id, const string &invite_link,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id));
+
+ if (invite_link.empty()) {
+ return promise.set_error(Status::Error(400, "Invite link must be non-empty"));
+ }
+
+ td_->create_handler<DeleteExportedChatInviteQuery>(std::move(promise))->send(dialog_id, invite_link);
+}
+
+void ContactsManager::delete_all_revoked_dialog_invite_links(DialogId dialog_id, UserId creator_user_id,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_manage_dialog_invite_links(dialog_id, creator_user_id != get_my_id()));
+ TRY_RESULT_PROMISE(promise, input_user, get_input_user(creator_user_id));
+
+ td_->create_handler<DeleteRevokedExportedChatInvitesQuery>(std::move(promise))
+ ->send(dialog_id, std::move(input_user));
+}
+
+void ContactsManager::check_dialog_invite_link(const string &invite_link, bool force, Promise<Unit> &&promise) {
+ auto it = invite_link_infos_.find(invite_link);
+ if (it != invite_link_infos_.end()) {
+ auto dialog_id = it->second->dialog_id;
+ if (!force && dialog_id.get_type() == DialogType::Chat && !get_chat_is_active(dialog_id.get_chat_id())) {
+ invite_link_infos_.erase(it);
+ } else {
+ return promise.set_value(Unit());
+ }
+ }
+
+ if (!DialogInviteLink::is_valid_invite_link(invite_link)) {
+ return promise.set_error(Status::Error(400, "Wrong invite link"));
+ }
+
+ CHECK(!invite_link.empty());
+ td_->create_handler<CheckChatInviteQuery>(std::move(promise))->send(invite_link);
+}
+
+void ContactsManager::import_dialog_invite_link(const string &invite_link, Promise<DialogId> &&promise) {
+ if (!DialogInviteLink::is_valid_invite_link(invite_link)) {
+ return promise.set_error(Status::Error(400, "Wrong invite link"));
+ }
+
+ td_->create_handler<ImportChatInviteQuery>(std::move(promise))->send(invite_link);
+}
+
+void ContactsManager::delete_chat_participant(ChatId chat_id, UserId user_id, bool revoke_messages,
+ Promise<Unit> &&promise) {
const Chat *c = get_chat(chat_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(3, "Chat info not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
if (!c->is_active) {
- return promise.set_error(Status::Error(3, "Chat is deactivated"));
+ return promise.set_error(Status::Error(400, "Chat is deactivated"));
}
- auto my_id = get_my_id("delete_chat_participant");
- if (c->left) {
+ auto my_id = get_my_id();
+ if (c->status.is_left()) {
if (user_id == my_id) {
+ if (revoke_messages) {
+ return td_->messages_manager_->delete_dialog_history(DialogId(chat_id), true, false, std::move(promise));
+ }
return promise.set_value(Unit());
} else {
- return promise.set_error(Status::Error(3, "Not in the chat"));
+ return promise.set_error(Status::Error(400, "Not in the chat"));
}
}
if (user_id != my_id) {
- auto my_status = get_chat_status(c);
+ auto my_status = get_chat_permissions(c);
if (!my_status.is_creator()) { // creator can delete anyone
auto participant = get_chat_participant(chat_id, user_id);
if (participant != nullptr) { // if have no information about participant, just send request to the server
+ /*
+ TODO
if (c->everyone_is_administrator) {
// if all are administrators, only invited by me participants can be deleted
- if (participant->inviter_user_id != my_id) {
- return promise.set_error(Status::Error(3, "Need to be inviter of a user to kick it from a basic group"));
+ if (participant->inviter_user_id_ != my_id) {
+ return promise.set_error(Status::Error(400, "Need to be inviter of a user to kick it from a basic group"));
}
} else {
// otherwise, only creator can kick administrators
- if (participant->status.is_administrator()) {
+ if (participant->status_.is_administrator()) {
return promise.set_error(
- Status::Error(3, "Only the creator of a basic group can kick group administrators"));
+ Status::Error(400, "Only the creator of a basic group can kick group administrators"));
}
// regular users can be kicked by administrators and their inviters
- if (!my_status.is_administrator() && participant->inviter_user_id != my_id) {
- return promise.set_error(Status::Error(3, "Need to be inviter of a user to kick it from a basic group"));
+ if (!my_status.is_administrator() && participant->inviter_user_id_ != my_id) {
+ return promise.set_error(Status::Error(400, "Need to be inviter of a user to kick it from a basic group"));
}
}
+ */
}
}
}
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(3, "User not found"));
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
}
// TODO invoke after
- td_->create_handler<DeleteChatUserQuery>(std::move(promise))->send(chat_id, std::move(input_user));
+ td_->create_handler<DeleteChatUserQuery>(std::move(promise))
+ ->send(chat_id, r_input_user.move_as_ok(), revoke_messages);
}
-void ContactsManager::restrict_channel_participant(ChannelId channel_id, UserId user_id, DialogParticipantStatus status,
- DialogParticipantStatus old_status, Promise<Unit> &&promise) {
- LOG(INFO) << "Restrict " << user_id << " in " << channel_id << " from " << old_status << " to " << status;
+void ContactsManager::restrict_channel_participant(ChannelId channel_id, DialogId participant_dialog_id,
+ DialogParticipantStatus &&new_status,
+ DialogParticipantStatus &&old_status, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ LOG(INFO) << "Restrict " << participant_dialog_id << " in " << channel_id << " from " << old_status << " to "
+ << new_status;
const Channel *c = get_channel(channel_id);
if (c == nullptr) {
- return promise.set_error(Status::Error(3, "Chat info not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
- if (!c->status.is_member()) {
- if (user_id == get_my_id("restrict_channel_participant")) {
- if (status.is_member()) {
- return promise.set_error(Status::Error(3, "Can't unrestrict self"));
+ if (!c->status.is_member() && !c->status.is_creator()) {
+ if (participant_dialog_id == DialogId(get_my_id())) {
+ if (new_status.is_member()) {
+ return promise.set_error(Status::Error(400, "Can't unrestrict self"));
}
return promise.set_value(Unit());
} else {
- return promise.set_error(Status::Error(3, "Not in the chat"));
+ return promise.set_error(Status::Error(400, "Not in the chat"));
}
}
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(3, "User not found"));
+ auto input_peer = td_->messages_manager_->get_input_peer(participant_dialog_id, AccessRights::Know);
+ if (input_peer == nullptr) {
+ return promise.set_error(Status::Error(400, "Member not found"));
}
- if (user_id == get_my_id("restrict_channel_participant")) {
- if (status.is_restricted() || status.is_banned()) {
- return promise.set_error(Status::Error(3, "Can't restrict self"));
+ if (participant_dialog_id == DialogId(get_my_id())) {
+ if (new_status.is_restricted() || new_status.is_banned()) {
+ return promise.set_error(Status::Error(400, "Can't restrict self"));
}
- if (status.is_member()) {
- return promise.set_error(Status::Error(3, "Can't unrestrict self"));
+ if (new_status.is_member()) {
+ return promise.set_error(Status::Error(400, "Can't unrestrict self"));
}
// leave the channel
+ speculative_add_channel_user(channel_id, participant_dialog_id.get_user_id(), new_status, c->status);
td_->create_handler<LeaveChannelQuery>(std::move(promise))->send(channel_id);
return;
}
- if (status.is_creator()) {
- return promise.set_error(Status::Error(3, "Not enough rights to restrict chat creator"));
+ switch (participant_dialog_id.get_type()) {
+ case DialogType::User:
+ // ok;
+ break;
+ case DialogType::Channel:
+ if (new_status.is_administrator() || new_status.is_member() || new_status.is_restricted()) {
+ return promise.set_error(Status::Error(400, "Other chats can be only banned or unbanned"));
+ }
+ break;
+ default:
+ return promise.set_error(Status::Error(400, "Can't restrict the chat"));
}
- if (!get_channel_status(c).can_restrict_members()) {
- return promise.set_error(Status::Error(3, "Not enough rights to restrict/unrestrict chat member"));
+ CHECK(!old_status.is_creator());
+ CHECK(!new_status.is_creator());
+
+ if (!get_channel_permissions(c).can_restrict_members()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to restrict/unrestrict chat member"));
}
- if (old_status.is_member() && !status.is_member() && !status.is_banned()) {
+ if (old_status.is_member() && !new_status.is_member() && !new_status.is_banned()) {
// we can't make participant Left without kicking it first
- auto on_result_promise = PromiseCreator::lambda([channel_id, user_id, status,
+ auto on_result_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id, participant_dialog_id,
+ new_status = std::move(new_status),
promise = std::move(promise)](Result<> result) mutable {
if (result.is_error()) {
return promise.set_error(result.move_as_error());
@@ -4527,34 +8468,79 @@ void ContactsManager::restrict_channel_participant(ChannelId channel_id, UserId
create_actor<SleepActor>(
"RestrictChannelParticipantSleepActor", 1.0,
- PromiseCreator::lambda([channel_id, user_id, status, promise = std::move(promise)](Result<> result) mutable {
+ PromiseCreator::lambda([actor_id, channel_id, participant_dialog_id, new_status = std::move(new_status),
+ promise = std::move(promise)](Result<> result) mutable {
if (result.is_error()) {
return promise.set_error(result.move_as_error());
}
- send_closure(G()->contacts_manager(), &ContactsManager::restrict_channel_participant, channel_id, user_id,
- status, DialogParticipantStatus::Banned(0), std::move(promise));
+ send_closure(actor_id, &ContactsManager::restrict_channel_participant, channel_id, participant_dialog_id,
+ std::move(new_status), DialogParticipantStatus::Banned(0), std::move(promise));
}))
.release();
});
promise = std::move(on_result_promise);
- status = DialogParticipantStatus::Banned(0);
+ new_status = DialogParticipantStatus::Banned(G()->unix_time() + 60);
+ }
+
+ if (new_status.is_member() && !old_status.is_member()) {
+ // there is no way in server API to invite someone and change restrictions
+ // we need to first change restrictions and then try to add the user
+ CHECK(participant_dialog_id.get_type() == DialogType::User);
+ new_status.set_is_member(false);
+ auto on_result_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), channel_id, participant_dialog_id, old_status = new_status,
+ promise = std::move(promise)](Result<> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+
+ create_actor<SleepActor>(
+ "AddChannelParticipantSleepActor", 1.0,
+ PromiseCreator::lambda([actor_id, channel_id, participant_dialog_id, old_status = std::move(old_status),
+ promise = std::move(promise)](Result<> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+
+ send_closure(actor_id, &ContactsManager::add_channel_participant, channel_id,
+ participant_dialog_id.get_user_id(), old_status, std::move(promise));
+ }))
+ .release();
+ });
+
+ promise = std::move(on_result_promise);
+ }
+
+ if (participant_dialog_id.get_type() == DialogType::User) {
+ speculative_add_channel_user(channel_id, participant_dialog_id.get_user_id(), new_status, old_status);
}
+ td_->create_handler<EditChannelBannedQuery>(std::move(promise))
+ ->send(channel_id, participant_dialog_id, std::move(input_peer), new_status);
+}
- speculative_add_channel_users(channel_id, status, old_status);
- td_->create_handler<EditChannelBannedQuery>(std::move(promise))->send(channel_id, std::move(input_user), status);
+void ContactsManager::on_set_channel_participant_status(ChannelId channel_id, DialogId participant_dialog_id,
+ DialogParticipantStatus status) {
+ if (G()->close_flag() || participant_dialog_id == DialogId(get_my_id())) {
+ return;
+ }
+
+ status.update_restrictions();
+ if (have_channel_participant_cache(channel_id)) {
+ update_channel_participant_status_cache(channel_id, participant_dialog_id, std::move(status));
+ }
}
ChannelId ContactsManager::migrate_chat_to_megagroup(ChatId chat_id, Promise<Unit> &promise) {
auto c = get_chat(chat_id);
if (c == nullptr) {
- promise.set_error(Status::Error(3, "Chat info not found"));
+ promise.set_error(Status::Error(400, "Chat info not found"));
return ChannelId();
}
- if (!c->is_creator) {
- promise.set_error(Status::Error(3, "Need creator rights in the chat"));
+ if (!c->status.is_creator()) {
+ promise.set_error(Status::Error(400, "Need creator rights in the chat"));
return ChannelId();
}
@@ -4566,67 +8552,363 @@ ChannelId ContactsManager::migrate_chat_to_megagroup(ChatId chat_id, Promise<Uni
return ChannelId();
}
-vector<DialogId> ContactsManager::get_created_public_dialogs(Promise<Unit> &&promise) {
- if (created_public_channels_inited_) {
- promise.set_value(Unit());
- return transform(created_public_channels_, [&](ChannelId channel_id) {
- DialogId dialog_id(channel_id);
- td_->messages_manager_->force_create_dialog(dialog_id, "get_created_public_dialogs");
- return dialog_id;
- });
+vector<ChannelId> ContactsManager::get_channel_ids(vector<tl_object_ptr<telegram_api::Chat>> &&chats,
+ const char *source) {
+ vector<ChannelId> channel_ids;
+ for (auto &chat : chats) {
+ auto channel_id = get_channel_id(chat);
+ if (!channel_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << " in " << to_string(chat);
+ continue;
+ }
+ on_get_chat(std::move(chat), source);
+ if (have_channel(channel_id)) {
+ channel_ids.push_back(channel_id);
+ }
}
+ return channel_ids;
+}
- td_->create_handler<GetCreatedPublicChannelsQuery>(std::move(promise))->send();
- return {};
+vector<DialogId> ContactsManager::get_dialog_ids(vector<tl_object_ptr<telegram_api::Chat>> &&chats,
+ const char *source) {
+ vector<DialogId> dialog_ids;
+ for (auto &chat : chats) {
+ auto channel_id = get_channel_id(chat);
+ if (!channel_id.is_valid()) {
+ auto chat_id = get_chat_id(chat);
+ if (!chat_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid chat from " << source << " in " << to_string(chat);
+ } else {
+ dialog_ids.push_back(DialogId(chat_id));
+ }
+ } else {
+ dialog_ids.push_back(DialogId(channel_id));
+ }
+ on_get_chat(std::move(chat), source);
+ }
+ return dialog_ids;
}
-void ContactsManager::on_get_created_public_channels(vector<tl_object_ptr<telegram_api::Chat>> &&chats) {
- created_public_channels_inited_ = true;
- created_public_channels_.clear();
+void ContactsManager::return_created_public_dialogs(Promise<td_api::object_ptr<td_api::chats>> &&promise,
+ const vector<ChannelId> &channel_ids) {
+ if (!promise) {
+ return;
+ }
- for (auto &chat : chats) {
- switch (chat->get_id()) {
- case telegram_api::chatEmpty::ID:
- LOG(ERROR) << "Receive chatEmpty as created public channel";
- break;
- case telegram_api::chat::ID:
- LOG(ERROR) << "Receive chat as created public channel";
- break;
- case telegram_api::chatForbidden::ID:
- LOG(ERROR) << "Receive chatForbidden as created public channel";
- break;
- case telegram_api::channel::ID: {
- auto c = static_cast<const telegram_api::channel *>(chat.get());
- ChannelId channel_id(c->id_);
+ auto total_count = narrow_cast<int32>(channel_ids.size());
+ promise.set_value(td_api::make_object<td_api::chats>(
+ total_count, transform(channel_ids, [](ChannelId channel_id) { return DialogId(channel_id).get(); })));
+}
+
+void ContactsManager::get_created_public_dialogs(PublicDialogType type,
+ Promise<td_api::object_ptr<td_api::chats>> &&promise,
+ bool from_binlog) {
+ auto index = static_cast<int32>(type);
+ if (created_public_channels_inited_[index]) {
+ return return_created_public_dialogs(std::move(promise), created_public_channels_[index]);
+ }
+
+ if (get_created_public_channels_queries_[index].empty() && G()->parameters().use_chat_info_db) {
+ auto pmc_key = PSTRING() << "public_channels" << index;
+ auto str = G()->td_db()->get_binlog_pmc()->get(pmc_key);
+ if (!str.empty()) {
+ auto r_channel_ids = transform(full_split(Slice(str), ','), [](Slice str) -> Result<ChannelId> {
+ TRY_RESULT(channel_id_int, to_integer_safe<int64>(str));
+ ChannelId channel_id(channel_id_int);
if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
- continue;
+ return Status::Error("Have invalid channel ID");
}
- created_public_channels_.push_back(channel_id);
- break;
- }
- case telegram_api::channelForbidden::ID: {
- auto c = static_cast<const telegram_api::channelForbidden *>(chat.get());
- ChannelId channel_id(c->id_);
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
- continue;
+ return channel_id;
+ });
+ if (std::any_of(r_channel_ids.begin(), r_channel_ids.end(),
+ [](auto &r_channel_id) { return r_channel_id.is_error(); })) {
+ LOG(ERROR) << "Can't parse " << str;
+ G()->td_db()->get_binlog_pmc()->erase(pmc_key);
+ } else {
+ Dependencies dependencies;
+ vector<ChannelId> channel_ids;
+ for (auto &r_channel_id : r_channel_ids) {
+ auto channel_id = r_channel_id.move_as_ok();
+ dependencies.add_dialog_and_dependencies(DialogId(channel_id));
+ channel_ids.push_back(channel_id);
+ }
+ if (!dependencies.resolve_force(td_, "get_created_public_dialogs")) {
+ G()->td_db()->get_binlog_pmc()->erase(pmc_key);
+ } else {
+ created_public_channels_[index] = std::move(channel_ids);
+ created_public_channels_inited_[index] = true;
+
+ if (type == PublicDialogType::HasUsername) {
+ update_created_public_broadcasts();
+ }
+
+ if (from_binlog) {
+ return return_created_public_dialogs(std::move(promise), created_public_channels_[index]);
+ }
}
- created_public_channels_.push_back(channel_id);
- break;
}
- default:
- UNREACHABLE();
}
- on_get_chat(std::move(chat));
}
+
+ reload_created_public_dialogs(type, std::move(promise));
+}
+
+void ContactsManager::reload_created_public_dialogs(PublicDialogType type,
+ Promise<td_api::object_ptr<td_api::chats>> &&promise) {
+ auto index = static_cast<int32>(type);
+ get_created_public_channels_queries_[index].push_back(std::move(promise));
+ if (get_created_public_channels_queries_[index].size() == 1) {
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), type](Result<Unit> &&result) {
+ send_closure(actor_id, &ContactsManager::finish_get_created_public_dialogs, type, std::move(result));
+ });
+ td_->create_handler<GetCreatedPublicChannelsQuery>(std::move(query_promise))->send(type, false);
+ }
+}
+
+void ContactsManager::finish_get_created_public_dialogs(PublicDialogType type, Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
+
+ auto index = static_cast<int32>(type);
+ auto promises = std::move(get_created_public_channels_queries_[index]);
+ reset_to_empty(get_created_public_channels_queries_[index]);
+ if (result.is_error()) {
+ fail_promises(promises, result.move_as_error());
+ return;
+ }
+
+ CHECK(created_public_channels_inited_[index]);
+ for (auto &promise : promises) {
+ return_created_public_dialogs(std::move(promise), created_public_channels_[index]);
+ }
+}
+
+void ContactsManager::update_created_public_channels(Channel *c, ChannelId channel_id) {
+ if (created_public_channels_inited_[0]) {
+ bool was_changed = false;
+ if (!c->usernames.has_editable_username() || !c->status.is_creator()) {
+ was_changed = td::remove(created_public_channels_[0], channel_id);
+ } else {
+ if (!td::contains(created_public_channels_[0], channel_id)) {
+ created_public_channels_[0].push_back(channel_id);
+ was_changed = true;
+ }
+ }
+ if (was_changed) {
+ if (!c->is_megagroup) {
+ update_created_public_broadcasts();
+ }
+
+ save_created_public_channels(PublicDialogType::HasUsername);
+
+ reload_created_public_dialogs(PublicDialogType::HasUsername, Promise<td_api::object_ptr<td_api::chats>>());
+ }
+ }
+ if (created_public_channels_inited_[1]) {
+ bool was_changed = false;
+ if (!c->has_location || !c->status.is_creator()) {
+ was_changed = td::remove(created_public_channels_[1], channel_id);
+ } else {
+ if (!td::contains(created_public_channels_[1], channel_id)) {
+ created_public_channels_[1].push_back(channel_id);
+ was_changed = true;
+ }
+ }
+ if (was_changed) {
+ save_created_public_channels(PublicDialogType::IsLocationBased);
+
+ reload_created_public_dialogs(PublicDialogType::IsLocationBased, Promise<td_api::object_ptr<td_api::chats>>());
+ }
+ }
+}
+
+void ContactsManager::on_get_created_public_channels(PublicDialogType type,
+ vector<tl_object_ptr<telegram_api::Chat>> &&chats) {
+ auto index = static_cast<int32>(type);
+ auto channel_ids = get_channel_ids(std::move(chats), "on_get_created_public_channels");
+ if (created_public_channels_inited_[index] && created_public_channels_[index] == channel_ids) {
+ return;
+ }
+ for (auto channel_id : channel_ids) {
+ td_->messages_manager_->force_create_dialog(DialogId(channel_id), "on_get_created_public_channels");
+ }
+ created_public_channels_[index] = std::move(channel_ids);
+ created_public_channels_inited_[index] = true;
+
+ if (type == PublicDialogType::HasUsername) {
+ update_created_public_broadcasts();
+ }
+
+ save_created_public_channels(type);
+}
+
+void ContactsManager::save_created_public_channels(PublicDialogType type) {
+ auto index = static_cast<int32>(type);
+ CHECK(created_public_channels_inited_[index]);
+ if (G()->parameters().use_chat_info_db) {
+ G()->td_db()->get_binlog_pmc()->set(
+ PSTRING() << "public_channels" << index,
+ implode(
+ transform(created_public_channels_[index], [](auto channel_id) { return PSTRING() << channel_id.get(); }),
+ ','));
+ }
+}
+
+void ContactsManager::update_created_public_broadcasts() {
+ CHECK(created_public_channels_inited_[0]);
+ vector<ChannelId> channel_ids;
+ for (auto &channel_id : created_public_channels_[0]) {
+ auto c = get_channel(channel_id);
+ if (!c->is_megagroup) {
+ channel_ids.push_back(channel_id);
+ }
+ }
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_update_created_public_broadcasts,
+ std::move(channel_ids));
+}
+
+void ContactsManager::check_created_public_dialogs_limit(PublicDialogType type, Promise<Unit> &&promise) {
+ td_->create_handler<GetCreatedPublicChannelsQuery>(std::move(promise))->send(type, true);
+}
+
+vector<DialogId> ContactsManager::get_dialogs_for_discussion(Promise<Unit> &&promise) {
+ if (dialogs_for_discussion_inited_) {
+ promise.set_value(Unit());
+ return transform(dialogs_for_discussion_, [&](DialogId dialog_id) {
+ td_->messages_manager_->force_create_dialog(dialog_id, "get_dialogs_for_discussion");
+ return dialog_id;
+ });
+ }
+
+ td_->create_handler<GetGroupsForDiscussionQuery>(std::move(promise))->send();
+ return {};
+}
+
+void ContactsManager::on_get_dialogs_for_discussion(vector<tl_object_ptr<telegram_api::Chat>> &&chats) {
+ dialogs_for_discussion_inited_ = true;
+ dialogs_for_discussion_ = get_dialog_ids(std::move(chats), "on_get_dialogs_for_discussion");
+}
+
+void ContactsManager::update_dialogs_for_discussion(DialogId dialog_id, bool is_suitable) {
+ if (!dialogs_for_discussion_inited_) {
+ return;
+ }
+
+ if (is_suitable) {
+ if (!td::contains(dialogs_for_discussion_, dialog_id)) {
+ LOG(DEBUG) << "Add " << dialog_id << " to list of suitable discussion chats";
+ dialogs_for_discussion_.insert(dialogs_for_discussion_.begin(), dialog_id);
+ }
+ } else {
+ if (td::remove(dialogs_for_discussion_, dialog_id)) {
+ LOG(DEBUG) << "Remove " << dialog_id << " from list of suitable discussion chats";
+ }
+ }
+}
+
+vector<DialogId> ContactsManager::get_inactive_channels(Promise<Unit> &&promise) {
+ if (inactive_channel_ids_inited_) {
+ promise.set_value(Unit());
+ return transform(inactive_channel_ids_, [&](ChannelId channel_id) { return DialogId(channel_id); });
+ }
+
+ td_->create_handler<GetInactiveChannelsQuery>(std::move(promise))->send();
+ return {};
+}
+
+void ContactsManager::on_get_inactive_channels(vector<tl_object_ptr<telegram_api::Chat>> &&chats,
+ Promise<Unit> &&promise) {
+ auto channel_ids = get_channel_ids(std::move(chats), "on_get_inactive_channels");
+
+ MultiPromiseActorSafe mpas{"GetInactiveChannelsMultiPromiseActor"};
+ mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), channel_ids,
+ promise = std::move(promise)](Unit) mutable {
+ send_closure(actor_id, &ContactsManager::on_create_inactive_channels, std::move(channel_ids), std::move(promise));
+ }));
+ mpas.set_ignore_errors(true);
+ auto lock_promise = mpas.get_promise();
+
+ for (auto channel_id : channel_ids) {
+ td_->messages_manager_->create_dialog(DialogId(channel_id), false, mpas.get_promise());
+ }
+
+ lock_promise.set_value(Unit());
}
-void ContactsManager::on_imported_contacts(int64 random_id, vector<UserId> imported_contact_user_ids,
- vector<int32> unimported_contact_invites) {
+void ContactsManager::on_create_inactive_channels(vector<ChannelId> &&channel_ids, Promise<Unit> &&promise) {
+ inactive_channel_ids_inited_ = true;
+ inactive_channel_ids_ = std::move(channel_ids);
+ promise.set_value(Unit());
+}
+
+void ContactsManager::remove_inactive_channel(ChannelId channel_id) {
+ if (inactive_channel_ids_inited_ && td::remove(inactive_channel_ids_, channel_id)) {
+ LOG(DEBUG) << "Remove " << channel_id << " from list of inactive channels";
+ }
+}
+
+void ContactsManager::remove_dialog_suggested_action(SuggestedAction action) {
+ auto it = dialog_suggested_actions_.find(action.dialog_id_);
+ if (it == dialog_suggested_actions_.end()) {
+ return;
+ }
+ remove_suggested_action(it->second, action);
+ if (it->second.empty()) {
+ dialog_suggested_actions_.erase(it);
+ }
+}
+
+void ContactsManager::dismiss_dialog_suggested_action(SuggestedAction action, Promise<Unit> &&promise) {
+ auto dialog_id = action.dialog_id_;
+ if (!td_->messages_manager_->have_dialog(dialog_id)) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ auto it = dialog_suggested_actions_.find(dialog_id);
+ if (it == dialog_suggested_actions_.end() || !td::contains(it->second, action)) {
+ return promise.set_value(Unit());
+ }
+
+ auto action_str = action.get_suggested_action_str();
+ if (action_str.empty()) {
+ return promise.set_value(Unit());
+ }
+
+ auto &queries = dismiss_suggested_action_queries_[dialog_id];
+ queries.push_back(std::move(promise));
+ if (queries.size() == 1) {
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), action](Result<Unit> &&result) {
+ send_closure(actor_id, &ContactsManager::on_dismiss_suggested_action, action, std::move(result));
+ });
+ td_->create_handler<DismissSuggestionQuery>(std::move(query_promise))->send(std::move(action));
+ }
+}
+
+void ContactsManager::on_dismiss_suggested_action(SuggestedAction action, Result<Unit> &&result) {
+ auto it = dismiss_suggested_action_queries_.find(action.dialog_id_);
+ CHECK(it != dismiss_suggested_action_queries_.end());
+ auto promises = std::move(it->second);
+ dismiss_suggested_action_queries_.erase(it);
+
+ if (result.is_error()) {
+ fail_promises(promises, result.move_as_error());
+ return;
+ }
+
+ remove_dialog_suggested_action(action);
+
+ set_promises(promises);
+}
+
+void ContactsManager::on_import_contacts_finished(int64 random_id, vector<UserId> imported_contact_user_ids,
+ vector<int32> unimported_contact_invites) {
LOG(INFO) << "Contacts import with random_id " << random_id
<< " has finished: " << format::as_array(imported_contact_user_ids);
- if (random_id == 0) {
+ if (random_id == 1) {
// import from change_imported_contacts
all_imported_contacts_ = std::move(next_all_imported_contacts_);
next_all_imported_contacts_.clear();
@@ -4642,12 +8924,12 @@ void ContactsManager::on_imported_contacts(int64 random_id, vector<UserId> impor
CHECK(unimported_contact_invites.size() == add_size);
CHECK(imported_contacts_unique_id_.size() == result_size);
- std::unordered_map<size_t, int32> unique_id_to_unimported_contact_invites;
+ std::unordered_map<int64, int32, Hash<int64>> unique_id_to_unimported_contact_invites;
for (size_t i = 0; i < add_size; i++) {
auto unique_id = imported_contacts_pos_[i];
- get_user_id_object(imported_contact_user_ids[i], "on_imported_contacts"); // to ensure updateUser
+ get_user_id_object(imported_contact_user_ids[i], "on_import_contacts_finished"); // to ensure updateUser
all_imported_contacts_[unique_id].set_user_id(imported_contact_user_ids[i]);
- unique_id_to_unimported_contact_invites[unique_id] = unimported_contact_invites[i];
+ unique_id_to_unimported_contact_invites[narrow_cast<int64>(unique_id)] = unimported_contact_invites[i];
}
if (G()->parameters().use_chat_info_db) {
@@ -4664,7 +8946,7 @@ void ContactsManager::on_imported_contacts(int64 random_id, vector<UserId> impor
auto unique_id = imported_contacts_unique_id_[i];
CHECK(unique_id < unique_size);
imported_contact_user_ids_[i] = all_imported_contacts_[unique_id].get_user_id();
- auto it = unique_id_to_unimported_contact_invites.find(unique_id);
+ auto it = unique_id_to_unimported_contact_invites.find(narrow_cast<int64>(unique_id));
if (it == unique_id_to_unimported_contact_invites.end()) {
unimported_contact_invites_[i] = 0;
} else {
@@ -4682,20 +8964,30 @@ void ContactsManager::on_imported_contacts(int64 random_id, vector<UserId> impor
}
void ContactsManager::on_deleted_contacts(const vector<UserId> &deleted_contact_user_ids) {
- LOG(INFO) << "Contacts deletion has finished";
+ LOG(INFO) << "Contacts deletion has finished for " << deleted_contact_user_ids;
for (auto user_id : deleted_contact_user_ids) {
- LOG(INFO) << "Drop contact with " << user_id;
auto u = get_user(user_id);
CHECK(u != nullptr);
- on_update_user_links(u, user_id, LinkState::KnowsPhoneNumber, u->inbound);
+ if (!u->is_contact) {
+ continue;
+ }
+
+ LOG(INFO) << "Drop contact with " << user_id;
+ on_update_user_is_contact(u, user_id, false, false);
+ CHECK(u->is_is_contact_changed);
+ u->cache_version = 0;
+ u->is_repaired = false;
update_user(u, user_id);
- CHECK(u->outbound != LinkState::Contact);
+ CHECK(!u->is_contact);
CHECK(!contacts_hints_.has_key(user_id.get()));
}
}
void ContactsManager::save_next_contacts_sync_date() {
+ if (G()->close_flag()) {
+ return;
+ }
if (!G()->parameters().use_chat_info_db) {
return;
}
@@ -4716,7 +9008,7 @@ void ContactsManager::on_get_contacts(tl_object_ptr<telegram_api::contacts_Conta
}
auto contacts = move_tl_object_as<telegram_api::contacts_contacts>(new_contacts);
- std::unordered_set<UserId, UserIdHash> contact_user_ids;
+ FlatHashSet<UserId, UserIdHash> contact_user_ids;
for (auto &user : contacts->users_) {
auto user_id = get_user_id(user);
if (!user_id.is_valid()) {
@@ -4725,23 +9017,25 @@ void ContactsManager::on_get_contacts(tl_object_ptr<telegram_api::contacts_Conta
}
contact_user_ids.insert(user_id);
}
- on_get_users(std::move(contacts->users_));
+ on_get_users(std::move(contacts->users_), "on_get_contacts");
- UserId my_id = get_my_id("on_get_contacts");
- for (auto &p : users_) {
- UserId user_id = p.first;
- User &u = p.second;
- bool is_contact = u.outbound == LinkState::Contact;
+ UserId my_id = get_my_id();
+ users_.foreach([&](const UserId &user_id, unique_ptr<User> &user) {
+ User *u = user.get();
bool should_be_contact = contact_user_ids.count(user_id) == 1;
- if (is_contact != should_be_contact) {
- if (is_contact) {
+ if (u->is_contact != should_be_contact) {
+ if (u->is_contact) {
LOG(INFO) << "Drop contact with " << user_id;
if (user_id != my_id) {
- CHECK(contacts_hints_.has_key(user_id.get()));
+ LOG_CHECK(contacts_hints_.has_key(user_id.get()))
+ << my_id << " " << user_id << " " << to_string(get_user_object(user_id, u));
}
- on_update_user_links(&u, user_id, LinkState::KnowsPhoneNumber, u.inbound);
- update_user(&u, user_id);
- CHECK(u.outbound != LinkState::Contact);
+ on_update_user_is_contact(u, user_id, false, false);
+ CHECK(u->is_is_contact_changed);
+ u->cache_version = 0;
+ u->is_repaired = false;
+ update_user(u, user_id);
+ CHECK(!u->is_contact);
if (user_id != my_id) {
CHECK(!contacts_hints_.has_key(user_id.get()));
}
@@ -4749,7 +9043,7 @@ void ContactsManager::on_get_contacts(tl_object_ptr<telegram_api::contacts_Conta
LOG(ERROR) << "Receive non-contact " << user_id << " in the list of contacts";
}
}
- }
+ });
saved_contact_count_ = contacts->saved_count_;
on_get_contacts_finished(std::numeric_limits<size_t>::max());
@@ -4762,12 +9056,12 @@ void ContactsManager::save_contacts_to_database() {
LOG(INFO) << "Schedule save contacts to database";
vector<UserId> user_ids =
- transform(contacts_hints_.search_empty(100000).second, [](int64 key) { return UserId(narrow_cast<int32>(key)); });
+ transform(contacts_hints_.search_empty(100000).second, [](int64 key) { return UserId(key); });
G()->td_db()->get_binlog_pmc()->set("saved_contact_count", to_string(saved_contact_count_));
G()->td_db()->get_binlog()->force_sync(PromiseCreator::lambda([user_ids = std::move(user_ids)](Result<> result) {
if (result.is_ok()) {
- LOG(INFO) << "Save contacts to database";
+ LOG(INFO) << "Saved contacts to database";
G()->td_db()->get_sqlite_pmc()->set(
"user_contacts", log_event_store(user_ids).as_slice().str(), PromiseCreator::lambda([](Result<> result) {
if (result.is_ok()) {
@@ -4781,14 +9075,13 @@ void ContactsManager::save_contacts_to_database() {
void ContactsManager::on_get_contacts_failed(Status error) {
CHECK(error.is_error());
next_contacts_sync_date_ = G()->unix_time() + Random::fast(5, 10);
- auto promises = std::move(load_contacts_queries_);
- load_contacts_queries_.clear();
- for (auto &promise : promises) {
- promise.set_error(error.clone());
- }
+ fail_promises(load_contacts_queries_, std::move(error));
}
void ContactsManager::on_load_contacts_from_database(string value) {
+ if (G()->close_flag()) {
+ return;
+ }
if (value.empty()) {
reload_contacts(true);
return;
@@ -4799,10 +9092,10 @@ void ContactsManager::on_load_contacts_from_database(string value) {
LOG(INFO) << "Successfully loaded " << user_ids.size() << " contacts from database";
- load_contact_users_multipromise_.add_promise(
- PromiseCreator::lambda([expected_contact_count = user_ids.size()](Result<> result) {
+ load_contact_users_multipromise_.add_promise(PromiseCreator::lambda(
+ [actor_id = actor_id(this), expected_contact_count = user_ids.size()](Result<Unit> result) {
if (result.is_ok()) {
- send_closure(G()->contacts_manager(), &ContactsManager::on_get_contacts_finished, expected_contact_count);
+ send_closure(actor_id, &ContactsManager::on_get_contacts_finished, expected_contact_count);
}
}));
@@ -4816,21 +9109,21 @@ void ContactsManager::on_load_contacts_from_database(string value) {
}
void ContactsManager::on_get_contacts_finished(size_t expected_contact_count) {
- LOG(INFO) << "Finished to get " << contacts_hints_.size() << " contacts out of " << expected_contact_count;
+ LOG(INFO) << "Finished to get " << contacts_hints_.size() << " contacts out of expected " << expected_contact_count;
are_contacts_loaded_ = true;
- auto promises = std::move(load_contacts_queries_);
- load_contacts_queries_.clear();
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(load_contacts_queries_);
if (expected_contact_count != contacts_hints_.size()) {
save_contacts_to_database();
}
}
void ContactsManager::on_get_contacts_statuses(vector<tl_object_ptr<telegram_api::contactStatus>> &&statuses) {
+ auto my_user_id = get_my_id();
for (auto &status : statuses) {
- on_update_user_online(UserId(status->user_id_), std::move(status->status_));
+ UserId user_id(status->user_id_);
+ if (user_id != my_user_id) {
+ on_update_user_online(user_id, std::move(status->status_));
+ }
}
save_next_contacts_sync_date();
}
@@ -4839,10 +9132,24 @@ void ContactsManager::on_update_online_status_privacy() {
td_->create_handler<GetContactsStatusesQuery>()->send();
}
-void ContactsManager::on_get_contacts_link(tl_object_ptr<telegram_api::contacts_link> &&link) {
- UserId user_id = get_user_id(link->user_);
- on_get_user(std::move(link->user_));
- on_update_user_links(user_id, std::move(link->my_link_), std::move(link->foreign_link_));
+void ContactsManager::on_update_phone_number_privacy() {
+ // all UserFull.need_phone_number_privacy_exception can be outdated now,
+ // so mark all of them as expired
+ users_full_.foreach([&](const UserId &user_id, unique_ptr<UserFull> &user_full) { user_full->expires_at = 0.0; });
+}
+
+void ContactsManager::invalidate_user_full(UserId user_id) {
+ auto user_full = get_user_full_force(user_id);
+ if (user_full != nullptr) {
+ td_->messages_manager_->on_dialog_info_full_invalidated(DialogId(user_id));
+
+ if (!user_full->is_expired()) {
+ user_full->expires_at = 0.0;
+ user_full->need_save_to_database = true;
+
+ update_user_full(user_full, user_id, "invalidate_user_full");
+ }
+ }
}
UserId ContactsManager::get_user_id(const tl_object_ptr<telegram_api::User> &user) {
@@ -4884,21 +9191,30 @@ ChannelId ContactsManager::get_channel_id(const tl_object_ptr<telegram_api::Chat
}
}
-void ContactsManager::on_get_user(tl_object_ptr<telegram_api::User> &&user_ptr, bool is_me, bool is_support) {
- LOG(DEBUG) << "Receive " << to_string(user_ptr);
+DialogId ContactsManager::get_dialog_id(const tl_object_ptr<telegram_api::Chat> &chat) {
+ auto channel_id = get_channel_id(chat);
+ if (channel_id.is_valid()) {
+ return DialogId(channel_id);
+ }
+ return DialogId(get_chat_id(chat));
+}
+
+void ContactsManager::on_get_user(tl_object_ptr<telegram_api::User> &&user_ptr, const char *source, bool is_me) {
+ LOG(DEBUG) << "Receive from " << source << ' ' << to_string(user_ptr);
int32 constructor_id = user_ptr->get_id();
if (constructor_id == telegram_api::userEmpty::ID) {
auto user = move_tl_object_as<telegram_api::userEmpty>(user_ptr);
UserId user_id(user->id_);
if (!user_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << user_id;
+ LOG(ERROR) << "Receive invalid " << user_id << " from " << source;
return;
}
- LOG(INFO) << "Receive empty " << user_id;
+ LOG(INFO) << "Receive empty " << user_id << " from " << source;
User *u = get_user_force(user_id);
- if (u == nullptr) {
- LOG(ERROR) << "Have no information about " << user_id << ", but received userEmpty";
+ if (u == nullptr && Slice(source) != Slice("GetUsersQuery")) {
+ // userEmpty should be received only through getUsers for nonexistent users
+ LOG(ERROR) << "Have no information about " << user_id << ", but received userEmpty from " << source;
}
return;
}
@@ -4911,173 +9227,213 @@ void ContactsManager::on_get_user(tl_object_ptr<telegram_api::User> &&user_ptr,
return;
}
int32 flags = user->flags_;
- LOG(INFO) << "Receive " << user_id << " with flags " << flags;
+ LOG(INFO) << "Receive " << user_id << " with flags " << flags << " from " << source;
if (is_me && (flags & USER_FLAG_IS_ME) == 0) {
- LOG(ERROR) << user_id << " doesn't have flag IS_ME, but must have it";
+ LOG(ERROR) << user_id << " doesn't have flag IS_ME, but must have it when received from " << source;
flags |= USER_FLAG_IS_ME;
}
bool is_bot = (flags & USER_FLAG_IS_BOT) != 0;
if (flags & USER_FLAG_IS_ME) {
set_my_id(user_id);
- td_->auth_manager_->set_is_bot(is_bot);
- } else {
- /*
- if (!(flags & USER_FLAG_HAS_ACCESS_HASH) && !(flags & USER_FLAG_IS_DELETED) &&
- !(flags & USER_FLAG_IS_INACCESSIBLE)) {
- LOG(ERROR) << user_id << " has no access_hash";
- return;
+ if (!is_bot) {
+ td_->option_manager_->set_option_string("my_phone_number", user->phone_);
}
- */
- }
-
- if (is_support) {
- support_user_id_ = user_id;
}
bool have_access_hash = (flags & USER_FLAG_HAS_ACCESS_HASH) != 0;
bool is_received = (flags & USER_FLAG_IS_INACCESSIBLE) == 0;
+ bool is_contact = (flags & USER_FLAG_IS_CONTACT) != 0;
- User *u = add_user(user_id);
- if ((have_access_hash || u->access_hash == -1) && u->access_hash != user->access_hash_) {
- LOG(DEBUG) << "Access hash has changed for " << user_id << " from " << u->access_hash << " to "
- << user->access_hash_;
- u->access_hash = user->access_hash_;
- u->is_changed = true;
+ if (!have_min_user(user_id)) {
+ if (!is_received) {
+ // we must preload received inaccessible users from database in order to not save
+ // the min-user to the database and to not override access_hash and another info
+ if (!have_user_force(user_id)) {
+ LOG(INFO) << "Receive inaccessible " << user_id;
+ }
+ } else if (is_contact && !are_contacts_loaded_) {
+ // preload contact users from database to know that is_contact didn't changed
+ // and the list of contacts doesn't need to be saved to the database
+ if (!have_user_force(user_id)) {
+ LOG(INFO) << "Receive contact " << user_id << " for the first time";
+ }
+ }
}
- if (is_received) {
+
+ User *u = add_user(user_id, "on_get_user");
+ if (have_access_hash) { // access_hash must be updated before photo
+ auto access_hash = user->access_hash_;
+ bool is_min_access_hash = !is_received && !((flags & USER_FLAG_HAS_PHONE_NUMBER) != 0 && user->phone_.empty());
+ if (u->access_hash != access_hash && (!is_min_access_hash || u->is_min_access_hash || u->access_hash == -1)) {
+ LOG(DEBUG) << "Access hash has changed for " << user_id << " from " << u->access_hash << "/"
+ << u->is_min_access_hash << " to " << access_hash << "/" << is_min_access_hash;
+ u->access_hash = access_hash;
+ u->is_min_access_hash = is_min_access_hash;
+ u->need_save_to_database = true;
+ }
+ }
+ if (is_received || !user->phone_.empty()) {
on_update_user_phone_number(u, user_id, std::move(user->phone_));
}
- on_update_user_photo(u, user_id, std::move(user->photo_));
+ if (is_received || u->need_apply_min_photo || !u->is_received) {
+ on_update_user_photo(u, user_id, std::move(user->photo_), source);
+ }
if (is_received) {
on_update_user_online(u, user_id, std::move(user->status_));
- }
- LinkState out, in;
- if (flags & USER_FLAG_IS_MUTUAL_CONTACT) {
- out = LinkState::Contact;
- in = LinkState::Contact;
- } else if (flags & USER_FLAG_IS_CONTACT) {
- out = LinkState::Contact;
- in = LinkState::Unknown;
- } else if (flags & USER_FLAG_HAS_PHONE_NUMBER) {
- out = LinkState::KnowsPhoneNumber;
- in = LinkState::Unknown;
- } else {
- out = LinkState::None;
- in = LinkState::Unknown;
+ auto is_mutual_contact = (flags & USER_FLAG_IS_MUTUAL_CONTACT) != 0;
+ on_update_user_is_contact(u, user_id, is_contact, is_mutual_contact);
}
- on_update_user_links(u, user_id, out, in);
if (is_received || !u->is_received) {
- on_update_user_name(u, user_id, std::move(user->first_name_), std::move(user->last_name_),
- std::move(user->username_));
+ on_update_user_name(u, user_id, std::move(user->first_name_), std::move(user->last_name_));
+ on_update_user_usernames(u, user_id, Usernames{std::move(user->username_), std::move(user->usernames_)});
}
+ on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(user->emoji_status_)));
bool is_verified = (flags & USER_FLAG_IS_VERIFIED) != 0;
+ bool is_premium = (flags & USER_FLAG_IS_PREMIUM) != 0;
+ bool is_support = (flags & USER_FLAG_IS_SUPPORT) != 0;
bool is_deleted = (flags & USER_FLAG_IS_DELETED) != 0;
bool can_join_groups = (flags & USER_FLAG_IS_PRIVATE_BOT) == 0;
bool can_read_all_group_messages = (flags & USER_FLAG_IS_BOT_WITH_PRIVACY_DISABLED) != 0;
- string restriction_reason = std::move(user->restriction_reason_);
+ bool can_be_added_to_attach_menu = (flags & USER_FLAG_IS_ATTACH_MENU_BOT) != 0;
+ bool attach_menu_enabled = (flags & USER_FLAG_ATTACH_MENU_ENABLED) != 0;
+ auto restriction_reasons = get_restriction_reasons(std::move(user->restriction_reason_));
+ bool is_scam = (flags & USER_FLAG_IS_SCAM) != 0;
bool is_inline_bot = (flags & USER_FLAG_IS_INLINE_BOT) != 0;
string inline_query_placeholder = user->bot_inline_placeholder_;
bool need_location_bot = (flags & USER_FLAG_NEED_LOCATION_BOT) != 0;
bool has_bot_info_version = (flags & USER_FLAG_HAS_BOT_INFO_VERSION) != 0;
-
- LOG_IF(ERROR, !can_join_groups && !is_bot) << "Receive not bot which can't join groups";
- LOG_IF(ERROR, can_read_all_group_messages && !is_bot) << "Receive not bot which can read all group messages";
- LOG_IF(ERROR, is_inline_bot && !is_bot) << "Receive not bot which is inline bot";
- LOG_IF(ERROR, need_location_bot && !is_inline_bot) << "Receive not inline bot which needs user location";
-
- if (is_received && !u->is_received) {
- u->is_received = true;
-
- LOG(DEBUG) << "Receive " << user_id;
- u->need_send_update = true;
- }
+ bool need_apply_min_photo = (flags & USER_FLAG_NEED_APPLY_MIN_PHOTO) != 0;
+ bool is_fake = (flags & USER_FLAG_IS_FAKE) != 0;
+
+ LOG_IF(ERROR, !can_join_groups && !is_bot)
+ << "Receive not bot " << user_id << " which can't join groups from " << source;
+ LOG_IF(ERROR, can_read_all_group_messages && !is_bot)
+ << "Receive not bot " << user_id << " which can read all group messages from " << source;
+ LOG_IF(ERROR, can_be_added_to_attach_menu && !is_bot)
+ << "Receive not bot " << user_id << " which can be added to attachment menu from " << source;
+ LOG_IF(ERROR, is_inline_bot && !is_bot) << "Receive not bot " << user_id << " which is inline bot from " << source;
+ LOG_IF(ERROR, need_location_bot && !is_inline_bot)
+ << "Receive not inline bot " << user_id << " which needs user location from " << source;
if (is_deleted) {
// just in case
is_verified = false;
+ is_premium = false;
+ is_support = false;
is_bot = false;
can_join_groups = false;
can_read_all_group_messages = false;
+ can_be_added_to_attach_menu = false;
is_inline_bot = false;
inline_query_placeholder = string();
need_location_bot = false;
has_bot_info_version = false;
+ need_apply_min_photo = false;
}
- LOG_IF(ERROR, has_bot_info_version && !is_bot) << "Receive not bot which has bot info version";
+ LOG_IF(ERROR, has_bot_info_version && !is_bot)
+ << "Receive not bot " << user_id << " which has bot info version from " << source;
int32 bot_info_version = has_bot_info_version ? user->bot_info_version_ : -1;
- if (is_verified != u->is_verified || is_bot != u->is_bot || can_join_groups != u->can_join_groups ||
- can_read_all_group_messages != u->can_read_all_group_messages || restriction_reason != u->restriction_reason ||
+ if (is_verified != u->is_verified || is_support != u->is_support || is_bot != u->is_bot ||
+ can_join_groups != u->can_join_groups || can_read_all_group_messages != u->can_read_all_group_messages ||
+ restriction_reasons != u->restriction_reasons || is_scam != u->is_scam || is_fake != u->is_fake ||
is_inline_bot != u->is_inline_bot || inline_query_placeholder != u->inline_query_placeholder ||
- need_location_bot != u->need_location_bot) {
- LOG_IF(ERROR, is_bot != u->is_bot && !is_deleted && !u->is_deleted) << "User.is_bot has changed";
+ need_location_bot != u->need_location_bot || can_be_added_to_attach_menu != u->can_be_added_to_attach_menu ||
+ attach_menu_enabled != u->attach_menu_enabled) {
+ LOG_IF(ERROR, is_bot != u->is_bot && !is_deleted && !u->is_deleted && u->is_received)
+ << "User.is_bot has changed for " << user_id << "/" << u->usernames << " from " << source << " from "
+ << u->is_bot << " to " << is_bot;
u->is_verified = is_verified;
+ u->is_support = is_support;
u->is_bot = is_bot;
u->can_join_groups = can_join_groups;
u->can_read_all_group_messages = can_read_all_group_messages;
- u->restriction_reason = std::move(restriction_reason);
+ u->restriction_reasons = std::move(restriction_reasons);
+ u->is_scam = is_scam;
+ u->is_fake = is_fake;
u->is_inline_bot = is_inline_bot;
u->inline_query_placeholder = std::move(inline_query_placeholder);
u->need_location_bot = need_location_bot;
+ u->can_be_added_to_attach_menu = can_be_added_to_attach_menu;
+ u->attach_menu_enabled = attach_menu_enabled;
LOG(DEBUG) << "Info has changed for " << user_id;
- u->need_send_update = true;
+ u->is_changed = true;
+ }
+ if (is_premium != u->is_premium) {
+ u->is_premium = is_premium;
+ u->is_changed = true;
}
if (u->bot_info_version != bot_info_version) {
u->bot_info_version = bot_info_version;
LOG(DEBUG) << "Bot info version has changed for " << user_id;
+ u->need_save_to_database = true;
+ }
+ if (is_received && u->need_apply_min_photo != need_apply_min_photo) {
+ u->need_apply_min_photo = need_apply_min_photo;
+ u->need_save_to_database = true;
+ }
+
+ if (is_received && !u->is_received) {
+ u->is_received = true;
+
+ LOG(DEBUG) << "Receive " << user_id;
u->is_changed = true;
}
if (is_deleted != u->is_deleted) {
u->is_deleted = is_deleted;
- if (u->is_deleted) {
- invalidate_user_full(user_id);
- }
-
- LOG(DEBUG) << "is_deleted has changed for " << user_id;
- u->need_send_update = true;
+ LOG(DEBUG) << "User.is_deleted has changed for " << user_id << " to " << u->is_deleted;
+ u->is_is_deleted_changed = true;
+ u->is_changed = true;
}
bool has_language_code = (flags & USER_FLAG_HAS_LANGUAGE_CODE) != 0;
- LOG_IF(ERROR, has_language_code && !td_->auth_manager_->is_bot()) << "Receive language code";
+ LOG_IF(ERROR, has_language_code && !td_->auth_manager_->is_bot())
+ << "Receive language code for " << user_id << " from " << source;
if (u->language_code != user->lang_code_ && !user->lang_code_.empty()) {
u->language_code = user->lang_code_;
- LOG(DEBUG) << "Language code has changed for " << user_id;
- u->need_send_update = true;
+ LOG(DEBUG) << "Language code has changed for " << user_id << " to " << u->language_code;
+ u->is_changed = true;
}
+ if (u->cache_version != User::CACHE_VERSION && u->is_received) {
+ u->cache_version = User::CACHE_VERSION;
+ u->need_save_to_database = true;
+ }
+ u->is_received_from_server = true;
update_user(u, user_id);
}
class ContactsManager::UserLogEvent {
public:
UserId user_id;
- User u;
+ const User *u_in = nullptr;
+ unique_ptr<User> u_out;
UserLogEvent() = default;
- UserLogEvent(UserId user_id, const User &u) : user_id(user_id), u(u) {
+ UserLogEvent(UserId user_id, const User *u) : user_id(user_id), u_in(u) {
}
template <class StorerT>
void store(StorerT &storer) const {
td::store(user_id, storer);
- td::store(u, storer);
+ td::store(*u_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(user_id, parser);
- td::parse(u, parser);
+ td::parse(u_out, parser);
}
};
@@ -5088,12 +9444,12 @@ void ContactsManager::save_user(User *u, UserId user_id, bool from_binlog) {
CHECK(u != nullptr);
if (!u->is_saved || !u->is_status_saved) { // TODO more effective handling of !u->is_status_saved
if (!from_binlog) {
- auto logevent = UserLogEvent(user_id, *u);
- auto storer = LogEventStorerImpl<UserLogEvent>(logevent);
- if (u->logevent_id == 0) {
- u->logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Users, storer);
+ auto log_event = UserLogEvent(user_id, u);
+ auto storer = get_log_event_storer(log_event);
+ if (u->log_event_id == 0) {
+ u->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Users, storer);
} else {
- BinlogHelper::rewrite(G()->td_db()->get_binlog(), u->logevent_id, LogEvent::HandlerType::Users, storer);
+ binlog_rewrite(G()->td_db()->get_binlog(), u->log_event_id, LogEvent::HandlerType::Users, storer);
}
}
@@ -5103,7 +9459,7 @@ void ContactsManager::save_user(User *u, UserId user_id, bool from_binlog) {
void ContactsManager::on_binlog_user_event(BinlogEvent &&event) {
if (!G()->parameters().use_chat_info_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
return;
}
@@ -5111,18 +9467,24 @@ void ContactsManager::on_binlog_user_event(BinlogEvent &&event) {
log_event_parse(log_event, event.data_).ensure();
auto user_id = log_event.user_id;
+ if (have_min_user(user_id) || !user_id.is_valid()) {
+ LOG(ERROR) << "Skip adding already added " << user_id;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ return;
+ }
+
LOG(INFO) << "Add " << user_id << " from binlog";
- User *u = add_user(user_id);
- CHECK(u->first_name.empty());
- *u = std::move(log_event.u); // users come from binlog before all other events, so just add them
+ users_.set(user_id, std::move(log_event.u_out));
- u->logevent_id = event.id_;
+ User *u = get_user(user_id);
+ CHECK(u != nullptr);
+ u->log_event_id = event.id_;
update_user(u, user_id, true, false);
}
string ContactsManager::get_user_database_key(UserId user_id) {
- return "us" + to_string(user_id.get());
+ return PSTRING() << "us" << user_id.get();
}
string ContactsManager::get_user_database_value(const User *u) {
@@ -5160,9 +9522,18 @@ void ContactsManager::save_user_to_database_impl(User *u, UserId user_id, string
}
void ContactsManager::on_save_user_to_database(UserId user_id, bool success) {
+ if (G()->close_flag()) {
+ return;
+ }
+
User *u = get_user(user_id);
CHECK(u != nullptr);
- CHECK(u->is_being_saved);
+ LOG_CHECK(u->is_being_saved) << user_id << " " << u->is_saved << " " << u->is_status_saved << " "
+ << load_user_from_database_queries_.count(user_id) << " " << u->is_received << " "
+ << u->is_deleted << " " << u->is_bot << " " << u->need_save_to_database << " "
+ << u->is_changed << " " << u->is_status_changed << " " << u->is_name_changed << " "
+ << u->is_username_changed << " " << u->is_photo_changed << " "
+ << u->is_is_contact_changed << " " << u->is_is_deleted_changed;
CHECK(load_user_from_database_queries_.count(user_id) == 0);
u->is_being_saved = false;
@@ -5174,12 +9545,12 @@ void ContactsManager::on_save_user_to_database(UserId user_id, bool success) {
LOG(INFO) << "Successfully saved " << user_id << " to database";
}
if (u->is_saved && u->is_status_saved) {
- if (u->logevent_id != 0) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), u->logevent_id);
- u->logevent_id = 0;
+ if (u->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), u->log_event_id);
+ u->log_event_id = 0;
}
} else {
- save_user(u, user_id, u->logevent_id != 0);
+ save_user(u, user_id, u->log_event_id != 0);
}
}
@@ -5201,12 +9572,18 @@ void ContactsManager::load_user_from_database_impl(UserId user_id, Promise<Unit>
G()->td_db()->get_sqlite_pmc()->get(get_user_database_key(user_id), PromiseCreator::lambda([user_id](string value) {
send_closure(G()->contacts_manager(),
&ContactsManager::on_load_user_from_database, user_id,
- std::move(value));
+ std::move(value), false);
}));
}
}
-void ContactsManager::on_load_user_from_database(UserId user_id, string value) {
+void ContactsManager::on_load_user_from_database(UserId user_id, string value, bool force) {
+ if (G()->close_flag() && !force) {
+ // the user is in Binlog and will be saved after restart
+ return;
+ }
+
+ CHECK(user_id.is_valid());
if (!loaded_from_database_users_.insert(user_id).second) {
return;
}
@@ -5226,7 +9603,7 @@ void ContactsManager::on_load_user_from_database(UserId user_id, string value) {
User *u = get_user(user_id);
if (u == nullptr) {
if (!value.empty()) {
- u = add_user(user_id);
+ u = add_user(user_id, "on_load_user_from_database");
log_event_parse(*u, value).ensure();
@@ -5240,15 +9617,13 @@ void ContactsManager::on_load_user_from_database(UserId user_id, string value) {
auto new_value = get_user_database_value(u);
if (value != new_value) {
save_user_to_database_impl(u, user_id, std::move(new_value));
- } else if (u->logevent_id != 0) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), u->logevent_id);
- u->logevent_id = 0;
+ } else if (u->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), u->log_event_id);
+ u->log_event_id = 0;
}
}
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(promises);
}
bool ContactsManager::have_user_force(UserId user_id) {
@@ -5256,6 +9631,77 @@ bool ContactsManager::have_user_force(UserId user_id) {
}
ContactsManager::User *ContactsManager::get_user_force(UserId user_id) {
+ auto u = get_user_force_impl(user_id);
+ if ((u == nullptr || !u->is_received) &&
+ (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() ||
+ user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id())) {
+ int32 flags = USER_FLAG_HAS_ACCESS_HASH | USER_FLAG_HAS_FIRST_NAME | USER_FLAG_NEED_APPLY_MIN_PHOTO;
+ int64 profile_photo_id = 0;
+ int32 profile_photo_dc_id = 1;
+ string first_name;
+ string last_name;
+ string username;
+ string phone_number;
+ int32 bot_info_version = 0;
+
+ if (user_id == get_service_notifications_user_id()) {
+ flags |= USER_FLAG_HAS_PHONE_NUMBER | USER_FLAG_IS_VERIFIED | USER_FLAG_IS_SUPPORT;
+ first_name = "Telegram";
+ if (G()->is_test_dc()) {
+ flags |= USER_FLAG_HAS_LAST_NAME;
+ last_name = "Notifications";
+ }
+ phone_number = "42777";
+ profile_photo_id = 3337190045231023;
+ } else if (user_id == get_replies_bot_user_id()) {
+ flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT;
+ if (!G()->is_test_dc()) {
+ flags |= USER_FLAG_IS_PRIVATE_BOT;
+ }
+ first_name = "Replies";
+ username = "replies";
+ bot_info_version = G()->is_test_dc() ? 1 : 3;
+ } else if (user_id == get_anonymous_bot_user_id()) {
+ flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT;
+ if (!G()->is_test_dc()) {
+ flags |= USER_FLAG_IS_PRIVATE_BOT;
+ }
+ first_name = "Group";
+ username = G()->is_test_dc() ? "izgroupbot" : "GroupAnonymousBot";
+ bot_info_version = G()->is_test_dc() ? 1 : 3;
+ profile_photo_id = 5159307831025969322;
+ } else if (user_id == get_channel_bot_user_id()) {
+ flags |= USER_FLAG_HAS_USERNAME | USER_FLAG_IS_BOT;
+ if (!G()->is_test_dc()) {
+ flags |= USER_FLAG_IS_PRIVATE_BOT;
+ }
+ first_name = G()->is_test_dc() ? "Channels" : "Channel";
+ username = G()->is_test_dc() ? "channelsbot" : "Channel_Bot";
+ bot_info_version = G()->is_test_dc() ? 1 : 4;
+ profile_photo_id = 587627495930570665;
+ }
+
+ telegram_api::object_ptr<telegram_api::userProfilePhoto> profile_photo;
+ if (!G()->is_test_dc() && profile_photo_id != 0) {
+ profile_photo = telegram_api::make_object<telegram_api::userProfilePhoto>(0, false /*ignored*/, profile_photo_id,
+ BufferSlice(), profile_photo_dc_id);
+ }
+
+ auto user = telegram_api::make_object<telegram_api::user>(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, user_id.get(), 1, first_name, string(), username,
+ phone_number, std::move(profile_photo), nullptr, bot_info_version, Auto(), string(), string(), nullptr,
+ vector<telegram_api::object_ptr<telegram_api::username>>());
+ on_get_user(std::move(user), "get_user_force");
+ u = get_user(user_id);
+ CHECK(u != nullptr && u->is_received);
+ }
+ return u;
+}
+
+ContactsManager::User *ContactsManager::get_user_force_impl(UserId user_id) {
if (!user_id.is_valid()) {
return nullptr;
}
@@ -5271,31 +9717,32 @@ ContactsManager::User *ContactsManager::get_user_force(UserId user_id) {
return nullptr;
}
- LOG(INFO) << "Try load " << user_id << " from database";
- on_load_user_from_database(user_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_user_database_key(user_id)));
+ LOG(INFO) << "Trying to load " << user_id << " from database";
+ on_load_user_from_database(user_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_user_database_key(user_id)), true);
return get_user(user_id);
}
class ContactsManager::ChatLogEvent {
public:
ChatId chat_id;
- Chat c;
+ const Chat *c_in = nullptr;
+ unique_ptr<Chat> c_out;
ChatLogEvent() = default;
- ChatLogEvent(ChatId chat_id, const Chat &c) : chat_id(chat_id), c(c) {
+ ChatLogEvent(ChatId chat_id, const Chat *c) : chat_id(chat_id), c_in(c) {
}
template <class StorerT>
void store(StorerT &storer) const {
td::store(chat_id, storer);
- td::store(c, storer);
+ td::store(*c_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(chat_id, parser);
- td::parse(c, parser);
+ td::parse(c_out, parser);
}
};
@@ -5306,12 +9753,12 @@ void ContactsManager::save_chat(Chat *c, ChatId chat_id, bool from_binlog) {
CHECK(c != nullptr);
if (!c->is_saved) {
if (!from_binlog) {
- auto logevent = ChatLogEvent(chat_id, *c);
- auto storer = LogEventStorerImpl<ChatLogEvent>(logevent);
- if (c->logevent_id == 0) {
- c->logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Chats, storer);
+ auto log_event = ChatLogEvent(chat_id, c);
+ auto storer = get_log_event_storer(log_event);
+ if (c->log_event_id == 0) {
+ c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Chats, storer);
} else {
- BinlogHelper::rewrite(G()->td_db()->get_binlog(), c->logevent_id, LogEvent::HandlerType::Chats, storer);
+ binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Chats, storer);
}
}
@@ -5322,7 +9769,7 @@ void ContactsManager::save_chat(Chat *c, ChatId chat_id, bool from_binlog) {
void ContactsManager::on_binlog_chat_event(BinlogEvent &&event) {
if (!G()->parameters().use_chat_info_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
return;
}
@@ -5330,18 +9777,24 @@ void ContactsManager::on_binlog_chat_event(BinlogEvent &&event) {
log_event_parse(log_event, event.data_).ensure();
auto chat_id = log_event.chat_id;
+ if (have_chat(chat_id) || !chat_id.is_valid()) {
+ LOG(ERROR) << "Skip adding already added " << chat_id;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ return;
+ }
+
LOG(INFO) << "Add " << chat_id << " from binlog";
- Chat *c = add_chat(chat_id);
- CHECK(!c->left && c->kicked);
- *c = std::move(log_event.c); // chats come from binlog before all other events, so just add them
+ chats_.set(chat_id, std::move(log_event.c_out));
- c->logevent_id = event.id_;
+ Chat *c = get_chat(chat_id);
+ CHECK(c != nullptr);
+ c->log_event_id = event.id_;
update_chat(c, chat_id, true, false);
}
string ContactsManager::get_chat_database_key(ChatId chat_id) {
- return "gr" + to_string(chat_id.get());
+ return PSTRING() << "gr" << chat_id.get();
}
string ContactsManager::get_chat_database_value(const Chat *c) {
@@ -5367,6 +9820,7 @@ void ContactsManager::save_chat_to_database(Chat *c, ChatId chat_id) {
void ContactsManager::save_chat_to_database_impl(Chat *c, ChatId chat_id, string value) {
CHECK(c != nullptr);
CHECK(load_chat_from_database_queries_.count(chat_id) == 0);
+ CHECK(!c->is_being_saved);
c->is_being_saved = true;
c->is_saved = true;
LOG(INFO) << "Trying to save to database " << chat_id;
@@ -5377,6 +9831,10 @@ void ContactsManager::save_chat_to_database_impl(Chat *c, ChatId chat_id, string
}
void ContactsManager::on_save_chat_to_database(ChatId chat_id, bool success) {
+ if (G()->close_flag()) {
+ return;
+ }
+
Chat *c = get_chat(chat_id);
CHECK(c != nullptr);
CHECK(c->is_being_saved);
@@ -5390,12 +9848,12 @@ void ContactsManager::on_save_chat_to_database(ChatId chat_id, bool success) {
LOG(INFO) << "Successfully saved " << chat_id << " to database";
}
if (c->is_saved) {
- if (c->logevent_id != 0) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), c->logevent_id);
- c->logevent_id = 0;
+ if (c->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), c->log_event_id);
+ c->log_event_id = 0;
}
} else {
- save_chat(c, chat_id, c->logevent_id != 0);
+ save_chat(c, chat_id, c->log_event_id != 0);
}
}
@@ -5417,12 +9875,18 @@ void ContactsManager::load_chat_from_database_impl(ChatId chat_id, Promise<Unit>
G()->td_db()->get_sqlite_pmc()->get(get_chat_database_key(chat_id), PromiseCreator::lambda([chat_id](string value) {
send_closure(G()->contacts_manager(),
&ContactsManager::on_load_chat_from_database, chat_id,
- std::move(value));
+ std::move(value), false);
}));
}
}
-void ContactsManager::on_load_chat_from_database(ChatId chat_id, string value) {
+void ContactsManager::on_load_chat_from_database(ChatId chat_id, string value, bool force) {
+ if (G()->close_flag() && !force) {
+ // the chat is in Binlog and will be saved after restart
+ return;
+ }
+
+ CHECK(chat_id.is_valid());
if (!loaded_from_database_chats_.insert(chat_id).second) {
return;
}
@@ -5455,9 +9919,9 @@ void ContactsManager::on_load_chat_from_database(ChatId chat_id, string value) {
auto new_value = get_chat_database_value(c);
if (value != new_value) {
save_chat_to_database_impl(c, chat_id, std::move(new_value));
- } else if (c->logevent_id != 0) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), c->logevent_id);
- c->logevent_id = 0;
+ } else if (c->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), c->log_event_id);
+ c->log_event_id = 0;
}
}
@@ -5465,9 +9929,7 @@ void ContactsManager::on_load_chat_from_database(ChatId chat_id, string value) {
LOG(ERROR) << "Can't find " << c->migrated_to_channel_id << " from " << chat_id;
}
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(promises);
}
bool ContactsManager::have_chat_force(ChatId chat_id) {
@@ -5494,31 +9956,32 @@ ContactsManager::Chat *ContactsManager::get_chat_force(ChatId chat_id) {
return nullptr;
}
- LOG(INFO) << "Try load " << chat_id << " from database";
- on_load_chat_from_database(chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_chat_database_key(chat_id)));
+ LOG(INFO) << "Trying to load " << chat_id << " from database";
+ on_load_chat_from_database(chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_chat_database_key(chat_id)), true);
return get_chat(chat_id);
}
class ContactsManager::ChannelLogEvent {
public:
ChannelId channel_id;
- Channel c;
+ const Channel *c_in = nullptr;
+ unique_ptr<Channel> c_out;
ChannelLogEvent() = default;
- ChannelLogEvent(ChannelId channel_id, const Channel &c) : channel_id(channel_id), c(c) {
+ ChannelLogEvent(ChannelId channel_id, const Channel *c) : channel_id(channel_id), c_in(c) {
}
template <class StorerT>
void store(StorerT &storer) const {
td::store(channel_id, storer);
- td::store(c, storer);
+ td::store(*c_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(channel_id, parser);
- td::parse(c, parser);
+ td::parse(c_out, parser);
}
};
@@ -5529,12 +9992,12 @@ void ContactsManager::save_channel(Channel *c, ChannelId channel_id, bool from_b
CHECK(c != nullptr);
if (!c->is_saved) {
if (!from_binlog) {
- auto logevent = ChannelLogEvent(channel_id, *c);
- auto storer = LogEventStorerImpl<ChannelLogEvent>(logevent);
- if (c->logevent_id == 0) {
- c->logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Channels, storer);
+ auto log_event = ChannelLogEvent(channel_id, c);
+ auto storer = get_log_event_storer(log_event);
+ if (c->log_event_id == 0) {
+ c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::Channels, storer);
} else {
- BinlogHelper::rewrite(G()->td_db()->get_binlog(), c->logevent_id, LogEvent::HandlerType::Channels, storer);
+ binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::Channels, storer);
}
}
@@ -5545,7 +10008,7 @@ void ContactsManager::save_channel(Channel *c, ChannelId channel_id, bool from_b
void ContactsManager::on_binlog_channel_event(BinlogEvent &&event) {
if (!G()->parameters().use_chat_info_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
return;
}
@@ -5553,18 +10016,24 @@ void ContactsManager::on_binlog_channel_event(BinlogEvent &&event) {
log_event_parse(log_event, event.data_).ensure();
auto channel_id = log_event.channel_id;
+ if (have_channel(channel_id) || !channel_id.is_valid()) {
+ LOG(ERROR) << "Skip adding already added " << channel_id;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ return;
+ }
+
LOG(INFO) << "Add " << channel_id << " from binlog";
- Channel *c = add_channel(channel_id);
- CHECK(c->status.is_banned());
- *c = std::move(log_event.c); // channels come from binlog before all other events, so just add them
+ channels_.set(channel_id, std::move(log_event.c_out));
- c->logevent_id = event.id_;
+ Channel *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+ c->log_event_id = event.id_;
update_channel(c, channel_id, true, false);
}
string ContactsManager::get_channel_database_key(ChannelId channel_id) {
- return "ch" + to_string(channel_id.get());
+ return PSTRING() << "ch" << channel_id.get();
}
string ContactsManager::get_channel_database_value(const Channel *c) {
@@ -5590,6 +10059,7 @@ void ContactsManager::save_channel_to_database(Channel *c, ChannelId channel_id)
void ContactsManager::save_channel_to_database_impl(Channel *c, ChannelId channel_id, string value) {
CHECK(c != nullptr);
CHECK(load_channel_from_database_queries_.count(channel_id) == 0);
+ CHECK(!c->is_being_saved);
c->is_being_saved = true;
c->is_saved = true;
LOG(INFO) << "Trying to save to database " << channel_id;
@@ -5601,6 +10071,10 @@ void ContactsManager::save_channel_to_database_impl(Channel *c, ChannelId channe
}
void ContactsManager::on_save_channel_to_database(ChannelId channel_id, bool success) {
+ if (G()->close_flag()) {
+ return;
+ }
+
Channel *c = get_channel(channel_id);
CHECK(c != nullptr);
CHECK(c->is_being_saved);
@@ -5614,12 +10088,12 @@ void ContactsManager::on_save_channel_to_database(ChannelId channel_id, bool suc
LOG(INFO) << "Successfully saved " << channel_id << " to database";
}
if (c->is_saved) {
- if (c->logevent_id != 0) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), c->logevent_id);
- c->logevent_id = 0;
+ if (c->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), c->log_event_id);
+ c->log_event_id = 0;
}
} else {
- save_channel(c, channel_id, c->logevent_id != 0);
+ save_channel(c, channel_id, c->log_event_id != 0);
}
}
@@ -5641,12 +10115,18 @@ void ContactsManager::load_channel_from_database_impl(ChannelId channel_id, Prom
G()->td_db()->get_sqlite_pmc()->get(
get_channel_database_key(channel_id), PromiseCreator::lambda([channel_id](string value) {
send_closure(G()->contacts_manager(), &ContactsManager::on_load_channel_from_database, channel_id,
- std::move(value));
+ std::move(value), false);
}));
}
}
-void ContactsManager::on_load_channel_from_database(ChannelId channel_id, string value) {
+void ContactsManager::on_load_channel_from_database(ChannelId channel_id, string value, bool force) {
+ if (G()->close_flag() && !force) {
+ // the channel is in Binlog and will be saved after restart
+ return;
+ }
+
+ CHECK(channel_id.is_valid());
if (!loaded_from_database_channels_.insert(channel_id).second) {
return;
}
@@ -5666,7 +10146,7 @@ void ContactsManager::on_load_channel_from_database(ChannelId channel_id, string
Channel *c = get_channel(channel_id);
if (c == nullptr) {
if (!value.empty()) {
- c = add_channel(channel_id);
+ c = add_channel(channel_id, "on_load_channel_from_database");
log_event_parse(*c, value).ensure();
@@ -5676,18 +10156,38 @@ void ContactsManager::on_load_channel_from_database(ChannelId channel_id, string
} else {
CHECK(!c->is_saved); // channel can't be saved before load completes
CHECK(!c->is_being_saved);
+ if (!value.empty()) {
+ Channel temp_c;
+ log_event_parse(temp_c, value).ensure();
+ if (c->participant_count == 0 && temp_c.participant_count != 0) {
+ c->participant_count = temp_c.participant_count;
+ CHECK(c->is_update_supergroup_sent);
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateSupergroup>(get_supergroup_object(channel_id, c)));
+ }
+
+ c->status.update_restrictions();
+ temp_c.status.update_restrictions();
+ if (temp_c.status != c->status) {
+ on_channel_status_changed(c, channel_id, temp_c.status, c->status);
+ CHECK(!c->is_being_saved);
+ }
+
+ if (temp_c.usernames != c->usernames) {
+ on_channel_usernames_changed(c, channel_id, temp_c.usernames, c->usernames);
+ CHECK(!c->is_being_saved);
+ }
+ }
auto new_value = get_channel_database_value(c);
if (value != new_value) {
save_channel_to_database_impl(c, channel_id, std::move(new_value));
- } else if (c->logevent_id != 0) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), c->logevent_id);
- c->logevent_id = 0;
+ } else if (c->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), c->log_event_id);
+ c->log_event_id = 0;
}
}
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(promises);
}
bool ContactsManager::have_channel_force(ChannelId channel_id) {
@@ -5710,32 +10210,33 @@ ContactsManager::Channel *ContactsManager::get_channel_force(ChannelId channel_i
return nullptr;
}
- LOG(INFO) << "Try load " << channel_id << " from database";
+ LOG(INFO) << "Trying to load " << channel_id << " from database";
on_load_channel_from_database(channel_id,
- G()->td_db()->get_sqlite_sync_pmc()->get(get_channel_database_key(channel_id)));
+ G()->td_db()->get_sqlite_sync_pmc()->get(get_channel_database_key(channel_id)), true);
return get_channel(channel_id);
}
class ContactsManager::SecretChatLogEvent {
public:
SecretChatId secret_chat_id;
- SecretChat c;
+ const SecretChat *c_in = nullptr;
+ unique_ptr<SecretChat> c_out;
SecretChatLogEvent() = default;
- SecretChatLogEvent(SecretChatId secret_chat_id, const SecretChat &c) : secret_chat_id(secret_chat_id), c(c) {
+ SecretChatLogEvent(SecretChatId secret_chat_id, const SecretChat *c) : secret_chat_id(secret_chat_id), c_in(c) {
}
template <class StorerT>
void store(StorerT &storer) const {
td::store(secret_chat_id, storer);
- td::store(c, storer);
+ td::store(*c_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(secret_chat_id, parser);
- td::parse(c, parser);
+ td::parse(c_out, parser);
}
};
@@ -5746,13 +10247,12 @@ void ContactsManager::save_secret_chat(SecretChat *c, SecretChatId secret_chat_i
CHECK(c != nullptr);
if (!c->is_saved) {
if (!from_binlog) {
- auto logevent = SecretChatLogEvent(secret_chat_id, *c);
- auto storer = LogEventStorerImpl<SecretChatLogEvent>(logevent);
- if (c->logevent_id == 0) {
- c->logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SecretChatInfos, storer);
+ auto log_event = SecretChatLogEvent(secret_chat_id, c);
+ auto storer = get_log_event_storer(log_event);
+ if (c->log_event_id == 0) {
+ c->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SecretChatInfos, storer);
} else {
- BinlogHelper::rewrite(G()->td_db()->get_binlog(), c->logevent_id, LogEvent::HandlerType::SecretChatInfos,
- storer);
+ binlog_rewrite(G()->td_db()->get_binlog(), c->log_event_id, LogEvent::HandlerType::SecretChatInfos, storer);
}
}
@@ -5763,7 +10263,7 @@ void ContactsManager::save_secret_chat(SecretChat *c, SecretChatId secret_chat_i
void ContactsManager::on_binlog_secret_chat_event(BinlogEvent &&event) {
if (!G()->parameters().use_chat_info_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
return;
}
@@ -5771,18 +10271,24 @@ void ContactsManager::on_binlog_secret_chat_event(BinlogEvent &&event) {
log_event_parse(log_event, event.data_).ensure();
auto secret_chat_id = log_event.secret_chat_id;
+ if (have_secret_chat(secret_chat_id) || !secret_chat_id.is_valid()) {
+ LOG(ERROR) << "Skip adding already added " << secret_chat_id;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ return;
+ }
+
LOG(INFO) << "Add " << secret_chat_id << " from binlog";
- SecretChat *c = add_secret_chat(secret_chat_id);
- CHECK(c->date == 0);
- *c = std::move(log_event.c); // secret chats come from binlog before all other events, so just add them
+ secret_chats_.set(secret_chat_id, std::move(log_event.c_out));
- c->logevent_id = event.id_;
+ SecretChat *c = get_secret_chat(secret_chat_id);
+ CHECK(c != nullptr);
+ c->log_event_id = event.id_;
update_secret_chat(c, secret_chat_id, true, false);
}
string ContactsManager::get_secret_chat_database_key(SecretChatId secret_chat_id) {
- return "sc" + to_string(secret_chat_id.get());
+ return PSTRING() << "sc" << secret_chat_id.get();
}
string ContactsManager::get_secret_chat_database_value(const SecretChat *c) {
@@ -5808,6 +10314,7 @@ void ContactsManager::save_secret_chat_to_database(SecretChat *c, SecretChatId s
void ContactsManager::save_secret_chat_to_database_impl(SecretChat *c, SecretChatId secret_chat_id, string value) {
CHECK(c != nullptr);
CHECK(load_secret_chat_from_database_queries_.count(secret_chat_id) == 0);
+ CHECK(!c->is_being_saved);
c->is_being_saved = true;
c->is_saved = true;
LOG(INFO) << "Trying to save to database " << secret_chat_id;
@@ -5820,6 +10327,10 @@ void ContactsManager::save_secret_chat_to_database_impl(SecretChat *c, SecretCha
}
void ContactsManager::on_save_secret_chat_to_database(SecretChatId secret_chat_id, bool success) {
+ if (G()->close_flag()) {
+ return;
+ }
+
SecretChat *c = get_secret_chat(secret_chat_id);
CHECK(c != nullptr);
CHECK(c->is_being_saved);
@@ -5833,12 +10344,12 @@ void ContactsManager::on_save_secret_chat_to_database(SecretChatId secret_chat_i
LOG(INFO) << "Successfully saved " << secret_chat_id << " to database";
}
if (c->is_saved) {
- if (c->logevent_id != 0) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), c->logevent_id);
- c->logevent_id = 0;
+ if (c->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), c->log_event_id);
+ c->log_event_id = 0;
}
} else {
- save_secret_chat(c, secret_chat_id, c->logevent_id != 0);
+ save_secret_chat(c, secret_chat_id, c->log_event_id != 0);
}
}
@@ -5861,12 +10372,18 @@ void ContactsManager::load_secret_chat_from_database_impl(SecretChatId secret_ch
G()->td_db()->get_sqlite_pmc()->get(
get_secret_chat_database_key(secret_chat_id), PromiseCreator::lambda([secret_chat_id](string value) {
send_closure(G()->contacts_manager(), &ContactsManager::on_load_secret_chat_from_database, secret_chat_id,
- std::move(value));
+ std::move(value), false);
}));
}
}
-void ContactsManager::on_load_secret_chat_from_database(SecretChatId secret_chat_id, string value) {
+void ContactsManager::on_load_secret_chat_from_database(SecretChatId secret_chat_id, string value, bool force) {
+ if (G()->close_flag() && !force) {
+ // the secret chat is in Binlog and will be saved after restart
+ return;
+ }
+
+ CHECK(secret_chat_id.is_valid());
if (!loaded_from_database_secret_chats_.insert(secret_chat_id).second) {
return;
}
@@ -5899,9 +10416,9 @@ void ContactsManager::on_load_secret_chat_from_database(SecretChatId secret_chat
auto new_value = get_secret_chat_database_value(c);
if (value != new_value) {
save_secret_chat_to_database_impl(c, secret_chat_id, std::move(new_value));
- } else if (c->logevent_id != 0) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), c->logevent_id);
- c->logevent_id = 0;
+ } else if (c->log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), c->log_event_id);
+ c->log_event_id = 0;
}
}
@@ -5910,9 +10427,7 @@ void ContactsManager::on_load_secret_chat_from_database(SecretChatId secret_chat
LOG(ERROR) << "Can't find " << c->user_id << " from " << secret_chat_id;
}
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(promises);
}
bool ContactsManager::have_secret_chat_force(SecretChatId secret_chat_id) {
@@ -5938,49 +10453,431 @@ ContactsManager::SecretChat *ContactsManager::get_secret_chat_force(SecretChatId
return nullptr;
}
- LOG(INFO) << "Try load " << secret_chat_id << " from database";
+ LOG(INFO) << "Trying to load " << secret_chat_id << " from database";
on_load_secret_chat_from_database(
- secret_chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_secret_chat_database_key(secret_chat_id)));
+ secret_chat_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_secret_chat_database_key(secret_chat_id)), true);
return get_secret_chat(secret_chat_id);
}
+void ContactsManager::save_user_full(const UserFull *user_full, UserId user_id) {
+ if (!G()->parameters().use_chat_info_db) {
+ return;
+ }
+
+ LOG(INFO) << "Trying to save to database full " << user_id;
+ CHECK(user_full != nullptr);
+ G()->td_db()->get_sqlite_pmc()->set(get_user_full_database_key(user_id), get_user_full_database_value(user_full),
+ Auto());
+}
+
+string ContactsManager::get_user_full_database_key(UserId user_id) {
+ return PSTRING() << "usf" << user_id.get();
+}
+
+string ContactsManager::get_user_full_database_value(const UserFull *user_full) {
+ return log_event_store(*user_full).as_slice().str();
+}
+
+void ContactsManager::on_load_user_full_from_database(UserId user_id, string value) {
+ LOG(INFO) << "Successfully loaded full " << user_id << " of size " << value.size() << " from database";
+ // G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto());
+ // return;
+
+ if (get_user_full(user_id) != nullptr || value.empty()) {
+ return;
+ }
+
+ UserFull *user_full = add_user_full(user_id);
+ auto status = log_event_parse(*user_full, value);
+ if (status.is_error()) {
+ // can't happen unless database is broken
+ LOG(ERROR) << "Repair broken full " << user_id << ' ' << format::as_hex_dump<4>(Slice(value));
+
+ // just clean all known data about the user and pretend that there was nothing in the database
+ users_full_.erase(user_id);
+ G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto());
+ return;
+ }
+
+ Dependencies dependencies;
+ dependencies.add(user_id);
+ if (!dependencies.resolve_force(td_, "on_load_user_full_from_database")) {
+ users_full_.erase(user_id);
+ G()->td_db()->get_sqlite_pmc()->erase(get_user_full_database_key(user_id), Auto());
+ return;
+ }
+
+ if (user_full->need_phone_number_privacy_exception && is_user_contact(user_id)) {
+ user_full->need_phone_number_privacy_exception = false;
+ }
+
+ User *u = get_user(user_id);
+ CHECK(u != nullptr);
+ if (u->photo.id != user_full->photo.id.get()) {
+ user_full->photo = Photo();
+ if (u->photo.id > 0) {
+ user_full->expires_at = 0.0;
+ }
+ }
+ if (!user_full->photo.is_empty()) {
+ register_user_photo(u, user_id, user_full->photo);
+ }
+
+ td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, false);
+
+ user_full->is_update_user_full_sent = true;
+ update_user_full(user_full, user_id, "on_load_user_full_from_database", true);
+
+ if (is_user_deleted(user_id)) {
+ drop_user_full(user_id);
+ } else if (user_full->expires_at == 0.0) {
+ reload_user_full(user_id, Auto());
+ }
+}
+
+ContactsManager::UserFull *ContactsManager::get_user_full_force(UserId user_id) {
+ if (!have_user_force(user_id)) {
+ return nullptr;
+ }
+
+ UserFull *user_full = get_user_full(user_id);
+ if (user_full != nullptr) {
+ return user_full;
+ }
+ if (!G()->parameters().use_chat_info_db) {
+ return nullptr;
+ }
+ if (!unavailable_user_fulls_.insert(user_id).second) {
+ return nullptr;
+ }
+
+ LOG(INFO) << "Trying to load full " << user_id << " from database";
+ on_load_user_full_from_database(user_id,
+ G()->td_db()->get_sqlite_sync_pmc()->get(get_user_full_database_key(user_id)));
+ return get_user_full(user_id);
+}
+
+void ContactsManager::save_chat_full(const ChatFull *chat_full, ChatId chat_id) {
+ if (!G()->parameters().use_chat_info_db) {
+ return;
+ }
+
+ LOG(INFO) << "Trying to save to database full " << chat_id;
+ CHECK(chat_full != nullptr);
+ G()->td_db()->get_sqlite_pmc()->set(get_chat_full_database_key(chat_id), get_chat_full_database_value(chat_full),
+ Auto());
+}
+
+string ContactsManager::get_chat_full_database_key(ChatId chat_id) {
+ return PSTRING() << "grf" << chat_id.get();
+}
+
+string ContactsManager::get_chat_full_database_value(const ChatFull *chat_full) {
+ return log_event_store(*chat_full).as_slice().str();
+}
+
+void ContactsManager::on_load_chat_full_from_database(ChatId chat_id, string value) {
+ LOG(INFO) << "Successfully loaded full " << chat_id << " of size " << value.size() << " from database";
+ // G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto());
+ // return;
+
+ if (get_chat_full(chat_id) != nullptr || value.empty()) {
+ return;
+ }
+
+ ChatFull *chat_full = add_chat_full(chat_id);
+ auto status = log_event_parse(*chat_full, value);
+ if (status.is_error()) {
+ // can't happen unless database is broken
+ LOG(ERROR) << "Repair broken full " << chat_id << ' ' << format::as_hex_dump<4>(Slice(value));
+
+ // just clean all known data about the chat and pretend that there was nothing in the database
+ chats_full_.erase(chat_id);
+ G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto());
+ return;
+ }
+
+ Dependencies dependencies;
+ dependencies.add(chat_id);
+ dependencies.add(chat_full->creator_user_id);
+ for (auto &participant : chat_full->participants) {
+ dependencies.add_message_sender_dependencies(participant.dialog_id_);
+ dependencies.add(participant.inviter_user_id_);
+ }
+ dependencies.add(chat_full->invite_link.get_creator_user_id());
+ if (!dependencies.resolve_force(td_, "on_load_chat_full_from_database")) {
+ chats_full_.erase(chat_id);
+ G()->td_db()->get_sqlite_pmc()->erase(get_chat_full_database_key(chat_id), Auto());
+ return;
+ }
+
+ Chat *c = get_chat(chat_id);
+ CHECK(c != nullptr);
+
+ bool need_invite_link = c->is_active && c->status.can_manage_invite_links();
+ bool have_invite_link = chat_full->invite_link.is_valid();
+ if (need_invite_link != have_invite_link) {
+ if (need_invite_link) {
+ // ignore ChatFull without invite link
+ chats_full_.erase(chat_id);
+ return;
+ } else {
+ chat_full->invite_link = DialogInviteLink();
+ }
+ }
+
+ if (!is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo)) {
+ chat_full->photo = Photo();
+ if (c->photo.small_file_id.is_valid()) {
+ reload_chat_full(chat_id, Auto());
+ }
+ }
+
+ auto photo = std::move(chat_full->photo);
+ chat_full->photo = Photo();
+ on_update_chat_full_photo(chat_full, chat_id, std::move(photo));
+
+ td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, false);
+
+ chat_full->is_update_chat_full_sent = true;
+ update_chat_full(chat_full, chat_id, "on_load_chat_full_from_database", true);
+}
+
+ContactsManager::ChatFull *ContactsManager::get_chat_full_force(ChatId chat_id, const char *source) {
+ if (!have_chat_force(chat_id)) {
+ return nullptr;
+ }
+
+ ChatFull *chat_full = get_chat_full(chat_id);
+ if (chat_full != nullptr) {
+ return chat_full;
+ }
+ if (!G()->parameters().use_chat_info_db) {
+ return nullptr;
+ }
+ if (!unavailable_chat_fulls_.insert(chat_id).second) {
+ return nullptr;
+ }
+
+ LOG(INFO) << "Trying to load full " << chat_id << " from database from " << source;
+ on_load_chat_full_from_database(chat_id,
+ G()->td_db()->get_sqlite_sync_pmc()->get(get_chat_full_database_key(chat_id)));
+ return get_chat_full(chat_id);
+}
+
+void ContactsManager::save_channel_full(const ChannelFull *channel_full, ChannelId channel_id) {
+ if (!G()->parameters().use_chat_info_db) {
+ return;
+ }
+
+ LOG(INFO) << "Trying to save to database full " << channel_id;
+ CHECK(channel_full != nullptr);
+ G()->td_db()->get_sqlite_pmc()->set(get_channel_full_database_key(channel_id),
+ get_channel_full_database_value(channel_full), Auto());
+}
+
+string ContactsManager::get_channel_full_database_key(ChannelId channel_id) {
+ return PSTRING() << "chf" << channel_id.get();
+}
+
+string ContactsManager::get_channel_full_database_value(const ChannelFull *channel_full) {
+ return log_event_store(*channel_full).as_slice().str();
+}
+
+void ContactsManager::on_load_channel_full_from_database(ChannelId channel_id, string value, const char *source) {
+ LOG(INFO) << "Successfully loaded full " << channel_id << " of size " << value.size() << " from database from "
+ << source;
+ // G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto());
+ // return;
+
+ if (get_channel_full(channel_id, true, "on_load_channel_full_from_database") != nullptr || value.empty()) {
+ return;
+ }
+
+ ChannelFull *channel_full = add_channel_full(channel_id);
+ auto status = log_event_parse(*channel_full, value);
+ if (status.is_error()) {
+ // can't happen unless database is broken
+ LOG(ERROR) << "Repair broken full " << channel_id << ' ' << format::as_hex_dump<4>(Slice(value));
+
+ // just clean all known data about the channel and pretend that there was nothing in the database
+ channels_full_.erase(channel_id);
+ G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto());
+ return;
+ }
+
+ Dependencies dependencies;
+ dependencies.add(channel_id);
+ // must not depend on the linked_dialog_id itself, because message database can be disabled
+ // the Dialog will be forcely created in update_channel_full
+ dependencies.add_dialog_dependencies(DialogId(channel_full->linked_channel_id));
+ dependencies.add(channel_full->migrated_from_chat_id);
+ for (auto bot_user_id : channel_full->bot_user_ids) {
+ dependencies.add(bot_user_id);
+ }
+ dependencies.add(channel_full->invite_link.get_creator_user_id());
+ if (!dependencies.resolve_force(td_, source)) {
+ channels_full_.erase(channel_id);
+ G()->td_db()->get_sqlite_pmc()->erase(get_channel_full_database_key(channel_id), Auto());
+ return;
+ }
+
+ Channel *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+
+ bool need_invite_link = c->status.can_manage_invite_links();
+ bool have_invite_link = channel_full->invite_link.is_valid();
+ if (need_invite_link != have_invite_link) {
+ if (need_invite_link) {
+ // ignore ChannelFull without invite link
+ channels_full_.erase(channel_id);
+ return;
+ } else {
+ channel_full->invite_link = DialogInviteLink();
+ }
+ }
+
+ if (!is_same_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), channel_full->photo, c->photo)) {
+ channel_full->photo = Photo();
+ if (c->photo.small_file_id.is_valid()) {
+ channel_full->expires_at = 0.0;
+ }
+ }
+ auto photo = std::move(channel_full->photo);
+ channel_full->photo = Photo();
+ on_update_channel_full_photo(channel_full, channel_id, std::move(photo));
+
+ if (channel_full->participant_count < channel_full->administrator_count) {
+ channel_full->participant_count = channel_full->administrator_count;
+ }
+ if (c->participant_count != 0 && c->participant_count != channel_full->participant_count) {
+ channel_full->participant_count = c->participant_count;
+
+ if (channel_full->participant_count < channel_full->administrator_count) {
+ channel_full->participant_count = channel_full->administrator_count;
+ channel_full->expires_at = 0.0;
+
+ c->participant_count = channel_full->participant_count;
+ c->is_changed = true;
+ }
+ }
+ if (c->can_be_deleted != channel_full->can_be_deleted) {
+ c->can_be_deleted = channel_full->can_be_deleted;
+ c->need_save_to_database = true;
+ }
+
+ if (invalidated_channels_full_.erase(channel_id) > 0 ||
+ (!c->is_slow_mode_enabled && channel_full->slow_mode_delay != 0)) {
+ do_invalidate_channel_full(channel_full, channel_id, !c->is_slow_mode_enabled);
+ }
+
+ td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, false);
+
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id),
+ channel_full->bot_user_ids, true);
+
+ update_channel(c, channel_id);
+
+ channel_full->is_update_channel_full_sent = true;
+ update_channel_full(channel_full, channel_id, "on_load_channel_full_from_database", true);
+
+ if (channel_full->expires_at == 0.0) {
+ load_channel_full(channel_id, true, Auto(), "on_load_channel_full_from_database");
+ }
+}
+
+ContactsManager::ChannelFull *ContactsManager::get_channel_full_force(ChannelId channel_id, bool only_local,
+ const char *source) {
+ if (!have_channel_force(channel_id)) {
+ return nullptr;
+ }
+
+ ChannelFull *channel_full = get_channel_full(channel_id, only_local, source);
+ if (channel_full != nullptr) {
+ return channel_full;
+ }
+ if (!G()->parameters().use_chat_info_db) {
+ return nullptr;
+ }
+ if (!unavailable_channel_fulls_.insert(channel_id).second) {
+ return nullptr;
+ }
+
+ LOG(INFO) << "Trying to load full " << channel_id << " from database from " << source;
+ on_load_channel_full_from_database(
+ channel_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_channel_full_database_key(channel_id)), source);
+ return get_channel_full(channel_id, only_local, source);
+}
+
+void ContactsManager::for_each_secret_chat_with_user(UserId user_id, const std::function<void(SecretChatId)> &f) {
+ auto it = secret_chats_with_user_.find(user_id);
+ if (it != secret_chats_with_user_.end()) {
+ for (auto secret_chat_id : it->second) {
+ f(secret_chat_id);
+ }
+ }
+}
+
void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, bool from_database) {
CHECK(u != nullptr);
- if (u->is_name_changed || u->is_username_changed || u->is_outbound_link_changed) {
- update_contacts_hints(u, user_id, from_database);
+ if (user_id == get_my_id()) {
+ if (td_->option_manager_->get_option_boolean("is_premium") != u->is_premium) {
+ td_->option_manager_->set_option_boolean("is_premium", u->is_premium);
+ send_closure(td_->config_manager_, &ConfigManager::request_config, true);
+ td_->stickers_manager_->reload_top_reactions();
+ }
}
- if (u->is_name_changed) {
- td_->messages_manager_->on_dialog_title_updated(DialogId(user_id));
- auto it = secret_chats_with_user_.find(user_id);
- if (it != secret_chats_with_user_.end()) {
- for (auto secret_chat_id : it->second) {
- td_->messages_manager_->on_dialog_title_updated(DialogId(secret_chat_id));
+ if (u->is_name_changed || u->is_username_changed || u->is_is_contact_changed) {
+ update_contacts_hints(u, user_id, from_database);
+ u->is_username_changed = false;
+ }
+ if (u->is_is_contact_changed) {
+ td_->messages_manager_->on_dialog_user_is_contact_updated(DialogId(user_id), u->is_contact);
+ if (is_user_contact(u, user_id, false)) {
+ auto user_full = get_user_full(user_id);
+ if (user_full != nullptr && user_full->need_phone_number_privacy_exception) {
+ on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, false);
+ update_user_full(user_full, user_id, "update_user");
}
}
+ u->is_is_contact_changed = false;
}
- if (u->is_photo_changed) {
- td_->messages_manager_->on_dialog_photo_updated(DialogId(user_id));
- auto it = secret_chats_with_user_.find(user_id);
- if (it != secret_chats_with_user_.end()) {
- for (auto secret_chat_id : it->second) {
- td_->messages_manager_->on_dialog_photo_updated(DialogId(secret_chat_id));
+ if (u->is_is_deleted_changed) {
+ td_->messages_manager_->on_dialog_user_is_deleted_updated(DialogId(user_id), u->is_deleted);
+ if (u->is_deleted) {
+ auto user_full = get_user_full(user_id); // must not load user_full from database before sending updateUser
+ if (user_full != nullptr) {
+ drop_user_full(user_id);
}
}
-
- UserFull *user_full = get_user_full(user_id);
- if (user_full != nullptr) {
- user_full->photos.clear();
- if (u->photo.id == 0) {
- user_full->photo_count = 0;
- } else {
- user_full->photo_count = -1;
- }
- user_full->photos_offset = user_full->photo_count;
+ u->is_is_deleted_changed = false;
+ }
+ if (u->is_name_changed) {
+ auto messages_manager = td_->messages_manager_.get();
+ messages_manager->on_dialog_title_updated(DialogId(user_id));
+ for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) {
+ messages_manager->on_dialog_title_updated(DialogId(secret_chat_id));
+ });
+ u->is_name_changed = false;
+ }
+ if (u->is_photo_changed) {
+ auto messages_manager = td_->messages_manager_.get();
+ messages_manager->on_dialog_photo_updated(DialogId(user_id));
+ for_each_secret_chat_with_user(user_id, [messages_manager](SecretChatId secret_chat_id) {
+ messages_manager->on_dialog_photo_updated(DialogId(secret_chat_id));
+ });
+ u->is_photo_changed = false;
+ }
+ if (u->is_phone_number_changed) {
+ if (!u->phone_number.empty() && !td_->auth_manager_->is_bot()) {
+ resolved_phone_numbers_[u->phone_number] = user_id;
}
+ u->is_phone_number_changed = false;
}
- if (u->is_status_changed && user_id != get_my_id("update_user")) {
- if (u->was_online >= G()->unix_time_cached()) {
- auto left_time = u->was_online - G()->server_time_cached() + 2.0;
+ if (u->is_status_changed && user_id != get_my_id()) {
+ auto left_time = get_user_was_online(u, user_id) - G()->server_time_cached();
+ if (left_time >= 0 && left_time < 30 * 86400) {
+ left_time += 2.0; // to guarantee expiration
LOG(DEBUG) << "Set online timeout for " << user_id << " in " << left_time;
user_online_timeout_.set_timeout_in(user_id.get(), left_time);
} else {
@@ -5988,87 +10885,185 @@ void ContactsManager::update_user(User *u, UserId user_id, bool from_binlog, boo
user_online_timeout_.cancel_timeout(user_id.get());
}
}
+ if (!td_->auth_manager_->is_bot()) {
+ if (u->restriction_reasons.empty()) {
+ restricted_user_ids_.erase(user_id);
+ } else {
+ restricted_user_ids_.insert(user_id);
+ }
+ }
- u->is_name_changed = false;
- u->is_username_changed = false;
- u->is_photo_changed = false;
- u->is_outbound_link_changed = false;
+ auto unix_time = G()->unix_time();
+ auto effective_custom_emoji_id = u->emoji_status.get_effective_custom_emoji_id(u->is_premium, unix_time);
+ if (effective_custom_emoji_id != u->last_sent_emoji_status) {
+ u->last_sent_emoji_status = effective_custom_emoji_id;
+ u->is_changed = true;
+ } else {
+ u->need_save_to_database = true;
+ }
+ if (u->last_sent_emoji_status.is_valid()) {
+ auto until_date = u->emoji_status.get_until_date();
+ auto left_time = until_date - unix_time;
+ if (left_time >= 0 && left_time < 30 * 86400) {
+ LOG(DEBUG) << "Set emoji status timeout for " << user_id << " in " << left_time;
+ user_emoji_status_timeout_.set_timeout_in(user_id.get(), left_time);
+ } else {
+ user_emoji_status_timeout_.cancel_timeout(user_id.get());
+ }
+ } else {
+ user_emoji_status_timeout_.cancel_timeout(user_id.get());
+ }
if (u->is_deleted) {
td_->inline_queries_manager_->remove_recent_inline_bot(user_id, Promise<>());
- /*
- DialogId dialog_id(user_id);
- for (auto category : {TopDialogCategory::Correspondent, TopDialogCategory::BotPM, TopDialogCategory::BotInline}) {
- send_closure(G()->top_dialog_manager(), &TopDialogManager::delete_dialog, category, dialog_id,
- get_input_peer_user(user_id, AccessRights::Read));
- }
- */
}
- LOG(DEBUG) << "Update " << user_id << ": is_changed = " << u->is_changed
- << ", need_send_update = " << u->need_send_update << ", is_status_chaned = " << u->is_status_changed;
- if (u->is_changed || u->need_send_update) {
+ LOG(DEBUG) << "Update " << user_id << ": need_save_to_database = " << u->need_save_to_database
+ << ", is_changed = " << u->is_changed << ", is_status_changed = " << u->is_status_changed
+ << ", from_binlog = " << from_binlog << ", from_database = " << from_database;
+ u->need_save_to_database |= u->is_changed;
+ if (u->need_save_to_database) {
if (!from_database) {
u->is_saved = false;
}
- if (u->need_send_update) {
- send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateUser>(get_user_object(user_id, u)));
- u->need_send_update = false;
- u->is_status_changed = false;
- }
+ u->need_save_to_database = false;
+ }
+ if (u->is_changed) {
+ send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateUser>(get_user_object(user_id, u)));
u->is_changed = false;
+ u->is_status_changed = false;
+ u->is_update_user_sent = true;
}
if (u->is_status_changed) {
if (!from_database) {
u->is_status_saved = false;
}
+ CHECK(u->is_update_user_sent);
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateUserStatus>(user_id.get(), get_user_status_object(user_id, u)));
u->is_status_changed = false;
}
+ if (u->is_online_status_changed) {
+ update_user_online_member_count(u);
+ u->is_online_status_changed = false;
+ }
if (!from_database) {
save_user(u, user_id, from_binlog);
}
+
+ if (u->cache_version != User::CACHE_VERSION && !u->is_repaired && have_input_peer_user(u, AccessRights::Read) &&
+ !G()->close_flag()) {
+ u->is_repaired = true;
+
+ LOG(INFO) << "Repairing cache of " << user_id;
+ reload_user(user_id, Promise<Unit>());
+ }
}
void ContactsManager::update_chat(Chat *c, ChatId chat_id, bool from_binlog, bool from_database) {
CHECK(c != nullptr);
+ bool need_update_chat_full = false;
if (c->is_photo_changed) {
td_->messages_manager_->on_dialog_photo_updated(DialogId(chat_id));
+ c->is_photo_changed = false;
+
+ auto chat_full = get_chat_full(chat_id); // must not load ChatFull
+ if (chat_full != nullptr &&
+ !is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo)) {
+ on_update_chat_full_photo(chat_full, chat_id, Photo());
+ if (chat_full->is_update_chat_full_sent) {
+ need_update_chat_full = true;
+ }
+ if (c->photo.small_file_id.is_valid()) {
+ reload_chat_full(chat_id, Auto());
+ }
+ }
}
if (c->is_title_changed) {
td_->messages_manager_->on_dialog_title_updated(DialogId(chat_id));
+ c->is_title_changed = false;
+ }
+ if (c->is_default_permissions_changed) {
+ td_->messages_manager_->on_dialog_default_permissions_updated(DialogId(chat_id));
+ c->is_default_permissions_changed = false;
+ }
+ if (c->is_is_active_changed) {
+ update_dialogs_for_discussion(DialogId(chat_id), c->is_active && c->status.is_creator());
+ c->is_is_active_changed = false;
+ }
+ if (c->is_status_changed) {
+ if (!c->status.can_manage_invite_links()) {
+ td_->messages_manager_->drop_dialog_pending_join_requests(DialogId(chat_id));
+ }
+ c->is_status_changed = false;
+ }
+ if (c->is_noforwards_changed) {
+ td_->messages_manager_->on_dialog_has_protected_content_updated(DialogId(chat_id));
+ c->is_noforwards_changed = false;
+ }
+
+ if (need_update_chat_full) {
+ auto chat_full = get_chat_full(chat_id);
+ CHECK(chat_full != nullptr);
+ update_chat_full(chat_full, chat_id, "update_chat");
}
- c->is_photo_changed = false;
- c->is_title_changed = false;
- LOG(DEBUG) << "Update " << chat_id << ": is_changed = " << c->is_changed
- << ", need_send_update = " << c->need_send_update;
- if (c->is_changed || c->need_send_update) {
+ LOG(DEBUG) << "Update " << chat_id << ": need_save_to_database = " << c->need_save_to_database
+ << ", is_changed = " << c->is_changed;
+ c->need_save_to_database |= c->is_changed;
+ if (c->need_save_to_database) {
if (!from_database) {
c->is_saved = false;
}
+ c->need_save_to_database = false;
+ }
+ if (c->is_changed) {
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateBasicGroup>(get_basic_group_object(chat_id, c)));
c->is_changed = false;
- if (c->need_send_update) {
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateBasicGroup>(get_basic_group_object(chat_id, c)));
- c->need_send_update = false;
- }
+ c->is_update_basic_group_sent = true;
}
if (!from_database) {
save_chat(c, chat_id, from_binlog);
}
+
+ if (c->cache_version != Chat::CACHE_VERSION && !c->is_repaired && have_input_peer_chat(c, AccessRights::Read) &&
+ !G()->close_flag()) {
+ c->is_repaired = true;
+
+ LOG(INFO) << "Repairing cache of " << chat_id;
+ reload_chat(chat_id, Promise<Unit>());
+ }
}
void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from_binlog, bool from_database) {
CHECK(c != nullptr);
+ bool need_update_channel_full = false;
if (c->is_photo_changed) {
td_->messages_manager_->on_dialog_photo_updated(DialogId(channel_id));
+ c->is_photo_changed = false;
+
+ auto channel_full = get_channel_full(channel_id, true, "update_channel");
+ if (channel_full != nullptr &&
+ !is_same_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), channel_full->photo, c->photo)) {
+ on_update_channel_full_photo(channel_full, channel_id, Photo());
+ if (channel_full->is_update_channel_full_sent) {
+ need_update_channel_full = true;
+ }
+ if (c->photo.small_file_id.is_valid()) {
+ if (channel_full->expires_at > 0.0) {
+ channel_full->expires_at = 0.0;
+ channel_full->need_save_to_database = true;
+ }
+ send_get_channel_full_query(channel_full, channel_id, Auto(), "update_channel");
+ }
+ }
}
if (c->is_title_changed) {
td_->messages_manager_->on_dialog_title_updated(DialogId(channel_id));
+ c->is_title_changed = false;
}
if (c->is_status_changed) {
c->status.update_restrictions();
@@ -6083,75 +11078,138 @@ void ContactsManager::update_channel(Channel *c, ChannelId channel_id, bool from
} else {
channel_unban_timeout_.cancel_timeout(channel_id.get());
}
+
+ if (c->is_megagroup) {
+ update_dialogs_for_discussion(DialogId(channel_id), c->status.is_administrator() && c->status.can_pin_messages());
+ }
+ if (!c->status.is_member()) {
+ remove_inactive_channel(channel_id);
+ }
+ if (!c->status.can_manage_invite_links()) {
+ td_->messages_manager_->drop_dialog_pending_join_requests(DialogId(channel_id));
+ }
+ c->is_status_changed = false;
}
if (c->is_username_changed) {
- if (c->status.is_creator() && created_public_channels_inited_) {
- if (c->username.empty()) {
- created_public_channels_.erase(
- std::remove(created_public_channels_.begin(), created_public_channels_.end(), channel_id),
- created_public_channels_.end());
- } else {
- if (std::find(created_public_channels_.begin(), created_public_channels_.end(), channel_id) ==
- created_public_channels_.end()) {
- created_public_channels_.push_back(channel_id);
- }
- }
+ if (c->status.is_creator()) {
+ update_created_public_channels(c, channel_id);
+ }
+ c->is_username_changed = false;
+ }
+ if (c->is_default_permissions_changed) {
+ td_->messages_manager_->on_dialog_default_permissions_updated(DialogId(channel_id));
+ if (c->default_permissions !=
+ RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false)) {
+ remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)});
}
+ c->is_default_permissions_changed = false;
+ }
+ if (c->is_has_location_changed) {
+ if (c->status.is_creator()) {
+ update_created_public_channels(c, channel_id);
+ }
+ c->is_has_location_changed = false;
+ }
+ if (c->is_creator_changed) {
+ update_created_public_channels(c, channel_id);
+ c->is_creator_changed = false;
+ }
+ if (c->is_noforwards_changed) {
+ td_->messages_manager_->on_dialog_has_protected_content_updated(DialogId(channel_id));
+ c->is_noforwards_changed = false;
+ }
+
+ if (!td_->auth_manager_->is_bot()) {
+ if (c->restriction_reasons.empty()) {
+ restricted_channel_ids_.erase(channel_id);
+ } else {
+ restricted_channel_ids_.insert(channel_id);
+ }
+ }
+
+ if (!is_channel_public(c) && !c->has_linked_channel) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_send_message_as_dialog_id,
+ DialogId(channel_id), DialogId(), false);
}
- c->is_photo_changed = false;
- c->is_title_changed = false;
- c->is_status_changed = false;
- c->is_username_changed = false;
- LOG(DEBUG) << "Update " << channel_id << ": is_changed = " << c->is_changed
- << ", need_send_update = " << c->need_send_update;
- if (c->is_changed || c->need_send_update) {
+ if (need_update_channel_full) {
+ auto channel_full = get_channel_full(channel_id, true, "update_channel");
+ CHECK(channel_full != nullptr);
+ update_channel_full(channel_full, channel_id, "update_channel");
+ }
+
+ LOG(DEBUG) << "Update " << channel_id << ": need_save_to_database = " << c->need_save_to_database
+ << ", is_changed = " << c->is_changed;
+ c->need_save_to_database |= c->is_changed;
+ if (c->need_save_to_database) {
if (!from_database) {
c->is_saved = false;
}
+ c->need_save_to_database = false;
+ }
+ if (c->is_changed) {
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateSupergroup>(get_supergroup_object(channel_id, c)));
c->is_changed = false;
- if (c->need_send_update) {
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateSupergroup>(get_supergroup_object(channel_id, c)));
- c->need_send_update = false;
- }
+ c->is_update_supergroup_sent = true;
}
if (!from_database) {
save_channel(c, channel_id, from_binlog);
}
- bool have_read_access = have_input_peer_channel(c, AccessRights::Read);
+ bool have_read_access = have_input_peer_channel(c, channel_id, AccessRights::Read);
+ bool is_member = c->status.is_member();
if (c->had_read_access && !have_read_access) {
- send_closure_later(G()->messages_manager(), &MessagesManager::delete_dialog, DialogId(channel_id));
- } else if (c->was_member != c->status.is_member()) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id),
+ Promise<Unit>());
+ } else if (!from_database && c->was_member != is_member) {
DialogId dialog_id(channel_id);
send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update channel",
- true);
+ true, true);
}
c->had_read_access = have_read_access;
- c->was_member = c->status.is_member();
+ c->was_member = is_member;
+
+ if (c->cache_version != Channel::CACHE_VERSION && !c->is_repaired &&
+ have_input_peer_channel(c, channel_id, AccessRights::Read) && !G()->close_flag()) {
+ c->is_repaired = true;
+
+ LOG(INFO) << "Repairing cache of " << channel_id;
+ reload_channel(channel_id, Promise<Unit>());
+ }
}
void ContactsManager::update_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog,
bool from_database) {
CHECK(c != nullptr);
- LOG(DEBUG) << "Update " << secret_chat_id << ": is_changed = " << c->is_changed
- << ", need_send_update = " << c->need_send_update;
- if (c->is_changed || c->need_send_update) {
+ LOG(DEBUG) << "Update " << secret_chat_id << ": need_save_to_database = " << c->need_save_to_database
+ << ", is_changed = " << c->is_changed;
+ c->need_save_to_database |= c->is_changed;
+ if (c->need_save_to_database) {
if (!from_database) {
c->is_saved = false;
}
- c->is_changed = false;
- if (c->need_send_update) {
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateSecretChat>(get_secret_chat_object(secret_chat_id, c)));
- c->need_send_update = false;
- }
+ c->need_save_to_database = false;
DialogId dialog_id(secret_chat_id);
send_closure_later(G()->messages_manager(), &MessagesManager::force_create_dialog, dialog_id, "update secret chat",
- true);
+ true, true);
+ if (c->is_state_changed) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_update_secret_chat_state, secret_chat_id,
+ c->state);
+ c->is_state_changed = false;
+ }
+ if (c->is_ttl_changed) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_message_ttl,
+ DialogId(secret_chat_id), MessageTtl(c->ttl));
+ c->is_ttl_changed = false;
+ }
+ }
+ if (c->is_changed) {
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateSecretChat>(get_secret_chat_object(secret_chat_id, c)));
+ c->is_changed = false;
}
if (!from_database) {
@@ -6159,360 +11217,949 @@ void ContactsManager::update_secret_chat(SecretChat *c, SecretChatId secret_chat
}
}
-void ContactsManager::update_user_full(UserFull *user_full, UserId user_id) {
+void ContactsManager::update_user_full(UserFull *user_full, UserId user_id, const char *source, bool from_database) {
CHECK(user_full != nullptr);
- if (user_full->is_changed) {
- user_full->is_changed = false;
- if (user_full->is_inited) {
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateUserFullInfo>(get_user_id_object(user_id, "updateUserFullInfo"),
- get_user_full_info_object(user_id, user_full)));
+ unavailable_user_fulls_.erase(user_id); // don't needed anymore
+ if (user_full->is_common_chat_count_changed) {
+ td_->messages_manager_->drop_common_dialogs_cache(user_id);
+ user_full->is_common_chat_count_changed = false;
+ }
+ if (user_full->are_files_changed) {
+ auto file_ids = photo_get_file_ids(user_full->description_photo);
+ if (user_full->description_animation_file_id.is_valid()) {
+ file_ids.push_back(user_full->description_animation_file_id);
+ }
+ if (user_full->registered_file_ids != file_ids) {
+ auto &file_source_id = user_full->file_source_id;
+ if (!file_source_id.is_valid()) {
+ file_source_id = user_full_file_source_ids_.get(user_id);
+ if (file_source_id.is_valid()) {
+ VLOG(file_references) << "Move " << file_source_id << " inside of " << user_id;
+ user_full_file_source_ids_.erase(user_id);
+ } else {
+ VLOG(file_references) << "Need to create new file source for full " << user_id;
+ file_source_id = td_->file_reference_manager_->create_user_full_file_source(user_id);
+ }
+ }
+
+ td_->file_manager_->change_files_source(file_source_id, user_full->registered_file_ids, file_ids);
+ user_full->registered_file_ids = std::move(file_ids);
}
}
+
+ user_full->need_send_update |= user_full->is_changed;
+ user_full->need_save_to_database |= user_full->is_changed;
+ user_full->is_changed = false;
+ if (user_full->need_send_update || user_full->need_save_to_database) {
+ LOG(INFO) << "Update full " << user_id << " from " << source;
+ }
+ if (user_full->need_send_update) {
+ {
+ auto u = get_user(user_id);
+ CHECK(u == nullptr || u->is_update_user_sent);
+ }
+ if (!user_full->is_update_user_full_sent) {
+ LOG(ERROR) << "Send partial updateUserFullInfo for " << user_id << " from " << source;
+ user_full->is_update_user_full_sent = true;
+ }
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateUserFullInfo>(get_user_id_object(user_id, "updateUserFullInfo"),
+ get_user_full_info_object(user_id, user_full)));
+ user_full->need_send_update = false;
+ }
+ if (user_full->need_save_to_database) {
+ if (!from_database) {
+ save_user_full(user_full, user_id);
+ }
+ user_full->need_save_to_database = false;
+ }
}
-void ContactsManager::update_chat_full(ChatFull *chat_full, ChatId chat_id) {
+void ContactsManager::update_chat_full(ChatFull *chat_full, ChatId chat_id, const char *source, bool from_database) {
CHECK(chat_full != nullptr);
- if (chat_full->is_changed) {
- vector<UserId> administrator_user_ids;
- for (auto &participant : chat_full->participants) {
- if (participant.status.is_administrator()) {
- administrator_user_ids.push_back(participant.user_id);
+ unavailable_chat_fulls_.erase(chat_id); // don't needed anymore
+
+ chat_full->need_send_update |= chat_full->is_changed;
+ chat_full->need_save_to_database |= chat_full->is_changed;
+ chat_full->is_changed = false;
+ if (chat_full->need_send_update || chat_full->need_save_to_database) {
+ LOG(INFO) << "Update full " << chat_id << " from " << source;
+ }
+ if (chat_full->need_send_update) {
+ vector<DialogAdministrator> administrators;
+ vector<UserId> bot_user_ids;
+ for (const auto &participant : chat_full->participants) {
+ if (participant.status_.is_administrator() && participant.dialog_id_.get_type() == DialogType::User) {
+ administrators.emplace_back(participant.dialog_id_.get_user_id(), participant.status_.get_rank(),
+ participant.status_.is_creator());
+ }
+ if (participant.dialog_id_.get_type() == DialogType::User) {
+ auto user_id = participant.dialog_id_.get_user_id();
+ if (is_user_bot(user_id)) {
+ bot_user_ids.push_back(user_id);
+ }
}
}
- on_update_dialog_administrators(DialogId(chat_id), std::move(administrator_user_ids), chat_full->version != -1);
+ td::remove_if(chat_full->bot_commands, [&bot_user_ids](const BotCommands &commands) {
+ return !td::contains(bot_user_ids, commands.get_bot_user_id());
+ });
- chat_full->is_changed = false;
+ on_update_dialog_administrators(DialogId(chat_id), std::move(administrators), chat_full->version != -1,
+ from_database);
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(chat_id),
+ std::move(bot_user_ids), from_database);
+
+ {
+ Chat *c = get_chat(chat_id);
+ CHECK(c == nullptr || c->is_update_basic_group_sent);
+ }
+ if (!chat_full->is_update_chat_full_sent) {
+ LOG(ERROR) << "Send partial updateBasicGroupFullInfo for " << chat_id << " from " << source;
+ chat_full->is_update_chat_full_sent = true;
+ }
send_closure(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateBasicGroupFullInfo>(get_basic_group_id_object(chat_id, "update_chat_full"),
get_basic_group_full_info_object(chat_full)));
+ chat_full->need_send_update = false;
+ }
+ if (chat_full->need_save_to_database) {
+ if (!from_database) {
+ save_chat_full(chat_full, chat_id);
+ }
+ chat_full->need_save_to_database = false;
}
}
-void ContactsManager::update_channel_full(ChannelFull *channel_full, ChannelId channel_id) {
+void ContactsManager::update_channel_full(ChannelFull *channel_full, ChannelId channel_id, const char *source,
+ bool from_database) {
CHECK(channel_full != nullptr);
- if (channel_full->is_changed) {
- if (channel_full->participant_count < channel_full->administrator_count) {
- channel_full->administrator_count = channel_full->participant_count;
+ unavailable_channel_fulls_.erase(channel_id); // don't needed anymore
+
+ CHECK(channel_full->participant_count >= channel_full->administrator_count);
+
+ if (channel_full->is_slow_mode_next_send_date_changed) {
+ auto now = G()->server_time();
+ if (channel_full->slow_mode_next_send_date > now + 3601) {
+ channel_full->slow_mode_next_send_date = static_cast<int32>(now) + 3601;
+ }
+ if (channel_full->slow_mode_next_send_date <= now) {
+ channel_full->slow_mode_next_send_date = 0;
+ }
+ if (channel_full->slow_mode_next_send_date == 0) {
+ slow_mode_delay_timeout_.cancel_timeout(channel_id.get());
+ } else {
+ slow_mode_delay_timeout_.set_timeout_in(channel_id.get(), channel_full->slow_mode_next_send_date - now + 0.002);
+ }
+ channel_full->is_slow_mode_next_send_date_changed = false;
+ }
+
+ if (channel_full->need_save_to_database) {
+ channel_full->is_changed |= td::remove_if(
+ channel_full->bot_commands, [bot_user_ids = &channel_full->bot_user_ids](const BotCommands &commands) {
+ return !td::contains(*bot_user_ids, commands.get_bot_user_id());
+ });
+ }
+
+ channel_full->need_send_update |= channel_full->is_changed;
+ channel_full->need_save_to_database |= channel_full->is_changed;
+ channel_full->is_changed = false;
+ if (channel_full->need_send_update || channel_full->need_save_to_database) {
+ LOG(INFO) << "Update full " << channel_id << " from " << source;
+ }
+ if (channel_full->need_send_update) {
+ if (channel_full->linked_channel_id.is_valid()) {
+ td_->messages_manager_->force_create_dialog(DialogId(channel_full->linked_channel_id), "update_channel_full",
+ true);
+ }
+
+ {
+ Channel *c = get_channel(channel_id);
+ CHECK(c == nullptr || c->is_update_supergroup_sent);
+ }
+ if (!channel_full->is_update_channel_full_sent) {
+ LOG(ERROR) << "Send partial updateSupergroupFullInfo for " << channel_id << " from " << source;
+ channel_full->is_update_channel_full_sent = true;
}
- channel_full->is_changed = false;
send_closure(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateSupergroupFullInfo>(get_supergroup_id_object(channel_id, "update_channel_full"),
- get_channel_full_info_object(channel_full)));
+ get_supergroup_full_info_object(channel_full, channel_id)));
+ channel_full->need_send_update = false;
+ }
+ if (channel_full->need_save_to_database) {
+ if (!from_database) {
+ save_channel_full(channel_full, channel_id);
+ }
+ channel_full->need_save_to_database = false;
}
}
-void ContactsManager::on_get_users(vector<tl_object_ptr<telegram_api::User>> &&users) {
+void ContactsManager::on_get_users(vector<tl_object_ptr<telegram_api::User>> &&users, const char *source) {
for (auto &user : users) {
- on_get_user(std::move(user));
+ on_get_user(std::move(user), source);
}
}
-void ContactsManager::on_get_user_full(tl_object_ptr<telegram_api::userFull> &&user_full) {
- UserId user_id = get_user_id(user_full->user_);
- if (!user_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << user_id;
- return;
- }
+void ContactsManager::on_get_user_full(tl_object_ptr<telegram_api::userFull> &&user) {
+ LOG(INFO) << "Receive " << to_string(user);
- on_get_user(std::move(user_full->user_));
- const User *u = get_user(user_id);
+ UserId user_id(user->id_);
+ User *u = get_user(user_id);
if (u == nullptr) {
+ LOG(ERROR) << "Failed to find " << user_id;
return;
}
- on_update_user_links(user_id, std::move(user_full->link_->my_link_), std::move(user_full->link_->foreign_link_));
- td_->messages_manager_->on_update_notify_settings(DialogId(user_id).get(), std::move(user_full->notify_settings_));
-
- UserFull *user = &users_full_[user_id];
- user->expires_at = Time::now() + USER_FULL_EXPIRE_TIME;
- user->is_inited = true;
+ apply_pending_user_photo(u, user_id);
- on_update_user_full_is_blocked(user, user_id, (user_full->flags_ & USER_FULL_FLAG_IS_BLOCKED) != 0);
+ td_->messages_manager_->on_update_dialog_notify_settings(DialogId(user_id), std::move(user->notify_settings_),
+ "on_get_user_full");
- bool can_be_called = user_full->phone_calls_available_ && !user_full->phone_calls_private_;
- bool has_private_calls = user_full->phone_calls_private_;
- if (user->can_be_called != can_be_called || user->has_private_calls != has_private_calls ||
- user->about != user_full->about_ || user->common_chat_count != user_full->common_chats_count_) {
- user->can_be_called = can_be_called;
- user->has_private_calls = has_private_calls;
- user->about = std::move(user_full->about_);
- user->common_chat_count = user_full->common_chats_count_;
+ td_->messages_manager_->on_update_dialog_theme_name(DialogId(user_id), std::move(user->theme_emoticon_));
- user->is_changed = true;
+ {
+ MessageId pinned_message_id;
+ if ((user->flags_ & USER_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) {
+ pinned_message_id = MessageId(ServerMessageId(user->pinned_msg_id_));
+ }
+ td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(user_id), pinned_message_id);
+ }
+ {
+ FolderId folder_id;
+ if ((user->flags_ & USER_FULL_FLAG_HAS_FOLDER_ID) != 0) {
+ folder_id = FolderId(user->folder_id_);
+ }
+ td_->messages_manager_->on_update_dialog_folder_id(DialogId(user_id), folder_id);
+ }
+ td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(
+ DialogId(user_id), (user->flags_ & USER_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0);
+ {
+ MessageTtl message_ttl;
+ if ((user->flags_ & USER_FULL_FLAG_HAS_MESSAGE_TTL) != 0) {
+ message_ttl = MessageTtl(user->ttl_period_);
+ }
+ td_->messages_manager_->on_update_dialog_message_ttl(DialogId(user_id), message_ttl);
}
- int32 photo_id =
- user_full->profile_photo_ == nullptr ? telegram_api::photoEmpty::ID : user_full->profile_photo_->get_id();
- if (photo_id == telegram_api::photoEmpty::ID) {
- user->photo_count = 0;
- user->photos_offset = 0;
- user->photos.clear();
- } else {
- CHECK(photo_id == telegram_api::photo::ID);
+ UserFull *user_full = add_user_full(user_id);
+ user_full->expires_at = Time::now() + USER_FULL_EXPIRE_TIME;
- // Photo profile_photo =
- // get_photo(td_->file_manager_.get(), move_tl_object_as<telegram_api::photo>(user_full->profile_photo_));
+ {
+ bool is_blocked = (user->flags_ & USER_FULL_FLAG_IS_BLOCKED) != 0;
+ on_update_user_full_is_blocked(user_full, user_id, is_blocked);
+ td_->messages_manager_->on_update_dialog_is_blocked(DialogId(user_id), is_blocked);
}
- if ((user_full->flags_ & USER_FULL_FLAG_HAS_BOT_INFO) != 0 && !u->is_deleted) {
- on_update_user_full_bot_info(user, user_id, u->bot_info_version, std::move(user_full->bot_info_));
+ on_update_user_full_common_chat_count(user_full, user_id, user->common_chats_count_);
+ on_update_user_full_need_phone_number_privacy_exception(user_full, user_id,
+ user->settings_->need_contacts_exception_);
+
+ bool can_pin_messages = user->can_pin_message_;
+ if (user_full->can_pin_messages != can_pin_messages) {
+ user_full->can_pin_messages = can_pin_messages;
+ user_full->is_changed = true;
}
- update_user_full(user, user_id);
-}
-void ContactsManager::on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count,
- vector<tl_object_ptr<telegram_api::Photo>> photos) {
- int32 photo_count = narrow_cast<int32>(photos.size());
- if (total_count < 0 || total_count < photo_count) {
- LOG(ERROR) << "Wrong photos total_count " << total_count << ". Receive " << photo_count << " photos";
- total_count = photo_count;
+ bool can_be_called = user->phone_calls_available_ && !user->phone_calls_private_;
+ bool supports_video_calls = user->video_calls_available_ && !user->phone_calls_private_;
+ bool has_private_calls = user->phone_calls_private_;
+ bool voice_messages_forbidden = u->is_premium ? user->voice_messages_forbidden_ : false;
+ auto premium_gift_options = get_premium_gift_options(std::move(user->premium_gifts_));
+ AdministratorRights group_administrator_rights(user->bot_group_admin_rights_, ChannelType::Megagroup);
+ AdministratorRights broadcast_administrator_rights(user->bot_broadcast_admin_rights_, ChannelType::Broadcast);
+ if (user_full->can_be_called != can_be_called || user_full->supports_video_calls != supports_video_calls ||
+ user_full->has_private_calls != has_private_calls ||
+ user_full->group_administrator_rights != group_administrator_rights ||
+ user_full->broadcast_administrator_rights != broadcast_administrator_rights ||
+ user_full->premium_gift_options != premium_gift_options ||
+ user_full->voice_messages_forbidden != voice_messages_forbidden) {
+ user_full->can_be_called = can_be_called;
+ user_full->supports_video_calls = supports_video_calls;
+ user_full->has_private_calls = has_private_calls;
+ user_full->group_administrator_rights = group_administrator_rights;
+ user_full->broadcast_administrator_rights = broadcast_administrator_rights;
+ user_full->premium_gift_options = std::move(premium_gift_options);
+ user_full->voice_messages_forbidden = voice_messages_forbidden;
+
+ user_full->is_changed = true;
}
- LOG_IF(ERROR, limit < photo_count) << "Requested not more than " << limit << " photos, but " << photo_count
- << " returned";
+ if (user_full->private_forward_name != user->private_forward_name_) {
+ if (user_full->private_forward_name.empty() != user->private_forward_name_.empty()) {
+ user_full->is_changed = true;
+ }
+ user_full->private_forward_name = std::move(user->private_forward_name_);
+ user_full->need_save_to_database = true;
+ }
+ if (user_full->about != user->about_) {
+ user_full->about = std::move(user->about_);
+ user_full->is_changed = true;
+ td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, true);
+ }
+ string description;
+ Photo description_photo;
+ FileId description_animation_file_id;
+ if (user->bot_info_ != nullptr && !td_->auth_manager_->is_bot()) {
+ description = std::move(user->bot_info_->description_);
+ description_photo =
+ get_photo(td_->file_manager_.get(), std::move(user->bot_info_->description_photo_), DialogId(user_id));
+ auto document = std::move(user->bot_info_->description_document_);
+ if (document != nullptr) {
+ int32 document_id = document->get_id();
+ if (document_id == telegram_api::document::ID) {
+ auto parsed_document = td_->documents_manager_->on_get_document(
+ move_tl_object_as<telegram_api::document>(document), DialogId(user_id));
+ if (parsed_document.type == Document::Type::Animation) {
+ description_animation_file_id = parsed_document.file_id;
+ } else {
+ LOG(ERROR) << "Receive non-animation document in bot description";
+ }
+ }
+ }
- UserFull *user = &users_full_[user_id];
- user->photo_count = total_count;
- CHECK(user->getting_photos_now);
- user->getting_photos_now = false;
+ on_update_user_full_commands(user_full, user_id, std::move(user->bot_info_->commands_));
+ on_update_user_full_menu_button(user_full, user_id, std::move(user->bot_info_->menu_button_));
+ }
+ if (user_full->description != description) {
+ user_full->description = std::move(description);
+ user_full->is_changed = true;
+ }
+ if (user_full->description_photo != description_photo ||
+ user_full->description_animation_file_id != description_animation_file_id) {
+ user_full->description_photo = std::move(description_photo);
+ user_full->description_animation_file_id = description_animation_file_id;
+ user_full->are_files_changed = true;
+ user_full->is_changed = true;
+ }
- if (user->photos_offset == -1) {
- user->photos_offset = 0;
- CHECK(user->photos.empty());
+ auto photo = get_photo(td_->file_manager_.get(), std::move(user->profile_photo_), DialogId(user_id));
+ // do_update_user_photo should be a no-op if server sent consistent data
+ do_update_user_photo(u, user_id, as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, photo), false,
+ "on_get_user_full");
+ if (photo != user_full->photo) {
+ user_full->photo = std::move(photo);
+ user_full->is_changed = true;
+ }
+ if (user_full->photo.is_empty()) {
+ drop_user_photos(user_id, true, false, "on_get_user_full");
+ } else {
+ register_user_photo(u, user_id, user_full->photo);
}
- if (offset != narrow_cast<int32>(user->photos.size()) + user->photos_offset) {
- LOG(INFO) << "Inappropriate offset to append " << user_id << " profile photos to cache: offset = " << offset
- << ", current_offset = " << user->photos_offset << ", photo_count = " << user->photos.size();
- user->photos.clear();
- user->photos_offset = offset;
+ // User must be updated before UserFull
+ if (u->is_changed) {
+ LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << user_id;
+ update_user(u, user_id);
}
- for (auto &photo : photos) {
- int32 photo_id = photo->get_id();
- if (photo_id == telegram_api::photoEmpty::ID) {
- LOG(ERROR) << "Have got empty profile photo in getUserPhotos request for " << user_id << " with offset " << offset
- << " and limit " << limit << ". Receive " << photo_count << " photos out of " << total_count
- << " photos";
- continue;
- }
- CHECK(photo_id == telegram_api::photo::ID);
+ user_full->is_update_user_full_sent = true;
+ update_user_full(user_full, user_id, "on_get_user_full");
- user->photos.push_back(
- get_photo(td_->file_manager_.get(), move_tl_object_as<telegram_api::photo>(photo), DialogId()));
+ // update peer settings after UserFull is created and updated to not update twice need_phone_number_privacy_exception
+ td_->messages_manager_->on_get_peer_settings(DialogId(user_id), std::move(user->settings_));
+}
+
+ContactsManager::UserPhotos *ContactsManager::add_user_photos(UserId user_id) {
+ auto &user_photos_ptr = user_photos_[user_id];
+ if (user_photos_ptr == nullptr) {
+ user_photos_ptr = make_unique<UserPhotos>();
}
+ return user_photos_ptr.get();
}
-bool ContactsManager::on_update_bot_info(tl_object_ptr<telegram_api::botInfo> &&bot_info) {
- CHECK(bot_info != nullptr);
- UserId user_id(bot_info->user_id_);
- if (!user_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << user_id;
- return false;
+void ContactsManager::on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count,
+ vector<tl_object_ptr<telegram_api::Photo>> photos) {
+ auto photo_count = narrow_cast<int32>(photos.size());
+ int32 min_total_count = (offset >= 0 && photo_count > 0 ? offset : 0) + photo_count;
+ if (total_count < min_total_count) {
+ LOG(ERROR) << "Receive wrong photos total_count " << total_count << " for user " << user_id << ": receive "
+ << photo_count << " photos with offset " << offset;
+ total_count = min_total_count;
}
+ LOG_IF(ERROR, limit < photo_count) << "Requested not more than " << limit << " photos, but " << photo_count
+ << " received";
- const User *u = get_user_force(user_id);
+ User *u = get_user(user_id);
if (u == nullptr) {
- LOG(ERROR) << "Have no " << user_id;
- return false;
+ LOG(ERROR) << "Can't find " << user_id;
+ return;
}
- if (u->is_deleted) {
- return false;
+ if (offset == -1) {
+ // from reload_user_profile_photo
+ CHECK(limit == 1);
+ for (auto &photo_ptr : photos) {
+ if (photo_ptr->get_id() == telegram_api::photo::ID) {
+ auto server_photo = telegram_api::move_object_as<telegram_api::photo>(photo_ptr);
+ if (server_photo->id_ == u->photo.id) {
+ auto profile_photo = convert_photo_to_profile_photo(server_photo);
+ if (profile_photo) {
+ LOG_IF(ERROR, u->access_hash == -1) << "Receive profile photo of " << user_id << " without access hash";
+ get_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, std::move(profile_photo));
+ } else {
+ LOG(ERROR) << "Failed to get profile photo from " << to_string(server_photo);
+ }
+ }
+
+ auto photo = get_photo(td_->file_manager_.get(), std::move(server_photo), DialogId(user_id));
+ register_user_photo(u, user_id, photo);
+ }
+ }
+ return;
}
- UserFull *user_full = &users_full_[user_id];
- bool result = on_update_user_full_bot_info(user_full, user_id, u->bot_info_version, std::move(bot_info));
- update_user_full(user_full, user_id);
- return result;
-}
+ LOG(INFO) << "Receive " << photo_count << " photos of " << user_id << " out of " << total_count << " with offset "
+ << offset << " and limit " << limit;
+ UserPhotos *user_photos = add_user_photos(user_id);
+ user_photos->count = total_count;
+ CHECK(user_photos->getting_now);
+ user_photos->getting_now = false;
-bool ContactsManager::on_update_user_full_bot_info(UserFull *user_full, UserId user_id, int32 bot_info_version,
- tl_object_ptr<telegram_api::botInfo> &&bot_info) {
- CHECK(user_full != nullptr);
- CHECK(bot_info != nullptr);
+ if (user_photos->offset == -1) {
+ user_photos->offset = 0;
+ CHECK(user_photos->photos.empty());
+ }
- if (user_full->bot_info != nullptr && user_full->bot_info->version > bot_info_version) {
- LOG(WARNING) << "Ignore outdated version of BotInfo for " << user_id << " with version " << bot_info_version
- << ", current version is " << user_full->bot_info->version;
- return false;
+ if (offset != narrow_cast<int32>(user_photos->photos.size()) + user_photos->offset) {
+ LOG(INFO) << "Inappropriate offset to append " << user_id << " profile photos to cache: offset = " << offset
+ << ", current_offset = " << user_photos->offset << ", photo_count = " << user_photos->photos.size();
+ user_photos->photos.clear();
+ user_photos->offset = offset;
}
- if (user_full->bot_info != nullptr && user_full->bot_info->version == bot_info_version) {
- LOG(DEBUG) << "Ignore already known version of BotInfo for " << user_id << " with version " << bot_info_version;
- return false;
+
+ for (auto &photo : photos) {
+ auto user_photo = get_photo(td_->file_manager_.get(), std::move(photo), DialogId(user_id));
+ if (user_photo.is_empty()) {
+ LOG(ERROR) << "Receive empty profile photo in getUserPhotos request for " << user_id << " with offset " << offset
+ << " and limit " << limit << ". Receive " << photo_count << " photos out of " << total_count
+ << " photos";
+ user_photos->count--;
+ CHECK(user_photos->count >= 0);
+ continue;
+ }
+
+ user_photos->photos.push_back(std::move(user_photo));
+ register_user_photo(u, user_id, user_photos->photos.back());
+ }
+ if (user_photos->offset > user_photos->count) {
+ user_photos->offset = user_photos->count;
+ user_photos->photos.clear();
}
- vector<std::pair<string, string>> commands;
- commands.reserve(bot_info->commands_.size());
- for (auto &command : bot_info->commands_) {
- commands.emplace_back(std::move(command->command_), std::move(command->description_));
+ auto known_photo_count = narrow_cast<int32>(user_photos->photos.size());
+ if (user_photos->offset + known_photo_count > user_photos->count) {
+ user_photos->photos.resize(user_photos->count - user_photos->offset);
}
- user_full->bot_info = make_unique<BotInfo>(bot_info_version, std::move(bot_info->description_), std::move(commands));
- user_full->is_changed = true;
- return true;
}
-void ContactsManager::on_get_chat(tl_object_ptr<telegram_api::Chat> &&chat) {
- LOG(DEBUG) << "Receive " << to_string(chat);
- downcast_call(*chat, OnChatUpdate(this));
+void ContactsManager::on_get_chat(tl_object_ptr<telegram_api::Chat> &&chat, const char *source) {
+ LOG(DEBUG) << "Receive from " << source << ' ' << to_string(chat);
+ downcast_call(*chat, [this, source](auto &c) { this->on_chat_update(c, source); });
}
-void ContactsManager::on_get_chats(vector<tl_object_ptr<telegram_api::Chat>> &&chats) {
+void ContactsManager::on_get_chats(vector<tl_object_ptr<telegram_api::Chat>> &&chats, const char *source) {
for (auto &chat : chats) {
auto constuctor_id = chat->get_id();
if (constuctor_id == telegram_api::channel::ID || constuctor_id == telegram_api::channelForbidden::ID) {
// apply info about megagroups before corresponding chats
- on_get_chat(std::move(chat));
+ on_get_chat(std::move(chat), source);
chat = nullptr;
}
}
for (auto &chat : chats) {
if (chat != nullptr) {
- on_get_chat(std::move(chat));
+ on_get_chat(std::move(chat), source);
chat = nullptr;
}
}
}
-void ContactsManager::on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&chat_full_ptr) {
+vector<BotCommands> ContactsManager::get_bot_commands(vector<tl_object_ptr<telegram_api::botInfo>> &&bot_infos,
+ const vector<DialogParticipant> *participants) {
+ vector<BotCommands> result;
+ if (td_->auth_manager_->is_bot()) {
+ return result;
+ }
+ for (auto &bot_info : bot_infos) {
+ if (bot_info->commands_.empty()) {
+ continue;
+ }
+
+ auto user_id = UserId(bot_info->user_id_);
+ if (!have_user_force(user_id)) {
+ LOG(ERROR) << "Receive unknown " << user_id;
+ continue;
+ }
+ if (!is_user_bot(user_id)) {
+ if (!is_user_deleted(user_id)) {
+ LOG(ERROR) << "Receive non-bot " << user_id;
+ }
+ continue;
+ }
+ if (participants != nullptr) {
+ bool is_participant = false;
+ for (auto &participant : *participants) {
+ if (participant.dialog_id_ == DialogId(user_id)) {
+ is_participant = true;
+ break;
+ }
+ }
+ if (!is_participant) {
+ LOG(ERROR) << "Skip commands of non-member bot " << user_id;
+ continue;
+ }
+ }
+ result.emplace_back(user_id, std::move(bot_info->commands_));
+ }
+ return result;
+}
+
+void ContactsManager::on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&chat_full_ptr, Promise<Unit> &&promise) {
+ LOG(INFO) << "Receive " << to_string(chat_full_ptr);
if (chat_full_ptr->get_id() == telegram_api::chatFull::ID) {
- auto chat_full = move_tl_object_as<telegram_api::chatFull>(chat_full_ptr);
- ChatId chat_id(chat_full->id_);
+ auto chat = move_tl_object_as<telegram_api::chatFull>(chat_full_ptr);
+ ChatId chat_id(chat->id_);
if (!chat_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << chat_id;
- return;
+ return promise.set_value(Unit());
+ }
+
+ Chat *c = get_chat(chat_id);
+ if (c == nullptr) {
+ LOG(ERROR) << "Can't find " << chat_id;
+ return promise.set_value(Unit());
+ }
+ {
+ MessageId pinned_message_id;
+ if ((chat->flags_ & CHAT_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) {
+ pinned_message_id = MessageId(ServerMessageId(chat->pinned_msg_id_));
+ }
+ if (c->version >= c->pinned_message_version) {
+ LOG(INFO) << "Receive pinned " << pinned_message_id << " in " << chat_id << " with version " << c->version
+ << ". Current version is " << c->pinned_message_version;
+ td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(chat_id), pinned_message_id);
+ if (c->version > c->pinned_message_version) {
+ c->pinned_message_version = c->version;
+ c->need_save_to_database = true;
+ update_chat(c, chat_id);
+ }
+ }
+ }
+ {
+ FolderId folder_id;
+ if ((chat->flags_ & CHAT_FULL_FLAG_HAS_FOLDER_ID) != 0) {
+ folder_id = FolderId(chat->folder_id_);
+ }
+ td_->messages_manager_->on_update_dialog_folder_id(DialogId(chat_id), folder_id);
+ }
+ td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(
+ DialogId(chat_id), (chat->flags_ & CHAT_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0);
+ {
+ InputGroupCallId input_group_call_id;
+ if (chat->call_ != nullptr) {
+ input_group_call_id = InputGroupCallId(chat->call_);
+ }
+ td_->messages_manager_->on_update_dialog_group_call_id(DialogId(chat_id), input_group_call_id);
+ }
+ {
+ DialogId default_join_group_call_as_dialog_id;
+ if (chat->groupcall_default_join_as_ != nullptr) {
+ default_join_group_call_as_dialog_id = DialogId(chat->groupcall_default_join_as_);
+ }
+ // use send closure later to not create synchronously default_join_group_call_as_dialog_id
+ send_closure_later(G()->messages_manager(),
+ &MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id, DialogId(chat_id),
+ default_join_group_call_as_dialog_id, false);
+ }
+ {
+ MessageTtl message_ttl;
+ if ((chat->flags_ & CHAT_FULL_FLAG_HAS_MESSAGE_TTL) != 0) {
+ message_ttl = MessageTtl(chat->ttl_period_);
+ }
+ td_->messages_manager_->on_update_dialog_message_ttl(DialogId(chat_id), message_ttl);
}
- ChatFull *chat = &chats_full_[chat_id];
- on_update_chat_full_invite_link(chat, std::move(chat_full->exported_invite_));
+ ChatFull *chat_full = add_chat_full(chat_id);
+ on_update_chat_full_invite_link(chat_full, std::move(chat->exported_invite_));
+ auto photo = get_photo(td_->file_manager_.get(), std::move(chat->chat_photo_), DialogId(chat_id));
+ // on_update_chat_photo should be a no-op if server sent consistent data
+ on_update_chat_photo(c, chat_id, as_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), 0, photo), false);
+ on_update_chat_full_photo(chat_full, chat_id, std::move(photo));
+ if (chat_full->description != chat->about_) {
+ chat_full->description = std::move(chat->about_);
+ chat_full->is_changed = true;
+ td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, true);
+ }
+ if (chat_full->can_set_username != chat->can_set_username_) {
+ chat_full->can_set_username = chat->can_set_username_;
+ chat_full->is_changed = true;
+ }
- // Ignoring chat_full->photo
+ on_get_chat_participants(std::move(chat->participants_), false);
+ td_->messages_manager_->on_update_dialog_notify_settings(DialogId(chat_id), std::move(chat->notify_settings_),
+ "on_get_chat_full");
- for (auto &bot_info : chat_full->bot_info_) {
- if (on_update_bot_info(std::move(bot_info))) {
- chat->is_changed = true;
- }
+ td_->messages_manager_->on_update_dialog_available_reactions(DialogId(chat_id),
+ std::move(chat->available_reactions_));
+
+ td_->messages_manager_->on_update_dialog_theme_name(DialogId(chat_id), std::move(chat->theme_emoticon_));
+
+ td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(chat_id), chat->requests_pending_,
+ std::move(chat->recent_requesters_));
+
+ auto bot_commands = get_bot_commands(std::move(chat->bot_info_), &chat_full->participants);
+ if (chat_full->bot_commands != bot_commands) {
+ chat_full->bot_commands = std::move(bot_commands);
+ chat_full->is_changed = true;
}
- on_get_chat_participants(std::move(chat_full->participants_));
- td_->messages_manager_->on_update_notify_settings(DialogId(chat_id).get(), std::move(chat_full->notify_settings_));
+ if (c->is_changed) {
+ LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << chat_id;
+ update_chat(c, chat_id);
+ }
- update_chat_full(chat, chat_id);
+ chat_full->is_update_chat_full_sent = true;
+ update_chat_full(chat_full, chat_id, "on_get_chat_full");
} else {
CHECK(chat_full_ptr->get_id() == telegram_api::channelFull::ID);
- auto channel_full = move_tl_object_as<telegram_api::channelFull>(chat_full_ptr);
- ChannelId channel_id(channel_full->id_);
+ auto channel = move_tl_object_as<telegram_api::channelFull>(chat_full_ptr);
+ ChannelId channel_id(channel->id_);
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id;
- return;
+ return promise.set_value(Unit());
}
- td_->messages_manager_->on_update_notify_settings(DialogId(channel_id).get(),
- std::move(channel_full->notify_settings_));
+ invalidated_channels_full_.erase(channel_id);
- // Ignoring channel_full->photo
+ if (!G()->close_flag()) {
+ auto channel_full = get_channel_full(channel_id, true, "on_get_channel_full");
+ if (channel_full != nullptr) {
+ if (channel_full->repair_request_version != 0 &&
+ channel_full->repair_request_version < channel_full->speculative_version) {
+ LOG(INFO) << "Receive ChannelFull with request version " << channel_full->repair_request_version
+ << ", but current speculative version is " << channel_full->speculative_version;
- if (!have_channel(channel_id)) {
- LOG(ERROR) << channel_id << " not found";
- return;
- }
+ channel_full->repair_request_version = channel_full->speculative_version;
- auto participant_count =
- (channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT) != 0 ? channel_full->participants_count_ : 0;
- auto administrator_count =
- (channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT) != 0 ? channel_full->admins_count_ : 0;
- auto restricted_count =
- (channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_BANNED_COUNT) != 0 ? channel_full->banned_count_ : 0;
- auto banned_count =
- (channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_BANNED_COUNT) != 0 ? channel_full->kicked_count_ : 0;
- auto can_get_participants = (channel_full->flags_ & CHANNEL_FULL_FLAG_CAN_GET_PARTICIPANTS) != 0;
- auto can_set_username = (channel_full->flags_ & CHANNEL_FULL_FLAG_CAN_SET_USERNAME) != 0;
- auto can_set_sticker_set = (channel_full->flags_ & CHANNEL_FULL_FLAG_CAN_SET_STICKERS) != 0;
- auto is_all_history_available = (channel_full->flags_ & CHANNEL_FULL_FLAG_IS_ALL_HISTORY_HIDDEN) == 0;
- int64 sticker_set_id = channel_full->stickerset_ == nullptr
- ? 0
- : td_->stickers_manager_->on_get_sticker_set(std::move(channel_full->stickerset_), true);
-
- ChannelFull *channel = &channels_full_[channel_id];
- channel->expires_at = Time::now() + CHANNEL_FULL_EXPIRE_TIME;
- if (channel->description != channel_full->about_ || channel->participant_count != participant_count ||
- channel->administrator_count != administrator_count || channel->restricted_count != restricted_count ||
- channel->banned_count != banned_count || channel->can_get_participants != can_get_participants ||
- channel->can_set_username != can_set_username || channel->can_set_sticker_set != can_set_sticker_set ||
- channel->sticker_set_id != sticker_set_id || channel->is_all_history_available != is_all_history_available) {
- channel->description = std::move(channel_full->about_);
- channel->participant_count = participant_count;
- channel->administrator_count = administrator_count;
- channel->restricted_count = restricted_count;
- channel->banned_count = banned_count;
- channel->can_get_participants = can_get_participants;
- channel->can_set_username = can_set_username;
- channel->can_set_sticker_set = can_set_sticker_set;
- channel->sticker_set_id = sticker_set_id;
- channel->is_all_history_available = is_all_history_available;
-
- channel->is_changed = true;
-
- if (participant_count != 0) {
- auto c = get_channel(channel_id);
- if (c != nullptr && c->participant_count != participant_count) {
- c->participant_count = participant_count;
- c->need_send_update = true;
- update_channel(c, channel_id);
+ auto input_channel = get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+ td_->create_handler<GetFullChannelQuery>(std::move(promise))->send(channel_id, std::move(input_channel));
+ return;
}
+ channel_full->repair_request_version = 0;
}
}
+ td_->messages_manager_->on_update_dialog_notify_settings(DialogId(channel_id), std::move(channel->notify_settings_),
+ "on_get_channel_full");
+
+ td_->messages_manager_->on_update_dialog_available_reactions(DialogId(channel_id),
+ std::move(channel->available_reactions_));
+
+ td_->messages_manager_->on_update_dialog_theme_name(DialogId(channel_id), std::move(channel->theme_emoticon_));
+
+ td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(channel_id), channel->requests_pending_,
+ std::move(channel->recent_requesters_));
+
+ {
+ MessageTtl message_ttl;
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_MESSAGE_TTL) != 0) {
+ message_ttl = MessageTtl(channel->ttl_period_);
+ }
+ td_->messages_manager_->on_update_dialog_message_ttl(DialogId(channel_id), message_ttl);
+ }
+
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ LOG(ERROR) << channel_id << " not found";
+ return promise.set_value(Unit());
+ }
+
+ ChannelFull *channel_full = add_channel_full(channel_id);
+
+ bool have_participant_count = (channel->flags_ & CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT) != 0;
+ auto participant_count = have_participant_count ? channel->participants_count_ : channel_full->participant_count;
+ auto administrator_count = 0;
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT) != 0) {
+ administrator_count = channel->admins_count_;
+ } else if (c->is_megagroup || c->status.is_administrator()) {
+ // in megagroups and administered channels don't drop known number of administrators
+ administrator_count = channel_full->administrator_count;
+ }
+ if (participant_count < administrator_count) {
+ participant_count = administrator_count;
+ }
+ auto restricted_count = (channel->flags_ & CHANNEL_FULL_FLAG_HAS_BANNED_COUNT) != 0 ? channel->banned_count_ : 0;
+ auto banned_count = (channel->flags_ & CHANNEL_FULL_FLAG_HAS_BANNED_COUNT) != 0 ? channel->kicked_count_ : 0;
+ auto can_get_participants = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_GET_PARTICIPANTS) != 0;
+ auto can_set_username = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_SET_USERNAME) != 0;
+ auto can_set_sticker_set = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_SET_STICKER_SET) != 0;
+ auto can_set_location = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_SET_LOCATION) != 0;
+ auto is_all_history_available = (channel->flags_ & CHANNEL_FULL_FLAG_IS_ALL_HISTORY_HIDDEN) == 0;
+ auto can_view_statistics = (channel->flags_ & CHANNEL_FULL_FLAG_CAN_VIEW_STATISTICS) != 0;
+ StickerSetId sticker_set_id;
+ if (channel->stickerset_ != nullptr) {
+ sticker_set_id =
+ td_->stickers_manager_->on_get_sticker_set(std::move(channel->stickerset_), true, "on_get_channel_full");
+ }
+ DcId stats_dc_id;
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_STATISTICS_DC_ID) != 0) {
+ stats_dc_id = DcId::create(channel->stats_dc_);
+ }
+ if (!stats_dc_id.is_exact() && can_view_statistics) {
+ LOG(ERROR) << "Receive can_view_statistics == true, but invalid statistics DC ID in " << channel_id;
+ can_view_statistics = false;
+ }
+
+ channel_full->repair_request_version = 0;
+ channel_full->expires_at = Time::now() + CHANNEL_FULL_EXPIRE_TIME;
+ if (channel_full->participant_count != participant_count ||
+ channel_full->administrator_count != administrator_count ||
+ channel_full->restricted_count != restricted_count || channel_full->banned_count != banned_count ||
+ channel_full->can_get_participants != can_get_participants ||
+ channel_full->can_set_username != can_set_username ||
+ channel_full->can_set_sticker_set != can_set_sticker_set ||
+ channel_full->can_set_location != can_set_location ||
+ channel_full->can_view_statistics != can_view_statistics || channel_full->stats_dc_id != stats_dc_id ||
+ channel_full->sticker_set_id != sticker_set_id ||
+ channel_full->is_all_history_available != is_all_history_available) {
+ channel_full->participant_count = participant_count;
+ channel_full->administrator_count = administrator_count;
+ channel_full->restricted_count = restricted_count;
+ channel_full->banned_count = banned_count;
+ channel_full->can_get_participants = can_get_participants;
+ channel_full->can_set_username = can_set_username;
+ channel_full->can_set_sticker_set = can_set_sticker_set;
+ channel_full->can_set_location = can_set_location;
+ channel_full->can_view_statistics = can_view_statistics;
+ channel_full->stats_dc_id = stats_dc_id;
+ channel_full->is_all_history_available = is_all_history_available;
+ channel_full->sticker_set_id = sticker_set_id;
+
+ channel_full->is_changed = true;
+ }
+ if (channel_full->description != channel->about_) {
+ channel_full->description = std::move(channel->about_);
+ channel_full->is_changed = true;
+ td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, true);
+ }
+
+ if (have_participant_count && c->participant_count != participant_count) {
+ c->participant_count = participant_count;
+ c->is_changed = true;
+ update_channel(c, channel_id);
+ }
+ if (!channel_full->is_can_view_statistics_inited) {
+ channel_full->is_can_view_statistics_inited = true;
+ channel_full->need_save_to_database = true;
+ }
+
+ auto photo = get_photo(td_->file_manager_.get(), std::move(channel->chat_photo_), DialogId(channel_id));
+ // on_update_channel_photo should be a no-op if server sent consistent data
+ on_update_channel_photo(
+ c, channel_id, as_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), c->access_hash, photo), false);
+ on_update_channel_full_photo(channel_full, channel_id, std::move(photo));
+
td_->messages_manager_->on_read_channel_outbox(channel_id,
- MessageId(ServerMessageId(channel_full->read_outbox_max_id_)));
- if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_AVAILABLE_MIN_MESSAGE_ID) != 0) {
+ MessageId(ServerMessageId(channel->read_outbox_max_id_)));
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_AVAILABLE_MIN_MESSAGE_ID) != 0) {
td_->messages_manager_->on_update_channel_max_unavailable_message_id(
- channel_id, MessageId(ServerMessageId(channel_full->available_min_id_)));
+ channel_id, MessageId(ServerMessageId(channel->available_min_id_)));
}
- td_->messages_manager_->on_read_channel_inbox(
- channel_id, MessageId(ServerMessageId(channel_full->read_inbox_max_id_)), channel_full->unread_count_);
+ td_->messages_manager_->on_read_channel_inbox(channel_id, MessageId(ServerMessageId(channel->read_inbox_max_id_)),
+ channel->unread_count_, channel->pts_, "ChannelFull");
- on_update_channel_full_invite_link(channel, std::move(channel_full->exported_invite_));
+ on_update_channel_full_invite_link(channel_full, std::move(channel->exported_invite_));
- if ((channel_full->flags_ & CHANNEL_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) {
- on_update_channel_full_pinned_message(channel, MessageId(ServerMessageId(channel_full->pinned_msg_id_)));
+ {
+ auto is_blocked = (channel->flags_ & CHANNEL_FULL_FLAG_IS_BLOCKED) != 0;
+ td_->messages_manager_->on_update_dialog_is_blocked(DialogId(channel_id), is_blocked);
+ }
+ {
+ MessageId pinned_message_id;
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_PINNED_MESSAGE) != 0) {
+ pinned_message_id = MessageId(ServerMessageId(channel->pinned_msg_id_));
+ }
+ td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(channel_id), pinned_message_id);
+ }
+ {
+ FolderId folder_id;
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_FOLDER_ID) != 0) {
+ folder_id = FolderId(channel->folder_id_);
+ }
+ td_->messages_manager_->on_update_dialog_folder_id(DialogId(channel_id), folder_id);
+ }
+ td_->messages_manager_->on_update_dialog_has_scheduled_server_messages(
+ DialogId(channel_id), (channel->flags_ & CHANNEL_FULL_FLAG_HAS_SCHEDULED_MESSAGES) != 0);
+ {
+ InputGroupCallId input_group_call_id;
+ if (channel->call_ != nullptr) {
+ input_group_call_id = InputGroupCallId(channel->call_);
+ }
+ td_->messages_manager_->on_update_dialog_group_call_id(DialogId(channel_id), input_group_call_id);
+ }
+ {
+ DialogId default_join_group_call_as_dialog_id;
+ if (channel->groupcall_default_join_as_ != nullptr) {
+ default_join_group_call_as_dialog_id = DialogId(channel->groupcall_default_join_as_);
+ }
+ // use send closure later to not create synchronously default_join_group_call_as_dialog_id
+ send_closure_later(G()->messages_manager(),
+ &MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id, DialogId(channel_id),
+ default_join_group_call_as_dialog_id, false);
+ }
+ {
+ DialogId default_send_message_as_dialog_id;
+ if (channel->default_send_as_ != nullptr) {
+ default_send_message_as_dialog_id = DialogId(channel->default_send_as_);
+ }
+ // use send closure later to not create synchronously default_send_message_as_dialog_id
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_default_send_message_as_dialog_id,
+ DialogId(channel_id), default_send_message_as_dialog_id, false);
+ }
+
+ if (participant_count >= 190) {
+ int32 online_member_count = 0;
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_ONLINE_MEMBER_COUNT) != 0) {
+ online_member_count = channel->online_count_;
+ }
+ td_->messages_manager_->on_update_dialog_online_member_count(DialogId(channel_id), online_member_count, true);
}
- for (auto &bot_info : channel_full->bot_info_) {
- on_update_bot_info(std::move(bot_info));
+ vector<UserId> bot_user_ids;
+ for (const auto &bot_info : channel->bot_info_) {
+ UserId user_id(bot_info->user_id_);
+ if (!is_user_bot(user_id)) {
+ continue;
+ }
+
+ bot_user_ids.push_back(user_id);
+ }
+ on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids));
+
+ auto bot_commands = get_bot_commands(std::move(channel->bot_info_), nullptr);
+ if (channel_full->bot_commands != bot_commands) {
+ channel_full->bot_commands = std::move(bot_commands);
+ channel_full->is_changed = true;
+ }
+
+ ChannelId linked_channel_id;
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_LINKED_CHANNEL_ID) != 0) {
+ linked_channel_id = ChannelId(channel->linked_chat_id_);
+ auto linked_channel = get_channel_force(linked_channel_id);
+ if (linked_channel == nullptr || c->is_megagroup == linked_channel->is_megagroup ||
+ channel_id == linked_channel_id) {
+ LOG(ERROR) << "Failed to add a link between " << channel_id << " and " << linked_channel_id;
+ linked_channel_id = ChannelId();
+ }
+ }
+ on_update_channel_full_linked_channel_id(channel_full, channel_id, linked_channel_id);
+
+ on_update_channel_full_location(channel_full, channel_id, DialogLocation(std::move(channel->location_)));
+
+ if (c->is_megagroup) {
+ int32 slow_mode_delay = 0;
+ int32 slow_mode_next_send_date = 0;
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_SLOW_MODE_DELAY) != 0) {
+ slow_mode_delay = channel->slowmode_seconds_;
+ }
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_HAS_SLOW_MODE_NEXT_SEND_DATE) != 0) {
+ slow_mode_next_send_date = channel->slowmode_next_send_date_;
+ }
+ on_update_channel_full_slow_mode_delay(channel_full, channel_id, slow_mode_delay, slow_mode_next_send_date);
+ }
+ if (channel_full->can_be_deleted != channel->can_delete_channel_) {
+ channel_full->can_be_deleted = channel->can_delete_channel_;
+ channel_full->need_save_to_database = true;
+ }
+ if (c->can_be_deleted != channel_full->can_be_deleted) {
+ c->can_be_deleted = channel_full->can_be_deleted;
+ c->need_save_to_database = true;
}
ChatId migrated_from_chat_id;
MessageId migrated_from_max_message_id;
- if (channel_full->flags_ & CHANNEL_FULL_FLAG_MIGRATED_FROM) {
- migrated_from_chat_id = ChatId(channel_full->migrated_from_chat_id_);
- migrated_from_max_message_id = MessageId(ServerMessageId(channel_full->migrated_from_max_id_));
+ if ((channel->flags_ & CHANNEL_FULL_FLAG_MIGRATED_FROM) != 0) {
+ migrated_from_chat_id = ChatId(channel->migrated_from_chat_id_);
+ migrated_from_max_message_id = MessageId(ServerMessageId(channel->migrated_from_max_id_));
+ }
+
+ if (channel_full->migrated_from_chat_id != migrated_from_chat_id ||
+ channel_full->migrated_from_max_message_id != migrated_from_max_message_id) {
+ channel_full->migrated_from_chat_id = migrated_from_chat_id;
+ channel_full->migrated_from_max_message_id = migrated_from_max_message_id;
+ channel_full->is_changed = true;
+ }
+
+ if (c->is_changed) {
+ LOG(ERROR) << "Receive inconsistent chatPhoto and chatPhotoInfo for " << channel_id;
+ update_channel(c, channel_id);
}
- if (channel->migrated_from_chat_id != migrated_from_chat_id ||
- channel->migrated_from_max_message_id != migrated_from_max_message_id) {
- channel->migrated_from_chat_id = migrated_from_chat_id;
- channel->migrated_from_max_message_id = migrated_from_max_message_id;
- channel->is_changed = true;
+ channel_full->is_update_channel_full_sent = true;
+ update_channel_full(channel_full, channel_id, "on_get_channel_full");
+
+ if (linked_channel_id.is_valid()) {
+ auto linked_channel_full = get_channel_full_force(linked_channel_id, true, "on_get_channel_full");
+ on_update_channel_full_linked_channel_id(linked_channel_full, linked_channel_id, channel_id);
+ if (linked_channel_full != nullptr) {
+ update_channel_full(linked_channel_full, linked_channel_id, "on_get_channel_full 2");
+ }
+ }
+
+ if (dismiss_suggested_action_queries_.count(DialogId(channel_id)) == 0) {
+ auto it = dialog_suggested_actions_.find(DialogId(channel_id));
+ if (it != dialog_suggested_actions_.end() || !channel->pending_suggestions_.empty()) {
+ vector<SuggestedAction> suggested_actions;
+ for (auto &action_str : channel->pending_suggestions_) {
+ SuggestedAction suggested_action(action_str, DialogId(channel_id));
+ if (!suggested_action.is_empty()) {
+ if (suggested_action == SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)} &&
+ (c->is_gigagroup ||
+ c->default_permissions != RestrictedRights(false, false, false, false, false, false, false, false,
+ false, false, false, false))) {
+ LOG(INFO) << "Skip ConvertToGigagroup suggested action";
+ } else {
+ suggested_actions.push_back(suggested_action);
+ }
+ }
+ }
+ if (it == dialog_suggested_actions_.end()) {
+ it = dialog_suggested_actions_.emplace(DialogId(channel_id), vector<SuggestedAction>()).first;
+ }
+ update_suggested_actions(it->second, std::move(suggested_actions));
+ if (it->second.empty()) {
+ dialog_suggested_actions_.erase(it);
+ }
+ }
}
+ }
+ promise.set_value(Unit());
+}
- update_channel_full(channel, channel_id);
+void ContactsManager::on_get_chat_full_failed(ChatId chat_id) {
+ if (G()->close_flag()) {
+ return;
}
+
+ LOG(INFO) << "Failed to get full " << chat_id;
}
-bool ContactsManager::is_update_about_username_change_received(UserId user_id) const {
- const User *u = get_user(user_id);
- if (u != nullptr) {
- return u->inbound == LinkState::Contact;
- } else {
- return false;
+void ContactsManager::on_get_channel_full_failed(ChannelId channel_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Failed to get full " << channel_id;
+ auto channel_full = get_channel_full(channel_id, true, "on_get_channel_full");
+ if (channel_full != nullptr) {
+ channel_full->repair_request_version = 0;
}
}
-void ContactsManager::on_update_user_name(UserId user_id, string &&first_name, string &&last_name, string &&username) {
+void ContactsManager::on_update_user_name(UserId user_id, string &&first_name, string &&last_name,
+ Usernames &&usernames) {
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << user_id;
return;
@@ -6520,16 +12167,16 @@ void ContactsManager::on_update_user_name(UserId user_id, string &&first_name, s
User *u = get_user_force(user_id);
if (u != nullptr) {
- on_update_user_name(u, user_id, std::move(first_name), std::move(last_name), std::move(username));
+ on_update_user_name(u, user_id, std::move(first_name), std::move(last_name));
+ on_update_user_usernames(u, user_id, std::move(usernames));
update_user(u, user_id);
} else {
LOG(INFO) << "Ignore update user name about unknown " << user_id;
}
}
-void ContactsManager::on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name,
- string &&username) {
- if (first_name.empty()) {
+void ContactsManager::on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name) {
+ if (first_name.empty() && last_name.empty()) {
first_name = u->phone_number;
}
if (u->first_name != first_name || u->last_name != last_name) {
@@ -6537,14 +12184,17 @@ void ContactsManager::on_update_user_name(User *u, UserId user_id, string &&firs
u->last_name = std::move(last_name);
u->is_name_changed = true;
LOG(DEBUG) << "Name has changed for " << user_id;
- u->need_send_update = true;
+ u->is_changed = true;
}
- td_->messages_manager_->on_dialog_username_updated(DialogId(user_id), u->username, username);
- if (u->username != username) {
- u->username = std::move(username);
+}
+
+void ContactsManager::on_update_user_usernames(User *u, UserId user_id, Usernames &&usernames) {
+ td_->messages_manager_->on_dialog_usernames_updated(DialogId(user_id), u->usernames, usernames);
+ if (u->usernames != usernames) {
+ u->usernames = std::move(usernames);
u->is_username_changed = true;
- LOG(DEBUG) << "Username has changed for " << user_id;
- u->need_send_update = true;
+ LOG(DEBUG) << "Usernames have changed for " << user_id;
+ u->is_changed = true;
}
}
@@ -6564,10 +12214,19 @@ void ContactsManager::on_update_user_phone_number(UserId user_id, string &&phone
}
void ContactsManager::on_update_user_phone_number(User *u, UserId user_id, string &&phone_number) {
+ clean_phone_number(phone_number);
if (u->phone_number != phone_number) {
+ if (!u->phone_number.empty()) {
+ auto it = resolved_phone_numbers_.find(u->phone_number);
+ if (it != resolved_phone_numbers_.end() && it->second == user_id) {
+ resolved_phone_numbers_.erase(it);
+ }
+ }
+
u->phone_number = std::move(phone_number);
+ u->is_phone_number_changed = true;
LOG(DEBUG) << "Phone number has changed for " << user_id;
- u->need_send_update = true;
+ u->is_changed = true;
}
}
@@ -6579,7 +12238,7 @@ void ContactsManager::on_update_user_photo(UserId user_id, tl_object_ptr<telegra
User *u = get_user_force(user_id);
if (u != nullptr) {
- on_update_user_photo(u, user_id, std::move(photo_ptr));
+ on_update_user_photo(u, user_id, std::move(photo_ptr), "on_update_user_photo");
update_user(u, user_id);
} else {
LOG(INFO) << "Ignore update user photo about unknown " << user_id;
@@ -6587,14 +12246,146 @@ void ContactsManager::on_update_user_photo(UserId user_id, tl_object_ptr<telegra
}
void ContactsManager::on_update_user_photo(User *u, UserId user_id,
- tl_object_ptr<telegram_api::UserProfilePhoto> &&photo_ptr) {
- ProfilePhoto new_photo = get_profile_photo(td_->file_manager_.get(), std::move(photo_ptr));
+ tl_object_ptr<telegram_api::UserProfilePhoto> &&photo, const char *source) {
+ if (td_->auth_manager_->is_bot() && !G()->parameters().use_file_db && !u->is_photo_inited) {
+ if (photo != nullptr && photo->get_id() == telegram_api::userProfilePhoto::ID) {
+ auto *profile_photo = static_cast<telegram_api::userProfilePhoto *>(photo.get());
+ if ((profile_photo->flags_ & telegram_api::userProfilePhoto::STRIPPED_THUMB_MASK) != 0) {
+ profile_photo->flags_ -= telegram_api::userProfilePhoto::STRIPPED_THUMB_MASK;
+ profile_photo->stripped_thumb_ = BufferSlice();
+ }
+ }
+ auto &old_photo = pending_user_photos_[user_id];
+ if (!LOG_IS_STRIPPED(ERROR) && to_string(old_photo) == to_string(photo)) {
+ return;
+ }
+
+ bool is_empty = photo == nullptr || photo->get_id() == telegram_api::userProfilePhotoEmpty::ID;
+ old_photo = std::move(photo);
+
+ drop_user_photos(user_id, is_empty, true, "on_update_user_photo");
+ return;
+ }
- if (new_photo != u->photo) {
+ do_update_user_photo(u, user_id, std::move(photo), source);
+}
+
+void ContactsManager::do_update_user_photo(User *u, UserId user_id,
+ tl_object_ptr<telegram_api::UserProfilePhoto> &&photo, const char *source) {
+ ProfilePhoto new_photo = get_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, std::move(photo));
+ if (td_->auth_manager_->is_bot()) {
+ new_photo.minithumbnail.clear();
+ }
+ do_update_user_photo(u, user_id, std::move(new_photo), true, source);
+}
+
+void ContactsManager::do_update_user_photo(User *u, UserId user_id, ProfilePhoto &&new_photo,
+ bool invalidate_photo_cache, const char *source) {
+ u->is_photo_inited = true;
+ if (need_update_profile_photo(u->photo, new_photo)) {
+ LOG_IF(ERROR, u->access_hash == -1 && new_photo.small_file_id.is_valid())
+ << "Update profile photo of " << user_id << " without access hash from " << source;
u->photo = new_photo;
u->is_photo_changed = true;
- LOG(DEBUG) << "Photo has changed for " << user_id;
- u->need_send_update = true;
+ LOG(DEBUG) << "Photo has changed for " << user_id << " to " << u->photo
+ << ", invalidate_photo_cache = " << invalidate_photo_cache;
+ u->is_changed = true;
+
+ if (invalidate_photo_cache) {
+ drop_user_photos(user_id, !u->photo.small_file_id.is_valid(), true, "do_update_user_photo");
+ } else {
+ auto user_full = get_user_full(user_id); // must not load UserFull
+ if (user_full != nullptr) {
+ if (u->photo.id != user_full->photo.id.get() && !user_full->photo.is_empty()) {
+ user_full->photo = Photo();
+ user_full->is_changed = true;
+ }
+ if (user_full->is_update_user_full_sent) {
+ update_user_full(user_full, user_id, "do_update_user_photo");
+ }
+ }
+ }
+ } else if (need_update_dialog_photo_minithumbnail(u->photo.minithumbnail, new_photo.minithumbnail)) {
+ u->photo.minithumbnail = std::move(new_photo.minithumbnail);
+ u->is_photo_changed = true;
+ u->is_changed = true;
+ }
+}
+
+void ContactsManager::register_user_photo(User *u, UserId user_id, const Photo &photo) {
+ auto photo_file_ids = photo_get_file_ids(photo);
+ if (photo.is_empty() || photo_file_ids.empty()) {
+ return;
+ }
+ auto first_file_id = photo_file_ids[0];
+ auto file_type = td_->file_manager_->get_file_view(first_file_id).get_type();
+ if (file_type == FileType::ProfilePhoto) {
+ return;
+ }
+ CHECK(file_type == FileType::Photo);
+ CHECK(u != nullptr);
+ auto photo_id = photo.id.get();
+ if (photo_id != 0 && u->photo_ids.emplace(photo_id).second) {
+ VLOG(file_references) << "Register photo " << photo_id << " of " << user_id;
+ if (user_id == get_my_id()) {
+ my_photo_file_id_[photo_id] = first_file_id;
+ }
+ auto file_source_id = user_profile_photo_file_source_ids_.get(std::make_pair(user_id, photo_id));
+ if (file_source_id.is_valid()) {
+ VLOG(file_references) << "Move " << file_source_id << " inside of " << user_id;
+ user_profile_photo_file_source_ids_.erase(std::make_pair(user_id, photo_id));
+ } else {
+ VLOG(file_references) << "Need to create new file source for photo " << photo_id << " of " << user_id;
+ file_source_id = td_->file_reference_manager_->create_user_photo_file_source(user_id, photo_id);
+ }
+ for (auto &file_id : photo_file_ids) {
+ td_->file_manager_->add_file_source(file_id, file_source_id);
+ }
+ }
+}
+
+void ContactsManager::on_update_user_emoji_status(UserId user_id,
+ tl_object_ptr<telegram_api::EmojiStatus> &&emoji_status) {
+ if (!user_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << user_id;
+ return;
+ }
+
+ User *u = get_user_force(user_id);
+ if (u != nullptr) {
+ on_update_user_emoji_status(u, user_id, EmojiStatus(std::move(emoji_status)));
+ update_user(u, user_id);
+ } else {
+ LOG(INFO) << "Ignore update user emoji status about unknown " << user_id;
+ }
+}
+
+void ContactsManager::on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status) {
+ if (u->emoji_status != emoji_status) {
+ LOG(DEBUG) << "Change emoji status of " << user_id << " from " << u->emoji_status << " to " << emoji_status;
+ u->emoji_status = emoji_status;
+ }
+}
+
+void ContactsManager::on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact) {
+ UserId my_id = get_my_id();
+ if (user_id == my_id) {
+ is_mutual_contact = is_contact;
+ }
+ if (!is_contact && is_mutual_contact) {
+ LOG(ERROR) << "Receive is_mutual_contact == true for non-contact " << user_id;
+ is_mutual_contact = false;
+ }
+
+ if (u->is_contact != is_contact || u->is_mutual_contact != is_mutual_contact) {
+ LOG(DEBUG) << "Update " << user_id << " is_contact from (" << u->is_contact << ", " << u->is_mutual_contact
+ << ") to (" << is_contact << ", " << is_mutual_contact << ")";
+ if (u->is_contact != is_contact) {
+ u->is_is_contact_changed = true;
+ }
+ u->is_contact = is_contact;
+ u->is_mutual_contact = is_mutual_contact;
+ u->is_changed = true;
}
}
@@ -6606,8 +12397,19 @@ void ContactsManager::on_update_user_online(UserId user_id, tl_object_ptr<telegr
User *u = get_user_force(user_id);
if (u != nullptr) {
+ if (u->is_bot) {
+ LOG(ERROR) << "Receive updateUserStatus about bot " << user_id;
+ return;
+ }
on_update_user_online(u, user_id, std::move(status));
update_user(u, user_id);
+
+ if (user_id == get_my_id() &&
+ was_online_remote_ != u->was_online) { // only update was_online_remote_ from updateUserStatus
+ was_online_remote_ = u->was_online;
+ VLOG(notifications) << "Set was_online_remote to " << was_online_remote_;
+ G()->td_db()->get_binlog_pmc()->set("my_was_online_remote", to_string(was_online_remote_));
+ }
} else {
LOG(INFO) << "Ignore update user online about unknown " << user_id;
}
@@ -6650,153 +12452,521 @@ void ContactsManager::on_update_user_online(User *u, UserId user_id, tl_object_p
if (new_online != u->was_online) {
LOG(DEBUG) << "Update " << user_id << " online from " << u->was_online << " to " << new_online;
+ bool old_is_online = u->was_online > G()->unix_time_cached();
+ bool new_is_online = new_online > G()->unix_time_cached();
u->was_online = new_online;
u->is_status_changed = true;
+ if (u->was_online > 0) {
+ u->local_was_online = 0;
+ }
- if (user_id == get_my_id("on_update_user_online")) {
- my_was_online_local_ = 0;
+ if (user_id == get_my_id()) {
+ if (my_was_online_local_ != 0 || old_is_online != new_is_online) {
+ my_was_online_local_ = 0;
+ u->is_online_status_changed = true;
+ }
if (is_offline) {
td_->on_online_updated(false, false);
}
+ } else if (old_is_online != new_is_online) {
+ u->is_online_status_changed = true;
}
}
}
-void ContactsManager::on_update_user_blocked(UserId user_id, bool is_blocked) {
- LOG(INFO) << "Receive update user blocked with " << user_id << " and is_blocked = " << is_blocked;
+void ContactsManager::on_update_user_local_was_online(UserId user_id, int32 local_was_online) {
+ CHECK(user_id.is_valid());
+
+ User *u = get_user_force(user_id);
+ if (u == nullptr) {
+ return;
+ }
+
+ on_update_user_local_was_online(u, user_id, local_was_online);
+ update_user(u, user_id);
+}
+
+void ContactsManager::on_update_user_local_was_online(User *u, UserId user_id, int32 local_was_online) {
+ CHECK(u != nullptr);
+ if (u->is_deleted || u->is_bot || u->is_support || user_id == get_my_id()) {
+ return;
+ }
+ if (u->was_online > G()->unix_time_cached()) {
+ // if user is currently online, ignore local online
+ return;
+ }
+
+ // bring users online for 30 seconds
+ local_was_online += 30;
+ if (local_was_online < G()->unix_time_cached() + 2 || local_was_online <= u->local_was_online ||
+ local_was_online <= u->was_online) {
+ return;
+ }
+
+ LOG(DEBUG) << "Update " << user_id << " local online from " << u->local_was_online << " to " << local_was_online;
+ bool old_is_online = u->local_was_online > G()->unix_time_cached();
+ u->local_was_online = local_was_online;
+ u->is_status_changed = true;
+
+ if (!old_is_online) {
+ u->is_online_status_changed = true;
+ }
+}
+
+void ContactsManager::on_update_user_is_blocked(UserId user_id, bool is_blocked) {
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << user_id;
return;
}
- UserFull *user_full = get_user_full(user_id);
- if (user_full == nullptr) {
+ UserFull *user_full = get_user_full_force(user_id);
+ if (user_full == nullptr || user_full->is_blocked == is_blocked) {
return;
}
on_update_user_full_is_blocked(user_full, user_id, is_blocked);
- update_user_full(user_full, user_id);
+ update_user_full(user_full, user_id, "on_update_user_is_blocked");
}
void ContactsManager::on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked) {
CHECK(user_full != nullptr);
- if (user_full->is_inited && user_full->is_blocked != is_blocked) {
+ if (user_full->is_blocked != is_blocked) {
+ LOG(INFO) << "Receive update user full is blocked with " << user_id << " and is_blocked = " << is_blocked;
user_full->is_blocked = is_blocked;
user_full->is_changed = true;
}
}
-void ContactsManager::on_delete_profile_photo(int64 profile_photo_id, Promise<Unit> promise) {
- UserId my_id = get_my_id("on_delete_profile_photo");
+void ContactsManager::on_update_user_common_chat_count(UserId user_id, int32 common_chat_count) {
+ LOG(INFO) << "Receive " << common_chat_count << " common chat count with " << user_id;
+ if (!user_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << user_id;
+ return;
+ }
- UserFull *user_full = get_user_full(my_id);
- if (user_full != nullptr) {
- // drop photo cache
- user_full->photos.clear();
- user_full->photo_count = -1;
- user_full->photos_offset = -1;
+ UserFull *user_full = get_user_full_force(user_id);
+ if (user_full == nullptr) {
+ return;
}
+ on_update_user_full_common_chat_count(user_full, user_id, common_chat_count);
+ update_user_full(user_full, user_id, "on_update_user_common_chat_count");
+}
- auto input_user = get_input_user(my_id);
- CHECK(input_user != nullptr);
- vector<tl_object_ptr<telegram_api::InputUser>> users;
- users.push_back(std::move(input_user));
- td_->create_handler<GetUsersQuery>(std::move(promise))->send(std::move(users));
+void ContactsManager::on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id,
+ int32 common_chat_count) {
+ CHECK(user_full != nullptr);
+ if (common_chat_count < 0) {
+ LOG(ERROR) << "Receive " << common_chat_count << " as common group count with " << user_id;
+ common_chat_count = 0;
+ }
+ if (user_full->common_chat_count != common_chat_count) {
+ user_full->common_chat_count = common_chat_count;
+ user_full->is_common_chat_count_changed = true;
+ user_full->is_changed = true;
+ }
}
-ContactsManager::LinkState ContactsManager::get_link_state(tl_object_ptr<telegram_api::ContactLink> &&link) {
- int32 id = link->get_id();
- switch (id) {
- case telegram_api::contactLinkUnknown::ID:
- return LinkState::Unknown;
- case telegram_api::contactLinkNone::ID:
- return LinkState::None;
- case telegram_api::contactLinkHasPhone::ID:
- return LinkState::KnowsPhoneNumber;
- case telegram_api::contactLinkContact::ID:
- return LinkState::Contact;
- default:
- UNREACHABLE();
+void ContactsManager::on_update_user_full_commands(UserFull *user_full, UserId user_id,
+ vector<tl_object_ptr<telegram_api::botCommand>> &&bot_commands) {
+ CHECK(user_full != nullptr);
+ auto commands = transform(std::move(bot_commands), [](tl_object_ptr<telegram_api::botCommand> &&bot_command) {
+ return BotCommand(std::move(bot_command));
+ });
+ if (user_full->commands != commands) {
+ user_full->commands = std::move(commands);
+ user_full->is_changed = true;
}
- return LinkState::Unknown;
}
-void ContactsManager::on_update_user_links(UserId user_id, tl_object_ptr<telegram_api::ContactLink> &&outbound,
- tl_object_ptr<telegram_api::ContactLink> &&inbound) {
+void ContactsManager::on_update_user_full_menu_button(UserFull *user_full, UserId user_id,
+ tl_object_ptr<telegram_api::BotMenuButton> &&bot_menu_button) {
+ CHECK(user_full != nullptr);
+ auto new_button = get_bot_menu_button(std::move(bot_menu_button));
+ bool is_changed;
+ if (user_full->menu_button == nullptr) {
+ is_changed = (new_button != nullptr);
+ } else {
+ is_changed = (new_button == nullptr || *user_full->menu_button != *new_button);
+ }
+ if (is_changed) {
+ user_full->menu_button = std::move(new_button);
+ user_full->is_changed = true;
+ }
+}
+
+void ContactsManager::on_update_user_need_phone_number_privacy_exception(UserId user_id,
+ bool need_phone_number_privacy_exception) {
+ LOG(INFO) << "Receive " << need_phone_number_privacy_exception << " need phone number privacy exception with "
+ << user_id;
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << user_id;
return;
}
- User *u = get_user_force(user_id);
+ UserFull *user_full = get_user_full_force(user_id);
+ if (user_full == nullptr) {
+ return;
+ }
+ on_update_user_full_need_phone_number_privacy_exception(user_full, user_id, need_phone_number_privacy_exception);
+ update_user_full(user_full, user_id, "on_update_user_need_phone_number_privacy_exception");
+}
+
+void ContactsManager::on_update_user_full_need_phone_number_privacy_exception(
+ UserFull *user_full, UserId user_id, bool need_phone_number_privacy_exception) const {
+ CHECK(user_full != nullptr);
+ if (need_phone_number_privacy_exception) {
+ const User *u = get_user(user_id);
+ if (u == nullptr || u->is_contact || user_id == get_my_id()) {
+ need_phone_number_privacy_exception = false;
+ }
+ }
+ if (user_full->need_phone_number_privacy_exception != need_phone_number_privacy_exception) {
+ user_full->need_phone_number_privacy_exception = need_phone_number_privacy_exception;
+ user_full->is_changed = true;
+ }
+}
+
+void ContactsManager::on_ignored_restriction_reasons_changed() {
+ restricted_user_ids_.foreach([&](const UserId &user_id) {
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateUser>(get_user_object(user_id, get_user(user_id))));
+ });
+ restricted_channel_ids_.foreach([&](const ChannelId &channel_id) {
+ send_closure(
+ G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateSupergroup>(get_supergroup_object(channel_id, get_channel(channel_id))));
+ });
+}
+
+void ContactsManager::on_set_profile_photo(tl_object_ptr<telegram_api::photos_photo> &&photo, int64 old_photo_id) {
+ LOG(INFO) << "Changed profile photo to " << to_string(photo);
+
+ UserId my_user_id = get_my_id();
+
+ if (old_photo_id != 0) {
+ delete_profile_photo_from_cache(my_user_id, old_photo_id, false);
+ }
+
+ add_profile_photo_to_cache(my_user_id,
+ get_photo(td_->file_manager_.get(), std::move(photo->photo_), DialogId(my_user_id)));
+
+ User *u = get_user(my_user_id);
if (u != nullptr) {
- on_update_user_links(u, user_id, get_link_state(std::move(outbound)), get_link_state(std::move(inbound)));
- update_user(u, user_id);
- } else {
- LOG(INFO) << "Ignore update user links about unknown " << user_id;
+ update_user(u, my_user_id);
}
+ auto *user_full = get_user_full(my_user_id);
+ if (user_full != nullptr) {
+ update_user_full(user_full, my_user_id, "on_set_profile_photo");
+ }
+
+ // if cache was correctly updated, this should produce no updates
+ on_get_users(std::move(photo->users_), "on_set_profile_photo");
}
-void ContactsManager::on_update_user_links(User *u, UserId user_id, LinkState outbound, LinkState inbound) {
- LOG(DEBUG) << "Update " << user_id << " links from (" << u->outbound << ", " << u->inbound << ") to (" << outbound
- << ", " << inbound << ")";
- UserId my_id = get_my_id("on_update_user_links");
- if (user_id == my_id) {
- if (outbound == LinkState::None && !td_->auth_manager_->is_bot()) {
- outbound = LinkState::KnowsPhoneNumber;
+void ContactsManager::on_delete_profile_photo(int64 profile_photo_id, Promise<Unit> promise) {
+ UserId my_user_id = get_my_id();
+
+ bool need_reget_user = delete_profile_photo_from_cache(my_user_id, profile_photo_id, true);
+ if (need_reget_user && !G()->close_flag()) {
+ return reload_user(my_user_id, std::move(promise));
+ }
+
+ promise.set_value(Unit());
+}
+
+void ContactsManager::add_profile_photo_to_cache(UserId user_id, Photo &&photo) {
+ if (photo.is_empty()) {
+ return;
+ }
+
+ // we have subsequence of user photos in user_photos_
+ // ProfilePhoto in User and Photo in UserFull
+
+ User *u = get_user_force(user_id);
+ if (u == nullptr) {
+ return;
+ }
+
+ LOG(INFO) << "Add profile photo " << photo.id.get() << " to cache";
+
+ // update photo list
+ auto user_photos = user_photos_.get_pointer(user_id);
+ if (user_photos != nullptr && user_photos->count != -1) {
+ if (user_photos->offset == 0) {
+ if (user_photos->photos.empty() || user_photos->photos[0].id.get() != photo.id.get()) {
+ user_photos->photos.insert(user_photos->photos.begin(), photo);
+ user_photos->count++;
+ register_user_photo(u, user_id, user_photos->photos[0]);
+ }
+ } else {
+ user_photos->count++;
+ user_photos->offset++;
}
- inbound = outbound;
}
- bool need_send_update = false;
- if (outbound != u->outbound && outbound != LinkState::Unknown) {
- need_send_update |= outbound != LinkState::None || u->outbound != LinkState::Unknown;
- LOG(DEBUG) << "Set outbound link to " << outbound << ", need_send_update = " << need_send_update;
- u->outbound = outbound;
- u->is_outbound_link_changed = true;
- u->is_changed = true;
+ // update ProfilePhoto in User
+ do_update_user_photo(u, user_id, as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, photo), false,
+ "add_profile_photo_to_cache");
+ update_user(u, user_id);
+
+ // update Photo in UserFull
+ auto user_full = get_user_full_force(user_id);
+ if (user_full != nullptr) {
+ if (user_full->photo != photo) {
+ user_full->photo = photo;
+ user_full->is_changed = true;
+ register_user_photo(u, user_id, photo);
+ }
+ update_user_full(user_full, user_id, "add_profile_photo_to_cache");
}
- if (inbound != u->inbound && inbound != LinkState::Unknown) {
- need_send_update |= inbound != LinkState::None || u->inbound != LinkState::Unknown;
- LOG(DEBUG) << "Set inbound link to " << inbound << ", need_send_update = " << need_send_update;
- u->inbound = inbound;
- u->is_changed = true;
+}
+
+bool ContactsManager::delete_profile_photo_from_cache(UserId user_id, int64 profile_photo_id, bool send_updates) {
+ CHECK(profile_photo_id != 0);
+
+ // we have subsequence of user photos in user_photos_
+ // ProfilePhoto in User and Photo in UserFull
+
+ LOG(INFO) << "Delete profile photo " << profile_photo_id << " from cache with" << (send_updates ? "" : "out")
+ << " updates";
+
+ User *u = get_user_force(user_id);
+ bool is_main_photo_deleted = u != nullptr && u->photo.id == profile_photo_id;
+
+ // update photo list
+ auto user_photos = user_photos_.get_pointer(user_id);
+ if (user_photos != nullptr && user_photos->count > 0) {
+ auto old_size = user_photos->photos.size();
+ if (td::remove_if(user_photos->photos,
+ [profile_photo_id](const auto &photo) { return photo.id.get() == profile_photo_id; })) {
+ auto removed_photos = old_size - user_photos->photos.size();
+ CHECK(removed_photos > 0);
+ LOG_IF(ERROR, removed_photos != 1) << "Had " << removed_photos << " photos with ID " << profile_photo_id;
+ user_photos->count -= narrow_cast<int32>(removed_photos);
+ // offset was not changed
+ CHECK(user_photos->count >= 0);
+ } else {
+ // failed to find photo to remove from cache
+ // don't know how to adjust user_photos->offset, so drop photos cache
+ LOG(INFO) << "Drop photos of " << user_id;
+ user_photos->photos.clear();
+ user_photos->count = -1;
+ user_photos->offset = -1;
+ }
+ }
+ bool have_new_photo =
+ user_photos != nullptr && user_photos->count != -1 && user_photos->offset == 0 && !user_photos->photos.empty();
+
+ auto user_full = get_user_full_force(user_id);
+ if (user_full != nullptr && user_full->photo.id.get() == profile_photo_id) {
+ // user_full->photo is empty or coincides with u->photo
+ CHECK(is_main_photo_deleted);
+ }
+
+ // update ProfilePhoto in User
+ bool need_reget_user = false;
+ if (is_main_photo_deleted) {
+ if (have_new_photo) {
+ do_update_user_photo(u, user_id,
+ as_profile_photo(td_->file_manager_.get(), user_id, u->access_hash, user_photos->photos[0]),
+ false, "delete_profile_photo_from_cache");
+ } else {
+ do_update_user_photo(u, user_id, ProfilePhoto(), false, "delete_profile_photo_from_cache 2");
+ need_reget_user = user_photos == nullptr || user_photos->count != 0;
+ }
+ if (send_updates) {
+ update_user(u, user_id);
+ }
+
+ // update Photo in UserFull
+ if (user_full != nullptr) {
+ if (have_new_photo) {
+ if (user_photos->photos[0] != user_full->photo) {
+ user_full->photo = user_photos->photos[0];
+ user_full->is_changed = true;
+ }
+ } else {
+ // repair UserFull photo
+ if (!user_full->photo.is_empty()) {
+ user_full->photo = Photo();
+ user_full->is_changed = true;
+ }
+ if (user_full->expires_at > 0.0) {
+ user_full->expires_at = 0.0;
+ user_full->need_save_to_database = true;
+ }
+
+ reload_user_full(user_id, Auto());
+ }
+ if (send_updates) {
+ update_user_full(user_full, user_id, "delete_profile_photo_from_cache");
+ }
+ }
}
- if (u->inbound == LinkState::Contact && u->outbound != LinkState::Contact) {
- u->inbound = LinkState::KnowsPhoneNumber;
- u->is_changed = true;
- need_send_update = true;
+
+ return need_reget_user;
+}
+
+void ContactsManager::drop_user_photos(UserId user_id, bool is_empty, bool drop_user_full_photo, const char *source) {
+ LOG(INFO) << "Drop user photos to " << (is_empty ? "empty" : "unknown") << " from " << source;
+ auto user_photos = user_photos_.get_pointer(user_id);
+ if (user_photos != nullptr) {
+ int32 new_count = is_empty ? 0 : -1;
+ if (user_photos->count == new_count) {
+ CHECK(user_photos->photos.empty());
+ CHECK(user_photos->offset == user_photos->count);
+ } else {
+ LOG(INFO) << "Drop photos of " << user_id << " to " << (is_empty ? "empty" : "unknown") << " from " << source;
+ user_photos->photos.clear();
+ user_photos->count = new_count;
+ user_photos->offset = user_photos->count;
+ }
}
- if (need_send_update) {
- LOG(DEBUG) << "Links have changed for " << user_id;
- u->need_send_update = true;
+ if (drop_user_full_photo) {
+ auto user_full = get_user_full(user_id); // must not load UserFull
+ if (user_full != nullptr) {
+ if (!user_full->photo.is_empty()) {
+ user_full->photo = Photo();
+ user_full->is_changed = true;
+ }
+ if (!is_empty) {
+ if (user_full->expires_at > 0.0) {
+ user_full->expires_at = 0.0;
+ user_full->need_save_to_database = true;
+ }
+ reload_user_full(user_id, Auto());
+ }
+ if (user_full->is_update_user_full_sent) {
+ update_user_full(user_full, user_id, "drop_user_photos");
+ }
+ }
}
}
-void ContactsManager::invalidate_user_full(UserId user_id) {
- auto user_full = get_user_full(user_id);
+void ContactsManager::drop_user_full(UserId user_id) {
+ auto user_full = get_user_full_force(user_id);
+
+ drop_user_photos(user_id, false, false, "drop_user_full");
+
if (user_full == nullptr) {
return;
}
user_full->expires_at = 0.0;
- user_full->photos.clear();
- user_full->photo_count = -1;
- user_full->photos_offset = -1;
- user_full->is_inited = true;
+ user_full->photo = Photo();
user_full->is_blocked = false;
user_full->can_be_called = false;
+ user_full->supports_video_calls = false;
user_full->has_private_calls = false;
+ user_full->need_phone_number_privacy_exception = false;
user_full->about = string();
+ user_full->description = string();
+ user_full->description_photo = Photo();
+ user_full->description_animation_file_id = FileId();
+ user_full->menu_button = nullptr;
+ user_full->commands.clear();
user_full->common_chat_count = 0;
- user_full->bot_info = nullptr;
+ user_full->private_forward_name.clear();
+ user_full->group_administrator_rights = {};
+ user_full->broadcast_administrator_rights = {};
+ user_full->premium_gift_options.clear();
+ user_full->voice_messages_forbidden = false;
+ user_full->are_files_changed = true;
user_full->is_changed = true;
- update_user_full(user_full, user_id);
+ update_user_full(user_full, user_id, "drop_user_full");
+ td_->group_call_manager_->on_update_dialog_about(DialogId(user_id), user_full->about, true);
+}
+
+void ContactsManager::update_user_online_member_count(User *u) {
+ if (u->online_member_dialogs.empty()) {
+ return;
+ }
+
+ auto now = G()->unix_time_cached();
+ vector<DialogId> expired_dialog_ids;
+ for (const auto &it : u->online_member_dialogs) {
+ auto dialog_id = it.first;
+ auto time = it.second;
+ if (time < now - MessagesManager::ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME) {
+ expired_dialog_ids.push_back(dialog_id);
+ continue;
+ }
+
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ auto chat_full = get_chat_full(chat_id);
+ CHECK(chat_full != nullptr);
+ update_chat_online_member_count(chat_full, chat_id, false);
+ break;
+ }
+ case DialogType::Channel: {
+ auto channel_id = dialog_id.get_channel_id();
+ update_channel_online_member_count(channel_id, false);
+ break;
+ }
+ case DialogType::User:
+ case DialogType::SecretChat:
+ case DialogType::None:
+ UNREACHABLE();
+ break;
+ }
+ }
+ for (auto &dialog_id : expired_dialog_ids) {
+ u->online_member_dialogs.erase(dialog_id);
+ if (dialog_id.get_type() == DialogType::Channel) {
+ cached_channel_participants_.erase(dialog_id.get_channel_id());
+ }
+ }
+}
+
+void ContactsManager::update_chat_online_member_count(const ChatFull *chat_full, ChatId chat_id, bool is_from_server) {
+ update_dialog_online_member_count(chat_full->participants, DialogId(chat_id), is_from_server);
+}
+
+void ContactsManager::update_channel_online_member_count(ChannelId channel_id, bool is_from_server) {
+ if (!is_megagroup_channel(channel_id)) {
+ return;
+ }
+
+ auto it = cached_channel_participants_.find(channel_id);
+ if (it == cached_channel_participants_.end()) {
+ return;
+ }
+ update_dialog_online_member_count(it->second, DialogId(channel_id), is_from_server);
+}
+
+void ContactsManager::update_dialog_online_member_count(const vector<DialogParticipant> &participants,
+ DialogId dialog_id, bool is_from_server) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ CHECK(dialog_id.is_valid());
+
+ int32 online_member_count = 0;
+ int32 time = G()->unix_time();
+ for (const auto &participant : participants) {
+ if (participant.dialog_id_.get_type() != DialogType::User) {
+ continue;
+ }
+ auto user_id = participant.dialog_id_.get_user_id();
+ auto u = get_user(user_id);
+ if (u != nullptr && !u->is_deleted && !u->is_bot) {
+ if (get_user_was_online(u, user_id) > time) {
+ online_member_count++;
+ }
+ if (is_from_server) {
+ u->online_member_dialogs[dialog_id] = time;
+ }
+ }
+ }
+ td_->messages_manager_->on_update_dialog_online_member_count(dialog_id, online_member_count, is_from_server);
}
-void ContactsManager::on_get_chat_participants(tl_object_ptr<telegram_api::ChatParticipants> &&participants_ptr) {
+void ContactsManager::on_get_chat_participants(tl_object_ptr<telegram_api::ChatParticipants> &&participants_ptr,
+ bool from_update) {
switch (participants_ptr->get_id()) {
case telegram_api::chatParticipantsForbidden::ID: {
auto participants = move_tl_object_as<telegram_api::chatParticipantsForbidden>(participants_ptr);
@@ -6811,7 +12981,9 @@ void ContactsManager::on_get_chat_participants(tl_object_ptr<telegram_api::ChatP
return;
}
- invalidate_chat_full(chat_id);
+ if (from_update) {
+ drop_chat_full(chat_id);
+ }
break;
}
case telegram_api::chatParticipants::ID: {
@@ -6828,9 +13000,9 @@ void ContactsManager::on_get_chat_participants(tl_object_ptr<telegram_api::ChatP
return;
}
- ChatFull *chat_full = get_chat_full(chat_id);
+ ChatFull *chat_full = get_chat_full_force(chat_id, "telegram_api::chatParticipants");
if (chat_full == nullptr) {
- LOG(INFO) << "Ignore update of members for unknown " << chat_id;
+ LOG(INFO) << "Ignore update of members for unknown full " << chat_id;
return;
}
@@ -6839,59 +13011,42 @@ void ContactsManager::on_get_chat_participants(tl_object_ptr<telegram_api::ChatP
new_participants.reserve(participants->participants_.size());
for (auto &participant_ptr : participants->participants_) {
- DialogParticipant dialog_participant;
- switch (participant_ptr->get_id()) {
- case telegram_api::chatParticipant::ID: {
- auto participant = move_tl_object_as<telegram_api::chatParticipant>(participant_ptr);
- dialog_participant = {UserId(participant->user_id_), UserId(participant->inviter_id_), participant->date_,
- DialogParticipantStatus::Member()};
- break;
- }
- case telegram_api::chatParticipantCreator::ID: {
- auto participant = move_tl_object_as<telegram_api::chatParticipantCreator>(participant_ptr);
- new_creator_user_id = UserId(participant->user_id_);
- dialog_participant = {new_creator_user_id, new_creator_user_id, c->date,
- DialogParticipantStatus::Creator(true)};
- break;
- }
- case telegram_api::chatParticipantAdmin::ID: {
- auto participant = move_tl_object_as<telegram_api::chatParticipantAdmin>(participant_ptr);
- dialog_participant = {UserId(participant->user_id_), UserId(participant->inviter_id_), participant->date_,
- DialogParticipantStatus::GroupAdministrator(c->is_creator)};
- break;
- }
- default:
- UNREACHABLE();
+ DialogParticipant dialog_participant(std::move(participant_ptr), c->date, c->status.is_creator());
+ if (!dialog_participant.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << dialog_participant;
+ continue;
}
- LOG_IF(ERROR, !have_user(dialog_participant.user_id))
- << "Have no information about " << dialog_participant.user_id;
- LOG_IF(ERROR, !have_user(dialog_participant.inviter_user_id))
- << "Have no information about " << dialog_participant.inviter_user_id;
- if (dialog_participant.joined_date < c->date) {
- LOG_IF(ERROR, dialog_participant.joined_date < c->date - 30 && c->date >= 1486000000)
- << "Wrong join date = " << dialog_participant.joined_date << " for " << dialog_participant.user_id << ", "
- << chat_id << " was created at " << c->date;
- dialog_participant.joined_date = c->date;
+ LOG_IF(ERROR, !td_->messages_manager_->have_dialog_info(dialog_participant.dialog_id_))
+ << "Have no information about " << dialog_participant.dialog_id_ << " as a member of " << chat_id;
+ LOG_IF(ERROR, !have_user(dialog_participant.inviter_user_id_))
+ << "Have no information about " << dialog_participant.inviter_user_id_ << " as a member of " << chat_id;
+ if (dialog_participant.joined_date_ < c->date) {
+ LOG_IF(ERROR, dialog_participant.joined_date_ < c->date - 30 && c->date >= 1486000000)
+ << "Wrong join date = " << dialog_participant.joined_date_ << " for " << dialog_participant.dialog_id_
+ << ", " << chat_id << " was created at " << c->date;
+ dialog_participant.joined_date_ = c->date;
+ }
+ if (dialog_participant.status_.is_creator() && dialog_participant.dialog_id_.get_type() == DialogType::User) {
+ new_creator_user_id = dialog_participant.dialog_id_.get_user_id();
}
new_participants.push_back(std::move(dialog_participant));
}
- if (new_creator_user_id.is_valid()) {
- LOG_IF(ERROR, !have_user(new_creator_user_id))
- << "Have no information about group creator " << new_creator_user_id;
- if (chat_full->creator_user_id.is_valid() && chat_full->creator_user_id != new_creator_user_id) {
- LOG(ERROR) << "Group creator has changed from " << chat_full->creator_user_id << " to "
- << new_creator_user_id;
- }
- }
if (chat_full->creator_user_id != new_creator_user_id) {
+ if (new_creator_user_id.is_valid() && chat_full->creator_user_id.is_valid()) {
+ LOG(ERROR) << "Group creator has changed from " << chat_full->creator_user_id << " to " << new_creator_user_id
+ << " in " << chat_id;
+ }
chat_full->creator_user_id = new_creator_user_id;
chat_full->is_changed = true;
}
- on_update_chat_full_participants(chat_full, chat_id, std::move(new_participants), participants->version_);
- update_chat_full(chat_full, chat_id);
+ on_update_chat_full_participants(chat_full, chat_id, std::move(new_participants), participants->version_,
+ from_update);
+ if (from_update) {
+ update_chat_full(chat_full, chat_id, "on_get_chat_participants");
+ }
break;
}
default:
@@ -6904,76 +13059,42 @@ const DialogParticipant *ContactsManager::get_chat_participant(ChatId chat_id, U
if (chat_full == nullptr) {
return nullptr;
}
- return get_chat_participant(chat_full, user_id);
+ return get_chat_full_participant(chat_full, DialogId(user_id));
}
-const DialogParticipant *ContactsManager::get_chat_participant(const ChatFull *chat_full, UserId user_id) const {
- for (auto &dialog_participant : chat_full->participants) {
- if (dialog_participant.user_id == user_id) {
+const DialogParticipant *ContactsManager::get_chat_full_participant(const ChatFull *chat_full, DialogId dialog_id) {
+ for (const auto &dialog_participant : chat_full->participants) {
+ if (dialog_participant.dialog_id_ == dialog_id) {
return &dialog_participant;
}
}
return nullptr;
}
-DialogParticipant ContactsManager::get_dialog_participant(
- ChannelId channel_id, tl_object_ptr<telegram_api::ChannelParticipant> &&participant_ptr) const {
- switch (participant_ptr->get_id()) {
- case telegram_api::channelParticipant::ID: {
- auto participant = move_tl_object_as<telegram_api::channelParticipant>(participant_ptr);
- return {UserId(participant->user_id_), UserId(), participant->date_, DialogParticipantStatus::Member()};
- }
- case telegram_api::channelParticipantSelf::ID: {
- auto participant = move_tl_object_as<telegram_api::channelParticipantSelf>(participant_ptr);
- return {UserId(participant->user_id_), UserId(participant->inviter_id_), participant->date_,
- get_channel_status(channel_id)};
- }
- case telegram_api::channelParticipantCreator::ID: {
- auto participant = move_tl_object_as<telegram_api::channelParticipantCreator>(participant_ptr);
- return {UserId(participant->user_id_), UserId(), 0, DialogParticipantStatus::Creator(true)};
- }
- case telegram_api::channelParticipantAdmin::ID: {
- auto participant = move_tl_object_as<telegram_api::channelParticipantAdmin>(participant_ptr);
- bool can_be_edited = (participant->flags_ & telegram_api::channelParticipantAdmin::CAN_EDIT_MASK) != 0;
- return {UserId(participant->user_id_), UserId(participant->promoted_by_), participant->date_,
- get_dialog_participant_status(can_be_edited, std::move(participant->admin_rights_))};
- }
- case telegram_api::channelParticipantBanned::ID: {
- auto participant = move_tl_object_as<telegram_api::channelParticipantBanned>(participant_ptr);
- auto is_member = (participant->flags_ & telegram_api::channelParticipantBanned::LEFT_MASK) == 0;
- return {UserId(participant->user_id_), UserId(participant->kicked_by_), participant->date_,
- get_dialog_participant_status(is_member, std::move(participant->banned_rights_))};
- }
- default:
- UNREACHABLE();
- return DialogParticipant();
- }
-}
-
tl_object_ptr<td_api::chatMember> ContactsManager::get_chat_member_object(
const DialogParticipant &dialog_participant) const {
- UserId participant_user_id = dialog_participant.user_id;
- return make_tl_object<td_api::chatMember>(
- get_user_id_object(participant_user_id, "chatMember.user_id"),
- get_user_id_object(dialog_participant.inviter_user_id, "chatMember.inviter_user_id"),
- dialog_participant.joined_date, dialog_participant.status.get_chat_member_status_object(),
- get_bot_info_object(participant_user_id));
+ DialogId dialog_id = dialog_participant.dialog_id_;
+ UserId participant_user_id;
+ if (dialog_id.get_type() == DialogType::User) {
+ participant_user_id = dialog_id.get_user_id();
+ } else {
+ td_->messages_manager_->force_create_dialog(dialog_id, "get_chat_member_object", true);
+ }
+ return td_api::make_object<td_api::chatMember>(
+ get_message_sender_object_const(td_, dialog_id, "get_chat_member_object"),
+ get_user_id_object(dialog_participant.inviter_user_id_, "chatMember.inviter_user_id"),
+ dialog_participant.joined_date_, dialog_participant.status_.get_chat_member_status_object());
}
-bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &status, const string &source) {
+bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &status, const char *source) {
LOG(INFO) << "Receive " << status << " in " << channel_id << " from " << source;
- if (status.message() == CSlice("SESSION_REVOKED") || status.message() == CSlice("USER_DEACTIVATED")) {
- // authorization is lost
- return true;
- }
- if (status.code() == 420 || status.code() == 429) {
- // flood wait
- return true;
- }
if (status.message() == CSlice("BOT_METHOD_INVALID")) {
LOG(ERROR) << "Receive BOT_METHOD_INVALID from " << source;
return true;
}
+ if (G()->is_expected_error(status)) {
+ return true;
+ }
if (status.message() == "CHANNEL_PRIVATE" || status.message() == "CHANNEL_PUBLIC_GROUP_NA") {
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive " << status.message() << " in invalid " << channel_id << " from " << source;
@@ -6982,7 +13103,9 @@ bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &s
auto c = get_channel(channel_id);
if (c == nullptr) {
- if (td_->auth_manager_->is_bot() && source == "GetChannelsQuery") {
+ if (Slice(source) == Slice("GetChannelDifferenceQuery") ||
+ (td_->auth_manager_->is_bot() && Slice(source) == Slice("GetChannelsQuery"))) {
+ // get channel difference after restart
// get channel from server by its identifier
return true;
}
@@ -6993,7 +13116,7 @@ bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &s
auto debug_channel_object = oneline(to_string(get_supergroup_object(channel_id, c)));
if (c->status.is_member()) {
LOG(INFO) << "Emulate leaving " << channel_id;
- // TODO we also may try to write to public channel
+ // TODO we also may try to write to a public channel
int32 flags = 0;
if (c->is_megagroup) {
flags |= CHANNEL_FLAG_IS_MEGAGROUP;
@@ -7002,13 +13125,23 @@ bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &s
}
telegram_api::channelForbidden update(flags, false /*ignored*/, false /*ignored*/, channel_id.get(),
c->access_hash, c->title, 0);
- on_chat_update(update);
- } else if (!c->username.empty()) {
- LOG(INFO) << "Drop username of " << channel_id;
- on_update_channel_username(c, channel_id, "");
+ on_chat_update(update, "CHANNEL_PRIVATE");
+ } else if (!c->status.is_banned()) {
+ if (!c->usernames.is_empty()) {
+ LOG(INFO) << "Drop usernames of " << channel_id;
+ on_update_channel_usernames(c, channel_id, Usernames());
+ }
+
+ on_update_channel_has_location(c, channel_id, false);
+
+ on_update_channel_linked_channel_id(channel_id, ChannelId());
+
update_channel(c, channel_id);
+
+ remove_dialog_access_by_invite_link(DialogId(channel_id));
}
- LOG_IF(ERROR, have_input_peer_channel(c, AccessRights::Read))
+ invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, source);
+ LOG_IF(ERROR, have_input_peer_channel(c, channel_id, AccessRights::Read))
<< "Have read access to channel after receiving CHANNEL_PRIVATE. Channel state: "
<< oneline(to_string(get_supergroup_object(channel_id, c)))
<< ". Previous channel state: " << debug_channel_object;
@@ -7018,38 +13151,243 @@ bool ContactsManager::on_get_channel_error(ChannelId channel_id, const Status &s
return false;
}
-void ContactsManager::on_get_channel_participants_success(
- ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset, int32 limit, int64 random_id,
- int32 total_count, vector<tl_object_ptr<telegram_api::ChannelParticipant>> &&participants) {
- LOG(INFO) << "Receive " << participants.size() << " members in " << channel_id;
- auto it = received_channel_participants_.find(random_id);
- CHECK(it != received_channel_participants_.end());
+bool ContactsManager::is_user_contact(UserId user_id, bool is_mutual) const {
+ return is_user_contact(get_user(user_id), user_id, is_mutual);
+}
+
+bool ContactsManager::is_user_contact(const User *u, UserId user_id, bool is_mutual) const {
+ return u != nullptr && (is_mutual ? u->is_mutual_contact : u->is_contact) && user_id != get_my_id();
+}
+
+void ContactsManager::on_get_channel_participants(
+ ChannelId channel_id, ChannelParticipantFilter &&filter, int32 offset, int32 limit, string additional_query,
+ int32 additional_limit, tl_object_ptr<telegram_api::channels_channelParticipants> &&channel_participants,
+ Promise<DialogParticipants> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
- it->second.first = total_count;
+ on_get_users(std::move(channel_participants->users_), "on_get_channel_participants");
+ on_get_chats(std::move(channel_participants->chats_), "on_get_channel_participants");
+ int32 total_count = channel_participants->count_;
+ auto participants = std::move(channel_participants->participants_);
+ LOG(INFO) << "Receive " << participants.size() << " " << filter << " members in " << channel_id;
- auto &result = it->second.second;
- CHECK(result.empty());
+ bool is_full = offset == 0 && static_cast<int32>(participants.size()) < limit && total_count < limit;
+
+ auto channel_type = get_channel_type(channel_id);
+ vector<DialogParticipant> result;
for (auto &participant_ptr : participants) {
- result.push_back(get_dialog_participant(channel_id, std::move(participant_ptr)));
+ auto debug_participant = to_string(participant_ptr);
+ result.emplace_back(std::move(participant_ptr), channel_type);
+ const auto &participant = result.back();
+ UserId participant_user_id;
+ if (participant.dialog_id_.get_type() == DialogType::User) {
+ participant_user_id = participant.dialog_id_.get_user_id();
+ }
+ if (!participant.is_valid() || (filter.is_bots() && !is_user_bot(participant_user_id)) ||
+ (filter.is_administrators() && !participant.status_.is_administrator()) ||
+ ((filter.is_recent() || filter.is_contacts() || filter.is_search()) && !participant.status_.is_member()) ||
+ (filter.is_contacts() && !is_user_contact(participant_user_id)) ||
+ (filter.is_restricted() && !participant.status_.is_restricted()) ||
+ (filter.is_banned() && !participant.status_.is_banned())) {
+ bool skip_error = ((filter.is_administrators() || filter.is_bots()) && is_user_deleted(participant_user_id)) ||
+ (filter.is_contacts() && participant_user_id == get_my_id());
+ if (!skip_error) {
+ LOG(ERROR) << "Receive " << participant << ", while searching for " << filter << " in " << channel_id
+ << " with offset " << offset << " and limit " << limit << ": " << oneline(debug_participant);
+ }
+ result.pop_back();
+ total_count--;
+ }
+ }
+
+ if (total_count < narrow_cast<int32>(result.size())) {
+ LOG(ERROR) << "Receive total_count = " << total_count << ", but have at least " << result.size() << " " << filter
+ << " members in " << channel_id;
+ total_count = static_cast<int32>(result.size());
+ } else if (is_full && total_count > static_cast<int32>(result.size())) {
+ LOG(ERROR) << "Fix total number of " << filter << " members from " << total_count << " to " << result.size()
+ << " in " << channel_id << " for request with limit " << limit << " and received " << participants.size()
+ << " results";
+ total_count = static_cast<int32>(result.size());
+ }
+
+ const auto max_participant_count = is_megagroup_channel(channel_id) ? 975 : 195;
+ auto participant_count =
+ filter.is_recent() && total_count != 0 && total_count < max_participant_count ? total_count : -1;
+ int32 administrator_count = filter.is_administrators() ? total_count : -1;
+ if (is_full && (filter.is_administrators() || filter.is_bots() || filter.is_recent())) {
+ vector<DialogAdministrator> administrators;
+ vector<UserId> bot_user_ids;
+ {
+ if (filter.is_recent()) {
+ for (const auto &participant : result) {
+ if (participant.dialog_id_.get_type() == DialogType::User) {
+ auto participant_user_id = participant.dialog_id_.get_user_id();
+ if (participant.status_.is_administrator()) {
+ administrators.emplace_back(participant_user_id, participant.status_.get_rank(),
+ participant.status_.is_creator());
+ }
+ if (is_user_bot(participant_user_id)) {
+ bot_user_ids.push_back(participant_user_id);
+ }
+ }
+ }
+ administrator_count = narrow_cast<int32>(administrators.size());
+
+ if (is_megagroup_channel(channel_id) && !td_->auth_manager_->is_bot()) {
+ cached_channel_participants_[channel_id] = result;
+ update_channel_online_member_count(channel_id, true);
+ }
+ } else if (filter.is_administrators()) {
+ for (const auto &participant : result) {
+ if (participant.dialog_id_.get_type() == DialogType::User) {
+ administrators.emplace_back(participant.dialog_id_.get_user_id(), participant.status_.get_rank(),
+ participant.status_.is_creator());
+ }
+ }
+ } else if (filter.is_bots()) {
+ bot_user_ids = transform(result, [](const DialogParticipant &participant) {
+ CHECK(participant.dialog_id_.get_type() == DialogType::User);
+ return participant.dialog_id_.get_user_id();
+ });
+ }
+ }
+ if (filter.is_administrators() || filter.is_recent()) {
+ on_update_dialog_administrators(DialogId(channel_id), std::move(administrators), true, false);
+ }
+ if (filter.is_bots() || filter.is_recent()) {
+ on_update_channel_bot_user_ids(channel_id, std::move(bot_user_ids));
+ }
+ }
+ if (have_channel_participant_cache(channel_id)) {
+ for (const auto &participant : result) {
+ add_channel_participant_to_cache(channel_id, participant, false);
+ }
}
- if (filter.is_administrators() && offset == 0 && static_cast<int32>(participants.size()) < limit) {
- on_update_dialog_administrators(
- DialogId(channel_id),
- transform(result, [](const DialogParticipant &participant) { return participant.user_id; }), true);
+ if (participant_count != -1 || administrator_count != -1) {
+ auto channel_full = get_channel_full_force(channel_id, true, "on_get_channel_participants_success");
+ if (channel_full != nullptr) {
+ if (administrator_count == -1) {
+ administrator_count = channel_full->administrator_count;
+ }
+ if (participant_count == -1) {
+ participant_count = channel_full->participant_count;
+ }
+ if (participant_count < administrator_count) {
+ participant_count = administrator_count;
+ }
+ if (channel_full->participant_count != participant_count) {
+ channel_full->participant_count = participant_count;
+ channel_full->is_changed = true;
+ }
+ if (channel_full->administrator_count != administrator_count) {
+ channel_full->administrator_count = administrator_count;
+ channel_full->is_changed = true;
+ }
+ update_channel_full(channel_full, channel_id, "on_get_channel_participants");
+ }
+ if (participant_count != -1) {
+ auto c = get_channel(channel_id);
+ if (c != nullptr && c->participant_count != participant_count) {
+ c->participant_count = participant_count;
+ c->is_changed = true;
+ update_channel(c, channel_id);
+ }
+ }
}
+
+ if (!additional_query.empty()) {
+ auto dialog_ids = transform(result, [](const DialogParticipant &participant) { return participant.dialog_id_; });
+ std::pair<int32, vector<DialogId>> result_dialog_ids =
+ search_among_dialogs(dialog_ids, additional_query, additional_limit);
+
+ total_count = result_dialog_ids.first;
+ FlatHashSet<DialogId, DialogIdHash> result_dialog_ids_set;
+ for (auto result_dialog_id : result_dialog_ids.second) {
+ CHECK(result_dialog_id.is_valid());
+ result_dialog_ids_set.insert(result_dialog_id);
+ }
+ auto all_participants = std::move(result);
+ result.clear();
+ for (auto &participant : all_participants) {
+ if (result_dialog_ids_set.count(participant.dialog_id_)) {
+ result_dialog_ids_set.erase(participant.dialog_id_);
+ result.push_back(std::move(participant));
+ }
+ }
+ }
+
+ promise.set_value(DialogParticipants{total_count, std::move(result)});
}
-void ContactsManager::on_get_channel_participants_fail(ChannelId channel_id, ChannelParticipantsFilter filter,
- int32 offset, int32 limit, int64 random_id) {
- // clean up
- received_channel_participants_.erase(random_id);
+bool ContactsManager::have_channel_participant_cache(ChannelId channel_id) const {
+ if (!td_->auth_manager_->is_bot()) {
+ return false;
+ }
+ auto c = get_channel(channel_id);
+ return c != nullptr && c->status.is_administrator();
}
-bool ContactsManager::speculative_add_count(int32 &count, int32 new_count) {
- new_count += count;
- if (new_count < 0) {
- new_count = 0;
+void ContactsManager::add_channel_participant_to_cache(ChannelId channel_id,
+ const DialogParticipant &dialog_participant,
+ bool allow_replace) {
+ CHECK(channel_id.is_valid());
+ CHECK(dialog_participant.is_valid());
+ auto &participants = channel_participants_[channel_id];
+ if (participants.participants_.empty()) {
+ channel_participant_cache_timeout_.set_timeout_in(channel_id.get(), CHANNEL_PARTICIPANT_CACHE_TIME);
+ }
+ auto &participant_info = participants.participants_[dialog_participant.dialog_id_];
+ if (participant_info.last_access_date_ > 0 && !allow_replace) {
+ return;
+ }
+ participant_info.participant_ = dialog_participant;
+ participant_info.last_access_date_ = G()->unix_time();
+}
+
+void ContactsManager::update_channel_participant_status_cache(ChannelId channel_id, DialogId participant_dialog_id,
+ DialogParticipantStatus &&dialog_participant_status) {
+ CHECK(channel_id.is_valid());
+ CHECK(participant_dialog_id.is_valid());
+ auto channel_participants_it = channel_participants_.find(channel_id);
+ if (channel_participants_it == channel_participants_.end()) {
+ return;
+ }
+ auto &participants = channel_participants_it->second;
+ auto it = participants.participants_.find(participant_dialog_id);
+ if (it == participants.participants_.end()) {
+ return;
+ }
+ auto &participant_info = it->second;
+ LOG(INFO) << "Update cached status of " << participant_dialog_id << " in " << channel_id << " from "
+ << participant_info.participant_.status_ << " to " << dialog_participant_status;
+ participant_info.participant_.status_ = std::move(dialog_participant_status);
+ participant_info.last_access_date_ = G()->unix_time();
+}
+
+const DialogParticipant *ContactsManager::get_channel_participant_from_cache(ChannelId channel_id,
+ DialogId participant_dialog_id) {
+ auto channel_participants_it = channel_participants_.find(channel_id);
+ if (channel_participants_it == channel_participants_.end()) {
+ return nullptr;
+ }
+
+ auto &participants = channel_participants_it->second.participants_;
+ CHECK(!participants.empty());
+ auto it = participants.find(participant_dialog_id);
+ if (it != participants.end()) {
+ it->second.participant_.status_.update_restrictions();
+ it->second.last_access_date_ = G()->unix_time();
+ return &it->second.participant_;
+ }
+ return nullptr;
+}
+
+bool ContactsManager::speculative_add_count(int32 &count, int32 delta_count, int32 min_count) {
+ auto new_count = count + delta_count;
+ if (new_count < min_count) {
+ new_count = min_count;
}
if (new_count == count) {
return false;
@@ -7059,171 +13397,628 @@ bool ContactsManager::speculative_add_count(int32 &count, int32 new_count) {
return true;
}
-void ContactsManager::speculative_add_channel_participants(ChannelId channel_id, int32 new_participant_count,
- bool by_me) {
+void ContactsManager::speculative_add_channel_participants(ChannelId channel_id, const vector<UserId> &added_user_ids,
+ UserId inviter_user_id, int32 date, bool by_me) {
+ auto it = cached_channel_participants_.find(channel_id);
+ auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_participants");
+ bool is_participants_cache_changed = false;
+
+ int32 delta_participant_count = 0;
+ for (auto user_id : added_user_ids) {
+ if (!user_id.is_valid()) {
+ continue;
+ }
+
+ delta_participant_count++;
+
+ if (it != cached_channel_participants_.end()) {
+ auto &participants = it->second;
+ bool is_found = false;
+ for (auto &participant : participants) {
+ if (participant.dialog_id_ == DialogId(user_id)) {
+ is_found = true;
+ break;
+ }
+ }
+ if (!is_found) {
+ is_participants_cache_changed = true;
+ participants.emplace_back(DialogId(user_id), inviter_user_id, date, DialogParticipantStatus::Member());
+ }
+ }
+
+ if (channel_full != nullptr && is_user_bot(user_id) && !td::contains(channel_full->bot_user_ids, user_id)) {
+ channel_full->bot_user_ids.push_back(user_id);
+ channel_full->need_save_to_database = true;
+ reload_channel_full(channel_id, Promise<Unit>(), "speculative_add_channel_participants");
+
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id),
+ channel_full->bot_user_ids, false);
+ }
+ }
+ if (is_participants_cache_changed) {
+ update_channel_online_member_count(channel_id, false);
+ }
+ if (channel_full != nullptr) {
+ if (channel_full->is_changed) {
+ channel_full->speculative_version++;
+ }
+ update_channel_full(channel_full, channel_id, "speculative_add_channel_participants");
+ }
+ if (delta_participant_count == 0) {
+ return;
+ }
+
+ speculative_add_channel_participant_count(channel_id, delta_participant_count, by_me);
+}
+
+void ContactsManager::speculative_delete_channel_participant(ChannelId channel_id, UserId deleted_user_id, bool by_me) {
+ if (!deleted_user_id.is_valid()) {
+ return;
+ }
+
+ auto it = cached_channel_participants_.find(channel_id);
+ if (it != cached_channel_participants_.end()) {
+ auto &participants = it->second;
+ for (size_t i = 0; i < participants.size(); i++) {
+ if (participants[i].dialog_id_ == DialogId(deleted_user_id)) {
+ participants.erase(participants.begin() + i);
+ update_channel_online_member_count(channel_id, false);
+ break;
+ }
+ }
+ }
+
+ if (is_user_bot(deleted_user_id)) {
+ auto channel_full = get_channel_full_force(channel_id, true, "speculative_delete_channel_participant");
+ if (channel_full != nullptr && td::remove(channel_full->bot_user_ids, deleted_user_id)) {
+ channel_full->need_save_to_database = true;
+ update_channel_full(channel_full, channel_id, "speculative_delete_channel_participant");
+
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id),
+ channel_full->bot_user_ids, false);
+ }
+ }
+
+ speculative_add_channel_participant_count(channel_id, -1, by_me);
+}
+
+void ContactsManager::speculative_add_channel_participant_count(ChannelId channel_id, int32 delta_participant_count,
+ bool by_me) {
if (by_me) {
// Currently ignore all changes made by the current user, because they may be already counted
- invalidate_channel_full(channel_id); // just in case
+ invalidate_channel_full(channel_id, false, "speculative_add_channel_participant_count"); // just in case
return;
}
- auto c = get_channel(channel_id);
- if (c != nullptr && c->participant_count != 0 && speculative_add_count(c->participant_count, new_participant_count)) {
- c->need_send_update = true;
+ auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_participant_count");
+ auto min_count = channel_full == nullptr ? 0 : channel_full->administrator_count;
+
+ auto c = get_channel_force(channel_id);
+ if (c != nullptr && c->participant_count != 0 &&
+ speculative_add_count(c->participant_count, delta_participant_count, min_count)) {
+ c->is_changed = true;
update_channel(c, channel_id);
}
- auto channel_full = get_channel_full(channel_id);
if (channel_full == nullptr) {
return;
}
- channel_full->is_changed |= speculative_add_count(channel_full->participant_count, new_participant_count);
+ channel_full->is_changed |=
+ speculative_add_count(channel_full->participant_count, delta_participant_count, min_count);
+
+ if (channel_full->is_changed) {
+ channel_full->speculative_version++;
+ }
- update_channel_full(channel_full, channel_id);
+ update_channel_full(channel_full, channel_id, "speculative_add_channel_participant_count");
}
-void ContactsManager::speculative_add_channel_users(ChannelId channel_id, DialogParticipantStatus status,
- DialogParticipantStatus old_status) {
- auto c = get_channel(channel_id);
+void ContactsManager::speculative_add_channel_user(ChannelId channel_id, UserId user_id,
+ const DialogParticipantStatus &new_status,
+ const DialogParticipantStatus &old_status) {
+ auto c = get_channel_force(channel_id);
+ // channel full must be loaded before c->participant_count is updated, because on_load_channel_full_from_database
+ // must copy the initial c->participant_count before it is speculatibely updated
+ auto channel_full = get_channel_full_force(channel_id, true, "speculative_add_channel_user");
+ int32 min_count = 0;
+ LOG(INFO) << "Speculatively change status of " << user_id << " in " << channel_id << " from " << old_status << " to "
+ << new_status;
+ if (channel_full != nullptr) {
+ channel_full->is_changed |= speculative_add_count(channel_full->administrator_count,
+ new_status.is_administrator() - old_status.is_administrator());
+ min_count = channel_full->administrator_count;
+ }
+
if (c != nullptr && c->participant_count != 0 &&
- speculative_add_count(c->participant_count, status.is_member() - old_status.is_member())) {
- c->need_send_update = true;
+ speculative_add_count(c->participant_count, new_status.is_member() - old_status.is_member(), min_count)) {
+ c->is_changed = true;
update_channel(c, channel_id);
}
- auto channel_full = get_channel_full(channel_id);
+ if (new_status.is_administrator() != old_status.is_administrator() ||
+ new_status.get_rank() != old_status.get_rank()) {
+ DialogId dialog_id(channel_id);
+ auto administrators_it = dialog_administrators_.find(dialog_id);
+ if (administrators_it != dialog_administrators_.end()) {
+ auto administrators = administrators_it->second;
+ if (new_status.is_administrator()) {
+ bool is_found = false;
+ for (auto &administrator : administrators) {
+ if (administrator.get_user_id() == user_id) {
+ is_found = true;
+ if (administrator.get_rank() != new_status.get_rank() ||
+ administrator.is_creator() != new_status.is_creator()) {
+ administrator = DialogAdministrator(user_id, new_status.get_rank(), new_status.is_creator());
+ on_update_dialog_administrators(dialog_id, std::move(administrators), true, false);
+ }
+ break;
+ }
+ }
+ if (!is_found) {
+ administrators.emplace_back(user_id, new_status.get_rank(), new_status.is_creator());
+ on_update_dialog_administrators(dialog_id, std::move(administrators), true, false);
+ }
+ } else {
+ size_t i = 0;
+ while (i != administrators.size() && administrators[i].get_user_id() != user_id) {
+ i++;
+ }
+ if (i != administrators.size()) {
+ administrators.erase(administrators.begin() + i);
+ on_update_dialog_administrators(dialog_id, std::move(administrators), true, false);
+ }
+ }
+ }
+ }
+
+ auto it = cached_channel_participants_.find(channel_id);
+ if (it != cached_channel_participants_.end()) {
+ auto &participants = it->second;
+ bool is_found = false;
+ for (size_t i = 0; i < participants.size(); i++) {
+ if (participants[i].dialog_id_ == DialogId(user_id)) {
+ if (!new_status.is_member()) {
+ participants.erase(participants.begin() + i);
+ update_channel_online_member_count(channel_id, false);
+ } else {
+ participants[i].status_ = new_status;
+ }
+ is_found = true;
+ break;
+ }
+ }
+ if (!is_found && new_status.is_member()) {
+ participants.emplace_back(DialogId(user_id), get_my_id(), G()->unix_time(), new_status);
+ update_channel_online_member_count(channel_id, false);
+ }
+ }
+
if (channel_full == nullptr) {
return;
}
+ channel_full->is_changed |= speculative_add_count(channel_full->participant_count,
+ new_status.is_member() - old_status.is_member(), min_count);
channel_full->is_changed |=
- speculative_add_count(channel_full->participant_count, status.is_member() - old_status.is_member());
- channel_full->is_changed |= speculative_add_count(channel_full->administrator_count,
- status.is_administrator() - old_status.is_administrator());
+ speculative_add_count(channel_full->restricted_count, new_status.is_restricted() - old_status.is_restricted());
channel_full->is_changed |=
- speculative_add_count(channel_full->restricted_count, status.is_restricted() - old_status.is_restricted());
- channel_full->is_changed |=
- speculative_add_count(channel_full->banned_count, status.is_banned() - old_status.is_banned());
+ speculative_add_count(channel_full->banned_count, new_status.is_banned() - old_status.is_banned());
+
+ if (channel_full->is_changed) {
+ channel_full->speculative_version++;
+ }
+
+ if (new_status.is_member() != old_status.is_member() && is_user_bot(user_id)) {
+ if (new_status.is_member()) {
+ if (!td::contains(channel_full->bot_user_ids, user_id)) {
+ channel_full->bot_user_ids.push_back(user_id);
+ channel_full->need_save_to_database = true;
+ reload_channel_full(channel_id, Promise<Unit>(), "speculative_add_channel_user");
- update_channel_full(channel_full, channel_id);
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id),
+ channel_full->bot_user_ids, false);
+ }
+ } else {
+ if (td::remove(channel_full->bot_user_ids, user_id)) {
+ channel_full->need_save_to_database = true;
+
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id),
+ channel_full->bot_user_ids, false);
+ }
+ }
+ }
+
+ update_channel_full(channel_full, channel_id, "speculative_add_channel_user");
}
-void ContactsManager::invalidate_channel_full(ChannelId channel_id) {
- LOG(INFO) << "Invalidate channel full for " << channel_id;
- // drop channel full cache
- // TODO at least need to invalidate channel invite link
- auto channel_full = get_channel_full(channel_id);
+void ContactsManager::invalidate_channel_full(ChannelId channel_id, bool need_drop_slow_mode_delay,
+ const char *source) {
+ LOG(INFO) << "Invalidate supergroup full for " << channel_id << " from " << source;
+ auto channel_full = get_channel_full(channel_id, true, "invalidate_channel_full"); // must not load ChannelFull
if (channel_full != nullptr) {
- channel_full->expires_at = 0.0;
+ do_invalidate_channel_full(channel_full, channel_id, need_drop_slow_mode_delay);
+ update_channel_full(channel_full, channel_id, source);
+ } else if (channel_id.is_valid()) {
+ invalidated_channels_full_.insert(channel_id);
}
+}
- // channel_full->is_changed = true;
- // update_channel_full(channel_full, channel_id);
+void ContactsManager::do_invalidate_channel_full(ChannelFull *channel_full, ChannelId channel_id,
+ bool need_drop_slow_mode_delay) {
+ CHECK(channel_full != nullptr);
+ td_->messages_manager_->on_dialog_info_full_invalidated(DialogId(channel_id));
+ if (channel_full->expires_at >= Time::now()) {
+ channel_full->expires_at = 0.0;
+ channel_full->need_save_to_database = true;
+ }
+ if (need_drop_slow_mode_delay && channel_full->slow_mode_delay != 0) {
+ channel_full->slow_mode_delay = 0;
+ channel_full->slow_mode_next_send_date = 0;
+ channel_full->is_slow_mode_next_send_date_changed = true;
+ channel_full->is_changed = true;
+ }
}
-void ContactsManager::on_get_chat_invite_link(ChatId chat_id,
- tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr) {
- CHECK(chat_id.is_valid());
- if (!have_chat(chat_id)) {
- LOG(ERROR) << chat_id << " not found";
+void ContactsManager::on_update_chat_full_photo(ChatFull *chat_full, ChatId chat_id, Photo photo) {
+ CHECK(chat_full != nullptr);
+ if (photo != chat_full->photo) {
+ chat_full->photo = std::move(photo);
+ chat_full->is_changed = true;
+ }
+
+ auto photo_file_ids = photo_get_file_ids(chat_full->photo);
+ if (chat_full->registered_photo_file_ids == photo_file_ids) {
return;
}
- auto chat_full = get_chat_full(chat_id);
- if (chat_full == nullptr) {
- update_invite_link(chat_invite_links_[chat_id], std::move(invite_link_ptr));
+ auto &file_source_id = chat_full->file_source_id;
+ if (!file_source_id.is_valid()) {
+ file_source_id = chat_full_file_source_ids_.get(chat_id);
+ if (file_source_id.is_valid()) {
+ VLOG(file_references) << "Move " << file_source_id << " inside of " << chat_id;
+ chat_full_file_source_ids_.erase(chat_id);
+ } else {
+ VLOG(file_references) << "Need to create new file source for full " << chat_id;
+ file_source_id = td_->file_reference_manager_->create_chat_full_file_source(chat_id);
+ }
+ }
+
+ td_->file_manager_->change_files_source(file_source_id, chat_full->registered_photo_file_ids, photo_file_ids);
+ chat_full->registered_photo_file_ids = std::move(photo_file_ids);
+}
+
+void ContactsManager::on_update_channel_full_photo(ChannelFull *channel_full, ChannelId channel_id, Photo photo) {
+ CHECK(channel_full != nullptr);
+ if (photo != channel_full->photo) {
+ channel_full->photo = std::move(photo);
+ channel_full->is_changed = true;
+ }
+
+ auto photo_file_ids = photo_get_file_ids(channel_full->photo);
+ if (channel_full->registered_photo_file_ids == photo_file_ids) {
return;
}
- on_update_chat_full_invite_link(chat_full, std::move(invite_link_ptr));
- update_chat_full(chat_full, chat_id);
+
+ auto &file_source_id = channel_full->file_source_id;
+ if (!file_source_id.is_valid()) {
+ file_source_id = channel_full_file_source_ids_.get(channel_id);
+ if (file_source_id.is_valid()) {
+ VLOG(file_references) << "Move " << file_source_id << " inside of " << channel_id;
+ channel_full_file_source_ids_.erase(channel_id);
+ } else {
+ VLOG(file_references) << "Need to create new file source for full " << channel_id;
+ file_source_id = td_->file_reference_manager_->create_channel_full_file_source(channel_id);
+ }
+ }
+
+ td_->file_manager_->change_files_source(file_source_id, channel_full->registered_photo_file_ids, photo_file_ids);
+ channel_full->registered_photo_file_ids = std::move(photo_file_ids);
}
-void ContactsManager::on_update_chat_full_invite_link(
- ChatFull *chat_full, tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr) {
+void ContactsManager::on_get_permanent_dialog_invite_link(DialogId dialog_id, const DialogInviteLink &invite_link) {
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ auto chat_full = get_chat_full_force(chat_id, "on_get_permanent_dialog_invite_link");
+ if (chat_full != nullptr && update_permanent_invite_link(chat_full->invite_link, invite_link)) {
+ chat_full->is_changed = true;
+ update_chat_full(chat_full, chat_id, "on_get_permanent_dialog_invite_link");
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ auto channel_id = dialog_id.get_channel_id();
+ auto channel_full = get_channel_full_force(channel_id, true, "on_get_permanent_dialog_invite_link");
+ if (channel_full != nullptr && update_permanent_invite_link(channel_full->invite_link, invite_link)) {
+ channel_full->is_changed = true;
+ update_channel_full(channel_full, channel_id, "on_get_permanent_dialog_invite_link");
+ }
+ break;
+ }
+ case DialogType::User:
+ case DialogType::SecretChat:
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+}
+
+void ContactsManager::on_update_chat_full_invite_link(ChatFull *chat_full,
+ tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link) {
CHECK(chat_full != nullptr);
- if (update_invite_link(chat_full->invite_link, std::move(invite_link_ptr))) {
+ if (update_permanent_invite_link(chat_full->invite_link, DialogInviteLink(std::move(invite_link), "ChatFull"))) {
chat_full->is_changed = true;
}
}
-void ContactsManager::on_get_channel_invite_link(ChannelId channel_id,
- tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr) {
- CHECK(channel_id.is_valid());
- if (!have_channel(channel_id)) {
- LOG(ERROR) << channel_id << " not found";
+void ContactsManager::on_update_channel_full_invite_link(
+ ChannelFull *channel_full, tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link) {
+ CHECK(channel_full != nullptr);
+ if (update_permanent_invite_link(channel_full->invite_link,
+ DialogInviteLink(std::move(invite_link), "ChannelFull"))) {
+ channel_full->is_changed = true;
+ }
+}
+
+void ContactsManager::remove_linked_channel_id(ChannelId channel_id) {
+ if (!channel_id.is_valid()) {
return;
}
+ auto linked_channel_id = linked_channel_ids_.get(channel_id);
+ if (linked_channel_id.is_valid()) {
+ linked_channel_ids_.erase(channel_id);
+ linked_channel_ids_.erase(linked_channel_id);
+ }
+}
+
+ChannelId ContactsManager::get_linked_channel_id(ChannelId channel_id) const {
auto channel_full = get_channel_full(channel_id);
- if (channel_full == nullptr) {
- update_invite_link(channel_invite_links_[channel_id], std::move(invite_link_ptr));
- return;
+ if (channel_full != nullptr) {
+ return channel_full->linked_channel_id;
}
- on_update_channel_full_invite_link(channel_full, std::move(invite_link_ptr));
- update_channel_full(channel_full, channel_id);
+
+ return linked_channel_ids_.get(channel_id);
}
-void ContactsManager::on_update_channel_full_invite_link(
- ChannelFull *channel_full, tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr) {
- CHECK(channel_full != nullptr);
- if (update_invite_link(channel_full->invite_link, std::move(invite_link_ptr))) {
+void ContactsManager::on_update_channel_full_linked_channel_id(ChannelFull *channel_full, ChannelId channel_id,
+ ChannelId linked_channel_id) {
+ auto old_linked_channel_id = get_linked_channel_id(channel_id);
+ LOG(INFO) << "Uplate linked channel in " << channel_id << " from " << old_linked_channel_id << " to "
+ << linked_channel_id;
+
+ if (channel_full != nullptr && channel_full->linked_channel_id != linked_channel_id &&
+ channel_full->linked_channel_id.is_valid()) {
+ get_channel_force(channel_full->linked_channel_id);
+ get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 0");
+ }
+ auto old_linked_linked_channel_id = get_linked_channel_id(linked_channel_id);
+
+ remove_linked_channel_id(channel_id);
+ remove_linked_channel_id(linked_channel_id);
+ if (channel_id.is_valid() && linked_channel_id.is_valid()) {
+ linked_channel_ids_.set(channel_id, linked_channel_id);
+ linked_channel_ids_.set(linked_channel_id, channel_id);
+ }
+
+ if (channel_full != nullptr && channel_full->linked_channel_id != linked_channel_id) {
+ if (channel_full->linked_channel_id.is_valid()) {
+ // remove link from a previously linked channel_full
+ auto linked_channel = get_channel_force(channel_full->linked_channel_id);
+ if (linked_channel != nullptr && linked_channel->has_linked_channel) {
+ linked_channel->has_linked_channel = false;
+ linked_channel->is_changed = true;
+ update_channel(linked_channel, channel_full->linked_channel_id);
+ reload_channel(channel_full->linked_channel_id, Auto());
+ }
+ auto linked_channel_full =
+ get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 1");
+ if (linked_channel_full != nullptr && linked_channel_full->linked_channel_id == channel_id) {
+ linked_channel_full->linked_channel_id = ChannelId();
+ linked_channel_full->is_changed = true;
+ update_channel_full(linked_channel_full, channel_full->linked_channel_id,
+ "on_update_channel_full_linked_channel_id 3");
+ }
+ }
+
+ channel_full->linked_channel_id = linked_channel_id;
channel_full->is_changed = true;
+
+ if (channel_full->linked_channel_id.is_valid()) {
+ // add link from a newly linked channel_full
+ auto linked_channel = get_channel_force(channel_full->linked_channel_id);
+ if (linked_channel != nullptr && !linked_channel->has_linked_channel) {
+ linked_channel->has_linked_channel = true;
+ linked_channel->is_changed = true;
+ update_channel(linked_channel, channel_full->linked_channel_id);
+ reload_channel(channel_full->linked_channel_id, Auto());
+ }
+ auto linked_channel_full =
+ get_channel_full_force(channel_full->linked_channel_id, true, "on_update_channel_full_linked_channel_id 2");
+ if (linked_channel_full != nullptr && linked_channel_full->linked_channel_id != channel_id) {
+ linked_channel_full->linked_channel_id = channel_id;
+ linked_channel_full->is_changed = true;
+ update_channel_full(linked_channel_full, channel_full->linked_channel_id,
+ "on_update_channel_full_linked_channel_id 4");
+ }
+ }
+ }
+
+ Channel *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+ if (linked_channel_id.is_valid() != c->has_linked_channel) {
+ c->has_linked_channel = linked_channel_id.is_valid();
+ c->is_changed = true;
+ update_channel(c, channel_id);
+ }
+
+ if (old_linked_channel_id != linked_channel_id) {
+ // must be called after the linked channel is changed
+ td_->messages_manager_->on_dialog_linked_channel_updated(DialogId(channel_id), old_linked_channel_id,
+ linked_channel_id);
+ }
+
+ if (linked_channel_id.is_valid()) {
+ auto new_linked_linked_channel_id = get_linked_channel_id(linked_channel_id);
+ LOG(INFO) << "Uplate linked channel in " << linked_channel_id << " from " << old_linked_linked_channel_id << " to "
+ << new_linked_linked_channel_id;
+ if (old_linked_linked_channel_id != new_linked_linked_channel_id) {
+ // must be called after the linked channel is changed
+ td_->messages_manager_->on_dialog_linked_channel_updated(
+ DialogId(linked_channel_id), old_linked_linked_channel_id, new_linked_linked_channel_id);
+ }
}
}
-void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link,
- tl_object_ptr<telegram_api::ChatInvite> &&chat_invite_ptr) {
- auto &invite_link_info = invite_link_infos_[invite_link];
- if (invite_link_info == nullptr) {
- invite_link_info = make_unique<InviteLinkInfo>();
+void ContactsManager::on_update_channel_full_location(ChannelFull *channel_full, ChannelId channel_id,
+ const DialogLocation &location) {
+ if (channel_full->location != location) {
+ channel_full->location = location;
+ channel_full->is_changed = true;
+ }
+
+ Channel *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+ on_update_channel_has_location(c, channel_id, !location.empty());
+ update_channel(c, channel_id);
+}
+
+void ContactsManager::on_update_channel_full_slow_mode_delay(ChannelFull *channel_full, ChannelId channel_id,
+ int32 slow_mode_delay, int32 slow_mode_next_send_date) {
+ if (slow_mode_delay < 0) {
+ LOG(ERROR) << "Receive slow mode delay " << slow_mode_delay << " in " << channel_id;
+ slow_mode_delay = 0;
+ }
+
+ if (channel_full->slow_mode_delay != slow_mode_delay) {
+ channel_full->slow_mode_delay = slow_mode_delay;
+ channel_full->is_changed = true;
+ }
+ on_update_channel_full_slow_mode_next_send_date(channel_full, slow_mode_next_send_date);
+
+ Channel *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+ bool is_slow_mode_enabled = slow_mode_delay != 0;
+ if (is_slow_mode_enabled != c->is_slow_mode_enabled) {
+ c->is_slow_mode_enabled = is_slow_mode_enabled;
+ c->is_changed = true;
+ update_channel(c, channel_id);
}
+}
+void ContactsManager::on_update_channel_full_slow_mode_next_send_date(ChannelFull *channel_full,
+ int32 slow_mode_next_send_date) {
+ if (slow_mode_next_send_date < 0) {
+ LOG(ERROR) << "Receive slow mode next send date " << slow_mode_next_send_date;
+ slow_mode_next_send_date = 0;
+ }
+ if (channel_full->slow_mode_delay == 0 && slow_mode_next_send_date > 0) {
+ LOG(ERROR) << "Slow mode is disabled, but next send date is " << slow_mode_next_send_date;
+ slow_mode_next_send_date = 0;
+ }
+
+ if (slow_mode_next_send_date != 0) {
+ auto now = G()->unix_time();
+ if (slow_mode_next_send_date <= now) {
+ slow_mode_next_send_date = 0;
+ }
+ if (slow_mode_next_send_date > now + 3601) {
+ slow_mode_next_send_date = now + 3601;
+ }
+ }
+ if (channel_full->slow_mode_next_send_date != slow_mode_next_send_date) {
+ channel_full->slow_mode_next_send_date = slow_mode_next_send_date;
+ channel_full->is_slow_mode_next_send_date_changed = true;
+ channel_full->is_changed = true;
+ }
+}
+
+void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link,
+ tl_object_ptr<telegram_api::ChatInvite> &&chat_invite_ptr,
+ Promise<Unit> &&promise) {
CHECK(chat_invite_ptr != nullptr);
switch (chat_invite_ptr->get_id()) {
- case telegram_api::chatInviteAlready::ID: {
- auto chat_invite_already = move_tl_object_as<telegram_api::chatInviteAlready>(chat_invite_ptr);
- auto chat_id = get_chat_id(chat_invite_already->chat_);
+ case telegram_api::chatInviteAlready::ID:
+ case telegram_api::chatInvitePeek::ID: {
+ telegram_api::object_ptr<telegram_api::Chat> chat = nullptr;
+ int32 accessible_before = 0;
+ if (chat_invite_ptr->get_id() == telegram_api::chatInviteAlready::ID) {
+ auto chat_invite_already = move_tl_object_as<telegram_api::chatInviteAlready>(chat_invite_ptr);
+ chat = std::move(chat_invite_already->chat_);
+ } else {
+ auto chat_invite_peek = move_tl_object_as<telegram_api::chatInvitePeek>(chat_invite_ptr);
+ chat = std::move(chat_invite_peek->chat_);
+ accessible_before = chat_invite_peek->expires_;
+ }
+ auto chat_id = get_chat_id(chat);
if (chat_id != ChatId() && !chat_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << chat_id;
chat_id = ChatId();
}
- auto channel_id = get_channel_id(chat_invite_already->chat_);
+ auto channel_id = get_channel_id(chat);
if (channel_id != ChannelId() && !channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id;
channel_id = ChannelId();
}
- on_get_chat(std::move(chat_invite_already->chat_));
+ if (accessible_before != 0 && (!channel_id.is_valid() || accessible_before < 0)) {
+ LOG(ERROR) << "Receive expires = " << accessible_before << " for invite link " << invite_link << " to "
+ << to_string(chat);
+ accessible_before = 0;
+ }
+ on_get_chat(std::move(chat), "chatInviteAlready");
CHECK(chat_id == ChatId() || channel_id == ChannelId());
- invite_link_info->chat_id = chat_id;
- invite_link_info->channel_id = channel_id;
- if (chat_id.is_valid()) {
- on_get_chat_invite_link(chat_id, make_tl_object<telegram_api::chatInviteExported>(invite_link));
+ // the access is already expired, reget the info
+ if (accessible_before != 0 && accessible_before <= G()->unix_time() + 1) {
+ td_->create_handler<CheckChatInviteQuery>(std::move(promise))->send(invite_link);
+ return;
}
- if (channel_id.is_valid()) {
- on_get_channel_invite_link(channel_id, make_tl_object<telegram_api::chatInviteExported>(invite_link));
+
+ DialogId dialog_id = chat_id.is_valid() ? DialogId(chat_id) : DialogId(channel_id);
+ auto &invite_link_info = invite_link_infos_[invite_link];
+ if (invite_link_info == nullptr) {
+ invite_link_info = make_unique<InviteLinkInfo>();
+ }
+ invite_link_info->dialog_id = dialog_id;
+ if (accessible_before != 0 && dialog_id.is_valid()) {
+ auto &access = dialog_access_by_invite_link_[dialog_id];
+ access.invite_links.insert(invite_link);
+ if (access.accessible_before < accessible_before) {
+ access.accessible_before = accessible_before;
+
+ auto expires_in = accessible_before - G()->unix_time() - 1;
+ invite_link_info_expire_timeout_.set_timeout_in(dialog_id.get(), expires_in);
+ }
}
break;
}
case telegram_api::chatInvite::ID: {
auto chat_invite = move_tl_object_as<telegram_api::chatInvite>(chat_invite_ptr);
- invite_link_info->chat_id = ChatId();
- invite_link_info->channel_id = ChannelId();
- invite_link_info->title = chat_invite->title_;
- invite_link_info->photo = get_dialog_photo(td_->file_manager_.get(), std::move(chat_invite->photo_));
- invite_link_info->participant_count = chat_invite->participants_count_;
- invite_link_info->participant_user_ids.clear();
+ vector<UserId> participant_user_ids;
for (auto &user : chat_invite->participants_) {
auto user_id = get_user_id(user);
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << user_id;
- } else {
- on_get_user(std::move(user));
+ continue;
}
- invite_link_info->participant_user_ids.push_back(user_id);
+
+ on_get_user(std::move(user), "chatInvite");
+ participant_user_ids.push_back(user_id);
+ }
+
+ auto &invite_link_info = invite_link_infos_[invite_link];
+ if (invite_link_info == nullptr) {
+ invite_link_info = make_unique<InviteLinkInfo>();
}
+ invite_link_info->dialog_id = DialogId();
+ invite_link_info->title = chat_invite->title_;
+ invite_link_info->photo = get_photo(td_->file_manager_.get(), std::move(chat_invite->photo_), DialogId());
+ invite_link_info->description = std::move(chat_invite->about_);
+ invite_link_info->participant_count = chat_invite->participants_count_;
+ invite_link_info->participant_user_ids = std::move(participant_user_ids);
+ invite_link_info->creates_join_request = std::move(chat_invite->request_needed_);
invite_link_info->is_chat = (chat_invite->flags_ & CHAT_INVITE_FLAG_IS_CHANNEL) == 0;
invite_link_info->is_channel = (chat_invite->flags_ & CHAT_INVITE_FLAG_IS_CHANNEL) != 0;
@@ -7247,55 +14042,29 @@ void ContactsManager::on_get_dialog_invite_link_info(const string &invite_link,
default:
UNREACHABLE();
}
+ promise.set_value(Unit());
}
-bool ContactsManager::is_valid_invite_link(const string &invite_link) {
- return !get_dialog_invite_link_hash(invite_link).empty();
-}
-
-Slice ContactsManager::get_dialog_invite_link_hash(const string &invite_link) {
- auto lower_cased_invite_link_str = to_lower(invite_link);
- Slice lower_cased_invite_link = lower_cased_invite_link_str;
- size_t offset = 0;
- if (begins_with(lower_cased_invite_link, "https://")) {
- offset = 8;
- } else if (begins_with(lower_cased_invite_link, "http://")) {
- offset = 7;
+void ContactsManager::remove_dialog_access_by_invite_link(DialogId dialog_id) {
+ auto access_it = dialog_access_by_invite_link_.find(dialog_id);
+ if (access_it == dialog_access_by_invite_link_.end()) {
+ return;
}
- lower_cased_invite_link.remove_prefix(offset);
- for (auto &url : INVITE_LINK_URLS) {
- if (begins_with(lower_cased_invite_link, url)) {
- return Slice(invite_link).substr(url.size() + offset);
- }
+ for (auto &invite_link : access_it->second.invite_links) {
+ invalidate_invite_link_info(invite_link);
}
- return Slice();
-}
+ dialog_access_by_invite_link_.erase(access_it);
-bool ContactsManager::update_invite_link(string &invite_link,
- tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr) {
- string new_invite_link;
- if (invite_link_ptr != nullptr) {
- switch (invite_link_ptr->get_id()) {
- case telegram_api::chatInviteEmpty::ID:
- // link is empty
- break;
- case telegram_api::chatInviteExported::ID: {
- auto chat_invite_exported = move_tl_object_as<telegram_api::chatInviteExported>(invite_link_ptr);
- new_invite_link = std::move(chat_invite_exported->link_);
- break;
- }
- default:
- UNREACHABLE();
- }
- }
+ invite_link_info_expire_timeout_.cancel_timeout(dialog_id.get());
+}
+bool ContactsManager::update_permanent_invite_link(DialogInviteLink &invite_link, DialogInviteLink new_invite_link) {
if (new_invite_link != invite_link) {
- if (!invite_link.empty()) {
- invite_link_infos_.erase(invite_link);
+ if (invite_link.is_valid() && invite_link.get_invite_link() != new_invite_link.get_invite_link()) {
+ // old link was invalidated
+ invite_link_infos_.erase(invite_link.get_invite_link());
}
- LOG_IF(ERROR, !new_invite_link.empty() && !is_valid_invite_link(new_invite_link))
- << "Unsupported invite link " << new_invite_link;
invite_link = std::move(new_invite_link);
return true;
@@ -7303,12 +14072,13 @@ bool ContactsManager::update_invite_link(string &invite_link,
return false;
}
-void ContactsManager::invalidate_invite_link(const string &invite_link) {
+void ContactsManager::invalidate_invite_link_info(const string &invite_link) {
+ LOG(INFO) << "Invalidate info about invite link " << invite_link;
invite_link_infos_.erase(invite_link);
}
void ContactsManager::repair_chat_participants(ChatId chat_id) {
- send_get_chat_full_query(chat_id, Auto());
+ send_get_chat_full_query(chat_id, Auto(), "repair_chat_participants");
}
void ContactsManager::on_update_chat_add_user(ChatId chat_id, UserId inviter_user_id, UserId user_id, int32 date,
@@ -7328,7 +14098,7 @@ void ContactsManager::on_update_chat_add_user(ChatId chat_id, UserId inviter_use
LOG(INFO) << "Receive updateChatParticipantAdd to " << chat_id << " with " << user_id << " invited by "
<< inviter_user_id << " at " << date << " with version " << version;
- ChatFull *chat_full = get_chat_full(chat_id);
+ ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_add_user");
if (chat_full == nullptr) {
LOG(INFO) << "Ignoring update about members of " << chat_id;
return;
@@ -7339,7 +14109,7 @@ void ContactsManager::on_update_chat_add_user(ChatId chat_id, UserId inviter_use
repair_chat_participants(chat_id);
return;
}
- if (c->left) {
+ if (c->status.is_left()) {
// possible if updates come out of order
LOG(WARNING) << "Receive updateChatParticipantAdd for left " << chat_id << ". Couldn't apply it";
@@ -7348,12 +14118,12 @@ void ContactsManager::on_update_chat_add_user(ChatId chat_id, UserId inviter_use
}
if (on_update_chat_full_participants_short(chat_full, chat_id, version)) {
for (auto &participant : chat_full->participants) {
- if (participant.user_id == user_id) {
- if (participant.inviter_user_id != inviter_user_id) {
+ if (participant.dialog_id_ == DialogId(user_id)) {
+ if (participant.inviter_user_id_ != inviter_user_id) {
LOG(ERROR) << user_id << " was readded to " << chat_id << " by " << inviter_user_id
- << ", previously invited by " << participant.inviter_user_id;
- participant.inviter_user_id = inviter_user_id;
- participant.joined_date = date;
+ << ", previously invited by " << participant.inviter_user_id_;
+ participant.inviter_user_id_ = inviter_user_id;
+ participant.joined_date_ = date;
repair_chat_participants(chat_id);
} else {
// Possible if update comes twice
@@ -7362,17 +14132,18 @@ void ContactsManager::on_update_chat_add_user(ChatId chat_id, UserId inviter_use
return;
}
}
- chat_full->participants.push_back(DialogParticipant{user_id, inviter_user_id, date,
+ chat_full->participants.push_back(DialogParticipant{DialogId(user_id), inviter_user_id, date,
user_id == chat_full->creator_user_id
- ? DialogParticipantStatus::Creator(true)
+ ? DialogParticipantStatus::Creator(true, false, string())
: DialogParticipantStatus::Member()});
+ update_chat_online_member_count(chat_full, chat_id, false);
chat_full->is_changed = true;
- update_chat_full(chat_full, chat_id);
+ update_chat_full(chat_full, chat_id, "on_update_chat_add_user");
// Chat is already updated
if (chat_full->version == c->version &&
narrow_cast<int32>(chat_full->participants.size()) != c->participant_count) {
- LOG(ERROR) << "Number of members of " << chat_id << " with version " << c->version << " is "
+ LOG(ERROR) << "Number of members in " << chat_id << " with version " << c->version << " is "
<< c->participant_count << " but there are " << chat_full->participants.size()
<< " members in the ChatFull";
repair_chat_participants(chat_id);
@@ -7399,7 +14170,7 @@ void ContactsManager::on_update_chat_edit_administrator(ChatId chat_id, UserId u
return;
}
- if (c->left) {
+ if (c->status.is_left()) {
// possible if updates come out of order
LOG(WARNING) << "Receive updateChatParticipantAdmin for left " << chat_id << ". Couldn't apply it";
@@ -7412,31 +14183,34 @@ void ContactsManager::on_update_chat_edit_administrator(ChatId chat_id, UserId u
}
CHECK(c->version >= 0);
+ auto status = is_administrator ? DialogParticipantStatus::GroupAdministrator(c->status.is_creator())
+ : DialogParticipantStatus::Member();
if (version > c->version) {
if (version != c->version + 1) {
- LOG(ERROR) << "Administrators of " << chat_id << " with version " << c->version
- << " has changed but new version is " << version;
+ LOG(INFO) << "Administrators of " << chat_id << " with version " << c->version
+ << " has changed, but new version is " << version;
repair_chat_participants(chat_id);
return;
}
c->version = version;
- c->is_changed = true;
- if (user_id == get_my_id("on_update_chat_edit_administrator")) {
- on_update_chat_rights(c, chat_id, c->is_creator, is_administrator, c->everyone_is_administrator);
+ c->need_save_to_database = true;
+ if (user_id == get_my_id() && !c->status.is_creator()) {
+ // if chat with version was already received, then the update is already processed
+ // so we need to call on_update_chat_status only if version > c->version
+ on_update_chat_status(c, chat_id, status);
}
update_chat(c, chat_id);
}
- ChatFull *chat_full = get_chat_full(chat_id);
+ ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_edit_administrator");
if (chat_full != nullptr) {
if (chat_full->version + 1 == version) {
for (auto &participant : chat_full->participants) {
- if (participant.user_id == user_id) {
- participant.status = is_administrator ? DialogParticipantStatus::GroupAdministrator(c->is_creator)
- : DialogParticipantStatus::Member();
+ if (participant.dialog_id_ == DialogId(user_id)) {
+ participant.status_ = std::move(status);
chat_full->is_changed = true;
- update_chat_full(chat_full, chat_id);
+ update_chat_full(chat_full, chat_id, "on_update_chat_edit_administrator");
return;
}
}
@@ -7459,7 +14233,7 @@ void ContactsManager::on_update_chat_delete_user(ChatId chat_id, UserId user_id,
LOG(INFO) << "Receive updateChatParticipantDelete from " << chat_id << " with " << user_id << " and version "
<< version;
- ChatFull *chat_full = get_chat_full(chat_id);
+ ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_delete_user");
if (chat_full == nullptr) {
LOG(INFO) << "Ignoring update about members of " << chat_id;
return;
@@ -7470,12 +14244,12 @@ void ContactsManager::on_update_chat_delete_user(ChatId chat_id, UserId user_id,
repair_chat_participants(chat_id);
return;
}
- if (user_id == get_my_id("on_update_chat_delete_user")) {
- LOG_IF(WARNING, !c->left) << "User was removed from " << chat_id
- << " but it is not left the group. Possible if updates comes out of order";
+ if (user_id == get_my_id()) {
+ LOG_IF(WARNING, c->status.is_member()) << "User was removed from " << chat_id
+ << " but it is not left the group. Possible if updates comes out of order";
return;
}
- if (c->left) {
+ if (c->status.is_left()) {
// possible if updates come out of order
LOG(INFO) << "Receive updateChatParticipantDelete for left " << chat_id;
@@ -7484,41 +14258,74 @@ void ContactsManager::on_update_chat_delete_user(ChatId chat_id, UserId user_id,
}
if (on_update_chat_full_participants_short(chat_full, chat_id, version)) {
for (size_t i = 0; i < chat_full->participants.size(); i++) {
- if (chat_full->participants[i].user_id == user_id) {
+ if (chat_full->participants[i].dialog_id_ == DialogId(user_id)) {
chat_full->participants[i] = chat_full->participants.back();
chat_full->participants.resize(chat_full->participants.size() - 1);
chat_full->is_changed = true;
- update_chat_full(chat_full, chat_id);
+ update_chat_online_member_count(chat_full, chat_id, false);
+ update_chat_full(chat_full, chat_id, "on_update_chat_delete_user");
- if (static_cast<int>(chat_full->participants.size()) != c->participant_count) {
+ if (static_cast<int32>(chat_full->participants.size()) != c->participant_count) {
repair_chat_participants(chat_id);
}
return;
}
}
- LOG(ERROR) << "Can't find group member " << user_id << " in " << chat_id << " to delete him";
+ LOG(ERROR) << "Can't find basic group member " << user_id << " in " << chat_id << " to be removed";
repair_chat_participants(chat_id);
}
}
-void ContactsManager::on_update_chat_everyone_is_administrator(ChatId chat_id, bool everyone_is_administrator,
- int32 version) {
+void ContactsManager::on_update_chat_status(Chat *c, ChatId chat_id, DialogParticipantStatus status) {
+ if (c->status != status) {
+ LOG(INFO) << "Update " << chat_id << " status from " << c->status << " to " << status;
+ bool need_reload_group_call = c->status.can_manage_calls() != status.can_manage_calls();
+ bool need_drop_invite_link = c->status.can_manage_invite_links() && !status.can_manage_invite_links();
+
+ c->status = std::move(status);
+ c->is_status_changed = true;
+
+ if (c->status.is_left()) {
+ c->participant_count = 0;
+ c->version = -1;
+ c->default_permissions_version = -1;
+ c->pinned_message_version = -1;
+
+ drop_chat_full(chat_id);
+ } else if (need_drop_invite_link) {
+ ChatFull *chat_full = get_chat_full_force(chat_id, "on_update_chat_status");
+ if (chat_full != nullptr) {
+ on_update_chat_full_invite_link(chat_full, nullptr);
+ update_chat_full(chat_full, chat_id, "on_update_chat_status");
+ }
+ }
+ if (need_reload_group_call) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights,
+ DialogId(chat_id));
+ }
+
+ c->is_changed = true;
+ }
+}
+
+void ContactsManager::on_update_chat_default_permissions(ChatId chat_id, RestrictedRights default_permissions,
+ int32 version) {
if (!chat_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << chat_id;
return;
}
- LOG(INFO) << "Receive updateChatAdmins in " << chat_id << " with version " << version
- << " and everyone_is_administrator = " << everyone_is_administrator << ". Current version is " << version;
-
auto c = get_chat_force(chat_id);
if (c == nullptr) {
LOG(INFO) << "Ignoring update about unknown " << chat_id;
return;
}
- if (c->left) {
+ LOG(INFO) << "Receive updateChatDefaultBannedRights in " << chat_id << " with " << default_permissions
+ << " and version " << version << ". Current version is " << c->version;
+
+ if (c->status.is_left()) {
// possible if updates come out of order
- LOG(WARNING) << "Receive updateChatAdmins for left " << chat_id << ". Couldn't apply it";
+ LOG(WARNING) << "Receive updateChatDefaultBannedRights for left " << chat_id << ". Couldn't apply it";
repair_chat_participants(chat_id); // just in case
return;
@@ -7530,95 +14337,160 @@ void ContactsManager::on_update_chat_everyone_is_administrator(ChatId chat_id, b
CHECK(c->version >= 0);
if (version > c->version) {
+ // this should be unreachable, because version and default permissions must be already updated from
+ // the chat object in on_chat_update
if (version != c->version + 1) {
- LOG(WARNING) << "Anyone can edit of " << chat_id << " with version " << c->version
- << " has changed but new version is " << version;
+ LOG(INFO) << "Default permissions of " << chat_id << " with version " << c->version
+ << " has changed, but new version is " << version;
repair_chat_participants(chat_id);
return;
}
- LOG_IF(ERROR, everyone_is_administrator == c->everyone_is_administrator)
- << "Receive updateChatAdmins in " << chat_id << " with version " << version
- << " and everyone_is_administrator = " << everyone_is_administrator
- << ", but everyone_is_administrator is not changed. Current version is " << c->version;
+ LOG_IF(ERROR, default_permissions == c->default_permissions)
+ << "Receive updateChatDefaultBannedRights in " << chat_id << " with version " << version
+ << " and default_permissions = " << default_permissions
+ << ", but default_permissions are not changed. Current version is " << c->version;
c->version = version;
- c->is_changed = true;
- on_update_chat_rights(c, chat_id, c->is_creator, c->is_administrator, everyone_is_administrator);
+ c->need_save_to_database = true;
+ on_update_chat_default_permissions(c, chat_id, default_permissions, version);
update_chat(c, chat_id);
}
}
-void ContactsManager::on_update_chat_left(Chat *c, ChatId chat_id, bool left, bool kicked) {
- if (c->left != left || c->kicked != kicked) {
- c->left = left;
- c->kicked = kicked;
+void ContactsManager::on_update_chat_default_permissions(Chat *c, ChatId chat_id, RestrictedRights default_permissions,
+ int32 version) {
+ if (c->default_permissions != default_permissions && version >= c->default_permissions_version) {
+ LOG(INFO) << "Update " << chat_id << " default permissions from " << c->default_permissions << " to "
+ << default_permissions << " and version from " << c->default_permissions_version << " to " << version;
+ c->default_permissions = default_permissions;
+ c->default_permissions_version = version;
+ c->is_default_permissions_changed = true;
+ c->need_save_to_database = true;
+ }
+}
- if (c->left) {
- c->participant_count = 0;
- c->version = -1;
+void ContactsManager::on_update_chat_noforwards(Chat *c, ChatId chat_id, bool noforwards) {
+ if (c->noforwards != noforwards) {
+ LOG(INFO) << "Update " << chat_id << " has_protected_content from " << c->noforwards << " to " << noforwards;
+ c->noforwards = noforwards;
+ c->is_noforwards_changed = true;
+ c->need_save_to_database = true;
+ }
+}
- invalidate_chat_full(chat_id);
- }
+void ContactsManager::on_update_chat_pinned_message(ChatId chat_id, MessageId pinned_message_id, int32 version) {
+ if (!chat_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << chat_id;
+ return;
+ }
+ auto c = get_chat_force(chat_id);
+ if (c == nullptr) {
+ LOG(INFO) << "Ignoring update about unknown " << chat_id;
+ return;
+ }
- c->need_send_update = true;
+ LOG(INFO) << "Receive updateChatPinnedMessage in " << chat_id << " with " << pinned_message_id << " and version "
+ << version << ". Current version is " << c->version << "/" << c->pinned_message_version;
+
+ if (c->status.is_left()) {
+ // possible if updates come out of order
+ repair_chat_participants(chat_id); // just in case
+ return;
}
-}
+ if (version <= -1) {
+ LOG(ERROR) << "Receive wrong version " << version << " for " << chat_id;
+ return;
+ }
+ CHECK(c->version >= 0);
-void ContactsManager::on_update_chat_rights(Chat *c, ChatId chat_id, bool is_creator, bool is_administrator,
- bool everyone_is_administrator) {
- if (c->is_creator != is_creator || c->is_administrator != is_administrator ||
- c->everyone_is_administrator != everyone_is_administrator) {
- c->is_creator = is_creator;
- c->is_administrator = is_administrator;
- c->everyone_is_administrator = everyone_is_administrator;
- c->can_edit = is_creator || is_administrator || everyone_is_administrator;
- c->need_send_update = true;
+ if (version >= c->pinned_message_version) {
+ if (version != c->version + 1 && version != c->version) {
+ LOG(INFO) << "Pinned message of " << chat_id << " with version " << c->version
+ << " has changed, but new version is " << version;
+ repair_chat_participants(chat_id);
+ } else if (version == c->version + 1) {
+ c->version = version;
+ c->need_save_to_database = true;
+ }
+ td_->messages_manager_->on_update_dialog_last_pinned_message_id(DialogId(chat_id), pinned_message_id);
+ if (version > c->pinned_message_version) {
+ LOG(INFO) << "Change pinned message version of " << chat_id << " from " << c->pinned_message_version << " to "
+ << version;
+ c->pinned_message_version = version;
+ c->need_save_to_database = true;
+ }
+ update_chat(c, chat_id);
}
}
void ContactsManager::on_update_chat_participant_count(Chat *c, ChatId chat_id, int32 participant_count, int32 version,
const string &debug_str) {
if (version <= -1) {
- LOG(ERROR) << "Receive wrong version " << version << " from " << debug_str;
+ LOG(ERROR) << "Receive wrong version " << version << " in " << chat_id << debug_str;
return;
}
if (version < c->version) {
// some outdated data
- LOG(INFO) << "Receive member count of " << chat_id << " with version " << version << " from " << debug_str
+ LOG(INFO) << "Receive number of members in " << chat_id << " with version " << version << debug_str
<< ", but current version is " << c->version;
return;
}
if (c->participant_count != participant_count) {
- if (version == c->version) {
+ if (version == c->version && participant_count != 0) {
// version is not changed when deleted user is removed from the chat
LOG_IF(ERROR, c->participant_count != participant_count + 1)
- << "Member count of " << chat_id << " has changed from " << c->participant_count << " to "
- << participant_count << ", but version " << c->version << " remains unchanged in " << debug_str;
+ << "Number of members in " << chat_id << " has changed from " << c->participant_count << " to "
+ << participant_count << ", but version " << c->version << " remains unchanged" << debug_str;
repair_chat_participants(chat_id);
}
c->participant_count = participant_count;
c->version = version;
- c->need_send_update = true;
+ c->is_changed = true;
return;
}
if (version > c->version) {
c->version = version;
- c->is_changed = true;
+ c->need_save_to_database = true;
}
}
void ContactsManager::on_update_chat_photo(Chat *c, ChatId chat_id,
tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr) {
- DialogPhoto new_chat_photo = get_dialog_photo(td_->file_manager_.get(), std::move(chat_photo_ptr));
+ on_update_chat_photo(
+ c, chat_id, get_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), 0, std::move(chat_photo_ptr)), true);
+}
- if (new_chat_photo != c->photo) {
- c->photo = new_chat_photo;
+void ContactsManager::on_update_chat_photo(Chat *c, ChatId chat_id, DialogPhoto &&photo, bool invalidate_photo_cache) {
+ if (td_->auth_manager_->is_bot()) {
+ photo.minithumbnail.clear();
+ }
+
+ if (need_update_dialog_photo(c->photo, photo)) {
+ c->photo = std::move(photo);
c->is_photo_changed = true;
- c->is_changed = true;
+ c->need_save_to_database = true;
+
+ if (invalidate_photo_cache) {
+ auto chat_full = get_chat_full(chat_id); // must not load ChatFull
+ if (chat_full != nullptr) {
+ if (!chat_full->photo.is_empty()) {
+ chat_full->photo = Photo();
+ chat_full->is_changed = true;
+ }
+ if (c->photo.small_file_id.is_valid()) {
+ reload_chat_full(chat_id, Auto());
+ }
+ update_chat_full(chat_full, chat_id, "on_update_chat_photo");
+ }
+ }
+ } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) {
+ c->photo.minithumbnail = std::move(photo.minithumbnail);
+ c->is_photo_changed = true;
+ c->need_save_to_database = true;
}
}
@@ -7626,24 +14498,43 @@ void ContactsManager::on_update_chat_title(Chat *c, ChatId chat_id, string &&tit
if (c->title != title) {
c->title = std::move(title);
c->is_title_changed = true;
- c->is_changed = true;
+ c->need_save_to_database = true;
}
}
void ContactsManager::on_update_chat_active(Chat *c, ChatId chat_id, bool is_active) {
if (c->is_active != is_active) {
c->is_active = is_active;
- c->need_send_update = true;
+ c->is_is_active_changed = true;
+ c->is_changed = true;
}
}
void ContactsManager::on_update_chat_migrated_to_channel_id(Chat *c, ChatId chat_id, ChannelId migrated_to_channel_id) {
if (c->migrated_to_channel_id != migrated_to_channel_id && migrated_to_channel_id.is_valid()) {
LOG_IF(ERROR, c->migrated_to_channel_id.is_valid())
- << "Group upgraded to supergroup has changed from " << c->migrated_to_channel_id << " to "
+ << "Upgraded supergroup ID for " << chat_id << " has changed from " << c->migrated_to_channel_id << " to "
<< migrated_to_channel_id;
c->migrated_to_channel_id = migrated_to_channel_id;
- c->need_send_update = true;
+ c->is_changed = true;
+ }
+}
+
+void ContactsManager::on_update_chat_description(ChatId chat_id, string &&description) {
+ if (!chat_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << chat_id;
+ return;
+ }
+
+ auto chat_full = get_chat_full_force(chat_id, "on_update_chat_description");
+ if (chat_full == nullptr) {
+ return;
+ }
+ if (chat_full->description != description) {
+ chat_full->description = std::move(description);
+ chat_full->is_changed = true;
+ update_chat_full(chat_full, chat_id, "on_update_chat_description");
+ td_->group_call_manager_->on_update_dialog_about(DialogId(chat_id), chat_full->description, true);
}
}
@@ -7662,16 +14553,17 @@ bool ContactsManager::on_update_chat_full_participants_short(ChatFull *chat_full
return true;
}
- LOG(ERROR) << "Member count of " << chat_id << " with version " << chat_full->version
- << " has changed but new version is " << version;
+ LOG(INFO) << "Number of members in " << chat_id << " with version " << chat_full->version
+ << " has changed, but new version is " << version;
repair_chat_participants(chat_id);
return false;
}
void ContactsManager::on_update_chat_full_participants(ChatFull *chat_full, ChatId chat_id,
- vector<DialogParticipant> participants, int32 version) {
+ vector<DialogParticipant> participants, int32 version,
+ bool from_update) {
if (version <= -1) {
- LOG(ERROR) << "Receive members with wrong version " << version;
+ LOG(ERROR) << "Receive members with wrong version " << version << " in " << chat_id;
return;
}
@@ -7682,7 +14574,8 @@ void ContactsManager::on_update_chat_full_participants(ChatFull *chat_full, Chat
return;
}
- if (chat_full->participants.size() != participants.size() && version == chat_full->version) {
+ if ((chat_full->participants.size() != participants.size() && version == chat_full->version) ||
+ (from_update && version != chat_full->version + 1)) {
LOG(INFO) << "Members of " << chat_id << " has changed";
// this is possible in very rare situations
repair_chat_participants(chat_id);
@@ -7691,31 +14584,67 @@ void ContactsManager::on_update_chat_full_participants(ChatFull *chat_full, Chat
chat_full->participants = std::move(participants);
chat_full->version = version;
chat_full->is_changed = true;
+ update_chat_online_member_count(chat_full, chat_id, true);
}
-void ContactsManager::invalidate_chat_full(ChatId chat_id) {
- ChatFull *chat_full = get_chat_full(chat_id);
+void ContactsManager::drop_chat_full(ChatId chat_id) {
+ ChatFull *chat_full = get_chat_full_force(chat_id, "drop_chat_full");
if (chat_full == nullptr) {
return;
}
- LOG(INFO) << "Invalidate groupFull of " << chat_id;
- //chat_full->creator_user_id = UserId();
+ LOG(INFO) << "Drop basicGroupFullInfo of " << chat_id;
+ on_update_chat_full_photo(chat_full, chat_id, Photo());
+ // chat_full->creator_user_id = UserId();
chat_full->participants.clear();
+ chat_full->bot_commands.clear();
chat_full->version = -1;
- update_invite_link(chat_full->invite_link, nullptr);
+ on_update_chat_full_invite_link(chat_full, nullptr);
+ update_chat_online_member_count(chat_full, chat_id, true);
chat_full->is_changed = true;
- update_chat_full(chat_full, chat_id);
+ update_chat_full(chat_full, chat_id, "drop_chat_full");
}
void ContactsManager::on_update_channel_photo(Channel *c, ChannelId channel_id,
tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr) {
- DialogPhoto new_chat_photo = get_dialog_photo(td_->file_manager_.get(), std::move(chat_photo_ptr));
+ on_update_channel_photo(
+ c, channel_id,
+ get_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), c->access_hash, std::move(chat_photo_ptr)),
+ true);
+}
- if (new_chat_photo != c->photo) {
- c->photo = new_chat_photo;
+void ContactsManager::on_update_channel_photo(Channel *c, ChannelId channel_id, DialogPhoto &&photo,
+ bool invalidate_photo_cache) {
+ if (td_->auth_manager_->is_bot()) {
+ photo.minithumbnail.clear();
+ }
+
+ if (need_update_dialog_photo(c->photo, photo)) {
+ c->photo = std::move(photo);
c->is_photo_changed = true;
- c->is_changed = true;
+ c->need_save_to_database = true;
+
+ if (invalidate_photo_cache) {
+ auto channel_full = get_channel_full(channel_id, true, "on_update_channel_photo"); // must not load ChannelFull
+ if (channel_full != nullptr) {
+ if (!channel_full->photo.is_empty()) {
+ channel_full->photo = Photo();
+ channel_full->is_changed = true;
+ }
+ if (c->photo.small_file_id.is_valid()) {
+ if (channel_full->expires_at > 0.0) {
+ channel_full->expires_at = 0.0;
+ channel_full->need_save_to_database = true;
+ }
+ reload_channel_full(channel_id, Auto(), "on_update_channel_photo");
+ }
+ update_channel_full(channel_full, channel_id, "on_update_channel_photo");
+ }
+ }
+ } else if (need_update_dialog_photo_minithumbnail(c->photo.minithumbnail, photo.minithumbnail)) {
+ c->photo.minithumbnail = std::move(photo.minithumbnail);
+ c->is_photo_changed = true;
+ c->need_save_to_database = true;
}
}
@@ -7723,21 +14652,111 @@ void ContactsManager::on_update_channel_title(Channel *c, ChannelId channel_id,
if (c->title != title) {
c->title = std::move(title);
c->is_title_changed = true;
- c->is_changed = true;
+ c->need_save_to_database = true;
}
}
void ContactsManager::on_update_channel_status(Channel *c, ChannelId channel_id, DialogParticipantStatus &&status) {
if (c->status != status) {
LOG(INFO) << "Update " << channel_id << " status from " << c->status << " to " << status;
+ if (c->is_update_supergroup_sent) {
+ on_channel_status_changed(c, channel_id, c->status, status);
+ }
c->status = status;
c->is_status_changed = true;
- c->need_send_update = true;
- invalidate_channel_full(channel_id);
+ c->is_changed = true;
}
}
-void ContactsManager::on_update_channel_username(ChannelId channel_id, string &&username) {
+void ContactsManager::on_channel_status_changed(Channel *c, ChannelId channel_id,
+ const DialogParticipantStatus &old_status,
+ const DialogParticipantStatus &new_status) {
+ CHECK(c->is_update_supergroup_sent);
+ bool have_channel_full = get_channel_full(channel_id) != nullptr;
+
+ bool need_reload_group_call = old_status.can_manage_calls() != new_status.can_manage_calls();
+ if (old_status.can_manage_invite_links() && !new_status.can_manage_invite_links()) {
+ auto channel_full = get_channel_full(channel_id, true, "on_channel_status_changed");
+ if (channel_full != nullptr) { // otherwise invite_link will be dropped when the channel is loaded
+ on_update_channel_full_invite_link(channel_full, nullptr);
+ do_invalidate_channel_full(channel_full, channel_id, !c->is_slow_mode_enabled);
+ update_channel_full(channel_full, channel_id, "on_channel_status_changed");
+ }
+ } else {
+ invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_status_changed");
+ }
+
+ if (old_status.is_creator() != new_status.is_creator()) {
+ c->is_creator_changed = true;
+
+ send_get_channel_full_query(nullptr, channel_id, Auto(), "update channel owner");
+ reload_dialog_administrators(DialogId(channel_id), {}, Auto());
+ remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)});
+ }
+
+ if (old_status.is_member() != new_status.is_member() || new_status.is_banned()) {
+ remove_dialog_access_by_invite_link(DialogId(channel_id));
+
+ if (new_status.is_member() || new_status.is_creator()) {
+ reload_channel_full(channel_id,
+ PromiseCreator::lambda([channel_id](Unit) { LOG(INFO) << "Reloaded full " << channel_id; }),
+ "on_channel_status_changed");
+ }
+ }
+ if (need_reload_group_call) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_update_dialog_group_call_rights,
+ DialogId(channel_id));
+ }
+ if (td_->auth_manager_->is_bot() && old_status.is_administrator() && !new_status.is_administrator()) {
+ channel_participants_.erase(channel_id);
+ }
+ if (td_->auth_manager_->is_bot() && old_status.is_member() && !new_status.is_member() &&
+ !G()->parameters().use_message_db) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_deleted, DialogId(channel_id),
+ Promise<Unit>());
+ }
+
+ // must not load ChannelFull, because must not change the Channel
+ CHECK(have_channel_full == (get_channel_full(channel_id) != nullptr));
+}
+
+void ContactsManager::on_update_channel_default_permissions(Channel *c, ChannelId channel_id,
+ RestrictedRights default_permissions) {
+ if (c->default_permissions != default_permissions) {
+ LOG(INFO) << "Update " << channel_id << " default permissions from " << c->default_permissions << " to "
+ << default_permissions;
+ c->default_permissions = default_permissions;
+ c->is_default_permissions_changed = true;
+ c->need_save_to_database = true;
+ }
+}
+
+void ContactsManager::on_update_channel_has_location(Channel *c, ChannelId channel_id, bool has_location) {
+ if (c->has_location != has_location) {
+ LOG(INFO) << "Update " << channel_id << " has_location from " << c->has_location << " to " << has_location;
+ c->has_location = has_location;
+ c->is_has_location_changed = true;
+ c->is_changed = true;
+ }
+}
+
+void ContactsManager::on_update_channel_noforwards(Channel *c, ChannelId channel_id, bool noforwards) {
+ if (c->noforwards != noforwards) {
+ LOG(INFO) << "Update " << channel_id << " has_protected_content from " << c->noforwards << " to " << noforwards;
+ c->noforwards = noforwards;
+ c->is_noforwards_changed = true;
+ c->need_save_to_database = true;
+ }
+}
+
+void ContactsManager::on_update_channel_editable_username(ChannelId channel_id, string &&username) {
+ Channel *c = get_channel(channel_id);
+ CHECK(c != nullptr);
+ on_update_channel_usernames(c, channel_id, c->usernames.change_editable_username(std::move(username)));
+ update_channel(c, channel_id);
+}
+
+void ContactsManager::on_update_channel_usernames(ChannelId channel_id, Usernames &&usernames) {
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id;
return;
@@ -7745,107 +14764,329 @@ void ContactsManager::on_update_channel_username(ChannelId channel_id, string &&
Channel *c = get_channel_force(channel_id);
if (c != nullptr) {
- on_update_channel_username(c, channel_id, std::move(username));
+ on_update_channel_usernames(c, channel_id, std::move(usernames));
update_channel(c, channel_id);
} else {
- LOG(ERROR) << "Ignore update channel username about unknown " << channel_id;
+ LOG(INFO) << "Ignore update channel usernames about unknown " << channel_id;
}
}
-void ContactsManager::on_update_channel_username(Channel *c, ChannelId channel_id, string &&username) {
- td_->messages_manager_->on_dialog_username_updated(DialogId(channel_id), c->username, username);
- if (c->username != username) {
- if (c->username.empty() || username.empty()) {
- // moving channel from private to public can change availability of chat members
- invalidate_channel_full(channel_id);
+void ContactsManager::on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames) {
+ td_->messages_manager_->on_dialog_usernames_updated(DialogId(channel_id), c->usernames, usernames);
+ if (c->usernames != usernames) {
+ if (c->is_update_supergroup_sent) {
+ on_channel_usernames_changed(c, channel_id, c->usernames, usernames);
}
- c->username = std::move(username);
+ c->usernames = std::move(usernames);
c->is_username_changed = true;
- c->need_send_update = true;
+ c->is_changed = true;
}
}
-void ContactsManager::on_update_channel_full_pinned_message(ChannelFull *channel_full, MessageId message_id) {
- if (!message_id.is_valid() && message_id != MessageId()) {
- LOG(ERROR) << "Receive " << message_id << " as pinned message";
- return;
+void ContactsManager::on_channel_usernames_changed(const Channel *c, ChannelId channel_id,
+ const Usernames &old_usernames, const Usernames &new_usernames) {
+ bool have_channel_full = get_channel_full(channel_id) != nullptr;
+ if (!old_usernames.has_first_username() || !new_usernames.has_first_username()) {
+ // moving channel from private to public can change availability of chat members
+ invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_usernames_changed");
}
- CHECK(channel_full != nullptr);
- if (channel_full->pinned_message_id != message_id) {
- channel_full->pinned_message_id = message_id;
- channel_full->is_changed = true;
- }
+ // must not load ChannelFull, because must not change the Channel
+ CHECK(have_channel_full == (get_channel_full(channel_id) != nullptr));
}
void ContactsManager::on_update_channel_description(ChannelId channel_id, string &&description) {
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
+ CHECK(channel_id.is_valid());
+ auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_description");
+ if (channel_full == nullptr) {
return;
}
+ if (channel_full->description != description) {
+ channel_full->description = std::move(description);
+ channel_full->is_changed = true;
+ update_channel_full(channel_full, channel_id, "on_update_channel_description");
+ td_->group_call_manager_->on_update_dialog_about(DialogId(channel_id), channel_full->description, true);
+ }
+}
- auto channel_full = get_channel_full(channel_id);
+void ContactsManager::on_update_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id) {
+ CHECK(channel_id.is_valid());
+ auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_sticker_set");
if (channel_full == nullptr) {
return;
}
- if (channel_full->description != description) {
- channel_full->description = std::move(description);
+ if (channel_full->sticker_set_id != sticker_set_id) {
+ channel_full->sticker_set_id = sticker_set_id;
channel_full->is_changed = true;
- update_channel_full(channel_full, channel_id);
+ update_channel_full(channel_full, channel_id, "on_update_channel_sticker_set");
}
}
-void ContactsManager::on_update_channel_sticker_set(ChannelId channel_id, int64 sticker_set_id) {
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
+void ContactsManager::on_update_channel_linked_channel_id(ChannelId channel_id, ChannelId group_channel_id) {
+ if (channel_id.is_valid()) {
+ auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_linked_channel_id 1");
+ on_update_channel_full_linked_channel_id(channel_full, channel_id, group_channel_id);
+ if (channel_full != nullptr) {
+ update_channel_full(channel_full, channel_id, "on_update_channel_linked_channel_id 3");
+ }
+ }
+ if (group_channel_id.is_valid()) {
+ auto channel_full = get_channel_full_force(group_channel_id, true, "on_update_channel_linked_channel_id 2");
+ on_update_channel_full_linked_channel_id(channel_full, group_channel_id, channel_id);
+ if (channel_full != nullptr) {
+ update_channel_full(channel_full, group_channel_id, "on_update_channel_linked_channel_id 4");
+ }
+ }
+}
+
+void ContactsManager::on_update_channel_location(ChannelId channel_id, const DialogLocation &location) {
+ auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_location");
+ if (channel_full != nullptr) {
+ on_update_channel_full_location(channel_full, channel_id, location);
+ update_channel_full(channel_full, channel_id, "on_update_channel_location");
+ }
+}
+
+void ContactsManager::on_update_channel_slow_mode_delay(ChannelId channel_id, int32 slow_mode_delay,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_slow_mode_delay");
+ if (channel_full != nullptr) {
+ on_update_channel_full_slow_mode_delay(channel_full, channel_id, slow_mode_delay, 0);
+ update_channel_full(channel_full, channel_id, "on_update_channel_slow_mode_delay");
+ }
+ promise.set_value(Unit());
+}
+
+void ContactsManager::on_update_channel_slow_mode_next_send_date(ChannelId channel_id, int32 slow_mode_next_send_date) {
+ auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_slow_mode_next_send_date");
+ if (channel_full != nullptr) {
+ on_update_channel_full_slow_mode_next_send_date(channel_full, slow_mode_next_send_date);
+ update_channel_full(channel_full, channel_id, "on_update_channel_slow_mode_next_send_date");
+ }
+}
+
+void ContactsManager::on_update_channel_bot_user_ids(ChannelId channel_id, vector<UserId> &&bot_user_ids) {
+ CHECK(channel_id.is_valid());
+ if (!have_channel(channel_id)) {
+ LOG(ERROR) << channel_id << " not found";
return;
}
- auto channel_full = get_channel_full(channel_id);
+ auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_bot_user_ids");
if (channel_full == nullptr) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id),
+ std::move(bot_user_ids), false);
return;
}
- if (channel_full->sticker_set_id != sticker_set_id) {
- channel_full->sticker_set_id = sticker_set_id;
+ on_update_channel_full_bot_user_ids(channel_full, channel_id, std::move(bot_user_ids));
+ update_channel_full(channel_full, channel_id, "on_update_channel_bot_user_ids");
+}
+
+void ContactsManager::on_update_channel_full_bot_user_ids(ChannelFull *channel_full, ChannelId channel_id,
+ vector<UserId> &&bot_user_ids) {
+ CHECK(channel_full != nullptr);
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_dialog_bots_updated, DialogId(channel_id),
+ bot_user_ids, false);
+ if (channel_full->bot_user_ids != bot_user_ids) {
+ channel_full->bot_user_ids = std::move(bot_user_ids);
+ channel_full->need_save_to_database = true;
+ }
+}
+
+void ContactsManager::on_update_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ CHECK(channel_id.is_valid());
+ auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_is_all_history_available");
+ if (channel_full != nullptr && channel_full->is_all_history_available != is_all_history_available) {
+ channel_full->is_all_history_available = is_all_history_available;
channel_full->is_changed = true;
- update_channel_full(channel_full, channel_id);
+ update_channel_full(channel_full, channel_id, "on_update_channel_is_all_history_available");
}
+ promise.set_value(Unit());
}
-void ContactsManager::on_update_channel_pinned_message(ChannelId channel_id, MessageId message_id) {
+void ContactsManager::on_update_channel_default_permissions(ChannelId channel_id,
+ RestrictedRights default_permissions) {
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id;
return;
}
- auto channel_full = get_channel_full(channel_id);
- if (channel_full == nullptr) {
+ Channel *c = get_channel_force(channel_id);
+ if (c != nullptr) {
+ on_update_channel_default_permissions(c, channel_id, std::move(default_permissions));
+ update_channel(c, channel_id);
+ } else {
+ LOG(INFO) << "Ignore update channel default permissions about unknown " << channel_id;
+ }
+}
+
+void ContactsManager::send_update_chat_member(DialogId dialog_id, UserId agent_user_id, int32 date,
+ const DialogInviteLink &invite_link,
+ const DialogParticipant &old_dialog_participant,
+ const DialogParticipant &new_dialog_participant) {
+ CHECK(td_->auth_manager_->is_bot());
+ td_->messages_manager_->force_create_dialog(dialog_id, "send_update_chat_member", true);
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatMember>(
+ dialog_id.get(), get_user_id_object(agent_user_id, "send_update_chat_member"), date,
+ invite_link.get_chat_invite_link_object(this), get_chat_member_object(old_dialog_participant),
+ get_chat_member_object(new_dialog_participant)));
+}
+
+void ContactsManager::on_update_bot_stopped(UserId user_id, int32 date, bool is_stopped) {
+ if (!td_->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Receive updateBotStopped by non-bot";
+ return;
+ }
+ if (date <= 0 || !have_user_force(user_id)) {
+ LOG(ERROR) << "Receive invalid updateBotStopped by " << user_id << " at " << date;
return;
}
- on_update_channel_full_pinned_message(channel_full, message_id);
- update_channel_full(channel_full, channel_id);
+
+ DialogParticipant old_dialog_participant(DialogId(get_my_id()), user_id, date, DialogParticipantStatus::Banned(0));
+ DialogParticipant new_dialog_participant(DialogId(get_my_id()), user_id, date, DialogParticipantStatus::Member());
+ if (is_stopped) {
+ std::swap(old_dialog_participant.status_, new_dialog_participant.status_);
+ }
+
+ send_update_chat_member(DialogId(user_id), user_id, date, DialogInviteLink(), old_dialog_participant,
+ new_dialog_participant);
}
-void ContactsManager::on_update_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available) {
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
+void ContactsManager::on_update_chat_participant(ChatId chat_id, UserId user_id, int32 date,
+ DialogInviteLink invite_link,
+ tl_object_ptr<telegram_api::ChatParticipant> old_participant,
+ tl_object_ptr<telegram_api::ChatParticipant> new_participant) {
+ if (!td_->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Receive updateChatParticipant by non-bot";
+ return;
+ }
+ if (!chat_id.is_valid() || !user_id.is_valid() || date <= 0 ||
+ (old_participant == nullptr && new_participant == nullptr)) {
+ LOG(ERROR) << "Receive invalid updateChatParticipant in " << chat_id << " by " << user_id << " at " << date << ": "
+ << to_string(old_participant) << " -> " << to_string(new_participant);
return;
}
- auto channel_full = get_channel_full(channel_id);
- if (channel_full == nullptr) {
+ const Chat *c = get_chat(chat_id);
+ if (c == nullptr) {
+ LOG(ERROR) << "Receive updateChatParticipant in unknown " << chat_id;
return;
}
- if (channel_full->is_all_history_available != is_all_history_available) {
- channel_full->is_all_history_available = is_all_history_available;
- channel_full->is_changed = true;
- update_channel_full(channel_full, channel_id);
+
+ DialogParticipant old_dialog_participant;
+ DialogParticipant new_dialog_participant;
+ if (old_participant != nullptr) {
+ old_dialog_participant = DialogParticipant(std::move(old_participant), c->date, c->status.is_creator());
+ if (new_participant == nullptr) {
+ new_dialog_participant = DialogParticipant::left(old_dialog_participant.dialog_id_);
+ } else {
+ new_dialog_participant = DialogParticipant(std::move(new_participant), c->date, c->status.is_creator());
+ }
+ } else {
+ new_dialog_participant = DialogParticipant(std::move(new_participant), c->date, c->status.is_creator());
+ old_dialog_participant = DialogParticipant::left(new_dialog_participant.dialog_id_);
+ }
+ if (old_dialog_participant.dialog_id_ != new_dialog_participant.dialog_id_ || !old_dialog_participant.is_valid() ||
+ !new_dialog_participant.is_valid()) {
+ LOG(ERROR) << "Receive wrong updateChatParticipant: " << old_dialog_participant << " -> " << new_dialog_participant;
+ return;
+ }
+ if (new_dialog_participant.dialog_id_ == DialogId(get_my_id()) &&
+ new_dialog_participant.status_ != get_chat_status(chat_id) && false) {
+ LOG(ERROR) << "Have status " << get_chat_status(chat_id) << " after receiving updateChatParticipant in " << chat_id
+ << " by " << user_id << " at " << date << " from " << old_dialog_participant << " to "
+ << new_dialog_participant;
}
+
+ send_update_chat_member(DialogId(chat_id), user_id, date, invite_link, old_dialog_participant,
+ new_dialog_participant);
+}
+
+void ContactsManager::on_update_channel_participant(ChannelId channel_id, UserId user_id, int32 date,
+ DialogInviteLink invite_link,
+ tl_object_ptr<telegram_api::ChannelParticipant> old_participant,
+ tl_object_ptr<telegram_api::ChannelParticipant> new_participant) {
+ if (!td_->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Receive updateChannelParticipant by non-bot";
+ return;
+ }
+ if (!channel_id.is_valid() || !user_id.is_valid() || date <= 0 ||
+ (old_participant == nullptr && new_participant == nullptr)) {
+ LOG(ERROR) << "Receive invalid updateChannelParticipant in " << channel_id << " by " << user_id << " at " << date
+ << ": " << to_string(old_participant) << " -> " << to_string(new_participant);
+ return;
+ }
+
+ DialogParticipant old_dialog_participant;
+ DialogParticipant new_dialog_participant;
+ auto channel_type = get_channel_type(channel_id);
+ if (old_participant != nullptr) {
+ old_dialog_participant = DialogParticipant(std::move(old_participant), channel_type);
+ if (new_participant == nullptr) {
+ new_dialog_participant = DialogParticipant::left(old_dialog_participant.dialog_id_);
+ } else {
+ new_dialog_participant = DialogParticipant(std::move(new_participant), channel_type);
+ }
+ } else {
+ new_dialog_participant = DialogParticipant(std::move(new_participant), channel_type);
+ old_dialog_participant = DialogParticipant::left(new_dialog_participant.dialog_id_);
+ }
+ if (old_dialog_participant.dialog_id_ != new_dialog_participant.dialog_id_ || !old_dialog_participant.is_valid() ||
+ !new_dialog_participant.is_valid()) {
+ LOG(ERROR) << "Receive wrong updateChannelParticipant: " << old_dialog_participant << " -> "
+ << new_dialog_participant;
+ return;
+ }
+ if (new_dialog_participant.status_.is_administrator() && user_id == get_my_id() &&
+ !new_dialog_participant.status_.can_be_edited()) {
+ LOG(ERROR) << "Fix wrong can_be_edited in " << new_dialog_participant << " from " << channel_id << " changed from "
+ << old_dialog_participant;
+ new_dialog_participant.status_.toggle_can_be_edited();
+ }
+
+ if (old_dialog_participant.dialog_id_ == DialogId(get_my_id()) && old_dialog_participant.status_.is_administrator() &&
+ !new_dialog_participant.status_.is_administrator()) {
+ channel_participants_.erase(channel_id);
+ } else if (have_channel_participant_cache(channel_id)) {
+ add_channel_participant_to_cache(channel_id, new_dialog_participant, true);
+ }
+ if (new_dialog_participant.dialog_id_ == DialogId(get_my_id()) &&
+ new_dialog_participant.status_ != get_channel_status(channel_id) && false) {
+ LOG(ERROR) << "Have status " << get_channel_status(channel_id) << " after receiving updateChannelParticipant in "
+ << channel_id << " by " << user_id << " at " << date << " from " << old_dialog_participant << " to "
+ << new_dialog_participant;
+ }
+
+ send_update_chat_member(DialogId(channel_id), user_id, date, invite_link, old_dialog_participant,
+ new_dialog_participant);
+}
+
+void ContactsManager::on_update_chat_invite_requester(DialogId dialog_id, UserId user_id, string about, int32 date,
+ DialogInviteLink invite_link) {
+ if (!td_->auth_manager_->is_bot() || date <= 0 || !have_user_force(user_id) ||
+ !td_->messages_manager_->have_dialog_info_force(dialog_id)) {
+ LOG(ERROR) << "Receive invalid updateBotChatInviteRequester by " << user_id << " in " << dialog_id << " at "
+ << date;
+ return;
+ }
+ td_->messages_manager_->force_create_dialog(dialog_id, "on_update_chat_invite_requester", true);
+
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateNewChatJoinRequest>(
+ dialog_id.get(),
+ td_api::make_object<td_api::chatJoinRequest>(
+ get_user_id_object(user_id, "on_update_chat_invite_requester"), date, about),
+ invite_link.get_chat_invite_link_object(this)));
}
void ContactsManager::update_contacts_hints(const User *u, UserId user_id, bool from_database) {
- bool is_contact = u->outbound == LinkState::Contact && user_id != get_my_id("update_contacts_hints");
+ bool is_contact = is_user_contact(u, user_id, false);
if (td_->auth_manager_->is_bot()) {
LOG_IF(ERROR, is_contact) << "Bot has " << user_id << " in the contacts list";
return;
@@ -7853,7 +15094,7 @@ void ContactsManager::update_contacts_hints(const User *u, UserId user_id, bool
int64 key = user_id.get();
string old_value = contacts_hints_.key_to_string(key);
- string new_value = is_contact ? u->first_name + " " + u->last_name + " " + u->username : "";
+ string new_value = is_contact ? get_user_search_text(u) : string();
if (new_value != old_value) {
if (is_contact) {
@@ -7866,7 +15107,7 @@ void ContactsManager::update_contacts_hints(const User *u, UserId user_id, bool
if (G()->parameters().use_chat_info_db) {
// update contacts database
if (!are_contacts_loaded_) {
- if (!from_database && load_contacts_queries_.empty()) {
+ if (!from_database && load_contacts_queries_.empty() && is_contact && u->is_is_contact_changed) {
search_contacts("", std::numeric_limits<int32>::max(), Auto());
}
} else {
@@ -7886,57 +15127,103 @@ bool ContactsManager::have_min_user(UserId user_id) const {
return users_.count(user_id) > 0;
}
+bool ContactsManager::is_user_premium(UserId user_id) const {
+ auto u = get_user(user_id);
+ return u != nullptr && u->is_premium;
+}
+
bool ContactsManager::is_user_deleted(UserId user_id) const {
auto u = get_user(user_id);
return u == nullptr || u->is_deleted;
}
+bool ContactsManager::is_user_support(UserId user_id) const {
+ auto u = get_user(user_id);
+ return u != nullptr && !u->is_deleted && u->is_support;
+}
+
bool ContactsManager::is_user_bot(UserId user_id) const {
auto u = get_user(user_id);
return u != nullptr && !u->is_deleted && u->is_bot;
}
-Result<BotData> ContactsManager::get_bot_data(UserId user_id) const {
- auto p = users_.find(user_id);
- if (p == users_.end()) {
- return Status::Error(5, "Bot not found");
+Result<ContactsManager::BotData> ContactsManager::get_bot_data(UserId user_id) const {
+ auto u = get_user(user_id);
+ if (u == nullptr) {
+ return Status::Error(400, "Bot not found");
}
-
- auto bot = &p->second;
- if (!bot->is_bot) {
- return Status::Error(5, "User is not a bot");
+ if (!u->is_bot) {
+ return Status::Error(400, "User is not a bot");
}
- if (bot->is_deleted) {
- return Status::Error(5, "Bot is deleted");
+ if (u->is_deleted) {
+ return Status::Error(400, "Bot is deleted");
}
- if (!bot->is_received) {
- return Status::Error(5, "Bot is inaccessible");
+ if (!u->is_received) {
+ return Status::Error(400, "Bot is inaccessible");
}
BotData bot_data;
- bot_data.username = bot->username;
- bot_data.can_join_groups = bot->can_join_groups;
- bot_data.can_read_all_group_messages = bot->can_read_all_group_messages;
- bot_data.is_inline = bot->is_inline_bot;
- bot_data.need_location = bot->need_location_bot;
+ bot_data.username = u->usernames.get_first_username();
+ bot_data.can_join_groups = u->can_join_groups;
+ bot_data.can_read_all_group_messages = u->can_read_all_group_messages;
+ bot_data.is_inline = u->is_inline_bot;
+ bot_data.need_location = u->need_location_bot;
+ bot_data.can_be_added_to_attach_menu = u->can_be_added_to_attach_menu;
return bot_data;
}
+bool ContactsManager::is_user_online(UserId user_id, int32 tolerance) const {
+ int32 was_online = get_user_was_online(get_user(user_id), user_id);
+ return was_online > G()->unix_time() - tolerance;
+}
+
+bool ContactsManager::is_user_status_exact(UserId user_id) const {
+ auto u = get_user(user_id);
+ return u != nullptr && !u->is_deleted && !u->is_bot && u->was_online > 0;
+}
+
+bool ContactsManager::can_report_user(UserId user_id) const {
+ auto u = get_user(user_id);
+ return u != nullptr && !u->is_deleted && !u->is_support && (u->is_bot || all_users_nearby_.count(user_id) != 0);
+}
+
const ContactsManager::User *ContactsManager::get_user(UserId user_id) const {
- auto p = users_.find(user_id);
- if (p == users_.end()) {
- return nullptr;
- } else {
- return &p->second;
- }
+ return users_.get_pointer(user_id);
}
ContactsManager::User *ContactsManager::get_user(UserId user_id) {
- auto p = users_.find(user_id);
- if (p == users_.end()) {
- return nullptr;
- } else {
- return &p->second;
+ return users_.get_pointer(user_id);
+}
+
+bool ContactsManager::is_dialog_info_received_from_server(DialogId dialog_id) const {
+ switch (dialog_id.get_type()) {
+ case DialogType::User: {
+ auto u = get_user(dialog_id.get_user_id());
+ return u != nullptr && u->is_received_from_server;
+ }
+ case DialogType::Chat: {
+ auto c = get_chat(dialog_id.get_chat_id());
+ return c != nullptr && c->is_received_from_server;
+ }
+ case DialogType::Channel: {
+ auto c = get_channel(dialog_id.get_channel_id());
+ return c != nullptr && c->is_received_from_server;
+ }
+ default:
+ return false;
+ }
+}
+
+void ContactsManager::reload_dialog_info(DialogId dialog_id, Promise<Unit> &&promise) {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return reload_user(dialog_id.get_user_id(), std::move(promise));
+ case DialogType::Chat:
+ return reload_chat(dialog_id.get_chat_id(), std::move(promise));
+ case DialogType::Channel:
+ return reload_channel(dialog_id.get_channel_id(), std::move(promise));
+ default:
+ return promise.set_error(Status::Error("Invalid dialog ID to reload"));
}
}
@@ -7947,7 +15234,7 @@ void ContactsManager::send_get_me_query(Td *td, Promise<Unit> &&promise) {
}
UserId ContactsManager::get_me(Promise<Unit> &&promise) {
- auto my_id = get_my_id("get_me");
+ auto my_id = get_my_id();
if (!have_user_force(my_id)) {
send_get_me_query(td_, std::move(promise));
return UserId();
@@ -7959,26 +15246,34 @@ UserId ContactsManager::get_me(Promise<Unit> &&promise) {
bool ContactsManager::get_user(UserId user_id, int left_tries, Promise<Unit> &&promise) {
if (!user_id.is_valid()) {
- promise.set_error(Status::Error(6, "Invalid user id"));
+ promise.set_error(Status::Error(400, "Invalid user identifier"));
return false;
}
- // TODO support loading user from database and merging it with min-user in memory
- if (!have_min_user(user_id)) {
+ if (user_id == get_service_notifications_user_id() || user_id == get_replies_bot_user_id() ||
+ user_id == get_anonymous_bot_user_id() || user_id == get_channel_bot_user_id()) {
+ get_user_force(user_id);
+ }
+
+ if (td_->auth_manager_->is_bot() ? !have_user(user_id) : !have_min_user(user_id)) {
// TODO UserLoader
if (left_tries > 2 && G()->parameters().use_chat_info_db) {
send_closure_later(actor_id(this), &ContactsManager::load_user_from_database, nullptr, user_id,
std::move(promise));
return false;
}
- auto input_user = get_input_user(user_id);
- if (left_tries == 1 || input_user == nullptr) {
- promise.set_error(Status::Error(6, "User not found"));
+ auto r_input_user = get_input_user(user_id);
+ if (left_tries == 1 || r_input_user.is_error()) {
+ if (r_input_user.is_error()) {
+ promise.set_error(r_input_user.move_as_error());
+ } else {
+ promise.set_error(Status::Error(400, "User not found"));
+ }
return false;
}
vector<tl_object_ptr<telegram_api::InputUser>> users;
- users.push_back(std::move(input_user));
+ users.push_back(r_input_user.move_as_ok());
td_->create_handler<GetUsersQuery>(std::move(promise))->send(std::move(users));
return false;
}
@@ -7987,96 +15282,95 @@ bool ContactsManager::get_user(UserId user_id, int left_tries, Promise<Unit> &&p
return true;
}
-ContactsManager::User *ContactsManager::add_user(UserId user_id) {
+ContactsManager::User *ContactsManager::add_user(UserId user_id, const char *source) {
CHECK(user_id.is_valid());
- return &users_[user_id];
+ auto &user_ptr = users_[user_id];
+ if (user_ptr == nullptr) {
+ user_ptr = make_unique<User>();
+ }
+ return user_ptr.get();
}
const ContactsManager::UserFull *ContactsManager::get_user_full(UserId user_id) const {
- auto p = users_full_.find(user_id);
- if (p == users_full_.end()) {
- return nullptr;
- } else {
- return &p->second;
- }
+ return users_full_.get_pointer(user_id);
}
ContactsManager::UserFull *ContactsManager::get_user_full(UserId user_id) {
- auto p = users_full_.find(user_id);
- if (p == users_full_.end()) {
- return nullptr;
- } else {
- return &p->second;
- }
+ return users_full_.get_pointer(user_id);
}
-bool ContactsManager::get_user_full(UserId user_id, Promise<Unit> &&promise) {
- auto user = get_user(user_id);
- if (user == nullptr) {
- promise.set_error(Status::Error(6, "User not found"));
- return false;
+ContactsManager::UserFull *ContactsManager::add_user_full(UserId user_id) {
+ CHECK(user_id.is_valid());
+ auto &user_full_ptr = users_full_[user_id];
+ if (user_full_ptr == nullptr) {
+ user_full_ptr = make_unique<UserFull>();
}
+ return user_full_ptr.get();
+}
- auto user_full = get_user_full(user_id);
- if (user_full == nullptr || !user_full->is_inited) {
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- promise.set_error(Status::Error(6, "Can't get info about unaccessible user"));
- return false;
- }
-
- send_get_user_full_query(user_id, std::move(input_user), std::move(promise));
- return false;
+void ContactsManager::reload_user(UserId user_id, Promise<Unit> &&promise) {
+ if (!user_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid user identifier"));
}
- if (user_full->is_expired() || user_full->is_bot_info_expired(user->bot_info_version)) {
- auto input_user = get_input_user(user_id);
- CHECK(input_user != nullptr);
- if (td_->auth_manager_->is_bot()) {
- send_get_user_full_query(user_id, std::move(input_user), std::move(promise));
- return false;
- } else {
- send_get_user_full_query(user_id, std::move(input_user), Auto());
- }
+
+ have_user_force(user_id);
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
}
- promise.set_value(Unit());
- return true;
+ // there is no much reason to combine different requests into one request
+ vector<tl_object_ptr<telegram_api::InputUser>> users;
+ users.push_back(r_input_user.move_as_ok());
+ td_->create_handler<GetUsersQuery>(std::move(promise))->send(std::move(users));
}
-void ContactsManager::send_get_user_full_query(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user,
- Promise<Unit> &&promise) {
- auto &promises = get_user_full_queries_[user_id];
- promises.push_back(std::move(promise));
- if (promises.size() != 1) {
- // query has already been sent, just wait for the result
- return;
+void ContactsManager::load_user_full(UserId user_id, bool force, Promise<Unit> &&promise, const char *source) {
+ auto u = get_user(user_id);
+ if (u == nullptr) {
+ return promise.set_error(Status::Error(400, "User not found"));
}
- td_->create_handler<GetFullUserQuery>()->send(user_id, std::move(input_user));
-}
+ auto user_full = get_user_full_force(user_id);
+ if (user_full == nullptr) {
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
-void ContactsManager::on_get_user_full_success(UserId user_id) {
- auto it = get_user_full_queries_.find(user_id);
- CHECK(it != get_user_full_queries_.end());
- CHECK(!it->second.empty());
- auto promises = std::move(it->second);
- get_user_full_queries_.erase(it);
+ return send_get_user_full_query(user_id, r_input_user.move_as_ok(), std::move(promise), source);
+ }
+ if (user_full->is_expired()) {
+ auto r_input_user = get_input_user(user_id);
+ CHECK(r_input_user.is_ok());
+ if (td_->auth_manager_->is_bot() && !force) {
+ return send_get_user_full_query(user_id, r_input_user.move_as_ok(), std::move(promise), "load expired user_full");
+ }
- for (auto &promise : promises) {
- promise.set_value(Unit());
+ send_get_user_full_query(user_id, r_input_user.move_as_ok(), Auto(), "load expired user_full");
}
+
+ promise.set_value(Unit());
}
-void ContactsManager::on_get_user_full_fail(UserId user_id, Status &&error) {
- auto it = get_user_full_queries_.find(user_id);
- CHECK(it != get_user_full_queries_.end());
- CHECK(!it->second.empty());
- auto promises = std::move(it->second);
- get_user_full_queries_.erase(it);
+void ContactsManager::reload_user_full(UserId user_id, Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_user, get_input_user(user_id));
+ send_get_user_full_query(user_id, std::move(input_user), std::move(promise), "reload_user_full");
+}
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+void ContactsManager::send_get_user_full_query(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user,
+ Promise<Unit> &&promise, const char *source) {
+ LOG(INFO) << "Get full " << user_id << " from " << source;
+ if (!user_id.is_valid()) {
+ return promise.set_error(Status::Error(500, "Invalid user_id"));
}
+ auto send_query =
+ PromiseCreator::lambda([td = td_, input_user = std::move(input_user)](Result<Promise<Unit>> &&promise) mutable {
+ if (promise.is_ok() && !G()->close_flag()) {
+ td->create_handler<GetFullUserQuery>(promise.move_as_ok())->send(std::move(input_user));
+ }
+ });
+ get_user_full_queries_.add_query(user_id.get(), std::move(send_query), std::move(promise));
}
std::pair<int32, vector<const Photo *>> ContactsManager::get_user_profile_photos(UserId user_id, int32 offset,
@@ -8085,49 +15379,51 @@ std::pair<int32, vector<const Photo *>> ContactsManager::get_user_profile_photos
result.first = -1;
if (offset < 0) {
- promise.set_error(Status::Error(3, "Parameter offset must be non-negative"));
+ promise.set_error(Status::Error(400, "Parameter offset must be non-negative"));
return result;
}
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return result;
}
if (limit > MAX_GET_PROFILE_PHOTOS) {
limit = MAX_GET_PROFILE_PHOTOS;
}
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- promise.set_error(Status::Error(6, "User not found"));
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ promise.set_error(r_input_user.move_as_error());
return result;
}
- auto user_full = &users_full_[user_id];
- if (user_full->getting_photos_now) {
+ apply_pending_user_photo(get_user(user_id), user_id);
+
+ auto user_photos = add_user_photos(user_id);
+ if (user_photos->getting_now) {
promise.set_error(Status::Error(400, "Request for new profile photos has already been sent"));
return result;
}
- if (user_full->photo_count != -1) { // know photo count
- CHECK(user_full->photos_offset != -1);
- result.first = user_full->photo_count;
+ if (user_photos->count != -1) { // know photo count
+ CHECK(user_photos->offset != -1);
+ result.first = user_photos->count;
- if (offset >= user_full->photo_count) {
+ if (offset >= user_photos->count) {
// offset if too big
promise.set_value(Unit());
return result;
}
- if (limit > user_full->photo_count - offset) {
- limit = user_full->photo_count - offset;
+ if (limit > user_photos->count - offset) {
+ limit = user_photos->count - offset;
}
- int32 cache_begin = user_full->photos_offset;
- int32 cache_end = cache_begin + narrow_cast<int32>(user_full->photos.size());
+ int32 cache_begin = user_photos->offset;
+ int32 cache_end = cache_begin + narrow_cast<int32>(user_photos->photos.size());
if (cache_begin <= offset && offset + limit <= cache_end) {
// answer query from cache
for (int i = 0; i < limit; i++) {
- result.second.push_back(&user_full->photos[i + offset - cache_begin]);
+ result.second.push_back(&user_photos->photos[i + offset - cache_begin]);
}
promise.set_value(Unit());
return result;
@@ -8140,46 +15436,134 @@ std::pair<int32, vector<const Photo *>> ContactsManager::get_user_profile_photos
}
}
- user_full->getting_photos_now = true;
+ user_photos->getting_now = true;
if (limit < MAX_GET_PROFILE_PHOTOS / 5) {
limit = MAX_GET_PROFILE_PHOTOS / 5; // make limit reasonable
}
- td_->create_handler<GetUserPhotosQuery>(std::move(promise))->send(user_id, std::move(input_user), offset, limit);
+ td_->create_handler<GetUserPhotosQuery>(std::move(promise))
+ ->send(user_id, r_input_user.move_as_ok(), offset, limit, 0);
return result;
}
+void ContactsManager::reload_user_profile_photo(UserId user_id, int64 photo_id, Promise<Unit> &&promise) {
+ get_user_force(user_id);
+ auto r_input_user = get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
+
+ // this request will be needed only to download the photo,
+ // so there is no reason to combine different requests for a photo into one request
+ td_->create_handler<GetUserPhotosQuery>(std::move(promise))
+ ->send(user_id, r_input_user.move_as_ok(), -1, 1, photo_id);
+}
+
+FileSourceId ContactsManager::get_user_profile_photo_file_source_id(UserId user_id, int64 photo_id) {
+ if (!user_id.is_valid()) {
+ return FileSourceId();
+ }
+
+ auto u = get_user(user_id);
+ if (u != nullptr && u->photo_ids.count(photo_id) != 0) {
+ VLOG(file_references) << "Don't need to create file source for photo " << photo_id << " of " << user_id;
+ // photo was already added, source ID was registered and shouldn't be needed
+ return FileSourceId();
+ }
+
+ auto &source_id = user_profile_photo_file_source_ids_[std::make_pair(user_id, photo_id)];
+ if (!source_id.is_valid()) {
+ source_id = td_->file_reference_manager_->create_user_photo_file_source(user_id, photo_id);
+ }
+ VLOG(file_references) << "Return " << source_id << " for photo " << photo_id << " of " << user_id;
+ return source_id;
+}
+
+FileSourceId ContactsManager::get_user_full_file_source_id(UserId user_id) {
+ if (!user_id.is_valid()) {
+ return FileSourceId();
+ }
+
+ auto user_full = get_user_full(user_id);
+ if (user_full != nullptr) {
+ VLOG(file_references) << "Don't need to create file source for full " << user_id;
+ // user full was already added, source ID was registered and shouldn't be needed
+ return user_full->is_update_user_full_sent ? FileSourceId() : user_full->file_source_id;
+ }
+
+ auto &source_id = user_full_file_source_ids_[user_id];
+ if (!source_id.is_valid()) {
+ source_id = td_->file_reference_manager_->create_user_full_file_source(user_id);
+ }
+ VLOG(file_references) << "Return " << source_id << " for full " << user_id;
+ return source_id;
+}
+
+FileSourceId ContactsManager::get_chat_full_file_source_id(ChatId chat_id) {
+ if (!chat_id.is_valid()) {
+ return FileSourceId();
+ }
+
+ auto chat_full = get_chat_full(chat_id);
+ if (chat_full != nullptr) {
+ VLOG(file_references) << "Don't need to create file source for full " << chat_id;
+ // chat full was already added, source ID was registered and shouldn't be needed
+ return chat_full->is_update_chat_full_sent ? FileSourceId() : chat_full->file_source_id;
+ }
+
+ auto &source_id = chat_full_file_source_ids_[chat_id];
+ if (!source_id.is_valid()) {
+ source_id = td_->file_reference_manager_->create_chat_full_file_source(chat_id);
+ }
+ VLOG(file_references) << "Return " << source_id << " for full " << chat_id;
+ return source_id;
+}
+
+FileSourceId ContactsManager::get_channel_full_file_source_id(ChannelId channel_id) {
+ if (!channel_id.is_valid()) {
+ return FileSourceId();
+ }
+
+ auto channel_full = get_channel_full(channel_id);
+ if (channel_full != nullptr) {
+ VLOG(file_references) << "Don't need to create file source for full " << channel_id;
+ // channel full was already added, source ID was registered and shouldn't be needed
+ return channel_full->is_update_channel_full_sent ? FileSourceId() : channel_full->file_source_id;
+ }
+
+ auto &source_id = channel_full_file_source_ids_[channel_id];
+ if (!source_id.is_valid()) {
+ source_id = td_->file_reference_manager_->create_channel_full_file_source(channel_id);
+ }
+ VLOG(file_references) << "Return " << source_id << " for full " << channel_id;
+ return source_id;
+}
+
bool ContactsManager::have_chat(ChatId chat_id) const {
return chats_.count(chat_id) > 0;
}
const ContactsManager::Chat *ContactsManager::get_chat(ChatId chat_id) const {
- auto p = chats_.find(chat_id);
- if (p == chats_.end()) {
- return nullptr;
- } else {
- return &p->second;
- }
+ return chats_.get_pointer(chat_id);
}
ContactsManager::Chat *ContactsManager::get_chat(ChatId chat_id) {
- auto p = chats_.find(chat_id);
- if (p == chats_.end()) {
- return nullptr;
- } else {
- return &p->second;
- }
+ return chats_.get_pointer(chat_id);
}
ContactsManager::Chat *ContactsManager::add_chat(ChatId chat_id) {
CHECK(chat_id.is_valid());
- return &chats_[chat_id];
+ auto &chat_ptr = chats_[chat_id];
+ if (chat_ptr == nullptr) {
+ chat_ptr = make_unique<Chat>();
+ }
+ return chat_ptr.get();
}
bool ContactsManager::get_chat(ChatId chat_id, int left_tries, Promise<Unit> &&promise) {
if (!chat_id.is_valid()) {
- promise.set_error(Status::Error(6, "Invalid basic group id"));
+ promise.set_error(Status::Error(400, "Invalid basic group identifier"));
return false;
}
@@ -8191,11 +15575,11 @@ bool ContactsManager::get_chat(ChatId chat_id, int left_tries, Promise<Unit> &&p
}
if (left_tries > 1) {
- td_->create_handler<GetChatsQuery>(std::move(promise))->send(vector<int32>{chat_id.get()});
+ td_->create_handler<GetChatsQuery>(std::move(promise))->send(vector<int64>{chat_id.get()});
return false;
}
- promise.set_error(Status::Error(6, "Group not found"));
+ promise.set_error(Status::Error(400, "Group not found"));
return false;
}
@@ -8203,110 +15587,118 @@ bool ContactsManager::get_chat(ChatId chat_id, int left_tries, Promise<Unit> &&p
return true;
}
-const ContactsManager::ChatFull *ContactsManager::get_chat_full(ChatId chat_id) const {
- auto p = chats_full_.find(chat_id);
- if (p == chats_full_.end()) {
- return nullptr;
- } else {
- return &p->second;
+void ContactsManager::reload_chat(ChatId chat_id, Promise<Unit> &&promise) {
+ if (!chat_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid basic group identifier"));
}
+
+ // there is no much reason to combine different requests into one request
+ td_->create_handler<GetChatsQuery>(std::move(promise))->send(vector<int64>{chat_id.get()});
+}
+
+const ContactsManager::ChatFull *ContactsManager::get_chat_full(ChatId chat_id) const {
+ return chats_full_.get_pointer(chat_id);
}
ContactsManager::ChatFull *ContactsManager::get_chat_full(ChatId chat_id) {
- auto p = chats_full_.find(chat_id);
- if (p == chats_full_.end()) {
- return nullptr;
- } else {
- return &p->second;
+ return chats_full_.get_pointer(chat_id);
+}
+
+ContactsManager::ChatFull *ContactsManager::add_chat_full(ChatId chat_id) {
+ CHECK(chat_id.is_valid());
+ auto &chat_full_ptr = chats_full_[chat_id];
+ if (chat_full_ptr == nullptr) {
+ chat_full_ptr = make_unique<ChatFull>();
}
+ return chat_full_ptr.get();
}
-bool ContactsManager::is_chat_full_outdated(ChatFull *chat_full, Chat *c, ChatId chat_id) {
+bool ContactsManager::is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id,
+ bool only_participants) const {
CHECK(c != nullptr);
CHECK(chat_full != nullptr);
+ if (!c->is_active && chat_full->version == -1) {
+ return false;
+ }
+
if (chat_full->version != c->version) {
- LOG(INFO) << "Have outdated ChatFull " << chat_id << " with current version "
- << (chat_full ? chat_full->version : -123456789) << " and chat version " << c->version;
+ LOG(INFO) << "Have outdated ChatFull " << chat_id << " with current version " << chat_full->version
+ << " and chat version " << c->version;
return true;
}
- for (auto &participant : chat_full->participants) {
- auto user = get_user(participant.user_id);
- if (user != nullptr && user->bot_info_version != -1) {
- auto user_full = get_user_full(participant.user_id);
- if (user_full == nullptr || user_full->is_bot_info_expired(user->bot_info_version)) {
- LOG(INFO) << "Have outdated botInfo for " << participant.user_id << " with version "
- << (user_full && user_full->bot_info ? user_full->bot_info->version : -123456789)
- << ", but current version is " << user->bot_info_version;
- return true;
- }
- }
+ if (!only_participants && c->is_active && c->status.can_manage_invite_links() && !chat_full->invite_link.is_valid()) {
+ LOG(INFO) << "Have outdated invite link in " << chat_id;
+ return true;
}
+ if (!only_participants &&
+ !is_same_dialog_photo(td_->file_manager_.get(), DialogId(chat_id), chat_full->photo, c->photo)) {
+ LOG(INFO) << "Have outdated chat photo in " << chat_id;
+ return true;
+ }
+
+ LOG(DEBUG) << "Full " << chat_id << " is up-to-date with version " << chat_full->version << " and photos " << c->photo
+ << '/' << chat_full->photo;
return false;
}
-bool ContactsManager::get_chat_full(ChatId chat_id, Promise<Unit> &&promise) {
- auto chat = get_chat(chat_id);
- if (chat == nullptr) {
- promise.set_error(Status::Error(6, "Group not found"));
- return false;
+void ContactsManager::load_chat_full(ChatId chat_id, bool force, Promise<Unit> &&promise, const char *source) {
+ auto c = get_chat(chat_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Group not found"));
}
- auto chat_full = get_chat_full(chat_id);
+ auto chat_full = get_chat_full_force(chat_id, source);
if (chat_full == nullptr) {
LOG(INFO) << "Full " << chat_id << " not found";
- send_get_chat_full_query(chat_id, std::move(promise));
- return false;
+ return send_get_chat_full_query(chat_id, std::move(promise), source);
}
- if (is_chat_full_outdated(chat_full, chat, chat_id)) {
+ if (is_chat_full_outdated(chat_full, c, chat_id, false)) {
LOG(INFO) << "Have outdated full " << chat_id;
- if (td_->auth_manager_->is_bot()) {
- send_get_chat_full_query(chat_id, std::move(promise));
- return false;
- } else {
- send_get_chat_full_query(chat_id, Auto());
+ if (td_->auth_manager_->is_bot() && !force) {
+ return send_get_chat_full_query(chat_id, std::move(promise), source);
}
+
+ send_get_chat_full_query(chat_id, Auto(), source);
}
promise.set_value(Unit());
- return true;
}
-void ContactsManager::send_get_chat_full_query(ChatId chat_id, Promise<Unit> &&promise) {
- auto &promises = get_chat_full_queries_[chat_id];
- promises.push_back(std::move(promise));
- if (promises.size() != 1) {
- // query has already been sent, just wait for the result
- return;
+void ContactsManager::reload_chat_full(ChatId chat_id, Promise<Unit> &&promise) {
+ send_get_chat_full_query(chat_id, std::move(promise), "reload_chat_full");
+}
+
+void ContactsManager::send_get_chat_full_query(ChatId chat_id, Promise<Unit> &&promise, const char *source) {
+ LOG(INFO) << "Get full " << chat_id << " from " << source;
+ if (!chat_id.is_valid()) {
+ return promise.set_error(Status::Error(500, "Invalid chat_id"));
}
+ auto send_query = PromiseCreator::lambda([td = td_, chat_id](Result<Promise<Unit>> &&promise) {
+ if (promise.is_ok() && !G()->close_flag()) {
+ td->create_handler<GetFullChatQuery>(promise.move_as_ok())->send(chat_id);
+ }
+ });
- td_->create_handler<GetFullChatQuery>()->send(chat_id);
+ get_chat_full_queries_.add_query(DialogId(chat_id).get(), std::move(send_query), std::move(promise));
}
-void ContactsManager::on_get_chat_full_success(ChatId chat_id) {
- auto it = get_chat_full_queries_.find(chat_id);
- CHECK(it != get_chat_full_queries_.end());
- CHECK(!it->second.empty());
- auto promises = std::move(it->second);
- get_chat_full_queries_.erase(it);
-
- for (auto &promise : promises) {
- promise.set_value(Unit());
+int32 ContactsManager::get_chat_date(ChatId chat_id) const {
+ auto c = get_chat(chat_id);
+ if (c == nullptr) {
+ return 0;
}
+ return c->date;
}
-void ContactsManager::on_get_chat_full_fail(ChatId chat_id, Status &&error) {
- auto it = get_chat_full_queries_.find(chat_id);
- CHECK(it != get_chat_full_queries_.end());
- CHECK(!it->second.empty());
- auto promises = std::move(it->second);
- get_chat_full_queries_.erase(it);
-
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+int32 ContactsManager::get_chat_participant_count(ChatId chat_id) const {
+ auto c = get_chat(chat_id);
+ if (c == nullptr) {
+ return 0;
}
+ return c->participant_count;
}
bool ContactsManager::get_chat_is_active(ChatId chat_id) const {
@@ -8317,6 +15709,14 @@ bool ContactsManager::get_chat_is_active(ChatId chat_id) const {
return c->is_active;
}
+ChannelId ContactsManager::get_chat_migrated_to_channel_id(ChatId chat_id) const {
+ auto c = get_chat(chat_id);
+ if (c == nullptr) {
+ return ChannelId();
+ }
+ return c->migrated_to_channel_id;
+}
+
DialogParticipantStatus ContactsManager::get_chat_status(ChatId chat_id) const {
auto c = get_chat(chat_id);
if (c == nullptr) {
@@ -8326,19 +15726,25 @@ DialogParticipantStatus ContactsManager::get_chat_status(ChatId chat_id) const {
}
DialogParticipantStatus ContactsManager::get_chat_status(const Chat *c) {
- if (c->kicked || !c->is_active) {
+ if (!c->is_active) {
return DialogParticipantStatus::Banned(0);
}
- if (c->left) {
- return DialogParticipantStatus::Left();
- }
- if (c->is_creator) {
- return DialogParticipantStatus::Creator(true);
+ return c->status;
+}
+
+DialogParticipantStatus ContactsManager::get_chat_permissions(ChatId chat_id) const {
+ auto c = get_chat(chat_id);
+ if (c == nullptr) {
+ return DialogParticipantStatus::Banned(0);
}
- if (c->can_edit) {
- return DialogParticipantStatus::GroupAdministrator(false);
+ return get_chat_permissions(c);
+}
+
+DialogParticipantStatus ContactsManager::get_chat_permissions(const Chat *c) const {
+ if (!c->is_active) {
+ return DialogParticipantStatus::Banned(0);
}
- return DialogParticipantStatus::Member();
+ return c->status.apply_restrictions(c->default_permissions, td_->auth_manager_->is_bot());
}
bool ContactsManager::is_appointed_chat_administrator(ChatId chat_id) const {
@@ -8346,16 +15752,24 @@ bool ContactsManager::is_appointed_chat_administrator(ChatId chat_id) const {
if (c == nullptr) {
return false;
}
- if (c->everyone_is_administrator) {
- return c->is_creator;
- } else {
- return c->can_edit;
- }
+ return c->status.is_administrator();
+}
+
+bool ContactsManager::is_channel_public(ChannelId channel_id) const {
+ return is_channel_public(get_channel(channel_id));
+}
+
+bool ContactsManager::is_channel_public(const Channel *c) {
+ return c != nullptr && (c->usernames.has_first_username() || c->has_location);
}
ChannelType ContactsManager::get_channel_type(ChannelId channel_id) const {
auto c = get_channel(channel_id);
if (c == nullptr) {
+ auto min_channel = get_min_channel(channel_id);
+ if (min_channel != nullptr) {
+ return min_channel->is_megagroup_ ? ChannelType::Megagroup : ChannelType::Broadcast;
+ }
return ChannelType::Unknown;
}
return get_channel_type(c);
@@ -8368,6 +15782,22 @@ ChannelType ContactsManager::get_channel_type(const Channel *c) {
return ChannelType::Broadcast;
}
+bool ContactsManager::is_broadcast_channel(ChannelId channel_id) const {
+ return get_channel_type(channel_id) == ChannelType::Broadcast;
+}
+
+bool ContactsManager::is_megagroup_channel(ChannelId channel_id) const {
+ return get_channel_type(channel_id) == ChannelType::Megagroup;
+}
+
+bool ContactsManager::is_forum_channel(ChannelId channel_id) const {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return false;
+ }
+ return c->is_forum;
+}
+
int32 ContactsManager::get_channel_date(ChannelId channel_id) const {
auto c = get_channel(channel_id);
if (c == nullptr) {
@@ -8389,6 +15819,39 @@ DialogParticipantStatus ContactsManager::get_channel_status(const Channel *c) {
return c->status;
}
+DialogParticipantStatus ContactsManager::get_channel_permissions(ChannelId channel_id) const {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return DialogParticipantStatus::Banned(0);
+ }
+ return get_channel_permissions(c);
+}
+
+DialogParticipantStatus ContactsManager::get_channel_permissions(const Channel *c) const {
+ c->status.update_restrictions();
+ if (!c->is_megagroup) {
+ // there is no restrictions in broadcast channels
+ return c->status;
+ }
+ return c->status.apply_restrictions(c->default_permissions, td_->auth_manager_->is_bot());
+}
+
+int32 ContactsManager::get_channel_participant_count(ChannelId channel_id) const {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return 0;
+ }
+ return c->participant_count;
+}
+
+bool ContactsManager::get_channel_is_verified(ChannelId channel_id) const {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return false;
+ }
+ return c->is_verified;
+}
+
bool ContactsManager::get_channel_sign_messages(ChannelId channel_id) const {
auto c = get_channel(channel_id);
if (c == nullptr) {
@@ -8401,6 +15864,60 @@ bool ContactsManager::get_channel_sign_messages(const Channel *c) {
return c->sign_messages;
}
+bool ContactsManager::get_channel_has_linked_channel(ChannelId channel_id) const {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return false;
+ }
+ return get_channel_has_linked_channel(c);
+}
+
+bool ContactsManager::get_channel_has_linked_channel(const Channel *c) {
+ return c->has_linked_channel;
+}
+
+bool ContactsManager::get_channel_can_be_deleted(ChannelId channel_id) const {
+ auto c = get_channel(channel_id);
+ if (c == nullptr) {
+ return false;
+ }
+ return get_channel_can_be_deleted(c);
+}
+
+bool ContactsManager::get_channel_can_be_deleted(const Channel *c) {
+ return c->can_be_deleted;
+}
+
+bool ContactsManager::get_channel_join_to_send(const Channel *c) {
+ return c->join_to_send || !c->is_megagroup || !c->has_linked_channel;
+}
+
+bool ContactsManager::get_channel_join_request(const Channel *c) {
+ return c->join_request && c->is_megagroup && (is_channel_public(c) || c->has_linked_channel);
+}
+
+ChannelId ContactsManager::get_channel_linked_channel_id(ChannelId channel_id) {
+ auto channel_full = get_channel_full_const(channel_id);
+ if (channel_full == nullptr) {
+ channel_full = get_channel_full_force(channel_id, true, "get_channel_linked_channel_id");
+ if (channel_full == nullptr) {
+ return ChannelId();
+ }
+ }
+ return channel_full->linked_channel_id;
+}
+
+int32 ContactsManager::get_channel_slow_mode_delay(ChannelId channel_id) {
+ auto channel_full = get_channel_full_const(channel_id);
+ if (channel_full == nullptr) {
+ channel_full = get_channel_full_force(channel_id, true, "get_channel_slow_mode_delay");
+ if (channel_full == nullptr) {
+ return 0;
+ }
+ }
+ return channel_full->slow_mode_delay;
+}
+
bool ContactsManager::have_channel(ChannelId channel_id) const {
return channels_.count(channel_id) > 0;
}
@@ -8409,32 +15926,38 @@ bool ContactsManager::have_min_channel(ChannelId channel_id) const {
return min_channels_.count(channel_id) > 0;
}
-const ContactsManager::Channel *ContactsManager::get_channel(ChannelId channel_id) const {
- auto p = channels_.find(channel_id);
- if (p == channels_.end()) {
- return nullptr;
- } else {
- return &p->second;
+const MinChannel *ContactsManager::get_min_channel(ChannelId channel_id) const {
+ return min_channels_.get_pointer(channel_id);
+}
+
+void ContactsManager::add_min_channel(ChannelId channel_id, const MinChannel &min_channel) {
+ if (have_channel(channel_id) || have_min_channel(channel_id) || !channel_id.is_valid()) {
+ return;
}
+ min_channels_.set(channel_id, td::make_unique<MinChannel>(min_channel));
+}
+
+const ContactsManager::Channel *ContactsManager::get_channel(ChannelId channel_id) const {
+ return channels_.get_pointer(channel_id);
}
ContactsManager::Channel *ContactsManager::get_channel(ChannelId channel_id) {
- auto p = channels_.find(channel_id);
- if (p == channels_.end()) {
- return nullptr;
- } else {
- return &p->second;
- }
+ return channels_.get_pointer(channel_id);
}
-ContactsManager::Channel *ContactsManager::add_channel(ChannelId channel_id) {
+ContactsManager::Channel *ContactsManager::add_channel(ChannelId channel_id, const char *source) {
CHECK(channel_id.is_valid());
- return &channels_[channel_id];
+ auto &channel_ptr = channels_[channel_id];
+ if (channel_ptr == nullptr) {
+ channel_ptr = make_unique<Channel>();
+ min_channels_.erase(channel_id);
+ }
+ return channel_ptr.get();
}
bool ContactsManager::get_channel(ChannelId channel_id, int left_tries, Promise<Unit> &&promise) {
if (!channel_id.is_valid()) {
- promise.set_error(Status::Error(6, "Invalid supergroup id"));
+ promise.set_error(Status::Error(400, "Invalid supergroup identifier"));
return false;
}
@@ -8450,7 +15973,7 @@ bool ContactsManager::get_channel(ChannelId channel_id, int left_tries, Promise<
return false;
}
- promise.set_error(Status::Error(6, "Supergroup not found"));
+ promise.set_error(Status::Error(400, "Supergroup not found"));
return false;
}
@@ -8458,94 +15981,115 @@ bool ContactsManager::get_channel(ChannelId channel_id, int left_tries, Promise<
return true;
}
-const ContactsManager::ChannelFull *ContactsManager::get_channel_full(ChannelId channel_id) const {
- auto p = channels_full_.find(channel_id);
- if (p == channels_full_.end()) {
- return nullptr;
- } else {
- return &p->second;
+void ContactsManager::reload_channel(ChannelId channel_id, Promise<Unit> &&promise) {
+ if (!channel_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid supergroup identifier"));
+ }
+
+ have_channel_force(channel_id);
+ auto input_channel = get_input_channel(channel_id);
+ if (input_channel == nullptr) {
+ input_channel = make_tl_object<telegram_api::inputChannel>(channel_id.get(), 0);
}
+
+ // there is no much reason to combine different requests into one request
+ // requests with 0 access_hash must not be merged
+ td_->create_handler<GetChannelsQuery>(std::move(promise))->send(std::move(input_channel));
}
-ContactsManager::ChannelFull *ContactsManager::get_channel_full(ChannelId channel_id) {
- auto p = channels_full_.find(channel_id);
- if (p == channels_full_.end()) {
+const ContactsManager::ChannelFull *ContactsManager::get_channel_full_const(ChannelId channel_id) const {
+ return channels_full_.get_pointer(channel_id);
+}
+
+const ContactsManager::ChannelFull *ContactsManager::get_channel_full(ChannelId channel_id) const {
+ return channels_full_.get_pointer(channel_id);
+}
+
+ContactsManager::ChannelFull *ContactsManager::get_channel_full(ChannelId channel_id, bool only_local,
+ const char *source) {
+ auto channel_full = channels_full_.get_pointer(channel_id);
+ if (channel_full == nullptr) {
return nullptr;
}
- auto channel_full = &p->second;
- if (channel_full->is_expired()) {
- auto input_channel = get_input_channel(channel_id);
- CHECK(input_channel != nullptr);
- send_get_channel_full_query(channel_id, std::move(input_channel), Auto());
+ if (!only_local && channel_full->is_expired() && !td_->auth_manager_->is_bot()) {
+ send_get_channel_full_query(channel_full, channel_id, Auto(), source);
}
return channel_full;
}
-bool ContactsManager::get_channel_full(ChannelId channel_id, Promise<Unit> &&promise) {
- auto channel_full = get_channel_full(channel_id);
- if (channel_full == nullptr) {
- auto input_channel = get_input_channel(channel_id);
- if (input_channel == nullptr) {
- promise.set_error(Status::Error(6, "Supergroup not found"));
- return false;
- }
+ContactsManager::ChannelFull *ContactsManager::add_channel_full(ChannelId channel_id) {
+ CHECK(channel_id.is_valid());
+ auto &channel_full_ptr = channels_full_[channel_id];
+ if (channel_full_ptr == nullptr) {
+ channel_full_ptr = make_unique<ChannelFull>();
+ }
+ return channel_full_ptr.get();
+}
- send_get_channel_full_query(channel_id, std::move(input_channel), std::move(promise));
- return false;
+void ContactsManager::load_channel_full(ChannelId channel_id, bool force, Promise<Unit> &&promise, const char *source) {
+ auto channel_full = get_channel_full_force(channel_id, true, source);
+ if (channel_full == nullptr) {
+ return send_get_channel_full_query(channel_full, channel_id, std::move(promise), source);
}
if (channel_full->is_expired()) {
- if (td_->auth_manager_->is_bot()) {
- auto input_channel = get_input_channel(channel_id);
- CHECK(input_channel != nullptr);
- send_get_channel_full_query(channel_id, std::move(input_channel), std::move(promise));
- return false;
- } else {
- // request has already been sent in get_channel_full
- // send_get_channel_full_query(channel_id, std::move(input_channel), Auto());
+ if (td_->auth_manager_->is_bot() && !force) {
+ return send_get_channel_full_query(channel_full, channel_id, std::move(promise), "load expired channel_full");
+ }
+
+ Promise<Unit> new_promise;
+ if (promise) {
+ new_promise = PromiseCreator::lambda([channel_id](Result<Unit> result) {
+ if (result.is_error()) {
+ LOG(INFO) << "Failed to reload expired " << channel_id << ": " << result.error();
+ } else {
+ LOG(INFO) << "Reloaded expired " << channel_id;
+ }
+ });
}
+ send_get_channel_full_query(channel_full, channel_id, std::move(new_promise), "load expired channel_full");
}
promise.set_value(Unit());
- return true;
}
-void ContactsManager::send_get_channel_full_query(ChannelId channel_id,
- tl_object_ptr<telegram_api::InputChannel> &&input_channel,
- Promise<Unit> &&promise) {
- auto &promises = get_channel_full_queries_[channel_id];
- promises.push_back(std::move(promise));
- if (promises.size() != 1) {
- // query has already been sent, just wait for the result
- return;
- }
-
- td_->create_handler<GetFullChannelQuery>()->send(channel_id, std::move(input_channel));
+void ContactsManager::reload_channel_full(ChannelId channel_id, Promise<Unit> &&promise, const char *source) {
+ send_get_channel_full_query(get_channel_full(channel_id, true, "reload_channel_full"), channel_id, std::move(promise),
+ source);
}
-void ContactsManager::on_get_channel_full_success(ChannelId channel_id) {
- auto it = get_channel_full_queries_.find(channel_id);
- CHECK(it != get_channel_full_queries_.end());
- CHECK(!it->second.empty());
- auto promises = std::move(it->second);
- get_channel_full_queries_.erase(it);
-
- for (auto &promise : promises) {
- promise.set_value(Unit());
+void ContactsManager::send_get_channel_full_query(ChannelFull *channel_full, ChannelId channel_id,
+ Promise<Unit> &&promise, const char *source) {
+ auto input_channel = get_input_channel(channel_id);
+ if (input_channel == nullptr) {
+ return promise.set_error(Status::Error(400, "Supergroup not found"));
}
-}
-void ContactsManager::on_get_channel_full_fail(ChannelId channel_id, Status &&error) {
- auto it = get_channel_full_queries_.find(channel_id);
- CHECK(it != get_channel_full_queries_.end());
- CHECK(!it->second.empty());
- auto promises = std::move(it->second);
- get_channel_full_queries_.erase(it);
+ if (!have_input_peer_channel(channel_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+ if (channel_full != nullptr) {
+ if (!promise) {
+ if (channel_full->repair_request_version != 0) {
+ LOG(INFO) << "Skip get full " << channel_id << " request from " << source;
+ return;
+ }
+ channel_full->repair_request_version = channel_full->speculative_version;
+ } else {
+ channel_full->repair_request_version = std::numeric_limits<uint32>::max();
+ }
}
+
+ LOG(INFO) << "Get full " << channel_id << " from " << source;
+ auto send_query = PromiseCreator::lambda(
+ [td = td_, channel_id, input_channel = std::move(input_channel)](Result<Promise<Unit>> &&promise) mutable {
+ if (promise.is_ok() && !G()->close_flag()) {
+ td->create_handler<GetFullChannelQuery>(promise.move_as_ok())->send(channel_id, std::move(input_channel));
+ }
+ });
+ get_chat_full_queries_.add_query(DialogId(channel_id).get(), std::move(send_query), std::move(promise));
}
bool ContactsManager::have_secret_chat(SecretChatId secret_chat_id) const {
@@ -8554,28 +16098,24 @@ bool ContactsManager::have_secret_chat(SecretChatId secret_chat_id) const {
ContactsManager::SecretChat *ContactsManager::add_secret_chat(SecretChatId secret_chat_id) {
CHECK(secret_chat_id.is_valid());
- return &secret_chats_[secret_chat_id];
+ auto &secret_chat_ptr = secret_chats_[secret_chat_id];
+ if (secret_chat_ptr == nullptr) {
+ secret_chat_ptr = make_unique<SecretChat>();
+ }
+ return secret_chat_ptr.get();
}
const ContactsManager::SecretChat *ContactsManager::get_secret_chat(SecretChatId secret_chat_id) const {
- auto it = secret_chats_.find(secret_chat_id);
- if (it == secret_chats_.end()) {
- return nullptr;
- }
- return &it->second;
+ return secret_chats_.get_pointer(secret_chat_id);
}
ContactsManager::SecretChat *ContactsManager::get_secret_chat(SecretChatId secret_chat_id) {
- auto it = secret_chats_.find(secret_chat_id);
- if (it == secret_chats_.end()) {
- return nullptr;
- }
- return &it->second;
+ return secret_chats_.get_pointer(secret_chat_id);
}
bool ContactsManager::get_secret_chat(SecretChatId secret_chat_id, bool force, Promise<Unit> &&promise) {
if (!secret_chat_id.is_valid()) {
- promise.set_error(Status::Error(6, "Invalid secret chat id"));
+ promise.set_error(Status::Error(400, "Invalid secret chat identifier"));
return false;
}
@@ -8586,7 +16126,7 @@ bool ContactsManager::get_secret_chat(SecretChatId secret_chat_id, bool force, P
return false;
}
- promise.set_error(Status::Error(6, "Secret chat not found"));
+ promise.set_error(Status::Error(400, "Secret chat not found"));
return false;
}
@@ -8596,390 +16136,716 @@ bool ContactsManager::get_secret_chat(SecretChatId secret_chat_id, bool force, P
void ContactsManager::on_update_secret_chat(SecretChatId secret_chat_id, int64 access_hash, UserId user_id,
SecretChatState state, bool is_outbound, int32 ttl, int32 date,
- string key_hash, int32 layer) {
+ string key_hash, int32 layer, FolderId initial_folder_id) {
LOG(INFO) << "Update " << secret_chat_id << " with " << user_id << " and access_hash " << access_hash;
auto *secret_chat = add_secret_chat(secret_chat_id);
if (access_hash != secret_chat->access_hash) {
secret_chat->access_hash = access_hash;
- secret_chat->is_changed = true;
+ secret_chat->need_save_to_database = true;
}
if (user_id.is_valid() && user_id != secret_chat->user_id) {
if (secret_chat->user_id.is_valid()) {
LOG(ERROR) << "Secret chat user has changed from " << secret_chat->user_id << " to " << user_id;
auto &old_secret_chat_ids = secret_chats_with_user_[secret_chat->user_id];
- old_secret_chat_ids.erase(std::remove(old_secret_chat_ids.begin(), old_secret_chat_ids.end(), secret_chat_id),
- old_secret_chat_ids.end());
+ td::remove(old_secret_chat_ids, secret_chat_id);
}
secret_chat->user_id = user_id;
secret_chats_with_user_[secret_chat->user_id].push_back(secret_chat_id);
- secret_chat->need_send_update = true;
+ secret_chat->is_changed = true;
}
if (state != SecretChatState::Unknown && state != secret_chat->state) {
secret_chat->state = state;
- secret_chat->need_send_update = true;
+ secret_chat->is_changed = true;
+ secret_chat->is_state_changed = true;
}
if (is_outbound != secret_chat->is_outbound) {
secret_chat->is_outbound = is_outbound;
- secret_chat->need_send_update = true;
+ secret_chat->is_changed = true;
}
if (ttl != -1 && ttl != secret_chat->ttl) {
secret_chat->ttl = ttl;
- secret_chat->need_send_update = true;
+ secret_chat->need_save_to_database = true;
+ secret_chat->is_ttl_changed = true;
}
if (date != 0 && date != secret_chat->date) {
secret_chat->date = date;
- secret_chat->is_changed = true;
+ secret_chat->need_save_to_database = true;
}
if (!key_hash.empty() && key_hash != secret_chat->key_hash) {
secret_chat->key_hash = std::move(key_hash);
- secret_chat->need_send_update = true;
+ secret_chat->is_changed = true;
}
if (layer != 0 && layer != secret_chat->layer) {
secret_chat->layer = layer;
- secret_chat->need_send_update = true;
+ secret_chat->is_changed = true;
+ }
+ if (initial_folder_id != FolderId() && initial_folder_id != secret_chat->initial_folder_id) {
+ secret_chat->initial_folder_id = initial_folder_id;
+ secret_chat->is_changed = true;
}
update_secret_chat(secret_chat, secret_chat_id);
}
-std::pair<int32, vector<UserId>> ContactsManager::search_among_users(const vector<UserId> &user_ids,
- const string &query, int32 limit) {
+std::pair<int32, vector<DialogId>> ContactsManager::search_among_dialogs(const vector<DialogId> &dialog_ids,
+ const string &query, int32 limit) const {
Hints hints; // TODO cache Hints
- UserId my_user_id = get_my_id("search_among_users");
- for (auto user_id : user_ids) {
- auto u = get_user(user_id);
- if (u == nullptr) {
- continue;
- }
- hints.add(user_id.get(), u->first_name + " " + u->last_name + " " + u->username);
- auto was_online = u->was_online;
- if (user_id == my_user_id && my_was_online_local_ != 0) {
- was_online = my_was_online_local_;
+ for (auto dialog_id : dialog_ids) {
+ int64 rating = 0;
+ if (dialog_id.get_type() == DialogType::User) {
+ auto user_id = dialog_id.get_user_id();
+ auto u = get_user(user_id);
+ if (u == nullptr) {
+ continue;
+ }
+ if (query.empty()) {
+ hints.add(dialog_id.get(), Slice(" "));
+ } else {
+ hints.add(dialog_id.get(), get_user_search_text(u));
+ }
+ rating = -get_user_was_online(u, user_id);
+ } else {
+ if (!td_->messages_manager_->have_dialog_info(dialog_id)) {
+ continue;
+ }
+ if (query.empty()) {
+ hints.add(dialog_id.get(), Slice(" "));
+ } else {
+ hints.add(dialog_id.get(), get_dialog_search_text(dialog_id));
+ }
}
- hints.set_rating(user_id.get(), -was_online);
+ hints.set_rating(dialog_id.get(), rating);
}
auto result = hints.search(query, limit, true);
- return {narrow_cast<int32>(result.first),
- transform(result.second, [](int64 key) { return UserId(narrow_cast<int32>(key)); })};
+ return {narrow_cast<int32>(result.first), transform(result.second, [](int64 key) { return DialogId(key); })};
}
-DialogParticipant ContactsManager::get_chat_participant(ChatId chat_id, UserId user_id, bool force,
- Promise<Unit> &&promise) {
- LOG(INFO) << "Trying to get " << user_id << " as member of " << chat_id;
- if (force) {
- promise.set_value(Unit());
- } else if (!get_chat_full(chat_id, std::move(promise))) {
- return DialogParticipant();
+void ContactsManager::add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit,
+ Promise<Unit> &&promise) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "add_dialog_participant")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- // promise is already set
- auto result = get_chat_participant(chat_id, user_id);
- if (result == nullptr) {
- return {user_id, UserId(), 0, DialogParticipantStatus::Left()};
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return promise.set_error(Status::Error(400, "Can't add members to a private chat"));
+ case DialogType::Chat:
+ return add_chat_participant(dialog_id.get_chat_id(), user_id, forward_limit, std::move(promise));
+ case DialogType::Channel:
+ return add_channel_participant(dialog_id.get_channel_id(), user_id, DialogParticipantStatus::Left(),
+ std::move(promise));
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Can't add members to a secret chat"));
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+}
+
+void ContactsManager::add_dialog_participants(DialogId dialog_id, const vector<UserId> &user_ids,
+ Promise<Unit> &&promise) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "add_dialog_participants")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- return *result;
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return promise.set_error(Status::Error(400, "Can't add members to a private chat"));
+ case DialogType::Chat:
+ return promise.set_error(Status::Error(400, "Can't add many members at once to a basic group chat"));
+ case DialogType::Channel:
+ return add_channel_participants(dialog_id.get_channel_id(), user_ids, std::move(promise));
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Can't add members to a secret chat"));
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
}
-std::pair<int32, vector<DialogParticipant>> ContactsManager::search_chat_participants(ChatId chat_id,
- const string &query, int32 limit,
- bool force,
- Promise<Unit> &&promise) {
- if (limit < 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be non-negative"));
- return {};
+void ContactsManager::set_dialog_participant_status(DialogId dialog_id, DialogId participant_dialog_id,
+ td_api::object_ptr<td_api::ChatMemberStatus> &&chat_member_status,
+ Promise<Unit> &&promise) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "set_dialog_participant_status")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- if (force) {
- promise.set_value(Unit());
- } else if (!get_chat_full(chat_id, std::move(promise))) {
- return {};
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return promise.set_error(Status::Error(400, "Chat member status can't be changed in private chats"));
+ case DialogType::Chat: {
+ auto status = get_dialog_participant_status(chat_member_status, ChannelType::Unknown);
+ if (participant_dialog_id.get_type() != DialogType::User) {
+ if (status == DialogParticipantStatus::Left()) {
+ return promise.set_value(Unit());
+ } else {
+ return promise.set_error(Status::Error(400, "Chats can't be members of basic groups"));
+ }
+ }
+ return set_chat_participant_status(dialog_id.get_chat_id(), participant_dialog_id.get_user_id(), status,
+ std::move(promise));
+ }
+ case DialogType::Channel:
+ return set_channel_participant_status(dialog_id.get_channel_id(), participant_dialog_id,
+ std::move(chat_member_status), std::move(promise));
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Chat member status can't be changed in secret chats"));
+ case DialogType::None:
+ default:
+ UNREACHABLE();
}
- // promise is already set
+}
- auto chat_full = get_chat_full(chat_id);
- if (chat_full == nullptr) {
- return {};
+void ContactsManager::ban_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id,
+ int32 banned_until_date, bool revoke_messages, Promise<Unit> &&promise) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "ban_dialog_participant")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- auto user_ids = transform(chat_full->participants, [](const auto &participant) { return participant.user_id; });
- int32 total_count;
- std::tie(total_count, user_ids) = search_among_users(user_ids, query, limit);
- return {total_count, transform(user_ids, [&](UserId user_id) { return *get_chat_participant(chat_full, user_id); })};
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return promise.set_error(Status::Error(400, "Can't ban members in private chats"));
+ case DialogType::Chat:
+ if (participant_dialog_id.get_type() != DialogType::User) {
+ return promise.set_error(Status::Error(400, "Can't ban chats in basic groups"));
+ }
+ return delete_chat_participant(dialog_id.get_chat_id(), participant_dialog_id.get_user_id(), revoke_messages,
+ std::move(promise));
+ case DialogType::Channel:
+ // must use td_api::chatMemberStatusBanned to properly fix banned_until_date
+ return set_channel_participant_status(dialog_id.get_channel_id(), participant_dialog_id,
+ td_api::make_object<td_api::chatMemberStatusBanned>(banned_until_date),
+ std::move(promise));
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Can't ban members in secret chats"));
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
}
-DialogParticipant ContactsManager::get_channel_participant(ChannelId channel_id, UserId user_id, int64 &random_id,
- bool force, Promise<Unit> &&promise) {
- LOG(INFO) << "Trying to get " << user_id << " as member of " << channel_id;
- if (random_id != 0) {
- // request has already been sent before
- auto it = received_channel_participant_.find(random_id);
- CHECK(it != received_channel_participant_.end());
- auto result = std::move(it->second);
- received_channel_participant_.erase(it);
- promise.set_value(Unit());
- return result;
+void ContactsManager::get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id,
+ Promise<td_api::object_ptr<td_api::chatMember>> &&promise) {
+ auto new_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), promise = std::move(promise)](Result<DialogParticipant> &&result) mutable {
+ TRY_RESULT_PROMISE(promise, dialog_participant, std::move(result));
+ send_closure(actor_id, &ContactsManager::finish_get_dialog_participant, std::move(dialog_participant),
+ std::move(promise));
+ });
+ do_get_dialog_participant(dialog_id, participant_dialog_id, std::move(new_promise));
+}
+
+void ContactsManager::finish_get_dialog_participant(DialogParticipant &&dialog_participant,
+ Promise<td_api::object_ptr<td_api::chatMember>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto participant_dialog_id = dialog_participant.dialog_id_;
+ bool is_user = participant_dialog_id.get_type() == DialogType::User;
+ if ((is_user && !have_user(participant_dialog_id.get_user_id())) ||
+ (!is_user && !td_->messages_manager_->have_dialog(participant_dialog_id))) {
+ return promise.set_error(Status::Error(400, "Member not found"));
+ }
+
+ promise.set_value(get_chat_member_object(dialog_participant));
+}
+
+void ContactsManager::do_get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id,
+ Promise<DialogParticipant> &&promise) {
+ LOG(INFO) << "Receive GetChatMember request to get " << participant_dialog_id << " in " << dialog_id;
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "do_get_dialog_participant")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- auto input_user = get_input_user(user_id);
- if (input_user == nullptr) {
- promise.set_error(Status::Error(6, "User not found"));
- return DialogParticipant();
+ switch (dialog_id.get_type()) {
+ case DialogType::User: {
+ auto my_user_id = get_my_id();
+ auto peer_user_id = dialog_id.get_user_id();
+ if (participant_dialog_id == DialogId(my_user_id)) {
+ return promise.set_value(DialogParticipant::private_member(my_user_id, peer_user_id));
+ }
+ if (participant_dialog_id == dialog_id) {
+ return promise.set_value(DialogParticipant::private_member(peer_user_id, my_user_id));
+ }
+
+ return promise.set_error(Status::Error(400, "Member not found"));
+ }
+ case DialogType::Chat:
+ if (participant_dialog_id.get_type() != DialogType::User) {
+ return promise.set_value(DialogParticipant::left(participant_dialog_id));
+ }
+ return get_chat_participant(dialog_id.get_chat_id(), participant_dialog_id.get_user_id(), std::move(promise));
+ case DialogType::Channel:
+ return get_channel_participant(dialog_id.get_channel_id(), participant_dialog_id, std::move(promise));
+ case DialogType::SecretChat: {
+ auto my_user_id = get_my_id();
+ auto peer_user_id = get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (participant_dialog_id == DialogId(my_user_id)) {
+ return promise.set_value(DialogParticipant::private_member(my_user_id, peer_user_id));
+ }
+ if (peer_user_id.is_valid() && participant_dialog_id == DialogId(peer_user_id)) {
+ return promise.set_value(DialogParticipant::private_member(peer_user_id, my_user_id));
+ }
+
+ return promise.set_error(Status::Error(400, "Member not found"));
+ }
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ return promise.set_error(Status::Error(500, "Wrong chat type"));
+ }
+}
+
+DialogParticipants ContactsManager::search_private_chat_participants(UserId my_user_id, UserId peer_user_id,
+ const string &query, int32 limit,
+ DialogParticipantFilter filter) const {
+ vector<DialogId> dialog_ids;
+ if (filter.is_dialog_participant_suitable(td_, DialogParticipant::private_member(my_user_id, peer_user_id))) {
+ dialog_ids.push_back(DialogId(my_user_id));
+ }
+ if (peer_user_id.is_valid() && peer_user_id != my_user_id &&
+ filter.is_dialog_participant_suitable(td_, DialogParticipant::private_member(peer_user_id, my_user_id))) {
+ dialog_ids.push_back(DialogId(peer_user_id));
+ }
+
+ auto result = search_among_dialogs(dialog_ids, query, limit);
+ return {result.first, transform(result.second, [&](DialogId dialog_id) {
+ auto user_id = dialog_id.get_user_id();
+ return DialogParticipant::private_member(user_id, user_id == my_user_id ? peer_user_id : my_user_id);
+ })};
+}
+
+void ContactsManager::search_dialog_participants(DialogId dialog_id, const string &query, int32 limit,
+ DialogParticipantFilter filter,
+ Promise<DialogParticipants> &&promise) {
+ LOG(INFO) << "Receive searchChatMembers request to search for \"" << query << "\" in " << dialog_id << " with filter "
+ << filter;
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "search_dialog_participants")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (limit < 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be non-negative"));
}
- if (!td_->auth_manager_->is_bot() && is_user_bot(user_id)) {
- // get BotInfo through UserFull
- auto user = get_user(user_id);
- auto user_full = get_user_full(user_id);
- if (user_full == nullptr || user_full->is_bot_info_expired(user->bot_info_version)) {
- if (force) {
- LOG(ERROR) << "Can't find cached UserFull";
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ promise.set_value(search_private_chat_participants(get_my_id(), dialog_id.get_user_id(), query, limit, filter));
+ return;
+ case DialogType::Chat:
+ return search_chat_participants(dialog_id.get_chat_id(), query, limit, filter, std::move(promise));
+ case DialogType::Channel: {
+ auto channel_id = dialog_id.get_channel_id();
+ if (filter.has_query()) {
+ return get_channel_participants(channel_id, filter.get_supergroup_members_filter_object(query), string(), 0,
+ limit, 0, std::move(promise));
} else {
- send_get_user_full_query(user_id, std::move(input_user), std::move(promise));
- return DialogParticipant();
+ return get_channel_participants(channel_id, filter.get_supergroup_members_filter_object(string()), query, 0,
+ 100, limit, std::move(promise));
}
}
+ case DialogType::SecretChat: {
+ auto peer_user_id = get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ promise.set_value(search_private_chat_participants(get_my_id(), peer_user_id, query, limit, filter));
+ return;
+ }
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ promise.set_error(Status::Error(500, "Wrong chat type"));
}
+}
- do {
- random_id = Random::secure_int64();
- } while (random_id == 0 || received_channel_participant_.find(random_id) != received_channel_participant_.end());
- received_channel_participant_[random_id]; // reserve place for result
+void ContactsManager::get_chat_participant(ChatId chat_id, UserId user_id, Promise<DialogParticipant> &&promise) {
+ LOG(INFO) << "Trying to get " << user_id << " as member of " << chat_id;
- LOG(DEBUG) << "Get info about " << user_id << " membership in the " << channel_id;
+ auto c = get_chat(chat_id);
+ if (c == nullptr) {
+ return promise.set_error(Status::Error(400, "Group not found"));
+ }
- auto on_result_promise = PromiseCreator::lambda(
- [this, random_id, promise = std::move(promise)](Result<DialogParticipant> r_dialog_participant) mutable {
- // ResultHandlers are cleared before managers, so it is safe to capture this
- auto it = received_channel_participant_.find(random_id);
- CHECK(it != received_channel_participant_.end());
+ auto chat_full = get_chat_full_force(chat_id, "get_chat_participant");
+ if (chat_full == nullptr || (td_->auth_manager_->is_bot() && is_chat_full_outdated(chat_full, c, chat_id, true))) {
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), chat_id, user_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ TRY_STATUS_PROMISE(promise, std::move(result));
+ send_closure(actor_id, &ContactsManager::finish_get_chat_participant, chat_id, user_id, std::move(promise));
+ });
+ send_get_chat_full_query(chat_id, std::move(query_promise), "get_chat_participant");
+ return;
+ }
- if (r_dialog_participant.is_error()) {
- received_channel_participant_.erase(it);
- promise.set_error(r_dialog_participant.move_as_error());
- } else {
- it->second = r_dialog_participant.move_as_ok();
- promise.set_value(Unit());
- }
- });
+ if (is_chat_full_outdated(chat_full, c, chat_id, true)) {
+ send_get_chat_full_query(chat_id, Auto(), "get_chat_participant lazy");
+ }
- td_->create_handler<GetChannelParticipantQuery>(std::move(on_result_promise))
- ->send(channel_id, user_id, std::move(input_user));
- return DialogParticipant();
+ finish_get_chat_participant(chat_id, user_id, std::move(promise));
}
-std::pair<int32, vector<DialogParticipant>> ContactsManager::get_channel_participants(
- ChannelId channel_id, const tl_object_ptr<td_api::SupergroupMembersFilter> &filter, int32 offset, int32 limit,
- int64 &random_id, bool force, Promise<Unit> &&promise) {
- if (random_id != 0) {
- // request has already been sent before
- auto it = received_channel_participants_.find(random_id);
- CHECK(it != received_channel_participants_.end());
- auto result = std::move(it->second);
- received_channel_participants_.erase(it);
- promise.set_value(Unit());
- return result;
- }
+void ContactsManager::finish_get_chat_participant(ChatId chat_id, UserId user_id,
+ Promise<DialogParticipant> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
- std::pair<int32, vector<DialogParticipant>> result;
- if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
- return result;
+ const auto *participant = get_chat_participant(chat_id, user_id);
+ if (participant == nullptr) {
+ return promise.set_value(DialogParticipant::left(DialogId(user_id)));
}
- if (offset < 0) {
- promise.set_error(Status::Error(3, "Parameter offset must be non-negative"));
- return result;
+ promise.set_value(DialogParticipant(*participant));
+}
+
+void ContactsManager::search_chat_participants(ChatId chat_id, const string &query, int32 limit,
+ DialogParticipantFilter filter, Promise<DialogParticipants> &&promise) {
+ if (limit < 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be non-negative"));
}
- auto channel_full = get_channel_full(channel_id);
- if (channel_full == nullptr || (!force && channel_full->is_expired())) {
- if (force) {
- LOG(ERROR) << "Can't find cached ChannelFull";
+ auto load_chat_full_promise = PromiseCreator::lambda([actor_id = actor_id(this), chat_id, query, limit, filter,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
} else {
- auto input_channel = get_input_channel(channel_id);
- if (input_channel == nullptr) {
- promise.set_error(Status::Error(6, "Supergroup not found"));
- } else {
- send_get_channel_full_query(channel_id, std::move(input_channel), std::move(promise));
- }
- return result;
+ send_closure(actor_id, &ContactsManager::do_search_chat_participants, chat_id, query, limit, filter,
+ std::move(promise));
}
+ });
+ load_chat_full(chat_id, false, std::move(load_chat_full_promise), "search_chat_participants");
+}
+
+void ContactsManager::do_search_chat_participants(ChatId chat_id, const string &query, int32 limit,
+ DialogParticipantFilter filter,
+ Promise<DialogParticipants> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto chat_full = get_chat_full(chat_id);
+ if (chat_full == nullptr) {
+ return promise.set_error(Status::Error(500, "Can't find basic group full info"));
}
- if (channel_full != nullptr && !channel_full->is_expired() && !channel_full->can_get_participants) {
- promise.set_error(Status::Error(3, "Supergroup members are unavailable"));
- return result;
+ vector<DialogId> dialog_ids;
+ for (const auto &participant : chat_full->participants) {
+ if (filter.is_dialog_participant_suitable(td_, participant)) {
+ dialog_ids.push_back(participant.dialog_id_);
+ }
}
- do {
- random_id = Random::secure_int64();
- } while (random_id == 0 || received_channel_participants_.find(random_id) != received_channel_participants_.end());
- received_channel_participants_[random_id]; // reserve place for result
+ int32 total_count;
+ std::tie(total_count, dialog_ids) = search_among_dialogs(dialog_ids, query, limit);
+ promise.set_value(DialogParticipants{total_count, transform(dialog_ids, [chat_full](DialogId dialog_id) {
+ return *ContactsManager::get_chat_full_participant(chat_full, dialog_id);
+ })});
+}
- LOG(DEBUG) << "Get members of the " << channel_id << " with offset = " << offset << " and limit = " << limit;
+void ContactsManager::get_channel_participant(ChannelId channel_id, DialogId participant_dialog_id,
+ Promise<DialogParticipant> &&promise) {
+ LOG(INFO) << "Trying to get " << participant_dialog_id << " as member of " << channel_id;
- td_->create_handler<GetChannelParticipantsQuery>(std::move(promise))
- ->send(channel_id, ChannelParticipantsFilter(filter), offset, limit, random_id);
- return result;
-}
+ auto input_peer = td_->messages_manager_->get_input_peer(participant_dialog_id, AccessRights::Know);
+ if (input_peer == nullptr) {
+ return promise.set_error(Status::Error(400, "Member not found"));
+ }
-vector<UserId> ContactsManager::get_dialog_administrators(DialogId dialog_id, int left_tries, Promise<Unit> &&promise) {
- auto it = dialog_administrators_.find(dialog_id);
- if (it != dialog_administrators_.end()) {
- promise.set_value(Unit());
- if (left_tries >= 2) {
- auto hash =
- get_vector_hash(transform(it->second, [](UserId user_id) { return static_cast<uint32>(user_id.get()); }));
- reload_dialog_administrators(dialog_id, hash, Auto()); // update administrators cache
+ if (have_channel_participant_cache(channel_id)) {
+ auto *participant = get_channel_participant_from_cache(channel_id, participant_dialog_id);
+ if (participant != nullptr) {
+ return promise.set_value(DialogParticipant{*participant});
}
- return it->second;
}
- if (left_tries >= 3) {
- load_dialog_administrators(dialog_id, std::move(promise));
- return {};
+ auto on_result_promise = PromiseCreator::lambda([actor_id = actor_id(this), channel_id, promise = std::move(promise)](
+ Result<DialogParticipant> r_dialog_participant) mutable {
+ TRY_RESULT_PROMISE(promise, dialog_participant, std::move(r_dialog_participant));
+ send_closure(actor_id, &ContactsManager::finish_get_channel_participant, channel_id, std::move(dialog_participant),
+ std::move(promise));
+ });
+
+ td_->create_handler<GetChannelParticipantQuery>(std::move(on_result_promise))
+ ->send(channel_id, participant_dialog_id, std::move(input_peer));
+}
+
+void ContactsManager::finish_get_channel_participant(ChannelId channel_id, DialogParticipant &&dialog_participant,
+ Promise<DialogParticipant> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ CHECK(dialog_participant.is_valid()); // checked in GetChannelParticipantQuery
+
+ LOG(INFO) << "Receive " << dialog_participant.dialog_id_ << " as a member of a channel " << channel_id;
+
+ dialog_participant.status_.update_restrictions();
+ if (have_channel_participant_cache(channel_id)) {
+ add_channel_participant_to_cache(channel_id, dialog_participant, false);
}
+ promise.set_value(std::move(dialog_participant));
+}
- if (left_tries >= 2) {
- reload_dialog_administrators(dialog_id, 0, std::move(promise));
- return {};
+void ContactsManager::get_channel_participants(ChannelId channel_id,
+ tl_object_ptr<td_api::SupergroupMembersFilter> &&filter,
+ string additional_query, int32 offset, int32 limit,
+ int32 additional_limit, Promise<DialogParticipants> &&promise) {
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ }
+ if (limit > MAX_GET_CHANNEL_PARTICIPANTS) {
+ limit = MAX_GET_CHANNEL_PARTICIPANTS;
}
- LOG(ERROR) << "Have no known administrators in " << dialog_id;
- promise.set_value(Unit());
- return {};
+ if (offset < 0) {
+ return promise.set_error(Status::Error(400, "Parameter offset must be non-negative"));
+ }
+
+ auto channel_full = get_channel_full_force(channel_id, true, "get_channel_participants");
+ if (channel_full != nullptr && !channel_full->is_expired() && !channel_full->can_get_participants) {
+ return promise.set_error(Status::Error(400, "Member list is inaccessible"));
+ }
+
+ ChannelParticipantFilter participant_filter(filter);
+ auto get_channel_participants_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), channel_id, filter = participant_filter,
+ additional_query = std::move(additional_query), offset, limit, additional_limit, promise = std::move(promise)](
+ Result<tl_object_ptr<telegram_api::channels_channelParticipants>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &ContactsManager::on_get_channel_participants, channel_id, std::move(filter), offset,
+ limit, std::move(additional_query), additional_limit, result.move_as_ok(), std::move(promise));
+ }
+ });
+ td_->create_handler<GetChannelParticipantsQuery>(std::move(get_channel_participants_promise))
+ ->send(channel_id, participant_filter, offset, limit);
}
-string ContactsManager::get_dialog_administrators_database_key(DialogId dialog_id) {
- return PSTRING() << "admin" << (-dialog_id.get());
+td_api::object_ptr<td_api::chatAdministrators> ContactsManager::get_chat_administrators_object(
+ const vector<DialogAdministrator> &dialog_administrators) {
+ auto administrator_objects = transform(dialog_administrators, [this](const DialogAdministrator &administrator) {
+ return administrator.get_chat_administrator_object(this);
+ });
+ return td_api::make_object<td_api::chatAdministrators>(std::move(administrator_objects));
}
-void ContactsManager::load_dialog_administrators(DialogId dialog_id, Promise<Unit> &&promise) {
+void ContactsManager::get_dialog_administrators(DialogId dialog_id,
+ Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_administrators")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::SecretChat:
+ return promise.set_value(td_api::make_object<td_api::chatAdministrators>());
+ case DialogType::Chat:
+ case DialogType::Channel:
+ break;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ return;
+ }
+
+ auto it = dialog_administrators_.find(dialog_id);
+ if (it != dialog_administrators_.end()) {
+ reload_dialog_administrators(dialog_id, it->second, Auto()); // update administrators cache
+ return promise.set_value(get_chat_administrators_object(it->second));
+ }
+
if (G()->parameters().use_chat_info_db) {
LOG(INFO) << "Load administrators of " << dialog_id << " from database";
- G()->td_db()->get_sqlite_pmc()->get(
- get_dialog_administrators_database_key(dialog_id),
- PromiseCreator::lambda([dialog_id, promise = std::move(promise)](string value) mutable {
- send_closure(G()->contacts_manager(), &ContactsManager::on_load_dialog_administrators_from_database,
- dialog_id, std::move(value), std::move(promise));
- }));
- } else {
- promise.set_value(Unit());
+ G()->td_db()->get_sqlite_pmc()->get(get_dialog_administrators_database_key(dialog_id),
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id,
+ promise = std::move(promise)](string value) mutable {
+ send_closure(actor_id,
+ &ContactsManager::on_load_dialog_administrators_from_database,
+ dialog_id, std::move(value), std::move(promise));
+ }));
+ return;
}
+
+ reload_dialog_administrators(dialog_id, {}, std::move(promise));
}
-void ContactsManager::on_load_dialog_administrators_from_database(DialogId dialog_id, string value,
- Promise<Unit> &&promise) {
+string ContactsManager::get_dialog_administrators_database_key(DialogId dialog_id) {
+ return PSTRING() << "adm" << (-dialog_id.get());
+}
+
+void ContactsManager::on_load_dialog_administrators_from_database(
+ DialogId dialog_id, string value, Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
if (value.empty()) {
- promise.set_value(Unit());
- return;
+ return reload_dialog_administrators(dialog_id, {}, std::move(promise));
}
- vector<UserId> user_ids;
- log_event_parse(user_ids, value).ensure();
+ vector<DialogAdministrator> administrators;
+ log_event_parse(administrators, value).ensure();
- LOG(INFO) << "Successfully loaded " << user_ids.size() << " administrators in " << dialog_id << " from database";
+ LOG(INFO) << "Successfully loaded " << administrators.size() << " administrators in " << dialog_id
+ << " from database";
- MultiPromiseActorSafe load_users_multipromise;
+ MultiPromiseActorSafe load_users_multipromise{"LoadUsersMultiPromiseActor"};
load_users_multipromise.add_promise(
- PromiseCreator::lambda([dialog_id, user_ids, promise = std::move(promise)](Result<> result) mutable {
- send_closure(G()->contacts_manager(), &ContactsManager::on_load_administrator_users_finished, dialog_id,
- std::move(user_ids), std::move(result), std::move(promise));
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, administrators,
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ send_closure(actor_id, &ContactsManager::on_load_administrator_users_finished, dialog_id,
+ std::move(administrators), std::move(result), std::move(promise));
}));
auto lock_promise = load_users_multipromise.get_promise();
- for (auto user_id : user_ids) {
- get_user(user_id, 3, load_users_multipromise.get_promise());
+ for (auto &administrator : administrators) {
+ get_user(administrator.get_user_id(), 3, load_users_multipromise.get_promise());
}
lock_promise.set_value(Unit());
}
-void ContactsManager::on_load_administrator_users_finished(DialogId dialog_id, vector<UserId> user_ids, Result<> result,
- Promise<Unit> promise) {
- if (result.is_ok()) {
- dialog_administrators_.emplace(dialog_id, std::move(user_ids));
+void ContactsManager::on_load_administrator_users_finished(
+ DialogId dialog_id, vector<DialogAdministrator> administrators, Result<> result,
+ Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (result.is_error()) {
+ return reload_dialog_administrators(dialog_id, {}, std::move(promise));
}
- promise.set_value(Unit());
+
+ auto it = dialog_administrators_.emplace(dialog_id, std::move(administrators)).first;
+ reload_dialog_administrators(dialog_id, it->second, Auto()); // update administrators cache
+ promise.set_value(get_chat_administrators_object(it->second));
}
-void ContactsManager::on_update_dialog_administrators(DialogId dialog_id, vector<UserId> administrator_user_ids,
- bool have_access) {
- LOG(INFO) << "Update administrators in " << dialog_id << " to " << format::as_array(administrator_user_ids);
+void ContactsManager::on_update_channel_administrator_count(ChannelId channel_id, int32 administrator_count) {
+ auto channel_full = get_channel_full_force(channel_id, true, "on_update_channel_administrator_count");
+ if (channel_full != nullptr && channel_full->administrator_count != administrator_count) {
+ channel_full->administrator_count = administrator_count;
+ channel_full->is_changed = true;
+
+ if (channel_full->participant_count < channel_full->administrator_count) {
+ channel_full->participant_count = channel_full->administrator_count;
+
+ auto c = get_channel(channel_id);
+ if (c != nullptr && c->participant_count != channel_full->participant_count) {
+ c->participant_count = channel_full->participant_count;
+ c->is_changed = true;
+ update_channel(c, channel_id);
+ }
+ }
+
+ update_channel_full(channel_full, channel_id, "on_update_channel_administrator_count");
+ }
+}
+
+void ContactsManager::on_update_dialog_administrators(DialogId dialog_id, vector<DialogAdministrator> &&administrators,
+ bool have_access, bool from_database) {
+ LOG(INFO) << "Update administrators in " << dialog_id << " to " << format::as_array(administrators);
if (have_access) {
- std::sort(administrator_user_ids.begin(), administrator_user_ids.end(),
- [](UserId lhs, UserId rhs) { return lhs.get() < rhs.get(); });
+ CHECK(dialog_id.is_valid());
+ std::sort(administrators.begin(), administrators.end(),
+ [](const DialogAdministrator &lhs, const DialogAdministrator &rhs) {
+ return lhs.get_user_id().get() < rhs.get_user_id().get();
+ });
auto it = dialog_administrators_.find(dialog_id);
if (it != dialog_administrators_.end()) {
- if (it->second == administrator_user_ids) {
+ if (it->second == administrators) {
return;
}
- it->second = std::move(administrator_user_ids);
+ it->second = std::move(administrators);
} else {
- it = dialog_administrators_.emplace(dialog_id, std::move(administrator_user_ids)).first;
+ it = dialog_administrators_.emplace(dialog_id, std::move(administrators)).first;
}
- if (G()->parameters().use_chat_info_db) {
+ if (G()->parameters().use_chat_info_db && !from_database) {
LOG(INFO) << "Save administrators of " << dialog_id << " to database";
G()->td_db()->get_sqlite_pmc()->set(get_dialog_administrators_database_key(dialog_id),
log_event_store(it->second).as_slice().str(), Auto());
}
} else {
+ dialog_administrators_.erase(dialog_id);
if (G()->parameters().use_chat_info_db) {
G()->td_db()->get_sqlite_pmc()->erase(get_dialog_administrators_database_key(dialog_id), Auto());
}
}
}
-void ContactsManager::reload_dialog_administrators(DialogId dialog_id, int32 hash, Promise<Unit> &&promise) {
- switch (dialog_id.get_type()) {
+void ContactsManager::reload_dialog_administrators(DialogId dialog_id,
+ const vector<DialogAdministrator> &dialog_administrators,
+ Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise) {
+ auto dialog_type = dialog_id.get_type();
+ if (dialog_type == DialogType::Chat && !get_chat_permissions(dialog_id.get_chat_id()).is_member()) {
+ return promise.set_value(td_api::make_object<td_api::chatAdministrators>());
+ }
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (promise) {
+ if (result.is_ok()) {
+ send_closure(actor_id, &ContactsManager::on_reload_dialog_administrators, dialog_id, std::move(promise));
+ } else {
+ promise.set_error(result.move_as_error());
+ }
+ }
+ });
+ switch (dialog_type) {
case DialogType::Chat:
- get_chat_full(dialog_id.get_chat_id(), std::move(promise));
+ load_chat_full(dialog_id.get_chat_id(), false, std::move(query_promise), "reload_dialog_administrators");
break;
- case DialogType::Channel:
- td_->create_handler<GetChannelAdministratorsQuery>(std::move(promise))->send(dialog_id.get_channel_id(), hash);
+ case DialogType::Channel: {
+ auto hash = get_vector_hash(transform(dialog_administrators, [](const DialogAdministrator &administrator) {
+ return static_cast<uint64>(administrator.get_user_id().get());
+ }));
+ td_->create_handler<GetChannelAdministratorsQuery>(std::move(query_promise))
+ ->send(dialog_id.get_channel_id(), hash);
break;
+ }
default:
UNREACHABLE();
}
}
-void ContactsManager::on_chat_update(telegram_api::chatEmpty &chat) {
+void ContactsManager::on_reload_dialog_administrators(
+ DialogId dialog_id, Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto it = dialog_administrators_.find(dialog_id);
+ if (it != dialog_administrators_.end()) {
+ return promise.set_value(get_chat_administrators_object(it->second));
+ }
+
+ LOG(ERROR) << "Failed to load administrators in " << dialog_id;
+ promise.set_error(Status::Error(500, "Failed to find chat administrators"));
+}
+
+void ContactsManager::on_chat_update(telegram_api::chatEmpty &chat, const char *source) {
ChatId chat_id(chat.id_);
if (!chat_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << chat_id;
+ LOG(ERROR) << "Receive invalid " << chat_id << " from " << source;
return;
}
if (!have_chat(chat_id)) {
- LOG(ERROR) << "Have no information about " << chat_id << " but received chatEmpty";
+ LOG(ERROR) << "Have no information about " << chat_id << " but received chatEmpty from " << source;
}
}
-void ContactsManager::on_chat_update(telegram_api::chat &chat) {
- auto debug_str = oneline(to_string(chat));
+void ContactsManager::on_chat_update(telegram_api::chat &chat, const char *source) {
+ auto debug_str = PSTRING() << " from " << source << " in " << oneline(to_string(chat));
ChatId chat_id(chat.id_);
if (!chat_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << chat_id << " in " << debug_str;
+ LOG(ERROR) << "Receive invalid " << chat_id << debug_str;
return;
}
- bool has_left = 0 != (chat.flags_ & CHAT_FLAG_USER_HAS_LEFT);
- bool was_kicked = 0 != (chat.flags_ & CHAT_FLAG_USER_WAS_KICKED);
- if (was_kicked) {
- LOG_IF(ERROR, has_left) << "Kicked and left in " << debug_str; // only one of the flags can be set
- has_left = true;
- }
-
- bool is_creator = 0 != (chat.flags_ & CHAT_FLAG_USER_IS_CREATOR);
- bool is_administrator = 0 != (chat.flags_ & CHAT_FLAG_IS_ADMINISTRATOR);
- bool everyone_is_administrator = 0 == (chat.flags_ & CHAT_FLAG_ADMINISTRATORS_ENABLED);
+ DialogParticipantStatus status = [&] {
+ bool is_creator = 0 != (chat.flags_ & CHAT_FLAG_USER_IS_CREATOR);
+ bool has_left = 0 != (chat.flags_ & CHAT_FLAG_USER_HAS_LEFT);
+ if (is_creator) {
+ return DialogParticipantStatus::Creator(!has_left, false, string());
+ } else if (chat.admin_rights_ != nullptr) {
+ return DialogParticipantStatus(false, std::move(chat.admin_rights_), string(), ChannelType::Unknown);
+ } else if (has_left) {
+ return DialogParticipantStatus::Left();
+ } else {
+ return DialogParticipantStatus::Member();
+ }
+ }();
bool is_active = 0 == (chat.flags_ & CHAT_FLAG_IS_DEACTIVATED);
@@ -8987,18 +16853,18 @@ void ContactsManager::on_chat_update(telegram_api::chat &chat) {
if (chat.flags_ & CHAT_FLAG_WAS_MIGRATED) {
switch (chat.migrated_to_->get_id()) {
case telegram_api::inputChannelEmpty::ID: {
- LOG(ERROR) << "Receive empty upgraded to supergroup for " << chat_id << " in " << debug_str;
+ LOG(ERROR) << "Receive empty upgraded to supergroup for " << chat_id << debug_str;
break;
}
case telegram_api::inputChannel::ID: {
auto input_channel = move_tl_object_as<telegram_api::inputChannel>(chat.migrated_to_);
migrated_to_channel_id = ChannelId(input_channel->channel_id_);
- if (!have_channel(migrated_to_channel_id)) {
+ if (!have_channel_force(migrated_to_channel_id)) {
if (!migrated_to_channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << migrated_to_channel_id << " in " << debug_str;
+ LOG(ERROR) << "Receive invalid " << migrated_to_channel_id << debug_str;
} else {
// temporarily create the channel
- Channel *c = add_channel(migrated_to_channel_id);
+ Channel *c = add_channel(migrated_to_channel_id, "on_chat_update");
c->access_hash = input_channel->access_hash_;
c->title = chat.title_;
c->status = DialogParticipantStatus::Left();
@@ -9018,67 +16884,86 @@ void ContactsManager::on_chat_update(telegram_api::chat &chat) {
}
}
- Chat *c = add_chat(chat_id);
+ Chat *c = get_chat_force(chat_id); // to load versions
+ if (c == nullptr) {
+ c = add_chat(chat_id);
+ }
on_update_chat_title(c, chat_id, std::move(chat.title_));
- if (!has_left) {
+ if (!status.is_left()) {
on_update_chat_participant_count(c, chat_id, chat.participants_count_, chat.version_, debug_str);
+ } else {
+ chat.photo_ = nullptr;
}
if (c->date != chat.date_) {
- LOG_IF(ERROR, c->date != 0) << "Chat creation date has changed from " << c->date << " to " << chat.date_ << " in "
+ LOG_IF(ERROR, c->date != 0) << "Chat creation date has changed from " << c->date << " to " << chat.date_
<< debug_str;
c->date = chat.date_;
- c->is_changed = true;
+ c->need_save_to_database = true;
}
- on_update_chat_left(c, chat_id, has_left, was_kicked);
- on_update_chat_rights(c, chat_id, is_creator, is_administrator, everyone_is_administrator);
+ on_update_chat_status(c, chat_id, std::move(status));
+ on_update_chat_default_permissions(c, chat_id, RestrictedRights(chat.default_banned_rights_), chat.version_);
on_update_chat_photo(c, chat_id, std::move(chat.photo_));
on_update_chat_active(c, chat_id, is_active);
+ on_update_chat_noforwards(c, chat_id, chat.noforwards_);
on_update_chat_migrated_to_channel_id(c, chat_id, migrated_to_channel_id);
- LOG_IF(ERROR, !is_active && !migrated_to_channel_id.is_valid()) << chat_id << " is deactivated in " << debug_str;
+ LOG_IF(INFO, !is_active && !migrated_to_channel_id.is_valid()) << chat_id << " is deactivated" << debug_str;
+ if (c->cache_version != Chat::CACHE_VERSION) {
+ c->cache_version = Chat::CACHE_VERSION;
+ c->need_save_to_database = true;
+ }
+ c->is_received_from_server = true;
update_chat(c, chat_id);
+
+ bool has_active_group_call = (chat.flags_ & CHAT_FLAG_HAS_ACTIVE_GROUP_CALL) != 0;
+ bool is_group_call_empty = (chat.flags_ & CHAT_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0;
+ td_->messages_manager_->on_update_dialog_group_call(DialogId(chat_id), has_active_group_call, is_group_call_empty,
+ "receive chat");
}
-void ContactsManager::on_chat_update(telegram_api::chatForbidden &chat) {
+void ContactsManager::on_chat_update(telegram_api::chatForbidden &chat, const char *source) {
ChatId chat_id(chat.id_);
if (!chat_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << chat_id;
+ LOG(ERROR) << "Receive invalid " << chat_id << " from " << source;
return;
}
+ bool is_uninited = get_chat_force(chat_id) == nullptr;
Chat *c = add_chat(chat_id);
- bool is_uninited = c->left == false && c->kicked == true;
-
on_update_chat_title(c, chat_id, std::move(chat.title_));
- // chat participant count will be updated in on_update_chat_left
- // leave rights as is
+ // chat participant count will be updated in on_update_chat_status
on_update_chat_photo(c, chat_id, nullptr);
if (c->date != 0) {
c->date = 0; // removed in 38-th layer
- c->is_changed = true;
+ c->need_save_to_database = true;
}
- on_update_chat_left(c, chat_id, true, true);
+ on_update_chat_status(c, chat_id, DialogParticipantStatus::Banned(0));
if (is_uninited) {
on_update_chat_active(c, chat_id, true);
on_update_chat_migrated_to_channel_id(c, chat_id, ChannelId());
} else {
// leave active and migrated to as is
}
+ if (c->cache_version != Chat::CACHE_VERSION) {
+ c->cache_version = Chat::CACHE_VERSION;
+ c->need_save_to_database = true;
+ }
+ c->is_received_from_server = true;
update_chat(c, chat_id);
}
-void ContactsManager::on_chat_update(telegram_api::channel &channel) {
+void ContactsManager::on_chat_update(telegram_api::channel &channel, const char *source) {
ChannelId channel_id(channel.id_);
if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id << ": " << to_string(channel);
+ LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << ": " << to_string(channel);
return;
}
if (channel.flags_ == 0 && channel.access_hash_ == 0 && channel.title_.empty()) {
Channel *c = get_channel_force(channel_id);
- LOG(ERROR) << "Receive empty " << to_string(channel) << ", have "
+ LOG(ERROR) << "Receive empty " << to_string(channel) << " from " << source << ", have "
<< to_string(get_supergroup_object(channel_id, c));
- if (c == nullptr) {
- min_channels_.insert(channel_id);
+ if (c == nullptr && !have_min_channel(channel_id)) {
+ min_channels_.set(channel_id, td::make_unique<MinChannel>());
}
return;
}
@@ -9087,41 +16972,63 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel) {
bool has_access_hash = (channel.flags_ & CHANNEL_FLAG_HAS_ACCESS_HASH) != 0;
auto access_hash = has_access_hash ? channel.access_hash_ : 0;
- bool anyone_can_invite = (channel.flags_ & CHANNEL_FLAG_ANYONE_CAN_INVITE) != 0;
+ bool has_linked_channel = (channel.flags_ & CHANNEL_FLAG_HAS_LINKED_CHAT) != 0;
bool sign_messages = (channel.flags_ & CHANNEL_FLAG_SIGN_MESSAGES) != 0;
+ bool join_to_send = (channel.flags_ & CHANNEL_FLAG_JOIN_TO_SEND) != 0;
+ bool join_request = (channel.flags_ & CHANNEL_FLAG_JOIN_REQUEST) != 0;
+ bool is_slow_mode_enabled = (channel.flags_ & CHANNEL_FLAG_IS_SLOW_MODE_ENABLED) != 0;
bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0;
bool is_verified = (channel.flags_ & CHANNEL_FLAG_IS_VERIFIED) != 0;
- string restriction_reason = std::move(channel.restriction_reason_);
- int32 participant_count =
- (channel.flags_ & CHANNEL_FLAG_HAS_PARTICIPANT_COUNT) != 0 ? channel.participants_count_ : 0;
+ auto restriction_reasons = get_restriction_reasons(std::move(channel.restriction_reason_));
+ bool is_scam = (channel.flags_ & CHANNEL_FLAG_IS_SCAM) != 0;
+ bool is_fake = (channel.flags_ & CHANNEL_FLAG_IS_FAKE) != 0;
+ bool is_gigagroup = (channel.flags_ & CHANNEL_FLAG_IS_GIGAGROUP) != 0;
+ bool is_forum = (channel.flags_ & CHANNEL_FLAG_IS_FORUM) != 0;
+ bool have_participant_count = (channel.flags_ & CHANNEL_FLAG_HAS_PARTICIPANT_COUNT) != 0;
+ int32 participant_count = have_participant_count ? channel.participants_count_ : 0;
+
+ if (have_participant_count) {
+ auto channel_full = get_channel_full_const(channel_id);
+ if (channel_full != nullptr && channel_full->administrator_count > participant_count) {
+ participant_count = channel_full->administrator_count;
+ }
+ }
{
bool is_broadcast = (channel.flags_ & CHANNEL_FLAG_IS_BROADCAST) != 0;
LOG_IF(ERROR, is_broadcast == is_megagroup)
- << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << ": "
+ << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << " from " << source << ": "
<< oneline(to_string(channel));
}
- if (!is_megagroup && anyone_can_invite) {
- LOG(ERROR) << "Anyone can invite new members to the " << channel_id;
- anyone_can_invite = false;
- }
-
if (is_megagroup) {
- LOG_IF(ERROR, sign_messages) << "Need to sign messages in the supergroup " << channel_id;
+ LOG_IF(ERROR, sign_messages) << "Need to sign messages in the supergroup " << channel_id << " from " << source;
sign_messages = true;
+ } else {
+ LOG_IF(ERROR, is_slow_mode_enabled) << "Slow mode enabled in the " << channel_id << " from " << source;
+ LOG_IF(ERROR, is_gigagroup) << "Receive broadcast group as " << channel_id << " from " << source;
+ LOG_IF(ERROR, is_forum) << "Receive broadcast forum as " << channel_id << " from " << source;
+ is_slow_mode_enabled = false;
+ is_gigagroup = false;
+ is_forum = false;
+ }
+ if (is_gigagroup) {
+ remove_dialog_suggested_action(SuggestedAction{SuggestedAction::Type::ConvertToGigagroup, DialogId(channel_id)});
}
- DialogParticipantStatus status = [&]() {
+ DialogParticipantStatus status = [&] {
bool has_left = (channel.flags_ & CHANNEL_FLAG_USER_HAS_LEFT) != 0;
bool is_creator = (channel.flags_ & CHANNEL_FLAG_USER_IS_CREATOR) != 0;
if (is_creator) {
- return DialogParticipantStatus::Creator(!has_left);
+ bool is_anonymous = channel.admin_rights_ != nullptr &&
+ (channel.admin_rights_->flags_ & telegram_api::chatAdminRights::ANONYMOUS_MASK) != 0;
+ return DialogParticipantStatus::Creator(!has_left, is_anonymous, string());
} else if (channel.admin_rights_ != nullptr) {
- return get_dialog_participant_status(false, std::move(channel.admin_rights_));
+ return DialogParticipantStatus(false, std::move(channel.admin_rights_), string(),
+ is_megagroup ? ChannelType::Megagroup : ChannelType::Broadcast);
} else if (channel.banned_rights_ != nullptr) {
- return get_dialog_participant_status(!has_left, std::move(channel.banned_rights_));
+ return DialogParticipantStatus(!has_left, std::move(channel.banned_rights_));
} else if (has_left) {
return DialogParticipantStatus::Left();
} else {
@@ -9130,117 +17037,216 @@ void ContactsManager::on_chat_update(telegram_api::channel &channel) {
}();
if (is_min) {
- // TODO there can be better support for min channels
Channel *c = get_channel_force(channel_id);
if (c != nullptr) {
LOG(DEBUG) << "Receive known min " << channel_id;
+
+ auto old_join_to_send = get_channel_join_to_send(c);
+ auto old_join_request = get_channel_join_request(c);
on_update_channel_title(c, channel_id, std::move(channel.title_));
- on_update_channel_username(c, channel_id, std::move(channel.username_));
+ on_update_channel_usernames(c, channel_id,
+ Usernames(std::move(channel.username_), std::move(channel.usernames_)));
on_update_channel_photo(c, channel_id, std::move(channel.photo_));
-
- if (c->anyone_can_invite != anyone_can_invite || c->is_megagroup != is_megagroup ||
- c->is_verified != is_verified) {
- c->anyone_can_invite = anyone_can_invite;
+ on_update_channel_default_permissions(c, channel_id, RestrictedRights(channel.default_banned_rights_));
+ on_update_channel_has_location(c, channel_id, channel.has_geo_);
+ on_update_channel_noforwards(c, channel_id, channel.noforwards_);
+
+ if (c->has_linked_channel != has_linked_channel || c->is_slow_mode_enabled != is_slow_mode_enabled ||
+ c->is_megagroup != is_megagroup || c->restriction_reasons != restriction_reasons || c->is_scam != is_scam ||
+ c->is_fake != is_fake || c->is_gigagroup != is_gigagroup || c->is_forum != is_forum) {
+ c->has_linked_channel = has_linked_channel;
+ c->is_slow_mode_enabled = is_slow_mode_enabled;
c->is_megagroup = is_megagroup;
+ c->restriction_reasons = std::move(restriction_reasons);
+ c->is_scam = is_scam;
+ c->is_fake = is_fake;
+ c->is_gigagroup = is_gigagroup;
+ c->is_forum = is_forum;
+
+ c->is_changed = true;
+ invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_min_channel");
+ }
+ if (c->join_to_send != join_to_send || c->join_request != join_request) {
+ c->join_to_send = join_to_send;
+ c->join_request = join_request;
+
+ c->need_save_to_database = true;
+ }
+ // sign_messages isn't known for min-channels
+ if (c->is_verified != is_verified) {
c->is_verified = is_verified;
- c->need_send_update = true;
- invalidate_channel_full(channel_id);
+ c->is_changed = true;
+ }
+ if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) {
+ c->is_changed = true;
}
update_channel(c, channel_id);
} else {
- min_channels_.insert(channel_id);
+ auto min_channel = td::make_unique<MinChannel>();
+ min_channel->photo_ =
+ get_dialog_photo(td_->file_manager_.get(), DialogId(channel_id), access_hash, std::move(channel.photo_));
+ if (td_->auth_manager_->is_bot()) {
+ min_channel->photo_.minithumbnail.clear();
+ }
+ min_channel->title_ = std::move(channel.title_);
+ min_channel->is_megagroup_ = is_megagroup;
+
+ min_channels_.set(channel_id, std::move(min_channel));
}
return;
}
if (!has_access_hash) {
- LOG(ERROR) << "Receive non-min " << channel_id << " without access_hash";
+ LOG(ERROR) << "Receive non-min " << channel_id << " without access_hash from " << source;
return;
}
- Channel *c = add_channel(channel_id);
- if (c->status.is_banned()) { // possibly uninited channel
- min_channels_.erase(channel_id);
+ if (status.is_creator()) {
+ // to correctly calculate is_ownership_transferred in on_update_channel_status
+ get_channel_force(channel_id);
}
+
+ Channel *c = add_channel(channel_id, "on_channel");
+ auto old_join_to_send = get_channel_join_to_send(c);
+ auto old_join_request = get_channel_join_request(c);
if (c->access_hash != access_hash) {
c->access_hash = access_hash;
- c->is_changed = true;
+ c->need_save_to_database = true;
}
on_update_channel_title(c, channel_id, std::move(channel.title_));
if (c->date != channel.date_) {
c->date = channel.date_;
- c->need_send_update = true;
+ c->is_changed = true;
}
on_update_channel_photo(c, channel_id, std::move(channel.photo_));
on_update_channel_status(c, channel_id, std::move(status));
- on_update_channel_username(c, channel_id, std::move(channel.username_)); // uses status, must be called after
-
- if (participant_count != 0 && participant_count != c->participant_count) {
+ on_update_channel_usernames(
+ c, channel_id,
+ Usernames(std::move(channel.username_),
+ std::move(channel.usernames_))); // uses status, must be called after on_update_channel_status
+ on_update_channel_default_permissions(c, channel_id, RestrictedRights(channel.default_banned_rights_));
+ on_update_channel_has_location(c, channel_id, channel.has_geo_);
+ on_update_channel_noforwards(c, channel_id, channel.noforwards_);
+
+ bool need_update_participant_count = have_participant_count && participant_count != c->participant_count;
+ if (need_update_participant_count) {
c->participant_count = participant_count;
- c->need_send_update = true;
+ c->is_changed = true;
}
- if (c->anyone_can_invite != anyone_can_invite || c->sign_messages != sign_messages ||
- c->is_megagroup != is_megagroup || c->is_verified != is_verified || c->restriction_reason != restriction_reason) {
- c->anyone_can_invite = anyone_can_invite;
- c->sign_messages = sign_messages;
+ bool need_invalidate_channel_full = false;
+ if (c->has_linked_channel != has_linked_channel || c->is_slow_mode_enabled != is_slow_mode_enabled ||
+ c->is_megagroup != is_megagroup || c->restriction_reasons != restriction_reasons || c->is_scam != is_scam ||
+ c->is_fake != is_fake || c->is_gigagroup != is_gigagroup || c->is_forum != is_forum) {
+ c->has_linked_channel = has_linked_channel;
+ c->is_slow_mode_enabled = is_slow_mode_enabled;
c->is_megagroup = is_megagroup;
+ c->restriction_reasons = std::move(restriction_reasons);
+ c->is_scam = is_scam;
+ c->is_fake = is_fake;
+ c->is_gigagroup = is_gigagroup;
+ c->is_forum = is_forum;
+ c->join_to_send = join_to_send;
+ c->join_request = join_request;
+
+ c->is_changed = true;
+ need_invalidate_channel_full = true;
+ }
+ if (c->join_to_send != join_to_send || c->join_request != join_request) {
+ c->join_to_send = join_to_send;
+ c->join_request = join_request;
+
+ c->need_save_to_database = true;
+ }
+ if (c->is_verified != is_verified || c->sign_messages != sign_messages) {
c->is_verified = is_verified;
- c->restriction_reason = std::move(restriction_reason);
+ c->sign_messages = sign_messages;
- c->need_send_update = true;
- invalidate_channel_full(channel_id);
+ c->is_changed = true;
+ }
+ if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) {
+ c->is_changed = true;
}
+ if (c->cache_version != Channel::CACHE_VERSION) {
+ c->cache_version = Channel::CACHE_VERSION;
+ c->need_save_to_database = true;
+ }
+ c->is_received_from_server = true;
update_channel(c, channel_id);
+
+ if (need_update_participant_count) {
+ auto channel_full = get_channel_full(channel_id, true, "on_chat_update");
+ if (channel_full != nullptr && channel_full->participant_count != participant_count) {
+ channel_full->participant_count = participant_count;
+ channel_full->is_changed = true;
+ update_channel_full(channel_full, channel_id, "on_chat_update");
+ }
+ }
+
+ if (need_invalidate_channel_full) {
+ invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_chat_update");
+ }
+
+ bool has_active_group_call = (channel.flags_ & CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL) != 0;
+ bool is_group_call_empty = (channel.flags_ & CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY) == 0;
+ td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), has_active_group_call, is_group_call_empty,
+ "receive channel");
}
-void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel) {
+void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel, const char *source) {
ChannelId channel_id(channel.id_);
if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id << ": " << to_string(channel);
+ LOG(ERROR) << "Receive invalid " << channel_id << " from " << source << ": " << to_string(channel);
return;
}
if (channel.flags_ == 0 && channel.access_hash_ == 0 && channel.title_.empty()) {
Channel *c = get_channel_force(channel_id);
- LOG(ERROR) << "Receive empty " << to_string(channel) << ", have "
+ LOG(ERROR) << "Receive empty " << to_string(channel) << " from " << source << ", have "
<< to_string(get_supergroup_object(channel_id, c));
- if (c == nullptr) {
- min_channels_.insert(channel_id);
+ if (c == nullptr && !have_min_channel(channel_id)) {
+ min_channels_.set(channel_id, td::make_unique<MinChannel>());
}
return;
}
- Channel *c = add_channel(channel_id);
- if (c->status.is_banned()) { // possibly uninited channel
- min_channels_.erase(channel_id);
- }
+ Channel *c = add_channel(channel_id, "on_channel_forbidden");
+ auto old_join_to_send = get_channel_join_to_send(c);
+ auto old_join_request = get_channel_join_request(c);
if (c->access_hash != channel.access_hash_) {
c->access_hash = channel.access_hash_;
- c->is_changed = true;
+ c->need_save_to_database = true;
}
on_update_channel_title(c, channel_id, std::move(channel.title_));
on_update_channel_photo(c, channel_id, nullptr);
if (c->date != 0) {
c->date = 0;
- c->need_send_update = true;
+ c->is_changed = true;
}
int32 unban_date = (channel.flags_ & CHANNEL_FLAG_HAS_UNBAN_DATE) != 0 ? channel.until_date_ : 0;
on_update_channel_status(c, channel_id, DialogParticipantStatus::Banned(unban_date));
- on_update_channel_username(c, channel_id, ""); // don't know if channel username is empty, but update it anyway
+ // on_update_channel_usernames(c, channel_id, Usernames()); // don't know if channel usernames are empty, so don't update it
+ tl_object_ptr<telegram_api::chatBannedRights> banned_rights; // == nullptr
+ on_update_channel_default_permissions(c, channel_id, RestrictedRights(banned_rights));
+ // on_update_channel_has_location(c, channel_id, false);
+ on_update_channel_noforwards(c, channel_id, false);
+ td_->messages_manager_->on_update_dialog_group_call(DialogId(channel_id), false, false, "receive channelForbidden");
- bool anyone_can_invite = false;
bool sign_messages = false;
+ bool join_to_send = false;
+ bool join_request = false;
+ bool is_slow_mode_enabled = false;
bool is_megagroup = (channel.flags_ & CHANNEL_FLAG_IS_MEGAGROUP) != 0;
bool is_verified = false;
- string restriction_reason;
+ bool is_scam = false;
+ bool is_fake = false;
{
bool is_broadcast = (channel.flags_ & CHANNEL_FLAG_IS_BROADCAST) != 0;
LOG_IF(ERROR, is_broadcast == is_megagroup)
- << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << ": "
+ << "Receive wrong channel flag is_broadcast == is_megagroup == " << is_megagroup << " from " << source << ": "
<< oneline(to_string(channel));
}
@@ -9248,51 +17254,106 @@ void ContactsManager::on_chat_update(telegram_api::channelForbidden &channel) {
sign_messages = true;
}
- if (c->participant_count != 0) {
- c->participant_count = 0;
- c->need_send_update = true;
+ bool need_invalidate_channel_full = false;
+ if (c->is_slow_mode_enabled != is_slow_mode_enabled || c->is_megagroup != is_megagroup ||
+ !c->restriction_reasons.empty() || c->is_scam != is_scam || c->is_fake != is_fake ||
+ c->join_to_send != join_to_send || c->join_request != join_request) {
+ // c->has_linked_channel = has_linked_channel;
+ c->is_slow_mode_enabled = is_slow_mode_enabled;
+ c->is_megagroup = is_megagroup;
+ c->restriction_reasons.clear();
+ c->is_scam = is_scam;
+ c->is_fake = is_fake;
+ c->join_to_send = join_to_send;
+ c->join_request = join_request;
+
+ c->is_changed = true;
+ need_invalidate_channel_full = true;
}
+ if (c->join_to_send != join_to_send || c->join_request != join_request) {
+ c->join_to_send = join_to_send;
+ c->join_request = join_request;
- if (c->anyone_can_invite != anyone_can_invite || c->sign_messages != sign_messages ||
- c->is_megagroup != is_megagroup || c->is_verified != is_verified || c->restriction_reason != restriction_reason) {
- c->anyone_can_invite = anyone_can_invite;
+ c->need_save_to_database = true;
+ }
+ if (c->sign_messages != sign_messages || c->is_verified != is_verified) {
c->sign_messages = sign_messages;
- c->is_megagroup = is_megagroup;
c->is_verified = is_verified;
- c->restriction_reason = std::move(restriction_reason);
- c->need_send_update = true;
- invalidate_channel_full(channel_id);
+ c->is_changed = true;
+ }
+ if (old_join_to_send != get_channel_join_to_send(c) || old_join_request != get_channel_join_request(c)) {
+ c->is_changed = true;
+ }
+
+ bool need_drop_participant_count = c->participant_count != 0;
+ if (need_drop_participant_count) {
+ c->participant_count = 0;
+ c->is_changed = true;
}
+ if (c->cache_version != Channel::CACHE_VERSION) {
+ c->cache_version = Channel::CACHE_VERSION;
+ c->need_save_to_database = true;
+ }
+ c->is_received_from_server = true;
update_channel(c, channel_id);
+
+ if (need_drop_participant_count) {
+ auto channel_full = get_channel_full(channel_id, true, "on_channel_forbidden");
+ if (channel_full != nullptr && channel_full->participant_count != 0) {
+ channel_full->participant_count = 0;
+ channel_full->administrator_count = 0;
+ channel_full->is_changed = true;
+ update_channel_full(channel_full, channel_id, "on_channel_forbidden 2");
+ }
+ }
+ if (need_invalidate_channel_full) {
+ invalidate_channel_full(channel_id, !c->is_slow_mode_enabled, "on_channel_forbidden 3");
+ }
}
void ContactsManager::on_upload_profile_photo(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) {
- LOG(INFO) << "File " << file_id << " has been uploaded";
-
auto it = uploaded_profile_photos_.find(file_id);
CHECK(it != uploaded_profile_photos_.end());
- auto promise = std::move(it->second);
+ double main_frame_timestamp = it->second.main_frame_timestamp;
+ bool is_animation = it->second.is_animation;
+ int32 reupload_count = it->second.reupload_count;
+ auto promise = std::move(it->second.promise);
uploaded_profile_photos_.erase(it);
+ LOG(INFO) << "Uploaded " << (is_animation ? "animated" : "static") << " profile photo " << file_id
+ << " with reupload_count = " << reupload_count;
FileView file_view = td_->file_manager_->get_file_view(file_id);
- if (file_view.has_remote_location()) {
- if (file_view.remote_location().is_web()) {
- // TODO reupload
- promise.set_error(Status::Error(400, "Can't use web photo as profile photo"));
- return;
+ if (file_view.has_remote_location() && input_file == nullptr) {
+ if (file_view.main_remote_location().is_web()) {
+ return promise.set_error(Status::Error(400, "Can't use web photo as profile photo"));
+ }
+ if (reupload_count == 3) { // upload, ForceReupload repair file reference, reupload
+ return promise.set_error(Status::Error(400, "Failed to reupload the file"));
}
- td_->create_handler<UpdateProfilePhotoQuery>(std::move(promise))
- ->send(file_view.remote_location().as_input_photo());
+ // delete file reference and forcely reupload the file
+ if (is_animation) {
+ CHECK(file_view.get_type() == FileType::Animation);
+ LOG_CHECK(file_view.main_remote_location().is_common()) << file_view.main_remote_location();
+ } else {
+ CHECK(file_view.get_type() == FileType::Photo);
+ LOG_CHECK(file_view.main_remote_location().is_photo()) << file_view.main_remote_location();
+ }
+ auto file_reference =
+ is_animation ? FileManager::extract_file_reference(file_view.main_remote_location().as_input_document())
+ : FileManager::extract_file_reference(file_view.main_remote_location().as_input_photo());
+ td_->file_manager_->delete_file_reference(file_id, file_reference);
+ upload_profile_photo(file_id, is_animation, main_frame_timestamp, std::move(promise), reupload_count + 1, {-1});
return;
}
CHECK(input_file != nullptr);
- td_->create_handler<UploadProfilePhotoQuery>(std::move(promise))->send(file_id, std::move(input_file));
+ td_->create_handler<UploadProfilePhotoQuery>(std::move(promise))
+ ->send(file_id, std::move(input_file), is_animation, main_frame_timestamp);
}
void ContactsManager::on_upload_profile_photo_error(FileId file_id, Status status) {
@@ -9302,22 +17363,19 @@ void ContactsManager::on_upload_profile_photo_error(FileId file_id, Status statu
auto it = uploaded_profile_photos_.find(file_id);
CHECK(it != uploaded_profile_photos_.end());
- auto promise = std::move(it->second);
+ auto promise = std::move(it->second.promise);
uploaded_profile_photos_.erase(it);
promise.set_error(std::move(status)); // TODO check that status has valid error code
}
-tl_object_ptr<td_api::UserStatus> ContactsManager::get_user_status_object(UserId user_id, const User *u) const {
+td_api::object_ptr<td_api::UserStatus> ContactsManager::get_user_status_object(UserId user_id, const User *u) const {
if (u->is_bot) {
return make_tl_object<td_api::userStatusOnline>(std::numeric_limits<int32>::max());
}
- int32 was_online = u->was_online;
- if (user_id == get_my_id("get_user_status_object") && my_was_online_local_ != 0) {
- was_online = my_was_online_local_;
- }
+ int32 was_online = get_user_was_online(u, user_id);
switch (was_online) {
case -3:
return make_tl_object<td_api::userStatusLastMonth>();
@@ -9338,16 +17396,17 @@ tl_object_ptr<td_api::UserStatus> ContactsManager::get_user_status_object(UserId
}
}
-int32 ContactsManager::get_user_id_object(UserId user_id, const char *source) const {
+td_api::object_ptr<td_api::updateUser> ContactsManager::get_update_unknown_user_object(UserId user_id) {
+ return td_api::make_object<td_api::updateUser>(td_api::make_object<td_api::user>(
+ user_id.get(), "", "", nullptr, "", td_api::make_object<td_api::userStatusEmpty>(), nullptr, nullptr, false,
+ false, false, false, false, "", false, false, false, td_api::make_object<td_api::userTypeUnknown>(), "", false));
+}
+
+int64 ContactsManager::get_user_id_object(UserId user_id, const char *source) const {
if (user_id.is_valid() && get_user(user_id) == nullptr && unknown_users_.count(user_id) == 0) {
- LOG(ERROR) << "Have no info about " << user_id << " from " << source;
+ LOG(ERROR) << "Have no information about " << user_id << " from " << source;
unknown_users_.insert(user_id);
- send_closure(G()->td(), &Td::send_update,
- td_api::make_object<td_api::updateUser>(td_api::make_object<td_api::user>(
- user_id.get(), "", "", "", "", td_api::make_object<td_api::userStatusEmpty>(),
- get_profile_photo_object(td_->file_manager_.get(), nullptr),
- get_link_state_object(LinkState::Unknown), get_link_state_object(LinkState::Unknown), false, "",
- false, td_api::make_object<td_api::userTypeUnknown>(), "")));
+ send_closure(G()->td(), &Td::send_update, get_update_unknown_user_object(user_id));
}
return user_id.get();
}
@@ -9365,20 +17424,23 @@ tl_object_ptr<td_api::user> ContactsManager::get_user_object(UserId user_id, con
type = make_tl_object<td_api::userTypeDeleted>();
} else if (u->is_bot) {
type = make_tl_object<td_api::userTypeBot>(u->can_join_groups, u->can_read_all_group_messages, u->is_inline_bot,
- u->inline_query_placeholder, u->need_location_bot);
+ u->inline_query_placeholder, u->need_location_bot,
+ u->can_be_added_to_attach_menu);
} else {
type = make_tl_object<td_api::userTypeRegular>();
}
+ auto emoji_status = u->last_sent_emoji_status.is_valid() ? u->emoji_status.get_emoji_status_object() : nullptr;
return make_tl_object<td_api::user>(
- user_id.get(), u->first_name, u->last_name, u->username, u->phone_number, get_user_status_object(user_id, u),
- get_profile_photo_object(td_->file_manager_.get(), &u->photo), get_link_state_object(u->outbound),
- get_link_state_object(u->inbound), u->is_verified, u->restriction_reason, u->is_received, std::move(type),
- u->language_code);
+ user_id.get(), u->first_name, u->last_name, u->usernames.get_usernames_object(), u->phone_number,
+ get_user_status_object(user_id, u), get_profile_photo_object(td_->file_manager_.get(), u->photo),
+ std::move(emoji_status), u->is_contact, u->is_mutual_contact, u->is_verified, u->is_premium, u->is_support,
+ get_restriction_reason_description(u->restriction_reasons), u->is_scam, u->is_fake, u->is_received,
+ std::move(type), u->language_code, u->attach_menu_enabled);
}
-vector<int32> ContactsManager::get_user_ids_object(const vector<UserId> &user_ids) const {
- return transform(user_ids, [this](UserId user_id) { return get_user_id_object(user_id, "get_user_ids_object"); });
+vector<int64> ContactsManager::get_user_ids_object(const vector<UserId> &user_ids, const char *source) const {
+ return transform(user_ids, [this, source](UserId user_id) { return get_user_id_object(user_id, source); });
}
tl_object_ptr<td_api::users> ContactsManager::get_users_object(int32 total_count,
@@ -9386,7 +17448,7 @@ tl_object_ptr<td_api::users> ContactsManager::get_users_object(int32 total_count
if (total_count == -1) {
total_count = narrow_cast<int32>(user_ids.size());
}
- return td_api::make_object<td_api::users>(total_count, get_user_ids_object(user_ids));
+ return td_api::make_object<td_api::users>(total_count, get_user_ids_object(user_ids, "get_users_object"));
}
tl_object_ptr<td_api::userFullInfo> ContactsManager::get_user_full_info_object(UserId user_id) const {
@@ -9396,21 +17458,63 @@ tl_object_ptr<td_api::userFullInfo> ContactsManager::get_user_full_info_object(U
tl_object_ptr<td_api::userFullInfo> ContactsManager::get_user_full_info_object(UserId user_id,
const UserFull *user_full) const {
CHECK(user_full != nullptr);
+ td_api::object_ptr<td_api::botInfo> bot_info;
bool is_bot = is_user_bot(user_id);
- return make_tl_object<td_api::userFullInfo>(user_full->is_blocked, user_full->can_be_called,
- user_full->has_private_calls, is_bot ? string() : user_full->about,
- is_bot ? user_full->about : string(), user_full->common_chat_count,
- get_bot_info_object(user_full->bot_info.get()));
+ bool is_premium = is_user_premium(user_id);
+ td_api::object_ptr<td_api::formattedText> bio_object;
+ if (is_bot) {
+ auto menu_button = get_bot_menu_button_object(td_, user_full->menu_button.get());
+ auto commands =
+ transform(user_full->commands, [](const auto &command) { return command.get_bot_command_object(); });
+ bot_info = td_api::make_object<td_api::botInfo>(
+ user_full->about, user_full->description,
+ get_photo_object(td_->file_manager_.get(), user_full->description_photo),
+ td_->animations_manager_->get_animation_object(user_full->description_animation_file_id),
+ std::move(menu_button), std::move(commands),
+ user_full->group_administrator_rights == AdministratorRights()
+ ? nullptr
+ : user_full->group_administrator_rights.get_chat_administrator_rights_object(),
+ user_full->broadcast_administrator_rights == AdministratorRights()
+ ? nullptr
+ : user_full->broadcast_administrator_rights.get_chat_administrator_rights_object());
+ } else {
+ FormattedText bio;
+ bio.text = user_full->about;
+ bio.entities = find_entities(bio.text, true, true);
+ if (!is_user_premium(user_id)) {
+ td::remove_if(bio.entities, [&](const MessageEntity &entity) {
+ if (entity.type == MessageEntity::Type::EmailAddress) {
+ return true;
+ }
+ if (entity.type == MessageEntity::Type::Url &&
+ !LinkManager::is_internal_link(utf8_utf16_substr(bio.text, entity.offset, entity.length))) {
+ return true;
+ }
+ return false;
+ });
+ }
+ bio_object = get_formatted_text_object(bio, true, 0);
+ }
+ auto voice_messages_forbidden = is_premium ? user_full->voice_messages_forbidden : false;
+ return make_tl_object<td_api::userFullInfo>(get_chat_photo_object(td_->file_manager_.get(), user_full->photo),
+ user_full->is_blocked, user_full->can_be_called,
+ user_full->supports_video_calls, user_full->has_private_calls,
+ !user_full->private_forward_name.empty(), voice_messages_forbidden,
+ user_full->need_phone_number_privacy_exception, std::move(bio_object),
+ get_premium_payment_options_object(user_full->premium_gift_options),
+ user_full->common_chat_count, std::move(bot_info));
}
-int32 ContactsManager::get_basic_group_id_object(ChatId chat_id, const char *source) const {
+td_api::object_ptr<td_api::updateBasicGroup> ContactsManager::get_update_unknown_basic_group_object(ChatId chat_id) {
+ return td_api::make_object<td_api::updateBasicGroup>(td_api::make_object<td_api::basicGroup>(
+ chat_id.get(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), true, 0));
+}
+
+int64 ContactsManager::get_basic_group_id_object(ChatId chat_id, const char *source) const {
if (chat_id.is_valid() && get_chat(chat_id) == nullptr && unknown_chats_.count(chat_id) == 0) {
- LOG(ERROR) << "Have no info about " << chat_id << " from " << source;
+ LOG(ERROR) << "Have no information about " << chat_id << " from " << source;
unknown_chats_.insert(chat_id);
- send_closure(
- G()->td(), &Td::send_update,
- td_api::make_object<td_api::updateBasicGroup>(td_api::make_object<td_api::basicGroup>(
- chat_id.get(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), true, true, 0)));
+ send_closure(G()->td(), &Td::send_update, get_update_unknown_basic_group_object(chat_id));
}
return chat_id.get();
}
@@ -9419,17 +17523,20 @@ tl_object_ptr<td_api::basicGroup> ContactsManager::get_basic_group_object(ChatId
return get_basic_group_object(chat_id, get_chat(chat_id));
}
-tl_object_ptr<td_api::basicGroup> ContactsManager::get_basic_group_object(ChatId chat_id, const Chat *chat) {
- if (chat == nullptr) {
+tl_object_ptr<td_api::basicGroup> ContactsManager::get_basic_group_object(ChatId chat_id, const Chat *c) {
+ if (c == nullptr) {
return nullptr;
}
- if (chat->migrated_to_channel_id.is_valid()) {
- get_channel_force(chat->migrated_to_channel_id);
+ if (c->migrated_to_channel_id.is_valid()) {
+ get_channel_force(c->migrated_to_channel_id);
}
+ return get_basic_group_object_const(chat_id, c);
+}
+
+tl_object_ptr<td_api::basicGroup> ContactsManager::get_basic_group_object_const(ChatId chat_id, const Chat *c) const {
return make_tl_object<td_api::basicGroup>(
- chat_id.get(), chat->participant_count, get_chat_status(chat).get_chat_member_status_object(),
- chat->everyone_is_administrator, chat->is_active,
- get_supergroup_id_object(chat->migrated_to_channel_id, "get_basic_group_object"));
+ chat_id.get(), c->participant_count, get_chat_status(c).get_chat_member_status_object(), c->is_active,
+ get_supergroup_id_object(c->migrated_to_channel_id, "get_basic_group_object"));
}
tl_object_ptr<td_api::basicGroupFullInfo> ContactsManager::get_basic_group_full_info_object(ChatId chat_id) const {
@@ -9439,21 +17546,35 @@ tl_object_ptr<td_api::basicGroupFullInfo> ContactsManager::get_basic_group_full_
tl_object_ptr<td_api::basicGroupFullInfo> ContactsManager::get_basic_group_full_info_object(
const ChatFull *chat_full) const {
CHECK(chat_full != nullptr);
+ auto bot_commands = transform(chat_full->bot_commands, [td = td_](const BotCommands &commands) {
+ return commands.get_bot_commands_object(td);
+ });
return make_tl_object<td_api::basicGroupFullInfo>(
+ get_chat_photo_object(td_->file_manager_.get(), chat_full->photo), chat_full->description,
get_user_id_object(chat_full->creator_user_id, "basicGroupFullInfo"),
transform(chat_full->participants,
[this](const DialogParticipant &chat_participant) { return get_chat_member_object(chat_participant); }),
- chat_full->invite_link);
+ chat_full->invite_link.get_chat_invite_link_object(this), std::move(bot_commands));
+}
+
+td_api::object_ptr<td_api::updateSupergroup> ContactsManager::get_update_unknown_supergroup_object(
+ ChannelId channel_id) const {
+ auto min_channel = get_min_channel(channel_id);
+ bool is_megagroup = min_channel == nullptr ? false : min_channel->is_megagroup_;
+ return td_api::make_object<td_api::updateSupergroup>(td_api::make_object<td_api::supergroup>(
+ channel_id.get(), nullptr, 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(), 0, false, false,
+ false, !is_megagroup, false, false, !is_megagroup, false, false, false, string(), false, false));
}
-int32 ContactsManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const {
+int64 ContactsManager::get_supergroup_id_object(ChannelId channel_id, const char *source) const {
if (channel_id.is_valid() && get_channel(channel_id) == nullptr && unknown_channels_.count(channel_id) == 0) {
- LOG(ERROR) << "Have no info about " << channel_id << " received from " << source;
+ if (have_min_channel(channel_id)) {
+ LOG(INFO) << "Have only min " << channel_id << " received from " << source;
+ } else {
+ LOG(ERROR) << "Have no information about " << channel_id << " received from " << source;
+ }
unknown_channels_.insert(channel_id);
- send_closure(G()->td(), &Td::send_update,
- td_api::make_object<td_api::updateSupergroup>(td_api::make_object<td_api::supergroup>(
- channel_id.get(), string(), 0, DialogParticipantStatus::Banned(0).get_chat_member_status_object(),
- 0, false, false, true, false, "")));
+ send_closure(G()->td(), &Td::send_update, get_update_unknown_supergroup_object(channel_id));
}
return channel_id.get();
}
@@ -9462,30 +17583,42 @@ tl_object_ptr<td_api::supergroup> ContactsManager::get_supergroup_object(Channel
return get_supergroup_object(channel_id, get_channel(channel_id));
}
-tl_object_ptr<td_api::supergroup> ContactsManager::get_supergroup_object(ChannelId channel_id,
- const Channel *channel) const {
- if (channel == nullptr) {
+tl_object_ptr<td_api::supergroup> ContactsManager::get_supergroup_object(ChannelId channel_id, const Channel *c) {
+ if (c == nullptr) {
return nullptr;
}
- return make_tl_object<td_api::supergroup>(
- channel_id.get(), channel->username, channel->date, get_channel_status(channel).get_chat_member_status_object(),
- channel->participant_count, channel->anyone_can_invite, channel->sign_messages, !channel->is_megagroup,
- channel->is_verified, channel->restriction_reason);
+ return td_api::make_object<td_api::supergroup>(
+ channel_id.get(), c->usernames.get_usernames_object(), c->date,
+ get_channel_status(c).get_chat_member_status_object(), c->participant_count, c->has_linked_channel,
+ c->has_location, c->sign_messages, get_channel_join_to_send(c), get_channel_join_request(c),
+ c->is_slow_mode_enabled, !c->is_megagroup, c->is_gigagroup, c->is_forum, c->is_verified,
+ get_restriction_reason_description(c->restriction_reasons), c->is_scam, c->is_fake);
}
-tl_object_ptr<td_api::supergroupFullInfo> ContactsManager::get_channel_full_info_object(ChannelId channel_id) const {
- return get_channel_full_info_object(get_channel_full(channel_id));
+tl_object_ptr<td_api::supergroupFullInfo> ContactsManager::get_supergroup_full_info_object(ChannelId channel_id) const {
+ return get_supergroup_full_info_object(get_channel_full(channel_id), channel_id);
}
-tl_object_ptr<td_api::supergroupFullInfo> ContactsManager::get_channel_full_info_object(
- const ChannelFull *channel_full) const {
+tl_object_ptr<td_api::supergroupFullInfo> ContactsManager::get_supergroup_full_info_object(
+ const ChannelFull *channel_full, ChannelId channel_id) const {
CHECK(channel_full != nullptr);
- return make_tl_object<td_api::supergroupFullInfo>(
- channel_full->description, channel_full->participant_count, channel_full->administrator_count,
- channel_full->restricted_count, channel_full->banned_count, channel_full->can_get_participants,
- channel_full->can_set_username, channel_full->can_set_sticker_set, channel_full->is_all_history_available,
- channel_full->sticker_set_id, channel_full->invite_link, channel_full->pinned_message_id.get(),
- get_basic_group_id_object(channel_full->migrated_from_chat_id, "get_channel_full_info_object"),
+ double slow_mode_delay_expires_in = 0;
+ if (channel_full->slow_mode_next_send_date != 0) {
+ slow_mode_delay_expires_in = max(channel_full->slow_mode_next_send_date - G()->server_time(), 1e-3);
+ }
+ auto bot_commands = transform(channel_full->bot_commands, [td = td_](const BotCommands &commands) {
+ return commands.get_bot_commands_object(td);
+ });
+ return td_api::make_object<td_api::supergroupFullInfo>(
+ get_chat_photo_object(td_->file_manager_.get(), channel_full->photo), channel_full->description,
+ channel_full->participant_count, channel_full->administrator_count, channel_full->restricted_count,
+ channel_full->banned_count, DialogId(channel_full->linked_channel_id).get(), channel_full->slow_mode_delay,
+ slow_mode_delay_expires_in, channel_full->can_get_participants, channel_full->can_set_username,
+ channel_full->can_set_sticker_set, channel_full->can_set_location, channel_full->can_view_statistics,
+ channel_full->is_all_history_available, channel_full->sticker_set_id.get(),
+ channel_full->location.get_chat_location_object(), channel_full->invite_link.get_chat_invite_link_object(this),
+ std::move(bot_commands),
+ get_basic_group_id_object(channel_full->migrated_from_chat_id, "get_supergroup_full_info_object"),
channel_full->migrated_from_max_message_id.get());
}
@@ -9504,15 +17637,18 @@ tl_object_ptr<td_api::SecretChatState> ContactsManager::get_secret_chat_state_ob
}
}
+td_api::object_ptr<td_api::updateSecretChat> ContactsManager::get_update_unknown_secret_chat_object(
+ SecretChatId secret_chat_id) {
+ return td_api::make_object<td_api::updateSecretChat>(td_api::make_object<td_api::secretChat>(
+ secret_chat_id.get(), 0, get_secret_chat_state_object(SecretChatState::Unknown), false, string(), 0));
+}
+
int32 ContactsManager::get_secret_chat_id_object(SecretChatId secret_chat_id, const char *source) const {
if (secret_chat_id.is_valid() && get_secret_chat(secret_chat_id) == nullptr &&
unknown_secret_chats_.count(secret_chat_id) == 0) {
- LOG(ERROR) << "Have no info about " << secret_chat_id << " from " << source;
+ LOG(ERROR) << "Have no information about " << secret_chat_id << " from " << source;
unknown_secret_chats_.insert(secret_chat_id);
- send_closure(
- G()->td(), &Td::send_update,
- td_api::make_object<td_api::updateSecretChat>(td_api::make_object<td_api::secretChat>(
- secret_chat_id.get(), 0, get_secret_chat_state_object(SecretChatState::Unknown), false, 0, string(), 0)));
+ send_closure(G()->td(), &Td::send_update, get_update_unknown_secret_chat_object(secret_chat_id));
}
return secret_chat_id.get();
}
@@ -9527,50 +17663,18 @@ tl_object_ptr<td_api::secretChat> ContactsManager::get_secret_chat_object(Secret
return nullptr;
}
get_user_force(secret_chat->user_id);
- return td_api::make_object<td_api::secretChat>(
- secret_chat_id.get(), get_user_id_object(secret_chat->user_id, "secretChat"),
- get_secret_chat_state_object(secret_chat->state), secret_chat->is_outbound, secret_chat->ttl,
- secret_chat->key_hash, secret_chat->layer);
-}
-
-tl_object_ptr<td_api::LinkState> ContactsManager::get_link_state_object(LinkState link) {
- switch (link) {
- case LinkState::Unknown:
- case LinkState::None:
- return make_tl_object<td_api::linkStateNone>();
- case LinkState::KnowsPhoneNumber:
- return make_tl_object<td_api::linkStateKnowsPhoneNumber>();
- case LinkState::Contact:
- return make_tl_object<td_api::linkStateIsContact>();
- default:
- UNREACHABLE();
- }
- return make_tl_object<td_api::linkStateNone>();
+ return get_secret_chat_object_const(secret_chat_id, secret_chat);
}
-tl_object_ptr<td_api::botInfo> ContactsManager::get_bot_info_object(const BotInfo *bot_info) {
- if (bot_info == nullptr) {
- return nullptr;
- }
-
- vector<tl_object_ptr<td_api::botCommand>> commands;
- for (auto &command : bot_info->commands) {
- commands.push_back(make_tl_object<td_api::botCommand>(command.first, command.second));
- }
-
- return make_tl_object<td_api::botInfo>(bot_info->description, std::move(commands));
+tl_object_ptr<td_api::secretChat> ContactsManager::get_secret_chat_object_const(SecretChatId secret_chat_id,
+ const SecretChat *secret_chat) const {
+ return td_api::make_object<td_api::secretChat>(secret_chat_id.get(),
+ get_user_id_object(secret_chat->user_id, "secretChat"),
+ get_secret_chat_state_object(secret_chat->state),
+ secret_chat->is_outbound, secret_chat->key_hash, secret_chat->layer);
}
-tl_object_ptr<td_api::botInfo> ContactsManager::get_bot_info_object(UserId user_id) const {
- auto user_full = get_user_full(user_id);
- if (user_full == nullptr || user_full->bot_info == nullptr) {
- return nullptr;
- }
- return get_bot_info_object(user_full->bot_info.get());
-}
-
-tl_object_ptr<td_api::chatInviteLinkInfo> ContactsManager::get_chat_invite_link_info_object(
- const string &invite_link) const {
+tl_object_ptr<td_api::chatInviteLinkInfo> ContactsManager::get_chat_invite_link_info_object(const string &invite_link) {
auto it = invite_link_infos_.find(invite_link);
if (it == invite_link_infos_.end()) {
return nullptr;
@@ -9579,54 +17683,67 @@ tl_object_ptr<td_api::chatInviteLinkInfo> ContactsManager::get_chat_invite_link_
auto invite_link_info = it->second.get();
CHECK(invite_link_info != nullptr);
- DialogId dialog_id;
+ DialogId dialog_id = invite_link_info->dialog_id;
string title;
const DialogPhoto *photo = nullptr;
+ DialogPhoto invite_link_photo;
+ string description;
int32 participant_count = 0;
- vector<int32> member_user_ids;
+ vector<int64> member_user_ids;
+ bool creates_join_request = false;
bool is_public = false;
+ bool is_member = false;
td_api::object_ptr<td_api::ChatType> chat_type;
- if (invite_link_info->chat_id != ChatId()) {
- CHECK(invite_link_info->channel_id == ChannelId());
- auto chat_id = invite_link_info->chat_id;
- const Chat *c = get_chat(chat_id);
-
- dialog_id = DialogId(invite_link_info->chat_id);
-
- if (c != nullptr) {
- title = c->title;
- photo = &c->photo;
- participant_count = c->participant_count;
- } else {
- LOG(ERROR) << "Have no information about " << chat_id;
- }
- chat_type = td_api::make_object<td_api::chatTypeBasicGroup>(
- get_basic_group_id_object(chat_id, "get_chat_invite_link_info_object"));
- } else if (invite_link_info->channel_id != ChannelId()) {
- CHECK(invite_link_info->chat_id == ChatId());
- auto channel_id = invite_link_info->channel_id;
- const Channel *c = get_channel(channel_id);
-
- dialog_id = DialogId(invite_link_info->channel_id);
-
- bool is_megagroup = false;
- if (c != nullptr) {
- title = c->title;
- photo = &c->photo;
- is_public = !c->username.empty();
- is_megagroup = c->is_megagroup;
- participant_count = c->participant_count;
- } else {
- LOG(ERROR) << "Have no information about " << channel_id;
+ if (dialog_id.is_valid()) {
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ const Chat *c = get_chat(chat_id);
+
+ if (c != nullptr) {
+ title = c->title;
+ photo = &c->photo;
+ participant_count = c->participant_count;
+ is_member = c->status.is_member();
+ } else {
+ LOG(ERROR) << "Have no information about " << chat_id;
+ }
+ chat_type = td_api::make_object<td_api::chatTypeBasicGroup>(
+ get_basic_group_id_object(chat_id, "get_chat_invite_link_info_object"));
+ break;
+ }
+ case DialogType::Channel: {
+ auto channel_id = dialog_id.get_channel_id();
+ const Channel *c = get_channel(channel_id);
+
+ bool is_megagroup = false;
+ if (c != nullptr) {
+ title = c->title;
+ photo = &c->photo;
+ is_public = is_channel_public(c);
+ is_megagroup = c->is_megagroup;
+ participant_count = c->participant_count;
+ is_member = c->status.is_member();
+ } else {
+ LOG(ERROR) << "Have no information about " << channel_id;
+ }
+ chat_type = td_api::make_object<td_api::chatTypeSupergroup>(
+ get_supergroup_id_object(channel_id, "get_chat_invite_link_info_object"), !is_megagroup);
+ break;
+ }
+ default:
+ UNREACHABLE();
}
- chat_type = td_api::make_object<td_api::chatTypeSupergroup>(
- get_supergroup_id_object(channel_id, "get_chat_invite_link_info_object"), !is_megagroup);
+ description = get_dialog_about(dialog_id);
} else {
title = invite_link_info->title;
- photo = &invite_link_info->photo;
+ invite_link_photo = as_fake_dialog_photo(invite_link_info->photo, dialog_id);
+ photo = &invite_link_photo;
+ description = invite_link_info->description;
participant_count = invite_link_info->participant_count;
- member_user_ids = get_user_ids_object(invite_link_info->participant_user_ids);
+ member_user_ids = get_user_ids_object(invite_link_info->participant_user_ids, "get_chat_invite_link_info_object");
+ creates_join_request = invite_link_info->creates_join_request;
is_public = invite_link_info->is_public;
if (invite_link_info->is_chat) {
@@ -9636,23 +17753,104 @@ tl_object_ptr<td_api::chatInviteLinkInfo> ContactsManager::get_chat_invite_link_
}
}
- if (dialog_id != DialogId()) {
+ if (dialog_id.is_valid()) {
td_->messages_manager_->force_create_dialog(dialog_id, "get_chat_invite_link_info_object");
}
+ int32 accessible_for = 0;
+ if (dialog_id.is_valid() && !is_member) {
+ auto access_it = dialog_access_by_invite_link_.find(dialog_id);
+ if (access_it != dialog_access_by_invite_link_.end()) {
+ accessible_for = td::max(1, access_it->second.accessible_before - G()->unix_time() - 1);
+ }
+ }
- return make_tl_object<td_api::chatInviteLinkInfo>(dialog_id.get(), std::move(chat_type), title,
- get_chat_photo_object(td_->file_manager_.get(), photo),
- participant_count, std::move(member_user_ids), is_public);
+ return make_tl_object<td_api::chatInviteLinkInfo>(dialog_id.get(), accessible_for, std::move(chat_type), title,
+ get_chat_photo_info_object(td_->file_manager_.get(), photo),
+ description, participant_count, std::move(member_user_ids),
+ creates_join_request, is_public);
}
-UserId ContactsManager::get_support_user(Promise<Unit> &&promise) {
+void ContactsManager::get_support_user(Promise<td_api::object_ptr<td_api::user>> &&promise) {
if (support_user_id_.is_valid()) {
- promise.set_value(Unit());
- return support_user_id_;
+ return promise.set_value(get_user_object(support_user_id_));
}
- td_->create_handler<GetSupportUserQuery>(std::move(promise))->send();
- return UserId();
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), promise = std::move(promise)](Result<UserId> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &ContactsManager::on_get_support_user, result.move_as_ok(), std::move(promise));
+ }
+ });
+ td_->create_handler<GetSupportUserQuery>(std::move(query_promise))->send();
+}
+
+void ContactsManager::on_get_support_user(UserId user_id, Promise<td_api::object_ptr<td_api::user>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ const User *u = get_user(user_id);
+ if (u == nullptr) {
+ return promise.set_error(Status::Error(500, "Can't find support user"));
+ }
+ if (!u->is_support) {
+ LOG(ERROR) << "Receive non-support " << user_id << ", but expected a support user";
+ }
+
+ support_user_id_ = user_id;
+ promise.set_value(get_user_object(user_id, u));
+}
+
+void ContactsManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ for (auto user_id : unknown_users_) {
+ if (!have_min_user(user_id)) {
+ updates.push_back(get_update_unknown_user_object(user_id));
+ }
+ }
+ for (auto chat_id : unknown_chats_) {
+ if (!have_chat(chat_id)) {
+ updates.push_back(get_update_unknown_basic_group_object(chat_id));
+ }
+ }
+ for (auto channel_id : unknown_channels_) {
+ if (!have_channel(channel_id)) {
+ updates.push_back(get_update_unknown_supergroup_object(channel_id));
+ }
+ }
+ for (auto secret_chat_id : unknown_secret_chats_) {
+ if (!have_secret_chat(secret_chat_id)) {
+ updates.push_back(get_update_unknown_secret_chat_object(secret_chat_id));
+ }
+ }
+
+ users_.foreach([&](const UserId &user_id, const unique_ptr<User> &user) {
+ updates.push_back(td_api::make_object<td_api::updateUser>(get_user_object(user_id, user.get())));
+ });
+ channels_.foreach([&](const ChannelId &channel_id, const unique_ptr<Channel> &channel) {
+ updates.push_back(td_api::make_object<td_api::updateSupergroup>(get_supergroup_object(channel_id, channel.get())));
+ });
+ // chat objects can contain channel_id, so they must be sent after channels
+ chats_.foreach([&](const ChatId &chat_id, const unique_ptr<Chat> &chat) {
+ updates.push_back(td_api::make_object<td_api::updateBasicGroup>(get_basic_group_object_const(chat_id, chat.get())));
+ });
+ // secret chat objects contain user_id, so they must be sent after users
+ secret_chats_.foreach([&](const SecretChatId &secret_chat_id, const unique_ptr<SecretChat> &secret_chat) {
+ updates.push_back(
+ td_api::make_object<td_api::updateSecretChat>(get_secret_chat_object_const(secret_chat_id, secret_chat.get())));
+ });
+
+ users_full_.foreach([&](const UserId &user_id, const unique_ptr<UserFull> &user_full) {
+ updates.push_back(td_api::make_object<td_api::updateUserFullInfo>(
+ user_id.get(), get_user_full_info_object(user_id, user_full.get())));
+ });
+ channels_full_.foreach([&](const ChannelId &channel_id, const unique_ptr<ChannelFull> &channel_full) {
+ updates.push_back(td_api::make_object<td_api::updateSupergroupFullInfo>(
+ channel_id.get(), get_supergroup_full_info_object(channel_full.get(), channel_id)));
+ });
+ chats_full_.foreach([&](const ChatId &chat_id, const unique_ptr<ChatFull> &chat_full) {
+ updates.push_back(td_api::make_object<td_api::updateBasicGroupFullInfo>(
+ chat_id.get(), get_basic_group_full_info_object(chat_full.get())));
+ });
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ContactsManager.h b/protocols/Telegram/tdlib/td/td/telegram/ContactsManager.h
index fc1bfc34ff..978a9c223b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ContactsManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/ContactsManager.h
@@ -1,72 +1,93 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/actor.h"
-#include "td/actor/MultiPromise.h"
-#include "td/actor/PromiseFuture.h"
-#include "td/actor/Timeout.h"
-
-#include "td/db/binlog/BinlogEvent.h"
-
#include "td/telegram/AccessRights.h"
+#include "td/telegram/BotCommand.h"
+#include "td/telegram/BotMenuButton.h"
#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChannelType.h"
#include "td/telegram/ChatId.h"
#include "td/telegram/Contact.h"
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/DialogAdministrator.h"
#include "td/telegram/DialogId.h"
+#include "td/telegram/DialogInviteLink.h"
+#include "td/telegram/DialogLocation.h"
#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/DialogParticipantFilter.h"
+#include "td/telegram/EmojiStatus.h"
#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/FolderId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/Location.h"
#include "td/telegram/MessageId.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/Photo.h"
+#include "td/telegram/PremiumGiftOption.h"
+#include "td/telegram/PublicDialogType.h"
+#include "td/telegram/QueryCombiner.h"
+#include "td/telegram/RestrictionReason.h"
#include "td/telegram/SecretChatId.h"
+#include "td/telegram/StickerSetId.h"
+#include "td/telegram/SuggestedAction.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
+#include "td/telegram/Usernames.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/MultiPromise.h"
+#include "td/actor/MultiTimeout.h"
#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/Hints.h"
-#include "td/utils/Slice.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
+#include "td/utils/Time.h"
+#include "td/utils/WaitFreeHashMap.h"
+#include "td/utils/WaitFreeHashSet.h"
+#include <functional>
#include <memory>
-#include <unordered_map>
-#include <unordered_set>
#include <utility>
namespace td {
-class Td;
+struct BinlogEvent;
-struct BotData {
- string username;
- bool can_join_groups;
- bool can_read_all_group_messages;
- bool is_inline;
- bool need_location;
-};
+class ChannelParticipantFilter;
-enum class ChannelType { Broadcast, Megagroup, Unknown };
+struct MinChannel;
-enum class CheckDialogUsernameResult { Ok, Invalid, Occupied, PublicDialogsTooMuch, PublicGroupsUnavailable };
+class Td;
-class ContactsManager : public Actor {
+class ContactsManager final : public Actor {
public:
ContactsManager(Td *td, ActorShared<> parent);
+ ContactsManager(const ContactsManager &) = delete;
+ ContactsManager &operator=(const ContactsManager &) = delete;
+ ContactsManager(ContactsManager &&) = delete;
+ ContactsManager &operator=(ContactsManager &&) = delete;
+ ~ContactsManager() final;
static UserId load_my_id();
static UserId get_user_id(const tl_object_ptr<telegram_api::User> &user);
static ChatId get_chat_id(const tl_object_ptr<telegram_api::Chat> &chat);
static ChannelId get_channel_id(const tl_object_ptr<telegram_api::Chat> &chat);
+ static DialogId get_dialog_id(const tl_object_ptr<telegram_api::Chat> &chat);
- tl_object_ptr<telegram_api::InputUser> get_input_user(UserId user_id) const;
- bool have_input_user(UserId user_id) const;
+ Result<tl_object_ptr<telegram_api::InputUser>> get_input_user(UserId user_id) const;
// TODO get_input_chat ???
@@ -85,32 +106,45 @@ class ContactsManager : public Actor {
AccessRights access_rights) const;
bool have_input_encrypted_peer(SecretChatId secret_chat_id, AccessRights access_rights) const;
- const DialogPhoto *get_user_dialog_photo(UserId user_id) const;
+ const DialogPhoto *get_user_dialog_photo(UserId user_id);
const DialogPhoto *get_chat_dialog_photo(ChatId chat_id) const;
const DialogPhoto *get_channel_dialog_photo(ChannelId channel_id) const;
- const DialogPhoto *get_secret_chat_dialog_photo(SecretChatId secret_chat_id) const;
+ const DialogPhoto *get_secret_chat_dialog_photo(SecretChatId secret_chat_id);
string get_user_title(UserId user_id) const;
string get_chat_title(ChatId chat_id) const;
string get_channel_title(ChannelId channel_id) const;
string get_secret_chat_title(SecretChatId secret_chat_id) const;
- bool is_update_about_username_change_received(UserId user_id) const;
+ RestrictedRights get_user_default_permissions(UserId user_id) const;
+ RestrictedRights get_chat_default_permissions(ChatId chat_id) const;
+ RestrictedRights get_channel_default_permissions(ChannelId channel_id) const;
+ RestrictedRights get_secret_chat_default_permissions(SecretChatId secret_chat_id) const;
+
+ bool get_chat_has_protected_content(ChatId chat_id) const;
+ bool get_channel_has_protected_content(ChannelId channel_id) const;
- string get_user_username(UserId user_id) const;
- string get_channel_username(ChannelId channel_id) const;
- string get_secret_chat_username(SecretChatId secret_chat_id) const;
+ string get_user_private_forward_name(UserId user_id);
+ bool get_user_voice_messages_forbidden(UserId user_id) const;
+
+ string get_dialog_about(DialogId dialog_id);
+
+ string get_dialog_search_text(DialogId dialog_id) const;
+
+ void for_each_secret_chat_with_user(UserId user_id, const std::function<void(SecretChatId)> &f);
+
+ string get_user_first_username(UserId user_id) const;
+ string get_channel_first_username(ChannelId channel_id) const;
int32 get_secret_chat_date(SecretChatId secret_chat_id) const;
int32 get_secret_chat_ttl(SecretChatId secret_chat_id) const;
UserId get_secret_chat_user_id(SecretChatId secret_chat_id) const;
+ bool get_secret_chat_is_outbound(SecretChatId secret_chat_id) const;
SecretChatState get_secret_chat_state(SecretChatId secret_chat_id) const;
int32 get_secret_chat_layer(SecretChatId secret_chat_id) const;
+ FolderId get_secret_chat_initial_folder_id(SecretChatId secret_chat_id) const;
- bool default_can_report_spam_in_secret_chat(SecretChatId secret_chat_id) const;
-
- void on_imported_contacts(int64 random_id, vector<UserId> imported_contact_user_ids,
- vector<int32> unimported_contact_invites);
+ void on_imported_contacts(int64 random_id, Result<tl_object_ptr<telegram_api::contacts_importedContacts>> result);
void on_deleted_contacts(const vector<UserId> &deleted_contact_user_ids);
@@ -122,146 +156,201 @@ class ContactsManager : public Actor {
void reload_contacts(bool force);
- void on_get_contacts_link(tl_object_ptr<telegram_api::contacts_link> &&link);
-
- void on_get_user(tl_object_ptr<telegram_api::User> &&user, bool is_me = false, bool is_support = false);
- void on_get_users(vector<tl_object_ptr<telegram_api::User>> &&users);
+ void on_get_user(tl_object_ptr<telegram_api::User> &&user, const char *source, bool is_me = false);
+ void on_get_users(vector<tl_object_ptr<telegram_api::User>> &&users, const char *source);
void on_binlog_user_event(BinlogEvent &&event);
void on_binlog_chat_event(BinlogEvent &&event);
void on_binlog_channel_event(BinlogEvent &&event);
void on_binlog_secret_chat_event(BinlogEvent &&event);
- void on_get_user_full(tl_object_ptr<telegram_api::userFull> &&user_full);
+ void on_get_user_full(tl_object_ptr<telegram_api::userFull> &&user);
void on_get_user_photos(UserId user_id, int32 offset, int32 limit, int32 total_count,
vector<tl_object_ptr<telegram_api::Photo>> photos);
- void on_get_chat(tl_object_ptr<telegram_api::Chat> &&chat);
- void on_get_chats(vector<tl_object_ptr<telegram_api::Chat>> &&chats);
+ void on_get_chat(tl_object_ptr<telegram_api::Chat> &&chat, const char *source);
+ void on_get_chats(vector<tl_object_ptr<telegram_api::Chat>> &&chats, const char *source);
- void on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&chat_full);
+ void on_get_chat_full(tl_object_ptr<telegram_api::ChatFull> &&chat_full, Promise<Unit> &&promise);
+ void on_get_chat_full_failed(ChatId chat_id);
+ void on_get_channel_full_failed(ChannelId channel_id);
void on_update_profile_success(int32 flags, const string &first_name, const string &last_name, const string &about);
- void on_update_user_name(UserId user_id, string &&first_name, string &&last_name, string &&username);
+ void on_update_user_name(UserId user_id, string &&first_name, string &&last_name, Usernames &&usernames);
void on_update_user_phone_number(UserId user_id, string &&phone_number);
void on_update_user_photo(UserId user_id, tl_object_ptr<telegram_api::UserProfilePhoto> &&photo_ptr);
+ void on_update_user_emoji_status(UserId user_id, tl_object_ptr<telegram_api::EmojiStatus> &&emoji_status);
void on_update_user_online(UserId user_id, tl_object_ptr<telegram_api::UserStatus> &&status);
- void on_update_user_links(UserId user_id, tl_object_ptr<telegram_api::ContactLink> &&outbound,
- tl_object_ptr<telegram_api::ContactLink> &&inbound);
- void on_update_user_blocked(UserId user_id, bool is_blocked);
+ void on_update_user_local_was_online(UserId user_id, int32 local_was_online);
+ void on_update_user_is_blocked(UserId user_id, bool is_blocked);
+ void on_update_user_common_chat_count(UserId user_id, int32 common_chat_count);
+ void on_update_user_need_phone_number_privacy_exception(UserId user_id, bool need_phone_number_privacy_exception);
+ void on_set_profile_photo(tl_object_ptr<telegram_api::photos_photo> &&photo, int64 old_photo_id);
void on_delete_profile_photo(int64 profile_photo_id, Promise<Unit> promise);
- void on_get_chat_participants(tl_object_ptr<telegram_api::ChatParticipants> &&participants);
+ void on_ignored_restriction_reasons_changed();
+
+ void on_get_chat_participants(tl_object_ptr<telegram_api::ChatParticipants> &&participants, bool from_update);
void on_update_chat_add_user(ChatId chat_id, UserId inviter_user_id, UserId user_id, int32 date, int32 version);
+ void on_update_chat_description(ChatId chat_id, string &&description);
void on_update_chat_edit_administrator(ChatId chat_id, UserId user_id, bool is_administrator, int32 version);
void on_update_chat_delete_user(ChatId chat_id, UserId user_id, int32 version);
- void on_update_chat_everyone_is_administrator(ChatId chat_id, bool everyone_is_administrator, int32 version);
+ void on_update_chat_default_permissions(ChatId chat_id, RestrictedRights default_permissions, int32 version);
+ void on_update_chat_pinned_message(ChatId chat_id, MessageId pinned_message_id, int32 version);
- void on_update_channel_username(ChannelId channel_id, string &&username);
+ void on_update_channel_editable_username(ChannelId channel_id, string &&username);
+ void on_update_channel_usernames(ChannelId channel_id, Usernames &&usernames);
void on_update_channel_description(ChannelId channel_id, string &&description);
- void on_update_channel_sticker_set(ChannelId channel_id, int64 sticker_set_id);
- void on_update_channel_pinned_message(ChannelId channel_id, MessageId message_id);
- void on_update_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available);
+ void on_update_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id);
+ void on_update_channel_linked_channel_id(ChannelId channel_id, ChannelId group_channel_id);
+ void on_update_channel_location(ChannelId channel_id, const DialogLocation &location);
+ void on_update_channel_slow_mode_delay(ChannelId channel_id, int32 slow_mode_delay, Promise<Unit> &&promise);
+ void on_update_channel_slow_mode_next_send_date(ChannelId channel_id, int32 slow_mode_next_send_date);
+ void on_update_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available,
+ Promise<Unit> &&promise);
+ void on_update_channel_default_permissions(ChannelId channel_id, RestrictedRights default_permissions);
+ void on_update_channel_administrator_count(ChannelId channel_id, int32 administrator_count);
- void on_update_dialog_administrators(DialogId dialog_id, vector<UserId> administrator_user_ids, bool have_access);
+ void on_update_bot_stopped(UserId user_id, int32 date, bool is_stopped);
+ void on_update_chat_participant(ChatId chat_id, UserId user_id, int32 date, DialogInviteLink invite_link,
+ tl_object_ptr<telegram_api::ChatParticipant> old_participant,
+ tl_object_ptr<telegram_api::ChatParticipant> new_participant);
+ void on_update_channel_participant(ChannelId channel_id, UserId user_id, int32 date, DialogInviteLink invite_link,
+ tl_object_ptr<telegram_api::ChannelParticipant> old_participant,
+ tl_object_ptr<telegram_api::ChannelParticipant> new_participant);
+ void on_update_chat_invite_requester(DialogId dialog_id, UserId user_id, string about, int32 date,
+ DialogInviteLink invite_link);
- static bool speculative_add_count(int32 &count, int32 new_count);
+ int32 on_update_peer_located(vector<tl_object_ptr<telegram_api::PeerLocated>> &&peers, bool from_update);
- void speculative_add_channel_participants(ChannelId channel_id, int32 new_participant_count, bool by_me);
+ void on_update_bot_commands(DialogId dialog_id, UserId bot_user_id,
+ vector<tl_object_ptr<telegram_api::botCommand>> &&bot_commands);
- void invalidate_channel_full(ChannelId channel_id);
+ void on_update_bot_menu_button(UserId bot_user_id, tl_object_ptr<telegram_api::BotMenuButton> &&bot_menu_button);
- bool on_get_channel_error(ChannelId channel_id, const Status &status, const string &source);
+ void on_update_dialog_administrators(DialogId dialog_id, vector<DialogAdministrator> &&administrators,
+ bool have_access, bool from_database);
- void on_get_channel_participants_success(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset,
- int32 limit, int64 random_id, int32 total_count,
- vector<tl_object_ptr<telegram_api::ChannelParticipant>> &&participants);
+ void speculative_add_channel_participants(ChannelId channel_id, const vector<UserId> &added_user_ids,
+ UserId inviter_user_id, int32 date, bool by_me);
- void on_get_channel_participants_fail(ChannelId channel_id, ChannelParticipantsFilter filter, int32 offset,
- int32 limit, int64 random_id);
+ void speculative_delete_channel_participant(ChannelId channel_id, UserId deleted_user_id, bool by_me);
- static Slice get_dialog_invite_link_hash(const string &invite_link);
+ void invalidate_channel_full(ChannelId channel_id, bool need_drop_slow_mode_delay, const char *source);
- void on_get_chat_invite_link(ChatId chat_id, tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr);
+ bool on_get_channel_error(ChannelId channel_id, const Status &status, const char *source);
- void on_get_channel_invite_link(ChannelId channel_id,
- tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr);
+ void on_get_permanent_dialog_invite_link(DialogId dialog_id, const DialogInviteLink &invite_link);
void on_get_dialog_invite_link_info(const string &invite_link,
- tl_object_ptr<telegram_api::ChatInvite> &&chat_invite_ptr);
+ tl_object_ptr<telegram_api::ChatInvite> &&chat_invite_ptr,
+ Promise<Unit> &&promise);
- void invalidate_invite_link(const string &invite_link);
+ void invalidate_invite_link_info(const string &invite_link);
- void on_get_created_public_channels(vector<tl_object_ptr<telegram_api::Chat>> &&chats);
+ void on_get_created_public_channels(PublicDialogType type, vector<tl_object_ptr<telegram_api::Chat>> &&chats);
- void on_get_user_full_success(UserId user_id);
+ void on_get_dialogs_for_discussion(vector<tl_object_ptr<telegram_api::Chat>> &&chats);
- void on_get_user_full_fail(UserId user_id, Status &&error);
+ void on_get_inactive_channels(vector<tl_object_ptr<telegram_api::Chat>> &&chats, Promise<Unit> &&promise);
- void on_get_chat_full_success(ChatId chat_id);
+ void remove_inactive_channel(ChannelId channel_id);
- void on_get_chat_full_fail(ChatId chat_id, Status &&error);
+ UserId get_my_id() const;
- void on_get_channel_full_success(ChannelId channel_id);
+ void set_my_online_status(bool is_online, bool send_update, bool is_local);
- void on_get_channel_full_fail(ChannelId channel_id, Status &&error);
+ struct MyOnlineStatusInfo {
+ bool is_online_local = false;
+ bool is_online_remote = false;
+ int32 was_online_local = 0;
+ int32 was_online_remote = 0;
+ };
- UserId get_my_id(const char *source) const;
- void set_my_online_status(bool is_online, bool send_update, bool is_local);
+ MyOnlineStatusInfo get_my_online_status() const;
- void on_update_online_status_privacy();
+ static UserId get_service_notifications_user_id();
- void on_channel_unban_timeout(ChannelId channel_id);
+ UserId add_service_notifications_user();
- void check_dialog_username(DialogId dialog_id, const string &username, Promise<CheckDialogUsernameResult> &&promise);
+ static UserId get_replies_bot_user_id();
- static td_api::object_ptr<td_api::CheckChatUsernameResult> get_check_chat_username_result_object(
- CheckDialogUsernameResult result);
+ static UserId get_anonymous_bot_user_id();
+
+ static UserId get_channel_bot_user_id();
+
+ UserId add_anonymous_bot_user();
+
+ UserId add_channel_bot_user();
+
+ void on_update_username_is_active(string &&username, bool is_active, Promise<Unit> &&promise);
- void set_account_ttl(int32 account_ttl, Promise<Unit> &&promise) const;
- void get_account_ttl(Promise<int32> &&promise) const;
+ void on_update_active_usernames_order(vector<string> &&usernames, Promise<Unit> &&promise);
- void get_active_sessions(Promise<tl_object_ptr<td_api::sessions>> &&promise) const;
- void terminate_session(int64 session_id, Promise<Unit> &&promise) const;
- void terminate_all_other_sessions(Promise<Unit> &&promise) const;
+ void on_update_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active,
+ Promise<Unit> &&promise);
+
+ void on_deactivate_channel_usernames(ChannelId channel_id, Promise<Unit> &&promise);
- void get_connected_websites(Promise<tl_object_ptr<td_api::connectedWebsites>> &&promise) const;
- void disconnect_website(int64 authorizations_id, Promise<Unit> &&promise) const;
- void disconnect_all_websites(Promise<Unit> &&promise) const;
+ void on_update_channel_active_usernames_order(ChannelId channel_id, vector<string> &&usernames,
+ Promise<Unit> &&promise);
- Status block_user(UserId user_id);
+ void on_update_online_status_privacy();
- Status unblock_user(UserId user_id);
+ void on_update_phone_number_privacy();
+
+ void invalidate_user_full(UserId user_id);
- int64 get_blocked_users(int32 offset, int32 limit, Promise<Unit> &&promise);
+ enum class CheckDialogUsernameResult : uint8 { Ok, Invalid, Occupied, PublicDialogsTooMuch, PublicGroupsUnavailable };
- void on_get_blocked_users_result(int32 offset, int32 limit, int64 random_id, int32 total_count,
- vector<tl_object_ptr<telegram_api::contactBlocked>> &&blocked_users);
+ void check_dialog_username(DialogId dialog_id, const string &username, Promise<CheckDialogUsernameResult> &&promise);
- void on_failed_get_blocked_users(int64 random_id);
+ static td_api::object_ptr<td_api::CheckChatUsernameResult> get_check_chat_username_result_object(
+ CheckDialogUsernameResult result);
- tl_object_ptr<td_api::users> get_blocked_users_object(int64 random_id);
+ void add_contact(Contact contact, bool share_phone_number, Promise<Unit> &&promise);
- std::pair<vector<UserId>, vector<int32>> import_contacts(const vector<tl_object_ptr<td_api::contact>> &contacts,
- int64 &random_id, Promise<Unit> &&promise);
+ std::pair<vector<UserId>, vector<int32>> import_contacts(const vector<Contact> &contacts, int64 &random_id,
+ Promise<Unit> &&promise);
std::pair<int32, vector<UserId>> search_contacts(const string &query, int32 limit, Promise<Unit> &&promise);
- void remove_contacts(vector<UserId> user_ids, Promise<Unit> &&promise);
+ void remove_contacts(const vector<UserId> &user_ids, Promise<Unit> &&promise);
+
+ void remove_contacts_by_phone_number(vector<string> user_phone_numbers, vector<UserId> user_ids,
+ Promise<Unit> &&promise);
int32 get_imported_contact_count(Promise<Unit> &&promise);
- std::pair<vector<UserId>, vector<int32>> change_imported_contacts(vector<tl_object_ptr<td_api::contact>> &&contacts,
- int64 &random_id, Promise<Unit> &&promise);
+ std::pair<vector<UserId>, vector<int32>> change_imported_contacts(vector<Contact> &contacts, int64 &random_id,
+ Promise<Unit> &&promise);
void clear_imported_contacts(Promise<Unit> &&promise);
void on_update_contacts_reset();
- void set_profile_photo(const tl_object_ptr<td_api::InputFile> &input_photo, Promise<Unit> &&promise);
+ UserId search_user_by_phone_number(string phone_number, Promise<Unit> &&promise);
+
+ void on_resolved_phone_number(const string &phone_number, UserId user_id);
+
+ void share_phone_number(UserId user_id, Promise<Unit> &&promise);
+
+ void search_dialogs_nearby(const Location &location, Promise<td_api::object_ptr<td_api::chatsNearby>> &&promise);
+
+ void set_location(const Location &location, Promise<Unit> &&promise);
+
+ static void set_location_visibility(Td *td);
+
+ void get_is_location_visible(Promise<Unit> &&promise);
+
+ FileId get_profile_photo_file_id(int64 photo_id) const;
+
+ void set_profile_photo(const td_api::object_ptr<td_api::InputChatPhoto> &input_photo, Promise<Unit> &&promise);
+
+ void send_update_profile_photo_query(FileId file_id, int64 old_photo_id, Promise<Unit> &&promise);
void delete_profile_photo(int64 profile_photo_id, Promise<Unit> &&promise);
@@ -271,93 +360,208 @@ class ContactsManager : public Actor {
void set_username(const string &username, Promise<Unit> &&promise);
- void toggle_chat_administrators(ChatId chat_id, bool everyone_is_administrator, Promise<Unit> &&promise);
+ void toggle_username_is_active(string &&username, bool is_active, Promise<Unit> &&promise);
+
+ void reorder_usernames(vector<string> &&usernames, Promise<Unit> &&promise);
+
+ void set_emoji_status(EmojiStatus emoji_status, Promise<Unit> &&promise);
+
+ void set_chat_description(ChatId chat_id, const string &description, Promise<Unit> &&promise);
void set_channel_username(ChannelId channel_id, const string &username, Promise<Unit> &&promise);
- void set_channel_sticker_set(ChannelId channel_id, int64 sticker_set_id, Promise<Unit> &&promise);
+ void toggle_channel_username_is_active(ChannelId channel_id, string &&username, bool is_active,
+ Promise<Unit> &&promise);
+
+ void disable_all_channel_usernames(ChannelId channel_id, Promise<Unit> &&promise);
+
+ void reorder_channel_usernames(ChannelId channel_id, vector<string> &&usernames, Promise<Unit> &&promise);
- void toggle_channel_invites(ChannelId channel_id, bool anyone_can_invite, Promise<Unit> &&promise);
+ void set_channel_sticker_set(ChannelId channel_id, StickerSetId sticker_set_id, Promise<Unit> &&promise);
void toggle_channel_sign_messages(ChannelId channel_id, bool sign_messages, Promise<Unit> &&promise);
+ void toggle_channel_join_to_send(ChannelId channel_id, bool joint_to_send, Promise<Unit> &&promise);
+
+ void toggle_channel_join_request(ChannelId channel_id, bool join_request, Promise<Unit> &&promise);
+
void toggle_channel_is_all_history_available(ChannelId channel_id, bool is_all_history_available,
Promise<Unit> &&promise);
+ void toggle_channel_is_forum(ChannelId channel_id, bool is_forum, Promise<Unit> &&promise);
+
+ void convert_channel_to_gigagroup(ChannelId channel_id, Promise<Unit> &&promise);
+
void set_channel_description(ChannelId channel_id, const string &description, Promise<Unit> &&promise);
- void pin_channel_message(ChannelId channel_id, MessageId message_id, bool disable_notification,
- Promise<Unit> &&promise);
+ void set_channel_discussion_group(DialogId dialog_id, DialogId discussion_dialog_id, Promise<Unit> &&promise);
- void unpin_channel_message(ChannelId channel_id, Promise<Unit> &&promise);
+ void set_channel_location(DialogId dialog_id, const DialogLocation &location, Promise<Unit> &&promise);
- void report_channel_spam(ChannelId channel_id, UserId user_id, const vector<MessageId> &message_ids,
- Promise<Unit> &&promise);
+ void set_channel_slow_mode_delay(DialogId dialog_id, int32 slow_mode_delay, Promise<Unit> &&promise);
- void delete_channel(ChannelId channel_id, Promise<Unit> &&promise);
+ void report_channel_spam(ChannelId channel_id, const vector<MessageId> &message_ids, Promise<Unit> &&promise);
- void add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, Promise<Unit> &&promise);
+ void delete_dialog(DialogId dialog_id, Promise<Unit> &&promise);
- void add_channel_participant(ChannelId channel_id, UserId user_id, Promise<Unit> &&promise,
- DialogParticipantStatus old_status = DialogParticipantStatus::Left());
+ void get_channel_statistics_dc_id(DialogId dialog_id, bool for_full_statistics, Promise<DcId> &&promise);
- void add_channel_participants(ChannelId channel_id, const vector<UserId> &user_ids, Promise<Unit> &&promise);
+ void get_channel_statistics(DialogId dialog_id, bool is_dark,
+ Promise<td_api::object_ptr<td_api::ChatStatistics>> &&promise);
- void change_chat_participant_status(ChatId chat_id, UserId user_id, DialogParticipantStatus status,
- Promise<Unit> &&promise);
+ bool can_get_channel_message_statistics(DialogId dialog_id) const;
- void change_channel_participant_status(ChannelId channel_id, UserId user_id, DialogParticipantStatus status,
- Promise<Unit> &&promise);
+ void get_channel_message_statistics(FullMessageId full_message_id, bool is_dark,
+ Promise<td_api::object_ptr<td_api::messageStatistics>> &&promise);
- void export_chat_invite_link(ChatId chat_id, Promise<Unit> &&promise);
+ void load_statistics_graph(DialogId dialog_id, string token, int64 x,
+ Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise);
- void export_channel_invite_link(ChannelId channel_id, Promise<Unit> &&promise);
+ struct CanTransferOwnershipResult {
+ enum class Type : uint8 { Ok, PasswordNeeded, PasswordTooFresh, SessionTooFresh };
+ Type type = Type::Ok;
+ int32 retry_after = 0;
+ };
+ void can_transfer_ownership(Promise<CanTransferOwnershipResult> &&promise);
- void check_dialog_invite_link(const string &invite_link, Promise<Unit> &&promise) const;
+ static td_api::object_ptr<td_api::CanTransferOwnershipResult> get_can_transfer_ownership_result_object(
+ CanTransferOwnershipResult result);
- void import_dialog_invite_link(const string &invite_link, Promise<DialogId> &&promise);
+ void transfer_dialog_ownership(DialogId dialog_id, UserId user_id, const string &password, Promise<Unit> &&promise);
+
+ void export_dialog_invite_link(DialogId dialog_id, string title, int32 expire_date, int32 usage_limit,
+ bool creates_join_request, bool is_permanent,
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise);
- string get_chat_invite_link(ChatId chat_id) const;
+ void edit_dialog_invite_link(DialogId dialog_id, const string &link, string title, int32 expire_date,
+ int32 usage_limit, bool creates_join_request,
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise);
- string get_channel_invite_link(ChannelId channel_id);
+ void get_dialog_invite_link(DialogId dialog_id, const string &invite_link,
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise);
- MessageId get_channel_pinned_message_id(ChannelId channel_id);
+ void get_dialog_invite_link_counts(DialogId dialog_id,
+ Promise<td_api::object_ptr<td_api::chatInviteLinkCounts>> &&promise);
+
+ void get_dialog_invite_links(DialogId dialog_id, UserId creator_user_id, bool is_revoked, int32 offset_date,
+ const string &offset_invite_link, int32 limit,
+ Promise<td_api::object_ptr<td_api::chatInviteLinks>> &&promise);
+
+ void get_dialog_invite_link_users(DialogId dialog_id, const string &invite_link,
+ td_api::object_ptr<td_api::chatInviteLinkMember> offset_member, int32 limit,
+ Promise<td_api::object_ptr<td_api::chatInviteLinkMembers>> &&promise);
+
+ void get_dialog_join_requests(DialogId dialog_id, const string &invite_link, const string &query,
+ td_api::object_ptr<td_api::chatJoinRequest> offset_request, int32 limit,
+ Promise<td_api::object_ptr<td_api::chatJoinRequests>> &&promise);
+
+ void process_dialog_join_request(DialogId dialog_id, UserId user_id, bool approve, Promise<Unit> &&promise);
+
+ void process_dialog_join_requests(DialogId dialog_id, const string &invite_link, bool approve,
+ Promise<Unit> &&promise);
+
+ void revoke_dialog_invite_link(DialogId dialog_id, const string &link,
+ Promise<td_api::object_ptr<td_api::chatInviteLinks>> &&promise);
+
+ void delete_revoked_dialog_invite_link(DialogId dialog_id, const string &invite_link, Promise<Unit> &&promise);
+
+ void delete_all_revoked_dialog_invite_links(DialogId dialog_id, UserId creator_user_id, Promise<Unit> &&promise);
+
+ void check_dialog_invite_link(const string &invite_link, bool force, Promise<Unit> &&promise);
+
+ void import_dialog_invite_link(const string &invite_link, Promise<DialogId> &&promise);
ChannelId migrate_chat_to_megagroup(ChatId chat_id, Promise<Unit> &promise);
- vector<DialogId> get_created_public_dialogs(Promise<Unit> &&promise);
+ void get_created_public_dialogs(PublicDialogType type, Promise<td_api::object_ptr<td_api::chats>> &&promise,
+ bool from_binlog);
+
+ void check_created_public_dialogs_limit(PublicDialogType type, Promise<Unit> &&promise);
+
+ void reload_created_public_dialogs(PublicDialogType type, Promise<td_api::object_ptr<td_api::chats>> &&promise);
+
+ vector<DialogId> get_dialogs_for_discussion(Promise<Unit> &&promise);
+
+ vector<DialogId> get_inactive_channels(Promise<Unit> &&promise);
+
+ void dismiss_dialog_suggested_action(SuggestedAction action, Promise<Unit> &&promise);
+
+ bool is_user_contact(UserId user_id, bool is_mutual = false) const;
+
+ bool is_user_premium(UserId user_id) const;
bool is_user_deleted(UserId user_id) const;
+ bool is_user_support(UserId user_id) const;
+
bool is_user_bot(UserId user_id) const;
+
+ struct BotData {
+ string username;
+ bool can_join_groups;
+ bool can_read_all_group_messages;
+ bool is_inline;
+ bool need_location;
+ bool can_be_added_to_attach_menu;
+ };
Result<BotData> get_bot_data(UserId user_id) const TD_WARN_UNUSED_RESULT;
+ bool is_user_online(UserId user_id, int32 tolerance = 0) const;
+
+ bool is_user_status_exact(UserId user_id) const;
+
+ bool can_report_user(UserId user_id) const;
+
bool have_user(UserId user_id) const;
bool have_min_user(UserId user_id) const;
bool have_user_force(UserId user_id);
+ bool is_dialog_info_received_from_server(DialogId dialog_id) const;
+
+ void reload_dialog_info(DialogId dialog_id, Promise<Unit> &&promise);
+
static void send_get_me_query(Td *td, Promise<Unit> &&promise);
UserId get_me(Promise<Unit> &&promise);
bool get_user(UserId user_id, int left_tries, Promise<Unit> &&promise);
- bool get_user_full(UserId user_id, Promise<Unit> &&promise);
+ void reload_user(UserId user_id, Promise<Unit> &&promise);
+ void load_user_full(UserId user_id, bool force, Promise<Unit> &&promise, const char *source);
+ FileSourceId get_user_full_file_source_id(UserId user_id);
+ void reload_user_full(UserId user_id, Promise<Unit> &&promise);
std::pair<int32, vector<const Photo *>> get_user_profile_photos(UserId user_id, int32 offset, int32 limit,
Promise<Unit> &&promise);
+ void reload_user_profile_photo(UserId user_id, int64 photo_id, Promise<Unit> &&promise);
+ FileSourceId get_user_profile_photo_file_source_id(UserId user_id, int64 photo_id);
bool have_chat(ChatId chat_id) const;
bool have_chat_force(ChatId chat_id);
bool get_chat(ChatId chat_id, int left_tries, Promise<Unit> &&promise);
- bool get_chat_full(ChatId chat_id, Promise<Unit> &&promise);
+ void reload_chat(ChatId chat_id, Promise<Unit> &&promise);
+ void load_chat_full(ChatId chat_id, bool force, Promise<Unit> &&promise, const char *source);
+ FileSourceId get_chat_full_file_source_id(ChatId chat_id);
+ void reload_chat_full(ChatId chat_id, Promise<Unit> &&promise);
+ int32 get_chat_date(ChatId chat_id) const;
+ int32 get_chat_participant_count(ChatId chat_id) const;
bool get_chat_is_active(ChatId chat_id) const;
+ ChannelId get_chat_migrated_to_channel_id(ChatId chat_id) const;
DialogParticipantStatus get_chat_status(ChatId chat_id) const;
+ DialogParticipantStatus get_chat_permissions(ChatId chat_id) const;
bool is_appointed_chat_administrator(ChatId chat_id) const;
- bool have_channel(ChannelId channel_id) const;
bool have_min_channel(ChannelId channel_id) const;
+ const MinChannel *get_min_channel(ChannelId channel_id) const;
+ void add_min_channel(ChannelId channel_id, const MinChannel &min_channel);
+
+ bool have_channel(ChannelId channel_id) const;
bool have_channel_force(ChannelId channel_id);
bool get_channel(ChannelId channel_id, int left_tries, Promise<Unit> &&promise);
- bool get_channel_full(ChannelId channel_id, Promise<Unit> &&promise);
+ void reload_channel(ChannelId channel_id, Promise<Unit> &&promise);
+ void load_channel_full(ChannelId channel_id, bool force, Promise<Unit> &&promise, const char *source);
+ FileSourceId get_channel_full_file_source_id(ChannelId channel_id);
+ void reload_channel_full(ChannelId channel_id, Promise<Unit> &&promise, const char *source);
+
+ bool is_channel_public(ChannelId channel_id) const;
bool have_secret_chat(SecretChatId secret_chat_id) const;
bool have_secret_chat_force(SecretChatId secret_chat_id);
@@ -365,116 +569,175 @@ class ContactsManager : public Actor {
bool get_secret_chat_full(SecretChatId secret_chat_id, Promise<Unit> &&promise);
ChannelType get_channel_type(ChannelId channel_id) const;
+ bool is_broadcast_channel(ChannelId channel_id) const;
+ bool is_megagroup_channel(ChannelId channel_id) const;
+ bool is_forum_channel(ChannelId channel_id) const;
int32 get_channel_date(ChannelId channel_id) const;
DialogParticipantStatus get_channel_status(ChannelId channel_id) const;
+ DialogParticipantStatus get_channel_permissions(ChannelId channel_id) const;
+ bool get_channel_is_verified(ChannelId channel_id) const;
+ int32 get_channel_participant_count(ChannelId channel_id) const;
bool get_channel_sign_messages(ChannelId channel_id) const;
+ bool get_channel_has_linked_channel(ChannelId channel_id) const;
+ bool get_channel_can_be_deleted(ChannelId channel_id) const;
+ ChannelId get_channel_linked_channel_id(ChannelId channel_id);
+ int32 get_channel_slow_mode_delay(ChannelId channel_id);
- std::pair<int32, vector<UserId>> search_among_users(const vector<UserId> &user_ids, const string &query, int32 limit);
+ void add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, Promise<Unit> &&promise);
- DialogParticipant get_chat_participant(ChatId chat_id, UserId user_id, bool force, Promise<Unit> &&promise);
+ void add_dialog_participants(DialogId dialog_id, const vector<UserId> &user_ids, Promise<Unit> &&promise);
- std::pair<int32, vector<DialogParticipant>> search_chat_participants(ChatId chat_id, const string &query, int32 limit,
- bool force, Promise<Unit> &&promise);
+ void set_dialog_participant_status(DialogId dialog_id, DialogId participant_dialog_id,
+ td_api::object_ptr<td_api::ChatMemberStatus> &&chat_member_status,
+ Promise<Unit> &&promise);
- DialogParticipant get_channel_participant(ChannelId channel_id, UserId user_id, int64 &random_id, bool force,
- Promise<Unit> &&promise);
+ void ban_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id, int32 banned_until_date,
+ bool revoke_messages, Promise<Unit> &&promise);
+
+ void on_set_channel_participant_status(ChannelId channel_id, DialogId participant_dialog_id,
+ DialogParticipantStatus status);
+
+ void get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id,
+ Promise<td_api::object_ptr<td_api::chatMember>> &&promise);
- std::pair<int32, vector<DialogParticipant>> get_channel_participants(
- ChannelId channel_id, const tl_object_ptr<td_api::SupergroupMembersFilter> &filter, int32 offset, int32 limit,
- int64 &random_id, bool force, Promise<Unit> &&promise);
+ void search_dialog_participants(DialogId dialog_id, const string &query, int32 limit, DialogParticipantFilter filter,
+ Promise<DialogParticipants> &&promise);
- DialogParticipant get_dialog_participant(ChannelId channel_id,
- tl_object_ptr<telegram_api::ChannelParticipant> &&participant_ptr) const;
+ void get_dialog_administrators(DialogId dialog_id, Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
- vector<UserId> get_dialog_administrators(DialogId chat_id, int left_tries, Promise<Unit> &&promise);
+ void get_channel_participants(ChannelId channel_id, tl_object_ptr<td_api::SupergroupMembersFilter> &&filter,
+ string additional_query, int32 offset, int32 limit, int32 additional_limit,
+ Promise<DialogParticipants> &&promise);
- int32 get_user_id_object(UserId user_id, const char *source) const;
+ int64 get_user_id_object(UserId user_id, const char *source) const;
tl_object_ptr<td_api::user> get_user_object(UserId user_id) const;
- vector<int32> get_user_ids_object(const vector<UserId> &user_ids) const;
+ vector<int64> get_user_ids_object(const vector<UserId> &user_ids, const char *source) const;
tl_object_ptr<td_api::users> get_users_object(int32 total_count, const vector<UserId> &user_ids) const;
tl_object_ptr<td_api::userFullInfo> get_user_full_info_object(UserId user_id) const;
- int32 get_basic_group_id_object(ChatId chat_id, const char *source) const;
+ int64 get_basic_group_id_object(ChatId chat_id, const char *source) const;
tl_object_ptr<td_api::basicGroup> get_basic_group_object(ChatId chat_id);
tl_object_ptr<td_api::basicGroupFullInfo> get_basic_group_full_info_object(ChatId chat_id) const;
- int32 get_supergroup_id_object(ChannelId channel_id, const char *source) const;
+ int64 get_supergroup_id_object(ChannelId channel_id, const char *source) const;
tl_object_ptr<td_api::supergroup> get_supergroup_object(ChannelId channel_id) const;
- tl_object_ptr<td_api::supergroupFullInfo> get_channel_full_info_object(ChannelId channel_id) const;
+ tl_object_ptr<td_api::supergroupFullInfo> get_supergroup_full_info_object(ChannelId channel_id) const;
int32 get_secret_chat_id_object(SecretChatId secret_chat_id, const char *source) const;
tl_object_ptr<td_api::secretChat> get_secret_chat_object(SecretChatId secret_chat_id);
void on_update_secret_chat(SecretChatId secret_chat_id, int64 access_hash, UserId user_id, SecretChatState state,
- bool is_outbound, int32 ttl, int32 date, string key_hash, int32 layer);
-
- void on_upload_profile_photo(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file);
- void on_upload_profile_photo_error(FileId file_id, Status status);
+ bool is_outbound, int32 ttl, int32 date, string key_hash, int32 layer,
+ FolderId initial_folder_id);
tl_object_ptr<td_api::chatMember> get_chat_member_object(const DialogParticipant &dialog_participant) const;
- tl_object_ptr<td_api::botInfo> get_bot_info_object(UserId user_id) const;
+ tl_object_ptr<td_api::chatInviteLinkInfo> get_chat_invite_link_info_object(const string &invite_link);
- tl_object_ptr<td_api::chatInviteLinkInfo> get_chat_invite_link_info_object(const string &invite_link) const;
+ void get_support_user(Promise<td_api::object_ptr<td_api::user>> &&promise);
- UserId get_support_user(Promise<Unit> &&promise);
+ void repair_chat_participants(ChatId chat_id);
- private:
- enum class LinkState : uint8 { Unknown, None, KnowsPhoneNumber, Contact };
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
+ static tl_object_ptr<td_api::dateRange> convert_date_range(
+ const tl_object_ptr<telegram_api::statsDateRangeDays> &obj);
+
+ static tl_object_ptr<td_api::StatisticalGraph> convert_stats_graph(tl_object_ptr<telegram_api::StatsGraph> obj);
- friend StringBuilder &operator<<(StringBuilder &string_builder, LinkState link_state);
+ static double get_percentage_value(double part, double total);
+ static tl_object_ptr<td_api::statisticalValue> convert_stats_absolute_value(
+ const tl_object_ptr<telegram_api::statsAbsValueAndPrev> &obj);
+
+ tl_object_ptr<td_api::chatStatisticsSupergroup> convert_megagroup_stats(
+ tl_object_ptr<telegram_api::stats_megagroupStats> obj);
+
+ static tl_object_ptr<td_api::chatStatisticsChannel> convert_broadcast_stats(
+ tl_object_ptr<telegram_api::stats_broadcastStats> obj);
+
+ static tl_object_ptr<td_api::messageStatistics> convert_message_stats(
+ tl_object_ptr<telegram_api::stats_messageStats> obj);
+
+ private:
struct User {
string first_name;
string last_name;
- string username;
+ Usernames usernames;
string phone_number;
int64 access_hash = -1;
+ EmojiStatus emoji_status;
+ CustomEmojiId last_sent_emoji_status;
ProfilePhoto photo;
- string restriction_reason;
+ vector<RestrictionReason> restriction_reasons;
string inline_query_placeholder;
int32 bot_info_version = -1;
int32 was_online = 0;
+ int32 local_was_online = 0;
string language_code;
- LinkState outbound = LinkState::Unknown;
- LinkState inbound = LinkState::Unknown;
+ FlatHashSet<int64> photo_ids;
+
+ FlatHashMap<DialogId, int32, DialogIdHash> online_member_dialogs; // id -> time
+
+ static constexpr uint32 CACHE_VERSION = 4;
+ uint32 cache_version = 0;
+ bool is_min_access_hash = true;
bool is_received = false;
bool is_verified = false;
+ bool is_premium = false;
+ bool is_support = false;
bool is_deleted = true;
bool is_bot = true;
bool can_join_groups = true;
bool can_read_all_group_messages = true;
bool is_inline_bot = false;
bool need_location_bot = false;
+ bool is_scam = false;
+ bool is_fake = false;
+ bool is_contact = false;
+ bool is_mutual_contact = false;
+ bool need_apply_min_photo = false;
+ bool can_be_added_to_attach_menu = false;
+ bool attach_menu_enabled = false;
+
+ bool is_photo_inited = false;
+
+ bool is_repaired = false; // whether cached value is rechecked
bool is_name_changed = true;
bool is_username_changed = true;
bool is_photo_changed = true;
- bool is_outbound_link_changed = true;
- bool is_changed = true; // have new changes not sent to the database except changes visible to the client
- bool need_send_update = true; // have new changes not sent to the client
+ bool is_phone_number_changed = true;
+ bool is_is_contact_changed = true;
+ bool is_is_deleted_changed = true;
+ bool is_changed = true; // have new changes that need to be sent to the client and database
+ bool need_save_to_database = true; // have new changes that need only to be saved to the database
bool is_status_changed = true;
+ bool is_online_status_changed = true; // whether online/offline has changed
+ bool is_update_user_sent = false;
bool is_saved = false; // is current user version being saved/is saved to the database
bool is_being_saved = false; // is current user being saved to the database
bool is_status_saved = false; // is current user status being saved/is saved to the database
- uint64 logevent_id = 0;
+ bool is_received_from_server = false; // true, if the user was received from the server and not the database
+
+ uint64 log_event_id = 0;
template <class StorerT>
void store(StorerT &storer) const;
@@ -483,41 +746,53 @@ class ContactsManager : public Actor {
void parse(ParserT &parser);
};
- struct BotInfo {
- int32 version = -1;
- string description;
- vector<std::pair<string, string>> commands;
-
- BotInfo(int32 version, string description, vector<std::pair<string, string>> &&commands)
- : version(version), description(std::move(description)), commands(std::move(commands)) {
- }
- };
-
- // do not forget to update invalidate_user_full and on_get_user_full
+ // do not forget to update drop_user_full and on_get_user_full
struct UserFull {
- vector<Photo> photos;
- int32 photo_count = -1;
- int32 photos_offset = -1;
-
- std::unique_ptr<BotInfo> bot_info;
+ Photo photo;
string about;
+ string private_forward_name;
+ string description;
+ Photo description_photo;
+ FileId description_animation_file_id;
+ vector<FileId> registered_file_ids;
+ FileSourceId file_source_id;
- int32 common_chat_count = 0;
+ vector<PremiumGiftOption> premium_gift_options;
+
+ unique_ptr<BotMenuButton> menu_button;
+ vector<BotCommand> commands;
+ AdministratorRights group_administrator_rights;
+ AdministratorRights broadcast_administrator_rights;
- bool getting_photos_now = false;
+ int32 common_chat_count = 0;
- bool is_inited = false; // photos and bot_info may be inited regardless this flag
bool is_blocked = false;
bool can_be_called = false;
+ bool supports_video_calls = false;
bool has_private_calls = false;
+ bool can_pin_messages = true;
+ bool need_phone_number_privacy_exception = false;
+ bool voice_messages_forbidden = false;
- bool is_changed = true;
+ bool is_common_chat_count_changed = true;
+ bool are_files_changed = true;
+ bool is_changed = true; // have new changes that need to be sent to the client and database
+ bool need_send_update = true; // have new changes that need only to be sent to the client
+ bool need_save_to_database = true; // have new changes that need only to be saved to the database
+ bool is_update_user_full_sent = false;
double expires_at = 0.0;
- bool is_bot_info_expired(int32 bot_info_version) const;
- bool is_expired() const;
+ bool is_expired() const {
+ return expires_at < Time::now();
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
};
struct Chat {
@@ -526,26 +801,38 @@ class ContactsManager : public Actor {
int32 participant_count = 0;
int32 date = 0;
int32 version = -1;
+ int32 default_permissions_version = -1;
+ int32 pinned_message_version = -1;
ChannelId migrated_to_channel_id;
- bool left = false;
- bool kicked = true;
+ DialogParticipantStatus status = DialogParticipantStatus::Banned(0);
+ RestrictedRights default_permissions{false, false, false, false, false, false,
+ false, false, false, false, false, false};
- bool is_creator = false;
- bool is_administrator = false;
- bool everyone_is_administrator = true;
- bool can_edit = true;
+ static constexpr uint32 CACHE_VERSION = 4;
+ uint32 cache_version = 0;
bool is_active = false;
+ bool noforwards = false;
bool is_title_changed = true;
bool is_photo_changed = true;
- bool is_changed = true; // have new changes not sent to the database except changes visible to the client
- bool need_send_update = true; // have new changes not sent to the client
+ bool is_default_permissions_changed = true;
+ bool is_status_changed = true;
+ bool is_is_active_changed = true;
+ bool is_noforwards_changed = true;
+ bool is_changed = true; // have new changes that need to be sent to the client and database
+ bool need_save_to_database = true; // have new changes that need only to be saved to the database
+ bool is_update_basic_group_sent = false;
+
+ bool is_repaired = false; // whether cached value is rechecked
bool is_saved = false; // is current chat version being saved/is saved to the database
bool is_being_saved = false; // is current chat being saved to the database
- uint64 logevent_id = 0;
+
+ bool is_received_from_server = false; // true, if the chat was received from the server and not the database
+
+ uint64 log_event_id = 0;
template <class StorerT>
void store(StorerT &storer) const;
@@ -554,44 +841,89 @@ class ContactsManager : public Actor {
void parse(ParserT &parser);
};
+ // do not forget to update drop_chat_full and on_get_chat_full
struct ChatFull {
int32 version = -1;
UserId creator_user_id;
vector<DialogParticipant> participants;
- string invite_link;
+ Photo photo;
+ vector<FileId> registered_photo_file_ids;
+ FileSourceId file_source_id;
+
+ string description;
+
+ DialogInviteLink invite_link;
+
+ vector<BotCommands> bot_commands;
+
+ bool can_set_username = false;
+
+ bool is_changed = true; // have new changes that need to be sent to the client and database
+ bool need_send_update = true; // have new changes that need only to be sent to the client
+ bool need_save_to_database = true; // have new changes that need only to be saved to the database
+ bool is_update_chat_full_sent = false;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
- bool is_changed = true;
+ template <class ParserT>
+ void parse(ParserT &parser);
};
struct Channel {
int64 access_hash = 0;
string title;
DialogPhoto photo;
- string username;
- string restriction_reason;
+ Usernames usernames;
+ vector<RestrictionReason> restriction_reasons;
DialogParticipantStatus status = DialogParticipantStatus::Banned(0);
+ RestrictedRights default_permissions{false, false, false, false, false, false,
+ false, false, false, false, false, false};
int32 date = 0;
int32 participant_count = 0;
- bool anyone_can_invite = false;
+ static constexpr uint32 CACHE_VERSION = 10;
+ uint32 cache_version = 0;
+
+ bool has_linked_channel = false;
+ bool has_location = false;
bool sign_messages = false;
+ bool is_slow_mode_enabled = false;
+ bool noforwards = false;
+ bool can_be_deleted = false;
+ bool join_to_send = false;
+ bool join_request = false;
bool is_megagroup = false;
+ bool is_gigagroup = false;
+ bool is_forum = false;
bool is_verified = false;
+ bool is_scam = false;
+ bool is_fake = false;
bool is_title_changed = true;
bool is_username_changed = true;
bool is_photo_changed = true;
+ bool is_default_permissions_changed = true;
bool is_status_changed = true;
+ bool is_has_location_changed = true;
+ bool is_noforwards_changed = true;
+ bool is_creator_changed = true;
bool had_read_access = true;
bool was_member = false;
- bool is_changed = true; // have new changes not sent to the database except changes visible to the client
- bool need_send_update = true; // have new changes not sent to the client
+ bool is_changed = true; // have new changes that need to be sent to the client and database
+ bool need_save_to_database = true; // have new changes that need only to be saved to the database
+ bool is_update_supergroup_sent = false;
+
+ bool is_repaired = false; // whether cached value is rechecked
bool is_saved = false; // is current channel version being saved/is saved to the database
bool is_being_saved = false; // is current channel being saved to the database
- uint64 logevent_id = 0;
+
+ bool is_received_from_server = false; // true, if the channel was received from the server and not the database
+
+ uint64 log_event_id = 0;
template <class StorerT>
void store(StorerT &storer) const;
@@ -600,48 +932,90 @@ class ContactsManager : public Actor {
void parse(ParserT &parser);
};
+ // do not forget to update invalidate_channel_full and on_get_chat_full
struct ChannelFull {
+ Photo photo;
+ vector<FileId> registered_photo_file_ids;
+ FileSourceId file_source_id;
+
string description;
int32 participant_count = 0;
int32 administrator_count = 0;
int32 restricted_count = 0;
int32 banned_count = 0;
- string invite_link;
- MessageId pinned_message_id;
- int64 sticker_set_id = 0; // do not forget to store along with access hash
+ DialogInviteLink invite_link;
+
+ vector<BotCommands> bot_commands;
+
+ uint32 speculative_version = 1;
+ uint32 repair_request_version = 0;
+
+ StickerSetId sticker_set_id;
+
+ ChannelId linked_channel_id;
+
+ DialogLocation location;
+
+ DcId stats_dc_id;
+
+ int32 slow_mode_delay = 0;
+ int32 slow_mode_next_send_date = 0;
MessageId migrated_from_max_message_id;
ChatId migrated_from_chat_id;
+ vector<UserId> bot_user_ids;
+
bool can_get_participants = false;
bool can_set_username = false;
bool can_set_sticker_set = false;
+ bool can_set_location = false;
+ bool can_view_statistics = false;
+ bool is_can_view_statistics_inited = false;
bool is_all_history_available = true;
+ bool can_be_deleted = false;
- bool is_changed = true;
+ bool is_slow_mode_next_send_date_changed = true;
+ bool is_changed = true; // have new changes that need to be sent to the client and database
+ bool need_send_update = true; // have new changes that need only to be sent to the client
+ bool need_save_to_database = true; // have new changes that need only to be saved to the database
+ bool is_update_channel_full_sent = false;
double expires_at = 0.0;
- bool is_expired() const;
+
+ bool is_expired() const {
+ return expires_at < Time::now();
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
};
struct SecretChat {
int64 access_hash = 0;
UserId user_id;
- SecretChatState state;
+ SecretChatState state = SecretChatState::Unknown;
string key_hash;
int32 ttl = 0;
int32 date = 0;
int32 layer = 0;
+ FolderId initial_folder_id;
bool is_outbound = false;
- bool is_changed = true; // have new changes not sent to the database except changes visible to the client
- bool need_send_update = true; // have new changes not sent to the client
+ bool is_ttl_changed = true;
+ bool is_state_changed = true;
+ bool is_changed = true; // have new changes that need to be sent to the client and database
+ bool need_save_to_database = true; // have new changes that need only to be saved to the database
bool is_saved = false; // is current secret chat version being saved/is saved to the database
bool is_being_saved = false; // is current secret chat being saved to the database
- uint64 logevent_id = 0;
+
+ uint64 log_event_id = 0;
template <class StorerT>
void store(StorerT &storer) const;
@@ -651,27 +1025,61 @@ class ContactsManager : public Actor {
};
struct InviteLinkInfo {
- ChatId chat_id; // TODO DialogId
- ChannelId channel_id;
+ // known dialog
+ DialogId dialog_id;
+
+ // unknown dialog
string title;
- DialogPhoto photo;
+ Photo photo;
+ string description;
int32 participant_count = 0;
vector<UserId> participant_user_ids;
-
+ bool creates_join_request = false;
bool is_chat = false;
bool is_channel = false;
bool is_public = false;
bool is_megagroup = false;
};
+ struct UserPhotos {
+ vector<Photo> photos;
+ int32 count = -1;
+ int32 offset = -1;
+ bool getting_now = false;
+ };
+
+ struct DialogNearby {
+ DialogId dialog_id;
+ int32 distance;
+
+ DialogNearby(DialogId dialog_id, int32 distance) : dialog_id(dialog_id), distance(distance) {
+ }
+
+ bool operator<(const DialogNearby &other) const {
+ return distance < other.distance || (distance == other.distance && dialog_id.get() < other.dialog_id.get());
+ }
+
+ bool operator==(const DialogNearby &other) const {
+ return distance == other.distance && dialog_id == other.dialog_id;
+ }
+
+ bool operator!=(const DialogNearby &other) const {
+ return !(*this == other);
+ }
+ };
+
class UserLogEvent;
class ChatLogEvent;
class ChannelLogEvent;
class SecretChatLogEvent;
- static constexpr int32 MAX_GET_PROFILE_PHOTOS = 100; // server side limit
- static constexpr size_t MAX_NAME_LENGTH = 255; // server side limit for first/last name and title
- static constexpr size_t MAX_BIO_LENGTH = 70; // server side limit
+ static constexpr int32 MAX_GET_PROFILE_PHOTOS = 100; // server side limit
+ static constexpr size_t MAX_NAME_LENGTH = 64; // server side limit for first/last name
+ static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat/channel description
+ static constexpr size_t MAX_INVITE_LINK_TITLE_LENGTH = 32; // server side limit
+ static constexpr int32 MAX_GET_CHANNEL_PARTICIPANTS = 200; // server side limit
+
+ static constexpr int32 CHANNEL_PARTICIPANT_CACHE_TIME = 1800; // some reasonable limit
static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0;
static constexpr int32 USER_FLAG_HAS_FIRST_NAME = 1 << 1;
@@ -694,28 +1102,56 @@ class ContactsManager : public Actor {
static constexpr int32 USER_FLAG_IS_INACCESSIBLE = 1 << 20;
static constexpr int32 USER_FLAG_NEED_LOCATION_BOT = 1 << 21;
static constexpr int32 USER_FLAG_HAS_LANGUAGE_CODE = 1 << 22;
+ static constexpr int32 USER_FLAG_IS_SUPPORT = 1 << 23;
+ static constexpr int32 USER_FLAG_IS_SCAM = 1 << 24;
+ static constexpr int32 USER_FLAG_NEED_APPLY_MIN_PHOTO = 1 << 25;
+ static constexpr int32 USER_FLAG_IS_FAKE = 1 << 26;
+ static constexpr int32 USER_FLAG_IS_ATTACH_MENU_BOT = 1 << 27;
+ static constexpr int32 USER_FLAG_IS_PREMIUM = 1 << 28;
+ static constexpr int32 USER_FLAG_ATTACH_MENU_ENABLED = 1 << 29;
+ static constexpr int32 USER_FLAG_HAS_EMOJI_STATUS = 1 << 30;
+ static constexpr int32 USER_FLAG_HAS_USERNAMES = 1 << 0;
static constexpr int32 USER_FULL_FLAG_IS_BLOCKED = 1 << 0;
static constexpr int32 USER_FULL_FLAG_HAS_ABOUT = 1 << 1;
static constexpr int32 USER_FULL_FLAG_HAS_PHOTO = 1 << 2;
static constexpr int32 USER_FULL_FLAG_HAS_BOT_INFO = 1 << 3;
+ static constexpr int32 USER_FULL_FLAG_HAS_PINNED_MESSAGE = 1 << 6;
+ static constexpr int32 USER_FULL_FLAG_CAN_PIN_MESSAGE = 1 << 7;
+ static constexpr int32 USER_FULL_FLAG_HAS_FOLDER_ID = 1 << 11;
+ static constexpr int32 USER_FULL_FLAG_HAS_SCHEDULED_MESSAGES = 1 << 12;
+ static constexpr int32 USER_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 14;
+ static constexpr int32 USER_FULL_FLAG_HAS_PRIVATE_FORWARD_NAME = 1 << 16;
+ static constexpr int32 USER_FULL_FLAG_HAS_GROUP_ADMINISTRATOR_RIGHTS = 1 << 17;
+ static constexpr int32 USER_FULL_FLAG_HAS_BROADCAST_ADMINISTRATOR_RIGHTS = 1 << 18;
+ static constexpr int32 USER_FULL_FLAG_HAS_VOICE_MESSAGES_FORBIDDEN = 1 << 20;
static constexpr int32 CHAT_FLAG_USER_IS_CREATOR = 1 << 0;
- static constexpr int32 CHAT_FLAG_USER_WAS_KICKED = 1 << 1;
static constexpr int32 CHAT_FLAG_USER_HAS_LEFT = 1 << 2;
- static constexpr int32 CHAT_FLAG_ADMINISTRATORS_ENABLED = 1 << 3;
- static constexpr int32 CHAT_FLAG_IS_ADMINISTRATOR = 1 << 4;
+ // static constexpr int32 CHAT_FLAG_ADMINISTRATORS_ENABLED = 1 << 3;
+ // static constexpr int32 CHAT_FLAG_IS_ADMINISTRATOR = 1 << 4;
static constexpr int32 CHAT_FLAG_IS_DEACTIVATED = 1 << 5;
static constexpr int32 CHAT_FLAG_WAS_MIGRATED = 1 << 6;
+ static constexpr int32 CHAT_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 23;
+ static constexpr int32 CHAT_FLAG_IS_GROUP_CALL_NON_EMPTY = 1 << 24;
+ static constexpr int32 CHAT_FLAG_NOFORWARDS = 1 << 25;
+
+ static constexpr int32 CHAT_FULL_FLAG_HAS_PINNED_MESSAGE = 1 << 6;
+ static constexpr int32 CHAT_FULL_FLAG_HAS_SCHEDULED_MESSAGES = 1 << 8;
+ static constexpr int32 CHAT_FULL_FLAG_HAS_FOLDER_ID = 1 << 11;
+ static constexpr int32 CHAT_FULL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 12;
+ static constexpr int32 CHAT_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 14;
+ static constexpr int32 CHAT_FULL_FLAG_HAS_PENDING_REQUEST_COUNT = 1 << 17;
+ static constexpr int32 CHAT_FULL_FLAG_HAS_AVAILABLE_REACTIONS = 1 << 18;
static constexpr int32 CHANNEL_FLAG_USER_IS_CREATOR = 1 << 0;
static constexpr int32 CHANNEL_FLAG_USER_HAS_LEFT = 1 << 2;
static constexpr int32 CHANNEL_FLAG_IS_BROADCAST = 1 << 5;
- static constexpr int32 CHANNEL_FLAG_IS_PUBLIC = 1 << 6;
+ static constexpr int32 CHANNEL_FLAG_HAS_USERNAME = 1 << 6;
static constexpr int32 CHANNEL_FLAG_IS_VERIFIED = 1 << 7;
static constexpr int32 CHANNEL_FLAG_IS_MEGAGROUP = 1 << 8;
static constexpr int32 CHANNEL_FLAG_IS_RESTRICTED = 1 << 9;
- static constexpr int32 CHANNEL_FLAG_ANYONE_CAN_INVITE = 1 << 10;
+ // static constexpr int32 CHANNEL_FLAG_ANYONE_CAN_INVITE = 1 << 10;
static constexpr int32 CHANNEL_FLAG_SIGN_MESSAGES = 1 << 11;
static constexpr int32 CHANNEL_FLAG_IS_MIN = 1 << 12;
static constexpr int32 CHANNEL_FLAG_HAS_ACCESS_HASH = 1 << 13;
@@ -723,6 +1159,19 @@ class ContactsManager : public Actor {
static constexpr int32 CHANNEL_FLAG_HAS_BANNED_RIGHTS = 1 << 15;
static constexpr int32 CHANNEL_FLAG_HAS_UNBAN_DATE = 1 << 16;
static constexpr int32 CHANNEL_FLAG_HAS_PARTICIPANT_COUNT = 1 << 17;
+ static constexpr int32 CHANNEL_FLAG_IS_SCAM = 1 << 19;
+ static constexpr int32 CHANNEL_FLAG_HAS_LINKED_CHAT = 1 << 20;
+ static constexpr int32 CHANNEL_FLAG_HAS_LOCATION = 1 << 21;
+ static constexpr int32 CHANNEL_FLAG_IS_SLOW_MODE_ENABLED = 1 << 22;
+ static constexpr int32 CHANNEL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 23;
+ static constexpr int32 CHANNEL_FLAG_IS_GROUP_CALL_NON_EMPTY = 1 << 24;
+ static constexpr int32 CHANNEL_FLAG_IS_FAKE = 1 << 25;
+ static constexpr int32 CHANNEL_FLAG_IS_GIGAGROUP = 1 << 26;
+ static constexpr int32 CHANNEL_FLAG_NOFORWARDS = 1 << 27;
+ static constexpr int32 CHANNEL_FLAG_JOIN_TO_SEND = 1 << 28;
+ static constexpr int32 CHANNEL_FLAG_JOIN_REQUEST = 1 << 29;
+ static constexpr int32 CHANNEL_FLAG_IS_FORUM = 1 << 30;
+ static constexpr int32 CHANNEL_FLAG_HAS_USERNAMES = 1 << 0;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_PARTICIPANT_COUNT = 1 << 0;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_ADMINISTRATOR_COUNT = 1 << 1;
@@ -731,10 +1180,27 @@ class ContactsManager : public Actor {
static constexpr int32 CHANNEL_FULL_FLAG_MIGRATED_FROM = 1 << 4;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_PINNED_MESSAGE = 1 << 5;
static constexpr int32 CHANNEL_FULL_FLAG_CAN_SET_USERNAME = 1 << 6;
- static constexpr int32 CHANNEL_FULL_FLAG_CAN_SET_STICKERS = 1 << 7;
+ static constexpr int32 CHANNEL_FULL_FLAG_CAN_SET_STICKER_SET = 1 << 7;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_STICKER_SET = 1 << 8;
static constexpr int32 CHANNEL_FULL_FLAG_HAS_AVAILABLE_MIN_MESSAGE_ID = 1 << 9;
static constexpr int32 CHANNEL_FULL_FLAG_IS_ALL_HISTORY_HIDDEN = 1 << 10;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_FOLDER_ID = 1 << 11;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_STATISTICS_DC_ID = 1 << 12;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_ONLINE_MEMBER_COUNT = 1 << 13;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_LINKED_CHANNEL_ID = 1 << 14;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_LOCATION = 1 << 15;
+ static constexpr int32 CHANNEL_FULL_FLAG_CAN_SET_LOCATION = 1 << 16;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_SLOW_MODE_DELAY = 1 << 17;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_SLOW_MODE_NEXT_SEND_DATE = 1 << 18;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_SCHEDULED_MESSAGES = 1 << 19;
+ static constexpr int32 CHANNEL_FULL_FLAG_CAN_VIEW_STATISTICS = 1 << 20;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_ACTIVE_GROUP_CALL = 1 << 21;
+ static constexpr int32 CHANNEL_FULL_FLAG_IS_BLOCKED = 1 << 22;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_EXPORTED_INVITE = 1 << 23;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_MESSAGE_TTL = 1 << 24;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_PENDING_REQUEST_COUNT = 1 << 28;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_DEFAULT_SEND_AS = 1 << 29;
+ static constexpr int32 CHANNEL_FULL_FLAG_HAS_AVAILABLE_REACTIONS = 1 << 30;
static constexpr int32 CHAT_INVITE_FLAG_IS_CHANNEL = 1 << 0;
static constexpr int32 CHAT_INVITE_FLAG_IS_BROADCAST = 1 << 1;
@@ -749,24 +1215,27 @@ class ContactsManager : public Actor {
static constexpr int32 ACCOUNT_UPDATE_LAST_NAME = 1 << 1;
static constexpr int32 ACCOUNT_UPDATE_ABOUT = 1 << 2;
- static const CSlice INVITE_LINK_URLS[3];
-
- static bool have_input_peer_user(const User *user, AccessRights access_rights);
- static bool have_input_peer_chat(const Chat *chat, AccessRights access_rights);
- static bool have_input_peer_channel(const Channel *c, AccessRights access_rights);
+ static bool have_input_peer_user(const User *u, AccessRights access_rights);
+ static bool have_input_peer_chat(const Chat *c, AccessRights access_rights);
+ bool have_input_peer_channel(const Channel *c, ChannelId channel_id, AccessRights access_rights,
+ bool from_linked = false) const;
static bool have_input_encrypted_peer(const SecretChat *secret_chat, AccessRights access_rights);
const User *get_user(UserId user_id) const;
User *get_user(UserId user_id);
User *get_user_force(UserId user_id);
+ User *get_user_force_impl(UserId user_id);
- User *add_user(UserId user_id);
+ User *add_user(UserId user_id, const char *source);
const UserFull *get_user_full(UserId user_id) const;
UserFull *get_user_full(UserId user_id);
+ UserFull *get_user_full_force(UserId user_id);
+
+ UserFull *add_user_full(UserId user_id);
void send_get_user_full_query(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user,
- Promise<Unit> &&promise);
+ Promise<Unit> &&promise, const char *source);
const Chat *get_chat(ChatId chat_id) const;
Chat *get_chat(ChatId chat_id);
@@ -776,20 +1245,27 @@ class ContactsManager : public Actor {
const ChatFull *get_chat_full(ChatId chat_id) const;
ChatFull *get_chat_full(ChatId chat_id);
+ ChatFull *get_chat_full_force(ChatId chat_id, const char *source);
+
+ ChatFull *add_chat_full(ChatId chat_id);
- void send_get_chat_full_query(ChatId chat_id, Promise<Unit> &&promise);
+ void send_get_chat_full_query(ChatId chat_id, Promise<Unit> &&promise, const char *source);
const Channel *get_channel(ChannelId channel_id) const;
Channel *get_channel(ChannelId channel_id);
Channel *get_channel_force(ChannelId channel_id);
- Channel *add_channel(ChannelId channel_id);
+ Channel *add_channel(ChannelId channel_id, const char *source);
const ChannelFull *get_channel_full(ChannelId channel_id) const;
- ChannelFull *get_channel_full(ChannelId channel_id);
+ const ChannelFull *get_channel_full_const(ChannelId channel_id) const;
+ ChannelFull *get_channel_full(ChannelId channel_id, bool only_local, const char *source);
+ ChannelFull *get_channel_full_force(ChannelId channel_id, bool only_local, const char *source);
- void send_get_channel_full_query(ChannelId channel_id, tl_object_ptr<telegram_api::InputChannel> &&input_channel,
- Promise<Unit> &&promise);
+ ChannelFull *add_channel_full(ChannelId channel_id);
+
+ void send_get_channel_full_query(ChannelFull *channel_full, ChannelId channel_id, Promise<Unit> &&promise,
+ const char *source);
const SecretChat *get_secret_chat(SecretChatId secret_chat_id) const;
SecretChat *get_secret_chat(SecretChatId secret_chat_id);
@@ -797,69 +1273,148 @@ class ContactsManager : public Actor {
SecretChat *add_secret_chat(SecretChatId secret_chat_id);
+ string get_user_search_text(UserId user_id) const;
+ static string get_user_search_text(const User *u);
+
static DialogParticipantStatus get_chat_status(const Chat *c);
+ DialogParticipantStatus get_chat_permissions(const Chat *c) const;
static ChannelType get_channel_type(const Channel *c);
static DialogParticipantStatus get_channel_status(const Channel *c);
+ DialogParticipantStatus get_channel_permissions(const Channel *c) const;
static bool get_channel_sign_messages(const Channel *c);
+ static bool get_channel_has_linked_channel(const Channel *c);
+ static bool get_channel_can_be_deleted(const Channel *c);
+ static bool get_channel_join_to_send(const Channel *c);
+ static bool get_channel_join_request(const Channel *c);
- void set_my_id(UserId my_id);
-
- static LinkState get_link_state(tl_object_ptr<telegram_api::ContactLink> &&link);
+ string get_channel_search_text(ChannelId channel_id) const;
+ static string get_channel_search_text(const Channel *c);
- void repair_chat_participants(ChatId chat_id);
+ void set_my_id(UserId my_id);
- static bool is_valid_username(const string &username);
+ static bool is_allowed_username(const string &username);
- bool on_update_bot_info(tl_object_ptr<telegram_api::botInfo> &&bot_info);
+ void on_set_emoji_status(EmojiStatus emoji_status, Promise<Unit> &&promise);
- void on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name, string &&username);
+ void on_update_user_name(User *u, UserId user_id, string &&first_name, string &&last_name);
+ void on_update_user_usernames(User *u, UserId user_id, Usernames &&usernames);
void on_update_user_phone_number(User *u, UserId user_id, string &&phone_number);
- void on_update_user_photo(User *u, UserId user_id, tl_object_ptr<telegram_api::UserProfilePhoto> &&photo_ptr);
+ void on_update_user_photo(User *u, UserId user_id, tl_object_ptr<telegram_api::UserProfilePhoto> &&photo,
+ const char *source);
+ void on_update_user_emoji_status(User *u, UserId user_id, EmojiStatus emoji_status);
+ void on_update_user_is_contact(User *u, UserId user_id, bool is_contact, bool is_mutual_contact);
void on_update_user_online(User *u, UserId user_id, tl_object_ptr<telegram_api::UserStatus> &&status);
- void on_update_user_links(User *u, UserId user_id, LinkState outbound, LinkState inbound);
+ void on_update_user_local_was_online(User *u, UserId user_id, int32 local_was_online);
- void on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked);
- bool on_update_user_full_bot_info(UserFull *user_full, UserId user_id, int32 bot_info_version,
- tl_object_ptr<telegram_api::botInfo> &&bot_info);
- void invalidate_user_full(UserId user_id);
+ void do_update_user_photo(User *u, UserId user_id, tl_object_ptr<telegram_api::UserProfilePhoto> &&photo,
+ const char *source);
+ void do_update_user_photo(User *u, UserId user_id, ProfilePhoto &&new_photo, bool invalidate_photo_cache,
+ const char *source);
+ void apply_pending_user_photo(User *u, UserId user_id);
+
+ void upload_profile_photo(FileId file_id, bool is_animation, double main_frame_timestamp, Promise<Unit> &&promise,
+ int reupload_count = 0, vector<int> bad_parts = {});
- void on_update_chat_left(Chat *c, ChatId chat_id, bool left, bool kicked);
+ void on_upload_profile_photo(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file);
+ void on_upload_profile_photo_error(FileId file_id, Status status);
+
+ void register_user_photo(User *u, UserId user_id, const Photo &photo);
+
+ static void on_update_user_full_is_blocked(UserFull *user_full, UserId user_id, bool is_blocked);
+ static void on_update_user_full_common_chat_count(UserFull *user_full, UserId user_id, int32 common_chat_count);
+ static void on_update_user_full_commands(UserFull *user_full, UserId user_id,
+ vector<tl_object_ptr<telegram_api::botCommand>> &&bot_commands);
+ static void on_update_user_full_menu_button(UserFull *user_full, UserId user_id,
+ tl_object_ptr<telegram_api::BotMenuButton> &&bot_menu_button);
+ void on_update_user_full_need_phone_number_privacy_exception(UserFull *user_full, UserId user_id,
+ bool need_phone_number_privacy_exception) const;
+
+ UserPhotos *add_user_photos(UserId user_id);
+ void add_profile_photo_to_cache(UserId user_id, Photo &&photo);
+ bool delete_profile_photo_from_cache(UserId user_id, int64 profile_photo_id, bool send_updates);
+ void drop_user_photos(UserId user_id, bool is_empty, bool drop_user_full_photo, const char *source);
+ void drop_user_full(UserId user_id);
+
+ void on_update_chat_status(Chat *c, ChatId chat_id, DialogParticipantStatus status);
+ static void on_update_chat_default_permissions(Chat *c, ChatId chat_id, RestrictedRights default_permissions,
+ int32 version);
void on_update_chat_participant_count(Chat *c, ChatId chat_id, int32 participant_count, int32 version,
const string &debug_str);
void on_update_chat_photo(Chat *c, ChatId chat_id, tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr);
- void on_update_chat_rights(Chat *c, ChatId chat_id, bool is_creator, bool is_administrator,
- bool everyone_is_administrator);
- void on_update_chat_title(Chat *c, ChatId chat_id, string &&title);
- void on_update_chat_active(Chat *c, ChatId chat_id, bool is_active);
- void on_update_chat_migrated_to_channel_id(Chat *c, ChatId chat_id, ChannelId migrated_to_channel_id);
+ void on_update_chat_photo(Chat *c, ChatId chat_id, DialogPhoto &&photo, bool invalidate_photo_cache);
+ static void on_update_chat_title(Chat *c, ChatId chat_id, string &&title);
+ static void on_update_chat_active(Chat *c, ChatId chat_id, bool is_active);
+ static void on_update_chat_migrated_to_channel_id(Chat *c, ChatId chat_id, ChannelId migrated_to_channel_id);
+ static void on_update_chat_noforwards(Chat *c, ChatId chat_id, bool noforwards);
+ void on_update_chat_full_photo(ChatFull *chat_full, ChatId chat_id, Photo photo);
bool on_update_chat_full_participants_short(ChatFull *chat_full, ChatId chat_id, int32 version);
void on_update_chat_full_participants(ChatFull *chat_full, ChatId chat_id, vector<DialogParticipant> participants,
- int32 version);
+ int32 version, bool from_update);
void on_update_chat_full_invite_link(ChatFull *chat_full,
- tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr);
+ tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link);
void on_update_channel_photo(Channel *c, ChannelId channel_id,
tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr);
- void on_update_channel_title(Channel *c, ChannelId channel_id, string &&title);
- void on_update_channel_username(Channel *c, ChannelId channel_id, string &&username);
+ void on_update_channel_photo(Channel *c, ChannelId channel_id, DialogPhoto &&photo, bool invalidate_photo_cache);
+ static void on_update_channel_title(Channel *c, ChannelId channel_id, string &&title);
+ void on_update_channel_usernames(Channel *c, ChannelId channel_id, Usernames &&usernames);
void on_update_channel_status(Channel *c, ChannelId channel_id, DialogParticipantStatus &&status);
+ static void on_update_channel_default_permissions(Channel *c, ChannelId channel_id,
+ RestrictedRights default_permissions);
+ static void on_update_channel_has_location(Channel *c, ChannelId channel_id, bool has_location);
+ static void on_update_channel_noforwards(Channel *c, ChannelId channel_id, bool noforwards);
+
+ void on_update_channel_bot_user_ids(ChannelId channel_id, vector<UserId> &&bot_user_ids);
+ void on_update_channel_full_photo(ChannelFull *channel_full, ChannelId channel_id, Photo photo);
void on_update_channel_full_invite_link(ChannelFull *channel_full,
- tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr);
- void on_update_channel_full_pinned_message(ChannelFull *channel_full, MessageId message_id);
+ tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link);
+ void on_update_channel_full_linked_channel_id(ChannelFull *channel_full, ChannelId channel_id,
+ ChannelId linked_channel_id);
+ void on_update_channel_full_location(ChannelFull *channel_full, ChannelId channel_id, const DialogLocation &location);
+ void on_update_channel_full_slow_mode_delay(ChannelFull *channel_full, ChannelId channel_id, int32 slow_mode_delay,
+ int32 slow_mode_next_send_date);
+ static void on_update_channel_full_slow_mode_next_send_date(ChannelFull *channel_full,
+ int32 slow_mode_next_send_date);
+ static void on_update_channel_full_bot_user_ids(ChannelFull *channel_full, ChannelId channel_id,
+ vector<UserId> &&bot_user_ids);
+
+ void toggle_username_is_active_impl(string &&username, bool is_active, Promise<Unit> &&promise);
+
+ void reorder_usernames_impl(vector<string> &&usernames, Promise<Unit> &&promise);
+
+ void on_channel_status_changed(Channel *c, ChannelId channel_id, const DialogParticipantStatus &old_status,
+ const DialogParticipantStatus &new_status);
+ void on_channel_usernames_changed(const Channel *c, ChannelId channel_id, const Usernames &old_usernames,
+ const Usernames &new_usernames);
+
+ void remove_linked_channel_id(ChannelId channel_id);
+ ChannelId get_linked_channel_id(ChannelId channel_id) const;
+
+ static bool speculative_add_count(int32 &count, int32 delta_count, int32 min_count = 0);
+
+ void speculative_add_channel_participant_count(ChannelId channel_id, int32 delta_participant_count, bool by_me);
+
+ void speculative_add_channel_user(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &new_status,
+ const DialogParticipantStatus &old_status);
+
+ void drop_chat_full(ChatId chat_id);
- void speculative_add_channel_users(ChannelId channel_id, DialogParticipantStatus status,
- DialogParticipantStatus old_status);
+ void do_invalidate_channel_full(ChannelFull *channel_full, ChannelId channel_id, bool need_drop_slow_mode_delay);
- void invalidate_chat_full(ChatId chat_id);
+ void update_user_online_member_count(User *u);
+ void update_chat_online_member_count(const ChatFull *chat_full, ChatId chat_id, bool is_from_server);
+ void update_channel_online_member_count(ChannelId channel_id, bool is_from_server);
+ void update_dialog_online_member_count(const vector<DialogParticipant> &participants, DialogId dialog_id,
+ bool is_from_server);
- void on_chat_update(telegram_api::chatEmpty &chat);
- void on_chat_update(telegram_api::chat &chat);
- void on_chat_update(telegram_api::chatForbidden &chat);
- void on_chat_update(telegram_api::channel &channel);
- void on_chat_update(telegram_api::channelForbidden &channel);
+ void on_chat_update(telegram_api::chatEmpty &chat, const char *source);
+ void on_chat_update(telegram_api::chat &chat, const char *source);
+ void on_chat_update(telegram_api::chatForbidden &chat, const char *source);
+ void on_chat_update(telegram_api::channel &channel, const char *source);
+ void on_chat_update(telegram_api::channelForbidden &channel, const char *source);
void save_user(User *u, UserId user_id, bool from_binlog);
static string get_user_database_key(UserId user_id);
@@ -869,7 +1424,7 @@ class ContactsManager : public Actor {
void on_save_user_to_database(UserId user_id, bool success);
void load_user_from_database(User *u, UserId user_id, Promise<Unit> promise);
void load_user_from_database_impl(UserId user_id, Promise<Unit> promise);
- void on_load_user_from_database(UserId user_id, string value);
+ void on_load_user_from_database(UserId user_id, string value, bool force);
void save_chat(Chat *c, ChatId chat_id, bool from_binlog);
static string get_chat_database_key(ChatId chat_id);
@@ -879,7 +1434,7 @@ class ContactsManager : public Actor {
void on_save_chat_to_database(ChatId chat_id, bool success);
void load_chat_from_database(Chat *c, ChatId chat_id, Promise<Unit> promise);
void load_chat_from_database_impl(ChatId chat_id, Promise<Unit> promise);
- void on_load_chat_from_database(ChatId chat_id, string value);
+ void on_load_chat_from_database(ChatId chat_id, string value, bool force);
void save_channel(Channel *c, ChannelId channel_id, bool from_binlog);
static string get_channel_database_key(ChannelId channel_id);
@@ -889,7 +1444,7 @@ class ContactsManager : public Actor {
void on_save_channel_to_database(ChannelId channel_id, bool success);
void load_channel_from_database(Channel *c, ChannelId channel_id, Promise<Unit> promise);
void load_channel_from_database_impl(ChannelId channel_id, Promise<Unit> promise);
- void on_load_channel_from_database(ChannelId channel_id, string value);
+ void on_load_channel_from_database(ChannelId channel_id, string value, bool force);
void save_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog);
static string get_secret_chat_database_key(SecretChatId secret_chat_id);
@@ -899,7 +1454,22 @@ class ContactsManager : public Actor {
void on_save_secret_chat_to_database(SecretChatId secret_chat_id, bool success);
void load_secret_chat_from_database(SecretChat *c, SecretChatId secret_chat_id, Promise<Unit> promise);
void load_secret_chat_from_database_impl(SecretChatId secret_chat_id, Promise<Unit> promise);
- void on_load_secret_chat_from_database(SecretChatId secret_chat_id, string value);
+ void on_load_secret_chat_from_database(SecretChatId secret_chat_id, string value, bool force);
+
+ static void save_user_full(const UserFull *user_full, UserId user_id);
+ static string get_user_full_database_key(UserId user_id);
+ static string get_user_full_database_value(const UserFull *user_full);
+ void on_load_user_full_from_database(UserId user_id, string value);
+
+ static void save_chat_full(const ChatFull *chat_full, ChatId chat_id);
+ static string get_chat_full_database_key(ChatId chat_id);
+ static string get_chat_full_database_value(const ChatFull *chat_full);
+ void on_load_chat_full_from_database(ChatId chat_id, string value);
+
+ static void save_channel_full(const ChannelFull *channel_full, ChannelId channel_id);
+ static string get_channel_full_database_key(ChannelId channel_id);
+ static string get_channel_full_database_value(const ChannelFull *channel_full);
+ void on_load_channel_full_from_database(ChannelId channel_id, string value, const char *source);
void update_user(User *u, UserId user_id, bool from_binlog = false, bool from_database = false);
void update_chat(Chat *c, ChatId chat_id, bool from_binlog = false, bool from_database = false);
@@ -907,13 +1477,18 @@ class ContactsManager : public Actor {
void update_secret_chat(SecretChat *c, SecretChatId secret_chat_id, bool from_binlog = false,
bool from_database = false);
- void update_user_full(UserFull *user_full, UserId user_id);
- void update_chat_full(ChatFull *chat_full, ChatId chat_id);
- void update_channel_full(ChannelFull *channel_full, ChannelId channel_id);
+ void update_user_full(UserFull *user_full, UserId user_id, const char *source, bool from_database = false);
+ void update_chat_full(ChatFull *chat_full, ChatId chat_id, const char *source, bool from_database = false);
+ void update_channel_full(ChannelFull *channel_full, ChannelId channel_id, const char *source,
+ bool from_database = false);
+
+ bool is_chat_full_outdated(const ChatFull *chat_full, const Chat *c, ChatId chat_id, bool only_participants) const;
+
+ bool is_user_contact(const User *u, UserId user_id, bool is_mutual) const;
- bool is_chat_full_outdated(ChatFull *chat_full, Chat *c, ChatId chat_id);
+ int32 get_user_was_online(const User *u, UserId user_id) const;
- int32 get_contacts_hash();
+ int64 get_contacts_hash();
void update_contacts_hints(const User *u, UserId user_id, bool from_database);
@@ -927,6 +1502,11 @@ class ContactsManager : public Actor {
void on_get_contacts_finished(size_t expected_contact_count);
+ void do_import_contacts(vector<Contact> contacts, int64 random_id, Promise<Unit> &&promise);
+
+ void on_import_contacts_finished(int64 random_id, vector<UserId> imported_contact_user_ids,
+ vector<int32> unimported_contact_invites);
+
void load_imported_contacts(Promise<Unit> &&promise);
void on_load_imported_contacts_from_database(string value);
@@ -936,69 +1516,245 @@ class ContactsManager : public Actor {
void on_clear_imported_contacts(vector<Contact> &&contacts, vector<size_t> contacts_unique_id,
std::pair<vector<size_t>, vector<Contact>> &&to_add, Promise<Unit> &&promise);
- static bool is_valid_invite_link(const string &invite_link);
+ void send_update_chat_member(DialogId dialog_id, UserId agent_user_id, int32 date,
+ const DialogInviteLink &invite_link, const DialogParticipant &old_dialog_participant,
+ const DialogParticipant &new_dialog_participant);
+
+ static vector<td_api::object_ptr<td_api::chatNearby>> get_chats_nearby_object(
+ const vector<DialogNearby> &dialogs_nearby);
+
+ void send_update_users_nearby() const;
+
+ void on_get_dialogs_nearby(Result<tl_object_ptr<telegram_api::Updates>> result,
+ Promise<td_api::object_ptr<td_api::chatsNearby>> &&promise);
+
+ void try_send_set_location_visibility_query();
+
+ void on_set_location_visibility_expire_date(int32 set_expire_date, int32 error_code);
+
+ void set_location_visibility_expire_date(int32 expire_date);
+
+ void on_get_is_location_visible(Result<tl_object_ptr<telegram_api::Updates>> &&result, Promise<Unit> &&promise);
+
+ void update_is_location_visible();
+
+ static bool is_channel_public(const Channel *c);
+
+ static void return_created_public_dialogs(Promise<td_api::object_ptr<td_api::chats>> &&promise,
+ const vector<ChannelId> &channel_ids);
+
+ void finish_get_created_public_dialogs(PublicDialogType type, Result<Unit> &&result);
+
+ void update_created_public_channels(Channel *c, ChannelId channel_id);
+
+ void save_created_public_channels(PublicDialogType type);
- bool update_invite_link(string &invite_link, tl_object_ptr<telegram_api::ExportedChatInvite> &&invite_link_ptr);
+ void update_created_public_broadcasts();
+
+ void export_dialog_invite_link_impl(DialogId dialog_id, string title, int32 expire_date, int32 usage_limit,
+ bool creates_join_request, bool is_permanent,
+ Promise<td_api::object_ptr<td_api::chatInviteLink>> &&promise);
+
+ void remove_dialog_access_by_invite_link(DialogId dialog_id);
+
+ Status can_manage_dialog_invite_links(DialogId dialog_id, bool creator_only = false);
+
+ bool update_permanent_invite_link(DialogInviteLink &invite_link, DialogInviteLink new_invite_link);
+
+ void add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, Promise<Unit> &&promise);
+
+ void add_channel_participant(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &old_status,
+ Promise<Unit> &&promise);
+
+ void add_channel_participants(ChannelId channel_id, const vector<UserId> &user_ids, Promise<Unit> &&promise);
+
+ vector<BotCommands> get_bot_commands(vector<tl_object_ptr<telegram_api::botInfo>> &&bot_infos,
+ const vector<DialogParticipant> *participants);
const DialogParticipant *get_chat_participant(ChatId chat_id, UserId user_id) const;
- const DialogParticipant *get_chat_participant(const ChatFull *chat_full, UserId user_id) const;
+ static const DialogParticipant *get_chat_full_participant(const ChatFull *chat_full, DialogId dialog_id);
+
+ std::pair<int32, vector<DialogId>> search_among_dialogs(const vector<DialogId> &dialog_ids, const string &query,
+ int32 limit) const;
+
+ DialogParticipants search_private_chat_participants(UserId my_user_id, UserId peer_user_id, const string &query,
+ int32 limit, DialogParticipantFilter filter) const;
+
+ void do_get_dialog_participant(DialogId dialog_id, DialogId participant_dialog_id,
+ Promise<DialogParticipant> &&promise);
+
+ void finish_get_dialog_participant(DialogParticipant &&dialog_participant,
+ Promise<td_api::object_ptr<td_api::chatMember>> &&promise);
+
+ void get_chat_participant(ChatId chat_id, UserId user_id, Promise<DialogParticipant> &&promise);
+
+ void finish_get_chat_participant(ChatId chat_id, UserId user_id, Promise<DialogParticipant> &&promise);
+
+ void get_channel_participant(ChannelId channel_id, DialogId participant_dialog_id,
+ Promise<DialogParticipant> &&promise);
+
+ void finish_get_channel_participant(ChannelId channel_id, DialogParticipant &&dialog_participant,
+ Promise<DialogParticipant> &&promise);
+
+ td_api::object_ptr<td_api::chatAdministrators> get_chat_administrators_object(
+ const vector<DialogAdministrator> &dialog_administrators);
static string get_dialog_administrators_database_key(DialogId dialog_id);
- void load_dialog_administrators(DialogId dialog_id, Promise<Unit> &&promise);
+ void on_load_dialog_administrators_from_database(DialogId dialog_id, string value,
+ Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
- void on_load_dialog_administrators_from_database(DialogId dialog_id, string value, Promise<Unit> &&promise);
+ void on_load_administrator_users_finished(DialogId dialog_id, vector<DialogAdministrator> administrators,
+ Result<> result,
+ Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
- void on_load_administrator_users_finished(DialogId dialog_id, vector<UserId> user_ids, Result<> result,
- Promise<Unit> promise);
+ void reload_dialog_administrators(DialogId dialog_id, const vector<DialogAdministrator> &dialog_administrators,
+ Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
- void reload_dialog_administrators(DialogId dialog_id, int32 hash, Promise<Unit> &&promise);
+ void on_reload_dialog_administrators(DialogId dialog_id,
+ Promise<td_api::object_ptr<td_api::chatAdministrators>> &&promise);
- tl_object_ptr<td_api::UserStatus> get_user_status_object(UserId user_id, const User *u) const;
+ void remove_dialog_suggested_action(SuggestedAction action);
- static tl_object_ptr<td_api::LinkState> get_link_state_object(LinkState link);
+ void on_dismiss_suggested_action(SuggestedAction action, Result<Unit> &&result);
- static tl_object_ptr<td_api::botInfo> get_bot_info_object(const BotInfo *bot_info);
+ static td_api::object_ptr<td_api::updateUser> get_update_unknown_user_object(UserId user_id);
+
+ td_api::object_ptr<td_api::UserStatus> get_user_status_object(UserId user_id, const User *u) const;
tl_object_ptr<td_api::user> get_user_object(UserId user_id, const User *u) const;
tl_object_ptr<td_api::userFullInfo> get_user_full_info_object(UserId user_id, const UserFull *user_full) const;
- tl_object_ptr<td_api::basicGroup> get_basic_group_object(ChatId chat_id, const Chat *chat);
+ static td_api::object_ptr<td_api::updateBasicGroup> get_update_unknown_basic_group_object(ChatId chat_id);
+
+ tl_object_ptr<td_api::basicGroup> get_basic_group_object(ChatId chat_id, const Chat *c);
+
+ tl_object_ptr<td_api::basicGroup> get_basic_group_object_const(ChatId chat_id, const Chat *c) const;
tl_object_ptr<td_api::basicGroupFullInfo> get_basic_group_full_info_object(const ChatFull *chat_full) const;
- tl_object_ptr<td_api::supergroup> get_supergroup_object(ChannelId channel_id, const Channel *channel) const;
+ td_api::object_ptr<td_api::updateSupergroup> get_update_unknown_supergroup_object(ChannelId channel_id) const;
+
+ static tl_object_ptr<td_api::supergroup> get_supergroup_object(ChannelId channel_id, const Channel *c);
- tl_object_ptr<td_api::supergroupFullInfo> get_channel_full_info_object(const ChannelFull *channel_full) const;
+ tl_object_ptr<td_api::supergroupFullInfo> get_supergroup_full_info_object(const ChannelFull *channel_full,
+ ChannelId channel_id) const;
static tl_object_ptr<td_api::SecretChatState> get_secret_chat_state_object(SecretChatState state);
+ static td_api::object_ptr<td_api::updateSecretChat> get_update_unknown_secret_chat_object(
+ SecretChatId secret_chat_id);
+
tl_object_ptr<td_api::secretChat> get_secret_chat_object(SecretChatId secret_chat_id, const SecretChat *secret_chat);
- void delete_chat_participant(ChatId chat_id, UserId user_id, Promise<Unit> &&promise);
+ tl_object_ptr<td_api::secretChat> get_secret_chat_object_const(SecretChatId secret_chat_id,
+ const SecretChat *secret_chat) const;
+
+ vector<ChannelId> get_channel_ids(vector<tl_object_ptr<telegram_api::Chat>> &&chats, const char *source);
+
+ vector<DialogId> get_dialog_ids(vector<tl_object_ptr<telegram_api::Chat>> &&chats, const char *source);
+
+ void on_create_inactive_channels(vector<ChannelId> &&channel_ids, Promise<Unit> &&promise);
+
+ void update_dialogs_for_discussion(DialogId dialog_id, bool is_suitable);
+
+ void send_edit_chat_admin_query(ChatId chat_id, UserId user_id, bool is_administrator, Promise<Unit> &&promise);
+
+ void delete_chat_participant(ChatId chat_id, UserId user_id, bool revoke_messages, Promise<Unit> &&promise);
+
+ void search_chat_participants(ChatId chat_id, const string &query, int32 limit, DialogParticipantFilter filter,
+ Promise<DialogParticipants> &&promise);
+
+ void do_search_chat_participants(ChatId chat_id, const string &query, int32 limit, DialogParticipantFilter filter,
+ Promise<DialogParticipants> &&promise);
+
+ void on_get_channel_participants(ChannelId channel_id, ChannelParticipantFilter &&filter, int32 offset, int32 limit,
+ string additional_query, int32 additional_limit,
+ tl_object_ptr<telegram_api::channels_channelParticipants> &&channel_participants,
+ Promise<DialogParticipants> &&promise);
+
+ bool have_channel_participant_cache(ChannelId channel_id) const;
+
+ void add_channel_participant_to_cache(ChannelId channel_id, const DialogParticipant &dialog_participant,
+ bool allow_replace);
+
+ void update_channel_participant_status_cache(ChannelId channel_id, DialogId participant_dialog_id,
+ DialogParticipantStatus &&dialog_participant_status);
+
+ const DialogParticipant *get_channel_participant_from_cache(ChannelId channel_id, DialogId participant_dialog_id);
+
+ void set_chat_participant_status(ChatId chat_id, UserId user_id, DialogParticipantStatus status,
+ Promise<Unit> &&promise);
+
+ void set_channel_participant_status(ChannelId channel_id, DialogId participant_dialog_id,
+ td_api::object_ptr<td_api::ChatMemberStatus> &&chat_member_status,
+ Promise<Unit> &&promise);
- void change_channel_participant_status_impl(ChannelId channel_id, UserId user_id, DialogParticipantStatus status,
- DialogParticipantStatus old_status, Promise<Unit> &&promise);
+ void set_channel_participant_status_impl(ChannelId channel_id, DialogId participant_dialog_id,
+ DialogParticipantStatus new_status, DialogParticipantStatus old_status,
+ Promise<Unit> &&promise);
- void promote_channel_participant(ChannelId channel_id, UserId user_id, DialogParticipantStatus status,
- DialogParticipantStatus old_status, Promise<Unit> &&promise);
+ void promote_channel_participant(ChannelId channel_id, UserId user_id, const DialogParticipantStatus &new_status,
+ const DialogParticipantStatus &old_status, Promise<Unit> &&promise);
- void restrict_channel_participant(ChannelId channel_id, UserId user_id, DialogParticipantStatus status,
- DialogParticipantStatus old_status, Promise<Unit> &&promise);
+ void restrict_channel_participant(ChannelId channel_id, DialogId participant_dialog_id,
+ DialogParticipantStatus &&new_status, DialogParticipantStatus &&old_status,
+ Promise<Unit> &&promise);
+
+ void transfer_channel_ownership(ChannelId channel_id, UserId user_id,
+ tl_object_ptr<telegram_api::InputCheckPasswordSRP> input_check_password,
+ Promise<Unit> &&promise);
+
+ void delete_chat(ChatId chat_id, Promise<Unit> &&promise);
+
+ void delete_channel(ChannelId channel_id, Promise<Unit> &&promise);
+
+ void get_channel_statistics_dc_id_impl(ChannelId channel_id, bool for_full_statistics, Promise<DcId> &&promise);
+
+ void send_get_channel_stats_query(DcId dc_id, ChannelId channel_id, bool is_dark,
+ Promise<td_api::object_ptr<td_api::ChatStatistics>> &&promise);
+
+ void send_get_channel_message_stats_query(DcId dc_id, FullMessageId full_message_id, bool is_dark,
+ Promise<td_api::object_ptr<td_api::messageStatistics>> &&promise);
+
+ void send_load_async_graph_query(DcId dc_id, string token, int64 x,
+ Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise);
+
+ void on_get_support_user(UserId user_id, Promise<td_api::object_ptr<td_api::user>> &&promise);
static void on_user_online_timeout_callback(void *contacts_manager_ptr, int64 user_id_long);
+ static void on_user_emoji_status_timeout_callback(void *contacts_manager_ptr, int64 user_id_long);
+
static void on_channel_unban_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long);
- template <class StorerT>
- static void store_link_state(const LinkState &link_state, StorerT &storer);
+ static void on_user_nearby_timeout_callback(void *contacts_manager_ptr, int64 user_id_long);
+
+ static void on_slow_mode_delay_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long);
+
+ static void on_invite_link_info_expire_timeout_callback(void *contacts_manager_ptr, int64 dialog_id_long);
+
+ static void on_channel_participant_cache_timeout_callback(void *contacts_manager_ptr, int64 channel_id_long);
+
+ void on_user_online_timeout(UserId user_id);
+
+ void on_user_emoji_status_timeout(UserId user_id);
+
+ void on_channel_unban_timeout(ChannelId channel_id);
+
+ void on_user_nearby_timeout(UserId user_id);
+
+ void on_slow_mode_delay_timeout(ChannelId channel_id);
+
+ void on_invite_link_info_expire_timeout(DialogId dialog_id);
- template <class ParserT>
- static void parse_link_state(LinkState &link_state, ParserT &parser);
+ void on_channel_participant_cache_timeout(ChannelId channel_id);
- void tear_down() override;
+ void start_up() final;
+
+ void tear_down() final;
Td *td_;
ActorShared<> parent_;
@@ -1006,74 +1762,150 @@ class ContactsManager : public Actor {
UserId support_user_id_;
int32 my_was_online_local_ = 0;
- std::unordered_map<UserId, User, UserIdHash> users_;
- std::unordered_map<UserId, UserFull, UserIdHash> users_full_;
- mutable std::unordered_set<UserId, UserIdHash> unknown_users_;
-
- std::unordered_map<ChatId, Chat, ChatIdHash> chats_;
- std::unordered_map<ChatId, ChatFull, ChatIdHash> chats_full_;
- mutable std::unordered_set<ChatId, ChatIdHash> unknown_chats_;
-
- std::unordered_set<ChannelId, ChannelIdHash> min_channels_;
- std::unordered_map<ChannelId, Channel, ChannelIdHash> channels_;
- std::unordered_map<ChannelId, ChannelFull, ChannelIdHash> channels_full_;
- mutable std::unordered_set<ChannelId, ChannelIdHash> unknown_channels_;
+ WaitFreeHashMap<UserId, unique_ptr<User>, UserIdHash> users_;
+ WaitFreeHashMap<UserId, unique_ptr<UserFull>, UserIdHash> users_full_;
+ WaitFreeHashMap<UserId, unique_ptr<UserPhotos>, UserIdHash> user_photos_;
+ mutable FlatHashSet<UserId, UserIdHash> unknown_users_;
+ WaitFreeHashMap<UserId, tl_object_ptr<telegram_api::UserProfilePhoto>, UserIdHash> pending_user_photos_;
+ struct UserIdPhotoIdHash {
+ uint32 operator()(const std::pair<UserId, int64> &pair) const {
+ return UserIdHash()(pair.first) * 2023654985u + Hash<int64>()(pair.second);
+ }
+ };
+ WaitFreeHashMap<std::pair<UserId, int64>, FileSourceId, UserIdPhotoIdHash> user_profile_photo_file_source_ids_;
+ FlatHashMap<int64, FileId> my_photo_file_id_;
+ WaitFreeHashMap<UserId, FileSourceId, UserIdHash> user_full_file_source_ids_;
+
+ WaitFreeHashMap<ChatId, unique_ptr<Chat>, ChatIdHash> chats_;
+ WaitFreeHashMap<ChatId, unique_ptr<ChatFull>, ChatIdHash> chats_full_;
+ mutable FlatHashSet<ChatId, ChatIdHash> unknown_chats_;
+ WaitFreeHashMap<ChatId, FileSourceId, ChatIdHash> chat_full_file_source_ids_;
+
+ WaitFreeHashMap<ChannelId, unique_ptr<MinChannel>, ChannelIdHash> min_channels_;
+ WaitFreeHashMap<ChannelId, unique_ptr<Channel>, ChannelIdHash> channels_;
+ WaitFreeHashMap<ChannelId, unique_ptr<ChannelFull>, ChannelIdHash> channels_full_;
+ mutable FlatHashSet<ChannelId, ChannelIdHash> unknown_channels_;
+ WaitFreeHashSet<ChannelId, ChannelIdHash> invalidated_channels_full_;
+ WaitFreeHashMap<ChannelId, FileSourceId, ChannelIdHash> channel_full_file_source_ids_;
+
+ WaitFreeHashMap<SecretChatId, unique_ptr<SecretChat>, SecretChatIdHash> secret_chats_;
+ mutable FlatHashSet<SecretChatId, SecretChatIdHash> unknown_secret_chats_;
+
+ FlatHashMap<UserId, vector<SecretChatId>, UserIdHash> secret_chats_with_user_;
+
+ struct DialogAccessByInviteLink {
+ FlatHashSet<string> invite_links;
+ int32 accessible_before = 0;
+ };
+ FlatHashMap<string, unique_ptr<InviteLinkInfo>> invite_link_infos_;
+ FlatHashMap<DialogId, DialogAccessByInviteLink, DialogIdHash> dialog_access_by_invite_link_;
- std::unordered_map<SecretChatId, SecretChat, SecretChatIdHash> secret_chats_;
- mutable std::unordered_set<SecretChatId, SecretChatIdHash> unknown_secret_chats_;
+ bool created_public_channels_inited_[2] = {false, false};
+ vector<ChannelId> created_public_channels_[2];
+ vector<Promise<td_api::object_ptr<td_api::chats>>> get_created_public_channels_queries_[2];
- std::unordered_map<UserId, vector<SecretChatId>, UserIdHash> secret_chats_with_user_;
+ bool dialogs_for_discussion_inited_ = false;
+ vector<DialogId> dialogs_for_discussion_;
- std::unordered_map<ChatId, string, ChatIdHash> chat_invite_links_; // in-memory cache for invite links
- std::unordered_map<ChannelId, string, ChannelIdHash> channel_invite_links_; // in-memory cache for invite links
- std::unordered_map<string, unique_ptr<InviteLinkInfo>> invite_link_infos_;
+ bool inactive_channel_ids_inited_ = false;
+ vector<ChannelId> inactive_channel_ids_;
- bool created_public_channels_inited_ = false;
- vector<ChannelId> created_public_channels_;
+ FlatHashMap<UserId, vector<Promise<Unit>>, UserIdHash> load_user_from_database_queries_;
+ FlatHashSet<UserId, UserIdHash> loaded_from_database_users_;
+ FlatHashSet<UserId, UserIdHash> unavailable_user_fulls_;
- std::unordered_map<UserId, vector<Promise<Unit>>, UserIdHash> load_user_from_database_queries_;
- std::unordered_set<UserId, UserIdHash> loaded_from_database_users_;
+ FlatHashMap<ChatId, vector<Promise<Unit>>, ChatIdHash> load_chat_from_database_queries_;
+ FlatHashSet<ChatId, ChatIdHash> loaded_from_database_chats_;
+ FlatHashSet<ChatId, ChatIdHash> unavailable_chat_fulls_;
- std::unordered_map<ChatId, vector<Promise<Unit>>, ChatIdHash> load_chat_from_database_queries_;
- std::unordered_set<ChatId, ChatIdHash> loaded_from_database_chats_;
+ FlatHashMap<ChannelId, vector<Promise<Unit>>, ChannelIdHash> load_channel_from_database_queries_;
+ FlatHashSet<ChannelId, ChannelIdHash> loaded_from_database_channels_;
+ FlatHashSet<ChannelId, ChannelIdHash> unavailable_channel_fulls_;
- std::unordered_map<ChannelId, vector<Promise<Unit>>, ChannelIdHash> load_channel_from_database_queries_;
- std::unordered_set<ChannelId, ChannelIdHash> loaded_from_database_channels_;
+ FlatHashMap<SecretChatId, vector<Promise<Unit>>, SecretChatIdHash> load_secret_chat_from_database_queries_;
+ FlatHashSet<SecretChatId, SecretChatIdHash> loaded_from_database_secret_chats_;
- std::unordered_map<SecretChatId, vector<Promise<Unit>>, SecretChatIdHash> load_secret_chat_from_database_queries_;
- std::unordered_set<SecretChatId, SecretChatIdHash> loaded_from_database_secret_chats_;
+ QueryCombiner get_user_full_queries_{"GetUserFullCombiner", 2.0};
+ QueryCombiner get_chat_full_queries_{"GetChatFullCombiner", 2.0};
- std::unordered_map<UserId, vector<Promise<Unit>>, UserIdHash> get_user_full_queries_;
- std::unordered_map<ChatId, vector<Promise<Unit>>, ChatIdHash> get_chat_full_queries_;
- std::unordered_map<ChannelId, vector<Promise<Unit>>, ChannelIdHash> get_channel_full_queries_;
+ FlatHashMap<DialogId, vector<DialogAdministrator>, DialogIdHash> dialog_administrators_;
- std::unordered_map<DialogId, vector<UserId>, DialogIdHash> dialog_administrators_;
+ FlatHashMap<DialogId, vector<SuggestedAction>, DialogIdHash> dialog_suggested_actions_;
+ FlatHashMap<DialogId, vector<Promise<Unit>>, DialogIdHash> dismiss_suggested_action_queries_;
class UploadProfilePhotoCallback;
std::shared_ptr<UploadProfilePhotoCallback> upload_profile_photo_callback_;
- std::unordered_map<FileId, Promise<Unit>, FileIdHash> uploaded_profile_photos_; // file_id -> promise
+ struct UploadedProfilePhoto {
+ double main_frame_timestamp;
+ bool is_animation;
+ int reupload_count;
+ Promise<Unit> promise;
+
+ UploadedProfilePhoto(double main_frame_timestamp, bool is_animation, int32 reupload_count, Promise<Unit> promise)
+ : main_frame_timestamp(main_frame_timestamp)
+ , is_animation(is_animation)
+ , reupload_count(reupload_count)
+ , promise(std::move(promise)) {
+ }
+ };
+ FlatHashMap<FileId, UploadedProfilePhoto, FileIdHash> uploaded_profile_photos_; // file_id -> promise
+
+ struct ImportContactsTask {
+ Promise<Unit> promise_;
+ vector<Contact> input_contacts_;
+ vector<UserId> imported_user_ids_;
+ vector<int32> unimported_contact_invites_;
+ };
+ FlatHashMap<int64, unique_ptr<ImportContactsTask>> import_contact_tasks_;
+
+ FlatHashMap<int64, std::pair<vector<UserId>, vector<int32>>> imported_contacts_;
- std::unordered_map<int64, std::pair<vector<UserId>, vector<int32>>> imported_contacts_;
+ FlatHashMap<ChannelId, vector<DialogParticipant>, ChannelIdHash> cached_channel_participants_;
- std::unordered_map<int64, DialogParticipant> received_channel_participant_;
- std::unordered_map<int64, std::pair<int32, vector<DialogParticipant>>> received_channel_participants_;
+ FlatHashMap<string, UserId> resolved_phone_numbers_;
- std::unordered_map<int64, std::pair<int32, vector<UserId>>>
- found_blocked_users_; // random_id -> [total_count, [user_id]...]
+ // bot-administrators only
+ struct ChannelParticipantInfo {
+ DialogParticipant participant_;
+
+ int32 last_access_date_ = 0;
+ };
+ struct ChannelParticipants {
+ FlatHashMap<DialogId, ChannelParticipantInfo, DialogIdHash> participants_;
+ };
+ FlatHashMap<ChannelId, ChannelParticipants, ChannelIdHash> channel_participants_;
bool are_contacts_loaded_ = false;
int32 next_contacts_sync_date_ = 0;
- Hints contacts_hints_; // search contacts by first name, last name and username
+ Hints contacts_hints_; // search contacts by first name, last name and usernames
vector<Promise<Unit>> load_contacts_queries_;
- MultiPromiseActor load_contact_users_multipromise_;
+ MultiPromiseActor load_contact_users_multipromise_{"LoadContactUsersMultiPromiseActor"};
int32 saved_contact_count_ = -1;
+ int32 was_online_local_ = 0;
+ int32 was_online_remote_ = 0;
+
bool are_imported_contacts_loaded_ = false;
vector<Promise<Unit>> load_imported_contacts_queries_;
- MultiPromiseActor load_imported_contact_users_multipromise_;
+ MultiPromiseActor load_imported_contact_users_multipromise_{"LoadImportedContactUsersMultiPromiseActor"};
vector<Contact> all_imported_contacts_;
bool are_imported_contacts_changing_ = false;
+ bool need_clear_imported_contacts_ = false;
+
+ vector<DialogNearby> users_nearby_;
+ vector<DialogNearby> channels_nearby_;
+ FlatHashSet<UserId, UserIdHash> all_users_nearby_;
+
+ int32 location_visibility_expire_date_ = 0;
+ int32 pending_location_visibility_expire_date_ = -1;
+ bool is_set_location_visibility_request_sent_ = false;
+ Location last_user_location_;
+
+ WaitFreeHashMap<ChannelId, ChannelId, ChannelIdHash> linked_channel_ids_;
+
+ WaitFreeHashSet<UserId, UserIdHash> restricted_user_ids_;
+ WaitFreeHashSet<ChannelId, ChannelIdHash> restricted_channel_ids_;
vector<Contact> next_all_imported_contacts_;
vector<size_t> imported_contacts_unique_id_;
@@ -1082,10 +1914,13 @@ class ContactsManager : public Actor {
vector<UserId> imported_contact_user_ids_; // result of change_imported_contacts
vector<int32> unimported_contact_invites_; // result of change_imported_contacts
- MultiTimeout user_online_timeout_;
- MultiTimeout channel_unban_timeout_;
-
- class OnChatUpdate;
+ MultiTimeout user_online_timeout_{"UserOnlineTimeout"};
+ MultiTimeout user_emoji_status_timeout_{"UserEmojiStatusTimeout"};
+ MultiTimeout channel_unban_timeout_{"ChannelUnbanTimeout"};
+ MultiTimeout user_nearby_timeout_{"UserNearbyTimeout"};
+ MultiTimeout slow_mode_delay_timeout_{"SlowModeDelayTimeout"};
+ MultiTimeout invite_link_info_expire_timeout_{"InviteLinkInfoExpireTimeout"};
+ MultiTimeout channel_participant_cache_timeout_{"ChannelParticipantCacheTimeout"};
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CountryInfoManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/CountryInfoManager.cpp
new file mode 100644
index 0000000000..3d222e5cbc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/CountryInfoManager.cpp
@@ -0,0 +1,543 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/CountryInfoManager.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/LanguagePackManager.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/Gzip.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/Time.h"
+#include "td/utils/tl_parsers.h"
+
+namespace td {
+
+class GetNearestDcQuery final : public Td::ResultHandler {
+ Promise<string> promise_;
+
+ public:
+ explicit GetNearestDcQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create_unauth(telegram_api::help_getNearestDc()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_getNearestDc>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ promise_.set_value(std::move(result->country_));
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status) && status.message() != "BOT_METHOD_INVALID") {
+ LOG(ERROR) << "GetNearestDc returned " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetCountriesListQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::help_CountriesList>> promise_;
+
+ public:
+ explicit GetCountriesListQuery(Promise<tl_object_ptr<telegram_api::help_CountriesList>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(const string &language_code, int32 hash) {
+ send_query(G()->net_query_creator().create_unauth(telegram_api::help_getCountriesList(language_code, hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ // LOG(ERROR) << base64url_encode(gzencode(packet, 0.9));
+ auto result_ptr = fetch_result<telegram_api::help_getCountriesList>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "GetCountriesList returned " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+struct CountryInfoManager::CallingCodeInfo {
+ string calling_code;
+ vector<string> prefixes;
+ vector<string> patterns;
+};
+
+struct CountryInfoManager::CountryInfo {
+ string country_code;
+ string default_name;
+ string name;
+ vector<CallingCodeInfo> calling_codes;
+ bool is_hidden = false;
+
+ td_api::object_ptr<td_api::countryInfo> get_country_info_object() const {
+ return td_api::make_object<td_api::countryInfo>(
+ country_code, name.empty() ? default_name : name, default_name, is_hidden,
+ transform(calling_codes, [](const CallingCodeInfo &info) { return info.calling_code; }));
+ }
+};
+
+struct CountryInfoManager::CountryList {
+ vector<CountryInfo> countries;
+ int32 hash = 0;
+ double next_reload_time = 0.0;
+
+ td_api::object_ptr<td_api::countries> get_countries_object() const {
+ return td_api::make_object<td_api::countries>(
+ transform(countries, [](const CountryInfo &info) { return info.get_country_info_object(); }));
+ }
+};
+
+CountryInfoManager::CountryInfoManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+}
+
+CountryInfoManager::~CountryInfoManager() = default;
+
+void CountryInfoManager::start_up() {
+ std::lock_guard<std::mutex> country_lock(country_mutex_);
+ manager_count_++;
+}
+
+void CountryInfoManager::tear_down() {
+ parent_.reset();
+
+ std::lock_guard<std::mutex> country_lock(country_mutex_);
+ manager_count_--;
+ if (manager_count_ == 0 && !countries_.empty()) {
+ LOG(INFO) << "Clear country info";
+ countries_.clear();
+ }
+}
+
+string CountryInfoManager::get_main_language_code() {
+ return to_lower(td_->language_pack_manager_.get_actor_unsafe()->get_main_language_code());
+}
+
+void CountryInfoManager::get_countries(Promise<td_api::object_ptr<td_api::countries>> &&promise) {
+ do_get_countries(get_main_language_code(), false, std::move(promise));
+}
+
+void CountryInfoManager::do_get_countries(string language_code, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::countries>> &&promise) {
+ if (is_recursive) {
+ auto main_language_code = get_main_language_code();
+ if (language_code != main_language_code) {
+ language_code = std::move(main_language_code);
+ is_recursive = false;
+ }
+ }
+
+ {
+ std::lock_guard<std::mutex> country_lock(country_mutex_);
+ auto list = get_country_list(this, language_code);
+ if (list != nullptr) {
+ return promise.set_value(list->get_countries_object());
+ }
+ }
+
+ if (is_recursive) {
+ return promise.set_error(Status::Error(500, "Requested data is inaccessible"));
+ }
+ if (language_code.empty()) {
+ return promise.set_error(Status::Error(400, "Invalid language code specified"));
+ }
+ load_country_list(language_code, 0,
+ PromiseCreator::lambda([actor_id = actor_id(this), language_code,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ send_closure(actor_id, &CountryInfoManager::do_get_countries, std::move(language_code), true,
+ std::move(promise));
+ }));
+}
+
+void CountryInfoManager::get_phone_number_info(string phone_number_prefix,
+ Promise<td_api::object_ptr<td_api::phoneNumberInfo>> &&promise) {
+ clean_phone_number(phone_number_prefix);
+ if (phone_number_prefix.empty()) {
+ return promise.set_value(td_api::make_object<td_api::phoneNumberInfo>(nullptr, string(), string()));
+ }
+ do_get_phone_number_info(std::move(phone_number_prefix), get_main_language_code(), false, std::move(promise));
+}
+
+void CountryInfoManager::do_get_phone_number_info(string phone_number_prefix, string language_code, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::phoneNumberInfo>> &&promise) {
+ if (is_recursive) {
+ auto main_language_code = get_main_language_code();
+ if (language_code != main_language_code) {
+ language_code = std::move(main_language_code);
+ is_recursive = false;
+ }
+ }
+ {
+ std::lock_guard<std::mutex> country_lock(country_mutex_);
+ auto list = get_country_list(this, language_code);
+ if (list != nullptr) {
+ return promise.set_value(get_phone_number_info_object(list, phone_number_prefix));
+ }
+ }
+
+ if (is_recursive) {
+ return promise.set_error(Status::Error(500, "Requested data is inaccessible"));
+ }
+ if (language_code.empty()) {
+ return promise.set_error(Status::Error(400, "Invalid language code specified"));
+ }
+ load_country_list(language_code, 0,
+ PromiseCreator::lambda([actor_id = actor_id(this), phone_number_prefix, language_code,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ send_closure(actor_id, &CountryInfoManager::do_get_phone_number_info,
+ std::move(phone_number_prefix), std::move(language_code), true, std::move(promise));
+ }));
+}
+
+td_api::object_ptr<td_api::phoneNumberInfo> CountryInfoManager::get_phone_number_info_sync(const string &language_code,
+ string phone_number_prefix) {
+ clean_phone_number(phone_number_prefix);
+ if (phone_number_prefix.empty()) {
+ return td_api::make_object<td_api::phoneNumberInfo>(nullptr, string(), string());
+ }
+
+ std::lock_guard<std::mutex> country_lock(country_mutex_);
+ auto list = get_country_list(nullptr, language_code);
+ if (list == nullptr) {
+ list = get_country_list(nullptr, "en");
+ }
+
+ return get_phone_number_info_object(list, phone_number_prefix);
+}
+
+td_api::object_ptr<td_api::phoneNumberInfo> CountryInfoManager::get_phone_number_info_object(const CountryList *list,
+ Slice phone_number) {
+ CHECK(list != nullptr);
+ const CountryInfo *best_country = nullptr;
+ const CallingCodeInfo *best_calling_code = nullptr;
+ size_t best_length = 0;
+ bool is_prefix = false; // is phone number a prefix of a valid country_code + prefix
+ for (auto &country : list->countries) {
+ for (auto &calling_code : country.calling_codes) {
+ if (begins_with(phone_number, calling_code.calling_code)) {
+ auto calling_code_size = calling_code.calling_code.size();
+ for (auto &prefix : calling_code.prefixes) {
+ if (begins_with(prefix, phone_number.substr(calling_code_size))) {
+ is_prefix = true;
+ }
+ if (calling_code_size + prefix.size() > best_length &&
+ begins_with(phone_number.substr(calling_code_size), prefix)) {
+ best_country = &country;
+ best_calling_code = &calling_code;
+ best_length = calling_code_size + prefix.size();
+ }
+ }
+ }
+ if (begins_with(calling_code.calling_code, phone_number)) {
+ is_prefix = true;
+ }
+ }
+ }
+ if (best_country == nullptr) {
+ return td_api::make_object<td_api::phoneNumberInfo>(nullptr, is_prefix ? phone_number.str() : string(),
+ is_prefix ? string() : phone_number.str());
+ }
+
+ Slice formatted_part = phone_number.substr(best_calling_code->calling_code.size());
+ string formatted_phone_number;
+ size_t max_matched_digits = 0;
+ for (auto &pattern : best_calling_code->patterns) {
+ string result;
+ size_t current_pattern_pos = 0;
+ bool is_failed_match = false;
+ size_t matched_digits = 0;
+ for (auto &c : formatted_part) {
+ while (current_pattern_pos < pattern.size() && pattern[current_pattern_pos] != 'X' &&
+ !is_digit(pattern[current_pattern_pos])) {
+ result += pattern[current_pattern_pos++];
+ }
+ if (current_pattern_pos == pattern.size()) {
+ // result += ' ';
+ }
+ if (current_pattern_pos >= pattern.size() || pattern[current_pattern_pos] == 'X') {
+ result += c;
+ current_pattern_pos++;
+ } else {
+ CHECK(is_digit(pattern[current_pattern_pos]));
+ if (c == pattern[current_pattern_pos]) {
+ matched_digits++;
+ result += c;
+ current_pattern_pos++;
+ } else {
+ is_failed_match = true;
+ break;
+ }
+ }
+ }
+ for (size_t i = current_pattern_pos; i < pattern.size(); i++) {
+ if (is_digit(pattern[i])) {
+ is_failed_match = true;
+ }
+ }
+ if (!is_failed_match && matched_digits >= max_matched_digits) {
+ max_matched_digits = matched_digits;
+ while (current_pattern_pos < pattern.size()) {
+ if (pattern[current_pattern_pos] == 'X') {
+ result.push_back('-');
+ } else {
+ CHECK(!is_digit(pattern[current_pattern_pos]));
+ result.push_back(' ');
+ }
+ current_pattern_pos++;
+ }
+ formatted_phone_number = std::move(result);
+ }
+ }
+
+ return td_api::make_object<td_api::phoneNumberInfo>(
+ best_country->get_country_info_object(), best_calling_code->calling_code,
+ formatted_phone_number.empty() ? formatted_part.str() : formatted_phone_number);
+}
+
+void CountryInfoManager::get_current_country_code(Promise<string> &&promise) {
+ td_->create_handler<GetNearestDcQuery>(std::move(promise))->send();
+}
+
+void CountryInfoManager::load_country_list(string language_code, int32 hash, Promise<Unit> &&promise) {
+ auto &queries = pending_load_country_queries_[language_code];
+ if (!promise && !queries.empty()) {
+ return;
+ }
+ queries.push_back(std::move(promise));
+ if (queries.size() == 1) {
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), language_code](Result<tl_object_ptr<telegram_api::help_CountriesList>> &&result) {
+ send_closure(actor_id, &CountryInfoManager::on_get_country_list, language_code, std::move(result));
+ });
+ td_->create_handler<GetCountriesListQuery>(std::move(query_promise))->send(language_code, hash);
+ }
+}
+
+void CountryInfoManager::on_get_country_list(const string &language_code,
+ Result<tl_object_ptr<telegram_api::help_CountriesList>> r_country_list) {
+ auto query_it = pending_load_country_queries_.find(language_code);
+ CHECK(query_it != pending_load_country_queries_.end());
+ auto promises = std::move(query_it->second);
+ CHECK(!promises.empty());
+ pending_load_country_queries_.erase(query_it);
+
+ if (r_country_list.is_error()) {
+ {
+ std::lock_guard<std::mutex> country_lock(country_mutex_);
+ auto it = countries_.find(language_code);
+ if (it != countries_.end()) {
+ // don't try to reload countries more often than once in 1-2 minutes
+ it->second->next_reload_time = max(Time::now() + Random::fast(60, 120), it->second->next_reload_time);
+
+ // if we have data for the language, then we don't need to fail promises
+ set_promises(promises);
+ return;
+ }
+ }
+ fail_promises(promises, r_country_list.move_as_error());
+ return;
+ }
+
+ {
+ std::lock_guard<std::mutex> country_lock(country_mutex_);
+ on_get_country_list_impl(language_code, r_country_list.move_as_ok());
+ }
+
+ set_promises(promises);
+}
+
+void CountryInfoManager::on_get_country_list_impl(const string &language_code,
+ tl_object_ptr<telegram_api::help_CountriesList> country_list) {
+ CHECK(country_list != nullptr);
+ LOG(DEBUG) << "Receive " << to_string(country_list);
+ auto &countries = countries_[language_code];
+ switch (country_list->get_id()) {
+ case telegram_api::help_countriesListNotModified::ID:
+ if (countries == nullptr) {
+ LOG(ERROR) << "Receive countriesListNotModified for unknown list with language code " << language_code;
+ countries_.erase(language_code);
+ } else {
+ LOG(INFO) << "List of countries with language code " << language_code << " is not modified";
+ countries->next_reload_time = Time::now() + Random::fast(86400, 2 * 86400);
+ }
+ break;
+ case telegram_api::help_countriesList::ID: {
+ auto list = move_tl_object_as<telegram_api::help_countriesList>(country_list);
+ if (countries == nullptr) {
+ countries = make_unique<CountryList>();
+ } else {
+ countries->countries.clear();
+ }
+ for (auto &c : list->countries_) {
+ CountryInfo info;
+ info.country_code = std::move(c->iso2_);
+ info.default_name = std::move(c->default_name_);
+ info.name = std::move(c->name_);
+ info.is_hidden = c->hidden_;
+ for (auto &code : c->country_codes_) {
+ auto r_calling_code = to_integer_safe<int32>(code->country_code_);
+ if (r_calling_code.is_error() || r_calling_code.ok() <= 0) {
+ LOG(ERROR) << "Receive invalid calling code " << code->country_code_ << " for country "
+ << info.country_code;
+ } else {
+ CallingCodeInfo calling_code_info;
+ calling_code_info.calling_code = std::move(code->country_code_);
+ calling_code_info.prefixes = std::move(code->prefixes_);
+ if (calling_code_info.prefixes.empty()) {
+ calling_code_info.prefixes.resize(1);
+ }
+ calling_code_info.patterns = std::move(code->patterns_);
+ info.calling_codes.push_back(std::move(calling_code_info));
+ }
+ }
+ if (info.calling_codes.empty()) {
+ LOG(ERROR) << "Receive empty list of calling codes for " << info.country_code;
+ continue;
+ }
+
+ countries->countries.push_back(std::move(info));
+ }
+ countries->hash = list->hash_;
+ countries->next_reload_time = Time::now() + Random::fast(86400, 2 * 86400);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+const CountryInfoManager::CountryList *CountryInfoManager::get_country_list(CountryInfoManager *manager,
+ const string &language_code) {
+ auto it = countries_.find(language_code);
+ if (it == countries_.end()) {
+ if (language_code == "en") {
+ static const BufferSlice en = gzdecode(
+ base64url_decode(
+ "eJyNW0tz48iRrhb1lrpn2h6PfXBMMGIjvN6IHQeJN458iyJBsQlSr1uRrCFrBAIaEJAs_QHf9ifsxQeHD77t_oK27_"
+ "4Pvvmyl73sbROkSIBEstAR3S01VVmVlZX55ZdZpf8M__6HX_z1v3_9T0LIv_zHHz7DF7JXqpKjkjv2fJ9GP3sHn_"
+ "3P33LlPfiak3Vt9dnx7W1-8WdDtka-Gbg8YON8yafDfG3GfRqwOfwsNZepF1efnS0mWvy9jeeqk7PSD5Mpdfk8oO62_"
+ "J4pk4T8eoJYvkE-ltyAT0Ka_02-TP1hOKZpPfaLkmYk9DteKbKxryY5LrmTkDsORfYSzaFkztEGuzpD2A5iV1UV28ICWX_"
+ "GUFldWX12Gssm1r0ih6C7B5qnZCVFEduwR05K_oSBGd0t2y1-riqJdWzyoTRjPh9RN2_TmZdeb7-oGdl26sNew3ngp_"
+ "e6p8RnfrqS3djrAPSNZKnDkbPe04oJn1lIb9n5hhyU_HCIyOYkUxfrfU9OS6_MH1L-I3URfzcV4RmXS-SbsjeHIwZ3vWD-K5t4Twuzp-"
+ "eSDV145uUyOY48no49LPbAXxUt6xzKVXJapu7EoWM2n6Z1MIyCWIcaOSozZ8LDWeocZGk93_ntCki2sKRcJ-fl0H8AE-"
+ "TrdO5hdpAkbdOmyz_xHA2wQ-hMqM-37UiWcWfGYy9AXzr1KU9hDWCVvPrs5DY2VizbBNnQD90xT-"
+ "uobp1VftNny5fkoAyR7aL7M8U2tiIb-zNAtvQZK0ox84w75GMZ1GY8X6V-OJ9Th84QPbR4__g8V6CH5_"
+ "AnBJ9UsxjvYe32CdlP5Luy51Lus3_P29wN8jWIYBrwcA6BYNMhTZ-baibOrUcOyz595U463lV1_dn7FVykzs5enDud0TkWJ1KmDfuw_"
+ "jQM0JjXVfH53YB_esH8meJxLmkZcX63iLHo6JC8IM4p5XvQmzn8lSH2LRTX4yolclgB_cab55CDr--KS51_"
+ "G8koBTmnGnpONwo5Q1JzkqyB7xdyWkEB_"
+ "5Fyum7kJPhekYswv5bTC2bOLMDnhhTFMeQiMycZZk5W5JysqTmlqMFYPacWzZyqGDmtKOc0BeYvqDmjoINsNMaAMfAX1lGLSk7VYaxRz"
+ "BkgY-gwD6yjyfB9bMfzRJ7bOMdKlXyseJAo87-tstnv8j32-Lt_w85EkYV2rdTJNxXImJCD8qUflukwmgs9X1mMX5UGOX_"
+ "T6U0dTB9JrM8FObOfeQCpyaHuOJ1TkzkRk2-SD5XP_xWw_Phfm08ehCmGU-pmnOXTcVZpRXvxHvLNeaTHdl5a-"
+ "J1mSPH4NjmoTLnDkDyuauI8XrHIcYUCGfE8F7f72h9QPK90Fmtvc57F2oaWxJTE6kn5K1jfc7zZMJV3lvrr2_"
+ "kP8cceOa14gIT5HvhQeg8QQ-I9DMh-ZUFlkPWTPArB5Mo1rE0fWf6a-"
+ "WPsvGVDiIkVwLVK6NPPf6LbefvdG36vPntnJuXuAGteHgHOEM6jZpzZPfjpKxtNo2gLhw4fpedQpELC19N8twqcpQH5lLov6TgxF_"
+ "Ptv53ZinxG-z4rauv_nhX1-HtZKxTzStE0kmtckuPqj3zohQFHfVOVhZhQbZGjKnNn1H9I6xjnO9RGVYiLqjfjbuRRGC_"
+ "UNZ1k5LvqFfmwmuMN2aLxuY15jIK5Y57EGOkLxii7xsT63Ed11YQh9UJOKspbcZbmmbUKOaqNQqDKPsJdxFhfA3-"
+ "pzQMPr8sk4VnUGuSgNnl5DJD4lApJfEhCTEK-B2v7PPAZsu-Yc53EtCEha5MD-5FinHNPVsR1da1PjmvBlHuPKLaB_4rr-"
+ "jpw5Tp3t3MRWca4EY-7JPt1_iMaI5puCn2i3iJf16nzEC0iyjdqoRDLWOTU4iPfc9mcI5xTM2NOVL8i7-"
+ "vU91g8OXIGa4w82rZhHThr3afuKI2te7KcyC_"
+ "5DaddyzdK5KBBhx5aMyibfPtNOJYtkw9vPZoWdydjb5bWQdn0gRWgxXNUASd9FpFCpO7Q5ayYbSxw1vMnSNyYpir0v0YdbA9rA9A3Qk6"
+ "xvgTErRKPvwBbTVPj3vKYOL4bTXLS4EOgcgH1sTpcLQhjvNEGeZ-xhbejPmLu9JGGRQ4bdMEgEL5VENu3A7IhdxkqK-"
+ "75NLrktAFwyBwvfERyv2qu196JqY1P5GPtp5AGHqCyk39TBfNVcT5uQJxE9kPjZAMjcQ7V6IP9QQ02ow62vlqQNmJlmwc1gEOB_"
+ "AzBoP2ipmfW140b8n65-e_LHKrrELWBKp7jLjrLl8iBET-P-fJFi5xcQK2Qb8E_yDqGKt7rRYccg_"
+ "wYaBvar1ULitDXLyAfVXwPCnckFxriOviiTw4uKI8IEbKuKV53QI4uQndC_"
+ "TRnkzUi9K9mlZw03fEb5CP9wrhPhfYbm4BjTZ9hdRVggxhbmm1y2Jz7lKX7FjlTF9d0TahPQG9UZ7O4id1vWseyV-"
+ "S8yhnUlA3qj3bkcEUT-mTzE9lv-vQnTNbUlMy4bPYW8lhttmca2XHdtMHuo112V8S6g681A-q8YNzHFPvLpUWOLumMAvVN44GhZ_"
+ "ZUL6_I4aXnj9FekZY4c6SmveySg0v6SDHOZhSTNsP5YqtGDlrMfUFxWBXnhFaDnLZe_MnL6xzvc5maME5aF4t6fOiNcX8zVDE-"
+ "tJrkuMV9PqR4zaQZ4lq4BedW8Wae76X7ZJIm5pKtDvm5TaO-"
+ "ZIsHQdST7LAnjvQKDS2zVml1yVnH84MpgPQWbydLO8R8tNUjZzYUicjY5ZlLSV9dn3ksf0MOW-"
+ "EzoCrma6rYXndQS9MXqIPXBDe9Xxmqzaz93oPf0Ff6ME35zaJ_qC_lo_neafH_l9x37Qib3LddIvttit5rgP20hHw-4UoJ-"
+ "TI5arMhdT2kz68VhX7YrsCZLHyhHY7SeQ7qZzXzLrHdJO_bnI2mAXPnAUP7_ook7re3IdfbPs-"
+ "3qfuA5gBl2zc2saQNubrNh3jNLIvrt7Yd2W_uBVMPiSVNbD_gY20eTMPoFhTj0_oGn17iSHLfA3LaDn_PAElCf4Lx8WTeTONY-"
+ "5octmnwhHFqWS-K174jB2AzHD-LhtBmVokcWYA-oxFis6I411sVcmh5Lh15mM7inphVjdZ1xt4T1p-"
+ "QhWdl1aAe9lzwUjbxN9deYJWc6NVakB8sKAYndD6CQgnxi7Vtz5NxmfRp64J8bVF_PqWOI-"
+ "wRJziv1SJfLfHUoiM2xvswhvgOzWqTfYs6eC9OEt_7WZBXLKDmM-pv67lnqolxwK2tqI_v4PnP1LfiZoubW1fkAHaI1g-GmqFjl_"
+ "xqYSPmu2AmPyqZd9h3VdcUsjDM-hSdtx9wl_8UYvdHphaP7UVjQ58Hi6BHbCzuk1n20g_nzPdpOpeBvtlvL6x-"
+ "ZD8o4tEaXnwHYw3IyVJ_HqJ9HlncA7Cu4eypM-ZPO97BaOJa3oI8DvL0mWNxJb7js25Blv2ej9Lxu6dKiTXuFjrSlzl-X6F9QT_Suo_"
+ "O6TXqWGz7xBvHFGNkBzCyQ2d8iOUkTfx2olMh7zvsOV-hzhIF0LslPR4PXLjDJwzr6UiSLvSHTp18gHj6wXNWV1nIWnqMUZ0G7Ivv6E_"
+ "LSqZdO01y0oGCw6eTEPNftSDmcp02cE4Whf8i5NM1T3HNwbb7je9NPdZpY06oY8AGz_"
+ "QF6R3Gd1u4PlDHdNgjdVAc1LPt0QN5iMcQO2M97vt1BmS_w8ETUV-Q43H3kX2e8_"
+ "eMovejmiLez5VF9q9maC0LsW0IZbslctgFPJ6h93vi3N6tkf0ui8yA3O9t9gBSPKhbJ1-_9VC7nvPy1v7A7BS_s-g2yNdd-"
+ "hjSfGStVUsPOwM1lrkgZ90pd_jjIwxP-54mJ3EFr_W7LXLcpQ-"
+ "LR4DYXs3UWyJkjjbY2UvF6tJfjQxbWeSXS87f5ZCCGBSAVoRwzmb9QJZnFt9fdKF-"
+ "64bMD7zoAtdbxViiVjB23bXFY0w5s6bs2uSkC7g3h2yM3JVDTG33WDexs9sH-wI_CCdRSGL5sSi2zw05gPW3ucmb_"
+ "8T1bPcuOscFir1g66gZff9PJXLwieJ9eDPjDWSvRo57n_8cunzHewBNXDf0rshRz5thbzf3lILYf3o2ObSZj_bxZUNcb_"
+ "YG5Ge9cA7R6ebrbMyAAEU72J4nWTfvjIEe8IjeM4QAxsHUbR_Z3INdIuc2Dcd88ax3B4fVtnjUlp_"
+ "ZZfKVHb2J8NZNBQQ74hxtV8ipzV5GU-"
+ "aAc2M9SiO2XZy0YvkqObDDMdYvi94aiXWtwZk9g8HTtt5TNnrKiGwDanPuTuij52NvV7SMO3r7IrJ1hDcXzGHrd2F7STtJip64Q1qf3e"
+ "G2_9hNcmw73hNz8TOTDXG9breW8gC_qLwiibHBbsNeItSk-Tbz3O1c_MaBJLEOFvgBXZYuLloDG5m-b3fIkR0VstRB-J6432FD7NsQ-"
+ "w7G39QM3XtgPygdIMOje1cz3hTb9qoHuPBkpHeQoXuffGt__qOX73uzz3-GzNX1P__FHXHsXk-Sk33QFeVL6HJNzmpO3qbOE_"
+ "5WoiCuQ-3byBfArS0KlSPDcBhyolQkGfnOvoO4fvHR_pGpyWJ_vCfHtfkzjQpX1Bcz-Fq_Qr7th_5D1AWuUMjqc0F_VMt-"
+ "u9Kvkv3KlI7xuNiq7bbqkH6D7Pe9yY530YbQL_sX5Lg_pXx5LY1gVMb7uv4lOe3TH_kbK0ufgynuafVb5KjvPTBgDekcYMacoQ-1S5_"
+ "PPP_7NvAbhuSLxFiLnEdnE_2WxA6uCHolfXTp4xvnCzjRB5YwR98SibGyf0UO-h7gPsqJ435Iv0cOIz0ZUjuZX3Cn3e-"
+ "Tj32AFD6mY3DDvjekkROk_c_I_r2W_nWkyxN1Qoy7xVy2D9yhT_"
+ "kzdtaGoQljrg8x16fuK92Rg6SM330ZlMjR4CF6ls9SsnL8Owjna9mtvuKgQQ4Hk128R8xZBjbJDexSim8Vv4BvDe5Abz-M6C6C-"
+ "5vcZbvfN7gnp4PXIdsdX0bmvq8r5Lslj7jm7ojB19_kgynLL9_tROUYhr_Zv59zXSMn15BLX6EEQjB4T_2C-"
+ "9vrBvm2HHXU5lPQzp9w0T2T9AU6NcnHgb09FXpnVcicCzDgmrMAcvZ2TOwZid93uh7AOOqGNEAwTI9j56ZOvrqhjsOjpFEPg9BN3_"
+ "tpRvy27CZ6m7f49SksJhe1dfTZ3m2DfNdwIPqdvOUNucMgNQfAlXkA370AWs7S8kZindsWOWx5c-8J41TGzly-XPuafNt0A-"
+ "a7i4IEVOiw4NmDxJjmqoYhJb6P-y53NXJwx2YM4-empu_ClMX6d1DP332fqucXP1Ok-E5RWdZG6zXvozpmwaeWL_"
+ "MRPNqTdCLEhHuLHN7vepcV93Jx2RtyfM9nQzp8xrmwhr4Z-cf__p_7_0VkiUI")
+ .ok());
+ TlBufferParser parser(&en);
+ auto result = telegram_api::help_getCountriesList::fetch_result(parser);
+ parser.fetch_end();
+ CHECK(parser.get_error() == nullptr);
+ on_get_country_list_impl(language_code, std::move(result));
+
+ it = countries_.find(language_code);
+ CHECK(it != countries_.end())
+ auto *country = it->second.get();
+ if (manager != nullptr) {
+ manager->load_country_list(language_code, country->hash, Auto());
+ }
+ return country;
+ }
+ return nullptr;
+ }
+
+ auto *country = it->second.get();
+ CHECK(country != nullptr);
+ if (manager != nullptr && country->next_reload_time < Time::now()) {
+ manager->load_country_list(language_code, country->hash, Auto());
+ }
+
+ return country;
+}
+
+int32 CountryInfoManager::manager_count_ = 0;
+std::mutex CountryInfoManager::country_mutex_;
+FlatHashMap<string, unique_ptr<CountryInfoManager::CountryList>> CountryInfoManager::countries_;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CountryInfoManager.h b/protocols/Telegram/tdlib/td/td/telegram/CountryInfoManager.h
new file mode 100644
index 0000000000..013c5024e0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/CountryInfoManager.h
@@ -0,0 +1,87 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+#include <mutex>
+
+namespace td {
+
+class Td;
+
+class CountryInfoManager final : public Actor {
+ public:
+ CountryInfoManager(Td *td, ActorShared<> parent);
+
+ void get_countries(Promise<td_api::object_ptr<td_api::countries>> &&promise);
+
+ void get_current_country_code(Promise<string> &&promise);
+
+ void get_phone_number_info(string phone_number_prefix,
+ Promise<td_api::object_ptr<td_api::phoneNumberInfo>> &&promise);
+
+ static td_api::object_ptr<td_api::phoneNumberInfo> get_phone_number_info_sync(const string &language_code,
+ string phone_number_prefix);
+
+ CountryInfoManager(const CountryInfoManager &) = delete;
+ CountryInfoManager &operator=(const CountryInfoManager &) = delete;
+ CountryInfoManager(CountryInfoManager &&) = delete;
+ CountryInfoManager &operator=(CountryInfoManager &&) = delete;
+ ~CountryInfoManager() final;
+
+ private:
+ void start_up() final;
+ void tear_down() final;
+
+ struct CallingCodeInfo;
+ struct CountryInfo;
+ struct CountryList;
+
+ string get_main_language_code();
+
+ void do_get_countries(string language_code, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::countries>> &&promise);
+
+ void do_get_phone_number_info(string phone_number_prefix, string language_code, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::phoneNumberInfo>> &&promise);
+
+ void load_country_list(string language_code, int32 hash, Promise<Unit> &&promise);
+
+ void on_get_country_list(const string &language_code,
+ Result<tl_object_ptr<telegram_api::help_CountriesList>> r_country_list);
+
+ static void on_get_country_list_impl(const string &language_code,
+ tl_object_ptr<telegram_api::help_CountriesList> country_list);
+
+ static const CountryList *get_country_list(CountryInfoManager *manager, const string &language_code);
+
+ static td_api::object_ptr<td_api::phoneNumberInfo> get_phone_number_info_object(const CountryList *list,
+ Slice phone_number);
+
+ static std::mutex country_mutex_;
+
+ static int32 manager_count_;
+
+ static FlatHashMap<string, unique_ptr<CountryList>> countries_;
+
+ FlatHashMap<string, vector<Promise<Unit>>> pending_load_country_queries_;
+
+ Td *td_;
+ ActorShared<> parent_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/CustomEmojiId.h b/protocols/Telegram/tdlib/td/td/telegram/CustomEmojiId.h
new file mode 100644
index 0000000000..863bf2fc46
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/CustomEmojiId.h
@@ -0,0 +1,65 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class CustomEmojiId {
+ int64 id = 0;
+
+ public:
+ CustomEmojiId() = default;
+
+ explicit CustomEmojiId(int64 custom_emoji_id) : id(custom_emoji_id) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
+ CustomEmojiId(T custom_emoji_id) = delete;
+
+ bool is_valid() const {
+ return id != 0;
+ }
+
+ int64 get() const {
+ return id;
+ }
+
+ bool operator==(const CustomEmojiId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const CustomEmojiId &other) const {
+ return id != other.id;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ storer.store_long(id);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ id = parser.fetch_long();
+ }
+};
+
+struct CustomEmojiIdHash {
+ uint32 operator()(CustomEmojiId custom_emoji_id) const {
+ return Hash<int64>()(custom_emoji_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, CustomEmojiId custom_emoji_id) {
+ return string_builder << "custom emoji " << custom_emoji_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.cpp b/protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.cpp
index 130d3c6615..bb8d42daff 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,11 +14,12 @@ namespace td {
void DelayDispatcher::send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback) {
send_with_callback_and_delay(std::move(query), std::move(callback), default_delay_);
}
+
void DelayDispatcher::send_with_callback_and_delay(NetQueryPtr query, ActorShared<NetQueryCallback> callback,
double delay) {
queue_.push({std::move(query), std::move(callback), delay});
loop();
-} // namespace td
+}
void DelayDispatcher::loop() {
if (!wakeup_at_.is_in_past()) {
@@ -43,4 +44,23 @@ void DelayDispatcher::loop() {
set_timeout_at(wakeup_at_.at());
}
+void DelayDispatcher::close_silent() {
+ while (!queue_.empty()) {
+ auto query = std::move(queue_.front());
+ queue_.pop();
+ query.net_query->clear();
+ }
+ stop();
+}
+
+void DelayDispatcher::tear_down() {
+ while (!queue_.empty()) {
+ auto query = std::move(queue_.front());
+ queue_.pop();
+ query.net_query->set_error(Global::request_aborted_error());
+ send_closure(std::move(query.callback), &NetQueryCallback::on_result, std::move(query.net_query));
+ }
+ parent_.reset();
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.h b/protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.h
index 42e836b283..36b593118e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/DelayDispatcher.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -16,14 +16,17 @@
namespace td {
-class DelayDispatcher : public Actor {
+class DelayDispatcher final : public Actor {
public:
- explicit DelayDispatcher(double default_delay) : default_delay_(default_delay) {
+ DelayDispatcher(double default_delay, ActorShared<> parent)
+ : default_delay_(default_delay), parent_(std::move(parent)) {
}
void send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback);
void send_with_callback_and_delay(NetQueryPtr query, ActorShared<NetQueryCallback> callback, double delay);
+ void close_silent();
+
private:
struct Query {
NetQueryPtr net_query;
@@ -33,8 +36,10 @@ class DelayDispatcher : public Actor {
std::queue<Query> queue_;
Timestamp wakeup_at_;
double default_delay_;
+ ActorShared<> parent_;
- void loop() override;
+ void loop() final;
+ void tear_down() final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Dependencies.cpp b/protocols/Telegram/tdlib/td/td/telegram/Dependencies.cpp
new file mode 100644
index 0000000000..df6781efc0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Dependencies.cpp
@@ -0,0 +1,130 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Dependencies.h"
+
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/WebPagesManager.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+
+namespace td {
+
+void Dependencies::add(UserId user_id) {
+ if (user_id.is_valid()) {
+ user_ids.insert(user_id);
+ }
+}
+
+void Dependencies::add(ChatId chat_id) {
+ if (chat_id.is_valid()) {
+ chat_ids.insert(chat_id);
+ }
+}
+
+void Dependencies::add(ChannelId channel_id) {
+ if (channel_id.is_valid()) {
+ channel_ids.insert(channel_id);
+ }
+}
+
+void Dependencies::add(SecretChatId secret_chat_id) {
+ if (secret_chat_id.is_valid()) {
+ secret_chat_ids.insert(secret_chat_id);
+ }
+}
+
+void Dependencies::add(WebPageId web_page_id) {
+ if (web_page_id.is_valid()) {
+ web_page_ids.insert(web_page_id);
+ }
+}
+
+void Dependencies::add_dialog_and_dependencies(DialogId dialog_id) {
+ if (dialog_id.is_valid() && dialog_ids.insert(dialog_id).second) {
+ add_dialog_dependencies(dialog_id);
+ }
+}
+
+void Dependencies::add_dialog_dependencies(DialogId dialog_id) {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ add(dialog_id.get_user_id());
+ break;
+ case DialogType::Chat:
+ add(dialog_id.get_chat_id());
+ break;
+ case DialogType::Channel:
+ add(dialog_id.get_channel_id());
+ break;
+ case DialogType::SecretChat:
+ add(dialog_id.get_secret_chat_id());
+ break;
+ case DialogType::None:
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+void Dependencies::add_message_sender_dependencies(DialogId dialog_id) {
+ if (dialog_id.get_type() == DialogType::User) {
+ add(dialog_id.get_user_id());
+ } else {
+ add_dialog_and_dependencies(dialog_id);
+ }
+}
+
+bool Dependencies::resolve_force(Td *td, const char *source) const {
+ bool success = true;
+ for (auto user_id : user_ids) {
+ if (!td->contacts_manager_->have_user_force(user_id)) {
+ LOG(ERROR) << "Can't find " << user_id << " from " << source;
+ success = false;
+ }
+ }
+ for (auto chat_id : chat_ids) {
+ if (!td->contacts_manager_->have_chat_force(chat_id)) {
+ LOG(ERROR) << "Can't find " << chat_id << " from " << source;
+ success = false;
+ }
+ }
+ for (auto channel_id : channel_ids) {
+ if (!td->contacts_manager_->have_channel_force(channel_id)) {
+ if (td->contacts_manager_->have_min_channel(channel_id)) {
+ LOG(INFO) << "Can't find " << channel_id << " from " << source << ", but have it as a min-channel";
+ continue;
+ }
+ LOG(ERROR) << "Can't find " << channel_id << " from " << source;
+ success = false;
+ }
+ }
+ for (auto secret_chat_id : secret_chat_ids) {
+ if (!td->contacts_manager_->have_secret_chat_force(secret_chat_id)) {
+ LOG(ERROR) << "Can't find " << secret_chat_id << " from " << source;
+ success = false;
+ }
+ }
+ for (auto dialog_id : dialog_ids) {
+ if (!td->messages_manager_->have_dialog_force(dialog_id, source)) {
+ LOG(ERROR) << "Can't find " << dialog_id << " from " << source;
+ td->messages_manager_->force_create_dialog(dialog_id, "resolve_dependencies_force", true);
+ success = false;
+ }
+ }
+ for (auto web_page_id : web_page_ids) {
+ if (!td->web_pages_manager_->have_web_page_force(web_page_id)) {
+ LOG(INFO) << "Can't find " << web_page_id << " from " << source;
+ success = false;
+ }
+ }
+ return success;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Dependencies.h b/protocols/Telegram/tdlib/td/td/telegram/Dependencies.h
new file mode 100644
index 0000000000..e4d3541fa5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Dependencies.h
@@ -0,0 +1,54 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/SecretChatId.h"
+#include "td/telegram/UserId.h"
+#include "td/telegram/WebPageId.h"
+
+#include "td/utils/FlatHashSet.h"
+
+namespace td {
+
+class Td;
+
+class Dependencies {
+ FlatHashSet<UserId, UserIdHash> user_ids;
+ FlatHashSet<ChatId, ChatIdHash> chat_ids;
+ FlatHashSet<ChannelId, ChannelIdHash> channel_ids;
+ FlatHashSet<SecretChatId, SecretChatIdHash> secret_chat_ids;
+ FlatHashSet<DialogId, DialogIdHash> dialog_ids;
+ FlatHashSet<WebPageId, WebPageIdHash> web_page_ids;
+
+ public:
+ void add(UserId user_id);
+
+ void add(ChatId chat_id);
+
+ void add(ChannelId channel_id);
+
+ void add(SecretChatId secret_chat_id);
+
+ void add(WebPageId web_page_id);
+
+ void add_dialog_and_dependencies(DialogId dialog_id);
+
+ void add_dialog_dependencies(DialogId dialog_id);
+
+ void add_message_sender_dependencies(DialogId dialog_id);
+
+ bool resolve_force(Td *td, const char *source) const;
+
+ const FlatHashSet<DialogId, DialogIdHash> &get_dialog_ids() const {
+ return dialog_ids;
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.cpp
index 8182d6dac8..472435ea67 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,17 +9,24 @@
#include "td/telegram/Global.h"
#include "td/telegram/misc.h"
#include "td/telegram/net/NetQueryDispatcher.h"
-#include "td/telegram/UserId.h"
-
#include "td/telegram/td_api.hpp"
+#include "td/telegram/TdDb.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/mtproto/DhHandshake.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/utils/algorithm.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
#include "td/utils/format.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
-#include "td/utils/Slice.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/tl_helpers.h"
@@ -34,32 +41,42 @@ void DeviceTokenManager::TokenInfo::store(StorerT &storer) const {
bool is_sync = state == State::Sync;
bool is_unregister = state == State::Unregister;
bool is_register = state == State::Register;
+ CHECK(state != State::Reregister);
BEGIN_STORE_FLAGS();
- STORE_FLAG(has_other_user_ids);
+ STORE_FLAG(false);
STORE_FLAG(is_sync);
STORE_FLAG(is_unregister);
STORE_FLAG(is_register);
STORE_FLAG(is_app_sandbox);
+ STORE_FLAG(encrypt);
+ STORE_FLAG(has_other_user_ids);
END_STORE_FLAGS();
store(token, storer);
if (has_other_user_ids) {
store(other_user_ids, storer);
}
+ if (encrypt) {
+ store(encryption_key, storer);
+ store(encryption_key_id, storer);
+ }
}
template <class ParserT>
void DeviceTokenManager::TokenInfo::parse(ParserT &parser) {
using td::parse;
+ bool has_other_user_ids_legacy;
bool has_other_user_ids;
bool is_sync;
bool is_unregister;
bool is_register;
BEGIN_PARSE_FLAGS();
- PARSE_FLAG(has_other_user_ids);
+ PARSE_FLAG(has_other_user_ids_legacy);
PARSE_FLAG(is_sync);
PARSE_FLAG(is_unregister);
PARSE_FLAG(is_register);
PARSE_FLAG(is_app_sandbox);
+ PARSE_FLAG(encrypt);
+ PARSE_FLAG(has_other_user_ids);
END_PARSE_FLAGS();
CHECK(is_sync + is_unregister + is_register == 1);
if (is_sync) {
@@ -70,90 +87,109 @@ void DeviceTokenManager::TokenInfo::parse(ParserT &parser) {
state = State::Register;
}
parse(token, parser);
+ if (has_other_user_ids_legacy) {
+ vector<int32> other_user_ids_legacy;
+ parse(other_user_ids_legacy, parser);
+ other_user_ids = transform(other_user_ids_legacy, [](int32 user_id) { return static_cast<int64>(user_id); });
+ }
if (has_other_user_ids) {
parse(other_user_ids, parser);
}
+ if (encrypt) {
+ parse(encryption_key, parser);
+ parse(encryption_key_id, parser);
+ }
}
-StringBuilder &operator<<(StringBuilder &string_builder, const DeviceTokenManager::TokenInfo &token_info) {
- switch (token_info.state) {
+StringBuilder &operator<<(StringBuilder &string_builder, const DeviceTokenManager::TokenInfo::State &state) {
+ switch (state) {
case DeviceTokenManager::TokenInfo::State::Sync:
- string_builder << "Synchronized";
- break;
+ return string_builder << "Synchronized";
case DeviceTokenManager::TokenInfo::State::Unregister:
- string_builder << "Unregister";
- break;
+ return string_builder << "Unregister";
case DeviceTokenManager::TokenInfo::State::Register:
- string_builder << "Register";
- break;
+ return string_builder << "Register";
+ case DeviceTokenManager::TokenInfo::State::Reregister:
+ return string_builder << "Reregister";
default:
UNREACHABLE();
+ return string_builder;
}
- string_builder << " token \"" << format::escaped(token_info.token) << "\"";
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DeviceTokenManager::TokenInfo &token_info) {
+ string_builder << token_info.state << " token \"" << format::escaped(token_info.token) << "\"";
if (!token_info.other_user_ids.empty()) {
string_builder << ", with other users " << token_info.other_user_ids;
}
if (token_info.is_app_sandbox) {
string_builder << ", sandboxed";
}
+ if (token_info.encrypt) {
+ string_builder << ", encrypted with ID " << token_info.encryption_key_id;
+ }
return string_builder;
}
void DeviceTokenManager::register_device(tl_object_ptr<td_api::DeviceToken> device_token_ptr,
- vector<int32> other_user_ids, Promise<tl_object_ptr<td_api::ok>> promise) {
+ const vector<UserId> &other_user_ids,
+ Promise<td_api::object_ptr<td_api::pushReceiverId>> promise) {
CHECK(device_token_ptr != nullptr);
TokenType token_type;
string token;
bool is_app_sandbox = false;
+ bool encrypt = false;
switch (device_token_ptr->get_id()) {
case td_api::deviceTokenApplePush::ID: {
auto device_token = static_cast<td_api::deviceTokenApplePush *>(device_token_ptr.get());
token = std::move(device_token->device_token_);
- token_type = TokenType::APNS;
+ token_type = TokenType::Apns;
is_app_sandbox = device_token->is_app_sandbox_;
break;
}
- case td_api::deviceTokenGoogleCloudMessaging::ID: {
- auto device_token = static_cast<td_api::deviceTokenGoogleCloudMessaging *>(device_token_ptr.get());
+ case td_api::deviceTokenFirebaseCloudMessaging::ID: {
+ auto device_token = static_cast<td_api::deviceTokenFirebaseCloudMessaging *>(device_token_ptr.get());
token = std::move(device_token->token_);
- token_type = TokenType::GCM;
+ token_type = TokenType::Fcm;
+ encrypt = device_token->encrypt_;
break;
}
case td_api::deviceTokenMicrosoftPush::ID: {
auto device_token = static_cast<td_api::deviceTokenMicrosoftPush *>(device_token_ptr.get());
token = std::move(device_token->channel_uri_);
- token_type = TokenType::MPNS;
+ token_type = TokenType::Mpns;
break;
}
case td_api::deviceTokenSimplePush::ID: {
auto device_token = static_cast<td_api::deviceTokenSimplePush *>(device_token_ptr.get());
token = std::move(device_token->endpoint_);
- token_type = TokenType::SIMPLE_PUSH;
+ token_type = TokenType::SimplePush;
break;
}
case td_api::deviceTokenUbuntuPush::ID: {
auto device_token = static_cast<td_api::deviceTokenUbuntuPush *>(device_token_ptr.get());
token = std::move(device_token->token_);
- token_type = TokenType::UBUNTU_PHONE;
+ token_type = TokenType::UbuntuPhone;
break;
}
case td_api::deviceTokenBlackBerryPush::ID: {
auto device_token = static_cast<td_api::deviceTokenBlackBerryPush *>(device_token_ptr.get());
token = std::move(device_token->token_);
- token_type = TokenType::BLACKBERRY;
+ token_type = TokenType::BlackBerry;
break;
}
case td_api::deviceTokenWindowsPush::ID: {
auto device_token = static_cast<td_api::deviceTokenWindowsPush *>(device_token_ptr.get());
token = std::move(device_token->access_token_);
- token_type = TokenType::WNS;
+ token_type = TokenType::Wns;
break;
}
case td_api::deviceTokenApplePushVoIP::ID: {
auto device_token = static_cast<td_api::deviceTokenApplePushVoIP *>(device_token_ptr.get());
token = std::move(device_token->device_token_);
- token_type = TokenType::APNS_VOIP;
+ token_type = TokenType::ApnsVoip;
is_app_sandbox = device_token->is_app_sandbox_;
+ encrypt = device_token->encrypt_;
break;
}
case td_api::deviceTokenWebPush::ID: {
@@ -172,53 +208,27 @@ void DeviceTokenManager::register_device(tl_object_ptr<td_api::DeviceToken> devi
}
if (!device_token->endpoint_.empty()) {
- class JsonKeys : public Jsonable {
- public:
- JsonKeys(Slice p256dh, Slice auth) : p256dh_(p256dh), auth_(auth) {
- }
- void store(JsonValueScope *scope) const {
- auto object = scope->enter_object();
- object << ctie("p256dh", p256dh_);
- object << ctie("auth", auth_);
- }
-
- private:
- Slice p256dh_;
- Slice auth_;
- };
- class JsonWebPushToken : public Jsonable {
- public:
- JsonWebPushToken(Slice endpoint, Slice p256dh, Slice auth)
- : endpoint_(endpoint), p256dh_(p256dh), auth_(auth) {
- }
- void store(JsonValueScope *scope) const {
- auto object = scope->enter_object();
- object << ctie("endpoint", endpoint_);
- object << ctie("keys", JsonKeys(p256dh_, auth_));
- }
-
- private:
- Slice endpoint_;
- Slice p256dh_;
- Slice auth_;
- };
-
- token = json_encode<string>(
- JsonWebPushToken(device_token->endpoint_, device_token->p256dh_base64url_, device_token->auth_base64url_));
+ token = json_encode<string>(json_object([&device_token](auto &o) {
+ o("endpoint", device_token->endpoint_);
+ o("keys", json_object([&device_token](auto &o) {
+ o("p256dh", device_token->p256dh_base64url_);
+ o("auth", device_token->auth_base64url_);
+ }));
+ }));
}
- token_type = TokenType::WEB_PUSH;
+ token_type = TokenType::WebPush;
break;
}
case td_api::deviceTokenMicrosoftPushVoIP::ID: {
auto device_token = static_cast<td_api::deviceTokenMicrosoftPushVoIP *>(device_token_ptr.get());
token = std::move(device_token->channel_uri_);
- token_type = TokenType::MPNS_VOIP;
+ token_type = TokenType::MpnsVoip;
break;
}
case td_api::deviceTokenTizenPush::ID: {
auto device_token = static_cast<td_api::deviceTokenTizenPush *>(device_token_ptr.get());
token = std::move(device_token->reg_id_);
- token_type = TokenType::TIZEN;
+ token_type = TokenType::Tizen;
break;
}
default:
@@ -229,41 +239,88 @@ void DeviceTokenManager::register_device(tl_object_ptr<td_api::DeviceToken> devi
return promise.set_error(Status::Error(400, "Device token must be encoded in UTF-8"));
}
for (auto &other_user_id : other_user_ids) {
- UserId user_id(other_user_id);
- if (!user_id.is_valid()) {
+ if (!other_user_id.is_valid()) {
return promise.set_error(Status::Error(400, "Invalid user_id among other user_ids"));
}
}
- if (other_user_ids.size() > MAX_OTHER_USER_IDS) {
- return promise.set_error(Status::Error(400, "Too much other user_ids"));
- }
+ auto input_user_ids = UserId::get_input_user_ids(other_user_ids);
auto &info = tokens_[token_type];
- info.net_query_id = 0;
if (token.empty()) {
if (info.token.empty()) {
// already unregistered
- return promise.set_value(make_tl_object<td_api::ok>());
+ return promise.set_value(td_api::make_object<td_api::pushReceiverId>());
}
info.state = TokenInfo::State::Unregister;
} else {
+ if ((info.state == TokenInfo::State::Reregister || info.state == TokenInfo::State::Sync) && info.token == token &&
+ info.other_user_ids == input_user_ids && info.is_app_sandbox == is_app_sandbox && encrypt == info.encrypt) {
+ int64 push_token_id = encrypt ? info.encryption_key_id : G()->get_option_integer("my_id");
+ return promise.set_value(td_api::make_object<td_api::pushReceiverId>(push_token_id));
+ }
+
info.state = TokenInfo::State::Register;
info.token = std::move(token);
}
- info.other_user_ids = std::move(other_user_ids);
+ info.net_query_id = 0;
+ info.other_user_ids = std::move(input_user_ids);
info.is_app_sandbox = is_app_sandbox;
- info.promise.set_value(make_tl_object<td_api::ok>());
+ if (encrypt != info.encrypt) {
+ if (encrypt) {
+ constexpr size_t ENCRYPTION_KEY_LENGTH = 256;
+ constexpr auto MIN_ENCRYPTION_KEY_ID = static_cast<int64>(10000000000000ll);
+ info.encryption_key.resize(ENCRYPTION_KEY_LENGTH);
+ while (true) {
+ Random::secure_bytes(info.encryption_key);
+ info.encryption_key_id = mtproto::DhHandshake::calc_key_id(info.encryption_key);
+ if (info.encryption_key_id <= -MIN_ENCRYPTION_KEY_ID || info.encryption_key_id >= MIN_ENCRYPTION_KEY_ID) {
+ // ensure that encryption key ID never collide with anything
+ break;
+ }
+ }
+ } else {
+ info.encryption_key.clear();
+ info.encryption_key_id = 0;
+ }
+ info.encrypt = encrypt;
+ }
+ info.promise.set_value(td_api::make_object<td_api::pushReceiverId>());
info.promise = std::move(promise);
save_info(token_type);
}
+void DeviceTokenManager::reregister_device() {
+ for (int32 token_type = 1; token_type < TokenType::Size; token_type++) {
+ auto &token = tokens_[token_type];
+ if (token.state == TokenInfo::State::Sync && !token.token.empty()) {
+ token.state = TokenInfo::State::Reregister;
+ }
+ }
+ loop();
+}
+
+vector<std::pair<int64, Slice>> DeviceTokenManager::get_encryption_keys() const {
+ vector<std::pair<int64, Slice>> result;
+ for (int32 token_type = 1; token_type < TokenType::Size; token_type++) {
+ auto &info = tokens_[token_type];
+ if (!info.token.empty() && info.state != TokenInfo::State::Unregister) {
+ if (info.encrypt) {
+ result.emplace_back(info.encryption_key_id, info.encryption_key);
+ } else {
+ result.emplace_back(G()->get_option_integer("my_id"), Slice());
+ }
+ }
+ }
+ return result;
+}
+
string DeviceTokenManager::get_database_key(int32 token_type) {
return PSTRING() << "device_token" << token_type;
}
void DeviceTokenManager::start_up() {
- for (int32 token_type = 1; token_type < TokenType::SIZE; token_type++) {
+ for (int32 token_type = 1; token_type < TokenType::Size; token_type++) {
auto serialized = G()->td_db()->get_binlog_pmc()->get(get_database_key(token_type));
if (serialized.empty()) {
continue;
@@ -272,7 +329,12 @@ void DeviceTokenManager::start_up() {
auto &token = tokens_[token_type];
char c = serialized[0];
if (c == '*') {
- unserialize(token, serialized.substr(1)).ensure();
+ auto status = unserialize(token, serialized.substr(1));
+ if (status.is_error()) {
+ token = TokenInfo();
+ LOG(ERROR) << "Invalid serialized TokenInfo: " << format::escaped(serialized) << ' ' << status;
+ continue;
+ }
} else {
// legacy
if (c == '+') {
@@ -287,7 +349,10 @@ void DeviceTokenManager::start_up() {
}
token.token = serialized.substr(1);
}
- LOG(INFO) << "GET device token " << token_type << "--->" << tokens_[token_type];
+ LOG(INFO) << "Have device token " << token_type << "--->" << token;
+ if (token.state == TokenInfo::State::Sync && !token.token.empty()) {
+ token.state = TokenInfo::State::Reregister;
+ }
}
loop();
}
@@ -301,7 +366,7 @@ void DeviceTokenManager::save_info(int32 token_type) {
}
sync_cnt_++;
G()->td_db()->get_binlog_pmc()->force_sync(
- PromiseCreator::event(self_closure(this, &DeviceTokenManager::dec_sync_cnt)));
+ create_event_promise(self_closure(this, &DeviceTokenManager::dec_sync_cnt)));
}
void DeviceTokenManager::dec_sync_cnt() {
@@ -310,10 +375,10 @@ void DeviceTokenManager::dec_sync_cnt() {
}
void DeviceTokenManager::loop() {
- if (sync_cnt_ != 0) {
+ if (sync_cnt_ != 0 || G()->close_flag()) {
return;
}
- for (int32 token_type = 1; token_type < TokenType::SIZE; token_type++) {
+ for (int32 token_type = 1; token_type < TokenType::Size; token_type++) {
auto &info = tokens_[token_type];
if (info.state == TokenInfo::State::Sync) {
continue;
@@ -323,13 +388,14 @@ void DeviceTokenManager::loop() {
}
// have to send query
NetQueryPtr net_query;
- auto other_user_ids = info.other_user_ids;
if (info.state == TokenInfo::State::Unregister) {
net_query = G()->net_query_creator().create(
- create_storer(telegram_api::account_unregisterDevice(token_type, info.token, std::move(other_user_ids))));
+ telegram_api::account_unregisterDevice(token_type, info.token, vector<int64>(info.other_user_ids)));
} else {
- net_query = G()->net_query_creator().create(create_storer(telegram_api::account_registerDevice(
- token_type, info.token, info.is_app_sandbox, BufferSlice(), std::move(other_user_ids))));
+ int32 flags = telegram_api::account_registerDevice::NO_MUTED_MASK;
+ net_query = G()->net_query_creator().create(
+ telegram_api::account_registerDevice(flags, false /*ignored*/, token_type, info.token, info.is_app_sandbox,
+ BufferSlice(info.encryption_key), vector<int64>(info.other_user_ids)));
}
info.net_query_id = net_query->id();
G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this, token_type));
@@ -338,43 +404,57 @@ void DeviceTokenManager::loop() {
void DeviceTokenManager::on_result(NetQueryPtr net_query) {
auto token_type = static_cast<TokenType>(get_link_token());
- CHECK(token_type >= 1 && token_type < TokenType::SIZE);
+ CHECK(token_type >= 1 && token_type < TokenType::Size);
auto &info = tokens_[token_type];
if (info.net_query_id != net_query->id()) {
net_query->clear();
return;
}
info.net_query_id = 0;
+ CHECK(info.state != TokenInfo::State::Sync);
+
static_assert(std::is_same<telegram_api::account_registerDevice::ReturnType,
telegram_api::account_unregisterDevice::ReturnType>::value,
"");
auto r_flag = fetch_result<telegram_api::account_registerDevice>(std::move(net_query));
-
- info.net_query_id = 0;
if (r_flag.is_ok() && r_flag.ok()) {
if (info.promise) {
- info.promise.set_value(make_tl_object<td_api::ok>());
+ int64 push_token_id = 0;
+ if (info.state == TokenInfo::State::Register) {
+ if (info.encrypt) {
+ push_token_id = info.encryption_key_id;
+ } else {
+ push_token_id = G()->get_option_integer("my_id");
+ }
+ }
+ info.promise.set_value(td_api::make_object<td_api::pushReceiverId>(push_token_id));
}
if (info.state == TokenInfo::State::Unregister) {
- info.token = "";
+ info.token.clear();
}
info.state = TokenInfo::State::Sync;
} else {
- if (info.promise) {
- if (r_flag.is_error()) {
- info.promise.set_error(r_flag.error().clone());
+ int32 retry_after = 0;
+ if (r_flag.is_error()) {
+ auto &error = r_flag.error();
+ if (!G()->is_expected_error(error)) {
+ LOG(ERROR) << "Failed to " << info.state << " device: " << error;
} else {
- info.promise.set_error(Status::Error(5, "Got false as result"));
+ retry_after = Global::get_retry_after(error.code(), error.message());
}
+ info.promise.set_error(r_flag.move_as_error());
+ } else {
+ info.promise.set_error(Status::Error(400, "Got false as result of registerDevice server request"));
}
- if (info.state == TokenInfo::State::Register) {
+ if (info.state == TokenInfo::State::Reregister) {
+ // keep trying to reregister the token
+ return set_timeout_in(clamp(retry_after, 1, 3600));
+ } else if (info.state == TokenInfo::State::Register) {
info.state = TokenInfo::State::Unregister;
} else {
+ CHECK(info.state == TokenInfo::State::Unregister);
info.state = TokenInfo::State::Sync;
- info.token = "";
- }
- if (r_flag.is_error()) {
- LOG(ERROR) << r_flag.error();
+ info.token.clear();
}
}
save_info(token_type);
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.h b/protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.h
index 8858f85d7c..faf13ef119 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/DeviceTokenManager.h
@@ -1,59 +1,66 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/net/NetQuery.h"
-
#include "td/telegram/td_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
#include "td/utils/StringBuilder.h"
#include <array>
+#include <utility>
namespace td {
-class DeviceTokenManager : public NetQueryCallback {
+class DeviceTokenManager final : public NetQueryCallback {
public:
explicit DeviceTokenManager(ActorShared<> parent) : parent_(std::move(parent)) {
}
- void register_device(tl_object_ptr<td_api::DeviceToken> device_token_ptr, vector<int32> other_user_ids,
- Promise<tl_object_ptr<td_api::ok>> promise);
+ void register_device(tl_object_ptr<td_api::DeviceToken> device_token_ptr, const vector<UserId> &other_user_ids,
+ Promise<td_api::object_ptr<td_api::pushReceiverId>> promise);
- private:
- static constexpr size_t MAX_OTHER_USER_IDS = 100;
+ void reregister_device();
+
+ vector<std::pair<int64, Slice>> get_encryption_keys() const;
+ private:
ActorShared<> parent_;
enum TokenType : int32 {
- APNS = 1,
- GCM = 2,
- MPNS = 3,
- SIMPLE_PUSH = 4,
- UBUNTU_PHONE = 5,
- BLACKBERRY = 6,
- UNUSED = 7,
- WNS = 8,
- APNS_VOIP = 9,
- WEB_PUSH = 10,
- MPNS_VOIP = 11,
- TIZEN = 12,
- SIZE
+ Apns = 1,
+ Fcm = 2,
+ Mpns = 3,
+ SimplePush = 4,
+ UbuntuPhone = 5,
+ BlackBerry = 6,
+ Unused = 7,
+ Wns = 8,
+ ApnsVoip = 9,
+ WebPush = 10,
+ MpnsVoip = 11,
+ Tizen = 12,
+ Size
};
struct TokenInfo {
- enum class State { Sync, Unregister, Register };
+ enum class State : int32 { Sync, Unregister, Register, Reregister };
State state = State::Sync;
string token;
uint64 net_query_id = 0;
- vector<int32> other_user_ids;
+ vector<int64> other_user_ids;
bool is_app_sandbox = false;
- Promise<tl_object_ptr<td_api::ok>> promise;
+ bool encrypt = false;
+ string encryption_key;
+ int64 encryption_key_id = 0;
+ Promise<td_api::object_ptr<td_api::pushReceiverId>> promise;
template <class StorerT>
void store(StorerT &storer) const;
@@ -62,20 +69,22 @@ class DeviceTokenManager : public NetQueryCallback {
void parse(ParserT &parser);
};
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const TokenInfo::State &state);
+
friend StringBuilder &operator<<(StringBuilder &string_builder, const TokenInfo &token_info);
- std::array<TokenInfo, TokenType::SIZE> tokens_;
+ std::array<TokenInfo, TokenType::Size> tokens_;
int32 sync_cnt_{0};
- void start_up() override;
+ void start_up() final;
static string get_database_key(int32 token_type);
void save_info(int32 token_type);
void dec_sync_cnt();
- void loop() override;
- void on_result(NetQueryPtr net_query) override;
+ void loop() final;
+ void on_result(NetQueryPtr net_query) final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DhCache.cpp b/protocols/Telegram/tdlib/td/td/telegram/DhCache.cpp
index 23c299583d..717ba11b3e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DhCache.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/DhCache.cpp
@@ -1,17 +1,16 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/DhCache.h"
-#include "td/db/Pmc.h"
-
#include "td/telegram/Global.h"
#include "td/telegram/TdDb.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
namespace td {
@@ -22,6 +21,18 @@ static string good_prime_key(Slice prime_str) {
}
int DhCache::is_good_prime(Slice prime_str) const {
+ static string built_in_good_prime =
+ hex_decode(
+ "c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f48198a0aa7c14058229493d22530f4dbfa336f6e0ac9"
+ "25139543aed44cce7c3720fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f642477fe96bb2a941d5bcd1d4a"
+ "c8cc49880708fa9b378e3c4f3a9060bee67cf9a4a4a695811051907e162753b56b0f6b410dba74d8a84b2a14b3144e0ef1284754fd17"
+ "ed950d5965b4b9dd46582db1178d169c6bc465b0d6ff9ca3928fef5b9ae4e418fc15e83ebea0f87fa9ff5eed70050ded2849f47bf959"
+ "d956850ce929851f0d8115f635b105ee2e4e15d04b2454bf6f4fadf034b10403119cd8e3b92fcc5b")
+ .move_as_ok();
+ if (prime_str == built_in_good_prime) {
+ return 1;
+ }
+
string value = G()->td_db()->get_binlog_pmc()->get(good_prime_key(prime_str));
if (value == "good") {
return 1;
@@ -29,7 +40,7 @@ int DhCache::is_good_prime(Slice prime_str) const {
if (value == "bad") {
return 0;
}
- CHECK(value == "");
+ CHECK(value.empty());
return -1;
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DhCache.h b/protocols/Telegram/tdlib/td/td/telegram/DhCache.h
index a2844a50b3..c162820d07 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DhCache.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/DhCache.h
@@ -1,24 +1,24 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/mtproto/crypto.h"
+#include "td/mtproto/DhCallback.h"
#include "td/utils/Slice.h"
namespace td {
-class DhCache : public DhCallback {
+class DhCache final : public mtproto::DhCallback {
public:
- int is_good_prime(Slice prime_str) const override;
- void add_good_prime(Slice prime_str) const override;
- void add_bad_prime(Slice prime_str) const override;
+ int is_good_prime(Slice prime_str) const final;
+ void add_good_prime(Slice prime_str) const final;
+ void add_bad_prime(Slice prime_str) const final;
- static DhCallback *instance() {
+ static mtproto::DhCallback *instance() {
static DhCache res;
return &res;
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DhConfig.h b/protocols/Telegram/tdlib/td/td/telegram/DhConfig.h
index a8a07e2e5a..1b01874fc4 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DhConfig.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/DhConfig.h
@@ -1,27 +1,32 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/utils/common.h"
namespace td {
+
class DhConfig {
public:
int32 version = 0;
string prime;
int32 g = 0;
+
bool empty() const {
return prime.empty();
}
+
template <class StorerT>
void store(StorerT &storer) const {
storer.store_int(version);
storer.store_string(prime);
storer.store_int(g);
}
+
template <class ParserT>
void parse(ParserT &parser) {
version = parser.fetch_int();
@@ -29,4 +34,5 @@ class DhConfig {
g = parser.fetch_int();
}
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogAction.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogAction.cpp
new file mode 100644
index 0000000000..b2941a3138
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogAction.cpp
@@ -0,0 +1,530 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogAction.h"
+
+#include "td/telegram/misc.h"
+#include "td/telegram/ServerMessageId.h"
+
+#include "td/utils/emoji.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/utf8.h"
+
+namespace td {
+
+bool DialogAction::is_valid_emoji(string &emoji) {
+ if (!clean_input_string(emoji)) {
+ return false;
+ }
+ return is_emoji(emoji);
+}
+
+void DialogAction::init(Type type) {
+ type_ = type;
+ progress_ = 0;
+ emoji_.clear();
+}
+
+void DialogAction::init(Type type, int32 progress) {
+ type_ = type;
+ progress_ = clamp(progress, 0, 100);
+ emoji_.clear();
+}
+
+void DialogAction::init(Type type, string emoji) {
+ if (is_valid_emoji(emoji)) {
+ type_ = type;
+ progress_ = 0;
+ emoji_ = std::move(emoji);
+ } else {
+ init(Type::Cancel);
+ }
+}
+
+void DialogAction::init(Type type, int32 message_id, string emoji, const string &data) {
+ if (ServerMessageId(message_id).is_valid() && is_valid_emoji(emoji) && check_utf8(data)) {
+ type_ = type;
+ progress_ = message_id;
+ emoji_ = PSTRING() << emoji << '\xFF' << data;
+ } else {
+ init(Type::Cancel);
+ }
+}
+
+DialogAction::DialogAction(Type type, int32 progress) {
+ init(type, progress);
+}
+
+DialogAction::DialogAction(td_api::object_ptr<td_api::ChatAction> &&action) {
+ if (action == nullptr) {
+ return;
+ }
+
+ switch (action->get_id()) {
+ case td_api::chatActionCancel::ID:
+ init(Type::Cancel);
+ break;
+ case td_api::chatActionTyping::ID:
+ init(Type::Typing);
+ break;
+ case td_api::chatActionRecordingVideo::ID:
+ init(Type::RecordingVideo);
+ break;
+ case td_api::chatActionUploadingVideo::ID: {
+ auto uploading_action = move_tl_object_as<td_api::chatActionUploadingVideo>(action);
+ init(Type::UploadingVideo, uploading_action->progress_);
+ break;
+ }
+ case td_api::chatActionRecordingVoiceNote::ID:
+ init(Type::RecordingVoiceNote);
+ break;
+ case td_api::chatActionUploadingVoiceNote::ID: {
+ auto uploading_action = move_tl_object_as<td_api::chatActionUploadingVoiceNote>(action);
+ init(Type::UploadingVoiceNote, uploading_action->progress_);
+ break;
+ }
+ case td_api::chatActionUploadingPhoto::ID: {
+ auto uploading_action = move_tl_object_as<td_api::chatActionUploadingPhoto>(action);
+ init(Type::UploadingPhoto, uploading_action->progress_);
+ break;
+ }
+ case td_api::chatActionUploadingDocument::ID: {
+ auto uploading_action = move_tl_object_as<td_api::chatActionUploadingDocument>(action);
+ init(Type::UploadingDocument, uploading_action->progress_);
+ break;
+ }
+ case td_api::chatActionChoosingLocation::ID:
+ init(Type::ChoosingLocation);
+ break;
+ case td_api::chatActionChoosingContact::ID:
+ init(Type::ChoosingContact);
+ break;
+ case td_api::chatActionStartPlayingGame::ID:
+ init(Type::StartPlayingGame);
+ break;
+ case td_api::chatActionRecordingVideoNote::ID:
+ init(Type::RecordingVideoNote);
+ break;
+ case td_api::chatActionUploadingVideoNote::ID: {
+ auto uploading_action = move_tl_object_as<td_api::chatActionUploadingVideoNote>(action);
+ init(Type::UploadingVideoNote, uploading_action->progress_);
+ break;
+ }
+ case td_api::chatActionChoosingSticker::ID:
+ init(Type::ChoosingSticker);
+ break;
+ case td_api::chatActionWatchingAnimations::ID: {
+ auto watching_animations_action = move_tl_object_as<td_api::chatActionWatchingAnimations>(action);
+ init(Type::WatchingAnimations, std::move(watching_animations_action->emoji_));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+DialogAction::DialogAction(telegram_api::object_ptr<telegram_api::SendMessageAction> &&action) {
+ switch (action->get_id()) {
+ case telegram_api::sendMessageCancelAction::ID:
+ init(Type::Cancel);
+ break;
+ case telegram_api::sendMessageTypingAction::ID:
+ init(Type::Typing);
+ break;
+ case telegram_api::sendMessageRecordVideoAction::ID:
+ init(Type::RecordingVideo);
+ break;
+ case telegram_api::sendMessageUploadVideoAction::ID: {
+ auto upload_video_action = move_tl_object_as<telegram_api::sendMessageUploadVideoAction>(action);
+ init(Type::UploadingVideo, upload_video_action->progress_);
+ break;
+ }
+ case telegram_api::sendMessageRecordAudioAction::ID:
+ init(Type::RecordingVoiceNote);
+ break;
+ case telegram_api::sendMessageUploadAudioAction::ID: {
+ auto upload_audio_action = move_tl_object_as<telegram_api::sendMessageUploadAudioAction>(action);
+ init(Type::UploadingVoiceNote, upload_audio_action->progress_);
+ break;
+ }
+ case telegram_api::sendMessageUploadPhotoAction::ID: {
+ auto upload_photo_action = move_tl_object_as<telegram_api::sendMessageUploadPhotoAction>(action);
+ init(Type::UploadingPhoto, upload_photo_action->progress_);
+ break;
+ }
+ case telegram_api::sendMessageUploadDocumentAction::ID: {
+ auto upload_document_action = move_tl_object_as<telegram_api::sendMessageUploadDocumentAction>(action);
+ init(Type::UploadingDocument, upload_document_action->progress_);
+ break;
+ }
+ case telegram_api::sendMessageGeoLocationAction::ID:
+ init(Type::ChoosingLocation);
+ break;
+ case telegram_api::sendMessageChooseContactAction::ID:
+ init(Type::ChoosingContact);
+ break;
+ case telegram_api::sendMessageGamePlayAction::ID:
+ init(Type::StartPlayingGame);
+ break;
+ case telegram_api::sendMessageRecordRoundAction::ID:
+ init(Type::RecordingVideoNote);
+ break;
+ case telegram_api::sendMessageUploadRoundAction::ID: {
+ auto upload_round_action = move_tl_object_as<telegram_api::sendMessageUploadRoundAction>(action);
+ init(Type::UploadingVideoNote, upload_round_action->progress_);
+ break;
+ }
+ case telegram_api::speakingInGroupCallAction::ID:
+ init(Type::SpeakingInVoiceChat);
+ break;
+ case telegram_api::sendMessageHistoryImportAction::ID: {
+ auto history_import_action = move_tl_object_as<telegram_api::sendMessageHistoryImportAction>(action);
+ init(Type::ImportingMessages, history_import_action->progress_);
+ break;
+ }
+ case telegram_api::sendMessageChooseStickerAction::ID:
+ init(Type::ChoosingSticker);
+ break;
+ case telegram_api::sendMessageEmojiInteractionSeen::ID: {
+ auto emoji_interaction_seen_action = move_tl_object_as<telegram_api::sendMessageEmojiInteractionSeen>(action);
+ init(Type::WatchingAnimations, std::move(emoji_interaction_seen_action->emoticon_));
+ break;
+ }
+ case telegram_api::sendMessageEmojiInteraction::ID: {
+ auto emoji_interaction_action = move_tl_object_as<telegram_api::sendMessageEmojiInteraction>(action);
+ init(Type::ClickingAnimatedEmoji, emoji_interaction_action->msg_id_,
+ std::move(emoji_interaction_action->emoticon_), emoji_interaction_action->interaction_->data_);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+tl_object_ptr<telegram_api::SendMessageAction> DialogAction::get_input_send_message_action() const {
+ switch (type_) {
+ case Type::Cancel:
+ return make_tl_object<telegram_api::sendMessageCancelAction>();
+ case Type::Typing:
+ return make_tl_object<telegram_api::sendMessageTypingAction>();
+ case Type::RecordingVideo:
+ return make_tl_object<telegram_api::sendMessageRecordVideoAction>();
+ case Type::UploadingVideo:
+ return make_tl_object<telegram_api::sendMessageUploadVideoAction>(progress_);
+ case Type::RecordingVoiceNote:
+ return make_tl_object<telegram_api::sendMessageRecordAudioAction>();
+ case Type::UploadingVoiceNote:
+ return make_tl_object<telegram_api::sendMessageUploadAudioAction>(progress_);
+ case Type::UploadingPhoto:
+ return make_tl_object<telegram_api::sendMessageUploadPhotoAction>(progress_);
+ case Type::UploadingDocument:
+ return make_tl_object<telegram_api::sendMessageUploadDocumentAction>(progress_);
+ case Type::ChoosingLocation:
+ return make_tl_object<telegram_api::sendMessageGeoLocationAction>();
+ case Type::ChoosingContact:
+ return make_tl_object<telegram_api::sendMessageChooseContactAction>();
+ case Type::StartPlayingGame:
+ return make_tl_object<telegram_api::sendMessageGamePlayAction>();
+ case Type::RecordingVideoNote:
+ return make_tl_object<telegram_api::sendMessageRecordRoundAction>();
+ case Type::UploadingVideoNote:
+ return make_tl_object<telegram_api::sendMessageUploadRoundAction>(progress_);
+ case Type::SpeakingInVoiceChat:
+ return make_tl_object<telegram_api::speakingInGroupCallAction>();
+ case Type::ImportingMessages:
+ return make_tl_object<telegram_api::sendMessageHistoryImportAction>(progress_);
+ case Type::ChoosingSticker:
+ return make_tl_object<telegram_api::sendMessageChooseStickerAction>();
+ case Type::WatchingAnimations:
+ return make_tl_object<telegram_api::sendMessageEmojiInteractionSeen>(emoji_);
+ case Type::ClickingAnimatedEmoji:
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+tl_object_ptr<secret_api::SendMessageAction> DialogAction::get_secret_input_send_message_action() const {
+ switch (type_) {
+ case Type::Cancel:
+ return make_tl_object<secret_api::sendMessageCancelAction>();
+ case Type::Typing:
+ return make_tl_object<secret_api::sendMessageTypingAction>();
+ case Type::RecordingVideo:
+ return make_tl_object<secret_api::sendMessageRecordVideoAction>();
+ case Type::UploadingVideo:
+ return make_tl_object<secret_api::sendMessageUploadVideoAction>();
+ case Type::RecordingVoiceNote:
+ return make_tl_object<secret_api::sendMessageRecordAudioAction>();
+ case Type::UploadingVoiceNote:
+ return make_tl_object<secret_api::sendMessageUploadAudioAction>();
+ case Type::UploadingPhoto:
+ return make_tl_object<secret_api::sendMessageUploadPhotoAction>();
+ case Type::UploadingDocument:
+ return make_tl_object<secret_api::sendMessageUploadDocumentAction>();
+ case Type::ChoosingLocation:
+ return make_tl_object<secret_api::sendMessageGeoLocationAction>();
+ case Type::ChoosingContact:
+ return make_tl_object<secret_api::sendMessageChooseContactAction>();
+ case Type::StartPlayingGame:
+ return make_tl_object<secret_api::sendMessageTypingAction>();
+ case Type::RecordingVideoNote:
+ return make_tl_object<secret_api::sendMessageRecordRoundAction>();
+ case Type::UploadingVideoNote:
+ return make_tl_object<secret_api::sendMessageUploadRoundAction>();
+ case Type::SpeakingInVoiceChat:
+ return make_tl_object<secret_api::sendMessageTypingAction>();
+ case Type::ImportingMessages:
+ return make_tl_object<secret_api::sendMessageTypingAction>();
+ case Type::ChoosingSticker:
+ return make_tl_object<secret_api::sendMessageTypingAction>();
+ case Type::WatchingAnimations:
+ return make_tl_object<secret_api::sendMessageTypingAction>();
+ case Type::ClickingAnimatedEmoji:
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+tl_object_ptr<td_api::ChatAction> DialogAction::get_chat_action_object() const {
+ switch (type_) {
+ case Type::Cancel:
+ return td_api::make_object<td_api::chatActionCancel>();
+ case Type::Typing:
+ return td_api::make_object<td_api::chatActionTyping>();
+ case Type::RecordingVideo:
+ return td_api::make_object<td_api::chatActionRecordingVideo>();
+ case Type::UploadingVideo:
+ return td_api::make_object<td_api::chatActionUploadingVideo>(progress_);
+ case Type::RecordingVoiceNote:
+ return td_api::make_object<td_api::chatActionRecordingVoiceNote>();
+ case Type::UploadingVoiceNote:
+ return td_api::make_object<td_api::chatActionUploadingVoiceNote>(progress_);
+ case Type::UploadingPhoto:
+ return td_api::make_object<td_api::chatActionUploadingPhoto>(progress_);
+ case Type::UploadingDocument:
+ return td_api::make_object<td_api::chatActionUploadingDocument>(progress_);
+ case Type::ChoosingLocation:
+ return td_api::make_object<td_api::chatActionChoosingLocation>();
+ case Type::ChoosingContact:
+ return td_api::make_object<td_api::chatActionChoosingContact>();
+ case Type::StartPlayingGame:
+ return td_api::make_object<td_api::chatActionStartPlayingGame>();
+ case Type::RecordingVideoNote:
+ return td_api::make_object<td_api::chatActionRecordingVideoNote>();
+ case Type::UploadingVideoNote:
+ return td_api::make_object<td_api::chatActionUploadingVideoNote>(progress_);
+ case Type::ChoosingSticker:
+ return td_api::make_object<td_api::chatActionChoosingSticker>();
+ case Type::WatchingAnimations:
+ return td_api::make_object<td_api::chatActionWatchingAnimations>(emoji_);
+ case Type::ImportingMessages:
+ case Type::SpeakingInVoiceChat:
+ case Type::ClickingAnimatedEmoji:
+ default:
+ UNREACHABLE();
+ return td_api::make_object<td_api::chatActionCancel>();
+ }
+}
+
+bool DialogAction::is_canceled_by_message_of_type(MessageContentType message_content_type) const {
+ if (message_content_type == MessageContentType::None) {
+ return true;
+ }
+
+ if (type_ == Type::Typing) {
+ return message_content_type == MessageContentType::Text || message_content_type == MessageContentType::Game ||
+ can_have_message_content_caption(message_content_type);
+ }
+
+ switch (message_content_type) {
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Document:
+ return type_ == Type::UploadingDocument;
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::Photo:
+ return type_ == Type::UploadingPhoto;
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::Video:
+ return type_ == Type::RecordingVideo || type_ == Type::UploadingVideo;
+ case MessageContentType::VideoNote:
+ return type_ == Type::RecordingVideoNote || type_ == Type::UploadingVideoNote;
+ case MessageContentType::VoiceNote:
+ return type_ == Type::RecordingVoiceNote || type_ == Type::UploadingVoiceNote;
+ case MessageContentType::Contact:
+ return type_ == Type::ChoosingContact;
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Venue:
+ return type_ == Type::ChoosingLocation;
+ case MessageContentType::Sticker:
+ return type_ == Type::ChoosingSticker;
+ case MessageContentType::Game:
+ case MessageContentType::Invoice:
+ case MessageContentType::Text:
+ case MessageContentType::Unsupported:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::Poll:
+ case MessageContentType::Dice:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+DialogAction DialogAction::get_uploading_action(MessageContentType message_content_type, int32 progress) {
+ switch (message_content_type) {
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Document:
+ return DialogAction(Type::UploadingDocument, progress);
+ case MessageContentType::Photo:
+ return DialogAction(Type::UploadingPhoto, progress);
+ case MessageContentType::Video:
+ return DialogAction(Type::UploadingVideo, progress);
+ case MessageContentType::VideoNote:
+ return DialogAction(Type::UploadingVideoNote, progress);
+ case MessageContentType::VoiceNote:
+ return DialogAction(Type::UploadingVoiceNote, progress);
+ default:
+ return DialogAction();
+ }
+}
+
+DialogAction DialogAction::get_typing_action() {
+ return DialogAction(Type::Typing, 0);
+}
+
+DialogAction DialogAction::get_speaking_action() {
+ return DialogAction(Type::SpeakingInVoiceChat, 0);
+}
+
+int32 DialogAction::get_importing_messages_action_progress() const {
+ if (type_ != Type::ImportingMessages) {
+ return -1;
+ }
+ return progress_;
+}
+
+string DialogAction::get_watching_animations_emoji() const {
+ if (type_ == Type::WatchingAnimations) {
+ return emoji_;
+ }
+ return string();
+}
+
+DialogAction::ClickingAnimateEmojiInfo DialogAction::get_clicking_animated_emoji_action_info() const {
+ ClickingAnimateEmojiInfo result;
+ if (type_ == Type::ClickingAnimatedEmoji) {
+ auto pos = emoji_.find('\xFF');
+ CHECK(pos < emoji_.size());
+ result.message_id = progress_;
+ result.emoji = emoji_.substr(0, pos);
+ result.data = emoji_.substr(pos + 1);
+ }
+ return result;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogAction &action) {
+ string_builder << "ChatAction";
+ const char *type = [action_type = action.type_] {
+ switch (action_type) {
+ case DialogAction::Type::Cancel:
+ return "Cancel";
+ case DialogAction::Type::Typing:
+ return "Typing";
+ case DialogAction::Type::RecordingVideo:
+ return "RecordingVideo";
+ case DialogAction::Type::UploadingVideo:
+ return "UploadingVideo";
+ case DialogAction::Type::RecordingVoiceNote:
+ return "RecordingVoiceNote";
+ case DialogAction::Type::UploadingVoiceNote:
+ return "UploadingVoiceNote";
+ case DialogAction::Type::UploadingPhoto:
+ return "UploadingPhoto";
+ case DialogAction::Type::UploadingDocument:
+ return "UploadingDocument";
+ case DialogAction::Type::ChoosingLocation:
+ return "ChoosingLocation";
+ case DialogAction::Type::ChoosingContact:
+ return "ChoosingContact";
+ case DialogAction::Type::StartPlayingGame:
+ return "StartPlayingGame";
+ case DialogAction::Type::RecordingVideoNote:
+ return "RecordingVideoNote";
+ case DialogAction::Type::UploadingVideoNote:
+ return "UploadingVideoNote";
+ case DialogAction::Type::SpeakingInVoiceChat:
+ return "SpeakingInVoiceChat";
+ case DialogAction::Type::ImportingMessages:
+ return "ImportingMessages";
+ case DialogAction::Type::ChoosingSticker:
+ return "ChoosingSticker";
+ case DialogAction::Type::WatchingAnimations:
+ return "WatchingAnimations";
+ case DialogAction::Type::ClickingAnimatedEmoji:
+ return "ClickingAnimatedEmoji";
+ default:
+ UNREACHABLE();
+ return "Cancel";
+ }
+ }();
+ string_builder << type << "Action";
+ if (action.type_ == DialogAction::Type::ClickingAnimatedEmoji) {
+ auto pos = action.emoji_.find('\xFF');
+ CHECK(pos < action.emoji_.size());
+ string_builder << '(' << action.progress_ << ")(" << Slice(action.emoji_).substr(0, pos) << ")("
+ << Slice(action.emoji_).substr(pos + 1) << ')';
+ } else {
+ if (action.progress_ != 0) {
+ string_builder << '(' << action.progress_ << "%)";
+ }
+ if (!action.emoji_.empty()) {
+ string_builder << '(' << action.emoji_ << ')';
+ }
+ }
+ return string_builder;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogAction.h b/protocols/Telegram/tdlib/td/td/telegram/DialogAction.h
new file mode 100644
index 0000000000..e28479f032
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogAction.h
@@ -0,0 +1,101 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MessageContentType.h"
+#include "td/telegram/secret_api.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class DialogAction {
+ enum class Type : int32 {
+ Cancel,
+ Typing,
+ RecordingVideo,
+ UploadingVideo,
+ RecordingVoiceNote,
+ UploadingVoiceNote,
+ UploadingPhoto,
+ UploadingDocument,
+ ChoosingLocation,
+ ChoosingContact,
+ StartPlayingGame,
+ RecordingVideoNote,
+ UploadingVideoNote,
+ SpeakingInVoiceChat,
+ ImportingMessages,
+ ChoosingSticker,
+ WatchingAnimations,
+ ClickingAnimatedEmoji
+ };
+ Type type_ = Type::Cancel;
+ int32 progress_ = 0;
+ string emoji_;
+
+ DialogAction(Type type, int32 progress);
+
+ void init(Type type);
+
+ void init(Type type, int32 progress);
+
+ void init(Type type, string emoji);
+
+ void init(Type type, int32 message_id, string emoji, const string &data);
+
+ static bool is_valid_emoji(string &emoji);
+
+ public:
+ DialogAction() = default;
+
+ explicit DialogAction(td_api::object_ptr<td_api::ChatAction> &&action);
+
+ explicit DialogAction(telegram_api::object_ptr<telegram_api::SendMessageAction> &&action);
+
+ tl_object_ptr<telegram_api::SendMessageAction> get_input_send_message_action() const;
+
+ tl_object_ptr<secret_api::SendMessageAction> get_secret_input_send_message_action() const;
+
+ td_api::object_ptr<td_api::ChatAction> get_chat_action_object() const;
+
+ bool is_canceled_by_message_of_type(MessageContentType message_content_type) const;
+
+ static DialogAction get_uploading_action(MessageContentType message_content_type, int32 progress);
+
+ static DialogAction get_typing_action();
+
+ static DialogAction get_speaking_action();
+
+ int32 get_importing_messages_action_progress() const;
+
+ string get_watching_animations_emoji() const;
+
+ struct ClickingAnimateEmojiInfo {
+ int32 message_id = 0;
+ string emoji;
+ string data;
+ };
+ ClickingAnimateEmojiInfo get_clicking_animated_emoji_action_info() const;
+
+ friend bool operator==(const DialogAction &lhs, const DialogAction &rhs) {
+ return lhs.type_ == rhs.type_ && lhs.progress_ == rhs.progress_ && lhs.emoji_ == rhs.emoji_;
+ }
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogAction &action);
+};
+
+inline bool operator!=(const DialogAction &lhs, const DialogAction &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogAction &action);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogActionBar.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogActionBar.cpp
new file mode 100644
index 0000000000..4ec2b51136
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogActionBar.cpp
@@ -0,0 +1,296 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogActionBar.h"
+
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/logging.h"
+
+namespace td {
+
+unique_ptr<DialogActionBar> DialogActionBar::create(bool can_report_spam, bool can_add_contact, bool can_block_user,
+ bool can_share_phone_number, bool can_report_location,
+ bool can_unarchive, int32 distance, bool can_invite_members,
+ string join_request_dialog_title, bool is_join_request_broadcast,
+ int32 join_request_date) {
+ if (!can_report_spam && !can_add_contact && !can_block_user && !can_share_phone_number && !can_report_location &&
+ !can_invite_members && join_request_dialog_title.empty()) {
+ return nullptr;
+ }
+
+ auto action_bar = make_unique<DialogActionBar>();
+ action_bar->can_report_spam_ = can_report_spam;
+ action_bar->can_add_contact_ = can_add_contact;
+ action_bar->can_block_user_ = can_block_user;
+ action_bar->can_share_phone_number_ = can_share_phone_number;
+ action_bar->can_report_location_ = can_report_location;
+ action_bar->can_unarchive_ = can_unarchive;
+ action_bar->distance_ = distance >= 0 ? distance : -1;
+ action_bar->can_invite_members_ = can_invite_members;
+ action_bar->join_request_dialog_title_ = std::move(join_request_dialog_title);
+ action_bar->is_join_request_broadcast_ = is_join_request_broadcast;
+ action_bar->join_request_date_ = join_request_date;
+ return action_bar;
+}
+
+bool DialogActionBar::is_empty() const {
+ return !can_report_spam_ && !can_add_contact_ && !can_block_user_ && !can_share_phone_number_ &&
+ !can_report_location_ && !can_invite_members_ && join_request_dialog_title_.empty();
+}
+
+void DialogActionBar::fix(Td *td, DialogId dialog_id, bool is_dialog_blocked, FolderId folder_id) {
+ auto dialog_type = dialog_id.get_type();
+ if (distance_ >= 0 && dialog_type != DialogType::User) {
+ LOG(ERROR) << "Receive distance " << distance_ << " to " << dialog_id;
+ distance_ = -1;
+ }
+
+ if (!join_request_dialog_title_.empty()) {
+ if (dialog_type != DialogType::User || join_request_date_ <= 0) {
+ LOG(ERROR) << "Receive join_request_date = " << join_request_date_ << " in " << dialog_id;
+ join_request_dialog_title_.clear();
+ is_join_request_broadcast_ = false;
+ join_request_date_ = 0;
+ } else if (can_report_location_ || can_report_spam_ || can_add_contact_ || can_block_user_ ||
+ can_share_phone_number_ || can_unarchive_ || can_invite_members_) {
+ LOG(ERROR) << "Receive action bar " << can_report_location_ << '/' << can_report_spam_ << '/' << can_add_contact_
+ << '/' << can_block_user_ << '/' << can_share_phone_number_ << '/' << can_report_location_ << '/'
+ << can_unarchive_ << '/' << can_invite_members_;
+ can_report_location_ = false;
+ can_report_spam_ = false;
+ can_add_contact_ = false;
+ can_block_user_ = false;
+ can_share_phone_number_ = false;
+ can_unarchive_ = false;
+ can_invite_members_ = false;
+ distance_ = -1;
+ }
+ }
+ if (join_request_dialog_title_.empty() && (is_join_request_broadcast_ || join_request_date_ != 0)) {
+ LOG(ERROR) << "Receive join request date = " << join_request_date_ << " and " << is_join_request_broadcast_
+ << " in " << dialog_id;
+ is_join_request_broadcast_ = false;
+ join_request_date_ = 0;
+ }
+ if (can_report_location_) {
+ if (dialog_type != DialogType::Channel) {
+ LOG(ERROR) << "Receive can_report_location in " << dialog_id;
+ can_report_location_ = false;
+ } else if (can_report_spam_ || can_add_contact_ || can_block_user_ || can_share_phone_number_ || can_unarchive_ ||
+ can_invite_members_) {
+ LOG(ERROR) << "Receive action bar " << can_report_spam_ << '/' << can_add_contact_ << '/' << can_block_user_
+ << '/' << can_share_phone_number_ << '/' << can_report_location_ << '/' << can_unarchive_ << '/'
+ << can_invite_members_;
+ can_report_spam_ = false;
+ can_add_contact_ = false;
+ can_block_user_ = false;
+ can_share_phone_number_ = false;
+ can_unarchive_ = false;
+ can_invite_members_ = false;
+ CHECK(distance_ == -1);
+ }
+ }
+ if (can_invite_members_) {
+ if (dialog_type != DialogType::Chat && (dialog_type != DialogType::Channel ||
+ td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()))) {
+ LOG(ERROR) << "Receive can_invite_members in " << dialog_id;
+ can_invite_members_ = false;
+ } else if (can_report_spam_ || can_add_contact_ || can_block_user_ || can_share_phone_number_ || can_unarchive_) {
+ LOG(ERROR) << "Receive action bar " << can_report_spam_ << '/' << can_add_contact_ << '/' << can_block_user_
+ << '/' << can_share_phone_number_ << '/' << can_unarchive_ << '/' << can_invite_members_;
+ can_report_spam_ = false;
+ can_add_contact_ = false;
+ can_block_user_ = false;
+ can_share_phone_number_ = false;
+ can_unarchive_ = false;
+ CHECK(distance_ == -1);
+ }
+ }
+ if (dialog_type == DialogType::User) {
+ auto user_id = dialog_id.get_user_id();
+ bool is_me = user_id == td->contacts_manager_->get_my_id();
+ bool is_deleted = td->contacts_manager_->is_user_deleted(user_id);
+ bool is_contact = td->contacts_manager_->is_user_contact(user_id);
+ if (is_me || is_dialog_blocked) {
+ can_report_spam_ = false;
+ can_unarchive_ = false;
+ }
+ if (is_me || is_dialog_blocked || is_deleted) {
+ can_share_phone_number_ = false;
+ }
+ if (is_me || is_dialog_blocked || is_deleted || is_contact) {
+ can_block_user_ = false;
+ can_add_contact_ = false;
+ }
+ }
+ if (folder_id != FolderId::archive()) {
+ can_unarchive_ = false;
+ }
+ if (can_share_phone_number_) {
+ CHECK(!can_report_location_);
+ CHECK(!can_invite_members_);
+ if (dialog_type != DialogType::User) {
+ LOG(ERROR) << "Receive can_share_phone_number in " << dialog_id;
+ can_share_phone_number_ = false;
+ } else if (can_report_spam_ || can_add_contact_ || can_block_user_ || can_unarchive_ || distance_ >= 0) {
+ LOG(ERROR) << "Receive action bar " << can_report_spam_ << '/' << can_add_contact_ << '/' << can_block_user_
+ << '/' << can_share_phone_number_ << '/' << can_unarchive_ << '/' << distance_;
+ can_report_spam_ = false;
+ can_add_contact_ = false;
+ can_block_user_ = false;
+ can_unarchive_ = false;
+ }
+ }
+ if (can_block_user_) {
+ CHECK(!can_report_location_);
+ CHECK(!can_invite_members_);
+ CHECK(!can_share_phone_number_);
+ if (dialog_type != DialogType::User) {
+ LOG(ERROR) << "Receive can_block_user in " << dialog_id;
+ can_block_user_ = false;
+ } else if (!can_report_spam_ || !can_add_contact_) {
+ LOG(ERROR) << "Receive action bar " << can_report_spam_ << '/' << can_add_contact_ << '/' << can_block_user_;
+ can_report_spam_ = true;
+ can_add_contact_ = true;
+ }
+ }
+ if (can_add_contact_) {
+ CHECK(!can_report_location_);
+ CHECK(!can_invite_members_);
+ CHECK(!can_share_phone_number_);
+ if (dialog_type != DialogType::User) {
+ LOG(ERROR) << "Receive can_add_contact in " << dialog_id;
+ can_add_contact_ = false;
+ } else if (can_report_spam_ != can_block_user_) {
+ LOG(ERROR) << "Receive action bar " << can_report_spam_ << '/' << can_add_contact_ << '/' << can_block_user_;
+ can_report_spam_ = false;
+ can_block_user_ = false;
+ can_unarchive_ = false;
+ }
+ }
+ if (!can_block_user_) {
+ distance_ = -1;
+ }
+ if (!can_report_spam_) {
+ can_unarchive_ = false;
+ }
+}
+
+td_api::object_ptr<td_api::ChatActionBar> DialogActionBar::get_chat_action_bar_object(DialogType dialog_type,
+ bool hide_unarchive) const {
+ if (!join_request_dialog_title_.empty()) {
+ CHECK(dialog_type == DialogType::User);
+ CHECK(!can_report_location_ && !can_share_phone_number_ && !can_block_user_ && !can_add_contact_ &&
+ !can_report_spam_ && !can_invite_members_);
+ return td_api::make_object<td_api::chatActionBarJoinRequest>(join_request_dialog_title_, is_join_request_broadcast_,
+ join_request_date_);
+ }
+ if (can_report_location_) {
+ CHECK(dialog_type == DialogType::Channel);
+ CHECK(!can_share_phone_number_ && !can_block_user_ && !can_add_contact_ && !can_report_spam_ &&
+ !can_invite_members_);
+ return td_api::make_object<td_api::chatActionBarReportUnrelatedLocation>();
+ }
+ if (can_invite_members_) {
+ CHECK(!can_share_phone_number_ && !can_block_user_ && !can_add_contact_ && !can_report_spam_);
+ return td_api::make_object<td_api::chatActionBarInviteMembers>();
+ }
+ if (can_share_phone_number_) {
+ CHECK(dialog_type == DialogType::User);
+ CHECK(!can_block_user_ && !can_add_contact_ && !can_report_spam_);
+ return td_api::make_object<td_api::chatActionBarSharePhoneNumber>();
+ }
+ if (hide_unarchive) {
+ if (can_add_contact_) {
+ return td_api::make_object<td_api::chatActionBarAddContact>();
+ } else {
+ return nullptr;
+ }
+ }
+ if (can_block_user_) {
+ CHECK(dialog_type == DialogType::User);
+ CHECK(can_report_spam_ && can_add_contact_);
+ return td_api::make_object<td_api::chatActionBarReportAddBlock>(can_unarchive_, distance_);
+ }
+ if (can_add_contact_) {
+ CHECK(dialog_type == DialogType::User);
+ CHECK(!can_report_spam_);
+ return td_api::make_object<td_api::chatActionBarAddContact>();
+ }
+ if (can_report_spam_) {
+ return td_api::make_object<td_api::chatActionBarReportSpam>(can_unarchive_);
+ }
+ return nullptr;
+}
+
+bool DialogActionBar::on_dialog_unarchived() {
+ if (!can_unarchive_) {
+ return false;
+ }
+
+ can_unarchive_ = false;
+ can_report_spam_ = false;
+ can_block_user_ = false;
+ // keep can_add_contact_
+ return true;
+}
+
+bool DialogActionBar::on_user_contact_added() {
+ if (!can_block_user_ && !can_add_contact_) {
+ return false;
+ }
+
+ can_block_user_ = false;
+ can_add_contact_ = false;
+ // keep can_unarchive_
+ distance_ = -1;
+ return true;
+}
+
+bool DialogActionBar::on_user_deleted() {
+ if (join_request_dialog_title_.empty() && !can_share_phone_number_ && !can_block_user_ && !can_add_contact_ &&
+ distance_ < 0) {
+ return false;
+ }
+
+ join_request_dialog_title_.clear();
+ is_join_request_broadcast_ = false;
+ join_request_date_ = 0;
+ can_share_phone_number_ = false;
+ can_block_user_ = false;
+ can_add_contact_ = false;
+ distance_ = -1;
+ return true;
+}
+
+bool DialogActionBar::on_outgoing_message() {
+ if (distance_ < 0) {
+ return false;
+ }
+
+ distance_ = -1;
+ return true;
+}
+
+bool operator==(const unique_ptr<DialogActionBar> &lhs, const unique_ptr<DialogActionBar> &rhs) {
+ if (lhs == nullptr) {
+ return rhs == nullptr;
+ }
+ if (rhs == nullptr) {
+ return false;
+ }
+ return lhs->can_report_spam_ == rhs->can_report_spam_ && lhs->can_add_contact_ == rhs->can_add_contact_ &&
+ lhs->can_block_user_ == rhs->can_block_user_ && lhs->can_share_phone_number_ == rhs->can_share_phone_number_ &&
+ lhs->can_report_location_ == rhs->can_report_location_ && lhs->can_unarchive_ == rhs->can_unarchive_ &&
+ lhs->distance_ == rhs->distance_ && lhs->can_invite_members_ == rhs->can_invite_members_ &&
+ lhs->join_request_dialog_title_ == rhs->join_request_dialog_title_ &&
+ lhs->is_join_request_broadcast_ == lhs->is_join_request_broadcast_ &&
+ lhs->join_request_date_ == rhs->join_request_date_;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogActionBar.h b/protocols/Telegram/tdlib/td/td/telegram/DialogActionBar.h
new file mode 100644
index 0000000000..bbdb4de98a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogActionBar.h
@@ -0,0 +1,118 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/FolderId.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class Td;
+
+class DialogActionBar {
+ int32 distance_ = -1; // distance to the peer
+ int32 join_request_date_ = 0;
+ string join_request_dialog_title_;
+
+ bool can_report_spam_ = false;
+ bool can_add_contact_ = false;
+ bool can_block_user_ = false;
+ bool can_share_phone_number_ = false;
+ bool can_report_location_ = false;
+ bool can_unarchive_ = false;
+ bool can_invite_members_ = false;
+ bool is_join_request_broadcast_ = false;
+
+ friend bool operator==(const unique_ptr<DialogActionBar> &lhs, const unique_ptr<DialogActionBar> &rhs);
+
+ public:
+ static unique_ptr<DialogActionBar> create(bool can_report_spam, bool can_add_contact, bool can_block_user,
+ bool can_share_phone_number, bool can_report_location, bool can_unarchive,
+ int32 distance, bool can_invite_members, string join_request_dialog_title,
+ bool is_join_request_broadcast, int32 join_request_date);
+
+ bool is_empty() const;
+
+ bool can_report_spam() const {
+ return can_report_spam_;
+ }
+
+ bool can_unarchive() const {
+ return can_unarchive_;
+ }
+
+ td_api::object_ptr<td_api::ChatActionBar> get_chat_action_bar_object(DialogType dialog_type,
+ bool hide_unarchive) const;
+
+ void fix(Td *td, DialogId dialog_id, bool is_dialog_blocked, FolderId folder_id);
+
+ bool on_dialog_unarchived();
+
+ bool on_user_contact_added();
+
+ bool on_user_deleted();
+
+ bool on_outgoing_message();
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ bool has_distance = distance_ >= 0;
+ bool has_join_request = !join_request_dialog_title_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(can_report_spam_);
+ STORE_FLAG(can_add_contact_);
+ STORE_FLAG(can_block_user_);
+ STORE_FLAG(can_share_phone_number_);
+ STORE_FLAG(can_report_location_);
+ STORE_FLAG(can_unarchive_);
+ STORE_FLAG(can_invite_members_);
+ STORE_FLAG(has_distance);
+ STORE_FLAG(is_join_request_broadcast_);
+ STORE_FLAG(has_join_request);
+ END_STORE_FLAGS();
+ if (has_distance) {
+ td::store(distance_, storer);
+ }
+ if (has_join_request) {
+ td::store(join_request_dialog_title_, storer);
+ td::store(join_request_date_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ bool has_distance;
+ bool has_join_request;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(can_report_spam_);
+ PARSE_FLAG(can_add_contact_);
+ PARSE_FLAG(can_block_user_);
+ PARSE_FLAG(can_share_phone_number_);
+ PARSE_FLAG(can_report_location_);
+ PARSE_FLAG(can_unarchive_);
+ PARSE_FLAG(can_invite_members_);
+ PARSE_FLAG(has_distance);
+ PARSE_FLAG(is_join_request_broadcast_);
+ PARSE_FLAG(has_join_request);
+ END_PARSE_FLAGS();
+ if (has_distance) {
+ td::parse(distance_, parser);
+ }
+ if (has_join_request) {
+ td::parse(join_request_dialog_title_, parser);
+ td::parse(join_request_date_, parser);
+ }
+ }
+};
+
+bool operator==(const unique_ptr<DialogActionBar> &lhs, const unique_ptr<DialogActionBar> &rhs);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogAdministrator.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogAdministrator.cpp
new file mode 100644
index 0000000000..c3dd674abf
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogAdministrator.cpp
@@ -0,0 +1,26 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogAdministrator.h"
+
+#include "td/telegram/ContactsManager.h"
+
+namespace td {
+
+td_api::object_ptr<td_api::chatAdministrator> DialogAdministrator::get_chat_administrator_object(
+ const ContactsManager *contacts_manager) const {
+ CHECK(contacts_manager != nullptr);
+ CHECK(user_id_.is_valid());
+ return td_api::make_object<td_api::chatAdministrator>(
+ contacts_manager->get_user_id_object(user_id_, "get_chat_administrator_object"), rank_, is_creator_);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogAdministrator &administrator) {
+ return string_builder << "ChatAdministrator[" << administrator.user_id_ << ", title = " << administrator.rank_
+ << ", is_owner = " << administrator.is_creator_ << "]";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogAdministrator.h b/protocols/Telegram/tdlib/td/td/telegram/DialogAdministrator.h
new file mode 100644
index 0000000000..17c662559d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogAdministrator.h
@@ -0,0 +1,89 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class ContactsManager;
+
+class DialogAdministrator {
+ UserId user_id_;
+ string rank_;
+ bool is_creator_ = false;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogAdministrator &administrator);
+
+ public:
+ DialogAdministrator() = default;
+
+ DialogAdministrator(UserId user_id, const string &rank, bool is_creator)
+ : user_id_(user_id), rank_(rank), is_creator_(is_creator) {
+ }
+
+ td_api::object_ptr<td_api::chatAdministrator> get_chat_administrator_object(
+ const ContactsManager *contacts_manager) const;
+
+ UserId get_user_id() const {
+ return user_id_;
+ }
+
+ const string &get_rank() const {
+ return rank_;
+ }
+
+ bool is_creator() const {
+ return is_creator_;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ bool has_rank = !rank_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_rank);
+ STORE_FLAG(is_creator_);
+ END_STORE_FLAGS();
+ store(user_id_, storer);
+ if (has_rank) {
+ store(rank_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ bool has_rank;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_rank);
+ PARSE_FLAG(is_creator_);
+ END_PARSE_FLAGS();
+ parse(user_id_, parser);
+ if (has_rank) {
+ parse(rank_, parser);
+ }
+ }
+};
+
+inline bool operator==(const DialogAdministrator &lhs, const DialogAdministrator &rhs) {
+ return lhs.get_user_id() == rhs.get_user_id() && lhs.get_rank() == rhs.get_rank() &&
+ lhs.is_creator() == rhs.is_creator();
+}
+
+inline bool operator!=(const DialogAdministrator &lhs, const DialogAdministrator &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogAdministrator &administrator);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogDate.h b/protocols/Telegram/tdlib/td/td/telegram/DialogDate.h
new file mode 100644
index 0000000000..0888691c84
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogDate.h
@@ -0,0 +1,73 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/ServerMessageId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <limits>
+
+namespace td {
+
+class DialogDate {
+ int64 order;
+ DialogId dialog_id;
+
+ public:
+ DialogDate(int64 order, DialogId dialog_id) : order(order), dialog_id(dialog_id) {
+ }
+
+ bool operator<(const DialogDate &other) const {
+ return order > other.order || (order == other.order && dialog_id.get() > other.dialog_id.get());
+ }
+
+ bool operator<=(const DialogDate &other) const {
+ return order >= other.order && (order != other.order || dialog_id.get() >= other.dialog_id.get());
+ }
+
+ bool operator==(const DialogDate &other) const {
+ return order == other.order && dialog_id == other.dialog_id;
+ }
+
+ bool operator!=(const DialogDate &other) const {
+ return order != other.order || dialog_id != other.dialog_id;
+ }
+
+ int64 get_order() const {
+ return order;
+ }
+ DialogId get_dialog_id() const {
+ return dialog_id;
+ }
+ int32 get_date() const {
+ return static_cast<int32>((order >> 32) & 0x7FFFFFFF);
+ }
+ MessageId get_message_id() const {
+ return MessageId(ServerMessageId(static_cast<int32>(order & 0x7FFFFFFF)));
+ }
+};
+
+const DialogDate MIN_DIALOG_DATE(std::numeric_limits<int64>::max(), DialogId());
+const DialogDate MAX_DIALOG_DATE(0, DialogId());
+const int64 DEFAULT_ORDER = -1;
+
+struct DialogDateHash {
+ uint32 operator()(const DialogDate &dialog_date) const {
+ return Hash<int64>()(dialog_date.get_order()) * 2023654985u + DialogIdHash()(dialog_date.get_dialog_id());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, DialogDate dialog_date) {
+ return string_builder << "[" << dialog_date.get_order() << ", " << dialog_date.get_dialog_id().get() << "]";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogDb.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogDb.cpp
index ea799bd529..e26911c89f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DialogDb.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogDb.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,19 +8,26 @@
#include "td/telegram/Version.h"
+#include "td/db/SqliteConnectionSafe.h"
#include "td/db/SqliteDb.h"
#include "td/db/SqliteKeyValue.h"
#include "td/db/SqliteStatement.h"
+#include "td/actor/actor.h"
+#include "td/actor/SchedulerLocalStorage.h"
+
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
namespace td {
// NB: must happen inside a transaction
-Status init_dialog_db(SqliteDb &db, int32 version, bool &was_created) {
- LOG(INFO) << "Init dialog db " << tag("version", version);
+Status init_dialog_db(SqliteDb &db, int32 version, KeyValueSyncInterface &binlog_pmc, bool &was_created) {
+ LOG(INFO) << "Init dialog database " << tag("version", version);
was_created = false;
// Check if database exists
@@ -34,12 +41,62 @@ Status init_dialog_db(SqliteDb &db, int32 version, bool &was_created) {
version = 0;
}
+ auto create_notification_group_table = [&db] {
+ return db.exec(
+ "CREATE TABLE IF NOT EXISTS notification_groups (notification_group_id INT4 PRIMARY KEY, dialog_id "
+ "INT8, last_notification_date INT4)");
+ };
+
+ auto create_last_notification_date_index = [&db] {
+ return db.exec(
+ "CREATE INDEX IF NOT EXISTS notification_group_by_last_notification_date ON notification_groups "
+ "(last_notification_date, dialog_id, notification_group_id) WHERE last_notification_date IS NOT NULL");
+ };
+
+ auto add_dialogs_in_folder_index = [&db] {
+ return db.exec(
+ "CREATE INDEX IF NOT EXISTS dialog_in_folder_by_dialog_order ON dialogs (folder_id, dialog_order, dialog_id) "
+ "WHERE folder_id IS NOT NULL");
+ };
+
if (version == 0) {
- LOG(INFO) << "Create new dialog db";
+ LOG(INFO) << "Create new dialog database";
was_created = true;
TRY_STATUS(
- db.exec("CREATE TABLE IF NOT EXISTS dialogs (dialog_id INT8 PRIMARY KEY, dialog_order INT8, data BLOB)"));
- TRY_STATUS(db.exec("CREATE INDEX IF NOT EXISTS dialog_by_dialog_order ON dialogs (dialog_order, dialog_id)"));
+ db.exec("CREATE TABLE IF NOT EXISTS dialogs (dialog_id INT8 PRIMARY KEY, dialog_order INT8, data BLOB, "
+ "folder_id INT4)"));
+ TRY_STATUS(create_notification_group_table());
+ TRY_STATUS(create_last_notification_date_index());
+ TRY_STATUS(add_dialogs_in_folder_index());
+ version = current_db_version();
+ }
+ if (version < static_cast<int32>(DbVersion::AddNotificationsSupport)) {
+ TRY_STATUS(create_notification_group_table());
+ TRY_STATUS(create_last_notification_date_index());
+ }
+ if (version < static_cast<int32>(DbVersion::AddFolders)) {
+ TRY_STATUS(db.exec("DROP INDEX IF EXISTS dialog_by_dialog_order"));
+ TRY_STATUS(db.exec("ALTER TABLE dialogs ADD COLUMN folder_id INT4"));
+ TRY_STATUS(add_dialogs_in_folder_index());
+ TRY_STATUS(db.exec("UPDATE dialogs SET folder_id = 0 WHERE dialog_id < -1500000000000 AND dialog_order > 0"));
+ }
+ if (version < static_cast<int32>(DbVersion::StorePinnedDialogsInBinlog)) {
+ // 9221294780217032704 == get_dialog_order(Auto(), MIN_PINNED_DIALOG_DATE - 1)
+ TRY_RESULT(get_pinned_dialogs_stmt,
+ db.get_statement("SELECT dialog_id FROM dialogs WHERE folder_id = ?1 AND dialog_order > "
+ "9221294780217032704 ORDER BY dialog_order DESC, dialog_id DESC"));
+ for (auto folder_id = 0; folder_id < 2; folder_id++) {
+ vector<string> pinned_dialog_ids;
+ TRY_STATUS(get_pinned_dialogs_stmt.bind_int32(1, folder_id));
+ TRY_STATUS(get_pinned_dialogs_stmt.step());
+ while (get_pinned_dialogs_stmt.has_row()) {
+ pinned_dialog_ids.push_back(PSTRING() << get_pinned_dialogs_stmt.view_int64(0));
+ TRY_STATUS(get_pinned_dialogs_stmt.step());
+ }
+ get_pinned_dialogs_stmt.reset();
+
+ binlog_pmc.set(PSTRING() << "pinned_dialog_ids" << folder_id, implode(pinned_dialog_ids, ','));
+ }
}
return Status::OK();
@@ -48,58 +105,106 @@ Status init_dialog_db(SqliteDb &db, int32 version, bool &was_created) {
// NB: must happen inside a transaction
Status drop_dialog_db(SqliteDb &db, int version) {
if (version < static_cast<int32>(DbVersion::DialogDbCreated)) {
- LOG(WARNING) << "Drop old pmc dialog_db";
+ if (version != 0) {
+ LOG(WARNING) << "Drop old pmc dialog_db";
+ }
SqliteKeyValue kv;
kv.init_with_connection(db.clone(), "common").ensure();
kv.erase_by_prefix("di");
}
- LOG(WARNING) << "Drop dialog_db " << tag("version", version) << tag("current_db_version", current_db_version());
- return db.exec("DROP TABLE IF EXISTS dialogs");
+ if (version != 0) {
+ LOG(WARNING) << "Drop dialog_db " << tag("version", version) << tag("current_db_version", current_db_version());
+ }
+ auto status = db.exec("DROP TABLE IF EXISTS dialogs");
+ TRY_STATUS(db.exec("DROP TABLE IF EXISTS notification_groups"));
+ return status;
}
-class DialogDbImpl : public DialogDbSyncInterface {
+class DialogDbImpl final : public DialogDbSyncInterface {
public:
explicit DialogDbImpl(SqliteDb db) : db_(std::move(db)) {
init().ensure();
}
Status init() {
- TRY_RESULT(add_dialog_stmt, db_.get_statement("INSERT OR REPLACE INTO dialogs VALUES(?1, ?2, ?3)"));
- TRY_RESULT(get_dialog_stmt, db_.get_statement("SELECT data FROM dialogs WHERE dialog_id = ?1"));
- TRY_RESULT(get_dialogs_stmt, db_.get_statement("SELECT data, dialog_id, dialog_order FROM dialogs WHERE "
- "dialog_order < ?1 OR (dialog_order = ?1 AND dialog_id < ?2) ORDER "
- "BY dialog_order DESC, dialog_id DESC LIMIT ?3"));
- /*
- TRY_RESULT(get_dialogs2_stmt, db_.get_statement("SELECT data FROM dialogs WHERE dialog_order <= ?1 AND
- (dialog_order != ?1 OR "
- "dialog_id < ?2) ORDER BY dialog_order, dialog_id DESC LIMIT
- ?3"));
- */
- add_dialog_stmt_ = std::move(add_dialog_stmt);
- get_dialog_stmt_ = std::move(get_dialog_stmt);
- get_dialogs_stmt_ = std::move(get_dialogs_stmt);
+ TRY_RESULT_ASSIGN(add_dialog_stmt_, db_.get_statement("INSERT OR REPLACE INTO dialogs VALUES(?1, ?2, ?3, ?4)"));
+ TRY_RESULT_ASSIGN(add_notification_group_stmt_,
+ db_.get_statement("INSERT OR REPLACE INTO notification_groups VALUES(?1, ?2, ?3)"));
+ TRY_RESULT_ASSIGN(delete_notification_group_stmt_,
+ db_.get_statement("DELETE FROM notification_groups WHERE notification_group_id = ?1"));
+ TRY_RESULT_ASSIGN(get_dialog_stmt_, db_.get_statement("SELECT data FROM dialogs WHERE dialog_id = ?1"));
+ TRY_RESULT_ASSIGN(
+ get_dialogs_stmt_,
+ db_.get_statement("SELECT data, dialog_id, dialog_order FROM dialogs WHERE "
+ "folder_id = ?1 AND (dialog_order < ?2 OR (dialog_order = ?2 AND dialog_id < ?3)) ORDER "
+ "BY dialog_order DESC, dialog_id DESC LIMIT ?4"));
+ TRY_RESULT_ASSIGN(
+ get_notification_groups_by_last_notification_date_stmt_,
+ db_.get_statement("SELECT notification_group_id, dialog_id, last_notification_date FROM notification_groups "
+ "WHERE last_notification_date < ?1 OR (last_notification_date = ?1 "
+ "AND (dialog_id < ?2 OR (dialog_id = ?2 AND notification_group_id < ?3))) ORDER BY "
+ "last_notification_date DESC, dialog_id DESC LIMIT ?4"));
+ // "WHERE (last_notification_date, dialog_id, notification_group_id) < (?1, ?2, ?3) ORDER BY "
+ // "last_notification_date DESC, dialog_id DESC, notification_group_id DESC LIMIT ?4"));
+ TRY_RESULT_ASSIGN(
+ get_notification_group_stmt_,
+ db_.get_statement(
+ "SELECT dialog_id, last_notification_date FROM notification_groups WHERE notification_group_id = ?1"));
+ TRY_RESULT_ASSIGN(
+ get_secret_chat_count_stmt_,
+ db_.get_statement(
+ "SELECT COUNT(*) FROM dialogs WHERE folder_id = ?1 AND dialog_order > 0 AND dialog_id < -1500000000000"));
// LOG(ERROR) << get_dialog_stmt_.explain().ok();
// LOG(ERROR) << get_dialogs_stmt_.explain().ok();
- // LOG(ERROR) << get_dialogs2_stmt.explain().ok();
- // LOG(FATAL) << "EPLAINED";
+ // LOG(ERROR) << get_notification_groups_by_last_notification_date_stmt_.explain().ok();
+ // LOG(ERROR) << get_notification_group_stmt_.explain().ok();
+ // LOG(FATAL) << "EXPLAINED";
return Status::OK();
}
- Status add_dialog(DialogId dialog_id, int64 order, BufferSlice data) override {
+ void add_dialog(DialogId dialog_id, FolderId folder_id, int64 order, BufferSlice data,
+ vector<NotificationGroupKey> notification_groups) final {
SCOPE_EXIT {
add_dialog_stmt_.reset();
};
add_dialog_stmt_.bind_int64(1, dialog_id.get()).ensure();
add_dialog_stmt_.bind_int64(2, order).ensure();
add_dialog_stmt_.bind_blob(3, data.as_slice()).ensure();
- TRY_STATUS(add_dialog_stmt_.step());
- return Status::OK();
+ if (order > 0) {
+ add_dialog_stmt_.bind_int32(4, folder_id.get()).ensure();
+ } else {
+ add_dialog_stmt_.bind_null(4).ensure();
+ }
+
+ add_dialog_stmt_.step().ensure();
+
+ for (auto &to_add : notification_groups) {
+ if (to_add.dialog_id.is_valid()) {
+ SCOPE_EXIT {
+ add_notification_group_stmt_.reset();
+ };
+ add_notification_group_stmt_.bind_int32(1, to_add.group_id.get()).ensure();
+ add_notification_group_stmt_.bind_int64(2, to_add.dialog_id.get()).ensure();
+ if (to_add.last_notification_date != 0) {
+ add_notification_group_stmt_.bind_int32(3, to_add.last_notification_date).ensure();
+ } else {
+ add_notification_group_stmt_.bind_null(3).ensure();
+ }
+ add_notification_group_stmt_.step().ensure();
+ } else {
+ SCOPE_EXIT {
+ delete_notification_group_stmt_.reset();
+ };
+ delete_notification_group_stmt_.bind_int32(1, to_add.group_id.get()).ensure();
+ delete_notification_group_stmt_.step().ensure();
+ }
+ }
}
- Result<BufferSlice> get_dialog(DialogId dialog_id) override {
+ Result<BufferSlice> get_dialog(DialogId dialog_id) final {
SCOPE_EXIT {
get_dialog_stmt_.reset();
};
@@ -112,32 +217,85 @@ class DialogDbImpl : public DialogDbSyncInterface {
return BufferSlice(get_dialog_stmt_.view_blob(0));
}
- Result<std::vector<BufferSlice>> get_dialogs(int64 order, DialogId dialog_id, int32 limit) override {
+ Result<NotificationGroupKey> get_notification_group(NotificationGroupId notification_group_id) final {
+ SCOPE_EXIT {
+ get_notification_group_stmt_.reset();
+ };
+ get_notification_group_stmt_.bind_int32(1, notification_group_id.get()).ensure();
+ TRY_STATUS(get_notification_group_stmt_.step());
+ if (!get_notification_group_stmt_.has_row()) {
+ return Status::Error("Not found");
+ }
+ return NotificationGroupKey(notification_group_id, DialogId(get_notification_group_stmt_.view_int64(0)),
+ get_last_notification_date(get_notification_group_stmt_, 1));
+ }
+
+ int32 get_secret_chat_count(FolderId folder_id) final {
+ SCOPE_EXIT {
+ get_secret_chat_count_stmt_.reset();
+ };
+ get_secret_chat_count_stmt_.bind_int32(1, folder_id.get()).ensure();
+ get_secret_chat_count_stmt_.step().ensure();
+ CHECK(get_secret_chat_count_stmt_.has_row());
+ return get_secret_chat_count_stmt_.view_int32(0);
+ }
+
+ DialogDbGetDialogsResult get_dialogs(FolderId folder_id, int64 order, DialogId dialog_id, int32 limit) final {
SCOPE_EXIT {
get_dialogs_stmt_.reset();
};
- get_dialogs_stmt_.bind_int64(1, order).ensure();
- get_dialogs_stmt_.bind_int64(2, dialog_id.get()).ensure();
- get_dialogs_stmt_.bind_int32(3, limit).ensure();
+ get_dialogs_stmt_.bind_int32(1, folder_id.get()).ensure();
+ get_dialogs_stmt_.bind_int64(2, order).ensure();
+ get_dialogs_stmt_.bind_int64(3, dialog_id.get()).ensure();
+ get_dialogs_stmt_.bind_int32(4, limit).ensure();
- std::vector<BufferSlice> dialogs;
- TRY_STATUS(get_dialogs_stmt_.step());
+ DialogDbGetDialogsResult result;
+ result.next_dialog_id = dialog_id;
+ result.next_order = order;
+ get_dialogs_stmt_.step().ensure();
while (get_dialogs_stmt_.has_row()) {
BufferSlice data(get_dialogs_stmt_.view_blob(0));
- auto loaded_dialog_id = get_dialogs_stmt_.view_int64(1);
- auto loaded_dialog_order = get_dialogs_stmt_.view_int64(2);
- LOG(INFO) << "Load chat " << loaded_dialog_id << " with order " << loaded_dialog_order;
- dialogs.emplace_back(std::move(data));
- TRY_STATUS(get_dialogs_stmt_.step());
+ result.next_dialog_id = DialogId(get_dialogs_stmt_.view_int64(1));
+ result.next_order = get_dialogs_stmt_.view_int64(2);
+ LOG(INFO) << "Load " << result.next_dialog_id << " with order " << result.next_order;
+ result.dialogs.emplace_back(std::move(data));
+ get_dialogs_stmt_.step().ensure();
}
- return std::move(dialogs);
+ return result;
+ }
+
+ vector<NotificationGroupKey> get_notification_groups_by_last_notification_date(
+ NotificationGroupKey notification_group_key, int32 limit) final {
+ auto &stmt = get_notification_groups_by_last_notification_date_stmt_;
+ SCOPE_EXIT {
+ stmt.reset();
+ };
+
+ stmt.bind_int32(1, notification_group_key.last_notification_date).ensure();
+ stmt.bind_int64(2, notification_group_key.dialog_id.get()).ensure();
+ stmt.bind_int32(3, notification_group_key.group_id.get()).ensure();
+ stmt.bind_int32(4, limit).ensure();
+
+ vector<NotificationGroupKey> notification_groups;
+ stmt.step().ensure();
+ while (stmt.has_row()) {
+ notification_groups.emplace_back(NotificationGroupId(stmt.view_int32(0)), DialogId(stmt.view_int64(1)),
+ get_last_notification_date(stmt, 2));
+ stmt.step().ensure();
+ }
+
+ return notification_groups;
+ }
+
+ Status begin_read_transaction() final {
+ return db_.begin_read_transaction();
}
- Status begin_transaction() override {
- return db_.begin_transaction();
+ Status begin_write_transaction() final {
+ return db_.begin_write_transaction();
}
- Status commit_transaction() override {
+ Status commit_transaction() final {
return db_.commit_transaction();
}
@@ -145,85 +303,159 @@ class DialogDbImpl : public DialogDbSyncInterface {
SqliteDb db_;
SqliteStatement add_dialog_stmt_;
+ SqliteStatement add_notification_group_stmt_;
+ SqliteStatement delete_notification_group_stmt_;
SqliteStatement get_dialog_stmt_;
SqliteStatement get_dialogs_stmt_;
+ SqliteStatement get_notification_groups_by_last_notification_date_stmt_;
+ SqliteStatement get_notification_group_stmt_;
+ SqliteStatement get_secret_chat_count_stmt_;
+
+ static int32 get_last_notification_date(SqliteStatement &stmt, int id) {
+ if (stmt.view_datatype(id) == SqliteStatement::Datatype::Null) {
+ return 0;
+ }
+ return stmt.view_int32(id);
+ }
};
std::shared_ptr<DialogDbSyncSafeInterface> create_dialog_db_sync(
std::shared_ptr<SqliteConnectionSafe> sqlite_connection) {
- class DialogDbSyncSafe : public DialogDbSyncSafeInterface {
+ class DialogDbSyncSafe final : public DialogDbSyncSafeInterface {
public:
explicit DialogDbSyncSafe(std::shared_ptr<SqliteConnectionSafe> sqlite_connection)
: lsls_db_([safe_connection = std::move(sqlite_connection)] {
- return std::make_unique<DialogDbImpl>(safe_connection->get().clone());
+ return make_unique<DialogDbImpl>(safe_connection->get().clone());
}) {
}
- DialogDbSyncInterface &get() override {
+ DialogDbSyncInterface &get() final {
return *lsls_db_.get();
}
private:
- LazySchedulerLocalStorage<std::unique_ptr<DialogDbSyncInterface>> lsls_db_;
+ LazySchedulerLocalStorage<unique_ptr<DialogDbSyncInterface>> lsls_db_;
};
return std::make_shared<DialogDbSyncSafe>(std::move(sqlite_connection));
}
-class DialogDbAsync : public DialogDbAsyncInterface {
+class DialogDbAsync final : public DialogDbAsyncInterface {
public:
DialogDbAsync(std::shared_ptr<DialogDbSyncSafeInterface> sync_db, int32 scheduler_id) {
impl_ = create_actor_on_scheduler<Impl>("DialogDbActor", scheduler_id, std::move(sync_db));
}
- void add_dialog(DialogId dialog_id, int64 order, BufferSlice data, Promise<> promise) override {
- send_closure_later(impl_, &Impl::add_dialog, dialog_id, order, std::move(data), std::move(promise));
+ void add_dialog(DialogId dialog_id, FolderId folder_id, int64 order, BufferSlice data,
+ vector<NotificationGroupKey> notification_groups, Promise<Unit> promise) final {
+ send_closure(impl_, &Impl::add_dialog, dialog_id, folder_id, order, std::move(data), std::move(notification_groups),
+ std::move(promise));
+ }
+
+ void get_notification_groups_by_last_notification_date(NotificationGroupKey notification_group_key, int32 limit,
+ Promise<vector<NotificationGroupKey>> promise) final {
+ send_closure(impl_, &Impl::get_notification_groups_by_last_notification_date, notification_group_key, limit,
+ std::move(promise));
}
- void get_dialog(DialogId dialog_id, Promise<BufferSlice> promise) override {
+
+ void get_notification_group(NotificationGroupId notification_group_id, Promise<NotificationGroupKey> promise) final {
+ send_closure(impl_, &Impl::get_notification_group, notification_group_id, std::move(promise));
+ }
+
+ void get_secret_chat_count(FolderId folder_id, Promise<int32> promise) final {
+ send_closure(impl_, &Impl::get_secret_chat_count, folder_id, std::move(promise));
+ }
+
+ void get_dialog(DialogId dialog_id, Promise<BufferSlice> promise) final {
send_closure_later(impl_, &Impl::get_dialog, dialog_id, std::move(promise));
}
- void get_dialogs(int64 order, DialogId dialog_id, int32 limit, Promise<std::vector<BufferSlice>> promise) override {
- send_closure_later(impl_, &Impl::get_dialogs, order, dialog_id, limit, std::move(promise));
+
+ void get_dialogs(FolderId folder_id, int64 order, DialogId dialog_id, int32 limit,
+ Promise<DialogDbGetDialogsResult> promise) final {
+ send_closure_later(impl_, &Impl::get_dialogs, folder_id, order, dialog_id, limit, std::move(promise));
}
- void close(Promise<> promise) override {
+
+ void close(Promise<Unit> promise) final {
send_closure_later(impl_, &Impl::close, std::move(promise));
}
+ void force_flush() final {
+ send_closure_later(impl_, &Impl::force_flush);
+ }
+
private:
- class Impl : public Actor {
+ class Impl final : public Actor {
public:
explicit Impl(std::shared_ptr<DialogDbSyncSafeInterface> sync_db_safe) : sync_db_safe_(std::move(sync_db_safe)) {
}
- void add_dialog(DialogId dialog_id, int64 order, BufferSlice data, Promise<> promise) {
- add_write_query([=, promise = std::move(promise), data = std::move(data)](Unit) mutable {
- promise.set_result(sync_db_->add_dialog(dialog_id, order, std::move(data)));
+
+ void add_dialog(DialogId dialog_id, FolderId folder_id, int64 order, BufferSlice data,
+ vector<NotificationGroupKey> notification_groups, Promise<Unit> promise) {
+ add_write_query([this, dialog_id, folder_id, order, promise = std::move(promise), data = std::move(data),
+ notification_groups = std::move(notification_groups)](Unit) mutable {
+ sync_db_->add_dialog(dialog_id, folder_id, order, std::move(data), std::move(notification_groups));
+ on_write_result(std::move(promise));
});
}
+
+ void on_write_result(Promise<Unit> &&promise) {
+ // We are inside a transaction and don't know how to handle errors
+ finished_writes_.push_back(std::move(promise));
+ }
+
+ void get_notification_groups_by_last_notification_date(NotificationGroupKey notification_group_key, int32 limit,
+ Promise<vector<NotificationGroupKey>> promise) {
+ add_read_query();
+ promise.set_value(sync_db_->get_notification_groups_by_last_notification_date(notification_group_key, limit));
+ }
+
+ void get_notification_group(NotificationGroupId notification_group_id, Promise<NotificationGroupKey> promise) {
+ add_read_query();
+ promise.set_result(sync_db_->get_notification_group(notification_group_id));
+ }
+
+ void get_secret_chat_count(FolderId folder_id, Promise<int32> promise) {
+ add_read_query();
+ promise.set_value(sync_db_->get_secret_chat_count(folder_id));
+ }
+
void get_dialog(DialogId dialog_id, Promise<BufferSlice> promise) {
add_read_query();
promise.set_result(sync_db_->get_dialog(dialog_id));
}
- void get_dialogs(int64 order, DialogId dialog_id, int32 limit, Promise<std::vector<BufferSlice>> promise) {
+
+ void get_dialogs(FolderId folder_id, int64 order, DialogId dialog_id, int32 limit,
+ Promise<DialogDbGetDialogsResult> promise) {
add_read_query();
- promise.set_result(sync_db_->get_dialogs(order, dialog_id, limit));
+ promise.set_value(sync_db_->get_dialogs(folder_id, order, dialog_id, limit));
}
- void close(Promise<> promise) {
+
+ void close(Promise<Unit> promise) {
do_flush();
sync_db_safe_.reset();
sync_db_ = nullptr;
- promise.set_result(Unit());
+ promise.set_value(Unit());
stop();
}
+ void force_flush() {
+ do_flush();
+ LOG(INFO) << "DialogDb flushed";
+ }
+
private:
std::shared_ptr<DialogDbSyncSafeInterface> sync_db_safe_;
DialogDbSyncInterface *sync_db_ = nullptr;
static constexpr size_t MAX_PENDING_QUERIES_COUNT{50};
- static constexpr double MAX_PENDING_QUERIES_DELAY{1};
- std::vector<Promise<>> pending_writes_;
+ static constexpr double MAX_PENDING_QUERIES_DELAY{0.01};
+
+ //NB: order is important, destructor of pending_writes_ will change finished_writes_
+ vector<Promise<Unit>> finished_writes_;
+ vector<Promise<Unit>> pending_writes_; // TODO use Action
double wakeup_at_ = 0;
+
template <class F>
void add_write_query(F &&f) {
- pending_writes_.push_back(PromiseCreator::lambda(std::forward<F>(f), PromiseCreator::Ignore()));
+ pending_writes_.push_back(PromiseCreator::lambda(std::forward<F>(f)));
if (pending_writes_.size() > MAX_PENDING_QUERIES_COUNT) {
do_flush();
wakeup_at_ = 0;
@@ -234,26 +466,27 @@ class DialogDbAsync : public DialogDbAsyncInterface {
set_timeout_at(wakeup_at_);
}
}
+
void add_read_query() {
do_flush();
}
+
void do_flush() {
if (pending_writes_.empty()) {
return;
}
- sync_db_->begin_transaction().ensure();
- for (auto &query : pending_writes_) {
- query.set_value(Unit());
- }
+ sync_db_->begin_write_transaction().ensure();
+ set_promises(pending_writes_);
sync_db_->commit_transaction().ensure();
- pending_writes_.clear();
+ set_promises(finished_writes_);
cancel_timeout();
}
- void timeout_expired() override {
+
+ void timeout_expired() final {
do_flush();
}
- void start_up() override {
+ void start_up() final {
sync_db_ = &sync_db_safe_->get();
}
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogDb.h b/protocols/Telegram/tdlib/td/td/telegram/DialogDb.h
index f5d51b43a1..e303437672 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DialogDb.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogDb.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,19 +7,31 @@
#pragma once
#include "td/telegram/DialogId.h"
+#include "td/telegram/FolderId.h"
+#include "td/telegram/NotificationGroupId.h"
+#include "td/telegram/NotificationGroupKey.h"
-#include "td/actor/PromiseFuture.h"
-
-#include "td/db/SqliteConnectionSafe.h"
+#include "td/db/KeyValueSyncInterface.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
#include <memory>
#include <utility>
namespace td {
+
+class SqliteConnectionSafe;
+class SqliteDb;
+
+struct DialogDbGetDialogsResult {
+ vector<BufferSlice> dialogs;
+ int64 next_order = 0;
+ DialogId next_dialog_id;
+};
+
class DialogDbSyncInterface {
public:
DialogDbSyncInterface() = default;
@@ -27,10 +39,22 @@ class DialogDbSyncInterface {
DialogDbSyncInterface &operator=(const DialogDbSyncInterface &) = delete;
virtual ~DialogDbSyncInterface() = default;
- virtual Status add_dialog(DialogId dialog_id, int64 order, BufferSlice data) = 0;
+ virtual void add_dialog(DialogId dialog_id, FolderId folder_id, int64 order, BufferSlice data,
+ vector<NotificationGroupKey> notification_groups) = 0;
+
virtual Result<BufferSlice> get_dialog(DialogId dialog_id) = 0;
- virtual Result<std::vector<BufferSlice>> get_dialogs(int64 order, DialogId dialog_id, int32 limit) = 0;
- virtual Status begin_transaction() = 0;
+
+ virtual DialogDbGetDialogsResult get_dialogs(FolderId folder_id, int64 order, DialogId dialog_id, int32 limit) = 0;
+
+ virtual vector<NotificationGroupKey> get_notification_groups_by_last_notification_date(
+ NotificationGroupKey notification_group_key, int32 limit) = 0;
+
+ virtual Result<NotificationGroupKey> get_notification_group(NotificationGroupId notification_group_id) = 0;
+
+ virtual int32 get_secret_chat_count(FolderId folder_id) = 0;
+
+ virtual Status begin_read_transaction() = 0;
+ virtual Status begin_write_transaction() = 0;
virtual Status commit_transaction() = 0;
};
@@ -51,18 +75,36 @@ class DialogDbAsyncInterface {
DialogDbAsyncInterface &operator=(const DialogDbAsyncInterface &) = delete;
virtual ~DialogDbAsyncInterface() = default;
- virtual void add_dialog(DialogId dialog_id, int64 order, BufferSlice data, Promise<> promise) = 0;
+ virtual void add_dialog(DialogId dialog_id, FolderId folder_id, int64 order, BufferSlice data,
+ vector<NotificationGroupKey> notification_groups, Promise<Unit> promise) = 0;
+
virtual void get_dialog(DialogId dialog_id, Promise<BufferSlice> promise) = 0;
- virtual void get_dialogs(int64 order, DialogId dialog_id, int32 limit, Promise<std::vector<BufferSlice>> promise) = 0;
- virtual void close(Promise<> promise) = 0;
+
+ virtual void get_dialogs(FolderId folder_id, int64 order, DialogId dialog_id, int32 limit,
+ Promise<DialogDbGetDialogsResult> promise) = 0;
+
+ virtual void get_notification_groups_by_last_notification_date(NotificationGroupKey notification_group_key,
+ int32 limit,
+ Promise<vector<NotificationGroupKey>> promise) = 0;
+
+ virtual void get_notification_group(NotificationGroupId notification_group_id,
+ Promise<NotificationGroupKey> promise) = 0;
+
+ virtual void get_secret_chat_count(FolderId folder_id, Promise<int32> promise) = 0;
+
+ virtual void close(Promise<Unit> promise) = 0;
+
+ virtual void force_flush() = 0;
};
-Status init_dialog_db(SqliteDb &db, int version, bool &was_created) TD_WARN_UNUSED_RESULT;
+Status init_dialog_db(SqliteDb &db, int version, KeyValueSyncInterface &binlog_pmc,
+ bool &was_created) TD_WARN_UNUSED_RESULT;
Status drop_dialog_db(SqliteDb &db, int version) TD_WARN_UNUSED_RESULT;
std::shared_ptr<DialogDbSyncSafeInterface> create_dialog_db_sync(
std::shared_ptr<SqliteConnectionSafe> sqlite_connection);
std::shared_ptr<DialogDbAsyncInterface> create_dialog_db_async(std::shared_ptr<DialogDbSyncSafeInterface> sync_db,
- int32 scheduler_id);
-}; // namespace td
+ int32 scheduler_id = -1);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogEventLog.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogEventLog.cpp
new file mode 100644
index 0000000000..69b771a54f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogEventLog.cpp
@@ -0,0 +1,592 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogEventLog.h"
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatReactions.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogInviteLink.h"
+#include "td/telegram/DialogLocation.h"
+#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/ForumTopicInfo.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/GroupCallManager.h"
+#include "td/telegram/GroupCallParticipant.h"
+#include "td/telegram/InputGroupCallId.h"
+#include "td/telegram/MessageSender.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/MessageTtl.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+static td_api::object_ptr<td_api::ChatEventAction> get_chat_event_action_object(
+ Td *td, ChannelId channel_id, tl_object_ptr<telegram_api::ChannelAdminLogEventAction> &&action_ptr,
+ DialogId &actor_dialog_id) {
+ CHECK(action_ptr != nullptr);
+ switch (action_ptr->get_id()) {
+ case telegram_api::channelAdminLogEventActionParticipantJoin::ID:
+ return td_api::make_object<td_api::chatEventMemberJoined>();
+ case telegram_api::channelAdminLogEventActionParticipantJoinByInvite::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantJoinByInvite>(action_ptr);
+ DialogInviteLink invite_link(std::move(action->invite_), "channelAdminLogEventActionParticipantJoinByInvite");
+ if (!invite_link.is_valid()) {
+ LOG(ERROR) << "Wrong invite link: " << invite_link;
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventMemberJoinedByInviteLink>(
+ invite_link.get_chat_invite_link_object(td->contacts_manager_.get()));
+ }
+ case telegram_api::channelAdminLogEventActionParticipantJoinByRequest::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantJoinByRequest>(action_ptr);
+ DialogInviteLink invite_link(std::move(action->invite_), "channelAdminLogEventActionParticipantJoinByRequest");
+ UserId approver_user_id(action->approved_by_);
+ if (!approver_user_id.is_valid()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventMemberJoinedByRequest>(
+ td->contacts_manager_->get_user_id_object(approver_user_id, "chatEventMemberJoinedByRequest"),
+ invite_link.get_chat_invite_link_object(td->contacts_manager_.get()));
+ }
+ case telegram_api::channelAdminLogEventActionParticipantLeave::ID:
+ return td_api::make_object<td_api::chatEventMemberLeft>();
+ case telegram_api::channelAdminLogEventActionParticipantInvite::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantInvite>(action_ptr);
+ DialogParticipant dialog_participant(std::move(action->participant_),
+ td->contacts_manager_->get_channel_type(channel_id));
+ if (!dialog_participant.is_valid() || dialog_participant.dialog_id_.get_type() != DialogType::User) {
+ LOG(ERROR) << "Wrong invite: " << dialog_participant;
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventMemberInvited>(
+ td->contacts_manager_->get_user_id_object(dialog_participant.dialog_id_.get_user_id(),
+ "chatEventMemberInvited"),
+ dialog_participant.status_.get_chat_member_status_object());
+ }
+ case telegram_api::channelAdminLogEventActionParticipantToggleBan::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantToggleBan>(action_ptr);
+ auto channel_type = td->contacts_manager_->get_channel_type(channel_id);
+ DialogParticipant old_dialog_participant(std::move(action->prev_participant_), channel_type);
+ DialogParticipant new_dialog_participant(std::move(action->new_participant_), channel_type);
+ if (old_dialog_participant.dialog_id_ != new_dialog_participant.dialog_id_) {
+ LOG(ERROR) << old_dialog_participant.dialog_id_ << " VS " << new_dialog_participant.dialog_id_;
+ return nullptr;
+ }
+ if (!old_dialog_participant.is_valid() || !new_dialog_participant.is_valid()) {
+ LOG(ERROR) << "Wrong restrict: " << old_dialog_participant << " -> " << new_dialog_participant;
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventMemberRestricted>(
+ get_message_sender_object(td, old_dialog_participant.dialog_id_, "chatEventMemberRestricted"),
+ old_dialog_participant.status_.get_chat_member_status_object(),
+ new_dialog_participant.status_.get_chat_member_status_object());
+ }
+ case telegram_api::channelAdminLogEventActionParticipantToggleAdmin::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantToggleAdmin>(action_ptr);
+ auto channel_type = td->contacts_manager_->get_channel_type(channel_id);
+ DialogParticipant old_dialog_participant(std::move(action->prev_participant_), channel_type);
+ DialogParticipant new_dialog_participant(std::move(action->new_participant_), channel_type);
+ if (old_dialog_participant.dialog_id_ != new_dialog_participant.dialog_id_) {
+ LOG(ERROR) << old_dialog_participant.dialog_id_ << " VS " << new_dialog_participant.dialog_id_;
+ return nullptr;
+ }
+ if (!old_dialog_participant.is_valid() || !new_dialog_participant.is_valid() ||
+ old_dialog_participant.dialog_id_.get_type() != DialogType::User) {
+ LOG(ERROR) << "Wrong edit administrator: " << old_dialog_participant << " -> " << new_dialog_participant;
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventMemberPromoted>(
+ td->contacts_manager_->get_user_id_object(old_dialog_participant.dialog_id_.get_user_id(),
+ "chatEventMemberPromoted"),
+ old_dialog_participant.status_.get_chat_member_status_object(),
+ new_dialog_participant.status_.get_chat_member_status_object());
+ }
+ case telegram_api::channelAdminLogEventActionChangeTitle::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeTitle>(action_ptr);
+ return td_api::make_object<td_api::chatEventTitleChanged>(std::move(action->prev_value_),
+ std::move(action->new_value_));
+ }
+ case telegram_api::channelAdminLogEventActionChangeAbout::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeAbout>(action_ptr);
+ return td_api::make_object<td_api::chatEventDescriptionChanged>(std::move(action->prev_value_),
+ std::move(action->new_value_));
+ }
+ case telegram_api::channelAdminLogEventActionChangeUsername::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeUsername>(action_ptr);
+ return td_api::make_object<td_api::chatEventUsernameChanged>(std::move(action->prev_value_),
+ std::move(action->new_value_));
+ }
+ case telegram_api::channelAdminLogEventActionChangeUsernames::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeUsernames>(action_ptr);
+ return td_api::make_object<td_api::chatEventActiveUsernamesChanged>(std::move(action->prev_value_),
+ std::move(action->new_value_));
+ }
+ case telegram_api::channelAdminLogEventActionChangePhoto::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangePhoto>(action_ptr);
+ auto file_manager = td->file_manager_.get();
+ auto old_photo = get_photo(file_manager, std::move(action->prev_photo_), DialogId(channel_id));
+ auto new_photo = get_photo(file_manager, std::move(action->new_photo_), DialogId(channel_id));
+ return td_api::make_object<td_api::chatEventPhotoChanged>(get_chat_photo_object(file_manager, old_photo),
+ get_chat_photo_object(file_manager, new_photo));
+ }
+ case telegram_api::channelAdminLogEventActionDefaultBannedRights::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionDefaultBannedRights>(action_ptr);
+ auto old_permissions = RestrictedRights(action->prev_banned_rights_);
+ auto new_permissions = RestrictedRights(action->new_banned_rights_);
+ return td_api::make_object<td_api::chatEventPermissionsChanged>(old_permissions.get_chat_permissions_object(),
+ new_permissions.get_chat_permissions_object());
+ }
+ case telegram_api::channelAdminLogEventActionToggleInvites::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleInvites>(action_ptr);
+ return td_api::make_object<td_api::chatEventInvitesToggled>(action->new_value_);
+ }
+ case telegram_api::channelAdminLogEventActionToggleSignatures::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleSignatures>(action_ptr);
+ return td_api::make_object<td_api::chatEventSignMessagesToggled>(action->new_value_);
+ }
+ case telegram_api::channelAdminLogEventActionUpdatePinned::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionUpdatePinned>(action_ptr);
+ auto message = td->messages_manager_->get_dialog_event_log_message_object(
+ DialogId(channel_id), std::move(action->message_), actor_dialog_id);
+ if (message == nullptr) {
+ return nullptr;
+ }
+ if (message->is_pinned_) {
+ return td_api::make_object<td_api::chatEventMessagePinned>(std::move(message));
+ } else {
+ return td_api::make_object<td_api::chatEventMessageUnpinned>(std::move(message));
+ }
+ }
+ case telegram_api::channelAdminLogEventActionSendMessage::ID: {
+ return nullptr;
+ }
+ case telegram_api::channelAdminLogEventActionEditMessage::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionEditMessage>(action_ptr);
+ DialogId old_sender_dialog_id;
+ auto old_message = td->messages_manager_->get_dialog_event_log_message_object(
+ DialogId(channel_id), std::move(action->prev_message_), old_sender_dialog_id);
+ DialogId new_sender_dialog_id;
+ auto new_message = td->messages_manager_->get_dialog_event_log_message_object(
+ DialogId(channel_id), std::move(action->new_message_), new_sender_dialog_id);
+ if (old_message == nullptr || new_message == nullptr) {
+ return nullptr;
+ }
+ if (old_sender_dialog_id == new_sender_dialog_id) {
+ actor_dialog_id = old_sender_dialog_id;
+ }
+ return td_api::make_object<td_api::chatEventMessageEdited>(std::move(old_message), std::move(new_message));
+ }
+ case telegram_api::channelAdminLogEventActionStopPoll::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionStopPoll>(action_ptr);
+ auto message = td->messages_manager_->get_dialog_event_log_message_object(
+ DialogId(channel_id), std::move(action->message_), actor_dialog_id);
+ if (message == nullptr) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventPollStopped>(std::move(message));
+ }
+ case telegram_api::channelAdminLogEventActionDeleteMessage::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionDeleteMessage>(action_ptr);
+ auto message = td->messages_manager_->get_dialog_event_log_message_object(
+ DialogId(channel_id), std::move(action->message_), actor_dialog_id);
+ if (message == nullptr) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventMessageDeleted>(std::move(message));
+ }
+ case telegram_api::channelAdminLogEventActionChangeStickerSet::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeStickerSet>(action_ptr);
+ auto old_sticker_set_id = td->stickers_manager_->add_sticker_set(std::move(action->prev_stickerset_));
+ auto new_sticker_set_id = td->stickers_manager_->add_sticker_set(std::move(action->new_stickerset_));
+ if (!old_sticker_set_id.is_valid() || !new_sticker_set_id.is_valid()) {
+ LOG(ERROR) << "Skip " << to_string(action);
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventStickerSetChanged>(old_sticker_set_id.get(),
+ new_sticker_set_id.get());
+ }
+ case telegram_api::channelAdminLogEventActionTogglePreHistoryHidden::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionTogglePreHistoryHidden>(action_ptr);
+ return td_api::make_object<td_api::chatEventIsAllHistoryAvailableToggled>(!action->new_value_);
+ }
+ case telegram_api::channelAdminLogEventActionChangeLinkedChat::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeLinkedChat>(action_ptr);
+
+ auto get_dialog_from_channel_id = [messages_manager = td->messages_manager_.get()](int64 channel_id_int) {
+ ChannelId channel_id(channel_id_int);
+ if (!channel_id.is_valid()) {
+ return DialogId();
+ }
+
+ DialogId dialog_id(channel_id);
+ messages_manager->force_create_dialog(dialog_id, "get_dialog_from_channel_id");
+ return dialog_id;
+ };
+
+ auto old_linked_dialog_id = get_dialog_from_channel_id(action->prev_value_);
+ auto new_linked_dialog_id = get_dialog_from_channel_id(action->new_value_);
+ if (old_linked_dialog_id == new_linked_dialog_id) {
+ LOG(ERROR) << "Receive the same linked " << new_linked_dialog_id;
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventLinkedChatChanged>(old_linked_dialog_id.get(),
+ new_linked_dialog_id.get());
+ }
+ case telegram_api::channelAdminLogEventActionChangeLocation::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeLocation>(action_ptr);
+ auto old_location = DialogLocation(std::move(action->prev_value_));
+ auto new_location = DialogLocation(std::move(action->new_value_));
+ return td_api::make_object<td_api::chatEventLocationChanged>(old_location.get_chat_location_object(),
+ new_location.get_chat_location_object());
+ }
+ case telegram_api::channelAdminLogEventActionToggleSlowMode::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleSlowMode>(action_ptr);
+ auto old_slow_mode_delay = clamp(action->prev_value_, 0, 86400 * 366);
+ auto new_slow_mode_delay = clamp(action->new_value_, 0, 86400 * 366);
+ return td_api::make_object<td_api::chatEventSlowModeDelayChanged>(old_slow_mode_delay, new_slow_mode_delay);
+ }
+ case telegram_api::channelAdminLogEventActionExportedInviteEdit::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionExportedInviteEdit>(action_ptr);
+ DialogInviteLink old_invite_link(std::move(action->prev_invite_), "channelAdminLogEventActionExportedInviteEdit");
+ DialogInviteLink new_invite_link(std::move(action->new_invite_), "channelAdminLogEventActionExportedInviteEdit");
+ if (!old_invite_link.is_valid() || !new_invite_link.is_valid()) {
+ LOG(ERROR) << "Wrong edited invite link: " << old_invite_link << " -> " << new_invite_link;
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventInviteLinkEdited>(
+ old_invite_link.get_chat_invite_link_object(td->contacts_manager_.get()),
+ new_invite_link.get_chat_invite_link_object(td->contacts_manager_.get()));
+ }
+ case telegram_api::channelAdminLogEventActionExportedInviteRevoke::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionExportedInviteRevoke>(action_ptr);
+ DialogInviteLink invite_link(std::move(action->invite_), "channelAdminLogEventActionExportedInviteRevoke");
+ if (!invite_link.is_valid()) {
+ LOG(ERROR) << "Wrong revoked invite link: " << invite_link;
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventInviteLinkRevoked>(
+ invite_link.get_chat_invite_link_object(td->contacts_manager_.get()));
+ }
+ case telegram_api::channelAdminLogEventActionExportedInviteDelete::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionExportedInviteDelete>(action_ptr);
+ DialogInviteLink invite_link(std::move(action->invite_), "channelAdminLogEventActionExportedInviteDelete");
+ if (!invite_link.is_valid()) {
+ LOG(ERROR) << "Wrong deleted invite link: " << invite_link;
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventInviteLinkDeleted>(
+ invite_link.get_chat_invite_link_object(td->contacts_manager_.get()));
+ }
+ case telegram_api::channelAdminLogEventActionStartGroupCall::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionStartGroupCall>(action_ptr);
+ auto input_group_call_id = InputGroupCallId(action->call_);
+ if (!input_group_call_id.is_valid()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventVideoChatCreated>(
+ td->group_call_manager_->get_group_call_id(input_group_call_id, DialogId(channel_id)).get());
+ }
+ case telegram_api::channelAdminLogEventActionDiscardGroupCall::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionDiscardGroupCall>(action_ptr);
+ auto input_group_call_id = InputGroupCallId(action->call_);
+ if (!input_group_call_id.is_valid()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventVideoChatEnded>(
+ td->group_call_manager_->get_group_call_id(input_group_call_id, DialogId(channel_id)).get());
+ }
+ case telegram_api::channelAdminLogEventActionParticipantMute::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantMute>(action_ptr);
+ GroupCallParticipant participant(action->participant_, 0);
+ if (!participant.is_valid()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventVideoChatParticipantIsMutedToggled>(
+ get_message_sender_object(td, participant.dialog_id, "chatEventVideoChatParticipantIsMutedToggled"), true);
+ }
+ case telegram_api::channelAdminLogEventActionParticipantUnmute::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantUnmute>(action_ptr);
+ GroupCallParticipant participant(action->participant_, 0);
+ if (!participant.is_valid()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventVideoChatParticipantIsMutedToggled>(
+ get_message_sender_object(td, participant.dialog_id, "chatEventVideoChatParticipantIsMutedToggled"), false);
+ }
+ case telegram_api::channelAdminLogEventActionParticipantVolume::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantVolume>(action_ptr);
+ GroupCallParticipant participant(action->participant_, 0);
+ if (!participant.is_valid()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventVideoChatParticipantVolumeLevelChanged>(
+ get_message_sender_object(td, participant.dialog_id, "chatEventVideoChatParticipantVolumeLevelChanged"),
+ participant.volume_level);
+ }
+ case telegram_api::channelAdminLogEventActionToggleGroupCallSetting::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleGroupCallSetting>(action_ptr);
+ return td_api::make_object<td_api::chatEventVideoChatMuteNewParticipantsToggled>(action->join_muted_);
+ }
+ case telegram_api::channelAdminLogEventActionChangeHistoryTTL::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeHistoryTTL>(action_ptr);
+ auto old_value = MessageTtl(clamp(action->prev_value_, 0, 86400 * 366));
+ auto new_value = MessageTtl(clamp(action->new_value_, 0, 86400 * 366));
+ return td_api::make_object<td_api::chatEventMessageTtlChanged>(old_value.get_message_ttl_object(),
+ new_value.get_message_ttl_object());
+ }
+ case telegram_api::channelAdminLogEventActionToggleNoForwards::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleNoForwards>(action_ptr);
+ return td_api::make_object<td_api::chatEventHasProtectedContentToggled>(action->new_value_);
+ }
+ case telegram_api::channelAdminLogEventActionChangeAvailableReactions::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeAvailableReactions>(action_ptr);
+ ChatReactions old_available_reactions(std::move(action->prev_value_));
+ ChatReactions new_available_reactions(std::move(action->new_value_));
+ return td_api::make_object<td_api::chatEventAvailableReactionsChanged>(
+ old_available_reactions.get_chat_available_reactions_object(),
+ new_available_reactions.get_chat_available_reactions_object());
+ }
+ case telegram_api::channelAdminLogEventActionToggleForum::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleForum>(action_ptr);
+ return td_api::make_object<td_api::chatEventIsForumToggled>(action->new_value_);
+ }
+ case telegram_api::channelAdminLogEventActionCreateTopic::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionCreateTopic>(action_ptr);
+ auto topic_info = ForumTopicInfo(action->topic_);
+ if (topic_info.is_empty()) {
+ return nullptr;
+ }
+ actor_dialog_id = topic_info.get_creator_dialog_id();
+ return td_api::make_object<td_api::chatEventForumTopicCreated>(topic_info.get_forum_topic_info_object(td));
+ }
+ case telegram_api::channelAdminLogEventActionEditTopic::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionEditTopic>(action_ptr);
+ auto old_topic_info = ForumTopicInfo(action->prev_topic_);
+ auto new_topic_info = ForumTopicInfo(action->new_topic_);
+ if (old_topic_info.is_empty() || new_topic_info.is_empty() ||
+ old_topic_info.get_top_thread_message_id() != new_topic_info.get_top_thread_message_id()) {
+ LOG(ERROR) << "Receive " << to_string(action);
+ return nullptr;
+ }
+ if (old_topic_info.is_closed() != new_topic_info.is_closed()) {
+ return td_api::make_object<td_api::chatEventForumTopicToggleIsClosed>(
+ new_topic_info.get_forum_topic_info_object(td));
+ }
+ return td_api::make_object<td_api::chatEventForumTopicEdited>(old_topic_info.get_forum_topic_info_object(td),
+ new_topic_info.get_forum_topic_info_object(td));
+ }
+ case telegram_api::channelAdminLogEventActionDeleteTopic::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionDeleteTopic>(action_ptr);
+ auto topic_info = ForumTopicInfo(action->topic_);
+ if (topic_info.is_empty()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventForumTopicDeleted>(topic_info.get_forum_topic_info_object(td));
+ }
+ case telegram_api::channelAdminLogEventActionPinTopic::ID: {
+ auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionPinTopic>(action_ptr);
+ ForumTopicInfo old_topic_info;
+ ForumTopicInfo new_topic_info;
+ if (action->prev_topic_ != nullptr) {
+ old_topic_info = ForumTopicInfo(action->prev_topic_);
+ }
+ if (action->new_topic_ != nullptr) {
+ new_topic_info = ForumTopicInfo(action->new_topic_);
+ }
+ if (old_topic_info.is_empty() && new_topic_info.is_empty()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatEventForumTopicPinned>(old_topic_info.get_forum_topic_info_object(td),
+ new_topic_info.get_forum_topic_info_object(td));
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+class GetChannelAdminLogQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::chatEvents>> promise_;
+ ChannelId channel_id_;
+
+ public:
+ explicit GetChannelAdminLogQuery(Promise<td_api::object_ptr<td_api::chatEvents>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, const string &query, int64 from_event_id, int32 limit,
+ tl_object_ptr<telegram_api::channelAdminLogEventsFilter> filter,
+ vector<tl_object_ptr<telegram_api::InputUser>> input_users) {
+ channel_id_ = channel_id;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+
+ int32 flags = 0;
+ if (filter != nullptr) {
+ flags |= telegram_api::channels_getAdminLog::EVENTS_FILTER_MASK;
+ }
+ if (!input_users.empty()) {
+ flags |= telegram_api::channels_getAdminLog::ADMINS_MASK;
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::channels_getAdminLog(
+ flags, std::move(input_channel), query, std::move(filter), std::move(input_users), from_event_id, 0, limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_getAdminLog>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto events = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive in " << channel_id_ << ' ' << to_string(events);
+ td_->contacts_manager_->on_get_users(std::move(events->users_), "on_get_event_log");
+ td_->contacts_manager_->on_get_chats(std::move(events->chats_), "on_get_event_log");
+
+ auto result = td_api::make_object<td_api::chatEvents>();
+ result->events_.reserve(events->events_.size());
+ for (auto &event : events->events_) {
+ if (event->date_ <= 0) {
+ LOG(ERROR) << "Receive wrong event date = " << event->date_;
+ event->date_ = 0;
+ }
+
+ UserId user_id(event->user_id_);
+ if (!user_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << user_id;
+ continue;
+ }
+ LOG_IF(ERROR, !td_->contacts_manager_->have_user(user_id)) << "Receive unknown " << user_id;
+
+ DialogId actor_dialog_id;
+ auto action = get_chat_event_action_object(td_, channel_id_, std::move(event->action_), actor_dialog_id);
+ if (action == nullptr) {
+ continue;
+ }
+ if (user_id == ContactsManager::get_channel_bot_user_id() && actor_dialog_id.is_valid() &&
+ actor_dialog_id.get_type() != DialogType::User) {
+ user_id = UserId();
+ } else {
+ actor_dialog_id = DialogId();
+ }
+ auto actor = get_message_sender_object_const(td_, user_id, actor_dialog_id, "GetChannelAdminLogQuery");
+ result->events_.push_back(
+ td_api::make_object<td_api::chatEvent>(event->id_, event->date_, std::move(actor), std::move(action)));
+ }
+
+ promise_.set_value(std::move(result));
+ }
+
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdminLogQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+static telegram_api::object_ptr<telegram_api::channelAdminLogEventsFilter> get_input_channel_admin_log_events_filter(
+ const td_api::object_ptr<td_api::chatEventLogFilters> &filters) {
+ if (filters == nullptr) {
+ return nullptr;
+ }
+
+ int32 flags = 0;
+ if (filters->message_edits_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::EDIT_MASK;
+ }
+ if (filters->message_deletions_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::DELETE_MASK;
+ }
+ if (filters->message_pins_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::PINNED_MASK;
+ }
+ if (filters->member_joins_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::JOIN_MASK;
+ }
+ if (filters->member_leaves_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::LEAVE_MASK;
+ }
+ if (filters->member_invites_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::INVITE_MASK;
+ }
+ if (filters->member_promotions_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::PROMOTE_MASK;
+ flags |= telegram_api::channelAdminLogEventsFilter::DEMOTE_MASK;
+ }
+ if (filters->member_restrictions_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::BAN_MASK;
+ flags |= telegram_api::channelAdminLogEventsFilter::UNBAN_MASK;
+ flags |= telegram_api::channelAdminLogEventsFilter::KICK_MASK;
+ flags |= telegram_api::channelAdminLogEventsFilter::UNKICK_MASK;
+ }
+ if (filters->info_changes_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::INFO_MASK;
+ }
+ if (filters->setting_changes_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::SETTINGS_MASK;
+ }
+ if (filters->invite_link_changes_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::INVITES_MASK;
+ }
+ if (filters->video_chat_changes_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::GROUP_CALL_MASK;
+ }
+ if (filters->forum_changes_) {
+ flags |= telegram_api::channelAdminLogEventsFilter::FORUMS_MASK;
+ }
+
+ return telegram_api::make_object<telegram_api::channelAdminLogEventsFilter>(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/);
+}
+
+void get_dialog_event_log(Td *td, DialogId dialog_id, const string &query, int64 from_event_id, int32 limit,
+ const td_api::object_ptr<td_api::chatEventLogFilters> &filters,
+ const vector<UserId> &user_ids, Promise<td_api::object_ptr<td_api::chatEvents>> &&promise) {
+ if (!td->messages_manager_->have_dialog_force(dialog_id, "get_dialog_event_log")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return promise.set_error(Status::Error(400, "Chat is not a supergroup chat"));
+ }
+
+ auto channel_id = dialog_id.get_channel_id();
+ if (!td->contacts_manager_->have_channel(channel_id)) {
+ return promise.set_error(Status::Error(400, "Chat info not found"));
+ }
+
+ if (!td->contacts_manager_->get_channel_status(channel_id).is_administrator()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to get event log"));
+ }
+
+ vector<tl_object_ptr<telegram_api::InputUser>> input_users;
+ for (auto user_id : user_ids) {
+ auto r_input_user = td->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
+ input_users.push_back(r_input_user.move_as_ok());
+ }
+
+ td->create_handler<GetChannelAdminLogQuery>(std::move(promise))
+ ->send(channel_id, query, from_event_id, limit, get_input_channel_admin_log_events_filter(filters),
+ std::move(input_users));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogEventLog.h b/protocols/Telegram/tdlib/td/td/telegram/DialogEventLog.h
new file mode 100644
index 0000000000..e47af602fb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogEventLog.h
@@ -0,0 +1,24 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+
+namespace td {
+
+class Td;
+
+void get_dialog_event_log(Td *td, DialogId dialog_id, const string &query, int64 from_event_id, int32 limit,
+ const td_api::object_ptr<td_api::chatEventLogFilters> &filters,
+ const vector<UserId> &user_ids, Promise<td_api::object_ptr<td_api::chatEvents>> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogFilter.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogFilter.cpp
new file mode 100644
index 0000000000..518e530226
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogFilter.cpp
@@ -0,0 +1,480 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogFilter.h"
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Global.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/emoji.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+
+namespace td {
+
+int32 DialogFilter::get_max_filter_dialogs() {
+ return narrow_cast<int32>(G()->get_option_integer("chat_filter_chosen_chat_count_max", 100));
+}
+
+unique_ptr<DialogFilter> DialogFilter::get_dialog_filter(
+ telegram_api::object_ptr<telegram_api::DialogFilter> filter_ptr, bool with_id) {
+ if (filter_ptr->get_id() != telegram_api::dialogFilter::ID) {
+ LOG(ERROR) << "Ignore " << to_string(filter_ptr);
+ return nullptr;
+ }
+ auto filter = telegram_api::move_object_as<telegram_api::dialogFilter>(filter_ptr);
+ DialogFilterId dialog_filter_id(filter->id_);
+ if (with_id && !dialog_filter_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << to_string(filter);
+ return nullptr;
+ }
+ auto dialog_filter = make_unique<DialogFilter>();
+ dialog_filter->dialog_filter_id = dialog_filter_id;
+ dialog_filter->title = std::move(filter->title_);
+ dialog_filter->emoji = std::move(filter->emoticon_);
+ FlatHashSet<DialogId, DialogIdHash> added_dialog_ids;
+ dialog_filter->pinned_dialog_ids = InputDialogId::get_input_dialog_ids(filter->pinned_peers_, &added_dialog_ids);
+ dialog_filter->included_dialog_ids = InputDialogId::get_input_dialog_ids(filter->include_peers_, &added_dialog_ids);
+ dialog_filter->excluded_dialog_ids = InputDialogId::get_input_dialog_ids(filter->exclude_peers_, &added_dialog_ids);
+ auto flags = filter->flags_;
+ dialog_filter->exclude_muted = (flags & telegram_api::dialogFilter::EXCLUDE_MUTED_MASK) != 0;
+ dialog_filter->exclude_read = (flags & telegram_api::dialogFilter::EXCLUDE_READ_MASK) != 0;
+ dialog_filter->exclude_archived = (flags & telegram_api::dialogFilter::EXCLUDE_ARCHIVED_MASK) != 0;
+ dialog_filter->include_contacts = (flags & telegram_api::dialogFilter::CONTACTS_MASK) != 0;
+ dialog_filter->include_non_contacts = (flags & telegram_api::dialogFilter::NON_CONTACTS_MASK) != 0;
+ dialog_filter->include_bots = (flags & telegram_api::dialogFilter::BOTS_MASK) != 0;
+ dialog_filter->include_groups = (flags & telegram_api::dialogFilter::GROUPS_MASK) != 0;
+ dialog_filter->include_channels = (flags & telegram_api::dialogFilter::BROADCASTS_MASK) != 0;
+ return dialog_filter;
+}
+
+void DialogFilter::remove_secret_chat_dialog_ids() {
+ auto remove_secret_chats = [](vector<InputDialogId> &input_dialog_ids) {
+ td::remove_if(input_dialog_ids, [](InputDialogId input_dialog_id) {
+ return input_dialog_id.get_dialog_id().get_type() == DialogType::SecretChat;
+ });
+ };
+ remove_secret_chats(pinned_dialog_ids);
+ remove_secret_chats(included_dialog_ids);
+ remove_secret_chats(excluded_dialog_ids);
+}
+
+bool DialogFilter::is_empty(bool for_server) const {
+ if (include_contacts || include_non_contacts || include_bots || include_groups || include_channels) {
+ return false;
+ }
+
+ if (for_server) {
+ vector<InputDialogId> empty_input_dialog_ids;
+ return InputDialogId::are_equivalent(pinned_dialog_ids, empty_input_dialog_ids) &&
+ InputDialogId::are_equivalent(included_dialog_ids, empty_input_dialog_ids);
+ } else {
+ return pinned_dialog_ids.empty() && included_dialog_ids.empty();
+ }
+}
+
+Status DialogFilter::check_limits() const {
+ auto get_server_dialog_count = [](const vector<InputDialogId> &input_dialog_ids) {
+ int32 result = 0;
+ for (auto &input_dialog_id : input_dialog_ids) {
+ if (input_dialog_id.get_dialog_id().get_type() != DialogType::SecretChat) {
+ result++;
+ }
+ }
+ return result;
+ };
+
+ auto excluded_server_dialog_count = get_server_dialog_count(excluded_dialog_ids);
+ auto included_server_dialog_count = get_server_dialog_count(included_dialog_ids);
+ auto pinned_server_dialog_count = get_server_dialog_count(pinned_dialog_ids);
+
+ auto excluded_secret_dialog_count = static_cast<int32>(excluded_dialog_ids.size()) - excluded_server_dialog_count;
+ auto included_secret_dialog_count = static_cast<int32>(included_dialog_ids.size()) - included_server_dialog_count;
+ auto pinned_secret_dialog_count = static_cast<int32>(pinned_dialog_ids.size()) - pinned_server_dialog_count;
+
+ auto limit = get_max_filter_dialogs();
+ if (excluded_server_dialog_count > limit || excluded_secret_dialog_count > limit) {
+ return Status::Error(400, "The maximum number of excluded chats exceeded");
+ }
+ if (included_server_dialog_count > limit || included_secret_dialog_count > limit) {
+ return Status::Error(400, "The maximum number of included chats exceeded");
+ }
+ if (included_server_dialog_count + pinned_server_dialog_count > limit ||
+ included_secret_dialog_count + pinned_secret_dialog_count > limit) {
+ return Status::Error(400, "The maximum number of pinned chats exceeded");
+ }
+
+ if (is_empty(false)) {
+ return Status::Error(400, "Folder must contain at least 1 chat");
+ }
+
+ if (include_contacts && include_non_contacts && include_bots && include_groups && include_channels &&
+ exclude_archived && !exclude_read && !exclude_muted) {
+ return Status::Error(400, "Folder must be different from the main chat list");
+ }
+
+ return Status::OK();
+}
+
+string DialogFilter::get_emoji_by_icon_name(const string &icon_name) {
+ init_icon_names();
+ auto it = icon_name_to_emoji_.find(icon_name);
+ if (it != icon_name_to_emoji_.end()) {
+ return it->second;
+ }
+ return string();
+}
+
+string DialogFilter::get_icon_name() const {
+ init_icon_names();
+ auto it = emoji_to_icon_name_.find(emoji);
+ if (it != emoji_to_icon_name_.end()) {
+ return it->second;
+ }
+ return string();
+}
+
+string DialogFilter::get_chosen_or_default_icon_name() const {
+ auto icon_name = get_icon_name();
+ if (!icon_name.empty()) {
+ return icon_name;
+ }
+
+ if (!pinned_dialog_ids.empty() || !included_dialog_ids.empty() || !excluded_dialog_ids.empty()) {
+ return "Custom";
+ }
+
+ if (include_contacts || include_non_contacts) {
+ if (!include_bots && !include_groups && !include_channels) {
+ return "Private";
+ }
+ } else {
+ if (!include_bots && !include_channels) {
+ if (!include_groups) {
+ // just in case
+ return "Custom";
+ }
+ return "Groups";
+ }
+ if (!include_bots && !include_groups) {
+ return "Channels";
+ }
+ if (!include_groups && !include_channels) {
+ return "Bots";
+ }
+ }
+ if (exclude_read && !exclude_muted) {
+ return "Unread";
+ }
+ if (exclude_muted && !exclude_read) {
+ return "Unmuted";
+ }
+ return "Custom";
+}
+
+string DialogFilter::get_default_icon_name(const td_api::chatFilter *filter) {
+ if (!filter->icon_name_.empty() && !get_emoji_by_icon_name(filter->icon_name_).empty()) {
+ return filter->icon_name_;
+ }
+
+ if (!filter->pinned_chat_ids_.empty() || !filter->included_chat_ids_.empty() || !filter->excluded_chat_ids_.empty()) {
+ return "Custom";
+ }
+
+ if (filter->include_contacts_ || filter->include_non_contacts_) {
+ if (!filter->include_bots_ && !filter->include_groups_ && !filter->include_channels_) {
+ return "Private";
+ }
+ } else {
+ if (!filter->include_bots_ && !filter->include_channels_) {
+ if (!filter->include_groups_) {
+ // just in case
+ return "Custom";
+ }
+ return "Groups";
+ }
+ if (!filter->include_bots_ && !filter->include_groups_) {
+ return "Channels";
+ }
+ if (!filter->include_groups_ && !filter->include_channels_) {
+ return "Bots";
+ }
+ }
+ if (filter->exclude_read_ && !filter->exclude_muted_) {
+ return "Unread";
+ }
+ if (filter->exclude_muted_ && !filter->exclude_read_) {
+ return "Unmuted";
+ }
+ return "Custom";
+}
+
+telegram_api::object_ptr<telegram_api::DialogFilter> DialogFilter::get_input_dialog_filter() const {
+ int32 flags = 0;
+ if (!emoji.empty()) {
+ flags |= telegram_api::dialogFilter::EMOTICON_MASK;
+ }
+ if (exclude_muted) {
+ flags |= telegram_api::dialogFilter::EXCLUDE_MUTED_MASK;
+ }
+ if (exclude_read) {
+ flags |= telegram_api::dialogFilter::EXCLUDE_READ_MASK;
+ }
+ if (exclude_archived) {
+ flags |= telegram_api::dialogFilter::EXCLUDE_ARCHIVED_MASK;
+ }
+ if (include_contacts) {
+ flags |= telegram_api::dialogFilter::CONTACTS_MASK;
+ }
+ if (include_non_contacts) {
+ flags |= telegram_api::dialogFilter::NON_CONTACTS_MASK;
+ }
+ if (include_bots) {
+ flags |= telegram_api::dialogFilter::BOTS_MASK;
+ }
+ if (include_groups) {
+ flags |= telegram_api::dialogFilter::GROUPS_MASK;
+ }
+ if (include_channels) {
+ flags |= telegram_api::dialogFilter::BROADCASTS_MASK;
+ }
+
+ return telegram_api::make_object<telegram_api::dialogFilter>(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, dialog_filter_id.get(), title, emoji,
+ InputDialogId::get_input_peers(pinned_dialog_ids), InputDialogId::get_input_peers(included_dialog_ids),
+ InputDialogId::get_input_peers(excluded_dialog_ids));
+}
+
+td_api::object_ptr<td_api::chatFilterInfo> DialogFilter::get_chat_filter_info_object() const {
+ return td_api::make_object<td_api::chatFilterInfo>(dialog_filter_id.get(), title, get_chosen_or_default_icon_name());
+}
+
+// merges changes from old_server_filter to new_server_filter in old_filter
+unique_ptr<DialogFilter> DialogFilter::merge_dialog_filter_changes(const DialogFilter *old_filter,
+ const DialogFilter *old_server_filter,
+ const DialogFilter *new_server_filter) {
+ CHECK(old_filter != nullptr);
+ CHECK(old_server_filter != nullptr);
+ CHECK(new_server_filter != nullptr);
+ CHECK(old_filter->dialog_filter_id == old_server_filter->dialog_filter_id);
+ CHECK(old_filter->dialog_filter_id == new_server_filter->dialog_filter_id);
+ auto dialog_filter_id = old_filter->dialog_filter_id;
+ auto new_filter = make_unique<DialogFilter>(*old_filter);
+ new_filter->dialog_filter_id = dialog_filter_id;
+
+ auto merge_ordered_changes = [dialog_filter_id](auto &new_dialog_ids, auto old_server_dialog_ids,
+ auto new_server_dialog_ids) {
+ if (old_server_dialog_ids == new_server_dialog_ids) {
+ LOG(INFO) << "Pinned chats was not changed remotely in " << dialog_filter_id << ", keep local changes";
+ return;
+ }
+
+ if (InputDialogId::are_equivalent(new_dialog_ids, old_server_dialog_ids)) {
+ LOG(INFO) << "Pinned chats was not changed locally in " << dialog_filter_id << ", keep remote changes";
+
+ size_t kept_server_dialogs = 0;
+ FlatHashSet<DialogId, DialogIdHash> removed_dialog_ids;
+ auto old_it = old_server_dialog_ids.rbegin();
+ for (auto &input_dialog_id : reversed(new_server_dialog_ids)) {
+ auto dialog_id = input_dialog_id.get_dialog_id();
+ while (old_it < old_server_dialog_ids.rend()) {
+ if (old_it->get_dialog_id() == dialog_id) {
+ kept_server_dialogs++;
+ ++old_it;
+ break;
+ }
+
+ // remove the dialog, it could be added back later
+ CHECK(old_it->get_dialog_id().is_valid());
+ removed_dialog_ids.insert(old_it->get_dialog_id());
+ ++old_it;
+ }
+ }
+ while (old_it < old_server_dialog_ids.rend()) {
+ // remove the dialog, it could be added back later
+ CHECK(old_it->get_dialog_id().is_valid());
+ removed_dialog_ids.insert(old_it->get_dialog_id());
+ ++old_it;
+ }
+ td::remove_if(new_dialog_ids, [&removed_dialog_ids](auto input_dialog_id) {
+ return removed_dialog_ids.count(input_dialog_id.get_dialog_id()) > 0;
+ });
+ new_dialog_ids.insert(new_dialog_ids.begin(), new_server_dialog_ids.begin(),
+ new_server_dialog_ids.end() - kept_server_dialogs);
+ } else {
+ LOG(WARNING) << "Ignore remote changes of pinned chats in " << dialog_filter_id;
+ // there are both local and remote changes; ignore remote changes for now
+ }
+ };
+
+ auto merge_changes = [](auto &new_dialog_ids, const auto &old_server_dialog_ids, const auto &new_server_dialog_ids) {
+ if (old_server_dialog_ids == new_server_dialog_ids) {
+ // fast path
+ return;
+ }
+
+ // merge additions and deletions from other clients to the local changes
+ FlatHashSet<DialogId, DialogIdHash> deleted_dialog_ids;
+ for (const auto &old_dialog_id : old_server_dialog_ids) {
+ CHECK(old_dialog_id.get_dialog_id().is_valid());
+ deleted_dialog_ids.insert(old_dialog_id.get_dialog_id());
+ }
+ FlatHashSet<DialogId, DialogIdHash> added_dialog_ids;
+ for (const auto &new_dialog_id : new_server_dialog_ids) {
+ auto dialog_id = new_dialog_id.get_dialog_id();
+ if (deleted_dialog_ids.erase(dialog_id) == 0) {
+ added_dialog_ids.insert(dialog_id);
+ }
+ }
+ vector<InputDialogId> result;
+ for (const auto &input_dialog_id : new_dialog_ids) {
+ // do not add dialog twice
+ added_dialog_ids.erase(input_dialog_id.get_dialog_id());
+ }
+ for (const auto &new_dialog_id : new_server_dialog_ids) {
+ if (added_dialog_ids.count(new_dialog_id.get_dialog_id()) == 1) {
+ result.push_back(new_dialog_id);
+ }
+ }
+ for (const auto &input_dialog_id : new_dialog_ids) {
+ if (deleted_dialog_ids.count(input_dialog_id.get_dialog_id()) == 0) {
+ result.push_back(input_dialog_id);
+ }
+ }
+ new_dialog_ids = std::move(result);
+ };
+
+ merge_ordered_changes(new_filter->pinned_dialog_ids, old_server_filter->pinned_dialog_ids,
+ new_server_filter->pinned_dialog_ids);
+ merge_changes(new_filter->included_dialog_ids, old_server_filter->included_dialog_ids,
+ new_server_filter->included_dialog_ids);
+ merge_changes(new_filter->excluded_dialog_ids, old_server_filter->excluded_dialog_ids,
+ new_server_filter->excluded_dialog_ids);
+
+ {
+ FlatHashSet<DialogId, DialogIdHash> added_dialog_ids;
+ auto remove_duplicates = [&added_dialog_ids](auto &input_dialog_ids) {
+ td::remove_if(input_dialog_ids, [&added_dialog_ids](auto input_dialog_id) {
+ auto dialog_id = input_dialog_id.get_dialog_id();
+ CHECK(dialog_id.is_valid());
+ return !added_dialog_ids.insert(dialog_id).second;
+ });
+ };
+ remove_duplicates(new_filter->pinned_dialog_ids);
+ remove_duplicates(new_filter->included_dialog_ids);
+ remove_duplicates(new_filter->excluded_dialog_ids);
+ }
+
+ auto update_value = [](auto &new_value, const auto &old_server_value, const auto &new_server_value) {
+ // if the value was changed from other client and wasn't changed from the current client, update it
+ if (new_server_value != old_server_value && old_server_value == new_value) {
+ new_value = new_server_value;
+ }
+ };
+
+ update_value(new_filter->exclude_muted, old_server_filter->exclude_muted, new_server_filter->exclude_muted);
+ update_value(new_filter->exclude_read, old_server_filter->exclude_read, new_server_filter->exclude_read);
+ update_value(new_filter->exclude_archived, old_server_filter->exclude_archived, new_server_filter->exclude_archived);
+ update_value(new_filter->include_contacts, old_server_filter->include_contacts, new_server_filter->include_contacts);
+ update_value(new_filter->include_non_contacts, old_server_filter->include_non_contacts,
+ new_server_filter->include_non_contacts);
+ update_value(new_filter->include_bots, old_server_filter->include_bots, new_server_filter->include_bots);
+ update_value(new_filter->include_groups, old_server_filter->include_groups, new_server_filter->include_groups);
+ update_value(new_filter->include_channels, old_server_filter->include_channels, new_server_filter->include_channels);
+
+ if (new_filter->check_limits().is_error()) {
+ LOG(WARNING) << "Failed to merge local and remote changes in " << new_filter->dialog_filter_id
+ << ", keep only local changes";
+ *new_filter = *old_filter;
+ }
+
+ update_value(new_filter->title, old_server_filter->title, new_server_filter->title);
+ update_value(new_filter->emoji, old_server_filter->emoji, new_server_filter->emoji);
+ return new_filter;
+}
+
+bool DialogFilter::are_similar(const DialogFilter &lhs, const DialogFilter &rhs) {
+ if (lhs.title == rhs.title) {
+ return true;
+ }
+ if (!are_flags_equal(lhs, rhs)) {
+ return false;
+ }
+
+ vector<InputDialogId> empty_input_dialog_ids;
+ if (InputDialogId::are_equivalent(lhs.excluded_dialog_ids, empty_input_dialog_ids) !=
+ InputDialogId::are_equivalent(rhs.excluded_dialog_ids, empty_input_dialog_ids)) {
+ return false;
+ }
+ if ((InputDialogId::are_equivalent(lhs.pinned_dialog_ids, empty_input_dialog_ids) &&
+ InputDialogId::are_equivalent(lhs.included_dialog_ids, empty_input_dialog_ids)) !=
+ (InputDialogId::are_equivalent(rhs.pinned_dialog_ids, empty_input_dialog_ids) &&
+ InputDialogId::are_equivalent(rhs.included_dialog_ids, empty_input_dialog_ids))) {
+ return false;
+ }
+
+ return true;
+}
+
+bool DialogFilter::are_equivalent(const DialogFilter &lhs, const DialogFilter &rhs) {
+ return lhs.title == rhs.title && lhs.emoji == rhs.emoji &&
+ InputDialogId::are_equivalent(lhs.pinned_dialog_ids, rhs.pinned_dialog_ids) &&
+ InputDialogId::are_equivalent(lhs.included_dialog_ids, rhs.included_dialog_ids) &&
+ InputDialogId::are_equivalent(lhs.excluded_dialog_ids, rhs.excluded_dialog_ids) && are_flags_equal(lhs, rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogFilter &filter) {
+ return string_builder << filter.dialog_filter_id << " (pinned " << filter.pinned_dialog_ids << ", included "
+ << filter.included_dialog_ids << ", excluded " << filter.excluded_dialog_ids << ", "
+ << filter.exclude_muted << ' ' << filter.exclude_read << ' ' << filter.exclude_archived << '/'
+ << filter.include_contacts << ' ' << filter.include_non_contacts << ' ' << filter.include_bots
+ << ' ' << filter.include_groups << ' ' << filter.include_channels << ')';
+}
+
+void DialogFilter::init_icon_names() {
+ static bool is_inited = [&] {
+ vector<string> emojis{"\xF0\x9F\x92\xAC", "\xE2\x9C\x85", "\xF0\x9F\x94\x94",
+ "\xF0\x9F\xA4\x96", "\xF0\x9F\x93\xA2", "\xF0\x9F\x91\xA5",
+ "\xF0\x9F\x91\xA4", "\xF0\x9F\x93\x81", "\xF0\x9F\x93\x8B",
+ "\xF0\x9F\x90\xB1", "\xF0\x9F\x91\x91", "\xE2\xAD\x90\xEF\xB8\x8F",
+ "\xF0\x9F\x8C\xB9", "\xF0\x9F\x8E\xAE", "\xF0\x9F\x8F\xA0",
+ "\xE2\x9D\xA4\xEF\xB8\x8F", "\xF0\x9F\x8E\xAD", "\xF0\x9F\x8D\xB8",
+ "\xE2\x9A\xBD\xEF\xB8\x8F", "\xF0\x9F\x8E\x93", "\xF0\x9F\x93\x88",
+ "\xE2\x9C\x88\xEF\xB8\x8F", "\xF0\x9F\x92\xBC", "\xF0\x9F\x9B\xAB",
+ "\xF0\x9F\x93\x95", "\xF0\x9F\x92\xA1", "\xF0\x9F\x91\x8D",
+ "\xF0\x9F\x92\xB0", "\xF0\x9F\x8E\xB5", "\xF0\x9F\x8E\xA8"};
+ vector<string> icon_names{"All", "Unread", "Unmuted", "Bots", "Channels", "Groups", "Private", "Custom",
+ "Setup", "Cat", "Crown", "Favorite", "Flower", "Game", "Home", "Love",
+ "Mask", "Party", "Sport", "Study", "Trade", "Travel", "Work", "Airplane",
+ "Book", "Light", "Like", "Money", "Note", "Palette"};
+
+ CHECK(emojis.size() == icon_names.size());
+ for (size_t i = 0; i < emojis.size(); i++) {
+ remove_emoji_modifiers_in_place(emojis[i]);
+ bool is_inserted = emoji_to_icon_name_.emplace(emojis[i], icon_names[i]).second &&
+ icon_name_to_emoji_.emplace(icon_names[i], emojis[i]).second;
+ CHECK(is_inserted);
+ }
+ return true;
+ }();
+ CHECK(is_inited);
+}
+
+bool DialogFilter::are_flags_equal(const DialogFilter &lhs, const DialogFilter &rhs) {
+ return lhs.exclude_muted == rhs.exclude_muted && lhs.exclude_read == rhs.exclude_read &&
+ lhs.exclude_archived == rhs.exclude_archived && lhs.include_contacts == rhs.include_contacts &&
+ lhs.include_non_contacts == rhs.include_non_contacts && lhs.include_bots == rhs.include_bots &&
+ lhs.include_groups == rhs.include_groups && lhs.include_channels == rhs.include_channels;
+}
+
+FlatHashMap<string, string> DialogFilter::emoji_to_icon_name_;
+FlatHashMap<string, string> DialogFilter::icon_name_to_emoji_;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogFilter.h b/protocols/Telegram/tdlib/td/td/telegram/DialogFilter.h
new file mode 100644
index 0000000000..f42759ca02
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogFilter.h
@@ -0,0 +1,105 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogFilterId.h"
+#include "td/telegram/InputDialogId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class DialogFilter {
+ public:
+ DialogFilterId dialog_filter_id;
+ string title;
+ string emoji;
+ vector<InputDialogId> pinned_dialog_ids;
+ vector<InputDialogId> included_dialog_ids;
+ vector<InputDialogId> excluded_dialog_ids;
+ bool exclude_muted = false;
+ bool exclude_read = false;
+ bool exclude_archived = false;
+ bool include_contacts = false;
+ bool include_non_contacts = false;
+ bool include_bots = false;
+ bool include_groups = false;
+ bool include_channels = false;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+
+ static int32 get_max_filter_dialogs();
+
+ static unique_ptr<DialogFilter> get_dialog_filter(telegram_api::object_ptr<telegram_api::DialogFilter> filter_ptr,
+ bool with_id);
+
+ void remove_secret_chat_dialog_ids();
+
+ bool is_empty(bool for_server) const;
+
+ Status check_limits() const;
+
+ static string get_emoji_by_icon_name(const string &icon_name);
+
+ string get_icon_name() const;
+
+ static string get_default_icon_name(const td_api::chatFilter *filter);
+
+ telegram_api::object_ptr<telegram_api::DialogFilter> get_input_dialog_filter() const;
+
+ td_api::object_ptr<td_api::chatFilterInfo> get_chat_filter_info_object() const;
+
+ // merges changes from old_server_filter to new_server_filter in old_filter
+ static unique_ptr<DialogFilter> merge_dialog_filter_changes(const DialogFilter *old_filter,
+ const DialogFilter *old_server_filter,
+ const DialogFilter *new_server_filter);
+
+ static bool are_similar(const DialogFilter &lhs, const DialogFilter &rhs);
+
+ static bool are_equivalent(const DialogFilter &lhs, const DialogFilter &rhs);
+
+ static bool are_flags_equal(const DialogFilter &lhs, const DialogFilter &rhs);
+
+ private:
+ static FlatHashMap<string, string> emoji_to_icon_name_;
+ static FlatHashMap<string, string> icon_name_to_emoji_;
+
+ static void init_icon_names();
+
+ string get_chosen_or_default_icon_name() const;
+};
+
+inline bool operator==(const DialogFilter &lhs, const DialogFilter &rhs) {
+ return lhs.dialog_filter_id == rhs.dialog_filter_id && lhs.title == rhs.title && lhs.emoji == rhs.emoji &&
+ lhs.pinned_dialog_ids == rhs.pinned_dialog_ids && lhs.included_dialog_ids == rhs.included_dialog_ids &&
+ lhs.excluded_dialog_ids == rhs.excluded_dialog_ids && DialogFilter::are_flags_equal(lhs, rhs);
+}
+
+inline bool operator!=(const DialogFilter &lhs, const DialogFilter &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator==(const unique_ptr<DialogFilter> &lhs, const unique_ptr<DialogFilter> &rhs) {
+ return *lhs == *rhs;
+}
+
+inline bool operator!=(const unique_ptr<DialogFilter> &lhs, const unique_ptr<DialogFilter> &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogFilter &filter);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogFilter.hpp b/protocols/Telegram/tdlib/td/td/telegram/DialogFilter.hpp
new file mode 100644
index 0000000000..2becce0430
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogFilter.hpp
@@ -0,0 +1,84 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogFilter.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void DialogFilter::store(StorerT &storer) const {
+ using td::store;
+ bool has_pinned_dialog_ids = !pinned_dialog_ids.empty();
+ bool has_included_dialog_ids = !included_dialog_ids.empty();
+ bool has_excluded_dialog_ids = !excluded_dialog_ids.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(exclude_muted);
+ STORE_FLAG(exclude_read);
+ STORE_FLAG(exclude_archived);
+ STORE_FLAG(include_contacts);
+ STORE_FLAG(include_non_contacts);
+ STORE_FLAG(include_bots);
+ STORE_FLAG(include_groups);
+ STORE_FLAG(include_channels);
+ STORE_FLAG(has_pinned_dialog_ids);
+ STORE_FLAG(has_included_dialog_ids);
+ STORE_FLAG(has_excluded_dialog_ids);
+ END_STORE_FLAGS();
+
+ store(dialog_filter_id, storer);
+ store(title, storer);
+ store(emoji, storer);
+ if (has_pinned_dialog_ids) {
+ store(pinned_dialog_ids, storer);
+ }
+ if (has_included_dialog_ids) {
+ store(included_dialog_ids, storer);
+ }
+ if (has_excluded_dialog_ids) {
+ store(excluded_dialog_ids, storer);
+ }
+}
+
+template <class ParserT>
+void DialogFilter::parse(ParserT &parser) {
+ using td::parse;
+ bool has_pinned_dialog_ids;
+ bool has_included_dialog_ids;
+ bool has_excluded_dialog_ids;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(exclude_muted);
+ PARSE_FLAG(exclude_read);
+ PARSE_FLAG(exclude_archived);
+ PARSE_FLAG(include_contacts);
+ PARSE_FLAG(include_non_contacts);
+ PARSE_FLAG(include_bots);
+ PARSE_FLAG(include_groups);
+ PARSE_FLAG(include_channels);
+ PARSE_FLAG(has_pinned_dialog_ids);
+ PARSE_FLAG(has_included_dialog_ids);
+ PARSE_FLAG(has_excluded_dialog_ids);
+ END_PARSE_FLAGS();
+
+ parse(dialog_filter_id, parser);
+ parse(title, parser);
+ parse(emoji, parser);
+ if (has_pinned_dialog_ids) {
+ parse(pinned_dialog_ids, parser);
+ }
+ if (has_included_dialog_ids) {
+ parse(included_dialog_ids, parser);
+ }
+ if (has_excluded_dialog_ids) {
+ parse(excluded_dialog_ids, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogFilterId.h b/protocols/Telegram/tdlib/td/td/telegram/DialogFilterId.h
new file mode 100644
index 0000000000..b8b746f330
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogFilterId.h
@@ -0,0 +1,73 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class DialogFilterId {
+ int32 id = 0;
+
+ public:
+ DialogFilterId() = default;
+
+ explicit constexpr DialogFilterId(int32 dialog_filter_id) : id(dialog_filter_id) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ DialogFilterId(T dialog_filter_id) = delete;
+
+ static constexpr DialogFilterId min() {
+ return DialogFilterId(static_cast<int32>(2));
+ }
+ static constexpr DialogFilterId max() {
+ return DialogFilterId(static_cast<int32>(255));
+ }
+
+ bool is_valid() const {
+ // don't check max() for greater future flexibility
+ return id >= min().get();
+ }
+
+ int32 get() const {
+ return id;
+ }
+
+ bool operator==(const DialogFilterId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const DialogFilterId &other) const {
+ return id != other.id;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ storer.store_int(id);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ id = parser.fetch_int();
+ }
+};
+
+struct DialogFilterIdHash {
+ uint32 operator()(DialogFilterId dialog_filter_id) const {
+ return Hash<int32>()(dialog_filter_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, DialogFilterId dialog_filter_id) {
+ return string_builder << "filter " << dialog_filter_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogId.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogId.cpp
index 7ea818599c..9c69d4ebb5 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DialogId.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogId.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,42 +10,31 @@
#include "td/utils/logging.h"
+#include <limits>
+
namespace td {
bool DialogId::is_valid() const {
- switch (get_type()) {
- case DialogType::User: {
- return get_user_id().is_valid();
- }
- case DialogType::Chat: {
- return get_chat_id().is_valid();
- }
- case DialogType::Channel: {
- return get_channel_id().is_valid();
- }
- case DialogType::SecretChat: {
- return get_secret_chat_id().is_valid();
- }
- case DialogType::None:
- return false;
- default:
- UNREACHABLE();
- return false;
- }
+ return get_type() != DialogType::None;
}
DialogType DialogId::get_type() const {
+ // check that valid ranges are continuous
+ static_assert(ZERO_CHANNEL_ID + 1 == -ChatId::MAX_CHAT_ID, "");
+ static_assert(
+ ZERO_SECRET_CHAT_ID + std::numeric_limits<int32>::max() + 1 == ZERO_CHANNEL_ID - ChannelId::MAX_CHANNEL_ID, "");
+
if (id < 0) {
- if (MIN_CHAT_ID <= id) {
+ if (-ChatId::MAX_CHAT_ID <= id) {
return DialogType::Chat;
}
- if (MIN_CHANNEL_ID <= id && id < MAX_CHANNEL_ID) {
+ if (ZERO_CHANNEL_ID - ChannelId::MAX_CHANNEL_ID <= id && id != ZERO_CHANNEL_ID) {
return DialogType::Channel;
}
- if (MIN_SECRET_ID <= id && id < MAX_SECRET_ID) {
+ if (ZERO_SECRET_CHAT_ID + std::numeric_limits<int32>::min() <= id && id != ZERO_SECRET_CHAT_ID) {
return DialogType::SecretChat;
}
- } else if (0 < id && id <= MAX_USER_ID) {
+ } else if (0 < id && id <= UserId::MAX_USER_ID) {
return DialogType::User;
}
return DialogType::None;
@@ -53,27 +42,27 @@ DialogType DialogId::get_type() const {
UserId DialogId::get_user_id() const {
CHECK(get_type() == DialogType::User);
- return UserId(static_cast<int32>(id));
+ return UserId(id);
}
ChatId DialogId::get_chat_id() const {
CHECK(get_type() == DialogType::Chat);
- return ChatId(static_cast<int32>(-id));
-}
-
-SecretChatId DialogId::get_secret_chat_id() const {
- CHECK(get_type() == DialogType::SecretChat);
- return SecretChatId(static_cast<int32>(id - ZERO_SECRET_ID));
+ return ChatId(-id);
}
ChannelId DialogId::get_channel_id() const {
CHECK(get_type() == DialogType::Channel);
- return ChannelId(static_cast<int32>(MAX_CHANNEL_ID - id));
+ return ChannelId(ZERO_CHANNEL_ID - id);
+}
+
+SecretChatId DialogId::get_secret_chat_id() const {
+ CHECK(get_type() == DialogType::SecretChat);
+ return SecretChatId(static_cast<int32>(id - ZERO_SECRET_CHAT_ID));
}
DialogId::DialogId(UserId user_id) {
if (user_id.is_valid()) {
- id = static_cast<int64>(user_id.get());
+ id = user_id.get();
} else {
id = 0;
}
@@ -81,7 +70,7 @@ DialogId::DialogId(UserId user_id) {
DialogId::DialogId(ChatId chat_id) {
if (chat_id.is_valid()) {
- id = -static_cast<int64>(chat_id.get());
+ id = -chat_id.get();
} else {
id = 0;
}
@@ -89,22 +78,34 @@ DialogId::DialogId(ChatId chat_id) {
DialogId::DialogId(ChannelId channel_id) {
if (channel_id.is_valid()) {
- id = MAX_CHANNEL_ID - static_cast<int64>(channel_id.get());
+ id = ZERO_CHANNEL_ID - channel_id.get();
} else {
id = 0;
}
}
-DialogId::DialogId(SecretChatId chat_id) {
- if (chat_id.is_valid()) {
- id = ZERO_SECRET_ID + static_cast<int64>(chat_id.get());
+DialogId::DialogId(SecretChatId secret_chat_id) {
+ if (secret_chat_id.is_valid()) {
+ id = ZERO_SECRET_CHAT_ID + static_cast<int64>(secret_chat_id.get());
} else {
id = 0;
}
}
-DialogId::DialogId(const tl_object_ptr<telegram_api::dialogPeer> &dialog_peer) {
- id = get_peer_id(dialog_peer->peer_);
+DialogId::DialogId(const tl_object_ptr<telegram_api::DialogPeer> &dialog_peer) {
+ CHECK(dialog_peer != nullptr);
+ switch (dialog_peer->get_id()) {
+ case telegram_api::dialogPeer::ID:
+ id = get_peer_id(static_cast<const telegram_api::dialogPeer *>(dialog_peer.get())->peer_);
+ break;
+ case telegram_api::dialogPeerFolder::ID:
+ LOG(ERROR) << "Receive unsupported " << to_string(dialog_peer);
+ id = 0;
+ break;
+ default:
+ id = 0;
+ UNREACHABLE();
+ }
}
DialogId::DialogId(const tl_object_ptr<telegram_api::Peer> &peer) : id(get_peer_id(peer)) {
@@ -122,7 +123,7 @@ int64 DialogId::get_peer_id(const tl_object_ptr<telegram_api::Peer> &peer) {
return 0;
}
- return static_cast<int64>(user_id.get());
+ return user_id.get();
}
case telegram_api::peerChat::ID: {
auto peer_chat = static_cast<const telegram_api::peerChat *>(peer.get());
@@ -132,7 +133,7 @@ int64 DialogId::get_peer_id(const tl_object_ptr<telegram_api::Peer> &peer) {
return 0;
}
- return -static_cast<int64>(chat_id.get());
+ return -chat_id.get();
}
case telegram_api::peerChannel::ID: {
auto peer_channel = static_cast<const telegram_api::peerChannel *>(peer.get());
@@ -142,7 +143,7 @@ int64 DialogId::get_peer_id(const tl_object_ptr<telegram_api::Peer> &peer) {
return 0;
}
- return MAX_CHANNEL_ID - static_cast<int64>(channel_id.get());
+ return ZERO_CHANNEL_ID - channel_id.get();
}
default:
UNREACHABLE();
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogId.h b/protocols/Telegram/tdlib/td/td/telegram/DialogId.h
index ac11ced8a8..6e354bfcb3 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DialogId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogId.h
@@ -1,43 +1,36 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/telegram_api.h"
-
#include "td/telegram/ChannelId.h"
#include "td/telegram/ChatId.h"
#include "td/telegram/SecretChatId.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/StringBuilder.h"
-#include <functional>
#include <type_traits>
namespace td {
-enum class DialogType { None, User, Chat, Channel, SecretChat };
+enum class DialogType : int32 { None, User, Chat, Channel, SecretChat };
class DialogId {
- static constexpr int64 MIN_SECRET_ID = -2002147483648ll;
- static constexpr int64 ZERO_SECRET_ID = -2000000000000ll;
- static constexpr int64 MAX_SECRET_ID = -1997852516353ll;
- static constexpr int64 MIN_CHANNEL_ID = -1002147483647ll;
- static constexpr int64 MAX_CHANNEL_ID = -1000000000000ll;
- static constexpr int64 MIN_CHAT_ID = -2147483647ll;
- static constexpr int64 MAX_USER_ID = 2147483647ll;
+ static constexpr int64 ZERO_SECRET_CHAT_ID = -2000000000000ll;
+ static constexpr int64 ZERO_CHANNEL_ID = -1000000000000ll;
int64 id = 0;
static int64 get_peer_id(const tl_object_ptr<telegram_api::Peer> &peer);
public:
- using UnderlyingType = decltype(id);
DialogId() = default;
explicit DialogId(int64 dialog_id) : id(dialog_id) {
@@ -45,12 +38,12 @@ class DialogId {
template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
DialogId(T dialog_id) = delete;
- explicit DialogId(const tl_object_ptr<telegram_api::dialogPeer> &dialog_peer);
+ explicit DialogId(const tl_object_ptr<telegram_api::DialogPeer> &dialog_peer);
explicit DialogId(const tl_object_ptr<telegram_api::Peer> &peer);
explicit DialogId(UserId user_id);
explicit DialogId(ChatId chat_id);
explicit DialogId(ChannelId channel_id);
- explicit DialogId(SecretChatId chat_id);
+ explicit DialogId(SecretChatId secret_chat_id);
int64 get() const {
return id;
@@ -70,8 +63,8 @@ class DialogId {
UserId get_user_id() const;
ChatId get_chat_id() const;
- SecretChatId get_secret_chat_id() const;
ChannelId get_channel_id() const;
+ SecretChatId get_secret_chat_id() const;
template <class StorerT>
void store(StorerT &storer) const {
@@ -85,8 +78,8 @@ class DialogId {
};
struct DialogIdHash {
- std::size_t operator()(DialogId dialog_id) const {
- return std::hash<int64>()(dialog_id.get());
+ uint32 operator()(DialogId dialog_id) const {
+ return Hash<int64>()(dialog_id.get());
}
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogInviteLink.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogInviteLink.cpp
new file mode 100644
index 0000000000..736a822ac4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogInviteLink.cpp
@@ -0,0 +1,132 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogInviteLink.h"
+
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/LinkManager.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
+
+namespace td {
+
+DialogInviteLink::DialogInviteLink(tl_object_ptr<telegram_api::ExportedChatInvite> exported_invite_ptr,
+ const char *source) {
+ if (exported_invite_ptr == nullptr) {
+ return;
+ }
+ if (exported_invite_ptr->get_id() != telegram_api::chatInviteExported::ID) {
+ CHECK(exported_invite_ptr->get_id() == telegram_api::chatInvitePublicJoinRequests::ID)
+ Slice slice(source);
+ if (slice != "channelAdminLogEventActionParticipantJoinByRequest" && slice != "updateChatParticipant" &&
+ slice != "updateChannelParticipant" && slice != "updateBotChatInviteRequester") {
+ LOG(ERROR) << "Receive from " << source << ' ' << to_string(exported_invite_ptr);
+ }
+ return;
+ }
+
+ auto exported_invite = move_tl_object_as<telegram_api::chatInviteExported>(exported_invite_ptr);
+ invite_link_ = std::move(exported_invite->link_);
+ title_ = std::move(exported_invite->title_);
+ creator_user_id_ = UserId(exported_invite->admin_id_);
+ date_ = exported_invite->date_;
+ expire_date_ = exported_invite->expire_date_;
+ usage_limit_ = exported_invite->usage_limit_;
+ usage_count_ = exported_invite->usage_;
+ edit_date_ = exported_invite->start_date_;
+ request_count_ = exported_invite->requested_;
+ creates_join_request_ = exported_invite->request_needed_;
+ is_revoked_ = exported_invite->revoked_;
+ is_permanent_ = exported_invite->permanent_;
+
+ string full_source = PSTRING() << "invite link " << invite_link_ << " from " << source;
+ LOG_IF(ERROR, !is_valid_invite_link(invite_link_)) << "Unsupported " << full_source;
+ if (!creator_user_id_.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << creator_user_id_ << " as creator of " << full_source;
+ creator_user_id_ = UserId();
+ }
+ if (date_ != 0 && date_ < 1000000000) {
+ LOG(ERROR) << "Receive wrong date " << date_ << " as a creation date of " << full_source;
+ date_ = 0;
+ }
+ if (expire_date_ != 0 && expire_date_ < 1000000000) {
+ LOG(ERROR) << "Receive wrong date " << expire_date_ << " as an expire date of " << full_source;
+ expire_date_ = 0;
+ }
+ if (usage_limit_ < 0) {
+ LOG(ERROR) << "Receive wrong usage limit " << usage_limit_ << " for " << full_source;
+ usage_limit_ = 0;
+ }
+ if (usage_count_ < 0) {
+ LOG(ERROR) << "Receive wrong usage count " << usage_count_ << " for " << full_source;
+ usage_count_ = 0;
+ }
+ if (edit_date_ != 0 && edit_date_ < 1000000000) {
+ LOG(ERROR) << "Receive wrong date " << edit_date_ << " as an edit date of " << full_source;
+ edit_date_ = 0;
+ }
+ if (request_count_ < 0) {
+ LOG(ERROR) << "Receive wrong pending join request count " << request_count_ << " for " << full_source;
+ request_count_ = 0;
+ }
+
+ if (is_permanent_ && (!title_.empty() || expire_date_ > 0 || usage_limit_ > 0 || edit_date_ > 0 ||
+ request_count_ > 0 || creates_join_request_)) {
+ LOG(ERROR) << "Receive wrong permanent " << full_source << ' ' << *this;
+ title_.clear();
+ expire_date_ = 0;
+ usage_limit_ = 0;
+ edit_date_ = 0;
+ request_count_ = 0;
+ creates_join_request_ = false;
+ }
+ if (creates_join_request_ && usage_limit_ > 0) {
+ LOG(ERROR) << "Receive wrong permanent " << full_source << ' ' << *this;
+ usage_limit_ = 0;
+ }
+}
+
+bool DialogInviteLink::is_valid_invite_link(Slice invite_link) {
+ return !LinkManager::get_dialog_invite_link_hash(invite_link).empty();
+}
+
+td_api::object_ptr<td_api::chatInviteLink> DialogInviteLink::get_chat_invite_link_object(
+ const ContactsManager *contacts_manager) const {
+ CHECK(contacts_manager != nullptr);
+ if (!is_valid()) {
+ return nullptr;
+ }
+
+ return td_api::make_object<td_api::chatInviteLink>(
+ invite_link_, title_, contacts_manager->get_user_id_object(creator_user_id_, "get_chat_invite_link_object"),
+ date_, edit_date_, expire_date_, usage_limit_, usage_count_, request_count_, creates_join_request_, is_permanent_,
+ is_revoked_);
+}
+
+bool operator==(const DialogInviteLink &lhs, const DialogInviteLink &rhs) {
+ return lhs.invite_link_ == rhs.invite_link_ && lhs.title_ == rhs.title_ &&
+ lhs.creator_user_id_ == rhs.creator_user_id_ && lhs.date_ == rhs.date_ && lhs.edit_date_ == rhs.edit_date_ &&
+ lhs.expire_date_ == rhs.expire_date_ && lhs.usage_limit_ == rhs.usage_limit_ &&
+ lhs.usage_count_ == rhs.usage_count_ && lhs.request_count_ == rhs.request_count_ &&
+ lhs.creates_join_request_ == rhs.creates_join_request_ && lhs.is_permanent_ == rhs.is_permanent_ &&
+ lhs.is_revoked_ == rhs.is_revoked_;
+}
+
+bool operator!=(const DialogInviteLink &lhs, const DialogInviteLink &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogInviteLink &invite_link) {
+ return string_builder << "ChatInviteLink[" << invite_link.invite_link_ << '(' << invite_link.title_ << ')'
+ << (invite_link.creates_join_request_ ? " creating join request" : "") << " by "
+ << invite_link.creator_user_id_ << " created at " << invite_link.date_ << " edited at "
+ << invite_link.edit_date_ << " expiring at " << invite_link.expire_date_ << " used by "
+ << invite_link.usage_count_ << " with usage limit " << invite_link.usage_limit_ << " and "
+ << invite_link.request_count_ << " pending join requests]";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogInviteLink.h b/protocols/Telegram/tdlib/td/td/telegram/DialogInviteLink.h
new file mode 100644
index 0000000000..813717f649
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogInviteLink.h
@@ -0,0 +1,161 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class ContactsManager;
+
+class DialogInviteLink {
+ string invite_link_;
+ string title_;
+ UserId creator_user_id_;
+ int32 date_ = 0;
+ int32 edit_date_ = 0;
+ int32 expire_date_ = 0;
+ int32 usage_limit_ = 0;
+ int32 usage_count_ = 0;
+ int32 request_count_ = 0;
+ bool creates_join_request_ = false;
+ bool is_revoked_ = false;
+ bool is_permanent_ = false;
+
+ friend bool operator==(const DialogInviteLink &lhs, const DialogInviteLink &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogInviteLink &invite_link);
+
+ public:
+ DialogInviteLink() = default;
+
+ DialogInviteLink(tl_object_ptr<telegram_api::ExportedChatInvite> exported_invite_ptr, const char *source);
+
+ static bool is_valid_invite_link(Slice invite_link);
+
+ td_api::object_ptr<td_api::chatInviteLink> get_chat_invite_link_object(const ContactsManager *contacts_manager) const;
+
+ bool is_valid() const {
+ return !invite_link_.empty() && creator_user_id_.is_valid() && date_ > 0;
+ }
+
+ bool is_permanent() const {
+ return is_permanent_;
+ }
+
+ const string &get_invite_link() const {
+ return invite_link_;
+ }
+
+ UserId get_creator_user_id() const {
+ return creator_user_id_;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ bool has_expire_date = expire_date_ != 0;
+ bool has_usage_limit = usage_limit_ != 0;
+ bool has_usage_count = usage_count_ != 0;
+ bool has_edit_date = edit_date_ != 0;
+ bool has_request_count = request_count_ != 0;
+ bool has_title = !title_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_revoked_);
+ STORE_FLAG(is_permanent_);
+ STORE_FLAG(has_expire_date);
+ STORE_FLAG(has_usage_limit);
+ STORE_FLAG(has_usage_count);
+ STORE_FLAG(has_edit_date);
+ STORE_FLAG(has_request_count);
+ STORE_FLAG(creates_join_request_);
+ STORE_FLAG(has_title);
+ END_STORE_FLAGS();
+ store(invite_link_, storer);
+ store(creator_user_id_, storer);
+ store(date_, storer);
+ if (has_expire_date) {
+ store(expire_date_, storer);
+ }
+ if (has_usage_limit) {
+ store(usage_limit_, storer);
+ }
+ if (has_usage_count) {
+ store(usage_count_, storer);
+ }
+ if (has_edit_date) {
+ store(edit_date_, storer);
+ }
+ if (has_request_count) {
+ store(request_count_, storer);
+ }
+ if (has_title) {
+ store(title_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ bool has_expire_date;
+ bool has_usage_limit;
+ bool has_usage_count;
+ bool has_edit_date;
+ bool has_request_count;
+ bool has_title;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_revoked_);
+ PARSE_FLAG(is_permanent_);
+ PARSE_FLAG(has_expire_date);
+ PARSE_FLAG(has_usage_limit);
+ PARSE_FLAG(has_usage_count);
+ PARSE_FLAG(has_edit_date);
+ PARSE_FLAG(has_request_count);
+ PARSE_FLAG(creates_join_request_);
+ PARSE_FLAG(has_title);
+ END_PARSE_FLAGS();
+ parse(invite_link_, parser);
+ parse(creator_user_id_, parser);
+ parse(date_, parser);
+ if (has_expire_date) {
+ parse(expire_date_, parser);
+ }
+ if (has_usage_limit) {
+ parse(usage_limit_, parser);
+ }
+ if (has_usage_count) {
+ parse(usage_count_, parser);
+ }
+ if (has_edit_date) {
+ parse(edit_date_, parser);
+ }
+ if (has_request_count) {
+ parse(request_count_, parser);
+ }
+ if (has_title) {
+ parse(title_, parser);
+ }
+ if (creates_join_request_) {
+ usage_limit_ = 0;
+ }
+ }
+};
+
+bool operator==(const DialogInviteLink &lhs, const DialogInviteLink &rhs);
+
+bool operator!=(const DialogInviteLink &lhs, const DialogInviteLink &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogInviteLink &invite_link);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogListId.h b/protocols/Telegram/tdlib/td/td/telegram/DialogListId.h
new file mode 100644
index 0000000000..19d386abbf
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogListId.h
@@ -0,0 +1,138 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogFilterId.h"
+#include "td/telegram/FolderId.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <limits>
+#include <type_traits>
+
+namespace td {
+
+class DialogListId {
+ int64 id = 0;
+
+ static constexpr int64 FILTER_ID_SHIFT = static_cast<int64>(1) << 32;
+
+ public:
+ DialogListId() = default;
+
+ explicit DialogListId(int64 dialog_list_id) : id(dialog_list_id) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ DialogListId(T dialog_list_id) = delete;
+
+ explicit DialogListId(DialogFilterId dialog_filter_id) : id(dialog_filter_id.get() + FILTER_ID_SHIFT) {
+ }
+ explicit DialogListId(FolderId folder_id) : id(folder_id.get()) {
+ }
+
+ explicit DialogListId(const td_api::object_ptr<td_api::ChatList> &chat_list) {
+ if (chat_list == nullptr) {
+ CHECK(id == FolderId::main().get());
+ return;
+ }
+ switch (chat_list->get_id()) {
+ case td_api::chatListArchive::ID:
+ id = FolderId::archive().get();
+ break;
+ case td_api::chatListMain::ID:
+ CHECK(id == FolderId::main().get());
+ break;
+ case td_api::chatListFilter::ID: {
+ DialogFilterId filter_id(static_cast<const td_api::chatListFilter *>(chat_list.get())->chat_filter_id_);
+ if (filter_id.is_valid()) {
+ *this = DialogListId(filter_id);
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+
+ td_api::object_ptr<td_api::ChatList> get_chat_list_object() const {
+ if (is_folder()) {
+ auto folder_id = get_folder_id();
+ if (folder_id == FolderId::archive()) {
+ return td_api::make_object<td_api::chatListArchive>();
+ }
+ if (folder_id == FolderId::main()) {
+ return td_api::make_object<td_api::chatListMain>();
+ }
+ return td_api::make_object<td_api::chatListMain>();
+ }
+ if (is_filter()) {
+ return td_api::make_object<td_api::chatListFilter>(get_filter_id().get());
+ }
+ UNREACHABLE();
+ return nullptr;
+ }
+
+ int64 get() const {
+ return id;
+ }
+
+ bool operator==(const DialogListId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const DialogListId &other) const {
+ return id != other.id;
+ }
+
+ bool is_folder() const {
+ return std::numeric_limits<int32>::min() <= id && id <= std::numeric_limits<int32>::max();
+ }
+
+ bool is_filter() const {
+ return std::numeric_limits<int32>::min() + FILTER_ID_SHIFT <= id &&
+ id <= std::numeric_limits<int32>::max() + FILTER_ID_SHIFT;
+ }
+
+ FolderId get_folder_id() const {
+ CHECK(is_folder());
+ return FolderId(static_cast<int32>(id));
+ }
+
+ DialogFilterId get_filter_id() const {
+ CHECK(is_filter());
+ return DialogFilterId(static_cast<int32>(id - FILTER_ID_SHIFT));
+ }
+};
+
+struct DialogListIdHash {
+ uint32 operator()(DialogListId dialog_list_id) const {
+ return Hash<int64>()(dialog_list_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, DialogListId dialog_list_id) {
+ if (dialog_list_id.is_folder()) {
+ auto folder_id = dialog_list_id.get_folder_id();
+ if (folder_id == FolderId::archive()) {
+ return string_builder << "Archive chat list";
+ }
+ if (folder_id == FolderId::main()) {
+ return string_builder << "Main chat list";
+ }
+ return string_builder << "chat list " << folder_id;
+ }
+ if (dialog_list_id.is_filter()) {
+ return string_builder << "chat list " << dialog_list_id.get_filter_id();
+ }
+ return string_builder << "chat list " << dialog_list_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogLocation.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogLocation.cpp
new file mode 100644
index 0000000000..26825faabb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogLocation.cpp
@@ -0,0 +1,63 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogLocation.h"
+
+#include "td/telegram/misc.h"
+
+namespace td {
+
+DialogLocation::DialogLocation(telegram_api::object_ptr<telegram_api::ChannelLocation> &&channel_location_ptr) {
+ if (channel_location_ptr != nullptr && channel_location_ptr->get_id() == telegram_api::channelLocation::ID) {
+ auto channel_location = static_cast<telegram_api::channelLocation *>(channel_location_ptr.get());
+ location_ = Location(channel_location->geo_point_);
+ address_ = std::move(channel_location->address_);
+ }
+}
+
+DialogLocation::DialogLocation(td_api::object_ptr<td_api::chatLocation> &&chat_location) {
+ if (chat_location != nullptr) {
+ location_ = Location(chat_location->location_);
+ address_ = std::move(chat_location->address_);
+ if (!clean_input_string(address_)) {
+ address_.clear();
+ }
+ }
+}
+
+bool DialogLocation::empty() const {
+ return location_.empty();
+}
+
+td_api::object_ptr<td_api::chatLocation> DialogLocation::get_chat_location_object() const {
+ if (empty()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatLocation>(location_.get_location_object(), address_);
+}
+
+telegram_api::object_ptr<telegram_api::InputGeoPoint> DialogLocation::get_input_geo_point() const {
+ return location_.get_input_geo_point();
+}
+
+const string &DialogLocation::get_address() const {
+ return address_;
+}
+
+bool operator==(const DialogLocation &lhs, const DialogLocation &rhs) {
+ return lhs.location_ == rhs.location_ && lhs.address_ == rhs.address_;
+}
+
+bool operator!=(const DialogLocation &lhs, const DialogLocation &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogLocation &location) {
+ return string_builder << "DialogLocation[location = " << location.location_ << ", address = " << location.address_
+ << "]";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogLocation.h b/protocols/Telegram/tdlib/td/td/telegram/DialogLocation.h
new file mode 100644
index 0000000000..9c94cd8050
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogLocation.h
@@ -0,0 +1,63 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/Location.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class DialogLocation {
+ Location location_;
+ string address_;
+
+ friend bool operator==(const DialogLocation &lhs, const DialogLocation &rhs);
+ friend bool operator!=(const DialogLocation &lhs, const DialogLocation &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogLocation &location);
+
+ public:
+ DialogLocation() = default;
+
+ explicit DialogLocation(telegram_api::object_ptr<telegram_api::ChannelLocation> &&channel_location_ptr);
+
+ explicit DialogLocation(td_api::object_ptr<td_api::chatLocation> &&chat_location);
+
+ bool empty() const;
+
+ td_api::object_ptr<td_api::chatLocation> get_chat_location_object() const;
+
+ telegram_api::object_ptr<telegram_api::InputGeoPoint> get_input_geo_point() const;
+
+ const string &get_address() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(location_, storer);
+ store(address_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(location_, parser);
+ parse(address_, parser);
+ }
+};
+
+bool operator==(const DialogLocation &lhs, const DialogLocation &rhs);
+bool operator!=(const DialogLocation &lhs, const DialogLocation &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogLocation &location);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.cpp
new file mode 100644
index 0000000000..fc1d6d398e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.cpp
@@ -0,0 +1,102 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogNotificationSettings.h"
+
+#include "td/telegram/Global.h"
+
+#include <limits>
+
+namespace td {
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogNotificationSettings &notification_settings) {
+ return string_builder << "[" << notification_settings.mute_until << ", " << notification_settings.sound << ", "
+ << notification_settings.show_preview << ", " << notification_settings.silent_send_message
+ << ", " << notification_settings.disable_pinned_message_notifications << ", "
+ << notification_settings.disable_mention_notifications << ", "
+ << notification_settings.use_default_mute_until << ", "
+ << notification_settings.use_default_show_preview << ", "
+ << notification_settings.use_default_disable_pinned_message_notifications << ", "
+ << notification_settings.use_default_disable_mention_notifications << ", "
+ << notification_settings.is_synchronized << "]";
+}
+
+td_api::object_ptr<td_api::chatNotificationSettings> get_chat_notification_settings_object(
+ const DialogNotificationSettings *notification_settings) {
+ CHECK(notification_settings != nullptr);
+ return td_api::make_object<td_api::chatNotificationSettings>(
+ notification_settings->use_default_mute_until, max(0, notification_settings->mute_until - G()->unix_time()),
+ is_notification_sound_default(notification_settings->sound),
+ get_notification_sound_ringtone_id(notification_settings->sound), notification_settings->use_default_show_preview,
+ notification_settings->show_preview, notification_settings->use_default_disable_pinned_message_notifications,
+ notification_settings->disable_pinned_message_notifications,
+ notification_settings->use_default_disable_mention_notifications,
+ notification_settings->disable_mention_notifications);
+}
+
+static int32 get_mute_until(int32 mute_for) {
+ if (mute_for <= 0) {
+ return 0;
+ }
+
+ const int32 MAX_PRECISE_MUTE_FOR = 366 * 86400;
+ int32 current_time = G()->unix_time();
+ if (mute_for > MAX_PRECISE_MUTE_FOR || mute_for >= std::numeric_limits<int32>::max() - current_time) {
+ return std::numeric_limits<int32>::max();
+ }
+ return mute_for + current_time;
+}
+
+Result<DialogNotificationSettings> get_dialog_notification_settings(
+ td_api::object_ptr<td_api::chatNotificationSettings> &&notification_settings, bool old_silent_send_message) {
+ if (notification_settings == nullptr) {
+ return Status::Error(400, "New notification settings must be non-empty");
+ }
+
+ int32 mute_until =
+ notification_settings->use_default_mute_for_ ? 0 : get_mute_until(notification_settings->mute_for_);
+ return DialogNotificationSettings(
+ notification_settings->use_default_mute_for_, mute_until,
+ get_notification_sound(notification_settings->use_default_sound_, notification_settings->sound_id_),
+ notification_settings->use_default_show_preview_, notification_settings->show_preview_, old_silent_send_message,
+ notification_settings->use_default_disable_pinned_message_notifications_,
+ notification_settings->disable_pinned_message_notifications_,
+ notification_settings->use_default_disable_mention_notifications_,
+ notification_settings->disable_mention_notifications_);
+}
+
+DialogNotificationSettings get_dialog_notification_settings(tl_object_ptr<telegram_api::peerNotifySettings> &&settings,
+ bool old_use_default_disable_pinned_message_notifications,
+ bool old_disable_pinned_message_notifications,
+ bool old_use_default_disable_mention_notifications,
+ bool old_disable_mention_notifications) {
+ if (settings == nullptr) {
+ return DialogNotificationSettings();
+ }
+ bool use_default_mute_until = (settings->flags_ & telegram_api::peerNotifySettings::MUTE_UNTIL_MASK) == 0;
+ bool use_default_show_preview = (settings->flags_ & telegram_api::peerNotifySettings::SHOW_PREVIEWS_MASK) == 0;
+ auto mute_until = use_default_mute_until || settings->mute_until_ <= G()->unix_time() ? 0 : settings->mute_until_;
+ bool silent_send_message =
+ (settings->flags_ & telegram_api::peerNotifySettings::SILENT_MASK) == 0 ? false : settings->silent_;
+ return {use_default_mute_until,
+ mute_until,
+ get_notification_sound(settings.get()),
+ use_default_show_preview,
+ settings->show_previews_,
+ silent_send_message,
+ old_use_default_disable_pinned_message_notifications,
+ old_disable_pinned_message_notifications,
+ old_use_default_disable_mention_notifications,
+ old_disable_mention_notifications};
+}
+
+bool are_default_dialog_notification_settings(const DialogNotificationSettings &settings, bool compare_sound) {
+ return settings.use_default_mute_until && (!compare_sound || is_notification_sound_default(settings.sound)) &&
+ settings.use_default_show_preview && settings.use_default_disable_pinned_message_notifications &&
+ settings.use_default_disable_mention_notifications;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.h b/protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.h
new file mode 100644
index 0000000000..3222f2a8aa
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.h
@@ -0,0 +1,74 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/NotificationSound.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class DialogNotificationSettings {
+ public:
+ int32 mute_until = 0;
+ unique_ptr<NotificationSound> sound;
+ bool show_preview = true;
+ bool silent_send_message = false;
+ bool use_default_mute_until = true;
+ bool use_default_show_preview = true;
+ bool is_use_default_fixed = true;
+ bool is_secret_chat_show_preview_fixed = false;
+ bool is_synchronized = false;
+
+ // local settings
+ bool use_default_disable_pinned_message_notifications = true;
+ bool disable_pinned_message_notifications = false;
+ bool use_default_disable_mention_notifications = true;
+ bool disable_mention_notifications = false;
+
+ DialogNotificationSettings() = default;
+
+ DialogNotificationSettings(bool use_default_mute_until, int32 mute_until, unique_ptr<NotificationSound> &&sound,
+ bool use_default_show_preview, bool show_preview, bool silent_send_message,
+ bool use_default_disable_pinned_message_notifications,
+ bool disable_pinned_message_notifications, bool use_default_disable_mention_notifications,
+ bool disable_mention_notifications)
+ : mute_until(mute_until)
+ , sound(std::move(sound))
+ , show_preview(show_preview)
+ , silent_send_message(silent_send_message)
+ , use_default_mute_until(use_default_mute_until)
+ , use_default_show_preview(use_default_show_preview)
+ , is_synchronized(true)
+ , use_default_disable_pinned_message_notifications(use_default_disable_pinned_message_notifications)
+ , disable_pinned_message_notifications(disable_pinned_message_notifications)
+ , use_default_disable_mention_notifications(use_default_disable_mention_notifications)
+ , disable_mention_notifications(disable_mention_notifications) {
+ }
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogNotificationSettings &notification_settings);
+
+td_api::object_ptr<td_api::chatNotificationSettings> get_chat_notification_settings_object(
+ const DialogNotificationSettings *notification_settings);
+
+Result<DialogNotificationSettings> get_dialog_notification_settings(
+ td_api::object_ptr<td_api::chatNotificationSettings> &&notification_settings, bool old_silent_send_message);
+
+DialogNotificationSettings get_dialog_notification_settings(tl_object_ptr<telegram_api::peerNotifySettings> &&settings,
+ bool old_use_default_disable_pinned_message_notifications,
+ bool old_disable_pinned_message_notifications,
+ bool old_use_default_disable_mention_notifications,
+ bool old_disable_mention_notifications);
+
+bool are_default_dialog_notification_settings(const DialogNotificationSettings &settings, bool compare_sound);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.hpp b/protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.hpp
new file mode 100644
index 0000000000..23be246f50
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogNotificationSettings.hpp
@@ -0,0 +1,89 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogNotificationSettings.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/NotificationSound.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const DialogNotificationSettings &notification_settings, StorerT &storer) {
+ bool is_muted = !notification_settings.use_default_mute_until && notification_settings.mute_until != 0 &&
+ notification_settings.mute_until > G()->unix_time();
+ bool has_sound = notification_settings.sound != nullptr;
+ bool has_ringtone_support = true;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_muted);
+ STORE_FLAG(has_sound);
+ STORE_FLAG(notification_settings.show_preview);
+ STORE_FLAG(notification_settings.silent_send_message);
+ STORE_FLAG(notification_settings.is_synchronized);
+ STORE_FLAG(notification_settings.use_default_mute_until);
+ STORE_FLAG(false); // use_default_sound
+ STORE_FLAG(notification_settings.use_default_show_preview);
+ STORE_FLAG(notification_settings.is_use_default_fixed);
+ STORE_FLAG(!notification_settings.use_default_disable_pinned_message_notifications);
+ STORE_FLAG(notification_settings.disable_pinned_message_notifications);
+ STORE_FLAG(!notification_settings.use_default_disable_mention_notifications);
+ STORE_FLAG(notification_settings.disable_mention_notifications);
+ STORE_FLAG(notification_settings.is_secret_chat_show_preview_fixed);
+ STORE_FLAG(has_ringtone_support);
+ END_STORE_FLAGS();
+ if (is_muted) {
+ store(notification_settings.mute_until, storer);
+ }
+ if (has_sound) {
+ store(notification_settings.sound, storer);
+ }
+}
+
+template <class ParserT>
+void parse(DialogNotificationSettings &notification_settings, ParserT &parser) {
+ bool is_muted;
+ bool has_sound;
+ bool use_default_sound;
+ bool use_disable_pinned_message_notifications;
+ bool use_disable_mention_notifications;
+ bool has_ringtone_support;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_muted);
+ PARSE_FLAG(has_sound);
+ PARSE_FLAG(notification_settings.show_preview);
+ PARSE_FLAG(notification_settings.silent_send_message);
+ PARSE_FLAG(notification_settings.is_synchronized);
+ PARSE_FLAG(notification_settings.use_default_mute_until);
+ PARSE_FLAG(use_default_sound);
+ PARSE_FLAG(notification_settings.use_default_show_preview);
+ PARSE_FLAG(notification_settings.is_use_default_fixed);
+ PARSE_FLAG(use_disable_pinned_message_notifications);
+ PARSE_FLAG(notification_settings.disable_pinned_message_notifications);
+ PARSE_FLAG(use_disable_mention_notifications);
+ PARSE_FLAG(notification_settings.disable_mention_notifications);
+ PARSE_FLAG(notification_settings.is_secret_chat_show_preview_fixed);
+ PARSE_FLAG(has_ringtone_support);
+ END_PARSE_FLAGS();
+ notification_settings.use_default_disable_pinned_message_notifications = !use_disable_pinned_message_notifications;
+ notification_settings.use_default_disable_mention_notifications = !use_disable_mention_notifications;
+ if (is_muted) {
+ parse(notification_settings.mute_until, parser);
+ }
+ if (has_sound) {
+ if (has_ringtone_support) {
+ parse_notification_sound(notification_settings.sound, parser);
+ } else {
+ string sound;
+ parse(sound, parser);
+ notification_settings.sound = use_default_sound ? nullptr : get_legacy_notification_sound(sound);
+ }
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.cpp
index 556d4bde23..0c9d9088bd 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.cpp
@@ -1,12 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/ContactsManager.h"
#include "td/telegram/Global.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/Td.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
@@ -15,177 +18,497 @@
namespace td {
-int32 DialogParticipantStatus::fix_until_date(int32 date) {
- if (date == std::numeric_limits<int32>::max() || date < 0) {
- return 0;
+AdministratorRights::AdministratorRights(const tl_object_ptr<telegram_api::chatAdminRights> &rights,
+ ChannelType channel_type) {
+ if (rights == nullptr) {
+ flags_ = 0;
+ return;
}
- return date;
-}
-DialogParticipantStatus DialogParticipantStatus::Creator(bool is_member) {
- return DialogParticipantStatus(Type::Creator,
- ALL_ADMINISTRATOR_RIGHTS | ALL_RESTRICTED_RIGHTS | (is_member ? IS_MEMBER : 0), 0);
-}
-
-DialogParticipantStatus DialogParticipantStatus::Administrator(bool can_be_edited, bool can_change_info,
- bool can_post_messages, bool can_edit_messages,
- bool can_delete_messages, bool can_invite_users,
- bool can_export_dialog_invite_link,
- bool can_restrict_members, bool can_pin_messages,
- bool can_promote_members) {
- uint32 flags = (static_cast<uint32>(can_be_edited) * CAN_BE_EDITED) |
- (static_cast<uint32>(can_change_info) * CAN_CHANGE_INFO_AND_SETTINGS) |
- (static_cast<uint32>(can_post_messages) * CAN_POST_MESSAGES) |
- (static_cast<uint32>(can_edit_messages) * CAN_EDIT_MESSAGES) |
- (static_cast<uint32>(can_delete_messages) * CAN_DELETE_MESSAGES) |
- (static_cast<uint32>(can_invite_users) * CAN_INVITE_USERS) |
- (static_cast<uint32>(can_export_dialog_invite_link) * CAN_EXPORT_DIALOG_INVITE_LINK) |
- (static_cast<uint32>(can_restrict_members) * CAN_RESTRICT_MEMBERS) |
- (static_cast<uint32>(can_pin_messages) * CAN_PIN_MESSAGES) |
- (static_cast<uint32>(can_promote_members) * CAN_PROMOTE_MEMBERS);
- if (flags == 0 || flags == CAN_BE_EDITED) {
- return Member();
+ if (!rights->other_) {
+ LOG(ERROR) << "Receive wrong other flag in " << to_string(rights);
}
- return DialogParticipantStatus(Type::Administrator, IS_MEMBER | ALL_RESTRICTED_RIGHTS | flags, 0);
+ *this = AdministratorRights(rights->anonymous_, rights->other_, rights->change_info_, rights->post_messages_,
+ rights->edit_messages_, rights->delete_messages_, rights->invite_users_,
+ rights->ban_users_, rights->pin_messages_, rights->manage_topics_, rights->add_admins_,
+ rights->manage_call_, channel_type);
}
-DialogParticipantStatus DialogParticipantStatus::Member() {
- return DialogParticipantStatus(Type::Member, IS_MEMBER | ALL_RESTRICTED_RIGHTS, 0);
-}
-
-DialogParticipantStatus DialogParticipantStatus::Restricted(bool is_member, int32 restricted_until_date,
- bool can_send_messages, bool can_send_media,
- bool can_send_stickers, bool can_send_animations,
- bool can_send_games, bool can_use_inline_bots,
- bool can_add_web_page_previews) {
- uint32 flags = (static_cast<uint32>(can_send_messages) * CAN_SEND_MESSAGES) |
- (static_cast<uint32>(can_send_media) * CAN_SEND_MEDIA) |
- (static_cast<uint32>(can_send_stickers) * CAN_SEND_STICKERS) |
- (static_cast<uint32>(can_send_animations) * CAN_SEND_ANIMATIONS) |
- (static_cast<uint32>(can_send_games) * CAN_SEND_GAMES) |
- (static_cast<uint32>(can_use_inline_bots) * CAN_USE_INLINE_BOTS) |
- (static_cast<uint32>(can_add_web_page_previews) * CAN_ADD_WEB_PAGE_PREVIEWS) |
- (static_cast<uint32>(is_member) * IS_MEMBER);
- if (flags == (IS_MEMBER | ALL_RESTRICTED_RIGHTS)) {
- return Member();
+AdministratorRights::AdministratorRights(const td_api::object_ptr<td_api::chatAdministratorRights> &rights,
+ ChannelType channel_type) {
+ if (rights == nullptr) {
+ flags_ = 0;
+ return;
}
- return DialogParticipantStatus(Type::Restricted, flags, fix_until_date(restricted_until_date));
+ *this = AdministratorRights(rights->is_anonymous_, rights->can_manage_chat_, rights->can_change_info_,
+ rights->can_post_messages_, rights->can_edit_messages_, rights->can_delete_messages_,
+ rights->can_invite_users_, rights->can_restrict_members_, rights->can_pin_messages_,
+ rights->can_manage_topics_, rights->can_promote_members_, rights->can_manage_video_chats_,
+ channel_type);
}
-DialogParticipantStatus DialogParticipantStatus::Left() {
- return DialogParticipantStatus(Type::Left, ALL_RESTRICTED_RIGHTS, 0);
-}
-
-DialogParticipantStatus DialogParticipantStatus::Banned(int32 banned_until_date) {
- return DialogParticipantStatus(Type::Banned, 0, fix_until_date(banned_until_date));
-}
-
-DialogParticipantStatus DialogParticipantStatus::GroupAdministrator(bool is_creator) {
- return DialogParticipantStatus::Administrator(is_creator, true, false, false, true, true, false, true, false, false);
-}
-
-DialogParticipantStatus DialogParticipantStatus::ChannelAdministrator(bool is_creator, bool is_megagroup) {
- if (is_megagroup) {
- return DialogParticipantStatus::Administrator(is_creator, true, false, false, true, true, false, true, true, false);
- } else {
- return DialogParticipantStatus::Administrator(is_creator, false, true, true, true, false, false, true, false,
- false);
+AdministratorRights::AdministratorRights(bool is_anonymous, bool can_manage_dialog, bool can_change_info,
+ bool can_post_messages, bool can_edit_messages, bool can_delete_messages,
+ bool can_invite_users, bool can_restrict_members, bool can_pin_messages,
+ bool can_manage_topics, bool can_promote_members, bool can_manage_calls,
+ ChannelType channel_type) {
+ switch (channel_type) {
+ case ChannelType::Broadcast:
+ can_pin_messages = false;
+ can_manage_topics = false;
+ is_anonymous = false;
+ break;
+ case ChannelType::Megagroup:
+ can_post_messages = false;
+ can_edit_messages = false;
+ break;
+ case ChannelType::Unknown:
+ break;
}
-}
-
-tl_object_ptr<td_api::ChatMemberStatus> DialogParticipantStatus::get_chat_member_status_object() const {
- switch (type_) {
- case Type::Creator:
- return make_tl_object<td_api::chatMemberStatusCreator>(is_member());
- case Type::Administrator:
- return make_tl_object<td_api::chatMemberStatusAdministrator>(
- can_be_edited(), can_change_info_and_settings(), can_post_messages(), can_edit_messages(),
- can_delete_messages(), can_invite_users() || can_export_dialog_invite_link(), can_restrict_members(),
- can_pin_messages(), can_promote_members());
- case Type::Member:
- return make_tl_object<td_api::chatMemberStatusMember>();
- case Type::Restricted:
- return make_tl_object<td_api::chatMemberStatusRestricted>(
- is_member(), until_date_, can_send_messages(), can_send_media(),
- can_send_stickers() && can_send_animations() && can_send_games() && can_use_inline_bots(),
- can_add_web_page_previews());
- case Type::Left:
- return make_tl_object<td_api::chatMemberStatusLeft>();
- case Type::Banned:
- return make_tl_object<td_api::chatMemberStatusBanned>(until_date_);
- default:
- UNREACHABLE();
- return nullptr;
+ flags_ = (static_cast<uint32>(can_manage_dialog) * CAN_MANAGE_DIALOG) |
+ (static_cast<uint32>(can_change_info) * CAN_CHANGE_INFO_AND_SETTINGS) |
+ (static_cast<uint32>(can_post_messages) * CAN_POST_MESSAGES) |
+ (static_cast<uint32>(can_edit_messages) * CAN_EDIT_MESSAGES) |
+ (static_cast<uint32>(can_delete_messages) * CAN_DELETE_MESSAGES) |
+ (static_cast<uint32>(can_invite_users) * CAN_INVITE_USERS) |
+ (static_cast<uint32>(can_restrict_members) * CAN_RESTRICT_MEMBERS) |
+ (static_cast<uint32>(can_pin_messages) * CAN_PIN_MESSAGES) |
+ (static_cast<uint32>(can_manage_topics) * CAN_MANAGE_TOPICS) |
+ (static_cast<uint32>(can_promote_members) * CAN_PROMOTE_MEMBERS) |
+ (static_cast<uint32>(can_manage_calls) * CAN_MANAGE_CALLS) |
+ (static_cast<uint32>(is_anonymous) * IS_ANONYMOUS);
+ if (flags_ != 0) {
+ flags_ |= CAN_MANAGE_DIALOG;
+ if (channel_type == ChannelType::Broadcast) {
+ flags_ |= CAN_RESTRICT_MEMBERS;
+ }
}
}
-tl_object_ptr<telegram_api::channelAdminRights> DialogParticipantStatus::get_channel_admin_rights() const {
+telegram_api::object_ptr<telegram_api::chatAdminRights> AdministratorRights::get_chat_admin_rights() const {
int32 flags = 0;
if (can_change_info_and_settings()) {
- flags |= telegram_api::channelAdminRights::CHANGE_INFO_MASK;
+ flags |= telegram_api::chatAdminRights::CHANGE_INFO_MASK;
}
if (can_post_messages()) {
- flags |= telegram_api::channelAdminRights::POST_MESSAGES_MASK;
+ flags |= telegram_api::chatAdminRights::POST_MESSAGES_MASK;
}
if (can_edit_messages()) {
- flags |= telegram_api::channelAdminRights::EDIT_MESSAGES_MASK;
+ flags |= telegram_api::chatAdminRights::EDIT_MESSAGES_MASK;
}
if (can_delete_messages()) {
- flags |= telegram_api::channelAdminRights::DELETE_MESSAGES_MASK;
+ flags |= telegram_api::chatAdminRights::DELETE_MESSAGES_MASK;
}
if (can_invite_users()) {
- flags |= telegram_api::channelAdminRights::INVITE_USERS_MASK;
- }
- if (can_export_dialog_invite_link()) {
- flags |= telegram_api::channelAdminRights::INVITE_LINK_MASK;
+ flags |= telegram_api::chatAdminRights::INVITE_USERS_MASK;
}
if (can_restrict_members()) {
- flags |= telegram_api::channelAdminRights::BAN_USERS_MASK;
+ flags |= telegram_api::chatAdminRights::BAN_USERS_MASK;
}
if (can_pin_messages()) {
- flags |= telegram_api::channelAdminRights::PIN_MESSAGES_MASK;
+ flags |= telegram_api::chatAdminRights::PIN_MESSAGES_MASK;
+ }
+ if (can_manage_topics()) {
+ flags |= telegram_api::chatAdminRights::MANAGE_TOPICS_MASK;
}
if (can_promote_members()) {
- flags |= telegram_api::channelAdminRights::ADD_ADMINS_MASK;
+ flags |= telegram_api::chatAdminRights::ADD_ADMINS_MASK;
+ }
+ if (can_manage_calls()) {
+ flags |= telegram_api::chatAdminRights::MANAGE_CALL_MASK;
+ }
+ if (can_manage_dialog()) {
+ flags |= telegram_api::chatAdminRights::OTHER_MASK;
+ }
+ if (is_anonymous()) {
+ flags |= telegram_api::chatAdminRights::ANONYMOUS_MASK;
}
- LOG(INFO) << "Create channel admin rights " << flags;
- return make_tl_object<telegram_api::channelAdminRights>(
+ return telegram_api::make_object<telegram_api::chatAdminRights>(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/);
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/);
}
-tl_object_ptr<telegram_api::channelBannedRights> DialogParticipantStatus::get_channel_banned_rights() const {
- int32 flags = 0;
- if (type_ == Type::Banned) {
- flags |= telegram_api::channelBannedRights::VIEW_MESSAGES_MASK;
+td_api::object_ptr<td_api::chatAdministratorRights> AdministratorRights::get_chat_administrator_rights_object() const {
+ return td_api::make_object<td_api::chatAdministratorRights>(
+ can_manage_dialog(), can_change_info_and_settings(), can_post_messages(), can_edit_messages(),
+ can_delete_messages(), can_invite_users(), can_restrict_members(), can_pin_messages(), can_manage_topics(),
+ can_promote_members(), can_manage_calls(), is_anonymous());
+}
+
+bool operator==(const AdministratorRights &lhs, const AdministratorRights &rhs) {
+ return lhs.flags_ == rhs.flags_;
+}
+
+bool operator!=(const AdministratorRights &lhs, const AdministratorRights &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const AdministratorRights &status) {
+ string_builder << "Administrator: ";
+ if (status.can_manage_dialog()) {
+ string_builder << "(manage)";
+ }
+ if (status.can_change_info_and_settings()) {
+ string_builder << "(change)";
+ }
+ if (status.can_post_messages()) {
+ string_builder << "(post)";
+ }
+ if (status.can_edit_messages()) {
+ string_builder << "(edit)";
+ }
+ if (status.can_delete_messages()) {
+ string_builder << "(delete)";
+ }
+ if (status.can_invite_users()) {
+ string_builder << "(invite)";
+ }
+ if (status.can_restrict_members()) {
+ string_builder << "(restrict)";
+ }
+ if (status.can_pin_messages()) {
+ string_builder << "(pin)";
+ }
+ if (status.can_manage_topics()) {
+ string_builder << "(manage_topics)";
}
+ if (status.can_promote_members()) {
+ string_builder << "(promote)";
+ }
+ if (status.can_manage_calls()) {
+ string_builder << "(voice chat)";
+ }
+ if (status.is_anonymous()) {
+ string_builder << "(anonymous)";
+ }
+ return string_builder;
+}
+
+RestrictedRights::RestrictedRights(const tl_object_ptr<telegram_api::chatBannedRights> &rights) {
+ if (rights == nullptr) {
+ flags_ = 0;
+ return;
+ }
+ if (rights->view_messages_) {
+ LOG(ERROR) << "Can't view messages in banned rights " << to_string(rights);
+ }
+ LOG_IF(ERROR, rights->until_date_ != std::numeric_limits<int32>::max())
+ << "Have until date " << rights->until_date_ << " in restricted rights";
+
+ *this =
+ RestrictedRights(!rights->send_messages_, !rights->send_media_, !rights->send_stickers_, !rights->send_gifs_,
+ !rights->send_games_, !rights->send_inline_, !rights->embed_links_, !rights->send_polls_,
+ !rights->change_info_, !rights->invite_users_, !rights->pin_messages_, !rights->manage_topics_);
+}
+
+RestrictedRights::RestrictedRights(const td_api::object_ptr<td_api::chatPermissions> &rights) {
+ if (rights == nullptr) {
+ flags_ = 0;
+ return;
+ }
+
+ bool can_send_polls = rights->can_send_polls_;
+ bool can_send_media = rights->can_send_media_messages_;
+ bool can_send_messages = rights->can_send_messages_ || can_send_media || can_send_polls ||
+ rights->can_send_other_messages_ || rights->can_add_web_page_previews_;
+ *this = RestrictedRights(can_send_messages, can_send_media, rights->can_send_other_messages_,
+ rights->can_send_other_messages_, rights->can_send_other_messages_,
+ rights->can_send_other_messages_, rights->can_add_web_page_previews_, can_send_polls,
+ rights->can_change_info_, rights->can_invite_users_, rights->can_pin_messages_,
+ rights->can_manage_topics_);
+}
+
+RestrictedRights::RestrictedRights(bool can_send_messages, bool can_send_media, bool can_send_stickers,
+ bool can_send_animations, bool can_send_games, bool can_use_inline_bots,
+ bool can_add_web_page_previews, bool can_send_polls,
+ bool can_change_info_and_settings, bool can_invite_users, bool can_pin_messages,
+ bool can_manage_topics) {
+ flags_ = (static_cast<uint32>(can_send_messages) * CAN_SEND_MESSAGES) |
+ (static_cast<uint32>(can_send_media) * CAN_SEND_MEDIA) |
+ (static_cast<uint32>(can_send_stickers) * CAN_SEND_STICKERS) |
+ (static_cast<uint32>(can_send_animations) * CAN_SEND_ANIMATIONS) |
+ (static_cast<uint32>(can_send_games) * CAN_SEND_GAMES) |
+ (static_cast<uint32>(can_use_inline_bots) * CAN_USE_INLINE_BOTS) |
+ (static_cast<uint32>(can_add_web_page_previews) * CAN_ADD_WEB_PAGE_PREVIEWS) |
+ (static_cast<uint32>(can_send_polls) * CAN_SEND_POLLS) |
+ (static_cast<uint32>(can_change_info_and_settings) * CAN_CHANGE_INFO_AND_SETTINGS) |
+ (static_cast<uint32>(can_invite_users) * CAN_INVITE_USERS) |
+ (static_cast<uint32>(can_pin_messages) * CAN_PIN_MESSAGES) |
+ (static_cast<uint32>(can_manage_topics) * CAN_MANAGE_TOPICS);
+}
+
+td_api::object_ptr<td_api::chatPermissions> RestrictedRights::get_chat_permissions_object() const {
+ return td_api::make_object<td_api::chatPermissions>(
+ can_send_messages(), can_send_media(), can_send_polls(),
+ can_send_stickers() || can_send_animations() || can_send_games() || can_use_inline_bots(),
+ can_add_web_page_previews(), can_change_info_and_settings(), can_invite_users(), can_pin_messages(),
+ can_manage_topics());
+}
+
+tl_object_ptr<telegram_api::chatBannedRights> RestrictedRights::get_chat_banned_rights() const {
+ int32 flags = 0;
if (!can_send_messages()) {
- flags |= telegram_api::channelBannedRights::SEND_MESSAGES_MASK;
+ flags |= telegram_api::chatBannedRights::SEND_MESSAGES_MASK;
}
if (!can_send_media()) {
- flags |= telegram_api::channelBannedRights::SEND_MEDIA_MASK;
+ flags |= telegram_api::chatBannedRights::SEND_MEDIA_MASK;
}
if (!can_send_stickers()) {
- flags |= telegram_api::channelBannedRights::SEND_STICKERS_MASK;
+ flags |= telegram_api::chatBannedRights::SEND_STICKERS_MASK;
}
if (!can_send_animations()) {
- flags |= telegram_api::channelBannedRights::SEND_GIFS_MASK;
+ flags |= telegram_api::chatBannedRights::SEND_GIFS_MASK;
}
if (!can_send_games()) {
- flags |= telegram_api::channelBannedRights::SEND_GAMES_MASK;
+ flags |= telegram_api::chatBannedRights::SEND_GAMES_MASK;
}
if (!can_use_inline_bots()) {
- flags |= telegram_api::channelBannedRights::SEND_INLINE_MASK;
+ flags |= telegram_api::chatBannedRights::SEND_INLINE_MASK;
}
if (!can_add_web_page_previews()) {
- flags |= telegram_api::channelBannedRights::EMBED_LINKS_MASK;
+ flags |= telegram_api::chatBannedRights::EMBED_LINKS_MASK;
+ }
+ if (!can_send_polls()) {
+ flags |= telegram_api::chatBannedRights::SEND_POLLS_MASK;
+ }
+ if (!can_change_info_and_settings()) {
+ flags |= telegram_api::chatBannedRights::CHANGE_INFO_MASK;
+ }
+ if (!can_invite_users()) {
+ flags |= telegram_api::chatBannedRights::INVITE_USERS_MASK;
+ }
+ if (!can_pin_messages()) {
+ flags |= telegram_api::chatBannedRights::PIN_MESSAGES_MASK;
+ }
+ if (!can_manage_topics()) {
+ flags |= telegram_api::chatBannedRights::MANAGE_TOPICS_MASK;
}
- LOG(INFO) << "Create channel banned rights " << flags << " until " << until_date_;
- return make_tl_object<telegram_api::channelBannedRights>(
+ LOG(INFO) << "Create chat banned rights " << flags;
+ return make_tl_object<telegram_api::chatBannedRights>(
flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- false /*ignored*/, false /*ignored*/, false /*ignored*/, until_date_);
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, 0);
+}
+
+bool operator==(const RestrictedRights &lhs, const RestrictedRights &rhs) {
+ return lhs.flags_ == rhs.flags_;
+}
+
+bool operator!=(const RestrictedRights &lhs, const RestrictedRights &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const RestrictedRights &status) {
+ string_builder << "Restricted: ";
+ if (!status.can_send_messages()) {
+ string_builder << "(text)";
+ }
+ if (!status.can_send_media()) {
+ string_builder << "(media)";
+ }
+ if (!status.can_send_stickers()) {
+ string_builder << "(stickers)";
+ }
+ if (!status.can_send_animations()) {
+ string_builder << "(animations)";
+ }
+ if (!status.can_send_games()) {
+ string_builder << "(games)";
+ }
+ if (!status.can_send_polls()) {
+ string_builder << "(polls)";
+ }
+ if (!status.can_use_inline_bots()) {
+ string_builder << "(inline bots)";
+ }
+ if (!status.can_add_web_page_previews()) {
+ string_builder << "(links)";
+ }
+ if (!status.can_change_info_and_settings()) {
+ string_builder << "(change)";
+ }
+ if (!status.can_invite_users()) {
+ string_builder << "(invite)";
+ }
+ if (!status.can_pin_messages()) {
+ string_builder << "(pin)";
+ }
+ if (!status.can_manage_topics()) {
+ string_builder << "(topics)";
+ }
+ return string_builder;
+}
+
+DialogParticipantStatus::DialogParticipantStatus(Type type, uint32 flags, int32 until_date, string rank)
+ : type_(type), flags_(flags), until_date_(until_date), rank_(strip_empty_characters(std::move(rank), 16)) {
+}
+
+int32 DialogParticipantStatus::fix_until_date(int32 date) {
+ if (date == std::numeric_limits<int32>::max() || date < 0) {
+ return 0;
+ }
+ return date;
+}
+
+DialogParticipantStatus DialogParticipantStatus::Creator(bool is_member, bool is_anonymous, string &&rank) {
+ return DialogParticipantStatus(Type::Creator,
+ AdministratorRights::ALL_ADMINISTRATOR_RIGHTS |
+ RestrictedRights::ALL_RESTRICTED_RIGHTS | (is_member ? IS_MEMBER : 0) |
+ (is_anonymous ? AdministratorRights::IS_ANONYMOUS : 0),
+ 0, std::move(rank));
+}
+
+DialogParticipantStatus DialogParticipantStatus::Administrator(AdministratorRights administrator_rights, string &&rank,
+ bool can_be_edited) {
+ uint32 flags = administrator_rights.flags_;
+ if (flags == 0) {
+ return Member();
+ }
+ flags = flags | (static_cast<uint32>(can_be_edited) * CAN_BE_EDITED);
+ return DialogParticipantStatus(
+ Type::Administrator,
+ IS_MEMBER | (RestrictedRights::ALL_RESTRICTED_RIGHTS & ~RestrictedRights::ALL_ADMIN_PERMISSION_RIGHTS) | flags, 0,
+ std::move(rank));
+}
+
+DialogParticipantStatus DialogParticipantStatus::Member() {
+ return DialogParticipantStatus(Type::Member, IS_MEMBER | RestrictedRights::ALL_RESTRICTED_RIGHTS, 0, string());
+}
+
+DialogParticipantStatus DialogParticipantStatus::Restricted(RestrictedRights restricted_rights, bool is_member,
+ int32 restricted_until_date) {
+ uint32 flags = restricted_rights.flags_;
+ if (flags == RestrictedRights::ALL_RESTRICTED_RIGHTS) {
+ return is_member ? Member() : Left();
+ }
+ flags |= (static_cast<uint32>(is_member) * IS_MEMBER);
+ return DialogParticipantStatus(Type::Restricted, flags, fix_until_date(restricted_until_date), string());
+}
+
+DialogParticipantStatus DialogParticipantStatus::Left() {
+ return DialogParticipantStatus(Type::Left, RestrictedRights::ALL_RESTRICTED_RIGHTS, 0, string());
+}
+
+DialogParticipantStatus DialogParticipantStatus::Banned(int32 banned_until_date) {
+ return DialogParticipantStatus(Type::Banned, 0, fix_until_date(banned_until_date), string());
+}
+
+DialogParticipantStatus DialogParticipantStatus::GroupAdministrator(bool is_creator) {
+ return Administrator(AdministratorRights(false, true, true, false, false, true, true, true, true, false, false, true,
+ ChannelType::Unknown),
+ string(), is_creator);
+}
+
+DialogParticipantStatus DialogParticipantStatus::ChannelAdministrator(bool is_creator, bool is_megagroup) {
+ auto rights = is_megagroup ? AdministratorRights(false, true, true, false, false, true, true, true, true, true, false,
+ false, ChannelType::Megagroup)
+ : AdministratorRights(false, true, false, true, true, true, false, true, false, false,
+ false, false, ChannelType::Broadcast);
+ return Administrator(rights, string(), is_creator);
+}
+
+DialogParticipantStatus::DialogParticipantStatus(bool can_be_edited,
+ tl_object_ptr<telegram_api::chatAdminRights> &&admin_rights,
+ string rank, ChannelType channel_type) {
+ CHECK(admin_rights != nullptr);
+ uint32 flags = AdministratorRights(admin_rights, channel_type).flags_ | AdministratorRights::CAN_MANAGE_DIALOG;
+ if (can_be_edited) {
+ flags |= CAN_BE_EDITED;
+ }
+ flags |= (RestrictedRights::ALL_RESTRICTED_RIGHTS & ~RestrictedRights::ALL_ADMIN_PERMISSION_RIGHTS) | IS_MEMBER;
+ *this = DialogParticipantStatus(Type::Administrator, flags, 0, std::move(rank));
+}
+
+DialogParticipantStatus::DialogParticipantStatus(bool is_member,
+ tl_object_ptr<telegram_api::chatBannedRights> &&banned_rights) {
+ CHECK(banned_rights != nullptr);
+ if (banned_rights->view_messages_) {
+ *this = DialogParticipantStatus::Banned(banned_rights->until_date_);
+ return;
+ }
+
+ auto until_date = fix_until_date(banned_rights->until_date_);
+ banned_rights->until_date_ = std::numeric_limits<int32>::max();
+ uint32 flags = RestrictedRights(banned_rights).flags_ | (static_cast<uint32>(is_member) * IS_MEMBER);
+ *this = DialogParticipantStatus(Type::Restricted, flags, until_date, string());
+}
+
+RestrictedRights DialogParticipantStatus::get_effective_restricted_rights() const {
+ return RestrictedRights(can_send_messages(), can_send_media(), can_send_stickers(), can_send_animations(),
+ can_send_games(), can_use_inline_bots(), can_add_web_page_previews(), can_send_polls(),
+ can_change_info_and_settings(), can_invite_users(), can_pin_messages(), can_create_topics());
+}
+
+tl_object_ptr<td_api::ChatMemberStatus> DialogParticipantStatus::get_chat_member_status_object() const {
+ switch (type_) {
+ case Type::Creator:
+ return td_api::make_object<td_api::chatMemberStatusCreator>(rank_, is_anonymous(), is_member());
+ case Type::Administrator:
+ return td_api::make_object<td_api::chatMemberStatusAdministrator>(
+ rank_, can_be_edited(), get_administrator_rights().get_chat_administrator_rights_object());
+ case Type::Member:
+ return td_api::make_object<td_api::chatMemberStatusMember>();
+ case Type::Restricted:
+ return td_api::make_object<td_api::chatMemberStatusRestricted>(
+ is_member(), until_date_, get_restricted_rights().get_chat_permissions_object());
+ case Type::Left:
+ return td_api::make_object<td_api::chatMemberStatusLeft>();
+ case Type::Banned:
+ return td_api::make_object<td_api::chatMemberStatusBanned>(until_date_);
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+tl_object_ptr<telegram_api::chatAdminRights> DialogParticipantStatus::get_chat_admin_rights() const {
+ return get_administrator_rights().get_chat_admin_rights();
+}
+
+tl_object_ptr<telegram_api::chatBannedRights> DialogParticipantStatus::get_chat_banned_rights() const {
+ auto result = get_restricted_rights().get_chat_banned_rights();
+ if (type_ == Type::Banned) {
+ result->flags_ |= telegram_api::chatBannedRights::VIEW_MESSAGES_MASK;
+ }
+ result->until_date_ = until_date_;
+ return result;
+}
+
+DialogParticipantStatus DialogParticipantStatus::apply_restrictions(RestrictedRights default_restrictions,
+ bool is_bot) const {
+ auto flags = flags_;
+ switch (type_) {
+ case Type::Creator:
+ // creator can do anything and isn't affected by restrictions
+ break;
+ case Type::Administrator:
+ // administrators aren't affected by restrictions, but if everyone can invite users,
+ // pin messages or change info, they also can do that
+ if (!is_bot) {
+ flags |= default_restrictions.flags_ & RestrictedRights::ALL_ADMIN_PERMISSION_RIGHTS;
+ }
+ break;
+ case Type::Member:
+ case Type::Restricted:
+ case Type::Left:
+ // members and restricted are affected by default restrictions
+ flags &= (~RestrictedRights::ALL_RESTRICTED_RIGHTS) | default_restrictions.flags_;
+ if (is_bot) {
+ flags &= ~RestrictedRights::ALL_ADMIN_PERMISSION_RIGHTS;
+ }
+ break;
+ case Type::Banned:
+ // banned can do nothing, even restrictions allows them to do that
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+
+ return DialogParticipantStatus(type_, flags, 0, string());
}
void DialogParticipantStatus::update_restrictions() const {
@@ -197,7 +520,7 @@ void DialogParticipantStatus::update_restrictions() const {
} else {
type_ = Type::Left;
}
- flags_ |= ALL_RESTRICTED_RIGHTS;
+ flags_ |= RestrictedRights::ALL_RESTRICTED_RIGHTS;
} else if (type_ == Type::Banned) {
type_ = Type::Left;
} else {
@@ -207,7 +530,8 @@ void DialogParticipantStatus::update_restrictions() const {
}
bool operator==(const DialogParticipantStatus &lhs, const DialogParticipantStatus &rhs) {
- return lhs.type_ == rhs.type_ && lhs.flags_ == rhs.flags_ && lhs.until_date_ == rhs.until_date_;
+ return lhs.type_ == rhs.type_ && lhs.flags_ == rhs.flags_ && lhs.until_date_ == rhs.until_date_ &&
+ lhs.rank_ == rhs.rank_;
}
bool operator!=(const DialogParticipantStatus &lhs, const DialogParticipantStatus &rhs) {
@@ -221,38 +545,26 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant
if (!status.is_member()) {
string_builder << "-non-member";
}
- return string_builder;
- case DialogParticipantStatus::Type::Administrator:
- string_builder << "Administrator: ";
- if (status.can_change_info_and_settings()) {
- string_builder << "(change)";
- }
- if (status.can_post_messages()) {
- string_builder << "(post)";
- }
- if (status.can_edit_messages()) {
- string_builder << "(edit)";
- }
- if (status.can_delete_messages()) {
- string_builder << "(delete)";
+ if (!status.rank_.empty()) {
+ string_builder << " [" << status.rank_ << "]";
}
- if (status.can_invite_users()) {
- string_builder << "(invite)";
+ if (status.is_anonymous()) {
+ string_builder << "-anonymous";
}
- if (status.can_restrict_members()) {
- string_builder << "(restrict)";
- }
- if (status.can_pin_messages()) {
- string_builder << "(pin)";
+ return string_builder;
+ case DialogParticipantStatus::Type::Administrator:
+ string_builder << status.get_administrator_rights();
+ if (status.can_be_edited()) {
+ string_builder << "(can_be_edited)";
}
- if (status.can_promote_members()) {
- string_builder << "(promote)";
+ if (!status.rank_.empty()) {
+ string_builder << " [" << status.rank_ << "]";
}
return string_builder;
case DialogParticipantStatus::Type::Member:
return string_builder << "Member";
case DialogParticipantStatus::Type::Restricted:
- string_builder << "Restricted ";
+ string_builder << status.get_restricted_rights();
if (status.until_date_ == 0) {
string_builder << "forever ";
} else {
@@ -261,28 +573,7 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant
if (!status.is_member()) {
string_builder << "non-";
}
- string_builder << "member: ";
- if (!status.can_send_messages()) {
- string_builder << "(text)";
- }
- if (!status.can_send_media()) {
- string_builder << "(media)";
- }
- if (!status.can_send_stickers()) {
- string_builder << "(stickers)";
- }
- if (!status.can_send_animations()) {
- string_builder << "(animations)";
- }
- if (!status.can_send_games()) {
- string_builder << "(games)";
- }
- if (!status.can_use_inline_bots()) {
- string_builder << "(inline bots)";
- }
- if (!status.can_add_web_page_previews()) {
- string_builder << "(links)";
- }
+ string_builder << "member";
return string_builder;
case DialogParticipantStatus::Type::Left:
return string_builder << "Left";
@@ -300,36 +591,53 @@ StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant
}
}
-DialogParticipantStatus get_dialog_participant_status(const tl_object_ptr<td_api::ChatMemberStatus> &status) {
+DialogParticipantStatus get_dialog_participant_status(const td_api::object_ptr<td_api::ChatMemberStatus> &status,
+ ChannelType channel_type) {
auto constructor_id = status == nullptr ? td_api::chatMemberStatusMember::ID : status->get_id();
+ auto fix_until_date = [](int32 until_date) {
+ if (until_date == 0) {
+ // fast path
+ return 0;
+ }
+
+ // if user is restricted for more than 366 days or less than 30 seconds from the current time,
+ // they are considered to be restricted forever
+ auto unix_time = G()->unix_time();
+ if (until_date < unix_time + 30 || until_date > unix_time + 366 * 86400) {
+ return 0;
+ }
+ return until_date;
+ };
switch (constructor_id) {
case td_api::chatMemberStatusCreator::ID: {
auto st = static_cast<const td_api::chatMemberStatusCreator *>(status.get());
- return DialogParticipantStatus::Creator(st->is_member_);
+ auto custom_title = st->custom_title_;
+ if (!clean_input_string(custom_title)) {
+ custom_title.clear();
+ }
+ return DialogParticipantStatus::Creator(st->is_member_, st->is_anonymous_, std::move(custom_title));
}
case td_api::chatMemberStatusAdministrator::ID: {
auto st = static_cast<const td_api::chatMemberStatusAdministrator *>(status.get());
- return DialogParticipantStatus::Administrator(
- st->can_be_edited_, st->can_change_info_, st->can_post_messages_, st->can_edit_messages_,
- st->can_delete_messages_, st->can_invite_users_, st->can_invite_users_, st->can_restrict_members_,
- st->can_pin_messages_, st->can_promote_members_);
+ auto custom_title = st->custom_title_;
+ if (!clean_input_string(custom_title)) {
+ custom_title.clear();
+ }
+ return DialogParticipantStatus::Administrator(AdministratorRights(st->rights_, channel_type),
+ std::move(custom_title), true /*st->can_be_edited_*/);
}
case td_api::chatMemberStatusMember::ID:
return DialogParticipantStatus::Member();
case td_api::chatMemberStatusRestricted::ID: {
auto st = static_cast<const td_api::chatMemberStatusRestricted *>(status.get());
- bool can_send_media =
- st->can_send_media_messages_ || st->can_send_other_messages_ || st->can_add_web_page_previews_;
- return DialogParticipantStatus::Restricted(
- st->is_member_, st->restricted_until_date_, st->can_send_messages_ || can_send_media, can_send_media,
- st->can_send_other_messages_, st->can_send_other_messages_, st->can_send_other_messages_,
- st->can_send_other_messages_, st->can_add_web_page_previews_);
+ return DialogParticipantStatus::Restricted(RestrictedRights(st->permissions_), st->is_member_,
+ fix_until_date(st->restricted_until_date_));
}
case td_api::chatMemberStatusLeft::ID:
return DialogParticipantStatus::Left();
case td_api::chatMemberStatusBanned::ID: {
auto st = static_cast<const td_api::chatMemberStatusBanned *>(status.get());
- return DialogParticipantStatus::Banned(st->banned_until_date_);
+ return DialogParticipantStatus::Banned(fix_until_date(st->banned_until_date_));
}
default:
UNREACHABLE();
@@ -337,92 +645,117 @@ DialogParticipantStatus get_dialog_participant_status(const tl_object_ptr<td_api
}
}
-DialogParticipantStatus get_dialog_participant_status(
- bool can_be_edited, const tl_object_ptr<telegram_api::channelAdminRights> &admin_rights) {
- bool can_change_info = (admin_rights->flags_ & telegram_api::channelAdminRights::CHANGE_INFO_MASK) != 0;
- bool can_post_messages = (admin_rights->flags_ & telegram_api::channelAdminRights::POST_MESSAGES_MASK) != 0;
- bool can_edit_messages = (admin_rights->flags_ & telegram_api::channelAdminRights::EDIT_MESSAGES_MASK) != 0;
- bool can_delete_messages = (admin_rights->flags_ & telegram_api::channelAdminRights::DELETE_MESSAGES_MASK) != 0;
- bool can_invite_users = (admin_rights->flags_ & telegram_api::channelAdminRights::INVITE_USERS_MASK) != 0;
- bool can_export_invite_link = (admin_rights->flags_ & telegram_api::channelAdminRights::INVITE_LINK_MASK) != 0;
- bool can_restrict_members = (admin_rights->flags_ & telegram_api::channelAdminRights::BAN_USERS_MASK) != 0;
- bool can_pin_messages = (admin_rights->flags_ & telegram_api::channelAdminRights::PIN_MESSAGES_MASK) != 0;
- bool can_promote_members = (admin_rights->flags_ & telegram_api::channelAdminRights::ADD_ADMINS_MASK) != 0;
- return DialogParticipantStatus::Administrator(can_be_edited, can_change_info, can_post_messages, can_edit_messages,
- can_delete_messages, can_invite_users, can_export_invite_link,
- can_restrict_members, can_pin_messages, can_promote_members);
-}
-
-DialogParticipantStatus get_dialog_participant_status(
- bool is_member, const tl_object_ptr<telegram_api::channelBannedRights> &banned_rights) {
- bool can_view_messages = (banned_rights->flags_ & telegram_api::channelBannedRights::VIEW_MESSAGES_MASK) == 0;
- if (!can_view_messages) {
- return DialogParticipantStatus::Banned(banned_rights->until_date_);
- }
- bool can_send_messages = (banned_rights->flags_ & telegram_api::channelBannedRights::SEND_MESSAGES_MASK) == 0;
- bool can_send_media_messages = (banned_rights->flags_ & telegram_api::channelBannedRights::SEND_MEDIA_MASK) == 0;
- bool can_send_stickers = (banned_rights->flags_ & telegram_api::channelBannedRights::SEND_STICKERS_MASK) == 0;
- bool can_send_animations = (banned_rights->flags_ & telegram_api::channelBannedRights::SEND_GIFS_MASK) == 0;
- bool can_send_games = (banned_rights->flags_ & telegram_api::channelBannedRights::SEND_GAMES_MASK) == 0;
- bool can_use_inline_bots = (banned_rights->flags_ & telegram_api::channelBannedRights::SEND_INLINE_MASK) == 0;
- bool can_add_web_page_previews = (banned_rights->flags_ & telegram_api::channelBannedRights::EMBED_LINKS_MASK) == 0;
- return DialogParticipantStatus::Restricted(is_member, banned_rights->until_date_, can_send_messages,
- can_send_media_messages, can_send_stickers, can_send_animations,
- can_send_games, can_use_inline_bots, can_add_web_page_previews);
-}
-
-tl_object_ptr<telegram_api::ChannelParticipantsFilter>
-ChannelParticipantsFilter::get_input_channel_participants_filter() const {
- switch (type) {
- case Recent:
- return make_tl_object<telegram_api::channelParticipantsRecent>();
- case Administrators:
- return make_tl_object<telegram_api::channelParticipantsAdmins>();
- case Search:
- return make_tl_object<telegram_api::channelParticipantsSearch>(query);
- case Restricted:
- return make_tl_object<telegram_api::channelParticipantsBanned>(query);
- case Banned:
- return make_tl_object<telegram_api::channelParticipantsKicked>(query);
- case Bots:
- return make_tl_object<telegram_api::channelParticipantsBots>();
+DialogParticipant::DialogParticipant(DialogId dialog_id, UserId inviter_user_id, int32 joined_date,
+ DialogParticipantStatus status)
+ : dialog_id_(dialog_id), inviter_user_id_(inviter_user_id), joined_date_(joined_date), status_(std::move(status)) {
+ if (!inviter_user_id_.is_valid() && inviter_user_id_ != UserId()) {
+ LOG(ERROR) << "Receive inviter " << inviter_user_id_;
+ inviter_user_id_ = UserId();
+ }
+ if (joined_date_ < 0) {
+ LOG(ERROR) << "Receive date " << joined_date_;
+ joined_date_ = 0;
+ }
+}
+
+DialogParticipant::DialogParticipant(tl_object_ptr<telegram_api::ChatParticipant> &&participant_ptr,
+ int32 chat_creation_date, bool is_creator) {
+ switch (participant_ptr->get_id()) {
+ case telegram_api::chatParticipant::ID: {
+ auto participant = move_tl_object_as<telegram_api::chatParticipant>(participant_ptr);
+ *this = {DialogId(UserId(participant->user_id_)), UserId(participant->inviter_id_), participant->date_,
+ DialogParticipantStatus::Member()};
+ break;
+ }
+ case telegram_api::chatParticipantCreator::ID: {
+ auto participant = move_tl_object_as<telegram_api::chatParticipantCreator>(participant_ptr);
+ *this = {DialogId(UserId(participant->user_id_)), UserId(participant->user_id_), chat_creation_date,
+ DialogParticipantStatus::Creator(true, false, string())};
+ break;
+ }
+ case telegram_api::chatParticipantAdmin::ID: {
+ auto participant = move_tl_object_as<telegram_api::chatParticipantAdmin>(participant_ptr);
+ *this = {DialogId(UserId(participant->user_id_)), UserId(participant->inviter_id_), participant->date_,
+ DialogParticipantStatus::GroupAdministrator(is_creator)};
+ break;
+ }
default:
UNREACHABLE();
- return nullptr;
}
}
-ChannelParticipantsFilter::ChannelParticipantsFilter(const tl_object_ptr<td_api::SupergroupMembersFilter> &filter) {
- if (filter == nullptr) {
- type = Recent;
- return;
- }
- switch (filter->get_id()) {
- case td_api::supergroupMembersFilterRecent::ID:
- type = Recent;
- return;
- case td_api::supergroupMembersFilterAdministrators::ID:
- type = Administrators;
- return;
- case td_api::supergroupMembersFilterSearch::ID:
- type = Search;
- query = static_cast<const td_api::supergroupMembersFilterSearch *>(filter.get())->query_;
- return;
- case td_api::supergroupMembersFilterRestricted::ID:
- type = Restricted;
- query = static_cast<const td_api::supergroupMembersFilterRestricted *>(filter.get())->query_;
- return;
- case td_api::supergroupMembersFilterBanned::ID:
- type = Banned;
- query = static_cast<const td_api::supergroupMembersFilterBanned *>(filter.get())->query_;
- return;
- case td_api::supergroupMembersFilterBots::ID:
- type = Bots;
- return;
+DialogParticipant::DialogParticipant(tl_object_ptr<telegram_api::ChannelParticipant> &&participant_ptr,
+ ChannelType channel_type) {
+ CHECK(participant_ptr != nullptr);
+
+ switch (participant_ptr->get_id()) {
+ case telegram_api::channelParticipant::ID: {
+ auto participant = move_tl_object_as<telegram_api::channelParticipant>(participant_ptr);
+ *this = {DialogId(UserId(participant->user_id_)), UserId(), participant->date_,
+ DialogParticipantStatus::Member()};
+ break;
+ }
+ case telegram_api::channelParticipantSelf::ID: {
+ auto participant = move_tl_object_as<telegram_api::channelParticipantSelf>(participant_ptr);
+ *this = {DialogId(UserId(participant->user_id_)), UserId(participant->inviter_id_), participant->date_,
+ DialogParticipantStatus::Member()};
+ break;
+ }
+ case telegram_api::channelParticipantCreator::ID: {
+ auto participant = move_tl_object_as<telegram_api::channelParticipantCreator>(participant_ptr);
+ *this = {DialogId(UserId(participant->user_id_)), UserId(), 0,
+ DialogParticipantStatus::Creator(true, participant->admin_rights_->anonymous_,
+ std::move(participant->rank_))};
+ break;
+ }
+ case telegram_api::channelParticipantAdmin::ID: {
+ auto participant = move_tl_object_as<telegram_api::channelParticipantAdmin>(participant_ptr);
+ *this = {DialogId(UserId(participant->user_id_)), UserId(participant->promoted_by_), participant->date_,
+ DialogParticipantStatus(participant->can_edit_, std::move(participant->admin_rights_),
+ std::move(participant->rank_), channel_type)};
+ break;
+ }
+ case telegram_api::channelParticipantLeft::ID: {
+ auto participant = move_tl_object_as<telegram_api::channelParticipantLeft>(participant_ptr);
+ *this = {DialogId(participant->peer_), UserId(), 0, DialogParticipantStatus::Left()};
+ break;
+ }
+ case telegram_api::channelParticipantBanned::ID: {
+ auto participant = move_tl_object_as<telegram_api::channelParticipantBanned>(participant_ptr);
+ *this = {DialogId(participant->peer_), UserId(participant->kicked_by_), participant->date_,
+ DialogParticipantStatus(!participant->left_, std::move(participant->banned_rights_))};
+ break;
+ }
default:
UNREACHABLE();
- type = Recent;
+ break;
+ }
+}
+
+bool DialogParticipant::is_valid() const {
+ if (!dialog_id_.is_valid() || joined_date_ < 0) {
+ return false;
}
+ if (status_.is_restricted() || status_.is_banned() || (status_.is_administrator() && !status_.is_creator())) {
+ return inviter_user_id_.is_valid();
+ }
+ return true;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant &dialog_participant) {
+ return string_builder << '[' << dialog_participant.dialog_id_ << " invited by " << dialog_participant.inviter_user_id_
+ << " at " << dialog_participant.joined_date_ << " with status " << dialog_participant.status_
+ << ']';
+}
+
+td_api::object_ptr<td_api::chatMembers> DialogParticipants::get_chat_members_object(Td *td) const {
+ vector<tl_object_ptr<td_api::chatMember>> chat_members;
+ chat_members.reserve(participants_.size());
+ for (auto &participant : participants_) {
+ chat_members.push_back(td->contacts_manager_->get_chat_member_object(participant));
+ }
+
+ return td_api::make_object<td_api::chatMembers>(total_count_, std::move(chat_members));
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.h b/protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.h
index ab4c36a320..105531a11d 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogParticipant.h
@@ -1,15 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/ChannelType.h"
+#include "td/telegram/DialogId.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
-
#include "td/telegram/UserId.h"
+#include "td/telegram/Version.h"
#include "td/utils/common.h"
#include "td/utils/StringBuilder.h"
@@ -17,19 +19,123 @@
namespace td {
-class DialogParticipantStatus {
+class Td;
+
+class AdministratorRights {
static constexpr uint32 CAN_CHANGE_INFO_AND_SETTINGS = 1 << 0;
static constexpr uint32 CAN_POST_MESSAGES = 1 << 1;
static constexpr uint32 CAN_EDIT_MESSAGES = 1 << 2;
static constexpr uint32 CAN_DELETE_MESSAGES = 1 << 3;
static constexpr uint32 CAN_INVITE_USERS = 1 << 4;
- static constexpr uint32 CAN_EXPORT_DIALOG_INVITE_LINK = 1 << 5;
+ // static constexpr uint32 CAN_EXPORT_DIALOG_INVITE_LINK = 1 << 5;
static constexpr uint32 CAN_RESTRICT_MEMBERS = 1 << 6;
static constexpr uint32 CAN_PIN_MESSAGES = 1 << 7;
static constexpr uint32 CAN_PROMOTE_MEMBERS = 1 << 8;
+ static constexpr uint32 CAN_MANAGE_CALLS = 1 << 9;
+ static constexpr uint32 CAN_MANAGE_DIALOG = 1 << 10;
+ static constexpr uint32 CAN_MANAGE_TOPICS = 1 << 11;
+ static constexpr uint32 IS_ANONYMOUS = 1 << 13;
- static constexpr uint32 CAN_BE_EDITED = 1 << 15;
+ static constexpr uint32 ALL_ADMINISTRATOR_RIGHTS = CAN_CHANGE_INFO_AND_SETTINGS | CAN_POST_MESSAGES |
+ CAN_EDIT_MESSAGES | CAN_DELETE_MESSAGES | CAN_INVITE_USERS |
+ CAN_RESTRICT_MEMBERS | CAN_PIN_MESSAGES | CAN_MANAGE_TOPICS |
+ CAN_PROMOTE_MEMBERS | CAN_MANAGE_CALLS | CAN_MANAGE_DIALOG;
+
+ uint32 flags_;
+
+ friend class DialogParticipantStatus;
+
+ explicit AdministratorRights(int32 flags) : flags_(flags & (ALL_ADMINISTRATOR_RIGHTS | IS_ANONYMOUS)) {
+ }
+
+ public:
+ AdministratorRights() : flags_(0) {
+ }
+
+ AdministratorRights(const tl_object_ptr<telegram_api::chatAdminRights> &admin_rights, ChannelType channel_type);
+
+ AdministratorRights(const td_api::object_ptr<td_api::chatAdministratorRights> &administrator_rights,
+ ChannelType channel_type);
+
+ AdministratorRights(bool is_anonymous, bool can_manage_dialog, bool can_change_info, bool can_post_messages,
+ bool can_edit_messages, bool can_delete_messages, bool can_invite_users,
+ bool can_restrict_members, bool can_pin_messages, bool can_manage_topics,
+ bool can_promote_members, bool can_manage_calls, ChannelType channel_type);
+ telegram_api::object_ptr<telegram_api::chatAdminRights> get_chat_admin_rights() const;
+
+ td_api::object_ptr<td_api::chatAdministratorRights> get_chat_administrator_rights_object() const;
+
+ bool can_manage_dialog() const {
+ return (flags_ & CAN_MANAGE_DIALOG) != 0;
+ }
+
+ bool can_change_info_and_settings() const {
+ return (flags_ & CAN_CHANGE_INFO_AND_SETTINGS) != 0;
+ }
+
+ bool can_post_messages() const {
+ return (flags_ & CAN_POST_MESSAGES) != 0;
+ }
+
+ bool can_edit_messages() const {
+ return (flags_ & CAN_EDIT_MESSAGES) != 0;
+ }
+
+ bool can_delete_messages() const {
+ return (flags_ & CAN_DELETE_MESSAGES) != 0;
+ }
+
+ bool can_invite_users() const {
+ return (flags_ & CAN_INVITE_USERS) != 0;
+ }
+
+ bool can_restrict_members() const {
+ return (flags_ & CAN_RESTRICT_MEMBERS) != 0;
+ }
+
+ bool can_pin_messages() const {
+ return (flags_ & CAN_PIN_MESSAGES) != 0;
+ }
+
+ bool can_manage_topics() const {
+ return (flags_ & CAN_MANAGE_TOPICS) != 0;
+ }
+
+ bool can_promote_members() const {
+ return (flags_ & CAN_PROMOTE_MEMBERS) != 0;
+ }
+
+ bool can_manage_calls() const {
+ return (flags_ & CAN_MANAGE_CALLS) != 0;
+ }
+
+ bool is_anonymous() const {
+ return (flags_ & IS_ANONYMOUS) != 0;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(flags_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(flags_, parser);
+ }
+
+ friend bool operator==(const AdministratorRights &lhs, const AdministratorRights &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const AdministratorRights &status);
+};
+
+bool operator==(const AdministratorRights &lhs, const AdministratorRights &rhs);
+
+bool operator!=(const AdministratorRights &lhs, const AdministratorRights &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const AdministratorRights &status);
+
+class RestrictedRights {
static constexpr uint32 CAN_SEND_MESSAGES = 1 << 16;
static constexpr uint32 CAN_SEND_MEDIA = 1 << 17;
static constexpr uint32 CAN_SEND_STICKERS = 1 << 18;
@@ -37,47 +143,149 @@ class DialogParticipantStatus {
static constexpr uint32 CAN_SEND_GAMES = 1 << 20;
static constexpr uint32 CAN_USE_INLINE_BOTS = 1 << 21;
static constexpr uint32 CAN_ADD_WEB_PAGE_PREVIEWS = 1 << 22;
+ static constexpr uint32 CAN_SEND_POLLS = 1 << 23;
+ static constexpr uint32 CAN_CHANGE_INFO_AND_SETTINGS = 1 << 24;
+ static constexpr uint32 CAN_INVITE_USERS = 1 << 25;
+ static constexpr uint32 CAN_PIN_MESSAGES = 1 << 26;
+ static constexpr uint32 CAN_MANAGE_TOPICS = 1 << 12;
+
+ static constexpr uint32 ALL_ADMIN_PERMISSION_RIGHTS =
+ CAN_CHANGE_INFO_AND_SETTINGS | CAN_INVITE_USERS | CAN_PIN_MESSAGES | CAN_MANAGE_TOPICS;
+
+ static constexpr uint32 ALL_RESTRICTED_RIGHTS =
+ CAN_SEND_MESSAGES | CAN_SEND_MEDIA | CAN_SEND_STICKERS | CAN_SEND_ANIMATIONS | CAN_SEND_GAMES |
+ CAN_USE_INLINE_BOTS | CAN_ADD_WEB_PAGE_PREVIEWS | CAN_SEND_POLLS | ALL_ADMIN_PERMISSION_RIGHTS;
+
+ uint32 flags_;
+
+ friend class DialogParticipantStatus;
+
+ explicit RestrictedRights(int32 flags) : flags_(flags & ALL_RESTRICTED_RIGHTS) {
+ }
+
+ public:
+ explicit RestrictedRights(const tl_object_ptr<telegram_api::chatBannedRights> &rights);
+
+ explicit RestrictedRights(const td_api::object_ptr<td_api::chatPermissions> &rights);
+
+ RestrictedRights(bool can_send_messages, bool can_send_media, bool can_send_stickers, bool can_send_animations,
+ bool can_send_games, bool can_use_inline_bots, bool can_add_web_page_previews, bool can_send_polls,
+ bool can_change_info_and_settings, bool can_invite_users, bool can_pin_messages,
+ bool can_manage_topics);
+
+ td_api::object_ptr<td_api::chatPermissions> get_chat_permissions_object() const;
+
+ tl_object_ptr<telegram_api::chatBannedRights> get_chat_banned_rights() const;
+
+ bool can_change_info_and_settings() const {
+ return (flags_ & CAN_CHANGE_INFO_AND_SETTINGS) != 0;
+ }
+
+ bool can_invite_users() const {
+ return (flags_ & CAN_INVITE_USERS) != 0;
+ }
+
+ bool can_pin_messages() const {
+ return (flags_ & CAN_PIN_MESSAGES) != 0;
+ }
+
+ bool can_manage_topics() const {
+ return (flags_ & CAN_MANAGE_TOPICS) != 0;
+ }
+
+ bool can_send_messages() const {
+ return (flags_ & CAN_SEND_MESSAGES) != 0;
+ }
+
+ bool can_send_media() const {
+ return (flags_ & CAN_SEND_MEDIA) != 0;
+ }
+
+ bool can_send_stickers() const {
+ return (flags_ & CAN_SEND_STICKERS) != 0;
+ }
+
+ bool can_send_animations() const {
+ return (flags_ & CAN_SEND_ANIMATIONS) != 0;
+ }
+
+ bool can_send_games() const {
+ return (flags_ & CAN_SEND_GAMES) != 0;
+ }
+
+ bool can_use_inline_bots() const {
+ return (flags_ & CAN_USE_INLINE_BOTS) != 0;
+ }
+
+ bool can_add_web_page_previews() const {
+ return (flags_ & CAN_ADD_WEB_PAGE_PREVIEWS) != 0;
+ }
+
+ bool can_send_polls() const {
+ return (flags_ & CAN_SEND_POLLS) != 0;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(flags_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(flags_, parser);
+ }
+
+ friend bool operator==(const RestrictedRights &lhs, const RestrictedRights &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const RestrictedRights &status);
+};
+
+bool operator==(const RestrictedRights &lhs, const RestrictedRights &rhs);
+
+bool operator!=(const RestrictedRights &lhs, const RestrictedRights &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const RestrictedRights &status);
+
+class DialogParticipantStatus {
+ // all flags are used
+ static constexpr uint32 HAS_RANK = 1 << 14;
+ static constexpr uint32 CAN_BE_EDITED = 1 << 15;
static constexpr uint32 IS_MEMBER = 1 << 27;
- // bits 28-31 reserved for Type and until_date flag
+ // bits 28-30 reserved for Type
static constexpr int TYPE_SHIFT = 28;
static constexpr uint32 HAS_UNTIL_DATE = 1u << 31;
- static constexpr uint32 ALL_ADMINISTRATOR_RIGHTS =
- CAN_CHANGE_INFO_AND_SETTINGS | CAN_POST_MESSAGES | CAN_EDIT_MESSAGES | CAN_DELETE_MESSAGES | CAN_INVITE_USERS |
- CAN_EXPORT_DIALOG_INVITE_LINK | CAN_RESTRICT_MEMBERS | CAN_PIN_MESSAGES | CAN_PROMOTE_MEMBERS;
-
- static constexpr uint32 ALL_RESTRICTED_RIGHTS = CAN_SEND_MESSAGES | CAN_SEND_MEDIA | CAN_SEND_STICKERS |
- CAN_SEND_ANIMATIONS | CAN_SEND_GAMES | CAN_USE_INLINE_BOTS |
- CAN_ADD_WEB_PAGE_PREVIEWS;
-
- enum class Type { Creator, Administrator, Member, Restricted, Left, Banned };
+ enum class Type : int32 { Creator, Administrator, Member, Restricted, Left, Banned };
// all fields are logically const, but should be updated in update_restrictions()
mutable Type type_;
mutable uint32 flags_;
mutable int32 until_date_; // restricted and banned only
+ string rank_; // creator and administrator only
static int32 fix_until_date(int32 date);
- DialogParticipantStatus(Type type, uint32 flags, int32 until_date)
- : type_(type), flags_(flags), until_date_(until_date) {
+ DialogParticipantStatus(Type type, uint32 flags, int32 until_date, string rank);
+
+ AdministratorRights get_administrator_rights() const {
+ return AdministratorRights(flags_);
+ }
+
+ RestrictedRights get_restricted_rights() const {
+ return RestrictedRights(flags_);
}
public:
- static DialogParticipantStatus Creator(bool is_member);
+ static DialogParticipantStatus Creator(bool is_member, bool is_anonymous, string &&rank);
- static DialogParticipantStatus Administrator(bool can_be_edited, bool can_change_info, bool can_post_messages,
- bool can_edit_messages, bool can_delete_messages, bool can_invite_users,
- bool can_export_dialog_invite_link, bool can_restrict_members,
- bool can_pin_messages, bool can_promote_members);
+ static DialogParticipantStatus Administrator(AdministratorRights administrator_rights, string &&rank,
+ bool can_be_edited);
static DialogParticipantStatus Member();
- static DialogParticipantStatus Restricted(bool is_member, int32 restricted_until_date, bool can_send_messages,
- bool can_send_media, bool can_send_stickers, bool can_send_animations,
- bool can_send_games, bool can_use_inline_bots,
- bool can_add_web_page_previews);
+ static DialogParticipantStatus Restricted(RestrictedRights restricted_rights, bool is_member,
+ int32 restricted_until_date);
static DialogParticipantStatus Left();
@@ -89,87 +297,137 @@ class DialogParticipantStatus {
// legacy rights
static DialogParticipantStatus ChannelAdministrator(bool is_creator, bool is_megagroup);
+ // forcely returns an administrator
+ DialogParticipantStatus(bool can_be_edited, tl_object_ptr<telegram_api::chatAdminRights> &&admin_rights, string rank,
+ ChannelType channel_type);
+
+ // forcely returns a restricted or banned
+ DialogParticipantStatus(bool is_member, tl_object_ptr<telegram_api::chatBannedRights> &&banned_rights);
+
+ RestrictedRights get_effective_restricted_rights() const;
+
+ DialogParticipantStatus apply_restrictions(RestrictedRights default_restrictions, bool is_bot) const;
+
tl_object_ptr<td_api::ChatMemberStatus> get_chat_member_status_object() const;
- tl_object_ptr<telegram_api::channelAdminRights> get_channel_admin_rights() const;
+ tl_object_ptr<telegram_api::chatAdminRights> get_chat_admin_rights() const;
- tl_object_ptr<telegram_api::channelBannedRights> get_channel_banned_rights() const;
+ tl_object_ptr<telegram_api::chatBannedRights> get_chat_banned_rights() const;
// unrestricts user if restriction time expired. Should be called before all privileges checks
void update_restrictions() const;
+ bool can_manage_dialog() const {
+ return get_administrator_rights().can_manage_dialog();
+ }
+
bool can_change_info_and_settings() const {
- return (flags_ & CAN_CHANGE_INFO_AND_SETTINGS) != 0;
+ return get_administrator_rights().can_change_info_and_settings() ||
+ get_restricted_rights().can_change_info_and_settings();
}
bool can_post_messages() const {
- return (flags_ & CAN_POST_MESSAGES) != 0;
+ return get_administrator_rights().can_post_messages();
}
bool can_edit_messages() const {
- return (flags_ & CAN_EDIT_MESSAGES) != 0;
+ return get_administrator_rights().can_edit_messages();
}
bool can_delete_messages() const {
- return (flags_ & CAN_DELETE_MESSAGES) != 0;
+ return get_administrator_rights().can_delete_messages();
}
bool can_invite_users() const {
- return (flags_ & CAN_INVITE_USERS) != 0;
+ return get_administrator_rights().can_invite_users() || get_restricted_rights().can_invite_users();
}
- bool can_export_dialog_invite_link() const {
- return (flags_ & CAN_EXPORT_DIALOG_INVITE_LINK) != 0;
+ bool can_manage_invite_links() const {
+ // invite links can be managed, only if administrator was explicitly granted the right
+ return get_administrator_rights().can_invite_users();
}
bool can_restrict_members() const {
- return (flags_ & CAN_RESTRICT_MEMBERS) != 0;
+ return get_administrator_rights().can_restrict_members();
}
bool can_pin_messages() const {
- return (flags_ & CAN_PIN_MESSAGES) != 0;
+ return get_administrator_rights().can_pin_messages() || get_restricted_rights().can_pin_messages();
+ }
+
+ bool can_edit_topics() const {
+ // topics can be edited, only if administrator was explicitly granted the right
+ return get_administrator_rights().can_manage_topics();
+ }
+
+ bool can_create_topics() const {
+ return get_administrator_rights().can_manage_topics() || get_restricted_rights().can_manage_topics();
}
bool can_promote_members() const {
- return (flags_ & CAN_PROMOTE_MEMBERS) != 0;
+ return get_administrator_rights().can_promote_members();
+ }
+
+ bool can_manage_calls() const {
+ return get_administrator_rights().can_manage_calls();
}
bool can_be_edited() const {
return (flags_ & CAN_BE_EDITED) != 0;
}
+ void toggle_can_be_edited() {
+ flags_ ^= CAN_BE_EDITED;
+ }
+
bool can_send_messages() const {
- return (flags_ & CAN_SEND_MESSAGES) != 0;
+ return get_restricted_rights().can_send_messages();
}
bool can_send_media() const {
- return (flags_ & CAN_SEND_MEDIA) != 0;
+ return get_restricted_rights().can_send_media();
}
bool can_send_stickers() const {
- return (flags_ & CAN_SEND_STICKERS) != 0;
+ return get_restricted_rights().can_send_stickers();
}
bool can_send_animations() const {
- return (flags_ & CAN_SEND_ANIMATIONS) != 0;
+ return get_restricted_rights().can_send_animations();
}
bool can_send_games() const {
- return (flags_ & CAN_SEND_GAMES) != 0;
+ return get_restricted_rights().can_send_games();
}
bool can_use_inline_bots() const {
- return (flags_ & CAN_USE_INLINE_BOTS) != 0;
+ return get_restricted_rights().can_use_inline_bots();
}
bool can_add_web_page_previews() const {
- return (flags_ & CAN_ADD_WEB_PAGE_PREVIEWS) != 0;
+ return get_restricted_rights().can_add_web_page_previews();
+ }
+
+ bool can_send_polls() const {
+ return get_restricted_rights().can_send_polls();
+ }
+
+ void set_is_member(bool is_member) {
+ if (is_member) {
+ flags_ |= IS_MEMBER;
+ } else {
+ flags_ &= ~IS_MEMBER;
+ }
}
bool is_member() const {
return (flags_ & IS_MEMBER) != 0;
}
+ bool is_left() const {
+ return (flags_ & IS_MEMBER) == 0;
+ }
+
bool is_creator() const {
return type_ == Type::Creator;
}
@@ -190,16 +448,30 @@ class DialogParticipantStatus {
return until_date_;
}
+ bool is_anonymous() const {
+ return get_administrator_rights().is_anonymous();
+ }
+
+ const string &get_rank() const {
+ return rank_;
+ }
+
template <class StorerT>
void store(StorerT &storer) const {
uint32 stored_flags = flags_ | (static_cast<uint32>(type_) << TYPE_SHIFT);
if (until_date_ > 0) {
stored_flags |= HAS_UNTIL_DATE;
}
+ if (!rank_.empty()) {
+ stored_flags |= HAS_RANK;
+ }
td::store(stored_flags, storer);
if (until_date_ > 0) {
td::store(until_date_, storer);
}
+ if (!rank_.empty()) {
+ td::store(rank_, storer);
+ }
}
template <class ParserT>
@@ -210,8 +482,18 @@ class DialogParticipantStatus {
td::parse(until_date_, parser);
stored_flags &= ~HAS_UNTIL_DATE;
}
+ if ((stored_flags & HAS_RANK) != 0) {
+ td::parse(rank_, parser);
+ stored_flags &= ~HAS_RANK;
+ }
type_ = static_cast<Type>(stored_flags >> TYPE_SHIFT);
flags_ = stored_flags & ((1 << TYPE_SHIFT) - 1);
+
+ if (is_creator()) {
+ flags_ |= AdministratorRights::ALL_ADMINISTRATOR_RIGHTS | RestrictedRights::ALL_RESTRICTED_RIGHTS;
+ } else if (is_administrator()) {
+ flags_ |= AdministratorRights::CAN_MANAGE_DIALOG;
+ }
}
friend bool operator==(const DialogParticipantStatus &lhs, const DialogParticipantStatus &rhs);
@@ -226,38 +508,69 @@ bool operator!=(const DialogParticipantStatus &lhs, const DialogParticipantStatu
StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipantStatus &status);
struct DialogParticipant {
- UserId user_id;
- UserId inviter_user_id;
- int32 joined_date = 0;
- DialogParticipantStatus status = DialogParticipantStatus::Left();
+ DialogId dialog_id_;
+ UserId inviter_user_id_;
+ int32 joined_date_ = 0;
+ DialogParticipantStatus status_ = DialogParticipantStatus::Left();
DialogParticipant() = default;
- DialogParticipant(UserId user_id, UserId inviter_user_id, int32 joined_date, DialogParticipantStatus status)
- : user_id(user_id), inviter_user_id(inviter_user_id), joined_date(joined_date), status(status) {
+ DialogParticipant(DialogId dialog_id, UserId inviter_user_id, int32 joined_date, DialogParticipantStatus status);
+
+ DialogParticipant(tl_object_ptr<telegram_api::ChatParticipant> &&participant_ptr, int32 chat_creation_date,
+ bool is_creator);
+
+ DialogParticipant(tl_object_ptr<telegram_api::ChannelParticipant> &&participant_ptr, ChannelType channel_type);
+
+ static DialogParticipant left(DialogId dialog_id) {
+ return {dialog_id, UserId(), 0, DialogParticipantStatus::Left()};
}
-};
-class ChannelParticipantsFilter {
- enum { Recent, Administrators, Search, Restricted, Banned, Bots } type;
- string query;
+ static DialogParticipant private_member(UserId user_id, UserId other_user_id) {
+ auto inviter_user_id = other_user_id.is_valid() ? other_user_id : user_id;
+ return {DialogId(user_id), inviter_user_id, 0, DialogParticipantStatus::Member()};
+ }
- public:
- explicit ChannelParticipantsFilter(const tl_object_ptr<td_api::SupergroupMembersFilter> &filter);
+ bool is_valid() const;
- tl_object_ptr<telegram_api::ChannelParticipantsFilter> get_input_channel_participants_filter() const;
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
+ td::store(inviter_user_id_, storer);
+ td::store(joined_date_, storer);
+ td::store(status_, storer);
+ }
- bool is_administrators() const {
- return type == Administrators;
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ if (parser.version() >= static_cast<int32>(Version::SupportBannedChannels)) {
+ td::parse(dialog_id_, parser);
+ } else {
+ UserId user_id;
+ td::parse(user_id, parser);
+ dialog_id_ = DialogId(user_id);
+ }
+ td::parse(inviter_user_id_, parser);
+ td::parse(joined_date_, parser);
+ td::parse(status_, parser);
}
};
-DialogParticipantStatus get_dialog_participant_status(const tl_object_ptr<td_api::ChatMemberStatus> &status);
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipant &dialog_participant);
+
+struct DialogParticipants {
+ int32 total_count_ = 0;
+ vector<DialogParticipant> participants_;
-DialogParticipantStatus get_dialog_participant_status(
- bool can_be_edited, const tl_object_ptr<telegram_api::channelAdminRights> &admin_rights);
+ DialogParticipants() = default;
+ DialogParticipants(int32 total_count, vector<DialogParticipant> &&participants)
+ : total_count_(total_count), participants_(std::move(participants)) {
+ }
+
+ td_api::object_ptr<td_api::chatMembers> get_chat_members_object(Td *td) const;
+};
-DialogParticipantStatus get_dialog_participant_status(
- bool is_member, const tl_object_ptr<telegram_api::channelBannedRights> &banned_rights);
+DialogParticipantStatus get_dialog_participant_status(const td_api::object_ptr<td_api::ChatMemberStatus> &status,
+ ChannelType channel_type);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogParticipantFilter.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogParticipantFilter.cpp
new file mode 100644
index 0000000000..040751da7f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogParticipantFilter.cpp
@@ -0,0 +1,142 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogParticipantFilter.h"
+
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/Td.h"
+
+namespace td {
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipantFilter &filter) {
+ switch (filter.type_) {
+ case DialogParticipantFilter::Type::Contacts:
+ return string_builder << "Contacts";
+ case DialogParticipantFilter::Type::Administrators:
+ return string_builder << "Administrators";
+ case DialogParticipantFilter::Type::Members:
+ return string_builder << "Members";
+ case DialogParticipantFilter::Type::Restricted:
+ return string_builder << "Restricted";
+ case DialogParticipantFilter::Type::Banned:
+ return string_builder << "Banned";
+ case DialogParticipantFilter::Type::Mention:
+ return string_builder << "Mention";
+ case DialogParticipantFilter::Type::Bots:
+ return string_builder << "Bots";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+DialogParticipantFilter::DialogParticipantFilter(const td_api::object_ptr<td_api::ChatMembersFilter> &filter) {
+ if (filter == nullptr) {
+ type_ = Type::Members;
+ return;
+ }
+ switch (filter->get_id()) {
+ case td_api::chatMembersFilterContacts::ID:
+ type_ = Type::Contacts;
+ break;
+ case td_api::chatMembersFilterAdministrators::ID:
+ type_ = Type::Administrators;
+ break;
+ case td_api::chatMembersFilterMembers::ID:
+ type_ = Type::Members;
+ break;
+ case td_api::chatMembersFilterRestricted::ID:
+ type_ = Type::Restricted;
+ break;
+ case td_api::chatMembersFilterBanned::ID:
+ type_ = Type::Banned;
+ break;
+ case td_api::chatMembersFilterMention::ID: {
+ auto mention_filter = static_cast<const td_api::chatMembersFilterMention *>(filter.get());
+ top_thread_message_id_ = MessageId(mention_filter->message_thread_id_);
+ if (!top_thread_message_id_.is_valid() || !top_thread_message_id_.is_server()) {
+ top_thread_message_id_ = MessageId();
+ }
+ type_ = Type::Mention;
+ break;
+ }
+ case td_api::chatMembersFilterBots::ID:
+ type_ = Type::Bots;
+ break;
+ default:
+ UNREACHABLE();
+ type_ = Type::Members;
+ break;
+ }
+}
+
+td_api::object_ptr<td_api::SupergroupMembersFilter> DialogParticipantFilter::get_supergroup_members_filter_object(
+ const string &query) const {
+ switch (type_) {
+ case Type::Contacts:
+ return td_api::make_object<td_api::supergroupMembersFilterContacts>();
+ case Type::Administrators:
+ return td_api::make_object<td_api::supergroupMembersFilterAdministrators>();
+ case Type::Members:
+ return td_api::make_object<td_api::supergroupMembersFilterSearch>(query);
+ case Type::Restricted:
+ return td_api::make_object<td_api::supergroupMembersFilterRestricted>(query);
+ case Type::Banned:
+ return td_api::make_object<td_api::supergroupMembersFilterBanned>(query);
+ case Type::Mention:
+ return td_api::make_object<td_api::supergroupMembersFilterMention>(query, top_thread_message_id_.get());
+ case Type::Bots:
+ return td_api::make_object<td_api::supergroupMembersFilterBots>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+bool DialogParticipantFilter::has_query() const {
+ switch (type_) {
+ case Type::Members:
+ case Type::Restricted:
+ case Type::Banned:
+ case Type::Mention:
+ return true;
+ case Type::Contacts:
+ case Type::Administrators:
+ case Type::Bots:
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+bool DialogParticipantFilter::is_dialog_participant_suitable(const Td *td, const DialogParticipant &participant) const {
+ switch (type_) {
+ case Type::Contacts:
+ return participant.dialog_id_.get_type() == DialogType::User &&
+ td->contacts_manager_->is_user_contact(participant.dialog_id_.get_user_id());
+ case Type::Administrators:
+ return participant.status_.is_administrator();
+ case Type::Members:
+ return participant.status_.is_member();
+ case Type::Restricted:
+ return participant.status_.is_restricted();
+ case Type::Banned:
+ return participant.status_.is_banned();
+ case Type::Mention:
+ return true;
+ case Type::Bots:
+ return participant.dialog_id_.get_type() == DialogType::User &&
+ td->contacts_manager_->is_user_bot(participant.dialog_id_.get_user_id());
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogParticipantFilter.h b/protocols/Telegram/tdlib/td/td/telegram/DialogParticipantFilter.h
new file mode 100644
index 0000000000..3b3e7166c9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogParticipantFilter.h
@@ -0,0 +1,39 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MessageId.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+struct DialogParticipant;
+class Td;
+
+class DialogParticipantFilter {
+ enum class Type : int32 { Contacts, Administrators, Members, Restricted, Banned, Mention, Bots };
+ Type type_;
+ MessageId top_thread_message_id_;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipantFilter &filter);
+
+ public:
+ explicit DialogParticipantFilter(const td_api::object_ptr<td_api::ChatMembersFilter> &filter);
+
+ td_api::object_ptr<td_api::SupergroupMembersFilter> get_supergroup_members_filter_object(const string &query) const;
+
+ bool has_query() const;
+
+ bool is_dialog_participant_suitable(const Td *td, const DialogParticipant &participant) const;
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogParticipantFilter &filter);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogSource.cpp b/protocols/Telegram/tdlib/td/td/telegram/DialogSource.cpp
new file mode 100644
index 0000000000..6f0b71cf3b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogSource.cpp
@@ -0,0 +1,98 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DialogSource.h"
+
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+
+namespace td {
+
+DialogSource DialogSource::mtproto_proxy() {
+ DialogSource result;
+ result.type_ = Type::MtprotoProxy;
+ return result;
+}
+
+DialogSource DialogSource::public_service_announcement(string psa_type, string psa_text) {
+ DialogSource result;
+ result.type_ = Type::PublicServiceAnnouncement;
+ result.psa_type_ = std::move(psa_type);
+ result.psa_text_ = std::move(psa_text);
+ return result;
+}
+
+Result<DialogSource> DialogSource::unserialize(Slice str) {
+ if (str.empty()) {
+ // legacy
+ return mtproto_proxy();
+ }
+ auto type_data = split(str);
+ TRY_RESULT(type, to_integer_safe<int32>(type_data.first));
+ switch (type) {
+ case static_cast<int32>(Type::MtprotoProxy):
+ return mtproto_proxy();
+ case static_cast<int32>(Type::PublicServiceAnnouncement): {
+ auto data = split(type_data.second, '\x01');
+ return public_service_announcement(data.first.str(), data.second.str());
+ }
+ default:
+ return Status::Error("Unexpected chat source type");
+ }
+}
+
+string DialogSource::serialize() const {
+ switch (type_) {
+ case DialogSource::Type::Membership:
+ UNREACHABLE();
+ return "";
+ case DialogSource::Type::MtprotoProxy:
+ return "1";
+ case DialogSource::Type::PublicServiceAnnouncement:
+ return PSTRING() << "2 " << psa_type_ << '\x01' << psa_text_;
+ default:
+ UNREACHABLE();
+ return "";
+ }
+}
+
+td_api::object_ptr<td_api::ChatSource> DialogSource::get_chat_source_object() const {
+ switch (type_) {
+ case DialogSource::Type::Membership:
+ return nullptr;
+ case DialogSource::Type::MtprotoProxy:
+ return td_api::make_object<td_api::chatSourceMtprotoProxy>();
+ case DialogSource::Type::PublicServiceAnnouncement:
+ return td_api::make_object<td_api::chatSourcePublicServiceAnnouncement>(psa_type_, psa_text_);
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+bool operator==(const DialogSource &lhs, const DialogSource &rhs) {
+ return lhs.type_ == rhs.type_ && lhs.psa_type_ == rhs.psa_type_ && lhs.psa_text_ == rhs.psa_text_;
+}
+
+bool operator!=(const DialogSource &lhs, const DialogSource &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogSource &source) {
+ switch (source.type_) {
+ case DialogSource::Type::Membership:
+ return string_builder << "chat list";
+ case DialogSource::Type::MtprotoProxy:
+ return string_builder << "MTProto proxy sponsor";
+ case DialogSource::Type::PublicServiceAnnouncement:
+ return string_builder << "public service announcement of type " << source.psa_type_;
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DialogSource.h b/protocols/Telegram/tdlib/td/td/telegram/DialogSource.h
new file mode 100644
index 0000000000..088c9ec726
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DialogSource.h
@@ -0,0 +1,48 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class DialogSource {
+ enum class Type : int32 { Membership, MtprotoProxy, PublicServiceAnnouncement };
+ Type type_ = Type::Membership;
+ string psa_type_;
+ string psa_text_;
+
+ friend bool operator==(const DialogSource &lhs, const DialogSource &rhs);
+
+ friend bool operator!=(const DialogSource &lhs, const DialogSource &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogSource &source);
+
+ public:
+ static DialogSource mtproto_proxy();
+
+ static DialogSource public_service_announcement(string psa_type, string psa_text);
+
+ static Result<DialogSource> unserialize(Slice str);
+
+ string serialize() const;
+
+ td_api::object_ptr<td_api::ChatSource> get_chat_source_object() const;
+};
+
+bool operator==(const DialogSource &lhs, const DialogSource &rhs);
+
+bool operator!=(const DialogSource &lhs, const DialogSource &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogSource &source);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Dimensions.cpp b/protocols/Telegram/tdlib/td/td/telegram/Dimensions.cpp
new file mode 100644
index 0000000000..9fd1a714c2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Dimensions.cpp
@@ -0,0 +1,51 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Dimensions.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+
+namespace td {
+
+static uint16 get_dimension(int32 size, const char *source) {
+ if (size < 0 || size > 65535) {
+ if (source != nullptr) {
+ LOG(ERROR) << "Wrong image dimension = " << size << " from " << source;
+ }
+ return 0;
+ }
+ return narrow_cast<uint16>(size);
+}
+
+Dimensions get_dimensions(int32 width, int32 height, const char *source) {
+ Dimensions result;
+ result.width = get_dimension(width, source);
+ result.height = get_dimension(height, source);
+ if (result.width == 0 || result.height == 0) {
+ result.width = 0;
+ result.height = 0;
+ }
+ return result;
+}
+
+uint32 get_dimensions_pixel_count(const Dimensions &dimensions) {
+ return static_cast<uint32>(dimensions.width) * static_cast<uint32>(dimensions.height);
+}
+
+bool operator==(const Dimensions &lhs, const Dimensions &rhs) {
+ return lhs.width == rhs.width && lhs.height == rhs.height;
+}
+
+bool operator!=(const Dimensions &lhs, const Dimensions &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Dimensions &dimensions) {
+ return string_builder << "(" << dimensions.width << ", " << dimensions.height << ")";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Dimensions.h b/protocols/Telegram/tdlib/td/td/telegram/Dimensions.h
new file mode 100644
index 0000000000..59430d9126
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Dimensions.h
@@ -0,0 +1,28 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+struct Dimensions {
+ uint16 width = 0;
+ uint16 height = 0;
+};
+
+Dimensions get_dimensions(int32 width, int32 height, const char *source);
+
+uint32 get_dimensions_pixel_count(const Dimensions &dimensions);
+
+bool operator==(const Dimensions &lhs, const Dimensions &rhs);
+bool operator!=(const Dimensions &lhs, const Dimensions &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Dimensions &dimensions);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Dimensions.hpp b/protocols/Telegram/tdlib/td/td/telegram/Dimensions.hpp
new file mode 100644
index 0000000000..19ffa8d421
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Dimensions.hpp
@@ -0,0 +1,28 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/Dimensions.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(Dimensions dimensions, StorerT &storer) {
+ store(static_cast<uint32>((static_cast<uint32>(dimensions.width) << 16) | dimensions.height), storer);
+}
+
+template <class ParserT>
+void parse(Dimensions &dimensions, ParserT &parser) {
+ uint32 width_height;
+ parse(width_height, parser);
+ dimensions.width = static_cast<uint16>(width_height >> 16);
+ dimensions.height = static_cast<uint16>(width_height & 0xFFFF);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Document.cpp b/protocols/Telegram/tdlib/td/td/telegram/Document.cpp
new file mode 100644
index 0000000000..0ab6ca53a2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Document.cpp
@@ -0,0 +1,113 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Document.h"
+
+#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/AudiosManager.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/VideoNotesManager.h"
+#include "td/telegram/VideosManager.h"
+
+#include "td/utils/algorithm.h"
+
+namespace td {
+
+vector<FileId> Document::get_file_ids(const Td *td) const {
+ vector<FileId> result;
+ append_file_ids(td, result);
+ return result;
+}
+
+void Document::append_file_ids(const Td *td, vector<FileId> &file_ids) const {
+ if (!file_id.is_valid() || empty()) {
+ return;
+ }
+
+ if (type == Type::Sticker) {
+ append(file_ids, td->stickers_manager_->get_sticker_file_ids(file_id));
+ return;
+ }
+
+ file_ids.push_back(file_id);
+
+ FileId thumbnail_file_id = [&] {
+ switch (type) {
+ case Type::Animation:
+ return td->animations_manager_->get_animation_thumbnail_file_id(file_id);
+ case Type::Audio:
+ return td->audios_manager_->get_audio_thumbnail_file_id(file_id);
+ case Type::General:
+ return td->documents_manager_->get_document_thumbnail_file_id(file_id);
+ case Type::Video:
+ return td->videos_manager_->get_video_thumbnail_file_id(file_id);
+ case Type::VideoNote:
+ return td->video_notes_manager_->get_video_note_thumbnail_file_id(file_id);
+ default:
+ return FileId();
+ }
+ }();
+ if (thumbnail_file_id.is_valid()) {
+ file_ids.push_back(thumbnail_file_id);
+ }
+
+ FileId animated_thumbnail_file_id = [&] {
+ switch (type) {
+ case Type::Animation:
+ return td->animations_manager_->get_animation_animated_thumbnail_file_id(file_id);
+ case Type::Video:
+ return td->videos_manager_->get_video_animated_thumbnail_file_id(file_id);
+ default:
+ return FileId();
+ }
+ }();
+ if (animated_thumbnail_file_id.is_valid()) {
+ file_ids.push_back(animated_thumbnail_file_id);
+ }
+
+ if (type == Type::Audio) {
+ td->audios_manager_->append_audio_album_cover_file_ids(file_id, file_ids);
+ }
+}
+
+bool operator==(const Document &lhs, const Document &rhs) {
+ return lhs.type == rhs.type && lhs.file_id == rhs.file_id;
+}
+
+bool operator!=(const Document &lhs, const Document &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Document::Type &document_type) {
+ switch (document_type) {
+ case Document::Type::Unknown:
+ return string_builder << "Unknown";
+ case Document::Type::Animation:
+ return string_builder << "Animation";
+ case Document::Type::Audio:
+ return string_builder << "Audio";
+ case Document::Type::General:
+ return string_builder << "Document";
+ case Document::Type::Sticker:
+ return string_builder << "Sticker";
+ case Document::Type::Video:
+ return string_builder << "Video";
+ case Document::Type::VideoNote:
+ return string_builder << "VideoNote";
+ case Document::Type::VoiceNote:
+ return string_builder << "VoiceNote";
+ default:
+ return string_builder << "Unreachable";
+ }
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Document &document) {
+ return string_builder << '[' << document.type << ' ' << document.file_id << ']';
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Document.h b/protocols/Telegram/tdlib/td/td/telegram/Document.h
new file mode 100644
index 0000000000..e58c816144
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Document.h
@@ -0,0 +1,46 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/FileId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class Td;
+
+struct Document {
+ // append only
+ enum class Type : int32 { Unknown, Animation, Audio, General, Sticker, Video, VideoNote, VoiceNote };
+
+ Type type = Type::Unknown;
+ FileId file_id;
+
+ Document() = default;
+ Document(Type type, FileId file_id) : type(type), file_id(file_id) {
+ }
+
+ bool empty() const {
+ return type == Type::Unknown;
+ }
+
+ vector<FileId> get_file_ids(const Td *td) const;
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const;
+};
+
+bool operator==(const Document &lhs, const Document &rhs);
+
+bool operator!=(const Document &lhs, const Document &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Document::Type &document_type);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Document &document);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Document.hpp b/protocols/Telegram/tdlib/td/td/telegram/Document.hpp
new file mode 100644
index 0000000000..3938a809ca
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Document.hpp
@@ -0,0 +1,107 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/Document.h"
+
+#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/AnimationsManager.hpp"
+#include "td/telegram/AudiosManager.h"
+#include "td/telegram/AudiosManager.hpp"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/DocumentsManager.hpp"
+#include "td/telegram/files/FileId.hpp"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/StickersManager.hpp"
+#include "td/telegram/Td.h"
+#include "td/telegram/VideoNotesManager.h"
+#include "td/telegram/VideoNotesManager.hpp"
+#include "td/telegram/VideosManager.h"
+#include "td/telegram/VideosManager.hpp"
+#include "td/telegram/VoiceNotesManager.h"
+#include "td/telegram/VoiceNotesManager.hpp"
+
+#include "td/utils/logging.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const Document &document, StorerT &storer) {
+ Td *td = storer.context()->td().get_actor_unsafe();
+ CHECK(td != nullptr);
+
+ store(document.type, storer);
+ switch (document.type) {
+ case Document::Type::Animation:
+ td->animations_manager_->store_animation(document.file_id, storer);
+ break;
+ case Document::Type::Audio:
+ td->audios_manager_->store_audio(document.file_id, storer);
+ break;
+ case Document::Type::General:
+ td->documents_manager_->store_document(document.file_id, storer);
+ break;
+ case Document::Type::Sticker:
+ td->stickers_manager_->store_sticker(document.file_id, false, storer, "Document");
+ break;
+ case Document::Type::Video:
+ td->videos_manager_->store_video(document.file_id, storer);
+ break;
+ case Document::Type::VideoNote:
+ td->video_notes_manager_->store_video_note(document.file_id, storer);
+ break;
+ case Document::Type::VoiceNote:
+ td->voice_notes_manager_->store_voice_note(document.file_id, storer);
+ break;
+ case Document::Type::Unknown:
+ default:
+ UNREACHABLE();
+ }
+}
+
+template <class ParserT>
+void parse(Document &document, ParserT &parser) {
+ Td *td = parser.context()->td().get_actor_unsafe();
+ CHECK(td != nullptr);
+
+ parse(document.type, parser);
+ switch (document.type) {
+ case Document::Type::Animation:
+ document.file_id = td->animations_manager_->parse_animation(parser);
+ break;
+ case Document::Type::Audio:
+ document.file_id = td->audios_manager_->parse_audio(parser);
+ break;
+ case Document::Type::General:
+ document.file_id = td->documents_manager_->parse_document(parser);
+ break;
+ case Document::Type::Sticker:
+ document.file_id = td->stickers_manager_->parse_sticker(false, parser);
+ break;
+ case Document::Type::Video:
+ document.file_id = td->videos_manager_->parse_video(parser);
+ break;
+ case Document::Type::VideoNote:
+ document.file_id = td->video_notes_manager_->parse_video_note(parser);
+ break;
+ case Document::Type::VoiceNote:
+ document.file_id = td->voice_notes_manager_->parse_voice_note(parser);
+ break;
+ case Document::Type::Unknown:
+ default:
+ LOG(ERROR) << "Have invalid Document type " << static_cast<int32>(document.type);
+ document = Document();
+ return;
+ }
+ if (!document.file_id.is_valid()) {
+ LOG(ERROR) << "Parse invalid document.file_id";
+ document = Document();
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.cpp
index f39ade91c2..ffc45945c6 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,21 +8,31 @@
#include "td/telegram/AnimationsManager.h"
#include "td/telegram/AudiosManager.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/files/FileEncryptionKey.h"
+#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
-#include "td/telegram/Photo.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/PhotoSizeSource.h"
+#include "td/telegram/secret_api.h"
+#include "td/telegram/StickerFormat.h"
#include "td/telegram/StickersManager.h"
+#include "td/telegram/StickerType.h"
#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/VideoNotesManager.h"
#include "td/telegram/VideosManager.h"
#include "td/telegram/VoiceNotesManager.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
+#include "td/actor/actor.h"
#include "td/utils/common.h"
-#include "td/utils/format.h"
#include "td/utils/HttpUrl.h"
#include "td/utils/logging.h"
#include "td/utils/MimeType.h"
@@ -42,27 +52,33 @@ namespace td {
DocumentsManager::DocumentsManager(Td *td) : td_(td) {
}
-tl_object_ptr<td_api::document> DocumentsManager::get_document_object(FileId file_id) {
+DocumentsManager::~DocumentsManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), documents_);
+}
+
+tl_object_ptr<td_api::document> DocumentsManager::get_document_object(FileId file_id,
+ PhotoFormat thumbnail_format) const {
if (!file_id.is_valid()) {
return nullptr;
}
- LOG(INFO) << "Return document " << file_id << " object";
- auto &document = documents_[file_id];
- CHECK(document != nullptr) << tag("file_id", file_id);
- document->is_changed = false;
- return make_tl_object<td_api::document>(document->file_name, document->mime_type,
- get_photo_size_object(td_->file_manager_.get(), &document->thumbnail),
- td_->file_manager_->get_file_object(file_id));
+ auto document = get_document(file_id);
+ CHECK(document != nullptr);
+ return make_tl_object<td_api::document>(
+ document->file_name, document->mime_type, get_minithumbnail_object(document->minithumbnail),
+ get_thumbnail_object(td_->file_manager_.get(), document->thumbnail, thumbnail_format),
+ td_->file_manager_->get_file_object(file_id));
}
-std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_document(
- RemoteDocument remote_document, DialogId owner_dialog_id, MultiPromiseActor *load_data_multipromise_ptr,
- DocumentType default_document_type) {
+Document DocumentsManager::on_get_document(RemoteDocument remote_document, DialogId owner_dialog_id,
+ MultiPromiseActor *load_data_multipromise_ptr,
+ Document::Type default_document_type, bool is_background, bool is_pattern,
+ bool is_ringtone) {
tl_object_ptr<telegram_api::documentAttributeAnimated> animated;
tl_object_ptr<telegram_api::documentAttributeVideo> video;
tl_object_ptr<telegram_api::documentAttributeAudio> audio;
tl_object_ptr<telegram_api::documentAttributeSticker> sticker;
+ tl_object_ptr<telegram_api::documentAttributeCustomEmoji> custom_emoji;
Dimensions dimensions;
string file_name;
bool has_stickers = false;
@@ -71,7 +87,8 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
switch (attribute->get_id()) {
case telegram_api::documentAttributeImageSize::ID: {
auto image_size = move_tl_object_as<telegram_api::documentAttributeImageSize>(attribute);
- dimensions = get_dimensions(image_size->w_, image_size->h_);
+ dimensions =
+ get_dimensions(image_size->w_, image_size->h_, oneline(to_string(remote_document.document)).c_str());
break;
}
case telegram_api::documentAttributeAnimated::ID:
@@ -96,19 +113,47 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
case telegram_api::documentAttributeHasStickers::ID:
has_stickers = true;
break;
+ case telegram_api::documentAttributeCustomEmoji::ID:
+ custom_emoji = move_tl_object_as<telegram_api::documentAttributeCustomEmoji>(attribute);
+ type_attributes++;
+ break;
default:
UNREACHABLE();
}
}
int32 video_duration = 0;
+ string video_waveform;
if (video != nullptr) {
video_duration = video->duration_;
- if (dimensions.width == 0) {
- dimensions = get_dimensions(video->w_, video->h_);
+ auto video_dimensions = get_dimensions(video->w_, video->h_, "documentAttributeVideo");
+ if (dimensions.width == 0 || (video_dimensions.width != 0 && video_dimensions != dimensions)) {
+ if (dimensions.width != 0) {
+ LOG(ERROR) << "Receive ambiguous video dimensions " << dimensions << " and " << video_dimensions;
+ }
+ dimensions = video_dimensions;
+ }
+ if (audio != nullptr) {
+ video_waveform = audio->waveform_.as_slice().str();
+ type_attributes--;
+ audio = nullptr;
}
if (animated != nullptr) {
- // video animation
+ type_attributes--;
+ if ((video->flags_ & telegram_api::documentAttributeVideo::ROUND_MESSAGE_MASK) != 0) {
+ // video note without sound
+ animated = nullptr;
+ } else if (sticker != nullptr || custom_emoji != nullptr) {
+ // sticker
+ type_attributes--;
+ animated = nullptr;
+ video = nullptr;
+ } else {
+ // video animation
+ video = nullptr;
+ }
+ } else if (sticker != nullptr || custom_emoji != nullptr) {
+ // some stickers uploaded before release
type_attributes--;
video = nullptr;
}
@@ -123,41 +168,49 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
type_attributes--;
sticker = nullptr;
}
+ if (animated != nullptr && custom_emoji != nullptr) {
+ // just in case
+ type_attributes--;
+ custom_emoji = nullptr;
+ }
auto document_type = default_document_type;
FileType file_type = FileType::Document;
Slice default_extension;
bool supports_streaming = false;
- if (type_attributes == 1 || default_document_type != DocumentType::General) { // not a general document
- if (animated != nullptr || default_document_type == DocumentType::Animation) {
- document_type = DocumentType::Animation;
+ StickerFormat sticker_format = StickerFormat::Unknown;
+ PhotoFormat thumbnail_format = PhotoFormat::Jpeg;
+ if (type_attributes == 1 || default_document_type != Document::Type::General) { // not a general document
+ if (animated != nullptr || default_document_type == Document::Type::Animation) {
+ document_type = Document::Type::Animation;
file_type = FileType::Animation;
- default_extension = "mp4";
- } else if (audio != nullptr || default_document_type == DocumentType::Audio ||
- default_document_type == DocumentType::VoiceNote) {
- bool is_voice_note = default_document_type == DocumentType::VoiceNote;
+ default_extension = Slice("mp4");
+ } else if (audio != nullptr || default_document_type == Document::Type::Audio ||
+ default_document_type == Document::Type::VoiceNote) {
+ bool is_voice_note = default_document_type == Document::Type::VoiceNote;
if (audio != nullptr) {
- is_voice_note = (audio->flags_ & telegram_api::documentAttributeAudio::Flags::VOICE_MASK) != 0;
+ is_voice_note = (audio->flags_ & telegram_api::documentAttributeAudio::VOICE_MASK) != 0;
}
if (is_voice_note) {
- document_type = DocumentType::VoiceNote;
+ document_type = Document::Type::VoiceNote;
file_type = FileType::VoiceNote;
- default_extension = "oga";
+ default_extension = Slice("oga");
file_name.clear();
} else {
- document_type = DocumentType::Audio;
+ document_type = Document::Type::Audio;
file_type = FileType::Audio;
- default_extension = "mp3";
+ default_extension = Slice("mp3");
}
- } else if (sticker != nullptr || default_document_type == DocumentType::Sticker) {
- document_type = DocumentType::Sticker;
+ } else if (sticker != nullptr || custom_emoji != nullptr || default_document_type == Document::Type::Sticker) {
+ document_type = Document::Type::Sticker;
file_type = FileType::Sticker;
- default_extension = "webp";
+ sticker_format = StickerFormat::Webp;
+ default_extension = Slice("webp");
owner_dialog_id = DialogId();
file_name.clear();
- } else if (video != nullptr || default_document_type == DocumentType::Video ||
- default_document_type == DocumentType::VideoNote) {
- bool is_video_note = default_document_type == DocumentType::VideoNote;
+ } else if (video != nullptr || default_document_type == Document::Type::Video ||
+ default_document_type == Document::Type::VideoNote) {
+ bool is_video_note = default_document_type == Document::Type::VideoNote;
if (video != nullptr) {
is_video_note = (video->flags_ & telegram_api::documentAttributeVideo::ROUND_MESSAGE_MASK) != 0;
if (!is_video_note) {
@@ -165,32 +218,79 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
}
}
if (is_video_note) {
- document_type = DocumentType::VideoNote;
+ document_type = Document::Type::VideoNote;
file_type = FileType::VideoNote;
file_name.clear();
} else {
- document_type = DocumentType::Video;
+ document_type = Document::Type::Video;
file_type = FileType::Video;
}
- default_extension = "mp4";
+ default_extension = Slice("mp4");
}
} else if (type_attributes >= 2) {
LOG(WARNING) << "Receive document with more than 1 type attribute: animated = " << to_string(animated)
- << ", sticker = " << to_string(sticker) << ", video = " << to_string(video)
- << ", audio = " << to_string(audio) << ", file_name = " << file_name << ", dimensions = " << dimensions
+ << ", sticker = " << to_string(sticker) << ", custom_emoji = " << to_string(custom_emoji)
+ << ", video = " << to_string(video) << ", audio = " << to_string(audio)
+ << ", file_name = " << file_name << ", dimensions = " << dimensions
<< ", has_stickers = " << has_stickers;
}
+ if (is_background) {
+ if (document_type != Document::Type::General) {
+ LOG(ERROR) << "Receive background of type " << document_type;
+ document_type = Document::Type::General;
+ }
+ file_type = FileType::Background;
+ if (is_pattern) {
+ default_extension = Slice("png");
+ thumbnail_format = PhotoFormat::Png;
+ } else {
+ default_extension = Slice("jpg");
+ }
+ }
+
+ if (is_ringtone) {
+ if (document_type != Document::Type::Audio) {
+ LOG(ERROR) << "Receive notification tone of type " << document_type;
+ document_type = Document::Type::Audio;
+ }
+ file_type = FileType::Ringtone;
+ default_extension = Slice("mp3");
+ }
+
int64 id;
int64 access_hash;
int32 dc_id;
- int32 size;
+ int64 size;
+ int32 date = 0;
string mime_type;
+ string file_reference;
+ string minithumbnail;
PhotoSize thumbnail;
+ AnimationSize animated_thumbnail;
+ FileId premium_animation_file_id;
FileEncryptionKey encryption_key;
bool is_web = false;
bool is_web_no_proxy = false;
string url;
+ FileLocationSource source = FileLocationSource::FromServer;
+
+ auto fix_tgs_sticker_type = [&] {
+ if (mime_type != "application/x-tgsticker") {
+ return;
+ }
+
+ sticker_format = StickerFormat::Tgs;
+ default_extension = Slice("tgs");
+ if (document_type == Document::Type::General) {
+ document_type = Document::Type::Sticker;
+ file_type = FileType::Sticker;
+ owner_dialog_id = DialogId();
+ file_name.clear();
+ thumbnail_format = PhotoFormat::Webp;
+ }
+ };
+
if (remote_document.document != nullptr) {
auto document = std::move(remote_document.document);
@@ -198,11 +298,55 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
access_hash = document->access_hash_;
dc_id = document->dc_id_;
size = document->size_;
+ if (is_ringtone) {
+ date = document->date_;
+ }
mime_type = std::move(document->mime_type_);
+ file_reference = document->file_reference_.as_slice().str();
+
+ if (document_type == Document::Type::Sticker && StickersManager::has_webp_thumbnail(document->thumbs_)) {
+ thumbnail_format = PhotoFormat::Webp;
+ }
+ fix_tgs_sticker_type();
+
+ if (owner_dialog_id.get_type() == DialogType::SecretChat) {
+ // secret_api::decryptedMessageMediaExternalDocument
+ if (document_type != Document::Type::Sticker) {
+ LOG(ERROR) << "Receive " << document_type << " in " << owner_dialog_id;
+ return {};
+ }
+ source = FileLocationSource::FromUser;
+ }
- if (document_type != DocumentType::VoiceNote) {
- thumbnail = get_photo_size(td_->file_manager_.get(), FileType::Thumbnail, 0, 0, owner_dialog_id,
- std::move(document->thumb_));
+ if (document_type != Document::Type::VoiceNote) {
+ for (auto &thumb : document->thumbs_) {
+ auto photo_size = get_photo_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 0),
+ id, access_hash, file_reference, DcId::create(dc_id), owner_dialog_id,
+ std::move(thumb), thumbnail_format);
+ if (photo_size.get_offset() == 0) {
+ if (!thumbnail.file_id.is_valid()) {
+ thumbnail = std::move(photo_size.get<0>());
+ }
+ } else {
+ minithumbnail = std::move(photo_size.get<1>());
+ }
+ }
+ }
+ for (auto &thumb : document->video_thumbs_) {
+ if (thumb->type_ == "v") {
+ if (!animated_thumbnail.file_id.is_valid()) {
+ animated_thumbnail =
+ get_animation_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 0), id,
+ access_hash, file_reference, DcId::create(dc_id), owner_dialog_id, std::move(thumb));
+ }
+ } else if (thumb->type_ == "f") {
+ if (!premium_animation_file_id.is_valid()) {
+ premium_animation_file_id =
+ register_photo_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 'f'), id,
+ access_hash, file_reference, owner_dialog_id, thumb->size_, DcId::create(dc_id),
+ get_sticker_format_photo_format(sticker_format));
+ }
+ }
}
} else if (remote_document.secret_file != nullptr) {
CHECK(remote_document.secret_document != nullptr);
@@ -217,19 +361,29 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
file_type = FileType::Encrypted;
encryption_key = FileEncryptionKey{document->key_.as_slice(), document->iv_.as_slice()};
if (encryption_key.empty()) {
- return {DocumentType::Unknown, FileId()};
+ return {};
}
- if (document_type != DocumentType::VoiceNote) {
- thumbnail = get_thumbnail_photo_size(td_->file_manager_.get(), std::move(document->thumb_), owner_dialog_id,
- document->thumb_w_, document->thumb_h_);
+ // do not allow encrypted TGS stickers
+ // fix_tgs_sticker_type();
+
+ if (document_type != Document::Type::VoiceNote) {
+ thumbnail = get_secret_thumbnail_photo_size(td_->file_manager_.get(), std::move(document->thumb_),
+ owner_dialog_id, document->thumb_w_, document->thumb_h_);
}
} else {
is_web = true;
id = Random::fast(0, std::numeric_limits<int32>::max());
dc_id = 0;
access_hash = 0;
- thumbnail = std::move(remote_document.thumbnail);
+ if (remote_document.thumbnail.type == 'v') {
+ static_cast<PhotoSize &>(animated_thumbnail) = std::move(remote_document.thumbnail);
+ } else {
+ thumbnail = std::move(remote_document.thumbnail);
+ if (remote_document.thumbnail.type == 'g') {
+ thumbnail_format = PhotoFormat::Gif;
+ }
+ }
auto web_document_ptr = std::move(remote_document.web_document);
switch (web_document_ptr->get_id()) {
@@ -238,11 +392,10 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
auto r_http_url = parse_url(web_document->url_);
if (r_http_url.is_error()) {
LOG(ERROR) << "Can't parse URL " << web_document->url_;
- return {DocumentType::Unknown, FileId()};
+ return {};
}
auto http_url = r_http_url.move_as_ok();
- dc_id = web_document->dc_id_;
access_hash = web_document->access_hash_;
url = http_url.get_url();
file_name = get_url_query_file_name(http_url.query_);
@@ -256,7 +409,7 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
if (web_document->url_.find('.') == string::npos) {
LOG(ERROR) << "Receive invalid URL " << web_document->url_;
- return {DocumentType::Unknown, FileId()};
+ return {};
}
url = std::move(web_document->url_);
@@ -268,14 +421,27 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
default:
UNREACHABLE();
}
+
+ // do not allow web TGS stickers
+ // fix_tgs_sticker_type();
+ }
+ if (document_type == Document::Type::Sticker && mime_type == "video/webm") {
+ sticker_format = StickerFormat::Webm;
+ default_extension = Slice("webm");
+ }
+ if (file_type == FileType::Encrypted && document_type == Document::Type::Sticker &&
+ size > get_max_sticker_file_size(sticker_format, StickerType::Regular, false)) {
+ document_type = Document::Type::General;
}
- LOG(DEBUG) << "Receive document with id = " << id << " of type " << static_cast<int32>(document_type);
- if (!is_web_no_proxy && !DcId::is_valid(dc_id)) {
+ LOG(DEBUG) << "Receive document with ID = " << id << " of type " << document_type;
+ if (!is_web && !DcId::is_valid(dc_id)) {
LOG(ERROR) << "Wrong dc_id = " << dc_id;
- return {DocumentType::Unknown, FileId()};
+ return {};
}
+ file_name = strip_empty_characters(file_name, 255, true);
+
auto suggested_file_name = file_name;
if (suggested_file_name.empty()) {
suggested_file_name = to_string(static_cast<uint64>(id));
@@ -289,20 +455,19 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
FileId file_id;
if (!is_web) {
file_id = td_->file_manager_->register_remote(
- FullRemoteFileLocation(file_type, id, access_hash, DcId::internal(dc_id)), FileLocationSource::FromServer,
+ FullRemoteFileLocation(file_type, id, access_hash, DcId::internal(dc_id), std::move(file_reference)), source,
owner_dialog_id, size, 0, suggested_file_name);
if (!encryption_key.empty()) {
td_->file_manager_->set_encryption_key(file_id, std::move(encryption_key));
}
} else if (!is_web_no_proxy) {
- file_id =
- td_->file_manager_->register_remote(FullRemoteFileLocation(file_type, url, access_hash, DcId::internal(dc_id)),
- FileLocationSource::FromServer, owner_dialog_id, 0, size, file_name);
+ file_id = td_->file_manager_->register_remote(FullRemoteFileLocation(file_type, url, access_hash), source,
+ owner_dialog_id, 0, size, file_name);
} else {
auto r_file_id = td_->file_manager_->from_persistent_id(url, file_type);
if (r_file_id.is_error()) {
LOG(ERROR) << "Can't register URL: " << r_file_id.error();
- return {DocumentType::Unknown, FileId()};
+ return {};
}
file_id = r_file_id.move_as_ok();
}
@@ -316,48 +481,68 @@ std::pair<DocumentsManager::DocumentType, FileId> DocumentsManager::on_get_docum
}
switch (document_type) {
- case DocumentType::Animation:
- // TODO use has_stickers
- td_->animations_manager_->create_animation(file_id, std::move(thumbnail), std::move(file_name),
- std::move(mime_type), video_duration, dimensions, !is_web);
+ case Document::Type::Animation:
+ td_->animations_manager_->create_animation(
+ file_id, std::move(minithumbnail), std::move(thumbnail), std::move(animated_thumbnail), has_stickers,
+ vector<FileId>(), std::move(file_name), std::move(mime_type), video_duration, dimensions, !is_web);
break;
- case DocumentType::Audio:
- CHECK(audio != nullptr);
- td_->audios_manager_->create_audio(file_id, std::move(thumbnail), std::move(file_name), std::move(mime_type),
- audio->duration_, std::move(audio->title_), std::move(audio->performer_),
+ case Document::Type::Audio: {
+ int32 duration = 0;
+ string title;
+ string performer;
+ if (audio != nullptr) {
+ duration = audio->duration_;
+ title = std::move(audio->title_);
+ performer = std::move(audio->performer_);
+ }
+ td_->audios_manager_->create_audio(file_id, std::move(minithumbnail), std::move(thumbnail), std::move(file_name),
+ std::move(mime_type), duration, std::move(title), std::move(performer), date,
!is_web);
break;
- case DocumentType::General:
- td_->documents_manager_->create_document(file_id, std::move(thumbnail), std::move(file_name),
- std::move(mime_type), !is_web);
+ }
+ case Document::Type::General:
+ create_document(file_id, std::move(minithumbnail), std::move(thumbnail), std::move(file_name),
+ std::move(mime_type), !is_web);
break;
- case DocumentType::Sticker:
- CHECK(sticker != nullptr);
- td_->stickers_manager_->create_sticker(file_id, std::move(thumbnail), dimensions, true, std::move(sticker),
- load_data_multipromise_ptr);
+ case Document::Type::Sticker:
+ if (thumbnail_format == PhotoFormat::Jpeg) {
+ minithumbnail = string();
+ }
+ td_->stickers_manager_->create_sticker(file_id, premium_animation_file_id, std::move(minithumbnail),
+ std::move(thumbnail), dimensions, std::move(sticker),
+ std::move(custom_emoji), sticker_format, load_data_multipromise_ptr);
break;
- case DocumentType::Video:
- td_->videos_manager_->create_video(file_id, std::move(thumbnail), has_stickers, vector<FileId>(),
+ case Document::Type::Video:
+ td_->videos_manager_->create_video(file_id, std::move(minithumbnail), std::move(thumbnail),
+ std::move(animated_thumbnail), has_stickers, vector<FileId>(),
std::move(file_name), std::move(mime_type), video_duration, dimensions,
supports_streaming, !is_web);
break;
- case DocumentType::VideoNote:
- td_->video_notes_manager_->create_video_note(file_id, std::move(thumbnail), video_duration, dimensions, !is_web);
+ case Document::Type::VideoNote:
+ td_->video_notes_manager_->create_video_note(file_id, std::move(minithumbnail), std::move(thumbnail),
+ video_duration, dimensions, std::move(video_waveform), !is_web);
break;
- case DocumentType::VoiceNote:
- CHECK(audio != nullptr);
- td_->voice_notes_manager_->create_voice_note(file_id, std::move(mime_type), audio->duration_,
- audio->waveform_.as_slice().str(), !is_web);
+ case Document::Type::VoiceNote: {
+ int32 duration = 0;
+ string waveform;
+ if (audio != nullptr) {
+ duration = audio->duration_;
+ waveform = audio->waveform_.as_slice().str();
+ }
+ td_->voice_notes_manager_->create_voice_note(file_id, std::move(mime_type), duration, std::move(waveform),
+ !is_web);
break;
- case DocumentType::Unknown:
+ }
+ case Document::Type::Unknown:
default:
UNREACHABLE();
}
return {document_type, file_id};
}
-FileId DocumentsManager::on_get_document(std::unique_ptr<Document> new_document, bool replace) {
+FileId DocumentsManager::on_get_document(unique_ptr<GeneralDocument> new_document, bool replace) {
auto file_id = new_document->file_id;
+ CHECK(file_id.is_valid());
LOG(INFO) << "Receive document " << file_id;
auto &d = documents_[new_document->file_id];
if (d == nullptr) {
@@ -366,13 +551,14 @@ FileId DocumentsManager::on_get_document(std::unique_ptr<Document> new_document,
CHECK(d->file_id == new_document->file_id);
if (d->mime_type != new_document->mime_type) {
LOG(DEBUG) << "Document " << file_id << " mime_type has changed";
- d->mime_type = new_document->mime_type;
- d->is_changed = true;
+ d->mime_type = std::move(new_document->mime_type);
}
if (d->file_name != new_document->file_name) {
LOG(DEBUG) << "Document " << file_id << " file_name has changed";
- d->file_name = new_document->file_name;
- d->is_changed = true;
+ d->file_name = std::move(new_document->file_name);
+ }
+ if (d->minithumbnail != new_document->minithumbnail) {
+ d->minithumbnail = std::move(new_document->minithumbnail);
}
if (d->thumbnail != new_document->thumbnail) {
if (!d->thumbnail.file_id.is_valid()) {
@@ -381,38 +567,34 @@ FileId DocumentsManager::on_get_document(std::unique_ptr<Document> new_document,
LOG(INFO) << "Document " << file_id << " thumbnail has changed from " << d->thumbnail << " to "
<< new_document->thumbnail;
}
- d->thumbnail = new_document->thumbnail;
- d->is_changed = true;
+ d->thumbnail = std::move(new_document->thumbnail);
}
}
return file_id;
}
-void DocumentsManager::create_document(FileId file_id, PhotoSize thumbnail, string file_name, string mime_type,
- bool replace) {
- auto d = make_unique<Document>();
+void DocumentsManager::create_document(FileId file_id, string minithumbnail, PhotoSize thumbnail, string file_name,
+ string mime_type, bool replace) {
+ auto d = make_unique<GeneralDocument>();
d->file_id = file_id;
d->file_name = std::move(file_name);
d->mime_type = std::move(mime_type);
+ if (!td_->auth_manager_->is_bot()) {
+ d->minithumbnail = std::move(minithumbnail);
+ }
d->thumbnail = std::move(thumbnail);
on_get_document(std::move(d), replace);
}
-const DocumentsManager::Document *DocumentsManager::get_document(FileId file_id) const {
- auto document = documents_.find(file_id);
- if (document == documents_.end()) {
- return nullptr;
- }
-
- CHECK(document->second->file_id == file_id);
- return document->second.get();
+const DocumentsManager::GeneralDocument *DocumentsManager::get_document(FileId file_id) const {
+ return documents_.get_pointer(file_id);
}
bool DocumentsManager::has_input_media(FileId file_id, FileId thumbnail_file_id, bool is_secret) const {
auto file_view = td_->file_manager_->get_file_view(file_id);
if (is_secret) {
- if (file_view.encryption_key().empty() || !file_view.has_remote_location()) {
+ if (!file_view.is_encrypted_secret() || file_view.encryption_key().empty() || !file_view.has_remote_location()) {
return false;
}
@@ -421,22 +603,28 @@ bool DocumentsManager::has_input_media(FileId file_id, FileId thumbnail_file_id,
if (file_view.is_encrypted()) {
return false;
}
- return file_view.has_remote_location() || file_view.has_url();
+ if (td_->auth_manager_->is_bot() && file_view.has_remote_location()) {
+ return true;
+ }
+ // having remote location is not enough to have InputMedia, because the file may not have valid file_reference
+ // also file_id needs to be duped, because upload can be called to repair the file_reference and every upload
+ // request must have unique file_id
+ return /* file_view.has_remote_location() || */ file_view.has_url();
}
}
SecretInputMedia DocumentsManager::get_secret_input_media(FileId document_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption, BufferSlice thumbnail) const {
- const Document *document = get_document(document_file_id);
+ const string &caption, BufferSlice thumbnail,
+ int32 layer) const {
+ const GeneralDocument *document = get_document(document_file_id);
CHECK(document != nullptr);
auto file_view = td_->file_manager_->get_file_view(document_file_id);
- auto &encryption_key = file_view.encryption_key();
- if (encryption_key.empty()) {
+ if (!file_view.is_encrypted_secret() || file_view.encryption_key().empty()) {
return SecretInputMedia{};
}
if (file_view.has_remote_location()) {
- input_file = file_view.remote_location().as_input_encrypted_file();
+ input_file = file_view.main_remote_location().as_input_encrypted_file();
}
if (!input_file) {
return SecretInputMedia{};
@@ -445,15 +633,17 @@ SecretInputMedia DocumentsManager::get_secret_input_media(FileId document_file_i
return SecretInputMedia{};
}
vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
- if (document->file_name.size()) {
+ if (!document->file_name.empty()) {
attributes.push_back(make_tl_object<secret_api::documentAttributeFilename>(document->file_name));
}
- return SecretInputMedia{
- std::move(input_file),
- make_tl_object<secret_api::decryptedMessageMediaDocument>(
- std::move(thumbnail), document->thumbnail.dimensions.width, document->thumbnail.dimensions.height,
- document->mime_type, narrow_cast<int32>(file_view.size()), BufferSlice(encryption_key.key_slice()),
- BufferSlice(encryption_key.iv_slice()), std::move(attributes), caption)};
+ return {std::move(input_file),
+ std::move(thumbnail),
+ document->thumbnail.dimensions,
+ document->mime_type,
+ file_view,
+ std::move(attributes),
+ caption,
+ layer};
}
tl_object_ptr<telegram_api::InputMedia> DocumentsManager::get_input_media(
@@ -463,29 +653,34 @@ tl_object_ptr<telegram_api::InputMedia> DocumentsManager::get_input_media(
if (file_view.is_encrypted()) {
return nullptr;
}
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
- return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.remote_location().as_input_document(), 0);
+ if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
+ return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.main_remote_location().as_input_document(), 0,
+ string());
}
if (file_view.has_url()) {
return make_tl_object<telegram_api::inputMediaDocumentExternal>(0, file_view.url(), 0);
}
- CHECK(!file_view.has_remote_location());
-
- const Document *document = get_document(file_id);
- CHECK(document != nullptr);
if (input_file != nullptr) {
+ const GeneralDocument *document = get_document(file_id);
+ CHECK(document != nullptr);
+
vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
- if (document->file_name.size()) {
+ if (!document->file_name.empty()) {
attributes.push_back(make_tl_object<telegram_api::documentAttributeFilename>(document->file_name));
}
int32 flags = 0;
if (input_thumbnail != nullptr) {
flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK;
}
+ if (file_view.get_type() == FileType::DocumentAsFile) {
+ flags |= telegram_api::inputMediaUploadedDocument::FORCE_FILE_MASK;
+ }
return make_tl_object<telegram_api::inputMediaUploadedDocument>(
- flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), document->mime_type,
- std::move(attributes), vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_file), std::move(input_thumbnail),
+ document->mime_type, std::move(attributes), vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ } else {
+ CHECK(!file_view.has_remote_location());
}
return nullptr;
@@ -504,54 +699,33 @@ void DocumentsManager::delete_document_thumbnail(FileId file_id) {
}
FileId DocumentsManager::dup_document(FileId new_id, FileId old_id) {
- const Document *old_document = get_document(old_id);
+ const GeneralDocument *old_document = get_document(old_id);
CHECK(old_document != nullptr);
auto &new_document = documents_[new_id];
- CHECK(!new_document);
- new_document = std::make_unique<Document>(*old_document);
+ CHECK(new_document == nullptr);
+ new_document = make_unique<GeneralDocument>(*old_document);
new_document->file_id = new_id;
- new_document->thumbnail.file_id = td_->file_manager_->dup_file_id(new_document->thumbnail.file_id);
+ new_document->thumbnail.file_id = td_->file_manager_->dup_file_id(new_document->thumbnail.file_id, "dup_document");
return new_id;
}
-bool DocumentsManager::merge_documents(FileId new_id, FileId old_id, bool can_delete_old) {
- if (!old_id.is_valid()) {
- LOG(ERROR) << "Old file id is invalid";
- return true;
- }
+void DocumentsManager::merge_documents(FileId new_id, FileId old_id) {
+ CHECK(old_id.is_valid() && new_id.is_valid());
+ CHECK(new_id != old_id);
LOG(INFO) << "Merge documents " << new_id << " and " << old_id;
- const Document *old_ = get_document(old_id);
+ const GeneralDocument *old_ = get_document(old_id);
CHECK(old_ != nullptr);
- if (old_id == new_id) {
- return old_->is_changed;
- }
- auto new_it = documents_.find(new_id);
- if (new_it == documents_.end()) {
- auto &old = documents_[old_id];
- old->is_changed = true;
- if (!can_delete_old) {
- dup_document(new_id, old_id);
- } else {
- old->file_id = new_id;
- documents_.emplace(new_id, std::move(old));
- }
+ const auto *new_ = get_document(new_id);
+ if (new_ == nullptr) {
+ dup_document(new_id, old_id);
} else {
- Document *new_ = new_it->second.get();
- CHECK(new_ != nullptr);
-
if (old_->thumbnail != new_->thumbnail) {
- // LOG_STATUS(td_->file_manager_->merge(new_->thumbnail.file_id, old_->thumbnail.file_id));
+ // LOG_STATUS(td_->file_manager_->merge(new_->thumbnail.file_id, old_->thumbnail.file_id));
}
-
- new_->is_changed = true;
}
LOG_STATUS(td_->file_manager_->merge(new_id, old_id));
- if (can_delete_old) {
- documents_.erase(old_id);
- }
- return true;
}
string DocumentsManager::get_document_search_text(FileId file_id) const {
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.h b/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.h
index edbc02d657..a835b10348 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.h
@@ -1,44 +1,47 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
#include "td/telegram/DialogId.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/EncryptedFile.h"
#include "td/telegram/files/FileId.h"
-#include "td/telegram/Photo.h"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/secret_api.h"
#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/WaitFreeHashMap.h"
-#include <unordered_map>
#include <utility>
namespace td {
+
class MultiPromiseActor;
class Td;
-} // namespace td
-
-namespace td {
class DocumentsManager {
public:
explicit DocumentsManager(Td *td);
-
- enum class DocumentType { Unknown, Animation, Audio, General, Sticker, Video, VideoNote, VoiceNote };
+ DocumentsManager(const DocumentsManager &) = delete;
+ DocumentsManager &operator=(const DocumentsManager &) = delete;
+ DocumentsManager(DocumentsManager &&) = delete;
+ DocumentsManager &operator=(DocumentsManager &&) = delete;
+ ~DocumentsManager();
class RemoteDocument {
public:
tl_object_ptr<telegram_api::document> document;
// or
- tl_object_ptr<telegram_api::encryptedFile> secret_file;
+ unique_ptr<EncryptedFile> secret_file;
tl_object_ptr<secret_api::decryptedMessageMediaDocument> secret_document;
// or
tl_object_ptr<telegram_api::WebDocument> web_document;
@@ -65,7 +68,7 @@ class DocumentsManager {
, attributes(std::move(attributes)) {
}
- RemoteDocument(tl_object_ptr<telegram_api::encryptedFile> &&secret_file,
+ RemoteDocument(unique_ptr<EncryptedFile> &&secret_file,
tl_object_ptr<secret_api::decryptedMessageMediaDocument> &&secret_document,
vector<tl_object_ptr<telegram_api::DocumentAttribute>> &&attributes)
: document(nullptr)
@@ -77,19 +80,21 @@ class DocumentsManager {
}
};
- tl_object_ptr<td_api::document> get_document_object(FileId file_id);
+ tl_object_ptr<td_api::document> get_document_object(FileId file_id, PhotoFormat thumbnail_format) const;
- std::pair<DocumentType, FileId> on_get_document(RemoteDocument remote_document, DialogId owner_dialog_id,
- MultiPromiseActor *load_data_multipromise_ptr = nullptr,
- DocumentType default_document_type = DocumentType::General);
+ Document on_get_document(RemoteDocument remote_document, DialogId owner_dialog_id,
+ MultiPromiseActor *load_data_multipromise_ptr = nullptr,
+ Document::Type default_document_type = Document::Type::General, bool is_background = false,
+ bool is_pattern = false, bool is_ringtone = false);
- void create_document(FileId file_id, PhotoSize thumbnail, string file_name, string mime_type, bool replace);
+ void create_document(FileId file_id, string minithumbnail, PhotoSize thumbnail, string file_name, string mime_type,
+ bool replace);
bool has_input_media(FileId file_id, FileId thumbnail_file_id, bool is_secret) const;
SecretInputMedia get_secret_input_media(FileId document_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption, BufferSlice thumbnail) const;
+ const string &caption, BufferSlice thumbnail, int32 layer) const;
tl_object_ptr<telegram_api::InputMedia> get_input_media(FileId file_id,
tl_object_ptr<telegram_api::InputFile> input_file,
@@ -101,33 +106,32 @@ class DocumentsManager {
FileId dup_document(FileId new_id, FileId old_id);
- bool merge_documents(FileId new_id, FileId old_id, bool can_delete_old);
+ void merge_documents(FileId new_id, FileId old_id);
- template <class T>
- void store_document(FileId file_id, T &storer) const;
+ template <class StorerT>
+ void store_document(FileId file_id, StorerT &storer) const;
- template <class T>
- FileId parse_document(T &parser);
+ template <class ParserT>
+ FileId parse_document(ParserT &parser);
string get_document_search_text(FileId file_id) const;
private:
- class Document {
+ class GeneralDocument {
public:
string file_name;
string mime_type;
+ string minithumbnail;
PhotoSize thumbnail;
FileId file_id;
-
- bool is_changed = true;
};
- const Document *get_document(FileId file_id) const;
+ const GeneralDocument *get_document(FileId file_id) const;
- FileId on_get_document(std::unique_ptr<Document> new_document, bool replace);
+ FileId on_get_document(unique_ptr<GeneralDocument> new_document, bool replace);
Td *td_;
- std::unordered_map<FileId, unique_ptr<Document>, FileIdHash> documents_; // file_id -> Document
+ WaitFreeHashMap<FileId, unique_ptr<GeneralDocument>, FileIdHash> documents_; // file_id -> GeneralDocument
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.hpp
index 71b45cf274..c7b15700c1 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/DocumentsManager.hpp
@@ -1,41 +1,46 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/telegram/DocumentsManager.h"
#include "td/telegram/files/FileId.hpp"
-#include "td/telegram/Photo.hpp"
+#include "td/telegram/PhotoSize.hpp"
+#include "td/telegram/Version.h"
-#include "td/utils/logging.h"
#include "td/utils/tl_helpers.h"
namespace td {
-template <class T>
-void DocumentsManager::store_document(FileId file_id, T &storer) const {
- LOG(DEBUG) << "Store document " << file_id;
- auto it = documents_.find(file_id);
- CHECK(it != documents_.end());
- const Document *document = it->second.get();
+template <class StorerT>
+void DocumentsManager::store_document(FileId file_id, StorerT &storer) const {
+ const GeneralDocument *document = get_document(file_id);
+ CHECK(document != nullptr);
store(document->file_name, storer);
store(document->mime_type, storer);
+ store(document->minithumbnail, storer);
store(document->thumbnail, storer);
store(file_id, storer);
}
-template <class T>
-FileId DocumentsManager::parse_document(T &parser) {
- auto document = make_unique<Document>();
+template <class ParserT>
+FileId DocumentsManager::parse_document(ParserT &parser) {
+ auto document = make_unique<GeneralDocument>();
parse(document->file_name, parser);
parse(document->mime_type, parser);
+ if (parser.version() >= static_cast<int32>(Version::SupportMinithumbnails)) {
+ parse(document->minithumbnail, parser);
+ }
parse(document->thumbnail, parser);
parse(document->file_id, parser);
- LOG(DEBUG) << "Parsed document " << document->file_id;
- return on_get_document(std::move(document), true);
+ if (parser.get_error() != nullptr || !document->file_id.is_valid()) {
+ return FileId();
+ }
+ return on_get_document(std::move(document), false);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DownloadManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/DownloadManager.cpp
new file mode 100644
index 0000000000..864f1838e0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DownloadManager.cpp
@@ -0,0 +1,840 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DownloadManager.h"
+
+#include "td/telegram/files/FileId.hpp"
+#include "td/telegram/files/FileSourceId.hpp"
+#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+
+#include "td/actor/MultiPromise.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/Hints.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+#include "td/utils/tl_helpers.h"
+
+#include <algorithm>
+#include <functional>
+#include <limits>
+#include <set>
+
+namespace td {
+
+struct FileDownloadInDatabase {
+ int64 download_id{};
+ FileId file_id;
+ FileSourceId file_source_id;
+ int32 priority{};
+ int32 created_at{};
+ int32 completed_at{};
+ bool is_paused{};
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_paused);
+ END_STORE_FLAGS();
+ td::store(download_id, storer);
+ td::store(file_id, storer);
+ td::store(file_source_id, storer);
+ td::store(priority, storer);
+ td::store(created_at, storer);
+ td::store(completed_at, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_paused);
+ END_PARSE_FLAGS();
+ td::parse(download_id, parser);
+ td::parse(file_id, parser);
+ td::parse(file_source_id, parser);
+ td::parse(priority, parser);
+ td::parse(created_at, parser);
+ td::parse(completed_at, parser);
+ }
+};
+
+class DownloadManagerImpl final : public DownloadManager {
+ public:
+ explicit DownloadManagerImpl(unique_ptr<Callback> callback) : callback_(std::move(callback)) {
+ }
+
+ void start_up() final {
+ init();
+ }
+
+ void after_get_difference() final {
+ load_database_files("after_get_difference");
+ }
+
+ void toggle_is_paused(FileId file_id, bool is_paused, Promise<Unit> promise) final {
+ TRY_STATUS_PROMISE(promise, check_is_active("toggle_is_paused"));
+ TRY_RESULT_PROMISE(promise, file_info_ptr, get_file_info(file_id));
+ toggle_is_paused(*file_info_ptr, is_paused);
+ promise.set_value(Unit());
+ }
+
+ void toggle_all_is_paused(bool is_paused, Promise<Unit> promise) final {
+ TRY_STATUS_PROMISE(promise, check_is_active("toggle_all_is_paused"));
+
+ vector<FileId> to_toggle;
+ for (auto &it : files_) {
+ FileInfo &file_info = *it.second;
+ if (!is_completed(file_info) && is_paused != file_info.is_paused) {
+ to_toggle.push_back(file_info.file_id);
+ }
+ }
+ for (auto file_id : to_toggle) {
+ auto r_file_info_ptr = get_file_info(file_id);
+ if (r_file_info_ptr.is_ok()) {
+ toggle_is_paused(*r_file_info_ptr.ok(), is_paused);
+ }
+ }
+
+ promise.set_value(Unit());
+ }
+
+ void remove_file(FileId file_id, FileSourceId file_source_id, bool delete_from_cache, Promise<Unit> promise) final {
+ promise.set_result(remove_file_impl(file_id, file_source_id, delete_from_cache, "remove_file"));
+ }
+
+ void remove_file_if_finished(FileId file_id) final {
+ remove_file_if_finished_impl(file_id).ignore();
+ }
+
+ void remove_all_files(bool only_active, bool only_completed, bool delete_from_cache, Promise<Unit> promise) final {
+ TRY_STATUS_PROMISE(promise, check_is_active("remove_all_files"));
+ vector<FileId> to_remove;
+ for (auto &it : files_) {
+ FileInfo &file_info = *it.second;
+ if (only_active && is_completed(file_info)) {
+ continue;
+ }
+ if (only_completed && !is_completed(file_info)) {
+ continue;
+ }
+ to_remove.push_back(file_info.file_id);
+ }
+ for (auto file_id : to_remove) {
+ remove_file_impl(file_id, {}, delete_from_cache, "remove_all_files");
+ }
+ promise.set_value(Unit());
+ }
+
+ void add_file(FileId file_id, FileSourceId file_source_id, string search_text, int8 priority,
+ Promise<td_api::object_ptr<td_api::file>> promise) final {
+ TRY_STATUS_PROMISE(promise, check_is_active("add_file"));
+
+ remove_file_impl(file_id, {}, false, "add_file");
+
+ auto download_id = next_download_id();
+
+ auto file_info = make_unique<FileInfo>();
+ file_info->download_id = download_id;
+ file_info->file_id = file_id;
+ file_info->file_source_id = file_source_id;
+ file_info->is_paused = false;
+ file_info->priority = priority;
+ file_info->created_at = G()->unix_time();
+ file_info->need_save_to_database = true;
+
+ add_file_info(std::move(file_info), search_text);
+
+ promise.set_value(callback_->get_file_object(file_id));
+ }
+
+ void change_search_text(FileId file_id, FileSourceId file_source_id, string search_text) final {
+ if (!is_search_inited_) {
+ return;
+ }
+
+ if (check_is_active("change_search_text").is_error()) {
+ return;
+ }
+ auto r_file_info_ptr = get_file_info(file_id, file_source_id);
+ if (r_file_info_ptr.is_error()) {
+ return;
+ }
+ auto &file_info = *r_file_info_ptr.ok();
+ hints_.add(file_info.download_id, search_text.empty() ? string(" ") : search_text);
+ }
+
+ void hints_synchronized(Result<Unit>) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "DownloadManager: hints are synchronized";
+ is_search_inited_ = true;
+ }
+
+ void search(string query, bool only_active, bool only_completed, string offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::foundFileDownloads>> promise) final {
+ return do_search(std::move(query), only_active, only_completed, std::move(offset), limit, std::move(promise),
+ Unit{});
+ }
+
+ void do_search(string query, bool only_active, bool only_completed, string offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::foundFileDownloads>> promise, Result<Unit>) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_STATUS_PROMISE(promise, check_is_active("do_search"));
+
+ if (!is_search_inited_) {
+ Promise<Unit> lock;
+ if (load_search_text_multipromise_.promise_count() == 0) {
+ load_search_text_multipromise_.add_promise(
+ promise_send_closure(actor_id(this), &DownloadManagerImpl::hints_synchronized));
+ load_search_text_multipromise_.set_ignore_errors(true);
+ lock = load_search_text_multipromise_.get_promise();
+ prepare_hints();
+ }
+ load_search_text_multipromise_.add_promise(promise_send_closure(actor_id(this), &DownloadManagerImpl::do_search,
+ std::move(query), only_active, only_completed,
+ std::move(offset), limit, std::move(promise)));
+ lock.set_value(Unit());
+ return;
+ }
+
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Limit must be positive"));
+ }
+ int64 offset_int64 = std::numeric_limits<int64>::max();
+ if (!offset.empty()) {
+ auto r_offset = to_integer_safe<int64>(offset);
+ if (r_offset.is_error()) {
+ return promise.set_error(Status::Error(400, "Invalid offset"));
+ }
+ offset_int64 = r_offset.move_as_ok();
+ }
+ auto download_ids = hints_.search(query, 10000, true).second;
+ FileCounters counters;
+ td::remove_if(download_ids, [&](int64 download_id) {
+ auto r = get_file_info(download_id);
+ CHECK(r.is_ok());
+ auto &file_info = *r.ok();
+ if (is_completed(file_info)) {
+ counters.completed_count++;
+ if (only_active) {
+ return true;
+ }
+ } else {
+ counters.active_count++;
+ if (file_info.is_paused) {
+ counters.paused_count++;
+ }
+ if (only_completed) {
+ return true;
+ }
+ }
+ if (download_id >= offset_int64) {
+ return true;
+ }
+ return false;
+ });
+ std::sort(download_ids.begin(), download_ids.end(), std::greater<>());
+ if (static_cast<int32>(download_ids.size()) > limit) {
+ download_ids.resize(limit);
+ }
+ auto file_downloads = transform(download_ids, [&](int64 download_id) {
+ on_file_viewed(download_id);
+
+ auto it = files_.find(download_id);
+ CHECK(it != files_.end());
+ const FileInfo &file_info = *it->second;
+ return callback_->get_file_download_object(file_info.file_id, file_info.file_source_id, file_info.created_at,
+ file_info.completed_at, file_info.is_paused);
+ });
+ td::remove_if(file_downloads, [](const auto &file_download) { return file_download->message_ == nullptr; });
+ string next_offset;
+ if (!download_ids.empty()) {
+ next_offset = to_string(download_ids.back());
+ }
+ promise.set_value(td_api::make_object<td_api::foundFileDownloads>(counters.get_downloaded_file_counts_object(),
+ std::move(file_downloads), next_offset));
+ }
+
+ void update_file_download_state(FileId internal_file_id, int64 downloaded_size, int64 size, int64 expected_size,
+ bool is_paused) final {
+ if (!callback_ || !is_database_loaded_) {
+ return;
+ }
+ LOG(INFO) << "Update file download state for file " << internal_file_id << " of size " << size << '/'
+ << expected_size << " to downloaded_size = " << downloaded_size << " and is_paused = " << is_paused;
+ auto r_file_info_ptr = get_file_info_by_internal(internal_file_id);
+ if (r_file_info_ptr.is_error()) {
+ return;
+ }
+ auto &file_info = *r_file_info_ptr.ok();
+ if (file_info.link_token != get_link_token()) {
+ LOG(INFO) << "Ignore update_file_download_state because of outdated link_token";
+ return;
+ }
+
+ bool need_update = false;
+ with_file_info(file_info, [&](FileInfo &file_info) {
+ file_info.size = size;
+ file_info.expected_size = expected_size;
+ file_info.downloaded_size = downloaded_size;
+ if (is_paused && file_info.is_paused != is_paused) {
+ file_info.is_paused = is_paused;
+ file_info.need_save_to_database = true;
+ need_update = true;
+ }
+ });
+ if (is_search_inited_ && need_update) {
+ callback_->update_file_changed(file_info.file_id, file_info.completed_at, file_info.is_paused, file_counters_);
+ }
+ }
+
+ void update_file_viewed(FileId file_id, FileSourceId file_source_id) final {
+ if (unviewed_completed_download_ids_.empty() || !callback_ || !is_database_loaded_) {
+ return;
+ }
+
+ LOG(INFO) << "File " << file_id << " was viewed from " << file_source_id;
+ auto r_file_info_ptr = get_file_info(file_id, file_source_id);
+ if (r_file_info_ptr.is_error()) {
+ return;
+ }
+ auto &file_info = *r_file_info_ptr.ok();
+ on_file_viewed(file_info.download_id);
+ }
+
+ private:
+ unique_ptr<Callback> callback_;
+ struct FileInfo {
+ int64 download_id{};
+ FileId file_id;
+ FileId internal_file_id;
+ FileSourceId file_source_id;
+ int8 priority{};
+ bool is_paused{};
+ bool is_counted{};
+ mutable bool is_registered{};
+ mutable bool need_save_to_database{};
+ int64 size{};
+ int64 expected_size{};
+ int64 downloaded_size{};
+ int32 created_at{};
+ int32 completed_at{};
+ uint64 link_token{};
+ };
+
+ FlatHashMap<FileId, int64, FileIdHash> by_file_id_;
+ FlatHashMap<FileId, int64, FileIdHash> by_internal_file_id_;
+ FlatHashMap<int64, unique_ptr<FileInfo>> files_;
+ std::set<int64> completed_download_ids_;
+ FlatHashSet<int64> unviewed_completed_download_ids_;
+ Hints hints_;
+
+ Counters counters_;
+ Counters sent_counters_;
+ FileCounters file_counters_;
+ const char *database_loading_source_ = nullptr;
+ bool is_inited_{false};
+ bool is_database_loaded_{false};
+ bool is_search_inited_{false};
+ int64 max_download_id_{0};
+ uint64 last_link_token_{0};
+ MultiPromiseActor load_search_text_multipromise_{"LoadFileSearchTextMultiPromiseActor"};
+
+ int64 next_download_id() {
+ return ++max_download_id_;
+ }
+
+ static bool is_completed(const FileInfo &file_info) {
+ return file_info.completed_at != 0;
+ }
+
+ static int64 get_file_size(const FileInfo &file_info) {
+ return file_info.size == 0 ? max(file_info.downloaded_size + 1, file_info.expected_size) : file_info.size;
+ }
+
+ static bool is_database_enabled() {
+ return G()->parameters().use_message_db;
+ }
+
+ static string pmc_key(const FileInfo &file_info) {
+ return PSTRING() << "dlds#" << file_info.download_id;
+ }
+
+ void sync_with_database(const FileInfo &file_info) {
+ if (!file_info.need_save_to_database) {
+ return;
+ }
+ file_info.need_save_to_database = false;
+
+ if (!is_database_enabled()) {
+ return;
+ }
+
+ LOG(INFO) << "Saving to download database file " << file_info.file_id << '/' << file_info.internal_file_id
+ << " with is_paused = " << file_info.is_paused;
+ FileDownloadInDatabase to_save;
+ to_save.download_id = file_info.download_id;
+ to_save.file_source_id = file_info.file_source_id;
+ to_save.is_paused = file_info.is_paused;
+ to_save.priority = file_info.priority;
+ to_save.created_at = file_info.created_at;
+ to_save.completed_at = file_info.completed_at;
+ to_save.file_id = file_info.file_id;
+ G()->td_db()->get_binlog_pmc()->set(pmc_key(file_info), log_event_store(to_save).as_slice().str());
+ }
+
+ static void remove_from_database(const FileInfo &file_info) {
+ if (!is_database_enabled()) {
+ return;
+ }
+
+ G()->td_db()->get_binlog_pmc()->erase(pmc_key(file_info));
+ }
+
+ void init() {
+ if (is_inited_) {
+ return;
+ }
+
+ if (is_database_enabled()) {
+ auto serialized_counter = G()->td_db()->get_binlog_pmc()->get("dlds_counter");
+ if (!serialized_counter.empty()) {
+ log_event_parse(sent_counters_, serialized_counter).ensure();
+ if (sent_counters_.downloaded_size == sent_counters_.total_size || sent_counters_.total_size == 0) {
+ G()->td_db()->get_binlog_pmc()->erase("dlds_counter");
+ sent_counters_ = Counters();
+ }
+ }
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase("dlds_counter");
+ G()->td_db()->get_binlog_pmc()->erase_by_prefix("dlds#");
+ }
+
+ callback_->update_counters(sent_counters_);
+ is_inited_ = true;
+ }
+
+ void add_file_from_database(FileDownloadInDatabase in_db) {
+ if (!in_db.file_id.is_valid() || !in_db.file_source_id.is_valid()) {
+ LOG(INFO) << "Skip adding file " << in_db.file_id << " from " << in_db.file_source_id;
+ return;
+ }
+ if (by_file_id_.count(in_db.file_id) != 0) {
+ // file has already been added
+ return;
+ }
+
+ auto file_info = make_unique<FileInfo>();
+ file_info->download_id = in_db.download_id;
+ file_info->file_id = in_db.file_id;
+ file_info->file_source_id = in_db.file_source_id;
+ file_info->is_paused = in_db.is_paused;
+ file_info->priority = narrow_cast<int8>(in_db.priority);
+ file_info->completed_at = in_db.completed_at;
+ file_info->created_at = in_db.created_at;
+
+ add_file_info(std::move(file_info), "");
+ }
+
+ void load_database_files(const char *source) {
+ if (is_database_loaded_) {
+ return;
+ }
+
+ if (!is_database_enabled()) {
+ is_database_loaded_ = true;
+ return;
+ }
+ CHECK(is_inited_);
+ LOG_CHECK(database_loading_source_ == nullptr) << database_loading_source_ << ' ' << source;
+ database_loading_source_ = source;
+
+ LOG(INFO) << "Start Download Manager database loading";
+
+ auto downloads_in_kv = G()->td_db()->get_binlog_pmc()->prefix_get("dlds#");
+ for (auto &it : downloads_in_kv) {
+ Slice key = it.first;
+ Slice value = it.second;
+ FileDownloadInDatabase in_db;
+ log_event_parse(in_db, value).ensure();
+ CHECK(in_db.download_id == to_integer_safe<int64>(key).ok());
+ max_download_id_ = max(in_db.download_id, max_download_id_);
+ add_file_from_database(in_db);
+ }
+
+ is_database_loaded_ = true;
+ database_loading_source_ = nullptr;
+ update_counters();
+ check_completed_downloads_size();
+
+ LOG(INFO) << "Finish Download Manager database loading";
+ }
+
+ void prepare_hints() {
+ for (auto &it : files_) {
+ const auto &file_info = *it.second;
+ auto promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), promise = load_search_text_multipromise_.get_promise(),
+ download_id = it.first](Result<string> r_search_text) mutable {
+ send_closure(actor_id, &DownloadManagerImpl::add_download_to_hints, download_id, std::move(r_search_text),
+ std::move(promise));
+ });
+ callback_->get_file_search_text(file_info.file_id, file_info.file_source_id, std::move(promise));
+ }
+ }
+
+ void add_download_to_hints(int64 download_id, Result<string> r_search_text, Promise<Unit> promise) {
+ auto it = files_.find(download_id);
+ if (it == files_.end()) {
+ return promise.set_value(Unit());
+ }
+
+ if (r_search_text.is_error()) {
+ if (!G()->close_flag()) {
+ remove_file_impl(it->second->file_id, {}, false, "add_download_to_hints");
+ }
+ } else {
+ auto search_text = r_search_text.move_as_ok();
+ // TODO: This is a race. Synchronous call would be better.
+ hints_.add(download_id, search_text.empty() ? string(" ") : search_text);
+ }
+ promise.set_value(Unit());
+ }
+
+ void add_file_info(unique_ptr<FileInfo> &&file_info, const string &search_text) {
+ CHECK(file_info != nullptr);
+ auto download_id = file_info->download_id;
+ file_info->internal_file_id = callback_->dup_file_id(file_info->file_id);
+ auto file_view = callback_->get_sync_file_view(file_info->file_id);
+ CHECK(!file_view.empty());
+ file_info->size = file_view.size();
+ file_info->expected_size = file_view.expected_size();
+ file_info->downloaded_size = file_view.local_total_size();
+ file_info->is_counted = !is_completed(*file_info);
+
+ if (file_info->completed_at > 0 && (file_info->size == 0 || file_info->downloaded_size != file_info->size)) {
+ LOG(INFO) << "Skip adding file " << file_info->file_id << " to recently downloaded files, because local size is "
+ << file_info->downloaded_size << " instead of expected " << file_info->size;
+ remove_from_database(*file_info);
+ return;
+ }
+
+ by_internal_file_id_[file_info->internal_file_id] = download_id;
+ by_file_id_[file_info->file_id] = download_id;
+ hints_.add(download_id, search_text.empty() ? string(" ") : search_text);
+ file_info->link_token = ++last_link_token_;
+
+ LOG(INFO) << "Adding to downloads file " << file_info->file_id << '/' << file_info->internal_file_id << " of size "
+ << file_info->size << '/' << file_info->expected_size
+ << " with downloaded_size = " << file_info->downloaded_size
+ << " and is_paused = " << file_info->is_paused;
+ auto it = files_.emplace(download_id, std::move(file_info)).first;
+ bool was_completed = is_completed(*it->second);
+ register_file_info(*it->second); // must be called before start_file, which can call update_file_download_state
+ if (is_completed(*it->second)) {
+ bool is_inserted = completed_download_ids_.insert(it->second->download_id).second;
+ CHECK(is_inserted == was_completed);
+ } else {
+ if (!it->second->is_paused) {
+ callback_->start_file(it->second->internal_file_id, it->second->priority,
+ actor_shared(this, it->second->link_token));
+ }
+ }
+ if (is_search_inited_) {
+ callback_->update_file_added(it->second->file_id, it->second->file_source_id, it->second->created_at,
+ it->second->completed_at, it->second->is_paused, file_counters_);
+ }
+ }
+
+ Status remove_file_impl(FileId file_id, FileSourceId file_source_id, bool delete_from_cache, const char *source) {
+ LOG(INFO) << "Remove from downloads file " << file_id << " from " << file_source_id;
+ TRY_STATUS(check_is_active(source));
+ TRY_RESULT(file_info_ptr, get_file_info(file_id, file_source_id));
+ auto &file_info = *file_info_ptr;
+ auto download_id = file_info.download_id;
+ if (!is_completed(file_info) && !file_info.is_paused) {
+ callback_->pause_file(file_info.internal_file_id);
+ }
+ unregister_file_info(file_info);
+ if (delete_from_cache) {
+ callback_->delete_file(file_info.internal_file_id);
+ }
+ by_internal_file_id_.erase(file_info.internal_file_id);
+ by_file_id_.erase(file_info.file_id);
+ hints_.remove(download_id);
+ completed_download_ids_.erase(download_id);
+
+ remove_from_database(file_info);
+ files_.erase(download_id);
+ if (is_search_inited_) {
+ callback_->update_file_removed(file_id, file_counters_);
+ }
+
+ update_counters();
+ on_file_viewed(download_id);
+
+ return Status::OK();
+ }
+
+ Status remove_file_if_finished_impl(FileId file_id) {
+ TRY_STATUS(check_is_active("remove_file_if_finished_impl"));
+ TRY_RESULT(file_info_ptr, get_file_info(file_id, {}));
+ if (!is_completed(*file_info_ptr)) {
+ return Status::Error("File is active");
+ }
+ return remove_file_impl(file_id, {}, false, "remove_file_if_finished_impl");
+ }
+
+ void timeout_expired() final {
+ clear_counters();
+ }
+
+ void clear_counters() {
+ if (!is_database_loaded_) {
+ return;
+ }
+ CHECK(counters_ == sent_counters_);
+ if (counters_.downloaded_size != counters_.total_size || counters_.total_size == 0) {
+ return;
+ }
+
+ for (auto &it : files_) {
+ if (is_completed(*it.second) || !it.second->is_paused) {
+ it.second->is_counted = false;
+ }
+ }
+ counters_ = Counters();
+ update_counters();
+ }
+
+ void tear_down() final {
+ callback_.reset();
+ }
+
+ void toggle_is_paused(const FileInfo &file_info, bool is_paused) {
+ if (is_completed(file_info) || is_paused == file_info.is_paused) {
+ return;
+ }
+ LOG(INFO) << "Change is_paused state of file " << file_info.file_id << " to " << is_paused;
+
+ with_file_info(file_info, [&](FileInfo &file_info) {
+ file_info.is_paused = is_paused;
+ file_info.need_save_to_database = true;
+ file_info.link_token = ++last_link_token_;
+ });
+ if (is_paused) {
+ callback_->pause_file(file_info.internal_file_id);
+ } else {
+ callback_->start_file(file_info.internal_file_id, file_info.priority, actor_shared(this, file_info.link_token));
+ }
+ if (is_search_inited_) {
+ callback_->update_file_changed(file_info.file_id, file_info.completed_at, file_info.is_paused, file_counters_);
+ }
+ }
+
+ void update_counters() {
+ if (!is_database_loaded_) {
+ return;
+ }
+ if (counters_ == sent_counters_) {
+ return;
+ }
+ CHECK(counters_.total_size >= 0);
+ CHECK(counters_.total_count >= 0);
+ CHECK(counters_.downloaded_size >= 0);
+ if ((counters_.downloaded_size == counters_.total_size && counters_.total_size != 0) || counters_ == Counters()) {
+ if (counters_.total_size != 0) {
+ constexpr double EMPTY_UPDATE_DELAY = 60.0;
+ set_timeout_in(EMPTY_UPDATE_DELAY);
+ } else {
+ cancel_timeout();
+ }
+ G()->td_db()->get_binlog_pmc()->erase("dlds_counter");
+ } else {
+ cancel_timeout();
+ G()->td_db()->get_binlog_pmc()->set("dlds_counter", log_event_store(counters_).as_slice().str());
+ }
+ sent_counters_ = counters_;
+ callback_->update_counters(counters_);
+ }
+
+ Result<const FileInfo *> get_file_info(FileId file_id, FileSourceId file_source_id = {}) {
+ auto it = by_file_id_.find(file_id);
+ if (it == by_file_id_.end()) {
+ return Status::Error(400, "Can't find file");
+ }
+ return get_file_info(it->second, file_source_id);
+ }
+
+ Result<const FileInfo *> get_file_info_by_internal(FileId file_id) {
+ auto it = by_internal_file_id_.find(file_id);
+ if (it == by_internal_file_id_.end()) {
+ return Status::Error(400, "Can't find file");
+ }
+ return get_file_info(it->second);
+ }
+
+ Result<const FileInfo *> get_file_info(int64 download_id, FileSourceId file_source_id = {}) {
+ auto it = files_.find(download_id);
+ if (it == files_.end()) {
+ return Status::Error(400, "Can't find file");
+ }
+ if (file_source_id.is_valid() && file_source_id != it->second->file_source_id) {
+ return Status::Error(400, "Can't find file with such source");
+ }
+ return it->second.get();
+ }
+
+ void unregister_file_info(const FileInfo &file_info) {
+ CHECK(file_info.is_registered);
+ file_info.is_registered = false;
+ if (file_info.is_counted && (is_completed(file_info) || !file_info.is_paused)) {
+ LOG(INFO) << "Unregister file " << file_info.file_id;
+ counters_.downloaded_size -= file_info.downloaded_size;
+ counters_.total_size -= get_file_size(file_info);
+ counters_.total_count--;
+ }
+ if (is_completed(file_info)) {
+ file_counters_.completed_count--;
+ CHECK(file_counters_.completed_count >= 0);
+ } else {
+ if (file_info.is_paused) {
+ file_counters_.paused_count--;
+ CHECK(file_counters_.paused_count >= 0);
+ }
+ file_counters_.active_count--;
+ CHECK(file_counters_.active_count >= file_counters_.paused_count);
+ }
+ }
+
+ void register_file_info(FileInfo &file_info) {
+ CHECK(!file_info.is_registered);
+ file_info.is_registered = true;
+ bool need_update = false;
+ if (!is_completed(file_info) && file_info.size != 0 && file_info.downloaded_size == file_info.size) {
+ LOG(INFO) << "Register file " << file_info.file_id;
+ file_info.is_paused = false;
+ file_info.completed_at = G()->unix_time();
+ file_info.need_save_to_database = true;
+
+ bool is_inserted = completed_download_ids_.insert(file_info.download_id).second;
+ CHECK(is_inserted);
+ if (file_info.is_counted) {
+ unviewed_completed_download_ids_.insert(file_info.download_id);
+ }
+
+ need_update = true;
+ }
+ if (file_info.is_counted && (is_completed(file_info) || !file_info.is_paused)) {
+ counters_.downloaded_size += file_info.downloaded_size;
+ counters_.total_size += get_file_size(file_info);
+ counters_.total_count++;
+ }
+ if (is_completed(file_info)) {
+ file_counters_.completed_count++;
+ } else {
+ if (file_info.is_paused) {
+ file_counters_.paused_count++;
+ }
+ file_counters_.active_count++;
+ }
+ if (is_search_inited_ && need_update) {
+ callback_->update_file_changed(file_info.file_id, file_info.completed_at, file_info.is_paused, file_counters_);
+ }
+ sync_with_database(file_info);
+ update_counters();
+ CHECK(file_info.is_registered);
+
+ check_completed_downloads_size();
+ }
+
+ void check_completed_downloads_size() {
+ if (!is_database_loaded_) {
+ return;
+ }
+
+ constexpr size_t MAX_COMPLETED_DOWNLOADS = 200;
+ while (completed_download_ids_.size() > MAX_COMPLETED_DOWNLOADS) {
+ auto download_id = *completed_download_ids_.begin();
+ auto file_info = get_file_info(download_id).move_as_ok();
+ remove_file_impl(file_info->file_id, FileSourceId(), false, "check_completed_downloads_size");
+ }
+ }
+
+ void on_file_viewed(int64 download_id) {
+ if (unviewed_completed_download_ids_.empty()) {
+ return;
+ }
+
+ LOG(INFO) << "Mark download " << download_id << " as viewed";
+ unviewed_completed_download_ids_.erase(download_id);
+ if (unviewed_completed_download_ids_.empty()) {
+ clear_counters();
+ }
+ }
+
+ template <class F>
+ void with_file_info(const FileInfo &const_file_info, F &&f) {
+ unregister_file_info(const_file_info);
+ auto &file_info = const_cast<FileInfo &>(const_file_info);
+ f(file_info);
+ register_file_info(file_info);
+ }
+
+ Status check_is_active(const char *source) {
+ if (!callback_) {
+ LOG(ERROR) << "DownloadManager is closed in " << source;
+ return Status::Error(500, "DownloadManager is closed");
+ }
+ CHECK(is_inited_);
+ load_database_files(source);
+ return Status::OK();
+ }
+};
+
+unique_ptr<DownloadManager> DownloadManager::create(unique_ptr<Callback> callback) {
+ return make_unique<DownloadManagerImpl>(std::move(callback));
+}
+
+td_api::object_ptr<td_api::updateFileDownloads> DownloadManager::Counters::get_update_file_downloads_object() const {
+ return td_api::make_object<td_api::updateFileDownloads>(total_size, total_count, downloaded_size);
+}
+
+td_api::object_ptr<td_api::downloadedFileCounts> DownloadManager::FileCounters::get_downloaded_file_counts_object()
+ const {
+ return td_api::make_object<td_api::downloadedFileCounts>(active_count, paused_count, completed_count);
+}
+
+template <class StorerT>
+void DownloadManager::Counters::store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ END_STORE_FLAGS();
+ td::store(total_size, storer);
+ td::store(total_count, storer);
+ td::store(downloaded_size, storer);
+}
+
+template <class ParserT>
+void DownloadManager::Counters::parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ END_PARSE_FLAGS();
+ td::parse(total_size, parser);
+ td::parse(total_count, parser);
+ td::parse(downloaded_size, parser);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DownloadManager.h b/protocols/Telegram/tdlib/td/td/telegram/DownloadManager.h
new file mode 100644
index 0000000000..71624c611f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DownloadManager.h
@@ -0,0 +1,112 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/td_api.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+
+namespace td {
+
+class DownloadManager : public Actor {
+ public:
+ struct Counters {
+ int64 total_size{};
+ int32 total_count{};
+ int64 downloaded_size{};
+
+ bool operator==(const Counters &other) const {
+ return total_size == other.total_size && total_count == other.total_count &&
+ downloaded_size == other.downloaded_size;
+ }
+
+ td_api::object_ptr<td_api::updateFileDownloads> get_update_file_downloads_object() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ struct FileCounters {
+ int32 active_count{};
+ int32 paused_count{};
+ int32 completed_count{};
+
+ bool operator==(const FileCounters &other) const {
+ return active_count == other.active_count && paused_count == other.paused_count &&
+ completed_count == other.completed_count;
+ }
+
+ td_api::object_ptr<td_api::downloadedFileCounts> get_downloaded_file_counts_object() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ // Callback is needed to make DownloadManager testable
+ class Callback {
+ public:
+ virtual ~Callback() = default;
+ virtual void update_counters(Counters counters) = 0;
+ virtual void update_file_added(FileId file_id, FileSourceId file_source_id, int32 add_date, int32 complete_date,
+ bool is_paused, FileCounters counters) = 0;
+ virtual void update_file_changed(FileId file_id, int32 complete_date, bool is_paused, FileCounters counters) = 0;
+ virtual void update_file_removed(FileId file_id, FileCounters counters) = 0;
+ virtual void start_file(FileId file_id, int8 priority, ActorShared<DownloadManager> download_manager) = 0;
+ virtual void pause_file(FileId file_id) = 0;
+ virtual void delete_file(FileId file_id) = 0;
+ virtual FileId dup_file_id(FileId file_id) = 0;
+
+ virtual void get_file_search_text(FileId file_id, FileSourceId file_source_id, Promise<string> &&promise) = 0;
+
+ virtual FileView get_sync_file_view(FileId file_id) = 0;
+ virtual td_api::object_ptr<td_api::file> get_file_object(FileId file_id) = 0;
+ virtual td_api::object_ptr<td_api::fileDownload> get_file_download_object(FileId file_id,
+ FileSourceId file_source_id,
+ int32 add_date, int32 complete_date,
+ bool is_paused) = 0;
+ };
+
+ static unique_ptr<DownloadManager> create(unique_ptr<Callback> callback);
+
+ //
+ // public interface for user
+ //
+ virtual void add_file(FileId file_id, FileSourceId file_source_id, string search_text, int8 priority,
+ Promise<td_api::object_ptr<td_api::file>> promise) = 0;
+ virtual void toggle_is_paused(FileId file_id, bool is_paused, Promise<Unit> promise) = 0;
+ virtual void toggle_all_is_paused(bool is_paused, Promise<Unit> promise) = 0;
+ virtual void search(string query, bool only_active, bool only_completed, string offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::foundFileDownloads>> promise) = 0;
+ virtual void remove_file(FileId file_id, FileSourceId file_source_id, bool delete_from_cache,
+ Promise<Unit> promise) = 0;
+ virtual void remove_all_files(bool only_active, bool only_completed, bool delete_from_cache,
+ Promise<Unit> promise) = 0;
+
+ //
+ // private interface to handle all kinds of updates
+ //
+ virtual void after_get_difference() = 0;
+ virtual void change_search_text(FileId file_id, FileSourceId file_source_id, string search_text) = 0;
+ virtual void remove_file_if_finished(FileId file_id) = 0;
+ virtual void update_file_download_state(FileId internal_file_id, int64 downloaded_size, int64 size,
+ int64 expected_size, bool is_paused) = 0;
+ virtual void update_file_viewed(FileId file_id, FileSourceId file_source_id) = 0;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DownloadManagerCallback.cpp b/protocols/Telegram/tdlib/td/td/telegram/DownloadManagerCallback.cpp
new file mode 100644
index 0000000000..5bcb15cf61
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DownloadManagerCallback.cpp
@@ -0,0 +1,125 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DownloadManagerCallback.h"
+
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/Td.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+void DownloadManagerCallback::update_counters(DownloadManager::Counters counters) {
+ send_closure(td_->actor_id(td_), &Td::send_update, counters.get_update_file_downloads_object());
+}
+
+void DownloadManagerCallback::update_file_added(FileId file_id, FileSourceId file_source_id, int32 add_date,
+ int32 complete_date, bool is_paused,
+ DownloadManager::FileCounters counters) {
+ send_closure(td_->actor_id(td_), &Td::send_update,
+ td_api::make_object<td_api::updateFileAddedToDownloads>(
+ get_file_download_object(file_id, file_source_id, add_date, complete_date, is_paused),
+ counters.get_downloaded_file_counts_object()));
+}
+
+void DownloadManagerCallback::update_file_changed(FileId file_id, int32 complete_date, bool is_paused,
+ DownloadManager::FileCounters counters) {
+ send_closure(td_->actor_id(td_), &Td::send_update,
+ td_api::make_object<td_api::updateFileDownload>(file_id.get(), complete_date, is_paused,
+ counters.get_downloaded_file_counts_object()));
+}
+
+void DownloadManagerCallback::update_file_removed(FileId file_id, DownloadManager::FileCounters counters) {
+ send_closure(td_->actor_id(td_), &Td::send_update,
+ td_api::make_object<td_api::updateFileRemovedFromDownloads>(
+ file_id.get(), counters.get_downloaded_file_counts_object()));
+}
+
+void DownloadManagerCallback::start_file(FileId file_id, int8 priority, ActorShared<DownloadManager> download_manager) {
+ send_closure_later(td_->file_manager_actor_, &FileManager::download, file_id,
+ make_download_file_callback(td_, std::move(download_manager)), priority,
+ FileManager::KEEP_DOWNLOAD_OFFSET, FileManager::IGNORE_DOWNLOAD_LIMIT,
+ Promise<td_api::object_ptr<td_api::file>>());
+}
+
+void DownloadManagerCallback::pause_file(FileId file_id) {
+ send_closure_later(td_->file_manager_actor_, &FileManager::download, file_id, nullptr, 0,
+ FileManager::KEEP_DOWNLOAD_OFFSET, FileManager::KEEP_DOWNLOAD_LIMIT,
+ Promise<td_api::object_ptr<td_api::file>>());
+}
+
+void DownloadManagerCallback::delete_file(FileId file_id) {
+ send_closure_later(td_->file_manager_actor_, &FileManager::delete_file, file_id, Promise<Unit>(),
+ "download manager callback");
+}
+
+FileId DownloadManagerCallback::dup_file_id(FileId file_id) {
+ return td_->file_manager_->dup_file_id(file_id, "DownloadManagerCallback");
+}
+
+void DownloadManagerCallback::get_file_search_text(FileId file_id, FileSourceId file_source_id,
+ Promise<string> &&promise) {
+ send_closure(td_->file_reference_manager_actor_, &FileReferenceManager::get_file_search_text, file_source_id,
+ get_file_view(file_id).get_unique_file_id(), std::move(promise));
+}
+
+FileView DownloadManagerCallback::get_file_view(FileId file_id) {
+ return td_->file_manager_->get_file_view(file_id);
+}
+
+FileView DownloadManagerCallback::get_sync_file_view(FileId file_id) {
+ td_->file_manager_->check_local_location(file_id, true);
+ return get_file_view(file_id);
+}
+
+td_api::object_ptr<td_api::file> DownloadManagerCallback::get_file_object(FileId file_id) {
+ return td_->file_manager_->get_file_object(file_id);
+}
+
+td_api::object_ptr<td_api::fileDownload> DownloadManagerCallback::get_file_download_object(
+ FileId file_id, FileSourceId file_source_id, int32 add_date, int32 complete_date, bool is_paused) {
+ return td_api::make_object<td_api::fileDownload>(td_->file_manager_->get_file_view(file_id).get_main_file_id().get(),
+ td_->file_reference_manager_->get_message_object(file_source_id),
+ add_date, complete_date, is_paused);
+}
+
+std::shared_ptr<FileManager::DownloadCallback> DownloadManagerCallback::make_download_file_callback(
+ Td *td, ActorShared<DownloadManager> download_manager) {
+ class Impl final : public FileManager::DownloadCallback {
+ public:
+ Impl(Td *td, ActorShared<DownloadManager> download_manager)
+ : td_(td), download_manager_(std::move(download_manager)) {
+ }
+ void on_progress(FileId file_id) final {
+ send_update(file_id, false);
+ }
+ void on_download_ok(FileId file_id) final {
+ send_update(file_id, false);
+ }
+ void on_download_error(FileId file_id, Status error) final {
+ send_update(file_id, true);
+ }
+
+ private:
+ Td *td_;
+ ActorShared<DownloadManager> download_manager_;
+
+ void send_update(FileId file_id, bool is_paused) const {
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ send_closure_later(download_manager_, &DownloadManager::update_file_download_state, file_id,
+ file_view.local_total_size(), file_view.size(), file_view.expected_size(), is_paused);
+ }
+ };
+ return std::make_shared<Impl>(td, std::move(download_manager));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DownloadManagerCallback.h b/protocols/Telegram/tdlib/td/td/telegram/DownloadManagerCallback.h
new file mode 100644
index 0000000000..3bf087733e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DownloadManagerCallback.h
@@ -0,0 +1,69 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DownloadManager.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/td_api.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+
+#include <memory>
+
+namespace td {
+
+class Td;
+
+class DownloadManagerCallback final : public DownloadManager::Callback {
+ public:
+ DownloadManagerCallback(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+ }
+
+ void update_counters(DownloadManager::Counters counters) final;
+
+ void update_file_added(FileId file_id, FileSourceId file_source_id, int32 add_date, int32 complete_date,
+ bool is_paused, DownloadManager::FileCounters counters) final;
+
+ void update_file_changed(FileId file_id, int32 complete_date, bool is_paused,
+ DownloadManager::FileCounters counters) final;
+
+ void update_file_removed(FileId file_id, DownloadManager::FileCounters counters) final;
+
+ void start_file(FileId file_id, int8 priority, ActorShared<DownloadManager> download_manager) final;
+
+ void pause_file(FileId file_id) final;
+
+ void delete_file(FileId file_id) final;
+
+ FileId dup_file_id(FileId file_id) final;
+
+ void get_file_search_text(FileId file_id, FileSourceId file_source_id, Promise<string> &&promise) final;
+
+ FileView get_sync_file_view(FileId file_id) final;
+
+ td_api::object_ptr<td_api::file> get_file_object(FileId file_id) final;
+
+ td_api::object_ptr<td_api::fileDownload> get_file_download_object(FileId file_id, FileSourceId file_source_id,
+ int32 add_date, int32 complete_date,
+ bool is_paused) final;
+
+ private:
+ Td *td_;
+ ActorShared<> parent_;
+
+ FileView get_file_view(FileId file_id);
+
+ static std::shared_ptr<FileManager::DownloadCallback> make_download_file_callback(
+ Td *td, ActorShared<DownloadManager> download_manager);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DraftMessage.cpp b/protocols/Telegram/tdlib/td/td/telegram/DraftMessage.cpp
new file mode 100644
index 0000000000..de600bc249
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DraftMessage.cpp
@@ -0,0 +1,95 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/DraftMessage.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/ServerMessageId.h"
+
+#include "td/utils/logging.h"
+
+namespace td {
+
+td_api::object_ptr<td_api::draftMessage> get_draft_message_object(const unique_ptr<DraftMessage> &draft_message) {
+ if (draft_message == nullptr) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::draftMessage>(draft_message->reply_to_message_id.get(), draft_message->date,
+ get_input_message_text_object(draft_message->input_message_text));
+}
+
+unique_ptr<DraftMessage> get_draft_message(ContactsManager *contacts_manager,
+ telegram_api::object_ptr<telegram_api::DraftMessage> &&draft_message_ptr) {
+ if (draft_message_ptr == nullptr) {
+ return nullptr;
+ }
+ auto constructor_id = draft_message_ptr->get_id();
+ switch (constructor_id) {
+ case telegram_api::draftMessageEmpty::ID:
+ return nullptr;
+ case telegram_api::draftMessage::ID: {
+ auto draft = move_tl_object_as<telegram_api::draftMessage>(draft_message_ptr);
+ auto flags = draft->flags_;
+ auto result = make_unique<DraftMessage>();
+ result->date = draft->date_;
+ if ((flags & telegram_api::draftMessage::REPLY_TO_MSG_ID_MASK) != 0) {
+ result->reply_to_message_id = MessageId(ServerMessageId(draft->reply_to_msg_id_));
+ if (!result->reply_to_message_id.is_valid()) {
+ LOG(ERROR) << "Receive " << result->reply_to_message_id << " as reply_to_message_id in the draft";
+ result->reply_to_message_id = MessageId();
+ }
+ }
+
+ auto entities = get_message_entities(contacts_manager, std::move(draft->entities_), "draftMessage");
+ auto status = fix_formatted_text(draft->message_, entities, true, true, true, true, true);
+ if (status.is_error()) {
+ LOG(ERROR) << "Receive error " << status << " while parsing draft " << draft->message_;
+ if (!clean_input_string(draft->message_)) {
+ draft->message_.clear();
+ }
+ entities = find_entities(draft->message_, false, true);
+ }
+ result->input_message_text.text = FormattedText{std::move(draft->message_), std::move(entities)};
+ result->input_message_text.disable_web_page_preview = draft->no_webpage_;
+ result->input_message_text.clear_draft = false;
+
+ return result;
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+Result<unique_ptr<DraftMessage>> get_draft_message(Td *td, DialogId dialog_id,
+ td_api::object_ptr<td_api::draftMessage> &&draft_message) {
+ if (draft_message == nullptr) {
+ return nullptr;
+ }
+
+ auto result = make_unique<DraftMessage>();
+ result->date = G()->unix_time();
+ result->reply_to_message_id = MessageId(draft_message->reply_to_message_id_);
+ if (result->reply_to_message_id != MessageId() && !result->reply_to_message_id.is_valid()) {
+ return Status::Error(400, "Invalid reply_to_message_id specified");
+ }
+
+ auto input_message_content = std::move(draft_message->input_message_text_);
+ if (input_message_content != nullptr) {
+ if (input_message_content->get_id() != td_api::inputMessageText::ID) {
+ return Status::Error(400, "Input message content type must be InputMessageText");
+ }
+ TRY_RESULT(message_content,
+ process_input_message_text(td, dialog_id, std::move(input_message_content), false, true));
+ result->input_message_text = std::move(message_content);
+ }
+
+ return std::move(result);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DraftMessage.h b/protocols/Telegram/tdlib/td/td/telegram/DraftMessage.h
new file mode 100644
index 0000000000..10c765718c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DraftMessage.h
@@ -0,0 +1,38 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/InputMessageText.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class ContactsManager;
+class Td;
+
+class DraftMessage {
+ public:
+ int32 date = 0;
+ MessageId reply_to_message_id;
+ InputMessageText input_message_text;
+};
+
+td_api::object_ptr<td_api::draftMessage> get_draft_message_object(const unique_ptr<DraftMessage> &draft_message);
+
+unique_ptr<DraftMessage> get_draft_message(ContactsManager *contacts_manager,
+ telegram_api::object_ptr<telegram_api::DraftMessage> &&draft_message_ptr);
+
+Result<unique_ptr<DraftMessage>> get_draft_message(Td *td, DialogId dialog_id,
+ td_api::object_ptr<td_api::draftMessage> &&draft_message);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/DraftMessage.hpp b/protocols/Telegram/tdlib/td/td/telegram/DraftMessage.hpp
new file mode 100644
index 0000000000..3671d4d69f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/DraftMessage.hpp
@@ -0,0 +1,31 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DraftMessage.h"
+
+#include "td/telegram/InputMessageText.hpp"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const DraftMessage &draft_message, StorerT &storer) {
+ store(draft_message.date, storer);
+ store(draft_message.reply_to_message_id, storer);
+ store(draft_message.input_message_text, storer);
+}
+
+template <class ParserT>
+void parse(DraftMessage &draft_message, ParserT &parser) {
+ parse(draft_message.date, parser);
+ parse(draft_message.reply_to_message_id, parser);
+ parse(draft_message.input_message_text, parser);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/EmailVerification.cpp b/protocols/Telegram/tdlib/td/td/telegram/EmailVerification.cpp
new file mode 100644
index 0000000000..234190a02f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/EmailVerification.cpp
@@ -0,0 +1,53 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/EmailVerification.h"
+
+#include "td/telegram/misc.h"
+
+namespace td {
+
+EmailVerification::EmailVerification(td_api::object_ptr<td_api::EmailAddressAuthentication> &&code) {
+ if (code == nullptr) {
+ return;
+ }
+ switch (code->get_id()) {
+ case td_api::emailAddressAuthenticationCode::ID:
+ type_ = Type::Code;
+ code_ = static_cast<const td_api::emailAddressAuthenticationCode *>(code.get())->code_;
+ break;
+ case td_api::emailAddressAuthenticationAppleId::ID:
+ type_ = Type::Apple;
+ code_ = static_cast<const td_api::emailAddressAuthenticationAppleId *>(code.get())->token_;
+ break;
+ case td_api::emailAddressAuthenticationGoogleId::ID:
+ type_ = Type::Google;
+ code_ = static_cast<const td_api::emailAddressAuthenticationGoogleId *>(code.get())->token_;
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ if (!clean_input_string(code_)) {
+ *this = {};
+ }
+}
+
+telegram_api::object_ptr<telegram_api::EmailVerification> EmailVerification::get_input_email_verification() const {
+ switch (type_) {
+ case Type::Code:
+ return telegram_api::make_object<telegram_api::emailVerificationCode>(code_);
+ case Type::Apple:
+ return telegram_api::make_object<telegram_api::emailVerificationApple>(code_);
+ case Type::Google:
+ return telegram_api::make_object<telegram_api::emailVerificationGoogle>(code_);
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/EmailVerification.h b/protocols/Telegram/tdlib/td/td/telegram/EmailVerification.h
new file mode 100644
index 0000000000..abd4d8ed34
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/EmailVerification.h
@@ -0,0 +1,37 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+class EmailVerification {
+ enum class Type : int32 { None, Code, Apple, Google };
+ Type type_ = Type::None;
+ string code_;
+
+ public:
+ EmailVerification() = default;
+
+ explicit EmailVerification(td_api::object_ptr<td_api::EmailAddressAuthentication> &&code);
+
+ telegram_api::object_ptr<telegram_api::EmailVerification> get_input_email_verification() const;
+
+ bool is_empty() const {
+ return type_ == Type::None;
+ }
+
+ bool is_email_code() const {
+ return type_ == Type::Code;
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/EmojiStatus.cpp b/protocols/Telegram/tdlib/td/td/telegram/EmojiStatus.cpp
new file mode 100644
index 0000000000..22a8869ffd
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/EmojiStatus.cpp
@@ -0,0 +1,330 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/EmojiStatus.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/Status.h"
+
+#include <limits>
+
+namespace td {
+
+struct EmojiStatuses {
+ int64 hash_ = 0;
+ vector<EmojiStatus> emoji_statuses_;
+
+ td_api::object_ptr<td_api::emojiStatuses> get_emoji_statuses_object() const {
+ auto emoji_statuses = transform(emoji_statuses_, [](const EmojiStatus &emoji_status) {
+ CHECK(!emoji_status.is_empty());
+ return emoji_status.get_emoji_status_object();
+ });
+
+ return td_api::make_object<td_api::emojiStatuses>(std::move(emoji_statuses));
+ }
+
+ EmojiStatuses() = default;
+
+ explicit EmojiStatuses(tl_object_ptr<telegram_api::account_emojiStatuses> &&emoji_statuses) {
+ CHECK(emoji_statuses != nullptr);
+ hash_ = emoji_statuses->hash_;
+ for (auto &status : emoji_statuses->statuses_) {
+ EmojiStatus emoji_status(std::move(status));
+ if (emoji_status.is_empty()) {
+ LOG(ERROR) << "Receive empty emoji status";
+ continue;
+ }
+ if (emoji_status.get_until_date() != 0) {
+ LOG(ERROR) << "Receive temporary emoji status";
+ emoji_status.clear_until_date();
+ }
+ emoji_statuses_.push_back(emoji_status);
+ }
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(hash_, storer);
+ td::store(emoji_statuses_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(hash_, parser);
+ td::parse(emoji_statuses_, parser);
+ }
+};
+
+static const string &get_default_emoji_statuses_database_key() {
+ static string key = "def_emoji_statuses";
+ return key;
+}
+
+static const string &get_recent_emoji_statuses_database_key() {
+ static string key = "rec_emoji_statuses";
+ return key;
+}
+
+static EmojiStatuses load_emoji_statuses(const string &key) {
+ EmojiStatuses result;
+ auto log_event_string = G()->td_db()->get_binlog_pmc()->get(key);
+ if (!log_event_string.empty()) {
+ log_event_parse(result, log_event_string).ensure();
+ } else {
+ result.hash_ = -1;
+ }
+ return result;
+}
+
+static void save_emoji_statuses(const string &key, const EmojiStatuses &emoji_statuses) {
+ G()->td_db()->get_binlog_pmc()->set(key, log_event_store(emoji_statuses).as_slice().str());
+}
+
+class GetDefaultEmojiStatusesQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::emojiStatuses>> promise_;
+
+ public:
+ explicit GetDefaultEmojiStatusesQuery(Promise<td_api::object_ptr<td_api::emojiStatuses>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(int64 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::account_getDefaultEmojiStatuses(hash), {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getDefaultEmojiStatuses>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto emoji_statuses_ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetDefaultEmojiStatusesQuery: " << to_string(emoji_statuses_ptr);
+
+ if (emoji_statuses_ptr->get_id() == telegram_api::account_emojiStatusesNotModified::ID) {
+ if (promise_) {
+ promise_.set_error(Status::Error(500, "Receive wrong server response"));
+ }
+ return;
+ }
+
+ CHECK(emoji_statuses_ptr->get_id() == telegram_api::account_emojiStatuses::ID);
+ EmojiStatuses emoji_statuses(move_tl_object_as<telegram_api::account_emojiStatuses>(emoji_statuses_ptr));
+ save_emoji_statuses(get_default_emoji_statuses_database_key(), emoji_statuses);
+
+ if (promise_) {
+ promise_.set_value(emoji_statuses.get_emoji_statuses_object());
+ }
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetRecentEmojiStatusesQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::emojiStatuses>> promise_;
+
+ public:
+ explicit GetRecentEmojiStatusesQuery(Promise<td_api::object_ptr<td_api::emojiStatuses>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(int64 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::account_getRecentEmojiStatuses(hash), {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getRecentEmojiStatuses>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto emoji_statuses_ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetRecentEmojiStatusesQuery: " << to_string(emoji_statuses_ptr);
+
+ if (emoji_statuses_ptr->get_id() == telegram_api::account_emojiStatusesNotModified::ID) {
+ if (promise_) {
+ promise_.set_error(Status::Error(500, "Receive wrong server response"));
+ }
+ return;
+ }
+
+ CHECK(emoji_statuses_ptr->get_id() == telegram_api::account_emojiStatuses::ID);
+ EmojiStatuses emoji_statuses(move_tl_object_as<telegram_api::account_emojiStatuses>(emoji_statuses_ptr));
+ save_emoji_statuses(get_recent_emoji_statuses_database_key(), emoji_statuses);
+
+ if (promise_) {
+ promise_.set_value(emoji_statuses.get_emoji_statuses_object());
+ }
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ClearRecentEmojiStatusesQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ClearRecentEmojiStatusesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_clearRecentEmojiStatuses(), {{"me"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_clearRecentEmojiStatuses>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ save_emoji_statuses(get_recent_emoji_statuses_database_key(), EmojiStatuses());
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+EmojiStatus::EmojiStatus(const td_api::object_ptr<td_api::emojiStatus> &emoji_status, int32 duration) {
+ if (emoji_status == nullptr) {
+ return;
+ }
+
+ custom_emoji_id_ = CustomEmojiId(emoji_status->custom_emoji_id_);
+ if (duration != 0) {
+ int32 current_time = G()->unix_time();
+ if (duration >= std::numeric_limits<int32>::max() - current_time) {
+ until_date_ = std::numeric_limits<int32>::max();
+ } else {
+ until_date_ = current_time + duration;
+ }
+ }
+}
+
+EmojiStatus::EmojiStatus(tl_object_ptr<telegram_api::EmojiStatus> &&emoji_status) {
+ if (emoji_status == nullptr) {
+ return;
+ }
+ switch (emoji_status->get_id()) {
+ case telegram_api::emojiStatusEmpty::ID:
+ break;
+ case telegram_api::emojiStatus::ID: {
+ auto status = static_cast<const telegram_api::emojiStatus *>(emoji_status.get());
+ custom_emoji_id_ = CustomEmojiId(status->document_id_);
+ break;
+ }
+ case telegram_api::emojiStatusUntil::ID: {
+ auto status = static_cast<const telegram_api::emojiStatusUntil *>(emoji_status.get());
+ custom_emoji_id_ = CustomEmojiId(status->document_id_);
+ until_date_ = status->until_;
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+tl_object_ptr<telegram_api::EmojiStatus> EmojiStatus::get_input_emoji_status() const {
+ if (is_empty()) {
+ return make_tl_object<telegram_api::emojiStatusEmpty>();
+ }
+ if (until_date_ != 0) {
+ return make_tl_object<telegram_api::emojiStatusUntil>(custom_emoji_id_.get(), until_date_);
+ }
+ return make_tl_object<telegram_api::emojiStatus>(custom_emoji_id_.get());
+}
+
+td_api::object_ptr<td_api::emojiStatus> EmojiStatus::get_emoji_status_object() const {
+ if (is_empty()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::emojiStatus>(custom_emoji_id_.get());
+}
+
+CustomEmojiId EmojiStatus::get_effective_custom_emoji_id(bool is_premium, int32 unix_time) const {
+ if (!is_premium) {
+ return CustomEmojiId();
+ }
+ if (until_date_ != 0 && until_date_ <= unix_time) {
+ return CustomEmojiId();
+ }
+ return custom_emoji_id_;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const EmojiStatus &emoji_status) {
+ if (emoji_status.is_empty()) {
+ return string_builder << "DefaultProfileBadge";
+ }
+ string_builder << emoji_status.custom_emoji_id_;
+ if (emoji_status.until_date_ != 0) {
+ string_builder << " until " << emoji_status.until_date_;
+ }
+ return string_builder;
+}
+
+void get_default_emoji_statuses(Td *td, Promise<td_api::object_ptr<td_api::emojiStatuses>> &&promise) {
+ auto statuses = load_emoji_statuses(get_default_emoji_statuses_database_key());
+ if (statuses.hash_ != -1 && promise) {
+ promise.set_value(statuses.get_emoji_statuses_object());
+ promise = Promise<td_api::object_ptr<td_api::emojiStatuses>>();
+ }
+ td->create_handler<GetDefaultEmojiStatusesQuery>(std::move(promise))->send(statuses.hash_);
+}
+
+void get_recent_emoji_statuses(Td *td, Promise<td_api::object_ptr<td_api::emojiStatuses>> &&promise) {
+ auto statuses = load_emoji_statuses(get_recent_emoji_statuses_database_key());
+ if (statuses.hash_ != -1 && promise) {
+ promise.set_value(statuses.get_emoji_statuses_object());
+ promise = Promise<td_api::object_ptr<td_api::emojiStatuses>>();
+ }
+ td->create_handler<GetRecentEmojiStatusesQuery>(std::move(promise))->send(statuses.hash_);
+}
+
+void add_recent_emoji_status(Td *td, EmojiStatus emoji_status) {
+ if (emoji_status.is_empty()) {
+ return;
+ }
+
+ if (td->stickers_manager_->is_default_emoji_status(emoji_status.get_custom_emoji_id())) {
+ LOG(INFO) << "Skip adding themed emoji status to recents";
+ return;
+ }
+
+ emoji_status.clear_until_date();
+ auto statuses = load_emoji_statuses(get_recent_emoji_statuses_database_key());
+ if (!statuses.emoji_statuses_.empty() && statuses.emoji_statuses_[0] == emoji_status) {
+ return;
+ }
+
+ statuses.hash_ = 0;
+ td::remove(statuses.emoji_statuses_, emoji_status);
+ statuses.emoji_statuses_.insert(statuses.emoji_statuses_.begin(), emoji_status);
+ constexpr size_t MAX_RECENT_EMOJI_STATUSES = 50; // server-side limit
+ if (statuses.emoji_statuses_.size() > MAX_RECENT_EMOJI_STATUSES) {
+ statuses.emoji_statuses_.resize(MAX_RECENT_EMOJI_STATUSES);
+ }
+ save_emoji_statuses(get_recent_emoji_statuses_database_key(), statuses);
+}
+
+void clear_recent_emoji_statuses(Td *td, Promise<Unit> &&promise) {
+ save_emoji_statuses(get_recent_emoji_statuses_database_key(), EmojiStatuses());
+ td->create_handler<ClearRecentEmojiStatusesQuery>(std::move(promise))->send();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/EmojiStatus.h b/protocols/Telegram/tdlib/td/td/telegram/EmojiStatus.h
new file mode 100644
index 0000000000..2044df4856
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/EmojiStatus.h
@@ -0,0 +1,110 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class Td;
+
+class EmojiStatus {
+ CustomEmojiId custom_emoji_id_;
+ int32 until_date_ = 0;
+
+ friend bool operator==(const EmojiStatus &lhs, const EmojiStatus &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const EmojiStatus &contact);
+
+ public:
+ EmojiStatus() = default;
+
+ EmojiStatus(const td_api::object_ptr<td_api::emojiStatus> &emoji_status, int32 duration);
+
+ explicit EmojiStatus(tl_object_ptr<telegram_api::EmojiStatus> &&emoji_status);
+
+ tl_object_ptr<telegram_api::EmojiStatus> get_input_emoji_status() const;
+
+ td_api::object_ptr<td_api::emojiStatus> get_emoji_status_object() const;
+
+ CustomEmojiId get_effective_custom_emoji_id(bool is_premium, int32 unix_time) const;
+
+ bool is_empty() const {
+ return !custom_emoji_id_.is_valid();
+ }
+
+ CustomEmojiId get_custom_emoji_id() const {
+ return custom_emoji_id_;
+ }
+
+ int32 get_until_date() const {
+ return until_date_;
+ }
+
+ void clear_until_date() {
+ until_date_ = 0;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ bool has_custom_emoji_id = custom_emoji_id_.is_valid();
+ bool has_until_date = until_date_ != 0;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_custom_emoji_id);
+ STORE_FLAG(has_until_date);
+ END_STORE_FLAGS();
+ if (has_custom_emoji_id) {
+ td::store(custom_emoji_id_, storer);
+ }
+ if (has_until_date) {
+ td::store(until_date_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ bool has_custom_emoji_id;
+ bool has_until_date;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_custom_emoji_id);
+ PARSE_FLAG(has_until_date);
+ END_PARSE_FLAGS();
+ if (has_custom_emoji_id) {
+ td::parse(custom_emoji_id_, parser);
+ }
+ if (has_until_date) {
+ td::parse(until_date_, parser);
+ }
+ }
+};
+
+inline bool operator==(const EmojiStatus &lhs, const EmojiStatus &rhs) {
+ return lhs.custom_emoji_id_ == rhs.custom_emoji_id_ && lhs.until_date_ == rhs.until_date_;
+}
+
+inline bool operator!=(const EmojiStatus &lhs, const EmojiStatus &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const EmojiStatus &emoji_status);
+
+void get_default_emoji_statuses(Td *td, Promise<td_api::object_ptr<td_api::emojiStatuses>> &&promise);
+
+void get_recent_emoji_statuses(Td *td, Promise<td_api::object_ptr<td_api::emojiStatuses>> &&promise);
+
+void add_recent_emoji_status(Td *td, EmojiStatus emoji_status);
+
+void clear_recent_emoji_statuses(Td *td, Promise<Unit> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/EncryptedFile.h b/protocols/Telegram/tdlib/td/td/telegram/EncryptedFile.h
new file mode 100644
index 0000000000..8d4937d566
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/EncryptedFile.h
@@ -0,0 +1,91 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/misc.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+struct EncryptedFile {
+ int64 id_ = 0;
+ int64 access_hash_ = 0;
+ int64 size_ = 0;
+ int32 dc_id_ = 0;
+ int32 key_fingerprint_ = 0;
+
+ EncryptedFile() = default;
+ EncryptedFile(int64 id, int64 access_hash, int64 size, int32 dc_id, int32 key_fingerprint)
+ : id_(id), access_hash_(access_hash), size_(size), dc_id_(dc_id), key_fingerprint_(key_fingerprint) {
+ CHECK(size_ >= 0);
+ }
+
+ static unique_ptr<EncryptedFile> get_encrypted_file(tl_object_ptr<telegram_api::EncryptedFile> file_ptr) {
+ if (file_ptr == nullptr || file_ptr->get_id() != telegram_api::encryptedFile::ID) {
+ return nullptr;
+ }
+ auto file = move_tl_object_as<telegram_api::encryptedFile>(file_ptr);
+ if (file->size_ < 0) {
+ return nullptr;
+ }
+ return make_unique<EncryptedFile>(file->id_, file->access_hash_, file->size_, file->dc_id_, file->key_fingerprint_);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ bool has_64bit_size = (size_ >= (static_cast<int64>(1) << 31));
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_64bit_size);
+ END_STORE_FLAGS();
+ store(id_, storer);
+ store(access_hash_, storer);
+ if (has_64bit_size) {
+ store(size_, storer);
+ } else {
+ store(narrow_cast<int32>(size_), storer);
+ }
+ store(dc_id_, storer);
+ store(key_fingerprint_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ bool has_64bit_size;
+ BEGIN_PARSE_FLAGS();
+ constexpr int32 OLD_MAGIC = 0x473d738a;
+ if (flags_parse == OLD_MAGIC) {
+ flags_parse = 0;
+ }
+ PARSE_FLAG(has_64bit_size);
+ END_PARSE_FLAGS();
+ parse(id_, parser);
+ parse(access_hash_, parser);
+ if (has_64bit_size) {
+ parse(size_, parser);
+ } else {
+ int32 int_size;
+ parse(int_size, parser);
+ size_ = int_size;
+ }
+ parse(dc_id_, parser);
+ parse(key_fingerprint_, parser);
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &sb, const EncryptedFile &file) {
+ return sb << "[" << tag("id", file.id_) << tag("access_hash", file.access_hash_) << tag("size", file.size_)
+ << tag("dc_id", file.dc_id_) << tag("key_fingerprint", file.key_fingerprint_) << "]";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.cpp
new file mode 100644
index 0000000000..441c4eb45a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.cpp
@@ -0,0 +1,475 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/FileReferenceManager.h"
+
+#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/AttachMenuManager.h"
+#include "td/telegram/BackgroundManager.h"
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/NotificationSettingsManager.h"
+#include "td/telegram/StickerSetId.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/WebPageId.h"
+#include "td/telegram/WebPagesManager.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/overloaded.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Time.h"
+
+namespace td {
+
+int VERBOSITY_NAME(file_references) = VERBOSITY_NAME(INFO);
+
+FileReferenceManager::FileReferenceManager(ActorShared<> parent) : parent_(std::move(parent)) {
+}
+
+FileReferenceManager::~FileReferenceManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), file_sources_, nodes_);
+}
+
+void FileReferenceManager::tear_down() {
+ parent_.reset();
+}
+
+bool FileReferenceManager::is_file_reference_error(const Status &error) {
+ return error.is_error() && error.code() == 400 && begins_with(error.message(), "FILE_REFERENCE_");
+}
+
+size_t FileReferenceManager::get_file_reference_error_pos(const Status &error) {
+ if (!is_file_reference_error(error)) {
+ return 0;
+ }
+ auto offset = Slice("FILE_REFERENCE_").size();
+ if (error.message().size() <= offset || !is_digit(error.message()[offset])) {
+ return 0;
+ }
+ return to_integer<size_t>(error.message().substr(offset)) + 1;
+}
+
+/*
+fileSourceMessage chat_id:int53 message_id:int53 = FileSource; // repaired with get_message_from_server
+fileSourceUserProfilePhoto user_id:int32 photo_id:int64 = FileSource; // repaired with photos.getUserPhotos
+fileSourceBasicGroupPhoto basic_group_id:int32 = FileSource; // no need to repair
+fileSourceSupergroupPhoto supergroup_id:int32 = FileSource; // no need to repair
+fileSourceWebPage url:string = FileSource; // repaired with messages.getWebPage
+fileSourceWallpapers = FileSource; // can't be repaired
+fileSourceSavedAnimations = FileSource; // repaired with messages.getSavedGifs
+fileSourceRecentStickers is_attached:Bool = FileSource; // repaired with messages.getRecentStickers, not reliable
+fileSourceFavoriteStickers = FileSource; // repaired with messages.getFavedStickers, not reliable
+fileSourceBackground background_id:int64 access_hash:int64 = FileSource; // repaired with account.getWallPaper
+fileSourceBasicGroupFull basic_group_id:int32 = FileSource; // repaired with messages.getFullChat
+fileSourceSupergroupFull supergroup_id:int32 = FileSource; // repaired with messages.getFullChannel
+fileSourceAppConfig = FileSource; // repaired with help.getAppConfig, not reliable
+fileSourceSavedRingtones = FileSource; // repaired with account.getSavedRingtones
+fileSourceUserFull = FileSource; // repaired with users.getFullUser
+fileSourceAttachmentMenuBot = FileSource; // repaired with messages.getAttachMenuBot
+*/
+
+FileSourceId FileReferenceManager::get_current_file_source_id() const {
+ return FileSourceId(narrow_cast<int32>(file_sources_.size()));
+}
+
+template <class T>
+FileSourceId FileReferenceManager::add_file_source_id(T source, Slice source_str) {
+ file_sources_.emplace_back(std::move(source));
+ VLOG(file_references) << "Create file source " << file_sources_.size() << " for " << source_str;
+ return get_current_file_source_id();
+}
+
+FileSourceId FileReferenceManager::create_message_file_source(FullMessageId full_message_id) {
+ FileSourceMessage source{full_message_id};
+ return add_file_source_id(source, PSLICE() << full_message_id);
+}
+
+FileSourceId FileReferenceManager::create_user_photo_file_source(UserId user_id, int64 photo_id) {
+ FileSourceUserPhoto source{photo_id, user_id};
+ return add_file_source_id(source, PSLICE() << "photo " << photo_id << " of " << user_id);
+}
+
+FileSourceId FileReferenceManager::create_web_page_file_source(string url) {
+ FileSourceWebPage source{std::move(url)};
+ auto source_str = PSTRING() << "web page of " << source.url;
+ return add_file_source_id(std::move(source), source_str);
+}
+
+FileSourceId FileReferenceManager::create_saved_animations_file_source() {
+ FileSourceSavedAnimations source;
+ return add_file_source_id(source, "saved animations");
+}
+
+FileSourceId FileReferenceManager::create_recent_stickers_file_source(bool is_attached) {
+ FileSourceRecentStickers source{is_attached};
+ return add_file_source_id(source, PSLICE() << "recent " << (is_attached ? "attached " : "") << "stickers");
+}
+
+FileSourceId FileReferenceManager::create_favorite_stickers_file_source() {
+ FileSourceFavoriteStickers source;
+ return add_file_source_id(source, "favorite stickers");
+}
+
+FileSourceId FileReferenceManager::create_background_file_source(BackgroundId background_id, int64 access_hash) {
+ FileSourceBackground source{background_id, access_hash};
+ return add_file_source_id(source, PSLICE() << background_id);
+}
+
+FileSourceId FileReferenceManager::create_chat_full_file_source(ChatId chat_id) {
+ FileSourceChatFull source{chat_id};
+ return add_file_source_id(source, PSLICE() << "full " << chat_id);
+}
+
+FileSourceId FileReferenceManager::create_channel_full_file_source(ChannelId channel_id) {
+ FileSourceChannelFull source{channel_id};
+ return add_file_source_id(source, PSLICE() << "full " << channel_id);
+}
+
+FileSourceId FileReferenceManager::create_app_config_file_source() {
+ FileSourceAppConfig source;
+ return add_file_source_id(source, "app config");
+}
+
+FileSourceId FileReferenceManager::create_saved_ringtones_file_source() {
+ FileSourceSavedRingtones source;
+ return add_file_source_id(source, "saved notification sounds");
+}
+
+FileSourceId FileReferenceManager::create_user_full_file_source(UserId user_id) {
+ FileSourceUserFull source{user_id};
+ return add_file_source_id(source, PSLICE() << "full " << user_id);
+}
+
+FileSourceId FileReferenceManager::create_attach_menu_bot_file_source(UserId user_id) {
+ FileSourceAttachMenuBot source{user_id};
+ return add_file_source_id(source, PSLICE() << "attachment menu bot " << user_id);
+}
+
+FileReferenceManager::Node &FileReferenceManager::add_node(NodeId node_id) {
+ CHECK(node_id.is_valid());
+ auto &node = nodes_[node_id];
+ if (node == nullptr) {
+ node = make_unique<Node>();
+ }
+ return *node;
+}
+
+bool FileReferenceManager::add_file_source(NodeId node_id, FileSourceId file_source_id) {
+ auto &node = add_node(node_id);
+ bool is_added = node.file_source_ids.add(file_source_id);
+ VLOG(file_references) << "Add " << (is_added ? "new" : "old") << ' ' << file_source_id << " for file " << node_id;
+ return is_added;
+}
+
+bool FileReferenceManager::remove_file_source(NodeId node_id, FileSourceId file_source_id) {
+ CHECK(node_id.is_valid());
+ auto *node = nodes_.get_pointer(node_id);
+ bool is_removed = node != nullptr && node->file_source_ids.remove(file_source_id);
+ if (is_removed) {
+ VLOG(file_references) << "Remove " << file_source_id << " from file " << node_id;
+ } else {
+ VLOG(file_references) << "Can't find " << file_source_id << " from file " << node_id << " to remove it";
+ }
+ return is_removed;
+}
+
+vector<FileSourceId> FileReferenceManager::get_some_file_sources(NodeId node_id) {
+ auto *node = nodes_.get_pointer(node_id);
+ if (node == nullptr) {
+ return {};
+ }
+ return node->file_source_ids.get_some_elements();
+}
+
+vector<FullMessageId> FileReferenceManager::get_some_message_file_sources(NodeId node_id) {
+ auto file_source_ids = get_some_file_sources(node_id);
+
+ vector<FullMessageId> result;
+ for (auto file_source_id : file_source_ids) {
+ auto index = static_cast<size_t>(file_source_id.get()) - 1;
+ CHECK(index < file_sources_.size());
+ const auto &file_source = file_sources_[index];
+ if (file_source.get_offset() == 0) {
+ result.push_back(file_source.get<FileSourceMessage>().full_message_id);
+ }
+ }
+ return result;
+}
+
+void FileReferenceManager::merge(NodeId to_node_id, NodeId from_node_id) {
+ auto *from_node_ptr = nodes_.get_pointer(from_node_id);
+ if (from_node_ptr == nullptr) {
+ return;
+ }
+ auto &from = *from_node_ptr;
+
+ auto &to = add_node(to_node_id);
+ VLOG(file_references) << "Merge " << to.file_source_ids.size() << " and " << from.file_source_ids.size()
+ << " sources of files " << to_node_id << " and " << from_node_id;
+ CHECK(!to.query || to.query->proxy.is_empty());
+ CHECK(!from.query || from.query->proxy.is_empty());
+ if (to.query || from.query) {
+ if (!to.query) {
+ to.query = make_unique<Query>();
+ to.query->generation = ++query_generation_;
+ }
+ if (from.query) {
+ combine(to.query->promises, std::move(from.query->promises));
+ to.query->active_queries += from.query->active_queries;
+ from.query->proxy = Destination(to_node_id, to.query->generation);
+ }
+ }
+ to.file_source_ids.merge(std::move(from.file_source_ids));
+ run_node(to_node_id);
+ run_node(from_node_id);
+}
+
+void FileReferenceManager::run_node(NodeId node_id) {
+ CHECK(node_id.is_valid());
+ auto *node_ptr = nodes_.get_pointer(node_id);
+ if (node_ptr == nullptr) {
+ return;
+ }
+ Node &node = *node_ptr;
+ if (!node.query) {
+ return;
+ }
+ if (node.query->active_queries != 0) {
+ return;
+ }
+ VLOG(file_references) << "Trying to repair file reference for file " << node_id;
+ if (node.query->promises.empty()) {
+ node.query = {};
+ return;
+ }
+ if (!node.file_source_ids.has_next()) {
+ VLOG(file_references) << "Have no more file sources to repair file reference for file " << node_id;
+ for (auto &p : node.query->promises) {
+ if (node.file_source_ids.empty()) {
+ p.set_error(Status::Error(400, "File source is not found"));
+ } else {
+ p.set_error(Status::Error(429, "Too Many Requests: retry after 1"));
+ }
+ }
+ node.query = {};
+ return;
+ }
+ if (node.last_successful_repair_time >= Time::now() - 60) {
+ VLOG(file_references) << "Recently repaired file reference for file " << node_id << ", do not try again";
+ for (auto &p : node.query->promises) {
+ p.set_error(Status::Error(429, "Too Many Requests: retry after 60"));
+ }
+ node.query = {};
+ return;
+ }
+ auto file_source_id = node.file_source_ids.next();
+ send_query(Destination(node_id, node.query->generation), file_source_id);
+}
+
+void FileReferenceManager::send_query(Destination dest, FileSourceId file_source_id) {
+ VLOG(file_references) << "Send file reference repair query for file " << dest.node_id << " with generation "
+ << dest.generation << " from " << file_source_id;
+ auto &node = add_node(dest.node_id);
+ node.query->active_queries++;
+
+ auto promise = PromiseCreator::lambda([dest, file_source_id, actor_id = actor_id(this),
+ file_manager_actor_id = G()->file_manager()](Result<Unit> result) {
+ auto new_promise = PromiseCreator::lambda([dest, file_source_id, actor_id](Result<Unit> result) {
+ Status status;
+ if (result.is_error()) {
+ status = result.move_as_error();
+ }
+ send_closure(actor_id, &FileReferenceManager::on_query_result, dest, file_source_id, std::move(status), 0);
+ });
+
+ send_closure(file_manager_actor_id, &FileManager::on_file_reference_repaired, dest.node_id, file_source_id,
+ std::move(result), std::move(new_promise));
+ });
+ auto index = static_cast<size_t>(file_source_id.get()) - 1;
+ CHECK(index < file_sources_.size());
+ file_sources_[index].visit(overloaded(
+ [&](const FileSourceMessage &source) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::get_message_from_server, source.full_message_id,
+ std::move(promise), "FileSourceMessage", nullptr);
+ },
+ [&](const FileSourceUserPhoto &source) {
+ send_closure_later(G()->contacts_manager(), &ContactsManager::reload_user_profile_photo, source.user_id,
+ source.photo_id, std::move(promise));
+ },
+ [&](const FileSourceChatPhoto &source) {
+ send_closure_later(G()->contacts_manager(), &ContactsManager::reload_chat, source.chat_id, std::move(promise));
+ },
+ [&](const FileSourceChannelPhoto &source) {
+ send_closure_later(G()->contacts_manager(), &ContactsManager::reload_channel, source.channel_id,
+ std::move(promise));
+ },
+ [&](const FileSourceWallpapers &source) { promise.set_error(Status::Error("Can't repair old wallpapers")); },
+ [&](const FileSourceWebPage &source) {
+ send_closure_later(G()->web_pages_manager(), &WebPagesManager::reload_web_page_by_url, source.url,
+ PromiseCreator::lambda([promise = std::move(promise)](Result<WebPageId> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(Unit());
+ }
+ }));
+ },
+ [&](const FileSourceSavedAnimations &source) {
+ send_closure_later(G()->animations_manager(), &AnimationsManager::repair_saved_animations, std::move(promise));
+ },
+ [&](const FileSourceRecentStickers &source) {
+ send_closure_later(G()->stickers_manager(), &StickersManager::repair_recent_stickers, source.is_attached,
+ std::move(promise));
+ },
+ [&](const FileSourceFavoriteStickers &source) {
+ send_closure_later(G()->stickers_manager(), &StickersManager::repair_favorite_stickers, std::move(promise));
+ },
+ [&](const FileSourceBackground &source) {
+ send_closure_later(G()->background_manager(), &BackgroundManager::reload_background, source.background_id,
+ source.access_hash, std::move(promise));
+ },
+ [&](const FileSourceChatFull &source) {
+ send_closure_later(G()->contacts_manager(), &ContactsManager::reload_chat_full, source.chat_id,
+ std::move(promise));
+ },
+ [&](const FileSourceChannelFull &source) {
+ send_closure_later(G()->contacts_manager(), &ContactsManager::reload_channel_full, source.channel_id,
+ std::move(promise), "repair file reference");
+ },
+ [&](const FileSourceAppConfig &source) {
+ send_closure_later(G()->config_manager(), &ConfigManager::reget_app_config, std::move(promise));
+ },
+ [&](const FileSourceSavedRingtones &source) {
+ send_closure_later(G()->notification_settings_manager(), &NotificationSettingsManager::repair_saved_ringtones,
+ std::move(promise));
+ },
+ [&](const FileSourceUserFull &source) {
+ send_closure_later(G()->contacts_manager(), &ContactsManager::reload_user_full, source.user_id,
+ std::move(promise));
+ },
+ [&](const FileSourceAttachMenuBot &source) {
+ send_closure_later(G()->attach_menu_manager(), &AttachMenuManager::reload_attach_menu_bot, source.user_id,
+ std::move(promise));
+ }));
+}
+
+FileReferenceManager::Destination FileReferenceManager::on_query_result(Destination dest, FileSourceId file_source_id,
+ Status status, int32 sub) {
+ if (G()->close_flag()) {
+ VLOG(file_references) << "Ignore file reference repair from " << file_source_id << " during closing";
+ return dest;
+ }
+
+ VLOG(file_references) << "Receive result of file reference repair query for file " << dest.node_id
+ << " with generation " << dest.generation << " from " << file_source_id << ": " << status << " "
+ << sub;
+ auto &node = add_node(dest.node_id);
+
+ auto query = node.query.get();
+ if (!query) {
+ return dest;
+ }
+ if (query->generation != dest.generation) {
+ return dest;
+ }
+ query->active_queries--;
+ CHECK(query->active_queries >= 0);
+
+ if (!query->proxy.is_empty()) {
+ query->active_queries -= sub;
+ CHECK(query->active_queries >= 0);
+ auto new_proxy = on_query_result(query->proxy, file_source_id, std::move(status), query->active_queries);
+ query->proxy = new_proxy;
+ run_node(dest.node_id);
+ return new_proxy;
+ }
+
+ if (status.is_ok()) {
+ node.last_successful_repair_time = Time::now();
+ for (auto &p : query->promises) {
+ p.set_value(Unit());
+ }
+ node.query = {};
+ }
+
+ run_node(dest.node_id);
+ return dest;
+}
+
+void FileReferenceManager::repair_file_reference(NodeId node_id, Promise<> promise) {
+ auto main_file_id = G()->td().get_actor_unsafe()->file_manager_->get_file_view(node_id).get_main_file_id();
+ VLOG(file_references) << "Repair file reference for file " << node_id << "/" << main_file_id;
+ node_id = main_file_id;
+ auto &node = add_node(node_id);
+ if (!node.query) {
+ node.query = make_unique<Query>();
+ node.query->generation = ++query_generation_;
+ node.file_source_ids.reset_position();
+ VLOG(file_references) << "Create new file reference repair query with generation " << query_generation_;
+ }
+ node.query->promises.push_back(std::move(promise));
+ run_node(node_id);
+}
+
+void FileReferenceManager::reload_photo(PhotoSizeSource source, Promise<Unit> promise) {
+ switch (source.get_type("reload_photo")) {
+ case PhotoSizeSource::Type::DialogPhotoBig:
+ case PhotoSizeSource::Type::DialogPhotoSmall:
+ case PhotoSizeSource::Type::DialogPhotoBigLegacy:
+ case PhotoSizeSource::Type::DialogPhotoSmallLegacy:
+ send_closure(G()->contacts_manager(), &ContactsManager::reload_dialog_info, source.dialog_photo().dialog_id,
+ std::move(promise));
+ break;
+ case PhotoSizeSource::Type::StickerSetThumbnail:
+ case PhotoSizeSource::Type::StickerSetThumbnailLegacy:
+ case PhotoSizeSource::Type::StickerSetThumbnailVersion:
+ send_closure(G()->stickers_manager(), &StickersManager::reload_sticker_set,
+ StickerSetId(source.sticker_set_thumbnail().sticker_set_id),
+ source.sticker_set_thumbnail().sticker_set_access_hash, std::move(promise));
+ break;
+ case PhotoSizeSource::Type::Legacy:
+ case PhotoSizeSource::Type::FullLegacy:
+ case PhotoSizeSource::Type::Thumbnail:
+ promise.set_error(Status::Error("Unexpected PhotoSizeSource type"));
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+void FileReferenceManager::get_file_search_text(FileSourceId file_source_id, string unique_file_id,
+ Promise<string> promise) {
+ auto index = static_cast<size_t>(file_source_id.get()) - 1;
+ CHECK(index < file_sources_.size());
+ file_sources_[index].visit(overloaded(
+ [&](const FileSourceMessage &source) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::get_message_file_search_text,
+ source.full_message_id, std::move(unique_file_id), std::move(promise));
+ },
+ [&](const auto &source) { promise.set_error(Status::Error(500, "Unsupported file source")); }));
+}
+
+td_api::object_ptr<td_api::message> FileReferenceManager::get_message_object(FileSourceId file_source_id) const {
+ auto index = static_cast<size_t>(file_source_id.get()) - 1;
+ CHECK(index < file_sources_.size());
+ td_api::object_ptr<td_api::message> result;
+ file_sources_[index].visit(overloaded(
+ [&](const FileSourceMessage &source) {
+ result = G()->td().get_actor_unsafe()->messages_manager_->get_message_object(source.full_message_id,
+ "FileReferenceManager");
+ },
+ [&](const auto &source) { LOG(ERROR) << "Unsupported file source"; }));
+ return result;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.h b/protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.h
new file mode 100644
index 0000000000..2f96ff2e4d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.h
@@ -0,0 +1,196 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/BackgroundId.h"
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/PhotoSizeSource.h"
+#include "td/telegram/SetWithPosition.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/Variant.h"
+#include "td/utils/WaitFreeHashMap.h"
+#include "td/utils/WaitFreeVector.h"
+
+namespace td {
+
+class Td;
+
+extern int VERBOSITY_NAME(file_references);
+
+class FileReferenceManager final : public Actor {
+ public:
+ explicit FileReferenceManager(ActorShared<> parent);
+ FileReferenceManager(const FileReferenceManager &) = delete;
+ FileReferenceManager &operator=(const FileReferenceManager &) = delete;
+ FileReferenceManager(FileReferenceManager &&) = delete;
+ FileReferenceManager &operator=(FileReferenceManager &&) = delete;
+ ~FileReferenceManager() final;
+
+ static bool is_file_reference_error(const Status &error);
+ static size_t get_file_reference_error_pos(const Status &error);
+
+ FileSourceId create_message_file_source(FullMessageId full_message_id);
+ FileSourceId create_user_photo_file_source(UserId user_id, int64 photo_id);
+ // file reference aren't used for chat/channel photo download and the photos can't be reused
+ // FileSourceId create_chat_photo_file_source(ChatId chat_id);
+ // FileSourceId create_channel_photo_file_source(ChannelId channel_id);
+ // FileSourceId create_wallpapers_file_source(); old wallpapers can't be repaired
+ FileSourceId create_web_page_file_source(string url);
+ FileSourceId create_saved_animations_file_source();
+ FileSourceId create_recent_stickers_file_source(bool is_attached);
+ FileSourceId create_favorite_stickers_file_source();
+ FileSourceId create_background_file_source(BackgroundId background_id, int64 access_hash);
+ FileSourceId create_chat_full_file_source(ChatId chat_id);
+ FileSourceId create_channel_full_file_source(ChannelId channel_id);
+ FileSourceId create_app_config_file_source();
+ FileSourceId create_saved_ringtones_file_source();
+ FileSourceId create_user_full_file_source(UserId user_id);
+ FileSourceId create_attach_menu_bot_file_source(UserId user_id);
+
+ using NodeId = FileId;
+ void repair_file_reference(NodeId node_id, Promise<> promise);
+
+ void get_file_search_text(FileSourceId file_source_id, string unique_file_id, Promise<string> promise);
+
+ td_api::object_ptr<td_api::message> get_message_object(FileSourceId file_source_id) const;
+
+ static void reload_photo(PhotoSizeSource source, Promise<Unit> promise);
+
+ bool add_file_source(NodeId node_id, FileSourceId file_source_id);
+
+ vector<FileSourceId> get_some_file_sources(NodeId node_id);
+
+ vector<FullMessageId> get_some_message_file_sources(NodeId node_id);
+
+ bool remove_file_source(NodeId node_id, FileSourceId file_source_id);
+
+ void merge(NodeId to_node_id, NodeId from_node_id);
+
+ template <class StorerT>
+ void store_file_source(FileSourceId file_source_id, StorerT &storer) const;
+
+ template <class ParserT>
+ FileSourceId parse_file_source(Td *td, ParserT &parser);
+
+ private:
+ struct Destination {
+ NodeId node_id;
+ int64 generation{0};
+
+ Destination() = default;
+ Destination(NodeId node_id, int64 generation) : node_id(node_id), generation(generation) {
+ }
+ bool is_empty() const {
+ return node_id.empty();
+ }
+ };
+ struct Query {
+ std::vector<Promise<>> promises;
+ int32 active_queries{0};
+ Destination proxy;
+ int64 generation{0};
+ };
+
+ struct Node {
+ SetWithPosition<FileSourceId> file_source_ids;
+ unique_ptr<Query> query;
+ double last_successful_repair_time = -1e10;
+ };
+
+ struct FileSourceMessage {
+ FullMessageId full_message_id;
+ };
+ struct FileSourceUserPhoto {
+ int64 photo_id;
+ UserId user_id;
+ };
+ struct FileSourceChatPhoto {
+ ChatId chat_id;
+ };
+ struct FileSourceChannelPhoto {
+ ChannelId channel_id;
+ };
+ struct FileSourceWallpapers {
+ // empty
+ };
+ struct FileSourceWebPage {
+ string url;
+ };
+ struct FileSourceSavedAnimations {
+ // empty
+ };
+ struct FileSourceRecentStickers {
+ bool is_attached;
+ };
+ struct FileSourceFavoriteStickers {
+ // empty
+ };
+ struct FileSourceBackground {
+ BackgroundId background_id;
+ int64 access_hash;
+ };
+ struct FileSourceChatFull {
+ ChatId chat_id;
+ };
+ struct FileSourceChannelFull {
+ ChannelId channel_id;
+ };
+ struct FileSourceAppConfig {
+ // empty
+ };
+ struct FileSourceSavedRingtones {
+ // empty
+ };
+ struct FileSourceUserFull {
+ UserId user_id;
+ };
+ struct FileSourceAttachMenuBot {
+ UserId user_id;
+ };
+
+ // append only
+ using FileSource =
+ Variant<FileSourceMessage, FileSourceUserPhoto, FileSourceChatPhoto, FileSourceChannelPhoto, FileSourceWallpapers,
+ FileSourceWebPage, FileSourceSavedAnimations, FileSourceRecentStickers, FileSourceFavoriteStickers,
+ FileSourceBackground, FileSourceChatFull, FileSourceChannelFull, FileSourceAppConfig,
+ FileSourceSavedRingtones, FileSourceUserFull, FileSourceAttachMenuBot>;
+ WaitFreeVector<FileSource> file_sources_;
+
+ int64 query_generation_{0};
+
+ WaitFreeHashMap<NodeId, unique_ptr<Node>, FileIdHash> nodes_;
+
+ ActorShared<> parent_;
+
+ Node &add_node(NodeId node_id);
+
+ void run_node(NodeId node);
+ void send_query(Destination dest, FileSourceId file_source_id);
+ Destination on_query_result(Destination dest, FileSourceId file_source_id, Status status, int32 sub = 0);
+
+ template <class T>
+ FileSourceId add_file_source_id(T source, Slice source_str);
+
+ FileSourceId get_current_file_source_id() const;
+
+ void tear_down() final;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.hpp
new file mode 100644
index 0000000000..e56ac1d3fe
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/FileReferenceManager.hpp
@@ -0,0 +1,139 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/AttachMenuManager.h"
+#include "td/telegram/BackgroundManager.h"
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/NotificationSettingsManager.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/UserId.h"
+#include "td/telegram/WebPagesManager.h"
+
+#include "td/utils/common.h"
+#include "td/utils/overloaded.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void FileReferenceManager::store_file_source(FileSourceId file_source_id, StorerT &storer) const {
+ auto index = static_cast<size_t>(file_source_id.get()) - 1;
+ CHECK(index < file_sources_.size());
+ auto &source = file_sources_[index];
+ td::store(source.get_offset(), storer);
+ source.visit(overloaded([&](const FileSourceMessage &source) { td::store(source.full_message_id, storer); },
+ [&](const FileSourceUserPhoto &source) {
+ td::store(source.user_id, storer);
+ td::store(source.photo_id, storer);
+ },
+ [&](const FileSourceChatPhoto &source) { td::store(source.chat_id, storer); },
+ [&](const FileSourceChannelPhoto &source) { td::store(source.channel_id, storer); },
+ [&](const FileSourceWallpapers &source) {},
+ [&](const FileSourceWebPage &source) { td::store(source.url, storer); },
+ [&](const FileSourceSavedAnimations &source) {},
+ [&](const FileSourceRecentStickers &source) { td::store(source.is_attached, storer); },
+ [&](const FileSourceFavoriteStickers &source) {},
+ [&](const FileSourceBackground &source) {
+ td::store(source.background_id, storer);
+ td::store(source.access_hash, storer);
+ },
+ [&](const FileSourceChatFull &source) { td::store(source.chat_id, storer); },
+ [&](const FileSourceChannelFull &source) { td::store(source.channel_id, storer); },
+ [&](const FileSourceAppConfig &source) {}, [&](const FileSourceSavedRingtones &source) {},
+ [&](const FileSourceUserFull &source) { td::store(source.user_id, storer); },
+ [&](const FileSourceAttachMenuBot &source) { td::store(source.user_id, storer); }));
+}
+
+template <class ParserT>
+FileSourceId FileReferenceManager::parse_file_source(Td *td, ParserT &parser) {
+ auto type = parser.fetch_int();
+ switch (type) {
+ case 0: {
+ FullMessageId full_message_id;
+ td::parse(full_message_id, parser);
+ return td->messages_manager_->get_message_file_source_id(full_message_id);
+ }
+ case 1: {
+ UserId user_id;
+ int64 photo_id;
+ td::parse(user_id, parser);
+ td::parse(photo_id, parser);
+ return td->contacts_manager_->get_user_profile_photo_file_source_id(user_id, photo_id);
+ }
+ case 2: {
+ ChatId chat_id;
+ td::parse(chat_id, parser);
+ return FileSourceId(); // there is no need to repair chat photos
+ }
+ case 3: {
+ ChannelId channel_id;
+ td::parse(channel_id, parser);
+ return FileSourceId(); // there is no need to repair channel photos
+ }
+ case 4:
+ return FileSourceId(); // there is no way to repair old wallpapers
+ case 5: {
+ string url;
+ td::parse(url, parser);
+ return td->web_pages_manager_->get_url_file_source_id(url);
+ }
+ case 6:
+ return td->animations_manager_->get_saved_animations_file_source_id();
+ case 7: {
+ bool is_attached;
+ td::parse(is_attached, parser);
+ return td->stickers_manager_->get_recent_stickers_file_source_id(is_attached);
+ }
+ case 8:
+ return td->stickers_manager_->get_favorite_stickers_file_source_id();
+ case 9: {
+ BackgroundId background_id;
+ int64 access_hash;
+ td::parse(background_id, parser);
+ td::parse(access_hash, parser);
+ return td->background_manager_->get_background_file_source_id(background_id, access_hash);
+ }
+ case 10: {
+ ChatId chat_id;
+ td::parse(chat_id, parser);
+ return td->contacts_manager_->get_chat_full_file_source_id(chat_id);
+ }
+ case 11: {
+ ChannelId channel_id;
+ td::parse(channel_id, parser);
+ return td->contacts_manager_->get_channel_full_file_source_id(channel_id);
+ }
+ case 12:
+ return td->stickers_manager_->get_app_config_file_source_id();
+ case 13:
+ return td->notification_settings_manager_->get_saved_ringtones_file_source_id();
+ case 14: {
+ UserId user_id;
+ td::parse(user_id, parser);
+ return td->contacts_manager_->get_user_full_file_source_id(user_id);
+ }
+ case 15: {
+ UserId user_id;
+ td::parse(user_id, parser);
+ return td->attach_menu_manager_->get_attach_menu_bot_file_source_id(user_id);
+ }
+ default:
+ parser.set_error("Invalid type in FileSource");
+ return FileSourceId();
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/FolderId.h b/protocols/Telegram/tdlib/td/td/telegram/FolderId.h
new file mode 100644
index 0000000000..8e83d49b92
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/FolderId.h
@@ -0,0 +1,68 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class FolderId {
+ int32 id = 0;
+
+ public:
+ FolderId() = default;
+
+ explicit FolderId(int32 folder_id) : id(folder_id) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ FolderId(T folder_id) = delete;
+
+ int32 get() const {
+ return id;
+ }
+
+ bool operator==(const FolderId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const FolderId &other) const {
+ return id != other.id;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ storer.store_int(id);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ id = parser.fetch_int();
+ }
+
+ static FolderId main() {
+ return FolderId();
+ }
+ static FolderId archive() {
+ return FolderId(1);
+ }
+};
+
+struct FolderIdHash {
+ uint32 operator()(FolderId folder_id) const {
+ return Hash<int32>()(folder_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, FolderId folder_id) {
+ return string_builder << "folder " << folder_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopic.cpp b/protocols/Telegram/tdlib/td/td/telegram/ForumTopic.cpp
new file mode 100644
index 0000000000..f59a24bec4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopic.cpp
@@ -0,0 +1,52 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ForumTopic.h"
+
+#include "td/telegram/DraftMessage.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/logging.h"
+
+namespace td {
+
+ForumTopic::ForumTopic(Td *td, tl_object_ptr<telegram_api::ForumTopic> &&forum_topic_ptr) {
+ CHECK(forum_topic_ptr != nullptr);
+ if (forum_topic_ptr->get_id() != telegram_api::forumTopic::ID) {
+ LOG(INFO) << "Receive " << to_string(forum_topic_ptr);
+ return;
+ }
+ info_ = ForumTopicInfo(forum_topic_ptr);
+ auto *forum_topic = static_cast<telegram_api::forumTopic *>(forum_topic_ptr.get());
+
+ last_message_id_ = MessageId(ServerMessageId(forum_topic->top_message_));
+ is_pinned_ = forum_topic->pinned_;
+ unread_count_ = forum_topic->unread_count_;
+ last_read_inbox_message_id_ = MessageId(ServerMessageId(forum_topic->read_inbox_max_id_));
+ last_read_outbox_message_id_ = MessageId(ServerMessageId(forum_topic->read_outbox_max_id_));
+ unread_mention_count_ = forum_topic->unread_mentions_count_;
+ unread_reaction_count_ = forum_topic->unread_reactions_count_;
+ notification_settings_ =
+ get_dialog_notification_settings(std::move(forum_topic->notify_settings_), false, false, false, false);
+ draft_message_ = get_draft_message(td->contacts_manager_.get(), std::move(forum_topic->draft_));
+}
+
+td_api::object_ptr<td_api::forumTopic> ForumTopic::get_forum_topic_object(Td *td) const {
+ if (is_empty()) {
+ return nullptr;
+ }
+
+ // TODO draft_message = can_send_message(dialog_id, info_.get_top_thread_message_id()).is_ok() ? ... : nullptr;
+ // TODO last_message
+ auto draft_message = get_draft_message_object(draft_message_);
+ return td_api::make_object<td_api::forumTopic>(
+ info_.get_forum_topic_info_object(td), nullptr, is_pinned_, unread_count_, last_read_inbox_message_id_.get(),
+ last_read_outbox_message_id_.get(), unread_mention_count_, unread_reaction_count_,
+ get_chat_notification_settings_object(&notification_settings_), std::move(draft_message));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopic.h b/protocols/Telegram/tdlib/td/td/telegram/ForumTopic.h
new file mode 100644
index 0000000000..6a9d7ebd95
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopic.h
@@ -0,0 +1,50 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogNotificationSettings.h"
+#include "td/telegram/ForumTopicInfo.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+class DraftMessage;
+class Td;
+
+class ForumTopic {
+ ForumTopicInfo info_;
+ MessageId last_message_id_;
+ bool is_pinned_ = false;
+ int32 unread_count_ = 0;
+ MessageId last_read_inbox_message_id_;
+ MessageId last_read_outbox_message_id_;
+ int32 unread_mention_count_ = 0;
+ int32 unread_reaction_count_ = 0;
+ DialogNotificationSettings notification_settings_;
+ unique_ptr<DraftMessage> draft_message_;
+
+ public:
+ ForumTopic() = default;
+
+ ForumTopic(Td *td, tl_object_ptr<telegram_api::ForumTopic> &&forum_topic_ptr);
+
+ bool is_empty() const {
+ return info_.is_empty();
+ }
+
+ MessageId get_top_thread_message_id() const {
+ return info_.get_top_thread_message_id();
+ }
+
+ td_api::object_ptr<td_api::forumTopic> get_forum_topic_object(Td *td) const;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.cpp b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.cpp
new file mode 100644
index 0000000000..947ed3ae70
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.cpp
@@ -0,0 +1,42 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ForumTopicEditedData.h"
+
+namespace td {
+
+td_api::object_ptr<td_api::MessageContent> ForumTopicEditedData::get_message_content_object() const {
+ if (edit_is_closed_) {
+ return td_api::make_object<td_api::messageForumTopicIsClosedToggled>(is_closed_);
+ }
+ return td_api::make_object<td_api::messageForumTopicEdited>(title_, edit_icon_custom_emoji_id_,
+ icon_custom_emoji_id_.get());
+}
+
+bool operator==(const ForumTopicEditedData &lhs, const ForumTopicEditedData &rhs) {
+ return lhs.title_ == rhs.title_ && lhs.icon_custom_emoji_id_ == rhs.icon_custom_emoji_id_ &&
+ lhs.edit_icon_custom_emoji_id_ == rhs.edit_icon_custom_emoji_id_ &&
+ lhs.edit_is_closed_ == rhs.edit_is_closed_ && lhs.is_closed_ == rhs.is_closed_;
+}
+
+bool operator!=(const ForumTopicEditedData &lhs, const ForumTopicEditedData &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ForumTopicEditedData &topic_edited_data) {
+ if (!topic_edited_data.title_.empty()) {
+ string_builder << "set title to \"" << topic_edited_data.title_ << '"';
+ }
+ if (topic_edited_data.edit_icon_custom_emoji_id_) {
+ string_builder << "set icon to " << topic_edited_data.icon_custom_emoji_id_;
+ }
+ if (topic_edited_data.edit_is_closed_) {
+ string_builder << "set is_closed to " << topic_edited_data.is_closed_;
+ }
+ return string_builder;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.h b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.h
new file mode 100644
index 0000000000..681835e866
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.h
@@ -0,0 +1,64 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class ForumTopicEditedData {
+ string title_;
+ CustomEmojiId icon_custom_emoji_id_;
+ bool edit_icon_custom_emoji_id_ = false;
+ bool edit_is_closed_ = false;
+ bool is_closed_ = false;
+
+ friend bool operator==(const ForumTopicEditedData &lhs, const ForumTopicEditedData &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const ForumTopicEditedData &topic_edited_data);
+
+ friend class ForumTopicInfo;
+
+ public:
+ ForumTopicEditedData() = default;
+
+ ForumTopicEditedData(string &&title, bool edit_icon_custom_emoji_id, int64 icon_custom_emoji_id, bool edit_is_closed,
+ bool is_closed)
+ : title_(std::move(title))
+ , icon_custom_emoji_id_(icon_custom_emoji_id)
+ , edit_icon_custom_emoji_id_(edit_icon_custom_emoji_id)
+ , edit_is_closed_(edit_is_closed)
+ , is_closed_(is_closed) {
+ }
+
+ bool is_empty() const {
+ return title_.empty() && !edit_icon_custom_emoji_id_ && !edit_is_closed_;
+ }
+
+ const string &get_title() const {
+ return title_;
+ }
+
+ td_api::object_ptr<td_api::MessageContent> get_message_content_object() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+bool operator==(const ForumTopicEditedData &lhs, const ForumTopicEditedData &rhs);
+bool operator!=(const ForumTopicEditedData &lhs, const ForumTopicEditedData &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ForumTopicEditedData &topic_edited_data);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.hpp b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.hpp
new file mode 100644
index 0000000000..6385da0a2d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicEditedData.hpp
@@ -0,0 +1,54 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/ForumTopicEditedData.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void ForumTopicEditedData::store(StorerT &storer) const {
+ bool has_title = !title_.empty();
+ bool has_icon_custom_emoji_id = icon_custom_emoji_id_.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(edit_icon_custom_emoji_id_);
+ STORE_FLAG(edit_is_closed_);
+ STORE_FLAG(is_closed_);
+ STORE_FLAG(has_title);
+ STORE_FLAG(has_icon_custom_emoji_id);
+ END_STORE_FLAGS();
+ if (has_title) {
+ td::store(title_, storer);
+ }
+ if (has_icon_custom_emoji_id) {
+ td::store(icon_custom_emoji_id_, storer);
+ }
+}
+
+template <class ParserT>
+void ForumTopicEditedData::parse(ParserT &parser) {
+ bool has_title;
+ bool has_icon_custom_emoji_id;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(edit_icon_custom_emoji_id_);
+ PARSE_FLAG(edit_is_closed_);
+ PARSE_FLAG(is_closed_);
+ PARSE_FLAG(has_title);
+ PARSE_FLAG(has_icon_custom_emoji_id);
+ END_PARSE_FLAGS();
+ if (has_title) {
+ td::parse(title_, parser);
+ }
+ if (has_icon_custom_emoji_id) {
+ td::parse(icon_custom_emoji_id_, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.cpp b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.cpp
new file mode 100644
index 0000000000..556f1b8cb1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.cpp
@@ -0,0 +1,43 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ForumTopicIcon.h"
+
+namespace td {
+
+ForumTopicIcon::ForumTopicIcon(int32 color, int64 custom_emoji_id)
+ : color_(color & 0xFFFFFF), custom_emoji_id_(custom_emoji_id) {
+}
+
+bool ForumTopicIcon::edit_custom_emoji_id(CustomEmojiId custom_emoji_id) {
+ if (custom_emoji_id_ != custom_emoji_id) {
+ custom_emoji_id_ = custom_emoji_id;
+ return true;
+ }
+ return false;
+}
+
+td_api::object_ptr<td_api::forumTopicIcon> ForumTopicIcon::get_forum_topic_icon_object() const {
+ return td_api::make_object<td_api::forumTopicIcon>(color_, custom_emoji_id_.get());
+}
+
+bool operator==(const ForumTopicIcon &lhs, const ForumTopicIcon &rhs) {
+ return lhs.color_ == rhs.color_ && lhs.custom_emoji_id_ == rhs.custom_emoji_id_;
+}
+
+bool operator!=(const ForumTopicIcon &lhs, const ForumTopicIcon &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ForumTopicIcon &topic_icon) {
+ string_builder << "icon color " << topic_icon.color_;
+ if (topic_icon.custom_emoji_id_.is_valid()) {
+ string_builder << " and " << topic_icon.custom_emoji_id_;
+ }
+ return string_builder;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.h b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.h
new file mode 100644
index 0000000000..3f79329161
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.h
@@ -0,0 +1,45 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class ForumTopicIcon {
+ int32 color_ = 0x6FB9F0;
+ CustomEmojiId custom_emoji_id_;
+
+ friend bool operator==(const ForumTopicIcon &lhs, const ForumTopicIcon &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const ForumTopicIcon &topic_icon);
+
+ public:
+ ForumTopicIcon() = default;
+ ForumTopicIcon(int32 color, int64 custom_emoji_id);
+
+ bool edit_custom_emoji_id(CustomEmojiId custom_emoji_id);
+
+ td_api::object_ptr<td_api::forumTopicIcon> get_forum_topic_icon_object() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+bool operator==(const ForumTopicIcon &lhs, const ForumTopicIcon &rhs);
+bool operator!=(const ForumTopicIcon &lhs, const ForumTopicIcon &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ForumTopicIcon &topic_icon);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.hpp b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.hpp
new file mode 100644
index 0000000000..2d4d6245e6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicIcon.hpp
@@ -0,0 +1,40 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/ForumTopicIcon.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void ForumTopicIcon::store(StorerT &storer) const {
+ bool has_custom_emoji_id = custom_emoji_id_.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_custom_emoji_id);
+ END_STORE_FLAGS();
+ td::store(color_, storer);
+ if (has_custom_emoji_id) {
+ td::store(custom_emoji_id_, storer);
+ }
+}
+
+template <class ParserT>
+void ForumTopicIcon::parse(ParserT &parser) {
+ bool has_custom_emoji_id;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_custom_emoji_id);
+ END_PARSE_FLAGS();
+ td::parse(color_, parser);
+ if (has_custom_emoji_id) {
+ td::parse(custom_emoji_id_, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicInfo.cpp b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicInfo.cpp
new file mode 100644
index 0000000000..e24577d9ec
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicInfo.cpp
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ForumTopicInfo.h"
+
+#include "td/telegram/MessageSender.h"
+#include "td/telegram/ServerMessageId.h"
+
+#include "td/utils/logging.h"
+
+namespace td {
+
+ForumTopicInfo::ForumTopicInfo(const tl_object_ptr<telegram_api::ForumTopic> &forum_topic_ptr) {
+ CHECK(forum_topic_ptr != nullptr);
+ if (forum_topic_ptr->get_id() != telegram_api::forumTopic::ID) {
+ LOG(ERROR) << "Receive " << to_string(forum_topic_ptr);
+ return;
+ }
+ const auto *forum_topic = static_cast<const telegram_api::forumTopic *>(forum_topic_ptr.get());
+
+ top_thread_message_id_ = MessageId(ServerMessageId(forum_topic->id_));
+ title_ = forum_topic->title_;
+ icon_ = ForumTopicIcon(forum_topic->icon_color_, forum_topic->icon_emoji_id_);
+ creation_date_ = forum_topic->date_;
+ creator_dialog_id_ = DialogId(forum_topic->from_id_);
+ is_outgoing_ = forum_topic->my_;
+ is_closed_ = forum_topic->closed_;
+
+ if (creation_date_ <= 0 || !top_thread_message_id_.is_valid() || !creator_dialog_id_.is_valid()) {
+ LOG(ERROR) << "Receive " << to_string(forum_topic_ptr);
+ *this = ForumTopicInfo();
+ }
+}
+
+bool ForumTopicInfo::apply_edited_data(const ForumTopicEditedData &edited_data) {
+ bool is_changed = false;
+ if (!edited_data.title_.empty() && edited_data.title_ != title_) {
+ title_ = edited_data.title_;
+ is_changed = true;
+ }
+ if (edited_data.edit_icon_custom_emoji_id_ && icon_.edit_custom_emoji_id(edited_data.icon_custom_emoji_id_)) {
+ is_changed = true;
+ }
+ if (edited_data.edit_is_closed_ && edited_data.is_closed_ != is_closed_) {
+ is_closed_ = edited_data.is_closed_;
+ is_changed = true;
+ }
+ return is_changed;
+}
+
+td_api::object_ptr<td_api::forumTopicInfo> ForumTopicInfo::get_forum_topic_info_object(Td *td) const {
+ if (is_empty()) {
+ return nullptr;
+ }
+
+ auto creator_id = get_message_sender_object_const(td, creator_dialog_id_, "get_forum_topic_info_object");
+ return td_api::make_object<td_api::forumTopicInfo>(top_thread_message_id_.get(), title_,
+ icon_.get_forum_topic_icon_object(), creation_date_,
+ std::move(creator_id), is_outgoing_, is_closed_);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ForumTopicInfo &topic_info) {
+ return string_builder << "Forum topic " << topic_info.top_thread_message_id_.get() << '/' << topic_info.title_
+ << " by " << topic_info.creator_dialog_id_ << " with " << topic_info.icon_;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicInfo.h b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicInfo.h
new file mode 100644
index 0000000000..aa1ae03d05
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicInfo.h
@@ -0,0 +1,77 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/ForumTopicEditedData.h"
+#include "td/telegram/ForumTopicIcon.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class Td;
+
+class ForumTopicInfo {
+ MessageId top_thread_message_id_;
+ string title_;
+ ForumTopicIcon icon_;
+ int32 creation_date_ = 0;
+ DialogId creator_dialog_id_;
+ bool is_outgoing_ = false;
+ bool is_closed_ = false;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const ForumTopicInfo &topic_info);
+
+ public:
+ ForumTopicInfo() = default;
+
+ explicit ForumTopicInfo(const tl_object_ptr<telegram_api::ForumTopic> &forum_topic_ptr);
+
+ ForumTopicInfo(MessageId top_thread_message_id, string title, ForumTopicIcon icon, int32 creation_date,
+ DialogId creator_dialog_id, bool is_outgoing, bool is_closed)
+ : top_thread_message_id_(top_thread_message_id)
+ , title_(std::move(title))
+ , icon_(std::move(icon))
+ , creation_date_(creation_date)
+ , creator_dialog_id_(creator_dialog_id)
+ , is_outgoing_(is_outgoing)
+ , is_closed_(is_closed) {
+ }
+
+ bool is_empty() const {
+ return !top_thread_message_id_.is_valid();
+ }
+
+ MessageId get_top_thread_message_id() const {
+ return top_thread_message_id_;
+ }
+
+ DialogId get_creator_dialog_id() const {
+ return creator_dialog_id_;
+ }
+
+ bool is_outgoing() const {
+ return is_outgoing_;
+ }
+
+ bool is_closed() const {
+ return is_closed_;
+ }
+
+ bool apply_edited_data(const ForumTopicEditedData &edited_data);
+
+ td_api::object_ptr<td_api::forumTopicInfo> get_forum_topic_info_object(Td *td) const;
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ForumTopicInfo &topic_info);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicManager.cpp
new file mode 100644
index 0000000000..8abac39bf8
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicManager.cpp
@@ -0,0 +1,358 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ForumTopicManager.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/ForumTopicIcon.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UpdatesManager.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/Random.h"
+
+namespace td {
+
+class CreateForumTopicQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::forumTopicInfo>> promise_;
+ ChannelId channel_id_;
+ DialogId creator_dialog_id_;
+ int64 random_id_;
+
+ public:
+ explicit CreateForumTopicQuery(Promise<td_api::object_ptr<td_api::forumTopicInfo>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, const string &title, int32 icon_color, CustomEmojiId icon_custom_emoji_id,
+ DialogId as_dialog_id) {
+ channel_id_ = channel_id;
+ creator_dialog_id_ = DialogId(td_->contacts_manager_->get_my_id());
+
+ int32 flags = 0;
+ if (icon_color != -1) {
+ flags |= telegram_api::channels_createForumTopic::ICON_COLOR_MASK;
+ }
+ if (icon_custom_emoji_id.is_valid()) {
+ flags |= telegram_api::channels_createForumTopic::ICON_EMOJI_ID_MASK;
+ }
+ tl_object_ptr<telegram_api::InputPeer> as_input_peer;
+ if (as_dialog_id.is_valid()) {
+ as_input_peer = td_->messages_manager_->get_input_peer(as_dialog_id, AccessRights::Write);
+ if (as_input_peer != nullptr) {
+ flags |= telegram_api::channels_createForumTopic::SEND_AS_MASK;
+ creator_dialog_id_ = as_dialog_id;
+ }
+ }
+
+ do {
+ random_id_ = Random::secure_int64();
+ } while (random_id_ == 0);
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_createForumTopic(flags, std::move(input_channel), title, icon_color,
+ icon_custom_emoji_id.get(), random_id_, std::move(as_input_peer)),
+ {{channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_createForumTopic>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for CreateForumTopicQuery: " << to_string(ptr);
+ auto message = UpdatesManager::get_message_by_random_id(ptr.get(), DialogId(channel_id_), random_id_);
+ if (message == nullptr || message->get_id() != telegram_api::messageService::ID) {
+ LOG(ERROR) << "Receive invalid result for CreateForumTopicQuery: " << to_string(ptr);
+ return promise_.set_error(Status::Error(400, "Invalid result received"));
+ }
+ auto service_message = static_cast<const telegram_api::messageService *>(message);
+ if (service_message->action_->get_id() != telegram_api::messageActionTopicCreate::ID) {
+ LOG(ERROR) << "Receive invalid result for CreateForumTopicQuery: " << to_string(ptr);
+ return promise_.set_error(Status::Error(400, "Invalid result received"));
+ }
+
+ auto action = static_cast<const telegram_api::messageActionTopicCreate *>(service_message->action_.get());
+ auto forum_topic_info =
+ td::make_unique<ForumTopicInfo>(MessageId(ServerMessageId(service_message->id_)), action->title_,
+ ForumTopicIcon(action->icon_color_, action->icon_emoji_id_),
+ service_message->date_, creator_dialog_id_, true, false);
+ td_->updates_manager_->on_get_updates(
+ std::move(ptr),
+ PromiseCreator::lambda([dialog_id = DialogId(channel_id_), forum_topic_info = std::move(forum_topic_info),
+ promise = std::move(promise_)](Unit result) mutable {
+ send_closure(G()->forum_topic_manager(), &ForumTopicManager::on_forum_topic_created, dialog_id,
+ std::move(forum_topic_info), std::move(promise));
+ }));
+ }
+
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "CreateForumTopicQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class EditForumTopicQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ ChannelId channel_id_;
+ MessageId top_thread_message_id_;
+
+ public:
+ explicit EditForumTopicQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, MessageId top_thread_message_id, const string &title,
+ CustomEmojiId icon_custom_emoji_id) {
+ channel_id_ = channel_id;
+ top_thread_message_id_ = top_thread_message_id;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+
+ int32 flags =
+ telegram_api::channels_editForumTopic::TITLE_MASK | telegram_api::channels_editForumTopic::ICON_EMOJI_ID_MASK;
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_editForumTopic(flags, std::move(input_channel),
+ top_thread_message_id.get_server_message_id().get(), title,
+ icon_custom_emoji_id.get(), false),
+ {{channel_id}}));
+ }
+
+ void send(ChannelId channel_id, MessageId top_thread_message_id, bool is_closed) {
+ channel_id_ = channel_id;
+ top_thread_message_id_ = top_thread_message_id;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+
+ int32 flags = telegram_api::channels_editForumTopic::CLOSED_MASK;
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_editForumTopic(flags, std::move(input_channel),
+ top_thread_message_id.get_server_message_id().get(), string(), 0,
+ is_closed),
+ {{channel_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_editForumTopic>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for EditForumTopicQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "TOPIC_NOT_MODIFIED" && !td_->auth_manager_->is_bot()) {
+ return promise_.set_value(Unit());
+ }
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "EditForumTopicQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+ForumTopicManager::ForumTopicManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+}
+
+ForumTopicManager::~ForumTopicManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), dialog_topics_);
+}
+
+void ForumTopicManager::tear_down() {
+ parent_.reset();
+}
+
+void ForumTopicManager::create_forum_topic(DialogId dialog_id, string &&title,
+ td_api::object_ptr<td_api::forumTopicIcon> &&icon,
+ Promise<td_api::object_ptr<td_api::forumTopicInfo>> &&promise) {
+ TRY_STATUS_PROMISE(promise, is_forum(dialog_id));
+ auto channel_id = dialog_id.get_channel_id();
+
+ if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_create_topics()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to create a topic"));
+ }
+
+ auto new_title = clean_name(std::move(title), MAX_FORUM_TOPIC_TITLE_LENGTH);
+ if (new_title.empty()) {
+ return promise.set_error(Status::Error(400, "Title must be non-empty"));
+ }
+
+ int32 icon_color = -1;
+ CustomEmojiId icon_custom_emoji_id;
+ if (icon != nullptr) {
+ icon_color = icon->color_;
+ if (icon_color < 0 || icon_color > 0xFFFFFF) {
+ return promise.set_error(Status::Error(400, "Invalid icon color specified"));
+ }
+ icon_custom_emoji_id = CustomEmojiId(icon->custom_emoji_id_);
+ }
+
+ DialogId as_dialog_id = td_->messages_manager_->get_dialog_default_send_message_as_dialog_id(dialog_id);
+
+ td_->create_handler<CreateForumTopicQuery>(std::move(promise))
+ ->send(channel_id, new_title, icon_color, icon_custom_emoji_id, as_dialog_id);
+}
+
+void ForumTopicManager::on_forum_topic_created(DialogId dialog_id, unique_ptr<ForumTopicInfo> &&forum_topic_info,
+ Promise<td_api::object_ptr<td_api::forumTopicInfo>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto topic_info = add_topic_info(dialog_id, std::move(forum_topic_info));
+ CHECK(topic_info != nullptr);
+ promise.set_value(topic_info->get_forum_topic_info_object(td_));
+}
+
+void ForumTopicManager::edit_forum_topic(DialogId dialog_id, MessageId top_thread_message_id, string &&title,
+ CustomEmojiId icon_custom_emoji_id, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, is_forum(dialog_id));
+ auto channel_id = dialog_id.get_channel_id();
+
+ if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Invalid message thread identifier specified"));
+ }
+
+ if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_edit_topics()) {
+ auto topic_info = get_topic_info(dialog_id, top_thread_message_id);
+ if (topic_info != nullptr && !topic_info->is_outgoing()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to edit the topic"));
+ }
+ }
+
+ auto new_title = clean_name(std::move(title), MAX_FORUM_TOPIC_TITLE_LENGTH);
+ if (new_title.empty()) {
+ return promise.set_error(Status::Error(400, "Title must be non-empty"));
+ }
+
+ td_->create_handler<EditForumTopicQuery>(std::move(promise))
+ ->send(channel_id, top_thread_message_id, new_title, icon_custom_emoji_id);
+}
+
+void ForumTopicManager::toggle_forum_topic_is_closed(DialogId dialog_id, MessageId top_thread_message_id,
+ bool is_closed, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, is_forum(dialog_id));
+ auto channel_id = dialog_id.get_channel_id();
+
+ if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Invalid message thread identifier specified"));
+ }
+
+ if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_edit_topics()) {
+ auto topic_info = get_topic_info(dialog_id, top_thread_message_id);
+ if (topic_info != nullptr && !topic_info->is_outgoing()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to close or open the topic"));
+ }
+ }
+
+ td_->create_handler<EditForumTopicQuery>(std::move(promise))->send(channel_id, top_thread_message_id, is_closed);
+}
+
+void ForumTopicManager::delete_forum_topic(DialogId dialog_id, MessageId top_thread_message_id,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, is_forum(dialog_id));
+ auto channel_id = dialog_id.get_channel_id();
+
+ if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Invalid message thread identifier specified"));
+ }
+
+ if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_delete_messages()) {
+ auto topic_info = get_topic_info(dialog_id, top_thread_message_id);
+ if (topic_info != nullptr && !topic_info->is_outgoing()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to delete the topic"));
+ }
+ }
+
+ td_->messages_manager_->delete_topic_history(dialog_id, top_thread_message_id, std::move(promise));
+}
+
+void ForumTopicManager::on_forum_topic_edited(DialogId dialog_id, MessageId top_thread_message_id,
+ const ForumTopicEditedData &edited_data) {
+ auto topic_info = get_topic_info(dialog_id, top_thread_message_id);
+ if (topic_info == nullptr) {
+ return;
+ }
+ if (topic_info->apply_edited_data(edited_data)) {
+ send_update_forum_topic_info(dialog_id, topic_info);
+ }
+}
+
+Status ForumTopicManager::is_forum(DialogId dialog_id) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "ForumTopicManager::is_forum")) {
+ return Status::Error(400, "Chat not found");
+ }
+ if (dialog_id.get_type() != DialogType::Channel ||
+ !td_->contacts_manager_->is_forum_channel(dialog_id.get_channel_id())) {
+ return Status::Error(400, "The chat is not a forum");
+ }
+ return Status::OK();
+}
+
+ForumTopicInfo *ForumTopicManager::add_topic_info(DialogId dialog_id, unique_ptr<ForumTopicInfo> &&forum_topic_info) {
+ CHECK(forum_topic_info != nullptr);
+ auto *dialog_info = dialog_topics_.get_pointer(dialog_id);
+ if (dialog_info == nullptr) {
+ dialog_topics_.set(dialog_id, make_unique<DialogTopics>());
+ dialog_info = dialog_topics_.get_pointer(dialog_id);
+ CHECK(dialog_info != nullptr);
+ }
+
+ MessageId top_thread_message_id = forum_topic_info->get_top_thread_message_id();
+ auto topic_info = dialog_info->topic_infos_.get_pointer(top_thread_message_id);
+ if (topic_info == nullptr) {
+ dialog_info->topic_infos_.set(top_thread_message_id, std::move(forum_topic_info));
+ topic_info = get_topic_info(dialog_id, top_thread_message_id);
+ CHECK(topic_info != nullptr);
+ send_update_forum_topic_info(dialog_id, topic_info);
+ }
+ return topic_info;
+}
+
+ForumTopicInfo *ForumTopicManager::get_topic_info(DialogId dialog_id, MessageId top_thread_message_id) {
+ auto *dialog_info = dialog_topics_.get_pointer(dialog_id);
+ if (dialog_info == nullptr) {
+ return nullptr;
+ }
+ return dialog_info->topic_infos_.get_pointer(top_thread_message_id);
+}
+
+const ForumTopicInfo *ForumTopicManager::get_topic_info(DialogId dialog_id, MessageId top_thread_message_id) const {
+ auto *dialog_info = dialog_topics_.get_pointer(dialog_id);
+ if (dialog_info == nullptr) {
+ return nullptr;
+ }
+ return dialog_info->topic_infos_.get_pointer(top_thread_message_id);
+}
+
+td_api::object_ptr<td_api::updateForumTopicInfo> ForumTopicManager::get_update_forum_topic_info(
+ DialogId dialog_id, const ForumTopicInfo *topic_info) const {
+ return td_api::make_object<td_api::updateForumTopicInfo>(dialog_id.get(),
+ topic_info->get_forum_topic_info_object(td_));
+}
+
+void ForumTopicManager::send_update_forum_topic_info(DialogId dialog_id, const ForumTopicInfo *topic_info) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ send_closure(G()->td(), &Td::send_update, get_update_forum_topic_info(dialog_id, topic_info));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ForumTopicManager.h b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicManager.h
new file mode 100644
index 0000000000..e386591116
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ForumTopicManager.h
@@ -0,0 +1,81 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/ForumTopicEditedData.h"
+#include "td/telegram/ForumTopicInfo.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/td_api.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+#include "td/utils/WaitFreeHashMap.h"
+
+namespace td {
+
+class Td;
+
+class ForumTopicManager final : public Actor {
+ public:
+ ForumTopicManager(Td *td, ActorShared<> parent);
+ ForumTopicManager(const ForumTopicManager &) = delete;
+ ForumTopicManager &operator=(const ForumTopicManager &) = delete;
+ ForumTopicManager(ForumTopicManager &&) = delete;
+ ForumTopicManager &operator=(ForumTopicManager &&) = delete;
+ ~ForumTopicManager() final;
+
+ void create_forum_topic(DialogId dialog_id, string &&title, td_api::object_ptr<td_api::forumTopicIcon> &&icon,
+ Promise<td_api::object_ptr<td_api::forumTopicInfo>> &&promise);
+
+ void on_forum_topic_created(DialogId dialog_id, unique_ptr<ForumTopicInfo> &&forum_topic_info,
+ Promise<td_api::object_ptr<td_api::forumTopicInfo>> &&promise);
+
+ void edit_forum_topic(DialogId dialog_id, MessageId top_thread_message_id, string &&title,
+ CustomEmojiId icon_custom_emoji_id, Promise<Unit> &&promise);
+
+ void toggle_forum_topic_is_closed(DialogId dialog_id, MessageId top_thread_message_id, bool is_closed,
+ Promise<Unit> &&promise);
+
+ void delete_forum_topic(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> &&promise);
+
+ void on_forum_topic_edited(DialogId dialog_id, MessageId top_thread_message_id,
+ const ForumTopicEditedData &edited_data);
+
+ private:
+ static constexpr size_t MAX_FORUM_TOPIC_TITLE_LENGTH = 128; // server side limit for forum topic title
+
+ struct DialogTopics {
+ WaitFreeHashMap<MessageId, unique_ptr<ForumTopicInfo>, MessageIdHash> topic_infos_;
+ };
+
+ void tear_down() final;
+
+ Status is_forum(DialogId dialog_id);
+
+ ForumTopicInfo *add_topic_info(DialogId dialog_id, unique_ptr<ForumTopicInfo> &&forum_topic_info);
+
+ ForumTopicInfo *get_topic_info(DialogId dialog_id, MessageId top_thread_message_id);
+
+ const ForumTopicInfo *get_topic_info(DialogId dialog_id, MessageId top_thread_message_id) const;
+
+ td_api::object_ptr<td_api::updateForumTopicInfo> get_update_forum_topic_info(DialogId dialog_id,
+ const ForumTopicInfo *topic_info) const;
+
+ void send_update_forum_topic_info(DialogId dialog_id, const ForumTopicInfo *topic_info) const;
+
+ Td *td_;
+ ActorShared<> parent_;
+
+ WaitFreeHashMap<DialogId, unique_ptr<DialogTopics>, DialogIdHash> dialog_topics_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/FullMessageId.h b/protocols/Telegram/tdlib/td/td/telegram/FullMessageId.h
new file mode 100644
index 0000000000..5fb83e0125
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/FullMessageId.h
@@ -0,0 +1,68 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+struct FullMessageId {
+ private:
+ DialogId dialog_id;
+ MessageId message_id;
+
+ public:
+ FullMessageId() : dialog_id(), message_id() {
+ }
+
+ FullMessageId(DialogId dialog_id, MessageId message_id) : dialog_id(dialog_id), message_id(message_id) {
+ }
+
+ bool operator==(const FullMessageId &other) const {
+ return dialog_id == other.dialog_id && message_id == other.message_id;
+ }
+
+ bool operator!=(const FullMessageId &other) const {
+ return !(*this == other);
+ }
+
+ DialogId get_dialog_id() const {
+ return dialog_id;
+ }
+ MessageId get_message_id() const {
+ return message_id;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ dialog_id.store(storer);
+ message_id.store(storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ dialog_id.parse(parser);
+ message_id.parse(parser);
+ }
+};
+
+struct FullMessageIdHash {
+ uint32 operator()(FullMessageId full_message_id) const {
+ return DialogIdHash()(full_message_id.get_dialog_id()) * 2023654985u +
+ MessageIdHash()(full_message_id.get_message_id());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, FullMessageId full_message_id) {
+ return string_builder << full_message_id.get_message_id() << " in " << full_message_id.get_dialog_id();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Game.cpp b/protocols/Telegram/tdlib/td/td/telegram/Game.cpp
index 000b4575d2..7523003a55 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Game.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Game.cpp
@@ -1,17 +1,16 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Game.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
#include "td/telegram/AnimationsManager.h"
#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Document.h"
#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/misc.h"
#include "td/telegram/Photo.h"
#include "td/telegram/Td.h"
@@ -20,12 +19,15 @@
namespace td {
-Game::Game(Td *td, tl_object_ptr<telegram_api::game> &&game, DialogId owner_dialog_id)
+Game::Game(Td *td, UserId bot_user_id, tl_object_ptr<telegram_api::game> &&game, FormattedText text,
+ DialogId owner_dialog_id)
: Game(td, std::move(game->title_), std::move(game->description_), std::move(game->photo_),
std::move(game->document_), owner_dialog_id) {
id_ = game->id_;
access_hash_ = game->access_hash_;
+ bot_user_id_ = bot_user_id.is_valid() ? bot_user_id : UserId();
short_name_ = game->short_name_;
+ text_ = std::move(text);
}
Game::Game(Td *td, string title, string description, tl_object_ptr<telegram_api::Photo> &&photo,
@@ -33,29 +35,20 @@ Game::Game(Td *td, string title, string description, tl_object_ptr<telegram_api:
: title_(std::move(title)), description_(std::move(description)) {
CHECK(td != nullptr);
CHECK(photo != nullptr);
- if (photo->get_id() == telegram_api::photo::ID) {
- photo_ = get_photo(td->file_manager_.get(), move_tl_object_as<telegram_api::photo>(photo), owner_dialog_id);
+ photo_ = get_photo(td->file_manager_.get(), std::move(photo), owner_dialog_id);
+ if (photo_.is_empty()) {
+ LOG(ERROR) << "Receive empty photo for game " << title_;
+ photo_.id = 0; // to prevent null photo in td_api
}
if (document != nullptr) {
int32 document_id = document->get_id();
if (document_id == telegram_api::document::ID) {
auto parsed_document =
td->documents_manager_->on_get_document(move_tl_object_as<telegram_api::document>(document), owner_dialog_id);
- switch (parsed_document.first) {
- case DocumentsManager::DocumentType::Animation:
- animation_file_id_ = parsed_document.second;
- break;
- case DocumentsManager::DocumentType::Audio:
- case DocumentsManager::DocumentType::General:
- case DocumentsManager::DocumentType::Sticker:
- case DocumentsManager::DocumentType::Video:
- case DocumentsManager::DocumentType::VideoNote:
- case DocumentsManager::DocumentType::VoiceNote:
- case DocumentsManager::DocumentType::Unknown:
- LOG(ERROR) << "Receive non-animation document in the game";
- break;
- default:
- UNREACHABLE();
+ if (parsed_document.type == Document::Type::Animation) {
+ animation_file_id_ = parsed_document.file_id;
+ } else {
+ LOG(ERROR) << "Receive non-animation document in the game";
}
}
}
@@ -65,40 +58,42 @@ Game::Game(UserId bot_user_id, string short_name) : bot_user_id_(bot_user_id), s
if (!bot_user_id_.is_valid()) {
bot_user_id_ = UserId();
}
+ photo_.id = 0; // to prevent null photo in td_api
}
-bool Game::empty() const {
+bool Game::is_empty() const {
return short_name_.empty();
}
-void Game::set_bot_user_id(UserId bot_user_id) {
- if (bot_user_id.is_valid()) {
- bot_user_id_ = bot_user_id;
- } else {
- bot_user_id_ = UserId();
- }
-}
-
UserId Game::get_bot_user_id() const {
return bot_user_id_;
}
-void Game::set_message_text(FormattedText &&text) {
- text_ = std::move(text);
+vector<FileId> Game::get_file_ids(const Td *td) const {
+ auto result = photo_get_file_ids(photo_);
+ Document(Document::Type::Animation, animation_file_id_).append_file_ids(td, result);
+ return result;
}
-tl_object_ptr<td_api::game> Game::get_game_object(const Td *td) const {
- return make_tl_object<td_api::game>(
- id_, short_name_, title_, get_formatted_text_object(text_), description_,
- get_photo_object(td->file_manager_.get(), &photo_),
- td->animations_manager_->get_animation_object(animation_file_id_, "get_game_object"));
+const FormattedText &Game::get_text() const {
+ return text_;
+}
+
+tl_object_ptr<td_api::game> Game::get_game_object(Td *td, bool skip_bot_commands) const {
+ return make_tl_object<td_api::game>(id_, short_name_, title_, get_formatted_text_object(text_, skip_bot_commands, -1),
+ description_, get_photo_object(td->file_manager_.get(), photo_),
+ td->animations_manager_->get_animation_object(animation_file_id_));
+}
+
+bool Game::has_input_media() const {
+ return bot_user_id_.is_valid();
}
tl_object_ptr<telegram_api::inputMediaGame> Game::get_input_media_game(const Td *td) const {
- auto input_user = td->contacts_manager_->get_input_user(bot_user_id_);
- CHECK(input_user != nullptr);
+ auto r_input_user = td->contacts_manager_->get_input_user(bot_user_id_);
+ CHECK(r_input_user.is_ok());
return make_tl_object<telegram_api::inputMediaGame>(
- make_tl_object<telegram_api::inputGameShortName>(std::move(input_user), short_name_));
+ make_tl_object<telegram_api::inputGameShortName>(r_input_user.move_as_ok(), short_name_));
}
bool operator==(const Game &lhs, const Game &rhs) {
@@ -112,10 +107,31 @@ bool operator!=(const Game &lhs, const Game &rhs) {
}
StringBuilder &operator<<(StringBuilder &string_builder, const Game &game) {
- return string_builder << "Game[id = " << game.id_ << ", access_hash = " << game.access_hash_
+ return string_builder << "Game[ID = " << game.id_ << ", access_hash = " << game.access_hash_
<< ", bot = " << game.bot_user_id_ << ", short_name = " << game.short_name_
<< ", title = " << game.title_ << ", description = " << game.description_
<< ", photo = " << game.photo_ << ", animation_file_id = " << game.animation_file_id_ << "]";
}
+Result<Game> process_input_message_game(const ContactsManager *contacts_manager,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
+ CHECK(input_message_content != nullptr);
+ CHECK(input_message_content->get_id() == td_api::inputMessageGame::ID);
+ auto input_message_game = move_tl_object_as<td_api::inputMessageGame>(input_message_content);
+
+ UserId bot_user_id(input_message_game->bot_user_id_);
+ TRY_STATUS(contacts_manager->get_input_user(bot_user_id));
+
+ if (!clean_input_string(input_message_game->game_short_name_)) {
+ return Status::Error(400, "Game short name must be encoded in UTF-8");
+ }
+
+ // TODO validate game_short_name
+ if (input_message_game->game_short_name_.empty()) {
+ return Status::Error(400, "Game short name must be non-empty");
+ }
+
+ return Game(bot_user_id, std::move(input_message_game->game_short_name_));
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Game.h b/protocols/Telegram/tdlib/td/td/telegram/Game.h
index 4afe3031cc..8f37584e9f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Game.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Game.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,16 +10,17 @@
#include "td/telegram/files/FileId.h"
#include "td/telegram/MessageEntity.h"
#include "td/telegram/Photo.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/utils/common.h"
+#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
namespace td {
+class ContactsManager;
class Td;
class Game {
@@ -42,22 +43,27 @@ class Game {
public:
Game() = default;
- Game(Td *td, tl_object_ptr<telegram_api::game> &&game, DialogId owner_dialog_id);
+ Game(Td *td, UserId bot_user_id, tl_object_ptr<telegram_api::game> &&game, FormattedText text,
+ DialogId owner_dialog_id);
+ // for inline results
Game(Td *td, string title, string description, tl_object_ptr<telegram_api::Photo> &&photo,
tl_object_ptr<telegram_api::Document> &&document, DialogId owner_dialog_id);
+ // for outgoing messages
Game(UserId bot_user_id, string short_name);
- bool empty() const;
-
- void set_bot_user_id(UserId bot_user_id);
+ bool is_empty() const;
UserId get_bot_user_id() const;
- void set_message_text(FormattedText &&text);
+ vector<FileId> get_file_ids(const Td *td) const;
+
+ const FormattedText &get_text() const;
- tl_object_ptr<td_api::game> get_game_object(const Td *td) const;
+ tl_object_ptr<td_api::game> get_game_object(Td *td, bool skip_bot_commands) const;
+
+ bool has_input_media() const;
tl_object_ptr<telegram_api::inputMediaGame> get_input_media_game(const Td *td) const;
@@ -73,4 +79,8 @@ bool operator!=(const Game &lhs, const Game &rhs);
StringBuilder &operator<<(StringBuilder &string_builder, const Game &game);
+Result<Game> process_input_message_game(const ContactsManager *contacts_manager,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content)
+ TD_WARN_UNUSED_RESULT;
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Game.hpp b/protocols/Telegram/tdlib/td/td/telegram/Game.hpp
index ba5b773d69..d827db1c98 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Game.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Game.hpp
@@ -1,14 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/telegram/Game.h"
#include "td/telegram/AnimationsManager.hpp"
+#include "td/telegram/MessageEntity.hpp"
#include "td/telegram/Photo.hpp"
+#include "td/telegram/Td.h"
#include "td/telegram/Version.h"
#include "td/utils/tl_helpers.h"
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GameManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/GameManager.cpp
new file mode 100644
index 0000000000..0f609b57f9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GameManager.cpp
@@ -0,0 +1,319 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/GameManager.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ChainId.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/InlineQueriesManager.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/net/NetQueryCreator.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/UpdatesManager.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class SetGameScoreQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit SetGameScoreQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId message_id, bool edit_message,
+ tl_object_ptr<telegram_api::InputUser> input_user, int32 score, bool force) {
+ int32 flags = 0;
+ if (edit_message) {
+ flags |= telegram_api::messages_setGameScore::EDIT_MESSAGE_MASK;
+ }
+ if (force) {
+ flags |= telegram_api::messages_setGameScore::FORCE_MASK;
+ }
+
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ CHECK(input_user != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_setGameScore(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer),
+ message_id.get_server_message_id().get(), std::move(input_user), score),
+ {{dialog_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_setGameScore>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SetGameScore: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for SetGameScoreQuery: " << status;
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetGameScoreQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetInlineGameScoreQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SetInlineGameScoreQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id, bool edit_message,
+ tl_object_ptr<telegram_api::InputUser> input_user, int32 score, bool force) {
+ CHECK(input_bot_inline_message_id != nullptr);
+ CHECK(input_user != nullptr);
+
+ int32 flags = 0;
+ if (edit_message) {
+ flags |= telegram_api::messages_setInlineGameScore::EDIT_MESSAGE_MASK;
+ }
+ if (force) {
+ flags |= telegram_api::messages_setInlineGameScore::FORCE_MASK;
+ }
+
+ auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_setInlineGameScore(flags, false /*ignored*/, false /*ignored*/,
+ std::move(input_bot_inline_message_id), std::move(input_user), score),
+ {}, dc_id));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_setInlineGameScore>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ LOG_IF(ERROR, !result_ptr.ok()) << "Receive false in result of setInlineGameScore";
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for SetInlineGameScoreQuery: " << status;
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetGameHighScoresQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::gameHighScores>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetGameHighScoresQuery(Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId message_id, tl_object_ptr<telegram_api::InputUser> input_user) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ CHECK(input_user != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::messages_getGameHighScores(
+ std::move(input_peer), message_id.get_server_message_id().get(), std::move(input_user))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getGameHighScores>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(td_->game_manager_->get_game_high_scores_object(result_ptr.move_as_ok()));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetGameHighScoresQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetInlineGameHighScoresQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::gameHighScores>> promise_;
+
+ public:
+ explicit GetInlineGameHighScoresQuery(Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id,
+ tl_object_ptr<telegram_api::InputUser> input_user) {
+ CHECK(input_bot_inline_message_id != nullptr);
+ CHECK(input_user != nullptr);
+
+ auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getInlineGameHighScores(std::move(input_bot_inline_message_id), std::move(input_user)),
+ {}, dc_id));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getInlineGameHighScores>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(td_->game_manager_->get_game_high_scores_object(result_ptr.move_as_ok()));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+GameManager::GameManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+}
+
+GameManager::~GameManager() = default;
+
+void GameManager::tear_down() {
+ parent_.reset();
+}
+
+void GameManager::set_game_score(FullMessageId full_message_id, bool edit_message, UserId user_id, int32 score,
+ bool force, Promise<td_api::object_ptr<td_api::message>> &&promise) {
+ CHECK(td_->auth_manager_->is_bot());
+
+ if (!td_->messages_manager_->have_message_force(full_message_id, "set_game_score")) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ auto dialog_id = full_message_id.get_dialog_id();
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Edit)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
+
+ if (!td_->messages_manager_->can_set_game_score(full_message_id)) {
+ return promise.set_error(Status::Error(400, "Game score can't be set"));
+ }
+
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), full_message_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ send_closure(actor_id, &GameManager::on_set_game_score, full_message_id, std::move(promise));
+ });
+ td_->create_handler<SetGameScoreQuery>(std::move(query_promise))
+ ->send(dialog_id, full_message_id.get_message_id(), edit_message, r_input_user.move_as_ok(), score, force);
+}
+
+void GameManager::on_set_game_score(FullMessageId full_message_id,
+ Promise<td_api::object_ptr<td_api::message>> &&promise) {
+ promise.set_value(td_->messages_manager_->get_message_object(full_message_id, "on_set_game_score"));
+}
+
+void GameManager::set_inline_game_score(const string &inline_message_id, bool edit_message, UserId user_id, int32 score,
+ bool force, Promise<Unit> &&promise) {
+ CHECK(td_->auth_manager_->is_bot());
+
+ auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
+ if (input_bot_inline_message_id == nullptr) {
+ return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
+ }
+
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
+
+ td_->create_handler<SetInlineGameScoreQuery>(std::move(promise))
+ ->send(std::move(input_bot_inline_message_id), edit_message, r_input_user.move_as_ok(), score, force);
+}
+
+void GameManager::get_game_high_scores(FullMessageId full_message_id, UserId user_id,
+ Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise) {
+ CHECK(td_->auth_manager_->is_bot());
+
+ if (!td_->messages_manager_->have_message_force(full_message_id, "get_game_high_scores")) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ auto dialog_id = full_message_id.get_dialog_id();
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+ auto message_id = full_message_id.get_message_id();
+ if (message_id.is_scheduled() || !message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Wrong message identifier specified"));
+ }
+
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
+
+ td_->create_handler<GetGameHighScoresQuery>(std::move(promise))
+ ->send(dialog_id, message_id, r_input_user.move_as_ok());
+}
+
+void GameManager::get_inline_game_high_scores(const string &inline_message_id, UserId user_id,
+ Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise) {
+ CHECK(td_->auth_manager_->is_bot());
+
+ auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
+ if (input_bot_inline_message_id == nullptr) {
+ return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
+ }
+
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
+
+ td_->create_handler<GetInlineGameHighScoresQuery>(std::move(promise))
+ ->send(std::move(input_bot_inline_message_id), r_input_user.move_as_ok());
+}
+
+td_api::object_ptr<td_api::gameHighScores> GameManager::get_game_high_scores_object(
+ telegram_api::object_ptr<telegram_api::messages_highScores> &&high_scores) {
+ td_->contacts_manager_->on_get_users(std::move(high_scores->users_), "get_game_high_scores_object");
+
+ auto result = td_api::make_object<td_api::gameHighScores>();
+ for (const auto &high_score : high_scores->scores_) {
+ int32 position = high_score->pos_;
+ UserId user_id(high_score->user_id_);
+ int32 score = high_score->score_;
+ if (position <= 0 || !user_id.is_valid() || score < 0) {
+ LOG(ERROR) << "Receive wrong " << to_string(high_score);
+ continue;
+ }
+ result->scores_.push_back(make_tl_object<td_api::gameHighScore>(
+ position, td_->contacts_manager_->get_user_id_object(user_id, "get_game_high_scores_object"), score));
+ }
+ return result;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GameManager.h b/protocols/Telegram/tdlib/td/td/telegram/GameManager.h
new file mode 100644
index 0000000000..084a664ecc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GameManager.h
@@ -0,0 +1,56 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+
+namespace td {
+
+class Td;
+
+class GameManager final : public Actor {
+ public:
+ GameManager(Td *td, ActorShared<> parent);
+ GameManager(const GameManager &) = delete;
+ GameManager &operator=(const GameManager &) = delete;
+ GameManager(GameManager &&) = delete;
+ GameManager &operator=(GameManager &&) = delete;
+ ~GameManager() final;
+
+ void set_game_score(FullMessageId full_message_id, bool edit_message, UserId user_id, int32 score, bool force,
+ Promise<td_api::object_ptr<td_api::message>> &&promise);
+
+ void set_inline_game_score(const string &inline_message_id, bool edit_message, UserId user_id, int32 score,
+ bool force, Promise<Unit> &&promise);
+
+ void get_game_high_scores(FullMessageId full_message_id, UserId user_id,
+ Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise);
+
+ void get_inline_game_high_scores(const string &inline_message_id, UserId user_id,
+ Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise);
+
+ td_api::object_ptr<td_api::gameHighScores> get_game_high_scores_object(
+ telegram_api::object_ptr<telegram_api::messages_highScores> &&high_scores);
+
+ private:
+ void tear_down() final;
+
+ void on_set_game_score(FullMessageId full_message_id, Promise<td_api::object_ptr<td_api::message>> &&promise);
+
+ Td *td_;
+ ActorShared<> parent_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GitCommitHash.cpp.in b/protocols/Telegram/tdlib/td/td/telegram/GitCommitHash.cpp.in
new file mode 100644
index 0000000000..89706f1324
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GitCommitHash.cpp.in
@@ -0,0 +1,15 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/GitCommitHash.h"
+
+namespace td {
+
+const char *get_git_commit_hash() {
+ return "@TD_GIT_COMMIT_HASH@";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/Scheduler.cpp b/protocols/Telegram/tdlib/td/td/telegram/GitCommitHash.h
index 720bf6bc4f..f4963d2e85 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/Scheduler.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/GitCommitHash.h
@@ -1,11 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/actor/impl2/Scheduler.h"
+#pragma once
namespace td {
-namespace actor2 {}
+
+const char *get_git_commit_hash();
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Global.cpp b/protocols/Telegram/tdlib/td/td/telegram/Global.cpp
index 22a3e25323..79bf898a49 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Global.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Global.cpp
@@ -1,37 +1,43 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Global.h"
-#include "td/telegram/ConfigShared.h"
+#include "td/telegram/AuthManager.h"
#include "td/telegram/net/ConnectionCreator.h"
-#include "td/telegram/net/MtprotoHeader.h"
#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/net/TempAuthKeyWatchdog.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/StateManager.h"
#include "td/telegram/TdDb.h"
-#include "td/telegram/Version.h"
-
-#include "td/actor/MultiPromise.h"
-#include "td/actor/PromiseFuture.h"
#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/port/Clocks.h"
#include "td/utils/tl_helpers.h"
+#include <cmath>
+
namespace td {
Global::Global() = default;
Global::~Global() = default;
+void Global::log_out(Slice reason) {
+ send_closure(auth_manager_, &AuthManager::on_authorization_lost, reason.str());
+}
+
void Global::close_all(Promise<> on_finished) {
td_db_->close_all(std::move(on_finished));
state_manager_.clear();
parameters_ = TdParameters();
}
+
void Global::close_and_destroy_all(Promise<> on_finished) {
td_db_->close_and_destroy_all(std::move(on_finished));
state_manager_.clear();
@@ -52,56 +58,274 @@ void Global::set_temp_auth_key_watchdog(ActorOwn<TempAuthKeyWatchdog> actor) {
temp_auth_key_watchdog_ = std::move(actor);
}
-const MtprotoHeader &Global::mtproto_header() const {
+MtprotoHeader &Global::mtproto_header() {
return *mtproto_header_;
}
-void Global::set_mtproto_header(std::unique_ptr<MtprotoHeader> mtproto_header) {
+void Global::set_mtproto_header(unique_ptr<MtprotoHeader> mtproto_header) {
mtproto_header_ = std::move(mtproto_header);
}
-Status Global::init(const TdParameters &parameters, ActorId<Td> td, std::unique_ptr<TdDb> td_db) {
+struct ServerTimeDiff {
+ double diff;
+ double system_time;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(diff, storer);
+ store(system_time, storer);
+ }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(diff, parser);
+ if (parser.get_left_len() != 0) {
+ parse(system_time, parser);
+ } else {
+ system_time = 0;
+ }
+ }
+};
+
+Status Global::init(const TdParameters &parameters, ActorId<Td> td, unique_ptr<TdDb> td_db_ptr) {
parameters_ = parameters;
gc_scheduler_id_ = min(Scheduler::instance()->sched_id() + 2, Scheduler::instance()->sched_count() - 1);
slow_net_scheduler_id_ = min(Scheduler::instance()->sched_id() + 3, Scheduler::instance()->sched_count() - 1);
td_ = td;
- td_db_ = std::move(td_db);
+ td_db_ = std::move(td_db_ptr);
- string save_diff_str = this->td_db()->get_binlog_pmc()->get("server_time_difference");
- if (save_diff_str.empty()) {
- server_time_difference_ = Clocks::system() - Time::now();
- server_time_difference_was_updated_ = false;
+ string saved_diff_str = td_db()->get_binlog_pmc()->get("server_time_difference");
+ auto system_time = Clocks::system();
+ auto default_time_difference = system_time - Time::now();
+ if (saved_diff_str.empty()) {
+ server_time_difference_ = default_time_difference;
} else {
- double save_diff;
- unserialize(save_diff, save_diff_str).ensure();
- double diff = save_diff + Clocks::system() - Time::now();
+ ServerTimeDiff saved_diff;
+ unserialize(saved_diff, saved_diff_str).ensure();
+
+ saved_diff_ = saved_diff.diff;
+ saved_system_time_ = saved_diff.system_time;
+
+ double diff = saved_diff.diff + default_time_difference;
+ if (saved_diff.system_time > system_time) {
+ double time_backwards_fix = saved_diff.system_time - system_time;
+ if (time_backwards_fix > 60) {
+ LOG(WARNING) << "Fix system time which went backwards: " << format::as_time(time_backwards_fix) << " "
+ << tag("saved_system_time", saved_diff.system_time) << tag("system_time", system_time);
+ }
+ diff += time_backwards_fix;
+ } else if (saved_diff.system_time != 0) {
+ const double MAX_TIME_FORWARD = 367 * 86400; // if more than 1 year has passed, the session is logged out anyway
+ if (saved_diff.system_time + MAX_TIME_FORWARD < system_time) {
+ double time_forward_fix = system_time - (saved_diff.system_time + MAX_TIME_FORWARD);
+ LOG(WARNING) << "Fix system time which went forward: " << format::as_time(time_forward_fix) << " "
+ << tag("saved_system_time", saved_diff.system_time) << tag("system_time", system_time);
+ diff -= time_forward_fix;
+ }
+ } else if (saved_diff.diff >= 1500000000 && system_time >= 1500000000) { // only for saved_diff.system_time == 0
+ diff = default_time_difference;
+ }
LOG(DEBUG) << "LOAD: " << tag("server_time_difference", diff);
server_time_difference_ = diff;
- server_time_difference_was_updated_ = false;
}
+ server_time_difference_was_updated_ = false;
+ dns_time_difference_ = default_time_difference;
+ dns_time_difference_was_updated_ = false;
return Status::OK();
}
+int32 Global::get_retry_after(int32 error_code, Slice error_message) {
+ if (error_code != 429) {
+ return 0;
+ }
+
+ Slice retry_after_prefix("Too Many Requests: retry after ");
+ if (!begins_with(error_message, retry_after_prefix)) {
+ return 0;
+ }
+
+ auto r_retry_after = to_integer_safe<int32>(error_message.substr(retry_after_prefix.size()));
+ if (r_retry_after.is_ok() && r_retry_after.ok() > 0) {
+ return r_retry_after.ok();
+ }
+ return 0;
+}
+
+int32 Global::to_unix_time(double server_time) const {
+ LOG_CHECK(1.0 <= server_time && server_time <= 2140000000.0)
+ << server_time << ' ' << Clocks::system() << ' ' << is_server_time_reliable() << ' '
+ << get_server_time_difference() << ' ' << Time::now() << ' ' << saved_diff_ << ' ' << saved_system_time_;
+ return static_cast<int32>(server_time);
+}
+
void Global::update_server_time_difference(double diff) {
if (!server_time_difference_was_updated_ || server_time_difference_ < diff) {
server_time_difference_ = diff;
server_time_difference_was_updated_ = true;
+ do_save_server_time_difference();
+
+ get_option_manager()->on_update_server_time_difference();
+ }
+}
+
+void Global::save_server_time() {
+ auto t = Time::now();
+ if (server_time_difference_was_updated_ && system_time_saved_at_.load(std::memory_order_relaxed) + 10 < t) {
+ system_time_saved_at_ = t;
+ do_save_server_time_difference();
+ }
+}
+
+void Global::do_save_server_time_difference() {
+ if (get_option_boolean("disable_time_adjustment_protection")) {
+ td_db()->get_binlog_pmc()->erase("server_time_difference");
+ return;
+ }
+
+ // diff = server_time - Time::now
+ // fixed_diff = server_time - Clocks::system
+ double system_time = Clocks::system();
+ double fixed_diff = server_time_difference_ + Time::now() - system_time;
+
+ ServerTimeDiff diff;
+ diff.diff = fixed_diff;
+ diff.system_time = system_time;
+ td_db()->get_binlog_pmc()->set("server_time_difference", serialize(diff));
+}
+
+void Global::update_dns_time_difference(double diff) {
+ dns_time_difference_ = diff;
+ dns_time_difference_was_updated_ = true;
+}
+
+double Global::get_dns_time_difference() const {
+ bool dns_flag = dns_time_difference_was_updated_;
+ double dns_diff = dns_time_difference_;
+ bool server_flag = server_time_difference_was_updated_;
+ double server_diff = server_time_difference_;
+ if (dns_flag != server_flag) {
+ return dns_flag ? dns_diff : server_diff;
+ }
+ if (dns_flag) {
+ return max(dns_diff, server_diff);
+ }
+ if (td_db_) {
+ return server_diff;
+ }
+ return Clocks::system() - Time::now();
+}
+
+DcId Global::get_webfile_dc_id() const {
+ auto dc_id = narrow_cast<int32>(get_option_integer("webfile_dc_id"));
+ if (!DcId::is_valid(dc_id)) {
+ if (is_test_dc()) {
+ dc_id = 2;
+ } else {
+ dc_id = 4;
+ }
- // diff = server_time - Time::now
- // save_diff = server_time - Clocks::system
- double save_diff = diff + Time::now() - Clocks::system();
- auto str = serialize(save_diff);
- td_db()->get_binlog_pmc()->set("server_time_difference", str);
+ CHECK(DcId::is_valid(dc_id));
}
+
+ return DcId::internal(dc_id);
+}
+
+bool Global::ignore_background_updates() const {
+ return !parameters_.use_file_db && !parameters_.use_secret_chats && get_option_boolean("ignore_background_updates");
+}
+
+void Global::set_net_query_stats(std::shared_ptr<NetQueryStats> net_query_stats) {
+ net_query_creator_.set_create_func(
+ [net_query_stats = std::move(net_query_stats)] { return td::make_unique<NetQueryCreator>(net_query_stats); });
}
-void Global::set_net_query_dispatcher(std::unique_ptr<NetQueryDispatcher> net_query_dispatcher) {
+void Global::set_net_query_dispatcher(unique_ptr<NetQueryDispatcher> net_query_dispatcher) {
net_query_dispatcher_ = std::move(net_query_dispatcher);
}
-void Global::set_shared_config(std::unique_ptr<ConfigShared> shared_config) {
- shared_config_ = std::move(shared_config);
+
+const OptionManager *Global::get_option_manager() const {
+ CHECK(option_manager_ != nullptr);
+ return option_manager_;
+}
+
+OptionManager *Global::get_option_manager() {
+ CHECK(option_manager_ != nullptr);
+ return option_manager_;
+}
+
+void Global::set_option_empty(Slice name) {
+ get_option_manager()->set_option_empty(name);
+}
+
+void Global::set_option_boolean(Slice name, bool value) {
+ get_option_manager()->set_option_boolean(name, value);
+}
+
+void Global::set_option_integer(Slice name, int64 value) {
+ get_option_manager()->set_option_integer(name, value);
+}
+
+void Global::set_option_string(Slice name, Slice value) {
+ get_option_manager()->set_option_string(name, value);
+}
+
+bool Global::have_option(Slice name) const {
+ return get_option_manager()->have_option(name);
+}
+
+bool Global::get_option_boolean(Slice name, bool default_value) const {
+ return get_option_manager()->get_option_boolean(name, default_value);
+}
+
+int64 Global::get_option_integer(Slice name, int64 default_value) const {
+ return get_option_manager()->get_option_integer(name, default_value);
+}
+
+string Global::get_option_string(Slice name, string default_value) const {
+ return get_option_manager()->get_option_string(name, std::move(default_value));
+}
+
+int64 Global::get_location_key(double latitude, double longitude) {
+ const double PI = 3.14159265358979323846;
+ latitude *= PI / 180;
+ longitude *= PI / 180;
+
+ int64 key = 0;
+ if (latitude < 0) {
+ latitude = -latitude;
+ key = 65536;
+ }
+
+ double f = std::tan(PI / 4 - latitude / 2);
+ key += static_cast<int64>(f * std::cos(longitude) * 128) * 256;
+ key += static_cast<int64>(f * std::sin(longitude) * 128);
+ if (key == 0) {
+ key = 1;
+ }
+ return key;
+}
+
+int64 Global::get_location_access_hash(double latitude, double longitude) {
+ auto it = location_access_hashes_.find(get_location_key(latitude, longitude));
+ if (it == location_access_hashes_.end()) {
+ return 0;
+ }
+ return it->second;
+}
+
+void Global::add_location_access_hash(double latitude, double longitude, int64 access_hash) {
+ if (access_hash == 0) {
+ return;
+ }
+
+ location_access_hashes_[get_location_key(latitude, longitude)] = access_hash;
+}
+
+double get_global_server_time() {
+ return G()->server_time();
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Global.h b/protocols/Telegram/tdlib/td/td/telegram/Global.h
index 63c07d5c3c..7cb0e99904 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Global.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Global.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,23 +7,20 @@
#pragma once
#include "td/telegram/DhConfig.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/net/MtprotoHeader.h"
#include "td/telegram/net/NetQueryCreator.h"
-#include "td/telegram/TdDb.h"
#include "td/telegram/TdParameters.h"
+#include "td/net/NetStats.h"
+
#include "td/actor/actor.h"
-#include "td/actor/Condition.h"
-#include "td/actor/PromiseFuture.h"
#include "td/actor/SchedulerLocalStorage.h"
-#include "td/db/binlog/ConcurrentBinlog.h"
-#include "td/db/BinlogKeyValue.h"
-#include "td/db/Pmc.h"
-
-#include "td/net/NetStats.h"
-
#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/Time.h"
@@ -33,49 +30,78 @@
#include <mutex>
namespace td {
+
class AnimationsManager;
+class AttachMenuManager;
+class AuthManager;
+class BackgroundManager;
class CallManager;
-class ConfigShared;
class ConfigManager;
class ConnectionCreator;
class ContactsManager;
+class DownloadManager;
class FileManager;
-class MtprotoHeader;
+class FileReferenceManager;
+class ForumTopicManager;
+class GameManager;
+class GroupCallManager;
+class LanguagePackManager;
+class LinkManager;
class MessagesManager;
class NetQueryDispatcher;
+class NotificationManager;
+class NotificationSettingsManager;
+class OptionManager;
+class PasswordManager;
class SecretChatsManager;
+class SponsoredMessageManager;
class StateManager;
class StickersManager;
class StorageManager;
class Td;
+class TdDb;
class TempAuthKeyWatchdog;
+class ThemeManager;
class TopDialogManager;
class UpdatesManager;
class WebPagesManager;
-}; // namespace td
-namespace td {
-class Global : public ActorContext {
+class Global final : public ActorContext {
public:
Global();
- ~Global() override;
+ ~Global() final;
Global(const Global &) = delete;
Global &operator=(const Global &) = delete;
Global(Global &&other) = delete;
Global &operator=(Global &&other) = delete;
- TdDb *td_db() {
- CHECK(td_db_);
+ static constexpr int32 ID = -572104940;
+ int32 get_id() const final {
+ return ID;
+ }
+
+#define td_db() get_td_db_impl(__FILE__, __LINE__)
+ TdDb *get_td_db_impl(const char *file, int line) {
+ LOG_CHECK(td_db_) << close_flag() << " " << file << " " << line;
return td_db_.get();
}
+
+ void log_out(Slice reason);
+
void close_all(Promise<> on_finished);
void close_and_destroy_all(Promise<> on_finished);
- Status init(const TdParameters &parameters, ActorId<Td> td, std::unique_ptr<TdDb> td_db) TD_WARN_UNUSED_RESULT;
+ Status init(const TdParameters &parameters, ActorId<Td> td, unique_ptr<TdDb> td_db_ptr) TD_WARN_UNUSED_RESULT;
Slice get_dir() const {
return parameters_.database_directory;
}
+ Slice get_secure_files_dir() const {
+ if (store_all_files_in_files_directory_) {
+ return get_files_dir();
+ }
+ return get_dir();
+ }
Slice get_files_dir() const {
return parameters_.files_directory;
}
@@ -83,24 +109,43 @@ class Global : public ActorContext {
return parameters_.use_test_dc;
}
+ bool ignore_background_updates() const;
+
NetQueryCreator &net_query_creator() {
- return net_query_creator_.get();
+ return *net_query_creator_.get();
}
- void set_net_query_dispatcher(std::unique_ptr<NetQueryDispatcher> net_query_dispatcher);
+ void set_net_query_stats(std::shared_ptr<NetQueryStats> net_query_stats);
+
+ void set_net_query_dispatcher(unique_ptr<NetQueryDispatcher> net_query_dispatcher);
NetQueryDispatcher &net_query_dispatcher() {
+ CHECK(have_net_query_dispatcher());
return *net_query_dispatcher_;
}
- void set_shared_config(std::unique_ptr<ConfigShared> shared_config);
-
- ConfigShared &shared_config() {
- return *shared_config_;
+ bool have_net_query_dispatcher() const {
+ return net_query_dispatcher_.get() != nullptr;
}
- double from_server_time(double date) const {
- return date - get_server_time_difference();
+ void set_option_empty(Slice name);
+
+ void set_option_boolean(Slice name, bool value);
+
+ void set_option_integer(Slice name, int64 value);
+
+ void set_option_string(Slice name, Slice value);
+
+ bool have_option(Slice name) const;
+
+ bool get_option_boolean(Slice name, bool default_value = false) const;
+
+ int64 get_option_integer(Slice name, int64 default_value = 0) const;
+
+ string get_option_string(Slice name, string default_value = "") const;
+
+ bool is_server_time_reliable() const {
+ return server_time_difference_was_updated_;
}
double to_server_time(double now) const {
return now + get_server_time_difference();
@@ -112,18 +157,24 @@ class Global : public ActorContext {
return to_server_time(Time::now_cached());
}
int32 unix_time() const {
- return static_cast<int32>(server_time());
+ return to_unix_time(server_time());
}
int32 unix_time_cached() const {
- return static_cast<int32>(server_time_cached());
+ return to_unix_time(server_time_cached());
}
void update_server_time_difference(double diff);
+ void save_server_time();
+
double get_server_time_difference() const {
return server_time_difference_.load(std::memory_order_relaxed);
}
+ void update_dns_time_difference(double diff);
+
+ double get_dns_time_difference() const;
+
ActorId<StateManager> state_manager() const {
return state_manager_;
}
@@ -134,66 +185,190 @@ class Global : public ActorContext {
ActorId<Td> td() const {
return td_;
}
+
ActorId<AnimationsManager> animations_manager() const {
return animations_manager_;
}
void set_animations_manager(ActorId<AnimationsManager> animations_manager) {
animations_manager_ = animations_manager;
}
+
+ ActorId<AttachMenuManager> attach_menu_manager() const {
+ return attach_menu_manager_;
+ }
+ void set_attach_menu_manager(ActorId<AttachMenuManager> attach_menu_manager) {
+ attach_menu_manager_ = attach_menu_manager;
+ }
+
+ void set_auth_manager(ActorId<AuthManager> auth_manager) {
+ auth_manager_ = auth_manager;
+ }
+
+ ActorId<BackgroundManager> background_manager() const {
+ return background_manager_;
+ }
+ void set_background_manager(ActorId<BackgroundManager> background_manager) {
+ background_manager_ = background_manager;
+ }
+
+ ActorId<CallManager> call_manager() const {
+ return call_manager_;
+ }
+ void set_call_manager(ActorId<CallManager> call_manager) {
+ call_manager_ = call_manager;
+ }
+
+ ActorId<ConfigManager> config_manager() const {
+ return config_manager_;
+ }
+ void set_config_manager(ActorId<ConfigManager> config_manager) {
+ config_manager_ = config_manager;
+ }
+
ActorId<ContactsManager> contacts_manager() const {
return contacts_manager_;
}
void set_contacts_manager(ActorId<ContactsManager> contacts_manager) {
contacts_manager_ = contacts_manager;
}
+
+ ActorId<DownloadManager> download_manager() const {
+ return download_manager_;
+ }
+ void set_download_manager(ActorId<DownloadManager> download_manager) {
+ download_manager_ = std::move(download_manager);
+ }
+
ActorId<FileManager> file_manager() const {
return file_manager_;
}
void set_file_manager(ActorId<FileManager> file_manager) {
file_manager_ = std::move(file_manager);
}
+
+ ActorId<FileReferenceManager> file_reference_manager() const {
+ return file_reference_manager_;
+ }
+ void set_file_reference_manager(ActorId<FileReferenceManager> file_reference_manager) {
+ file_reference_manager_ = std::move(file_reference_manager);
+ }
+
+ ActorId<ForumTopicManager> forum_topic_manager() const {
+ return forum_topic_manager_;
+ }
+ void set_forum_topic_manager(ActorId<ForumTopicManager> forum_topic_manager) {
+ forum_topic_manager_ = forum_topic_manager;
+ }
+
+ ActorId<GameManager> game_manager() const {
+ return game_manager_;
+ }
+ void set_game_manager(ActorId<GameManager> game_manager) {
+ game_manager_ = game_manager;
+ }
+
+ ActorId<GroupCallManager> group_call_manager() const {
+ return group_call_manager_;
+ }
+ void set_group_call_manager(ActorId<GroupCallManager> group_call_manager) {
+ group_call_manager_ = group_call_manager;
+ }
+
+ ActorId<LanguagePackManager> language_pack_manager() const {
+ return language_pack_manager_;
+ }
+ void set_language_pack_manager(ActorId<LanguagePackManager> language_pack_manager) {
+ language_pack_manager_ = language_pack_manager;
+ }
+
+ ActorId<LinkManager> link_manager() const {
+ return link_manager_;
+ }
+ void set_link_manager(ActorId<LinkManager> link_manager) {
+ link_manager_ = link_manager;
+ }
+
ActorId<MessagesManager> messages_manager() const {
return messages_manager_;
}
void set_messages_manager(ActorId<MessagesManager> messages_manager) {
messages_manager_ = messages_manager;
}
+
+ ActorId<NotificationManager> notification_manager() const {
+ return notification_manager_;
+ }
+ void set_notification_manager(ActorId<NotificationManager> notification_manager) {
+ notification_manager_ = notification_manager;
+ }
+
+ ActorId<NotificationSettingsManager> notification_settings_manager() const {
+ return notification_settings_manager_;
+ }
+ void set_notification_settings_manager(ActorId<NotificationSettingsManager> notification_settings_manager) {
+ notification_settings_manager_ = notification_settings_manager;
+ }
+
+ void set_option_manager(OptionManager *option_manager) {
+ option_manager_ = option_manager;
+ }
+
+ ActorId<PasswordManager> password_manager() const {
+ return password_manager_;
+ }
+ void set_password_manager(ActorId<PasswordManager> password_manager) {
+ password_manager_ = password_manager;
+ }
+
ActorId<SecretChatsManager> secret_chats_manager() const {
return secret_chats_manager_;
}
void set_secret_chats_manager(ActorId<SecretChatsManager> secret_chats_manager) {
secret_chats_manager_ = secret_chats_manager;
}
- ActorId<CallManager> call_manager() const {
- return call_manager_;
+
+ ActorId<SponsoredMessageManager> sponsored_message_manager() const {
+ return sponsored_message_manager_;
}
- void set_call_manager(ActorId<CallManager> call_manager) {
- call_manager_ = call_manager;
+ void set_sponsored_message_manager(ActorId<SponsoredMessageManager> sponsored_message_manager) {
+ sponsored_message_manager_ = sponsored_message_manager;
}
+
ActorId<StickersManager> stickers_manager() const {
return stickers_manager_;
}
void set_stickers_manager(ActorId<StickersManager> stickers_manager) {
stickers_manager_ = stickers_manager;
}
+
ActorId<StorageManager> storage_manager() const {
return storage_manager_;
}
void set_storage_manager(ActorId<StorageManager> storage_manager) {
storage_manager_ = storage_manager;
}
+
+ ActorId<ThemeManager> theme_manager() const {
+ return theme_manager_;
+ }
+ void set_theme_manager(ActorId<ThemeManager> theme_manager) {
+ theme_manager_ = theme_manager;
+ }
+
ActorId<TopDialogManager> top_dialog_manager() const {
return top_dialog_manager_;
}
void set_top_dialog_manager(ActorId<TopDialogManager> top_dialog_manager) {
top_dialog_manager_ = top_dialog_manager;
}
+
ActorId<UpdatesManager> updates_manager() const {
return updates_manager_;
}
void set_updates_manager(ActorId<UpdatesManager> updates_manager) {
updates_manager_ = updates_manager;
}
+
ActorId<WebPagesManager> web_pages_manager() const {
return web_pages_manager_;
}
@@ -201,33 +376,22 @@ class Global : public ActorContext {
web_pages_manager_ = web_pages_manager;
}
- ActorId<ConfigManager> config_manager() const {
- return config_manager_;
- }
- void set_config_manager(ActorId<ConfigManager> config_manager) {
- config_manager_ = config_manager;
- }
-
ActorId<ConnectionCreator> connection_creator() const;
void set_connection_creator(ActorOwn<ConnectionCreator> connection_creator);
ActorId<TempAuthKeyWatchdog> temp_auth_key_watchdog() const;
void set_temp_auth_key_watchdog(ActorOwn<TempAuthKeyWatchdog> actor);
- const MtprotoHeader &mtproto_header() const;
- void set_mtproto_header(std::unique_ptr<MtprotoHeader> mtproto_header);
+ MtprotoHeader &mtproto_header();
+ void set_mtproto_header(unique_ptr<MtprotoHeader> mtproto_header);
+ bool have_mtproto_header() const {
+ return mtproto_header_ != nullptr;
+ }
const TdParameters &parameters() const {
return parameters_;
}
- int32 get_my_id() const {
- return my_id_;
- }
- void set_my_id(int32 my_id) {
- my_id_ = my_id;
- }
-
int32 get_gc_scheduler_id() const {
return gc_scheduler_id_;
}
@@ -236,9 +400,7 @@ class Global : public ActorContext {
return slow_net_scheduler_id_;
}
-#if !TD_HAVE_ATOMIC_SHARED_PTR
- std::mutex dh_config_mutex_;
-#endif
+ DcId get_webfile_dc_id() const;
std::shared_ptr<DhConfig> get_dh_config() {
#if !TD_HAVE_ATOMIC_SHARED_PTR
@@ -249,21 +411,18 @@ class Global : public ActorContext {
return atomic_load(&dh_config_);
#endif
}
+
void set_dh_config(std::shared_ptr<DhConfig> new_dh_config) {
#if !TD_HAVE_ATOMIC_SHARED_PTR
std::lock_guard<std::mutex> guard(dh_config_mutex_);
- dh_config_ = new_dh_config;
+ dh_config_ = std::move(new_dh_config);
#else
atomic_store(&dh_config_, std::move(new_dh_config));
#endif
}
- void wait_binlog_replay_finish(Promise<> promise) {
- binlog_replay_finish_.wait(std::move(promise));
- }
-
- void on_binlog_replay_finish() {
- binlog_replay_finish_.set_true();
+ static Status request_aborted_error() {
+ return Status::Error(500, "Request aborted");
}
void set_close_flag() {
@@ -273,6 +432,25 @@ class Global : public ActorContext {
return close_flag_.load();
}
+ Status close_status() const {
+ return close_flag() ? request_aborted_error() : Status::OK();
+ }
+
+ bool is_expected_error(const Status &error) const {
+ CHECK(error.is_error());
+ if (error.code() == 401) {
+ // authorization is lost
+ return true;
+ }
+ if (error.code() == 420 || error.code() == 429) {
+ // flood wait
+ return true;
+ }
+ return close_flag();
+ }
+
+ static int32 get_retry_after(int32 error_code, Slice error_message);
+
const std::vector<std::shared_ptr<NetStatsCallback>> &get_net_stats_file_callbacks() {
return net_stats_file_callbacks_;
}
@@ -280,54 +458,104 @@ class Global : public ActorContext {
net_stats_file_callbacks_ = std::move(callbacks);
}
+ int64 get_location_access_hash(double latitude, double longitude);
+
+ void add_location_access_hash(double latitude, double longitude, int64 access_hash);
+
+ void set_store_all_files_in_files_directory(bool flag) {
+ store_all_files_in_files_directory_ = flag;
+ }
+
private:
std::shared_ptr<DhConfig> dh_config_;
- std::unique_ptr<TdDb> td_db_;
- Condition binlog_replay_finish_;
+ unique_ptr<TdDb> td_db_;
ActorId<Td> td_;
ActorId<AnimationsManager> animations_manager_;
+ ActorId<AttachMenuManager> attach_menu_manager_;
+ ActorId<AuthManager> auth_manager_;
+ ActorId<BackgroundManager> background_manager_;
+ ActorId<CallManager> call_manager_;
+ ActorId<ConfigManager> config_manager_;
ActorId<ContactsManager> contacts_manager_;
+ ActorId<DownloadManager> download_manager_;
ActorId<FileManager> file_manager_;
+ ActorId<FileReferenceManager> file_reference_manager_;
+ ActorId<ForumTopicManager> forum_topic_manager_;
+ ActorId<GameManager> game_manager_;
+ ActorId<GroupCallManager> group_call_manager_;
+ ActorId<LanguagePackManager> language_pack_manager_;
+ ActorId<LinkManager> link_manager_;
ActorId<MessagesManager> messages_manager_;
+ ActorId<NotificationManager> notification_manager_;
+ ActorId<NotificationSettingsManager> notification_settings_manager_;
+ ActorId<PasswordManager> password_manager_;
ActorId<SecretChatsManager> secret_chats_manager_;
- ActorId<CallManager> call_manager_;
+ ActorId<SponsoredMessageManager> sponsored_message_manager_;
ActorId<StickersManager> stickers_manager_;
ActorId<StorageManager> storage_manager_;
+ ActorId<ThemeManager> theme_manager_;
ActorId<TopDialogManager> top_dialog_manager_;
ActorId<UpdatesManager> updates_manager_;
ActorId<WebPagesManager> web_pages_manager_;
- ActorId<ConfigManager> config_manager_;
ActorOwn<ConnectionCreator> connection_creator_;
ActorOwn<TempAuthKeyWatchdog> temp_auth_key_watchdog_;
- std::unique_ptr<MtprotoHeader> mtproto_header_;
+ unique_ptr<MtprotoHeader> mtproto_header_;
+
+ OptionManager *option_manager_ = nullptr;
TdParameters parameters_;
- int32 gc_scheduler_id_;
- int32 slow_net_scheduler_id_;
+ int32 gc_scheduler_id_ = 0;
+ int32 slow_net_scheduler_id_ = 0;
- std::atomic<double> server_time_difference_;
- std::atomic<bool> server_time_difference_was_updated_;
+ std::atomic<bool> store_all_files_in_files_directory_{false};
+
+ std::atomic<double> server_time_difference_{0.0};
+ std::atomic<bool> server_time_difference_was_updated_{false};
+ std::atomic<double> dns_time_difference_{0.0};
+ std::atomic<bool> dns_time_difference_was_updated_{false};
std::atomic<bool> close_flag_{false};
+ std::atomic<double> system_time_saved_at_{-1e10};
+ double saved_diff_ = 0.0;
+ double saved_system_time_ = 0.0;
+
+#if !TD_HAVE_ATOMIC_SHARED_PTR
+ std::mutex dh_config_mutex_;
+#endif
std::vector<std::shared_ptr<NetStatsCallback>> net_stats_file_callbacks_;
ActorId<StateManager> state_manager_;
- SchedulerLocalStorage<NetQueryCreator> net_query_creator_;
- std::unique_ptr<NetQueryDispatcher> net_query_dispatcher_;
+ LazySchedulerLocalStorage<unique_ptr<NetQueryCreator>> net_query_creator_;
+ unique_ptr<NetQueryDispatcher> net_query_dispatcher_;
+
+ static int64 get_location_key(double latitude, double longitude);
+
+ FlatHashMap<int64, int64> location_access_hashes_;
+
+ int32 to_unix_time(double server_time) const;
- std::unique_ptr<ConfigShared> shared_config_;
+ const OptionManager *get_option_manager() const;
- int32 my_id_ = 0; // hack
+ OptionManager *get_option_manager();
+
+ void do_save_server_time_difference();
void do_close(Promise<> on_finish, bool destroy_flag);
};
-inline Global *G() {
- CHECK(Scheduler::context());
- return static_cast<Global *>(Scheduler::context());
+#define G() G_impl(__FILE__, __LINE__)
+
+inline Global *G_impl(const char *file, int line) {
+ ActorContext *context = Scheduler::context();
+ LOG_CHECK(context != nullptr && context->get_id() == Global::ID)
+ << "Context = " << context << " in " << file << " at " << line;
+ return static_cast<Global *>(context);
}
+
+double get_global_server_time();
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GroupCallId.h b/protocols/Telegram/tdlib/td/td/telegram/GroupCallId.h
new file mode 100644
index 0000000000..7e0c7352b3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GroupCallId.h
@@ -0,0 +1,53 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class GroupCallId {
+ public:
+ GroupCallId() = default;
+
+ explicit GroupCallId(int32 group_call_id) : id(group_call_id) {
+ }
+
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ GroupCallId(T group_call_id) = delete;
+
+ bool is_valid() const {
+ return id > 0;
+ }
+
+ int32 get() const {
+ return id;
+ }
+
+ bool operator==(const GroupCallId &other) const {
+ return id == other.id;
+ }
+
+ private:
+ int32 id{0};
+};
+
+struct GroupCallIdHash {
+ uint32 operator()(GroupCallId group_call_id) const {
+ return Hash<int32>()(group_call_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &sb, const GroupCallId group_call_id) {
+ return sb << "group call " << group_call_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GroupCallManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/GroupCallManager.cpp
new file mode 100644
index 0000000000..51e972d937
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GroupCallManager.cpp
@@ -0,0 +1,4861 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/GroupCallManager.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogAction.h"
+#include "td/telegram/DialogParticipantFilter.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/MessageSender.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/UpdatesManager.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
+
+#include <map>
+#include <utility>
+
+namespace td {
+
+class GetGroupCallStreamChannelsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::groupCallStreams>> promise_;
+
+ public:
+ explicit GetGroupCallStreamChannelsQuery(Promise<td_api::object_ptr<td_api::groupCallStreams>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, DcId stream_dc_id) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_getGroupCallStreamChannels(input_group_call_id.get_input_group_call()), {}, stream_dc_id,
+ NetQuery::Type::DownloadSmall));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_getGroupCallStreamChannels>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ auto streams = transform(ptr->channels_, [](const tl_object_ptr<telegram_api::groupCallStreamChannel> &channel) {
+ return td_api::make_object<td_api::groupCallStream>(channel->channel_, channel->scale_,
+ channel->last_timestamp_ms_);
+ });
+ promise_.set_value(td_api::make_object<td_api::groupCallStreams>(std::move(streams)));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetGroupCallStreamQuery final : public Td::ResultHandler {
+ Promise<string> promise_;
+
+ public:
+ explicit GetGroupCallStreamQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, DcId stream_dc_id, int64 time_offset, int32 scale, int32 channel_id,
+ int32 video_quality) {
+ int32 stream_flags = 0;
+ if (channel_id != 0) {
+ stream_flags |= telegram_api::inputGroupCallStream::VIDEO_CHANNEL_MASK;
+ }
+ auto input_stream = make_tl_object<telegram_api::inputGroupCallStream>(
+ stream_flags, input_group_call_id.get_input_group_call(), time_offset, scale, channel_id, video_quality);
+ int32 flags = 0;
+ auto query = G()->net_query_creator().create(
+ telegram_api::upload_getFile(flags, false /*ignored*/, false /*ignored*/, std::move(input_stream), 0, 1 << 20),
+ {}, stream_dc_id, NetQuery::Type::DownloadSmall);
+ query->total_timeout_limit_ = 0;
+ send_query(std::move(query));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::upload_getFile>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ if (ptr->get_id() != telegram_api::upload_file::ID) {
+ return on_error(Status::Error(500, "Receive unexpected server response"));
+ }
+
+ auto file = move_tl_object_as<telegram_api::upload_file>(ptr);
+ promise_.set_value(file->bytes_.as_slice().str());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetGroupCallJoinAsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::messageSenders>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetGroupCallJoinAsQuery(Promise<td_api::object_ptr<td_api::messageSenders>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ send_query(G()->net_query_creator().create(telegram_api::phone_getGroupCallJoinAs(std::move(input_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_getGroupCallJoinAs>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetGroupCallJoinAsQuery: " << to_string(ptr);
+
+ td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetGroupCallJoinAsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetGroupCallJoinAsQuery");
+
+ promise_.set_value(convert_message_senders_object(td_, ptr->peers_));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetGroupCallJoinAsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SaveDefaultGroupCallJoinAsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SaveDefaultGroupCallJoinAsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, DialogId as_dialog_id) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ auto as_input_peer = td_->messages_manager_->get_input_peer(as_dialog_id, AccessRights::Read);
+ CHECK(as_input_peer != nullptr);
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_saveDefaultGroupCallJoinAs(std::move(input_peer), std::move(as_input_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_saveDefaultGroupCallJoinAs>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto success = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SaveDefaultGroupCallJoinAsQuery: " << success;
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ // td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetGroupCallJoinAsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class CreateGroupCallQuery final : public Td::ResultHandler {
+ Promise<InputGroupCallId> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit CreateGroupCallQuery(Promise<InputGroupCallId> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &title, int32 start_date, bool is_rtmp_stream) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ int32 flags = 0;
+ if (!title.empty()) {
+ flags |= telegram_api::phone_createGroupCall::TITLE_MASK;
+ }
+ if (start_date > 0) {
+ flags |= telegram_api::phone_createGroupCall::SCHEDULE_DATE_MASK;
+ }
+ if (is_rtmp_stream) {
+ flags |= telegram_api::phone_createGroupCall::RTMP_STREAM_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::phone_createGroupCall(
+ flags, false, std::move(input_peer), Random::secure_int32(), title, start_date)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_createGroupCall>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for CreateGroupCallQuery: " << to_string(ptr);
+
+ auto group_call_ids = td_->updates_manager_->get_update_new_group_call_ids(ptr.get());
+ if (group_call_ids.empty()) {
+ LOG(ERROR) << "Receive wrong CreateGroupCallQuery response " << to_string(ptr);
+ return on_error(Status::Error(500, "Receive wrong response"));
+ }
+ auto group_call_id = group_call_ids[0];
+ for (const auto &other_group_call_id : group_call_ids) {
+ if (group_call_id != other_group_call_id) {
+ LOG(ERROR) << "Receive wrong CreateGroupCallQuery response " << to_string(ptr);
+ return on_error(Status::Error(500, "Receive wrong response"));
+ }
+ }
+
+ td_->updates_manager_->on_get_updates(
+ std::move(ptr), PromiseCreator::lambda([promise = std::move(promise_), group_call_id](Unit) mutable {
+ promise.set_value(std::move(group_call_id));
+ }));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "CreateGroupCallQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetGroupCallRtmpStreamUrlGroupCallQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::rtmpUrl>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetGroupCallRtmpStreamUrlGroupCallQuery(Promise<td_api::object_ptr<td_api::rtmpUrl>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, bool revoke) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ send_query(
+ G()->net_query_creator().create(telegram_api::phone_getGroupCallStreamRtmpUrl(std::move(input_peer), revoke)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_getGroupCallStreamRtmpUrl>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ promise_.set_value(td_api::make_object<td_api::rtmpUrl>(ptr->url_, ptr->key_));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetGroupCallRtmpStreamUrlGroupCallQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetGroupCallQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::phone_groupCall>> promise_;
+
+ public:
+ explicit GetGroupCallQuery(Promise<tl_object_ptr<telegram_api::phone_groupCall>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, int32 limit) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_getGroupCall(input_group_call_id.get_input_group_call(), limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_getGroupCall>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetGroupCallQuery: " << to_string(ptr);
+
+ promise_.set_value(std::move(ptr));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetGroupCallParticipantQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ InputGroupCallId input_group_call_id_;
+
+ public:
+ explicit GetGroupCallParticipantQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, vector<tl_object_ptr<telegram_api::InputPeer>> &&input_peers,
+ vector<int32> &&source_ids) {
+ input_group_call_id_ = input_group_call_id;
+ auto limit = narrow_cast<int32>(max(input_peers.size(), source_ids.size()));
+ send_query(G()->net_query_creator().create(telegram_api::phone_getGroupParticipants(
+ input_group_call_id.get_input_group_call(), std::move(input_peers), std::move(source_ids), string(), limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_getGroupParticipants>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ td_->group_call_manager_->on_get_group_call_participants(input_group_call_id_, result_ptr.move_as_ok(), false,
+ string());
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetGroupCallParticipantsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ InputGroupCallId input_group_call_id_;
+ string offset_;
+
+ public:
+ explicit GetGroupCallParticipantsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, string offset, int32 limit) {
+ input_group_call_id_ = input_group_call_id;
+ offset_ = std::move(offset);
+ send_query(G()->net_query_creator().create(telegram_api::phone_getGroupParticipants(
+ input_group_call_id.get_input_group_call(), vector<tl_object_ptr<telegram_api::InputPeer>>(), vector<int32>(),
+ offset_, limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_getGroupParticipants>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ td_->group_call_manager_->on_get_group_call_participants(input_group_call_id_, result_ptr.move_as_ok(), true,
+ offset_);
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class StartScheduledGroupCallQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit StartScheduledGroupCallQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_startScheduledGroupCall(input_group_call_id.get_input_group_call())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_startScheduledGroupCall>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for StartScheduledGroupCallQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "GROUPCALL_NOT_MODIFIED") {
+ promise_.set_value(Unit());
+ return;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class JoinGroupCallQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ InputGroupCallId input_group_call_id_;
+ DialogId as_dialog_id_;
+ uint64 generation_ = 0;
+
+ public:
+ explicit JoinGroupCallQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ NetQueryRef send(InputGroupCallId input_group_call_id, DialogId as_dialog_id, const string &payload, bool is_muted,
+ bool is_video_stopped, const string &invite_hash, uint64 generation) {
+ input_group_call_id_ = input_group_call_id;
+ as_dialog_id_ = as_dialog_id;
+ generation_ = generation;
+
+ tl_object_ptr<telegram_api::InputPeer> join_as_input_peer;
+ if (as_dialog_id.is_valid()) {
+ join_as_input_peer = td_->messages_manager_->get_input_peer(as_dialog_id, AccessRights::Read);
+ } else {
+ join_as_input_peer = make_tl_object<telegram_api::inputPeerSelf>();
+ }
+ CHECK(join_as_input_peer != nullptr);
+
+ int32 flags = 0;
+ if (is_muted) {
+ flags |= telegram_api::phone_joinGroupCall::MUTED_MASK;
+ }
+ if (!invite_hash.empty()) {
+ flags |= telegram_api::phone_joinGroupCall::INVITE_HASH_MASK;
+ }
+ if (is_video_stopped) {
+ flags |= telegram_api::phone_joinGroupCall::VIDEO_STOPPED_MASK;
+ }
+ auto query = G()->net_query_creator().create(telegram_api::phone_joinGroupCall(
+ flags, false /*ignored*/, false /*ignored*/, input_group_call_id.get_input_group_call(),
+ std::move(join_as_input_peer), invite_hash, make_tl_object<telegram_api::dataJSON>(payload)));
+ auto join_query_ref = query.get_weak();
+ send_query(std::move(query));
+ return join_query_ref;
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_joinGroupCall>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for JoinGroupCallQuery with generation " << generation_ << ": " << to_string(ptr);
+ td_->group_call_manager_->process_join_group_call_response(input_group_call_id_, generation_, std::move(ptr),
+ std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class JoinGroupCallPresentationQuery final : public Td::ResultHandler {
+ InputGroupCallId input_group_call_id_;
+ uint64 generation_ = 0;
+
+ public:
+ NetQueryRef send(InputGroupCallId input_group_call_id, const string &payload, uint64 generation) {
+ input_group_call_id_ = input_group_call_id;
+ generation_ = generation;
+
+ auto query = G()->net_query_creator().create(telegram_api::phone_joinGroupCallPresentation(
+ input_group_call_id.get_input_group_call(), make_tl_object<telegram_api::dataJSON>(payload)));
+ auto join_query_ref = query.get_weak();
+ send_query(std::move(query));
+ return join_query_ref;
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_joinGroupCallPresentation>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for JoinGroupCallPresentationQuery with generation " << generation_ << ": "
+ << to_string(ptr);
+ td_->group_call_manager_->process_join_group_call_presentation_response(input_group_call_id_, generation_,
+ std::move(ptr), Status::OK());
+ }
+
+ void on_error(Status status) final {
+ td_->group_call_manager_->process_join_group_call_presentation_response(input_group_call_id_, generation_, nullptr,
+ std::move(status));
+ }
+};
+
+class LeaveGroupCallPresentationQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit LeaveGroupCallPresentationQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_leaveGroupCallPresentation(input_group_call_id.get_input_group_call())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_editGroupCallTitle>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for LeaveGroupCallPresentationQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "PARTICIPANT_PRESENTATION_MISSING") {
+ promise_.set_value(Unit());
+ return;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class EditGroupCallTitleQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit EditGroupCallTitleQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, const string &title) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_editGroupCallTitle(input_group_call_id.get_input_group_call(), title)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_editGroupCallTitle>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for EditGroupCallTitleQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "GROUPCALL_NOT_MODIFIED") {
+ promise_.set_value(Unit());
+ return;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleGroupCallStartSubscriptionQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ToggleGroupCallStartSubscriptionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, bool start_subscribed) {
+ send_query(G()->net_query_creator().create(telegram_api::phone_toggleGroupCallStartSubscription(
+ input_group_call_id.get_input_group_call(), start_subscribed)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_toggleGroupCallStartSubscription>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for ToggleGroupCallStartSubscriptionQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "GROUPCALL_NOT_MODIFIED") {
+ promise_.set_value(Unit());
+ return;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleGroupCallSettingsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ToggleGroupCallSettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(int32 flags, InputGroupCallId input_group_call_id, bool join_muted) {
+ send_query(G()->net_query_creator().create(telegram_api::phone_toggleGroupCallSettings(
+ flags, false /*ignored*/, input_group_call_id.get_input_group_call(), join_muted)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_toggleGroupCallSettings>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for ToggleGroupCallSettingsQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "GROUPCALL_NOT_MODIFIED") {
+ promise_.set_value(Unit());
+ return;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class InviteToGroupCallQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit InviteToGroupCallQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, vector<tl_object_ptr<telegram_api::InputUser>> input_users) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_inviteToGroupCall(input_group_call_id.get_input_group_call(), std::move(input_users))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_inviteToGroupCall>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for InviteToGroupCallQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ExportGroupCallInviteQuery final : public Td::ResultHandler {
+ Promise<string> promise_;
+
+ public:
+ explicit ExportGroupCallInviteQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, bool can_self_unmute) {
+ int32 flags = 0;
+ if (can_self_unmute) {
+ flags |= telegram_api::phone_exportGroupCallInvite::CAN_SELF_UNMUTE_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::phone_exportGroupCallInvite(
+ flags, false /*ignored*/, input_group_call_id.get_input_group_call())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_exportGroupCallInvite>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ promise_.set_value(std::move(ptr->link_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleGroupCallRecordQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ToggleGroupCallRecordQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, bool is_enabled, const string &title, bool record_video,
+ bool use_portrait_orientation) {
+ int32 flags = 0;
+ if (is_enabled) {
+ flags |= telegram_api::phone_toggleGroupCallRecord::START_MASK;
+ }
+ if (!title.empty()) {
+ flags |= telegram_api::phone_toggleGroupCallRecord::TITLE_MASK;
+ }
+ if (record_video) {
+ flags |= telegram_api::phone_toggleGroupCallRecord::VIDEO_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::phone_toggleGroupCallRecord(
+ flags, false /*ignored*/, false /*ignored*/, input_group_call_id.get_input_group_call(), title,
+ use_portrait_orientation)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_toggleGroupCallRecord>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for ToggleGroupCallRecordQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "GROUPCALL_NOT_MODIFIED") {
+ promise_.set_value(Unit());
+ return;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class EditGroupCallParticipantQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit EditGroupCallParticipantQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, DialogId dialog_id, bool set_is_mited, bool is_muted,
+ int32 volume_level, bool set_raise_hand, bool raise_hand, bool set_video_is_stopped, bool video_is_stopped,
+ bool set_video_is_paused, bool video_is_paused, bool set_presentation_is_paused,
+ bool presentation_is_paused) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Know);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = 0;
+ if (set_raise_hand) {
+ flags |= telegram_api::phone_editGroupCallParticipant::RAISE_HAND_MASK;
+ } else if (volume_level) {
+ flags |= telegram_api::phone_editGroupCallParticipant::VOLUME_MASK;
+ } else if (set_is_mited) {
+ flags |= telegram_api::phone_editGroupCallParticipant::MUTED_MASK;
+ } else if (set_video_is_stopped) {
+ flags |= telegram_api::phone_editGroupCallParticipant::VIDEO_STOPPED_MASK;
+ } else if (set_video_is_paused) {
+ flags |= telegram_api::phone_editGroupCallParticipant::VIDEO_PAUSED_MASK;
+ } else if (set_presentation_is_paused) {
+ flags |= telegram_api::phone_editGroupCallParticipant::PRESENTATION_PAUSED_MASK;
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::phone_editGroupCallParticipant(
+ flags, input_group_call_id.get_input_group_call(), std::move(input_peer), is_muted, volume_level, raise_hand,
+ video_is_stopped, video_is_paused, presentation_is_paused)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_editGroupCallParticipant>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for EditGroupCallParticipantQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class CheckGroupCallQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit CheckGroupCallQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, vector<int32> &&audio_sources) {
+ for (auto &audio_source : audio_sources) {
+ CHECK(audio_source != 0);
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_checkGroupCall(input_group_call_id.get_input_group_call(), std::move(audio_sources))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_checkGroupCall>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ vector<int32> active_audio_sources = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for CheckGroupCallQuery: " << active_audio_sources;
+
+ if (!active_audio_sources.empty()) {
+ promise_.set_value(Unit());
+ } else {
+ promise_.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class LeaveGroupCallQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit LeaveGroupCallQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id, int32 audio_source) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_leaveGroupCall(input_group_call_id.get_input_group_call(), audio_source)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_leaveGroupCall>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for LeaveGroupCallQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class DiscardGroupCallQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit DiscardGroupCallQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(InputGroupCallId input_group_call_id) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::phone_discardGroupCall(input_group_call_id.get_input_group_call())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::phone_discardGroupCall>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for DiscardGroupCallQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+struct GroupCallManager::GroupCall {
+ GroupCallId group_call_id;
+ DialogId dialog_id;
+ string title;
+ bool is_inited = false;
+ bool is_active = false;
+ bool is_rtmp_stream = false;
+ bool is_joined = false;
+ bool need_rejoin = false;
+ bool is_being_left = false;
+ bool is_speaking = false;
+ bool can_self_unmute = false;
+ bool can_be_managed = false;
+ bool has_hidden_listeners = false;
+ bool syncing_participants = false;
+ bool need_syncing_participants = false;
+ bool loaded_all_participants = false;
+ bool start_subscribed = false;
+ bool is_my_video_paused = false;
+ bool is_my_video_enabled = false;
+ bool is_my_presentation_paused = false;
+ bool mute_new_participants = false;
+ bool allowed_toggle_mute_new_participants = false;
+ bool joined_date_asc = false;
+ bool is_video_recorded = false;
+ int32 scheduled_start_date = 0;
+ int32 participant_count = 0;
+ int32 duration = 0;
+ int32 audio_source = 0;
+ int32 joined_date = 0;
+ int32 record_start_date = 0;
+ int32 unmuted_video_count = 0;
+ int32 unmuted_video_limit = 0;
+ DcId stream_dc_id;
+ DialogId as_dialog_id;
+
+ int32 version = -1;
+ int32 leave_version = -1;
+ int32 title_version = -1;
+ int32 start_subscribed_version = -1;
+ int32 can_enable_video_version = -1;
+ int32 mute_version = -1;
+ int32 stream_dc_id_version = -1;
+ int32 record_start_date_version = -1;
+ int32 scheduled_start_date_version = -1;
+
+ vector<Promise<Unit>> after_join;
+ bool have_pending_start_subscribed = false;
+ bool pending_start_subscribed = false;
+ bool have_pending_is_my_video_paused = false;
+ bool pending_is_my_video_paused = false;
+ bool have_pending_is_my_video_enabled = false;
+ bool pending_is_my_video_enabled = false;
+ bool have_pending_is_my_presentation_paused = false;
+ bool pending_is_my_presentation_paused = false;
+ bool have_pending_mute_new_participants = false;
+ bool pending_mute_new_participants = false;
+ string pending_title;
+ bool have_pending_record_start_date = false;
+ int32 pending_record_start_date = 0;
+ string pending_record_title;
+ bool pending_record_record_video = false;
+ bool pending_record_use_portrait_orientation = false;
+ uint64 toggle_recording_generation = 0;
+};
+
+struct GroupCallManager::GroupCallParticipants {
+ vector<GroupCallParticipant> participants;
+ string next_offset;
+ GroupCallParticipantOrder min_order = GroupCallParticipantOrder::max();
+ bool joined_date_asc = false;
+ int32 local_unmuted_video_count = 0;
+
+ bool are_administrators_loaded = false;
+ vector<DialogId> administrator_dialog_ids;
+
+ struct PendingUpdates {
+ FlatHashMap<DialogId, unique_ptr<GroupCallParticipant>, DialogIdHash> updates;
+ };
+ std::map<int32, PendingUpdates> pending_version_updates_;
+ std::map<int32, PendingUpdates> pending_mute_updates_;
+};
+
+struct GroupCallManager::GroupCallRecentSpeakers {
+ vector<std::pair<DialogId, int32>> users; // participant + time; sorted by time
+ bool is_changed = false;
+ vector<std::pair<DialogId, bool>> last_sent_users;
+};
+
+struct GroupCallManager::PendingJoinRequest {
+ NetQueryRef query_ref;
+ uint64 generation = 0;
+ int32 audio_source = 0;
+ DialogId as_dialog_id;
+ Promise<string> promise;
+};
+
+GroupCallManager::GroupCallManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+ update_group_call_participant_order_timeout_.set_callback(on_update_group_call_participant_order_timeout_callback);
+ update_group_call_participant_order_timeout_.set_callback_data(static_cast<void *>(this));
+
+ check_group_call_is_joined_timeout_.set_callback(on_check_group_call_is_joined_timeout_callback);
+ check_group_call_is_joined_timeout_.set_callback_data(static_cast<void *>(this));
+
+ pending_send_speaking_action_timeout_.set_callback(on_pending_send_speaking_action_timeout_callback);
+ pending_send_speaking_action_timeout_.set_callback_data(static_cast<void *>(this));
+
+ recent_speaker_update_timeout_.set_callback(on_recent_speaker_update_timeout_callback);
+ recent_speaker_update_timeout_.set_callback_data(static_cast<void *>(this));
+
+ sync_participants_timeout_.set_callback(on_sync_participants_timeout_callback);
+ sync_participants_timeout_.set_callback_data(static_cast<void *>(this));
+}
+
+GroupCallManager::~GroupCallManager() = default;
+
+void GroupCallManager::tear_down() {
+ parent_.reset();
+}
+
+void GroupCallManager::on_update_group_call_participant_order_timeout_callback(void *group_call_manager_ptr,
+ int64 group_call_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto group_call_manager = static_cast<GroupCallManager *>(group_call_manager_ptr);
+ send_closure_later(group_call_manager->actor_id(group_call_manager),
+ &GroupCallManager::on_update_group_call_participant_order_timeout,
+ GroupCallId(narrow_cast<int32>(group_call_id_int)));
+}
+
+void GroupCallManager::on_update_group_call_participant_order_timeout(GroupCallId group_call_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Receive update group call participant order timeout in " << group_call_id;
+ auto input_group_call_id = get_input_group_call_id(group_call_id).move_as_ok();
+
+ if (!need_group_call_participants(input_group_call_id)) {
+ return;
+ }
+
+ bool can_self_unmute = get_group_call_can_self_unmute(input_group_call_id);
+ auto *participants = add_group_call_participants(input_group_call_id);
+ update_group_call_participants_order(input_group_call_id, can_self_unmute, participants,
+ "on_update_group_call_participant_order_timeout");
+}
+
+void GroupCallManager::on_check_group_call_is_joined_timeout_callback(void *group_call_manager_ptr,
+ int64 group_call_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto group_call_manager = static_cast<GroupCallManager *>(group_call_manager_ptr);
+ send_closure_later(group_call_manager->actor_id(group_call_manager),
+ &GroupCallManager::on_check_group_call_is_joined_timeout,
+ GroupCallId(narrow_cast<int32>(group_call_id_int)));
+}
+
+void GroupCallManager::on_check_group_call_is_joined_timeout(GroupCallId group_call_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Receive check group call is_joined timeout in " << group_call_id;
+ auto input_group_call_id = get_input_group_call_id(group_call_id).move_as_ok();
+
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ auto audio_source = group_call->audio_source;
+ if (!group_call->is_joined || is_group_call_being_joined(input_group_call_id) ||
+ check_group_call_is_joined_timeout_.has_timeout(group_call_id.get()) || audio_source == 0) {
+ return;
+ }
+
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), input_group_call_id, audio_source](Result<Unit> &&result) mutable {
+ send_closure(actor_id, &GroupCallManager::finish_check_group_call_is_joined, input_group_call_id, audio_source,
+ std::move(result));
+ });
+ td_->create_handler<CheckGroupCallQuery>(std::move(promise))->send(input_group_call_id, {audio_source});
+}
+
+void GroupCallManager::on_pending_send_speaking_action_timeout_callback(void *group_call_manager_ptr,
+ int64 group_call_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto group_call_manager = static_cast<GroupCallManager *>(group_call_manager_ptr);
+ send_closure_later(group_call_manager->actor_id(group_call_manager),
+ &GroupCallManager::on_send_speaking_action_timeout,
+ GroupCallId(narrow_cast<int32>(group_call_id_int)));
+}
+
+void GroupCallManager::on_send_speaking_action_timeout(GroupCallId group_call_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Receive send_speaking_action timeout in " << group_call_id;
+ auto input_group_call_id = get_input_group_call_id(group_call_id).move_as_ok();
+
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited && group_call->dialog_id.is_valid());
+ if (!group_call->is_joined || !group_call->is_speaking) {
+ return;
+ }
+
+ CHECK(group_call->as_dialog_id.is_valid());
+ on_user_speaking_in_group_call(group_call_id, group_call->as_dialog_id, G()->unix_time());
+
+ pending_send_speaking_action_timeout_.add_timeout_in(group_call_id.get(), 4.0);
+
+ td_->messages_manager_->send_dialog_action(group_call->dialog_id, MessageId(), DialogAction::get_speaking_action(),
+ Promise<Unit>());
+}
+
+void GroupCallManager::on_recent_speaker_update_timeout_callback(void *group_call_manager_ptr,
+ int64 group_call_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto group_call_manager = static_cast<GroupCallManager *>(group_call_manager_ptr);
+ send_closure_later(group_call_manager->actor_id(group_call_manager),
+ &GroupCallManager::on_recent_speaker_update_timeout,
+ GroupCallId(narrow_cast<int32>(group_call_id_int)));
+}
+
+void GroupCallManager::on_recent_speaker_update_timeout(GroupCallId group_call_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Receive recent speaker update timeout in " << group_call_id;
+ auto input_group_call_id = get_input_group_call_id(group_call_id).move_as_ok();
+
+ get_recent_speakers(get_group_call(input_group_call_id),
+ false); // will update the list and send updateGroupCall if needed
+}
+
+void GroupCallManager::on_sync_participants_timeout_callback(void *group_call_manager_ptr, int64 group_call_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto group_call_manager = static_cast<GroupCallManager *>(group_call_manager_ptr);
+ send_closure_later(group_call_manager->actor_id(group_call_manager), &GroupCallManager::on_sync_participants_timeout,
+ GroupCallId(narrow_cast<int32>(group_call_id_int)));
+}
+
+void GroupCallManager::on_sync_participants_timeout(GroupCallId group_call_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Receive sync participants timeout in " << group_call_id;
+ auto input_group_call_id = get_input_group_call_id(group_call_id).move_as_ok();
+
+ sync_group_call_participants(input_group_call_id);
+}
+
+bool GroupCallManager::is_group_call_being_joined(InputGroupCallId input_group_call_id) const {
+ return pending_join_requests_.count(input_group_call_id) != 0;
+}
+
+bool GroupCallManager::is_group_call_joined(InputGroupCallId input_group_call_id) const {
+ auto group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr) {
+ return false;
+ }
+ return group_call->is_joined && !group_call->is_being_left;
+}
+
+GroupCallId GroupCallManager::get_group_call_id(InputGroupCallId input_group_call_id, DialogId dialog_id) {
+ if (td_->auth_manager_->is_bot() || !input_group_call_id.is_valid()) {
+ return GroupCallId();
+ }
+ return add_group_call(input_group_call_id, dialog_id)->group_call_id;
+}
+
+Result<InputGroupCallId> GroupCallManager::get_input_group_call_id(GroupCallId group_call_id) {
+ if (!group_call_id.is_valid()) {
+ return Status::Error(400, "Invalid group call identifier specified");
+ }
+ if (group_call_id.get() <= 0 || group_call_id.get() > max_group_call_id_.get()) {
+ return Status::Error(400, "Wrong group call identifier specified");
+ }
+ CHECK(static_cast<size_t>(group_call_id.get()) <= input_group_call_ids_.size());
+ auto input_group_call_id = input_group_call_ids_[group_call_id.get() - 1];
+ LOG(DEBUG) << "Found " << input_group_call_id;
+ return input_group_call_id;
+}
+
+GroupCallId GroupCallManager::get_next_group_call_id(InputGroupCallId input_group_call_id) {
+ max_group_call_id_ = GroupCallId(max_group_call_id_.get() + 1);
+ input_group_call_ids_.push_back(input_group_call_id);
+ return max_group_call_id_;
+}
+
+GroupCallManager::GroupCall *GroupCallManager::add_group_call(InputGroupCallId input_group_call_id,
+ DialogId dialog_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto &group_call = group_calls_[input_group_call_id];
+ if (group_call == nullptr) {
+ group_call = make_unique<GroupCall>();
+ group_call->group_call_id = get_next_group_call_id(input_group_call_id);
+ LOG(INFO) << "Add " << input_group_call_id << " from " << dialog_id << " as " << group_call->group_call_id;
+ }
+ if (!group_call->dialog_id.is_valid()) {
+ group_call->dialog_id = dialog_id;
+ }
+ return group_call.get();
+}
+
+const GroupCallManager::GroupCall *GroupCallManager::get_group_call(InputGroupCallId input_group_call_id) const {
+ auto it = group_calls_.find(input_group_call_id);
+ if (it == group_calls_.end()) {
+ return nullptr;
+ } else {
+ return it->second.get();
+ }
+}
+
+GroupCallManager::GroupCall *GroupCallManager::get_group_call(InputGroupCallId input_group_call_id) {
+ auto it = group_calls_.find(input_group_call_id);
+ if (it == group_calls_.end()) {
+ return nullptr;
+ } else {
+ return it->second.get();
+ }
+}
+
+Status GroupCallManager::can_manage_group_calls(DialogId dialog_id) const {
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ if (!td_->contacts_manager_->get_chat_permissions(chat_id).can_manage_calls()) {
+ return Status::Error(400, "Not enough rights in the chat");
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ auto channel_id = dialog_id.get_channel_id();
+ if (!td_->contacts_manager_->get_channel_permissions(channel_id).can_manage_calls()) {
+ return Status::Error(400, "Not enough rights in the chat");
+ }
+ break;
+ }
+ case DialogType::User:
+ case DialogType::SecretChat:
+ return Status::Error(400, "Chat can't have a voice chat");
+ case DialogType::None:
+ // OK
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return Status::OK();
+}
+
+bool GroupCallManager::can_manage_group_call(InputGroupCallId input_group_call_id) const {
+ auto group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr) {
+ return false;
+ }
+ return can_manage_group_calls(group_call->dialog_id).is_ok();
+}
+
+bool GroupCallManager::get_group_call_can_self_unmute(InputGroupCallId input_group_call_id) const {
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ return group_call->can_self_unmute;
+}
+
+bool GroupCallManager::get_group_call_joined_date_asc(InputGroupCallId input_group_call_id) const {
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ return group_call->joined_date_asc;
+}
+
+void GroupCallManager::get_group_call_join_as(DialogId dialog_id,
+ Promise<td_api::object_ptr<td_api::messageSenders>> &&promise) {
+ if (!dialog_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
+ }
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_group_call_join_as")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access chat"));
+ }
+
+ td_->create_handler<GetGroupCallJoinAsQuery>(std::move(promise))->send(dialog_id);
+}
+
+void GroupCallManager::set_group_call_default_join_as(DialogId dialog_id, DialogId as_dialog_id,
+ Promise<Unit> &&promise) {
+ if (!dialog_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
+ }
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "set_group_call_default_join_as")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access chat"));
+ }
+
+ switch (as_dialog_id.get_type()) {
+ case DialogType::User:
+ if (as_dialog_id != DialogId(td_->contacts_manager_->get_my_id())) {
+ return promise.set_error(Status::Error(400, "Can't join voice chat as another user"));
+ }
+ break;
+ case DialogType::Chat:
+ case DialogType::Channel:
+ if (!td_->messages_manager_->have_dialog_force(as_dialog_id, "set_group_call_default_join_as 2")) {
+ return promise.set_error(Status::Error(400, "Participant chat not found"));
+ }
+ break;
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Can't join voice chat as a secret chat"));
+ default:
+ return promise.set_error(Status::Error(400, "Invalid default participant identifier specified"));
+ }
+ if (!td_->messages_manager_->have_input_peer(as_dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access specified default participant chat"));
+ }
+
+ td_->create_handler<SaveDefaultGroupCallJoinAsQuery>(std::move(promise))->send(dialog_id, as_dialog_id);
+ td_->messages_manager_->on_update_dialog_default_join_group_call_as_dialog_id(dialog_id, as_dialog_id, true);
+}
+
+void GroupCallManager::create_voice_chat(DialogId dialog_id, string title, int32 start_date, bool is_rtmp_stream,
+ Promise<GroupCallId> &&promise) {
+ if (!dialog_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
+ }
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "create_voice_chat")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access chat"));
+ }
+
+ TRY_STATUS_PROMISE(promise, can_manage_group_calls(dialog_id));
+
+ title = clean_name(title, MAX_TITLE_LENGTH);
+
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_id, promise = std::move(promise)](Result<InputGroupCallId> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::on_voice_chat_created, dialog_id, result.move_as_ok(),
+ std::move(promise));
+ }
+ });
+ td_->create_handler<CreateGroupCallQuery>(std::move(query_promise))
+ ->send(dialog_id, title, start_date, is_rtmp_stream);
+}
+
+void GroupCallManager::get_voice_chat_rtmp_stream_url(DialogId dialog_id, bool revoke,
+ Promise<td_api::object_ptr<td_api::rtmpUrl>> &&promise) {
+ if (!dialog_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
+ }
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_voice_chat_rtmp_stream_url")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access chat"));
+ }
+
+ TRY_STATUS_PROMISE(promise, can_manage_group_calls(dialog_id));
+
+ td_->create_handler<GetGroupCallRtmpStreamUrlGroupCallQuery>(std::move(promise))->send(dialog_id, revoke);
+}
+
+void GroupCallManager::on_voice_chat_created(DialogId dialog_id, InputGroupCallId input_group_call_id,
+ Promise<GroupCallId> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ if (!input_group_call_id.is_valid()) {
+ return promise.set_error(Status::Error(500, "Receive invalid group call identifier"));
+ }
+
+ td_->messages_manager_->on_update_dialog_group_call(dialog_id, true, true, "on_voice_chat_created");
+ td_->messages_manager_->on_update_dialog_group_call_id(dialog_id, input_group_call_id);
+
+ promise.set_value(get_group_call_id(input_group_call_id, dialog_id));
+}
+
+void GroupCallManager::get_group_call(GroupCallId group_call_id,
+ Promise<td_api::object_ptr<td_api::groupCall>> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto group_call = get_group_call(input_group_call_id);
+ if (group_call != nullptr && group_call->is_inited) {
+ return promise.set_value(get_group_call_object(group_call, get_recent_speakers(group_call, false)));
+ }
+
+ reload_group_call(input_group_call_id, std::move(promise));
+}
+
+void GroupCallManager::on_update_group_call_rights(InputGroupCallId input_group_call_id) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto group_call = get_group_call(input_group_call_id);
+ if (need_group_call_participants(input_group_call_id, group_call)) {
+ CHECK(group_call != nullptr && group_call->is_inited);
+ try_load_group_call_administrators(input_group_call_id, group_call->dialog_id);
+
+ auto *group_call_participants = add_group_call_participants(input_group_call_id);
+ if (group_call_participants->are_administrators_loaded) {
+ update_group_call_participants_can_be_muted(
+ input_group_call_id, can_manage_group_calls(group_call->dialog_id).is_ok(), group_call_participants);
+ }
+ }
+
+ if (group_call != nullptr && group_call->is_inited) {
+ bool can_be_managed = group_call->is_active && can_manage_group_calls(group_call->dialog_id).is_ok();
+ if (can_be_managed != group_call->can_be_managed) {
+ group_call->can_be_managed = can_be_managed;
+ send_update_group_call(group_call, "on_update_group_call_rights");
+ }
+ }
+
+ reload_group_call(input_group_call_id, Auto());
+}
+
+void GroupCallManager::reload_group_call(InputGroupCallId input_group_call_id,
+ Promise<td_api::object_ptr<td_api::groupCall>> &&promise) {
+ if (td_->auth_manager_->is_bot()) {
+ return promise.set_error(Status::Error(400, "Bots can't get group call info"));
+ }
+ if (!input_group_call_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid group call identifier specified"));
+ }
+
+ auto &queries = load_group_call_queries_[input_group_call_id];
+ queries.push_back(std::move(promise));
+ if (queries.size() == 1) {
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id](
+ Result<tl_object_ptr<telegram_api::phone_groupCall>> &&result) {
+ send_closure(actor_id, &GroupCallManager::finish_get_group_call, input_group_call_id, std::move(result));
+ });
+ td_->create_handler<GetGroupCallQuery>(std::move(query_promise))->send(input_group_call_id, 3);
+ }
+}
+
+void GroupCallManager::finish_get_group_call(InputGroupCallId input_group_call_id,
+ Result<tl_object_ptr<telegram_api::phone_groupCall>> &&result) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
+
+ auto it = load_group_call_queries_.find(input_group_call_id);
+ CHECK(it != load_group_call_queries_.end());
+ CHECK(!it->second.empty());
+ auto promises = std::move(it->second);
+ load_group_call_queries_.erase(it);
+
+ if (result.is_ok()) {
+ td_->contacts_manager_->on_get_users(std::move(result.ok_ref()->users_), "finish_get_group_call");
+ td_->contacts_manager_->on_get_chats(std::move(result.ok_ref()->chats_), "finish_get_group_call");
+
+ if (update_group_call(result.ok()->call_, DialogId()) != input_group_call_id) {
+ LOG(ERROR) << "Expected " << input_group_call_id << ", but received " << to_string(result.ok());
+ result = Status::Error(500, "Receive another group call");
+ }
+ }
+
+ if (result.is_error()) {
+ fail_promises(promises, result.move_as_error());
+ return;
+ }
+
+ auto call = result.move_as_ok();
+ int32 version = 0;
+ if (call->call_->get_id() == telegram_api::groupCall::ID) {
+ version = static_cast<const telegram_api::groupCall *>(call->call_.get())->version_;
+ }
+ process_group_call_participants(input_group_call_id, std::move(call->participants_), version, string(), true, false);
+ if (need_group_call_participants(input_group_call_id)) {
+ auto *group_call_participants = add_group_call_participants(input_group_call_id);
+ if (group_call_participants->next_offset.empty()) {
+ group_call_participants->next_offset = std::move(call->participants_next_offset_);
+ }
+ }
+
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ for (auto &promise : promises) {
+ if (promise) {
+ promise.set_value(get_group_call_object(group_call, get_recent_speakers(group_call, false)));
+ }
+ }
+}
+
+void GroupCallManager::finish_check_group_call_is_joined(InputGroupCallId input_group_call_id, int32 audio_source,
+ Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Finish check group call is_joined for " << input_group_call_id;
+
+ if (result.is_error()) {
+ auto message = result.error().message();
+ if (message == "GROUPCALL_JOIN_MISSING" || message == "GROUPCALL_FORBIDDEN" || message == "GROUPCALL_INVALID") {
+ on_group_call_left(input_group_call_id, audio_source, message == "GROUPCALL_JOIN_MISSING");
+ }
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ CHECK(audio_source != 0);
+ if (!group_call->is_joined || is_group_call_being_joined(input_group_call_id) ||
+ check_group_call_is_joined_timeout_.has_timeout(group_call->group_call_id.get()) ||
+ group_call->audio_source != audio_source) {
+ return;
+ }
+
+ int32 next_timeout = result.is_ok() ? CHECK_GROUP_CALL_IS_JOINED_TIMEOUT : 1;
+ check_group_call_is_joined_timeout_.set_timeout_in(group_call->group_call_id.get(), next_timeout);
+}
+
+const string &GroupCallManager::get_group_call_title(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ return group_call->pending_title.empty() ? group_call->title : group_call->pending_title;
+}
+
+bool GroupCallManager::get_group_call_start_subscribed(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ return group_call->have_pending_start_subscribed ? group_call->pending_start_subscribed
+ : group_call->start_subscribed;
+}
+
+bool GroupCallManager::get_group_call_is_my_video_paused(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ return group_call->have_pending_is_my_video_paused ? group_call->pending_is_my_video_paused
+ : group_call->is_my_video_paused;
+}
+
+bool GroupCallManager::get_group_call_is_my_video_enabled(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ return group_call->have_pending_is_my_video_enabled ? group_call->pending_is_my_video_enabled
+ : group_call->is_my_video_enabled;
+}
+
+bool GroupCallManager::get_group_call_is_my_presentation_paused(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ return group_call->have_pending_is_my_presentation_paused ? group_call->pending_is_my_presentation_paused
+ : group_call->is_my_presentation_paused;
+}
+
+bool GroupCallManager::get_group_call_mute_new_participants(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ return group_call->have_pending_mute_new_participants ? group_call->pending_mute_new_participants
+ : group_call->mute_new_participants;
+}
+
+int32 GroupCallManager::get_group_call_record_start_date(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ return group_call->have_pending_record_start_date ? group_call->pending_record_start_date
+ : group_call->record_start_date;
+}
+
+bool GroupCallManager::get_group_call_is_video_recorded(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ return group_call->have_pending_record_start_date ? group_call->pending_record_record_video
+ : group_call->is_video_recorded;
+}
+
+bool GroupCallManager::get_group_call_has_recording(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ return get_group_call_record_start_date(group_call) != 0;
+}
+
+bool GroupCallManager::get_group_call_can_enable_video(const GroupCall *group_call) {
+ CHECK(group_call != nullptr);
+ if (group_call->unmuted_video_limit <= 0) {
+ return true;
+ }
+ return group_call->unmuted_video_count < group_call->unmuted_video_limit;
+}
+
+bool GroupCallManager::need_group_call_participants(InputGroupCallId input_group_call_id) const {
+ return need_group_call_participants(input_group_call_id, get_group_call(input_group_call_id));
+}
+
+bool GroupCallManager::need_group_call_participants(InputGroupCallId input_group_call_id,
+ const GroupCall *group_call) const {
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return false;
+ }
+ if (group_call->is_joined || group_call->need_rejoin || is_group_call_being_joined(input_group_call_id)) {
+ return true;
+ }
+ return false;
+}
+
+void GroupCallManager::on_get_group_call_participants(
+ InputGroupCallId input_group_call_id, tl_object_ptr<telegram_api::phone_groupParticipants> &&participants,
+ bool is_load, const string &offset) {
+ LOG(INFO) << "Receive group call participants: " << to_string(participants);
+
+ CHECK(participants != nullptr);
+ td_->contacts_manager_->on_get_users(std::move(participants->users_), "on_get_group_call_participants");
+ td_->contacts_manager_->on_get_chats(std::move(participants->chats_), "on_get_group_call_participants");
+
+ if (!need_group_call_participants(input_group_call_id)) {
+ return;
+ }
+
+ bool is_sync = is_load && offset.empty();
+ if (is_sync) {
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ is_sync = group_call->syncing_participants;
+ if (is_sync) {
+ group_call->syncing_participants = false;
+
+ if (group_call->version >= participants->version_) {
+ LOG(INFO) << "Ignore result of outdated participants sync with version " << participants->version_ << " in "
+ << input_group_call_id << " from " << group_call->dialog_id << ", because current version is "
+ << group_call->version;
+ return;
+ }
+ LOG(INFO) << "Finish syncing participants in " << input_group_call_id << " from " << group_call->dialog_id
+ << " with version " << participants->version_;
+ group_call->version = participants->version_;
+ }
+ }
+
+ auto is_empty = participants->participants_.empty();
+ process_group_call_participants(input_group_call_id, std::move(participants->participants_), participants->version_,
+ offset, is_load, is_sync);
+
+ if (!is_sync) {
+ on_receive_group_call_version(input_group_call_id, participants->version_);
+ }
+
+ if (is_load) {
+ auto *group_call_participants = add_group_call_participants(input_group_call_id);
+ if (group_call_participants->next_offset == offset) {
+ if (!offset.empty() && participants->next_offset_.empty() && group_call_participants->joined_date_asc) {
+ LOG(INFO) << "Ignore empty next_offset";
+ } else {
+ group_call_participants->next_offset = std::move(participants->next_offset_);
+ }
+ }
+
+ if (is_empty || is_sync) {
+ bool need_update = false;
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ if (is_empty && !group_call->loaded_all_participants) {
+ group_call->loaded_all_participants = true;
+ need_update = true;
+ }
+
+ auto real_participant_count = participants->count_;
+ if (!group_call->is_joined) {
+ real_participant_count++;
+ }
+ if (is_empty) {
+ auto known_participant_count = static_cast<int32>(group_call_participants->participants.size());
+ if (real_participant_count != known_participant_count) {
+ LOG(ERROR) << "Receive participant count " << real_participant_count << ", but know "
+ << known_participant_count << " participants in " << input_group_call_id << " from "
+ << group_call->dialog_id;
+ real_participant_count = known_participant_count;
+ }
+ }
+ if (!is_empty && is_sync && group_call->loaded_all_participants && real_participant_count > 50) {
+ group_call->loaded_all_participants = false;
+ need_update = true;
+ }
+ if (real_participant_count != group_call->participant_count) {
+ if (!is_sync) {
+ LOG(ERROR) << "Have participant count " << group_call->participant_count << " instead of "
+ << real_participant_count << " in " << input_group_call_id << " from " << group_call->dialog_id;
+ }
+ need_update |=
+ set_group_call_participant_count(group_call, real_participant_count, "on_get_group_call_participants");
+ }
+ if (process_pending_group_call_participant_updates(input_group_call_id)) {
+ need_update = false;
+ }
+ if (group_call->loaded_all_participants || !group_call_participants->min_order.has_video()) {
+ set_group_call_unmuted_video_count(group_call, group_call_participants->local_unmuted_video_count,
+ "on_get_group_call_participants");
+ }
+ if (need_update) {
+ send_update_group_call(group_call, "on_get_group_call_participants");
+ }
+
+ if (is_sync && group_call->need_syncing_participants) {
+ group_call->need_syncing_participants = false;
+ sync_group_call_participants(input_group_call_id);
+ }
+ }
+ }
+}
+
+GroupCallManager::GroupCallParticipants *GroupCallManager::add_group_call_participants(
+ InputGroupCallId input_group_call_id) {
+ CHECK(need_group_call_participants(input_group_call_id));
+
+ auto &participants = group_call_participants_[input_group_call_id];
+ if (participants == nullptr) {
+ participants = make_unique<GroupCallParticipants>();
+ participants->joined_date_asc = get_group_call_joined_date_asc(input_group_call_id);
+ }
+ return participants.get();
+}
+
+GroupCallParticipant *GroupCallManager::get_group_call_participant(InputGroupCallId input_group_call_id,
+ DialogId dialog_id) {
+ return get_group_call_participant(add_group_call_participants(input_group_call_id), dialog_id);
+}
+
+GroupCallParticipant *GroupCallManager::get_group_call_participant(GroupCallParticipants *group_call_participants,
+ DialogId dialog_id) const {
+ if (!dialog_id.is_valid()) {
+ return nullptr;
+ }
+ if (dialog_id == DialogId(td_->contacts_manager_->get_my_id())) {
+ for (auto &group_call_participant : group_call_participants->participants) {
+ if (group_call_participant.is_self) {
+ return &group_call_participant;
+ }
+ }
+ } else {
+ for (auto &group_call_participant : group_call_participants->participants) {
+ if (group_call_participant.dialog_id == dialog_id) {
+ return &group_call_participant;
+ }
+ }
+ }
+ return nullptr;
+}
+
+void GroupCallManager::on_update_group_call_participants(
+ InputGroupCallId input_group_call_id, vector<tl_object_ptr<telegram_api::groupCallParticipant>> &&participants,
+ int32 version, bool is_recursive) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ if (!need_group_call_participants(input_group_call_id)) {
+ int32 diff = 0;
+ int32 video_diff = 0;
+ bool need_update = false;
+ auto group_call = get_group_call(input_group_call_id);
+ for (auto &group_call_participant : participants) {
+ GroupCallParticipant participant(group_call_participant, version);
+ if (!participant.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << to_string(group_call_participant);
+ continue;
+ }
+ if (participant.joined_date == 0) {
+ if (group_call == nullptr || version > group_call->leave_version) {
+ diff--;
+ video_diff += participant.video_diff;
+ }
+ remove_recent_group_call_speaker(input_group_call_id, participant.dialog_id);
+ } else {
+ if (group_call == nullptr || version >= group_call->leave_version) {
+ if (participant.is_just_joined) {
+ diff++;
+ }
+ video_diff += participant.video_diff;
+ }
+ on_participant_speaking_in_group_call(input_group_call_id, participant);
+ }
+ }
+
+ if (group_call != nullptr && group_call->is_inited && group_call->is_active && group_call->version == -1) {
+ need_update |= set_group_call_participant_count(group_call, group_call->participant_count + diff,
+ "on_update_group_call_participants");
+ need_update |= set_group_call_unmuted_video_count(group_call, group_call->unmuted_video_count + video_diff,
+ "on_update_group_call_participants");
+ }
+ if (need_update) {
+ send_update_group_call(group_call, "on_update_group_call_participants");
+ }
+
+ LOG(INFO) << "Ignore updateGroupCallParticipants in " << input_group_call_id;
+ return;
+ }
+ if (version <= 0) {
+ LOG(ERROR) << "Ignore updateGroupCallParticipants with invalid version " << version << " in "
+ << input_group_call_id;
+ return;
+ }
+ if (participants.empty()) {
+ LOG(INFO) << "Ignore empty updateGroupCallParticipants with version " << version << " in " << input_group_call_id;
+ return;
+ }
+
+ auto *group_call_participants = add_group_call_participants(input_group_call_id);
+ if (!is_recursive) {
+ vector<DialogId> missing_participants;
+ for (auto &group_call_participant : participants) {
+ GroupCallParticipant participant(group_call_participant, version);
+ if (participant.is_valid() && participant.is_min && participant.joined_date != 0 &&
+ get_group_call_participant(group_call_participants, participant.dialog_id) == nullptr) {
+ missing_participants.push_back(participant.dialog_id);
+ }
+ }
+ if (!missing_participants.empty()) {
+ LOG(INFO) << "Can't apply min updates about " << missing_participants << " in " << input_group_call_id;
+ auto input_peers = transform(missing_participants, &MessagesManager::get_input_peer_force);
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id,
+ participants = std::move(participants), version](Result<Unit> &&result) mutable {
+ send_closure(actor_id, &GroupCallManager::on_update_group_call_participants, input_group_call_id,
+ std::move(participants), version, true);
+ });
+ td_->create_handler<GetGroupCallParticipantQuery>(std::move(query_promise))
+ ->send(input_group_call_id, std::move(input_peers), {});
+ return;
+ }
+ }
+
+ auto &pending_version_updates = group_call_participants->pending_version_updates_[version].updates;
+ auto &pending_mute_updates = group_call_participants->pending_mute_updates_[version].updates;
+ LOG(INFO) << "Have " << pending_version_updates.size() << " versioned and " << pending_mute_updates.size()
+ << " mute pending updates for " << input_group_call_id;
+ for (auto &group_call_participant : participants) {
+ GroupCallParticipant participant(group_call_participant, version);
+ if (!participant.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << to_string(group_call_participant);
+ continue;
+ }
+ if (participant.is_min && participant.joined_date != 0) {
+ auto old_participant = get_group_call_participant(group_call_participants, participant.dialog_id);
+ if (old_participant == nullptr) {
+ LOG(ERROR) << "Can't apply min update about " << participant.dialog_id << " in " << input_group_call_id;
+ on_receive_group_call_version(input_group_call_id, version, true);
+ return;
+ }
+
+ participant.update_from(*old_participant);
+ CHECK(!participant.is_min);
+ }
+ auto dialog_id = participant.dialog_id;
+ if (dialog_id.get_type() != DialogType::User && participant.joined_date != 0) {
+ td_->messages_manager_->force_create_dialog(dialog_id, "on_update_group_call_participants 2", true);
+ }
+
+ if (GroupCallParticipant::is_versioned_update(group_call_participant)) {
+ pending_version_updates[dialog_id] = td::make_unique<GroupCallParticipant>(std::move(participant));
+ } else {
+ pending_mute_updates[dialog_id] = td::make_unique<GroupCallParticipant>(std::move(participant));
+ }
+ }
+
+ process_pending_group_call_participant_updates(input_group_call_id);
+}
+
+bool GroupCallManager::process_pending_group_call_participant_updates(InputGroupCallId input_group_call_id) {
+ if (!need_group_call_participants(input_group_call_id)) {
+ return false;
+ }
+
+ auto participants_it = group_call_participants_.find(input_group_call_id);
+ if (participants_it == group_call_participants_.end()) {
+ return false;
+ }
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ if (group_call->version == -1 || !group_call->is_active) {
+ return false;
+ }
+
+ std::pair<int32, int32> diff{0, 0};
+ bool is_left = false;
+ bool need_rejoin = true;
+ auto &pending_version_updates = participants_it->second->pending_version_updates_;
+ auto &pending_mute_updates = participants_it->second->pending_mute_updates_;
+
+ LOG(INFO) << "Process " << pending_version_updates.size() << " versioned and " << pending_mute_updates.size()
+ << " mute updates for " << input_group_call_id;
+
+ auto process_mute_updates = [&] {
+ while (!pending_mute_updates.empty()) {
+ auto it = pending_mute_updates.begin();
+ auto version = it->first;
+ if (version > group_call->version) {
+ return;
+ }
+ auto &participants = it->second.updates;
+ for (auto &participant_it : participants) {
+ auto &participant = *participant_it.second;
+ on_participant_speaking_in_group_call(input_group_call_id, participant);
+ auto mute_diff = process_group_call_participant(input_group_call_id, std::move(participant));
+ CHECK(mute_diff.first == 0);
+ diff.second += mute_diff.second;
+ }
+ pending_mute_updates.erase(it);
+ }
+ };
+
+ bool need_update = false;
+ while (!pending_version_updates.empty()) {
+ process_mute_updates();
+
+ auto it = pending_version_updates.begin();
+ auto version = it->first;
+ auto &participants = it->second.updates;
+ if (version <= group_call->version) {
+ for (auto &participant_it : participants) {
+ auto &participant = *participant_it.second;
+ on_participant_speaking_in_group_call(input_group_call_id, participant);
+ if (participant.is_self || participant.joined_date != 0) {
+ auto new_diff = process_group_call_participant(input_group_call_id, std::move(participant));
+ diff.first += new_diff.first;
+ diff.second += new_diff.second;
+ }
+ }
+ LOG(INFO) << "Ignore already applied updateGroupCallParticipants with version " << version << " in "
+ << input_group_call_id << " from " << group_call->dialog_id;
+ pending_version_updates.erase(it);
+ continue;
+ }
+
+ if (version == group_call->version + 1) {
+ group_call->version = version;
+ for (auto &participant_it : participants) {
+ auto &participant = *participant_it.second;
+ if (participant.is_self && group_call->is_joined &&
+ (participant.joined_date == 0) == (participant.audio_source == group_call->audio_source)) {
+ is_left = true;
+ if (participant.joined_date != 0) {
+ need_rejoin = false;
+ } else {
+ continue;
+ }
+ }
+ auto new_diff = process_group_call_participant(input_group_call_id, std::move(participant));
+ diff.first += new_diff.first;
+ diff.second += new_diff.second;
+ }
+ pending_version_updates.erase(it);
+ } else {
+ // found a gap
+ if (!group_call->syncing_participants) {
+ LOG(INFO) << "Receive " << participants.size() << " group call participant updates with version " << version
+ << ", but current version is " << group_call->version;
+ sync_participants_timeout_.add_timeout_in(group_call->group_call_id.get(), 1.0);
+ }
+ break;
+ }
+ }
+
+ process_mute_updates();
+
+ if (!pending_mute_updates.empty()) {
+ on_receive_group_call_version(input_group_call_id, pending_mute_updates.begin()->first);
+ }
+
+ if (pending_version_updates.empty() && pending_mute_updates.empty()) {
+ sync_participants_timeout_.cancel_timeout(group_call->group_call_id.get());
+ }
+
+ need_update |= set_group_call_participant_count(group_call, group_call->participant_count + diff.first,
+ "process_pending_group_call_participant_updates");
+ need_update |= set_group_call_unmuted_video_count(group_call, group_call->unmuted_video_count + diff.second,
+ "process_pending_group_call_participant_updates");
+ if (is_left && group_call->is_joined) {
+ on_group_call_left_impl(group_call, need_rejoin, "process_pending_group_call_participant_updates");
+ need_update = true;
+ }
+ need_update |= try_clear_group_call_participants(input_group_call_id);
+ if (need_update) {
+ send_update_group_call(group_call, "process_pending_group_call_participant_updates");
+ }
+
+ return need_update;
+}
+
+void GroupCallManager::sync_group_call_participants(InputGroupCallId input_group_call_id) {
+ if (!need_group_call_participants(input_group_call_id)) {
+ return;
+ }
+
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+
+ sync_participants_timeout_.cancel_timeout(group_call->group_call_id.get());
+
+ if (group_call->syncing_participants) {
+ group_call->need_syncing_participants = true;
+ return;
+ }
+ group_call->syncing_participants = true;
+ group_call->need_syncing_participants = false;
+
+ LOG(INFO) << "Force participants synchronization in " << input_group_call_id << " from " << group_call->dialog_id;
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id](
+ Result<tl_object_ptr<telegram_api::phone_groupCall>> &&result) {
+ send_closure(actor_id, &GroupCallManager::on_sync_group_call_participants, input_group_call_id, std::move(result));
+ });
+
+ td_->create_handler<GetGroupCallQuery>(std::move(promise))->send(input_group_call_id, 100);
+}
+
+void GroupCallManager::on_sync_group_call_participants(InputGroupCallId input_group_call_id,
+ Result<tl_object_ptr<telegram_api::phone_groupCall>> &&result) {
+ if (G()->close_flag() || !need_group_call_participants(input_group_call_id)) {
+ return;
+ }
+
+ if (result.is_error()) {
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ CHECK(group_call->syncing_participants);
+ group_call->syncing_participants = false;
+
+ sync_participants_timeout_.add_timeout_in(group_call->group_call_id.get(),
+ group_call->need_syncing_participants ? 0.0 : 1.0);
+ return;
+ }
+
+ auto call = result.move_as_ok();
+ if (call->call_->get_id() == telegram_api::groupCall::ID) {
+ auto *group_call = static_cast<const telegram_api::groupCall *>(call->call_.get());
+ auto participants = make_tl_object<telegram_api::phone_groupParticipants>(
+ group_call->participants_count_, std::move(call->participants_), std::move(call->participants_next_offset_),
+ std::move(call->chats_), std::move(call->users_), group_call->version_);
+ on_get_group_call_participants(input_group_call_id, std::move(participants), true, string());
+ }
+
+ if (update_group_call(call->call_, DialogId()) != input_group_call_id) {
+ LOG(ERROR) << "Expected " << input_group_call_id << ", but received " << to_string(result.ok());
+ }
+}
+
+GroupCallParticipantOrder GroupCallManager::get_real_participant_order(bool can_self_unmute,
+ const GroupCallParticipant &participant,
+ const GroupCallParticipants *participants) {
+ auto real_order = participant.get_real_order(can_self_unmute, participants->joined_date_asc);
+ if (real_order >= participants->min_order) {
+ return real_order;
+ }
+ if (participant.is_self) {
+ return participants->min_order;
+ }
+ if (real_order.is_valid()) {
+ LOG(DEBUG) << "Order " << real_order << " of " << participant.dialog_id << " is less than last known order "
+ << participants->min_order;
+ }
+ return GroupCallParticipantOrder();
+}
+
+void GroupCallManager::process_group_call_participants(
+ InputGroupCallId input_group_call_id, vector<tl_object_ptr<telegram_api::groupCallParticipant>> &&participants,
+ int32 version, const string &offset, bool is_load, bool is_sync) {
+ // if receive exactly one participant, then the current user is the only participant
+ // there are no reasons to process it independently
+ if (offset.empty() && is_load && participants.size() >= 2 && participants[0]->self_) {
+ GroupCallParticipant participant(participants[0], version);
+ if (participant.is_valid()) {
+ process_my_group_call_participant(input_group_call_id, std::move(participant));
+ }
+ participants.erase(participants.begin());
+ }
+ if (!need_group_call_participants(input_group_call_id)) {
+ for (auto &group_call_participant : participants) {
+ GroupCallParticipant participant(group_call_participant, version);
+ if (!participant.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << to_string(group_call_participant);
+ continue;
+ }
+ if (participant.dialog_id.get_type() != DialogType::User) {
+ td_->messages_manager_->force_create_dialog(participant.dialog_id, "process_group_call_participants", true);
+ }
+
+ on_participant_speaking_in_group_call(input_group_call_id, participant);
+ }
+ return;
+ }
+
+ FlatHashSet<DialogId, DialogIdHash> old_participant_dialog_ids;
+ if (is_sync) {
+ auto *group_call_participants = add_group_call_participants(input_group_call_id);
+ for (auto &participant : group_call_participants->participants) {
+ CHECK(participant.dialog_id.is_valid());
+ old_participant_dialog_ids.insert(participant.dialog_id);
+ }
+ }
+
+ auto min_order = GroupCallParticipantOrder::max();
+ DialogId debug_min_order_dialog_id;
+ bool can_self_unmute = get_group_call_can_self_unmute(input_group_call_id);
+ bool joined_date_asc = get_group_call_joined_date_asc(input_group_call_id);
+ for (auto &group_call_participant : participants) {
+ GroupCallParticipant participant(group_call_participant, version);
+ if (!participant.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << to_string(group_call_participant);
+ continue;
+ }
+ if (participant.is_min) {
+ LOG(ERROR) << "Receive unexpected min " << to_string(group_call_participant);
+ continue;
+ }
+ if (participant.dialog_id.get_type() != DialogType::User) {
+ td_->messages_manager_->force_create_dialog(participant.dialog_id, "process_group_call_participants", true);
+ }
+
+ if (is_load) {
+ auto real_order = participant.get_server_order(can_self_unmute, joined_date_asc);
+ if (real_order > min_order) {
+ LOG(ERROR) << "Receive group call participant " << participant.dialog_id << " with order " << real_order
+ << " after group call participant " << debug_min_order_dialog_id << " with order " << min_order;
+ } else {
+ min_order = real_order;
+ debug_min_order_dialog_id = participant.dialog_id;
+ }
+ }
+ if (is_sync) {
+ old_participant_dialog_ids.erase(participant.dialog_id);
+ }
+ process_group_call_participant(input_group_call_id, std::move(participant));
+ }
+ if (is_load && participants.empty() && !joined_date_asc) {
+ // If loaded 0 participants and new participants are added to the beginning of the list,
+ // then the end of the list was reached.
+ // Set min_order to the minimum possible value to send updates about all participants with order less than
+ // the current min_order. There can be such participants if the last loaded participant had a fake active_date.
+ min_order = GroupCallParticipantOrder::min();
+ }
+ if (is_sync) {
+ auto *group_call_participants = add_group_call_participants(input_group_call_id);
+ auto &group_participants = group_call_participants->participants;
+ for (auto participant_it = group_participants.begin(); participant_it != group_participants.end();) {
+ auto &participant = *participant_it;
+ if (old_participant_dialog_ids.count(participant.dialog_id) == 0) {
+ // successfully synced old user
+ ++participant_it;
+ continue;
+ }
+
+ if (participant.is_self) {
+ if (participant.order != min_order) {
+ participant.order = min_order;
+ send_update_group_call_participant(input_group_call_id, participant, "process_group_call_participants self");
+ }
+ ++participant_it;
+ continue;
+ }
+
+ // not synced user and not self, needs to be deleted
+ if (participant.order.is_valid()) {
+ CHECK(participant.order >= group_call_participants->min_order);
+ participant.order = GroupCallParticipantOrder();
+ send_update_group_call_participant(input_group_call_id, participant, "process_group_call_participants sync");
+ }
+ on_remove_group_call_participant(input_group_call_id, participant.dialog_id);
+ group_call_participants->local_unmuted_video_count -= participant.get_has_video();
+ participant_it = group_participants.erase(participant_it);
+ }
+ if (group_call_participants->min_order < min_order) {
+ // if previously known more users, adjust min_order
+ LOG(INFO) << "Decrease min_order from " << group_call_participants->min_order << " to " << min_order << " in "
+ << input_group_call_id;
+ group_call_participants->min_order = min_order;
+ update_group_call_participants_order(input_group_call_id, can_self_unmute, group_call_participants,
+ "decrease min_order");
+ }
+ }
+ if (is_load) {
+ auto *group_call_participants = add_group_call_participants(input_group_call_id);
+ if (group_call_participants->min_order > min_order) {
+ LOG(INFO) << "Increase min_order from " << group_call_participants->min_order << " to " << min_order << " in "
+ << input_group_call_id;
+ group_call_participants->min_order = min_order;
+ update_group_call_participants_order(input_group_call_id, can_self_unmute, group_call_participants,
+ "increase min_order");
+ }
+ }
+}
+
+bool GroupCallManager::update_group_call_participant_can_be_muted(bool can_manage,
+ const GroupCallParticipants *participants,
+ GroupCallParticipant &participant) {
+ bool is_admin = td::contains(participants->administrator_dialog_ids, participant.dialog_id);
+ return participant.update_can_be_muted(can_manage, is_admin);
+}
+
+void GroupCallManager::update_group_call_participants_can_be_muted(InputGroupCallId input_group_call_id,
+ bool can_manage,
+ GroupCallParticipants *participants) {
+ CHECK(participants != nullptr);
+ LOG(INFO) << "Update group call participants can_be_muted in " << input_group_call_id;
+ for (auto &participant : participants->participants) {
+ if (update_group_call_participant_can_be_muted(can_manage, participants, participant) &&
+ participant.order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, participant,
+ "update_group_call_participants_can_be_muted");
+ }
+ }
+}
+
+void GroupCallManager::update_group_call_participants_order(InputGroupCallId input_group_call_id, bool can_self_unmute,
+ GroupCallParticipants *participants, const char *source) {
+ for (auto &participant : participants->participants) {
+ auto new_order = get_real_participant_order(can_self_unmute, participant, participants);
+ if (new_order != participant.order) {
+ participant.order = new_order;
+ send_update_group_call_participant(input_group_call_id, participant, "process_group_call_participants load");
+ }
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ update_group_call_participant_order_timeout_.set_timeout_in(group_call->group_call_id.get(),
+ UPDATE_GROUP_CALL_PARTICIPANT_ORDER_TIMEOUT);
+}
+
+void GroupCallManager::process_my_group_call_participant(InputGroupCallId input_group_call_id,
+ GroupCallParticipant &&participant) {
+ CHECK(participant.is_valid());
+ CHECK(participant.is_self);
+ if (!need_group_call_participants(input_group_call_id)) {
+ return;
+ }
+ auto my_participant = get_group_call_participant(add_group_call_participants(input_group_call_id),
+ DialogId(td_->contacts_manager_->get_my_id()));
+ if (my_participant == nullptr || my_participant->is_fake || my_participant->joined_date < participant.joined_date ||
+ (my_participant->joined_date <= participant.joined_date &&
+ my_participant->audio_source != participant.audio_source)) {
+ process_group_call_participant(input_group_call_id, std::move(participant));
+ }
+}
+
+std::pair<int32, int32> GroupCallManager::process_group_call_participant(InputGroupCallId input_group_call_id,
+ GroupCallParticipant &&participant) {
+ if (!participant.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << participant;
+ return {0, 0};
+ }
+ if (!need_group_call_participants(input_group_call_id)) {
+ return {0, 0};
+ }
+
+ LOG(INFO) << "Process " << participant << " in " << input_group_call_id;
+
+ if (participant.is_self) {
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ auto can_self_unmute = group_call->is_active && !participant.get_is_muted_by_admin();
+ if (can_self_unmute != group_call->can_self_unmute) {
+ group_call->can_self_unmute = can_self_unmute;
+ send_update_group_call(group_call, "process_group_call_participant 1");
+ sync_group_call_participants(input_group_call_id); // participant order is different for administrators
+ }
+ }
+
+ bool can_self_unmute = get_group_call_can_self_unmute(input_group_call_id);
+ bool can_manage = can_manage_group_call(input_group_call_id);
+ auto *participants = add_group_call_participants(input_group_call_id);
+ for (size_t i = 0; i < participants->participants.size(); i++) {
+ auto &old_participant = participants->participants[i];
+ if (old_participant.dialog_id == participant.dialog_id || (old_participant.is_self && participant.is_self)) {
+ if (participant.joined_date == 0) {
+ LOG(INFO) << "Remove " << old_participant;
+ if (old_participant.order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, participant, "process_group_call_participant remove");
+ }
+ on_remove_group_call_participant(input_group_call_id, old_participant.dialog_id);
+ remove_recent_group_call_speaker(input_group_call_id, old_participant.dialog_id);
+ int32 unmuted_video_diff = -old_participant.get_has_video();
+ participants->local_unmuted_video_count += unmuted_video_diff;
+ participants->participants.erase(participants->participants.begin() + i);
+ return {-1, unmuted_video_diff};
+ }
+
+ if (old_participant.version > participant.version) {
+ LOG(INFO) << "Ignore outdated update of " << old_participant.dialog_id;
+ return {0, 0};
+ }
+
+ if (old_participant.dialog_id != participant.dialog_id) {
+ on_remove_group_call_participant(input_group_call_id, old_participant.dialog_id);
+ on_add_group_call_participant(input_group_call_id, participant.dialog_id);
+ }
+
+ participant.update_from(old_participant);
+
+ participant.is_just_joined = false;
+ participant.order = get_real_participant_order(can_self_unmute, participant, participants);
+ update_group_call_participant_can_be_muted(can_manage, participants, participant);
+
+ LOG(INFO) << "Edit " << old_participant << " to " << participant;
+ if (old_participant != participant && (old_participant.order.is_valid() || participant.order.is_valid())) {
+ send_update_group_call_participant(input_group_call_id, participant, "process_group_call_participant edit");
+ }
+ on_participant_speaking_in_group_call(input_group_call_id, participant);
+ int32 unmuted_video_diff = participant.get_has_video() - old_participant.get_has_video();
+ participants->local_unmuted_video_count += unmuted_video_diff;
+ old_participant = std::move(participant);
+ return {0, unmuted_video_diff};
+ }
+ }
+
+ if (participant.joined_date == 0) {
+ LOG(INFO) << "Remove unknown " << participant;
+ remove_recent_group_call_speaker(input_group_call_id, participant.dialog_id);
+ return {-1, participant.video_diff};
+ }
+
+ CHECK(!participant.is_min);
+ int diff = participant.is_just_joined ? 1 : 0;
+ participant.order = get_real_participant_order(can_self_unmute, participant, participants);
+ if (participant.is_just_joined) {
+ LOG(INFO) << "Add new " << participant;
+ } else {
+ LOG(INFO) << "Receive new " << participant;
+ }
+ participant.is_just_joined = false;
+ participants->local_unmuted_video_count += participant.get_has_video();
+ update_group_call_participant_can_be_muted(can_manage, participants, participant);
+ participants->participants.push_back(std::move(participant));
+ if (participants->participants.back().order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, participants->participants.back(),
+ "process_group_call_participant add");
+ } else {
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ if (group_call->loaded_all_participants) {
+ group_call->loaded_all_participants = false;
+ send_update_group_call(group_call, "process_group_call_participant 2");
+ }
+ }
+ on_add_group_call_participant(input_group_call_id, participants->participants.back().dialog_id);
+ on_participant_speaking_in_group_call(input_group_call_id, participants->participants.back());
+ return {diff, participants->participants.back().video_diff};
+}
+
+void GroupCallManager::on_add_group_call_participant(InputGroupCallId input_group_call_id,
+ DialogId participant_dialog_id) {
+ auto &participants = participant_id_to_group_call_id_[participant_dialog_id];
+ CHECK(!td::contains(participants, input_group_call_id));
+ participants.push_back(input_group_call_id);
+}
+
+void GroupCallManager::on_remove_group_call_participant(InputGroupCallId input_group_call_id,
+ DialogId participant_dialog_id) {
+ auto it = participant_id_to_group_call_id_.find(participant_dialog_id);
+ CHECK(it != participant_id_to_group_call_id_.end());
+ bool is_removed = td::remove(it->second, input_group_call_id);
+ CHECK(is_removed);
+ if (it->second.empty()) {
+ participant_id_to_group_call_id_.erase(it);
+ }
+}
+
+void GroupCallManager::on_update_dialog_about(DialogId dialog_id, const string &about, bool from_server) {
+ auto it = participant_id_to_group_call_id_.find(dialog_id);
+ if (it == participant_id_to_group_call_id_.end()) {
+ return;
+ }
+ CHECK(!it->second.empty());
+
+ for (const auto &input_group_call_id : it->second) {
+ auto participant = get_group_call_participant(input_group_call_id, dialog_id);
+ CHECK(participant != nullptr);
+ if ((from_server || participant->is_fake) && participant->about != about) {
+ participant->about = about;
+ if (participant->order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, *participant, "on_update_dialog_about");
+ }
+ }
+ }
+}
+
+int32 GroupCallManager::cancel_join_group_call_request(InputGroupCallId input_group_call_id) {
+ auto it = pending_join_requests_.find(input_group_call_id);
+ if (it == pending_join_requests_.end()) {
+ return 0;
+ }
+
+ CHECK(it->second != nullptr);
+ if (!it->second->query_ref.empty()) {
+ cancel_query(it->second->query_ref);
+ }
+ it->second->promise.set_error(Status::Error(200, "Canceled"));
+ auto audio_source = it->second->audio_source;
+ pending_join_requests_.erase(it);
+ return audio_source;
+}
+
+int32 GroupCallManager::cancel_join_group_call_presentation_request(InputGroupCallId input_group_call_id) {
+ auto it = pending_join_presentation_requests_.find(input_group_call_id);
+ if (it == pending_join_presentation_requests_.end()) {
+ return 0;
+ }
+
+ CHECK(it->second != nullptr);
+ if (!it->second->query_ref.empty()) {
+ cancel_query(it->second->query_ref);
+ }
+ it->second->promise.set_error(Status::Error(200, "Canceled"));
+ auto audio_source = it->second->audio_source;
+ pending_join_presentation_requests_.erase(it);
+ return audio_source;
+}
+
+void GroupCallManager::get_group_call_streams(GroupCallId group_call_id,
+ Promise<td_api::object_ptr<td_api::groupCallStreams>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ reload_group_call(input_group_call_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, promise = std::move(promise)](
+ Result<td_api::object_ptr<td_api::groupCall>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::get_group_call_streams, group_call_id,
+ std::move(promise));
+ }
+ }));
+ return;
+ }
+ if (!group_call->is_active || !group_call->stream_dc_id.is_exact()) {
+ return promise.set_error(Status::Error(400, "Group call can't be streamed"));
+ }
+ if (!group_call->is_joined) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(PromiseCreator::lambda(
+ [actor_id = actor_id(this), group_call_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::get_group_call_streams, group_call_id, std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), input_group_call_id, audio_source = group_call->audio_source,
+ promise = std::move(promise)](Result<td_api::object_ptr<td_api::groupCallStreams>> &&result) mutable {
+ send_closure(actor_id, &GroupCallManager::finish_get_group_call_streams, input_group_call_id, audio_source,
+ std::move(result), std::move(promise));
+ });
+ td_->create_handler<GetGroupCallStreamChannelsQuery>(std::move(query_promise))
+ ->send(input_group_call_id, group_call->stream_dc_id);
+}
+
+void GroupCallManager::finish_get_group_call_streams(InputGroupCallId input_group_call_id, int32 audio_source,
+ Result<td_api::object_ptr<td_api::groupCallStreams>> &&result,
+ Promise<td_api::object_ptr<td_api::groupCallStreams>> &&promise) {
+ if (!G()->close_flag() && result.is_error()) {
+ auto message = result.error().message();
+ if (message == "GROUPCALL_JOIN_MISSING" || message == "GROUPCALL_FORBIDDEN" || message == "GROUPCALL_INVALID") {
+ on_group_call_left(input_group_call_id, audio_source, message == "GROUPCALL_JOIN_MISSING");
+ }
+ }
+
+ promise.set_result(std::move(result));
+}
+
+void GroupCallManager::get_group_call_stream_segment(GroupCallId group_call_id, int64 time_offset, int32 scale,
+ int32 channel_id,
+ td_api::object_ptr<td_api::GroupCallVideoQuality> quality,
+ Promise<string> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ reload_group_call(input_group_call_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, time_offset, scale, channel_id,
+ quality = std::move(quality), promise = std::move(promise)](
+ Result<td_api::object_ptr<td_api::groupCall>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::get_group_call_stream_segment, group_call_id,
+ time_offset, scale, channel_id, std::move(quality), std::move(promise));
+ }
+ }));
+ return;
+ }
+ if (!group_call->is_active || !group_call->stream_dc_id.is_exact()) {
+ return promise.set_error(Status::Error(400, "Group call can't be streamed"));
+ }
+ if (!group_call->is_joined) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(PromiseCreator::lambda(
+ [actor_id = actor_id(this), group_call_id, time_offset, scale, channel_id, quality = std::move(quality),
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::get_group_call_stream_segment, group_call_id, time_offset,
+ scale, channel_id, std::move(quality), std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ int32 video_quality = 0;
+ if (quality != nullptr) {
+ switch (quality->get_id()) {
+ case td_api::groupCallVideoQualityThumbnail::ID:
+ video_quality = 0;
+ break;
+ case td_api::groupCallVideoQualityMedium::ID:
+ video_quality = 1;
+ break;
+ case td_api::groupCallVideoQualityFull::ID:
+ video_quality = 2;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, audio_source = group_call->audio_source,
+ promise = std::move(promise)](Result<string> &&result) mutable {
+ send_closure(actor_id, &GroupCallManager::finish_get_group_call_stream_segment, input_group_call_id,
+ audio_source, std::move(result), std::move(promise));
+ });
+ td_->create_handler<GetGroupCallStreamQuery>(std::move(query_promise))
+ ->send(input_group_call_id, group_call->stream_dc_id, time_offset, scale, channel_id, video_quality);
+}
+
+void GroupCallManager::finish_get_group_call_stream_segment(InputGroupCallId input_group_call_id, int32 audio_source,
+ Result<string> &&result, Promise<string> &&promise) {
+ if (!G()->close_flag()) {
+ if (result.is_ok()) {
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr);
+ if (group_call->is_inited && check_group_call_is_joined_timeout_.has_timeout(group_call->group_call_id.get())) {
+ check_group_call_is_joined_timeout_.set_timeout_in(group_call->group_call_id.get(),
+ CHECK_GROUP_CALL_IS_JOINED_TIMEOUT);
+ }
+ } else {
+ auto message = result.error().message();
+ if (message == "GROUPCALL_JOIN_MISSING" || message == "GROUPCALL_FORBIDDEN" || message == "GROUPCALL_INVALID") {
+ on_group_call_left(input_group_call_id, audio_source, message == "GROUPCALL_JOIN_MISSING");
+ }
+ }
+ }
+
+ promise.set_result(std::move(result));
+}
+
+void GroupCallManager::start_scheduled_group_call(GroupCallId group_call_id, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ reload_group_call(input_group_call_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, promise = std::move(promise)](
+ Result<td_api::object_ptr<td_api::groupCall>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::start_scheduled_group_call, group_call_id,
+ std::move(promise));
+ }
+ }));
+ return;
+ }
+ if (!group_call->can_be_managed) {
+ return promise.set_error(Status::Error(400, "Not enough rights to start the group call"));
+ }
+ if (!group_call->is_active) {
+ return promise.set_error(Status::Error(400, "Group call already ended"));
+ }
+ if (group_call->scheduled_start_date == 0) {
+ return promise.set_value(Unit());
+ }
+
+ td_->create_handler<StartScheduledGroupCallQuery>(std::move(promise))->send(input_group_call_id);
+}
+
+void GroupCallManager::join_group_call(GroupCallId group_call_id, DialogId as_dialog_id, int32 audio_source,
+ string &&payload, bool is_muted, bool is_my_video_enabled,
+ const string &invite_hash, Promise<string> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr);
+ if (group_call->is_inited && !group_call->is_active) {
+ return promise.set_error(Status::Error(400, "Group call is finished"));
+ }
+ bool need_update = false;
+ bool is_rejoin = group_call->need_rejoin;
+ if (group_call->need_rejoin) {
+ group_call->need_rejoin = false;
+ need_update = true;
+ }
+
+ cancel_join_group_call_request(input_group_call_id);
+
+ bool have_as_dialog_id = true;
+ {
+ auto my_dialog_id = DialogId(td_->contacts_manager_->get_my_id());
+ if (!as_dialog_id.is_valid()) {
+ as_dialog_id = my_dialog_id;
+ }
+ auto dialog_type = as_dialog_id.get_type();
+ if (dialog_type == DialogType::User) {
+ if (as_dialog_id != my_dialog_id) {
+ return promise.set_error(Status::Error(400, "Can't join voice chat as another user"));
+ }
+ if (!td_->contacts_manager_->have_user_force(as_dialog_id.get_user_id())) {
+ have_as_dialog_id = false;
+ }
+ } else {
+ if (!td_->messages_manager_->have_dialog_force(as_dialog_id, "join_group_call")) {
+ return promise.set_error(Status::Error(400, "Join as chat not found"));
+ }
+ }
+ if (!td_->messages_manager_->have_input_peer(as_dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the join as participant"));
+ }
+ if (dialog_type == DialogType::SecretChat) {
+ return promise.set_error(Status::Error(400, "Can't join voice chat as a secret chat"));
+ }
+ }
+
+ if (group_call->is_being_left) {
+ group_call->is_being_left = false;
+ need_update |= group_call->is_joined;
+ }
+
+ auto generation = ++join_group_request_generation_;
+ auto &request = pending_join_requests_[input_group_call_id];
+ request = make_unique<PendingJoinRequest>();
+ request->generation = generation;
+ request->audio_source = audio_source;
+ request->as_dialog_id = as_dialog_id;
+ request->promise = std::move(promise);
+
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), generation, input_group_call_id](Result<Unit> &&result) {
+ CHECK(result.is_error());
+ send_closure(actor_id, &GroupCallManager::finish_join_group_call, input_group_call_id, generation,
+ result.move_as_error());
+ });
+ request->query_ref =
+ td_->create_handler<JoinGroupCallQuery>(std::move(query_promise))
+ ->send(input_group_call_id, as_dialog_id, payload, is_muted, !is_my_video_enabled, invite_hash, generation);
+
+ if (group_call->dialog_id.is_valid()) {
+ td_->messages_manager_->on_update_dialog_default_join_group_call_as_dialog_id(group_call->dialog_id, as_dialog_id,
+ true);
+ } else {
+ if (as_dialog_id.get_type() != DialogType::User) {
+ td_->messages_manager_->force_create_dialog(as_dialog_id, "join_group_call");
+ }
+ }
+ if (group_call->is_inited && have_as_dialog_id) {
+ GroupCallParticipant participant;
+ participant.is_self = true;
+ participant.dialog_id = as_dialog_id;
+ participant.about = td_->contacts_manager_->get_dialog_about(participant.dialog_id);
+ participant.audio_source = audio_source;
+ participant.joined_date = G()->unix_time();
+ // if can_self_unmute has never been inited from self-participant,
+ // it contains reasonable default "!call.mute_new_participants || call.can_be_managed"
+ participant.server_is_muted_by_admin = !group_call->can_self_unmute && !can_manage_group_call(input_group_call_id);
+ participant.server_is_muted_by_themselves = is_muted && !participant.server_is_muted_by_admin;
+ participant.is_just_joined = !is_rejoin;
+ participant.video_diff = get_group_call_can_enable_video(group_call) && is_my_video_enabled;
+ participant.is_fake = true;
+ auto diff = process_group_call_participant(input_group_call_id, std::move(participant));
+ if (diff.first != 0) {
+ CHECK(diff.first == 1);
+ need_update |= set_group_call_participant_count(group_call, group_call->participant_count + diff.first,
+ "join_group_call", true);
+ }
+ if (diff.second != 0) {
+ CHECK(diff.second == 1);
+ need_update |= set_group_call_unmuted_video_count(group_call, group_call->unmuted_video_count + diff.second,
+ "join_group_call");
+ }
+ }
+ if (group_call->is_my_video_enabled != is_my_video_enabled) {
+ group_call->is_my_video_enabled = is_my_video_enabled;
+ if (!is_my_video_enabled) {
+ group_call->is_my_video_paused = false;
+ }
+ need_update = true;
+ }
+
+ if (group_call->is_inited && need_update) {
+ send_update_group_call(group_call, "join_group_call");
+ }
+
+ try_load_group_call_administrators(input_group_call_id, group_call->dialog_id);
+}
+
+void GroupCallManager::start_group_call_screen_sharing(GroupCallId group_call_id, int32 audio_source, string &&payload,
+ Promise<string> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr);
+ if (!group_call->is_inited || !group_call->is_active) {
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ if (!group_call->is_joined || group_call->is_being_left) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, audio_source, payload = std::move(payload),
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ } else {
+ send_closure(actor_id, &GroupCallManager::start_group_call_screen_sharing, group_call_id, audio_source,
+ std::move(payload), std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ cancel_join_group_call_presentation_request(input_group_call_id);
+
+ auto generation = ++join_group_request_generation_;
+ auto &request = pending_join_presentation_requests_[input_group_call_id];
+ request = make_unique<PendingJoinRequest>();
+ request->generation = generation;
+ request->audio_source = audio_source;
+ request->promise = std::move(promise);
+
+ request->query_ref =
+ td_->create_handler<JoinGroupCallPresentationQuery>()->send(input_group_call_id, payload, generation);
+
+ bool need_update = false;
+ if (group_call->is_inited && need_update) {
+ send_update_group_call(group_call, "start_group_call_screen_sharing");
+ }
+}
+
+void GroupCallManager::end_group_call_screen_sharing(GroupCallId group_call_id, Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr);
+ if (!group_call->is_inited || !group_call->is_active) {
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ if (!group_call->is_joined || group_call->is_being_left) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(PromiseCreator::lambda(
+ [actor_id = actor_id(this), group_call_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ } else {
+ send_closure(actor_id, &GroupCallManager::end_group_call_screen_sharing, group_call_id,
+ std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ cancel_join_group_call_presentation_request(input_group_call_id);
+
+ group_call->have_pending_is_my_presentation_paused = false;
+ group_call->pending_is_my_presentation_paused = false;
+
+ td_->create_handler<LeaveGroupCallPresentationQuery>(std::move(promise))->send(input_group_call_id);
+}
+
+void GroupCallManager::try_load_group_call_administrators(InputGroupCallId input_group_call_id, DialogId dialog_id) {
+ if (!dialog_id.is_valid() || !need_group_call_participants(input_group_call_id) ||
+ can_manage_group_calls(dialog_id).is_error()) {
+ LOG(INFO) << "Don't need to load administrators in " << input_group_call_id << " from " << dialog_id;
+ return;
+ }
+
+ auto promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id](Result<DialogParticipants> &&result) {
+ send_closure(actor_id, &GroupCallManager::finish_load_group_call_administrators, input_group_call_id,
+ std::move(result));
+ });
+ td_->contacts_manager_->search_dialog_participants(
+ dialog_id, string(), 100, DialogParticipantFilter(td_api::make_object<td_api::chatMembersFilterAdministrators>()),
+ std::move(promise));
+}
+
+void GroupCallManager::finish_load_group_call_administrators(InputGroupCallId input_group_call_id,
+ Result<DialogParticipants> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (result.is_error()) {
+ LOG(WARNING) << "Failed to get administrators of " << input_group_call_id << ": " << result.error();
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (!need_group_call_participants(input_group_call_id, group_call)) {
+ return;
+ }
+ CHECK(group_call != nullptr);
+ if (!group_call->dialog_id.is_valid() || !can_manage_group_calls(group_call->dialog_id).is_ok()) {
+ return;
+ }
+
+ vector<DialogId> administrator_dialog_ids;
+ auto participants = result.move_as_ok();
+ for (auto &administrator : participants.participants_) {
+ if (administrator.status_.can_manage_calls() &&
+ administrator.dialog_id_ != DialogId(td_->contacts_manager_->get_my_id())) {
+ administrator_dialog_ids.push_back(administrator.dialog_id_);
+ }
+ }
+
+ auto *group_call_participants = add_group_call_participants(input_group_call_id);
+ if (group_call_participants->are_administrators_loaded &&
+ group_call_participants->administrator_dialog_ids == administrator_dialog_ids) {
+ return;
+ }
+
+ LOG(INFO) << "Set administrators of " << input_group_call_id << " to " << administrator_dialog_ids;
+ group_call_participants->are_administrators_loaded = true;
+ group_call_participants->administrator_dialog_ids = std::move(administrator_dialog_ids);
+
+ update_group_call_participants_can_be_muted(input_group_call_id, true, group_call_participants);
+}
+
+void GroupCallManager::process_join_group_call_response(InputGroupCallId input_group_call_id, uint64 generation,
+ tl_object_ptr<telegram_api::Updates> &&updates,
+ Promise<Unit> &&promise) {
+ auto it = pending_join_requests_.find(input_group_call_id);
+ if (it == pending_join_requests_.end() || it->second->generation != generation) {
+ LOG(INFO) << "Ignore JoinGroupCallQuery response with " << input_group_call_id << " and generation " << generation;
+ return;
+ }
+
+ td_->updates_manager_->on_get_updates(std::move(updates),
+ PromiseCreator::lambda([promise = std::move(promise)](Unit) mutable {
+ promise.set_error(Status::Error(500, "Wrong join response received"));
+ }));
+}
+
+void GroupCallManager::process_join_group_call_presentation_response(InputGroupCallId input_group_call_id,
+ uint64 generation,
+ tl_object_ptr<telegram_api::Updates> &&updates,
+ Status status) {
+ auto it = pending_join_presentation_requests_.find(input_group_call_id);
+ if (it == pending_join_presentation_requests_.end() || it->second->generation != generation) {
+ LOG(INFO) << "Ignore JoinGroupCallPresentationQuery response with " << input_group_call_id << " and generation "
+ << generation;
+ return;
+ }
+ auto promise = std::move(it->second->promise);
+ pending_join_presentation_requests_.erase(it);
+
+ if (status.is_error()) {
+ return promise.set_error(std::move(status));
+ }
+ CHECK(updates != nullptr);
+
+ string params = UpdatesManager::extract_join_group_call_presentation_params(updates.get());
+ if (params.empty()) {
+ return promise.set_error(
+ Status::Error(500, "Wrong start group call screen sharing response received: parameters are missing"));
+ }
+ td_->updates_manager_->on_get_updates(
+ std::move(updates), PromiseCreator::lambda([params = std::move(params), promise = std::move(promise)](
+ Unit) mutable { promise.set_value(std::move(params)); }));
+}
+
+bool GroupCallManager::on_join_group_call_response(InputGroupCallId input_group_call_id, string json_response) {
+ auto it = pending_join_requests_.find(input_group_call_id);
+ if (it == pending_join_requests_.end()) {
+ return false;
+ }
+ CHECK(it->second != nullptr);
+
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr);
+ group_call->is_joined = true;
+ group_call->need_rejoin = false;
+ group_call->is_being_left = false;
+ group_call->joined_date = G()->unix_time();
+ group_call->audio_source = it->second->audio_source;
+ group_call->as_dialog_id = it->second->as_dialog_id;
+ it->second->promise.set_value(std::move(json_response));
+ if (group_call->audio_source != 0) {
+ check_group_call_is_joined_timeout_.set_timeout_in(group_call->group_call_id.get(),
+ CHECK_GROUP_CALL_IS_JOINED_TIMEOUT);
+ }
+ pending_join_requests_.erase(it);
+ try_clear_group_call_participants(input_group_call_id);
+ process_group_call_after_join_requests(input_group_call_id, "on_join_group_call_response");
+ return true;
+}
+
+void GroupCallManager::finish_join_group_call(InputGroupCallId input_group_call_id, uint64 generation, Status error) {
+ CHECK(error.is_error());
+ auto it = pending_join_requests_.find(input_group_call_id);
+ if (it == pending_join_requests_.end() || (generation != 0 && it->second->generation != generation)) {
+ return;
+ }
+ it->second->promise.set_error(std::move(error));
+ auto as_dialog_id = it->second->as_dialog_id;
+ pending_join_requests_.erase(it);
+
+ if (G()->close_flag()) {
+ return;
+ }
+
+ const GroupCall *group_call = get_group_call(input_group_call_id);
+ remove_recent_group_call_speaker(input_group_call_id, as_dialog_id);
+ if (try_clear_group_call_participants(input_group_call_id)) {
+ CHECK(group_call != nullptr);
+ send_update_group_call(group_call, "finish_join_group_call");
+ }
+ process_group_call_after_join_requests(input_group_call_id, "finish_join_group_call");
+
+ if (group_call != nullptr && group_call->dialog_id.is_valid()) {
+ update_group_call_dialog(group_call, "finish_join_group_call", false);
+ td_->messages_manager_->reload_dialog_info_full(group_call->dialog_id, "finish_join_group_call");
+ }
+}
+
+void GroupCallManager::process_group_call_after_join_requests(InputGroupCallId input_group_call_id,
+ const char *source) {
+ GroupCall *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ return;
+ }
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ LOG(ERROR) << "Failed to process after-join requests from " << source << ": "
+ << is_group_call_being_joined(input_group_call_id) << " " << group_call->need_rejoin;
+ return;
+ }
+ if (group_call->after_join.empty()) {
+ return;
+ }
+
+ if (!group_call->is_active || !group_call->is_joined) {
+ fail_promises(group_call->after_join, Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ } else {
+ set_promises(group_call->after_join);
+ }
+}
+
+void GroupCallManager::set_group_call_title(GroupCallId group_call_id, string title, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ reload_group_call(
+ input_group_call_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, title, promise = std::move(promise)](
+ Result<td_api::object_ptr<td_api::groupCall>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::set_group_call_title, group_call_id, std::move(title),
+ std::move(promise));
+ }
+ }));
+ return;
+ }
+ if (!group_call->is_active || !group_call->can_be_managed) {
+ return promise.set_error(Status::Error(400, "Can't change group call title"));
+ }
+
+ title = clean_name(title, MAX_TITLE_LENGTH);
+ if (title == get_group_call_title(group_call)) {
+ return promise.set_value(Unit());
+ }
+
+ // there is no reason to save promise; we will send an update with actual value anyway
+
+ if (group_call->pending_title.empty()) {
+ send_edit_group_call_title_query(input_group_call_id, title);
+ }
+ group_call->pending_title = std::move(title);
+ send_update_group_call(group_call, "set_group_call_title");
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::send_edit_group_call_title_query(InputGroupCallId input_group_call_id, const string &title) {
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, title](Result<Unit> result) {
+ send_closure(actor_id, &GroupCallManager::on_edit_group_call_title, input_group_call_id, title, std::move(result));
+ });
+ td_->create_handler<EditGroupCallTitleQuery>(std::move(promise))->send(input_group_call_id, title);
+}
+
+void GroupCallManager::on_edit_group_call_title(InputGroupCallId input_group_call_id, const string &title,
+ Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return;
+ }
+
+ if (group_call->pending_title != title && group_call->can_be_managed) {
+ // need to send another request
+ send_edit_group_call_title_query(input_group_call_id, group_call->pending_title);
+ return;
+ }
+
+ bool is_different = group_call->pending_title != group_call->title;
+ if (is_different && group_call->can_be_managed) {
+ LOG(ERROR) << "Failed to set title to " << group_call->pending_title << " in " << input_group_call_id << ": "
+ << result.error();
+ }
+ group_call->pending_title.clear();
+ if (is_different) {
+ send_update_group_call(group_call, "on_set_group_call_title failed");
+ }
+}
+
+void GroupCallManager::toggle_group_call_is_my_video_paused(GroupCallId group_call_id, bool is_my_video_paused,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ if (!group_call->is_joined) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, is_my_video_paused,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ } else {
+ send_closure(actor_id, &GroupCallManager::toggle_group_call_is_my_video_paused, group_call_id,
+ is_my_video_paused, std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ if (is_my_video_paused == get_group_call_is_my_video_paused(group_call)) {
+ return promise.set_value(Unit());
+ }
+
+ // there is no reason to save promise; we will send an update with actual value anyway
+
+ group_call->pending_is_my_video_paused = is_my_video_paused;
+ if (!group_call->have_pending_is_my_video_paused) {
+ group_call->have_pending_is_my_video_paused = true;
+ send_toggle_group_call_is_my_video_paused_query(input_group_call_id, group_call->as_dialog_id, is_my_video_paused);
+ }
+ send_update_group_call(group_call, "toggle_group_call_is_my_video_paused");
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::send_toggle_group_call_is_my_video_paused_query(InputGroupCallId input_group_call_id,
+ DialogId as_dialog_id, bool is_my_video_paused) {
+ auto promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, is_my_video_paused](Result<Unit> result) {
+ send_closure(actor_id, &GroupCallManager::on_toggle_group_call_is_my_video_paused, input_group_call_id,
+ is_my_video_paused, std::move(result));
+ });
+ td_->create_handler<EditGroupCallParticipantQuery>(std::move(promise))
+ ->send(input_group_call_id, as_dialog_id, false, false, 0, false, false, false, false, true, is_my_video_paused,
+ false, false);
+}
+
+void GroupCallManager::on_toggle_group_call_is_my_video_paused(InputGroupCallId input_group_call_id,
+ bool is_my_video_paused, Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active ||
+ !group_call->have_pending_is_my_video_paused) {
+ return;
+ }
+
+ if (result.is_error()) {
+ group_call->have_pending_is_my_video_paused = false;
+ LOG(ERROR) << "Failed to set is_my_video_paused to " << is_my_video_paused << " in " << input_group_call_id << ": "
+ << result.error();
+ if (group_call->pending_is_my_video_paused != group_call->is_my_video_paused) {
+ send_update_group_call(group_call, "on_toggle_group_call_is_my_video_paused failed");
+ }
+ } else {
+ group_call->is_my_video_paused = is_my_video_paused;
+ if (group_call->pending_is_my_video_paused != is_my_video_paused) {
+ // need to send another request
+ send_toggle_group_call_is_my_video_paused_query(input_group_call_id, group_call->as_dialog_id,
+ group_call->pending_is_my_video_paused);
+ return;
+ }
+
+ group_call->have_pending_is_my_video_paused = false;
+ }
+}
+
+void GroupCallManager::toggle_group_call_is_my_video_enabled(GroupCallId group_call_id, bool is_my_video_enabled,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ if (!group_call->is_joined) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, is_my_video_enabled,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ } else {
+ send_closure(actor_id, &GroupCallManager::toggle_group_call_is_my_video_enabled, group_call_id,
+ is_my_video_enabled, std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ if (is_my_video_enabled == get_group_call_is_my_video_enabled(group_call)) {
+ return promise.set_value(Unit());
+ }
+
+ // there is no reason to save promise; we will send an update with actual value anyway
+
+ group_call->pending_is_my_video_enabled = is_my_video_enabled;
+ if (!group_call->have_pending_is_my_video_enabled) {
+ group_call->have_pending_is_my_video_enabled = true;
+ send_toggle_group_call_is_my_video_enabled_query(input_group_call_id, group_call->as_dialog_id,
+ is_my_video_enabled);
+ }
+ send_update_group_call(group_call, "toggle_group_call_is_my_video_enabled");
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::send_toggle_group_call_is_my_video_enabled_query(InputGroupCallId input_group_call_id,
+ DialogId as_dialog_id,
+ bool is_my_video_enabled) {
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), input_group_call_id, is_my_video_enabled](Result<Unit> result) {
+ send_closure(actor_id, &GroupCallManager::on_toggle_group_call_is_my_video_enabled, input_group_call_id,
+ is_my_video_enabled, std::move(result));
+ });
+ td_->create_handler<EditGroupCallParticipantQuery>(std::move(promise))
+ ->send(input_group_call_id, as_dialog_id, false, false, 0, false, false, true, !is_my_video_enabled, false, false,
+ false, false);
+}
+
+void GroupCallManager::on_toggle_group_call_is_my_video_enabled(InputGroupCallId input_group_call_id,
+ bool is_my_video_enabled, Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active ||
+ !group_call->have_pending_is_my_video_enabled) {
+ return;
+ }
+
+ if (result.is_error()) {
+ group_call->have_pending_is_my_video_enabled = false;
+ LOG(ERROR) << "Failed to set is_my_video_enabled to " << is_my_video_enabled << " in " << input_group_call_id
+ << ": " << result.error();
+ if (group_call->pending_is_my_video_enabled != group_call->is_my_video_enabled) {
+ send_update_group_call(group_call, "on_toggle_group_call_is_my_video_enabled failed");
+ }
+ } else {
+ group_call->is_my_video_enabled = is_my_video_enabled;
+ if (group_call->pending_is_my_video_enabled != is_my_video_enabled) {
+ // need to send another request
+ send_toggle_group_call_is_my_video_enabled_query(input_group_call_id, group_call->as_dialog_id,
+ group_call->pending_is_my_video_enabled);
+ return;
+ }
+
+ group_call->have_pending_is_my_video_enabled = false;
+ }
+}
+
+void GroupCallManager::toggle_group_call_is_my_presentation_paused(GroupCallId group_call_id,
+ bool is_my_presentation_paused,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ if (!group_call->is_joined) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, is_my_presentation_paused,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ } else {
+ send_closure(actor_id, &GroupCallManager::toggle_group_call_is_my_presentation_paused, group_call_id,
+ is_my_presentation_paused, std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ if (is_my_presentation_paused == get_group_call_is_my_presentation_paused(group_call)) {
+ return promise.set_value(Unit());
+ }
+
+ // there is no reason to save promise; we will send an update with actual value anyway
+
+ group_call->pending_is_my_presentation_paused = is_my_presentation_paused;
+ if (!group_call->have_pending_is_my_presentation_paused) {
+ group_call->have_pending_is_my_presentation_paused = true;
+ send_toggle_group_call_is_my_presentation_paused_query(input_group_call_id, group_call->as_dialog_id,
+ is_my_presentation_paused);
+ }
+ send_update_group_call(group_call, "toggle_group_call_is_my_presentation_paused");
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::send_toggle_group_call_is_my_presentation_paused_query(InputGroupCallId input_group_call_id,
+ DialogId as_dialog_id,
+ bool is_my_presentation_paused) {
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), input_group_call_id, is_my_presentation_paused](Result<Unit> result) {
+ send_closure(actor_id, &GroupCallManager::on_toggle_group_call_is_my_presentation_paused, input_group_call_id,
+ is_my_presentation_paused, std::move(result));
+ });
+ td_->create_handler<EditGroupCallParticipantQuery>(std::move(promise))
+ ->send(input_group_call_id, as_dialog_id, false, false, 0, false, false, false, false, false, false, true,
+ is_my_presentation_paused);
+}
+
+void GroupCallManager::on_toggle_group_call_is_my_presentation_paused(InputGroupCallId input_group_call_id,
+ bool is_my_presentation_paused,
+ Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active ||
+ !group_call->have_pending_is_my_presentation_paused) {
+ return;
+ }
+
+ if (result.is_error()) {
+ group_call->have_pending_is_my_presentation_paused = false;
+ LOG(ERROR) << "Failed to set is_my_presentation_paused to " << is_my_presentation_paused << " in "
+ << input_group_call_id << ": " << result.error();
+ if (group_call->pending_is_my_presentation_paused != group_call->is_my_presentation_paused) {
+ send_update_group_call(group_call, "on_toggle_group_call_is_my_presentation_paused failed");
+ }
+ } else {
+ group_call->is_my_presentation_paused = is_my_presentation_paused;
+ if (group_call->pending_is_my_presentation_paused != is_my_presentation_paused) {
+ // need to send another request
+ send_toggle_group_call_is_my_presentation_paused_query(input_group_call_id, group_call->as_dialog_id,
+ group_call->pending_is_my_presentation_paused);
+ return;
+ }
+
+ group_call->have_pending_is_my_presentation_paused = false;
+ }
+}
+
+void GroupCallManager::toggle_group_call_start_subscribed(GroupCallId group_call_id, bool start_subscribed,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ reload_group_call(input_group_call_id,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), group_call_id, start_subscribed, promise = std::move(promise)](
+ Result<td_api::object_ptr<td_api::groupCall>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::toggle_group_call_start_subscribed,
+ group_call_id, start_subscribed, std::move(promise));
+ }
+ }));
+ return;
+ }
+ if (!group_call->is_active || group_call->scheduled_start_date <= 0) {
+ return promise.set_error(Status::Error(400, "Group call isn't scheduled"));
+ }
+
+ if (start_subscribed == get_group_call_start_subscribed(group_call)) {
+ return promise.set_value(Unit());
+ }
+
+ // there is no reason to save promise; we will send an update with actual value anyway
+
+ group_call->pending_start_subscribed = start_subscribed;
+ if (!group_call->have_pending_start_subscribed) {
+ group_call->have_pending_start_subscribed = true;
+ send_toggle_group_call_start_subscription_query(input_group_call_id, start_subscribed);
+ }
+ send_update_group_call(group_call, "toggle_group_call_start_subscribed");
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::send_toggle_group_call_start_subscription_query(InputGroupCallId input_group_call_id,
+ bool start_subscribed) {
+ auto promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, start_subscribed](Result<Unit> result) {
+ send_closure(actor_id, &GroupCallManager::on_toggle_group_call_start_subscription, input_group_call_id,
+ start_subscribed, std::move(result));
+ });
+ td_->create_handler<ToggleGroupCallStartSubscriptionQuery>(std::move(promise))
+ ->send(input_group_call_id, start_subscribed);
+}
+
+void GroupCallManager::on_toggle_group_call_start_subscription(InputGroupCallId input_group_call_id,
+ bool start_subscribed, Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active ||
+ !group_call->have_pending_start_subscribed) {
+ return;
+ }
+
+ if (result.is_error()) {
+ group_call->have_pending_start_subscribed = false;
+ LOG(ERROR) << "Failed to set enabled_start_notification to " << start_subscribed << " in " << input_group_call_id
+ << ": " << result.error();
+ if (group_call->pending_start_subscribed != group_call->start_subscribed) {
+ send_update_group_call(group_call, "on_toggle_group_call_start_subscription failed");
+ }
+ } else {
+ if (group_call->pending_start_subscribed != start_subscribed) {
+ // need to send another request
+ send_toggle_group_call_start_subscription_query(input_group_call_id, group_call->pending_start_subscribed);
+ return;
+ }
+
+ group_call->have_pending_start_subscribed = false;
+ if (group_call->start_subscribed != start_subscribed) {
+ LOG(ERROR) << "Failed to set enabled_start_notification to " << start_subscribed << " in " << input_group_call_id;
+ send_update_group_call(group_call, "on_toggle_group_call_start_subscription failed 2");
+ }
+ }
+}
+
+void GroupCallManager::toggle_group_call_mute_new_participants(GroupCallId group_call_id, bool mute_new_participants,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ reload_group_call(input_group_call_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, mute_new_participants,
+ promise = std::move(promise)](
+ Result<td_api::object_ptr<td_api::groupCall>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::toggle_group_call_mute_new_participants,
+ group_call_id, mute_new_participants, std::move(promise));
+ }
+ }));
+ return;
+ }
+ if (!group_call->is_active || !group_call->can_be_managed || !group_call->allowed_toggle_mute_new_participants) {
+ return promise.set_error(Status::Error(400, "Can't change mute_new_participants setting"));
+ }
+
+ if (mute_new_participants == get_group_call_mute_new_participants(group_call)) {
+ return promise.set_value(Unit());
+ }
+
+ // there is no reason to save promise; we will send an update with actual value anyway
+
+ group_call->pending_mute_new_participants = mute_new_participants;
+ if (!group_call->have_pending_mute_new_participants) {
+ group_call->have_pending_mute_new_participants = true;
+ send_toggle_group_call_mute_new_participants_query(input_group_call_id, mute_new_participants);
+ }
+ send_update_group_call(group_call, "toggle_group_call_mute_new_participants");
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::send_toggle_group_call_mute_new_participants_query(InputGroupCallId input_group_call_id,
+ bool mute_new_participants) {
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), input_group_call_id, mute_new_participants](Result<Unit> result) {
+ send_closure(actor_id, &GroupCallManager::on_toggle_group_call_mute_new_participants, input_group_call_id,
+ mute_new_participants, std::move(result));
+ });
+ int32 flags = telegram_api::phone_toggleGroupCallSettings::JOIN_MUTED_MASK;
+ td_->create_handler<ToggleGroupCallSettingsQuery>(std::move(promise))
+ ->send(flags, input_group_call_id, mute_new_participants);
+}
+
+void GroupCallManager::on_toggle_group_call_mute_new_participants(InputGroupCallId input_group_call_id,
+ bool mute_new_participants, Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active ||
+ !group_call->have_pending_mute_new_participants) {
+ return;
+ }
+
+ if (result.is_error()) {
+ group_call->have_pending_mute_new_participants = false;
+ if (group_call->can_be_managed && group_call->allowed_toggle_mute_new_participants) {
+ LOG(ERROR) << "Failed to set mute_new_participants to " << mute_new_participants << " in " << input_group_call_id
+ << ": " << result.error();
+ }
+ if (group_call->pending_mute_new_participants != group_call->mute_new_participants) {
+ send_update_group_call(group_call, "on_toggle_group_call_mute_new_participants failed");
+ }
+ } else {
+ if (group_call->pending_mute_new_participants != mute_new_participants) {
+ // need to send another request
+ send_toggle_group_call_mute_new_participants_query(input_group_call_id,
+ group_call->pending_mute_new_participants);
+ return;
+ }
+
+ group_call->have_pending_mute_new_participants = false;
+ if (group_call->mute_new_participants != mute_new_participants) {
+ LOG(ERROR) << "Failed to set mute_new_participants to " << mute_new_participants << " in " << input_group_call_id;
+ send_update_group_call(group_call, "on_toggle_group_call_mute_new_participants failed 2");
+ }
+ }
+}
+
+void GroupCallManager::revoke_group_call_invite_link(GroupCallId group_call_id, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ reload_group_call(input_group_call_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, promise = std::move(promise)](
+ Result<td_api::object_ptr<td_api::groupCall>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::revoke_group_call_invite_link, group_call_id,
+ std::move(promise));
+ }
+ }));
+ return;
+ }
+ if (!group_call->is_active || !group_call->can_be_managed) {
+ return promise.set_error(Status::Error(400, "Can't reset invite hash in the group call"));
+ }
+
+ int32 flags = telegram_api::phone_toggleGroupCallSettings::RESET_INVITE_HASH_MASK;
+ td_->create_handler<ToggleGroupCallSettingsQuery>(std::move(promise))->send(flags, input_group_call_id, false);
+}
+
+void GroupCallManager::invite_group_call_participants(GroupCallId group_call_id, vector<UserId> &&user_ids,
+ Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ vector<tl_object_ptr<telegram_api::InputUser>> input_users;
+ auto my_user_id = td_->contacts_manager_->get_my_id();
+ for (auto user_id : user_ids) {
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
+ }
+
+ if (user_id == my_user_id) {
+ // can't invite self
+ continue;
+ }
+ input_users.push_back(r_input_user.move_as_ok());
+ }
+
+ if (input_users.empty()) {
+ return promise.set_value(Unit());
+ }
+
+ td_->create_handler<InviteToGroupCallQuery>(std::move(promise))->send(input_group_call_id, std::move(input_users));
+}
+
+void GroupCallManager::get_group_call_invite_link(GroupCallId group_call_id, bool can_self_unmute,
+ Promise<string> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ reload_group_call(input_group_call_id,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), group_call_id, can_self_unmute, promise = std::move(promise)](
+ Result<td_api::object_ptr<td_api::groupCall>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::get_group_call_invite_link, group_call_id,
+ can_self_unmute, std::move(promise));
+ }
+ }));
+ return;
+ }
+ if (!group_call->is_active) {
+ return promise.set_error(Status::Error(400, "Can't get group call invite link"));
+ }
+
+ if (can_self_unmute && !group_call->can_be_managed) {
+ return promise.set_error(Status::Error(400, "Not enough rights in the group call"));
+ }
+
+ td_->create_handler<ExportGroupCallInviteQuery>(std::move(promise))->send(input_group_call_id, can_self_unmute);
+}
+
+void GroupCallManager::toggle_group_call_recording(GroupCallId group_call_id, bool is_enabled, string title,
+ bool record_video, bool use_portrait_orientation,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited) {
+ reload_group_call(
+ input_group_call_id,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), group_call_id, is_enabled, title, record_video, use_portrait_orientation,
+ promise = std::move(promise)](Result<td_api::object_ptr<td_api::groupCall>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &GroupCallManager::toggle_group_call_recording, group_call_id, is_enabled,
+ std::move(title), record_video, use_portrait_orientation, std::move(promise));
+ }
+ }));
+ return;
+ }
+ if (!group_call->is_active || !group_call->can_be_managed) {
+ return promise.set_error(Status::Error(400, "Can't manage group call recording"));
+ }
+
+ title = clean_name(title, MAX_TITLE_LENGTH);
+
+ if (is_enabled == get_group_call_has_recording(group_call)) {
+ return promise.set_value(Unit());
+ }
+
+ // there is no reason to save promise; we will send an update with actual value anyway
+
+ if (!group_call->have_pending_record_start_date) {
+ send_toggle_group_call_recording_query(input_group_call_id, is_enabled, title, record_video,
+ use_portrait_orientation, toggle_recording_generation_ + 1);
+ }
+ group_call->have_pending_record_start_date = true;
+ group_call->pending_record_start_date = is_enabled ? G()->unix_time() : 0;
+ group_call->pending_record_title = std::move(title);
+ group_call->pending_record_record_video = record_video;
+ group_call->pending_record_use_portrait_orientation = use_portrait_orientation;
+ group_call->toggle_recording_generation = ++toggle_recording_generation_;
+ send_update_group_call(group_call, "toggle_group_call_recording");
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::send_toggle_group_call_recording_query(InputGroupCallId input_group_call_id, bool is_enabled,
+ const string &title, bool record_video,
+ bool use_portrait_orientation, uint64 generation) {
+ auto promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, generation](Result<Unit> result) {
+ send_closure(actor_id, &GroupCallManager::on_toggle_group_call_recording, input_group_call_id, generation,
+ std::move(result));
+ });
+ td_->create_handler<ToggleGroupCallRecordQuery>(std::move(promise))
+ ->send(input_group_call_id, is_enabled, title, record_video, use_portrait_orientation);
+}
+
+void GroupCallManager::on_toggle_group_call_recording(InputGroupCallId input_group_call_id, uint64 generation,
+ Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return;
+ }
+
+ CHECK(group_call->have_pending_record_start_date);
+
+ if (group_call->toggle_recording_generation != generation && group_call->can_be_managed) {
+ // need to send another request
+ send_toggle_group_call_recording_query(input_group_call_id, group_call->pending_record_start_date != 0,
+ group_call->pending_record_title, group_call->pending_record_record_video,
+ group_call->pending_record_use_portrait_orientation,
+ group_call->toggle_recording_generation);
+ return;
+ }
+
+ auto current_record_start_date = get_group_call_record_start_date(group_call);
+ auto current_is_video_recorded = get_group_call_is_video_recorded(group_call);
+ group_call->have_pending_record_start_date = false;
+ if (current_record_start_date != get_group_call_record_start_date(group_call) ||
+ current_is_video_recorded != get_group_call_is_video_recorded(group_call)) {
+ send_update_group_call(group_call, "on_toggle_group_call_recording");
+ }
+}
+
+void GroupCallManager::set_group_call_participant_is_speaking(GroupCallId group_call_id, int32 audio_source,
+ bool is_speaking, Promise<Unit> &&promise, int32 date) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return promise.set_value(Unit());
+ }
+ if (!group_call->is_joined) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, audio_source, is_speaking,
+ promise = std::move(promise), date](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_value(Unit());
+ } else {
+ send_closure(actor_id, &GroupCallManager::set_group_call_participant_is_speaking, group_call_id,
+ audio_source, is_speaking, std::move(promise), date);
+ }
+ }));
+ return;
+ }
+ return promise.set_value(Unit());
+ }
+ if (audio_source == 0) {
+ audio_source = group_call->audio_source;
+ if (audio_source == 0) {
+ return promise.set_error(Status::Error(400, "Can't speak without joining the group call"));
+ }
+ }
+
+ bool is_recursive = false;
+ if (date == 0) {
+ date = G()->unix_time();
+ } else {
+ is_recursive = true;
+ }
+ if (group_call->audio_source != 0 && audio_source != group_call->audio_source && !is_recursive && is_speaking &&
+ check_group_call_is_joined_timeout_.has_timeout(group_call_id.get())) {
+ check_group_call_is_joined_timeout_.set_timeout_in(group_call_id.get(), CHECK_GROUP_CALL_IS_JOINED_TIMEOUT);
+ }
+ DialogId dialog_id =
+ set_group_call_participant_is_speaking_by_source(input_group_call_id, audio_source, is_speaking, date);
+ if (!dialog_id.is_valid()) {
+ if (!is_recursive) {
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, audio_source, is_speaking,
+ promise = std::move(promise), date](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_value(Unit());
+ } else {
+ send_closure(actor_id, &GroupCallManager::set_group_call_participant_is_speaking, group_call_id, audio_source,
+ is_speaking, std::move(promise), date);
+ }
+ });
+ td_->create_handler<GetGroupCallParticipantQuery>(std::move(query_promise))
+ ->send(input_group_call_id, {}, {audio_source});
+ } else {
+ LOG(INFO) << "Failed to find participant with source " << audio_source << " in " << group_call_id << " from "
+ << group_call->dialog_id;
+ promise.set_value(Unit());
+ }
+ return;
+ }
+
+ if (is_speaking) {
+ on_user_speaking_in_group_call(group_call_id, dialog_id, date, is_recursive);
+ }
+
+ if (group_call->audio_source == audio_source && group_call->dialog_id.is_valid() &&
+ group_call->is_speaking != is_speaking) {
+ group_call->is_speaking = is_speaking;
+ if (is_speaking) {
+ pending_send_speaking_action_timeout_.add_timeout_in(group_call_id.get(), 0.0);
+ }
+ }
+
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::toggle_group_call_participant_is_muted(GroupCallId group_call_id, DialogId dialog_id,
+ bool is_muted, Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ if (!group_call->is_joined) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, dialog_id, is_muted,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ } else {
+ send_closure(actor_id, &GroupCallManager::toggle_group_call_participant_is_muted, group_call_id,
+ dialog_id, is_muted, std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ auto participants = add_group_call_participants(input_group_call_id);
+ auto participant = get_group_call_participant(participants, dialog_id);
+ if (participant == nullptr) {
+ return promise.set_error(Status::Error(400, "Can't find group call participant"));
+ }
+ dialog_id = participant->dialog_id;
+
+ bool can_manage = can_manage_group_call(input_group_call_id);
+ bool is_admin = td::contains(participants->administrator_dialog_ids, dialog_id);
+
+ auto participant_copy = *participant;
+ if (!participant_copy.set_pending_is_muted(is_muted, can_manage, is_admin)) {
+ return promise.set_error(Status::Error(400, PSLICE() << "Can't " << (is_muted ? "" : "un") << "mute user"));
+ }
+ if (participant_copy == *participant) {
+ return promise.set_value(Unit());
+ }
+ *participant = std::move(participant_copy);
+
+ participant->pending_is_muted_generation = ++toggle_is_muted_generation_;
+ if (participant->order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, *participant, "toggle_group_call_participant_is_muted");
+ }
+
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, dialog_id,
+ generation = participant->pending_is_muted_generation,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ promise = Promise<Unit>();
+ }
+ send_closure(actor_id, &GroupCallManager::on_toggle_group_call_participant_is_muted, input_group_call_id, dialog_id,
+ generation, std::move(promise));
+ });
+ td_->create_handler<EditGroupCallParticipantQuery>(std::move(query_promise))
+ ->send(input_group_call_id, dialog_id, true, is_muted, 0, false, false, false, false, false, false, false, false);
+}
+
+void GroupCallManager::on_toggle_group_call_participant_is_muted(InputGroupCallId input_group_call_id,
+ DialogId dialog_id, uint64 generation,
+ Promise<Unit> &&promise) {
+ if (G()->close_flag()) {
+ return promise.set_value(Unit());
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined) {
+ return promise.set_value(Unit());
+ }
+
+ auto participants = add_group_call_participants(input_group_call_id);
+ auto participant = get_group_call_participant(participants, dialog_id);
+ if (participant == nullptr || participant->pending_is_muted_generation != generation) {
+ return promise.set_value(Unit());
+ }
+
+ CHECK(participant->have_pending_is_muted);
+ participant->have_pending_is_muted = false;
+ bool can_manage = can_manage_group_call(input_group_call_id);
+ if (update_group_call_participant_can_be_muted(can_manage, participants, *participant) ||
+ participant->server_is_muted_by_themselves != participant->pending_is_muted_by_themselves ||
+ participant->server_is_muted_by_admin != participant->pending_is_muted_by_admin ||
+ participant->server_is_muted_locally != participant->pending_is_muted_locally) {
+ LOG(ERROR) << "Failed to mute/unmute " << dialog_id << " in " << input_group_call_id;
+ if (participant->order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, *participant,
+ "on_toggle_group_call_participant_is_muted");
+ }
+ }
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::set_group_call_participant_volume_level(GroupCallId group_call_id, DialogId dialog_id,
+ int32 volume_level, Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+ if (volume_level < GroupCallParticipant::MIN_VOLUME_LEVEL || volume_level > GroupCallParticipant::MAX_VOLUME_LEVEL) {
+ return promise.set_error(Status::Error(400, "Wrong volume level specified"));
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ if (!group_call->is_joined) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, dialog_id, volume_level,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ } else {
+ send_closure(actor_id, &GroupCallManager::set_group_call_participant_volume_level, group_call_id,
+ dialog_id, volume_level, std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ auto participant = get_group_call_participant(input_group_call_id, dialog_id);
+ if (participant == nullptr) {
+ return promise.set_error(Status::Error(400, "Can't find group call participant"));
+ }
+ dialog_id = participant->dialog_id;
+
+ if (participant->is_self) {
+ return promise.set_error(Status::Error(400, "Can't change self volume level"));
+ }
+
+ if (participant->get_volume_level() == volume_level) {
+ return promise.set_value(Unit());
+ }
+
+ participant->pending_volume_level = volume_level;
+ participant->pending_volume_level_generation = ++set_volume_level_generation_;
+ if (participant->order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, *participant, "set_group_call_participant_volume_level");
+ }
+
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, dialog_id,
+ generation = participant->pending_volume_level_generation,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ promise = Promise<Unit>();
+ }
+ send_closure(actor_id, &GroupCallManager::on_set_group_call_participant_volume_level, input_group_call_id,
+ dialog_id, generation, std::move(promise));
+ });
+ td_->create_handler<EditGroupCallParticipantQuery>(std::move(query_promise))
+ ->send(input_group_call_id, dialog_id, false, false, volume_level, false, false, false, false, false, false,
+ false, false);
+}
+
+void GroupCallManager::on_set_group_call_participant_volume_level(InputGroupCallId input_group_call_id,
+ DialogId dialog_id, uint64 generation,
+ Promise<Unit> &&promise) {
+ if (G()->close_flag()) {
+ return promise.set_value(Unit());
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined) {
+ return promise.set_value(Unit());
+ }
+
+ auto participant = get_group_call_participant(input_group_call_id, dialog_id);
+ if (participant == nullptr || participant->pending_volume_level_generation != generation) {
+ return promise.set_value(Unit());
+ }
+
+ CHECK(participant->pending_volume_level != 0);
+ if (participant->volume_level != participant->pending_volume_level) {
+ LOG(ERROR) << "Failed to set volume level of " << dialog_id << " in " << input_group_call_id;
+ participant->pending_volume_level = 0;
+ if (participant->order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, *participant,
+ "on_set_group_call_participant_volume_level");
+ }
+ } else {
+ participant->pending_volume_level = 0;
+ }
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::toggle_group_call_participant_is_hand_raised(GroupCallId group_call_id, DialogId dialog_id,
+ bool is_hand_raised, Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active) {
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ if (!group_call->is_joined) {
+ if (is_group_call_being_joined(input_group_call_id) || group_call->need_rejoin) {
+ group_call->after_join.push_back(
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, dialog_id, is_hand_raised,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ } else {
+ send_closure(actor_id, &GroupCallManager::toggle_group_call_participant_is_hand_raised, group_call_id,
+ dialog_id, is_hand_raised, std::move(promise));
+ }
+ }));
+ return;
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+
+ auto participants = add_group_call_participants(input_group_call_id);
+ auto participant = get_group_call_participant(participants, dialog_id);
+ if (participant == nullptr) {
+ return promise.set_error(Status::Error(400, "Can't find group call participant"));
+ }
+ dialog_id = participant->dialog_id;
+
+ if (is_hand_raised == participant->get_is_hand_raised()) {
+ return promise.set_value(Unit());
+ }
+
+ if (!participant->is_self) {
+ if (is_hand_raised) {
+ return promise.set_error(Status::Error(400, "Can't raise others hand"));
+ } else {
+ if (!can_manage_group_call(input_group_call_id)) {
+ return promise.set_error(Status::Error(400, "Have not enough rights in the group call"));
+ }
+ }
+ }
+
+ participant->have_pending_is_hand_raised = true;
+ participant->pending_is_hand_raised = is_hand_raised;
+ participant->pending_is_hand_raised_generation = ++toggle_is_hand_raised_generation_;
+ if (participant->order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, *participant,
+ "toggle_group_call_participant_is_hand_raised");
+ }
+
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, dialog_id,
+ generation = participant->pending_is_hand_raised_generation,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ promise = Promise<Unit>();
+ }
+ send_closure(actor_id, &GroupCallManager::on_toggle_group_call_participant_is_hand_raised, input_group_call_id,
+ dialog_id, generation, std::move(promise));
+ });
+ td_->create_handler<EditGroupCallParticipantQuery>(std::move(query_promise))
+ ->send(input_group_call_id, dialog_id, false, false, 0, true, is_hand_raised, false, false, false, false, false,
+ false);
+}
+
+void GroupCallManager::on_toggle_group_call_participant_is_hand_raised(InputGroupCallId input_group_call_id,
+ DialogId dialog_id, uint64 generation,
+ Promise<Unit> &&promise) {
+ if (G()->close_flag()) {
+ return promise.set_value(Unit());
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined) {
+ return promise.set_value(Unit());
+ }
+
+ auto participant = get_group_call_participant(input_group_call_id, dialog_id);
+ if (participant == nullptr || participant->pending_is_hand_raised_generation != generation) {
+ return promise.set_value(Unit());
+ }
+
+ CHECK(participant->have_pending_is_hand_raised);
+ participant->have_pending_is_hand_raised = false;
+ if (participant->get_is_hand_raised() != participant->pending_is_hand_raised) {
+ LOG(ERROR) << "Failed to change raised hand state for " << dialog_id << " in " << input_group_call_id;
+ if (participant->order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, *participant,
+ "on_toggle_group_call_participant_is_hand_raised");
+ }
+ }
+ promise.set_value(Unit());
+}
+
+void GroupCallManager::load_group_call_participants(GroupCallId group_call_id, int32 limit, Promise<Unit> &&promise) {
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ }
+
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (!need_group_call_participants(input_group_call_id, group_call)) {
+ return promise.set_error(Status::Error(400, "Can't load group call participants"));
+ }
+ CHECK(group_call != nullptr && group_call->is_inited);
+ if (group_call->loaded_all_participants) {
+ return promise.set_value(Unit());
+ }
+
+ string next_offset;
+ auto participants_it = group_call_participants_.find(input_group_call_id);
+ if (participants_it != group_call_participants_.end()) {
+ CHECK(participants_it->second != nullptr);
+ next_offset = participants_it->second->next_offset;
+ }
+ if (limit == 1 && next_offset.empty()) {
+ // prevent removing self as the first user and deducing that there are no more participants
+ limit = 2;
+ }
+ td_->create_handler<GetGroupCallParticipantsQuery>(std::move(promise))
+ ->send(input_group_call_id, std::move(next_offset), limit);
+}
+
+void GroupCallManager::leave_group_call(GroupCallId group_call_id, Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr || !group_call->is_inited || !group_call->is_active || !group_call->is_joined ||
+ group_call->is_being_left) {
+ if (cancel_join_group_call_request(input_group_call_id) != 0) {
+ if (try_clear_group_call_participants(input_group_call_id)) {
+ send_update_group_call(group_call, "leave_group_call 1");
+ }
+ process_group_call_after_join_requests(input_group_call_id, "leave_group_call 1");
+ return promise.set_value(Unit());
+ }
+ if (group_call != nullptr && group_call->need_rejoin) {
+ group_call->need_rejoin = false;
+ send_update_group_call(group_call, "leave_group_call");
+ if (try_clear_group_call_participants(input_group_call_id)) {
+ send_update_group_call(group_call, "leave_group_call 2");
+ }
+ process_group_call_after_join_requests(input_group_call_id, "leave_group_call 2");
+ return promise.set_value(Unit());
+ }
+ return promise.set_error(Status::Error(400, "GROUPCALL_JOIN_MISSING"));
+ }
+ auto audio_source = cancel_join_group_call_request(input_group_call_id);
+ if (audio_source == 0) {
+ audio_source = group_call->audio_source;
+ }
+ group_call->is_being_left = true;
+ group_call->need_rejoin = false;
+ send_update_group_call(group_call, "leave_group_call");
+
+ process_group_call_after_join_requests(input_group_call_id, "leave_group_call 3");
+
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), input_group_call_id, audio_source,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_ok()) {
+ // just in case
+ send_closure(actor_id, &GroupCallManager::on_group_call_left, input_group_call_id, audio_source, false);
+ }
+ promise.set_result(std::move(result));
+ });
+ td_->create_handler<LeaveGroupCallQuery>(std::move(query_promise))->send(input_group_call_id, audio_source);
+}
+
+void GroupCallManager::on_group_call_left(InputGroupCallId input_group_call_id, int32 audio_source, bool need_rejoin) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ if (group_call->is_joined && group_call->audio_source == audio_source) {
+ on_group_call_left_impl(group_call, need_rejoin, "on_group_call_left");
+ send_update_group_call(group_call, "on_group_call_left");
+ }
+}
+
+void GroupCallManager::on_group_call_left_impl(GroupCall *group_call, bool need_rejoin, const char *source) {
+ CHECK(group_call != nullptr && group_call->is_inited && group_call->is_joined);
+ LOG(INFO) << "Leave " << group_call->group_call_id << " in " << group_call->dialog_id
+ << " with need_rejoin = " << need_rejoin << " from " << source;
+ group_call->is_joined = false;
+ group_call->need_rejoin = need_rejoin && !group_call->is_being_left;
+ if (group_call->need_rejoin && group_call->dialog_id.is_valid()) {
+ auto dialog_id = group_call->dialog_id;
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read) ||
+ (dialog_id.get_type() == DialogType::Chat &&
+ !td_->contacts_manager_->get_chat_status(dialog_id.get_chat_id()).is_member())) {
+ group_call->need_rejoin = false;
+ }
+ }
+ group_call->is_being_left = false;
+ group_call->is_speaking = false;
+ group_call->is_my_video_paused = false;
+ group_call->is_my_video_enabled = false;
+ group_call->is_my_presentation_paused = false;
+ group_call->have_pending_is_my_video_enabled = false;
+ if (!group_call->is_active) {
+ group_call->can_be_managed = false;
+ }
+ group_call->joined_date = 0;
+ group_call->audio_source = 0;
+ check_group_call_is_joined_timeout_.cancel_timeout(group_call->group_call_id.get());
+ auto input_group_call_id = get_input_group_call_id(group_call->group_call_id).ok();
+ try_clear_group_call_participants(input_group_call_id);
+ if (!group_call->need_rejoin) {
+ if (is_group_call_being_joined(input_group_call_id)) {
+ LOG(ERROR) << "Left a being joined group call. Did you change audio_source_id without leaving the group call?";
+ } else {
+ process_group_call_after_join_requests(input_group_call_id, "on_group_call_left_impl");
+ }
+ }
+}
+
+void GroupCallManager::discard_group_call(GroupCallId group_call_id, Promise<Unit> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_group_call_id, get_input_group_call_id(group_call_id));
+ td_->create_handler<DiscardGroupCallQuery>(std::move(promise))->send(input_group_call_id);
+}
+
+void GroupCallManager::on_update_group_call_connection(string &&connection_params) {
+ if (!pending_group_call_join_params_.empty()) {
+ LOG(ERROR) << "Receive duplicate connection params";
+ }
+ pending_group_call_join_params_ = std::move(connection_params);
+}
+
+void GroupCallManager::on_update_group_call(tl_object_ptr<telegram_api::GroupCall> group_call_ptr, DialogId dialog_id) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ if (dialog_id != DialogId() && !dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive " << to_string(group_call_ptr) << " in invalid " << dialog_id;
+ dialog_id = DialogId();
+ }
+ auto input_group_call_id = update_group_call(group_call_ptr, dialog_id);
+ if (input_group_call_id.is_valid()) {
+ LOG(INFO) << "Update " << input_group_call_id << " from " << dialog_id;
+ } else {
+ LOG(ERROR) << "Receive invalid " << to_string(group_call_ptr);
+ }
+}
+
+bool GroupCallManager::try_clear_group_call_participants(InputGroupCallId input_group_call_id) {
+ if (need_group_call_participants(input_group_call_id)) {
+ return false;
+ }
+
+ auto group_call = get_group_call(input_group_call_id);
+ if (group_call != nullptr) {
+ update_group_call_participant_order_timeout_.cancel_timeout(group_call->group_call_id.get());
+ remove_recent_group_call_speaker(input_group_call_id, group_call->as_dialog_id);
+ }
+
+ auto participants_it = group_call_participants_.find(input_group_call_id);
+ if (participants_it == group_call_participants_.end()) {
+ return false;
+ }
+
+ auto participants = std::move(participants_it->second);
+ CHECK(participants != nullptr);
+ group_call_participants_.erase(participants_it);
+
+ CHECK(group_call != nullptr && group_call->is_inited);
+ LOG(INFO) << "Clear participants in " << input_group_call_id << " from " << group_call->dialog_id;
+ if (group_call->loaded_all_participants) {
+ group_call->loaded_all_participants = false;
+ send_update_group_call(group_call, "try_clear_group_call_participants");
+ }
+ group_call->leave_version = group_call->version;
+ group_call->version = -1;
+
+ bool need_update = false;
+ for (auto &participant : participants->participants) {
+ if (participant.order.is_valid()) {
+ CHECK(participant.order >= participants->min_order);
+ participant.order = GroupCallParticipantOrder();
+ send_update_group_call_participant(input_group_call_id, participant, "try_clear_group_call_participants");
+
+ if (participant.is_self) {
+ need_update |= set_group_call_participant_count(group_call, group_call->participant_count - 1,
+ "try_clear_group_call_participants");
+ if (participant.get_has_video()) {
+ need_update |= set_group_call_unmuted_video_count(group_call, group_call->unmuted_video_count - 1,
+ "try_clear_group_call_participants");
+ }
+ }
+ }
+ on_remove_group_call_participant(input_group_call_id, participant.dialog_id);
+ }
+ participants->local_unmuted_video_count = 0;
+
+ if (group_call_participants_.empty()) {
+ CHECK(participant_id_to_group_call_id_.empty());
+ }
+ return need_update;
+}
+
+InputGroupCallId GroupCallManager::update_group_call(const tl_object_ptr<telegram_api::GroupCall> &group_call_ptr,
+ DialogId dialog_id) {
+ CHECK(group_call_ptr != nullptr);
+
+ InputGroupCallId input_group_call_id;
+ GroupCall call;
+ call.is_inited = true;
+
+ switch (group_call_ptr->get_id()) {
+ case telegram_api::groupCall::ID: {
+ auto group_call = static_cast<const telegram_api::groupCall *>(group_call_ptr.get());
+ input_group_call_id = InputGroupCallId(group_call->id_, group_call->access_hash_);
+ call.is_active = true;
+ call.is_rtmp_stream = group_call->rtmp_stream_;
+ call.has_hidden_listeners = group_call->listeners_hidden_;
+ call.title = group_call->title_;
+ call.start_subscribed = group_call->schedule_start_subscribed_;
+ call.mute_new_participants = group_call->join_muted_;
+ call.joined_date_asc = group_call->join_date_asc_;
+ call.allowed_toggle_mute_new_participants = group_call->can_change_join_muted_;
+ call.participant_count = group_call->participants_count_;
+ call.unmuted_video_count = group_call->unmuted_video_count_;
+ call.unmuted_video_limit = group_call->unmuted_video_limit_;
+ if ((group_call->flags_ & telegram_api::groupCall::STREAM_DC_ID_MASK) != 0) {
+ call.stream_dc_id = DcId::create(group_call->stream_dc_id_);
+ if (!call.stream_dc_id.is_exact()) {
+ LOG(ERROR) << "Receive invalid stream DC ID " << call.stream_dc_id << " in " << input_group_call_id;
+ call.stream_dc_id = DcId();
+ }
+ } else {
+ call.stream_dc_id = DcId();
+ }
+ if ((group_call->flags_ & telegram_api::groupCall::RECORD_START_DATE_MASK) != 0) {
+ call.record_start_date = group_call->record_start_date_;
+ call.is_video_recorded = group_call->record_video_active_;
+ if (call.record_start_date <= 0) {
+ LOG(ERROR) << "Receive invalid record start date " << group_call->record_start_date_ << " in "
+ << input_group_call_id;
+ call.record_start_date = 0;
+ call.is_video_recorded = false;
+ }
+ } else {
+ call.record_start_date = 0;
+ call.is_video_recorded = false;
+ }
+ if ((group_call->flags_ & telegram_api::groupCall::SCHEDULE_DATE_MASK) != 0) {
+ call.scheduled_start_date = group_call->schedule_date_;
+ if (call.scheduled_start_date <= 0) {
+ LOG(ERROR) << "Receive invalid scheduled start date " << group_call->schedule_date_ << " in "
+ << input_group_call_id;
+ call.scheduled_start_date = 0;
+ }
+ } else {
+ call.scheduled_start_date = 0;
+ }
+ if (call.scheduled_start_date == 0) {
+ call.start_subscribed = false;
+ }
+
+ call.version = group_call->version_;
+ call.title_version = group_call->version_;
+ call.can_enable_video_version = group_call->version_;
+ call.start_subscribed_version = group_call->version_;
+ call.mute_version = group_call->version_;
+ call.stream_dc_id_version = group_call->version_;
+ call.record_start_date_version = group_call->version_;
+ call.scheduled_start_date_version = group_call->version_;
+ break;
+ }
+ case telegram_api::groupCallDiscarded::ID: {
+ auto group_call = static_cast<const telegram_api::groupCallDiscarded *>(group_call_ptr.get());
+ input_group_call_id = InputGroupCallId(group_call->id_, group_call->access_hash_);
+ call.duration = group_call->duration_;
+ finish_join_group_call(input_group_call_id, 0, Status::Error(400, "Group call ended"));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ if (!input_group_call_id.is_valid() || call.participant_count < 0) {
+ return {};
+ }
+
+ string join_params = std::move(pending_group_call_join_params_);
+ pending_group_call_join_params_.clear();
+
+ bool need_update = false;
+ auto *group_call = add_group_call(input_group_call_id, dialog_id);
+ call.group_call_id = group_call->group_call_id;
+ call.dialog_id = dialog_id.is_valid() ? dialog_id : group_call->dialog_id;
+ call.can_be_managed = call.is_active && can_manage_group_calls(call.dialog_id).is_ok();
+ call.can_self_unmute = call.is_active && (!call.mute_new_participants || call.can_be_managed);
+ if (!group_call->dialog_id.is_valid()) {
+ group_call->dialog_id = dialog_id;
+ }
+ if (call.is_active && join_params.empty() && !group_call->is_joined &&
+ (group_call->need_rejoin || is_group_call_being_joined(input_group_call_id))) {
+ call.participant_count++;
+ }
+ LOG(INFO) << "Update " << call.group_call_id << " with " << group_call->participant_count
+ << " participants and version " << group_call->version;
+ if (!group_call->is_inited) {
+ call.is_joined = group_call->is_joined;
+ call.need_rejoin = group_call->need_rejoin;
+ call.is_being_left = group_call->is_being_left;
+ call.is_speaking = group_call->is_speaking;
+ call.is_my_video_paused = group_call->is_my_video_paused;
+ call.is_my_video_enabled = group_call->is_my_video_enabled;
+ call.is_my_presentation_paused = group_call->is_my_presentation_paused;
+ call.syncing_participants = group_call->syncing_participants;
+ call.need_syncing_participants = group_call->need_syncing_participants;
+ call.loaded_all_participants = group_call->loaded_all_participants;
+ call.audio_source = group_call->audio_source;
+ call.as_dialog_id = group_call->as_dialog_id;
+ *group_call = std::move(call);
+
+ need_update = true;
+ if (need_group_call_participants(input_group_call_id, group_call)) {
+ if (process_pending_group_call_participant_updates(input_group_call_id)) {
+ need_update = false;
+ }
+ try_load_group_call_administrators(input_group_call_id, group_call->dialog_id);
+ } else {
+ group_call->version = -1;
+ }
+ } else {
+ if (!group_call->is_active) {
+ // never update ended calls
+ } else if (!call.is_active) {
+ // always update to an ended call, droping also is_joined, is_speaking and other local flags
+ fail_promises(group_call->after_join, Status::Error(400, "Group call ended"));
+ *group_call = std::move(call);
+ need_update = true;
+ } else {
+ if (call.is_rtmp_stream != group_call->is_rtmp_stream) {
+ group_call->is_rtmp_stream = call.is_rtmp_stream;
+ need_update = true;
+ }
+ if (call.has_hidden_listeners != group_call->has_hidden_listeners) {
+ group_call->has_hidden_listeners = call.has_hidden_listeners;
+ need_update = true;
+ }
+ if ((call.unmuted_video_count != group_call->unmuted_video_count ||
+ call.unmuted_video_limit != group_call->unmuted_video_limit) &&
+ call.can_enable_video_version >= group_call->can_enable_video_version) {
+ auto old_can_enable_video = get_group_call_can_enable_video(group_call);
+ group_call->unmuted_video_count = call.unmuted_video_count;
+ group_call->unmuted_video_limit = call.unmuted_video_limit;
+ group_call->can_enable_video_version = call.can_enable_video_version;
+ if (old_can_enable_video != get_group_call_can_enable_video(group_call)) {
+ need_update = true;
+ }
+ }
+ if (call.start_subscribed != group_call->start_subscribed &&
+ call.start_subscribed_version >= group_call->start_subscribed_version) {
+ auto old_start_subscribed = get_group_call_start_subscribed(group_call);
+ group_call->start_subscribed = call.start_subscribed;
+ group_call->start_subscribed_version = call.start_subscribed_version;
+ if (old_start_subscribed != get_group_call_start_subscribed(group_call)) {
+ need_update = true;
+ }
+ }
+ auto mute_flags_changed =
+ call.mute_new_participants != group_call->mute_new_participants ||
+ call.allowed_toggle_mute_new_participants != group_call->allowed_toggle_mute_new_participants;
+ if (mute_flags_changed && call.mute_version >= group_call->mute_version) {
+ auto old_mute_new_participants = get_group_call_mute_new_participants(group_call);
+ need_update |= (call.allowed_toggle_mute_new_participants && call.can_be_managed) !=
+ (group_call->allowed_toggle_mute_new_participants && group_call->can_be_managed);
+ group_call->mute_new_participants = call.mute_new_participants;
+ group_call->allowed_toggle_mute_new_participants = call.allowed_toggle_mute_new_participants;
+ group_call->mute_version = call.mute_version;
+ if (old_mute_new_participants != get_group_call_mute_new_participants(group_call)) {
+ need_update = true;
+ }
+ }
+ if (call.title != group_call->title && call.title_version >= group_call->title_version) {
+ string old_group_call_title = get_group_call_title(group_call);
+ group_call->title = std::move(call.title);
+ group_call->title_version = call.title_version;
+ if (old_group_call_title != get_group_call_title(group_call)) {
+ need_update = true;
+ }
+ }
+ if (call.can_be_managed != group_call->can_be_managed) {
+ group_call->can_be_managed = call.can_be_managed;
+ need_update = true;
+ }
+ if (call.stream_dc_id != group_call->stream_dc_id &&
+ call.stream_dc_id_version >= group_call->stream_dc_id_version) {
+ group_call->stream_dc_id = call.stream_dc_id;
+ group_call->stream_dc_id_version = call.stream_dc_id_version;
+ }
+ // flag call.joined_date_asc must not change
+ if ((call.record_start_date != group_call->record_start_date ||
+ call.is_video_recorded != group_call->is_video_recorded) &&
+ call.record_start_date_version >= group_call->record_start_date_version) {
+ int32 old_record_start_date = get_group_call_record_start_date(group_call);
+ bool old_is_video_recorded = get_group_call_is_video_recorded(group_call);
+ group_call->record_start_date = call.record_start_date;
+ group_call->is_video_recorded = call.is_video_recorded;
+ group_call->record_start_date_version = call.record_start_date_version;
+ if (old_record_start_date != get_group_call_record_start_date(group_call) ||
+ old_is_video_recorded != get_group_call_is_video_recorded(group_call)) {
+ need_update = true;
+ }
+ }
+ if (call.scheduled_start_date != group_call->scheduled_start_date &&
+ call.scheduled_start_date_version >= group_call->scheduled_start_date_version) {
+ LOG_IF(ERROR, group_call->scheduled_start_date == 0) << call.group_call_id << " became scheduled";
+ group_call->scheduled_start_date = call.scheduled_start_date;
+ group_call->scheduled_start_date_version = call.scheduled_start_date_version;
+ need_update = true;
+ }
+ if (call.version > group_call->version) {
+ if (group_call->version != -1) {
+ // if we know group call version, then update participants only by corresponding updates
+ on_receive_group_call_version(input_group_call_id, call.version);
+ } else {
+ need_update |= set_group_call_participant_count(group_call, call.participant_count, "update_group_call");
+ if (need_group_call_participants(input_group_call_id, group_call) && !join_params.empty() &&
+ group_call->version == -1) {
+ LOG(INFO) << "Init " << call.group_call_id << " version to " << call.version;
+ group_call->version = call.version;
+ if (process_pending_group_call_participant_updates(input_group_call_id)) {
+ need_update = false;
+ }
+ }
+ }
+ } else if (call.version == group_call->version) {
+ set_group_call_participant_count(group_call, call.participant_count, "update_group_call fix");
+ need_update = true;
+ }
+ }
+ }
+ if (!group_call->is_active && group_call_recent_speakers_.erase(group_call->group_call_id) != 0) {
+ need_update = true;
+ }
+ if (!join_params.empty()) {
+ need_update |= on_join_group_call_response(input_group_call_id, std::move(join_params));
+ }
+ update_group_call_dialog(group_call, "update_group_call", false); // must be after join response is processed
+ need_update |= try_clear_group_call_participants(input_group_call_id);
+ if (need_update) {
+ send_update_group_call(group_call, "update_group_call");
+ }
+ return input_group_call_id;
+}
+
+void GroupCallManager::on_receive_group_call_version(InputGroupCallId input_group_call_id, int32 version,
+ bool immediate_sync) {
+ auto *group_call = get_group_call(input_group_call_id);
+ if (!need_group_call_participants(input_group_call_id, group_call)) {
+ return;
+ }
+ CHECK(group_call != nullptr && group_call->is_inited);
+ if (group_call->version == -1) {
+ return;
+ }
+ if (version <= group_call->version) {
+ return;
+ }
+ if (group_call->syncing_participants) {
+ return;
+ }
+
+ // found a gap
+ LOG(INFO) << "Receive version " << version << " for group call " << input_group_call_id;
+ auto *group_call_participants = add_group_call_participants(input_group_call_id);
+ group_call_participants->pending_version_updates_[version]; // reserve place for updates
+ if (immediate_sync) {
+ sync_participants_timeout_.set_timeout_in(group_call->group_call_id.get(), 0.0);
+ } else {
+ sync_participants_timeout_.add_timeout_in(group_call->group_call_id.get(), 1.0);
+ }
+}
+
+void GroupCallManager::on_participant_speaking_in_group_call(InputGroupCallId input_group_call_id,
+ const GroupCallParticipant &participant) {
+ auto active_date = td::max(participant.active_date, participant.joined_date - 60);
+ if (active_date < G()->unix_time() - RECENT_SPEAKER_TIMEOUT) {
+ return;
+ }
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr) {
+ return;
+ }
+
+ on_user_speaking_in_group_call(group_call->group_call_id, participant.dialog_id, active_date, !participant.is_min);
+}
+
+void GroupCallManager::on_user_speaking_in_group_call(GroupCallId group_call_id, DialogId dialog_id, int32 date,
+ bool is_recursive) {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (date < G()->unix_time() - RECENT_SPEAKER_TIMEOUT) {
+ return;
+ }
+
+ auto input_group_call_id = get_input_group_call_id(group_call_id).move_as_ok();
+
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call != nullptr && group_call->is_inited && !group_call->is_active) {
+ return;
+ }
+
+ if (!td_->messages_manager_->have_dialog_info_force(dialog_id) ||
+ (!is_recursive && need_group_call_participants(input_group_call_id, group_call) &&
+ get_group_call_participant(input_group_call_id, dialog_id) == nullptr)) {
+ if (is_recursive) {
+ LOG(ERROR) << "Failed to find speaking " << dialog_id << " from " << input_group_call_id;
+ } else {
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), group_call_id, dialog_id, date](Result<Unit> &&result) {
+ if (!G()->close_flag() && result.is_ok()) {
+ send_closure(actor_id, &GroupCallManager::on_user_speaking_in_group_call, group_call_id, dialog_id, date,
+ true);
+ }
+ });
+ vector<tl_object_ptr<telegram_api::InputPeer>> input_peers;
+ input_peers.push_back(MessagesManager::get_input_peer_force(dialog_id));
+ td_->create_handler<GetGroupCallParticipantQuery>(std::move(query_promise))
+ ->send(input_group_call_id, std::move(input_peers), {});
+ }
+ return;
+ }
+
+ LOG(INFO) << "Add " << dialog_id << " as recent speaker at " << date << " in " << input_group_call_id;
+ auto &recent_speakers = group_call_recent_speakers_[group_call_id];
+ if (recent_speakers == nullptr) {
+ recent_speakers = make_unique<GroupCallRecentSpeakers>();
+ }
+
+ for (size_t i = 0; i < recent_speakers->users.size(); i++) {
+ if (recent_speakers->users[i].first == dialog_id) {
+ if (recent_speakers->users[i].second >= date) {
+ LOG(INFO) << "Ignore outdated speaking information";
+ return;
+ }
+ recent_speakers->users[i].second = date;
+ while (i > 0 && recent_speakers->users[i - 1].second < date) {
+ std::swap(recent_speakers->users[i - 1], recent_speakers->users[i]);
+ i--;
+ }
+ on_group_call_recent_speakers_updated(group_call, recent_speakers.get());
+ return;
+ }
+ }
+
+ for (size_t i = 0; i <= recent_speakers->users.size(); i++) {
+ if (i == recent_speakers->users.size() || recent_speakers->users[i].second <= date) {
+ if (dialog_id.get_type() != DialogType::User) {
+ td_->messages_manager_->force_create_dialog(dialog_id, "on_user_speaking_in_group_call", true);
+ }
+ recent_speakers->users.insert(recent_speakers->users.begin() + i, {dialog_id, date});
+ break;
+ }
+ }
+ static constexpr size_t MAX_RECENT_SPEAKERS = 3;
+ if (recent_speakers->users.size() > MAX_RECENT_SPEAKERS) {
+ recent_speakers->users.pop_back();
+ }
+
+ on_group_call_recent_speakers_updated(group_call, recent_speakers.get());
+}
+
+void GroupCallManager::remove_recent_group_call_speaker(InputGroupCallId input_group_call_id, DialogId dialog_id) {
+ auto *group_call = get_group_call(input_group_call_id);
+ if (group_call == nullptr) {
+ return;
+ }
+
+ auto recent_speakers_it = group_call_recent_speakers_.find(group_call->group_call_id);
+ if (recent_speakers_it == group_call_recent_speakers_.end()) {
+ return;
+ }
+ auto &recent_speakers = recent_speakers_it->second;
+ CHECK(recent_speakers != nullptr);
+ for (size_t i = 0; i < recent_speakers->users.size(); i++) {
+ if (recent_speakers->users[i].first == dialog_id) {
+ LOG(INFO) << "Remove " << dialog_id << " from recent speakers in " << input_group_call_id << " from "
+ << group_call->dialog_id;
+ recent_speakers->users.erase(recent_speakers->users.begin() + i);
+ on_group_call_recent_speakers_updated(group_call, recent_speakers.get());
+ return;
+ }
+ }
+}
+
+void GroupCallManager::on_group_call_recent_speakers_updated(const GroupCall *group_call,
+ GroupCallRecentSpeakers *recent_speakers) {
+ if (group_call == nullptr || !group_call->is_inited || recent_speakers->is_changed) {
+ if (group_call != nullptr) {
+ LOG(INFO) << "Don't need to send update of recent speakers in " << group_call->group_call_id << " from "
+ << group_call->dialog_id;
+ }
+ return;
+ }
+
+ recent_speakers->is_changed = true;
+
+ LOG(INFO) << "Schedule update of recent speakers in " << group_call->group_call_id << " from "
+ << group_call->dialog_id;
+ const double MAX_RECENT_SPEAKER_UPDATE_DELAY = 0.5;
+ recent_speaker_update_timeout_.set_timeout_in(group_call->group_call_id.get(), MAX_RECENT_SPEAKER_UPDATE_DELAY);
+}
+
+DialogId GroupCallManager::set_group_call_participant_is_speaking_by_source(InputGroupCallId input_group_call_id,
+ int32 audio_source, bool is_speaking,
+ int32 date) {
+ CHECK(audio_source != 0);
+ auto participants_it = group_call_participants_.find(input_group_call_id);
+ if (participants_it == group_call_participants_.end()) {
+ return DialogId();
+ }
+
+ for (auto &participant : participants_it->second->participants) {
+ if (participant.audio_source == audio_source || participant.presentation_audio_source == audio_source) {
+ if (is_speaking && participant.get_is_muted_by_admin()) {
+ // don't allow to show as speaking muted by admin participants
+ return DialogId();
+ }
+ if (participant.is_speaking != is_speaking) {
+ participant.is_speaking = is_speaking;
+ if (is_speaking) {
+ participant.local_active_date = max(participant.local_active_date, date);
+ }
+ bool can_self_unmute = get_group_call_can_self_unmute(input_group_call_id);
+ auto old_order = participant.order;
+ participant.order = get_real_participant_order(can_self_unmute, participant, participants_it->second.get());
+ if (participant.order.is_valid() || old_order.is_valid()) {
+ send_update_group_call_participant(input_group_call_id, participant,
+ "set_group_call_participant_is_speaking_by_source");
+ }
+ }
+
+ return participant.dialog_id;
+ }
+ }
+ return DialogId();
+}
+
+bool GroupCallManager::set_group_call_participant_count(GroupCall *group_call, int32 count, const char *source,
+ bool force_update) {
+ CHECK(group_call != nullptr);
+ CHECK(group_call->is_inited);
+ if (group_call->participant_count == count) {
+ return false;
+ }
+
+ LOG(DEBUG) << "Set " << group_call->group_call_id << " participant count to " << count << " from " << source;
+ auto input_group_call_id = get_input_group_call_id(group_call->group_call_id).ok();
+ if (count < 0) {
+ LOG(ERROR) << "Participant count became negative in " << group_call->group_call_id << " in "
+ << group_call->dialog_id << " from " << source;
+ count = 0;
+ reload_group_call(input_group_call_id, Auto());
+ }
+
+ bool result = false;
+ if (need_group_call_participants(input_group_call_id, group_call)) {
+ auto known_participant_count =
+ static_cast<int32>(add_group_call_participants(input_group_call_id)->participants.size());
+ if (count < known_participant_count) {
+ if (group_call->is_joined) {
+ LOG(ERROR) << "Participant count became " << count << " from " << source << ", which is less than known "
+ << known_participant_count << " number of participants in " << input_group_call_id << " from "
+ << group_call->dialog_id;
+ }
+ count = known_participant_count;
+ } else if (group_call->loaded_all_participants && !group_call->has_hidden_listeners &&
+ count > known_participant_count) {
+ if (group_call->joined_date_asc) {
+ group_call->loaded_all_participants = false;
+ result = true;
+ } else {
+ count = known_participant_count;
+ }
+ }
+ }
+
+ if (group_call->participant_count == count) {
+ return result;
+ }
+
+ group_call->participant_count = count;
+ update_group_call_dialog(group_call, source, force_update);
+ return true;
+}
+
+bool GroupCallManager::set_group_call_unmuted_video_count(GroupCall *group_call, int32 count, const char *source) {
+ CHECK(group_call != nullptr);
+ CHECK(group_call->is_inited);
+
+ auto participants_it = group_call_participants_.find(get_input_group_call_id(group_call->group_call_id).ok());
+ if (participants_it != group_call_participants_.end()) {
+ auto group_call_participants = participants_it->second.get();
+ CHECK(group_call_participants != nullptr);
+ CHECK(group_call_participants->local_unmuted_video_count >= 0);
+ CHECK(static_cast<size_t>(group_call_participants->local_unmuted_video_count) <=
+ group_call_participants->participants.size());
+ if (group_call->loaded_all_participants || !group_call_participants->min_order.has_video()) {
+ if (group_call_participants->local_unmuted_video_count != count &&
+ group_call->unmuted_video_count != group_call_participants->local_unmuted_video_count) {
+ LOG(INFO) << "Use local count " << group_call_participants->local_unmuted_video_count
+ << " of unmuted videos instead of " << count;
+ }
+ count = group_call_participants->local_unmuted_video_count;
+ }
+ }
+
+ if (count < 0) {
+ LOG(ERROR) << "Video participant count became negative in " << group_call->group_call_id << " in "
+ << group_call->dialog_id << " from " << source;
+ count = 0;
+ auto input_group_call_id = get_input_group_call_id(group_call->group_call_id).ok();
+ reload_group_call(input_group_call_id, Auto());
+ }
+
+ if (group_call->unmuted_video_count == count) {
+ return false;
+ }
+
+ LOG(DEBUG) << "Set " << group_call->group_call_id << " video participant count to " << count << " from " << source;
+
+ auto old_can_enable_video = get_group_call_can_enable_video(group_call);
+ group_call->unmuted_video_count = count;
+ return old_can_enable_video != get_group_call_can_enable_video(group_call);
+}
+
+void GroupCallManager::update_group_call_dialog(const GroupCall *group_call, const char *source, bool force) {
+ CHECK(group_call != nullptr);
+ if (!group_call->dialog_id.is_valid()) {
+ return;
+ }
+
+ td_->messages_manager_->on_update_dialog_group_call(group_call->dialog_id, group_call->is_active,
+ group_call->participant_count == 0, source, force);
+}
+
+vector<td_api::object_ptr<td_api::groupCallRecentSpeaker>> GroupCallManager::get_recent_speakers(
+ const GroupCall *group_call, bool for_update) {
+ CHECK(group_call != nullptr && group_call->is_inited);
+
+ auto recent_speakers_it = group_call_recent_speakers_.find(group_call->group_call_id);
+ if (recent_speakers_it == group_call_recent_speakers_.end()) {
+ return Auto();
+ }
+
+ auto *recent_speakers = recent_speakers_it->second.get();
+ CHECK(recent_speakers != nullptr);
+ LOG(INFO) << "Found " << recent_speakers->users.size() << " recent speakers in " << group_call->group_call_id
+ << " from " << group_call->dialog_id;
+ auto now = G()->unix_time();
+ while (!recent_speakers->users.empty() && recent_speakers->users.back().second < now - RECENT_SPEAKER_TIMEOUT) {
+ recent_speakers->users.pop_back();
+ }
+
+ vector<std::pair<DialogId, bool>> recent_speaker_users;
+ for (auto &recent_speaker : recent_speakers->users) {
+ recent_speaker_users.emplace_back(recent_speaker.first, recent_speaker.second > now - 8);
+ }
+
+ if (recent_speakers->is_changed) {
+ recent_speakers->is_changed = false;
+ recent_speaker_update_timeout_.cancel_timeout(group_call->group_call_id.get());
+ }
+ if (!recent_speaker_users.empty()) {
+ auto next_timeout = recent_speakers->users.back().second + RECENT_SPEAKER_TIMEOUT - now + 1;
+ if (recent_speaker_users[0].second) { // if someone is speaking, recheck in 1 second
+ next_timeout = 1;
+ }
+ recent_speaker_update_timeout_.add_timeout_in(group_call->group_call_id.get(), next_timeout);
+ }
+
+ auto get_result = [recent_speaker_users, td = td_] {
+ return transform(recent_speaker_users, [td](const std::pair<DialogId, bool> &recent_speaker_user) {
+ return td_api::make_object<td_api::groupCallRecentSpeaker>(
+ get_message_sender_object(td, recent_speaker_user.first, "get_recent_speakers"), recent_speaker_user.second);
+ });
+ };
+ if (recent_speakers->last_sent_users != recent_speaker_users) {
+ recent_speakers->last_sent_users = std::move(recent_speaker_users);
+
+ if (!for_update) {
+ // the change must be received through update first
+ send_closure(G()->td(), &Td::send_update, get_update_group_call_object(group_call, get_result()));
+ }
+ }
+
+ return get_result();
+}
+
+tl_object_ptr<td_api::groupCall> GroupCallManager::get_group_call_object(
+ const GroupCall *group_call, vector<td_api::object_ptr<td_api::groupCallRecentSpeaker>> recent_speakers) {
+ CHECK(group_call != nullptr);
+ CHECK(group_call->is_inited);
+
+ int32 scheduled_start_date = group_call->scheduled_start_date;
+ bool is_active = scheduled_start_date == 0 ? group_call->is_active : false;
+ bool is_joined = group_call->is_joined && !group_call->is_being_left;
+ bool start_subscribed = get_group_call_start_subscribed(group_call);
+ bool is_my_video_enabled = get_group_call_is_my_video_enabled(group_call);
+ bool is_my_video_paused = is_my_video_enabled && get_group_call_is_my_video_paused(group_call);
+ bool mute_new_participants = get_group_call_mute_new_participants(group_call);
+ bool can_toggle_mute_new_participants =
+ group_call->is_active && group_call->can_be_managed && group_call->allowed_toggle_mute_new_participants;
+ bool can_enable_video = get_group_call_can_enable_video(group_call);
+ int32 record_start_date = get_group_call_record_start_date(group_call);
+ int32 record_duration = record_start_date == 0 ? 0 : max(G()->unix_time() - record_start_date + 1, 1);
+ bool is_video_recorded = get_group_call_is_video_recorded(group_call);
+ return td_api::make_object<td_api::groupCall>(
+ group_call->group_call_id.get(), get_group_call_title(group_call), scheduled_start_date, start_subscribed,
+ is_active, group_call->is_rtmp_stream, is_joined, group_call->need_rejoin, group_call->can_be_managed,
+ group_call->participant_count, group_call->has_hidden_listeners, group_call->loaded_all_participants,
+ std::move(recent_speakers), is_my_video_enabled, is_my_video_paused, can_enable_video, mute_new_participants,
+ can_toggle_mute_new_participants, record_duration, is_video_recorded, group_call->duration);
+}
+
+tl_object_ptr<td_api::updateGroupCall> GroupCallManager::get_update_group_call_object(
+ const GroupCall *group_call, vector<td_api::object_ptr<td_api::groupCallRecentSpeaker>> recent_speakers) {
+ return td_api::make_object<td_api::updateGroupCall>(get_group_call_object(group_call, std::move(recent_speakers)));
+}
+
+tl_object_ptr<td_api::updateGroupCallParticipant> GroupCallManager::get_update_group_call_participant_object(
+ GroupCallId group_call_id, const GroupCallParticipant &participant) {
+ return td_api::make_object<td_api::updateGroupCallParticipant>(group_call_id.get(),
+ participant.get_group_call_participant_object(td_));
+}
+
+void GroupCallManager::send_update_group_call(const GroupCall *group_call, const char *source) {
+ LOG(INFO) << "Send update about " << group_call->group_call_id << " from " << source;
+ send_closure(G()->td(), &Td::send_update,
+ get_update_group_call_object(group_call, get_recent_speakers(group_call, true)));
+}
+
+void GroupCallManager::send_update_group_call_participant(GroupCallId group_call_id,
+ const GroupCallParticipant &participant, const char *source) {
+ LOG(INFO) << "Send update about " << participant << " in " << group_call_id << " from " << source;
+ send_closure(G()->td(), &Td::send_update, get_update_group_call_participant_object(group_call_id, participant));
+}
+
+void GroupCallManager::send_update_group_call_participant(InputGroupCallId input_group_call_id,
+ const GroupCallParticipant &participant, const char *source) {
+ auto group_call = get_group_call(input_group_call_id);
+ CHECK(group_call != nullptr && group_call->is_inited);
+ send_update_group_call_participant(group_call->group_call_id, participant, source);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GroupCallManager.h b/protocols/Telegram/tdlib/td/td/telegram/GroupCallManager.h
new file mode 100644
index 0000000000..4da12359c7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GroupCallManager.h
@@ -0,0 +1,427 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/GroupCallId.h"
+#include "td/telegram/GroupCallParticipant.h"
+#include "td/telegram/GroupCallParticipantOrder.h"
+#include "td/telegram/InputGroupCallId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/MultiTimeout.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+#include <utility>
+
+namespace td {
+
+class Td;
+
+class GroupCallManager final : public Actor {
+ public:
+ GroupCallManager(Td *td, ActorShared<> parent);
+ GroupCallManager(const GroupCallManager &) = delete;
+ GroupCallManager &operator=(const GroupCallManager &) = delete;
+ GroupCallManager(GroupCallManager &&) = delete;
+ GroupCallManager &operator=(GroupCallManager &&) = delete;
+ ~GroupCallManager() final;
+
+ bool is_group_call_being_joined(InputGroupCallId input_group_call_id) const;
+
+ bool is_group_call_joined(InputGroupCallId input_group_call_id) const;
+
+ GroupCallId get_group_call_id(InputGroupCallId input_group_call_id, DialogId dialog_id);
+
+ void get_group_call_join_as(DialogId dialog_id, Promise<td_api::object_ptr<td_api::messageSenders>> &&promise);
+
+ void set_group_call_default_join_as(DialogId dialog_id, DialogId as_dialog_id, Promise<Unit> &&promise);
+
+ void create_voice_chat(DialogId dialog_id, string title, int32 start_date, bool is_rtmp_stream,
+ Promise<GroupCallId> &&promise);
+
+ void get_voice_chat_rtmp_stream_url(DialogId dialog_id, bool revoke,
+ Promise<td_api::object_ptr<td_api::rtmpUrl>> &&promise);
+
+ void get_group_call(GroupCallId group_call_id, Promise<td_api::object_ptr<td_api::groupCall>> &&promise);
+
+ void on_update_group_call_rights(InputGroupCallId input_group_call_id);
+
+ void reload_group_call(InputGroupCallId input_group_call_id,
+ Promise<td_api::object_ptr<td_api::groupCall>> &&promise);
+
+ void get_group_call_streams(GroupCallId group_call_id,
+ Promise<td_api::object_ptr<td_api::groupCallStreams>> &&promise);
+
+ void get_group_call_stream_segment(GroupCallId group_call_id, int64 time_offset, int32 scale, int32 channel_id,
+ td_api::object_ptr<td_api::GroupCallVideoQuality> quality,
+ Promise<string> &&promise);
+
+ void start_scheduled_group_call(GroupCallId group_call_id, Promise<Unit> &&promise);
+
+ void join_group_call(GroupCallId group_call_id, DialogId as_dialog_id, int32 audio_source, string &&payload,
+ bool is_muted, bool is_my_video_enabled, const string &invite_hash, Promise<string> &&promise);
+
+ void start_group_call_screen_sharing(GroupCallId group_call_id, int32 audio_source, string &&payload,
+ Promise<string> &&promise);
+
+ void end_group_call_screen_sharing(GroupCallId group_call_id, Promise<Unit> &&promise);
+
+ void set_group_call_title(GroupCallId group_call_id, string title, Promise<Unit> &&promise);
+
+ void toggle_group_call_is_my_video_paused(GroupCallId group_call_id, bool is_my_video_paused,
+ Promise<Unit> &&promise);
+
+ void toggle_group_call_is_my_video_enabled(GroupCallId group_call_id, bool is_my_video_enabled,
+ Promise<Unit> &&promise);
+
+ void toggle_group_call_is_my_presentation_paused(GroupCallId group_call_id, bool is_my_presentation_paused,
+ Promise<Unit> &&promise);
+
+ void toggle_group_call_start_subscribed(GroupCallId group_call_id, bool start_subscribed, Promise<Unit> &&promise);
+
+ void toggle_group_call_mute_new_participants(GroupCallId group_call_id, bool mute_new_participants,
+ Promise<Unit> &&promise);
+
+ void revoke_group_call_invite_link(GroupCallId group_call_id, Promise<Unit> &&promise);
+
+ void invite_group_call_participants(GroupCallId group_call_id, vector<UserId> &&user_ids, Promise<Unit> &&promise);
+
+ void get_group_call_invite_link(GroupCallId group_call_id, bool can_self_unmute, Promise<string> &&promise);
+
+ void toggle_group_call_recording(GroupCallId group_call_id, bool is_enabled, string title, bool record_video,
+ bool use_portrait_orientation, Promise<Unit> &&promise);
+
+ void set_group_call_participant_is_speaking(GroupCallId group_call_id, int32 audio_source, bool is_speaking,
+ Promise<Unit> &&promise, int32 date = 0);
+
+ void toggle_group_call_participant_is_muted(GroupCallId group_call_id, DialogId dialog_id, bool is_muted,
+ Promise<Unit> &&promise);
+
+ void set_group_call_participant_volume_level(GroupCallId group_call_id, DialogId dialog_id, int32 volume_level,
+ Promise<Unit> &&promise);
+
+ void toggle_group_call_participant_is_hand_raised(GroupCallId group_call_id, DialogId dialog_id, bool is_hand_raised,
+ Promise<Unit> &&promise);
+
+ void load_group_call_participants(GroupCallId group_call_id, int32 limit, Promise<Unit> &&promise);
+
+ void leave_group_call(GroupCallId group_call_id, Promise<Unit> &&promise);
+
+ void discard_group_call(GroupCallId group_call_id, Promise<Unit> &&promise);
+
+ void on_update_dialog_about(DialogId dialog_id, const string &about, bool from_server);
+
+ void on_update_group_call_connection(string &&connection_params);
+
+ void on_update_group_call(tl_object_ptr<telegram_api::GroupCall> group_call_ptr, DialogId dialog_id);
+
+ void on_user_speaking_in_group_call(GroupCallId group_call_id, DialogId dialog_id, int32 date,
+ bool is_recursive = false);
+
+ void on_get_group_call_participants(InputGroupCallId input_group_call_id,
+ tl_object_ptr<telegram_api::phone_groupParticipants> &&participants, bool is_load,
+ const string &offset);
+
+ void on_update_group_call_participants(InputGroupCallId input_group_call_id,
+ vector<tl_object_ptr<telegram_api::groupCallParticipant>> &&participants,
+ int32 version, bool is_recursive = false);
+
+ void process_join_group_call_response(InputGroupCallId input_group_call_id, uint64 generation,
+ tl_object_ptr<telegram_api::Updates> &&updates, Promise<Unit> &&promise);
+
+ void process_join_group_call_presentation_response(InputGroupCallId input_group_call_id, uint64 generation,
+ tl_object_ptr<telegram_api::Updates> &&updates, Status status);
+
+ private:
+ struct GroupCall;
+ struct GroupCallParticipants;
+ struct GroupCallRecentSpeakers;
+ struct PendingJoinRequest;
+
+ static constexpr int32 RECENT_SPEAKER_TIMEOUT = 60 * 60;
+ static constexpr int32 UPDATE_GROUP_CALL_PARTICIPANT_ORDER_TIMEOUT = 10;
+ static constexpr int32 CHECK_GROUP_CALL_IS_JOINED_TIMEOUT = 10;
+ static constexpr size_t MAX_TITLE_LENGTH = 64; // server side limit for group call/call record title length
+
+ void tear_down() final;
+
+ static void on_update_group_call_participant_order_timeout_callback(void *group_call_manager_ptr,
+ int64 group_call_id_int);
+
+ void on_update_group_call_participant_order_timeout(GroupCallId group_call_id);
+
+ static void on_check_group_call_is_joined_timeout_callback(void *group_call_manager_ptr, int64 group_call_id_int);
+
+ void on_check_group_call_is_joined_timeout(GroupCallId group_call_id);
+
+ static void on_pending_send_speaking_action_timeout_callback(void *group_call_manager_ptr, int64 group_call_id_int);
+
+ void on_send_speaking_action_timeout(GroupCallId group_call_id);
+
+ static void on_recent_speaker_update_timeout_callback(void *group_call_manager_ptr, int64 group_call_id_int);
+
+ void on_recent_speaker_update_timeout(GroupCallId group_call_id);
+
+ static void on_sync_participants_timeout_callback(void *group_call_manager_ptr, int64 group_call_id_int);
+
+ void on_sync_participants_timeout(GroupCallId group_call_id);
+
+ Result<InputGroupCallId> get_input_group_call_id(GroupCallId group_call_id);
+
+ GroupCallId get_next_group_call_id(InputGroupCallId input_group_call_id);
+
+ GroupCall *add_group_call(InputGroupCallId input_group_call_id, DialogId dialog_id);
+
+ const GroupCall *get_group_call(InputGroupCallId input_group_call_id) const;
+ GroupCall *get_group_call(InputGroupCallId input_group_call_id);
+
+ Status can_manage_group_calls(DialogId dialog_id) const;
+
+ bool can_manage_group_call(InputGroupCallId input_group_call_id) const;
+
+ bool get_group_call_can_self_unmute(InputGroupCallId input_group_call_id) const;
+
+ bool get_group_call_joined_date_asc(InputGroupCallId input_group_call_id) const;
+
+ void on_voice_chat_created(DialogId dialog_id, InputGroupCallId input_group_call_id, Promise<GroupCallId> &&promise);
+
+ void finish_get_group_call(InputGroupCallId input_group_call_id,
+ Result<tl_object_ptr<telegram_api::phone_groupCall>> &&result);
+
+ void finish_get_group_call_streams(InputGroupCallId input_group_call_id, int32 audio_source,
+ Result<td_api::object_ptr<td_api::groupCallStreams>> &&result,
+ Promise<td_api::object_ptr<td_api::groupCallStreams>> &&promise);
+
+ void finish_get_group_call_stream_segment(InputGroupCallId input_group_call_id, int32 audio_source,
+ Result<string> &&result, Promise<string> &&promise);
+
+ void finish_check_group_call_is_joined(InputGroupCallId input_group_call_id, int32 audio_source,
+ Result<Unit> &&result);
+
+ static const string &get_group_call_title(const GroupCall *group_call);
+
+ static bool get_group_call_start_subscribed(const GroupCall *group_call);
+
+ static bool get_group_call_is_my_video_paused(const GroupCall *group_call);
+
+ static bool get_group_call_is_my_video_enabled(const GroupCall *group_call);
+
+ static bool get_group_call_is_my_presentation_paused(const GroupCall *group_call);
+
+ static bool get_group_call_mute_new_participants(const GroupCall *group_call);
+
+ static int32 get_group_call_record_start_date(const GroupCall *group_call);
+
+ static bool get_group_call_is_video_recorded(const GroupCall *group_call);
+
+ static bool get_group_call_has_recording(const GroupCall *group_call);
+
+ static bool get_group_call_can_enable_video(const GroupCall *group_call);
+
+ bool need_group_call_participants(InputGroupCallId input_group_call_id) const;
+
+ bool need_group_call_participants(InputGroupCallId input_group_call_id, const GroupCall *group_call) const;
+
+ bool process_pending_group_call_participant_updates(InputGroupCallId input_group_call_id);
+
+ void sync_group_call_participants(InputGroupCallId input_group_call_id);
+
+ void on_sync_group_call_participants(InputGroupCallId input_group_call_id,
+ Result<tl_object_ptr<telegram_api::phone_groupCall>> &&result);
+
+ static GroupCallParticipantOrder get_real_participant_order(bool can_self_unmute,
+ const GroupCallParticipant &participant,
+ const GroupCallParticipants *participants);
+
+ void process_my_group_call_participant(InputGroupCallId input_group_call_id, GroupCallParticipant &&participant);
+
+ void process_group_call_participants(InputGroupCallId group_call_id,
+ vector<tl_object_ptr<telegram_api::groupCallParticipant>> &&participants,
+ int32 version, const string &offset, bool is_load, bool is_sync);
+
+ static bool update_group_call_participant_can_be_muted(bool can_manage, const GroupCallParticipants *participants,
+ GroupCallParticipant &participant);
+
+ void update_group_call_participants_can_be_muted(InputGroupCallId input_group_call_id, bool can_manage,
+ GroupCallParticipants *participants);
+
+ void update_group_call_participants_order(InputGroupCallId input_group_call_id, bool can_self_unmute,
+ GroupCallParticipants *participants, const char *source);
+
+ // returns participant_count_diff and video_participant_count_diff
+ std::pair<int32, int32> process_group_call_participant(InputGroupCallId group_call_id,
+ GroupCallParticipant &&participant);
+
+ void on_add_group_call_participant(InputGroupCallId input_group_call_id, DialogId participant_dialog_id);
+
+ void on_remove_group_call_participant(InputGroupCallId input_group_call_id, DialogId participant_dialog_id);
+
+ void try_load_group_call_administrators(InputGroupCallId input_group_call_id, DialogId dialog_id);
+
+ void finish_load_group_call_administrators(InputGroupCallId input_group_call_id, Result<DialogParticipants> &&result);
+
+ int32 cancel_join_group_call_request(InputGroupCallId input_group_call_id);
+
+ int32 cancel_join_group_call_presentation_request(InputGroupCallId input_group_call_id);
+
+ bool on_join_group_call_response(InputGroupCallId input_group_call_id, string json_response);
+
+ void finish_join_group_call(InputGroupCallId input_group_call_id, uint64 generation, Status error);
+
+ void process_group_call_after_join_requests(InputGroupCallId input_group_call_id, const char *source);
+
+ GroupCallParticipants *add_group_call_participants(InputGroupCallId input_group_call_id);
+
+ GroupCallParticipant *get_group_call_participant(InputGroupCallId input_group_call_id, DialogId dialog_id);
+
+ GroupCallParticipant *get_group_call_participant(GroupCallParticipants *group_call_participants,
+ DialogId dialog_id) const;
+
+ void send_edit_group_call_title_query(InputGroupCallId input_group_call_id, const string &title);
+
+ void on_edit_group_call_title(InputGroupCallId input_group_call_id, const string &title, Result<Unit> &&result);
+
+ void send_toggle_group_call_start_subscription_query(InputGroupCallId input_group_call_id, bool start_subscribed);
+
+ void on_toggle_group_call_start_subscription(InputGroupCallId input_group_call_id, bool start_subscribed,
+ Result<Unit> &&result);
+
+ void send_toggle_group_call_is_my_video_paused_query(InputGroupCallId input_group_call_id, DialogId as_dialog_id,
+ bool is_my_video_paused);
+
+ void on_toggle_group_call_is_my_video_paused(InputGroupCallId input_group_call_id, bool is_my_video_paused,
+ Result<Unit> &&result);
+
+ void send_toggle_group_call_is_my_video_enabled_query(InputGroupCallId input_group_call_id, DialogId as_dialog_id,
+ bool is_my_video_enabled);
+
+ void on_toggle_group_call_is_my_video_enabled(InputGroupCallId input_group_call_id, bool is_my_video_enabled,
+ Result<Unit> &&result);
+
+ void send_toggle_group_call_is_my_presentation_paused_query(InputGroupCallId input_group_call_id,
+ DialogId as_dialog_id, bool is_my_presentation_paused);
+
+ void on_toggle_group_call_is_my_presentation_paused(InputGroupCallId input_group_call_id,
+ bool is_my_presentation_paused, Result<Unit> &&result);
+
+ void send_toggle_group_call_mute_new_participants_query(InputGroupCallId input_group_call_id,
+ bool mute_new_participants);
+
+ void on_toggle_group_call_mute_new_participants(InputGroupCallId input_group_call_id, bool mute_new_participants,
+ Result<Unit> &&result);
+
+ void send_toggle_group_call_recording_query(InputGroupCallId input_group_call_id, bool is_enabled,
+ const string &title, bool record_video, bool use_portrait_orientation,
+ uint64 generation);
+
+ void on_toggle_group_call_recording(InputGroupCallId input_group_call_id, uint64 generation, Result<Unit> &&result);
+
+ void on_toggle_group_call_participant_is_muted(InputGroupCallId input_group_call_id, DialogId dialog_id,
+ uint64 generation, Promise<Unit> &&promise);
+
+ void on_set_group_call_participant_volume_level(InputGroupCallId input_group_call_id, DialogId dialog_id,
+ uint64 generation, Promise<Unit> &&promise);
+
+ void on_toggle_group_call_participant_is_hand_raised(InputGroupCallId input_group_call_id, DialogId dialog_id,
+ uint64 generation, Promise<Unit> &&promise);
+
+ void on_group_call_left(InputGroupCallId input_group_call_id, int32 audio_source, bool need_rejoin);
+
+ void on_group_call_left_impl(GroupCall *group_call, bool need_rejoin, const char *source);
+
+ InputGroupCallId update_group_call(const tl_object_ptr<telegram_api::GroupCall> &group_call_ptr, DialogId dialog_id);
+
+ void on_receive_group_call_version(InputGroupCallId input_group_call_id, int32 version, bool immediate_sync = false);
+
+ void on_participant_speaking_in_group_call(InputGroupCallId input_group_call_id,
+ const GroupCallParticipant &participant);
+
+ void remove_recent_group_call_speaker(InputGroupCallId input_group_call_id, DialogId dialog_id);
+
+ void on_group_call_recent_speakers_updated(const GroupCall *group_call, GroupCallRecentSpeakers *recent_speakers);
+
+ DialogId set_group_call_participant_is_speaking_by_source(InputGroupCallId input_group_call_id, int32 audio_source,
+ bool is_speaking, int32 date);
+
+ bool try_clear_group_call_participants(InputGroupCallId input_group_call_id);
+
+ bool set_group_call_participant_count(GroupCall *group_call, int32 count, const char *source,
+ bool force_update = false);
+
+ bool set_group_call_unmuted_video_count(GroupCall *group_call, int32 count, const char *source);
+
+ void update_group_call_dialog(const GroupCall *group_call, const char *source, bool force);
+
+ vector<td_api::object_ptr<td_api::groupCallRecentSpeaker>> get_recent_speakers(const GroupCall *group_call,
+ bool for_update);
+
+ static tl_object_ptr<td_api::updateGroupCall> get_update_group_call_object(
+ const GroupCall *group_call, vector<td_api::object_ptr<td_api::groupCallRecentSpeaker>> recent_speakers);
+
+ static tl_object_ptr<td_api::groupCall> get_group_call_object(
+ const GroupCall *group_call, vector<td_api::object_ptr<td_api::groupCallRecentSpeaker>> recent_speakers);
+
+ tl_object_ptr<td_api::updateGroupCallParticipant> get_update_group_call_participant_object(
+ GroupCallId group_call_id, const GroupCallParticipant &participant);
+
+ void send_update_group_call(const GroupCall *group_call, const char *source);
+
+ void send_update_group_call_participant(GroupCallId group_call_id, const GroupCallParticipant &participant,
+ const char *source);
+
+ void send_update_group_call_participant(InputGroupCallId input_group_call_id, const GroupCallParticipant &participant,
+ const char *source);
+
+ Td *td_;
+ ActorShared<> parent_;
+
+ GroupCallId max_group_call_id_;
+
+ vector<InputGroupCallId> input_group_call_ids_;
+
+ FlatHashMap<InputGroupCallId, unique_ptr<GroupCall>, InputGroupCallIdHash> group_calls_;
+
+ string pending_group_call_join_params_;
+
+ FlatHashMap<InputGroupCallId, unique_ptr<GroupCallParticipants>, InputGroupCallIdHash> group_call_participants_;
+ FlatHashMap<DialogId, vector<InputGroupCallId>, DialogIdHash> participant_id_to_group_call_id_;
+
+ FlatHashMap<GroupCallId, unique_ptr<GroupCallRecentSpeakers>, GroupCallIdHash> group_call_recent_speakers_;
+
+ FlatHashMap<InputGroupCallId, vector<Promise<td_api::object_ptr<td_api::groupCall>>>, InputGroupCallIdHash>
+ load_group_call_queries_;
+
+ FlatHashMap<InputGroupCallId, unique_ptr<PendingJoinRequest>, InputGroupCallIdHash> pending_join_requests_;
+ FlatHashMap<InputGroupCallId, unique_ptr<PendingJoinRequest>, InputGroupCallIdHash>
+ pending_join_presentation_requests_;
+ uint64 join_group_request_generation_ = 0;
+
+ uint64 toggle_recording_generation_ = 0;
+
+ uint64 toggle_is_muted_generation_ = 0;
+
+ uint64 set_volume_level_generation_ = 0;
+
+ uint64 toggle_is_hand_raised_generation_ = 0;
+
+ MultiTimeout update_group_call_participant_order_timeout_{"UpdateGroupCallParticipantOrderTimeout"};
+ MultiTimeout check_group_call_is_joined_timeout_{"CheckGroupCallIsJoinedTimeout"};
+ MultiTimeout pending_send_speaking_action_timeout_{"PendingSendSpeakingActionTimeout"};
+ MultiTimeout recent_speaker_update_timeout_{"RecentSpeakerUpdateTimeout"};
+ MultiTimeout sync_participants_timeout_{"SyncParticipantsTimeout"};
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipant.cpp b/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipant.cpp
new file mode 100644
index 0000000000..bf1a675442
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipant.cpp
@@ -0,0 +1,321 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/GroupCallParticipant.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/MessageSender.h"
+
+#include "td/utils/logging.h"
+
+#include <limits>
+
+namespace td {
+
+GroupCallParticipant::GroupCallParticipant(const tl_object_ptr<telegram_api::groupCallParticipant> &participant,
+ int32 call_version) {
+ CHECK(participant != nullptr);
+ dialog_id = DialogId(participant->peer_);
+ about = participant->about_;
+ audio_source = participant->source_;
+ server_is_muted_by_themselves = participant->can_self_unmute_;
+ server_is_muted_by_admin = participant->muted_ && !participant->can_self_unmute_;
+ server_is_muted_locally = participant->muted_by_you_;
+ is_self = participant->self_;
+ if ((participant->flags_ & telegram_api::groupCallParticipant::VOLUME_MASK) != 0) {
+ volume_level = participant->volume_;
+ if (volume_level < MIN_VOLUME_LEVEL || volume_level > MAX_VOLUME_LEVEL) {
+ LOG(ERROR) << "Receive " << to_string(participant);
+ volume_level = 10000;
+ }
+ is_volume_level_local = !participant->volume_by_admin_;
+ }
+ if (!participant->left_) {
+ joined_date = participant->date_;
+ if ((participant->flags_ & telegram_api::groupCallParticipant::ACTIVE_DATE_MASK) != 0) {
+ active_date = participant->active_date_;
+ }
+ if (joined_date <= 0 || active_date < 0) {
+ LOG(ERROR) << "Receive invalid active_date/joined_date in " << to_string(participant);
+ joined_date = 1;
+ active_date = 0;
+ }
+ if ((participant->flags_ & telegram_api::groupCallParticipant::RAISE_HAND_RATING_MASK) != 0) {
+ raise_hand_rating = participant->raise_hand_rating_;
+ if (raise_hand_rating < 0) {
+ LOG(ERROR) << "Receive invalid raise_hand_rating in " << to_string(participant);
+ raise_hand_rating = 0;
+ }
+ }
+ }
+ is_just_joined = participant->just_joined_;
+ is_min = participant->min_;
+ version = call_version;
+
+ if (participant->video_ != nullptr) {
+ video_payload = GroupCallVideoPayload(participant->video_.get());
+ }
+ if (participant->presentation_ != nullptr) {
+ if (participant->presentation_->flags_ & telegram_api::groupCallParticipantVideo::AUDIO_SOURCE_MASK) {
+ presentation_audio_source = participant->presentation_->audio_source_;
+ }
+ presentation_payload = GroupCallVideoPayload(participant->presentation_.get());
+ }
+
+ if (is_just_joined && get_has_video()) {
+ video_diff++;
+ }
+}
+
+bool GroupCallParticipant::is_versioned_update(const tl_object_ptr<telegram_api::groupCallParticipant> &participant) {
+ // updates about new and left participants must be applyed as versioned, even they don't increase version
+ return participant->just_joined_ || participant->left_ || participant->versioned_;
+}
+
+GroupCallParticipantOrder GroupCallParticipant::get_real_order(bool can_self_unmute, bool joined_date_asc) const {
+ auto sort_active_date = td::max(active_date, local_active_date);
+ if (sort_active_date == 0 && !get_is_muted_by_admin()) { // if the participant isn't muted by admin
+ if (get_is_muted_by_themselves()) {
+ sort_active_date = joined_date;
+ } else {
+ sort_active_date = G()->unix_time();
+ }
+ }
+ if (sort_active_date < G()->unix_time() - 300) {
+ sort_active_date = 0;
+ }
+ auto sort_raise_hand_rating = can_self_unmute ? raise_hand_rating : 0;
+ auto sort_joined_date = joined_date_asc ? std::numeric_limits<int32>::max() - joined_date : joined_date;
+ bool has_video = !video_payload.is_empty() || !presentation_payload.is_empty();
+ return GroupCallParticipantOrder(has_video, sort_active_date, sort_raise_hand_rating, sort_joined_date);
+}
+
+GroupCallParticipantOrder GroupCallParticipant::get_server_order(bool can_self_unmute, bool joined_date_asc) const {
+ auto sort_active_date = active_date;
+ if (sort_active_date == 0 && !server_is_muted_by_admin) { // if the participant isn't muted by admin
+ if (server_is_muted_by_themselves) {
+ sort_active_date = joined_date;
+ } else {
+ sort_active_date = G()->unix_time();
+ }
+ }
+ auto sort_raise_hand_rating = can_self_unmute ? raise_hand_rating : 0;
+ auto sort_joined_date = joined_date_asc ? std::numeric_limits<int32>::max() - joined_date : joined_date;
+ bool has_video = !video_payload.is_empty() || !presentation_payload.is_empty();
+ return GroupCallParticipantOrder(has_video, sort_active_date, sort_raise_hand_rating, sort_joined_date);
+}
+
+bool GroupCallParticipant::get_is_muted_by_themselves() const {
+ return have_pending_is_muted ? pending_is_muted_by_themselves : server_is_muted_by_themselves;
+}
+
+bool GroupCallParticipant::get_is_muted_by_admin() const {
+ return have_pending_is_muted ? pending_is_muted_by_admin : server_is_muted_by_admin;
+}
+
+bool GroupCallParticipant::get_is_muted_locally() const {
+ return have_pending_is_muted ? pending_is_muted_locally : server_is_muted_locally;
+}
+
+bool GroupCallParticipant::get_is_muted_for_all_users() const {
+ return get_is_muted_by_admin() || get_is_muted_by_themselves();
+}
+
+int32 GroupCallParticipant::get_volume_level() const {
+ return pending_volume_level != 0 ? pending_volume_level : volume_level;
+}
+
+bool GroupCallParticipant::get_is_hand_raised() const {
+ return have_pending_is_hand_raised ? pending_is_hand_raised : raise_hand_rating != 0;
+}
+
+int32 GroupCallParticipant::get_has_video() const {
+ return video_payload.is_empty() && presentation_payload.is_empty() ? 0 : 1;
+}
+
+void GroupCallParticipant::update_from(const GroupCallParticipant &old_participant) {
+ CHECK(!old_participant.is_min);
+ if (joined_date < old_participant.joined_date) {
+ LOG(ERROR) << "Join date of " << old_participant.dialog_id << " decreased from " << old_participant.joined_date
+ << " to " << joined_date;
+ joined_date = old_participant.joined_date;
+ }
+ if (active_date < old_participant.active_date) {
+ active_date = old_participant.active_date;
+ }
+ local_active_date = old_participant.local_active_date;
+ is_speaking = old_participant.is_speaking;
+ if (is_min) {
+ server_is_muted_locally = old_participant.server_is_muted_locally;
+
+ if (old_participant.is_volume_level_local && !is_volume_level_local) {
+ is_volume_level_local = true;
+ volume_level = old_participant.volume_level;
+ }
+
+ if (audio_source == old_participant.audio_source) {
+ is_self = old_participant.is_self;
+ }
+ }
+ is_min = false;
+
+ pending_volume_level = old_participant.pending_volume_level;
+ pending_volume_level_generation = old_participant.pending_volume_level_generation;
+
+ have_pending_is_muted = old_participant.have_pending_is_muted;
+ pending_is_muted_by_themselves = old_participant.pending_is_muted_by_themselves;
+ pending_is_muted_by_admin = old_participant.pending_is_muted_by_admin;
+ pending_is_muted_locally = old_participant.pending_is_muted_locally;
+ pending_is_muted_generation = old_participant.pending_is_muted_generation;
+
+ have_pending_is_hand_raised = old_participant.have_pending_is_hand_raised;
+ pending_is_hand_raised = old_participant.pending_is_hand_raised;
+ pending_is_hand_raised_generation = old_participant.pending_is_hand_raised_generation;
+}
+
+bool GroupCallParticipant::update_can_be_muted(bool can_manage, bool is_admin) {
+ bool is_muted_by_admin = get_is_muted_by_admin();
+ bool is_muted_by_themselves = get_is_muted_by_themselves();
+ bool is_muted_locally = get_is_muted_locally();
+
+ CHECK(!is_muted_by_admin || !is_muted_by_themselves);
+
+ bool new_can_be_muted_for_all_users = false;
+ bool new_can_be_unmuted_for_all_users = false;
+ bool new_can_be_muted_only_for_self = !can_manage && !is_muted_locally;
+ bool new_can_be_unmuted_only_for_self = !can_manage && is_muted_locally;
+ if (is_self) {
+ // current user can be muted if !is_muted_by_themselves && !is_muted_by_admin; after that is_muted_by_themselves
+ // current user can be unmuted if is_muted_by_themselves; after that !is_muted
+ new_can_be_muted_for_all_users = !is_muted_by_themselves && !is_muted_by_admin;
+ new_can_be_unmuted_for_all_users = is_muted_by_themselves;
+ new_can_be_muted_only_for_self = false;
+ new_can_be_unmuted_only_for_self = false;
+ } else if (is_admin) {
+ // admin user can be muted if can_manage && !is_muted_by_themselves; after that is_muted_by_themselves
+ // admin user can't be unmuted
+ new_can_be_muted_for_all_users = can_manage && !is_muted_by_themselves;
+ } else {
+ // other users can be muted if can_manage && !is_muted_by_admin; after that is_muted_by_admin
+ // other users can be unmuted if can_manage && is_muted_by_admin; after that is_muted_by_themselves
+ new_can_be_muted_for_all_users = can_manage && !is_muted_by_admin;
+ new_can_be_unmuted_for_all_users = can_manage && is_muted_by_admin;
+ }
+ CHECK(static_cast<int>(new_can_be_muted_for_all_users) + static_cast<int>(new_can_be_unmuted_for_all_users) +
+ static_cast<int>(new_can_be_muted_only_for_self) + static_cast<int>(new_can_be_unmuted_only_for_self) <=
+ 1);
+ if (new_can_be_muted_for_all_users != can_be_muted_for_all_users ||
+ new_can_be_unmuted_for_all_users != can_be_unmuted_for_all_users ||
+ new_can_be_muted_only_for_self != can_be_muted_only_for_self ||
+ new_can_be_unmuted_only_for_self != can_be_unmuted_only_for_self) {
+ can_be_muted_for_all_users = new_can_be_muted_for_all_users;
+ can_be_unmuted_for_all_users = new_can_be_unmuted_for_all_users;
+ can_be_muted_only_for_self = new_can_be_muted_only_for_self;
+ can_be_unmuted_only_for_self = new_can_be_unmuted_only_for_self;
+ return true;
+ }
+ return false;
+}
+
+bool GroupCallParticipant::set_pending_is_muted(bool is_muted, bool can_manage, bool is_admin) {
+ update_can_be_muted(can_manage, is_admin);
+ if (is_muted) {
+ if (!can_be_muted_for_all_users && !can_be_muted_only_for_self) {
+ return false;
+ }
+ CHECK(!can_be_muted_for_all_users || !can_be_muted_only_for_self);
+ } else {
+ if (!can_be_unmuted_for_all_users && !can_be_unmuted_only_for_self) {
+ return false;
+ }
+ CHECK(!can_be_unmuted_for_all_users || !can_be_unmuted_only_for_self);
+ }
+
+ if (is_self) {
+ pending_is_muted_by_themselves = is_muted;
+ pending_is_muted_by_admin = false;
+ pending_is_muted_locally = false;
+ } else {
+ pending_is_muted_by_themselves = get_is_muted_by_themselves();
+ pending_is_muted_by_admin = get_is_muted_by_admin();
+ pending_is_muted_locally = get_is_muted_locally();
+ if (is_muted) {
+ if (can_be_muted_only_for_self) {
+ // local mute
+ pending_is_muted_locally = true;
+ } else {
+ // admin mute
+ CHECK(can_be_muted_for_all_users);
+ CHECK(can_manage);
+ if (is_admin) {
+ CHECK(!pending_is_muted_by_themselves);
+ pending_is_muted_by_admin = false;
+ pending_is_muted_by_themselves = true;
+ } else {
+ CHECK(!pending_is_muted_by_admin);
+ pending_is_muted_by_admin = true;
+ pending_is_muted_by_themselves = false;
+ }
+ }
+ } else {
+ if (can_be_unmuted_only_for_self) {
+ // local unmute
+ pending_is_muted_locally = false;
+ } else {
+ // admin unmute
+ CHECK(can_be_unmuted_for_all_users);
+ CHECK(can_manage);
+ CHECK(!is_admin);
+ pending_is_muted_by_admin = false;
+ pending_is_muted_by_themselves = true;
+ }
+ }
+ }
+
+ have_pending_is_muted = true;
+ update_can_be_muted(can_manage, is_admin);
+ return true;
+}
+
+td_api::object_ptr<td_api::groupCallParticipant> GroupCallParticipant::get_group_call_participant_object(Td *td) const {
+ if (!is_valid()) {
+ return nullptr;
+ }
+
+ return td_api::make_object<td_api::groupCallParticipant>(
+ get_message_sender_object(td, dialog_id, "get_group_call_participant_object"), audio_source,
+ presentation_audio_source, video_payload.get_group_call_participant_video_info_object(),
+ presentation_payload.get_group_call_participant_video_info_object(), about, is_self, is_speaking,
+ get_is_hand_raised(), can_be_muted_for_all_users, can_be_unmuted_for_all_users, can_be_muted_only_for_self,
+ can_be_unmuted_only_for_self, get_is_muted_for_all_users(), get_is_muted_locally(), get_is_muted_by_themselves(),
+ get_volume_level(), order.get_group_call_participant_order_object());
+}
+
+bool operator==(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs) {
+ return lhs.dialog_id == rhs.dialog_id && lhs.audio_source == rhs.audio_source &&
+ lhs.presentation_audio_source == rhs.presentation_audio_source && lhs.video_payload == rhs.video_payload &&
+ lhs.presentation_payload == rhs.presentation_payload && lhs.about == rhs.about && lhs.is_self == rhs.is_self &&
+ lhs.is_speaking == rhs.is_speaking && lhs.get_is_hand_raised() == rhs.get_is_hand_raised() &&
+ lhs.can_be_muted_for_all_users == rhs.can_be_muted_for_all_users &&
+ lhs.can_be_unmuted_for_all_users == rhs.can_be_unmuted_for_all_users &&
+ lhs.can_be_muted_only_for_self == rhs.can_be_muted_only_for_self &&
+ lhs.can_be_unmuted_only_for_self == rhs.can_be_unmuted_only_for_self &&
+ lhs.get_is_muted_for_all_users() == rhs.get_is_muted_for_all_users() &&
+ lhs.get_is_muted_locally() == rhs.get_is_muted_locally() &&
+ lhs.get_is_muted_by_themselves() == rhs.get_is_muted_by_themselves() &&
+ lhs.get_volume_level() == rhs.get_volume_level() && lhs.order == rhs.order;
+}
+
+bool operator!=(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const GroupCallParticipant &group_call_participant) {
+ return string_builder << "GroupCallParticipant[" << group_call_participant.dialog_id << " with source "
+ << group_call_participant.audio_source << " and order " << group_call_participant.order << ']';
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipant.h b/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipant.h
new file mode 100644
index 0000000000..50fea35f99
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipant.h
@@ -0,0 +1,112 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/GroupCallParticipantOrder.h"
+#include "td/telegram/GroupCallVideoPayload.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class Td;
+
+struct GroupCallParticipant {
+ DialogId dialog_id;
+ string about;
+ GroupCallVideoPayload video_payload;
+ GroupCallVideoPayload presentation_payload;
+ int32 audio_source = 0;
+ int32 presentation_audio_source = 0;
+ int64 raise_hand_rating = 0;
+ int32 joined_date = 0;
+ int32 active_date = 0;
+ int32 volume_level = 10000;
+ bool is_volume_level_local = false;
+ bool server_is_muted_by_themselves = false;
+ bool server_is_muted_by_admin = false;
+ bool server_is_muted_locally = false;
+ bool is_self = false;
+
+ bool can_be_muted_for_all_users = false;
+ bool can_be_unmuted_for_all_users = false;
+ bool can_be_muted_only_for_self = false;
+ bool can_be_unmuted_only_for_self = false;
+
+ bool is_min = false;
+ bool is_fake = false;
+ bool is_just_joined = false;
+ bool is_speaking = false;
+ int32 video_diff = 0;
+ int32 local_active_date = 0;
+ GroupCallParticipantOrder order;
+ int32 version = 0;
+
+ int32 pending_volume_level = 0;
+ uint64 pending_volume_level_generation = 0;
+
+ bool have_pending_is_muted = false;
+ bool pending_is_muted_by_themselves = false;
+ bool pending_is_muted_by_admin = false;
+ bool pending_is_muted_locally = false;
+ uint64 pending_is_muted_generation = 0;
+
+ bool have_pending_is_hand_raised = false;
+ bool pending_is_hand_raised = false;
+ uint64 pending_is_hand_raised_generation = 0;
+
+ static constexpr int32 MIN_VOLUME_LEVEL = 1;
+ static constexpr int32 MAX_VOLUME_LEVEL = 20000;
+
+ GroupCallParticipant() = default;
+
+ GroupCallParticipant(const tl_object_ptr<telegram_api::groupCallParticipant> &participant, int32 call_version);
+
+ static bool is_versioned_update(const tl_object_ptr<telegram_api::groupCallParticipant> &participant);
+
+ void update_from(const GroupCallParticipant &old_participant);
+
+ bool update_can_be_muted(bool can_manage, bool is_admin);
+
+ bool set_pending_is_muted(bool is_muted, bool can_manage, bool is_admin);
+
+ GroupCallParticipantOrder get_real_order(bool can_self_unmute, bool joined_date_asc) const;
+
+ GroupCallParticipantOrder get_server_order(bool can_self_unmute, bool joined_date_asc) const;
+
+ bool is_valid() const {
+ return dialog_id.is_valid();
+ }
+
+ bool get_is_muted_by_themselves() const;
+
+ bool get_is_muted_by_admin() const;
+
+ bool get_is_muted_locally() const;
+
+ bool get_is_muted_for_all_users() const;
+
+ int32 get_volume_level() const;
+
+ bool get_is_hand_raised() const;
+
+ int32 get_has_video() const;
+
+ td_api::object_ptr<td_api::groupCallParticipant> get_group_call_participant_object(Td *td) const;
+};
+
+bool operator==(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs);
+
+bool operator!=(const GroupCallParticipant &lhs, const GroupCallParticipant &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const GroupCallParticipant &group_call_participant);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipantOrder.cpp b/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipantOrder.cpp
new file mode 100644
index 0000000000..1a661dd103
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipantOrder.cpp
@@ -0,0 +1,73 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/GroupCallParticipantOrder.h"
+
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+
+#include <limits>
+#include <tuple>
+
+namespace td {
+
+GroupCallParticipantOrder GroupCallParticipantOrder::min() {
+ return GroupCallParticipantOrder(false, 0, 0, 1);
+}
+
+GroupCallParticipantOrder GroupCallParticipantOrder::max() {
+ return GroupCallParticipantOrder(true, std::numeric_limits<int32>::max(), std::numeric_limits<int64>::max(),
+ std::numeric_limits<int32>::max());
+}
+
+bool GroupCallParticipantOrder::is_valid() const {
+ return *this != GroupCallParticipantOrder();
+}
+
+string GroupCallParticipantOrder::get_group_call_participant_order_object() const {
+ if (!is_valid()) {
+ return string();
+ }
+ return PSTRING() << (has_video_ ? '1' : '0') << lpad0(to_string(active_date_), 10)
+ << lpad0(to_string(raise_hand_rating_), 19) << lpad0(to_string(joined_date_), 10);
+}
+
+bool operator==(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs) {
+ return lhs.has_video_ == rhs.has_video_ && lhs.active_date_ == rhs.active_date_ &&
+ lhs.joined_date_ == rhs.joined_date_ && lhs.raise_hand_rating_ == rhs.raise_hand_rating_;
+}
+
+bool operator!=(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs) {
+ return !(lhs == rhs);
+}
+
+bool operator<(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs) {
+ auto lhs_has_video_ = static_cast<int32>(lhs.has_video_);
+ auto rhs_has_video_ = static_cast<int32>(rhs.has_video_);
+ return std::tie(lhs_has_video_, lhs.active_date_, lhs.raise_hand_rating_, lhs.joined_date_) <
+ std::tie(rhs_has_video_, rhs.active_date_, rhs.raise_hand_rating_, rhs.joined_date_);
+}
+
+bool operator<=(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs) {
+ return !(rhs < lhs);
+}
+
+bool operator>(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs) {
+ return rhs < lhs;
+}
+
+bool operator>=(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs) {
+ return !(lhs < rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder,
+ const GroupCallParticipantOrder &group_call_participant_order) {
+ return string_builder << group_call_participant_order.has_video_ << '/' << group_call_participant_order.active_date_
+ << '/' << group_call_participant_order.raise_hand_rating_ << '/'
+ << group_call_participant_order.joined_date_;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipantOrder.h b/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipantOrder.h
new file mode 100644
index 0000000000..0ea6d278b1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GroupCallParticipantOrder.h
@@ -0,0 +1,64 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class GroupCallParticipantOrder {
+ bool has_video_ = false;
+ int32 active_date_ = 0;
+ int32 joined_date_ = 0;
+ int64 raise_hand_rating_ = 0;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder,
+ const GroupCallParticipantOrder &group_call_participant_order);
+
+ friend bool operator==(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs);
+
+ friend bool operator<(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs);
+
+ public:
+ GroupCallParticipantOrder() = default;
+
+ GroupCallParticipantOrder(bool has_video, int32 active_date, int64 raise_hand_rating, int32 joined_date)
+ : has_video_(has_video)
+ , active_date_(active_date)
+ , joined_date_(joined_date)
+ , raise_hand_rating_(raise_hand_rating) {
+ }
+
+ static GroupCallParticipantOrder min();
+
+ static GroupCallParticipantOrder max();
+
+ bool is_valid() const;
+
+ bool has_video() const {
+ return has_video_;
+ }
+
+ string get_group_call_participant_order_object() const;
+};
+
+bool operator==(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs);
+
+bool operator!=(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs);
+
+bool operator<(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs);
+
+bool operator<=(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs);
+
+bool operator>(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs);
+
+bool operator>=(const GroupCallParticipantOrder &lhs, const GroupCallParticipantOrder &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const GroupCallParticipantOrder &group_call_participant_order);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GroupCallVideoPayload.cpp b/protocols/Telegram/tdlib/td/td/telegram/GroupCallVideoPayload.cpp
new file mode 100644
index 0000000000..4386582773
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GroupCallVideoPayload.cpp
@@ -0,0 +1,61 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/GroupCallVideoPayload.h"
+
+#include "td/utils/algorithm.h"
+
+namespace td {
+
+bool operator==(const GroupCallVideoPayload &lhs, const GroupCallVideoPayload &rhs) {
+ if (lhs.source_groups_.size() != rhs.source_groups_.size() || lhs.endpoint_ != rhs.endpoint_ ||
+ lhs.is_paused_ != rhs.is_paused_) {
+ return false;
+ }
+ for (size_t i = 0; i < lhs.source_groups_.size(); i++) {
+ if (lhs.source_groups_[i].semantics_ != rhs.source_groups_[i].semantics_ ||
+ lhs.source_groups_[i].source_ids_ != rhs.source_groups_[i].source_ids_) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool GroupCallVideoPayload::is_empty() const {
+ return endpoint_.empty() || source_groups_.empty();
+}
+
+td_api::object_ptr<td_api::groupCallParticipantVideoInfo>
+GroupCallVideoPayload::get_group_call_participant_video_info_object() const {
+ if (is_empty()) {
+ return nullptr;
+ }
+
+ auto get_group_call_video_source_group_object = [](const GroupCallVideoSourceGroup &group) {
+ return td_api::make_object<td_api::groupCallVideoSourceGroup>(group.semantics_, vector<int32>(group.source_ids_));
+ };
+ return td_api::make_object<td_api::groupCallParticipantVideoInfo>(
+ transform(source_groups_, get_group_call_video_source_group_object), endpoint_, is_paused_);
+}
+
+GroupCallVideoPayload::GroupCallVideoPayload(const telegram_api::groupCallParticipantVideo *video) {
+ if (video == nullptr) {
+ return;
+ }
+
+ endpoint_ = video->endpoint_;
+ source_groups_ = transform(video->source_groups_, [](auto &&source_group) {
+ GroupCallVideoSourceGroup result;
+ result.semantics_ = source_group->semantics_;
+ result.source_ids_ = source_group->sources_;
+ return result;
+ });
+ if (!is_empty()) {
+ is_paused_ = video->paused_;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/GroupCallVideoPayload.h b/protocols/Telegram/tdlib/td/td/telegram/GroupCallVideoPayload.h
new file mode 100644
index 0000000000..e86f1073f7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/GroupCallVideoPayload.h
@@ -0,0 +1,40 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+class GroupCallVideoPayload {
+ struct GroupCallVideoSourceGroup {
+ string semantics_;
+ vector<int32> source_ids_;
+ };
+
+ vector<GroupCallVideoSourceGroup> source_groups_;
+ string endpoint_;
+ bool is_paused_ = false;
+
+ friend bool operator==(const GroupCallVideoPayload &lhs, const GroupCallVideoPayload &rhs);
+
+ public:
+ GroupCallVideoPayload() = default;
+
+ explicit GroupCallVideoPayload(const telegram_api::groupCallParticipantVideo *video);
+
+ bool is_empty() const;
+
+ td_api::object_ptr<td_api::groupCallParticipantVideoInfo> get_group_call_participant_video_info_object() const;
+};
+
+bool operator==(const GroupCallVideoPayload &lhs, const GroupCallVideoPayload &rhs);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/HashtagHints.cpp b/protocols/Telegram/tdlib/td/td/telegram/HashtagHints.cpp
index 416835eb01..e9fbea523f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/HashtagHints.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/HashtagHints.cpp
@@ -1,18 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/HashtagHints.h"
-#include "td/db/Pmc.h"
#include "td/telegram/Global.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/db/SqliteKeyValueAsync.h"
+
+#include "td/utils/HashTableUtils.h"
#include "td/utils/logging.h"
#include "td/utils/tl_helpers.h"
-
-#include <functional>
+#include "td/utils/utf8.h"
namespace td {
@@ -45,7 +48,7 @@ void HashtagHints::remove_hashtag(string hashtag, Promise<> promise) {
if (hashtag[0] == '#') {
hashtag = hashtag.substr(1);
}
- auto key = std::hash<std::string>()(hashtag);
+ auto key = Hash<string>()(hashtag);
if (hints_.has_key(key)) {
hints_.remove(key);
G()->td_db()->get_sqlite_pmc()->set(get_key(), serialize(keys_to_strings(hints_.search_empty(101).second)),
@@ -71,13 +74,21 @@ string HashtagHints::get_key() const {
}
void HashtagHints::hashtag_used_impl(const string &hashtag) {
- // TODO: may be it should be optimized a little
- auto key = std::hash<std::string>()(hashtag);
+ if (!check_utf8(hashtag)) {
+ LOG(ERROR) << "Trying to add invalid UTF-8 hashtag \"" << hashtag << '"';
+ return;
+ }
+
+ auto key = Hash<string>()(hashtag);
hints_.add(key, hashtag);
hints_.set_rating(key, -++counter_);
}
void HashtagHints::from_db(Result<string> data, bool dummy) {
+ if (G()->close_flag()) {
+ return;
+ }
+
sync_with_db_ = true;
if (data.is_error() || data.ok().empty()) {
return;
@@ -85,7 +96,7 @@ void HashtagHints::from_db(Result<string> data, bool dummy) {
std::vector<string> hashtags;
auto status = unserialize(hashtags, data.ok());
if (status.is_error()) {
- LOG(ERROR) << status;
+ LOG(ERROR) << "Failed to unserialize hashtag hints: " << status;
return;
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/HashtagHints.h b/protocols/Telegram/tdlib/td/td/telegram/HashtagHints.h
index b99cc50cd6..9434769fa5 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/HashtagHints.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/HashtagHints.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,14 +7,15 @@
#pragma once
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
#include "td/utils/common.h"
#include "td/utils/Hints.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
namespace td {
-class HashtagHints : public Actor {
+
+class HashtagHints final : public Actor {
public:
HashtagHints(string mode, ActorShared<> parent);
@@ -34,10 +35,11 @@ class HashtagHints : public Actor {
string get_key() const;
- void start_up() override;
+ void start_up() final;
void hashtag_used_impl(const string &hashtag);
void from_db(Result<string> data, bool dummy);
std::vector<string> keys_to_strings(const std::vector<int64> &keys);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.cpp
index c84aa8ce60..d2c2b91821 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.cpp
@@ -1,14 +1,10 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/InlineQueriesManager.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/td_api.hpp"
-#include "td/telegram/telegram_api.h"
-#include "td/telegram/telegram_api.hpp"
#include "td/telegram/AccessRights.h"
#include "td/telegram/AnimationsManager.h"
@@ -16,42 +12,58 @@
#include "td/telegram/AuthManager.h"
#include "td/telegram/Contact.h"
#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Document.h"
#include "td/telegram/DocumentsManager.h"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Game.h"
#include "td/telegram/Global.h"
+#include "td/telegram/InputInvoice.h"
+#include "td/telegram/InputMessageText.h"
+#include "td/telegram/Location.h"
+#include "td/telegram/MessageContent.h"
+#include "td/telegram/MessageContentType.h"
#include "td/telegram/MessageEntity.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/misc.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/Photo.h"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/PhotoSize.h"
#include "td/telegram/ReplyMarkup.h"
#include "td/telegram/StickersManager.h"
#include "td/telegram/Td.h"
-#include "td/telegram/VideoNotesManager.h"
+#include "td/telegram/td_api.hpp"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/telegram/telegram_api.hpp"
+#include "td/telegram/ThemeManager.h"
+#include "td/telegram/UpdatesManager.h"
+#include "td/telegram/Venue.h"
#include "td/telegram/VideosManager.h"
#include "td/telegram/VoiceNotesManager.h"
-#include "td/telegram/WebPageId.h"
-#include "td/telegram/WebPagesManager.h"
-
-#include "td/telegram/net/DcId.h"
+#include "td/utils/algorithm.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/HttpUrl.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/tl_parsers.h"
#include <algorithm>
-#include <functional>
namespace td {
-class GetInlineBotResultsQuery : public Td::ResultHandler {
+class GetInlineBotResultsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
+ DialogId dialog_id_;
UserId bot_user_id_;
uint64 query_hash_;
@@ -61,51 +73,52 @@ class GetInlineBotResultsQuery : public Td::ResultHandler {
explicit GetInlineBotResultsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- NetQueryRef send(UserId bot_user_id, tl_object_ptr<telegram_api::InputUser> bot_input_user, DialogId dialog_id,
- Location user_location, const string &query, const string &offset, uint64 query_hash) {
+ NetQueryRef send(UserId bot_user_id, DialogId dialog_id, tl_object_ptr<telegram_api::InputUser> bot_input_user,
+ tl_object_ptr<telegram_api::InputPeer> input_peer, Location user_location, const string &query,
+ const string &offset, uint64 query_hash) {
+ CHECK(input_peer != nullptr);
bot_user_id_ = bot_user_id;
+ dialog_id_ = dialog_id;
query_hash_ = query_hash;
int32 flags = 0;
if (!user_location.empty()) {
flags |= GET_INLINE_BOT_RESULTS_FLAG_HAS_LOCATION;
}
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
- if (input_peer == nullptr) {
- input_peer = make_tl_object<telegram_api::inputPeerEmpty>();
- }
-
- auto net_query = G()->net_query_creator().create(create_storer(telegram_api::messages_getInlineBotResults(
+ auto net_query = G()->net_query_creator().create(telegram_api::messages_getInlineBotResults(
flags, std::move(bot_input_user), std::move(input_peer),
- user_location.empty() ? nullptr : user_location.get_input_geo_point(), query, offset)));
+ user_location.empty() ? nullptr : user_location.get_input_geo_point(), query, offset));
auto result = net_query.get_weak();
- net_query->need_resend_on_503 = false;
+ net_query->need_resend_on_503_ = false;
send_query(std::move(net_query));
return result;
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getInlineBotResults>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->inline_queries_manager_->on_get_inline_query_results(bot_user_id_, query_hash_, result_ptr.move_as_ok());
+ td_->inline_queries_manager_->on_get_inline_query_results(dialog_id_, bot_user_id_, query_hash_,
+ result_ptr.move_as_ok());
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- if (status.code() == NetQuery::Cancelled) {
- status = Status::Error(406, "Request cancelled");
+ void on_error(Status status) final {
+ if (status.code() == NetQuery::Canceled) {
+ status = Status::Error(406, "Request canceled");
+ } else if (status.message() == "BOT_RESPONSE_TIMEOUT") {
+ status = Status::Error(502, "The bot is not responding");
}
- LOG(INFO) << "Inline query returned error " << status;
+ LOG(INFO) << "Receive error for GetInlineBotResultsQuery: " << status;
- td->inline_queries_manager_->on_get_inline_query_results(bot_user_id_, query_hash_, nullptr);
+ td_->inline_queries_manager_->on_get_inline_query_results(dialog_id_, bot_user_id_, query_hash_, nullptr);
promise_.set_error(std::move(status));
}
};
-class SetInlineBotResultsQuery : public Td::ResultHandler {
+class SetInlineBotResultsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -130,25 +143,121 @@ class SetInlineBotResultsQuery : public Td::ResultHandler {
flags |= telegram_api::messages_setInlineBotResults::SWITCH_PM_MASK;
inline_bot_switch_pm = make_tl_object<telegram_api::inlineBotSwitchPM>(switch_pm_text, switch_pm_parameter);
}
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_setInlineBotResults(
+ send_query(G()->net_query_creator().create(telegram_api::messages_setInlineBotResults(
flags, false /*ignored*/, false /*ignored*/, inline_query_id, std::move(results), cache_time, next_offset,
- std::move(inline_bot_switch_pm)))));
+ std::move(inline_bot_switch_pm))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setInlineBotResults>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
if (!result) {
- LOG(INFO) << "Sending answer to an inline query has failed";
+ LOG(ERROR) << "Sending answer to an inline query has failed";
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class RequestSimpleWebViewQuery final : public Td::ResultHandler {
+ Promise<string> promise_;
+
+ public:
+ explicit RequestSimpleWebViewQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(tl_object_ptr<telegram_api::InputUser> &&input_user, const string &url,
+ const td_api::object_ptr<td_api::themeParameters> &theme, string &&platform) {
+ tl_object_ptr<telegram_api::dataJSON> theme_parameters;
+ int32 flags = 0;
+ if (theme != nullptr) {
+ flags |= telegram_api::messages_requestSimpleWebView::THEME_PARAMS_MASK;
+
+ theme_parameters = make_tl_object<telegram_api::dataJSON>(string());
+ theme_parameters->data_ = ThemeManager::get_theme_parameters_json_string(theme, false);
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_requestSimpleWebView(
+ flags, std::move(input_user), url, std::move(theme_parameters), platform)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_requestSimpleWebView>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for RequestSimpleWebViewQuery: " << to_string(ptr);
+ promise_.set_value(std::move(ptr->url_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SendWebViewDataQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SendWebViewDataQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(tl_object_ptr<telegram_api::InputUser> &&input_user, int64 random_id, const string &button_text,
+ const string &data) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_sendWebViewData(std::move(input_user), random_id, button_text, data)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_sendWebViewData>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SendWebViewDataQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SendWebViewResultMessageQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::sentWebAppMessage>> promise_;
+
+ public:
+ explicit SendWebViewResultMessageQuery(Promise<td_api::object_ptr<td_api::sentWebAppMessage>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(const string &bot_query_id, tl_object_ptr<telegram_api::InputBotInlineResult> &&result) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_sendWebViewResultMessage(bot_query_id, std::move(result))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_sendWebViewResultMessage>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SendWebViewResultMessageQuery: " << to_string(ptr);
+ promise_.set_value(td_api::make_object<td_api::sentWebAppMessage>(
+ InlineQueriesManager::get_inline_message_id(std::move(ptr->msg_id_))));
+ }
+
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
@@ -156,6 +265,7 @@ class SetInlineBotResultsQuery : public Td::ResultHandler {
InlineQueriesManager::InlineQueriesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
drop_inline_query_result_timeout_.set_callback(on_drop_inline_query_result_timeout_callback);
drop_inline_query_result_timeout_.set_callback_data(static_cast<void *>(this));
+ next_inline_query_time_ = Time::now();
}
void InlineQueriesManager::tear_down() {
@@ -164,6 +274,9 @@ void InlineQueriesManager::tear_down() {
void InlineQueriesManager::on_drop_inline_query_result_timeout_callback(void *inline_queries_manager_ptr,
int64 query_hash) {
+ if (G()->close_flag()) {
+ return;
+ }
auto inline_queries_manager = static_cast<InlineQueriesManager *>(inline_queries_manager_ptr);
auto it = inline_queries_manager->inline_query_results_.find(query_hash);
CHECK(it != inline_queries_manager->inline_query_results_.end());
@@ -175,13 +288,30 @@ void InlineQueriesManager::on_drop_inline_query_result_timeout_callback(void *in
}
void InlineQueriesManager::after_get_difference() {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
if (recently_used_bots_loaded_ < 2) {
Promise<Unit> promise;
load_recently_used_bots(promise);
}
}
-tl_object_ptr<telegram_api::inputBotInlineMessageID> InlineQueriesManager::get_input_bot_inline_message_id(
+int32 InlineQueriesManager::get_inline_message_dc_id(
+ const tl_object_ptr<telegram_api::InputBotInlineMessageID> &inline_message_id) {
+ CHECK(inline_message_id != nullptr);
+ switch (inline_message_id->get_id()) {
+ case telegram_api::inputBotInlineMessageID::ID:
+ return static_cast<const telegram_api::inputBotInlineMessageID *>(inline_message_id.get())->dc_id_;
+ case telegram_api::inputBotInlineMessageID64::ID:
+ return static_cast<const telegram_api::inputBotInlineMessageID64 *>(inline_message_id.get())->dc_id_;
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+}
+
+tl_object_ptr<telegram_api::InputBotInlineMessageID> InlineQueriesManager::get_input_bot_inline_message_id(
const string &inline_message_id) {
auto r_binary = base64url_decode(inline_message_id);
if (r_binary.is_error()) {
@@ -189,67 +319,46 @@ tl_object_ptr<telegram_api::inputBotInlineMessageID> InlineQueriesManager::get_i
}
BufferSlice buffer_slice(r_binary.ok());
TlBufferParser parser(&buffer_slice);
- auto result = telegram_api::inputBotInlineMessageID::fetch(parser);
+ auto result = buffer_slice.size() == 20 ? telegram_api::inputBotInlineMessageID::fetch(parser)
+ : telegram_api::inputBotInlineMessageID64::fetch(parser);
parser.fetch_end();
if (parser.get_error()) {
return nullptr;
}
- if (!DcId::is_valid(result->dc_id_)) {
+ if (!DcId::is_valid(get_inline_message_dc_id(result))) {
return nullptr;
}
- LOG(INFO) << "Have inline message id: " << to_string(result);
+ LOG(INFO) << "Have inline message identifier: " << to_string(result);
return result;
}
string InlineQueriesManager::get_inline_message_id(
- tl_object_ptr<telegram_api::inputBotInlineMessageID> &&input_bot_inline_message_id) {
+ tl_object_ptr<telegram_api::InputBotInlineMessageID> &&input_bot_inline_message_id) {
if (input_bot_inline_message_id == nullptr) {
- return "";
+ return string();
}
- LOG(INFO) << "Got inline message id: " << to_string(input_bot_inline_message_id);
+ LOG(INFO) << "Got inline message identifier: " << to_string(input_bot_inline_message_id);
return base64url_encode(serialize(*input_bot_inline_message_id));
}
-Result<FormattedText> InlineQueriesManager::process_input_caption(
- td_api::object_ptr<td_api::formattedText> &&caption) const {
- return td_->messages_manager_->process_input_caption(DialogId(), std::move(caption), true);
-}
-
-tl_object_ptr<telegram_api::inputBotInlineMessageMediaAuto>
-InlineQueriesManager::get_input_bot_inline_message_media_auto(
- const FormattedText &caption, tl_object_ptr<telegram_api::ReplyMarkup> &&input_reply_markup) const {
- int32 flags = 0;
- if (input_reply_markup != nullptr) {
- flags |= telegram_api::inputBotInlineMessageText::REPLY_MARKUP_MASK;
- }
- auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption.entities);
- if (!entities.empty()) {
- flags |= telegram_api::inputBotInlineMessageText::ENTITIES_MASK;
- }
-
- return make_tl_object<telegram_api::inputBotInlineMessageMediaAuto>(flags, caption.text, std::move(entities),
- std::move(input_reply_markup));
-}
-
Result<tl_object_ptr<telegram_api::InputBotInlineMessage>> InlineQueriesManager::get_inline_message(
tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup_ptr, int32 allowed_media_content_id) const {
if (input_message_content == nullptr) {
- return Status::Error(400, "Inline message can't be empty");
+ return Status::Error(400, "Inline message must be non-empty");
}
TRY_RESULT(reply_markup, get_reply_markup(std::move(reply_markup_ptr), true, true, false, true));
- auto input_reply_markup = get_input_reply_markup(reply_markup);
- int32 flags = 0;
- if (input_reply_markup != nullptr) {
- flags |= telegram_api::inputBotInlineMessageText::REPLY_MARKUP_MASK;
- }
+ auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), reply_markup);
auto constructor_id = input_message_content->get_id();
if (constructor_id == td_api::inputMessageText::ID) {
- TRY_RESULT(input_message_text,
- td_->messages_manager_->process_input_message_text(DialogId(), std::move(input_message_content), true));
-
+ TRY_RESULT(input_message_text, process_input_message_text(td_, DialogId(td_->contacts_manager_->get_my_id()),
+ std::move(input_message_content), true));
+ int32 flags = 0;
+ if (input_reply_markup != nullptr) {
+ flags |= telegram_api::inputBotInlineMessageText::REPLY_MARKUP_MASK;
+ }
if (input_message_text.disable_web_page_preview) {
flags |= telegram_api::inputBotInlineMessageText::NO_WEBPAGE_MASK;
}
@@ -258,190 +367,94 @@ Result<tl_object_ptr<telegram_api::InputBotInlineMessage>> InlineQueriesManager:
}
return make_tl_object<telegram_api::inputBotInlineMessageText>(
flags, false /*ignored*/, std::move(input_message_text.text.text),
- get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities),
+ get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities,
+ "get_inline_message"),
std::move(input_reply_markup));
}
if (constructor_id == td_api::inputMessageContact::ID) {
- TRY_RESULT(contact, MessagesManager::process_input_message_contact(std::move(input_message_content)));
- return contact.get_input_bot_inline_message_media_contact(flags, std::move(input_reply_markup));
+ TRY_RESULT(contact, process_input_message_contact(std::move(input_message_content)));
+ return contact.get_input_bot_inline_message_media_contact(std::move(input_reply_markup));
+ }
+ if (constructor_id == td_api::inputMessageInvoice::ID) {
+ TRY_RESULT(input_invoice,
+ InputInvoice::process_input_message_invoice(std::move(input_message_content), td_, DialogId(), false));
+ return input_invoice.get_input_bot_inline_message_media_invoice(std::move(input_reply_markup), td_);
}
if (constructor_id == td_api::inputMessageLocation::ID) {
- TRY_RESULT(location, MessagesManager::process_input_message_location(std::move(input_message_content)));
- return make_tl_object<telegram_api::inputBotInlineMessageMediaGeo>(flags, location.first.get_input_geo_point(),
- location.second, std::move(input_reply_markup));
+ TRY_RESULT(location, process_input_message_location(std::move(input_message_content)));
+ int32 flags = 0;
+ if (input_reply_markup != nullptr) {
+ flags |= telegram_api::inputBotInlineMessageMediaGeo::REPLY_MARKUP_MASK;
+ }
+ if (location.heading != 0) {
+ flags |= telegram_api::inputBotInlineMessageMediaGeo::HEADING_MASK;
+ }
+ if (location.live_period != 0) {
+ flags |= telegram_api::inputBotInlineMessageMediaGeo::PERIOD_MASK;
+ flags |= telegram_api::inputBotInlineMessageMediaGeo::PROXIMITY_NOTIFICATION_RADIUS_MASK;
+ }
+ return make_tl_object<telegram_api::inputBotInlineMessageMediaGeo>(
+ flags, location.location.get_input_geo_point(), location.heading, location.live_period,
+ location.proximity_alert_radius, std::move(input_reply_markup));
}
if (constructor_id == td_api::inputMessageVenue::ID) {
- TRY_RESULT(venue, MessagesManager::process_input_message_venue(std::move(input_message_content)));
- return venue.get_input_bot_inline_message_media_venue(flags, std::move(input_reply_markup));
+ TRY_RESULT(venue, process_input_message_venue(std::move(input_message_content)));
+ return venue.get_input_bot_inline_message_media_venue(std::move(input_reply_markup));
}
if (constructor_id == allowed_media_content_id) {
- if (constructor_id == td_api::inputMessageAnimation::ID) {
- auto input_message_animation = static_cast<td_api::inputMessageAnimation *>(input_message_content.get());
- TRY_RESULT(caption, process_input_caption(std::move(input_message_animation->caption_)));
- return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup));
- }
- if (constructor_id == td_api::inputMessageAudio::ID) {
- auto input_message_audio = static_cast<td_api::inputMessageAudio *>(input_message_content.get());
- TRY_RESULT(caption, process_input_caption(std::move(input_message_audio->caption_)));
- return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup));
- }
- if (constructor_id == td_api::inputMessageDocument::ID) {
- auto input_message_document = static_cast<td_api::inputMessageDocument *>(input_message_content.get());
- TRY_RESULT(caption, process_input_caption(std::move(input_message_document->caption_)));
- return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup));
- }
- if (constructor_id == td_api::inputMessagePhoto::ID) {
- auto input_message_photo = static_cast<td_api::inputMessagePhoto *>(input_message_content.get());
- TRY_RESULT(caption, process_input_caption(std::move(input_message_photo->caption_)));
- return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup));
- }
- if (constructor_id == td_api::inputMessageSticker::ID) {
- // auto input_message_sticker = static_cast<const td_api::inputMessageSticker *>(input_message_content.get());
- return make_tl_object<telegram_api::inputBotInlineMessageMediaAuto>(flags, "", Auto(),
- std::move(input_reply_markup));
- }
- if (constructor_id == td_api::inputMessageVideo::ID) {
- auto input_message_video = static_cast<td_api::inputMessageVideo *>(input_message_content.get());
- TRY_RESULT(caption, process_input_caption(std::move(input_message_video->caption_)));
- return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup));
- }
- if (constructor_id == td_api::inputMessageVoiceNote::ID) {
- auto input_message_voice_note = static_cast<td_api::inputMessageVoiceNote *>(input_message_content.get());
- TRY_RESULT(caption, process_input_caption(std::move(input_message_voice_note->caption_)));
- return get_input_bot_inline_message_media_auto(caption, std::move(input_reply_markup));
+ TRY_RESULT(caption, get_formatted_text(td_, DialogId(td_->contacts_manager_->get_my_id()),
+ extract_input_caption(input_message_content), true, true, true, false));
+ int32 flags = 0;
+ if (input_reply_markup != nullptr) {
+ flags |= telegram_api::inputBotInlineMessageMediaAuto::REPLY_MARKUP_MASK;
+ }
+ auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "get_inline_message");
+ if (!entities.empty()) {
+ flags |= telegram_api::inputBotInlineMessageMediaAuto::ENTITIES_MASK;
}
+ return make_tl_object<telegram_api::inputBotInlineMessageMediaAuto>(flags, caption.text, std::move(entities),
+ std::move(input_reply_markup));
}
return Status::Error(400, "Unallowed inline message content type");
}
bool InlineQueriesManager::register_inline_message_content(
int64 query_id, const string &result_id, FileId file_id,
- tl_object_ptr<telegram_api::BotInlineMessage> &&inline_message, int32 allowed_media_content_id, Photo *photo,
- Game *game) {
- CHECK(inline_message != nullptr);
- CHECK((allowed_media_content_id == td_api::inputMessagePhoto::ID) == (photo != nullptr));
- CHECK((allowed_media_content_id == td_api::inputMessageGame::ID) == (game != nullptr));
- CHECK((allowed_media_content_id != td_api::inputMessagePhoto::ID &&
- allowed_media_content_id != td_api::inputMessageGame::ID && allowed_media_content_id != -1) ==
- file_id.is_valid());
-
- unique_ptr<MessageContent> message_content;
- tl_object_ptr<telegram_api::ReplyMarkup> reply_markup;
- bool disable_web_page_preview = false;
- switch (inline_message->get_id()) {
- case telegram_api::botInlineMessageText::ID: {
- auto inline_message_text = move_tl_object_as<telegram_api::botInlineMessageText>(inline_message);
- auto entities = get_message_entities(td_->contacts_manager_.get(), std::move(inline_message_text->entities_),
- "botInlineMessageText");
- auto status = fix_formatted_text(inline_message_text->message_, entities, false, true, true, false);
- if (status.is_error()) {
- LOG(ERROR) << "Receive error " << status << " while parsing botInlineMessageText "
- << inline_message_text->message_;
- break;
- }
+ tl_object_ptr<telegram_api::BotInlineMessage> &&inline_message, int32 allowed_media_content_id, bool allow_invoice,
+ Photo *photo, Game *game) {
+ CHECK(query_id != 0);
+ if (result_id.empty()) {
+ return false;
+ }
- disable_web_page_preview =
- (inline_message_text->flags_ & telegram_api::botInlineMessageText::NO_WEBPAGE_MASK) != 0;
- WebPageId web_page_id;
- if (!disable_web_page_preview) {
- web_page_id =
- td_->web_pages_manager_->get_web_page_by_url(get_first_url(inline_message_text->message_, entities));
- }
- message_content = make_unique<MessageText>(
- FormattedText{std::move(inline_message_text->message_), std::move(entities)}, web_page_id);
- reply_markup = std::move(inline_message_text->reply_markup_);
- break;
- }
- case telegram_api::botInlineMessageMediaGeo::ID: {
- auto inline_message_geo = move_tl_object_as<telegram_api::botInlineMessageMediaGeo>(inline_message);
- if (inline_message_geo->period_ > 0) {
- message_content =
- make_unique<MessageLiveLocation>(Location(inline_message_geo->geo_), inline_message_geo->period_);
- } else {
- message_content = make_unique<MessageLocation>(Location(inline_message_geo->geo_));
- }
- reply_markup = std::move(inline_message_geo->reply_markup_);
- break;
- }
- case telegram_api::botInlineMessageMediaVenue::ID: {
- auto inline_message_venue = move_tl_object_as<telegram_api::botInlineMessageMediaVenue>(inline_message);
- message_content = make_unique<MessageVenue>(
- Venue(inline_message_venue->geo_, std::move(inline_message_venue->title_),
- std::move(inline_message_venue->address_), std::move(inline_message_venue->provider_),
- std::move(inline_message_venue->venue_id_)));
- reply_markup = std::move(inline_message_venue->reply_markup_);
- break;
+ InlineMessageContent content =
+ create_inline_message_content(td_, file_id, std::move(inline_message), allowed_media_content_id, photo, game);
+ if (content.message_content != nullptr) {
+ if (!allow_invoice && content.message_content->get_type() == MessageContentType::Invoice) {
+ return false;
}
- case telegram_api::botInlineMessageMediaContact::ID: {
- auto inline_message_contact = move_tl_object_as<telegram_api::botInlineMessageMediaContact>(inline_message);
- message_content = make_unique<MessageContact>(Contact(inline_message_contact->phone_number_,
- inline_message_contact->first_name_,
- inline_message_contact->last_name_, 0));
- reply_markup = std::move(inline_message_contact->reply_markup_);
- break;
- }
- case telegram_api::botInlineMessageMediaAuto::ID: {
- auto input_message_media_auto = move_tl_object_as<telegram_api::botInlineMessageMediaAuto>(inline_message);
- auto caption = td_->messages_manager_->get_message_text(input_message_media_auto->message_,
- std::move(input_message_media_auto->entities_), 0);
- reply_markup = std::move(input_message_media_auto->reply_markup_);
-
- if (allowed_media_content_id == td_api::inputMessageAnimation::ID) {
- message_content = make_unique<MessageAnimation>(file_id, std::move(caption));
- } else if (allowed_media_content_id == td_api::inputMessageAudio::ID) {
- message_content = make_unique<MessageAudio>(file_id, std::move(caption));
- } else if (allowed_media_content_id == td_api::inputMessageDocument::ID) {
- message_content = make_unique<MessageDocument>(file_id, std::move(caption));
- } else if (allowed_media_content_id == td_api::inputMessageGame::ID) {
- CHECK(game != nullptr);
- // TODO game->set_short_name(std::move(caption));
- message_content = make_unique<MessageGame>(std::move(*game));
- } else if (allowed_media_content_id == td_api::inputMessagePhoto::ID) {
- message_content = make_unique<MessagePhoto>(std::move(*photo), std::move(caption));
- } else if (allowed_media_content_id == td_api::inputMessageSticker::ID) {
- message_content = make_unique<MessageSticker>(file_id);
- } else if (allowed_media_content_id == td_api::inputMessageVideo::ID) {
- message_content = make_unique<MessageVideo>(file_id, std::move(caption));
- } else if (allowed_media_content_id == td_api::inputMessageVoiceNote::ID) {
- message_content = make_unique<MessageVoiceNote>(file_id, std::move(caption), true);
- } else {
- input_message_media_auto->reply_markup_ = std::move(reply_markup);
- input_message_media_auto->message_ = std::move(caption.text);
- inline_message = std::move(input_message_media_auto);
- }
- break;
- }
- default:
- UNREACHABLE();
- }
- if (message_content != nullptr) {
- inline_message_contents_[query_id][result_id] = {
- std::move(message_content),
- get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false), disable_web_page_preview};
+
+ inline_message_contents_[query_id].emplace(result_id, std::move(content));
return true;
}
-
- LOG(WARNING) << "Unallowed bot inline message " << to_string(inline_message);
return false;
}
-std::tuple<const MessageContent *, const ReplyMarkup *, bool> InlineQueriesManager::get_inline_message_content(
- int64 query_id, const string &result_id) {
+const InlineMessageContent *InlineQueriesManager::get_inline_message_content(int64 query_id, const string &result_id) {
auto it = inline_message_contents_.find(query_id);
if (it == inline_message_contents_.end()) {
- return std::make_tuple(nullptr, nullptr, false);
+ return nullptr;
}
auto result_it = it->second.find(result_id);
if (result_it == it->second.end()) {
- return std::make_tuple(nullptr, nullptr, false);
+ return nullptr;
}
if (update_bot_usage(get_inline_bot_user_id(query_id))) {
save_recently_used_bots();
}
- return std::make_tuple(result_it->second.message_content.get(), result_it->second.message_reply_markup.get(),
- result_it->second.disable_web_page_preview);
+ return &result_it->second;
}
UserId InlineQueriesManager::get_inline_bot_user_id(int64 query_id) const {
@@ -452,13 +465,22 @@ UserId InlineQueriesManager::get_inline_bot_user_id(int64 query_id) const {
return it->second;
}
-void InlineQueriesManager::answer_inline_query(int64 inline_query_id, bool is_personal,
- vector<tl_object_ptr<td_api::InputInlineQueryResult>> &&input_results,
- int32 cache_time, const string &next_offset,
- const string &switch_pm_text, const string &switch_pm_parameter,
- Promise<Unit> &&promise) const {
- if (!td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(400, "Method can be used by bots only"));
+void InlineQueriesManager::answer_inline_query(
+ int64 inline_query_id, bool is_personal, vector<td_api::object_ptr<td_api::InputInlineQueryResult>> &&input_results,
+ int32 cache_time, const string &next_offset, const string &switch_pm_text, const string &switch_pm_parameter,
+ Promise<Unit> &&promise) const {
+ CHECK(td_->auth_manager_->is_bot());
+
+ if (!switch_pm_text.empty()) {
+ if (switch_pm_parameter.empty()) {
+ return promise.set_error(Status::Error(400, "Can't use empty switch_pm_parameter"));
+ }
+ if (switch_pm_parameter.size() > 64) {
+ return promise.set_error(Status::Error(400, "Too long switch_pm_parameter specified"));
+ }
+ if (!is_base64url_characters(switch_pm_parameter)) {
+ return promise.set_error(Status::Error(400, "Unallowed characters in switch_pm_parameter are used"));
+ }
}
vector<tl_object_ptr<telegram_api::InputBotInlineResult>> results;
@@ -466,412 +488,461 @@ void InlineQueriesManager::answer_inline_query(int64 inline_query_id, bool is_pe
bool is_gallery = false;
bool force_vertical = false;
for (auto &input_result : input_results) {
- if (input_result == nullptr) {
- return promise.set_error(Status::Error(400, "Inline query result must not be empty"));
- }
-
- string id;
- string url;
- string type;
- string title;
- string description;
- string thumbnail_url;
- string content_url;
- string content_type;
- int32 thumbnail_width = 0;
- int32 thumbnail_height = 0;
- int32 width = 0;
- int32 height = 0;
- int32 duration = 0;
-
- FileType file_type = FileType::Temp;
- Result<tl_object_ptr<telegram_api::InputBotInlineMessage>> r_inline_message;
- switch (input_result->get_id()) {
- case td_api::inputInlineQueryResultAnimatedGif::ID: {
- auto animated_gif = move_tl_object_as<td_api::inputInlineQueryResultAnimatedGif>(input_result);
- type = "gif";
- id = std::move(animated_gif->id_);
- title = std::move(animated_gif->title_);
- thumbnail_url = std::move(animated_gif->thumbnail_url_);
- content_url = std::move(animated_gif->gif_url_);
- content_type = "image/gif";
- // duration = animated_gif->gif_duration_;
- width = animated_gif->gif_width_;
- height = animated_gif->gif_height_;
- is_gallery = true;
-
- file_type = FileType::Animation;
- r_inline_message =
- get_inline_message(std::move(animated_gif->input_message_content_), std::move(animated_gif->reply_markup_),
- td_api::inputMessageAnimation::ID);
- break;
- }
- case td_api::inputInlineQueryResultAnimatedMpeg4::ID: {
- auto animated_mpeg4 = move_tl_object_as<td_api::inputInlineQueryResultAnimatedMpeg4>(input_result);
- type = "gif";
- id = std::move(animated_mpeg4->id_);
- title = std::move(animated_mpeg4->title_);
- thumbnail_url = std::move(animated_mpeg4->thumbnail_url_);
- content_url = std::move(animated_mpeg4->mpeg4_url_);
- content_type = "video/mp4";
- duration = animated_mpeg4->mpeg4_duration_;
- width = animated_mpeg4->mpeg4_width_;
- height = animated_mpeg4->mpeg4_height_;
- is_gallery = true;
-
- file_type = FileType::Animation;
- r_inline_message =
- get_inline_message(std::move(animated_mpeg4->input_message_content_),
- std::move(animated_mpeg4->reply_markup_), td_api::inputMessageAnimation::ID);
- break;
- }
- case td_api::inputInlineQueryResultArticle::ID: {
- auto article = move_tl_object_as<td_api::inputInlineQueryResultArticle>(input_result);
- type = "article";
- id = std::move(article->id_);
- if (!article->url_.empty()) {
- content_url = std::move(article->url_);
- content_type = "text/html";
- if (!article->hide_url_) {
- url = content_url;
- }
- }
- title = std::move(article->title_);
- description = std::move(article->description_);
- thumbnail_url = std::move(article->thumbnail_url_);
- if (!thumbnail_url.empty()) {
- thumbnail_width = article->thumbnail_width_;
- thumbnail_height = article->thumbnail_height_;
- }
- force_vertical = true;
+ TRY_RESULT_PROMISE(promise, result,
+ get_input_bot_inline_result(std::move(input_result), &is_gallery, &force_vertical));
+ results.push_back(std::move(result));
+ }
- r_inline_message =
- get_inline_message(std::move(article->input_message_content_), std::move(article->reply_markup_), -1);
- break;
- }
- case td_api::inputInlineQueryResultAudio::ID: {
- auto audio = move_tl_object_as<td_api::inputInlineQueryResultAudio>(input_result);
- type = "audio";
- id = std::move(audio->id_);
- title = std::move(audio->title_);
- description = std::move(audio->performer_);
- content_url = std::move(audio->audio_url_);
- content_type = "audio/mpeg";
- duration = audio->audio_duration_;
- force_vertical = true;
-
- file_type = FileType::Audio;
- r_inline_message = get_inline_message(std::move(audio->input_message_content_), std::move(audio->reply_markup_),
- td_api::inputMessageAudio::ID);
- break;
- }
- case td_api::inputInlineQueryResultContact::ID: {
- auto contact = move_tl_object_as<td_api::inputInlineQueryResultContact>(input_result);
- type = "contact";
- id = std::move(contact->id_);
- string phone_number = trim(contact->contact_->phone_number_);
- string first_name = trim(contact->contact_->first_name_);
- string last_name = trim(contact->contact_->last_name_);
- if (phone_number.empty()) {
- return promise.set_error(Status::Error(400, "Field \"phone_number\" must contain a valid phone number"));
- }
- if (first_name.empty()) {
- return promise.set_error(Status::Error(400, "Field \"first_name\" should be non-empty"));
- }
- title = last_name.empty() ? first_name : first_name + " " + last_name;
- description = std::move(phone_number);
- thumbnail_url = std::move(contact->thumbnail_url_);
- if (!thumbnail_url.empty()) {
- thumbnail_width = contact->thumbnail_width_;
- thumbnail_height = contact->thumbnail_height_;
- }
- force_vertical = true;
+ td_->create_handler<SetInlineBotResultsQuery>(std::move(promise))
+ ->send(inline_query_id, is_gallery && !force_vertical, is_personal, std::move(results), cache_time, next_offset,
+ switch_pm_text, switch_pm_parameter);
+}
- r_inline_message =
- get_inline_message(std::move(contact->input_message_content_), std::move(contact->reply_markup_), -1);
- break;
- }
- case td_api::inputInlineQueryResultDocument::ID: {
- auto document = move_tl_object_as<td_api::inputInlineQueryResultDocument>(input_result);
- type = "file";
- id = std::move(document->id_);
- title = std::move(document->title_);
- description = std::move(document->description_);
- thumbnail_url = std::move(document->thumbnail_url_);
- content_url = std::move(document->document_url_);
- content_type = std::move(document->mime_type_);
- thumbnail_width = document->thumbnail_width_;
- thumbnail_height = document->thumbnail_height_;
-
- if (content_url.find('.') != string::npos) {
- if (begins_with(content_type, "application/pdf")) {
- content_type = "application/pdf";
- } else if (begins_with(content_type, "application/zip")) {
- content_type = "application/zip";
- } else {
- return promise.set_error(Status::Error(400, "Unallowed document MIME type"));
- }
- }
+void InlineQueriesManager::get_simple_web_view_url(UserId bot_user_id, string &&url,
+ const td_api::object_ptr<td_api::themeParameters> &theme,
+ string &&platform, Promise<string> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id));
+ TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id));
- file_type = FileType::Document;
- r_inline_message = get_inline_message(std::move(document->input_message_content_),
- std::move(document->reply_markup_), td_api::inputMessageDocument::ID);
- break;
+ td_->create_handler<RequestSimpleWebViewQuery>(std::move(promise))
+ ->send(std::move(input_user), url, theme, std::move(platform));
+}
+
+void InlineQueriesManager::send_web_view_data(UserId bot_user_id, string &&button_text, string &&data,
+ Promise<Unit> &&promise) const {
+ TRY_RESULT_PROMISE(promise, bot_data, td_->contacts_manager_->get_bot_data(bot_user_id));
+
+ int64 random_id;
+ do {
+ random_id = Random::secure_int64();
+ } while (random_id == 0);
+
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(bot_user_id));
+
+ td_->create_handler<SendWebViewDataQuery>(std::move(promise))
+ ->send(std::move(input_user), random_id, button_text, data);
+}
+
+void InlineQueriesManager::answer_web_view_query(
+ const string &web_view_query_id, td_api::object_ptr<td_api::InputInlineQueryResult> &&input_result,
+ Promise<td_api::object_ptr<td_api::sentWebAppMessage>> &&promise) const {
+ CHECK(td_->auth_manager_->is_bot());
+
+ TRY_RESULT_PROMISE(promise, result, get_input_bot_inline_result(std::move(input_result), nullptr, nullptr));
+
+ td_->create_handler<SendWebViewResultMessageQuery>(std::move(promise))->send(web_view_query_id, std::move(result));
+}
+
+Result<tl_object_ptr<telegram_api::InputBotInlineResult>> InlineQueriesManager::get_input_bot_inline_result(
+ td_api::object_ptr<td_api::InputInlineQueryResult> &&result, bool *is_gallery, bool *force_vertical) const {
+ if (result == nullptr) {
+ return Status::Error(400, "Inline query result must be non-empty");
+ }
+
+ string id;
+ string url;
+ string type;
+ string title;
+ string description;
+ string thumbnail_url;
+ string thumbnail_type = "image/jpeg";
+ string content_url;
+ string content_type;
+ int32 thumbnail_width = 0;
+ int32 thumbnail_height = 0;
+ int32 width = 0;
+ int32 height = 0;
+ int32 duration = 0;
+
+ FileType file_type = FileType::Temp;
+ Result<tl_object_ptr<telegram_api::InputBotInlineMessage>> r_inline_message = Status::Error(500, "Uninited");
+ switch (result->get_id()) {
+ case td_api::inputInlineQueryResultAnimation::ID: {
+ auto animation = move_tl_object_as<td_api::inputInlineQueryResultAnimation>(result);
+ type = "gif";
+ id = std::move(animation->id_);
+ title = std::move(animation->title_);
+ thumbnail_url = std::move(animation->thumbnail_url_);
+ if (!animation->thumbnail_mime_type_.empty()) {
+ thumbnail_type = std::move(animation->thumbnail_mime_type_);
+ }
+ content_url = std::move(animation->video_url_);
+ content_type = std::move(animation->video_mime_type_);
+ if (content_type != "image/gif" && content_type != "video/mp4") {
+ return Status::Error(400, "Wrong animation MIME type specified");
+ }
+ duration = animation->video_duration_;
+ width = animation->video_width_;
+ height = animation->video_height_;
+ if (is_gallery != nullptr) {
+ *is_gallery = true;
}
- case td_api::inputInlineQueryResultGame::ID: {
- auto game = move_tl_object_as<td_api::inputInlineQueryResultGame>(input_result);
- auto r_reply_markup = get_reply_markup(std::move(game->reply_markup_), true, true, false, true);
- if (r_reply_markup.is_error()) {
- return promise.set_error(r_reply_markup.move_as_error());
- }
- auto input_reply_markup = get_input_reply_markup(r_reply_markup.ok());
- int32 flags = 0;
- if (input_reply_markup != nullptr) {
- flags |= telegram_api::inputBotInlineMessageGame::REPLY_MARKUP_MASK;
+ file_type = FileType::Animation;
+ r_inline_message = get_inline_message(std::move(animation->input_message_content_),
+ std::move(animation->reply_markup_), td_api::inputMessageAnimation::ID);
+ break;
+ }
+ case td_api::inputInlineQueryResultArticle::ID: {
+ auto article = move_tl_object_as<td_api::inputInlineQueryResultArticle>(result);
+ type = "article";
+ id = std::move(article->id_);
+ if (!article->url_.empty()) {
+ content_url = std::move(article->url_);
+ content_type = "text/html";
+ if (!article->hide_url_) {
+ url = content_url;
}
- auto result = make_tl_object<telegram_api::inputBotInlineResultGame>(
- game->id_, game->game_short_name_,
- make_tl_object<telegram_api::inputBotInlineMessageGame>(flags, std::move(input_reply_markup)));
- results.push_back(std::move(result));
- continue;
}
- case td_api::inputInlineQueryResultLocation::ID: {
- auto location = move_tl_object_as<td_api::inputInlineQueryResultLocation>(input_result);
- type = "geo";
- id = std::move(location->id_);
- title = std::move(location->title_);
- description = to_string(location->location_->latitude_) + " " + to_string(location->location_->longitude_);
- thumbnail_url = std::move(location->thumbnail_url_);
- // duration = location->live_period_;
- if (!thumbnail_url.empty()) {
- thumbnail_width = location->thumbnail_width_;
- thumbnail_height = location->thumbnail_height_;
- }
-
- r_inline_message =
- get_inline_message(std::move(location->input_message_content_), std::move(location->reply_markup_), -1);
- break;
+ title = std::move(article->title_);
+ description = std::move(article->description_);
+ thumbnail_url = std::move(article->thumbnail_url_);
+ if (!thumbnail_url.empty()) {
+ thumbnail_width = article->thumbnail_width_;
+ thumbnail_height = article->thumbnail_height_;
}
- case td_api::inputInlineQueryResultPhoto::ID: {
- auto photo = move_tl_object_as<td_api::inputInlineQueryResultPhoto>(input_result);
- type = "photo";
- id = std::move(photo->id_);
- title = std::move(photo->title_);
- description = std::move(photo->description_);
- thumbnail_url = std::move(photo->thumbnail_url_);
- content_url = std::move(photo->photo_url_);
- content_type = "image/jpeg";
- width = photo->photo_width_;
- height = photo->photo_height_;
- is_gallery = true;
-
- file_type = FileType::Photo;
- r_inline_message = get_inline_message(std::move(photo->input_message_content_), std::move(photo->reply_markup_),
- td_api::inputMessagePhoto::ID);
- break;
+ if (force_vertical != nullptr) {
+ *force_vertical = true;
}
- case td_api::inputInlineQueryResultSticker::ID: {
- auto sticker = move_tl_object_as<td_api::inputInlineQueryResultSticker>(input_result);
- type = "sticker";
- id = std::move(sticker->id_);
- thumbnail_url = std::move(sticker->thumbnail_url_);
- content_url = std::move(sticker->sticker_url_);
- content_type = "image/webp";
- width = sticker->sticker_width_;
- height = sticker->sticker_height_;
- is_gallery = true;
-
- if (content_url.find('.') != string::npos) {
- return promise.set_error(Status::Error(400, "Wrong sticker_file_id specified"));
- }
- file_type = FileType::Sticker;
- r_inline_message = get_inline_message(std::move(sticker->input_message_content_),
- std::move(sticker->reply_markup_), td_api::inputMessageSticker::ID);
- break;
+ r_inline_message =
+ get_inline_message(std::move(article->input_message_content_), std::move(article->reply_markup_), -1);
+ break;
+ }
+ case td_api::inputInlineQueryResultAudio::ID: {
+ auto audio = move_tl_object_as<td_api::inputInlineQueryResultAudio>(result);
+ type = "audio";
+ id = std::move(audio->id_);
+ title = std::move(audio->title_);
+ description = std::move(audio->performer_);
+ content_url = std::move(audio->audio_url_);
+ content_type = "audio/mpeg";
+ duration = audio->audio_duration_;
+ if (force_vertical != nullptr) {
+ *force_vertical = true;
}
- case td_api::inputInlineQueryResultVenue::ID: {
- auto venue = move_tl_object_as<td_api::inputInlineQueryResultVenue>(input_result);
- type = "venue";
- id = std::move(venue->id_);
- title = std::move(venue->venue_->title_);
- description = std::move(venue->venue_->address_);
- thumbnail_url = std::move(venue->thumbnail_url_);
- if (!thumbnail_url.empty()) {
- thumbnail_width = venue->thumbnail_width_;
- thumbnail_height = venue->thumbnail_height_;
- }
- r_inline_message =
- get_inline_message(std::move(venue->input_message_content_), std::move(venue->reply_markup_), -1);
- break;
+ file_type = FileType::Audio;
+ r_inline_message = get_inline_message(std::move(audio->input_message_content_), std::move(audio->reply_markup_),
+ td_api::inputMessageAudio::ID);
+ break;
+ }
+ case td_api::inputInlineQueryResultContact::ID: {
+ auto contact = move_tl_object_as<td_api::inputInlineQueryResultContact>(result);
+ if (contact->contact_ == nullptr) {
+ return Status::Error(400, "Contact must be non-empty");
}
- case td_api::inputInlineQueryResultVideo::ID: {
- auto video = move_tl_object_as<td_api::inputInlineQueryResultVideo>(input_result);
- type = "video";
- id = std::move(video->id_);
- title = std::move(video->title_);
- description = std::move(video->description_);
- thumbnail_url = std::move(video->thumbnail_url_);
- content_url = std::move(video->video_url_);
- content_type = std::move(video->mime_type_);
- width = video->video_width_;
- height = video->video_height_;
- duration = video->video_duration_;
-
- if (content_url.find('.') != string::npos) {
- if (begins_with(content_type, "video/mp4")) {
- content_type = "video/mp4";
- } else if (begins_with(content_type, "text/html")) {
- content_type = "text/html";
- } else {
- return promise.set_error(Status::Error(400, "Unallowed video MIME type"));
- }
- }
-
- file_type = FileType::Video;
- r_inline_message = get_inline_message(std::move(video->input_message_content_), std::move(video->reply_markup_),
- td_api::inputMessageVideo::ID);
- break;
+ type = "contact";
+ id = std::move(contact->id_);
+ string phone_number = trim(contact->contact_->phone_number_);
+ string first_name = trim(contact->contact_->first_name_);
+ string last_name = trim(contact->contact_->last_name_);
+ if (phone_number.empty()) {
+ return Status::Error(400, "Field \"phone_number\" must contain a valid phone number");
}
- case td_api::inputInlineQueryResultVoiceNote::ID: {
- auto voice_note = move_tl_object_as<td_api::inputInlineQueryResultVoiceNote>(input_result);
- type = "voice";
- id = std::move(voice_note->id_);
- title = std::move(voice_note->title_);
- content_url = std::move(voice_note->voice_note_url_);
- content_type = "audio/ogg";
- duration = voice_note->voice_note_duration_;
- force_vertical = true;
-
- file_type = FileType::VoiceNote;
- r_inline_message = get_inline_message(std::move(voice_note->input_message_content_),
- std::move(voice_note->reply_markup_), td_api::inputMessageVoiceNote::ID);
- break;
+ if (first_name.empty()) {
+ return Status::Error(400, "Field \"first_name\" must be non-empty");
}
- default:
- UNREACHABLE();
- break;
- }
- if (r_inline_message.is_error()) {
- return promise.set_error(r_inline_message.move_as_error());
- }
- auto inline_message = r_inline_message.move_as_ok();
- if (inline_message->get_id() == telegram_api::inputBotInlineMessageMediaAuto::ID && file_type == FileType::Temp) {
- return promise.set_error(Status::Error(400, "Sent message content should be explicitly specified"));
- }
-
- int32 flags = 0;
- if (!title.empty()) {
- flags |= telegram_api::inputBotInlineResult::TITLE_MASK;
- if (!clean_input_string(title)) {
- return promise.set_error(Status::Error(400, "Strings must be encoded in UTF-8"));
+ if (last_name.empty()) {
+ title = std::move(first_name);
+ } else {
+ title = PSTRING() << first_name << ' ' << last_name;
}
- }
- if (!description.empty()) {
- flags |= telegram_api::inputBotInlineResult::DESCRIPTION_MASK;
- if (!clean_input_string(description)) {
- return promise.set_error(Status::Error(400, "Strings must be encoded in UTF-8"));
+ description = std::move(phone_number);
+ thumbnail_url = std::move(contact->thumbnail_url_);
+ if (!thumbnail_url.empty()) {
+ thumbnail_width = contact->thumbnail_width_;
+ thumbnail_height = contact->thumbnail_height_;
}
+ if (force_vertical != nullptr) {
+ *force_vertical = true;
+ }
+
+ r_inline_message =
+ get_inline_message(std::move(contact->input_message_content_), std::move(contact->reply_markup_), -1);
+ break;
}
+ case td_api::inputInlineQueryResultDocument::ID: {
+ auto document = move_tl_object_as<td_api::inputInlineQueryResultDocument>(result);
+ type = "file";
+ id = std::move(document->id_);
+ title = std::move(document->title_);
+ description = std::move(document->description_);
+ thumbnail_url = std::move(document->thumbnail_url_);
+ content_url = std::move(document->document_url_);
+ content_type = std::move(document->mime_type_);
+ thumbnail_width = document->thumbnail_width_;
+ thumbnail_height = document->thumbnail_height_;
+
+ if (content_url.find('.') != string::npos) {
+ if (begins_with(content_type, "application/pdf")) {
+ content_type = "application/pdf";
+ } else if (begins_with(content_type, "application/zip")) {
+ content_type = "application/zip";
+ } else {
+ return Status::Error(400, "Unallowed document MIME type");
+ }
+ }
- if (file_type != FileType::Temp && content_url.find('.') == string::npos) {
- auto r_file_id = td_->file_manager_->get_input_file_id(
- file_type, make_tl_object<td_api::inputFileRemote>(content_url), DialogId(), false, false);
- if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(400, r_file_id.error().message()));
+ file_type = FileType::Document;
+ r_inline_message = get_inline_message(std::move(document->input_message_content_),
+ std::move(document->reply_markup_), td_api::inputMessageDocument::ID);
+ break;
+ }
+ case td_api::inputInlineQueryResultGame::ID: {
+ auto game = move_tl_object_as<td_api::inputInlineQueryResultGame>(result);
+ auto r_reply_markup = get_reply_markup(std::move(game->reply_markup_), true, true, false, true);
+ if (r_reply_markup.is_error()) {
+ return r_reply_markup.move_as_error();
}
- auto file_id = r_file_id.ok();
- FileView file_view = td_->file_manager_->get_file_view(file_id);
- CHECK(file_view.has_remote_location());
- if (file_view.is_encrypted()) {
- return promise.set_error(Status::Error(400, "Can't send encrypted file"));
+
+ auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_reply_markup.ok());
+ int32 flags = 0;
+ if (input_reply_markup != nullptr) {
+ flags |= telegram_api::inputBotInlineMessageGame::REPLY_MARKUP_MASK;
}
- if (file_view.remote_location().is_web()) {
- return promise.set_error(Status::Error(400, "Can't send web file"));
+ return make_tl_object<telegram_api::inputBotInlineResultGame>(
+ game->id_, game->game_short_name_,
+ make_tl_object<telegram_api::inputBotInlineMessageGame>(flags, std::move(input_reply_markup)));
+ }
+ case td_api::inputInlineQueryResultLocation::ID: {
+ auto location = move_tl_object_as<td_api::inputInlineQueryResultLocation>(result);
+ if (location->location_ == nullptr) {
+ return Status::Error(400, "Location must be non-empty");
}
-
- if (file_type == FileType::Photo) {
- auto result = make_tl_object<telegram_api::inputBotInlineResultPhoto>(
- id, type, file_view.remote_location().as_input_photo(), std::move(inline_message));
- results.push_back(std::move(result));
- continue;
+ type = "geo";
+ id = std::move(location->id_);
+ title = std::move(location->title_);
+ description = PSTRING() << location->location_->latitude_ << ' ' << location->location_->longitude_;
+ thumbnail_url = std::move(location->thumbnail_url_);
+ // duration = location->live_period_;
+ if (!thumbnail_url.empty()) {
+ thumbnail_width = location->thumbnail_width_;
+ thumbnail_height = location->thumbnail_height_;
}
- auto result = make_tl_object<telegram_api::inputBotInlineResultDocument>(
- flags, id, type, title, description, file_view.remote_location().as_input_document(),
- std::move(inline_message));
- results.push_back(std::move(result));
- continue;
+ r_inline_message =
+ get_inline_message(std::move(location->input_message_content_), std::move(location->reply_markup_), -1);
+ break;
}
-
- if (!url.empty()) {
- flags |= telegram_api::inputBotInlineResult::URL_MASK;
- if (!clean_input_string(url)) {
- return promise.set_error(Status::Error(400, "Strings must be encoded in UTF-8"));
+ case td_api::inputInlineQueryResultPhoto::ID: {
+ auto photo = move_tl_object_as<td_api::inputInlineQueryResultPhoto>(result);
+ type = "photo";
+ id = std::move(photo->id_);
+ title = std::move(photo->title_);
+ description = std::move(photo->description_);
+ thumbnail_url = std::move(photo->thumbnail_url_);
+ content_url = std::move(photo->photo_url_);
+ content_type = "image/jpeg";
+ width = photo->photo_width_;
+ height = photo->photo_height_;
+ if (is_gallery != nullptr) {
+ *is_gallery = true;
}
+
+ file_type = FileType::Photo;
+ r_inline_message = get_inline_message(std::move(photo->input_message_content_), std::move(photo->reply_markup_),
+ td_api::inputMessagePhoto::ID);
+ break;
}
- tl_object_ptr<telegram_api::inputWebDocument> thumbnail;
- if (!thumbnail_url.empty()) {
- flags |= telegram_api::inputBotInlineResult::THUMB_MASK;
- if (!clean_input_string(thumbnail_url)) {
- return promise.set_error(Status::Error(400, "Strings must be encoded in UTF-8"));
+ case td_api::inputInlineQueryResultSticker::ID: {
+ auto sticker = move_tl_object_as<td_api::inputInlineQueryResultSticker>(result);
+ type = "sticker";
+ id = std::move(sticker->id_);
+ thumbnail_url = std::move(sticker->thumbnail_url_);
+ content_url = std::move(sticker->sticker_url_);
+ content_type = "image/webp"; // or "application/x-tgsticker"/"video/webm"; not used for previously uploaded files
+ width = sticker->sticker_width_;
+ height = sticker->sticker_height_;
+ if (is_gallery != nullptr) {
+ *is_gallery = true;
}
- vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
- if (thumbnail_width > 0 && thumbnail_height > 0) {
- attributes.push_back(
- make_tl_object<telegram_api::documentAttributeImageSize>(thumbnail_width, thumbnail_height));
+ if (content_url.find('.') != string::npos) {
+ return Status::Error(400, "Wrong sticker_file_id specified");
}
- thumbnail = make_tl_object<telegram_api::inputWebDocument>(thumbnail_url, 0, "image/jpeg", std::move(attributes));
+
+ file_type = FileType::Sticker;
+ r_inline_message = get_inline_message(std::move(sticker->input_message_content_),
+ std::move(sticker->reply_markup_), td_api::inputMessageSticker::ID);
+ break;
}
- tl_object_ptr<telegram_api::inputWebDocument> content;
- if (!content_url.empty() || !content_type.empty()) {
- flags |= telegram_api::inputBotInlineResult::CONTENT_MASK;
- if (!clean_input_string(content_url)) {
- return promise.set_error(Status::Error(400, "Strings must be encoded in UTF-8"));
+ case td_api::inputInlineQueryResultVenue::ID: {
+ auto venue = move_tl_object_as<td_api::inputInlineQueryResultVenue>(result);
+ if (venue->venue_ == nullptr) {
+ return Status::Error(400, "Venue must be non-empty");
}
- if (!clean_input_string(content_type)) {
- return promise.set_error(Status::Error(400, "Strings must be encoded in UTF-8"));
+ type = "venue";
+ id = std::move(venue->id_);
+ title = std::move(venue->venue_->title_);
+ description = std::move(venue->venue_->address_);
+ thumbnail_url = std::move(venue->thumbnail_url_);
+ if (!thumbnail_url.empty()) {
+ thumbnail_width = venue->thumbnail_width_;
+ thumbnail_height = venue->thumbnail_height_;
}
- vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
- if (width > 0 && height > 0) {
- if (duration > 0 && !begins_with(content_type, "image/")) {
- attributes.push_back(make_tl_object<telegram_api::documentAttributeVideo>(
- 0, false /*ignored*/, false /*ignored*/, duration, width, height));
+ r_inline_message =
+ get_inline_message(std::move(venue->input_message_content_), std::move(venue->reply_markup_), -1);
+ break;
+ }
+ case td_api::inputInlineQueryResultVideo::ID: {
+ auto video = move_tl_object_as<td_api::inputInlineQueryResultVideo>(result);
+ type = "video";
+ id = std::move(video->id_);
+ title = std::move(video->title_);
+ description = std::move(video->description_);
+ thumbnail_url = std::move(video->thumbnail_url_);
+ content_url = std::move(video->video_url_);
+ content_type = std::move(video->mime_type_);
+ width = video->video_width_;
+ height = video->video_height_;
+ duration = video->video_duration_;
+
+ if (content_url.find('.') != string::npos) {
+ if (begins_with(content_type, "video/mp4")) {
+ content_type = "video/mp4";
+ } else if (begins_with(content_type, "text/html")) {
+ content_type = "text/html";
} else {
- attributes.push_back(make_tl_object<telegram_api::documentAttributeImageSize>(width, height));
- }
- } else if (duration > 0) {
- if (type == "audio") {
- attributes.push_back(make_tl_object<telegram_api::documentAttributeAudio>(
- telegram_api::documentAttributeAudio::TITLE_MASK | telegram_api::documentAttributeAudio::PERFORMER_MASK,
- false /*ignored*/, duration, title, description, BufferSlice()));
- } else if (type == "voice") {
- attributes.push_back(make_tl_object<telegram_api::documentAttributeAudio>(
- telegram_api::documentAttributeAudio::VOICE_MASK, false /*ignored*/, duration, "", "", BufferSlice()));
+ return Status::Error(400, "Unallowed video MIME type");
}
}
- attributes.push_back(make_tl_object<telegram_api::documentAttributeFilename>(get_url_file_name(content_url)));
- content = make_tl_object<telegram_api::inputWebDocument>(content_url, 0, content_type, std::move(attributes));
+ file_type = FileType::Video;
+ r_inline_message = get_inline_message(std::move(video->input_message_content_), std::move(video->reply_markup_),
+ td_api::inputMessageVideo::ID);
+ break;
+ }
+ case td_api::inputInlineQueryResultVoiceNote::ID: {
+ auto voice_note = move_tl_object_as<td_api::inputInlineQueryResultVoiceNote>(result);
+ type = "voice";
+ id = std::move(voice_note->id_);
+ title = std::move(voice_note->title_);
+ content_url = std::move(voice_note->voice_note_url_);
+ content_type = "audio/ogg";
+ duration = voice_note->voice_note_duration_;
+ if (force_vertical != nullptr) {
+ *force_vertical = true;
+ }
+
+ file_type = FileType::VoiceNote;
+ r_inline_message = get_inline_message(std::move(voice_note->input_message_content_),
+ std::move(voice_note->reply_markup_), td_api::inputMessageVoiceNote::ID);
+ break;
}
+ default:
+ UNREACHABLE();
+ break;
+ }
+ if (r_inline_message.is_error()) {
+ return r_inline_message.move_as_error();
+ }
+ auto inline_message = r_inline_message.move_as_ok();
+ if (inline_message->get_id() == telegram_api::inputBotInlineMessageMediaAuto::ID && file_type == FileType::Temp) {
+ return Status::Error(400, "Sent message content must be explicitly specified");
+ }
- auto result = make_tl_object<telegram_api::inputBotInlineResult>(
- flags, id, type, title, description, url, std::move(thumbnail), std::move(content), std::move(inline_message));
- results.push_back(std::move(result));
+ if (duration < 0) {
+ duration = 0;
}
- td_->create_handler<SetInlineBotResultsQuery>(std::move(promise))
- ->send(inline_query_id, is_gallery && !force_vertical, is_personal, std::move(results), cache_time, next_offset,
- switch_pm_text, switch_pm_parameter);
+ int32 flags = 0;
+ if (!title.empty()) {
+ flags |= telegram_api::inputBotInlineResult::TITLE_MASK;
+ if (!clean_input_string(title)) {
+ return Status::Error(400, "Strings must be encoded in UTF-8");
+ }
+ }
+ if (!description.empty()) {
+ flags |= telegram_api::inputBotInlineResult::DESCRIPTION_MASK;
+ if (!clean_input_string(description)) {
+ return Status::Error(400, "Strings must be encoded in UTF-8");
+ }
+ }
+
+ if (file_type != FileType::Temp && content_url.find('.') == string::npos) {
+ auto r_file_id = td_->file_manager_->get_input_file_id(
+ file_type, make_tl_object<td_api::inputFileRemote>(content_url), DialogId(), false, false);
+ if (r_file_id.is_error()) {
+ return Status::Error(400, r_file_id.error().message());
+ }
+ auto file_id = r_file_id.ok();
+ FileView file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(file_view.has_remote_location());
+ if (file_view.is_encrypted()) {
+ return Status::Error(400, "Can't send encrypted file");
+ }
+ if (file_view.main_remote_location().is_web()) {
+ return Status::Error(400, "Can't send web file");
+ }
+
+ if (file_type == FileType::Photo) {
+ return make_tl_object<telegram_api::inputBotInlineResultPhoto>(
+ id, type, file_view.main_remote_location().as_input_photo(), std::move(inline_message));
+ }
+
+ return make_tl_object<telegram_api::inputBotInlineResultDocument>(
+ flags, id, type, title, description, file_view.main_remote_location().as_input_document(),
+ std::move(inline_message));
+ }
+
+ if (!url.empty()) {
+ flags |= telegram_api::inputBotInlineResult::URL_MASK;
+ if (!clean_input_string(url)) {
+ return Status::Error(400, "Strings must be encoded in UTF-8");
+ }
+ }
+ tl_object_ptr<telegram_api::inputWebDocument> thumbnail;
+ if (!thumbnail_url.empty()) {
+ flags |= telegram_api::inputBotInlineResult::THUMB_MASK;
+ if (!clean_input_string(thumbnail_url)) {
+ return Status::Error(400, "Strings must be encoded in UTF-8");
+ }
+ vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
+ if (thumbnail_width > 0 && thumbnail_height > 0) {
+ attributes.push_back(make_tl_object<telegram_api::documentAttributeImageSize>(thumbnail_width, thumbnail_height));
+ }
+ thumbnail = make_tl_object<telegram_api::inputWebDocument>(thumbnail_url, 0, thumbnail_type, std::move(attributes));
+ }
+ tl_object_ptr<telegram_api::inputWebDocument> content;
+ if (!content_url.empty() || !content_type.empty()) {
+ flags |= telegram_api::inputBotInlineResult::CONTENT_MASK;
+ if (!clean_input_string(content_url)) {
+ return Status::Error(400, "Strings must be encoded in UTF-8");
+ }
+ if (!clean_input_string(content_type)) {
+ return Status::Error(400, "Strings must be encoded in UTF-8");
+ }
+
+ vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
+ if (width > 0 && height > 0) {
+ if ((duration > 0 || type == "video" || content_type == "video/mp4") && !begins_with(content_type, "image/")) {
+ attributes.push_back(make_tl_object<telegram_api::documentAttributeVideo>(
+ 0, false /*ignored*/, false /*ignored*/, duration, width, height));
+ } else {
+ attributes.push_back(make_tl_object<telegram_api::documentAttributeImageSize>(width, height));
+ }
+ } else if (type == "audio") {
+ attributes.push_back(make_tl_object<telegram_api::documentAttributeAudio>(
+ telegram_api::documentAttributeAudio::TITLE_MASK | telegram_api::documentAttributeAudio::PERFORMER_MASK,
+ false /*ignored*/, duration, title, description, BufferSlice()));
+ } else if (type == "voice") {
+ attributes.push_back(make_tl_object<telegram_api::documentAttributeAudio>(
+ telegram_api::documentAttributeAudio::VOICE_MASK, false /*ignored*/, duration, "", "", BufferSlice()));
+ }
+ attributes.push_back(make_tl_object<telegram_api::documentAttributeFilename>(get_url_file_name(content_url)));
+
+ content = make_tl_object<telegram_api::inputWebDocument>(content_url, 0, content_type, std::move(attributes));
+ }
+
+ return make_tl_object<telegram_api::inputBotInlineResult>(
+ flags, id, type, title, description, url, std::move(thumbnail), std::move(content), std::move(inline_message));
}
uint64 InlineQueriesManager::send_inline_query(UserId bot_user_id, DialogId dialog_id, Location user_location,
const string &query, const string &offset, Promise<Unit> &&promise) {
if (td_->auth_manager_->is_bot()) {
- promise.set_error(Status::Error(5, "Bot can't send inline queries to other bot"));
+ promise.set_error(Status::Error(400, "Bot can't send inline queries to other bot"));
return 0;
}
@@ -881,18 +952,47 @@ uint64 InlineQueriesManager::send_inline_query(UserId bot_user_id, DialogId dial
return 0;
}
if (!r_bot_data.ok().is_inline) {
- promise.set_error(Status::Error(5, "Bot doesn't support inline queries"));
+ promise.set_error(Status::Error(400, "Bot doesn't support inline queries"));
return 0;
}
- uint64 query_hash = std::hash<std::string>()(trim(query));
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ input_peer = make_tl_object<telegram_api::inputPeerEmpty>();
+ }
+
+ auto peer_type = [&] {
+ switch (input_peer->get_id()) {
+ case telegram_api::inputPeerEmpty::ID:
+ return 0;
+ case telegram_api::inputPeerSelf::ID:
+ return 1;
+ case telegram_api::inputPeerChat::ID:
+ return 2;
+ case telegram_api::inputPeerUser::ID:
+ case telegram_api::inputPeerUserFromMessage::ID:
+ return dialog_id == DialogId(bot_user_id) ? 3 : 4;
+ case telegram_api::inputPeerChannel::ID:
+ case telegram_api::inputPeerChannelFromMessage::ID:
+ return 5 + static_cast<int>(td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()));
+ default:
+ UNREACHABLE();
+ return -1;
+ }
+ }();
+
+ uint64 query_hash = Hash<string>()(trim(query));
query_hash = query_hash * 2023654985u + bot_user_id.get();
- query_hash = query_hash * 2023654985u + std::hash<std::string>()(offset);
+ query_hash = query_hash * 2023654985u + static_cast<uint64>(peer_type);
+ query_hash = query_hash * 2023654985u + Hash<string>()(offset);
if (r_bot_data.ok().need_location) {
query_hash = query_hash * 2023654985u + static_cast<uint64>(user_location.get_latitude() * 1e4);
query_hash = query_hash * 2023654985u + static_cast<uint64>(user_location.get_longitude() * 1e4);
}
query_hash &= 0x7FFFFFFFFFFFFFFF;
+ if (query_hash == 0) {
+ query_hash = 1;
+ }
auto it = inline_query_results_.find(query_hash);
if (it != inline_query_results_.end()) {
@@ -907,12 +1007,13 @@ uint64 InlineQueriesManager::send_inline_query(UserId bot_user_id, DialogId dial
if (pending_inline_query_ != nullptr) {
LOG(INFO) << "Drop inline query " << pending_inline_query_->query_hash;
- on_get_inline_query_results(pending_inline_query_->bot_user_id, pending_inline_query_->query_hash, nullptr);
- pending_inline_query_->promise.set_error(Status::Error(406, "Request cancelled"));
+ on_get_inline_query_results(pending_inline_query_->dialog_id, pending_inline_query_->bot_user_id,
+ pending_inline_query_->query_hash, nullptr);
+ pending_inline_query_->promise.set_error(Status::Error(406, "Request canceled"));
}
- pending_inline_query_ = make_unique<PendingInlineQuery>(
- PendingInlineQuery{query_hash, bot_user_id, dialog_id, user_location, query, offset, std::move(promise)});
+ pending_inline_query_ = make_unique<PendingInlineQuery>(PendingInlineQuery{
+ query_hash, bot_user_id, dialog_id, std::move(input_peer), user_location, query, offset, std::move(promise)});
loop();
@@ -928,17 +1029,17 @@ void InlineQueriesManager::loop() {
auto now = Time::now();
if (now >= next_inline_query_time_) {
LOG(INFO) << "Send inline query " << pending_inline_query_->query_hash;
- auto bot_input_user = td_->contacts_manager_->get_input_user(pending_inline_query_->bot_user_id);
- if (bot_input_user != nullptr) {
+ auto r_bot_input_user = td_->contacts_manager_->get_input_user(pending_inline_query_->bot_user_id);
+ if (r_bot_input_user.is_ok()) {
if (!sent_query_.empty()) {
LOG(INFO) << "Cancel inline query request";
cancel_query(sent_query_);
}
- sent_query_ =
- td_->create_handler<GetInlineBotResultsQuery>(std::move(pending_inline_query_->promise))
- ->send(pending_inline_query_->bot_user_id, std::move(bot_input_user), pending_inline_query_->dialog_id,
- pending_inline_query_->user_location, pending_inline_query_->query, pending_inline_query_->offset,
- pending_inline_query_->query_hash);
+ sent_query_ = td_->create_handler<GetInlineBotResultsQuery>(std::move(pending_inline_query_->promise))
+ ->send(pending_inline_query_->bot_user_id, pending_inline_query_->dialog_id,
+ r_bot_input_user.move_as_ok(), std::move(pending_inline_query_->input_peer),
+ pending_inline_query_->user_location, pending_inline_query_->query,
+ pending_inline_query_->offset, pending_inline_query_->query_hash);
next_inline_query_time_ = now + INLINE_QUERY_DELAY_MS * 1e-3;
}
@@ -965,14 +1066,14 @@ static tl_object_ptr<T> copy(const tl_object_ptr<T> &obj) {
template <>
td_api::object_ptr<td_api::localFile> copy(const td_api::localFile &obj) {
- return td_api::make_object<td_api::localFile>(obj.path_, obj.can_be_downloaded_, obj.can_be_deleted_,
- obj.is_downloading_active_, obj.is_downloading_completed_,
- obj.downloaded_prefix_size_, obj.downloaded_size_);
+ return td_api::make_object<td_api::localFile>(
+ obj.path_, obj.can_be_downloaded_, obj.can_be_deleted_, obj.is_downloading_active_, obj.is_downloading_completed_,
+ obj.download_offset_, obj.downloaded_prefix_size_, obj.downloaded_size_);
}
template <>
td_api::object_ptr<td_api::remoteFile> copy(const td_api::remoteFile &obj) {
- return td_api::make_object<td_api::remoteFile>(obj.id_, obj.is_uploading_active_, obj.is_uploading_completed_,
- obj.uploaded_size_);
+ return td_api::make_object<td_api::remoteFile>(obj.id_, obj.unique_id_, obj.is_uploading_active_,
+ obj.is_uploading_completed_, obj.uploaded_size_);
}
template <>
@@ -987,8 +1088,14 @@ td_api::object_ptr<td_api::file> copy(const td_api::file &obj) {
}
template <>
+tl_object_ptr<td_api::minithumbnail> copy(const td_api::minithumbnail &obj) {
+ return td_api::make_object<td_api::minithumbnail>(obj.width_, obj.height_, obj.data_);
+}
+
+template <>
tl_object_ptr<td_api::photoSize> copy(const td_api::photoSize &obj) {
- return make_tl_object<td_api::photoSize>(obj.type_, copy(obj.photo_), obj.width_, obj.height_);
+ return td_api::make_object<td_api::photoSize>(obj.type_, copy(obj.photo_), obj.width_, obj.height_,
+ vector<int32>(obj.progressive_sizes_));
}
static tl_object_ptr<td_api::photoSize> copy_photo_size(const tl_object_ptr<td_api::photoSize> &obj) {
@@ -996,16 +1103,60 @@ static tl_object_ptr<td_api::photoSize> copy_photo_size(const tl_object_ptr<td_a
}
template <>
+tl_object_ptr<td_api::thumbnail> copy(const td_api::thumbnail &obj) {
+ auto format = [&]() -> td_api::object_ptr<td_api::ThumbnailFormat> {
+ switch (obj.format_->get_id()) {
+ case td_api::thumbnailFormatJpeg::ID:
+ return td_api::make_object<td_api::thumbnailFormatJpeg>();
+ case td_api::thumbnailFormatPng::ID:
+ return td_api::make_object<td_api::thumbnailFormatPng>();
+ case td_api::thumbnailFormatWebp::ID:
+ return td_api::make_object<td_api::thumbnailFormatWebp>();
+ case td_api::thumbnailFormatTgs::ID:
+ return td_api::make_object<td_api::thumbnailFormatTgs>();
+ case td_api::thumbnailFormatMpeg4::ID:
+ return td_api::make_object<td_api::thumbnailFormatMpeg4>();
+ case td_api::thumbnailFormatGif::ID:
+ return td_api::make_object<td_api::thumbnailFormatGif>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+ }();
+
+ return td_api::make_object<td_api::thumbnail>(std::move(format), obj.width_, obj.height_, copy(obj.file_));
+}
+
+static tl_object_ptr<td_api::thumbnail> copy_thumbnail(const tl_object_ptr<td_api::thumbnail> &obj) {
+ return copy(obj);
+}
+
+template <>
+tl_object_ptr<td_api::StickerFormat> copy(const td_api::StickerFormat &obj) {
+ switch (obj.get_id()) {
+ case td_api::stickerFormatWebp::ID:
+ return td_api::make_object<td_api::stickerFormatWebp>();
+ case td_api::stickerFormatTgs::ID:
+ return td_api::make_object<td_api::stickerFormatTgs>();
+ case td_api::stickerFormatWebm::ID:
+ return td_api::make_object<td_api::stickerFormatWebm>();
+ default:
+ UNREACHABLE();
+ }
+ return nullptr;
+}
+
+template <>
tl_object_ptr<td_api::MaskPoint> copy(const td_api::MaskPoint &obj) {
switch (obj.get_id()) {
case td_api::maskPointForehead::ID:
- return make_tl_object<td_api::maskPointForehead>();
+ return td_api::make_object<td_api::maskPointForehead>();
case td_api::maskPointEyes::ID:
- return make_tl_object<td_api::maskPointEyes>();
+ return td_api::make_object<td_api::maskPointEyes>();
case td_api::maskPointMouth::ID:
- return make_tl_object<td_api::maskPointMouth>();
+ return td_api::make_object<td_api::maskPointMouth>();
case td_api::maskPointChin::ID:
- return make_tl_object<td_api::maskPointChin>();
+ return td_api::make_object<td_api::maskPointChin>();
default:
UNREACHABLE();
}
@@ -1014,149 +1165,232 @@ tl_object_ptr<td_api::MaskPoint> copy(const td_api::MaskPoint &obj) {
template <>
tl_object_ptr<td_api::maskPosition> copy(const td_api::maskPosition &obj) {
- return make_tl_object<td_api::maskPosition>(copy(obj.point_), obj.x_shift_, obj.y_shift_, obj.scale_);
+ return td_api::make_object<td_api::maskPosition>(copy(obj.point_), obj.x_shift_, obj.y_shift_, obj.scale_);
+}
+
+template <>
+tl_object_ptr<td_api::StickerType> copy(const td_api::StickerType &obj) {
+ switch (obj.get_id()) {
+ case td_api::stickerTypeRegular::ID:
+ return td_api::make_object<td_api::stickerTypeRegular>();
+ case td_api::stickerTypeMask::ID:
+ return td_api::make_object<td_api::stickerTypeMask>();
+ case td_api::stickerTypeCustomEmoji::ID:
+ return td_api::make_object<td_api::stickerTypeCustomEmoji>();
+ default:
+ UNREACHABLE();
+ }
+ return nullptr;
+}
+
+template <>
+tl_object_ptr<td_api::point> copy(const td_api::point &obj) {
+ return td_api::make_object<td_api::point>(obj.x_, obj.y_);
+}
+
+template <>
+tl_object_ptr<td_api::VectorPathCommand> copy(const td_api::VectorPathCommand &obj) {
+ switch (obj.get_id()) {
+ case td_api::vectorPathCommandLine::ID: {
+ auto &command = static_cast<const td_api::vectorPathCommandLine &>(obj);
+ return td_api::make_object<td_api::vectorPathCommandLine>(copy(command.end_point_));
+ }
+ case td_api::vectorPathCommandCubicBezierCurve::ID: {
+ auto &command = static_cast<const td_api::vectorPathCommandCubicBezierCurve &>(obj);
+ return td_api::make_object<td_api::vectorPathCommandCubicBezierCurve>(
+ copy(command.start_control_point_), copy(command.end_control_point_), copy(command.end_point_));
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+static tl_object_ptr<td_api::VectorPathCommand> copy_vector_path_command(
+ const tl_object_ptr<td_api::VectorPathCommand> &obj) {
+ return copy(obj);
+}
+
+template <>
+tl_object_ptr<td_api::closedVectorPath> copy(const td_api::closedVectorPath &obj) {
+ return td_api::make_object<td_api::closedVectorPath>(transform(obj.commands_, copy_vector_path_command));
+}
+
+static tl_object_ptr<td_api::closedVectorPath> copy_closed_vector_path(
+ const tl_object_ptr<td_api::closedVectorPath> &obj) {
+ return copy(obj);
+}
+
+template <>
+tl_object_ptr<td_api::SpeechRecognitionResult> copy(const td_api::SpeechRecognitionResult &obj) {
+ switch (obj.get_id()) {
+ case td_api::speechRecognitionResultPending::ID:
+ return td_api::make_object<td_api::speechRecognitionResultPending>(
+ static_cast<const td_api::speechRecognitionResultPending &>(obj).partial_text_);
+ case td_api::speechRecognitionResultText::ID:
+ return td_api::make_object<td_api::speechRecognitionResultText>(
+ static_cast<const td_api::speechRecognitionResultText &>(obj).text_);
+ case td_api::speechRecognitionResultError::ID: {
+ auto *error = static_cast<const td_api::speechRecognitionResultError &>(obj).error_.get();
+ return td_api::make_object<td_api::speechRecognitionResultError>(
+ td_api::make_object<td_api::error>(error->code_, error->message_));
+ }
+ default:
+ UNREACHABLE();
+ }
+ return nullptr;
}
template <>
tl_object_ptr<td_api::animation> copy(const td_api::animation &obj) {
- return make_tl_object<td_api::animation>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
- copy(obj.thumbnail_), copy(obj.animation_));
+ return td_api::make_object<td_api::animation>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
+ obj.has_stickers_, copy(obj.minithumbnail_), copy(obj.thumbnail_),
+ copy(obj.animation_));
}
template <>
tl_object_ptr<td_api::audio> copy(const td_api::audio &obj) {
- return make_tl_object<td_api::audio>(obj.duration_, obj.title_, obj.performer_, obj.file_name_, obj.mime_type_,
- copy(obj.album_cover_thumbnail_), copy(obj.audio_));
+ return td_api::make_object<td_api::audio>(obj.duration_, obj.title_, obj.performer_, obj.file_name_, obj.mime_type_,
+ copy(obj.album_cover_minithumbnail_), copy(obj.album_cover_thumbnail_),
+ transform(obj.external_album_covers_, copy_thumbnail), copy(obj.audio_));
}
template <>
tl_object_ptr<td_api::document> copy(const td_api::document &obj) {
- return make_tl_object<td_api::document>(obj.file_name_, obj.mime_type_, copy(obj.thumbnail_), copy(obj.document_));
+ return td_api::make_object<td_api::document>(obj.file_name_, obj.mime_type_, copy(obj.minithumbnail_),
+ copy(obj.thumbnail_), copy(obj.document_));
}
template <>
tl_object_ptr<td_api::photo> copy(const td_api::photo &obj) {
- return make_tl_object<td_api::photo>(obj.id_, obj.has_stickers_, transform(obj.sizes_, copy_photo_size));
+ return td_api::make_object<td_api::photo>(obj.has_stickers_, copy(obj.minithumbnail_),
+ transform(obj.sizes_, copy_photo_size));
}
template <>
tl_object_ptr<td_api::sticker> copy(const td_api::sticker &obj) {
- return make_tl_object<td_api::sticker>(obj.set_id_, obj.width_, obj.height_, obj.emoji_, obj.is_mask_,
- copy(obj.mask_position_), copy(obj.thumbnail_), copy(obj.sticker_));
+ return td_api::make_object<td_api::sticker>(obj.set_id_, obj.width_, obj.height_, obj.emoji_, copy(obj.format_),
+ copy(obj.type_), copy(obj.mask_position_), obj.custom_emoji_id_,
+ transform(obj.outline_, copy_closed_vector_path), copy(obj.thumbnail_),
+ obj.is_premium_, copy(obj.premium_animation_), copy(obj.sticker_));
}
template <>
tl_object_ptr<td_api::video> copy(const td_api::video &obj) {
- return make_tl_object<td_api::video>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
- obj.has_stickers_, obj.supports_streaming_, copy(obj.thumbnail_),
- copy(obj.video_));
+ return td_api::make_object<td_api::video>(obj.duration_, obj.width_, obj.height_, obj.file_name_, obj.mime_type_,
+ obj.has_stickers_, obj.supports_streaming_, copy(obj.minithumbnail_),
+ copy(obj.thumbnail_), copy(obj.video_));
}
template <>
tl_object_ptr<td_api::voiceNote> copy(const td_api::voiceNote &obj) {
- return make_tl_object<td_api::voiceNote>(obj.duration_, obj.waveform_, obj.mime_type_, copy(obj.voice_));
+ return td_api::make_object<td_api::voiceNote>(obj.duration_, obj.waveform_, obj.mime_type_,
+ copy(obj.speech_recognition_result_), copy(obj.voice_));
}
template <>
tl_object_ptr<td_api::contact> copy(const td_api::contact &obj) {
- return make_tl_object<td_api::contact>(obj.phone_number_, obj.first_name_, obj.last_name_, obj.user_id_);
+ return td_api::make_object<td_api::contact>(obj.phone_number_, obj.first_name_, obj.last_name_, obj.vcard_,
+ obj.user_id_);
}
template <>
tl_object_ptr<td_api::location> copy(const td_api::location &obj) {
- return make_tl_object<td_api::location>(obj.latitude_, obj.longitude_);
+ return td_api::make_object<td_api::location>(obj.latitude_, obj.longitude_, obj.horizontal_accuracy_);
}
template <>
tl_object_ptr<td_api::venue> copy(const td_api::venue &obj) {
- return make_tl_object<td_api::venue>(copy(obj.location_), obj.title_, obj.address_, obj.provider_, obj.id_);
+ return td_api::make_object<td_api::venue>(copy(obj.location_), obj.title_, obj.address_, obj.provider_, obj.id_,
+ obj.type_);
}
template <>
tl_object_ptr<td_api::formattedText> copy(const td_api::formattedText &obj) {
- // there is no entities in the game text
- return make_tl_object<td_api::formattedText>(obj.text_, vector<tl_object_ptr<td_api::textEntity>>());
+ // there are no entities in the game text
+ return td_api::make_object<td_api::formattedText>(obj.text_, vector<tl_object_ptr<td_api::textEntity>>());
}
template <>
tl_object_ptr<td_api::game> copy(const td_api::game &obj) {
- return make_tl_object<td_api::game>(obj.id_, obj.short_name_, obj.title_, copy(obj.text_), obj.description_,
- copy(obj.photo_), copy(obj.animation_));
+ return td_api::make_object<td_api::game>(obj.id_, obj.short_name_, obj.title_, copy(obj.text_), obj.description_,
+ copy(obj.photo_), copy(obj.animation_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultArticle> copy(const td_api::inlineQueryResultArticle &obj) {
- return make_tl_object<td_api::inlineQueryResultArticle>(obj.id_, obj.url_, obj.hide_url_, obj.title_,
- obj.description_, copy(obj.thumbnail_));
+ return td_api::make_object<td_api::inlineQueryResultArticle>(obj.id_, obj.url_, obj.hide_url_, obj.title_,
+ obj.description_, copy(obj.thumbnail_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultContact> copy(const td_api::inlineQueryResultContact &obj) {
- return make_tl_object<td_api::inlineQueryResultContact>(obj.id_, copy(obj.contact_), copy(obj.thumbnail_));
+ return td_api::make_object<td_api::inlineQueryResultContact>(obj.id_, copy(obj.contact_), copy(obj.thumbnail_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultLocation> copy(const td_api::inlineQueryResultLocation &obj) {
- return make_tl_object<td_api::inlineQueryResultLocation>(obj.id_, copy(obj.location_), obj.title_,
- copy(obj.thumbnail_));
+ return td_api::make_object<td_api::inlineQueryResultLocation>(obj.id_, copy(obj.location_), obj.title_,
+ copy(obj.thumbnail_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultVenue> copy(const td_api::inlineQueryResultVenue &obj) {
- return make_tl_object<td_api::inlineQueryResultVenue>(obj.id_, copy(obj.venue_), copy(obj.thumbnail_));
+ return td_api::make_object<td_api::inlineQueryResultVenue>(obj.id_, copy(obj.venue_), copy(obj.thumbnail_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultGame> copy(const td_api::inlineQueryResultGame &obj) {
- return make_tl_object<td_api::inlineQueryResultGame>(obj.id_, copy(obj.game_));
+ return td_api::make_object<td_api::inlineQueryResultGame>(obj.id_, copy(obj.game_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultAnimation> copy(const td_api::inlineQueryResultAnimation &obj) {
- return make_tl_object<td_api::inlineQueryResultAnimation>(obj.id_, copy(obj.animation_), obj.title_);
+ return td_api::make_object<td_api::inlineQueryResultAnimation>(obj.id_, copy(obj.animation_), obj.title_);
}
template <>
tl_object_ptr<td_api::inlineQueryResultAudio> copy(const td_api::inlineQueryResultAudio &obj) {
- return make_tl_object<td_api::inlineQueryResultAudio>(obj.id_, copy(obj.audio_));
+ return td_api::make_object<td_api::inlineQueryResultAudio>(obj.id_, copy(obj.audio_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultDocument> copy(const td_api::inlineQueryResultDocument &obj) {
- return make_tl_object<td_api::inlineQueryResultDocument>(obj.id_, copy(obj.document_), obj.title_, obj.description_);
+ return td_api::make_object<td_api::inlineQueryResultDocument>(obj.id_, copy(obj.document_), obj.title_,
+ obj.description_);
}
template <>
tl_object_ptr<td_api::inlineQueryResultPhoto> copy(const td_api::inlineQueryResultPhoto &obj) {
- return make_tl_object<td_api::inlineQueryResultPhoto>(obj.id_, copy(obj.photo_), obj.title_, obj.description_);
+ return td_api::make_object<td_api::inlineQueryResultPhoto>(obj.id_, copy(obj.photo_), obj.title_, obj.description_);
}
template <>
tl_object_ptr<td_api::inlineQueryResultSticker> copy(const td_api::inlineQueryResultSticker &obj) {
- return make_tl_object<td_api::inlineQueryResultSticker>(obj.id_, copy(obj.sticker_));
+ return td_api::make_object<td_api::inlineQueryResultSticker>(obj.id_, copy(obj.sticker_));
}
template <>
tl_object_ptr<td_api::inlineQueryResultVideo> copy(const td_api::inlineQueryResultVideo &obj) {
- return make_tl_object<td_api::inlineQueryResultVideo>(obj.id_, copy(obj.video_), obj.title_, obj.description_);
+ return td_api::make_object<td_api::inlineQueryResultVideo>(obj.id_, copy(obj.video_), obj.title_, obj.description_);
}
template <>
tl_object_ptr<td_api::inlineQueryResultVoiceNote> copy(const td_api::inlineQueryResultVoiceNote &obj) {
- return make_tl_object<td_api::inlineQueryResultVoiceNote>(obj.id_, copy(obj.voice_note_), obj.title_);
+ return td_api::make_object<td_api::inlineQueryResultVoiceNote>(obj.id_, copy(obj.voice_note_), obj.title_);
}
static tl_object_ptr<td_api::InlineQueryResult> copy_result(const tl_object_ptr<td_api::InlineQueryResult> &obj_ptr) {
tl_object_ptr<td_api::InlineQueryResult> result;
- downcast_call(*obj_ptr, [&result](const auto &obj) { result = copy(obj); });
+ downcast_call(const_cast<td_api::InlineQueryResult &>(*obj_ptr), [&result](const auto &obj) { result = copy(obj); });
return result;
}
template <>
tl_object_ptr<td_api::inlineQueryResults> copy(const td_api::inlineQueryResults &obj) {
- return make_tl_object<td_api::inlineQueryResults>(obj.inline_query_id_, obj.next_offset_,
- transform(obj.results_, copy_result), obj.switch_pm_text_,
- obj.switch_pm_parameter_);
+ return td_api::make_object<td_api::inlineQueryResults>(obj.inline_query_id_, obj.next_offset_,
+ transform(obj.results_, copy_result), obj.switch_pm_text_,
+ obj.switch_pm_parameter_);
}
tl_object_ptr<td_api::inlineQueryResults> InlineQueriesManager::decrease_pending_request_count(uint64 query_hash) {
@@ -1170,6 +1404,7 @@ tl_object_ptr<td_api::inlineQueryResults> InlineQueriesManager::decrease_pending
auto left_time = it->second.cache_expire_time - Time::now();
if (left_time < 0) {
LOG(INFO) << "Drop cache for inline query " << query_hash;
+ drop_inline_query_result_timeout_.cancel_timeout(static_cast<int64>(query_hash));
auto result = std::move(it->second.results);
inline_query_results_.erase(it);
return result;
@@ -1180,15 +1415,16 @@ tl_object_ptr<td_api::inlineQueryResults> InlineQueriesManager::decrease_pending
return copy(it->second.results);
}
-tl_object_ptr<td_api::photoSize> InlineQueriesManager::register_thumbnail(
+tl_object_ptr<td_api::thumbnail> InlineQueriesManager::register_thumbnail(
tl_object_ptr<telegram_api::WebDocument> &&web_document_ptr) const {
PhotoSize thumbnail = get_web_document_photo_size(td_->file_manager_.get(), FileType::Thumbnail, DialogId(),
std::move(web_document_ptr));
- if (!thumbnail.file_id.is_valid()) {
+ if (!thumbnail.file_id.is_valid() || thumbnail.type == 'v') {
return nullptr;
}
- return get_photo_size_object(td_->file_manager_.get(), &thumbnail);
+ return get_thumbnail_object(td_->file_manager_.get(), thumbnail,
+ thumbnail.type == 'g' ? PhotoFormat::Gif : PhotoFormat::Jpeg);
}
string InlineQueriesManager::get_web_document_url(const tl_object_ptr<telegram_api::WebDocument> &web_document_ptr) {
@@ -1196,7 +1432,7 @@ string InlineQueriesManager::get_web_document_url(const tl_object_ptr<telegram_a
return {};
}
- string url;
+ Slice url;
switch (web_document_ptr->get_id()) {
case telegram_api::webDocument::ID: {
auto web_document = static_cast<const telegram_api::webDocument *>(web_document_ptr.get());
@@ -1238,17 +1474,19 @@ string InlineQueriesManager::get_web_document_content_type(
return {};
}
-void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint64 query_hash,
+void InlineQueriesManager::on_get_inline_query_results(DialogId dialog_id, UserId bot_user_id, uint64 query_hash,
tl_object_ptr<telegram_api::messages_botResults> &&results) {
LOG(INFO) << "Receive results for inline query " << query_hash;
- if (results == nullptr) {
+ if (results == nullptr || results->query_id_ == 0) {
decrease_pending_request_count(query_hash);
return;
}
LOG(INFO) << to_string(results);
- td_->contacts_manager_->on_get_users(std::move(results->users_));
+ td_->contacts_manager_->on_get_users(std::move(results->users_), "on_get_inline_query_results");
+ auto dialog_type = dialog_id.get_type();
+ bool allow_invoice = dialog_type != DialogType::SecretChat;
vector<tl_object_ptr<td_api::InlineQueryResult>> output_results;
for (auto &result_ptr : results->results_) {
tl_object_ptr<td_api::InlineQueryResult> output_result;
@@ -1260,16 +1498,28 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
bool has_photo = (flags & BOT_INLINE_MEDIA_RESULT_FLAG_HAS_PHOTO) != 0;
bool is_photo = result->type_ == "photo";
if (result->type_ == "game") {
+ if (!has_photo) {
+ LOG(ERROR) << "Receive game without photo in the result of inline query: " << to_string(result);
+ break;
+ }
+ if (dialog_type == DialogType::Channel &&
+ td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) {
+ continue;
+ }
+ if (dialog_type == DialogType::SecretChat) {
+ continue;
+ }
+
auto game = make_tl_object<td_api::inlineQueryResultGame>();
Game inline_game(td_, std::move(result->title_), std::move(result->description_), std::move(result->photo_),
std::move(result->document_), DialogId());
game->id_ = std::move(result->id_);
- game->game_ = inline_game.get_game_object(td_);
+ game->game_ = inline_game.get_game_object(td_, true);
if (!register_inline_message_content(results->query_id_, game->id_, FileId(),
- std::move(result->send_message_), td_api::inputMessageGame::ID, nullptr,
- &inline_game)) {
+ std::move(result->send_message_), td_api::inputMessageGame::ID,
+ allow_invoice, nullptr, &inline_game)) {
continue;
}
output_result = std::move(game);
@@ -1284,105 +1534,108 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
auto parsed_document = td_->documents_manager_->on_get_document(
move_tl_object_as<telegram_api::document>(document_ptr), DialogId());
- switch (parsed_document.first) {
- case DocumentsManager::DocumentType::Animation: {
+ switch (parsed_document.type) {
+ case Document::Type::Animation: {
LOG_IF(WARNING, result->type_ != "gif") << "Wrong result type " << result->type_;
auto animation = make_tl_object<td_api::inlineQueryResultAnimation>();
animation->id_ = std::move(result->id_);
- animation->animation_ =
- td_->animations_manager_->get_animation_object(parsed_document.second, "inlineQueryResultAnimation");
+ animation->animation_ = td_->animations_manager_->get_animation_object(parsed_document.file_id);
animation->title_ = std::move(result->title_);
- if (!register_inline_message_content(results->query_id_, animation->id_, parsed_document.second,
- std::move(result->send_message_),
- td_api::inputMessageAnimation::ID)) {
+ if (!register_inline_message_content(results->query_id_, animation->id_, parsed_document.file_id,
+ std::move(result->send_message_), td_api::inputMessageAnimation::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(animation);
break;
}
- case DocumentsManager::DocumentType::Audio: {
+ case Document::Type::Audio: {
LOG_IF(WARNING, result->type_ != "audio") << "Wrong result type " << result->type_;
auto audio = make_tl_object<td_api::inlineQueryResultAudio>();
audio->id_ = std::move(result->id_);
- audio->audio_ = td_->audios_manager_->get_audio_object(parsed_document.second);
+ audio->audio_ = td_->audios_manager_->get_audio_object(parsed_document.file_id);
- if (!register_inline_message_content(results->query_id_, audio->id_, parsed_document.second,
- std::move(result->send_message_), td_api::inputMessageAudio::ID)) {
+ if (!register_inline_message_content(results->query_id_, audio->id_, parsed_document.file_id,
+ std::move(result->send_message_), td_api::inputMessageAudio::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(audio);
break;
}
- case DocumentsManager::DocumentType::General: {
+ case Document::Type::General: {
LOG_IF(WARNING, result->type_ != "file") << "Wrong result type " << result->type_;
auto document = make_tl_object<td_api::inlineQueryResultDocument>();
document->id_ = std::move(result->id_);
- document->document_ = td_->documents_manager_->get_document_object(parsed_document.second);
+ document->document_ =
+ td_->documents_manager_->get_document_object(parsed_document.file_id, PhotoFormat::Jpeg);
document->title_ = std::move(result->title_);
document->description_ = std::move(result->description_);
- if (!register_inline_message_content(results->query_id_, document->id_, parsed_document.second,
- std::move(result->send_message_),
- td_api::inputMessageDocument::ID)) {
+ if (!register_inline_message_content(results->query_id_, document->id_, parsed_document.file_id,
+ std::move(result->send_message_), td_api::inputMessageDocument::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(document);
break;
}
- case DocumentsManager::DocumentType::Sticker: {
+ case Document::Type::Sticker: {
LOG_IF(WARNING, result->type_ != "sticker") << "Wrong result type " << result->type_;
auto sticker = make_tl_object<td_api::inlineQueryResultSticker>();
sticker->id_ = std::move(result->id_);
- sticker->sticker_ = td_->stickers_manager_->get_sticker_object(parsed_document.second);
+ sticker->sticker_ = td_->stickers_manager_->get_sticker_object(parsed_document.file_id);
- if (!register_inline_message_content(results->query_id_, sticker->id_, parsed_document.second,
- std::move(result->send_message_), td_api::inputMessageSticker::ID)) {
+ if (!register_inline_message_content(results->query_id_, sticker->id_, parsed_document.file_id,
+ std::move(result->send_message_), td_api::inputMessageSticker::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(sticker);
break;
}
- case DocumentsManager::DocumentType::Video: {
+ case Document::Type::Video: {
LOG_IF(WARNING, result->type_ != "video") << "Wrong result type " << result->type_;
auto video = make_tl_object<td_api::inlineQueryResultVideo>();
video->id_ = std::move(result->id_);
- video->video_ = td_->videos_manager_->get_video_object(parsed_document.second);
+ video->video_ = td_->videos_manager_->get_video_object(parsed_document.file_id);
video->title_ = std::move(result->title_);
video->description_ = std::move(result->description_);
- if (!register_inline_message_content(results->query_id_, video->id_, parsed_document.second,
- std::move(result->send_message_), td_api::inputMessageVideo::ID)) {
+ if (!register_inline_message_content(results->query_id_, video->id_, parsed_document.file_id,
+ std::move(result->send_message_), td_api::inputMessageVideo::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(video);
break;
}
- case DocumentsManager::DocumentType::VideoNote:
+ case Document::Type::VideoNote:
// FIXME
break;
- case DocumentsManager::DocumentType::VoiceNote: {
+ case Document::Type::VoiceNote: {
LOG_IF(WARNING, result->type_ != "voice") << "Wrong result type " << result->type_;
auto voice_note = make_tl_object<td_api::inlineQueryResultVoiceNote>();
voice_note->id_ = std::move(result->id_);
- voice_note->voice_note_ = td_->voice_notes_manager_->get_voice_note_object(parsed_document.second);
+ voice_note->voice_note_ = td_->voice_notes_manager_->get_voice_note_object(parsed_document.file_id);
voice_note->title_ = std::move(result->title_);
- if (!register_inline_message_content(results->query_id_, voice_note->id_, parsed_document.second,
- std::move(result->send_message_),
- td_api::inputMessageVoiceNote::ID)) {
+ if (!register_inline_message_content(results->query_id_, voice_note->id_, parsed_document.file_id,
+ std::move(result->send_message_), td_api::inputMessageVoiceNote::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(voice_note);
break;
}
- case DocumentsManager::DocumentType::Unknown:
+ case Document::Type::Unknown:
// invalid document
break;
default:
@@ -1393,21 +1646,18 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
LOG_IF(ERROR, !is_photo) << "Wrong result type " << result->type_;
auto photo = make_tl_object<td_api::inlineQueryResultPhoto>();
photo->id_ = std::move(result->id_);
- auto photo_ptr = std::move(result->photo_);
- int32 photo_id = photo_ptr->get_id();
- if (photo_id == telegram_api::photoEmpty::ID) {
+ Photo p = get_photo(td_->file_manager_.get(), std::move(result->photo_), DialogId());
+ if (p.is_empty()) {
LOG(ERROR) << "Receive empty cached photo in the result of inline query";
break;
}
- CHECK(photo_id == telegram_api::photo::ID);
-
- Photo p = get_photo(td_->file_manager_.get(), move_tl_object_as<telegram_api::photo>(photo_ptr), DialogId());
- photo->photo_ = get_photo_object(td_->file_manager_.get(), &p);
+ photo->photo_ = get_photo_object(td_->file_manager_.get(), p);
photo->title_ = std::move(result->title_);
photo->description_ = std::move(result->description_);
if (!register_inline_message_content(results->query_id_, photo->id_, FileId(),
- std::move(result->send_message_), td_api::inputMessagePhoto::ID, &p)) {
+ std::move(result->send_message_), td_api::inputMessagePhoto::ID,
+ allow_invoice, &p)) {
continue;
}
output_result = std::move(photo);
@@ -1422,7 +1672,7 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
if (result->type_ == "article") {
auto article = make_tl_object<td_api::inlineQueryResultArticle>();
article->id_ = std::move(result->id_);
- article->url_ = get_web_document_url(std::move(result->content_));
+ article->url_ = get_web_document_url(result->content_);
if (result->url_.empty()) {
article->hide_url_ = true;
} else {
@@ -1435,7 +1685,7 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
article->thumbnail_ = register_thumbnail(std::move(result->thumb_));
if (!register_inline_message_content(results->query_id_, article->id_, FileId(),
- std::move(result->send_message_), -1)) {
+ std::move(result->send_message_), -1, allow_invoice)) {
continue;
}
output_result = std::move(article);
@@ -1446,16 +1696,16 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
auto inline_message_contact =
static_cast<const telegram_api::botInlineMessageMediaContact *>(result->send_message_.get());
Contact c(inline_message_contact->phone_number_, inline_message_contact->first_name_,
- inline_message_contact->last_name_, 0);
+ inline_message_contact->last_name_, inline_message_contact->vcard_, UserId());
contact->contact_ = c.get_contact_object();
} else {
- Contact c(std::move(result->description_), std::move(result->title_), string(), 0);
+ Contact c(std::move(result->description_), std::move(result->title_), string(), string(), UserId());
contact->contact_ = c.get_contact_object();
}
contact->thumbnail_ = register_thumbnail(std::move(result->thumb_));
if (!register_inline_message_content(results->query_id_, contact->id_, FileId(),
- std::move(result->send_message_), -1)) {
+ std::move(result->send_message_), -1, allow_invoice)) {
continue;
}
output_result = std::move(contact);
@@ -1469,14 +1719,14 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
Location l(inline_message_geo->geo_);
location->location_ = l.get_location_object();
} else {
- auto coordinates = split(Slice(result->description_));
- Location l(to_double(coordinates.first), to_double(coordinates.second));
+ auto latitude_longitude = split(Slice(result->description_));
+ Location l(to_double(latitude_longitude.first), to_double(latitude_longitude.second), 0.0, 0);
location->location_ = l.get_location_object();
}
location->thumbnail_ = register_thumbnail(std::move(result->thumb_));
if (!register_inline_message_content(results->query_id_, location->id_, FileId(),
- std::move(result->send_message_), -1)) {
+ std::move(result->send_message_), -1, allow_invoice)) {
continue;
}
output_result = std::move(location);
@@ -1487,22 +1737,23 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
auto inline_message_venue =
static_cast<const telegram_api::botInlineMessageMediaVenue *>(result->send_message_.get());
Venue v(inline_message_venue->geo_, inline_message_venue->title_, inline_message_venue->address_,
- inline_message_venue->provider_, inline_message_venue->venue_id_);
+ inline_message_venue->provider_, inline_message_venue->venue_id_,
+ inline_message_venue->venue_type_);
venue->venue_ = v.get_venue_object();
} else if (result->send_message_->get_id() == telegram_api::botInlineMessageMediaGeo::ID) {
auto inline_message_geo =
static_cast<const telegram_api::botInlineMessageMediaGeo *>(result->send_message_.get());
Venue v(inline_message_geo->geo_, std::move(result->title_), std::move(result->description_), string(),
- string());
+ string(), string());
venue->venue_ = v.get_venue_object();
} else {
- Venue v(nullptr, std::move(result->title_), std::move(result->description_), string(), string());
+ Venue v(nullptr, std::move(result->title_), std::move(result->description_), string(), string(), string());
venue->venue_ = v.get_venue_object();
}
venue->thumbnail_ = register_thumbnail(std::move(result->thumb_));
if (!register_inline_message_content(results->query_id_, venue->id_, FileId(),
- std::move(result->send_message_), -1)) {
+ std::move(result->send_message_), -1, allow_invoice)) {
continue;
}
output_result = std::move(venue);
@@ -1512,26 +1763,27 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
PhotoSize photo_size = get_web_document_photo_size(td_->file_manager_.get(), FileType::Temp, DialogId(),
std::move(result->content_));
- if (!photo_size.file_id.is_valid()) {
+ if (!photo_size.file_id.is_valid() || photo_size.type == 'v' || photo_size.type == 'g') {
LOG(ERROR) << "Receive invalid web document photo";
continue;
}
Photo new_photo;
+ new_photo.id = 0;
PhotoSize thumbnail = get_web_document_photo_size(td_->file_manager_.get(), FileType::Thumbnail, DialogId(),
std::move(result->thumb_));
- if (thumbnail.file_id.is_valid()) {
+ if (thumbnail.file_id.is_valid() && thumbnail.type != 'v' && thumbnail.type != 'g') {
new_photo.photos.push_back(std::move(thumbnail));
}
new_photo.photos.push_back(std::move(photo_size));
- photo->photo_ = get_photo_object(td_->file_manager_.get(), &new_photo);
+ photo->photo_ = get_photo_object(td_->file_manager_.get(), new_photo);
photo->title_ = std::move(result->title_);
photo->description_ = std::move(result->description_);
if (!register_inline_message_content(results->query_id_, photo->id_, FileId(),
std::move(result->send_message_), td_api::inputMessagePhoto::ID,
- &new_photo)) {
+ allow_invoice, &new_photo)) {
continue;
}
output_result = std::move(photo);
@@ -1551,21 +1803,21 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
}
auto default_document_type = [type = result->type_, is_animation] {
if (type == "audio") {
- return DocumentsManager::DocumentType::Audio;
+ return Document::Type::Audio;
}
if (is_animation) {
- return DocumentsManager::DocumentType::Animation;
+ return Document::Type::Animation;
}
if (type == "sticker") {
- return DocumentsManager::DocumentType::Sticker;
+ return Document::Type::Sticker;
}
if (type == "video") {
- return DocumentsManager::DocumentType::Video;
+ return Document::Type::Video;
}
if (type == "voice") {
- return DocumentsManager::DocumentType::VoiceNote;
+ return Document::Type::VoiceNote;
}
- return DocumentsManager::DocumentType::General;
+ return Document::Type::General;
}();
auto parsed_document = td_->documents_manager_->on_get_document(
@@ -1574,68 +1826,73 @@ void InlineQueriesManager::on_get_inline_query_results(UserId bot_user_id, uint6
std::move(result->thumb_)),
std::move(attributes)},
DialogId(), nullptr, default_document_type);
- auto file_id = parsed_document.second;
+ auto file_id = parsed_document.file_id;
if (!file_id.is_valid()) {
continue;
}
- if (result->type_ == "audio" && parsed_document.first == DocumentsManager::DocumentType::Audio) {
+ if (result->type_ == "audio" && parsed_document.type == Document::Type::Audio) {
auto audio = make_tl_object<td_api::inlineQueryResultAudio>();
audio->id_ = std::move(result->id_);
audio->audio_ = td_->audios_manager_->get_audio_object(file_id);
if (!register_inline_message_content(results->query_id_, audio->id_, file_id,
- std::move(result->send_message_), td_api::inputMessageAudio::ID)) {
+ std::move(result->send_message_), td_api::inputMessageAudio::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(audio);
- } else if (result->type_ == "file" && parsed_document.first == DocumentsManager::DocumentType::General) {
+ } else if (result->type_ == "file" && parsed_document.type == Document::Type::General) {
auto document = make_tl_object<td_api::inlineQueryResultDocument>();
document->id_ = std::move(result->id_);
- document->document_ = td_->documents_manager_->get_document_object(file_id);
+ document->document_ = td_->documents_manager_->get_document_object(file_id, PhotoFormat::Jpeg);
document->title_ = std::move(result->title_);
document->description_ = std::move(result->description_);
if (!register_inline_message_content(results->query_id_, document->id_, file_id,
- std::move(result->send_message_), td_api::inputMessageDocument::ID)) {
+ std::move(result->send_message_), td_api::inputMessageDocument::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(document);
- } else if (is_animation && parsed_document.first == DocumentsManager::DocumentType::Animation) {
+ } else if (is_animation && parsed_document.type == Document::Type::Animation) {
auto animation = make_tl_object<td_api::inlineQueryResultAnimation>();
animation->id_ = std::move(result->id_);
- animation->animation_ =
- td_->animations_manager_->get_animation_object(file_id, "inlineQueryResultAnimationCached");
+ animation->animation_ = td_->animations_manager_->get_animation_object(file_id);
animation->title_ = std::move(result->title_);
if (!register_inline_message_content(results->query_id_, animation->id_, file_id,
- std::move(result->send_message_), td_api::inputMessageAnimation::ID)) {
+ std::move(result->send_message_), td_api::inputMessageAnimation::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(animation);
- } else if (result->type_ == "sticker" && parsed_document.first == DocumentsManager::DocumentType::Sticker) {
+ } else if (result->type_ == "sticker" && parsed_document.type == Document::Type::Sticker) {
auto sticker = make_tl_object<td_api::inlineQueryResultSticker>();
sticker->id_ = std::move(result->id_);
sticker->sticker_ = td_->stickers_manager_->get_sticker_object(file_id);
if (!register_inline_message_content(results->query_id_, sticker->id_, file_id,
- std::move(result->send_message_), td_api::inputMessageSticker::ID)) {
+ std::move(result->send_message_), td_api::inputMessageSticker::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(sticker);
- } else if (result->type_ == "video" && parsed_document.first == DocumentsManager::DocumentType::Video) {
+ } else if (result->type_ == "video" && parsed_document.type == Document::Type::Video) {
auto video = make_tl_object<td_api::inlineQueryResultVideo>();
video->id_ = std::move(result->id_);
video->video_ = td_->videos_manager_->get_video_object(file_id);
video->title_ = std::move(result->title_);
video->description_ = std::move(result->description_);
if (!register_inline_message_content(results->query_id_, video->id_, file_id,
- std::move(result->send_message_), td_api::inputMessageVideo::ID)) {
+ std::move(result->send_message_), td_api::inputMessageVideo::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(video);
- } else if (result->type_ == "voice" && parsed_document.first == DocumentsManager::DocumentType::VoiceNote) {
+ } else if (result->type_ == "voice" && parsed_document.type == Document::Type::VoiceNote) {
auto voice_note = make_tl_object<td_api::inlineQueryResultVoiceNote>();
voice_note->id_ = std::move(result->id_);
voice_note->voice_note_ = td_->voice_notes_manager_->get_voice_note_object(file_id);
voice_note->title_ = std::move(result->title_);
if (!register_inline_message_content(results->query_id_, voice_note->id_, file_id,
- std::move(result->send_message_), td_api::inputMessageVoiceNote::ID)) {
+ std::move(result->send_message_), td_api::inputMessageVoiceNote::ID,
+ allow_invoice)) {
continue;
}
output_result = std::move(voice_note);
@@ -1691,7 +1948,7 @@ void InlineQueriesManager::save_recently_used_bots() {
value += ',';
value_ids += ',';
}
- value += td_->contacts_manager_->get_user_username(bot_user_id);
+ value += td_->contacts_manager_->get_user_first_username(bot_user_id);
value_ids += to_string(bot_user_id.get());
}
G()->td_db()->get_binlog_pmc()->set("recently_used_inline_bot_usernames", value);
@@ -1707,7 +1964,7 @@ bool InlineQueriesManager::load_recently_used_bots(Promise<Unit> &promise) {
auto bot_ids = full_split(saved_bot_ids, ',');
string saved_bots = G()->td_db()->get_binlog_pmc()->get("recently_used_inline_bot_usernames");
auto bot_usernames = full_split(saved_bots, ',');
- if (bot_ids.empty() && bot_usernames.empty()) {
+ if (bot_ids.empty()) {
recently_used_bots_loaded_ = 2;
if (!recently_used_bot_user_ids_.empty()) {
save_recently_used_bots();
@@ -1715,34 +1972,25 @@ bool InlineQueriesManager::load_recently_used_bots(Promise<Unit> &promise) {
return true;
}
+ LOG(INFO) << "Load recently used inline bots " << saved_bots << '/' << saved_bot_ids;
if (recently_used_bots_loaded_ == 1 && resolve_recent_inline_bots_multipromise_.promise_count() == 0) {
// queries was sent and have already been finished
auto newly_used_bots = std::move(recently_used_bot_user_ids_);
recently_used_bot_user_ids_.clear();
- if (bot_ids.empty()) {
- // legacy, can be removed in the future
- for (auto it = bot_usernames.rbegin(); it != bot_usernames.rend(); ++it) {
- auto dialog_id = td_->messages_manager_->resolve_dialog_username(*it);
- if (dialog_id.get_type() == DialogType::User) {
- update_bot_usage(dialog_id.get_user_id());
- }
- }
- } else {
- for (auto it = bot_ids.rbegin(); it != bot_ids.rend(); ++it) {
- UserId user_id(to_integer<int32>(*it));
- if (td_->contacts_manager_->have_user(user_id)) {
- update_bot_usage(user_id);
- } else {
- LOG(ERROR) << "Can't find " << user_id;
- }
+ for (auto it = bot_ids.rbegin(); it != bot_ids.rend(); ++it) {
+ UserId user_id(to_integer<int64>(*it));
+ if (td_->contacts_manager_->have_user(user_id)) {
+ update_bot_usage(user_id);
+ } else {
+ LOG(ERROR) << "Can't find " << user_id;
}
}
for (auto it = newly_used_bots.rbegin(); it != newly_used_bots.rend(); ++it) {
update_bot_usage(*it);
}
recently_used_bots_loaded_ = 2;
- if (!newly_used_bots.empty() || (bot_ids.empty() && !bot_usernames.empty())) {
+ if (!newly_used_bots.empty()) {
save_recently_used_bots();
}
return true;
@@ -1751,52 +1999,75 @@ bool InlineQueriesManager::load_recently_used_bots(Promise<Unit> &promise) {
resolve_recent_inline_bots_multipromise_.add_promise(std::move(promise));
if (recently_used_bots_loaded_ == 0) {
resolve_recent_inline_bots_multipromise_.set_ignore_errors(true);
- if (bot_ids.empty() || !G()->parameters().use_chat_info_db) {
+ auto lock = resolve_recent_inline_bots_multipromise_.get_promise();
+ if (!G()->parameters().use_chat_info_db) {
for (auto &bot_username : bot_usernames) {
td_->messages_manager_->search_public_dialog(bot_username, false,
resolve_recent_inline_bots_multipromise_.get_promise());
}
} else {
for (auto &bot_id : bot_ids) {
- UserId user_id(to_integer<int32>(bot_id));
+ UserId user_id(to_integer<int64>(bot_id));
td_->contacts_manager_->get_user(user_id, 3, resolve_recent_inline_bots_multipromise_.get_promise());
}
}
+ lock.set_value(Unit());
recently_used_bots_loaded_ = 1;
}
return false;
}
tl_object_ptr<td_api::inlineQueryResults> InlineQueriesManager::get_inline_query_results_object(uint64 query_hash) {
- // TODO filter out games if request is sent in a broadcast channel or in a secret chat
return decrease_pending_request_count(query_hash);
}
void InlineQueriesManager::on_new_query(int64 query_id, UserId sender_user_id, Location user_location,
- const string &query, const string &offset) {
+ tl_object_ptr<telegram_api::InlineQueryPeerType> peer_type, const string &query,
+ const string &offset) {
if (!sender_user_id.is_valid()) {
LOG(ERROR) << "Receive new inline query from invalid " << sender_user_id;
return;
}
- LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Have no info about " << sender_user_id;
+ LOG_IF(ERROR, !td_->contacts_manager_->have_user(sender_user_id)) << "Receive unknown " << sender_user_id;
if (!td_->auth_manager_->is_bot()) {
LOG(ERROR) << "Receive new inline query";
return;
}
+ auto chat_type = [&]() -> td_api::object_ptr<td_api::ChatType> {
+ if (peer_type == nullptr) {
+ return nullptr;
+ }
+
+ switch (peer_type->get_id()) {
+ case telegram_api::inlineQueryPeerTypeSameBotPM::ID:
+ return td_api::make_object<td_api::chatTypePrivate>(sender_user_id.get());
+ case telegram_api::inlineQueryPeerTypePM::ID:
+ return td_api::make_object<td_api::chatTypePrivate>(0);
+ case telegram_api::inlineQueryPeerTypeChat::ID:
+ return td_api::make_object<td_api::chatTypeBasicGroup>(0);
+ case telegram_api::inlineQueryPeerTypeMegagroup::ID:
+ return td_api::make_object<td_api::chatTypeSupergroup>(0, false);
+ case telegram_api::inlineQueryPeerTypeBroadcast::ID:
+ return td_api::make_object<td_api::chatTypeSupergroup>(0, true);
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+ }();
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateNewInlineQuery>(
query_id, td_->contacts_manager_->get_user_id_object(sender_user_id, "updateNewInlineQuery"),
- user_location.get_location_object(), query, offset));
+ user_location.get_location_object(), std::move(chat_type), query, offset));
}
void InlineQueriesManager::on_chosen_result(
UserId user_id, Location user_location, const string &query, const string &result_id,
- tl_object_ptr<telegram_api::inputBotInlineMessageID> &&input_bot_inline_message_id) {
+ tl_object_ptr<telegram_api::InputBotInlineMessageID> &&input_bot_inline_message_id) {
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive chosen inline query result from invalid " << user_id;
return;
}
- LOG_IF(ERROR, !td_->contacts_manager_->have_user(user_id)) << "Have no info about " << user_id;
+ LOG_IF(ERROR, !td_->contacts_manager_->have_user(user_id)) << "Receive unknown " << user_id;
if (!td_->auth_manager_->is_bot()) {
LOG(ERROR) << "Receive chosen inline query result";
return;
@@ -1838,12 +2109,10 @@ bool InlineQueriesManager::update_bot_usage(UserId bot_user_id) {
}
void InlineQueriesManager::remove_recent_inline_bot(UserId bot_user_id, Promise<Unit> &&promise) {
- auto it = std::find(recently_used_bot_user_ids_.begin(), recently_used_bot_user_ids_.end(), bot_user_id);
- if (it != recently_used_bot_user_ids_.end()) {
- recently_used_bot_user_ids_.erase(it);
+ if (td::remove(recently_used_bot_user_ids_, bot_user_id)) {
save_recently_used_bots();
}
- return promise.set_value(Unit());
+ promise.set_value(Unit());
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.h b/protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.h
index 97548c41ed..99f9da57a4 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/InlineQueriesManager.h
@@ -1,53 +1,60 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/actor.h"
-#include "td/actor/MultiPromise.h"
-#include "td/actor/Timeout.h"
-
#include "td/telegram/DialogId.h"
#include "td/telegram/files/FileId.h"
#include "td/telegram/Location.h"
+#include "td/telegram/MessageContent.h"
#include "td/telegram/MessageEntity.h"
#include "td/telegram/net/NetQuery.h"
#include "td/telegram/Photo.h"
-#include "td/telegram/ReplyMarkup.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
+#include "td/actor/actor.h"
+#include "td/actor/MultiPromise.h"
+#include "td/actor/MultiTimeout.h"
+
#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
-#include <tuple>
-#include <unordered_map>
#include <utility>
namespace td {
class Td;
-class MessageContent;
-
class Game;
-class InlineQueriesManager : public Actor {
+class InlineQueriesManager final : public Actor {
public:
InlineQueriesManager(Td *td, ActorShared<> parent);
void after_get_difference();
void answer_inline_query(int64 inline_query_id, bool is_personal,
- vector<tl_object_ptr<td_api::InputInlineQueryResult>> &&input_results, int32 cache_time,
+ vector<td_api::object_ptr<td_api::InputInlineQueryResult>> &&input_results, int32 cache_time,
const string &next_offset, const string &switch_pm_text, const string &switch_pm_parameter,
Promise<Unit> &&promise) const;
+ void get_simple_web_view_url(UserId bot_user_id, string &&url,
+ const td_api::object_ptr<td_api::themeParameters> &theme, string &&platform,
+ Promise<string> &&promise);
+
+ void send_web_view_data(UserId bot_user_id, string &&button_text, string &&data, Promise<Unit> &&promise) const;
+
+ void answer_web_view_query(const string &web_view_query_id,
+ td_api::object_ptr<td_api::InputInlineQueryResult> &&input_result,
+ Promise<td_api::object_ptr<td_api::sentWebAppMessage>> &&promise) const;
+
uint64 send_inline_query(UserId bot_user_id, DialogId dialog_id, Location user_location, const string &query,
const string &offset, Promise<Unit> &&promise);
@@ -55,27 +62,29 @@ class InlineQueriesManager : public Actor {
void remove_recent_inline_bot(UserId bot_user_id, Promise<Unit> &&promise);
- std::tuple<const MessageContent *, const ReplyMarkup *, bool> get_inline_message_content(int64 query_id,
- const string &result_id);
+ const InlineMessageContent *get_inline_message_content(int64 query_id, const string &result_id);
UserId get_inline_bot_user_id(int64 query_id) const;
- void on_get_inline_query_results(UserId bot_user_id, uint64 query_hash,
+ void on_get_inline_query_results(DialogId dialog_id, UserId bot_user_id, uint64 query_hash,
tl_object_ptr<telegram_api::messages_botResults> &&results);
tl_object_ptr<td_api::inlineQueryResults> get_inline_query_results_object(uint64 query_hash);
- void on_new_query(int64 query_id, UserId sender_user_id, Location user_location, const string &query,
+ void on_new_query(int64 query_id, UserId sender_user_id, Location user_location,
+ tl_object_ptr<telegram_api::InlineQueryPeerType> peer_type, const string &query,
const string &offset);
void on_chosen_result(UserId user_id, Location user_location, const string &query, const string &result_id,
- tl_object_ptr<telegram_api::inputBotInlineMessageID> &&input_bot_inline_message_id);
+ tl_object_ptr<telegram_api::InputBotInlineMessageID> &&input_bot_inline_message_id);
- static tl_object_ptr<telegram_api::inputBotInlineMessageID> get_input_bot_inline_message_id(
+ static int32 get_inline_message_dc_id(const tl_object_ptr<telegram_api::InputBotInlineMessageID> &inline_message_id);
+
+ static tl_object_ptr<telegram_api::InputBotInlineMessageID> get_input_bot_inline_message_id(
const string &inline_message_id);
static string get_inline_message_id(
- tl_object_ptr<telegram_api::inputBotInlineMessageID> &&input_bot_inline_message_id);
+ tl_object_ptr<telegram_api::InputBotInlineMessageID> &&input_bot_inline_message_id);
private:
static constexpr int32 MAX_RECENT_INLINE_BOTS = 20; // some reasonable value
@@ -86,21 +95,20 @@ class InlineQueriesManager : public Actor {
static constexpr int32 BOT_INLINE_MEDIA_RESULT_FLAG_HAS_TITLE = 1 << 2;
static constexpr int32 BOT_INLINE_MEDIA_RESULT_FLAG_HAS_DESCRIPTION = 1 << 3;
- Result<FormattedText> process_input_caption(td_api::object_ptr<td_api::formattedText> &&caption) const;
-
- tl_object_ptr<telegram_api::inputBotInlineMessageMediaAuto> get_input_bot_inline_message_media_auto(
- const FormattedText &caption, tl_object_ptr<telegram_api::ReplyMarkup> &&input_reply_markup) const;
+ Result<tl_object_ptr<telegram_api::InputBotInlineResult>> get_input_bot_inline_result(
+ td_api::object_ptr<td_api::InputInlineQueryResult> &&result, bool *is_gallery, bool *force_vertical) const;
Result<tl_object_ptr<telegram_api::InputBotInlineMessage>> get_inline_message(
tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup_ptr,
- int32 allowed_media_content_id) const TD_WARN_UNUSED_RESULT; // TODO make static
+ int32 allowed_media_content_id) const TD_WARN_UNUSED_RESULT;
bool register_inline_message_content(int64 query_id, const string &result_id, FileId file_id,
tl_object_ptr<telegram_api::BotInlineMessage> &&inline_message,
- int32 allowed_media_content_id, Photo *photo = nullptr, Game *game = nullptr);
+ int32 allowed_media_content_id, bool allow_invoice, Photo *photo = nullptr,
+ Game *game = nullptr);
- tl_object_ptr<td_api::photoSize> register_thumbnail(
+ tl_object_ptr<td_api::thumbnail> register_thumbnail(
tl_object_ptr<telegram_api::WebDocument> &&web_document_ptr) const;
static string get_web_document_url(const tl_object_ptr<telegram_api::WebDocument> &web_document_ptr);
@@ -115,12 +123,12 @@ class InlineQueriesManager : public Actor {
static void on_drop_inline_query_result_timeout_callback(void *inline_queries_manager_ptr, int64 query_hash);
- void loop() override;
+ void loop() final;
- void tear_down() override;
+ void tear_down() final;
int32 recently_used_bots_loaded_ = 0; // 0 - not loaded, 1 - load request was sent, 2 - loaded
- MultiPromiseActor resolve_recent_inline_bots_multipromise_;
+ MultiPromiseActor resolve_recent_inline_bots_multipromise_{"ResolveRecentInlineBotsMultiPromiseActor"};
vector<UserId> recently_used_bot_user_ids_;
@@ -128,13 +136,14 @@ class InlineQueriesManager : public Actor {
uint64 query_hash;
UserId bot_user_id;
DialogId dialog_id;
+ tl_object_ptr<telegram_api::InputPeer> input_peer;
Location user_location;
string query;
string offset;
Promise<Unit> promise;
};
- double next_inline_query_time_ = -1.0;
+ double next_inline_query_time_ = 0.0;
unique_ptr<PendingInlineQuery> pending_inline_query_;
NetQueryRef sent_query_;
@@ -144,19 +153,13 @@ class InlineQueriesManager : public Actor {
int32 pending_request_count;
};
- MultiTimeout drop_inline_query_result_timeout_;
- std::unordered_map<uint64, InlineQueryResult> inline_query_results_; // query_hash -> result
-
- struct InlineMessageContent {
- unique_ptr<MessageContent> message_content;
- unique_ptr<ReplyMarkup> message_reply_markup;
- bool disable_web_page_preview;
- };
+ MultiTimeout drop_inline_query_result_timeout_{"DropInlineQueryResultTimeout"};
+ FlatHashMap<uint64, InlineQueryResult> inline_query_results_; // query_hash -> result
- std::unordered_map<int64, std::unordered_map<string, InlineMessageContent>>
+ FlatHashMap<int64, FlatHashMap<string, InlineMessageContent>>
inline_message_contents_; // query_id -> [result_id -> inline_message_content]
- std::unordered_map<int64, UserId> query_id_to_bot_user_id_;
+ FlatHashMap<int64, UserId> query_id_to_bot_user_id_;
Td *td_;
ActorShared<> parent_;
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputDialogId.cpp b/protocols/Telegram/tdlib/td/td/telegram/InputDialogId.cpp
new file mode 100644
index 0000000000..9ba53e3dc9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputDialogId.cpp
@@ -0,0 +1,157 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/InputDialogId.h"
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/logging.h"
+
+namespace td {
+
+InputDialogId::InputDialogId(const tl_object_ptr<telegram_api::InputPeer> &input_peer) {
+ CHECK(input_peer != nullptr);
+ switch (input_peer->get_id()) {
+ case telegram_api::inputPeerUser::ID: {
+ auto input_user = static_cast<const telegram_api::inputPeerUser *>(input_peer.get());
+ UserId user_id(input_user->user_id_);
+ if (user_id.is_valid()) {
+ dialog_id = DialogId(user_id);
+ access_hash = input_user->access_hash_;
+ return;
+ }
+ break;
+ }
+ case telegram_api::inputPeerChat::ID: {
+ auto input_chat = static_cast<const telegram_api::inputPeerChat *>(input_peer.get());
+ ChatId chat_id(input_chat->chat_id_);
+ if (chat_id.is_valid()) {
+ dialog_id = DialogId(chat_id);
+ return;
+ }
+ break;
+ }
+ case telegram_api::inputPeerChannel::ID: {
+ auto input_channel = static_cast<const telegram_api::inputPeerChannel *>(input_peer.get());
+ ChannelId channel_id(input_channel->channel_id_);
+ if (channel_id.is_valid()) {
+ dialog_id = DialogId(channel_id);
+ access_hash = input_channel->access_hash_;
+ return;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ LOG(ERROR) << "Receive " << to_string(input_peer);
+}
+
+vector<InputDialogId> InputDialogId::get_input_dialog_ids(
+ const vector<tl_object_ptr<telegram_api::InputPeer>> &input_peers,
+ FlatHashSet<DialogId, DialogIdHash> *added_dialog_ids) {
+ FlatHashSet<DialogId, DialogIdHash> temp_added_dialog_ids;
+ if (added_dialog_ids == nullptr) {
+ added_dialog_ids = &temp_added_dialog_ids;
+ }
+ vector<InputDialogId> result;
+ result.reserve(input_peers.size());
+ for (auto &input_peer : input_peers) {
+ InputDialogId input_dialog_id(input_peer);
+ if (input_dialog_id.is_valid() && added_dialog_ids->insert(input_dialog_id.get_dialog_id()).second) {
+ result.push_back(input_dialog_id);
+ }
+ }
+ return result;
+}
+
+vector<DialogId> InputDialogId::get_dialog_ids(const vector<InputDialogId> &input_dialog_ids) {
+ return transform(input_dialog_ids, [](InputDialogId input_dialog_id) { return input_dialog_id.get_dialog_id(); });
+}
+
+vector<telegram_api::object_ptr<telegram_api::InputDialogPeer>> InputDialogId::get_input_dialog_peers(
+ const vector<InputDialogId> &input_dialog_ids) {
+ vector<telegram_api::object_ptr<telegram_api::InputDialogPeer>> result;
+ result.reserve(input_dialog_ids.size());
+ for (const auto &input_dialog_id : input_dialog_ids) {
+ auto input_peer = input_dialog_id.get_input_peer();
+ if (input_peer != nullptr) {
+ result.push_back(telegram_api::make_object<telegram_api::inputDialogPeer>(std::move(input_peer)));
+ }
+ }
+ return result;
+}
+
+vector<telegram_api::object_ptr<telegram_api::InputPeer>> InputDialogId::get_input_peers(
+ const vector<InputDialogId> &input_dialog_ids) {
+ vector<telegram_api::object_ptr<telegram_api::InputPeer>> result;
+ result.reserve(input_dialog_ids.size());
+ for (const auto &input_dialog_id : input_dialog_ids) {
+ auto input_peer = input_dialog_id.get_input_peer();
+ CHECK(input_peer != nullptr);
+ result.push_back(std::move(input_peer));
+ }
+ return result;
+}
+
+tl_object_ptr<telegram_api::InputPeer> InputDialogId::get_input_peer() const {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return make_tl_object<telegram_api::inputPeerUser>(dialog_id.get_user_id().get(), access_hash);
+ case DialogType::Chat:
+ return make_tl_object<telegram_api::inputPeerChat>(dialog_id.get_chat_id().get());
+ case DialogType::Channel:
+ return make_tl_object<telegram_api::inputPeerChannel>(dialog_id.get_channel_id().get(), access_hash);
+ case DialogType::SecretChat:
+ case DialogType::None:
+ return nullptr;
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+bool InputDialogId::are_equivalent(const vector<InputDialogId> &lhs, const vector<InputDialogId> &rhs) {
+ auto lhs_it = lhs.begin();
+ auto rhs_it = rhs.begin();
+ while (lhs_it != lhs.end() || rhs_it != rhs.end()) {
+ while (lhs_it != lhs.end() && lhs_it->get_dialog_id().get_type() == DialogType::SecretChat) {
+ ++lhs_it;
+ }
+ while (rhs_it != rhs.end() && rhs_it->get_dialog_id().get_type() == DialogType::SecretChat) {
+ ++rhs_it;
+ }
+ if (lhs_it == lhs.end() || rhs_it == rhs.end()) {
+ break;
+ }
+ if (lhs_it->get_dialog_id() != rhs_it->get_dialog_id()) {
+ return false;
+ }
+ ++lhs_it;
+ ++rhs_it;
+ }
+ return lhs_it == lhs.end() && rhs_it == rhs.end();
+}
+
+bool InputDialogId::contains(const vector<InputDialogId> &input_dialog_ids, DialogId dialog_id) {
+ for (auto &input_dialog_id : input_dialog_ids) {
+ if (input_dialog_id.get_dialog_id() == dialog_id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InputDialogId::remove(vector<InputDialogId> &input_dialog_ids, DialogId dialog_id) {
+ return td::remove_if(input_dialog_ids, [dialog_id](InputDialogId input_dialog_id) {
+ return input_dialog_id.get_dialog_id() == dialog_id;
+ });
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputDialogId.h b/protocols/Telegram/tdlib/td/td/telegram/InputDialogId.h
new file mode 100644
index 0000000000..ed3a879687
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputDialogId.h
@@ -0,0 +1,82 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class InputDialogId {
+ DialogId dialog_id;
+ int64 access_hash = 0;
+
+ public:
+ InputDialogId() = default;
+
+ explicit InputDialogId(DialogId dialog_id) : dialog_id(dialog_id) {
+ }
+
+ explicit InputDialogId(const tl_object_ptr<telegram_api::InputPeer> &input_peer);
+
+ static vector<InputDialogId> get_input_dialog_ids(const vector<tl_object_ptr<telegram_api::InputPeer>> &input_peers,
+ FlatHashSet<DialogId, DialogIdHash> *added_dialog_ids = nullptr);
+
+ static vector<DialogId> get_dialog_ids(const vector<InputDialogId> &input_dialog_ids);
+
+ static vector<telegram_api::object_ptr<telegram_api::InputDialogPeer>> get_input_dialog_peers(
+ const vector<InputDialogId> &input_dialog_ids);
+
+ static vector<telegram_api::object_ptr<telegram_api::InputPeer>> get_input_peers(
+ const vector<InputDialogId> &input_dialog_ids);
+
+ static bool are_equivalent(const vector<InputDialogId> &lhs, const vector<InputDialogId> &rhs);
+
+ static bool contains(const vector<InputDialogId> &input_dialog_ids, DialogId dialog_id);
+
+ static bool remove(vector<InputDialogId> &input_dialog_ids, DialogId dialog_id);
+
+ bool operator==(const InputDialogId &other) const {
+ return dialog_id == other.dialog_id && access_hash == other.access_hash;
+ }
+
+ bool operator!=(const InputDialogId &other) const {
+ return !(*this == other);
+ }
+
+ bool is_valid() const {
+ return dialog_id.is_valid();
+ }
+
+ DialogId get_dialog_id() const {
+ return dialog_id;
+ }
+
+ tl_object_ptr<telegram_api::InputPeer> get_input_peer() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ dialog_id.store(storer);
+ storer.store_long(access_hash);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ dialog_id.parse(parser);
+ access_hash = parser.fetch_long();
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, InputDialogId input_dialog_id) {
+ return string_builder << "input " << input_dialog_id.get_dialog_id();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputGroupCallId.cpp b/protocols/Telegram/tdlib/td/td/telegram/InputGroupCallId.cpp
new file mode 100644
index 0000000000..494799676e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputGroupCallId.cpp
@@ -0,0 +1,23 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/InputGroupCallId.h"
+
+namespace td {
+
+InputGroupCallId::InputGroupCallId(const tl_object_ptr<telegram_api::inputGroupCall> &input_group_call)
+ : group_call_id(input_group_call->id_), access_hash(input_group_call->access_hash_) {
+}
+
+tl_object_ptr<telegram_api::inputGroupCall> InputGroupCallId::get_input_group_call() const {
+ return make_tl_object<telegram_api::inputGroupCall>(group_call_id, access_hash);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, InputGroupCallId input_group_call_id) {
+ return string_builder << "input group call " << input_group_call_id.group_call_id;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputGroupCallId.h b/protocols/Telegram/tdlib/td/td/telegram/InputGroupCallId.h
new file mode 100644
index 0000000000..852b68d905
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputGroupCallId.h
@@ -0,0 +1,72 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class InputGroupCallId {
+ int64 group_call_id = 0;
+ int64 access_hash = 0;
+
+ public:
+ InputGroupCallId() = default;
+
+ explicit InputGroupCallId(const tl_object_ptr<telegram_api::inputGroupCall> &input_group_call);
+
+ InputGroupCallId(int64 group_call_id, int64 access_hash) : group_call_id(group_call_id), access_hash(access_hash) {
+ }
+
+ bool operator==(const InputGroupCallId &other) const {
+ return group_call_id == other.group_call_id;
+ }
+
+ bool operator!=(const InputGroupCallId &other) const {
+ return !(*this == other);
+ }
+
+ bool is_identical(const InputGroupCallId &other) const {
+ return group_call_id == other.group_call_id && access_hash == other.access_hash;
+ }
+
+ bool is_valid() const {
+ return group_call_id != 0;
+ }
+
+ uint32 get_hash() const {
+ return Hash<int64>()(group_call_id);
+ }
+
+ tl_object_ptr<telegram_api::inputGroupCall> get_input_group_call() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ storer.store_long(group_call_id);
+ storer.store_long(access_hash);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ group_call_id = parser.fetch_long();
+ access_hash = parser.fetch_long();
+ }
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, InputGroupCallId input_group_call_id);
+};
+
+struct InputGroupCallIdHash {
+ uint32 operator()(InputGroupCallId input_group_call_id) const {
+ return input_group_call_id.get_hash();
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputInvoice.cpp b/protocols/Telegram/tdlib/td/td/telegram/InputInvoice.cpp
new file mode 100644
index 0000000000..f10fb4e312
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputInvoice.cpp
@@ -0,0 +1,413 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/InputInvoice.h"
+
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/HttpUrl.h"
+#include "td/utils/logging.h"
+#include "td/utils/MimeType.h"
+#include "td/utils/PathView.h"
+
+namespace td {
+
+bool operator==(const InputInvoice &lhs, const InputInvoice &rhs) {
+ auto are_invoice_equal = [](const InputInvoice::Invoice &lhs, const InputInvoice::Invoice &rhs) {
+ return lhs.is_test_ == rhs.is_test_ && lhs.need_name_ == rhs.need_name_ &&
+ lhs.need_phone_number_ == rhs.need_phone_number_ && lhs.need_email_address_ == rhs.need_email_address_ &&
+ lhs.need_shipping_address_ == rhs.need_shipping_address_ &&
+ lhs.send_phone_number_to_provider_ == rhs.send_phone_number_to_provider_ &&
+ lhs.send_email_address_to_provider_ == rhs.send_email_address_to_provider_ &&
+ lhs.is_flexible_ == rhs.is_flexible_ && lhs.currency_ == rhs.currency_ &&
+ lhs.price_parts_ == rhs.price_parts_ && lhs.max_tip_amount_ == rhs.max_tip_amount_ &&
+ lhs.suggested_tip_amounts_ == rhs.suggested_tip_amounts_ &&
+ lhs.recurring_payment_terms_of_service_url_ == rhs.recurring_payment_terms_of_service_url_;
+ };
+
+ return lhs.title_ == rhs.title_ && lhs.description_ == rhs.description_ && lhs.photo_ == rhs.photo_ &&
+ lhs.start_parameter_ == rhs.start_parameter_ && are_invoice_equal(lhs.invoice_, rhs.invoice_) &&
+ lhs.payload_ == rhs.payload_ && lhs.provider_token_ == rhs.provider_token_ &&
+ lhs.provider_data_ == rhs.provider_data_ && lhs.extended_media_ == rhs.extended_media_ &&
+ lhs.total_amount_ == rhs.total_amount_ && lhs.receipt_message_id_ == rhs.receipt_message_id_;
+}
+
+bool operator!=(const InputInvoice &lhs, const InputInvoice &rhs) {
+ return !(lhs == rhs);
+}
+
+InputInvoice::InputInvoice(tl_object_ptr<telegram_api::messageMediaInvoice> &&message_invoice, Td *td,
+ DialogId owner_dialog_id, FormattedText &&message) {
+ title_ = std::move(message_invoice->title_);
+ description_ = std::move(message_invoice->description_);
+ photo_ = get_web_document_photo(td->file_manager_.get(), std::move(message_invoice->photo_), owner_dialog_id);
+ start_parameter_ = std::move(message_invoice->start_param_);
+ invoice_.currency_ = std::move(message_invoice->currency_);
+ invoice_.is_test_ = message_invoice->test_;
+ invoice_.need_shipping_address_ = message_invoice->shipping_address_requested_;
+ // payload_ = string();
+ // provider_token_ = string();
+ // provider_data_ = string();
+ extended_media_ =
+ MessageExtendedMedia(td, std::move(message_invoice->extended_media_), std::move(message), owner_dialog_id);
+ if (message_invoice->total_amount_ <= 0 || !check_currency_amount(message_invoice->total_amount_)) {
+ LOG(ERROR) << "Receive invalid total amount " << message_invoice->total_amount_;
+ message_invoice->total_amount_ = 0;
+ }
+ total_amount_ = message_invoice->total_amount_;
+ if ((message_invoice->flags_ & telegram_api::messageMediaInvoice::RECEIPT_MSG_ID_MASK) != 0) {
+ receipt_message_id_ = MessageId(ServerMessageId(message_invoice->receipt_msg_id_));
+ if (!receipt_message_id_.is_valid()) {
+ LOG(ERROR) << "Receive as receipt message " << receipt_message_id_ << " in " << owner_dialog_id;
+ receipt_message_id_ = MessageId();
+ }
+ }
+}
+
+InputInvoice::InputInvoice(tl_object_ptr<telegram_api::botInlineMessageMediaInvoice> &&message_invoice, Td *td,
+ DialogId owner_dialog_id) {
+ title_ = std::move(message_invoice->title_);
+ description_ = std::move(message_invoice->description_);
+ photo_ = get_web_document_photo(td->file_manager_.get(), std::move(message_invoice->photo_), owner_dialog_id);
+ // start_parameter_ = string();
+ invoice_.currency_ = std::move(message_invoice->currency_);
+ invoice_.is_test_ = message_invoice->test_;
+ invoice_.need_shipping_address_ = message_invoice->shipping_address_requested_;
+ // payload_ = string();
+ // provider_token_ = string();
+ // provider_data_ = string();
+ // extended_media_ = MessageExtendedMedia();
+ if (message_invoice->total_amount_ <= 0 || !check_currency_amount(message_invoice->total_amount_)) {
+ LOG(ERROR) << "Receive invalid total amount " << message_invoice->total_amount_;
+ message_invoice->total_amount_ = 0;
+ }
+ total_amount_ = message_invoice->total_amount_;
+ // receipt_message_id_ = MessageId();
+}
+
+Result<InputInvoice> InputInvoice::process_input_message_invoice(
+ td_api::object_ptr<td_api::InputMessageContent> &&input_message_content, Td *td, DialogId owner_dialog_id,
+ bool is_premium) {
+ CHECK(input_message_content != nullptr);
+ CHECK(input_message_content->get_id() == td_api::inputMessageInvoice::ID);
+ auto input_invoice = move_tl_object_as<td_api::inputMessageInvoice>(input_message_content);
+ if (input_invoice->invoice_ == nullptr) {
+ return Status::Error(400, "Invoice must be non-empty");
+ }
+
+ if (!clean_input_string(input_invoice->title_)) {
+ return Status::Error(400, "Invoice title must be encoded in UTF-8");
+ }
+ if (!clean_input_string(input_invoice->description_)) {
+ return Status::Error(400, "Invoice description must be encoded in UTF-8");
+ }
+ if (!clean_input_string(input_invoice->photo_url_)) {
+ return Status::Error(400, "Invoice photo URL must be encoded in UTF-8");
+ }
+ if (!clean_input_string(input_invoice->start_parameter_)) {
+ return Status::Error(400, "Invoice bot start parameter must be encoded in UTF-8");
+ }
+ if (!clean_input_string(input_invoice->provider_token_)) {
+ return Status::Error(400, "Invoice provider token must be encoded in UTF-8");
+ }
+ if (!clean_input_string(input_invoice->provider_data_)) {
+ return Status::Error(400, "Invoice provider data must be encoded in UTF-8");
+ }
+ if (!clean_input_string(input_invoice->invoice_->currency_)) {
+ return Status::Error(400, "Invoice currency must be encoded in UTF-8");
+ }
+
+ InputInvoice result;
+ result.title_ = std::move(input_invoice->title_);
+ result.description_ = std::move(input_invoice->description_);
+
+ auto r_http_url = parse_url(input_invoice->photo_url_);
+ if (r_http_url.is_error()) {
+ if (!input_invoice->photo_url_.empty()) {
+ LOG(INFO) << "Can't register url " << input_invoice->photo_url_;
+ }
+ } else {
+ auto url = r_http_url.ok().get_url();
+ auto r_invoice_file_id = td->file_manager_->from_persistent_id(url, FileType::Temp);
+ if (r_invoice_file_id.is_error()) {
+ LOG(INFO) << "Can't register url " << url;
+ } else {
+ auto invoice_file_id = r_invoice_file_id.move_as_ok();
+
+ PhotoSize s;
+ s.type = 'n';
+ s.dimensions = get_dimensions(input_invoice->photo_width_, input_invoice->photo_height_, nullptr);
+ s.size = input_invoice->photo_size_; // TODO use invoice_file_id size
+ s.file_id = invoice_file_id;
+
+ result.photo_.id = 0;
+ result.photo_.photos.push_back(s);
+ }
+ }
+ result.start_parameter_ = std::move(input_invoice->start_parameter_);
+
+ result.invoice_.currency_ = std::move(input_invoice->invoice_->currency_);
+ result.invoice_.price_parts_.reserve(input_invoice->invoice_->price_parts_.size());
+ int64 total_amount = 0;
+ for (auto &price : input_invoice->invoice_->price_parts_) {
+ if (!clean_input_string(price->label_)) {
+ return Status::Error(400, "Invoice price label must be encoded in UTF-8");
+ }
+ if (!check_currency_amount(price->amount_)) {
+ return Status::Error(400, "Too big amount of the currency specified");
+ }
+ result.invoice_.price_parts_.emplace_back(std::move(price->label_), price->amount_);
+ total_amount += price->amount_;
+ }
+ if (total_amount <= 0) {
+ return Status::Error(400, "Total price must be positive");
+ }
+ if (!check_currency_amount(total_amount)) {
+ return Status::Error(400, "Total price is too big");
+ }
+ result.total_amount_ = total_amount;
+
+ if (input_invoice->invoice_->max_tip_amount_ < 0 ||
+ !check_currency_amount(input_invoice->invoice_->max_tip_amount_)) {
+ return Status::Error(400, "Invalid max_tip_amount of the currency specified");
+ }
+ for (auto tip_amount : input_invoice->invoice_->suggested_tip_amounts_) {
+ if (tip_amount <= 0) {
+ return Status::Error(400, "Suggested tip amount must be positive");
+ }
+ if (tip_amount > input_invoice->invoice_->max_tip_amount_) {
+ return Status::Error(400, "Suggested tip amount can't be bigger than max_tip_amount");
+ }
+ }
+ if (input_invoice->invoice_->suggested_tip_amounts_.size() > 4) {
+ return Status::Error(400, "There can be at most 4 suggested tip amounts");
+ }
+
+ result.invoice_.max_tip_amount_ = input_invoice->invoice_->max_tip_amount_;
+ result.invoice_.suggested_tip_amounts_ = std::move(input_invoice->invoice_->suggested_tip_amounts_);
+ result.invoice_.recurring_payment_terms_of_service_url_ =
+ std::move(input_invoice->invoice_->recurring_payment_terms_of_service_url_);
+ result.invoice_.is_test_ = input_invoice->invoice_->is_test_;
+ result.invoice_.need_name_ = input_invoice->invoice_->need_name_;
+ result.invoice_.need_phone_number_ = input_invoice->invoice_->need_phone_number_;
+ result.invoice_.need_email_address_ = input_invoice->invoice_->need_email_address_;
+ result.invoice_.need_shipping_address_ = input_invoice->invoice_->need_shipping_address_;
+ result.invoice_.send_phone_number_to_provider_ = input_invoice->invoice_->send_phone_number_to_provider_;
+ result.invoice_.send_email_address_to_provider_ = input_invoice->invoice_->send_email_address_to_provider_;
+ result.invoice_.is_flexible_ = input_invoice->invoice_->is_flexible_;
+ if (result.invoice_.send_phone_number_to_provider_) {
+ result.invoice_.need_phone_number_ = true;
+ }
+ if (result.invoice_.send_email_address_to_provider_) {
+ result.invoice_.need_email_address_ = true;
+ }
+ if (result.invoice_.is_flexible_) {
+ result.invoice_.need_shipping_address_ = true;
+ }
+
+ result.payload_ = std::move(input_invoice->payload_);
+ result.provider_token_ = std::move(input_invoice->provider_token_);
+ result.provider_data_ = std::move(input_invoice->provider_data_);
+
+ TRY_RESULT(extended_media, MessageExtendedMedia::get_message_extended_media(
+ td, std::move(input_invoice->extended_media_content_), owner_dialog_id, is_premium));
+ result.extended_media_ = std::move(extended_media);
+
+ return result;
+}
+
+tl_object_ptr<td_api::messageInvoice> InputInvoice::get_message_invoice_object(Td *td, bool skip_bot_commands,
+ int32 max_media_timestamp) const {
+ return make_tl_object<td_api::messageInvoice>(
+ title_, get_product_description_object(description_), get_photo_object(td->file_manager_.get(), photo_),
+ invoice_.currency_, total_amount_, start_parameter_, invoice_.is_test_, invoice_.need_shipping_address_,
+ receipt_message_id_.get(),
+ extended_media_.get_message_extended_media_object(td, skip_bot_commands, max_media_timestamp));
+}
+
+tl_object_ptr<telegram_api::invoice> InputInvoice::Invoice::get_input_invoice() const {
+ int32 flags = 0;
+ if (is_test_) {
+ flags |= telegram_api::invoice::TEST_MASK;
+ }
+ if (need_name_) {
+ flags |= telegram_api::invoice::NAME_REQUESTED_MASK;
+ }
+ if (need_phone_number_) {
+ flags |= telegram_api::invoice::PHONE_REQUESTED_MASK;
+ }
+ if (need_email_address_) {
+ flags |= telegram_api::invoice::EMAIL_REQUESTED_MASK;
+ }
+ if (need_shipping_address_) {
+ flags |= telegram_api::invoice::SHIPPING_ADDRESS_REQUESTED_MASK;
+ }
+ if (send_phone_number_to_provider_) {
+ flags |= telegram_api::invoice::PHONE_TO_PROVIDER_MASK;
+ }
+ if (send_email_address_to_provider_) {
+ flags |= telegram_api::invoice::EMAIL_TO_PROVIDER_MASK;
+ }
+ if (is_flexible_) {
+ flags |= telegram_api::invoice::FLEXIBLE_MASK;
+ }
+ if (max_tip_amount_ != 0) {
+ flags |= telegram_api::invoice::MAX_TIP_AMOUNT_MASK;
+ }
+ if (!recurring_payment_terms_of_service_url_.empty()) {
+ flags |= telegram_api::invoice::RECURRING_TERMS_URL_MASK;
+ }
+
+ auto prices = transform(price_parts_, [](const LabeledPricePart &price) {
+ return telegram_api::make_object<telegram_api::labeledPrice>(price.label, price.amount);
+ });
+ return make_tl_object<telegram_api::invoice>(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, currency_, std::move(prices),
+ max_tip_amount_, vector<int64>(suggested_tip_amounts_), recurring_payment_terms_of_service_url_);
+}
+
+static tl_object_ptr<telegram_api::inputWebDocument> get_input_web_document(const FileManager *file_manager,
+ const Photo &photo) {
+ if (photo.is_empty()) {
+ return nullptr;
+ }
+
+ CHECK(photo.photos.size() == 1);
+ const PhotoSize &size = photo.photos[0];
+ CHECK(size.file_id.is_valid());
+
+ vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
+ if (size.dimensions.width != 0 && size.dimensions.height != 0) {
+ attributes.push_back(
+ make_tl_object<telegram_api::documentAttributeImageSize>(size.dimensions.width, size.dimensions.height));
+ }
+
+ auto file_view = file_manager->get_file_view(size.file_id);
+ CHECK(file_view.has_url());
+
+ auto file_name = get_url_file_name(file_view.url());
+ return make_tl_object<telegram_api::inputWebDocument>(
+ file_view.url(), size.size, MimeType::from_extension(PathView(file_name).extension(), "image/jpeg"),
+ std::move(attributes));
+}
+
+tl_object_ptr<telegram_api::inputMediaInvoice> InputInvoice::get_input_media_invoice(
+ Td *td, tl_object_ptr<telegram_api::InputFile> input_file,
+ tl_object_ptr<telegram_api::InputFile> input_thumbnail) const {
+ int32 flags = 0;
+ if (!start_parameter_.empty()) {
+ flags |= telegram_api::inputMediaInvoice::START_PARAM_MASK;
+ }
+ auto input_web_document = get_input_web_document(td->file_manager_.get(), photo_);
+ if (input_web_document != nullptr) {
+ flags |= telegram_api::inputMediaInvoice::PHOTO_MASK;
+ }
+ telegram_api::object_ptr<telegram_api::InputMedia> extended_media;
+ if (!extended_media_.is_empty()) {
+ flags |= telegram_api::inputMediaInvoice::EXTENDED_MEDIA_MASK;
+ extended_media = extended_media_.get_input_media(td, std::move(input_file), std::move(input_thumbnail));
+ if (extended_media == nullptr) {
+ return nullptr;
+ }
+ }
+
+ return make_tl_object<telegram_api::inputMediaInvoice>(
+ flags, title_, description_, std::move(input_web_document), invoice_.get_input_invoice(), BufferSlice(payload_),
+ provider_token_,
+ telegram_api::make_object<telegram_api::dataJSON>(provider_data_.empty() ? "null" : provider_data_),
+ start_parameter_, std::move(extended_media));
+}
+
+tl_object_ptr<telegram_api::inputBotInlineMessageMediaInvoice> InputInvoice::get_input_bot_inline_message_media_invoice(
+ tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup, Td *td) const {
+ int32 flags = 0;
+ if (reply_markup != nullptr) {
+ flags |= telegram_api::inputBotInlineMessageMediaInvoice::REPLY_MARKUP_MASK;
+ }
+ auto input_web_document = get_input_web_document(td->file_manager_.get(), photo_);
+ if (input_web_document != nullptr) {
+ flags |= telegram_api::inputBotInlineMessageMediaInvoice::PHOTO_MASK;
+ }
+ return make_tl_object<telegram_api::inputBotInlineMessageMediaInvoice>(
+ flags, title_, description_, std::move(input_web_document), invoice_.get_input_invoice(), BufferSlice(payload_),
+ provider_token_,
+ telegram_api::make_object<telegram_api::dataJSON>(provider_data_.empty() ? "null" : provider_data_),
+ std::move(reply_markup));
+}
+
+vector<FileId> InputInvoice::get_file_ids(const Td *td) const {
+ auto file_ids = photo_get_file_ids(photo_);
+ extended_media_.append_file_ids(td, file_ids);
+ return file_ids;
+}
+
+void InputInvoice::delete_thumbnail(Td *td) {
+ extended_media_.delete_thumbnail(td);
+}
+
+bool InputInvoice::need_reget() const {
+ return extended_media_.need_reget();
+}
+
+bool InputInvoice::has_media_timestamp() const {
+ return extended_media_.has_media_timestamp();
+}
+
+bool InputInvoice::is_equal_but_different(const InputInvoice &other) const {
+ return extended_media_.is_equal_but_different(other.extended_media_);
+}
+
+const FormattedText *InputInvoice::get_caption() const {
+ return extended_media_.get_caption();
+}
+
+int32 InputInvoice::get_duration(const Td *td) const {
+ return extended_media_.get_duration(td);
+}
+
+FileId InputInvoice::get_upload_file_id() const {
+ return extended_media_.get_upload_file_id();
+}
+
+FileId InputInvoice::get_any_file_id() const {
+ return extended_media_.get_any_file_id();
+}
+
+FileId InputInvoice::get_thumbnail_file_id(const Td *td) const {
+ return extended_media_.get_thumbnail_file_id(td);
+}
+
+void InputInvoice::update_from(const InputInvoice &old_input_invoice) {
+ extended_media_.update_from(old_input_invoice.extended_media_);
+}
+
+bool InputInvoice::update_extended_media(telegram_api::object_ptr<telegram_api::MessageExtendedMedia> extended_media,
+ DialogId owner_dialog_id, Td *td) {
+ return extended_media_.update_to(td, std::move(extended_media), owner_dialog_id);
+}
+
+bool InputInvoice::need_poll_extended_media() const {
+ return extended_media_.need_poll();
+}
+
+tl_object_ptr<td_api::formattedText> get_product_description_object(const string &description) {
+ FormattedText result;
+ result.text = description;
+ result.entities = find_entities(result.text, true, true);
+ return get_formatted_text_object(result, true, 0);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputInvoice.h b/protocols/Telegram/tdlib/td/td/telegram/InputInvoice.h
new file mode 100644
index 0000000000..c5f7d0abc7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputInvoice.h
@@ -0,0 +1,133 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/LabeledPricePart.h"
+#include "td/telegram/MessageExtendedMedia.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class Td;
+
+class InputInvoice {
+ struct Invoice {
+ string currency_;
+ vector<LabeledPricePart> price_parts_;
+ int64 max_tip_amount_ = 0;
+ vector<int64> suggested_tip_amounts_;
+ string recurring_payment_terms_of_service_url_;
+ bool is_test_ = false;
+ bool need_name_ = false;
+ bool need_phone_number_ = false;
+ bool need_email_address_ = false;
+ bool need_shipping_address_ = false;
+ bool send_phone_number_to_provider_ = false;
+ bool send_email_address_to_provider_ = false;
+ bool is_flexible_ = false;
+
+ Invoice() = default;
+ Invoice(string &&currency, bool is_test, bool need_shipping_address)
+ : currency_(std::move(currency)), is_test_(is_test), need_shipping_address_(need_shipping_address) {
+ }
+
+ tl_object_ptr<telegram_api::invoice> get_input_invoice() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ string title_;
+ string description_;
+ Photo photo_;
+ string start_parameter_;
+ Invoice invoice_;
+ string payload_;
+ string provider_token_;
+ string provider_data_;
+ MessageExtendedMedia extended_media_;
+
+ int64 total_amount_ = 0;
+ MessageId receipt_message_id_;
+
+ friend bool operator==(const InputInvoice &lhs, const InputInvoice &rhs);
+
+ public:
+ InputInvoice() = default;
+
+ InputInvoice(tl_object_ptr<telegram_api::messageMediaInvoice> &&message_invoice, Td *td, DialogId owner_dialog_id,
+ FormattedText &&message);
+
+ InputInvoice(tl_object_ptr<telegram_api::botInlineMessageMediaInvoice> &&message_invoice, Td *td,
+ DialogId owner_dialog_id);
+
+ static Result<InputInvoice> process_input_message_invoice(
+ td_api::object_ptr<td_api::InputMessageContent> &&input_message_content, Td *td, DialogId owner_dialog_id,
+ bool is_premium);
+
+ tl_object_ptr<td_api::messageInvoice> get_message_invoice_object(Td *td, bool skip_bot_commands,
+ int32 max_media_timestamp) const;
+
+ tl_object_ptr<telegram_api::inputMediaInvoice> get_input_media_invoice(
+ Td *td, tl_object_ptr<telegram_api::InputFile> input_file,
+ tl_object_ptr<telegram_api::InputFile> input_thumbnail) const;
+
+ tl_object_ptr<telegram_api::inputBotInlineMessageMediaInvoice> get_input_bot_inline_message_media_invoice(
+ tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup, Td *td) const;
+
+ vector<FileId> get_file_ids(const Td *td) const;
+
+ void delete_thumbnail(Td *td);
+
+ bool need_reget() const;
+
+ bool has_media_timestamp() const;
+
+ bool is_equal_but_different(const InputInvoice &other) const;
+
+ const FormattedText *get_caption() const;
+
+ int32 get_duration(const Td *td) const;
+
+ FileId get_upload_file_id() const;
+
+ FileId get_any_file_id() const;
+
+ FileId get_thumbnail_file_id(const Td *td) const;
+
+ void update_from(const InputInvoice &old_input_invoice);
+
+ bool update_extended_media(telegram_api::object_ptr<telegram_api::MessageExtendedMedia> extended_media,
+ DialogId owner_dialog_id, Td *td);
+
+ bool need_poll_extended_media() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+bool operator==(const InputInvoice &lhs, const InputInvoice &rhs);
+bool operator!=(const InputInvoice &lhs, const InputInvoice &rhs);
+
+tl_object_ptr<td_api::formattedText> get_product_description_object(const string &description);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputInvoice.hpp b/protocols/Telegram/tdlib/td/td/telegram/InputInvoice.hpp
new file mode 100644
index 0000000000..497d64f2ef
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputInvoice.hpp
@@ -0,0 +1,195 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/InputInvoice.h"
+
+#include "td/telegram/MessageExtendedMedia.hpp"
+#include "td/telegram/Photo.hpp"
+#include "td/telegram/Version.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void InputInvoice::Invoice::store(StorerT &storer) const {
+ using td::store;
+ bool has_tip = max_tip_amount_ != 0;
+ bool is_recurring = !recurring_payment_terms_of_service_url_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_test_);
+ STORE_FLAG(need_name_);
+ STORE_FLAG(need_phone_number_);
+ STORE_FLAG(need_email_address_);
+ STORE_FLAG(need_shipping_address_);
+ STORE_FLAG(is_flexible_);
+ STORE_FLAG(send_phone_number_to_provider_);
+ STORE_FLAG(send_email_address_to_provider_);
+ STORE_FLAG(has_tip);
+ STORE_FLAG(is_recurring);
+ END_STORE_FLAGS();
+ store(currency_, storer);
+ store(price_parts_, storer);
+ if (has_tip) {
+ store(max_tip_amount_, storer);
+ store(suggested_tip_amounts_, storer);
+ }
+ if (is_recurring) {
+ store(recurring_payment_terms_of_service_url_, storer);
+ }
+}
+
+template <class ParserT>
+void InputInvoice::Invoice::parse(ParserT &parser) {
+ using td::parse;
+ bool has_tip;
+ bool is_recurring;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_test_);
+ PARSE_FLAG(need_name_);
+ PARSE_FLAG(need_phone_number_);
+ PARSE_FLAG(need_email_address_);
+ PARSE_FLAG(need_shipping_address_);
+ PARSE_FLAG(is_flexible_);
+ PARSE_FLAG(send_phone_number_to_provider_);
+ PARSE_FLAG(send_email_address_to_provider_);
+ PARSE_FLAG(has_tip);
+ PARSE_FLAG(is_recurring);
+ END_PARSE_FLAGS();
+ parse(currency_, parser);
+ parse(price_parts_, parser);
+ if (has_tip) {
+ parse(max_tip_amount_, parser);
+ parse(suggested_tip_amounts_, parser);
+ }
+ if (is_recurring) {
+ parse(recurring_payment_terms_of_service_url_, parser);
+ }
+}
+
+template <class StorerT>
+void InputInvoice::store(StorerT &storer) const {
+ using td::store;
+ bool has_description = !description_.empty();
+ bool has_photo = !photo_.is_empty();
+ bool has_start_parameter = !start_parameter_.empty();
+ bool has_payload = !payload_.empty();
+ bool has_provider_token = !provider_token_.empty();
+ bool has_provider_data = !provider_data_.empty();
+ bool has_total_amount = total_amount_ != 0;
+ bool has_receipt_message_id = receipt_message_id_.is_valid();
+ bool has_extended_media = !extended_media_.is_empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_description);
+ STORE_FLAG(has_photo);
+ STORE_FLAG(has_start_parameter);
+ STORE_FLAG(has_payload);
+ STORE_FLAG(has_provider_token);
+ STORE_FLAG(has_provider_data);
+ STORE_FLAG(has_total_amount);
+ STORE_FLAG(has_receipt_message_id);
+ STORE_FLAG(has_extended_media);
+ END_STORE_FLAGS();
+ store(title_, storer);
+ if (has_description) {
+ store(description_, storer);
+ }
+ if (has_photo) {
+ store(photo_, storer);
+ }
+ if (has_start_parameter) {
+ store(start_parameter_, storer);
+ }
+ store(invoice_, storer);
+ if (has_payload) {
+ store(payload_, storer);
+ }
+ if (has_provider_token) {
+ store(provider_token_, storer);
+ }
+ if (has_provider_data) {
+ store(provider_data_, storer);
+ }
+ if (has_total_amount) {
+ store(total_amount_, storer);
+ }
+ if (has_receipt_message_id) {
+ store(receipt_message_id_, storer);
+ }
+ if (has_extended_media) {
+ store(extended_media_, storer);
+ }
+}
+
+template <class ParserT>
+void InputInvoice::parse(ParserT &parser) {
+ using td::parse;
+ bool has_description;
+ bool has_photo;
+ bool has_start_parameter;
+ bool has_payload;
+ bool has_provider_token;
+ bool has_provider_data;
+ bool has_total_amount;
+ bool has_receipt_message_id;
+ bool has_extended_media;
+ if (parser.version() >= static_cast<int32>(Version::AddInputInvoiceFlags)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_description);
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(has_start_parameter);
+ PARSE_FLAG(has_payload);
+ PARSE_FLAG(has_provider_token);
+ PARSE_FLAG(has_provider_data);
+ PARSE_FLAG(has_total_amount);
+ PARSE_FLAG(has_receipt_message_id);
+ PARSE_FLAG(has_extended_media);
+ END_PARSE_FLAGS();
+ } else {
+ has_description = true;
+ has_photo = true;
+ has_start_parameter = true;
+ has_payload = true;
+ has_provider_token = true;
+ has_provider_data = parser.version() >= static_cast<int32>(Version::AddMessageInvoiceProviderData);
+ has_total_amount = true;
+ has_receipt_message_id = true;
+ has_extended_media = false;
+ }
+ parse(title_, parser);
+ if (has_description) {
+ parse(description_, parser);
+ }
+ if (has_photo) {
+ parse(photo_, parser);
+ }
+ if (has_start_parameter) {
+ parse(start_parameter_, parser);
+ }
+ parse(invoice_, parser);
+ if (has_payload) {
+ parse(payload_, parser);
+ }
+ if (has_provider_token) {
+ parse(provider_token_, parser);
+ }
+ if (has_provider_data) {
+ parse(provider_data_, parser);
+ }
+ if (has_total_amount) {
+ parse(total_amount_, parser);
+ }
+ if (has_receipt_message_id) {
+ parse(receipt_message_id_, parser);
+ }
+ if (has_extended_media) {
+ parse(extended_media_, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputMessageText.cpp b/protocols/Telegram/tdlib/td/td/telegram/InputMessageText.cpp
new file mode 100644
index 0000000000..843a3a3e84
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputMessageText.cpp
@@ -0,0 +1,43 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/InputMessageText.h"
+
+#include "td/telegram/MessageEntity.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+bool operator==(const InputMessageText &lhs, const InputMessageText &rhs) {
+ return lhs.text == rhs.text && lhs.disable_web_page_preview == rhs.disable_web_page_preview &&
+ lhs.clear_draft == rhs.clear_draft;
+}
+
+bool operator!=(const InputMessageText &lhs, const InputMessageText &rhs) {
+ return !(lhs == rhs);
+}
+
+Result<InputMessageText> process_input_message_text(const Td *td, DialogId dialog_id,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
+ bool is_bot, bool for_draft) {
+ CHECK(input_message_content != nullptr);
+ CHECK(input_message_content->get_id() == td_api::inputMessageText::ID);
+ auto input_message_text = static_cast<td_api::inputMessageText *>(input_message_content.get());
+ TRY_RESULT(text, get_formatted_text(td, dialog_id, std::move(input_message_text->text_), is_bot, for_draft, for_draft,
+ for_draft));
+ return InputMessageText{std::move(text), input_message_text->disable_web_page_preview_,
+ input_message_text->clear_draft_};
+}
+
+// used only for draft
+td_api::object_ptr<td_api::inputMessageText> get_input_message_text_object(const InputMessageText &input_message_text) {
+ return td_api::make_object<td_api::inputMessageText>(get_formatted_text_object(input_message_text.text, false, -1),
+ input_message_text.disable_web_page_preview,
+ input_message_text.clear_draft);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputMessageText.h b/protocols/Telegram/tdlib/td/td/telegram/InputMessageText.h
new file mode 100644
index 0000000000..208792258f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputMessageText.h
@@ -0,0 +1,39 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/Status.h"
+
+namespace td {
+
+class Td;
+
+class InputMessageText {
+ public:
+ FormattedText text;
+ bool disable_web_page_preview = false;
+ bool clear_draft = false;
+ InputMessageText() = default;
+ InputMessageText(FormattedText text, bool disable_web_page_preview, bool clear_draft)
+ : text(std::move(text)), disable_web_page_preview(disable_web_page_preview), clear_draft(clear_draft) {
+ }
+};
+
+bool operator==(const InputMessageText &lhs, const InputMessageText &rhs);
+bool operator!=(const InputMessageText &lhs, const InputMessageText &rhs);
+
+Result<InputMessageText> process_input_message_text(const Td *td, DialogId dialog_id,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
+ bool is_bot, bool for_draft = false) TD_WARN_UNUSED_RESULT;
+
+td_api::object_ptr<td_api::inputMessageText> get_input_message_text_object(const InputMessageText &input_message_text);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/InputMessageText.hpp b/protocols/Telegram/tdlib/td/td/telegram/InputMessageText.hpp
new file mode 100644
index 0000000000..d50096e796
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/InputMessageText.hpp
@@ -0,0 +1,35 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/InputMessageText.h"
+
+#include "td/telegram/MessageEntity.hpp"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const InputMessageText &input_message_text, StorerT &storer) {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(input_message_text.disable_web_page_preview);
+ STORE_FLAG(input_message_text.clear_draft);
+ END_STORE_FLAGS();
+ store(input_message_text.text, storer);
+}
+
+template <class ParserT>
+void parse(InputMessageText &input_message_text, ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(input_message_text.disable_web_page_preview);
+ PARSE_FLAG(input_message_text.clear_draft);
+ END_PARSE_FLAGS();
+ parse(input_message_text.text, parser);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/JsonValue.cpp b/protocols/Telegram/tdlib/td/td/telegram/JsonValue.cpp
new file mode 100644
index 0000000000..db67dd50b1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/JsonValue.cpp
@@ -0,0 +1,247 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/JsonValue.h"
+
+#include "td/telegram/misc.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/JsonBuilder.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/utf8.h"
+
+#include <utility>
+
+namespace td {
+
+static td_api::object_ptr<td_api::JsonValue> get_json_value_object(const JsonValue &json_value);
+
+static td_api::object_ptr<td_api::jsonObjectMember> get_json_value_member_object(
+ const std::pair<MutableSlice, JsonValue> &json_value_member) {
+ return td_api::make_object<td_api::jsonObjectMember>(json_value_member.first.str(),
+ get_json_value_object(json_value_member.second));
+}
+
+static td_api::object_ptr<td_api::JsonValue> get_json_value_object(const JsonValue &json_value) {
+ switch (json_value.type()) {
+ case JsonValue::Type::Null:
+ return td_api::make_object<td_api::jsonValueNull>();
+ case JsonValue::Type::Boolean:
+ return td_api::make_object<td_api::jsonValueBoolean>(json_value.get_boolean());
+ case JsonValue::Type::Number:
+ return td_api::make_object<td_api::jsonValueNumber>(to_double(json_value.get_number()));
+ case JsonValue::Type::String:
+ return td_api::make_object<td_api::jsonValueString>(json_value.get_string().str());
+ case JsonValue::Type::Array:
+ return td_api::make_object<td_api::jsonValueArray>(transform(json_value.get_array(), get_json_value_object));
+ case JsonValue::Type::Object:
+ return td_api::make_object<td_api::jsonValueObject>(
+ transform(json_value.get_object(), get_json_value_member_object));
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+Result<td_api::object_ptr<td_api::JsonValue>> get_json_value(MutableSlice json) {
+ TRY_RESULT(json_value, json_decode(json));
+ return get_json_value_object(json_value);
+}
+
+Result<telegram_api::object_ptr<telegram_api::JSONValue>> get_input_json_value(MutableSlice json) {
+ TRY_RESULT(json_value, get_json_value(json));
+ return convert_json_value(std::move(json_value));
+}
+
+static td_api::object_ptr<td_api::jsonObjectMember> convert_json_value_member_object(
+ const telegram_api::object_ptr<telegram_api::jsonObjectValue> &json_object_value) {
+ CHECK(json_object_value != nullptr);
+ return td_api::make_object<td_api::jsonObjectMember>(json_object_value->key_,
+ convert_json_value_object(json_object_value->value_));
+}
+
+td_api::object_ptr<td_api::JsonValue> convert_json_value_object(
+ const tl_object_ptr<telegram_api::JSONValue> &json_value) {
+ CHECK(json_value != nullptr);
+ switch (json_value->get_id()) {
+ case telegram_api::jsonNull::ID:
+ return td_api::make_object<td_api::jsonValueNull>();
+ case telegram_api::jsonBool::ID:
+ return td_api::make_object<td_api::jsonValueBoolean>(
+ static_cast<const telegram_api::jsonBool *>(json_value.get())->value_);
+ case telegram_api::jsonNumber::ID:
+ return td_api::make_object<td_api::jsonValueNumber>(
+ static_cast<const telegram_api::jsonNumber *>(json_value.get())->value_);
+ case telegram_api::jsonString::ID:
+ return td_api::make_object<td_api::jsonValueString>(
+ static_cast<const telegram_api::jsonString *>(json_value.get())->value_);
+ case telegram_api::jsonArray::ID:
+ return td_api::make_object<td_api::jsonValueArray>(
+ transform(static_cast<const telegram_api::jsonArray *>(json_value.get())->value_, convert_json_value_object));
+ case telegram_api::jsonObject::ID:
+ return td_api::make_object<td_api::jsonValueObject>(transform(
+ static_cast<const telegram_api::jsonObject *>(json_value.get())->value_, convert_json_value_member_object));
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+static telegram_api::object_ptr<telegram_api::jsonObjectValue> convert_json_value_member(
+ td_api::object_ptr<td_api::jsonObjectMember> &&json_object_member) {
+ CHECK(json_object_member != nullptr);
+ if (!clean_input_string(json_object_member->key_)) {
+ json_object_member->key_.clear();
+ }
+ return telegram_api::make_object<telegram_api::jsonObjectValue>(
+ json_object_member->key_, convert_json_value(std::move(json_object_member->value_)));
+}
+
+tl_object_ptr<telegram_api::JSONValue> convert_json_value(td_api::object_ptr<td_api::JsonValue> &&json_value) {
+ if (json_value == nullptr) {
+ return td_api::make_object<telegram_api::jsonNull>();
+ }
+ switch (json_value->get_id()) {
+ case td_api::jsonValueNull::ID:
+ return telegram_api::make_object<telegram_api::jsonNull>();
+ case td_api::jsonValueBoolean::ID:
+ return telegram_api::make_object<telegram_api::jsonBool>(
+ static_cast<const td_api::jsonValueBoolean *>(json_value.get())->value_);
+ case td_api::jsonValueNumber::ID:
+ return telegram_api::make_object<telegram_api::jsonNumber>(
+ static_cast<const td_api::jsonValueNumber *>(json_value.get())->value_);
+ case td_api::jsonValueString::ID: {
+ auto &str = static_cast<td_api::jsonValueString *>(json_value.get())->value_;
+ if (!clean_input_string(str)) {
+ str.clear();
+ }
+ return telegram_api::make_object<telegram_api::jsonString>(str);
+ }
+ case td_api::jsonValueArray::ID:
+ return telegram_api::make_object<telegram_api::jsonArray>(
+ transform(std::move(static_cast<td_api::jsonValueArray *>(json_value.get())->values_), convert_json_value));
+ case td_api::jsonValueObject::ID:
+ return telegram_api::make_object<telegram_api::jsonObject>(transform(
+ std::move(static_cast<td_api::jsonValueObject *>(json_value.get())->members_), convert_json_value_member));
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+namespace {
+
+class JsonableJsonValue final : public Jsonable {
+ public:
+ explicit JsonableJsonValue(const td_api::JsonValue *json_value) : json_value_(json_value) {
+ }
+ void store(JsonValueScope *scope) const {
+ if (json_value_ == nullptr) {
+ *scope << JsonNull();
+ return;
+ }
+ switch (json_value_->get_id()) {
+ case td_api::jsonValueNull::ID:
+ *scope << JsonNull();
+ break;
+ case td_api::jsonValueBoolean::ID:
+ *scope << JsonBool(static_cast<const td_api::jsonValueBoolean *>(json_value_)->value_);
+ break;
+ case td_api::jsonValueNumber::ID:
+ *scope << static_cast<const td_api::jsonValueNumber *>(json_value_)->value_;
+ break;
+ case td_api::jsonValueString::ID: {
+ auto &str = static_cast<const td_api::jsonValueString *>(json_value_)->value_;
+ if (!check_utf8(str)) {
+ LOG(ERROR) << "Have incorrect UTF-8 string " << str;
+ *scope << "";
+ } else {
+ *scope << str;
+ }
+ break;
+ }
+ case td_api::jsonValueArray::ID: {
+ auto array = scope->enter_array();
+ for (auto &value : static_cast<const td_api::jsonValueArray *>(json_value_)->values_) {
+ array << JsonableJsonValue(value.get());
+ }
+ break;
+ }
+ case td_api::jsonValueObject::ID: {
+ auto object = scope->enter_object();
+ for (auto &member : static_cast<const td_api::jsonValueObject *>(json_value_)->members_) {
+ if (member != nullptr) {
+ if (!check_utf8(member->key_)) {
+ LOG(ERROR) << "Have incorrect UTF-8 object key " << member->key_;
+ } else {
+ object(member->key_, JsonableJsonValue(member->value_.get()));
+ }
+ }
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ private:
+ const td_api::JsonValue *json_value_;
+};
+
+} // namespace
+
+string get_json_string(const td_api::JsonValue *json_value) {
+ return json_encode<string>(JsonableJsonValue(json_value));
+}
+
+bool get_json_value_bool(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name) {
+ CHECK(json_value != nullptr);
+ if (json_value->get_id() == telegram_api::jsonBool::ID) {
+ return static_cast<const telegram_api::jsonBool *>(json_value.get())->value_;
+ }
+ LOG(ERROR) << "Expected Boolean as " << name << ", but found " << to_string(json_value);
+ return false;
+}
+
+int32 get_json_value_int(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name) {
+ CHECK(json_value != nullptr);
+ if (json_value->get_id() == telegram_api::jsonNumber::ID) {
+ return static_cast<int32>(static_cast<const telegram_api::jsonNumber *>(json_value.get())->value_);
+ }
+ LOG(ERROR) << "Expected Integer as " << name << ", but found " << to_string(json_value);
+ return 0;
+}
+
+int64 get_json_value_long(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name) {
+ CHECK(json_value != nullptr);
+ if (json_value->get_id() == telegram_api::jsonString::ID) {
+ return to_integer<int64>(static_cast<const telegram_api::jsonString *>(json_value.get())->value_);
+ }
+ LOG(ERROR) << "Expected Long as " << name << ", but found " << to_string(json_value);
+ return 0;
+}
+
+double get_json_value_double(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name) {
+ CHECK(json_value != nullptr);
+ if (json_value->get_id() == telegram_api::jsonNumber::ID) {
+ return static_cast<const telegram_api::jsonNumber *>(json_value.get())->value_;
+ }
+ LOG(ERROR) << "Expected Double as " << name << ", but found " << to_string(json_value);
+ return 0.0;
+}
+
+string get_json_value_string(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name) {
+ CHECK(json_value != nullptr);
+ if (json_value->get_id() == telegram_api::jsonString::ID) {
+ return std::move(static_cast<telegram_api::jsonString *>(json_value.get())->value_);
+ }
+ LOG(ERROR) << "Expected String as " << name << ", but found " << to_string(json_value);
+ return string();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/JsonValue.h b/protocols/Telegram/tdlib/td/td/telegram/JsonValue.h
new file mode 100644
index 0000000000..a84aa32158
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/JsonValue.h
@@ -0,0 +1,39 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+Result<td_api::object_ptr<td_api::JsonValue>> get_json_value(MutableSlice json);
+
+Result<telegram_api::object_ptr<telegram_api::JSONValue>> get_input_json_value(MutableSlice json);
+
+td_api::object_ptr<td_api::JsonValue> convert_json_value_object(
+ const tl_object_ptr<telegram_api::JSONValue> &json_value);
+
+tl_object_ptr<telegram_api::JSONValue> convert_json_value(td_api::object_ptr<td_api::JsonValue> &&json_value);
+
+string get_json_string(const td_api::JsonValue *json_value);
+
+bool get_json_value_bool(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name);
+
+int32 get_json_value_int(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name);
+
+int64 get_json_value_long(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name);
+
+double get_json_value_double(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name);
+
+string get_json_value_string(telegram_api::object_ptr<telegram_api::JSONValue> &&json_value, Slice name);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/LabeledPricePart.h b/protocols/Telegram/tdlib/td/td/telegram/LabeledPricePart.h
new file mode 100644
index 0000000000..7788d7640f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/LabeledPricePart.h
@@ -0,0 +1,47 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+struct LabeledPricePart {
+ string label;
+ int64 amount = 0;
+
+ LabeledPricePart() = default;
+ LabeledPricePart(string &&label, int64 amount) : label(std::move(label)), amount(amount) {
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ storer.store_string(label);
+ storer.store_binary(amount);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ label = parser.template fetch_string<string>();
+ amount = parser.fetch_long();
+ }
+};
+
+inline bool operator==(const LabeledPricePart &lhs, const LabeledPricePart &rhs) {
+ return lhs.label == rhs.label && lhs.amount == rhs.amount;
+}
+
+inline bool operator!=(const LabeledPricePart &lhs, const LabeledPricePart &rhs) {
+ return !(lhs == rhs);
+}
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, const LabeledPricePart &labeled_price_part) {
+ return string_builder << '[' << labeled_price_part.label << ": " << labeled_price_part.amount << ']';
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/LanguagePackManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/LanguagePackManager.cpp
new file mode 100644
index 0000000000..823ebf0ed7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/LanguagePackManager.cpp
@@ -0,0 +1,1920 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/LanguagePackManager.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/td_api.hpp"
+
+#include "td/db/DbKey.h"
+#include "td/db/SqliteDb.h"
+#include "td/db/SqliteKeyValue.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/ExitGuard.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+
+#include <atomic>
+#include <limits>
+#include <map>
+#include <utility>
+
+namespace td {
+
+struct LanguagePackManager::PluralizedString {
+ string zero_value_;
+ string one_value_;
+ string two_value_;
+ string few_value_;
+ string many_value_;
+ string other_value_;
+
+ PluralizedString(string &&zero_value, string &&one_value, string &&two_value, string &&few_value, string &&many_value,
+ string &&other_value)
+ : zero_value_(std::move(zero_value))
+ , one_value_(std::move(one_value))
+ , two_value_(std::move(two_value))
+ , few_value_(std::move(few_value))
+ , many_value_(std::move(many_value))
+ , other_value_(std::move(other_value)) {
+ }
+};
+
+struct LanguagePackManager::Language {
+ std::mutex mutex_;
+ std::atomic<int32> version_{-1};
+ std::atomic<int32> key_count_{0};
+ std::string base_language_code_;
+ bool is_full_ = false;
+ bool was_loaded_full_ = false;
+ bool has_get_difference_query_ = false;
+ vector<Promise<Unit>> get_difference_queries_;
+ FlatHashMap<string, string> ordinary_strings_;
+ FlatHashMap<string, unique_ptr<PluralizedString>> pluralized_strings_;
+ FlatHashSet<string> deleted_strings_;
+ SqliteKeyValue kv_; // usages must be guarded by database_->mutex_
+};
+
+struct LanguagePackManager::LanguageInfo {
+ string name_;
+ string native_name_;
+ string base_language_code_;
+ string plural_code_;
+ bool is_official_ = false;
+ bool is_rtl_ = false;
+ bool is_beta_ = false;
+ bool is_from_database_ = false;
+ int32 total_string_count_ = 0; // TODO move to LanguagePack and calculate as max(total_string_count)
+ int32 translated_string_count_ = 0;
+ string translation_url_;
+
+ friend bool operator==(const LanguageInfo &lhs, const LanguageInfo &rhs) {
+ return lhs.name_ == rhs.name_ && lhs.native_name_ == rhs.native_name_ &&
+ lhs.base_language_code_ == rhs.base_language_code_ && lhs.plural_code_ == rhs.plural_code_ &&
+ lhs.is_official_ == rhs.is_official_ && lhs.is_rtl_ == rhs.is_rtl_ && lhs.is_beta_ == rhs.is_beta_ &&
+ lhs.total_string_count_ == rhs.total_string_count_ &&
+ lhs.translated_string_count_ == rhs.translated_string_count_ && lhs.translation_url_ == rhs.translation_url_;
+ }
+};
+
+struct LanguagePackManager::LanguagePack {
+ std::mutex mutex_;
+ SqliteKeyValue pack_kv_; // usages must be guarded by database_->mutex_
+ std::map<string, LanguageInfo> custom_language_pack_infos_; // sorted by language_code
+ vector<std::pair<string, LanguageInfo>> server_language_pack_infos_; // sorted by server
+ FlatHashMap<string, unique_ptr<LanguageInfo>> all_server_language_pack_infos_;
+ FlatHashMap<string, unique_ptr<Language>> languages_;
+};
+
+struct LanguagePackManager::LanguageDatabase {
+ std::mutex mutex_;
+ string path_;
+ SqliteDb database_;
+ FlatHashMap<string, unique_ptr<LanguagePack>> language_packs_;
+};
+
+LanguagePackManager::~LanguagePackManager() = default;
+
+bool LanguagePackManager::check_language_pack_name(Slice name) {
+ for (auto c : name) {
+ if (c != '_' && !is_alpha(c)) {
+ return false;
+ }
+ }
+ return name.size() <= 64;
+}
+
+bool LanguagePackManager::check_language_code_name(Slice name) {
+ for (auto c : name) {
+ if (c != '-' && !is_alpha(c) && !is_digit(c)) {
+ return false;
+ }
+ }
+ return name.size() <= 64 && (is_custom_language_code(name) || name.empty() || name.size() >= 2);
+}
+
+bool LanguagePackManager::is_custom_language_code(Slice language_code) {
+ return !language_code.empty() && language_code[0] == 'X';
+}
+
+static Result<SqliteDb> open_database(const string &path) {
+ TRY_RESULT(database, SqliteDb::open_with_key(path, true, DbKey::empty()));
+ TRY_STATUS(database.exec("PRAGMA journal_mode=WAL"));
+ return std::move(database);
+}
+
+static int32 load_database_language_version(SqliteKeyValue *kv) {
+ CHECK(kv != nullptr);
+ if (kv->empty()) {
+ return -1;
+ }
+ string str_version = kv->get("!version");
+ if (str_version.empty()) {
+ return -1;
+ }
+
+ return to_integer<int32>(str_version);
+}
+
+static int32 load_database_language_key_count(SqliteKeyValue *kv) {
+ CHECK(kv != nullptr);
+ if (kv->empty()) {
+ return 0;
+ }
+ string str_key_count = kv->get("!key_count");
+ if (str_key_count.empty()) {
+ // calculate key count once for the database and cache it
+ int key_count = 0;
+ for (auto &str : kv->get_all()) {
+ key_count += str.first[0] != '!' && (str.second[0] == '1' || str.second[0] == '2');
+ }
+ LOG(INFO) << "Set language pack key count in database to " << key_count;
+ kv->set("!key_count", to_string(key_count));
+ return key_count;
+ }
+
+ return to_integer<int32>(str_key_count);
+}
+
+static string load_database_language_base_language_code(SqliteKeyValue *kv) {
+ CHECK(kv != nullptr);
+ if (kv->empty()) {
+ return string();
+ }
+ return kv->get("!base_language_code");
+}
+
+LanguagePackManager::LanguageDatabase *LanguagePackManager::add_language_database(string path) {
+ auto it = language_databases_.find(path);
+ if (it != language_databases_.end()) {
+ return it->second.get();
+ }
+
+ SqliteDb database;
+ if (!path.empty()) {
+ auto r_database = open_database(path);
+ if (r_database.is_error()) {
+ LOG(ERROR) << "Can't open language pack database " << path << ": " << r_database.error();
+ return add_language_database(string());
+ }
+
+ database = r_database.move_as_ok();
+ }
+
+ it = language_databases_.emplace(path, make_unique<LanguageDatabase>()).first;
+ it->second->path_ = std::move(path);
+ it->second->database_ = std::move(database);
+ return it->second.get();
+}
+
+LanguagePackManager::LanguagePackManager(ActorShared<> parent) : parent_(std::move(parent)) {
+ std::lock_guard<std::mutex> database_lock(language_database_mutex_);
+ manager_count_++;
+ language_pack_ = G()->get_option_string("localization_target");
+ language_code_ = G()->get_option_string("language_pack_id");
+ CHECK(check_language_pack_name(language_pack_));
+ CHECK(check_language_code_name(language_code_));
+
+ database_ = add_language_database(G()->get_option_string("language_pack_database_path"));
+ if (!language_pack_.empty() && !language_code_.empty()) {
+ auto language = add_language(database_, language_pack_, language_code_);
+
+ std::lock_guard<std::mutex> language_lock(language->mutex_);
+ base_language_code_ = language->base_language_code_;
+ if (!check_language_code_name(base_language_code_)) {
+ LOG(ERROR) << "Have invalid base language pack ID \"" << base_language_code_ << '"';
+ base_language_code_.clear();
+ }
+ if (!base_language_code_.empty()) {
+ add_language(database_, language_pack_, base_language_code_);
+ }
+
+ LOG(INFO) << "Use localization target \"" << language_pack_ << "\" with language pack \"" << language_code_
+ << "\" based on \"" << base_language_code_ << "\" of version " << language->version_.load()
+ << " with database \"" << database_->path_ << '"';
+ }
+}
+
+void LanguagePackManager::start_up() {
+ if (language_pack_.empty() || language_code_.empty()) {
+ return;
+ }
+
+ auto language = get_language(database_, language_pack_, language_code_);
+ CHECK(language != nullptr);
+ if (language->version_ == -1) {
+ load_empty_language_pack(language_code_);
+ }
+ repair_chosen_language_info();
+
+ if (!base_language_code_.empty()) {
+ auto base_language = get_language(database_, language_pack_, base_language_code_);
+ CHECK(base_language != nullptr);
+ if (base_language->version_ == -1) {
+ load_empty_language_pack(base_language_code_);
+ }
+ }
+
+ on_language_pack_version_changed(false, -1);
+ on_language_pack_version_changed(true, -1);
+}
+
+void LanguagePackManager::tear_down() {
+ if (ExitGuard::is_exited()) {
+ return;
+ }
+ std::lock_guard<std::mutex> lock(language_database_mutex_);
+ manager_count_--;
+ if (manager_count_ == 0) {
+ // can't clear language packs, because they can be accessed later using synchronous requests
+ // LOG(INFO) << "Clear language packs";
+ // language_databases_.clear();
+ }
+}
+
+string LanguagePackManager::get_main_language_code() {
+ if (language_pack_.empty() || language_code_.empty()) {
+ return "en";
+ }
+ if (language_code_.size() == 2) {
+ return language_code_;
+ }
+
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack_);
+ CHECK(pack_it != database_->language_packs_.end());
+
+ LanguageInfo *info = nullptr;
+ LanguagePack *pack = pack_it->second.get();
+ std::lock_guard<std::mutex> languages_lock(pack->mutex_);
+ if (is_custom_language_code(language_code_)) {
+ auto custom_it = pack->custom_language_pack_infos_.find(language_code_);
+ if (custom_it != pack->custom_language_pack_infos_.end()) {
+ info = &custom_it->second;
+ }
+ } else {
+ for (auto &server_info : pack->server_language_pack_infos_) {
+ if (server_info.first == language_code_) {
+ info = &server_info.second;
+ }
+ }
+ }
+
+ if (info == nullptr) {
+ LOG(WARNING) << "Failed to find information about chosen language " << language_code_
+ << ", ensure that valid language pack ID is used";
+ if (!is_custom_language_code(language_code_)) {
+ search_language_info(language_code_, Auto());
+ }
+ } else {
+ if (!info->base_language_code_.empty()) {
+ return info->base_language_code_;
+ }
+ if (!info->plural_code_.empty()) {
+ return info->plural_code_;
+ }
+ }
+ return "en";
+}
+
+vector<string> LanguagePackManager::get_used_language_codes() {
+ if (language_pack_.empty() || language_code_.empty()) {
+ return {};
+ }
+
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack_);
+ CHECK(pack_it != database_->language_packs_.end());
+
+ LanguageInfo *info = nullptr;
+ LanguagePack *pack = pack_it->second.get();
+ std::lock_guard<std::mutex> languages_lock(pack->mutex_);
+ if (is_custom_language_code(language_code_)) {
+ auto custom_it = pack->custom_language_pack_infos_.find(language_code_);
+ if (custom_it != pack->custom_language_pack_infos_.end()) {
+ info = &custom_it->second;
+ }
+ } else {
+ for (auto &server_info : pack->server_language_pack_infos_) {
+ if (server_info.first == language_code_) {
+ info = &server_info.second;
+ }
+ }
+ }
+
+ vector<string> result;
+ if (language_code_.size() == 2) {
+ result.push_back(language_code_);
+ }
+ if (info == nullptr) {
+ LOG(INFO) << "Failed to find information about chosen language " << language_code_
+ << ", ensure that valid language pack ID is used";
+ if (!is_custom_language_code(language_code_)) {
+ search_language_info(language_code_, Auto());
+ }
+ } else {
+ if (!info->base_language_code_.empty()) {
+ result.push_back(info->base_language_code_);
+ }
+ if (!info->plural_code_.empty()) {
+ result.push_back(info->plural_code_);
+ }
+ }
+ return result;
+}
+
+void LanguagePackManager::on_language_pack_changed() {
+ auto new_language_pack = G()->get_option_string("localization_target");
+ if (new_language_pack == language_pack_) {
+ return;
+ }
+
+ language_pack_ = std::move(new_language_pack);
+ CHECK(check_language_pack_name(language_pack_));
+ inc_generation();
+}
+
+void LanguagePackManager::on_language_code_changed() {
+ auto new_language_code = G()->get_option_string("language_pack_id");
+ if (new_language_code == language_code_) {
+ return;
+ }
+
+ language_code_ = std::move(new_language_code);
+ CHECK(check_language_code_name(language_code_));
+ inc_generation();
+}
+
+void LanguagePackManager::on_language_pack_version_changed(bool is_base, int32 new_version) {
+ if (language_pack_.empty() || language_code_.empty()) {
+ return;
+ }
+
+ Language *language = get_language(database_, language_pack_, language_code_);
+ int32 version = language == nullptr ? static_cast<int32>(-1) : language->version_.load();
+ LOG(INFO) << (is_base ? "Base" : "Main") << " language pack vesrion has changed from main " << version << " to "
+ << new_version;
+ if (version == -1) {
+ return load_empty_language_pack(language_code_);
+ }
+
+ if (new_version < 0) {
+ Slice version_key = is_base ? Slice("base_language_pack_version") : Slice("language_pack_version");
+ new_version = narrow_cast<int32>(G()->get_option_integer(version_key, -1));
+ }
+ if (new_version <= 0) {
+ return;
+ }
+
+ string language_code;
+ if (is_base) {
+ language_code = base_language_code_;
+ if (language_code.empty()) {
+ LOG(ERROR) << "Have no base language, but received new version " << new_version;
+ return;
+ }
+ language = get_language(database_, language_pack_, language_code);
+ version = language == nullptr ? static_cast<int32>(-1) : language->version_.load();
+ if (version == -1) {
+ return load_empty_language_pack(language_code);
+ }
+ } else {
+ language_code = language_code_;
+ }
+ if (is_custom_language_code(language_code) || new_version <= version) {
+ return;
+ }
+
+ LOG(INFO) << (is_base ? "Base" : "Main") << " language pack " << language_code << " vesrion has changed to "
+ << new_version;
+ send_language_get_difference_query(language, std::move(language_code), version, Auto());
+}
+
+void LanguagePackManager::send_language_get_difference_query(Language *language, string language_code, int32 version,
+ Promise<Unit> &&promise) {
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ language->get_difference_queries_.push_back(std::move(promise));
+ if (language->has_get_difference_query_) {
+ return;
+ }
+
+ CHECK(language->get_difference_queries_.size() == 1);
+ language->has_get_difference_query_ = true;
+ auto request_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code,
+ from_version = version](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::langpack_getDifference>(std::move(r_query));
+ if (r_result.is_error()) {
+ send_closure(actor_id, &LanguagePackManager::on_failed_get_difference, std::move(language_pack),
+ std::move(language_code), r_result.move_as_error());
+ return;
+ }
+
+ auto result = r_result.move_as_ok();
+ LOG(INFO) << "Receive language pack difference for language pack " << result->lang_code_ << " from version "
+ << result->from_version_ << " with version " << result->version_ << " of size "
+ << result->strings_.size();
+ to_lower_inplace(result->lang_code_);
+ LOG_IF(ERROR, result->lang_code_ != language_code)
+ << "Receive strings for " << result->lang_code_ << " instead of " << language_code;
+ LOG_IF(ERROR, result->from_version_ != from_version)
+ << "Receive strings from " << result->from_version_ << " instead of " << from_version;
+ send_closure(actor_id, &LanguagePackManager::on_get_language_pack_strings, std::move(language_pack),
+ std::move(language_code), result->version_, true, vector<string>(), std::move(result->strings_),
+ Promise<td_api::object_ptr<td_api::languagePackStrings>>());
+ });
+ send_with_promise(G()->net_query_creator().create_unauth(
+ telegram_api::langpack_getDifference(language_pack_, language_code, version)),
+ std::move(request_promise));
+}
+
+void LanguagePackManager::on_language_pack_too_long(string language_code) {
+ if (language_code == language_code_) {
+ return on_language_pack_version_changed(false, std::numeric_limits<int32>::max());
+ }
+ if (language_code == base_language_code_) {
+ return on_language_pack_version_changed(true, std::numeric_limits<int32>::max());
+ }
+ LOG(WARNING) << "Receive languagePackTooLong for language " << language_code << ", but use language "
+ << language_code_ << " with base language " << base_language_code_;
+}
+
+void LanguagePackManager::on_update_language_pack(tl_object_ptr<telegram_api::langPackDifference> difference) {
+ LOG(INFO) << "Receive update language pack difference for language pack " << difference->lang_code_
+ << " from version " << difference->from_version_ << " with version " << difference->version_ << " of size "
+ << difference->strings_.size();
+ to_lower_inplace(difference->lang_code_);
+ if (language_code_.empty()) {
+ LOG(INFO) << "Ignore difference for language pack " << difference->lang_code_
+ << ", because have no used language pack";
+ return;
+ }
+ if (language_pack_.empty()) {
+ LOG(WARNING) << "Ignore difference for language pack " << difference->lang_code_
+ << ", because localization target is not set";
+ return;
+ }
+ if (difference->lang_code_ != language_code_ && difference->lang_code_ != base_language_code_) {
+ LOG(WARNING) << "Ignore difference for language pack " << difference->lang_code_ << ", because using language pack "
+ << language_code_ << " based on " << base_language_code_;
+ return;
+ }
+ if (is_custom_language_code(difference->lang_code_) || difference->lang_code_.empty()) {
+ LOG(ERROR) << "Ignore difference for language pack " << difference->lang_code_;
+ return;
+ }
+
+ Language *language = get_language(database_, language_pack_, difference->lang_code_);
+ int32 version = language == nullptr ? static_cast<int32>(-1) : language->version_.load();
+ if (difference->version_ <= version) {
+ LOG(INFO) << "Skip applying already applied language pack updates";
+ return;
+ }
+ if (version == -1 || version < difference->from_version_) {
+ LOG(INFO) << "Can't apply language pack difference";
+ return on_language_pack_version_changed(difference->lang_code_ != language_code_, difference->version_);
+ }
+
+ on_get_language_pack_strings(language_pack_, std::move(difference->lang_code_), difference->version_, true,
+ vector<string>(), std::move(difference->strings_),
+ Promise<td_api::object_ptr<td_api::languagePackStrings>>());
+}
+
+void LanguagePackManager::inc_generation() {
+ G()->set_option_empty("language_pack_version");
+ G()->set_option_empty("base_language_pack_version");
+
+ if (!language_pack_.empty() && !language_code_.empty()) {
+ LOG(INFO) << "Add main language " << language_code_;
+ CHECK(check_language_code_name(language_code_));
+ auto language = add_language(database_, language_pack_, language_code_);
+ on_language_pack_version_changed(false, std::numeric_limits<int32>::max());
+ repair_chosen_language_info();
+
+ {
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ base_language_code_ = language->base_language_code_;
+ }
+ if (!check_language_code_name(base_language_code_)) {
+ LOG(ERROR) << "Have invalid base language pack ID \"" << base_language_code_ << '"';
+ base_language_code_.clear();
+ }
+ if (!base_language_code_.empty()) {
+ CHECK(base_language_code_ != language_code_);
+ LOG(INFO) << "Add base language " << base_language_code_;
+ add_language(database_, language_pack_, base_language_code_);
+ on_language_pack_version_changed(true, std::numeric_limits<int32>::max());
+ }
+ }
+ LOG(INFO) << "Finished to apply new language pack";
+}
+
+LanguagePackManager::Language *LanguagePackManager::get_language(LanguageDatabase *database,
+ const string &language_pack,
+ const string &language_code) {
+ std::unique_lock<std::mutex> lock(database->mutex_);
+ auto it = database->language_packs_.find(language_pack);
+ if (it == database->language_packs_.end()) {
+ return nullptr;
+ }
+ LanguagePack *pack = it->second.get();
+ lock.unlock();
+ return get_language(pack, language_code);
+}
+
+LanguagePackManager::Language *LanguagePackManager::get_language(LanguagePack *language_pack,
+ const string &language_code) {
+ CHECK(language_pack != nullptr);
+ std::lock_guard<std::mutex> lock(language_pack->mutex_);
+ auto it = language_pack->languages_.find(language_code);
+ if (it == language_pack->languages_.end()) {
+ return nullptr;
+ }
+ return it->second.get();
+}
+
+static string get_database_table_name(const string &language_pack, const string &language_code) {
+ return PSTRING() << "\"kv_" << language_pack << '_' << language_code << '"';
+}
+
+LanguagePackManager::Language *LanguagePackManager::add_language(LanguageDatabase *database,
+ const string &language_pack,
+ const string &language_code) {
+ std::lock_guard<std::mutex> packs_lock(database->mutex_);
+ auto pack_it = database->language_packs_.find(language_pack);
+ if (pack_it == database->language_packs_.end()) {
+ auto pack = make_unique<LanguagePack>();
+ if (!database->database_.empty()) {
+ pack->pack_kv_.init_with_connection(database->database_.clone(), get_database_table_name(language_pack, "0"))
+ .ensure();
+ bool need_drop_server = false;
+ for (auto &lang : pack->pack_kv_.get_all()) {
+ auto as_bool = [](Slice data) {
+ if (data == "true") {
+ return true;
+ }
+ if (data != "false") {
+ LOG(ERROR) << "Have invalid boolean value \"" << data << "\" in the database";
+ }
+ return false;
+ };
+
+ if (lang.first == "!server") {
+ // legacy info format, drop cache
+ need_drop_server = true;
+ continue;
+ }
+ if (lang.first == "!server2") {
+ auto all_infos = full_split(lang.second, '\x00');
+ if (all_infos.size() % 11 == 0) {
+ for (size_t i = 0; i < all_infos.size(); i += 11) {
+ if (all_infos[i].empty()) {
+ LOG(ERROR) << "Have empty info about a language pack";
+ continue;
+ }
+ LanguageInfo info;
+ info.name_ = std::move(all_infos[i + 1]);
+ info.native_name_ = std::move(all_infos[i + 2]);
+ info.base_language_code_ = std::move(all_infos[i + 3]);
+ info.plural_code_ = std::move(all_infos[i + 4]);
+ info.is_official_ = as_bool(all_infos[i + 5]);
+ info.is_rtl_ = as_bool(all_infos[i + 6]);
+ info.is_beta_ = as_bool(all_infos[i + 7]);
+ info.is_from_database_ = true;
+ info.total_string_count_ = to_integer<int32>(all_infos[i + 8]);
+ info.translated_string_count_ = to_integer<int32>(all_infos[i + 9]);
+ info.translation_url_ = std::move(all_infos[i + 10]);
+ pack->all_server_language_pack_infos_.emplace(all_infos[i], td::make_unique<LanguageInfo>(info));
+ pack->server_language_pack_infos_.emplace_back(std::move(all_infos[i]), std::move(info));
+ }
+ } else {
+ LOG(ERROR) << "Have wrong language pack info \"" << lang.second << "\" in the database";
+ }
+ continue;
+ }
+
+ auto all_infos = full_split(lang.second, '\x00');
+ if (all_infos.size() < 2) {
+ LOG(ERROR) << "Have wrong custom language pack info \"" << lang.second << '"';
+ continue;
+ }
+ auto &info = pack->custom_language_pack_infos_[lang.first];
+ info.name_ = std::move(all_infos[0]);
+ info.native_name_ = std::move(all_infos[1]);
+ if (all_infos.size() > 2) {
+ CHECK(all_infos.size() == 10);
+ info.base_language_code_ = std::move(all_infos[2]);
+ info.plural_code_ = std::move(all_infos[3]);
+ info.is_official_ = as_bool(all_infos[4]);
+ info.is_rtl_ = as_bool(all_infos[5]);
+ info.is_beta_ = as_bool(all_infos[6]);
+ info.total_string_count_ = to_integer<int32>(all_infos[7]);
+ info.translated_string_count_ = to_integer<int32>(all_infos[8]);
+ info.translation_url_ = std::move(all_infos[9]);
+ }
+ info.is_from_database_ = true;
+ }
+ if (need_drop_server) {
+ LOG(INFO) << "Drop old server language pack info cache";
+ pack->pack_kv_.erase("!server");
+ }
+ }
+ pack_it = database->language_packs_.emplace(language_pack, std::move(pack)).first;
+ }
+ LanguagePack *pack = pack_it->second.get();
+
+ std::lock_guard<std::mutex> languages_lock(pack->mutex_);
+ auto code_it = pack->languages_.find(language_code);
+ if (code_it == pack->languages_.end()) {
+ auto language = make_unique<Language>();
+ if (!database->database_.empty()) {
+ language->kv_
+ .init_with_connection(database->database_.clone(), get_database_table_name(language_pack, language_code))
+ .ensure();
+ language->version_ = load_database_language_version(&language->kv_);
+ language->key_count_ = load_database_language_key_count(&language->kv_);
+ language->base_language_code_ = load_database_language_base_language_code(&language->kv_);
+ LOG(INFO) << "Loaded language " << language_code << " with version " << language->version_.load()
+ << ", key count " << language->key_count_.load() << " and base language "
+ << language->base_language_code_;
+ }
+ code_it = pack->languages_.emplace(language_code, std::move(language)).first;
+ }
+ return code_it->second.get();
+}
+
+bool LanguagePackManager::language_has_string_unsafe(const Language *language, const string &key) {
+ return language->ordinary_strings_.count(key) != 0 || language->pluralized_strings_.count(key) != 0 ||
+ language->deleted_strings_.count(key) != 0;
+}
+
+bool LanguagePackManager::language_has_strings(Language *language, const vector<string> &keys) {
+ if (language == nullptr) {
+ return false;
+ }
+
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ if (language->is_full_) {
+ return true;
+ }
+ if (keys.empty()) {
+ return false; // language is already checked to be not full
+ }
+ for (auto &key : keys) {
+ if (!language_has_string_unsafe(language, key)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void LanguagePackManager::load_language_string_unsafe(Language *language, const string &key, const string &value) {
+ CHECK(is_valid_key(key));
+ if (value[0] == '1') {
+ language->ordinary_strings_.emplace(key, value.substr(1));
+ return;
+ }
+
+ if (value[0] == '2') {
+ auto all = full_split(Slice(value).substr(1), '\x00');
+ if (all.size() == 6) {
+ language->pluralized_strings_.emplace(
+ key, td::make_unique<PluralizedString>(all[0].str(), all[1].str(), all[2].str(), all[3].str(), all[4].str(),
+ all[5].str()));
+ return;
+ }
+ }
+
+ LOG_IF(ERROR, !value.empty() && value != "3") << "Have invalid value \"" << value << '"';
+ if (!language->is_full_) {
+ language->deleted_strings_.insert(key);
+ }
+}
+
+bool LanguagePackManager::load_language_strings(LanguageDatabase *database, Language *language,
+ const vector<string> &keys) {
+ if (language == nullptr) {
+ return false;
+ }
+
+ std::lock_guard<std::mutex> database_lock(database->mutex_);
+ std::lock_guard<std::mutex> language_lock(language->mutex_);
+ if (language->is_full_) {
+ LOG(DEBUG) << "The language pack is already full in memory";
+ return true;
+ }
+ if (language->kv_.empty()) {
+ LOG(DEBUG) << "The language pack has no database";
+ return false;
+ }
+ LOG(DEBUG) << "Begin to load a language pack from database";
+ if (keys.empty()) {
+ if (language->version_ == -1 && language->was_loaded_full_) {
+ LOG(DEBUG) << "The language pack has already been loaded";
+ return false;
+ }
+
+ auto all_strings = language->kv_.get_all();
+ for (auto &str : all_strings) {
+ if (str.first[0] == '!') {
+ continue;
+ }
+
+ if (!language_has_string_unsafe(language, str.first)) {
+ LOG(DEBUG) << "Load string with key " << str.first << " from database";
+ load_language_string_unsafe(language, str.first, str.second);
+ }
+ }
+ language->was_loaded_full_ = true;
+
+ if (language->version_ == -1) {
+ return false;
+ }
+
+ language->is_full_ = true;
+ language->deleted_strings_.clear();
+ return true;
+ }
+
+ bool have_all = true;
+ for (auto &key : keys) {
+ if (!language_has_string_unsafe(language, key)) {
+ auto value = language->kv_.get(key);
+ if (value.empty()) {
+ if (language->version_ == -1) {
+ LOG(DEBUG) << "Have no string with key " << key << " in the database";
+ have_all = false;
+ continue;
+ }
+
+ // have full language in the database, so this string is just deleted
+ }
+ LOG(DEBUG) << "Load string with key " << key << " from database";
+ load_language_string_unsafe(language, key, value);
+ }
+ }
+ return have_all;
+}
+
+td_api::object_ptr<td_api::LanguagePackStringValue> LanguagePackManager::get_language_pack_string_value_object(
+ const string &value) {
+ return td_api::make_object<td_api::languagePackStringValueOrdinary>(value);
+}
+
+td_api::object_ptr<td_api::LanguagePackStringValue> LanguagePackManager::get_language_pack_string_value_object(
+ const PluralizedString &value) {
+ return td_api::make_object<td_api::languagePackStringValuePluralized>(
+ value.zero_value_, value.one_value_, value.two_value_, value.few_value_, value.many_value_, value.other_value_);
+}
+
+td_api::object_ptr<td_api::LanguagePackStringValue> LanguagePackManager::get_language_pack_string_value_object() {
+ return td_api::make_object<td_api::languagePackStringValueDeleted>();
+}
+
+td_api::object_ptr<td_api::languagePackString> LanguagePackManager::get_language_pack_string_object(
+ const string &key, const string &value) {
+ return td_api::make_object<td_api::languagePackString>(key, get_language_pack_string_value_object(value));
+}
+
+td_api::object_ptr<td_api::languagePackString> LanguagePackManager::get_language_pack_string_object(
+ const string &key, const PluralizedString &value) {
+ return td_api::make_object<td_api::languagePackString>(key, get_language_pack_string_value_object(value));
+}
+
+td_api::object_ptr<td_api::languagePackString> LanguagePackManager::get_language_pack_string_object(const string &key) {
+ return td_api::make_object<td_api::languagePackString>(key, get_language_pack_string_value_object());
+}
+
+td_api::object_ptr<td_api::LanguagePackStringValue> LanguagePackManager::get_language_pack_string_value_object(
+ const Language *language, const string &key) {
+ CHECK(language != nullptr);
+ auto ordinary_it = language->ordinary_strings_.find(key);
+ if (ordinary_it != language->ordinary_strings_.end()) {
+ return get_language_pack_string_value_object(ordinary_it->second);
+ }
+ auto pluralized_it = language->pluralized_strings_.find(key);
+ if (pluralized_it != language->pluralized_strings_.end()) {
+ return get_language_pack_string_value_object(*pluralized_it->second);
+ }
+ LOG_IF(ERROR, !language->is_full_ && language->deleted_strings_.count(key) == 0) << "Have no string for key " << key;
+ return get_language_pack_string_value_object();
+}
+
+td_api::object_ptr<td_api::languagePackString> LanguagePackManager::get_language_pack_string_object(
+ const Language *language, const string &key) {
+ return td_api::make_object<td_api::languagePackString>(key, get_language_pack_string_value_object(language, key));
+}
+
+td_api::object_ptr<td_api::languagePackStrings> LanguagePackManager::get_language_pack_strings_object(
+ Language *language, const vector<string> &keys) {
+ CHECK(language != nullptr);
+
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ vector<td_api::object_ptr<td_api::languagePackString>> strings;
+ if (keys.empty()) {
+ for (auto &str : language->ordinary_strings_) {
+ strings.push_back(get_language_pack_string_object(str.first, str.second));
+ }
+ for (auto &str : language->pluralized_strings_) {
+ strings.push_back(get_language_pack_string_object(str.first, *str.second));
+ }
+ } else {
+ for (auto &key : keys) {
+ strings.push_back(get_language_pack_string_object(language, key));
+ }
+ }
+
+ return td_api::make_object<td_api::languagePackStrings>(std::move(strings));
+}
+
+void LanguagePackManager::get_languages(bool only_local,
+ Promise<td_api::object_ptr<td_api::localizationTargetInfo>> promise) {
+ if (language_pack_.empty()) {
+ return promise.set_error(Status::Error(400, "Option \"localization_target\" needs to be set first"));
+ }
+
+ if (only_local) {
+ return on_get_languages(vector<tl_object_ptr<telegram_api::langPackLanguage>>(), language_pack_, true,
+ std::move(promise));
+ }
+
+ auto request_promise = PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_,
+ promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::langpack_getLanguages>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+
+ send_closure(actor_id, &LanguagePackManager::on_get_languages, r_result.move_as_ok(), std::move(language_pack),
+ false, std::move(promise));
+ });
+ send_with_promise(G()->net_query_creator().create_unauth(telegram_api::langpack_getLanguages(language_pack_)),
+ std::move(request_promise));
+}
+
+void LanguagePackManager::search_language_info(string language_code,
+ Promise<td_api::object_ptr<td_api::languagePackInfo>> promise) {
+ if (language_pack_.empty()) {
+ return promise.set_error(Status::Error(400, "Option \"localization_target\" needs to be set first"));
+ }
+
+ auto request_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code,
+ promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::langpack_getLanguage>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+
+ send_closure(actor_id, &LanguagePackManager::on_get_language, r_result.move_as_ok(), std::move(language_pack),
+ std::move(language_code), std::move(promise));
+ });
+ send_with_promise(
+ G()->net_query_creator().create_unauth(telegram_api::langpack_getLanguage(language_pack_, language_code)),
+ std::move(request_promise));
+}
+
+void LanguagePackManager::repair_chosen_language_info() {
+ CHECK(!language_pack_.empty() && !language_code_.empty());
+ if (is_custom_language_code(language_code_)) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack_);
+ CHECK(pack_it != database_->language_packs_.end());
+
+ LanguagePack *pack = pack_it->second.get();
+ std::lock_guard<std::mutex> languages_lock(pack->mutex_);
+ for (auto &server_info : pack->server_language_pack_infos_) {
+ if (server_info.first == language_code_) {
+ return;
+ }
+ }
+
+ LOG(INFO) << "Repair info about language " << language_code_;
+ search_language_info(language_code_, Auto());
+}
+
+td_api::object_ptr<td_api::languagePackInfo> LanguagePackManager::get_language_pack_info_object(
+ const string &language_code, const LanguageInfo &info) {
+ return td_api::make_object<td_api::languagePackInfo>(language_code, info.base_language_code_, info.name_,
+ info.native_name_, info.plural_code_, info.is_official_,
+ info.is_rtl_, info.is_beta_, false, info.total_string_count_,
+ info.translated_string_count_, 0, info.translation_url_);
+}
+
+string LanguagePackManager::get_language_info_string(const LanguageInfo &info) {
+ return PSTRING() << info.name_ << '\x00' << info.native_name_ << '\x00' << info.base_language_code_ << '\x00'
+ << info.plural_code_ << '\x00' << info.is_official_ << '\x00' << info.is_rtl_ << '\x00'
+ << info.is_beta_ << '\x00' << info.total_string_count_ << '\x00' << info.translated_string_count_
+ << '\x00' << info.translation_url_;
+}
+
+void LanguagePackManager::on_get_language_info(const string &language_pack,
+ td_api::languagePackInfo *language_pack_info) {
+ CHECK(language_pack_info != nullptr);
+ auto language = add_language(database_, language_pack, language_pack_info->id_);
+ language_pack_info->local_string_count_ = language->key_count_;
+ SqliteKeyValue *kv = nullptr;
+ bool was_updated_base_language_code = false;
+ {
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ if (language_pack_info->base_language_pack_id_ != language->base_language_code_) {
+ language->base_language_code_ = language_pack_info->base_language_pack_id_;
+ if (language_pack == language_pack_ && language_pack_info->id_ == language_code_) {
+ base_language_code_ = language->base_language_code_;
+ was_updated_base_language_code = true;
+ }
+ if (!language->kv_.empty()) {
+ kv = &language->kv_;
+ }
+ }
+ }
+ if (was_updated_base_language_code) {
+ G()->set_option_empty("base_language_pack_version");
+ if (!base_language_code_.empty()) {
+ add_language(database_, language_pack_, base_language_code_);
+ on_language_pack_version_changed(true, std::numeric_limits<int32>::max());
+ }
+ }
+ if (kv != nullptr) {
+ std::lock_guard<std::mutex> lock(database_->mutex_);
+ kv->set("!base_language_code", language_pack_info->base_language_pack_id_);
+ }
+}
+
+void LanguagePackManager::save_server_language_pack_infos(LanguagePack *pack) {
+ // mutexes are locked by the caller
+ if (pack->pack_kv_.empty()) {
+ return;
+ }
+
+ LOG(INFO) << "Save changes server language pack infos";
+ vector<string> all_strings;
+ all_strings.reserve(2 * pack->server_language_pack_infos_.size());
+ for (auto &info : pack->server_language_pack_infos_) {
+ all_strings.push_back(info.first);
+ all_strings.push_back(get_language_info_string(info.second));
+ }
+
+ pack->pack_kv_.set("!server2", implode(all_strings, '\x00'));
+}
+
+void LanguagePackManager::on_get_languages(vector<tl_object_ptr<telegram_api::langPackLanguage>> languages,
+ string language_pack, bool only_local,
+ Promise<td_api::object_ptr<td_api::localizationTargetInfo>> promise) {
+ auto results = td_api::make_object<td_api::localizationTargetInfo>();
+ FlatHashSet<string> added_languages;
+
+ auto add_language_info = [&results, &added_languages](const string &language_code, const LanguageInfo &info,
+ bool is_installed) {
+ if (!language_code.empty() && added_languages.insert(language_code).second) {
+ results->language_packs_.push_back(get_language_pack_info_object(language_code, info));
+ results->language_packs_.back()->is_installed_ = is_installed;
+ }
+ };
+
+ {
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack);
+ if (pack_it != database_->language_packs_.end()) {
+ LanguagePack *pack = pack_it->second.get();
+ std::lock_guard<std::mutex> pack_lock(pack->mutex_);
+ for (auto &info : pack->custom_language_pack_infos_) {
+ add_language_info(info.first, info.second, true);
+ }
+ if (only_local) {
+ for (auto &info : pack->server_language_pack_infos_) {
+ add_language_info(info.first, info.second, false);
+ }
+ }
+ }
+ }
+
+ vector<std::pair<string, LanguageInfo>> all_server_infos;
+ for (auto &language : languages) {
+ auto r_info = get_language_info(language.get());
+ if (r_info.is_error()) {
+ continue;
+ }
+
+ add_language_info(language->lang_code_, r_info.ok(), false);
+ all_server_infos.emplace_back(std::move(language->lang_code_), r_info.move_as_ok());
+ }
+
+ for (auto &language_pack_info : results->language_packs_) {
+ on_get_language_info(language_pack, language_pack_info.get());
+ }
+
+ if (!only_local) {
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack);
+ if (pack_it != database_->language_packs_.end()) {
+ LanguagePack *pack = pack_it->second.get();
+ std::lock_guard<std::mutex> pack_lock(pack->mutex_);
+ if (pack->server_language_pack_infos_ != all_server_infos) {
+ for (auto &info : all_server_infos) {
+ pack->all_server_language_pack_infos_[info.first] = td::make_unique<LanguageInfo>(info.second);
+ }
+ pack->server_language_pack_infos_ = std::move(all_server_infos);
+
+ save_server_language_pack_infos(pack);
+ }
+ }
+ }
+ promise.set_value(std::move(results));
+}
+
+void LanguagePackManager::on_get_language(tl_object_ptr<telegram_api::langPackLanguage> lang_pack_language,
+ string language_pack, string language_code,
+ Promise<td_api::object_ptr<td_api::languagePackInfo>> promise) {
+ CHECK(lang_pack_language != nullptr);
+ auto r_info = get_language_info(lang_pack_language.get());
+ if (r_info.is_error()) {
+ return promise.set_error(r_info.move_as_error());
+ }
+
+ auto result = get_language_pack_info_object(lang_pack_language->lang_code_, r_info.ok());
+
+ on_get_language_info(language_pack, result.get());
+
+ // updating languages cache
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack);
+ if (pack_it != database_->language_packs_.end()) {
+ LanguagePack *pack = pack_it->second.get();
+ std::lock_guard<std::mutex> pack_lock(pack->mutex_);
+
+ result->is_installed_ = pack->custom_language_pack_infos_.count(lang_pack_language->lang_code_) != 0 ||
+ pack->custom_language_pack_infos_.count(language_code) != 0;
+
+ bool is_changed = false;
+ for (auto &info : pack->server_language_pack_infos_) {
+ if (info.first == lang_pack_language->lang_code_ || info.first == language_code) {
+ if (!(info.second == r_info.ok())) {
+ LOG(INFO) << "Language pack " << info.first << " was changed";
+ is_changed = true;
+ info.second = r_info.ok();
+ }
+ }
+ }
+ pack->all_server_language_pack_infos_[lang_pack_language->lang_code_] =
+ td::make_unique<LanguageInfo>(r_info.move_as_ok());
+
+ if (is_changed) {
+ save_server_language_pack_infos(pack);
+ }
+ } else {
+ LOG(ERROR) << "Failed to find localization target " << language_pack;
+ }
+
+ promise.set_value(std::move(result));
+}
+
+void LanguagePackManager::get_language_pack_strings(string language_code, vector<string> keys,
+ Promise<td_api::object_ptr<td_api::languagePackStrings>> promise) {
+ if (!check_language_code_name(language_code) || language_code.empty()) {
+ return promise.set_error(Status::Error(400, "Language pack ID is invalid"));
+ }
+ if (language_pack_.empty()) {
+ return promise.set_error(Status::Error(400, "Option \"localization_target\" needs to be set first"));
+ }
+ for (auto &key : keys) {
+ if (!is_valid_key(key)) {
+ return promise.set_error(Status::Error(400, "Invalid key name"));
+ }
+ }
+
+ Language *language = add_language(database_, language_pack_, language_code);
+ if (language_has_strings(language, keys)) {
+ return promise.set_value(get_language_pack_strings_object(language, keys));
+ }
+ if (load_language_strings(database_, language, keys)) {
+ return promise.set_value(get_language_pack_strings_object(language, keys));
+ }
+
+ if (is_custom_language_code(language_code)) {
+ return promise.set_error(Status::Error(400, "Custom language pack not found"));
+ }
+
+ if (keys.empty()) {
+ auto &queries = get_all_language_pack_strings_queries_[language_pack_][language_code].queries_;
+ queries.push_back(std::move(promise));
+ if (queries.size() != 1) {
+ // send request only once
+ return;
+ }
+
+ auto result_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code](
+ Result<td_api::object_ptr<td_api::languagePackStrings>> r_strings) mutable {
+ send_closure(actor_id, &LanguagePackManager::on_get_all_language_pack_strings, std::move(language_pack),
+ std::move(language_code), std::move(r_strings));
+ });
+ auto request_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code,
+ promise = std::move(result_promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::langpack_getLangPack>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+
+ auto result = r_result.move_as_ok();
+ to_lower_inplace(result->lang_code_);
+ LOG(INFO) << "Receive language pack " << result->lang_code_ << " from version " << result->from_version_
+ << " with version " << result->version_ << " of size " << result->strings_.size();
+ LOG_IF(ERROR, result->lang_code_ != language_code)
+ << "Receive strings for " << result->lang_code_ << " instead of " << language_code;
+ LOG_IF(ERROR, result->from_version_ != 0) << "Receive language pack from version " << result->from_version_;
+ send_closure(actor_id, &LanguagePackManager::on_get_language_pack_strings, std::move(language_pack),
+ std::move(language_code), result->version_, false, vector<string>(), std::move(result->strings_),
+ std::move(promise));
+ });
+ send_with_promise(
+ G()->net_query_creator().create_unauth(telegram_api::langpack_getLangPack(language_pack_, language_code)),
+ std::move(request_promise));
+ } else {
+ auto request_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), language_pack = language_pack_, language_code, keys,
+ promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::langpack_getStrings>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+
+ send_closure(actor_id, &LanguagePackManager::on_get_language_pack_strings, std::move(language_pack),
+ std::move(language_code), -1, false, std::move(keys), r_result.move_as_ok(), std::move(promise));
+ });
+ send_with_promise(G()->net_query_creator().create_unauth(
+ telegram_api::langpack_getStrings(language_pack_, language_code, std::move(keys))),
+ std::move(request_promise));
+ }
+}
+
+void LanguagePackManager::load_empty_language_pack(const string &language_code) {
+ if (is_custom_language_code(language_code)) {
+ return;
+ }
+ get_language_pack_strings(language_code, vector<string>(), Auto());
+}
+
+void LanguagePackManager::synchronize_language_pack(string language_code, Promise<Unit> promise) {
+ if (!check_language_code_name(language_code) || language_code.empty()) {
+ return promise.set_error(Status::Error(400, "Language pack ID is invalid"));
+ }
+ if (language_pack_.empty()) {
+ return promise.set_error(Status::Error(400, "Option \"localization_target\" needs to be set first"));
+ }
+ if (is_custom_language_code(language_code)) {
+ return promise.set_value(Unit());
+ }
+
+ Language *language = add_language(database_, language_pack_, language_code);
+ load_language_strings(database_, language, vector<string>());
+
+ int32 version = language->version_.load();
+ if (version == -1) {
+ version = 0;
+ }
+ send_language_get_difference_query(language, std::move(language_code), version, std::move(promise));
+}
+
+static td_api::object_ptr<td_api::LanguagePackStringValue> copy_language_pack_string_value(
+ const td_api::LanguagePackStringValue *value) {
+ switch (value->get_id()) {
+ case td_api::languagePackStringValueOrdinary::ID: {
+ auto old_value = static_cast<const td_api::languagePackStringValueOrdinary *>(value);
+ return make_tl_object<td_api::languagePackStringValueOrdinary>(old_value->value_);
+ }
+ case td_api::languagePackStringValuePluralized::ID: {
+ auto old_value = static_cast<const td_api::languagePackStringValuePluralized *>(value);
+ return make_tl_object<td_api::languagePackStringValuePluralized>(
+ std::move(old_value->zero_value_), std::move(old_value->one_value_), std::move(old_value->two_value_),
+ std::move(old_value->few_value_), std::move(old_value->many_value_), std::move(old_value->other_value_));
+ }
+ case td_api::languagePackStringValueDeleted::ID:
+ return make_tl_object<td_api::languagePackStringValueDeleted>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+void LanguagePackManager::on_get_all_language_pack_strings(
+ string language_pack, string language_code, Result<td_api::object_ptr<td_api::languagePackStrings>> r_strings) {
+ auto &queries = get_all_language_pack_strings_queries_[language_pack][language_code].queries_;
+ auto promises = std::move(queries);
+ CHECK(!promises.empty());
+ auto it = get_all_language_pack_strings_queries_.find(language_pack);
+ it->second.erase(language_code);
+ if (it->second.empty()) {
+ get_all_language_pack_strings_queries_.erase(it);
+ }
+
+ if (r_strings.is_error()) {
+ fail_promises(promises, r_strings.move_as_error());
+ return;
+ }
+
+ auto strings = r_strings.move_as_ok();
+ size_t left_non_empty_promise_count = 0;
+ for (auto &promise : promises) {
+ if (promise) {
+ left_non_empty_promise_count++;
+ }
+ }
+ for (auto &promise : promises) {
+ if (promise) {
+ if (left_non_empty_promise_count == 1) {
+ LOG(DEBUG) << "Set last non-empty promise";
+ promise.set_value(std::move(strings));
+ } else {
+ LOG(DEBUG) << "Set non-empty promise";
+ vector<td_api::object_ptr<td_api::languagePackString>> strings_copy;
+ for (auto &result : strings->strings_) {
+ CHECK(result != nullptr);
+ strings_copy.push_back(td_api::make_object<td_api::languagePackString>(
+ result->key_, copy_language_pack_string_value(result->value_.get())));
+ }
+ promise.set_value(td_api::make_object<td_api::languagePackStrings>(std::move(strings_copy)));
+ }
+ left_non_empty_promise_count--;
+ } else {
+ LOG(DEBUG) << "Set empty promise";
+ promise.set_value(nullptr);
+ }
+ }
+ CHECK(left_non_empty_promise_count == 0);
+}
+
+td_api::object_ptr<td_api::Object> LanguagePackManager::get_language_pack_string(const string &database_path,
+ const string &language_pack,
+ const string &language_code,
+ const string &key) {
+ if (!check_language_pack_name(language_pack) || language_pack.empty()) {
+ return td_api::make_object<td_api::error>(400, "Localization target is invalid");
+ }
+ if (!check_language_code_name(language_code) || language_code.empty()) {
+ return td_api::make_object<td_api::error>(400, "Language pack ID is invalid");
+ }
+ if (!is_valid_key(key)) {
+ return td_api::make_object<td_api::error>(400, "Key is invalid");
+ }
+
+ std::unique_lock<std::mutex> language_databases_lock(language_database_mutex_);
+ LanguageDatabase *database = add_language_database(database_path);
+ CHECK(database != nullptr);
+ language_databases_lock.unlock();
+
+ Language *language = add_language(database, language_pack, language_code);
+ vector<string> keys{key};
+ if (language_has_strings(language, keys)) {
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ return get_language_pack_string_value_object(language, key);
+ }
+ if (load_language_strings(database, language, keys)) {
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ return get_language_pack_string_value_object(language, key);
+ }
+ return td_api::make_object<td_api::error>(404, "Not Found");
+}
+
+bool LanguagePackManager::is_valid_key(Slice key) {
+ for (auto c : key) {
+ if (!is_alnum(c) && c != '_' && c != '.' && c != '-') {
+ return false;
+ }
+ }
+ return !key.empty();
+}
+
+void LanguagePackManager::save_strings_to_database(SqliteKeyValue *kv, int32 new_version, bool new_is_full,
+ int32 new_key_count, vector<std::pair<string, string>> &&strings) {
+ LOG(DEBUG) << "Save to database a language pack with new version " << new_version << " and " << strings.size()
+ << " new strings";
+ if (new_version == -1 && strings.empty()) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(database_->mutex_);
+ CHECK(kv != nullptr);
+ if (kv->empty()) {
+ LOG(DEBUG) << "There is no associated database key-value";
+ return;
+ }
+ auto old_version = load_database_language_version(kv);
+ if (old_version > new_version || (old_version == new_version && strings.empty())) {
+ LOG(DEBUG) << "Language pack version doesn't increased from " << old_version;
+ return;
+ }
+
+ kv->begin_write_transaction().ensure();
+ for (const auto &str : strings) {
+ if (!is_valid_key(str.first)) {
+ LOG(ERROR) << "Have invalid key \"" << str.first << '"';
+ continue;
+ }
+
+ if (new_is_full && str.second == "3") {
+ kv->erase(str.first);
+ } else {
+ kv->set(str.first, str.second);
+ }
+ LOG(DEBUG) << "Save language pack string with key " << str.first << " to database";
+ }
+ if (old_version != new_version) {
+ LOG(DEBUG) << "Set language pack version in database to " << new_version;
+ kv->set("!version", to_string(new_version));
+ }
+ if (new_key_count != -1) {
+ LOG(DEBUG) << "Set language pack key count in database to " << new_key_count;
+ kv->set("!key_count", to_string(new_key_count));
+ }
+ kv->commit_transaction().ensure();
+}
+
+void LanguagePackManager::on_get_language_pack_strings(
+ string language_pack, string language_code, int32 version, bool is_diff, vector<string> &&keys,
+ vector<tl_object_ptr<telegram_api::LangPackString>> results,
+ Promise<td_api::object_ptr<td_api::languagePackStrings>> promise) {
+ Language *language = get_language(database_, language_pack, language_code);
+ bool is_version_changed = false;
+ int32 new_database_version = -1;
+ int32 new_key_count = -1;
+ bool new_is_full = false;
+ vector<std::pair<string, string>> database_strings;
+ if (language == nullptr || language->version_ < version || !keys.empty()) {
+ if (language == nullptr) {
+ language = add_language(database_, language_pack, language_code);
+ CHECK(language != nullptr);
+ }
+ load_language_strings(database_, language, keys);
+
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ int32 key_count_delta = 0;
+ if (language->version_ < version || !keys.empty()) {
+ vector<td_api::object_ptr<td_api::languagePackString>> strings;
+ if (language->version_ < version) {
+ LOG(INFO) << "Set language pack " << language_code << " version to " << version;
+ language->version_ = version;
+ new_database_version = version;
+ is_version_changed = true;
+ }
+
+ for (auto &result : results) {
+ CHECK(result != nullptr);
+ switch (result->get_id()) {
+ case telegram_api::langPackString::ID: {
+ auto str = telegram_api::move_object_as<telegram_api::langPackString>(result);
+ if (!is_valid_key(str->key_)) {
+ LOG(ERROR) << "Receive invalid key \"" << str->key_ << '"';
+ break;
+ }
+ auto it = language->ordinary_strings_.find(str->key_);
+ if (it == language->ordinary_strings_.end()) {
+ key_count_delta++;
+ it = language->ordinary_strings_.emplace(str->key_, std::move(str->value_)).first;
+ } else {
+ it->second = std::move(str->value_);
+ }
+ key_count_delta -= static_cast<int32>(language->pluralized_strings_.erase(str->key_));
+ language->deleted_strings_.erase(str->key_);
+ if (is_diff) {
+ strings.push_back(get_language_pack_string_object(it->first, it->second));
+ }
+ database_strings.emplace_back(std::move(str->key_), PSTRING() << '1' << it->second);
+ break;
+ }
+ case telegram_api::langPackStringPluralized::ID: {
+ auto str = telegram_api::move_object_as<telegram_api::langPackStringPluralized>(result);
+ if (!is_valid_key(str->key_)) {
+ LOG(ERROR) << "Receive invalid key \"" << str->key_ << '"';
+ break;
+ }
+ auto value = td::make_unique<PluralizedString>(std::move(str->zero_value_), std::move(str->one_value_),
+ std::move(str->two_value_), std::move(str->few_value_),
+ std::move(str->many_value_), std::move(str->other_value_));
+ auto it = language->pluralized_strings_.find(str->key_);
+ if (it == language->pluralized_strings_.end()) {
+ key_count_delta++;
+ it = language->pluralized_strings_.emplace(str->key_, std::move(value)).first;
+ } else {
+ it->second = std::move(value);
+ }
+ key_count_delta -= static_cast<int32>(language->ordinary_strings_.erase(str->key_));
+ language->deleted_strings_.erase(str->key_);
+ if (is_diff) {
+ strings.push_back(get_language_pack_string_object(it->first, *it->second));
+ }
+ database_strings.emplace_back(
+ std::move(str->key_), PSTRING()
+ << '2' << it->second->zero_value_ << '\x00' << it->second->one_value_
+ << '\x00' << it->second->two_value_ << '\x00' << it->second->few_value_
+ << '\x00' << it->second->many_value_ << '\x00' << it->second->other_value_);
+ break;
+ }
+ case telegram_api::langPackStringDeleted::ID: {
+ auto str = telegram_api::move_object_as<telegram_api::langPackStringDeleted>(result);
+ if (!is_valid_key(str->key_)) {
+ LOG(ERROR) << "Receive invalid key \"" << str->key_ << '"';
+ break;
+ }
+ key_count_delta -= static_cast<int32>(language->ordinary_strings_.erase(str->key_));
+ key_count_delta -= static_cast<int32>(language->pluralized_strings_.erase(str->key_));
+ language->deleted_strings_.insert(str->key_);
+ if (is_diff) {
+ strings.push_back(get_language_pack_string_object(str->key_));
+ }
+ database_strings.emplace_back(std::move(str->key_), "3");
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+ if (!language->is_full_) {
+ for (const auto &key : keys) {
+ if (!language_has_string_unsafe(language, key)) {
+ LOG(ERROR) << "Doesn't receive key " << key << " from server";
+ language->deleted_strings_.insert(key);
+ if (is_diff) {
+ strings.push_back(get_language_pack_string_object(key));
+ }
+ database_strings.emplace_back(key, "3");
+ }
+ }
+ }
+
+ if (key_count_delta != 0) {
+ new_key_count = language->key_count_ + key_count_delta;
+ language->key_count_ = new_key_count;
+ }
+
+ if (is_diff) {
+ send_closure(
+ G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateLanguagePackStrings>(language_pack, language_code, std::move(strings)));
+ }
+
+ if (keys.empty() && !is_diff) {
+ CHECK(new_database_version >= 0);
+ language->is_full_ = true;
+ language->deleted_strings_.clear();
+ }
+ new_is_full = language->is_full_;
+ }
+ }
+ if (is_custom_language_code(language_code) && new_database_version == -1) {
+ new_database_version = 1;
+ }
+
+ save_strings_to_database(&language->kv_, new_database_version, new_is_full, new_key_count,
+ std::move(database_strings));
+
+ if (is_diff) {
+ CHECK(language != nullptr);
+ vector<Promise<Unit>> get_difference_queries;
+ {
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ if (language->has_get_difference_query_) {
+ language->has_get_difference_query_ = false;
+ get_difference_queries = std::move(language->get_difference_queries_);
+ reset_to_empty(language->get_difference_queries_);
+ is_version_changed = true;
+ }
+ }
+ for (auto &query : get_difference_queries) {
+ query.set_value(Unit());
+ }
+ }
+ if (is_version_changed && language_pack == language_pack_ &&
+ (language_code == language_code_ || language_code == base_language_code_)) {
+ send_closure_later(actor_id(this), &LanguagePackManager::on_language_pack_version_changed,
+ language_code != language_code_, -1);
+ }
+
+ if (promise) {
+ promise.set_value(get_language_pack_strings_object(language, keys));
+ }
+}
+
+void LanguagePackManager::on_failed_get_difference(string language_pack, string language_code, Status error) {
+ Language *language = get_language(database_, language_pack, language_code);
+ CHECK(language != nullptr);
+ vector<Promise<Unit>> get_difference_queries;
+ {
+ std::lock_guard<std::mutex> lock(language->mutex_);
+ if (language->has_get_difference_query_) {
+ language->has_get_difference_query_ = false;
+ if (language_pack == language_pack_ &&
+ (language_code == language_code_ || language_code == base_language_code_)) {
+ send_closure_later(actor_id(this), &LanguagePackManager::on_language_pack_version_changed,
+ language_code != language_code_, -1);
+ }
+ get_difference_queries = std::move(language->get_difference_queries_);
+ reset_to_empty(language->get_difference_queries_);
+ }
+ }
+ fail_promises(get_difference_queries, std::move(error));
+}
+
+void LanguagePackManager::add_custom_server_language(string language_code, Promise<Unit> &&promise) {
+ if (language_pack_.empty()) {
+ return promise.set_error(Status::Error(400, "Option \"localization_target\" needs to be set first"));
+ }
+ if (!check_language_code_name(language_code)) {
+ return promise.set_error(Status::Error(400, "Language pack ID must contain only letters, digits and hyphen"));
+ }
+ if (is_custom_language_code(language_code)) {
+ return promise.set_error(
+ Status::Error(400, "Custom local language pack can't be added through addCustomServerLanguagePack"));
+ }
+
+ if (get_language(database_, language_pack_, language_code) == nullptr) {
+ return promise.set_error(Status::Error(400, "Language pack not found"));
+ }
+
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack_);
+ CHECK(pack_it != database_->language_packs_.end());
+ LanguagePack *pack = pack_it->second.get();
+ std::lock_guard<std::mutex> pack_lock(pack->mutex_);
+ auto it = pack->all_server_language_pack_infos_.find(language_code);
+ if (it == pack->all_server_language_pack_infos_.end()) {
+ return promise.set_error(Status::Error(400, "Language pack info not found"));
+ }
+ auto &info = pack->custom_language_pack_infos_[language_code];
+ info = *it->second;
+ if (!pack->pack_kv_.empty()) {
+ pack->pack_kv_.set(language_code, get_language_info_string(info));
+ }
+
+ promise.set_value(Unit());
+}
+
+Result<tl_object_ptr<telegram_api::LangPackString>> LanguagePackManager::convert_to_telegram_api(
+ tl_object_ptr<td_api::languagePackString> &&str) {
+ if (str == nullptr) {
+ return Status::Error(400, "Language pack strings must be non-empty");
+ }
+
+ string key = std::move(str->key_);
+ if (!is_valid_key(key)) {
+ return Status::Error(400, "Key is invalid");
+ }
+
+ if (str->value_ == nullptr) {
+ return make_tl_object<telegram_api::langPackStringDeleted>(std::move(key));
+ }
+ switch (str->value_->get_id()) {
+ case td_api::languagePackStringValueOrdinary::ID: {
+ auto value = static_cast<td_api::languagePackStringValueOrdinary *>(str->value_.get());
+ if (!clean_input_string(value->value_)) {
+ return Status::Error(400, "Language pack string value must be encoded in UTF-8");
+ }
+ return make_tl_object<telegram_api::langPackString>(std::move(key), std::move(value->value_));
+ }
+ case td_api::languagePackStringValuePluralized::ID: {
+ auto value = static_cast<td_api::languagePackStringValuePluralized *>(str->value_.get());
+ if (!clean_input_string(value->zero_value_) || !clean_input_string(value->one_value_) ||
+ !clean_input_string(value->two_value_) || !clean_input_string(value->few_value_) ||
+ !clean_input_string(value->many_value_) || !clean_input_string(value->other_value_)) {
+ return Status::Error(400, "Language pack string value must be encoded in UTF-8");
+ }
+ return make_tl_object<telegram_api::langPackStringPluralized>(
+ 31, std::move(key), std::move(value->zero_value_), std::move(value->one_value_), std::move(value->two_value_),
+ std::move(value->few_value_), std::move(value->many_value_), std::move(value->other_value_));
+ }
+ case td_api::languagePackStringValueDeleted::ID:
+ // there is no reason to save deleted strings in a custom language pack to database
+ return make_tl_object<telegram_api::langPackStringDeleted>(std::move(key));
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+Result<LanguagePackManager::LanguageInfo> LanguagePackManager::get_language_info(
+ telegram_api::langPackLanguage *language) {
+ if (!check_language_code_name(language->lang_code_) || language->lang_code_.empty()) {
+ LOG(ERROR) << "Receive unsupported language pack ID " << language->lang_code_ << " from server";
+ return Status::Error(500, "Unsupported language pack ID");
+ }
+ if (is_custom_language_code(language->lang_code_)) {
+ LOG(ERROR) << "Receive custom language pack ID \"" << language->lang_code_ << "\" from server";
+ return Status::Error(500, "Unallowed custom language pack ID");
+ }
+ to_lower_inplace(language->lang_code_);
+
+ LanguageInfo info;
+ info.name_ = std::move(language->name_);
+ info.native_name_ = std::move(language->native_name_);
+ info.base_language_code_ = std::move(language->base_lang_code_);
+ info.plural_code_ = std::move(language->plural_code_);
+ info.is_official_ = language->official_;
+ info.is_rtl_ = language->rtl_;
+ info.is_beta_ = language->beta_;
+ info.is_from_database_ = false;
+ info.total_string_count_ = language->strings_count_;
+ info.translated_string_count_ = language->translated_count_;
+ info.translation_url_ = language->translations_url_;
+
+ if (!check_language_code_name(info.base_language_code_)) {
+ LOG(ERROR) << "Have invalid base language pack ID \"" << info.base_language_code_ << '"';
+ info.base_language_code_.clear();
+ }
+ if (is_custom_language_code(info.base_language_code_)) {
+ LOG(ERROR) << "Receive custom base language pack ID \"" << info.base_language_code_ << "\" from server";
+ info.base_language_code_.clear();
+ }
+ if (info.base_language_code_ == language->lang_code_) {
+ LOG(ERROR) << "Receive language pack \"" << info.base_language_code_ << "\"based on self";
+ info.base_language_code_.clear();
+ }
+ return std::move(info);
+}
+
+Result<LanguagePackManager::LanguageInfo> LanguagePackManager::get_language_info(
+ td_api::languagePackInfo *language_pack_info) {
+ if (language_pack_info == nullptr) {
+ return Status::Error(400, "Language pack info must be non-empty");
+ }
+
+ if (!clean_input_string(language_pack_info->id_)) {
+ return Status::Error(400, "Language pack ID must be encoded in UTF-8");
+ }
+ if (!clean_input_string(language_pack_info->base_language_pack_id_)) {
+ return Status::Error(400, "Base language pack ID must be encoded in UTF-8");
+ }
+ if (!clean_input_string(language_pack_info->name_)) {
+ return Status::Error(400, "Language pack name must be encoded in UTF-8");
+ }
+ if (!clean_input_string(language_pack_info->native_name_)) {
+ return Status::Error(400, "Language pack native name must be encoded in UTF-8");
+ }
+ if (!clean_input_string(language_pack_info->plural_code_)) {
+ return Status::Error(400, "Language pack plural code must be encoded in UTF-8");
+ }
+ if (!clean_input_string(language_pack_info->translation_url_)) {
+ return Status::Error(400, "Language pack translation url must be encoded in UTF-8");
+ }
+ if (language_pack_info->total_string_count_ < 0) {
+ language_pack_info->total_string_count_ = 0;
+ }
+ if (language_pack_info->translated_string_count_ < 0) {
+ language_pack_info->translated_string_count_ = 0;
+ }
+ if (!check_language_code_name(language_pack_info->id_)) {
+ return Status::Error(400, "Language pack ID must contain only letters, digits and hyphen");
+ }
+
+ if (is_custom_language_code(language_pack_info->id_)) {
+ language_pack_info->base_language_pack_id_.clear();
+ language_pack_info->is_official_ = false;
+ language_pack_info->is_rtl_ = false;
+ language_pack_info->is_beta_ = false;
+ language_pack_info->translation_url_.clear();
+ }
+
+ LanguageInfo info;
+ info.name_ = std::move(language_pack_info->name_);
+ info.native_name_ = std::move(language_pack_info->native_name_);
+ info.base_language_code_ = std::move(language_pack_info->base_language_pack_id_);
+ info.plural_code_ = std::move(language_pack_info->plural_code_);
+ info.is_official_ = language_pack_info->is_official_;
+ info.is_rtl_ = language_pack_info->is_rtl_;
+ info.is_beta_ = language_pack_info->is_beta_;
+ info.is_from_database_ = true;
+ info.total_string_count_ = language_pack_info->total_string_count_;
+ info.translated_string_count_ = language_pack_info->translated_string_count_;
+ info.translation_url_ = std::move(language_pack_info->translation_url_);
+
+ return std::move(info);
+}
+
+void LanguagePackManager::set_custom_language(td_api::object_ptr<td_api::languagePackInfo> &&language_pack_info,
+ vector<tl_object_ptr<td_api::languagePackString>> strings,
+ Promise<Unit> &&promise) {
+ if (language_pack_.empty()) {
+ return promise.set_error(Status::Error(400, "Option \"localization_target\" needs to be set first"));
+ }
+
+ auto r_info = get_language_info(language_pack_info.get());
+ if (r_info.is_error()) {
+ return promise.set_error(r_info.move_as_error());
+ }
+ auto language_code = std::move(language_pack_info->id_);
+ if (!is_custom_language_code(language_code)) {
+ return promise.set_error(Status::Error(400, "Custom language pack ID must begin with 'X'"));
+ }
+
+ vector<tl_object_ptr<telegram_api::LangPackString>> server_strings;
+ for (auto &str : strings) {
+ auto r_result = convert_to_telegram_api(std::move(str));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+ server_strings.push_back(r_result.move_as_ok());
+ }
+
+ // TODO atomic replace
+ do_delete_language(language_code).ensure();
+ on_get_language_pack_strings(language_pack_, language_code, 1, false, vector<string>(), std::move(server_strings),
+ Auto());
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack_);
+ CHECK(pack_it != database_->language_packs_.end());
+ LanguagePack *pack = pack_it->second.get();
+ std::lock_guard<std::mutex> pack_lock(pack->mutex_);
+ auto &info = pack->custom_language_pack_infos_[language_code];
+ info = r_info.move_as_ok();
+ if (!pack->pack_kv_.empty()) {
+ pack->pack_kv_.set(language_code, get_language_info_string(info));
+ }
+
+ promise.set_value(Unit());
+}
+
+void LanguagePackManager::edit_custom_language_info(td_api::object_ptr<td_api::languagePackInfo> &&language_pack_info,
+ Promise<Unit> &&promise) {
+ if (language_pack_.empty()) {
+ return promise.set_error(Status::Error(400, "Option \"localization_target\" needs to be set first"));
+ }
+
+ auto r_info = get_language_info(language_pack_info.get());
+ if (r_info.is_error()) {
+ return promise.set_error(r_info.move_as_error());
+ }
+ auto language_code = std::move(language_pack_info->id_);
+ if (!is_custom_language_code(language_code)) {
+ return promise.set_error(Status::Error(400, "Custom language pack ID must begin with 'X'"));
+ }
+
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack_);
+ CHECK(pack_it != database_->language_packs_.end());
+ LanguagePack *pack = pack_it->second.get();
+ std::lock_guard<std::mutex> pack_lock(pack->mutex_);
+ auto language_info_it = pack->custom_language_pack_infos_.find(language_code);
+ if (language_info_it == pack->custom_language_pack_infos_.end()) {
+ return promise.set_error(Status::Error(400, "Custom language pack is not found"));
+ }
+ auto &info = language_info_it->second;
+ info = r_info.move_as_ok();
+ if (!pack->pack_kv_.empty()) {
+ pack->pack_kv_.set(language_code, get_language_info_string(info));
+ }
+
+ promise.set_value(Unit());
+}
+
+void LanguagePackManager::set_custom_language_string(string language_code,
+ tl_object_ptr<td_api::languagePackString> str,
+ Promise<Unit> &&promise) {
+ if (language_pack_.empty()) {
+ return promise.set_error(Status::Error(400, "Option \"localization_target\" needs to be set first"));
+ }
+ if (!check_language_code_name(language_code)) {
+ return promise.set_error(Status::Error(400, "Language pack ID must contain only letters, digits and hyphen"));
+ }
+ if (!is_custom_language_code(language_code)) {
+ return promise.set_error(Status::Error(400, "Custom language pack ID must begin with 'X'"));
+ }
+
+ if (get_language(database_, language_pack_, language_code) == nullptr) {
+ return promise.set_error(Status::Error(400, "Custom language pack not found"));
+ }
+ if (str == nullptr) {
+ return promise.set_error(Status::Error(400, "Language pack strings must not be null"));
+ }
+
+ vector<string> keys{str->key_};
+
+ auto r_str = convert_to_telegram_api(std::move(str));
+ if (r_str.is_error()) {
+ return promise.set_error(r_str.move_as_error());
+ }
+
+ vector<tl_object_ptr<telegram_api::LangPackString>> server_strings;
+ server_strings.push_back(r_str.move_as_ok());
+
+ on_get_language_pack_strings(language_pack_, language_code, 1, true, std::move(keys), std::move(server_strings),
+ Auto());
+ promise.set_value(Unit());
+}
+
+void LanguagePackManager::delete_language(string language_code, Promise<Unit> &&promise) {
+ if (language_pack_.empty()) {
+ return promise.set_error(Status::Error(400, "Option \"localization_target\" needs to be set first"));
+ }
+ if (!check_language_code_name(language_code)) {
+ return promise.set_error(Status::Error(400, "Language pack ID is invalid"));
+ }
+ if (language_code.empty()) {
+ return promise.set_error(Status::Error(400, "Language pack ID is empty"));
+ }
+ if (language_code_ == language_code || base_language_code_ == language_code) {
+ return promise.set_error(Status::Error(400, "Currently used language pack can't be deleted"));
+ }
+
+ auto status = do_delete_language(language_code);
+ if (status.is_error()) {
+ promise.set_error(std::move(status));
+ } else {
+ promise.set_value(Unit());
+ }
+}
+
+Status LanguagePackManager::do_delete_language(const string &language_code) {
+ add_language(database_, language_pack_, language_code);
+
+ std::lock_guard<std::mutex> packs_lock(database_->mutex_);
+ auto pack_it = database_->language_packs_.find(language_pack_);
+ CHECK(pack_it != database_->language_packs_.end());
+ LanguagePack *pack = pack_it->second.get();
+
+ std::lock_guard<std::mutex> languages_lock(pack->mutex_);
+ auto code_it = pack->languages_.find(language_code);
+ CHECK(code_it != pack->languages_.end());
+ auto language = code_it->second.get();
+ if (language->has_get_difference_query_) {
+ return Status::Error(400, "Language pack can't be deleted now, try again later");
+ }
+ if (!language->kv_.empty()) {
+ language->kv_.drop().ignore();
+ CHECK(language->kv_.empty());
+ CHECK(!database_->database_.empty());
+ language->kv_
+ .init_with_connection(database_->database_.clone(), get_database_table_name(language_pack_, language_code))
+ .ensure();
+ }
+ std::lock_guard<std::mutex> language_lock(language->mutex_);
+ language->version_ = -1;
+ language->key_count_ = load_database_language_key_count(&language->kv_);
+ language->is_full_ = false;
+ language->ordinary_strings_.clear();
+ language->pluralized_strings_.clear();
+ language->deleted_strings_.clear();
+
+ if (!pack->pack_kv_.empty()) {
+ pack->pack_kv_.erase(language_code);
+ }
+ pack->custom_language_pack_infos_.erase(language_code);
+
+ return Status::OK();
+}
+
+void LanguagePackManager::on_result(NetQueryPtr query) {
+ auto token = get_link_token();
+ container_.extract(token).set_value(std::move(query));
+}
+
+void LanguagePackManager::send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> promise) {
+ auto id = container_.create(std::move(promise));
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, id));
+}
+
+void LanguagePackManager::hangup() {
+ container_.for_each(
+ [](auto id, Promise<NetQueryPtr> &promise) { promise.set_error(Global::request_aborted_error()); });
+ stop();
+}
+
+int32 LanguagePackManager::manager_count_ = 0;
+std::mutex LanguagePackManager::language_database_mutex_;
+std::unordered_map<string, unique_ptr<LanguagePackManager::LanguageDatabase>, Hash<string>>
+ LanguagePackManager::language_databases_;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/LanguagePackManager.h b/protocols/Telegram/tdlib/td/td/telegram/LanguagePackManager.h
new file mode 100644
index 0000000000..d8872f24a6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/LanguagePackManager.h
@@ -0,0 +1,201 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Container.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+#include <mutex>
+#include <unordered_map>
+#include <utility>
+
+namespace td {
+
+class SqliteKeyValue;
+
+class LanguagePackManager final : public NetQueryCallback {
+ public:
+ explicit LanguagePackManager(ActorShared<> parent);
+ LanguagePackManager(const LanguagePackManager &) = delete;
+ LanguagePackManager &operator=(const LanguagePackManager &) = delete;
+ LanguagePackManager(LanguagePackManager &&) = delete;
+ LanguagePackManager &operator=(LanguagePackManager &&) = delete;
+ ~LanguagePackManager() final;
+
+ static bool check_language_pack_name(Slice name);
+
+ static bool check_language_code_name(Slice name);
+
+ static bool is_custom_language_code(Slice language_code);
+
+ string get_main_language_code();
+
+ vector<string> get_used_language_codes();
+
+ void on_language_pack_changed();
+
+ void on_language_code_changed();
+
+ void on_language_pack_version_changed(bool is_base, int32 new_version);
+
+ void on_language_pack_too_long(string language_code);
+
+ void get_languages(bool only_local, Promise<td_api::object_ptr<td_api::localizationTargetInfo>> promise);
+
+ void search_language_info(string language_code, Promise<td_api::object_ptr<td_api::languagePackInfo>> promise);
+
+ void get_language_pack_strings(string language_code, vector<string> keys,
+ Promise<td_api::object_ptr<td_api::languagePackStrings>> promise);
+
+ static td_api::object_ptr<td_api::Object> get_language_pack_string(const string &database_path,
+ const string &language_pack,
+ const string &language_code, const string &key);
+
+ void synchronize_language_pack(string language_code, Promise<Unit> promise);
+
+ void on_update_language_pack(tl_object_ptr<telegram_api::langPackDifference> difference);
+
+ void add_custom_server_language(string language_code, Promise<Unit> &&promise);
+
+ void set_custom_language(td_api::object_ptr<td_api::languagePackInfo> &&language_pack_info,
+ vector<tl_object_ptr<td_api::languagePackString>> strings, Promise<Unit> &&promise);
+
+ void edit_custom_language_info(td_api::object_ptr<td_api::languagePackInfo> &&language_pack_info,
+ Promise<Unit> &&promise);
+
+ void set_custom_language_string(string language_code, tl_object_ptr<td_api::languagePackString> str,
+ Promise<Unit> &&promise);
+
+ void delete_language(string language_code, Promise<Unit> &&promise);
+
+ private:
+ struct PluralizedString;
+ struct Language;
+ struct LanguageInfo;
+ struct LanguagePack;
+ struct LanguageDatabase;
+
+ ActorShared<> parent_;
+
+ string language_pack_;
+ string language_code_;
+ string base_language_code_;
+ LanguageDatabase *database_ = nullptr;
+
+ struct PendingQueries {
+ vector<Promise<td_api::object_ptr<td_api::languagePackStrings>>> queries_;
+ };
+
+ FlatHashMap<string, FlatHashMap<string, PendingQueries>> get_all_language_pack_strings_queries_;
+
+ static int32 manager_count_;
+
+ static std::mutex language_database_mutex_;
+ static std::unordered_map<string, unique_ptr<LanguageDatabase>, Hash<string>> language_databases_;
+
+ static LanguageDatabase *add_language_database(string path);
+
+ static Language *get_language(LanguageDatabase *database, const string &language_pack, const string &language_code);
+ static Language *get_language(LanguagePack *language_pack, const string &language_code);
+
+ static Language *add_language(LanguageDatabase *database, const string &language_pack, const string &language_code);
+
+ static bool language_has_string_unsafe(const Language *language, const string &key);
+ static bool language_has_strings(Language *language, const vector<string> &keys);
+
+ static void load_language_string_unsafe(Language *language, const string &key, const string &value);
+ static bool load_language_strings(LanguageDatabase *database, Language *language, const vector<string> &keys);
+
+ static td_api::object_ptr<td_api::LanguagePackStringValue> get_language_pack_string_value_object(const string &value);
+ static td_api::object_ptr<td_api::LanguagePackStringValue> get_language_pack_string_value_object(
+ const PluralizedString &value);
+ static td_api::object_ptr<td_api::LanguagePackStringValue> get_language_pack_string_value_object();
+
+ static td_api::object_ptr<td_api::languagePackString> get_language_pack_string_object(const string &key,
+ const string &value);
+ static td_api::object_ptr<td_api::languagePackString> get_language_pack_string_object(const string &key,
+ const PluralizedString &value);
+ static td_api::object_ptr<td_api::languagePackString> get_language_pack_string_object(const string &key);
+
+ static td_api::object_ptr<td_api::LanguagePackStringValue> get_language_pack_string_value_object(
+ const Language *language, const string &key);
+
+ static td_api::object_ptr<td_api::languagePackString> get_language_pack_string_object(const Language *language,
+ const string &key);
+
+ static td_api::object_ptr<td_api::languagePackStrings> get_language_pack_strings_object(Language *language,
+ const vector<string> &keys);
+
+ static td_api::object_ptr<td_api::languagePackInfo> get_language_pack_info_object(const string &language_code,
+ const LanguageInfo &info);
+
+ static Result<LanguageInfo> get_language_info(telegram_api::langPackLanguage *language);
+
+ static Result<LanguageInfo> get_language_info(td_api::languagePackInfo *language_pack_info);
+
+ static string get_language_info_string(const LanguageInfo &info);
+
+ static Result<tl_object_ptr<telegram_api::LangPackString>> convert_to_telegram_api(
+ tl_object_ptr<td_api::languagePackString> &&str);
+
+ void inc_generation();
+
+ void repair_chosen_language_info();
+
+ static bool is_valid_key(Slice key);
+
+ void save_strings_to_database(SqliteKeyValue *kv, int32 new_version, bool new_is_full, int32 new_key_count,
+ vector<std::pair<string, string>> &&strings);
+
+ void load_empty_language_pack(const string &language_code);
+
+ void on_get_language_pack_strings(string language_pack, string language_code, int32 version, bool is_diff,
+ vector<string> &&keys, vector<tl_object_ptr<telegram_api::LangPackString>> results,
+ Promise<td_api::object_ptr<td_api::languagePackStrings>> promise);
+
+ void on_get_all_language_pack_strings(string language_pack, string language_code,
+ Result<td_api::object_ptr<td_api::languagePackStrings>> r_strings);
+
+ void send_language_get_difference_query(Language *language, string language_code, int32 version,
+ Promise<Unit> &&promise);
+
+ void on_failed_get_difference(string language_pack, string language_code, Status error);
+
+ void on_get_language_info(const string &language_pack, td_api::languagePackInfo *language_pack_info);
+
+ static void save_server_language_pack_infos(LanguagePack *pack);
+
+ void on_get_languages(vector<tl_object_ptr<telegram_api::langPackLanguage>> languages, string language_pack,
+ bool only_local, Promise<td_api::object_ptr<td_api::localizationTargetInfo>> promise);
+
+ void on_get_language(tl_object_ptr<telegram_api::langPackLanguage> lang_pack_language, string language_pack,
+ string language_code, Promise<td_api::object_ptr<td_api::languagePackInfo>> promise);
+
+ Status do_delete_language(const string &language_code);
+
+ void on_result(NetQueryPtr query) final;
+
+ void start_up() final;
+ void hangup() final;
+ void tear_down() final;
+
+ Container<Promise<NetQueryPtr>> container_;
+ void send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> promise);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/LinkManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/LinkManager.cpp
new file mode 100644
index 0000000000..0b7e76d735
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/LinkManager.cpp
@@ -0,0 +1,1958 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/LinkManager.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/BackgroundType.h"
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChannelType.h"
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/mtproto/ProxySecret.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/HttpUrl.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/Time.h"
+#include "td/utils/utf8.h"
+
+#include <tuple>
+
+namespace td {
+
+static bool is_valid_start_parameter(Slice start_parameter) {
+ return start_parameter.size() <= 64 && is_base64url_characters(start_parameter);
+}
+
+static bool is_valid_phone_number(Slice phone_number) {
+ if (phone_number.empty() || phone_number.size() > 32) {
+ return false;
+ }
+ for (auto c : phone_number) {
+ if (!is_digit(c)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static string get_url_query_hash(bool is_tg, const HttpUrlQuery &url_query) {
+ const auto &path = url_query.path_;
+ if (is_tg) {
+ if (path.size() == 1 && path[0] == "join" && !url_query.get_arg("invite").empty()) {
+ // join?invite=abcdef
+ return url_query.get_arg("invite").str();
+ }
+ } else {
+ if (path.size() >= 2 && path[0] == "joinchat" && !path[1].empty()) {
+ // /joinchat/<link>
+ return path[1];
+ }
+ if (!path.empty() && path[0].size() >= 2 && (path[0][0] == ' ' || path[0][0] == '+')) {
+ if (is_valid_phone_number(Slice(path[0]).substr(1))) {
+ return string();
+ }
+ // /+<link>
+ return path[0].substr(1);
+ }
+ }
+ return string();
+}
+
+static AdministratorRights get_administrator_rights(Slice rights, bool for_channel) {
+ bool can_manage_dialog = false;
+ bool can_change_info = false;
+ bool can_post_messages = false;
+ bool can_edit_messages = false;
+ bool can_delete_messages = false;
+ bool can_invite_users = false;
+ bool can_restrict_members = false;
+ bool can_pin_messages = false;
+ bool can_manage_topics = false;
+ bool can_promote_members = false;
+ bool can_manage_calls = false;
+ bool is_anonymous = false;
+ for (auto right : full_split(rights, ' ')) {
+ if (right == "change_info") {
+ can_change_info = true;
+ } else if (right == "post_messages") {
+ can_post_messages = true;
+ } else if (right == "edit_messages") {
+ can_edit_messages = true;
+ } else if (right == "delete_messages") {
+ can_delete_messages = true;
+ } else if (right == "restrict_members") {
+ can_restrict_members = true;
+ } else if (right == "invite_users") {
+ can_invite_users = true;
+ } else if (right == "pin_messages") {
+ can_pin_messages = true;
+ } else if (right == "manage_topics") {
+ can_manage_topics = true;
+ } else if (right == "promote_members") {
+ can_promote_members = true;
+ } else if (right == "manage_video_chats") {
+ can_manage_calls = true;
+ } else if (right == "anonymous") {
+ is_anonymous = true;
+ } else if (right == "manage_chat") {
+ can_manage_dialog = true;
+ }
+ }
+ return AdministratorRights(is_anonymous, can_manage_dialog, can_change_info, can_post_messages, can_edit_messages,
+ can_delete_messages, can_invite_users, can_restrict_members, can_pin_messages,
+ can_manage_topics, can_promote_members, can_manage_calls,
+ for_channel ? ChannelType::Broadcast : ChannelType::Megagroup);
+}
+
+td_api::object_ptr<td_api::targetChatChosen> get_target_chat_chosen(Slice chat_types) {
+ bool allow_users = false;
+ bool allow_bots = false;
+ bool allow_groups = false;
+ bool allow_channels = false;
+ for (auto chat_type : full_split(chat_types, ' ')) {
+ if (chat_type == "users") {
+ allow_users = true;
+ } else if (chat_type == "bots") {
+ allow_bots = true;
+ } else if (chat_type == "groups") {
+ allow_groups = true;
+ } else if (chat_type == "channels") {
+ allow_channels = true;
+ }
+ }
+ if (!allow_users && !allow_bots && !allow_groups && !allow_channels) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::targetChatChosen>(allow_users, allow_bots, allow_groups, allow_channels);
+}
+
+class LinkManager::InternalLinkActiveSessions final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeActiveSessions>();
+ }
+};
+
+class LinkManager::InternalLinkAttachMenuBot final : public InternalLink {
+ td_api::object_ptr<td_api::targetChatChosen> allowed_chat_types_;
+ unique_ptr<InternalLink> dialog_link_;
+ string bot_username_;
+ string url_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ td_api::object_ptr<td_api::TargetChat> target_chat;
+ if (dialog_link_ != nullptr) {
+ target_chat = td_api::make_object<td_api::targetChatInternalLink>(dialog_link_->get_internal_link_type_object());
+ } else if (allowed_chat_types_ != nullptr) {
+ target_chat = td_api::make_object<td_api::targetChatChosen>(
+ allowed_chat_types_->allow_user_chats_, allowed_chat_types_->allow_bot_chats_,
+ allowed_chat_types_->allow_group_chats_, allowed_chat_types_->allow_channel_chats_);
+ } else {
+ target_chat = td_api::make_object<td_api::targetChatCurrent>();
+ }
+ return td_api::make_object<td_api::internalLinkTypeAttachmentMenuBot>(std::move(target_chat), bot_username_, url_);
+ }
+
+ public:
+ InternalLinkAttachMenuBot(td_api::object_ptr<td_api::targetChatChosen> allowed_chat_types,
+ unique_ptr<InternalLink> dialog_link, string bot_username, Slice start_parameter)
+ : allowed_chat_types_(std::move(allowed_chat_types))
+ , dialog_link_(std::move(dialog_link))
+ , bot_username_(std::move(bot_username)) {
+ if (!start_parameter.empty()) {
+ url_ = PSTRING() << "start://" << start_parameter;
+ }
+ }
+};
+
+class LinkManager::InternalLinkAuthenticationCode final : public InternalLink {
+ string code_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeAuthenticationCode>(code_);
+ }
+
+ public:
+ explicit InternalLinkAuthenticationCode(string code) : code_(std::move(code)) {
+ }
+};
+
+class LinkManager::InternalLinkBackground final : public InternalLink {
+ string background_name_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeBackground>(background_name_);
+ }
+
+ public:
+ explicit InternalLinkBackground(string background_name) : background_name_(std::move(background_name)) {
+ }
+};
+
+class LinkManager::InternalLinkBotAddToChannel final : public InternalLink {
+ string bot_username_;
+ AdministratorRights administrator_rights_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeBotAddToChannel>(
+ bot_username_, administrator_rights_.get_chat_administrator_rights_object());
+ }
+
+ public:
+ InternalLinkBotAddToChannel(string bot_username, AdministratorRights &&administrator_rights)
+ : bot_username_(std::move(bot_username)), administrator_rights_(std::move(administrator_rights)) {
+ }
+};
+
+class LinkManager::InternalLinkBotStart final : public InternalLink {
+ string bot_username_;
+ string start_parameter_;
+ bool autostart_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ bool autostart = autostart_;
+ if (Scheduler::context() != nullptr && bot_username_ == G()->get_option_string("premium_bot_username")) {
+ autostart = true;
+ }
+ return td_api::make_object<td_api::internalLinkTypeBotStart>(bot_username_, start_parameter_, autostart);
+ }
+
+ public:
+ InternalLinkBotStart(string bot_username, string start_parameter, bool autostart)
+ : bot_username_(std::move(bot_username)), start_parameter_(std::move(start_parameter)), autostart_(autostart) {
+ }
+};
+
+class LinkManager::InternalLinkBotStartInGroup final : public InternalLink {
+ string bot_username_;
+ string start_parameter_;
+ AdministratorRights administrator_rights_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeBotStartInGroup>(
+ bot_username_, start_parameter_,
+ administrator_rights_ == AdministratorRights() ? nullptr
+ : administrator_rights_.get_chat_administrator_rights_object());
+ }
+
+ public:
+ InternalLinkBotStartInGroup(string bot_username, string start_parameter, AdministratorRights &&administrator_rights)
+ : bot_username_(std::move(bot_username))
+ , start_parameter_(std::move(start_parameter))
+ , administrator_rights_(std::move(administrator_rights)) {
+ }
+};
+
+class LinkManager::InternalLinkChangePhoneNumber final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeChangePhoneNumber>();
+ }
+};
+
+class LinkManager::InternalLinkConfirmPhone final : public InternalLink {
+ string hash_;
+ string phone_number_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypePhoneNumberConfirmation>(hash_, phone_number_);
+ }
+
+ public:
+ InternalLinkConfirmPhone(string hash, string phone_number)
+ : hash_(std::move(hash)), phone_number_(std::move(phone_number)) {
+ }
+};
+
+class LinkManager::InternalLinkDialogInvite final : public InternalLink {
+ string url_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeChatInvite>(url_);
+ }
+
+ public:
+ explicit InternalLinkDialogInvite(string url) : url_(std::move(url)) {
+ }
+};
+
+class LinkManager::InternalLinkFilterSettings final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeFilterSettings>();
+ }
+};
+
+class LinkManager::InternalLinkGame final : public InternalLink {
+ string bot_username_;
+ string game_short_name_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeGame>(bot_username_, game_short_name_);
+ }
+
+ public:
+ InternalLinkGame(string bot_username, string game_short_name)
+ : bot_username_(std::move(bot_username)), game_short_name_(std::move(game_short_name)) {
+ }
+};
+
+class LinkManager::InternalLinkInstantView final : public InternalLink {
+ string url_;
+ string fallback_url_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeInstantView>(url_, fallback_url_);
+ }
+
+ public:
+ InternalLinkInstantView(string url, string fallback_url)
+ : url_(std::move(url)), fallback_url_(std::move(fallback_url)) {
+ }
+};
+
+class LinkManager::InternalLinkInvoice final : public InternalLink {
+ string invoice_name_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeInvoice>(invoice_name_);
+ }
+
+ public:
+ explicit InternalLinkInvoice(string invoice_name) : invoice_name_(std::move(invoice_name)) {
+ }
+};
+
+class LinkManager::InternalLinkLanguage final : public InternalLink {
+ string language_pack_id_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeLanguagePack>(language_pack_id_);
+ }
+
+ public:
+ explicit InternalLinkLanguage(string language_pack_id) : language_pack_id_(std::move(language_pack_id)) {
+ }
+};
+
+class LinkManager::InternalLinkLanguageSettings final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeLanguageSettings>();
+ }
+};
+
+class LinkManager::InternalLinkMessage final : public InternalLink {
+ string url_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeMessage>(url_);
+ }
+
+ public:
+ explicit InternalLinkMessage(string url) : url_(std::move(url)) {
+ }
+};
+
+class LinkManager::InternalLinkMessageDraft final : public InternalLink {
+ FormattedText text_;
+ bool contains_link_ = false;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeMessageDraft>(get_formatted_text_object(text_, true, -1),
+ contains_link_);
+ }
+
+ public:
+ InternalLinkMessageDraft(FormattedText &&text, bool contains_link)
+ : text_(std::move(text)), contains_link_(contains_link) {
+ }
+};
+
+class LinkManager::InternalLinkPassportDataRequest final : public InternalLink {
+ UserId bot_user_id_;
+ string scope_;
+ string public_key_;
+ string nonce_;
+ string callback_url_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypePassportDataRequest>(bot_user_id_.get(), scope_, public_key_,
+ nonce_, callback_url_);
+ }
+
+ public:
+ InternalLinkPassportDataRequest(UserId bot_user_id, string scope, string public_key, string nonce,
+ string callback_url)
+ : bot_user_id_(bot_user_id)
+ , scope_(std::move(scope))
+ , public_key_(std::move(public_key))
+ , nonce_(std::move(nonce))
+ , callback_url_(std::move(callback_url)) {
+ }
+};
+
+class LinkManager::InternalLinkPremiumFeatures final : public InternalLink {
+ string referrer_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypePremiumFeatures>(referrer_);
+ }
+
+ public:
+ explicit InternalLinkPremiumFeatures(string referrer) : referrer_(std::move(referrer)) {
+ }
+};
+
+class LinkManager::InternalLinkPrivacyAndSecuritySettings final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypePrivacyAndSecuritySettings>();
+ }
+};
+
+class LinkManager::InternalLinkProxy final : public InternalLink {
+ string server_;
+ int32 port_;
+ td_api::object_ptr<td_api::ProxyType> type_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ CHECK(type_ != nullptr);
+ auto type = type_.get();
+ auto proxy_type = [type]() -> td_api::object_ptr<td_api::ProxyType> {
+ switch (type->get_id()) {
+ case td_api::proxyTypeSocks5::ID: {
+ auto type_socks = static_cast<const td_api::proxyTypeSocks5 *>(type);
+ return td_api::make_object<td_api::proxyTypeSocks5>(type_socks->username_, type_socks->password_);
+ }
+ case td_api::proxyTypeMtproto::ID: {
+ auto type_mtproto = static_cast<const td_api::proxyTypeMtproto *>(type);
+ return td_api::make_object<td_api::proxyTypeMtproto>(type_mtproto->secret_);
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+ }();
+ return td_api::make_object<td_api::internalLinkTypeProxy>(server_, port_, std::move(proxy_type));
+ }
+
+ public:
+ InternalLinkProxy(string server, int32 port, td_api::object_ptr<td_api::ProxyType> type)
+ : server_(std::move(server)), port_(port), type_(std::move(type)) {
+ }
+};
+
+class LinkManager::InternalLinkPublicDialog final : public InternalLink {
+ string dialog_username_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypePublicChat>(dialog_username_);
+ }
+
+ public:
+ explicit InternalLinkPublicDialog(string dialog_username) : dialog_username_(std::move(dialog_username)) {
+ }
+};
+
+class LinkManager::InternalLinkQrCodeAuthentication final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeQrCodeAuthentication>();
+ }
+};
+
+class LinkManager::InternalLinkRestorePurchases final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeRestorePurchases>();
+ }
+};
+
+class LinkManager::InternalLinkSettings final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeSettings>();
+ }
+};
+
+class LinkManager::InternalLinkStickerSet final : public InternalLink {
+ string sticker_set_name_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeStickerSet>(sticker_set_name_);
+ }
+
+ public:
+ explicit InternalLinkStickerSet(string sticker_set_name) : sticker_set_name_(std::move(sticker_set_name)) {
+ }
+};
+
+class LinkManager::InternalLinkTheme final : public InternalLink {
+ string theme_name_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeTheme>(theme_name_);
+ }
+
+ public:
+ explicit InternalLinkTheme(string theme_name) : theme_name_(std::move(theme_name)) {
+ }
+};
+
+class LinkManager::InternalLinkThemeSettings final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeThemeSettings>();
+ }
+};
+
+class LinkManager::InternalLinkUnknownDeepLink final : public InternalLink {
+ string link_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeUnknownDeepLink>(link_);
+ }
+
+ public:
+ explicit InternalLinkUnknownDeepLink(string link) : link_(std::move(link)) {
+ }
+};
+
+class LinkManager::InternalLinkUnsupportedProxy final : public InternalLink {
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeUnsupportedProxy>();
+ }
+};
+
+class LinkManager::InternalLinkUserPhoneNumber final : public InternalLink {
+ string phone_number_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeUserPhoneNumber>(phone_number_);
+ }
+
+ public:
+ explicit InternalLinkUserPhoneNumber(string phone_number) : phone_number_(std::move(phone_number)) {
+ }
+};
+
+class LinkManager::InternalLinkVoiceChat final : public InternalLink {
+ string dialog_username_;
+ string invite_hash_;
+ bool is_live_stream_;
+
+ td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
+ return td_api::make_object<td_api::internalLinkTypeVideoChat>(dialog_username_, invite_hash_, is_live_stream_);
+ }
+
+ public:
+ InternalLinkVoiceChat(string dialog_username, string invite_hash, bool is_live_stream)
+ : dialog_username_(std::move(dialog_username))
+ , invite_hash_(std::move(invite_hash))
+ , is_live_stream_(is_live_stream) {
+ }
+};
+
+class GetDeepLinkInfoQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::deepLinkInfo>> promise_;
+
+ public:
+ explicit GetDeepLinkInfoQuery(Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(Slice link) {
+ send_query(G()->net_query_creator().create_unauth(telegram_api::help_getDeepLinkInfo(link.str())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_getDeepLinkInfo>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ switch (result->get_id()) {
+ case telegram_api::help_deepLinkInfoEmpty::ID:
+ return promise_.set_value(nullptr);
+ case telegram_api::help_deepLinkInfo::ID: {
+ auto info = telegram_api::move_object_as<telegram_api::help_deepLinkInfo>(result);
+ auto entities = get_message_entities(nullptr, std::move(info->entities_), "GetDeepLinkInfoQuery");
+ auto status = fix_formatted_text(info->message_, entities, true, true, true, true, true);
+ if (status.is_error()) {
+ LOG(ERROR) << "Receive error " << status << " while parsing deep link info " << info->message_;
+ if (!clean_input_string(info->message_)) {
+ info->message_.clear();
+ }
+ entities = find_entities(info->message_, true, true);
+ }
+ FormattedText text{std::move(info->message_), std::move(entities)};
+ return promise_.set_value(
+ td_api::make_object<td_api::deepLinkInfo>(get_formatted_text_object(text, true, -1), info->update_app_));
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class RequestUrlAuthQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::LoginUrlInfo>> promise_;
+ string url_;
+ DialogId dialog_id_;
+
+ public:
+ explicit RequestUrlAuthQuery(Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(string url, FullMessageId full_message_id, int32 button_id) {
+ url_ = std::move(url);
+ int32 flags = 0;
+ tl_object_ptr<telegram_api::InputPeer> input_peer;
+ if (full_message_id.get_dialog_id().is_valid()) {
+ dialog_id_ = full_message_id.get_dialog_id();
+ input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+ flags |= telegram_api::messages_requestUrlAuth::PEER_MASK;
+ } else {
+ flags |= telegram_api::messages_requestUrlAuth::URL_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_requestUrlAuth(
+ flags, std::move(input_peer), full_message_id.get_message_id().get_server_message_id().get(), button_id,
+ url_)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_requestUrlAuth>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for RequestUrlAuthQuery: " << to_string(result);
+ switch (result->get_id()) {
+ case telegram_api::urlAuthResultRequest::ID: {
+ auto request = telegram_api::move_object_as<telegram_api::urlAuthResultRequest>(result);
+ UserId bot_user_id = ContactsManager::get_user_id(request->bot_);
+ if (!bot_user_id.is_valid()) {
+ return on_error(Status::Error(500, "Receive invalid bot_user_id"));
+ }
+ td_->contacts_manager_->on_get_user(std::move(request->bot_), "RequestUrlAuthQuery");
+ promise_.set_value(td_api::make_object<td_api::loginUrlInfoRequestConfirmation>(
+ url_, request->domain_, td_->contacts_manager_->get_user_id_object(bot_user_id, "RequestUrlAuthQuery"),
+ request->request_write_access_));
+ break;
+ }
+ case telegram_api::urlAuthResultAccepted::ID: {
+ auto accepted = telegram_api::move_object_as<telegram_api::urlAuthResultAccepted>(result);
+ promise_.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(accepted->url_, true));
+ break;
+ }
+ case telegram_api::urlAuthResultDefault::ID:
+ promise_.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url_, false));
+ break;
+ }
+ }
+
+ void on_error(Status status) final {
+ if (!dialog_id_.is_valid() ||
+ !td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "RequestUrlAuthQuery")) {
+ LOG(INFO) << "Receive error for RequestUrlAuthQuery: " << status;
+ }
+ promise_.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url_, false));
+ }
+};
+
+class AcceptUrlAuthQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::httpUrl>> promise_;
+ string url_;
+ DialogId dialog_id_;
+
+ public:
+ explicit AcceptUrlAuthQuery(Promise<td_api::object_ptr<td_api::httpUrl>> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(string url, FullMessageId full_message_id, int32 button_id, bool allow_write_access) {
+ url_ = std::move(url);
+ int32 flags = 0;
+ tl_object_ptr<telegram_api::InputPeer> input_peer;
+ if (full_message_id.get_dialog_id().is_valid()) {
+ dialog_id_ = full_message_id.get_dialog_id();
+ input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+ flags |= telegram_api::messages_acceptUrlAuth::PEER_MASK;
+ } else {
+ flags |= telegram_api::messages_acceptUrlAuth::URL_MASK;
+ }
+ if (allow_write_access) {
+ flags |= telegram_api::messages_acceptUrlAuth::WRITE_ALLOWED_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_acceptUrlAuth(
+ flags, false /*ignored*/, std::move(input_peer), full_message_id.get_message_id().get_server_message_id().get(),
+ button_id, url_)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_acceptUrlAuth>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive " << to_string(result);
+ switch (result->get_id()) {
+ case telegram_api::urlAuthResultRequest::ID:
+ LOG(ERROR) << "Receive unexpected " << to_string(result);
+ return on_error(Status::Error(500, "Receive unexpected urlAuthResultRequest"));
+ case telegram_api::urlAuthResultAccepted::ID: {
+ auto accepted = telegram_api::move_object_as<telegram_api::urlAuthResultAccepted>(result);
+ promise_.set_value(td_api::make_object<td_api::httpUrl>(accepted->url_));
+ break;
+ }
+ case telegram_api::urlAuthResultDefault::ID:
+ promise_.set_value(td_api::make_object<td_api::httpUrl>(url_));
+ break;
+ }
+ }
+
+ void on_error(Status status) final {
+ if (!dialog_id_.is_valid() ||
+ !td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "AcceptUrlAuthQuery")) {
+ LOG(INFO) << "Receive error for AcceptUrlAuthQuery: " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+LinkManager::LinkManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+}
+
+LinkManager::~LinkManager() = default;
+
+void LinkManager::start_up() {
+ autologin_update_time_ = Time::now() - 365 * 86400;
+ autologin_domains_ = full_split(G()->td_db()->get_binlog_pmc()->get("autologin_domains"), '\xFF');
+
+ url_auth_domains_ = full_split(G()->td_db()->get_binlog_pmc()->get("url_auth_domains"), '\xFF');
+
+ whitelisted_domains_ = full_split(G()->td_db()->get_binlog_pmc()->get("whitelisted_domains"), '\xFF');
+}
+
+void LinkManager::tear_down() {
+ parent_.reset();
+}
+
+static bool tolower_begins_with(Slice str, Slice prefix) {
+ if (prefix.size() > str.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < prefix.size(); i++) {
+ if (to_lower(str[i]) != prefix[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+Result<string> LinkManager::check_link(CSlice link, bool http_only, bool https_only) {
+ auto result = check_link_impl(link, http_only, https_only);
+ if (result.is_ok()) {
+ return result;
+ }
+ auto error = result.move_as_error();
+ if (check_utf8(link)) {
+ return Status::Error(400, PSLICE() << "URL '" << link << "' is invalid: " << error.message());
+ } else {
+ return Status::Error(400, PSLICE() << "URL is invalid: " << error.message());
+ }
+}
+
+string LinkManager::get_checked_link(Slice link, bool http_only, bool https_only) {
+ auto result = check_link_impl(link, http_only, https_only);
+ if (result.is_ok()) {
+ return result.move_as_ok();
+ }
+ return string();
+}
+
+Result<string> LinkManager::check_link_impl(Slice link, bool http_only, bool https_only) {
+ bool is_tg = false;
+ bool is_ton = false;
+ if (tolower_begins_with(link, "tg:")) {
+ link.remove_prefix(3);
+ is_tg = true;
+ } else if (tolower_begins_with(link, "ton:")) {
+ link.remove_prefix(4);
+ is_ton = true;
+ }
+ if ((is_tg || is_ton) && begins_with(link, "//")) {
+ link.remove_prefix(2);
+ }
+ TRY_RESULT(http_url, parse_url(link));
+ if (https_only && (http_url.protocol_ != HttpUrl::Protocol::Https || is_tg || is_ton)) {
+ return Status::Error("Only HTTPS links are allowed");
+ }
+ if (is_tg || is_ton) {
+ if (http_only) {
+ return Status::Error("Only HTTP links are allowed");
+ }
+ if (tolower_begins_with(link, "http://") || http_url.protocol_ == HttpUrl::Protocol::Https ||
+ !http_url.userinfo_.empty() || http_url.specified_port_ != 0 || http_url.is_ipv6_) {
+ return Status::Error(is_tg ? Slice("Wrong tg URL") : Slice("Wrong ton URL"));
+ }
+
+ Slice query(http_url.query_);
+ CHECK(query[0] == '/');
+ if (query.size() > 1 && query[1] == '?') {
+ query.remove_prefix(1);
+ }
+ for (auto c : http_url.host_) {
+ if (!is_alnum(c) && c != '-' && c != '_') {
+ return Status::Error("Unallowed characters in URL host");
+ }
+ }
+ return PSTRING() << (is_tg ? "tg" : "ton") << "://" << http_url.host_ << query;
+ }
+
+ if (http_url.host_.find('.') == string::npos && !http_url.is_ipv6_) {
+ return Status::Error("Wrong HTTP URL");
+ }
+ return http_url.get_url();
+}
+
+LinkManager::LinkInfo LinkManager::get_link_info(Slice link) {
+ LinkInfo result;
+ if (link.empty()) {
+ return result;
+ }
+ link.truncate(link.find('#'));
+
+ bool is_tg = false;
+ if (tolower_begins_with(link, "tg:")) {
+ link.remove_prefix(3);
+ if (begins_with(link, "//")) {
+ link.remove_prefix(2);
+ }
+ is_tg = true;
+ }
+
+ auto r_http_url = parse_url(link);
+ if (r_http_url.is_error()) {
+ return result;
+ }
+ auto http_url = r_http_url.move_as_ok();
+
+ if (!http_url.userinfo_.empty() || http_url.is_ipv6_) {
+ return result;
+ }
+
+ if (is_tg) {
+ if (tolower_begins_with(link, "http://") || http_url.protocol_ == HttpUrl::Protocol::Https ||
+ http_url.specified_port_ != 0) {
+ return result;
+ }
+
+ result.type_ = LinkType::Tg;
+ result.query_ = link.str();
+ return result;
+ } else {
+ if (http_url.port_ != 80 && http_url.port_ != 443) {
+ return result;
+ }
+
+ auto host = url_decode(http_url.host_, false);
+ to_lower_inplace(host);
+ if (ends_with(host, ".t.me") && host.size() >= 9 && host.find('.') == host.size() - 5) {
+ Slice subdomain(&host[0], host.size() - 5);
+ if (is_valid_username(subdomain) && subdomain != "addemoji" && subdomain != "addstickers" &&
+ subdomain != "addtheme" && subdomain != "auth" && subdomain != "confirmphone" && subdomain != "invoice" &&
+ subdomain != "joinchat" && subdomain != "login" && subdomain != "proxy" && subdomain != "setlanguage" &&
+ subdomain != "share" && subdomain != "socks") {
+ result.type_ = LinkType::TMe;
+ result.query_ = PSTRING() << '/' << subdomain << http_url.query_;
+ return result;
+ }
+ }
+ if (begins_with(host, "www.")) {
+ host = host.substr(4);
+ }
+
+ string cur_t_me_url;
+ vector<Slice> t_me_urls{Slice("t.me"), Slice("telegram.me"), Slice("telegram.dog")};
+ if (Scheduler::context() != nullptr) { // for tests only
+ cur_t_me_url = G()->get_option_string("t_me_url");
+ if (tolower_begins_with(cur_t_me_url, "http://") || tolower_begins_with(cur_t_me_url, "https://")) {
+ Slice t_me_url = cur_t_me_url;
+ t_me_url = t_me_url.substr(t_me_url[4] == 's' ? 8 : 7);
+ if (!td::contains(t_me_urls, t_me_url)) {
+ t_me_urls.push_back(t_me_url);
+ }
+ }
+ }
+
+ for (auto t_me_url : t_me_urls) {
+ if (host == t_me_url) {
+ result.type_ = LinkType::TMe;
+
+ Slice query = http_url.query_;
+ while (true) {
+ if (begins_with(query, "/s/")) {
+ query.remove_prefix(2);
+ continue;
+ }
+ if (begins_with(query, "/%73/")) {
+ query.remove_prefix(4);
+ continue;
+ }
+ break;
+ }
+ result.query_ = query.str();
+ return result;
+ }
+ }
+
+ if (http_url.query_.size() > 1) {
+ for (auto telegraph_url : {Slice("telegra.ph"), Slice("te.legra.ph"), Slice("graph.org")}) {
+ if (host == telegraph_url) {
+ result.type_ = LinkType::Telegraph;
+ result.query_ = std::move(http_url.query_);
+ return result;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+bool LinkManager::is_internal_link(Slice link) {
+ auto info = get_link_info(link);
+ return info.type_ != LinkType::External;
+}
+
+unique_ptr<LinkManager::InternalLink> LinkManager::parse_internal_link(Slice link, bool is_trusted) {
+ auto info = get_link_info(link);
+ switch (info.type_) {
+ case LinkType::External:
+ return nullptr;
+ case LinkType::Tg:
+ return parse_tg_link_query(info.query_, is_trusted);
+ case LinkType::TMe:
+ return parse_t_me_link_query(info.query_, is_trusted);
+ case LinkType::Telegraph:
+ return td::make_unique<InternalLinkInstantView>(PSTRING() << "https://telegra.ph" << info.query_, link.str());
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+namespace {
+struct CopyArg {
+ Slice name_;
+ const HttpUrlQuery *url_query_;
+ bool *is_first_;
+
+ CopyArg(Slice name, const HttpUrlQuery *url_query, bool *is_first)
+ : name_(name), url_query_(url_query), is_first_(is_first) {
+ }
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const CopyArg &copy_arg) {
+ auto arg = copy_arg.url_query_->get_arg(copy_arg.name_);
+ if (arg.empty()) {
+ for (const auto &query_arg : copy_arg.url_query_->args_) {
+ if (query_arg.first == copy_arg.name_) {
+ char c = *copy_arg.is_first_ ? '?' : '&';
+ *copy_arg.is_first_ = false;
+ return string_builder << c << copy_arg.name_;
+ }
+ }
+ return string_builder;
+ }
+ char c = *copy_arg.is_first_ ? '?' : '&';
+ *copy_arg.is_first_ = false;
+ return string_builder << c << copy_arg.name_ << '=' << url_encode(arg);
+}
+} // namespace
+
+unique_ptr<LinkManager::InternalLink> LinkManager::parse_tg_link_query(Slice query, bool is_trusted) {
+ const auto url_query = parse_url_query(query);
+ const auto &path = url_query.path_;
+
+ bool is_first_arg = true;
+ auto copy_arg = [&](Slice name) {
+ return CopyArg(name, &url_query, &is_first_arg);
+ };
+ auto pass_arg = [&](Slice name) {
+ return url_encode(url_query.get_arg(name));
+ };
+ auto get_arg = [&](Slice name) {
+ return url_query.get_arg(name).str();
+ };
+ auto has_arg = [&](Slice name) {
+ return !url_query.get_arg(name).empty();
+ };
+
+ if (path.size() == 1 && path[0] == "resolve") {
+ if (is_valid_username(get_arg("domain"))) {
+ if (has_arg("post")) {
+ // resolve?domain=<username>&post=12345&single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
+ return td::make_unique<InternalLinkMessage>(PSTRING() << "tg:resolve" << copy_arg("domain") << copy_arg("post")
+ << copy_arg("single") << copy_arg("thread")
+ << copy_arg("comment") << copy_arg("t"));
+ }
+ auto username = get_arg("domain");
+ for (auto &arg : url_query.args_) {
+ if (arg.first == "voicechat" || arg.first == "videochat" || arg.first == "livestream") {
+ // resolve?domain=<username>&videochat
+ // resolve?domain=<username>&videochat=<invite_hash>
+ if (Scheduler::context() != nullptr) {
+ send_closure(G()->messages_manager(), &MessagesManager::reload_voice_chat_on_search, username);
+ }
+ return td::make_unique<InternalLinkVoiceChat>(std::move(username), arg.second, arg.first == "livestream");
+ }
+ if (arg.first == "start" && is_valid_start_parameter(arg.second)) {
+ // resolve?domain=<bot_username>&start=<parameter>
+ return td::make_unique<InternalLinkBotStart>(std::move(username), arg.second, is_trusted);
+ }
+ if (arg.first == "startgroup" && is_valid_start_parameter(arg.second)) {
+ // resolve?domain=<bot_username>&startgroup=<parameter>
+ // resolve?domain=<bot_username>&startgroup=>parameter>&admin=change_info+delete_messages+restrict_members
+ // resolve?domain=<bot_username>&startgroup&admin=change_info+delete_messages+restrict_members
+ auto administrator_rights = get_administrator_rights(url_query.get_arg("admin"), false);
+ return td::make_unique<InternalLinkBotStartInGroup>(std::move(username), arg.second,
+ std::move(administrator_rights));
+ }
+ if (arg.first == "startchannel") {
+ // resolve?domain=<bot_username>&startchannel&admin=change_info+post_messages+promote_members
+ auto administrator_rights = get_administrator_rights(url_query.get_arg("admin"), true);
+ if (administrator_rights != AdministratorRights()) {
+ return td::make_unique<InternalLinkBotAddToChannel>(std::move(username), std::move(administrator_rights));
+ }
+ }
+ if (arg.first == "game" && !arg.second.empty()) {
+ // resolve?domain=<bot_username>&game=<short_name>
+ return td::make_unique<InternalLinkGame>(std::move(username), arg.second);
+ }
+ }
+ if (!url_query.get_arg("attach").empty()) {
+ // resolve?domain=<username>&attach=<bot_username>
+ // resolve?domain=<username>&attach=<bot_username>&startattach=<start_parameter>
+ return td::make_unique<InternalLinkAttachMenuBot>(
+ nullptr, td::make_unique<InternalLinkPublicDialog>(std::move(username)), url_query.get_arg("attach").str(),
+ url_query.get_arg("startattach"));
+ } else if (url_query.has_arg("startattach")) {
+ // resolve?domain=<bot_username>&startattach&choose=users+bots+groups+channels
+ // resolve?domain=<bot_username>&startattach=<start_parameter>&choose=users+bots+groups+channels
+ return td::make_unique<InternalLinkAttachMenuBot>(get_target_chat_chosen(url_query.get_arg("choose")), nullptr,
+ std::move(username), url_query.get_arg("startattach"));
+ }
+ if (username == "telegrampassport") {
+ // resolve?domain=telegrampassport&bot_id=<bot_user_id>&scope=<scope>&public_key=<public_key>&nonce=<nonce>
+ return get_internal_link_passport(query, url_query.args_);
+ }
+ // resolve?domain=<username>
+ return td::make_unique<InternalLinkPublicDialog>(std::move(username));
+ } else if (is_valid_phone_number(get_arg("phone"))) {
+ auto user_link = td::make_unique<InternalLinkUserPhoneNumber>(get_arg("phone"));
+ if (!url_query.get_arg("attach").empty()) {
+ // resolve?phone=<phone_number>&attach=<bot_username>
+ // resolve?phone=<phone_number>&attach=<bot_username>&startattach=<start_parameter>
+ return td::make_unique<InternalLinkAttachMenuBot>(
+ nullptr, std::move(user_link), url_query.get_arg("attach").str(), url_query.get_arg("startattach"));
+ }
+ // resolve?phone=12345
+ return std::move(user_link);
+ }
+ } else if (path.size() == 1 && path[0] == "login") {
+ // login?code=123456
+ if (has_arg("code")) {
+ return td::make_unique<InternalLinkAuthenticationCode>(get_arg("code"));
+ }
+ // login?token=<token>
+ if (has_arg("token")) {
+ return td::make_unique<InternalLinkQrCodeAuthentication>();
+ }
+ } else if (path.size() == 1 && path[0] == "restore_purchases") {
+ // restore_purchases
+ return td::make_unique<InternalLinkRestorePurchases>();
+ } else if (path.size() == 1 && path[0] == "passport") {
+ // passport?bot_id=<bot_user_id>&scope=<scope>&public_key=<public_key>&nonce=<nonce>
+ return get_internal_link_passport(query, url_query.args_);
+ } else if (path.size() == 1 && path[0] == "premium_offer") {
+ // premium_offer?ref=<referrer>
+ return td::make_unique<InternalLinkPremiumFeatures>(get_arg("ref"));
+ } else if (!path.empty() && path[0] == "settings") {
+ if (path.size() == 2 && path[1] == "change_number") {
+ // settings/change_number
+ return td::make_unique<InternalLinkChangePhoneNumber>();
+ }
+ if (path.size() == 2 && path[1] == "devices") {
+ // settings/devices
+ return td::make_unique<InternalLinkActiveSessions>();
+ }
+ if (path.size() == 2 && path[1] == "folders") {
+ // settings/folders
+ return td::make_unique<InternalLinkFilterSettings>();
+ }
+ if (path.size() == 2 && path[1] == "language") {
+ // settings/language
+ return td::make_unique<InternalLinkLanguageSettings>();
+ }
+ if (path.size() == 2 && path[1] == "privacy") {
+ // settings/privacy
+ return td::make_unique<InternalLinkPrivacyAndSecuritySettings>();
+ }
+ if (path.size() == 2 && path[1] == "themes") {
+ // settings/themes
+ return td::make_unique<InternalLinkThemeSettings>();
+ }
+ // settings
+ return td::make_unique<InternalLinkSettings>();
+ } else if (path.size() == 1 && path[0] == "join") {
+ // join?invite=<hash>
+ if (has_arg("invite")) {
+ return td::make_unique<InternalLinkDialogInvite>(PSTRING() << "tg:join?invite="
+ << url_encode(get_url_query_hash(true, url_query)));
+ }
+ } else if (path.size() == 1 && (path[0] == "addstickers" || path[0] == "addemoji")) {
+ // addstickers?set=<name>
+ // addemoji?set=<name>
+ if (has_arg("set")) {
+ return td::make_unique<InternalLinkStickerSet>(get_arg("set"));
+ }
+ } else if (path.size() == 1 && path[0] == "setlanguage") {
+ // setlanguage?lang=<name>
+ if (has_arg("lang")) {
+ return td::make_unique<InternalLinkLanguage>(get_arg("lang"));
+ }
+ } else if (path.size() == 1 && path[0] == "addtheme") {
+ // addtheme?slug=<name>
+ if (has_arg("slug")) {
+ return td::make_unique<InternalLinkTheme>(get_arg("slug"));
+ }
+ } else if (path.size() == 1 && path[0] == "confirmphone") {
+ if (has_arg("hash") && has_arg("phone")) {
+ // confirmphone?phone=<phone>&hash=<hash>
+ return td::make_unique<InternalLinkConfirmPhone>(get_arg("hash"), get_arg("phone"));
+ }
+ } else if (path.size() == 1 && path[0] == "socks") {
+ if (has_arg("server") && has_arg("port")) {
+ // socks?server=<server>&port=<port>&user=<user>&pass=<pass>
+ auto port = to_integer<int32>(get_arg("port"));
+ if (0 < port && port < 65536) {
+ return td::make_unique<InternalLinkProxy>(
+ get_arg("server"), port, td_api::make_object<td_api::proxyTypeSocks5>(get_arg("user"), get_arg("pass")));
+ } else {
+ return td::make_unique<InternalLinkUnsupportedProxy>();
+ }
+ }
+ } else if (path.size() == 1 && path[0] == "proxy") {
+ if (has_arg("server") && has_arg("port")) {
+ // proxy?server=<server>&port=<port>&secret=<secret>
+ auto port = to_integer<int32>(get_arg("port"));
+ if (0 < port && port < 65536 && mtproto::ProxySecret::from_link(get_arg("secret")).is_ok()) {
+ return td::make_unique<InternalLinkProxy>(get_arg("server"), port,
+ td_api::make_object<td_api::proxyTypeMtproto>(get_arg("secret")));
+ } else {
+ return td::make_unique<InternalLinkUnsupportedProxy>();
+ }
+ }
+ } else if (path.size() == 1 && path[0] == "privatepost") {
+ // privatepost?channel=123456789&post=12345&single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
+ if (has_arg("channel") && has_arg("post")) {
+ return td::make_unique<InternalLinkMessage>(
+ PSTRING() << "tg:privatepost" << copy_arg("channel") << copy_arg("post") << copy_arg("single")
+ << copy_arg("thread") << copy_arg("comment") << copy_arg("t"));
+ }
+ } else if (path.size() == 1 && path[0] == "bg") {
+ // bg?color=<color>
+ // bg?gradient=<hex_color>-<hex_color>&rotation=...
+ // bg?gradient=<hex_color>~<hex_color>~<hex_color>~<hex_color>
+ // bg?slug=<background_name>&mode=blur+motion
+ // bg?slug=<pattern_name>&intensity=...&bg_color=...&mode=blur+motion
+ if (has_arg("color")) {
+ return td::make_unique<InternalLinkBackground>(pass_arg("color"));
+ }
+ if (has_arg("gradient")) {
+ return td::make_unique<InternalLinkBackground>(PSTRING() << pass_arg("gradient") << copy_arg("rotation"));
+ }
+ if (has_arg("slug")) {
+ return td::make_unique<InternalLinkBackground>(PSTRING()
+ << pass_arg("slug") << copy_arg("mode") << copy_arg("intensity")
+ << copy_arg("bg_color") << copy_arg("rotation"));
+ }
+ } else if (path.size() == 1 && path[0] == "invoice") {
+ // invoice?slug=<invoice_name>
+ if (has_arg("slug")) {
+ return td::make_unique<InternalLinkInvoice>(url_query.get_arg("slug").str());
+ }
+ } else if (path.size() == 1 && (path[0] == "share" || path[0] == "msg" || path[0] == "msg_url")) {
+ // msg_url?url=<url>
+ // msg_url?url=<url>&text=<text>
+ return get_internal_link_message_draft(get_arg("url"), get_arg("text"));
+ }
+ if (!path.empty() && !path[0].empty()) {
+ return td::make_unique<InternalLinkUnknownDeepLink>(PSTRING() << "tg://" << query);
+ }
+ return nullptr;
+}
+
+unique_ptr<LinkManager::InternalLink> LinkManager::parse_t_me_link_query(Slice query, bool is_trusted) {
+ CHECK(query[0] == '/');
+ const auto url_query = parse_url_query(query);
+ const auto &path = url_query.path_;
+ if (path.empty() || path[0].empty()) {
+ return nullptr;
+ }
+
+ bool is_first_arg = true;
+ auto copy_arg = [&](Slice name) {
+ return CopyArg(name, &url_query, &is_first_arg);
+ };
+
+ auto get_arg = [&](Slice name) {
+ return url_query.get_arg(name).str();
+ };
+ auto has_arg = [&](Slice name) {
+ return !url_query.get_arg(name).empty();
+ };
+
+ if (path[0] == "c") {
+ if (path.size() >= 3 && to_integer<int64>(path[1]) > 0 && to_integer<int64>(path[2]) > 0) {
+ // /c/123456789/12345?single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
+ // /c/123456789/1234/12345?single&comment=<message_id>&t=<media_timestamp>
+ is_first_arg = false;
+ auto post = to_integer<int64>(path[2]);
+ auto thread = PSTRING() << copy_arg("thread");
+ if (path.size() >= 4 && to_integer<int64>(path[3]) > 0) {
+ thread = PSTRING() << "&thread=" << post;
+ post = to_integer<int64>(path[3]);
+ }
+ return td::make_unique<InternalLinkMessage>(PSTRING() << "tg:privatepost?channel=" << to_integer<int64>(path[1])
+ << "&post=" << post << copy_arg("single") << thread
+ << copy_arg("comment") << copy_arg("t"));
+ }
+ } else if (path[0] == "login") {
+ if (path.size() >= 2 && !path[1].empty()) {
+ // /login/<code>
+ return td::make_unique<InternalLinkAuthenticationCode>(path[1]);
+ }
+ } else if (path[0] == "joinchat") {
+ if (path.size() >= 2 && !path[1].empty()) {
+ // /joinchat/<link>
+ return td::make_unique<InternalLinkDialogInvite>(PSTRING() << "tg:join?invite="
+ << url_encode(get_url_query_hash(false, url_query)));
+ }
+ } else if (path[0][0] == ' ' || path[0][0] == '+') {
+ if (path[0].size() >= 2) {
+ if (is_valid_phone_number(Slice(path[0]).substr(1))) {
+ auto user_link = td::make_unique<InternalLinkUserPhoneNumber>(path[0].substr(1));
+ if (!url_query.get_arg("attach").empty()) {
+ // /+<phone_number>?attach=<bot_username>
+ // /+<phone_number>?attach=<bot_username>&startattach=<start_parameter>
+ return td::make_unique<InternalLinkAttachMenuBot>(
+ nullptr, std::move(user_link), url_query.get_arg("attach").str(), url_query.get_arg("startattach"));
+ }
+ // /+<phone_number>
+ return std::move(user_link);
+ } else {
+ // /+<link>
+ return td::make_unique<InternalLinkDialogInvite>(PSTRING() << "tg:join?invite="
+ << url_encode(get_url_query_hash(false, url_query)));
+ }
+ }
+ } else if (path[0] == "addstickers" || path[0] == "addemoji") {
+ if (path.size() >= 2 && !path[1].empty()) {
+ // /addstickers/<name>
+ // /addemoji/<name>
+ return td::make_unique<InternalLinkStickerSet>(path[1]);
+ }
+ } else if (path[0] == "setlanguage") {
+ if (path.size() >= 2 && !path[1].empty()) {
+ // /setlanguage/<name>
+ return td::make_unique<InternalLinkLanguage>(path[1]);
+ }
+ } else if (path[0] == "addtheme") {
+ if (path.size() >= 2 && !path[1].empty()) {
+ // /addtheme/<name>
+ return td::make_unique<InternalLinkTheme>(path[1]);
+ }
+ } else if (path[0] == "confirmphone") {
+ if (has_arg("hash") && has_arg("phone")) {
+ // /confirmphone?phone=<phone>&hash=<hash>
+ return td::make_unique<InternalLinkConfirmPhone>(get_arg("hash"), get_arg("phone"));
+ }
+ } else if (path[0] == "socks") {
+ if (has_arg("server") && has_arg("port")) {
+ // /socks?server=<server>&port=<port>&user=<user>&pass=<pass>
+ auto port = to_integer<int32>(get_arg("port"));
+ if (0 < port && port < 65536) {
+ return td::make_unique<InternalLinkProxy>(
+ get_arg("server"), port, td_api::make_object<td_api::proxyTypeSocks5>(get_arg("user"), get_arg("pass")));
+ } else {
+ return td::make_unique<InternalLinkUnsupportedProxy>();
+ }
+ }
+ } else if (path[0] == "proxy") {
+ if (has_arg("server") && has_arg("port")) {
+ // /proxy?server=<server>&port=<port>&secret=<secret>
+ auto port = to_integer<int32>(get_arg("port"));
+ if (0 < port && port < 65536 && mtproto::ProxySecret::from_link(get_arg("secret")).is_ok()) {
+ return td::make_unique<InternalLinkProxy>(get_arg("server"), port,
+ td_api::make_object<td_api::proxyTypeMtproto>(get_arg("secret")));
+ } else {
+ return td::make_unique<InternalLinkUnsupportedProxy>();
+ }
+ }
+ } else if (path[0] == "bg") {
+ if (path.size() >= 2 && !path[1].empty()) {
+ // /bg/<hex_color>
+ // /bg/<hex_color>-<hex_color>?rotation=...
+ // /bg/<hex_color>~<hex_color>~<hex_color>~<hex_color>
+ // /bg/<background_name>?mode=blur+motion
+ // /bg/<pattern_name>?intensity=...&bg_color=...&mode=blur+motion
+ return td::make_unique<InternalLinkBackground>(PSTRING()
+ << url_encode(path[1]) << copy_arg("mode") << copy_arg("intensity")
+ << copy_arg("bg_color") << copy_arg("rotation"));
+ }
+ } else if (path[0] == "invoice") {
+ if (path.size() >= 2 && !path[1].empty()) {
+ // /invoice/<name>
+ return td::make_unique<InternalLinkInvoice>(path[1]);
+ }
+ } else if (path[0][0] == '$') {
+ if (path[0].size() >= 2) {
+ // /$<invoice_name>
+ return td::make_unique<InternalLinkInvoice>(path[0].substr(1));
+ }
+ } else if (path[0] == "share" || path[0] == "msg") {
+ if (!(path.size() > 1 && (path[1] == "bookmarklet" || path[1] == "embed"))) {
+ // /share?url=<url>
+ // /share/url?url=<url>&text=<text>
+ return get_internal_link_message_draft(get_arg("url"), get_arg("text"));
+ }
+ } else if (path[0] == "iv") {
+ if (path.size() == 1 && has_arg("url")) {
+ // /iv?url=<url>&rhash=<rhash>
+ return td::make_unique<InternalLinkInstantView>(
+ PSTRING() << "https://t.me/iv" << copy_arg("url") << copy_arg("rhash"), get_arg("url"));
+ }
+ } else if (is_valid_username(path[0])) {
+ if (path.size() >= 2 && to_integer<int64>(path[1]) > 0) {
+ // /<username>/12345?single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
+ // /<username>/1234/12345?single&comment=<message_id>&t=<media_timestamp>
+ is_first_arg = false;
+ auto post = to_integer<int64>(path[1]);
+ auto thread = PSTRING() << copy_arg("thread");
+ if (path.size() >= 3 && to_integer<int64>(path[2]) > 0) {
+ thread = PSTRING() << "&thread=" << post;
+ post = to_integer<int64>(path[2]);
+ }
+ return td::make_unique<InternalLinkMessage>(PSTRING() << "tg:resolve?domain=" << url_encode(path[0])
+ << "&post=" << post << copy_arg("single") << thread
+ << copy_arg("comment") << copy_arg("t"));
+ }
+ auto username = path[0];
+ for (auto &arg : url_query.args_) {
+ if (arg.first == "voicechat" || arg.first == "videochat" || arg.first == "livestream") {
+ // /<username>?videochat
+ // /<username>?videochat=<invite_hash>
+ if (Scheduler::context() != nullptr) {
+ send_closure(G()->messages_manager(), &MessagesManager::reload_voice_chat_on_search, username);
+ }
+ return td::make_unique<InternalLinkVoiceChat>(std::move(username), arg.second, arg.first == "livestream");
+ }
+ if (arg.first == "start" && is_valid_start_parameter(arg.second)) {
+ // /<bot_username>?start=<parameter>
+ return td::make_unique<InternalLinkBotStart>(std::move(username), arg.second, is_trusted);
+ }
+ if (arg.first == "startgroup" && is_valid_start_parameter(arg.second)) {
+ // /<bot_username>?startgroup=<parameter>
+ // /<bot_username>?startgroup=<parameter>&admin=change_info+delete_messages+restrict_members
+ // /<bot_username>?startgroup&admin=change_info+delete_messages+restrict_members
+ auto administrator_rights = get_administrator_rights(url_query.get_arg("admin"), false);
+ return td::make_unique<InternalLinkBotStartInGroup>(std::move(username), arg.second,
+ std::move(administrator_rights));
+ }
+ if (arg.first == "startchannel") {
+ // /<bot_username>?startchannel&admin=change_info+post_messages+promote_members
+ auto administrator_rights = get_administrator_rights(url_query.get_arg("admin"), true);
+ if (administrator_rights != AdministratorRights()) {
+ return td::make_unique<InternalLinkBotAddToChannel>(std::move(username), std::move(administrator_rights));
+ }
+ }
+ if (arg.first == "game" && !arg.second.empty()) {
+ // /<bot_username>?game=<short_name>
+ return td::make_unique<InternalLinkGame>(std::move(username), arg.second);
+ }
+ }
+ if (!url_query.get_arg("attach").empty()) {
+ // /<username>?attach=<bot_username>
+ // /<username>?attach=<bot_username>&startattach=<start_parameter>
+ return td::make_unique<InternalLinkAttachMenuBot>(
+ nullptr, td::make_unique<InternalLinkPublicDialog>(std::move(username)), url_query.get_arg("attach").str(),
+ url_query.get_arg("startattach"));
+ } else if (url_query.has_arg("startattach")) {
+ // /<bot_username>?startattach&choose=users+bots+groups+channels
+ // /<bot_username>?startattach=<start_parameter>&choose=users+bots+groups+channels
+ return td::make_unique<InternalLinkAttachMenuBot>(get_target_chat_chosen(url_query.get_arg("choose")), nullptr,
+ std::move(username), url_query.get_arg("startattach"));
+ }
+
+ // /<username>
+ return td::make_unique<InternalLinkPublicDialog>(std::move(username));
+ }
+ return nullptr;
+}
+
+unique_ptr<LinkManager::InternalLink> LinkManager::get_internal_link_message_draft(Slice url, Slice text) {
+ if (url.empty() && text.empty()) {
+ return nullptr;
+ }
+ while (!text.empty() && text.back() == '\n') {
+ text.remove_suffix(1);
+ }
+ url = trim(url);
+ if (url.empty()) {
+ url = text;
+ text = Slice();
+ }
+ FormattedText full_text;
+ bool contains_url = false;
+ if (!text.empty()) {
+ contains_url = true;
+ full_text.text = PSTRING() << url << '\n' << text;
+ } else {
+ full_text.text = url.str();
+ }
+ if (fix_formatted_text(full_text.text, full_text.entities, false, false, false, true, true).is_error()) {
+ return nullptr;
+ }
+ if (full_text.text[0] == '@') {
+ full_text.text = ' ' + full_text.text;
+ for (auto &entity : full_text.entities) {
+ entity.offset++;
+ }
+ }
+ return td::make_unique<InternalLinkMessageDraft>(std::move(full_text), contains_url);
+}
+
+unique_ptr<LinkManager::InternalLink> LinkManager::get_internal_link_passport(
+ Slice query, const vector<std::pair<string, string>> &args) {
+ auto get_arg = [&args](Slice key) {
+ for (auto &arg : args) {
+ if (arg.first == key) {
+ return Slice(arg.second);
+ }
+ }
+ return Slice();
+ };
+
+ UserId bot_user_id(to_integer<int64>(get_arg("bot_id")));
+ auto scope = get_arg("scope");
+ auto public_key = get_arg("public_key");
+ auto nonce = get_arg("nonce");
+ if (nonce.empty()) {
+ nonce = get_arg("payload");
+ }
+ auto callback_url = get_arg("callback_url");
+
+ if (!bot_user_id.is_valid() || scope.empty() || public_key.empty() || nonce.empty()) {
+ return td::make_unique<InternalLinkUnknownDeepLink>(PSTRING() << "tg://" << query);
+ }
+ return td::make_unique<InternalLinkPassportDataRequest>(bot_user_id, scope.str(), public_key.str(), nonce.str(),
+ callback_url.str());
+}
+
+void LinkManager::update_autologin_domains(string autologin_token, vector<string> autologin_domains,
+ vector<string> url_auth_domains, vector<string> whitelisted_domains) {
+ autologin_update_time_ = Time::now();
+ autologin_token_ = std::move(autologin_token);
+ if (autologin_domains_ != autologin_domains) {
+ autologin_domains_ = std::move(autologin_domains);
+ G()->td_db()->get_binlog_pmc()->set("autologin_domains", implode(autologin_domains_, '\xFF'));
+ }
+ if (url_auth_domains_ != url_auth_domains) {
+ url_auth_domains_ = std::move(url_auth_domains);
+ G()->td_db()->get_binlog_pmc()->set("url_auth_domains", implode(url_auth_domains_, '\xFF'));
+ }
+ if (whitelisted_domains_ != whitelisted_domains) {
+ whitelisted_domains_ = std::move(whitelisted_domains);
+ G()->td_db()->get_binlog_pmc()->set("whitelisted_domains", implode(whitelisted_domains_, '\xFF'));
+ }
+}
+
+void LinkManager::get_deep_link_info(Slice link, Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise) {
+ Slice link_scheme("tg:");
+ if (begins_with(link, link_scheme)) {
+ link.remove_prefix(link_scheme.size());
+ if (begins_with(link, "//")) {
+ link.remove_prefix(2);
+ }
+ }
+ size_t pos = 0;
+ while (pos < link.size() && link[pos] != '/' && link[pos] != '?' && link[pos] != '#') {
+ pos++;
+ }
+ link.truncate(pos);
+ td_->create_handler<GetDeepLinkInfoQuery>(std::move(promise))->send(link);
+}
+
+void LinkManager::get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) {
+ auto default_result = td_api::make_object<td_api::loginUrlInfoOpen>(link, false);
+ if (G()->close_flag()) {
+ return promise.set_value(std::move(default_result));
+ }
+
+ auto r_url = parse_url(link);
+ if (r_url.is_error()) {
+ return promise.set_value(std::move(default_result));
+ }
+
+ bool skip_confirm = td::contains(whitelisted_domains_, r_url.ok().host_);
+ default_result->skip_confirm_ = skip_confirm;
+
+ if (!td::contains(autologin_domains_, r_url.ok().host_)) {
+ if (td::contains(url_auth_domains_, r_url.ok().host_)) {
+ td_->create_handler<RequestUrlAuthQuery>(std::move(promise))->send(link, FullMessageId(), 0);
+ return;
+ }
+ return promise.set_value(std::move(default_result));
+ }
+
+ if (autologin_update_time_ < Time::now() - 10000) {
+ auto query_promise =
+ PromiseCreator::lambda([link = std::move(link), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ return promise.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(link, false));
+ }
+ send_closure(G()->link_manager(), &LinkManager::get_external_link_info, std::move(link), std::move(promise));
+ });
+ return send_closure(G()->config_manager(), &ConfigManager::reget_app_config, std::move(query_promise));
+ }
+
+ if (autologin_token_.empty()) {
+ return promise.set_value(std::move(default_result));
+ }
+
+ auto url = r_url.move_as_ok();
+ url.protocol_ = HttpUrl::Protocol::Https;
+ Slice path = url.query_;
+ path.truncate(url.query_.find_first_of("?#"));
+ Slice parameters_hash = Slice(url.query_).substr(path.size());
+ Slice parameters = parameters_hash;
+ parameters.truncate(parameters.find('#'));
+ Slice hash = parameters_hash.substr(parameters.size());
+
+ string added_parameter;
+ if (parameters.empty()) {
+ added_parameter = '?';
+ } else if (parameters.size() == 1) {
+ CHECK(parameters == "?");
+ } else {
+ added_parameter = '&';
+ }
+ added_parameter += "autologin_token=";
+ added_parameter += autologin_token_;
+
+ url.query_ = PSTRING() << path << parameters << added_parameter << hash;
+
+ promise.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url.get_url(), skip_confirm));
+}
+
+void LinkManager::get_login_url_info(FullMessageId full_message_id, int64 button_id,
+ Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) {
+ TRY_RESULT_PROMISE(promise, url, td_->messages_manager_->get_login_button_url(full_message_id, button_id));
+ td_->create_handler<RequestUrlAuthQuery>(std::move(promise))
+ ->send(std::move(url), full_message_id, narrow_cast<int32>(button_id));
+}
+
+void LinkManager::get_login_url(FullMessageId full_message_id, int64 button_id, bool allow_write_access,
+ Promise<td_api::object_ptr<td_api::httpUrl>> &&promise) {
+ TRY_RESULT_PROMISE(promise, url, td_->messages_manager_->get_login_button_url(full_message_id, button_id));
+ td_->create_handler<AcceptUrlAuthQuery>(std::move(promise))
+ ->send(std::move(url), full_message_id, narrow_cast<int32>(button_id), allow_write_access);
+}
+
+void LinkManager::get_link_login_url(const string &url, bool allow_write_access,
+ Promise<td_api::object_ptr<td_api::httpUrl>> &&promise) {
+ td_->create_handler<AcceptUrlAuthQuery>(std::move(promise))->send(url, FullMessageId(), 0, allow_write_access);
+}
+
+Result<string> LinkManager::get_background_url(const string &name,
+ td_api::object_ptr<td_api::BackgroundType> background_type) {
+ TRY_RESULT(type, BackgroundType::get_background_type(background_type.get()));
+ auto url = PSTRING() << G()->get_option_string("t_me_url", "https://t.me/") << "bg/";
+ auto link = type.get_link();
+ if (type.has_file()) {
+ url += name;
+ if (!link.empty()) {
+ url += '?';
+ url += link;
+ }
+ } else {
+ url += link;
+ }
+ return url;
+}
+
+string LinkManager::get_dialog_invite_link_hash(Slice invite_link) {
+ auto link_info = get_link_info(invite_link);
+ if (link_info.type_ != LinkType::Tg && link_info.type_ != LinkType::TMe) {
+ return string();
+ }
+ const auto url_query = parse_url_query(link_info.query_);
+ return get_url_query_hash(link_info.type_ == LinkType::Tg, url_query);
+}
+
+string LinkManager::get_dialog_invite_link(Slice hash, bool is_internal) {
+ if (!is_base64url_characters(hash)) {
+ return string();
+ }
+ if (is_internal) {
+ return PSTRING() << "tg:join?invite=" << hash;
+ } else {
+ return PSTRING() << G()->get_option_string("t_me_url", "https://t.me/") << '+' << hash;
+ }
+}
+
+string LinkManager::get_instant_view_link_url(Slice link) {
+ auto link_info = get_link_info(link);
+ if (link_info.type_ != LinkType::TMe) {
+ return string();
+ }
+ const auto url_query = parse_url_query(link_info.query_);
+ const auto &path = url_query.path_;
+ if (path.size() == 1 && path[0] == "iv") {
+ return url_query.get_arg("url").str();
+ }
+ return string();
+}
+
+string LinkManager::get_instant_view_link_rhash(Slice link) {
+ auto link_info = get_link_info(link);
+ if (link_info.type_ != LinkType::TMe) {
+ return string();
+ }
+ const auto url_query = parse_url_query(link_info.query_);
+ const auto &path = url_query.path_;
+ if (path.size() == 1 && path[0] == "iv" && !url_query.get_arg("url").empty()) {
+ return url_query.get_arg("rhash").str();
+ }
+ return string();
+}
+
+string LinkManager::get_instant_view_link(Slice url, Slice rhash) {
+ return PSTRING() << G()->get_option_string("t_me_url", "https://t.me/") << "iv?url=" << url_encode(url)
+ << "&rhash=" << url_encode(rhash);
+}
+
+UserId LinkManager::get_link_user_id(Slice url) {
+ string lower_cased_url = to_lower(url);
+ url = lower_cased_url;
+
+ Slice link_scheme("tg:");
+ if (!begins_with(url, link_scheme)) {
+ return UserId();
+ }
+ url.remove_prefix(link_scheme.size());
+ if (begins_with(url, "//")) {
+ url.remove_prefix(2);
+ }
+
+ Slice host("user");
+ if (!begins_with(url, host) || (url.size() > host.size() && Slice("/?#").find(url[host.size()]) == Slice::npos)) {
+ return UserId();
+ }
+ url.remove_prefix(host.size());
+ if (begins_with(url, "/")) {
+ url.remove_prefix(1);
+ }
+ if (!begins_with(url, "?")) {
+ return UserId();
+ }
+ url.remove_prefix(1);
+ url.truncate(url.find('#'));
+
+ for (auto parameter : full_split(url, '&')) {
+ Slice key;
+ Slice value;
+ std::tie(key, value) = split(parameter, '=');
+ if (key == Slice("id")) {
+ auto r_user_id = to_integer_safe<int64>(value);
+ if (r_user_id.is_error()) {
+ return UserId();
+ }
+ return UserId(r_user_id.ok());
+ }
+ }
+ return UserId();
+}
+
+Result<CustomEmojiId> LinkManager::get_link_custom_emoji_id(Slice url) {
+ string lower_cased_url = to_lower(url);
+ url = lower_cased_url;
+
+ Slice link_scheme("tg:");
+ if (!begins_with(url, link_scheme)) {
+ return Status::Error(400, "Custom emoji URL must have scheme tg");
+ }
+ url.remove_prefix(link_scheme.size());
+ if (begins_with(url, "//")) {
+ url.remove_prefix(2);
+ }
+
+ Slice host("emoji");
+ if (!begins_with(url, host) || (url.size() > host.size() && Slice("/?#").find(url[host.size()]) == Slice::npos)) {
+ return Status::Error(400, PSLICE() << "Custom emoji URL must have host \"" << host << '"');
+ }
+ url.remove_prefix(host.size());
+ if (begins_with(url, "/")) {
+ url.remove_prefix(1);
+ }
+ if (!begins_with(url, "?")) {
+ return Status::Error(400, "Custom emoji URL must have an emoji identifier");
+ }
+ url.remove_prefix(1);
+ url.truncate(url.find('#'));
+
+ for (auto parameter : full_split(url, '&')) {
+ Slice key;
+ Slice value;
+ std::tie(key, value) = split(parameter, '=');
+ if (key == Slice("id")) {
+ auto r_document_id = to_integer_safe<int64>(value);
+ if (r_document_id.is_error() || r_document_id.ok() == 0) {
+ return Status::Error(400, "Invalid custom emoji identifier specified");
+ }
+ return CustomEmojiId(r_document_id.ok());
+ }
+ }
+ return Status::Error(400, "Custom emoji URL must have an emoji identifier");
+}
+
+Result<MessageLinkInfo> LinkManager::get_message_link_info(Slice url) {
+ if (url.empty()) {
+ return Status::Error("URL must be non-empty");
+ }
+ auto link_info = get_link_info(url);
+ if (link_info.type_ != LinkType::Tg && link_info.type_ != LinkType::TMe) {
+ return Status::Error("Invalid message link URL");
+ }
+ url = link_info.query_;
+
+ Slice username;
+ Slice channel_id_slice;
+ Slice message_id_slice;
+ Slice comment_message_id_slice = "0";
+ Slice top_thread_message_id_slice;
+ Slice media_timestamp_slice;
+ bool is_single = false;
+ bool for_comment = false;
+ if (link_info.type_ == LinkType::Tg) {
+ // resolve?domain=username&post=12345&single&t=123&comment=12&thread=21
+ // privatepost?channel=123456789&post=12345&single&t=123&comment=12&thread=21
+
+ bool is_resolve = false;
+ if (begins_with(url, "resolve")) {
+ url = url.substr(7);
+ is_resolve = true;
+ } else if (begins_with(url, "privatepost")) {
+ url = url.substr(11);
+ } else {
+ return Status::Error("Wrong message link URL");
+ }
+
+ if (begins_with(url, "/")) {
+ url = url.substr(1);
+ }
+ if (!begins_with(url, "?")) {
+ return Status::Error("Wrong message link URL");
+ }
+ url = url.substr(1);
+
+ auto args = full_split(url, '&');
+ for (auto arg : args) {
+ auto key_value = split(arg, '=');
+ if (is_resolve) {
+ if (key_value.first == "domain") {
+ username = key_value.second;
+ }
+ } else {
+ if (key_value.first == "channel") {
+ channel_id_slice = key_value.second;
+ }
+ }
+ if (key_value.first == "post") {
+ message_id_slice = key_value.second;
+ }
+ if (key_value.first == "t") {
+ media_timestamp_slice = key_value.second;
+ }
+ if (key_value.first == "single") {
+ is_single = true;
+ }
+ if (key_value.first == "comment") {
+ comment_message_id_slice = key_value.second;
+ }
+ if (key_value.first == "thread") {
+ for_comment = true;
+ top_thread_message_id_slice = key_value.second;
+ }
+ }
+ } else {
+ // /c/123456789/12345
+ // /c/123456789/1234/12345
+ // /username/12345?single
+
+ CHECK(!url.empty() && url[0] == '/');
+ url.remove_prefix(1);
+
+ auto username_end_pos = url.find('/');
+ if (username_end_pos == Slice::npos) {
+ return Status::Error("Wrong message link URL");
+ }
+ username = url.substr(0, username_end_pos);
+ url = url.substr(username_end_pos + 1);
+ if (username == "c") {
+ username = Slice();
+ auto channel_id_end_pos = url.find('/');
+ if (channel_id_end_pos == Slice::npos) {
+ return Status::Error("Wrong message link URL");
+ }
+ channel_id_slice = url.substr(0, channel_id_end_pos);
+ url = url.substr(channel_id_end_pos + 1);
+ }
+
+ auto query_pos = url.find('?');
+ message_id_slice = url.substr(0, query_pos);
+ if (query_pos != Slice::npos) {
+ auto args = full_split(url.substr(query_pos + 1), '&');
+ for (auto arg : args) {
+ auto key_value = split(arg, '=');
+ if (key_value.first == "t") {
+ media_timestamp_slice = key_value.second;
+ }
+ if (key_value.first == "single") {
+ is_single = true;
+ }
+ if (key_value.first == "comment") {
+ comment_message_id_slice = key_value.second;
+ }
+ if (key_value.first == "thread") {
+ for_comment = true;
+ top_thread_message_id_slice = key_value.second;
+ }
+ }
+ }
+ auto slash_pos = message_id_slice.find('/');
+ if (slash_pos != Slice::npos) {
+ top_thread_message_id_slice = message_id_slice.substr(0, slash_pos);
+ message_id_slice.remove_prefix(slash_pos + 1);
+ }
+ }
+
+ ChannelId channel_id;
+ if (username.empty()) {
+ auto r_channel_id = to_integer_safe<int64>(channel_id_slice);
+ if (r_channel_id.is_error() || !ChannelId(r_channel_id.ok()).is_valid()) {
+ return Status::Error("Wrong channel ID");
+ }
+ channel_id = ChannelId(r_channel_id.ok());
+ }
+
+ auto r_message_id = to_integer_safe<int32>(message_id_slice);
+ if (r_message_id.is_error() || !ServerMessageId(r_message_id.ok()).is_valid()) {
+ return Status::Error("Wrong message ID");
+ }
+
+ int32 top_thread_message_id = 0;
+ if (!top_thread_message_id_slice.empty()) {
+ auto r_top_thread_message_id = to_integer_safe<int32>(top_thread_message_id_slice);
+ if (r_top_thread_message_id.is_error()) {
+ return Status::Error("Wrong message thread ID");
+ }
+ top_thread_message_id = r_top_thread_message_id.ok();
+ if (!ServerMessageId(top_thread_message_id).is_valid()) {
+ return Status::Error("Invalid message thread ID");
+ }
+ }
+
+ auto r_comment_message_id = to_integer_safe<int32>(comment_message_id_slice);
+ if (r_comment_message_id.is_error() ||
+ !(r_comment_message_id.ok() == 0 || ServerMessageId(r_comment_message_id.ok()).is_valid())) {
+ return Status::Error("Wrong comment message ID");
+ }
+
+ bool is_media_timestamp_invalid = false;
+ int32 media_timestamp = 0;
+ const int32 MAX_MEDIA_TIMESTAMP = 10000000;
+ if (!media_timestamp_slice.empty()) {
+ int32 current_value = 0;
+ for (size_t i = 0; i <= media_timestamp_slice.size(); i++) {
+ auto c = i < media_timestamp_slice.size() ? media_timestamp_slice[i] : 's';
+ if ('0' <= c && c <= '9') {
+ current_value = current_value * 10 + c - '0';
+ if (current_value > MAX_MEDIA_TIMESTAMP) {
+ is_media_timestamp_invalid = true;
+ break;
+ }
+ } else {
+ auto mul = 0;
+ switch (to_lower(c)) {
+ case 'h':
+ mul = 3600;
+ break;
+ case 'm':
+ mul = 60;
+ break;
+ case 's':
+ mul = 1;
+ break;
+ }
+ if (mul == 0 || current_value > MAX_MEDIA_TIMESTAMP / mul ||
+ media_timestamp + current_value * mul > MAX_MEDIA_TIMESTAMP) {
+ is_media_timestamp_invalid = true;
+ break;
+ }
+ media_timestamp += current_value * mul;
+ current_value = 0;
+ }
+ }
+ }
+
+ MessageLinkInfo info;
+ info.username = username.str();
+ info.channel_id = channel_id;
+ info.message_id = MessageId(ServerMessageId(r_message_id.ok()));
+ info.comment_message_id = MessageId(ServerMessageId(r_comment_message_id.ok()));
+ info.top_thread_message_id = MessageId(ServerMessageId(top_thread_message_id));
+ info.media_timestamp = is_media_timestamp_invalid ? 0 : media_timestamp;
+ info.is_single = is_single;
+ info.for_comment = for_comment;
+ LOG(INFO) << "Have link to " << info.message_id << " in chat @" << info.username << "/" << channel_id.get();
+ return std::move(info);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/LinkManager.h b/protocols/Telegram/tdlib/td/td/telegram/LinkManager.h
new file mode 100644
index 0000000000..6de87f5018
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/LinkManager.h
@@ -0,0 +1,166 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/MessageLinkInfo.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+#include <utility>
+
+namespace td {
+
+class Td;
+
+class LinkManager final : public Actor {
+ public:
+ LinkManager(Td *td, ActorShared<> parent);
+
+ LinkManager(const LinkManager &) = delete;
+ LinkManager &operator=(const LinkManager &) = delete;
+ LinkManager(LinkManager &&) = delete;
+ LinkManager &operator=(LinkManager &&) = delete;
+ ~LinkManager() final;
+
+ class InternalLink {
+ public:
+ InternalLink() = default;
+ InternalLink(const InternalLink &) = delete;
+ InternalLink &operator=(const InternalLink &) = delete;
+ InternalLink(InternalLink &&) = delete;
+ InternalLink &operator=(InternalLink &&) = delete;
+ virtual ~InternalLink() = default;
+
+ virtual td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const = 0;
+ };
+
+ // checks whether the link is a valid tg, ton or HTTP(S) URL and returns it in a canonical form
+ static Result<string> check_link(CSlice link, bool http_only = false, bool https_only = false);
+
+ // same as check_link, but returns an empty string instead of an error
+ static string get_checked_link(Slice link, bool http_only = false, bool https_only = false);
+
+ // returns whether a link is an internal link, supported or not
+ static bool is_internal_link(Slice link);
+
+ // checks whether the link is a supported tg or t.me link and parses it
+ static unique_ptr<InternalLink> parse_internal_link(Slice link, bool is_trusted = false);
+
+ void update_autologin_domains(string autologin_token, vector<string> autologin_domains,
+ vector<string> url_auth_domains, vector<string> whitelisted_domains);
+
+ void get_deep_link_info(Slice link, Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise);
+
+ void get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise);
+
+ void get_login_url_info(FullMessageId full_message_id, int64 button_id,
+ Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise);
+
+ void get_login_url(FullMessageId full_message_id, int64 button_id, bool allow_write_access,
+ Promise<td_api::object_ptr<td_api::httpUrl>> &&promise);
+
+ void get_link_login_url(const string &url, bool allow_write_access,
+ Promise<td_api::object_ptr<td_api::httpUrl>> &&promise);
+
+ static Result<string> get_background_url(const string &name,
+ td_api::object_ptr<td_api::BackgroundType> background_type);
+
+ static string get_dialog_invite_link_hash(Slice invite_link);
+
+ static string get_dialog_invite_link(Slice hash, bool is_internal);
+
+ static string get_instant_view_link_url(Slice link);
+
+ static string get_instant_view_link_rhash(Slice link);
+
+ static string get_instant_view_link(Slice url, Slice rhash);
+
+ static UserId get_link_user_id(Slice url);
+
+ static Result<CustomEmojiId> get_link_custom_emoji_id(Slice url);
+
+ static Result<MessageLinkInfo> get_message_link_info(Slice url);
+
+ private:
+ void start_up() final;
+
+ void tear_down() final;
+
+ class InternalLinkActiveSessions;
+ class InternalLinkAttachMenuBot;
+ class InternalLinkAuthenticationCode;
+ class InternalLinkBackground;
+ class InternalLinkBotAddToChannel;
+ class InternalLinkBotStart;
+ class InternalLinkBotStartInGroup;
+ class InternalLinkChangePhoneNumber;
+ class InternalLinkConfirmPhone;
+ class InternalLinkDialogInvite;
+ class InternalLinkFilterSettings;
+ class InternalLinkGame;
+ class InternalLinkInstantView;
+ class InternalLinkInvoice;
+ class InternalLinkLanguage;
+ class InternalLinkLanguageSettings;
+ class InternalLinkMessage;
+ class InternalLinkMessageDraft;
+ class InternalLinkPassportDataRequest;
+ class InternalLinkPremiumFeatures;
+ class InternalLinkPrivacyAndSecuritySettings;
+ class InternalLinkProxy;
+ class InternalLinkPublicDialog;
+ class InternalLinkQrCodeAuthentication;
+ class InternalLinkRestorePurchases;
+ class InternalLinkSettings;
+ class InternalLinkStickerSet;
+ class InternalLinkTheme;
+ class InternalLinkThemeSettings;
+ class InternalLinkUnknownDeepLink;
+ class InternalLinkUnsupportedProxy;
+ class InternalLinkUserPhoneNumber;
+ class InternalLinkVoiceChat;
+
+ enum class LinkType : int32 { External, TMe, Tg, Telegraph };
+
+ struct LinkInfo {
+ LinkType type_ = LinkType::External;
+ string query_;
+ };
+ // returns information about the link
+ static LinkInfo get_link_info(Slice link);
+
+ static unique_ptr<InternalLink> parse_tg_link_query(Slice query, bool is_trusted);
+
+ static unique_ptr<InternalLink> parse_t_me_link_query(Slice query, bool is_trusted);
+
+ static unique_ptr<InternalLink> get_internal_link_passport(Slice query,
+ const vector<std::pair<string, string>> &args);
+
+ static unique_ptr<InternalLink> get_internal_link_message_draft(Slice url, Slice text);
+
+ static Result<string> check_link_impl(Slice link, bool http_only, bool https_only);
+
+ Td *td_;
+ ActorShared<> parent_;
+
+ string autologin_token_;
+ vector<string> autologin_domains_;
+ double autologin_update_time_ = 0.0;
+ vector<string> url_auth_domains_;
+ vector<string> whitelisted_domains_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Location.cpp b/protocols/Telegram/tdlib/td/td/telegram/Location.cpp
index 1d5fe0db92..0baa0d1764 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Location.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Location.cpp
@@ -1,36 +1,42 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Location.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/utils/common.h"
-#include "td/utils/logging.h"
-
#include <cmath>
namespace td {
-void Location::init(double latitude, double longitude) {
+double Location::fix_accuracy(double accuracy) {
+ if (!std::isfinite(accuracy) || accuracy <= 0.0) {
+ return 0.0;
+ }
+ if (accuracy >= 1500.0) {
+ return 1500.0;
+ }
+ return accuracy;
+}
+
+void Location::init(double latitude, double longitude, double horizontal_accuracy, int64 access_hash) {
if (std::isfinite(latitude) && std::isfinite(longitude) && std::abs(latitude) <= 90 && std::abs(longitude) <= 180) {
is_empty_ = false;
latitude_ = latitude;
longitude_ = longitude;
+ horizontal_accuracy_ = fix_accuracy(horizontal_accuracy);
+ access_hash_ = access_hash;
+ G()->add_location_access_hash(latitude_, longitude_, access_hash_);
}
}
-Location::Location(double latitude, double longitude) {
- init(latitude, longitude);
+Location::Location(double latitude, double longitude, double horizontal_accuracy, int64 access_hash) {
+ init(latitude, longitude, horizontal_accuracy, access_hash);
}
Location::Location(const tl_object_ptr<secret_api::decryptedMessageMediaGeoPoint> &geo_point)
- : Location(geo_point->lat_, geo_point->long_) {
+ : Location(geo_point->lat_, geo_point->long_, 0.0, 0) {
}
Location::Location(const tl_object_ptr<telegram_api::GeoPoint> &geo_point_ptr) {
@@ -42,7 +48,7 @@ Location::Location(const tl_object_ptr<telegram_api::GeoPoint> &geo_point_ptr) {
break;
case telegram_api::geoPoint::ID: {
auto geo_point = static_cast<const telegram_api::geoPoint *>(geo_point_ptr.get());
- init(geo_point->lat_, geo_point->long_);
+ init(geo_point->lat_, geo_point->long_, geo_point->accuracy_radius_, geo_point->access_hash_);
break;
}
default:
@@ -56,27 +62,37 @@ Location::Location(const tl_object_ptr<td_api::location> &location) {
return;
}
- init(location->latitude_, location->longitude_);
+ init(location->latitude_, location->longitude_, location->horizontal_accuracy_, 0);
}
bool Location::empty() const {
return is_empty_;
}
+bool Location::is_valid_map_point() const {
+ const double MAX_VALID_MAP_LATITUDE = 85.05112877;
+ return !empty() && std::abs(latitude_) <= MAX_VALID_MAP_LATITUDE;
+}
+
tl_object_ptr<td_api::location> Location::get_location_object() const {
if (empty()) {
return nullptr;
}
- return make_tl_object<td_api::location>(latitude_, longitude_);
+ return make_tl_object<td_api::location>(latitude_, longitude_, horizontal_accuracy_);
}
tl_object_ptr<telegram_api::InputGeoPoint> Location::get_input_geo_point() const {
if (empty()) {
- LOG(ERROR) << "Location is empty";
return make_tl_object<telegram_api::inputGeoPointEmpty>();
}
- return make_tl_object<telegram_api::inputGeoPoint>(latitude_, longitude_);
+ int32 flags = 0;
+ if (horizontal_accuracy_ > 0) {
+ flags |= telegram_api::inputGeoPoint::ACCURACY_RADIUS_MASK;
+ }
+
+ return make_tl_object<telegram_api::inputGeoPoint>(flags, latitude_, longitude_,
+ static_cast<int32>(std::ceil(horizontal_accuracy_)));
}
tl_object_ptr<telegram_api::inputMediaGeoPoint> Location::get_input_media_geo_point() const {
@@ -92,7 +108,8 @@ bool operator==(const Location &lhs, const Location &rhs) {
return rhs.is_empty_;
}
return !rhs.is_empty_ && std::abs(lhs.latitude_ - rhs.latitude_) < 1e-6 &&
- std::abs(lhs.longitude_ - rhs.longitude_) < 1e-6;
+ std::abs(lhs.longitude_ - rhs.longitude_) < 1e-6 &&
+ std::abs(lhs.horizontal_accuracy_ - rhs.horizontal_accuracy_) < 1e-6;
}
bool operator!=(const Location &lhs, const Location &rhs) {
@@ -104,72 +121,43 @@ StringBuilder &operator<<(StringBuilder &string_builder, const Location &locatio
return string_builder << "Location[empty]";
}
return string_builder << "Location[latitude = " << location.latitude_ << ", longitude = " << location.longitude_
- << "]";
-}
-
-Venue::Venue(const tl_object_ptr<telegram_api::GeoPoint> &geo_point_ptr, string title, string address, string provider,
- string id)
- : location_(geo_point_ptr)
- , title_(std::move(title))
- , address_(std::move(address))
- , provider_(std::move(provider))
- , id_(std::move(id)) {
+ << ", accuracy = " << location.horizontal_accuracy_ << "]";
}
-Venue::Venue(Location location, string title, string address, string provider, string id)
- : location_(location)
- , title_(std::move(title))
- , address_(std::move(address))
- , provider_(std::move(provider))
- , id_(std::move(id)) {
-}
-
-Venue::Venue(const tl_object_ptr<td_api::venue> &venue)
- : location_(venue->location_)
- , title_(venue->title_)
- , address_(venue->address_)
- , provider_(venue->provider_)
- , id_(venue->id_) {
-}
+Result<InputMessageLocation> process_input_message_location(
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
+ CHECK(input_message_content != nullptr);
+ CHECK(input_message_content->get_id() == td_api::inputMessageLocation::ID);
+ auto input_location = static_cast<const td_api::inputMessageLocation *>(input_message_content.get());
-bool Venue::empty() const {
- return location_.empty();
-}
-
-tl_object_ptr<td_api::venue> Venue::get_venue_object() const {
- return make_tl_object<td_api::venue>(location_.get_location_object(), title_, address_, provider_, id_);
-}
+ Location location(input_location->location_);
+ if (location.empty()) {
+ return Status::Error(400, "Wrong location specified");
+ }
-tl_object_ptr<telegram_api::inputMediaVenue> Venue::get_input_media_venue() const {
- return make_tl_object<telegram_api::inputMediaVenue>(location_.get_input_geo_point(), title_, address_, provider_,
- id_, "");
-}
+ constexpr int32 MIN_LIVE_LOCATION_PERIOD = 60; // seconds, server side limit
+ constexpr int32 MAX_LIVE_LOCATION_PERIOD = 86400; // seconds, server side limit
-SecretInputMedia Venue::get_secret_input_media_venue() const {
- return SecretInputMedia{nullptr,
- make_tl_object<secret_api::decryptedMessageMediaVenue>(
- location_.get_latitude(), location_.get_longitude(), title_, address_, provider_, id_)};
-}
+ auto period = input_location->live_period_;
+ if (period != 0 && (period < MIN_LIVE_LOCATION_PERIOD || period > MAX_LIVE_LOCATION_PERIOD)) {
+ return Status::Error(400, "Wrong live location period specified");
+ }
-tl_object_ptr<telegram_api::inputBotInlineMessageMediaVenue> Venue::get_input_bot_inline_message_media_venue(
- int32 flags, tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) const {
- return make_tl_object<telegram_api::inputBotInlineMessageMediaVenue>(
- flags, location_.get_input_geo_point(), title_, address_, provider_, id_, std::move(reply_markup));
-}
+ constexpr int32 MIN_LIVE_LOCATION_HEADING = 1; // degrees, server side limit
+ constexpr int32 MAX_LIVE_LOCATION_HEADING = 360; // degrees, server side limit
-bool operator==(const Venue &lhs, const Venue &rhs) {
- return lhs.location_ == rhs.location_ && lhs.title_ == rhs.title_ && lhs.address_ == rhs.address_ &&
- lhs.provider_ == rhs.provider_ && lhs.id_ == rhs.id_;
-}
+ auto heading = input_location->heading_;
+ if (heading != 0 && (heading < MIN_LIVE_LOCATION_HEADING || heading > MAX_LIVE_LOCATION_HEADING)) {
+ return Status::Error(400, "Wrong live location heading specified");
+ }
-bool operator!=(const Venue &lhs, const Venue &rhs) {
- return !(lhs == rhs);
-}
+ constexpr int32 MAX_PROXIMITY_ALERT_RADIUS = 100000; // meters, server side limit
+ auto proximity_alert_radius = input_location->proximity_alert_radius_;
+ if (proximity_alert_radius < 0 || proximity_alert_radius > MAX_PROXIMITY_ALERT_RADIUS) {
+ return Status::Error(400, "Wrong live location proximity alert radius specified");
+ }
-StringBuilder &operator<<(StringBuilder &string_builder, const Venue &venue) {
- return string_builder << "Venue[location = " << venue.location_ << ", title = " << venue.title_
- << ", address = " << venue.address_ << ", provider = " << venue.provider_
- << ", id = " << venue.id_ << "]";
+ return InputMessageLocation(std::move(location), period, heading, proximity_alert_radius);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Location.h b/protocols/Telegram/tdlib/td/td/telegram/Location.h
index 3ab7175206..799420bbac 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Location.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Location.h
@@ -1,20 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/common.h"
-#include "td/utils/StringBuilder.h"
-#include "td/utils/tl_helpers.h"
-
+#include "td/telegram/Global.h"
#include "td/telegram/secret_api.h"
+#include "td/telegram/SecretInputMedia.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
-#include "td/telegram/SecretInputMedia.h"
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
namespace td {
@@ -22,18 +23,22 @@ class Location {
bool is_empty_ = true;
double latitude_ = 0.0;
double longitude_ = 0.0;
+ double horizontal_accuracy_ = 0.0;
+ mutable int64 access_hash_ = 0;
friend bool operator==(const Location &lhs, const Location &rhs);
friend bool operator!=(const Location &lhs, const Location &rhs);
friend StringBuilder &operator<<(StringBuilder &string_builder, const Location &location);
- void init(double latitude, double longitude);
+ void init(double latitude, double longitude, double horizontal_accuracy, int64 access_hash);
+
+ static double fix_accuracy(double accuracy);
public:
Location() = default;
- Location(double latitude, double longitude);
+ Location(double latitude, double longitude, double horizontal_accuracy, int64 access_hash);
explicit Location(const tl_object_ptr<secret_api::decryptedMessageMediaGeoPoint> &geo_point);
@@ -43,6 +48,8 @@ class Location {
bool empty() const;
+ bool is_valid_map_point() const;
+
tl_object_ptr<td_api::location> get_location_object() const;
tl_object_ptr<telegram_api::InputGeoPoint> get_input_geo_point() const;
@@ -52,29 +59,60 @@ class Location {
double get_latitude() const {
return latitude_;
}
+
double get_longitude() const {
return longitude_;
}
+
+ int64 get_access_hash() const {
+ return access_hash_;
+ }
+
+ void set_access_hash(int64 access_hash) const {
+ access_hash_ = access_hash;
+ }
+
SecretInputMedia get_secret_input_media_geo_point() const;
template <class StorerT>
void store(StorerT &storer) const {
using td::store;
+ bool has_access_hash = access_hash_ != 0;
+ bool has_horizontal_accuracy = horizontal_accuracy_ > 0.0;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_empty_);
+ STORE_FLAG(has_access_hash);
+ STORE_FLAG(has_horizontal_accuracy);
END_STORE_FLAGS();
store(latitude_, storer);
store(longitude_, storer);
+ if (has_access_hash) {
+ store(access_hash_, storer);
+ }
+ if (has_horizontal_accuracy) {
+ store(horizontal_accuracy_, storer);
+ }
}
template <class ParserT>
void parse(ParserT &parser) {
using td::parse;
+ bool has_access_hash;
+ bool has_horizontal_accuracy;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_empty_);
+ PARSE_FLAG(has_access_hash);
+ PARSE_FLAG(has_horizontal_accuracy);
END_PARSE_FLAGS();
parse(latitude_, parser);
parse(longitude_, parser);
+ if (has_access_hash) {
+ parse(access_hash_, parser);
+ G()->add_location_access_hash(latitude_, longitude_, access_hash_);
+ }
+ if (has_horizontal_accuracy) {
+ parse(horizontal_accuracy_, parser);
+ }
}
};
@@ -83,64 +121,20 @@ bool operator!=(const Location &lhs, const Location &rhs);
StringBuilder &operator<<(StringBuilder &string_builder, const Location &location);
-class Venue {
- Location location_;
- string title_;
- string address_;
- string provider_;
- string id_;
-
- friend bool operator==(const Venue &lhs, const Venue &rhs);
- friend bool operator!=(const Venue &lhs, const Venue &rhs);
-
- friend StringBuilder &operator<<(StringBuilder &string_builder, const Venue &venue);
-
- public:
- Venue() = default;
-
- Venue(const tl_object_ptr<telegram_api::GeoPoint> &geo_point_ptr, string title, string address, string provider,
- string id);
-
- Venue(Location location, string title, string address, string provider, string id);
-
- explicit Venue(const tl_object_ptr<td_api::venue> &venue);
-
- bool empty() const;
-
- tl_object_ptr<td_api::venue> get_venue_object() const;
-
- tl_object_ptr<telegram_api::inputMediaVenue> get_input_media_venue() const;
-
- SecretInputMedia get_secret_input_media_venue() const;
-
- // TODO very strange function
- tl_object_ptr<telegram_api::inputBotInlineMessageMediaVenue> get_input_bot_inline_message_media_venue(
- int32 flags, tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) const;
-
- template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(location_, storer);
- store(title_, storer);
- store(address_, storer);
- store(provider_, storer);
- store(id_, storer);
- }
-
- template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- parse(location_, parser);
- parse(title_, parser);
- parse(address_, parser);
- parse(provider_, parser);
- parse(id_, parser);
+struct InputMessageLocation {
+ Location location;
+ int32 live_period;
+ int32 heading;
+ int32 proximity_alert_radius;
+
+ InputMessageLocation(Location &&location, int32 live_period, int32 heading, int32 proximity_alert_radius)
+ : location(std::move(location))
+ , live_period(live_period)
+ , heading(heading)
+ , proximity_alert_radius(proximity_alert_radius) {
}
};
-
-bool operator==(const Venue &lhs, const Venue &rhs);
-bool operator!=(const Venue &lhs, const Venue &rhs);
-
-StringBuilder &operator<<(StringBuilder &string_builder, const Venue &venue);
+Result<InputMessageLocation> process_input_message_location(
+ td_api::object_ptr<td_api::InputMessageContent> &&input_message_content) TD_WARN_UNUSED_RESULT;
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Log.cpp b/protocols/Telegram/tdlib/td/td/telegram/Log.cpp
index 5aff20a87c..8dbd9f661f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Log.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Log.cpp
@@ -1,36 +1,45 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Log.h"
+#include "td/telegram/Client.h"
+#include "td/telegram/Logging.h"
+#include "td/telegram/td_api.h"
+
#include "td/utils/common.h"
-#include "td/utils/FileLog.h"
-#include "td/utils/logging.h"
-#include "td/utils/Slice.h"
+
+#include <mutex>
namespace td {
-static FileLog file_log;
-static TsLog ts_log(&file_log);
+static std::mutex log_mutex;
+static string log_file_path;
static int64 max_log_file_size = 10 << 20;
static Log::FatalErrorCallbackPtr fatal_error_callback;
-static void fatal_error_callback_wrapper(CSlice message) {
- CHECK(fatal_error_callback != nullptr);
- fatal_error_callback(message.c_str());
+static void fatal_error_callback_wrapper(int verbosity_level, const char *message) {
+ if (verbosity_level == 0) {
+ auto callback = fatal_error_callback;
+ if (callback != nullptr) {
+ callback(message);
+ }
+ }
}
bool Log::set_file_path(string file_path) {
+ std::lock_guard<std::mutex> lock(log_mutex);
if (file_path.empty()) {
- log_interface = default_log_interface;
- return true;
+ log_file_path.clear();
+ return Logging::set_current_stream(td_api::make_object<td_api::logStreamDefault>()).is_ok();
}
- if (file_log.init(file_path, max_log_file_size)) {
- log_interface = &ts_log;
+ if (Logging::set_current_stream(td_api::make_object<td_api::logStreamFile>(file_path, max_log_file_size, true))
+ .is_ok()) {
+ log_file_path = std::move(file_path);
return true;
}
@@ -38,21 +47,25 @@ bool Log::set_file_path(string file_path) {
}
void Log::set_max_file_size(int64 max_file_size) {
- max_log_file_size = max(max_file_size, static_cast<int64>(0));
- file_log.set_rotate_threshold(max_log_file_size);
+ std::lock_guard<std::mutex> lock(log_mutex);
+ max_log_file_size = max(max_file_size, static_cast<int64>(1));
+ Logging::set_current_stream(td_api::make_object<td_api::logStreamFile>(log_file_path, max_log_file_size, true))
+ .ignore();
}
void Log::set_verbosity_level(int new_verbosity_level) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + new_verbosity_level);
+ std::lock_guard<std::mutex> lock(log_mutex);
+ Logging::set_verbosity_level(new_verbosity_level).ignore();
}
void Log::set_fatal_error_callback(FatalErrorCallbackPtr callback) {
+ std::lock_guard<std::mutex> lock(log_mutex);
if (callback == nullptr) {
+ ClientManager::set_log_message_callback(0, nullptr);
fatal_error_callback = nullptr;
- set_log_fatal_error_callback(nullptr);
} else {
fatal_error_callback = callback;
- set_log_fatal_error_callback(fatal_error_callback_wrapper);
+ ClientManager::set_log_message_callback(0, fatal_error_callback_wrapper);
}
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Log.h b/protocols/Telegram/tdlib/td/td/telegram/Log.h
index 7d5e0f4345..7b6f066249 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Log.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Log.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -20,6 +20,8 @@ namespace td {
/**
* Interface for managing the internal logging of TDLib.
* By default TDLib writes logs to stderr or an OS specific log and uses a verbosity level of 5.
+ * These functions are deprecated since TDLib 1.4.0 in favor of the td::td_api::setLogVerbosityLevel,
+ * td::td_api::setLogStream and other synchronous requests for managing the internal TDLib logging.
*/
class Log {
public:
@@ -28,6 +30,7 @@ class Log {
* By default TDLib writes logs to stderr or an OS specific log.
* Use this method to write the log to a file instead.
*
+ * \deprecated Use synchronous td::td_api::setLogStream request instead.
* \param[in] file_path Path to a file where the internal TDLib log will be written. Use an empty path to
* switch back to the default logging behaviour.
* \return True on success, or false otherwise, i.e. if the file can't be opened for writing.
@@ -35,10 +38,11 @@ class Log {
static bool set_file_path(std::string file_path);
/**
- * Sets maximum size of the file to where the internal TDLib log is written before the file will be auto-rotated.
+ * Sets the maximum size of the file to where the internal TDLib log is written before the file will be auto-rotated.
* Unused if log is not written to a file. Defaults to 10 MB.
*
- * \param[in] max_file_size Maximum size of the file to where the internal TDLib log is written before the file
+ * \deprecated Use synchronous td::td_api::setLogStream request instead.
+ * \param[in] max_file_size The maximum size of the file to where the internal TDLib log is written before the file
* will be auto-rotated. Should be positive.
*/
static void set_max_file_size(std::int64_t max_file_size);
@@ -47,6 +51,7 @@ class Log {
* Sets the verbosity level of the internal logging of TDLib.
* By default the TDLib uses a verbosity level of 5 for logging.
*
+ * \deprecated Use synchronous td::td_api::setLogVerbosityLevel request instead.
* \param[in] new_verbosity_level New value of the verbosity level for logging.
* Value 0 corresponds to fatal errors,
* value 1 corresponds to errors,
@@ -71,6 +76,7 @@ class Log {
* The TDLib will crash as soon as callback returns.
* By default the callback is not set.
*
+ * \deprecated Use ClientManager::set_log_message_callback instead.
* \param[in] callback Callback that will be called when a fatal error happens.
* Pass nullptr to remove the callback.
*/
diff --git a/protocols/Telegram/tdlib/td/td/telegram/LogDotNet.cpp b/protocols/Telegram/tdlib/td/td/telegram/LogDotNet.cpp
index 97e33c440a..ede220aa41 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/LogDotNet.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/LogDotNet.cpp
@@ -1,14 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#pragma managed(push, off)
#include "td/telegram/Log.h"
+#pragma managed(pop)
#include "td/utils/port/CxCli.h"
+#pragma managed(push, off)
#include <cstdint>
+#pragma managed(pop)
namespace Telegram {
namespace Td {
@@ -18,6 +22,7 @@ using namespace CxCli;
/// <summary>
/// Class for managing internal TDLib logging.
/// </summary>
+[DEPRECATED_ATTRIBUTE("Telegram.Td.Log class is deprecated, please use Telegram.Td.Api.*Log* requests instead.")]
public ref class Log sealed {
public:
/// <summary>
@@ -27,6 +32,7 @@ public:
/// Value 0 means FATAL, value 1 means ERROR, value 2 means WARNING, value 3 means INFO, value 4 means DEBUG,
/// value greater than 4 can be used to enable even more logging.
/// Default value of the log verbosity level is 5.</param>
+ [DEPRECATED_ATTRIBUTE("SetVerbosityLevel is deprecated, please use Telegram.Td.Api.SetLogVerbosityLevel request instead.")]
static void SetVerbosityLevel(int verbosityLevel) {
::td::Log::set_verbosity_level(verbosityLevel);
}
@@ -38,15 +44,17 @@ public:
/// <param name="filePath">Path to a file for writing TDLib internal log. Use an empty path to switch back to logging
/// to the System.err.</param>
/// <returns>Returns whether opening the log file succeeded.</returns>
+ [DEPRECATED_ATTRIBUTE("SetFilePath is deprecated, please use Telegram.Td.Api.SetLogStream request instead.")]
static bool SetFilePath(String^ filePath) {
return ::td::Log::set_file_path(string_to_unmanaged(filePath));
}
/// <summary>
- /// Changes maximum size of TDLib log file.
+ /// Changes the maximum size of TDLib log file.
/// </summary>
- /// <param name="maxFileSize">Maximum size of the file to where the internal TDLib log is written
+ /// <param name="maxFileSize">The maximum size of the file to where the internal TDLib log is written
/// before the file will be auto-rotated. Must be positive. Defaults to 10 MB.</param>
+ [DEPRECATED_ATTRIBUTE("SetMaxFileSize is deprecated, please use Telegram.Td.Api.SetLogStream request instead.")]
static void SetMaxFileSize(std::int64_t maxFileSize) {
::td::Log::set_max_file_size(maxFileSize);
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Logging.cpp b/protocols/Telegram/tdlib/td/td/telegram/Logging.cpp
new file mode 100644
index 0000000000..8a0232a8b0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Logging.cpp
@@ -0,0 +1,155 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Logging.h"
+
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileGcWorker.h"
+#include "td/telegram/files/FileLoaderUtils.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/net/ConnectionCreator.h"
+#include "td/telegram/net/DcAuthManager.h"
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/NotificationManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/UpdatesManager.h"
+
+#include "td/mtproto/SessionConnection.h"
+#include "td/mtproto/Transport.h"
+
+#include "td/db/binlog/Binlog.h"
+#include "td/db/SqliteStatement.h"
+
+#include "td/net/GetHostByNameActor.h"
+#include "td/net/TransparentProxy.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/ExitGuard.h"
+#include "td/utils/FileLog.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/NullLog.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/TsLog.h"
+
+#include <atomic>
+#include <map>
+#include <mutex>
+
+namespace td {
+
+static std::mutex logging_mutex;
+static FileLog file_log;
+static TsLog ts_log(&file_log);
+static NullLog null_log;
+static ExitGuard exit_guard;
+
+#define ADD_TAG(tag) \
+ { #tag, &VERBOSITY_NAME(tag) }
+static const std::map<Slice, int *> log_tags{
+ ADD_TAG(td_init), ADD_TAG(update_file), ADD_TAG(connections), ADD_TAG(binlog),
+ ADD_TAG(proxy), ADD_TAG(net_query), ADD_TAG(td_requests), ADD_TAG(dc),
+ ADD_TAG(file_loader), ADD_TAG(mtproto), ADD_TAG(raw_mtproto), ADD_TAG(fd),
+ ADD_TAG(actor), ADD_TAG(sqlite), ADD_TAG(notifications), ADD_TAG(get_difference),
+ ADD_TAG(file_gc), ADD_TAG(config_recoverer), ADD_TAG(dns_resolver), ADD_TAG(file_references)};
+#undef ADD_TAG
+
+Status Logging::set_current_stream(td_api::object_ptr<td_api::LogStream> stream) {
+ if (stream == nullptr) {
+ return Status::Error("Log stream must be non-empty");
+ }
+
+ std::lock_guard<std::mutex> lock(logging_mutex);
+ switch (stream->get_id()) {
+ case td_api::logStreamDefault::ID:
+ log_interface = default_log_interface;
+ return Status::OK();
+ case td_api::logStreamFile::ID: {
+ auto file_stream = td_api::move_object_as<td_api::logStreamFile>(stream);
+ auto max_log_file_size = file_stream->max_file_size_;
+ if (max_log_file_size <= 0) {
+ return Status::Error("Max log file size must be positive");
+ }
+ auto redirect_stderr = file_stream->redirect_stderr_;
+
+ TRY_STATUS(file_log.init(file_stream->path_, max_log_file_size, redirect_stderr));
+ std::atomic_thread_fence(std::memory_order_release); // better than nothing
+ log_interface = &ts_log;
+ return Status::OK();
+ }
+ case td_api::logStreamEmpty::ID:
+ log_interface = &null_log;
+ return Status::OK();
+ default:
+ UNREACHABLE();
+ return Status::OK();
+ }
+}
+
+Result<td_api::object_ptr<td_api::LogStream>> Logging::get_current_stream() {
+ std::lock_guard<std::mutex> lock(logging_mutex);
+ if (log_interface == default_log_interface) {
+ return td_api::make_object<td_api::logStreamDefault>();
+ }
+ if (log_interface == &null_log) {
+ return td_api::make_object<td_api::logStreamEmpty>();
+ }
+ if (log_interface == &ts_log) {
+ return td_api::make_object<td_api::logStreamFile>(file_log.get_path().str(), file_log.get_rotate_threshold(),
+ file_log.get_redirect_stderr());
+ }
+ return Status::Error("Log stream is unrecognized");
+}
+
+Status Logging::set_verbosity_level(int new_verbosity_level) {
+ std::lock_guard<std::mutex> lock(logging_mutex);
+ if (0 <= new_verbosity_level && new_verbosity_level <= VERBOSITY_NAME(NEVER)) {
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + new_verbosity_level);
+ return Status::OK();
+ }
+
+ return Status::Error("Wrong new verbosity level specified");
+}
+
+int Logging::get_verbosity_level() {
+ std::lock_guard<std::mutex> lock(logging_mutex);
+ return GET_VERBOSITY_LEVEL();
+}
+
+vector<string> Logging::get_tags() {
+ return transform(log_tags, [](auto &tag) { return tag.first.str(); });
+}
+
+Status Logging::set_tag_verbosity_level(Slice tag, int new_verbosity_level) {
+ auto it = log_tags.find(tag);
+ if (it == log_tags.end()) {
+ return Status::Error("Log tag is not found");
+ }
+
+ std::lock_guard<std::mutex> lock(logging_mutex);
+ *it->second = clamp(new_verbosity_level, 1, VERBOSITY_NAME(NEVER));
+ return Status::OK();
+}
+
+Result<int> Logging::get_tag_verbosity_level(Slice tag) {
+ auto it = log_tags.find(tag);
+ if (it == log_tags.end()) {
+ return Status::Error("Log tag is not found");
+ }
+
+ std::lock_guard<std::mutex> lock(logging_mutex);
+ return *it->second;
+}
+
+void Logging::add_message(int log_verbosity_level, Slice message) {
+ int VERBOSITY_NAME(client) = clamp(log_verbosity_level, 0, VERBOSITY_NAME(NEVER));
+ VLOG(client) << message;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Logging.h b/protocols/Telegram/tdlib/td/td/telegram/Logging.h
new file mode 100644
index 0000000000..b3c569d6e0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Logging.h
@@ -0,0 +1,36 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class Logging {
+ public:
+ static Status set_current_stream(td_api::object_ptr<td_api::LogStream> stream);
+
+ static Result<td_api::object_ptr<td_api::LogStream>> get_current_stream();
+
+ static Status set_verbosity_level(int new_verbosity_level);
+
+ static int get_verbosity_level();
+
+ static vector<string> get_tags();
+
+ static Status set_tag_verbosity_level(Slice tag, int new_verbosity_level);
+
+ static Result<int> get_tag_verbosity_level(Slice tag);
+
+ static void add_message(int log_verbosity_level, Slice message);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageContent.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageContent.cpp
new file mode 100644
index 0000000000..dc31183abd
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageContent.cpp
@@ -0,0 +1,6244 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageContent.h"
+
+#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/AnimationsManager.hpp"
+#include "td/telegram/AudiosManager.h"
+#include "td/telegram/AudiosManager.hpp"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/CallDiscardReason.h"
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
+#include "td/telegram/Contact.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/Dependencies.h"
+#include "td/telegram/DialogAction.h"
+#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/DocumentsManager.hpp"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileId.hpp"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/ForumTopicEditedData.h"
+#include "td/telegram/ForumTopicEditedData.hpp"
+#include "td/telegram/ForumTopicIcon.h"
+#include "td/telegram/ForumTopicIcon.hpp"
+#include "td/telegram/ForumTopicManager.h"
+#include "td/telegram/Game.h"
+#include "td/telegram/Game.hpp"
+#include "td/telegram/Global.h"
+#include "td/telegram/GroupCallManager.h"
+#include "td/telegram/HashtagHints.h"
+#include "td/telegram/InputGroupCallId.h"
+#include "td/telegram/InputInvoice.h"
+#include "td/telegram/InputInvoice.hpp"
+#include "td/telegram/InputMessageText.h"
+#include "td/telegram/Location.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/MessageEntity.hpp"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/MessageSearchFilter.h"
+#include "td/telegram/MessageSender.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/OrderInfo.h"
+#include "td/telegram/OrderInfo.hpp"
+#include "td/telegram/Photo.h"
+#include "td/telegram/Photo.hpp"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/PhotoSizeSource.h"
+#include "td/telegram/PollId.h"
+#include "td/telegram/PollId.hpp"
+#include "td/telegram/PollManager.h"
+#include "td/telegram/secret_api.hpp"
+#include "td/telegram/SecureValue.h"
+#include "td/telegram/SecureValue.hpp"
+#include "td/telegram/StickerFormat.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/StickersManager.hpp"
+#include "td/telegram/StickerType.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TopDialogManager.h"
+#include "td/telegram/UserId.h"
+#include "td/telegram/Venue.h"
+#include "td/telegram/Version.h"
+#include "td/telegram/VideoNotesManager.h"
+#include "td/telegram/VideoNotesManager.hpp"
+#include "td/telegram/VideosManager.h"
+#include "td/telegram/VideosManager.hpp"
+#include "td/telegram/VoiceNotesManager.h"
+#include "td/telegram/VoiceNotesManager.hpp"
+#include "td/telegram/WebPageId.h"
+#include "td/telegram/WebPagesManager.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/MultiPromise.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/emoji.h"
+#include "td/utils/format.h"
+#include "td/utils/HttpUrl.h"
+#include "td/utils/logging.h"
+#include "td/utils/MimeType.h"
+#include "td/utils/misc.h"
+#include "td/utils/PathView.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/utf8.h"
+
+#include <limits>
+#include <utility>
+
+namespace td {
+
+class MessageText final : public MessageContent {
+ public:
+ FormattedText text;
+ WebPageId web_page_id;
+
+ MessageText() = default;
+ MessageText(FormattedText text, WebPageId web_page_id) : text(std::move(text)), web_page_id(web_page_id) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Text;
+ }
+};
+
+class MessageAnimation final : public MessageContent {
+ public:
+ FileId file_id;
+
+ FormattedText caption;
+
+ MessageAnimation() = default;
+ MessageAnimation(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Animation;
+ }
+};
+
+class MessageAudio final : public MessageContent {
+ public:
+ FileId file_id;
+
+ FormattedText caption;
+
+ MessageAudio() = default;
+ MessageAudio(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Audio;
+ }
+};
+
+class MessageDocument final : public MessageContent {
+ public:
+ FileId file_id;
+
+ FormattedText caption;
+
+ MessageDocument() = default;
+ MessageDocument(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Document;
+ }
+};
+
+class MessagePhoto final : public MessageContent {
+ public:
+ Photo photo;
+
+ FormattedText caption;
+
+ MessagePhoto() = default;
+ MessagePhoto(Photo &&photo, FormattedText &&caption) : photo(std::move(photo)), caption(std::move(caption)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Photo;
+ }
+};
+
+class MessageSticker final : public MessageContent {
+ public:
+ FileId file_id;
+ bool is_premium = false;
+
+ MessageSticker() = default;
+ MessageSticker(FileId file_id, bool is_premium) : file_id(file_id), is_premium(is_premium) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Sticker;
+ }
+};
+
+class MessageVideo final : public MessageContent {
+ public:
+ FileId file_id;
+
+ FormattedText caption;
+
+ MessageVideo() = default;
+ MessageVideo(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Video;
+ }
+};
+
+class MessageVoiceNote final : public MessageContent {
+ public:
+ FileId file_id;
+
+ FormattedText caption;
+ bool is_listened;
+
+ MessageVoiceNote() = default;
+ MessageVoiceNote(FileId file_id, FormattedText &&caption, bool is_listened)
+ : file_id(file_id), caption(std::move(caption)), is_listened(is_listened) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::VoiceNote;
+ }
+};
+
+class MessageContact final : public MessageContent {
+ public:
+ Contact contact;
+
+ MessageContact() = default;
+ explicit MessageContact(Contact &&contact) : contact(std::move(contact)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Contact;
+ }
+};
+
+class MessageLocation final : public MessageContent {
+ public:
+ Location location;
+
+ MessageLocation() = default;
+ explicit MessageLocation(Location &&location) : location(std::move(location)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Location;
+ }
+};
+
+class MessageVenue final : public MessageContent {
+ public:
+ Venue venue;
+
+ MessageVenue() = default;
+ explicit MessageVenue(Venue &&venue) : venue(std::move(venue)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Venue;
+ }
+};
+
+class MessageChatCreate final : public MessageContent {
+ public:
+ string title;
+ vector<UserId> participant_user_ids;
+
+ MessageChatCreate() = default;
+ MessageChatCreate(string &&title, vector<UserId> &&participant_user_ids)
+ : title(std::move(title)), participant_user_ids(std::move(participant_user_ids)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatCreate;
+ }
+};
+
+class MessageChatChangeTitle final : public MessageContent {
+ public:
+ string title;
+
+ MessageChatChangeTitle() = default;
+ explicit MessageChatChangeTitle(string &&title) : title(std::move(title)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatChangeTitle;
+ }
+};
+
+class MessageChatChangePhoto final : public MessageContent {
+ public:
+ Photo photo;
+
+ MessageChatChangePhoto() = default;
+ explicit MessageChatChangePhoto(Photo &&photo) : photo(std::move(photo)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatChangePhoto;
+ }
+};
+
+class MessageChatDeletePhoto final : public MessageContent {
+ public:
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatDeletePhoto;
+ }
+};
+
+class MessageChatDeleteHistory final : public MessageContent {
+ public:
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatDeleteHistory;
+ }
+};
+
+class MessageChatAddUsers final : public MessageContent {
+ public:
+ vector<UserId> user_ids;
+
+ MessageChatAddUsers() = default;
+ explicit MessageChatAddUsers(vector<UserId> &&user_ids) : user_ids(std::move(user_ids)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatAddUsers;
+ }
+};
+
+class MessageChatJoinedByLink final : public MessageContent {
+ public:
+ bool is_approved = false;
+
+ MessageChatJoinedByLink() = default;
+ explicit MessageChatJoinedByLink(bool is_approved) : is_approved(is_approved) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatJoinedByLink;
+ }
+};
+
+class MessageChatDeleteUser final : public MessageContent {
+ public:
+ UserId user_id;
+
+ MessageChatDeleteUser() = default;
+ explicit MessageChatDeleteUser(UserId user_id) : user_id(user_id) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatDeleteUser;
+ }
+};
+
+class MessageChatMigrateTo final : public MessageContent {
+ public:
+ ChannelId migrated_to_channel_id;
+
+ MessageChatMigrateTo() = default;
+ explicit MessageChatMigrateTo(ChannelId migrated_to_channel_id) : migrated_to_channel_id(migrated_to_channel_id) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatMigrateTo;
+ }
+};
+
+class MessageChannelCreate final : public MessageContent {
+ public:
+ string title;
+
+ MessageChannelCreate() = default;
+ explicit MessageChannelCreate(string &&title) : title(std::move(title)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChannelCreate;
+ }
+};
+
+class MessageChannelMigrateFrom final : public MessageContent {
+ public:
+ string title;
+ ChatId migrated_from_chat_id;
+
+ MessageChannelMigrateFrom() = default;
+ MessageChannelMigrateFrom(string &&title, ChatId migrated_from_chat_id)
+ : title(std::move(title)), migrated_from_chat_id(migrated_from_chat_id) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChannelMigrateFrom;
+ }
+};
+
+class MessagePinMessage final : public MessageContent {
+ public:
+ MessageId message_id;
+
+ MessagePinMessage() = default;
+ explicit MessagePinMessage(MessageId message_id) : message_id(message_id) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::PinMessage;
+ }
+};
+
+class MessageGame final : public MessageContent {
+ public:
+ Game game;
+
+ MessageGame() = default;
+ explicit MessageGame(Game &&game) : game(std::move(game)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Game;
+ }
+};
+
+class MessageGameScore final : public MessageContent {
+ public:
+ MessageId game_message_id;
+ int64 game_id;
+ int32 score;
+
+ MessageGameScore() = default;
+ MessageGameScore(MessageId game_message_id, int64 game_id, int32 score)
+ : game_message_id(game_message_id), game_id(game_id), score(score) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::GameScore;
+ }
+};
+
+class MessageScreenshotTaken final : public MessageContent {
+ public:
+ MessageContentType get_type() const final {
+ return MessageContentType::ScreenshotTaken;
+ }
+};
+
+class MessageChatSetTtl final : public MessageContent {
+ public:
+ int32 ttl;
+
+ MessageChatSetTtl() = default;
+ explicit MessageChatSetTtl(int32 ttl) : ttl(ttl) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatSetTtl;
+ }
+};
+
+class MessageUnsupported final : public MessageContent {
+ public:
+ static constexpr int32 CURRENT_VERSION = 14;
+ int32 version = CURRENT_VERSION;
+
+ MessageUnsupported() = default;
+ explicit MessageUnsupported(int32 version) : version(version) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Unsupported;
+ }
+};
+
+class MessageCall final : public MessageContent {
+ public:
+ int64 call_id;
+ int32 duration;
+ CallDiscardReason discard_reason;
+ bool is_video;
+
+ MessageCall() = default;
+ MessageCall(int64 call_id, int32 duration, CallDiscardReason discard_reason, bool is_video)
+ : call_id(call_id), duration(duration), discard_reason(discard_reason), is_video(is_video) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Call;
+ }
+};
+
+class MessageInvoice final : public MessageContent {
+ public:
+ InputInvoice input_invoice;
+
+ MessageInvoice() = default;
+ explicit MessageInvoice(InputInvoice &&input_invoice) : input_invoice(std::move(input_invoice)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Invoice;
+ }
+};
+
+class MessagePaymentSuccessful final : public MessageContent {
+ public:
+ DialogId invoice_dialog_id;
+ MessageId invoice_message_id;
+ string currency;
+ int64 total_amount = 0;
+ string invoice_payload; // or invoice_slug for users
+ bool is_recurring = false;
+ bool is_first_recurring = false;
+
+ // bots only part
+ string shipping_option_id;
+ unique_ptr<OrderInfo> order_info;
+ string telegram_payment_charge_id;
+ string provider_payment_charge_id;
+
+ MessagePaymentSuccessful() = default;
+ MessagePaymentSuccessful(DialogId invoice_dialog_id, MessageId invoice_message_id, string &&currency,
+ int64 total_amount, string &&invoice_payload, bool is_recurring, bool is_first_recurring)
+ : invoice_dialog_id(invoice_dialog_id)
+ , invoice_message_id(invoice_message_id)
+ , currency(std::move(currency))
+ , total_amount(total_amount)
+ , invoice_payload(std::move(invoice_payload))
+ , is_recurring(is_recurring || is_first_recurring)
+ , is_first_recurring(is_first_recurring) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::PaymentSuccessful;
+ }
+};
+
+class MessageVideoNote final : public MessageContent {
+ public:
+ FileId file_id;
+
+ bool is_viewed = false;
+
+ MessageVideoNote() = default;
+ MessageVideoNote(FileId file_id, bool is_viewed) : file_id(file_id), is_viewed(is_viewed) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::VideoNote;
+ }
+};
+
+class MessageContactRegistered final : public MessageContent {
+ public:
+ MessageContentType get_type() const final {
+ return MessageContentType::ContactRegistered;
+ }
+};
+
+class MessageExpiredPhoto final : public MessageContent {
+ public:
+ MessageExpiredPhoto() = default;
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ExpiredPhoto;
+ }
+};
+
+class MessageExpiredVideo final : public MessageContent {
+ public:
+ MessageExpiredVideo() = default;
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ExpiredVideo;
+ }
+};
+
+class MessageLiveLocation final : public MessageContent {
+ public:
+ Location location;
+ int32 period = 0;
+ int32 heading = 0;
+ int32 proximity_alert_radius = 0;
+
+ MessageLiveLocation() = default;
+ MessageLiveLocation(Location &&location, int32 period, int32 heading, int32 proximity_alert_radius)
+ : location(std::move(location))
+ , period(period)
+ , heading(heading)
+ , proximity_alert_radius(proximity_alert_radius) {
+ if (period < 0) {
+ this->period = 0;
+ }
+ if (heading < 0 || heading > 360) {
+ LOG(ERROR) << "Receive wrong heading " << heading;
+ this->heading = 0;
+ }
+ if (proximity_alert_radius < 0) {
+ this->proximity_alert_radius = 0;
+ }
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::LiveLocation;
+ }
+};
+
+class MessageCustomServiceAction final : public MessageContent {
+ public:
+ string message;
+
+ MessageCustomServiceAction() = default;
+ explicit MessageCustomServiceAction(string &&message) : message(std::move(message)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::CustomServiceAction;
+ }
+};
+
+class MessageWebsiteConnected final : public MessageContent {
+ public:
+ string domain_name;
+
+ MessageWebsiteConnected() = default;
+ explicit MessageWebsiteConnected(string &&domain_name) : domain_name(std::move(domain_name)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::WebsiteConnected;
+ }
+};
+
+class MessagePassportDataSent final : public MessageContent {
+ public:
+ vector<SecureValueType> types;
+
+ MessagePassportDataSent() = default;
+ explicit MessagePassportDataSent(vector<SecureValueType> &&types) : types(std::move(types)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::PassportDataSent;
+ }
+};
+
+class MessagePassportDataReceived final : public MessageContent {
+ public:
+ vector<EncryptedSecureValue> values;
+ EncryptedSecureCredentials credentials;
+
+ MessagePassportDataReceived() = default;
+ MessagePassportDataReceived(vector<EncryptedSecureValue> &&values, EncryptedSecureCredentials &&credentials)
+ : values(std::move(values)), credentials(std::move(credentials)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::PassportDataReceived;
+ }
+};
+
+class MessagePoll final : public MessageContent {
+ public:
+ PollId poll_id;
+
+ MessagePoll() = default;
+ explicit MessagePoll(PollId poll_id) : poll_id(poll_id) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Poll;
+ }
+};
+
+class MessageDice final : public MessageContent {
+ public:
+ string emoji;
+ int32 dice_value = 0;
+
+ static constexpr const char *DEFAULT_EMOJI = "🎲";
+
+ MessageDice() = default;
+ MessageDice(const string &emoji, int32 dice_value)
+ : emoji(emoji.empty() ? string(DEFAULT_EMOJI) : remove_emoji_modifiers(emoji)), dice_value(dice_value) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::Dice;
+ }
+
+ bool is_valid() const {
+ if (dice_value < 0) {
+ return false;
+ }
+ if (emoji == DEFAULT_EMOJI || emoji == "🎯") {
+ return dice_value <= 6;
+ }
+ return dice_value <= 1000;
+ }
+};
+
+constexpr const char *MessageDice::DEFAULT_EMOJI;
+
+class MessageProximityAlertTriggered final : public MessageContent {
+ public:
+ DialogId traveler_dialog_id;
+ DialogId watcher_dialog_id;
+ int32 distance = 0;
+
+ MessageProximityAlertTriggered() = default;
+ MessageProximityAlertTriggered(DialogId traveler_dialog_id, DialogId watcher_dialog_id, int32 distance)
+ : traveler_dialog_id(traveler_dialog_id), watcher_dialog_id(watcher_dialog_id), distance(distance) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ProximityAlertTriggered;
+ }
+};
+
+class MessageGroupCall final : public MessageContent {
+ public:
+ InputGroupCallId input_group_call_id;
+ int32 duration = -1;
+ int32 schedule_date = -1;
+
+ MessageGroupCall() = default;
+ MessageGroupCall(InputGroupCallId input_group_call_id, int32 duration, int32 schedule_date)
+ : input_group_call_id(input_group_call_id), duration(duration), schedule_date(schedule_date) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::GroupCall;
+ }
+};
+
+class MessageInviteToGroupCall final : public MessageContent {
+ public:
+ InputGroupCallId input_group_call_id;
+ vector<UserId> user_ids;
+
+ MessageInviteToGroupCall() = default;
+ MessageInviteToGroupCall(InputGroupCallId input_group_call_id, vector<UserId> &&user_ids)
+ : input_group_call_id(input_group_call_id), user_ids(std::move(user_ids)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::InviteToGroupCall;
+ }
+};
+
+class MessageChatSetTheme final : public MessageContent {
+ public:
+ string emoji;
+
+ MessageChatSetTheme() = default;
+ explicit MessageChatSetTheme(string &&emoji) : emoji(std::move(emoji)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::ChatSetTheme;
+ }
+};
+
+class MessageWebViewDataSent final : public MessageContent {
+ public:
+ string button_text;
+
+ MessageWebViewDataSent() = default;
+ explicit MessageWebViewDataSent(string &&button_text) : button_text(std::move(button_text)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::WebViewDataSent;
+ }
+};
+
+class MessageWebViewDataReceived final : public MessageContent {
+ public:
+ string button_text;
+ string data;
+
+ MessageWebViewDataReceived() = default;
+ MessageWebViewDataReceived(string &&button_text, string &&data)
+ : button_text(std::move(button_text)), data(std::move(data)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::WebViewDataReceived;
+ }
+};
+
+class MessageGiftPremium final : public MessageContent {
+ public:
+ string currency;
+ int64 amount = 0;
+ int32 months = 0;
+
+ MessageGiftPremium() = default;
+ MessageGiftPremium(string &&currency, int64 amount, int32 months)
+ : currency(std::move(currency)), amount(amount), months(months) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::GiftPremium;
+ }
+};
+
+class MessageTopicCreate final : public MessageContent {
+ public:
+ string title;
+ ForumTopicIcon icon;
+
+ MessageTopicCreate() = default;
+ MessageTopicCreate(string &&title, ForumTopicIcon &&icon) : title(std::move(title)), icon(std::move(icon)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::TopicCreate;
+ }
+};
+
+class MessageTopicEdit final : public MessageContent {
+ public:
+ ForumTopicEditedData edited_data;
+
+ MessageTopicEdit() = default;
+ explicit MessageTopicEdit(ForumTopicEditedData &&edited_data) : edited_data(std::move(edited_data)) {
+ }
+
+ MessageContentType get_type() const final {
+ return MessageContentType::TopicEdit;
+ }
+};
+
+template <class StorerT>
+static void store(const MessageContent *content, StorerT &storer) {
+ CHECK(content != nullptr);
+
+ Td *td = storer.context()->td().get_actor_unsafe();
+ CHECK(td != nullptr);
+
+ auto content_type = content->get_type();
+ store(content_type, storer);
+
+ switch (content_type) {
+ case MessageContentType::Animation: {
+ const auto *m = static_cast<const MessageAnimation *>(content);
+ td->animations_manager_->store_animation(m->file_id, storer);
+ store(m->caption, storer);
+ break;
+ }
+ case MessageContentType::Audio: {
+ const auto *m = static_cast<const MessageAudio *>(content);
+ td->audios_manager_->store_audio(m->file_id, storer);
+ store(m->caption, storer);
+ store(true, storer);
+ break;
+ }
+ case MessageContentType::Contact: {
+ const auto *m = static_cast<const MessageContact *>(content);
+ store(m->contact, storer);
+ break;
+ }
+ case MessageContentType::Document: {
+ const auto *m = static_cast<const MessageDocument *>(content);
+ td->documents_manager_->store_document(m->file_id, storer);
+ store(m->caption, storer);
+ break;
+ }
+ case MessageContentType::Game: {
+ const auto *m = static_cast<const MessageGame *>(content);
+ store(m->game, storer);
+ break;
+ }
+ case MessageContentType::Invoice: {
+ const auto *m = static_cast<const MessageInvoice *>(content);
+ store(m->input_invoice, storer);
+ break;
+ }
+ case MessageContentType::LiveLocation: {
+ const auto *m = static_cast<const MessageLiveLocation *>(content);
+ store(m->location, storer);
+ store(m->period, storer);
+ store(m->heading, storer);
+ store(m->proximity_alert_radius, storer);
+ break;
+ }
+ case MessageContentType::Location: {
+ const auto *m = static_cast<const MessageLocation *>(content);
+ store(m->location, storer);
+ break;
+ }
+ case MessageContentType::Photo: {
+ const auto *m = static_cast<const MessagePhoto *>(content);
+ store(m->photo, storer);
+ store(m->caption, storer);
+ break;
+ }
+ case MessageContentType::Sticker: {
+ const auto *m = static_cast<const MessageSticker *>(content);
+ td->stickers_manager_->store_sticker(m->file_id, false, storer, "MessageSticker");
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(m->is_premium);
+ END_STORE_FLAGS();
+ break;
+ }
+ case MessageContentType::Text: {
+ const auto *m = static_cast<const MessageText *>(content);
+ store(m->text, storer);
+ store(m->web_page_id, storer);
+ break;
+ }
+ case MessageContentType::Unsupported: {
+ const auto *m = static_cast<const MessageUnsupported *>(content);
+ store(m->version, storer);
+ break;
+ }
+ case MessageContentType::Venue: {
+ const auto *m = static_cast<const MessageVenue *>(content);
+ store(m->venue, storer);
+ break;
+ }
+ case MessageContentType::Video: {
+ const auto *m = static_cast<const MessageVideo *>(content);
+ td->videos_manager_->store_video(m->file_id, storer);
+ store(m->caption, storer);
+ break;
+ }
+ case MessageContentType::VideoNote: {
+ const auto *m = static_cast<const MessageVideoNote *>(content);
+ td->video_notes_manager_->store_video_note(m->file_id, storer);
+ store(m->is_viewed, storer);
+ break;
+ }
+ case MessageContentType::VoiceNote: {
+ const auto *m = static_cast<const MessageVoiceNote *>(content);
+ td->voice_notes_manager_->store_voice_note(m->file_id, storer);
+ store(m->caption, storer);
+ store(m->is_listened, storer);
+ break;
+ }
+ case MessageContentType::ChatCreate: {
+ const auto *m = static_cast<const MessageChatCreate *>(content);
+ store(m->title, storer);
+ store(m->participant_user_ids, storer);
+ break;
+ }
+ case MessageContentType::ChatChangeTitle: {
+ const auto *m = static_cast<const MessageChatChangeTitle *>(content);
+ store(m->title, storer);
+ break;
+ }
+ case MessageContentType::ChatChangePhoto: {
+ const auto *m = static_cast<const MessageChatChangePhoto *>(content);
+ store(m->photo, storer);
+ break;
+ }
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ break;
+ case MessageContentType::ChatAddUsers: {
+ const auto *m = static_cast<const MessageChatAddUsers *>(content);
+ store(m->user_ids, storer);
+ break;
+ }
+ case MessageContentType::ChatJoinedByLink: {
+ auto m = static_cast<const MessageChatJoinedByLink *>(content);
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(m->is_approved);
+ END_STORE_FLAGS();
+ break;
+ }
+ case MessageContentType::ChatDeleteUser: {
+ const auto *m = static_cast<const MessageChatDeleteUser *>(content);
+ store(m->user_id, storer);
+ break;
+ }
+ case MessageContentType::ChatMigrateTo: {
+ const auto *m = static_cast<const MessageChatMigrateTo *>(content);
+ store(m->migrated_to_channel_id, storer);
+ break;
+ }
+ case MessageContentType::ChannelCreate: {
+ const auto *m = static_cast<const MessageChannelCreate *>(content);
+ store(m->title, storer);
+ break;
+ }
+ case MessageContentType::ChannelMigrateFrom: {
+ const auto *m = static_cast<const MessageChannelMigrateFrom *>(content);
+ store(m->title, storer);
+ store(m->migrated_from_chat_id, storer);
+ break;
+ }
+ case MessageContentType::PinMessage: {
+ const auto *m = static_cast<const MessagePinMessage *>(content);
+ store(m->message_id, storer);
+ break;
+ }
+ case MessageContentType::GameScore: {
+ const auto *m = static_cast<const MessageGameScore *>(content);
+ store(m->game_message_id, storer);
+ store(m->game_id, storer);
+ store(m->score, storer);
+ break;
+ }
+ case MessageContentType::ScreenshotTaken:
+ break;
+ case MessageContentType::ChatSetTtl: {
+ const auto *m = static_cast<const MessageChatSetTtl *>(content);
+ store(m->ttl, storer);
+ break;
+ }
+ case MessageContentType::Call: {
+ const auto *m = static_cast<const MessageCall *>(content);
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(m->is_video);
+ END_STORE_FLAGS();
+ store(m->call_id, storer);
+ store(m->duration, storer);
+ store(m->discard_reason, storer);
+ break;
+ }
+ case MessageContentType::PaymentSuccessful: {
+ const auto *m = static_cast<const MessagePaymentSuccessful *>(content);
+ bool has_payload = !m->invoice_payload.empty();
+ bool has_shipping_option_id = !m->shipping_option_id.empty();
+ bool has_order_info = m->order_info != nullptr;
+ bool has_telegram_payment_charge_id = !m->telegram_payment_charge_id.empty();
+ bool has_provider_payment_charge_id = !m->provider_payment_charge_id.empty();
+ bool has_invoice_message_id = m->invoice_message_id.is_valid();
+ bool is_correctly_stored = true;
+ bool has_invoice_dialog_id = m->invoice_dialog_id.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_payload);
+ STORE_FLAG(has_shipping_option_id);
+ STORE_FLAG(has_order_info);
+ STORE_FLAG(has_telegram_payment_charge_id);
+ STORE_FLAG(has_provider_payment_charge_id);
+ STORE_FLAG(has_invoice_message_id);
+ STORE_FLAG(is_correctly_stored);
+ STORE_FLAG(has_invoice_dialog_id);
+ STORE_FLAG(m->is_recurring);
+ STORE_FLAG(m->is_first_recurring);
+ END_STORE_FLAGS();
+ store(m->currency, storer);
+ store(m->total_amount, storer);
+ if (has_payload) {
+ store(m->invoice_payload, storer);
+ }
+ if (has_shipping_option_id) {
+ store(m->shipping_option_id, storer);
+ }
+ if (has_order_info) {
+ store(m->order_info, storer);
+ }
+ if (has_telegram_payment_charge_id) {
+ store(m->telegram_payment_charge_id, storer);
+ }
+ if (has_provider_payment_charge_id) {
+ store(m->provider_payment_charge_id, storer);
+ }
+ if (has_invoice_message_id) {
+ store(m->invoice_message_id, storer);
+ }
+ if (has_invoice_dialog_id) {
+ store(m->invoice_dialog_id, storer);
+ }
+ break;
+ }
+ case MessageContentType::ContactRegistered:
+ break;
+ case MessageContentType::ExpiredPhoto:
+ break;
+ case MessageContentType::ExpiredVideo:
+ break;
+ case MessageContentType::CustomServiceAction: {
+ const auto *m = static_cast<const MessageCustomServiceAction *>(content);
+ store(m->message, storer);
+ break;
+ }
+ case MessageContentType::WebsiteConnected: {
+ const auto *m = static_cast<const MessageWebsiteConnected *>(content);
+ store(m->domain_name, storer);
+ break;
+ }
+ case MessageContentType::PassportDataSent: {
+ const auto *m = static_cast<const MessagePassportDataSent *>(content);
+ store(m->types, storer);
+ break;
+ }
+ case MessageContentType::PassportDataReceived: {
+ const auto *m = static_cast<const MessagePassportDataReceived *>(content);
+ store(m->values, storer);
+ store(m->credentials, storer);
+ break;
+ }
+ case MessageContentType::Poll: {
+ const auto *m = static_cast<const MessagePoll *>(content);
+ store(m->poll_id, storer);
+ break;
+ }
+ case MessageContentType::Dice: {
+ const auto *m = static_cast<const MessageDice *>(content);
+ store(m->emoji, storer);
+ store(m->dice_value, storer);
+ break;
+ }
+ case MessageContentType::ProximityAlertTriggered: {
+ const auto *m = static_cast<const MessageProximityAlertTriggered *>(content);
+ store(m->traveler_dialog_id, storer);
+ store(m->watcher_dialog_id, storer);
+ store(m->distance, storer);
+ break;
+ }
+ case MessageContentType::GroupCall: {
+ const auto *m = static_cast<const MessageGroupCall *>(content);
+ bool has_duration = m->duration >= 0;
+ bool has_schedule_date = m->schedule_date > 0;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_duration);
+ STORE_FLAG(has_schedule_date);
+ END_STORE_FLAGS();
+ store(m->input_group_call_id, storer);
+ if (has_duration) {
+ store(m->duration, storer);
+ }
+ if (has_schedule_date) {
+ store(m->schedule_date, storer);
+ }
+ break;
+ }
+ case MessageContentType::InviteToGroupCall: {
+ const auto *m = static_cast<const MessageInviteToGroupCall *>(content);
+ store(m->input_group_call_id, storer);
+ store(m->user_ids, storer);
+ break;
+ }
+ case MessageContentType::ChatSetTheme: {
+ const auto *m = static_cast<const MessageChatSetTheme *>(content);
+ store(m->emoji, storer);
+ break;
+ }
+ case MessageContentType::WebViewDataSent: {
+ const auto *m = static_cast<const MessageWebViewDataSent *>(content);
+ store(m->button_text, storer);
+ break;
+ }
+ case MessageContentType::WebViewDataReceived: {
+ const auto *m = static_cast<const MessageWebViewDataReceived *>(content);
+ store(m->button_text, storer);
+ store(m->data, storer);
+ break;
+ }
+ case MessageContentType::GiftPremium: {
+ const auto *m = static_cast<const MessageGiftPremium *>(content);
+ BEGIN_STORE_FLAGS();
+ END_STORE_FLAGS();
+ store(m->currency, storer);
+ store(m->amount, storer);
+ store(m->months, storer);
+ break;
+ }
+ case MessageContentType::TopicCreate: {
+ const auto *m = static_cast<const MessageTopicCreate *>(content);
+ store(m->title, storer);
+ store(m->icon, storer);
+ break;
+ }
+ case MessageContentType::TopicEdit: {
+ const auto *m = static_cast<const MessageTopicEdit *>(content);
+ store(m->edited_data, storer);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+template <class ParserT>
+static void parse_caption(FormattedText &caption, ParserT &parser) {
+ parse(caption.text, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddCaptionEntities)) {
+ parse(caption.entities, parser);
+ remove_empty_entities(caption.entities);
+ } else {
+ if (!check_utf8(caption.text)) {
+ caption.text.clear();
+ }
+ caption.entities = find_entities(caption.text, false, true);
+ }
+}
+
+template <class ParserT>
+static void parse(unique_ptr<MessageContent> &content, ParserT &parser) {
+ Td *td = parser.context()->td().get_actor_unsafe();
+ CHECK(td != nullptr);
+
+ MessageContentType content_type;
+ parse(content_type, parser);
+
+ bool is_bad = false;
+ switch (content_type) {
+ case MessageContentType::Animation: {
+ auto m = make_unique<MessageAnimation>();
+ m->file_id = td->animations_manager_->parse_animation(parser);
+ parse_caption(m->caption, parser);
+ is_bad = !m->file_id.is_valid();
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Audio: {
+ auto m = make_unique<MessageAudio>();
+ m->file_id = td->audios_manager_->parse_audio(parser);
+ parse_caption(m->caption, parser);
+ bool legacy_is_listened;
+ parse(legacy_is_listened, parser);
+ is_bad = !m->file_id.is_valid();
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Contact: {
+ auto m = make_unique<MessageContact>();
+ parse(m->contact, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Document: {
+ auto m = make_unique<MessageDocument>();
+ m->file_id = td->documents_manager_->parse_document(parser);
+ parse_caption(m->caption, parser);
+ is_bad = !m->file_id.is_valid();
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Game: {
+ auto m = make_unique<MessageGame>();
+ parse(m->game, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Invoice: {
+ auto m = make_unique<MessageInvoice>();
+ parse(m->input_invoice, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::LiveLocation: {
+ auto m = make_unique<MessageLiveLocation>();
+ parse(m->location, parser);
+ parse(m->period, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddLiveLocationHeading)) {
+ parse(m->heading, parser);
+ } else {
+ m->heading = 0;
+ }
+ if (parser.version() >= static_cast<int32>(Version::AddLiveLocationProximityAlertDistance)) {
+ parse(m->proximity_alert_radius, parser);
+ } else {
+ m->proximity_alert_radius = 0;
+ }
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Location: {
+ auto m = make_unique<MessageLocation>();
+ parse(m->location, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Photo: {
+ auto m = make_unique<MessagePhoto>();
+ parse(m->photo, parser);
+ is_bad |= m->photo.is_bad();
+ parse_caption(m->caption, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Sticker: {
+ auto m = make_unique<MessageSticker>();
+ m->file_id = td->stickers_manager_->parse_sticker(false, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddMessageStickerFlags)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(m->is_premium);
+ END_PARSE_FLAGS();
+ }
+ is_bad = !m->file_id.is_valid();
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Text: {
+ auto m = make_unique<MessageText>();
+ parse(m->text, parser);
+ parse(m->web_page_id, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Unsupported: {
+ auto m = make_unique<MessageUnsupported>();
+ if (parser.version() >= static_cast<int32>(Version::AddMessageUnsupportedVersion)) {
+ parse(m->version, parser);
+ } else {
+ m->version = 0;
+ }
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Venue: {
+ auto m = make_unique<MessageVenue>();
+ parse(m->venue, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Video: {
+ auto m = make_unique<MessageVideo>();
+ m->file_id = td->videos_manager_->parse_video(parser);
+ parse_caption(m->caption, parser);
+ is_bad = !m->file_id.is_valid();
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::VideoNote: {
+ auto m = make_unique<MessageVideoNote>();
+ m->file_id = td->video_notes_manager_->parse_video_note(parser);
+ parse(m->is_viewed, parser);
+ is_bad = !m->file_id.is_valid();
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::VoiceNote: {
+ auto m = make_unique<MessageVoiceNote>();
+ m->file_id = td->voice_notes_manager_->parse_voice_note(parser);
+ parse_caption(m->caption, parser);
+ parse(m->is_listened, parser);
+ is_bad = !m->file_id.is_valid();
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChatCreate: {
+ auto m = make_unique<MessageChatCreate>();
+ parse(m->title, parser);
+ parse(m->participant_user_ids, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChatChangeTitle: {
+ auto m = make_unique<MessageChatChangeTitle>();
+ parse(m->title, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChatChangePhoto: {
+ auto m = make_unique<MessageChatChangePhoto>();
+ parse(m->photo, parser);
+ if (m->photo.is_empty()) {
+ is_bad = true;
+ }
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChatDeletePhoto:
+ content = make_unique<MessageChatDeletePhoto>();
+ break;
+ case MessageContentType::ChatDeleteHistory:
+ content = make_unique<MessageChatDeleteHistory>();
+ break;
+ case MessageContentType::ChatAddUsers: {
+ auto m = make_unique<MessageChatAddUsers>();
+ parse(m->user_ids, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChatJoinedByLink: {
+ auto m = make_unique<MessageChatJoinedByLink>();
+ if (parser.version() >= static_cast<int32>(Version::AddInviteLinksRequiringApproval)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(m->is_approved);
+ END_PARSE_FLAGS();
+ } else {
+ m->is_approved = false;
+ }
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChatDeleteUser: {
+ auto m = make_unique<MessageChatDeleteUser>();
+ parse(m->user_id, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChatMigrateTo: {
+ auto m = make_unique<MessageChatMigrateTo>();
+ parse(m->migrated_to_channel_id, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChannelCreate: {
+ auto m = make_unique<MessageChannelCreate>();
+ parse(m->title, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChannelMigrateFrom: {
+ auto m = make_unique<MessageChannelMigrateFrom>();
+ parse(m->title, parser);
+ parse(m->migrated_from_chat_id, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::PinMessage: {
+ auto m = make_unique<MessagePinMessage>();
+ parse(m->message_id, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::GameScore: {
+ auto m = make_unique<MessageGameScore>();
+ parse(m->game_message_id, parser);
+ parse(m->game_id, parser);
+ parse(m->score, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ScreenshotTaken:
+ content = make_unique<MessageScreenshotTaken>();
+ break;
+ case MessageContentType::ChatSetTtl: {
+ auto m = make_unique<MessageChatSetTtl>();
+ parse(m->ttl, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Call: {
+ auto m = make_unique<MessageCall>();
+ if (parser.version() >= static_cast<int32>(Version::AddVideoCallsSupport)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(m->is_video);
+ END_PARSE_FLAGS();
+ } else {
+ m->is_video = false;
+ }
+ parse(m->call_id, parser);
+ parse(m->duration, parser);
+ parse(m->discard_reason, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::PaymentSuccessful: {
+ auto m = make_unique<MessagePaymentSuccessful>();
+ bool has_payload;
+ bool has_shipping_option_id;
+ bool has_order_info;
+ bool has_telegram_payment_charge_id;
+ bool has_provider_payment_charge_id;
+ bool has_invoice_message_id;
+ bool is_correctly_stored;
+ bool has_invoice_dialog_id;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_payload);
+ PARSE_FLAG(has_shipping_option_id);
+ PARSE_FLAG(has_order_info);
+ PARSE_FLAG(has_telegram_payment_charge_id);
+ PARSE_FLAG(has_provider_payment_charge_id);
+ PARSE_FLAG(has_invoice_message_id);
+ PARSE_FLAG(is_correctly_stored);
+ PARSE_FLAG(has_invoice_dialog_id);
+ PARSE_FLAG(m->is_recurring);
+ PARSE_FLAG(m->is_first_recurring);
+ END_PARSE_FLAGS();
+ parse(m->currency, parser);
+ parse(m->total_amount, parser);
+ if (is_correctly_stored) {
+ if (has_payload) {
+ parse(m->invoice_payload, parser);
+ }
+ if (has_shipping_option_id) {
+ parse(m->shipping_option_id, parser);
+ }
+ } else {
+ if (has_payload) {
+ parse(m->total_amount, parser);
+ }
+ if (has_shipping_option_id) {
+ parse(m->invoice_payload, parser);
+ }
+ }
+ if (has_order_info) {
+ parse(m->order_info, parser);
+ }
+ if (has_telegram_payment_charge_id) {
+ parse(m->telegram_payment_charge_id, parser);
+ }
+ if (has_provider_payment_charge_id) {
+ parse(m->provider_payment_charge_id, parser);
+ }
+ if (has_invoice_message_id) {
+ parse(m->invoice_message_id, parser);
+ }
+ if (has_invoice_dialog_id) {
+ parse(m->invoice_dialog_id, parser);
+ }
+ if (is_correctly_stored) {
+ content = std::move(m);
+ } else {
+ content = make_unique<MessageUnsupported>(0);
+ }
+ break;
+ }
+ case MessageContentType::ContactRegistered:
+ content = make_unique<MessageContactRegistered>();
+ break;
+ case MessageContentType::ExpiredPhoto:
+ content = make_unique<MessageExpiredPhoto>();
+ break;
+ case MessageContentType::ExpiredVideo:
+ content = make_unique<MessageExpiredVideo>();
+ break;
+ case MessageContentType::CustomServiceAction: {
+ auto m = make_unique<MessageCustomServiceAction>();
+ parse(m->message, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::WebsiteConnected: {
+ auto m = make_unique<MessageWebsiteConnected>();
+ parse(m->domain_name, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::PassportDataSent: {
+ auto m = make_unique<MessagePassportDataSent>();
+ parse(m->types, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::PassportDataReceived: {
+ auto m = make_unique<MessagePassportDataReceived>();
+ parse(m->values, parser);
+ parse(m->credentials, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Poll: {
+ auto m = make_unique<MessagePoll>();
+ parse(m->poll_id, parser);
+ is_bad = !m->poll_id.is_valid();
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::Dice: {
+ auto m = make_unique<MessageDice>();
+ if (parser.version() >= static_cast<int32>(Version::AddDiceEmoji)) {
+ parse(m->emoji, parser);
+ remove_emoji_modifiers_in_place(m->emoji);
+ } else {
+ m->emoji = MessageDice::DEFAULT_EMOJI;
+ }
+ parse(m->dice_value, parser);
+ is_bad = !m->is_valid();
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ProximityAlertTriggered: {
+ auto m = make_unique<MessageProximityAlertTriggered>();
+ parse(m->traveler_dialog_id, parser);
+ parse(m->watcher_dialog_id, parser);
+ parse(m->distance, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::GroupCall: {
+ auto m = make_unique<MessageGroupCall>();
+ bool has_duration;
+ bool has_schedule_date;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_duration);
+ PARSE_FLAG(has_schedule_date);
+ END_PARSE_FLAGS();
+ parse(m->input_group_call_id, parser);
+ if (has_duration) {
+ parse(m->duration, parser);
+ }
+ if (has_schedule_date) {
+ parse(m->schedule_date, parser);
+ }
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::InviteToGroupCall: {
+ auto m = make_unique<MessageInviteToGroupCall>();
+ parse(m->input_group_call_id, parser);
+ parse(m->user_ids, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::ChatSetTheme: {
+ auto m = make_unique<MessageChatSetTheme>();
+ parse(m->emoji, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::WebViewDataSent: {
+ auto m = make_unique<MessageWebViewDataSent>();
+ parse(m->button_text, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::WebViewDataReceived: {
+ auto m = make_unique<MessageWebViewDataReceived>();
+ parse(m->button_text, parser);
+ parse(m->data, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::GiftPremium: {
+ auto m = make_unique<MessageGiftPremium>();
+ BEGIN_PARSE_FLAGS();
+ END_PARSE_FLAGS();
+ parse(m->currency, parser);
+ parse(m->amount, parser);
+ parse(m->months, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::TopicCreate: {
+ auto m = make_unique<MessageTopicCreate>();
+ parse(m->title, parser);
+ parse(m->icon, parser);
+ content = std::move(m);
+ break;
+ }
+ case MessageContentType::TopicEdit: {
+ auto m = make_unique<MessageTopicEdit>();
+ parse(m->edited_data, parser);
+ content = std::move(m);
+ break;
+ }
+ default:
+ LOG(FATAL) << "Have unknown message content type " << static_cast<int32>(content_type);
+ }
+ if (is_bad) {
+ LOG(ERROR) << "Load a message with an invalid content of type " << content_type;
+ content = make_unique<MessageUnsupported>(0);
+ }
+}
+
+void store_message_content(const MessageContent *content, LogEventStorerCalcLength &storer) {
+ store(content, storer);
+}
+
+void store_message_content(const MessageContent *content, LogEventStorerUnsafe &storer) {
+ store(content, storer);
+}
+
+void parse_message_content(unique_ptr<MessageContent> &content, LogEventParser &parser) {
+ parse(content, parser);
+}
+
+InlineMessageContent create_inline_message_content(Td *td, FileId file_id,
+ tl_object_ptr<telegram_api::BotInlineMessage> &&bot_inline_message,
+ int32 allowed_media_content_id, Photo *photo, Game *game) {
+ CHECK(bot_inline_message != nullptr);
+ CHECK((allowed_media_content_id == td_api::inputMessagePhoto::ID) == (photo != nullptr));
+ CHECK((allowed_media_content_id == td_api::inputMessageGame::ID) == (game != nullptr));
+ CHECK((allowed_media_content_id != td_api::inputMessagePhoto::ID &&
+ allowed_media_content_id != td_api::inputMessageGame::ID && allowed_media_content_id != -1) ==
+ file_id.is_valid());
+
+ InlineMessageContent result;
+ tl_object_ptr<telegram_api::ReplyMarkup> reply_markup;
+ result.disable_web_page_preview = false;
+ switch (bot_inline_message->get_id()) {
+ case telegram_api::botInlineMessageText::ID: {
+ auto inline_message = move_tl_object_as<telegram_api::botInlineMessageText>(bot_inline_message);
+ auto entities = get_message_entities(td->contacts_manager_.get(), std::move(inline_message->entities_),
+ "botInlineMessageText");
+ auto status = fix_formatted_text(inline_message->message_, entities, false, true, true, false, false);
+ if (status.is_error()) {
+ LOG(ERROR) << "Receive error " << status << " while parsing botInlineMessageText " << inline_message->message_;
+ break;
+ }
+
+ result.disable_web_page_preview = inline_message->no_webpage_;
+ FormattedText text{std::move(inline_message->message_), std::move(entities)};
+ WebPageId web_page_id;
+ if (!result.disable_web_page_preview) {
+ web_page_id = td->web_pages_manager_->get_web_page_by_url(get_first_url(text));
+ }
+ result.message_content = make_unique<MessageText>(std::move(text), web_page_id);
+ reply_markup = std::move(inline_message->reply_markup_);
+ break;
+ }
+ case telegram_api::botInlineMessageMediaInvoice::ID: {
+ auto inline_message = move_tl_object_as<telegram_api::botInlineMessageMediaInvoice>(bot_inline_message);
+ reply_markup = std::move(inline_message->reply_markup_);
+ result.message_content = make_unique<MessageInvoice>(InputInvoice(std::move(inline_message), td, DialogId()));
+ break;
+ }
+ case telegram_api::botInlineMessageMediaGeo::ID: {
+ auto inline_message = move_tl_object_as<telegram_api::botInlineMessageMediaGeo>(bot_inline_message);
+ if ((inline_message->flags_ & telegram_api::botInlineMessageMediaGeo::PERIOD_MASK) != 0 &&
+ inline_message->period_ > 0) {
+ auto heading = (inline_message->flags_ & telegram_api::botInlineMessageMediaGeo::HEADING_MASK) != 0
+ ? inline_message->heading_
+ : 0;
+ auto approacing_notification_radius =
+ (inline_message->flags_ & telegram_api::botInlineMessageMediaGeo::PROXIMITY_NOTIFICATION_RADIUS_MASK) != 0
+ ? inline_message->proximity_notification_radius_
+ : 0;
+ result.message_content = make_unique<MessageLiveLocation>(
+ Location(inline_message->geo_), inline_message->period_, heading, approacing_notification_radius);
+ } else {
+ result.message_content = make_unique<MessageLocation>(Location(inline_message->geo_));
+ }
+ reply_markup = std::move(inline_message->reply_markup_);
+ break;
+ }
+ case telegram_api::botInlineMessageMediaVenue::ID: {
+ auto inline_message = move_tl_object_as<telegram_api::botInlineMessageMediaVenue>(bot_inline_message);
+ result.message_content = make_unique<MessageVenue>(
+ Venue(inline_message->geo_, std::move(inline_message->title_), std::move(inline_message->address_),
+ std::move(inline_message->provider_), std::move(inline_message->venue_id_),
+ std::move(inline_message->venue_type_)));
+ reply_markup = std::move(inline_message->reply_markup_);
+ break;
+ }
+ case telegram_api::botInlineMessageMediaContact::ID: {
+ auto inline_message = move_tl_object_as<telegram_api::botInlineMessageMediaContact>(bot_inline_message);
+ result.message_content = make_unique<MessageContact>(
+ Contact(std::move(inline_message->phone_number_), std::move(inline_message->first_name_),
+ std::move(inline_message->last_name_), std::move(inline_message->vcard_), UserId()));
+ reply_markup = std::move(inline_message->reply_markup_);
+ break;
+ }
+ case telegram_api::botInlineMessageMediaAuto::ID: {
+ auto inline_message = move_tl_object_as<telegram_api::botInlineMessageMediaAuto>(bot_inline_message);
+ auto caption =
+ get_message_text(td->contacts_manager_.get(), inline_message->message_, std::move(inline_message->entities_),
+ true, false, 0, false, "create_inline_message_content");
+ if (allowed_media_content_id == td_api::inputMessageAnimation::ID) {
+ result.message_content = make_unique<MessageAnimation>(file_id, std::move(caption));
+ } else if (allowed_media_content_id == td_api::inputMessageAudio::ID) {
+ result.message_content = make_unique<MessageAudio>(file_id, std::move(caption));
+ } else if (allowed_media_content_id == td_api::inputMessageDocument::ID) {
+ result.message_content = make_unique<MessageDocument>(file_id, std::move(caption));
+ } else if (allowed_media_content_id == td_api::inputMessageGame::ID) {
+ CHECK(game != nullptr);
+ // TODO game->set_short_name(std::move(caption));
+ result.message_content = make_unique<MessageGame>(std::move(*game));
+ } else if (allowed_media_content_id == td_api::inputMessagePhoto::ID) {
+ result.message_content = make_unique<MessagePhoto>(std::move(*photo), std::move(caption));
+ } else if (allowed_media_content_id == td_api::inputMessageSticker::ID) {
+ result.message_content = make_unique<MessageSticker>(file_id, false);
+ } else if (allowed_media_content_id == td_api::inputMessageVideo::ID) {
+ result.message_content = make_unique<MessageVideo>(file_id, std::move(caption));
+ } else if (allowed_media_content_id == td_api::inputMessageVoiceNote::ID) {
+ result.message_content = make_unique<MessageVoiceNote>(file_id, std::move(caption), true);
+ } else {
+ LOG(WARNING) << "Unallowed bot inline message " << to_string(inline_message);
+ }
+
+ reply_markup = std::move(inline_message->reply_markup_);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ result.message_reply_markup = get_reply_markup(std::move(reply_markup), td->auth_manager_->is_bot(), true, false);
+ return result;
+}
+
+unique_ptr<MessageContent> create_text_message_content(string text, vector<MessageEntity> entities,
+ WebPageId web_page_id) {
+ return make_unique<MessageText>(FormattedText{std::move(text), std::move(entities)}, web_page_id);
+}
+
+unique_ptr<MessageContent> create_contact_registered_message_content() {
+ return make_unique<MessageContactRegistered>();
+}
+
+unique_ptr<MessageContent> create_screenshot_taken_message_content() {
+ return make_unique<MessageScreenshotTaken>();
+}
+
+unique_ptr<MessageContent> create_chat_set_ttl_message_content(int32 ttl) {
+ return make_unique<MessageChatSetTtl>(ttl);
+}
+
+static Result<InputMessageContent> create_input_message_content(
+ DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content, Td *td,
+ FormattedText caption, FileId file_id, PhotoSize thumbnail, vector<FileId> sticker_file_ids, bool is_premium) {
+ CHECK(input_message_content != nullptr);
+ LOG(INFO) << "Create InputMessageContent with file " << file_id << " and thumbnail " << thumbnail.file_id;
+
+ FileView file_view;
+ string file_name;
+ string mime_type;
+ if (file_id.is_valid()) {
+ file_view = td->file_manager_->get_file_view(file_id);
+ auto suggested_path = file_view.suggested_path();
+ const PathView path_view(suggested_path);
+ file_name = path_view.file_name().str();
+ mime_type = MimeType::from_extension(path_view.extension());
+ }
+
+ bool disable_web_page_preview = false;
+ bool clear_draft = false;
+ unique_ptr<MessageContent> content;
+ UserId via_bot_user_id;
+ int32 ttl = 0;
+ string emoji;
+ bool is_bot = td->auth_manager_->is_bot();
+ switch (input_message_content->get_id()) {
+ case td_api::inputMessageText::ID: {
+ TRY_RESULT(input_message_text,
+ process_input_message_text(td, dialog_id, std::move(input_message_content), is_bot));
+ disable_web_page_preview = input_message_text.disable_web_page_preview;
+ clear_draft = input_message_text.clear_draft;
+
+ if (is_bot && static_cast<int64>(utf8_length(input_message_text.text.text)) >
+ G()->get_option_integer("message_text_length_max")) {
+ return Status::Error(400, "Message is too long");
+ }
+
+ WebPageId web_page_id;
+ bool can_add_web_page_previews =
+ dialog_id.get_type() != DialogType::Channel ||
+ td->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id()).can_add_web_page_previews();
+ if (!is_bot && !disable_web_page_preview && can_add_web_page_previews) {
+ web_page_id = td->web_pages_manager_->get_web_page_by_url(get_first_url(input_message_text.text));
+ }
+ content = make_unique<MessageText>(std::move(input_message_text.text), web_page_id);
+ break;
+ }
+ case td_api::inputMessageAnimation::ID: {
+ auto input_animation = static_cast<td_api::inputMessageAnimation *>(input_message_content.get());
+
+ bool has_stickers = !sticker_file_ids.empty();
+ td->animations_manager_->create_animation(
+ file_id, string(), thumbnail, AnimationSize(), has_stickers, std::move(sticker_file_ids),
+ std::move(file_name), std::move(mime_type), input_animation->duration_,
+ get_dimensions(input_animation->width_, input_animation->height_, nullptr), false);
+
+ content = make_unique<MessageAnimation>(file_id, std::move(caption));
+ break;
+ }
+ case td_api::inputMessageAudio::ID: {
+ auto input_audio = static_cast<td_api::inputMessageAudio *>(input_message_content.get());
+
+ if (!clean_input_string(input_audio->title_)) {
+ return Status::Error(400, "Audio title must be encoded in UTF-8");
+ }
+ if (!clean_input_string(input_audio->performer_)) {
+ return Status::Error(400, "Audio performer must be encoded in UTF-8");
+ }
+
+ td->audios_manager_->create_audio(file_id, string(), thumbnail, std::move(file_name), std::move(mime_type),
+ input_audio->duration_, std::move(input_audio->title_),
+ std::move(input_audio->performer_), 0, false);
+
+ content = make_unique<MessageAudio>(file_id, std::move(caption));
+ break;
+ }
+ case td_api::inputMessageDice::ID: {
+ auto input_dice = static_cast<td_api::inputMessageDice *>(input_message_content.get());
+ if (!clean_input_string(input_dice->emoji_)) {
+ return Status::Error(400, "Dice emoji must be encoded in UTF-8");
+ }
+ content = td::make_unique<MessageDice>(input_dice->emoji_, 0);
+ clear_draft = input_dice->clear_draft_;
+ break;
+ }
+ case td_api::inputMessageDocument::ID:
+ td->documents_manager_->create_document(file_id, string(), thumbnail, std::move(file_name), std::move(mime_type),
+ false);
+
+ content = make_unique<MessageDocument>(file_id, std::move(caption));
+ break;
+ case td_api::inputMessagePhoto::ID: {
+ auto input_photo = static_cast<td_api::inputMessagePhoto *>(input_message_content.get());
+
+ if (input_photo->width_ < 0 || input_photo->width_ > 10000) {
+ return Status::Error(400, "Wrong photo width");
+ }
+ if (input_photo->height_ < 0 || input_photo->height_ > 10000) {
+ return Status::Error(400, "Wrong photo height");
+ }
+ ttl = input_photo->ttl_;
+
+ auto message_photo = make_unique<MessagePhoto>();
+
+ if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
+ message_photo->photo.id = file_view.remote_location().get_id();
+ }
+ if (message_photo->photo.is_empty()) {
+ message_photo->photo.id = 0;
+ }
+ message_photo->photo.date = G()->unix_time();
+ int32 type = 'i';
+ if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
+ auto photo_size_source = file_view.remote_location().get_source();
+ if (photo_size_source.get_type("create_input_message_content") == PhotoSizeSource::Type::Thumbnail) {
+ auto old_type = photo_size_source.thumbnail().thumbnail_type;
+ if (old_type != 't') {
+ type = old_type;
+ }
+ }
+ }
+
+ PhotoSize s;
+ s.type = type;
+ s.dimensions = get_dimensions(input_photo->width_, input_photo->height_, nullptr);
+ auto size = file_view.size();
+ if (size < 0 || size >= 1000000000) {
+ return Status::Error(400, "Wrong photo size");
+ }
+ s.size = static_cast<int32>(size);
+ s.file_id = file_id;
+
+ if (thumbnail.file_id.is_valid()) {
+ message_photo->photo.photos.push_back(std::move(thumbnail));
+ }
+
+ message_photo->photo.photos.push_back(s);
+
+ message_photo->photo.has_stickers = !sticker_file_ids.empty();
+ message_photo->photo.sticker_file_ids = std::move(sticker_file_ids);
+
+ message_photo->caption = std::move(caption);
+
+ content = std::move(message_photo);
+ break;
+ }
+ case td_api::inputMessageSticker::ID: {
+ auto input_sticker = static_cast<td_api::inputMessageSticker *>(input_message_content.get());
+
+ emoji = std::move(input_sticker->emoji_);
+
+ td->stickers_manager_->create_sticker(file_id, FileId(), string(), thumbnail,
+ get_dimensions(input_sticker->width_, input_sticker->height_, nullptr),
+ nullptr, nullptr, StickerFormat::Unknown, nullptr);
+
+ content = make_unique<MessageSticker>(file_id, is_premium);
+ break;
+ }
+ case td_api::inputMessageVideo::ID: {
+ auto input_video = static_cast<td_api::inputMessageVideo *>(input_message_content.get());
+
+ ttl = input_video->ttl_;
+
+ bool has_stickers = !sticker_file_ids.empty();
+ td->videos_manager_->create_video(
+ file_id, string(), thumbnail, AnimationSize(), has_stickers, std::move(sticker_file_ids),
+ std::move(file_name), std::move(mime_type), input_video->duration_,
+ get_dimensions(input_video->width_, input_video->height_, nullptr), input_video->supports_streaming_, false);
+
+ content = make_unique<MessageVideo>(file_id, std::move(caption));
+ break;
+ }
+ case td_api::inputMessageVideoNote::ID: {
+ auto input_video_note = static_cast<td_api::inputMessageVideoNote *>(input_message_content.get());
+
+ auto length = input_video_note->length_;
+ if (length < 0 || length >= 640) {
+ return Status::Error(400, "Wrong video note length");
+ }
+
+ td->video_notes_manager_->create_video_note(file_id, string(), thumbnail, input_video_note->duration_,
+ get_dimensions(length, length, nullptr), string(), false);
+
+ content = make_unique<MessageVideoNote>(file_id, false);
+ break;
+ }
+ case td_api::inputMessageVoiceNote::ID: {
+ auto input_voice_note = static_cast<td_api::inputMessageVoiceNote *>(input_message_content.get());
+
+ td->voice_notes_manager_->create_voice_note(file_id, std::move(mime_type), input_voice_note->duration_,
+ std::move(input_voice_note->waveform_), false);
+
+ content = make_unique<MessageVoiceNote>(file_id, std::move(caption), false);
+ break;
+ }
+ case td_api::inputMessageLocation::ID: {
+ TRY_RESULT(location, process_input_message_location(std::move(input_message_content)));
+ if (location.live_period == 0) {
+ content = make_unique<MessageLocation>(std::move(location.location));
+ } else {
+ content = make_unique<MessageLiveLocation>(std::move(location.location), location.live_period, location.heading,
+ location.proximity_alert_radius);
+ }
+ break;
+ }
+ case td_api::inputMessageVenue::ID: {
+ TRY_RESULT(venue, process_input_message_venue(std::move(input_message_content)));
+ content = make_unique<MessageVenue>(std::move(venue));
+ break;
+ }
+ case td_api::inputMessageContact::ID: {
+ TRY_RESULT(contact, process_input_message_contact(std::move(input_message_content)));
+ content = make_unique<MessageContact>(std::move(contact));
+ break;
+ }
+ case td_api::inputMessageGame::ID: {
+ TRY_RESULT(game, process_input_message_game(td->contacts_manager_.get(), std::move(input_message_content)));
+ via_bot_user_id = game.get_bot_user_id();
+ if (via_bot_user_id == td->contacts_manager_->get_my_id()) {
+ via_bot_user_id = UserId();
+ }
+
+ content = make_unique<MessageGame>(std::move(game));
+ break;
+ }
+ case td_api::inputMessageInvoice::ID: {
+ if (!is_bot) {
+ return Status::Error(400, "Invoices can be sent only by bots");
+ }
+
+ TRY_RESULT(input_invoice, InputInvoice::process_input_message_invoice(std::move(input_message_content), td,
+ dialog_id, is_premium));
+ content = make_unique<MessageInvoice>(std::move(input_invoice));
+ break;
+ }
+ case td_api::inputMessagePoll::ID: {
+ const size_t MAX_POLL_QUESTION_LENGTH = is_bot ? 300 : 255; // server-side limit
+ constexpr size_t MAX_POLL_OPTION_LENGTH = 100; // server-side limit
+ constexpr size_t MAX_POLL_OPTIONS = 10; // server-side limit
+ auto input_poll = static_cast<td_api::inputMessagePoll *>(input_message_content.get());
+ if (!clean_input_string(input_poll->question_)) {
+ return Status::Error(400, "Poll question must be encoded in UTF-8");
+ }
+ if (input_poll->question_.empty()) {
+ return Status::Error(400, "Poll question must be non-empty");
+ }
+ if (utf8_length(input_poll->question_) > MAX_POLL_QUESTION_LENGTH) {
+ return Status::Error(400, PSLICE() << "Poll question length must not exceed " << MAX_POLL_QUESTION_LENGTH);
+ }
+ if (input_poll->options_.size() <= 1) {
+ return Status::Error(400, "Poll must have at least 2 option");
+ }
+ if (input_poll->options_.size() > MAX_POLL_OPTIONS) {
+ return Status::Error(400, PSLICE() << "Poll can't have more than " << MAX_POLL_OPTIONS << " options");
+ }
+ for (auto &option : input_poll->options_) {
+ if (!clean_input_string(option)) {
+ return Status::Error(400, "Poll options must be encoded in UTF-8");
+ }
+ if (option.empty()) {
+ return Status::Error(400, "Poll options must be non-empty");
+ }
+ if (utf8_length(option) > MAX_POLL_OPTION_LENGTH) {
+ return Status::Error(400, PSLICE() << "Poll options length must not exceed " << MAX_POLL_OPTION_LENGTH);
+ }
+ }
+
+ bool allow_multiple_answers = false;
+ bool is_quiz = false;
+ int32 correct_option_id = -1;
+ FormattedText explanation;
+ if (input_poll->type_ == nullptr) {
+ return Status::Error(400, "Poll type must be non-empty");
+ }
+ switch (input_poll->type_->get_id()) {
+ case td_api::pollTypeRegular::ID: {
+ auto type = td_api::move_object_as<td_api::pollTypeRegular>(input_poll->type_);
+ allow_multiple_answers = type->allow_multiple_answers_;
+ break;
+ }
+ case td_api::pollTypeQuiz::ID: {
+ auto type = td_api::move_object_as<td_api::pollTypeQuiz>(input_poll->type_);
+ is_quiz = true;
+ correct_option_id = type->correct_option_id_;
+ if (correct_option_id < 0 || correct_option_id >= static_cast<int32>(input_poll->options_.size())) {
+ return Status::Error(400, "Wrong correct option ID specified");
+ }
+ auto r_explanation =
+ get_formatted_text(td, dialog_id, std::move(type->explanation_), is_bot, true, true, false);
+ if (r_explanation.is_error()) {
+ return r_explanation.move_as_error();
+ }
+ explanation = r_explanation.move_as_ok();
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ int32 open_period = is_bot ? input_poll->open_period_ : 0;
+ int32 close_date = is_bot ? input_poll->close_date_ : 0;
+ if (open_period != 0) {
+ close_date = 0;
+ }
+ bool is_closed = is_bot ? input_poll->is_closed_ : false;
+ content = make_unique<MessagePoll>(
+ td->poll_manager_->create_poll(std::move(input_poll->question_), std::move(input_poll->options_),
+ input_poll->is_anonymous_, allow_multiple_answers, is_quiz, correct_option_id,
+ std::move(explanation), open_period, close_date, is_closed));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ return InputMessageContent{std::move(content), disable_web_page_preview, clear_draft, ttl,
+ via_bot_user_id, std::move(emoji)};
+}
+
+Result<InputMessageContent> get_input_message_content(
+ DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content, Td *td, bool is_premium) {
+ bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
+
+ LOG(INFO) << "Get input message content from " << to_string(input_message_content);
+
+ bool have_file = true;
+ // TODO: send from secret chat to common
+ Result<FileId> r_file_id = Status::Error(500, "Have no file");
+ tl_object_ptr<td_api::inputThumbnail> input_thumbnail;
+ vector<FileId> sticker_file_ids;
+ switch (input_message_content->get_id()) {
+ case td_api::inputMessageAnimation::ID: {
+ auto input_message = static_cast<td_api::inputMessageAnimation *>(input_message_content.get());
+ r_file_id = td->file_manager_->get_input_file_id(FileType::Animation, input_message->animation_, dialog_id, false,
+ is_secret, true);
+ input_thumbnail = std::move(input_message->thumbnail_);
+ if (!input_message->added_sticker_file_ids_.empty()) {
+ sticker_file_ids = td->stickers_manager_->get_attached_sticker_file_ids(input_message->added_sticker_file_ids_);
+ }
+ break;
+ }
+ case td_api::inputMessageAudio::ID: {
+ auto input_message = static_cast<td_api::inputMessageAudio *>(input_message_content.get());
+ r_file_id =
+ td->file_manager_->get_input_file_id(FileType::Audio, input_message->audio_, dialog_id, false, is_secret);
+ input_thumbnail = std::move(input_message->album_cover_thumbnail_);
+ break;
+ }
+ case td_api::inputMessageDocument::ID: {
+ auto input_message = static_cast<td_api::inputMessageDocument *>(input_message_content.get());
+ auto file_type = input_message->disable_content_type_detection_ ? FileType::DocumentAsFile : FileType::Document;
+ r_file_id =
+ td->file_manager_->get_input_file_id(file_type, input_message->document_, dialog_id, false, is_secret, true);
+ input_thumbnail = std::move(input_message->thumbnail_);
+ break;
+ }
+ case td_api::inputMessagePhoto::ID: {
+ auto input_message = static_cast<td_api::inputMessagePhoto *>(input_message_content.get());
+ r_file_id =
+ td->file_manager_->get_input_file_id(FileType::Photo, input_message->photo_, dialog_id, false, is_secret);
+ input_thumbnail = std::move(input_message->thumbnail_);
+ if (!input_message->added_sticker_file_ids_.empty()) {
+ sticker_file_ids = td->stickers_manager_->get_attached_sticker_file_ids(input_message->added_sticker_file_ids_);
+ }
+ break;
+ }
+ case td_api::inputMessageSticker::ID: {
+ auto input_message = static_cast<td_api::inputMessageSticker *>(input_message_content.get());
+ r_file_id =
+ td->file_manager_->get_input_file_id(FileType::Sticker, input_message->sticker_, dialog_id, false, is_secret);
+ input_thumbnail = std::move(input_message->thumbnail_);
+ break;
+ }
+ case td_api::inputMessageVideo::ID: {
+ auto input_message = static_cast<td_api::inputMessageVideo *>(input_message_content.get());
+ r_file_id =
+ td->file_manager_->get_input_file_id(FileType::Video, input_message->video_, dialog_id, false, is_secret);
+ input_thumbnail = std::move(input_message->thumbnail_);
+ if (!input_message->added_sticker_file_ids_.empty()) {
+ sticker_file_ids = td->stickers_manager_->get_attached_sticker_file_ids(input_message->added_sticker_file_ids_);
+ }
+ break;
+ }
+ case td_api::inputMessageVideoNote::ID: {
+ auto input_message = static_cast<td_api::inputMessageVideoNote *>(input_message_content.get());
+ r_file_id = td->file_manager_->get_input_file_id(FileType::VideoNote, input_message->video_note_, dialog_id,
+ false, is_secret);
+ input_thumbnail = std::move(input_message->thumbnail_);
+ break;
+ }
+ case td_api::inputMessageVoiceNote::ID: {
+ auto input_message = static_cast<td_api::inputMessageVoiceNote *>(input_message_content.get());
+ r_file_id = td->file_manager_->get_input_file_id(FileType::VoiceNote, input_message->voice_note_, dialog_id,
+ false, is_secret);
+ break;
+ }
+ default:
+ have_file = false;
+ break;
+ }
+ // TODO is path of files must be stored in bytes instead of UTF-8 string?
+
+ FileId file_id;
+ if (have_file) {
+ if (r_file_id.is_error()) {
+ return Status::Error(400, r_file_id.error().message());
+ }
+ file_id = r_file_id.ok();
+ CHECK(file_id.is_valid());
+ }
+
+ PhotoSize thumbnail;
+ if (input_thumbnail != nullptr) {
+ auto r_thumbnail_file_id =
+ td->file_manager_->get_input_thumbnail_file_id(input_thumbnail->thumbnail_, dialog_id, is_secret);
+ if (r_thumbnail_file_id.is_error()) {
+ LOG(WARNING) << "Ignore thumbnail file: " << r_thumbnail_file_id.error().message();
+ } else {
+ thumbnail.type = 't';
+ thumbnail.dimensions = get_dimensions(input_thumbnail->width_, input_thumbnail->height_, nullptr);
+ thumbnail.file_id = r_thumbnail_file_id.ok();
+ CHECK(thumbnail.file_id.is_valid());
+
+ FileView thumbnail_file_view = td->file_manager_->get_file_view(thumbnail.file_id);
+ if (thumbnail_file_view.has_remote_location()) {
+ // TODO td->file_manager_->delete_remote_location(thumbnail.file_id);
+ }
+ }
+ }
+
+ bool is_bot = td->auth_manager_->is_bot();
+ TRY_RESULT(caption, get_formatted_text(td, dialog_id, extract_input_caption(input_message_content), is_bot, true,
+ false, false));
+ if (is_bot && static_cast<int64>(utf8_length(caption.text)) > G()->get_option_integer("message_caption_length_max")) {
+ return Status::Error(400, "Message caption is too long");
+ }
+ return create_input_message_content(dialog_id, std::move(input_message_content), td, std::move(caption), file_id,
+ std::move(thumbnail), std::move(sticker_file_ids), is_premium);
+}
+
+bool can_have_input_media(const Td *td, const MessageContent *content, bool is_server) {
+ switch (content->get_type()) {
+ case MessageContentType::Game:
+ return is_server || static_cast<const MessageGame *>(content)->game.has_input_media();
+ case MessageContentType::Poll:
+ return td->poll_manager_->has_input_media(static_cast<const MessagePoll *>(content)->poll_id);
+ case MessageContentType::Unsupported:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ return false;
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Contact:
+ case MessageContentType::Dice:
+ case MessageContentType::Document:
+ case MessageContentType::Invoice:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Photo:
+ case MessageContentType::Sticker:
+ case MessageContentType::Text:
+ case MessageContentType::Venue:
+ case MessageContentType::Video:
+ case MessageContentType::VideoNote:
+ case MessageContentType::VoiceNote:
+ return true;
+ default:
+ UNREACHABLE();
+ }
+}
+
+SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
+ tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
+ BufferSlice thumbnail, int32 layer) {
+ switch (content->get_type()) {
+ case MessageContentType::Animation: {
+ const auto *m = static_cast<const MessageAnimation *>(content);
+ return td->animations_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
+ std::move(thumbnail), layer);
+ }
+ case MessageContentType::Audio: {
+ const auto *m = static_cast<const MessageAudio *>(content);
+ return td->audios_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
+ std::move(thumbnail), layer);
+ }
+ case MessageContentType::Contact: {
+ const auto *m = static_cast<const MessageContact *>(content);
+ return m->contact.get_secret_input_media_contact();
+ }
+ case MessageContentType::Document: {
+ const auto *m = static_cast<const MessageDocument *>(content);
+ return td->documents_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
+ std::move(thumbnail), layer);
+ }
+ case MessageContentType::Location: {
+ const auto *m = static_cast<const MessageLocation *>(content);
+ return m->location.get_secret_input_media_geo_point();
+ }
+ case MessageContentType::Photo: {
+ const auto *m = static_cast<const MessagePhoto *>(content);
+ return photo_get_secret_input_media(td->file_manager_.get(), m->photo, std::move(input_file), m->caption.text,
+ std::move(thumbnail));
+ }
+ case MessageContentType::Sticker: {
+ const auto *m = static_cast<const MessageSticker *>(content);
+ return td->stickers_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail),
+ layer);
+ }
+ case MessageContentType::Text: {
+ CHECK(input_file == nullptr);
+ CHECK(thumbnail.empty());
+ const auto *m = static_cast<const MessageText *>(content);
+ return td->web_pages_manager_->get_secret_input_media(m->web_page_id);
+ }
+ case MessageContentType::Venue: {
+ const auto *m = static_cast<const MessageVenue *>(content);
+ return m->venue.get_secret_input_media_venue();
+ }
+ case MessageContentType::Video: {
+ const auto *m = static_cast<const MessageVideo *>(content);
+ return td->videos_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
+ std::move(thumbnail), layer);
+ }
+ case MessageContentType::VideoNote: {
+ const auto *m = static_cast<const MessageVideoNote *>(content);
+ return td->video_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail),
+ layer);
+ }
+ case MessageContentType::VoiceNote: {
+ const auto *m = static_cast<const MessageVoiceNote *>(content);
+ return td->voice_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
+ layer);
+ }
+ case MessageContentType::Call:
+ case MessageContentType::Dice:
+ case MessageContentType::Game:
+ case MessageContentType::Invoice:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Poll:
+ case MessageContentType::Unsupported:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return SecretInputMedia{};
+}
+
+static tl_object_ptr<telegram_api::InputMedia> get_input_media_impl(
+ const MessageContent *content, Td *td, tl_object_ptr<telegram_api::InputFile> input_file,
+ tl_object_ptr<telegram_api::InputFile> input_thumbnail, int32 ttl, const string &emoji) {
+ if (!can_have_input_media(td, content, false)) {
+ return nullptr;
+ }
+ switch (content->get_type()) {
+ case MessageContentType::Animation: {
+ const auto *m = static_cast<const MessageAnimation *>(content);
+ return td->animations_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
+ }
+ case MessageContentType::Audio: {
+ const auto *m = static_cast<const MessageAudio *>(content);
+ return td->audios_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
+ }
+ case MessageContentType::Contact: {
+ const auto *m = static_cast<const MessageContact *>(content);
+ return m->contact.get_input_media_contact();
+ }
+ case MessageContentType::Dice: {
+ const auto *m = static_cast<const MessageDice *>(content);
+ return make_tl_object<telegram_api::inputMediaDice>(m->emoji);
+ }
+ case MessageContentType::Document: {
+ const auto *m = static_cast<const MessageDocument *>(content);
+ return td->documents_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
+ }
+ case MessageContentType::Game: {
+ const auto *m = static_cast<const MessageGame *>(content);
+ return m->game.get_input_media_game(td);
+ }
+ case MessageContentType::Invoice: {
+ const auto *m = static_cast<const MessageInvoice *>(content);
+ return m->input_invoice.get_input_media_invoice(td, std::move(input_file), std::move(input_thumbnail));
+ }
+ case MessageContentType::LiveLocation: {
+ const auto *m = static_cast<const MessageLiveLocation *>(content);
+ int32 flags = telegram_api::inputMediaGeoLive::PERIOD_MASK;
+ if (m->heading != 0) {
+ flags |= telegram_api::inputMediaGeoLive::HEADING_MASK;
+ }
+ flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK;
+ return make_tl_object<telegram_api::inputMediaGeoLive>(flags, false /*ignored*/,
+ m->location.get_input_geo_point(), m->heading, m->period,
+ m->proximity_alert_radius);
+ }
+ case MessageContentType::Location: {
+ const auto *m = static_cast<const MessageLocation *>(content);
+ return m->location.get_input_media_geo_point();
+ }
+ case MessageContentType::Photo: {
+ const auto *m = static_cast<const MessagePhoto *>(content);
+ return photo_get_input_media(td->file_manager_.get(), m->photo, std::move(input_file), ttl);
+ }
+ case MessageContentType::Poll: {
+ const auto *m = static_cast<const MessagePoll *>(content);
+ return td->poll_manager_->get_input_media(m->poll_id);
+ }
+ case MessageContentType::Sticker: {
+ const auto *m = static_cast<const MessageSticker *>(content);
+ return td->stickers_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail),
+ emoji);
+ }
+ case MessageContentType::Venue: {
+ const auto *m = static_cast<const MessageVenue *>(content);
+ return m->venue.get_input_media_venue();
+ }
+ case MessageContentType::Video: {
+ const auto *m = static_cast<const MessageVideo *>(content);
+ return td->videos_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail), ttl);
+ }
+ case MessageContentType::VideoNote: {
+ const auto *m = static_cast<const MessageVideoNote *>(content);
+ return td->video_notes_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
+ }
+ case MessageContentType::VoiceNote: {
+ const auto *m = static_cast<const MessageVoiceNote *>(content);
+ return td->voice_notes_manager_->get_input_media(m->file_id, std::move(input_file));
+ }
+ case MessageContentType::Text:
+ case MessageContentType::Unsupported:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return nullptr;
+}
+
+tl_object_ptr<telegram_api::InputMedia> get_input_media(const MessageContent *content, Td *td,
+ tl_object_ptr<telegram_api::InputFile> input_file,
+ tl_object_ptr<telegram_api::InputFile> input_thumbnail,
+ FileId file_id, FileId thumbnail_file_id, int32 ttl,
+ bool force) {
+ bool had_input_file = input_file != nullptr;
+ bool had_input_thumbnail = input_thumbnail != nullptr;
+ auto input_media =
+ get_input_media_impl(content, td, std::move(input_file), std::move(input_thumbnail), ttl, string());
+ auto was_uploaded = FileManager::extract_was_uploaded(input_media);
+ if (had_input_file) {
+ if (!was_uploaded) {
+ // if we had InputFile, but has failed to use it, then we need to immediately cancel file upload
+ // so the next upload with the same file can succeed
+ CHECK(file_id.is_valid());
+ td->file_manager_->cancel_upload(file_id);
+ if (had_input_thumbnail) {
+ CHECK(thumbnail_file_id.is_valid());
+ td->file_manager_->cancel_upload(thumbnail_file_id);
+ }
+ }
+ } else {
+ CHECK(!had_input_thumbnail);
+ }
+ if (!was_uploaded) {
+ auto file_reference = FileManager::extract_file_reference(input_media);
+ if (file_reference == FileReferenceView::invalid_file_reference()) {
+ if (!force) {
+ LOG(INFO) << "File " << file_id << " has invalid file reference";
+ return nullptr;
+ }
+ LOG(ERROR) << "File " << file_id << " has invalid file reference, but we forced to use it";
+ }
+ }
+ return input_media;
+}
+
+tl_object_ptr<telegram_api::InputMedia> get_input_media(const MessageContent *content, Td *td, int32 ttl,
+ const string &emoji, bool force) {
+ auto input_media = get_input_media_impl(content, td, nullptr, nullptr, ttl, emoji);
+ auto file_reference = FileManager::extract_file_reference(input_media);
+ if (file_reference == FileReferenceView::invalid_file_reference()) {
+ auto file_id = get_message_content_any_file_id(content);
+ if (!force) {
+ LOG(INFO) << "File " << file_id << " has invalid file reference";
+ return nullptr;
+ }
+ LOG(ERROR) << "File " << file_id << " has invalid file reference, but we forced to use it";
+ }
+ return input_media;
+}
+
+tl_object_ptr<telegram_api::InputMedia> get_fake_input_media(Td *td, tl_object_ptr<telegram_api::InputFile> input_file,
+ FileId file_id) {
+ FileView file_view = td->file_manager_->get_file_view(file_id);
+ auto file_type = file_view.get_type();
+ if (is_document_file_type(file_type)) {
+ vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
+ auto file_path = file_view.suggested_path();
+ const PathView path_view(file_path);
+ Slice file_name = path_view.file_name();
+ if (!file_name.empty()) {
+ attributes.push_back(make_tl_object<telegram_api::documentAttributeFilename>(file_name.str()));
+ }
+ string mime_type = MimeType::from_extension(path_view.extension());
+ int32 flags = 0;
+ if (file_type == FileType::Video) {
+ flags |= telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK;
+ }
+ if (file_type == FileType::DocumentAsFile) {
+ flags |= telegram_api::inputMediaUploadedDocument::FORCE_FILE_MASK;
+ }
+ return make_tl_object<telegram_api::inputMediaUploadedDocument>(
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_file), nullptr, mime_type, std::move(attributes),
+ vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ } else {
+ CHECK(file_type == FileType::Photo);
+ return make_tl_object<telegram_api::inputMediaUploadedPhoto>(
+ 0, std::move(input_file), vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ }
+}
+
+void delete_message_content_thumbnail(MessageContent *content, Td *td) {
+ switch (content->get_type()) {
+ case MessageContentType::Animation: {
+ auto *m = static_cast<MessageAnimation *>(content);
+ return td->animations_manager_->delete_animation_thumbnail(m->file_id);
+ }
+ case MessageContentType::Audio: {
+ auto *m = static_cast<MessageAudio *>(content);
+ return td->audios_manager_->delete_audio_thumbnail(m->file_id);
+ }
+ case MessageContentType::Document: {
+ auto *m = static_cast<MessageDocument *>(content);
+ return td->documents_manager_->delete_document_thumbnail(m->file_id);
+ }
+ case MessageContentType::Invoice: {
+ auto *m = static_cast<MessageInvoice *>(content);
+ return m->input_invoice.delete_thumbnail(td);
+ }
+ case MessageContentType::Photo: {
+ auto *m = static_cast<MessagePhoto *>(content);
+ return photo_delete_thumbnail(m->photo);
+ }
+ case MessageContentType::Sticker: {
+ auto *m = static_cast<MessageSticker *>(content);
+ return td->stickers_manager_->delete_sticker_thumbnail(m->file_id);
+ }
+ case MessageContentType::Video: {
+ auto *m = static_cast<MessageVideo *>(content);
+ return td->videos_manager_->delete_video_thumbnail(m->file_id);
+ }
+ case MessageContentType::VideoNote: {
+ auto *m = static_cast<MessageVideoNote *>(content);
+ return td->video_notes_manager_->delete_video_note_thumbnail(m->file_id);
+ }
+ case MessageContentType::Contact:
+ case MessageContentType::Dice:
+ case MessageContentType::Game:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Venue:
+ case MessageContentType::VoiceNote:
+ case MessageContentType::Text:
+ case MessageContentType::Unsupported:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::Poll:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+Status can_send_message_content(DialogId dialog_id, const MessageContent *content, bool is_forward, const Td *td) {
+ auto dialog_type = dialog_id.get_type();
+ RestrictedRights permissions = [&] {
+ switch (dialog_type) {
+ case DialogType::User:
+ return td->contacts_manager_->get_user_default_permissions(dialog_id.get_user_id());
+ case DialogType::Chat:
+ return td->contacts_manager_->get_chat_permissions(dialog_id.get_chat_id()).get_effective_restricted_rights();
+ case DialogType::Channel:
+ return td->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id())
+ .get_effective_restricted_rights();
+ case DialogType::SecretChat:
+ return td->contacts_manager_->get_secret_chat_default_permissions(dialog_id.get_secret_chat_id());
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ return td->contacts_manager_->get_user_default_permissions(UserId());
+ }
+ }();
+
+ auto content_type = content->get_type();
+ switch (content_type) {
+ case MessageContentType::Animation:
+ if (!permissions.can_send_animations()) {
+ return Status::Error(400, "Not enough rights to send animations to the chat");
+ }
+ break;
+ case MessageContentType::Audio:
+ if (!permissions.can_send_media()) {
+ return Status::Error(400, "Not enough rights to send music to the chat");
+ }
+ break;
+ case MessageContentType::Contact:
+ if (!permissions.can_send_messages()) {
+ return Status::Error(400, "Not enough rights to send contacts to the chat");
+ }
+ break;
+ case MessageContentType::Dice:
+ if (!permissions.can_send_stickers()) {
+ return Status::Error(400, "Not enough rights to send dice to the chat");
+ }
+ if (dialog_type == DialogType::SecretChat) {
+ return Status::Error(400, "Dice can't be sent to secret chats");
+ }
+ break;
+ case MessageContentType::Document:
+ if (!permissions.can_send_media()) {
+ return Status::Error(400, "Not enough rights to send documents to the chat");
+ }
+ break;
+ case MessageContentType::Game:
+ if (dialog_type == DialogType::Channel &&
+ td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) {
+ // return Status::Error(400, "Games can't be sent to channel chats");
+ }
+ if (dialog_type == DialogType::SecretChat) {
+ return Status::Error(400, "Games can't be sent to secret chats");
+ }
+ if (!permissions.can_send_games()) {
+ return Status::Error(400, "Not enough rights to send games to the chat");
+ }
+ break;
+ case MessageContentType::Invoice:
+ if (!permissions.can_send_messages()) {
+ return Status::Error(400, "Not enough rights to send invoice messages to the chat");
+ }
+ if (dialog_type == DialogType::SecretChat) {
+ return Status::Error(400, "Invoice messages can't be sent to secret chats");
+ }
+ break;
+ case MessageContentType::LiveLocation:
+ if (!permissions.can_send_messages()) {
+ return Status::Error(400, "Not enough rights to send live locations to the chat");
+ }
+ break;
+ case MessageContentType::Location:
+ if (!permissions.can_send_messages()) {
+ return Status::Error(400, "Not enough rights to send locations to the chat");
+ }
+ break;
+ case MessageContentType::Photo:
+ if (!permissions.can_send_media()) {
+ return Status::Error(400, "Not enough rights to send photos to the chat");
+ }
+ break;
+ case MessageContentType::Poll:
+ if (!permissions.can_send_polls()) {
+ return Status::Error(400, "Not enough rights to send polls to the chat");
+ }
+ if (dialog_type == DialogType::Channel &&
+ td->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id()) &&
+ !td->poll_manager_->get_poll_is_anonymous(static_cast<const MessagePoll *>(content)->poll_id)) {
+ return Status::Error(400, "Non-anonymous polls can't be sent to channel chats");
+ }
+ if (dialog_type == DialogType::User && !is_forward && !td->auth_manager_->is_bot() &&
+ !td->contacts_manager_->is_user_bot(dialog_id.get_user_id())) {
+ return Status::Error(400, "Polls can't be sent to the private chat");
+ }
+ if (dialog_type == DialogType::SecretChat) {
+ return Status::Error(400, "Polls can't be sent to secret chats");
+ }
+ break;
+ case MessageContentType::Sticker:
+ if (!permissions.can_send_stickers()) {
+ return Status::Error(400, "Not enough rights to send stickers to the chat");
+ }
+ if (get_message_content_sticker_type(td, content) == StickerType::CustomEmoji) {
+ return Status::Error(400, "Can't send emoji stickers in messages");
+ }
+ break;
+ case MessageContentType::Text:
+ if (!permissions.can_send_messages()) {
+ return Status::Error(400, "Not enough rights to send text messages to the chat");
+ }
+ break;
+ case MessageContentType::Venue:
+ if (!permissions.can_send_messages()) {
+ return Status::Error(400, "Not enough rights to send venues to the chat");
+ }
+ break;
+ case MessageContentType::Video:
+ if (!permissions.can_send_media()) {
+ return Status::Error(400, "Not enough rights to send videos to the chat");
+ }
+ break;
+ case MessageContentType::VideoNote:
+ if (!permissions.can_send_media()) {
+ return Status::Error(400, "Not enough rights to send video notes to the chat");
+ }
+ if (dialog_type == DialogType::User &&
+ td->contacts_manager_->get_user_voice_messages_forbidden(dialog_id.get_user_id())) {
+ return Status::Error(400, "User restricted receiving of voice messages");
+ }
+ break;
+ case MessageContentType::VoiceNote:
+ if (!permissions.can_send_media()) {
+ return Status::Error(400, "Not enough rights to send voice notes to the chat");
+ }
+ if (dialog_type == DialogType::User &&
+ td->contacts_manager_->get_user_voice_messages_forbidden(dialog_id.get_user_id())) {
+ return Status::Error(400, "User restricted receiving of video messages");
+ }
+ break;
+ case MessageContentType::None:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Unsupported:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ UNREACHABLE();
+ }
+ return Status::OK();
+}
+
+bool can_forward_message_content(const MessageContent *content) {
+ auto content_type = content->get_type();
+ if (content_type == MessageContentType::Text) {
+ auto *text = static_cast<const MessageText *>(content);
+ return !is_empty_string(text->text.text); // text must be non-empty in the new message
+ }
+ if (content_type == MessageContentType::Poll) {
+ auto *poll = static_cast<const MessagePoll *>(content);
+ return !PollManager::is_local_poll_id(poll->poll_id);
+ }
+
+ return !is_service_message_content(content_type) && content_type != MessageContentType::Unsupported &&
+ content_type != MessageContentType::ExpiredPhoto && content_type != MessageContentType::ExpiredVideo;
+}
+
+bool update_opened_message_content(MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::VideoNote: {
+ auto video_note_content = static_cast<MessageVideoNote *>(content);
+ if (video_note_content->is_viewed) {
+ return false;
+ }
+ video_note_content->is_viewed = true;
+ return true;
+ }
+ case MessageContentType::VoiceNote: {
+ auto voice_note_content = static_cast<MessageVoiceNote *>(content);
+ if (voice_note_content->is_listened) {
+ return false;
+ }
+ voice_note_content->is_listened = true;
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+static int32 get_message_content_text_index_mask(const MessageContent *content) {
+ const FormattedText *text = get_message_content_text(content);
+ if (text == nullptr || content->get_type() == MessageContentType::Game) {
+ return 0;
+ }
+
+ for (auto &entity : text->entities) {
+ if (entity.type == MessageEntity::Type::Url || entity.type == MessageEntity::Type::EmailAddress ||
+ entity.type == MessageEntity::Type::TextUrl) {
+ return message_search_filter_index_mask(MessageSearchFilter::Url);
+ }
+ }
+ return 0;
+}
+
+static int32 get_message_content_media_index_mask(const MessageContent *content, const Td *td, bool is_outgoing) {
+ switch (content->get_type()) {
+ case MessageContentType::Animation:
+ return message_search_filter_index_mask(MessageSearchFilter::Animation);
+ case MessageContentType::Audio:
+ return message_search_filter_index_mask(MessageSearchFilter::Audio);
+ case MessageContentType::Document:
+ return message_search_filter_index_mask(MessageSearchFilter::Document);
+ case MessageContentType::Photo:
+ return message_search_filter_index_mask(MessageSearchFilter::Photo) |
+ message_search_filter_index_mask(MessageSearchFilter::PhotoAndVideo);
+ case MessageContentType::Video:
+ return message_search_filter_index_mask(MessageSearchFilter::Video) |
+ message_search_filter_index_mask(MessageSearchFilter::PhotoAndVideo);
+ case MessageContentType::VideoNote:
+ return message_search_filter_index_mask(MessageSearchFilter::VideoNote) |
+ message_search_filter_index_mask(MessageSearchFilter::VoiceAndVideoNote);
+ case MessageContentType::VoiceNote:
+ return message_search_filter_index_mask(MessageSearchFilter::VoiceNote) |
+ message_search_filter_index_mask(MessageSearchFilter::VoiceAndVideoNote);
+ case MessageContentType::ChatChangePhoto:
+ return message_search_filter_index_mask(MessageSearchFilter::ChatPhoto);
+ case MessageContentType::Call: {
+ int32 index_mask = message_search_filter_index_mask(MessageSearchFilter::Call);
+ const auto *m = static_cast<const MessageCall *>(content);
+ if (!is_outgoing &&
+ (m->discard_reason == CallDiscardReason::Declined || m->discard_reason == CallDiscardReason::Missed)) {
+ index_mask |= message_search_filter_index_mask(MessageSearchFilter::MissedCall);
+ }
+ return index_mask;
+ }
+ case MessageContentType::Text:
+ case MessageContentType::Contact:
+ case MessageContentType::Game:
+ case MessageContentType::Invoice:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Sticker:
+ case MessageContentType::Unsupported:
+ case MessageContentType::Venue:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::Poll:
+ case MessageContentType::Dice:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ return 0;
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+ return 0;
+}
+
+int32 get_message_content_index_mask(const MessageContent *content, const Td *td, bool is_outgoing) {
+ return get_message_content_text_index_mask(content) | get_message_content_media_index_mask(content, td, is_outgoing);
+}
+
+StickerType get_message_content_sticker_type(const Td *td, const MessageContent *content) {
+ CHECK(content->get_type() == MessageContentType::Sticker);
+ return td->stickers_manager_->get_sticker_type(static_cast<const MessageSticker *>(content)->file_id);
+}
+
+MessageId get_message_content_pinned_message_id(const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::PinMessage:
+ return static_cast<const MessagePinMessage *>(content)->message_id;
+ default:
+ return MessageId();
+ }
+}
+
+string get_message_content_theme_name(const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::ChatSetTheme:
+ return static_cast<const MessageChatSetTheme *>(content)->emoji;
+ default:
+ return string();
+ }
+}
+
+FullMessageId get_message_content_replied_message_id(DialogId dialog_id, const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::PinMessage:
+ return {dialog_id, static_cast<const MessagePinMessage *>(content)->message_id};
+ case MessageContentType::GameScore:
+ return {dialog_id, static_cast<const MessageGameScore *>(content)->game_message_id};
+ case MessageContentType::PaymentSuccessful: {
+ auto *m = static_cast<const MessagePaymentSuccessful *>(content);
+ if (!m->invoice_message_id.is_valid()) {
+ return FullMessageId();
+ }
+
+ auto reply_in_dialog_id = m->invoice_dialog_id.is_valid() ? m->invoice_dialog_id : dialog_id;
+ return {reply_in_dialog_id, m->invoice_message_id};
+ }
+ default:
+ return FullMessageId();
+ }
+}
+
+std::pair<InputGroupCallId, bool> get_message_content_group_call_info(const MessageContent *content) {
+ CHECK(content->get_type() == MessageContentType::GroupCall);
+ const auto *m = static_cast<const MessageGroupCall *>(content);
+ return {m->input_group_call_id, m->duration >= 0};
+}
+
+vector<UserId> get_message_content_added_user_ids(const MessageContent *content) {
+ CHECK(content->get_type() == MessageContentType::ChatAddUsers);
+ return static_cast<const MessageChatAddUsers *>(content)->user_ids;
+}
+
+UserId get_message_content_deleted_user_id(const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::ChatDeleteUser:
+ return static_cast<const MessageChatDeleteUser *>(content)->user_id;
+ default:
+ return UserId();
+ }
+}
+
+int32 get_message_content_live_location_period(const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::LiveLocation:
+ return static_cast<const MessageLiveLocation *>(content)->period;
+ default:
+ return 0;
+ }
+}
+
+bool get_message_content_poll_is_anonymous(const Td *td, const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::Poll:
+ return td->poll_manager_->get_poll_is_anonymous(static_cast<const MessagePoll *>(content)->poll_id);
+ default:
+ return false;
+ }
+}
+
+bool get_message_content_poll_is_closed(const Td *td, const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::Poll:
+ return td->poll_manager_->get_poll_is_closed(static_cast<const MessagePoll *>(content)->poll_id);
+ default:
+ return true;
+ }
+}
+
+bool has_message_content_web_page(const MessageContent *content) {
+ if (content->get_type() == MessageContentType::Text) {
+ return static_cast<const MessageText *>(content)->web_page_id.is_valid();
+ }
+ return false;
+}
+
+void remove_message_content_web_page(MessageContent *content) {
+ CHECK(content->get_type() == MessageContentType::Text);
+ static_cast<MessageText *>(content)->web_page_id = WebPageId();
+}
+
+bool can_message_content_have_media_timestamp(const MessageContent *content) {
+ CHECK(content != nullptr);
+ switch (content->get_type()) {
+ case MessageContentType::Audio:
+ case MessageContentType::Video:
+ case MessageContentType::VideoNote:
+ case MessageContentType::VoiceNote:
+ return true;
+ case MessageContentType::Invoice: {
+ const auto *m = static_cast<const MessageInvoice *>(content);
+ return m->input_invoice.has_media_timestamp();
+ }
+ default:
+ return has_message_content_web_page(content);
+ }
+}
+
+void set_message_content_poll_answer(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ vector<int32> &&option_ids, Promise<Unit> &&promise) {
+ CHECK(content->get_type() == MessageContentType::Poll);
+ td->poll_manager_->set_poll_answer(static_cast<const MessagePoll *>(content)->poll_id, full_message_id,
+ std::move(option_ids), std::move(promise));
+}
+
+void get_message_content_poll_voters(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ int32 option_id, int32 offset, int32 limit,
+ Promise<std::pair<int32, vector<UserId>>> &&promise) {
+ CHECK(content->get_type() == MessageContentType::Poll);
+ td->poll_manager_->get_poll_voters(static_cast<const MessagePoll *>(content)->poll_id, full_message_id, option_id,
+ offset, limit, std::move(promise));
+}
+
+void stop_message_content_poll(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ unique_ptr<ReplyMarkup> &&reply_markup, Promise<Unit> &&promise) {
+ CHECK(content->get_type() == MessageContentType::Poll);
+ td->poll_manager_->stop_poll(static_cast<const MessagePoll *>(content)->poll_id, full_message_id,
+ std::move(reply_markup), std::move(promise));
+}
+
+static void merge_location_access_hash(const Location &first, const Location &second) {
+ if (second.get_access_hash() != 0) {
+ first.set_access_hash(second.get_access_hash());
+ } else {
+ second.set_access_hash(first.get_access_hash());
+ }
+}
+
+static bool need_message_text_changed_warning(const MessageText *old_content, const MessageText *new_content) {
+ if (new_content->text.text == "Unsupported characters" ||
+ new_content->text.text == "This channel is blocked because it was used to spread pornographic content." ||
+ begins_with(new_content->text.text,
+ "This group has been temporarily suspended to give its moderators time to clean up after users who "
+ "posted illegal pornographic content.")) {
+ // message contained unsupported characters or is restricted, text is replaced
+ return false;
+ }
+ if (/* old_message->message_id.is_yet_unsent() && */ !old_content->text.entities.empty() &&
+ old_content->text.entities[0].offset == 0 &&
+ (new_content->text.entities.empty() || new_content->text.entities[0] != old_content->text.entities[0]) &&
+ old_content->text.text != new_content->text.text && ends_with(old_content->text.text, new_content->text.text)) {
+ // server has deleted first entity and left-trimed the message
+ return false;
+ }
+ return true;
+}
+
+static bool need_message_entities_changed_warning(const vector<MessageEntity> &old_entities,
+ const vector<MessageEntity> &new_entities) {
+ size_t old_pos = 0;
+ size_t new_pos = 0;
+ // compare entities, skipping some known to be different
+ while (old_pos < old_entities.size() || new_pos < new_entities.size()) {
+ // TODO remove after find_phone_numbers is implemented
+ while (new_pos < new_entities.size() && new_entities[new_pos].type == MessageEntity::Type::PhoneNumber) {
+ new_pos++;
+ }
+
+ if (old_pos < old_entities.size() && new_pos < new_entities.size() &&
+ old_entities[old_pos] == new_entities[new_pos]) {
+ old_pos++;
+ new_pos++;
+ continue;
+ }
+
+ if (old_pos < old_entities.size() && (old_entities[old_pos].type == MessageEntity::Type::MentionName ||
+ old_entities[old_pos].type == MessageEntity::Type::CustomEmoji)) {
+ // server can delete some MentionName and CustomEmoji entities
+ old_pos++;
+ continue;
+ }
+
+ if (old_pos < old_entities.size() || new_pos < new_entities.size()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void merge_message_contents(Td *td, const MessageContent *old_content, MessageContent *new_content,
+ bool need_message_changed_warning, DialogId dialog_id, bool need_merge_files,
+ bool &is_content_changed, bool &need_update) {
+ MessageContentType content_type = new_content->get_type();
+ CHECK(old_content->get_type() == content_type);
+
+ switch (content_type) {
+ case MessageContentType::Text: {
+ const auto *old_ = static_cast<const MessageText *>(old_content);
+ const auto *new_ = static_cast<const MessageText *>(new_content);
+ auto get_content_object = [td, dialog_id](const MessageContent *content) {
+ return to_string(
+ get_message_content_object(content, td, dialog_id, -1, false, false, std::numeric_limits<int32>::max()));
+ };
+ if (old_->text.text != new_->text.text) {
+ if (need_message_changed_warning && need_message_text_changed_warning(old_, new_)) {
+ LOG(ERROR) << "Message text has changed in " << get_content_object(old_content) << ". New content is "
+ << get_content_object(new_content);
+ }
+ need_update = true;
+ }
+ if (old_->text.entities != new_->text.entities) {
+ const int32 MAX_CUSTOM_ENTITIES_COUNT = 100; // server-side limit
+ if (need_message_changed_warning && need_message_text_changed_warning(old_, new_) &&
+ old_->text.entities.size() <= MAX_CUSTOM_ENTITIES_COUNT &&
+ need_message_entities_changed_warning(old_->text.entities, new_->text.entities)) {
+ LOG(WARNING) << "Entities have changed in " << get_content_object(old_content) << ". New content is "
+ << get_content_object(new_content);
+ }
+ need_update = true;
+ }
+ if (old_->web_page_id != new_->web_page_id) {
+ LOG(INFO) << "Old: " << old_->web_page_id << ", new: " << new_->web_page_id;
+ is_content_changed = true;
+ need_update |= td->web_pages_manager_->have_web_page(old_->web_page_id) ||
+ td->web_pages_manager_->have_web_page(new_->web_page_id);
+ }
+ break;
+ }
+ case MessageContentType::Animation: {
+ const auto *old_ = static_cast<const MessageAnimation *>(old_content);
+ const auto *new_ = static_cast<const MessageAnimation *>(new_content);
+ if (old_->file_id != new_->file_id) {
+ if (need_merge_files) {
+ td->animations_manager_->merge_animations(new_->file_id, old_->file_id);
+ }
+ need_update = true;
+ }
+ if (old_->caption != new_->caption) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Audio: {
+ const auto *old_ = static_cast<const MessageAudio *>(old_content);
+ const auto *new_ = static_cast<const MessageAudio *>(new_content);
+ if (old_->file_id != new_->file_id) {
+ if (need_merge_files) {
+ td->audios_manager_->merge_audios(new_->file_id, old_->file_id);
+ }
+ need_update = true;
+ }
+ if (old_->caption != new_->caption) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Contact: {
+ const auto *old_ = static_cast<const MessageContact *>(old_content);
+ const auto *new_ = static_cast<const MessageContact *>(new_content);
+ if (old_->contact != new_->contact) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Document: {
+ const auto *old_ = static_cast<const MessageDocument *>(old_content);
+ const auto *new_ = static_cast<const MessageDocument *>(new_content);
+ if (old_->file_id != new_->file_id) {
+ if (need_merge_files) {
+ td->documents_manager_->merge_documents(new_->file_id, old_->file_id);
+ }
+ need_update = true;
+ }
+ if (old_->caption != new_->caption) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Game: {
+ const auto *old_ = static_cast<const MessageGame *>(old_content);
+ const auto *new_ = static_cast<const MessageGame *>(new_content);
+ if (old_->game != new_->game) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Invoice: {
+ const auto *old_ = static_cast<const MessageInvoice *>(old_content);
+ auto *new_ = static_cast<MessageInvoice *>(new_content);
+ new_->input_invoice.update_from(old_->input_invoice);
+ if (old_->input_invoice != new_->input_invoice) {
+ need_update = true;
+ } else if (old_->input_invoice.is_equal_but_different(new_->input_invoice)) {
+ is_content_changed = true;
+ }
+ break;
+ }
+ case MessageContentType::LiveLocation: {
+ const auto *old_ = static_cast<const MessageLiveLocation *>(old_content);
+ const auto *new_ = static_cast<const MessageLiveLocation *>(new_content);
+ if (old_->location != new_->location) {
+ need_update = true;
+ }
+ if (old_->period != new_->period || old_->heading != new_->heading ||
+ old_->proximity_alert_radius != new_->proximity_alert_radius) {
+ need_update = true;
+ }
+ if (old_->location.get_access_hash() != new_->location.get_access_hash()) {
+ is_content_changed = true;
+ merge_location_access_hash(old_->location, new_->location);
+ }
+ break;
+ }
+ case MessageContentType::Location: {
+ const auto *old_ = static_cast<const MessageLocation *>(old_content);
+ const auto *new_ = static_cast<const MessageLocation *>(new_content);
+ if (old_->location != new_->location) {
+ need_update = true;
+ }
+ if (old_->location.get_access_hash() != new_->location.get_access_hash()) {
+ is_content_changed = true;
+ merge_location_access_hash(old_->location, new_->location);
+ }
+ break;
+ }
+ case MessageContentType::Photo: {
+ const auto *old_ = static_cast<const MessagePhoto *>(old_content);
+ auto *new_ = static_cast<MessagePhoto *>(new_content);
+ const Photo *old_photo = &old_->photo;
+ Photo *new_photo = &new_->photo;
+ if (old_photo->date != new_photo->date) {
+ LOG(DEBUG) << "Photo date has changed from " << old_photo->date << " to " << new_photo->date;
+ is_content_changed = true;
+ }
+ if (old_photo->id.get() != new_photo->id.get() || old_->caption != new_->caption) {
+ need_update = true;
+ }
+ if (old_photo->minithumbnail != new_photo->minithumbnail) {
+ need_update = true;
+ }
+ if (old_photo->photos != new_photo->photos) {
+ LOG(DEBUG) << "Merge photos " << old_photo->photos << " and " << new_photo->photos
+ << ", need_merge_files = " << need_merge_files;
+ auto new_photos_size = new_photo->photos.size();
+ auto old_photos_size = old_photo->photos.size();
+
+ bool need_merge = false;
+ if (need_merge_files && (old_photos_size == 1 || (old_photos_size == 2 && old_photo->photos[0].type == 't')) &&
+ old_photo->photos.back().type == 'i') {
+ // first time get info about sent photo
+ if (old_photos_size == 2) {
+ new_photo->photos.push_back(old_photo->photos[0]);
+ }
+ new_photo->photos.push_back(old_photo->photos.back());
+ need_merge = true;
+ need_update = true;
+ } else {
+ // get sent photo again
+ if (old_photos_size == 2 + new_photos_size && old_photo->photos[new_photos_size].type == 't') {
+ new_photo->photos.push_back(old_photo->photos[new_photos_size]);
+ }
+ if (old_photos_size == 1 + new_photo->photos.size() && old_photo->photos.back().type == 'i') {
+ new_photo->photos.push_back(old_photo->photos.back());
+ need_merge = true;
+ }
+ if (old_photo->photos != new_photo->photos) {
+ new_photo->photos.resize(
+ new_photos_size); // return previous size, because we shouldn't add local photo sizes
+ need_merge = false;
+ need_update = true;
+ }
+ }
+
+ LOG(DEBUG) << "Merge photos " << old_photo->photos << " and " << new_photo->photos
+ << " with new photos size = " << new_photos_size << ", need_merge = " << need_merge
+ << ", need_update = " << need_update;
+ if (need_merge && new_photos_size != 0) {
+ FileId old_file_id = get_message_content_upload_file_id(old_content);
+ FileView old_file_view = td->file_manager_->get_file_view(old_file_id);
+ FileId new_file_id = new_photo->photos[0].file_id;
+ FileView new_file_view = td->file_manager_->get_file_view(new_file_id);
+ CHECK(new_file_view.has_remote_location());
+
+ LOG(DEBUG) << "Trying to merge old file " << old_file_id << " and new file " << new_file_id;
+ if (new_file_view.remote_location().is_web()) {
+ LOG(ERROR) << "Have remote web photo location";
+ } else if (!old_file_view.has_remote_location() ||
+ old_file_view.main_remote_location().get_file_reference() !=
+ new_file_view.remote_location().get_file_reference() ||
+ old_file_view.main_remote_location().get_access_hash() !=
+ new_file_view.remote_location().get_access_hash()) {
+ FileId file_id = td->file_manager_->register_remote(
+ FullRemoteFileLocation(PhotoSizeSource::thumbnail(FileType::Photo, 'i'),
+ new_file_view.remote_location().get_id(),
+ new_file_view.remote_location().get_access_hash(), DcId::invalid(),
+ new_file_view.remote_location().get_file_reference().str()),
+ FileLocationSource::FromServer, dialog_id, old_photo->photos.back().size, 0, "");
+ LOG_STATUS(td->file_manager_->merge(file_id, old_file_id));
+ }
+ }
+ }
+ break;
+ }
+ case MessageContentType::Sticker: {
+ const auto *old_ = static_cast<const MessageSticker *>(old_content);
+ const auto *new_ = static_cast<const MessageSticker *>(new_content);
+ if (old_->file_id != new_->file_id) {
+ if (need_merge_files) {
+ td->stickers_manager_->merge_stickers(new_->file_id, old_->file_id);
+ }
+ need_update = true;
+ }
+ if (old_->is_premium != new_->is_premium) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Venue: {
+ const auto *old_ = static_cast<const MessageVenue *>(old_content);
+ const auto *new_ = static_cast<const MessageVenue *>(new_content);
+ if (old_->venue != new_->venue) {
+ need_update = true;
+ }
+ if (old_->venue.location().get_access_hash() != new_->venue.location().get_access_hash()) {
+ is_content_changed = true;
+ merge_location_access_hash(old_->venue.location(), new_->venue.location());
+ }
+ break;
+ }
+ case MessageContentType::Video: {
+ const auto *old_ = static_cast<const MessageVideo *>(old_content);
+ const auto *new_ = static_cast<const MessageVideo *>(new_content);
+ if (old_->file_id != new_->file_id) {
+ if (need_merge_files) {
+ td->videos_manager_->merge_videos(new_->file_id, old_->file_id);
+ }
+ need_update = true;
+ }
+ if (old_->caption != new_->caption) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::VideoNote: {
+ const auto *old_ = static_cast<const MessageVideoNote *>(old_content);
+ const auto *new_ = static_cast<const MessageVideoNote *>(new_content);
+ if (old_->file_id != new_->file_id) {
+ if (need_merge_files) {
+ td->video_notes_manager_->merge_video_notes(new_->file_id, old_->file_id);
+ }
+ need_update = true;
+ }
+ if (old_->is_viewed != new_->is_viewed) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::VoiceNote: {
+ const auto *old_ = static_cast<const MessageVoiceNote *>(old_content);
+ const auto *new_ = static_cast<const MessageVoiceNote *>(new_content);
+ if (old_->file_id != new_->file_id) {
+ if (need_merge_files) {
+ td->voice_notes_manager_->merge_voice_notes(new_->file_id, old_->file_id);
+ }
+ need_update = true;
+ }
+ if (old_->caption != new_->caption || old_->is_listened != new_->is_listened) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ChatCreate: {
+ const auto *old_ = static_cast<const MessageChatCreate *>(old_content);
+ const auto *new_ = static_cast<const MessageChatCreate *>(new_content);
+ if (old_->title != new_->title || old_->participant_user_ids != new_->participant_user_ids) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ChatChangeTitle: {
+ const auto *old_ = static_cast<const MessageChatChangeTitle *>(old_content);
+ const auto *new_ = static_cast<const MessageChatChangeTitle *>(new_content);
+ if (old_->title != new_->title) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ChatChangePhoto: {
+ const auto *old_ = static_cast<const MessageChatChangePhoto *>(old_content);
+ const auto *new_ = static_cast<const MessageChatChangePhoto *>(new_content);
+ if (old_->photo != new_->photo) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ChatDeletePhoto:
+ break;
+ case MessageContentType::ChatDeleteHistory:
+ break;
+ case MessageContentType::ChatAddUsers: {
+ const auto *old_ = static_cast<const MessageChatAddUsers *>(old_content);
+ const auto *new_ = static_cast<const MessageChatAddUsers *>(new_content);
+ if (old_->user_ids != new_->user_ids) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ChatJoinedByLink: {
+ auto old_ = static_cast<const MessageChatJoinedByLink *>(old_content);
+ auto new_ = static_cast<const MessageChatJoinedByLink *>(new_content);
+ if (old_->is_approved != new_->is_approved) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ChatDeleteUser: {
+ const auto *old_ = static_cast<const MessageChatDeleteUser *>(old_content);
+ const auto *new_ = static_cast<const MessageChatDeleteUser *>(new_content);
+ if (old_->user_id != new_->user_id) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ChatMigrateTo: {
+ const auto *old_ = static_cast<const MessageChatMigrateTo *>(old_content);
+ const auto *new_ = static_cast<const MessageChatMigrateTo *>(new_content);
+ if (old_->migrated_to_channel_id != new_->migrated_to_channel_id) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ChannelCreate: {
+ const auto *old_ = static_cast<const MessageChannelCreate *>(old_content);
+ const auto *new_ = static_cast<const MessageChannelCreate *>(new_content);
+ if (old_->title != new_->title) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ChannelMigrateFrom: {
+ const auto *old_ = static_cast<const MessageChannelMigrateFrom *>(old_content);
+ const auto *new_ = static_cast<const MessageChannelMigrateFrom *>(new_content);
+ if (old_->title != new_->title || old_->migrated_from_chat_id != new_->migrated_from_chat_id) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::PinMessage: {
+ const auto *old_ = static_cast<const MessagePinMessage *>(old_content);
+ const auto *new_ = static_cast<const MessagePinMessage *>(new_content);
+ if (old_->message_id != new_->message_id) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::GameScore: {
+ const auto *old_ = static_cast<const MessageGameScore *>(old_content);
+ const auto *new_ = static_cast<const MessageGameScore *>(new_content);
+ if (old_->game_message_id != new_->game_message_id || old_->game_id != new_->game_id ||
+ old_->score != new_->score) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ScreenshotTaken:
+ break;
+ case MessageContentType::ChatSetTtl: {
+ const auto *old_ = static_cast<const MessageChatSetTtl *>(old_content);
+ const auto *new_ = static_cast<const MessageChatSetTtl *>(new_content);
+ if (old_->ttl != new_->ttl) {
+ LOG(ERROR) << "Ttl has changed from " << old_->ttl << " to " << new_->ttl;
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Call: {
+ const auto *old_ = static_cast<const MessageCall *>(old_content);
+ const auto *new_ = static_cast<const MessageCall *>(new_content);
+ if (old_->call_id != new_->call_id || old_->is_video != new_->is_video) {
+ is_content_changed = true;
+ }
+ if (old_->duration != new_->duration || old_->discard_reason != new_->discard_reason) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::PaymentSuccessful: {
+ const auto *old_ = static_cast<const MessagePaymentSuccessful *>(old_content);
+ const auto *new_ = static_cast<const MessagePaymentSuccessful *>(new_content);
+ if (old_->invoice_dialog_id != new_->invoice_dialog_id || old_->invoice_message_id != new_->invoice_message_id ||
+ old_->currency != new_->currency || old_->total_amount != new_->total_amount ||
+ old_->invoice_payload != new_->invoice_payload || old_->shipping_option_id != new_->shipping_option_id ||
+ old_->telegram_payment_charge_id != new_->telegram_payment_charge_id ||
+ old_->provider_payment_charge_id != new_->provider_payment_charge_id ||
+ ((old_->order_info != nullptr || new_->order_info != nullptr) &&
+ (old_->order_info == nullptr || new_->order_info == nullptr || *old_->order_info != *new_->order_info)) ||
+ old_->is_recurring != new_->is_recurring || old_->is_first_recurring != new_->is_first_recurring) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ContactRegistered:
+ break;
+ case MessageContentType::ExpiredPhoto:
+ break;
+ case MessageContentType::ExpiredVideo:
+ break;
+ case MessageContentType::CustomServiceAction: {
+ const auto *old_ = static_cast<const MessageCustomServiceAction *>(old_content);
+ const auto *new_ = static_cast<const MessageCustomServiceAction *>(new_content);
+ if (old_->message != new_->message) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::WebsiteConnected: {
+ const auto *old_ = static_cast<const MessageWebsiteConnected *>(old_content);
+ const auto *new_ = static_cast<const MessageWebsiteConnected *>(new_content);
+ if (old_->domain_name != new_->domain_name) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::PassportDataSent: {
+ const auto *old_ = static_cast<const MessagePassportDataSent *>(old_content);
+ const auto *new_ = static_cast<const MessagePassportDataSent *>(new_content);
+ if (old_->types != new_->types) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::PassportDataReceived: {
+ const auto *old_ = static_cast<const MessagePassportDataReceived *>(old_content);
+ const auto *new_ = static_cast<const MessagePassportDataReceived *>(new_content);
+ if (old_->values != new_->values || old_->credentials != new_->credentials) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Poll: {
+ const auto *old_ = static_cast<const MessagePoll *>(old_content);
+ const auto *new_ = static_cast<const MessagePoll *>(new_content);
+ if (old_->poll_id != new_->poll_id) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Dice: {
+ const auto *old_ = static_cast<const MessageDice *>(old_content);
+ const auto *new_ = static_cast<const MessageDice *>(new_content);
+ if (old_->emoji != new_->emoji || old_->dice_value != new_->dice_value) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::ProximityAlertTriggered: {
+ const auto *old_ = static_cast<const MessageProximityAlertTriggered *>(old_content);
+ const auto *new_ = static_cast<const MessageProximityAlertTriggered *>(new_content);
+ if (old_->traveler_dialog_id != new_->traveler_dialog_id || old_->watcher_dialog_id != new_->watcher_dialog_id ||
+ old_->distance != new_->distance) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::GroupCall: {
+ const auto *old_ = static_cast<const MessageGroupCall *>(old_content);
+ const auto *new_ = static_cast<const MessageGroupCall *>(new_content);
+ if (old_->input_group_call_id != new_->input_group_call_id || old_->duration != new_->duration ||
+ old_->schedule_date != new_->schedule_date) {
+ need_update = true;
+ }
+ if (!old_->input_group_call_id.is_identical(new_->input_group_call_id)) {
+ is_content_changed = true;
+ }
+ break;
+ }
+ case MessageContentType::InviteToGroupCall: {
+ const auto *old_ = static_cast<const MessageInviteToGroupCall *>(old_content);
+ const auto *new_ = static_cast<const MessageInviteToGroupCall *>(new_content);
+ if (old_->input_group_call_id != new_->input_group_call_id || old_->user_ids != new_->user_ids) {
+ need_update = true;
+ }
+ if (!old_->input_group_call_id.is_identical(new_->input_group_call_id)) {
+ is_content_changed = true;
+ }
+ break;
+ }
+ case MessageContentType::ChatSetTheme: {
+ const auto *old_ = static_cast<const MessageChatSetTheme *>(old_content);
+ const auto *new_ = static_cast<const MessageChatSetTheme *>(new_content);
+ if (old_->emoji != new_->emoji) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::WebViewDataSent: {
+ const auto *old_ = static_cast<const MessageWebViewDataSent *>(old_content);
+ const auto *new_ = static_cast<const MessageWebViewDataSent *>(new_content);
+ if (old_->button_text != new_->button_text) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::WebViewDataReceived: {
+ const auto *old_ = static_cast<const MessageWebViewDataReceived *>(old_content);
+ const auto *new_ = static_cast<const MessageWebViewDataReceived *>(new_content);
+ if (old_->button_text != new_->button_text || old_->data != new_->data) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::GiftPremium: {
+ const auto *old_ = static_cast<const MessageGiftPremium *>(old_content);
+ const auto *new_ = static_cast<const MessageGiftPremium *>(new_content);
+ if (old_->currency != new_->currency || old_->amount != new_->amount || old_->months != new_->months) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::TopicCreate: {
+ const auto *old_ = static_cast<const MessageTopicCreate *>(old_content);
+ const auto *new_ = static_cast<const MessageTopicCreate *>(new_content);
+ if (old_->title != new_->title || old_->icon != new_->icon) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::TopicEdit: {
+ const auto *old_ = static_cast<const MessageTopicEdit *>(old_content);
+ const auto *new_ = static_cast<const MessageTopicEdit *>(new_content);
+ if (old_->edited_data != new_->edited_data) {
+ need_update = true;
+ }
+ break;
+ }
+ case MessageContentType::Unsupported: {
+ const auto *old_ = static_cast<const MessageUnsupported *>(old_content);
+ const auto *new_ = static_cast<const MessageUnsupported *>(new_content);
+ if (old_->version != new_->version) {
+ is_content_changed = true;
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+bool merge_message_content_file_id(Td *td, MessageContent *message_content, FileId new_file_id) {
+ if (!new_file_id.is_valid()) {
+ return false;
+ }
+
+ // secret chats only
+ LOG(INFO) << "Merge message content of a message with file " << new_file_id;
+ MessageContentType content_type = message_content->get_type();
+ switch (content_type) {
+ case MessageContentType::Animation: {
+ auto content = static_cast<MessageAnimation *>(message_content);
+ if (new_file_id != content->file_id) {
+ td->animations_manager_->merge_animations(new_file_id, content->file_id);
+ content->file_id = new_file_id;
+ return true;
+ }
+ break;
+ }
+ case MessageContentType::Audio: {
+ auto content = static_cast<MessageAudio *>(message_content);
+ if (new_file_id != content->file_id) {
+ td->audios_manager_->merge_audios(new_file_id, content->file_id);
+ content->file_id = new_file_id;
+ return true;
+ }
+ break;
+ }
+ case MessageContentType::Document: {
+ auto content = static_cast<MessageDocument *>(message_content);
+ if (new_file_id != content->file_id) {
+ td->documents_manager_->merge_documents(new_file_id, content->file_id);
+ content->file_id = new_file_id;
+ return true;
+ }
+ break;
+ }
+ case MessageContentType::Photo: {
+ auto content = static_cast<MessagePhoto *>(message_content);
+ Photo *photo = &content->photo;
+ if (!photo->photos.empty() && photo->photos.back().type == 'i') {
+ FileId &old_file_id = photo->photos.back().file_id;
+ if (old_file_id != new_file_id) {
+ LOG_STATUS(td->file_manager_->merge(new_file_id, old_file_id));
+ old_file_id = new_file_id;
+ return true;
+ }
+ }
+ break;
+ }
+ case MessageContentType::Sticker: {
+ auto content = static_cast<MessageSticker *>(message_content);
+ if (new_file_id != content->file_id) {
+ td->stickers_manager_->merge_stickers(new_file_id, content->file_id);
+ content->file_id = new_file_id;
+ return true;
+ }
+ break;
+ }
+ case MessageContentType::Video: {
+ auto content = static_cast<MessageVideo *>(message_content);
+ if (new_file_id != content->file_id) {
+ td->videos_manager_->merge_videos(new_file_id, content->file_id);
+ content->file_id = new_file_id;
+ return true;
+ }
+ break;
+ }
+ case MessageContentType::VideoNote: {
+ auto content = static_cast<MessageVideoNote *>(message_content);
+ if (new_file_id != content->file_id) {
+ td->video_notes_manager_->merge_video_notes(new_file_id, content->file_id);
+ content->file_id = new_file_id;
+ return true;
+ }
+ break;
+ }
+ case MessageContentType::VoiceNote: {
+ auto content = static_cast<MessageVoiceNote *>(message_content);
+ if (new_file_id != content->file_id) {
+ td->voice_notes_manager_->merge_voice_notes(new_file_id, content->file_id);
+ content->file_id = new_file_id;
+ return true;
+ }
+ break;
+ }
+ case MessageContentType::Contact:
+ case MessageContentType::Game:
+ case MessageContentType::Invoice:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Text:
+ case MessageContentType::Venue:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Unsupported:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::Poll:
+ case MessageContentType::Dice:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ LOG(ERROR) << "Receive new file " << new_file_id << " in a sent message of the type " << content_type;
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ return false;
+}
+
+static bool can_be_animated_emoji(const FormattedText &text) {
+ if (!is_emoji(text.text)) {
+ return false;
+ }
+ if (text.entities.empty()) {
+ return true;
+ }
+ if (text.entities.size() == 1 && text.entities[0].type == MessageEntity::Type::CustomEmoji &&
+ text.entities[0].offset == 0 && static_cast<size_t>(text.entities[0].length) == utf8_utf16_length(text.text) &&
+ text.entities[0].custom_emoji_id.is_valid()) {
+ return true;
+ }
+ return false;
+}
+
+static CustomEmojiId get_custom_emoji_id(const FormattedText &text) {
+ return text.entities.empty() ? CustomEmojiId() : text.entities[0].custom_emoji_id;
+}
+
+void register_message_content(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ const char *source) {
+ switch (content->get_type()) {
+ case MessageContentType::Text: {
+ auto text = static_cast<const MessageText *>(content);
+ if (text->web_page_id.is_valid()) {
+ td->web_pages_manager_->register_web_page(text->web_page_id, full_message_id, source);
+ } else if (can_be_animated_emoji(text->text)) {
+ td->stickers_manager_->register_emoji(text->text.text, get_custom_emoji_id(text->text), full_message_id,
+ source);
+ }
+ return;
+ }
+ case MessageContentType::VideoNote:
+ return td->video_notes_manager_->register_video_note(static_cast<const MessageVideoNote *>(content)->file_id,
+ full_message_id, source);
+ case MessageContentType::VoiceNote:
+ return td->voice_notes_manager_->register_voice_note(static_cast<const MessageVoiceNote *>(content)->file_id,
+ full_message_id, source);
+ case MessageContentType::Poll:
+ return td->poll_manager_->register_poll(static_cast<const MessagePoll *>(content)->poll_id, full_message_id,
+ source);
+ case MessageContentType::Dice: {
+ auto dice = static_cast<const MessageDice *>(content);
+ return td->stickers_manager_->register_dice(dice->emoji, dice->dice_value, full_message_id, source);
+ }
+ case MessageContentType::GiftPremium:
+ return td->stickers_manager_->register_premium_gift(static_cast<const MessageGiftPremium *>(content)->months,
+ full_message_id, source);
+ default:
+ return;
+ }
+}
+
+void reregister_message_content(Td *td, const MessageContent *old_content, const MessageContent *new_content,
+ FullMessageId full_message_id, const char *source) {
+ auto old_content_type = old_content->get_type();
+ auto new_content_type = new_content->get_type();
+ if (old_content_type == new_content_type) {
+ switch (old_content_type) {
+ case MessageContentType::Text: {
+ auto old_text = static_cast<const MessageText *>(old_content);
+ auto new_text = static_cast<const MessageText *>(new_content);
+ if (old_text->web_page_id == new_text->web_page_id &&
+ (old_text->text == new_text->text ||
+ (!can_be_animated_emoji(old_text->text) && !can_be_animated_emoji(new_text->text)))) {
+ return;
+ }
+ break;
+ }
+ case MessageContentType::VideoNote:
+ if (static_cast<const MessageVideoNote *>(old_content)->file_id ==
+ static_cast<const MessageVideoNote *>(new_content)->file_id) {
+ return;
+ }
+ break;
+ case MessageContentType::VoiceNote:
+ if (static_cast<const MessageVoiceNote *>(old_content)->file_id ==
+ static_cast<const MessageVoiceNote *>(new_content)->file_id) {
+ return;
+ }
+ break;
+ case MessageContentType::Poll:
+ if (static_cast<const MessagePoll *>(old_content)->poll_id ==
+ static_cast<const MessagePoll *>(new_content)->poll_id) {
+ return;
+ }
+ break;
+ case MessageContentType::Dice:
+ if (static_cast<const MessageDice *>(old_content)->emoji ==
+ static_cast<const MessageDice *>(new_content)->emoji &&
+ static_cast<const MessageDice *>(old_content)->dice_value ==
+ static_cast<const MessageDice *>(new_content)->dice_value) {
+ return;
+ }
+ break;
+ case MessageContentType::GiftPremium:
+ if (static_cast<const MessageGiftPremium *>(old_content)->months ==
+ static_cast<const MessageGiftPremium *>(new_content)->months) {
+ return;
+ }
+ break;
+ default:
+ return;
+ }
+ }
+ unregister_message_content(td, old_content, full_message_id, source);
+ register_message_content(td, new_content, full_message_id, source);
+}
+
+void unregister_message_content(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ const char *source) {
+ switch (content->get_type()) {
+ case MessageContentType::Text: {
+ auto text = static_cast<const MessageText *>(content);
+ if (text->web_page_id.is_valid()) {
+ td->web_pages_manager_->unregister_web_page(text->web_page_id, full_message_id, source);
+ } else if (can_be_animated_emoji(text->text)) {
+ td->stickers_manager_->unregister_emoji(text->text.text, get_custom_emoji_id(text->text), full_message_id,
+ source);
+ }
+ return;
+ }
+ case MessageContentType::VideoNote:
+ return td->video_notes_manager_->unregister_video_note(static_cast<const MessageVideoNote *>(content)->file_id,
+ full_message_id, source);
+ case MessageContentType::VoiceNote:
+ return td->voice_notes_manager_->unregister_voice_note(static_cast<const MessageVoiceNote *>(content)->file_id,
+ full_message_id, source);
+ case MessageContentType::Poll:
+ return td->poll_manager_->unregister_poll(static_cast<const MessagePoll *>(content)->poll_id, full_message_id,
+ source);
+ case MessageContentType::Dice: {
+ auto dice = static_cast<const MessageDice *>(content);
+ return td->stickers_manager_->unregister_dice(dice->emoji, dice->dice_value, full_message_id, source);
+ }
+ case MessageContentType::GiftPremium:
+ return td->stickers_manager_->unregister_premium_gift(static_cast<const MessageGiftPremium *>(content)->months,
+ full_message_id, source);
+ default:
+ return;
+ }
+}
+
+template <class ToT, class FromT>
+static tl_object_ptr<ToT> secret_to_telegram(FromT &from);
+
+// photoSizeEmpty type:string = PhotoSize;
+static auto secret_to_telegram(secret_api::photoSizeEmpty &empty) {
+ if (!clean_input_string(empty.type_)) {
+ empty.type_.clear();
+ }
+ return make_tl_object<telegram_api::photoSizeEmpty>(empty.type_);
+}
+
+// photoSize type:string location:FileLocation w:int h:int size:int = PhotoSize;
+static auto secret_to_telegram(secret_api::photoSize &photo_size) {
+ if (!clean_input_string(photo_size.type_)) {
+ photo_size.type_.clear();
+ }
+ return make_tl_object<telegram_api::photoSize>(photo_size.type_, photo_size.w_, photo_size.h_, photo_size.size_);
+}
+
+// photoCachedSize type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
+static auto secret_to_telegram(secret_api::photoCachedSize &photo_size) {
+ if (!clean_input_string(photo_size.type_)) {
+ photo_size.type_.clear();
+ }
+ return make_tl_object<telegram_api::photoCachedSize>(photo_size.type_, photo_size.w_, photo_size.h_,
+ photo_size.bytes_.clone());
+}
+
+// documentAttributeImageSize w:int h:int = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeImageSize &image_size) {
+ return make_tl_object<telegram_api::documentAttributeImageSize>(image_size.w_, image_size.h_);
+}
+
+// documentAttributeAnimated = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeAnimated &animated) {
+ return make_tl_object<telegram_api::documentAttributeAnimated>();
+}
+
+// documentAttributeSticker23 = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeSticker23 &sticker) {
+ return make_tl_object<telegram_api::documentAttributeSticker>(
+ 0, false /*ignored*/, "", make_tl_object<telegram_api::inputStickerSetEmpty>(), nullptr);
+}
+
+static auto secret_to_telegram(secret_api::inputStickerSetEmpty &sticker_set) {
+ return make_tl_object<telegram_api::inputStickerSetEmpty>();
+}
+
+static auto secret_to_telegram(secret_api::inputStickerSetShortName &sticker_set) {
+ if (!clean_input_string(sticker_set.short_name_)) {
+ sticker_set.short_name_.clear();
+ }
+ return make_tl_object<telegram_api::inputStickerSetShortName>(sticker_set.short_name_);
+}
+
+// documentAttributeSticker alt:string stickerset:InputStickerSet = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeSticker &sticker) {
+ if (!clean_input_string(sticker.alt_)) {
+ sticker.alt_.clear();
+ }
+ return make_tl_object<telegram_api::documentAttributeSticker>(
+ 0, false /*ignored*/, sticker.alt_, secret_to_telegram<telegram_api::InputStickerSet>(*sticker.stickerset_),
+ nullptr);
+}
+
+// documentAttributeVideo23 duration:int w:int h:int = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeVideo23 &video) {
+ return make_tl_object<telegram_api::documentAttributeVideo>(0, false /*ignored*/, false /*ignored*/, video.duration_,
+ video.w_, video.h_);
+}
+
+// documentAttributeFilename file_name:string = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeFilename &filename) {
+ if (!clean_input_string(filename.file_name_)) {
+ filename.file_name_.clear();
+ }
+ return make_tl_object<telegram_api::documentAttributeFilename>(filename.file_name_);
+}
+
+// documentAttributeVideo flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeVideo &video) {
+ return make_tl_object<telegram_api::documentAttributeVideo>(
+ (video.flags_ & secret_api::documentAttributeVideo::ROUND_MESSAGE_MASK) != 0
+ ? telegram_api::documentAttributeVideo::ROUND_MESSAGE_MASK
+ : 0,
+ video.round_message_, false, video.duration_, video.w_, video.h_);
+}
+
+static auto telegram_documentAttributeAudio(bool is_voice_note, int duration, string title, string performer,
+ BufferSlice waveform) {
+ if (!clean_input_string(title)) {
+ title.clear();
+ }
+ if (!clean_input_string(performer)) {
+ performer.clear();
+ }
+
+ int32 flags = 0;
+ if (is_voice_note) {
+ flags |= telegram_api::documentAttributeAudio::VOICE_MASK;
+ }
+ if (!title.empty()) {
+ flags |= telegram_api::documentAttributeAudio::TITLE_MASK;
+ }
+ if (!performer.empty()) {
+ flags |= telegram_api::documentAttributeAudio::PERFORMER_MASK;
+ }
+ if (!waveform.empty()) {
+ flags |= telegram_api::documentAttributeAudio::WAVEFORM_MASK;
+ }
+ return make_tl_object<telegram_api::documentAttributeAudio>(flags, is_voice_note, duration, std::move(title),
+ std::move(performer), std::move(waveform));
+}
+
+// documentAttributeAudio23 duration:int = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeAudio23 &audio) {
+ return telegram_documentAttributeAudio(false, audio.duration_, "", "", Auto());
+}
+// documentAttributeAudio45 duration:int title:string performer:string = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeAudio45 &audio) {
+ return telegram_documentAttributeAudio(false, audio.duration_, audio.title_, audio.performer_, Auto());
+}
+
+// documentAttributeAudio flags:# voice:flags.10?true duration:int title:flags.0?string
+// performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
+static auto secret_to_telegram(secret_api::documentAttributeAudio &audio) {
+ return telegram_documentAttributeAudio((audio.flags_ & secret_api::documentAttributeAudio::VOICE_MASK) != 0,
+ audio.duration_, audio.title_, audio.performer_, audio.waveform_.clone());
+}
+
+static auto secret_to_telegram(std::vector<tl_object_ptr<secret_api::DocumentAttribute>> &attributes) {
+ std::vector<tl_object_ptr<telegram_api::DocumentAttribute>> res;
+ for (auto &attribute : attributes) {
+ auto telegram_attribute = secret_to_telegram<telegram_api::DocumentAttribute>(*attribute);
+ if (telegram_attribute) {
+ res.push_back(std::move(telegram_attribute));
+ }
+ }
+ return res;
+}
+
+// decryptedMessageMediaExternalDocument id:long access_hash:long date:int mime_type:string size:int
+// thumb:PhotoSize dc_id:int attributes:Vector<DocumentAttribute> = DecryptedMessageMedia;
+static auto secret_to_telegram_document(secret_api::decryptedMessageMediaExternalDocument &from) {
+ if (!clean_input_string(from.mime_type_)) {
+ from.mime_type_.clear();
+ }
+ vector<telegram_api::object_ptr<telegram_api::PhotoSize>> thumbnails;
+ thumbnails.push_back(secret_to_telegram<telegram_api::PhotoSize>(*from.thumb_));
+ return make_tl_object<telegram_api::document>(0, from.id_, from.access_hash_, BufferSlice(), from.date_,
+ from.mime_type_, from.size_, std::move(thumbnails), Auto(), from.dc_id_,
+ secret_to_telegram(from.attributes_));
+}
+
+template <class ToT, class FromT>
+static tl_object_ptr<ToT> secret_to_telegram(FromT &from) {
+ tl_object_ptr<ToT> res;
+ downcast_call(from, [&](auto &p) { res = secret_to_telegram(p); });
+ return res;
+}
+
+static unique_ptr<MessageContent> get_document_message_content(Document &&parsed_document, FormattedText &&caption,
+ bool is_opened, bool is_premium) {
+ auto file_id = parsed_document.file_id;
+ if (!parsed_document.empty()) {
+ CHECK(file_id.is_valid());
+ }
+ switch (parsed_document.type) {
+ case Document::Type::Animation:
+ return make_unique<MessageAnimation>(file_id, std::move(caption));
+ case Document::Type::Audio:
+ return make_unique<MessageAudio>(file_id, std::move(caption));
+ case Document::Type::General:
+ return make_unique<MessageDocument>(file_id, std::move(caption));
+ case Document::Type::Sticker:
+ return make_unique<MessageSticker>(file_id, is_premium);
+ case Document::Type::Unknown:
+ return make_unique<MessageUnsupported>();
+ case Document::Type::Video:
+ return make_unique<MessageVideo>(file_id, std::move(caption));
+ case Document::Type::VideoNote:
+ return make_unique<MessageVideoNote>(file_id, is_opened);
+ case Document::Type::VoiceNote:
+ return make_unique<MessageVoiceNote>(file_id, std::move(caption), is_opened);
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+static unique_ptr<MessageContent> get_document_message_content(Td *td, tl_object_ptr<telegram_api::document> &&document,
+ DialogId owner_dialog_id, FormattedText &&caption,
+ bool is_opened, bool is_premium,
+ MultiPromiseActor *load_data_multipromise_ptr) {
+ return get_document_message_content(
+ td->documents_manager_->on_get_document(std::move(document), owner_dialog_id, load_data_multipromise_ptr),
+ std::move(caption), is_opened, is_premium);
+}
+
+unique_ptr<MessageContent> get_secret_message_content(
+ Td *td, string message_text, unique_ptr<EncryptedFile> file,
+ tl_object_ptr<secret_api::DecryptedMessageMedia> &&media_ptr,
+ vector<tl_object_ptr<secret_api::MessageEntity>> &&secret_entities, DialogId owner_dialog_id,
+ MultiPromiseActor &load_data_multipromise, bool is_premium) {
+ int32 constructor_id = media_ptr == nullptr ? secret_api::decryptedMessageMediaEmpty::ID : media_ptr->get_id();
+ auto caption = [&] {
+ switch (constructor_id) {
+ case secret_api::decryptedMessageMediaVideo::ID: {
+ auto media = static_cast<secret_api::decryptedMessageMediaVideo *>(media_ptr.get());
+ return std::move(media->caption_);
+ }
+ case secret_api::decryptedMessageMediaPhoto::ID: {
+ auto media = static_cast<secret_api::decryptedMessageMediaPhoto *>(media_ptr.get());
+ return std::move(media->caption_);
+ }
+ case secret_api::decryptedMessageMediaDocument46::ID: {
+ auto media = static_cast<secret_api::decryptedMessageMediaDocument46 *>(media_ptr.get());
+ return std::move(media->caption_);
+ }
+ case secret_api::decryptedMessageMediaDocument::ID: {
+ auto media = static_cast<secret_api::decryptedMessageMediaDocument *>(media_ptr.get());
+ return std::move(media->caption_);
+ }
+ default:
+ return string();
+ }
+ }();
+ if (!clean_input_string(caption)) {
+ caption.clear();
+ }
+
+ if (message_text.empty()) {
+ message_text = std::move(caption);
+ } else if (!caption.empty()) {
+ message_text = message_text + "\n\n" + caption;
+ }
+
+ auto entities = get_message_entities(td, std::move(secret_entities), is_premium, load_data_multipromise);
+ auto status = fix_formatted_text(message_text, entities, true, false, true, td->auth_manager_->is_bot(), false);
+ if (status.is_error()) {
+ LOG(WARNING) << "Receive error " << status << " while parsing secret message \"" << message_text
+ << "\" with entities " << format::as_array(entities);
+ if (!clean_input_string(message_text)) {
+ message_text.clear();
+ }
+ entities = find_entities(message_text, true, td->auth_manager_->is_bot());
+ }
+
+ // support of old layer and old constructions
+ switch (constructor_id) {
+ case secret_api::decryptedMessageMediaDocument46::ID: {
+ auto media = move_tl_object_as<secret_api::decryptedMessageMediaDocument46>(media_ptr);
+ media_ptr = make_tl_object<secret_api::decryptedMessageMediaDocument>(
+ std::move(media->thumb_), media->thumb_w_, media->thumb_h_, media->mime_type_, media->size_,
+ std::move(media->key_), std::move(media->iv_), std::move(media->attributes_), string());
+
+ constructor_id = secret_api::decryptedMessageMediaDocument::ID;
+ break;
+ }
+ case secret_api::decryptedMessageMediaVideo::ID: {
+ auto media = move_tl_object_as<secret_api::decryptedMessageMediaVideo>(media_ptr);
+ vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
+ attributes.emplace_back(
+ make_tl_object<secret_api::documentAttributeVideo>(0, false, media->duration_, media->w_, media->h_));
+ media_ptr = make_tl_object<secret_api::decryptedMessageMediaDocument>(
+ std::move(media->thumb_), media->thumb_w_, media->thumb_h_, media->mime_type_, media->size_,
+ std::move(media->key_), std::move(media->iv_), std::move(attributes), string());
+
+ constructor_id = secret_api::decryptedMessageMediaDocument::ID;
+ break;
+ }
+ default:
+ break;
+ }
+
+ bool is_media_empty = false;
+ switch (constructor_id) {
+ case secret_api::decryptedMessageMediaEmpty::ID:
+ if (message_text.empty()) {
+ LOG(ERROR) << "Receive empty message text and media";
+ }
+ is_media_empty = true;
+ break;
+ case secret_api::decryptedMessageMediaGeoPoint::ID: {
+ auto media = move_tl_object_as<secret_api::decryptedMessageMediaGeoPoint>(media_ptr);
+
+ auto m = make_unique<MessageLocation>(Location(media));
+ if (m->location.empty()) {
+ is_media_empty = true;
+ break;
+ }
+
+ return std::move(m);
+ }
+ case secret_api::decryptedMessageMediaVenue::ID: {
+ auto media = move_tl_object_as<secret_api::decryptedMessageMediaVenue>(media_ptr);
+
+ if (!clean_input_string(media->title_)) {
+ media->title_.clear();
+ }
+ if (!clean_input_string(media->address_)) {
+ media->address_.clear();
+ }
+ if (!clean_input_string(media->provider_)) {
+ media->provider_.clear();
+ }
+ if (!clean_input_string(media->venue_id_)) {
+ media->venue_id_.clear();
+ }
+
+ auto m = make_unique<MessageVenue>(Venue(Location(media->lat_, media->long_, 0.0, 0), std::move(media->title_),
+ std::move(media->address_), std::move(media->provider_),
+ std::move(media->venue_id_), string()));
+ if (m->venue.empty()) {
+ is_media_empty = true;
+ break;
+ }
+
+ return std::move(m);
+ }
+ case secret_api::decryptedMessageMediaContact::ID: {
+ auto media = move_tl_object_as<secret_api::decryptedMessageMediaContact>(media_ptr);
+ if (!clean_input_string(media->phone_number_)) {
+ media->phone_number_.clear();
+ }
+ if (!clean_input_string(media->first_name_)) {
+ media->first_name_.clear();
+ }
+ if (!clean_input_string(media->last_name_)) {
+ media->last_name_.clear();
+ }
+ return make_unique<MessageContact>(Contact(std::move(media->phone_number_), std::move(media->first_name_),
+ std::move(media->last_name_), string(), UserId()));
+ }
+ case secret_api::decryptedMessageMediaWebPage::ID: {
+ auto media = move_tl_object_as<secret_api::decryptedMessageMediaWebPage>(media_ptr);
+ if (!clean_input_string(media->url_)) {
+ media->url_.clear();
+ }
+ auto r_http_url = parse_url(media->url_);
+ if (r_http_url.is_error()) {
+ is_media_empty = true;
+ break;
+ }
+ auto url = r_http_url.ok().get_url();
+
+ auto result = make_unique<MessageText>(FormattedText{std::move(message_text), std::move(entities)}, WebPageId());
+ td->web_pages_manager_->get_web_page_by_url(
+ url,
+ PromiseCreator::lambda([&web_page_id = result->web_page_id, promise = load_data_multipromise.get_promise()](
+ Result<WebPageId> r_web_page_id) mutable {
+ if (r_web_page_id.is_ok()) {
+ web_page_id = r_web_page_id.move_as_ok();
+ }
+ promise.set_value(Unit());
+ }));
+ return std::move(result);
+ }
+ case secret_api::decryptedMessageMediaExternalDocument::ID: {
+ auto media = move_tl_object_as<secret_api::decryptedMessageMediaExternalDocument>(media_ptr);
+ return get_document_message_content(td, secret_to_telegram_document(*media), owner_dialog_id,
+ FormattedText{std::move(message_text), std::move(entities)}, false,
+ is_premium, &load_data_multipromise);
+ }
+ default:
+ break;
+ }
+ if (file == nullptr && !is_media_empty) {
+ LOG(ERROR) << "Received secret message with media, but without a file";
+ is_media_empty = true;
+ }
+ if (is_media_empty) {
+ return create_text_message_content(std::move(message_text), std::move(entities), WebPageId());
+ }
+ switch (constructor_id) {
+ case secret_api::decryptedMessageMediaPhoto::ID: {
+ auto media = move_tl_object_as<secret_api::decryptedMessageMediaPhoto>(media_ptr);
+ return make_unique<MessagePhoto>(
+ get_encrypted_file_photo(td->file_manager_.get(), std::move(file), std::move(media), owner_dialog_id),
+ FormattedText{std::move(message_text), std::move(entities)});
+ }
+ case secret_api::decryptedMessageMediaDocument::ID: {
+ auto media = move_tl_object_as<secret_api::decryptedMessageMediaDocument>(media_ptr);
+ if (!clean_input_string(media->mime_type_)) {
+ media->mime_type_.clear();
+ }
+ auto attributes = secret_to_telegram(media->attributes_);
+ for (auto &attribute : attributes) {
+ CHECK(attribute != nullptr);
+ if (attribute->get_id() == telegram_api::documentAttributeSticker::ID) {
+ auto attribute_sticker = static_cast<telegram_api::documentAttributeSticker *>(attribute.get());
+ CHECK(attribute_sticker->stickerset_ != nullptr);
+ if (attribute_sticker->stickerset_->get_id() != telegram_api::inputStickerSetEmpty::ID) {
+ attribute_sticker->stickerset_ = make_tl_object<telegram_api::inputStickerSetEmpty>();
+ }
+ }
+ }
+
+ media->attributes_.clear();
+ auto document = td->documents_manager_->on_get_document(
+ {std::move(file), std::move(media), std::move(attributes)}, owner_dialog_id);
+ return get_document_message_content(std::move(document), {std::move(message_text), std::move(entities)}, false,
+ false);
+ }
+ default:
+ LOG(ERROR) << "Unsupported: " << to_string(media_ptr);
+ return make_unique<MessageUnsupported>();
+ }
+}
+
+unique_ptr<MessageContent> get_message_content(Td *td, FormattedText message,
+ tl_object_ptr<telegram_api::MessageMedia> &&media_ptr,
+ DialogId owner_dialog_id, bool is_content_read, UserId via_bot_user_id,
+ int32 *ttl, bool *disable_web_page_preview, const char *source) {
+ if (!td->auth_manager_->was_authorized() && !G()->close_flag() && media_ptr != nullptr &&
+ media_ptr->get_id() != telegram_api::messageMediaEmpty::ID) {
+ LOG(ERROR) << "Receive without authorization from " << source << ": " << to_string(media_ptr);
+ media_ptr = nullptr;
+ }
+ if (disable_web_page_preview != nullptr) {
+ *disable_web_page_preview = false;
+ }
+
+ int32 constructor_id = media_ptr == nullptr ? telegram_api::messageMediaEmpty::ID : media_ptr->get_id();
+ switch (constructor_id) {
+ case telegram_api::messageMediaEmpty::ID:
+ if (message.text.empty()) {
+ LOG(ERROR) << "Receive empty message text and media from " << source;
+ }
+ if (disable_web_page_preview != nullptr) {
+ *disable_web_page_preview = true;
+ }
+ return make_unique<MessageText>(std::move(message), WebPageId());
+ case telegram_api::messageMediaPhoto::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaPhoto>(media_ptr);
+ if (media->photo_ == nullptr) {
+ if ((media->flags_ & telegram_api::messageMediaPhoto::TTL_SECONDS_MASK) == 0) {
+ LOG(ERROR) << "Receive messageMediaPhoto without photo and TTL from " << source << ": "
+ << oneline(to_string(media));
+ break;
+ }
+
+ return make_unique<MessageExpiredPhoto>();
+ }
+
+ auto photo = get_photo(td->file_manager_.get(), std::move(media->photo_), owner_dialog_id);
+ if (photo.is_empty()) {
+ return make_unique<MessageExpiredPhoto>();
+ }
+
+ if (ttl != nullptr && (media->flags_ & telegram_api::messageMediaPhoto::TTL_SECONDS_MASK) != 0) {
+ *ttl = media->ttl_seconds_;
+ }
+ return make_unique<MessagePhoto>(std::move(photo), std::move(message));
+ }
+ case telegram_api::messageMediaDice::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaDice>(media_ptr);
+
+ auto m = td::make_unique<MessageDice>(media->emoticon_, media->value_);
+ if (!m->is_valid()) {
+ break;
+ }
+
+ return std::move(m);
+ }
+ case telegram_api::messageMediaGeo::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaGeo>(media_ptr);
+
+ auto m = make_unique<MessageLocation>(Location(media->geo_));
+ if (m->location.empty()) {
+ break;
+ }
+
+ return std::move(m);
+ }
+ case telegram_api::messageMediaGeoLive::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaGeoLive>(media_ptr);
+ auto location = Location(media->geo_);
+ if (location.empty()) {
+ break;
+ }
+
+ int32 period = media->period_;
+ if (period <= 0) {
+ LOG(ERROR) << "Receive wrong live location period = " << period << " from " << source;
+ return make_unique<MessageLocation>(std::move(location));
+ }
+ return make_unique<MessageLiveLocation>(std::move(location), period, media->heading_,
+ media->proximity_notification_radius_);
+ }
+ case telegram_api::messageMediaVenue::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaVenue>(media_ptr);
+ auto m = make_unique<MessageVenue>(Venue(media->geo_, std::move(media->title_), std::move(media->address_),
+ std::move(media->provider_), std::move(media->venue_id_),
+ std::move(media->venue_type_)));
+ if (m->venue.empty()) {
+ break;
+ }
+
+ return std::move(m);
+ }
+ case telegram_api::messageMediaContact::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaContact>(media_ptr);
+ if (media->user_id_ != 0) {
+ td->contacts_manager_->get_user_id_object(UserId(media->user_id_),
+ "MessageMediaContact"); // to ensure updateUser
+ }
+ return make_unique<MessageContact>(Contact(std::move(media->phone_number_), std::move(media->first_name_),
+ std::move(media->last_name_), std::move(media->vcard_),
+ UserId(media->user_id_)));
+ }
+ case telegram_api::messageMediaDocument::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaDocument>(media_ptr);
+ if (media->document_ == nullptr) {
+ if ((media->flags_ & telegram_api::messageMediaDocument::TTL_SECONDS_MASK) == 0) {
+ LOG(ERROR) << "Receive messageMediaDocument without document and TTL from " << source << ": "
+ << oneline(to_string(media));
+ break;
+ }
+
+ return make_unique<MessageExpiredVideo>();
+ }
+
+ auto document_ptr = std::move(media->document_);
+ int32 document_id = document_ptr->get_id();
+ if (document_id == telegram_api::documentEmpty::ID) {
+ break;
+ }
+ CHECK(document_id == telegram_api::document::ID);
+
+ if (ttl != nullptr && (media->flags_ & telegram_api::messageMediaDocument::TTL_SECONDS_MASK) != 0) {
+ *ttl = media->ttl_seconds_;
+ }
+ return get_document_message_content(td, move_tl_object_as<telegram_api::document>(document_ptr), owner_dialog_id,
+ std::move(message), is_content_read, !media->nopremium_, nullptr);
+ }
+ case telegram_api::messageMediaGame::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaGame>(media_ptr);
+
+ auto m = make_unique<MessageGame>(
+ Game(td, via_bot_user_id, std::move(media->game_), std::move(message), owner_dialog_id));
+ if (m->game.is_empty()) {
+ break;
+ }
+ return std::move(m);
+ }
+ case telegram_api::messageMediaInvoice::ID:
+ return td::make_unique<MessageInvoice>(InputInvoice(
+ move_tl_object_as<telegram_api::messageMediaInvoice>(media_ptr), td, owner_dialog_id, std::move(message)));
+ case telegram_api::messageMediaWebPage::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaWebPage>(media_ptr);
+ if (disable_web_page_preview != nullptr) {
+ *disable_web_page_preview = (media->webpage_ == nullptr);
+ }
+ auto web_page_id = td->web_pages_manager_->on_get_web_page(std::move(media->webpage_), owner_dialog_id);
+ return make_unique<MessageText>(std::move(message), web_page_id);
+ }
+ case telegram_api::messageMediaPoll::ID: {
+ auto media = move_tl_object_as<telegram_api::messageMediaPoll>(media_ptr);
+ auto poll_id =
+ td->poll_manager_->on_get_poll(PollId(), std::move(media->poll_), std::move(media->results_), source);
+ if (!poll_id.is_valid()) {
+ break;
+ }
+ return make_unique<MessagePoll>(poll_id);
+ }
+ case telegram_api::messageMediaUnsupported::ID:
+ return make_unique<MessageUnsupported>();
+ default:
+ UNREACHABLE();
+ }
+
+ // explicit empty media message
+ if (disable_web_page_preview != nullptr) {
+ *disable_web_page_preview = true;
+ }
+ return make_unique<MessageText>(std::move(message), WebPageId());
+}
+
+unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const MessageContent *content,
+ MessageContentDupType type, MessageCopyOptions &&copy_options) {
+ CHECK(content != nullptr);
+ if (copy_options.send_copy) {
+ CHECK(type == MessageContentDupType::Copy || type == MessageContentDupType::ServerCopy);
+ }
+ if (type != MessageContentDupType::Forward && type != MessageContentDupType::SendViaBot &&
+ !can_have_input_media(td, content, type == MessageContentDupType::ServerCopy)) {
+ return nullptr;
+ }
+
+ bool to_secret = dialog_id.get_type() == DialogType::SecretChat;
+ auto fix_file_id = [dialog_id, to_secret, file_manager = td->file_manager_.get()](FileId file_id) {
+ auto file_view = file_manager->get_file_view(file_id);
+ if (to_secret && !file_view.is_encrypted_secret()) {
+ auto download_file_id = file_manager->dup_file_id(file_id, "dup_message_content to secret");
+ file_id = file_manager
+ ->register_generate(FileType::Encrypted, FileLocationSource::FromServer, file_view.suggested_path(),
+ PSTRING() << "#file_id#" << download_file_id.get(), dialog_id, file_view.size())
+ .ok();
+ }
+ return file_manager->dup_file_id(file_id, "dup_message_content");
+ };
+
+ FileId thumbnail_file_id;
+ if (to_secret) {
+ thumbnail_file_id = get_message_content_thumbnail_file_id(content, td);
+ }
+ auto replace_caption = (type == MessageContentDupType::Copy || type == MessageContentDupType::ServerCopy) &&
+ copy_options.replace_caption;
+ switch (content->get_type()) {
+ case MessageContentType::Animation: {
+ auto result = make_unique<MessageAnimation>(*static_cast<const MessageAnimation *>(content));
+ if (replace_caption) {
+ result->caption = std::move(copy_options.new_caption);
+ }
+ if (td->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
+ return std::move(result);
+ }
+ result->file_id = td->animations_manager_->dup_animation(fix_file_id(result->file_id), result->file_id);
+ CHECK(result->file_id.is_valid());
+ return std::move(result);
+ }
+ case MessageContentType::Audio: {
+ auto result = make_unique<MessageAudio>(*static_cast<const MessageAudio *>(content));
+ if (replace_caption) {
+ result->caption = std::move(copy_options.new_caption);
+ }
+ if (td->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
+ return std::move(result);
+ }
+ result->file_id = td->audios_manager_->dup_audio(fix_file_id(result->file_id), result->file_id);
+ CHECK(result->file_id.is_valid());
+ return std::move(result);
+ }
+ case MessageContentType::Contact:
+ return make_unique<MessageContact>(*static_cast<const MessageContact *>(content));
+ case MessageContentType::Dice: {
+ auto result = td::make_unique<MessageDice>(*static_cast<const MessageDice *>(content));
+ if (type != MessageContentDupType::Forward) {
+ result->dice_value = 0;
+ }
+ return std::move(result);
+ }
+ case MessageContentType::Document: {
+ auto result = make_unique<MessageDocument>(*static_cast<const MessageDocument *>(content));
+ if (replace_caption) {
+ result->caption = std::move(copy_options.new_caption);
+ }
+ if (td->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
+ return std::move(result);
+ }
+ result->file_id = td->documents_manager_->dup_document(fix_file_id(result->file_id), result->file_id);
+ CHECK(result->file_id.is_valid());
+ return std::move(result);
+ }
+ case MessageContentType::Game:
+ return make_unique<MessageGame>(*static_cast<const MessageGame *>(content));
+ case MessageContentType::Invoice:
+ if (type == MessageContentDupType::Copy) {
+ return nullptr;
+ }
+ return make_unique<MessageInvoice>(*static_cast<const MessageInvoice *>(content));
+ case MessageContentType::LiveLocation:
+ if (!to_secret && (type == MessageContentDupType::Send || type == MessageContentDupType::SendViaBot)) {
+ return make_unique<MessageLiveLocation>(*static_cast<const MessageLiveLocation *>(content));
+ } else {
+ return make_unique<MessageLocation>(Location(static_cast<const MessageLiveLocation *>(content)->location));
+ }
+ case MessageContentType::Location:
+ return make_unique<MessageLocation>(*static_cast<const MessageLocation *>(content));
+ case MessageContentType::Photo: {
+ auto result = make_unique<MessagePhoto>(*static_cast<const MessagePhoto *>(content));
+ if (replace_caption) {
+ result->caption = std::move(copy_options.new_caption);
+ }
+
+ CHECK(!result->photo.photos.empty());
+ if ((result->photo.photos.size() > 2 || result->photo.photos.back().type != 'i') && !to_secret) {
+ // already sent photo
+ // having remote location is not enough to have InputMedia, because the file may not have valid file_reference
+ // also file_id needs to be duped, because upload can be called to repair the file_reference and every upload
+ // request must have unique file_id
+ if (!td->auth_manager_->is_bot()) {
+ result->photo.photos.back().file_id = fix_file_id(result->photo.photos.back().file_id);
+ }
+ return std::move(result);
+ }
+
+ // Find 'i' or largest
+ PhotoSize photo;
+ for (const auto &size : result->photo.photos) {
+ if (size.type == 'i') {
+ photo = size;
+ }
+ }
+ if (photo.type == 0) {
+ for (const auto &size : result->photo.photos) {
+ if (photo.type == 0 || photo < size) {
+ photo = size;
+ }
+ }
+ }
+
+ // Find 't' or smallest
+ PhotoSize thumbnail;
+ for (const auto &size : result->photo.photos) {
+ if (size.type == 't') {
+ thumbnail = size;
+ }
+ }
+ if (thumbnail.type == 0) {
+ for (const auto &size : result->photo.photos) {
+ if (size.type != photo.type && (thumbnail.type == 0 || size < thumbnail)) {
+ thumbnail = size;
+ }
+ }
+ }
+
+ result->photo.photos.clear();
+ bool has_thumbnail = thumbnail.type != 0;
+ if (has_thumbnail) {
+ thumbnail.type = 't';
+ result->photo.photos.push_back(std::move(thumbnail));
+ }
+ photo.type = 'i';
+ result->photo.photos.push_back(std::move(photo));
+
+ if (photo_has_input_media(td->file_manager_.get(), result->photo, to_secret, td->auth_manager_->is_bot())) {
+ return std::move(result);
+ }
+
+ result->photo.photos.back().file_id = fix_file_id(result->photo.photos.back().file_id);
+ if (has_thumbnail) {
+ result->photo.photos[0].file_id =
+ td->file_manager_->dup_file_id(result->photo.photos[0].file_id, "dup_message_content photo");
+ }
+ return std::move(result);
+ }
+ case MessageContentType::Poll:
+ if (type == MessageContentDupType::Copy || type == MessageContentDupType::ServerCopy) {
+ return make_unique<MessagePoll>(
+ td->poll_manager_->dup_poll(static_cast<const MessagePoll *>(content)->poll_id));
+ } else {
+ return make_unique<MessagePoll>(*static_cast<const MessagePoll *>(content));
+ }
+ case MessageContentType::Sticker: {
+ auto result = make_unique<MessageSticker>(*static_cast<const MessageSticker *>(content));
+ result->is_premium = td->option_manager_->get_option_boolean("is_premium");
+ if (td->stickers_manager_->has_input_media(result->file_id, to_secret)) {
+ return std::move(result);
+ }
+ result->file_id = td->stickers_manager_->dup_sticker(fix_file_id(result->file_id), result->file_id);
+ CHECK(result->file_id.is_valid());
+ return std::move(result);
+ }
+ case MessageContentType::Text: {
+ auto result = make_unique<MessageText>(*static_cast<const MessageText *>(content));
+ if (type == MessageContentDupType::Copy || type == MessageContentDupType::ServerCopy) {
+ remove_unallowed_entities(td, result->text, dialog_id);
+ }
+ return std::move(result);
+ }
+ case MessageContentType::Venue:
+ return make_unique<MessageVenue>(*static_cast<const MessageVenue *>(content));
+ case MessageContentType::Video: {
+ auto result = make_unique<MessageVideo>(*static_cast<const MessageVideo *>(content));
+ if (replace_caption) {
+ result->caption = std::move(copy_options.new_caption);
+ }
+ if (td->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
+ return std::move(result);
+ }
+ result->file_id = td->videos_manager_->dup_video(fix_file_id(result->file_id), result->file_id);
+ CHECK(result->file_id.is_valid());
+ return std::move(result);
+ }
+ case MessageContentType::VideoNote: {
+ auto result = make_unique<MessageVideoNote>(*static_cast<const MessageVideoNote *>(content));
+ result->is_viewed = false;
+ if (td->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
+ return std::move(result);
+ }
+ result->file_id = td->video_notes_manager_->dup_video_note(fix_file_id(result->file_id), result->file_id);
+ CHECK(result->file_id.is_valid());
+ return std::move(result);
+ }
+ case MessageContentType::VoiceNote: {
+ auto result = make_unique<MessageVoiceNote>(*static_cast<const MessageVoiceNote *>(content));
+ if (replace_caption) {
+ result->caption = std::move(copy_options.new_caption);
+ }
+ result->is_listened = false;
+ if (td->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
+ return std::move(result);
+ }
+ result->file_id = td->voice_notes_manager_->dup_voice_note(fix_file_id(result->file_id), result->file_id);
+ CHECK(result->file_id.is_valid());
+ return std::move(result);
+ }
+ case MessageContentType::Unsupported:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ return nullptr;
+ default:
+ UNREACHABLE();
+ }
+ UNREACHABLE();
+ return nullptr;
+}
+
+unique_ptr<MessageContent> get_action_message_content(Td *td, tl_object_ptr<telegram_api::MessageAction> &&action_ptr,
+ DialogId owner_dialog_id, DialogId reply_in_dialog_id,
+ MessageId reply_to_message_id) {
+ CHECK(action_ptr != nullptr);
+
+ switch (action_ptr->get_id()) {
+ case telegram_api::messageActionEmpty::ID:
+ LOG(ERROR) << "Receive empty message action in " << owner_dialog_id;
+ break;
+ case telegram_api::messageActionChatCreate::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionChatCreate>(action_ptr);
+
+ vector<UserId> participant_user_ids;
+ participant_user_ids.reserve(action->users_.size());
+ for (auto &user : action->users_) {
+ UserId user_id(user);
+ if (user_id.is_valid()) {
+ participant_user_ids.push_back(user_id);
+ } else {
+ LOG(ERROR) << "Receive messageActionChatCreate with invalid " << user_id << " in " << owner_dialog_id;
+ }
+ }
+
+ return td::make_unique<MessageChatCreate>(std::move(action->title_), std::move(participant_user_ids));
+ }
+ case telegram_api::messageActionChatEditTitle::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionChatEditTitle>(action_ptr);
+ return td::make_unique<MessageChatChangeTitle>(std::move(action->title_));
+ }
+ case telegram_api::messageActionChatEditPhoto::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionChatEditPhoto>(action_ptr);
+ auto photo = get_photo(td->file_manager_.get(), std::move(action->photo_), owner_dialog_id);
+ if (photo.is_empty()) {
+ break;
+ }
+ return make_unique<MessageChatChangePhoto>(std::move(photo));
+ }
+ case telegram_api::messageActionChatDeletePhoto::ID: {
+ return make_unique<MessageChatDeletePhoto>();
+ }
+ case telegram_api::messageActionHistoryClear::ID: {
+ return make_unique<MessageChatDeleteHistory>();
+ }
+ case telegram_api::messageActionChatAddUser::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionChatAddUser>(action_ptr);
+
+ vector<UserId> user_ids;
+ user_ids.reserve(action->users_.size());
+ for (auto &user : action->users_) {
+ UserId user_id(user);
+ if (user_id.is_valid()) {
+ user_ids.push_back(user_id);
+ } else {
+ LOG(ERROR) << "Receive messageActionChatAddUser with invalid " << user_id << " in " << owner_dialog_id;
+ }
+ }
+
+ return td::make_unique<MessageChatAddUsers>(std::move(user_ids));
+ }
+ case telegram_api::messageActionChatJoinedByLink::ID:
+ return make_unique<MessageChatJoinedByLink>(false);
+ case telegram_api::messageActionChatDeleteUser::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionChatDeleteUser>(action_ptr);
+
+ UserId user_id(action->user_id_);
+ if (!user_id.is_valid()) {
+ LOG(ERROR) << "Receive messageActionChatDeleteUser with invalid " << user_id << " in " << owner_dialog_id;
+ break;
+ }
+
+ return make_unique<MessageChatDeleteUser>(user_id);
+ }
+ case telegram_api::messageActionChatMigrateTo::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionChatMigrateTo>(action_ptr);
+
+ ChannelId migrated_to_channel_id(action->channel_id_);
+ if (!migrated_to_channel_id.is_valid()) {
+ LOG(ERROR) << "Receive messageActionChatMigrateTo with invalid " << migrated_to_channel_id << " in "
+ << owner_dialog_id;
+ break;
+ }
+
+ return make_unique<MessageChatMigrateTo>(migrated_to_channel_id);
+ }
+ case telegram_api::messageActionChannelCreate::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionChannelCreate>(action_ptr);
+ return td::make_unique<MessageChannelCreate>(std::move(action->title_));
+ }
+ case telegram_api::messageActionChannelMigrateFrom::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionChannelMigrateFrom>(action_ptr);
+ ChatId chat_id(action->chat_id_);
+ LOG_IF(ERROR, !chat_id.is_valid()) << "Receive messageActionChannelMigrateFrom with invalid " << chat_id << " in "
+ << owner_dialog_id;
+
+ return td::make_unique<MessageChannelMigrateFrom>(std::move(action->title_), chat_id);
+ }
+ case telegram_api::messageActionPinMessage::ID: {
+ if (reply_in_dialog_id.is_valid() && reply_in_dialog_id != owner_dialog_id) {
+ LOG(ERROR) << "Receive pinned message with " << reply_to_message_id << " in " << owner_dialog_id
+ << " in another " << reply_in_dialog_id;
+ reply_to_message_id = MessageId();
+ reply_in_dialog_id = DialogId();
+ }
+ if (!reply_to_message_id.is_valid()) {
+ // possible in basic groups
+ LOG(INFO) << "Receive pinned message with " << reply_to_message_id << " in " << owner_dialog_id;
+ reply_to_message_id = MessageId();
+ }
+ return make_unique<MessagePinMessage>(reply_to_message_id);
+ }
+ case telegram_api::messageActionGameScore::ID: {
+ if (reply_in_dialog_id.is_valid() && reply_in_dialog_id != owner_dialog_id) {
+ LOG(ERROR) << "Receive game score with " << reply_to_message_id << " in " << owner_dialog_id << " in another "
+ << reply_in_dialog_id;
+ reply_to_message_id = MessageId();
+ reply_in_dialog_id = DialogId();
+ }
+ if (!reply_to_message_id.is_valid()) {
+ // possible in basic groups
+ LOG(INFO) << "Receive game score with " << reply_to_message_id << " in " << owner_dialog_id;
+ reply_to_message_id = MessageId();
+ }
+ auto action = move_tl_object_as<telegram_api::messageActionGameScore>(action_ptr);
+ return make_unique<MessageGameScore>(reply_to_message_id, action->game_id_, action->score_);
+ }
+ case telegram_api::messageActionPhoneCall::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionPhoneCall>(action_ptr);
+ auto duration =
+ (action->flags_ & telegram_api::messageActionPhoneCall::DURATION_MASK) != 0 ? action->duration_ : 0;
+ if (duration < 0) {
+ LOG(ERROR) << "Receive invalid " << oneline(to_string(action));
+ break;
+ }
+ return make_unique<MessageCall>(action->call_id_, duration, get_call_discard_reason(action->reason_),
+ action->video_);
+ }
+ case telegram_api::messageActionPaymentSent::ID: {
+ if (td->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Receive MessageActionPaymentSent in " << owner_dialog_id;
+ break;
+ }
+ auto action = move_tl_object_as<telegram_api::messageActionPaymentSent>(action_ptr);
+ if (!reply_to_message_id.is_valid()) {
+ if (reply_to_message_id != MessageId()) {
+ LOG(ERROR) << "Receive succesful payment message with " << reply_to_message_id << " in " << owner_dialog_id;
+ }
+ reply_in_dialog_id = DialogId();
+ reply_to_message_id = MessageId();
+ }
+ if (action->total_amount_ <= 0 || !check_currency_amount(action->total_amount_)) {
+ LOG(ERROR) << "Receive invalid total amount " << action->total_amount_;
+ action->total_amount_ = 0;
+ }
+ return td::make_unique<MessagePaymentSuccessful>(
+ reply_in_dialog_id, reply_to_message_id, std::move(action->currency_), action->total_amount_,
+ std::move(action->invoice_slug_), action->recurring_used_, action->recurring_init_);
+ }
+ case telegram_api::messageActionPaymentSentMe::ID: {
+ if (!td->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Receive MessageActionPaymentSentMe in " << owner_dialog_id;
+ break;
+ }
+ auto action = move_tl_object_as<telegram_api::messageActionPaymentSentMe>(action_ptr);
+ if (action->total_amount_ <= 0 || !check_currency_amount(action->total_amount_)) {
+ LOG(ERROR) << "Receive invalid total amount " << action->total_amount_;
+ action->total_amount_ = 0;
+ }
+ auto result = td::make_unique<MessagePaymentSuccessful>(DialogId(), MessageId(), std::move(action->currency_),
+ action->total_amount_, action->payload_.as_slice().str(),
+ action->recurring_used_, action->recurring_init_);
+ result->shipping_option_id = std::move(action->shipping_option_id_);
+ result->order_info = get_order_info(std::move(action->info_));
+ result->telegram_payment_charge_id = std::move(action->charge_->id_);
+ result->provider_payment_charge_id = std::move(action->charge_->provider_charge_id_);
+ return std::move(result);
+ }
+ case telegram_api::messageActionScreenshotTaken::ID: {
+ return make_unique<MessageScreenshotTaken>();
+ }
+ case telegram_api::messageActionCustomAction::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionCustomAction>(action_ptr);
+ return td::make_unique<MessageCustomServiceAction>(std::move(action->message_));
+ }
+ case telegram_api::messageActionBotAllowed::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionBotAllowed>(action_ptr);
+ return td::make_unique<MessageWebsiteConnected>(std::move(action->domain_));
+ }
+ case telegram_api::messageActionSecureValuesSent::ID: {
+ LOG_IF(ERROR, td->auth_manager_->is_bot()) << "Receive MessageActionSecureValuesSent in " << owner_dialog_id;
+ auto action = move_tl_object_as<telegram_api::messageActionSecureValuesSent>(action_ptr);
+ return td::make_unique<MessagePassportDataSent>(get_secure_value_types(action->types_));
+ }
+ case telegram_api::messageActionSecureValuesSentMe::ID: {
+ LOG_IF(ERROR, !td->auth_manager_->is_bot()) << "Receive MessageActionSecureValuesSentMe in " << owner_dialog_id;
+ auto action = move_tl_object_as<telegram_api::messageActionSecureValuesSentMe>(action_ptr);
+ return td::make_unique<MessagePassportDataReceived>(
+ get_encrypted_secure_values(td->file_manager_.get(), std::move(action->values_)),
+ get_encrypted_secure_credentials(std::move(action->credentials_)));
+ }
+ case telegram_api::messageActionContactSignUp::ID: {
+ LOG_IF(ERROR, td->auth_manager_->is_bot()) << "Receive ContactRegistered in " << owner_dialog_id;
+ return td::make_unique<MessageContactRegistered>();
+ }
+ case telegram_api::messageActionGeoProximityReached::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionGeoProximityReached>(action_ptr);
+ DialogId traveler_id(action->from_id_);
+ DialogId watcher_id(action->to_id_);
+ int32 distance = action->distance_;
+ if (!traveler_id.is_valid() || !watcher_id.is_valid() || distance < 0) {
+ LOG(ERROR) << "Receive invalid " << oneline(to_string(action));
+ break;
+ }
+
+ return make_unique<MessageProximityAlertTriggered>(traveler_id, watcher_id, distance);
+ }
+ case telegram_api::messageActionGroupCall::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionGroupCall>(action_ptr);
+ int32 duration = -1;
+ if ((action->flags_ & telegram_api::messageActionGroupCall::DURATION_MASK) != 0) {
+ duration = action->duration_;
+ if (duration < 0) {
+ LOG(ERROR) << "Receive invalid " << oneline(to_string(action));
+ break;
+ }
+ }
+ return make_unique<MessageGroupCall>(InputGroupCallId(action->call_), duration, -1);
+ }
+ case telegram_api::messageActionInviteToGroupCall::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionInviteToGroupCall>(action_ptr);
+
+ vector<UserId> user_ids;
+ user_ids.reserve(action->users_.size());
+ for (auto &user : action->users_) {
+ UserId user_id(user);
+ if (user_id.is_valid()) {
+ user_ids.push_back(user_id);
+ } else {
+ LOG(ERROR) << "Receive messageActionInviteToGroupCall with invalid " << user_id << " in " << owner_dialog_id;
+ }
+ }
+
+ return td::make_unique<MessageInviteToGroupCall>(InputGroupCallId(action->call_), std::move(user_ids));
+ }
+ case telegram_api::messageActionSetMessagesTTL::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionSetMessagesTTL>(action_ptr);
+ if (action->period_ < 0) {
+ LOG(ERROR) << "Receive wrong TTL = " << action->period_;
+ break;
+ }
+ return make_unique<MessageChatSetTtl>(action->period_);
+ }
+ case telegram_api::messageActionGroupCallScheduled::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionGroupCallScheduled>(action_ptr);
+ if (action->schedule_date_ <= 0) {
+ LOG(ERROR) << "Receive wrong schedule_date = " << action->schedule_date_;
+ break;
+ }
+ return make_unique<MessageGroupCall>(InputGroupCallId(action->call_), -1, action->schedule_date_);
+ }
+ case telegram_api::messageActionSetChatTheme::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionSetChatTheme>(action_ptr);
+ return td::make_unique<MessageChatSetTheme>(std::move(action->emoticon_));
+ }
+ case telegram_api::messageActionChatJoinedByRequest::ID:
+ return make_unique<MessageChatJoinedByLink>(true);
+ case telegram_api::messageActionWebViewDataSent::ID: {
+ if (td->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Receive messageActionWebViewDataSent in " << owner_dialog_id;
+ break;
+ }
+ auto action = move_tl_object_as<telegram_api::messageActionWebViewDataSent>(action_ptr);
+ return td::make_unique<MessageWebViewDataSent>(std::move(action->text_));
+ }
+ case telegram_api::messageActionWebViewDataSentMe::ID: {
+ if (!td->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Receive messageActionWebViewDataSentMe in " << owner_dialog_id;
+ break;
+ }
+ auto action = move_tl_object_as<telegram_api::messageActionWebViewDataSentMe>(action_ptr);
+ return td::make_unique<MessageWebViewDataReceived>(std::move(action->text_), std::move(action->data_));
+ }
+ case telegram_api::messageActionGiftPremium::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionGiftPremium>(action_ptr);
+ if (action->amount_ <= 0 || !check_currency_amount(action->amount_)) {
+ LOG(ERROR) << "Receive invalid premium gift price " << action->amount_;
+ action->amount_ = 0;
+ }
+ return td::make_unique<MessageGiftPremium>(std::move(action->currency_), action->amount_, action->months_);
+ }
+ case telegram_api::messageActionTopicCreate::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionTopicCreate>(action_ptr);
+ return td::make_unique<MessageTopicCreate>(std::move(action->title_),
+ ForumTopicIcon(action->icon_color_, action->icon_emoji_id_));
+ }
+ case telegram_api::messageActionTopicEdit::ID: {
+ auto action = move_tl_object_as<telegram_api::messageActionTopicEdit>(action_ptr);
+ auto edit_icon_custom_emoji_id = (action->flags_ & telegram_api::messageActionTopicEdit::ICON_EMOJI_ID_MASK) != 0;
+ auto edit_is_closed = (action->flags_ & telegram_api::messageActionTopicEdit::CLOSED_MASK) != 0;
+ return td::make_unique<MessageTopicEdit>(ForumTopicEditedData{std::move(action->title_),
+ edit_icon_custom_emoji_id, action->icon_emoji_id_,
+ edit_is_closed, action->closed_});
+ }
+ default:
+ UNREACHABLE();
+ }
+ // explicit empty or wrong action
+ return make_unique<MessageText>(FormattedText(), WebPageId());
+}
+
+tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content, Td *td,
+ DialogId dialog_id, int32 message_date,
+ bool is_content_secret, bool skip_bot_commands,
+ int32 max_media_timestamp) {
+ CHECK(content != nullptr);
+ switch (content->get_type()) {
+ case MessageContentType::Animation: {
+ const auto *m = static_cast<const MessageAnimation *>(content);
+ return make_tl_object<td_api::messageAnimation>(
+ td->animations_manager_->get_animation_object(m->file_id),
+ get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), is_content_secret);
+ }
+ case MessageContentType::Audio: {
+ const auto *m = static_cast<const MessageAudio *>(content);
+ return make_tl_object<td_api::messageAudio>(
+ td->audios_manager_->get_audio_object(m->file_id),
+ get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp));
+ }
+ case MessageContentType::Contact: {
+ const auto *m = static_cast<const MessageContact *>(content);
+ return make_tl_object<td_api::messageContact>(m->contact.get_contact_object());
+ }
+ case MessageContentType::Document: {
+ const auto *m = static_cast<const MessageDocument *>(content);
+ return make_tl_object<td_api::messageDocument>(
+ td->documents_manager_->get_document_object(m->file_id, PhotoFormat::Jpeg),
+ get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp));
+ }
+ case MessageContentType::Game: {
+ const auto *m = static_cast<const MessageGame *>(content);
+ return make_tl_object<td_api::messageGame>(m->game.get_game_object(td, skip_bot_commands));
+ }
+ case MessageContentType::Invoice: {
+ const auto *m = static_cast<const MessageInvoice *>(content);
+ return m->input_invoice.get_message_invoice_object(td, skip_bot_commands, max_media_timestamp);
+ }
+ case MessageContentType::LiveLocation: {
+ const auto *m = static_cast<const MessageLiveLocation *>(content);
+ auto passed = max(G()->unix_time_cached() - message_date, 0);
+ auto expires_in = max(0, m->period - passed);
+ auto heading = expires_in == 0 ? 0 : m->heading;
+ auto proximity_alert_radius = expires_in == 0 ? 0 : m->proximity_alert_radius;
+ return make_tl_object<td_api::messageLocation>(m->location.get_location_object(), m->period, expires_in, heading,
+ proximity_alert_radius);
+ }
+ case MessageContentType::Location: {
+ const auto *m = static_cast<const MessageLocation *>(content);
+ return make_tl_object<td_api::messageLocation>(m->location.get_location_object(), 0, 0, 0, 0);
+ }
+ case MessageContentType::Photo: {
+ const auto *m = static_cast<const MessagePhoto *>(content);
+ auto photo = get_photo_object(td->file_manager_.get(), m->photo);
+ if (photo == nullptr) {
+ LOG(ERROR) << "Have empty " << m->photo;
+ return make_tl_object<td_api::messageExpiredPhoto>();
+ }
+ auto caption = get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp);
+ return make_tl_object<td_api::messagePhoto>(std::move(photo), std::move(caption), is_content_secret);
+ }
+ case MessageContentType::Sticker: {
+ const auto *m = static_cast<const MessageSticker *>(content);
+ auto sticker = td->stickers_manager_->get_sticker_object(m->file_id);
+ CHECK(sticker != nullptr);
+ auto is_premium = m->is_premium && sticker->premium_animation_ != nullptr;
+ return make_tl_object<td_api::messageSticker>(std::move(sticker), is_premium);
+ }
+ case MessageContentType::Text: {
+ const auto *m = static_cast<const MessageText *>(content);
+ if (can_be_animated_emoji(m->text) && !m->web_page_id.is_valid()) {
+ auto animated_emoji =
+ td->stickers_manager_->get_animated_emoji_object(m->text.text, get_custom_emoji_id(m->text));
+ if (animated_emoji != nullptr) {
+ return td_api::make_object<td_api::messageAnimatedEmoji>(std::move(animated_emoji), m->text.text);
+ }
+ }
+ return make_tl_object<td_api::messageText>(
+ get_formatted_text_object(m->text, skip_bot_commands, max_media_timestamp),
+ td->web_pages_manager_->get_web_page_object(m->web_page_id));
+ }
+ case MessageContentType::Unsupported:
+ return make_tl_object<td_api::messageUnsupported>();
+ case MessageContentType::Venue: {
+ const auto *m = static_cast<const MessageVenue *>(content);
+ return make_tl_object<td_api::messageVenue>(m->venue.get_venue_object());
+ }
+ case MessageContentType::Video: {
+ const auto *m = static_cast<const MessageVideo *>(content);
+ return make_tl_object<td_api::messageVideo>(
+ td->videos_manager_->get_video_object(m->file_id),
+ get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), is_content_secret);
+ }
+ case MessageContentType::VideoNote: {
+ const auto *m = static_cast<const MessageVideoNote *>(content);
+ return make_tl_object<td_api::messageVideoNote>(td->video_notes_manager_->get_video_note_object(m->file_id),
+ m->is_viewed, is_content_secret);
+ }
+ case MessageContentType::VoiceNote: {
+ const auto *m = static_cast<const MessageVoiceNote *>(content);
+ return make_tl_object<td_api::messageVoiceNote>(
+ td->voice_notes_manager_->get_voice_note_object(m->file_id),
+ get_formatted_text_object(m->caption, skip_bot_commands, max_media_timestamp), m->is_listened);
+ }
+ case MessageContentType::ChatCreate: {
+ const auto *m = static_cast<const MessageChatCreate *>(content);
+ return make_tl_object<td_api::messageBasicGroupChatCreate>(
+ m->title, td->contacts_manager_->get_user_ids_object(m->participant_user_ids, "MessageChatCreate"));
+ }
+ case MessageContentType::ChatChangeTitle: {
+ const auto *m = static_cast<const MessageChatChangeTitle *>(content);
+ return make_tl_object<td_api::messageChatChangeTitle>(m->title);
+ }
+ case MessageContentType::ChatChangePhoto: {
+ const auto *m = static_cast<const MessageChatChangePhoto *>(content);
+ auto photo = get_chat_photo_object(td->file_manager_.get(), m->photo);
+ if (photo == nullptr) {
+ LOG(ERROR) << "Have empty chat " << m->photo;
+ return make_tl_object<td_api::messageChatDeletePhoto>();
+ }
+ return make_tl_object<td_api::messageChatChangePhoto>(std::move(photo));
+ }
+ case MessageContentType::ChatDeletePhoto:
+ return make_tl_object<td_api::messageChatDeletePhoto>();
+ case MessageContentType::ChatDeleteHistory:
+ return make_tl_object<td_api::messageUnsupported>();
+ case MessageContentType::ChatAddUsers: {
+ const auto *m = static_cast<const MessageChatAddUsers *>(content);
+ return make_tl_object<td_api::messageChatAddMembers>(
+ td->contacts_manager_->get_user_ids_object(m->user_ids, "MessageChatAddUsers"));
+ }
+ case MessageContentType::ChatJoinedByLink: {
+ const MessageChatJoinedByLink *m = static_cast<const MessageChatJoinedByLink *>(content);
+ if (m->is_approved) {
+ return make_tl_object<td_api::messageChatJoinByRequest>();
+ }
+ return make_tl_object<td_api::messageChatJoinByLink>();
+ }
+ case MessageContentType::ChatDeleteUser: {
+ const auto *m = static_cast<const MessageChatDeleteUser *>(content);
+ return make_tl_object<td_api::messageChatDeleteMember>(
+ td->contacts_manager_->get_user_id_object(m->user_id, "MessageChatDeleteMember"));
+ }
+ case MessageContentType::ChatMigrateTo: {
+ const auto *m = static_cast<const MessageChatMigrateTo *>(content);
+ return make_tl_object<td_api::messageChatUpgradeTo>(
+ td->contacts_manager_->get_supergroup_id_object(m->migrated_to_channel_id, "MessageChatUpgradeTo"));
+ }
+ case MessageContentType::ChannelCreate: {
+ const auto *m = static_cast<const MessageChannelCreate *>(content);
+ return make_tl_object<td_api::messageSupergroupChatCreate>(m->title);
+ }
+ case MessageContentType::ChannelMigrateFrom: {
+ const auto *m = static_cast<const MessageChannelMigrateFrom *>(content);
+ return make_tl_object<td_api::messageChatUpgradeFrom>(
+ m->title,
+ td->contacts_manager_->get_basic_group_id_object(m->migrated_from_chat_id, "MessageChatUpgradeFrom"));
+ }
+ case MessageContentType::PinMessage: {
+ const auto *m = static_cast<const MessagePinMessage *>(content);
+ return make_tl_object<td_api::messagePinMessage>(m->message_id.get());
+ }
+ case MessageContentType::GameScore: {
+ const auto *m = static_cast<const MessageGameScore *>(content);
+ return make_tl_object<td_api::messageGameScore>(m->game_message_id.get(), m->game_id, m->score);
+ }
+ case MessageContentType::ScreenshotTaken:
+ return make_tl_object<td_api::messageScreenshotTaken>();
+ case MessageContentType::ChatSetTtl: {
+ const auto *m = static_cast<const MessageChatSetTtl *>(content);
+ return make_tl_object<td_api::messageChatSetTtl>(m->ttl);
+ }
+ case MessageContentType::Call: {
+ const auto *m = static_cast<const MessageCall *>(content);
+ return make_tl_object<td_api::messageCall>(m->is_video, get_call_discard_reason_object(m->discard_reason),
+ m->duration);
+ }
+ case MessageContentType::PaymentSuccessful: {
+ const auto *m = static_cast<const MessagePaymentSuccessful *>(content);
+ if (td->auth_manager_->is_bot()) {
+ return make_tl_object<td_api::messagePaymentSuccessfulBot>(
+ m->currency, m->total_amount, m->is_recurring, m->is_first_recurring, m->invoice_payload,
+ m->shipping_option_id, get_order_info_object(m->order_info), m->telegram_payment_charge_id,
+ m->provider_payment_charge_id);
+ } else {
+ auto invoice_dialog_id = m->invoice_dialog_id.is_valid() ? m->invoice_dialog_id : dialog_id;
+ return make_tl_object<td_api::messagePaymentSuccessful>(invoice_dialog_id.get(), m->invoice_message_id.get(),
+ m->currency, m->total_amount, m->is_recurring,
+ m->is_first_recurring, m->invoice_payload);
+ }
+ }
+ case MessageContentType::ContactRegistered:
+ return make_tl_object<td_api::messageContactRegistered>();
+ case MessageContentType::ExpiredPhoto:
+ return make_tl_object<td_api::messageExpiredPhoto>();
+ case MessageContentType::ExpiredVideo:
+ return make_tl_object<td_api::messageExpiredVideo>();
+ case MessageContentType::CustomServiceAction: {
+ const auto *m = static_cast<const MessageCustomServiceAction *>(content);
+ return make_tl_object<td_api::messageCustomServiceAction>(m->message);
+ }
+ case MessageContentType::WebsiteConnected: {
+ const auto *m = static_cast<const MessageWebsiteConnected *>(content);
+ return make_tl_object<td_api::messageWebsiteConnected>(m->domain_name);
+ }
+ case MessageContentType::PassportDataSent: {
+ const auto *m = static_cast<const MessagePassportDataSent *>(content);
+ return make_tl_object<td_api::messagePassportDataSent>(get_passport_element_types_object(m->types));
+ }
+ case MessageContentType::PassportDataReceived: {
+ const auto *m = static_cast<const MessagePassportDataReceived *>(content);
+ return make_tl_object<td_api::messagePassportDataReceived>(
+ get_encrypted_passport_element_object(td->file_manager_.get(), m->values),
+ get_encrypted_credentials_object(m->credentials));
+ }
+ case MessageContentType::Poll: {
+ const auto *m = static_cast<const MessagePoll *>(content);
+ return make_tl_object<td_api::messagePoll>(td->poll_manager_->get_poll_object(m->poll_id));
+ }
+ case MessageContentType::Dice: {
+ const auto *m = static_cast<const MessageDice *>(content);
+ auto initial_state = td->stickers_manager_->get_dice_stickers_object(m->emoji, 0);
+ auto final_state =
+ m->dice_value == 0 ? nullptr : td->stickers_manager_->get_dice_stickers_object(m->emoji, m->dice_value);
+ auto success_animation_frame_number =
+ td->stickers_manager_->get_dice_success_animation_frame_number(m->emoji, m->dice_value);
+ return make_tl_object<td_api::messageDice>(std::move(initial_state), std::move(final_state), m->emoji,
+ m->dice_value, success_animation_frame_number);
+ }
+ case MessageContentType::ProximityAlertTriggered: {
+ const auto *m = static_cast<const MessageProximityAlertTriggered *>(content);
+ return make_tl_object<td_api::messageProximityAlertTriggered>(
+ get_message_sender_object(td, m->traveler_dialog_id, "messageProximityAlertTriggered 1"),
+ get_message_sender_object(td, m->watcher_dialog_id, "messageProximityAlertTriggered 2"), m->distance);
+ }
+ case MessageContentType::GroupCall: {
+ const auto *m = static_cast<const MessageGroupCall *>(content);
+ if (m->duration >= 0) {
+ return make_tl_object<td_api::messageVideoChatEnded>(m->duration);
+ } else {
+ auto group_call_id = td->group_call_manager_->get_group_call_id(m->input_group_call_id, DialogId()).get();
+ if (m->schedule_date > 0) {
+ return make_tl_object<td_api::messageVideoChatScheduled>(group_call_id, m->schedule_date);
+ } else {
+ return make_tl_object<td_api::messageVideoChatStarted>(group_call_id);
+ }
+ }
+ }
+ case MessageContentType::InviteToGroupCall: {
+ const auto *m = static_cast<const MessageInviteToGroupCall *>(content);
+ return make_tl_object<td_api::messageInviteVideoChatParticipants>(
+ td->group_call_manager_->get_group_call_id(m->input_group_call_id, DialogId()).get(),
+ td->contacts_manager_->get_user_ids_object(m->user_ids, "MessageInviteToGroupCall"));
+ }
+ case MessageContentType::ChatSetTheme: {
+ const auto *m = static_cast<const MessageChatSetTheme *>(content);
+ return make_tl_object<td_api::messageChatSetTheme>(m->emoji);
+ }
+ case MessageContentType::WebViewDataSent: {
+ const auto *m = static_cast<const MessageWebViewDataSent *>(content);
+ return make_tl_object<td_api::messageWebAppDataSent>(m->button_text);
+ }
+ case MessageContentType::WebViewDataReceived: {
+ const auto *m = static_cast<const MessageWebViewDataReceived *>(content);
+ return make_tl_object<td_api::messageWebAppDataReceived>(m->button_text, m->data);
+ }
+ case MessageContentType::GiftPremium: {
+ const auto *m = static_cast<const MessageGiftPremium *>(content);
+ return make_tl_object<td_api::messageGiftedPremium>(
+ m->currency, m->amount, m->months, td->stickers_manager_->get_premium_gift_sticker_object(m->months));
+ }
+ case MessageContentType::TopicCreate: {
+ const auto *m = static_cast<const MessageTopicCreate *>(content);
+ return td_api::make_object<td_api::messageForumTopicCreated>(m->title, m->icon.get_forum_topic_icon_object());
+ }
+ case MessageContentType::TopicEdit: {
+ const auto *m = static_cast<const MessageTopicEdit *>(content);
+ return m->edited_data.get_message_content_object();
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+ UNREACHABLE();
+ return nullptr;
+}
+
+FormattedText *get_message_content_text_mutable(MessageContent *content) {
+ return const_cast<FormattedText *>(get_message_content_text(content));
+}
+
+const FormattedText *get_message_content_text(const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::Text:
+ return &static_cast<const MessageText *>(content)->text;
+ case MessageContentType::Game:
+ return &static_cast<const MessageGame *>(content)->game.get_text();
+ default:
+ return get_message_content_caption(content);
+ }
+}
+
+const FormattedText *get_message_content_caption(const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::Animation:
+ return &static_cast<const MessageAnimation *>(content)->caption;
+ case MessageContentType::Audio:
+ return &static_cast<const MessageAudio *>(content)->caption;
+ case MessageContentType::Document:
+ return &static_cast<const MessageDocument *>(content)->caption;
+ case MessageContentType::Invoice:
+ return static_cast<const MessageInvoice *>(content)->input_invoice.get_caption();
+ case MessageContentType::Photo:
+ return &static_cast<const MessagePhoto *>(content)->caption;
+ case MessageContentType::Video:
+ return &static_cast<const MessageVideo *>(content)->caption;
+ case MessageContentType::VoiceNote:
+ return &static_cast<const MessageVoiceNote *>(content)->caption;
+ default:
+ return nullptr;
+ }
+}
+
+int32 get_message_content_duration(const MessageContent *content, const Td *td) {
+ CHECK(content != nullptr);
+ switch (content->get_type()) {
+ case MessageContentType::Animation: {
+ auto animation_file_id = static_cast<const MessageAnimation *>(content)->file_id;
+ return td->animations_manager_->get_animation_duration(animation_file_id);
+ }
+ case MessageContentType::Audio: {
+ auto audio_file_id = static_cast<const MessageAudio *>(content)->file_id;
+ return td->audios_manager_->get_audio_duration(audio_file_id);
+ }
+ case MessageContentType::Invoice:
+ return static_cast<const MessageInvoice *>(content)->input_invoice.get_duration(td);
+ case MessageContentType::Video: {
+ auto video_file_id = static_cast<const MessageVideo *>(content)->file_id;
+ return td->videos_manager_->get_video_duration(video_file_id);
+ }
+ case MessageContentType::VideoNote: {
+ auto video_note_file_id = static_cast<const MessageVideoNote *>(content)->file_id;
+ return td->video_notes_manager_->get_video_note_duration(video_note_file_id);
+ }
+ case MessageContentType::VoiceNote: {
+ auto voice_file_id = static_cast<const MessageVoiceNote *>(content)->file_id;
+ return td->voice_notes_manager_->get_voice_note_duration(voice_file_id);
+ }
+ default:
+ return 0;
+ }
+}
+
+int32 get_message_content_media_duration(const MessageContent *content, const Td *td) {
+ CHECK(content != nullptr);
+ switch (content->get_type()) {
+ case MessageContentType::Audio: {
+ auto audio_file_id = static_cast<const MessageAudio *>(content)->file_id;
+ return td->audios_manager_->get_audio_duration(audio_file_id);
+ }
+ case MessageContentType::Invoice:
+ return static_cast<const MessageInvoice *>(content)->input_invoice.get_duration(td);
+ case MessageContentType::Text: {
+ auto web_page_id = static_cast<const MessageText *>(content)->web_page_id;
+ return td->web_pages_manager_->get_web_page_media_duration(web_page_id);
+ }
+ case MessageContentType::Video: {
+ auto video_file_id = static_cast<const MessageVideo *>(content)->file_id;
+ return td->videos_manager_->get_video_duration(video_file_id);
+ }
+ case MessageContentType::VideoNote: {
+ auto video_note_file_id = static_cast<const MessageVideoNote *>(content)->file_id;
+ return td->video_notes_manager_->get_video_note_duration(video_note_file_id);
+ }
+ case MessageContentType::VoiceNote: {
+ auto voice_file_id = static_cast<const MessageVoiceNote *>(content)->file_id;
+ return td->voice_notes_manager_->get_voice_note_duration(voice_file_id);
+ }
+ default:
+ return -1;
+ }
+}
+
+const Photo *get_message_content_photo(const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::Photo:
+ return &static_cast<const MessagePhoto *>(content)->photo;
+ default:
+ break;
+ }
+ return nullptr;
+}
+
+FileId get_message_content_upload_file_id(const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::Animation:
+ return static_cast<const MessageAnimation *>(content)->file_id;
+ case MessageContentType::Audio:
+ return static_cast<const MessageAudio *>(content)->file_id;
+ case MessageContentType::Document:
+ return static_cast<const MessageDocument *>(content)->file_id;
+ case MessageContentType::Invoice:
+ return static_cast<const MessageInvoice *>(content)->input_invoice.get_upload_file_id();
+ case MessageContentType::Photo:
+ return get_photo_upload_file_id(static_cast<const MessagePhoto *>(content)->photo);
+ case MessageContentType::Sticker:
+ return static_cast<const MessageSticker *>(content)->file_id;
+ case MessageContentType::Video:
+ return static_cast<const MessageVideo *>(content)->file_id;
+ case MessageContentType::VideoNote:
+ return static_cast<const MessageVideoNote *>(content)->file_id;
+ case MessageContentType::VoiceNote:
+ return static_cast<const MessageVoiceNote *>(content)->file_id;
+ default:
+ break;
+ }
+ return FileId();
+}
+
+FileId get_message_content_any_file_id(const MessageContent *content) {
+ FileId result = get_message_content_upload_file_id(content);
+ if (!result.is_valid()) {
+ if (content->get_type() == MessageContentType::Photo) {
+ result = get_photo_any_file_id(static_cast<const MessagePhoto *>(content)->photo);
+ } else if (content->get_type() == MessageContentType::Invoice) {
+ result = static_cast<const MessageInvoice *>(content)->input_invoice.get_any_file_id();
+ }
+ }
+ return result;
+}
+
+void update_message_content_file_id_remote(MessageContent *content, FileId file_id) {
+ if (file_id.get_remote() == 0) {
+ return;
+ }
+ FileId *old_file_id = [&] {
+ switch (content->get_type()) {
+ case MessageContentType::Animation:
+ return &static_cast<MessageAnimation *>(content)->file_id;
+ case MessageContentType::Audio:
+ return &static_cast<MessageAudio *>(content)->file_id;
+ case MessageContentType::Document:
+ return &static_cast<MessageDocument *>(content)->file_id;
+ case MessageContentType::Sticker:
+ return &static_cast<MessageSticker *>(content)->file_id;
+ case MessageContentType::Video:
+ return &static_cast<MessageVideo *>(content)->file_id;
+ case MessageContentType::VideoNote:
+ return &static_cast<MessageVideoNote *>(content)->file_id;
+ case MessageContentType::VoiceNote:
+ return &static_cast<MessageVoiceNote *>(content)->file_id;
+ default:
+ return static_cast<FileId *>(nullptr);
+ }
+ }();
+ if (old_file_id != nullptr && *old_file_id == file_id && old_file_id->get_remote() == 0) {
+ *old_file_id = file_id;
+ }
+}
+
+FileId get_message_content_thumbnail_file_id(const MessageContent *content, const Td *td) {
+ switch (content->get_type()) {
+ case MessageContentType::Animation:
+ return td->animations_manager_->get_animation_thumbnail_file_id(
+ static_cast<const MessageAnimation *>(content)->file_id);
+ case MessageContentType::Audio:
+ return td->audios_manager_->get_audio_thumbnail_file_id(static_cast<const MessageAudio *>(content)->file_id);
+ case MessageContentType::Document:
+ return td->documents_manager_->get_document_thumbnail_file_id(
+ static_cast<const MessageDocument *>(content)->file_id);
+ case MessageContentType::Invoice:
+ return static_cast<const MessageInvoice *>(content)->input_invoice.get_thumbnail_file_id(td);
+ case MessageContentType::Photo:
+ return get_photo_thumbnail_file_id(static_cast<const MessagePhoto *>(content)->photo);
+ case MessageContentType::Sticker:
+ return td->stickers_manager_->get_sticker_thumbnail_file_id(
+ static_cast<const MessageSticker *>(content)->file_id);
+ case MessageContentType::Video:
+ return td->videos_manager_->get_video_thumbnail_file_id(static_cast<const MessageVideo *>(content)->file_id);
+ case MessageContentType::VideoNote:
+ return td->video_notes_manager_->get_video_note_thumbnail_file_id(
+ static_cast<const MessageVideoNote *>(content)->file_id);
+ case MessageContentType::VoiceNote:
+ return FileId();
+ default:
+ break;
+ }
+ return FileId();
+}
+
+vector<FileId> get_message_content_file_ids(const MessageContent *content, const Td *td) {
+ switch (content->get_type()) {
+ case MessageContentType::Photo:
+ return photo_get_file_ids(static_cast<const MessagePhoto *>(content)->photo);
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Document:
+ case MessageContentType::Sticker:
+ case MessageContentType::Video:
+ case MessageContentType::VideoNote:
+ case MessageContentType::VoiceNote: {
+ auto document_type = [&] {
+ switch (content->get_type()) {
+ case MessageContentType::Animation:
+ return Document::Type::Animation;
+ case MessageContentType::Audio:
+ return Document::Type::Audio;
+ case MessageContentType::Document:
+ return Document::Type::General;
+ case MessageContentType::Sticker:
+ return Document::Type::Sticker;
+ case MessageContentType::Video:
+ return Document::Type::Video;
+ case MessageContentType::VideoNote:
+ return Document::Type::VideoNote;
+ case MessageContentType::VoiceNote:
+ return Document::Type::VoiceNote;
+ default:
+ UNREACHABLE();
+ return Document::Type::Unknown;
+ }
+ }();
+ return Document(document_type, get_message_content_upload_file_id(content)).get_file_ids(td);
+ }
+ case MessageContentType::Game:
+ return static_cast<const MessageGame *>(content)->game.get_file_ids(td);
+ case MessageContentType::Invoice:
+ return static_cast<const MessageInvoice *>(content)->input_invoice.get_file_ids(td);
+ case MessageContentType::ChatChangePhoto:
+ return photo_get_file_ids(static_cast<const MessageChatChangePhoto *>(content)->photo);
+ case MessageContentType::PassportDataReceived: {
+ vector<FileId> result;
+ for (auto &value : static_cast<const MessagePassportDataReceived *>(content)->values) {
+ auto process_encrypted_secure_file = [&result](const EncryptedSecureFile &file) {
+ if (file.file.file_id.is_valid()) {
+ result.push_back(file.file.file_id);
+ }
+ };
+ for (auto &file : value.files) {
+ process_encrypted_secure_file(file);
+ }
+ process_encrypted_secure_file(value.front_side);
+ process_encrypted_secure_file(value.reverse_side);
+ process_encrypted_secure_file(value.selfie);
+ for (auto &file : value.translations) {
+ process_encrypted_secure_file(file);
+ }
+ }
+ return result;
+ }
+ default:
+ return {};
+ }
+}
+
+string get_message_content_search_text(const Td *td, const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::Text: {
+ const auto *text = static_cast<const MessageText *>(content);
+ if (!text->web_page_id.is_valid()) {
+ return text->text.text;
+ }
+ return PSTRING() << text->text.text << ' ' << td->web_pages_manager_->get_web_page_search_text(text->web_page_id);
+ }
+ case MessageContentType::Animation: {
+ const auto *animation = static_cast<const MessageAnimation *>(content);
+ return PSTRING() << td->animations_manager_->get_animation_search_text(animation->file_id) << ' '
+ << animation->caption.text;
+ }
+ case MessageContentType::Audio: {
+ const auto *audio = static_cast<const MessageAudio *>(content);
+ return PSTRING() << td->audios_manager_->get_audio_search_text(audio->file_id) << ' ' << audio->caption.text;
+ }
+ case MessageContentType::Document: {
+ const auto *document = static_cast<const MessageDocument *>(content);
+ return PSTRING() << td->documents_manager_->get_document_search_text(document->file_id) << ' '
+ << document->caption.text;
+ }
+ case MessageContentType::Invoice: {
+ const auto *invoice = static_cast<const MessageInvoice *>(content);
+ return invoice->input_invoice.get_caption()->text;
+ }
+ case MessageContentType::Photo: {
+ const auto *photo = static_cast<const MessagePhoto *>(content);
+ return photo->caption.text;
+ }
+ case MessageContentType::Video: {
+ const auto *video = static_cast<const MessageVideo *>(content);
+ return PSTRING() << td->videos_manager_->get_video_search_text(video->file_id) << " " << video->caption.text;
+ }
+ case MessageContentType::Poll: {
+ const auto *poll = static_cast<const MessagePoll *>(content);
+ return td->poll_manager_->get_poll_search_text(poll->poll_id);
+ }
+ case MessageContentType::TopicCreate: {
+ const auto *topic_create = static_cast<const MessageTopicCreate *>(content);
+ return topic_create->title;
+ }
+ case MessageContentType::TopicEdit: {
+ const auto *topic_edit = static_cast<const MessageTopicEdit *>(content);
+ return topic_edit->edited_data.get_title();
+ }
+ case MessageContentType::Contact:
+ case MessageContentType::Game:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Sticker:
+ case MessageContentType::Unsupported:
+ case MessageContentType::Venue:
+ case MessageContentType::VideoNote:
+ case MessageContentType::VoiceNote:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::Dice:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ return string();
+ default:
+ UNREACHABLE();
+ return string();
+ }
+}
+
+bool update_message_content_extended_media(MessageContent *content,
+ telegram_api::object_ptr<telegram_api::MessageExtendedMedia> extended_media,
+ DialogId owner_dialog_id, Td *td) {
+ CHECK(content != nullptr);
+ CHECK(content->get_type() == MessageContentType::Invoice);
+ return static_cast<MessageInvoice *>(content)->input_invoice.update_extended_media(std::move(extended_media),
+ owner_dialog_id, td);
+}
+
+bool need_poll_message_content_extended_media(const MessageContent *content) {
+ CHECK(content != nullptr);
+ if (content->get_type() != MessageContentType::Invoice) {
+ return false;
+ }
+ return static_cast<const MessageInvoice *>(content)->input_invoice.need_poll_extended_media();
+}
+
+void get_message_content_animated_emoji_click_sticker(const MessageContent *content, FullMessageId full_message_id,
+ Td *td, Promise<td_api::object_ptr<td_api::sticker>> &&promise) {
+ if (content->get_type() != MessageContentType::Text) {
+ return promise.set_error(Status::Error(400, "Message is not an animated emoji message"));
+ }
+
+ const auto &text = static_cast<const MessageText *>(content)->text;
+ if (!can_be_animated_emoji(text)) {
+ return promise.set_error(Status::Error(400, "Message is not an animated emoji message"));
+ }
+ td->stickers_manager_->get_animated_emoji_click_sticker(text.text, full_message_id, std::move(promise));
+}
+
+void on_message_content_animated_emoji_clicked(const MessageContent *content, FullMessageId full_message_id, Td *td,
+ string &&emoji, string &&data) {
+ if (content->get_type() != MessageContentType::Text) {
+ return;
+ }
+
+ remove_emoji_modifiers_in_place(emoji);
+ auto &text = static_cast<const MessageText *>(content)->text;
+ if (!text.entities.empty() || remove_emoji_modifiers(text.text) != emoji) {
+ return;
+ }
+ auto error = td->stickers_manager_->on_animated_emoji_message_clicked(std::move(emoji), full_message_id, data);
+ if (error.is_error()) {
+ LOG(WARNING) << "Failed to process animated emoji click with data \"" << data << "\": " << error;
+ }
+}
+
+bool need_reget_message_content(const MessageContent *content) {
+ CHECK(content != nullptr);
+ switch (content->get_type()) {
+ case MessageContentType::Unsupported: {
+ const auto *m = static_cast<const MessageUnsupported *>(content);
+ return m->version != MessageUnsupported::CURRENT_VERSION;
+ }
+ case MessageContentType::Invoice: {
+ const auto *m = static_cast<const MessageInvoice *>(content);
+ return m->input_invoice.need_reget();
+ }
+ default:
+ return false;
+ }
+}
+
+bool need_delay_message_content_notification(const MessageContent *content, UserId my_user_id) {
+ switch (content->get_type()) {
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatJoinedByLink:
+ return true;
+ case MessageContentType::ChatAddUsers: {
+ auto &added_user_ids = static_cast<const MessageChatAddUsers *>(content)->user_ids;
+ return !td::contains(added_user_ids, my_user_id);
+ }
+ case MessageContentType::ChatDeleteUser:
+ return static_cast<const MessageChatDeleteUser *>(content)->user_id != my_user_id;
+ default:
+ return false;
+ }
+}
+
+void update_expired_message_content(unique_ptr<MessageContent> &content) {
+ switch (content->get_type()) {
+ case MessageContentType::Photo:
+ content = make_unique<MessageExpiredPhoto>();
+ break;
+ case MessageContentType::Video:
+ content = make_unique<MessageExpiredVideo>();
+ break;
+ case MessageContentType::Unsupported:
+ // can happen if message content file identifier is broken
+ break;
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ // can happen if message content has been reget from somewhere
+ break;
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Document:
+ case MessageContentType::Sticker:
+ case MessageContentType::VideoNote:
+ case MessageContentType::VoiceNote:
+ // can happen if server will send a document with a wrong content
+ content = make_unique<MessageExpiredVideo>();
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+void update_failed_to_send_message_content(Td *td, unique_ptr<MessageContent> &content) {
+ // do not forget about failed to send message forwards
+ switch (content->get_type()) {
+ case MessageContentType::Poll: {
+ const auto *message_poll = static_cast<const MessagePoll *>(content.get());
+ if (PollManager::is_local_poll_id(message_poll->poll_id)) {
+ td->poll_manager_->stop_local_poll(message_poll->poll_id);
+ }
+ break;
+ }
+ default:
+ // nothing to do
+ break;
+ }
+}
+
+void add_message_content_dependencies(Dependencies &dependencies, const MessageContent *message_content) {
+ switch (message_content->get_type()) {
+ case MessageContentType::Text: {
+ const auto *content = static_cast<const MessageText *>(message_content);
+ dependencies.add(content->web_page_id);
+ break;
+ }
+ case MessageContentType::Animation:
+ break;
+ case MessageContentType::Audio:
+ break;
+ case MessageContentType::Contact: {
+ const auto *content = static_cast<const MessageContact *>(message_content);
+ dependencies.add(content->contact.get_user_id());
+ break;
+ }
+ case MessageContentType::Document:
+ break;
+ case MessageContentType::Game: {
+ const auto *content = static_cast<const MessageGame *>(message_content);
+ dependencies.add(content->game.get_bot_user_id());
+ break;
+ }
+ case MessageContentType::Invoice:
+ break;
+ case MessageContentType::LiveLocation:
+ break;
+ case MessageContentType::Location:
+ break;
+ case MessageContentType::Photo:
+ break;
+ case MessageContentType::Sticker:
+ break;
+ case MessageContentType::Venue:
+ break;
+ case MessageContentType::Video:
+ break;
+ case MessageContentType::VideoNote:
+ break;
+ case MessageContentType::VoiceNote:
+ break;
+ case MessageContentType::ChatCreate: {
+ const auto *content = static_cast<const MessageChatCreate *>(message_content);
+ for (auto &participant_user_id : content->participant_user_ids) {
+ dependencies.add(participant_user_id);
+ }
+ break;
+ }
+ case MessageContentType::ChatChangeTitle:
+ break;
+ case MessageContentType::ChatChangePhoto:
+ break;
+ case MessageContentType::ChatDeletePhoto:
+ break;
+ case MessageContentType::ChatDeleteHistory:
+ break;
+ case MessageContentType::ChatAddUsers: {
+ const auto *content = static_cast<const MessageChatAddUsers *>(message_content);
+ for (auto &user_id : content->user_ids) {
+ dependencies.add(user_id);
+ }
+ break;
+ }
+ case MessageContentType::ChatJoinedByLink:
+ break;
+ case MessageContentType::ChatDeleteUser: {
+ const auto *content = static_cast<const MessageChatDeleteUser *>(message_content);
+ dependencies.add(content->user_id);
+ break;
+ }
+ case MessageContentType::ChatMigrateTo: {
+ const auto *content = static_cast<const MessageChatMigrateTo *>(message_content);
+ dependencies.add(content->migrated_to_channel_id);
+ break;
+ }
+ case MessageContentType::ChannelCreate:
+ break;
+ case MessageContentType::ChannelMigrateFrom: {
+ const auto *content = static_cast<const MessageChannelMigrateFrom *>(message_content);
+ dependencies.add(content->migrated_from_chat_id);
+ break;
+ }
+ case MessageContentType::PinMessage:
+ break;
+ case MessageContentType::GameScore:
+ break;
+ case MessageContentType::ScreenshotTaken:
+ break;
+ case MessageContentType::ChatSetTtl:
+ break;
+ case MessageContentType::Unsupported:
+ break;
+ case MessageContentType::Call:
+ break;
+ case MessageContentType::PaymentSuccessful: {
+ const auto *content = static_cast<const MessagePaymentSuccessful *>(message_content);
+ dependencies.add_dialog_and_dependencies(content->invoice_dialog_id);
+ break;
+ }
+ case MessageContentType::ContactRegistered:
+ break;
+ case MessageContentType::ExpiredPhoto:
+ break;
+ case MessageContentType::ExpiredVideo:
+ break;
+ case MessageContentType::CustomServiceAction:
+ break;
+ case MessageContentType::WebsiteConnected:
+ break;
+ case MessageContentType::PassportDataSent:
+ break;
+ case MessageContentType::PassportDataReceived:
+ break;
+ case MessageContentType::Poll:
+ // no need to add poll dependencies, because they are forcely loaded with the poll
+ break;
+ case MessageContentType::Dice:
+ break;
+ case MessageContentType::ProximityAlertTriggered: {
+ const auto *content = static_cast<const MessageProximityAlertTriggered *>(message_content);
+ dependencies.add_message_sender_dependencies(content->traveler_dialog_id);
+ dependencies.add_message_sender_dependencies(content->watcher_dialog_id);
+ break;
+ }
+ case MessageContentType::GroupCall:
+ break;
+ case MessageContentType::InviteToGroupCall: {
+ const auto *content = static_cast<const MessageInviteToGroupCall *>(message_content);
+ for (auto &user_id : content->user_ids) {
+ dependencies.add(user_id);
+ }
+ break;
+ }
+ case MessageContentType::ChatSetTheme:
+ break;
+ case MessageContentType::WebViewDataSent:
+ break;
+ case MessageContentType::WebViewDataReceived:
+ break;
+ case MessageContentType::GiftPremium:
+ break;
+ case MessageContentType::TopicCreate:
+ break;
+ case MessageContentType::TopicEdit:
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ add_formatted_text_dependencies(dependencies, get_message_content_text(message_content));
+}
+
+void update_forum_topic_info_by_service_message_content(Td *td, const MessageContent *content, DialogId dialog_id,
+ MessageId top_thread_message_id) {
+ if (!top_thread_message_id.is_valid()) {
+ return;
+ }
+ switch (content->get_type()) {
+ case MessageContentType::TopicEdit:
+ return td->forum_topic_manager_->on_forum_topic_edited(
+ dialog_id, top_thread_message_id, static_cast<const MessageTopicEdit *>(content)->edited_data);
+ default:
+ // nothing to do
+ return;
+ }
+}
+
+void on_sent_message_content(Td *td, const MessageContent *content) {
+ switch (content->get_type()) {
+ case MessageContentType::Animation:
+ return td->animations_manager_->add_saved_animation_by_id(get_message_content_upload_file_id(content));
+ case MessageContentType::Sticker:
+ return td->stickers_manager_->add_recent_sticker_by_id(false, get_message_content_upload_file_id(content));
+ default:
+ // nothing to do
+ return;
+ }
+}
+
+void move_message_content_sticker_set_to_top(Td *td, const MessageContent *content) {
+ CHECK(content != nullptr);
+ if (content->get_type() == MessageContentType::Sticker) {
+ td->stickers_manager_->move_sticker_set_to_top_by_sticker_id(get_message_content_upload_file_id(content));
+ return;
+ }
+
+ auto text = get_message_content_text(content);
+ if (text == nullptr) {
+ return;
+ }
+ vector<CustomEmojiId> custom_emoji_ids;
+ for (auto &entity : text->entities) {
+ if (entity.type == MessageEntity::Type::CustomEmoji) {
+ custom_emoji_ids.push_back(entity.custom_emoji_id);
+ }
+ }
+ if (!custom_emoji_ids.empty()) {
+ td->stickers_manager_->move_sticker_set_to_top_by_custom_emoji_ids(custom_emoji_ids);
+ }
+}
+
+bool is_unsent_animated_emoji_click(Td *td, DialogId dialog_id, const DialogAction &action) {
+ auto emoji = action.get_watching_animations_emoji();
+ if (emoji.empty()) {
+ // not a WatchingAnimations action
+ return false;
+ }
+ return !td->stickers_manager_->is_sent_animated_emoji_click(dialog_id, remove_emoji_modifiers(emoji));
+}
+
+void init_stickers_manager(Td *td) {
+ td->stickers_manager_->init();
+}
+
+void on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date) {
+ send_closure(G()->top_dialog_manager(), &TopDialogManager::on_dialog_used, category, dialog_id, date);
+}
+
+void update_used_hashtags(Td *td, const MessageContent *content) {
+ const FormattedText *text = get_message_content_text(content);
+ if (text == nullptr || text->text.empty()) {
+ return;
+ }
+
+ const unsigned char *ptr = Slice(text->text).ubegin();
+ const unsigned char *end = Slice(text->text).uend();
+ int32 utf16_pos = 0;
+ uint32 skipped_code = 0;
+ for (auto &entity : text->entities) {
+ if (entity.type != MessageEntity::Type::Hashtag) {
+ continue;
+ }
+ while (utf16_pos < entity.offset && ptr < end) {
+ utf16_pos += 1 + (ptr[0] >= 0xf0);
+ ptr = next_utf8_unsafe(ptr, &skipped_code);
+ }
+ CHECK(utf16_pos == entity.offset);
+ auto from = ptr;
+
+ while (utf16_pos < entity.offset + entity.length && ptr < end) {
+ utf16_pos += 1 + (ptr[0] >= 0xf0);
+ ptr = next_utf8_unsafe(ptr, &skipped_code);
+ }
+ CHECK(utf16_pos == entity.offset + entity.length);
+ auto to = ptr;
+
+ send_closure(td->hashtag_hints_, &HashtagHints::hashtag_used, Slice(from + 1, to).str());
+ }
+}
+
+void recognize_message_content_speech(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ Promise<Unit> &&promise) {
+ switch (content->get_type()) {
+ case MessageContentType::VideoNote:
+ return td->video_notes_manager_->recognize_speech(full_message_id, std::move(promise));
+ case MessageContentType::VoiceNote:
+ return td->voice_notes_manager_->recognize_speech(full_message_id, std::move(promise));
+ default:
+ return promise.set_error(Status::Error(400, "Invalid message specified"));
+ }
+}
+
+void rate_message_content_speech_recognition(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ bool is_good, Promise<Unit> &&promise) {
+ switch (content->get_type()) {
+ case MessageContentType::VideoNote:
+ return td->video_notes_manager_->rate_speech_recognition(full_message_id, is_good, std::move(promise));
+ case MessageContentType::VoiceNote:
+ return td->voice_notes_manager_->rate_speech_recognition(full_message_id, is_good, std::move(promise));
+ default:
+ return promise.set_error(Status::Error(400, "Invalid message specified"));
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageContent.h b/protocols/Telegram/tdlib/td/td/telegram/MessageContent.h
new file mode 100644
index 0000000000..22438bc884
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageContent.h
@@ -0,0 +1,276 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/EncryptedFile.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/InputGroupCallId.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/MessageContentType.h"
+#include "td/telegram/MessageCopyOptions.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/ReplyMarkup.h"
+#include "td/telegram/secret_api.h"
+#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/StickerType.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/TopDialogCategory.h"
+#include "td/telegram/UserId.h"
+#include "td/telegram/WebPageId.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+#include <utility>
+
+namespace td {
+
+class Dependencies;
+class DialogAction;
+class Game;
+class MultiPromiseActor;
+struct Photo;
+class Td;
+
+// Do not forget to update merge_message_contents when one of the inheritors of this class changes
+class MessageContent {
+ public:
+ MessageContent() = default;
+ MessageContent(const MessageContent &) = default;
+ MessageContent &operator=(const MessageContent &) = default;
+ MessageContent(MessageContent &&) = default;
+ MessageContent &operator=(MessageContent &&) = default;
+
+ virtual MessageContentType get_type() const = 0;
+ virtual ~MessageContent() = default;
+};
+
+struct InputMessageContent {
+ unique_ptr<MessageContent> content;
+ bool disable_web_page_preview = false;
+ bool clear_draft = false;
+ int32 ttl = 0;
+ UserId via_bot_user_id;
+ string emoji;
+
+ InputMessageContent(unique_ptr<MessageContent> &&content, bool disable_web_page_preview, bool clear_draft, int32 ttl,
+ UserId via_bot_user_id, string emoji)
+ : content(std::move(content))
+ , disable_web_page_preview(disable_web_page_preview)
+ , clear_draft(clear_draft)
+ , ttl(ttl)
+ , via_bot_user_id(via_bot_user_id)
+ , emoji(std::move(emoji)) {
+ }
+};
+
+struct InlineMessageContent {
+ unique_ptr<MessageContent> message_content;
+ unique_ptr<ReplyMarkup> message_reply_markup;
+ bool disable_web_page_preview;
+};
+
+void store_message_content(const MessageContent *content, LogEventStorerCalcLength &storer);
+
+void store_message_content(const MessageContent *content, LogEventStorerUnsafe &storer);
+
+void parse_message_content(unique_ptr<MessageContent> &content, LogEventParser &parser);
+
+InlineMessageContent create_inline_message_content(Td *td, FileId file_id,
+ tl_object_ptr<telegram_api::BotInlineMessage> &&bot_inline_message,
+ int32 allowed_media_content_id, Photo *photo, Game *game);
+
+unique_ptr<MessageContent> create_text_message_content(string text, vector<MessageEntity> entities,
+ WebPageId web_page_id);
+
+unique_ptr<MessageContent> create_contact_registered_message_content();
+
+unique_ptr<MessageContent> create_screenshot_taken_message_content();
+
+unique_ptr<MessageContent> create_chat_set_ttl_message_content(int32 ttl);
+
+Result<InputMessageContent> get_input_message_content(
+ DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content, Td *td, bool is_premium);
+
+bool can_have_input_media(const Td *td, const MessageContent *content, bool is_server);
+
+SecretInputMedia get_secret_input_media(const MessageContent *content, Td *td,
+ tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
+ BufferSlice thumbnail, int32 layer);
+
+tl_object_ptr<telegram_api::InputMedia> get_input_media(const MessageContent *content, Td *td,
+ tl_object_ptr<telegram_api::InputFile> input_file,
+ tl_object_ptr<telegram_api::InputFile> input_thumbnail,
+ FileId file_id, FileId thumbnail_file_id, int32 ttl,
+ bool force);
+
+tl_object_ptr<telegram_api::InputMedia> get_input_media(const MessageContent *content, Td *td, int32 ttl,
+ const string &emoji, bool force);
+
+tl_object_ptr<telegram_api::InputMedia> get_fake_input_media(Td *td, tl_object_ptr<telegram_api::InputFile> input_file,
+ FileId file_id);
+
+void delete_message_content_thumbnail(MessageContent *content, Td *td);
+
+Status can_send_message_content(DialogId dialog_id, const MessageContent *content, bool is_forward, const Td *td);
+
+bool can_forward_message_content(const MessageContent *content);
+
+bool update_opened_message_content(MessageContent *content);
+
+int32 get_message_content_index_mask(const MessageContent *content, const Td *td, bool is_outgoing);
+
+StickerType get_message_content_sticker_type(const Td *td, const MessageContent *content);
+
+MessageId get_message_content_pinned_message_id(const MessageContent *content);
+
+string get_message_content_theme_name(const MessageContent *content);
+
+FullMessageId get_message_content_replied_message_id(DialogId dialog_id, const MessageContent *content);
+
+std::pair<InputGroupCallId, bool> get_message_content_group_call_info(const MessageContent *content);
+
+vector<UserId> get_message_content_added_user_ids(const MessageContent *content);
+
+UserId get_message_content_deleted_user_id(const MessageContent *content);
+
+int32 get_message_content_live_location_period(const MessageContent *content);
+
+bool get_message_content_poll_is_anonymous(const Td *td, const MessageContent *content);
+
+bool get_message_content_poll_is_closed(const Td *td, const MessageContent *content);
+
+bool has_message_content_web_page(const MessageContent *content);
+
+void remove_message_content_web_page(MessageContent *content);
+
+bool can_message_content_have_media_timestamp(const MessageContent *content);
+
+void set_message_content_poll_answer(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ vector<int32> &&option_ids, Promise<Unit> &&promise);
+
+void get_message_content_poll_voters(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ int32 option_id, int32 offset, int32 limit,
+ Promise<std::pair<int32, vector<UserId>>> &&promise);
+
+void stop_message_content_poll(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ unique_ptr<ReplyMarkup> &&reply_markup, Promise<Unit> &&promise);
+
+void merge_message_contents(Td *td, const MessageContent *old_content, MessageContent *new_content,
+ bool need_message_changed_warning, DialogId dialog_id, bool need_merge_files,
+ bool &is_content_changed, bool &need_update);
+
+bool merge_message_content_file_id(Td *td, MessageContent *message_content, FileId new_file_id);
+
+void register_message_content(Td *td, const MessageContent *content, FullMessageId full_message_id, const char *source);
+
+void reregister_message_content(Td *td, const MessageContent *old_content, const MessageContent *new_content,
+ FullMessageId full_message_id, const char *source);
+
+void unregister_message_content(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ const char *source);
+
+unique_ptr<MessageContent> get_secret_message_content(
+ Td *td, string message_text, unique_ptr<EncryptedFile> file,
+ tl_object_ptr<secret_api::DecryptedMessageMedia> &&media_ptr,
+ vector<tl_object_ptr<secret_api::MessageEntity>> &&secret_entities, DialogId owner_dialog_id,
+ MultiPromiseActor &load_data_multipromise, bool is_premium);
+
+unique_ptr<MessageContent> get_message_content(Td *td, FormattedText message_text,
+ tl_object_ptr<telegram_api::MessageMedia> &&media_ptr,
+ DialogId owner_dialog_id, bool is_content_read, UserId via_bot_user_id,
+ int32 *ttl, bool *disable_web_page_preview, const char *source);
+
+enum class MessageContentDupType : int32 { Send, SendViaBot, Forward, Copy, ServerCopy };
+
+unique_ptr<MessageContent> dup_message_content(Td *td, DialogId dialog_id, const MessageContent *content,
+ MessageContentDupType type, MessageCopyOptions &&copy_options);
+
+unique_ptr<MessageContent> get_action_message_content(Td *td, tl_object_ptr<telegram_api::MessageAction> &&action_ptr,
+ DialogId owner_dialog_id, DialogId reply_in_dialog_id,
+ MessageId reply_to_message_id);
+
+tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content, Td *td,
+ DialogId dialog_id, int32 message_date,
+ bool is_content_secret, bool skip_bot_commands,
+ int32 max_media_timestamp);
+
+FormattedText *get_message_content_text_mutable(MessageContent *content);
+
+const FormattedText *get_message_content_text(const MessageContent *content);
+
+const FormattedText *get_message_content_caption(const MessageContent *content);
+
+int32 get_message_content_duration(const MessageContent *content, const Td *td);
+
+int32 get_message_content_media_duration(const MessageContent *content, const Td *td);
+
+const Photo *get_message_content_photo(const MessageContent *content);
+
+FileId get_message_content_upload_file_id(const MessageContent *content);
+
+FileId get_message_content_any_file_id(const MessageContent *content);
+
+void update_message_content_file_id_remote(MessageContent *content, FileId file_id);
+
+FileId get_message_content_thumbnail_file_id(const MessageContent *content, const Td *td);
+
+vector<FileId> get_message_content_file_ids(const MessageContent *content, const Td *td);
+
+string get_message_content_search_text(const Td *td, const MessageContent *content);
+
+bool update_message_content_extended_media(MessageContent *content,
+ telegram_api::object_ptr<telegram_api::MessageExtendedMedia> extended_media,
+ DialogId owner_dialog_id, Td *td);
+
+bool need_poll_message_content_extended_media(const MessageContent *content);
+
+void get_message_content_animated_emoji_click_sticker(const MessageContent *content, FullMessageId full_message_id,
+ Td *td, Promise<td_api::object_ptr<td_api::sticker>> &&promise);
+
+void on_message_content_animated_emoji_clicked(const MessageContent *content, FullMessageId full_message_id, Td *td,
+ string &&emoji, string &&data);
+
+bool need_reget_message_content(const MessageContent *content);
+
+bool need_delay_message_content_notification(const MessageContent *content, UserId my_user_id);
+
+void update_expired_message_content(unique_ptr<MessageContent> &content);
+
+void update_failed_to_send_message_content(Td *td, unique_ptr<MessageContent> &content);
+
+void add_message_content_dependencies(Dependencies &dependencies, const MessageContent *message_content);
+
+void update_forum_topic_info_by_service_message_content(Td *td, const MessageContent *content, DialogId dialog_id,
+ MessageId top_thread_message_id);
+
+void on_sent_message_content(Td *td, const MessageContent *content);
+
+void move_message_content_sticker_set_to_top(Td *td, const MessageContent *content);
+
+bool is_unsent_animated_emoji_click(Td *td, DialogId dialog_id, const DialogAction &action);
+
+void init_stickers_manager(Td *td);
+
+void on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date);
+
+void update_used_hashtags(Td *td, const MessageContent *content);
+
+void recognize_message_content_speech(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ Promise<Unit> &&promise);
+
+void rate_message_content_speech_recognition(Td *td, const MessageContent *content, FullMessageId full_message_id,
+ bool is_good, Promise<Unit> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageContentType.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageContentType.cpp
new file mode 100644
index 0000000000..1395b1e70f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageContentType.cpp
@@ -0,0 +1,391 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageContentType.h"
+
+namespace td {
+
+StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType content_type) {
+ switch (content_type) {
+ case MessageContentType::None:
+ return string_builder << "None";
+ case MessageContentType::Animation:
+ return string_builder << "Animation";
+ case MessageContentType::Audio:
+ return string_builder << "Audio";
+ case MessageContentType::Document:
+ return string_builder << "Document";
+ case MessageContentType::ExpiredPhoto:
+ return string_builder << "ExpiredPhoto";
+ case MessageContentType::Photo:
+ return string_builder << "Photo";
+ case MessageContentType::ExpiredVideo:
+ return string_builder << "ExpiredVideo";
+ case MessageContentType::Video:
+ return string_builder << "Video";
+ case MessageContentType::VideoNote:
+ return string_builder << "VideoNote";
+ case MessageContentType::VoiceNote:
+ return string_builder << "VoiceNote";
+ case MessageContentType::Contact:
+ return string_builder << "Contact";
+ case MessageContentType::LiveLocation:
+ return string_builder << "LiveLocation";
+ case MessageContentType::Location:
+ return string_builder << "Location";
+ case MessageContentType::Venue:
+ return string_builder << "Venue";
+ case MessageContentType::Game:
+ return string_builder << "Game";
+ case MessageContentType::Invoice:
+ return string_builder << "Invoice";
+ case MessageContentType::Sticker:
+ return string_builder << "Sticker";
+ case MessageContentType::Text:
+ return string_builder << "Text";
+ case MessageContentType::Unsupported:
+ return string_builder << "Unsupported";
+ case MessageContentType::ChatCreate:
+ return string_builder << "ChatCreate";
+ case MessageContentType::ChatChangeTitle:
+ return string_builder << "ChatChangeTitle";
+ case MessageContentType::ChatChangePhoto:
+ return string_builder << "ChatChangePhoto";
+ case MessageContentType::ChatDeletePhoto:
+ return string_builder << "ChatDeletePhoto";
+ case MessageContentType::ChatDeleteHistory:
+ return string_builder << "ChatDeleteHistory";
+ case MessageContentType::ChatAddUsers:
+ return string_builder << "ChatAddUsers";
+ case MessageContentType::ChatJoinedByLink:
+ return string_builder << "ChatJoinedByLink";
+ case MessageContentType::ChatDeleteUser:
+ return string_builder << "ChatDeleteUser";
+ case MessageContentType::ChatMigrateTo:
+ return string_builder << "ChatMigrateTo";
+ case MessageContentType::ChannelCreate:
+ return string_builder << "ChannelCreate";
+ case MessageContentType::ChannelMigrateFrom:
+ return string_builder << "ChannelMigrateFrom";
+ case MessageContentType::PinMessage:
+ return string_builder << "PinMessage";
+ case MessageContentType::GameScore:
+ return string_builder << "GameScore";
+ case MessageContentType::ScreenshotTaken:
+ return string_builder << "ScreenshotTaken";
+ case MessageContentType::ChatSetTtl:
+ return string_builder << "ChatSetTtl";
+ case MessageContentType::Call:
+ return string_builder << "Call";
+ case MessageContentType::PaymentSuccessful:
+ return string_builder << "PaymentSuccessful";
+ case MessageContentType::ContactRegistered:
+ return string_builder << "ContactRegistered";
+ case MessageContentType::CustomServiceAction:
+ return string_builder << "CustomServiceAction";
+ case MessageContentType::WebsiteConnected:
+ return string_builder << "WebsiteConnected";
+ case MessageContentType::PassportDataSent:
+ return string_builder << "PassportDataSent";
+ case MessageContentType::PassportDataReceived:
+ return string_builder << "PassportDataReceived";
+ case MessageContentType::Poll:
+ return string_builder << "Poll";
+ case MessageContentType::Dice:
+ return string_builder << "Dice";
+ case MessageContentType::ProximityAlertTriggered:
+ return string_builder << "ProximityAlertTriggered";
+ case MessageContentType::GroupCall:
+ return string_builder << "GroupCall";
+ case MessageContentType::InviteToGroupCall:
+ return string_builder << "InviteToGroupCall";
+ case MessageContentType::ChatSetTheme:
+ return string_builder << "ChatSetTheme";
+ case MessageContentType::WebViewDataSent:
+ return string_builder << "WebViewDataSent";
+ case MessageContentType::WebViewDataReceived:
+ return string_builder << "WebViewDataReceived";
+ case MessageContentType::GiftPremium:
+ return string_builder << "GiftPremium";
+ case MessageContentType::TopicCreate:
+ return string_builder << "TopicCreate";
+ case MessageContentType::TopicEdit:
+ return string_builder << "TopicEdit";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+bool is_allowed_media_group_content(MessageContentType content_type) {
+ switch (content_type) {
+ case MessageContentType::Audio:
+ case MessageContentType::Document:
+ case MessageContentType::Photo:
+ case MessageContentType::Video:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ return true;
+ case MessageContentType::Animation:
+ case MessageContentType::Contact:
+ case MessageContentType::Game:
+ case MessageContentType::Invoice:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Sticker:
+ case MessageContentType::Text:
+ case MessageContentType::Unsupported:
+ case MessageContentType::Venue:
+ case MessageContentType::VideoNote:
+ case MessageContentType::VoiceNote:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::Poll:
+ case MessageContentType::Dice:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+bool is_homogenous_media_group_content(MessageContentType content_type) {
+ return content_type == MessageContentType::Audio || content_type == MessageContentType::Document;
+}
+
+bool is_secret_message_content(int32 ttl, MessageContentType content_type) {
+ if (ttl <= 0 || ttl > 60) {
+ return false;
+ }
+ switch (content_type) {
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Photo:
+ case MessageContentType::Video:
+ case MessageContentType::VideoNote:
+ case MessageContentType::VoiceNote:
+ return true;
+ case MessageContentType::Contact:
+ case MessageContentType::Document:
+ case MessageContentType::Game:
+ case MessageContentType::Invoice:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Sticker:
+ case MessageContentType::Text:
+ case MessageContentType::Unsupported:
+ case MessageContentType::Venue:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::Poll:
+ case MessageContentType::Dice:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+bool is_service_message_content(MessageContentType content_type) {
+ switch (content_type) {
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Contact:
+ case MessageContentType::Document:
+ case MessageContentType::Game:
+ case MessageContentType::Invoice:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Photo:
+ case MessageContentType::Sticker:
+ case MessageContentType::Text:
+ case MessageContentType::Unsupported:
+ case MessageContentType::Venue:
+ case MessageContentType::Video:
+ case MessageContentType::VideoNote:
+ case MessageContentType::VoiceNote:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::Poll:
+ case MessageContentType::Dice:
+ return false;
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ return true;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+bool can_have_message_content_caption(MessageContentType content_type) {
+ switch (content_type) {
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Document:
+ case MessageContentType::Photo:
+ case MessageContentType::Video:
+ case MessageContentType::VoiceNote:
+ return true;
+ case MessageContentType::Contact:
+ case MessageContentType::Game:
+ case MessageContentType::Invoice:
+ case MessageContentType::LiveLocation:
+ case MessageContentType::Location:
+ case MessageContentType::Sticker:
+ case MessageContentType::Text:
+ case MessageContentType::Unsupported:
+ case MessageContentType::Venue:
+ case MessageContentType::VideoNote:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::Poll:
+ case MessageContentType::Dice:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+uint64 get_message_content_chain_id(MessageContentType content_type) {
+ switch (content_type) {
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Document:
+ case MessageContentType::Invoice:
+ case MessageContentType::Photo:
+ case MessageContentType::Sticker:
+ case MessageContentType::Video:
+ case MessageContentType::VideoNote:
+ case MessageContentType::VoiceNote:
+ return 1;
+ default:
+ return 2;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageContentType.h b/protocols/Telegram/tdlib/td/td/telegram/MessageContentType.h
new file mode 100644
index 0000000000..d75c1b4efe
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageContentType.h
@@ -0,0 +1,91 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+// increase MessageUnsupported::CURRENT_VERSION each time a new message content type is added
+enum class MessageContentType : int32 {
+ None = -1,
+ Text,
+ Animation,
+ Audio,
+ Document,
+ Photo,
+ Sticker,
+ Video,
+ VoiceNote,
+ Contact,
+ Location,
+ Venue,
+ ChatCreate,
+ ChatChangeTitle,
+ ChatChangePhoto,
+ ChatDeletePhoto,
+ ChatDeleteHistory,
+ ChatAddUsers,
+ ChatJoinedByLink,
+ ChatDeleteUser,
+ ChatMigrateTo,
+ ChannelCreate,
+ ChannelMigrateFrom,
+ PinMessage,
+ Game,
+ GameScore,
+ ScreenshotTaken,
+ ChatSetTtl,
+ Unsupported,
+ Call,
+ Invoice,
+ PaymentSuccessful,
+ VideoNote,
+ ContactRegistered,
+ ExpiredPhoto,
+ ExpiredVideo,
+ LiveLocation,
+ CustomServiceAction,
+ WebsiteConnected,
+ PassportDataSent,
+ PassportDataReceived,
+ Poll,
+ Dice,
+ ProximityAlertTriggered,
+ GroupCall,
+ InviteToGroupCall,
+ ChatSetTheme,
+ WebViewDataSent,
+ WebViewDataReceived,
+ GiftPremium,
+ TopicCreate,
+ TopicEdit
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, MessageContentType content_type);
+
+bool is_allowed_media_group_content(MessageContentType content_type);
+
+bool is_homogenous_media_group_content(MessageContentType content_type);
+
+bool is_secret_message_content(int32 ttl, MessageContentType content_type);
+
+bool is_service_message_content(MessageContentType content_type);
+
+bool can_have_message_content_caption(MessageContentType content_type);
+
+uint64 get_message_content_chain_id(MessageContentType content_type);
+
+struct MessageContentTypeHash {
+ uint32 operator()(MessageContentType content_type) const {
+ return Hash<int32>()(static_cast<int32>(content_type));
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageCopyOptions.h b/protocols/Telegram/tdlib/td/td/telegram/MessageCopyOptions.h
new file mode 100644
index 0000000000..c0b490eeda
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageCopyOptions.h
@@ -0,0 +1,57 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/ReplyMarkup.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+struct MessageCopyOptions {
+ bool send_copy = false;
+ bool replace_caption = false;
+ FormattedText new_caption;
+ MessageId reply_to_message_id;
+ unique_ptr<ReplyMarkup> reply_markup;
+
+ MessageCopyOptions() = default;
+ MessageCopyOptions(bool send_copy, bool remove_caption) : send_copy(send_copy), replace_caption(remove_caption) {
+ }
+
+ bool is_supported_server_side() const {
+ if (!send_copy) {
+ return true;
+ }
+ if ((replace_caption && !new_caption.text.empty()) || reply_to_message_id.is_valid() || reply_markup != nullptr) {
+ return false;
+ }
+ return true;
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, MessageCopyOptions copy_options) {
+ if (copy_options.send_copy) {
+ string_builder << "CopyOptions[replace_caption = " << copy_options.replace_caption;
+ if (copy_options.replace_caption) {
+ string_builder << ", new_caption = " << copy_options.new_caption;
+ }
+ if (copy_options.reply_to_message_id.is_valid()) {
+ string_builder << ", in reply to " << copy_options.reply_to_message_id;
+ }
+ if (copy_options.reply_markup != nullptr) {
+ string_builder << ", with reply markup";
+ }
+ string_builder << "]";
+ }
+ return string_builder;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageDb.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageDb.cpp
new file mode 100644
index 0000000000..1eb2c70c92
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageDb.cpp
@@ -0,0 +1,1260 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageDb.h"
+
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/UserId.h"
+#include "td/telegram/Version.h"
+
+#include "td/db/SqliteConnectionSafe.h"
+#include "td/db/SqliteDb.h"
+#include "td/db/SqliteStatement.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/SchedulerLocalStorage.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StackAllocator.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/Time.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/unicode.h"
+#include "td/utils/utf8.h"
+
+#include <algorithm>
+#include <array>
+#include <iterator>
+#include <limits>
+#include <tuple>
+#include <utility>
+
+namespace td {
+
+static constexpr int32 MESSAGE_DB_INDEX_COUNT = 30;
+static constexpr int32 MESSAGE_DB_INDEX_COUNT_OLD = 9;
+
+// NB: must happen inside a transaction
+Status init_message_db(SqliteDb &db, int32 version) {
+ LOG(INFO) << "Init message database " << tag("version", version);
+
+ // Check if database exists
+ TRY_RESULT(has_table, db.has_table("messages"));
+ if (!has_table) {
+ version = 0;
+ } else if (version < static_cast<int32>(DbVersion::DialogDbCreated) || version > current_db_version()) {
+ TRY_STATUS(drop_message_db(db, version));
+ version = 0;
+ }
+
+ auto add_media_indices = [&db](int begin, int end) {
+ for (int i = begin; i < end; i++) {
+ TRY_STATUS(db.exec(PSLICE() << "CREATE INDEX IF NOT EXISTS message_index_" << i
+ << " ON messages (dialog_id, message_id) WHERE (index_mask & " << (1 << i)
+ << ") != 0"));
+ }
+ return Status::OK();
+ };
+
+ auto add_fts = [&db] {
+ TRY_STATUS(
+ db.exec("CREATE INDEX IF NOT EXISTS message_by_search_id ON messages "
+ "(search_id) WHERE search_id IS NOT NULL"));
+
+ TRY_STATUS(
+ db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(text, content='messages', "
+ "content_rowid='search_id', tokenize = \"unicode61 remove_diacritics 0 tokenchars '\a'\")"));
+ TRY_STATUS(db.exec(
+ "CREATE TRIGGER IF NOT EXISTS trigger_fts_delete BEFORE DELETE ON messages WHEN OLD.search_id IS NOT NULL"
+ " BEGIN INSERT INTO messages_fts(messages_fts, rowid, text) VALUES(\'delete\', OLD.search_id, OLD.text); END"));
+ TRY_STATUS(db.exec(
+ "CREATE TRIGGER IF NOT EXISTS trigger_fts_insert AFTER INSERT ON messages WHEN NEW.search_id IS NOT NULL"
+ " BEGIN INSERT INTO messages_fts(rowid, text) VALUES(NEW.search_id, NEW.text); END"));
+ //TRY_STATUS(db.exec(
+ //"CREATE TRIGGER IF NOT EXISTS trigger_fts_update AFTER UPDATE ON messages WHEN NEW.search_id IS NOT NULL OR "
+ //"OLD.search_id IS NOT NULL"
+ //" BEGIN "
+ //"INSERT INTO messages_fts(messages_fts, rowid, text) VALUES(\'delete\', OLD.search_id, OLD.text); "
+ //"INSERT INTO messages_fts(rowid, text) VALUES(NEW.search_id, NEW.text); "
+ //" END"));
+
+ return Status::OK();
+ };
+ auto add_call_index = [&db] {
+ for (int i = static_cast<int>(MessageSearchFilter::Call) - 1; i < static_cast<int>(MessageSearchFilter::MissedCall);
+ i++) {
+ TRY_STATUS(db.exec(PSLICE() << "CREATE INDEX IF NOT EXISTS full_message_index_" << i
+ << " ON messages (unique_message_id) WHERE (index_mask & " << (1 << i) << ") != 0"));
+ }
+ return Status::OK();
+ };
+ auto add_notification_id_index = [&db] {
+ return db.exec(
+ "CREATE INDEX IF NOT EXISTS message_by_notification_id ON messages (dialog_id, notification_id) WHERE "
+ "notification_id IS NOT NULL");
+ };
+ auto add_scheduled_messages_table = [&db] {
+ TRY_STATUS(
+ db.exec("CREATE TABLE IF NOT EXISTS scheduled_messages (dialog_id INT8, message_id INT8, "
+ "server_message_id INT4, data BLOB, PRIMARY KEY (dialog_id, message_id))"));
+
+ TRY_STATUS(
+ db.exec("CREATE INDEX IF NOT EXISTS message_by_server_message_id ON scheduled_messages "
+ "(dialog_id, server_message_id) WHERE server_message_id IS NOT NULL"));
+ return Status::OK();
+ };
+
+ if (version == 0) {
+ LOG(INFO) << "Create new message database";
+ TRY_STATUS(
+ db.exec("CREATE TABLE IF NOT EXISTS messages (dialog_id INT8, message_id INT8, unique_message_id INT4, "
+ "sender_user_id INT8, random_id INT8, data BLOB, ttl_expires_at INT4, index_mask INT4, search_id INT8, "
+ "text STRING, notification_id INT4, top_thread_message_id INT8, PRIMARY KEY (dialog_id, message_id))"));
+
+ TRY_STATUS(
+ db.exec("CREATE INDEX IF NOT EXISTS message_by_random_id ON messages (dialog_id, random_id) "
+ "WHERE random_id IS NOT NULL"));
+
+ TRY_STATUS(
+ db.exec("CREATE INDEX IF NOT EXISTS message_by_unique_message_id ON messages "
+ "(unique_message_id) WHERE unique_message_id IS NOT NULL"));
+
+ TRY_STATUS(
+ db.exec("CREATE INDEX IF NOT EXISTS message_by_ttl ON messages "
+ "(ttl_expires_at) WHERE ttl_expires_at IS NOT NULL"));
+
+ TRY_STATUS(add_media_indices(0, MESSAGE_DB_INDEX_COUNT));
+
+ TRY_STATUS(add_fts());
+
+ TRY_STATUS(add_call_index());
+
+ TRY_STATUS(add_notification_id_index());
+
+ TRY_STATUS(add_scheduled_messages_table());
+
+ version = current_db_version();
+ }
+ if (version < static_cast<int32>(DbVersion::MessageDbMediaIndex)) {
+ TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN index_mask INT4"));
+ TRY_STATUS(add_media_indices(0, MESSAGE_DB_INDEX_COUNT_OLD));
+ }
+ if (version < static_cast<int32>(DbVersion::MessageDb30MediaIndex)) {
+ TRY_STATUS(add_media_indices(MESSAGE_DB_INDEX_COUNT_OLD, MESSAGE_DB_INDEX_COUNT));
+ }
+ if (version < static_cast<int32>(DbVersion::MessageDbFts)) {
+ TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN search_id INT8"));
+ TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN text STRING"));
+ TRY_STATUS(add_fts());
+ }
+ if (version < static_cast<int32>(DbVersion::MessagesCallIndex)) {
+ TRY_STATUS(add_call_index());
+ }
+ if (version < static_cast<int32>(DbVersion::AddNotificationsSupport)) {
+ TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN notification_id INT4"));
+ TRY_STATUS(add_notification_id_index());
+ }
+ if (version < static_cast<int32>(DbVersion::AddScheduledMessages)) {
+ TRY_STATUS(add_scheduled_messages_table());
+ }
+ if (version < static_cast<int32>(DbVersion::AddMessageThreadSupport)) {
+ TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN top_thread_message_id INT8"));
+ }
+ return Status::OK();
+}
+
+// NB: must happen inside a transaction
+Status drop_message_db(SqliteDb &db, int32 version) {
+ LOG(WARNING) << "Drop message database " << tag("version", version)
+ << tag("current_db_version", current_db_version());
+ return db.exec("DROP TABLE IF EXISTS messages");
+}
+
+class MessageDbImpl final : public MessageDbSyncInterface {
+ public:
+ explicit MessageDbImpl(SqliteDb db) : db_(std::move(db)) {
+ init().ensure();
+ }
+
+ Status init() {
+ TRY_RESULT_ASSIGN(
+ add_message_stmt_,
+ db_.get_statement("INSERT OR REPLACE INTO messages VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)"));
+ TRY_RESULT_ASSIGN(delete_message_stmt_,
+ db_.get_statement("DELETE FROM messages WHERE dialog_id = ?1 AND message_id = ?2"));
+ TRY_RESULT_ASSIGN(delete_all_dialog_messages_stmt_,
+ db_.get_statement("DELETE FROM messages WHERE dialog_id = ?1 AND message_id <= ?2"));
+ TRY_RESULT_ASSIGN(delete_dialog_messages_by_sender_stmt_,
+ db_.get_statement("DELETE FROM messages WHERE dialog_id = ?1 AND sender_user_id = ?2"));
+
+ TRY_RESULT_ASSIGN(
+ get_message_stmt_,
+ db_.get_statement("SELECT message_id, data FROM messages WHERE dialog_id = ?1 AND message_id = ?2"));
+ TRY_RESULT_ASSIGN(
+ get_message_by_random_id_stmt_,
+ db_.get_statement("SELECT message_id, data FROM messages WHERE dialog_id = ?1 AND random_id = ?2"));
+ TRY_RESULT_ASSIGN(
+ get_message_by_unique_message_id_stmt_,
+ db_.get_statement("SELECT dialog_id, message_id, data FROM messages WHERE unique_message_id = ?1"));
+
+ TRY_RESULT_ASSIGN(
+ get_expiring_messages_stmt_,
+ db_.get_statement(
+ "SELECT dialog_id, message_id, data FROM messages WHERE ?1 < ttl_expires_at AND ttl_expires_at <= ?2"));
+ TRY_RESULT_ASSIGN(get_expiring_messages_helper_stmt_,
+ db_.get_statement("SELECT MAX(ttl_expires_at), COUNT(*) FROM (SELECT ttl_expires_at FROM "
+ "messages WHERE ?1 < ttl_expires_at LIMIT ?2) AS T"));
+
+ TRY_RESULT_ASSIGN(get_messages_stmt_.asc_stmt_,
+ db_.get_statement("SELECT data, message_id FROM messages WHERE dialog_id = ?1 AND message_id > "
+ "?2 ORDER BY message_id ASC LIMIT ?3"));
+ TRY_RESULT_ASSIGN(get_messages_stmt_.desc_stmt_,
+ db_.get_statement("SELECT data, message_id FROM messages WHERE dialog_id = ?1 AND message_id < "
+ "?2 ORDER BY message_id DESC LIMIT ?3"));
+ TRY_RESULT_ASSIGN(get_scheduled_messages_stmt_,
+ db_.get_statement("SELECT data, message_id FROM scheduled_messages WHERE dialog_id = ?1 AND "
+ "message_id < ?2 ORDER BY message_id DESC LIMIT ?3"));
+ TRY_RESULT_ASSIGN(get_messages_from_notification_id_stmt_,
+ db_.get_statement("SELECT data, message_id FROM messages WHERE dialog_id = ?1 AND "
+ "notification_id < ?2 ORDER BY notification_id DESC LIMIT ?3"));
+ TRY_RESULT_ASSIGN(get_messages_fts_stmt_,
+ db_.get_statement("SELECT dialog_id, message_id, data, search_id FROM messages WHERE search_id "
+ "IN (SELECT rowid FROM messages_fts WHERE messages_fts MATCH ?1 AND rowid < ?2 "
+ "ORDER BY rowid DESC LIMIT ?3) ORDER BY search_id DESC"));
+
+ for (int32 i = 0; i < MESSAGE_DB_INDEX_COUNT; i++) {
+ TRY_RESULT_ASSIGN(
+ get_message_ids_stmts_[i],
+ db_.get_statement(
+ PSLICE() << "SELECT message_id FROM messages WHERE dialog_id = ?1 AND message_id < ?2 AND (index_mask & "
+ << (1 << i) << ") != 0 ORDER BY message_id DESC LIMIT 1000000"));
+
+ TRY_RESULT_ASSIGN(
+ get_messages_from_index_stmts_[i].desc_stmt_,
+ db_.get_statement(
+ PSLICE()
+ << "SELECT data, message_id FROM messages WHERE dialog_id = ?1 AND message_id < ?2 AND (index_mask & "
+ << (1 << i) << ") != 0 ORDER BY message_id DESC LIMIT ?3"));
+
+ TRY_RESULT_ASSIGN(
+ get_messages_from_index_stmts_[i].asc_stmt_,
+ db_.get_statement(
+ PSLICE()
+ << "SELECT data, message_id FROM messages WHERE dialog_id = ?1 AND message_id > ?2 AND (index_mask & "
+ << (1 << i) << ") != 0 ORDER BY message_id ASC LIMIT ?3"));
+
+ // LOG(ERROR) << get_messages_from_index_stmts_[i].desc_stmt_.explain().ok();
+ // LOG(ERROR) << get_messages_from_index_stmts_[i].asc_stmt_.explain().ok();
+ }
+
+ for (int i = static_cast<int>(MessageSearchFilter::Call) - 1, pos = 0;
+ i < static_cast<int>(MessageSearchFilter::MissedCall); i++, pos++) {
+ TRY_RESULT_ASSIGN(
+ get_calls_stmts_[pos],
+ db_.get_statement(
+ PSLICE()
+ << "SELECT dialog_id, message_id, data FROM messages WHERE unique_message_id < ?1 AND (index_mask & "
+ << (1 << i) << ") != 0 ORDER BY unique_message_id DESC LIMIT ?2"));
+ }
+
+ TRY_RESULT_ASSIGN(add_scheduled_message_stmt_,
+ db_.get_statement("INSERT OR REPLACE INTO scheduled_messages VALUES(?1, ?2, ?3, ?4)"));
+ TRY_RESULT_ASSIGN(
+ get_scheduled_message_stmt_,
+ db_.get_statement("SELECT message_id, data FROM scheduled_messages WHERE dialog_id = ?1 AND message_id = ?2"));
+ TRY_RESULT_ASSIGN(
+ get_scheduled_server_message_stmt_,
+ db_.get_statement(
+ "SELECT message_id, data FROM scheduled_messages WHERE dialog_id = ?1 AND server_message_id = ?2"));
+ TRY_RESULT_ASSIGN(delete_scheduled_message_stmt_,
+ db_.get_statement("DELETE FROM scheduled_messages WHERE dialog_id = ?1 AND message_id = ?2"));
+ TRY_RESULT_ASSIGN(
+ delete_scheduled_server_message_stmt_,
+ db_.get_statement("DELETE FROM scheduled_messages WHERE dialog_id = ?1 AND server_message_id = ?2"));
+
+ // LOG(ERROR) << get_message_stmt_.explain().ok();
+ // LOG(ERROR) << get_messages_from_notification_id_stmt.explain().ok();
+ // LOG(ERROR) << get_message_by_random_id_stmt_.explain().ok();
+ // LOG(ERROR) << get_message_by_unique_message_id_stmt_.explain().ok();
+
+ // LOG(ERROR) << get_expiring_messages_stmt_.explain().ok();
+ // LOG(ERROR) << get_expiring_messages_helper_stmt_.explain().ok();
+
+ // LOG(FATAL) << "EXPLAINED";
+
+ return Status::OK();
+ }
+
+ void add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, DialogId sender_dialog_id,
+ int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
+ NotificationId notification_id, MessageId top_thread_message_id, BufferSlice data) final {
+ LOG(INFO) << "Add " << full_message_id << " to database";
+ auto dialog_id = full_message_id.get_dialog_id();
+ auto message_id = full_message_id.get_message_id();
+ LOG_CHECK(dialog_id.is_valid()) << dialog_id << ' ' << message_id << ' ' << full_message_id;
+ CHECK(message_id.is_valid());
+ SCOPE_EXIT {
+ add_message_stmt_.reset();
+ };
+ add_message_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ add_message_stmt_.bind_int64(2, message_id.get()).ensure();
+
+ if (unique_message_id.is_valid()) {
+ add_message_stmt_.bind_int32(3, unique_message_id.get()).ensure();
+ } else {
+ add_message_stmt_.bind_null(3).ensure();
+ }
+
+ if (sender_dialog_id.is_valid()) {
+ add_message_stmt_.bind_int64(4, sender_dialog_id.get()).ensure();
+ } else {
+ add_message_stmt_.bind_null(4).ensure();
+ }
+
+ if (random_id != 0) {
+ add_message_stmt_.bind_int64(5, random_id).ensure();
+ } else {
+ add_message_stmt_.bind_null(5).ensure();
+ }
+
+ add_message_stmt_.bind_blob(6, data.as_slice()).ensure();
+
+ if (ttl_expires_at != 0) {
+ add_message_stmt_.bind_int32(7, ttl_expires_at).ensure();
+ } else {
+ add_message_stmt_.bind_null(7).ensure();
+ }
+
+ if (index_mask != 0) {
+ add_message_stmt_.bind_int32(8, index_mask).ensure();
+ } else {
+ add_message_stmt_.bind_null(8).ensure();
+ }
+ if (search_id != 0) {
+ // add dialog_id to text
+ text += PSTRING() << " \a" << dialog_id.get();
+ if (index_mask != 0) {
+ for (int i = 0; i < MESSAGE_DB_INDEX_COUNT; i++) {
+ if ((index_mask & (1 << i))) {
+ text += PSTRING() << " \a\a" << i;
+ }
+ }
+ }
+ add_message_stmt_.bind_int64(9, search_id).ensure();
+ } else {
+ text = "";
+ add_message_stmt_.bind_null(9).ensure();
+ }
+ if (!text.empty()) {
+ add_message_stmt_.bind_string(10, text).ensure();
+ } else {
+ add_message_stmt_.bind_null(10).ensure();
+ }
+ if (notification_id.is_valid()) {
+ add_message_stmt_.bind_int32(11, notification_id.get()).ensure();
+ } else {
+ add_message_stmt_.bind_null(11).ensure();
+ }
+ if (top_thread_message_id.is_valid()) {
+ add_message_stmt_.bind_int64(12, top_thread_message_id.get()).ensure();
+ } else {
+ add_message_stmt_.bind_null(12).ensure();
+ }
+
+ add_message_stmt_.step().ensure();
+ }
+
+ void add_scheduled_message(FullMessageId full_message_id, BufferSlice data) final {
+ LOG(INFO) << "Add " << full_message_id << " to database";
+ auto dialog_id = full_message_id.get_dialog_id();
+ auto message_id = full_message_id.get_message_id();
+ CHECK(dialog_id.is_valid());
+ CHECK(message_id.is_valid_scheduled());
+ SCOPE_EXIT {
+ add_scheduled_message_stmt_.reset();
+ };
+ add_scheduled_message_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ add_scheduled_message_stmt_.bind_int64(2, message_id.get()).ensure();
+
+ if (message_id.is_scheduled_server()) {
+ add_scheduled_message_stmt_.bind_int32(3, message_id.get_scheduled_server_message_id().get()).ensure();
+ } else {
+ add_scheduled_message_stmt_.bind_null(3).ensure();
+ }
+
+ add_scheduled_message_stmt_.bind_blob(4, data.as_slice()).ensure();
+
+ add_scheduled_message_stmt_.step().ensure();
+ }
+
+ void delete_message(FullMessageId full_message_id) final {
+ LOG(INFO) << "Delete " << full_message_id << " from database";
+ auto dialog_id = full_message_id.get_dialog_id();
+ auto message_id = full_message_id.get_message_id();
+ CHECK(dialog_id.is_valid());
+ CHECK(message_id.is_valid() || message_id.is_valid_scheduled());
+ bool is_scheduled = message_id.is_scheduled();
+ bool is_scheduled_server = is_scheduled && message_id.is_scheduled_server();
+ auto &stmt = is_scheduled
+ ? (is_scheduled_server ? delete_scheduled_server_message_stmt_ : delete_scheduled_message_stmt_)
+ : delete_message_stmt_;
+ SCOPE_EXIT {
+ stmt.reset();
+ };
+ stmt.bind_int64(1, dialog_id.get()).ensure();
+ if (is_scheduled_server) {
+ stmt.bind_int32(2, message_id.get_scheduled_server_message_id().get()).ensure();
+ } else {
+ stmt.bind_int64(2, message_id.get()).ensure();
+ }
+ stmt.step().ensure();
+ }
+
+ void delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id) final {
+ LOG(INFO) << "Delete all messages in " << dialog_id << " up to " << from_message_id << " from database";
+ CHECK(dialog_id.is_valid());
+ CHECK(from_message_id.is_valid());
+ SCOPE_EXIT {
+ delete_all_dialog_messages_stmt_.reset();
+ };
+ delete_all_dialog_messages_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ delete_all_dialog_messages_stmt_.bind_int64(2, from_message_id.get()).ensure();
+ auto status = delete_all_dialog_messages_stmt_.step();
+ if (status.is_error()) {
+ LOG(ERROR) << status;
+ }
+ }
+
+ void delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id) final {
+ LOG(INFO) << "Delete all messages in " << dialog_id << " sent by " << sender_dialog_id << " from database";
+ CHECK(dialog_id.is_valid());
+ CHECK(sender_dialog_id.is_valid());
+ SCOPE_EXIT {
+ delete_dialog_messages_by_sender_stmt_.reset();
+ };
+ delete_dialog_messages_by_sender_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ delete_dialog_messages_by_sender_stmt_.bind_int64(2, sender_dialog_id.get()).ensure();
+ delete_dialog_messages_by_sender_stmt_.step().ensure();
+ }
+
+ Result<MessageDbDialogMessage> get_message(FullMessageId full_message_id) final {
+ auto dialog_id = full_message_id.get_dialog_id();
+ auto message_id = full_message_id.get_message_id();
+ CHECK(dialog_id.is_valid());
+ CHECK(message_id.is_valid() || message_id.is_valid_scheduled());
+ bool is_scheduled = message_id.is_scheduled();
+ bool is_scheduled_server = is_scheduled && message_id.is_scheduled_server();
+ auto &stmt = is_scheduled ? (is_scheduled_server ? get_scheduled_server_message_stmt_ : get_scheduled_message_stmt_)
+ : get_message_stmt_;
+ SCOPE_EXIT {
+ stmt.reset();
+ };
+
+ stmt.bind_int64(1, dialog_id.get()).ensure();
+ if (is_scheduled_server) {
+ stmt.bind_int32(2, message_id.get_scheduled_server_message_id().get()).ensure();
+ } else {
+ stmt.bind_int64(2, message_id.get()).ensure();
+ }
+ stmt.step().ensure();
+ if (!stmt.has_row()) {
+ return Status::Error("Not found");
+ }
+ MessageId received_message_id(stmt.view_int64(0));
+ Slice data = stmt.view_blob(1);
+ if (is_scheduled_server) {
+ CHECK(received_message_id.is_scheduled());
+ CHECK(received_message_id.is_scheduled_server());
+ CHECK(received_message_id.get_scheduled_server_message_id() == message_id.get_scheduled_server_message_id());
+ } else {
+ LOG_CHECK(received_message_id == message_id)
+ << received_message_id << ' ' << message_id << ' ' << get_message_info(received_message_id, data, true).first;
+ }
+ return MessageDbDialogMessage{received_message_id, BufferSlice(data)};
+ }
+
+ Result<MessageDbMessage> get_message_by_unique_message_id(ServerMessageId unique_message_id) final {
+ if (!unique_message_id.is_valid()) {
+ return Status::Error("Invalid unique_message_id");
+ }
+ SCOPE_EXIT {
+ get_message_by_unique_message_id_stmt_.reset();
+ };
+ get_message_by_unique_message_id_stmt_.bind_int32(1, unique_message_id.get()).ensure();
+ get_message_by_unique_message_id_stmt_.step().ensure();
+ if (!get_message_by_unique_message_id_stmt_.has_row()) {
+ return Status::Error("Not found");
+ }
+ DialogId dialog_id(get_message_by_unique_message_id_stmt_.view_int64(0));
+ MessageId message_id(get_message_by_unique_message_id_stmt_.view_int64(1));
+ return MessageDbMessage{dialog_id, message_id, BufferSlice(get_message_by_unique_message_id_stmt_.view_blob(2))};
+ }
+
+ Result<MessageDbDialogMessage> get_message_by_random_id(DialogId dialog_id, int64 random_id) final {
+ SCOPE_EXIT {
+ get_message_by_random_id_stmt_.reset();
+ };
+ get_message_by_random_id_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ get_message_by_random_id_stmt_.bind_int64(2, random_id).ensure();
+ get_message_by_random_id_stmt_.step().ensure();
+ if (!get_message_by_random_id_stmt_.has_row()) {
+ return Status::Error("Not found");
+ }
+ MessageId message_id(get_message_by_random_id_stmt_.view_int64(0));
+ return MessageDbDialogMessage{message_id, BufferSlice(get_message_by_random_id_stmt_.view_blob(1))};
+ }
+
+ Result<MessageDbDialogMessage> get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id,
+ MessageId last_message_id, int32 date) final {
+ int64 left_message_id = first_message_id.get();
+ int64 right_message_id = last_message_id.get();
+ LOG_CHECK(left_message_id <= right_message_id) << first_message_id << " " << last_message_id;
+ auto first_messages = get_messages_inner(get_messages_stmt_.asc_stmt_, dialog_id, left_message_id - 1, 1);
+ if (!first_messages.empty()) {
+ MessageId real_first_message_id;
+ int32 real_first_message_date;
+ std::tie(real_first_message_id, real_first_message_date) = get_message_info(first_messages[0]);
+ if (real_first_message_date <= date) {
+ // we definitely have at least one suitable message, let's do a binary search
+ left_message_id = real_first_message_id.get();
+
+ MessageId prev_found_message_id;
+ while (left_message_id <= right_message_id) {
+ auto middle_message_id = left_message_id + ((right_message_id - left_message_id) >> 1);
+ auto messages = get_messages_inner(get_messages_stmt_.asc_stmt_, dialog_id, middle_message_id, 1);
+
+ MessageId message_id;
+ int32 message_date = std::numeric_limits<int32>::max();
+ if (!messages.empty()) {
+ std::tie(message_id, message_date) = get_message_info(messages[0]);
+ }
+ if (message_date <= date) {
+ left_message_id = message_id.get();
+ } else {
+ right_message_id = middle_message_id - 1;
+ }
+
+ if (prev_found_message_id == message_id) {
+ // we may be very close to the result, let's check
+ auto left_messages = get_messages_inner(get_messages_stmt_.asc_stmt_, dialog_id, left_message_id - 1, 2);
+ CHECK(!left_messages.empty());
+ if (left_messages.size() == 1) {
+ // only one message has left, result is found
+ break;
+ }
+
+ MessageId next_message_id;
+ int32 next_message_date;
+ std::tie(next_message_id, next_message_date) = get_message_info(left_messages[1]);
+ if (next_message_date <= date) {
+ // next message has lesser date, adjusting left message
+ left_message_id = next_message_id.get();
+ } else {
+ // next message has bigger date, result is found
+ break;
+ }
+ }
+
+ prev_found_message_id = message_id;
+ }
+
+ // left_message_id is always an id of suitable message, let's return it
+ return get_message({dialog_id, MessageId(left_message_id)});
+ }
+ }
+
+ return Status::Error("Not found");
+ }
+
+ std::pair<vector<MessageDbMessage>, int32> get_expiring_messages(int32 expires_from, int32 expires_till,
+ int32 limit) final {
+ SCOPE_EXIT {
+ get_expiring_messages_stmt_.reset();
+ get_expiring_messages_helper_stmt_.reset();
+ };
+
+ vector<MessageDbMessage> messages;
+ // load messages
+ if (expires_from <= expires_till) {
+ get_expiring_messages_stmt_.bind_int32(1, expires_from).ensure();
+ get_expiring_messages_stmt_.bind_int32(2, expires_till).ensure();
+ get_expiring_messages_stmt_.step().ensure();
+
+ while (get_expiring_messages_stmt_.has_row()) {
+ DialogId dialog_id(get_expiring_messages_stmt_.view_int64(0));
+ MessageId message_id(get_expiring_messages_stmt_.view_int64(1));
+ BufferSlice data(get_expiring_messages_stmt_.view_blob(2));
+ messages.push_back(MessageDbMessage{dialog_id, message_id, std::move(data)});
+ get_expiring_messages_stmt_.step().ensure();
+ }
+ }
+
+ // calc next expires_till
+ get_expiring_messages_helper_stmt_.bind_int32(1, expires_till).ensure();
+ get_expiring_messages_helper_stmt_.bind_int32(2, limit).ensure();
+ get_expiring_messages_helper_stmt_.step().ensure();
+ CHECK(get_expiring_messages_helper_stmt_.has_row());
+ int32 count = get_expiring_messages_helper_stmt_.view_int32(1);
+ int32 next_expires_till = -1;
+ if (count != 0) {
+ next_expires_till = get_expiring_messages_helper_stmt_.view_int32(0);
+ }
+ return std::make_pair(std::move(messages), next_expires_till);
+ }
+
+ MessageDbCalendar get_dialog_message_calendar(MessageDbDialogCalendarQuery query) final {
+ auto &stmt = get_messages_from_index_stmts_[message_search_filter_index(query.filter)].desc_stmt_;
+ SCOPE_EXIT {
+ stmt.reset();
+ };
+ int32 limit = 1000;
+ stmt.bind_int64(1, query.dialog_id.get()).ensure();
+ stmt.bind_int64(2, query.from_message_id.get()).ensure();
+ stmt.bind_int32(3, limit).ensure();
+
+ vector<MessageDbDialogMessage> messages;
+ vector<int32> total_counts;
+ stmt.step().ensure();
+ int32 current_day = std::numeric_limits<int32>::max();
+ while (stmt.has_row()) {
+ auto data_slice = stmt.view_blob(0);
+ MessageId message_id(stmt.view_int64(1));
+ auto info = get_message_info(message_id, data_slice, false);
+ auto day = (query.tz_offset + info.second) / 86400;
+ if (day >= current_day) {
+ CHECK(!total_counts.empty());
+ total_counts.back()++;
+ } else {
+ current_day = day;
+ messages.push_back(MessageDbDialogMessage{message_id, BufferSlice(data_slice)});
+ total_counts.push_back(1);
+ }
+ stmt.step().ensure();
+ }
+ return MessageDbCalendar{std::move(messages), std::move(total_counts)};
+ }
+
+ Result<MessageDbMessagePositions> get_dialog_sparse_message_positions(
+ MessageDbGetDialogSparseMessagePositionsQuery query) final {
+ auto &stmt = get_message_ids_stmts_[message_search_filter_index(query.filter)];
+ SCOPE_EXIT {
+ stmt.reset();
+ };
+ stmt.bind_int64(1, query.dialog_id.get()).ensure();
+ stmt.bind_int64(2, query.from_message_id.get()).ensure();
+
+ vector<MessageId> message_ids;
+ stmt.step().ensure();
+ while (stmt.has_row()) {
+ message_ids.push_back(MessageId(stmt.view_int64(0)));
+ stmt.step().ensure();
+ }
+
+ MessageDbMessagePositions positions;
+ int32 limit = min(query.limit, static_cast<int32>(message_ids.size()));
+ if (limit > 0) {
+ double delta = static_cast<double>(message_ids.size()) / limit;
+ positions.total_count = static_cast<int32>(message_ids.size());
+ positions.positions.reserve(limit);
+ for (int32 i = 0; i < limit; i++) {
+ auto position = static_cast<int32>((i + 0.5) * delta);
+ auto message_id = message_ids[position];
+ TRY_RESULT(message, get_message({query.dialog_id, message_id}));
+ auto date = get_message_info(message).second;
+ positions.positions.push_back(MessageDbMessagePosition{position, date, message_id});
+ }
+ }
+ return positions;
+ }
+
+ vector<MessageDbDialogMessage> get_messages(MessageDbMessagesQuery query) final {
+ if (query.filter != MessageSearchFilter::Empty) {
+ return get_messages_from_index(query.dialog_id, query.from_message_id, query.filter, query.offset, query.limit);
+ }
+ return get_messages_impl(get_messages_stmt_, query.dialog_id, query.from_message_id, query.offset, query.limit);
+ }
+
+ vector<MessageDbDialogMessage> get_scheduled_messages(DialogId dialog_id, int32 limit) final {
+ return get_messages_inner(get_scheduled_messages_stmt_, dialog_id, std::numeric_limits<int64>::max(), limit);
+ }
+
+ vector<MessageDbDialogMessage> get_messages_from_notification_id(DialogId dialog_id,
+ NotificationId from_notification_id,
+ int32 limit) final {
+ auto &stmt = get_messages_from_notification_id_stmt_;
+ SCOPE_EXIT {
+ stmt.reset();
+ };
+ stmt.bind_int64(1, dialog_id.get()).ensure();
+ stmt.bind_int32(2, from_notification_id.get()).ensure();
+ stmt.bind_int32(3, limit).ensure();
+
+ vector<MessageDbDialogMessage> result;
+ stmt.step().ensure();
+ while (stmt.has_row()) {
+ auto data_slice = stmt.view_blob(0);
+ MessageId message_id(stmt.view_int64(1));
+ result.push_back(MessageDbDialogMessage{message_id, BufferSlice(data_slice)});
+ LOG(INFO) << "Load " << message_id << " in " << dialog_id << " from database";
+ stmt.step().ensure();
+ }
+ return result;
+ }
+
+ static string prepare_query(Slice query) {
+ auto is_word_character = [](uint32 a) {
+ switch (get_unicode_simple_category(a)) {
+ case UnicodeSimpleCategory::Letter:
+ case UnicodeSimpleCategory::DecimalNumber:
+ case UnicodeSimpleCategory::Number:
+ return true;
+ default:
+ return a == '_';
+ }
+ };
+
+ const size_t MAX_QUERY_SIZE = 1024;
+ query = utf8_truncate(query, MAX_QUERY_SIZE);
+ auto buf = StackAllocator::alloc(query.size() * 4 + 100);
+ StringBuilder sb(buf.as_slice());
+ bool in_word{false};
+
+ for (auto ptr = query.ubegin(), end = query.uend(); ptr < end;) {
+ uint32 code;
+ auto code_ptr = ptr;
+ ptr = next_utf8_unsafe(ptr, &code);
+ if (is_word_character(code)) {
+ if (!in_word) {
+ in_word = true;
+ sb << "\"";
+ }
+ sb << Slice(code_ptr, ptr);
+ } else {
+ if (in_word) {
+ in_word = false;
+ sb << "\" ";
+ }
+ }
+ }
+ if (in_word) {
+ sb << "\" ";
+ }
+
+ if (sb.is_error()) {
+ LOG(ERROR) << "StringBuilder buffer overflow";
+ return "";
+ }
+
+ return sb.as_cslice().str();
+ }
+
+ MessageDbFtsResult get_messages_fts(MessageDbFtsQuery query) final {
+ SCOPE_EXIT {
+ get_messages_fts_stmt_.reset();
+ };
+
+ LOG(INFO) << tag("query", query.query) << query.dialog_id << tag("filter", query.filter)
+ << tag("from_search_id", query.from_search_id) << tag("limit", query.limit);
+ string words = prepare_query(query.query);
+ LOG(INFO) << tag("from", query.query) << tag("to", words);
+
+ // dialog_id kludge
+ if (query.dialog_id.is_valid()) {
+ words += PSTRING() << " \"\a" << query.dialog_id.get() << "\"";
+ }
+
+ // index_mask kludge
+ if (query.filter != MessageSearchFilter::Empty) {
+ words += PSTRING() << " \"\a\a" << message_search_filter_index(query.filter) << "\"";
+ }
+
+ auto &stmt = get_messages_fts_stmt_;
+ stmt.bind_string(1, words).ensure();
+ if (query.from_search_id == 0) {
+ query.from_search_id = std::numeric_limits<int64>::max();
+ }
+ stmt.bind_int64(2, query.from_search_id).ensure();
+ stmt.bind_int32(3, query.limit).ensure();
+ MessageDbFtsResult result;
+ auto status = stmt.step();
+ if (status.is_error()) {
+ LOG(ERROR) << status;
+ return result;
+ }
+ while (stmt.has_row()) {
+ DialogId dialog_id(stmt.view_int64(0));
+ MessageId message_id(stmt.view_int64(1));
+ auto data_slice = stmt.view_blob(2);
+ auto search_id = stmt.view_int64(3);
+ result.next_search_id = search_id;
+ result.messages.push_back(MessageDbMessage{dialog_id, message_id, BufferSlice(data_slice)});
+ stmt.step().ensure();
+ }
+ return result;
+ }
+
+ vector<MessageDbDialogMessage> get_messages_from_index(DialogId dialog_id, MessageId from_message_id,
+ MessageSearchFilter filter, int32 offset, int32 limit) {
+ auto &stmt = get_messages_from_index_stmts_[message_search_filter_index(filter)];
+ return get_messages_impl(stmt, dialog_id, from_message_id, offset, limit);
+ }
+
+ MessageDbCallsResult get_calls(MessageDbCallsQuery query) final {
+ int32 pos;
+ if (query.filter == MessageSearchFilter::Call) {
+ pos = 0;
+ } else if (query.filter == MessageSearchFilter::MissedCall) {
+ pos = 1;
+ } else {
+ UNREACHABLE();
+ }
+
+ auto &stmt = get_calls_stmts_[pos];
+ SCOPE_EXIT {
+ stmt.reset();
+ };
+
+ stmt.bind_int32(1, query.from_unique_message_id).ensure();
+ stmt.bind_int32(2, query.limit).ensure();
+
+ MessageDbCallsResult result;
+ stmt.step().ensure();
+ while (stmt.has_row()) {
+ DialogId dialog_id(stmt.view_int64(0));
+ MessageId message_id(stmt.view_int64(1));
+ auto data_slice = stmt.view_blob(2);
+ result.messages.push_back(MessageDbMessage{dialog_id, message_id, BufferSlice(data_slice)});
+ stmt.step().ensure();
+ }
+ return result;
+ }
+
+ Status begin_write_transaction() final {
+ return db_.begin_write_transaction();
+ }
+ Status commit_transaction() final {
+ return db_.commit_transaction();
+ }
+
+ private:
+ SqliteDb db_;
+
+ SqliteStatement add_message_stmt_;
+
+ SqliteStatement delete_message_stmt_;
+ SqliteStatement delete_all_dialog_messages_stmt_;
+ SqliteStatement delete_dialog_messages_by_sender_stmt_;
+
+ SqliteStatement get_message_stmt_;
+ SqliteStatement get_message_by_random_id_stmt_;
+ SqliteStatement get_message_by_unique_message_id_stmt_;
+ SqliteStatement get_expiring_messages_stmt_;
+ SqliteStatement get_expiring_messages_helper_stmt_;
+
+ struct GetMessagesStmt {
+ SqliteStatement asc_stmt_;
+ SqliteStatement desc_stmt_;
+ };
+ GetMessagesStmt get_messages_stmt_;
+ SqliteStatement get_scheduled_messages_stmt_;
+ SqliteStatement get_messages_from_notification_id_stmt_;
+
+ std::array<SqliteStatement, MESSAGE_DB_INDEX_COUNT> get_message_ids_stmts_;
+ std::array<GetMessagesStmt, MESSAGE_DB_INDEX_COUNT> get_messages_from_index_stmts_;
+ std::array<SqliteStatement, 2> get_calls_stmts_;
+
+ SqliteStatement get_messages_fts_stmt_;
+
+ SqliteStatement add_scheduled_message_stmt_;
+ SqliteStatement get_scheduled_message_stmt_;
+ SqliteStatement get_scheduled_server_message_stmt_;
+ SqliteStatement delete_scheduled_message_stmt_;
+ SqliteStatement delete_scheduled_server_message_stmt_;
+
+ static vector<MessageDbDialogMessage> get_messages_impl(GetMessagesStmt &stmt, DialogId dialog_id,
+ MessageId from_message_id, int32 offset, int32 limit) {
+ LOG_CHECK(dialog_id.is_valid()) << dialog_id;
+ CHECK(from_message_id.is_valid());
+
+ LOG(INFO) << "Loading messages in " << dialog_id << " from " << from_message_id << " with offset = " << offset
+ << " and limit = " << limit;
+
+ auto message_id = from_message_id.get();
+
+ if (message_id >= MessageId::max().get()) {
+ message_id--;
+ }
+
+ auto left_message_id = message_id;
+ auto left_cnt = limit + offset;
+
+ auto right_message_id = message_id - 1;
+ auto right_cnt = -offset;
+
+ vector<MessageDbDialogMessage> left;
+ vector<MessageDbDialogMessage> right;
+
+ if (left_cnt != 0) {
+ if (right_cnt == 1 && false) {
+ left_message_id++;
+ left_cnt++;
+ }
+
+ left = get_messages_inner(stmt.desc_stmt_, dialog_id, left_message_id, left_cnt);
+
+ if (right_cnt == 1 && !left.empty() && false /*get_message_id(left[0].as_slice()) == message_id*/) {
+ right_cnt = 0;
+ }
+ }
+ if (right_cnt != 0) {
+ right = get_messages_inner(stmt.asc_stmt_, dialog_id, right_message_id, right_cnt);
+ std::reverse(right.begin(), right.end());
+ }
+ if (left.empty()) {
+ return right;
+ }
+ if (right.empty()) {
+ return left;
+ }
+
+ right.reserve(right.size() + left.size());
+ std::move(left.begin(), left.end(), std::back_inserter(right));
+
+ return right;
+ }
+
+ static vector<MessageDbDialogMessage> get_messages_inner(SqliteStatement &stmt, DialogId dialog_id,
+ int64 from_message_id, int32 limit) {
+ SCOPE_EXIT {
+ stmt.reset();
+ };
+ stmt.bind_int64(1, dialog_id.get()).ensure();
+ stmt.bind_int64(2, from_message_id).ensure();
+ stmt.bind_int32(3, limit).ensure();
+
+ LOG(INFO) << "Begin to load " << limit << " messages in " << dialog_id << " from " << MessageId(from_message_id)
+ << " from database";
+ vector<MessageDbDialogMessage> result;
+ stmt.step().ensure();
+ while (stmt.has_row()) {
+ auto data_slice = stmt.view_blob(0);
+ MessageId message_id(stmt.view_int64(1));
+ result.push_back(MessageDbDialogMessage{message_id, BufferSlice(data_slice)});
+ LOG(INFO) << "Loaded " << message_id << " in " << dialog_id << " from database";
+ stmt.step().ensure();
+ }
+ return result;
+ }
+
+ static std::pair<MessageId, int32> get_message_info(const MessageDbDialogMessage &message, bool from_data = false) {
+ return get_message_info(message.message_id, message.data.as_slice(), from_data);
+ }
+
+ static std::pair<MessageId, int32> get_message_info(MessageId message_id, Slice data, bool from_data) {
+ LogEventParser message_date_parser(data);
+ int32 flags;
+ int32 flags2 = 0;
+ int32 flags3 = 0;
+ td::parse(flags, message_date_parser);
+ if ((flags & (1 << 29)) != 0) {
+ td::parse(flags2, message_date_parser);
+ if ((flags2 & (1 << 29)) != 0) {
+ td::parse(flags3, message_date_parser);
+ }
+ }
+ bool has_sender = (flags & (1 << 10)) != 0;
+ MessageId data_message_id;
+ td::parse(data_message_id, message_date_parser);
+ UserId sender_user_id;
+ if (has_sender) {
+ td::parse(sender_user_id, message_date_parser);
+ }
+ int32 date;
+ td::parse(date, message_date_parser);
+ LOG(INFO) << "Loaded " << message_id << "(aka " << data_message_id << ") sent at " << date << " by "
+ << sender_user_id;
+ return {from_data ? data_message_id : message_id, date};
+ }
+};
+
+std::shared_ptr<MessageDbSyncSafeInterface> create_message_db_sync(
+ std::shared_ptr<SqliteConnectionSafe> sqlite_connection) {
+ class MessageDbSyncSafe final : public MessageDbSyncSafeInterface {
+ public:
+ explicit MessageDbSyncSafe(std::shared_ptr<SqliteConnectionSafe> sqlite_connection)
+ : lsls_db_([safe_connection = std::move(sqlite_connection)] {
+ return make_unique<MessageDbImpl>(safe_connection->get().clone());
+ }) {
+ }
+ MessageDbSyncInterface &get() final {
+ return *lsls_db_.get();
+ }
+
+ private:
+ LazySchedulerLocalStorage<unique_ptr<MessageDbSyncInterface>> lsls_db_;
+ };
+ return std::make_shared<MessageDbSyncSafe>(std::move(sqlite_connection));
+}
+
+class MessageDbAsync final : public MessageDbAsyncInterface {
+ public:
+ MessageDbAsync(std::shared_ptr<MessageDbSyncSafeInterface> sync_db, int32 scheduler_id) {
+ impl_ = create_actor_on_scheduler<Impl>("MessageDbActor", scheduler_id, std::move(sync_db));
+ }
+
+ void add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, DialogId sender_dialog_id,
+ int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
+ NotificationId notification_id, MessageId top_thread_message_id, BufferSlice data,
+ Promise<> promise) final {
+ send_closure_later(impl_, &Impl::add_message, full_message_id, unique_message_id, sender_dialog_id, random_id,
+ ttl_expires_at, index_mask, search_id, std::move(text), notification_id, top_thread_message_id,
+ std::move(data), std::move(promise));
+ }
+ void add_scheduled_message(FullMessageId full_message_id, BufferSlice data, Promise<> promise) final {
+ send_closure_later(impl_, &Impl::add_scheduled_message, full_message_id, std::move(data), std::move(promise));
+ }
+
+ void delete_message(FullMessageId full_message_id, Promise<> promise) final {
+ send_closure_later(impl_, &Impl::delete_message, full_message_id, std::move(promise));
+ }
+ void delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id, Promise<> promise) final {
+ send_closure_later(impl_, &Impl::delete_all_dialog_messages, dialog_id, from_message_id, std::move(promise));
+ }
+ void delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id, Promise<> promise) final {
+ send_closure_later(impl_, &Impl::delete_dialog_messages_by_sender, dialog_id, sender_dialog_id, std::move(promise));
+ }
+
+ void get_message(FullMessageId full_message_id, Promise<MessageDbDialogMessage> promise) final {
+ send_closure_later(impl_, &Impl::get_message, full_message_id, std::move(promise));
+ }
+ void get_message_by_unique_message_id(ServerMessageId unique_message_id, Promise<MessageDbMessage> promise) final {
+ send_closure_later(impl_, &Impl::get_message_by_unique_message_id, unique_message_id, std::move(promise));
+ }
+ void get_message_by_random_id(DialogId dialog_id, int64 random_id, Promise<MessageDbDialogMessage> promise) final {
+ send_closure_later(impl_, &Impl::get_message_by_random_id, dialog_id, random_id, std::move(promise));
+ }
+ void get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id, MessageId last_message_id, int32 date,
+ Promise<MessageDbDialogMessage> promise) final {
+ send_closure_later(impl_, &Impl::get_dialog_message_by_date, dialog_id, first_message_id, last_message_id, date,
+ std::move(promise));
+ }
+
+ void get_dialog_message_calendar(MessageDbDialogCalendarQuery query, Promise<MessageDbCalendar> promise) final {
+ send_closure_later(impl_, &Impl::get_dialog_message_calendar, std::move(query), std::move(promise));
+ }
+
+ void get_dialog_sparse_message_positions(MessageDbGetDialogSparseMessagePositionsQuery query,
+ Promise<MessageDbMessagePositions> promise) final {
+ send_closure_later(impl_, &Impl::get_dialog_sparse_message_positions, std::move(query), std::move(promise));
+ }
+
+ void get_messages(MessageDbMessagesQuery query, Promise<vector<MessageDbDialogMessage>> promise) final {
+ send_closure_later(impl_, &Impl::get_messages, std::move(query), std::move(promise));
+ }
+ void get_scheduled_messages(DialogId dialog_id, int32 limit, Promise<vector<MessageDbDialogMessage>> promise) final {
+ send_closure_later(impl_, &Impl::get_scheduled_messages, dialog_id, limit, std::move(promise));
+ }
+ void get_messages_from_notification_id(DialogId dialog_id, NotificationId from_notification_id, int32 limit,
+ Promise<vector<MessageDbDialogMessage>> promise) final {
+ send_closure_later(impl_, &Impl::get_messages_from_notification_id, dialog_id, from_notification_id, limit,
+ std::move(promise));
+ }
+ void get_calls(MessageDbCallsQuery query, Promise<MessageDbCallsResult> promise) final {
+ send_closure_later(impl_, &Impl::get_calls, std::move(query), std::move(promise));
+ }
+ void get_messages_fts(MessageDbFtsQuery query, Promise<MessageDbFtsResult> promise) final {
+ send_closure_later(impl_, &Impl::get_messages_fts, std::move(query), std::move(promise));
+ }
+ void get_expiring_messages(int32 expires_from, int32 expires_till, int32 limit,
+ Promise<std::pair<vector<MessageDbMessage>, int32>> promise) final {
+ send_closure_later(impl_, &Impl::get_expiring_messages, expires_from, expires_till, limit, std::move(promise));
+ }
+
+ void close(Promise<> promise) final {
+ send_closure_later(impl_, &Impl::close, std::move(promise));
+ }
+
+ void force_flush() final {
+ send_closure_later(impl_, &Impl::force_flush);
+ }
+
+ private:
+ class Impl final : public Actor {
+ public:
+ explicit Impl(std::shared_ptr<MessageDbSyncSafeInterface> sync_db_safe) : sync_db_safe_(std::move(sync_db_safe)) {
+ }
+ void add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, DialogId sender_dialog_id,
+ int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
+ NotificationId notification_id, MessageId top_thread_message_id, BufferSlice data,
+ Promise<> promise) {
+ add_write_query([this, full_message_id, unique_message_id, sender_dialog_id, random_id, ttl_expires_at,
+ index_mask, search_id, text = std::move(text), notification_id, top_thread_message_id,
+ data = std::move(data), promise = std::move(promise)](Unit) mutable {
+ sync_db_->add_message(full_message_id, unique_message_id, sender_dialog_id, random_id, ttl_expires_at,
+ index_mask, search_id, std::move(text), notification_id, top_thread_message_id,
+ std::move(data));
+ on_write_result(std::move(promise));
+ });
+ }
+ void add_scheduled_message(FullMessageId full_message_id, BufferSlice data, Promise<> promise) {
+ add_write_query([this, full_message_id, promise = std::move(promise), data = std::move(data)](Unit) mutable {
+ sync_db_->add_scheduled_message(full_message_id, std::move(data));
+ on_write_result(std::move(promise));
+ });
+ }
+
+ void delete_message(FullMessageId full_message_id, Promise<> promise) {
+ add_write_query([this, full_message_id, promise = std::move(promise)](Unit) mutable {
+ sync_db_->delete_message(full_message_id);
+ on_write_result(std::move(promise));
+ });
+ }
+
+ void on_write_result(Promise<Unit> &&promise) {
+ // We are inside a transaction and don't know how to handle errors
+ finished_writes_.push_back(std::move(promise));
+ }
+
+ void delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id, Promise<> promise) {
+ add_read_query();
+ sync_db_->delete_all_dialog_messages(dialog_id, from_message_id);
+ promise.set_value(Unit());
+ }
+
+ void delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id, Promise<> promise) {
+ add_read_query();
+ sync_db_->delete_dialog_messages_by_sender(dialog_id, sender_dialog_id);
+ promise.set_value(Unit());
+ }
+
+ void get_message(FullMessageId full_message_id, Promise<MessageDbDialogMessage> promise) {
+ add_read_query();
+ promise.set_result(sync_db_->get_message(full_message_id));
+ }
+ void get_message_by_unique_message_id(ServerMessageId unique_message_id, Promise<MessageDbMessage> promise) {
+ add_read_query();
+ promise.set_result(sync_db_->get_message_by_unique_message_id(unique_message_id));
+ }
+ void get_message_by_random_id(DialogId dialog_id, int64 random_id, Promise<MessageDbDialogMessage> promise) {
+ add_read_query();
+ promise.set_result(sync_db_->get_message_by_random_id(dialog_id, random_id));
+ }
+ void get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id, MessageId last_message_id,
+ int32 date, Promise<MessageDbDialogMessage> promise) {
+ add_read_query();
+ promise.set_result(sync_db_->get_dialog_message_by_date(dialog_id, first_message_id, last_message_id, date));
+ }
+
+ void get_dialog_message_calendar(MessageDbDialogCalendarQuery query, Promise<MessageDbCalendar> promise) {
+ add_read_query();
+ promise.set_value(sync_db_->get_dialog_message_calendar(std::move(query)));
+ }
+
+ void get_dialog_sparse_message_positions(MessageDbGetDialogSparseMessagePositionsQuery query,
+ Promise<MessageDbMessagePositions> promise) {
+ add_read_query();
+ promise.set_result(sync_db_->get_dialog_sparse_message_positions(std::move(query)));
+ }
+
+ void get_messages(MessageDbMessagesQuery query, Promise<vector<MessageDbDialogMessage>> promise) {
+ add_read_query();
+ promise.set_value(sync_db_->get_messages(std::move(query)));
+ }
+ void get_scheduled_messages(DialogId dialog_id, int32 limit, Promise<vector<MessageDbDialogMessage>> promise) {
+ add_read_query();
+ promise.set_value(sync_db_->get_scheduled_messages(dialog_id, limit));
+ }
+ void get_messages_from_notification_id(DialogId dialog_id, NotificationId from_notification_id, int32 limit,
+ Promise<vector<MessageDbDialogMessage>> promise) {
+ add_read_query();
+ promise.set_value(sync_db_->get_messages_from_notification_id(dialog_id, from_notification_id, limit));
+ }
+ void get_calls(MessageDbCallsQuery query, Promise<MessageDbCallsResult> promise) {
+ add_read_query();
+ promise.set_value(sync_db_->get_calls(std::move(query)));
+ }
+ void get_messages_fts(MessageDbFtsQuery query, Promise<MessageDbFtsResult> promise) {
+ add_read_query();
+ promise.set_value(sync_db_->get_messages_fts(std::move(query)));
+ }
+ void get_expiring_messages(int32 expires_from, int32 expires_till, int32 limit,
+ Promise<std::pair<vector<MessageDbMessage>, int32>> promise) {
+ add_read_query();
+ promise.set_value(sync_db_->get_expiring_messages(expires_from, expires_till, limit));
+ }
+
+ void close(Promise<> promise) {
+ do_flush();
+ sync_db_safe_.reset();
+ sync_db_ = nullptr;
+ promise.set_value(Unit());
+ stop();
+ }
+
+ void force_flush() {
+ do_flush();
+ LOG(INFO) << "MessageDb flushed";
+ }
+
+ private:
+ std::shared_ptr<MessageDbSyncSafeInterface> sync_db_safe_;
+ MessageDbSyncInterface *sync_db_ = nullptr;
+
+ static constexpr size_t MAX_PENDING_QUERIES_COUNT{50};
+ static constexpr double MAX_PENDING_QUERIES_DELAY{0.01};
+
+ //NB: order is important, destructor of pending_writes_ will change finished_writes_
+ vector<Promise<Unit>> finished_writes_;
+ vector<Promise<Unit>> pending_writes_; // TODO use Action
+ double wakeup_at_ = 0;
+
+ template <class F>
+ void add_write_query(F &&f) {
+ pending_writes_.push_back(PromiseCreator::lambda(std::forward<F>(f)));
+ if (pending_writes_.size() > MAX_PENDING_QUERIES_COUNT) {
+ do_flush();
+ wakeup_at_ = 0;
+ } else if (wakeup_at_ == 0) {
+ wakeup_at_ = Time::now_cached() + MAX_PENDING_QUERIES_DELAY;
+ }
+ if (wakeup_at_ != 0) {
+ set_timeout_at(wakeup_at_);
+ }
+ }
+ void add_read_query() {
+ do_flush();
+ }
+ void do_flush() {
+ if (pending_writes_.empty()) {
+ return;
+ }
+ sync_db_->begin_write_transaction().ensure();
+ set_promises(pending_writes_);
+ sync_db_->commit_transaction().ensure();
+ set_promises(finished_writes_);
+ cancel_timeout();
+ }
+ void timeout_expired() final {
+ do_flush();
+ }
+
+ void start_up() final {
+ sync_db_ = &sync_db_safe_->get();
+ }
+ };
+ ActorOwn<Impl> impl_;
+};
+
+std::shared_ptr<MessageDbAsyncInterface> create_message_db_async(std::shared_ptr<MessageDbSyncSafeInterface> sync_db,
+ int32 scheduler_id) {
+ return std::make_shared<MessageDbAsync>(std::move(sync_db), scheduler_id);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageDb.h b/protocols/Telegram/tdlib/td/td/telegram/MessageDb.h
new file mode 100644
index 0000000000..b94dac56f0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageDb.h
@@ -0,0 +1,207 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/MessageSearchFilter.h"
+#include "td/telegram/NotificationId.h"
+#include "td/telegram/ServerMessageId.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+#include <memory>
+#include <utility>
+
+namespace td {
+
+class SqliteConnectionSafe;
+class SqliteDb;
+
+struct MessageDbMessagesQuery {
+ DialogId dialog_id;
+ MessageSearchFilter filter{MessageSearchFilter::Empty};
+ MessageId from_message_id;
+ int32 offset{0};
+ int32 limit{100};
+};
+
+struct MessageDbDialogMessage {
+ MessageId message_id;
+ BufferSlice data;
+};
+
+struct MessageDbMessage {
+ DialogId dialog_id;
+ MessageId message_id;
+ BufferSlice data;
+};
+
+struct MessageDbDialogCalendarQuery {
+ DialogId dialog_id;
+ MessageSearchFilter filter{MessageSearchFilter::Empty};
+ MessageId from_message_id;
+ int32 tz_offset{0};
+};
+
+struct MessageDbCalendar {
+ vector<MessageDbDialogMessage> messages;
+ vector<int32> total_counts;
+};
+
+struct MessageDbGetDialogSparseMessagePositionsQuery {
+ DialogId dialog_id;
+ MessageSearchFilter filter{MessageSearchFilter::Empty};
+ MessageId from_message_id;
+ int32 limit{0};
+};
+
+struct MessageDbMessagePosition {
+ int32 position;
+ int32 date;
+ MessageId message_id;
+};
+
+struct MessageDbMessagePositions {
+ int32 total_count{0};
+ vector<MessageDbMessagePosition> positions;
+};
+
+struct MessageDbFtsQuery {
+ string query;
+ DialogId dialog_id;
+ MessageSearchFilter filter{MessageSearchFilter::Empty};
+ int64 from_search_id{0};
+ int32 limit{100};
+};
+struct MessageDbFtsResult {
+ vector<MessageDbMessage> messages;
+ int64 next_search_id{1};
+};
+
+struct MessageDbCallsQuery {
+ MessageSearchFilter filter{MessageSearchFilter::Empty};
+ int32 from_unique_message_id{0};
+ int32 limit{100};
+};
+
+struct MessageDbCallsResult {
+ vector<MessageDbMessage> messages;
+};
+
+class MessageDbSyncInterface {
+ public:
+ MessageDbSyncInterface() = default;
+ MessageDbSyncInterface(const MessageDbSyncInterface &) = delete;
+ MessageDbSyncInterface &operator=(const MessageDbSyncInterface &) = delete;
+ virtual ~MessageDbSyncInterface() = default;
+
+ virtual void add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, DialogId sender_dialog_id,
+ int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
+ NotificationId notification_id, MessageId top_thread_message_id, BufferSlice data) = 0;
+ virtual void add_scheduled_message(FullMessageId full_message_id, BufferSlice data) = 0;
+
+ virtual void delete_message(FullMessageId full_message_id) = 0;
+ virtual void delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id) = 0;
+ virtual void delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id) = 0;
+
+ virtual Result<MessageDbDialogMessage> get_message(FullMessageId full_message_id) = 0;
+ virtual Result<MessageDbMessage> get_message_by_unique_message_id(ServerMessageId unique_message_id) = 0;
+ virtual Result<MessageDbDialogMessage> get_message_by_random_id(DialogId dialog_id, int64 random_id) = 0;
+ virtual Result<MessageDbDialogMessage> get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id,
+ MessageId last_message_id, int32 date) = 0;
+
+ virtual MessageDbCalendar get_dialog_message_calendar(MessageDbDialogCalendarQuery query) = 0;
+
+ virtual Result<MessageDbMessagePositions> get_dialog_sparse_message_positions(
+ MessageDbGetDialogSparseMessagePositionsQuery query) = 0;
+
+ virtual vector<MessageDbDialogMessage> get_messages(MessageDbMessagesQuery query) = 0;
+ virtual vector<MessageDbDialogMessage> get_scheduled_messages(DialogId dialog_id, int32 limit) = 0;
+ virtual vector<MessageDbDialogMessage> get_messages_from_notification_id(DialogId dialog_id,
+ NotificationId from_notification_id,
+ int32 limit) = 0;
+
+ virtual std::pair<vector<MessageDbMessage>, int32> get_expiring_messages(int32 expires_from, int32 expires_till,
+ int32 limit) = 0;
+ virtual MessageDbCallsResult get_calls(MessageDbCallsQuery query) = 0;
+ virtual MessageDbFtsResult get_messages_fts(MessageDbFtsQuery query) = 0;
+
+ virtual Status begin_write_transaction() = 0;
+ virtual Status commit_transaction() = 0;
+};
+
+class MessageDbSyncSafeInterface {
+ public:
+ MessageDbSyncSafeInterface() = default;
+ MessageDbSyncSafeInterface(const MessageDbSyncSafeInterface &) = delete;
+ MessageDbSyncSafeInterface &operator=(const MessageDbSyncSafeInterface &) = delete;
+ virtual ~MessageDbSyncSafeInterface() = default;
+
+ virtual MessageDbSyncInterface &get() = 0;
+};
+
+class MessageDbAsyncInterface {
+ public:
+ MessageDbAsyncInterface() = default;
+ MessageDbAsyncInterface(const MessageDbAsyncInterface &) = delete;
+ MessageDbAsyncInterface &operator=(const MessageDbAsyncInterface &) = delete;
+ virtual ~MessageDbAsyncInterface() = default;
+
+ virtual void add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, DialogId sender_dialog_id,
+ int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
+ NotificationId notification_id, MessageId top_thread_message_id, BufferSlice data,
+ Promise<> promise) = 0;
+ virtual void add_scheduled_message(FullMessageId full_message_id, BufferSlice data, Promise<> promise) = 0;
+
+ virtual void delete_message(FullMessageId full_message_id, Promise<> promise) = 0;
+ virtual void delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id, Promise<> promise) = 0;
+ virtual void delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id, Promise<> promise) = 0;
+
+ virtual void get_message(FullMessageId full_message_id, Promise<MessageDbDialogMessage> promise) = 0;
+ virtual void get_message_by_unique_message_id(ServerMessageId unique_message_id,
+ Promise<MessageDbMessage> promise) = 0;
+ virtual void get_message_by_random_id(DialogId dialog_id, int64 random_id,
+ Promise<MessageDbDialogMessage> promise) = 0;
+ virtual void get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id, MessageId last_message_id,
+ int32 date, Promise<MessageDbDialogMessage> promise) = 0;
+
+ virtual void get_dialog_message_calendar(MessageDbDialogCalendarQuery query, Promise<MessageDbCalendar> promise) = 0;
+
+ virtual void get_dialog_sparse_message_positions(MessageDbGetDialogSparseMessagePositionsQuery query,
+ Promise<MessageDbMessagePositions> promise) = 0;
+
+ virtual void get_messages(MessageDbMessagesQuery query, Promise<vector<MessageDbDialogMessage>> promise) = 0;
+ virtual void get_scheduled_messages(DialogId dialog_id, int32 limit,
+ Promise<vector<MessageDbDialogMessage>> promise) = 0;
+ virtual void get_messages_from_notification_id(DialogId dialog_id, NotificationId from_notification_id, int32 limit,
+ Promise<vector<MessageDbDialogMessage>> promise) = 0;
+
+ virtual void get_calls(MessageDbCallsQuery, Promise<MessageDbCallsResult> promise) = 0;
+ virtual void get_messages_fts(MessageDbFtsQuery query, Promise<MessageDbFtsResult> promise) = 0;
+
+ virtual void get_expiring_messages(int32 expires_from, int32 expires_till, int32 limit,
+ Promise<std::pair<vector<MessageDbMessage>, int32>> promise) = 0;
+
+ virtual void close(Promise<> promise) = 0;
+ virtual void force_flush() = 0;
+};
+
+Status init_message_db(SqliteDb &db, int version) TD_WARN_UNUSED_RESULT;
+Status drop_message_db(SqliteDb &db, int version) TD_WARN_UNUSED_RESULT;
+
+std::shared_ptr<MessageDbSyncSafeInterface> create_message_db_sync(
+ std::shared_ptr<SqliteConnectionSafe> sqlite_connection);
+
+std::shared_ptr<MessageDbAsyncInterface> create_message_db_async(std::shared_ptr<MessageDbSyncSafeInterface> sync_db,
+ int32 scheduler_id = -1);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.cpp
index 7af90fb0b0..5bd2deb968 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,82 +7,125 @@
#include "td/telegram/MessageEntity.h"
#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Dependencies.h"
+#include "td/telegram/LinkManager.h"
#include "td/telegram/misc.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/SecretChatLayer.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/Td.h"
-#include "td/utils/HttpUrl.h"
+#include "td/actor/MultiPromise.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/format.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/Promise.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/unicode.h"
#include "td/utils/utf8.h"
#include <algorithm>
#include <cstring>
+#include <limits>
#include <tuple>
#include <unordered_set>
namespace td {
-StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity &message_entity) {
- bool has_argument = false;
- string_builder << '[';
- switch (message_entity.type) {
+int MessageEntity::get_type_priority(Type type) {
+ static const int priorities[] = {50 /*Mention*/,
+ 50 /*Hashtag*/,
+ 50 /*BotCommand*/,
+ 50 /*Url*/,
+ 50 /*EmailAddress*/,
+ 90 /*Bold*/,
+ 91 /*Italic*/,
+ 20 /*Code*/,
+ 11 /*Pre*/,
+ 10 /*PreCode*/,
+ 49 /*TextUrl*/,
+ 49 /*MentionName*/,
+ 50 /*Cashtag*/,
+ 50 /*PhoneNumber*/,
+ 92 /*Underline*/,
+ 93 /*Strikethrough*/,
+ 0 /*BlockQuote*/,
+ 50 /*BankCardNumber*/,
+ 50 /*MediaTimestamp*/,
+ 94 /*Spoiler*/,
+ 99 /*CustomEmoji*/};
+ static_assert(sizeof(priorities) / sizeof(priorities[0]) == static_cast<size_t>(MessageEntity::Type::Size), "");
+ return priorities[static_cast<int32>(type)];
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity::Type &message_entity_type) {
+ switch (message_entity_type) {
case MessageEntity::Type::Mention:
- string_builder << "Mention";
- break;
+ return string_builder << "Mention";
case MessageEntity::Type::Hashtag:
- string_builder << "Hashtag";
- break;
+ return string_builder << "Hashtag";
case MessageEntity::Type::BotCommand:
- string_builder << "BotCommand";
- break;
+ return string_builder << "BotCommand";
case MessageEntity::Type::Url:
- string_builder << "Url";
- break;
+ return string_builder << "Url";
case MessageEntity::Type::EmailAddress:
- string_builder << "EmailAddress";
- break;
+ return string_builder << "EmailAddress";
case MessageEntity::Type::Bold:
- string_builder << "Bold";
- break;
+ return string_builder << "Bold";
case MessageEntity::Type::Italic:
- string_builder << "Italic";
- break;
+ return string_builder << "Italic";
+ case MessageEntity::Type::Underline:
+ return string_builder << "Underline";
+ case MessageEntity::Type::Strikethrough:
+ return string_builder << "Strikethrough";
+ case MessageEntity::Type::BlockQuote:
+ return string_builder << "BlockQuote";
case MessageEntity::Type::Code:
- string_builder << "Code";
- break;
+ return string_builder << "Code";
case MessageEntity::Type::Pre:
- string_builder << "Pre";
- break;
+ return string_builder << "Pre";
case MessageEntity::Type::PreCode:
- string_builder << "PreCode";
- has_argument = true;
- break;
+ return string_builder << "PreCode";
case MessageEntity::Type::TextUrl:
- string_builder << "TextUrl";
- has_argument = true;
- break;
+ return string_builder << "TextUrl";
case MessageEntity::Type::MentionName:
- string_builder << "MentionName";
- break;
+ return string_builder << "MentionName";
case MessageEntity::Type::Cashtag:
- string_builder << "Cashtag";
- break;
+ return string_builder << "Cashtag";
case MessageEntity::Type::PhoneNumber:
- string_builder << "PhoneNumber";
- break;
+ return string_builder << "PhoneNumber";
+ case MessageEntity::Type::BankCardNumber:
+ return string_builder << "BankCardNumber";
+ case MessageEntity::Type::MediaTimestamp:
+ return string_builder << "MediaTimestamp";
+ case MessageEntity::Type::Spoiler:
+ return string_builder << "Spoiler";
+ case MessageEntity::Type::CustomEmoji:
+ return string_builder << "CustomEmoji";
default:
UNREACHABLE();
- string_builder << "Impossible";
- break;
+ return string_builder << "Impossible";
}
+}
- string_builder << ", offset = " << message_entity.offset << ", length = " << message_entity.length;
- if (has_argument) {
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity &message_entity) {
+ string_builder << '[' << message_entity.type << ", offset = " << message_entity.offset
+ << ", length = " << message_entity.length;
+ if (message_entity.media_timestamp >= 0) {
+ string_builder << ", media_timestamp = \"" << message_entity.media_timestamp << "\"";
+ }
+ if (!message_entity.argument.empty()) {
string_builder << ", argument = \"" << message_entity.argument << "\"";
}
if (message_entity.user_id.is_valid()) {
string_builder << ", " << message_entity.user_id;
}
+ if (message_entity.custom_emoji_id.is_valid()) {
+ string_builder << ", " << message_entity.custom_emoji_id;
+ }
string_builder << ']';
return string_builder;
}
@@ -103,6 +146,12 @@ tl_object_ptr<td_api::TextEntityType> MessageEntity::get_text_entity_type_object
return make_tl_object<td_api::textEntityTypeBold>();
case MessageEntity::Type::Italic:
return make_tl_object<td_api::textEntityTypeItalic>();
+ case MessageEntity::Type::Underline:
+ return make_tl_object<td_api::textEntityTypeUnderline>();
+ case MessageEntity::Type::Strikethrough:
+ return make_tl_object<td_api::textEntityTypeStrikethrough>();
+ case MessageEntity::Type::BlockQuote:
+ return nullptr;
case MessageEntity::Type::Code:
return make_tl_object<td_api::textEntityTypeCode>();
case MessageEntity::Type::Pre:
@@ -112,11 +161,20 @@ tl_object_ptr<td_api::TextEntityType> MessageEntity::get_text_entity_type_object
case MessageEntity::Type::TextUrl:
return make_tl_object<td_api::textEntityTypeTextUrl>(argument);
case MessageEntity::Type::MentionName:
+ // can't use contacts_manager, because can be called from a static request
return make_tl_object<td_api::textEntityTypeMentionName>(user_id.get());
case MessageEntity::Type::Cashtag:
return make_tl_object<td_api::textEntityTypeCashtag>();
case MessageEntity::Type::PhoneNumber:
return make_tl_object<td_api::textEntityTypePhoneNumber>();
+ case MessageEntity::Type::BankCardNumber:
+ return make_tl_object<td_api::textEntityTypeBankCardNumber>();
+ case MessageEntity::Type::MediaTimestamp:
+ return make_tl_object<td_api::textEntityTypeMediaTimestamp>(media_timestamp);
+ case MessageEntity::Type::Spoiler:
+ return make_tl_object<td_api::textEntityTypeSpoiler>();
+ case MessageEntity::Type::CustomEmoji:
+ return make_tl_object<td_api::textEntityTypeCustomEmoji>(custom_emoji_id.get());
default:
UNREACHABLE();
return nullptr;
@@ -127,48 +185,64 @@ tl_object_ptr<td_api::textEntity> MessageEntity::get_text_entity_object() const
return make_tl_object<td_api::textEntity>(offset, length, get_text_entity_type_object());
}
-vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities) {
+vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities,
+ bool skip_bot_commands, int32 max_media_timestamp) {
vector<tl_object_ptr<td_api::textEntity>> result;
result.reserve(entities.size());
for (auto &entity : entities) {
- result.push_back(entity.get_text_entity_object());
+ if (skip_bot_commands && entity.type == MessageEntity::Type::BotCommand) {
+ continue;
+ }
+ if (entity.type == MessageEntity::Type::MediaTimestamp && max_media_timestamp < entity.media_timestamp) {
+ continue;
+ }
+ auto entity_object = entity.get_text_entity_object();
+ if (entity_object->type_ != nullptr) {
+ result.push_back(std::move(entity_object));
+ }
}
return result;
}
-static bool is_word_character(uint32 a) {
- switch (get_unicode_simple_category(a)) {
+StringBuilder &operator<<(StringBuilder &string_builder, const FormattedText &text) {
+ return string_builder << '"' << text.text << "\" with entities " << text.entities;
+}
+
+td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands,
+ int32 max_media_timestamp) {
+ return td_api::make_object<td_api::formattedText>(
+ text.text, get_text_entities_object(text.entities, skip_bot_commands, max_media_timestamp));
+}
+
+static bool is_word_character(uint32 code) {
+ switch (get_unicode_simple_category(code)) {
case UnicodeSimpleCategory::Letter:
case UnicodeSimpleCategory::DecimalNumber:
case UnicodeSimpleCategory::Number:
return true;
default:
- return a == '_';
+ return code == '_';
}
}
-td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text) {
- return td_api::make_object<td_api::formattedText>(text.text, get_text_entities_object(text.entities));
-}
-
/*
static bool is_word_boundary(uint32 a, uint32 b) {
return is_word_character(a) ^ is_word_character(b);
}
*/
-static bool is_alpha_digit(uint32 a) {
- return ('0' <= a && a <= '9') || ('a' <= a && a <= 'z') || ('A' <= a && a <= 'Z');
+static bool is_alpha_digit(uint32 code) {
+ return ('0' <= code && code <= '9') || ('a' <= code && code <= 'z') || ('A' <= code && code <= 'Z');
}
-static bool is_alpha_digit_or_underscore(uint32 a) {
- return is_alpha_digit(a) || a == '_';
+static bool is_alpha_digit_or_underscore(uint32 code) {
+ return is_alpha_digit(code) || code == '_';
}
-static bool is_alpha_digit_or_underscore_or_minus(uint32 a) {
- return is_alpha_digit_or_underscore(a) || a == '-';
+static bool is_alpha_digit_or_underscore_or_minus(uint32 code) {
+ return is_alpha_digit_or_underscore(code) || code == '-';
}
// This functions just implements corresponding regexps
@@ -182,7 +256,7 @@ static vector<Slice> match_mentions(Slice str) {
// '/(?<=\B)@([a-zA-Z0-9_]{2,32})(?=\b)/u'
while (true) {
- ptr = reinterpret_cast<const unsigned char *>(std::memchr(ptr, '@', narrow_cast<int32>(end - ptr)));
+ ptr = static_cast<const unsigned char *>(std::memchr(ptr, '@', narrow_cast<int32>(end - ptr)));
if (ptr == nullptr) {
break;
}
@@ -226,7 +300,7 @@ static vector<Slice> match_bot_commands(Slice str) {
// '/(?<!\b|[\/<>])\/([a-zA-Z0-9_]{1,64})(?:@([a-zA-Z0-9_]{3,32}))?(?!\B|[\/<>])/u'
while (true) {
- ptr = reinterpret_cast<const unsigned char *>(std::memchr(ptr, '/', narrow_cast<int32>(end - ptr)));
+ ptr = static_cast<const unsigned char *>(std::memchr(ptr, '/', narrow_cast<int32>(end - ptr)));
if (ptr == nullptr) {
break;
}
@@ -278,7 +352,7 @@ static vector<Slice> match_bot_commands(Slice str) {
static bool is_hashtag_letter(uint32 c, UnicodeSimpleCategory &category) {
category = get_unicode_simple_category(c);
- if (c == '_' || c == 0x200c) {
+ if (c == '_' || c == 0x200c || c == 0xb7 || (0xd80 <= c && c <= 0xdff)) {
return true;
}
switch (category) {
@@ -296,13 +370,13 @@ static vector<Slice> match_hashtags(Slice str) {
const unsigned char *end = str.uend();
const unsigned char *ptr = begin;
- // '/(?<=^|[^\d_\pL\x{200c}])#([\d_\pL\x{200c}]{1,256})(?![\d_\pL\x{200c}]*#)/u'
+ // '/(?<=^|[^\d_\pL\x{200c}\x{0d80}-\x{0dff}])#([\d_\pL\x{200c}\x{0d80}-\x{0dff}]{1,256})(?![\d_\pL\x{200c}\x{0d80}-\x{0dff}]*#)/u'
// and at least one letter
UnicodeSimpleCategory category;
while (true) {
- ptr = reinterpret_cast<const unsigned char *>(std::memchr(ptr, '#', narrow_cast<int32>(end - ptr)));
+ ptr = static_cast<const unsigned char *>(std::memchr(ptr, '#', narrow_cast<int32>(end - ptr)));
if (ptr == nullptr) {
break;
}
@@ -359,11 +433,11 @@ static vector<Slice> match_cashtags(Slice str) {
const unsigned char *end = str.uend();
const unsigned char *ptr = begin;
- // '/(?<=^|[^$\d_\pL\x{200c}])\$([A-Z]{3,8})(?![$\d_\pL\x{200c}])/u'
+ // '/(?<=^|[^$\d_\pL\x{200c}\x{0d80}-\x{0dff}])\$(1INCH|[A-Z]{1,8})(?![$\d_\pL\x{200c}\x{0d80}-\x{0dff}])/u'
UnicodeSimpleCategory category;
while (true) {
- ptr = reinterpret_cast<const unsigned char *>(std::memchr(ptr, '$', narrow_cast<int32>(end - ptr)));
+ ptr = static_cast<const unsigned char *>(std::memchr(ptr, '$', narrow_cast<int32>(end - ptr)));
if (ptr == nullptr) {
break;
}
@@ -379,12 +453,16 @@ static vector<Slice> match_cashtags(Slice str) {
}
auto cashtag_begin = ++ptr;
- while (ptr != end && 'Z' >= *ptr && *ptr >= 'A') {
- ptr++;
+ if (end - ptr >= 5 && Slice(ptr, ptr + 5) == Slice("1INCH")) {
+ ptr += 5;
+ } else {
+ while (ptr != end && 'Z' >= *ptr && *ptr >= 'A') {
+ ptr++;
+ }
}
auto cashtag_end = ptr;
auto cashtag_size = cashtag_end - cashtag_begin;
- if (cashtag_size < 3 || cashtag_size > 8) {
+ if (cashtag_size < 1 || cashtag_size > 8) {
continue;
}
@@ -401,6 +479,204 @@ static vector<Slice> match_cashtags(Slice str) {
return result;
}
+static vector<Slice> match_media_timestamps(Slice str) {
+ vector<Slice> result;
+ const unsigned char *begin = str.ubegin();
+ const unsigned char *end = str.uend();
+ const unsigned char *ptr = begin;
+
+ while (true) {
+ ptr = static_cast<const unsigned char *>(std::memchr(ptr, ':', narrow_cast<int32>(end - ptr)));
+ if (ptr == nullptr) {
+ break;
+ }
+
+ auto media_timestamp_begin = ptr;
+ while (media_timestamp_begin != begin &&
+ (media_timestamp_begin[-1] == ':' || is_digit(media_timestamp_begin[-1]))) {
+ media_timestamp_begin--;
+ }
+ auto media_timestamp_end = ptr;
+ while (media_timestamp_end + 1 != end && (media_timestamp_end[1] == ':' || is_digit(media_timestamp_end[1]))) {
+ media_timestamp_end++;
+ }
+ media_timestamp_end++;
+
+ if (media_timestamp_begin != ptr && media_timestamp_end != ptr + 1 && is_digit(ptr[1])) {
+ ptr = media_timestamp_end;
+
+ if (media_timestamp_begin != begin) {
+ uint32 prev;
+ next_utf8_unsafe(prev_utf8_unsafe(media_timestamp_begin), &prev);
+
+ if (is_word_character(prev)) {
+ continue;
+ }
+ }
+ if (media_timestamp_end != end) {
+ uint32 next;
+ next_utf8_unsafe(media_timestamp_end, &next);
+
+ if (is_word_character(next)) {
+ continue;
+ }
+ }
+
+ result.emplace_back(media_timestamp_begin, media_timestamp_end);
+ } else {
+ ptr = media_timestamp_end;
+ }
+ }
+ return result;
+}
+
+static vector<Slice> match_bank_card_numbers(Slice str) {
+ vector<Slice> result;
+ const unsigned char *begin = str.ubegin();
+ const unsigned char *end = str.uend();
+ const unsigned char *ptr = begin;
+
+ // '/(?<=^|[^+_\pL\d-.,])[\d -]{13,}([^_\pL\d-]|$)/'
+
+ while (true) {
+ while (ptr != end && !is_digit(*ptr)) {
+ ptr++;
+ }
+ if (ptr == end) {
+ break;
+ }
+ if (ptr != begin) {
+ uint32 prev;
+ next_utf8_unsafe(prev_utf8_unsafe(ptr), &prev);
+
+ if (prev == '.' || prev == ',' || prev == '+' || prev == '-' || prev == '_' ||
+ get_unicode_simple_category(prev) == UnicodeSimpleCategory::Letter) {
+ while (ptr != end && (is_digit(*ptr) || *ptr == ' ' || *ptr == '-')) {
+ ptr++;
+ }
+ continue;
+ }
+ }
+
+ auto card_number_begin = ptr;
+ size_t digit_count = 0;
+ while (ptr != end && (is_digit(*ptr) || *ptr == ' ' || *ptr == '-')) {
+ if (*ptr == ' ' && digit_count >= 16 && digit_count <= 19 &&
+ digit_count == static_cast<size_t>(ptr - card_number_begin)) {
+ // continuous card number
+ break;
+ }
+ digit_count += static_cast<size_t>(is_digit(*ptr));
+ ptr++;
+ }
+ if (digit_count < 13 || digit_count > 19) {
+ continue;
+ }
+
+ auto card_number_end = ptr;
+ while (!is_digit(card_number_end[-1])) {
+ card_number_end--;
+ }
+ auto card_number_size = static_cast<size_t>(card_number_end - card_number_begin);
+ if (card_number_size > 2 * digit_count - 1) {
+ continue;
+ }
+ if (card_number_end != end) {
+ uint32 next;
+ next_utf8_unsafe(card_number_end, &next);
+ if (next == '-' || next == '_' || get_unicode_simple_category(next) == UnicodeSimpleCategory::Letter) {
+ continue;
+ }
+ }
+
+ result.emplace_back(card_number_begin, card_number_end);
+ }
+ return result;
+}
+
+static bool is_url_unicode_symbol(uint32 c) {
+ if (0x2000 <= c && c <= 0x206f) { // General Punctuation
+ // Zero Width Non-Joiner/Joiner and various dashes
+ return c == 0x200c || c == 0x200d || (0x2010 <= c && c <= 0x2015);
+ }
+ return get_unicode_simple_category(c) != UnicodeSimpleCategory::Separator;
+}
+
+static bool is_url_path_symbol(uint32 c) {
+ switch (c) {
+ case '\n':
+ case '<':
+ case '>':
+ case '"':
+ case 0xab: // «
+ case 0xbb: // »
+ return false;
+ default:
+ return is_url_unicode_symbol(c);
+ }
+}
+
+static vector<Slice> match_tg_urls(Slice str) {
+ vector<Slice> result;
+ const unsigned char *begin = str.ubegin();
+ const unsigned char *end = str.uend();
+ const unsigned char *ptr = begin;
+
+ // '(tg|ton)://[a-z0-9_-]{1,253}([/?#][^\s\x{2000}-\x{200b}\x{200e}-\x{200f}\x{2016}-\x{206f}<>«»"]*)?'
+
+ Slice bad_path_end_chars(".:;,('?!`");
+
+ while (end - ptr > 5) {
+ ptr = static_cast<const unsigned char *>(std::memchr(ptr, ':', narrow_cast<int32>(end - ptr)));
+ if (ptr == nullptr) {
+ break;
+ }
+
+ const unsigned char *url_begin = nullptr;
+ if (end - ptr >= 3 && ptr[1] == '/' && ptr[2] == '/') {
+ if (ptr - begin >= 2 && to_lower(ptr[-2]) == 't' && to_lower(ptr[-1]) == 'g') {
+ url_begin = ptr - 2;
+ } else if (ptr - begin >= 3 && to_lower(ptr[-3]) == 't' && to_lower(ptr[-2]) == 'o' && to_lower(ptr[-1]) == 'n') {
+ url_begin = ptr - 3;
+ }
+ }
+ if (url_begin == nullptr) {
+ ++ptr;
+ continue;
+ }
+
+ ptr += 3;
+ auto domain_begin = ptr;
+ while (ptr != end && ptr - domain_begin != 253 && is_alpha_digit_or_underscore_or_minus(*ptr)) {
+ ptr++;
+ }
+ if (ptr == domain_begin) {
+ continue;
+ }
+
+ if (ptr != end && (*ptr == '/' || *ptr == '?' || *ptr == '#')) {
+ auto path_end_ptr = ptr + 1;
+ while (path_end_ptr != end) {
+ uint32 code = 0;
+ auto next_ptr = next_utf8_unsafe(path_end_ptr, &code);
+ if (!is_url_path_symbol(code)) {
+ break;
+ }
+ path_end_ptr = next_ptr;
+ }
+ while (path_end_ptr > ptr + 1 && bad_path_end_chars.find(path_end_ptr[-1]) < bad_path_end_chars.size()) {
+ path_end_ptr--;
+ }
+ if (ptr[0] == '/' || path_end_ptr > ptr + 1) {
+ ptr = path_end_ptr;
+ }
+ }
+
+ result.emplace_back(url_begin, ptr);
+ }
+ return result;
+}
+
static vector<Slice> match_urls(Slice str) {
vector<Slice> result;
const unsigned char *begin = str.ubegin();
@@ -430,14 +706,12 @@ static vector<Slice> match_urls(Slice str) {
case '<':
case '>':
case '"':
+ case '@':
case 0xab: // «
case 0xbb: // »
return false;
default:
- if (0x2000 <= c && c <= 0x206f) { // General Punctuation
- return c == 0x200c || c == 0x200d; // Zero Width Non-Joiner/Joiner
- }
- return get_unicode_simple_category(c) != UnicodeSimpleCategory::Separator;
+ return is_url_unicode_symbol(c);
}
};
@@ -445,68 +719,56 @@ static vector<Slice> match_urls(Slice str) {
if (c < 0xc0) {
return c == '.' || is_alpha_digit_or_underscore_or_minus(c) || c == '~';
}
- if (0x2000 <= c && c <= 0x206f) { // General Punctuation
- return c == 0x200c || c == 0x200d; // Zero Width Non-Joiner/Joiner
- }
- return get_unicode_simple_category(c) != UnicodeSimpleCategory::Separator;
- };
-
- const auto &is_path_symbol = [](uint32 c) {
- switch (c) {
- case '\n':
- case '<':
- case '>':
- case '"':
- case 0xab: // «
- case 0xbb: // »
- return false;
- default:
- if (0x2000 <= c && c <= 0x206f) { // General Punctuation
- return c == 0x200c || c == 0x200d; // Zero Width Non-Joiner/Joiner
- }
- return get_unicode_simple_category(c) != UnicodeSimpleCategory::Separator;
- }
+ return is_url_unicode_symbol(c);
};
Slice bad_path_end_chars(".:;,('?!`");
while (true) {
auto dot_pos = str.find('.');
- if (dot_pos > str.size()) {
+ if (dot_pos > str.size() || dot_pos + 1 == str.size()) {
break;
}
+ if (str[dot_pos + 1] == ' ') {
+ // fast path
+ str = str.substr(dot_pos + 2);
+ begin = str.ubegin();
+ continue;
+ }
- const unsigned char *last_at_ptr = nullptr;
- const unsigned char *domain_end_ptr = begin + dot_pos;
- while (domain_end_ptr != end) {
+ const unsigned char *domain_begin_ptr = begin + dot_pos;
+ while (domain_begin_ptr != begin) {
+ domain_begin_ptr = prev_utf8_unsafe(domain_begin_ptr);
uint32 code = 0;
- auto next_ptr = next_utf8_unsafe(domain_end_ptr, &code);
- if (code == '@') {
- last_at_ptr = domain_end_ptr;
- }
- if (!is_user_data_symbol(code)) {
+ auto next_ptr = next_utf8_unsafe(domain_begin_ptr, &code);
+ if (!is_domain_symbol(code)) {
+ domain_begin_ptr = next_ptr;
break;
}
- domain_end_ptr = next_ptr;
}
- domain_end_ptr = last_at_ptr == nullptr ? begin + dot_pos : last_at_ptr + 1;
+
+ const unsigned char *last_at_ptr = nullptr;
+ const unsigned char *domain_end_ptr = begin + dot_pos;
while (domain_end_ptr != end) {
uint32 code = 0;
auto next_ptr = next_utf8_unsafe(domain_end_ptr, &code);
- if (!is_domain_symbol(code)) {
+ if (code == '@') {
+ last_at_ptr = domain_end_ptr;
+ } else if (!is_domain_symbol(code)) {
break;
}
domain_end_ptr = next_ptr;
}
- const unsigned char *domain_begin_ptr = begin + dot_pos;
- while (domain_begin_ptr != begin) {
- domain_begin_ptr = prev_utf8_unsafe(domain_begin_ptr);
- uint32 code = 0;
- auto next_ptr = next_utf8_unsafe(domain_begin_ptr, &code);
- if (last_at_ptr == nullptr ? !is_domain_symbol(code) : !is_user_data_symbol(code)) {
- domain_begin_ptr = next_ptr;
- break;
+ if (last_at_ptr != nullptr) {
+ while (domain_begin_ptr != begin) {
+ domain_begin_ptr = prev_utf8_unsafe(domain_begin_ptr);
+ uint32 code = 0;
+ auto next_ptr = next_utf8_unsafe(domain_begin_ptr, &code);
+ if (!is_user_data_symbol(code)) {
+ domain_begin_ptr = next_ptr;
+ break;
+ }
}
}
// LOG(ERROR) << "Domain: " << Slice(domain_begin_ptr, domain_end_ptr);
@@ -535,15 +797,15 @@ static vector<Slice> match_urls(Slice str) {
while (path_end_ptr != end) {
uint32 code = 0;
auto next_ptr = next_utf8_unsafe(path_end_ptr, &code);
- if (!is_path_symbol(code)) {
+ if (!is_url_path_symbol(code)) {
break;
}
path_end_ptr = next_ptr;
}
- while (bad_path_end_chars.find(path_end_ptr[-1]) < bad_path_end_chars.size()) {
+ while (path_end_ptr > url_end_ptr + 1 && bad_path_end_chars.find(path_end_ptr[-1]) < bad_path_end_chars.size()) {
path_end_ptr--;
}
- if (url_end_ptr[0] == '/' || url_end_ptr[0] == '#' || path_end_ptr > url_end_ptr + 1) {
+ if (url_end_ptr[0] == '/' || path_end_ptr > url_end_ptr + 1) {
url_end_ptr = path_end_ptr;
}
}
@@ -555,6 +817,9 @@ static vector<Slice> match_urls(Slice str) {
bool is_bad = false;
const unsigned char *url_begin_ptr = domain_begin_ptr;
if (url_begin_ptr != begin && url_begin_ptr[-1] == '@') {
+ if (last_at_ptr != nullptr) {
+ is_bad = true;
+ }
auto user_data_begin_ptr = url_begin_ptr - 1;
while (user_data_begin_ptr != begin) {
user_data_begin_ptr = prev_utf8_unsafe(user_data_begin_ptr);
@@ -590,9 +855,7 @@ static vector<Slice> match_urls(Slice str) {
url_begin_ptr = url_begin_ptr - 7;
} else if (ends_with(protocol, "https")) {
url_begin_ptr = url_begin_ptr - 8;
- } else if (ends_with(protocol, "sftp")) {
- url_begin_ptr = url_begin_ptr - 7;
- } else if (ends_with(protocol, "ftp") && protocol != "tftp") {
+ } else if (ends_with(protocol, "ftp") && protocol != "tftp" && protocol != "sftp") {
url_begin_ptr = url_begin_ptr - 6;
} else {
is_bad = true;
@@ -607,7 +870,7 @@ static vector<Slice> match_urls(Slice str) {
}
}
}
- // LOG(ERROR) << "full: " << Slice(url_begin_ptr, url_end_ptr) << " " << is_bad;
+ // LOG(ERROR) << "Full: " << Slice(url_begin_ptr, url_end_ptr) << " " << is_bad;
if (!is_bad) {
if (url_end_ptr > begin + dot_pos + 1) {
@@ -632,45 +895,96 @@ static vector<Slice> match_urls(Slice str) {
return result;
}
+static bool is_valid_bank_card(Slice str) {
+ const size_t MIN_CARD_LENGTH = 13;
+ const size_t MAX_CARD_LENGTH = 19;
+ char digits[MAX_CARD_LENGTH];
+ size_t digit_count = 0;
+ for (auto c : str) {
+ if (is_digit(c)) {
+ CHECK(digit_count < MAX_CARD_LENGTH);
+ digits[digit_count++] = c;
+ }
+ }
+ CHECK(digit_count >= MIN_CARD_LENGTH);
+
+ // Luhn algorithm
+ int32 sum = 0;
+ for (size_t i = digit_count; i > 0; i--) {
+ int32 digit = digits[i - 1] - '0';
+ if ((digit_count - i) % 2 == 0) {
+ sum += digit;
+ } else {
+ sum += (digit < 5 ? 2 * digit : 2 * digit - 9);
+ }
+ }
+ if (sum % 10 != 0) {
+ return false;
+ }
+
+ int32 prefix1 = (digits[0] - '0');
+ int32 prefix2 = prefix1 * 10 + (digits[1] - '0');
+ int32 prefix3 = prefix2 * 10 + (digits[2] - '0');
+ int32 prefix4 = prefix3 * 10 + (digits[3] - '0');
+ if (prefix1 == 4) {
+ // Visa
+ return digit_count == 13 || digit_count == 16 || digit_count == 18 || digit_count == 19;
+ }
+ if ((51 <= prefix2 && prefix2 <= 55) || (2221 <= prefix4 && prefix4 <= 2720)) {
+ // mastercard
+ return digit_count == 16;
+ }
+ if (prefix2 == 34 || prefix2 == 37) {
+ // American Express
+ return digit_count == 15;
+ }
+ if (prefix2 == 62 || prefix2 == 81) {
+ // UnionPay
+ return digit_count >= 16;
+ }
+ if (2200 <= prefix4 && prefix4 <= 2204) {
+ // MIR
+ return digit_count == 16;
+ }
+ return true; // skip length check
+}
+
bool is_email_address(Slice str) {
- // /^([a-z0-9_-]{0,26}[.+]){0,10}[a-z0-9_-]{1,35}@(([a-z0-9][a-z0-9_-]{0,28})?[a-z0-9][.]){1,6}[a-z]{2,6}$/i
+ // /^([a-z0-9_-]{0,26}[.+]){0,10}[a-z0-9_-]{1,35}@(([a-z0-9][a-z0-9_-]{0,28})?[a-z0-9][.]){1,6}[a-z]{2,8}$/i
Slice userdata;
Slice domain;
std::tie(userdata, domain) = split(str, '@');
- vector<Slice> userdata_parts;
+ if (domain.empty()) {
+ return false;
+ }
+
size_t prev = 0;
+ size_t userdata_part_count = 0;
for (size_t i = 0; i < userdata.size(); i++) {
if (userdata[i] == '.' || userdata[i] == '+') {
- userdata_parts.push_back(userdata.substr(prev, i - prev));
- prev = i + 1;
- }
- }
- userdata_parts.push_back(userdata.substr(prev));
- if (userdata_parts.size() >= 12) {
- return false;
- }
- for (auto &part : userdata_parts) {
- for (auto c : part) {
- if (!is_alpha_digit_or_underscore_or_minus(c)) {
+ if (i - prev >= 27) {
return false;
}
+ userdata_part_count++;
+ prev = i + 1;
+ } else if (!is_alpha_digit_or_underscore_or_minus(userdata[i])) {
+ return false;
}
}
- if (userdata_parts.back().empty() || userdata_parts.back().size() >= 36) {
+ userdata_part_count++;
+ if (userdata_part_count >= 12) {
return false;
}
- userdata_parts.pop_back();
- for (auto &part : userdata_parts) {
- if (part.size() >= 27) {
- return false;
- }
+ auto last_part_length = userdata.size() - prev;
+ if (last_part_length == 0 || last_part_length >= 36) {
+ return false;
}
vector<Slice> domain_parts = full_split(domain, '.');
if (domain_parts.size() <= 1 || domain_parts.size() > 7) {
return false;
}
- if (domain_parts.back().size() <= 1 || domain_parts.back().size() >= 7) {
+ if (domain_parts.back().size() <= 1 || domain_parts.back().size() >= 9) {
return false;
}
for (auto c : domain_parts.back()) {
@@ -828,8 +1142,19 @@ static bool is_common_tld(Slice str) {
"இந்தியா", "հայ", "新加坡", "فلسطين", "政务", "xperia", "xxx", "xyz", "yachts", "yahoo", "yamaxun", "yandex",
"ye", "yodobashi", "yoga", "yokohama", "you", "youtube", "yt", "yun", "za", "zappos", "zara", "zero", "zip",
"zippo", "zm", "zone", "zuerich",
- // comment for clang-format to prevent him from placing all strings on separate lines
+ // comment for clang-format to prevent it from placing all strings on separate lines
"zw"});
+ bool is_lower = true;
+ for (auto c : str) {
+ if (static_cast<uint32>(c - 'a') > 'z' - 'a') {
+ is_lower = false;
+ break;
+ }
+ }
+ if (is_lower) {
+ // fast path
+ return tlds.count(str) > 0;
+ }
string str_lower = utf8_to_lower(str);
if (str_lower != str && utf8_substr(Slice(str_lower), 1) == utf8_substr(str, 1)) {
return false;
@@ -837,13 +1162,12 @@ static bool is_common_tld(Slice str) {
return tlds.count(str_lower) > 0;
}
-Slice fix_url(Slice str) {
+static Slice fix_url(Slice str) {
auto full_url = str;
bool has_protocol = false;
auto str_begin = to_lower(str.substr(0, 8));
- if (begins_with(str_begin, "http://") || begins_with(str_begin, "https://") || begins_with(str_begin, "sftp://") ||
- begins_with(str_begin, "ftp://")) {
+ if (begins_with(str_begin, "http://") || begins_with(str_begin, "https://") || begins_with(str_begin, "ftp://")) {
auto pos = str.find(':');
str = str.substr(pos + 3);
has_protocol = true;
@@ -858,10 +1182,12 @@ Slice fix_url(Slice str) {
}
domain.truncate(domain.rfind(':'));
- string domain_lower = domain.str();
- to_lower_inplace(domain_lower);
- if (domain_lower == "teiegram.org") {
- return Slice();
+ if (domain.size() == 12 && (domain[0] == 't' || domain[0] == 'T')) {
+ string domain_lower = domain.str();
+ to_lower_inplace(domain_lower);
+ if (domain_lower == "teiegram.org") {
+ return Slice();
+ }
}
int32 balance[3] = {0, 0, 0};
@@ -897,42 +1223,44 @@ Slice fix_url(Slice str) {
}
full_url.remove_suffix(path.size() - path_pos);
- vector<Slice> domain_parts = full_split(domain, '.');
- if (domain_parts.size() <= 1) {
- return Slice();
- }
-
- bool is_ipv4 = domain_parts.size() == 4;
+ size_t prev = 0;
+ size_t domain_part_count = 0;
bool has_non_digit = false;
- for (auto &part : domain_parts) {
- if (part.empty() || part.size() >= 64) {
- return Slice();
- }
- if (part.back() == '-') {
- return Slice();
- }
-
- if (!has_non_digit) {
- if (part.size() > 3) {
- is_ipv4 = false;
+ bool is_ipv4 = true;
+ for (size_t i = 0; i <= domain.size(); i++) {
+ if (i == domain.size() || domain[i] == '.') {
+ auto part_size = i - prev;
+ if (part_size == 0 || part_size >= 64 || domain[i - 1] == '-') {
+ return Slice();
}
- for (auto c : part) {
- if (!is_digit(c)) {
+ if (is_ipv4) {
+ if (part_size > 3) {
+ is_ipv4 = false;
+ }
+ if (part_size == 3 &&
+ (domain[prev] >= '3' || (domain[prev] == '2' && (domain[prev + 1] >= '6' ||
+ (domain[prev + 1] == '5' && domain[prev + 2] >= '6'))))) {
+ is_ipv4 = false;
+ }
+ if (domain[prev] == '0' && part_size >= 2) {
is_ipv4 = false;
- has_non_digit = true;
}
}
- if (part.size() == 3 &&
- (part[0] >= '3' || (part[0] == '2' && (part[1] >= '6' || (part[1] == '5' && part[2] >= '6'))))) {
- is_ipv4 = false;
- }
- if (part[0] == '0' && part.size() >= 2) {
- is_ipv4 = false;
+
+ domain_part_count++;
+ if (i != domain.size()) {
+ prev = i + 1;
}
+ } else if (!is_digit(domain[i])) {
+ is_ipv4 = false;
+ has_non_digit = true;
}
}
+ if (domain_part_count == 1) {
+ return Slice();
+ }
- if (is_ipv4) {
+ if (is_ipv4 && domain_part_count == 4) {
return full_url;
}
@@ -940,7 +1268,7 @@ Slice fix_url(Slice str) {
return Slice();
}
- auto tld = domain_parts.back();
+ auto tld = domain.substr(prev);
if (utf8_length(tld) <= 1) {
return Slice();
}
@@ -967,31 +1295,40 @@ Slice fix_url(Slice str) {
}
}
- domain_parts.pop_back();
- if (domain_parts.back().find('_') < domain_parts.back().size()) {
- return Slice();
+ CHECK(prev > 0);
+ prev--;
+ while (prev-- > 0) {
+ if (domain[prev] == '_') {
+ return Slice();
+ } else if (domain[prev] == '.') {
+ break;
+ }
}
return full_url;
}
-const std::unordered_set<Slice, SliceHash> &get_valid_short_usernames() {
- static const std::unordered_set<Slice, SliceHash> valid_usernames{
- "ya", "gif", "wiki", "vid", "bing", "pic", "bold", "imdb", "coub", "like", "vote", "giff", "cap"};
+const FlatHashSet<Slice, SliceHash> &get_valid_short_usernames() {
+ static const FlatHashSet<Slice, SliceHash> valid_usernames = [] {
+ FlatHashSet<Slice, SliceHash> result;
+ for (auto username : {"gif", "wiki", "vid", "bing", "pic", "bold", "imdb", "coub", "like", "vote"}) {
+ result.insert(Slice(username));
+ }
+ return result;
+ }();
return valid_usernames;
}
vector<Slice> find_mentions(Slice str) {
auto mentions = match_mentions(str);
- mentions.erase(std::remove_if(mentions.begin(), mentions.end(),
- [](Slice mention) {
- mention.remove_prefix(1);
- if (mention.size() >= 5) {
- return false;
- }
- return get_valid_short_usernames().count(mention) == 0;
- }),
- mentions.end());
+ td::remove_if(mentions, [](Slice mention) {
+ mention.remove_prefix(1);
+ if (mention.size() >= 4) {
+ return false;
+ }
+ auto lowered_mention = to_lower(mention);
+ return get_valid_short_usernames().count(lowered_mention) == 0;
+ });
return mentions;
}
@@ -1007,11 +1344,27 @@ vector<Slice> find_cashtags(Slice str) {
return match_cashtags(str);
}
+vector<Slice> find_bank_card_numbers(Slice str) {
+ vector<Slice> result;
+ for (auto bank_card : match_bank_card_numbers(str)) {
+ if (is_valid_bank_card(bank_card)) {
+ result.emplace_back(bank_card);
+ }
+ }
+ return result;
+}
+
+vector<Slice> find_tg_urls(Slice str) {
+ return match_tg_urls(str);
+}
+
vector<std::pair<Slice, bool>> find_urls(Slice str) {
vector<std::pair<Slice, bool>> result;
for (auto url : match_urls(str)) {
if (is_email_address(url)) {
result.emplace_back(url, true);
+ } else if (begins_with(url, "mailto:") && is_email_address(url.substr(7))) {
+ result.emplace_back(url.substr(7), true);
} else {
url = fix_url(url);
if (!url.empty()) {
@@ -1022,80 +1375,285 @@ vector<std::pair<Slice, bool>> find_urls(Slice str) {
return result;
}
-// sorts entities, removes intersecting and empty entities
-static void fix_entities(vector<MessageEntity> &entities) {
- if (entities.empty()) {
+vector<std::pair<Slice, int32>> find_media_timestamps(Slice str) {
+ vector<std::pair<Slice, int32>> result;
+ for (auto media_timestamp : match_media_timestamps(str)) {
+ vector<Slice> parts = full_split(media_timestamp, ':');
+ CHECK(parts.size() >= 2);
+ if (parts.size() > 3 || parts.back().size() != 2) {
+ continue;
+ }
+ auto seconds = to_integer<int32>(parts.back());
+ if (seconds >= 60) {
+ continue;
+ }
+ if (parts.size() == 2) {
+ if (parts[0].size() > 4 || parts[0].empty()) {
+ continue;
+ }
+
+ auto minutes = to_integer<int32>(parts[0]);
+ result.emplace_back(media_timestamp, minutes * 60 + seconds);
+ continue;
+ } else {
+ if (parts[0].size() > 2 || parts[1].size() > 2 || parts[0].empty() || parts[1].empty()) {
+ continue;
+ }
+
+ auto minutes = to_integer<int32>(parts[1]);
+ if (minutes >= 60) {
+ continue;
+ }
+ auto hours = to_integer<int32>(parts[0]);
+ result.emplace_back(media_timestamp, hours * 3600 + minutes * 60 + seconds);
+ }
+ }
+ return result;
+}
+
+void remove_empty_entities(vector<MessageEntity> &entities) {
+ td::remove_if(entities, [](const auto &entity) {
+ if (entity.length <= 0) {
+ return true;
+ }
+ switch (entity.type) {
+ case MessageEntity::Type::TextUrl:
+ return entity.argument.empty();
+ case MessageEntity::Type::MentionName:
+ return !entity.user_id.is_valid();
+ case MessageEntity::Type::CustomEmoji:
+ return !entity.custom_emoji_id.is_valid();
+ default:
+ return false;
+ }
+ });
+}
+
+static int32 text_length(Slice text) {
+ return narrow_cast<int32>(utf8_utf16_length(text));
+}
+
+static void sort_entities(vector<MessageEntity> &entities) {
+ if (std::is_sorted(entities.begin(), entities.end())) {
return;
}
std::sort(entities.begin(), entities.end());
+}
- int32 last_entity_end = 0;
- size_t left_entities = 0;
- for (size_t i = 0; i < entities.size(); i++) {
- if (entities[i].length > 0 && entities[i].offset >= last_entity_end) {
- last_entity_end = entities[i].offset + entities[i].length;
- if (i != left_entities) {
- entities[left_entities] = std::move(entities[i]);
- }
- left_entities++;
- }
+#define check_is_sorted(entities) check_is_sorted_impl((entities), __LINE__)
+static void check_is_sorted_impl(const vector<MessageEntity> &entities, int line) {
+ LOG_CHECK(std::is_sorted(entities.begin(), entities.end())) << line << " " << entities;
+}
+
+#define check_non_intersecting(entities) check_non_intersecting_impl((entities), __LINE__)
+static void check_non_intersecting_impl(const vector<MessageEntity> &entities, int line) {
+ for (size_t i = 0; i + 1 < entities.size(); i++) {
+ LOG_CHECK(entities[i].offset + entities[i].length <= entities[i + 1].offset) << line << " " << entities;
}
- entities.erase(entities.begin() + left_entities, entities.end());
}
-vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool only_urls) {
- vector<MessageEntity> entities;
+static constexpr int32 get_entity_type_mask(MessageEntity::Type type) {
+ return 1 << static_cast<int32>(type);
+}
+
+static constexpr int32 get_splittable_entities_mask() {
+ return get_entity_type_mask(MessageEntity::Type::Bold) | get_entity_type_mask(MessageEntity::Type::Italic) |
+ get_entity_type_mask(MessageEntity::Type::Underline) |
+ get_entity_type_mask(MessageEntity::Type::Strikethrough) | get_entity_type_mask(MessageEntity::Type::Spoiler);
+}
+
+static constexpr int32 get_blockquote_entities_mask() {
+ return get_entity_type_mask(MessageEntity::Type::BlockQuote);
+}
+
+static constexpr int32 get_continuous_entities_mask() {
+ return get_entity_type_mask(MessageEntity::Type::Mention) | get_entity_type_mask(MessageEntity::Type::Hashtag) |
+ get_entity_type_mask(MessageEntity::Type::BotCommand) | get_entity_type_mask(MessageEntity::Type::Url) |
+ get_entity_type_mask(MessageEntity::Type::EmailAddress) | get_entity_type_mask(MessageEntity::Type::TextUrl) |
+ get_entity_type_mask(MessageEntity::Type::MentionName) | get_entity_type_mask(MessageEntity::Type::Cashtag) |
+ get_entity_type_mask(MessageEntity::Type::PhoneNumber) |
+ get_entity_type_mask(MessageEntity::Type::BankCardNumber) |
+ get_entity_type_mask(MessageEntity::Type::MediaTimestamp) |
+ get_entity_type_mask(MessageEntity::Type::CustomEmoji);
+}
+
+static constexpr int32 get_pre_entities_mask() {
+ return get_entity_type_mask(MessageEntity::Type::Pre) | get_entity_type_mask(MessageEntity::Type::Code) |
+ get_entity_type_mask(MessageEntity::Type::PreCode);
+}
+
+static constexpr int32 get_user_entities_mask() {
+ return get_splittable_entities_mask() | get_blockquote_entities_mask() |
+ get_entity_type_mask(MessageEntity::Type::TextUrl) | get_entity_type_mask(MessageEntity::Type::MentionName) |
+ get_entity_type_mask(MessageEntity::Type::CustomEmoji) | get_pre_entities_mask();
+}
+
+static int32 is_splittable_entity(MessageEntity::Type type) {
+ return (get_entity_type_mask(type) & get_splittable_entities_mask()) != 0;
+}
+
+static int32 is_blockquote_entity(MessageEntity::Type type) {
+ return type == MessageEntity::Type::BlockQuote;
+}
+
+static int32 is_continuous_entity(MessageEntity::Type type) {
+ return (get_entity_type_mask(type) & get_continuous_entities_mask()) != 0;
+}
+
+static int32 is_pre_entity(MessageEntity::Type type) {
+ return (get_entity_type_mask(type) & get_pre_entities_mask()) != 0;
+}
+
+static int32 is_user_entity(MessageEntity::Type type) {
+ return (get_entity_type_mask(type) & get_user_entities_mask()) != 0;
+}
+
+static int32 is_hidden_data_entity(MessageEntity::Type type) {
+ return (get_entity_type_mask(type) &
+ (get_entity_type_mask(MessageEntity::Type::TextUrl) | get_entity_type_mask(MessageEntity::Type::MentionName) |
+ get_pre_entities_mask())) != 0;
+}
+
+static constexpr size_t SPLITTABLE_ENTITY_TYPE_COUNT = 5;
+
+static size_t get_splittable_entity_type_index(MessageEntity::Type type) {
+ if (static_cast<int32>(type) <= static_cast<int32>(MessageEntity::Type::Bold) + 1) {
+ // Bold or Italic
+ return static_cast<int32>(type) - static_cast<int32>(MessageEntity::Type::Bold);
+ } else if (static_cast<int32>(type) <= static_cast<int32>(MessageEntity::Type::Underline) + 1) {
+ // Underline or Strikethrough
+ return static_cast<int32>(type) - static_cast<int32>(MessageEntity::Type::Underline) + 2;
+ } else {
+ CHECK(type == MessageEntity::Type::Spoiler);
+ return 4;
+ }
+}
+
+static bool are_entities_valid(const vector<MessageEntity> &entities) {
+ if (entities.empty()) {
+ return true;
+ }
+ check_is_sorted(entities);
- if (!only_urls) {
- auto mentions = find_mentions(text);
- for (auto &mention : mentions) {
- entities.emplace_back(MessageEntity::Type::Mention, narrow_cast<int32>(mention.begin() - text.begin()),
- narrow_cast<int32>(mention.size()));
+ int32 end_pos[SPLITTABLE_ENTITY_TYPE_COUNT];
+ std::fill_n(end_pos, SPLITTABLE_ENTITY_TYPE_COUNT, -1);
+ vector<const MessageEntity *> nested_entities_stack;
+ int32 nested_entity_type_mask = 0;
+ for (auto &entity : entities) {
+ while (!nested_entities_stack.empty() &&
+ entity.offset >= nested_entities_stack.back()->offset + nested_entities_stack.back()->length) {
+ // remove non-intersecting entities from the stack
+ nested_entity_type_mask -= get_entity_type_mask(nested_entities_stack.back()->type);
+ nested_entities_stack.pop_back();
}
- if (!skip_bot_commands) {
- auto bot_commands = find_bot_commands(text);
- for (auto &bot_command : bot_commands) {
- entities.emplace_back(MessageEntity::Type::BotCommand, narrow_cast<int32>(bot_command.begin() - text.begin()),
- narrow_cast<int32>(bot_command.size()));
+ if (!nested_entities_stack.empty()) {
+ if (entity.offset + entity.length > nested_entities_stack.back()->offset + nested_entities_stack.back()->length) {
+ // entity intersects some previous entity
+ return false;
+ }
+ if ((nested_entity_type_mask & get_entity_type_mask(entity.type)) != 0) {
+ // entity has the same type as one of the previous nested
+ return false;
+ }
+ auto parent_type = nested_entities_stack.back()->type;
+ if (is_pre_entity(parent_type)) {
+ // Pre and Code can't contain nested entities
+ return false;
+ }
+ // parents are not pre after this point
+ if (is_pre_entity(entity.type) && (nested_entity_type_mask & ~get_blockquote_entities_mask()) != 0) {
+ // Pre and Code can't be part of other entities, except blockquote
+ return false;
+ }
+ if ((is_continuous_entity(entity.type) || is_blockquote_entity(entity.type)) &&
+ (nested_entity_type_mask & get_continuous_entities_mask()) != 0) {
+ // continuous and blockquote can't be part of other continuous entity
+ return false;
+ }
+ if ((nested_entity_type_mask & get_splittable_entities_mask()) != 0) {
+ // the previous nested entity may be needed to split for consistency
+ // alternatively, better entity merging needs to be implemented
+ return false;
}
}
- auto hashtags = find_hashtags(text);
- for (auto &hashtag : hashtags) {
- entities.emplace_back(MessageEntity::Type::Hashtag, narrow_cast<int32>(hashtag.begin() - text.begin()),
- narrow_cast<int32>(hashtag.size()));
+ if (is_splittable_entity(entity.type)) {
+ auto index = get_splittable_entity_type_index(entity.type);
+ if (end_pos[index] >= entity.offset) {
+ // the entities can be merged
+ return false;
+ }
+ end_pos[index] = entity.offset + entity.length;
}
+ nested_entities_stack.push_back(&entity);
+ nested_entity_type_mask += get_entity_type_mask(entity.type);
+ }
+ return true;
+}
- auto cashtags = find_cashtags(text);
- for (auto &cashtag : cashtags) {
- entities.emplace_back(MessageEntity::Type::Cashtag, narrow_cast<int32>(cashtag.begin() - text.begin()),
- narrow_cast<int32>(cashtag.size()));
+// removes all intersecting entities, including nested
+static void remove_intersecting_entities(vector<MessageEntity> &entities) {
+ check_is_sorted(entities);
+ int32 last_entity_end = 0;
+ size_t left_entities = 0;
+ for (size_t i = 0; i < entities.size(); i++) {
+ CHECK(entities[i].length > 0);
+ if (entities[i].offset >= last_entity_end) {
+ last_entity_end = entities[i].offset + entities[i].length;
+ if (i != left_entities) {
+ entities[left_entities] = std::move(entities[i]);
+ }
+ left_entities++;
}
+ }
+ entities.erase(entities.begin() + left_entities, entities.end());
+}
- // TODO find_phone_numbers
+// continuous_entities and blockquote_entities must be pre-sorted and non-overlapping
+static void remove_entities_intersecting_blockquote(vector<MessageEntity> &entities,
+ const vector<MessageEntity> &blockquote_entities) {
+ check_non_intersecting(entities);
+ check_non_intersecting(blockquote_entities);
+ if (blockquote_entities.empty()) {
+ // fast path
+ return;
}
- auto urls = find_urls(text);
- for (auto &url : urls) {
- // TODO better find messageEntityUrl
- auto type = url.second ? MessageEntity::Type::EmailAddress : MessageEntity::Type::Url;
- if (only_urls && type != MessageEntity::Type::Url) {
+ auto blockquote_it = blockquote_entities.begin();
+ size_t left_entities = 0;
+ for (size_t i = 0; i < entities.size(); i++) {
+ while (blockquote_it != blockquote_entities.end() &&
+ (blockquote_it->type != MessageEntity::Type::BlockQuote ||
+ blockquote_it->offset + blockquote_it->length <= entities[i].offset)) {
+ ++blockquote_it;
+ }
+ if (blockquote_it != blockquote_entities.end() &&
+ (blockquote_it->offset + blockquote_it->length < entities[i].offset + entities[i].length ||
+ (entities[i].offset < blockquote_it->offset &&
+ blockquote_it->offset < entities[i].offset + entities[i].length))) {
continue;
}
- auto offset = narrow_cast<int32>(url.first.begin() - text.begin());
- auto length = narrow_cast<int32>(url.first.size());
- entities.emplace_back(type, offset, length);
+ if (i != left_entities) {
+ entities[left_entities] = std::move(entities[i]);
+ }
+ left_entities++;
}
+ entities.erase(entities.begin() + left_entities, entities.end());
+}
+// keeps only non-intersecting entities
+// fixes entity offsets from UTF-8 to UTF-16 offsets
+static void fix_entity_offsets(Slice text, vector<MessageEntity> &entities) {
if (entities.empty()) {
- return entities;
+ return;
}
- fix_entities(entities);
+ sort_entities(entities);
+
+ remove_intersecting_entities(entities);
- // fix offsets to utf16 offsets
const unsigned char *begin = text.ubegin();
const unsigned char *ptr = begin;
const unsigned char *end = text.uend();
@@ -1106,16 +1664,17 @@ vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool onl
auto entity_begin = entity.offset;
auto entity_end = entity.offset + entity.length;
- int32 pos = static_cast<int32>(ptr - begin);
+ auto pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
cnt--;
entity.offset = utf16_pos;
}
+ uint32 skipped_code = 0;
while (ptr != end && cnt > 0) {
unsigned char c = ptr[0];
utf16_pos += 1 + (c >= 0xf0);
- ptr = next_utf8_unsafe(ptr, nullptr);
+ ptr = next_utf8_unsafe(ptr, &skipped_code);
pos = static_cast<int32>(ptr - begin);
if (entity_begin == pos) {
@@ -1128,6 +1687,60 @@ vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool onl
}
CHECK(cnt == 0);
}
+}
+
+vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps) {
+ vector<MessageEntity> entities;
+
+ auto add_entities = [&entities, &text](MessageEntity::Type type, vector<Slice> (*find_entities_f)(Slice)) mutable {
+ auto new_entities = find_entities_f(text);
+ for (auto &entity : new_entities) {
+ auto offset = narrow_cast<int32>(entity.begin() - text.begin());
+ auto length = narrow_cast<int32>(entity.size());
+ entities.emplace_back(type, offset, length);
+ }
+ };
+ add_entities(MessageEntity::Type::Mention, find_mentions);
+ if (!skip_bot_commands) {
+ add_entities(MessageEntity::Type::BotCommand, find_bot_commands);
+ }
+ add_entities(MessageEntity::Type::Hashtag, find_hashtags);
+ add_entities(MessageEntity::Type::Cashtag, find_cashtags);
+ // TODO find_phone_numbers
+ add_entities(MessageEntity::Type::BankCardNumber, find_bank_card_numbers);
+ add_entities(MessageEntity::Type::Url, find_tg_urls);
+ auto urls = find_urls(text);
+ for (auto &url : urls) {
+ auto type = url.second ? MessageEntity::Type::EmailAddress : MessageEntity::Type::Url;
+ auto offset = narrow_cast<int32>(url.first.begin() - text.begin());
+ auto length = narrow_cast<int32>(url.first.size());
+ entities.emplace_back(type, offset, length);
+ }
+ if (!skip_media_timestamps) {
+ auto media_timestamps = find_media_timestamps(text);
+ for (auto &entity : media_timestamps) {
+ auto offset = narrow_cast<int32>(entity.first.begin() - text.begin());
+ auto length = narrow_cast<int32>(entity.first.size());
+ entities.emplace_back(MessageEntity::Type::MediaTimestamp, offset, length, entity.second);
+ }
+ }
+
+ fix_entity_offsets(text, entities);
+
+ return entities;
+}
+
+static vector<MessageEntity> find_media_timestamp_entities(Slice text) {
+ vector<MessageEntity> entities;
+
+ auto media_timestamps = find_media_timestamps(text);
+ for (auto &entity : media_timestamps) {
+ auto offset = narrow_cast<int32>(entity.first.begin() - text.begin());
+ auto length = narrow_cast<int32>(entity.first.size());
+ entities.emplace_back(MessageEntity::Type::MediaTimestamp, offset, length, entity.second);
+ }
+
+ fix_entity_offsets(text, entities);
return entities;
}
@@ -1148,24 +1761,28 @@ static vector<MessageEntity> merge_entities(vector<MessageEntity> old_entities,
for (auto &old_entity : old_entities) {
while (new_it != new_end && new_it->offset + new_it->length <= old_entity.offset) {
result.push_back(std::move(*new_it));
- new_it++;
+ ++new_it;
}
auto old_entity_end = old_entity.offset + old_entity.length;
result.push_back(std::move(old_entity));
while (new_it != new_end && new_it->offset < old_entity_end) {
- new_it++;
+ ++new_it;
}
}
while (new_it != new_end) {
result.push_back(std::move(*new_it));
- new_it++;
+ ++new_it;
}
return result;
}
-string get_first_url(Slice text, const vector<MessageEntity> &entities) {
- for (auto &entity : entities) {
+static bool is_plain_domain(Slice url) {
+ return url.find('/') >= url.size() && url.find('?') >= url.size() && url.find('#') >= url.size();
+}
+
+string get_first_url(const FormattedText &text) {
+ for (auto &entity : text.entities) {
switch (entity.type) {
case MessageEntity::Type::Mention:
break;
@@ -1173,28 +1790,56 @@ string get_first_url(Slice text, const vector<MessageEntity> &entities) {
break;
case MessageEntity::Type::BotCommand:
break;
- case MessageEntity::Type::Url:
- return utf8_utf16_substr(text, entity.offset, entity.length).str();
+ case MessageEntity::Type::Url: {
+ if (entity.length <= 4) {
+ continue;
+ }
+ auto url = utf8_utf16_substr(text.text, entity.offset, entity.length);
+ string scheme = to_lower(url.substr(0, 4));
+ if (scheme == "ton:" || begins_with(scheme, "tg:") || scheme == "ftp:" || is_plain_domain(url)) {
+ continue;
+ }
+ return url.str();
+ }
case MessageEntity::Type::EmailAddress:
break;
case MessageEntity::Type::Bold:
break;
case MessageEntity::Type::Italic:
break;
+ case MessageEntity::Type::Underline:
+ break;
+ case MessageEntity::Type::Strikethrough:
+ break;
+ case MessageEntity::Type::BlockQuote:
+ break;
case MessageEntity::Type::Code:
break;
case MessageEntity::Type::Pre:
break;
case MessageEntity::Type::PreCode:
break;
- case MessageEntity::Type::TextUrl:
- return entity.argument;
+ case MessageEntity::Type::TextUrl: {
+ Slice url = entity.argument;
+ if (begins_with(url, "ton:") || begins_with(url, "tg:") || begins_with(url, "ftp:")) {
+ continue;
+ }
+ return url.str();
+ }
case MessageEntity::Type::MentionName:
break;
case MessageEntity::Type::Cashtag:
break;
case MessageEntity::Type::PhoneNumber:
break;
+ case MessageEntity::Type::BankCardNumber:
+ break;
+ case MessageEntity::Type::MediaTimestamp:
+ break;
+ case MessageEntity::Type::Spoiler:
+ break;
+ case MessageEntity::Type::CustomEmoji:
+ break;
default:
UNREACHABLE();
}
@@ -1203,44 +1848,6 @@ string get_first_url(Slice text, const vector<MessageEntity> &entities) {
return string();
}
-static UserId get_link_user_id(Slice url) {
- auto lower_cased_url = to_lower(url);
- url = lower_cased_url;
-
- Slice link_scheme("tg:");
- if (!begins_with(url, link_scheme)) {
- return UserId();
- }
- url.remove_prefix(link_scheme.size());
- if (begins_with(url, "//")) {
- url.remove_prefix(2);
- }
-
- Slice host("user");
- if (!begins_with(url, host)) {
- return UserId();
- }
- url.remove_prefix(host.size());
- if (begins_with(url, "/")) {
- url.remove_prefix(1);
- }
- if (!begins_with(url, "?")) {
- return UserId();
- }
- url.remove_prefix(1);
- url.truncate(url.find('#'));
-
- for (auto parameter : full_split(url, '&')) {
- Slice key;
- Slice value;
- std::tie(key, value) = split(parameter, '=');
- if (key == Slice("id")) {
- return UserId(to_integer<int32>(value));
- }
- }
- return UserId();
-}
-
Result<vector<MessageEntity>> parse_markdown(string &text) {
string result;
vector<MessageEntity> entities;
@@ -1256,7 +1863,7 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
}
if (c != '_' && c != '*' && c != '`' && c != '[') {
if (is_utf8_character_first_code_unit(c)) {
- utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogaite pair
+ utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
}
result.push_back(text[i]);
continue;
@@ -1277,7 +1884,7 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
i += 2;
is_pre = true;
size_t language_end = i;
- while (language_end < size && !is_space(text[language_end]) && text[language_end] != '`') {
+ while (!is_space(text[language_end]) && text[language_end] != '`') {
language_end++;
}
if (i != language_end && language_end < size && text[language_end] != '`') {
@@ -1294,11 +1901,11 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
}
}
- int32 utf16_entity_length = 0;
+ int32 entity_offset = utf16_offset;
while (i < size && (text[i] != end_character || (is_pre && !(text[i + 1] == '`' && text[i + 2] == '`')))) {
auto cur_ch = static_cast<unsigned char>(text[i]);
if (is_utf8_character_first_code_unit(cur_ch)) {
- utf16_entity_length += 1 + (cur_ch >= 0xf0); // >= 4 bytes in symbol => surrogaite pair
+ utf16_offset += 1 + (cur_ch >= 0xf0); // >= 4 bytes in symbol => surrogate pair
}
result.push_back(text[i++]);
}
@@ -1306,18 +1913,19 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
return Status::Error(400, PSLICE() << "Can't find end of the entity starting at byte offset " << begin_pos);
}
- if (utf16_entity_length > 0) {
+ if (entity_offset != utf16_offset) {
+ auto entity_length = utf16_offset - entity_offset;
switch (c) {
case '_':
- entities.emplace_back(MessageEntity::Type::Italic, utf16_offset, utf16_entity_length);
+ entities.emplace_back(MessageEntity::Type::Italic, entity_offset, entity_length);
break;
case '*':
- entities.emplace_back(MessageEntity::Type::Bold, utf16_offset, utf16_entity_length);
+ entities.emplace_back(MessageEntity::Type::Bold, entity_offset, entity_length);
break;
case '[': {
string url;
if (text[i + 1] != '(') {
- // use text as a url
+ // use text as a URL
url.assign(text, begin_pos + 1, i - begin_pos - 1);
} else {
i += 2;
@@ -1325,14 +1933,13 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
url.push_back(text[i++]);
}
}
- auto user_id = get_link_user_id(url);
+ auto user_id = LinkManager::get_link_user_id(url);
if (user_id.is_valid()) {
- entities.emplace_back(utf16_offset, utf16_entity_length, user_id);
+ entities.emplace_back(entity_offset, entity_length, user_id);
} else {
- auto r_http_url = parse_url(url);
- if (r_http_url.is_ok() && url.find('.') != string::npos) {
- entities.emplace_back(MessageEntity::Type::TextUrl, utf16_offset, utf16_entity_length,
- r_http_url.ok().get_url());
+ url = LinkManager::get_checked_link(url);
+ if (!url.empty()) {
+ entities.emplace_back(MessageEntity::Type::TextUrl, entity_offset, entity_length, std::move(url));
}
}
break;
@@ -1340,18 +1947,17 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
case '`':
if (is_pre) {
if (language.empty()) {
- entities.emplace_back(MessageEntity::Type::Pre, utf16_offset, utf16_entity_length);
+ entities.emplace_back(MessageEntity::Type::Pre, entity_offset, entity_length);
} else {
- entities.emplace_back(MessageEntity::Type::PreCode, utf16_offset, utf16_entity_length, language);
+ entities.emplace_back(MessageEntity::Type::PreCode, entity_offset, entity_length, language);
}
} else {
- entities.emplace_back(MessageEntity::Type::Code, utf16_offset, utf16_entity_length);
+ entities.emplace_back(MessageEntity::Type::Code, entity_offset, entity_length);
}
break;
default:
UNREACHABLE();
}
- utf16_offset += utf16_entity_length;
}
if (is_pre) {
i += 2;
@@ -1361,7 +1967,977 @@ Result<vector<MessageEntity>> parse_markdown(string &text) {
return entities;
}
-static uint32 decode_html_entity(const string &text, size_t &pos) {
+static Result<vector<MessageEntity>> do_parse_markdown_v2(CSlice text, string &result) {
+ vector<MessageEntity> entities;
+ int32 utf16_offset = 0;
+
+ struct EntityInfo {
+ MessageEntity::Type type;
+ string argument;
+ int32 entity_offset;
+ size_t entity_byte_offset;
+ size_t entity_begin_pos;
+
+ EntityInfo(MessageEntity::Type type, string argument, int32 entity_offset, size_t entity_byte_offset,
+ size_t entity_begin_pos)
+ : type(type)
+ , argument(std::move(argument))
+ , entity_offset(entity_offset)
+ , entity_byte_offset(entity_byte_offset)
+ , entity_begin_pos(entity_begin_pos) {
+ }
+ };
+ std::vector<EntityInfo> nested_entities;
+
+ for (size_t i = 0; i < text.size(); i++) {
+ auto c = static_cast<unsigned char>(text[i]);
+ if (c == '\\' && text[i + 1] > 0 && text[i + 1] <= 126) {
+ i++;
+ utf16_offset += 1;
+ result += text[i];
+ continue;
+ }
+
+ Slice reserved_characters("_*[]()~`>#+-=|{}.!");
+ if (!nested_entities.empty()) {
+ switch (nested_entities.back().type) {
+ case MessageEntity::Type::Code:
+ case MessageEntity::Type::Pre:
+ case MessageEntity::Type::PreCode:
+ reserved_characters = Slice("`");
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (reserved_characters.find(text[i]) == Slice::npos) {
+ if (is_utf8_character_first_code_unit(c)) {
+ utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
+ }
+ result.push_back(text[i]);
+ continue;
+ }
+
+ bool is_end_of_an_entity = false;
+ if (!nested_entities.empty()) {
+ is_end_of_an_entity = [&] {
+ switch (nested_entities.back().type) {
+ case MessageEntity::Type::Bold:
+ return c == '*';
+ case MessageEntity::Type::Italic:
+ return c == '_' && text[i + 1] != '_';
+ case MessageEntity::Type::Code:
+ return c == '`';
+ case MessageEntity::Type::Pre:
+ case MessageEntity::Type::PreCode:
+ return c == '`' && text[i + 1] == '`' && text[i + 2] == '`';
+ case MessageEntity::Type::TextUrl:
+ return c == ']';
+ case MessageEntity::Type::Underline:
+ return c == '_' && text[i + 1] == '_';
+ case MessageEntity::Type::Strikethrough:
+ return c == '~';
+ case MessageEntity::Type::Spoiler:
+ return c == '|' && text[i + 1] == '|';
+ case MessageEntity::Type::CustomEmoji:
+ return c == ']';
+ default:
+ UNREACHABLE();
+ return false;
+ }
+ }();
+ }
+
+ if (!is_end_of_an_entity) {
+ // begin of an entity
+ MessageEntity::Type type;
+ string argument;
+ auto entity_byte_offset = i;
+ switch (c) {
+ case '_':
+ if (text[i + 1] == '_') {
+ type = MessageEntity::Type::Underline;
+ i++;
+ } else {
+ type = MessageEntity::Type::Italic;
+ }
+ break;
+ case '*':
+ type = MessageEntity::Type::Bold;
+ break;
+ case '~':
+ type = MessageEntity::Type::Strikethrough;
+ break;
+ case '|':
+ if (text[i + 1] == '|') {
+ i++;
+ type = MessageEntity::Type::Spoiler;
+ } else {
+ return Status::Error(400, PSLICE() << "Character '" << text[i]
+ << "' is reserved and must be escaped with the preceding '\\'");
+ }
+ break;
+ case '[':
+ type = MessageEntity::Type::TextUrl;
+ break;
+ case '`':
+ if (text[i + 1] == '`' && text[i + 2] == '`') {
+ i += 3;
+ type = MessageEntity::Type::Pre;
+ size_t language_end = i;
+ while (!is_space(text[language_end]) && text[language_end] != '`') {
+ language_end++;
+ }
+ if (i != language_end && language_end < text.size() && text[language_end] != '`') {
+ type = MessageEntity::Type::PreCode;
+ argument = text.substr(i, language_end - i).str();
+ i = language_end;
+ }
+ // skip one new line in the beginning of the text
+ if (text[i] == '\n' || text[i] == '\r') {
+ if ((text[i + 1] == '\n' || text[i + 1] == '\r') && text[i] != text[i + 1]) {
+ i += 2;
+ } else {
+ i++;
+ }
+ }
+
+ i--;
+ } else {
+ type = MessageEntity::Type::Code;
+ }
+ break;
+ case '!':
+ if (text[i + 1] == '[') {
+ i++;
+ type = MessageEntity::Type::CustomEmoji;
+ } else {
+ return Status::Error(400, PSLICE() << "Character '" << text[i]
+ << "' is reserved and must be escaped with the preceding '\\'");
+ }
+ break;
+ default:
+ return Status::Error(
+ 400, PSLICE() << "Character '" << text[i] << "' is reserved and must be escaped with the preceding '\\'");
+ }
+ nested_entities.emplace_back(type, std::move(argument), utf16_offset, entity_byte_offset, result.size());
+ } else {
+ // end of an entity
+ auto type = nested_entities.back().type;
+ auto argument = std::move(nested_entities.back().argument);
+ UserId user_id;
+ CustomEmojiId custom_emoji_id;
+ bool skip_entity = utf16_offset == nested_entities.back().entity_offset;
+ switch (type) {
+ case MessageEntity::Type::Bold:
+ case MessageEntity::Type::Italic:
+ case MessageEntity::Type::Code:
+ case MessageEntity::Type::Strikethrough:
+ break;
+ case MessageEntity::Type::Underline:
+ case MessageEntity::Type::Spoiler:
+ i++;
+ break;
+ case MessageEntity::Type::Pre:
+ case MessageEntity::Type::PreCode:
+ i += 2;
+ break;
+ case MessageEntity::Type::TextUrl: {
+ string url;
+ if (text[i + 1] != '(') {
+ // use text as a URL
+ url = result.substr(nested_entities.back().entity_begin_pos);
+ } else {
+ i += 2;
+ auto url_begin_pos = i;
+ while (i < text.size() && text[i] != ')') {
+ if (text[i] == '\\' && text[i + 1] > 0 && text[i + 1] <= 126) {
+ url += text[i + 1];
+ i += 2;
+ continue;
+ }
+ url += text[i++];
+ }
+ if (text[i] != ')') {
+ return Status::Error(400, PSLICE() << "Can't find end of a URL at byte offset " << url_begin_pos);
+ }
+ }
+ user_id = LinkManager::get_link_user_id(url);
+ if (!user_id.is_valid()) {
+ url = LinkManager::get_checked_link(url);
+ if (url.empty()) {
+ skip_entity = true;
+ } else {
+ argument = std::move(url);
+ }
+ }
+ break;
+ }
+ case MessageEntity::Type::CustomEmoji: {
+ if (text[i + 1] != '(') {
+ return Status::Error(400, "Custom emoji entity must contain a tg://emoji URL");
+ }
+ i += 2;
+ string url;
+ auto url_begin_pos = i;
+ while (i < text.size() && text[i] != ')') {
+ if (text[i] == '\\' && text[i + 1] > 0 && text[i + 1] <= 126) {
+ url += text[i + 1];
+ i += 2;
+ continue;
+ }
+ url += text[i++];
+ }
+ if (text[i] != ')') {
+ return Status::Error(400, PSLICE()
+ << "Can't find end of a custom emoji URL at byte offset " << url_begin_pos);
+ }
+ TRY_RESULT_ASSIGN(custom_emoji_id, LinkManager::get_link_custom_emoji_id(url));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ return false;
+ }
+
+ if (!skip_entity) {
+ auto entity_offset = nested_entities.back().entity_offset;
+ auto entity_length = utf16_offset - entity_offset;
+ if (user_id.is_valid()) {
+ entities.emplace_back(entity_offset, entity_length, user_id);
+ } else if (custom_emoji_id.is_valid()) {
+ entities.emplace_back(type, entity_offset, entity_length, custom_emoji_id);
+ } else {
+ entities.emplace_back(type, entity_offset, entity_length, std::move(argument));
+ }
+ }
+ nested_entities.pop_back();
+ }
+ }
+ if (!nested_entities.empty()) {
+ return Status::Error(400, PSLICE() << "Can't find end of " << nested_entities.back().type
+ << " entity at byte offset " << nested_entities.back().entity_byte_offset);
+ }
+
+ sort_entities(entities);
+
+ return entities;
+}
+
+Result<vector<MessageEntity>> parse_markdown_v2(string &text) {
+ string result;
+ TRY_RESULT(entities, do_parse_markdown_v2(text, result));
+ text = result;
+ return entities;
+}
+
+static vector<Slice> find_text_url_entities_v3(Slice text) {
+ vector<Slice> result;
+ size_t size = text.size();
+ for (size_t i = 0; i < size; i++) {
+ if (text[i] != '[') {
+ continue;
+ }
+
+ auto text_begin = i;
+ auto text_end = text_begin + 1;
+ while (text_end < size && text[text_end] != ']') {
+ text_end++;
+ }
+
+ i = text_end; // prevent quadratic asymptotic
+
+ if (text_end == size || text_end == text_begin + 1) {
+ continue;
+ }
+
+ auto url_begin = text_end + 1;
+ if (url_begin == size || text[url_begin] != '(') {
+ continue;
+ }
+
+ size_t url_end = url_begin + 1;
+ while (url_end < size && text[url_end] != ')') {
+ url_end++;
+ }
+
+ i = url_end; // prevent quadratic asymptotic, disallows [a](b[c](t.me)
+
+ if (url_end < size) {
+ Slice url = text.substr(url_begin + 1, url_end - url_begin - 1);
+ if (!LinkManager::get_checked_link(url).empty()) {
+ result.push_back(text.substr(text_begin, text_end - text_begin + 1));
+ result.push_back(text.substr(url_begin, url_end - url_begin + 1));
+ }
+ }
+ }
+ return result;
+}
+
+// entities must be valid for the text
+static FormattedText parse_text_url_entities_v3(Slice text, const vector<MessageEntity> &entities) {
+ // continuous entities can't intersect TextUrl entities,
+ // so try to find new TextUrl entities only between the predetermined continuous entities
+
+ Slice debug_initial_text = text;
+
+ FormattedText result;
+ int32 result_text_utf16_length = 0;
+ vector<MessageEntity> part_entities;
+ vector<MessageEntity> part_splittable_entities[SPLITTABLE_ENTITY_TYPE_COUNT];
+ int32 part_begin = 0;
+ int32 max_end = 0;
+ int32 skipped_length = 0;
+ auto add_part = [&](int32 part_end) {
+ // we have [part_begin, max_end) kept part and [max_end, part_end) part to parse text_url entities
+
+ if (max_end != part_begin) {
+ // add all entities from the kept part
+ auto kept_part_text = utf8_utf16_substr(text, 0, max_end - part_begin);
+ text = text.substr(kept_part_text.size());
+
+ result.text.append(kept_part_text.begin(), kept_part_text.size());
+ append(result.entities, std::move(part_entities));
+ part_entities.clear();
+ result_text_utf16_length += max_end - part_begin;
+ }
+
+ size_t splittable_entity_pos[SPLITTABLE_ENTITY_TYPE_COUNT] = {};
+ for (const auto &splittable_entities : part_splittable_entities) {
+ check_non_intersecting(splittable_entities);
+ }
+ if (part_end != max_end) {
+ // try to find text_url entities in the left part
+ auto parsed_part_text = utf8_utf16_substr(text, 0, part_end - max_end);
+ text = text.substr(parsed_part_text.size());
+
+ vector<Slice> text_urls = find_text_url_entities_v3(parsed_part_text);
+
+ int32 text_utf16_offset = max_end;
+ size_t prev_pos = 0;
+ for (size_t i = 0; i < text_urls.size(); i += 2) {
+ auto text_begin_pos = static_cast<size_t>(text_urls[i].begin() - parsed_part_text.begin());
+ auto text_end_pos = text_begin_pos + text_urls[i].size() - 1;
+ auto url_begin_pos = static_cast<size_t>(text_urls[i + 1].begin() - parsed_part_text.begin());
+ auto url_end_pos = url_begin_pos + text_urls[i + 1].size() - 1;
+ CHECK(parsed_part_text[text_begin_pos] == '[');
+ CHECK(parsed_part_text[text_end_pos] == ']');
+ CHECK(url_begin_pos == text_end_pos + 1);
+ CHECK(parsed_part_text[url_begin_pos] == '(');
+ CHECK(parsed_part_text[url_end_pos] == ')');
+
+ Slice before_text_url = parsed_part_text.substr(prev_pos, text_begin_pos - prev_pos);
+ auto before_text_url_utf16_length = text_length(before_text_url);
+ result_text_utf16_length += before_text_url_utf16_length;
+ result.text.append(before_text_url.begin(), before_text_url.size());
+ text_utf16_offset += before_text_url_utf16_length;
+
+ Slice text_url = parsed_part_text.substr(text_begin_pos + 1, text_end_pos - text_begin_pos - 1);
+ auto text_url_utf16_length = text_length(text_url);
+ Slice url = parsed_part_text.substr(url_begin_pos + 1, url_end_pos - url_begin_pos - 1);
+ auto url_utf16_length = text_length(url);
+ result.entities.emplace_back(MessageEntity::Type::TextUrl, result_text_utf16_length, text_url_utf16_length,
+ LinkManager::get_checked_link(url));
+ result.text.append(text_url.begin(), text_url.size());
+ result_text_utf16_length += text_url_utf16_length;
+
+ auto initial_utf16_length = 1 + text_url_utf16_length + 1 + 1 + url_utf16_length + 1;
+
+ // adjust splittable entities, removing deleted parts from them
+ // in the segment [text_utf16_offset, text_utf16_offset + initial_utf16_length)
+ // the first character and the last (url_utf16_length + 3) characters are deleted
+ for (size_t index = 0; index < SPLITTABLE_ENTITY_TYPE_COUNT; index++) {
+ auto &pos = splittable_entity_pos[index];
+ auto &splittable_entities = part_splittable_entities[index];
+ while (pos < splittable_entities.size() &&
+ splittable_entities[pos].offset < text_utf16_offset + initial_utf16_length) {
+ auto offset = splittable_entities[pos].offset;
+ auto length = splittable_entities[pos].length;
+ if (offset + length > text_utf16_offset + 1 + text_url_utf16_length) {
+ // ends after last removed part; truncate length
+ length = text_utf16_offset + 1 + text_url_utf16_length - offset;
+ }
+ if (offset >= text_utf16_offset + 1) {
+ offset--;
+ } else if (offset + length >= text_utf16_offset + 1) {
+ length--;
+ }
+ if (length > 0) {
+ CHECK(offset >= skipped_length);
+ CHECK(offset - skipped_length + length <= result_text_utf16_length);
+ if (offset < text_utf16_offset && offset + length > text_utf16_offset) {
+ // entity intersects start on the new text_url entity; split it
+ result.entities.emplace_back(splittable_entities[pos].type, offset - skipped_length,
+ text_utf16_offset - offset);
+ length -= text_utf16_offset - offset;
+ offset = text_utf16_offset;
+ }
+ result.entities.emplace_back(splittable_entities[pos].type, offset - skipped_length, length);
+ }
+ if (splittable_entities[pos].offset + splittable_entities[pos].length >
+ text_utf16_offset + initial_utf16_length) {
+ // begins before end of the segment, but ends after it
+ // need to keep the entity for future segments, so split the entity
+ splittable_entities[pos].length = splittable_entities[pos].offset + splittable_entities[pos].length -
+ (text_utf16_offset + initial_utf16_length);
+ splittable_entities[pos].offset = text_utf16_offset + initial_utf16_length;
+ } else {
+ pos++;
+ }
+ }
+ }
+ text_utf16_offset += initial_utf16_length;
+
+ skipped_length += 2 + 2 + url_utf16_length;
+ prev_pos = url_end_pos + 1;
+ }
+
+ result.text.append(parsed_part_text.begin() + prev_pos, parsed_part_text.size() - prev_pos);
+ result_text_utf16_length += part_end - text_utf16_offset;
+ }
+
+ // now add all left splittable entities from [part_begin, part_end)
+ for (size_t index = 0; index < SPLITTABLE_ENTITY_TYPE_COUNT; index++) {
+ auto &pos = splittable_entity_pos[index];
+ auto &splittable_entities = part_splittable_entities[index];
+ while (pos < splittable_entities.size() && splittable_entities[pos].offset < part_end) {
+ if (splittable_entities[pos].offset + splittable_entities[pos].length > part_end) {
+ // begins before end of the segment, but ends after it
+ // need to keep the entity for future segments, so split the entity
+ // entities don't intersect each other, so there can be at most one such entity
+ result.entities.emplace_back(splittable_entities[pos].type, splittable_entities[pos].offset - skipped_length,
+ part_end - splittable_entities[pos].offset);
+
+ splittable_entities[pos].length =
+ splittable_entities[pos].offset + splittable_entities[pos].length - part_end;
+ splittable_entities[pos].offset = part_end;
+ } else {
+ result.entities.emplace_back(splittable_entities[pos].type, splittable_entities[pos].offset - skipped_length,
+ splittable_entities[pos].length);
+ pos++;
+ }
+ }
+ if (pos == splittable_entities.size()) {
+ splittable_entities.clear();
+ } else {
+ CHECK(pos == splittable_entities.size() - 1);
+ LOG_CHECK(!text.empty()) << '"' << debug_initial_text << "\" " << entities;
+ splittable_entities[0] = std::move(splittable_entities.back());
+ splittable_entities.resize(1);
+ }
+ }
+
+ part_begin = part_end;
+ };
+
+ for (const auto &entity : entities) {
+ if (is_splittable_entity(entity.type)) {
+ auto index = get_splittable_entity_type_index(entity.type);
+ part_splittable_entities[index].push_back(entity);
+ continue;
+ }
+ CHECK(is_continuous_entity(entity.type));
+
+ if (entity.offset > max_end) {
+ // found a gap from max_end to entity.offset between predetermined entities
+ add_part(entity.offset);
+ } else {
+ CHECK(entity.offset == max_end);
+ }
+
+ max_end = entity.offset + entity.length;
+ part_entities.push_back(entity);
+ part_entities.back().offset -= skipped_length;
+ }
+ add_part(part_begin + text_length(text));
+
+ return result;
+}
+
+static vector<MessageEntity> find_splittable_entities_v3(Slice text, const vector<MessageEntity> &entities) {
+ std::unordered_set<int32, Hash<int32>> unallowed_boundaries;
+ for (auto &entity : entities) {
+ unallowed_boundaries.insert(entity.offset);
+ unallowed_boundaries.insert(entity.offset + entity.length);
+ if (entity.type == MessageEntity::Type::Mention || entity.type == MessageEntity::Type::Hashtag ||
+ entity.type == MessageEntity::Type::BotCommand || entity.type == MessageEntity::Type::Cashtag ||
+ entity.type == MessageEntity::Type::PhoneNumber || entity.type == MessageEntity::Type::BankCardNumber) {
+ for (int32 i = 1; i < entity.length; i++) {
+ unallowed_boundaries.insert(entity.offset + i);
+ }
+ }
+ }
+
+ auto found_entities = find_entities(text, false, true);
+ td::remove_if(found_entities, [](const auto &entity) {
+ return entity.type == MessageEntity::Type::EmailAddress || entity.type == MessageEntity::Type::Url;
+ });
+ for (auto &entity : found_entities) {
+ for (int32 i = 0; i <= entity.length; i++) {
+ unallowed_boundaries.insert(entity.offset + i);
+ }
+ }
+
+ vector<MessageEntity> result;
+ int32 splittable_entity_offset[SPLITTABLE_ENTITY_TYPE_COUNT] = {};
+ int32 utf16_offset = 0;
+ for (size_t i = 0; i + 1 < text.size(); i++) {
+ auto c = static_cast<unsigned char>(text[i]);
+ if (is_utf8_character_first_code_unit(c)) {
+ utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
+ }
+ if ((c == '_' || c == '*' || c == '~' || c == '|') && text[i] == text[i + 1] &&
+ unallowed_boundaries.count(utf16_offset) == 0) {
+ auto j = i + 2;
+ while (j != text.size() && text[j] == text[i] &&
+ unallowed_boundaries.count(utf16_offset + static_cast<int32>(j - i - 1)) == 0) {
+ j++;
+ }
+ if (j == i + 2) {
+ auto type = [c] {
+ switch (c) {
+ case '_':
+ return MessageEntity::Type::Italic;
+ case '*':
+ return MessageEntity::Type::Bold;
+ case '~':
+ return MessageEntity::Type::Strikethrough;
+ case '|':
+ return MessageEntity::Type::Spoiler;
+ default:
+ UNREACHABLE();
+ return MessageEntity::Type::Size;
+ }
+ }();
+ auto index = get_splittable_entity_type_index(type);
+ if (splittable_entity_offset[index] != 0) {
+ auto length = utf16_offset - splittable_entity_offset[index] - 1;
+ if (length > 0) {
+ result.emplace_back(type, splittable_entity_offset[index], length);
+ }
+ splittable_entity_offset[index] = 0;
+ } else {
+ splittable_entity_offset[index] = utf16_offset + 1;
+ }
+ }
+ utf16_offset += narrow_cast<int32>(j - i - 1);
+ i = j - 1;
+ }
+ }
+ return result;
+}
+
+// entities must be valid and can contain only splittable and continuous entities
+// __italic__ ~~strikethrough~~ **bold** ||spoiler|| and [text_url](telegram.org) entities are left to be parsed
+static FormattedText parse_markdown_v3_without_pre(Slice text, vector<MessageEntity> entities) {
+ check_is_sorted(entities);
+
+ FormattedText parsed_text_url_text;
+ if (text.find('[') != string::npos) {
+ parsed_text_url_text = parse_text_url_entities_v3(text, entities);
+ text = parsed_text_url_text.text;
+ entities = std::move(parsed_text_url_text.entities);
+ }
+ // splittable entities are sorted only within a fixed type now
+
+ bool have_splittable_entities = false;
+ for (size_t i = 0; i + 1 < text.size(); i++) {
+ if ((text[i] == '_' || text[i] == '*' || text[i] == '~' || text[i] == '|') && text[i] == text[i + 1]) {
+ have_splittable_entities = true;
+ break;
+ }
+ }
+ if (!have_splittable_entities) {
+ // fast path
+ sort_entities(entities);
+ return {text.str(), std::move(entities)};
+ }
+
+ auto found_splittable_entities = find_splittable_entities_v3(text, entities);
+ vector<int32> removed_pos;
+ for (auto &entity : found_splittable_entities) {
+ removed_pos.push_back(entity.offset - 1);
+ removed_pos.push_back(entity.offset + entity.length + 1);
+ }
+ std::sort(removed_pos.begin(), removed_pos.end());
+
+ string new_text;
+ CHECK(text.size() >= 2 * removed_pos.size());
+ new_text.reserve(text.size() - 2 * removed_pos.size());
+ size_t j = 0;
+ int32 utf16_offset = 0;
+ for (size_t i = 0; i < text.size(); i++) {
+ auto c = static_cast<unsigned char>(text[i]);
+ if (is_utf8_character_first_code_unit(c)) {
+ utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
+ }
+ if (j < removed_pos.size() && utf16_offset == removed_pos[j]) {
+ i++;
+ utf16_offset++;
+ CHECK(j + 1 == removed_pos.size() || removed_pos[j + 1] >= removed_pos[j] + 2);
+ j++;
+ } else {
+ new_text += text[i];
+ }
+ }
+ CHECK(j == removed_pos.size());
+ combine(entities, std::move(found_splittable_entities));
+ for (auto &entity : entities) {
+ auto removed_before_begin = narrow_cast<int32>(
+ std::upper_bound(removed_pos.begin(), removed_pos.end(), entity.offset) - removed_pos.begin());
+ auto removed_before_end = narrow_cast<int32>(
+ std::upper_bound(removed_pos.begin(), removed_pos.end(), entity.offset + entity.length) - removed_pos.begin());
+ entity.length -= 2 * (removed_before_end - removed_before_begin);
+ entity.offset -= 2 * removed_before_begin;
+ CHECK(entity.offset >= 0);
+ CHECK(entity.length >= 0);
+ CHECK(entity.offset + entity.length <= utf16_offset);
+ }
+
+ remove_empty_entities(entities);
+
+ sort_entities(entities);
+ return {std::move(new_text), std::move(entities)};
+}
+
+static FormattedText parse_pre_entities_v3(Slice text) {
+ string result;
+ vector<MessageEntity> entities;
+ size_t size = text.size();
+ int32 utf16_offset = 0;
+ for (size_t i = 0; i < size; i++) {
+ auto c = static_cast<unsigned char>(text[i]);
+ if (c != '`') {
+ if (is_utf8_character_first_code_unit(c)) {
+ utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
+ }
+ result.push_back(text[i]);
+ continue;
+ }
+
+ size_t j = i + 1;
+ while (j < size && text[j] == '`') {
+ j++;
+ }
+
+ if (j - i == 1 || j - i == 3) {
+ // trying to find end of the entity
+ int32 entity_length = 0;
+ bool is_found = false;
+ for (size_t end_tag_begin = j; end_tag_begin < size; end_tag_begin++) {
+ auto cur_c = static_cast<unsigned char>(text[end_tag_begin]);
+ if (cur_c == '`') {
+ // possible end tag
+ size_t end_tag_end = end_tag_begin + 1;
+ while (end_tag_end < size && text[end_tag_end] == '`') {
+ end_tag_end++;
+ }
+ if (end_tag_end - end_tag_begin == j - i) {
+ // end tag found
+ CHECK(entity_length > 0);
+ entities.emplace_back(j - i == 3 ? MessageEntity::Type::Pre : MessageEntity::Type::Code, utf16_offset,
+ entity_length);
+ result.append(text.begin() + j, end_tag_begin - j);
+ utf16_offset += entity_length;
+ i = end_tag_end - 1;
+ is_found = true;
+ break;
+ } else {
+ // not an end tag, skip
+ entity_length += narrow_cast<int32>(end_tag_end - end_tag_begin);
+ end_tag_begin = end_tag_end - 1;
+ }
+ } else if (is_utf8_character_first_code_unit(cur_c)) {
+ entity_length += 1 + (cur_c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
+ }
+ }
+ if (is_found) {
+ continue;
+ }
+ }
+
+ result.append(text.begin() + i, j - i);
+ utf16_offset += narrow_cast<int32>(j - i);
+ i = j - 1;
+ }
+ return {std::move(result), std::move(entities)};
+}
+
+// entities must be valid for the text
+static FormattedText parse_pre_entities_v3(Slice text, vector<MessageEntity> entities) {
+ // nothing can intersect pre entities, so ignore all '`' inside the predetermined entities
+ // and try to find new pre entities only between the predetermined entities
+
+ FormattedText result;
+ int32 result_text_utf16_length = 0;
+ int32 part_begin = 0;
+ int32 max_end = 0;
+ int32 skipped_length = 0;
+
+ auto add_part = [&](int32 part_end) {
+ // we have [part_begin, max_end) kept part and [max_end, part_end) part to parse pre entities
+ CHECK(part_begin == result_text_utf16_length + skipped_length);
+
+ if (max_end != part_begin) {
+ // add the kept part
+ auto kept_part_text = utf8_utf16_substr(text, 0, max_end - part_begin);
+ text = text.substr(kept_part_text.size());
+
+ result.text.append(kept_part_text.begin(), kept_part_text.size());
+ result_text_utf16_length += max_end - part_begin;
+ }
+
+ if (part_end != max_end) {
+ // try to find pre entities in the left part
+ auto parsed_part_text = utf8_utf16_substr(text, 0, part_end - max_end);
+ text = text.substr(parsed_part_text.size());
+
+ if (parsed_part_text.find('`') == string::npos) {
+ // fast path, no pre entities; just append the text
+ result.text.append(parsed_part_text.begin(), parsed_part_text.size());
+ result_text_utf16_length += part_end - max_end;
+ } else {
+ FormattedText parsed_text = parse_pre_entities_v3(parsed_part_text);
+ int32 new_skipped_length = 0;
+ for (auto &entity : parsed_text.entities) {
+ new_skipped_length += (entity.type == MessageEntity::Type::Pre ? 6 : 2);
+ }
+ CHECK(new_skipped_length < part_end - max_end);
+ result.text += parsed_text.text;
+ for (auto &entity : parsed_text.entities) {
+ entity.offset += result_text_utf16_length;
+ }
+ append(result.entities, std::move(parsed_text.entities));
+ result_text_utf16_length += part_end - max_end - new_skipped_length;
+ skipped_length += new_skipped_length;
+ }
+ }
+
+ part_begin = part_end;
+ };
+
+ for (auto &entity : entities) {
+ if (entity.offset > max_end) {
+ // found a gap from max_end to entity.offset between predetermined entities
+ add_part(entity.offset);
+ }
+
+ max_end = td::max(max_end, entity.offset + entity.length);
+ result.entities.push_back(std::move(entity));
+ result.entities.back().offset -= skipped_length;
+ }
+ add_part(part_begin + text_length(text));
+
+ return result;
+}
+
+// text entities must be valid
+// returned entities must be resplit and fixed
+FormattedText parse_markdown_v3(FormattedText text) {
+ if (text.text.find('`') != string::npos) {
+ text = parse_pre_entities_v3(text.text, std::move(text.entities));
+ check_is_sorted(text.entities);
+ }
+
+ bool have_pre = false;
+ for (auto &entity : text.entities) {
+ if (is_pre_entity(entity.type)) {
+ have_pre = true;
+ break;
+ }
+ }
+ if (!have_pre) {
+ // fast path
+ return parse_markdown_v3_without_pre(text.text, std::move(text.entities));
+ }
+
+ FormattedText result;
+ int32 result_text_utf16_length = 0;
+ vector<MessageEntity> part_entities;
+ int32 part_begin = 0;
+ int32 max_end = 0;
+ Slice left_text = text.text;
+
+ auto add_part = [&](int32 part_end) {
+ auto part_text = utf8_utf16_substr(left_text, 0, part_end - part_begin);
+ left_text = left_text.substr(part_text.size());
+
+ FormattedText part = parse_markdown_v3_without_pre(part_text, std::move(part_entities));
+ part_entities.clear();
+
+ result.text += part.text;
+ for (auto &entity : part.entities) {
+ entity.offset += result_text_utf16_length;
+ }
+ append(result.entities, std::move(part.entities));
+ result_text_utf16_length += text_length(part.text);
+ part_begin = part_end;
+ };
+
+ for (size_t i = 0; i < text.entities.size(); i++) {
+ auto &entity = text.entities[i];
+ CHECK(is_splittable_entity(entity.type) || is_pre_entity(entity.type) || is_continuous_entity(entity.type));
+ if (is_pre_entity(entity.type)) {
+ CHECK(entity.offset >= max_end);
+ CHECK(i + 1 == text.entities.size() || text.entities[i + 1].offset >= entity.offset + entity.length);
+
+ add_part(entity.offset);
+
+ auto part_text = utf8_utf16_substr(left_text, 0, entity.length);
+ left_text = left_text.substr(part_text.size());
+
+ result.text.append(part_text.begin(), part_text.size());
+ result.entities.push_back(entity);
+ result.entities.back().offset = result_text_utf16_length;
+ result_text_utf16_length += entity.length;
+ part_begin = entity.offset + entity.length;
+ } else {
+ part_entities.push_back(entity);
+ part_entities.back().offset -= part_begin;
+ }
+
+ max_end = td::max(max_end, entity.offset + entity.length);
+ }
+ add_part(part_begin + text_length(left_text));
+
+ return result;
+}
+
+// text entities must be valid
+FormattedText get_markdown_v3(FormattedText text) {
+ if (text.entities.empty()) {
+ return text;
+ }
+
+ check_is_sorted(text.entities);
+ for (auto &entity : text.entities) {
+ if (!is_user_entity(entity.type)) {
+ return text;
+ }
+ }
+
+ FormattedText result;
+ struct EntityInfo {
+ const MessageEntity *entity;
+ int32 utf16_added_before;
+
+ EntityInfo(MessageEntity *entity, int32 utf16_added_before)
+ : entity(entity), utf16_added_before(utf16_added_before) {
+ }
+ };
+ vector<EntityInfo> nested_entities_stack;
+ size_t current_entity = 0;
+
+ int32 utf16_offset = 0;
+ int32 utf16_added = 0;
+
+ for (size_t pos = 0; pos <= text.text.size(); pos++) {
+ auto c = static_cast<unsigned char>(text.text[pos]);
+ if (is_utf8_character_first_code_unit(c)) {
+ while (!nested_entities_stack.empty()) {
+ const auto *entity = nested_entities_stack.back().entity;
+ auto entity_end = entity->offset + entity->length;
+ if (utf16_offset < entity_end) {
+ break;
+ }
+
+ CHECK(utf16_offset == entity_end);
+
+ switch (entity->type) {
+ case MessageEntity::Type::Italic:
+ result.text += "__";
+ utf16_added += 2;
+ break;
+ case MessageEntity::Type::Bold:
+ result.text += "**";
+ utf16_added += 2;
+ break;
+ case MessageEntity::Type::Strikethrough:
+ result.text += "~~";
+ utf16_added += 2;
+ break;
+ case MessageEntity::Type::Spoiler:
+ result.text += "||";
+ utf16_added += 2;
+ break;
+ case MessageEntity::Type::TextUrl:
+ result.text += "](";
+ result.text += entity->argument;
+ result.text += ')';
+ utf16_added += narrow_cast<int32>(3 + entity->argument.size());
+ break;
+ case MessageEntity::Type::Code:
+ result.text += '`';
+ utf16_added++;
+ break;
+ case MessageEntity::Type::Pre:
+ result.text += "```";
+ utf16_added += 3;
+ break;
+ default:
+ result.entities.push_back(*entity);
+ result.entities.back().offset += nested_entities_stack.back().utf16_added_before;
+ result.entities.back().length += utf16_added - nested_entities_stack.back().utf16_added_before;
+ break;
+ }
+ nested_entities_stack.pop_back();
+ }
+
+ while (current_entity < text.entities.size() && utf16_offset >= text.entities[current_entity].offset) {
+ CHECK(utf16_offset == text.entities[current_entity].offset);
+ switch (text.entities[current_entity].type) {
+ case MessageEntity::Type::Italic:
+ result.text += "__";
+ utf16_added += 2;
+ break;
+ case MessageEntity::Type::Bold:
+ result.text += "**";
+ utf16_added += 2;
+ break;
+ case MessageEntity::Type::Strikethrough:
+ result.text += "~~";
+ utf16_added += 2;
+ break;
+ case MessageEntity::Type::Spoiler:
+ result.text += "||";
+ utf16_added += 2;
+ break;
+ case MessageEntity::Type::TextUrl:
+ result.text += '[';
+ utf16_added++;
+ break;
+ case MessageEntity::Type::Code:
+ result.text += '`';
+ utf16_added++;
+ break;
+ case MessageEntity::Type::Pre:
+ result.text += "```";
+ utf16_added += 3;
+ break;
+ default:
+ // keep as is
+ break;
+ }
+ nested_entities_stack.emplace_back(&text.entities[current_entity++], utf16_added);
+ }
+ utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
+ }
+ if (pos == text.text.size()) {
+ break;
+ }
+
+ result.text.push_back(text.text[pos]);
+ }
+
+ sort_entities(result.entities);
+ if (parse_markdown_v3(result) != text) {
+ return text;
+ }
+ return result;
+}
+
+static uint32 decode_html_entity(CSlice text, size_t &pos) {
auto c = static_cast<unsigned char>(text[pos]);
if (c != '&') {
return 0;
@@ -1391,14 +2967,14 @@ static uint32 decode_html_entity(const string &text, size_t &pos) {
while (is_alpha(text[end_pos])) {
end_pos++;
}
- string entity(text, pos + 1, end_pos - pos - 1);
- if (entity == "lt") {
+ Slice entity = text.substr(pos + 1, end_pos - pos - 1);
+ if (entity == Slice("lt")) {
res = static_cast<uint32>('<');
- } else if (entity == "gt") {
+ } else if (entity == Slice("gt")) {
res = static_cast<uint32>('>');
- } else if (entity == "amp") {
+ } else if (entity == Slice("amp")) {
res = static_cast<uint32>('&');
- } else if (entity == "quot") {
+ } else if (entity == Slice("quot")) {
res = static_cast<uint32>('"');
} else {
// unsupported literal entity
@@ -1414,12 +2990,26 @@ static uint32 decode_html_entity(const string &text, size_t &pos) {
return res;
}
-Result<vector<MessageEntity>> parse_html(string &text) {
- string result;
+static Result<vector<MessageEntity>> do_parse_html(CSlice text, string &result) {
vector<MessageEntity> entities;
- size_t size = text.size();
int32 utf16_offset = 0;
- for (size_t i = 0; i < size; i++) {
+
+ struct EntityInfo {
+ string tag_name;
+ string argument;
+ int32 entity_offset;
+ size_t entity_begin_pos;
+
+ EntityInfo(string tag_name, string argument, int32 entity_offset, size_t entity_begin_pos)
+ : tag_name(std::move(tag_name))
+ , argument(std::move(argument))
+ , entity_offset(entity_offset)
+ , entity_begin_pos(entity_begin_pos) {
+ }
+ };
+ std::vector<EntityInfo> nested_entities;
+
+ for (size_t i = 0; i < text.size(); i++) {
auto c = static_cast<unsigned char>(text[i]);
if (c == '&') {
auto ch = decode_html_entity(text, i);
@@ -1432,226 +3022,227 @@ Result<vector<MessageEntity>> parse_html(string &text) {
}
if (c != '<') {
if (is_utf8_character_first_code_unit(c)) {
- utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogaite pair
+ utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
}
result.push_back(text[i]);
continue;
}
- // we are at begin of the entity
- size_t begin_pos = i++;
- if (text[i] == '/') {
- return Status::Error(400, PSLICE() << "Unexpected end tag at byte offset " << begin_pos);
- }
- while (!is_space(text[i]) && text[i] != '>') {
- i++;
- }
- if (text[i] == 0) {
- return Status::Error(400, PSLICE() << "Unclosed start tag at byte offset " << begin_pos);
- }
-
- string tag_name(text, begin_pos + 1, i - begin_pos - 1);
- to_lower_inplace(tag_name);
- if (tag_name != "em" && tag_name != "strong" && tag_name != "a" && tag_name != "b" && tag_name != "i" &&
- tag_name != "pre" && tag_name != "code") {
- return Status::Error(400,
- PSLICE() << "Unsupported start tag \"" << tag_name << "\" at byte offset " << begin_pos);
- }
-
- string url;
- // string language; TODO PreCode support
- while (text[i] != '>') {
- while (text[i] != 0 && is_space(text[i])) {
- i++;
- }
- if (text[i] == '>') {
- break;
- }
- auto attribute_begin_pos = i;
- while (!is_space(text[i]) && text[i] != '=') {
- i++;
- }
- string attribute_name(text, attribute_begin_pos, i - attribute_begin_pos);
- if (attribute_name.empty()) {
- return Status::Error(400, PSLICE() << "Expected equal sign in declaration of attribute of the tag \""
- << tag_name << "\" at byte offset " << begin_pos);
- }
- while (text[i] != 0 && is_space(text[i])) {
- i++;
- }
- if (text[i] != '=') {
- return Status::Error(400, PSLICE() << "Expected equal sign in declaration of attribute of the tag \""
- << tag_name << "\" at byte offset " << begin_pos);
- }
- i++;
- while (text[i] != 0 && is_space(text[i])) {
+ auto begin_pos = i++;
+ if (text[i] != '/') {
+ // begin of an entity
+ while (!is_space(text[i]) && text[i] != '>') {
i++;
}
if (text[i] == 0) {
return Status::Error(400, PSLICE() << "Unclosed start tag at byte offset " << begin_pos);
}
- string attribute_value;
- if (text[i] != '\'' && text[i] != '"') {
- // A name token (a sequence of letters, digits, periods, or hyphens). Name tokens are not case sensitive.
- auto token_begin_pos = i;
- while (is_alnum(text[i]) || text[i] == '.' || text[i] == '-') {
+ string tag_name = to_lower(text.substr(begin_pos + 1, i - begin_pos - 1));
+ if (tag_name != "a" && tag_name != "b" && tag_name != "strong" && tag_name != "i" && tag_name != "em" &&
+ tag_name != "s" && tag_name != "strike" && tag_name != "del" && tag_name != "u" && tag_name != "ins" &&
+ tag_name != "tg-spoiler" && tag_name != "tg-emoji" && tag_name != "span" && tag_name != "pre" &&
+ tag_name != "code") {
+ return Status::Error(400, PSLICE()
+ << "Unsupported start tag \"" << tag_name << "\" at byte offset " << begin_pos);
+ }
+
+ string argument;
+ while (text[i] != '>') {
+ while (text[i] != 0 && is_space(text[i])) {
i++;
}
- attribute_value.assign(text, token_begin_pos, i - token_begin_pos);
- to_lower_inplace(attribute_value);
-
- if (!is_space(text[i]) && text[i] != '>') {
- return Status::Error(400, PSLICE() << "Unexpected end of name token at byte offset " << token_begin_pos);
+ if (text[i] == '>') {
+ break;
}
- } else {
- // A string literal
- char end_character = text[i++];
- while (text[i] != end_character && text[i] != 0) {
- if (text[i] == '&') {
- auto ch = decode_html_entity(text, i);
- if (ch != 0) {
- append_utf8_character(attribute_value, ch);
- continue;
+ auto attribute_begin_pos = i;
+ while (!is_space(text[i]) && text[i] != '=') {
+ i++;
+ }
+ Slice attribute_name = text.substr(attribute_begin_pos, i - attribute_begin_pos);
+ if (attribute_name.empty()) {
+ return Status::Error(
+ 400, PSLICE() << "Empty attribute name in the tag \"" << tag_name << "\" at byte offset " << begin_pos);
+ }
+ while (text[i] != 0 && is_space(text[i])) {
+ i++;
+ }
+ if (text[i] != '=') {
+ return Status::Error(400, PSLICE() << "Expected equal sign in declaration of an attribute of the tag \""
+ << tag_name << "\" at byte offset " << begin_pos);
+ }
+ i++;
+ while (text[i] != 0 && is_space(text[i])) {
+ i++;
+ }
+ if (text[i] == 0) {
+ return Status::Error(400, PSLICE()
+ << "Unclosed start tag \"" << tag_name << "\" at byte offset " << begin_pos);
+ }
+
+ string attribute_value;
+ if (text[i] != '\'' && text[i] != '"') {
+ // A name token (a sequence of letters, digits, periods, or hyphens). Name tokens are not case sensitive.
+ auto token_begin_pos = i;
+ while (is_alnum(text[i]) || text[i] == '.' || text[i] == '-') {
+ i++;
+ }
+ attribute_value = to_lower(text.substr(token_begin_pos, i - token_begin_pos));
+
+ if (!is_space(text[i]) && text[i] != '>') {
+ return Status::Error(400, PSLICE() << "Unexpected end of name token at byte offset " << token_begin_pos);
+ }
+ } else {
+ // A string literal
+ char end_character = text[i++];
+ while (text[i] != end_character && text[i] != 0) {
+ if (text[i] == '&') {
+ auto ch = decode_html_entity(text, i);
+ if (ch != 0) {
+ append_utf8_character(attribute_value, ch);
+ continue;
+ }
}
+ attribute_value.push_back(text[i++]);
+ }
+ if (text[i] == end_character) {
+ i++;
}
- attribute_value.push_back(text[i++]);
}
- if (text[i] == end_character) {
- i++;
+ if (text[i] == 0) {
+ return Status::Error(400, PSLICE() << "Unclosed start tag at byte offset " << begin_pos);
+ }
+
+ if (tag_name == "a" && attribute_name == Slice("href")) {
+ argument = std::move(attribute_value);
+ } else if (tag_name == "code" && attribute_name == Slice("class") &&
+ begins_with(attribute_value, "language-")) {
+ argument = attribute_value.substr(9);
+ } else if (tag_name == "span" && attribute_name == Slice("class") && begins_with(attribute_value, "tg-")) {
+ argument = attribute_value.substr(3);
+ } else if (tag_name == "tg-emoji" && attribute_name == Slice("emoji-id")) {
+ argument = std::move(attribute_value);
}
}
- if (text[i] == 0) {
- return Status::Error(400, PSLICE() << "Unclosed start tag at byte offset " << begin_pos);
+
+ if (tag_name == "span" && argument != "spoiler") {
+ return Status::Error(400, PSLICE()
+ << "Tag \"span\" must have class \"tg-spoiler\" at byte offset " << begin_pos);
}
- if (tag_name == "a" && attribute_name == "href") {
- url = attribute_value;
+ nested_entities.emplace_back(std::move(tag_name), std::move(argument), utf16_offset, result.size());
+ } else {
+ // end of an entity
+ if (nested_entities.empty()) {
+ return Status::Error(400, PSLICE() << "Unexpected end tag at byte offset " << begin_pos);
}
- }
- i++;
- int32 utf16_entity_length = 0;
- size_t entity_begin_pos = result.size();
- while (text[i] != 0 && text[i] != '<') {
- auto cur_ch = static_cast<unsigned char>(text[i]);
- if (cur_ch == '&') {
- auto ch = decode_html_entity(text, i);
- if (ch != 0) {
- utf16_entity_length += 1 + (ch > 0xffff);
- append_utf8_character(result, ch);
- continue;
- }
+ while (!is_space(text[i]) && text[i] != '>') {
+ i++;
}
- if (is_utf8_character_first_code_unit(cur_ch)) {
- utf16_entity_length += 1 + (cur_ch >= 0xf0); // >= 4 bytes in symbol => surrogaite pair
+ string end_tag_name = to_lower(text.substr(begin_pos + 2, i - begin_pos - 2));
+ while (is_space(text[i]) && text[i] != 0) {
+ i++;
+ }
+ if (text[i] != '>') {
+ return Status::Error(400, PSLICE() << "Unclosed end tag at byte offset " << begin_pos);
}
- result.push_back(text[i++]);
- }
- if (text[i] == 0) {
- return Status::Error(400,
- PSLICE() << "Can't found end tag corresponding to start tag at byte offset " << begin_pos);
- }
- auto end_tag_begin_pos = i++;
- if (text[i] != '/') {
- return Status::Error(400, PSLICE() << "Expected end tag at byte offset " << end_tag_begin_pos);
- }
- while (!is_space(text[i]) && text[i] != '>') {
- i++;
- }
- string end_tag_name(text, end_tag_begin_pos + 2, i - end_tag_begin_pos - 2);
- while (is_space(text[i]) && text[i] != 0) {
- i++;
- }
- if (text[i] != '>') {
- return Status::Error(400, PSLICE() << "Unclosed end tag at byte offset " << end_tag_begin_pos);
- }
- if (!end_tag_name.empty() && end_tag_name != tag_name) {
- return Status::Error(400, PSLICE() << "Unmatched end tag at byte offset " << end_tag_begin_pos
- << ", expected \"</" << tag_name << ">\", found\"</" << end_tag_name << ">\"");
- }
+ string tag_name = std::move(nested_entities.back().tag_name);
+ if (!end_tag_name.empty() && end_tag_name != tag_name) {
+ return Status::Error(400, PSLICE() << "Unmatched end tag at byte offset " << begin_pos << ", expected \"</"
+ << tag_name << ">\", found \"</" << end_tag_name << ">\"");
+ }
- if (utf16_entity_length > 0) {
- if (tag_name == "i" || tag_name == "em") {
- entities.emplace_back(MessageEntity::Type::Italic, utf16_offset, utf16_entity_length);
- } else if (tag_name == "b" || tag_name == "strong") {
- entities.emplace_back(MessageEntity::Type::Bold, utf16_offset, utf16_entity_length);
- } else if (tag_name == "a") {
- if (url.empty()) {
- url = result.substr(entity_begin_pos);
- }
- auto user_id = get_link_user_id(url);
- if (user_id.is_valid()) {
- entities.emplace_back(utf16_offset, utf16_entity_length, user_id);
- } else {
- auto r_http_url = parse_url(url);
- if (r_http_url.is_ok() && url.find('.') != string::npos) {
- entities.emplace_back(MessageEntity::Type::TextUrl, utf16_offset, utf16_entity_length,
- r_http_url.ok().get_url());
+ if (utf16_offset > nested_entities.back().entity_offset) {
+ auto entity_offset = nested_entities.back().entity_offset;
+ auto entity_length = utf16_offset - entity_offset;
+ if (tag_name == "i" || tag_name == "em") {
+ entities.emplace_back(MessageEntity::Type::Italic, entity_offset, entity_length);
+ } else if (tag_name == "b" || tag_name == "strong") {
+ entities.emplace_back(MessageEntity::Type::Bold, entity_offset, entity_length);
+ } else if (tag_name == "s" || tag_name == "strike" || tag_name == "del") {
+ entities.emplace_back(MessageEntity::Type::Strikethrough, entity_offset, entity_length);
+ } else if (tag_name == "u" || tag_name == "ins") {
+ entities.emplace_back(MessageEntity::Type::Underline, entity_offset, entity_length);
+ } else if (tag_name == "tg-spoiler" || (tag_name == "span" && nested_entities.back().argument == "spoiler")) {
+ entities.emplace_back(MessageEntity::Type::Spoiler, entity_offset, entity_length);
+ } else if (tag_name == "tg-emoji") {
+ auto r_document_id = to_integer_safe<int64>(nested_entities.back().argument);
+ if (r_document_id.is_error() || r_document_id.ok() == 0) {
+ return Status::Error(400, "Invalid custom emoji identifier specified");
+ }
+ entities.emplace_back(MessageEntity::Type::CustomEmoji, entity_offset, entity_length,
+ CustomEmojiId(r_document_id.ok()));
+ } else if (tag_name == "a") {
+ auto url = std::move(nested_entities.back().argument);
+ if (url.empty()) {
+ url = result.substr(nested_entities.back().entity_begin_pos);
+ }
+ auto user_id = LinkManager::get_link_user_id(url);
+ if (user_id.is_valid()) {
+ entities.emplace_back(entity_offset, entity_length, user_id);
+ } else {
+ url = LinkManager::get_checked_link(url);
+ if (!url.empty()) {
+ entities.emplace_back(MessageEntity::Type::TextUrl, entity_offset, entity_length, std::move(url));
+ }
}
+ } else if (tag_name == "pre") {
+ if (!entities.empty() && entities.back().type == MessageEntity::Type::Code &&
+ entities.back().offset == entity_offset && entities.back().length == entity_length &&
+ !entities.back().argument.empty()) {
+ entities.back().type = MessageEntity::Type::PreCode;
+ } else {
+ entities.emplace_back(MessageEntity::Type::Pre, entity_offset, entity_length);
+ }
+ } else if (tag_name == "code") {
+ if (!entities.empty() && entities.back().type == MessageEntity::Type::Pre &&
+ entities.back().offset == entity_offset && entities.back().length == entity_length &&
+ !nested_entities.back().argument.empty()) {
+ entities.back().type = MessageEntity::Type::PreCode;
+ entities.back().argument = std::move(nested_entities.back().argument);
+ } else {
+ entities.emplace_back(MessageEntity::Type::Code, entity_offset, entity_length,
+ nested_entities.back().argument);
+ }
+ } else {
+ UNREACHABLE();
}
- } else if (tag_name == "pre") {
- entities.emplace_back(MessageEntity::Type::Pre, utf16_offset, utf16_entity_length);
- } else if (tag_name == "code") {
- entities.emplace_back(MessageEntity::Type::Code, utf16_offset, utf16_entity_length);
}
- utf16_offset += utf16_entity_length;
+ nested_entities.pop_back();
}
}
- text = result;
- return entities;
-}
+ if (!nested_entities.empty()) {
+ return Status::Error(
+ 400, PSLICE() << "Can't find end tag corresponding to start tag " << nested_entities.back().tag_name);
+ }
-vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(const ContactsManager *contacts_manager,
- const vector<MessageEntity> &entities) {
- vector<tl_object_ptr<telegram_api::MessageEntity>> result;
for (auto &entity : entities) {
- switch (entity.type) {
- case MessageEntity::Type::Mention:
- case MessageEntity::Type::Hashtag:
- case MessageEntity::Type::BotCommand:
- case MessageEntity::Type::Url:
- case MessageEntity::Type::EmailAddress:
- case MessageEntity::Type::Cashtag:
- case MessageEntity::Type::PhoneNumber:
- continue;
- case MessageEntity::Type::Bold:
- result.push_back(make_tl_object<telegram_api::messageEntityBold>(entity.offset, entity.length));
- break;
- case MessageEntity::Type::Italic:
- result.push_back(make_tl_object<telegram_api::messageEntityItalic>(entity.offset, entity.length));
- break;
- case MessageEntity::Type::Code:
- result.push_back(make_tl_object<telegram_api::messageEntityCode>(entity.offset, entity.length));
- break;
- case MessageEntity::Type::Pre:
- result.push_back(make_tl_object<telegram_api::messageEntityPre>(entity.offset, entity.length, string()));
- break;
- case MessageEntity::Type::PreCode:
- result.push_back(make_tl_object<telegram_api::messageEntityPre>(entity.offset, entity.length, entity.argument));
- break;
- case MessageEntity::Type::TextUrl:
- result.push_back(
- make_tl_object<telegram_api::messageEntityTextUrl>(entity.offset, entity.length, entity.argument));
- break;
- case MessageEntity::Type::MentionName: {
- auto input_user = contacts_manager->get_input_user(entity.user_id);
- CHECK(input_user != nullptr);
- result.push_back(make_tl_object<telegram_api::inputMessageEntityMentionName>(entity.offset, entity.length,
- std::move(input_user)));
- break;
- }
- default:
- UNREACHABLE();
+ if (entity.type == MessageEntity::Type::Code && !entity.argument.empty()) {
+ entity.argument.clear();
}
}
- return result;
+ sort_entities(entities);
+
+ return entities;
+}
+
+Result<vector<MessageEntity>> parse_html(string &text) {
+ string result;
+ TRY_RESULT(entities, do_parse_html(text, result));
+ if (!check_utf8(result)) {
+ return Status::Error(400,
+ "Text contains invalid Unicode characters after decoding HTML entities, check for unmatched "
+ "surrogate code units");
+ }
+ text = result;
+ return entities;
}
vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entities(
- const vector<MessageEntity> &entities) {
+ const vector<MessageEntity> &entities, int32 layer) {
vector<tl_object_ptr<secret_api::MessageEntity>> result;
for (auto &entity : entities) {
switch (entity.type) {
@@ -1665,6 +3256,10 @@ vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entiti
break;
case MessageEntity::Type::BotCommand:
break;
+ case MessageEntity::Type::PhoneNumber:
+ break;
+ case MessageEntity::Type::BankCardNumber:
+ break;
case MessageEntity::Type::Url:
result.push_back(make_tl_object<secret_api::messageEntityUrl>(entity.offset, entity.length));
break;
@@ -1677,6 +3272,21 @@ vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entiti
case MessageEntity::Type::Italic:
result.push_back(make_tl_object<secret_api::messageEntityItalic>(entity.offset, entity.length));
break;
+ case MessageEntity::Type::Underline:
+ if (layer >= static_cast<int32>(SecretChatLayer::NewEntities)) {
+ result.push_back(make_tl_object<secret_api::messageEntityUnderline>(entity.offset, entity.length));
+ }
+ break;
+ case MessageEntity::Type::Strikethrough:
+ if (layer >= static_cast<int32>(SecretChatLayer::NewEntities)) {
+ result.push_back(make_tl_object<secret_api::messageEntityStrike>(entity.offset, entity.length));
+ }
+ break;
+ case MessageEntity::Type::BlockQuote:
+ if (layer >= static_cast<int32>(SecretChatLayer::NewEntities)) {
+ result.push_back(make_tl_object<secret_api::messageEntityBlockquote>(entity.offset, entity.length));
+ }
+ break;
case MessageEntity::Type::Code:
result.push_back(make_tl_object<secret_api::messageEntityCode>(entity.offset, entity.length));
break;
@@ -1692,7 +3302,18 @@ vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entiti
break;
case MessageEntity::Type::MentionName:
break;
- case MessageEntity::Type::PhoneNumber:
+ case MessageEntity::Type::MediaTimestamp:
+ break;
+ case MessageEntity::Type::Spoiler:
+ if (layer >= static_cast<int32>(SecretChatLayer::SpoilerAndCustomEmojiEntities)) {
+ result.push_back(make_tl_object<secret_api::messageEntitySpoiler>(entity.offset, entity.length));
+ }
+ break;
+ case MessageEntity::Type::CustomEmoji:
+ if (layer >= static_cast<int32>(SecretChatLayer::SpoilerAndCustomEmojiEntities)) {
+ result.push_back(make_tl_object<secret_api::messageEntityCustomEmoji>(entity.offset, entity.length,
+ entity.custom_emoji_id.get()));
+ }
break;
default:
UNREACHABLE();
@@ -1703,68 +3324,124 @@ vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entiti
}
Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contacts_manager,
- const vector<tl_object_ptr<td_api::textEntity>> &input_entities) {
+ vector<tl_object_ptr<td_api::textEntity>> &&input_entities,
+ bool allow_all) {
vector<MessageEntity> entities;
- for (auto &entity : input_entities) {
- if (entity == nullptr || entity->type_ == nullptr) {
+ entities.reserve(input_entities.size());
+ for (auto &input_entity : input_entities) {
+ if (input_entity == nullptr || input_entity->type_ == nullptr) {
continue;
}
- switch (entity->type_->get_id()) {
+ auto offset = input_entity->offset_;
+ auto length = input_entity->length_;
+ switch (input_entity->type_->get_id()) {
case td_api::textEntityTypeMention::ID:
+ entities.emplace_back(MessageEntity::Type::Mention, offset, length);
+ break;
case td_api::textEntityTypeHashtag::ID:
+ entities.emplace_back(MessageEntity::Type::Hashtag, offset, length);
+ break;
case td_api::textEntityTypeBotCommand::ID:
+ entities.emplace_back(MessageEntity::Type::BotCommand, offset, length);
+ break;
case td_api::textEntityTypeUrl::ID:
+ entities.emplace_back(MessageEntity::Type::Url, offset, length);
+ break;
case td_api::textEntityTypeEmailAddress::ID:
+ entities.emplace_back(MessageEntity::Type::EmailAddress, offset, length);
+ break;
case td_api::textEntityTypeCashtag::ID:
+ entities.emplace_back(MessageEntity::Type::Cashtag, offset, length);
+ break;
case td_api::textEntityTypePhoneNumber::ID:
+ entities.emplace_back(MessageEntity::Type::PhoneNumber, offset, length);
+ break;
+ case td_api::textEntityTypeBankCardNumber::ID:
+ entities.emplace_back(MessageEntity::Type::BankCardNumber, offset, length);
break;
case td_api::textEntityTypeBold::ID:
- entities.emplace_back(MessageEntity::Type::Bold, entity->offset_, entity->length_);
+ entities.emplace_back(MessageEntity::Type::Bold, offset, length);
break;
case td_api::textEntityTypeItalic::ID:
- entities.emplace_back(MessageEntity::Type::Italic, entity->offset_, entity->length_);
+ entities.emplace_back(MessageEntity::Type::Italic, offset, length);
+ break;
+ case td_api::textEntityTypeUnderline::ID:
+ entities.emplace_back(MessageEntity::Type::Underline, offset, length);
+ break;
+ case td_api::textEntityTypeStrikethrough::ID:
+ entities.emplace_back(MessageEntity::Type::Strikethrough, offset, length);
break;
case td_api::textEntityTypeCode::ID:
- entities.emplace_back(MessageEntity::Type::Code, entity->offset_, entity->length_);
+ entities.emplace_back(MessageEntity::Type::Code, offset, length);
break;
case td_api::textEntityTypePre::ID:
- entities.emplace_back(MessageEntity::Type::Pre, entity->offset_, entity->length_);
+ entities.emplace_back(MessageEntity::Type::Pre, offset, length);
break;
case td_api::textEntityTypePreCode::ID: {
- auto entity_pre_code = static_cast<td_api::textEntityTypePreCode *>(entity->type_.get());
- if (!clean_input_string(entity_pre_code->language_)) {
+ auto entity = static_cast<td_api::textEntityTypePreCode *>(input_entity->type_.get());
+ if (!clean_input_string(entity->language_)) {
return Status::Error(400, "MessageEntityPreCode.language must be encoded in UTF-8");
}
- entities.emplace_back(MessageEntity::Type::PreCode, entity->offset_, entity->length_,
- entity_pre_code->language_);
+ entities.emplace_back(MessageEntity::Type::PreCode, offset, length, entity->language_);
break;
}
case td_api::textEntityTypeTextUrl::ID: {
- auto entity_text_url = static_cast<td_api::textEntityTypeTextUrl *>(entity->type_.get());
- if (!clean_input_string(entity_text_url->url_)) {
+ auto entity = static_cast<td_api::textEntityTypeTextUrl *>(input_entity->type_.get());
+ if (!clean_input_string(entity->url_)) {
return Status::Error(400, "MessageEntityTextUrl.url must be encoded in UTF-8");
}
- auto r_http_url = parse_url(entity_text_url->url_);
- if (r_http_url.is_error()) {
- return Status::Error(400, PSTRING() << "Wrong message entity: " << r_http_url.error().message());
+ auto user_id = LinkManager::get_link_user_id(entity->url_);
+ if (user_id.is_valid()) {
+ if (contacts_manager != nullptr) {
+ TRY_STATUS(contacts_manager->get_input_user(user_id));
+ }
+ entities.emplace_back(offset, length, user_id);
+ break;
}
- entities.emplace_back(MessageEntity::Type::TextUrl, entity->offset_, entity->length_,
- r_http_url.ok().get_url());
+ auto r_url = LinkManager::check_link(entity->url_);
+ if (r_url.is_error()) {
+ return Status::Error(400, PSTRING() << "Entity " << r_url.error().message());
+ }
+ entities.emplace_back(MessageEntity::Type::TextUrl, offset, length, r_url.move_as_ok());
break;
}
case td_api::textEntityTypeMentionName::ID: {
- auto entity_mention_name = static_cast<td_api::textEntityTypeMentionName *>(entity->type_.get());
- UserId user_id(entity_mention_name->user_id_);
- if (!contacts_manager->have_input_user(user_id)) {
- return Status::Error(7, "Have no access to the user");
+ auto entity = static_cast<td_api::textEntityTypeMentionName *>(input_entity->type_.get());
+ UserId user_id(entity->user_id_);
+ if (contacts_manager != nullptr) {
+ TRY_STATUS(contacts_manager->get_input_user(user_id));
}
- entities.emplace_back(entity->offset_, entity->length_, user_id);
+ entities.emplace_back(offset, length, user_id);
+ break;
+ }
+ case td_api::textEntityTypeMediaTimestamp::ID: {
+ auto entity = static_cast<td_api::textEntityTypeMediaTimestamp *>(input_entity->type_.get());
+ if (entity->media_timestamp_ < 0) {
+ return Status::Error(400, "Invalid media timestamp specified");
+ }
+ entities.emplace_back(MessageEntity::Type::MediaTimestamp, offset, length, entity->media_timestamp_);
+ break;
+ }
+ case td_api::textEntityTypeSpoiler::ID:
+ entities.emplace_back(MessageEntity::Type::Spoiler, offset, length);
+ break;
+ case td_api::textEntityTypeCustomEmoji::ID: {
+ auto entity = static_cast<td_api::textEntityTypeCustomEmoji *>(input_entity->type_.get());
+ CustomEmojiId custom_emoji_id(entity->custom_emoji_id_);
+ if (!custom_emoji_id.is_valid()) {
+ return Status::Error(400, "Invalid custom emoji identifier specified");
+ }
+ entities.emplace_back(MessageEntity::Type::CustomEmoji, offset, length, custom_emoji_id);
break;
}
default:
UNREACHABLE();
}
+ CHECK(!entities.empty());
+ if (!allow_all && !is_user_entity(entities.back().type)) {
+ entities.pop_back();
+ }
}
return entities;
}
@@ -1774,100 +3451,128 @@ vector<MessageEntity> get_message_entities(const ContactsManager *contacts_manag
const char *source) {
vector<MessageEntity> entities;
entities.reserve(server_entities.size());
- for (auto &entity : server_entities) {
- switch (entity->get_id()) {
+ for (auto &server_entity : server_entities) {
+ switch (server_entity->get_id()) {
case telegram_api::messageEntityUnknown::ID:
break;
case telegram_api::messageEntityMention::ID: {
- auto entity_mention = static_cast<const telegram_api::messageEntityMention *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Mention, entity_mention->offset_, entity_mention->length_);
+ auto entity = static_cast<const telegram_api::messageEntityMention *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Mention, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityHashtag::ID: {
- auto entity_hashtag = static_cast<const telegram_api::messageEntityHashtag *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Hashtag, entity_hashtag->offset_, entity_hashtag->length_);
+ auto entity = static_cast<const telegram_api::messageEntityHashtag *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Hashtag, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityCashtag::ID: {
- auto entity_cashtag = static_cast<const telegram_api::messageEntityCashtag *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Cashtag, entity_cashtag->offset_, entity_cashtag->length_);
+ auto entity = static_cast<const telegram_api::messageEntityCashtag *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Cashtag, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityPhone::ID: {
- auto entity_phone = static_cast<const telegram_api::messageEntityPhone *>(entity.get());
- entities.emplace_back(MessageEntity::Type::PhoneNumber, entity_phone->offset_, entity_phone->length_);
+ auto entity = static_cast<const telegram_api::messageEntityPhone *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::PhoneNumber, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityBotCommand::ID: {
- auto entity_bot_command = static_cast<const telegram_api::messageEntityBotCommand *>(entity.get());
- entities.emplace_back(MessageEntity::Type::BotCommand, entity_bot_command->offset_,
- entity_bot_command->length_);
+ auto entity = static_cast<const telegram_api::messageEntityBotCommand *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::BotCommand, entity->offset_, entity->length_);
+ break;
+ }
+ case telegram_api::messageEntityBankCard::ID: {
+ auto entity = static_cast<const telegram_api::messageEntityBankCard *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::BankCardNumber, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityUrl::ID: {
- auto entity_url = static_cast<const telegram_api::messageEntityUrl *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Url, entity_url->offset_, entity_url->length_);
+ auto entity = static_cast<const telegram_api::messageEntityUrl *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Url, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityEmail::ID: {
- auto entity_email = static_cast<const telegram_api::messageEntityEmail *>(entity.get());
- entities.emplace_back(MessageEntity::Type::EmailAddress, entity_email->offset_, entity_email->length_);
+ auto entity = static_cast<const telegram_api::messageEntityEmail *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::EmailAddress, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityBold::ID: {
- auto entity_bold = static_cast<const telegram_api::messageEntityBold *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Bold, entity_bold->offset_, entity_bold->length_);
+ auto entity = static_cast<const telegram_api::messageEntityBold *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Bold, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityItalic::ID: {
- auto entity_italic = static_cast<const telegram_api::messageEntityItalic *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Italic, entity_italic->offset_, entity_italic->length_);
+ auto entity = static_cast<const telegram_api::messageEntityItalic *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Italic, entity->offset_, entity->length_);
+ break;
+ }
+ case telegram_api::messageEntityUnderline::ID: {
+ auto entity = static_cast<const telegram_api::messageEntityUnderline *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Underline, entity->offset_, entity->length_);
+ break;
+ }
+ case telegram_api::messageEntityStrike::ID: {
+ auto entity = static_cast<const telegram_api::messageEntityStrike *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Strikethrough, entity->offset_, entity->length_);
+ break;
+ }
+ case telegram_api::messageEntitySpoiler::ID: {
+ auto entity = static_cast<const telegram_api::messageEntitySpoiler *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Spoiler, entity->offset_, entity->length_);
+ break;
+ }
+ case telegram_api::messageEntityBlockquote::ID: {
+ auto entity = static_cast<const telegram_api::messageEntityBlockquote *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::BlockQuote, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityCode::ID: {
- auto entity_code = static_cast<const telegram_api::messageEntityCode *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Code, entity_code->offset_, entity_code->length_);
+ auto entity = static_cast<const telegram_api::messageEntityCode *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::Code, entity->offset_, entity->length_);
break;
}
case telegram_api::messageEntityPre::ID: {
- auto entity_pre = static_cast<telegram_api::messageEntityPre *>(entity.get());
- if (entity_pre->language_.empty()) {
- entities.emplace_back(MessageEntity::Type::Pre, entity_pre->offset_, entity_pre->length_);
+ auto entity = static_cast<telegram_api::messageEntityPre *>(server_entity.get());
+ if (entity->language_.empty()) {
+ entities.emplace_back(MessageEntity::Type::Pre, entity->offset_, entity->length_);
} else {
- entities.emplace_back(MessageEntity::Type::PreCode, entity_pre->offset_, entity_pre->length_,
- std::move(entity_pre->language_));
+ entities.emplace_back(MessageEntity::Type::PreCode, entity->offset_, entity->length_,
+ std::move(entity->language_));
}
break;
}
case telegram_api::messageEntityTextUrl::ID: {
- // TODO const telegram_api::messageEntityTextUrl *
- auto entity_text_url = static_cast<telegram_api::messageEntityTextUrl *>(entity.get());
- auto r_http_url = parse_url(entity_text_url->url_);
- if (r_http_url.is_error()) {
- LOG(ERROR) << "Wrong URL entity: \"" << entity_text_url->url_ << "\": " << r_http_url.error().message()
- << " from " << source;
+ auto entity = static_cast<const telegram_api::messageEntityTextUrl *>(server_entity.get());
+ auto r_url = LinkManager::check_link(entity->url_);
+ if (r_url.is_error()) {
+ LOG(ERROR) << "Entity " << r_url.error().message() << " from " << source;
continue;
}
- entities.emplace_back(MessageEntity::Type::TextUrl, entity_text_url->offset_, entity_text_url->length_,
- r_http_url.ok().get_url());
+ entities.emplace_back(MessageEntity::Type::TextUrl, entity->offset_, entity->length_, r_url.move_as_ok());
break;
}
case telegram_api::messageEntityMentionName::ID: {
- auto entity_mention_name = static_cast<const telegram_api::messageEntityMentionName *>(entity.get());
- UserId user_id(entity_mention_name->user_id_);
+ auto entity = static_cast<const telegram_api::messageEntityMentionName *>(server_entity.get());
+ UserId user_id(entity->user_id_);
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << user_id << " in MentionName from " << source;
continue;
}
- if (!contacts_manager->have_user(user_id)) {
+ if (contacts_manager == nullptr) {
LOG(ERROR) << "Receive unknown " << user_id << " in MentionName from " << source;
continue;
}
- if (!contacts_manager->have_input_user(user_id)) {
- LOG(ERROR) << "Receive unaccessible " << user_id << " in MentionName from " << source;
+ auto r_input_user = contacts_manager->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ LOG(ERROR) << "Receive wrong " << user_id << ": " << r_input_user.error() << " from " << source;
continue;
}
- entities.emplace_back(entity_mention_name->offset_, entity_mention_name->length_, user_id);
+ entities.emplace_back(entity->offset_, entity->length_, user_id);
+ break;
+ }
+ case telegram_api::messageEntityCustomEmoji::ID: {
+ auto entity = static_cast<const telegram_api::messageEntityCustomEmoji *>(server_entity.get());
+ entities.emplace_back(MessageEntity::Type::CustomEmoji, entity->offset_, entity->length_,
+ CustomEmojiId(entity->document_id_));
break;
}
default:
@@ -1877,11 +3582,15 @@ vector<MessageEntity> get_message_entities(const ContactsManager *contacts_manag
return entities;
}
-vector<MessageEntity> get_message_entities(vector<tl_object_ptr<secret_api::MessageEntity>> &&secret_entities) {
+vector<MessageEntity> get_message_entities(Td *td, vector<tl_object_ptr<secret_api::MessageEntity>> &&secret_entities,
+ bool is_premium, MultiPromiseActor &load_data_multipromise) {
+ constexpr size_t MAX_SECRET_CHAT_ENTITIES = 1000;
+ constexpr size_t MAX_CUSTOM_EMOJI_ENTITIES = 100;
vector<MessageEntity> entities;
entities.reserve(secret_entities.size());
- for (auto &entity : secret_entities) {
- switch (entity->get_id()) {
+ vector<CustomEmojiId> custom_emoji_ids;
+ for (auto &secret_entity : secret_entities) {
+ switch (secret_entity->get_id()) {
case secret_api::messageEntityUnknown::ID:
break;
case secret_api::messageEntityMention::ID:
@@ -1899,92 +3608,140 @@ vector<MessageEntity> get_message_entities(vector<tl_object_ptr<secret_api::Mess
case secret_api::messageEntityBotCommand::ID:
// skip all bot commands in secret chats
break;
+ case secret_api::messageEntityBankCard::ID:
+ // skip, will find it ourselves
+ break;
case secret_api::messageEntityUrl::ID: {
- auto entity_url = static_cast<const secret_api::messageEntityUrl *>(entity.get());
+ auto entity = static_cast<const secret_api::messageEntityUrl *>(secret_entity.get());
// TODO skip URL when find_urls will be better
- entities.emplace_back(MessageEntity::Type::Url, entity_url->offset_, entity_url->length_);
+ entities.emplace_back(MessageEntity::Type::Url, entity->offset_, entity->length_);
break;
}
case secret_api::messageEntityEmail::ID: {
- auto entity_email = static_cast<const secret_api::messageEntityEmail *>(entity.get());
+ auto entity = static_cast<const secret_api::messageEntityEmail *>(secret_entity.get());
// TODO skip emails when find_urls will be better
- entities.emplace_back(MessageEntity::Type::EmailAddress, entity_email->offset_, entity_email->length_);
+ entities.emplace_back(MessageEntity::Type::EmailAddress, entity->offset_, entity->length_);
break;
}
case secret_api::messageEntityBold::ID: {
- auto entity_bold = static_cast<const secret_api::messageEntityBold *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Bold, entity_bold->offset_, entity_bold->length_);
+ auto entity = static_cast<const secret_api::messageEntityBold *>(secret_entity.get());
+ entities.emplace_back(MessageEntity::Type::Bold, entity->offset_, entity->length_);
break;
}
case secret_api::messageEntityItalic::ID: {
- auto entity_italic = static_cast<const secret_api::messageEntityItalic *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Italic, entity_italic->offset_, entity_italic->length_);
+ auto entity = static_cast<const secret_api::messageEntityItalic *>(secret_entity.get());
+ entities.emplace_back(MessageEntity::Type::Italic, entity->offset_, entity->length_);
+ break;
+ }
+ case secret_api::messageEntityUnderline::ID: {
+ auto entity = static_cast<const secret_api::messageEntityUnderline *>(secret_entity.get());
+ entities.emplace_back(MessageEntity::Type::Underline, entity->offset_, entity->length_);
+ break;
+ }
+ case secret_api::messageEntityStrike::ID: {
+ auto entity = static_cast<const secret_api::messageEntityStrike *>(secret_entity.get());
+ entities.emplace_back(MessageEntity::Type::Strikethrough, entity->offset_, entity->length_);
+ break;
+ }
+ case secret_api::messageEntityBlockquote::ID: {
+ auto entity = static_cast<const secret_api::messageEntityBlockquote *>(secret_entity.get());
+ entities.emplace_back(MessageEntity::Type::BlockQuote, entity->offset_, entity->length_);
break;
}
case secret_api::messageEntityCode::ID: {
- auto entity_code = static_cast<const secret_api::messageEntityCode *>(entity.get());
- entities.emplace_back(MessageEntity::Type::Code, entity_code->offset_, entity_code->length_);
+ auto entity = static_cast<const secret_api::messageEntityCode *>(secret_entity.get());
+ entities.emplace_back(MessageEntity::Type::Code, entity->offset_, entity->length_);
break;
}
case secret_api::messageEntityPre::ID: {
- auto entity_pre = static_cast<secret_api::messageEntityPre *>(entity.get());
- if (!clean_input_string(entity_pre->language_)) {
- LOG(WARNING) << "Wrong language in entity: \"" << entity_pre->language_ << '"';
- entity_pre->language_.clear();
+ auto entity = static_cast<secret_api::messageEntityPre *>(secret_entity.get());
+ if (!clean_input_string(entity->language_)) {
+ LOG(WARNING) << "Wrong language in entity: \"" << entity->language_ << '"';
+ entity->language_.clear();
}
- if (entity_pre->language_.empty()) {
- entities.emplace_back(MessageEntity::Type::Pre, entity_pre->offset_, entity_pre->length_);
+ if (entity->language_.empty()) {
+ entities.emplace_back(MessageEntity::Type::Pre, entity->offset_, entity->length_);
} else {
- entities.emplace_back(MessageEntity::Type::PreCode, entity_pre->offset_, entity_pre->length_,
- std::move(entity_pre->language_));
+ entities.emplace_back(MessageEntity::Type::PreCode, entity->offset_, entity->length_,
+ std::move(entity->language_));
}
break;
}
case secret_api::messageEntityTextUrl::ID: {
- auto entity_text_url = static_cast<secret_api::messageEntityTextUrl *>(entity.get());
- if (!clean_input_string(entity_text_url->url_)) {
- LOG(WARNING) << "Wrong URL entity: \"" << entity_text_url->url_ << '"';
+ auto entity = static_cast<secret_api::messageEntityTextUrl *>(secret_entity.get());
+ if (!clean_input_string(entity->url_)) {
+ LOG(WARNING) << "Wrong URL entity: \"" << entity->url_ << '"';
continue;
}
- auto r_http_url = parse_url(entity_text_url->url_);
- if (r_http_url.is_error()) {
- LOG(WARNING) << "Wrong URL entity: \"" << entity_text_url->url_ << "\": " << r_http_url.error().message();
+ auto r_url = LinkManager::check_link(entity->url_);
+ if (r_url.is_error()) {
+ LOG(WARNING) << "Entity " << r_url.error().message();
continue;
}
- entities.emplace_back(MessageEntity::Type::TextUrl, entity_text_url->offset_, entity_text_url->length_,
- r_http_url.ok().get_url());
+ entities.emplace_back(MessageEntity::Type::TextUrl, entity->offset_, entity->length_, r_url.move_as_ok());
break;
}
case secret_api::messageEntityMentionName::ID:
// skip all name mentions in secret chats
break;
+ case secret_api::messageEntitySpoiler::ID: {
+ auto entity = static_cast<const secret_api::messageEntitySpoiler *>(secret_entity.get());
+ entities.emplace_back(MessageEntity::Type::Spoiler, entity->offset_, entity->length_);
+ break;
+ }
+ case secret_api::messageEntityCustomEmoji::ID: {
+ auto entity = static_cast<const secret_api::messageEntityCustomEmoji *>(secret_entity.get());
+ CustomEmojiId custom_emoji_id(entity->document_id_);
+ if (is_premium || !td->stickers_manager_->is_premium_custom_emoji(custom_emoji_id, false)) {
+ if (custom_emoji_ids.size() < MAX_CUSTOM_EMOJI_ENTITIES) {
+ entities.emplace_back(MessageEntity::Type::CustomEmoji, entity->offset_, entity->length_, custom_emoji_id);
+ custom_emoji_ids.push_back(custom_emoji_id);
+ }
+ }
+ break;
+ }
default:
UNREACHABLE();
}
+
+ if (entities.size() >= MAX_SECRET_CHAT_ENTITIES) {
+ break;
+ }
+ }
+
+ if (!custom_emoji_ids.empty() && !is_premium) {
+ // preload custom emoji to check that they aren't premium
+ td->stickers_manager_->get_custom_emoji_stickers(
+ std::move(custom_emoji_ids), true,
+ PromiseCreator::lambda(
+ [promise = load_data_multipromise.get_promise()](td_api::object_ptr<td_api::stickers> result) mutable {
+ promise.set_value(Unit());
+ }));
}
+
return entities;
}
-Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool allow_empty, bool skip_new_entities,
- bool skip_bot_commands, bool for_draft) {
- if (!check_utf8(text)) {
- return Status::Error(400, "Strings must be encoded in UTF-8");
- }
+// like clean_input_string but also fixes entities
+// entities must be sorted, can be nested, but must not intersect each other
+static Result<string> clean_input_string_with_entities(const string &text, vector<MessageEntity> &entities) {
+ check_is_sorted(entities);
- fix_entities(entities);
+ struct EntityInfo {
+ MessageEntity *entity;
+ int32 utf16_skipped_before;
- bool in_entity = false;
- bool has_non_space_in_entity = false;
+ EntityInfo(MessageEntity *entity, int32 utf16_skipped_before)
+ : entity(entity), utf16_skipped_before(utf16_skipped_before) {
+ }
+ };
+ vector<EntityInfo> nested_entities_stack;
size_t current_entity = 0;
- int32 skipped_before_current_entity = 0;
- size_t left_entities = 0; // will remove all entities containing spaces only
int32 utf16_offset = 0;
int32 utf16_skipped = 0;
size_t text_size = text.size();
- size_t last_non_space_pos = text_size + 1;
- int32 last_non_space_utf16_offset = 0;
string result;
result.reserve(text_size);
@@ -1992,36 +3749,30 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
auto c = static_cast<unsigned char>(text[pos]);
bool is_utf8_character_begin = is_utf8_character_first_code_unit(c);
if (is_utf8_character_begin) {
- if (in_entity) {
- CHECK(current_entity < entities.size());
- if (utf16_offset >= entities[current_entity].offset + entities[current_entity].length) {
- if (utf16_offset != entities[current_entity].offset + entities[current_entity].length) {
- CHECK(utf16_offset == entities[current_entity].offset + entities[current_entity].length + 1);
- return Status::Error(16, PSLICE() << "Entity beginning at UTF-16 offset " << entities[current_entity].offset
- << " ends in a middle of a UTF-16 symbol at byte offset " << pos);
- }
- entities[current_entity].offset -= skipped_before_current_entity;
- entities[current_entity].length -= utf16_skipped - skipped_before_current_entity;
- in_entity = false;
-
- if (has_non_space_in_entity) {
- // TODO check entities for validness, for example, that mentions, hashtags, cashtags and URLs are valid
- if (current_entity != left_entities) {
- entities[left_entities] = std::move(entities[current_entity]);
- }
- left_entities++;
- }
- current_entity++;
+ while (!nested_entities_stack.empty()) {
+ auto *entity = nested_entities_stack.back().entity;
+ auto entity_end = entity->offset + entity->length;
+ if (utf16_offset < entity_end) {
+ break;
}
+
+ if (utf16_offset != entity_end) {
+ CHECK(utf16_offset == entity_end + 1);
+ return Status::Error(400, PSLICE() << "Entity beginning at UTF-16 offset " << entity->offset
+ << " ends in a middle of a UTF-16 symbol at byte offset " << pos);
+ }
+
+ auto skipped_before_current_entity = nested_entities_stack.back().utf16_skipped_before;
+ entity->offset -= skipped_before_current_entity;
+ entity->length -= utf16_skipped - skipped_before_current_entity;
+ nested_entities_stack.pop_back();
}
- if (!in_entity && current_entity < entities.size() && utf16_offset >= entities[current_entity].offset) {
+ while (current_entity < entities.size() && utf16_offset >= entities[current_entity].offset) {
if (utf16_offset != entities[current_entity].offset) {
CHECK(utf16_offset == entities[current_entity].offset + 1);
- return Status::Error(16, PSLICE() << "Entity begins in a middle of a UTF-16 symbol at byte offset " << pos);
+ return Status::Error(400, PSLICE() << "Entity begins in a middle of a UTF-16 symbol at byte offset " << pos);
}
- in_entity = true;
- has_non_space_in_entity = false;
- skipped_before_current_entity = utf16_skipped;
+ nested_entities_stack.emplace_back(&entities[current_entity++], utf16_skipped);
}
}
if (pos == text_size) {
@@ -2073,10 +3824,10 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
break;
default:
if (is_utf8_character_begin) {
- utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogaite pair
+ utf16_offset += 1 + (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
}
if (c == 0xe2 && pos + 2 < text_size) {
- unsigned char next = static_cast<unsigned char>(text[pos + 1]);
+ auto next = static_cast<unsigned char>(text[pos + 1]);
if (next == 0x80) {
next = static_cast<unsigned char>(text[pos + 2]);
if (0xa8 <= next && next <= 0xae) {
@@ -2087,7 +3838,7 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
}
}
if (c == 0xcc && pos + 1 < text_size) {
- unsigned char next = static_cast<unsigned char>(text[pos + 1]);
+ auto next = static_cast<unsigned char>(text[pos + 1]);
// remove vertical lines
if (next == 0xb3 || next == 0xbf || next == 0x8a) {
pos++;
@@ -2097,48 +3848,366 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
}
result.push_back(text[pos]);
+ break;
+ }
+ }
+
+ if (current_entity != entities.size()) {
+ return Status::Error(400, PSLICE() << "Entity begins after the end of the text at UTF-16 offset "
+ << entities[current_entity].offset);
+ }
+ if (!nested_entities_stack.empty()) {
+ auto *entity = nested_entities_stack.back().entity;
+ return Status::Error(400, PSLICE() << "Entity beginning at UTF-16 offset " << entity->offset
+ << " ends after the end of the text at UTF-16 offset "
+ << entity->offset + entity->length);
+ }
+
+ replace_offending_characters(result);
+
+ return result;
+}
+
+// removes entities containing whitespaces only
+// entities must be sorted by offset and length, but not necessary by type
+// returns {last_non_whitespace_pos, last_non_whitespace_utf16_offset}
+static std::pair<size_t, int32> remove_invalid_entities(const string &text, vector<MessageEntity> &entities) {
+ if (entities.empty()) {
+ // fast path
+ for (size_t pos = 0; pos < text.size(); pos++) {
+ auto back_pos = text.size() - pos - 1;
+ auto c = text[back_pos];
+ if (c != '\n' && c != ' ') {
+ return {back_pos, 0 /*unused*/};
+ }
+ }
+
+ return {text.size(), -1};
+ }
+
+ // check_is_sorted(entities);
+ vector<MessageEntity *> nested_entities_stack;
+ size_t current_entity = 0;
+
+ size_t last_non_whitespace_pos = text.size();
+
+ int32 utf16_offset = 0;
+ int32 last_non_whitespace_utf16_offset = -1;
+
+ remove_empty_entities(entities);
- if (c != '\n') {
- has_non_space_in_entity = true;
- last_non_space_pos = result.size();
- last_non_space_utf16_offset = utf16_offset - utf16_skipped;
+ for (size_t pos = 0; pos <= text.size(); pos++) {
+ while (!nested_entities_stack.empty()) {
+ auto *entity = nested_entities_stack.back();
+ auto entity_end = entity->offset + entity->length;
+ if (utf16_offset < entity_end) {
+ break;
+ }
+
+ if (last_non_whitespace_utf16_offset >= entity->offset || is_hidden_data_entity(entity->type)) {
+ // keep entity
+ // TODO check entity for validness, for example, that mentions, hashtags, cashtags and URLs are valid
+ } else {
+ entity->length = 0;
+ }
+
+ nested_entities_stack.pop_back();
+ }
+ while (current_entity < entities.size() && utf16_offset >= entities[current_entity].offset) {
+ nested_entities_stack.push_back(&entities[current_entity++]);
+ }
+
+ if (pos == text.size()) {
+ break;
+ }
+
+ if (!nested_entities_stack.empty() && nested_entities_stack.back()->offset == utf16_offset &&
+ (text[pos] == '\n' || text[pos] == ' ')) {
+ // entities was fixed, so there can't be more than one splittable entity of each type, one blockquote and
+ // one continuous entity for the given offset
+ for (size_t i = nested_entities_stack.size(); i > 0; i--) {
+ auto *entity = nested_entities_stack[i - 1];
+ if (entity->offset != utf16_offset || is_hidden_data_entity(entity->type)) {
+ break;
+ }
+ entity->offset++;
+ entity->length--;
+ if (entity->length == 0) {
+ CHECK(i == nested_entities_stack.size());
+ nested_entities_stack.pop_back();
+ }
+ }
+ }
+
+ auto c = static_cast<unsigned char>(text[pos]);
+ switch (c) {
+ case '\n':
+ case 32:
+ break;
+ default:
+ while (!is_utf8_character_first_code_unit(static_cast<unsigned char>(text[pos + 1]))) {
+ pos++;
}
+ utf16_offset += (c >= 0xf0); // >= 4 bytes in symbol => surrogate pair
+ last_non_whitespace_pos = pos;
+ last_non_whitespace_utf16_offset = utf16_offset;
break;
}
+
+ utf16_offset++;
}
- entities.erase(entities.begin() + left_entities, entities.end());
+ CHECK(nested_entities_stack.empty());
+ CHECK(current_entity == entities.size());
+
+ remove_empty_entities(entities);
+
+ return {last_non_whitespace_pos, last_non_whitespace_utf16_offset};
+}
+
+// enitities must contain only splittable entities
+static void split_entities(vector<MessageEntity> &entities, const vector<MessageEntity> &other_entities) {
+ check_is_sorted(entities);
+ check_is_sorted(other_entities);
+
+ int32 begin_pos[SPLITTABLE_ENTITY_TYPE_COUNT] = {};
+ int32 end_pos[SPLITTABLE_ENTITY_TYPE_COUNT] = {};
+ auto it = entities.begin();
+ vector<MessageEntity> result;
+ auto add_entities = [&](int32 end_offset) {
+ auto flush_entities = [&](int32 offset) {
+ for (auto type : {MessageEntity::Type::Bold, MessageEntity::Type::Italic, MessageEntity::Type::Underline,
+ MessageEntity::Type::Strikethrough, MessageEntity::Type::Spoiler}) {
+ auto index = get_splittable_entity_type_index(type);
+ if (end_pos[index] != 0 && begin_pos[index] < offset) {
+ if (end_pos[index] <= offset) {
+ result.emplace_back(type, begin_pos[index], end_pos[index] - begin_pos[index]);
+ begin_pos[index] = 0;
+ end_pos[index] = 0;
+ } else {
+ result.emplace_back(type, begin_pos[index], offset - begin_pos[index]);
+ begin_pos[index] = offset;
+ }
+ }
+ }
+ };
+
+ while (it != entities.end()) {
+ if (it->offset >= end_offset) {
+ break;
+ }
+ CHECK(is_splittable_entity(it->type));
+ auto index = get_splittable_entity_type_index(it->type);
+ if (it->offset <= end_pos[index] && end_pos[index] != 0) {
+ if (it->offset + it->length > end_pos[index]) {
+ end_pos[index] = it->offset + it->length;
+ }
+ } else {
+ flush_entities(it->offset);
+ begin_pos[index] = it->offset;
+ end_pos[index] = it->offset + it->length;
+ }
+ ++it;
+ }
+ flush_entities(end_offset);
+ };
+
+ vector<const MessageEntity *> nested_entities_stack;
+ auto add_offset = [&](int32 offset) {
+ while (!nested_entities_stack.empty() &&
+ offset >= nested_entities_stack.back()->offset + nested_entities_stack.back()->length) {
+ // remove non-intersecting entities from the stack
+ auto old_size = result.size();
+ add_entities(nested_entities_stack.back()->offset + nested_entities_stack.back()->length);
+ if (is_pre_entity(nested_entities_stack.back()->type)) {
+ result.resize(old_size);
+ }
+ nested_entities_stack.pop_back();
+ }
+
+ add_entities(offset);
+ };
+ for (auto &other_entity : other_entities) {
+ add_offset(other_entity.offset);
+ nested_entities_stack.push_back(&other_entity);
+ }
+ add_offset(std::numeric_limits<int32>::max());
+
+ entities = std::move(result);
+
+ // entities are sorted only by offset now, re-sort if needed
+ sort_entities(entities);
+}
+
+static vector<MessageEntity> resplit_entities(vector<MessageEntity> &&splittable_entities,
+ vector<MessageEntity> &&entities) {
+ if (!splittable_entities.empty()) {
+ split_entities(splittable_entities, entities); // can merge some entities
+
+ if (entities.empty()) {
+ return std::move(splittable_entities);
+ }
+
+ combine(entities, std::move(splittable_entities));
+ sort_entities(entities);
+ }
+ return std::move(entities);
+}
+
+static void fix_entities(vector<MessageEntity> &entities) {
+ sort_entities(entities);
+
+ if (are_entities_valid(entities)) {
+ // fast path
+ return;
+ }
+
+ vector<MessageEntity> continuous_entities;
+ vector<MessageEntity> blockquote_entities;
+ vector<MessageEntity> splittable_entities;
+ for (auto &entity : entities) {
+ if (is_splittable_entity(entity.type)) {
+ splittable_entities.push_back(std::move(entity));
+ } else if (is_blockquote_entity(entity.type)) {
+ blockquote_entities.push_back(std::move(entity));
+ } else {
+ continuous_entities.push_back(std::move(entity));
+ }
+ }
+ remove_intersecting_entities(continuous_entities); // continuous entities can't intersect each other
+
+ if (!blockquote_entities.empty()) {
+ remove_intersecting_entities(blockquote_entities); // blockquote entities can't intersect each other
+
+ // blockquote entities can contain continuous entities, but can't intersect them in the other ways
+ remove_entities_intersecting_blockquote(continuous_entities, blockquote_entities);
+
+ combine(continuous_entities, std::move(blockquote_entities));
+ sort_entities(continuous_entities);
+ }
+
+ // must be called once to not merge some adjacent entities
+ entities = resplit_entities(std::move(splittable_entities), std::move(continuous_entities));
+ check_is_sorted(entities);
+}
+
+static void merge_new_entities(vector<MessageEntity> &entities, vector<MessageEntity> new_entities) {
+ check_is_sorted(entities);
+ if (new_entities.empty()) {
+ // fast path
+ return;
+ }
+
+ check_non_intersecting(new_entities);
- if (last_non_space_pos == text_size + 1) {
+ vector<MessageEntity> continuous_entities;
+ vector<MessageEntity> blockquote_entities;
+ vector<MessageEntity> splittable_entities;
+ for (auto &entity : entities) {
+ if (is_splittable_entity(entity.type)) {
+ splittable_entities.push_back(std::move(entity));
+ } else if (is_blockquote_entity(entity.type)) {
+ blockquote_entities.push_back(std::move(entity));
+ } else {
+ continuous_entities.push_back(std::move(entity));
+ }
+ }
+
+ remove_entities_intersecting_blockquote(new_entities, blockquote_entities);
+
+ // merge before combining with blockquote entities
+ continuous_entities = merge_entities(std::move(continuous_entities), std::move(new_entities));
+
+ if (!blockquote_entities.empty()) {
+ combine(continuous_entities, std::move(blockquote_entities));
+ sort_entities(continuous_entities);
+ }
+
+ // must be called once to not merge some adjacent entities
+ entities = resplit_entities(std::move(splittable_entities), std::move(continuous_entities));
+ check_is_sorted(entities);
+}
+
+Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool allow_empty, bool skip_new_entities,
+ bool skip_bot_commands, bool skip_media_timestamps, bool for_draft) {
+ string result;
+ if (entities.empty()) {
+ // fast path
+ if (!clean_input_string(text)) {
+ return Status::Error(400, "Strings must be encoded in UTF-8");
+ }
+ result = std::move(text);
+ } else {
+ if (!check_utf8(text)) {
+ return Status::Error(400, "Strings must be encoded in UTF-8");
+ }
+
+ for (auto &entity : entities) {
+ if (entity.offset < 0 || entity.offset > 1000000) {
+ return Status::Error(400, PSLICE() << "Receive an entity with incorrect offset " << entity.offset);
+ }
+ if (entity.length < 0 || entity.length > 1000000) {
+ return Status::Error(400, PSLICE() << "Receive an entity with incorrect length " << entity.length);
+ }
+ }
+ remove_empty_entities(entities);
+
+ fix_entities(entities);
+
+ TRY_RESULT_ASSIGN(result, clean_input_string_with_entities(text, entities));
+ }
+
+ // now entities are still sorted by offset and length, but not type,
+ // because some characters could be deleted and after that some entities begin to share a common end
+
+ size_t last_non_whitespace_pos;
+ int32 last_non_whitespace_utf16_offset;
+ std::tie(last_non_whitespace_pos, last_non_whitespace_utf16_offset) = remove_invalid_entities(result, entities);
+ if (last_non_whitespace_utf16_offset == -1) {
if (allow_empty) {
text.clear();
entities.clear();
return Status::OK();
}
- return Status::Error(3, "Message must be non-empty");
+ return Status::Error(400, "Message must be non-empty");
}
+ // re-fix entities if needed after removal of some characters
+ // the sort order can be incorrect by type
+ // some splittable entities may be needed to be concatenated
+ fix_entities(entities);
+
if (for_draft) {
text = std::move(result);
} else {
// rtrim
- result.resize(last_non_space_pos);
+ CHECK(last_non_whitespace_pos < result.size());
+ result.resize(last_non_whitespace_pos + 1);
+ while (!entities.empty() && entities.back().offset > last_non_whitespace_utf16_offset) {
+ CHECK(is_hidden_data_entity(entities.back().type));
+ entities.pop_back();
+ }
+ bool need_sort = false;
for (auto &entity : entities) {
- if (entity.offset + entity.length > last_non_space_utf16_offset) {
- entity.length = last_non_space_utf16_offset - entity.offset;
+ if (entity.offset + entity.length > last_non_whitespace_utf16_offset + 1) {
+ entity.length = last_non_whitespace_utf16_offset + 1 - entity.offset;
+ need_sort = true;
CHECK(entity.length > 0);
}
}
+ if (need_sort) {
+ sort_entities(entities);
+ }
// ltrim
- size_t first_non_spaces_pos = 0;
+ size_t first_non_whitespaces_pos = 0;
size_t first_entity_begin_pos = entities.empty() ? result.size() : entities[0].offset;
- while (first_non_spaces_pos < first_entity_begin_pos &&
- (result[first_non_spaces_pos] == ' ' || result[first_non_spaces_pos] == '\n')) {
- first_non_spaces_pos++;
+ while (first_non_whitespaces_pos < first_entity_begin_pos &&
+ (result[first_non_whitespaces_pos] == ' ' || result[first_non_whitespaces_pos] == '\n')) {
+ first_non_whitespaces_pos++;
}
- if (first_non_spaces_pos > 0) {
- int32 offset = narrow_cast<int32>(first_non_spaces_pos);
- text = result.substr(first_non_spaces_pos);
+ if (first_non_whitespaces_pos > 0) {
+ auto offset = narrow_cast<int32>(first_non_whitespaces_pos);
+ text = result.substr(first_non_whitespaces_pos);
for (auto &entity : entities) {
entity.offset -= offset;
CHECK(entity.offset >= 0);
@@ -2147,9 +4216,10 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
text = std::move(result);
}
}
+ LOG_CHECK(check_utf8(text)) << text;
if (!allow_empty && is_empty_string(text)) {
- return Status::Error(3, "Message must be non-empty");
+ return Status::Error(400, "Message must be non-empty");
}
constexpr size_t LENGTH_LIMIT = 35000; // server side limit
@@ -2159,25 +4229,286 @@ Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool al
new_size--;
}
text.resize(new_size);
- while (!entities.empty() && entities.back().offset + entities.back().length > 8192) {
- entities.pop_back();
- }
+
+ td::remove_if(entities, [text_utf16_length = text_length(text)](const auto &entity) {
+ return entity.offset + entity.length > text_utf16_length;
+ });
}
if (!skip_new_entities) {
- entities = merge_entities(std::move(entities), find_entities(text, skip_bot_commands));
+ merge_new_entities(entities, find_entities(text, skip_bot_commands, skip_media_timestamps));
+ } else if (!skip_media_timestamps) {
+ merge_new_entities(entities, find_media_timestamp_entities(text));
+ }
+
+ // new whitespace-only entities could be added after splitting of entities
+ remove_invalid_entities(text, entities);
+
+ return Status::OK();
+}
+
+FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text,
+ vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
+ bool skip_new_entities, bool skip_media_timestamps, int32 send_date, bool from_album,
+ const char *source) {
+ auto entities = get_message_entities(contacts_manager, std::move(server_entities), source);
+ auto debug_message_text = message_text;
+ auto debug_entities = entities;
+ auto status = fix_formatted_text(message_text, entities, true, skip_new_entities, true, skip_media_timestamps, false);
+ if (status.is_error()) {
+ // message entities in media albums can be wrong because of a long time ago fixed server-side bug
+ if (!from_album && (send_date == 0 || send_date > 1600340000)) { // approximate fix date
+ LOG(ERROR) << "Receive error " << status << " while parsing message text from " << source << " sent at "
+ << send_date << " with content \"" << debug_message_text << "\" -> \"" << message_text
+ << "\" with entities " << format::as_array(debug_entities) << " -> " << format::as_array(entities);
+ }
+ if (!clean_input_string(message_text)) {
+ message_text.clear();
+ }
+ entities = find_entities(message_text, false, skip_media_timestamps);
+ }
+ return FormattedText{std::move(message_text), std::move(entities)};
+}
+
+td_api::object_ptr<td_api::formattedText> extract_input_caption(
+ tl_object_ptr<td_api::InputMessageContent> &input_message_content) {
+ switch (input_message_content->get_id()) {
+ case td_api::inputMessageAnimation::ID: {
+ auto input_animation = static_cast<td_api::inputMessageAnimation *>(input_message_content.get());
+ return std::move(input_animation->caption_);
+ }
+ case td_api::inputMessageAudio::ID: {
+ auto input_audio = static_cast<td_api::inputMessageAudio *>(input_message_content.get());
+ return std::move(input_audio->caption_);
+ }
+ case td_api::inputMessageDocument::ID: {
+ auto input_document = static_cast<td_api::inputMessageDocument *>(input_message_content.get());
+ return std::move(input_document->caption_);
+ }
+ case td_api::inputMessagePhoto::ID: {
+ auto input_photo = static_cast<td_api::inputMessagePhoto *>(input_message_content.get());
+ return std::move(input_photo->caption_);
+ }
+ case td_api::inputMessageVideo::ID: {
+ auto input_video = static_cast<td_api::inputMessageVideo *>(input_message_content.get());
+ return std::move(input_video->caption_);
+ }
+ case td_api::inputMessageVoiceNote::ID: {
+ auto input_voice_note = static_cast<td_api::inputMessageVoiceNote *>(input_message_content.get());
+ return std::move(input_voice_note->caption_);
+ }
+ default:
+ return nullptr;
}
+}
- for (auto it = entities.begin(); it != entities.end(); ++it) {
- CHECK(it->length > 0);
- if (it + 1 != entities.end()) {
- CHECK(it->offset + it->length <= (it + 1)->offset);
+Result<FormattedText> get_formatted_text(const Td *td, DialogId dialog_id,
+ td_api::object_ptr<td_api::formattedText> &&text, bool is_bot,
+ bool allow_empty, bool skip_media_timestamps, bool for_draft) {
+ if (text == nullptr) {
+ if (allow_empty) {
+ return FormattedText();
}
+
+ return Status::Error(400, "Text must be non-empty");
}
- // TODO MAX_MESSAGE_LENGTH and MAX_CAPTION_LENGTH
+ TRY_RESULT(entities, get_message_entities(td->contacts_manager_.get(), std::move(text->entities_)));
+ auto need_skip_bot_commands = need_always_skip_bot_commands(td->contacts_manager_.get(), dialog_id, is_bot);
+ bool parse_markdown = td->option_manager_->get_option_boolean("always_parse_markdown");
+ TRY_STATUS(fix_formatted_text(text->text_, entities, allow_empty, parse_markdown, need_skip_bot_commands,
+ is_bot || skip_media_timestamps || parse_markdown, for_draft));
+
+ FormattedText result{std::move(text->text_), std::move(entities)};
+ if (parse_markdown) {
+ result = parse_markdown_v3(std::move(result));
+ fix_formatted_text(result.text, result.entities, allow_empty, false, need_skip_bot_commands,
+ is_bot || skip_media_timestamps, for_draft)
+ .ensure();
+ }
+ remove_unallowed_entities(td, result, dialog_id);
+ return std::move(result);
+}
- return Status::OK();
+void add_formatted_text_dependencies(Dependencies &dependencies, const FormattedText *text) {
+ if (text == nullptr) {
+ return;
+ }
+ for (auto &entity : text->entities) {
+ if (entity.user_id.is_valid()) {
+ dependencies.add(entity.user_id);
+ }
+ }
+}
+
+bool has_media_timestamps(const FormattedText *text, int32 min_media_timestamp, int32 max_media_timestamp) {
+ if (text == nullptr) {
+ return false;
+ }
+ for (auto &entity : text->entities) {
+ if (entity.type == MessageEntity::Type::MediaTimestamp && min_media_timestamp <= entity.media_timestamp &&
+ entity.media_timestamp <= max_media_timestamp) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool has_bot_commands(const FormattedText *text) {
+ if (text == nullptr) {
+ return false;
+ }
+ for (auto &entity : text->entities) {
+ if (entity.type == MessageEntity::Type::BotCommand) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot) {
+ if (!dialog_id.is_valid()) {
+ return true;
+ }
+ if (is_bot) {
+ return false;
+ }
+
+ switch (dialog_id.get_type()) {
+ case DialogType::User: {
+ auto user_id = dialog_id.get_user_id();
+ return user_id == ContactsManager::get_replies_bot_user_id() || !contacts_manager->is_user_bot(user_id);
+ }
+ case DialogType::SecretChat: {
+ auto user_id = contacts_manager->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ return !user_id.is_valid() || !contacts_manager->is_user_bot(user_id);
+ }
+ case DialogType::Chat:
+ case DialogType::Channel:
+ case DialogType::None:
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(const ContactsManager *contacts_manager,
+ const vector<MessageEntity> &entities,
+ const char *source) {
+ vector<tl_object_ptr<telegram_api::MessageEntity>> result;
+ vector<MessageEntity> splittable_entities;
+ for (auto &entity : entities) {
+ if (!is_user_entity(entity.type)) {
+ continue;
+ }
+ if (is_splittable_entity(entity.type)) {
+ splittable_entities.push_back(entity);
+ continue;
+ }
+ switch (entity.type) {
+ case MessageEntity::Type::BlockQuote:
+ result.push_back(make_tl_object<telegram_api::messageEntityBlockquote>(entity.offset, entity.length));
+ break;
+ case MessageEntity::Type::Code:
+ result.push_back(make_tl_object<telegram_api::messageEntityCode>(entity.offset, entity.length));
+ break;
+ case MessageEntity::Type::Pre:
+ result.push_back(make_tl_object<telegram_api::messageEntityPre>(entity.offset, entity.length, string()));
+ break;
+ case MessageEntity::Type::PreCode:
+ result.push_back(make_tl_object<telegram_api::messageEntityPre>(entity.offset, entity.length, entity.argument));
+ break;
+ case MessageEntity::Type::TextUrl:
+ result.push_back(
+ make_tl_object<telegram_api::messageEntityTextUrl>(entity.offset, entity.length, entity.argument));
+ break;
+ case MessageEntity::Type::MentionName: {
+ auto r_input_user = contacts_manager->get_input_user(entity.user_id);
+ LOG_CHECK(r_input_user.is_ok()) << source << ' ' << entity.user_id << ' ' << r_input_user.error();
+ result.push_back(make_tl_object<telegram_api::inputMessageEntityMentionName>(entity.offset, entity.length,
+ r_input_user.move_as_ok()));
+ break;
+ }
+ case MessageEntity::Type::CustomEmoji:
+ result.push_back(make_tl_object<telegram_api::messageEntityCustomEmoji>(entity.offset, entity.length,
+ entity.custom_emoji_id.get()));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+ split_entities(splittable_entities, vector<MessageEntity>());
+ for (auto &entity : splittable_entities) {
+ switch (entity.type) {
+ case MessageEntity::Type::Bold:
+ result.push_back(make_tl_object<telegram_api::messageEntityBold>(entity.offset, entity.length));
+ break;
+ case MessageEntity::Type::Italic:
+ result.push_back(make_tl_object<telegram_api::messageEntityItalic>(entity.offset, entity.length));
+ break;
+ case MessageEntity::Type::Underline:
+ result.push_back(make_tl_object<telegram_api::messageEntityUnderline>(entity.offset, entity.length));
+ break;
+ case MessageEntity::Type::Strikethrough:
+ result.push_back(make_tl_object<telegram_api::messageEntityStrike>(entity.offset, entity.length));
+ break;
+ case MessageEntity::Type::Spoiler:
+ result.push_back(make_tl_object<telegram_api::messageEntitySpoiler>(entity.offset, entity.length));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ return result;
+}
+
+vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(const ContactsManager *contacts_manager,
+ const FormattedText *text,
+ const char *source) {
+ if (text != nullptr && !text->entities.empty()) {
+ return get_input_message_entities(contacts_manager, text->entities, source);
+ }
+ return {};
+}
+
+void remove_premium_custom_emoji_entities(const Td *td, vector<MessageEntity> &entities, bool remove_unknown) {
+ td::remove_if(entities, [&](const MessageEntity &entity) {
+ return entity.type == MessageEntity::Type::CustomEmoji &&
+ td->stickers_manager_->is_premium_custom_emoji(entity.custom_emoji_id, remove_unknown);
+ });
+}
+
+void remove_unallowed_entities(const Td *td, FormattedText &text, DialogId dialog_id) {
+ if (text.entities.empty()) {
+ return;
+ }
+
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ auto layer = td->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
+ td::remove_if(text.entities, [layer](const MessageEntity &entity) {
+ if (layer < static_cast<int32>(SecretChatLayer::NewEntities) &&
+ (entity.type == MessageEntity::Type::Underline || entity.type == MessageEntity::Type::Strikethrough ||
+ entity.type == MessageEntity::Type::BlockQuote)) {
+ return true;
+ }
+ if (layer < static_cast<int32>(SecretChatLayer::SpoilerAndCustomEmojiEntities) &&
+ (entity.type == MessageEntity::Type::Spoiler || entity.type == MessageEntity::Type::CustomEmoji)) {
+ return true;
+ }
+ return false;
+ });
+
+ if (layer < static_cast<int32>(SecretChatLayer::NewEntities)) {
+ sort_entities(text.entities);
+ remove_intersecting_entities(text.entities);
+ }
+ }
+ if (!td->option_manager_->get_option_boolean("is_premium") &&
+ dialog_id != DialogId(td->contacts_manager_->get_my_id())) {
+ remove_premium_custom_emoji_entities(td, text.entities, false);
+ }
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.h b/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.h
index 871af94f7f..482c0626fb 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.h
@@ -1,35 +1,36 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/secret_api.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/utils/common.h"
+#include "td/utils/FlatHashSet.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
-#include "td/utils/tl_helpers.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include <tuple>
-#include <unordered_set>
#include <utility>
namespace td {
class ContactsManager;
+class Dependencies;
+class MultiPromiseActor;
+class Td;
class MessageEntity {
- tl_object_ptr<td_api::TextEntityType> get_text_entity_type_object() const;
-
public:
+ // don't forget to update get_type_priority()
enum class Type : int32 {
Mention,
Hashtag,
@@ -44,68 +45,79 @@ class MessageEntity {
TextUrl,
MentionName,
Cashtag,
- PhoneNumber
+ PhoneNumber,
+ Underline,
+ Strikethrough,
+ BlockQuote,
+ BankCardNumber,
+ MediaTimestamp,
+ Spoiler,
+ CustomEmoji,
+ Size
};
- Type type;
- int32 offset;
- int32 length;
+ Type type = Type::Size;
+ int32 offset = -1;
+ int32 length = -1;
+ int32 media_timestamp = -1;
string argument;
UserId user_id;
+ CustomEmojiId custom_emoji_id;
MessageEntity() = default;
MessageEntity(Type type, int32 offset, int32 length, string argument = "")
- : type(type), offset(offset), length(length), argument(std::move(argument)), user_id() {
+ : type(type), offset(offset), length(length), argument(std::move(argument)) {
}
MessageEntity(int32 offset, int32 length, UserId user_id)
- : type(Type::MentionName), offset(offset), length(length), argument(), user_id(user_id) {
+ : type(Type::MentionName), offset(offset), length(length), user_id(user_id) {
+ }
+ MessageEntity(Type type, int32 offset, int32 length, int32 media_timestamp)
+ : type(type), offset(offset), length(length), media_timestamp(media_timestamp) {
+ CHECK(type == Type::MediaTimestamp);
+ }
+ MessageEntity(Type type, int32 offset, int32 length, CustomEmojiId custom_emoji_id)
+ : type(type), offset(offset), length(length), custom_emoji_id(custom_emoji_id) {
+ CHECK(type == Type::CustomEmoji);
}
tl_object_ptr<td_api::textEntity> get_text_entity_object() const;
bool operator==(const MessageEntity &other) const {
- return offset == other.offset && length == other.length && type == other.type && argument == other.argument &&
- user_id == other.user_id;
+ return offset == other.offset && length == other.length && type == other.type &&
+ media_timestamp == other.media_timestamp && argument == other.argument && user_id == other.user_id &&
+ custom_emoji_id == other.custom_emoji_id;
}
bool operator<(const MessageEntity &other) const {
- return std::tie(offset, length, type) < std::tie(other.offset, other.length, other.type);
+ if (offset != other.offset) {
+ return offset < other.offset;
+ }
+ if (length != other.length) {
+ return length > other.length;
+ }
+ auto priority = get_type_priority(type);
+ auto other_priority = get_type_priority(other.type);
+ return priority < other_priority;
}
bool operator!=(const MessageEntity &rhs) const {
return !(*this == rhs);
}
- // TODO move to hpp
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(type, storer);
- store(offset, storer);
- store(length, storer);
- if (type == Type::PreCode || type == Type::TextUrl) {
- store(argument, storer);
- }
- if (type == Type::MentionName) {
- store(user_id, storer);
- }
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- parse(type, parser);
- parse(offset, parser);
- parse(length, parser);
- if (type == Type::PreCode || type == Type::TextUrl) {
- parse(argument, parser);
- }
- if (type == Type::MentionName) {
- parse(user_id, parser);
- }
- }
+ void parse(ParserT &parser);
+
+ private:
+ tl_object_ptr<td_api::TextEntityType> get_text_entity_type_object() const;
+
+ static int get_type_priority(Type type);
};
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity::Type &message_entity_type);
+
StringBuilder &operator<<(StringBuilder &string_builder, const MessageEntity &message_entity);
struct FormattedText {
@@ -113,18 +125,14 @@ struct FormattedText {
vector<MessageEntity> entities;
template <class StorerT>
- void store(StorerT &storer) const {
- td::store(text, storer);
- td::store(entities, storer);
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- td::parse(text, parser);
- td::parse(entities, parser);
- }
+ void parse(ParserT &parser);
};
+StringBuilder &operator<<(StringBuilder &string_builder, const FormattedText &text);
+
inline bool operator==(const FormattedText &lhs, const FormattedText &rhs) {
return lhs.text == rhs.text && lhs.entities == rhs.entities;
}
@@ -133,44 +141,88 @@ inline bool operator!=(const FormattedText &lhs, const FormattedText &rhs) {
return !(lhs == rhs);
}
-const std::unordered_set<Slice, SliceHash> &get_valid_short_usernames();
+const FlatHashSet<Slice, SliceHash> &get_valid_short_usernames();
Result<vector<MessageEntity>> get_message_entities(const ContactsManager *contacts_manager,
- const vector<tl_object_ptr<td_api::textEntity>> &input_entities);
+ vector<tl_object_ptr<td_api::textEntity>> &&input_entities,
+ bool allow_all = false);
-vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities);
+vector<tl_object_ptr<td_api::textEntity>> get_text_entities_object(const vector<MessageEntity> &entities,
+ bool skip_bot_commands, int32 max_media_timestamp);
-td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text);
+td_api::object_ptr<td_api::formattedText> get_formatted_text_object(const FormattedText &text, bool skip_bot_commands,
+ int32 max_media_timestamp);
-vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool only_urls = false);
+void remove_premium_custom_emoji_entities(const Td *td, vector<MessageEntity> &entities, bool remove_unknown);
+
+void remove_unallowed_entities(const Td *td, FormattedText &text, DialogId dialog_id);
+
+vector<MessageEntity> find_entities(Slice text, bool skip_bot_commands, bool skip_media_timestamps);
vector<Slice> find_mentions(Slice str);
vector<Slice> find_bot_commands(Slice str);
vector<Slice> find_hashtags(Slice str);
vector<Slice> find_cashtags(Slice str);
+vector<Slice> find_bank_card_numbers(Slice str);
+vector<Slice> find_tg_urls(Slice str);
bool is_email_address(Slice str);
-vector<std::pair<Slice, bool>> find_urls(Slice str); // slice + is_email_address
+vector<std::pair<Slice, bool>> find_urls(Slice str); // slice + is_email_address
+vector<std::pair<Slice, int32>> find_media_timestamps(Slice str); // slice + media_timestamp
+
+void remove_empty_entities(vector<MessageEntity> &entities);
-string get_first_url(Slice text, const vector<MessageEntity> &entities);
+string get_first_url(const FormattedText &text);
Result<vector<MessageEntity>> parse_markdown(string &text);
+Result<vector<MessageEntity>> parse_markdown_v2(string &text);
+
+FormattedText parse_markdown_v3(FormattedText text);
+
+FormattedText get_markdown_v3(FormattedText text);
+
Result<vector<MessageEntity>> parse_html(string &text);
vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(const ContactsManager *contacts_manager,
- const vector<MessageEntity> &entities);
+ const vector<MessageEntity> &entities,
+ const char *source);
+
+vector<tl_object_ptr<telegram_api::MessageEntity>> get_input_message_entities(const ContactsManager *contacts_manager,
+ const FormattedText *text,
+ const char *source);
vector<tl_object_ptr<secret_api::MessageEntity>> get_input_secret_message_entities(
- const vector<MessageEntity> &entities);
+ const vector<MessageEntity> &entities, int32 layer);
vector<MessageEntity> get_message_entities(const ContactsManager *contacts_manager,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
const char *source);
-vector<MessageEntity> get_message_entities(vector<tl_object_ptr<secret_api::MessageEntity>> &&secret_entities);
+vector<MessageEntity> get_message_entities(Td *td, vector<tl_object_ptr<secret_api::MessageEntity>> &&secret_entities,
+ bool is_premium, MultiPromiseActor &load_data_multipromise);
// like clean_input_string but also validates entities
Status fix_formatted_text(string &text, vector<MessageEntity> &entities, bool allow_empty, bool skip_new_entities,
- bool skip_bot_commands, bool for_draft) TD_WARN_UNUSED_RESULT;
+ bool skip_bot_commands, bool skip_media_timestamps, bool for_draft) TD_WARN_UNUSED_RESULT;
+
+FormattedText get_message_text(const ContactsManager *contacts_manager, string message_text,
+ vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
+ bool skip_new_entities, bool skip_media_timestamps, int32 send_date, bool from_album,
+ const char *source);
+
+td_api::object_ptr<td_api::formattedText> extract_input_caption(
+ tl_object_ptr<td_api::InputMessageContent> &input_message_content);
+
+Result<FormattedText> get_formatted_text(const Td *td, DialogId dialog_id,
+ td_api::object_ptr<td_api::formattedText> &&text, bool is_bot,
+ bool allow_empty, bool skip_media_timestamps, bool for_draft);
+
+void add_formatted_text_dependencies(Dependencies &dependencies, const FormattedText *text);
+
+bool has_media_timestamps(const FormattedText *text, int32 min_media_timestamp, int32 max_media_timestamp);
+
+bool has_bot_commands(const FormattedText *text);
+
+bool need_always_skip_bot_commands(const ContactsManager *contacts_manager, DialogId dialog_id, bool is_bot);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.hpp b/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.hpp
new file mode 100644
index 0000000000..bef0aea0c7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageEntity.hpp
@@ -0,0 +1,68 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MessageEntity.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void MessageEntity::store(StorerT &storer) const {
+ using td::store;
+ store(type, storer);
+ store(offset, storer);
+ store(length, storer);
+ if (type == Type::PreCode || type == Type::TextUrl) {
+ store(argument, storer);
+ }
+ if (type == Type::MentionName) {
+ store(user_id, storer);
+ }
+ if (type == Type::MediaTimestamp) {
+ store(media_timestamp, storer);
+ }
+ if (type == Type::CustomEmoji) {
+ store(custom_emoji_id, storer);
+ }
+}
+
+template <class ParserT>
+void MessageEntity::parse(ParserT &parser) {
+ using td::parse;
+ parse(type, parser);
+ parse(offset, parser);
+ parse(length, parser);
+ if (type == Type::PreCode || type == Type::TextUrl) {
+ parse(argument, parser);
+ }
+ if (type == Type::MentionName) {
+ parse(user_id, parser);
+ }
+ if (type == Type::MediaTimestamp) {
+ parse(media_timestamp, parser);
+ }
+ if (type == Type::CustomEmoji) {
+ parse(custom_emoji_id, parser);
+ }
+}
+
+template <class StorerT>
+void FormattedText::store(StorerT &storer) const {
+ td::store(text, storer);
+ td::store(entities, storer);
+}
+
+template <class ParserT>
+void FormattedText::parse(ParserT &parser) {
+ td::parse(text, parser);
+ td::parse(entities, parser);
+ remove_empty_entities(entities);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.cpp
new file mode 100644
index 0000000000..660912a60f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.cpp
@@ -0,0 +1,328 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageExtendedMedia.h"
+
+#include "td/telegram/Document.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/MessageContent.h"
+#include "td/telegram/MessageContentType.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/VideosManager.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/logging.h"
+
+namespace td {
+
+MessageExtendedMedia::MessageExtendedMedia(
+ Td *td, telegram_api::object_ptr<telegram_api::MessageExtendedMedia> &&extended_media, FormattedText &&caption,
+ DialogId owner_dialog_id) {
+ if (extended_media == nullptr) {
+ return;
+ }
+ caption_ = std::move(caption);
+
+ switch (extended_media->get_id()) {
+ case telegram_api::messageExtendedMediaPreview::ID: {
+ auto media = move_tl_object_as<telegram_api::messageExtendedMediaPreview>(extended_media);
+ type_ = Type::Preview;
+ duration_ = media->video_duration_;
+ dimensions_ = get_dimensions(media->w_, media->h_, "MessageExtendedMedia");
+ if (media->thumb_ != nullptr) {
+ if (media->thumb_->get_id() == telegram_api::photoStrippedSize::ID) {
+ auto thumb = move_tl_object_as<telegram_api::photoStrippedSize>(media->thumb_);
+ minithumbnail_ = thumb->bytes_.as_slice().str();
+ } else {
+ LOG(ERROR) << "Receive " << to_string(media->thumb_);
+ }
+ }
+ break;
+ }
+ case telegram_api::messageExtendedMedia::ID: {
+ auto media = move_tl_object_as<telegram_api::messageExtendedMedia>(extended_media);
+ type_ = Type::Unsupported;
+ switch (media->media_->get_id()) {
+ case telegram_api::messageMediaPhoto::ID: {
+ auto photo = move_tl_object_as<telegram_api::messageMediaPhoto>(media->media_);
+ if (photo->photo_ == nullptr) {
+ break;
+ }
+
+ photo_ = get_photo(td->file_manager_.get(), std::move(photo->photo_), owner_dialog_id);
+ if (photo_.is_empty()) {
+ break;
+ }
+ type_ = Type::Photo;
+ break;
+ }
+ case telegram_api::messageMediaDocument::ID: {
+ auto document = move_tl_object_as<telegram_api::messageMediaDocument>(media->media_);
+ if (document->document_ == nullptr) {
+ break;
+ }
+
+ auto document_ptr = std::move(document->document_);
+ int32 document_id = document_ptr->get_id();
+ if (document_id == telegram_api::documentEmpty::ID) {
+ break;
+ }
+ CHECK(document_id == telegram_api::document::ID);
+
+ auto parsed_document = td->documents_manager_->on_get_document(
+ move_tl_object_as<telegram_api::document>(document_ptr), owner_dialog_id, nullptr);
+ if (parsed_document.empty() || parsed_document.type != Document::Type::Video) {
+ break;
+ }
+ CHECK(parsed_document.file_id.is_valid());
+ video_file_id_ = parsed_document.file_id;
+ type_ = Type::Video;
+ break;
+ }
+ default:
+ break;
+ }
+ if (type_ == Type::Unsupported) {
+ unsupported_version_ = CURRENT_VERSION;
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+Result<MessageExtendedMedia> MessageExtendedMedia::get_message_extended_media(
+ Td *td, td_api::object_ptr<td_api::InputMessageContent> &&extended_media_content, DialogId owner_dialog_id,
+ bool is_premium) {
+ if (extended_media_content == nullptr) {
+ return MessageExtendedMedia();
+ }
+ if (!owner_dialog_id.is_valid()) {
+ return Status::Error(400, "Extended media can't be added to the invoice");
+ }
+
+ auto input_content_type = extended_media_content->get_id();
+ if (input_content_type != td_api::inputMessagePhoto::ID && input_content_type != td_api::inputMessageVideo::ID) {
+ return Status::Error("Invalid extended media content specified");
+ }
+ TRY_RESULT(input_message_content,
+ get_input_message_content(owner_dialog_id, std::move(extended_media_content), td, is_premium));
+ if (input_message_content.ttl != 0) {
+ return Status::Error("Can't use self-destructing extended media");
+ }
+
+ auto content = input_message_content.content.get();
+ auto content_type = content->get_type();
+ MessageExtendedMedia result;
+ CHECK(content_type == MessageContentType::Photo || content_type == MessageContentType::Video);
+ result.caption_ = *get_message_content_caption(content);
+ if (content_type == MessageContentType::Photo) {
+ result.type_ = Type::Photo;
+ result.photo_ = *get_message_content_photo(content);
+ } else {
+ CHECK(content_type == MessageContentType::Video);
+ result.type_ = Type::Video;
+ result.video_file_id_ = get_message_content_upload_file_id(content);
+ }
+ return result;
+}
+
+void MessageExtendedMedia::update_from(const MessageExtendedMedia &old_extended_media) {
+ if (!is_media() && old_extended_media.is_media()) {
+ *this = old_extended_media;
+ }
+}
+
+bool MessageExtendedMedia::update_to(Td *td,
+ telegram_api::object_ptr<telegram_api::MessageExtendedMedia> extended_media_ptr,
+ DialogId owner_dialog_id) {
+ MessageExtendedMedia new_extended_media(td, std::move(extended_media_ptr), FormattedText(caption_), owner_dialog_id);
+ if (!new_extended_media.is_media() && is_media()) {
+ return false;
+ }
+ if (*this != new_extended_media || is_equal_but_different(new_extended_media)) {
+ *this = std::move(new_extended_media);
+ return true;
+ }
+ return false;
+}
+
+td_api::object_ptr<td_api::MessageExtendedMedia> MessageExtendedMedia::get_message_extended_media_object(
+ Td *td, bool skip_bot_commands, int32 max_media_timestamp) const {
+ if (type_ == Type::Empty) {
+ return nullptr;
+ }
+
+ auto caption = get_formatted_text_object(caption_, skip_bot_commands, max_media_timestamp);
+ switch (type_) {
+ case Type::Unsupported:
+ return td_api::make_object<td_api::messageExtendedMediaUnsupported>(std::move(caption));
+ case Type::Preview:
+ return td_api::make_object<td_api::messageExtendedMediaPreview>(dimensions_.width, dimensions_.height, duration_,
+ get_minithumbnail_object(minithumbnail_),
+ std::move(caption));
+ case Type::Photo: {
+ auto photo = get_photo_object(td->file_manager_.get(), photo_);
+ CHECK(photo != nullptr);
+ return td_api::make_object<td_api::messageExtendedMediaPhoto>(std::move(photo), std::move(caption));
+ }
+ case Type::Video:
+ return td_api::make_object<td_api::messageExtendedMediaVideo>(
+ td->videos_manager_->get_video_object(video_file_id_), std::move(caption));
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+void MessageExtendedMedia::append_file_ids(const Td *td, vector<FileId> &file_ids) const {
+ switch (type_) {
+ case Type::Empty:
+ case Type::Unsupported:
+ case Type::Preview:
+ break;
+ case Type::Photo:
+ append(file_ids, photo_get_file_ids(photo_));
+ break;
+ case Type::Video:
+ Document(Document::Type::Video, video_file_id_).append_file_ids(td, file_ids);
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+void MessageExtendedMedia::delete_thumbnail(Td *td) {
+ switch (type_) {
+ case Type::Empty:
+ case Type::Unsupported:
+ case Type::Preview:
+ break;
+ case Type::Photo:
+ photo_delete_thumbnail(photo_);
+ break;
+ case Type::Video:
+ td->videos_manager_->delete_video_thumbnail(video_file_id_);
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+int32 MessageExtendedMedia::get_duration(const Td *td) const {
+ if (!has_media_timestamp()) {
+ return 0;
+ }
+ return td->videos_manager_->get_video_duration(video_file_id_);
+}
+
+FileId MessageExtendedMedia::get_upload_file_id() const {
+ switch (type_) {
+ case Type::Empty:
+ case Type::Unsupported:
+ case Type::Preview:
+ break;
+ case Type::Photo:
+ return get_photo_upload_file_id(photo_);
+ case Type::Video:
+ return video_file_id_;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ return FileId();
+}
+
+FileId MessageExtendedMedia::get_any_file_id() const {
+ switch (type_) {
+ case Type::Empty:
+ case Type::Unsupported:
+ case Type::Preview:
+ break;
+ case Type::Photo:
+ return get_photo_any_file_id(photo_);
+ case Type::Video:
+ return video_file_id_;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ return FileId();
+}
+
+FileId MessageExtendedMedia::get_thumbnail_file_id(const Td *td) const {
+ switch (type_) {
+ case Type::Empty:
+ case Type::Unsupported:
+ case Type::Preview:
+ break;
+ case Type::Photo:
+ return get_photo_thumbnail_file_id(photo_);
+ case Type::Video:
+ return td->videos_manager_->get_video_thumbnail_file_id(video_file_id_);
+ default:
+ UNREACHABLE();
+ break;
+ }
+ return FileId();
+}
+
+telegram_api::object_ptr<telegram_api::InputMedia> MessageExtendedMedia::get_input_media(
+ Td *td, tl_object_ptr<telegram_api::InputFile> input_file,
+ tl_object_ptr<telegram_api::InputFile> input_thumbnail) const {
+ switch (type_) {
+ case Type::Empty:
+ case Type::Unsupported:
+ case Type::Preview:
+ break;
+ case Type::Photo:
+ return photo_get_input_media(td->file_manager_.get(), photo_, std::move(input_file), 0);
+ case Type::Video:
+ return td->videos_manager_->get_input_media(video_file_id_, std::move(input_file), std::move(input_thumbnail), 0);
+ default:
+ UNREACHABLE();
+ break;
+ }
+ return nullptr;
+}
+
+bool MessageExtendedMedia::is_equal_but_different(const MessageExtendedMedia &other) const {
+ return type_ == Type::Unsupported && other.type_ == Type::Unsupported &&
+ unsupported_version_ != other.unsupported_version_;
+}
+
+bool operator==(const MessageExtendedMedia &lhs, const MessageExtendedMedia &rhs) {
+ if (lhs.type_ != rhs.type_ || lhs.caption_ != rhs.caption_) {
+ return false;
+ }
+ switch (lhs.type_) {
+ case MessageExtendedMedia::Type::Empty:
+ return true;
+ case MessageExtendedMedia::Type::Unsupported:
+ // don't compare unsupported_version_
+ return true;
+ case MessageExtendedMedia::Type::Preview:
+ return lhs.duration_ == rhs.duration_ && lhs.dimensions_ == rhs.dimensions_ &&
+ lhs.minithumbnail_ == rhs.minithumbnail_;
+ case MessageExtendedMedia::Type::Photo:
+ return lhs.photo_ == rhs.photo_;
+ case MessageExtendedMedia::Type::Video:
+ return lhs.video_file_id_ == rhs.video_file_id_;
+ default:
+ UNREACHABLE();
+ return true;
+ }
+}
+
+bool operator!=(const MessageExtendedMedia &lhs, const MessageExtendedMedia &rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.h b/protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.h
new file mode 100644
index 0000000000..ec90452eb8
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.h
@@ -0,0 +1,118 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class Td;
+
+class MessageExtendedMedia {
+ enum class Type : int32 { Empty, Unsupported, Preview, Photo, Video };
+ Type type_ = Type::Empty;
+ FormattedText caption_;
+
+ static constexpr int32 CURRENT_VERSION = 1;
+
+ // for Unsupported
+ int32 unsupported_version_ = 0;
+
+ // for Preview
+ int32 duration_ = 0;
+ Dimensions dimensions_;
+ string minithumbnail_;
+
+ // for Photo
+ Photo photo_;
+
+ // for Video
+ FileId video_file_id_;
+
+ friend bool operator==(const MessageExtendedMedia &lhs, const MessageExtendedMedia &rhs);
+
+ bool is_media() const {
+ return type_ != Type::Empty && type_ != Type::Preview;
+ }
+
+ public:
+ MessageExtendedMedia() = default;
+
+ MessageExtendedMedia(Td *td, telegram_api::object_ptr<telegram_api::MessageExtendedMedia> &&extended_media,
+ FormattedText &&caption, DialogId owner_dialog_id);
+
+ static Result<MessageExtendedMedia> get_message_extended_media(
+ Td *td, td_api::object_ptr<td_api::InputMessageContent> &&extended_media_content, DialogId owner_dialog_id,
+ bool is_premium);
+
+ bool is_empty() const {
+ return type_ == Type::Empty;
+ }
+
+ void update_from(const MessageExtendedMedia &old_extended_media);
+
+ bool update_to(Td *td, telegram_api::object_ptr<telegram_api::MessageExtendedMedia> extended_media_ptr,
+ DialogId owner_dialog_id);
+
+ td_api::object_ptr<td_api::MessageExtendedMedia> get_message_extended_media_object(Td *td, bool skip_bot_commands,
+ int32 max_media_timestamp) const;
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const;
+
+ void delete_thumbnail(Td *td);
+
+ bool need_reget() const {
+ return type_ == Type::Unsupported && unsupported_version_ < CURRENT_VERSION;
+ }
+
+ bool need_poll() const {
+ return type_ == Type::Preview;
+ }
+
+ bool has_media_timestamp() const {
+ return type_ == Type::Video;
+ }
+
+ bool is_equal_but_different(const MessageExtendedMedia &other) const;
+
+ int32 get_duration(const Td *td) const;
+
+ const FormattedText *get_caption() const {
+ return &caption_;
+ }
+
+ FileId get_upload_file_id() const;
+
+ FileId get_any_file_id() const;
+
+ FileId get_thumbnail_file_id(const Td *td) const;
+
+ telegram_api::object_ptr<telegram_api::InputMedia> get_input_media(
+ Td *td, tl_object_ptr<telegram_api::InputFile> input_file,
+ tl_object_ptr<telegram_api::InputFile> input_thumbnail) const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+bool operator==(const MessageExtendedMedia &lhs, const MessageExtendedMedia &rhs);
+
+bool operator!=(const MessageExtendedMedia &lhs, const MessageExtendedMedia &rhs);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.hpp b/protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.hpp
new file mode 100644
index 0000000000..72c35dc693
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageExtendedMedia.hpp
@@ -0,0 +1,115 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MessageExtendedMedia.h"
+#include "td/telegram/Photo.hpp"
+#include "td/telegram/Td.h"
+#include "td/telegram/VideosManager.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void MessageExtendedMedia::store(StorerT &storer) const {
+ bool has_caption = !caption_.text.empty();
+ bool has_unsupported_version = unsupported_version_ != 0;
+ bool has_duration = duration_ != 0;
+ bool has_dimensions = dimensions_.width != 0 || dimensions_.height != 0;
+ bool has_minithumbnail = !minithumbnail_.empty();
+ bool has_photo = !photo_.is_empty();
+ bool has_video = video_file_id_.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_caption);
+ STORE_FLAG(has_unsupported_version);
+ STORE_FLAG(has_duration);
+ STORE_FLAG(has_dimensions);
+ STORE_FLAG(has_minithumbnail);
+ STORE_FLAG(has_photo);
+ STORE_FLAG(has_video);
+ END_STORE_FLAGS();
+ td::store(type_, storer);
+ if (has_caption) {
+ td::store(caption_, storer);
+ }
+ if (has_unsupported_version) {
+ td::store(unsupported_version_, storer);
+ }
+ if (has_duration) {
+ td::store(duration_, storer);
+ }
+ if (has_dimensions) {
+ td::store(dimensions_, storer);
+ }
+ if (has_minithumbnail) {
+ td::store(minithumbnail_, storer);
+ }
+ if (has_photo) {
+ td::store(photo_, storer);
+ }
+ if (has_video) {
+ Td *td = storer.context()->td().get_actor_unsafe();
+ td->videos_manager_->store_video(video_file_id_, storer);
+ }
+}
+
+template <class ParserT>
+void MessageExtendedMedia::parse(ParserT &parser) {
+ bool has_caption;
+ bool has_unsupported_version;
+ bool has_duration;
+ bool has_dimensions;
+ bool has_minithumbnail;
+ bool has_photo;
+ bool has_video;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_caption);
+ PARSE_FLAG(has_unsupported_version);
+ PARSE_FLAG(has_duration);
+ PARSE_FLAG(has_dimensions);
+ PARSE_FLAG(has_minithumbnail);
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(has_video);
+ END_PARSE_FLAGS();
+ td::parse(type_, parser);
+ if (has_caption) {
+ td::parse(caption_, parser);
+ }
+ if (has_unsupported_version) {
+ td::parse(unsupported_version_, parser);
+ }
+ if (has_duration) {
+ td::parse(duration_, parser);
+ }
+ if (has_dimensions) {
+ td::parse(dimensions_, parser);
+ }
+ if (has_minithumbnail) {
+ td::parse(minithumbnail_, parser);
+ }
+ bool is_bad = false;
+ if (has_photo) {
+ td::parse(photo_, parser);
+ is_bad = photo_.is_bad();
+ }
+ if (has_video) {
+ Td *td = parser.context()->td().get_actor_unsafe();
+ video_file_id_ = td->videos_manager_->parse_video(parser);
+ is_bad = !video_file_id_.is_valid();
+ }
+ if (is_bad) {
+ LOG(ERROR) << "Failed to parse MessageExtendedMedia";
+ photo_ = Photo();
+ video_file_id_ = FileId();
+ type_ = Type::Unsupported;
+ unsupported_version_ = 0;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageId.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageId.cpp
new file mode 100644
index 0000000000..e35cb8dde2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageId.cpp
@@ -0,0 +1,165 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageId.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+
+namespace td {
+
+MessageId::MessageId(ScheduledServerMessageId server_message_id, int32 send_date, bool force) {
+ if (send_date <= (1 << 30)) {
+ LOG(ERROR) << "Scheduled message send date " << send_date << " is in the past";
+ return;
+ }
+ if (!server_message_id.is_valid() && !force) {
+ LOG(ERROR) << "Scheduled message ID " << server_message_id.get() << " is invalid";
+ return;
+ }
+ id = (static_cast<int64>(send_date - (1 << 30)) << 21) | (static_cast<int64>(server_message_id.get()) << 3) |
+ SCHEDULED_MASK;
+}
+
+bool MessageId::is_valid() const {
+ if (id <= 0 || id > max().get()) {
+ return false;
+ }
+ if ((id & FULL_TYPE_MASK) == 0) {
+ return true;
+ }
+ int32 type = (id & TYPE_MASK);
+ return type == TYPE_YET_UNSENT || type == TYPE_LOCAL;
+}
+
+bool MessageId::is_valid_scheduled() const {
+ if (id <= 0 || id > (static_cast<int64>(1) << 51)) {
+ return false;
+ }
+ int32 type = (id & TYPE_MASK);
+ return type == SCHEDULED_MASK || type == (SCHEDULED_MASK | TYPE_YET_UNSENT) || type == (SCHEDULED_MASK | TYPE_LOCAL);
+}
+
+bool MessageId::is_valid_sponsored() const {
+ if (id <= max().get() || id > (static_cast<int64>(1) << 51)) {
+ return false;
+ }
+ int32 type = (id & TYPE_MASK);
+ return type == TYPE_LOCAL;
+}
+
+MessageType MessageId::get_type() const {
+ if (id <= 0 || id > (static_cast<int64>(1) << 51)) {
+ return MessageType::None;
+ }
+
+ if (is_scheduled()) {
+ switch (id & TYPE_MASK) {
+ case SCHEDULED_MASK | TYPE_YET_UNSENT:
+ return MessageType::YetUnsent;
+ case SCHEDULED_MASK | TYPE_LOCAL:
+ return MessageType::Local;
+ case SCHEDULED_MASK:
+ return MessageType::Server;
+ default:
+ return MessageType::None;
+ }
+ }
+
+ if ((id & FULL_TYPE_MASK) == 0) {
+ return MessageType::Server;
+ }
+ switch (id & TYPE_MASK) {
+ case TYPE_YET_UNSENT:
+ return MessageType::YetUnsent;
+ case TYPE_LOCAL:
+ return MessageType::Local;
+ default:
+ return MessageType::None;
+ }
+}
+
+ServerMessageId MessageId::get_server_message_id_force() const {
+ CHECK(!is_scheduled());
+ return ServerMessageId(narrow_cast<int32>(id >> SERVER_ID_SHIFT));
+}
+
+MessageId MessageId::get_next_message_id(MessageType type) const {
+ if (is_scheduled()) {
+ CHECK(is_valid_scheduled());
+ auto current_type = get_type();
+ if (static_cast<int32>(current_type) < static_cast<int32>(type)) {
+ return MessageId(id - static_cast<int32>(current_type) + static_cast<int32>(type));
+ }
+ int64 base_id = (id & ~TYPE_MASK) + TYPE_MASK + 1 + SCHEDULED_MASK;
+ switch (type) {
+ case MessageType::Server:
+ return MessageId(base_id);
+ case MessageType::YetUnsent:
+ return MessageId(base_id + TYPE_YET_UNSENT);
+ case MessageType::Local:
+ return MessageId(base_id + TYPE_LOCAL);
+ case MessageType::None:
+ default:
+ UNREACHABLE();
+ return MessageId();
+ }
+ }
+
+ switch (type) {
+ case MessageType::Server:
+ if (is_server()) {
+ return MessageId(ServerMessageId(get_server_message_id().get() + 1));
+ }
+ return get_next_server_message_id();
+ case MessageType::YetUnsent:
+ return MessageId(((id + TYPE_MASK + 1 - TYPE_YET_UNSENT) & ~TYPE_MASK) + TYPE_YET_UNSENT);
+ case MessageType::Local:
+ return MessageId(((id + TYPE_MASK + 1 - TYPE_LOCAL) & ~TYPE_MASK) + TYPE_LOCAL);
+ case MessageType::None:
+ default:
+ UNREACHABLE();
+ return MessageId();
+ }
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, MessageId message_id) {
+ if (message_id.is_scheduled()) {
+ string_builder << "scheduled ";
+
+ if (!message_id.is_valid_scheduled()) {
+ return string_builder << "invalid message " << message_id.get();
+ }
+ if (message_id.is_scheduled_server()) {
+ return string_builder << "server message " << message_id.get_scheduled_server_message_id_force().get();
+ }
+ if (message_id.is_local()) {
+ return string_builder << "local message " << message_id.get_scheduled_server_message_id_force().get();
+ }
+ if (message_id.is_yet_unsent()) {
+ return string_builder << "yet unsent message " << message_id.get_scheduled_server_message_id_force().get();
+ }
+ return string_builder << "bugged message " << message_id.get();
+ }
+
+ if (!message_id.is_valid()) {
+ return string_builder << "invalid message " << message_id.get();
+ }
+ if (message_id.is_server()) {
+ return string_builder << "server message " << message_id.get_server_message_id_force().get();
+ }
+ if (message_id.is_local()) {
+ return string_builder << "local message " << message_id.get_server_message_id_force().get() << '.'
+ << (message_id.get() & MessageId::FULL_TYPE_MASK);
+ }
+ if (message_id.is_yet_unsent()) {
+ return string_builder << "yet unsent message " << message_id.get_server_message_id_force().get() << '.'
+ << (message_id.get() & MessageId::FULL_TYPE_MASK);
+ }
+ return string_builder << "bugged message " << message_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageId.h b/protocols/Telegram/tdlib/td/td/telegram/MessageId.h
index 4af1075579..baf7cae841 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/MessageId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageId.h
@@ -1,114 +1,102 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/DialogId.h"
+#include "td/telegram/ScheduledServerMessageId.h"
+#include "td/telegram/ServerMessageId.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
-#include "td/utils/misc.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/StringBuilder.h"
-#include "td/utils/tl_helpers.h"
-#include <functional>
#include <limits>
#include <type_traits>
namespace td {
-class ServerMessageId {
- int32 id = 0;
+enum class MessageType : int32 { None, Server, YetUnsent, Local };
- public:
- ServerMessageId() = default;
+class MessageId {
+ int64 id = 0;
- explicit ServerMessageId(int32 message_id) : id(message_id) {
- }
- template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
- ServerMessageId(T message_id) = delete;
+ static constexpr int32 SERVER_ID_SHIFT = 20;
+ static constexpr int32 SHORT_TYPE_MASK = (1 << 2) - 1;
+ static constexpr int32 TYPE_MASK = (1 << 3) - 1;
+ static constexpr int32 FULL_TYPE_MASK = (1 << SERVER_ID_SHIFT) - 1;
+ static constexpr int32 SCHEDULED_MASK = 4;
+ static constexpr int32 TYPE_YET_UNSENT = 1;
+ static constexpr int32 TYPE_LOCAL = 2;
- bool is_valid() const {
- return id > 0;
- }
+ friend StringBuilder &operator<<(StringBuilder &string_builder, MessageId message_id);
- int32 get() const {
- return id;
- }
+ // ordinary message ID layout
+ // |-------31--------|---17---|1|--2-|
+ // |server_message_id|local_id|0|type|
- bool operator==(const ServerMessageId &other) const {
- return id == other.id;
- }
+ // scheduled message ID layout
+ // |-------30-------|----18---|1|--2-|
+ // |send_date-2**30 |server_id|1|type|
- bool operator!=(const ServerMessageId &other) const {
- return id != other.id;
- }
+ // sponsored message ID layout
+ // |-------31--------|---17---|1|-2|
+ // |11111111111111111|local_id|0|10|
- template <class StorerT>
- void store(StorerT &storer) const {
- storer.store_int(id);
- }
+ ServerMessageId get_server_message_id_force() const;
- template <class ParserT>
- void parse(ParserT &parser) {
- id = parser.fetch_int();
+ ScheduledServerMessageId get_scheduled_server_message_id_force() const {
+ CHECK(is_scheduled());
+ return ScheduledServerMessageId(static_cast<int32>((id >> 3) & ((1 << 18) - 1)));
}
-};
-
-class MessageId {
- int64 id = 0;
public:
- static constexpr int32 SERVER_ID_SHIFT = 20;
- static constexpr int32 TYPE_MASK = (1 << 3) - 1;
- static constexpr int32 FULL_TYPE_MASK = (1 << SERVER_ID_SHIFT) - 1;
- static constexpr int32 TYPE_YET_UNSENT = 1;
- static constexpr int32 TYPE_LOCAL = 2;
-
MessageId() = default;
explicit MessageId(ServerMessageId server_message_id)
: id(static_cast<int64>(server_message_id.get()) << SERVER_ID_SHIFT) {
}
+ MessageId(ScheduledServerMessageId server_message_id, int32 send_date, bool force = false);
+
explicit constexpr MessageId(int64 message_id) : id(message_id) {
}
template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
MessageId(T message_id) = delete;
static constexpr MessageId min() {
- return MessageId(static_cast<int64>(MessageId::TYPE_LOCAL));
+ return MessageId(static_cast<int64>(MessageId::TYPE_YET_UNSENT));
}
static constexpr MessageId max() {
return MessageId(static_cast<int64>(std::numeric_limits<int32>::max()) << SERVER_ID_SHIFT);
}
- bool is_valid() const {
- if (id <= 0 || id > max().get()) {
- return false;
- }
- if ((id & FULL_TYPE_MASK) == 0) {
- return true;
- }
- int32 type = (id & TYPE_MASK);
- return type == TYPE_YET_UNSENT || type == TYPE_LOCAL;
- }
+ bool is_valid() const;
+
+ bool is_valid_scheduled() const;
+
+ bool is_valid_sponsored() const;
int64 get() const {
return id;
}
+ MessageType get_type() const;
+
+ bool is_scheduled() const {
+ return (id & SCHEDULED_MASK) != 0;
+ }
+
bool is_yet_unsent() const {
- CHECK(is_valid());
- return (id & TYPE_MASK) == TYPE_YET_UNSENT;
+ CHECK(is_valid() || is_scheduled());
+ return (id & SHORT_TYPE_MASK) == TYPE_YET_UNSENT;
}
bool is_local() const {
- CHECK(is_valid());
- return (id & TYPE_MASK) == TYPE_LOCAL;
+ CHECK(is_valid() || is_scheduled());
+ return (id & SHORT_TYPE_MASK) == TYPE_LOCAL;
}
bool is_server() const {
@@ -116,110 +104,87 @@ class MessageId {
return (id & FULL_TYPE_MASK) == 0;
}
- ServerMessageId get_server_message_id() const {
- CHECK(id == 0 || is_server());
- return ServerMessageId(narrow_cast<int32>(id >> SERVER_ID_SHIFT));
+ bool is_scheduled_server() const {
+ CHECK(is_valid_scheduled());
+ return (id & SHORT_TYPE_MASK) == 0;
}
- // returns smallest server message id not less than this message id
- MessageId get_next_server_message_id() const {
- return MessageId((id + FULL_TYPE_MASK) & ~FULL_TYPE_MASK);
+ bool is_any_server() const {
+ return is_scheduled() ? is_scheduled_server() : is_server();
}
- bool operator==(const MessageId &other) const {
- return id == other.id;
+ ServerMessageId get_server_message_id() const {
+ CHECK(id == 0 || is_server());
+ return get_server_message_id_force();
}
- bool operator!=(const MessageId &other) const {
- return id != other.id;
+ // returns greatest server message identifier not bigger than this message identifier
+ MessageId get_prev_server_message_id() const {
+ CHECK(!is_scheduled());
+ return MessageId(id & ~FULL_TYPE_MASK);
}
- template <class StorerT>
- void store(StorerT &storer) const {
- storer.store_long(id);
+ // returns smallest server message identifier not less than this message identifier
+ MessageId get_next_server_message_id() const {
+ CHECK(!is_scheduled());
+ return MessageId((id + FULL_TYPE_MASK) & ~FULL_TYPE_MASK);
}
- template <class ParserT>
- void parse(ParserT &parser) {
- id = parser.fetch_long();
- }
-};
+ MessageId get_next_message_id(MessageType type) const;
-struct MessageIdHash {
- std::size_t operator()(MessageId message_id) const {
- return std::hash<int64>()(message_id.get());
+ ScheduledServerMessageId get_scheduled_server_message_id() const {
+ CHECK(is_scheduled_server());
+ return get_scheduled_server_message_id_force();
}
-};
-inline StringBuilder &operator<<(StringBuilder &string_builder, MessageId message_id) {
- if (!message_id.is_valid()) {
- return string_builder << "invalid message " << message_id.get();
+ int32 get_scheduled_message_date() const {
+ CHECK(is_valid_scheduled());
+ return static_cast<int32>(id >> 21) + (1 << 30);
}
- if (message_id.is_server()) {
- return string_builder << "server message " << (message_id.get() >> MessageId::SERVER_ID_SHIFT);
- }
- if (message_id.is_local()) {
- return string_builder << "local message " << (message_id.get() >> MessageId::SERVER_ID_SHIFT) << '.'
- << (message_id.get() & MessageId::FULL_TYPE_MASK);
- }
- if (message_id.is_yet_unsent()) {
- return string_builder << "yet unsent message " << (message_id.get() >> MessageId::SERVER_ID_SHIFT) << '.'
- << (message_id.get() & MessageId::FULL_TYPE_MASK);
- }
- return string_builder << "bugged message " << message_id.get();
-}
-struct FullMessageId {
- private:
- DialogId dialog_id;
- MessageId message_id;
-
- public:
- FullMessageId() : dialog_id(), message_id() {
+ bool operator==(const MessageId &other) const {
+ return id == other.id;
}
- FullMessageId(DialogId dialog_id, MessageId message_id) : dialog_id(dialog_id), message_id(message_id) {
+ bool operator!=(const MessageId &other) const {
+ return id != other.id;
}
- bool operator==(const FullMessageId &other) const {
- return dialog_id == other.dialog_id && message_id == other.message_id;
+ friend bool operator<(const MessageId &lhs, const MessageId &rhs) {
+ CHECK(lhs.is_scheduled() == rhs.is_scheduled());
+ return lhs.id < rhs.id;
}
- bool operator!=(const FullMessageId &other) const {
- return !(*this == other);
+ friend bool operator>(const MessageId &lhs, const MessageId &rhs) {
+ CHECK(lhs.is_scheduled() == rhs.is_scheduled());
+ return lhs.id > rhs.id;
}
- DialogId get_dialog_id() const {
- return dialog_id;
+ friend bool operator<=(const MessageId &lhs, const MessageId &rhs) {
+ CHECK(lhs.is_scheduled() == rhs.is_scheduled());
+ return lhs.id <= rhs.id;
}
- MessageId get_message_id() const {
- return message_id;
+
+ friend bool operator>=(const MessageId &lhs, const MessageId &rhs) {
+ CHECK(lhs.is_scheduled() == rhs.is_scheduled());
+ return lhs.id >= rhs.id;
}
template <class StorerT>
void store(StorerT &storer) const {
- using ::td::store;
- store(dialog_id, storer);
- store(message_id, storer);
+ storer.store_long(id);
}
template <class ParserT>
void parse(ParserT &parser) {
- using ::td::parse;
- parse(dialog_id, parser);
- parse(message_id, parser);
+ id = parser.fetch_long();
}
};
-struct FullMessageIdHash {
- std::size_t operator()(FullMessageId full_message_id) const {
- return DialogIdHash()(full_message_id.get_dialog_id()) * 2023654985u +
- MessageIdHash()(full_message_id.get_message_id());
+struct MessageIdHash {
+ uint32 operator()(MessageId message_id) const {
+ return Hash<int64>()(message_id.get());
}
};
-inline StringBuilder &operator<<(StringBuilder &string_builder, FullMessageId full_message_id) {
- return string_builder << full_message_id.get_message_id() << " in " << full_message_id.get_dialog_id();
-}
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageLinkInfo.h b/protocols/Telegram/tdlib/td/td/telegram/MessageLinkInfo.h
new file mode 100644
index 0000000000..351923acea
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageLinkInfo.h
@@ -0,0 +1,33 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageId.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+struct MessageLinkInfo {
+ string username;
+ // or
+ ChannelId channel_id;
+
+ MessageId message_id;
+ bool is_single = false;
+ int32 media_timestamp = 0;
+
+ MessageId top_thread_message_id;
+
+ DialogId comment_dialog_id;
+ MessageId comment_message_id;
+ bool for_comment = false;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageReaction.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageReaction.cpp
new file mode 100644
index 0000000000..36449d09cb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageReaction.cpp
@@ -0,0 +1,1017 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageReaction.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Dependencies.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessageSender.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/UpdatesManager.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/as.h"
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/crypto.h"
+#include "td/utils/emoji.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+#include "td/utils/utf8.h"
+
+#include <algorithm>
+#include <utility>
+
+namespace td {
+
+static size_t get_max_reaction_count() {
+ bool is_premium = G()->get_option_boolean("is_premium");
+ auto option_key = is_premium ? Slice("reactions_user_max_premium") : Slice("reactions_user_max_default");
+ return static_cast<size_t>(
+ max(static_cast<int32>(1), static_cast<int32>(G()->get_option_integer(option_key, is_premium ? 3 : 1))));
+}
+
+static int64 get_custom_emoji_id(const string &reaction) {
+ auto r_decoded = base64_decode(Slice(&reaction[1], reaction.size() - 1));
+ CHECK(r_decoded.is_ok());
+ CHECK(r_decoded.ok().size() == 8);
+ return as<int64>(r_decoded.ok().c_str());
+}
+
+static string get_custom_emoji_string(int64 custom_emoji_id) {
+ char s[8];
+ as<int64>(&s) = custom_emoji_id;
+ return PSTRING() << '#' << base64_encode(Slice(s, 8));
+}
+
+telegram_api::object_ptr<telegram_api::Reaction> get_input_reaction(const string &reaction) {
+ if (reaction.empty()) {
+ return telegram_api::make_object<telegram_api::reactionEmpty>();
+ }
+ if (is_custom_reaction(reaction)) {
+ return telegram_api::make_object<telegram_api::reactionCustomEmoji>(get_custom_emoji_id(reaction));
+ }
+ return telegram_api::make_object<telegram_api::reactionEmoji>(reaction);
+}
+
+string get_message_reaction_string(const telegram_api::object_ptr<telegram_api::Reaction> &reaction) {
+ if (reaction == nullptr) {
+ return string();
+ }
+ switch (reaction->get_id()) {
+ case telegram_api::reactionEmpty::ID:
+ return string();
+ case telegram_api::reactionEmoji::ID: {
+ const string &emoji = static_cast<const telegram_api::reactionEmoji *>(reaction.get())->emoticon_;
+ if (is_custom_reaction(emoji)) {
+ return string();
+ }
+ return emoji;
+ }
+ case telegram_api::reactionCustomEmoji::ID:
+ return get_custom_emoji_string(
+ static_cast<const telegram_api::reactionCustomEmoji *>(reaction.get())->document_id_);
+ default:
+ UNREACHABLE();
+ return string();
+ }
+}
+
+td_api::object_ptr<td_api::ReactionType> get_reaction_type_object(const string &reaction) {
+ CHECK(!reaction.empty());
+ if (is_custom_reaction(reaction)) {
+ return td_api::make_object<td_api::reactionTypeCustomEmoji>(get_custom_emoji_id(reaction));
+ }
+ return td_api::make_object<td_api::reactionTypeEmoji>(reaction);
+}
+
+string get_message_reaction_string(const td_api::object_ptr<td_api::ReactionType> &type) {
+ if (type == nullptr) {
+ return string();
+ }
+ switch (type->get_id()) {
+ case td_api::reactionTypeEmoji::ID: {
+ const string &emoji = static_cast<const td_api::reactionTypeEmoji *>(type.get())->emoji_;
+ if (!check_utf8(emoji) || is_custom_reaction(emoji)) {
+ return string();
+ }
+ return emoji;
+ }
+ case td_api::reactionTypeCustomEmoji::ID:
+ return get_custom_emoji_string(
+ static_cast<const td_api::reactionTypeCustomEmoji *>(type.get())->custom_emoji_id_);
+ default:
+ UNREACHABLE();
+ return string();
+ }
+}
+
+class GetMessagesReactionsQuery final : public Td::ResultHandler {
+ DialogId dialog_id_;
+ vector<MessageId> message_ids_;
+
+ public:
+ void send(DialogId dialog_id, vector<MessageId> &&message_ids) {
+ dialog_id_ = dialog_id;
+ message_ids_ = std::move(message_ids);
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_getMessagesReactions(
+ std::move(input_peer), MessagesManager::get_server_message_ids(message_ids_))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getMessagesReactions>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetMessagesReactionsQuery: " << to_string(ptr);
+ if (ptr->get_id() == telegram_api::updates::ID) {
+ auto &updates = static_cast<telegram_api::updates *>(ptr.get())->updates_;
+ FlatHashSet<MessageId, MessageIdHash> skipped_message_ids;
+ for (auto message_id : message_ids_) {
+ skipped_message_ids.insert(message_id);
+ }
+ for (const auto &update : updates) {
+ if (update->get_id() == telegram_api::updateMessageReactions::ID) {
+ auto update_message_reactions = static_cast<const telegram_api::updateMessageReactions *>(update.get());
+ if (DialogId(update_message_reactions->peer_) == dialog_id_) {
+ skipped_message_ids.erase(MessageId(ServerMessageId(update_message_reactions->msg_id_)));
+ }
+ }
+ }
+ for (auto message_id : skipped_message_ids) {
+ td_->messages_manager_->update_message_reactions({dialog_id_, message_id}, nullptr);
+ }
+ }
+ td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
+ td_->messages_manager_->try_reload_message_reactions(dialog_id_, true);
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagesReactionsQuery");
+ td_->messages_manager_->try_reload_message_reactions(dialog_id_, true);
+ }
+};
+
+class SendReactionQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit SendReactionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(FullMessageId full_message_id, vector<string> reactions, bool is_big, bool add_to_recent) {
+ dialog_id_ = full_message_id.get_dialog_id();
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = 0;
+ if (!reactions.empty()) {
+ flags |= telegram_api::messages_sendReaction::REACTION_MASK;
+
+ if (is_big) {
+ flags |= telegram_api::messages_sendReaction::BIG_MASK;
+ }
+
+ if (add_to_recent) {
+ flags |= telegram_api::messages_sendReaction::ADD_TO_RECENT_MASK;
+ }
+ }
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_sendReaction(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer),
+ full_message_id.get_message_id().get_server_message_id().get(),
+ transform(reactions, get_input_reaction)),
+ {{dialog_id_}, {full_message_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_sendReaction>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SendReactionQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "MESSAGE_NOT_MODIFIED") {
+ return promise_.set_value(Unit());
+ }
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendReactionQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetMessageReactionsListQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::addedReactions>> promise_;
+ DialogId dialog_id_;
+ MessageId message_id_;
+ string reaction_;
+ string offset_;
+
+ public:
+ explicit GetMessageReactionsListQuery(Promise<td_api::object_ptr<td_api::addedReactions>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(FullMessageId full_message_id, string reaction, string offset, int32 limit) {
+ dialog_id_ = full_message_id.get_dialog_id();
+ message_id_ = full_message_id.get_message_id();
+ reaction_ = std::move(reaction);
+ offset_ = std::move(offset);
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = 0;
+ if (!reaction_.empty()) {
+ flags |= telegram_api::messages_getMessageReactionsList::REACTION_MASK;
+ }
+ if (!offset_.empty()) {
+ flags |= telegram_api::messages_getMessageReactionsList::OFFSET_MASK;
+ }
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getMessageReactionsList(flags, std::move(input_peer),
+ message_id_.get_server_message_id().get(),
+ get_input_reaction(reaction_), offset_, limit),
+ {{full_message_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getMessageReactionsList>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetMessageReactionsListQuery: " << to_string(ptr);
+
+ td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetMessageReactionsListQuery");
+ td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetMessageReactionsListQuery");
+
+ int32 total_count = ptr->count_;
+ auto received_reaction_count = static_cast<int32>(ptr->reactions_.size());
+ if (total_count < received_reaction_count) {
+ LOG(ERROR) << "Receive invalid total_count in " << to_string(ptr);
+ total_count = received_reaction_count;
+ }
+
+ vector<td_api::object_ptr<td_api::addedReaction>> reactions;
+ FlatHashMap<string, vector<DialogId>> recent_reactions;
+ for (const auto &reaction : ptr->reactions_) {
+ DialogId dialog_id(reaction->peer_id_);
+ auto reaction_str = get_message_reaction_string(reaction->reaction_);
+ if (!dialog_id.is_valid() || (reaction_.empty() ? reaction_str.empty() : reaction_ != reaction_str)) {
+ LOG(ERROR) << "Receive unexpected " << to_string(reaction);
+ continue;
+ }
+
+ if (offset_.empty()) {
+ recent_reactions[reaction_str].push_back(dialog_id);
+ }
+
+ auto message_sender = get_min_message_sender_object(td_, dialog_id, "GetMessageReactionsListQuery");
+ if (message_sender != nullptr) {
+ reactions.push_back(td_api::make_object<td_api::addedReaction>(get_reaction_type_object(reaction_str),
+ std::move(message_sender)));
+ }
+ }
+
+ if (offset_.empty()) {
+ td_->messages_manager_->on_get_message_reaction_list({dialog_id_, message_id_}, reaction_,
+ std::move(recent_reactions), total_count);
+ }
+
+ promise_.set_value(
+ td_api::make_object<td_api::addedReactions>(total_count, std::move(reactions), ptr->next_offset_));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessageReactionsListQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetDefaultReactionQuery final : public Td::ResultHandler {
+ string reaction_;
+
+ public:
+ void send(const string &reaction) {
+ reaction_ = reaction;
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_setDefaultReaction(get_input_reaction(reaction))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_setDefaultReaction>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ if (!result_ptr.ok()) {
+ return on_error(Status::Error(400, "Receive false"));
+ }
+
+ auto default_reaction = td_->option_manager_->get_option_string("default_reaction", "-");
+ if (default_reaction != reaction_) {
+ send_set_default_reaction_query(td_);
+ } else {
+ td_->option_manager_->set_option_empty("default_reaction_needs_sync");
+ }
+ }
+
+ void on_error(Status status) final {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Receive error for SetDefaultReactionQuery: " << status;
+ td_->option_manager_->set_option_empty("default_reaction_needs_sync");
+ send_closure(G()->config_manager(), &ConfigManager::request_config, false);
+ }
+};
+
+class ReportReactionQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit ReportReactionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId message_id, DialogId chooser_dialog_id) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ auto chooser_input_peer = td_->messages_manager_->get_input_peer(chooser_dialog_id, AccessRights::Know);
+ if (chooser_input_peer == nullptr) {
+ return promise_.set_error(Status::Error(400, "Reaction sender is not accessible"));
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_reportReaction(
+ std::move(input_peer), message_id.get_server_message_id().get(), std::move(chooser_input_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_reportReaction>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportReactionQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+void MessageReaction::add_recent_chooser_dialog_id(DialogId dialog_id) {
+ recent_chooser_dialog_ids_.insert(recent_chooser_dialog_ids_.begin(), dialog_id);
+ if (recent_chooser_dialog_ids_.size() > MAX_RECENT_CHOOSERS + 1) {
+ LOG(ERROR) << "Have " << recent_chooser_dialog_ids_.size() << " recent reaction choosers";
+ recent_chooser_dialog_ids_.resize(MAX_RECENT_CHOOSERS + 1);
+ }
+}
+
+bool MessageReaction::remove_recent_chooser_dialog_id(DialogId dialog_id) {
+ return td::remove(recent_chooser_dialog_ids_, dialog_id);
+}
+
+void MessageReaction::update_recent_chooser_dialog_ids(const MessageReaction &old_reaction) {
+ if (recent_chooser_dialog_ids_.size() != MAX_RECENT_CHOOSERS) {
+ return;
+ }
+ CHECK(is_chosen_ && old_reaction.is_chosen_);
+ CHECK(reaction_ == old_reaction.reaction_);
+ CHECK(old_reaction.recent_chooser_dialog_ids_.size() == MAX_RECENT_CHOOSERS + 1);
+ for (size_t i = 0; i < MAX_RECENT_CHOOSERS; i++) {
+ if (recent_chooser_dialog_ids_[i] != old_reaction.recent_chooser_dialog_ids_[i]) {
+ return;
+ }
+ }
+ recent_chooser_dialog_ids_ = old_reaction.recent_chooser_dialog_ids_;
+ recent_chooser_min_channels_ = old_reaction.recent_chooser_min_channels_;
+}
+
+void MessageReaction::set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, bool have_recent_choosers) {
+ if (is_chosen_ == is_chosen) {
+ return;
+ }
+
+ is_chosen_ = is_chosen;
+
+ if (chooser_dialog_id.is_valid()) {
+ choose_count_ += is_chosen_ ? 1 : -1;
+ if (have_recent_choosers) {
+ remove_recent_chooser_dialog_id(chooser_dialog_id);
+ if (is_chosen_) {
+ add_recent_chooser_dialog_id(chooser_dialog_id);
+ }
+ }
+ }
+}
+
+td_api::object_ptr<td_api::messageReaction> MessageReaction::get_message_reaction_object(Td *td, UserId my_user_id,
+ UserId peer_user_id) const {
+ CHECK(!is_empty());
+
+ vector<td_api::object_ptr<td_api::MessageSender>> recent_choosers;
+ if (my_user_id.is_valid()) {
+ CHECK(peer_user_id.is_valid());
+ if (is_chosen()) {
+ auto recent_chooser = get_min_message_sender_object(td, DialogId(my_user_id), "get_message_reaction_object");
+ if (recent_chooser != nullptr) {
+ recent_choosers.push_back(std::move(recent_chooser));
+ }
+ }
+ if (choose_count_ >= (is_chosen() ? 2 : 1)) {
+ auto recent_chooser = get_min_message_sender_object(td, DialogId(peer_user_id), "get_message_reaction_object");
+ if (recent_chooser != nullptr) {
+ recent_choosers.push_back(std::move(recent_chooser));
+ }
+ }
+ } else {
+ for (auto dialog_id : recent_chooser_dialog_ids_) {
+ auto recent_chooser = get_min_message_sender_object(td, dialog_id, "get_message_reaction_object");
+ if (recent_chooser != nullptr) {
+ recent_choosers.push_back(std::move(recent_chooser));
+ if (recent_choosers.size() == MAX_RECENT_CHOOSERS) {
+ break;
+ }
+ }
+ }
+ }
+ return td_api::make_object<td_api::messageReaction>(get_reaction_type_object(reaction_), choose_count_, is_chosen_,
+ std::move(recent_choosers));
+}
+
+bool operator==(const MessageReaction &lhs, const MessageReaction &rhs) {
+ return lhs.reaction_ == rhs.reaction_ && lhs.choose_count_ == rhs.choose_count_ && lhs.is_chosen_ == rhs.is_chosen_ &&
+ lhs.recent_chooser_dialog_ids_ == rhs.recent_chooser_dialog_ids_;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageReaction &reaction) {
+ string_builder << '[' << reaction.reaction_ << (reaction.is_chosen_ ? " X " : " x ") << reaction.choose_count_;
+ if (!reaction.recent_chooser_dialog_ids_.empty()) {
+ string_builder << " by " << reaction.recent_chooser_dialog_ids_;
+ }
+ return string_builder << ']';
+}
+
+td_api::object_ptr<td_api::unreadReaction> UnreadMessageReaction::get_unread_reaction_object(Td *td) const {
+ auto sender_id = get_min_message_sender_object(td, sender_dialog_id_, "get_unread_reaction_object");
+ if (sender_id == nullptr) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::unreadReaction>(get_reaction_type_object(reaction_), std::move(sender_id),
+ is_big_);
+}
+
+bool operator==(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs) {
+ return lhs.reaction_ == rhs.reaction_ && lhs.sender_dialog_id_ == rhs.sender_dialog_id_ && lhs.is_big_ == rhs.is_big_;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReaction &unread_reaction) {
+ return string_builder << '[' << unread_reaction.reaction_ << (unread_reaction.is_big_ ? " BY " : " by ")
+ << unread_reaction.sender_dialog_id_ << ']';
+}
+
+unique_ptr<MessageReactions> MessageReactions::get_message_reactions(
+ Td *td, tl_object_ptr<telegram_api::messageReactions> &&reactions, bool is_bot) {
+ if (reactions == nullptr || is_bot) {
+ return nullptr;
+ }
+
+ auto result = make_unique<MessageReactions>();
+ result->can_get_added_reactions_ = reactions->can_see_list_;
+ result->is_min_ = reactions->min_;
+
+ FlatHashSet<string> reaction_strings;
+ vector<std::pair<int32, string>> chosen_reaction_order;
+ for (auto &reaction_count : reactions->results_) {
+ auto reaction_str = get_message_reaction_string(reaction_count->reaction_);
+ if (reaction_count->count_ <= 0 || reaction_count->count_ >= MessageReaction::MAX_CHOOSE_COUNT ||
+ reaction_str.empty()) {
+ LOG(ERROR) << "Receive reaction " << reaction_str << " with invalid count " << reaction_count->count_;
+ continue;
+ }
+
+ if (!reaction_strings.insert(reaction_str).second) {
+ LOG(ERROR) << "Receive duplicate reaction " << reaction_str;
+ continue;
+ }
+
+ FlatHashSet<DialogId, DialogIdHash> recent_choosers;
+ vector<DialogId> recent_chooser_dialog_ids;
+ vector<std::pair<ChannelId, MinChannel>> recent_chooser_min_channels;
+ for (auto &peer_reaction : reactions->recent_reactions_) {
+ auto peer_reaction_str = get_message_reaction_string(peer_reaction->reaction_);
+ if (peer_reaction_str == reaction_str) {
+ DialogId dialog_id(peer_reaction->peer_id_);
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << dialog_id << " as a recent chooser for reaction " << reaction_str;
+ continue;
+ }
+ if (!recent_choosers.insert(dialog_id).second) {
+ LOG(ERROR) << "Receive duplicate " << dialog_id << " as a recent chooser for reaction " << reaction_str;
+ continue;
+ }
+ if (!td->messages_manager_->have_dialog_info(dialog_id)) {
+ auto dialog_type = dialog_id.get_type();
+ if (dialog_type == DialogType::User) {
+ auto user_id = dialog_id.get_user_id();
+ if (!td->contacts_manager_->have_min_user(user_id)) {
+ LOG(ERROR) << "Receive unknown " << user_id;
+ continue;
+ }
+ } else if (dialog_type == DialogType::Channel) {
+ auto channel_id = dialog_id.get_channel_id();
+ auto min_channel = td->contacts_manager_->get_min_channel(channel_id);
+ if (min_channel == nullptr) {
+ LOG(ERROR) << "Receive unknown reacted " << channel_id;
+ continue;
+ }
+ recent_chooser_min_channels.emplace_back(channel_id, *min_channel);
+ } else {
+ LOG(ERROR) << "Receive unknown reacted " << dialog_id;
+ continue;
+ }
+ }
+
+ recent_chooser_dialog_ids.push_back(dialog_id);
+ if (peer_reaction->unread_) {
+ result->unread_reactions_.emplace_back(std::move(peer_reaction_str), dialog_id, peer_reaction->big_);
+ }
+ if (recent_chooser_dialog_ids.size() == MessageReaction::MAX_RECENT_CHOOSERS) {
+ break;
+ }
+ }
+ }
+
+ bool is_chosen = (reaction_count->flags_ & telegram_api::reactionCount::CHOSEN_ORDER_MASK) != 0;
+ if (is_chosen) {
+ chosen_reaction_order.emplace_back(reaction_count->chosen_order_, reaction_str);
+ }
+ result->reactions_.push_back({std::move(reaction_str), reaction_count->count_, is_chosen,
+ std::move(recent_chooser_dialog_ids), std::move(recent_chooser_min_channels)});
+ }
+ if (chosen_reaction_order.size() > 1) {
+ std::sort(chosen_reaction_order.begin(), chosen_reaction_order.end());
+ result->chosen_reaction_order_ =
+ transform(chosen_reaction_order, [](const std::pair<int32, string> &order) { return order.second; });
+ }
+ return result;
+}
+
+MessageReaction *MessageReactions::get_reaction(const string &reaction) {
+ for (auto &added_reaction : reactions_) {
+ if (added_reaction.get_reaction() == reaction) {
+ return &added_reaction;
+ }
+ }
+ return nullptr;
+}
+
+const MessageReaction *MessageReactions::get_reaction(const string &reaction) const {
+ for (auto &added_reaction : reactions_) {
+ if (added_reaction.get_reaction() == reaction) {
+ return &added_reaction;
+ }
+ }
+ return nullptr;
+}
+
+void MessageReactions::update_from(const MessageReactions &old_reactions) {
+ if (is_min_ && !old_reactions.is_min_) {
+ // chosen reaction was known, keep it
+ is_min_ = false;
+ for (const auto &old_reaction : old_reactions.reactions_) {
+ if (old_reaction.is_chosen()) {
+ auto *reaction = get_reaction(old_reaction.get_reaction());
+ if (reaction != nullptr) {
+ reaction->set_is_chosen(true, DialogId(), false);
+ }
+ }
+ }
+ unread_reactions_ = old_reactions.unread_reactions_;
+ chosen_reaction_order_ = old_reactions.chosen_reaction_order_;
+ }
+ for (const auto &old_reaction : old_reactions.reactions_) {
+ if (old_reaction.is_chosen() &&
+ old_reaction.get_recent_chooser_dialog_ids().size() == MessageReaction::MAX_RECENT_CHOOSERS + 1) {
+ auto *reaction = get_reaction(old_reaction.get_reaction());
+ if (reaction != nullptr && reaction->is_chosen()) {
+ reaction->update_recent_chooser_dialog_ids(old_reaction);
+ }
+ }
+ }
+}
+
+bool MessageReactions::add_reaction(const string &reaction, bool is_big, DialogId chooser_dialog_id,
+ bool have_recent_choosers) {
+ vector<string> new_chosen_reaction_order = get_chosen_reactions();
+
+ auto added_reaction = get_reaction(reaction);
+ if (added_reaction == nullptr) {
+ vector<DialogId> recent_chooser_dialog_ids;
+ if (have_recent_choosers) {
+ recent_chooser_dialog_ids.push_back(chooser_dialog_id);
+ }
+ reactions_.push_back({reaction, 1, true, std::move(recent_chooser_dialog_ids), Auto()});
+ new_chosen_reaction_order.emplace_back(reaction);
+ } else if (!added_reaction->is_chosen()) {
+ added_reaction->set_is_chosen(true, chooser_dialog_id, have_recent_choosers);
+ new_chosen_reaction_order.emplace_back(reaction);
+ } else if (!is_big) {
+ return false;
+ }
+
+ auto max_reaction_count = get_max_reaction_count();
+ while (new_chosen_reaction_order.size() > max_reaction_count) {
+ auto index = new_chosen_reaction_order[0] == reaction ? 1 : 0;
+ CHECK(static_cast<size_t>(index) < new_chosen_reaction_order.size());
+ bool is_removed = do_remove_reaction(new_chosen_reaction_order[index], chooser_dialog_id, have_recent_choosers);
+ CHECK(is_removed);
+ new_chosen_reaction_order.erase(new_chosen_reaction_order.begin() + index);
+ }
+
+ if (new_chosen_reaction_order.size() == 1) {
+ new_chosen_reaction_order.clear();
+ }
+ chosen_reaction_order_ = std::move(new_chosen_reaction_order);
+ return true;
+}
+
+bool MessageReactions::remove_reaction(const string &reaction, DialogId chooser_dialog_id, bool have_recent_choosers) {
+ if (do_remove_reaction(reaction, chooser_dialog_id, have_recent_choosers)) {
+ if (!chosen_reaction_order_.empty()) {
+ bool is_removed = td::remove(chosen_reaction_order_, reaction);
+ CHECK(is_removed);
+
+ auto max_reaction_count = get_max_reaction_count();
+ while (chosen_reaction_order_.size() > max_reaction_count) {
+ is_removed = do_remove_reaction(chosen_reaction_order_[0], chooser_dialog_id, have_recent_choosers);
+ CHECK(is_removed);
+ chosen_reaction_order_.erase(chosen_reaction_order_.begin());
+ }
+
+ if (chosen_reaction_order_.size() <= 1) {
+ reset_to_empty(chosen_reaction_order_);
+ }
+ }
+
+ return true;
+ }
+ return false;
+}
+
+bool MessageReactions::do_remove_reaction(const string &reaction, DialogId chooser_dialog_id,
+ bool have_recent_choosers) {
+ for (auto it = reactions_.begin(); it != reactions_.end(); ++it) {
+ auto &message_reaction = *it;
+ if (message_reaction.get_reaction() == reaction) {
+ if (message_reaction.is_chosen()) {
+ message_reaction.set_is_chosen(false, chooser_dialog_id, have_recent_choosers);
+ if (message_reaction.is_empty()) {
+ it = reactions_.erase(it);
+ }
+ return true;
+ }
+ break;
+ }
+ }
+ return false;
+}
+
+void MessageReactions::sort_reactions(const FlatHashMap<string, size_t> &active_reaction_pos) {
+ std::sort(reactions_.begin(), reactions_.end(),
+ [&active_reaction_pos](const MessageReaction &lhs, const MessageReaction &rhs) {
+ if (lhs.get_choose_count() != rhs.get_choose_count()) {
+ return lhs.get_choose_count() > rhs.get_choose_count();
+ }
+ auto lhs_it = active_reaction_pos.find(lhs.get_reaction());
+ auto lhs_pos = lhs_it != active_reaction_pos.end() ? lhs_it->second : active_reaction_pos.size();
+ auto rhs_it = active_reaction_pos.find(rhs.get_reaction());
+ auto rhs_pos = rhs_it != active_reaction_pos.end() ? rhs_it->second : active_reaction_pos.size();
+ if (lhs_pos != rhs_pos) {
+ return lhs_pos < rhs_pos;
+ }
+
+ return lhs.get_reaction() < rhs.get_reaction();
+ });
+}
+
+void MessageReactions::fix_chosen_reaction(DialogId my_dialog_id) {
+ bool need_fix = false;
+ for (auto &reaction : reactions_) {
+ if (!reaction.is_chosen() && reaction.remove_recent_chooser_dialog_id(my_dialog_id)) {
+ LOG(WARNING) << "Fix recent chosen reaction in " << *this;
+ need_fix = true;
+ }
+ }
+ if (!need_fix) {
+ return;
+ }
+ for (auto &reaction : reactions_) {
+ if (reaction.is_chosen() && !td::contains(reaction.get_recent_chooser_dialog_ids(), my_dialog_id)) {
+ reaction.add_recent_chooser_dialog_id(my_dialog_id);
+ }
+ }
+}
+
+vector<string> MessageReactions::get_chosen_reactions() const {
+ if (!chosen_reaction_order_.empty()) {
+ return chosen_reaction_order_;
+ }
+
+ vector<string> reaction_order;
+ for (auto &reaction : reactions_) {
+ if (reaction.is_chosen()) {
+ reaction_order.push_back(reaction.get_reaction());
+ }
+ }
+ return reaction_order;
+}
+
+bool MessageReactions::are_consistent_with_list(const string &reaction, FlatHashMap<string, vector<DialogId>> reactions,
+ int32 total_count) const {
+ auto are_consistent = [](const vector<DialogId> &lhs, const vector<DialogId> &rhs) {
+ size_t i = 0;
+ size_t max_i = td::min(lhs.size(), rhs.size());
+ while (i < max_i && lhs[i] == rhs[i]) {
+ i++;
+ }
+ return i == max_i;
+ };
+
+ if (reaction.empty()) {
+ // received list and total_count for all reactions
+ int32 old_total_count = 0;
+ for (const auto &message_reaction : reactions_) {
+ CHECK(!message_reaction.get_reaction().empty());
+ if (!are_consistent(reactions[message_reaction.get_reaction()],
+ message_reaction.get_recent_chooser_dialog_ids())) {
+ return false;
+ }
+ old_total_count += message_reaction.get_choose_count();
+ reactions.erase(message_reaction.get_reaction());
+ }
+ return old_total_count == total_count && reactions.empty();
+ }
+
+ // received list and total_count for a single reaction
+ const auto *message_reaction = get_reaction(reaction);
+ if (message_reaction == nullptr) {
+ return reactions.count(reaction) == 0 && total_count == 0;
+ } else {
+ return are_consistent(reactions[reaction], message_reaction->get_recent_chooser_dialog_ids()) &&
+ message_reaction->get_choose_count() == total_count;
+ }
+}
+
+vector<td_api::object_ptr<td_api::messageReaction>> MessageReactions::get_message_reactions_object(
+ Td *td, UserId my_user_id, UserId peer_user_id) const {
+ return transform(reactions_, [td, my_user_id, peer_user_id](const MessageReaction &reaction) {
+ return reaction.get_message_reaction_object(td, my_user_id, peer_user_id);
+ });
+}
+
+void MessageReactions::add_min_channels(Td *td) const {
+ for (const auto &reaction : reactions_) {
+ for (const auto &recent_chooser_min_channel : reaction.get_recent_chooser_min_channels()) {
+ LOG(INFO) << "Add min reacted " << recent_chooser_min_channel.first;
+ td->contacts_manager_->add_min_channel(recent_chooser_min_channel.first, recent_chooser_min_channel.second);
+ }
+ }
+}
+
+void MessageReactions::add_dependencies(Dependencies &dependencies) const {
+ for (const auto &reaction : reactions_) {
+ const auto &dialog_ids = reaction.get_recent_chooser_dialog_ids();
+ for (auto dialog_id : dialog_ids) {
+ // don't load the dialog itself
+ // it will be created in get_message_reaction_object if needed
+ dependencies.add_dialog_dependencies(dialog_id);
+ }
+ }
+}
+
+bool MessageReactions::need_update_message_reactions(const MessageReactions *old_reactions,
+ const MessageReactions *new_reactions) {
+ if (old_reactions == nullptr) {
+ // add reactions
+ return new_reactions != nullptr;
+ }
+ if (new_reactions == nullptr) {
+ // remove reactions when they are disabled
+ return true;
+ }
+
+ // unread_reactions_ and chosen_reaction_order_ are updated independently; compare all other fields
+ return old_reactions->reactions_ != new_reactions->reactions_ || old_reactions->is_min_ != new_reactions->is_min_ ||
+ old_reactions->can_get_added_reactions_ != new_reactions->can_get_added_reactions_ ||
+ old_reactions->need_polling_ != new_reactions->need_polling_;
+}
+
+bool MessageReactions::need_update_unread_reactions(const MessageReactions *old_reactions,
+ const MessageReactions *new_reactions) {
+ if (old_reactions == nullptr || old_reactions->unread_reactions_.empty()) {
+ return !(new_reactions == nullptr || new_reactions->unread_reactions_.empty());
+ }
+ return new_reactions == nullptr || old_reactions->unread_reactions_ != new_reactions->unread_reactions_;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions) {
+ return string_builder << (reactions.is_min_ ? "Min" : "") << "MessageReactions{" << reactions.reactions_
+ << " with unread " << reactions.unread_reactions_ << ", reaction order "
+ << reactions.chosen_reaction_order_
+ << " and can_get_added_reactions = " << reactions.can_get_added_reactions_;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr<MessageReactions> &reactions) {
+ if (reactions == nullptr) {
+ return string_builder << "null";
+ }
+ return string_builder << *reactions;
+}
+
+bool is_custom_reaction(const string &reaction) {
+ return reaction[0] == '#';
+}
+
+bool is_active_reaction(const string &reaction, const FlatHashMap<string, size_t> &active_reaction_pos) {
+ return !reaction.empty() && (is_custom_reaction(reaction) || active_reaction_pos.count(reaction) > 0);
+}
+
+void reload_message_reactions(Td *td, DialogId dialog_id, vector<MessageId> &&message_ids) {
+ if (!td->messages_manager_->have_input_peer(dialog_id, AccessRights::Read) || message_ids.empty()) {
+ return;
+ }
+
+ for (const auto &message_id : message_ids) {
+ CHECK(message_id.is_valid());
+ CHECK(message_id.is_server());
+ }
+
+ td->create_handler<GetMessagesReactionsQuery>()->send(dialog_id, std::move(message_ids));
+}
+
+void send_message_reaction(Td *td, FullMessageId full_message_id, vector<string> reactions, bool is_big,
+ bool add_to_recent, Promise<Unit> &&promise) {
+ td->create_handler<SendReactionQuery>(std::move(promise))
+ ->send(full_message_id, std::move(reactions), is_big, add_to_recent);
+}
+
+void get_message_added_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::addedReactions>> &&promise) {
+ if (!td->messages_manager_->have_message_force(full_message_id, "get_message_added_reactions")) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ auto message_id = full_message_id.get_message_id();
+ if (full_message_id.get_dialog_id().get_type() == DialogType::SecretChat || !message_id.is_valid() ||
+ !message_id.is_server()) {
+ return promise.set_value(td_api::make_object<td_api::addedReactions>(0, Auto(), string()));
+ }
+
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ }
+ static constexpr int32 MAX_GET_ADDED_REACTIONS = 100; // server side limit
+ if (limit > MAX_GET_ADDED_REACTIONS) {
+ limit = MAX_GET_ADDED_REACTIONS;
+ }
+
+ td->create_handler<GetMessageReactionsListQuery>(std::move(promise))
+ ->send(full_message_id, std::move(reaction), std::move(offset), limit);
+}
+
+void set_default_reaction(Td *td, string reaction, Promise<Unit> &&promise) {
+ if (reaction.empty()) {
+ return promise.set_error(Status::Error(400, "Default reaction must be non-empty"));
+ }
+ if (!is_custom_reaction(reaction) && !td->stickers_manager_->is_active_reaction(reaction)) {
+ return promise.set_error(Status::Error(400, "Can't set incative reaction as default"));
+ }
+
+ if (td->option_manager_->get_option_string("default_reaction", "-") != reaction) {
+ td->option_manager_->set_option_string("default_reaction", reaction);
+ if (!td->option_manager_->get_option_boolean("default_reaction_needs_sync")) {
+ td->option_manager_->set_option_boolean("default_reaction_needs_sync", true);
+ send_set_default_reaction_query(td);
+ }
+ }
+ promise.set_value(Unit());
+}
+
+void send_set_default_reaction_query(Td *td) {
+ td->create_handler<SetDefaultReactionQuery>()->send(td->option_manager_->get_option_string("default_reaction"));
+}
+
+void send_update_default_reaction_type(const string &default_reaction) {
+ if (default_reaction.empty()) {
+ LOG(ERROR) << "Have no default reaction";
+ return;
+ }
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateDefaultReactionType>(get_reaction_type_object(default_reaction)));
+}
+
+void report_message_reactions(Td *td, FullMessageId full_message_id, DialogId chooser_dialog_id,
+ Promise<Unit> &&promise) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ if (!td->messages_manager_->have_dialog_force(dialog_id, "send_callback_query")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!td->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ if (!td->messages_manager_->have_message_force(full_message_id, "report_user_reactions")) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ auto message_id = full_message_id.get_message_id();
+ if (message_id.is_valid_scheduled()) {
+ return promise.set_error(Status::Error(400, "Can't report reactions on scheduled messages"));
+ }
+ if (!message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Message reactions can't be reported"));
+ }
+
+ if (!td->messages_manager_->have_input_peer(chooser_dialog_id, AccessRights::Know)) {
+ return promise.set_error(Status::Error(400, "Reaction sender not found"));
+ }
+
+ td->create_handler<ReportReactionQuery>(std::move(promise))->send(dialog_id, message_id, chooser_dialog_id);
+}
+
+vector<string> get_recent_reactions(Td *td) {
+ return td->stickers_manager_->get_recent_reactions();
+}
+
+vector<string> get_top_reactions(Td *td) {
+ return td->stickers_manager_->get_top_reactions();
+}
+
+void add_recent_reaction(Td *td, const string &reaction) {
+ td->stickers_manager_->add_recent_reaction(reaction);
+}
+
+int64 get_reactions_hash(const vector<string> &reactions) {
+ vector<uint64> numbers;
+ for (auto &reaction : reactions) {
+ if (is_custom_reaction(reaction)) {
+ auto custom_emoji_id = static_cast<uint64>(get_custom_emoji_id(reaction));
+ numbers.push_back(custom_emoji_id >> 32);
+ numbers.push_back(custom_emoji_id & 0xFFFFFFFF);
+ } else {
+ auto emoji = remove_emoji_selectors(reaction);
+ unsigned char hash[16];
+ md5(emoji, {hash, sizeof(hash)});
+ auto get = [hash](int num) {
+ return static_cast<uint32>(hash[num]);
+ };
+
+ numbers.push_back(0);
+ numbers.push_back(static_cast<int32>((get(0) << 24) + (get(1) << 16) + (get(2) << 8) + get(3)));
+ }
+ }
+ return get_vector_hash(numbers);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageReaction.h b/protocols/Telegram/tdlib/td/td/telegram/MessageReaction.h
new file mode 100644
index 0000000000..e6c04a7eab
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageReaction.h
@@ -0,0 +1,240 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/MinChannel.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
+#include "td/utils/StringBuilder.h"
+
+#include <utility>
+
+namespace td {
+
+class Dependencies;
+
+class Td;
+
+class MessageReaction {
+ static constexpr int32 MAX_CHOOSE_COUNT = 2147483640;
+
+ static constexpr size_t MAX_RECENT_CHOOSERS = 3;
+
+ string reaction_;
+ int32 choose_count_ = 0;
+ bool is_chosen_ = false;
+ vector<DialogId> recent_chooser_dialog_ids_;
+ vector<std::pair<ChannelId, MinChannel>> recent_chooser_min_channels_;
+
+ friend bool operator==(const MessageReaction &lhs, const MessageReaction &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageReaction &message_reaction);
+
+ friend struct MessageReactions;
+
+ MessageReaction(string reaction, int32 choose_count, bool is_chosen, vector<DialogId> &&recent_chooser_dialog_ids,
+ vector<std::pair<ChannelId, MinChannel>> &&recent_chooser_min_channels)
+ : reaction_(std::move(reaction))
+ , choose_count_(choose_count)
+ , is_chosen_(is_chosen)
+ , recent_chooser_dialog_ids_(std::move(recent_chooser_dialog_ids))
+ , recent_chooser_min_channels_(std::move(recent_chooser_min_channels)) {
+ }
+
+ bool is_empty() const {
+ return choose_count_ <= 0;
+ }
+
+ bool is_chosen() const {
+ return is_chosen_;
+ }
+
+ void set_is_chosen(bool is_chosen, DialogId chooser_dialog_id, bool have_recent_choosers);
+
+ void add_recent_chooser_dialog_id(DialogId dialog_id);
+
+ bool remove_recent_chooser_dialog_id(DialogId dialog_id);
+
+ void update_recent_chooser_dialog_ids(const MessageReaction &old_reaction);
+
+ int32 get_choose_count() const {
+ return choose_count_;
+ }
+
+ const vector<DialogId> &get_recent_chooser_dialog_ids() const {
+ return recent_chooser_dialog_ids_;
+ }
+
+ const vector<std::pair<ChannelId, MinChannel>> &get_recent_chooser_min_channels() const {
+ return recent_chooser_min_channels_;
+ }
+
+ td_api::object_ptr<td_api::messageReaction> get_message_reaction_object(Td *td, UserId my_user_id,
+ UserId peer_user_id) const;
+
+ public:
+ MessageReaction() = default;
+
+ const string &get_reaction() const {
+ return reaction_;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+bool operator==(const MessageReaction &lhs, const MessageReaction &rhs);
+
+inline bool operator!=(const MessageReaction &lhs, const MessageReaction &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageReaction &reaction);
+
+class UnreadMessageReaction {
+ string reaction_;
+ DialogId sender_dialog_id_;
+ bool is_big_ = false;
+
+ friend bool operator==(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReaction &message_reaction);
+
+ public:
+ UnreadMessageReaction() = default;
+
+ UnreadMessageReaction(string reaction, DialogId sender_dialog_id, bool is_big)
+ : reaction_(std::move(reaction)), sender_dialog_id_(sender_dialog_id), is_big_(is_big) {
+ }
+
+ td_api::object_ptr<td_api::unreadReaction> get_unread_reaction_object(Td *td) const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+bool operator==(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs);
+
+inline bool operator!=(const UnreadMessageReaction &lhs, const UnreadMessageReaction &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const UnreadMessageReaction &unread_reaction);
+
+struct MessageReactions {
+ vector<MessageReaction> reactions_;
+ vector<UnreadMessageReaction> unread_reactions_;
+ vector<string> chosen_reaction_order_;
+ bool is_min_ = false;
+ bool need_polling_ = true;
+ bool can_get_added_reactions_ = false;
+
+ MessageReactions() = default;
+
+ static unique_ptr<MessageReactions> get_message_reactions(Td *td,
+ tl_object_ptr<telegram_api::messageReactions> &&reactions,
+ bool is_bot);
+
+ MessageReaction *get_reaction(const string &reaction);
+
+ const MessageReaction *get_reaction(const string &reaction) const;
+
+ void update_from(const MessageReactions &old_reactions);
+
+ bool add_reaction(const string &reaction, bool is_big, DialogId chooser_dialog_id, bool have_recent_choosers);
+
+ bool remove_reaction(const string &reaction, DialogId chooser_dialog_id, bool have_recent_choosers);
+
+ void sort_reactions(const FlatHashMap<string, size_t> &active_reaction_pos);
+
+ void fix_chosen_reaction(DialogId my_dialog_id);
+
+ vector<string> get_chosen_reactions() const;
+
+ bool are_consistent_with_list(const string &reaction, FlatHashMap<string, vector<DialogId>> reactions,
+ int32 total_count) const;
+
+ vector<td_api::object_ptr<td_api::messageReaction>> get_message_reactions_object(Td *td, UserId my_user_id,
+ UserId peer_user_id) const;
+
+ void add_min_channels(Td *td) const;
+
+ void add_dependencies(Dependencies &dependencies) const;
+
+ static bool need_update_message_reactions(const MessageReactions *old_reactions,
+ const MessageReactions *new_reactions);
+
+ static bool need_update_unread_reactions(const MessageReactions *old_reactions,
+ const MessageReactions *new_reactions);
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+
+ private:
+ bool do_remove_reaction(const string &reaction, DialogId chooser_dialog_id, bool have_recent_choosers);
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageReactions &reactions);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr<MessageReactions> &reactions);
+
+telegram_api::object_ptr<telegram_api::Reaction> get_input_reaction(const string &reaction);
+
+td_api::object_ptr<td_api::ReactionType> get_reaction_type_object(const string &reaction);
+
+string get_message_reaction_string(const telegram_api::object_ptr<telegram_api::Reaction> &reaction);
+
+string get_message_reaction_string(const td_api::object_ptr<td_api::ReactionType> &type);
+
+bool is_custom_reaction(const string &reaction);
+
+bool is_active_reaction(const string &reaction, const FlatHashMap<string, size_t> &active_reaction_pos);
+
+void reload_message_reactions(Td *td, DialogId dialog_id, vector<MessageId> &&message_ids);
+
+void send_message_reaction(Td *td, FullMessageId full_message_id, vector<string> reactions, bool is_big,
+ bool add_to_recent, Promise<Unit> &&promise);
+
+void get_message_added_reactions(Td *td, FullMessageId full_message_id, string reaction, string offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::addedReactions>> &&promise);
+
+void set_default_reaction(Td *td, string reaction, Promise<Unit> &&promise);
+
+void send_set_default_reaction_query(Td *td);
+
+void send_update_default_reaction_type(const string &default_reaction);
+
+void report_message_reactions(Td *td, FullMessageId full_message_id, DialogId chooser_dialog_id,
+ Promise<Unit> &&promise);
+
+vector<string> get_recent_reactions(Td *td);
+
+vector<string> get_top_reactions(Td *td);
+
+void add_recent_reaction(Td *td, const string &reaction);
+
+int64 get_reactions_hash(const vector<string> &reactions);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageReaction.hpp b/protocols/Telegram/tdlib/td/td/telegram/MessageReaction.hpp
new file mode 100644
index 0000000000..040604b58a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageReaction.hpp
@@ -0,0 +1,124 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MessageReaction.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void MessageReaction::store(StorerT &storer) const {
+ CHECK(!is_empty());
+ bool has_recent_chooser_dialog_ids = !recent_chooser_dialog_ids_.empty();
+ bool has_recent_chooser_min_channels = !recent_chooser_min_channels_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_chosen_);
+ STORE_FLAG(has_recent_chooser_dialog_ids);
+ STORE_FLAG(has_recent_chooser_min_channels);
+ END_STORE_FLAGS();
+ td::store(reaction_, storer);
+ td::store(choose_count_, storer);
+ if (has_recent_chooser_dialog_ids) {
+ td::store(recent_chooser_dialog_ids_, storer);
+ }
+ if (has_recent_chooser_min_channels) {
+ td::store(recent_chooser_min_channels_, storer);
+ }
+}
+
+template <class ParserT>
+void MessageReaction::parse(ParserT &parser) {
+ bool has_recent_chooser_dialog_ids;
+ bool has_recent_chooser_min_channels;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_chosen_);
+ PARSE_FLAG(has_recent_chooser_dialog_ids);
+ PARSE_FLAG(has_recent_chooser_min_channels);
+ END_PARSE_FLAGS();
+ td::parse(reaction_, parser);
+ td::parse(choose_count_, parser);
+ if (has_recent_chooser_dialog_ids) {
+ td::parse(recent_chooser_dialog_ids_, parser);
+ }
+ if (has_recent_chooser_min_channels) {
+ td::parse(recent_chooser_min_channels_, parser);
+ }
+ CHECK(!is_empty());
+ CHECK(!reaction_.empty());
+}
+
+template <class StorerT>
+void UnreadMessageReaction::store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_big_);
+ END_STORE_FLAGS();
+ td::store(reaction_, storer);
+ td::store(sender_dialog_id_, storer);
+}
+
+template <class ParserT>
+void UnreadMessageReaction::parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_big_);
+ END_PARSE_FLAGS();
+ td::parse(reaction_, parser);
+ td::parse(sender_dialog_id_, parser);
+ CHECK(!reaction_.empty());
+}
+
+template <class StorerT>
+void MessageReactions::store(StorerT &storer) const {
+ bool has_reactions = !reactions_.empty();
+ bool has_unread_reactions = !unread_reactions_.empty();
+ bool has_chosen_reaction_order = !chosen_reaction_order_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_min_);
+ STORE_FLAG(need_polling_);
+ STORE_FLAG(can_get_added_reactions_);
+ STORE_FLAG(has_unread_reactions);
+ STORE_FLAG(has_reactions);
+ STORE_FLAG(has_chosen_reaction_order);
+ END_STORE_FLAGS();
+ if (has_reactions) {
+ td::store(reactions_, storer);
+ }
+ if (has_unread_reactions) {
+ td::store(unread_reactions_, storer);
+ }
+ if (has_chosen_reaction_order) {
+ td::store(chosen_reaction_order_, storer);
+ }
+}
+
+template <class ParserT>
+void MessageReactions::parse(ParserT &parser) {
+ bool has_reactions;
+ bool has_unread_reactions;
+ bool has_chosen_reaction_order;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_min_);
+ PARSE_FLAG(need_polling_);
+ PARSE_FLAG(can_get_added_reactions_);
+ PARSE_FLAG(has_unread_reactions);
+ PARSE_FLAG(has_reactions);
+ PARSE_FLAG(has_chosen_reaction_order);
+ END_PARSE_FLAGS();
+ if (has_reactions) {
+ td::parse(reactions_, parser);
+ }
+ if (has_unread_reactions) {
+ td::parse(unread_reactions_, parser);
+ }
+ if (has_chosen_reaction_order) {
+ td::parse(chosen_reaction_order_, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageReplyHeader.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyHeader.cpp
new file mode 100644
index 0000000000..a2cb530888
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyHeader.cpp
@@ -0,0 +1,62 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageReplyHeader.h"
+
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/ScheduledServerMessageId.h"
+#include "td/telegram/ServerMessageId.h"
+
+#include "td/utils/logging.h"
+
+namespace td {
+
+MessageReplyHeader::MessageReplyHeader(tl_object_ptr<telegram_api::messageReplyHeader> &&reply_header,
+ DialogId dialog_id, MessageId message_id, int32 date, bool can_have_thread) {
+ if (reply_header == nullptr) {
+ return;
+ }
+ if (reply_header->reply_to_scheduled_) {
+ reply_to_message_id = MessageId(ScheduledServerMessageId(reply_header->reply_to_msg_id_), date);
+ if (message_id.is_scheduled()) {
+ auto reply_to_peer_id = std::move(reply_header->reply_to_peer_id_);
+ if (reply_to_peer_id != nullptr) {
+ reply_in_dialog_id = DialogId(reply_to_peer_id);
+ LOG(ERROR) << "Receive reply to " << FullMessageId{reply_in_dialog_id, reply_to_message_id} << " in "
+ << FullMessageId{dialog_id, message_id};
+ reply_to_message_id = MessageId();
+ reply_in_dialog_id = DialogId();
+ }
+ } else {
+ LOG(ERROR) << "Receive reply to " << reply_to_message_id << " in " << FullMessageId{dialog_id, message_id};
+ reply_to_message_id = MessageId();
+ }
+ } else {
+ reply_to_message_id = MessageId(ServerMessageId(reply_header->reply_to_msg_id_));
+ auto reply_to_peer_id = std::move(reply_header->reply_to_peer_id_);
+ if (reply_to_peer_id != nullptr) {
+ reply_in_dialog_id = DialogId(reply_to_peer_id);
+ if (!reply_in_dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive reply in invalid " << to_string(reply_to_peer_id);
+ reply_to_message_id = MessageId();
+ reply_in_dialog_id = DialogId();
+ }
+ if (reply_in_dialog_id == dialog_id) {
+ reply_in_dialog_id = DialogId(); // just in case
+ }
+ }
+ if (reply_to_message_id.is_valid() && !message_id.is_scheduled() && !reply_in_dialog_id.is_valid()) {
+ if ((reply_header->flags_ & telegram_api::messageReplyHeader::REPLY_TO_TOP_ID_MASK) != 0) {
+ top_thread_message_id = MessageId(ServerMessageId(reply_header->reply_to_top_id_));
+ } else if (can_have_thread) {
+ top_thread_message_id = reply_to_message_id;
+ }
+ is_topic_message = reply_header->forum_topic_;
+ }
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageReplyHeader.h b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyHeader.h
new file mode 100644
index 0000000000..bdabdb4396
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyHeader.h
@@ -0,0 +1,29 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+struct MessageReplyHeader {
+ MessageId reply_to_message_id;
+ DialogId reply_in_dialog_id;
+ MessageId top_thread_message_id;
+ bool is_topic_message = false;
+
+ MessageReplyHeader() = default;
+
+ MessageReplyHeader(tl_object_ptr<telegram_api::messageReplyHeader> &&reply_header, DialogId dialog_id,
+ MessageId message_id, int32 date, bool can_have_thread);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.cpp
new file mode 100644
index 0000000000..226abc010b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.cpp
@@ -0,0 +1,232 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageReplyInfo.h"
+
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/MessageSender.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+
+namespace td {
+
+MessageReplyInfo::MessageReplyInfo(Td *td, tl_object_ptr<telegram_api::messageReplies> &&reply_info, bool is_bot) {
+ if (reply_info == nullptr) {
+ return;
+ }
+ if (reply_info->replies_ < 0) {
+ LOG(ERROR) << "Receive wrong " << to_string(reply_info);
+ return;
+ }
+ if (is_bot || reply_info->channel_id_ == 777) {
+ is_dropped_ = true;
+ return;
+ }
+ reply_count_ = reply_info->replies_;
+ pts_ = reply_info->replies_pts_;
+
+ is_comment_ = reply_info->comments_;
+
+ if (is_comment_) {
+ channel_id_ = ChannelId(reply_info->channel_id_);
+ if (!channel_id_.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << channel_id_;
+ channel_id_ = ChannelId();
+ is_comment_ = false;
+ }
+ }
+
+ if (is_comment_) {
+ for (const auto &peer : reply_info->recent_repliers_) {
+ DialogId dialog_id(peer);
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive " << dialog_id << " as a recent replier";
+ continue;
+ }
+ if (td::contains(recent_replier_dialog_ids_, dialog_id)) {
+ LOG(ERROR) << "Receive duplicate " << dialog_id << " as a recent replier";
+ continue;
+ }
+ if (!td->messages_manager_->have_dialog_info(dialog_id)) {
+ auto dialog_type = dialog_id.get_type();
+ if (dialog_type == DialogType::User) {
+ auto replier_user_id = dialog_id.get_user_id();
+ if (!td->contacts_manager_->have_min_user(replier_user_id)) {
+ LOG(ERROR) << "Receive unknown replied " << replier_user_id;
+ continue;
+ }
+ } else if (dialog_type == DialogType::Channel) {
+ auto replier_channel_id = dialog_id.get_channel_id();
+ auto min_channel = td->contacts_manager_->get_min_channel(replier_channel_id);
+ if (min_channel == nullptr) {
+ LOG(ERROR) << "Receive unknown replied " << replier_channel_id;
+ continue;
+ }
+ replier_min_channels_.emplace_back(replier_channel_id, *min_channel);
+ } else {
+ LOG(ERROR) << "Receive unknown replied " << dialog_id;
+ continue;
+ }
+ }
+ recent_replier_dialog_ids_.push_back(dialog_id);
+ if (recent_replier_dialog_ids_.size() == MAX_RECENT_REPLIERS) {
+ break;
+ }
+ }
+ }
+ if ((reply_info->flags_ & telegram_api::messageReplies::MAX_ID_MASK) != 0 &&
+ ServerMessageId(reply_info->max_id_).is_valid()) {
+ max_message_id_ = MessageId(ServerMessageId(reply_info->max_id_));
+ }
+ if ((reply_info->flags_ & telegram_api::messageReplies::READ_MAX_ID_MASK) != 0 &&
+ ServerMessageId(reply_info->read_max_id_).is_valid()) {
+ last_read_inbox_message_id_ = MessageId(ServerMessageId(reply_info->read_max_id_));
+ }
+ if (last_read_inbox_message_id_ > max_message_id_) { // possible if last thread message was deleted after it was read
+ max_message_id_ = last_read_inbox_message_id_;
+ }
+ LOG(DEBUG) << "Parsed " << oneline(to_string(reply_info)) << " to " << *this;
+}
+
+bool MessageReplyInfo::need_update_to(const MessageReplyInfo &other) const {
+ if (other.is_empty() && !is_empty()) {
+ // ignore updates to empty reply info, because we will hide the info ourselves
+ // return true;
+ }
+ if (other.pts_ < pts_ && !other.was_dropped()) {
+ return false;
+ }
+ return reply_count_ != other.reply_count_ || recent_replier_dialog_ids_ != other.recent_replier_dialog_ids_ ||
+ replier_min_channels_.size() != other.replier_min_channels_.size() || is_comment_ != other.is_comment_ ||
+ channel_id_ != other.channel_id_;
+}
+
+bool MessageReplyInfo::update_max_message_ids(const MessageReplyInfo &other) {
+ return update_max_message_ids(other.max_message_id_, other.last_read_inbox_message_id_,
+ other.last_read_outbox_message_id_);
+}
+
+bool MessageReplyInfo::update_max_message_ids(MessageId other_max_message_id,
+ MessageId other_last_read_inbox_message_id,
+ MessageId other_last_read_outbox_message_id) {
+ bool result = false;
+ if (other_last_read_inbox_message_id > last_read_inbox_message_id_) {
+ last_read_inbox_message_id_ = other_last_read_inbox_message_id;
+ result = true;
+ }
+ if (other_last_read_outbox_message_id > last_read_outbox_message_id_) {
+ last_read_outbox_message_id_ = other_last_read_outbox_message_id;
+ result = true;
+ }
+ if (other_max_message_id.is_valid() ||
+ (!other_last_read_inbox_message_id.is_valid() && !other_last_read_outbox_message_id.is_valid())) {
+ if (other_max_message_id < last_read_inbox_message_id_) {
+ other_max_message_id = last_read_inbox_message_id_;
+ }
+ if (other_max_message_id < last_read_outbox_message_id_) {
+ other_max_message_id = last_read_outbox_message_id_;
+ }
+ if (other_max_message_id != max_message_id_) {
+ max_message_id_ = other_max_message_id;
+ result = true;
+ }
+ }
+ return result;
+}
+
+bool MessageReplyInfo::add_reply(DialogId replier_dialog_id, MessageId reply_message_id, int diff) {
+ CHECK(!is_empty());
+ CHECK(diff == +1 || diff == -1);
+
+ if (diff == -1 && reply_count_ == 0) {
+ return false;
+ }
+
+ reply_count_ += diff;
+ if (is_comment_ && replier_dialog_id.is_valid()) {
+ if (replier_dialog_id.get_type() == DialogType::Channel) {
+ // the replier_dialog_id is never min, because it is the sender of a message
+ for (auto it = replier_min_channels_.begin(); it != replier_min_channels_.end(); ++it) {
+ if (it->first == replier_dialog_id.get_channel_id()) {
+ replier_min_channels_.erase(it);
+ break;
+ }
+ }
+ }
+
+ td::remove(recent_replier_dialog_ids_, replier_dialog_id);
+ if (diff > 0) {
+ recent_replier_dialog_ids_.insert(recent_replier_dialog_ids_.begin(), replier_dialog_id);
+ if (recent_replier_dialog_ids_.size() > MAX_RECENT_REPLIERS) {
+ recent_replier_dialog_ids_.pop_back();
+ }
+ } else {
+ auto max_repliers = static_cast<size_t>(reply_count_);
+ if (recent_replier_dialog_ids_.size() > max_repliers) {
+ recent_replier_dialog_ids_.resize(max_repliers);
+ }
+ }
+ }
+
+ if (diff > 0 && reply_message_id > max_message_id_) {
+ max_message_id_ = reply_message_id;
+ }
+ return true;
+}
+
+bool MessageReplyInfo::need_reget(const Td *td) const {
+ for (auto &dialog_id : recent_replier_dialog_ids_) {
+ if (dialog_id.get_type() != DialogType::User && !td->messages_manager_->have_dialog_info(dialog_id)) {
+ if (dialog_id.get_type() == DialogType::Channel &&
+ td->contacts_manager_->have_min_channel(dialog_id.get_channel_id())) {
+ return false;
+ }
+ LOG(INFO) << "Reget a message because of replied " << dialog_id;
+ return true;
+ }
+ }
+ return false;
+}
+
+td_api::object_ptr<td_api::messageReplyInfo> MessageReplyInfo::get_message_reply_info_object(
+ Td *td, MessageId dialog_last_read_inbox_message_id) const {
+ if (is_empty()) {
+ return nullptr;
+ }
+
+ vector<td_api::object_ptr<td_api::MessageSender>> recent_repliers;
+ for (auto dialog_id : recent_replier_dialog_ids_) {
+ auto recent_replier = get_min_message_sender_object(td, dialog_id, "get_message_reply_info_object");
+ if (recent_replier != nullptr) {
+ recent_repliers.push_back(std::move(recent_replier));
+ }
+ }
+ auto last_read_inbox_message_id = last_read_inbox_message_id_;
+ if (last_read_inbox_message_id.is_valid() && last_read_inbox_message_id < dialog_last_read_inbox_message_id) {
+ last_read_inbox_message_id = min(dialog_last_read_inbox_message_id, max_message_id_);
+ }
+ return td_api::make_object<td_api::messageReplyInfo>(reply_count_, std::move(recent_repliers),
+ last_read_inbox_message_id.get(),
+ last_read_outbox_message_id_.get(), max_message_id_.get());
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageReplyInfo &reply_info) {
+ if (reply_info.is_comment_) {
+ return string_builder << reply_info.reply_count_ << " comments in " << reply_info.channel_id_ << " by "
+ << reply_info.recent_replier_dialog_ids_ << " read up to "
+ << reply_info.last_read_inbox_message_id_ << "/" << reply_info.last_read_outbox_message_id_;
+ } else {
+ return string_builder << reply_info.reply_count_ << " replies read up to " << reply_info.last_read_inbox_message_id_
+ << "/" << reply_info.last_read_outbox_message_id_;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.h b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.h
new file mode 100644
index 0000000000..1daf721684
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.h
@@ -0,0 +1,74 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/MinChannel.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+#include <utility>
+
+namespace td {
+
+class Td;
+
+struct MessageReplyInfo {
+ int32 reply_count_ = -1;
+ int32 pts_ = -1;
+ vector<DialogId> recent_replier_dialog_ids_; // comments only
+ vector<std::pair<ChannelId, MinChannel>> replier_min_channels_; // comments only
+ ChannelId channel_id_; // comments only
+ MessageId max_message_id_;
+ MessageId last_read_inbox_message_id_;
+ MessageId last_read_outbox_message_id_;
+ bool is_comment_ = false;
+ bool is_dropped_ = false;
+
+ static constexpr size_t MAX_RECENT_REPLIERS = 3;
+
+ MessageReplyInfo() = default;
+
+ MessageReplyInfo(Td *td, tl_object_ptr<telegram_api::messageReplies> &&reply_info, bool is_bot);
+
+ bool is_empty() const {
+ return reply_count_ < 0;
+ }
+
+ bool was_dropped() const {
+ return is_dropped_;
+ }
+
+ bool need_update_to(const MessageReplyInfo &other) const;
+
+ bool update_max_message_ids(MessageId other_max_message_id, MessageId other_last_read_inbox_message_id,
+ MessageId other_last_read_outbox_message_id);
+
+ bool update_max_message_ids(const MessageReplyInfo &other);
+
+ bool add_reply(DialogId replier_dialog_id, MessageId reply_message_id, int diff);
+
+ bool need_reget(const Td *td) const;
+
+ td_api::object_ptr<td_api::messageReplyInfo> get_message_reply_info_object(
+ Td *td, MessageId dialog_last_read_inbox_message_id) const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageReplyInfo &reply_info);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.hpp b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.hpp
new file mode 100644
index 0000000000..5ef0c2b54b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageReplyInfo.hpp
@@ -0,0 +1,104 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MessageReplyInfo.h"
+#include "td/telegram/MinChannel.hpp"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void MessageReplyInfo::store(StorerT &storer) const {
+ CHECK(!is_empty());
+ bool has_recent_replier_dialog_ids = !recent_replier_dialog_ids_.empty();
+ bool has_channel_id = channel_id_.is_valid();
+ bool has_max_message_id = max_message_id_.is_valid();
+ bool has_last_read_inbox_message_id = last_read_inbox_message_id_.is_valid();
+ bool has_last_read_outbox_message_id = last_read_outbox_message_id_.is_valid();
+ bool has_replier_min_channels = !replier_min_channels_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_comment_);
+ STORE_FLAG(has_recent_replier_dialog_ids);
+ STORE_FLAG(has_channel_id);
+ STORE_FLAG(has_max_message_id);
+ STORE_FLAG(has_last_read_inbox_message_id);
+ STORE_FLAG(has_last_read_outbox_message_id);
+ STORE_FLAG(has_replier_min_channels);
+ END_STORE_FLAGS();
+ td::store(reply_count_, storer);
+ td::store(pts_, storer);
+ if (has_recent_replier_dialog_ids) {
+ td::store(recent_replier_dialog_ids_, storer);
+ }
+ if (has_channel_id) {
+ td::store(channel_id_, storer);
+ }
+ if (has_max_message_id) {
+ td::store(max_message_id_, storer);
+ }
+ if (has_last_read_inbox_message_id) {
+ td::store(last_read_inbox_message_id_, storer);
+ }
+ if (has_last_read_outbox_message_id) {
+ td::store(last_read_outbox_message_id_, storer);
+ }
+ if (has_replier_min_channels) {
+ td::store(replier_min_channels_, storer);
+ }
+}
+
+template <class ParserT>
+void MessageReplyInfo::parse(ParserT &parser) {
+ bool has_recent_replier_dialog_ids;
+ bool has_channel_id;
+ bool has_max_message_id;
+ bool has_last_read_inbox_message_id;
+ bool has_last_read_outbox_message_id;
+ bool has_replier_min_channels;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_comment_);
+ PARSE_FLAG(has_recent_replier_dialog_ids);
+ PARSE_FLAG(has_channel_id);
+ PARSE_FLAG(has_max_message_id);
+ PARSE_FLAG(has_last_read_inbox_message_id);
+ PARSE_FLAG(has_last_read_outbox_message_id);
+ PARSE_FLAG(has_replier_min_channels);
+ END_PARSE_FLAGS();
+ td::parse(reply_count_, parser);
+ td::parse(pts_, parser);
+ if (has_recent_replier_dialog_ids) {
+ td::parse(recent_replier_dialog_ids_, parser);
+ }
+ if (has_channel_id) {
+ td::parse(channel_id_, parser);
+ }
+ if (has_max_message_id) {
+ td::parse(max_message_id_, parser);
+ }
+ if (has_last_read_inbox_message_id) {
+ td::parse(last_read_inbox_message_id_, parser);
+ }
+ if (has_last_read_outbox_message_id) {
+ td::parse(last_read_outbox_message_id_, parser);
+ }
+ if (has_replier_min_channels) {
+ td::parse(replier_min_channels_, parser);
+ }
+
+ if (channel_id_.get() == 777) {
+ *this = MessageReplyInfo();
+ is_dropped_ = true;
+ }
+ if (recent_replier_dialog_ids_.size() > MAX_RECENT_REPLIERS) {
+ recent_replier_dialog_ids_.resize(MAX_RECENT_REPLIERS);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageSearchFilter.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageSearchFilter.cpp
new file mode 100644
index 0000000000..cb2189aeb8
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageSearchFilter.cpp
@@ -0,0 +1,148 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageSearchFilter.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+tl_object_ptr<telegram_api::MessagesFilter> get_input_messages_filter(MessageSearchFilter filter) {
+ switch (filter) {
+ case MessageSearchFilter::Empty:
+ return make_tl_object<telegram_api::inputMessagesFilterEmpty>();
+ case MessageSearchFilter::Animation:
+ return make_tl_object<telegram_api::inputMessagesFilterGif>();
+ case MessageSearchFilter::Audio:
+ return make_tl_object<telegram_api::inputMessagesFilterMusic>();
+ case MessageSearchFilter::Document:
+ return make_tl_object<telegram_api::inputMessagesFilterDocument>();
+ case MessageSearchFilter::Photo:
+ return make_tl_object<telegram_api::inputMessagesFilterPhotos>();
+ case MessageSearchFilter::Video:
+ return make_tl_object<telegram_api::inputMessagesFilterVideo>();
+ case MessageSearchFilter::VoiceNote:
+ return make_tl_object<telegram_api::inputMessagesFilterVoice>();
+ case MessageSearchFilter::PhotoAndVideo:
+ return make_tl_object<telegram_api::inputMessagesFilterPhotoVideo>();
+ case MessageSearchFilter::Url:
+ return make_tl_object<telegram_api::inputMessagesFilterUrl>();
+ case MessageSearchFilter::ChatPhoto:
+ return make_tl_object<telegram_api::inputMessagesFilterChatPhotos>();
+ case MessageSearchFilter::Call:
+ return make_tl_object<telegram_api::inputMessagesFilterPhoneCalls>(0, false /*ignored*/);
+ case MessageSearchFilter::MissedCall:
+ return make_tl_object<telegram_api::inputMessagesFilterPhoneCalls>(
+ telegram_api::inputMessagesFilterPhoneCalls::MISSED_MASK, false /*ignored*/);
+ case MessageSearchFilter::VideoNote:
+ return make_tl_object<telegram_api::inputMessagesFilterRoundVideo>();
+ case MessageSearchFilter::VoiceAndVideoNote:
+ return make_tl_object<telegram_api::inputMessagesFilterRoundVoice>();
+ case MessageSearchFilter::Mention:
+ return make_tl_object<telegram_api::inputMessagesFilterMyMentions>();
+ case MessageSearchFilter::Pinned:
+ return make_tl_object<telegram_api::inputMessagesFilterPinned>();
+ case MessageSearchFilter::UnreadMention:
+ case MessageSearchFilter::FailedToSend:
+ case MessageSearchFilter::UnreadReaction:
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+MessageSearchFilter get_message_search_filter(const tl_object_ptr<td_api::SearchMessagesFilter> &filter) {
+ if (filter == nullptr) {
+ return MessageSearchFilter::Empty;
+ }
+ switch (filter->get_id()) {
+ case td_api::searchMessagesFilterEmpty::ID:
+ return MessageSearchFilter::Empty;
+ case td_api::searchMessagesFilterAnimation::ID:
+ return MessageSearchFilter::Animation;
+ case td_api::searchMessagesFilterAudio::ID:
+ return MessageSearchFilter::Audio;
+ case td_api::searchMessagesFilterDocument::ID:
+ return MessageSearchFilter::Document;
+ case td_api::searchMessagesFilterPhoto::ID:
+ return MessageSearchFilter::Photo;
+ case td_api::searchMessagesFilterVideo::ID:
+ return MessageSearchFilter::Video;
+ case td_api::searchMessagesFilterVoiceNote::ID:
+ return MessageSearchFilter::VoiceNote;
+ case td_api::searchMessagesFilterPhotoAndVideo::ID:
+ return MessageSearchFilter::PhotoAndVideo;
+ case td_api::searchMessagesFilterUrl::ID:
+ return MessageSearchFilter::Url;
+ case td_api::searchMessagesFilterChatPhoto::ID:
+ return MessageSearchFilter::ChatPhoto;
+ case td_api::searchMessagesFilterVideoNote::ID:
+ return MessageSearchFilter::VideoNote;
+ case td_api::searchMessagesFilterVoiceAndVideoNote::ID:
+ return MessageSearchFilter::VoiceAndVideoNote;
+ case td_api::searchMessagesFilterMention::ID:
+ return MessageSearchFilter::Mention;
+ case td_api::searchMessagesFilterUnreadMention::ID:
+ return MessageSearchFilter::UnreadMention;
+ case td_api::searchMessagesFilterFailedToSend::ID:
+ return MessageSearchFilter::FailedToSend;
+ case td_api::searchMessagesFilterPinned::ID:
+ return MessageSearchFilter::Pinned;
+ case td_api::searchMessagesFilterUnreadReaction::ID:
+ return MessageSearchFilter::UnreadReaction;
+ default:
+ UNREACHABLE();
+ return MessageSearchFilter::Empty;
+ }
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, MessageSearchFilter filter) {
+ switch (filter) {
+ case MessageSearchFilter::Empty:
+ return string_builder << "Empty";
+ case MessageSearchFilter::Animation:
+ return string_builder << "Animation";
+ case MessageSearchFilter::Audio:
+ return string_builder << "Audio";
+ case MessageSearchFilter::Document:
+ return string_builder << "Document";
+ case MessageSearchFilter::Photo:
+ return string_builder << "Photo";
+ case MessageSearchFilter::Video:
+ return string_builder << "Video";
+ case MessageSearchFilter::VoiceNote:
+ return string_builder << "VoiceNote";
+ case MessageSearchFilter::PhotoAndVideo:
+ return string_builder << "PhotoAndVideo";
+ case MessageSearchFilter::Url:
+ return string_builder << "Url";
+ case MessageSearchFilter::ChatPhoto:
+ return string_builder << "ChatPhoto";
+ case MessageSearchFilter::Call:
+ return string_builder << "Call";
+ case MessageSearchFilter::MissedCall:
+ return string_builder << "MissedCall";
+ case MessageSearchFilter::VideoNote:
+ return string_builder << "VideoNote";
+ case MessageSearchFilter::VoiceAndVideoNote:
+ return string_builder << "VoiceAndVideoNote";
+ case MessageSearchFilter::Mention:
+ return string_builder << "Mention";
+ case MessageSearchFilter::UnreadMention:
+ return string_builder << "UnreadMention";
+ case MessageSearchFilter::FailedToSend:
+ return string_builder << "FailedToSend";
+ case MessageSearchFilter::Pinned:
+ return string_builder << "Pinned";
+ case MessageSearchFilter::UnreadReaction:
+ return string_builder << "UnreadReaction";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageSearchFilter.h b/protocols/Telegram/tdlib/td/td/telegram/MessageSearchFilter.h
new file mode 100644
index 0000000000..883b6ebcbb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageSearchFilter.h
@@ -0,0 +1,68 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+// append only before Size
+enum class MessageSearchFilter : int32 {
+ Empty,
+ Animation,
+ Audio,
+ Document,
+ Photo,
+ Video,
+ VoiceNote,
+ PhotoAndVideo,
+ Url,
+ ChatPhoto,
+ Call,
+ MissedCall,
+ VideoNote,
+ VoiceAndVideoNote,
+ Mention,
+ UnreadMention,
+ FailedToSend,
+ Pinned,
+ UnreadReaction,
+ Size
+};
+
+inline constexpr size_t message_search_filter_count() {
+ return static_cast<int32>(MessageSearchFilter::Size) - 1;
+}
+
+inline int32 message_search_filter_index(MessageSearchFilter filter) {
+ CHECK(filter != MessageSearchFilter::Empty);
+ return static_cast<int32>(filter) - 1;
+}
+
+inline int32 message_search_filter_index_mask(MessageSearchFilter filter) {
+ if (filter == MessageSearchFilter::Empty) {
+ return 0;
+ }
+ return 1 << message_search_filter_index(filter);
+}
+
+inline int32 call_message_search_filter_index(MessageSearchFilter filter) {
+ CHECK(filter == MessageSearchFilter::Call || filter == MessageSearchFilter::MissedCall);
+ return static_cast<int32>(filter) - static_cast<int32>(MessageSearchFilter::Call);
+}
+
+tl_object_ptr<telegram_api::MessagesFilter> get_input_messages_filter(MessageSearchFilter filter);
+
+MessageSearchFilter get_message_search_filter(const tl_object_ptr<td_api::SearchMessagesFilter> &filter);
+
+StringBuilder &operator<<(StringBuilder &string_builder, MessageSearchFilter filter);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageSender.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageSender.cpp
new file mode 100644
index 0000000000..e4dbcccc19
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageSender.cpp
@@ -0,0 +1,169 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageSender.h"
+
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+
+namespace td {
+
+td_api::object_ptr<td_api::MessageSender> get_message_sender_object_const(Td *td, UserId user_id, DialogId dialog_id,
+ const char *source) {
+ if (dialog_id.is_valid() && td->messages_manager_->have_dialog(dialog_id)) {
+ return td_api::make_object<td_api::messageSenderChat>(dialog_id.get());
+ }
+ if (!user_id.is_valid()) {
+ // can happen only if the server sends a message with wrong sender
+ LOG(ERROR) << "Receive message with wrong sender " << user_id << '/' << dialog_id << " from " << source;
+ user_id = td->contacts_manager_->add_service_notifications_user();
+ }
+ return td_api::make_object<td_api::messageSenderUser>(td->contacts_manager_->get_user_id_object(user_id, source));
+}
+
+td_api::object_ptr<td_api::MessageSender> get_message_sender_object_const(Td *td, DialogId dialog_id,
+ const char *source) {
+ if (dialog_id.get_type() == DialogType::User) {
+ return get_message_sender_object_const(td, dialog_id.get_user_id(), DialogId(), source);
+ }
+ return get_message_sender_object_const(td, UserId(), dialog_id, source);
+}
+
+td_api::object_ptr<td_api::MessageSender> get_message_sender_object(Td *td, UserId user_id, DialogId dialog_id,
+ const char *source) {
+ if (dialog_id.is_valid() && !td->messages_manager_->have_dialog(dialog_id)) {
+ LOG(ERROR) << "Failed to find " << dialog_id;
+ td->messages_manager_->force_create_dialog(dialog_id, source);
+ }
+ if (!user_id.is_valid() && td->auth_manager_->is_bot()) {
+ td->contacts_manager_->add_anonymous_bot_user();
+ td->contacts_manager_->add_channel_bot_user();
+ td->contacts_manager_->add_service_notifications_user();
+ }
+ return get_message_sender_object_const(td, user_id, dialog_id, source);
+}
+
+td_api::object_ptr<td_api::MessageSender> get_message_sender_object(Td *td, DialogId dialog_id, const char *source) {
+ if (dialog_id.get_type() == DialogType::User) {
+ return get_message_sender_object(td, dialog_id.get_user_id(), DialogId(), source);
+ }
+ return get_message_sender_object(td, UserId(), dialog_id, source);
+}
+
+td_api::object_ptr<td_api::MessageSender> get_min_message_sender_object(Td *td, DialogId dialog_id,
+ const char *source) {
+ auto dialog_type = dialog_id.get_type();
+ if (dialog_type == DialogType::User) {
+ auto user_id = dialog_id.get_user_id();
+ if (td->contacts_manager_->have_min_user(user_id)) {
+ return td_api::make_object<td_api::messageSenderUser>(td->contacts_manager_->get_user_id_object(user_id, source));
+ }
+ } else {
+ if (!td->messages_manager_->have_dialog(dialog_id) &&
+ (td->messages_manager_->have_dialog_info(dialog_id) ||
+ (dialog_type == DialogType::Channel && td->contacts_manager_->have_min_channel(dialog_id.get_channel_id())))) {
+ LOG(INFO) << "Force creation of " << dialog_id;
+ td->messages_manager_->force_create_dialog(dialog_id, source, true);
+ }
+ if (td->messages_manager_->have_dialog(dialog_id)) {
+ return td_api::make_object<td_api::messageSenderChat>(dialog_id.get());
+ }
+ }
+ LOG(ERROR) << "Can't return unknown " << dialog_id << " from " << source;
+ return nullptr;
+}
+
+vector<DialogId> get_message_sender_dialog_ids(Td *td,
+ const vector<telegram_api::object_ptr<telegram_api::Peer>> &peers) {
+ vector<DialogId> message_sender_dialog_ids;
+ message_sender_dialog_ids.reserve(peers.size());
+ for (auto &peer : peers) {
+ DialogId dialog_id(peer);
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << dialog_id << " as message sender";
+ continue;
+ }
+ if (dialog_id.get_type() == DialogType::User) {
+ if (!td->contacts_manager_->have_user(dialog_id.get_user_id())) {
+ LOG(ERROR) << "Receive unknown " << dialog_id.get_user_id();
+ continue;
+ }
+ } else {
+ if (!td->messages_manager_->have_dialog_info(dialog_id)) {
+ continue;
+ }
+ td->messages_manager_->force_create_dialog(dialog_id, "get_message_sender_dialog_ids");
+ if (!td->messages_manager_->have_dialog(dialog_id)) {
+ continue;
+ }
+ }
+ message_sender_dialog_ids.push_back(dialog_id);
+ }
+ return message_sender_dialog_ids;
+}
+
+td_api::object_ptr<td_api::messageSenders> convert_message_senders_object(
+ Td *td, const vector<telegram_api::object_ptr<telegram_api::Peer>> &peers) {
+ auto dialog_ids = get_message_sender_dialog_ids(td, peers);
+ auto message_senders = transform(dialog_ids, [td](DialogId dialog_id) {
+ return get_message_sender_object(td, dialog_id, "convert_message_senders_object");
+ });
+ return td_api::make_object<td_api::messageSenders>(narrow_cast<int32>(dialog_ids.size()), std::move(message_senders));
+}
+
+Result<DialogId> get_message_sender_dialog_id(Td *td,
+ const td_api::object_ptr<td_api::MessageSender> &message_sender_id,
+ bool check_access, bool allow_empty) {
+ if (message_sender_id == nullptr) {
+ if (allow_empty) {
+ return DialogId();
+ }
+ return Status::Error(400, "Message sender must be non-empty");
+ }
+ switch (message_sender_id->get_id()) {
+ case td_api::messageSenderUser::ID: {
+ auto user_id = UserId(static_cast<const td_api::messageSenderUser *>(message_sender_id.get())->user_id_);
+ if (!user_id.is_valid()) {
+ if (allow_empty && user_id == UserId()) {
+ return DialogId();
+ }
+ return Status::Error(400, "Invalid user identifier specified");
+ }
+ bool know_user = td->contacts_manager_->have_user_force(user_id);
+ if (check_access && !know_user) {
+ return Status::Error(400, "Unknown user identifier specified");
+ }
+ return DialogId(user_id);
+ }
+ case td_api::messageSenderChat::ID: {
+ auto dialog_id = DialogId(static_cast<const td_api::messageSenderChat *>(message_sender_id.get())->chat_id_);
+ if (!dialog_id.is_valid()) {
+ if (allow_empty && dialog_id == DialogId()) {
+ return DialogId();
+ }
+ return Status::Error(400, "Invalid chat identifier specified");
+ }
+ bool know_dialog = dialog_id.get_type() == DialogType::User
+ ? td->contacts_manager_->have_user_force(dialog_id.get_user_id())
+ : td->messages_manager_->have_dialog_force(dialog_id, "get_message_sender_dialog_id");
+ if (check_access && !know_dialog) {
+ return Status::Error(400, "Unknown chat identifier specified");
+ }
+ return dialog_id;
+ }
+ default:
+ UNREACHABLE();
+ return DialogId();
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageSender.h b/protocols/Telegram/tdlib/td/td/telegram/MessageSender.h
new file mode 100644
index 0000000000..120fe5ff92
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageSender.h
@@ -0,0 +1,44 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class Td;
+
+td_api::object_ptr<td_api::MessageSender> get_message_sender_object_const(Td *td, UserId user_id, DialogId dialog_id,
+ const char *source);
+
+td_api::object_ptr<td_api::MessageSender> get_message_sender_object_const(Td *td, DialogId dialog_id,
+ const char *source);
+
+td_api::object_ptr<td_api::MessageSender> get_message_sender_object(Td *td, UserId user_id, DialogId dialog_id,
+ const char *source);
+
+td_api::object_ptr<td_api::MessageSender> get_message_sender_object(Td *td, DialogId dialog_id, const char *source);
+
+td_api::object_ptr<td_api::MessageSender> get_min_message_sender_object(Td *td, DialogId dialog_id, const char *source);
+
+vector<DialogId> get_message_sender_dialog_ids(Td *td,
+ const vector<telegram_api::object_ptr<telegram_api::Peer>> &peers);
+
+td_api::object_ptr<td_api::messageSenders> convert_message_senders_object(
+ Td *td, const vector<telegram_api::object_ptr<telegram_api::Peer>> &peers);
+
+Result<DialogId> get_message_sender_dialog_id(Td *td,
+ const td_api::object_ptr<td_api::MessageSender> &message_sender_id,
+ bool check_access, bool allow_empty);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageThreadDb.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageThreadDb.cpp
new file mode 100644
index 0000000000..12c250f8a9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageThreadDb.cpp
@@ -0,0 +1,343 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageThreadDb.h"
+
+#include "td/telegram/Version.h"
+
+#include "td/db/SqliteConnectionSafe.h"
+#include "td/db/SqliteDb.h"
+#include "td/db/SqliteStatement.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/SchedulerLocalStorage.h"
+
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/Time.h"
+
+namespace td {
+// NB: must happen inside a transaction
+Status init_message_thread_db(SqliteDb &db, int32 version) {
+ LOG(INFO) << "Init message thread database " << tag("version", version);
+
+ // Check if database exists
+ TRY_RESULT(has_table, db.has_table("threads"));
+ if (!has_table) {
+ version = 0;
+ }
+
+ if (version > current_db_version()) {
+ TRY_STATUS(drop_message_thread_db(db, version));
+ version = 0;
+ }
+
+ if (version == 0) {
+ LOG(INFO) << "Create new message thread database";
+ TRY_STATUS(
+ db.exec("CREATE TABLE IF NOT EXISTS threads (dialog_id INT8, thread_id INT8, thread_order INT8, data BLOB, "
+ "PRIMARY KEY (dialog_id, thread_id))"));
+ TRY_STATUS(
+ db.exec("CREATE INDEX IF NOT EXISTS dialog_threads_by_thread_order ON threads (dialog_id, thread_order)"));
+ version = current_db_version();
+ }
+
+ return Status::OK();
+}
+
+// NB: must happen inside a transaction
+Status drop_message_thread_db(SqliteDb &db, int version) {
+ if (version > current_db_version()) {
+ LOG(WARNING) << "Drop message_thread_db " << tag("version", version)
+ << tag("current_db_version", current_db_version());
+ }
+ return db.exec("DROP TABLE IF EXISTS threads");
+}
+
+class MessageThreadDbImpl final : public MessageThreadDbSyncInterface {
+ public:
+ explicit MessageThreadDbImpl(SqliteDb db) : db_(std::move(db)) {
+ init().ensure();
+ }
+
+ Status init() {
+ TRY_RESULT_ASSIGN(add_thread_stmt_, db_.get_statement("INSERT OR REPLACE INTO threads VALUES(?1, ?2, ?3, ?4)"));
+ TRY_RESULT_ASSIGN(delete_thread_stmt_,
+ db_.get_statement("DELETE FROM threads WHERE dialog_id = ?1 AND thread_id = ?2"));
+ TRY_RESULT_ASSIGN(delete_all_dialog_threads_stmt_, db_.get_statement("DELETE FROM threads WHERE dialog_id = ?1"));
+ TRY_RESULT_ASSIGN(get_thread_stmt_,
+ db_.get_statement("SELECT data FROM threads WHERE dialog_id = ?1 AND thread_id = ?2"));
+ TRY_RESULT_ASSIGN(get_threads_stmt_,
+ db_.get_statement("SELECT data, dialog_id, thread_id, thread_order FROM threads WHERE dialog_id "
+ "= ?1 AND thread_order < ?2 ORDER BY thread_order DESC LIMIT ?3"));
+
+ // LOG(ERROR) << delete_thread_stmt_.explain().ok();
+ // LOG(ERROR) << delete_all_dialog_threads_stmt_.explain().ok();
+ // LOG(ERROR) << get_thread_stmt_.explain().ok();
+ // LOG(ERROR) << get_threads_stmt_.explain().ok();
+ // LOG(FATAL) << "EXPLAINED";
+
+ return Status::OK();
+ }
+
+ void add_message_thread(DialogId dialog_id, MessageId top_thread_message_id, int64 order, BufferSlice data) final {
+ SCOPE_EXIT {
+ add_thread_stmt_.reset();
+ };
+ add_thread_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ add_thread_stmt_.bind_int64(2, top_thread_message_id.get()).ensure();
+ add_thread_stmt_.bind_int64(3, order).ensure();
+ add_thread_stmt_.bind_blob(4, data.as_slice()).ensure();
+ add_thread_stmt_.step().ensure();
+ }
+
+ void delete_message_thread(DialogId dialog_id, MessageId top_thread_message_id) final {
+ SCOPE_EXIT {
+ delete_thread_stmt_.reset();
+ };
+ delete_thread_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ delete_thread_stmt_.bind_int64(2, top_thread_message_id.get()).ensure();
+ delete_thread_stmt_.step().ensure();
+ }
+
+ void delete_all_dialog_message_threads(DialogId dialog_id) final {
+ SCOPE_EXIT {
+ delete_all_dialog_threads_stmt_.reset();
+ };
+ delete_all_dialog_threads_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ delete_all_dialog_threads_stmt_.step().ensure();
+ }
+
+ BufferSlice get_message_thread(DialogId dialog_id, MessageId top_thread_message_id) final {
+ SCOPE_EXIT {
+ get_thread_stmt_.reset();
+ };
+
+ get_thread_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ get_thread_stmt_.bind_int64(2, top_thread_message_id.get()).ensure();
+ get_thread_stmt_.step().ensure();
+ if (!get_thread_stmt_.has_row()) {
+ return BufferSlice();
+ }
+ return BufferSlice(get_thread_stmt_.view_blob(0));
+ }
+
+ MessageThreadDbMessageThreads get_message_threads(DialogId dialog_id, int64 offset_order, int32 limit) final {
+ SCOPE_EXIT {
+ get_threads_stmt_.reset();
+ };
+
+ get_threads_stmt_.bind_int64(1, dialog_id.get()).ensure();
+ get_threads_stmt_.bind_int64(2, offset_order).ensure();
+ get_threads_stmt_.bind_int32(3, limit).ensure();
+
+ MessageThreadDbMessageThreads result;
+ result.next_order = offset_order;
+ get_threads_stmt_.step().ensure();
+ while (get_threads_stmt_.has_row()) {
+ BufferSlice data(get_threads_stmt_.view_blob(0));
+ result.next_order = get_threads_stmt_.view_int64(3);
+ LOG(INFO) << "Load thread of " << MessageId(get_threads_stmt_.view_int64(2)) << " in "
+ << DialogId(get_threads_stmt_.view_int64(1)) << " with order " << result.next_order;
+ result.message_threads.emplace_back(std::move(data));
+ get_threads_stmt_.step().ensure();
+ }
+ return result;
+ }
+
+ Status begin_write_transaction() final {
+ return db_.begin_write_transaction();
+ }
+
+ Status commit_transaction() final {
+ return db_.commit_transaction();
+ }
+
+ private:
+ SqliteDb db_;
+
+ SqliteStatement add_thread_stmt_;
+ SqliteStatement delete_thread_stmt_;
+ SqliteStatement delete_all_dialog_threads_stmt_;
+ SqliteStatement get_thread_stmt_;
+ SqliteStatement get_threads_stmt_;
+};
+
+std::shared_ptr<MessageThreadDbSyncSafeInterface> create_message_thread_db_sync(
+ std::shared_ptr<SqliteConnectionSafe> sqlite_connection) {
+ class MessageThreadDbSyncSafe final : public MessageThreadDbSyncSafeInterface {
+ public:
+ explicit MessageThreadDbSyncSafe(std::shared_ptr<SqliteConnectionSafe> sqlite_connection)
+ : lsls_db_([safe_connection = std::move(sqlite_connection)] {
+ return make_unique<MessageThreadDbImpl>(safe_connection->get().clone());
+ }) {
+ }
+ MessageThreadDbSyncInterface &get() final {
+ return *lsls_db_.get();
+ }
+
+ private:
+ LazySchedulerLocalStorage<unique_ptr<MessageThreadDbSyncInterface>> lsls_db_;
+ };
+ return std::make_shared<MessageThreadDbSyncSafe>(std::move(sqlite_connection));
+}
+
+class MessageThreadDbAsync final : public MessageThreadDbAsyncInterface {
+ public:
+ MessageThreadDbAsync(std::shared_ptr<MessageThreadDbSyncSafeInterface> sync_db, int32 scheduler_id) {
+ impl_ = create_actor_on_scheduler<Impl>("MessageThreadDbActor", scheduler_id, std::move(sync_db));
+ }
+
+ void add_message_thread(DialogId dialog_id, MessageId top_thread_message_id, int64 order, BufferSlice data,
+ Promise<Unit> promise) final {
+ send_closure(impl_, &Impl::add_message_thread, dialog_id, top_thread_message_id, order, std::move(data),
+ std::move(promise));
+ }
+
+ void delete_message_thread(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> promise) final {
+ send_closure(impl_, &Impl::delete_message_thread, dialog_id, top_thread_message_id, std::move(promise));
+ }
+
+ void delete_all_dialog_message_threads(DialogId dialog_id, Promise<Unit> promise) final {
+ send_closure(impl_, &Impl::delete_all_dialog_message_threads, dialog_id, std::move(promise));
+ }
+
+ void get_message_thread(DialogId dialog_id, MessageId top_thread_message_id, Promise<BufferSlice> promise) final {
+ send_closure_later(impl_, &Impl::get_message_thread, dialog_id, top_thread_message_id, std::move(promise));
+ }
+
+ void get_message_threads(DialogId dialog_id, int64 offset_order, int32 limit,
+ Promise<MessageThreadDbMessageThreads> promise) final {
+ send_closure_later(impl_, &Impl::get_message_threads, dialog_id, offset_order, limit, std::move(promise));
+ }
+
+ void close(Promise<Unit> promise) final {
+ send_closure_later(impl_, &Impl::close, std::move(promise));
+ }
+
+ void force_flush() final {
+ send_closure_later(impl_, &Impl::force_flush);
+ }
+
+ private:
+ class Impl final : public Actor {
+ public:
+ explicit Impl(std::shared_ptr<MessageThreadDbSyncSafeInterface> sync_db_safe)
+ : sync_db_safe_(std::move(sync_db_safe)) {
+ }
+
+ void add_message_thread(DialogId dialog_id, MessageId top_thread_message_id, int64 order, BufferSlice data,
+ Promise<Unit> promise) {
+ add_write_query([this, dialog_id, top_thread_message_id, order, data = std::move(data),
+ promise = std::move(promise)](Unit) mutable {
+ sync_db_->add_message_thread(dialog_id, top_thread_message_id, order, std::move(data));
+ on_write_result(std::move(promise));
+ });
+ }
+
+ void delete_message_thread(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> promise) {
+ add_write_query([this, dialog_id, top_thread_message_id, promise = std::move(promise)](Unit) mutable {
+ sync_db_->delete_message_thread(dialog_id, top_thread_message_id);
+ on_write_result(std::move(promise));
+ });
+ }
+
+ void delete_all_dialog_message_threads(DialogId dialog_id, Promise<Unit> promise) {
+ add_write_query([this, dialog_id, promise = std::move(promise)](Unit) mutable {
+ sync_db_->delete_all_dialog_message_threads(dialog_id);
+ on_write_result(std::move(promise));
+ });
+ }
+
+ void on_write_result(Promise<Unit> &&promise) {
+ // We are inside a transaction and don't know how to handle errors
+ finished_writes_.push_back(std::move(promise));
+ }
+
+ void get_message_thread(DialogId dialog_id, MessageId top_thread_message_id, Promise<BufferSlice> promise) {
+ add_read_query();
+ promise.set_result(sync_db_->get_message_thread(dialog_id, top_thread_message_id));
+ }
+
+ void get_message_threads(DialogId dialog_id, int64 offset_order, int32 limit,
+ Promise<MessageThreadDbMessageThreads> promise) {
+ add_read_query();
+ promise.set_result(sync_db_->get_message_threads(dialog_id, offset_order, limit));
+ }
+
+ void close(Promise<> promise) {
+ do_flush();
+ sync_db_safe_.reset();
+ sync_db_ = nullptr;
+ promise.set_value(Unit());
+ stop();
+ }
+
+ void force_flush() {
+ do_flush();
+ LOG(INFO) << "MessageThreadDb flushed";
+ }
+
+ private:
+ std::shared_ptr<MessageThreadDbSyncSafeInterface> sync_db_safe_;
+ MessageThreadDbSyncInterface *sync_db_ = nullptr;
+
+ static constexpr size_t MAX_PENDING_QUERIES_COUNT{50};
+ static constexpr double MAX_PENDING_QUERIES_DELAY{0.01};
+
+ //NB: order is important, destructor of pending_writes_ will change finished_writes_
+ vector<Promise<Unit>> finished_writes_;
+ vector<Promise<Unit>> pending_writes_; // TODO use Action
+ double wakeup_at_ = 0;
+
+ template <class F>
+ void add_write_query(F &&f) {
+ pending_writes_.push_back(PromiseCreator::lambda(std::forward<F>(f)));
+ if (pending_writes_.size() > MAX_PENDING_QUERIES_COUNT) {
+ do_flush();
+ wakeup_at_ = 0;
+ } else if (wakeup_at_ == 0) {
+ wakeup_at_ = Time::now_cached() + MAX_PENDING_QUERIES_DELAY;
+ }
+ if (wakeup_at_ != 0) {
+ set_timeout_at(wakeup_at_);
+ }
+ }
+
+ void add_read_query() {
+ do_flush();
+ }
+
+ void do_flush() {
+ if (pending_writes_.empty()) {
+ return;
+ }
+ sync_db_->begin_write_transaction().ensure();
+ set_promises(pending_writes_);
+ sync_db_->commit_transaction().ensure();
+ set_promises(finished_writes_);
+ cancel_timeout();
+ }
+
+ void timeout_expired() final {
+ do_flush();
+ }
+
+ void start_up() final {
+ sync_db_ = &sync_db_safe_->get();
+ }
+ };
+ ActorOwn<Impl> impl_;
+};
+
+std::shared_ptr<MessageThreadDbAsyncInterface> create_message_thread_db_async(
+ std::shared_ptr<MessageThreadDbSyncSafeInterface> sync_db, int32 scheduler_id) {
+ return std::make_shared<MessageThreadDbAsync>(std::move(sync_db), scheduler_id);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageThreadDb.h b/protocols/Telegram/tdlib/td/td/telegram/MessageThreadDb.h
new file mode 100644
index 0000000000..3206f0a01c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageThreadDb.h
@@ -0,0 +1,98 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageId.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+#include <memory>
+#include <utility>
+
+namespace td {
+
+class SqliteConnectionSafe;
+class SqliteDb;
+
+struct MessageThreadDbMessageThreads {
+ vector<BufferSlice> message_threads;
+ int64 next_order = 0;
+};
+
+class MessageThreadDbSyncInterface {
+ public:
+ MessageThreadDbSyncInterface() = default;
+ MessageThreadDbSyncInterface(const MessageThreadDbSyncInterface &) = delete;
+ MessageThreadDbSyncInterface &operator=(const MessageThreadDbSyncInterface &) = delete;
+ virtual ~MessageThreadDbSyncInterface() = default;
+
+ virtual void add_message_thread(DialogId dialog_id, MessageId top_thread_message_id, int64 order,
+ BufferSlice data) = 0;
+
+ virtual void delete_message_thread(DialogId dialog_id, MessageId top_thread_message_id) = 0;
+
+ virtual void delete_all_dialog_message_threads(DialogId dialog_id) = 0;
+
+ virtual BufferSlice get_message_thread(DialogId dialog_id, MessageId top_thread_message_id) = 0;
+
+ virtual MessageThreadDbMessageThreads get_message_threads(DialogId dialog_id, int64 offset_order, int32 limit) = 0;
+
+ virtual Status begin_write_transaction() = 0;
+
+ virtual Status commit_transaction() = 0;
+};
+
+class MessageThreadDbSyncSafeInterface {
+ public:
+ MessageThreadDbSyncSafeInterface() = default;
+ MessageThreadDbSyncSafeInterface(const MessageThreadDbSyncSafeInterface &) = delete;
+ MessageThreadDbSyncSafeInterface &operator=(const MessageThreadDbSyncSafeInterface &) = delete;
+ virtual ~MessageThreadDbSyncSafeInterface() = default;
+
+ virtual MessageThreadDbSyncInterface &get() = 0;
+};
+
+class MessageThreadDbAsyncInterface {
+ public:
+ MessageThreadDbAsyncInterface() = default;
+ MessageThreadDbAsyncInterface(const MessageThreadDbAsyncInterface &) = delete;
+ MessageThreadDbAsyncInterface &operator=(const MessageThreadDbAsyncInterface &) = delete;
+ virtual ~MessageThreadDbAsyncInterface() = default;
+
+ virtual void add_message_thread(DialogId dialog_id, MessageId top_thread_message_id, int64 order, BufferSlice data,
+ Promise<Unit> promise) = 0;
+
+ virtual void delete_message_thread(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> promise) = 0;
+
+ virtual void delete_all_dialog_message_threads(DialogId dialog_id, Promise<Unit> promise) = 0;
+
+ virtual void get_message_thread(DialogId dialog_id, MessageId top_thread_message_id,
+ Promise<BufferSlice> promise) = 0;
+
+ virtual void get_message_threads(DialogId dialog_id, int64 offset_order, int32 limit,
+ Promise<MessageThreadDbMessageThreads> promise) = 0;
+
+ virtual void close(Promise<Unit> promise) = 0;
+
+ virtual void force_flush() = 0;
+};
+
+Status init_message_thread_db(SqliteDb &db, int version) TD_WARN_UNUSED_RESULT;
+
+Status drop_message_thread_db(SqliteDb &db, int version) TD_WARN_UNUSED_RESULT;
+
+std::shared_ptr<MessageThreadDbSyncSafeInterface> create_message_thread_db_sync(
+ std::shared_ptr<SqliteConnectionSafe> sqlite_connection);
+
+std::shared_ptr<MessageThreadDbAsyncInterface> create_message_thread_db_async(
+ std::shared_ptr<MessageThreadDbSyncSafeInterface> sync_db, int32 scheduler_id = -1);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageThreadInfo.h b/protocols/Telegram/tdlib/td/td/telegram/MessageThreadInfo.h
new file mode 100644
index 0000000000..1376092705
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageThreadInfo.h
@@ -0,0 +1,22 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageId.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+struct MessageThreadInfo {
+ DialogId dialog_id;
+ vector<MessageId> message_ids;
+ int32 unread_message_count = 0;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageTtl.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessageTtl.cpp
new file mode 100644
index 0000000000..f7363eb4bc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageTtl.cpp
@@ -0,0 +1,27 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageTtl.h"
+
+namespace td {
+
+bool MessageTtl::is_empty() const {
+ return period_ == 0;
+}
+
+int32 MessageTtl::get_message_ttl_object() const {
+ return period_;
+}
+
+bool operator==(const MessageTtl &lhs, const MessageTtl &rhs) {
+ return lhs.period_ == rhs.period_;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageTtl &message_ttl) {
+ return string_builder << "MessageTtl[" << message_ttl.period_ << "]";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessageTtl.h b/protocols/Telegram/tdlib/td/td/telegram/MessageTtl.h
new file mode 100644
index 0000000000..34931d643d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessageTtl.h
@@ -0,0 +1,56 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+#include <type_traits>
+
+namespace td {
+
+class MessageTtl {
+ int32 period_ = 0;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageTtl &message_ttl);
+
+ friend bool operator==(const MessageTtl &lhs, const MessageTtl &rhs);
+
+ public:
+ MessageTtl() = default;
+
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ MessageTtl(T period) = delete;
+
+ explicit MessageTtl(int32 period) : period_(period) {
+ }
+
+ bool is_empty() const;
+
+ int32 get_message_ttl_object() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(period_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(period_, parser);
+ }
+};
+
+bool operator==(const MessageTtl &lhs, const MessageTtl &rhs);
+
+inline bool operator!=(const MessageTtl &lhs, const MessageTtl &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const MessageTtl &message_ttl);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessagesDb.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessagesDb.cpp
deleted file mode 100644
index 8ada08bfab..0000000000
--- a/protocols/Telegram/tdlib/td/td/telegram/MessagesDb.cpp
+++ /dev/null
@@ -1,1006 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#include "td/telegram/MessagesDb.h"
-
-#include "td/telegram/logevent/LogEvent.h"
-#include "td/telegram/Version.h"
-
-#include "td/db/SqliteDb.h"
-#include "td/db/SqliteStatement.h"
-
-#include "td/actor/PromiseFuture.h"
-
-#include "td/utils/format.h"
-#include "td/utils/logging.h"
-#include "td/utils/ScopeGuard.h"
-#include "td/utils/Slice.h"
-#include "td/utils/StackAllocator.h"
-#include "td/utils/StringBuilder.h"
-#include "td/utils/Time.h"
-#include "td/utils/tl_helpers.h"
-#include "td/utils/unicode.h"
-#include "td/utils/utf8.h"
-
-#include <algorithm>
-#include <array>
-#include <iterator>
-#include <limits>
-#include <tuple>
-
-namespace td {
-
-static constexpr int32 MESSAGES_DB_INDEX_COUNT = 30;
-static constexpr int32 MESSAGES_DB_INDEX_COUNT_OLD = 9;
-
-// NB: must happen inside a transaction
-Status init_messages_db(SqliteDb &db, int32 version) {
- LOG(INFO) << "Init message db " << tag("version", version);
-
- // Check if database exists
- TRY_RESULT(has_table, db.has_table("messages"));
- if (!has_table) {
- version = 0;
- } else if (version < static_cast<int32>(DbVersion::DialogDbCreated) || version > current_db_version()) {
- TRY_STATUS(drop_messages_db(db, version));
- version = 0;
- }
-
- auto add_media_indices = [&db](int begin, int end) {
- for (int i = begin; i < end; i++) {
- TRY_STATUS(db.exec(PSLICE() << "CREATE INDEX IF NOT EXISTS message_index_" << i
- << " ON messages (dialog_id, message_id) WHERE (index_mask & " << (1 << i)
- << ") != 0"));
- }
- return Status::OK();
- };
-
- auto add_fts = [&db] {
- TRY_STATUS(
- db.exec("CREATE INDEX IF NOT EXISTS message_by_search_id ON messages "
- "(search_id) WHERE search_id IS NOT NULL"));
-
- TRY_STATUS(
- db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(text, content='messages', "
- "content_rowid='search_id', tokenize = \"unicode61 remove_diacritics 0 tokenchars '\a'\")"));
- TRY_STATUS(db.exec(
- "CREATE TRIGGER IF NOT EXISTS trigger_fts_delete BEFORE DELETE ON messages WHEN OLD.search_id IS NOT NULL"
- " BEGIN INSERT INTO messages_fts(messages_fts, rowid, text) VALUES(\'delete\', OLD.search_id, OLD.text); END"));
- TRY_STATUS(db.exec(
- "CREATE TRIGGER IF NOT EXISTS trigger_fts_insert AFTER INSERT ON messages WHEN NEW.search_id IS NOT NULL"
- " BEGIN INSERT INTO messages_fts(rowid, text) VALUES(NEW.search_id, NEW.text); END"));
- //TRY_STATUS(db.exec(
- //"CREATE TRIGGER IF NOT EXISTS trigger_fts_update AFTER UPDATE ON messages WHEN NEW.search_id IS NOT NULL OR "
- //"OLD.search_id IS NOT NULL"
- //" BEGIN "
- //"INSERT INTO messages_fts(messages_fts, rowid, text) VALUES(\'delete\', OLD.search_id, OLD.text); "
- //"INSERT INTO messages_fts(rowid, text) VALUES(NEW.search_id, NEW.text); "
- //" END"));
-
- return Status::OK();
- };
- auto add_call_index = [&db]() {
- for (int i = static_cast<int>(SearchMessagesFilter::Call) - 1;
- i < static_cast<int>(SearchMessagesFilter::MissedCall); i++) {
- TRY_STATUS(db.exec(PSLICE() << "CREATE INDEX IF NOT EXISTS full_message_index_" << i
- << " ON messages (unique_message_id) WHERE (index_mask & " << (1 << i) << ") != 0"));
- }
- return Status::OK();
- };
-
- if (version == 0) {
- LOG(INFO) << "Create new messages db";
- TRY_STATUS(
- db.exec("CREATE TABLE IF NOT EXISTS messages (dialog_id INT8, message_id INT8, "
- "unique_message_id INT4, sender_user_id INT4, random_id INT8, data BLOB, "
- "ttl_expires_at INT4, index_mask INT4, search_id INT8, text STRING, PRIMARY KEY "
- "(dialog_id, message_id))"));
-
- TRY_STATUS(
- db.exec("CREATE INDEX IF NOT EXISTS message_by_random_id ON messages (dialog_id, random_id) "
- "WHERE random_id IS NOT NULL"));
- TRY_STATUS(
- db.exec("CREATE INDEX IF NOT EXISTS message_by_unique_message_id ON messages "
- "(unique_message_id) WHERE unique_message_id IS NOT NULL"));
-
- TRY_STATUS(
- db.exec("CREATE INDEX IF NOT EXISTS message_by_ttl ON messages "
- "(ttl_expires_at) WHERE ttl_expires_at IS NOT NULL"));
-
- TRY_STATUS(add_media_indices(0, MESSAGES_DB_INDEX_COUNT));
-
- TRY_STATUS(add_fts());
-
- TRY_STATUS(add_call_index());
-
- version = current_db_version();
- }
- if (version < static_cast<int32>(DbVersion::MessagesDbMediaIndex)) {
- TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN index_mask INT4"));
- TRY_STATUS(add_media_indices(0, MESSAGES_DB_INDEX_COUNT_OLD));
- }
- if (version < static_cast<int32>(DbVersion::MessagesDb30MediaIndex)) {
- TRY_STATUS(add_media_indices(MESSAGES_DB_INDEX_COUNT_OLD, MESSAGES_DB_INDEX_COUNT));
- }
- if (version < static_cast<int32>(DbVersion::MessagesDbFts)) {
- TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN search_id INT8"));
- TRY_STATUS(db.exec("ALTER TABLE messages ADD COLUMN text STRING"));
- TRY_STATUS(add_fts());
- }
- if (version < static_cast<int32>(DbVersion::MessagesCallIndex)) {
- TRY_STATUS(add_call_index());
- }
- return Status::OK();
-}
-
-// NB: must happen inside a transaction
-Status drop_messages_db(SqliteDb &db, int32 version) {
- LOG(WARNING) << "Drop messages db " << tag("version", version) << tag("current_db_version", current_db_version());
- return db.exec("DROP TABLE IF EXISTS messages");
-}
-
-class MessagesDbImpl : public MessagesDbSyncInterface {
- public:
- explicit MessagesDbImpl(SqliteDb db) : db_(std::move(db)) {
- init().ensure();
- }
-
- Status init() {
- TRY_RESULT(add_message_stmt,
- db_.get_statement("INSERT OR REPLACE INTO messages VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)"));
- TRY_RESULT(delete_message_stmt, db_.get_statement("DELETE FROM messages WHERE dialog_id = ?1 AND message_id = ?2"));
- TRY_RESULT(delete_all_dialog_messages_stmt,
- db_.get_statement("DELETE FROM messages WHERE dialog_id = ?1 AND message_id <= ?2"));
- TRY_RESULT(delete_dialog_messages_from_user_stmt,
- db_.get_statement("DELETE FROM messages WHERE dialog_id = ?1 AND sender_user_id == ?2"));
-
- TRY_RESULT(get_message_stmt,
- db_.get_statement("SELECT data FROM messages WHERE dialog_id = ?1 AND message_id = ?2"));
- TRY_RESULT(get_message_by_random_id_stmt,
- db_.get_statement("SELECT data FROM messages WHERE dialog_id = ?1 AND random_id = ?2"));
- TRY_RESULT(get_message_by_unique_message_id_stmt,
- db_.get_statement("SELECT dialog_id, data FROM messages WHERE unique_message_id = ?1"));
-
- TRY_RESULT(get_expiring_messages_stmt,
- db_.get_statement("SELECT dialog_id, data FROM messages WHERE ?1 < ttl_expires_at AND ttl_expires_at <= "
- "?2"));
- TRY_RESULT(get_expiring_messages_helper_stmt,
- db_.get_statement("SELECT MAX(ttl_expires_at), COUNT(*) FROM (SELECT ttl_expires_at FROM messages WHERE "
- "?1 < ttl_expires_at LIMIT ?2) AS T"));
-
- TRY_RESULT(get_messages_asc_stmt,
- db_.get_statement("SELECT data, message_id FROM messages WHERE dialog_id = ?1 AND "
- "message_id > ?2 ORDER BY message_id ASC LIMIT ?3"));
- TRY_RESULT(get_messages_desc_stmt, db_.get_statement("SELECT data, message_id FROM messages WHERE dialog_id = ?1 "
- "AND message_id < ?2 ORDER BY message_id DESC LIMIT ?3"));
- TRY_RESULT(
- get_messages_fts_stmt,
- db_.get_statement("SELECT dialog_id, data, search_id FROM messages WHERE search_id IN (SELECT rowid FROM "
- "messages_fts WHERE messages_fts MATCH ?1 AND rowid < ?2 ORDER BY rowid DESC LIMIT "
- "?3) ORDER BY search_id DESC"));
-
- for (int32 i = 0; i < MESSAGES_DB_INDEX_COUNT; i++) {
- TRY_RESULT(get_messages_from_index_desc_stmt,
- db_.get_statement(PSLICE() << "SELECT data, message_id FROM messages WHERE dialog_id = ?1 "
- "AND message_id < ?2 AND (index_mask & "
- << (1 << i) << ") != 0 ORDER BY message_id DESC LIMIT ?3"));
- get_messages_from_index_stmts_[i].desc_stmt_ = std::move(get_messages_from_index_desc_stmt);
-
- TRY_RESULT(get_messages_from_index_asc_stmt,
- db_.get_statement(PSLICE() << "SELECT data, message_id FROM messages WHERE dialog_id = ?1 "
- "AND message_id > ?2 AND (index_mask & "
- << (1 << i) << ") != 0 ORDER BY message_id ASC LIMIT ?3"));
- get_messages_from_index_stmts_[i].asc_stmt_ = std::move(get_messages_from_index_asc_stmt);
-
- // LOG(ERROR) << get_messages_from_index_stmts_[i].explain().ok();
- }
-
- for (int i = static_cast<int>(SearchMessagesFilter::Call) - 1, pos = 0;
- i < static_cast<int>(SearchMessagesFilter::MissedCall); i++, pos++) {
- TRY_RESULT(get_messages_from_index_stmt,
- db_.get_statement(PSLICE() << "SELECT dialog_id, data FROM messages "
- "WHERE unique_message_id < ?1 AND (index_mask & "
- << (1 << i) << ") != 0 ORDER BY unique_message_id DESC LIMIT ?2"));
- get_calls_stmts_[pos] = std::move(get_messages_from_index_stmt);
- // LOG(ERROR) << get_messages_from_index_stmts_[i].explain().ok();
- }
-
- add_message_stmt_ = std::move(add_message_stmt);
- delete_message_stmt_ = std::move(delete_message_stmt);
- delete_all_dialog_messages_stmt_ = std::move(delete_all_dialog_messages_stmt);
- delete_dialog_messages_from_user_stmt_ = std::move(delete_dialog_messages_from_user_stmt);
-
- get_message_stmt_ = std::move(get_message_stmt);
- get_message_by_random_id_stmt_ = std::move(get_message_by_random_id_stmt);
- get_message_by_unique_message_id_stmt_ = std::move(get_message_by_unique_message_id_stmt);
-
- get_expiring_messages_stmt_ = std::move(get_expiring_messages_stmt);
- get_expiring_messages_helper_stmt_ = std::move(get_expiring_messages_helper_stmt);
-
- get_messages_stmt_.asc_stmt_ = std::move(get_messages_asc_stmt);
- get_messages_stmt_.desc_stmt_ = std::move(get_messages_desc_stmt);
-
- get_messages_fts_stmt_ = std::move(get_messages_fts_stmt);
-
- // LOG(ERROR) << get_message_stmt_.explain().ok();
- // LOG(ERROR) << get_message_by_random_id_stmt_.explain().ok();
- // LOG(ERROR) << get_message_by_unique_message_id_stmt_.explain().ok();
-
- // LOG(ERROR) << get_expiring_messages_stmt_.explain().ok();
- // LOG(ERROR) << get_expiring_messages_helper_stmt_.explain().ok();
-
- // LOG(ERROR) << get_messages_asc_stmt_.explain().ok();
- // LOG(ERROR) << get_messages_desc_stmt_.explain().ok();
- // LOG(FATAL) << "EXPLAINED";
-
- return Status::OK();
- }
-
- Status add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, UserId sender_user_id,
- int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
- BufferSlice data) override {
- LOG(INFO) << "Add " << full_message_id << " to database";
- auto dialog_id = full_message_id.get_dialog_id();
- auto message_id = full_message_id.get_message_id();
- CHECK(dialog_id.is_valid());
- CHECK(message_id.is_valid());
- SCOPE_EXIT {
- add_message_stmt_.reset();
- };
- add_message_stmt_.bind_int64(1, dialog_id.get()).ensure();
- add_message_stmt_.bind_int64(2, message_id.get()).ensure();
-
- if (unique_message_id.is_valid()) {
- add_message_stmt_.bind_int32(3, unique_message_id.get()).ensure();
- } else {
- add_message_stmt_.bind_null(3).ensure();
- }
-
- if (sender_user_id.is_valid()) {
- add_message_stmt_.bind_int32(4, sender_user_id.get()).ensure();
- } else {
- add_message_stmt_.bind_null(4).ensure();
- }
-
- if (random_id != 0) {
- add_message_stmt_.bind_int64(5, random_id).ensure();
- } else {
- add_message_stmt_.bind_null(5).ensure();
- }
-
- add_message_stmt_.bind_blob(6, data.as_slice()).ensure();
-
- if (ttl_expires_at != 0) {
- add_message_stmt_.bind_int32(7, ttl_expires_at).ensure();
- } else {
- add_message_stmt_.bind_null(7).ensure();
- }
-
- if (index_mask != 0) {
- add_message_stmt_.bind_int32(8, index_mask).ensure();
- } else {
- add_message_stmt_.bind_null(8).ensure();
- }
- if (search_id != 0) {
- // add dialog_id to text
- text += PSTRING() << " \a" << dialog_id.get();
- if (index_mask) {
- for (int i = 0; i < MESSAGES_DB_INDEX_COUNT; i++) {
- if ((index_mask & (1 << i))) {
- text += PSTRING() << " \a\a" << i;
- }
- }
- }
- add_message_stmt_.bind_int64(9, search_id).ensure();
- } else {
- text = "";
- add_message_stmt_.bind_null(9).ensure();
- }
- if (!text.empty()) {
- add_message_stmt_.bind_string(10, text).ensure();
- } else {
- add_message_stmt_.bind_null(10).ensure();
- }
-
- add_message_stmt_.step().ensure();
-
- return Status::OK();
- }
-
- Status delete_message(FullMessageId full_message_id) override {
- auto dialog_id = full_message_id.get_dialog_id();
- auto message_id = full_message_id.get_message_id();
- CHECK(dialog_id.is_valid());
- CHECK(message_id.is_valid());
- SCOPE_EXIT {
- delete_message_stmt_.reset();
- };
- delete_message_stmt_.bind_int64(1, dialog_id.get()).ensure();
- delete_message_stmt_.bind_int64(2, message_id.get()).ensure();
- delete_message_stmt_.step().ensure();
- return Status::OK();
- }
-
- Status delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id) override {
- CHECK(dialog_id.is_valid());
- CHECK(from_message_id.is_valid());
- SCOPE_EXIT {
- delete_all_dialog_messages_stmt_.reset();
- };
- delete_all_dialog_messages_stmt_.bind_int64(1, dialog_id.get()).ensure();
- delete_all_dialog_messages_stmt_.bind_int64(2, from_message_id.get()).ensure();
- auto status = delete_all_dialog_messages_stmt_.step();
- if (status.is_error()) {
- LOG(ERROR) << status;
- }
- return status;
- }
-
- Status delete_dialog_messages_from_user(DialogId dialog_id, UserId sender_user_id) override {
- CHECK(dialog_id.is_valid());
- CHECK(sender_user_id.is_valid());
- SCOPE_EXIT {
- delete_dialog_messages_from_user_stmt_.reset();
- };
- delete_dialog_messages_from_user_stmt_.bind_int64(1, dialog_id.get()).ensure();
- delete_dialog_messages_from_user_stmt_.bind_int32(2, sender_user_id.get()).ensure();
- delete_dialog_messages_from_user_stmt_.step().ensure();
- return Status::OK();
- }
-
- Result<BufferSlice> get_message(FullMessageId full_message_id) override {
- auto dialog_id = full_message_id.get_dialog_id();
- auto message_id = full_message_id.get_message_id();
- CHECK(dialog_id.is_valid());
- CHECK(message_id.is_valid());
-
- SCOPE_EXIT {
- get_message_stmt_.reset();
- };
- get_message_stmt_.bind_int64(1, dialog_id.get()).ensure();
- get_message_stmt_.bind_int64(2, message_id.get()).ensure();
- get_message_stmt_.step().ensure();
- if (!get_message_stmt_.has_row()) {
- return Status::Error("Not found");
- }
- return BufferSlice(get_message_stmt_.view_blob(0));
- }
-
- Result<std::pair<DialogId, BufferSlice>> get_message_by_unique_message_id(
- ServerMessageId unique_message_id) override {
- if (!unique_message_id.is_valid()) {
- return Status::Error("unique_message_id is invalid");
- }
- SCOPE_EXIT {
- get_message_by_unique_message_id_stmt_.reset();
- };
- get_message_by_unique_message_id_stmt_.bind_int32(1, unique_message_id.get()).ensure();
- get_message_by_unique_message_id_stmt_.step().ensure();
- if (!get_message_by_unique_message_id_stmt_.has_row()) {
- return Status::Error("Not found");
- }
- DialogId dialog_id(get_message_by_unique_message_id_stmt_.view_int64(0));
- return std::make_pair(dialog_id, BufferSlice(get_message_by_unique_message_id_stmt_.view_blob(1)));
- }
-
- Result<BufferSlice> get_message_by_random_id(DialogId dialog_id, int64 random_id) override {
- SCOPE_EXIT {
- get_message_by_random_id_stmt_.reset();
- };
- get_message_by_random_id_stmt_.bind_int64(1, dialog_id.get()).ensure();
- get_message_by_random_id_stmt_.bind_int64(2, random_id).ensure();
- get_message_by_random_id_stmt_.step().ensure();
- if (!get_message_by_random_id_stmt_.has_row()) {
- return Status::Error("Not found");
- }
- return BufferSlice(get_message_by_random_id_stmt_.view_blob(0));
- }
-
- Result<BufferSlice> get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id,
- MessageId last_message_id, int32 date) override {
- int64 left_message_id = first_message_id.get();
- int64 right_message_id = last_message_id.get();
- CHECK(left_message_id <= right_message_id) << first_message_id << " " << last_message_id;
- TRY_RESULT(first_messages,
- get_messages_inner(get_messages_stmt_.asc_stmt_, dialog_id.get(), left_message_id - 1, 1));
- if (!first_messages.empty()) {
- MessageId real_first_message_id;
- int32 real_first_message_date;
- std::tie(real_first_message_id, real_first_message_date) = get_message_info(first_messages[0]);
- if (real_first_message_date <= date) {
- // we definitely have at least one suitable message, let's do a binary search
- left_message_id = real_first_message_id.get();
-
- MessageId prev_found_message_id;
- while (left_message_id <= right_message_id) {
- auto middle_message_id = left_message_id + ((right_message_id - left_message_id) >> 1);
- TRY_RESULT(messages, get_messages_inner(get_messages_stmt_.asc_stmt_, dialog_id.get(), middle_message_id, 1));
-
- MessageId message_id;
- int32 message_date = std::numeric_limits<int32>::max();
- if (!messages.empty()) {
- std::tie(message_id, message_date) = get_message_info(messages[0]);
- }
- if (message_date <= date) {
- left_message_id = message_id.get();
- } else {
- right_message_id = middle_message_id - 1;
- }
-
- if (prev_found_message_id == message_id) {
- // we may be very close to the result, let's check
- TRY_RESULT(left_messages,
- get_messages_inner(get_messages_stmt_.asc_stmt_, dialog_id.get(), left_message_id - 1, 2));
- CHECK(!left_messages.empty());
- if (left_messages.size() == 1) {
- // only one message has left, result is found
- break;
- }
-
- MessageId next_message_id;
- int32 next_message_date;
- std::tie(next_message_id, next_message_date) = get_message_info(left_messages[1]);
- if (next_message_date <= date) {
- // next message has lesser date, adjusting left message
- left_message_id = next_message_id.get();
- } else {
- // next message has bigger date, result is found
- break;
- }
- }
-
- prev_found_message_id = message_id;
- }
-
- // left_message_id is always an id of suitable message, let's return it
- return get_message({dialog_id, MessageId(left_message_id)});
- }
- }
-
- return Status::Error("Not found");
- }
-
- Result<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> get_expiring_messages(int32 expire_from,
- int32 expire_till,
- int32 limit) override {
- SCOPE_EXIT {
- get_expiring_messages_stmt_.reset();
- get_expiring_messages_helper_stmt_.reset();
- };
-
- std::vector<std::pair<DialogId, BufferSlice>> messages;
- // load messages
- if (expire_from <= expire_till) {
- get_expiring_messages_stmt_.bind_int32(1, expire_from).ensure();
- get_expiring_messages_stmt_.bind_int32(2, expire_till).ensure();
- get_expiring_messages_stmt_.step().ensure();
-
- while (get_expiring_messages_stmt_.has_row()) {
- DialogId dialog_id(get_expiring_messages_stmt_.view_int64(0));
- BufferSlice data(get_expiring_messages_stmt_.view_blob(1));
- messages.push_back(std::make_pair(dialog_id, std::move(data)));
- get_expiring_messages_stmt_.step().ensure();
- }
- }
-
- // calc next expire_till
- get_expiring_messages_helper_stmt_.bind_int32(1, expire_till).ensure();
- get_expiring_messages_helper_stmt_.bind_int32(2, limit).ensure();
- get_expiring_messages_helper_stmt_.step().ensure();
- CHECK(get_expiring_messages_helper_stmt_.has_row());
- int32 count = get_expiring_messages_helper_stmt_.view_int32(1);
- int32 next_expire_till = -1;
- if (count != 0) {
- next_expire_till = get_expiring_messages_helper_stmt_.view_int32(0);
- }
- return std::make_pair(std::move(messages), next_expire_till);
- }
-
- Result<MessagesDbMessagesResult> get_messages(MessagesDbMessagesQuery query) override {
- if (query.index_mask != 0) {
- return get_messages_from_index(query.dialog_id, query.from_message_id, query.index_mask, query.offset,
- query.limit);
- }
- return get_messages_impl(get_messages_stmt_, query.dialog_id, query.from_message_id, query.offset, query.limit);
- }
-
- static string prepare_query(Slice query) {
- auto is_word_character = [](uint32 a) {
- switch (get_unicode_simple_category(a)) {
- case UnicodeSimpleCategory::Letter:
- case UnicodeSimpleCategory::DecimalNumber:
- case UnicodeSimpleCategory::Number:
- return true;
- default:
- return a == '_';
- }
- };
-
- const size_t MAX_QUERY_SIZE = 1024;
- query.truncate(MAX_QUERY_SIZE);
- auto buf = StackAllocator::alloc(query.size() * 4 + 100);
- StringBuilder sb(buf.as_slice());
- bool in_word{false};
-
- for (auto ptr = query.ubegin(), end = query.uend(); ptr < end;) {
- uint32 code;
- auto code_ptr = ptr;
- ptr = next_utf8_unsafe(ptr, &code);
- if (is_word_character(code)) {
- if (!in_word) {
- in_word = true;
- sb << "\"";
- }
- sb << Slice(code_ptr, ptr);
- } else {
- if (in_word) {
- in_word = false;
- sb << "\" ";
- }
- }
- }
- if (in_word) {
- sb << "\" ";
- }
-
- if (sb.is_error()) {
- LOG(ERROR) << "StringBuilder buffer overflow";
- return "";
- }
-
- return sb.as_cslice().str();
- }
-
- Result<MessagesDbFtsResult> get_messages_fts(MessagesDbFtsQuery query) override {
- SCOPE_EXIT {
- get_messages_fts_stmt_.reset();
- };
-
- LOG(INFO) << tag("query", query.query) << query.dialog_id << tag("index_mask", query.index_mask)
- << tag("from_search_id", query.from_search_id) << tag("limit", query.limit);
- string words = prepare_query(query.query);
- LOG(INFO) << tag("from", query.query) << tag("to", words);
-
- // dialog_id kludge
- if (query.dialog_id.is_valid()) {
- words += PSTRING() << " \"\a" << query.dialog_id.get() << "\"";
- }
-
- // index_mask kludge
- if (query.index_mask != 0) {
- int index_i = -1;
- for (int i = 0; i < MESSAGES_DB_INDEX_COUNT; i++) {
- if (query.index_mask == (1 << i)) {
- index_i = i;
- break;
- }
- }
- if (index_i == -1) {
- return Status::Error("Union of index types is not supported");
- }
- words += PSTRING() << " \"\a\a" << index_i << "\"";
- }
-
- auto &stmt = get_messages_fts_stmt_;
- stmt.bind_string(1, words).ensure();
- if (query.from_search_id == 0) {
- query.from_search_id = std::numeric_limits<int64>::max();
- }
- stmt.bind_int64(2, query.from_search_id).ensure();
- stmt.bind_int32(3, query.limit).ensure();
- MessagesDbFtsResult result;
- auto status = stmt.step();
- if (status.is_error()) {
- LOG(ERROR) << status;
- return std::move(result);
- }
- while (stmt.has_row()) {
- auto dialog_id = stmt.view_int64(0);
- auto data_slice = stmt.view_blob(1);
- auto search_id = stmt.view_int64(2);
- result.next_search_id = search_id;
- result.messages.push_back(MessagesDbMessage{DialogId(dialog_id), BufferSlice(data_slice)});
- stmt.step().ensure();
- }
- return std::move(result);
- }
-
- Result<MessagesDbMessagesResult> get_messages_from_index(DialogId dialog_id, MessageId from_message_id,
- int32 index_mask, int32 offset, int32 limit) {
- CHECK(index_mask != 0);
- CHECK(index_mask < (1 << MESSAGES_DB_INDEX_COUNT)) << tag("index_mask", index_mask);
- int index_i = -1;
- for (int i = 0; i < MESSAGES_DB_INDEX_COUNT; i++) {
- if (index_mask == (1 << i)) {
- index_i = i;
- break;
- }
- }
- if (index_i == -1) {
- return Status::Error("Union is not supported");
- }
-
- auto &stmt = get_messages_from_index_stmts_[index_i];
- return get_messages_impl(stmt, dialog_id, from_message_id, offset, limit);
- }
-
- Result<MessagesDbCallsResult> get_calls(MessagesDbCallsQuery query) override {
- CHECK(query.index_mask != 0);
- CHECK(query.index_mask < (1 << MESSAGES_DB_INDEX_COUNT)) << tag("index_mask", query.index_mask);
- int index_i = -1;
- for (int i = 0; i < MESSAGES_DB_INDEX_COUNT; i++) {
- if (query.index_mask == (1 << i)) {
- index_i = i;
- break;
- }
- }
- if (index_i == -1) {
- return Status::Error("Union is not supported");
- }
- int32 pos;
- if (index_i + 1 == static_cast<int>(SearchMessagesFilter::Call)) {
- pos = 0;
- } else if (index_i + 1 == static_cast<int>(SearchMessagesFilter::MissedCall)) {
- pos = 1;
- } else {
- return Status::Error(PSLICE() << "Index_mask is not Call or MissedCall " << query.index_mask);
- }
-
- auto &stmt = get_calls_stmts_[pos];
- SCOPE_EXIT {
- stmt.reset();
- };
-
- stmt.bind_int32(1, query.from_unique_message_id).ensure();
- stmt.bind_int32(2, query.limit).ensure();
-
- MessagesDbCallsResult result;
- stmt.step().ensure();
- while (stmt.has_row()) {
- auto dialog_id = stmt.view_int64(0);
- auto data_slice = stmt.view_blob(1);
- result.messages.push_back(MessagesDbMessage{DialogId(dialog_id), BufferSlice(data_slice)});
- stmt.step().ensure();
- }
- return std::move(result);
- }
-
- Status begin_transaction() override {
- return db_.begin_transaction();
- }
- Status commit_transaction() override {
- return db_.commit_transaction();
- }
-
- private:
- SqliteDb db_;
-
- SqliteStatement add_message_stmt_;
-
- SqliteStatement delete_message_stmt_;
- SqliteStatement delete_all_dialog_messages_stmt_;
- SqliteStatement delete_dialog_messages_from_user_stmt_;
-
- SqliteStatement get_message_stmt_;
- SqliteStatement get_message_by_random_id_stmt_;
- SqliteStatement get_message_by_unique_message_id_stmt_;
- SqliteStatement get_expiring_messages_stmt_;
- SqliteStatement get_expiring_messages_helper_stmt_;
-
- struct GetMessagesStmt {
- SqliteStatement asc_stmt_;
- SqliteStatement desc_stmt_;
- };
- GetMessagesStmt get_messages_stmt_;
-
- std::array<GetMessagesStmt, MESSAGES_DB_INDEX_COUNT> get_messages_from_index_stmts_;
- std::array<SqliteStatement, 2> get_calls_stmts_;
-
- SqliteStatement get_messages_fts_stmt_;
-
- Result<MessagesDbMessagesResult> get_messages_impl(GetMessagesStmt &stmt, DialogId dialog_id,
- MessageId from_message_id, int32 offset, int32 limit) {
- CHECK(dialog_id.is_valid()) << dialog_id;
- CHECK(from_message_id.is_valid());
-
- auto message_id = from_message_id.get();
-
- if (message_id >= MessageId::max().get()) {
- message_id--;
- }
-
- auto left_message_id = message_id;
- auto left_cnt = limit + offset;
-
- auto right_message_id = message_id - 1;
- auto right_cnt = -offset;
-
- std::vector<BufferSlice> left;
- std::vector<BufferSlice> right;
-
- if (left_cnt != 0) {
- if (right_cnt == 1 && false) {
- left_message_id++;
- left_cnt++;
- }
-
- TRY_RESULT(left_tmp, get_messages_inner(stmt.desc_stmt_, dialog_id.get(), left_message_id, left_cnt));
- left = std::move(left_tmp);
-
- if (right_cnt == 1 && !left.empty() && false /*get_message_id(left[0].as_slice()) == message_id*/) {
- right_cnt = 0;
- }
- }
- if (right_cnt != 0) {
- TRY_RESULT(right_tmp, get_messages_inner(stmt.asc_stmt_, dialog_id.get(), right_message_id, right_cnt));
- right = std::move(right_tmp);
- std::reverse(right.begin(), right.end());
- }
- if (left.empty()) {
- return MessagesDbMessagesResult{std::move(right)};
- }
- if (right.empty()) {
- return MessagesDbMessagesResult{std::move(left)};
- }
-
- right.reserve(right.size() + left.size());
- std::move(left.begin(), left.end(), std::back_inserter(right));
-
- return MessagesDbMessagesResult{std::move(right)};
- }
-
- Result<std::vector<BufferSlice>> get_messages_inner(SqliteStatement &stmt, int64 dialog_id, int64 from_message_id,
- int32 limit) {
- SCOPE_EXIT {
- stmt.reset();
- };
- stmt.bind_int64(1, dialog_id).ensure();
- stmt.bind_int64(2, from_message_id).ensure();
- stmt.bind_int32(3, limit).ensure();
-
- std::vector<BufferSlice> result;
- stmt.step().ensure();
- while (stmt.has_row()) {
- auto data_slice = stmt.view_blob(0);
- result.emplace_back(data_slice);
- auto message_id = stmt.view_int64(1);
- LOG(INFO) << "Load " << MessageId(message_id) << " in " << DialogId(dialog_id) << " from database";
- stmt.step().ensure();
- }
- return std::move(result);
- }
-
- static std::tuple<MessageId, int32> get_message_info(const BufferSlice &message) {
- LogEventParser message_date_parser(message.as_slice());
- int32 flags;
- td::parse(flags, message_date_parser);
- bool has_sender = (flags >> 10) & 1;
- MessageId message_id;
- td::parse(message_id, message_date_parser);
- UserId sender_user_id;
- if (has_sender) {
- td::parse(sender_user_id, message_date_parser);
- }
- int32 date;
- td::parse(date, message_date_parser);
- LOG(INFO) << "Load " << message_id << " sent at " << date << " by " << sender_user_id;
- return std::make_tuple(message_id, date);
- }
-};
-
-std::shared_ptr<MessagesDbSyncSafeInterface> create_messages_db_sync(
- std::shared_ptr<SqliteConnectionSafe> sqlite_connection) {
- class MessagesDbSyncSafe : public MessagesDbSyncSafeInterface {
- public:
- explicit MessagesDbSyncSafe(std::shared_ptr<SqliteConnectionSafe> sqlite_connection)
- : lsls_db_([safe_connection = std::move(sqlite_connection)] {
- return std::make_unique<MessagesDbImpl>(safe_connection->get().clone());
- }) {
- }
- MessagesDbSyncInterface &get() override {
- return *lsls_db_.get();
- }
-
- private:
- LazySchedulerLocalStorage<std::unique_ptr<MessagesDbSyncInterface>> lsls_db_;
- };
- return std::make_shared<MessagesDbSyncSafe>(std::move(sqlite_connection));
-}
-
-class MessagesDbAsync : public MessagesDbAsyncInterface {
- public:
- MessagesDbAsync(std::shared_ptr<MessagesDbSyncSafeInterface> sync_db, int32 scheduler_id) {
- impl_ = create_actor_on_scheduler<Impl>("MessagesDbActor", scheduler_id, std::move(sync_db));
- }
-
- void add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, UserId sender_user_id,
- int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
- BufferSlice data, Promise<> promise) override {
- send_closure_later(impl_, &Impl::add_message, full_message_id, unique_message_id, sender_user_id, random_id,
- ttl_expires_at, index_mask, search_id, std::move(text), std::move(data), std::move(promise));
- }
-
- void delete_message(FullMessageId full_message_id, Promise<> promise) override {
- send_closure_later(impl_, &Impl::delete_message, full_message_id, std::move(promise));
- }
- void delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id, Promise<> promise) override {
- send_closure_later(impl_, &Impl::delete_all_dialog_messages, dialog_id, from_message_id, std::move(promise));
- }
- void delete_dialog_messages_from_user(DialogId dialog_id, UserId sender_user_id, Promise<> promise) override {
- send_closure_later(impl_, &Impl::delete_dialog_messages_from_user, dialog_id, sender_user_id, std::move(promise));
- }
-
- void get_message(FullMessageId full_message_id, Promise<BufferSlice> promise) override {
- send_closure_later(impl_, &Impl::get_message, full_message_id, std::move(promise));
- }
- void get_message_by_unique_message_id(ServerMessageId unique_message_id,
- Promise<std::pair<DialogId, BufferSlice>> promise) override {
- send_closure_later(impl_, &Impl::get_message_by_unique_message_id, unique_message_id, std::move(promise));
- }
- void get_message_by_random_id(DialogId dialog_id, int64 random_id, Promise<BufferSlice> promise) override {
- send_closure_later(impl_, &Impl::get_message_by_random_id, dialog_id, random_id, std::move(promise));
- }
- void get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id, MessageId last_message_id, int32 date,
- Promise<BufferSlice> promise) override {
- send_closure_later(impl_, &Impl::get_dialog_message_by_date, dialog_id, first_message_id, last_message_id, date,
- std::move(promise));
- }
-
- void get_messages(MessagesDbMessagesQuery query, Promise<MessagesDbMessagesResult> promise) override {
- send_closure_later(impl_, &Impl::get_messages, std::move(query), std::move(promise));
- }
- void get_calls(MessagesDbCallsQuery query, Promise<MessagesDbCallsResult> promise) override {
- send_closure_later(impl_, &Impl::get_calls, std::move(query), std::move(promise));
- }
- void get_messages_fts(MessagesDbFtsQuery query, Promise<MessagesDbFtsResult> promise) override {
- send_closure_later(impl_, &Impl::get_messages_fts, std::move(query), std::move(promise));
- }
- void get_expiring_messages(
- int32 expire_from, int32 expire_till, int32 limit,
- Promise<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> promise) override {
- send_closure_later(impl_, &Impl::get_expiring_messages, expire_from, expire_till, limit, std::move(promise));
- }
-
- void close(Promise<> promise) override {
- send_closure_later(impl_, &Impl::close, std::move(promise));
- }
-
- void force_flush() override {
- send_closure_later(impl_, &Impl::force_flush);
- }
-
- private:
- class Impl : public Actor {
- public:
- explicit Impl(std::shared_ptr<MessagesDbSyncSafeInterface> sync_db_safe) : sync_db_safe_(std::move(sync_db_safe)) {
- }
- void add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, UserId sender_user_id,
- int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
- BufferSlice data, Promise<> promise) {
- add_write_query([=, promise = std::move(promise), data = std::move(data), text = std::move(text)](Unit) mutable {
- promise.set_result(sync_db_->add_message(full_message_id, unique_message_id, sender_user_id, random_id,
- ttl_expires_at, index_mask, search_id, std::move(text),
- std::move(data)));
- });
- }
-
- void delete_message(FullMessageId full_message_id, Promise<> promise) {
- add_write_query([=, promise = std::move(promise)](Unit) mutable {
- promise.set_result(sync_db_->delete_message(full_message_id));
- });
- }
- void delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id, Promise<> promise) {
- add_read_query();
- promise.set_result(sync_db_->delete_all_dialog_messages(dialog_id, from_message_id));
- }
- void delete_dialog_messages_from_user(DialogId dialog_id, UserId sender_user_id, Promise<> promise) {
- add_read_query();
- promise.set_result(sync_db_->delete_dialog_messages_from_user(dialog_id, sender_user_id));
- }
-
- void get_message(FullMessageId full_message_id, Promise<BufferSlice> promise) {
- add_read_query();
- promise.set_result(sync_db_->get_message(full_message_id));
- }
- void get_message_by_unique_message_id(ServerMessageId unique_message_id,
- Promise<std::pair<DialogId, BufferSlice>> promise) {
- add_read_query();
- promise.set_result(sync_db_->get_message_by_unique_message_id(unique_message_id));
- }
- void get_message_by_random_id(DialogId dialog_id, int64 random_id, Promise<BufferSlice> promise) {
- add_read_query();
- promise.set_result(sync_db_->get_message_by_random_id(dialog_id, random_id));
- }
- void get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id, MessageId last_message_id,
- int32 date, Promise<BufferSlice> promise) {
- add_read_query();
- promise.set_result(sync_db_->get_dialog_message_by_date(dialog_id, first_message_id, last_message_id, date));
- }
-
- void get_messages(MessagesDbMessagesQuery query, Promise<MessagesDbMessagesResult> promise) {
- add_read_query();
- promise.set_result(sync_db_->get_messages(std::move(query)));
- }
- void get_calls(MessagesDbCallsQuery query, Promise<MessagesDbCallsResult> promise) {
- add_read_query();
- promise.set_result(sync_db_->get_calls(std::move(query)));
- }
- void get_messages_fts(MessagesDbFtsQuery query, Promise<MessagesDbFtsResult> promise) {
- add_read_query();
- promise.set_result(sync_db_->get_messages_fts(std::move(query)));
- }
- void get_expiring_messages(int32 expire_from, int32 expire_till, int32 limit,
- Promise<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> promise) {
- add_read_query();
- promise.set_result(sync_db_->get_expiring_messages(expire_from, expire_till, limit));
- }
-
- void close(Promise<> promise) {
- do_flush();
- sync_db_safe_.reset();
- sync_db_ = nullptr;
- promise.set_value(Unit());
- stop();
- }
-
- void force_flush() {
- LOG(INFO) << "MessagesDb flushed";
- do_flush();
- }
-
- private:
- std::shared_ptr<MessagesDbSyncSafeInterface> sync_db_safe_;
- MessagesDbSyncInterface *sync_db_ = nullptr;
-
- static constexpr size_t MAX_PENDING_QUERIES_COUNT{50};
- static constexpr double MAX_PENDING_QUERIES_DELAY{1};
- std::vector<Promise<>> pending_writes_;
- double wakeup_at_ = 0;
- template <class F>
- void add_write_query(F &&f) {
- pending_writes_.push_back(PromiseCreator::lambda(std::forward<F>(f), PromiseCreator::Ignore()));
- if (pending_writes_.size() > MAX_PENDING_QUERIES_COUNT) {
- do_flush();
- wakeup_at_ = 0;
- } else if (wakeup_at_ == 0) {
- wakeup_at_ = Time::now_cached() + MAX_PENDING_QUERIES_DELAY;
- }
- if (wakeup_at_ != 0) {
- set_timeout_at(wakeup_at_);
- }
- }
- void add_read_query() {
- do_flush();
- }
- void do_flush() {
- if (pending_writes_.empty()) {
- return;
- }
- sync_db_->begin_transaction().ensure();
- for (auto &query : pending_writes_) {
- query.set_value(Unit());
- }
- sync_db_->commit_transaction().ensure();
- pending_writes_.clear();
- cancel_timeout();
- }
- void timeout_expired() override {
- do_flush();
- }
-
- void start_up() override {
- sync_db_ = &sync_db_safe_->get();
- }
- };
- ActorOwn<Impl> impl_;
-};
-
-std::shared_ptr<MessagesDbAsyncInterface> create_messages_db_async(std::shared_ptr<MessagesDbSyncSafeInterface> sync_db,
- int32 scheduler_id) {
- return std::make_shared<MessagesDbAsync>(std::move(sync_db), scheduler_id);
-}
-
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessagesDb.h b/protocols/Telegram/tdlib/td/td/telegram/MessagesDb.h
deleted file mode 100644
index 86843dd59b..0000000000
--- a/protocols/Telegram/tdlib/td/td/telegram/MessagesDb.h
+++ /dev/null
@@ -1,169 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/telegram/DialogId.h"
-#include "td/telegram/MessageId.h"
-
-#include "td/actor/PromiseFuture.h"
-
-#include "td/db/SqliteConnectionSafe.h"
-
-#include "td/utils/buffer.h"
-#include "td/utils/common.h"
-#include "td/utils/Status.h"
-
-#include <memory>
-#include <utility>
-
-namespace td {
-// append only before Size
-enum class SearchMessagesFilter {
- Empty,
- Animation,
- Audio,
- Document,
- Photo,
- Video,
- VoiceNote,
- PhotoAndVideo,
- Url,
- ChatPhoto,
- Call,
- MissedCall,
- VideoNote,
- VoiceAndVideoNote,
- Mention,
- UnreadMention,
- Size
-};
-
-struct MessagesDbMessagesQuery {
- DialogId dialog_id;
- int32 index_mask{0};
- MessageId from_message_id;
- int32 offset{0};
- int32 limit{100};
-};
-
-struct MessagesDbMessagesResult {
- std::vector<BufferSlice> messages;
-};
-
-struct MessagesDbMessage {
- DialogId dialog_id;
- BufferSlice data;
-};
-
-struct MessagesDbFtsQuery {
- string query;
- DialogId dialog_id;
- int32 index_mask{0};
- int64 from_search_id{0};
- int32 limit{100};
-};
-struct MessagesDbFtsResult {
- std::vector<MessagesDbMessage> messages;
- int64 next_search_id{1};
-};
-
-struct MessagesDbCallsQuery {
- int32 index_mask{0};
- int32 from_unique_message_id{0};
- int32 limit{100};
-};
-struct MessagesDbCallsResult {
- std::vector<MessagesDbMessage> messages;
-};
-
-class MessagesDbSyncInterface {
- public:
- MessagesDbSyncInterface() = default;
- MessagesDbSyncInterface(const MessagesDbSyncInterface &) = delete;
- MessagesDbSyncInterface &operator=(const MessagesDbSyncInterface &) = delete;
- virtual ~MessagesDbSyncInterface() = default;
-
- virtual Status add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, UserId sender_user_id,
- int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
- BufferSlice data) = 0;
-
- virtual Status delete_message(FullMessageId full_message_id) = 0;
- virtual Status delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id) = 0;
- virtual Status delete_dialog_messages_from_user(DialogId dialog_id, UserId sender_user_id) = 0;
-
- virtual Result<BufferSlice> get_message(FullMessageId full_message_id) = 0;
- virtual Result<std::pair<DialogId, BufferSlice>> get_message_by_unique_message_id(
- ServerMessageId unique_message_id) = 0;
- virtual Result<BufferSlice> get_message_by_random_id(DialogId dialog_id, int64 random_id) = 0;
- virtual Result<BufferSlice> get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id,
- MessageId last_message_id, int32 date) = 0;
-
- virtual Result<MessagesDbMessagesResult> get_messages(MessagesDbMessagesQuery query) = 0;
-
- virtual Result<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> get_expiring_messages(
- int32 expire_from, int32 expire_till, int32 limit) = 0;
- virtual Result<MessagesDbCallsResult> get_calls(MessagesDbCallsQuery query) = 0;
- virtual Result<MessagesDbFtsResult> get_messages_fts(MessagesDbFtsQuery query) = 0;
-
- virtual Status begin_transaction() = 0;
- virtual Status commit_transaction() = 0;
-};
-
-class MessagesDbSyncSafeInterface {
- public:
- MessagesDbSyncSafeInterface() = default;
- MessagesDbSyncSafeInterface(const MessagesDbSyncSafeInterface &) = delete;
- MessagesDbSyncSafeInterface &operator=(const MessagesDbSyncSafeInterface &) = delete;
- virtual ~MessagesDbSyncSafeInterface() = default;
-
- virtual MessagesDbSyncInterface &get() = 0;
-};
-
-class MessagesDbAsyncInterface {
- public:
- MessagesDbAsyncInterface() = default;
- MessagesDbAsyncInterface(const MessagesDbAsyncInterface &) = delete;
- MessagesDbAsyncInterface &operator=(const MessagesDbAsyncInterface &) = delete;
- virtual ~MessagesDbAsyncInterface() = default;
-
- virtual void add_message(FullMessageId full_message_id, ServerMessageId unique_message_id, UserId sender_user_id,
- int64 random_id, int32 ttl_expires_at, int32 index_mask, int64 search_id, string text,
- BufferSlice data, Promise<> promise) = 0;
-
- virtual void delete_message(FullMessageId full_message_id, Promise<> promise) = 0;
- virtual void delete_all_dialog_messages(DialogId dialog_id, MessageId from_message_id, Promise<> promise) = 0;
- virtual void delete_dialog_messages_from_user(DialogId dialog_id, UserId sender_user_id, Promise<> promise) = 0;
-
- virtual void get_message(FullMessageId full_message_id, Promise<BufferSlice> promise) = 0;
- virtual void get_message_by_unique_message_id(ServerMessageId unique_message_id,
- Promise<std::pair<DialogId, BufferSlice>> promise) = 0;
- virtual void get_message_by_random_id(DialogId dialog_id, int64 random_id, Promise<BufferSlice> promise) = 0;
- virtual void get_dialog_message_by_date(DialogId dialog_id, MessageId first_message_id, MessageId last_message_id,
- int32 date, Promise<BufferSlice> promise) = 0;
-
- virtual void get_messages(MessagesDbMessagesQuery query, Promise<MessagesDbMessagesResult>) = 0;
-
- virtual void get_calls(MessagesDbCallsQuery, Promise<MessagesDbCallsResult>) = 0;
- virtual void get_messages_fts(MessagesDbFtsQuery query, Promise<MessagesDbFtsResult> promise) = 0;
-
- virtual void get_expiring_messages(
- int32 expire_from, int32 expire_till, int32 limit,
- Promise<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> promise) = 0;
-
- virtual void close(Promise<> promise) = 0;
- virtual void force_flush() = 0;
-};
-
-Status init_messages_db(SqliteDb &db, int version) TD_WARN_UNUSED_RESULT;
-Status drop_messages_db(SqliteDb &db, int version) TD_WARN_UNUSED_RESULT;
-
-std::shared_ptr<MessagesDbSyncSafeInterface> create_messages_db_sync(
- std::shared_ptr<SqliteConnectionSafe> sqlite_connection);
-
-std::shared_ptr<MessagesDbAsyncInterface> create_messages_db_async(std::shared_ptr<MessagesDbSyncSafeInterface> sync_db,
- int32 scheduler_id);
-}; // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessagesManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/MessagesManager.cpp
index a2b603db6c..2891bf3133 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/MessagesManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessagesManager.cpp
@@ -1,300 +1,480 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/MessagesManager.h"
-#include "td/telegram/secret_api.hpp"
-#include "td/telegram/td_api.hpp"
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/PromiseFuture.h"
-#include "td/actor/SleepActor.h"
-
-#include "td/db/binlog/BinlogHelper.h"
-
-#include "td/telegram/AnimationsManager.h"
-#include "td/telegram/AnimationsManager.hpp"
-#include "td/telegram/AudiosManager.h"
-#include "td/telegram/AudiosManager.hpp"
#include "td/telegram/AuthManager.h"
-#include "td/telegram/ConfigShared.h"
+#include "td/telegram/ChainId.h"
+#include "td/telegram/ChannelType.h"
+#include "td/telegram/ChatId.h"
#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Dependencies.h"
+#include "td/telegram/DialogActionBar.h"
#include "td/telegram/DialogDb.h"
-#include "td/telegram/DocumentsManager.h"
-#include "td/telegram/DocumentsManager.hpp"
+#include "td/telegram/DialogFilter.h"
+#include "td/telegram/DialogFilter.hpp"
+#include "td/telegram/DialogLocation.h"
+#include "td/telegram/DialogNotificationSettings.hpp"
+#include "td/telegram/DownloadManager.h"
+#include "td/telegram/DraftMessage.h"
+#include "td/telegram/DraftMessage.hpp"
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileId.hpp"
+#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/FileManager.h"
-#include "td/telegram/Game.h"
-#include "td/telegram/Game.hpp"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
-#include "td/telegram/HashtagHints.h"
+#include "td/telegram/GroupCallManager.h"
#include "td/telegram/InlineQueriesManager.h"
+#include "td/telegram/InputMessageText.h"
+#include "td/telegram/LinkManager.h"
+#include "td/telegram/Location.h"
#include "td/telegram/logevent/LogEvent.h"
-#include "td/telegram/MessagesDb.h"
+#include "td/telegram/MessageContent.h"
+#include "td/telegram/MessageDb.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/MessageEntity.hpp"
+#include "td/telegram/MessageReaction.h"
+#include "td/telegram/MessageReaction.hpp"
+#include "td/telegram/MessageReplyInfo.hpp"
+#include "td/telegram/MessageSender.h"
#include "td/telegram/misc.h"
-#include "td/telegram/net/NetActor.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQuery.h"
-#include "td/telegram/Payments.h"
-#include "td/telegram/Payments.hpp"
+#include "td/telegram/NotificationGroupType.h"
+#include "td/telegram/NotificationManager.h"
+#include "td/telegram/NotificationSettingsManager.h"
+#include "td/telegram/NotificationSound.h"
+#include "td/telegram/NotificationType.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/PollId.h"
+#include "td/telegram/PublicDialogType.h"
+#include "td/telegram/ReplyMarkup.h"
#include "td/telegram/ReplyMarkup.hpp"
-#include "td/telegram/SecretChatActor.h"
#include "td/telegram/SecretChatsManager.h"
-#include "td/telegram/SequenceDispatcher.h"
-#include "td/telegram/StickersManager.h"
-#include "td/telegram/StickersManager.hpp"
+#include "td/telegram/SponsoredMessageManager.h"
+#include "td/telegram/StickerType.h"
#include "td/telegram/Td.h"
-#include "td/telegram/TopDialogManager.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/telegram/TopDialogCategory.h"
#include "td/telegram/UpdatesManager.h"
-#include "td/telegram/VideoNotesManager.h"
-#include "td/telegram/VideoNotesManager.hpp"
-#include "td/telegram/VideosManager.h"
-#include "td/telegram/VideosManager.hpp"
-#include "td/telegram/VoiceNotesManager.h"
-#include "td/telegram/VoiceNotesManager.hpp"
+#include "td/telegram/Version.h"
#include "td/telegram/WebPageId.h"
-#include "td/telegram/WebPagesManager.h"
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/SqliteKeyValue.h"
+#include "td/db/SqliteKeyValueAsync.h"
+
+#include "td/actor/SleepActor.h"
+
+#include "td/utils/algorithm.h"
#include "td/utils/format.h"
-#include "td/utils/HttpUrl.h"
-#include "td/utils/MimeType.h"
#include "td/utils/misc.h"
#include "td/utils/PathView.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
#include "td/utils/tl_helpers.h"
-#include "td/utils/tl_storers.h"
#include "td/utils/utf8.h"
#include <algorithm>
#include <cstring>
-#include <iterator>
#include <limits>
-#include <set>
#include <tuple>
#include <type_traits>
-#include <unordered_map>
#include <unordered_set>
#include <utility>
namespace td {
-class NetActorOnce : public NetActor {
- void hangup() override {
- on_error(0, Status::Error(500, "Request aborted"));
- stop();
+class GetDialogFiltersQuery final : public Td::ResultHandler {
+ Promise<vector<tl_object_ptr<telegram_api::DialogFilter>>> promise_;
+
+ public:
+ explicit GetDialogFiltersQuery(Promise<vector<tl_object_ptr<telegram_api::DialogFilter>>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getDialogFilters()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getDialogFilters>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
}
- void on_result_finish() override {
- stop();
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
}
};
-void dummyUpdate::store(TlStorerToString &s, const char *field_name) const {
- s.store_class_begin(field_name, "dummyUpdate");
- s.store_class_end();
-}
+class UpdateDialogFilterQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
-class updateSentMessage : public telegram_api::Update {
public:
- int64 random_id_;
+ explicit UpdateDialogFilterQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
- MessageId message_id_;
- int32 date_;
+ void send(DialogFilterId dialog_filter_id, tl_object_ptr<telegram_api::DialogFilter> filter) {
+ int32 flags = 0;
+ if (filter != nullptr) {
+ flags |= telegram_api::messages_updateDialogFilter::FILTER_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_updateDialogFilter(flags, dialog_filter_id.get(), std::move(filter))));
+ }
- updateSentMessage(int64 random_id, MessageId message_id, int32 date)
- : random_id_(random_id), message_id_(message_id), date_(date) {
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_updateDialogFilter>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ LOG(INFO) << "Receive result for UpdateDialogFilterQuery: " << result_ptr.ok();
+ promise_.set_value(Unit());
}
- static constexpr int32 ID = 1234567890;
- int32 get_id() const override {
- return ID;
+ void on_error(Status status) final {
+ LOG(ERROR) << "Receive error for UpdateDialogFilterQuery: " << status;
+ promise_.set_error(std::move(status));
}
+};
- void store(TlStorerUnsafe &s) const override {
- UNREACHABLE();
+class UpdateDialogFiltersOrderQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit UpdateDialogFiltersOrderQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void store(TlStorerCalcLength &s) const override {
- UNREACHABLE();
+ void send(const vector<DialogFilterId> &dialog_filter_ids, int32 main_dialog_list_position) {
+ auto filter_ids = transform(dialog_filter_ids, [](auto dialog_filter_id) { return dialog_filter_id.get(); });
+ CHECK(0 <= main_dialog_list_position);
+ CHECK(main_dialog_list_position <= static_cast<int32>(filter_ids.size()));
+ filter_ids.insert(filter_ids.begin() + main_dialog_list_position, 0);
+ send_query(G()->net_query_creator().create(telegram_api::messages_updateDialogFiltersOrder(std::move(filter_ids))));
}
- void store(TlStorerToString &s, const char *field_name) const override {
- s.store_class_begin(field_name, "updateSentMessage");
- s.store_field("random_id_", random_id_);
- s.store_field("message_id_", message_id_.get());
- s.store_field("date_", date_);
- s.store_class_end();
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_updateDialogFiltersOrder>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ LOG(INFO) << "Receive result for UpdateDialogFiltersOrderQuery: " << result_ptr.ok();
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
}
};
-class GetDialogQuery : public Td::ResultHandler {
+class GetSuggestedDialogFiltersQuery final : public Td::ResultHandler {
+ Promise<vector<tl_object_ptr<telegram_api::dialogFilterSuggested>>> promise_;
+
+ public:
+ explicit GetSuggestedDialogFiltersQuery(Promise<vector<tl_object_ptr<telegram_api::dialogFilterSuggested>>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getSuggestedDialogFilters()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getSuggestedDialogFilters>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetOnlinesQuery final : public Td::ResultHandler {
+ DialogId dialog_id_;
+
+ public:
+ void send(DialogId dialog_id) {
+ dialog_id_ = dialog_id;
+ CHECK(dialog_id.get_type() == DialogType::Channel);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_getOnlines(std::move(input_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getOnlines>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ td_->messages_manager_->on_update_dialog_online_member_count(dialog_id_, result->onlines_, true);
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetOnlinesQuery");
+ td_->messages_manager_->on_update_dialog_online_member_count(dialog_id_, 0, true);
+ }
+};
+
+class GetAllDraftsQuery final : public Td::ResultHandler {
+ public:
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getAllDrafts()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getAllDrafts>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetAllDraftsQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for GetAllDraftsQuery: " << status;
+ }
+ status.ignore();
+ }
+};
+
+class GetDialogQuery final : public Td::ResultHandler {
DialogId dialog_id_;
public:
void send(DialogId dialog_id) {
dialog_id_ = dialog_id;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getPeerDialogs(
- td->messages_manager_->get_input_dialog_peers({dialog_id}, AccessRights::Read)))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getPeerDialogs(
+ td_->messages_manager_->get_input_dialog_peers({dialog_id}, AccessRights::Read)),
+ {{dialog_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getPeerDialogs>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
- LOG(INFO) << "Receive chat: " << to_string(result);
-
- td->contacts_manager_->on_get_chats(std::move(result->chats_));
- td->contacts_manager_->on_get_users(std::move(result->users_));
- td->messages_manager_->on_get_dialogs(
- std::move(result->dialogs_), -1, std::move(result->messages_),
- PromiseCreator::lambda([td = td, dialog_id = dialog_id_](Result<> result) {
- if (result.is_ok()) {
- td->messages_manager_->on_get_dialog_success(dialog_id);
- } else {
- if (G()->close_flag()) {
- return;
- }
- td->messages_manager_->on_get_dialog_error(dialog_id, result.error(), "OnGetDialogs");
- td->messages_manager_->on_get_dialog_fail(dialog_id, result.move_as_error());
- }
- }));
+ LOG(INFO) << "Receive result for GetDialogQuery: " << to_string(result);
+
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetDialogQuery");
+ td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetDialogQuery");
+ td_->messages_manager_->on_get_dialogs(FolderId(), std::move(result->dialogs_), -1, std::move(result->messages_),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(),
+ dialog_id = dialog_id_](Result<> result) {
+ send_closure(actor_id, &MessagesManager::on_get_dialog_query_finished,
+ dialog_id,
+ result.is_error() ? result.move_as_error() : Status::OK());
+ }));
}
- void on_error(uint64 id, Status status) override {
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogQuery");
- td->messages_manager_->on_get_dialog_fail(dialog_id_, std::move(status));
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogQuery");
+ td_->messages_manager_->on_get_dialog_query_finished(dialog_id_, std::move(status));
}
};
-class GetPinnedDialogsQuery : public NetActorOnce {
+class GetDialogsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
+ bool is_single_ = false;
public:
- explicit GetPinnedDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit GetDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- NetQueryRef send(uint64 sequence_id) {
- auto query = G()->net_query_creator().create(create_storer(telegram_api::messages_getPinnedDialogs()));
- auto result = query.get_weak();
- send_closure(td->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback,
- std::move(query), actor_shared(this), sequence_id);
- return result;
+ void send(vector<InputDialogId> input_dialog_ids) {
+ CHECK(!input_dialog_ids.empty());
+ CHECK(input_dialog_ids.size() <= 100);
+ is_single_ = input_dialog_ids.size() == 1;
+ auto input_dialog_peers = InputDialogId::get_input_dialog_peers(input_dialog_ids);
+ CHECK(input_dialog_peers.size() == input_dialog_ids.size());
+ send_query(G()->net_query_creator().create(telegram_api::messages_getPeerDialogs(std::move(input_dialog_peers))));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_getPinnedDialogs>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getPeerDialogs>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
- LOG(INFO) << "Receive pinned chats: " << to_string(result);
+ LOG(INFO) << "Receive result for GetDialogsQuery: " << to_string(result);
- td->contacts_manager_->on_get_chats(std::move(result->chats_));
- td->contacts_manager_->on_get_users(std::move(result->users_));
- std::reverse(result->dialogs_.begin(), result->dialogs_.end());
- td->messages_manager_->on_get_dialogs(std::move(result->dialogs_), -2, std::move(result->messages_),
- std::move(promise_));
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetDialogsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetDialogsQuery");
+ td_->messages_manager_->on_get_dialogs(FolderId(), std::move(result->dialogs_), -1, std::move(result->messages_),
+ std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ if (is_single_ && status.code() == 400) {
+ return promise_.set_value(Unit());
+ }
promise_.set_error(std::move(status));
}
};
-/*
-class GetDialogsQuery : public Td::ResultHandler {
+
+class GetPinnedDialogsQuery final : public Td::ResultHandler {
+ FolderId folder_id_;
Promise<Unit> promise_;
public:
- explicit GetDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit GetPinnedDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(vector<DialogId> &&dialog_ids) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getPeerDialogs(
- td->messages_manager_->get_input_dialog_peers(dialog_ids, AccessRights::Read)))));
+ NetQueryRef send(FolderId folder_id) {
+ folder_id_ = folder_id;
+ auto query =
+ G()->net_query_creator().create(telegram_api::messages_getPinnedDialogs(folder_id.get()), {{folder_id}});
+ auto result = query.get_weak();
+ send_query(std::move(query));
+ return result;
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_getPeerDialogs>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getPinnedDialogs>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto result = result_ptr.move_as_ok();
- LOG(INFO) << "Receive chats: " << to_string(result);
+ LOG(INFO) << "Receive pinned chats in " << folder_id_ << ": " << to_string(result);
- td->contacts_manager_->on_get_chats(std::move(result->chats_));
- td->contacts_manager_->on_get_users(std::move(result->users_));
- td->messages_manager_->on_get_dialogs(std::move(result->dialogs_), -1, std::move(result->messages_),
- std::move(promise_));
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetPinnedDialogsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetPinnedDialogsQuery");
+ td_->messages_manager_->on_get_dialogs(folder_id_, std::move(result->dialogs_), -2, std::move(result->messages_),
+ std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-*/
-class GetMessagesQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class GetDialogUnreadMarksQuery final : public Td::ResultHandler {
public:
- explicit GetMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getDialogUnreadMarks()));
}
- void send(vector<tl_object_ptr<telegram_api::InputMessage>> &&message_ids) {
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::messages_getMessages(std::move(message_ids)))));
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getDialogUnreadMarks>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto results = result_ptr.move_as_ok();
+ for (auto &result : results) {
+ td_->messages_manager_->on_update_dialog_is_marked_as_unread(DialogId(result), true);
+ }
+
+ G()->td_db()->get_binlog_pmc()->set("fetched_marks_as_unread", "1");
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_getMessages>(packet);
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for GetDialogUnreadMarksQuery: " << status;
+ }
+ status.ignore();
+ }
+};
+
+class GetDiscussionMessageQuery final : public Td::ResultHandler {
+ Promise<MessageThreadInfo> promise_;
+ DialogId dialog_id_;
+ MessageId message_id_;
+ DialogId expected_dialog_id_;
+ MessageId expected_message_id_;
+
+ public:
+ explicit GetDiscussionMessageQuery(Promise<MessageThreadInfo> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id) {
+ dialog_id_ = dialog_id;
+ message_id_ = message_id;
+ expected_dialog_id_ = expected_dialog_id;
+ expected_message_id_ = expected_message_id;
+ CHECK(expected_dialog_id_.is_valid());
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getDiscussionMessage(std::move(input_peer), message_id.get_server_message_id().get())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getDiscussionMessage>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto ptr = result_ptr.move_as_ok();
- // LOG(INFO) << "Receive result for GetMessagesQuery " << to_string(ptr);
- int32 constructor_id = ptr->get_id();
- switch (constructor_id) {
- case telegram_api::messages_messages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messages>(ptr);
+ td_->messages_manager_->process_discussion_message(result_ptr.move_as_ok(), dialog_id_, message_id_,
+ expected_dialog_id_, expected_message_id_, std::move(promise_));
+ }
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_messages(std::move(messages->messages_), false, "get messages");
- break;
- }
- case telegram_api::messages_messagesSlice::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(ptr);
+ void on_error(Status status) final {
+ if (expected_dialog_id_ == dialog_id_) {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDiscussionMessageQuery");
+ }
+ if (status.message() == "MSG_ID_INVALID") {
+ td_->messages_manager_->get_message_from_server({dialog_id_, message_id_}, Promise<Unit>(),
+ "GetDiscussionMessageQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_messages(std::move(messages->messages_), false, "get messages slice");
- break;
- }
- case telegram_api::messages_channelMessages::ID: {
- LOG(ERROR) << "Receive channel messages in GetMessagesQuery";
- auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(ptr);
+class GetMessagesQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_messages(std::move(messages->messages_), false, "get channel messages");
- break;
- }
- default:
- UNREACHABLE();
+ public:
+ explicit GetMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(vector<tl_object_ptr<telegram_api::InputMessage>> &&message_ids) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getMessages(std::move(message_ids))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getMessages>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
- promise_.set_value(Unit());
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "GetMessagesQuery");
+ LOG_IF(ERROR, info.is_channel_messages) << "Receive channel messages in GetMessagesQuery";
+ td_->messages_manager_->on_get_messages(std::move(info.messages), info.is_channel_messages, false,
+ std::move(promise_), "GetMessagesQuery");
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "MESSAGE_IDS_EMPTY") {
promise_.set_value(Unit());
return;
@@ -303,306 +483,455 @@ class GetMessagesQuery : public Td::ResultHandler {
}
};
-class GetChannelMessagesQuery : public Td::ResultHandler {
+class GetChannelMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
+ MessageId last_new_message_id_;
+ bool can_be_inaccessible_ = false;
public:
explicit GetChannelMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(ChannelId channel_id, tl_object_ptr<telegram_api::InputChannel> &&input_channel,
- vector<tl_object_ptr<telegram_api::InputMessage>> &&message_ids) {
+ vector<tl_object_ptr<telegram_api::InputMessage>> &&message_ids, MessageId last_new_message_id) {
channel_id_ = channel_id;
+ last_new_message_id_ = last_new_message_id;
+ can_be_inaccessible_ = message_ids.size() == 1 && message_ids[0]->get_id() != telegram_api::inputMessageID::ID;
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_getMessages(std::move(input_channel), std::move(message_ids)))));
+ telegram_api::channels_getMessages(std::move(input_channel), std::move(message_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_getMessages>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "GetChannelMessagesQuery");
+ LOG_IF(ERROR, !info.is_channel_messages) << "Receive ordinary messages in GetChannelMessagesQuery";
+ // messages with invalid big identifiers can be received as messageEmpty
+ // bots can receive messageEmpty because of their privacy mode
+ if (last_new_message_id_.is_valid() && !td_->auth_manager_->is_bot()) {
+ vector<MessageId> empty_message_ids;
+ for (auto &message : info.messages) {
+ if (message->get_id() == telegram_api::messageEmpty::ID) {
+ auto message_id = MessagesManager::get_message_id(message, false);
+ if (message_id.is_valid() && message_id <= last_new_message_id_) {
+ empty_message_ids.push_back(message_id);
+ }
+ }
+ }
+ td_->messages_manager_->on_get_empty_messages(DialogId(channel_id_), empty_message_ids);
}
+ const char *source = can_be_inaccessible_ ? "GetRepliedChannelMessageQuery" : "GetChannelMessagesQuery";
+ td_->messages_manager_->get_channel_difference_if_needed(
+ DialogId(channel_id_), std::move(info),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), source,
+ promise = std::move(promise_)](Result<MessagesManager::MessagesInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto info = result.move_as_ok();
+ send_closure(actor_id, &MessagesManager::on_get_messages, std::move(info.messages),
+ info.is_channel_messages, false, std::move(promise), source);
+ }
+ }));
+ }
- auto ptr = result_ptr.move_as_ok();
- LOG(DEBUG) << "Receive result for GetChannelMessagesQuery " << to_string(ptr);
- int32 constructor_id = ptr->get_id();
- switch (constructor_id) {
- case telegram_api::messages_messages::ID: {
- LOG(ERROR) << "Receive ordinary messages in GetChannelMessagesQuery";
- auto messages = move_tl_object_as<telegram_api::messages_messages>(ptr);
+ void on_error(Status status) final {
+ if (status.message() == "MESSAGE_IDS_EMPTY") {
+ promise_.set_value(Unit());
+ return;
+ }
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelMessagesQuery");
+ promise_.set_error(std::move(status));
+ }
+};
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_messages(std::move(messages->messages_), true, "get messages");
- break;
- }
- case telegram_api::messages_messagesSlice::ID: {
- LOG(ERROR) << "Receive ordinary messages in GetChannelMessagesQuery";
- auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(ptr);
+class GetScheduledMessagesQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_messages(std::move(messages->messages_), true, "get messages slice");
- break;
- }
- case telegram_api::messages_channelMessages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(ptr);
+ public:
+ explicit GetScheduledMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_messages(std::move(messages->messages_), true, "get channel messages");
- break;
- }
- default:
- UNREACHABLE();
+ void send(DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> &&input_peer, vector<int32> &&message_ids) {
+ dialog_id_ = dialog_id;
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getScheduledMessages(std::move(input_peer), std::move(message_ids))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getScheduledMessages>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
- promise_.set_value(Unit());
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "GetScheduledMessagesQuery");
+ LOG_IF(ERROR, info.is_channel_messages != (dialog_id_.get_type() == DialogType::Channel))
+ << "Receive wrong messages constructor in GetScheduledMessagesQuery";
+ td_->messages_manager_->on_get_messages(std::move(info.messages), info.is_channel_messages, true,
+ std::move(promise_), "GetScheduledMessagesQuery");
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == "MESSAGE_IDS_EMPTY") {
promise_.set_value(Unit());
return;
}
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelMessagesQuery");
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetScheduledMessagesQuery");
promise_.set_error(std::move(status));
}
};
-class GetChannelPinnedMessageQuery : public Td::ResultHandler {
- Promise<MessageId> promise_;
- ChannelId channel_id_;
+class UpdateDialogPinnedMessageQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
public:
- explicit GetChannelPinnedMessageQuery(Promise<MessageId> &&promise) : promise_(std::move(promise)) {
+ explicit UpdateDialogPinnedMessageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id) {
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
- if (input_channel == nullptr) {
- return promise_.set_error(Status::Error(6, "Can't access the chat"));
+ void send(DialogId dialog_id, MessageId message_id, bool is_unpin, bool disable_notification, bool only_for_self) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ LOG(INFO) << "Can't update pinned message in " << dialog_id;
+ return on_error(Status::Error(400, "Can't update pinned message"));
}
- channel_id_ = channel_id;
- vector<tl_object_ptr<telegram_api::InputMessage>> input_messages;
- input_messages.push_back(make_tl_object<telegram_api::inputMessagePinned>());
+ int32 flags = 0;
+ if (disable_notification) {
+ flags |= telegram_api::messages_updatePinnedMessage::SILENT_MASK;
+ }
+ if (is_unpin) {
+ flags |= telegram_api::messages_updatePinnedMessage::UNPIN_MASK;
+ }
+ if (only_for_self) {
+ flags |= telegram_api::messages_updatePinnedMessage::PM_ONESIDE_MASK;
+ }
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_getMessages(std::move(input_channel), std::move(input_messages)))));
+ telegram_api::messages_updatePinnedMessage(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ std::move(input_peer), message_id.get_server_message_id().get())));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::channels_getMessages>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_updatePinnedMessage>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(DEBUG) << "Receive result for GetChannelPinnedMessageQuery " << to_string(ptr);
- int32 constructor_id = ptr->get_id();
- switch (constructor_id) {
- case telegram_api::messages_messages::ID:
- case telegram_api::messages_messagesSlice::ID:
- LOG(ERROR) << "Receive ordinary messages in GetChannelPinnedMessageQuery " << to_string(ptr);
- return promise_.set_error(Status::Error(500, "Receive wrong request result"));
- case telegram_api::messages_channelMessages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(ptr);
+ LOG(INFO) << "Receive result for UpdateDialogPinnedMessageQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- if (messages->messages_.empty()) {
- return promise_.set_value(MessageId());
- }
- if (messages->messages_.size() >= 2) {
- LOG(ERROR) << to_string(ptr);
- return promise_.set_error(Status::Error(500, "More than 1 pinned message received"));
- }
- auto full_message_id = td->messages_manager_->on_get_message(std::move(messages->messages_[0]), false, true,
- false, false, "get channel pinned messages");
- if (full_message_id.get_dialog_id().is_valid() && full_message_id.get_dialog_id() != DialogId(channel_id_)) {
- LOG(ERROR) << full_message_id << " " << to_string(ptr);
- return promise_.set_error(Status::Error(500, "Receive pinned message in a wrong chat"));
- }
- return promise_.set_value(full_message_id.get_message_id());
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UpdateDialogPinnedMessageQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class UnpinAllMessagesQuery final : public Td::ResultHandler {
+ Promise<AffectedHistory> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit UnpinAllMessagesQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId top_thread_message_id) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Write);
+ if (input_peer == nullptr) {
+ LOG(INFO) << "Can't unpin all messages in " << dialog_id_;
+ return on_error(Status::Error(400, "Can't unpin all messages"));
+ }
+
+ int32 flags = 0;
+ if (top_thread_message_id.is_valid()) {
+ flags |= telegram_api::messages_unpinAllMessages::TOP_MSG_ID_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_unpinAllMessages(
+ flags, std::move(input_peer), top_thread_message_id.get_server_message_id().get())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_unpinAllMessages>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UnpinAllMessagesQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetMessageReadParticipantsQuery final : public Td::ResultHandler {
+ Promise<vector<UserId>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetMessageReadParticipantsQuery(Promise<vector<UserId>> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId message_id) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::messages_getMessageReadParticipants(
+ std::move(input_peer), message_id.get_server_message_id().get())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getMessageReadParticipants>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(UserId::get_user_ids(result_ptr.ok()));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessageReadParticipantsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class TranslateTextQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::text>> promise_;
+
+ public:
+ explicit TranslateTextQuery(Promise<td_api::object_ptr<td_api::text>> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(const string &text, const string &from_language_code, const string &to_language_code) {
+ int flags = telegram_api::messages_translateText::TEXT_MASK;
+ if (!from_language_code.empty()) {
+ flags |= telegram_api::messages_translateText::FROM_LANG_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_translateText(flags, nullptr, 0, text, from_language_code, to_language_code)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_translateText>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for TranslateTextQuery: " << to_string(ptr);
+ switch (ptr->get_id()) {
+ case telegram_api::messages_translateNoResult::ID:
+ return promise_.set_value(nullptr);
+ case telegram_api::messages_translateResultText::ID: {
+ auto text = telegram_api::move_object_as<telegram_api::messages_translateResultText>(ptr);
+ return promise_.set_value(td_api::make_object<td_api::text>(text->text_));
}
default:
UNREACHABLE();
}
}
- void on_error(uint64 id, Status status) override {
- if (status.message() == "MESSAGE_IDS_EMPTY") {
- promise_.set_value(MessageId());
- return;
- }
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelPinnedMessageQuery");
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class ExportChannelMessageLinkQuery : public Td::ResultHandler {
+class ExportChannelMessageLinkQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
MessageId message_id_;
- bool for_group_;
+ bool for_group_ = false;
+ bool ignore_result_ = false;
public:
explicit ExportChannelMessageLinkQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, MessageId message_id, bool for_group) {
+ void send(ChannelId channel_id, MessageId message_id, bool for_group, bool ignore_result) {
channel_id_ = channel_id;
message_id_ = message_id;
for_group_ = for_group;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
- CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_exportMessageLink(
- std::move(input_channel), message_id.get_server_message_id().get(), for_group))));
+ ignore_result_ = ignore_result;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ if (input_channel == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+ int32 flags = 0;
+ if (for_group) {
+ flags |= telegram_api::channels_exportMessageLink::GROUPED_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_exportMessageLink(flags, false /*ignored*/, false /*ignored*/, std::move(input_channel),
+ message_id.get_server_message_id().get())));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_exportMessageLink>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(DEBUG) << "Receive result for ExportChannelMessageLinkQuery " << to_string(ptr);
- td->messages_manager_->on_get_public_message_link({DialogId(channel_id_), message_id_}, for_group_,
- std::move(ptr->link_), std::move(ptr->html_));
+ LOG(DEBUG) << "Receive result for ExportChannelMessageLinkQuery: " << to_string(ptr);
+ if (!ignore_result_) {
+ td_->messages_manager_->on_get_public_message_link({DialogId(channel_id_), message_id_}, for_group_,
+ std::move(ptr->link_), std::move(ptr->html_));
+ }
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "ExportChannelMessageLinkQuery");
+ void on_error(Status status) final {
+ if (!ignore_result_) {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ExportChannelMessageLinkQuery");
+ }
promise_.set_error(std::move(status));
}
};
-class GetDialogListQuery : public NetActorOnce {
+class GetDialogListQuery final : public Td::ResultHandler {
+ FolderId folder_id_;
Promise<Unit> promise_;
public:
explicit GetDialogListQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(int32 offset_date, ServerMessageId offset_message_id, DialogId offset_dialog_id, int32 limit,
- uint64 sequence_id) {
- auto input_peer = td->messages_manager_->get_input_peer(offset_dialog_id, AccessRights::Read);
- if (input_peer == nullptr) {
- input_peer = make_tl_object<telegram_api::inputPeerEmpty>();
- }
+ void send(FolderId folder_id, int32 offset_date, ServerMessageId offset_message_id, DialogId offset_dialog_id,
+ int32 limit) {
+ folder_id_ = folder_id;
+ auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id);
+ CHECK(input_peer != nullptr);
- int32 flags = telegram_api::messages_getDialogs::EXCLUDE_PINNED_MASK;
- auto query = G()->net_query_creator().create(create_storer(telegram_api::messages_getDialogs(
- flags, false /*ignored*/, offset_date, offset_message_id.get(), std::move(input_peer), limit)));
- send_closure(td->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback,
- std::move(query), actor_shared(this), sequence_id);
+ int32 flags =
+ telegram_api::messages_getDialogs::EXCLUDE_PINNED_MASK | telegram_api::messages_getDialogs::FOLDER_ID_MASK;
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getDialogs(flags, false /*ignored*/, folder_id.get(), offset_date,
+ offset_message_id.get(), std::move(input_peer), limit, 0),
+ {{folder_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getDialogs>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetDialogListQuery " << to_string(ptr);
- int32 constructor_id = ptr->get_id();
- auto promise = PromiseCreator::lambda(
- [promise = std::move(promise_)](Result<> result) mutable { promise.set_result(std::move(result)); });
- if (constructor_id == telegram_api::messages_dialogs::ID) {
- auto dialogs = move_tl_object_as<telegram_api::messages_dialogs>(ptr);
- td->contacts_manager_->on_get_chats(std::move(dialogs->chats_));
- td->contacts_manager_->on_get_users(std::move(dialogs->users_));
- td->messages_manager_->on_get_dialogs(std::move(dialogs->dialogs_), narrow_cast<int32>(dialogs->dialogs_.size()),
- std::move(dialogs->messages_), std::move(promise));
- } else {
- CHECK(constructor_id == telegram_api::messages_dialogsSlice::ID);
- auto dialogs = move_tl_object_as<telegram_api::messages_dialogsSlice>(ptr);
- td->contacts_manager_->on_get_chats(std::move(dialogs->chats_));
- td->contacts_manager_->on_get_users(std::move(dialogs->users_));
- td->messages_manager_->on_get_dialogs(std::move(dialogs->dialogs_), max(dialogs->count_, 0),
- std::move(dialogs->messages_), std::move(promise));
+ LOG(INFO) << "Receive chats from chat list of " << folder_id_ << ": " << to_string(ptr);
+ switch (ptr->get_id()) {
+ case telegram_api::messages_dialogs::ID: {
+ auto dialogs = move_tl_object_as<telegram_api::messages_dialogs>(ptr);
+ td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery");
+ td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery");
+ td_->messages_manager_->on_get_dialogs(folder_id_, std::move(dialogs->dialogs_),
+ narrow_cast<int32>(dialogs->dialogs_.size()),
+ std::move(dialogs->messages_), std::move(promise_));
+ break;
+ }
+ case telegram_api::messages_dialogsSlice::ID: {
+ auto dialogs = move_tl_object_as<telegram_api::messages_dialogsSlice>(ptr);
+ td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "GetDialogListQuery slice");
+ td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "GetDialogListQuery slice");
+ td_->messages_manager_->on_get_dialogs(folder_id_, std::move(dialogs->dialogs_), max(dialogs->count_, 0),
+ std::move(dialogs->messages_), std::move(promise_));
+ break;
+ }
+ case telegram_api::messages_dialogsNotModified::ID:
+ LOG(ERROR) << "Receive " << to_string(ptr);
+ return on_error(Status::Error(500, "Receive wrong server response messages.dialogsNotModified"));
+ default:
+ UNREACHABLE();
}
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class SearchPublicDialogsQuery : public Td::ResultHandler {
+class SearchPublicDialogsQuery final : public Td::ResultHandler {
string query_;
public:
void send(const string &query) {
query_ = query;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::contacts_search(query, 3 /* ignored server-side */))));
+ send_query(G()->net_query_creator().create(telegram_api::contacts_search(query, 3 /* ignored server-side */)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::contacts_search>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto dialogs = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for SearchPublicDialogsQuery " << to_string(dialogs);
- td->contacts_manager_->on_get_chats(std::move(dialogs->chats_));
- td->contacts_manager_->on_get_users(std::move(dialogs->users_));
- td->messages_manager_->on_get_public_dialogs_search_result(query_, std::move(dialogs->my_results_),
- std::move(dialogs->results_));
+ LOG(INFO) << "Receive result for SearchPublicDialogsQuery: " << to_string(dialogs);
+ td_->contacts_manager_->on_get_users(std::move(dialogs->users_), "SearchPublicDialogsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(dialogs->chats_), "SearchPublicDialogsQuery");
+ td_->messages_manager_->on_get_public_dialogs_search_result(query_, std::move(dialogs->my_results_),
+ std::move(dialogs->results_));
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for SearchPublicDialogsQuery: " << status;
- td->messages_manager_->on_failed_public_dialogs_search(query_, std::move(status));
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ if (status.message() == "QUERY_TOO_SHORT") {
+ return td_->messages_manager_->on_get_public_dialogs_search_result(query_, {}, {});
+ }
+ LOG(ERROR) << "Receive error for SearchPublicDialogsQuery: " << status;
+ }
+ td_->messages_manager_->on_failed_public_dialogs_search(query_, std::move(status));
}
};
-class GetCommonDialogsQuery : public Td::ResultHandler {
+class GetCommonDialogsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
UserId user_id_;
+ int64 offset_chat_id_ = 0;
public:
explicit GetCommonDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(UserId user_id, int32 offset_chat_id, int32 limit) {
+ void send(UserId user_id, tl_object_ptr<telegram_api::InputUser> &&input_user, int64 offset_chat_id, int32 limit) {
user_id_ = user_id;
- LOG(INFO) << "Get common dialogs with " << user_id << " from " << offset_chat_id << " with limit " << limit;
-
- auto input_user = td->contacts_manager_->get_input_user(user_id);
- CHECK(input_user != nullptr);
+ offset_chat_id_ = offset_chat_id;
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_getCommonChats(std::move(input_user), offset_chat_id, limit))));
+ telegram_api::messages_getCommonChats(std::move(input_user), offset_chat_id, limit)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getCommonChats>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto chats_ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetCommonDialogsQuery " << to_string(chats_ptr);
- int32 constructor_id = chats_ptr->get_id();
- switch (constructor_id) {
+ LOG(INFO) << "Receive result for GetCommonDialogsQuery: " << to_string(chats_ptr);
+ switch (chats_ptr->get_id()) {
case telegram_api::messages_chats::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chats>(chats_ptr);
- td->messages_manager_->on_get_common_dialogs(user_id_, std::move(chats->chats_),
- narrow_cast<int32>(chats->chats_.size()));
+ td_->messages_manager_->on_get_common_dialogs(user_id_, offset_chat_id_, std::move(chats->chats_),
+ narrow_cast<int32>(chats->chats_.size()));
break;
}
case telegram_api::messages_chatsSlice::ID: {
auto chats = move_tl_object_as<telegram_api::messages_chatsSlice>(chats_ptr);
- td->messages_manager_->on_get_common_dialogs(user_id_, std::move(chats->chats_), chats->count_);
+ td_->messages_manager_->on_get_common_dialogs(user_id_, offset_chat_id_, std::move(chats->chats_),
+ chats->count_);
break;
}
default:
@@ -612,12 +941,68 @@ class GetCommonDialogsQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetBlockedDialogsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::messageSenders>> promise_;
+ int32 offset_;
+ int32 limit_;
+
+ public:
+ explicit GetBlockedDialogsQuery(Promise<td_api::object_ptr<td_api::messageSenders>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(int32 offset, int32 limit) {
+ offset_ = offset;
+ limit_ = limit;
+
+ send_query(G()->net_query_creator().create(telegram_api::contacts_getBlocked(offset, limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_getBlocked>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetBlockedDialogsQuery: " << to_string(ptr);
+
+ switch (ptr->get_id()) {
+ case telegram_api::contacts_blocked::ID: {
+ auto blocked_peers = move_tl_object_as<telegram_api::contacts_blocked>(ptr);
+
+ td_->contacts_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery");
+ td_->messages_manager_->on_get_blocked_dialogs(offset_, limit_,
+ narrow_cast<int32>(blocked_peers->blocked_.size()),
+ std::move(blocked_peers->blocked_), std::move(promise_));
+ break;
+ }
+ case telegram_api::contacts_blockedSlice::ID: {
+ auto blocked_peers = move_tl_object_as<telegram_api::contacts_blockedSlice>(ptr);
+
+ td_->contacts_manager_->on_get_users(std::move(blocked_peers->users_), "GetBlockedDialogsQuery slice");
+ td_->contacts_manager_->on_get_chats(std::move(blocked_peers->chats_), "GetBlockedDialogsQuery slice");
+ td_->messages_manager_->on_get_blocked_dialogs(offset_, limit_, blocked_peers->count_,
+ std::move(blocked_peers->blocked_), std::move(promise_));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class CreateChatQuery : public Td::ResultHandler {
+class CreateChatQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
int64 random_id_;
@@ -627,28 +1012,27 @@ class CreateChatQuery : public Td::ResultHandler {
void send(vector<tl_object_ptr<telegram_api::InputUser>> &&input_users, const string &title, int64 random_id) {
random_id_ = random_id;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_createChat(std::move(input_users), title))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_createChat(std::move(input_users), title)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_createChat>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for createChat " << to_string(ptr);
- td->messages_manager_->on_create_new_dialog_success(random_id_, std::move(ptr), DialogType::Chat,
- std::move(promise_));
+ LOG(INFO) << "Receive result for CreateChatQuery: " << to_string(ptr);
+ td_->messages_manager_->on_create_new_dialog_success(random_id_, std::move(ptr), DialogType::Chat,
+ std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->messages_manager_->on_create_new_dialog_fail(random_id_, std::move(status), std::move(promise_));
+ void on_error(Status status) final {
+ td_->messages_manager_->on_create_new_dialog_fail(random_id_, std::move(status), std::move(promise_));
}
};
-class CreateChannelQuery : public Td::ResultHandler {
+class CreateChannelQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
int64 random_id_;
@@ -656,61 +1040,282 @@ class CreateChannelQuery : public Td::ResultHandler {
explicit CreateChannelQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(const string &title, bool is_megagroup, const string &about, int64 random_id) {
+ void send(const string &title, bool is_megagroup, const string &about, const DialogLocation &location,
+ bool for_import, int64 random_id) {
int32 flags = 0;
if (is_megagroup) {
flags |= telegram_api::channels_createChannel::MEGAGROUP_MASK;
} else {
flags |= telegram_api::channels_createChannel::BROADCAST_MASK;
}
+ if (!location.empty()) {
+ flags |= telegram_api::channels_createChannel::GEO_POINT_MASK;
+ }
+ if (for_import) {
+ flags |= telegram_api::channels_createChannel::FOR_IMPORT_MASK;
+ }
random_id_ = random_id;
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::channels_createChannel(flags, false /*ignored*/, false /*ignored*/, title, about))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_createChannel(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, title,
+ about, location.get_input_geo_point(), location.get_address())));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_createChannel>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for createChannel " << to_string(ptr);
- td->messages_manager_->on_create_new_dialog_success(random_id_, std::move(ptr), DialogType::Channel,
- std::move(promise_));
+ LOG(INFO) << "Receive result for CreateChannelQuery: " << to_string(ptr);
+ td_->messages_manager_->on_create_new_dialog_success(random_id_, std::move(ptr), DialogType::Channel,
+ std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->messages_manager_->on_create_new_dialog_fail(random_id_, std::move(status), std::move(promise_));
+ void on_error(Status status) final {
+ td_->messages_manager_->on_create_new_dialog_fail(random_id_, std::move(status), std::move(promise_));
}
};
-class EditDialogPhotoQuery : public Td::ResultHandler {
+class CheckHistoryImportQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<td_api::MessageFileType>> promise_;
+
+ public:
+ explicit CheckHistoryImportQuery(Promise<tl_object_ptr<td_api::MessageFileType>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(const string &message_file_head) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_checkHistoryImport(message_file_head)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_checkHistoryImport>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for CheckHistoryImportQuery: " << to_string(ptr);
+ auto file_type = [&]() -> td_api::object_ptr<td_api::MessageFileType> {
+ if (ptr->pm_) {
+ return td_api::make_object<td_api::messageFileTypePrivate>(ptr->title_);
+ } else if (ptr->group_) {
+ return td_api::make_object<td_api::messageFileTypeGroup>(ptr->title_);
+ } else {
+ return td_api::make_object<td_api::messageFileTypeUnknown>();
+ }
+ }();
+ promise_.set_value(std::move(file_type));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class CheckHistoryImportPeerQuery final : public Td::ResultHandler {
+ Promise<string> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit CheckHistoryImportPeerQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::messages_checkHistoryImportPeer(std::move(input_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_checkHistoryImportPeer>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for CheckHistoryImportPeerQuery: " << to_string(ptr);
+ promise_.set_value(std::move(ptr->confirm_text_));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "CheckHistoryImportPeerQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class InitHistoryImportQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
FileId file_id_;
DialogId dialog_id_;
+ vector<FileId> attached_file_ids_;
+
+ public:
+ explicit InitHistoryImportQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, FileId file_id, tl_object_ptr<telegram_api::InputFile> &&input_file,
+ vector<FileId> attached_file_ids) {
+ CHECK(input_file != nullptr);
+ file_id_ = file_id;
+ dialog_id_ = dialog_id;
+ attached_file_ids_ = std::move(attached_file_ids);
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::messages_initHistoryImport(
+ std::move(input_peer), std::move(input_file), narrow_cast<int32>(attached_file_ids_.size()))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_initHistoryImport>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+
+ auto ptr = result_ptr.move_as_ok();
+ td_->messages_manager_->start_import_messages(dialog_id_, ptr->id_, std::move(attached_file_ids_),
+ std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (FileReferenceManager::is_file_reference_error(status)) {
+ LOG(ERROR) << "Receive file reference error " << status;
+ }
+ if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
+ // TODO support FILE_PART_*_MISSING
+ }
+
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "InitHistoryImportQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class UploadImportedMediaQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+ int64 import_id_;
+ FileId file_id_;
+
+ public:
+ explicit UploadImportedMediaQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, int64 import_id, const string &file_name, FileId file_id,
+ tl_object_ptr<telegram_api::InputMedia> &&input_media) {
+ CHECK(input_media != nullptr);
+ dialog_id_ = dialog_id;
+ import_id_ = import_id;
+ file_id_ = file_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_uploadImportedMedia(
+ std::move(input_peer), import_id, file_name, std::move(input_media))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_uploadImportedMedia>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+
+ // ignore response
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (FileReferenceManager::is_file_reference_error(status)) {
+ LOG(ERROR) << "Receive file reference error " << status;
+ }
+ if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
+ // TODO support FILE_PART_*_MISSING
+ }
+
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UploadImportedMediaQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class StartImportHistoryQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit StartImportHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, int64 import_id) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ CHECK(input_peer != nullptr);
+
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_startHistoryImport(std::move(input_peer), import_id)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_startHistoryImport>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ if (!result_ptr.ok()) {
+ return on_error(Status::Error(500, "Import history returned false"));
+ }
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartImportHistoryQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class EditDialogPhotoQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ FileId file_id_;
+ bool was_uploaded_ = false;
+ string file_reference_;
+ DialogId dialog_id_;
public:
explicit EditDialogPhotoQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(FileId file_id, DialogId dialog_id, tl_object_ptr<telegram_api::InputChatPhoto> &&input_chat_photo) {
+ void send(DialogId dialog_id, FileId file_id, tl_object_ptr<telegram_api::InputChatPhoto> &&input_chat_photo) {
CHECK(input_chat_photo != nullptr);
file_id_ = file_id;
+ was_uploaded_ = FileManager::extract_was_uploaded(input_chat_photo);
+ file_reference_ = FileManager::extract_file_reference(input_chat_photo);
dialog_id_ = dialog_id;
switch (dialog_id.get_type()) {
case DialogType::Chat:
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::messages_editChatPhoto(dialog_id.get_chat_id().get(), std::move(input_chat_photo)))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_editChatPhoto(dialog_id.get_chat_id().get(), std::move(input_chat_photo))));
break;
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_editPhoto(std::move(input_channel), std::move(input_chat_photo)))));
+ telegram_api::channels_editPhoto(std::move(input_channel), std::move(input_chat_photo))));
break;
}
default:
@@ -718,45 +1323,55 @@ class EditDialogPhotoQuery : public Td::ResultHandler {
}
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
static_assert(std::is_same<telegram_api::messages_editChatPhoto::ReturnType,
telegram_api::channels_editPhoto::ReturnType>::value,
"");
auto result_ptr = fetch_result<telegram_api::messages_editChatPhoto>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for editDialogPhoto: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
+ LOG(INFO) << "Receive result for EditDialogPhotoQuery: " << to_string(ptr);
- if (file_id_.is_valid()) {
- td->file_manager_->delete_partial_remote_location(file_id_);
+ if (file_id_.is_valid() && was_uploaded_) {
+ td_->file_manager_->delete_partial_remote_location(file_id_);
}
- promise_.set_value(Unit());
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- if (file_id_.is_valid()) {
- td->file_manager_->delete_partial_remote_location(file_id_);
+ void on_error(Status status) final {
+ if (file_id_.is_valid() && was_uploaded_) {
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+ }
+ if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ if (file_id_.is_valid() && !was_uploaded_) {
+ VLOG(file_references) << "Receive " << status << " for " << file_id_;
+ td_->file_manager_->delete_file_reference(file_id_, file_reference_);
+ td_->messages_manager_->upload_dialog_photo(dialog_id_, file_id_, false, 0.0, false, std::move(promise_), {-1});
+ return;
+ } else {
+ LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_
+ << ", was_uploaded = " << was_uploaded_;
+ }
}
- td->updates_manager_->get_difference("EditDialogPhotoQuery");
if (status.message() == "CHAT_NOT_MODIFIED") {
- if (!td->auth_manager_->is_bot()) {
+ if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditDialogPhotoQuery");
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditDialogPhotoQuery");
}
+ td_->updates_manager_->get_difference("EditDialogPhotoQuery");
promise_.set_error(std::move(status));
}
};
-class EditDialogTitleQuery : public Td::ResultHandler {
+class EditDialogTitleQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
@@ -769,14 +1384,13 @@ class EditDialogTitleQuery : public Td::ResultHandler {
switch (dialog_id.get_type()) {
case DialogType::Chat:
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_editChatTitle(dialog_id.get_chat_id().get(), title))));
+ telegram_api::messages_editChatTitle(dialog_id.get_chat_id().get(), title)));
break;
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_editTitle(std::move(input_channel), title))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_editTitle(std::move(input_channel), title)));
break;
}
default:
@@ -784,38 +1398,237 @@ class EditDialogTitleQuery : public Td::ResultHandler {
}
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
static_assert(std::is_same<telegram_api::messages_editChatTitle::ReturnType,
telegram_api::channels_editTitle::ReturnType>::value,
"");
auto result_ptr = fetch_result<telegram_api::messages_editChatTitle>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for editDialogTitle " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
+ LOG(INFO) << "Receive result for EditDialogTitleQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
- promise_.set_value(Unit());
+ void on_error(Status status) final {
+ td_->updates_manager_->get_difference("EditDialogTitleQuery");
+
+ if (status.message() == "CHAT_NOT_MODIFIED") {
+ if (!td_->auth_manager_->is_bot()) {
+ promise_.set_value(Unit());
+ return;
+ }
+ } else {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditDialogTitleQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetChatAvailableReactionsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit SetChatAvailableReactionsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const ChatReactions &available_reactions) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_setChatAvailableReactions(
+ std::move(input_peer), available_reactions.get_input_chat_reactions())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_setChatAvailableReactions>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SetChatAvailableReactionsQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "CHAT_NOT_MODIFIED") {
+ if (!td_->auth_manager_->is_bot()) {
+ promise_.set_value(Unit());
+ return;
+ }
+ } else {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetChatAvailableReactionsQuery");
+ td_->messages_manager_->reload_dialog_info_full(dialog_id_, "SetChatAvailableReactionsQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetChatThemeQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit SetChatThemeQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const string &theme_name) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::messages_setChatTheme(std::move(input_peer), theme_name)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_setChatTheme>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SetChatThemeQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "CHAT_NOT_MODIFIED") {
+ if (!td_->auth_manager_->is_bot()) {
+ promise_.set_value(Unit());
+ return;
+ }
+ } else {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetChatThemeQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetHistoryTtlQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit SetHistoryTtlQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, int32 period) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ CHECK(input_peer != nullptr);
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_setHistoryTTL(std::move(input_peer), period)));
}
- void on_error(uint64 id, Status status) override {
- td->updates_manager_->get_difference("EditDialogTitleQuery");
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_setHistoryTTL>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SetHistoryTtlQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+ void on_error(Status status) final {
if (status.message() == "CHAT_NOT_MODIFIED") {
- if (!td->auth_manager_->is_bot()) {
+ if (!td_->auth_manager_->is_bot()) {
promise_.set_value(Unit());
return;
}
} else {
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditDialogTitleQuery");
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetHistoryTtlQuery");
}
promise_.set_error(std::move(status));
}
};
-class SaveDraftMessageQuery : public Td::ResultHandler {
+class EditChatDefaultBannedRightsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit EditChatDefaultBannedRightsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, RestrictedRights permissions) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::messages_editChatDefaultBannedRights(
+ std::move(input_peer), permissions.get_chat_banned_rights())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_editChatDefaultBannedRights>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for EditChatDefaultBannedRightsQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "CHAT_NOT_MODIFIED") {
+ if (!td_->auth_manager_->is_bot()) {
+ promise_.set_value(Unit());
+ return;
+ }
+ } else {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditChatDefaultBannedRightsQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleNoForwardsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit ToggleNoForwardsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, bool has_protected_content) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_toggleNoForwards(std::move(input_peer), has_protected_content)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_toggleNoForwards>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for ToggleNoForwardsQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "CHAT_NOT_MODIFIED") {
+ promise_.set_value(Unit());
+ return;
+ } else {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleNoForwardsQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SaveDraftMessageQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
@@ -824,11 +1637,12 @@ class SaveDraftMessageQuery : public Td::ResultHandler {
}
void send(DialogId dialog_id, const unique_ptr<DraftMessage> &draft_message) {
- LOG(INFO) << "Save draft in " << dialog_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
LOG(INFO) << "Can't update draft message because have no write access to " << dialog_id;
- return;
+ return on_error(Status::Error(400, "Can't save draft message"));
}
int32 flags = 0;
@@ -841,43 +1655,75 @@ class SaveDraftMessageQuery : public Td::ResultHandler {
if (draft_message->input_message_text.disable_web_page_preview) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW;
}
- if (draft_message->input_message_text.text.entities.size()) {
+ if (!draft_message->input_message_text.text.entities.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES;
}
}
- dialog_id_ = dialog_id;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_saveDraft(
- flags, false /*ignored*/, reply_to_message_id.get(), std::move(input_peer),
- draft_message == nullptr ? "" : draft_message->input_message_text.text.text,
- draft_message == nullptr ? vector<tl_object_ptr<telegram_api::MessageEntity>>()
- : get_input_message_entities(td->contacts_manager_.get(),
- draft_message->input_message_text.text.entities)))));
+ vector<tl_object_ptr<telegram_api::MessageEntity>> input_message_entities;
+ if (draft_message != nullptr) {
+ input_message_entities = get_input_message_entities(
+ td_->contacts_manager_.get(), draft_message->input_message_text.text.entities, "SaveDraftMessageQuery");
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_saveDraft(flags, false /*ignored*/, reply_to_message_id.get(), 0, std::move(input_peer),
+ draft_message == nullptr ? "" : draft_message->input_message_text.text.text,
+ std::move(input_message_entities)),
+ {{dialog_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_saveDraft>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
if (!result) {
- on_error(id, Status::Error(400, "Save draft failed"));
+ on_error(Status::Error(400, "Save draft failed"));
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SaveDraftMessageQuery")) {
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SaveDraftMessageQuery")) {
LOG(ERROR) << "Receive error for SaveDraftMessageQuery: " << status;
}
promise_.set_error(std::move(status));
}
};
-class ToggleDialogPinQuery : public Td::ResultHandler {
+class ClearAllDraftsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ClearAllDraftsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::messages_clearAllDrafts()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_clearAllDrafts>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ LOG(INFO) << "Receive result for ClearAllDraftsQuery: " << result_ptr.ok();
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for ClearAllDraftsQuery: " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleDialogPinQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
bool is_pinned_;
@@ -889,9 +1735,10 @@ class ToggleDialogPinQuery : public Td::ResultHandler {
void send(DialogId dialog_id, bool is_pinned) {
dialog_id_ = dialog_id;
is_pinned_ = is_pinned;
- auto input_peer = td->messages_manager_->get_input_dialog_peer(dialog_id, AccessRights::Read);
+
+ auto input_peer = td_->messages_manager_->get_input_dialog_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
- return;
+ return on_error(Status::Error(400, "Can't access the chat"));
}
int32 flags = 0;
@@ -899,113 +1746,261 @@ class ToggleDialogPinQuery : public Td::ResultHandler {
flags |= telegram_api::messages_toggleDialogPin::PINNED_MASK;
}
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_toggleDialogPin(flags, false /*ignored*/, std::move(input_peer)))));
+ telegram_api::messages_toggleDialogPin(flags, false /*ignored*/, std::move(input_peer)), {{dialog_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_toggleDialogPin>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
if (!result) {
- on_error(id, Status::Error(400, "Toggle dialog pin failed"));
+ on_error(Status::Error(400, "Toggle dialog pin failed"));
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogPinQuery")) {
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogPinQuery")) {
LOG(ERROR) << "Receive error for ToggleDialogPinQuery: " << status;
}
- td->messages_manager_->on_update_dialog_pinned(dialog_id_, !is_pinned_);
+ td_->messages_manager_->on_update_pinned_dialogs(FolderId::main());
+ td_->messages_manager_->on_update_pinned_dialogs(FolderId::archive());
promise_.set_error(std::move(status));
}
};
-class ReorderPinnedDialogsQuery : public Td::ResultHandler {
+class ReorderPinnedDialogsQuery final : public Td::ResultHandler {
+ FolderId folder_id_;
Promise<Unit> promise_;
public:
explicit ReorderPinnedDialogsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(const vector<DialogId> &dialog_ids) {
+ void send(FolderId folder_id, const vector<DialogId> &dialog_ids) {
+ folder_id_ = folder_id;
int32 flags = telegram_api::messages_reorderPinnedDialogs::FORCE_MASK;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_reorderPinnedDialogs(
- flags, true /*ignored*/, td->messages_manager_->get_input_dialog_peers(dialog_ids, AccessRights::Read)))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_reorderPinnedDialogs(
+ flags, true /*ignored*/, folder_id.get(),
+ td_->messages_manager_->get_input_dialog_peers(dialog_ids, AccessRights::Read))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_reorderPinnedDialogs>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
if (!result) {
- return on_error(id, Status::Error(400, "Result is false"));
+ return on_error(Status::Error(400, "Result is false"));
+ }
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for ReorderPinnedDialogsQuery: " << status;
+ }
+ td_->messages_manager_->on_update_pinned_dialogs(folder_id_);
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleDialogUnreadMarkQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+ bool is_marked_as_unread_;
+
+ public:
+ explicit ToggleDialogUnreadMarkQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, bool is_marked_as_unread) {
+ dialog_id_ = dialog_id;
+ is_marked_as_unread_ = is_marked_as_unread;
+
+ auto input_peer = td_->messages_manager_->get_input_dialog_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = 0;
+ if (is_marked_as_unread) {
+ flags |= telegram_api::messages_markDialogUnread::UNREAD_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_markDialogUnread(flags, false /*ignored*/, std::move(input_peer)), {{dialog_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_markDialogUnread>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ if (!result) {
+ on_error(Status::Error(400, "Toggle dialog mark failed"));
}
- LOG(INFO) << "Pinned chats reordered";
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for ReorderPinnedDialogsQuery: " << status;
- td->messages_manager_->on_update_pinned_dialogs();
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogUnreadMarkQuery")) {
+ LOG(ERROR) << "Receive error for ToggleDialogUnreadMarkQuery: " << status;
+ }
+ if (!G()->close_flag()) {
+ td_->messages_manager_->on_update_dialog_is_marked_as_unread(dialog_id_, !is_marked_as_unread_);
+ }
promise_.set_error(std::move(status));
}
};
-class GetMessagesViewsQuery : public Td::ResultHandler {
+class ToggleDialogIsBlockedQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+ bool is_blocked_;
+
+ public:
+ explicit ToggleDialogIsBlockedQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, bool is_blocked) {
+ dialog_id_ = dialog_id;
+ is_blocked_ = is_blocked;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Know);
+ CHECK(input_peer != nullptr && input_peer->get_id() != telegram_api::inputPeerEmpty::ID);
+ vector<ChainId> chain_ids{{dialog_id, MessageContentType::Photo}, {dialog_id, MessageContentType::Text}};
+ auto query = is_blocked ? G()->net_query_creator().create(telegram_api::contacts_block(std::move(input_peer)),
+ std::move(chain_ids))
+ : G()->net_query_creator().create(telegram_api::contacts_unblock(std::move(input_peer)),
+ std::move(chain_ids));
+ send_query(std::move(query));
+ }
+
+ void on_result(BufferSlice packet) final {
+ static_assert(
+ std::is_same<telegram_api::contacts_block::ReturnType, telegram_api::contacts_unblock::ReturnType>::value, "");
+ auto result_ptr = fetch_result<telegram_api::contacts_block>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ LOG_IF(WARNING, !result) << "Block/Unblock " << dialog_id_ << " has failed";
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ToggleDialogIsBlockedQuery")) {
+ LOG(ERROR) << "Receive error for ToggleDialogIsBlockedQuery: " << status;
+ }
+ if (!G()->close_flag()) {
+ td_->messages_manager_->on_update_dialog_is_blocked(dialog_id_, !is_blocked_);
+ td_->messages_manager_->get_dialog_info_full(dialog_id_, Auto(), "ToggleDialogIsBlockedQuery");
+ td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "ToggleDialogIsBlockedQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetMessagesViewsQuery final : public Td::ResultHandler {
DialogId dialog_id_;
vector<MessageId> message_ids_;
public:
void send(DialogId dialog_id, vector<MessageId> &&message_ids, bool increment_view_counter) {
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ dialog_id_ = dialog_id;
+ message_ids_ = std::move(message_ids);
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
- LOG(ERROR) << "Can't update message views because doesn't have info about the " << dialog_id;
- return;
+ return on_error(Status::Error(400, "Can't access the chat"));
}
- LOG(INFO) << "View " << message_ids.size() << " messages in " << dialog_id
- << ", increment = " << increment_view_counter;
- dialog_id_ = dialog_id;
- message_ids_ = std::move(message_ids);
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getMessagesViews(
- std::move(input_peer), MessagesManager::get_server_message_ids(message_ids_), increment_view_counter))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_getMessagesViews(
+ std::move(input_peer), MessagesManager::get_server_message_ids(message_ids_), increment_view_counter)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getMessagesViews>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- vector<int32> views = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetMessagesViewsQuery " << format::as_array(views);
- if (message_ids_.size() != views.size()) {
- return on_error(id, Status::Error(500, "Wrong number of message views returned"));
+ auto result = result_ptr.move_as_ok();
+ auto interaction_infos = std::move(result->views_);
+ if (message_ids_.size() != interaction_infos.size()) {
+ return on_error(Status::Error(500, "Wrong number of message views returned"));
}
-
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetMessagesViewsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetMessagesViewsQuery");
for (size_t i = 0; i < message_ids_.size(); i++) {
- td->messages_manager_->on_update_message_views({dialog_id_, message_ids_[i]}, views[i]);
+ FullMessageId full_message_id{dialog_id_, message_ids_[i]};
+
+ auto *info = interaction_infos[i].get();
+ auto flags = info->flags_;
+ auto view_count = (flags & telegram_api::messageViews::VIEWS_MASK) != 0 ? info->views_ : 0;
+ auto forward_count = (flags & telegram_api::messageViews::FORWARDS_MASK) != 0 ? info->forwards_ : 0;
+ td_->messages_manager_->on_update_message_interaction_info(full_message_id, view_count, forward_count, true,
+ std::move(info->replies_));
}
+ td_->messages_manager_->finish_get_message_views(dialog_id_, message_ids_);
}
- void on_error(uint64 id, Status status) override {
- if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagesViewsQuery")) {
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagesViewsQuery")) {
LOG(ERROR) << "Receive error for GetMessagesViewsQuery: " << status;
}
- status.ignore();
+ td_->messages_manager_->finish_get_message_views(dialog_id_, message_ids_);
+ }
+};
+
+class GetExtendedMediaQuery final : public Td::ResultHandler {
+ DialogId dialog_id_;
+ vector<MessageId> message_ids_;
+
+ public:
+ void send(DialogId dialog_id, vector<MessageId> &&message_ids) {
+ dialog_id_ = dialog_id;
+ message_ids_ = std::move(message_ids);
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_getExtendedMedia(
+ std::move(input_peer), MessagesManager::get_server_message_ids(message_ids_))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getExtendedMedia>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetExtendedMediaQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
+ td_->messages_manager_->finish_get_message_extended_media(dialog_id_, message_ids_);
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetExtendedMediaQuery");
+ td_->messages_manager_->finish_get_message_extended_media(dialog_id_, message_ids_);
}
};
-class ReadMessagesContentsQuery : public Td::ResultHandler {
+class ReadMessagesContentsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -1013,36 +2008,38 @@ class ReadMessagesContentsQuery : public Td::ResultHandler {
}
void send(vector<MessageId> &&message_ids) {
- LOG(INFO) << "Receive ReadMessagesContentsQuery for messages " << format::as_array(message_ids);
-
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::messages_readMessageContents(MessagesManager::get_server_message_ids(message_ids)))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_readMessageContents(MessagesManager::get_server_message_ids(message_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_readMessageContents>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto affected_messages = result_ptr.move_as_ok();
CHECK(affected_messages->get_id() == telegram_api::messages_affectedMessages::ID);
+ LOG(INFO) << "Receive result for ReadMessagesContentsQuery: " << to_string(affected_messages);
if (affected_messages->pts_count_ > 0) {
- td->messages_manager_->add_pending_update(make_tl_object<dummyUpdate>(), affected_messages->pts_,
- affected_messages->pts_count_, false, "read messages content query");
+ td_->updates_manager_->add_pending_pts_update(make_tl_object<dummyUpdate>(), affected_messages->pts_,
+ affected_messages->pts_count_, Time::now(), Promise<Unit>(),
+ "read messages content query");
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for read message contents: " << status;
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for read message contents: " << status;
+ }
promise_.set_error(std::move(status));
}
};
-class ReadChannelMessagesContentsQuery : public Td::ResultHandler {
+class ReadChannelMessagesContentsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
@@ -1053,23 +2050,20 @@ class ReadChannelMessagesContentsQuery : public Td::ResultHandler {
void send(ChannelId channel_id, vector<MessageId> &&message_ids) {
channel_id_ = channel_id;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
if (input_channel == nullptr) {
LOG(ERROR) << "Have no input channel for " << channel_id;
- return;
+ return on_error(Status::Error(500, "Can't read channel message contents"));
}
- LOG(INFO) << "Receive ReadChannelMessagesContentsQuery for messages " << format::as_array(message_ids) << " in "
- << channel_id;
-
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_readMessageContents(
- std::move(input_channel), MessagesManager::get_server_message_ids(message_ids)))));
+ send_query(G()->net_query_creator().create(telegram_api::channels_readMessageContents(
+ std::move(input_channel), MessagesManager::get_server_message_ids(message_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_readMessageContents>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
@@ -1078,15 +2072,15 @@ class ReadChannelMessagesContentsQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- if (!td->contacts_manager_->on_get_channel_error(channel_id_, status, "ReadChannelMessagesContentsQuery")) {
+ void on_error(Status status) final {
+ if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReadChannelMessagesContentsQuery")) {
LOG(ERROR) << "Receive error for read messages contents in " << channel_id_ << ": " << status;
}
promise_.set_error(std::move(status));
}
};
-class GetDialogMessageByDateQuery : public Td::ResultHandler {
+class GetDialogMessageByDateQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
int32 date_;
@@ -1097,9 +2091,9 @@ class GetDialogMessageByDateQuery : public Td::ResultHandler {
}
void send(DialogId dialog_id, int32 date, int64 random_id) {
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
- return promise_.set_error(Status::Error(500, "Have no info about the chat"));
+ return promise_.set_error(Status::Error(400, "Can't access the chat"));
}
dialog_id_ = dialog_id;
@@ -1107,64 +2101,45 @@ class GetDialogMessageByDateQuery : public Td::ResultHandler {
random_id_ = random_id;
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_getHistory(std::move(input_peer), 0, date, -3, 5, 0, 0, 0))));
+ telegram_api::messages_getHistory(std::move(input_peer), 0, date, -3, 5, 0, 0, 0)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getHistory>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto ptr = result_ptr.move_as_ok();
- int32 constructor_id = ptr->get_id();
- switch (constructor_id) {
- case telegram_api::messages_messages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messages>(ptr);
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_dialog_message_by_date_success(dialog_id_, date_, random_id_,
- std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_messagesSlice::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(ptr);
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_dialog_message_by_date_success(dialog_id_, date_, random_id_,
- std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_channelMessages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(ptr);
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_dialog_message_by_date_success(dialog_id_, date_, random_id_,
- std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_messagesNotModified::ID:
- return on_error(id, Status::Error(500, "Server returned messagesNotModified"));
- default:
- UNREACHABLE();
- }
-
- promise_.set_value(Unit());
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "GetDialogMessageByDateQuery");
+ td_->messages_manager_->get_channel_difference_if_needed(
+ dialog_id_, std::move(info),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_, date = date_,
+ random_id = random_id_,
+ promise = std::move(promise_)](Result<MessagesManager::MessagesInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto info = result.move_as_ok();
+ send_closure(actor_id, &MessagesManager::on_get_dialog_message_by_date_success, dialog_id, date, random_id,
+ std::move(info.messages), std::move(promise));
+ }
+ }));
}
- void on_error(uint64 id, Status status) override {
- if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogMessageByDateQuery")) {
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogMessageByDateQuery")) {
LOG(ERROR) << "Receive error for GetDialogMessageByDateQuery in " << dialog_id_ << ": " << status;
}
promise_.set_error(std::move(status));
- td->messages_manager_->on_get_dialog_message_by_date_fail(random_id_);
+ td_->messages_manager_->on_get_dialog_message_by_date_fail(random_id_);
}
};
-class GetHistoryQuery : public Td::ResultHandler {
+class GetHistoryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
MessageId from_message_id_;
+ MessageId old_last_new_message_id_;
int32 offset_;
int32 limit_;
bool from_the_end_;
@@ -1173,96 +2148,73 @@ class GetHistoryQuery : public Td::ResultHandler {
explicit GetHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit) {
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ void send(DialogId dialog_id, MessageId from_message_id, MessageId old_last_new_message_id, int32 offset,
+ int32 limit) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
- LOG(ERROR) << "Can't get chat history in " << dialog_id << " because doesn't have info about the chat";
- return promise_.set_error(Status::Error(500, "Have no info about the chat"));
+ return promise_.set_error(Status::Error(400, "Can't access the chat"));
}
CHECK(offset < 0);
dialog_id_ = dialog_id;
from_message_id_ = from_message_id;
+ old_last_new_message_id_ = old_last_new_message_id;
offset_ = offset;
limit_ = limit;
from_the_end_ = false;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getHistory(
- std::move(input_peer), from_message_id.get_server_message_id().get(), 0, offset, limit, 0, 0, 0))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_getHistory(
+ std::move(input_peer), from_message_id.get_server_message_id().get(), 0, offset, limit, 0, 0, 0)));
}
- void send_get_from_the_end(DialogId dialog_id, int32 limit) {
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ void send_get_from_the_end(DialogId dialog_id, MessageId old_last_new_message_id, int32 limit) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
- LOG(ERROR) << "Can't get chat history because doesn't have info about the chat";
- return promise_.set_error(Status::Error(500, "Have no info about the chat"));
+ return promise_.set_error(Status::Error(400, "Can't access the chat"));
}
dialog_id_ = dialog_id;
+ old_last_new_message_id_ = old_last_new_message_id;
offset_ = 0;
limit_ = limit;
from_the_end_ = true;
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_getHistory(std::move(input_peer), 0, 0, 0, limit, 0, 0, 0))));
+ telegram_api::messages_getHistory(std::move(input_peer), 0, 0, 0, limit, 0, 0, 0)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getHistory>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
-
- auto ptr = result_ptr.move_as_ok();
- // LOG(INFO) << "Receive result for GetHistoryQuery " << to_string(ptr);
- int32 constructor_id = ptr->get_id();
- switch (constructor_id) {
- case telegram_api::messages_messages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messages>(ptr);
-
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_history(dialog_id_, from_message_id_, offset_, limit_, from_the_end_,
- std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_messagesSlice::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(ptr);
- // TODO use messages->count_
-
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_history(dialog_id_, from_message_id_, offset_, limit_, from_the_end_,
- std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_channelMessages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(ptr);
- // TODO use messages->count_, messages->pts_
-
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_history(dialog_id_, from_message_id_, offset_, limit_, from_the_end_,
- std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_messagesNotModified::ID:
- LOG(ERROR) << "Receive messagesNotModified";
- break;
- default:
- UNREACHABLE();
- }
-
- promise_.set_value(Unit());
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "GetHistoryQuery");
+ td_->messages_manager_->get_channel_difference_if_needed(
+ dialog_id_, std::move(info),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_,
+ from_message_id = from_message_id_, old_last_new_message_id = old_last_new_message_id_,
+ offset = offset_, limit = limit_, from_the_end = from_the_end_,
+ promise = std::move(promise_)](Result<MessagesManager::MessagesInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto info = result.move_as_ok();
+ // TODO use info.total_count, info.pts
+ send_closure(actor_id, &MessagesManager::on_get_history, dialog_id, from_message_id,
+ old_last_new_message_id, offset, limit, from_the_end, std::move(info.messages),
+ std::move(promise));
+ }
+ }));
}
- void on_error(uint64 id, Status status) override {
- if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetHistoryQuery")) {
- LOG(ERROR) << "Receive error for getHistoryQuery in " << dialog_id_ << ": " << status;
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetHistoryQuery")) {
+ LOG(ERROR) << "Receive error for GetHistoryQuery in " << dialog_id_ << ": " << status;
}
promise_.set_error(std::move(status));
}
};
-class ReadHistoryQuery : public Td::ResultHandler {
+class ReadHistoryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
@@ -1272,408 +2224,838 @@ class ReadHistoryQuery : public Td::ResultHandler {
void send(DialogId dialog_id, MessageId max_message_id) {
dialog_id_ = dialog_id;
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::messages_readHistory(td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read),
- max_message_id.get_server_message_id().get()))));
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_readHistory(std::move(input_peer), max_message_id.get_server_message_id().get()),
+ {{dialog_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_readHistory>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto affected_messages = result_ptr.move_as_ok();
CHECK(affected_messages->get_id() == telegram_api::messages_affectedMessages::ID);
- LOG(INFO) << "Receive result for readHistory: " << to_string(affected_messages);
+ LOG(INFO) << "Receive result for ReadHistoryQuery: " << to_string(affected_messages);
if (affected_messages->pts_count_ > 0) {
- td->messages_manager_->add_pending_update(make_tl_object<dummyUpdate>(), affected_messages->pts_,
- affected_messages->pts_count_, false, "read history query");
+ td_->updates_manager_->add_pending_pts_update(make_tl_object<dummyUpdate>(), affected_messages->pts_,
+ affected_messages->pts_count_, Time::now(), Promise<Unit>(),
+ "read history query");
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadHistoryQuery")) {
- LOG(ERROR) << "Receive error for readHistory: " << status;
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadHistoryQuery")) {
+ LOG(ERROR) << "Receive error for ReadHistoryQuery: " << status;
}
promise_.set_error(std::move(status));
}
};
-class ReadChannelHistoryQuery : public Td::ResultHandler {
+class ReadChannelHistoryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
- bool allow_error_;
public:
explicit ReadChannelHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, MessageId max_message_id, bool allow_error) {
+ void send(ChannelId channel_id, MessageId max_message_id) {
channel_id_ = channel_id;
- allow_error_ = allow_error;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::channels_readHistory(std::move(input_channel), max_message_id.get_server_message_id().get()))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_readHistory(std::move(input_channel), max_message_id.get_server_message_id().get()),
+ {{DialogId(channel_id)}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_readHistory>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- bool result = result_ptr.ok();
- LOG_IF(ERROR, !result && !allow_error_) << "Read history failed";
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ReadChannelHistoryQuery")) {
+ LOG(ERROR) << "Receive error for ReadChannelHistoryQuery: " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ReadDiscussionQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit ReadDiscussionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId top_thread_message_id, MessageId max_message_id) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_readDiscussion(std::move(input_peer),
+ top_thread_message_id.get_server_message_id().get(),
+ max_message_id.get_server_message_id().get()),
+ {{dialog_id}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_readDiscussion>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- if (!td->contacts_manager_->on_get_channel_error(channel_id_, status, "ReadChannelHistoryQuery")) {
- LOG(ERROR) << "Receive error for readChannelHistory: " << status;
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadDiscussionQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetSearchResultCalendarQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+ MessageId from_message_id_;
+ MessageSearchFilter filter_;
+ int64 random_id_;
+
+ public:
+ explicit GetSearchResultCalendarQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId from_message_id, MessageSearchFilter filter, int64 random_id) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ dialog_id_ = dialog_id;
+ from_message_id_ = from_message_id;
+ filter_ = filter;
+ random_id_ = random_id;
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_getSearchResultsCalendar(
+ std::move(input_peer), get_input_messages_filter(filter), from_message_id.get_server_message_id().get(), 0)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getSearchResultsCalendar>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetSearchResultCalendarQuery: " << to_string(result);
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "GetSearchResultCalendarQuery");
+ td_->contacts_manager_->on_get_chats(std::move(result->chats_), "GetSearchResultCalendarQuery");
+
+ // unused: inexact:flags.0?true min_date:int min_msg_id:int offset_id_offset:flags.1?int
+
+ MessagesManager::MessagesInfo info;
+ info.messages = std::move(result->messages_);
+ info.total_count = result->count_;
+ info.is_channel_messages = dialog_id_.get_type() == DialogType::Channel;
+
+ td_->messages_manager_->get_channel_difference_if_needed(
+ dialog_id_, std::move(info),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_,
+ from_message_id = from_message_id_, filter = filter_, random_id = random_id_,
+ periods = std::move(result->periods_),
+ promise = std::move(promise_)](Result<MessagesManager::MessagesInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto info = result.move_as_ok();
+ send_closure(actor_id, &MessagesManager::on_get_message_search_result_calendar, dialog_id, from_message_id,
+ filter, random_id, info.total_count, std::move(info.messages), std::move(periods),
+ std::move(promise));
+ }
+ }));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetSearchResultCalendarQuery");
+ td_->messages_manager_->on_failed_get_message_search_result_calendar(dialog_id_, random_id_);
promise_.set_error(std::move(status));
}
};
-class SearchMessagesQuery : public Td::ResultHandler {
+class GetMessagePositionQuery final : public Td::ResultHandler {
+ Promise<int32> promise_;
+ DialogId dialog_id_;
+ MessageId message_id_;
+ MessageId top_thread_message_id_;
+ MessageSearchFilter filter_;
+
+ public:
+ explicit GetMessagePositionQuery(Promise<int32> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId message_id, MessageSearchFilter filter, MessageId top_thread_message_id) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ dialog_id_ = dialog_id;
+ message_id_ = message_id;
+ top_thread_message_id_ = top_thread_message_id;
+ filter_ = filter;
+
+ if (filter == MessageSearchFilter::Empty && !top_thread_message_id.is_valid()) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getHistory(
+ std::move(input_peer), message_id.get_server_message_id().get(), 0, -1, 1, 0, 0, 0)));
+ } else {
+ int32 flags = 0;
+ if (top_thread_message_id.is_valid()) {
+ flags |= telegram_api::messages_search::TOP_MSG_ID_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_search(
+ flags, std::move(input_peer), string(), nullptr, top_thread_message_id.get_server_message_id().get(),
+ get_input_messages_filter(filter), 0, std::numeric_limits<int32>::max(),
+ message_id.get_server_message_id().get(), -1, 1, std::numeric_limits<int32>::max(), 0, 0)));
+ }
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_search>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto messages_ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetMessagePositionQuery: " << to_string(messages_ptr);
+ switch (messages_ptr->get_id()) {
+ case telegram_api::messages_messages::ID: {
+ auto messages = move_tl_object_as<telegram_api::messages_messages>(messages_ptr);
+ if (messages->messages_.size() != 1 ||
+ MessagesManager::get_message_id(messages->messages_[0], false) != message_id_) {
+ return promise_.set_error(Status::Error(400, "Message not found by the filter"));
+ }
+ return promise_.set_value(narrow_cast<int32>(messages->messages_.size()));
+ }
+ case telegram_api::messages_messagesSlice::ID: {
+ auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(messages_ptr);
+ if (messages->messages_.size() != 1 ||
+ MessagesManager::get_message_id(messages->messages_[0], false) != message_id_) {
+ return promise_.set_error(Status::Error(400, "Message not found by the filter"));
+ }
+ if (messages->offset_id_offset_ <= 0) {
+ LOG(ERROR) << "Failed to receive position for " << message_id_ << " in thread of " << top_thread_message_id_
+ << " in " << dialog_id_ << " by " << filter_;
+ return promise_.set_error(Status::Error(400, "Message position is unknown"));
+ }
+ return promise_.set_value(std::move(messages->offset_id_offset_));
+ }
+ case telegram_api::messages_channelMessages::ID: {
+ auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(messages_ptr);
+ if (messages->messages_.size() != 1 ||
+ MessagesManager::get_message_id(messages->messages_[0], false) != message_id_) {
+ return promise_.set_error(Status::Error(400, "Message not found by the filter"));
+ }
+ if (messages->offset_id_offset_ <= 0) {
+ LOG(ERROR) << "Failed to receive position for " << message_id_ << " in " << dialog_id_ << " by " << filter_;
+ return promise_.set_error(Status::Error(500, "Message position is unknown"));
+ }
+ return promise_.set_value(std::move(messages->offset_id_offset_));
+ }
+ case telegram_api::messages_messagesNotModified::ID:
+ LOG(ERROR) << "Server returned messagesNotModified in response to GetMessagePositionQuery";
+ return promise_.set_error(Status::Error(500, "Receive invalid response"));
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagePositionQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SearchMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
string query_;
- UserId sender_user_id_;
+ DialogId sender_dialog_id_;
MessageId from_message_id_;
int32 offset_;
int32 limit_;
- SearchMessagesFilter filter_;
+ MessageSearchFilter filter_;
+ MessageId top_thread_message_id_;
int64 random_id_;
+ bool handle_errors_ = true;
public:
explicit SearchMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(DialogId dialog_id, const string &query, UserId sender_user_id,
- telegram_api::object_ptr<telegram_api::InputUser> &&sender_input_user, MessageId from_message_id,
- int32 offset, int32 limit, SearchMessagesFilter filter, int64 random_id) {
- auto input_peer = dialog_id.is_valid() ? td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read)
+ void send(DialogId dialog_id, const string &query, DialogId sender_dialog_id, MessageId from_message_id, int32 offset,
+ int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, int64 random_id) {
+ auto input_peer = dialog_id.is_valid() ? td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read)
: make_tl_object<telegram_api::inputPeerEmpty>();
- if (input_peer == nullptr) {
- LOG(ERROR) << "Can't search messages because doesn't have info about the chat";
- return promise_.set_error(Status::Error(500, "Have no info about the chat"));
- }
+ CHECK(input_peer != nullptr);
dialog_id_ = dialog_id;
query_ = query;
- sender_user_id_ = sender_user_id;
+ sender_dialog_id_ = sender_dialog_id;
from_message_id_ = from_message_id;
offset_ = offset;
limit_ = limit;
filter_ = filter;
+ top_thread_message_id_ = top_thread_message_id;
random_id_ = random_id;
- if (filter == SearchMessagesFilter::UnreadMention) {
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::messages_getUnreadMentions(std::move(input_peer), from_message_id.get_server_message_id().get(),
- offset, limit, std::numeric_limits<int32>::max(), 0))));
+ auto top_msg_id = top_thread_message_id.get_server_message_id().get();
+ auto offset_id = from_message_id.get_server_message_id().get();
+ if (filter == MessageSearchFilter::UnreadMention) {
+ int32 flags = 0;
+ if (top_thread_message_id.is_valid()) {
+ flags |= telegram_api::messages_getUnreadMentions::TOP_MSG_ID_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_getUnreadMentions(
+ flags, std::move(input_peer), top_msg_id, offset_id, offset, limit, std::numeric_limits<int32>::max(), 0)));
+ } else if (filter == MessageSearchFilter::UnreadReaction) {
+ int32 flags = 0;
+ if (top_thread_message_id.is_valid()) {
+ flags |= telegram_api::messages_getUnreadReactions::TOP_MSG_ID_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_getUnreadReactions(
+ flags, std::move(input_peer), top_msg_id, offset_id, offset, limit, std::numeric_limits<int32>::max(), 0)));
+ } else if (top_thread_message_id.is_valid() && query.empty() && !sender_dialog_id.is_valid() &&
+ filter == MessageSearchFilter::Empty) {
+ handle_errors_ = dialog_id.get_type() != DialogType::Channel ||
+ !td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id());
+ send_query(G()->net_query_creator().create(telegram_api::messages_getReplies(
+ std::move(input_peer), top_msg_id, offset_id, 0, offset, limit, std::numeric_limits<int32>::max(), 0, 0)));
} else {
int32 flags = 0;
- if (sender_input_user != nullptr) {
+ tl_object_ptr<telegram_api::InputPeer> sender_input_peer;
+ if (sender_dialog_id.is_valid()) {
flags |= telegram_api::messages_search::FROM_ID_MASK;
+ sender_input_peer = td_->messages_manager_->get_input_peer(sender_dialog_id, AccessRights::Know);
+ CHECK(sender_input_peer != nullptr);
+ }
+ if (top_thread_message_id.is_valid()) {
+ flags |= telegram_api::messages_search::TOP_MSG_ID_MASK;
}
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_search(
- flags, std::move(input_peer), query, std::move(sender_input_user),
- MessagesManager::get_input_messages_filter(filter), 0, std::numeric_limits<int32>::max(),
- from_message_id.get_server_message_id().get(), offset, limit, std::numeric_limits<int32>::max(), 0, 0))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_search(flags, std::move(input_peer), query, std::move(sender_input_peer), top_msg_id,
+ get_input_messages_filter(filter), 0, std::numeric_limits<int32>::max(),
+ offset_id, offset, limit, std::numeric_limits<int32>::max(), 0, 0)));
}
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
static_assert(std::is_same<telegram_api::messages_getUnreadMentions::ReturnType,
telegram_api::messages_search::ReturnType>::value,
"");
+ static_assert(std::is_same<telegram_api::messages_getUnreadReactions::ReturnType,
+ telegram_api::messages_search::ReturnType>::value,
+ "");
+ static_assert(
+ std::is_same<telegram_api::messages_getReplies::ReturnType, telegram_api::messages_search::ReturnType>::value,
+ "");
auto result_ptr = fetch_result<telegram_api::messages_search>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "SearchMessagesQuery");
+ td_->messages_manager_->get_channel_difference_if_needed(
+ dialog_id_, std::move(info),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_,
+ query = std::move(query_), sender_dialog_id = sender_dialog_id_,
+ from_message_id = from_message_id_, offset = offset_, limit = limit_, filter = filter_,
+ top_thread_message_id = top_thread_message_id_, random_id = random_id_,
+ promise = std::move(promise_)](Result<MessagesManager::MessagesInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto info = result.move_as_ok();
+ send_closure(actor_id, &MessagesManager::on_get_dialog_messages_search_result, dialog_id, query,
+ sender_dialog_id, from_message_id, offset, limit, filter, top_thread_message_id, random_id,
+ info.total_count, std::move(info.messages), std::move(promise));
+ }
+ }));
+ }
+
+ void on_error(Status status) final {
+ if (handle_errors_) {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SearchMessagesQuery");
}
+ td_->messages_manager_->on_failed_dialog_messages_search(dialog_id_, random_id_);
+ promise_.set_error(std::move(status));
+ }
+};
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for SearchMessagesQuery: " << to_string(ptr);
- int32 constructor_id = ptr->get_id();
- switch (constructor_id) {
- case telegram_api::messages_messages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messages>(ptr);
+class GetSearchResultPositionsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::messagePositions>> promise_;
+ DialogId dialog_id_;
+ MessageSearchFilter filter_;
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_dialog_messages_search_result(
- dialog_id_, query_, sender_user_id_, from_message_id_, offset_, limit_, filter_, random_id_,
- narrow_cast<int32>(messages->messages_.size()), std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_messagesSlice::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(ptr);
+ public:
+ explicit GetSearchResultPositionsQuery(Promise<td_api::object_ptr<td_api::messagePositions>> &&promise)
+ : promise_(std::move(promise)) {
+ }
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_dialog_messages_search_result(
- dialog_id_, query_, sender_user_id_, from_message_id_, offset_, limit_, filter_, random_id_,
- messages->count_, std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_channelMessages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(ptr);
+ void send(DialogId dialog_id, MessageSearchFilter filter, MessageId from_message_id, int32 limit) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return promise_.set_error(Status::Error(400, "Can't access the chat"));
+ }
+ dialog_id_ = dialog_id;
+ filter_ = filter;
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_dialog_messages_search_result(
- dialog_id_, query_, sender_user_id_, from_message_id_, offset_, limit_, filter_, random_id_,
- messages->count_, std::move(messages->messages_));
- break;
- }
- default:
- UNREACHABLE();
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getSearchResultsPositions(std::move(input_peer), get_input_messages_filter(filter),
+ from_message_id.get_server_message_id().get(), limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getSearchResultsPositions>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
- promise_.set_value(Unit());
+ td_->messages_manager_->on_get_dialog_sparse_message_positions(dialog_id_, filter_, result_ptr.move_as_ok(),
+ std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetSearchResultPositionsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetSearchCountersQuery final : public Td::ResultHandler {
+ Promise<int32> promise_;
+ DialogId dialog_id_;
+ MessageSearchFilter filter_;
+
+ public:
+ explicit GetSearchCountersQuery(Promise<int32> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageSearchFilter filter) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return promise_.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ dialog_id_ = dialog_id;
+ filter_ = filter;
+
+ CHECK(filter != MessageSearchFilter::Empty);
+ CHECK(filter != MessageSearchFilter::UnreadMention);
+ CHECK(filter != MessageSearchFilter::FailedToSend);
+ CHECK(filter != MessageSearchFilter::UnreadReaction);
+ vector<telegram_api::object_ptr<telegram_api::MessagesFilter>> filters;
+ filters.push_back(get_input_messages_filter(filter));
+
+ int32 flags = 0;
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_getSearchCounters(flags, std::move(input_peer), 0, std::move(filters))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getSearchCounters>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ if (result.size() != 1 || result[0]->filter_->get_id() != get_input_messages_filter(filter_)->get_id()) {
+ LOG(ERROR) << "Receive unexpected response for get message count in " << dialog_id_ << " with filter " << filter_
+ << ": " << to_string(result);
+ return on_error(Status::Error(500, "Receive wrong response"));
+ }
+
+ td_->messages_manager_->on_get_dialog_message_count(dialog_id_, filter_, result[0]->count_, std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SearchMessagesQuery");
- td->messages_manager_->on_failed_dialog_messages_search(dialog_id_, random_id_);
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetSearchCountersQuery");
promise_.set_error(std::move(status));
}
};
-class SearchMessagesGlobalQuery : public Td::ResultHandler {
+class SearchMessagesGlobalQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
string query_;
int32 offset_date_;
DialogId offset_dialog_id_;
MessageId offset_message_id_;
int32 limit_;
+ MessageSearchFilter filter_;
+ int32 min_date_;
+ int32 max_date_;
int64 random_id_;
public:
explicit SearchMessagesGlobalQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(const string &query, int32 offset_date, DialogId offset_dialog_id, MessageId offset_message_id, int32 limit,
- int64 random_id) {
+ void send(FolderId folder_id, bool ignore_folder_id, const string &query, int32 offset_date,
+ DialogId offset_dialog_id, MessageId offset_message_id, int32 limit, MessageSearchFilter filter,
+ int32 min_date, int32 max_date, int64 random_id) {
query_ = query;
offset_date_ = offset_date;
offset_dialog_id_ = offset_dialog_id;
offset_message_id_ = offset_message_id;
limit_ = limit;
random_id_ = random_id;
+ filter_ = filter;
+ min_date_ = min_date;
+ max_date_ = max_date;
- auto input_peer = td->messages_manager_->get_input_peer(offset_dialog_id, AccessRights::Read);
- if (input_peer == nullptr) {
- input_peer = make_tl_object<telegram_api::inputPeerEmpty>();
- }
+ auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id);
+ CHECK(input_peer != nullptr);
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_searchGlobal(
- query, offset_date_, std::move(input_peer), offset_message_id.get_server_message_id().get(), limit))));
+ int32 flags = 0;
+ if (!ignore_folder_id) {
+ flags |= telegram_api::messages_searchGlobal::FOLDER_ID_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_searchGlobal(
+ flags, folder_id.get(), query, get_input_messages_filter(filter), min_date_, max_date_, offset_date_,
+ std::move(input_peer), offset_message_id.get_server_message_id().get(), limit)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_searchGlobal>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "SearchMessagesGlobalQuery");
+ td_->messages_manager_->get_channel_differences_if_needed(
+ std::move(info),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), query = std::move(query_),
+ offset_date = offset_date_, offset_dialog_id = offset_dialog_id_,
+ offset_message_id = offset_message_id_, limit = limit_, filter = std::move(filter_),
+ min_date = min_date_, max_date = max_date_, random_id = random_id_,
+ promise = std::move(promise_)](Result<MessagesManager::MessagesInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto info = result.move_as_ok();
+ send_closure(actor_id, &MessagesManager::on_get_messages_search_result, query, offset_date,
+ offset_dialog_id, offset_message_id, limit, filter, min_date, max_date, random_id,
+ info.total_count, std::move(info.messages), std::move(promise));
+ }
+ }));
+ }
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for SearchMessagesGlobalQuery: " << to_string(ptr);
- int32 constructor_id = ptr->get_id();
- switch (constructor_id) {
- case telegram_api::messages_messages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messages>(ptr);
+ void on_error(Status status) final {
+ td_->messages_manager_->on_failed_messages_search(random_id_);
+ promise_.set_error(std::move(status));
+ }
+};
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_messages_search_result(
- query_, offset_date_, offset_dialog_id_, offset_message_id_, limit_, random_id_,
- narrow_cast<int32>(messages->messages_.size()), std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_messagesSlice::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(ptr);
+class GetAllScheduledMessagesQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+ uint32 generation_;
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_messages_search_result(query_, offset_date_, offset_dialog_id_,
- offset_message_id_, limit_, random_id_, messages->count_,
- std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_channelMessages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(ptr);
+ public:
+ explicit GetAllScheduledMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_messages_search_result(query_, offset_date_, offset_dialog_id_,
- offset_message_id_, limit_, random_id_, messages->count_,
- std::move(messages->messages_));
- break;
- }
- default:
- UNREACHABLE();
+ void send(DialogId dialog_id, int64 hash, uint32 generation) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ dialog_id_ = dialog_id;
+ generation_ = generation;
+
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getScheduledHistory(std::move(input_peer), hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getScheduledHistory>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ if (result_ptr.ok()->get_id() == telegram_api::messages_messagesNotModified::ID) {
+ td_->messages_manager_->on_get_scheduled_server_messages(dialog_id_, generation_, Auto(), true);
+ } else {
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "GetAllScheduledMessagesQuery");
+ td_->messages_manager_->on_get_scheduled_server_messages(dialog_id_, generation_, std::move(info.messages),
+ false);
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- td->messages_manager_->on_failed_messages_search(random_id_);
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetAllScheduledMessagesQuery");
promise_.set_error(std::move(status));
}
};
-class GetRecentLocationsQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class SearchSentMediaQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::foundMessages>> promise_;
+
+ public:
+ explicit SearchSentMediaQuery(Promise<td_api::object_ptr<td_api::foundMessages>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(const string &query, int32 limit) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_searchSentMedia(
+ query, telegram_api::make_object<telegram_api::inputMessagesFilterDocument>(), limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_searchSentMedia>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "SearchSentMediaQuery");
+ td_->messages_manager_->get_channel_differences_if_needed(
+ std::move(info),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(),
+ promise = std::move(promise_)](Result<MessagesManager::MessagesInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto info = result.move_as_ok();
+ send_closure(actor_id, &MessagesManager::on_get_outgoing_document_messages, std::move(info.messages),
+ std::move(promise));
+ }
+ }));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetRecentLocationsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::messages>> promise_;
DialogId dialog_id_;
int32 limit_;
- int64 random_id_;
public:
- explicit GetRecentLocationsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit GetRecentLocationsQuery(Promise<td_api::object_ptr<td_api::messages>> &&promise)
+ : promise_(std::move(promise)) {
}
- void send(DialogId dialog_id, int32 limit, int64 random_id) {
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ void send(DialogId dialog_id, int32 limit) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
if (input_peer == nullptr) {
- LOG(ERROR) << "Can't get recent locations because doesn't have info about the chat";
- return promise_.set_error(Status::Error(500, "Have no info about the chat"));
+ return on_error(Status::Error(400, "Chat is not accessible"));
}
dialog_id_ = dialog_id;
limit_ = limit;
- random_id_ = random_id;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_getRecentLocations(std::move(input_peer), limit, 0))));
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getRecentLocations(std::move(input_peer), limit, 0)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getRecentLocations>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetRecentLocationsQuery: " << to_string(ptr);
- int32 constructor_id = ptr->get_id();
- switch (constructor_id) {
- case telegram_api::messages_messages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messages>(ptr);
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "GetRecentLocationsQuery");
+ td_->messages_manager_->get_channel_difference_if_needed(
+ dialog_id_, std::move(info),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(), dialog_id = dialog_id_, limit = limit_,
+ promise = std::move(promise_)](Result<MessagesManager::MessagesInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto info = result.move_as_ok();
+ send_closure(actor_id, &MessagesManager::on_get_recent_locations, dialog_id, limit, info.total_count,
+ std::move(info.messages), std::move(promise));
+ }
+ }));
+ }
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_recent_locations(dialog_id_, limit_, random_id_,
- narrow_cast<int32>(messages->messages_.size()),
- std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_messagesSlice::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(ptr);
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetRecentLocationsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_recent_locations(dialog_id_, limit_, random_id_, messages->count_,
- std::move(messages->messages_));
- break;
- }
- case telegram_api::messages_channelMessages::ID: {
- auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(ptr);
+class GetMessagePublicForwardsQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::foundMessages>> promise_;
+ DialogId dialog_id_;
+ int32 limit_;
- td->contacts_manager_->on_get_chats(std::move(messages->chats_));
- td->contacts_manager_->on_get_users(std::move(messages->users_));
- td->messages_manager_->on_get_recent_locations(dialog_id_, limit_, random_id_, messages->count_,
- std::move(messages->messages_));
- break;
- }
- default:
- UNREACHABLE();
+ public:
+ explicit GetMessagePublicForwardsQuery(Promise<td_api::object_ptr<td_api::foundMessages>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(DcId dc_id, FullMessageId full_message_id, int32 offset_date, DialogId offset_dialog_id,
+ ServerMessageId offset_message_id, int32 limit) {
+ dialog_id_ = full_message_id.get_dialog_id();
+ limit_ = limit;
+
+ auto input_peer = MessagesManager::get_input_peer_force(offset_dialog_id);
+ CHECK(input_peer != nullptr);
+
+ send_query(
+ G()->net_query_creator().create(telegram_api::stats_getMessagePublicForwards(
+ td_->contacts_manager_->get_input_channel(dialog_id_.get_channel_id()),
+ full_message_id.get_message_id().get_server_message_id().get(), offset_date,
+ std::move(input_peer), offset_message_id.get(), limit),
+ {}, dc_id));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::stats_getMessagePublicForwards>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
- promise_.set_value(Unit());
+ auto info = td_->messages_manager_->get_messages_info(result_ptr.move_as_ok(), "GetMessagePublicForwardsQuery");
+ td_->messages_manager_->get_channel_differences_if_needed(
+ std::move(info),
+ PromiseCreator::lambda([actor_id = td_->messages_manager_actor_.get(),
+ promise = std::move(promise_)](Result<MessagesManager::MessagesInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto info = result.move_as_ok();
+ send_closure(actor_id, &MessagesManager::on_get_message_public_forwards, info.total_count,
+ std::move(info.messages), std::move(promise));
+ }
+ }));
}
- void on_error(uint64 id, Status status) override {
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetRecentLocationsQuery");
- td->messages_manager_->on_get_recent_locations_failed(random_id_);
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetMessagePublicForwardsQuery");
promise_.set_error(std::move(status));
}
};
-class DeleteHistoryQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class HidePromoDataQuery final : public Td::ResultHandler {
DialogId dialog_id_;
- MessageId max_message_id_;
- bool remove_from_dialog_list_;
- void send_request() {
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
- if (input_peer == nullptr) {
- return promise_.set_error(Status::Error(3, "Chat is not accessible"));
- }
+ public:
+ void send(DialogId dialog_id) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::help_hidePromoData(std::move(input_peer))));
+ }
- int32 flags = 0;
- if (!remove_from_dialog_list_) {
- flags |= telegram_api::messages_deleteHistory::JUST_CLEAR_MASK;
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_hidePromoData>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
- LOG(INFO) << "Delete " << dialog_id_ << " history up to " << max_message_id_ << " with flags " << flags;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_deleteHistory(
- flags, false /*ignored*/, std::move(input_peer), max_message_id_.get_server_message_id().get()))));
+ // we are not interested in the result
}
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "HidePromoDataQuery")) {
+ LOG(ERROR) << "Receive error for sponsored chat hiding: " << status;
+ }
+ }
+};
+
+class DeleteHistoryQuery final : public Td::ResultHandler {
+ Promise<AffectedHistory> promise_;
+ DialogId dialog_id_;
+
public:
- explicit DeleteHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit DeleteHistoryQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
- void send(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list) {
+ void send(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list, bool revoke) {
dialog_id_ = dialog_id;
- max_message_id_ = max_message_id;
- remove_from_dialog_list_ = remove_from_dialog_list;
- send_request();
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return promise_.set_error(Status::Error(400, "Chat is not accessible"));
+ }
+
+ int32 flags = 0;
+ if (!remove_from_dialog_list) {
+ flags |= telegram_api::messages_deleteHistory::JUST_CLEAR_MASK;
+ }
+ if (revoke) {
+ flags |= telegram_api::messages_deleteHistory::REVOKE_MASK;
+ }
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_deleteHistory(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer),
+ max_message_id.get_server_message_id().get(), 0, 0)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_deleteHistory>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto affected_history = result_ptr.move_as_ok();
- CHECK(affected_history->get_id() == telegram_api::messages_affectedHistory::ID);
+ promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
+ }
- if (affected_history->pts_count_ > 0) {
- td->messages_manager_->add_pending_update(make_tl_object<dummyUpdate>(), affected_history->pts_,
- affected_history->pts_count_, false, "delete history query");
- }
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteHistoryQuery");
+ promise_.set_error(std::move(status));
+ }
+};
- if (affected_history->offset_ > 0) {
- send_request();
- return;
+class DeleteTopicHistoryQuery final : public Td::ResultHandler {
+ Promise<AffectedHistory> promise_;
+ ChannelId channel_id_;
+
+ public:
+ explicit DeleteTopicHistoryQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId top_thread_message_id) {
+ CHECK(dialog_id.get_type() == DialogType::Channel);
+ channel_id_ = dialog_id.get_channel_id();
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id_);
+ CHECK(input_channel != nullptr);
+
+ send_query(G()->net_query_creator().create(telegram_api::channels_deleteTopicHistory(
+ std::move(input_channel), top_thread_message_id.get_server_message_id().get())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_deleteTopicHistory>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
- promise_.set_value(Unit());
+ promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
- void on_error(uint64 id, Status status) override {
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteHistoryQuery");
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteTopicHistoryQuery");
promise_.set_error(std::move(status));
}
};
-class DeleteChannelHistoryQuery : public Td::ResultHandler {
+class DeleteChannelHistoryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
ChannelId channel_id_;
MessageId max_message_id_;
@@ -1683,287 +3065,410 @@ class DeleteChannelHistoryQuery : public Td::ResultHandler {
explicit DeleteChannelHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, MessageId max_message_id, bool allow_error) {
+ void send(ChannelId channel_id, MessageId max_message_id, bool allow_error, bool revoke) {
channel_id_ = channel_id;
max_message_id_ = max_message_id;
allow_error_ = allow_error;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::channels_deleteHistory(std::move(input_channel), max_message_id.get_server_message_id().get()))));
+ int32 flags = 0;
+ if (revoke) {
+ flags |= telegram_api::channels_deleteHistory::FOR_EVERYONE_MASK;
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::channels_deleteHistory(
+ flags, false /*ignored*/, std::move(input_channel), max_message_id.get_server_message_id().get())));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_deleteHistory>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- bool result = result_ptr.ok();
- LOG_IF(ERROR, !allow_error_ && !result)
- << "Delete history in " << channel_id_ << " up to " << max_message_id_ << " failed";
-
- promise_.set_value(Unit());
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for DeleteChannelHistoryQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- if (!td->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelHistoryQuery")) {
- LOG(ERROR) << "Receive error for deleteChannelHistory: " << status;
+ void on_error(Status status) final {
+ if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelHistoryQuery")) {
+ LOG(ERROR) << "Receive error for DeleteChannelHistoryQuery: " << status;
}
promise_.set_error(std::move(status));
}
};
-class DeleteUserHistoryQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
- ChannelId channel_id_;
- UserId user_id_;
+class DeleteMessagesByDateQuery final : public Td::ResultHandler {
+ Promise<AffectedHistory> promise_;
+ DialogId dialog_id_;
- void send_request() {
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id_);
- if (input_channel == nullptr) {
- return promise_.set_error(Status::Error(3, "Chat is not accessible"));
+ public:
+ explicit DeleteMessagesByDateQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return promise_.set_error(Status::Error(400, "Chat is not accessible"));
}
- auto input_user = td->contacts_manager_->get_input_user(user_id_);
- if (input_user == nullptr) {
- return promise_.set_error(Status::Error(3, "Usat is not accessible"));
+
+ int32 flags = telegram_api::messages_deleteHistory::JUST_CLEAR_MASK |
+ telegram_api::messages_deleteHistory::MIN_DATE_MASK |
+ telegram_api::messages_deleteHistory::MAX_DATE_MASK;
+ if (revoke) {
+ flags |= telegram_api::messages_deleteHistory::REVOKE_MASK;
}
- LOG(INFO) << "Delete all messages from " << user_id_ << " in " << channel_id_;
+ send_query(G()->net_query_creator().create(telegram_api::messages_deleteHistory(
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), 0, min_date, max_date)));
+ }
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_deleteUserHistory(std::move(input_channel), std::move(input_user)))));
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_deleteHistory>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
- public:
- explicit DeleteUserHistoryQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteMessagesByDateQuery");
+ promise_.set_error(std::move(status));
}
+};
- void send(ChannelId channel_id, UserId user_id) {
- channel_id_ = channel_id;
- user_id_ = user_id;
+class DeletePhoneCallHistoryQuery final : public Td::ResultHandler {
+ Promise<AffectedHistory> promise_;
- send_request();
+ public:
+ explicit DeletePhoneCallHistoryQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::channels_deleteUserHistory>(packet);
+ void send(bool revoke) {
+ int32 flags = 0;
+ if (revoke) {
+ flags |= telegram_api::messages_deletePhoneCallHistory::REVOKE_MASK;
+ }
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_deletePhoneCallHistory(flags, false /*ignored*/)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_deletePhoneCallHistory>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto affected_history = result_ptr.move_as_ok();
- CHECK(affected_history->get_id() == telegram_api::messages_affectedHistory::ID);
+ auto affected_messages = result_ptr.move_as_ok();
+ if (!affected_messages->messages_.empty()) {
+ td_->messages_manager_->process_pts_update(
+ make_tl_object<telegram_api::updateDeleteMessages>(std::move(affected_messages->messages_), 0, 0));
+ }
+ promise_.set_value(AffectedHistory(std::move(affected_messages)));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
- if (affected_history->pts_count_ > 0) {
- td->messages_manager_->add_pending_channel_update(DialogId(channel_id_), make_tl_object<dummyUpdate>(),
- affected_history->pts_, affected_history->pts_count_,
- "delete user history query");
+class BlockFromRepliesQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit BlockFromRepliesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(MessageId message_id, bool need_delete_message, bool need_delete_all_messages, bool report_spam) {
+ int32 flags = 0;
+ if (need_delete_message) {
+ flags |= telegram_api::contacts_blockFromReplies::DELETE_MESSAGE_MASK;
+ }
+ if (need_delete_all_messages) {
+ flags |= telegram_api::contacts_blockFromReplies::DELETE_HISTORY_MASK;
+ }
+ if (report_spam) {
+ flags |= telegram_api::contacts_blockFromReplies::REPORT_SPAM_MASK;
}
+ send_query(G()->net_query_creator().create(telegram_api::contacts_blockFromReplies(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, message_id.get_server_message_id().get())));
+ }
- if (affected_history->offset_ > 0) {
- send_request();
- return;
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_blockFromReplies>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
- promise_.set_value(Unit());
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for BlockFromRepliesQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteUserHistoryQuery");
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class ReadAllMentionsQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
- DialogId dialog_id_;
+class DeleteParticipantHistoryQuery final : public Td::ResultHandler {
+ Promise<AffectedHistory> promise_;
+ ChannelId channel_id_;
+ DialogId sender_dialog_id_;
+
+ public:
+ explicit DeleteParticipantHistoryQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id, DialogId sender_dialog_id) {
+ channel_id_ = channel_id;
+ sender_dialog_id_ = sender_dialog_id;
- void send_request() {
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ if (input_channel == nullptr) {
+ return promise_.set_error(Status::Error(400, "Chat is not accessible"));
+ }
+ auto input_peer = td_->messages_manager_->get_input_peer(sender_dialog_id, AccessRights::Know);
if (input_peer == nullptr) {
- return promise_.set_error(Status::Error(3, "Chat is not accessible"));
+ return promise_.set_error(Status::Error(400, "Message sender is not accessible"));
}
- LOG(INFO) << "Read all mentions in " << dialog_id_;
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_deleteParticipantHistory(std::move(input_channel), std::move(input_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_deleteParticipantHistory>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::messages_readMentions(std::move(input_peer)))));
+ promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
+ void on_error(Status status) final {
+ if (sender_dialog_id_.get_type() != DialogType::Channel) {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteParticipantHistoryQuery");
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ReadMentionsQuery final : public Td::ResultHandler {
+ Promise<AffectedHistory> promise_;
+ DialogId dialog_id_;
+
public:
- explicit ReadAllMentionsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ReadMentionsQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
}
- void send(DialogId dialog_id) {
+ void send(DialogId dialog_id, MessageId top_thread_message_id) {
dialog_id_ = dialog_id;
- send_request();
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return promise_.set_error(Status::Error(400, "Chat is not accessible"));
+ }
+
+ int32 flags = 0;
+ if (top_thread_message_id.is_valid()) {
+ flags |= telegram_api::messages_readMentions::TOP_MSG_ID_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_readMentions(flags, std::move(input_peer),
+ top_thread_message_id.get_server_message_id().get()),
+ {{dialog_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_readMentions>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto affected_history = result_ptr.move_as_ok();
- CHECK(affected_history->get_id() == telegram_api::messages_affectedHistory::ID);
+ promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
+ }
- if (affected_history->pts_count_ > 0) {
- if (dialog_id_.get_type() == DialogType::Channel) {
- LOG(ERROR) << "Receive pts_count " << affected_history->pts_count_ << " in result of ReadAllMentionsQuery in "
- << dialog_id_;
- td->updates_manager_->get_difference("Wrong messages_readMentions result");
- } else {
- td->messages_manager_->add_pending_update(make_tl_object<dummyUpdate>(), affected_history->pts_,
- affected_history->pts_count_, false, "read all mentions query");
- }
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadMentionsQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ReadReactionsQuery final : public Td::ResultHandler {
+ Promise<AffectedHistory> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit ReadReactionsQuery(Promise<AffectedHistory> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, MessageId top_thread_message_id) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return promise_.set_error(Status::Error(400, "Chat is not accessible"));
}
- if (affected_history->offset_ > 0) {
- send_request();
- return;
+ int32 flags = 0;
+ if (top_thread_message_id.is_valid()) {
+ flags |= telegram_api::messages_readReactions::TOP_MSG_ID_MASK;
}
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_readReactions(flags, std::move(input_peer),
+ top_thread_message_id.get_server_message_id().get()),
+ {{dialog_id}}));
+ }
- promise_.set_value(Unit());
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_readReactions>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(AffectedHistory(result_ptr.move_as_ok()));
}
- void on_error(uint64 id, Status status) override {
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadAllMentionsQuery");
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReadReactionsQuery");
promise_.set_error(std::move(status));
}
};
-class SendSecretMessageActor : public NetActor {
- int64 random_id_;
+class SaveDefaultSendAsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
public:
- void send(DialogId dialog_id, int64 reply_to_random_id, int32 ttl, const string &message, SecretInputMedia media,
- vector<tl_object_ptr<secret_api::MessageEntity>> &&entities, UserId via_bot_user_id, int64 media_album_id,
- int64 random_id) {
- if (false && !media.empty()) {
- td->messages_manager_->on_send_secret_message_error(random_id, Status::Error(400, "FILE_PART_1_MISSING"), Auto());
- return;
- }
+ explicit SaveDefaultSendAsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
- CHECK(dialog_id.get_type() == DialogType::SecretChat);
- random_id_ = random_id;
+ void send(DialogId dialog_id, DialogId send_as_dialog_id) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
- int32 flags = 0;
- if (reply_to_random_id != 0) {
- flags |= secret_api::decryptedMessage::REPLY_TO_RANDOM_ID_MASK;
- }
- if (via_bot_user_id.is_valid()) {
- flags |= secret_api::decryptedMessage::VIA_BOT_NAME_MASK;
- }
- if (!media.empty()) {
- flags |= secret_api::decryptedMessage::MEDIA_MASK;
- }
- if (!entities.empty()) {
- flags |= secret_api::decryptedMessage::ENTITIES_MASK;
- }
- if (media_album_id != 0) {
- CHECK(media_album_id < 0);
- flags |= secret_api::decryptedMessage::GROUPED_ID_MASK;
+ auto send_as_input_peer = td_->messages_manager_->get_input_peer(send_as_dialog_id, AccessRights::Read);
+ CHECK(send_as_input_peer != nullptr);
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_saveDefaultSendAs(std::move(input_peer), std::move(send_as_input_peer)),
+ {{dialog_id, MessageContentType::Photo}, {dialog_id, MessageContentType::Text}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_saveDefaultSendAs>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
- send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_message, dialog_id.get_secret_chat_id(),
- make_tl_object<secret_api::decryptedMessage>(
- flags, random_id, ttl, message, std::move(media.decrypted_media_), std::move(entities),
- td->contacts_manager_->get_user_username(via_bot_user_id), reply_to_random_id, -media_album_id),
- std::move(media.input_file_),
- PromiseCreator::event(self_closure(this, &SendSecretMessageActor::done)));
+ auto success = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SaveDefaultSendAsQuery: " << success;
+
+ promise_.set_value(Unit());
}
- void done() {
- stop();
+ void on_error(Status status) final {
+ // td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SaveDefaultSendAsQuery");
+ promise_.set_error(std::move(status));
}
};
-class SendMessageActor : public NetActorOnce {
+class SendMessageQuery final : public Td::ResultHandler {
int64 random_id_;
DialogId dialog_id_;
public:
- void send(int32 flags, DialogId dialog_id, MessageId reply_to_message_id,
+ void send(int32 flags, DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> as_input_peer,
+ MessageId reply_to_message_id, MessageId top_thread_message_id, int32 schedule_date,
tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup,
- vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities, const string &message, int64 random_id,
- NetQueryRef *send_query_ref, uint64 sequence_dispatcher_id) {
+ vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities, const string &text, bool is_copy,
+ int64 random_id, NetQueryRef *send_query_ref) {
random_id_ = random_id;
dialog_id_ = dialog_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
- on_error(0, Status::Error(400, "Have no write access to the chat"));
- return;
+ return on_error(Status::Error(400, "Have no write access to the chat"));
}
if (!entities.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES;
}
+ if (as_input_peer != nullptr) {
+ flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
+ }
- auto query = G()->net_query_creator().create(create_storer(telegram_api::messages_sendMessage(
- flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer),
- reply_to_message_id.get_server_message_id().get(), message, random_id, std::move(reply_markup),
- std::move(entities))));
- if (G()->shared_config().get_option_boolean("use_quick_ack")) {
- query->quick_ack_promise_ = PromiseCreator::lambda(
- [random_id](Unit) {
- send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
- },
- PromiseCreator::Ignore());
+ CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server());
+ CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server());
+ auto query = G()->net_query_creator().create(
+ telegram_api::messages_sendMessage(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, std::move(input_peer), reply_to_message_id.get_server_message_id().get(),
+ top_thread_message_id.get_server_message_id().get(), text, random_id, std::move(reply_markup),
+ std::move(entities), schedule_date, std::move(as_input_peer)),
+ {{dialog_id, MessageContentType::Text},
+ {dialog_id, is_copy ? MessageContentType::Photo : MessageContentType::Text}});
+ if (td_->option_manager_->get_option_boolean("use_quick_ack")) {
+ query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result<Unit> result) {
+ if (result.is_ok()) {
+ send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
+ }
+ });
}
*send_query_ref = query.get_weak();
- query->debug("send to MessagesManager::MultiSequenceDispatcher");
- send_closure(td->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback,
- std::move(query), actor_shared(this), sequence_dispatcher_id);
+ send_query(std::move(query));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendMessage>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for sendMessage for " << random_id_ << ": " << to_string(ptr);
+ LOG(INFO) << "Receive result for SendMessageQuery for " << random_id_ << ": " << to_string(ptr);
auto constructor_id = ptr->get_id();
if (constructor_id != telegram_api::updateShortSentMessage::ID) {
- td->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendMessage");
- return td->updates_manager_->on_get_updates(std::move(ptr));
+ td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendMessage");
+ return td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
auto sent_message = move_tl_object_as<telegram_api::updateShortSentMessage>(ptr);
- td->messages_manager_->on_update_sent_text_message(random_id_, std::move(sent_message->media_),
- std::move(sent_message->entities_));
+ td_->messages_manager_->on_update_sent_text_message(random_id_, std::move(sent_message->media_),
+ std::move(sent_message->entities_));
auto message_id = MessageId(ServerMessageId(sent_message->id_));
+ auto ttl_period = (sent_message->flags_ & telegram_api::updateShortSentMessage::TTL_PERIOD_MASK) != 0
+ ? sent_message->ttl_period_
+ : 0;
+ auto update = make_tl_object<updateSentMessage>(random_id_, message_id, sent_message->date_, ttl_period);
if (dialog_id_.get_type() == DialogType::Channel) {
- td->messages_manager_->add_pending_channel_update(
- dialog_id_, make_tl_object<updateSentMessage>(random_id_, message_id, sent_message->date_),
- sent_message->pts_, sent_message->pts_count_, "send message actor");
+ td_->messages_manager_->add_pending_channel_update(dialog_id_, std::move(update), sent_message->pts_,
+ sent_message->pts_count_, Promise<Unit>(),
+ "send message actor");
return;
}
- td->messages_manager_->add_pending_update(
- make_tl_object<updateSentMessage>(random_id_, message_id, sent_message->date_), sent_message->pts_,
- sent_message->pts_count_, false, "send message actor");
+ td_->updates_manager_->add_pending_pts_update(std::move(update), sent_message->pts_, sent_message->pts_count_,
+ Time::now(), Promise<Unit>(), "send message actor");
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for sendMessage: " << status;
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for SendMessage: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMessageActor");
- td->messages_manager_->on_send_message_fail(random_id_, std::move(status));
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMessageQuery");
+ td_->messages_manager_->on_send_message_fail(random_id_, std::move(status));
}
};
-class StartBotQuery : public Td::ResultHandler {
+class StartBotQuery final : public Td::ResultHandler {
int64 random_id_;
DialogId dialog_id_;
@@ -1975,133 +3480,151 @@ class StartBotQuery : public Td::ResultHandler {
random_id_ = random_id;
dialog_id_ = dialog_id;
- auto query = G()->net_query_creator().create(create_storer(
- telegram_api::messages_startBot(std::move(bot_input_user), std::move(input_peer), random_id, parameter)));
- if (G()->shared_config().get_option_boolean("use_quick_ack")) {
- query->quick_ack_promise_ = PromiseCreator::lambda(
- [random_id](Unit) {
- send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
- },
- PromiseCreator::Ignore());
+ auto query = G()->net_query_creator().create(
+ telegram_api::messages_startBot(std::move(bot_input_user), std::move(input_peer), random_id, parameter),
+ {{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}});
+ if (td_->option_manager_->get_option_boolean("use_quick_ack")) {
+ query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result<Unit> result) {
+ if (result.is_ok()) {
+ send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
+ }
+ });
}
auto send_query_ref = query.get_weak();
send_query(std::move(query));
return send_query_ref;
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_startBot>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for startBot for " << random_id_ << ": " << to_string(ptr);
+ LOG(INFO) << "Receive result for StartBotQuery for " << random_id_ << ": " << to_string(ptr);
// Result may contain messageActionChatAddUser
- // td->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "StartBot");
- td->updates_manager_->on_get_updates(std::move(ptr));
+ // td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "StartBot");
+ td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for startBot: " << status;
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for StartBotQuery: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message should be re-sent
return;
}
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartBotQuery");
- td->messages_manager_->on_send_message_fail(random_id_, std::move(status));
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "StartBotQuery");
+ td_->messages_manager_->on_send_message_fail(random_id_, std::move(status));
}
};
-class SendInlineBotResultQuery : public Td::ResultHandler {
+class SendInlineBotResultQuery final : public Td::ResultHandler {
int64 random_id_;
DialogId dialog_id_;
public:
- NetQueryRef send(int32 flags, DialogId dialog_id, MessageId reply_to_message_id, int64 random_id, int64 query_id,
- const string &result_id) {
+ NetQueryRef send(int32 flags, DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> as_input_peer,
+ MessageId reply_to_message_id, MessageId top_thread_message_id, int32 schedule_date, int64 random_id,
+ int64 query_id, const string &result_id) {
random_id_ = random_id;
dialog_id_ = dialog_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
- auto query = G()->net_query_creator().create(create_storer(telegram_api::messages_sendInlineBotResult(
- flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer),
- reply_to_message_id.get_server_message_id().get(), random_id, query_id, result_id)));
+ if (as_input_peer != nullptr) {
+ flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
+ }
+
+ CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server());
+ CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server());
+ auto query = G()->net_query_creator().create(
+ telegram_api::messages_sendInlineBotResult(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer),
+ reply_to_message_id.get_server_message_id().get(), top_thread_message_id.get_server_message_id().get(),
+ random_id, query_id, result_id, schedule_date, std::move(as_input_peer)),
+ {{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}});
auto send_query_ref = query.get_weak();
send_query(std::move(query));
return send_query_ref;
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendInlineBotResult>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for sendInlineBotResult for " << random_id_ << ": " << to_string(ptr);
- td->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendInlineBotResult");
- td->updates_manager_->on_get_updates(std::move(ptr));
+ LOG(INFO) << "Receive result for SendInlineBotResultQuery for " << random_id_ << ": " << to_string(ptr);
+ td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendInlineBotResult");
+ td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for sendInlineBotResult: " << status;
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for SendInlineBotResultQuery: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendInlineBotResultQuery");
- td->messages_manager_->on_send_message_fail(random_id_, std::move(status));
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendInlineBotResultQuery");
+ td_->messages_manager_->on_send_message_fail(random_id_, std::move(status));
}
};
-class SendMultiMediaActor : public NetActorOnce {
+class SendMultiMediaQuery final : public Td::ResultHandler {
+ vector<FileId> file_ids_;
+ vector<string> file_references_;
vector<int64> random_ids_;
DialogId dialog_id_;
public:
- void send(int32 flags, DialogId dialog_id, MessageId reply_to_message_id,
- vector<tl_object_ptr<telegram_api::inputSingleMedia>> &&input_single_media, uint64 sequence_dispatcher_id) {
+ void send(int32 flags, DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> as_input_peer,
+ MessageId reply_to_message_id, MessageId top_thread_message_id, int32 schedule_date,
+ vector<FileId> &&file_ids, vector<tl_object_ptr<telegram_api::inputSingleMedia>> &&input_single_media,
+ bool is_copy) {
for (auto &single_media : input_single_media) {
random_ids_.push_back(single_media->random_id_);
+ CHECK(FileManager::extract_was_uploaded(single_media->media_) == false);
+ file_references_.push_back(FileManager::extract_file_reference(single_media->media_));
}
dialog_id_ = dialog_id;
+ file_ids_ = std::move(file_ids);
+ CHECK(file_ids_.size() == random_ids_.size());
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
- on_error(0, Status::Error(400, "Have no write access to the chat"));
- return;
+ return on_error(Status::Error(400, "Have no write access to the chat"));
}
- auto query = G()->net_query_creator().create(create_storer(telegram_api::messages_sendMultiMedia(
- flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(input_peer),
- reply_to_message_id.get_server_message_id().get(), std::move(input_single_media))));
- if (G()->shared_config().get_option_boolean("use_quick_ack")) {
- query->quick_ack_promise_ = PromiseCreator::lambda(
- [random_ids = random_ids_](Unit) {
- for (auto random_id : random_ids) {
- send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
- }
- },
- PromiseCreator::Ignore());
+ if (as_input_peer != nullptr) {
+ flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
}
- query->debug("send to MessagesManager::MultiSequenceDispatcher");
- send_closure(td->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback,
- std::move(query), actor_shared(this), sequence_dispatcher_id);
+
+ // no quick ack, because file reference errors are very likely to happen
+ CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server());
+ CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server());
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_sendMultiMedia(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, std::move(input_peer),
+ reply_to_message_id.get_server_message_id().get(),
+ top_thread_message_id.get_server_message_id().get(),
+ std::move(input_single_media), schedule_date, std::move(as_input_peer)),
+ {{dialog_id, is_copy ? MessageContentType::Text : MessageContentType::Photo},
+ {dialog_id, MessageContentType::Photo}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendMultiMedia>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for sendMultiMedia for " << format::as_array(random_ids_) << ": " << to_string(ptr);
+ LOG(INFO) << "Receive result for SendMultiMedia for " << format::as_array(random_ids_) << ": " << to_string(ptr);
- auto sent_random_ids = td->updates_manager_->get_sent_messages_random_ids(ptr.get());
+ auto sent_random_ids = UpdatesManager::get_sent_messages_random_ids(ptr.get());
bool is_result_wrong = false;
auto sent_random_ids_size = sent_random_ids.size();
for (auto &random_id : random_ids_) {
@@ -2110,7 +3633,7 @@ class SendMultiMediaActor : public NetActorOnce {
if (random_ids_.size() == 1) {
is_result_wrong = true;
}
- td->messages_manager_->on_send_message_fail(random_id, Status::Error(400, "Message was not sent"));
+ td_->messages_manager_->on_send_message_fail(random_id, Status::Error(400, "Message was not sent"));
} else {
sent_random_ids.erase(it);
}
@@ -2119,268 +3642,318 @@ class SendMultiMediaActor : public NetActorOnce {
is_result_wrong = true;
}
if (!is_result_wrong) {
- auto sent_messages = td->updates_manager_->get_new_messages(ptr.get());
+ auto sent_messages = UpdatesManager::get_new_messages(ptr.get());
if (sent_random_ids_size != sent_messages.size()) {
is_result_wrong = true;
}
for (auto &sent_message : sent_messages) {
- if (td->messages_manager_->get_message_dialog_id(*sent_message) != dialog_id_) {
+ if (MessagesManager::get_message_dialog_id(*sent_message) != dialog_id_) {
is_result_wrong = true;
}
}
}
if (is_result_wrong) {
- LOG(ERROR) << "Receive wrong result for sendMultiMedia with random_ids " << format::as_array(random_ids_)
+ LOG(ERROR) << "Receive wrong result for SendMultiMediaQuery with random_ids " << format::as_array(random_ids_)
<< " to " << dialog_id_ << ": " << oneline(to_string(ptr));
- td->updates_manager_->schedule_get_difference("Wrong sendMultiMedia result");
+ td_->updates_manager_->schedule_get_difference("Wrong sendMultiMedia result");
}
- td->updates_manager_->on_get_updates(std::move(ptr));
+ td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for sendMultiMedia: " << status;
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for SendMultiMedia: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMultiMediaActor");
+ if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ auto pos = FileReferenceManager::get_file_reference_error_pos(status);
+ if (1 <= pos && pos <= file_ids_.size() && file_ids_[pos - 1].is_valid()) {
+ VLOG(file_references) << "Receive " << status << " for " << file_ids_[pos - 1];
+ td_->file_manager_->delete_file_reference(file_ids_[pos - 1], file_references_[pos - 1]);
+ td_->messages_manager_->on_send_media_group_file_reference_error(dialog_id_, std::move(random_ids_));
+ return;
+ } else {
+ LOG(ERROR) << "Receive file reference error " << status << ", but file_ids = " << file_ids_
+ << ", message_count = " << file_ids_.size();
+ }
+ }
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMultiMediaQuery");
for (auto &random_id : random_ids_) {
- td->messages_manager_->on_send_message_fail(random_id, status.clone());
+ td_->messages_manager_->on_send_message_fail(random_id, status.clone());
}
}
};
-class SendMediaActor : public NetActorOnce {
- int64 random_id_;
+class SendMediaQuery final : public Td::ResultHandler {
+ int64 random_id_ = 0;
FileId file_id_;
FileId thumbnail_file_id_;
DialogId dialog_id_;
+ string file_reference_;
+ bool was_uploaded_ = false;
+ bool was_thumbnail_uploaded_ = false;
public:
- void send(FileId file_id, FileId thumbnail_file_id, int32 flags, DialogId dialog_id, MessageId reply_to_message_id,
+ void send(FileId file_id, FileId thumbnail_file_id, int32 flags, DialogId dialog_id,
+ tl_object_ptr<telegram_api::InputPeer> as_input_peer, MessageId reply_to_message_id,
+ MessageId top_thread_message_id, int32 schedule_date,
tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup,
- vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities, const string &message,
- tl_object_ptr<telegram_api::InputMedia> &&input_media, int64 random_id, NetQueryRef *send_query_ref,
- uint64 sequence_dispatcher_id) {
+ vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities, const string &text,
+ tl_object_ptr<telegram_api::InputMedia> &&input_media, MessageContentType content_type, bool is_copy,
+ int64 random_id, NetQueryRef *send_query_ref) {
random_id_ = random_id;
file_id_ = file_id;
thumbnail_file_id_ = thumbnail_file_id;
dialog_id_ = dialog_id;
+ file_reference_ = FileManager::extract_file_reference(input_media);
+ was_uploaded_ = FileManager::extract_was_uploaded(input_media);
+ was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media);
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
- on_error(0, Status::Error(400, "Have no write access to the chat"));
- return;
+ return on_error(Status::Error(400, "Have no write access to the chat"));
}
+
if (!entities.empty()) {
flags |= telegram_api::messages_sendMedia::ENTITIES_MASK;
}
+ if (as_input_peer != nullptr) {
+ flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
+ }
- telegram_api::messages_sendMedia request(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- std::move(input_peer), reply_to_message_id.get_server_message_id().get(),
- std::move(input_media), message, random_id, std::move(reply_markup),
- std::move(entities));
- LOG(INFO) << "Send media: " << to_string(request);
- auto query = G()->net_query_creator().create(create_storer(request));
- if (G()->shared_config().get_option_boolean("use_quick_ack")) {
- query->quick_ack_promise_ = PromiseCreator::lambda(
- [random_id](Unit) {
- send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
- },
- PromiseCreator::Ignore());
+ CHECK(reply_to_message_id == MessageId() || reply_to_message_id.is_server());
+ CHECK(top_thread_message_id == MessageId() || top_thread_message_id.is_server());
+ auto query = G()->net_query_creator().create(
+ telegram_api::messages_sendMedia(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ std::move(input_peer), reply_to_message_id.get_server_message_id().get(),
+ top_thread_message_id.get_server_message_id().get(), std::move(input_media), text, random_id,
+ std::move(reply_markup), std::move(entities), schedule_date, std::move(as_input_peer)),
+ {{dialog_id, content_type}, {dialog_id, is_copy ? MessageContentType::Text : content_type}});
+ if (td_->option_manager_->get_option_boolean("use_quick_ack") && was_uploaded_) {
+ query->quick_ack_promise_ = PromiseCreator::lambda([random_id](Result<Unit> result) {
+ if (result.is_ok()) {
+ send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
+ }
+ });
}
*send_query_ref = query.get_weak();
- query->debug("send to MessagesManager::MultiSequenceDispatcher");
- send_closure(td->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback,
- std::move(query), actor_shared(this), sequence_dispatcher_id);
+ send_query(std::move(query));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendMedia>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- if (thumbnail_file_id_.is_valid()) {
+ if (was_thumbnail_uploaded_) {
+ CHECK(thumbnail_file_id_.is_valid());
// always delete partial remote location for the thumbnail, because it can't be reused anyway
// TODO delete it only in the case it can't be merged with file thumbnail
- td->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
+ td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for sendMedia for " << random_id_ << ": " << to_string(ptr);
- td->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendMedia");
- td->updates_manager_->on_get_updates(std::move(ptr));
+ LOG(INFO) << "Receive result for SendMediaQuery for " << random_id_ << ": " << to_string(ptr);
+ td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(), "SendMedia");
+ td_->updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for sendMedia: " << status;
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for SendMedia: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMediaActor");
- if (file_id_.is_valid()) {
+ if (was_uploaded_) {
+ if (was_thumbnail_uploaded_) {
+ CHECK(thumbnail_file_id_.is_valid());
+ // always delete partial remote location for the thumbnail, because it can't be reused anyway
+ td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
+ }
+
+ CHECK(file_id_.is_valid());
if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
- td->messages_manager_->on_send_message_file_part_missing(random_id_,
- to_integer<int32>(status.message().substr(10)));
+ td_->messages_manager_->on_send_message_file_part_missing(random_id_,
+ to_integer<int32>(status.message().substr(10)));
return;
} else {
if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) {
- td->file_manager_->delete_partial_remote_location(file_id_);
+ td_->file_manager_->delete_partial_remote_location(file_id_);
}
}
+ } else if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ if (file_id_.is_valid() && !was_uploaded_) {
+ VLOG(file_references) << "Receive " << status << " for " << file_id_;
+ td_->file_manager_->delete_file_reference(file_id_, file_reference_);
+ td_->messages_manager_->on_send_message_file_reference_error(random_id_);
+ return;
+ } else {
+ LOG(ERROR) << "Receive file reference error, but file_id = " << file_id_
+ << ", was_uploaded = " << was_uploaded_;
+ }
}
- if (thumbnail_file_id_.is_valid()) {
- // always delete partial remote location for the thumbnail, because it can't be reused anyway
- td->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
- }
- td->messages_manager_->on_send_message_fail(random_id_, std::move(status));
+
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendMediaQuery");
+ td_->messages_manager_->on_send_message_fail(random_id_, std::move(status));
}
};
-class UploadMediaQuery : public Td::ResultHandler {
+class UploadMediaQuery final : public Td::ResultHandler {
DialogId dialog_id_;
MessageId message_id_;
FileId file_id_;
FileId thumbnail_file_id_;
+ string file_reference_;
+ bool was_uploaded_ = false;
+ bool was_thumbnail_uploaded_ = false;
public:
void send(DialogId dialog_id, MessageId message_id, FileId file_id, FileId thumbnail_file_id,
tl_object_ptr<telegram_api::InputMedia> &&input_media) {
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
- if (input_peer == nullptr) {
- on_error(0, Status::Error(400, "Have no write access to the chat"));
- return;
- }
CHECK(input_media != nullptr);
-
dialog_id_ = dialog_id;
message_id_ = message_id;
file_id_ = file_id;
thumbnail_file_id_ = thumbnail_file_id;
+ file_reference_ = FileManager::extract_file_reference(input_media);
+ was_uploaded_ = FileManager::extract_was_uploaded(input_media);
+ was_thumbnail_uploaded_ = FileManager::extract_was_thumbnail_uploaded(input_media);
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Have no write access to the chat"));
+ }
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_uploadMedia(std::move(input_peer), std::move(input_media)))));
+ telegram_api::messages_uploadMedia(std::move(input_peer), std::move(input_media))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_uploadMedia>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
+ }
+
+ if (was_thumbnail_uploaded_) {
+ CHECK(thumbnail_file_id_.is_valid());
+ // always delete partial remote location for the thumbnail, because it can't be reused anyway
+ td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for uploadMedia: " << to_string(ptr);
- td->messages_manager_->on_upload_message_media_success(dialog_id_, message_id_, std::move(ptr));
+ LOG(INFO) << "Receive result for UploadMediaQuery for " << message_id_ << " in " << dialog_id_ << ": "
+ << to_string(ptr);
+ td_->messages_manager_->on_upload_message_media_success(dialog_id_, message_id_, std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- LOG(WARNING) << "Receive error for uploadMedia: " << status;
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for UploadMediaQuery for " << message_id_ << " in " << dialog_id_ << ": " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
return;
}
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "UploadMediaQuery");
- if (file_id_.is_valid()) {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UploadMediaQuery");
+ if (was_uploaded_) {
+ if (was_thumbnail_uploaded_) {
+ CHECK(thumbnail_file_id_.is_valid());
+ // always delete partial remote location for the thumbnail, because it can't be reused anyway
+ td_->file_manager_->delete_partial_remote_location(thumbnail_file_id_);
+ }
+
+ CHECK(file_id_.is_valid());
if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
- td->messages_manager_->on_upload_message_media_file_part_missing(
+ td_->messages_manager_->on_upload_message_media_file_part_missing(
dialog_id_, message_id_, to_integer<int32>(status.message().substr(10)));
return;
} else {
if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) {
- td->file_manager_->delete_partial_remote_location(file_id_);
+ td_->file_manager_->delete_partial_remote_location(file_id_);
}
}
+ } else if (FileReferenceManager::is_file_reference_error(status)) {
+ LOG(ERROR) << "Receive file reference error for UploadMediaQuery";
}
- td->messages_manager_->on_upload_message_media_fail(dialog_id_, message_id_, std::move(status));
+ td_->messages_manager_->on_upload_message_media_fail(dialog_id_, message_id_, std::move(status));
}
};
-class EditMessageActor : public NetActorOnce {
+class SendScheduledMessageQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
- explicit EditMessageActor(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit SendScheduledMessageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(int32 flags, DialogId dialog_id, MessageId message_id, const string &message,
- vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities,
- tl_object_ptr<telegram_api::InputGeoPoint> &&input_geo_point,
- tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup, uint64 sequence_dispatcher_id) {
+ void send(DialogId dialog_id, MessageId message_id) {
dialog_id_ = dialog_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit);
if (input_peer == nullptr) {
- on_error(0, Status::Error(400, "Can't access the chat"));
- return;
+ return on_error(Status::Error(400, "Can't access the chat"));
}
- if (reply_markup != nullptr) {
- flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP;
- }
- if (!entities.empty()) {
- flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES;
- }
- if (!message.empty()) {
- flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_MESSAGE;
- }
- if (input_geo_point != nullptr) {
- flags |= telegram_api::messages_editMessage::GEO_POINT_MASK;
- }
- LOG(DEBUG) << "Edit message with flags " << flags;
-
- auto query = G()->net_query_creator().create(create_storer(telegram_api::messages_editMessage(
- flags, false /*ignored*/, false /*ignored*/, std::move(input_peer), message_id.get_server_message_id().get(),
- message, std::move(reply_markup), std::move(entities), std::move(input_geo_point))));
-
- query->debug("send to MessagesManager::MultiSequenceDispatcher");
- send_closure(td->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback,
- std::move(query), actor_shared(this), sequence_dispatcher_id);
+ int32 server_message_id = message_id.get_scheduled_server_message_id().get();
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_sendScheduledMessages(std::move(input_peer), {server_message_id}),
+ {{dialog_id, MessageContentType::Text}, {dialog_id, MessageContentType::Photo}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_editMessage>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_sendScheduledMessages>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for editMessage: " << to_string(ptr);
- if (ptr->get_id() == telegram_api::updateShortSentMessage::ID) {
- LOG(ERROR) << "Receive updateShortSentMessage in edit message";
- return on_error(id, Status::Error(500, "Unsupported result was returned from the server"));
- }
-
- td->updates_manager_->on_get_updates(std::move(ptr));
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for SendScheduledMessageQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for editMessage: " << status;
- if (!td->auth_manager_->is_bot() && status.message() == "MESSAGE_NOT_MODIFIED") {
- return promise_.set_value(Unit());
- }
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditMessageActor");
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for SendScheduledMessageQuery: " << status;
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendScheduledMessageQuery");
promise_.set_error(std::move(status));
}
};
-class EditInlineMessageQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class EditMessageQuery final : public Td::ResultHandler {
+ Promise<int32> promise_;
+ DialogId dialog_id_;
+ MessageId message_id_;
public:
- explicit EditInlineMessageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit EditMessageQuery(Promise<Unit> &&promise) {
+ promise_ = PromiseCreator::lambda([promise = std::move(promise)](Result<int32> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(Unit());
+ }
+ });
+ }
+ explicit EditMessageQuery(Promise<int32> &&promise) : promise_(std::move(promise)) {
}
- void send(int32 flags, tl_object_ptr<telegram_api::inputBotInlineMessageID> input_bot_inline_message_id,
- const string &message, vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities,
- tl_object_ptr<telegram_api::InputGeoPoint> &&input_geo_point,
- tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) {
- CHECK(input_bot_inline_message_id != nullptr);
+ void send(int32 flags, DialogId dialog_id, MessageId message_id, const string &text,
+ vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities,
+ tl_object_ptr<telegram_api::InputMedia> &&input_media,
+ tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup, int32 schedule_date) {
+ dialog_id_ = dialog_id;
+ message_id_ = message_id;
+
+ if (input_media != nullptr && false) {
+ return on_error(Status::Error(400, "FILE_PART_1_MISSING"));
+ }
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
if (reply_markup != nullptr) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP;
@@ -2388,274 +3961,167 @@ class EditInlineMessageQuery : public Td::ResultHandler {
if (!entities.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES;
}
- if (!message.empty()) {
+ if (!text.empty()) {
flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_MESSAGE;
}
- if (input_geo_point != nullptr) {
- flags |= telegram_api::messages_editInlineBotMessage::GEO_POINT_MASK;
+ if (input_media != nullptr) {
+ flags |= telegram_api::messages_editMessage::MEDIA_MASK;
+ }
+ if (schedule_date != 0) {
+ flags |= telegram_api::messages_editMessage::SCHEDULE_DATE_MASK;
}
- LOG(DEBUG) << "Edit inline message with flags " << flags;
- auto dc_id = DcId::internal(input_bot_inline_message_id->dc_id_);
+ int32 server_message_id = schedule_date != 0 ? message_id.get_scheduled_server_message_id().get()
+ : message_id.get_server_message_id().get();
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_editInlineBotMessage(
- flags, false /*ignored*/, false /*ignored*/, std::move(input_bot_inline_message_id), message,
- std::move(reply_markup), std::move(entities), std::move(input_geo_point))),
- dc_id));
+ telegram_api::messages_editMessage(flags, false /*ignored*/, std::move(input_peer), server_message_id, text,
+ std::move(input_media), std::move(reply_markup), std::move(entities),
+ schedule_date),
+ {{dialog_id}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_editInlineBotMessage>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_editMessage>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- LOG_IF(ERROR, !result_ptr.ok()) << "Receive false in result of editInlineMessage";
-
- promise_.set_value(Unit());
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for EditMessageQuery: " << to_string(ptr);
+ auto pts = td_->updates_manager_->get_update_edit_message_pts(ptr.get(), {dialog_id_, message_id_});
+ auto promise = PromiseCreator::lambda(
+ [promise = std::move(promise_), pts](Result<Unit> result) mutable { promise.set_value(std::move(pts)); });
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise));
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for editInlineMessage: " << status;
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for EditMessage: " << status;
+ if (!td_->auth_manager_->is_bot() && status.message() == "MESSAGE_NOT_MODIFIED") {
+ return promise_.set_value(0);
+ }
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditMessageQuery");
promise_.set_error(std::move(status));
}
};
-class SetGameScoreActor : public NetActorOnce {
+class EditInlineMessageQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- DialogId dialog_id_;
public:
- explicit SetGameScoreActor(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit EditInlineMessageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(DialogId dialog_id, MessageId message_id, bool edit_message,
- tl_object_ptr<telegram_api::InputUser> input_user, int32 score, bool force, uint64 sequence_dispatcher_id) {
- int32 flags = 0;
- if (edit_message) {
- flags |= telegram_api::messages_setGameScore::EDIT_MESSAGE_MASK;
- }
- if (force) {
- flags |= telegram_api::messages_setGameScore::FORCE_MASK;
- }
+ void send(int32 flags, tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id,
+ const string &text, vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities,
+ tl_object_ptr<telegram_api::InputMedia> &&input_media,
+ tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) {
+ CHECK(input_bot_inline_message_id != nullptr);
- dialog_id_ = dialog_id;
+ // file in an inline message can't be uploaded to another datacenter,
+ // so only previously uploaded files or URLs can be used in the InputMedia
+ CHECK(!FileManager::extract_was_uploaded(input_media));
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit);
- if (input_peer == nullptr) {
- on_error(0, Status::Error(400, "Can't access the chat"));
- return;
+ if (reply_markup != nullptr) {
+ flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_REPLY_MARKUP;
}
-
- CHECK(input_user != nullptr);
- auto query = G()->net_query_creator().create(create_storer(
- telegram_api::messages_setGameScore(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer),
- message_id.get_server_message_id().get(), std::move(input_user), score)));
-
- LOG(INFO) << "Set game score to " << score;
-
- query->debug("send to MessagesManager::MultiSequenceDispatcher");
- send_closure(td->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback,
- std::move(query), actor_shared(this), sequence_dispatcher_id);
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_setGameScore>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ if (!entities.empty()) {
+ flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_ENTITIES;
}
-
- auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for setGameScore: " << to_string(ptr);
- td->updates_manager_->on_get_updates(std::move(ptr));
-
- promise_.set_value(Unit());
- }
-
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for setGameScore: " << status;
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetGameScoreActor");
- promise_.set_error(std::move(status));
- }
-};
-
-class SetInlineGameScoreQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
-
- public:
- explicit SetInlineGameScoreQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
- }
-
- void send(tl_object_ptr<telegram_api::inputBotInlineMessageID> input_bot_inline_message_id, bool edit_message,
- tl_object_ptr<telegram_api::InputUser> input_user, int32 score, bool force) {
- CHECK(input_bot_inline_message_id != nullptr);
- CHECK(input_user != nullptr);
-
- int32 flags = 0;
- if (edit_message) {
- flags |= telegram_api::messages_setInlineGameScore::EDIT_MESSAGE_MASK;
+ if (!text.empty()) {
+ flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_MESSAGE;
}
- if (force) {
- flags |= telegram_api::messages_setInlineGameScore::FORCE_MASK;
+ if (input_media != nullptr) {
+ flags |= telegram_api::messages_editInlineBotMessage::MEDIA_MASK;
}
- LOG(INFO) << "Set inline game score to " << score;
- auto dc_id = DcId::internal(input_bot_inline_message_id->dc_id_);
+ auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id));
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_setInlineGameScore(flags, false /*ignored*/, false /*ignored*/,
- std::move(input_bot_inline_message_id),
- std::move(input_user), score)),
- dc_id));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_setInlineGameScore>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
-
- LOG_IF(ERROR, !result_ptr.ok()) << "Receive false in result of setInlineGameScore";
-
- promise_.set_value(Unit());
- }
-
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for setInlineGameScore: " << status;
- promise_.set_error(std::move(status));
- }
-};
-
-class GetGameHighScoresQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
- DialogId dialog_id_;
- int64 random_id_;
-
- public:
- explicit GetGameHighScoresQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ telegram_api::messages_editInlineBotMessage(flags, false /*ignored*/, std::move(input_bot_inline_message_id),
+ text, std::move(input_media), std::move(reply_markup),
+ std::move(entities)),
+ {}, dc_id));
}
- void send(DialogId dialog_id, MessageId message_id, tl_object_ptr<telegram_api::InputUser> input_user,
- int64 random_id) {
- dialog_id_ = dialog_id;
- random_id_ = random_id;
-
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
- CHECK(input_peer != nullptr);
-
- CHECK(input_user != nullptr);
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getGameHighScores(
- std::move(input_peer), message_id.get_server_message_id().get(), std::move(input_user)))));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_getGameHighScores>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_editInlineBotMessage>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->messages_manager_->on_get_game_high_scores(random_id_, result_ptr.move_as_ok());
- promise_.set_value(Unit());
- }
-
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for getGameHighScores: " << status;
- td->messages_manager_->on_get_game_high_scores(random_id_, nullptr);
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetGameHighScoresQuery");
- promise_.set_error(std::move(status));
- }
-};
-
-class GetInlineGameHighScoresQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
- int64 random_id_;
-
- public:
- explicit GetInlineGameHighScoresQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
- }
-
- void send(tl_object_ptr<telegram_api::inputBotInlineMessageID> input_bot_inline_message_id,
- tl_object_ptr<telegram_api::InputUser> input_user, int64 random_id) {
- CHECK(input_bot_inline_message_id != nullptr);
- CHECK(input_user != nullptr);
-
- random_id_ = random_id;
-
- auto dc_id = DcId::internal(input_bot_inline_message_id->dc_id_);
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getInlineGameHighScores(
- std::move(input_bot_inline_message_id), std::move(input_user))),
- dc_id));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_getInlineGameHighScores>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
+ LOG_IF(ERROR, !result_ptr.ok()) << "Receive false in result of editInlineMessage";
- td->messages_manager_->on_get_game_high_scores(random_id_, result_ptr.move_as_ok());
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for getInlineGameHighScores: " << status;
- td->messages_manager_->on_get_game_high_scores(random_id_, nullptr);
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for EditInlineMessageQuery: " << status;
promise_.set_error(std::move(status));
}
};
-class ForwardMessagesActor : public NetActorOnce {
+class ForwardMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
vector<int64> random_ids_;
+ DialogId from_dialog_id_;
DialogId to_dialog_id_;
public:
- explicit ForwardMessagesActor(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ForwardMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(int32 flags, DialogId to_dialog_id, DialogId from_dialog_id, const vector<MessageId> &message_ids,
- vector<int64> &&random_ids, uint64 sequence_dispatcher_id) {
- LOG(INFO) << "Forward " << format::as_array(message_ids) << " from " << from_dialog_id << " to " << to_dialog_id;
-
+ void send(int32 flags, DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id,
+ tl_object_ptr<telegram_api::InputPeer> as_input_peer, const vector<MessageId> &message_ids,
+ vector<int64> &&random_ids, int32 schedule_date) {
random_ids_ = random_ids;
+ from_dialog_id_ = from_dialog_id;
to_dialog_id_ = to_dialog_id;
- auto to_input_peer = td->messages_manager_->get_input_peer(to_dialog_id, AccessRights::Write);
+ auto to_input_peer = td_->messages_manager_->get_input_peer(to_dialog_id, AccessRights::Write);
if (to_input_peer == nullptr) {
- on_error(0, Status::Error(400, "Have no write access to the chat"));
- return;
+ return on_error(Status::Error(400, "Have no write access to the chat"));
}
- auto from_input_peer = td->messages_manager_->get_input_peer(from_dialog_id, AccessRights::Read);
+ auto from_input_peer = td_->messages_manager_->get_input_peer(from_dialog_id, AccessRights::Read);
if (from_input_peer == nullptr) {
- on_error(0, Status::Error(400, "Can't access the chat to forward messages from"));
- return;
+ return on_error(Status::Error(400, "Can't access the chat to forward messages from"));
}
- auto query = G()->net_query_creator().create(create_storer(telegram_api::messages_forwardMessages(
- flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(from_input_peer),
- MessagesManager::get_server_message_ids(message_ids), std::move(random_ids), std::move(to_input_peer))));
- if (G()->shared_config().get_option_boolean("use_quick_ack")) {
- query->quick_ack_promise_ = PromiseCreator::lambda(
- [random_ids = random_ids_](Unit) {
- for (auto random_id : random_ids) {
- send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
- }
- },
- PromiseCreator::Ignore());
+ if (as_input_peer != nullptr) {
+ flags |= MessagesManager::SEND_MESSAGE_FLAG_HAS_SEND_AS;
}
- send_closure(td->messages_manager_->sequence_dispatcher_, &MultiSequenceDispatcher::send_with_callback,
- std::move(query), actor_shared(this), sequence_dispatcher_id);
+ if (top_thread_message_id.is_valid()) {
+ flags |= MessagesManager::SEND_MESSAGE_FLAG_IS_FROM_THREAD;
+ }
+
+ auto query = G()->net_query_creator().create(
+ telegram_api::messages_forwardMessages(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, std::move(from_input_peer), MessagesManager::get_server_message_ids(message_ids),
+ std::move(random_ids), std::move(to_input_peer), top_thread_message_id.get_server_message_id().get(),
+ schedule_date, std::move(as_input_peer)),
+ {{to_dialog_id, MessageContentType::Text}, {to_dialog_id, MessageContentType::Photo}});
+ if (td_->option_manager_->get_option_boolean("use_quick_ack")) {
+ query->quick_ack_promise_ = PromiseCreator::lambda([random_ids = random_ids_](Result<Unit> result) {
+ if (result.is_ok()) {
+ for (auto random_id : random_ids) {
+ send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
+ }
+ }
+ });
+ }
+ send_query(std::move(query));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_forwardMessages>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for forwardMessages for " << format::as_array(random_ids_) << ": " << to_string(ptr);
- auto sent_random_ids = td->updates_manager_->get_sent_messages_random_ids(ptr.get());
+ LOG(INFO) << "Receive result for ForwardMessagesQuery for " << format::as_array(random_ids_) << ": "
+ << to_string(ptr);
+ auto sent_random_ids = UpdatesManager::get_sent_messages_random_ids(ptr.get());
bool is_result_wrong = false;
auto sent_random_ids_size = sent_random_ids.size();
for (auto &random_id : random_ids_) {
@@ -2664,7 +4130,7 @@ class ForwardMessagesActor : public NetActorOnce {
if (random_ids_.size() == 1) {
is_result_wrong = true;
}
- td->messages_manager_->on_send_message_fail(random_id, Status::Error(400, "Message was not forwarded"));
+ td_->messages_manager_->on_send_message_fail(random_id, Status::Error(400, "Message was not forwarded"));
} else {
sent_random_ids.erase(it);
}
@@ -2673,12 +4139,12 @@ class ForwardMessagesActor : public NetActorOnce {
is_result_wrong = true;
}
if (!is_result_wrong) {
- auto sent_messages = td->updates_manager_->get_new_messages(ptr.get());
+ auto sent_messages = UpdatesManager::get_new_messages(ptr.get());
if (sent_random_ids_size != sent_messages.size()) {
is_result_wrong = true;
}
for (auto &sent_message : sent_messages) {
- if (td->messages_manager_->get_message_dialog_id(*sent_message) != to_dialog_id_) {
+ if (MessagesManager::get_message_dialog_id(*sent_message) != to_dialog_id_) {
is_result_wrong = true;
}
}
@@ -2686,28 +4152,33 @@ class ForwardMessagesActor : public NetActorOnce {
if (is_result_wrong) {
LOG(ERROR) << "Receive wrong result for forwarding messages with random_ids " << format::as_array(random_ids_)
<< " to " << to_dialog_id_ << ": " << oneline(to_string(ptr));
- td->updates_manager_->schedule_get_difference("Wrong forwardMessages result");
+ td_->updates_manager_->schedule_get_difference("Wrong forwardMessages result");
}
- td->updates_manager_->on_get_updates(std::move(ptr));
- promise_.set_value(Unit());
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
LOG(INFO) << "Receive error for forward messages: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, messages should be re-sent
return;
}
// no on_get_dialog_error call, because two dialogs are involved
+ if (status.code() == 400 && status.message() == CSlice("CHAT_FORWARDS_RESTRICTED")) {
+ td_->contacts_manager_->reload_dialog_info(from_dialog_id_, Promise<Unit>());
+ }
+ if (status.code() == 400 && status.message() == CSlice("SEND_AS_PEER_INVALID")) {
+ td_->messages_manager_->reload_dialog_info_full(to_dialog_id_, "SEND_AS_PEER_INVALID");
+ }
for (auto &random_id : random_ids_) {
- td->messages_manager_->on_send_message_fail(random_id, status.clone());
+ td_->messages_manager_->on_send_message_fail(random_id, status.clone());
}
promise_.set_error(std::move(status));
}
};
-class SendScreenshotNotificationQuery : public Td::ResultHandler {
+class SendScreenshotNotificationQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
int64 random_id_;
DialogId dialog_id_;
@@ -2720,464 +4191,502 @@ class SendScreenshotNotificationQuery : public Td::ResultHandler {
random_id_ = random_id;
dialog_id_ = dialog_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
- auto query = G()->net_query_creator().create(
- create_storer(telegram_api::messages_sendScreenshotNotification(std::move(input_peer), 0, random_id)));
- send_query(std::move(query));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_sendScreenshotNotification(std::move(input_peer), 0, random_id),
+ {{dialog_id, MessageContentType::Text}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_sendScreenshotNotification>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for SendScreenshotNotificationQuery for " << random_id_ << ": " << to_string(ptr);
- td->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(),
- "SendScreenshotNotificationQuery");
- td->updates_manager_->on_get_updates(std::move(ptr));
- promise_.set_value(Unit());
+ td_->messages_manager_->check_send_message_result(random_id_, dialog_id_, ptr.get(),
+ "SendScreenshotNotificationQuery");
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
LOG(INFO) << "Receive error for SendScreenshotNotificationQuery: " << status;
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, messages should be re-sent
return;
}
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendScreenshotNotificationQuery");
- td->messages_manager_->on_send_message_fail(random_id_, status.clone());
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendScreenshotNotificationQuery");
+ td_->messages_manager_->on_send_message_fail(random_id_, status.clone());
promise_.set_error(std::move(status));
}
};
-class SetTypingQuery : public Td::ResultHandler {
+class SetTypingQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
+ int32 generation_ = 0;
public:
explicit SetTypingQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- NetQueryRef send(DialogId dialog_id, tl_object_ptr<telegram_api::SendMessageAction> &&action) {
+ NetQueryRef send(DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> &&input_peer, MessageId message_id,
+ tl_object_ptr<telegram_api::SendMessageAction> &&action) {
dialog_id_ = dialog_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
CHECK(input_peer != nullptr);
- auto net_query = G()->net_query_creator().create(
- create_storer(telegram_api::messages_setTyping(std::move(input_peer), std::move(action))));
- auto result = net_query.get_weak();
- send_query(std::move(net_query));
+ int32 flags = 0;
+ if (message_id.is_valid()) {
+ flags |= telegram_api::messages_setTyping::TOP_MSG_ID_MASK;
+ }
+ auto query = G()->net_query_creator().create(telegram_api::messages_setTyping(
+ flags, std::move(input_peer), message_id.get_server_message_id().get(), std::move(action)));
+ query->total_timeout_limit_ = 2;
+ auto result = query.get_weak();
+ generation_ = result.generation();
+ send_query(std::move(query));
return result;
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setTyping>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
// ignore result
-
promise_.set_value(Unit());
+
+ send_closure_later(G()->messages_manager(), &MessagesManager::after_set_typing_query, dialog_id_, generation_);
}
- void on_error(uint64 id, Status status) override {
- if (status.code() == NetQuery::Cancelled) {
+ void on_error(Status status) final {
+ if (status.code() == NetQuery::Canceled) {
return promise_.set_value(Unit());
}
- LOG(INFO) << "Receive error for set typing: " << status;
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetTypingQuery");
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetTypingQuery")) {
+ LOG(INFO) << "Receive error for set typing: " << status;
+ }
promise_.set_error(std::move(status));
+
+ send_closure_later(G()->messages_manager(), &MessagesManager::after_set_typing_query, dialog_id_, generation_);
}
};
-class DeleteMessagesQuery : public Td::ResultHandler {
+class DeleteMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- int32 query_count_;
+ DialogId dialog_id_;
+ vector<int32> server_message_ids_;
public:
explicit DeleteMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(vector<MessageId> &&message_ids, bool revoke) {
- LOG(INFO) << "Send deleteMessagesQuery to delete " << format::as_array(message_ids);
+ void send(DialogId dialog_id, vector<int32> &&server_message_ids, bool revoke) {
+ dialog_id_ = dialog_id;
+ server_message_ids_ = server_message_ids;
+
int32 flags = 0;
if (revoke) {
flags |= telegram_api::messages_deleteMessages::REVOKE_MASK;
}
- query_count_ = 0;
- auto server_message_ids = MessagesManager::get_server_message_ids(message_ids);
- const size_t MAX_SLICE_SIZE = 100;
- for (size_t i = 0; i < server_message_ids.size(); i += MAX_SLICE_SIZE) {
- auto end_i = i + MAX_SLICE_SIZE;
- auto end = end_i < server_message_ids.size() ? server_message_ids.begin() + end_i : server_message_ids.end();
- vector<int32> slice(server_message_ids.begin() + i, end);
-
- query_count_++;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_deleteMessages(flags, false /*ignored*/, std::move(slice)))));
- }
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_deleteMessages(flags, false /*ignored*/, std::move(server_message_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_deleteMessages>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto affected_messages = result_ptr.move_as_ok();
- CHECK(affected_messages->get_id() == telegram_api::messages_affectedMessages::ID);
-
if (affected_messages->pts_count_ > 0) {
- td->messages_manager_->add_pending_update(make_tl_object<dummyUpdate>(), affected_messages->pts_,
- affected_messages->pts_count_, false, "delete messages query");
- }
- if (--query_count_ == 0) {
+ td_->updates_manager_->add_pending_pts_update(make_tl_object<dummyUpdate>(), affected_messages->pts_,
+ affected_messages->pts_count_, Time::now(), std::move(promise_),
+ "delete messages query");
+ } else {
promise_.set_value(Unit());
}
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for delete messages: " << status;
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ // MESSAGE_DELETE_FORBIDDEN can be returned in group chats when administrator rights was removed
+ // MESSAGE_DELETE_FORBIDDEN can be returned in private chats for bots when revoke time limit exceeded
+ if (status.message() != "MESSAGE_DELETE_FORBIDDEN" ||
+ (dialog_id_.get_type() == DialogType::User && !td_->auth_manager_->is_bot())) {
+ LOG(ERROR) << "Receive error for delete messages: " << status;
+ }
+ }
+ td_->messages_manager_->on_failed_message_deletion(dialog_id_, server_message_ids_);
promise_.set_error(std::move(status));
}
};
-class DeleteChannelMessagesQuery : public Td::ResultHandler {
+class DeleteChannelMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- int32 query_count_;
ChannelId channel_id_;
+ vector<int32> server_message_ids_;
public:
explicit DeleteChannelMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(ChannelId channel_id, vector<MessageId> &&message_ids) {
+ void send(ChannelId channel_id, vector<int32> &&server_message_ids) {
channel_id_ = channel_id;
- LOG(INFO) << "Send deleteChannelMessagesQuery to delete " << format::as_array(message_ids) << " in the "
- << channel_id;
-
- query_count_ = 0;
- auto server_message_ids = MessagesManager::get_server_message_ids(message_ids);
- const size_t MAX_SLICE_SIZE = 100;
- for (size_t i = 0; i < server_message_ids.size(); i += MAX_SLICE_SIZE) {
- auto end_i = i + MAX_SLICE_SIZE;
- auto end = end_i < server_message_ids.size() ? server_message_ids.begin() + end_i : server_message_ids.end();
- vector<int32> slice(server_message_ids.begin() + i, end);
-
- query_count_++;
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
- CHECK(input_channel != nullptr);
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::channels_deleteMessages(std::move(input_channel), std::move(slice)))));
- }
+ server_message_ids_ = server_message_ids;
+
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ CHECK(input_channel != nullptr);
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_deleteMessages(std::move(input_channel), std::move(server_message_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::channels_deleteMessages>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto affected_messages = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for deleteChannelMessages: " << to_string(affected_messages);
- CHECK(affected_messages->get_id() == telegram_api::messages_affectedMessages::ID);
-
if (affected_messages->pts_count_ > 0) {
- td->messages_manager_->add_pending_channel_update(DialogId(channel_id_), make_tl_object<dummyUpdate>(),
- affected_messages->pts_, affected_messages->pts_count_,
- "DeleteChannelMessagesQuery");
- }
- if (--query_count_ == 0) {
+ td_->messages_manager_->add_pending_channel_update(DialogId(channel_id_), make_tl_object<dummyUpdate>(),
+ affected_messages->pts_, affected_messages->pts_count_,
+ std::move(promise_), "DeleteChannelMessagesQuery");
+ } else {
promise_.set_value(Unit());
}
}
- void on_error(uint64 id, Status status) override {
- if (!td->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelMessagesQuery")) {
- LOG(ERROR) << "Receive error for delete channel messages: " << status;
+ void on_error(Status status) final {
+ if (!td_->contacts_manager_->on_get_channel_error(channel_id_, status, "DeleteChannelMessagesQuery")) {
+ if (status.message() != "MESSAGE_DELETE_FORBIDDEN") {
+ LOG(ERROR) << "Receive error for delete channel messages: " << status;
+ }
}
+ td_->messages_manager_->on_failed_message_deletion(DialogId(channel_id_), server_message_ids_);
promise_.set_error(std::move(status));
}
};
-class GetNotifySettingsQuery : public Td::ResultHandler {
+class DeleteScheduledMessagesQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- NotificationSettingsScope scope_;
+ DialogId dialog_id_;
+ vector<MessageId> message_ids_;
public:
- explicit GetNotifySettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit DeleteScheduledMessagesQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(NotificationSettingsScope scope) {
- scope_ = scope;
- auto input_notify_peer = td->messages_manager_->get_input_notify_peer(scope);
- CHECK(input_notify_peer != nullptr);
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::account_getNotifySettings(std::move(input_notify_peer)))));
+ void send(DialogId dialog_id, vector<MessageId> &&message_ids) {
+ dialog_id_ = dialog_id;
+ message_ids_ = std::move(message_ids);
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_deleteScheduledMessages(
+ std::move(input_peer), MessagesManager::get_scheduled_server_message_ids(message_ids_))));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_getNotifySettings>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_deleteScheduledMessages>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- td->messages_manager_->on_update_notify_settings(scope_, std::move(ptr));
-
- promise_.set_value(Unit());
+ LOG(INFO) << "Receive result for DeleteScheduledMessagesQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "DeleteScheduledMessagesQuery")) {
+ LOG(ERROR) << "Receive error for delete scheduled messages: " << status;
+ }
+ td_->messages_manager_->on_failed_scheduled_message_deletion(dialog_id_, message_ids_);
promise_.set_error(std::move(status));
}
};
-class UpdateNotifySettingsQuery : public Td::ResultHandler {
- NotificationSettingsScope scope_;
+class GetPeerSettingsQuery final : public Td::ResultHandler {
+ DialogId dialog_id_;
public:
- void send(NotificationSettingsScope scope, const NotificationSettings &new_settings) {
- auto input_notify_peer = td->messages_manager_->get_input_notify_peer(scope);
- if (input_notify_peer == nullptr) {
- return;
- }
- int32 flags = 0;
- if (new_settings.show_preview) {
- flags |= telegram_api::inputPeerNotifySettings::SHOW_PREVIEWS_MASK;
- }
- if (new_settings.silent_send_message) {
- flags |= telegram_api::inputPeerNotifySettings::SILENT_MASK;
- }
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_updateNotifySettings(
- std::move(input_notify_peer),
- make_tl_object<telegram_api::inputPeerNotifySettings>(flags, false /*ignored*/, false /*ignored*/,
- new_settings.mute_until, new_settings.sound)))));
- scope_ = scope;
+ void send(DialogId dialog_id) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ CHECK(input_peer != nullptr);
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_getPeerSettings(std::move(input_peer))));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_updateNotifySettings>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getPeerSettings>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- bool result = result_ptr.ok();
- if (!result) {
- return on_error(id, Status::Error(400, "Receive false as result"));
- }
+ auto ptr = result_ptr.move_as_ok();
+ td_->contacts_manager_->on_get_users(std::move(ptr->users_), "GetPeerSettingsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "GetPeerSettingsQuery");
+ td_->messages_manager_->on_get_peer_settings(dialog_id_, std::move(ptr->settings_));
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for set notification settings: " << status;
- status.ignore();
-
- if (!td->auth_manager_->is_bot() && td->messages_manager_->get_input_notify_peer(scope_) != nullptr) {
- // trying to repair notification settings for this scope
- td->create_handler<GetNotifySettingsQuery>(Promise<>())->send(scope_);
- }
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for get peer settings: " << status;
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPeerSettingsQuery");
}
};
-class ResetNotifySettingsQuery : public Td::ResultHandler {
+class UpdatePeerSettingsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
public:
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_resetNotifySettings())));
+ explicit UpdatePeerSettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_resetNotifySettings>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ void send(DialogId dialog_id, bool is_spam_dialog) {
+ dialog_id_ = dialog_id;
+
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return promise_.set_value(Unit());
}
- bool result = result_ptr.ok();
- if (!result) {
- return on_error(id, Status::Error(400, "Receive false as result"));
+ if (is_spam_dialog) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_reportSpam(std::move(input_peer))));
+ } else {
+ send_query(G()->net_query_creator().create(telegram_api::messages_hidePeerSettingsBar(std::move(input_peer))));
}
}
- void on_error(uint64 id, Status status) override {
- LOG(WARNING) << "Receive error for reset notification settings: " << status;
- status.ignore();
+ void on_result(BufferSlice packet) final {
+ static_assert(std::is_same<telegram_api::messages_reportSpam::ReturnType,
+ telegram_api::messages_hidePeerSettingsBar::ReturnType>::value,
+ "");
+ auto result_ptr = fetch_result<telegram_api::messages_reportSpam>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ td_->messages_manager_->on_get_peer_settings(dialog_id_, make_tl_object<telegram_api::peerSettings>(), true);
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for update peer settings: " << status;
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UpdatePeerSettingsQuery");
+ td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "UpdatePeerSettingsQuery");
+ promise_.set_error(std::move(status));
}
};
-class GetPeerSettingsQuery : public Td::ResultHandler {
+class ReportEncryptedSpamQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
- explicit GetPeerSettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ReportEncryptedSpamQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send(DialogId dialog_id) {
dialog_id_ = dialog_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ auto input_peer = td_->messages_manager_->get_input_encrypted_chat(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::messages_getPeerSettings(std::move(input_peer)))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_reportEncryptedSpam(std::move(input_peer))));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_getPeerSettings>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_reportEncryptedSpam>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->messages_manager_->on_get_peer_settings(dialog_id_, result_ptr.move_as_ok());
+ td_->messages_manager_->on_get_peer_settings(dialog_id_, make_tl_object<telegram_api::peerSettings>(), true);
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for get peer settings: " << status;
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPeerSettingsQuery");
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for report encrypted spam: " << status;
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportEncryptedSpamQuery");
+ td_->messages_manager_->reget_dialog_action_bar(
+ DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id_.get_secret_chat_id())),
+ "ReportEncryptedSpamQuery");
promise_.set_error(std::move(status));
}
};
-class UpdatePeerSettingsQuery : public Td::ResultHandler {
+class ReportPeerQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
- explicit UpdatePeerSettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ReportPeerQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(DialogId dialog_id, bool is_spam_dialog) {
+ void send(DialogId dialog_id, const vector<MessageId> &message_ids, ReportReason &&report_reason) {
dialog_id_ = dialog_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
- if (is_spam_dialog) {
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::messages_reportSpam(std::move(input_peer)))));
+ if (message_ids.empty()) {
+ send_query(G()->net_query_creator().create(telegram_api::account_reportPeer(
+ std::move(input_peer), report_reason.get_input_report_reason(), report_reason.get_message())));
} else {
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::messages_hideReportSpam(std::move(input_peer)))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_report(std::move(input_peer), MessagesManager::get_server_message_ids(message_ids),
+ report_reason.get_input_report_reason(), report_reason.get_message())));
}
}
- void on_result(uint64 id, BufferSlice packet) override {
- static_assert(std::is_same<telegram_api::messages_reportSpam::ReturnType,
- telegram_api::messages_hideReportSpam::ReturnType>::value,
- "");
- auto result_ptr = fetch_result<telegram_api::messages_reportSpam>(packet);
+ void on_result(BufferSlice packet) final {
+ static_assert(
+ std::is_same<telegram_api::account_reportPeer::ReturnType, telegram_api::messages_report::ReturnType>::value,
+ "");
+ auto result_ptr = fetch_result<telegram_api::account_reportPeer>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->messages_manager_->on_get_peer_settings(dialog_id_,
- make_tl_object<telegram_api::peerSettings>(0, false /*ignored*/));
+ bool result = result_ptr.ok();
+ if (!result) {
+ return on_error(Status::Error(400, "Receive false as result"));
+ }
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for update peer settings: " << status;
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "UpdatePeerSettingsQuery");
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for report peer: " << status;
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportPeerQuery");
+ td_->messages_manager_->reget_dialog_action_bar(dialog_id_, "ReportPeerQuery");
promise_.set_error(std::move(status));
}
};
-class ReportEncryptedSpamQuery : public Td::ResultHandler {
+class ReportProfilePhotoQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
+ FileId file_id_;
+ string file_reference_;
+ ReportReason report_reason_;
public:
- explicit ReportEncryptedSpamQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit ReportProfilePhotoQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(DialogId dialog_id) {
+ void send(DialogId dialog_id, FileId file_id, tl_object_ptr<telegram_api::InputPhoto> &&input_photo,
+ ReportReason &&report_reason) {
dialog_id_ = dialog_id;
+ file_id_ = file_id;
+ file_reference_ = FileManager::extract_file_reference(input_photo);
+ report_reason_ = std::move(report_reason);
- auto input_peer = td->messages_manager_->get_input_encrypted_chat(dialog_id, AccessRights::Read);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
- LOG(INFO) << "Report spam in " << to_string(input_peer);
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_reportEncryptedSpam(std::move(input_peer)))));
+ send_query(G()->net_query_creator().create(telegram_api::account_reportProfilePhoto(
+ std::move(input_peer), std::move(input_photo), report_reason_.get_input_report_reason(),
+ report_reason_.get_message())));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::messages_reportEncryptedSpam>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_reportProfilePhoto>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->messages_manager_->on_get_peer_settings(dialog_id_,
- make_tl_object<telegram_api::peerSettings>(0, false /*ignored*/));
+ bool result = result_ptr.ok();
+ if (!result) {
+ return on_error(Status::Error(400, "Receive false as result"));
+ }
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for report encrypted spam: " << status;
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportEncryptedSpamQuery");
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for report chat photo: " << status;
+ if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ VLOG(file_references) << "Receive " << status << " for " << file_id_;
+ td_->file_manager_->delete_file_reference(file_id_, file_reference_);
+ td_->file_reference_manager_->repair_file_reference(
+ file_id_,
+ PromiseCreator::lambda([dialog_id = dialog_id_, file_id = file_id_, report_reason = std::move(report_reason_),
+ promise = std::move(promise_)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ LOG(INFO) << "Reported photo " << file_id << " is likely to be deleted";
+ return promise.set_value(Unit());
+ }
+ send_closure(G()->messages_manager(), &MessagesManager::report_dialog_photo, dialog_id, file_id,
+ std::move(report_reason), std::move(promise));
+ }));
+ return;
+ }
+
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportProfilePhotoQuery");
promise_.set_error(std::move(status));
}
};
-class ReportPeerQuery : public Td::ResultHandler {
+class EditPeerFoldersQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
DialogId dialog_id_;
public:
- explicit ReportPeerQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit EditPeerFoldersQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(DialogId dialog_id, tl_object_ptr<telegram_api::ReportReason> &&report_reason,
- const vector<MessageId> &message_ids) {
+ void send(DialogId dialog_id, FolderId folder_id) {
dialog_id_ = dialog_id;
- auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
CHECK(input_peer != nullptr);
- if (message_ids.empty()) {
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::account_reportPeer(std::move(input_peer), std::move(report_reason)))));
- } else {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_report(
- std::move(input_peer), MessagesManager::get_server_message_ids(message_ids), std::move(report_reason)))));
- }
+ vector<telegram_api::object_ptr<telegram_api::inputFolderPeer>> input_folder_peers;
+ input_folder_peers.push_back(
+ telegram_api::make_object<telegram_api::inputFolderPeer>(std::move(input_peer), folder_id.get()));
+ send_query(G()->net_query_creator().create(telegram_api::folders_editPeerFolders(std::move(input_folder_peers))));
}
- void on_result(uint64 id, BufferSlice packet) override {
- static_assert(
- std::is_same<telegram_api::account_reportPeer::ReturnType, telegram_api::messages_report::ReturnType>::value,
- "");
- auto result_ptr = fetch_result<telegram_api::account_reportPeer>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::folders_editPeerFolders>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- bool result = result_ptr.ok();
- if (!result) {
- return on_error(id, Status::Error(400, "Receive false as result"));
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for EditPeerFoldersQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "EditPeerFoldersQuery")) {
+ LOG(INFO) << "Receive error for EditPeerFoldersQuery: " << status;
}
- promise_.set_value(Unit());
- }
+ // trying to repair folder ID for this dialog
+ td_->messages_manager_->get_dialog_info_full(dialog_id_, Auto(), "EditPeerFoldersQuery");
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for report peer: " << status;
- td->messages_manager_->on_get_dialog_error(dialog_id_, status, "ReportPeerQuery");
promise_.set_error(std::move(status));
}
};
-class GetChannelDifferenceQuery : public Td::ResultHandler {
+class GetChannelDifferenceQuery final : public Td::ResultHandler {
DialogId dialog_id_;
int32 pts_;
int32 limit_;
@@ -3195,30 +4704,30 @@ class GetChannelDifferenceQuery : public Td::ResultHandler {
if (force) {
flags |= telegram_api::updates_getChannelDifference::FORCE_MASK;
}
- send_query(G()->net_query_creator().create(create_storer(telegram_api::updates_getChannelDifference(
+ send_query(G()->net_query_creator().create(telegram_api::updates_getChannelDifference(
flags, false /*ignored*/, std::move(input_channel), make_tl_object<telegram_api::channelMessagesFilterEmpty>(),
- pts, limit))));
+ pts, limit)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::updates_getChannelDifference>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, result_ptr.move_as_ok());
+ td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, result_ptr.move_as_ok());
}
- void on_error(uint64 id, Status status) override {
- if (!td->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChannelDifferenceQuery")) {
- LOG(ERROR) << "updates.getChannelDifference error for " << dialog_id_ << ": " << status;
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetChannelDifferenceQuery")) {
+ LOG(ERROR) << "Receive error for GetChannelDifferenceQuery for " << dialog_id_ << " with pts " << pts_
+ << " and limit " << limit_ << ": " << status;
}
- td->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, nullptr);
- status.ignore();
+ td_->messages_manager_->on_get_channel_difference(dialog_id_, pts_, limit_, nullptr);
}
};
-class ResolveUsernameQuery : public Td::ResultHandler {
+class ResolveUsernameQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
string username_;
@@ -3228,685 +4737,120 @@ class ResolveUsernameQuery : public Td::ResultHandler {
void send(const string &username) {
username_ = username;
-
- LOG(INFO) << "Send ResolveUsernameQuery with username = " << username;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::contacts_resolveUsername(username))));
+ send_query(G()->net_query_creator().create(telegram_api::contacts_resolveUsername(username)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::contacts_resolveUsername>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(DEBUG) << "Receive result for resolveUsername " << to_string(ptr);
- td->contacts_manager_->on_get_users(std::move(ptr->users_));
- td->contacts_manager_->on_get_chats(std::move(ptr->chats_));
+ LOG(DEBUG) << "Receive result for ResolveUsernameQuery: " << to_string(ptr);
+ td_->contacts_manager_->on_get_users(std::move(ptr->users_), "ResolveUsernameQuery");
+ td_->contacts_manager_->on_get_chats(std::move(ptr->chats_), "ResolveUsernameQuery");
- td->messages_manager_->on_resolved_username(username_, DialogId(ptr->peer_));
+ td_->messages_manager_->on_resolved_username(username_, DialogId(ptr->peer_));
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
if (status.message() == Slice("USERNAME_NOT_OCCUPIED")) {
- td->messages_manager_->drop_username(username_);
+ td_->messages_manager_->drop_username(username_);
}
promise_.set_error(std::move(status));
}
};
-class GetChannelAdminLogQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
- ChannelId channel_id_;
- int64 random_id_;
-
+class MessagesManager::UploadMediaCallback final : public FileManager::UploadCallback {
public:
- explicit GetChannelAdminLogQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
- }
-
- void send(ChannelId channel_id, const string &query, int64 from_event_id, int32 limit,
- tl_object_ptr<telegram_api::channelAdminLogEventsFilter> filter,
- vector<tl_object_ptr<telegram_api::InputUser>> input_users, int64 random_id) {
- channel_id_ = channel_id;
- random_id_ = random_id;
-
- auto input_channel = td->contacts_manager_->get_input_channel(channel_id);
- CHECK(input_channel != nullptr);
-
- int32 flags = 0;
- if (filter != nullptr) {
- flags |= telegram_api::channels_getAdminLog::EVENTS_FILTER_MASK;
- }
- if (!input_users.empty()) {
- flags |= telegram_api::channels_getAdminLog::ADMINS_MASK;
- }
-
- send_query(G()->net_query_creator().create(create_storer(telegram_api::channels_getAdminLog(
- flags, std::move(input_channel), query, std::move(filter), std::move(input_users), from_event_id, 0, limit))));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::channels_getAdminLog>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
-
- td->messages_manager_->on_get_event_log(random_id_, result_ptr.move_as_ok());
- promise_.set_value(Unit());
- }
-
- void on_error(uint64 id, Status status) override {
- td->contacts_manager_->on_get_channel_error(channel_id_, status, "GetChannelAdminLogQuery");
- td->messages_manager_->on_get_event_log(random_id_, nullptr);
- promise_.set_error(std::move(status));
- }
-};
-
-bool operator==(const InputMessageText &lhs, const InputMessageText &rhs) {
- return lhs.text == rhs.text && lhs.disable_web_page_preview == rhs.disable_web_page_preview &&
- lhs.clear_draft == rhs.clear_draft;
-}
-
-bool operator!=(const InputMessageText &lhs, const InputMessageText &rhs) {
- return !(lhs == rhs);
-}
-
-class MessagesManager::UploadMediaCallback : public FileManager::UploadCallback {
- public:
- void on_progress(FileId file_id) override {
- }
- void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) override {
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_media, file_id, std::move(input_file),
nullptr);
}
- void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) override {
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_media, file_id, nullptr,
std::move(input_file));
}
- void on_upload_error(FileId file_id, Status error) override {
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
+ UNREACHABLE();
+ }
+ void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_media_error, file_id, std::move(error));
}
};
-class MessagesManager::UploadThumbnailCallback : public FileManager::UploadCallback {
+class MessagesManager::UploadThumbnailCallback final : public FileManager::UploadCallback {
public:
- void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) override {
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_thumbnail, file_id, std::move(input_file));
}
- void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) override {
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
+ UNREACHABLE();
+ }
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
UNREACHABLE();
}
- void on_upload_error(FileId file_id, Status error) override {
+ void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_thumbnail, file_id, nullptr);
}
};
-class MessagesManager::UploadDialogPhotoCallback : public FileManager::UploadCallback {
+class MessagesManager::UploadDialogPhotoCallback final : public FileManager::UploadCallback {
public:
- void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) override {
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_dialog_photo, file_id,
std::move(input_file));
}
- void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) override {
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
UNREACHABLE();
}
- void on_upload_error(FileId file_id, Status error) override {
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
+ UNREACHABLE();
+ }
+ void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_dialog_photo_error, file_id,
std::move(error));
}
};
-template <class StorerT>
-static void store(const MessageContent *content, StorerT &storer) {
- CHECK(content != nullptr);
-
- Td *td = storer.context()->td().get_actor_unsafe();
- CHECK(td != nullptr);
-
- auto content_id = content->get_id();
- store(content_id, storer);
-
- switch (content_id) {
- case MessageAnimation::ID: {
- auto m = static_cast<const MessageAnimation *>(content);
- td->animations_manager_->store_animation(m->file_id, storer);
- store(m->caption, storer);
- break;
- }
- case MessageAudio::ID: {
- auto m = static_cast<const MessageAudio *>(content);
- td->audios_manager_->store_audio(m->file_id, storer);
- store(m->caption, storer);
- store(true, storer);
- break;
- }
- case MessageContact::ID: {
- auto m = static_cast<const MessageContact *>(content);
- store(m->contact, storer);
- break;
- }
- case MessageDocument::ID: {
- auto m = static_cast<const MessageDocument *>(content);
- td->documents_manager_->store_document(m->file_id, storer);
- store(m->caption, storer);
- break;
- }
- case MessageGame::ID: {
- auto m = static_cast<const MessageGame *>(content);
- store(m->game, storer);
- break;
- }
- case MessageInvoice::ID: {
- auto m = static_cast<const MessageInvoice *>(content);
- store(m->title, storer);
- store(m->description, storer);
- store(m->photo, storer);
- store(m->start_parameter, storer);
- store(m->invoice, storer);
- store(m->payload, storer);
- store(m->provider_token, storer);
- store(m->provider_data, storer);
- store(m->total_amount, storer);
- store(m->receipt_message_id, storer);
- break;
- }
- case MessageLiveLocation::ID: {
- auto m = static_cast<const MessageLiveLocation *>(content);
- store(m->location, storer);
- store(m->period, storer);
- break;
- }
- case MessageLocation::ID: {
- auto m = static_cast<const MessageLocation *>(content);
- store(m->location, storer);
- break;
- }
- case MessagePhoto::ID: {
- auto m = static_cast<const MessagePhoto *>(content);
- store(m->photo, storer);
- store(m->caption, storer);
- break;
- }
- case MessageSticker::ID: {
- auto m = static_cast<const MessageSticker *>(content);
- td->stickers_manager_->store_sticker(m->file_id, false, storer);
- break;
- }
- case MessageText::ID: {
- auto m = static_cast<const MessageText *>(content);
- store(m->text, storer);
- store(m->web_page_id, storer);
- break;
- }
- case MessageUnsupported::ID:
- break;
- case MessageVenue::ID: {
- auto m = static_cast<const MessageVenue *>(content);
- store(m->venue, storer);
- break;
- }
- case MessageVideo::ID: {
- auto m = static_cast<const MessageVideo *>(content);
- td->videos_manager_->store_video(m->file_id, storer);
- store(m->caption, storer);
- break;
- }
- case MessageVideoNote::ID: {
- auto m = static_cast<const MessageVideoNote *>(content);
- td->video_notes_manager_->store_video_note(m->file_id, storer);
- store(m->is_viewed, storer);
- break;
- }
- case MessageVoiceNote::ID: {
- auto m = static_cast<const MessageVoiceNote *>(content);
- td->voice_notes_manager_->store_voice_note(m->file_id, storer);
- store(m->caption, storer);
- store(m->is_listened, storer);
- break;
- }
- case MessageChatCreate::ID: {
- auto m = static_cast<const MessageChatCreate *>(content);
- store(m->title, storer);
- store(m->participant_user_ids, storer);
- break;
- }
- case MessageChatChangeTitle::ID: {
- auto m = static_cast<const MessageChatChangeTitle *>(content);
- store(m->title, storer);
- break;
- }
- case MessageChatChangePhoto::ID: {
- auto m = static_cast<const MessageChatChangePhoto *>(content);
- store(m->photo, storer);
- break;
- }
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- break;
- case MessageChatAddUsers::ID: {
- auto m = static_cast<const MessageChatAddUsers *>(content);
- store(m->user_ids, storer);
- break;
- }
- case MessageChatJoinedByLink::ID:
- break;
- case MessageChatDeleteUser::ID: {
- auto m = static_cast<const MessageChatDeleteUser *>(content);
- store(m->user_id, storer);
- break;
- }
- case MessageChatMigrateTo::ID: {
- auto m = static_cast<const MessageChatMigrateTo *>(content);
- store(m->migrated_to_channel_id, storer);
- break;
- }
- case MessageChannelCreate::ID: {
- auto m = static_cast<const MessageChannelCreate *>(content);
- store(m->title, storer);
- break;
- }
- case MessageChannelMigrateFrom::ID: {
- auto m = static_cast<const MessageChannelMigrateFrom *>(content);
- store(m->title, storer);
- store(m->migrated_from_chat_id, storer);
- break;
- }
- case MessagePinMessage::ID: {
- auto m = static_cast<const MessagePinMessage *>(content);
- store(m->message_id, storer);
- break;
- }
- case MessageGameScore::ID: {
- auto m = static_cast<const MessageGameScore *>(content);
- store(m->game_message_id, storer);
- store(m->game_id, storer);
- store(m->score, storer);
- break;
- }
- case MessageScreenshotTaken::ID:
- break;
- case MessageChatSetTtl::ID: {
- auto m = static_cast<const MessageChatSetTtl *>(content);
- store(m->ttl, storer);
- break;
- }
- case MessageCall::ID: {
- auto m = static_cast<const MessageCall *>(content);
- store(m->call_id, storer);
- store(m->duration, storer);
- store(m->discard_reason, storer);
- break;
- }
- case MessagePaymentSuccessful::ID: {
- auto m = static_cast<const MessagePaymentSuccessful *>(content);
- bool has_payload = !m->invoice_payload.empty();
- bool has_shipping_option_id = !m->shipping_option_id.empty();
- bool has_order_info = m->order_info != nullptr;
- bool has_telegram_payment_charge_id = !m->telegram_payment_charge_id.empty();
- bool has_provider_payment_charge_id = !m->provider_payment_charge_id.empty();
- bool has_invoice_message_id = m->invoice_message_id.is_valid();
- BEGIN_STORE_FLAGS();
- STORE_FLAG(has_payload);
- STORE_FLAG(has_shipping_option_id);
- STORE_FLAG(has_order_info);
- STORE_FLAG(has_telegram_payment_charge_id);
- STORE_FLAG(has_provider_payment_charge_id);
- STORE_FLAG(has_invoice_message_id);
- END_STORE_FLAGS();
- store(m->currency, storer);
- store(m->total_amount, storer);
- if (has_payload) {
- store(m->total_amount, storer);
- }
- if (has_shipping_option_id) {
- store(m->invoice_payload, storer);
- }
- if (has_order_info) {
- store(*m->order_info, storer);
- }
- if (has_telegram_payment_charge_id) {
- store(m->telegram_payment_charge_id, storer);
- }
- if (has_provider_payment_charge_id) {
- store(m->provider_payment_charge_id, storer);
- }
- if (has_invoice_message_id) {
- store(m->invoice_message_id, storer);
- }
- break;
- }
- case MessageContactRegistered::ID:
- break;
- case MessageExpiredPhoto::ID:
- break;
- case MessageExpiredVideo::ID:
- break;
- case MessageCustomServiceAction::ID: {
- auto m = static_cast<const MessageCustomServiceAction *>(content);
- store(m->message, storer);
- break;
- }
- case MessageWebsiteConnected::ID: {
- auto m = static_cast<const MessageWebsiteConnected *>(content);
- store(m->domain_name, storer);
- break;
- }
- default:
- UNREACHABLE();
+class MessagesManager::UploadImportedMessagesCallback final : public FileManager::UploadCallback {
+ public:
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages, file_id,
+ std::move(input_file));
}
-}
-
-template <class ParserT>
-static void parse_caption(FormattedText &caption, ParserT &parser) {
- parse(caption.text, parser);
- if (parser.version() >= static_cast<int32>(Version::AddCaptionEntities)) {
- parse(caption.entities, parser);
- } else {
- caption.entities = find_entities(caption.text, false);
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
+ UNREACHABLE();
}
-}
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
+ UNREACHABLE();
+ }
+ void on_upload_error(FileId file_id, Status error) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_messages_error, file_id,
+ std::move(error));
+ }
+};
-template <class ParserT>
-static void parse(unique_ptr<MessageContent> &content, ParserT &parser) {
- Td *td = parser.context()->td().get_actor_unsafe();
- CHECK(td != nullptr);
-
- int32 content_id;
- parse(content_id, parser);
-
- bool is_bad = false;
- switch (content_id) {
- case MessageAnimation::ID: {
- auto m = make_unique<MessageAnimation>();
- m->file_id = td->animations_manager_->parse_animation(parser);
- parse_caption(m->caption, parser);
- is_bad = !m->file_id.is_valid();
- content = std::move(m);
- break;
- }
- case MessageAudio::ID: {
- auto m = make_unique<MessageAudio>();
- m->file_id = td->audios_manager_->parse_audio(parser);
- parse_caption(m->caption, parser);
- bool legacy_is_listened;
- parse(legacy_is_listened, parser);
- is_bad = !m->file_id.is_valid();
- content = std::move(m);
- break;
- }
- case MessageContact::ID: {
- auto m = make_unique<MessageContact>();
- parse(m->contact, parser);
- content = std::move(m);
- break;
- }
- case MessageDocument::ID: {
- auto m = make_unique<MessageDocument>();
- m->file_id = td->documents_manager_->parse_document(parser);
- parse_caption(m->caption, parser);
- is_bad = !m->file_id.is_valid();
- content = std::move(m);
- break;
- }
- case MessageGame::ID: {
- auto m = make_unique<MessageGame>();
- parse(m->game, parser);
- content = std::move(m);
- break;
- }
- case MessageInvoice::ID: {
- auto m = make_unique<MessageInvoice>();
- parse(m->title, parser);
- parse(m->description, parser);
- parse(m->photo, parser);
- parse(m->start_parameter, parser);
- parse(m->invoice, parser);
- parse(m->payload, parser);
- parse(m->provider_token, parser);
- if (parser.version() >= static_cast<int32>(Version::AddMessageInvoiceProviderData)) {
- parse(m->provider_data, parser);
- } else {
- m->provider_data.clear();
- }
- parse(m->total_amount, parser);
- parse(m->receipt_message_id, parser);
- content = std::move(m);
- break;
- }
- case MessageLiveLocation::ID: {
- auto m = make_unique<MessageLiveLocation>();
- parse(m->location, parser);
- parse(m->period, parser);
- content = std::move(m);
- break;
- }
- case MessageLocation::ID: {
- auto m = make_unique<MessageLocation>();
- parse(m->location, parser);
- content = std::move(m);
- break;
- }
- case MessagePhoto::ID: {
- auto m = make_unique<MessagePhoto>();
- parse(m->photo, parser);
- for (auto &photo_size : m->photo.photos) {
- if (!photo_size.file_id.is_valid()) {
- is_bad = true;
- }
- }
- parse_caption(m->caption, parser);
- content = std::move(m);
- break;
- }
- case MessageSticker::ID: {
- auto m = make_unique<MessageSticker>();
- m->file_id = td->stickers_manager_->parse_sticker(false, parser);
- is_bad = !m->file_id.is_valid();
- content = std::move(m);
- break;
- }
- case MessageText::ID: {
- auto m = make_unique<MessageText>();
- parse(m->text, parser);
- parse(m->web_page_id, parser);
- content = std::move(m);
- break;
- }
- case MessageUnsupported::ID:
- content = make_unique<MessageUnsupported>();
- break;
- case MessageVenue::ID: {
- auto m = make_unique<MessageVenue>();
- parse(m->venue, parser);
- content = std::move(m);
- break;
- }
- case MessageVideo::ID: {
- auto m = make_unique<MessageVideo>();
- m->file_id = td->videos_manager_->parse_video(parser);
- parse_caption(m->caption, parser);
- is_bad = !m->file_id.is_valid();
- content = std::move(m);
- break;
- }
- case MessageVideoNote::ID: {
- auto m = make_unique<MessageVideoNote>();
- m->file_id = td->video_notes_manager_->parse_video_note(parser);
- parse(m->is_viewed, parser);
- is_bad = !m->file_id.is_valid();
- content = std::move(m);
- break;
- }
- case MessageVoiceNote::ID: {
- auto m = make_unique<MessageVoiceNote>();
- m->file_id = td->voice_notes_manager_->parse_voice_note(parser);
- parse_caption(m->caption, parser);
- parse(m->is_listened, parser);
- is_bad = !m->file_id.is_valid();
- content = std::move(m);
- break;
- }
- case MessageChatCreate::ID: {
- auto m = make_unique<MessageChatCreate>();
- parse(m->title, parser);
- parse(m->participant_user_ids, parser);
- content = std::move(m);
- break;
- }
- case MessageChatChangeTitle::ID: {
- auto m = make_unique<MessageChatChangeTitle>();
- parse(m->title, parser);
- content = std::move(m);
- break;
- }
- case MessageChatChangePhoto::ID: {
- auto m = make_unique<MessageChatChangePhoto>();
- parse(m->photo, parser);
- content = std::move(m);
- break;
- }
- case MessageChatDeletePhoto::ID:
- content = make_unique<MessageChatDeletePhoto>();
- break;
- case MessageChatDeleteHistory::ID:
- content = make_unique<MessageChatDeleteHistory>();
- break;
- case MessageChatAddUsers::ID: {
- auto m = make_unique<MessageChatAddUsers>();
- parse(m->user_ids, parser);
- content = std::move(m);
- break;
- }
- case MessageChatJoinedByLink::ID:
- content = make_unique<MessageChatJoinedByLink>();
- break;
- case MessageChatDeleteUser::ID: {
- auto m = make_unique<MessageChatDeleteUser>();
- parse(m->user_id, parser);
- content = std::move(m);
- break;
- }
- case MessageChatMigrateTo::ID: {
- auto m = make_unique<MessageChatMigrateTo>();
- parse(m->migrated_to_channel_id, parser);
- content = std::move(m);
- break;
- }
- case MessageChannelCreate::ID: {
- auto m = make_unique<MessageChannelCreate>();
- parse(m->title, parser);
- content = std::move(m);
- break;
- }
- case MessageChannelMigrateFrom::ID: {
- auto m = make_unique<MessageChannelMigrateFrom>();
- parse(m->title, parser);
- parse(m->migrated_from_chat_id, parser);
- content = std::move(m);
- break;
- }
- case MessagePinMessage::ID: {
- auto m = make_unique<MessagePinMessage>();
- parse(m->message_id, parser);
- content = std::move(m);
- break;
- }
- case MessageGameScore::ID: {
- auto m = make_unique<MessageGameScore>();
- parse(m->game_message_id, parser);
- parse(m->game_id, parser);
- parse(m->score, parser);
- content = std::move(m);
- break;
- }
- case MessageScreenshotTaken::ID:
- content = make_unique<MessageScreenshotTaken>();
- break;
- case MessageChatSetTtl::ID: {
- auto m = make_unique<MessageChatSetTtl>();
- parse(m->ttl, parser);
- content = std::move(m);
- break;
- }
- case MessageCall::ID: {
- auto m = make_unique<MessageCall>();
- parse(m->call_id, parser);
- parse(m->duration, parser);
- parse(m->discard_reason, parser);
- content = std::move(m);
- break;
- }
- case MessagePaymentSuccessful::ID: {
- auto m = make_unique<MessagePaymentSuccessful>();
- bool has_payload;
- bool has_shipping_option_id;
- bool has_order_info;
- bool has_telegram_payment_charge_id;
- bool has_provider_payment_charge_id;
- bool has_invoice_message_id;
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(has_payload);
- PARSE_FLAG(has_shipping_option_id);
- PARSE_FLAG(has_order_info);
- PARSE_FLAG(has_telegram_payment_charge_id);
- PARSE_FLAG(has_provider_payment_charge_id);
- PARSE_FLAG(has_invoice_message_id);
- END_PARSE_FLAGS();
- parse(m->currency, parser);
- parse(m->total_amount, parser);
- if (has_payload) {
- parse(m->total_amount, parser);
- }
- if (has_shipping_option_id) {
- parse(m->invoice_payload, parser);
- }
- if (has_order_info) {
- m->order_info = make_unique<OrderInfo>();
- parse(*m->order_info, parser);
- }
- if (has_telegram_payment_charge_id) {
- parse(m->telegram_payment_charge_id, parser);
- }
- if (has_provider_payment_charge_id) {
- parse(m->provider_payment_charge_id, parser);
- }
- if (has_invoice_message_id) {
- parse(m->invoice_message_id, parser);
- }
- content = std::move(m);
- break;
- }
- case MessageContactRegistered::ID:
- content = make_unique<MessageContactRegistered>();
- break;
- case MessageExpiredPhoto::ID:
- content = make_unique<MessageExpiredPhoto>();
- break;
- case MessageExpiredVideo::ID:
- content = make_unique<MessageExpiredVideo>();
- break;
- case MessageCustomServiceAction::ID: {
- auto m = make_unique<MessageCustomServiceAction>();
- parse(m->message, parser);
- content = std::move(m);
- break;
- }
- case MessageWebsiteConnected::ID: {
- auto m = make_unique<MessageWebsiteConnected>();
- parse(m->domain_name, parser);
- content = std::move(m);
- break;
- }
- default:
- UNREACHABLE();
+class MessagesManager::UploadImportedMessageAttachmentCallback final : public FileManager::UploadCallback {
+ public:
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_message_attachment, file_id,
+ std::move(input_file));
}
- if (is_bad) {
- LOG(ERROR) << "Load a message with an invalid content of type " << content_id;
- content = make_unique<MessageUnsupported>();
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
+ UNREACHABLE();
}
-}
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
+ UNREACHABLE();
+ }
+ void on_upload_error(FileId file_id, Status error) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_upload_imported_message_attachment_error, file_id,
+ std::move(error));
+ }
+};
template <class StorerT>
void MessagesManager::Message::store(StorerT &storer) const {
@@ -3915,10 +4859,10 @@ void MessagesManager::Message::store(StorerT &storer) const {
bool has_edit_date = edit_date > 0;
bool has_random_id = random_id != 0;
bool is_forwarded = forward_info != nullptr;
- bool is_reply = reply_to_message_id.is_valid();
+ bool is_reply = reply_to_message_id.is_valid() || reply_to_message_id.is_valid_scheduled();
bool is_reply_to_random_id = reply_to_random_id != 0;
bool is_via_bot = via_bot_user_id.is_valid();
- bool has_views = views > 0;
+ bool has_view_count = view_count > 0;
bool has_reply_markup = reply_markup != nullptr;
bool has_ttl = ttl != 0;
bool has_author_signature = !author_signature.empty();
@@ -3926,6 +4870,33 @@ void MessagesManager::Message::store(StorerT &storer) const {
bool has_media_album_id = media_album_id != 0;
bool has_forward_from =
is_forwarded && (forward_info->from_dialog_id.is_valid() || forward_info->from_message_id.is_valid());
+ bool has_send_date = message_id.is_yet_unsent() && send_date != 0;
+ bool has_flags2 = true;
+ bool has_notification_id = notification_id.is_valid();
+ bool has_forward_sender_name = is_forwarded && !forward_info->sender_name.empty();
+ bool has_send_error_code = send_error_code != 0;
+ bool has_real_forward_from = real_forward_from_dialog_id.is_valid() && real_forward_from_message_id.is_valid();
+ bool has_legacy_layer = legacy_layer != 0;
+ bool has_restriction_reasons = !restriction_reasons.empty();
+ bool has_forward_psa_type = is_forwarded && !forward_info->psa_type.empty();
+ bool has_forward_count = forward_count > 0;
+ bool has_reply_info = !reply_info.is_empty();
+ bool has_sender_dialog_id = sender_dialog_id.is_valid();
+ bool has_reply_in_dialog_id = is_reply && reply_in_dialog_id.is_valid();
+ bool has_top_thread_message_id = top_thread_message_id.is_valid();
+ bool has_thread_draft_message = thread_draft_message != nullptr;
+ bool has_local_thread_message_ids = !local_thread_message_ids.empty();
+ bool has_linked_top_thread_message_id = linked_top_thread_message_id.is_valid();
+ bool has_interaction_info_update_date = interaction_info_update_date != 0;
+ bool has_send_emoji = !send_emoji.empty();
+ bool is_imported = is_forwarded && forward_info->is_imported;
+ bool has_ttl_period = ttl_period != 0;
+ bool has_max_reply_media_timestamp = max_reply_media_timestamp >= 0;
+ bool are_message_media_timestamp_entities_found = true;
+ bool has_flags3 = true;
+ bool has_reactions = reactions != nullptr;
+ bool has_available_reactions_generation = available_reactions_generation != 0;
+ bool has_history_generation = history_generation != 0;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_channel_post);
STORE_FLAG(is_outgoing);
@@ -3944,7 +4915,7 @@ void MessagesManager::Message::store(StorerT &storer) const {
STORE_FLAG(is_reply);
STORE_FLAG(is_reply_to_random_id);
STORE_FLAG(is_via_bot);
- STORE_FLAG(has_views);
+ STORE_FLAG(has_view_count);
STORE_FLAG(has_reply_markup);
STORE_FLAG(has_ttl);
STORE_FLAG(has_author_signature);
@@ -3955,7 +4926,54 @@ void MessagesManager::Message::store(StorerT &storer) const {
STORE_FLAG(has_forward_from);
STORE_FLAG(in_game_share);
STORE_FLAG(is_content_secret);
+ STORE_FLAG(has_send_date);
+ STORE_FLAG(has_flags2);
END_STORE_FLAGS();
+ if (has_flags2) {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_notification_id);
+ STORE_FLAG(is_mention_notification_disabled);
+ STORE_FLAG(had_forward_info);
+ STORE_FLAG(has_forward_sender_name);
+ STORE_FLAG(has_send_error_code);
+ STORE_FLAG(hide_via_bot);
+ STORE_FLAG(is_bot_start_message);
+ STORE_FLAG(has_real_forward_from);
+ STORE_FLAG(has_legacy_layer);
+ STORE_FLAG(hide_edit_date);
+ STORE_FLAG(has_restriction_reasons);
+ STORE_FLAG(is_from_scheduled);
+ STORE_FLAG(is_copy);
+ STORE_FLAG(has_forward_psa_type);
+ STORE_FLAG(has_forward_count);
+ STORE_FLAG(has_reply_info);
+ STORE_FLAG(has_sender_dialog_id);
+ STORE_FLAG(has_reply_in_dialog_id);
+ STORE_FLAG(has_top_thread_message_id);
+ STORE_FLAG(has_thread_draft_message);
+ STORE_FLAG(has_local_thread_message_ids);
+ STORE_FLAG(has_linked_top_thread_message_id);
+ STORE_FLAG(is_pinned);
+ STORE_FLAG(has_interaction_info_update_date);
+ STORE_FLAG(has_send_emoji);
+ STORE_FLAG(is_imported);
+ STORE_FLAG(has_ttl_period);
+ STORE_FLAG(has_max_reply_media_timestamp);
+ STORE_FLAG(are_message_media_timestamp_entities_found);
+ STORE_FLAG(has_flags3);
+ END_STORE_FLAGS();
+ }
+ if (has_flags3) {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(noforwards);
+ STORE_FLAG(has_explicit_sender);
+ STORE_FLAG(has_reactions);
+ STORE_FLAG(has_available_reactions_generation);
+ STORE_FLAG(update_stickersets_order);
+ STORE_FLAG(is_topic_message);
+ STORE_FLAG(has_history_generation);
+ END_STORE_FLAGS();
+ }
store(message_id, storer);
if (has_sender) {
@@ -3965,21 +4983,34 @@ void MessagesManager::Message::store(StorerT &storer) const {
if (has_edit_date) {
store(edit_date, storer);
}
+ if (has_send_date) {
+ store(send_date, storer);
+ }
if (has_random_id) {
store(random_id, storer);
}
if (is_forwarded) {
store(forward_info->sender_user_id, storer);
store(forward_info->date, storer);
- store(forward_info->dialog_id, storer);
+ store(forward_info->sender_dialog_id, storer);
store(forward_info->message_id, storer);
if (has_forward_author_signature) {
store(forward_info->author_signature, storer);
}
+ if (has_forward_sender_name) {
+ store(forward_info->sender_name, storer);
+ }
if (has_forward_from) {
store(forward_info->from_dialog_id, storer);
store(forward_info->from_message_id, storer);
}
+ if (has_forward_psa_type) {
+ store(forward_info->psa_type, storer);
+ }
+ }
+ if (has_real_forward_from) {
+ store(real_forward_from_dialog_id, storer);
+ store(real_forward_from_message_id, storer);
}
if (is_reply) {
store(reply_to_message_id, storer);
@@ -3990,18 +5021,24 @@ void MessagesManager::Message::store(StorerT &storer) const {
if (is_via_bot) {
store(via_bot_user_id, storer);
}
- if (has_views) {
- store(views, storer);
+ if (has_view_count) {
+ store(view_count, storer);
+ }
+ if (has_forward_count) {
+ store(forward_count, storer);
+ }
+ if (has_reply_info) {
+ store(reply_info, storer);
}
if (has_ttl) {
store(ttl, storer);
- double server_time = storer.context()->server_time();
- if (ttl_expires_at == 0) {
- store(-1.0, storer);
- } else {
- double ttl_left = max(ttl_expires_at - Time::now_cached(), 0.0);
- store(ttl_left, storer);
- store(server_time, storer);
+ store_time(ttl_expires_at, storer);
+ }
+ if (has_send_error_code) {
+ store(send_error_code, storer);
+ store(send_error_message, storer);
+ if (send_error_code == 429) {
+ store_time(try_resend_at, storer);
}
}
if (has_author_signature) {
@@ -4010,9 +5047,57 @@ void MessagesManager::Message::store(StorerT &storer) const {
if (has_media_album_id) {
store(media_album_id, storer);
}
- store(static_cast<const MessageContent *>(content.get()), storer); // TODO unique_ptr with const propagation
+ if (has_notification_id) {
+ store(notification_id, storer);
+ }
+ if (has_legacy_layer) {
+ store(legacy_layer, storer);
+ }
+ if (has_restriction_reasons) {
+ store(restriction_reasons, storer);
+ }
+ if (has_sender_dialog_id) {
+ store(sender_dialog_id, storer);
+ }
+ if (has_reply_in_dialog_id) {
+ store(reply_in_dialog_id, storer);
+ }
+ if (has_top_thread_message_id) {
+ store(top_thread_message_id, storer);
+ }
+ if (has_thread_draft_message) {
+ store(thread_draft_message, storer);
+ }
+ if (has_local_thread_message_ids) {
+ store(local_thread_message_ids, storer);
+ }
+ if (has_linked_top_thread_message_id) {
+ store(linked_top_thread_message_id, storer);
+ }
+ if (has_interaction_info_update_date) {
+ store(interaction_info_update_date, storer);
+ }
+ if (has_send_emoji) {
+ store(send_emoji, storer);
+ }
+ store_message_content(content.get(), storer);
if (has_reply_markup) {
- store(*reply_markup, storer);
+ store(reply_markup, storer);
+ }
+ if (has_ttl_period) {
+ store(ttl_period, storer);
+ }
+ if (has_max_reply_media_timestamp) {
+ store(max_reply_media_timestamp, storer);
+ }
+ if (has_reactions) {
+ store(reactions, storer);
+ }
+ if (has_available_reactions_generation) {
+ store(available_reactions_generation, storer);
+ }
+ if (has_history_generation) {
+ store(history_generation, storer);
}
}
@@ -4027,13 +5112,39 @@ void MessagesManager::Message::parse(ParserT &parser) {
bool is_reply;
bool is_reply_to_random_id;
bool is_via_bot;
- bool has_views;
+ bool has_view_count;
bool has_reply_markup;
bool has_ttl;
bool has_author_signature;
bool has_forward_author_signature;
bool has_media_album_id;
bool has_forward_from;
+ bool has_send_date;
+ bool has_flags2;
+ bool has_notification_id = false;
+ bool has_forward_sender_name = false;
+ bool has_send_error_code = false;
+ bool has_real_forward_from = false;
+ bool has_legacy_layer = false;
+ bool has_restriction_reasons = false;
+ bool has_forward_psa_type = false;
+ bool has_forward_count = false;
+ bool has_reply_info = false;
+ bool has_sender_dialog_id = false;
+ bool has_reply_in_dialog_id = false;
+ bool has_top_thread_message_id = false;
+ bool has_thread_draft_message = false;
+ bool has_local_thread_message_ids = false;
+ bool has_linked_top_thread_message_id = false;
+ bool has_interaction_info_update_date = false;
+ bool has_send_emoji = false;
+ bool is_imported = false;
+ bool has_ttl_period = false;
+ bool has_max_reply_media_timestamp = false;
+ bool has_flags3 = false;
+ bool has_reactions = false;
+ bool has_available_reactions_generation = false;
+ bool has_history_generation = false;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_channel_post);
PARSE_FLAG(is_outgoing);
@@ -4052,7 +5163,7 @@ void MessagesManager::Message::parse(ParserT &parser) {
PARSE_FLAG(is_reply);
PARSE_FLAG(is_reply_to_random_id);
PARSE_FLAG(is_via_bot);
- PARSE_FLAG(has_views);
+ PARSE_FLAG(has_view_count);
PARSE_FLAG(has_reply_markup);
PARSE_FLAG(has_ttl);
PARSE_FLAG(has_author_signature);
@@ -4063,7 +5174,54 @@ void MessagesManager::Message::parse(ParserT &parser) {
PARSE_FLAG(has_forward_from);
PARSE_FLAG(in_game_share);
PARSE_FLAG(is_content_secret);
+ PARSE_FLAG(has_send_date);
+ PARSE_FLAG(has_flags2);
END_PARSE_FLAGS();
+ if (has_flags2) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_notification_id);
+ PARSE_FLAG(is_mention_notification_disabled);
+ PARSE_FLAG(had_forward_info);
+ PARSE_FLAG(has_forward_sender_name);
+ PARSE_FLAG(has_send_error_code);
+ PARSE_FLAG(hide_via_bot);
+ PARSE_FLAG(is_bot_start_message);
+ PARSE_FLAG(has_real_forward_from);
+ PARSE_FLAG(has_legacy_layer);
+ PARSE_FLAG(hide_edit_date);
+ PARSE_FLAG(has_restriction_reasons);
+ PARSE_FLAG(is_from_scheduled);
+ PARSE_FLAG(is_copy);
+ PARSE_FLAG(has_forward_psa_type);
+ PARSE_FLAG(has_forward_count);
+ PARSE_FLAG(has_reply_info);
+ PARSE_FLAG(has_sender_dialog_id);
+ PARSE_FLAG(has_reply_in_dialog_id);
+ PARSE_FLAG(has_top_thread_message_id);
+ PARSE_FLAG(has_thread_draft_message);
+ PARSE_FLAG(has_local_thread_message_ids);
+ PARSE_FLAG(has_linked_top_thread_message_id);
+ PARSE_FLAG(is_pinned);
+ PARSE_FLAG(has_interaction_info_update_date);
+ PARSE_FLAG(has_send_emoji);
+ PARSE_FLAG(is_imported);
+ PARSE_FLAG(has_ttl_period);
+ PARSE_FLAG(has_max_reply_media_timestamp);
+ PARSE_FLAG(are_media_timestamp_entities_found);
+ PARSE_FLAG(has_flags3);
+ END_PARSE_FLAGS();
+ }
+ if (has_flags3) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(noforwards);
+ PARSE_FLAG(has_explicit_sender);
+ PARSE_FLAG(has_reactions);
+ PARSE_FLAG(has_available_reactions_generation);
+ PARSE_FLAG(update_stickersets_order);
+ PARSE_FLAG(is_topic_message);
+ PARSE_FLAG(has_history_generation);
+ END_PARSE_FLAGS();
+ }
parse(message_id, parser);
random_y = get_random_y(message_id);
@@ -4074,6 +5232,13 @@ void MessagesManager::Message::parse(ParserT &parser) {
if (has_edit_date) {
parse(edit_date, parser);
}
+ if (has_send_date) {
+ CHECK(message_id.is_valid() || message_id.is_valid_scheduled());
+ CHECK(message_id.is_yet_unsent());
+ parse(send_date, parser);
+ } else if (message_id.is_valid() && message_id.is_yet_unsent()) {
+ send_date = date; // for backward compatibility
+ }
if (has_random_id) {
parse(random_id, parser);
}
@@ -4081,15 +5246,26 @@ void MessagesManager::Message::parse(ParserT &parser) {
forward_info = make_unique<MessageForwardInfo>();
parse(forward_info->sender_user_id, parser);
parse(forward_info->date, parser);
- parse(forward_info->dialog_id, parser);
+ parse(forward_info->sender_dialog_id, parser);
parse(forward_info->message_id, parser);
if (has_forward_author_signature) {
parse(forward_info->author_signature, parser);
}
+ if (has_forward_sender_name) {
+ parse(forward_info->sender_name, parser);
+ }
if (has_forward_from) {
parse(forward_info->from_dialog_id, parser);
parse(forward_info->from_message_id, parser);
}
+ if (has_forward_psa_type) {
+ parse(forward_info->psa_type, parser);
+ }
+ forward_info->is_imported = is_imported;
+ }
+ if (has_real_forward_from) {
+ parse(real_forward_from_dialog_id, parser);
+ parse(real_forward_from_message_id, parser);
}
if (is_reply) {
parse(reply_to_message_id, parser);
@@ -4100,21 +5276,24 @@ void MessagesManager::Message::parse(ParserT &parser) {
if (is_via_bot) {
parse(via_bot_user_id, parser);
}
- if (has_views) {
- parse(views, parser);
+ if (has_view_count) {
+ parse(view_count, parser);
+ }
+ if (has_forward_count) {
+ parse(forward_count, parser);
+ }
+ if (has_reply_info) {
+ parse(reply_info, parser);
}
if (has_ttl) {
parse(ttl, parser);
- double ttl_left;
- parse(ttl_left, parser);
- if (ttl_left < -0.1) {
- ttl_expires_at = 0;
- } else {
- double old_server_time;
- parse(old_server_time, parser);
- double passed_server_time = max(parser.context()->server_time() - old_server_time, 0.0);
- ttl_left = max(ttl_left - passed_server_time, 0.0);
- ttl_expires_at = Time::now_cached() + ttl_left;
+ parse_time(ttl_expires_at, parser);
+ }
+ if (has_send_error_code) {
+ parse(send_error_code, parser);
+ parse(send_error_message, parser);
+ if (send_error_code == 429) {
+ parse_time(try_resend_at, parser);
}
}
if (has_author_signature) {
@@ -4123,82 +5302,87 @@ void MessagesManager::Message::parse(ParserT &parser) {
if (has_media_album_id) {
parse(media_album_id, parser);
}
- parse(content, parser);
+ if (has_notification_id) {
+ parse(notification_id, parser);
+ }
+ if (has_legacy_layer) {
+ parse(legacy_layer, parser);
+ }
+ if (has_restriction_reasons) {
+ parse(restriction_reasons, parser);
+ }
+ if (has_sender_dialog_id) {
+ parse(sender_dialog_id, parser);
+ }
+ if (has_reply_in_dialog_id) {
+ parse(reply_in_dialog_id, parser);
+ }
+ if (has_top_thread_message_id) {
+ parse(top_thread_message_id, parser);
+ }
+ if (has_thread_draft_message) {
+ parse(thread_draft_message, parser);
+ }
+ if (has_local_thread_message_ids) {
+ parse(local_thread_message_ids, parser);
+ }
+ if (has_linked_top_thread_message_id) {
+ parse(linked_top_thread_message_id, parser);
+ }
+ if (has_interaction_info_update_date) {
+ parse(interaction_info_update_date, parser);
+ }
+ if (has_send_emoji) {
+ parse(send_emoji, parser);
+ }
+ parse_message_content(content, parser);
if (has_reply_markup) {
- reply_markup = make_unique<ReplyMarkup>();
- parse(*reply_markup, parser);
+ parse(reply_markup, parser);
}
- is_content_secret |= is_secret_message_content(ttl, content->get_id()); // repair is_content_secret for old messages
-}
-
-template <class StorerT>
-void store(const NotificationSettings &notification_settings, StorerT &storer) {
- bool is_muted = notification_settings.mute_until != 0 && notification_settings.mute_until > G()->unix_time();
- bool has_sound = notification_settings.sound != "default";
- BEGIN_STORE_FLAGS();
- STORE_FLAG(is_muted);
- STORE_FLAG(has_sound);
- STORE_FLAG(notification_settings.show_preview);
- STORE_FLAG(notification_settings.silent_send_message);
- STORE_FLAG(notification_settings.is_synchronized);
- END_STORE_FLAGS();
- if (is_muted) {
- store(notification_settings.mute_until, storer);
+ if (has_ttl_period) {
+ parse(ttl_period, parser);
}
- if (has_sound) {
- store(notification_settings.sound, storer);
+ if (has_max_reply_media_timestamp) {
+ parse(max_reply_media_timestamp, parser);
}
-}
-
-template <class ParserT>
-void parse(NotificationSettings &notification_settings, ParserT &parser) {
- bool is_muted;
- bool has_sound;
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(is_muted);
- PARSE_FLAG(has_sound);
- PARSE_FLAG(notification_settings.show_preview);
- PARSE_FLAG(notification_settings.silent_send_message);
- PARSE_FLAG(notification_settings.is_synchronized);
- END_PARSE_FLAGS();
- if (is_muted) {
- parse(notification_settings.mute_until, parser);
+ if (has_reactions) {
+ parse(reactions, parser);
}
- if (has_sound) {
- parse(notification_settings.sound, parser);
+ if (has_available_reactions_generation) {
+ parse(available_reactions_generation, parser);
+ }
+ if (has_history_generation) {
+ parse(history_generation, parser);
}
-}
-
-template <class StorerT>
-void store(const InputMessageText &input_message_text, StorerT &storer) {
- BEGIN_STORE_FLAGS();
- STORE_FLAG(input_message_text.disable_web_page_preview);
- STORE_FLAG(input_message_text.clear_draft);
- END_STORE_FLAGS();
- store(input_message_text.text, storer);
-}
-template <class ParserT>
-void parse(InputMessageText &input_message_text, ParserT &parser) {
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(input_message_text.disable_web_page_preview);
- PARSE_FLAG(input_message_text.clear_draft);
- END_PARSE_FLAGS();
- parse(input_message_text.text, parser);
+ CHECK(content != nullptr);
+ is_content_secret |=
+ is_secret_message_content(ttl, content->get_type()); // repair is_content_secret for old messages
+ if (hide_edit_date && content->get_type() == MessageContentType::LiveLocation) {
+ hide_edit_date = false;
+ }
}
template <class StorerT>
-void store(const DraftMessage &draft_message, StorerT &storer) {
- store(draft_message.date, storer);
- store(draft_message.reply_to_message_id, storer);
- store(draft_message.input_message_text, storer);
+void MessagesManager::NotificationGroupInfo::store(StorerT &storer) const {
+ using td::store;
+ store(group_id, storer);
+ store(last_notification_date, storer);
+ store(last_notification_id, storer);
+ store(max_removed_notification_id, storer);
+ store(max_removed_message_id, storer);
}
template <class ParserT>
-void parse(DraftMessage &draft_message, ParserT &parser) {
- parse(draft_message.date, parser);
- parse(draft_message.reply_to_message_id, parser);
- parse(draft_message.input_message_text, parser);
+void MessagesManager::NotificationGroupInfo::parse(ParserT &parser) {
+ using td::parse;
+ parse(group_id, parser);
+ parse(last_notification_date, parser);
+ parse(last_notification_id, parser);
+ parse(max_removed_notification_id, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddNotificationGroupInfoMaxRemovedMessageId)) {
+ parse(max_removed_message_id, parser);
+ }
}
template <class StorerT>
@@ -4209,10 +5393,10 @@ void MessagesManager::Dialog::store(StorerT &storer) const {
last_database_message = get_message(this, last_database_message_id);
}
+ auto dialog_type = dialog_id.get_type();
bool has_draft_message = draft_message != nullptr;
bool has_last_database_message = last_database_message != nullptr;
bool has_first_database_message_id = first_database_message_id.is_valid();
- bool is_pinned = pinned_order != DEFAULT_ORDER;
bool has_first_database_message_id_by_index = true;
bool has_message_count_by_index = true;
bool has_client_data = !client_data.empty();
@@ -4221,13 +5405,41 @@ void MessagesManager::Dialog::store(StorerT &storer) const {
bool has_local_unread_count = local_unread_count != 0;
bool has_deleted_last_message = delete_last_message_date > 0;
bool has_last_clear_history_message_id = last_clear_history_message_id.is_valid();
+ bool has_last_database_message_id = !has_last_database_message && last_database_message_id.is_valid();
+ bool has_message_notification_group =
+ message_notification_group.group_id.is_valid() && !message_notification_group.try_reuse;
+ bool has_mention_notification_group =
+ mention_notification_group.group_id.is_valid() && !mention_notification_group.try_reuse;
+ bool has_new_secret_chat_notification_id = new_secret_chat_notification_id.is_valid();
+ bool has_pinned_message_notification = pinned_message_notification_message_id.is_valid();
+ bool has_last_pinned_message_id = last_pinned_message_id.is_valid();
+ bool has_flags2 = true;
+ bool has_max_notification_message_id =
+ max_notification_message_id.is_valid() && max_notification_message_id > last_new_message_id;
+ bool has_folder_id = folder_id != FolderId();
+ bool has_pending_read_channel_inbox = pending_read_channel_inbox_pts != 0;
+ bool has_last_yet_unsent_message = last_message_id.is_valid() && last_message_id.is_yet_unsent();
+ bool has_active_group_call_id = active_group_call_id.is_valid();
+ bool has_message_ttl = !message_ttl.is_empty();
+ bool has_default_join_group_call_as_dialog_id = default_join_group_call_as_dialog_id.is_valid();
+ bool store_has_bots = dialog_type == DialogType::Chat || dialog_type == DialogType::Channel;
+ bool has_theme_name = !theme_name.empty();
+ bool has_flags3 = true;
+ bool has_pending_join_requests = pending_join_request_count != 0;
+ bool has_action_bar = action_bar != nullptr;
+ bool has_default_send_message_as_dialog_id = default_send_message_as_dialog_id.is_valid();
+ bool has_legacy_available_reactions = false;
+ bool has_available_reactions_generation = available_reactions_generation != 0;
+ bool has_have_full_history_source = have_full_history && have_full_history_source != 0;
+ bool has_available_reactions = !available_reactions.empty();
+ bool has_history_generation = history_generation != 0;
BEGIN_STORE_FLAGS();
STORE_FLAG(has_draft_message);
STORE_FLAG(has_last_database_message);
- STORE_FLAG(know_can_report_spam);
- STORE_FLAG(can_report_spam);
+ STORE_FLAG(false); // legacy_know_can_report_spam
+ STORE_FLAG(false); // action_bar->can_report_spam
STORE_FLAG(has_first_database_message_id);
- STORE_FLAG(is_pinned);
+ STORE_FLAG(false); // legacy_is_pinned
STORE_FLAG(has_first_database_message_id_by_index);
STORE_FLAG(has_message_count_by_index);
STORE_FLAG(has_client_data);
@@ -4242,9 +5454,70 @@ void MessagesManager::Dialog::store(StorerT &storer) const {
STORE_FLAG(has_last_clear_history_message_id);
STORE_FLAG(is_last_message_deleted_locally);
STORE_FLAG(has_contact_registered_message);
+ STORE_FLAG(has_last_database_message_id);
+ STORE_FLAG(need_repair_server_unread_count);
+ STORE_FLAG(is_marked_as_unread);
+ STORE_FLAG(has_message_notification_group);
+ STORE_FLAG(has_mention_notification_group);
+ STORE_FLAG(has_new_secret_chat_notification_id);
+ STORE_FLAG(has_pinned_message_notification);
+ STORE_FLAG(has_last_pinned_message_id);
+ STORE_FLAG(is_last_pinned_message_id_inited);
+ STORE_FLAG(has_flags2);
END_STORE_FLAGS();
store(dialog_id, storer); // must be stored at offset 4
+
+ if (has_flags2) {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_max_notification_message_id);
+ STORE_FLAG(has_folder_id);
+ STORE_FLAG(is_folder_id_inited);
+ STORE_FLAG(has_pending_read_channel_inbox);
+ STORE_FLAG(know_action_bar);
+ STORE_FLAG(false); // action_bar->can_add_contact
+ STORE_FLAG(false); // action_bar->can_block_user
+ STORE_FLAG(false); // action_bar->can_share_phone_number
+ STORE_FLAG(false); // action_bar->can_report_location
+ STORE_FLAG(has_scheduled_server_messages);
+ STORE_FLAG(has_scheduled_database_messages);
+ STORE_FLAG(need_repair_channel_server_unread_count);
+ STORE_FLAG(false); // action_bar->can_unarchive
+ STORE_FLAG(false); // action_bar_has_distance
+ STORE_FLAG(has_outgoing_messages);
+ STORE_FLAG(has_last_yet_unsent_message);
+ STORE_FLAG(is_blocked);
+ STORE_FLAG(is_is_blocked_inited);
+ STORE_FLAG(has_active_group_call);
+ STORE_FLAG(is_group_call_empty);
+ STORE_FLAG(has_active_group_call_id);
+ STORE_FLAG(false); // action_bar->can_invite_members
+ STORE_FLAG(has_message_ttl);
+ STORE_FLAG(is_message_ttl_inited);
+ STORE_FLAG(has_default_join_group_call_as_dialog_id);
+ STORE_FLAG(store_has_bots ? has_bots : false);
+ STORE_FLAG(store_has_bots ? is_has_bots_inited : false);
+ STORE_FLAG(is_theme_name_inited);
+ STORE_FLAG(has_theme_name);
+ STORE_FLAG(has_flags3);
+ END_STORE_FLAGS();
+ }
+ if (has_flags3) {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_pending_join_requests);
+ STORE_FLAG(need_repair_action_bar);
+ STORE_FLAG(has_action_bar);
+ STORE_FLAG(has_default_send_message_as_dialog_id);
+ STORE_FLAG(need_drop_default_send_message_as_dialog_id);
+ STORE_FLAG(has_legacy_available_reactions);
+ STORE_FLAG(is_available_reactions_inited);
+ STORE_FLAG(has_available_reactions_generation);
+ STORE_FLAG(has_have_full_history_source);
+ STORE_FLAG(has_available_reactions);
+ STORE_FLAG(has_history_generation);
+ END_STORE_FLAGS();
+ }
+
store(last_new_message_id, storer);
store(server_unread_count, storer);
if (has_local_unread_count) {
@@ -4255,7 +5528,7 @@ void MessagesManager::Dialog::store(StorerT &storer) const {
store(reply_markup_message_id, storer);
store(notification_settings, storer);
if (has_draft_message) {
- store(*draft_message, storer);
+ store(draft_message, storer);
}
store(last_clear_history_date, storer);
store(order, storer);
@@ -4265,9 +5538,6 @@ void MessagesManager::Dialog::store(StorerT &storer) const {
if (has_first_database_message_id) {
store(first_database_message_id, storer);
}
- if (is_pinned) {
- store(pinned_order, storer);
- }
if (has_deleted_last_message) {
store(delete_last_message_date, storer);
store(deleted_last_message_id, storer);
@@ -4297,6 +5567,69 @@ void MessagesManager::Dialog::store(StorerT &storer) const {
if (has_max_unavailable_message_id) {
store(max_unavailable_message_id, storer);
}
+ if (has_last_database_message_id) {
+ store(last_database_message_id, storer);
+ }
+ if (has_message_notification_group) {
+ store(message_notification_group, storer);
+ }
+ if (has_mention_notification_group) {
+ store(mention_notification_group, storer);
+ }
+ if (has_new_secret_chat_notification_id) {
+ store(new_secret_chat_notification_id, storer);
+ }
+ if (has_pinned_message_notification) {
+ store(pinned_message_notification_message_id, storer);
+ }
+ if (has_last_pinned_message_id) {
+ store(last_pinned_message_id, storer);
+ }
+ if (has_max_notification_message_id) {
+ store(max_notification_message_id, storer);
+ }
+ if (has_folder_id) {
+ store(folder_id, storer);
+ }
+ if (has_pending_read_channel_inbox) {
+ store(pending_read_channel_inbox_pts, storer);
+ store(pending_read_channel_inbox_max_message_id, storer);
+ store(pending_read_channel_inbox_server_unread_count, storer);
+ }
+ if (has_active_group_call_id) {
+ store(active_group_call_id, storer);
+ }
+ if (has_message_ttl) {
+ store(message_ttl, storer);
+ }
+ if (has_default_join_group_call_as_dialog_id) {
+ store(default_join_group_call_as_dialog_id, storer);
+ }
+ if (has_theme_name) {
+ store(theme_name, storer);
+ }
+ if (has_pending_join_requests) {
+ store(pending_join_request_count, storer);
+ store(pending_join_request_user_ids, storer);
+ }
+ if (has_action_bar) {
+ store(action_bar, storer);
+ }
+ if (has_default_send_message_as_dialog_id) {
+ store(default_send_message_as_dialog_id, storer);
+ }
+ if (has_available_reactions) {
+ store(available_reactions, storer);
+ }
+ if (has_available_reactions_generation) {
+ store(available_reactions_generation, storer);
+ }
+ if (has_have_full_history_source) {
+ store(have_full_history_source, storer);
+ }
+ if (has_history_generation) {
+ store(history_generation, storer);
+ }
}
// do not forget to resolve dialog dependencies including dependencies of last_message
@@ -4305,8 +5638,9 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
using td::parse;
bool has_draft_message;
bool has_last_database_message;
+ bool legacy_know_can_report_spam;
bool has_first_database_message_id;
- bool is_pinned;
+ bool legacy_is_pinned;
bool has_first_database_message_id_by_index;
bool has_message_count_by_index;
bool has_client_data;
@@ -4315,13 +5649,44 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
bool has_local_unread_count;
bool has_deleted_last_message;
bool has_last_clear_history_message_id;
+ bool has_last_database_message_id;
+ bool has_message_notification_group;
+ bool has_mention_notification_group;
+ bool has_new_secret_chat_notification_id;
+ bool has_pinned_message_notification;
+ bool has_last_pinned_message_id;
+ bool has_flags2;
+ bool has_max_notification_message_id = false;
+ bool has_folder_id = false;
+ bool has_pending_read_channel_inbox = false;
+ bool has_active_group_call_id = false;
+ bool has_message_ttl = false;
+ bool has_default_join_group_call_as_dialog_id = false;
+ bool has_theme_name = false;
+ bool has_flags3 = false;
+ bool has_pending_join_requests = false;
+ bool action_bar_can_report_spam = false;
+ bool action_bar_can_add_contact = false;
+ bool action_bar_can_block_user = false;
+ bool action_bar_can_share_phone_number = false;
+ bool action_bar_can_report_location = false;
+ bool action_bar_can_unarchive = false;
+ bool action_bar_has_distance = false;
+ bool action_bar_can_invite_members = false;
+ bool has_action_bar = false;
+ bool has_default_send_message_as_dialog_id = false;
+ bool has_legacy_available_reactions = false;
+ bool has_available_reactions_generation = false;
+ bool has_have_full_history_source = false;
+ bool has_available_reactions = false;
+ bool has_history_generation = false;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_draft_message);
PARSE_FLAG(has_last_database_message);
- PARSE_FLAG(know_can_report_spam);
- PARSE_FLAG(can_report_spam);
+ PARSE_FLAG(legacy_know_can_report_spam);
+ PARSE_FLAG(action_bar_can_report_spam);
PARSE_FLAG(has_first_database_message_id);
- PARSE_FLAG(is_pinned);
+ PARSE_FLAG(legacy_is_pinned);
PARSE_FLAG(has_first_database_message_id_by_index);
PARSE_FLAG(has_message_count_by_index);
PARSE_FLAG(has_client_data);
@@ -4336,9 +5701,88 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
PARSE_FLAG(has_last_clear_history_message_id);
PARSE_FLAG(is_last_message_deleted_locally);
PARSE_FLAG(has_contact_registered_message);
+ PARSE_FLAG(has_last_database_message_id);
+ PARSE_FLAG(need_repair_server_unread_count);
+ PARSE_FLAG(is_marked_as_unread);
+ PARSE_FLAG(has_message_notification_group);
+ PARSE_FLAG(has_mention_notification_group);
+ PARSE_FLAG(has_new_secret_chat_notification_id);
+ PARSE_FLAG(has_pinned_message_notification);
+ PARSE_FLAG(has_last_pinned_message_id);
+ PARSE_FLAG(is_last_pinned_message_id_inited);
+ PARSE_FLAG(has_flags2);
END_PARSE_FLAGS();
parse(dialog_id, parser); // must be stored at offset 4
+
+ if (has_flags2) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_max_notification_message_id);
+ PARSE_FLAG(has_folder_id);
+ PARSE_FLAG(is_folder_id_inited);
+ PARSE_FLAG(has_pending_read_channel_inbox);
+ PARSE_FLAG(know_action_bar);
+ PARSE_FLAG(action_bar_can_add_contact);
+ PARSE_FLAG(action_bar_can_block_user);
+ PARSE_FLAG(action_bar_can_share_phone_number);
+ PARSE_FLAG(action_bar_can_report_location);
+ PARSE_FLAG(has_scheduled_server_messages);
+ PARSE_FLAG(has_scheduled_database_messages);
+ PARSE_FLAG(need_repair_channel_server_unread_count);
+ PARSE_FLAG(action_bar_can_unarchive);
+ PARSE_FLAG(action_bar_has_distance);
+ PARSE_FLAG(has_outgoing_messages);
+ PARSE_FLAG(had_last_yet_unsent_message);
+ PARSE_FLAG(is_blocked);
+ PARSE_FLAG(is_is_blocked_inited);
+ PARSE_FLAG(has_active_group_call);
+ PARSE_FLAG(is_group_call_empty);
+ PARSE_FLAG(has_active_group_call_id);
+ PARSE_FLAG(action_bar_can_invite_members);
+ PARSE_FLAG(has_message_ttl);
+ PARSE_FLAG(is_message_ttl_inited);
+ PARSE_FLAG(has_default_join_group_call_as_dialog_id);
+ PARSE_FLAG(has_bots);
+ PARSE_FLAG(is_has_bots_inited);
+ PARSE_FLAG(is_theme_name_inited);
+ PARSE_FLAG(has_theme_name);
+ PARSE_FLAG(has_flags3);
+ END_PARSE_FLAGS();
+ } else {
+ is_folder_id_inited = false;
+ has_scheduled_server_messages = false;
+ has_scheduled_database_messages = false;
+ need_repair_channel_server_unread_count = false;
+ has_outgoing_messages = false;
+ had_last_yet_unsent_message = false;
+ is_blocked = false;
+ is_is_blocked_inited = false;
+ has_active_group_call = false;
+ is_group_call_empty = false;
+ is_message_ttl_inited = false;
+ has_bots = false;
+ is_has_bots_inited = false;
+ is_theme_name_inited = false;
+ }
+ if (has_flags3) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_pending_join_requests);
+ PARSE_FLAG(need_repair_action_bar);
+ PARSE_FLAG(has_action_bar);
+ PARSE_FLAG(has_default_send_message_as_dialog_id);
+ PARSE_FLAG(need_drop_default_send_message_as_dialog_id);
+ PARSE_FLAG(has_legacy_available_reactions);
+ PARSE_FLAG(is_available_reactions_inited);
+ PARSE_FLAG(has_available_reactions_generation);
+ PARSE_FLAG(has_have_full_history_source);
+ PARSE_FLAG(has_available_reactions);
+ PARSE_FLAG(has_history_generation);
+ END_PARSE_FLAGS();
+ } else {
+ need_repair_action_bar = false;
+ is_available_reactions_inited = false;
+ }
+
parse(last_new_message_id, parser);
parse(server_unread_count, parser);
if (has_local_unread_count) {
@@ -4355,20 +5799,19 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
parse(reply_markup_message_id, parser);
parse(notification_settings, parser);
if (has_draft_message) {
- draft_message = make_unique<DraftMessage>();
- parse(*draft_message, parser);
+ parse(draft_message, parser);
}
parse(last_clear_history_date, parser);
parse(order, parser);
if (has_last_database_message) {
- messages = make_unique<Message>();
- parse(*messages, parser);
+ parse(messages, parser);
}
if (has_first_database_message_id) {
parse(first_database_message_id, parser);
}
- if (is_pinned) {
- parse(pinned_order, parser);
+ if (legacy_is_pinned) {
+ int64 legacy_pinned_order;
+ parse(legacy_pinned_order, parser);
}
if (has_deleted_last_message) {
parse(delete_last_message_date, parser);
@@ -4381,7 +5824,13 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
if (has_first_database_message_id_by_index) {
int32 size;
parse(size, parser);
- CHECK(static_cast<size_t>(size) <= first_database_message_id_by_index.size())
+ if (size < 0) {
+ // the log event is broken
+ // it should be impossible, but has happenned at least once
+ parser.set_error("Wrong first_database_message_id_by_index table size");
+ return;
+ }
+ LOG_CHECK(static_cast<size_t>(size) <= first_database_message_id_by_index.size())
<< size << " " << first_database_message_id_by_index.size();
for (int32 i = 0; i < size; i++) {
parse(first_database_message_id_by_index[i], parser);
@@ -4390,16 +5839,28 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
if (has_message_count_by_index) {
int32 size;
parse(size, parser);
- CHECK(static_cast<size_t>(size) <= message_count_by_index.size());
+ if (size < 0) {
+ // the log event is broken
+ // it should be impossible, but has happenned at least once
+ parser.set_error("Wrong message_count_by_index table size");
+ return;
+ }
+ LOG_CHECK(static_cast<size_t>(size) <= message_count_by_index.size())
+ << size << " " << message_count_by_index.size();
for (int32 i = 0; i < size; i++) {
parse(message_count_by_index[i], parser);
}
}
- unread_mention_count = message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)];
+ unread_mention_count = message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadMention)];
LOG(INFO) << "Set unread mention message count in " << dialog_id << " to " << unread_mention_count;
if (unread_mention_count < 0) {
unread_mention_count = 0;
}
+ unread_reaction_count = message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadReaction)];
+ LOG(INFO) << "Set unread reaction count in " << dialog_id << " to " << unread_reaction_count;
+ if (unread_reaction_count < 0) {
+ unread_reaction_count = 0;
+ }
if (has_client_data) {
parse(client_data, parser);
}
@@ -4409,33 +5870,114 @@ void MessagesManager::Dialog::parse(ParserT &parser) {
if (has_max_unavailable_message_id) {
parse(max_unavailable_message_id, parser);
}
+ if (has_last_database_message_id) {
+ parse(last_database_message_id, parser);
+ }
+ if (has_message_notification_group) {
+ parse(message_notification_group, parser);
+ }
+ if (has_mention_notification_group) {
+ parse(mention_notification_group, parser);
+ }
+ if (has_new_secret_chat_notification_id) {
+ parse(new_secret_chat_notification_id, parser);
+ }
+ if (has_pinned_message_notification) {
+ parse(pinned_message_notification_message_id, parser);
+ }
+ if (has_last_pinned_message_id) {
+ parse(last_pinned_message_id, parser);
+ }
+ if (has_max_notification_message_id) {
+ parse(max_notification_message_id, parser);
+ }
+ if (has_folder_id) {
+ parse(folder_id, parser);
+ }
+ if (has_pending_read_channel_inbox) {
+ parse(pending_read_channel_inbox_pts, parser);
+ parse(pending_read_channel_inbox_max_message_id, parser);
+ parse(pending_read_channel_inbox_server_unread_count, parser);
+ }
+ int32 action_bar_distance = -1;
+ if (action_bar_has_distance) {
+ parse(action_bar_distance, parser);
+ }
+ if (has_active_group_call_id) {
+ parse(active_group_call_id, parser);
+ }
+ if (has_message_ttl) {
+ parse(message_ttl, parser);
+ }
+ if (has_default_join_group_call_as_dialog_id) {
+ parse(default_join_group_call_as_dialog_id, parser);
+ }
+ if (has_theme_name) {
+ parse(theme_name, parser);
+ }
+ if (has_pending_join_requests) {
+ parse(pending_join_request_count, parser);
+ parse(pending_join_request_user_ids, parser);
+ }
+ if (has_action_bar) {
+ parse(action_bar, parser);
+ }
+ if (has_default_send_message_as_dialog_id) {
+ parse(default_send_message_as_dialog_id, parser);
+ }
+ if (has_available_reactions) {
+ parse(available_reactions, parser);
+ } else if (has_legacy_available_reactions) {
+ vector<string> legacy_available_reactions;
+ parse(legacy_available_reactions, parser);
+ available_reactions = ChatReactions(std::move(legacy_available_reactions));
+ }
+ if (has_available_reactions_generation) {
+ parse(available_reactions_generation, parser);
+ }
+ if (has_have_full_history_source) {
+ parse(have_full_history_source, parser);
+ }
+ if (has_history_generation) {
+ parse(history_generation, parser);
+ }
+
+ (void)legacy_know_can_report_spam;
+ if (know_action_bar && !has_action_bar) {
+ action_bar = DialogActionBar::create(
+ action_bar_can_report_spam, action_bar_can_add_contact, action_bar_can_block_user,
+ action_bar_can_share_phone_number, action_bar_can_report_location, action_bar_can_unarchive,
+ has_outgoing_messages ? -1 : action_bar_distance, action_bar_can_invite_members, string(), false, 0);
+ }
}
template <class StorerT>
-void store(const CallsDbState &state, StorerT &storer) {
- store(static_cast<int32>(state.first_calls_database_message_id_by_index.size()), storer);
- for (auto first_message_id : state.first_calls_database_message_id_by_index) {
+void MessagesManager::CallsDbState::store(StorerT &storer) const {
+ using td::store;
+ store(static_cast<int32>(first_calls_database_message_id_by_index.size()), storer);
+ for (auto first_message_id : first_calls_database_message_id_by_index) {
store(first_message_id, storer);
}
- store(static_cast<int32>(state.message_count_by_index.size()), storer);
- for (auto message_count : state.message_count_by_index) {
+ store(static_cast<int32>(message_count_by_index.size()), storer);
+ for (auto message_count : message_count_by_index) {
store(message_count, storer);
}
}
template <class ParserT>
-void parse(CallsDbState &state, ParserT &parser) {
+void MessagesManager::CallsDbState::parse(ParserT &parser) {
+ using td::parse;
int32 size;
parse(size, parser);
- CHECK(static_cast<size_t>(size) <= state.first_calls_database_message_id_by_index.size())
- << size << " " << state.first_calls_database_message_id_by_index.size();
+ LOG_CHECK(static_cast<size_t>(size) <= first_calls_database_message_id_by_index.size())
+ << size << " " << first_calls_database_message_id_by_index.size();
for (int32 i = 0; i < size; i++) {
- parse(state.first_calls_database_message_id_by_index[i], parser);
+ parse(first_calls_database_message_id_by_index[i], parser);
}
parse(size, parser);
- CHECK(static_cast<size_t>(size) <= state.message_count_by_index.size());
+ LOG_CHECK(static_cast<size_t>(size) <= message_count_by_index.size()) << size << " " << message_count_by_index.size();
for (int32 i = 0; i < size; i++) {
- parse(state.message_count_by_index[i], parser);
+ parse(message_count_by_index[i], parser);
}
}
@@ -4467,18 +6009,16 @@ void MessagesManager::save_calls_db_state() {
G()->td_db()->get_sqlite_pmc()->set("calls_db_state", log_event_store(calls_db_state_).as_slice().str(), Auto());
}
-MessagesManager::Dialog::~Dialog() {
- if (!G()->close_flag()) {
- LOG(ERROR) << "Destroy " << dialog_id;
- }
-}
-
-MessagesManager::MessagesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
- // td_->create_handler<GetDialogListQuery>()->send(2147000000, ServerMessageId(), DialogId(), 5);
-
+MessagesManager::MessagesManager(Td *td, ActorShared<> parent)
+ : recently_found_dialogs_{td, "recently_found", MAX_RECENT_DIALOGS}
+ , recently_opened_dialogs_{td, "recently_opened", MAX_RECENT_DIALOGS}
+ , td_(td)
+ , parent_(std::move(parent)) {
upload_media_callback_ = std::make_shared<UploadMediaCallback>();
upload_thumbnail_callback_ = std::make_shared<UploadThumbnailCallback>();
upload_dialog_photo_callback_ = std::make_shared<UploadDialogPhotoCallback>();
+ upload_imported_messages_callback_ = std::make_shared<UploadImportedMessagesCallback>();
+ upload_imported_message_attachment_callback_ = std::make_shared<UploadImportedMessageAttachmentCallback>();
channel_get_difference_timeout_.set_callback(on_channel_get_difference_timeout_callback);
channel_get_difference_timeout_.set_callback_data(static_cast<void *>(this));
@@ -4489,9 +6029,15 @@ MessagesManager::MessagesManager(Td *td, ActorShared<> parent) : td_(td), parent
pending_message_views_timeout_.set_callback(on_pending_message_views_timeout_callback);
pending_message_views_timeout_.set_callback_data(static_cast<void *>(this));
+ pending_message_live_location_view_timeout_.set_callback(on_pending_message_live_location_view_timeout_callback);
+ pending_message_live_location_view_timeout_.set_callback_data(static_cast<void *>(this));
+
pending_draft_message_timeout_.set_callback(on_pending_draft_message_timeout_callback);
pending_draft_message_timeout_.set_callback_data(static_cast<void *>(this));
+ pending_read_history_timeout_.set_callback(on_pending_read_history_timeout_callback);
+ pending_read_history_timeout_.set_callback_data(static_cast<void *>(this));
+
pending_updated_dialog_timeout_.set_callback(on_pending_updated_dialog_timeout_callback);
pending_updated_dialog_timeout_.set_callback_data(static_cast<void *>(this));
@@ -4507,50 +6053,28 @@ MessagesManager::MessagesManager(Td *td, ActorShared<> parent) : td_(td), parent
active_dialog_action_timeout_.set_callback(on_active_dialog_action_timeout_callback);
active_dialog_action_timeout_.set_callback_data(static_cast<void *>(this));
- sequence_dispatcher_ = create_actor<MultiSequenceDispatcher>("multi sequence dispatcher");
+ update_dialog_online_member_count_timeout_.set_callback(on_update_dialog_online_member_count_timeout_callback);
+ update_dialog_online_member_count_timeout_.set_callback_data(static_cast<void *>(this));
- if (G()->parameters().use_message_db) {
- auto last_database_server_dialog_date_string = G()->td_db()->get_binlog_pmc()->get("last_server_dialog_date");
- if (!last_database_server_dialog_date_string.empty()) {
- string order_str;
- string dialog_id_str;
- std::tie(order_str, dialog_id_str) = split(last_database_server_dialog_date_string);
+ preload_folder_dialog_list_timeout_.set_callback(on_preload_folder_dialog_list_timeout_callback);
+ preload_folder_dialog_list_timeout_.set_callback_data(static_cast<void *>(this));
- auto r_order = to_integer_safe<int64>(order_str);
- auto r_dialog_id = to_integer_safe<int64>(dialog_id_str);
- if (r_order.is_error() || r_dialog_id.is_error()) {
- LOG(ERROR) << "Can't parse " << last_database_server_dialog_date_string;
- } else {
- last_database_server_dialog_date_ = DialogDate(r_order.ok(), DialogId(r_dialog_id.ok()));
- }
- }
- LOG(INFO) << "Load last_database_server_dialog_date_ = " << last_database_server_dialog_date_;
-
- auto unread_message_count_string = G()->td_db()->get_binlog_pmc()->get("unread_message_count");
- if (!unread_message_count_string.empty()) {
- string total_count;
- string muted_count;
- std::tie(total_count, muted_count) = split(unread_message_count_string);
-
- auto r_total_count = to_integer_safe<int32>(total_count);
- auto r_muted_count = to_integer_safe<int32>(muted_count);
- if (r_total_count.is_error() || r_muted_count.is_error()) {
- LOG(ERROR) << "Can't parse " << unread_message_count_string;
- } else {
- unread_message_total_count_ = r_total_count.ok();
- unread_message_muted_count_ = r_muted_count.ok();
- is_unread_count_inited_ = true;
- send_update_unread_message_count(DialogId(), true, "load unread_message_count");
- }
- }
- LOG(INFO) << "Load last_database_server_dialog_date_ = " << last_database_server_dialog_date_;
- } else {
- G()->td_db()->get_binlog_pmc()->erase("last_server_dialog_date");
- G()->td_db()->get_binlog_pmc()->erase("unread_message_count");
- }
+ update_viewed_messages_timeout_.set_callback(on_update_viewed_messages_timeout_callback);
+ update_viewed_messages_timeout_.set_callback_data(static_cast<void *>(this));
}
-MessagesManager::~MessagesManager() = default;
+MessagesManager::~MessagesManager() {
+ Scheduler::instance()->destroy_on_scheduler(
+ G()->get_gc_scheduler_id(), ttl_nodes_, ttl_heap_, being_sent_messages_, update_message_ids_,
+ update_scheduled_message_ids_, message_id_to_dialog_id_, last_clear_history_message_id_to_dialog_id_, dialogs_,
+ postponed_chat_read_inbox_updates_, found_public_dialogs_, found_on_server_dialogs_, found_common_dialogs_,
+ message_embedding_codes_[0], message_embedding_codes_[1], replied_by_media_timestamp_messages_,
+ notification_group_id_to_dialog_id_, active_get_channel_differencies_, get_channel_difference_to_log_event_id_,
+ channel_get_difference_retry_timeouts_, is_channel_difference_finished_, resolved_usernames_,
+ inaccessible_resolved_usernames_, dialog_bot_command_message_ids_, full_message_id_to_file_source_id_,
+ last_outgoing_forwarded_message_date_, dialog_viewed_messages_, dialog_online_member_counts_,
+ previous_repaired_read_inbox_max_message_id_);
+}
void MessagesManager::on_channel_get_difference_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
if (G()->close_flag()) {
@@ -4558,11 +6082,8 @@ void MessagesManager::on_channel_get_difference_timeout_callback(void *messages_
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
- DialogId dialog_id(dialog_id_int);
- CHECK(dialog_id.get_type() == DialogType::Channel);
- auto d = messages_manager->get_dialog(dialog_id);
- CHECK(d != nullptr);
- messages_manager->get_channel_difference(dialog_id, d->pts, true, "on_channel_get_difference_timeout_callback");
+ send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_channel_get_difference_timeout,
+ DialogId(dialog_id_int));
}
void MessagesManager::on_pending_message_views_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
@@ -4571,28 +6092,19 @@ void MessagesManager::on_pending_message_views_timeout_callback(void *messages_m
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
- DialogId dialog_id(dialog_id_int);
- auto d = messages_manager->get_dialog(dialog_id);
- CHECK(d != nullptr);
- CHECK(!d->pending_viewed_message_ids.empty());
+ send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_pending_message_views_timeout,
+ DialogId(dialog_id_int));
+}
- const size_t MAX_MESSAGE_VIEWS = 100; // server side limit
- vector<MessageId> message_ids;
- message_ids.reserve(min(d->pending_viewed_message_ids.size(), MAX_MESSAGE_VIEWS));
- for (auto message_id : d->pending_viewed_message_ids) {
- message_ids.push_back(message_id);
- if (message_ids.size() >= MAX_MESSAGE_VIEWS) {
- messages_manager->td_->create_handler<GetMessagesViewsQuery>()->send(dialog_id, std::move(message_ids),
- d->increment_view_counter);
- message_ids.clear();
- }
- }
- if (!message_ids.empty()) {
- messages_manager->td_->create_handler<GetMessagesViewsQuery>()->send(dialog_id, std::move(message_ids),
- d->increment_view_counter);
+void MessagesManager::on_pending_message_live_location_view_timeout_callback(void *messages_manager_ptr,
+ int64 task_id) {
+ if (G()->close_flag()) {
+ return;
}
- d->pending_viewed_message_ids.clear();
- d->increment_view_counter = false;
+
+ auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
+ send_closure_later(messages_manager->actor_id(messages_manager),
+ &MessagesManager::view_message_live_location_on_server, task_id);
}
void MessagesManager::on_pending_draft_message_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
@@ -4601,13 +6113,27 @@ void MessagesManager::on_pending_draft_message_timeout_callback(void *messages_m
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
- DialogId dialog_id(dialog_id_int);
- messages_manager->save_dialog_draft_message_on_server(dialog_id);
+ send_closure_later(messages_manager->actor_id(messages_manager),
+ &MessagesManager::save_dialog_draft_message_on_server, DialogId(dialog_id_int));
+}
+
+void MessagesManager::on_pending_read_history_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
+ send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::do_read_history_on_server,
+ DialogId(dialog_id_int));
}
void MessagesManager::on_pending_updated_dialog_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
+ // no check for G()->close_flag() to save dialogs even while closing
+
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
// TODO it is unsafe to save dialog to database before binlog is flushed
+
+ // no send_closure_later, because messages_manager can be not an actor while closing
messages_manager->save_dialog_to_database(DialogId(dialog_id_int));
}
@@ -4617,7 +6143,8 @@ void MessagesManager::on_pending_unload_dialog_timeout_callback(void *messages_m
}
auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
- messages_manager->unload_dialog(DialogId(dialog_id_int));
+ send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::unload_dialog,
+ DialogId(dialog_id_int));
}
void MessagesManager::on_dialog_unmute_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
@@ -4650,6 +6177,37 @@ void MessagesManager::on_active_dialog_action_timeout_callback(void *messages_ma
DialogId(dialog_id_int));
}
+void MessagesManager::on_update_dialog_online_member_count_timeout_callback(void *messages_manager_ptr,
+ int64 dialog_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
+ send_closure_later(messages_manager->actor_id(messages_manager),
+ &MessagesManager::on_update_dialog_online_member_count_timeout, DialogId(dialog_id_int));
+}
+
+void MessagesManager::on_preload_folder_dialog_list_timeout_callback(void *messages_manager_ptr, int64 folder_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
+ send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::preload_folder_dialog_list,
+ FolderId(narrow_cast<int32>(folder_id_int)));
+}
+
+void MessagesManager::on_update_viewed_messages_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
+ send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::on_update_viewed_messages_timeout,
+ DialogId(dialog_id_int));
+}
+
BufferSlice MessagesManager::get_dialog_database_value(const Dialog *d) {
// can't use log_event_store, because it tries to parse stored Dialog
LogEventStorerCalcLength storer_calc_length;
@@ -4658,7 +6216,7 @@ BufferSlice MessagesManager::get_dialog_database_value(const Dialog *d) {
BufferSlice value_buffer{storer_calc_length.get_length()};
auto value = value_buffer.as_slice();
- LogEventStorerUnsafe storer_unsafe(value.begin());
+ LogEventStorerUnsafe storer_unsafe(value.ubegin());
store(*d, storer_unsafe);
return value_buffer;
}
@@ -4668,31 +6226,102 @@ void MessagesManager::save_dialog_to_database(DialogId dialog_id) {
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
LOG(INFO) << "Save " << dialog_id << " to database";
+ vector<NotificationGroupKey> changed_group_keys;
+ bool can_reuse_notification_group = false;
+ auto add_group_key = [&](auto &group_info) {
+ if (group_info.is_changed) {
+ can_reuse_notification_group |= group_info.try_reuse;
+ changed_group_keys.emplace_back(group_info.group_id, group_info.try_reuse ? DialogId() : dialog_id,
+ group_info.last_notification_date);
+ group_info.is_changed = false;
+ }
+ };
+ add_group_key(d->message_notification_group);
+ add_group_key(d->mention_notification_group);
+ auto fixed_folder_id = d->folder_id == FolderId::archive() ? FolderId::archive() : FolderId::main();
G()->td_db()->get_dialog_db_async()->add_dialog(
- dialog_id, d->order, get_dialog_database_value(d), PromiseCreator::lambda([dialog_id](Result<> result) {
- send_closure(G()->messages_manager(), &MessagesManager::on_save_dialog_to_database, dialog_id, result.is_ok());
+ dialog_id, fixed_folder_id, d->is_folder_id_inited ? d->order : 0, get_dialog_database_value(d),
+ std::move(changed_group_keys), PromiseCreator::lambda([dialog_id, can_reuse_notification_group](Result<> result) {
+ send_closure(G()->messages_manager(), &MessagesManager::on_save_dialog_to_database, dialog_id,
+ can_reuse_notification_group, result.is_ok());
}));
}
-void MessagesManager::on_save_dialog_to_database(DialogId dialog_id, bool success) {
+void MessagesManager::on_save_dialog_to_database(DialogId dialog_id, bool can_reuse_notification_group, bool success) {
LOG(INFO) << "Successfully saved " << dialog_id << " to database";
+
+ if (success && can_reuse_notification_group && !G()->close_flag()) {
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ try_reuse_notification_group(d->message_notification_group);
+ try_reuse_notification_group(d->mention_notification_group);
+ }
+
// TODO erase some events from binlog
}
+void MessagesManager::try_reuse_notification_group(NotificationGroupInfo &group_info) {
+ if (!group_info.try_reuse) {
+ return;
+ }
+ if (group_info.is_changed) {
+ LOG(ERROR) << "Failed to reuse changed " << group_info.group_id;
+ return;
+ }
+ group_info.try_reuse = false;
+ if (!group_info.group_id.is_valid()) {
+ LOG(ERROR) << "Failed to reuse invalid " << group_info.group_id;
+ return;
+ }
+ CHECK(group_info.last_notification_id == NotificationId());
+ CHECK(group_info.last_notification_date == 0);
+ send_closure_later(G()->notification_manager(), &NotificationManager::try_reuse_notification_group_id,
+ group_info.group_id);
+ notification_group_id_to_dialog_id_.erase(group_info.group_id);
+ group_info.group_id = NotificationGroupId();
+ group_info.max_removed_notification_id = NotificationId();
+ group_info.max_removed_message_id = MessageId();
+}
+
+void MessagesManager::invalidate_message_indexes(Dialog *d) {
+ CHECK(d != nullptr);
+ bool is_secret = d->dialog_id.get_type() == DialogType::SecretChat;
+ for (size_t i = 0; i < d->message_count_by_index.size(); i++) {
+ if (is_secret || i == static_cast<size_t>(message_search_filter_index(MessageSearchFilter::FailedToSend))) {
+ // always know all messages
+ d->first_database_message_id_by_index[i] = MessageId::min();
+ // keep the count
+ } else {
+ // some messages are unknown; drop first_database_message_id and count
+ d->first_database_message_id_by_index[i] = MessageId();
+ d->message_count_by_index[i] = -1;
+ }
+ }
+}
+
void MessagesManager::update_message_count_by_index(Dialog *d, int diff, const Message *m) {
auto index_mask = get_message_index_mask(d->dialog_id, m);
- index_mask &= ~search_messages_filter_index_mask(
- SearchMessagesFilter::UnreadMention); // unread mention count has been already manually updated
+ index_mask &= ~message_search_filter_index_mask(
+ MessageSearchFilter::UnreadMention); // unread mention count has been already manually updated
+ index_mask &= ~message_search_filter_index_mask(
+ MessageSearchFilter::UnreadReaction); // unread reaction count has been already manually updated
+
+ update_message_count_by_index(d, diff, index_mask);
+}
+
+void MessagesManager::update_message_count_by_index(Dialog *d, int diff, int32 index_mask) {
if (index_mask == 0) {
return;
}
+ LOG(INFO) << "Update message count by " << diff << " in index mask " << index_mask;
int i = 0;
for (auto &message_count : d->message_count_by_index) {
if (((index_mask >> i) & 1) != 0 && message_count != -1) {
message_count += diff;
if (message_count < 0) {
- if (d->dialog_id.get_type() == DialogType::SecretChat) {
+ if (d->dialog_id.get_type() == DialogType::SecretChat ||
+ i == message_search_filter_index(MessageSearchFilter::FailedToSend)) {
message_count = 0;
} else {
message_count = -1;
@@ -4703,7 +6332,7 @@ void MessagesManager::update_message_count_by_index(Dialog *d, int diff, const M
i++;
}
- i = static_cast<int>(SearchMessagesFilter::Call) - 1;
+ i = static_cast<int>(MessageSearchFilter::Call) - 1;
for (auto &message_count : calls_db_state_.message_count_by_index) {
if (((index_mask >> i) & 1) != 0 && message_count != -1) {
message_count += diff;
@@ -4722,112 +6351,74 @@ void MessagesManager::update_message_count_by_index(Dialog *d, int diff, const M
int32 MessagesManager::get_message_index_mask(DialogId dialog_id, const Message *m) const {
CHECK(m != nullptr);
- if (m->message_id.is_yet_unsent() || m->is_failed_to_send) {
+ if (m->message_id.is_scheduled() || m->message_id.is_yet_unsent()) {
return 0;
}
+ if (m->is_failed_to_send) {
+ return message_search_filter_index_mask(MessageSearchFilter::FailedToSend);
+ }
bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
if (!m->message_id.is_server() && !is_secret) {
return 0;
}
+
+ int32 index_mask = 0;
+ if (m->is_pinned) {
+ index_mask |= message_search_filter_index_mask(MessageSearchFilter::Pinned);
+ }
// retain second condition just in case
if (m->is_content_secret || (m->ttl > 0 && !is_secret)) {
- return 0;
+ return index_mask;
}
- int32 mentions_mask = get_message_content_index_mask(m->content.get(), is_secret, m->is_outgoing);
+ index_mask |= get_message_content_index_mask(m->content.get(), td_, m->is_outgoing);
if (m->contains_mention) {
- mentions_mask |= search_messages_filter_index_mask(SearchMessagesFilter::Mention);
+ index_mask |= message_search_filter_index_mask(MessageSearchFilter::Mention);
if (m->contains_unread_mention) {
- mentions_mask |= search_messages_filter_index_mask(SearchMessagesFilter::UnreadMention);
+ index_mask |= message_search_filter_index_mask(MessageSearchFilter::UnreadMention);
}
}
- return mentions_mask;
+ if (has_unread_message_reactions(dialog_id, m)) {
+ index_mask |= message_search_filter_index_mask(MessageSearchFilter::UnreadReaction);
+ }
+ LOG(INFO) << "Have index mask " << index_mask << " for " << m->message_id << " in " << dialog_id;
+ return index_mask;
}
-int32 MessagesManager::get_message_content_index_mask(const MessageContent *content, bool is_secret,
- bool is_outgoing) const {
- switch (content->get_id()) {
- case MessageAnimation::ID:
- return search_messages_filter_index_mask(SearchMessagesFilter::Animation);
- case MessageAudio::ID: {
- auto message_audio = static_cast<const MessageAudio *>(content);
- auto duration = td_->audios_manager_->get_audio_duration(message_audio->file_id);
- return is_secret || duration > 0 ? search_messages_filter_index_mask(SearchMessagesFilter::Audio)
- : search_messages_filter_index_mask(SearchMessagesFilter::Document);
- }
- case MessageDocument::ID:
- return search_messages_filter_index_mask(SearchMessagesFilter::Document);
- case MessagePhoto::ID:
- return search_messages_filter_index_mask(SearchMessagesFilter::Photo) |
- search_messages_filter_index_mask(SearchMessagesFilter::PhotoAndVideo);
- case MessageText::ID:
- for (auto &entity : static_cast<const MessageText *>(content)->text.entities) {
- if (entity.type == MessageEntity::Type::Url || entity.type == MessageEntity::Type::EmailAddress) {
- return search_messages_filter_index_mask(SearchMessagesFilter::Url);
- }
- }
- return 0;
- case MessageVideo::ID: {
- auto message_video = static_cast<const MessageVideo *>(content);
- auto duration = td_->videos_manager_->get_video_duration(message_video->file_id);
- return is_secret || duration > 0 ? search_messages_filter_index_mask(SearchMessagesFilter::Video) |
- search_messages_filter_index_mask(SearchMessagesFilter::PhotoAndVideo)
- : search_messages_filter_index_mask(SearchMessagesFilter::Document);
- }
- case MessageVideoNote::ID: {
- auto message_video_note = static_cast<const MessageVideoNote *>(content);
- auto duration = td_->video_notes_manager_->get_video_note_duration(message_video_note->file_id);
- return is_secret || duration > 0 ? search_messages_filter_index_mask(SearchMessagesFilter::VideoNote) |
- search_messages_filter_index_mask(SearchMessagesFilter::VoiceAndVideoNote)
- : search_messages_filter_index_mask(SearchMessagesFilter::Document);
- }
- case MessageVoiceNote::ID:
- return search_messages_filter_index_mask(SearchMessagesFilter::VoiceNote) |
- search_messages_filter_index_mask(SearchMessagesFilter::VoiceAndVideoNote);
- case MessageChatChangePhoto::ID:
- return search_messages_filter_index_mask(SearchMessagesFilter::ChatPhoto);
- case MessageCall::ID: {
- int32 index_mask = search_messages_filter_index_mask(SearchMessagesFilter::Call);
- auto message_call = static_cast<const MessageCall *>(content);
- if (!is_outgoing && (message_call->discard_reason == CallDiscardReason::Declined ||
- message_call->discard_reason == CallDiscardReason::Missed)) {
- index_mask |= search_messages_filter_index_mask(SearchMessagesFilter::MissedCall);
- }
- return index_mask;
- }
- case MessageContact::ID:
- case MessageGame::ID:
- case MessageInvoice::ID:
- case MessageLiveLocation::ID:
- case MessageLocation::ID:
- case MessageSticker::ID:
- case MessageUnsupported::ID:
- case MessageVenue::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- return 0;
- default:
- UNREACHABLE();
- return 0;
+void MessagesManager::update_reply_count_by_message(Dialog *d, int diff, const Message *m) {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ if (td_->auth_manager_->is_bot() || !m->top_thread_message_id.is_valid() ||
+ m->top_thread_message_id == m->message_id || !m->message_id.is_valid() || !m->message_id.is_server()) {
+ return;
+ }
+
+ update_message_reply_count(d, m->top_thread_message_id, get_message_sender(m), m->message_id,
+ diff < 0 ? G()->unix_time() : m->date, diff);
+}
+
+void MessagesManager::update_message_reply_count(Dialog *d, MessageId message_id, DialogId replier_dialog_id,
+ MessageId reply_message_id, int32 update_date, int diff,
+ bool is_recursive) {
+ if (d == nullptr) {
+ return;
+ }
+
+ Message *m = get_message(d, message_id);
+ if (m == nullptr || !is_active_message_reply_info(d->dialog_id, m->reply_info)) {
+ return;
+ }
+ LOG(INFO) << "Update reply count to " << message_id << " in " << d->dialog_id << " by " << diff << " from "
+ << reply_message_id << " sent by " << replier_dialog_id;
+ if (m->interaction_info_update_date < update_date &&
+ m->reply_info.add_reply(replier_dialog_id, reply_message_id, diff)) {
+ on_message_reply_info_changed(d->dialog_id, m);
+ on_message_changed(d, m, true, "update_message_reply_count_by_message");
+ }
+
+ if (!is_recursive && is_discussion_message(d->dialog_id, m)) {
+ update_message_reply_count(get_dialog(m->forward_info->from_dialog_id), m->forward_info->from_message_id,
+ replier_dialog_id, reply_message_id, update_date, diff, true);
}
- return 0;
}
vector<MessageId> MessagesManager::get_message_ids(const vector<int64> &input_message_ids) {
@@ -4843,27 +6434,45 @@ vector<int32> MessagesManager::get_server_message_ids(const vector<MessageId> &m
return transform(message_ids, [](MessageId message_id) { return message_id.get_server_message_id().get(); });
}
-tl_object_ptr<telegram_api::InputMessage> MessagesManager::get_input_message(MessageId message_id) {
- return make_tl_object<telegram_api::inputMessageID>(message_id.get_server_message_id().get());
+vector<int32> MessagesManager::get_scheduled_server_message_ids(const vector<MessageId> &message_ids) {
+ return transform(message_ids,
+ [](MessageId message_id) { return message_id.get_scheduled_server_message_id().get(); });
}
tl_object_ptr<telegram_api::InputPeer> MessagesManager::get_input_peer(DialogId dialog_id,
AccessRights access_rights) const {
switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return td_->contacts_manager_->get_input_peer_user(dialog_id.get_user_id(), access_rights);
+ case DialogType::Chat:
+ return td_->contacts_manager_->get_input_peer_chat(dialog_id.get_chat_id(), access_rights);
+ case DialogType::Channel:
+ return td_->contacts_manager_->get_input_peer_channel(dialog_id.get_channel_id(), access_rights);
+ case DialogType::SecretChat:
+ return nullptr;
+ case DialogType::None:
+ return make_tl_object<telegram_api::inputPeerEmpty>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+tl_object_ptr<telegram_api::InputPeer> MessagesManager::get_input_peer_force(DialogId dialog_id) {
+ switch (dialog_id.get_type()) {
case DialogType::User: {
UserId user_id = dialog_id.get_user_id();
- return td_->contacts_manager_->get_input_peer_user(user_id, access_rights);
+ return make_tl_object<telegram_api::inputPeerUser>(user_id.get(), 0);
}
case DialogType::Chat: {
ChatId chat_id = dialog_id.get_chat_id();
- return td_->contacts_manager_->get_input_peer_chat(chat_id, access_rights);
+ return make_tl_object<telegram_api::inputPeerChat>(chat_id.get());
}
case DialogType::Channel: {
ChannelId channel_id = dialog_id.get_channel_id();
- return td_->contacts_manager_->get_input_peer_channel(channel_id, access_rights);
+ return make_tl_object<telegram_api::inputPeerChannel>(channel_id.get(), 0);
}
case DialogType::SecretChat:
- return nullptr;
case DialogType::None:
return make_tl_object<telegram_api::inputPeerEmpty>();
default:
@@ -4887,7 +6496,7 @@ vector<tl_object_ptr<telegram_api::InputPeer>> MessagesManager::get_input_peers(
return input_peers;
}
-tl_object_ptr<telegram_api::inputDialogPeer> MessagesManager::get_input_dialog_peer(DialogId dialog_id,
+tl_object_ptr<telegram_api::InputDialogPeer> MessagesManager::get_input_dialog_peer(DialogId dialog_id,
AccessRights access_rights) const {
switch (dialog_id.get_type()) {
case DialogType::User:
@@ -4903,9 +6512,9 @@ tl_object_ptr<telegram_api::inputDialogPeer> MessagesManager::get_input_dialog_p
}
}
-vector<tl_object_ptr<telegram_api::inputDialogPeer>> MessagesManager::get_input_dialog_peers(
+vector<tl_object_ptr<telegram_api::InputDialogPeer>> MessagesManager::get_input_dialog_peers(
const vector<DialogId> &dialog_ids, AccessRights access_rights) const {
- vector<tl_object_ptr<telegram_api::inputDialogPeer>> input_dialog_peers;
+ vector<tl_object_ptr<telegram_api::InputDialogPeer>> input_dialog_peers;
input_dialog_peers.reserve(dialog_ids.size());
for (auto &dialog_id : dialog_ids) {
auto input_dialog_peer = get_input_dialog_peer(dialog_id, access_rights);
@@ -5009,62 +6618,30 @@ tl_object_ptr<telegram_api::inputEncryptedChat> MessagesManager::get_input_encry
}
}
-bool MessagesManager::is_allowed_useless_update(const tl_object_ptr<telegram_api::Update> &update) const {
+bool MessagesManager::is_allowed_useless_update(const tl_object_ptr<telegram_api::Update> &update) {
auto constructor_id = update->get_id();
if (constructor_id == dummyUpdate::ID) {
// allow dummyUpdate just in case
return true;
}
- if (constructor_id == telegram_api::updateNewMessage::ID) {
- auto message = static_cast<const telegram_api::updateNewMessage *>(update.get())->message_.get();
- if (message->get_id() == telegram_api::message::ID) {
- auto m = static_cast<const telegram_api::message *>(message);
- bool is_outgoing = (m->flags_ & MESSAGE_FLAG_IS_OUT) != 0 ||
- UserId(m->from_id_) == td_->contacts_manager_->get_my_id("is_allowed_useless_update");
- if (is_outgoing && m->media_ != nullptr && m->media_->get_id() != telegram_api::messageMediaEmpty::ID) {
- // allow outgoing media, because they are returned if random_id coincided
- return true;
- }
- }
- if (message->get_id() == telegram_api::messageService::ID) {
- auto m = static_cast<const telegram_api::messageService *>(message);
- bool is_outgoing = (m->flags_ & MESSAGE_FLAG_IS_OUT) != 0 ||
- UserId(m->from_id_) == td_->contacts_manager_->get_my_id("is_allowed_useless_update");
- if (is_outgoing && m->action_->get_id() == telegram_api::messageActionScreenshotTaken::ID) {
- // allow outgoing messageActionScreenshotTaken, because they are returned if random_id coincided
- return true;
- }
- }
+ if (constructor_id == telegram_api::updateNewMessage::ID ||
+ constructor_id == telegram_api::updateNewChannelMessage::ID) {
+ // new outgoing messages are received again if random_id coincide
+ return true;
}
return false;
}
-bool MessagesManager::check_update_dialog_id(const tl_object_ptr<telegram_api::Update> &update, DialogId dialog_id) {
- switch (dialog_id.get_type()) {
- case DialogType::User:
- case DialogType::Chat:
- return true;
- case DialogType::Channel:
- case DialogType::SecretChat:
- case DialogType::None:
- LOG(ERROR) << "Receive update in wrong " << dialog_id << ": " << oneline(to_string(update));
- return false;
- default:
- UNREACHABLE();
- return false;
- }
-}
-
-void MessagesManager::skip_old_pending_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts,
- int32 old_pts, int32 pts_count, const char *source) {
+void MessagesManager::skip_old_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts,
+ int32 old_pts, int32 pts_count, const char *source) {
if (update->get_id() == telegram_api::updateNewMessage::ID) {
auto update_new_message = static_cast<telegram_api::updateNewMessage *>(update.get());
- auto full_message_id = get_full_message_id(update_new_message->message_);
- if (update_message_ids_.find(full_message_id) != update_message_ids_.end()) {
- if (new_pts == old_pts) { // otherwise message can be already deleted
- // apply sent message anyway
- on_get_message(std::move(update_new_message->message_), true, false, true, true,
+ auto full_message_id = get_full_message_id(update_new_message->message_, false);
+ if (update_message_ids_.count(full_message_id) > 0) {
+ if (new_pts == old_pts || old_pts == std::numeric_limits<int32>::max()) {
+ // apply sent message anyway if it is definitely non-deleted or being skipped because of pts overflow
+ on_get_message(std::move(update_new_message->message_), true, false, false, true, true,
"updateNewMessage with an awaited message");
return;
} else {
@@ -5077,12 +6654,13 @@ void MessagesManager::skip_old_pending_update(tl_object_ptr<telegram_api::Update
if (update->get_id() == updateSentMessage::ID) {
auto update_sent_message = static_cast<updateSentMessage *>(update.get());
if (being_sent_messages_.count(update_sent_message->random_id_) > 0) {
- if (new_pts == old_pts) { // otherwise message can be already deleted
- // apply sent message anyway
+ if (new_pts == old_pts || old_pts == std::numeric_limits<int32>::max()) {
+ // apply sent message anyway if it is definitely non-deleted or being skipped because of pts overflow
on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_,
- update_sent_message->date_, FileId(), "process old updateSentMessage");
+ update_sent_message->date_, update_sent_message->ttl_period_, FileId(),
+ "process old updateSentMessage");
return;
- } else {
+ } else if (update_sent_message->random_id_ != 0) {
LOG(ERROR) << "Receive awaited sent " << update_sent_message->message_id_ << " from " << source << " with pts "
<< new_pts << " and pts_count " << pts_count << ", but current pts is " << old_pts;
dump_debug_message_op(get_dialog(being_sent_messages_[update_sent_message->random_id_].get_dialog_id()), 3);
@@ -5096,212 +6674,98 @@ void MessagesManager::skip_old_pending_update(tl_object_ptr<telegram_api::Update
<< "Receive useless update " << oneline(to_string(update)) << " from " << source;
}
-void MessagesManager::add_pending_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 pts_count,
- bool force_apply, const char *source) {
- // do not try to run getDifference from this function
- CHECK(update != nullptr);
- CHECK(source != nullptr);
- LOG(INFO) << "Receive from " << source << " pending " << to_string(update) << "new_pts = " << new_pts
- << ", pts_count = " << pts_count << ", force_apply = " << force_apply;
- if (pts_count < 0 || new_pts <= pts_count) {
- LOG(ERROR) << "Receive update with wrong pts = " << new_pts << " or pts_count = " << pts_count << " from " << source
- << ": " << oneline(to_string(update));
- return;
- }
-
- // TODO need to save all updates that can change result of running queries not associated with pts (for example
- // getHistory) and apply them to result of this queries
-
- switch (update->get_id()) {
- case dummyUpdate::ID:
- case updateSentMessage::ID:
- case telegram_api::updateReadMessagesContents::ID:
- case telegram_api::updateDeleteMessages::ID:
- // nothing to check
- break;
- case telegram_api::updateNewMessage::ID: {
- auto update_new_message = static_cast<const telegram_api::updateNewMessage *>(update.get());
- DialogId dialog_id = get_message_dialog_id(update_new_message->message_);
- if (!check_update_dialog_id(update, dialog_id)) {
- return;
- }
- break;
- }
- case telegram_api::updateReadHistoryInbox::ID: {
- auto update_read_history_inbox = static_cast<const telegram_api::updateReadHistoryInbox *>(update.get());
- auto dialog_id = DialogId(update_read_history_inbox->peer_);
- if (!check_update_dialog_id(update, dialog_id)) {
- return;
- }
- break;
- }
- case telegram_api::updateReadHistoryOutbox::ID: {
- auto update_read_history_outbox = static_cast<const telegram_api::updateReadHistoryOutbox *>(update.get());
- auto dialog_id = DialogId(update_read_history_outbox->peer_);
- if (!check_update_dialog_id(update, dialog_id)) {
- return;
- }
- break;
- }
- case telegram_api::updateEditMessage::ID: {
- auto update_edit_message = static_cast<const telegram_api::updateEditMessage *>(update.get());
- DialogId dialog_id = get_message_dialog_id(update_edit_message->message_);
- if (!check_update_dialog_id(update, dialog_id)) {
- return;
- }
- break;
- }
- default:
- LOG(ERROR) << "Receive unexpected update " << oneline(to_string(update)) << "from " << source;
- return;
- }
-
- if (force_apply) {
- CHECK(pending_updates_.empty());
- CHECK(accumulated_pts_ == -1);
- if (pts_count != 0) {
- LOG(ERROR) << "Receive forced update with pts_count = " << pts_count << " from " << source;
- }
-
- process_update(std::move(update));
- return;
- }
- if (DROP_UPDATES) {
- return set_get_difference_timeout(1.0);
- }
+MessagesManager::Dialog *MessagesManager::get_service_notifications_dialog() {
+ UserId service_notifications_user_id = td_->contacts_manager_->add_service_notifications_user();
+ DialogId service_notifications_dialog_id(service_notifications_user_id);
+ force_create_dialog(service_notifications_dialog_id, "get_service_notifications_dialog");
+ return get_dialog(service_notifications_dialog_id);
+}
- int32 old_pts = td_->updates_manager_->get_pts();
- if (new_pts < old_pts - 19999) {
- // restore pts after delete_first_messages
- LOG(ERROR) << "Restore pts after delete_first_messages from " << old_pts << " to " << new_pts
- << " is temporarily disabled, pts_count = " << pts_count << ", update is from " << source << ": "
- << oneline(to_string(update));
- if (old_pts < 10000000 && update->get_id() == telegram_api::updateNewMessage::ID) {
- auto update_new_message = static_cast<telegram_api::updateNewMessage *>(update.get());
- auto dialog_id = get_message_dialog_id(update_new_message->message_);
- dump_debug_message_op(get_dialog(dialog_id), 6);
+void MessagesManager::save_auth_notification_ids() {
+ auto min_date = G()->unix_time() - AUTH_NOTIFICATION_ID_CACHE_TIME;
+ vector<string> ids;
+ for (const auto &it : auth_notification_id_date_) {
+ auto date = it.second;
+ if (date < min_date) {
+ continue;
}
- set_get_difference_timeout(0.001);
-
- /*
- LOG(WARNING) << "Restore pts after delete_first_messages";
- td_->updates_manager_->set_pts(new_pts - 1, "restore pts after delete_first_messages");
- old_pts = td_->updates_manager_->get_pts();
- CHECK(old_pts == new_pts - 1);
- */
+ ids.push_back(it.first);
+ ids.push_back(to_string(date));
}
- if (new_pts <= old_pts) {
- skip_old_pending_update(std::move(update), new_pts, old_pts, pts_count, source);
+ if (ids.empty()) {
+ G()->td_db()->get_binlog_pmc()->erase("auth_notification_ids");
return;
}
- if (td_->updates_manager_->running_get_difference()) {
- LOG(INFO) << "Save pending update got while running getDifference from " << source;
- CHECK(update->get_id() == dummyUpdate::ID || update->get_id() == updateSentMessage::ID);
- if (pts_count > 0) {
- postponed_pts_updates_.emplace(new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count));
- }
- return;
- }
+ G()->td_db()->get_binlog_pmc()->set("auth_notification_ids", implode(ids, ','));
+}
- if (old_pts + pts_count > new_pts) {
- LOG(WARNING) << "old_pts (= " << old_pts << ") + pts_count (= " << pts_count << ") > new_pts (= " << new_pts
- << "). Logged in " << G()->shared_config().get_option_integer("authorization_date") << ". Update from "
- << source << " = " << oneline(to_string(update));
- set_get_difference_timeout(0.001);
+void MessagesManager::on_update_service_notification(tl_object_ptr<telegram_api::updateServiceNotification> &&update,
+ bool skip_new_entities, Promise<Unit> &&promise) {
+ if (td_->auth_manager_->is_bot()) {
return;
}
- accumulated_pts_count_ += pts_count;
- if (new_pts > accumulated_pts_) {
- accumulated_pts_ = new_pts;
- }
-
- if (old_pts + accumulated_pts_count_ > accumulated_pts_) {
- LOG(WARNING) << "old_pts (= " << old_pts << ") + accumulated_pts_count (= " << accumulated_pts_count_
- << ") > accumulated_pts (= " << accumulated_pts_ << "). new_pts = " << new_pts
- << ", pts_count = " << pts_count << ". Logged in "
- << G()->shared_config().get_option_integer("authorization_date") << ". Update from " << source << " = "
- << oneline(to_string(update));
- set_get_difference_timeout(0.001);
+ bool has_date = (update->flags_ & telegram_api::updateServiceNotification::INBOX_DATE_MASK) != 0;
+ auto date = has_date ? update->inbox_date_ : G()->unix_time();
+ if (date <= 0) {
+ LOG(ERROR) << "Receive message date " << date << " in " << to_string(update);
return;
}
-
- LOG_IF(INFO, pts_count == 0 && update->get_id() != dummyUpdate::ID) << "Skip useless update " << to_string(update);
-
- if (pending_updates_.empty() && old_pts + accumulated_pts_count_ == accumulated_pts_ &&
- !pts_gap_timeout_.has_timeout()) {
- if (pts_count > 0) {
- process_update(std::move(update));
-
- td_->updates_manager_->set_pts(accumulated_pts_, "process pending updates fast path")
- .set_value(Unit()); // TODO can't set until get messages really stored on persistent storage
- accumulated_pts_count_ = 0;
- accumulated_pts_ = -1;
+ bool is_auth_notification = update->type_.size() >= 5 && begins_with(update->type_, "auth");
+ if (is_auth_notification) {
+ auto &old_date = auth_notification_id_date_[update->type_.substr(4)];
+ if (date <= old_date) {
+ LOG(INFO) << "Skip already applied " << to_string(update);
+ return;
}
- return;
- }
+ old_date = date;
- if (pts_count > 0) {
- pending_updates_.emplace(new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count));
- }
-
- if (old_pts + accumulated_pts_count_ < accumulated_pts_) {
- set_get_difference_timeout(UpdatesManager::MAX_UNFILLED_GAP_TIME);
- return;
+ if (auth_notification_id_date_.size() > MAX_SAVED_AUTH_NOTIFICATION_IDS) {
+ auto min_date = date + 1;
+ const string *min_key = nullptr;
+ for (const auto &it : auth_notification_id_date_) {
+ if (it.second < min_date) {
+ min_date = it.second;
+ min_key = &it.first;
+ }
+ }
+ CHECK(min_key != nullptr);
+ auth_notification_id_date_.erase(*min_key);
+ }
}
- CHECK(old_pts + accumulated_pts_count_ == accumulated_pts_);
- if (!pending_updates_.empty()) {
- process_pending_updates();
- }
-}
-
-MessagesManager::Dialog *MessagesManager::get_service_notifications_dialog() {
- UserId service_notifications_user_id(777000);
- if (!td_->contacts_manager_->have_user_force(service_notifications_user_id) ||
- !td_->contacts_manager_->have_user(service_notifications_user_id)) {
- auto user = telegram_api::make_object<telegram_api::user>(
- 131127, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- false /*ignored*/, 777000, 1, "Telegram", "Updates", string(), "42777",
- telegram_api::make_object<telegram_api::userProfilePhoto>(
- 3337190045231018,
- telegram_api::make_object<telegram_api::fileLocation>(1, 702229962, 26779, 5859320227133863146),
- telegram_api::make_object<telegram_api::fileLocation>(1, 702229962, 26781, -3695031185685824216)),
- nullptr, 0, string(), string(), string());
- td_->contacts_manager_->on_get_user(std::move(user));
- }
- DialogId service_notifications_dialog_id(service_notifications_user_id);
- force_create_dialog(service_notifications_dialog_id, "get_service_notifications_dialog");
- return get_dialog(service_notifications_dialog_id);
-}
-
-void MessagesManager::on_update_service_notification(tl_object_ptr<telegram_api::updateServiceNotification> &&update) {
+ bool is_authorized = td_->auth_manager_->is_authorized();
+ bool is_user = is_authorized && !td_->auth_manager_->is_bot();
+ auto contacts_manager = is_authorized ? td_->contacts_manager_.get() : nullptr;
+ auto message_text = get_message_text(contacts_manager, std::move(update->message_), std::move(update->entities_),
+ skip_new_entities, !is_user, date, false, "on_update_service_notification");
+ DialogId owner_dialog_id = is_user ? get_service_notifications_dialog()->dialog_id : DialogId();
int32 ttl = 0;
- auto content = get_message_content(
- get_message_text(std::move(update->message_), std::move(update->entities_), update->inbox_date_),
- std::move(update->media_),
- td_->auth_manager_->is_bot() ? DialogId() : get_service_notifications_dialog()->dialog_id, false, UserId(), &ttl);
- bool is_content_secret = is_secret_message_content(ttl, content->get_id());
- if ((update->flags_ & telegram_api::updateServiceNotification::POPUP_MASK) != 0) {
+ bool disable_web_page_preview = false;
+ auto content = get_message_content(td_, std::move(message_text), std::move(update->media_), owner_dialog_id, false,
+ UserId(), &ttl, &disable_web_page_preview, "updateServiceNotification");
+ bool is_content_secret = is_secret_message_content(ttl, content->get_type());
+
+ if (update->popup_) {
send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateServiceNotification>(
- update->type_, get_message_content_object(content.get(), update->inbox_date_, is_content_secret)));
+ td_api::make_object<td_api::updateServiceNotification>(
+ update->type_, get_message_content_object(content.get(), td_, owner_dialog_id, date,
+ is_content_secret, true, -1)));
}
- if ((update->flags_ & telegram_api::updateServiceNotification::INBOX_DATE_MASK) != 0 &&
- !td_->auth_manager_->is_bot()) {
+ if (has_date && is_user) {
Dialog *d = get_service_notifications_dialog();
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
CHECK(dialog_id.get_type() == DialogType::User);
+
auto new_message = make_unique<Message>();
- new_message->message_id = get_next_local_message_id(d);
- new_message->random_y = get_random_y(new_message->message_id);
+ set_message_id(new_message, get_next_local_message_id(d));
new_message->sender_user_id = dialog_id.get_user_id();
- new_message->date = update->inbox_date_;
+ new_message->date = date;
new_message->ttl = ttl;
+ new_message->disable_web_page_preview = disable_web_page_preview;
new_message->is_content_secret = is_content_secret;
new_message->content = std::move(content);
new_message->have_previous = true;
@@ -5310,147 +6774,22 @@ void MessagesManager::on_update_service_notification(tl_object_ptr<telegram_api:
bool need_update = true;
bool need_update_dialog_pos = false;
- Message *m = add_message_to_dialog(d, std::move(new_message), true, &need_update, &need_update_dialog_pos,
- "on_update_service_notification");
+ const Message *m = add_message_to_dialog(d, std::move(new_message), true, &need_update, &need_update_dialog_pos,
+ "on_update_service_notification");
if (m != nullptr && need_update) {
send_update_new_message(d, m);
}
+ register_new_local_message_id(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "on_update_service_notification");
}
}
-}
-
-void MessagesManager::on_update_contact_registered(tl_object_ptr<telegram_api::updateContactRegistered> &&update) {
- if (update->date_ <= 0) {
- LOG(ERROR) << "Receive wrong date " << update->date_ << " in updateContactRegistered";
- return;
- }
- UserId user_id(update->user_id_);
- if (!user_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << user_id << " in updateContactRegistered";
- return;
- }
-
- if (!td_->auth_manager_->is_bot() &&
- !G()->shared_config().get_option_boolean("disable_contact_registered_notifications")) {
- DialogId dialog_id(user_id);
- force_create_dialog(dialog_id, "on_update_contact_registered");
- Dialog *d = get_dialog(dialog_id);
- CHECK(d != nullptr);
-
- if (d->has_contact_registered_message) {
- LOG(INFO) << "Ignore duplicate updateContactRegistered about " << user_id;
- return;
- }
- if (d->last_message_id.is_valid()) {
- auto m = get_message(d, d->last_message_id);
- CHECK(m != nullptr);
- if (m->content->get_id() == MessageContactRegistered::ID) {
- LOG(INFO) << "Ignore duplicate updateContactRegistered about " << user_id;
- return;
- }
- }
-
- auto new_message = make_unique<Message>();
- new_message->message_id = get_next_local_message_id(d);
- new_message->random_y = get_random_y(new_message->message_id);
- new_message->sender_user_id = user_id;
- new_message->date = update->date_;
- new_message->content = make_unique<MessageContactRegistered>();
- new_message->have_previous = true;
- new_message->have_next = true;
-
- bool need_update = true;
- bool need_update_dialog_pos = false;
-
- Message *m = add_message_to_dialog(d, std::move(new_message), true, &need_update, &need_update_dialog_pos,
- "on_update_contact_registered");
- if (m != nullptr && need_update) {
- send_update_new_message(d, m);
- }
-
- if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "on_update_contact_registered");
- }
- }
-}
-
-void MessagesManager::on_update_new_channel_message(tl_object_ptr<telegram_api::updateNewChannelMessage> &&update) {
- int new_pts = update->pts_;
- int pts_count = update->pts_count_;
- DialogId dialog_id = get_message_dialog_id(update->message_);
- switch (dialog_id.get_type()) {
- case DialogType::None:
- return;
- case DialogType::User:
- case DialogType::Chat:
- case DialogType::SecretChat:
- LOG(ERROR) << "Receive updateNewChannelMessage in wrong " << dialog_id;
- return;
- case DialogType::Channel: {
- auto channel_id = dialog_id.get_channel_id();
- if (!td_->contacts_manager_->have_channel(channel_id)) {
- // if min channel was received
- if (td_->contacts_manager_->have_min_channel(channel_id)) {
- td_->updates_manager_->schedule_get_difference("on_update_new_channel_message");
- return;
- }
- }
- // Ok
- break;
- }
- default:
- UNREACHABLE();
- return;
- }
-
- if (pts_count < 0 || new_pts <= pts_count) {
- LOG(ERROR) << "Receive new channel message with wrong pts = " << new_pts << " or pts_count = " << pts_count << ": "
- << oneline(to_string(update));
- return;
- }
-
- add_pending_channel_update(dialog_id, std::move(update), new_pts, pts_count, "on_update_new_channel_message");
-}
-
-void MessagesManager::on_update_edit_channel_message(tl_object_ptr<telegram_api::updateEditChannelMessage> &&update) {
- int new_pts = update->pts_;
- int pts_count = update->pts_count_;
- DialogId dialog_id = get_message_dialog_id(update->message_);
- switch (dialog_id.get_type()) {
- case DialogType::None:
- return;
- case DialogType::User:
- case DialogType::Chat:
- case DialogType::SecretChat:
- LOG(ERROR) << "Receive updateNewChannelMessage in wrong " << dialog_id;
- return;
- case DialogType::Channel: {
- auto channel_id = dialog_id.get_channel_id();
- if (!td_->contacts_manager_->have_channel(channel_id)) {
- // if min channel was received
- if (td_->contacts_manager_->have_min_channel(channel_id)) {
- td_->updates_manager_->schedule_get_difference("on_update_edit_channel_message");
- return;
- }
- }
- // Ok
- break;
- }
- default:
- UNREACHABLE();
- return;
- }
+ promise.set_value(Unit());
- if (pts_count < 0 || new_pts <= pts_count) {
- LOG(ERROR) << "Receive edited channel message with wrong pts = " << new_pts << " or pts_count = " << pts_count
- << ": " << oneline(to_string(update));
- return;
+ if (is_auth_notification) {
+ save_auth_notification_ids();
}
-
- add_pending_channel_update(dialog_id, std::move(update), new_pts, pts_count, "on_update_edit_channel_message");
}
void MessagesManager::on_update_read_channel_inbox(tl_object_ptr<telegram_api::updateReadChannelInbox> &&update) {
@@ -5460,8 +6799,13 @@ void MessagesManager::on_update_read_channel_inbox(tl_object_ptr<telegram_api::u
return;
}
- DialogId dialog_id = DialogId(channel_id);
- read_history_inbox(dialog_id, MessageId(ServerMessageId(update->max_id_)), -1, "updateReadChannelInbox");
+ FolderId folder_id;
+ if ((update->flags_ & telegram_api::updateReadChannelInbox::FOLDER_ID_MASK) != 0) {
+ folder_id = FolderId(update->folder_id_);
+ }
+ on_update_dialog_folder_id(DialogId(channel_id), folder_id);
+ on_read_channel_inbox(channel_id, MessageId(ServerMessageId(update->max_id_)), update->still_unread_count_,
+ update->pts_, "updateReadChannelInbox");
}
void MessagesManager::on_update_read_channel_outbox(tl_object_ptr<telegram_api::updateReadChannelOutbox> &&update) {
@@ -5482,20 +6826,44 @@ void MessagesManager::on_update_read_channel_messages_contents(
LOG(ERROR) << "Receive invalid " << channel_id << " in updateChannelReadMessagesContents";
return;
}
-
DialogId dialog_id = DialogId(channel_id);
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "on_update_read_channel_messages_contents");
if (d == nullptr) {
LOG(INFO) << "Receive read channel messages contents update in unknown " << dialog_id;
return;
}
+ if ((update->flags_ & telegram_api::updateChannelReadMessagesContents::TOP_MSG_ID_MASK) != 0) {
+ // TODO
+ return;
+ }
+
for (auto &server_message_id : update->messages_) {
read_channel_message_content_from_updates(d, MessageId(ServerMessageId(server_message_id)));
}
}
+void MessagesManager::on_update_read_message_comments(DialogId dialog_id, MessageId message_id,
+ MessageId max_message_id, MessageId last_read_inbox_message_id,
+ MessageId last_read_outbox_message_id) {
+ Dialog *d = get_dialog_force(dialog_id, "on_update_read_message_comments");
+ if (d == nullptr) {
+ LOG(INFO) << "Ignore update of read message comments in unknown " << dialog_id << " in updateReadDiscussion";
+ return;
+ }
+
+ auto m = get_message_force(d, message_id, "on_update_read_message_comments");
+ if (m == nullptr || !m->message_id.is_server() || m->top_thread_message_id != m->message_id ||
+ !is_active_message_reply_info(dialog_id, m->reply_info)) {
+ return;
+ }
+ if (m->reply_info.update_max_message_ids(max_message_id, last_read_inbox_message_id, last_read_outbox_message_id)) {
+ on_message_reply_info_changed(dialog_id, m);
+ on_message_changed(d, m, true, "on_update_read_message_comments");
+ }
+}
+
void MessagesManager::on_update_channel_too_long(tl_object_ptr<telegram_api::updateChannelTooLong> &&update,
bool force_apply) {
ChannelId channel_id(update->channel_id_);
@@ -5505,76 +6873,614 @@ void MessagesManager::on_update_channel_too_long(tl_object_ptr<telegram_api::upd
}
DialogId dialog_id = DialogId(channel_id);
- auto d = get_dialog_force(dialog_id);
+ auto d = get_dialog_force(dialog_id, "on_update_channel_too_long 4");
if (d == nullptr) {
auto pts = load_channel_pts(dialog_id);
if (pts > 0) {
- d = add_dialog(dialog_id);
+ d = add_dialog(dialog_id, "on_update_channel_too_long 5");
CHECK(d != nullptr);
CHECK(d->pts == pts);
- update_dialog_pos(d, false, "on_update_channel_too_long");
+ update_dialog_pos(d, "on_update_channel_too_long 6");
}
}
int32 update_pts = (update->flags_ & UPDATE_CHANNEL_TO_LONG_FLAG_HAS_PTS) ? update->pts_ : 0;
- if (force_apply) {
+ if (d != nullptr) {
+ if (update_pts == 0 || update_pts > d->pts) {
+ get_channel_difference(dialog_id, d->pts, true, "on_update_channel_too_long 1");
+ }
+ } else {
+ if (force_apply) {
+ get_channel_difference(dialog_id, -1, true, "on_update_channel_too_long 2");
+ } else {
+ td_->updates_manager_->schedule_get_difference("on_update_channel_too_long 3");
+ }
+ }
+}
+
+void MessagesManager::on_update_message_view_count(FullMessageId full_message_id, int32 view_count) {
+ if (view_count < 0) {
+ LOG(ERROR) << "Receive " << view_count << " views in updateChannelMessageViews for " << full_message_id;
+ return;
+ }
+ update_message_interaction_info(full_message_id, view_count, -1, false, nullptr, false, nullptr);
+}
+
+void MessagesManager::on_update_message_forward_count(FullMessageId full_message_id, int32 forward_count) {
+ if (forward_count < 0) {
+ LOG(ERROR) << "Receive " << forward_count << " forwards in updateChannelMessageForwards for " << full_message_id;
+ return;
+ }
+ update_message_interaction_info(full_message_id, -1, forward_count, false, nullptr, false, nullptr);
+}
+
+void MessagesManager::on_update_message_reactions(FullMessageId full_message_id,
+ tl_object_ptr<telegram_api::messageReactions> &&reactions,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto new_reactions = MessageReactions::get_message_reactions(td_, std::move(reactions), td_->auth_manager_->is_bot());
+ if (!have_message_force(full_message_id, "on_update_message_reactions")) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ LOG(INFO) << "Ignore updateMessageReaction in inaccessible " << full_message_id;
+ promise.set_value(Unit());
+ return;
+ }
+ const Dialog *d = get_dialog(dialog_id);
if (d == nullptr) {
- get_channel_difference(dialog_id, -1, true, "on_update_channel_too_long 1");
+ LOG(INFO) << "Ignore updateMessageReaction in unknown " << dialog_id;
+ promise.set_value(Unit());
+ return;
+ }
+
+ // there is no message, so the update can be ignored
+ if ((new_reactions != nullptr && !new_reactions->unread_reactions_.empty()) || d->unread_reaction_count > 0) {
+ // but if there are unread reactions or the chat has unread reactions,
+ // then number of unread reactions could have been changed, so reload the number of unread reactions
+ send_get_dialog_query(dialog_id, std::move(promise), 0, "on_update_message_reactions");
} else {
- get_channel_difference(dialog_id, d->pts, true, "on_update_channel_too_long 2");
+ promise.set_value(Unit());
}
+ return;
+ }
+
+ update_message_interaction_info(full_message_id, -1, -1, false, nullptr, true, std::move(new_reactions));
+ promise.set_value(Unit());
+}
+
+void MessagesManager::update_message_reactions(FullMessageId full_message_id,
+ unique_ptr<MessageReactions> &&reactions) {
+ update_message_interaction_info(full_message_id, -1, -1, false, nullptr, true, std::move(reactions));
+}
+
+void MessagesManager::on_get_message_reaction_list(FullMessageId full_message_id, const string &reaction,
+ FlatHashMap<string, vector<DialogId>> reactions, int32 total_count) {
+ const Message *m = get_message_force(full_message_id, "on_get_message_reaction_list");
+ if (m == nullptr || m->reactions == nullptr) {
+ return;
+ }
+
+ // it's impossible to use received reactions to update message reactions, because there is no way to find,
+ // which reactions are chosen by the current user, so just reload message reactions for consistency
+ if (m->reactions->are_consistent_with_list(reaction, std::move(reactions), total_count)) {
+ return;
+ }
+
+ LOG(INFO) << "Need reload reactions in " << full_message_id << " for consistency";
+
+ auto it = pending_reactions_.find(full_message_id);
+ if (it != pending_reactions_.end()) {
+ it->second.was_updated = true;
} else {
- if (d == nullptr) {
- td_->updates_manager_->schedule_get_difference("on_update_channel_too_long");
- } else if (update_pts > d->pts) {
- get_channel_difference(dialog_id, d->pts, true, "on_update_channel_too_long 3");
+ queue_message_reactions_reload(full_message_id);
+ }
+}
+
+void MessagesManager::on_update_message_interaction_info(FullMessageId full_message_id, int32 view_count,
+ int32 forward_count, bool has_reply_info,
+ tl_object_ptr<telegram_api::messageReplies> &&reply_info) {
+ if (view_count < 0 || forward_count < 0) {
+ LOG(ERROR) << "Receive " << view_count << "/" << forward_count << " interaction counters for " << full_message_id;
+ return;
+ }
+ update_message_interaction_info(full_message_id, view_count, forward_count, has_reply_info, std::move(reply_info),
+ false, nullptr);
+}
+
+void MessagesManager::on_pending_message_views_timeout(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ const size_t MAX_MESSAGE_VIEWS = 100; // server side limit
+ vector<MessageId> message_ids;
+ message_ids.reserve(min(d->pending_viewed_message_ids.size(), MAX_MESSAGE_VIEWS));
+ for (auto message_id : d->pending_viewed_message_ids) {
+ auto *m = get_message(d, message_id);
+ if (m == nullptr) {
+ continue;
+ }
+ if (m->has_get_message_views_query) {
+ if (!d->increment_view_counter || m->need_view_counter_increment) {
+ continue;
+ }
+ m->need_view_counter_increment = true;
+ } else {
+ m->has_get_message_views_query = true;
+ m->need_view_counter_increment = d->increment_view_counter;
+ }
+ message_ids.push_back(message_id);
+ if (message_ids.size() >= MAX_MESSAGE_VIEWS) {
+ td_->create_handler<GetMessagesViewsQuery>()->send(dialog_id, std::move(message_ids), d->increment_view_counter);
+ message_ids.clear();
}
}
+ if (!message_ids.empty()) {
+ td_->create_handler<GetMessagesViewsQuery>()->send(dialog_id, std::move(message_ids), d->increment_view_counter);
+ }
+ d->pending_viewed_message_ids.clear();
+ d->increment_view_counter = false;
}
-void MessagesManager::on_update_message_views(FullMessageId full_message_id, int32 views) {
+void MessagesManager::update_message_interaction_info(FullMessageId full_message_id, int32 view_count,
+ int32 forward_count, bool has_reply_info,
+ tl_object_ptr<telegram_api::messageReplies> &&reply_info,
+ bool has_reactions, unique_ptr<MessageReactions> &&reactions) {
auto dialog_id = full_message_id.get_dialog_id();
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "update_message_interaction_info");
if (d == nullptr) {
- LOG(INFO) << "Ignore updateMessageViews in unknown " << dialog_id;
return;
}
auto message_id = full_message_id.get_message_id();
- Message *m = get_message_force(d, message_id);
+ Message *m = get_message_force(d, message_id, "update_message_interaction_info");
if (m == nullptr) {
- LOG(INFO) << "Ignore updateMessageViews about unknown " << full_message_id;
+ LOG(INFO) << "Ignore message interaction info about unknown " << full_message_id;
+ if (!message_id.is_scheduled() && message_id > d->last_new_message_id &&
+ dialog_id.get_type() == DialogType::Channel) {
+ get_channel_difference(dialog_id, d->pts, true, "update_message_interaction_info");
+ }
return;
}
- if (update_message_views(full_message_id.get_dialog_id(), m, views)) {
- on_message_changed(d, m, "on_update_message_views");
+ if (view_count < 0) {
+ view_count = m->view_count;
+ }
+ if (forward_count < 0) {
+ forward_count = m->forward_count;
+ }
+ bool is_empty_reply_info = reply_info == nullptr;
+ MessageReplyInfo new_reply_info(td_, std::move(reply_info), td_->auth_manager_->is_bot());
+ if (new_reply_info.is_empty() && !is_empty_reply_info) {
+ has_reply_info = false;
+ }
+
+ if (update_message_interaction_info(d, m, view_count, forward_count, has_reply_info, std::move(new_reply_info),
+ has_reactions, std::move(reactions), "update_message_interaction_info")) {
+ on_message_changed(d, m, true, "update_message_interaction_info");
}
}
-bool MessagesManager::update_message_views(DialogId dialog_id, Message *m, int32 views) {
+bool MessagesManager::is_thread_message(DialogId dialog_id, const Message *m) const {
CHECK(m != nullptr);
- if (views > m->views) {
- m->views = views;
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateMessageViews>(dialog_id.get(), m->message_id.get(), m->views));
+ return is_thread_message(dialog_id, m->message_id, m->reply_info, m->content->get_type());
+}
+
+bool MessagesManager::is_thread_message(DialogId dialog_id, MessageId message_id, const MessageReplyInfo &reply_info,
+ MessageContentType content_type) const {
+ if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
+ return false;
+ }
+ if (!message_id.is_valid() || !message_id.is_server()) {
+ return false;
+ }
+ return !reply_info.is_empty() || reply_info.was_dropped() || content_type == MessageContentType::TopicCreate;
+}
+
+bool MessagesManager::is_active_message_reply_info(DialogId dialog_id, const MessageReplyInfo &reply_info) const {
+ if (reply_info.is_empty()) {
+ return false;
+ }
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return false;
+ }
+
+ if (!reply_info.is_comment_) {
+ return true;
+ }
+ if (!is_broadcast_channel(dialog_id)) {
return true;
}
+
+ auto channel_id = dialog_id.get_channel_id();
+ if (!td_->contacts_manager_->get_channel_has_linked_channel(channel_id)) {
+ return false;
+ }
+
+ auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(channel_id);
+ if (!linked_channel_id.is_valid()) {
+ // keep the comment button while linked channel is unknown
+ send_closure_later(G()->contacts_manager(), &ContactsManager::load_channel_full, channel_id, false, Promise<Unit>(),
+ "is_active_message_reply_info");
+ return true;
+ }
+
+ if (linked_channel_id != reply_info.channel_id_) {
+ return false;
+ }
+
+ return true;
+}
+
+bool MessagesManager::is_visible_message_reply_info(DialogId dialog_id, const Message *m) const {
+ CHECK(m != nullptr);
+ if (!m->message_id.is_valid()) {
+ return false;
+ }
+ bool is_broadcast = is_broadcast_channel(dialog_id);
+ if (!m->message_id.is_server() && !(is_broadcast && m->message_id.is_yet_unsent())) {
+ return false;
+ }
+ if (is_broadcast && (m->had_reply_markup || m->reply_markup != nullptr)) {
+ return false;
+ }
+ if (!is_active_message_reply_info(dialog_id, m->reply_info)) {
+ return false;
+ }
+ if (m->reply_info.is_comment_ && is_broadcast &&
+ td_->contacts_manager_->have_channel_force(m->reply_info.channel_id_) &&
+ !td_->contacts_manager_->have_input_peer_channel(m->reply_info.channel_id_, AccessRights::Read)) {
+ // keep the comment button while have no information about the linked channel
+ return false;
+ }
+ return true;
+}
+
+bool MessagesManager::is_visible_message_reactions(DialogId dialog_id, const Message *m) const {
+ if (m == nullptr || !m->message_id.is_valid()) {
+ return false;
+ }
+
+ const Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ if (get_message_active_reactions(d, m).empty()) {
+ return false;
+ }
+ if (m->available_reactions_generation != d->available_reactions_generation) {
+ return false;
+ }
+ return true;
+}
+
+bool MessagesManager::has_unread_message_reactions(DialogId dialog_id, const Message *m) const {
+ CHECK(m != nullptr);
+ return m->reactions != nullptr && !m->reactions->unread_reactions_.empty() &&
+ is_visible_message_reactions(dialog_id, m);
+}
+
+void MessagesManager::on_message_reply_info_changed(DialogId dialog_id, const Message *m) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (is_visible_message_reply_info(dialog_id, m)) {
+ send_update_message_interaction_info(dialog_id, m);
+ }
+}
+
+td_api::object_ptr<td_api::messageInteractionInfo> MessagesManager::get_message_interaction_info_object(
+ DialogId dialog_id, const Message *m) const {
+ bool is_visible_reply_info = is_visible_message_reply_info(dialog_id, m);
+ bool has_reactions =
+ m->reactions != nullptr && !m->reactions->reactions_.empty() && is_visible_message_reactions(dialog_id, m);
+ if (m->view_count == 0 && m->forward_count == 0 && !is_visible_reply_info && !has_reactions) {
+ return nullptr;
+ }
+ if (m->message_id.is_scheduled() && (m->forward_info == nullptr || is_broadcast_channel(dialog_id))) {
+ return nullptr;
+ }
+ if (m->message_id.is_local() && m->forward_info == nullptr) {
+ return nullptr;
+ }
+
+ td_api::object_ptr<td_api::messageReplyInfo> reply_info;
+ if (is_visible_reply_info) {
+ auto expected_dialog_id = m->reply_info.is_comment_ ? DialogId(m->reply_info.channel_id_) : dialog_id;
+ const Dialog *d = get_dialog(expected_dialog_id);
+ reply_info =
+ m->reply_info.get_message_reply_info_object(td_, d != nullptr ? d->last_read_inbox_message_id : MessageId());
+ CHECK(reply_info != nullptr);
+ }
+
+ vector<td_api::object_ptr<td_api::messageReaction>> reactions;
+ if (has_reactions) {
+ UserId my_user_id;
+ UserId peer_user_id;
+ if (dialog_id.get_type() == DialogType::User) {
+ my_user_id = td_->contacts_manager_->get_my_id();
+ peer_user_id = dialog_id.get_user_id();
+ }
+ reactions = m->reactions->get_message_reactions_object(td_, my_user_id, peer_user_id);
+ }
+
+ return td_api::make_object<td_api::messageInteractionInfo>(m->view_count, m->forward_count, std::move(reply_info),
+ std::move(reactions));
+}
+
+vector<td_api::object_ptr<td_api::unreadReaction>> MessagesManager::get_unread_reactions_object(
+ DialogId dialog_id, const Message *m) const {
+ if (!has_unread_message_reactions(dialog_id, m)) {
+ return {};
+ }
+
+ vector<td_api::object_ptr<td_api::unreadReaction>> unread_reactions;
+ for (const auto &unread_reaction : m->reactions->unread_reactions_) {
+ auto unread_reaction_object = unread_reaction.get_unread_reaction_object(td_);
+ if (unread_reaction_object != nullptr) {
+ unread_reactions.push_back(std::move(unread_reaction_object));
+ }
+ }
+ return unread_reactions;
+}
+
+bool MessagesManager::update_message_interaction_info(Dialog *d, Message *m, int32 view_count, int32 forward_count,
+ bool has_reply_info, MessageReplyInfo &&reply_info,
+ bool has_reactions, unique_ptr<MessageReactions> &&reactions,
+ const char *source) {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ auto dialog_id = d->dialog_id;
+ m->interaction_info_update_date = G()->unix_time(); // doesn't force message saving
+ if (m->message_id.is_valid_scheduled()) {
+ has_reply_info = false;
+ has_reactions = false;
+ }
+ bool need_update_reply_info = has_reply_info && m->reply_info.need_update_to(reply_info);
+ if (has_reply_info && m->reply_info.channel_id_ == reply_info.channel_id_) {
+ if (need_update_reply_info) {
+ reply_info.update_max_message_ids(m->reply_info);
+ } else {
+ if (m->reply_info.update_max_message_ids(reply_info) && view_count <= m->view_count &&
+ forward_count <= m->forward_count) {
+ on_message_reply_info_changed(dialog_id, m);
+ on_message_changed(d, m, true, "update_message_interaction_info");
+ }
+ }
+ }
+ if (has_reactions) {
+ auto it = pending_reactions_.find({dialog_id, m->message_id});
+ if (it != pending_reactions_.end()) {
+ has_reactions = false;
+ it->second.was_updated = true;
+ }
+ }
+ if (has_reactions && reactions != nullptr) {
+ if (m->reactions != nullptr) {
+ reactions->update_from(*m->reactions);
+ }
+ reactions->sort_reactions(active_reaction_pos_);
+ reactions->fix_chosen_reaction(get_my_dialog_id());
+ if (d->default_send_message_as_dialog_id.is_valid()) {
+ // the reaction could be set by previous owner of the broadcast
+ // reactions->fix_chosen_reaction(d->default_send_message_as_dialog_id);
+ }
+ }
+ bool need_update_reactions =
+ has_reactions && MessageReactions::need_update_message_reactions(m->reactions.get(), reactions.get());
+ bool need_update_unread_reactions =
+ has_reactions && MessageReactions::need_update_unread_reactions(m->reactions.get(), reactions.get());
+ bool need_update_chosen_reaction_order = has_reactions && reactions != nullptr && m->reactions != nullptr &&
+ m->reactions->chosen_reaction_order_ != reactions->chosen_reaction_order_;
+ if (view_count > m->view_count || forward_count > m->forward_count || need_update_reply_info ||
+ need_update_reactions || need_update_unread_reactions || need_update_chosen_reaction_order) {
+ LOG(DEBUG) << "Update interaction info of " << FullMessageId{dialog_id, m->message_id} << " from " << m->view_count
+ << '/' << m->forward_count << '/' << m->reply_info << '/' << m->reactions << " to " << view_count << '/'
+ << forward_count << '/' << reply_info << '/' << reactions;
+ bool need_update = false;
+ if (view_count > m->view_count) {
+ m->view_count = view_count;
+ need_update = true;
+ }
+ if (forward_count > m->forward_count) {
+ m->forward_count = forward_count;
+ need_update = true;
+ }
+ if (need_update_reply_info) {
+ if (m->reply_info.channel_id_ != reply_info.channel_id_) {
+ if (m->reply_info.channel_id_.is_valid() && reply_info.channel_id_.is_valid() && m->message_id.is_server()) {
+ LOG(ERROR) << "Reply info of " << FullMessageId{dialog_id, m->message_id} << " changed from " << m->reply_info
+ << " to " << reply_info << " from " << source;
+ }
+ }
+ m->reply_info = std::move(reply_info);
+ if (!m->top_thread_message_id.is_valid() && is_thread_message(dialog_id, m)) {
+ m->top_thread_message_id = m->message_id;
+ }
+ need_update |= is_visible_message_reply_info(dialog_id, m);
+ }
+ int32 new_dialog_unread_reaction_count = -1;
+ if (need_update_reactions || need_update_unread_reactions) {
+ CHECK(m->message_id.is_valid());
+
+ int32 unread_reaction_diff = 0;
+ unread_reaction_diff -= (has_unread_message_reactions(dialog_id, m) ? 1 : 0);
+ m->reactions = std::move(reactions);
+ m->available_reactions_generation = d->available_reactions_generation;
+ unread_reaction_diff += (has_unread_message_reactions(dialog_id, m) ? 1 : 0);
+
+ if (is_visible_message_reactions(dialog_id, m)) {
+ need_update |= need_update_reactions;
+ if (need_update_unread_reactions) {
+ if (unread_reaction_diff != 0) {
+ // remove_message_notification_id(d, m, true, true);
+
+ if (d->unread_reaction_count + unread_reaction_diff < 0) {
+ if (is_dialog_inited(d)) {
+ LOG(ERROR) << "Unread reaction count of " << dialog_id << " became negative from " << source;
+ }
+ } else {
+ set_dialog_unread_reaction_count(d, d->unread_reaction_count + unread_reaction_diff);
+ on_dialog_updated(dialog_id, "update_message_interaction_info");
+ }
+ }
+
+ if (unread_reaction_diff < 0) {
+ send_update_message_unread_reactions(dialog_id, m, d->unread_reaction_count);
+ } else {
+ new_dialog_unread_reaction_count = d->unread_reaction_count;
+ }
+ }
+ }
+ } else if (has_reactions) {
+ bool is_changed = false;
+ if (m->available_reactions_generation != d->available_reactions_generation) {
+ m->available_reactions_generation = d->available_reactions_generation;
+ is_changed = true;
+ }
+ if (need_update_chosen_reaction_order) {
+ m->reactions->chosen_reaction_order_ = std::move(reactions->chosen_reaction_order_);
+ is_changed = true;
+ }
+ if (is_changed) {
+ on_message_changed(d, m, false, "update_message_interaction_info");
+ }
+ }
+ if (need_update) {
+ send_update_message_interaction_info(dialog_id, m);
+ }
+ if (new_dialog_unread_reaction_count >= 0) {
+ send_update_message_unread_reactions(dialog_id, m, new_dialog_unread_reaction_count);
+ }
+ return true;
+ } else if (has_reactions && m->available_reactions_generation != d->available_reactions_generation) {
+ m->available_reactions_generation = d->available_reactions_generation;
+ on_message_changed(d, m, false, "update_message_interaction_info");
+ }
return false;
}
+void MessagesManager::on_update_live_location_viewed(FullMessageId full_message_id) {
+ LOG(DEBUG) << "Live location was viewed in " << full_message_id;
+ if (!are_active_live_location_messages_loaded_) {
+ get_active_live_location_messages(PromiseCreator::lambda([actor_id = actor_id(this), full_message_id](Unit result) {
+ send_closure(actor_id, &MessagesManager::on_update_live_location_viewed, full_message_id);
+ }));
+ return;
+ }
+
+ auto active_live_location_message_ids = get_active_live_location_messages(Auto());
+ if (!td::contains(active_live_location_message_ids, full_message_id)) {
+ LOG(DEBUG) << "Can't find " << full_message_id << " in " << active_live_location_message_ids;
+ return;
+ }
+
+ send_update_message_live_location_viewed(full_message_id);
+}
+
+void MessagesManager::on_update_some_live_location_viewed(Promise<Unit> &&promise) {
+ LOG(DEBUG) << "Some live location was viewed";
+ if (!are_active_live_location_messages_loaded_) {
+ get_active_live_location_messages(
+ PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Unit result) mutable {
+ send_closure(actor_id, &MessagesManager::on_update_some_live_location_viewed, std::move(promise));
+ }));
+ return;
+ }
+
+ // update all live locations, because it is unknown, which exactly was viewed
+ auto active_live_location_message_ids = get_active_live_location_messages(Auto());
+ for (const auto &full_message_id : active_live_location_message_ids) {
+ send_update_message_live_location_viewed(full_message_id);
+ }
+
+ promise.set_value(Unit());
+}
+
+void MessagesManager::on_update_message_extended_media(
+ FullMessageId full_message_id, telegram_api::object_ptr<telegram_api::MessageExtendedMedia> extended_media) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "on_update_message_extended_media");
+ if (d == nullptr) {
+ LOG(INFO) << "Ignore update of message extended media in unknown " << dialog_id;
+ return;
+ }
+
+ auto m = get_message_force(d, full_message_id.get_message_id(), "on_update_message_extended_media");
+ if (m == nullptr) {
+ LOG(INFO) << "Ignore update of message extended media in unknown " << full_message_id;
+ return;
+ }
+
+ auto content = m->content.get();
+ auto content_type = content->get_type();
+ if (content_type != MessageContentType::Invoice) {
+ if (content_type != MessageContentType::Unsupported) {
+ LOG(ERROR) << "Receive updateMessageExtendedMedia for " << full_message_id << " of type " << content_type;
+ }
+ return;
+ }
+ if (update_message_content_extended_media(content, std::move(extended_media), dialog_id, td_)) {
+ send_update_message_content(d, m, true, "on_update_message_extended_media");
+ on_message_changed(d, m, true, "on_update_message_extended_media");
+ on_message_notification_changed(d, m, "on_update_message_extended_media"); // usually a no-op
+ }
+}
+
+bool MessagesManager::need_skip_bot_commands(DialogId dialog_id, const Message *m) const {
+ if (td_->auth_manager_->is_bot()) {
+ return false;
+ }
+
+ if (m != nullptr && m->message_id.is_scheduled()) {
+ return true;
+ }
+
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ return (d->is_has_bots_inited && !d->has_bots) || is_broadcast_channel(dialog_id);
+}
+
+void MessagesManager::on_external_update_message_content(FullMessageId full_message_id) {
+ Dialog *d = get_dialog(full_message_id.get_dialog_id());
+ CHECK(d != nullptr);
+ Message *m = get_message(d, full_message_id.get_message_id());
+ CHECK(m != nullptr);
+ send_update_message_content(d, m, true, "on_external_update_message_content");
+ // must not call on_message_changed, because the message itself wasn't changed
+ if (m->message_id == d->last_message_id) {
+ send_update_chat_last_message_impl(d, "on_external_update_message_content");
+ }
+ on_message_notification_changed(d, m, "on_external_update_message_content");
+}
+
+void MessagesManager::on_update_message_content(FullMessageId full_message_id) {
+ Dialog *d = get_dialog(full_message_id.get_dialog_id());
+ CHECK(d != nullptr);
+ Message *m = get_message(d, full_message_id.get_message_id());
+ CHECK(m != nullptr);
+ send_update_message_content(d, m, true, "on_update_message_content");
+ on_message_changed(d, m, true, "on_update_message_content");
+ on_message_notification_changed(d, m, "on_update_message_content");
+}
+
bool MessagesManager::update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention,
const char *source) {
- CHECK(m != nullptr) << source;
+ LOG_CHECK(m != nullptr) << source;
+ CHECK(!m->message_id.is_scheduled());
if (!contains_unread_mention && m->contains_unread_mention) {
+ remove_message_notification_id(d, m, true, true); // must be called before contains_unread_mention is updated
+
m->contains_unread_mention = false;
if (d->unread_mention_count == 0) {
- LOG_IF(ERROR, d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] != -1)
- << "Unread mention count of " << d->dialog_id << " became negative from " << source;
+ if (is_dialog_inited(d)) {
+ LOG(ERROR) << "Unread mention count of " << d->dialog_id << " became negative from " << source;
+ }
} else {
- d->unread_mention_count--;
- d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] =
- d->unread_mention_count;
+ set_dialog_unread_mention_count(d, d->unread_mention_count - 1);
on_dialog_updated(d->dialog_id, "update_message_contains_unread_mention");
}
LOG(INFO) << "Update unread mention message count in " << d->dialog_id << " to " << d->unread_mention_count
@@ -5588,28 +7494,78 @@ bool MessagesManager::update_message_contains_unread_mention(Dialog *d, Message
return false;
}
-void MessagesManager::on_read_channel_inbox(ChannelId channel_id, MessageId max_message_id, int32 server_unread_count) {
+bool MessagesManager::remove_message_unread_reactions(Dialog *d, Message *m, const char *source) {
+ CHECK(m != nullptr);
+ CHECK(!m->message_id.is_scheduled());
+ if (!has_unread_message_reactions(d->dialog_id, m)) {
+ return false;
+ }
+ // remove_message_notification_id(d, m, true, true);
+
+ m->reactions->unread_reactions_.clear();
+ if (d->unread_reaction_count == 0) {
+ if (is_dialog_inited(d)) {
+ LOG(ERROR) << "Unread reaction count of " << d->dialog_id << " became negative from " << source;
+ }
+ } else {
+ set_dialog_unread_reaction_count(d, d->unread_reaction_count - 1);
+ on_dialog_updated(d->dialog_id, "remove_message_unread_reactions");
+ }
+ LOG(INFO) << "Update unread reaction count in " << d->dialog_id << " to " << d->unread_reaction_count
+ << " by reading " << m->message_id << " from " << source;
+
+ send_update_message_unread_reactions(d->dialog_id, m, d->unread_reaction_count);
+
+ return true;
+}
+
+void MessagesManager::on_read_channel_inbox(ChannelId channel_id, MessageId max_message_id, int32 server_unread_count,
+ int32 pts, const char *source) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ CHECK(!max_message_id.is_scheduled());
+ if (!max_message_id.is_valid() && server_unread_count <= 0) {
+ return;
+ }
+
DialogId dialog_id(channel_id);
- if (max_message_id.is_valid() || server_unread_count > 0) {
- /*
- // dropping unread count can make things worse, so don't drop it
- if (server_unread_count > 0 && G()->parameters().use_message_db) {
- const Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr) {
- return;
- }
+ Dialog *d = get_dialog_force(dialog_id, source);
+ if (d == nullptr) {
+ LOG(INFO) << "Receive read inbox in unknown " << dialog_id << " from " << source;
+ return;
+ }
- if (d->is_last_read_inbox_message_id_inited) {
- server_unread_count = -1;
+ /*
+ // dropping unread count can make things worse, so don't drop it
+ if (server_unread_count > 0 && G()->parameters().use_message_db && d->is_last_read_inbox_message_id_inited) {
+ server_unread_count = -1;
+ }
+ */
+
+ if (d->pts == pts) {
+ read_history_inbox(dialog_id, max_message_id, server_unread_count, source);
+ } else if (d->pts > pts) {
+ // outdated update, need to repair server_unread_count from the server
+ repair_channel_server_unread_count(d);
+ } else {
+ // update from the future, keep it until it can be applied
+ if (pts >= d->pending_read_channel_inbox_pts) {
+ if (d->pending_read_channel_inbox_pts == 0) {
+ channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
+ d->pending_read_channel_inbox_pts = pts;
+ d->pending_read_channel_inbox_max_message_id = max_message_id;
+ d->pending_read_channel_inbox_server_unread_count = server_unread_count;
+ on_dialog_updated(dialog_id, "on_read_channel_inbox");
}
- */
- read_history_inbox(dialog_id, max_message_id, server_unread_count, "on_read_channel_inbox");
}
}
void MessagesManager::on_read_channel_outbox(ChannelId channel_id, MessageId max_message_id) {
DialogId dialog_id(channel_id);
+ CHECK(!max_message_id.is_scheduled());
if (max_message_id.is_valid()) {
read_history_outbox(dialog_id, max_message_id);
}
@@ -5623,6 +7579,7 @@ void MessagesManager::on_update_channel_max_unavailable_message_id(ChannelId cha
}
DialogId dialog_id(channel_id);
+ CHECK(!max_unavailable_message_id.is_scheduled());
if (!max_unavailable_message_id.is_valid() && max_unavailable_message_id != MessageId()) {
LOG(ERROR) << "Receive wrong max_unavailable_message_id: " << max_unavailable_message_id;
max_unavailable_message_id = MessageId();
@@ -5631,220 +7588,350 @@ void MessagesManager::on_update_channel_max_unavailable_message_id(ChannelId cha
"on_update_channel_max_unavailable_message_id");
}
-bool MessagesManager::need_cancel_user_dialog_action(int32 action_id, int32 message_content_id) {
- if (message_content_id == -1) {
- return true;
+void MessagesManager::on_update_dialog_online_member_count(DialogId dialog_id, int32 online_member_count,
+ bool is_from_server) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
}
- if (action_id == td_api::chatActionTyping::ID) {
- return message_content_id == MessageText::ID || message_content_id == MessageGame::ID ||
- can_have_message_content_caption(message_content_id);
- }
-
- switch (message_content_id) {
- case MessageAnimation::ID:
- case MessageAudio::ID:
- case MessageDocument::ID:
- return action_id == td_api::chatActionUploadingDocument::ID;
- case MessageExpiredPhoto::ID:
- case MessagePhoto::ID:
- return action_id == td_api::chatActionUploadingPhoto::ID;
- case MessageExpiredVideo::ID:
- case MessageVideo::ID:
- return action_id == td_api::chatActionRecordingVideo::ID || action_id == td_api::chatActionUploadingVideo::ID;
- case MessageVideoNote::ID:
- return action_id == td_api::chatActionRecordingVideoNote::ID ||
- action_id == td_api::chatActionUploadingVideoNote::ID;
- case MessageVoiceNote::ID:
- return action_id == td_api::chatActionRecordingVoiceNote::ID ||
- action_id == td_api::chatActionUploadingVoiceNote::ID;
- case MessageContact::ID:
- return action_id == td_api::chatActionChoosingContact::ID;
- case MessageLiveLocation::ID:
- case MessageLocation::ID:
- case MessageVenue::ID:
- return action_id == td_api::chatActionChoosingLocation::ID;
- case MessageText::ID:
- case MessageGame::ID:
- case MessageUnsupported::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- return false;
- default:
- UNREACHABLE();
- return false;
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive number of online members in invalid " << dialog_id;
+ return;
+ }
+
+ if (is_broadcast_channel(dialog_id)) {
+ LOG_IF(ERROR, online_member_count != 0)
+ << "Receive " << online_member_count << " as a number of online members in a channel " << dialog_id;
+ return;
+ }
+
+ if (online_member_count < 0) {
+ LOG(ERROR) << "Receive " << online_member_count << " as a number of online members in a " << dialog_id;
+ return;
}
+
+ set_dialog_online_member_count(dialog_id, online_member_count, is_from_server,
+ "on_update_channel_online_member_count");
}
-void MessagesManager::on_user_dialog_action(DialogId dialog_id, UserId user_id,
- tl_object_ptr<td_api::ChatAction> &&action, int32 message_content_id) {
- if (td_->auth_manager_->is_bot() || !user_id.is_valid() || is_broadcast_channel(dialog_id)) {
+void MessagesManager::on_update_delete_scheduled_messages(DialogId dialog_id,
+ vector<ScheduledServerMessageId> &&server_message_ids) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive deleted scheduled messages in invalid " << dialog_id;
return;
}
- bool is_canceled = action == nullptr || action->get_id() == td_api::chatActionCancel::ID;
+ Dialog *d = get_dialog_force(dialog_id, "on_update_delete_scheduled_messages");
+ if (d == nullptr) {
+ LOG(INFO) << "Skip updateDeleteScheduledMessages in unknown " << dialog_id;
+ return;
+ }
+
+ vector<int64> deleted_message_ids;
+ for (auto server_message_id : server_message_ids) {
+ if (!server_message_id.is_valid()) {
+ LOG(ERROR) << "Incoming update tries to delete scheduled message " << server_message_id.get();
+ continue;
+ }
+
+ auto message = do_delete_scheduled_message(d, MessageId(server_message_id, std::numeric_limits<int32>::max()), true,
+ "on_update_delete_scheduled_messages");
+ if (message != nullptr) {
+ deleted_message_ids.push_back(message->message_id.get());
+ }
+ }
+
+ send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true);
+
+ send_update_chat_has_scheduled_messages(d, true);
+}
+
+void MessagesManager::on_update_created_public_broadcasts(vector<ChannelId> channel_ids) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ if (created_public_broadcasts_inited_ && created_public_broadcasts_ == channel_ids) {
+ return;
+ }
+
+ LOG(INFO) << "Update create public channels to " << channel_ids;
+ for (auto channel_id : channel_ids) {
+ force_create_dialog(DialogId(channel_id), "on_update_created_public_broadcasts");
+ }
+
+ created_public_broadcasts_inited_ = true;
+ created_public_broadcasts_ = std::move(channel_ids);
+}
+
+void MessagesManager::on_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id,
+ DialogAction action, int32 date, MessageContentType message_content_type) {
+ if (td_->auth_manager_->is_bot() || !typing_dialog_id.is_valid()) {
+ return;
+ }
+ if (top_thread_message_id != MessageId() && !top_thread_message_id.is_valid()) {
+ LOG(ERROR) << "Ignore " << action << " in the message thread of " << top_thread_message_id;
+ return;
+ }
+
+ auto dialog_type = dialog_id.get_type();
+ if (action == DialogAction::get_speaking_action()) {
+ if ((dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) || top_thread_message_id.is_valid()) {
+ LOG(ERROR) << "Receive " << action << " in thread of " << top_thread_message_id << " in " << dialog_id;
+ return;
+ }
+ const Dialog *d = get_dialog_force(dialog_id, "on_dialog_action");
+ if (d != nullptr && d->active_group_call_id.is_valid()) {
+ auto group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, dialog_id);
+ td_->group_call_manager_->on_user_speaking_in_group_call(group_call_id, typing_dialog_id, date);
+ }
+ return;
+ }
+
+ if (is_broadcast_channel(dialog_id)) {
+ return;
+ }
+
+ auto typing_dialog_type = typing_dialog_id.get_type();
+ if (typing_dialog_type != DialogType::User && dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) {
+ LOG(ERROR) << "Ignore " << action << " of " << typing_dialog_id << " in " << dialog_id;
+ return;
+ }
+
+ {
+ auto message_import_progress = action.get_importing_messages_action_progress();
+ if (message_import_progress >= 0) {
+ // TODO
+ return;
+ }
+ }
+
+ {
+ auto clicking_info = action.get_clicking_animated_emoji_action_info();
+ if (!clicking_info.data.empty()) {
+ if (date > G()->unix_time() - 10 && dialog_type == DialogType::User && dialog_id == typing_dialog_id) {
+ FullMessageId full_message_id{dialog_id, MessageId(ServerMessageId(clicking_info.message_id))};
+ auto *m = get_message_force(full_message_id, "on_dialog_action");
+ if (m != nullptr) {
+ on_message_content_animated_emoji_clicked(m->content.get(), full_message_id, td_,
+ std::move(clicking_info.emoji), std::move(clicking_info.data));
+ }
+ }
+ return;
+ }
+ }
+
+ if (is_unsent_animated_emoji_click(td_, dialog_id, action)) {
+ LOG(DEBUG) << "Ignore unsent " << action;
+ return;
+ }
+
+ if (!have_dialog(dialog_id)) {
+ LOG(DEBUG) << "Ignore " << action << " in unknown " << dialog_id;
+ return;
+ }
+
+ if (typing_dialog_type == DialogType::User) {
+ if (!td_->contacts_manager_->have_min_user(typing_dialog_id.get_user_id())) {
+ LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id.get_user_id();
+ return;
+ }
+ } else {
+ if (!have_dialog_info_force(typing_dialog_id)) {
+ LOG(DEBUG) << "Ignore " << action << " of unknown " << typing_dialog_id;
+ return;
+ }
+ force_create_dialog(typing_dialog_id, "on_dialog_action", true);
+ if (!have_dialog(typing_dialog_id)) {
+ LOG(ERROR) << "Failed to create typing " << typing_dialog_id;
+ return;
+ }
+ }
+
+ bool is_canceled = action == DialogAction();
+ if ((!is_canceled || message_content_type != MessageContentType::None) && typing_dialog_type == DialogType::User) {
+ td_->contacts_manager_->on_update_user_local_was_online(typing_dialog_id.get_user_id(), date);
+ }
+
+ if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) {
+ CHECK(typing_dialog_type == DialogType::User);
+ auto user_id = typing_dialog_id.get_user_id();
+ if (!td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_status_exact(user_id) &&
+ !get_dialog(dialog_id)->is_opened && !is_canceled) {
+ return;
+ }
+ }
+
if (is_canceled) {
+ // passed top_thread_message_id must be ignored
auto actions_it = active_dialog_actions_.find(dialog_id);
if (actions_it == active_dialog_actions_.end()) {
return;
}
auto &active_actions = actions_it->second;
- auto it = std::find_if(active_actions.begin(), active_actions.end(),
- [user_id](const ActiveDialogAction &action) { return action.user_id == user_id; });
+ auto it = std::find_if(
+ active_actions.begin(), active_actions.end(),
+ [typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; });
if (it == active_actions.end()) {
return;
}
- if (!need_cancel_user_dialog_action(it->action_id, message_content_id)) {
+ if (!(typing_dialog_type == DialogType::User &&
+ td_->contacts_manager_->is_user_bot(typing_dialog_id.get_user_id())) &&
+ !it->action.is_canceled_by_message_of_type(message_content_type)) {
return;
}
- LOG(DEBUG) << "Cancel action of " << user_id << " in " << dialog_id;
+ LOG(DEBUG) << "Cancel action of " << typing_dialog_id << " in " << dialog_id;
+ top_thread_message_id = it->top_thread_message_id;
active_actions.erase(it);
if (active_actions.empty()) {
active_dialog_actions_.erase(dialog_id);
LOG(DEBUG) << "Cancel action timeout in " << dialog_id;
active_dialog_action_timeout_.cancel_timeout(dialog_id.get());
}
- if (action == nullptr) {
- action = make_tl_object<td_api::chatActionCancel>();
- }
} else {
+ if (date < G()->unix_time_cached() - DIALOG_ACTION_TIMEOUT - 60) {
+ LOG(DEBUG) << "Ignore too old action of " << typing_dialog_id << " in " << dialog_id << " sent at " << date;
+ return;
+ }
auto &active_actions = active_dialog_actions_[dialog_id];
- auto it = std::find_if(active_actions.begin(), active_actions.end(),
- [user_id](const ActiveDialogAction &action) { return action.user_id == user_id; });
- int32 prev_action_id = 0;
- int32 prev_progress = 0;
+ auto it = std::find_if(
+ active_actions.begin(), active_actions.end(),
+ [typing_dialog_id](const ActiveDialogAction &action) { return action.typing_dialog_id == typing_dialog_id; });
+ MessageId prev_top_thread_message_id;
+ DialogAction prev_action;
if (it != active_actions.end()) {
- LOG(DEBUG) << "Re-add action of " << user_id << " in " << dialog_id;
- prev_action_id = it->action_id;
- prev_progress = it->progress;
+ LOG(DEBUG) << "Re-add action of " << typing_dialog_id << " in " << dialog_id;
+ prev_top_thread_message_id = it->top_thread_message_id;
+ prev_action = it->action;
active_actions.erase(it);
} else {
- LOG(DEBUG) << "Add action of " << user_id << " in " << dialog_id;
- }
-
- auto action_id = action->get_id();
- auto progress = [&] {
- switch (action_id) {
- case td_api::chatActionUploadingVideo::ID:
- return static_cast<td_api::chatActionUploadingVideo &>(*action).progress_;
- case td_api::chatActionUploadingVoiceNote::ID:
- return static_cast<td_api::chatActionUploadingVoiceNote &>(*action).progress_;
- case td_api::chatActionUploadingPhoto::ID:
- return static_cast<td_api::chatActionUploadingPhoto &>(*action).progress_;
- case td_api::chatActionUploadingDocument::ID:
- return static_cast<td_api::chatActionUploadingDocument &>(*action).progress_;
- case td_api::chatActionUploadingVideoNote::ID:
- return static_cast<td_api::chatActionUploadingVideoNote &>(*action).progress_;
- default:
- return 0;
- }
- }();
- active_actions.emplace_back(user_id, action_id, Time::now());
- if (action_id == prev_action_id && progress <= prev_progress) {
+ LOG(DEBUG) << "Add action of " << typing_dialog_id << " in " << dialog_id;
+ }
+
+ active_actions.emplace_back(top_thread_message_id, typing_dialog_id, action, Time::now());
+ if (top_thread_message_id == prev_top_thread_message_id && action == prev_action) {
return;
}
+ if (top_thread_message_id != prev_top_thread_message_id && prev_top_thread_message_id.is_valid()) {
+ send_update_chat_action(dialog_id, prev_top_thread_message_id, typing_dialog_id, DialogAction());
+ }
if (active_actions.size() == 1u) {
LOG(DEBUG) << "Set action timeout in " << dialog_id;
active_dialog_action_timeout_.set_timeout_in(dialog_id.get(), DIALOG_ACTION_TIMEOUT);
}
}
- LOG(DEBUG) << "Send action of " << user_id << " in " << dialog_id << ": " << to_string(action);
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateUserChatAction>(
- dialog_id.get(), td_->contacts_manager_->get_user_id_object(user_id, "on_user_dialog_action"),
- std::move(action)));
+ if (top_thread_message_id.is_valid()) {
+ send_update_chat_action(dialog_id, MessageId(), typing_dialog_id, action);
+ }
+ send_update_chat_action(dialog_id, top_thread_message_id, typing_dialog_id, action);
}
-void MessagesManager::cancel_user_dialog_action(DialogId dialog_id, const Message *m) {
+void MessagesManager::cancel_dialog_action(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
- if (m->forward_info != nullptr || m->via_bot_user_id.is_valid() || m->is_channel_post) {
+ if (td_->auth_manager_->is_bot() || m->forward_info != nullptr || m->had_forward_info ||
+ m->via_bot_user_id.is_valid() || m->hide_via_bot || m->is_channel_post || m->message_id.is_scheduled()) {
return;
}
- on_user_dialog_action(dialog_id, m->sender_user_id, nullptr, m->content->get_id());
+ on_dialog_action(dialog_id, MessageId() /*ignored*/, get_message_sender(m), DialogAction(), m->date,
+ m->content->get_type());
+}
+
+void MessagesManager::add_postponed_channel_update(DialogId dialog_id, tl_object_ptr<telegram_api::Update> &&update,
+ int32 new_pts, int32 pts_count, Promise<Unit> &&promise) {
+ postponed_channel_updates_[dialog_id].emplace(
+ new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count, std::move(promise)));
}
void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_ptr<telegram_api::Update> &&update,
- int32 new_pts, int32 pts_count, const char *source,
- bool is_postponed_udpate) {
+ int32 new_pts, int32 pts_count, Promise<Unit> &&promise,
+ const char *source, bool is_postponed_update) {
LOG(INFO) << "Receive from " << source << " pending " << to_string(update);
CHECK(update != nullptr);
- CHECK(dialog_id.get_type() == DialogType::Channel);
+ if (dialog_id.get_type() != DialogType::Channel) {
+ LOG(ERROR) << "Receive channel update in invalid " << dialog_id << " from " << source << ": "
+ << oneline(to_string(update));
+ promise.set_value(Unit());
+ return;
+ }
if (pts_count < 0 || new_pts <= pts_count) {
LOG(ERROR) << "Receive channel update from " << source << " with wrong pts = " << new_pts
<< " or pts_count = " << pts_count << ": " << oneline(to_string(update));
+ promise.set_value(Unit());
+ return;
+ }
+
+ auto channel_id = dialog_id.get_channel_id();
+ if (!td_->contacts_manager_->have_channel(channel_id) && td_->contacts_manager_->have_min_channel(channel_id)) {
+ td_->updates_manager_->schedule_get_difference("add_pending_channel_update 1");
+ promise.set_value(Unit());
return;
}
// TODO need to save all updates that can change result of running queries not associated with pts (for example
- // getHistory) and apply them to result of this queries
+ // getHistory) and apply them to result of these queries
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "add_pending_channel_update 2");
if (d == nullptr) {
auto pts = load_channel_pts(dialog_id);
if (pts > 0) {
- auto channel_id = dialog_id.get_channel_id();
if (!td_->contacts_manager_->have_channel(channel_id)) {
// do not create dialog if there is no info about the channel
+ LOG(INFO) << "There is no info about " << channel_id << ", so ignore " << oneline(to_string(update));
+ promise.set_value(Unit());
return;
}
- d = add_dialog(dialog_id);
+ if (new_pts <= pts && new_pts >= pts - 19999) {
+ LOG(INFO) << "There is no need to process an update with pts " << new_pts << " in " << dialog_id << " with pts "
+ << pts;
+ promise.set_value(Unit());
+ return;
+ }
+
+ if (new_pts > pts && pts != new_pts - pts_count) {
+ LOG(INFO) << "Found a gap in unknown " << dialog_id << " with pts = " << pts << ". new_pts = " << new_pts
+ << ", pts_count = " << pts_count << " in update from " << source;
+ add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise));
+ get_channel_difference(dialog_id, pts, true, "add_pending_channel_update 3");
+ return;
+ }
+
+ d = add_dialog(dialog_id, "add_pending_channel_update 4");
CHECK(d != nullptr);
CHECK(d->pts == pts);
- update_dialog_pos(d, false, "add_pending_channel_update");
+ update_dialog_pos(d, "add_pending_channel_update 5");
}
}
if (d == nullptr) {
// if there is no dialog, it can be created by the update
LOG(INFO) << "Receive pending update from " << source << " about unknown " << dialog_id;
if (running_get_channel_difference(dialog_id)) {
+ add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise));
return;
}
} else {
int32 old_pts = d->pts;
if (new_pts <= old_pts) { // very old or unuseful update
- if (new_pts < old_pts - 19999 && !is_postponed_udpate) {
- // restore channel pts after delete_first_messages
- LOG(ERROR) << "Restore pts in " << d->dialog_id << " from " << source << " after delete_first_messages from "
- << old_pts << " to " << new_pts << " is temporarily disabled, pts_count = " << pts_count
- << ", update is from " << source << ": " << oneline(to_string(update));
- if (old_pts < 10000000) {
- dump_debug_message_op(d, 6);
- }
- get_channel_difference(dialog_id, old_pts, true, "add_pending_channel_update old");
- }
-
if (update->get_id() == telegram_api::updateNewChannelMessage::ID) {
auto update_new_channel_message = static_cast<telegram_api::updateNewChannelMessage *>(update.get());
- auto message_id = get_message_id(update_new_channel_message->message_);
+ auto message_id = get_message_id(update_new_channel_message->message_, false);
FullMessageId full_message_id(dialog_id, message_id);
- if (update_message_ids_.find(full_message_id) != update_message_ids_.end()) {
+ if (update_message_ids_.count(full_message_id) > 0) {
// apply sent channel message
- on_get_message(std::move(update_new_channel_message->message_), true, true, true, true,
+ on_get_message(std::move(update_new_channel_message->message_), true, true, false, true, true,
"updateNewChannelMessage with an awaited message");
+ promise.set_value(Unit());
return;
}
}
@@ -5853,39 +7940,47 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p
if (being_sent_messages_.count(update_sent_message->random_id_) > 0) {
// apply sent channel message
on_send_message_success(update_sent_message->random_id_, update_sent_message->message_id_,
- update_sent_message->date_, FileId(), "process old updateSentChannelMessage");
+ update_sent_message->date_, update_sent_message->ttl_period_, FileId(),
+ "process old updateSentChannelMessage");
+ promise.set_value(Unit());
return;
}
}
- LOG_IF(WARNING, new_pts == old_pts && pts_count == 0)
+ LOG_IF(WARNING, new_pts == old_pts && pts_count == 0 && !is_allowed_useless_update(update))
<< "Receive from " << source << " useless channel update " << oneline(to_string(update));
+ LOG(INFO) << "Skip already applied channel update";
+ promise.set_value(Unit());
return;
}
if (running_get_channel_difference(dialog_id)) {
- if (pts_count > 0) {
- d->postponed_channel_updates.emplace(new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count));
- }
+ LOG(INFO) << "Postpone channel update, because getChannelDifference is run";
+ add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise));
return;
}
- if (old_pts + pts_count != new_pts) {
- LOG(WARNING) << "Found a gap in the " << dialog_id << " with pts = " << old_pts << ". new_pts = " << new_pts
- << ", pts_count = " << pts_count << " in update from " << source;
-
- if (pts_count > 0) {
- d->postponed_channel_updates.emplace(new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count));
+ if (old_pts != new_pts - pts_count) {
+ LOG(INFO) << "Found a gap in the " << dialog_id << " with pts = " << old_pts << ". new_pts = " << new_pts
+ << ", pts_count = " << pts_count << " in update from " << source;
+ if (d->was_opened || td_->contacts_manager_->get_channel_status(channel_id).is_member() ||
+ is_dialog_sponsored(d)) {
+ add_postponed_channel_update(dialog_id, std::move(update), new_pts, pts_count, std::move(promise));
+ get_channel_difference(dialog_id, old_pts, true, "add_pending_channel_update pts mismatch");
+ } else {
+ promise.set_value(Unit());
}
-
- get_channel_difference(dialog_id, old_pts, true, "add_pending_channel_update pts mismatch");
return;
}
}
if (d == nullptr || pts_count > 0) {
- process_channel_update(std::move(update));
- CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differencies_[dialog_id] << '"';
+ if (!process_channel_update(std::move(update)) &&
+ channel_get_difference_retry_timeout_.has_timeout(dialog_id.get())) {
+ promise.set_value(Unit());
+ return;
+ }
+ LOG_CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differencies_[dialog_id] << '"';
} else {
LOG_IF(INFO, update->get_id() != dummyUpdate::ID)
<< "Skip useless channel update from " << source << ": " << to_string(update);
@@ -5894,80 +7989,96 @@ void MessagesManager::add_pending_channel_update(DialogId dialog_id, tl_object_p
if (d == nullptr) {
d = get_dialog(dialog_id);
if (d == nullptr) {
- // dialog was not created by the update
+ LOG(INFO) << "Update didn't created " << dialog_id;
+ promise.set_value(Unit());
return;
}
}
CHECK(new_pts > d->pts);
set_channel_pts(d, new_pts, source);
+ promise.set_value(Unit());
}
-void MessagesManager::set_get_difference_timeout(double timeout) {
- if (!pts_gap_timeout_.has_timeout()) {
- LOG(INFO) << "Gap in pts has found, current pts is " << td_->updates_manager_->get_pts();
- pts_gap_timeout_.set_callback(std::move(UpdatesManager::fill_pts_gap));
- pts_gap_timeout_.set_callback_data(static_cast<void *>(td_));
- pts_gap_timeout_.set_timeout_in(timeout);
- }
+bool MessagesManager::is_old_channel_update(DialogId dialog_id, int32 new_pts) {
+ CHECK(dialog_id.get_type() == DialogType::Channel);
+
+ const Dialog *d = get_dialog_force(dialog_id, "is_old_channel_update");
+ return new_pts <= (d == nullptr ? load_channel_pts(dialog_id) : d->pts);
}
-void MessagesManager::process_update(tl_object_ptr<telegram_api::Update> &&update) {
- switch (update->get_id()) {
+void MessagesManager::process_pts_update(tl_object_ptr<telegram_api::Update> &&update_ptr) {
+ switch (update_ptr->get_id()) {
case dummyUpdate::ID:
LOG(INFO) << "Process dummyUpdate";
break;
- case telegram_api::updateNewMessage::ID:
+ case telegram_api::updateNewMessage::ID: {
+ auto update = move_tl_object_as<telegram_api::updateNewMessage>(update_ptr);
LOG(INFO) << "Process updateNewMessage";
- on_get_message(std::move(move_tl_object_as<telegram_api::updateNewMessage>(update)->message_), true, false, true,
- true, "updateNewMessage");
+ on_get_message(std::move(update->message_), true, false, false, true, true, "updateNewMessage");
break;
+ }
case updateSentMessage::ID: {
- auto send_message_success_update = move_tl_object_as<updateSentMessage>(update);
- LOG(INFO) << "Process updateSentMessage " << send_message_success_update->random_id_;
- on_send_message_success(send_message_success_update->random_id_, send_message_success_update->message_id_,
- send_message_success_update->date_, FileId(), "process updateSentMessage");
+ auto update = move_tl_object_as<updateSentMessage>(update_ptr);
+ LOG(INFO) << "Process updateSentMessage " << update->random_id_;
+ on_send_message_success(update->random_id_, update->message_id_, update->date_, update->ttl_period_, FileId(),
+ "process updateSentMessage");
break;
}
case telegram_api::updateReadMessagesContents::ID: {
- auto read_contents_update = move_tl_object_as<telegram_api::updateReadMessagesContents>(update);
+ auto update = move_tl_object_as<telegram_api::updateReadMessagesContents>(update_ptr);
LOG(INFO) << "Process updateReadMessageContents";
- for (auto &message_id : read_contents_update->messages_) {
+ for (auto &message_id : update->messages_) {
read_message_content_from_updates(MessageId(ServerMessageId(message_id)));
}
break;
}
case telegram_api::updateEditMessage::ID: {
- auto full_message_id =
- on_get_message(std::move(move_tl_object_as<telegram_api::updateEditMessage>(update)->message_), false, false,
- false, false, "updateEditMessage");
+ auto update = move_tl_object_as<telegram_api::updateEditMessage>(update_ptr);
LOG(INFO) << "Process updateEditMessage";
- if (full_message_id != FullMessageId() && td_->auth_manager_->is_bot()) {
- send_update_message_edited(full_message_id);
- }
+ bool had_message = have_message_force(get_full_message_id(update->message_, false), "updateEditMessage");
+ auto full_message_id =
+ on_get_message(std::move(update->message_), false, false, false, false, false, "updateEditMessage");
+ on_message_edited(full_message_id, update->pts_, had_message);
break;
}
case telegram_api::updateDeleteMessages::ID: {
- auto delete_update = move_tl_object_as<telegram_api::updateDeleteMessages>(update);
+ auto update = move_tl_object_as<telegram_api::updateDeleteMessages>(update_ptr);
LOG(INFO) << "Process updateDeleteMessages";
vector<MessageId> message_ids;
- for (auto &message : delete_update->messages_) {
+ for (auto message : update->messages_) {
message_ids.push_back(MessageId(ServerMessageId(message)));
}
delete_messages_from_updates(message_ids);
break;
}
case telegram_api::updateReadHistoryInbox::ID: {
- auto read_update = move_tl_object_as<telegram_api::updateReadHistoryInbox>(update);
+ auto update = move_tl_object_as<telegram_api::updateReadHistoryInbox>(update_ptr);
LOG(INFO) << "Process updateReadHistoryInbox";
- read_history_inbox(DialogId(read_update->peer_), MessageId(ServerMessageId(read_update->max_id_)), -1,
+ DialogId dialog_id(update->peer_);
+ FolderId folder_id;
+ if ((update->flags_ & telegram_api::updateReadHistoryInbox::FOLDER_ID_MASK) != 0) {
+ folder_id = FolderId(update->folder_id_);
+ }
+ on_update_dialog_folder_id(dialog_id, folder_id);
+ read_history_inbox(dialog_id, MessageId(ServerMessageId(update->max_id_)), -1 /*update->still_unread_count*/,
"updateReadHistoryInbox");
break;
}
case telegram_api::updateReadHistoryOutbox::ID: {
- auto read_update = move_tl_object_as<telegram_api::updateReadHistoryOutbox>(update);
+ auto update = move_tl_object_as<telegram_api::updateReadHistoryOutbox>(update_ptr);
LOG(INFO) << "Process updateReadHistoryOutbox";
- read_history_outbox(DialogId(read_update->peer_), MessageId(ServerMessageId(read_update->max_id_)));
+ read_history_outbox(DialogId(update->peer_), MessageId(ServerMessageId(update->max_id_)));
+ break;
+ }
+ case telegram_api::updatePinnedMessages::ID: {
+ auto update = move_tl_object_as<telegram_api::updatePinnedMessages>(update_ptr);
+ LOG(INFO) << "Process updatePinnedMessages";
+ vector<MessageId> message_ids;
+ for (auto message : update->messages_) {
+ message_ids.push_back(MessageId(ServerMessageId(message)));
+ }
+ update_dialog_pinned_messages_from_updates(DialogId(update->peer_), message_ids, update->pinned_);
break;
}
default:
@@ -5976,194 +8087,310 @@ void MessagesManager::process_update(tl_object_ptr<telegram_api::Update> &&updat
CHECK(!td_->updates_manager_->running_get_difference());
}
-void MessagesManager::process_channel_update(tl_object_ptr<telegram_api::Update> &&update) {
- switch (update->get_id()) {
+bool MessagesManager::process_channel_update(tl_object_ptr<telegram_api::Update> &&update_ptr) {
+ switch (update_ptr->get_id()) {
case dummyUpdate::ID:
LOG(INFO) << "Process dummyUpdate";
break;
case updateSentMessage::ID: {
- auto send_message_success_update = move_tl_object_as<updateSentMessage>(update);
- LOG(INFO) << "Process updateSentMessage " << send_message_success_update->random_id_;
- on_send_message_success(send_message_success_update->random_id_, send_message_success_update->message_id_,
- send_message_success_update->date_, FileId(), "process updateSentChannelMessage");
+ auto update = move_tl_object_as<updateSentMessage>(update_ptr);
+ LOG(INFO) << "Process updateSentMessage " << update->random_id_;
+ on_send_message_success(update->random_id_, update->message_id_, update->date_, update->ttl_period_, FileId(),
+ "process updateSentChannelMessage");
break;
}
- case telegram_api::updateNewChannelMessage::ID:
+ case telegram_api::updateNewChannelMessage::ID: {
+ auto update = move_tl_object_as<telegram_api::updateNewChannelMessage>(update_ptr);
LOG(INFO) << "Process updateNewChannelMessage";
- on_get_message(std::move(move_tl_object_as<telegram_api::updateNewChannelMessage>(update)->message_), true, true,
- true, true, "updateNewChannelMessage");
+ on_get_message(std::move(update->message_), true, true, false, true, true, "updateNewChannelMessage");
break;
+ }
case telegram_api::updateDeleteChannelMessages::ID: {
- auto delete_channel_messages_update = move_tl_object_as<telegram_api::updateDeleteChannelMessages>(update);
+ auto update = move_tl_object_as<telegram_api::updateDeleteChannelMessages>(update_ptr);
LOG(INFO) << "Process updateDeleteChannelMessages";
- ChannelId channel_id(delete_channel_messages_update->channel_id_);
+ ChannelId channel_id(update->channel_id_);
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id;
break;
}
vector<MessageId> message_ids;
- for (auto &message : delete_channel_messages_update->messages_) {
- message_ids.push_back(MessageId(ServerMessageId(message)));
+ for (auto &message : update->messages_) {
+ auto message_id = MessageId(ServerMessageId(message));
+ if (message_id.is_valid()) {
+ message_ids.push_back(message_id);
+ } else {
+ LOG(ERROR) << "Receive updateDeleteChannelMessages with message " << message << " in " << channel_id;
+ }
}
- auto dialog_id = DialogId(channel_id);
- delete_dialog_messages_from_updates(dialog_id, message_ids);
+ delete_dialog_messages(DialogId(channel_id), message_ids, true, "updateDeleteChannelMessages");
break;
}
case telegram_api::updateEditChannelMessage::ID: {
- auto full_message_id =
- on_get_message(std::move(move_tl_object_as<telegram_api::updateEditChannelMessage>(update)->message_), false,
- true, false, false, "updateEditChannelMessage");
+ auto update = move_tl_object_as<telegram_api::updateEditChannelMessage>(update_ptr);
LOG(INFO) << "Process updateEditChannelMessage";
- if (full_message_id != FullMessageId() && td_->auth_manager_->is_bot()) {
- send_update_message_edited(full_message_id);
+ bool had_message = have_message_force(get_full_message_id(update->message_, false), "updateEditChannelMessage");
+ auto full_message_id =
+ on_get_message(std::move(update->message_), false, true, false, false, false, "updateEditChannelMessage");
+ if (full_message_id == FullMessageId()) {
+ return false;
}
+ on_message_edited(full_message_id, update->pts_, had_message);
break;
}
- default:
- UNREACHABLE();
- }
-}
-
-void MessagesManager::process_pending_updates() {
- for (auto &update : pending_updates_) {
- process_update(std::move(update.second.update));
- }
-
- td_->updates_manager_->set_pts(accumulated_pts_, "process pending updates")
- .set_value(Unit()); // TODO can't set until get messages really stored on persistent storage
- drop_pending_updates();
-}
+ case telegram_api::updatePinnedChannelMessages::ID: {
+ auto update = move_tl_object_as<telegram_api::updatePinnedChannelMessages>(update_ptr);
+ LOG(INFO) << "Process updatePinnedChannelMessages";
+ ChannelId channel_id(update->channel_id_);
+ if (!channel_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << channel_id;
+ break;
+ }
-void MessagesManager::drop_pending_updates() {
- accumulated_pts_count_ = 0;
- accumulated_pts_ = -1;
- pts_gap_timeout_.cancel_timeout();
- pending_updates_.clear();
-}
+ vector<MessageId> message_ids;
+ for (auto &message : update->messages_) {
+ message_ids.push_back(MessageId(ServerMessageId(message)));
+ }
-NotificationSettingsScope MessagesManager::get_notification_settings_scope(
- tl_object_ptr<telegram_api::NotifyPeer> &&notify_peer_ptr) const {
- switch (notify_peer_ptr->get_id()) {
- case telegram_api::notifyPeer::ID: {
- auto notify_peer = move_tl_object_as<telegram_api::notifyPeer>(notify_peer_ptr);
- return DialogId(notify_peer->peer_).get();
+ update_dialog_pinned_messages_from_updates(DialogId(channel_id), message_ids, update->pinned_);
+ break;
}
- case telegram_api::notifyUsers::ID:
- return NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS;
- case telegram_api::notifyChats::ID:
- return NOTIFICATION_SETTINGS_FOR_GROUP_CHATS;
- case telegram_api::notifyAll::ID:
- return NOTIFICATION_SETTINGS_FOR_ALL_CHATS;
default:
UNREACHABLE();
- return 0;
}
+ return true;
}
-NotificationSettingsScope MessagesManager::get_notification_settings_scope(
- const tl_object_ptr<td_api::NotificationSettingsScope> &scope) const {
- if (scope == nullptr) {
- return NOTIFICATION_SETTINGS_FOR_ALL_CHATS;
+void MessagesManager::on_message_edited(FullMessageId full_message_id, int32 pts, bool had_message) {
+ if (full_message_id == FullMessageId()) {
+ return;
}
- int32 scope_id = scope->get_id();
- switch (scope_id) {
- case td_api::notificationSettingsScopeChat::ID:
- return static_cast<const td_api::notificationSettingsScopeChat *>(scope.get())->chat_id_;
- case td_api::notificationSettingsScopePrivateChats::ID:
- return NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS;
- case td_api::notificationSettingsScopeBasicGroupChats::ID:
- return NOTIFICATION_SETTINGS_FOR_GROUP_CHATS;
- case td_api::notificationSettingsScopeAllChats::ID:
- return NOTIFICATION_SETTINGS_FOR_ALL_CHATS;
- default:
- UNREACHABLE();
- return 0;
+
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog(dialog_id);
+ Message *m = get_message(d, full_message_id.get_message_id());
+ CHECK(m != nullptr);
+ m->last_edit_pts = pts;
+ d->last_edited_message_id = m->message_id;
+ if (td_->auth_manager_->is_bot()) {
+ send_update_message_edited(dialog_id, m);
}
-}
+ update_used_hashtags(dialog_id, m);
-string MessagesManager::get_notification_settings_scope_database_key(NotificationSettingsScope scope) {
- switch (scope) {
- case NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS:
- return "nsfpc";
- case NOTIFICATION_SETTINGS_FOR_GROUP_CHATS:
- return "nsfgc";
- case NOTIFICATION_SETTINGS_FOR_ALL_CHATS:
- return "nsfac";
- default:
- UNREACHABLE();
- return "";
+ if (!had_message &&
+ ((m->reactions != nullptr && !m->reactions->unread_reactions_.empty()) || d->unread_reaction_count > 0)) {
+ // if new message with unread reactions was added or the chat has unread reactions,
+ // then number of unread reactions could have been changed, so reload the number of unread reactions
+ send_get_dialog_query(dialog_id, Promise<Unit>(), 0, "on_message_edited");
}
}
-bool MessagesManager::update_notification_settings(NotificationSettingsScope scope,
- NotificationSettings *current_settings,
- const NotificationSettings &new_settings) {
- bool need_update = current_settings->mute_until != new_settings.mute_until ||
- current_settings->sound != new_settings.sound ||
- current_settings->show_preview != new_settings.show_preview ||
- current_settings->is_synchronized != new_settings.is_synchronized;
- bool is_changed = need_update || current_settings->silent_send_message != new_settings.silent_send_message;
+bool MessagesManager::update_dialog_notification_settings(DialogId dialog_id,
+ DialogNotificationSettings *current_settings,
+ DialogNotificationSettings &&new_settings) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return false;
+ }
+
+ bool need_update_server = current_settings->mute_until != new_settings.mute_until ||
+ !are_equivalent_notification_sounds(current_settings->sound, new_settings.sound) ||
+ current_settings->show_preview != new_settings.show_preview ||
+ current_settings->use_default_mute_until != new_settings.use_default_mute_until ||
+ current_settings->use_default_show_preview != new_settings.use_default_show_preview;
+ bool need_update_local =
+ current_settings->use_default_disable_pinned_message_notifications !=
+ new_settings.use_default_disable_pinned_message_notifications ||
+ current_settings->disable_pinned_message_notifications != new_settings.disable_pinned_message_notifications ||
+ current_settings->use_default_disable_mention_notifications !=
+ new_settings.use_default_disable_mention_notifications ||
+ current_settings->disable_mention_notifications != new_settings.disable_mention_notifications;
+
+ bool is_changed = need_update_server || need_update_local ||
+ current_settings->is_synchronized != new_settings.is_synchronized ||
+ current_settings->is_use_default_fixed != new_settings.is_use_default_fixed ||
+ are_different_equivalent_notification_sounds(current_settings->sound, new_settings.sound);
if (is_changed) {
- if (scope != NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS && scope != NOTIFICATION_SETTINGS_FOR_GROUP_CHATS &&
- scope != NOTIFICATION_SETTINGS_FOR_ALL_CHATS) {
- DialogId dialog_id(scope);
- CHECK(dialog_id.is_valid());
- Dialog *d = get_dialog(dialog_id);
- CHECK(d != nullptr) << "Wrong " << dialog_id << " in update_notification_settings";
- update_dialog_unmute_timeout(d, current_settings->mute_until, new_settings.mute_until);
- on_dialog_updated(dialog_id, "update_notification_settings");
- } else {
- string key = get_notification_settings_scope_database_key(scope);
- G()->td_db()->get_binlog_pmc()->set(key, log_event_store(new_settings).as_slice().str());
+ Dialog *d = get_dialog(dialog_id);
+ LOG_CHECK(d != nullptr) << "Wrong " << dialog_id << " in update_dialog_notification_settings";
+ bool was_dialog_mentions_disabled = is_dialog_mention_notifications_disabled(d);
+
+ VLOG(notifications) << "Update notification settings in " << dialog_id << " from " << *current_settings << " to "
+ << new_settings;
+
+ update_dialog_unmute_timeout(d, current_settings->use_default_mute_until, current_settings->mute_until,
+ new_settings.use_default_mute_until, new_settings.mute_until);
+
+ *current_settings = std::move(new_settings);
+ on_dialog_updated(dialog_id, "update_dialog_notification_settings");
+
+ if (is_dialog_muted(d)) {
+ // no check for was_muted to clean pending message notifications in chats with unsynchronized settings
+ remove_all_dialog_notifications(d, false, "update_dialog_notification_settings 2");
+ }
+ if (is_dialog_pinned_message_notifications_disabled(d) && d->mention_notification_group.group_id.is_valid() &&
+ d->pinned_message_notification_message_id.is_valid()) {
+ remove_dialog_pinned_message_notification(d, "update_dialog_notification_settings 3");
+ }
+ if (was_dialog_mentions_disabled != is_dialog_mention_notifications_disabled(d)) {
+ if (was_dialog_mentions_disabled) {
+ update_dialog_mention_notification_count(d);
+ } else {
+ remove_dialog_mention_notifications(d);
+ }
}
- LOG(INFO) << "Update notification settings in " << scope << " from " << *current_settings << " to " << new_settings;
- *current_settings = new_settings;
- if (need_update) {
- send_closure(
- G()->td(), &Td::send_update,
- make_tl_object<td_api::updateNotificationSettings>(get_notification_settings_scope_object(scope),
- get_notification_settings_object(current_settings)));
+ if (need_update_server || need_update_local) {
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatNotificationSettings>(
+ dialog_id.get(), get_chat_notification_settings_object(current_settings)));
}
}
- return is_changed;
+ return need_update_server;
+}
+
+void MessagesManager::schedule_dialog_unmute(DialogId dialog_id, bool use_default, int32 mute_until) {
+ auto now = G()->unix_time_cached();
+ if (!use_default && mute_until >= now && mute_until < now + 366 * 86400) {
+ dialog_unmute_timeout_.set_timeout_in(dialog_id.get(), mute_until - now + 1);
+ } else {
+ dialog_unmute_timeout_.cancel_timeout(dialog_id.get());
+ }
}
-void MessagesManager::update_dialog_unmute_timeout(Dialog *d, int32 old_mute_until, int32 new_mute_until) {
- if (old_mute_until == new_mute_until) {
+void MessagesManager::update_dialog_unmute_timeout(Dialog *d, bool &old_use_default, int32 &old_mute_until,
+ bool new_use_default, int32 new_mute_until) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
return;
}
- CHECK(d != nullptr);
- auto now = G()->unix_time_cached();
- if (new_mute_until >= now && new_mute_until < now + 366 * 86400) {
- dialog_unmute_timeout_.set_timeout_in(d->dialog_id.get(), new_mute_until - now + 1);
- } else {
- dialog_unmute_timeout_.cancel_timeout(d->dialog_id.get());
+ if (old_use_default == new_use_default && old_mute_until == new_mute_until) {
+ return;
}
+ CHECK(d != nullptr);
+ CHECK(old_mute_until >= 0);
+
+ schedule_dialog_unmute(d->dialog_id, new_use_default, new_mute_until);
- if (old_mute_until != -1 && is_unread_count_inited_ && d->order != DEFAULT_ORDER) {
+ auto scope = get_dialog_notification_setting_scope(d->dialog_id);
+ auto scope_mute_until = td_->notification_settings_manager_->get_scope_mute_until(scope);
+ bool was_muted = (old_use_default ? scope_mute_until : old_mute_until) != 0;
+ bool is_muted = (new_use_default ? scope_mute_until : new_mute_until) != 0;
+ if (was_muted != is_muted && need_unread_counter(d->order)) {
auto unread_count = d->server_unread_count + d->local_unread_count;
- if (unread_count != 0) {
- if (old_mute_until != 0 && new_mute_until == 0) {
- unread_message_muted_count_ -= unread_count;
- send_update_unread_message_count(d->dialog_id, true, "on_dialog_unmute");
+ if (unread_count != 0 || d->is_marked_as_unread) {
+ for (auto &list : get_dialog_lists(d)) {
+ if (unread_count != 0 && list.is_message_unread_count_inited_) {
+ int32 delta = was_muted ? -unread_count : unread_count;
+ list.unread_message_muted_count_ += delta;
+ send_update_unread_message_count(list, d->dialog_id, true, "update_dialog_unmute_timeout");
+ }
+ if (list.is_dialog_unread_count_inited_) {
+ int32 delta = was_muted ? -1 : 1;
+ list.unread_dialog_muted_count_ += delta;
+ if (unread_count == 0 && d->is_marked_as_unread) {
+ list.unread_dialog_muted_marked_count_ += delta;
+ }
+ send_update_unread_chat_count(list, d->dialog_id, true, "update_dialog_unmute_timeout");
+ }
}
- if (old_mute_until == 0 && new_mute_until != 0) {
- unread_message_muted_count_ += unread_count;
- send_update_unread_message_count(d->dialog_id, true, "on_dialog_mute");
+ }
+ }
+
+ old_use_default = new_use_default;
+ old_mute_until = new_mute_until;
+
+ if (was_muted != is_muted && !dialog_filters_.empty()) {
+ update_dialog_lists(d, get_dialog_positions(d), true, false, "update_dialog_unmute_timeout");
+ }
+}
+
+void MessagesManager::on_update_notification_scope_is_muted(NotificationSettingsScope scope, bool is_muted) {
+ if (G()->parameters().use_message_db) {
+ std::unordered_map<DialogListId, int32, DialogListIdHash> delta;
+ std::unordered_map<DialogListId, int32, DialogListIdHash> total_count;
+ std::unordered_map<DialogListId, int32, DialogListIdHash> marked_count;
+ std::unordered_set<DialogListId, DialogListIdHash> dialog_list_ids;
+ dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
+ Dialog *d = dialog.get();
+ if (need_unread_counter(d->order) && d->notification_settings.use_default_mute_until &&
+ get_dialog_notification_setting_scope(d->dialog_id) == scope) {
+ int32 unread_count = d->server_unread_count + d->local_unread_count;
+ if (unread_count != 0) {
+ for (auto dialog_list_id : get_dialog_list_ids(d)) {
+ delta[dialog_list_id] += unread_count;
+ total_count[dialog_list_id]++;
+ dialog_list_ids.insert(dialog_list_id);
+ }
+ } else if (d->is_marked_as_unread) {
+ for (auto dialog_list_id : get_dialog_list_ids(d)) {
+ total_count[dialog_list_id]++;
+ marked_count[dialog_list_id]++;
+ dialog_list_ids.insert(dialog_list_id);
+ }
+ }
+ }
+ });
+ for (auto dialog_list_id : dialog_list_ids) {
+ auto *list = get_dialog_list(dialog_list_id);
+ CHECK(list != nullptr);
+ if (delta[dialog_list_id] != 0 && list->is_message_unread_count_inited_) {
+ if (is_muted) {
+ list->unread_message_muted_count_ += delta[dialog_list_id];
+ } else {
+ list->unread_message_muted_count_ -= delta[dialog_list_id];
+ }
+ send_update_unread_message_count(*list, DialogId(), true, "on_update_notification_scope_is_muted");
+ }
+ if (total_count[dialog_list_id] != 0 && list->is_dialog_unread_count_inited_) {
+ if (is_muted) {
+ list->unread_dialog_muted_count_ += total_count[dialog_list_id];
+ list->unread_dialog_muted_marked_count_ += marked_count[dialog_list_id];
+ } else {
+ list->unread_dialog_muted_count_ -= total_count[dialog_list_id];
+ list->unread_dialog_muted_marked_count_ -= marked_count[dialog_list_id];
+ }
+ send_update_unread_chat_count(*list, DialogId(), true, "on_update_notification_scope_is_muted");
}
}
}
+
+ if (!dialog_filters_.empty()) {
+ dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
+ Dialog *d = dialog.get();
+ if (d->order != DEFAULT_ORDER && d->notification_settings.use_default_mute_until &&
+ get_dialog_notification_setting_scope(d->dialog_id) == scope) {
+ update_dialog_lists(d, get_dialog_positions(d), true, false, "on_update_notification_scope_is_muted");
+ }
+ });
+ }
+
+ if (is_muted) {
+ dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
+ Dialog *d = dialog.get();
+ if (d->order != DEFAULT_ORDER && d->notification_settings.use_default_mute_until &&
+ get_dialog_notification_setting_scope(d->dialog_id) == scope) {
+ remove_all_dialog_notifications(d, false, "on_update_notification_scope_is_muted");
+ }
+ });
+ }
}
void MessagesManager::on_dialog_unmute(DialogId dialog_id) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
+ if (d->notification_settings.use_default_mute_until) {
+ return;
+ }
if (d->notification_settings.mute_until == 0) {
return;
}
@@ -6172,82 +8399,531 @@ void MessagesManager::on_dialog_unmute(DialogId dialog_id) {
if (d->notification_settings.mute_until > now) {
LOG(ERROR) << "Failed to unmute " << dialog_id << " in " << now << ", will be unmuted in "
<< d->notification_settings.mute_until;
- update_dialog_unmute_timeout(d, -1, d->notification_settings.mute_until);
+ schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until);
return;
}
LOG(INFO) << "Unmute " << dialog_id;
- update_dialog_unmute_timeout(d, d->notification_settings.mute_until, 0);
- d->notification_settings.mute_until = 0;
+ update_dialog_unmute_timeout(d, d->notification_settings.use_default_mute_until, d->notification_settings.mute_until,
+ false, 0);
send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateNotificationSettings>(
- get_notification_settings_scope_object(NotificationSettingsScope(dialog_id.get())),
- get_notification_settings_object(&d->notification_settings)));
+ make_tl_object<td_api::updateChatNotificationSettings>(
+ dialog_id.get(), get_chat_notification_settings_object(&d->notification_settings)));
on_dialog_updated(dialog_id, "on_dialog_unmute");
}
-void MessagesManager::on_update_notify_settings(
- NotificationSettingsScope scope, tl_object_ptr<telegram_api::PeerNotifySettings> &&peer_notify_settings) {
- const NotificationSettings notification_settings = get_notification_settings(std::move(peer_notify_settings));
- if (!notification_settings.is_synchronized) {
+void MessagesManager::on_update_dialog_notify_settings(
+ DialogId dialog_id, tl_object_ptr<telegram_api::peerNotifySettings> &&peer_notify_settings, const char *source) {
+ if (td_->auth_manager_->is_bot()) {
return;
}
- NotificationSettings *current_settings = get_notification_settings(scope, true);
+ VLOG(notifications) << "Receive notification settings for " << dialog_id << " from " << source << ": "
+ << to_string(peer_notify_settings);
+
+ DialogNotificationSettings *current_settings = get_dialog_notification_settings(dialog_id, true);
if (current_settings == nullptr) {
return;
}
- update_notification_settings(scope, current_settings, notification_settings);
+
+ DialogNotificationSettings notification_settings = ::td::get_dialog_notification_settings(
+ std::move(peer_notify_settings), current_settings->use_default_disable_pinned_message_notifications,
+ current_settings->disable_pinned_message_notifications,
+ current_settings->use_default_disable_mention_notifications, current_settings->disable_mention_notifications);
+ if (!notification_settings.is_synchronized) {
+ return;
+ }
+
+ update_dialog_notification_settings(dialog_id, current_settings, std::move(notification_settings));
}
-bool MessagesManager::get_dialog_report_spam_state(DialogId dialog_id, Promise<Unit> &&promise) {
- Dialog *d = get_dialog_force(dialog_id);
+void MessagesManager::on_update_dialog_available_reactions(
+ DialogId dialog_id, telegram_api::object_ptr<telegram_api::ChatReactions> &&available_reactions) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ Dialog *d = get_dialog_force(dialog_id, "on_update_dialog_available_reactions");
if (d == nullptr) {
- promise.set_error(Status::Error(3, "Chat not found"));
+ return;
+ }
+
+ set_dialog_available_reactions(d, ChatReactions(std::move(available_reactions)));
+}
+
+void MessagesManager::set_dialog_available_reactions(Dialog *d, ChatReactions &&available_reactions) {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(d != nullptr);
+ switch (d->dialog_id.get_type()) {
+ case DialogType::Chat:
+ case DialogType::Channel:
+ // ok
+ break;
+ case DialogType::User:
+ case DialogType::SecretChat:
+ default:
+ UNREACHABLE();
+ break;
+ }
+ if (d->available_reactions == available_reactions) {
+ if (!d->is_available_reactions_inited) {
+ d->is_available_reactions_inited = true;
+ on_dialog_updated(d->dialog_id, "set_dialog_available_reactions");
+ }
+ return;
+ }
+
+ VLOG(notifications) << "Update available reactions in " << d->dialog_id << " to " << available_reactions;
+
+ auto old_active_reactions = get_active_reactions(d->available_reactions);
+ auto new_active_reactions = get_active_reactions(available_reactions);
+ bool need_update = old_active_reactions != new_active_reactions;
+ bool need_update_message_reactions_visibility = old_active_reactions.empty() != new_active_reactions.empty();
+
+ d->available_reactions = std::move(available_reactions);
+ d->is_available_reactions_inited = true;
+ if (need_update_message_reactions_visibility) {
+ if (!old_active_reactions.empty()) {
+ hide_dialog_message_reactions(d);
+ }
+
+ set_dialog_next_available_reactions_generation(d, d->available_reactions_generation);
+ }
+ on_dialog_updated(d->dialog_id, "set_dialog_available_reactions");
+
+ if (need_update) {
+ send_update_chat_available_reactions(d);
+ }
+}
+
+void MessagesManager::set_dialog_next_available_reactions_generation(Dialog *d, uint32 generation) {
+ CHECK(d != nullptr);
+ switch (d->dialog_id.get_type()) {
+ case DialogType::Chat:
+ case DialogType::Channel:
+ // ok
+ break;
+ case DialogType::User:
+ case DialogType::SecretChat:
+ default:
+ UNREACHABLE();
+ break;
+ }
+ if (get_active_reactions(d->available_reactions).empty()) {
+ // 0 -> 1
+ // 1 -> 3
+ generation = generation + (generation & 1) + 1;
+ } else {
+ // 0 -> 2
+ // 1 -> 2
+ generation = generation - (generation & 1) + 2;
+ }
+ LOG(INFO) << "Change available reactions generation from " << d->available_reactions_generation << " to "
+ << generation << " in " << d->dialog_id;
+ d->available_reactions_generation = generation;
+}
+
+void MessagesManager::hide_dialog_message_reactions(Dialog *d) {
+ vector<MessageId> message_ids;
+ find_messages(d->messages.get(), message_ids,
+ [](const Message *m) { return m->reactions != nullptr && !m->reactions->reactions_.empty(); });
+ for (auto message_id : message_ids) {
+ Message *m = get_message(d, message_id);
+ CHECK(m != nullptr);
+ CHECK(m->reactions != nullptr);
+ bool need_update_unread_reactions = !m->reactions->unread_reactions_.empty();
+ m->reactions = nullptr;
+ // don't resave all messages to the database
+ if (need_update_unread_reactions) {
+ send_update_message_unread_reactions(d->dialog_id, m, d->unread_reaction_count);
+ }
+ send_update_message_interaction_info(d->dialog_id, m);
+ }
+ if (d->unread_reaction_count != 0) {
+ set_dialog_unread_reaction_count(d, 0);
+ }
+}
+
+void MessagesManager::set_active_reactions(vector<string> active_reactions) {
+ if (active_reactions == active_reactions_) {
+ return;
+ }
+
+ LOG(INFO) << "Set active reactions to " << active_reactions;
+ bool is_changed = active_reactions != active_reactions_;
+ active_reactions_ = std::move(active_reactions);
+
+ auto old_active_reaction_pos_ = std::move(active_reaction_pos_);
+ active_reaction_pos_.clear();
+ for (size_t i = 0; i < active_reactions_.size(); i++) {
+ active_reaction_pos_[active_reactions_[i]] = i;
+ }
+
+ dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
+ Dialog *d = dialog.get();
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ if (is_changed) {
+ send_update_chat_available_reactions(d);
+ }
+ break;
+ case DialogType::Chat:
+ case DialogType::Channel: {
+ auto old_reactions = d->available_reactions.get_active_reactions(old_active_reaction_pos_);
+ auto new_reactions = d->available_reactions.get_active_reactions(active_reaction_pos_);
+ if (old_reactions != new_reactions) {
+ if (old_reactions.empty() != new_reactions.empty()) {
+ if (!old_reactions.empty()) {
+ hide_dialog_message_reactions(d);
+ }
+ set_dialog_next_available_reactions_generation(d, d->available_reactions_generation);
+ on_dialog_updated(d->dialog_id, "set_active_reactions");
+ }
+ send_update_chat_available_reactions(d);
+ }
+ break;
+ }
+ case DialogType::SecretChat:
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ });
+}
+
+ChatReactions MessagesManager::get_active_reactions(const ChatReactions &available_reactions) const {
+ return available_reactions.get_active_reactions(active_reaction_pos_);
+}
+
+ChatReactions MessagesManager::get_dialog_active_reactions(const Dialog *d) const {
+ CHECK(d != nullptr);
+ switch (d->dialog_id.get_type()) {
+ case DialogType::User:
+ return ChatReactions(true, true);
+ case DialogType::Chat:
+ case DialogType::Channel:
+ return get_active_reactions(d->available_reactions);
+ case DialogType::SecretChat:
+ return {};
+ default:
+ UNREACHABLE();
+ return {};
+ }
+}
+
+ChatReactions MessagesManager::get_message_active_reactions(const Dialog *d, const Message *m) const {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ if (is_service_message_content(m->content->get_type()) || m->ttl > 0) {
+ return ChatReactions();
+ }
+ if (is_discussion_message(d->dialog_id, m)) {
+ d = get_dialog(m->forward_info->from_dialog_id);
+ if (d == nullptr) {
+ LOG(ERROR) << "Failed to find linked " << m->forward_info->from_dialog_id
+ << " to determine correct active reactions";
+ return ChatReactions();
+ }
+ }
+ return get_dialog_active_reactions(d);
+}
+
+bool MessagesManager::need_poll_dialog_message_reactions(const Dialog *d) {
+ CHECK(d != nullptr);
+ switch (d->dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::SecretChat:
+ return false;
+ case DialogType::Chat:
+ case DialogType::Channel:
+ return (d->available_reactions_generation & 1) == 0;
+ default:
+ UNREACHABLE();
+ return {};
+ }
+}
+
+bool MessagesManager::need_poll_message_reactions(const Dialog *d, const Message *m) {
+ CHECK(m != nullptr);
+ if (!m->message_id.is_valid() || !m->message_id.is_server()) {
+ return false;
+ }
+ if (!need_poll_dialog_message_reactions(d)) {
return false;
}
+ if (m->reactions != nullptr) {
+ return true;
+ }
+ if (m->available_reactions_generation == d->available_reactions_generation) {
+ return false;
+ }
+ if (is_service_message_content(m->content->get_type())) {
+ return false;
+ }
+ return true;
+}
- if (!have_input_peer(dialog_id, AccessRights::Read)) {
- promise.set_error(Status::Error(3, "Can't access the chat"));
+void MessagesManager::queue_message_reactions_reload(FullMessageId full_message_id) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ CHECK(dialog_id.is_valid());
+ auto message_id = full_message_id.get_message_id();
+ CHECK(message_id.is_valid());
+ being_reloaded_reactions_[dialog_id].message_ids.insert(message_id);
+ try_reload_message_reactions(dialog_id, false);
+}
+
+void MessagesManager::queue_message_reactions_reload(DialogId dialog_id, const vector<MessageId> &message_ids) {
+ LOG(INFO) << "Queue reload of reactions in " << message_ids << " in " << dialog_id;
+ auto &message_ids_to_reload = being_reloaded_reactions_[dialog_id].message_ids;
+ for (auto &message_id : message_ids) {
+ CHECK(message_id.is_valid());
+ message_ids_to_reload.insert(message_id);
+ }
+ try_reload_message_reactions(dialog_id, false);
+}
+
+void MessagesManager::try_reload_message_reactions(DialogId dialog_id, bool is_finished) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto it = being_reloaded_reactions_.find(dialog_id);
+ if (it == being_reloaded_reactions_.end()) {
+ return;
+ }
+ if (is_finished) {
+ CHECK(it->second.is_request_sent);
+ it->second.is_request_sent = false;
+
+ if (it->second.message_ids.empty()) {
+ being_reloaded_reactions_.erase(it);
+ return;
+ }
+ } else if (it->second.is_request_sent) {
+ return;
+ }
+
+ CHECK(!it->second.message_ids.empty());
+ CHECK(!it->second.is_request_sent);
+
+ it->second.is_request_sent = true;
+
+ static constexpr size_t MAX_MESSAGE_IDS = 100; // server-side limit
+ vector<MessageId> message_ids;
+ for (auto message_id_it = it->second.message_ids.begin();
+ message_id_it != it->second.message_ids.end() && message_ids.size() < MAX_MESSAGE_IDS; ++message_id_it) {
+ message_ids.push_back(*message_id_it);
+ }
+ for (auto message_id : message_ids) {
+ it->second.message_ids.erase(message_id);
+ }
+ reload_message_reactions(td_, dialog_id, std::move(message_ids));
+}
+
+bool MessagesManager::update_dialog_silent_send_message(Dialog *d, bool silent_send_message) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
return false;
}
- if (d->know_can_report_spam) {
- promise.set_value(Unit());
- return d->can_report_spam;
+ CHECK(d != nullptr);
+ LOG_IF(WARNING, !d->notification_settings.is_synchronized)
+ << "Have unknown notification settings in " << d->dialog_id;
+ if (d->notification_settings.silent_send_message == silent_send_message) {
+ return false;
}
+ LOG(INFO) << "Update silent send message in " << d->dialog_id << " to " << silent_send_message;
+ d->notification_settings.silent_send_message = silent_send_message;
+
+ on_dialog_updated(d->dialog_id, "update_dialog_silent_send_message");
+
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatDefaultDisableNotification>(d->dialog_id.get(), silent_send_message));
+ return true;
+}
+
+void MessagesManager::reget_dialog_action_bar(DialogId dialog_id, const char *source, bool is_repair) {
+ if (G()->close_flag() || !dialog_id.is_valid() || td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ Dialog *d = get_dialog_force(dialog_id, source);
+ if (d == nullptr) {
+ return;
+ }
+
+ if (is_repair && !d->need_repair_action_bar) {
+ d->need_repair_action_bar = true;
+ on_dialog_updated(dialog_id, source);
+ }
+
+ LOG(INFO) << "Reget action bar in " << dialog_id << " from " << source;
switch (dialog_id.get_type()) {
case DialogType::User:
+ td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Auto());
+ return;
case DialogType::Chat:
case DialogType::Channel:
- td_->create_handler<GetPeerSettingsQuery>(std::move(promise))->send(dialog_id);
- return false;
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return;
+ }
+
+ return td_->create_handler<GetPeerSettingsQuery>()->send(dialog_id);
case DialogType::SecretChat:
- promise.set_value(Unit());
- return false;
case DialogType::None:
default:
UNREACHABLE();
- return false;
}
}
-void MessagesManager::change_dialog_report_spam_state(DialogId dialog_id, bool is_spam_dialog,
- Promise<Unit> &&promise) {
- Dialog *d = get_dialog_force(dialog_id);
+void MessagesManager::repair_dialog_action_bar(Dialog *d, const char *source) {
+ CHECK(d != nullptr);
+ auto dialog_id = d->dialog_id;
+ d->need_repair_action_bar = true;
+ if (have_input_peer(dialog_id, AccessRights::Read)) {
+ create_actor<SleepActor>(
+ "RepairChatActionBarActor", 1.0,
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, source](Result<Unit> result) {
+ send_closure(actor_id, &MessagesManager::reget_dialog_action_bar, dialog_id, source, true);
+ }))
+ .release();
+ }
+ // there is no need to change action bar
+ on_dialog_updated(dialog_id, source);
+}
+
+void MessagesManager::hide_dialog_action_bar(DialogId dialog_id) {
+ Dialog *d = get_dialog_force(dialog_id, "hide_dialog_action_bar");
if (d == nullptr) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+ return;
+ }
+ hide_dialog_action_bar(d);
+}
+
+void MessagesManager::hide_dialog_action_bar(Dialog *d) {
+ CHECK(d->dialog_id.get_type() != DialogType::SecretChat);
+ if (!d->know_action_bar) {
+ return;
+ }
+ if (d->need_repair_action_bar) {
+ d->need_repair_action_bar = false;
+ on_dialog_updated(d->dialog_id, "hide_dialog_action_bar");
+ }
+ if (d->action_bar == nullptr) {
+ return;
+ }
+
+ d->action_bar = nullptr;
+ send_update_chat_action_bar(d);
+}
+
+void MessagesManager::remove_dialog_action_bar(DialogId dialog_id, Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "remove_dialog_action_bar");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ dialog_id = DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()));
+ d = get_dialog_force(dialog_id, "remove_dialog_action_bar 2");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat with the user not found"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+ }
+
+ if (!d->know_action_bar) {
+ return promise.set_error(Status::Error(400, "Can't update chat action bar"));
+ }
+ if (d->need_repair_action_bar) {
+ d->need_repair_action_bar = false;
+ on_dialog_updated(dialog_id, "remove_dialog_action_bar");
+ }
+ if (d->action_bar == nullptr) {
+ return promise.set_value(Unit());
+ }
+
+ d->action_bar = nullptr;
+ send_update_chat_action_bar(d);
+
+ toggle_dialog_report_spam_state_on_server(dialog_id, false, 0, std::move(promise));
+}
+
+void MessagesManager::repair_dialog_active_group_call_id(DialogId dialog_id) {
+ if (have_input_peer(dialog_id, AccessRights::Read)) {
+ LOG(INFO) << "Repair active voice chat ID in " << dialog_id;
+ create_actor<SleepActor>("RepairChatActiveVoiceChatId", 1.0,
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result<Unit> result) {
+ send_closure(actor_id, &MessagesManager::do_repair_dialog_active_group_call_id,
+ dialog_id);
+ }))
+ .release();
+ }
+}
+
+void MessagesManager::do_repair_dialog_active_group_call_id(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
}
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ bool need_repair_active_group_call_id = d->has_active_group_call && !d->active_group_call_id.is_valid();
+ bool need_repair_expected_group_call_id =
+ d->has_expected_active_group_call_id && d->active_group_call_id != d->expected_active_group_call_id;
+ d->has_expected_active_group_call_id = false;
+ if (!need_repair_active_group_call_id && !need_repair_expected_group_call_id) {
+ return;
+ }
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- return promise.set_error(Status::Error(3, "Can't access the chat"));
+ return;
+ }
+
+ reload_dialog_info_full(dialog_id, "do_repair_dialog_active_group_call_id");
+}
+
+class MessagesManager::ToggleDialogReportSpamStateOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+ bool is_spam_dialog_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
+ td::store(is_spam_dialog_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(dialog_id_, parser);
+ td::parse(is_spam_dialog_, parser);
}
+};
+
+uint64 MessagesManager::save_toggle_dialog_report_spam_state_on_server_log_event(DialogId dialog_id,
+ bool is_spam_dialog) {
+ ToggleDialogReportSpamStateOnServerLogEvent log_event{dialog_id, is_spam_dialog};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogReportSpamStateOnServer,
+ get_log_event_storer(log_event));
+}
- if (!d->know_can_report_spam || !d->can_report_spam) {
- return promise.set_error(Status::Error(3, "Can't update chat report spam state"));
+void MessagesManager::toggle_dialog_report_spam_state_on_server(DialogId dialog_id, bool is_spam_dialog,
+ uint64 log_event_id, Promise<Unit> &&promise) {
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_toggle_dialog_report_spam_state_on_server_log_event(dialog_id, is_spam_dialog);
}
+ auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
+ promise = std::move(new_promise); // to prevent self-move
+
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
@@ -6257,10 +8933,11 @@ void MessagesManager::change_dialog_report_spam_state(DialogId dialog_id, bool i
if (is_spam_dialog) {
return td_->create_handler<ReportEncryptedSpamQuery>(std::move(promise))->send(dialog_id);
} else {
- d->can_report_spam = false;
- on_dialog_updated(dialog_id, "change_dialog_report_spam_state");
- promise.set_value(Unit());
- return;
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (!user_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Peer user not found"));
+ }
+ return td_->create_handler<UpdatePeerSettingsQuery>(std::move(promise))->send(DialogId(user_id), false);
}
case DialogType::None:
default:
@@ -6270,9 +8947,10 @@ void MessagesManager::change_dialog_report_spam_state(DialogId dialog_id, bool i
}
bool MessagesManager::can_report_dialog(DialogId dialog_id) const {
+ // doesn't include possibility of report from action bar
switch (dialog_id.get_type()) {
case DialogType::User:
- return td_->contacts_manager_->is_user_bot(dialog_id.get_user_id());
+ return td_->contacts_manager_->can_report_user(dialog_id.get_user_id());
case DialogType::Chat:
return false;
case DialogType::Channel:
@@ -6286,94 +8964,208 @@ bool MessagesManager::can_report_dialog(DialogId dialog_id) const {
}
}
-void MessagesManager::report_dialog(DialogId dialog_id, const tl_object_ptr<td_api::ChatReportReason> &reason,
- const vector<MessageId> &message_ids, Promise<Unit> &&promise) {
- Dialog *d = get_dialog_force(dialog_id);
+void MessagesManager::report_dialog(DialogId dialog_id, const vector<MessageId> &message_ids, ReportReason &&reason,
+ Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "report_dialog");
if (d == nullptr) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- return promise.set_error(Status::Error(3, "Can't access the chat"));
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
}
- if (reason == nullptr) {
- return promise.set_error(Status::Error(3, "Reason shouldn't be empty"));
+ Dialog *user_d = d;
+ bool is_dialog_spam_report = false;
+ bool can_report_spam = false;
+ if (reason.is_spam() && message_ids.empty()) {
+ // report from action bar
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ auto user_dialog_id = DialogId(td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()));
+ user_d = get_dialog_force(user_dialog_id, "report_dialog 2");
+ if (user_d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat with the user not found"));
+ }
+ }
+ is_dialog_spam_report = user_d->know_action_bar;
+ can_report_spam = user_d->action_bar != nullptr && user_d->action_bar->can_report_spam();
+ }
+
+ if (is_dialog_spam_report && can_report_spam) {
+ hide_dialog_action_bar(user_d);
+ return toggle_dialog_report_spam_state_on_server(dialog_id, true, 0, std::move(promise));
}
if (!can_report_dialog(dialog_id)) {
- return promise.set_error(Status::Error(3, "Chat can't be reported"));
+ if (is_dialog_spam_report) {
+ return promise.set_value(Unit());
+ }
+
+ return promise.set_error(Status::Error(400, "Chat can't be reported"));
}
vector<MessageId> server_message_ids;
for (auto message_id : message_ids) {
+ if (message_id.is_scheduled()) {
+ return promise.set_error(Status::Error(400, "Can't report scheduled messages"));
+ }
if (message_id.is_valid() && message_id.is_server()) {
server_message_ids.push_back(message_id);
}
}
- tl_object_ptr<telegram_api::ReportReason> report_reason;
- switch (reason->get_id()) {
- case td_api::chatReportReasonSpam::ID:
- report_reason = make_tl_object<telegram_api::inputReportReasonSpam>();
- break;
- case td_api::chatReportReasonViolence::ID:
- report_reason = make_tl_object<telegram_api::inputReportReasonViolence>();
- break;
- case td_api::chatReportReasonPornography::ID:
- report_reason = make_tl_object<telegram_api::inputReportReasonPornography>();
- break;
- case td_api::chatReportReasonCustom::ID: {
- auto other_reason = static_cast<const td_api::chatReportReasonCustom *>(reason.get());
- auto text = other_reason->text_;
- if (!clean_input_string(text)) {
- return promise.set_error(Status::Error(400, "Text must be encoded in UTF-8"));
- }
+ if (dialog_id.get_type() == DialogType::Channel && reason.is_unrelated_location()) {
+ hide_dialog_action_bar(d);
+ }
- report_reason = make_tl_object<telegram_api::inputReportReasonOther>(text);
- break;
- }
- default:
- UNREACHABLE();
+ td_->create_handler<ReportPeerQuery>(std::move(promise))->send(dialog_id, server_message_ids, std::move(reason));
+}
+
+void MessagesManager::report_dialog_photo(DialogId dialog_id, FileId file_id, ReportReason &&reason,
+ Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "report_dialog_photo");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- CHECK(report_reason != nullptr);
- td_->create_handler<ReportPeerQuery>(std::move(promise))
- ->send(dialog_id, std::move(report_reason), server_message_ids);
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ if (!can_report_dialog(dialog_id)) {
+ return promise.set_error(Status::Error(400, "Chat photo can't be reported"));
+ }
+
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ if (file_view.empty()) {
+ return promise.set_error(Status::Error(400, "Unknown file ID"));
+ }
+ if (file_view.get_type() != FileType::Photo || !file_view.has_remote_location() ||
+ !file_view.remote_location().is_photo()) {
+ return promise.set_error(Status::Error(400, "Only full chat photos can be reported"));
+ }
+
+ td_->create_handler<ReportProfilePhotoQuery>(std::move(promise))
+ ->send(dialog_id, file_id, file_view.remote_location().as_input_photo(), std::move(reason));
}
void MessagesManager::on_get_peer_settings(DialogId dialog_id,
- tl_object_ptr<telegram_api::peerSettings> &&peer_settings) {
+ tl_object_ptr<telegram_api::peerSettings> &&peer_settings,
+ bool ignore_privacy_exception) {
CHECK(peer_settings != nullptr);
- Dialog *d = get_dialog_force(dialog_id);
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (dialog_id.get_type() == DialogType::User && !ignore_privacy_exception) {
+ td_->contacts_manager_->on_update_user_need_phone_number_privacy_exception(dialog_id.get_user_id(),
+ peer_settings->need_contacts_exception_);
+ }
+
+ Dialog *d = get_dialog_force(dialog_id, "on_get_peer_settings");
if (d == nullptr) {
return;
}
- d->know_can_report_spam = true;
- d->can_report_spam = (peer_settings->flags_ & telegram_api::peerSettings::REPORT_SPAM_MASK) != 0;
- on_dialog_updated(dialog_id, "can_report_spam");
+ auto distance =
+ (peer_settings->flags_ & telegram_api::peerSettings::GEO_DISTANCE_MASK) != 0 ? peer_settings->geo_distance_ : -1;
+ if (distance < -1 || d->has_outgoing_messages) {
+ distance = -1;
+ }
+ auto action_bar =
+ DialogActionBar::create(peer_settings->report_spam_, peer_settings->add_contact_, peer_settings->block_contact_,
+ peer_settings->share_contact_, peer_settings->report_geo_, peer_settings->autoarchived_,
+ distance, peer_settings->invite_members_, peer_settings->request_chat_title_,
+ peer_settings->request_chat_broadcast_, peer_settings->request_chat_date_);
+
+ fix_dialog_action_bar(d, action_bar.get());
+
+ if (d->action_bar == action_bar) {
+ if (!d->know_action_bar || d->need_repair_action_bar) {
+ d->know_action_bar = true;
+ d->need_repair_action_bar = false;
+ on_dialog_updated(d->dialog_id, "on_get_peer_settings");
+ }
+ return;
+ }
+
+ d->know_action_bar = true;
+ d->need_repair_action_bar = false;
+ d->action_bar = std::move(action_bar);
+
+ send_update_chat_action_bar(d);
+}
+
+void MessagesManager::fix_dialog_action_bar(const Dialog *d, DialogActionBar *action_bar) {
+ if (action_bar == nullptr) {
+ return;
+ }
+
+ CHECK(d != nullptr);
+ action_bar->fix(td_, d->dialog_id, d->is_blocked, d->folder_id);
+}
+
+Result<string> MessagesManager::get_login_button_url(FullMessageId full_message_id, int64 button_id) {
+ Dialog *d = get_dialog_force(full_message_id.get_dialog_id(), "get_login_button_url");
+ if (d == nullptr) {
+ return Status::Error(400, "Chat not found");
+ }
+ if (!have_input_peer(d->dialog_id, AccessRights::Read)) {
+ return Status::Error(400, "Can't access the chat");
+ }
+
+ auto m = get_message_force(d, full_message_id.get_message_id(), "get_login_button_url");
+ if (m == nullptr) {
+ return Status::Error(400, "Message not found");
+ }
+ if (m->reply_markup == nullptr || m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
+ return Status::Error(400, "Message has no inline keyboard");
+ }
+ if (m->message_id.is_scheduled()) {
+ return Status::Error(400, "Can't use login buttons from scheduled messages");
+ }
+ if (!m->message_id.is_server()) {
+ // it shouldn't have UrlAuth buttons anyway
+ return Status::Error(400, "Message is not server");
+ }
+ if (d->dialog_id.get_type() == DialogType::SecretChat) {
+ // secret chat messages can't have reply markup, so this shouldn't happen now
+ return Status::Error(400, "Message is in a secret chat");
+ }
+ if (button_id < std::numeric_limits<int32>::min() || button_id > std::numeric_limits<int32>::max()) {
+ return Status::Error(400, "Invalid button identifier specified");
+ }
+
+ for (auto &row : m->reply_markup->inline_keyboard) {
+ for (auto &button : row) {
+ if (button.type == InlineKeyboardButton::Type::UrlAuth && button.id == button_id) {
+ return button.data;
+ }
+ }
+ }
+
+ return Status::Error(400, "Button not found");
}
void MessagesManager::load_secret_thumbnail(FileId thumbnail_file_id) {
- class Callback : public FileManager::DownloadCallback {
+ class Callback final : public FileManager::DownloadCallback {
public:
- explicit Callback(Promise<> download_promise) : download_promise_(std::move(download_promise)) {
+ explicit Callback(Promise<Unit> download_promise) : download_promise_(std::move(download_promise)) {
}
- void on_download_ok(FileId file_id) override {
+ void on_download_ok(FileId file_id) final {
download_promise_.set_value(Unit());
}
- void on_download_error(FileId file_id, Status error) override {
+ void on_download_error(FileId file_id, Status error) final {
download_promise_.set_error(std::move(error));
}
private:
- Promise<> download_promise_;
+ Promise<Unit> download_promise_;
};
auto thumbnail_promise = PromiseCreator::lambda([actor_id = actor_id(this),
- thumbnail_file_id](Result<BufferSlice> r_thumbnail) mutable {
+ thumbnail_file_id](Result<BufferSlice> r_thumbnail) {
BufferSlice thumbnail_slice;
if (r_thumbnail.is_ok()) {
thumbnail_slice = r_thumbnail.move_as_ok();
@@ -6391,7 +9183,8 @@ void MessagesManager::load_secret_thumbnail(FileId thumbnail_file_id) {
});
send_closure(G()->file_manager(), &FileManager::download, thumbnail_file_id,
- std::make_unique<Callback>(std::move(download_promise)), 1);
+ std::make_shared<Callback>(std::move(download_promise)), 1, -1, -1,
+ Promise<td_api::object_ptr<td_api::file>>());
}
void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file,
@@ -6400,7 +9193,7 @@ void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr<telegram_api
auto it = being_uploaded_files_.find(file_id);
if (it == being_uploaded_files_.end()) {
- // callback may be called just before the file upload was cancelled
+ // callback may be called just before the file upload was canceled
return;
}
@@ -6411,20 +9204,20 @@ void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr<telegram_api
Message *m = get_message(full_message_id);
if (m == nullptr) {
- // message has already been deleted by the user or sent to inaccessible channel, do not need to send it
- // file upload should be already cancelled in cancel_send_message_query, it shouldn't happen
+ // message has already been deleted by the user or sent to inaccessible channel, do not need to send or edit it
+ // file upload should be already canceled in cancel_send_message_query, it shouldn't happen
LOG(ERROR) << "Message with a media has already been deleted";
return;
}
+ bool is_edit = m->message_id.is_any_server();
auto dialog_id = full_message_id.get_dialog_id();
auto can_send_status = can_send_message(dialog_id);
- if (can_send_status.is_error()) {
- // user has left the chat during upload of the file or lost his privileges
- LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status.error();
+ if (!is_edit && can_send_status.is_error()) {
+ // user has left the chat during upload of the file or lost their privileges
+ LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status;
- int64 random_id = begin_send_message(dialog_id, m);
- on_send_message_fail(random_id, can_send_status.move_as_error());
+ fail_send_message(full_message_id, std::move(can_send_status));
return;
}
@@ -6434,18 +9227,25 @@ void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr<telegram_api
case DialogType::Channel:
if (input_file && thumbnail_file_id.is_valid()) {
// TODO: download thumbnail if needed (like in secret chats)
- being_uploaded_thumbnails_[thumbnail_file_id] = {full_message_id, file_id, std::move(input_file)};
LOG(INFO) << "Ask to upload thumbnail " << thumbnail_file_id;
- td_->file_manager_->upload(thumbnail_file_id, upload_thumbnail_callback_, 1, m->message_id.get());
+ bool is_inserted =
+ being_uploaded_thumbnails_
+ .emplace(thumbnail_file_id, UploadedThumbnailInfo{full_message_id, file_id, std::move(input_file)})
+ .second;
+ CHECK(is_inserted);
+ td_->file_manager_->upload(thumbnail_file_id, upload_thumbnail_callback_, 32, m->message_id.get());
} else {
do_send_media(dialog_id, m, file_id, thumbnail_file_id, std::move(input_file), nullptr);
}
break;
case DialogType::SecretChat:
if (thumbnail_file_id.is_valid()) {
- being_loaded_secret_thumbnails_[thumbnail_file_id] = {full_message_id, file_id,
- std::move(input_encrypted_file)};
LOG(INFO) << "Ask to load thumbnail " << thumbnail_file_id;
+ bool is_inserted = being_loaded_secret_thumbnails_
+ .emplace(thumbnail_file_id, UploadedSecretThumbnailInfo{full_message_id, file_id,
+ std::move(input_encrypted_file)})
+ .second;
+ CHECK(is_inserted);
load_secret_thumbnail(thumbnail_file_id);
} else {
@@ -6462,32 +9262,51 @@ void MessagesManager::on_upload_media(FileId file_id, tl_object_ptr<telegram_api
void MessagesManager::do_send_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id,
tl_object_ptr<telegram_api::InputFile> input_file,
tl_object_ptr<telegram_api::InputFile> input_thumbnail) {
- if (input_file == nullptr) {
- CHECK(input_thumbnail == nullptr);
- file_id = FileId();
- thumbnail_file_id = FileId();
- }
CHECK(m != nullptr);
- on_message_media_uploaded(
- dialog_id, m, get_input_media(m->content.get(), std::move(input_file), std::move(input_thumbnail), m->ttl),
- file_id, thumbnail_file_id);
+
+ bool have_input_file = input_file != nullptr;
+ bool have_input_thumbnail = input_thumbnail != nullptr;
+ LOG(INFO) << "Do send media file " << file_id << " with thumbnail " << thumbnail_file_id
+ << ", have_input_file = " << have_input_file << ", have_input_thumbnail = " << have_input_thumbnail
+ << ", TTL = " << m->ttl;
+
+ MessageContent *content = nullptr;
+ if (m->message_id.is_any_server()) {
+ content = m->edited_content.get();
+ if (content == nullptr) {
+ LOG(ERROR) << "Message has no edited content";
+ return;
+ }
+ } else {
+ content = m->content.get();
+ }
+
+ auto input_media = get_input_media(content, td_, std::move(input_file), std::move(input_thumbnail), file_id,
+ thumbnail_file_id, m->ttl, true);
+ LOG_CHECK(input_media != nullptr) << to_string(get_message_object(dialog_id, m, "do_send_media")) << ' '
+ << have_input_file << ' ' << have_input_thumbnail << ' ' << file_id << ' '
+ << thumbnail_file_id << ' ' << m->ttl;
+
+ on_message_media_uploaded(dialog_id, m, std::move(input_media), file_id, thumbnail_file_id);
}
void MessagesManager::do_send_secret_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_encrypted_file,
BufferSlice thumbnail) {
- if (input_encrypted_file == nullptr) {
- file_id = FileId();
- thumbnail_file_id = FileId();
- }
-
CHECK(dialog_id.get_type() == DialogType::SecretChat);
CHECK(m != nullptr);
+ CHECK(m->message_id.is_valid());
+ CHECK(m->message_id.is_yet_unsent());
+
+ bool have_input_file = input_encrypted_file != nullptr;
+ LOG(INFO) << "Do send secret media file " << file_id << " with thumbnail " << thumbnail_file_id
+ << ", have_input_file = " << have_input_file;
+
auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
on_secret_message_media_uploaded(
dialog_id, m,
- get_secret_input_media(m->content.get(), std::move(input_encrypted_file), std::move(thumbnail), layer), file_id,
- thumbnail_file_id);
+ get_secret_input_media(m->content.get(), td_, std::move(input_encrypted_file), std::move(thumbnail), layer),
+ file_id, thumbnail_file_id);
}
void MessagesManager::on_upload_media_error(FileId file_id, Status status) {
@@ -6501,7 +9320,7 @@ void MessagesManager::on_upload_media_error(FileId file_id, Status status) {
auto it = being_uploaded_files_.find(file_id);
if (it == being_uploaded_files_.end()) {
- // callback may be called just before the file upload was cancelled
+ // callback may be called just before the file upload was canceled
return;
}
@@ -6509,8 +9328,12 @@ void MessagesManager::on_upload_media_error(FileId file_id, Status status) {
being_uploaded_files_.erase(it);
- fail_send_message(full_message_id, status.code() > 0 ? status.code() : 500,
- status.message().str()); // TODO CHECK that status has always a code
+ bool is_edit = full_message_id.get_message_id().is_any_server();
+ if (is_edit) {
+ fail_edit_message_media(full_message_id, std::move(status));
+ } else {
+ fail_send_message(full_message_id, std::move(status));
+ }
}
void MessagesManager::on_load_secret_thumbnail(FileId thumbnail_file_id, BufferSlice thumbnail) {
@@ -6537,23 +9360,23 @@ void MessagesManager::on_load_secret_thumbnail(FileId thumbnail_file_id, BufferS
if (m == nullptr) {
// message has already been deleted by the user, do not need to send it
// cancel file upload of the main file to allow next upload with the same file to succeed
- td_->file_manager_->upload(file_id, nullptr, 0, 0);
LOG(INFO) << "Message with a media has already been deleted";
+ cancel_upload_file(file_id, "on_load_secret_thumbnail");
return;
}
+ CHECK(m->message_id.is_yet_unsent());
if (thumbnail.empty()) {
- delete_message_content_thumbnail(m->content.get());
+ delete_message_content_thumbnail(m->content.get(), td_);
}
auto dialog_id = full_message_id.get_dialog_id();
auto can_send_status = can_send_message(dialog_id);
if (can_send_status.is_error()) {
// secret chat was closed during load of the file
- LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status.error();
+ LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status;
- int64 random_id = begin_send_message(dialog_id, m);
- on_send_message_fail(random_id, can_send_status.move_as_error());
+ fail_send_message(full_message_id, std::move(can_send_status));
return;
}
@@ -6571,7 +9394,7 @@ void MessagesManager::on_upload_thumbnail(FileId thumbnail_file_id,
auto it = being_uploaded_thumbnails_.find(thumbnail_file_id);
if (it == being_uploaded_thumbnails_.end()) {
- // callback may be called just before the thumbnail upload was cancelled
+ // callback may be called just before the thumbnail upload was canceled
return;
}
@@ -6583,24 +9406,25 @@ void MessagesManager::on_upload_thumbnail(FileId thumbnail_file_id,
Message *m = get_message(full_message_id);
if (m == nullptr) {
- // message has already been deleted by the user or sent to inaccessible channel, do not need to send it
- // thumbnail file upload should be already cancelled in cancel_send_message_query
+ // message has already been deleted by the user or sent to inaccessible channel, do not need to send or edit it
+ // thumbnail file upload should be already canceled in cancel_send_message_query
LOG(ERROR) << "Message with a media has already been deleted";
return;
}
+ bool is_edit = m->message_id.is_any_server();
+
if (thumbnail_input_file == nullptr) {
- delete_message_content_thumbnail(m->content.get());
+ delete_message_content_thumbnail(is_edit ? m->edited_content.get() : m->content.get(), td_);
}
auto dialog_id = full_message_id.get_dialog_id();
auto can_send_status = can_send_message(dialog_id);
- if (can_send_status.is_error()) {
- // user has left the chat during upload of the thumbnail or lost his privileges
- LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status.error();
+ if (!is_edit && can_send_status.is_error()) {
+ // user has left the chat during upload of the thumbnail or lost their privileges
+ LOG(INFO) << "Can't send a message to " << dialog_id << ": " << can_send_status;
- int64 random_id = begin_send_message(dialog_id, m);
- on_send_message_fail(random_id, can_send_status.move_as_error());
+ fail_send_message(full_message_id, std::move(can_send_status));
return;
}
@@ -6610,35 +9434,64 @@ void MessagesManager::on_upload_thumbnail(FileId thumbnail_file_id,
void MessagesManager::on_upload_dialog_photo(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) {
LOG(INFO) << "File " << file_id << " has been uploaded";
- auto it = uploaded_dialog_photos_.find(file_id);
- if (it == uploaded_dialog_photos_.end()) {
+ auto it = being_uploaded_dialog_photos_.find(file_id);
+ if (it == being_uploaded_dialog_photos_.end()) {
// just in case, as in on_upload_media
return;
}
- Promise<Unit> promise = std::move(it->second.promise);
DialogId dialog_id = it->second.dialog_id;
+ double main_frame_timestamp = it->second.main_frame_timestamp;
+ bool is_animation = it->second.is_animation;
+ bool is_reupload = it->second.is_reupload;
+ Promise<Unit> promise = std::move(it->second.promise);
- uploaded_dialog_photos_.erase(it);
+ being_uploaded_dialog_photos_.erase(it);
- tl_object_ptr<telegram_api::InputChatPhoto> input_chat_photo;
FileView file_view = td_->file_manager_->get_file_view(file_id);
CHECK(!file_view.is_encrypted());
- if (file_view.has_remote_location()) {
- if (file_view.remote_location().is_web()) {
- // TODO reupload
- promise.set_error(Status::Error(400, "Can't use web photo as profile photo"));
- return;
+ if (input_file == nullptr && file_view.has_remote_location()) {
+ if (file_view.main_remote_location().is_web()) {
+ return promise.set_error(Status::Error(400, "Can't use web photo as profile photo"));
+ }
+ if (is_reupload) {
+ return promise.set_error(Status::Error(400, "Failed to reupload the file"));
+ }
+
+ if (is_animation) {
+ CHECK(file_view.get_type() == FileType::Animation);
+ // delete file reference and forcely reupload the file
+ auto file_reference = FileManager::extract_file_reference(file_view.main_remote_location().as_input_document());
+ td_->file_manager_->delete_file_reference(file_id, file_reference);
+ upload_dialog_photo(dialog_id, file_id, is_animation, main_frame_timestamp, true, std::move(promise), {-1});
+ } else {
+ CHECK(file_view.get_type() == FileType::Photo);
+ auto input_photo = file_view.main_remote_location().as_input_photo();
+ auto input_chat_photo = make_tl_object<telegram_api::inputChatPhoto>(std::move(input_photo));
+ send_edit_dialog_photo_query(dialog_id, file_id, std::move(input_chat_photo), std::move(promise));
+ }
+ return;
+ }
+ CHECK(input_file != nullptr);
+
+ int32 flags = 0;
+ tl_object_ptr<telegram_api::InputFile> photo_input_file;
+ tl_object_ptr<telegram_api::InputFile> video_input_file;
+ if (is_animation) {
+ flags |= telegram_api::inputChatUploadedPhoto::VIDEO_MASK;
+ video_input_file = std::move(input_file);
+
+ if (main_frame_timestamp != 0.0) {
+ flags |= telegram_api::inputChatUploadedPhoto::VIDEO_START_TS_MASK;
}
- input_chat_photo = make_tl_object<telegram_api::inputChatPhoto>(file_view.remote_location().as_input_photo());
- file_id = FileId();
} else {
- CHECK(input_file != nullptr);
- input_chat_photo = make_tl_object<telegram_api::inputChatUploadedPhoto>(std::move(input_file));
+ flags |= telegram_api::inputChatUploadedPhoto::FILE_MASK;
+ photo_input_file = std::move(input_file);
}
- // TODO invoke after
- td_->create_handler<EditDialogPhotoQuery>(std::move(promise))->send(file_id, dialog_id, std::move(input_chat_photo));
+ auto input_chat_photo = make_tl_object<telegram_api::inputChatUploadedPhoto>(
+ flags, std::move(photo_input_file), std::move(video_input_file), main_frame_timestamp);
+ send_edit_dialog_photo_query(dialog_id, file_id, std::move(input_chat_photo), std::move(promise));
}
void MessagesManager::on_upload_dialog_photo_error(FileId file_id, Status status) {
@@ -6650,55 +9503,168 @@ void MessagesManager::on_upload_dialog_photo_error(FileId file_id, Status status
LOG(INFO) << "File " << file_id << " has upload error " << status;
CHECK(status.is_error());
- auto it = uploaded_dialog_photos_.find(file_id);
- if (it == uploaded_dialog_photos_.end()) {
+ auto it = being_uploaded_dialog_photos_.find(file_id);
+ if (it == being_uploaded_dialog_photos_.end()) {
// just in case, as in on_upload_media_error
return;
}
Promise<Unit> promise = std::move(it->second.promise);
- uploaded_dialog_photos_.erase(it);
+ being_uploaded_dialog_photos_.erase(it);
promise.set_error(std::move(status));
}
-void MessagesManager::before_get_difference() {
- running_get_difference_ = true;
+void MessagesManager::on_upload_imported_messages(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) {
+ LOG(INFO) << "File " << file_id << " has been uploaded";
- postponed_pts_updates_.insert(std::make_move_iterator(pending_updates_.begin()),
- std::make_move_iterator(pending_updates_.end()));
+ auto it = being_uploaded_imported_messages_.find(file_id);
+ if (it == being_uploaded_imported_messages_.end()) {
+ // just in case, as in on_upload_media
+ return;
+ }
+
+ CHECK(it->second != nullptr);
+ DialogId dialog_id = it->second->dialog_id;
+ vector<FileId> attached_file_ids = std::move(it->second->attached_file_ids);
+ bool is_reupload = it->second->is_reupload;
+ Promise<Unit> promise = std::move(it->second->promise);
- drop_pending_updates();
+ being_uploaded_imported_messages_.erase(it);
+
+ TRY_STATUS_PROMISE(promise, can_send_message(dialog_id));
+
+ FileView file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.is_encrypted());
+ if (input_file == nullptr && file_view.has_remote_location()) {
+ if (file_view.main_remote_location().is_web()) {
+ return promise.set_error(Status::Error(400, "Can't use web file"));
+ }
+ if (is_reupload) {
+ return promise.set_error(Status::Error(400, "Failed to reupload the file"));
+ }
+
+ CHECK(file_view.get_type() == FileType::Document);
+ // delete file reference and forcely reupload the file
+ auto file_reference = FileManager::extract_file_reference(file_view.main_remote_location().as_input_document());
+ td_->file_manager_->delete_file_reference(file_id, file_reference);
+ upload_imported_messages(dialog_id, file_id, std::move(attached_file_ids), true, std::move(promise), {-1});
+ return;
+ }
+ CHECK(input_file != nullptr);
+
+ td_->create_handler<InitHistoryImportQuery>(std::move(promise))
+ ->send(dialog_id, file_id, std::move(input_file), std::move(attached_file_ids));
}
-void MessagesManager::after_get_difference() {
- CHECK(!td_->updates_manager_->running_get_difference());
+void MessagesManager::on_upload_imported_messages_error(FileId file_id, Status status) {
+ if (G()->close_flag()) {
+ // do not fail upload if closing
+ return;
+ }
- if (postponed_pts_updates_.size()) {
- LOG(INFO) << "Begin to apply postponed pts updates";
- auto old_pts = td_->updates_manager_->get_pts();
- for (auto &update : postponed_pts_updates_) {
- auto new_pts = update.second.pts;
- if (new_pts <= old_pts) {
- skip_old_pending_update(std::move(update.second.update), new_pts, old_pts, update.second.pts_count,
- "after get difference");
- } else {
- add_pending_update(std::move(update.second.update), update.second.pts, update.second.pts_count, false,
- "after get difference");
- }
- CHECK(!td_->updates_manager_->running_get_difference());
+ LOG(INFO) << "File " << file_id << " has upload error " << status;
+ CHECK(status.is_error());
+
+ auto it = being_uploaded_imported_messages_.find(file_id);
+ if (it == being_uploaded_imported_messages_.end()) {
+ // just in case, as in on_upload_media_error
+ return;
+ }
+
+ Promise<Unit> promise = std::move(it->second->promise);
+
+ being_uploaded_imported_messages_.erase(it);
+
+ promise.set_error(std::move(status));
+}
+
+void MessagesManager::on_upload_imported_message_attachment(FileId file_id,
+ tl_object_ptr<telegram_api::InputFile> input_file) {
+ LOG(INFO) << "File " << file_id << " has been uploaded";
+
+ auto it = being_uploaded_imported_message_attachments_.find(file_id);
+ if (it == being_uploaded_imported_message_attachments_.end()) {
+ // just in case, as in on_upload_media
+ return;
+ }
+
+ CHECK(it->second != nullptr);
+ DialogId dialog_id = it->second->dialog_id;
+ int64 import_id = it->second->import_id;
+ bool is_reupload = it->second->is_reupload;
+ Promise<Unit> promise = std::move(it->second->promise);
+
+ being_uploaded_imported_message_attachments_.erase(it);
+
+ FileView file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.is_encrypted());
+ if (input_file == nullptr && file_view.has_remote_location()) {
+ if (file_view.main_remote_location().is_web()) {
+ return promise.set_error(Status::Error(400, "Can't use web file"));
+ }
+ if (is_reupload) {
+ return promise.set_error(Status::Error(400, "Failed to reupload the file"));
}
- postponed_pts_updates_.clear();
- LOG(INFO) << "Finish to apply postponed pts updates";
+
+ // delete file reference and forcely reupload the file
+ auto file_reference =
+ file_view.get_type() == FileType::Photo
+ ? FileManager::extract_file_reference(file_view.main_remote_location().as_input_photo())
+ : FileManager::extract_file_reference(file_view.main_remote_location().as_input_document());
+ td_->file_manager_->delete_file_reference(file_id, file_reference);
+ upload_imported_message_attachment(dialog_id, import_id, file_id, true, std::move(promise), {-1});
+ return;
+ }
+ CHECK(input_file != nullptr);
+
+ auto suggested_path = file_view.suggested_path();
+ const PathView path_view(suggested_path);
+ td_->create_handler<UploadImportedMediaQuery>(std::move(promise))
+ ->send(dialog_id, import_id, path_view.file_name().str(), file_id,
+ get_fake_input_media(td_, std::move(input_file), file_id));
+}
+
+void MessagesManager::on_upload_imported_message_attachment_error(FileId file_id, Status status) {
+ if (G()->close_flag()) {
+ // do not fail upload if closing
+ return;
+ }
+
+ LOG(INFO) << "File " << file_id << " has upload error " << status;
+ CHECK(status.is_error());
+
+ auto it = being_uploaded_imported_message_attachments_.find(file_id);
+ if (it == being_uploaded_imported_message_attachments_.end()) {
+ // just in case, as in on_upload_media_error
+ return;
}
+ Promise<Unit> promise = std::move(it->second->promise);
+
+ being_uploaded_imported_message_attachments_.erase(it);
+
+ promise.set_error(std::move(status));
+}
+
+void MessagesManager::before_get_difference() {
+ running_get_difference_ = true;
+
+ // scheduled messages are not returned in getDifference, so we must always reget them after it
+ scheduled_messages_sync_generation_++;
+}
+
+void MessagesManager::after_get_difference() {
+ CHECK(!td_->updates_manager_->running_get_difference());
+
running_get_difference_ = false;
if (!pending_on_get_dialogs_.empty()) {
LOG(INFO) << "Apply postponed results of getDialogs";
for (auto &res : pending_on_get_dialogs_) {
- on_get_dialogs(std::move(res.dialogs), res.total_count, std::move(res.messages), std::move(res.promise));
+ on_get_dialogs(res.folder_id, std::move(res.dialogs), res.total_count, std::move(res.messages),
+ std::move(res.promise));
}
pending_on_get_dialogs_.clear();
}
@@ -6710,14 +9676,28 @@ void MessagesManager::after_get_difference() {
send_update_chat_read_inbox(get_dialog(dialog_id), false, "after_get_difference");
}
}
- if (have_postponed_unread_message_count_update_) {
- LOG(INFO) << "Send postponed unread message count update";
- send_update_unread_message_count(DialogId(), false, "after_get_difference");
+ while (!postponed_unread_message_count_updates_.empty()) {
+ auto *list = get_dialog_list(*postponed_unread_message_count_updates_.begin());
+ CHECK(list != nullptr);
+ send_update_unread_message_count(*list, DialogId(), true, "after_get_difference");
+ }
+ while (!postponed_unread_chat_count_updates_.empty()) {
+ auto *list = get_dialog_list(*postponed_unread_chat_count_updates_.begin());
+ CHECK(list != nullptr);
+ send_update_unread_chat_count(*list, DialogId(), true, "after_get_difference");
}
+ vector<FullMessageId> update_message_ids_to_delete;
for (auto &it : update_message_ids_) {
- // this is impossible for ordinary chats because updates coming during getDifference have already been applied
- auto dialog_id = it.first.get_dialog_id();
+ // there can be unhandled updateMessageId updates after getDifference even for ordinary chats,
+ // because despite updates coming during getDifference have already been applied,
+ // some of them could be postponed because of pts gap
+ auto full_message_id = it.first;
+ auto dialog_id = full_message_id.get_dialog_id();
+ auto message_id = full_message_id.get_message_id();
+ auto old_message_id = it.second;
+ CHECK(message_id.is_valid());
+ CHECK(message_id.is_server());
switch (dialog_id.get_type()) {
case DialogType::Channel:
// get channel difference may prevent updates from being applied
@@ -6726,13 +9706,46 @@ void MessagesManager::after_get_difference() {
}
// fallthrough
case DialogType::User:
- case DialogType::Chat:
- LOG(ERROR) << "Receive updateMessageId from " << it.second << " to " << it.first
- << " but not receive corresponding message. " << td_->updates_manager_->get_state();
- if (dialog_id.get_type() != DialogType::Channel) {
+ case DialogType::Chat: {
+ if (!have_message_force({dialog_id, old_message_id}, "after get difference")) {
+ // The sent message has already been deleted by the user or sent to inaccessible channel.
+ // The sent message may never be received, but we will need updateMessageId in case the message is received
+ // to delete it from the server and not add to the chat.
+ // But if the chat is inaccessible or the message is in an inaccessible chat part, then we will not be able to
+ // add the message or delete it from the server. In this case we forget updateMessageId for such messages in
+ // order to not check them over and over.
+ const Dialog *d = get_dialog(dialog_id);
+ if (!have_input_peer(dialog_id, AccessRights::Read) ||
+ (d != nullptr &&
+ message_id <= td::max(d->last_clear_history_message_id, d->max_unavailable_message_id))) {
+ update_message_ids_to_delete.push_back(full_message_id);
+ }
+ break;
+ }
+
+ const Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ if (dialog_id.get_type() == DialogType::Channel || message_id <= d->last_new_message_id) {
+ LOG(ERROR) << "Receive updateMessageId from " << old_message_id << " to " << full_message_id
+ << " but not receive corresponding message, last_new_message_id = " << d->last_new_message_id;
+ }
+ if (dialog_id.get_type() != DialogType::Channel && message_id <= d->last_new_message_id) {
dump_debug_message_op(get_dialog(dialog_id));
}
+ if (message_id <= d->last_new_message_id) {
+ get_message_from_server(
+ full_message_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), full_message_id, old_message_id](Result<Unit> result) {
+ send_closure(actor_id, &MessagesManager::on_restore_missing_message_after_get_difference,
+ full_message_id, old_message_id, std::move(result));
+ }),
+ "get missing");
+ } else if (dialog_id.get_type() == DialogType::Channel) {
+ LOG(INFO) << "Schedule getDifference in " << dialog_id.get_channel_id();
+ channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
+ }
break;
+ }
case DialogType::SecretChat:
break;
case DialogType::None:
@@ -6741,128 +9754,331 @@ void MessagesManager::after_get_difference() {
break;
}
}
+ for (const auto &full_message_id : update_message_ids_to_delete) {
+ update_message_ids_.erase(full_message_id);
+ }
+
+ if (!td_->auth_manager_->is_bot()) {
+ if (!G()->td_db()->get_binlog_pmc()->isset("fetched_marks_as_unread")) {
+ td_->create_handler<GetDialogUnreadMarksQuery>()->send();
+ }
+
+ auto dialog_list_id = DialogListId(FolderId::archive());
+ auto *list = get_dialog_list(dialog_list_id);
+ CHECK(list != nullptr);
+ if (!list->is_dialog_unread_count_inited_) {
+ int32 limit = list->are_pinned_dialogs_inited_ ? static_cast<int32>(list->pinned_dialogs_.size())
+ : get_pinned_dialogs_limit(dialog_list_id);
+ LOG(INFO) << "Loading chat list in " << dialog_list_id << " to init total unread count";
+ get_dialogs_from_list(dialog_list_id, limit + 2, Auto());
+ }
+ }
+}
+
+void MessagesManager::on_restore_missing_message_after_get_difference(FullMessageId full_message_id,
+ MessageId old_message_id, Result<Unit> result) {
+ if (result.is_error()) {
+ LOG(WARNING) << "Failed to get missing " << full_message_id << " for " << old_message_id << ": " << result.error();
+ } else {
+ LOG(WARNING) << "Successfully get missing " << full_message_id << " for " << old_message_id;
- if (td_->is_online()) {
- // TODO move to AnimationsManager
- td_->animations_manager_->get_saved_animations(Auto());
+ bool have_message = have_message_force(full_message_id, "on_restore_missing_message_after_get_difference");
+ if (!have_message && update_message_ids_.count(full_message_id)) {
+ LOG(ERROR) << "Receive messageEmpty instead of missing " << full_message_id << " for " << old_message_id;
+
+ delete_dialog_messages(full_message_id.get_dialog_id(), {old_message_id}, false,
+ "on_restore_missing_message_after_get_difference");
+
+ update_message_ids_.erase(full_message_id);
+ }
+ }
+}
+
+void MessagesManager::on_get_empty_messages(DialogId dialog_id, const vector<MessageId> &empty_message_ids) {
+ if (!empty_message_ids.empty()) {
+ delete_dialog_messages(dialog_id, empty_message_ids, false, "on_get_empty_messages");
+ }
+}
+
+MessagesManager::MessagesInfo MessagesManager::get_messages_info(
+ tl_object_ptr<telegram_api::messages_Messages> &&messages_ptr, const char *source) {
+ CHECK(messages_ptr != nullptr);
+ LOG(DEBUG) << "Receive result for " << source << ": " << to_string(messages_ptr);
+
+ vector<tl_object_ptr<telegram_api::User>> users;
+ vector<tl_object_ptr<telegram_api::Chat>> chats;
+ MessagesInfo result;
+ switch (messages_ptr->get_id()) {
+ case telegram_api::messages_messages::ID: {
+ auto messages = move_tl_object_as<telegram_api::messages_messages>(messages_ptr);
+
+ users = std::move(messages->users_);
+ chats = std::move(messages->chats_);
+ result.total_count = narrow_cast<int32>(messages->messages_.size());
+ result.messages = std::move(messages->messages_);
+ break;
+ }
+ case telegram_api::messages_messagesSlice::ID: {
+ auto messages = move_tl_object_as<telegram_api::messages_messagesSlice>(messages_ptr);
+
+ users = std::move(messages->users_);
+ chats = std::move(messages->chats_);
+ result.total_count = messages->count_;
+ result.messages = std::move(messages->messages_);
+ break;
+ }
+ case telegram_api::messages_channelMessages::ID: {
+ auto messages = move_tl_object_as<telegram_api::messages_channelMessages>(messages_ptr);
- // TODO move to StickersManager
- td_->stickers_manager_->get_installed_sticker_sets(false, Auto());
- td_->stickers_manager_->get_installed_sticker_sets(true, Auto());
- td_->stickers_manager_->get_featured_sticker_sets(Auto());
- td_->stickers_manager_->get_recent_stickers(false, Auto());
- td_->stickers_manager_->get_recent_stickers(true, Auto());
- td_->stickers_manager_->get_favorite_stickers(Auto());
+ users = std::move(messages->users_);
+ chats = std::move(messages->chats_);
+ result.total_count = messages->count_;
+ result.messages = std::move(messages->messages_);
+ result.is_channel_messages = true;
+ break;
+ }
+ case telegram_api::messages_messagesNotModified::ID:
+ LOG(ERROR) << "Server returned messagesNotModified in response to " << source;
+ break;
+ default:
+ UNREACHABLE();
+ break;
}
- load_notification_settings();
+ td_->contacts_manager_->on_get_users(std::move(users), source);
+ td_->contacts_manager_->on_get_chats(std::move(chats), source);
- // TODO move to ContactsManager or delete after users will become persistent
- td_->contacts_manager_->get_user(td_->contacts_manager_->get_my_id("after_get_difference"), false, Promise<Unit>());
+ return result;
+}
- // TODO resend some messages
+void MessagesManager::get_channel_difference_if_needed(DialogId dialog_id, MessagesInfo &&messages_info,
+ Promise<MessagesInfo> &&promise) {
+ if (!dialog_id.is_valid()) {
+ return get_channel_differences_if_needed(std::move(messages_info), std::move(promise));
+ }
+ for (auto &message : messages_info.messages) {
+ if (need_channel_difference_to_add_message(dialog_id, message)) {
+ return run_after_channel_difference(
+ dialog_id,
+ PromiseCreator::lambda([messages_info = std::move(messages_info), promise = std::move(promise)](
+ Unit ignored) mutable { promise.set_value(std::move(messages_info)); }));
+ }
+ }
+ promise.set_value(std::move(messages_info));
+}
+
+void MessagesManager::get_channel_differences_if_needed(MessagesInfo &&messages_info, Promise<MessagesInfo> &&promise) {
+ MultiPromiseActorSafe mpas{"GetChannelDifferencesIfNeededMultiPromiseActor"};
+ mpas.add_promise(Promise<Unit>());
+ mpas.set_ignore_errors(true);
+ auto lock = mpas.get_promise();
+ for (auto &message : messages_info.messages) {
+ if (message == nullptr) {
+ continue;
+ }
+
+ auto dialog_id = get_message_dialog_id(message);
+ if (need_channel_difference_to_add_message(dialog_id, message)) {
+ run_after_channel_difference(dialog_id, mpas.get_promise());
+ }
+ }
+ // must be added after messages_info is checked
+ mpas.add_promise(PromiseCreator::lambda([messages_info = std::move(messages_info), promise = std::move(promise)](
+ Unit ignored) mutable { promise.set_value(std::move(messages_info)); }));
+ lock.set_value(Unit());
}
void MessagesManager::on_get_messages(vector<tl_object_ptr<telegram_api::Message>> &&messages, bool is_channel_message,
- const char *source) {
- LOG(DEBUG) << "Receive " << messages.size() << " messages";
+ bool is_scheduled, Promise<Unit> &&promise, const char *source) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
for (auto &message : messages) {
- on_get_message(std::move(message), false, is_channel_message, false, false, source);
+ LOG(INFO) << "Receive " << to_string(message);
+ on_get_message(std::move(message), false, is_channel_message, is_scheduled, false, false, source);
+ }
+ promise.set_value(Unit());
+}
+
+bool MessagesManager::delete_newer_server_messages_at_the_end(Dialog *d, MessageId max_message_id) {
+ vector<MessageId> message_ids;
+ find_newer_messages(d->messages.get(), max_message_id, message_ids);
+ if (message_ids.empty()) {
+ return false;
+ }
+
+ vector<MessageId> server_message_ids;
+ vector<MessageId> kept_message_ids;
+ for (auto message_id : message_ids) {
+ CHECK(message_id > max_message_id);
+ if (message_id.is_server()) {
+ server_message_ids.push_back(message_id);
+ } else {
+ kept_message_ids.push_back(message_id);
+ }
}
+
+ delete_dialog_messages(d, server_message_ids, false, "delete_newer_server_messages_at_the_end");
+
+ // connect all messages with ID > max_message_id
+ for (size_t i = 0; i + 1 < kept_message_ids.size(); i++) {
+ auto m = get_message(d, kept_message_ids[i]);
+ CHECK(m != nullptr);
+ if (!m->have_next) {
+ m->have_next = true;
+ attach_message_to_next(d, kept_message_ids[i], "delete_newer_server_messages_at_the_end");
+ }
+ }
+
+ return !kept_message_ids.empty();
}
-void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit,
- bool from_the_end, vector<tl_object_ptr<telegram_api::Message>> &&messages) {
+void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_id, MessageId old_last_new_message_id,
+ int32 offset, int32 limit, bool from_the_end,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
LOG(INFO) << "Receive " << messages.size() << " history messages " << (from_the_end ? "from the end " : "") << "in "
<< dialog_id << " from " << from_message_id << " with offset " << offset << " and limit " << limit;
CHECK(-limit < offset && offset <= 0);
CHECK(offset < 0 || from_the_end);
+ CHECK(!from_message_id.is_scheduled());
- if (narrow_cast<int32>(messages.size()) < limit + offset && !messages.empty()) {
- MessageId first_received_message_id = get_message_id(messages.back());
- if (first_received_message_id.get() >= from_message_id.get()) {
- // it is likely that there is no more history messages on the server
- Dialog *d = get_dialog(dialog_id);
- if (d != nullptr && d->first_database_message_id.is_valid() &&
- d->first_database_message_id.get() <= first_received_message_id.get()) {
- d->have_full_history = true;
- on_dialog_updated(dialog_id, "set have_full_history");
- }
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ MessageId last_received_message_id = messages.empty() ? MessageId() : get_message_id(messages[0], false);
+ if (old_last_new_message_id < d->last_new_message_id && (from_the_end || old_last_new_message_id < from_message_id) &&
+ last_received_message_id < d->last_new_message_id) {
+ // new server messages were added to the dialog since the request was sent, but weren't received
+ // they should have been received, so we must repeat the request to get them
+ if (from_the_end) {
+ get_history_from_the_end_impl(d, false, false, std::move(promise), "on_get_history");
+ } else {
+ get_history_impl(d, from_message_id, offset, limit, false, false, std::move(promise));
}
+ return;
}
- if (from_the_end && narrow_cast<int32>(messages.size()) < limit) {
- // it is likely that there is no more history messages on the server
- Dialog *d = get_dialog(dialog_id);
- if (d != nullptr) {
+ // the server can return less messages than requested if some of messages are deleted during request
+ // but if it happens, it is likely that there are no more messages on the server
+ bool have_full_history = from_the_end && narrow_cast<int32>(messages.size()) < limit && messages.size() <= 1;
+
+ if (messages.empty()) {
+ if (have_full_history) {
d->have_full_history = true;
+ d->have_full_history_source = 1;
on_dialog_updated(dialog_id, "set have_full_history");
}
- }
- if (messages.empty()) {
- if (from_the_end) {
- Dialog *d = get_dialog(dialog_id);
- if (d != nullptr && d->have_full_history) {
+ if (from_the_end && d->have_full_history && d->messages == nullptr) {
+ if (!d->last_database_message_id.is_valid()) {
set_dialog_is_empty(d, "on_get_history empty");
+ } else {
+ LOG(INFO) << "Skip marking " << dialog_id << " as empty, because it probably has messages from "
+ << d->first_database_message_id << " to " << d->last_database_message_id << " in the database";
}
}
// be aware that in some cases an empty answer may be returned, because of the race of getHistory and deleteMessages
- // and not because there is no more messages
+ // and not because there are no more messages
+ promise.set_value(Unit());
return;
}
- // TODO check that messages are received in decreasing message_id order
+ if (messages.size() > 1) {
+ // check that messages are received in decreasing message_id order
+ MessageId cur_message_id = MessageId::max();
+ for (const auto &message : messages) {
+ MessageId message_id = get_message_id(message, false);
+ if (message_id >= cur_message_id) {
+ string error = PSTRING() << "Receive messages in the wrong order in history of " << dialog_id << " from "
+ << from_message_id << " with offset " << offset << ", limit " << limit
+ << ", from_the_end = " << from_the_end << ": ";
+ for (const auto &debug_message : messages) {
+ error += to_string(debug_message);
+ }
+ LOG(ERROR) << error;
+ promise.set_value(Unit());
+ return;
+ }
+ cur_message_id = message_id;
+ }
+ }
- // be aware that dialog may not yet exist
// be aware that returned messages are guaranteed to be consecutive messages, but if !from_the_end they
// may be older (if some messages was deleted) or newer (if some messages were added) than an expected answer
// be aware that any subset of the returned messages may be already deleted and returned as MessageEmpty
bool is_channel_message = dialog_id.get_type() == DialogType::Channel;
MessageId first_added_message_id;
- MessageId last_received_message_id = get_message_id(messages[0]);
MessageId last_added_message_id;
bool have_next = false;
- Dialog *d = get_dialog(dialog_id);
- MessageId prev_last_new_message_id;
- MessageId prev_first_database_message_id;
- MessageId prev_last_database_message_id;
- MessageId prev_last_message_id;
- if (d != nullptr) {
- prev_last_new_message_id = d->last_new_message_id;
- prev_first_database_message_id = d->first_database_message_id;
- prev_last_database_message_id = d->last_database_message_id;
- prev_last_message_id = d->last_message_id;
+ if (narrow_cast<int32>(messages.size()) < limit + offset && messages.size() <= 1) {
+ MessageId first_received_message_id = get_message_id(messages.back(), false);
+ if (first_received_message_id >= from_message_id && d->first_database_message_id.is_valid() &&
+ first_received_message_id >= d->first_database_message_id) {
+ // it is likely that there are no more history messages on the server
+ have_full_history = true;
+ }
+ }
+
+ if (d->last_new_message_id.is_valid()) {
+ // remove too new messages from response
+ while (!messages.empty()) {
+ if (get_message_dialog_id(messages[0]) == dialog_id &&
+ get_message_id(messages[0], false) <= d->last_new_message_id) {
+ // the message is old enough
+ break;
+ }
+
+ LOG(INFO) << "Ignore too new " << get_message_id(messages[0], false);
+ messages.erase(messages.begin());
+ if (messages.empty()) {
+ // received no suitable messages; try again
+ return promise.set_value(Unit());
+ } else {
+ last_received_message_id = get_message_id(messages[0], false);
+ }
+ }
+ }
+
+ bool prev_have_full_history = d->have_full_history;
+ MessageId prev_last_new_message_id = d->last_new_message_id;
+ MessageId prev_first_database_message_id = d->first_database_message_id;
+ MessageId prev_last_database_message_id = d->last_database_message_id;
+ MessageId prev_last_message_id = d->last_message_id;
+ if (from_the_end) {
+ // delete all server messages with ID > last_received_message_id
+ // there were no new messages received after the getHistory request was sent, so they are already deleted message
+ if (delete_newer_server_messages_at_the_end(d, last_received_message_id)) {
+ have_next = true;
+ }
}
for (auto &message : messages) {
- if (!have_next && from_the_end && get_message_id(message).get() < d->last_message_id.get()) {
+ auto expected_message_id = get_message_id(message, false);
+ if (!have_next && from_the_end && expected_message_id < d->last_message_id) {
// last message in the dialog should be attached to the next message if there is some
have_next = true;
}
auto message_dialog_id = get_message_dialog_id(message);
if (message_dialog_id != dialog_id) {
- LOG(ERROR) << "Receive " << get_message_id(message) << " in wrong " << message_dialog_id << " instead of "
+ LOG(ERROR) << "Receive " << expected_message_id << " in wrong " << message_dialog_id << " instead of "
<< dialog_id << ": " << oneline(to_string(message));
continue;
}
auto full_message_id =
- on_get_message(std::move(message), false, is_channel_message, false, have_next, "get history");
+ on_get_message(std::move(message), false, is_channel_message, false, false, have_next, "get history");
auto message_id = full_message_id.get_message_id();
if (message_id.is_valid()) {
+ CHECK(message_id == expected_message_id);
if (!last_added_message_id.is_valid()) {
last_added_message_id = message_id;
}
if (!have_next) {
- if (d == nullptr) {
- d = get_dialog(dialog_id);
- CHECK(d != nullptr);
- }
have_next = true;
} else if (first_added_message_id.is_valid()) {
Message *next_message = get_message(d, first_added_message_id);
@@ -6870,110 +10086,144 @@ void MessagesManager::on_get_history(DialogId dialog_id, MessageId from_message_
if (!next_message->have_previous) {
LOG(INFO) << "Fix have_previous for " << first_added_message_id;
next_message->have_previous = true;
- attach_message_to_previous(d, first_added_message_id);
+ attach_message_to_previous(d, first_added_message_id, "on_get_history");
}
}
first_added_message_id = message_id;
- if (!message_id.is_yet_unsent()) {
- // message should be already saved to database in on_get_message
- // add_message_to_database(d, get_message(d, message_id), "on_get_history");
- }
}
}
- if (d == nullptr) {
- return;
+ if (from_the_end && last_added_message_id.is_valid() && last_added_message_id != last_received_message_id) {
+ CHECK(last_added_message_id < last_received_message_id);
+ delete_newer_server_messages_at_the_end(d, last_added_message_id);
+ }
+
+ if (have_full_history) {
+ d->have_full_history = true;
+ d->have_full_history_source = 2;
+ on_dialog_updated(dialog_id, "set have_full_history 2");
}
- // LOG_IF(ERROR, d->first_message_id.is_valid() && d->first_message_id.get() > first_received_message_id.get())
- // << "Receive message " << first_received_message_id << ", but first chat message is " << d->first_message_id;
+ // LOG_IF(ERROR, d->first_message_id.is_valid() && d->first_message_id > first_received_message_id)
+ // << "Receive " << first_received_message_id << ", but first chat message is " << d->first_message_id;
+ if (from_the_end && !d->last_new_message_id.is_valid()) {
+ set_dialog_last_new_message_id(
+ d, last_added_message_id.is_valid() ? last_added_message_id : last_received_message_id, "on_get_history");
+ }
+ bool intersect_last_database_message_ids =
+ last_added_message_id >= d->first_database_message_id && d->last_database_message_id >= first_added_message_id;
bool need_update_database_message_ids =
- last_added_message_id.is_valid() &&
- (from_the_end || (last_added_message_id.get() >= d->first_database_message_id.get() &&
- d->last_database_message_id.get() >= first_added_message_id.get()));
- if (from_the_end) {
- if (!d->last_new_message_id.is_valid()) {
- set_dialog_last_new_message_id(
- d, last_added_message_id.is_valid() ? last_added_message_id : last_received_message_id, "on_get_history");
- }
- if (last_added_message_id.is_valid()) {
- if (last_added_message_id.get() > d->last_message_id.get()) {
- CHECK(d->last_new_message_id.is_valid());
- set_dialog_last_message_id(d, last_added_message_id, "on_get_history");
- send_update_chat_last_message(d, "on_get_history");
- }
- }
+ last_added_message_id.is_valid() && (from_the_end || intersect_last_database_message_ids);
+ if (from_the_end && last_added_message_id.is_valid() && last_added_message_id > d->last_message_id) {
+ CHECK(d->last_new_message_id.is_valid());
+ set_dialog_last_message_id(d, last_added_message_id, "on_get_history");
+ send_update_chat_last_message(d, "on_get_history");
}
if (need_update_database_message_ids) {
- bool is_dialog_updated = false;
+ if (from_the_end && !intersect_last_database_message_ids && d->last_database_message_id.is_valid()) {
+ if (d->last_database_message_id < first_added_message_id || last_added_message_id == d->last_message_id) {
+ set_dialog_first_database_message_id(d, MessageId(), "on_get_history 1");
+ set_dialog_last_database_message_id(d, MessageId(), "on_get_history 1");
+ } else {
+ auto min_message_id = td::min(d->first_database_message_id, d->last_message_id);
+ LOG_CHECK(last_added_message_id < min_message_id)
+ << need_update_database_message_ids << ' ' << first_added_message_id << ' ' << last_added_message_id << ' '
+ << d->first_database_message_id << ' ' << d->last_database_message_id << ' ' << d->last_new_message_id
+ << ' ' << d->last_message_id << ' ' << prev_first_database_message_id << ' '
+ << prev_last_database_message_id << ' ' << prev_last_new_message_id << ' ' << prev_last_message_id;
+ if (min_message_id <= last_added_message_id.get_next_message_id(MessageType::Server)) {
+ // connect local messages with last received server message
+ set_dialog_first_database_message_id(d, last_added_message_id, "on_get_history 2");
+ } else {
+ LOG(WARNING) << "Have last " << d->last_message_id << " and first database " << d->first_database_message_id
+ << " in " << dialog_id << ", but received history from the end only up to "
+ << last_added_message_id;
+ // can't connect messages, because there can be unknown server messages after last_added_message_id
+ }
+ }
+ }
if (!d->last_database_message_id.is_valid()) {
CHECK(d->last_message_id.is_valid());
MessagesConstIterator it(d, d->last_message_id);
+ MessageId new_first_database_message_id;
while (*it != nullptr) {
auto message_id = (*it)->message_id;
if (message_id.is_server() || message_id.is_local()) {
if (!d->last_database_message_id.is_valid()) {
set_dialog_last_database_message_id(d, message_id, "on_get_history");
}
- set_dialog_first_database_message_id(d, message_id, "on_get_history");
+ new_first_database_message_id = message_id;
try_restore_dialog_reply_markup(d, *it);
}
--it;
}
- is_dialog_updated = true;
+ if (new_first_database_message_id.is_valid()) {
+ set_dialog_first_database_message_id(d, new_first_database_message_id, "on_get_history");
+ }
} else {
- CHECK(d->last_new_message_id.is_valid())
+ LOG_CHECK(d->last_new_message_id.is_valid())
<< dialog_id << " " << from_the_end << " " << d->first_database_message_id << " "
<< d->last_database_message_id << " " << first_added_message_id << " " << last_added_message_id << " "
- << d->last_message_id << prev_last_new_message_id << prev_first_database_message_id
- << prev_last_database_message_id << prev_last_message_id;
+ << d->last_message_id << " " << d->last_new_message_id << " " << d->have_full_history << " "
+ << prev_last_new_message_id << " " << prev_first_database_message_id << " " << prev_last_database_message_id
+ << " " << prev_last_message_id << " " << prev_have_full_history << " " << d->debug_last_new_message_id << " "
+ << d->debug_first_database_message_id << " " << d->debug_last_database_message_id << " " << from_message_id
+ << " " << offset << " " << limit << " " << messages.size() << " " << last_received_message_id << " "
+ << d->debug_set_dialog_last_database_message_id;
CHECK(d->first_database_message_id.is_valid());
{
MessagesConstIterator it(d, d->first_database_message_id);
if (*it != nullptr && ((*it)->message_id == d->first_database_message_id || (*it)->have_next)) {
+ MessageId new_first_database_message_id = d->first_database_message_id;
while (*it != nullptr) {
auto message_id = (*it)->message_id;
- if ((message_id.is_server() || message_id.is_local()) &&
- message_id.get() < d->first_database_message_id.get()) {
- set_dialog_first_database_message_id(d, message_id, "on_get_history 2");
+ if ((message_id.is_server() || message_id.is_local()) && message_id < new_first_database_message_id) {
+ new_first_database_message_id = message_id;
try_restore_dialog_reply_markup(d, *it);
- is_dialog_updated = true;
}
--it;
}
+ if (new_first_database_message_id != d->first_database_message_id) {
+ set_dialog_first_database_message_id(d, new_first_database_message_id, "on_get_history 2");
+ }
}
}
{
MessagesConstIterator it(d, d->last_database_message_id);
if (*it != nullptr && ((*it)->message_id == d->last_database_message_id || (*it)->have_next)) {
+ MessageId new_last_database_message_id = d->last_database_message_id;
while (*it != nullptr) {
auto message_id = (*it)->message_id;
- if ((message_id.is_server() || message_id.is_local()) &&
- message_id.get() > d->last_database_message_id.get()) {
- set_dialog_last_database_message_id(d, message_id, "on_get_history 2");
- is_dialog_updated = true;
+ if ((message_id.is_server() || message_id.is_local()) && message_id > new_last_database_message_id) {
+ new_last_database_message_id = message_id;
}
++it;
}
+ if (new_last_database_message_id != d->last_database_message_id) {
+ set_dialog_last_database_message_id(d, new_last_database_message_id, "on_get_history 2");
+ }
}
}
}
- CHECK(d->first_database_message_id.is_valid());
+ LOG_CHECK(d->first_database_message_id.is_valid())
+ << dialog_id << " " << from_the_end << " " << d->first_database_message_id << " " << d->last_database_message_id
+ << " " << first_added_message_id << " " << last_added_message_id << " " << d->last_message_id << " "
+ << d->last_new_message_id << " " << d->have_full_history << " " << prev_last_new_message_id << " "
+ << prev_first_database_message_id << " " << prev_last_database_message_id << " " << prev_last_message_id << " "
+ << prev_have_full_history << " " << d->debug_last_new_message_id << " " << d->debug_first_database_message_id
+ << " " << d->debug_last_database_message_id << " " << from_message_id << " " << offset << " " << limit << " "
+ << messages.size() << " " << last_received_message_id << " " << d->debug_set_dialog_last_database_message_id;
CHECK(d->last_database_message_id.is_valid());
for (auto &first_message_id : d->first_database_message_id_by_index) {
- if (first_added_message_id.get() < first_message_id.get() &&
- first_message_id.get() <= last_added_message_id.get()) {
+ if (first_added_message_id < first_message_id && first_message_id <= last_added_message_id) {
first_message_id = first_added_message_id;
}
}
-
- if (is_dialog_updated) {
- on_dialog_updated(dialog_id, "on_get_history");
- }
}
+ promise.set_value(Unit());
}
vector<DialogId> MessagesManager::get_peers_dialog_ids(vector<tl_object_ptr<telegram_api::Peer>> &&peers) {
@@ -6994,69 +10244,143 @@ void MessagesManager::on_get_public_dialogs_search_result(const string &query,
vector<tl_object_ptr<telegram_api::Peer>> &&peers) {
auto it = search_public_dialogs_queries_.find(query);
CHECK(it != search_public_dialogs_queries_.end());
- CHECK(it->second.size() > 0);
+ CHECK(!it->second.empty());
auto promises = std::move(it->second);
search_public_dialogs_queries_.erase(it);
found_public_dialogs_[query] = get_peers_dialog_ids(std::move(peers));
found_on_server_dialogs_[query] = get_peers_dialog_ids(std::move(my_peers));
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(promises);
}
void MessagesManager::on_failed_public_dialogs_search(const string &query, Status &&error) {
auto it = search_public_dialogs_queries_.find(query);
CHECK(it != search_public_dialogs_queries_.end());
- CHECK(it->second.size() > 0);
+ CHECK(!it->second.empty());
auto promises = std::move(it->second);
search_public_dialogs_queries_.erase(it);
found_public_dialogs_[query]; // negative cache
found_on_server_dialogs_[query]; // negative cache
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+ fail_promises(promises, std::move(error));
+}
+
+void MessagesManager::on_get_message_search_result_calendar(
+ DialogId dialog_id, MessageId from_message_id, MessageSearchFilter filter, int64 random_id, int32 total_count,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ vector<tl_object_ptr<telegram_api::searchResultsCalendarPeriod>> &&periods, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto it = found_dialog_message_calendars_.find(random_id);
+ CHECK(it != found_dialog_message_calendars_.end());
+
+ int32 received_message_count = 0;
+ for (auto &message : messages) {
+ auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
+ false, false, false, "on_get_message_search_result_calendar");
+ if (new_full_message_id == FullMessageId()) {
+ total_count--;
+ continue;
+ }
+
+ if (new_full_message_id.get_dialog_id() != dialog_id) {
+ LOG(ERROR) << "Receive " << new_full_message_id << " instead of a message in " << dialog_id;
+ total_count--;
+ continue;
+ }
+
+ received_message_count++;
}
+ if (total_count < received_message_count) {
+ LOG(ERROR) << "Receive " << received_message_count << " valid messages out of " << total_count << " in "
+ << messages.size() << " messages";
+ total_count = received_message_count;
+ }
+
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)];
+ if (old_message_count != total_count) {
+ old_message_count = total_count;
+ on_dialog_updated(dialog_id, "on_get_message_search_result_calendar");
+ }
+
+ vector<td_api::object_ptr<td_api::messageCalendarDay>> days;
+ for (auto &period : periods) {
+ auto message_id = MessageId(ServerMessageId(period->min_msg_id_));
+ const auto *m = get_message(d, message_id);
+ if (m == nullptr) {
+ LOG(ERROR) << "Failed to find " << message_id;
+ continue;
+ }
+ if (period->count_ <= 0) {
+ LOG(ERROR) << "Receive " << to_string(period);
+ continue;
+ }
+ days.push_back(td_api::make_object<td_api::messageCalendarDay>(
+ period->count_, get_message_object(dialog_id, m, "on_get_message_search_result_calendar")));
+ }
+ it->second = td_api::make_object<td_api::messageCalendar>(total_count, std::move(days));
+ promise.set_value(Unit());
}
-void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, const string &query,
- UserId sender_user_id, MessageId from_message_id,
- int32 offset, int32 limit, SearchMessagesFilter filter,
- int64 random_id, int32 total_count,
- vector<tl_object_ptr<telegram_api::Message>> &&messages) {
+void MessagesManager::on_failed_get_message_search_result_calendar(DialogId dialog_id, int64 random_id) {
+ auto it = found_dialog_message_calendars_.find(random_id);
+ CHECK(it != found_dialog_message_calendars_.end());
+ found_dialog_message_calendars_.erase(it);
+}
+
+void MessagesManager::on_get_dialog_messages_search_result(
+ DialogId dialog_id, const string &query, DialogId sender_dialog_id, MessageId from_message_id, int32 offset,
+ int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id, int64 random_id, int32 total_count,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
LOG(INFO) << "Receive " << messages.size() << " found messages in " << dialog_id;
if (!dialog_id.is_valid()) {
CHECK(query.empty());
- CHECK(!sender_user_id.is_valid());
+ CHECK(!sender_dialog_id.is_valid());
+ CHECK(!top_thread_message_id.is_valid());
auto it = found_call_messages_.find(random_id);
CHECK(it != found_call_messages_.end());
MessageId first_added_message_id;
if (messages.empty()) {
- // messages may be empty because there is no more messages or they can't be found due to global limit
- // anyway pretend that there is no more messages
+ // messages may be empty because there are no more messages or they can't be found due to global limit
+ // anyway pretend that there are no more messages
first_added_message_id = MessageId::min();
}
auto &result = it->second.second;
CHECK(result.empty());
+ int32 added_message_count = 0;
for (auto &message : messages) {
- auto new_message = on_get_message(std::move(message), false, false, false, false, "search call messages");
- if (new_message != FullMessageId()) {
- result.push_back(new_message);
+ auto new_full_message_id =
+ on_get_message(std::move(message), false, false, false, false, false, "search call messages");
+ if (new_full_message_id == FullMessageId()) {
+ continue;
}
- auto message_id = new_message.get_message_id();
- if (message_id.get() < first_added_message_id.get() || !first_added_message_id.is_valid()) {
+ result.push_back(new_full_message_id);
+ added_message_count++;
+
+ auto message_id = new_full_message_id.get_message_id();
+ CHECK(message_id.is_valid());
+ if (message_id < first_added_message_id || !first_added_message_id.is_valid()) {
first_added_message_id = message_id;
}
}
+ if (total_count < added_message_count) {
+ LOG(ERROR) << "Receive total_count = " << total_count << ", but added " << added_message_count
+ << " messages out of " << messages.size();
+ total_count = added_message_count;
+ }
if (G()->parameters().use_message_db) {
bool update_state = false;
- auto &old_message_count = calls_db_state_.message_count_by_index[search_calls_filter_index(filter)];
+ auto &old_message_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)];
if (old_message_count != total_count) {
LOG(INFO) << "Update calls database message count to " << total_count;
old_message_count = total_count;
@@ -7064,15 +10388,13 @@ void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, c
}
auto &old_first_db_message_id =
- calls_db_state_.first_calls_database_message_id_by_index[search_calls_filter_index(filter)];
- bool from_the_end = !from_message_id.is_valid() || from_message_id.get() >= MessageId::max().get();
- LOG(INFO) << "from_the_end = " << from_the_end << ", old_first_db_message_id = " << old_first_db_message_id.get()
- << ", first_added_message_id = " << first_added_message_id.get()
- << ", from_message_id = " << from_message_id.get();
- if ((from_the_end ||
- (old_first_db_message_id.is_valid() && old_first_db_message_id.get() <= from_message_id.get())) &&
- (!old_first_db_message_id.is_valid() || first_added_message_id.get() < old_first_db_message_id.get())) {
- LOG(INFO) << "Update calls database first message id to " << first_added_message_id;
+ calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)];
+ bool from_the_end = !from_message_id.is_valid() || from_message_id >= MessageId::max();
+ LOG(INFO) << "Have from_the_end = " << from_the_end << ", old_first_db_message_id = " << old_first_db_message_id
+ << ", first_added_message_id = " << first_added_message_id << ", from_message_id = " << from_message_id;
+ if ((from_the_end || (old_first_db_message_id.is_valid() && old_first_db_message_id <= from_message_id)) &&
+ (!old_first_db_message_id.is_valid() || first_added_message_id < old_first_db_message_id)) {
+ LOG(INFO) << "Update calls database first message to " << first_added_message_id;
old_first_db_message_id = first_added_message_id;
update_state = true;
}
@@ -7081,6 +10403,7 @@ void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, c
}
}
it->second.first = total_count;
+ promise.set_value(Unit());
return;
}
@@ -7091,65 +10414,99 @@ void MessagesManager::on_get_dialog_messages_search_result(DialogId dialog_id, c
CHECK(result.empty());
MessageId first_added_message_id;
if (messages.empty()) {
- // messages may be empty because there is no more messages or they can't be found due to global limit
- // anyway pretend that there is no more messages
+ // messages may be empty because there are no more messages or they can't be found due to global limit
+ // anyway pretend that there are no more messages
first_added_message_id = MessageId::min();
}
+ bool can_be_in_different_dialog = top_thread_message_id.is_valid() && is_broadcast_channel(dialog_id);
+ DialogId real_dialog_id;
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
for (auto &message : messages) {
- auto new_message = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false,
- false, "search chat messages");
- if (new_message != FullMessageId()) {
- if (new_message.get_dialog_id() != dialog_id) {
- LOG(ERROR) << "Receive " << new_message << " instead of a message in " << dialog_id;
+ auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
+ false, false, false, "on_get_dialog_messages_search_result");
+ if (new_full_message_id == FullMessageId()) {
+ total_count--;
+ continue;
+ }
+
+ if (new_full_message_id.get_dialog_id() != dialog_id) {
+ if (!can_be_in_different_dialog) {
+ LOG(ERROR) << "Receive " << new_full_message_id << " instead of a message in " << dialog_id;
+ total_count--;
continue;
+ } else {
+ if (!real_dialog_id.is_valid()) {
+ real_dialog_id = new_full_message_id.get_dialog_id();
+ found_dialog_messages_dialog_id_[random_id] = real_dialog_id;
+ } else if (new_full_message_id.get_dialog_id() != real_dialog_id) {
+ LOG(ERROR) << "Receive " << new_full_message_id << " instead of a message in " << real_dialog_id << " or "
+ << dialog_id;
+ total_count--;
+ continue;
+ }
}
+ }
- // TODO check that messages are returned in decreasing message_id order
- auto message_id = new_message.get_message_id();
- if (message_id.get() < first_added_message_id.get() || !first_added_message_id.is_valid()) {
- first_added_message_id = message_id;
- }
- result.push_back(message_id);
- } else {
+ auto message_id = new_full_message_id.get_message_id();
+ if (filter == MessageSearchFilter::UnreadMention && message_id <= d->last_read_all_mentions_message_id &&
+ !real_dialog_id.is_valid()) {
total_count--;
+ continue;
+ }
+
+ // TODO check that messages are returned in decreasing message_id order
+ if (message_id < first_added_message_id || !first_added_message_id.is_valid()) {
+ first_added_message_id = message_id;
}
+ result.push_back(message_id);
}
if (total_count < static_cast<int32>(result.size())) {
LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size()
<< " messages";
total_count = static_cast<int32>(result.size());
}
- if (query.empty() && !sender_user_id.is_valid() && filter != SearchMessagesFilter::Empty &&
- G()->parameters().use_message_db) {
- Dialog *d = get_dialog(dialog_id);
- CHECK(d != nullptr);
+ if (query.empty() && !sender_dialog_id.is_valid() && filter != MessageSearchFilter::Empty &&
+ !top_thread_message_id.is_valid()) {
+ bool from_the_end = !from_message_id.is_valid() ||
+ (d->last_message_id != MessageId() && from_message_id > d->last_message_id) ||
+ from_message_id >= MessageId::max();
bool update_dialog = false;
- auto &old_message_count = d->message_count_by_index[search_messages_filter_index(filter)];
+ auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)];
if (old_message_count != total_count) {
old_message_count = total_count;
- if (filter == SearchMessagesFilter::UnreadMention) {
+ if (filter == MessageSearchFilter::UnreadMention) {
d->unread_mention_count = old_message_count;
+ update_dialog_mention_notification_count(d);
send_update_chat_unread_mention_count(d);
}
+ if (filter == MessageSearchFilter::UnreadReaction) {
+ d->unread_reaction_count = old_message_count;
+ // update_dialog_mention_notification_count(d);
+ send_update_chat_unread_reaction_count(d, "on_get_dialog_messages_search_result");
+ }
update_dialog = true;
}
- auto &old_first_db_message_id = d->first_database_message_id_by_index[search_messages_filter_index(filter)];
- bool from_the_end = !from_message_id.is_valid() ||
- (d->last_message_id != MessageId() && from_message_id.get() > d->last_message_id.get()) ||
- from_message_id.get() >= MessageId::max().get();
+ auto &old_first_database_message_id = d->first_database_message_id_by_index[message_search_filter_index(filter)];
if ((from_the_end ||
- (old_first_db_message_id.is_valid() && old_first_db_message_id.get() <= from_message_id.get())) &&
- (!old_first_db_message_id.is_valid() || first_added_message_id.get() < old_first_db_message_id.get())) {
- old_first_db_message_id = first_added_message_id;
+ (old_first_database_message_id.is_valid() && old_first_database_message_id <= from_message_id)) &&
+ (!old_first_database_message_id.is_valid() || first_added_message_id < old_first_database_message_id)) {
+ old_first_database_message_id = first_added_message_id;
update_dialog = true;
}
if (update_dialog) {
on_dialog_updated(dialog_id, "search results");
}
+
+ if (from_the_end && filter == MessageSearchFilter::Pinned) {
+ set_dialog_last_pinned_message_id(d, result.empty() ? MessageId() : result[0]);
+ }
}
+
it->second.first = total_count;
+ promise.set_value(Unit());
}
void MessagesManager::on_failed_dialog_messages_search(DialogId dialog_id, int64 random_id) {
@@ -7165,10 +10522,48 @@ void MessagesManager::on_failed_dialog_messages_search(DialogId dialog_id, int64
found_dialog_messages_.erase(it);
}
+void MessagesManager::on_get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, int32 total_count,
+ Promise<int32> &&promise) {
+ LOG(INFO) << "Receive " << total_count << " message count in " << dialog_id << " with filter " << filter;
+ if (total_count < 0) {
+ LOG(ERROR) << "Receive total message count = " << total_count << " in " << dialog_id << " with filter " << filter;
+ total_count = 0;
+ }
+
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ CHECK(filter != MessageSearchFilter::Empty);
+ CHECK(filter != MessageSearchFilter::UnreadMention);
+ CHECK(filter != MessageSearchFilter::UnreadReaction);
+ CHECK(filter != MessageSearchFilter::FailedToSend);
+
+ auto &old_message_count = d->message_count_by_index[message_search_filter_index(filter)];
+ if (old_message_count != total_count) {
+ old_message_count = total_count;
+ on_dialog_updated(dialog_id, "on_get_dialog_message_count");
+ }
+
+ if (total_count == 0) {
+ auto &old_first_database_message_id = d->first_database_message_id_by_index[message_search_filter_index(filter)];
+ if (old_first_database_message_id != MessageId::min()) {
+ old_first_database_message_id = MessageId::min();
+ on_dialog_updated(dialog_id, "on_get_dialog_message_count");
+ }
+ if (filter == MessageSearchFilter::Pinned) {
+ set_dialog_last_pinned_message_id(d, MessageId());
+ }
+ }
+ promise.set_value(std::move(total_count));
+}
+
void MessagesManager::on_get_messages_search_result(const string &query, int32 offset_date, DialogId offset_dialog_id,
- MessageId offset_message_id, int32 limit, int64 random_id,
- int32 total_count,
- vector<tl_object_ptr<telegram_api::Message>> &&messages) {
+ MessageId offset_message_id, int32 limit,
+ MessageSearchFilter filter, int32 min_date, int32 max_date,
+ int64 random_id, int32 total_count,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
LOG(INFO) << "Receive " << messages.size() << " found messages";
auto it = found_messages_.find(random_id);
CHECK(it != found_messages_.end());
@@ -7177,11 +10572,11 @@ void MessagesManager::on_get_messages_search_result(const string &query, int32 o
CHECK(result.empty());
for (auto &message : messages) {
auto dialog_id = get_message_dialog_id(message);
- auto new_message = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false,
- false, "search messages");
- if (new_message != FullMessageId()) {
- CHECK(dialog_id == new_message.get_dialog_id());
- result.push_back(new_message);
+ auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
+ false, false, false, "search messages");
+ if (new_full_message_id != FullMessageId()) {
+ CHECK(dialog_id == new_full_message_id.get_dialog_id());
+ result.push_back(new_full_message_id);
} else {
total_count--;
}
@@ -7192,6 +10587,7 @@ void MessagesManager::on_get_messages_search_result(const string &query, int32 o
total_count = static_cast<int32>(result.size());
}
it->second.first = total_count;
+ promise.set_value(Unit());
}
void MessagesManager::on_failed_messages_search(int64 random_id) {
@@ -7200,30 +10596,111 @@ void MessagesManager::on_failed_messages_search(int64 random_id) {
found_messages_.erase(it);
}
-void MessagesManager::on_get_recent_locations(DialogId dialog_id, int32 limit, int64 random_id, int32 total_count,
- vector<tl_object_ptr<telegram_api::Message>> &&messages) {
- LOG(INFO) << "Receive " << messages.size() << " recent locations in " << dialog_id;
- auto it = found_dialog_recent_location_messages_.find(random_id);
- CHECK(it != found_dialog_recent_location_messages_.end());
+void MessagesManager::on_get_outgoing_document_messages(vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
- auto &result = it->second.second;
- CHECK(result.empty());
+ FoundMessages found_messages;
+ for (auto &message : messages) {
+ auto dialog_id = get_message_dialog_id(message);
+ auto full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false,
+ false, false, "on_get_outgoing_document_messages");
+ if (full_message_id != FullMessageId()) {
+ CHECK(dialog_id == full_message_id.get_dialog_id());
+ found_messages.full_message_ids.push_back(full_message_id);
+ }
+ }
+ auto result = get_found_messages_object(found_messages, "on_get_outgoing_document_messages");
+ td::remove_if(result->messages_,
+ [](const auto &message) { return message->content_->get_id() != td_api::messageDocument::ID; });
+ result->total_count_ = narrow_cast<int32>(result->messages_.size());
+ promise.set_value(std::move(result));
+}
+
+void MessagesManager::on_get_scheduled_server_messages(DialogId dialog_id, uint32 generation,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ bool is_not_modified) {
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ if (generation < d->scheduled_messages_sync_generation) {
+ LOG(INFO) << "Ignore scheduled messages with old generation " << generation << " instead of "
+ << d->scheduled_messages_sync_generation << " in " << dialog_id;
+ return;
+ }
+ d->scheduled_messages_sync_generation = generation;
+
+ if (is_not_modified) {
+ LOG(INFO) << "Scheduled messages are mot modified in " << dialog_id;
+ return;
+ }
+
+ vector<MessageId> old_message_ids;
+ find_old_messages(d->scheduled_messages.get(),
+ MessageId(ScheduledServerMessageId(), std::numeric_limits<int32>::max(), true), old_message_ids);
+ FlatHashMap<ScheduledServerMessageId, MessageId, ScheduledServerMessageIdHash> old_server_message_ids;
+ for (auto &message_id : old_message_ids) {
+ if (message_id.is_scheduled_server()) {
+ old_server_message_ids[message_id.get_scheduled_server_message_id()] = message_id;
+ }
+ }
+
+ bool is_channel_message = dialog_id.get_type() == DialogType::Channel;
+ bool has_scheduled_server_messages = false;
for (auto &message : messages) {
- auto new_message = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false,
- false, "get recent locations");
- if (new_message != FullMessageId()) {
- if (new_message.get_dialog_id() != dialog_id) {
- LOG(ERROR) << "Receive " << new_message << " instead of a message in " << dialog_id;
+ auto message_dialog_id = get_message_dialog_id(message);
+ if (message_dialog_id != dialog_id) {
+ LOG(ERROR) << "Receive " << get_message_id(message, true) << " in wrong " << message_dialog_id << " instead of "
+ << dialog_id << ": " << oneline(to_string(message));
+ continue;
+ }
+
+ auto full_message_id = on_get_message(std::move(message), d->sent_scheduled_messages, is_channel_message, true,
+ false, false, "on_get_scheduled_server_messages");
+ auto message_id = full_message_id.get_message_id();
+ if (message_id.is_valid_scheduled()) {
+ CHECK(message_id.is_scheduled_server());
+ old_server_message_ids.erase(message_id.get_scheduled_server_message_id());
+ has_scheduled_server_messages = true;
+ }
+ }
+ on_update_dialog_has_scheduled_server_messages(dialog_id, has_scheduled_server_messages);
+
+ for (const auto &it : old_server_message_ids) {
+ auto message_id = it.second;
+ auto message = do_delete_scheduled_message(d, message_id, true, "on_get_scheduled_server_messages");
+ CHECK(message != nullptr);
+ send_update_delete_messages(dialog_id, {message->message_id.get()}, true);
+ }
+
+ send_update_chat_has_scheduled_messages(d, false);
+}
+
+void MessagesManager::on_get_recent_locations(DialogId dialog_id, int32 limit, int32 total_count,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<td_api::object_ptr<td_api::messages>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ LOG(INFO) << "Receive " << messages.size() << " recent locations in " << dialog_id;
+ vector<MessageId> result;
+ for (auto &message : messages) {
+ auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
+ false, false, false, "get recent locations");
+ if (new_full_message_id != FullMessageId()) {
+ if (new_full_message_id.get_dialog_id() != dialog_id) {
+ LOG(ERROR) << "Receive " << new_full_message_id << " instead of a message in " << dialog_id;
+ total_count--;
continue;
}
- auto m = get_message(new_message);
- if (m->content->get_id() != MessageLiveLocation::ID) {
- LOG(ERROR) << "Receive a message of wrong type " << m->content->get_id() << " in on_get_recent_locations in "
+ auto m = get_message(new_full_message_id);
+ CHECK(m != nullptr);
+ if (m->content->get_type() != MessageContentType::LiveLocation) {
+ LOG(ERROR) << "Receive a message of wrong type " << m->content->get_type() << " in on_get_recent_locations in "
<< dialog_id;
+ total_count--;
continue;
}
- result.push_back(new_message.get_message_id());
+ result.push_back(m->message_id);
} else {
total_count--;
}
@@ -7233,18 +10710,50 @@ void MessagesManager::on_get_recent_locations(DialogId dialog_id, int32 limit, i
<< " messages";
total_count = static_cast<int32>(result.size());
}
- it->second.first = total_count;
+ promise.set_value(get_messages_object(total_count, dialog_id, result, true, "on_get_recent_locations"));
}
-void MessagesManager::on_get_recent_locations_failed(int64 random_id) {
- auto it = found_dialog_recent_location_messages_.find(random_id);
- CHECK(it != found_dialog_recent_location_messages_.end());
- found_dialog_recent_location_messages_.erase(it);
+void MessagesManager::on_get_message_public_forwards(int32 total_count,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ LOG(INFO) << "Receive " << messages.size() << " forwarded messages";
+ vector<td_api::object_ptr<td_api::message>> result;
+ FullMessageId last_full_message_id;
+ for (auto &message : messages) {
+ auto dialog_id = get_message_dialog_id(message);
+ auto new_full_message_id = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel,
+ false, false, false, "get message public forwards");
+ if (new_full_message_id != FullMessageId()) {
+ CHECK(dialog_id == new_full_message_id.get_dialog_id());
+ result.push_back(get_message_object(new_full_message_id, "on_get_message_public_forwards"));
+ CHECK(result.back() != nullptr);
+ last_full_message_id = new_full_message_id;
+ } else {
+ total_count--;
+ }
+ }
+ if (total_count < static_cast<int32>(result.size())) {
+ LOG(ERROR) << "Receive " << result.size() << " valid messages out of " << total_count << " in " << messages.size()
+ << " messages";
+ total_count = static_cast<int32>(result.size());
+ }
+ string next_offset;
+ if (!result.empty()) {
+ auto m = get_message(last_full_message_id);
+ CHECK(m != nullptr);
+ next_offset = PSTRING() << m->date << "," << last_full_message_id.get_dialog_id().get() << ","
+ << m->message_id.get_server_message_id().get();
+ }
+
+ promise.set_value(td_api::make_object<td_api::foundMessages>(total_count, std::move(result), next_offset));
}
void MessagesManager::delete_messages_from_updates(const vector<MessageId> &message_ids) {
- std::unordered_map<DialogId, vector<int64>, DialogIdHash> deleted_message_ids;
- std::unordered_map<DialogId, bool, DialogIdHash> need_update_dialog_pos;
+ FlatHashMap<DialogId, vector<int64>, DialogIdHash> deleted_message_ids;
+ FlatHashMap<DialogId, bool, DialogIdHash> need_update_dialog_pos;
+ vector<unique_ptr<Message>> deleted_messages;
for (auto message_id : message_ids) {
if (!message_id.is_valid() || !message_id.is_server()) {
LOG(ERROR) << "Incoming update tries to delete " << message_id;
@@ -7253,18 +10762,24 @@ void MessagesManager::delete_messages_from_updates(const vector<MessageId> &mess
Dialog *d = get_dialog_by_message_id(message_id);
if (d != nullptr) {
- auto m = delete_message(d, message_id, true, &need_update_dialog_pos[d->dialog_id], "updates");
- CHECK(m != nullptr);
- CHECK(m->message_id == message_id);
- deleted_message_ids[d->dialog_id].push_back(message_id.get());
+ auto message =
+ delete_message(d, message_id, true, &need_update_dialog_pos[d->dialog_id], "delete_messages_from_updates");
+ CHECK(message != nullptr);
+ LOG_CHECK(message->message_id == message_id) << message_id << " " << message->message_id << " " << d->dialog_id;
+ deleted_message_ids[d->dialog_id].push_back(message->message_id.get());
+ deleted_messages.push_back(std::move(message));
}
if (last_clear_history_message_id_to_dialog_id_.count(message_id)) {
d = get_dialog(last_clear_history_message_id_to_dialog_id_[message_id]);
CHECK(d != nullptr);
- auto m = delete_message(d, message_id, true, &need_update_dialog_pos[d->dialog_id], "updates");
- CHECK(m == nullptr);
+ auto message =
+ delete_message(d, message_id, true, &need_update_dialog_pos[d->dialog_id], "delete_messages_from_updates");
+ CHECK(message == nullptr);
}
}
+ if (deleted_messages.size() >= MIN_DELETED_ASYNCHRONOUSLY_MESSAGES) {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), deleted_messages);
+ }
for (auto &it : need_update_dialog_pos) {
if (it.second) {
auto dialog_id = it.first;
@@ -7275,303 +10790,115 @@ void MessagesManager::delete_messages_from_updates(const vector<MessageId> &mess
}
for (auto &it : deleted_message_ids) {
auto dialog_id = it.first;
- send_update_delete_messages(dialog_id, std::move(it.second), true, false);
+ send_update_delete_messages(dialog_id, std::move(it.second), true);
}
}
-void MessagesManager::delete_dialog_messages_from_updates(DialogId dialog_id, const vector<MessageId> &message_ids) {
- CHECK(dialog_id.get_type() == DialogType::Channel || dialog_id.get_type() == DialogType::SecretChat);
- Dialog *d = get_dialog_force(dialog_id);
+void MessagesManager::delete_dialog_messages(DialogId dialog_id, const vector<MessageId> &message_ids,
+ bool force_update_for_not_found_messages, const char *source) {
+ Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages");
if (d == nullptr) {
- LOG(INFO) << "Ignore deleteChannelMessages for unknown " << dialog_id;
+ LOG(INFO) << "Ignore deleteChannelMessages for unknown " << dialog_id << " from " << source;
CHECK(dialog_id.get_type() == DialogType::Channel);
return;
}
+ delete_dialog_messages(d, message_ids, force_update_for_not_found_messages, source);
+}
+
+void MessagesManager::delete_dialog_messages(Dialog *d, const vector<MessageId> &message_ids,
+ bool force_update_for_not_found_messages, const char *source) {
+ vector<unique_ptr<Message>> deleted_messages;
vector<int64> deleted_message_ids;
bool need_update_dialog_pos = false;
+ bool need_update_chat_has_scheduled_messages = false;
for (auto message_id : message_ids) {
- if (!message_id.is_valid() || (!message_id.is_server() && dialog_id.get_type() != DialogType::SecretChat)) {
- LOG(ERROR) << "Incoming update tries to delete " << message_id;
- continue;
- }
+ CHECK(message_id.is_valid() || message_id.is_valid_scheduled());
- if (delete_message(d, message_id, true, &need_update_dialog_pos, "updates") != nullptr) {
- deleted_message_ids.push_back(message_id.get());
+ bool force_update = force_update_for_not_found_messages && !is_deleted_message(d, message_id);
+ auto message = delete_message(d, message_id, true, &need_update_dialog_pos, source);
+ if (message == nullptr) {
+ if (force_update) {
+ deleted_message_ids.push_back(message_id.get());
+ }
+ } else {
+ need_update_chat_has_scheduled_messages |= message->message_id.is_scheduled();
+ deleted_message_ids.push_back(message->message_id.get());
+ deleted_messages.push_back(std::move(message));
}
}
+ if (deleted_messages.size() >= MIN_DELETED_ASYNCHRONOUSLY_MESSAGES) {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), deleted_messages);
+ }
if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "delete_dialog_messages_from_updates");
+ send_update_chat_last_message(d, source);
}
- send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true, false);
-}
+ send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), true);
-bool MessagesManager::is_secret_message_content(int32 ttl, int32 content_type) {
- if (ttl <= 0 || ttl > 60) {
- return false;
- }
- switch (content_type) {
- case MessageAnimation::ID:
- case MessageAudio::ID:
- case MessagePhoto::ID:
- case MessageVideo::ID:
- case MessageVideoNote::ID:
- case MessageVoiceNote::ID:
- return true;
- case MessageContact::ID:
- case MessageDocument::ID:
- case MessageGame::ID:
- case MessageInvoice::ID:
- case MessageLiveLocation::ID:
- case MessageLocation::ID:
- case MessageSticker::ID:
- case MessageText::ID:
- case MessageUnsupported::ID:
- case MessageVenue::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- return false;
- default:
- UNREACHABLE();
- return false;
+ if (need_update_chat_has_scheduled_messages) {
+ send_update_chat_has_scheduled_messages(d, true);
}
}
-bool MessagesManager::is_service_message_content(int32 content_type) {
- switch (content_type) {
- case MessageAnimation::ID:
- case MessageAudio::ID:
- case MessageContact::ID:
- case MessageDocument::ID:
- case MessageGame::ID:
- case MessageInvoice::ID:
- case MessageLiveLocation::ID:
- case MessageLocation::ID:
- case MessagePhoto::ID:
- case MessageSticker::ID:
- case MessageText::ID:
- case MessageUnsupported::ID:
- case MessageVenue::ID:
- case MessageVideo::ID:
- case MessageVideoNote::ID:
- case MessageVoiceNote::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- return false;
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- return true;
- default:
- UNREACHABLE();
- return false;
+void MessagesManager::update_dialog_pinned_messages_from_updates(DialogId dialog_id,
+ const vector<MessageId> &message_ids, bool is_pin) {
+ Dialog *d = get_dialog_force(dialog_id, "update_dialog_pinned_messages_from_updates");
+ if (d == nullptr) {
+ LOG(INFO) << "Ignore updatePinnedMessages for unknown " << dialog_id;
+ return;
}
-}
-bool MessagesManager::can_have_message_content_caption(int32 content_type) {
- switch (content_type) {
- case MessageAnimation::ID:
- case MessageAudio::ID:
- case MessageDocument::ID:
- case MessagePhoto::ID:
- case MessageVideo::ID:
- case MessageVoiceNote::ID:
- return true;
- case MessageContact::ID:
- case MessageGame::ID:
- case MessageInvoice::ID:
- case MessageLiveLocation::ID:
- case MessageLocation::ID:
- case MessageSticker::ID:
- case MessageText::ID:
- case MessageUnsupported::ID:
- case MessageVenue::ID:
- case MessageVideoNote::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- return false;
- default:
- UNREACHABLE();
- return false;
+ for (auto message_id : message_ids) {
+ if (!message_id.is_valid() || (!message_id.is_server() && dialog_id.get_type() != DialogType::SecretChat)) {
+ LOG(ERROR) << "Incoming update tries to pin/unpin " << message_id << " in " << dialog_id;
+ continue;
+ }
+
+ Message *m = get_message_force(d, message_id, "update_dialog_pinned_messages_from_updates");
+ if (m != nullptr && update_message_is_pinned(d, m, is_pin, "update_dialog_pinned_messages_from_updates")) {
+ on_message_changed(d, m, true, "update_dialog_pinned_messages_from_updates");
+ }
}
}
-string MessagesManager::get_search_text(const Message *m) {
- if (m->is_content_secret) {
- return "";
- }
- switch (m->content->get_id()) {
- case MessageText::ID: {
- auto *text = static_cast<const MessageText *>(m->content.get());
- if (!text->web_page_id.is_valid()) {
- return text->text.text;
- }
- return PSTRING() << text->text.text << " "
- << td_->web_pages_manager_->get_web_page_search_text(text->web_page_id);
- }
- case MessageAnimation::ID: {
- auto animation = static_cast<const MessageAnimation *>(m->content.get());
- return PSTRING() << td_->animations_manager_->get_animation_search_text(animation->file_id) << " "
- << animation->caption.text;
- }
- case MessageAudio::ID: {
- auto audio = static_cast<const MessageAudio *>(m->content.get());
- return PSTRING() << td_->audios_manager_->get_audio_search_text(audio->file_id) << " " << audio->caption.text;
- }
- case MessageDocument::ID: {
- auto document = static_cast<const MessageDocument *>(m->content.get());
- return PSTRING() << td_->documents_manager_->get_document_search_text(document->file_id) << " "
- << document->caption.text;
- }
- case MessagePhoto::ID: {
- auto photo = static_cast<const MessagePhoto *>(m->content.get());
- return PSTRING() << photo->caption.text;
- }
- case MessageVideo::ID: {
- auto video = static_cast<const MessageVideo *>(m->content.get());
- return PSTRING() << td_->videos_manager_->get_video_search_text(video->file_id) << " " << video->caption.text;
- }
- case MessageContact::ID:
- case MessageGame::ID:
- case MessageInvoice::ID:
- case MessageLiveLocation::ID:
- case MessageLocation::ID:
- case MessageSticker::ID:
- case MessageUnsupported::ID:
- case MessageVenue::ID:
- case MessageVideoNote::ID:
- case MessageVoiceNote::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- return "";
- default:
- UNREACHABLE();
- return "";
+bool MessagesManager::update_message_is_pinned(Dialog *d, Message *m, bool is_pinned, const char *source) {
+ CHECK(m != nullptr);
+ CHECK(!m->message_id.is_scheduled());
+ if (m->is_pinned == is_pinned) {
+ return false;
}
+
+ LOG(INFO) << "Update message is_pinned of " << m->message_id << " in " << d->dialog_id << " to " << is_pinned
+ << " from " << source;
+ auto old_index_mask = get_message_index_mask(d->dialog_id, m);
+ m->is_pinned = is_pinned;
+ auto new_index_mask = get_message_index_mask(d->dialog_id, m);
+ update_message_count_by_index(d, -1, old_index_mask & ~new_index_mask);
+ update_message_count_by_index(d, +1, new_index_mask & ~old_index_mask);
+
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateMessageIsPinned>(d->dialog_id.get(), m->message_id.get(), is_pinned));
+ if (is_pinned) {
+ if (d->is_last_pinned_message_id_inited && m->message_id > d->last_pinned_message_id) {
+ set_dialog_last_pinned_message_id(d, m->message_id);
+ }
+ } else {
+ if (d->is_last_pinned_message_id_inited && m->message_id == d->last_pinned_message_id) {
+ if (d->message_count_by_index[message_search_filter_index(MessageSearchFilter::Pinned)] == 0) {
+ set_dialog_last_pinned_message_id(d, MessageId());
+ } else {
+ drop_dialog_last_pinned_message_id(d);
+ }
+ }
+ }
+ return true;
}
-bool MessagesManager::is_allowed_media_group_content(int32 content_type) {
- switch (content_type) {
- case MessagePhoto::ID:
- case MessageVideo::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- return true;
- case MessageAnimation::ID:
- case MessageAudio::ID:
- case MessageContact::ID:
- case MessageDocument::ID:
- case MessageGame::ID:
- case MessageInvoice::ID:
- case MessageLiveLocation::ID:
- case MessageLocation::ID:
- case MessageSticker::ID:
- case MessageText::ID:
- case MessageUnsupported::ID:
- case MessageVenue::ID:
- case MessageVideoNote::ID:
- case MessageVoiceNote::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- return false;
- default:
- UNREACHABLE();
- return false;
+string MessagesManager::get_message_search_text(const Message *m) const {
+ if (m->is_content_secret) {
+ return string();
}
+ return get_message_content_search_text(td_, m->content.get());
}
bool MessagesManager::can_forward_message(DialogId from_dialog_id, const Message *m) {
@@ -7581,6 +10908,9 @@ bool MessagesManager::can_forward_message(DialogId from_dialog_id, const Message
if (m->ttl > 0) {
return false;
}
+ if (m->message_id.is_scheduled()) {
+ return false;
+ }
switch (from_dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
@@ -7595,18 +10925,45 @@ bool MessagesManager::can_forward_message(DialogId from_dialog_id, const Message
return false;
}
- auto content_id = m->content->get_id();
- return !is_service_message_content(content_id) && content_id != MessageUnsupported::ID &&
- content_id != MessageExpiredPhoto::ID && content_id != MessageExpiredVideo::ID;
+ return can_forward_message_content(m->content.get());
+}
+
+bool MessagesManager::can_save_message(DialogId dialog_id, const Message *m) const {
+ if (m == nullptr || m->noforwards || m->is_content_secret) {
+ return false;
+ }
+ return !get_dialog_has_protected_content(dialog_id);
+}
+
+bool MessagesManager::can_get_message_statistics(FullMessageId full_message_id) {
+ return can_get_message_statistics(full_message_id.get_dialog_id(),
+ get_message_force(full_message_id, "can_get_message_statistics"));
+}
+
+bool MessagesManager::can_get_message_statistics(DialogId dialog_id, const Message *m) const {
+ if (td_->auth_manager_->is_bot()) {
+ return false;
+ }
+ if (m == nullptr || m->message_id.is_scheduled() || !m->message_id.is_server() || m->view_count == 0 ||
+ m->had_forward_info || (m->forward_info != nullptr && m->forward_info->message_id.is_valid())) {
+ return false;
+ }
+ return td_->contacts_manager_->can_get_channel_message_statistics(dialog_id);
}
-bool MessagesManager::can_delete_channel_message(DialogParticipantStatus status, const Message *m, bool is_bot) {
+bool MessagesManager::can_delete_channel_message(const DialogParticipantStatus &status, const Message *m, bool is_bot) {
if (m == nullptr) {
return true;
}
if (m->message_id.is_local() || m->message_id.is_yet_unsent()) {
return true;
}
+ if (m->message_id.is_scheduled()) {
+ if (m->is_channel_post) {
+ return status.can_post_messages();
+ }
+ return true;
+ }
if (is_bot && G()->unix_time_cached() >= m->date + 2 * 86400) {
// bots can't delete messages older than 2 days
@@ -7617,8 +10974,9 @@ bool MessagesManager::can_delete_channel_message(DialogParticipantStatus status,
if (m->message_id.get_server_message_id().get() == 1) {
return false;
}
- auto content_id = m->content->get_id();
- if (content_id == MessageChannelMigrateFrom::ID || content_id == MessageChannelCreate::ID) {
+ auto content_type = m->content->get_type();
+ if (content_type == MessageContentType::ChannelMigrateFrom || content_type == MessageContentType::ChannelCreate ||
+ content_type == MessageContentType::TopicCreate) {
return false;
}
@@ -7630,21 +10988,49 @@ bool MessagesManager::can_delete_channel_message(DialogParticipantStatus status,
return false;
}
- if (m->is_channel_post || is_service_message_content(content_id)) {
+ if (m->is_channel_post || is_service_message_content(content_type)) {
return status.can_post_messages();
}
return true;
}
+bool MessagesManager::can_delete_message(DialogId dialog_id, const Message *m) const {
+ if (m == nullptr) {
+ return true;
+ }
+ if (m->message_id.is_local() || m->message_id.is_yet_unsent()) {
+ return true;
+ }
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return true;
+ case DialogType::Chat:
+ return true;
+ case DialogType::Channel: {
+ auto dialog_status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
+ return can_delete_channel_message(dialog_status, m, td_->auth_manager_->is_bot());
+ }
+ case DialogType::SecretChat:
+ return true;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
bool MessagesManager::can_revoke_message(DialogId dialog_id, const Message *m) const {
if (m == nullptr) {
- return false;
+ return true;
}
if (m->message_id.is_local()) {
return false;
}
- if (dialog_id == DialogId(td_->contacts_manager_->get_my_id("can_revoke_message"))) {
+ if (dialog_id == get_my_dialog_id()) {
+ return false;
+ }
+ if (m->message_id.is_scheduled()) {
return false;
}
if (m->message_id.is_yet_unsent()) {
@@ -7652,39 +11038,49 @@ bool MessagesManager::can_revoke_message(DialogId dialog_id, const Message *m) c
}
CHECK(m->message_id.is_server());
- bool is_appointed_administrator = false;
- bool can_revoke_incoming = false;
- const int32 DEFAULT_REVOKE_TIME_LIMIT = 2 * 86400;
- int32 revoke_time_limit = G()->shared_config().get_option_integer("revoke_time_limit", DEFAULT_REVOKE_TIME_LIMIT);
+ const int32 DEFAULT_REVOKE_TIME_LIMIT = td_->auth_manager_->is_bot() ? 2 * 86400 : std::numeric_limits<int32>::max();
+ auto content_type = m->content->get_type();
switch (dialog_id.get_type()) {
- case DialogType::User:
- can_revoke_incoming = G()->shared_config().get_option_boolean("revoke_pm_inbox");
- revoke_time_limit = G()->shared_config().get_option_integer("revoke_pm_time_limit", DEFAULT_REVOKE_TIME_LIMIT);
- break;
- case DialogType::Chat:
- is_appointed_administrator = td_->contacts_manager_->is_appointed_chat_administrator(dialog_id.get_chat_id());
- break;
+ case DialogType::User: {
+ bool can_revoke_incoming = td_->option_manager_->get_option_boolean("revoke_pm_inbox", true);
+ int64 revoke_time_limit =
+ td_->option_manager_->get_option_integer("revoke_pm_time_limit", DEFAULT_REVOKE_TIME_LIMIT);
+
+ if (G()->unix_time_cached() - m->date < 86400 && content_type == MessageContentType::Dice) {
+ return false;
+ }
+ return ((m->is_outgoing && !is_service_message_content(content_type)) ||
+ (can_revoke_incoming && content_type != MessageContentType::ScreenshotTaken)) &&
+ G()->unix_time_cached() - m->date <= revoke_time_limit;
+ }
+ case DialogType::Chat: {
+ bool is_appointed_administrator =
+ td_->contacts_manager_->is_appointed_chat_administrator(dialog_id.get_chat_id());
+ int64 revoke_time_limit =
+ td_->option_manager_->get_option_integer("revoke_time_limit", DEFAULT_REVOKE_TIME_LIMIT);
+
+ return ((m->is_outgoing && !is_service_message_content(content_type)) || is_appointed_administrator) &&
+ G()->unix_time_cached() - m->date <= revoke_time_limit;
+ }
case DialogType::Channel:
return true; // any server message that can be deleted will be deleted for all participants
case DialogType::SecretChat:
- return !is_service_message_content(
- m->content->get_id()); // all non-service messages will be deleted for everyone
+ // all non-service messages will be deleted for everyone if secret chat is active
+ return td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Active &&
+ !is_service_message_content(content_type);
case DialogType::None:
default:
UNREACHABLE();
return false;
}
-
- return (((m->is_outgoing || can_revoke_incoming) && !is_service_message_content(m->content->get_id())) ||
- is_appointed_administrator) &&
- G()->unix_time_cached() - m->date <= revoke_time_limit;
}
void MessagesManager::delete_messages(DialogId dialog_id, const vector<MessageId> &input_message_ids, bool revoke,
Promise<Unit> &&promise) {
- Dialog *d = get_dialog_force(dialog_id);
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ Dialog *d = get_dialog_force(dialog_id, "delete_messages");
if (d == nullptr) {
- return promise.set_error(Status::Error(6, "Chat is not found"));
+ return promise.set_error(Status::Error(400, "Chat is not found"));
}
if (input_message_ids.empty()) {
@@ -7697,65 +11093,94 @@ void MessagesManager::delete_messages(DialogId dialog_id, const vector<MessageId
vector<MessageId> message_ids;
message_ids.reserve(input_message_ids.size());
vector<MessageId> deleted_server_message_ids;
+
+ vector<MessageId> deleted_scheduled_server_message_ids;
for (auto message_id : input_message_ids) {
- if (!message_id.is_valid()) {
- return promise.set_error(Status::Error(6, "Invalid message identifier"));
+ if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
+ return promise.set_error(Status::Error(400, "Invalid message identifier"));
}
+
message_id = get_persistent_message_id(d, message_id);
message_ids.push_back(message_id);
- if (get_message_force(d, message_id) != nullptr && (message_id.is_server() || is_secret)) {
- deleted_server_message_ids.push_back(message_id);
+ auto m = get_message_force(d, message_id, "delete_messages");
+ if (m != nullptr) {
+ if (m->message_id.is_scheduled()) {
+ if (m->message_id.is_scheduled_server()) {
+ deleted_scheduled_server_message_ids.push_back(m->message_id);
+ }
+ } else {
+ if (m->message_id.is_server() || is_secret) {
+ deleted_server_message_ids.push_back(m->message_id);
+ }
+ }
}
}
bool is_bot = td_->auth_manager_->is_bot();
- switch (dialog_id.get_type()) {
- case DialogType::User:
- case DialogType::Chat:
- if (is_bot) {
- for (auto message_id : message_ids) {
- if (message_id.is_server() && !can_revoke_message(dialog_id, get_message(d, message_id))) {
- return promise.set_error(Status::Error(6, "Message can't be deleted"));
- }
- }
- }
- break;
- case DialogType::Channel: {
- auto dialog_status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
- for (auto message_id : message_ids) {
- if (!can_delete_channel_message(dialog_status, get_message(d, message_id), is_bot)) {
- return promise.set_error(Status::Error(6, "Message can't be deleted"));
- }
- }
- break;
+ for (auto message_id : message_ids) {
+ auto m = get_message(d, message_id);
+ if (!can_delete_message(dialog_id, m)) {
+ return promise.set_error(Status::Error(400, "Message can't be deleted"));
+ }
+ if (is_bot && !message_id.is_scheduled() && message_id.is_server() && !can_revoke_message(dialog_id, m)) {
+ return promise.set_error(Status::Error(400, "Message can't be deleted for everyone"));
}
- case DialogType::SecretChat:
- break;
- case DialogType::None:
- default:
- UNREACHABLE();
}
- delete_messages_from_server(dialog_id, std::move(deleted_server_message_ids), revoke, 0, std::move(promise));
+ MultiPromiseActorSafe mpas{"DeleteMessagesMultiPromiseActor"};
+ mpas.add_promise(std::move(promise));
- bool need_update_dialog_pos = false;
- vector<int64> deleted_message_ids;
- for (auto message_id : message_ids) {
- auto m = delete_message(d, message_id, true, &need_update_dialog_pos, DELETE_MESSAGE_USER_REQUEST_SOURCE);
- if (m == nullptr) {
- LOG(INFO) << "Can't delete " << message_id << " because it is not found";
+ auto lock = mpas.get_promise();
+ delete_messages_on_server(dialog_id, std::move(deleted_server_message_ids), revoke, 0, mpas.get_promise());
+ delete_scheduled_messages_on_server(dialog_id, std::move(deleted_scheduled_server_message_ids), 0,
+ mpas.get_promise());
+ lock.set_value(Unit());
+
+ delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE);
+}
+
+void MessagesManager::erase_delete_messages_log_event(uint64 log_event_id) {
+ if (!G()->close_flag()) {
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ }
+}
+
+void MessagesManager::delete_sent_message_on_server(DialogId dialog_id, MessageId message_id,
+ MessageId old_message_id) {
+ // this would be a no-op, because replies have already been removed in cancel_send_message_query
+ // update_reply_to_message_id(dialog_id, old_message_id, message_id, false, "delete_sent_message_on_server");
+
+ // being sent message was deleted by the user or is in an inaccessible channel
+ // don't need to send an update to the user, because the message has already been deleted
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ LOG(INFO) << "Ignore sent " << message_id << " in inaccessible " << dialog_id;
+ return;
+ }
+
+ LOG(INFO) << "Delete already deleted sent " << message_id << " in " << dialog_id << " from server";
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ if (get_message_force(d, message_id, "delete_sent_message_on_server") != nullptr) {
+ delete_messages(dialog_id, {message_id}, true, Auto());
+ } else {
+ if (message_id.is_valid()) {
+ CHECK(message_id.is_server());
+ delete_messages_on_server(dialog_id, {message_id}, true, 0, Auto());
} else {
- deleted_message_ids.push_back(m->message_id.get());
+ CHECK(message_id.is_scheduled_server());
+ delete_scheduled_messages_on_server(dialog_id, {message_id}, 0, Auto());
}
- }
- if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "delete_messages");
+ bool need_update_dialog_pos = false;
+ auto m = delete_message(d, message_id, true, &need_update_dialog_pos, "delete_sent_message_on_server");
+ CHECK(m == nullptr);
+ if (need_update_dialog_pos) { // last_clear_history_message_id might be removed
+ update_dialog_pos(d, "delete_sent_message_on_server");
+ }
}
- send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true, false);
}
-class MessagesManager::DeleteMessagesFromServerLogEvent {
+class MessagesManager::DeleteMessagesOnServerLogEvent {
public:
DialogId dialog_id_;
vector<MessageId> message_ids_;
@@ -7782,61 +11207,66 @@ class MessagesManager::DeleteMessagesFromServerLogEvent {
}
};
-void MessagesManager::delete_messages_from_server(DialogId dialog_id, vector<MessageId> message_ids, bool revoke,
- uint64 logevent_id, Promise<Unit> &&promise) {
+uint64 MessagesManager::save_delete_messages_on_server_log_event(DialogId dialog_id,
+ const vector<MessageId> &message_ids, bool revoke) {
+ DeleteMessagesOnServerLogEvent log_event{dialog_id, message_ids, revoke};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteMessagesOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::delete_messages_on_server(DialogId dialog_id, vector<MessageId> message_ids, bool revoke,
+ uint64 log_event_id, Promise<Unit> &&promise) {
if (message_ids.empty()) {
- promise.set_value(Unit());
- return;
+ return promise.set_value(Unit());
}
LOG(INFO) << (revoke ? "Revoke " : "Delete ") << format::as_array(message_ids) << " in " << dialog_id
<< " from server";
- if (logevent_id == 0 && G()->parameters().use_message_db) {
- DeleteMessagesFromServerLogEvent logevent;
- logevent.dialog_id_ = dialog_id;
- logevent.message_ids_ = message_ids;
- logevent.revoke_ = revoke;
-
- auto storer = LogEventStorerImpl<DeleteMessagesFromServerLogEvent>(logevent);
- logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteMessagesFromServer, storer);
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_delete_messages_on_server_log_event(dialog_id, message_ids, revoke);
}
- if (logevent_id != 0) {
- auto new_promise = PromiseCreator::lambda([logevent_id, promise = std::move(promise)](Result<Unit> result) mutable {
- if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
- }
-
- promise.set_result(std::move(result));
- });
- promise = std::move(new_promise);
+ MultiPromiseActorSafe mpas{"DeleteMessagesOnServerMultiPromiseActor"};
+ mpas.add_promise(std::move(promise));
+ if (log_event_id != 0) {
+ mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), log_event_id](Unit) {
+ send_closure(actor_id, &MessagesManager::erase_delete_messages_log_event, log_event_id);
+ }));
}
-
- switch (dialog_id.get_type()) {
+ auto lock = mpas.get_promise();
+ auto dialog_type = dialog_id.get_type();
+ switch (dialog_type) {
case DialogType::User:
case DialogType::Chat:
- td_->create_handler<DeleteMessagesQuery>(std::move(promise))->send(std::move(message_ids), revoke);
- break;
- case DialogType::Channel:
- td_->create_handler<DeleteChannelMessagesQuery>(std::move(promise))
- ->send(dialog_id.get_channel_id(), std::move(message_ids));
+ case DialogType::Channel: {
+ auto server_message_ids = MessagesManager::get_server_message_ids(message_ids);
+ const size_t MAX_SLICE_SIZE = 100; // server side limit
+ for (size_t i = 0; i < server_message_ids.size(); i += MAX_SLICE_SIZE) {
+ auto end_i = i + MAX_SLICE_SIZE;
+ auto end = end_i < server_message_ids.size() ? server_message_ids.begin() + end_i : server_message_ids.end();
+ if (dialog_type != DialogType::Channel) {
+ td_->create_handler<DeleteMessagesQuery>(mpas.get_promise())
+ ->send(dialog_id, {server_message_ids.begin() + i, end}, revoke);
+ } else {
+ td_->create_handler<DeleteChannelMessagesQuery>(mpas.get_promise())
+ ->send(dialog_id.get_channel_id(), {server_message_ids.begin() + i, end});
+ }
+ }
break;
+ }
case DialogType::SecretChat: {
vector<int64> random_ids;
- auto d = get_dialog_force(dialog_id);
+ auto d = get_dialog_force(dialog_id, "delete_messages_on_server");
CHECK(d != nullptr);
for (auto &message_id : message_ids) {
- auto *message = get_message(d, message_id);
- if (message != nullptr) {
- random_ids.push_back(message->random_id);
+ auto *m = get_message(d, message_id);
+ if (m != nullptr) {
+ random_ids.push_back(m->random_id);
}
}
if (!random_ids.empty()) {
send_closure(G()->secret_chats_manager(), &SecretChatsManager::delete_messages, dialog_id.get_secret_chat_id(),
- std::move(random_ids), std::move(promise));
- } else {
- promise.set_value(Unit());
+ std::move(random_ids), mpas.get_promise());
}
break;
}
@@ -7844,56 +11274,190 @@ void MessagesManager::delete_messages_from_server(DialogId dialog_id, vector<Mes
default:
UNREACHABLE();
}
+ lock.set_value(Unit());
}
-void MessagesManager::delete_dialog_history(DialogId dialog_id, bool remove_from_dialog_list, Promise<Unit> &&promise) {
- LOG(INFO) << "Receive deleteChatHistory request to delete all messages in " << dialog_id
- << ", remove_from_chat_list is " << remove_from_dialog_list;
+class MessagesManager::DeleteScheduledMessagesOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+ vector<MessageId> message_ids_;
- Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
+ td::store(message_ids_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(dialog_id_, parser);
+ td::parse(message_ids_, parser);
+ }
+};
+
+uint64 MessagesManager::save_delete_scheduled_messages_on_server_log_event(DialogId dialog_id,
+ const vector<MessageId> &message_ids) {
+ DeleteScheduledMessagesOnServerLogEvent log_event{dialog_id, message_ids};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteScheduledMessagesOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::delete_scheduled_messages_on_server(DialogId dialog_id, vector<MessageId> message_ids,
+ uint64 log_event_id, Promise<Unit> &&promise) {
+ if (message_ids.empty()) {
+ return promise.set_value(Unit());
}
+ LOG(INFO) << "Delete " << format::as_array(message_ids) << " in " << dialog_id << " from server";
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_delete_scheduled_messages_on_server_log_event(dialog_id, message_ids);
+ }
+
+ auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
+ promise = std::move(new_promise); // to prevent self-move
+
+ td_->create_handler<DeleteScheduledMessagesQuery>(std::move(promise))->send(dialog_id, std::move(message_ids));
+}
+
+void MessagesManager::on_failed_message_deletion(DialogId dialog_id, const vector<int32> &server_message_ids) {
+ if (G()->close_flag()) {
+ return;
+ }
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ vector<FullMessageId> full_message_ids;
+ for (auto &server_message_id : server_message_ids) {
+ auto message_id = MessageId(ServerMessageId(server_message_id));
+ d->deleted_message_ids.erase(message_id);
+ full_message_ids.emplace_back(dialog_id, message_id);
+ }
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- return promise.set_error(Status::Error(3, "Chat info not found"));
+ return;
}
+ get_messages_from_server(std::move(full_message_ids), Promise<Unit>(), "on_failed_message_deletion");
+}
- auto dialog_type = dialog_id.get_type();
- bool is_secret = false;
- switch (dialog_type) {
+void MessagesManager::on_failed_scheduled_message_deletion(DialogId dialog_id, const vector<MessageId> &message_ids) {
+ if (G()->close_flag()) {
+ return;
+ }
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ for (auto &message_id : message_ids) {
+ d->deleted_scheduled_server_message_ids.erase(message_id.get_scheduled_server_message_id());
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return;
+ }
+ load_dialog_scheduled_messages(dialog_id, false, 0, Promise<Unit>());
+}
+
+MessagesManager::CanDeleteDialog MessagesManager::can_delete_dialog(const Dialog *d) const {
+ if (is_dialog_sponsored(d)) {
+ auto chat_source = sponsored_dialog_source_.get_chat_source_object();
+ if (chat_source != nullptr) {
+ switch (chat_source->get_id()) {
+ case td_api::chatSourcePublicServiceAnnouncement::ID:
+ // can delete for self (but only while removing from dialog list)
+ return {true, false};
+ default:
+ return {false, false};
+ }
+ }
+ }
+ if (td_->auth_manager_->is_bot() || !have_input_peer(d->dialog_id, AccessRights::Read)) {
+ return {false, false};
+ }
+
+ switch (d->dialog_id.get_type()) {
case DialogType::User:
+ if (d->dialog_id == get_my_dialog_id() || td_->contacts_manager_->is_user_deleted(d->dialog_id.get_user_id()) ||
+ td_->contacts_manager_->is_user_bot(d->dialog_id.get_user_id())) {
+ return {true, false};
+ }
+ return {true, td_->option_manager_->get_option_boolean("revoke_pm_inbox", true)};
case DialogType::Chat:
- // ok
- break;
+ // chats can be deleted only for self and can be deleted for everyone by their creator
+ return {true, td_->contacts_manager_->get_chat_status(d->dialog_id.get_chat_id()).is_creator()};
case DialogType::Channel:
- if (is_broadcast_channel(dialog_id)) {
- return promise.set_error(Status::Error(3, "Can't delete chat history in a channel"));
- }
- if (!get_dialog_username(dialog_id).empty()) {
- return promise.set_error(Status::Error(3, "Can't delete chat history in a public supergroup"));
- }
- break;
+ // private supergroups can be deleted for self
+ return {!is_broadcast_channel(d->dialog_id) &&
+ !td_->contacts_manager_->is_channel_public(d->dialog_id.get_channel_id()),
+ td_->contacts_manager_->get_channel_can_be_deleted(d->dialog_id.get_channel_id())};
case DialogType::SecretChat:
- is_secret = true;
- // ok
- break;
+ if (td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id()) == SecretChatState::Closed) {
+ // in a closed secret chats there is no way to delete messages for both users
+ return {true, false};
+ }
+ // active secret chats can be deleted only for both users
+ return {false, true};
case DialogType::None:
default:
UNREACHABLE();
- break;
+ return {false, false};
+ }
+}
+
+void MessagesManager::delete_dialog_history(DialogId dialog_id, bool remove_from_dialog_list, bool revoke,
+ Promise<Unit> &&promise) {
+ LOG(INFO) << "Receive deleteChatHistory request to delete all messages in " << dialog_id
+ << ", remove_from_chat_list is " << remove_from_dialog_list << ", revoke is " << revoke;
+
+ Dialog *d = get_dialog_force(dialog_id, "delete_dialog_history");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Chat info not found"));
+ }
+
+ if (is_dialog_sponsored(d)) {
+ auto chat_source = sponsored_dialog_source_.get_chat_source_object();
+ if (chat_source == nullptr || chat_source->get_id() != td_api::chatSourcePublicServiceAnnouncement::ID) {
+ return promise.set_error(Status::Error(400, "Can't delete the chat"));
+ }
+ if (!remove_from_dialog_list) {
+ return promise.set_error(Status::Error(400, "Can't delete chat history without removing the chat"));
+ }
+
+ removed_sponsored_dialog_id_ = dialog_id;
+ remove_sponsored_dialog();
+
+ td_->create_handler<HidePromoDataQuery>()->send(dialog_id);
+ promise.set_value(Unit());
+ return;
+ }
+
+ auto can_delete = can_delete_dialog(d);
+ if (revoke) {
+ if (!can_delete.for_all_users_) {
+ if (!can_delete.for_self_) {
+ return promise.set_error(Status::Error(400, "Chat history can't be deleted"));
+ }
+
+ LOG(INFO) << "Can't delete history of " << dialog_id << " for everyone; delete it only for self";
+ revoke = false;
+ }
+ } else {
+ if (!can_delete.for_self_) {
+ return promise.set_error(
+ Status::Error(400, PSLICE() << "Can't delete history of " << dialog_id << " only for self"));
+ }
}
auto last_new_message_id = d->last_new_message_id;
- if (!is_secret && last_new_message_id == MessageId()) {
- // TODO get dialog from the server and delete history from last message id
+ if (dialog_id.get_type() != DialogType::SecretChat && last_new_message_id == MessageId()) {
+ // TODO get dialog from the server and delete history from last message identifier
}
bool allow_error = d->messages == nullptr;
+ auto old_order = d->order;
delete_all_dialog_messages(d, remove_from_dialog_list, true);
- if (last_new_message_id.is_valid() && last_new_message_id == d->max_unavailable_message_id) {
+ if (last_new_message_id.is_valid() && last_new_message_id == d->max_unavailable_message_id && !revoke &&
+ !(old_order != DEFAULT_ORDER && remove_from_dialog_list)) {
// history has already been cleared, nothing to do
promise.set_value(Unit());
return;
@@ -7901,20 +11465,22 @@ void MessagesManager::delete_dialog_history(DialogId dialog_id, bool remove_from
set_dialog_max_unavailable_message_id(dialog_id, last_new_message_id, false, "delete_dialog_history");
- delete_dialog_history_from_server(dialog_id, last_new_message_id, remove_from_dialog_list, allow_error, 0,
- std::move(promise));
+ delete_dialog_history_on_server(dialog_id, last_new_message_id, remove_from_dialog_list, revoke, allow_error, 0,
+ std::move(promise));
}
-class MessagesManager::DeleteDialogHistoryFromServerLogEvent {
+class MessagesManager::DeleteDialogHistoryOnServerLogEvent {
public:
DialogId dialog_id_;
MessageId max_message_id_;
bool remove_from_dialog_list_;
+ bool revoke_;
template <class StorerT>
void store(StorerT &storer) const {
BEGIN_STORE_FLAGS();
STORE_FLAG(remove_from_dialog_list_);
+ STORE_FLAG(revoke_);
END_STORE_FLAGS();
td::store(dialog_id_, storer);
@@ -7925,6 +11491,7 @@ class MessagesManager::DeleteDialogHistoryFromServerLogEvent {
void parse(ParserT &parser) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(remove_from_dialog_list_);
+ PARSE_FLAG(revoke_);
END_PARSE_FLAGS();
td::parse(dialog_id_, parser);
@@ -7932,48 +11499,44 @@ class MessagesManager::DeleteDialogHistoryFromServerLogEvent {
}
};
-void MessagesManager::delete_dialog_history_from_server(DialogId dialog_id, MessageId max_message_id,
- bool remove_from_dialog_list, bool allow_error,
- uint64 logevent_id, Promise<Unit> &&promise) {
- LOG(INFO) << "Delete history in " << dialog_id << " up to " << max_message_id << " from server";
+uint64 MessagesManager::save_delete_dialog_history_on_server_log_event(DialogId dialog_id, MessageId max_message_id,
+ bool remove_from_dialog_list, bool revoke) {
+ DeleteDialogHistoryOnServerLogEvent log_event{dialog_id, max_message_id, remove_from_dialog_list, revoke};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteDialogHistoryOnServer,
+ get_log_event_storer(log_event));
+}
- if (logevent_id == 0 && G()->parameters().use_message_db) {
- DeleteDialogHistoryFromServerLogEvent logevent;
- logevent.dialog_id_ = dialog_id;
- logevent.max_message_id_ = max_message_id;
- logevent.remove_from_dialog_list_ = remove_from_dialog_list;
+void MessagesManager::delete_dialog_history_on_server(DialogId dialog_id, MessageId max_message_id,
+ bool remove_from_dialog_list, bool revoke, bool allow_error,
+ uint64 log_event_id, Promise<Unit> &&promise) {
+ LOG(INFO) << "Delete history in " << dialog_id << " up to " << max_message_id << " from server";
- auto storer = LogEventStorerImpl<DeleteDialogHistoryFromServerLogEvent>(logevent);
- logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteDialogHistoryFromServer, storer);
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id =
+ save_delete_dialog_history_on_server_log_event(dialog_id, max_message_id, remove_from_dialog_list, revoke);
}
- if (logevent_id != 0) {
- auto new_promise = PromiseCreator::lambda([logevent_id, promise = std::move(promise)](Result<Unit> result) mutable {
- if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
- }
-
- promise.set_result(std::move(result));
- });
- promise = std::move(new_promise);
- }
+ auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
+ promise = std::move(new_promise); // to prevent self-move
switch (dialog_id.get_type()) {
case DialogType::User:
- case DialogType::Chat:
- td_->create_handler<DeleteHistoryQuery>(std::move(promise))
- ->send(dialog_id, max_message_id, remove_from_dialog_list);
+ case DialogType::Chat: {
+ AffectedHistoryQuery query = [td = td_, max_message_id, remove_from_dialog_list, revoke](
+ DialogId dialog_id, Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<DeleteHistoryQuery>(std::move(query_promise))
+ ->send(dialog_id, max_message_id, remove_from_dialog_list, revoke);
+ };
+ run_affected_history_query_until_complete(dialog_id, std::move(query), false, std::move(promise));
break;
+ }
case DialogType::Channel:
td_->create_handler<DeleteChannelHistoryQuery>(std::move(promise))
- ->send(dialog_id.get_channel_id(), max_message_id, allow_error);
+ ->send(dialog_id.get_channel_id(), max_message_id, allow_error, revoke);
break;
case DialogType::SecretChat:
- // TODO: use promise
send_closure(G()->secret_chats_manager(), &SecretChatsManager::delete_all_messages,
- dialog_id.get_secret_chat_id(), Promise<>());
- promise.set_value(Unit());
+ dialog_id.get_secret_chat_id(), std::move(promise));
break;
case DialogType::None:
default:
@@ -7982,88 +11545,197 @@ void MessagesManager::delete_dialog_history_from_server(DialogId dialog_id, Mess
}
}
-void MessagesManager::find_messages_from_user(const unique_ptr<Message> &m, UserId user_id,
- vector<MessageId> &message_ids) {
+void MessagesManager::delete_topic_history(DialogId dialog_id, MessageId top_thread_message_id,
+ Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "delete_dialog_history");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Chat info not found"));
+ }
+
+ // auto old_order = d->order;
+ // delete_all_dialog_topic_messages(d, top_thread_message_id);
+
+ delete_topic_history_on_server(dialog_id, top_thread_message_id, 0, std::move(promise));
+}
+
+class MessagesManager::DeleteTopicHistoryOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+ MessageId top_thread_message_id_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ END_STORE_FLAGS();
+ td::store(dialog_id_, storer);
+ td::store(top_thread_message_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ END_PARSE_FLAGS();
+ td::parse(dialog_id_, parser);
+ td::parse(top_thread_message_id_, parser);
+ }
+};
+
+uint64 MessagesManager::save_delete_topic_history_on_server_log_event(DialogId dialog_id,
+ MessageId top_thread_message_id) {
+ DeleteTopicHistoryOnServerLogEvent log_event{dialog_id, top_thread_message_id};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteTopicHistoryOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::delete_topic_history_on_server(DialogId dialog_id, MessageId top_thread_message_id,
+ uint64 log_event_id, Promise<Unit> &&promise) {
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_delete_topic_history_on_server_log_event(dialog_id, top_thread_message_id);
+ }
+
+ auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
+ promise = std::move(new_promise); // to prevent self-move
+
+ AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id,
+ Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<DeleteTopicHistoryQuery>(std::move(query_promise))->send(dialog_id, top_thread_message_id);
+ };
+ run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise));
+}
+
+void MessagesManager::delete_all_call_messages(bool revoke, Promise<Unit> &&promise) {
+ delete_all_call_messages_on_server(revoke, 0, std::move(promise));
+}
+
+class MessagesManager::DeleteAllCallMessagesOnServerLogEvent {
+ public:
+ bool revoke_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(revoke_);
+ END_STORE_FLAGS();
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(revoke_);
+ END_PARSE_FLAGS();
+ }
+};
+
+uint64 MessagesManager::save_delete_all_call_messages_on_server_log_event(bool revoke) {
+ DeleteAllCallMessagesOnServerLogEvent log_event{revoke};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteAllCallMessagesOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::delete_all_call_messages_on_server(bool revoke, uint64 log_event_id, Promise<Unit> &&promise) {
+ if (log_event_id == 0) {
+ log_event_id = save_delete_all_call_messages_on_server_log_event(revoke);
+ }
+
+ AffectedHistoryQuery query = [td = td_, revoke](DialogId /*dialog_id*/, Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<DeletePhoneCallHistoryQuery>(std::move(query_promise))->send(revoke);
+ };
+ run_affected_history_query_until_complete(DialogId(), std::move(query), false,
+ get_erase_log_event_promise(log_event_id, std::move(promise)));
+}
+
+void MessagesManager::find_messages(const Message *m, vector<MessageId> &message_ids,
+ const std::function<bool(const Message *)> &condition) {
if (m == nullptr) {
return;
}
- find_messages_from_user(m->left, user_id, message_ids);
+ find_messages(m->left.get(), message_ids, condition);
- if (m->sender_user_id == user_id) {
+ if (condition(m)) {
message_ids.push_back(m->message_id);
}
- find_messages_from_user(m->right, user_id, message_ids);
+ find_messages(m->right.get(), message_ids, condition);
}
-void MessagesManager::find_unread_mentions(const unique_ptr<Message> &m, vector<MessageId> &message_ids) {
+void MessagesManager::find_old_messages(const Message *m, MessageId max_message_id, vector<MessageId> &message_ids) {
if (m == nullptr) {
return;
}
- find_unread_mentions(m->left, message_ids);
+ find_old_messages(m->left.get(), max_message_id, message_ids);
- if (m->contains_unread_mention) {
+ if (m->message_id <= max_message_id) {
message_ids.push_back(m->message_id);
- }
- find_unread_mentions(m->right, message_ids);
+ find_old_messages(m->right.get(), max_message_id, message_ids);
+ }
}
-void MessagesManager::find_old_messages(const unique_ptr<Message> &m, MessageId max_message_id,
- vector<MessageId> &message_ids) {
+void MessagesManager::find_newer_messages(const Message *m, MessageId min_message_id, vector<MessageId> &message_ids) {
if (m == nullptr) {
return;
}
- find_old_messages(m->left, max_message_id, message_ids);
+ if (m->message_id > min_message_id) {
+ find_newer_messages(m->left.get(), min_message_id, message_ids);
- if (m->message_id.get() <= max_message_id.get()) {
message_ids.push_back(m->message_id);
-
- find_old_messages(m->right, max_message_id, message_ids);
}
+
+ find_newer_messages(m->right.get(), min_message_id, message_ids);
}
-void MessagesManager::find_unloadable_messages(const Dialog *d, int32 unload_before_date, const unique_ptr<Message> &m,
- vector<MessageId> &message_ids, int32 &left_to_unload) const {
+void MessagesManager::find_unloadable_messages(const Dialog *d, int32 unload_before_date, const Message *m,
+ vector<MessageId> &message_ids,
+ bool &has_left_to_unload_messages) const {
if (m == nullptr) {
return;
}
+ if (message_ids.size() >= MAX_UNLOADED_MESSAGES) {
+ has_left_to_unload_messages = true;
+ return;
+ }
- find_unloadable_messages(d, unload_before_date, m->left, message_ids, left_to_unload);
+ find_unloadable_messages(d, unload_before_date, m->left.get(), message_ids, has_left_to_unload_messages);
- if (can_unload_message(d, m.get())) {
+ if (can_unload_message(d, m)) {
if (m->last_access_date <= unload_before_date) {
message_ids.push_back(m->message_id);
} else {
- left_to_unload++;
+ has_left_to_unload_messages = true;
}
}
- find_unloadable_messages(d, unload_before_date, m->right, message_ids, left_to_unload);
+ if (has_left_to_unload_messages && m->date > unload_before_date) {
+ // we aren't interested in unloading too new messages
+ return;
+ }
+
+ find_unloadable_messages(d, unload_before_date, m->right.get(), message_ids, has_left_to_unload_messages);
}
-void MessagesManager::delete_dialog_messages_from_user(DialogId dialog_id, UserId user_id, Promise<Unit> &&promise) {
+void MessagesManager::delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id,
+ Promise<Unit> &&promise) {
bool is_bot = td_->auth_manager_->is_bot();
- if (is_bot) {
- return promise.set_error(Status::Error(3, "Method is not available for bots"));
- }
+ CHECK(!is_bot);
- LOG(INFO) << "Receive deleteChatMessagesFromUser request to delete all messages in " << dialog_id << " from the user "
- << user_id;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages_by_sender");
if (d == nullptr) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Write)) {
- return promise.set_error(Status::Error(3, "Not enough rights"));
+ return promise.set_error(Status::Error(400, "Not enough rights"));
}
- if (!td_->contacts_manager_->have_input_user(user_id)) {
- return promise.set_error(Status::Error(3, "User not found"));
+ if (!have_input_peer(sender_dialog_id, AccessRights::Know)) {
+ return promise.set_error(Status::Error(400, "Message sender not found"));
}
ChannelId channel_id;
@@ -8072,16 +11744,16 @@ void MessagesManager::delete_dialog_messages_from_user(DialogId dialog_id, UserI
case DialogType::User:
case DialogType::Chat:
case DialogType::SecretChat:
- return promise.set_error(Status::Error(3, "All messages from a user can be deleted only in supergroup chats"));
+ return promise.set_error(
+ Status::Error(400, "All messages from a sender can be deleted only in supergroup chats"));
case DialogType::Channel: {
channel_id = dialog_id.get_channel_id();
- auto channel_type = td_->contacts_manager_->get_channel_type(channel_id);
- if (channel_type != ChannelType::Megagroup) {
- return promise.set_error(Status::Error(3, "The method is available only for supergroup chats"));
+ if (!td_->contacts_manager_->is_megagroup_channel(channel_id)) {
+ return promise.set_error(Status::Error(400, "The method is available only for supergroup chats"));
}
- channel_status = td_->contacts_manager_->get_channel_status(channel_id);
+ channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
if (!channel_status.can_delete_messages()) {
- return promise.set_error(Status::Error(5, "Need delete messages administator right in the supergroup chat"));
+ return promise.set_error(Status::Error(400, "Need delete messages administator right in the supergroup chat"));
}
channel_id = dialog_id.get_channel_id();
break;
@@ -8093,136 +11765,294 @@ void MessagesManager::delete_dialog_messages_from_user(DialogId dialog_id, UserI
}
CHECK(channel_id.is_valid());
- if (G()->parameters().use_message_db) {
- LOG(INFO) << "Delete all messages from " << user_id << " in " << dialog_id << " from database";
- G()->td_db()->get_messages_db_async()->delete_dialog_messages_from_user(dialog_id, user_id,
- Auto()); // TODO Promise
+ if (sender_dialog_id.get_type() == DialogType::SecretChat) {
+ return promise.set_value(Unit());
}
- vector<MessageId> message_ids;
- find_messages_from_user(d->messages, user_id, message_ids);
-
- vector<int64> deleted_message_ids;
- bool need_update_dialog_pos = false;
- for (auto message_id : message_ids) {
- auto m = get_message(d, message_id);
- CHECK(m != nullptr);
- CHECK(m->sender_user_id == user_id);
- CHECK(m->message_id == message_id);
- if (can_delete_channel_message(channel_status, m, is_bot)) {
- deleted_message_ids.push_back(message_id.get());
- auto p = delete_message(d, message_id, true, &need_update_dialog_pos, "delete messages from user");
- CHECK(p.get() == m);
- }
+ if (G()->parameters().use_message_db) {
+ LOG(INFO) << "Delete all messages from " << sender_dialog_id << " in " << dialog_id << " from database";
+ G()->td_db()->get_message_db_async()->delete_dialog_messages_by_sender(dialog_id, sender_dialog_id,
+ Auto()); // TODO Promise
}
- if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "delete_messages_from_user");
- }
+ vector<MessageId> message_ids;
+ find_messages(d->messages.get(), message_ids, [sender_dialog_id, channel_status, is_bot](const Message *m) {
+ return sender_dialog_id == get_message_sender(m) && can_delete_channel_message(channel_status, m, is_bot);
+ });
- send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true, false);
+ delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE);
- delete_all_channel_messages_from_user_on_server(channel_id, user_id, 0, std::move(promise));
+ delete_all_channel_messages_by_sender_on_server(channel_id, sender_dialog_id, 0, std::move(promise));
}
-class MessagesManager::DeleteAllChannelMessagesFromUserOnServerLogEvent {
+class MessagesManager::DeleteAllChannelMessagesFromSenderOnServerLogEvent {
public:
ChannelId channel_id_;
- UserId user_id_;
+ DialogId sender_dialog_id_;
template <class StorerT>
void store(StorerT &storer) const {
td::store(channel_id_, storer);
- td::store(user_id_, storer);
+ td::store(sender_dialog_id_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
td::parse(channel_id_, parser);
- td::parse(user_id_, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddKeyboardButtonFlags)) {
+ td::parse(sender_dialog_id_, parser);
+ } else {
+ UserId user_id;
+ td::parse(user_id, parser);
+ sender_dialog_id_ = DialogId(user_id);
+ }
}
};
-void MessagesManager::delete_all_channel_messages_from_user_on_server(ChannelId channel_id, UserId user_id,
- uint64 logevent_id, Promise<Unit> &&promise) {
- if (logevent_id == 0 && G()->parameters().use_chat_info_db) {
- DeleteAllChannelMessagesFromUserOnServerLogEvent logevent;
- logevent.channel_id_ = channel_id;
- logevent.user_id_ = user_id;
+uint64 MessagesManager::save_delete_all_channel_messages_by_sender_on_server_log_event(ChannelId channel_id,
+ DialogId sender_dialog_id) {
+ DeleteAllChannelMessagesFromSenderOnServerLogEvent log_event{channel_id, sender_dialog_id};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteAllChannelMessagesFromSenderOnServer,
+ get_log_event_storer(log_event));
+}
- auto storer = LogEventStorerImpl<DeleteAllChannelMessagesFromUserOnServerLogEvent>(logevent);
- logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(),
- LogEvent::HandlerType::DeleteAllChannelMessagesFromUserOnServer, storer);
+void MessagesManager::delete_all_channel_messages_by_sender_on_server(ChannelId channel_id, DialogId sender_dialog_id,
+ uint64 log_event_id, Promise<Unit> &&promise) {
+ if (log_event_id == 0 && G()->parameters().use_chat_info_db) {
+ log_event_id = save_delete_all_channel_messages_by_sender_on_server_log_event(channel_id, sender_dialog_id);
}
- if (logevent_id != 0) {
- auto new_promise = PromiseCreator::lambda([logevent_id, promise = std::move(promise)](Result<Unit> result) mutable {
- if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
+ AffectedHistoryQuery query = [td = td_, sender_dialog_id](DialogId dialog_id,
+ Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<DeleteParticipantHistoryQuery>(std::move(query_promise))
+ ->send(dialog_id.get_channel_id(), sender_dialog_id);
+ };
+ run_affected_history_query_until_complete(DialogId(channel_id), std::move(query),
+ sender_dialog_id.get_type() != DialogType::User,
+ get_erase_log_event_promise(log_event_id, std::move(promise)));
+}
+
+void MessagesManager::delete_dialog_messages_by_date(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke,
+ Promise<Unit> &&promise) {
+ bool is_bot = td_->auth_manager_->is_bot();
+ CHECK(!is_bot);
+
+ Dialog *d = get_dialog_force(dialog_id, "delete_dialog_messages_by_date");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ if (min_date > max_date) {
+ return promise.set_error(Status::Error(400, "Wrong date interval specified"));
+ }
+
+ const int32 telegram_launch_date = 1376438400;
+ if (max_date < telegram_launch_date) {
+ return promise.set_value(Unit());
+ }
+ if (min_date < telegram_launch_date) {
+ min_date = telegram_launch_date;
+ }
+
+ auto current_date = max(G()->unix_time(), 1635000000);
+ if (min_date >= current_date - 30) {
+ return promise.set_value(Unit());
+ }
+ if (max_date >= current_date - 30) {
+ max_date = current_date - 31;
+ }
+ CHECK(min_date <= max_date);
+
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ break;
+ case DialogType::Chat:
+ if (revoke) {
+ return promise.set_error(Status::Error(400, "Bulk message revocation is unsupported in basic group chats"));
}
+ break;
+ case DialogType::Channel:
+ return promise.set_error(Status::Error(400, "Bulk message deletion is unsupported in supergroup chats"));
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Bulk message deletion is unsupported in secret chats"));
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ break;
+ }
- promise.set_result(std::move(result));
- });
- promise = std::move(new_promise);
+ // TODO delete in database by dates
+
+ vector<MessageId> message_ids;
+ find_messages_by_date(d->messages.get(), min_date, max_date, message_ids);
+
+ delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE);
+
+ delete_dialog_messages_by_date_on_server(dialog_id, min_date, max_date, revoke, 0, std::move(promise));
+}
+
+class MessagesManager::DeleteDialogMessagesByDateOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+ int32 min_date_;
+ int32 max_date_;
+ bool revoke_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(revoke_);
+ END_STORE_FLAGS();
+ td::store(dialog_id_, storer);
+ td::store(min_date_, storer);
+ td::store(max_date_, storer);
}
- td_->create_handler<DeleteUserHistoryQuery>(std::move(promise))->send(channel_id, user_id);
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(revoke_);
+ END_PARSE_FLAGS();
+ td::parse(dialog_id_, parser);
+ td::parse(min_date_, parser);
+ td::parse(max_date_, parser);
+ }
+};
+
+uint64 MessagesManager::save_delete_dialog_messages_by_date_on_server_log_event(DialogId dialog_id, int32 min_date,
+ int32 max_date, bool revoke) {
+ DeleteDialogMessagesByDateOnServerLogEvent log_event{dialog_id, min_date, max_date, revoke};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::delete_dialog_messages_by_date_on_server(DialogId dialog_id, int32 min_date, int32 max_date,
+ bool revoke, uint64 log_event_id,
+ Promise<Unit> &&promise) {
+ if (log_event_id == 0 && G()->parameters().use_chat_info_db) {
+ log_event_id = save_delete_dialog_messages_by_date_on_server_log_event(dialog_id, min_date, max_date, revoke);
+ }
+
+ AffectedHistoryQuery query = [td = td_, min_date, max_date, revoke](DialogId dialog_id,
+ Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<DeleteMessagesByDateQuery>(std::move(query_promise))
+ ->send(dialog_id, min_date, max_date, revoke);
+ };
+ run_affected_history_query_until_complete(dialog_id, std::move(query), true,
+ get_erase_log_event_promise(log_event_id, std::move(promise)));
+}
+
+int32 MessagesManager::get_unload_dialog_delay() const {
+ constexpr int32 DIALOG_UNLOAD_DELAY = 60; // seconds
+ constexpr int32 DIALOG_UNLOAD_BOT_DELAY = 1800; // seconds
+
+ CHECK(is_message_unload_enabled());
+ auto default_unload_delay = td_->auth_manager_->is_bot() ? DIALOG_UNLOAD_BOT_DELAY : DIALOG_UNLOAD_DELAY;
+ return narrow_cast<int32>(td_->option_manager_->get_option_integer("message_unload_delay", default_unload_delay));
+}
+
+double MessagesManager::get_next_unload_dialog_delay(Dialog *d) const {
+ if (d->unload_dialog_delay_seed == 0) {
+ d->unload_dialog_delay_seed = Random::fast(1, 1000000000);
+ }
+ auto delay = get_unload_dialog_delay() / 4;
+ return delay + delay * 1e-9 * d->unload_dialog_delay_seed;
}
void MessagesManager::unload_dialog(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
+ if (!d->has_unload_timeout) {
+ LOG(INFO) << "Don't need to unload " << dialog_id;
+ // possible right after the dialog was opened
+ return;
+ }
+
+ if (!is_message_unload_enabled()) {
+ // just in case
+ LOG(INFO) << "Message unload is disabled in " << dialog_id;
+ d->has_unload_timeout = false;
+ return;
+ }
+
vector<MessageId> to_unload_message_ids;
- int32 left_to_unload = 0;
- find_unloadable_messages(d, G()->unix_time_cached() - DIALOG_UNLOAD_DELAY + 2, d->messages, to_unload_message_ids,
- left_to_unload);
+ bool has_left_to_unload_messages = false;
+ find_unloadable_messages(d, G()->unix_time_cached() - get_unload_dialog_delay() + 2, d->messages.get(),
+ to_unload_message_ids, has_left_to_unload_messages);
vector<int64> unloaded_message_ids;
+ vector<unique_ptr<Message>> unloaded_messages;
for (auto message_id : to_unload_message_ids) {
- unload_message(d, message_id);
- unloaded_message_ids.push_back(message_id.get());
+ auto message = unload_message(d, message_id);
+ CHECK(message != nullptr);
+ if (message->is_update_sent) {
+ unloaded_message_ids.push_back(message->message_id.get());
+ }
+ unloaded_messages.push_back(std::move(message));
+ }
+ if (unloaded_messages.size() >= MIN_DELETED_ASYNCHRONOUSLY_MESSAGES) {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), unloaded_messages);
}
- if (!unloaded_message_ids.empty()) {
- if (!G()->parameters().use_message_db) {
- d->have_full_history = false;
- }
+ if (!to_unload_message_ids.empty() && !G()->parameters().use_message_db && !d->is_empty) {
+ d->have_full_history = false;
+ d->have_full_history_source = 0;
+ }
+ if (!unloaded_message_ids.empty()) {
send_closure_later(
G()->td(), &Td::send_update,
make_tl_object<td_api::updateDeleteMessages>(dialog_id.get(), std::move(unloaded_message_ids), false, true));
}
- if (left_to_unload > 0) {
- LOG(INFO) << "Need to unload " << left_to_unload << " messages more in " << dialog_id;
- pending_unload_dialog_timeout_.add_timeout_in(d->dialog_id.get(), DIALOG_UNLOAD_DELAY);
+ if (has_left_to_unload_messages) {
+ LOG(DEBUG) << "Need to unload more messages in " << dialog_id;
+ pending_unload_dialog_timeout_.add_timeout_in(
+ d->dialog_id.get(),
+ to_unload_message_ids.size() >= MAX_UNLOADED_MESSAGES ? 1.0 : get_next_unload_dialog_delay(d));
+ } else {
+ d->has_unload_timeout = false;
}
}
-void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dialog_list, bool is_permanent) {
+void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dialog_list, bool is_permanently_deleted) {
CHECK(d != nullptr);
+ LOG(INFO) << "Delete all messages in " << d->dialog_id
+ << " with remove_from_dialog_list = " << remove_from_dialog_list
+ << " and is_permanently_deleted = " << is_permanently_deleted;
if (is_debug_message_op_enabled()) {
- d->debug_message_op.emplace_back(Dialog::MessageOp::DeleteAll, MessageId(), -1, remove_from_dialog_list, false,
- false, "");
+ d->debug_message_op.emplace_back(Dialog::MessageOp::DeleteAll, MessageId(), MessageContentType::None,
+ remove_from_dialog_list, false, false, "");
}
if (d->server_unread_count + d->local_unread_count > 0) {
MessageId max_message_id =
d->last_database_message_id.is_valid() ? d->last_database_message_id : d->last_new_message_id;
if (max_message_id.is_valid()) {
- read_history_inbox(d->dialog_id, max_message_id, -1, "delete_all_dialog_messages");
+ read_history_inbox(d->dialog_id, max_message_id, -1, "delete_all_dialog_messages 1");
}
if (d->server_unread_count != 0 || d->local_unread_count != 0) {
- set_dialog_last_read_inbox_message_id(d, MessageId::min(), 0, 0, true, "delete_all_dialog_messages");
+ set_dialog_last_read_inbox_message_id(d, MessageId::min(), 0, 0, true, "delete_all_dialog_messages 2");
}
}
if (d->unread_mention_count > 0) {
- d->unread_mention_count = 0;
- d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] = 0;
+ set_dialog_unread_mention_count(d, 0);
send_update_chat_unread_mention_count(d);
}
+ if (d->unread_reaction_count > 0) {
+ set_dialog_unread_reaction_count(d, 0);
+ send_update_chat_unread_reaction_count(d, "delete_all_dialog_messages");
+ }
bool has_last_message_id = d->last_message_id != MessageId();
int32 last_message_date = 0;
@@ -8240,10 +12070,11 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia
}
vector<int64> deleted_message_ids;
- do_delete_all_dialog_messages(d, d->messages, deleted_message_ids);
- delete_all_dialog_messages_from_database(d->dialog_id, MessageId::max(), "delete_all_dialog_messages");
- if (is_permanent) {
+ do_delete_all_dialog_messages(d, d->messages, is_permanently_deleted, deleted_message_ids);
+ delete_all_dialog_messages_from_database(d, MessageId::max(), "delete_all_dialog_messages 3");
+ if (is_permanently_deleted) {
for (auto id : deleted_message_ids) {
+ CHECK(id != 0);
d->deleted_message_ids.insert(MessageId{id});
}
}
@@ -8252,65 +12083,107 @@ void MessagesManager::delete_all_dialog_messages(Dialog *d, bool remove_from_dia
set_dialog_reply_markup(d, MessageId());
}
- set_dialog_first_database_message_id(d, MessageId(), "delete_all_dialog_messages");
- set_dialog_last_database_message_id(d, MessageId(), "delete_all_dialog_messages");
- set_dialog_last_clear_history_date(d, last_message_date, last_clear_history_message_id, "delete_all_dialog_messages");
- d->last_read_all_mentions_message_id = MessageId(); // it is not needed anymore
+ set_dialog_first_database_message_id(d, MessageId(), "delete_all_dialog_messages 4");
+ set_dialog_last_database_message_id(d, MessageId(), "delete_all_dialog_messages 5");
+ set_dialog_last_clear_history_date(d, last_message_date, last_clear_history_message_id,
+ "delete_all_dialog_messages 6");
+ d->last_read_all_mentions_message_id = MessageId(); // it is not needed anymore
+ d->message_notification_group.max_removed_notification_id = NotificationId(); // it is not needed anymore
+ d->message_notification_group.max_removed_message_id = MessageId(); // it is not needed anymore
+ d->mention_notification_group.max_removed_notification_id = NotificationId(); // it is not needed anymore
+ d->mention_notification_group.max_removed_message_id = MessageId(); // it is not needed anymore
std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), 0);
+ d->notification_id_to_message_id.clear();
if (has_last_message_id) {
- set_dialog_last_message_id(d, MessageId(), "delete_all_dialog_messages");
- send_update_chat_last_message(d, "delete_all_dialog_messages");
+ set_dialog_last_message_id(d, MessageId(), "delete_all_dialog_messages 7");
+ send_update_chat_last_message(d, "delete_all_dialog_messages 8");
}
- if (remove_from_dialog_list && d->pinned_order != DEFAULT_ORDER) {
- set_dialog_is_pinned(d, false);
+ if (remove_from_dialog_list) {
+ set_dialog_order(d, DEFAULT_ORDER, true, false, "delete_all_dialog_messages 9");
+ } else {
+ update_dialog_pos(d, "delete_all_dialog_messages 10");
}
- update_dialog_pos(d, remove_from_dialog_list, "delete_all_dialog_messages");
- on_dialog_updated(d->dialog_id, "delete_all_dialog_messages");
+ on_dialog_updated(d->dialog_id, "delete_all_dialog_messages 11");
- send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), is_permanent, false);
+ send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), is_permanently_deleted);
}
-void MessagesManager::delete_dialog(DialogId dialog_id) {
- Dialog *d = get_dialog_force(dialog_id);
+void MessagesManager::on_dialog_deleted(DialogId dialog_id, Promise<Unit> &&promise) {
+ LOG(INFO) << "Delete " << dialog_id;
+ Dialog *d = get_dialog_force(dialog_id, "on_dialog_deleted");
if (d == nullptr) {
- return;
+ return promise.set_value(Unit());
}
delete_all_dialog_messages(d, true, false);
if (dialog_id.get_type() != DialogType::SecretChat) {
d->have_full_history = false;
+ d->have_full_history_source = 0;
+ d->is_empty = false;
d->need_restore_reply_markup = true;
+ on_dialog_updated(dialog_id, "on_dialog_deleted");
+ }
+ recently_found_dialogs_.remove_dialog(dialog_id);
+ recently_opened_dialogs_.remove_dialog(dialog_id);
+ if (dialog_id.get_type() == DialogType::Channel) {
+ G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id));
}
close_dialog(d);
+ promise.set_value(Unit());
}
-void MessagesManager::read_all_dialog_mentions(DialogId dialog_id, Promise<Unit> &&promise) {
- bool is_bot = td_->auth_manager_->is_bot();
- if (is_bot) {
- return promise.set_error(Status::Error(3, "Method is not available for bots"));
+void MessagesManager::on_update_dialog_group_call_rights(DialogId dialog_id) {
+ auto d = get_dialog(dialog_id);
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ if (d->active_group_call_id.is_valid()) {
+ td_->group_call_manager_->on_update_group_call_rights(d->active_group_call_id);
}
+}
- Dialog *d = get_dialog_force(dialog_id);
+void MessagesManager::read_all_dialog_mentions(DialogId dialog_id, MessageId top_thread_message_id,
+ Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "read_all_dialog_mentions");
if (d == nullptr) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- LOG(INFO) << "Receive readAllChatMentions request in " << dialog_id << " with " << d->unread_mention_count
- << " unread mentions";
+ TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageId()));
+
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- return promise.set_error(Status::Error(3, "Chat is not accessible"));
+ return promise.set_error(Status::Error(400, "Chat is not accessible"));
+ }
+
+ if (top_thread_message_id.is_valid()) {
+ LOG(INFO) << "Receive readAllChatMentions request in thread of " << top_thread_message_id << " in " << dialog_id;
+ AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id,
+ Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<ReadMentionsQuery>(std::move(query_promise))->send(dialog_id, top_thread_message_id);
+ };
+ run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise));
+ return;
+ } else {
+ LOG(INFO) << "Receive readAllChatMentions request in " << dialog_id << " with " << d->unread_mention_count
+ << " unread mentions";
+ }
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ CHECK(d->unread_mention_count == 0);
+ return promise.set_value(Unit());
}
- if (d->last_new_message_id.get() > d->last_read_all_mentions_message_id.get()) {
+ if (d->last_new_message_id > d->last_read_all_mentions_message_id) {
d->last_read_all_mentions_message_id = d->last_new_message_id;
- on_dialog_updated(dialog_id, "read_all_mentions");
+ on_dialog_updated(dialog_id, "read_all_dialog_mentions");
}
vector<MessageId> message_ids;
- find_unread_mentions(d->messages, message_ids);
+ find_messages(d->messages.get(), message_ids, [](const Message *m) { return m->contains_unread_mention; });
LOG(INFO) << "Found " << message_ids.size() << " messages with unread mentions in memory";
bool is_update_sent = false;
@@ -8319,24 +12192,26 @@ void MessagesManager::read_all_dialog_mentions(DialogId dialog_id, Promise<Unit>
CHECK(m != nullptr);
CHECK(m->contains_unread_mention);
CHECK(m->message_id == message_id);
+ CHECK(m->message_id.is_valid());
+ remove_message_notification_id(d, m, true, false); // must be called before contains_unread_mention is updated
m->contains_unread_mention = false;
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageMentionRead>(dialog_id.get(), m->message_id.get(), 0));
is_update_sent = true;
- on_message_changed(d, m, "read_all_mentions");
+ on_message_changed(d, m, true, "read_all_dialog_mentions");
}
if (d->unread_mention_count != 0) {
- d->unread_mention_count = 0;
- d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] = 0;
+ set_dialog_unread_mention_count(d, 0);
if (!is_update_sent) {
send_update_chat_unread_mention_count(d);
} else {
LOG(INFO) << "Update unread mention message count in " << dialog_id << " to " << d->unread_mention_count;
- on_dialog_updated(dialog_id, "read_all_mentions");
+ on_dialog_updated(dialog_id, "read_all_dialog_mentions");
}
}
+ remove_message_dialog_notifications(d, MessageId::max(), true, "read_all_dialog_mentions");
read_all_dialog_mentions_on_server(dialog_id, 0, std::move(promise));
}
@@ -8356,29 +12231,122 @@ class MessagesManager::ReadAllDialogMentionsOnServerLogEvent {
}
};
-void MessagesManager::read_all_dialog_mentions_on_server(DialogId dialog_id, uint64 logevent_id,
+uint64 MessagesManager::save_read_all_dialog_mentions_on_server_log_event(DialogId dialog_id) {
+ ReadAllDialogMentionsOnServerLogEvent log_event{dialog_id};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadAllDialogMentionsOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::read_all_dialog_mentions_on_server(DialogId dialog_id, uint64 log_event_id,
Promise<Unit> &&promise) {
- if (logevent_id == 0 && G()->parameters().use_message_db) {
- ReadAllDialogMentionsOnServerLogEvent logevent;
- logevent.dialog_id_ = dialog_id;
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_read_all_dialog_mentions_on_server_log_event(dialog_id);
+ }
+
+ AffectedHistoryQuery query = [td = td_](DialogId dialog_id, Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<ReadMentionsQuery>(std::move(query_promise))->send(dialog_id, MessageId());
+ };
+ run_affected_history_query_until_complete(dialog_id, std::move(query), false,
+ get_erase_log_event_promise(log_event_id, std::move(promise)));
+}
- auto storer = LogEventStorerImpl<ReadAllDialogMentionsOnServerLogEvent>(logevent);
- logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadAllDialogMentionsOnServer, storer);
+void MessagesManager::read_all_dialog_reactions(DialogId dialog_id, MessageId top_thread_message_id,
+ Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "read_all_dialog_reactions");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- if (logevent_id != 0) {
- auto new_promise = PromiseCreator::lambda([logevent_id, promise = std::move(promise)](Result<Unit> result) mutable {
- if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
- }
+ TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageId()));
- promise.set_result(std::move(result));
- });
- promise = std::move(new_promise);
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Chat is not accessible"));
}
- td_->create_handler<ReadAllMentionsQuery>(std::move(promise))->send(dialog_id);
+ if (top_thread_message_id.is_valid()) {
+ LOG(INFO) << "Receive readAllChatReactions request in thread of " << top_thread_message_id << " in " << dialog_id;
+ AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id,
+ Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<ReadReactionsQuery>(std::move(query_promise))->send(dialog_id, top_thread_message_id);
+ };
+ run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise));
+ return;
+ } else {
+ LOG(INFO) << "Receive readAllChatReactions request in " << dialog_id << " with " << d->unread_reaction_count
+ << " unread reactions";
+ }
+
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ CHECK(d->unread_reaction_count == 0);
+ return promise.set_value(Unit());
+ }
+
+ vector<MessageId> message_ids;
+ find_messages(d->messages.get(), message_ids,
+ [this, dialog_id](const Message *m) { return has_unread_message_reactions(dialog_id, m); });
+
+ LOG(INFO) << "Found " << message_ids.size() << " messages with unread reactions in memory";
+ bool is_update_sent = false;
+ for (auto message_id : message_ids) {
+ auto m = get_message(d, message_id);
+ CHECK(m != nullptr);
+ CHECK(has_unread_message_reactions(dialog_id, m));
+ CHECK(m->message_id == message_id);
+ CHECK(m->message_id.is_valid());
+ // remove_message_notification_id(d, m, true, false); // must be called before unread_reactions are cleared
+ m->reactions->unread_reactions_.clear();
+
+ send_update_message_unread_reactions(dialog_id, m, 0);
+ is_update_sent = true;
+ on_message_changed(d, m, true, "read_all_dialog_reactions");
+ }
+
+ if (d->unread_reaction_count != 0) {
+ set_dialog_unread_reaction_count(d, 0);
+ if (!is_update_sent) {
+ send_update_chat_unread_reaction_count(d, "read_all_dialog_reactions");
+ } else {
+ LOG(INFO) << "Update unread reaction message count in " << dialog_id << " to " << d->unread_reaction_count;
+ on_dialog_updated(dialog_id, "read_all_dialog_reactions");
+ }
+ }
+ // remove_message_dialog_notifications(d, MessageId::max(), true, "read_all_dialog_reactions");
+
+ read_all_dialog_reactions_on_server(dialog_id, 0, std::move(promise));
+}
+
+class MessagesManager::ReadAllDialogReactionsOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(dialog_id_, parser);
+ }
+};
+
+uint64 MessagesManager::save_read_all_dialog_reactions_on_server_log_event(DialogId dialog_id) {
+ ReadAllDialogReactionsOnServerLogEvent log_event{dialog_id};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadAllDialogReactionsOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::read_all_dialog_reactions_on_server(DialogId dialog_id, uint64 log_event_id,
+ Promise<Unit> &&promise) {
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_read_all_dialog_reactions_on_server_log_event(dialog_id);
+ }
+
+ AffectedHistoryQuery query = [td = td_](DialogId dialog_id, Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<ReadReactionsQuery>(std::move(query_promise))->send(dialog_id, MessageId());
+ };
+ run_affected_history_query_until_complete(dialog_id, std::move(query), false,
+ get_erase_log_event_promise(log_event_id, std::move(promise)));
}
void MessagesManager::read_message_content_from_updates(MessageId message_id) {
@@ -8396,47 +12364,33 @@ void MessagesManager::read_message_content_from_updates(MessageId message_id) {
}
void MessagesManager::read_channel_message_content_from_updates(Dialog *d, MessageId message_id) {
+ CHECK(d != nullptr);
if (!message_id.is_valid() || !message_id.is_server()) {
LOG(ERROR) << "Incoming update tries to read content of " << message_id << " in " << d->dialog_id;
return;
}
- Message *m = get_message_force(d, message_id);
+ Message *m = get_message_force(d, message_id, "read_channel_message_content_from_updates");
if (m != nullptr) {
read_message_content(d, m, false, "read_channel_message_content_from_updates");
- }
-}
-
-bool MessagesManager::update_opened_message_content(Message *m) {
- switch (m->content->get_id()) {
- case MessageVideoNote::ID: {
- auto content = static_cast<MessageVideoNote *>(m->content.get());
- if (content->is_viewed) {
- return false;
- }
- content->is_viewed = true;
- return true;
- }
- case MessageVoiceNote::ID: {
- auto content = static_cast<MessageVoiceNote *>(m->content.get());
- if (content->is_listened) {
- return false;
- }
- content->is_listened = true;
- return true;
- }
- default:
- return false;
+ } else if (message_id > d->last_new_message_id) {
+ get_channel_difference(d->dialog_id, d->pts, true, "read_channel_message_content_from_updates");
}
}
bool MessagesManager::read_message_content(Dialog *d, Message *m, bool is_local_read, const char *source) {
- CHECK(m != nullptr) << source;
+ LOG_CHECK(m != nullptr) << source;
+ CHECK(!m->message_id.is_scheduled());
bool is_mention_read = update_message_contains_unread_mention(d, m, false, "read_message_content");
- bool is_content_read = update_opened_message_content(m) | ttl_on_open(d, m, Time::now(), is_local_read);
+ bool is_content_read = update_opened_message_content(m->content.get());
+ if (ttl_on_open(d, m, Time::now(), is_local_read)) {
+ is_content_read = true;
+ }
+ LOG(INFO) << "Read message content of " << m->message_id << " in " << d->dialog_id
+ << ": is_mention_read = " << is_mention_read << ", is_content_read = " << is_content_read;
if (is_mention_read || is_content_read) {
- on_message_changed(d, m, "read_message_content");
+ on_message_changed(d, m, true, "read_message_content");
if (is_content_read) {
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateMessageContentOpened>(d->dialog_id.get(), m->message_id.get()));
@@ -8446,139 +12400,270 @@ bool MessagesManager::read_message_content(Dialog *d, Message *m, bool is_local_
return false;
}
+bool MessagesManager::has_incoming_notification(DialogId dialog_id, const Message *m) const {
+ if (m->is_from_scheduled) {
+ return true;
+ }
+ return !m->is_outgoing && dialog_id != get_my_dialog_id();
+}
+
+int32 MessagesManager::calc_new_unread_count_from_last_unread(Dialog *d, MessageId max_message_id,
+ MessageType type) const {
+ CHECK(!max_message_id.is_scheduled());
+ MessagesConstIterator it(d, max_message_id);
+ if (*it == nullptr || (*it)->message_id != max_message_id) {
+ return -1;
+ }
+
+ int32 unread_count = type == MessageType::Server ? d->server_unread_count : d->local_unread_count;
+ while (*it != nullptr && (*it)->message_id > d->last_read_inbox_message_id) {
+ if (has_incoming_notification(d->dialog_id, *it) && (*it)->message_id.get_type() == type) {
+ unread_count--;
+ }
+ --it;
+ }
+ if (*it == nullptr || (*it)->message_id != d->last_read_inbox_message_id) {
+ return -1;
+ }
+
+ LOG(INFO) << "Found " << unread_count << " unread messages in " << d->dialog_id << " from last unread message";
+ return unread_count;
+}
+
+int32 MessagesManager::calc_new_unread_count_from_the_end(Dialog *d, MessageId max_message_id, MessageType type,
+ int32 hint_unread_count) const {
+ CHECK(!max_message_id.is_scheduled());
+ int32 unread_count = 0;
+ MessagesConstIterator it(d, MessageId::max());
+ while (*it != nullptr && (*it)->message_id > max_message_id) {
+ if (has_incoming_notification(d->dialog_id, *it) && (*it)->message_id.get_type() == type) {
+ unread_count++;
+ }
+ --it;
+ }
+
+ bool is_count_exact = d->last_message_id.is_valid() && *it != nullptr;
+ if (hint_unread_count >= 0) {
+ if (is_count_exact) {
+ if (hint_unread_count == unread_count) {
+ return hint_unread_count;
+ }
+ } else {
+ if (hint_unread_count >= unread_count) {
+ return hint_unread_count;
+ }
+ }
+
+ // hint_unread_count is definitely wrong, ignore it
+
+ if (need_unread_counter(d->order)) {
+ LOG(ERROR) << "Receive hint_unread_count = " << hint_unread_count << ", but found " << unread_count
+ << " unread messages in " << d->dialog_id;
+ }
+ }
+
+ if (!is_count_exact) {
+ // unread count is likely to be calculated wrong, so ignore it
+ return -1;
+ }
+
+ LOG(INFO) << "Found " << unread_count << " unread messages in " << d->dialog_id << " from the end";
+ return unread_count;
+}
+
+int32 MessagesManager::calc_new_unread_count(Dialog *d, MessageId max_message_id, MessageType type,
+ int32 hint_unread_count) const {
+ CHECK(!max_message_id.is_scheduled());
+ if (d->is_empty) {
+ return 0;
+ }
+
+ if (!d->last_read_inbox_message_id.is_valid()) {
+ return calc_new_unread_count_from_the_end(d, max_message_id, type, hint_unread_count);
+ }
+
+ if (!d->last_message_id.is_valid() ||
+ (d->last_message_id.get() - max_message_id.get() > max_message_id.get() - d->last_read_inbox_message_id.get())) {
+ int32 unread_count = calc_new_unread_count_from_last_unread(d, max_message_id, type);
+ return unread_count >= 0 ? unread_count
+ : calc_new_unread_count_from_the_end(d, max_message_id, type, hint_unread_count);
+ } else {
+ int32 unread_count = calc_new_unread_count_from_the_end(d, max_message_id, type, hint_unread_count);
+ return unread_count >= 0 ? unread_count : calc_new_unread_count_from_last_unread(d, max_message_id, type);
+ }
+}
+
+void MessagesManager::repair_server_unread_count(DialogId dialog_id, int32 unread_count, const char *source) {
+ if (td_->auth_manager_->is_bot() || !have_input_peer(dialog_id, AccessRights::Read)) {
+ return;
+ }
+ if (pending_read_history_timeout_.has_timeout(dialog_id.get())) {
+ return; // postpone until read history request is sent
+ }
+
+ LOG(INFO) << "Repair server unread count in " << dialog_id << " from " << unread_count << " from " << source;
+ create_actor<SleepActor>("RepairServerUnreadCountSleepActor", 0.2,
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result<Unit> result) {
+ send_closure(actor_id, &MessagesManager::send_get_dialog_query, dialog_id, Promise<Unit>(),
+ 0, "repair_server_unread_count");
+ }))
+ .release();
+}
+
+void MessagesManager::repair_channel_server_unread_count(Dialog *d) {
+ CHECK(d != nullptr);
+ CHECK(d->dialog_id.get_type() == DialogType::Channel);
+
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ if (d->last_read_inbox_message_id >= d->last_new_message_id) {
+ // all messages are already read
+ return;
+ }
+ if (!need_unread_counter(d->order)) {
+ // there are no unread counters in left channels
+ return;
+ }
+ if (!d->need_repair_channel_server_unread_count) {
+ d->need_repair_channel_server_unread_count = true;
+ on_dialog_updated(d->dialog_id, "repair_channel_server_unread_count");
+ }
+
+ LOG(INFO) << "Reload ChannelFull for " << d->dialog_id << " to repair unread message counts";
+ get_dialog_info_full(d->dialog_id, Auto(), "repair_channel_server_unread_count");
+}
+
void MessagesManager::read_history_inbox(DialogId dialog_id, MessageId max_message_id, int32 unread_count,
const char *source) {
+ CHECK(!max_message_id.is_scheduled());
+
if (td_->auth_manager_->is_bot()) {
return;
}
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "read_history_inbox");
if (d != nullptr) {
- if (unread_count < 0) {
- if (!max_message_id.is_valid()) {
- LOG(ERROR) << "Receive read inbox update in " << dialog_id << " up to " << max_message_id << " from " << source;
- return;
- }
- } else {
- if (!max_message_id.is_valid() && max_message_id != MessageId()) {
- LOG(ERROR) << "Receive read inbox history in " << dialog_id << " up to " << max_message_id << " from "
- << source;
- return;
- }
+ if (d->need_repair_channel_server_unread_count) {
+ d->need_repair_channel_server_unread_count = false;
+ on_dialog_updated(dialog_id, "read_history_inbox");
}
- if (d->is_last_read_inbox_message_id_inited && max_message_id.get() <= d->last_read_inbox_message_id.get()) {
+
+ // there can be updateReadHistoryInbox up to message 0, if messages where read and then all messages where deleted
+ if (!max_message_id.is_valid() && max_message_id != MessageId()) {
+ LOG(ERROR) << "Receive read inbox update in " << dialog_id << " up to " << max_message_id << " from " << source;
+ return;
+ }
+ if (d->is_last_read_inbox_message_id_inited && max_message_id <= d->last_read_inbox_message_id) {
LOG(INFO) << "Receive read inbox update in " << dialog_id << " up to " << max_message_id << " from " << source
<< ", but all messages have already been read up to " << d->last_read_inbox_message_id;
+ if (max_message_id == d->last_read_inbox_message_id && unread_count >= 0 &&
+ unread_count != d->server_unread_count) {
+ set_dialog_last_read_inbox_message_id(d, MessageId::min(), unread_count, d->local_unread_count, true, source);
+ }
return;
}
if (max_message_id != MessageId() && max_message_id.is_yet_unsent()) {
- LOG(ERROR) << "Try to update last read inbox message in " << dialog_id << " with " << max_message_id << " from "
+ LOG(ERROR) << "Tried to update last read inbox message in " << dialog_id << " with " << max_message_id << " from "
<< source;
return;
}
- if (unread_count > 0 && max_message_id.get() >= d->last_new_message_id.get() &&
- max_message_id.get() >= d->last_message_id.get() && max_message_id.get() >= d->last_database_message_id.get()) {
- LOG(INFO) << "Have unknown " << unread_count << " unread messages in " << dialog_id;
+ if (max_message_id != MessageId() && unread_count > 0 && max_message_id >= d->last_new_message_id &&
+ max_message_id >= d->last_message_id && max_message_id >= d->last_database_message_id) {
+ if (d->last_new_message_id.is_valid()) {
+ LOG(ERROR) << "Have unknown " << unread_count << " unread messages up to " << max_message_id << " in "
+ << dialog_id << " with last_new_message_id = " << d->last_new_message_id
+ << ", last_message_id = " << d->last_message_id
+ << ", last_database_message_id = " << d->last_database_message_id << " from " << source;
+ }
unread_count = 0;
}
- LOG_IF(INFO, d->last_new_message_id.is_valid() && max_message_id.get() > d->last_new_message_id.get() &&
- max_message_id.is_server() && dialog_id.get_type() != DialogType::Channel &&
- !running_get_difference_)
+ LOG_IF(INFO, d->last_new_message_id.is_valid() && max_message_id > d->last_new_message_id &&
+ max_message_id > d->max_notification_message_id && max_message_id.is_server() &&
+ dialog_id.get_type() != DialogType::Channel && !running_get_difference_)
<< "Receive read inbox update up to unknown " << max_message_id << " in " << dialog_id << " from " << source
<< ". Last new is " << d->last_new_message_id << ", unread_count = " << unread_count
- << ". Possible only for deleted incoming message. " << td_->updates_manager_->get_state();
+ << ". Possible only for deleted incoming message";
if (dialog_id.get_type() == DialogType::SecretChat) {
- // TODO: protect with logevent
- suffix_load_till_message_id(
- d, d->last_read_inbox_message_id,
- PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, from_message_id = max_message_id,
- till_message_id = d->last_read_inbox_message_id,
- timestamp = Time::now()](Result<Unit>) {
- send_closure(actor_id, &MessagesManager::ttl_read_history_inbox, dialog_id, from_message_id,
- till_message_id, timestamp);
- }));
+ ttl_read_history(d, false, max_message_id, d->last_read_inbox_message_id, Time::now());
}
- int32 local_unread_count = 0;
- int32 server_unread_count = 0;
- if (dialog_id != DialogId(td_->contacts_manager_->get_my_id("read_history_inbox"))) {
- MessagesConstIterator it(d, MessageId::max());
- while (*it != nullptr && (*it)->message_id.get() > max_message_id.get()) {
- if (!(*it)->is_outgoing) {
- if ((*it)->message_id.is_server()) {
- server_unread_count++;
- } else {
- CHECK((*it)->message_id.is_local());
- local_unread_count++;
- }
- }
- --it;
- }
+ if (max_message_id > d->last_new_message_id && dialog_id.get_type() == DialogType::Channel) {
+ LOG(INFO) << "Schedule getDifference in " << dialog_id.get_channel_id();
+ channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
}
- if (unread_count >= 0) {
- if (unread_count < server_unread_count) {
- LOG(ERROR) << "Receive unread_count = " << unread_count << ", but have at least " << server_unread_count
- << " unread messages in " << dialog_id;
- } else {
- server_unread_count = unread_count;
+
+ int32 server_unread_count = calc_new_unread_count(d, max_message_id, MessageType::Server, unread_count);
+ int32 local_unread_count =
+ d->local_unread_count == 0 ? 0 : calc_new_unread_count(d, max_message_id, MessageType::Local, -1);
+
+ if (server_unread_count < 0) {
+ server_unread_count = unread_count >= 0 ? unread_count : d->server_unread_count;
+ if (dialog_id.get_type() != DialogType::SecretChat && have_input_peer(dialog_id, AccessRights::Read) &&
+ need_unread_counter(d->order)) {
+ d->need_repair_server_unread_count = true;
+ repair_server_unread_count(dialog_id, server_unread_count, "read_history_inbox");
}
}
+ if (local_unread_count < 0) {
+ // TODO repair local unread count
+ local_unread_count = d->local_unread_count;
+ }
set_dialog_last_read_inbox_message_id(d, max_message_id, server_unread_count, local_unread_count, true, source);
+
+ if (d->is_marked_as_unread && max_message_id != MessageId()) {
+ set_dialog_is_marked_as_unread(d, false);
+ }
} else {
LOG(INFO) << "Receive read inbox about unknown " << dialog_id << " from " << source;
}
}
void MessagesManager::read_history_outbox(DialogId dialog_id, MessageId max_message_id, int32 read_date) {
+ CHECK(!max_message_id.is_scheduled());
+
if (td_->auth_manager_->is_bot()) {
return;
}
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "read_history_outbox");
if (d != nullptr) {
if (!max_message_id.is_valid()) {
LOG(ERROR) << "Receive read outbox update in " << dialog_id << " with " << max_message_id;
return;
}
- if (max_message_id.get() <= d->last_read_outbox_message_id.get()) {
+ if (max_message_id <= d->last_read_outbox_message_id) {
LOG(INFO) << "Receive read outbox update up to " << max_message_id
<< ", but all messages have already been read up to " << d->last_read_outbox_message_id;
return;
}
if (max_message_id.is_yet_unsent()) {
- LOG(ERROR) << "Try to update last read outbox message with " << max_message_id;
+ LOG(ERROR) << "Tried to update last read outbox message with " << max_message_id << " in " << dialog_id;
return;
}
// it is impossible for just sent outgoing messages because updates are ordered by pts
- LOG_IF(INFO, d->last_new_message_id.is_valid() && max_message_id.get() > d->last_new_message_id.get() &&
- dialog_id.get_type() != DialogType::Channel)
- << "Receive read outbox update about unknown " << max_message_id << " in " << dialog_id << " with last new "
- << d->last_new_message_id << ". Possible only for deleted outgoing message. "
- << td_->updates_manager_->get_state();
+ if (d->last_new_message_id.is_valid() && max_message_id > d->last_new_message_id &&
+ dialog_id.get_type() != DialogType::Channel) {
+ LOG(INFO) << "Receive read outbox update about unknown " << max_message_id << " in " << dialog_id
+ << " with last new " << d->last_new_message_id << ". Possible only for deleted outgoing message";
+ }
if (dialog_id.get_type() == DialogType::SecretChat) {
- double server_time = Time::now();
- double read_time = server_time;
+ double server_time = G()->server_time();
+ double read_time = Time::now();
if (read_date <= 0) {
LOG(ERROR) << "Receive wrong read date " << read_date << " in " << dialog_id;
} else if (read_date < server_time) {
- read_time = read_date;
- }
- // TODO: protect with logevent
- suffix_load_till_message_id(
- d, d->last_read_outbox_message_id,
- PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, from_message_id = max_message_id,
- till_message_id = d->last_read_outbox_message_id, read_time](Result<Unit>) {
- send_closure(actor_id, &MessagesManager::ttl_read_history_outbox, dialog_id, from_message_id,
- till_message_id, read_time);
- }));
+ read_time -= (server_time - read_date);
+ }
+ ttl_read_history(d, true, max_message_id, d->last_read_outbox_message_id, read_time);
}
set_dialog_last_read_outbox_message_id(d, max_message_id);
@@ -8587,40 +12672,224 @@ void MessagesManager::read_history_outbox(DialogId dialog_id, MessageId max_mess
}
}
-void MessagesManager::recalc_unread_message_count() {
- if (td_->auth_manager_->is_bot() || !need_unread_count_recalc_) {
+bool MessagesManager::need_unread_counter(int64 dialog_order) {
+ return dialog_order != DEFAULT_ORDER;
+}
+
+int32 MessagesManager::get_dialog_total_count(const DialogList &list) const {
+ int32 sponsored_dialog_count = 0;
+ if (sponsored_dialog_id_.is_valid() && list.dialog_list_id == DialogListId(FolderId::main())) {
+ auto d = get_dialog(sponsored_dialog_id_);
+ CHECK(d != nullptr);
+ if (is_dialog_sponsored(d)) {
+ sponsored_dialog_count = 1;
+ }
+ }
+ if (list.server_dialog_total_count_ != -1 && list.secret_chat_total_count_ != -1) {
+ return std::max(list.server_dialog_total_count_ + list.secret_chat_total_count_,
+ list.in_memory_dialog_total_count_) +
+ sponsored_dialog_count;
+ }
+ if (list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
+ return list.in_memory_dialog_total_count_ + sponsored_dialog_count;
+ }
+ return list.in_memory_dialog_total_count_ + sponsored_dialog_count + 1;
+}
+
+void MessagesManager::repair_server_dialog_total_count(DialogListId dialog_list_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ if (!dialog_list_id.is_folder()) {
+ // can repair total count only in folders
return;
}
- need_unread_count_recalc_ = false;
- is_unread_count_inited_ = true;
- int32 total_count = 0;
- int32 muted_count = 0;
- for (auto &dialog_date : ordered_server_dialogs_) {
- auto dialog_id = dialog_date.get_dialog_id();
- Dialog *d = get_dialog(dialog_id);
- CHECK(d != nullptr);
- int unread_count = d->server_unread_count + d->local_unread_count;
- if (d->order != DEFAULT_ORDER && unread_count > 0) {
- total_count += unread_count;
- if (d->notification_settings.mute_until != 0) {
- muted_count += unread_count;
+ LOG(INFO) << "Repair total chat count in " << dialog_list_id;
+ td_->create_handler<GetDialogListQuery>(Promise<Unit>())
+ ->send(dialog_list_id.get_folder_id(), 2147483647, ServerMessageId(), DialogId(), 1);
+}
+
+void MessagesManager::repair_secret_chat_total_count(DialogListId dialog_list_id) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (G()->parameters().use_message_db && dialog_list_id.is_folder()) {
+ // race-prone
+ G()->td_db()->get_dialog_db_async()->get_secret_chat_count(
+ dialog_list_id.get_folder_id(),
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_list_id](Result<int32> result) {
+ if (result.is_error()) {
+ return;
+ }
+ send_closure(actor_id, &MessagesManager::on_get_secret_chat_total_count, dialog_list_id, result.move_as_ok());
+ }));
+ } else {
+ int32 total_count = 0;
+ auto *list = get_dialog_list(dialog_list_id);
+ CHECK(list != nullptr);
+ for (auto &folder_id : get_dialog_list_folder_ids(*list)) {
+ const auto *folder_list = get_dialog_list(DialogListId(folder_id));
+ CHECK(folder_list != nullptr);
+ if (folder_list->need_unread_count_recalc_) {
+ // can't repair total secret chat count yet
+ return;
+ }
+
+ const auto *folder = get_dialog_folder(folder_id);
+ CHECK(folder != nullptr);
+ for (const auto &dialog_date : folder->ordered_dialogs_) {
+ auto dialog_id = dialog_date.get_dialog_id();
+ if (dialog_id.get_type() == DialogType::SecretChat && dialog_date.get_order() != DEFAULT_ORDER) {
+ total_count++;
+ }
+ }
+ }
+ on_get_secret_chat_total_count(dialog_list_id, total_count);
+ }
+}
+
+void MessagesManager::on_get_secret_chat_total_count(DialogListId dialog_list_id, int32 total_count) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(!td_->auth_manager_->is_bot());
+ auto *list = get_dialog_list(dialog_list_id);
+ if (list == nullptr) {
+ // just in case
+ return;
+ }
+ CHECK(total_count >= 0);
+ if (list->secret_chat_total_count_ != total_count) {
+ auto old_dialog_total_count = get_dialog_total_count(*list);
+ list->secret_chat_total_count_ = total_count;
+ if (list->is_dialog_unread_count_inited_) {
+ if (old_dialog_total_count != get_dialog_total_count(*list)) {
+ send_update_unread_chat_count(*list, DialogId(), true, "on_get_secret_chat_total_count");
} else {
- LOG(DEBUG) << "Have " << unread_count << " messages in unmuted " << dialog_id;
+ save_unread_chat_count(*list);
}
}
}
+}
+
+void MessagesManager::recalc_unread_count(DialogListId dialog_list_id, int32 old_dialog_total_count, bool force) {
+ if (G()->close_flag() || td_->auth_manager_->is_bot() || !G()->parameters().use_message_db) {
+ return;
+ }
- if (unread_message_total_count_ != total_count || unread_message_muted_count_ != muted_count) {
- unread_message_total_count_ = total_count;
- unread_message_muted_count_ = muted_count;
- send_update_unread_message_count(DialogId(), true, "recalc_unread_message_count");
+ auto *list_ptr = get_dialog_list(dialog_list_id);
+ CHECK(list_ptr != nullptr);
+ auto &list = *list_ptr;
+ if (!list.need_unread_count_recalc_ && !force) {
+ return;
+ }
+ LOG(INFO) << "Recalculate unread counts in " << dialog_list_id;
+ list.need_unread_count_recalc_ = false;
+ list.is_message_unread_count_inited_ = true;
+ list.is_dialog_unread_count_inited_ = true;
+
+ int32 message_total_count = 0;
+ int32 message_muted_count = 0;
+ int32 dialog_total_count = 0;
+ int32 dialog_muted_count = 0;
+ int32 dialog_marked_count = 0;
+ int32 dialog_muted_marked_count = 0;
+ int32 server_dialog_total_count = 0;
+ int32 secret_chat_total_count = 0;
+ for (auto folder_id : get_dialog_list_folder_ids(list)) {
+ const auto &folder = *get_dialog_folder(folder_id);
+ for (const auto &dialog_date : folder.ordered_dialogs_) {
+ if (dialog_date.get_order() == DEFAULT_ORDER) {
+ break;
+ }
+
+ auto dialog_id = dialog_date.get_dialog_id();
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ if (!is_dialog_in_list(d, dialog_list_id)) {
+ continue;
+ }
+
+ int unread_count = d->server_unread_count + d->local_unread_count;
+ if (need_unread_counter(d->order) && (unread_count > 0 || d->is_marked_as_unread)) {
+ message_total_count += unread_count;
+ dialog_total_count++;
+ if (unread_count == 0 && d->is_marked_as_unread) {
+ dialog_marked_count++;
+ }
+
+ LOG(DEBUG) << "Have " << unread_count << " messages in " << dialog_id;
+ if (is_dialog_muted(d)) {
+ message_muted_count += unread_count;
+ dialog_muted_count++;
+ if (unread_count == 0 && d->is_marked_as_unread) {
+ dialog_muted_marked_count++;
+ }
+ }
+ }
+ if (d->order != DEFAULT_ORDER) { // must not count sponsored dialog, which is added independently
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ secret_chat_total_count++;
+ } else {
+ server_dialog_total_count++;
+ }
+ }
+ }
+ }
+
+ if (list.unread_message_total_count_ != message_total_count ||
+ list.unread_message_muted_count_ != message_muted_count) {
+ list.unread_message_total_count_ = message_total_count;
+ list.unread_message_muted_count_ = message_muted_count;
+ send_update_unread_message_count(list, DialogId(), true, "recalc_unread_count");
+ }
+
+ if (old_dialog_total_count == -1) {
+ old_dialog_total_count = get_dialog_total_count(list);
+ }
+ bool need_save = false;
+ if (list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
+ if (server_dialog_total_count != list.server_dialog_total_count_ ||
+ secret_chat_total_count != list.secret_chat_total_count_) {
+ list.server_dialog_total_count_ = server_dialog_total_count;
+ list.secret_chat_total_count_ = secret_chat_total_count;
+ need_save = true;
+ }
+ } else {
+ if (list.server_dialog_total_count_ == -1) {
+ // recalc_unread_count is called only after getDialogs request; it is unneeded to call getDialogs again
+ repair_server_dialog_total_count(dialog_list_id);
+ }
+
+ if (list.secret_chat_total_count_ == -1) {
+ repair_secret_chat_total_count(dialog_list_id);
+ }
+ }
+ if (list.unread_dialog_total_count_ != dialog_total_count || list.unread_dialog_muted_count_ != dialog_muted_count ||
+ list.unread_dialog_marked_count_ != dialog_marked_count ||
+ list.unread_dialog_muted_marked_count_ != dialog_muted_marked_count ||
+ old_dialog_total_count != get_dialog_total_count(list)) {
+ list.unread_dialog_total_count_ = dialog_total_count;
+ list.unread_dialog_muted_count_ = dialog_muted_count;
+ list.unread_dialog_marked_count_ = dialog_marked_count;
+ list.unread_dialog_muted_marked_count_ = dialog_muted_marked_count;
+ send_update_unread_chat_count(list, DialogId(), true, "recalc_unread_count");
+ } else if (need_save) {
+ save_unread_chat_count(list);
}
}
void MessagesManager::set_dialog_last_read_inbox_message_id(Dialog *d, MessageId message_id, int32 server_unread_count,
int32 local_unread_count, bool force_update,
const char *source) {
+ CHECK(!message_id.is_scheduled());
+
if (td_->auth_manager_->is_bot()) {
return;
}
@@ -8637,20 +12906,87 @@ void MessagesManager::set_dialog_last_read_inbox_message_id(Dialog *d, MessageId
int32 old_unread_count = d->server_unread_count + d->local_unread_count;
d->server_unread_count = server_unread_count;
d->local_unread_count = local_unread_count;
- int32 new_unread_count = d->server_unread_count + d->local_unread_count;
- int32 delta = new_unread_count - old_unread_count;
- if (delta != 0 && d->order != DEFAULT_ORDER && is_unread_count_inited_) {
- unread_message_total_count_ += delta;
- if (d->notification_settings.mute_until != 0) {
- unread_message_muted_count_ += delta;
+
+ if (need_unread_counter(d->order)) {
+ const int32 new_unread_count = d->server_unread_count + d->local_unread_count;
+ for (auto &list : get_dialog_lists(d)) {
+ int32 delta = new_unread_count - old_unread_count;
+ if (delta != 0 && list.is_message_unread_count_inited_) {
+ list.unread_message_total_count_ += delta;
+ if (is_dialog_muted(d)) {
+ list.unread_message_muted_count_ += delta;
+ }
+ send_update_unread_message_count(list, d->dialog_id, force_update, source);
+ }
+ delta = static_cast<int32>(new_unread_count != 0) - static_cast<int32>(old_unread_count != 0);
+ if (delta != 0 && list.is_dialog_unread_count_inited_) {
+ if (d->is_marked_as_unread) {
+ list.unread_dialog_marked_count_ -= delta;
+ } else {
+ list.unread_dialog_total_count_ += delta;
+ }
+ if (is_dialog_muted(d)) {
+ if (d->is_marked_as_unread) {
+ list.unread_dialog_muted_marked_count_ -= delta;
+ } else {
+ list.unread_dialog_muted_count_ += delta;
+ }
+ }
+ send_update_unread_chat_count(list, d->dialog_id, force_update, source);
+ }
+ }
+
+ bool was_unread = old_unread_count != 0 || d->is_marked_as_unread;
+ bool is_unread = new_unread_count != 0 || d->is_marked_as_unread;
+ if (!dialog_filters_.empty() && was_unread != is_unread) {
+ update_dialog_lists(d, get_dialog_positions(d), true, false, "set_dialog_last_read_inbox_message_id");
+ }
+ }
+
+ if (message_id != MessageId::min() && d->last_read_inbox_message_id.is_valid() &&
+ (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) {
+ VLOG(notifications) << "Remove some notifications in " << d->dialog_id
+ << " after updating last read inbox message to " << message_id
+ << " and unread message count to " << server_unread_count << " + " << local_unread_count
+ << " from " << source;
+ if (d->message_notification_group.group_id.is_valid()) {
+ auto total_count = get_dialog_pending_notification_count(d, false);
+ if (total_count == 0) {
+ set_dialog_last_notification(d->dialog_id, d->message_notification_group, 0, NotificationId(), source);
+ }
+ if (!d->pending_new_message_notifications.empty()) {
+ for (auto &it : d->pending_new_message_notifications) {
+ if (it.second <= message_id) {
+ it.first = DialogId();
+ }
+ }
+ flush_pending_new_message_notifications(d->dialog_id, false, DialogId(UserId(static_cast<int64>(1))));
+ }
+ total_count -= static_cast<int32>(d->pending_new_message_notifications.size());
+ if (total_count < 0) {
+ LOG(ERROR) << "Total message notification count is " << total_count << " in " << d->dialog_id
+ << " with old unread_count = " << old_unread_count << " and " << d->pending_new_message_notifications
+ << " pending new message notifications after reading history up to " << message_id;
+ total_count = 0;
+ }
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group,
+ d->message_notification_group.group_id, NotificationId(), d->last_read_inbox_message_id,
+ total_count, Slice(source) == Slice("view_messages"), Promise<Unit>());
+ }
+
+ if (d->mention_notification_group.group_id.is_valid() && d->pinned_message_notification_message_id.is_valid() &&
+ d->pinned_message_notification_message_id <= d->last_read_inbox_message_id) {
+ // remove pinned message notification when it is read
+ remove_dialog_pinned_message_notification(d, source);
}
- send_update_unread_message_count(d->dialog_id, force_update, source);
}
send_update_chat_read_inbox(d, force_update, source);
}
void MessagesManager::set_dialog_last_read_outbox_message_id(Dialog *d, MessageId message_id) {
+ CHECK(!message_id.is_scheduled());
+
if (td_->auth_manager_->is_bot()) {
return;
}
@@ -8665,11 +13001,15 @@ void MessagesManager::set_dialog_last_read_outbox_message_id(Dialog *d, MessageI
void MessagesManager::set_dialog_max_unavailable_message_id(DialogId dialog_id, MessageId max_unavailable_message_id,
bool from_update, const char *source) {
- Dialog *d = get_dialog_force(dialog_id);
+ CHECK(!max_unavailable_message_id.is_scheduled());
+
+ Dialog *d = get_dialog_force(dialog_id, source);
if (d != nullptr) {
- if (d->last_new_message_id.is_valid() && max_unavailable_message_id.get() > d->last_new_message_id.get()) {
- LOG(ERROR) << "Tried to set " << dialog_id << " max unavailable message id to " << max_unavailable_message_id
- << " from " << source << ", but last new message id is " << d->last_new_message_id;
+ if (d->last_new_message_id.is_valid() && max_unavailable_message_id > d->last_new_message_id && from_update) {
+ if (!td_->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Tried to set " << dialog_id << " max unavailable message to " << max_unavailable_message_id
+ << " from " << source << ", but last new message is " << d->last_new_message_id;
+ }
max_unavailable_message_id = d->last_new_message_id;
}
@@ -8678,16 +13018,16 @@ void MessagesManager::set_dialog_max_unavailable_message_id(DialogId dialog_id,
}
if (max_unavailable_message_id.is_valid() && max_unavailable_message_id.is_yet_unsent()) {
- LOG(ERROR) << "Try to update " << dialog_id << " last read outbox message with " << max_unavailable_message_id
+ LOG(ERROR) << "Tried to update " << dialog_id << " max unavailable message with " << max_unavailable_message_id
<< " from " << source;
return;
}
- LOG(INFO) << "Set max unavailable message id to " << max_unavailable_message_id << " in " << dialog_id << " from "
+ LOG(INFO) << "Set max unavailable message to " << max_unavailable_message_id << " in " << dialog_id << " from "
<< source;
on_dialog_updated(dialog_id, "set_dialog_max_unavailable_message_id");
- if (d->max_unavailable_message_id.get() > max_unavailable_message_id.get()) {
+ if (d->max_unavailable_message_id > max_unavailable_message_id) {
d->max_unavailable_message_id = max_unavailable_message_id;
return;
}
@@ -8695,7 +13035,7 @@ void MessagesManager::set_dialog_max_unavailable_message_id(DialogId dialog_id,
d->max_unavailable_message_id = max_unavailable_message_id;
vector<MessageId> message_ids;
- find_old_messages(d->messages, max_unavailable_message_id, message_ids);
+ find_old_messages(d->messages.get(), max_unavailable_message_id, message_ids);
vector<int64> deleted_message_ids;
bool need_update_dialog_pos = false;
@@ -8706,218 +13046,182 @@ void MessagesManager::set_dialog_max_unavailable_message_id(DialogId dialog_id,
auto m = get_message(d, message_id);
CHECK(m != nullptr);
- CHECK(m->message_id.get() <= max_unavailable_message_id.get());
+ CHECK(m->message_id <= max_unavailable_message_id);
CHECK(m->message_id == message_id);
- deleted_message_ids.push_back(message_id.get());
auto p =
delete_message(d, message_id, !from_update, &need_update_dialog_pos, "set_dialog_max_unavailable_message_id");
CHECK(p.get() == m);
+ deleted_message_ids.push_back(p->message_id.get());
}
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "set_dialog_max_unavailable_message_id");
}
- send_update_delete_messages(dialog_id, std::move(deleted_message_ids), !from_update, false);
+ send_update_delete_messages(dialog_id, std::move(deleted_message_ids), !from_update);
if (d->server_unread_count + d->local_unread_count > 0) {
read_history_inbox(dialog_id, max_unavailable_message_id, -1, "set_dialog_max_unavailable_message_id");
}
} else {
- LOG(INFO) << "Receive max unavailable message identifier in unknown " << dialog_id << " from " << source;
+ LOG(INFO) << "Receive max unavailable message in unknown " << dialog_id << " from " << source;
}
}
-tl_object_ptr<td_api::MessageContent> MessagesManager::get_message_content_object(const MessageContent *content,
- int32 message_date,
- bool is_content_secret) const {
- CHECK(content != nullptr);
- switch (content->get_id()) {
- case MessageAnimation::ID: {
- const MessageAnimation *m = static_cast<const MessageAnimation *>(content);
- return make_tl_object<td_api::messageAnimation>(
- td_->animations_manager_->get_animation_object(m->file_id, "get_message_content_object"),
- get_formatted_text_object(m->caption), is_content_secret);
- }
- case MessageAudio::ID: {
- const MessageAudio *m = static_cast<const MessageAudio *>(content);
- return make_tl_object<td_api::messageAudio>(td_->audios_manager_->get_audio_object(m->file_id),
- get_formatted_text_object(m->caption));
- }
- case MessageContact::ID: {
- const MessageContact *m = static_cast<const MessageContact *>(content);
- return make_tl_object<td_api::messageContact>(m->contact.get_contact_object());
- }
- case MessageDocument::ID: {
- const MessageDocument *m = static_cast<const MessageDocument *>(content);
- return make_tl_object<td_api::messageDocument>(td_->documents_manager_->get_document_object(m->file_id),
- get_formatted_text_object(m->caption));
- }
- case MessageGame::ID: {
- const MessageGame *m = static_cast<const MessageGame *>(content);
- return make_tl_object<td_api::messageGame>(m->game.get_game_object(td_));
- }
- case MessageInvoice::ID: {
- const MessageInvoice *m = static_cast<const MessageInvoice *>(content);
- return make_tl_object<td_api::messageInvoice>(
- m->title, m->description, get_photo_object(td_->file_manager_.get(), &m->photo), m->invoice.currency,
- m->total_amount, m->start_parameter, m->invoice.is_test, m->invoice.need_shipping_address,
- m->receipt_message_id.get());
- }
- case MessageLiveLocation::ID: {
- const MessageLiveLocation *m = static_cast<const MessageLiveLocation *>(content);
- auto passed = max(G()->unix_time_cached() - message_date, 0);
- return make_tl_object<td_api::messageLocation>(m->location.get_location_object(), m->period,
- max(0, m->period - passed));
- }
- case MessageLocation::ID: {
- const MessageLocation *m = static_cast<const MessageLocation *>(content);
- return make_tl_object<td_api::messageLocation>(m->location.get_location_object(), 0, 0);
- }
- case MessagePhoto::ID: {
- const MessagePhoto *m = static_cast<const MessagePhoto *>(content);
- return make_tl_object<td_api::messagePhoto>(get_photo_object(td_->file_manager_.get(), &m->photo),
- get_formatted_text_object(m->caption), is_content_secret);
- }
- case MessageSticker::ID: {
- const MessageSticker *m = static_cast<const MessageSticker *>(content);
- return make_tl_object<td_api::messageSticker>(td_->stickers_manager_->get_sticker_object(m->file_id));
- }
- case MessageText::ID: {
- const MessageText *m = static_cast<const MessageText *>(content);
- return make_tl_object<td_api::messageText>(get_formatted_text_object(m->text),
- td_->web_pages_manager_->get_web_page_object(m->web_page_id));
- }
- case MessageUnsupported::ID: {
- return make_tl_object<td_api::messageUnsupported>();
- }
- case MessageVenue::ID: {
- const MessageVenue *m = static_cast<const MessageVenue *>(content);
- return make_tl_object<td_api::messageVenue>(m->venue.get_venue_object());
- }
- case MessageVideo::ID: {
- const MessageVideo *m = static_cast<const MessageVideo *>(content);
- return make_tl_object<td_api::messageVideo>(td_->videos_manager_->get_video_object(m->file_id),
- get_formatted_text_object(m->caption), is_content_secret);
- }
- case MessageVideoNote::ID: {
- const MessageVideoNote *m = static_cast<const MessageVideoNote *>(content);
- return make_tl_object<td_api::messageVideoNote>(td_->video_notes_manager_->get_video_note_object(m->file_id),
- m->is_viewed, is_content_secret);
- }
- case MessageVoiceNote::ID: {
- const MessageVoiceNote *m = static_cast<const MessageVoiceNote *>(content);
- return make_tl_object<td_api::messageVoiceNote>(td_->voice_notes_manager_->get_voice_note_object(m->file_id),
- get_formatted_text_object(m->caption), m->is_listened);
- }
- case MessageChatCreate::ID: {
- const MessageChatCreate *m = static_cast<const MessageChatCreate *>(content);
- return make_tl_object<td_api::messageBasicGroupChatCreate>(
- m->title, td_->contacts_manager_->get_user_ids_object(m->participant_user_ids));
- }
- case MessageChatChangeTitle::ID: {
- const MessageChatChangeTitle *m = static_cast<const MessageChatChangeTitle *>(content);
- return make_tl_object<td_api::messageChatChangeTitle>(m->title);
- }
- case MessageChatChangePhoto::ID: {
- const MessageChatChangePhoto *m = static_cast<const MessageChatChangePhoto *>(content);
- return make_tl_object<td_api::messageChatChangePhoto>(get_photo_object(td_->file_manager_.get(), &m->photo));
- }
- case MessageChatDeletePhoto::ID:
- return make_tl_object<td_api::messageChatDeletePhoto>();
- case MessageChatDeleteHistory::ID:
- return make_tl_object<td_api::messageUnsupported>();
- case MessageChatAddUsers::ID: {
- const MessageChatAddUsers *m = static_cast<const MessageChatAddUsers *>(content);
- return make_tl_object<td_api::messageChatAddMembers>(td_->contacts_manager_->get_user_ids_object(m->user_ids));
- }
- case MessageChatJoinedByLink::ID:
- return make_tl_object<td_api::messageChatJoinByLink>();
- case MessageChatDeleteUser::ID: {
- const MessageChatDeleteUser *m = static_cast<const MessageChatDeleteUser *>(content);
- return make_tl_object<td_api::messageChatDeleteMember>(
- td_->contacts_manager_->get_user_id_object(m->user_id, "messageChatDeleteMember"));
- }
- case MessageChatMigrateTo::ID: {
- const MessageChatMigrateTo *m = static_cast<const MessageChatMigrateTo *>(content);
- return make_tl_object<td_api::messageChatUpgradeTo>(
- td_->contacts_manager_->get_supergroup_id_object(m->migrated_to_channel_id, "messageChatUpgradeTo"));
- }
- case MessageChannelCreate::ID: {
- const MessageChannelCreate *m = static_cast<const MessageChannelCreate *>(content);
- return make_tl_object<td_api::messageSupergroupChatCreate>(m->title);
- }
- case MessageChannelMigrateFrom::ID: {
- const MessageChannelMigrateFrom *m = static_cast<const MessageChannelMigrateFrom *>(content);
- return make_tl_object<td_api::messageChatUpgradeFrom>(
- m->title,
- td_->contacts_manager_->get_basic_group_id_object(m->migrated_from_chat_id, "messageChatUpgradeFrom"));
- }
- case MessagePinMessage::ID: {
- const MessagePinMessage *m = static_cast<const MessagePinMessage *>(content);
- return make_tl_object<td_api::messagePinMessage>(m->message_id.get());
- }
- case MessageGameScore::ID: {
- const MessageGameScore *m = static_cast<const MessageGameScore *>(content);
- return make_tl_object<td_api::messageGameScore>(m->game_message_id.get(), m->game_id, m->score);
- }
- case MessageScreenshotTaken::ID:
- return make_tl_object<td_api::messageScreenshotTaken>();
- case MessageChatSetTtl::ID: {
- const MessageChatSetTtl *m = static_cast<const MessageChatSetTtl *>(content);
- return make_tl_object<td_api::messageChatSetTtl>(m->ttl);
- }
- case MessageCall::ID: {
- const MessageCall *m = static_cast<const MessageCall *>(content);
- return make_tl_object<td_api::messageCall>(get_call_discard_reason_object(m->discard_reason), m->duration);
- }
- case MessagePaymentSuccessful::ID: {
- const MessagePaymentSuccessful *m = static_cast<const MessagePaymentSuccessful *>(content);
- if (td_->auth_manager_->is_bot()) {
- return make_tl_object<td_api::messagePaymentSuccessfulBot>(
- m->invoice_message_id.get(), m->currency, m->total_amount, m->invoice_payload, m->shipping_option_id,
- get_order_info_object(m->order_info), m->telegram_payment_charge_id, m->provider_payment_charge_id);
- } else {
- return make_tl_object<td_api::messagePaymentSuccessful>(m->invoice_message_id.get(), m->currency,
- m->total_amount);
+void MessagesManager::set_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, bool is_from_server,
+ const char *source) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ Dialog *d = get_dialog(dialog_id);
+ if (d == nullptr) {
+ return;
+ }
+
+ if (online_member_count < 0) {
+ LOG(ERROR) << "Receive online_member_count = " << online_member_count << " in " << dialog_id;
+ online_member_count = 0;
+ }
+
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat: {
+ auto participant_count = td_->contacts_manager_->get_chat_participant_count(dialog_id.get_chat_id());
+ if (online_member_count > participant_count) {
+ online_member_count = participant_count;
}
+ break;
}
- case MessageContactRegistered::ID:
- return make_tl_object<td_api::messageContactRegistered>();
- case MessageExpiredPhoto::ID:
- return make_tl_object<td_api::messageExpiredPhoto>();
- case MessageExpiredVideo::ID:
- return make_tl_object<td_api::messageExpiredVideo>();
- case MessageCustomServiceAction::ID: {
- const MessageCustomServiceAction *m = static_cast<const MessageCustomServiceAction *>(content);
- return make_tl_object<td_api::messageCustomServiceAction>(m->message);
- }
- case MessageWebsiteConnected::ID: {
- const MessageWebsiteConnected *m = static_cast<const MessageWebsiteConnected *>(content);
- return make_tl_object<td_api::messageWebsiteConnected>(m->domain_name);
+ case DialogType::Channel: {
+ auto participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id());
+ if (participant_count != 0 && online_member_count > participant_count) {
+ online_member_count = participant_count;
+ }
+ break;
}
default:
- UNREACHABLE();
- return nullptr;
+ break;
+ }
+
+ auto &info = dialog_online_member_counts_[dialog_id];
+ LOG(INFO) << "Change number of online members from " << info.online_member_count << " to " << online_member_count
+ << " in " << dialog_id << " from " << source;
+ bool need_update = d->is_opened && (!info.is_update_sent || info.online_member_count != online_member_count);
+ info.online_member_count = online_member_count;
+ info.update_time = Time::now();
+
+ if (need_update) {
+ info.is_update_sent = true;
+ send_update_chat_online_member_count(dialog_id, online_member_count);
+ }
+ if (d->is_opened) {
+ if (is_from_server) {
+ update_dialog_online_member_count_timeout_.set_timeout_in(dialog_id.get(), ONLINE_MEMBER_COUNT_UPDATE_TIME);
+ } else {
+ update_dialog_online_member_count_timeout_.add_timeout_in(dialog_id.get(), ONLINE_MEMBER_COUNT_UPDATE_TIME);
+ }
+ }
+}
+
+void MessagesManager::on_update_dialog_online_member_count_timeout(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Expired timeout for number of online members in " << dialog_id;
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ if (!d->is_opened) {
+ send_update_chat_online_member_count(dialog_id, 0);
+ return;
+ }
+
+ if (dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(dialog_id)) {
+ auto participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id());
+ if (participant_count == 0 || participant_count >= 195) {
+ td_->create_handler<GetOnlinesQuery>()->send(dialog_id);
+ } else {
+ td_->contacts_manager_->get_channel_participants(dialog_id.get_channel_id(),
+ td_api::make_object<td_api::supergroupMembersFilterRecent>(),
+ string(), 0, 200, 200, Auto());
+ }
+ return;
+ }
+ if (dialog_id.get_type() == DialogType::Chat) {
+ // we need actual online status state, so we need to reget chat participants
+ td_->contacts_manager_->repair_chat_participants(dialog_id.get_chat_id());
+ return;
}
- UNREACHABLE();
- return nullptr;
}
-MessageId MessagesManager::get_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr) {
- int32 constructor_id = message_ptr->get_id();
- switch (constructor_id) {
+void MessagesManager::on_update_viewed_messages_timeout(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Expired timeout for updating of recently viewed messages in " << dialog_id;
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ if (!d->is_opened) {
+ return;
+ }
+
+ auto it = dialog_viewed_messages_.find(dialog_id);
+ if (it == dialog_viewed_messages_.end() || !td_->is_online()) {
+ return;
+ }
+
+ auto &info = it->second;
+ CHECK(info != nullptr);
+ vector<MessageId> reaction_message_ids;
+ vector<MessageId> views_message_ids;
+ vector<MessageId> extended_media_message_ids;
+ for (auto &message_it : info->message_id_to_view_id) {
+ Message *m = get_message_force(d, message_it.first, "on_update_viewed_messages_timeout");
+ CHECK(m != nullptr);
+ CHECK(m->message_id.is_valid());
+ CHECK(m->message_id.is_server());
+ if (need_poll_message_reactions(d, m)) {
+ reaction_message_ids.push_back(m->message_id);
+ }
+ if (m->view_count > 0 && !m->has_get_message_views_query) {
+ m->has_get_message_views_query = true;
+ views_message_ids.push_back(m->message_id);
+ }
+ if (need_poll_message_content_extended_media(m->content.get()) && !m->has_get_extended_media_query) {
+ m->has_get_extended_media_query = true;
+ extended_media_message_ids.push_back(m->message_id);
+ }
+ }
+
+ if (!reaction_message_ids.empty()) {
+ queue_message_reactions_reload(dialog_id, reaction_message_ids);
+ }
+ if (!views_message_ids.empty()) {
+ td_->create_handler<GetMessagesViewsQuery>()->send(dialog_id, std::move(views_message_ids), false);
+ }
+ if (!extended_media_message_ids.empty()) {
+ td_->create_handler<GetExtendedMediaQuery>()->send(dialog_id, std::move(extended_media_message_ids));
+ }
+
+ update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD);
+}
+
+MessageId MessagesManager::get_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr, bool is_scheduled) {
+ switch (message_ptr->get_id()) {
case telegram_api::messageEmpty::ID: {
auto message = static_cast<const telegram_api::messageEmpty *>(message_ptr.get());
- return MessageId(ServerMessageId(message->id_));
+ return is_scheduled ? MessageId() : MessageId(ServerMessageId(message->id_));
}
case telegram_api::message::ID: {
auto message = static_cast<const telegram_api::message *>(message_ptr.get());
- return MessageId(ServerMessageId(message->id_));
+ return is_scheduled ? MessageId(ScheduledServerMessageId(message->id_), message->date_)
+ : MessageId(ServerMessageId(message->id_));
}
case telegram_api::messageService::ID: {
auto message = static_cast<const telegram_api::messageService *>(message_ptr.get());
- return MessageId(ServerMessageId(message->id_));
+ return is_scheduled ? MessageId(ScheduledServerMessageId(message->id_), message->date_)
+ : MessageId(ServerMessageId(message->id_));
}
default:
UNREACHABLE();
@@ -8925,57 +13229,34 @@ MessageId MessagesManager::get_message_id(const tl_object_ptr<telegram_api::Mess
}
}
-DialogId MessagesManager::get_message_dialog_id(const tl_object_ptr<telegram_api::Message> &message_ptr) const {
- return get_full_message_id(message_ptr).get_dialog_id();
-}
-
-FullMessageId MessagesManager::get_full_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr) const {
- int32 constructor_id = message_ptr->get_id();
- DialogId dialog_id;
- MessageId message_id;
- UserId sender_user_id;
- switch (constructor_id) {
+DialogId MessagesManager::get_message_dialog_id(const tl_object_ptr<telegram_api::Message> &message_ptr) {
+ CHECK(message_ptr != nullptr);
+ switch (message_ptr->get_id()) {
case telegram_api::messageEmpty::ID: {
auto message = static_cast<const telegram_api::messageEmpty *>(message_ptr.get());
- LOG(INFO) << "Receive MessageEmpty";
- message_id = MessageId(ServerMessageId(message->id_));
- break;
+ return message->peer_id_ == nullptr ? DialogId() : DialogId(message->peer_id_);
}
case telegram_api::message::ID: {
auto message = static_cast<const telegram_api::message *>(message_ptr.get());
- dialog_id = DialogId(message->to_id_);
- message_id = MessageId(ServerMessageId(message->id_));
- if (message->flags_ & MESSAGE_FLAG_HAS_FROM_ID) {
- sender_user_id = UserId(message->from_id_);
- }
- break;
+ return DialogId(message->peer_id_);
}
case telegram_api::messageService::ID: {
auto message = static_cast<const telegram_api::messageService *>(message_ptr.get());
- dialog_id = DialogId(message->to_id_);
- message_id = MessageId(ServerMessageId(message->id_));
- if (message->flags_ & MESSAGE_FLAG_HAS_FROM_ID) {
- sender_user_id = UserId(message->from_id_);
- }
- break;
+ return DialogId(message->peer_id_);
}
default:
UNREACHABLE();
- break;
+ return DialogId();
}
+}
- UserId my_id = td_->contacts_manager_->get_my_id("get_full_message_id");
- DialogId my_dialog_id = DialogId(my_id);
- if (dialog_id == my_dialog_id) {
- LOG_IF(ERROR, !sender_user_id.is_valid()) << "Receive invalid " << sender_user_id;
- dialog_id = DialogId(sender_user_id);
- }
- return {dialog_id, message_id};
+FullMessageId MessagesManager::get_full_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr,
+ bool is_scheduled) {
+ return {get_message_dialog_id(message_ptr), get_message_id(message_ptr, is_scheduled)};
}
int32 MessagesManager::get_message_date(const tl_object_ptr<telegram_api::Message> &message_ptr) {
- int32 constructor_id = message_ptr->get_id();
- switch (constructor_id) {
+ switch (message_ptr->get_id()) {
case telegram_api::messageEmpty::ID:
return 0;
case telegram_api::message::ID: {
@@ -8992,86 +13273,132 @@ int32 MessagesManager::get_message_date(const tl_object_ptr<telegram_api::Messag
}
}
-void MessagesManager::ttl_read_history_inbox(DialogId dialog_id, MessageId from_message_id, MessageId till_message_id,
- double timestamp) {
- auto *d = get_dialog(dialog_id);
- CHECK(d != nullptr);
- auto now = Time::now();
- for (auto it = MessagesIterator(d, from_message_id); *it && (*it)->message_id.get() >= till_message_id.get(); --it) {
- auto *message = *it;
- if (!message->is_outgoing && !message->message_id.is_yet_unsent()) {
- ttl_on_view(d, message, timestamp, now);
- }
- }
+void MessagesManager::ttl_read_history(Dialog *d, bool is_outgoing, MessageId from_message_id,
+ MessageId till_message_id, double view_date) {
+ CHECK(!from_message_id.is_scheduled());
+ CHECK(!till_message_id.is_scheduled());
+
+ // TODO: protect with log event
+ suffix_load_till_message_id(d, till_message_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id, is_outgoing,
+ from_message_id, till_message_id, view_date](Result<Unit>) {
+ send_closure(actor_id, &MessagesManager::ttl_read_history_impl, dialog_id, is_outgoing,
+ from_message_id, till_message_id, view_date);
+ }));
}
-void MessagesManager::ttl_read_history_outbox(DialogId dialog_id, MessageId from_message_id, MessageId till_message_id,
- double timestamp) {
+
+void MessagesManager::ttl_read_history_impl(DialogId dialog_id, bool is_outgoing, MessageId from_message_id,
+ MessageId till_message_id, double view_date) {
+ CHECK(!from_message_id.is_scheduled());
+ CHECK(!till_message_id.is_scheduled());
+
auto *d = get_dialog(dialog_id);
CHECK(d != nullptr);
auto now = Time::now();
- for (auto it = MessagesIterator(d, from_message_id); *it && (*it)->message_id.get() >= till_message_id.get(); --it) {
- auto *message = *it;
- if (message->is_outgoing && !message->message_id.is_yet_unsent()) {
- ttl_on_view(d, message, timestamp, now);
+ for (auto it = MessagesIterator(d, from_message_id); *it && (*it)->message_id >= till_message_id; --it) {
+ auto *m = *it;
+ if (m->is_outgoing == is_outgoing) {
+ ttl_on_view(d, m, view_date, now);
}
}
}
-void MessagesManager::ttl_on_view(const Dialog *d, Message *message, double view_date, double now) {
- if (message->ttl > 0 && message->ttl_expires_at == 0 && !message->is_content_secret) {
- message->ttl_expires_at = message->ttl + view_date;
- ttl_register_message(d->dialog_id, message, now);
- on_message_changed(d, message, "ttl_on_view");
+void MessagesManager::ttl_on_view(const Dialog *d, Message *m, double view_date, double now) {
+ if (m->ttl > 0 && m->ttl_expires_at == 0 && !m->message_id.is_scheduled() && !m->message_id.is_yet_unsent() &&
+ !m->is_failed_to_send && !m->is_content_secret) {
+ m->ttl_expires_at = m->ttl + view_date;
+ ttl_register_message(d->dialog_id, m, now);
+ on_message_changed(d, m, true, "ttl_on_view");
}
}
-bool MessagesManager::ttl_on_open(Dialog *d, Message *message, double now, bool is_local_read) {
- if (message->ttl > 0 && message->ttl_expires_at == 0) {
+bool MessagesManager::ttl_on_open(Dialog *d, Message *m, double now, bool is_local_read) {
+ CHECK(!m->message_id.is_scheduled());
+ if (m->ttl > 0 && m->ttl_expires_at == 0) {
if (!is_local_read && d->dialog_id.get_type() != DialogType::SecretChat) {
- on_message_ttl_expired(d, message);
+ on_message_ttl_expired(d, m);
} else {
- message->ttl_expires_at = message->ttl + now;
- ttl_register_message(d->dialog_id, message, now);
+ m->ttl_expires_at = m->ttl + now;
+ ttl_register_message(d->dialog_id, m, now);
}
return true;
}
return false;
}
-void MessagesManager::ttl_register_message(DialogId dialog_id, const Message *message, double now) {
- if (message->ttl_expires_at == 0) {
- return;
- }
- auto it_flag = ttl_nodes_.insert(TtlNode(dialog_id, message->message_id));
+void MessagesManager::ttl_register_message(DialogId dialog_id, const Message *m, double now) {
+ CHECK(m != nullptr);
+ CHECK(m->ttl_expires_at != 0);
+ CHECK(!m->message_id.is_scheduled());
+
+ auto it_flag = ttl_nodes_.emplace(dialog_id, m->message_id, false);
CHECK(it_flag.second);
auto it = it_flag.first;
- ttl_heap_.insert(message->ttl_expires_at, it->as_heap_node());
+ ttl_heap_.insert(m->ttl_expires_at, it->as_heap_node());
ttl_update_timeout(now);
}
-void MessagesManager::ttl_unregister_message(DialogId dialog_id, const Message *message, double now) {
- if (message->ttl_expires_at == 0) {
+void MessagesManager::ttl_period_register_message(DialogId dialog_id, const Message *m, double server_time) {
+ CHECK(m != nullptr);
+ CHECK(m->ttl_period != 0);
+ CHECK(!m->message_id.is_scheduled());
+
+ auto it_flag = ttl_nodes_.emplace(dialog_id, m->message_id, true);
+ CHECK(it_flag.second);
+ auto it = it_flag.first;
+
+ auto now = Time::now();
+ ttl_heap_.insert(now + (m->date + m->ttl_period - server_time), it->as_heap_node());
+ ttl_update_timeout(now);
+}
+
+void MessagesManager::ttl_unregister_message(DialogId dialog_id, const Message *m, const char *source) {
+ if (m->ttl_expires_at == 0) {
+ return;
+ }
+ CHECK(!m->message_id.is_scheduled());
+
+ auto it = ttl_nodes_.find(TtlNode(dialog_id, m->message_id, false));
+
+ // expect m->ttl == 0, but m->ttl_expires_at > 0 from binlog
+ LOG_CHECK(it != ttl_nodes_.end()) << dialog_id << " " << m->message_id << " " << source << " " << G()->close_flag()
+ << " " << m->ttl << " " << m->ttl_expires_at << " " << Time::now() << " "
+ << m->from_database;
+
+ auto *heap_node = it->as_heap_node();
+ if (heap_node->in_heap()) {
+ ttl_heap_.erase(heap_node);
+ }
+ ttl_nodes_.erase(it);
+ ttl_update_timeout(Time::now());
+}
+
+void MessagesManager::ttl_period_unregister_message(DialogId dialog_id, const Message *m) {
+ if (m->ttl_period == 0) {
return;
}
+ CHECK(!m->message_id.is_scheduled());
+
+ auto it = ttl_nodes_.find(TtlNode(dialog_id, m->message_id, true));
- TtlNode ttl_node(dialog_id, message->message_id);
- auto it = ttl_nodes_.find(ttl_node);
CHECK(it != ttl_nodes_.end());
+
auto *heap_node = it->as_heap_node();
if (heap_node->in_heap()) {
ttl_heap_.erase(heap_node);
}
ttl_nodes_.erase(it);
- ttl_update_timeout(now);
+ ttl_update_timeout(Time::now());
}
void MessagesManager::ttl_loop(double now) {
- std::unordered_map<DialogId, std::vector<MessageId>, DialogIdHash> to_delete;
+ FlatHashMap<DialogId, std::vector<MessageId>, DialogIdHash> to_delete;
while (!ttl_heap_.empty() && ttl_heap_.top_key() < now) {
- auto full_message_id = TtlNode::from_heap_node(ttl_heap_.pop())->full_message_id;
+ TtlNode *ttl_node = TtlNode::from_heap_node(ttl_heap_.pop());
+ auto full_message_id = ttl_node->full_message_id_;
auto dialog_id = full_message_id.get_dialog_id();
- if (dialog_id.get_type() == DialogType::SecretChat) {
+ if (dialog_id.get_type() == DialogType::SecretChat || ttl_node->by_ttl_period_) {
to_delete[dialog_id].push_back(full_message_id.get_message_id());
} else {
auto d = get_dialog(dialog_id);
@@ -9079,11 +13406,11 @@ void MessagesManager::ttl_loop(double now) {
auto m = get_message(d, full_message_id.get_message_id());
CHECK(m != nullptr);
on_message_ttl_expired(d, m);
- on_message_changed(d, m, "ttl_loop");
+ on_message_changed(d, m, true, "ttl_loop");
}
}
for (auto &it : to_delete) {
- delete_dialog_messages_from_updates(it.first, it.second);
+ delete_dialog_messages(it.first, it.second, false, "ttl_loop");
}
ttl_update_timeout(now);
}
@@ -9099,50 +13426,56 @@ void MessagesManager::ttl_update_timeout(double now) {
ttl_slot_.set_timeout_in(ttl_heap_.top_key() - now);
}
-void MessagesManager::on_message_ttl_expired(Dialog *d, Message *message) {
+void MessagesManager::on_message_ttl_expired(Dialog *d, Message *m) {
CHECK(d != nullptr);
- CHECK(message != nullptr);
- CHECK(message->ttl > 0);
+ CHECK(m != nullptr);
+ CHECK(m->ttl > 0);
CHECK(d->dialog_id.get_type() != DialogType::SecretChat);
- ttl_unregister_message(d->dialog_id, message, Time::now());
- on_message_ttl_expired_impl(d, message);
- send_update_message_content(d->dialog_id, message->message_id, message->content.get(), message->date,
- message->is_content_secret, "on_message_ttl_expired");
+ ttl_unregister_message(d->dialog_id, m, "on_message_ttl_expired");
+ unregister_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_ttl_expired");
+ remove_message_file_sources(d->dialog_id, m);
+ on_message_ttl_expired_impl(d, m);
+ register_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_ttl_expired");
+ send_update_message_content(d, m, true, "on_message_ttl_expired");
+ // the caller must call on_message_changed
}
-void MessagesManager::on_message_ttl_expired_impl(Dialog *d, Message *message) {
+void MessagesManager::on_message_ttl_expired_impl(Dialog *d, Message *m) {
CHECK(d != nullptr);
- CHECK(message != nullptr);
- CHECK(message->ttl > 0);
+ CHECK(m != nullptr);
+ CHECK(m->message_id.is_valid());
+ CHECK(!m->message_id.is_yet_unsent());
+ CHECK(m->ttl > 0);
CHECK(d->dialog_id.get_type() != DialogType::SecretChat);
- delete_message_files(message);
- switch (message->content->get_id()) {
- case MessagePhoto::ID:
- message->content = make_unique<MessageExpiredPhoto>();
- break;
- case MessageVideo::ID:
- message->content = make_unique<MessageExpiredVideo>();
- break;
- default:
- UNREACHABLE();
- }
- message->ttl = 0;
- message->ttl_expires_at = 0;
- if (message->reply_markup != nullptr) {
- if (message->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
+ delete_message_files(d->dialog_id, m);
+ update_expired_message_content(m->content);
+ m->ttl = 0;
+ m->ttl_expires_at = 0;
+ if (m->reply_markup != nullptr) {
+ if (m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
if (!td_->auth_manager_->is_bot()) {
- if (d->reply_markup_message_id == message->message_id) {
+ if (d->reply_markup_message_id == m->message_id) {
set_dialog_reply_markup(d, MessageId());
}
}
- message->had_reply_markup = true;
+ m->had_reply_markup = true;
}
- message->reply_markup = nullptr;
+ m->reply_markup = nullptr;
}
- update_message_contains_unread_mention(d, message, false, "on_message_ttl_expired_impl");
- message->contains_mention = false;
- message->reply_to_message_id = MessageId();
- message->is_content_secret = false;
+ remove_message_notification_id(d, m, true, true);
+ update_message_contains_unread_mention(d, m, false, "on_message_ttl_expired_impl");
+ remove_message_unread_reactions(d, m, "on_message_ttl_expired_impl");
+ unregister_message_reply(d->dialog_id, m);
+ m->noforwards = false;
+ m->contains_mention = false;
+ m->reply_to_message_id = MessageId();
+ m->reply_to_random_id = 0;
+ m->max_reply_media_timestamp = -1;
+ m->reply_in_dialog_id = DialogId();
+ m->top_thread_message_id = MessageId();
+ m->is_topic_message = false;
+ m->linked_top_thread_message_id = MessageId();
+ m->is_content_secret = false;
}
void MessagesManager::loop() {
@@ -9154,33 +13487,428 @@ void MessagesManager::loop() {
}
}
+class MessagesManager::DialogFiltersLogEvent {
+ public:
+ int32 server_main_dialog_list_position = 0;
+ int32 main_dialog_list_position = 0;
+ int32 updated_date = 0;
+ const vector<unique_ptr<DialogFilter>> *server_dialog_filters_in;
+ const vector<unique_ptr<DialogFilter>> *dialog_filters_in;
+ vector<unique_ptr<DialogFilter>> server_dialog_filters_out;
+ vector<unique_ptr<DialogFilter>> dialog_filters_out;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ bool has_server_dialog_filters = !server_dialog_filters_in->empty();
+ bool has_dialog_filters = !dialog_filters_in->empty();
+ bool has_server_main_dialog_list_position = server_main_dialog_list_position != 0;
+ bool has_main_dialog_list_position = main_dialog_list_position != 0;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_server_dialog_filters);
+ STORE_FLAG(has_dialog_filters);
+ STORE_FLAG(has_server_main_dialog_list_position);
+ STORE_FLAG(has_main_dialog_list_position);
+ END_STORE_FLAGS();
+ td::store(updated_date, storer);
+ if (has_server_dialog_filters) {
+ td::store(*server_dialog_filters_in, storer);
+ }
+ if (has_dialog_filters) {
+ td::store(*dialog_filters_in, storer);
+ }
+ if (has_server_main_dialog_list_position) {
+ td::store(server_main_dialog_list_position, storer);
+ }
+ if (has_main_dialog_list_position) {
+ td::store(main_dialog_list_position, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ bool has_server_dialog_filters = true;
+ bool has_dialog_filters = true;
+ bool has_server_main_dialog_list_position = false;
+ bool has_main_dialog_list_position = false;
+ if (parser.version() >= static_cast<int32>(Version::AddMainDialogListPosition)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_server_dialog_filters);
+ PARSE_FLAG(has_dialog_filters);
+ PARSE_FLAG(has_server_main_dialog_list_position);
+ PARSE_FLAG(has_main_dialog_list_position);
+ END_PARSE_FLAGS();
+ }
+ td::parse(updated_date, parser);
+ if (has_server_dialog_filters) {
+ td::parse(server_dialog_filters_out, parser);
+ }
+ if (has_dialog_filters) {
+ td::parse(dialog_filters_out, parser);
+ }
+ if (has_server_main_dialog_list_position) {
+ td::parse(server_main_dialog_list_position, parser);
+ }
+ if (has_main_dialog_list_position) {
+ td::parse(main_dialog_list_position, parser);
+ }
+ }
+};
+
void MessagesManager::tear_down() {
parent_.reset();
+
+ LOG(DEBUG) << "Have " << dialogs_.calc_size() << " chats with " << added_message_count_ << " messages to free";
+}
+
+void MessagesManager::hangup() {
+ postponed_channel_updates_.clear();
+
+ if (!G()->parameters().use_message_db) {
+ while (!being_uploaded_files_.empty()) {
+ auto it = being_uploaded_files_.begin();
+ auto full_message_id = it->second.first;
+ being_uploaded_files_.erase(it);
+ if (full_message_id.get_message_id().is_yet_unsent()) {
+ fail_send_message(full_message_id, Global::request_aborted_error());
+ }
+ }
+ while (!being_uploaded_thumbnails_.empty()) {
+ auto it = being_uploaded_thumbnails_.begin();
+ auto full_message_id = it->second.full_message_id;
+ being_uploaded_thumbnails_.erase(it);
+ if (full_message_id.get_message_id().is_yet_unsent()) {
+ fail_send_message(full_message_id, Global::request_aborted_error());
+ }
+ }
+ while (!being_loaded_secret_thumbnails_.empty()) {
+ auto it = being_loaded_secret_thumbnails_.begin();
+ auto full_message_id = it->second.full_message_id;
+ being_loaded_secret_thumbnails_.erase(it);
+ if (full_message_id.get_message_id().is_yet_unsent()) {
+ fail_send_message(full_message_id, Global::request_aborted_error());
+ }
+ }
+ while (!being_sent_messages_.empty()) {
+ on_send_message_fail(being_sent_messages_.begin()->first, Global::request_aborted_error());
+ }
+ }
+
+ fail_promises(load_active_live_location_messages_queries_, Global::request_aborted_error());
+ fail_promises(dialog_filter_reload_queries_, Global::request_aborted_error());
+ auto fail_promise_map = [](auto &queries) {
+ while (!queries.empty()) {
+ auto it = queries.begin();
+ auto promises = std::move(it->second);
+ queries.erase(it);
+ fail_promises(promises, Global::request_aborted_error());
+ }
+ };
+ fail_promise_map(get_dialog_queries_);
+ fail_promise_map(load_scheduled_messages_from_database_queries_);
+ fail_promise_map(run_after_get_channel_difference_);
+ fail_promise_map(search_public_dialogs_queries_);
+ while (!pending_channel_on_get_dialogs_.empty()) {
+ auto it = pending_channel_on_get_dialogs_.begin();
+ auto promise = std::move(it->second.promise);
+ pending_channel_on_get_dialogs_.erase(it);
+ promise.set_error(Global::request_aborted_error());
+ }
+ while (!get_dialogs_tasks_.empty()) {
+ auto it = get_dialogs_tasks_.begin();
+ auto promise = std::move(it->second.promise);
+ get_dialogs_tasks_.erase(it);
+ promise.set_error(Global::request_aborted_error());
+ }
+
+ stop();
}
void MessagesManager::start_up() {
+ init();
+}
+
+void MessagesManager::create_folders() {
+ LOG(INFO) << "Create folders";
+ dialog_folders_[FolderId::main()].folder_id = FolderId::main();
+ dialog_folders_[FolderId::archive()].folder_id = FolderId::archive();
+
+ add_dialog_list(DialogListId(FolderId::main()));
+ add_dialog_list(DialogListId(FolderId::archive()));
+}
+
+void MessagesManager::init() {
+ if (is_inited_) {
+ return;
+ }
+ is_inited_ = true;
+
+ td_->notification_settings_manager_->init(); // load scope notification settings
+ init_stickers_manager(td_); // load available reactions
+
always_wait_for_mailbox();
+ start_time_ = Time::now();
+ last_channel_pts_jump_warning_time_ = start_time_ - 3600;
+
+ bool is_authorized = td_->auth_manager_->is_authorized();
+ bool was_authorized_user = td_->auth_manager_->was_authorized() && !td_->auth_manager_->is_bot();
+ if (was_authorized_user) {
+ create_folders(); // ensure that Main and Archive dialog lists are created
+ }
+ if (is_authorized && td_->auth_manager_->is_bot()) {
+ disable_get_dialog_filter_ = true;
+ }
+ authorization_date_ = td_->option_manager_->get_option_integer("authorization_date");
+
+ if (was_authorized_user) {
+ auto dialog_filters = G()->td_db()->get_binlog_pmc()->get("dialog_filters");
+ if (!dialog_filters.empty()) {
+ DialogFiltersLogEvent log_event;
+ if (log_event_parse(log_event, dialog_filters).is_ok()) {
+ server_main_dialog_list_position_ = log_event.server_main_dialog_list_position;
+ main_dialog_list_position_ = log_event.main_dialog_list_position;
+ if (!td_->option_manager_->get_option_boolean("is_premium") &&
+ (server_main_dialog_list_position_ != 0 || main_dialog_list_position_ != 0)) {
+ LOG(INFO) << "Ignore main chat list position " << server_main_dialog_list_position_ << '/'
+ << main_dialog_list_position_;
+ server_main_dialog_list_position_ = 0;
+ main_dialog_list_position_ = 0;
+ }
+
+ dialog_filters_updated_date_ = G()->ignore_background_updates() ? 0 : log_event.updated_date;
+ std::unordered_set<DialogFilterId, DialogFilterIdHash> server_dialog_filter_ids;
+ for (auto &dialog_filter : log_event.server_dialog_filters_out) {
+ if (dialog_filter->dialog_filter_id.is_valid() &&
+ server_dialog_filter_ids.insert(dialog_filter->dialog_filter_id).second) {
+ server_dialog_filters_.push_back(std::move(dialog_filter));
+ }
+ }
+ for (auto &dialog_filter : log_event.dialog_filters_out) {
+ add_dialog_filter(std::move(dialog_filter), false, "binlog");
+ }
+ LOG(INFO) << "Loaded server chat filters "
+ << get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_)
+ << " and local chat filters " << get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_);
+ } else {
+ LOG(ERROR) << "Failed to parse chat filters from binlog";
+ }
+ }
+ send_update_chat_filters(); // always send updateChatFilters
+ }
+
+ if (G()->parameters().use_message_db && was_authorized_user) {
+ // erase old keys
+ G()->td_db()->get_binlog_pmc()->erase("last_server_dialog_date");
+ G()->td_db()->get_binlog_pmc()->erase("unread_message_count");
+ G()->td_db()->get_binlog_pmc()->erase("unread_dialog_count");
+
+ auto last_database_server_dialog_dates = G()->td_db()->get_binlog_pmc()->prefix_get("last_server_dialog_date");
+ for (auto &it : last_database_server_dialog_dates) {
+ auto r_folder_id = to_integer_safe<int32>(it.first);
+ if (r_folder_id.is_error()) {
+ LOG(ERROR) << "Can't parse folder ID from " << it.first;
+ continue;
+ }
+
+ string order_str;
+ string dialog_id_str;
+ std::tie(order_str, dialog_id_str) = split(it.second);
+
+ auto r_order = to_integer_safe<int64>(order_str);
+ auto r_dialog_id = to_integer_safe<int64>(dialog_id_str);
+ if (r_order.is_error() || r_dialog_id.is_error()) {
+ LOG(ERROR) << "Can't parse " << it.second;
+ } else {
+ FolderId folder_id(r_folder_id.ok());
+ auto *folder = get_dialog_folder(folder_id);
+ CHECK(folder != nullptr);
+ DialogDate dialog_date(r_order.ok(), DialogId(r_dialog_id.ok()));
+ if (dialog_date.get_date() == 0 && dialog_date != MAX_DIALOG_DATE) {
+ LOG(ERROR) << "Ignore incorrect last database server dialog date " << dialog_date << " in " << folder_id;
+ } else {
+ if (folder->last_database_server_dialog_date_ < dialog_date) {
+ folder->last_database_server_dialog_date_ = dialog_date;
+ }
+ LOG(INFO) << "Loaded last_database_server_dialog_date_ " << folder->last_database_server_dialog_date_
+ << " in " << folder_id;
+ }
+ }
+ }
+
+ auto sponsored_dialog_id_string = G()->td_db()->get_binlog_pmc()->get("sponsored_dialog_id");
+ if (!sponsored_dialog_id_string.empty()) {
+ auto dialog_id_source = split(Slice(sponsored_dialog_id_string));
+ auto r_dialog_id = to_integer_safe<int64>(dialog_id_source.first);
+ auto r_source = DialogSource::unserialize(dialog_id_source.second);
+ if (r_dialog_id.is_error() || r_source.is_error()) {
+ LOG(ERROR) << "Can't parse " << sponsored_dialog_id_string;
+ } else {
+ DialogId dialog_id(r_dialog_id.ok());
+
+ const Dialog *d = get_dialog_force(dialog_id, "init");
+ if (d != nullptr) {
+ LOG(INFO) << "Loaded sponsored " << dialog_id;
+ add_sponsored_dialog(d, r_source.move_as_ok());
+ } else {
+ LOG(ERROR) << "Can't load " << dialog_id;
+ }
+ }
+ }
+
+ auto pinned_dialog_ids = G()->td_db()->get_binlog_pmc()->prefix_get("pinned_dialog_ids");
+ for (auto &it : pinned_dialog_ids) {
+ auto r_folder_id = to_integer_safe<int32>(it.first);
+ if (r_folder_id.is_error()) {
+ LOG(ERROR) << "Can't parse folder ID from " << it.first;
+ continue;
+ }
+ FolderId folder_id(r_folder_id.ok());
+
+ auto r_dialog_ids = transform(full_split(Slice(it.second), ','), [](Slice str) -> Result<DialogId> {
+ TRY_RESULT(dialog_id_int, to_integer_safe<int64>(str));
+ DialogId dialog_id(dialog_id_int);
+ if (!dialog_id.is_valid()) {
+ return Status::Error("Have invalid dialog ID");
+ }
+ return dialog_id;
+ });
+ if (std::any_of(r_dialog_ids.begin(), r_dialog_ids.end(),
+ [](auto &r_dialog_id) { return r_dialog_id.is_error(); })) {
+ LOG(ERROR) << "Can't parse " << it.second;
+ reload_pinned_dialogs(DialogListId(folder_id), Auto());
+ } else {
+ auto *list = get_dialog_list(DialogListId(folder_id));
+ CHECK(list != nullptr);
+ CHECK(list->pinned_dialogs_.empty());
+ for (auto &r_dialog_id : reversed(r_dialog_ids)) {
+ auto dialog_id = r_dialog_id.move_as_ok();
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Loaded " << dialog_id << " as a pinned dialog";
+ continue;
+ }
+ auto order = get_next_pinned_dialog_order();
+ list->pinned_dialogs_.emplace_back(order, dialog_id);
+ list->pinned_dialog_id_orders_.emplace(dialog_id, order);
+ }
+ std::reverse(list->pinned_dialogs_.begin(), list->pinned_dialogs_.end());
+ list->are_pinned_dialogs_inited_ = true;
+ update_list_last_pinned_dialog_date(*list);
+
+ LOG(INFO) << "Loaded pinned chats " << list->pinned_dialogs_ << " in " << folder_id;
+ }
+ }
+
+ auto unread_message_counts = G()->td_db()->get_binlog_pmc()->prefix_get("unread_message_count");
+ for (auto &it : unread_message_counts) {
+ auto r_dialog_list_id = to_integer_safe<int64>(it.first);
+ if (r_dialog_list_id.is_error()) {
+ LOG(ERROR) << "Can't parse dialog list ID from " << it.first;
+ continue;
+ }
+ string total_count;
+ string muted_count;
+ std::tie(total_count, muted_count) = split(it.second);
+
+ auto r_total_count = to_integer_safe<int32>(total_count);
+ auto r_muted_count = to_integer_safe<int32>(muted_count);
+ if (r_total_count.is_error() || r_muted_count.is_error()) {
+ LOG(ERROR) << "Can't parse " << it.second;
+ } else {
+ DialogListId dialog_list_id(r_dialog_list_id.ok());
+ auto *list = get_dialog_list(dialog_list_id);
+ if (list != nullptr) {
+ list->unread_message_total_count_ = r_total_count.ok();
+ list->unread_message_muted_count_ = r_muted_count.ok();
+ list->is_message_unread_count_inited_ = true;
+ send_update_unread_message_count(*list, DialogId(), true, "load unread_message_count", true);
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase("unread_message_count" + it.first);
+ }
+ }
+ }
+
+ auto unread_dialog_counts = G()->td_db()->get_binlog_pmc()->prefix_get("unread_dialog_count");
+ for (auto &it : unread_dialog_counts) {
+ auto r_dialog_list_id = to_integer_safe<int64>(it.first);
+ if (r_dialog_list_id.is_error()) {
+ LOG(ERROR) << "Can't parse dialog list ID from " << it.first;
+ continue;
+ }
+
+ auto counts = transform(full_split(Slice(it.second)), [](Slice str) { return to_integer_safe<int32>(str); });
+ if ((counts.size() != 4 && counts.size() != 6) ||
+ std::any_of(counts.begin(), counts.end(), [](auto &c) { return c.is_error(); })) {
+ LOG(ERROR) << "Can't parse " << it.second;
+ } else {
+ DialogListId dialog_list_id(r_dialog_list_id.ok());
+ auto *list = get_dialog_list(dialog_list_id);
+ if (list != nullptr) {
+ list->unread_dialog_total_count_ = counts[0].ok();
+ list->unread_dialog_muted_count_ = counts[1].ok();
+ list->unread_dialog_marked_count_ = counts[2].ok();
+ list->unread_dialog_muted_marked_count_ = counts[3].ok();
+ if (counts.size() == 6) {
+ list->server_dialog_total_count_ = counts[4].ok();
+ list->secret_chat_total_count_ = counts[5].ok();
+ }
+ if (list->server_dialog_total_count_ == -1) {
+ repair_server_dialog_total_count(dialog_list_id);
+ }
+ if (list->secret_chat_total_count_ == -1) {
+ repair_secret_chat_total_count(dialog_list_id);
+ }
+ list->is_dialog_unread_count_inited_ = true;
+ send_update_unread_chat_count(*list, DialogId(), true, "load unread_dialog_count", true);
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase("unread_dialog_count" + it.first);
+ }
+ }
+ }
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase_by_prefix("pinned_dialog_ids");
+ G()->td_db()->get_binlog_pmc()->erase_by_prefix("last_server_dialog_date");
+ G()->td_db()->get_binlog_pmc()->erase_by_prefix("unread_message_count");
+ G()->td_db()->get_binlog_pmc()->erase_by_prefix("unread_dialog_count");
+ G()->td_db()->get_binlog_pmc()->erase("sponsored_dialog_id");
+ }
+ G()->td_db()->get_binlog_pmc()->erase("dialog_pinned_current_order");
+
if (G()->parameters().use_message_db) {
ttl_db_loop_start(G()->server_time());
}
+
load_calls_db_state();
- vector<NotificationSettingsScope> scopes{NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS,
- NOTIFICATION_SETTINGS_FOR_GROUP_CHATS, NOTIFICATION_SETTINGS_FOR_ALL_CHATS};
- for (auto scope : scopes) {
- auto notification_settings_string =
- G()->td_db()->get_binlog_pmc()->get(get_notification_settings_scope_database_key(scope));
- if (!notification_settings_string.empty()) {
- NotificationSettings notification_settings;
- log_event_parse(notification_settings, notification_settings_string).ensure();
- if (!notification_settings.is_synchronized) {
+ if (was_authorized_user && is_authorized) {
+ if (need_synchronize_dialog_filters()) {
+ reload_dialog_filters();
+ } else {
+ auto cache_time = get_dialog_filters_cache_time();
+ schedule_dialog_filters_reload(cache_time - max(0, G()->unix_time() - dialog_filters_updated_date_));
+ }
+ }
+
+ auto auth_notification_ids_string = G()->td_db()->get_binlog_pmc()->get("auth_notification_ids");
+ if (!auth_notification_ids_string.empty()) {
+ VLOG(notifications) << "Loaded auth_notification_ids = " << auth_notification_ids_string;
+ auto ids = full_split(auth_notification_ids_string, ',');
+ CHECK(ids.size() % 2 == 0);
+ bool is_changed = false;
+ auto min_date = G()->unix_time() - AUTH_NOTIFICATION_ID_CACHE_TIME;
+ for (size_t i = 0; i < ids.size(); i += 2) {
+ auto date = to_integer_safe<int32>(ids[i + 1]).ok();
+ if (date < min_date || ids[i].empty()) {
+ is_changed = true;
continue;
}
-
- NotificationSettings *current_settings = get_notification_settings(scope, true);
- CHECK(current_settings != nullptr);
- update_notification_settings(scope, current_settings, notification_settings);
+ if (auth_notification_id_date_.size() == MAX_SAVED_AUTH_NOTIFICATION_IDS) {
+ is_changed = true;
+ break;
+ }
+ auth_notification_id_date_.emplace(std::move(ids[i]), date);
+ }
+ if (is_changed) {
+ save_auth_notification_ids();
}
}
@@ -9242,7 +13970,7 @@ void MessagesManager::start_up() {
} else if (begins_with(log_string, " local message ")) {
log_string.remove_prefix(15);
} else {
- LOG(ERROR) << "Message id expected, found " << log_string;
+ LOG(ERROR) << "Message identifier expected, found " << log_string;
continue;
}
@@ -9254,9 +13982,9 @@ void MessagesManager::start_up() {
}
log_string.remove_prefix(log_string.find(' ') + 1);
- auto message_id = MessageId((static_cast<int64>(server_message_id) << MessageId::SERVER_ID_SHIFT) + add);
+ auto message_id = MessageId(MessageId(ServerMessageId(server_message_id)).get() + add);
- int32 content_type = to_integer<int32>(log_string);
+ auto content_type = log_string.substr(0, log_string.find(' '));
log_string.remove_prefix(log_string.find(' ') + 1);
auto read_bool = [](Slice &str) {
@@ -9276,18 +14004,16 @@ void MessagesManager::start_up() {
bool have_previous = read_bool(log_string);
bool have_next = read_bool(log_string);
- CHECK(content_type != MessageChatDeleteHistory::ID); // not supported
if (op == "MessageOpAdd") {
auto m = make_unique<Message>();
- m->random_y = get_random_y(message_id);
- m->message_id = message_id;
+ set_message_id(m, message_id);
m->date = G()->unix_time();
- m->content = make_unique<MessageText>(FormattedText{"text", vector<MessageEntity>()}, WebPageId());
+ m->content = create_text_message_content("text", {}, {});
m->have_previous = have_previous;
m->have_next = have_next;
- bool need_update = false;
+ bool need_update = from_update;
bool need_update_dialog_pos = false;
if (add_message_to_dialog(dialog_id, std::move(m), from_update, &need_update, &need_update_dialog_pos,
"Unknown source") == nullptr) {
@@ -9332,73 +14058,90 @@ void MessagesManager::start_up() {
*/
}
+void MessagesManager::on_authorization_success() {
+ CHECK(td_->auth_manager_->is_authorized());
+ authorization_date_ = td_->option_manager_->get_option_integer("authorization_date");
+
+ if (td_->auth_manager_->is_bot()) {
+ disable_get_dialog_filter_ = true;
+ return;
+ }
+
+ create_folders();
+
+ reload_dialog_filters();
+}
+
void MessagesManager::ttl_db_loop_start(double server_now) {
- ttl_db_expire_from_ = 0;
- ttl_db_expire_till_ = static_cast<int32>(server_now) + 15 /* 15 seconds */;
+ ttl_db_expires_from_ = 0;
+ ttl_db_expires_till_ = static_cast<int32>(server_now) + 15 /* 15 seconds */;
ttl_db_has_query_ = false;
ttl_db_loop(server_now);
}
void MessagesManager::ttl_db_loop(double server_now) {
- LOG(INFO) << "ttl_db: loop " << tag("expire_from", ttl_db_expire_from_) << tag("expire_till", ttl_db_expire_till_)
- << tag("has_query", ttl_db_has_query_);
+ LOG(INFO) << "Begin ttl_db loop: " << tag("expires_from", ttl_db_expires_from_)
+ << tag("expires_till", ttl_db_expires_till_) << tag("has_query", ttl_db_has_query_);
if (ttl_db_has_query_) {
return;
}
auto now = static_cast<int32>(server_now);
- if (ttl_db_expire_till_ < 0) {
- LOG(INFO) << "ttl_db: finished";
+ if (ttl_db_expires_till_ < 0) {
+ LOG(INFO) << "Finish ttl_db loop";
return;
}
- if (now < ttl_db_expire_from_) {
+ if (now < ttl_db_expires_from_) {
ttl_db_slot_.set_event(EventCreator::yield(actor_shared(this, YieldType::TtlDb)));
- auto wakeup_in = ttl_db_expire_from_ - server_now;
+ auto wakeup_in = ttl_db_expires_from_ - server_now;
ttl_db_slot_.set_timeout_in(wakeup_in);
- LOG(INFO) << "ttl_db: " << tag("wakeup in", wakeup_in);
+ LOG(INFO) << "Set ttl_db timeout in " << wakeup_in;
return;
}
ttl_db_has_query_ = true;
int32 limit = 50;
- LOG(INFO) << "ttl_db: send query " << tag("expire_from", ttl_db_expire_from_)
- << tag("expire_till", ttl_db_expire_till_) << tag("limit", limit);
- G()->td_db()->get_messages_db_async()->get_expiring_messages(
- ttl_db_expire_from_, ttl_db_expire_till_, limit,
+ LOG(INFO) << "Send ttl_db query " << tag("expires_from", ttl_db_expires_from_)
+ << tag("expires_till", ttl_db_expires_till_) << tag("limit", limit);
+ G()->td_db()->get_message_db_async()->get_expiring_messages(
+ ttl_db_expires_from_, ttl_db_expires_till_, limit,
PromiseCreator::lambda(
- [actor_id = actor_id(this)](Result<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> result) {
+ [actor_id = actor_id(this)](Result<std::pair<std::vector<MessageDbMessage>, int32>> result) {
send_closure(actor_id, &MessagesManager::ttl_db_on_result, std::move(result), false);
}));
}
-void MessagesManager::ttl_db_on_result(Result<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> r_result,
- bool dummy) {
+void MessagesManager::ttl_db_on_result(Result<std::pair<std::vector<MessageDbMessage>, int32>> r_result, bool dummy) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(r_result.is_ok());
auto result = r_result.move_as_ok();
ttl_db_has_query_ = false;
- ttl_db_expire_from_ = ttl_db_expire_till_;
- ttl_db_expire_till_ = result.second;
+ ttl_db_expires_from_ = ttl_db_expires_till_;
+ ttl_db_expires_till_ = result.second;
- LOG(INFO) << "ttl_db: query result " << tag("new expire_till", ttl_db_expire_till_)
+ LOG(INFO) << "Receive ttl_db query result " << tag("new expires_till", ttl_db_expires_till_)
<< tag("got messages", result.first.size());
for (auto &dialog_message : result.first) {
- on_get_message_from_database(dialog_message.first, get_dialog_force(dialog_message.first), dialog_message.second);
+ on_get_message_from_database(dialog_message, false, "ttl_db_on_result");
}
ttl_db_loop(G()->server_time());
}
-void MessagesManager::on_send_secret_message_error(int64 random_id, Status error, Promise<> promise) {
+void MessagesManager::on_send_secret_message_error(int64 random_id, Status error, Promise<Unit> promise) {
promise.set_value(Unit()); // TODO: set after error is saved
- LOG(INFO) << "Receive error for SecretChatsManager::send_message: " << error;
auto it = being_sent_messages_.find(random_id);
if (it != being_sent_messages_.end()) {
auto full_message_id = it->second;
- auto *message = get_message(full_message_id);
- if (message != nullptr) {
- auto file_id = get_message_content_file_id(message->content.get());
+ auto *m = get_message(full_message_id);
+ if (m != nullptr) {
+ auto file_id = get_message_content_upload_file_id(m->content.get());
if (file_id.is_valid()) {
if (G()->close_flag() && G()->parameters().use_message_db) {
// do not send error, message will be re-sent
@@ -9420,15 +14163,13 @@ void MessagesManager::on_send_secret_message_error(int64 random_id, Status error
}
void MessagesManager::on_send_secret_message_success(int64 random_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::EncryptedFile> file_ptr,
- Promise<> promise) {
+ unique_ptr<EncryptedFile> file, Promise<Unit> promise) {
promise.set_value(Unit()); // TODO: set after message is saved
FileId new_file_id;
- if (file_ptr != nullptr && file_ptr->get_id() == telegram_api::encryptedFile::ID) {
- auto file = move_tl_object_as<telegram_api::encryptedFile>(file_ptr);
+ if (file != nullptr) {
if (!DcId::is_valid(file->dc_id_)) {
- LOG(ERROR) << "Wrong dc_id = " << file->dc_id_ << " in file " << to_string(file);
+ LOG(ERROR) << "Wrong dc_id = " << file->dc_id_ << " in file " << *file;
} else {
DialogId owner_dialog_id;
auto it = being_sent_messages_.find(random_id);
@@ -9437,51 +14178,92 @@ void MessagesManager::on_send_secret_message_success(int64 random_id, MessageId
}
new_file_id = td_->file_manager_->register_remote(
- FullRemoteFileLocation(FileType::Encrypted, file->id_, file->access_hash_, DcId::internal(file->dc_id_)),
- FileLocationSource::FromServer, owner_dialog_id, 0, 0, "");
+ FullRemoteFileLocation(FileType::Encrypted, file->id_, file->access_hash_, DcId::internal(file->dc_id_), ""),
+ FileLocationSource::FromServer, owner_dialog_id, 0, file->size_, to_string(static_cast<uint64>(file->id_)));
}
}
- on_send_message_success(random_id, message_id, date, new_file_id, "process send_secret_message_success");
+ on_send_message_success(random_id, message_id, date, 0, new_file_id, "on_send_secret_message_success");
}
void MessagesManager::delete_secret_messages(SecretChatId secret_chat_id, std::vector<int64> random_ids,
- Promise<> promise) {
- promise.set_value(Unit()); // TODO: set after event is saved
+ Promise<Unit> promise) {
+ LOG(DEBUG) << "On delete messages in " << secret_chat_id << " with random_ids " << random_ids;
+ CHECK(secret_chat_id.is_valid());
+
DialogId dialog_id(secret_chat_id);
- Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr) {
+ if (!have_dialog_force(dialog_id, "delete_secret_messages")) {
LOG(ERROR) << "Ignore delete secret messages in unknown " << dialog_id;
+ promise.set_value(Unit());
return;
}
+ auto pending_secret_message = make_unique<PendingSecretMessage>();
+ pending_secret_message->success_promise = std::move(promise);
+ pending_secret_message->type = PendingSecretMessage::Type::DeleteMessages;
+ pending_secret_message->dialog_id = dialog_id;
+ pending_secret_message->random_ids = std::move(random_ids);
+
+ add_secret_message(std::move(pending_secret_message));
+}
+
+void MessagesManager::finish_delete_secret_messages(DialogId dialog_id, std::vector<int64> random_ids,
+ Promise<Unit> promise) {
+ LOG(INFO) << "Delete messages with random_ids " << random_ids << " in " << dialog_id;
+ promise.set_value(Unit()); // TODO: set after event is saved
+
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
vector<MessageId> to_delete_message_ids;
for (auto &random_id : random_ids) {
- auto message_id = get_message_id_by_random_id(d, random_id);
+ auto message_id = get_message_id_by_random_id(d, random_id, "finish_delete_secret_messages");
if (!message_id.is_valid()) {
+ LOG(INFO) << "Can't find message with random_id " << random_id;
continue;
}
const Message *m = get_message(d, message_id);
CHECK(m != nullptr);
- if (!is_service_message_content(m->content->get_id())) {
+ if (!is_service_message_content(m->content->get_type())) {
to_delete_message_ids.push_back(message_id);
+ } else {
+ LOG(INFO) << "Skip deletion of service " << message_id;
}
}
- delete_dialog_messages_from_updates(dialog_id, to_delete_message_ids);
+ delete_dialog_messages(d, to_delete_message_ids, true, "finish_delete_secret_messages");
}
-void MessagesManager::delete_secret_chat_history(SecretChatId secret_chat_id, MessageId last_message_id,
- Promise<> promise) {
- promise.set_value(Unit()); // TODO: set after event is saved
- auto dialog_id = DialogId(secret_chat_id);
- Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr) {
- LOG(ERROR) << "Ignore delete secret chat history in unknown " << dialog_id;
+void MessagesManager::delete_secret_chat_history(SecretChatId secret_chat_id, bool remove_from_dialog_list,
+ MessageId last_message_id, Promise<Unit> promise) {
+ LOG(DEBUG) << "Delete history in " << secret_chat_id << " up to " << last_message_id;
+ CHECK(secret_chat_id.is_valid());
+ CHECK(!last_message_id.is_scheduled());
+
+ DialogId dialog_id(secret_chat_id);
+ if (!have_dialog_force(dialog_id, "delete_secret_chat_history")) {
+ LOG(ERROR) << "Ignore delete history in unknown " << dialog_id;
+ promise.set_value(Unit());
return;
}
+ auto pending_secret_message = make_unique<PendingSecretMessage>();
+ pending_secret_message->success_promise = std::move(promise);
+ pending_secret_message->type = PendingSecretMessage::Type::DeleteHistory;
+ pending_secret_message->dialog_id = dialog_id;
+ pending_secret_message->last_message_id = last_message_id;
+ pending_secret_message->remove_from_dialog_list = remove_from_dialog_list;
+
+ add_secret_message(std::move(pending_secret_message));
+}
+
+void MessagesManager::finish_delete_secret_chat_history(DialogId dialog_id, bool remove_from_dialog_list,
+ MessageId last_message_id, Promise<Unit> promise) {
+ LOG(DEBUG) << "Delete history in " << dialog_id << " up to " << last_message_id;
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
// TODO: probably last_message_id is not needed
- delete_all_dialog_messages(d, false, true);
+ delete_all_dialog_messages(d, remove_from_dialog_list, true);
+ promise.set_value(Unit()); // TODO: set after event is saved
}
void MessagesManager::read_secret_chat_outbox(SecretChatId secret_chat_id, int32 up_to_date, int32 read_date) {
@@ -9490,11 +14272,19 @@ void MessagesManager::read_secret_chat_outbox(SecretChatId secret_chat_id, int32
return;
}
auto dialog_id = DialogId(secret_chat_id);
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "read_secret_chat_outbox");
if (d == nullptr) {
return;
}
- // TODO: protect with logevent
+
+ if (read_date > 0) {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id);
+ if (user_id.is_valid()) {
+ td_->contacts_manager_->on_update_user_local_was_online(user_id, read_date);
+ }
+ }
+
+ // TODO: protect with log event
suffix_load_till_date(
d, up_to_date,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, up_to_date, read_date](Result<Unit> result) {
@@ -9507,7 +14297,7 @@ void MessagesManager::read_secret_chat_outbox_inner(DialogId dialog_id, int32 up
CHECK(d != nullptr);
auto end = MessagesConstIterator(d, MessageId::max());
- while (*end && (*end)->date > up_to_date) {
+ while (*end && ((*end)->date > up_to_date || (*end)->message_id.is_yet_unsent())) {
--end;
}
if (!*end) {
@@ -9519,22 +14309,22 @@ void MessagesManager::read_secret_chat_outbox_inner(DialogId dialog_id, int32 up
read_history_outbox(dialog_id, max_message_id, read_date);
}
-void MessagesManager::open_secret_message(SecretChatId secret_chat_id, int64 random_id, Promise<> promise) {
+void MessagesManager::open_secret_message(SecretChatId secret_chat_id, int64 random_id, Promise<Unit> promise) {
promise.set_value(Unit()); // TODO: set after event is saved
DialogId dialog_id(secret_chat_id);
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "open_secret_message");
if (d == nullptr) {
LOG(ERROR) << "Ignore opening secret chat message in unknown " << dialog_id;
return;
}
- auto message_id = get_message_id_by_random_id(d, random_id);
+ auto message_id = get_message_id_by_random_id(d, random_id, "open_secret_message");
if (!message_id.is_valid()) {
return;
}
Message *m = get_message(d, message_id);
CHECK(m != nullptr);
- if (message_id.is_yet_unsent() || !m->is_outgoing) {
+ if (m->message_id.is_yet_unsent() || m->is_failed_to_send || !m->is_outgoing) {
LOG(ERROR) << "Peer has opened wrong " << message_id << " in " << dialog_id;
return;
}
@@ -9542,9 +14332,30 @@ void MessagesManager::open_secret_message(SecretChatId secret_chat_id, int64 ran
read_message_content(d, m, false, "open_secret_message");
}
+void MessagesManager::on_update_secret_chat_state(SecretChatId secret_chat_id, SecretChatState state) {
+ if (state == SecretChatState::Closed && !td_->auth_manager_->is_bot()) {
+ DialogId dialog_id(secret_chat_id);
+ Dialog *d = get_dialog_force(dialog_id, "on_update_secret_chat_state");
+ if (d != nullptr) {
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ remove_new_secret_chat_notification(d, true);
+ }
+ if (d->message_notification_group.group_id.is_valid() && get_dialog_pending_notification_count(d, false) == 0 &&
+ !d->message_notification_group.last_notification_id.is_valid()) {
+ CHECK(d->message_notification_group.last_notification_date == 0);
+ d->message_notification_group.try_reuse = true;
+ d->message_notification_group.is_changed = true;
+ on_dialog_updated(d->dialog_id, "on_update_secret_chat_state");
+ }
+ CHECK(!d->mention_notification_group.group_id.is_valid()); // there can't be unread mentions in secret chats
+ }
+ }
+}
+
void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId user_id, MessageId message_id,
- int32 date, tl_object_ptr<telegram_api::encryptedFile> file,
- tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise) {
+ int32 date, unique_ptr<EncryptedFile> file,
+ tl_object_ptr<secret_api::decryptedMessage> message,
+ Promise<Unit> promise) {
LOG(DEBUG) << "On get " << to_string(message);
CHECK(message != nullptr);
CHECK(secret_chat_id.is_valid());
@@ -9552,6 +14363,12 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId
CHECK(message_id.is_valid());
CHECK(date > 0);
+ if (message->random_id_ == 0) {
+ LOG(ERROR) << "Ignore secret message with random_id == 0";
+ promise.set_error(Status::Error(400, "Invalid random_id"));
+ return;
+ }
+
auto pending_secret_message = make_unique<PendingSecretMessage>();
pending_secret_message->success_promise = std::move(promise);
MessageInfo &message_info = pending_secret_message->message_info;
@@ -9562,62 +14379,85 @@ void MessagesManager::on_get_secret_message(SecretChatId secret_chat_id, UserId
message_info.random_id = message->random_id_;
message_info.ttl = message->ttl_;
- Dialog *d = get_dialog_force(message_info.dialog_id);
+ Dialog *d = get_dialog_force(message_info.dialog_id, "on_get_secret_message");
+ if (d == nullptr && have_dialog_info_force(message_info.dialog_id)) {
+ force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true);
+ d = get_dialog(message_info.dialog_id);
+ }
if (d == nullptr) {
LOG(ERROR) << "Ignore secret message in unknown " << message_info.dialog_id;
pending_secret_message->success_promise.set_error(Status::Error(500, "Chat not found"));
return;
}
+ pending_secret_message_ids_[message_info.dialog_id][message_info.random_id] = message_id;
+
pending_secret_message->load_data_multipromise.add_promise(Auto());
auto lock_promise = pending_secret_message->load_data_multipromise.get_promise();
int32 flags = MESSAGE_FLAG_HAS_UNREAD_CONTENT | MESSAGE_FLAG_HAS_FROM_ID;
if ((message->flags_ & secret_api::decryptedMessage::REPLY_TO_RANDOM_ID_MASK) != 0) {
- message_info.reply_to_message_id =
- get_message_id_by_random_id(get_dialog(message_info.dialog_id), message->reply_to_random_id_);
- if (message_info.reply_to_message_id.is_valid()) {
- flags |= MESSAGE_FLAG_IS_REPLY;
+ message_info.reply_header.reply_to_message_id =
+ get_message_id_by_random_id(d, message->reply_to_random_id_, "on_get_secret_message");
+ if (!message_info.reply_header.reply_to_message_id.is_valid()) {
+ auto dialog_it = pending_secret_message_ids_.find(message_info.dialog_id);
+ if (dialog_it != pending_secret_message_ids_.end()) {
+ auto message_it = dialog_it->second.find(message->reply_to_random_id_);
+ if (message_it != dialog_it->second.end()) {
+ message_info.reply_header.reply_to_message_id = message_it->second;
+ }
+ }
}
}
- if ((message->flags_ & secret_api::decryptedMessage::ENTITIES_MASK) != 0) {
- flags |= MESSAGE_FLAG_HAS_ENTITIES;
- }
- if ((message->flags_ & secret_api::decryptedMessage::MEDIA_MASK) != 0) {
- flags |= MESSAGE_FLAG_HAS_MEDIA;
+ if ((message->flags_ & secret_api::decryptedMessage::SILENT_MASK) != 0) {
+ flags |= MESSAGE_FLAG_IS_SILENT;
}
if (!clean_input_string(message->via_bot_name_)) {
LOG(WARNING) << "Receive invalid bot username " << message->via_bot_name_;
message->via_bot_name_.clear();
}
- if ((message->flags_ & secret_api::decryptedMessage::VIA_BOT_NAME_MASK) != 0 && !message->via_bot_name_.empty()) {
- pending_secret_message->load_data_multipromise.add_promise(
- PromiseCreator::lambda([this, via_bot_name = message->via_bot_name_, &flags = message_info.flags,
- &via_bot_user_id = message_info.via_bot_user_id](Unit) mutable {
- auto dialog_id = resolve_dialog_username(via_bot_name);
- if (dialog_id.is_valid() && dialog_id.get_type() == DialogType::User) {
- flags |= MESSAGE_FLAG_IS_SENT_VIA_BOT;
- via_bot_user_id = dialog_id.get_user_id();
- }
- }));
- search_public_dialog(message->via_bot_name_, false, pending_secret_message->load_data_multipromise.get_promise());
+ if (!message->via_bot_name_.empty()) {
+ auto request_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), via_bot_username = message->via_bot_name_, message_info_ptr = &message_info,
+ promise = pending_secret_message->load_data_multipromise.get_promise()](Unit) mutable {
+ send_closure(actor_id, &MessagesManager::on_resolve_secret_chat_message_via_bot_username, via_bot_username,
+ message_info_ptr, std::move(promise));
+ });
+ search_public_dialog(message->via_bot_name_, false, std::move(request_promise));
}
if ((message->flags_ & secret_api::decryptedMessage::GROUPED_ID_MASK) != 0 && message->grouped_id_ != 0) {
message_info.media_album_id = message->grouped_id_;
- flags |= MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID;
}
message_info.flags = flags;
message_info.content = get_secret_message_content(
- std::move(message->message_), std::move(file), std::move(message->media_), std::move(message->entities_),
- message_info.dialog_id, pending_secret_message->load_data_multipromise);
+ td_, std::move(message->message_), std::move(file), std::move(message->media_), std::move(message->entities_),
+ message_info.dialog_id, pending_secret_message->load_data_multipromise,
+ td_->contacts_manager_->is_user_premium(user_id));
add_secret_message(std::move(pending_secret_message), std::move(lock_promise));
}
+void MessagesManager::on_resolve_secret_chat_message_via_bot_username(const string &via_bot_username,
+ MessageInfo *message_info_ptr,
+ Promise<Unit> &&promise) {
+ if (!G()->close_flag()) {
+ auto dialog_id = resolve_dialog_username(via_bot_username);
+ if (dialog_id.is_valid() && dialog_id.get_type() == DialogType::User) {
+ auto user_id = dialog_id.get_user_id();
+ auto r_bot_data = td_->contacts_manager_->get_bot_data(user_id);
+ if (r_bot_data.is_ok() && r_bot_data.ok().is_inline) {
+ message_info_ptr->flags |= MESSAGE_FLAG_IS_SENT_VIA_BOT;
+ message_info_ptr->via_bot_user_id = user_id;
+ }
+ }
+ }
+ promise.set_value(Unit());
+}
+
void MessagesManager::on_secret_chat_screenshot_taken(SecretChatId secret_chat_id, UserId user_id, MessageId message_id,
- int32 date, int64 random_id, Promise<> promise) {
+ int32 date, int64 random_id, Promise<Unit> promise) {
LOG(DEBUG) << "On screenshot taken in " << secret_chat_id;
CHECK(secret_chat_id.is_valid());
CHECK(user_id.is_valid());
@@ -9633,9 +14473,13 @@ void MessagesManager::on_secret_chat_screenshot_taken(SecretChatId secret_chat_i
message_info.date = date;
message_info.random_id = random_id;
message_info.flags = MESSAGE_FLAG_HAS_FROM_ID;
- message_info.content = make_unique<MessageScreenshotTaken>();
+ message_info.content = create_screenshot_taken_message_content();
- Dialog *d = get_dialog_force(message_info.dialog_id);
+ Dialog *d = get_dialog_force(message_info.dialog_id, "on_secret_chat_screenshot_taken");
+ if (d == nullptr && have_dialog_info_force(message_info.dialog_id)) {
+ force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true);
+ d = get_dialog(message_info.dialog_id);
+ }
if (d == nullptr) {
LOG(ERROR) << "Ignore secret message in unknown " << message_info.dialog_id;
pending_secret_message->success_promise.set_error(Status::Error(500, "Chat not found"));
@@ -9646,14 +14490,14 @@ void MessagesManager::on_secret_chat_screenshot_taken(SecretChatId secret_chat_i
}
void MessagesManager::on_secret_chat_ttl_changed(SecretChatId secret_chat_id, UserId user_id, MessageId message_id,
- int32 date, int32 ttl, int64 random_id, Promise<> promise) {
- LOG(DEBUG) << "On ttl set in " << secret_chat_id << " to " << ttl;
+ int32 date, int32 ttl, int64 random_id, Promise<Unit> promise) {
+ LOG(DEBUG) << "On TTL set in " << secret_chat_id << " to " << ttl;
CHECK(secret_chat_id.is_valid());
CHECK(user_id.is_valid());
CHECK(message_id.is_valid());
CHECK(date > 0);
if (ttl < 0) {
- LOG(WARNING) << "Receive wrong ttl = " << ttl;
+ LOG(WARNING) << "Receive wrong TTL = " << ttl;
promise.set_value(Unit());
return;
}
@@ -9667,9 +14511,13 @@ void MessagesManager::on_secret_chat_ttl_changed(SecretChatId secret_chat_id, Us
message_info.date = date;
message_info.random_id = random_id;
message_info.flags = MESSAGE_FLAG_HAS_FROM_ID;
- message_info.content = make_unique<MessageChatSetTtl>(ttl);
+ message_info.content = create_chat_set_ttl_message_content(ttl);
- Dialog *d = get_dialog_force(message_info.dialog_id);
+ Dialog *d = get_dialog_force(message_info.dialog_id, "on_secret_chat_ttl_changed");
+ if (d == nullptr && have_dialog_info_force(message_info.dialog_id)) {
+ force_create_dialog(message_info.dialog_id, "on_get_secret_message", true, true);
+ d = get_dialog(message_info.dialog_id);
+ }
if (d == nullptr) {
LOG(ERROR) << "Ignore secret message in unknown " << message_info.dialog_id;
pending_secret_message->success_promise.set_error(Status::Error(500, "Chat not found"));
@@ -9685,12 +14533,9 @@ void MessagesManager::add_secret_message(unique_ptr<PendingSecretMessage> pendin
multipromise.set_ignore_errors(true);
int64 token = pending_secret_messages_.add(std::move(pending_secret_message));
- multipromise.add_promise(PromiseCreator::lambda([token, actor_id = actor_id(this),
- this](Result<Unit> result) mutable {
- if (result.is_ok()) { // if we aren't closing
- this->pending_secret_messages_.finish(token, [actor_id](unique_ptr<PendingSecretMessage> pending_secret_message) {
- send_closure_later(actor_id, &MessagesManager::finish_add_secret_message, std::move(pending_secret_message));
- });
+ multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), token](Result<Unit> result) {
+ if (result.is_ok()) {
+ send_closure(actor_id, &MessagesManager::on_add_secret_message_ready, token);
}
}));
@@ -9700,61 +14545,93 @@ void MessagesManager::add_secret_message(unique_ptr<PendingSecretMessage> pendin
lock_promise.set_value(Unit());
}
+void MessagesManager::on_add_secret_message_ready(int64 token) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ pending_secret_messages_.finish(
+ token, [actor_id = actor_id(this)](unique_ptr<PendingSecretMessage> pending_secret_message) {
+ send_closure_later(actor_id, &MessagesManager::finish_add_secret_message, std::move(pending_secret_message));
+ });
+}
+
void MessagesManager::finish_add_secret_message(unique_ptr<PendingSecretMessage> pending_secret_message) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ if (pending_secret_message->type == PendingSecretMessage::Type::DeleteMessages) {
+ return finish_delete_secret_messages(pending_secret_message->dialog_id,
+ std::move(pending_secret_message->random_ids),
+ std::move(pending_secret_message->success_promise));
+ }
+ if (pending_secret_message->type == PendingSecretMessage::Type::DeleteHistory) {
+ return finish_delete_secret_chat_history(
+ pending_secret_message->dialog_id, pending_secret_message->remove_from_dialog_list,
+ pending_secret_message->last_message_id, std::move(pending_secret_message->success_promise));
+ }
+
auto d = get_dialog(pending_secret_message->message_info.dialog_id);
CHECK(d != nullptr);
auto random_id = pending_secret_message->message_info.random_id;
- auto message_id = get_message_id_by_random_id(d, random_id);
+ auto message_id = get_message_id_by_random_id(d, random_id, "finish_add_secret_message");
if (message_id.is_valid()) {
if (message_id != pending_secret_message->message_info.message_id) {
LOG(WARNING) << "Ignore duplicate " << pending_secret_message->message_info.message_id
<< " received earlier with " << message_id << " and random_id " << random_id;
}
} else {
+ if (!td_->contacts_manager_->is_user_premium(pending_secret_message->message_info.sender_user_id)) {
+ auto message_text = get_message_content_text_mutable(pending_secret_message->message_info.content.get());
+ if (message_text != nullptr) {
+ remove_premium_custom_emoji_entities(td_, message_text->entities, true);
+ }
+ }
+
on_get_message(std::move(pending_secret_message->message_info), true, false, true, true,
"finish add secret message");
}
- pending_secret_message->success_promise.set_value(Unit()); // TODO: set after message is saved
-}
-
-void MessagesManager::fix_message_info_dialog_id(MessageInfo &message_info) const {
- if (message_info.dialog_id != DialogId(td_->contacts_manager_->get_my_id("fix_message_info_dialog_id"))) {
- return;
- }
-
- UserId sender_user_id = message_info.sender_user_id;
- if (!sender_user_id.is_valid()) {
- LOG(ERROR) << "Receive invalid sender user id in private chat";
- return;
+ auto dialog_it = pending_secret_message_ids_.find(d->dialog_id);
+ if (dialog_it != pending_secret_message_ids_.end()) {
+ auto message_it = dialog_it->second.find(random_id);
+ if (message_it != dialog_it->second.end() && message_it->second == message_id) {
+ dialog_it->second.erase(message_it);
+ if (dialog_it->second.empty()) {
+ pending_secret_message_ids_.erase(dialog_it);
+ }
+ }
}
- message_info.dialog_id = DialogId(sender_user_id);
- LOG_IF(ERROR, (message_info.flags & MESSAGE_FLAG_IS_OUT) != 0)
- << "Receive message out flag for incoming " << message_info.message_id << " in " << message_info.dialog_id;
+ pending_secret_message->success_promise.set_value(Unit()); // TODO: set after message is saved
}
MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message(
- tl_object_ptr<telegram_api::Message> message_ptr, const char *source) const {
+ tl_object_ptr<telegram_api::Message> message_ptr, bool is_scheduled, const char *source) const {
LOG(DEBUG) << "Receive from " << source << " " << to_string(message_ptr);
- CHECK(message_ptr != nullptr) << source;
- int32 constructor_id = message_ptr->get_id();
+ LOG_CHECK(message_ptr != nullptr) << source;
MessageInfo message_info;
- switch (constructor_id) {
+ message_info.message_id = get_message_id(message_ptr, is_scheduled);
+ switch (message_ptr->get_id()) {
case telegram_api::messageEmpty::ID:
+ message_info.message_id = MessageId();
break;
case telegram_api::message::ID: {
auto message = move_tl_object_as<telegram_api::message>(message_ptr);
- message_info.dialog_id = DialogId(message->to_id_);
- message_info.message_id = MessageId(ServerMessageId(message->id_));
- if (message->flags_ & MESSAGE_FLAG_HAS_FROM_ID) {
- message_info.sender_user_id = UserId(message->from_id_);
+ message_info.dialog_id = DialogId(message->peer_id_);
+ if (message->from_id_ != nullptr) {
+ message_info.sender_dialog_id = DialogId(message->from_id_);
+ } else {
+ message_info.sender_dialog_id = message_info.dialog_id;
}
message_info.date = message->date_;
message_info.forward_header = std::move(message->fwd_from_);
- message_info.reply_to_message_id = MessageId(ServerMessageId(
- message->flags_ & MESSAGE_FLAG_IS_REPLY ? message->reply_to_msg_id_ : 0)); // TODO zero init in fetch
+ bool can_have_thread = !is_scheduled && message_info.dialog_id.get_type() == DialogType::Channel &&
+ !is_broadcast_channel(message_info.dialog_id);
+ message_info.reply_header = MessageReplyHeader(std::move(message->reply_to_), message_info.dialog_id,
+ message_info.message_id, message_info.date, can_have_thread);
if (message->flags_ & MESSAGE_FLAG_IS_SENT_VIA_BOT) {
message_info.via_bot_user_id = UserId(message->via_bot_id_);
if (!message_info.via_bot_user_id.is_valid()) {
@@ -9762,8 +14639,15 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message(
message_info.via_bot_user_id = UserId();
}
}
- if (message->flags_ & MESSAGE_FLAG_HAS_VIEWS) {
- message_info.views = message->views_;
+ if (message->flags_ & MESSAGE_FLAG_HAS_INTERACTION_INFO) {
+ message_info.view_count = message->views_;
+ message_info.forward_count = message->forwards_;
+ }
+ if (message->flags_ & MESSAGE_FLAG_HAS_REPLY_INFO) {
+ message_info.reply_info = std::move(message->replies_);
+ }
+ if (message->flags_ & MESSAGE_FLAG_HAS_REACTIONS) {
+ message_info.reactions = std::move(message->reactions_);
}
if (message->flags_ & MESSAGE_FLAG_HAS_EDIT_DATE) {
message_info.edit_date = message->edit_date_;
@@ -9771,43 +14655,66 @@ MessagesManager::MessageInfo MessagesManager::parse_telegram_api_message(
if (message->flags_ & MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID) {
message_info.media_album_id = message->grouped_id_;
}
+ if (message->flags_ & MESSAGE_FLAG_HAS_TTL_PERIOD) {
+ message_info.ttl_period = message->ttl_period_;
+ }
message_info.flags = message->flags_;
- fix_message_info_dialog_id(message_info);
bool is_content_read = (message->flags_ & MESSAGE_FLAG_HAS_UNREAD_CONTENT) == 0;
- if (is_message_auto_read(message_info.dialog_id, (message->flags_ & MESSAGE_FLAG_IS_OUT) != 0, true)) {
+ if (is_message_auto_read(message_info.dialog_id, (message->flags_ & MESSAGE_FLAG_IS_OUT) != 0)) {
is_content_read = true;
}
+ if (is_scheduled) {
+ is_content_read = false;
+ }
+ auto new_source = PSTRING() << FullMessageId(message_info.dialog_id, message_info.message_id) << " sent by "
+ << message_info.sender_dialog_id << " from " << source;
message_info.content = get_message_content(
- get_message_text(std::move(message->message_), std::move(message->entities_),
- message_info.forward_header ? message_info.forward_header->date_ : message_info.date),
+ td_,
+ get_message_text(td_->contacts_manager_.get(), std::move(message->message_), std::move(message->entities_),
+ true, td_->auth_manager_->is_bot(),
+ message_info.forward_header ? message_info.forward_header->date_ : message_info.date,
+ message_info.media_album_id != 0, new_source.c_str()),
std::move(message->media_), message_info.dialog_id, is_content_read, message_info.via_bot_user_id,
- &message_info.ttl);
+ &message_info.ttl, &message_info.disable_web_page_preview, new_source.c_str());
message_info.reply_markup =
message->flags_ & MESSAGE_FLAG_HAS_REPLY_MARKUP ? std::move(message->reply_markup_) : nullptr;
+ message_info.restriction_reasons = get_restriction_reasons(std::move(message->restriction_reason_));
message_info.author_signature = std::move(message->post_author_);
break;
}
case telegram_api::messageService::ID: {
auto message = move_tl_object_as<telegram_api::messageService>(message_ptr);
- message_info.dialog_id = DialogId(message->to_id_);
- message_info.message_id = MessageId(ServerMessageId(message->id_));
- if (message->flags_ & MESSAGE_FLAG_HAS_FROM_ID) {
- message_info.sender_user_id = UserId(message->from_id_);
+ message_info.dialog_id = DialogId(message->peer_id_);
+ if (message->from_id_ != nullptr) {
+ message_info.sender_dialog_id = DialogId(message->from_id_);
+ } else {
+ message_info.sender_dialog_id = message_info.dialog_id;
}
message_info.date = message->date_;
+ if (message->flags_ & MESSAGE_FLAG_HAS_TTL_PERIOD) {
+ message_info.ttl_period = message->ttl_period_;
+ }
message_info.flags = message->flags_;
- fix_message_info_dialog_id(message_info);
- MessageId reply_to_message_id = MessageId(ServerMessageId(
- message->flags_ & MESSAGE_FLAG_IS_REPLY ? message->reply_to_msg_id_ : 0)); // TODO zero init in fetch
- message_info.content =
- get_message_action_content(std::move(message->action_), message_info.dialog_id, reply_to_message_id);
+ bool can_have_thread = !is_scheduled && message_info.dialog_id.get_type() == DialogType::Channel &&
+ !is_broadcast_channel(message_info.dialog_id);
+ message_info.reply_header = MessageReplyHeader(std::move(message->reply_to_), message_info.dialog_id,
+ message_info.message_id, message_info.date, can_have_thread);
+ message_info.content = get_action_message_content(td_, std::move(message->action_), message_info.dialog_id,
+ message_info.reply_header.reply_in_dialog_id,
+ message_info.reply_header.reply_to_message_id);
+ message_info.reply_header.reply_in_dialog_id = DialogId();
+ message_info.reply_header.reply_to_message_id = MessageId();
break;
}
default:
UNREACHABLE();
break;
}
+ if (message_info.sender_dialog_id.is_valid() && message_info.sender_dialog_id.get_type() == DialogType::User) {
+ message_info.sender_user_id = message_info.sender_dialog_id.get_user_id();
+ message_info.sender_dialog_id = DialogId();
+ }
return message_info;
}
@@ -9815,74 +14722,93 @@ std::pair<DialogId, unique_ptr<MessagesManager::Message>> MessagesManager::creat
bool is_channel_message) {
DialogId dialog_id = message_info.dialog_id;
MessageId message_id = message_info.message_id;
- if (!message_id.is_valid() || !dialog_id.is_valid()) {
+ if ((!message_id.is_valid() && !message_id.is_valid_scheduled()) || !dialog_id.is_valid()) {
if (message_id != MessageId() || dialog_id != DialogId()) {
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id;
}
return {DialogId(), nullptr};
}
- if (message_id.is_yet_unsent()) {
+ if (message_id.is_yet_unsent() || message_id.is_local()) {
LOG(ERROR) << "Receive " << message_id;
return {DialogId(), nullptr};
}
CHECK(message_info.content != nullptr);
+ auto dialog_type = dialog_id.get_type();
UserId sender_user_id = message_info.sender_user_id;
+ DialogId sender_dialog_id = message_info.sender_dialog_id;
if (!sender_user_id.is_valid()) {
- if (!is_broadcast_channel(dialog_id)) {
- LOG(ERROR) << "Invalid " << sender_user_id << " specified to be a sender of the " << message_id << " in "
- << dialog_id;
- return {DialogId(), nullptr};
- }
-
if (sender_user_id != UserId()) {
LOG(ERROR) << "Receive invalid " << sender_user_id;
sender_user_id = UserId();
}
+ if (!is_broadcast_channel(dialog_id) && td_->auth_manager_->is_bot()) {
+ if (dialog_id == sender_dialog_id) {
+ td_->contacts_manager_->add_anonymous_bot_user();
+ } else {
+ td_->contacts_manager_->add_service_notifications_user();
+ td_->contacts_manager_->add_channel_bot_user();
+ }
+ }
}
-
- auto dialog_type = dialog_id.get_type();
- LOG_IF(ERROR, is_channel_message && dialog_type != DialogType::Channel)
- << "is_channel_message is true for message received in the " << dialog_id;
-
- int32 flags = message_info.flags;
- if (flags & ~(MESSAGE_FLAG_IS_OUT | MESSAGE_FLAG_IS_FORWARDED | MESSAGE_FLAG_IS_REPLY | MESSAGE_FLAG_HAS_MENTION |
- MESSAGE_FLAG_HAS_UNREAD_CONTENT | MESSAGE_FLAG_HAS_REPLY_MARKUP | MESSAGE_FLAG_HAS_ENTITIES |
- MESSAGE_FLAG_HAS_FROM_ID | MESSAGE_FLAG_HAS_MEDIA | MESSAGE_FLAG_HAS_VIEWS |
- MESSAGE_FLAG_IS_SENT_VIA_BOT | MESSAGE_FLAG_IS_SILENT | MESSAGE_FLAG_IS_POST |
- MESSAGE_FLAG_HAS_EDIT_DATE | MESSAGE_FLAG_HAS_AUTHOR_SIGNATURE | MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID)) {
- LOG(ERROR) << "Unsupported message flags = " << flags << " received";
+ if (sender_dialog_id.is_valid()) {
+ if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) {
+ LOG(ERROR) << "Receive " << message_id << " sent by " << sender_dialog_id << " in " << dialog_id;
+ return {DialogId(), nullptr};
+ }
+ } else if (sender_dialog_id != DialogId()) {
+ LOG(ERROR) << "Receive invalid " << sender_dialog_id;
+ sender_dialog_id = DialogId();
+ }
+ if (message_id.is_scheduled()) {
+ is_channel_message = (dialog_type == DialogType::Channel);
}
+ int32 flags = message_info.flags;
bool is_outgoing = (flags & MESSAGE_FLAG_IS_OUT) != 0;
bool is_silent = (flags & MESSAGE_FLAG_IS_SILENT) != 0;
bool is_channel_post = (flags & MESSAGE_FLAG_IS_POST) != 0;
+ bool is_legacy = (flags & MESSAGE_FLAG_IS_LEGACY) != 0;
+ bool hide_edit_date = (flags & MESSAGE_FLAG_HIDE_EDIT_DATE) != 0;
+ bool is_from_scheduled = (flags & MESSAGE_FLAG_IS_FROM_SCHEDULED) != 0;
+ bool is_pinned = (flags & MESSAGE_FLAG_IS_PINNED) != 0;
+ bool noforwards = (flags & MESSAGE_FLAG_NOFORWARDS) != 0;
- UserId my_id = td_->contacts_manager_->get_my_id("create_message");
+ LOG_IF(ERROR, is_channel_message != (dialog_type == DialogType::Channel))
+ << "Receive wrong is_channel_message for " << message_id << " in " << dialog_id;
+ if (is_channel_post && !is_broadcast_channel(dialog_id)) {
+ LOG(ERROR) << "Receive is_channel_post for " << message_id << " in " << dialog_id;
+ is_channel_post = false;
+ }
+
+ UserId my_id = td_->contacts_manager_->get_my_id();
DialogId my_dialog_id = DialogId(my_id);
- if (dialog_id == my_dialog_id) {
- // dialog_id should be already fixed
- CHECK(sender_user_id == my_id);
+ if (dialog_id == my_dialog_id && (sender_user_id != my_id || sender_dialog_id.is_valid())) {
+ LOG(ERROR) << "Receive " << sender_user_id << "/" << sender_dialog_id << " as a sender of " << message_id
+ << " instead of self";
+ sender_user_id = my_id;
+ sender_dialog_id = DialogId();
}
- if (sender_user_id.is_valid() && (sender_user_id == my_id && dialog_id != my_dialog_id) != is_outgoing) {
- // if (content->get_id() != MessageChatAddUser::ID) { // TODO: we have wrong flags for invites via links
+ bool supposed_to_be_outgoing = sender_user_id == my_id && !(dialog_id == my_dialog_id && !message_id.is_scheduled());
+ if (sender_user_id.is_valid() && supposed_to_be_outgoing != is_outgoing) {
LOG(ERROR) << "Receive wrong message out flag: me is " << my_id << ", message is from " << sender_user_id
<< ", flags = " << flags << " for " << message_id << " in " << dialog_id;
- // }
- }
-
- MessageId reply_to_message_id = message_info.reply_to_message_id;
- if (reply_to_message_id != MessageId() &&
- (!reply_to_message_id.is_valid() || reply_to_message_id.get() >= message_id.get())) {
- LOG(ERROR) << "Receive reply to wrong " << reply_to_message_id << " in " << message_id;
- reply_to_message_id = MessageId();
- }
+ is_outgoing = supposed_to_be_outgoing;
- UserId via_bot_user_id = message_info.via_bot_user_id;
- if (!via_bot_user_id.is_valid()) {
- via_bot_user_id = UserId();
+ /*
+ // it is useless to call getChannelDifference, because the channel pts will be increased already
+ if (dialog_type == DialogType::Channel && !running_get_difference_ && !running_get_channel_difference(dialog_id) &&
+ get_channel_difference_to_log_event_id_.count(dialog_id) == 0) {
+ // it is safer to completely ignore the message and re-get it through getChannelDifference
+ Dialog *d = get_dialog(dialog_id);
+ if (d != nullptr) {
+ channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
+ return {DialogId(), nullptr};
+ }
+ }
+ */
}
int32 date = message_info.date;
@@ -9891,59 +14817,147 @@ std::pair<DialogId, unique_ptr<MessagesManager::Message>> MessagesManager::creat
date = 1;
}
+ MessageId reply_to_message_id = message_info.reply_header.reply_to_message_id;
+ DialogId reply_in_dialog_id = message_info.reply_header.reply_in_dialog_id;
+ MessageId top_thread_message_id = message_info.reply_header.top_thread_message_id;
+ bool is_topic_message = message_info.reply_header.is_topic_message;
+ fix_server_reply_to_message_id(dialog_id, message_id, reply_in_dialog_id, reply_to_message_id);
+ fix_server_reply_to_message_id(dialog_id, message_id, reply_in_dialog_id, top_thread_message_id);
+
+ UserId via_bot_user_id = message_info.via_bot_user_id;
+ if (!via_bot_user_id.is_valid()) {
+ via_bot_user_id = UserId();
+ }
+
int32 edit_date = message_info.edit_date;
if (edit_date < 0) {
LOG(ERROR) << "Wrong edit_date = " << edit_date << " received in " << message_id << " in " << dialog_id;
edit_date = 0;
}
+ auto content_type = message_info.content->get_type();
+ if (content_type == MessageContentType::Sticker &&
+ get_message_content_sticker_type(td_, message_info.content.get()) == StickerType::CustomEmoji) {
+ LOG(INFO) << "Replace emoji sticker with an empty message";
+ message_info.content = create_text_message_content("Invalid sticker", {}, WebPageId());
+ content_type = message_info.content->get_type();
+ }
+
+ if (hide_edit_date && td_->auth_manager_->is_bot()) {
+ hide_edit_date = false;
+ }
+ if (hide_edit_date && content_type == MessageContentType::LiveLocation) {
+ hide_edit_date = false;
+ }
+
+ int32 ttl_period = message_info.ttl_period;
+ if (ttl_period < 0 || (message_id.is_scheduled() && ttl_period != 0)) {
+ LOG(ERROR) << "Wrong TTL period = " << ttl_period << " received in " << message_id << " in " << dialog_id;
+ ttl_period = 0;
+ }
+
int32 ttl = message_info.ttl;
- bool is_content_secret =
- is_secret_message_content(ttl, message_info.content->get_id()); // should be calculated before TTL is adjusted
- if (ttl < 0) {
- LOG(ERROR) << "Wrong ttl = " << ttl << " received in " << message_id << " in " << dialog_id;
+ bool is_content_secret = is_secret_message_content(ttl, content_type); // must be calculated before TTL is adjusted
+ if (ttl < 0 || (message_id.is_scheduled() && ttl != 0)) {
+ LOG(ERROR) << "Wrong TTL = " << ttl << " received in " << message_id << " in " << dialog_id;
ttl = 0;
} else if (ttl > 0) {
- ttl = max(ttl, get_message_content_duration(message_info.content.get()) + 1);
+ ttl = max(ttl, get_message_content_duration(message_info.content.get(), td_) + 1);
}
- int32 views = message_info.views;
- if (views < 0) {
- LOG(ERROR) << "Wrong views = " << views << " received in " << message_id << " in " << dialog_id;
- views = 0;
+ if (message_id.is_scheduled()) {
+ if (message_info.reply_info != nullptr) {
+ LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with reply info";
+ message_info.reply_info = nullptr;
+ }
+ if (message_info.reactions != nullptr) {
+ LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with reactions";
+ message_info.reactions = nullptr;
+ }
+ }
+ int32 view_count = message_info.view_count;
+ if (view_count < 0) {
+ LOG(ERROR) << "Wrong view_count = " << view_count << " received in " << message_id << " in " << dialog_id;
+ view_count = 0;
+ }
+ int32 forward_count = message_info.forward_count;
+ if (forward_count < 0) {
+ LOG(ERROR) << "Wrong forward_count = " << forward_count << " received in " << message_id << " in " << dialog_id;
+ forward_count = 0;
+ }
+ MessageReplyInfo reply_info(td_, std::move(message_info.reply_info), td_->auth_manager_->is_bot());
+ if (!top_thread_message_id.is_valid() && is_thread_message(dialog_id, message_id, reply_info, content_type)) {
+ top_thread_message_id = message_id;
+ is_topic_message = (content_type == MessageContentType::TopicCreate);
+ }
+ if (top_thread_message_id.is_valid() && dialog_type != DialogType::Channel) {
+ // just in case
+ top_thread_message_id = MessageId();
+ }
+ if (!top_thread_message_id.is_valid()) {
+ // just in case
+ is_topic_message = false;
+ }
+ auto reactions =
+ MessageReactions::get_message_reactions(td_, std::move(message_info.reactions), td_->auth_manager_->is_bot());
+ if (reactions != nullptr) {
+ reactions->sort_reactions(active_reaction_pos_);
+ reactions->fix_chosen_reaction(get_my_dialog_id());
+ }
+
+ bool has_forward_info = message_info.forward_header != nullptr;
+
+ if (sender_dialog_id.is_valid() && sender_dialog_id != dialog_id && have_dialog_info_force(sender_dialog_id)) {
+ force_create_dialog(sender_dialog_id, "create_message", sender_dialog_id.get_type() != DialogType::User);
}
- LOG(INFO) << "Receive " << message_id << " in " << dialog_id << " from " << sender_user_id;
+ LOG(INFO) << "Receive " << message_id << " in " << dialog_id << " from " << sender_user_id << "/" << sender_dialog_id;
auto message = make_unique<Message>();
- message->random_y = get_random_y(message_id);
- message->message_id = message_id;
+ set_message_id(message, message_id);
message->sender_user_id = sender_user_id;
+ message->sender_dialog_id = sender_dialog_id;
message->date = date;
+ message->ttl_period = ttl_period;
message->ttl = ttl;
+ message->disable_web_page_preview = message_info.disable_web_page_preview;
message->edit_date = edit_date;
message->random_id = message_info.random_id;
- message->forward_info = get_message_forward_info(std::move(message_info.forward_header));
+ message->forward_info = get_message_forward_info(std::move(message_info.forward_header), {dialog_id, message_id});
message->reply_to_message_id = reply_to_message_id;
+ message->reply_in_dialog_id = reply_in_dialog_id;
+ message->top_thread_message_id = top_thread_message_id;
+ message->is_topic_message = is_topic_message;
message->via_bot_user_id = via_bot_user_id;
+ message->restriction_reasons = std::move(message_info.restriction_reasons);
message->author_signature = std::move(message_info.author_signature);
message->is_outgoing = is_outgoing;
message->is_channel_post = is_channel_post;
message->contains_mention =
- !is_outgoing && dialog_type != DialogType::User && (flags & MESSAGE_FLAG_HAS_MENTION) != 0;
+ !is_outgoing && dialog_type != DialogType::User &&
+ ((flags & MESSAGE_FLAG_HAS_MENTION) != 0 || content_type == MessageContentType::PinMessage);
message->contains_unread_mention =
- message_id.is_server() && message->contains_mention && (flags & MESSAGE_FLAG_HAS_UNREAD_CONTENT) != 0 &&
+ !message_id.is_scheduled() && message_id.is_server() && message->contains_mention &&
+ (flags & MESSAGE_FLAG_HAS_UNREAD_CONTENT) != 0 &&
(dialog_type == DialogType::Chat || (dialog_type == DialogType::Channel && !is_broadcast_channel(dialog_id)));
message->disable_notification = is_silent;
message->is_content_secret = is_content_secret;
- message->views = views;
+ message->hide_edit_date = hide_edit_date;
+ message->is_from_scheduled = is_from_scheduled;
+ message->is_pinned = is_pinned;
+ message->noforwards = noforwards;
+ message->interaction_info_update_date = G()->unix_time();
+ message->view_count = view_count;
+ message->forward_count = forward_count;
+ message->reply_info = std::move(reply_info);
+ message->reactions = std::move(reactions);
+ message->legacy_layer = (is_legacy ? MTPROTO_LAYER : 0);
message->content = std::move(message_info.content);
message->reply_markup = get_reply_markup(std::move(message_info.reply_markup), td_->auth_manager_->is_bot(), false,
- message->contains_mention || dialog_id.get_type() == DialogType::User);
+ message->contains_mention || dialog_type == DialogType::User);
- auto content_id = message->content->get_id();
- if (content_id == MessageExpiredPhoto::ID || content_id == MessageExpiredVideo::ID) {
- CHECK(message->ttl == 0); // ttl is ignored/set to 0 if the message has already been expired
+ if (content_type == MessageContentType::ExpiredPhoto || content_type == MessageContentType::ExpiredVideo) {
+ CHECK(message->ttl == 0); // TTL is ignored/set to 0 if the message has already been expired
if (message->reply_markup != nullptr) {
if (message->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
message->had_reply_markup = true;
@@ -9951,27 +14965,75 @@ std::pair<DialogId, unique_ptr<MessagesManager::Message>> MessagesManager::creat
message->reply_markup = nullptr;
}
message->reply_to_message_id = MessageId();
+ message->reply_to_random_id = 0;
+ message->reply_in_dialog_id = DialogId();
+ message->top_thread_message_id = MessageId();
+ message->is_topic_message = false;
+ message->linked_top_thread_message_id = MessageId();
}
if (message_info.media_album_id != 0) {
- if (!is_allowed_media_group_content(content_id)) {
- LOG(ERROR) << "Receive media group id " << message_info.media_album_id << " in " << message_id << " from "
- << dialog_id << " with content "
- << oneline(to_string(
- get_message_content_object(message->content.get(), message->date, is_content_secret)));
+ if (!is_allowed_media_group_content(content_type)) {
+ if (content_type != MessageContentType::Unsupported) {
+ LOG(ERROR) << "Receive media group identifier " << message_info.media_album_id << " in " << message_id
+ << " from " << dialog_id << " with content "
+ << oneline(to_string(get_message_content_object(message->content.get(), td_, dialog_id,
+ message->date, is_content_secret, false, -1)));
+ }
} else {
message->media_album_id = message_info.media_album_id;
}
}
+ if (message->forward_info == nullptr && has_forward_info) {
+ message->had_forward_info = true;
+ }
+
return {dialog_id, std::move(message)};
}
+MessageId MessagesManager::find_old_message_id(DialogId dialog_id, MessageId message_id) const {
+ if (message_id.is_scheduled()) {
+ CHECK(message_id.is_scheduled_server());
+ auto dialog_it = update_scheduled_message_ids_.find(dialog_id);
+ if (dialog_it != update_scheduled_message_ids_.end()) {
+ auto it = dialog_it->second.find(message_id.get_scheduled_server_message_id());
+ if (it != dialog_it->second.end()) {
+ return it->second;
+ }
+ }
+ } else {
+ CHECK(message_id.is_server());
+ auto it = update_message_ids_.find(FullMessageId(dialog_id, message_id));
+ if (it != update_message_ids_.end()) {
+ return it->second;
+ }
+ }
+ return MessageId();
+}
+
+void MessagesManager::delete_update_message_id(DialogId dialog_id, MessageId message_id) {
+ if (message_id.is_scheduled()) {
+ CHECK(message_id.is_scheduled_server());
+ auto dialog_it = update_scheduled_message_ids_.find(dialog_id);
+ CHECK(dialog_it != update_scheduled_message_ids_.end());
+ auto erased_count = dialog_it->second.erase(message_id.get_scheduled_server_message_id());
+ CHECK(erased_count > 0);
+ if (dialog_it->second.empty()) {
+ update_scheduled_message_ids_.erase(dialog_it);
+ }
+ } else {
+ CHECK(message_id.is_server());
+ auto erased_count = update_message_ids_.erase(FullMessageId(dialog_id, message_id));
+ CHECK(erased_count > 0);
+ }
+}
+
FullMessageId MessagesManager::on_get_message(tl_object_ptr<telegram_api::Message> message_ptr, bool from_update,
- bool is_channel_message, bool have_previous, bool have_next,
- const char *source) {
- return on_get_message(parse_telegram_api_message(std::move(message_ptr), source), from_update, is_channel_message,
- have_previous, have_next, source);
+ bool is_channel_message, bool is_scheduled, bool have_previous,
+ bool have_next, const char *source) {
+ return on_get_message(parse_telegram_api_message(std::move(message_ptr), is_scheduled, source), from_update,
+ is_channel_message, have_previous, have_next, source);
}
FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool from_update, bool is_channel_message,
@@ -9984,78 +15046,118 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool f
}
MessageId message_id = new_message->message_id;
- DialogId my_dialog_id = DialogId(td_->contacts_manager_->get_my_id("on_get_message"));
-
new_message->have_previous = have_previous;
new_message->have_next = have_next;
bool need_update = from_update;
bool need_update_dialog_pos = false;
- FullMessageId full_message_id(dialog_id, message_id);
- auto it = update_message_ids_.find(full_message_id);
- if (it != update_message_ids_.end()) {
+ MessageId old_message_id = find_old_message_id(dialog_id, message_id);
+ bool is_sent_message = false;
+ if (old_message_id.is_valid() || old_message_id.is_valid_scheduled()) {
+ LOG(INFO) << "Found temporary " << old_message_id << " for " << FullMessageId{dialog_id, message_id};
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
- if (!from_update) {
- LOG_IF(ERROR, message_id.get() <= d->last_new_message_id.get())
- << "New " << message_id << " has id less than last_new_message_id = " << d->last_new_message_id;
- LOG(ERROR) << "Ignore " << it->second << " received not through update from " << source << ": "
- << oneline(to_string(get_message_object(dialog_id, new_message.get()))); // TODO move to INFO
- dump_debug_message_op(d, 3); // TODO remove
- if (dialog_id.get_type() == DialogType::Channel && have_input_peer(dialog_id, AccessRights::Read)) {
- channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
+ if (!from_update && !message_id.is_scheduled()) {
+ if (message_id <= d->last_new_message_id) {
+ if (get_message_force(d, message_id, "receive missed unsent message not from update") != nullptr) {
+ LOG(ERROR) << "New " << old_message_id << "/" << message_id << " in " << dialog_id << " from " << source
+ << " has identifier less than last_new_message_id = " << d->last_new_message_id;
+ return FullMessageId();
+ }
+ // if there is no message yet, then it is likely was missed because of a server bug and is being repaired via
+ // get_message_from_server from after_get_difference
+ if (!has_qts_messages(dialog_id)) {
+ // TODO move to INFO
+ LOG(ERROR) << "Receive " << old_message_id << "/" << message_id << " in " << dialog_id << " from " << source
+ << " with identifier less than last_new_message_id = " << d->last_new_message_id
+ << " and trying to add it anyway";
+ }
+ } else {
+ // TODO move to INFO
+ LOG(ERROR) << "Ignore " << old_message_id << "/" << message_id << " received not through update from " << source
+ << ": " << oneline(to_string(get_message_object(dialog_id, new_message.get(), "on_get_message")));
+ if (dialog_id.get_type() == DialogType::Channel && have_input_peer(dialog_id, AccessRights::Read)) {
+ channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
+ }
+ return FullMessageId();
}
- return FullMessageId();
}
- MessageId old_message_id = it->second;
-
- update_message_ids_.erase(it);
+ delete_update_message_id(dialog_id, message_id);
- if (!new_message->is_outgoing && dialog_id != my_dialog_id) {
+ if (!new_message->is_outgoing && dialog_id != get_my_dialog_id()) {
// sent message is not from me
- LOG(ERROR) << "Sent in " << dialog_id << " " << message_id << " is sent by " << new_message->sender_user_id;
+ LOG(ERROR) << "Sent in " << dialog_id << " " << message_id << " is sent by " << new_message->sender_user_id << "/"
+ << new_message->sender_dialog_id;
return FullMessageId();
}
+ // must be called before delete_message
+ update_reply_to_message_id(dialog_id, old_message_id, message_id, true, "on_get_message");
+
+ being_readded_message_id_ = {dialog_id, old_message_id};
unique_ptr<Message> old_message =
delete_message(d, old_message_id, false, &need_update_dialog_pos, "add sent message");
if (old_message == nullptr) {
- // message has already been deleted by the user or sent to inaccessible channel
- // don't need to send update to the user, because the message has already been deleted
- LOG(INFO) << "Delete already deleted sent " << new_message->message_id << " from server";
- delete_messages_from_server(dialog_id, {new_message->message_id}, true, 0, Auto());
+ delete_sent_message_on_server(dialog_id, message_id, old_message_id);
+ being_readded_message_id_ = FullMessageId();
return FullMessageId();
}
+ old_message_id = old_message->message_id;
need_update = false;
- new_message->message_id = old_message_id;
- new_message->random_y = get_random_y(new_message->message_id);
+ if (old_message_id.is_valid() && message_id.is_valid() && message_id < old_message_id &&
+ !has_qts_messages(dialog_id) && !d->had_yet_unsent_message_id_overflow) {
+ LOG(ERROR) << "Sent " << old_message_id << " to " << dialog_id << " as " << message_id;
+ }
+
+ set_message_id(new_message, old_message_id);
+ new_message->from_database = false;
new_message->have_previous = false;
new_message->have_next = false;
- update_message(d, old_message, std::move(new_message), true, &need_update_dialog_pos);
+ update_message(d, old_message.get(), std::move(new_message), &need_update_dialog_pos, false);
new_message = std::move(old_message);
- new_message->message_id = message_id;
- new_message->random_y = get_random_y(new_message->message_id);
+ if (new_message->reply_to_message_id != MessageId() && new_message->reply_to_message_id.is_yet_unsent()) {
+ LOG(INFO) << "Drop reply to " << new_message->reply_to_message_id;
+ new_message->reply_to_message_id = MessageId();
+ }
+
+ set_message_id(new_message, message_id);
send_update_message_send_succeeded(d, old_message_id, new_message.get());
- try_add_active_live_location(dialog_id, new_message.get());
+ if (!message_id.is_scheduled()) {
+ is_sent_message = true;
+ }
- new_message->have_previous = true;
- new_message->have_next = true;
+ if (!from_update) {
+ new_message->have_previous = have_previous;
+ new_message->have_next = have_next;
+ } else {
+ new_message->have_previous = true;
+ new_message->have_next = true;
+ }
}
- Message *m = add_message_to_dialog(dialog_id, std::move(new_message), from_update, &need_update,
- &need_update_dialog_pos, source);
+ const Message *m = add_message_to_dialog(dialog_id, std::move(new_message), from_update, &need_update,
+ &need_update_dialog_pos, source);
+ being_readded_message_id_ = FullMessageId();
Dialog *d = get_dialog(dialog_id);
if (m == nullptr) {
if (need_update_dialog_pos && d != nullptr) {
send_update_chat_last_message(d, "on_get_message");
}
+ if (old_message_id.is_valid() || old_message_id.is_valid_scheduled()) {
+ CHECK(d != nullptr);
+ if (!old_message_id.is_valid() || !message_id.is_valid() || old_message_id <= message_id) {
+ LOG(ERROR) << "Failed to add just sent " << old_message_id << " to " << dialog_id << " as " << message_id
+ << " from " << source << ": " << debug_add_message_to_dialog_fail_reason_;
+ }
+ send_update_delete_messages(dialog_id, {message_id.get()}, true);
+ }
return FullMessageId();
}
@@ -10073,101 +15175,158 @@ FullMessageId MessagesManager::on_get_message(MessageInfo &&message_info, bool f
send_update_new_message(d, m);
}
- if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ if (is_sent_message) {
+ try_add_active_live_location(dialog_id, m);
+
+ // add_message_to_dialog will not update counts, because need_update == false
+ update_message_count_by_index(d, +1, m);
+ }
+
+ if (is_sent_message || (need_update && !message_id.is_scheduled())) {
+ update_reply_count_by_message(d, +1, m);
+ update_forward_count(dialog_id, m);
+ }
+
+ if (dialog_id.get_type() == DialogType::Channel && !have_input_peer(dialog_id, AccessRights::Read)) {
auto p = delete_message(d, message_id, false, &need_update_dialog_pos, "get a message in inaccessible chat");
CHECK(p.get() == m);
// CHECK(d->messages == nullptr);
- send_update_delete_messages(dialog_id, {message_id.get()}, false, false);
+ send_update_delete_messages(dialog_id, {p->message_id.get()}, false);
// don't need to update dialog pos
return FullMessageId();
}
+ if (m->message_id.is_scheduled()) {
+ send_update_chat_has_scheduled_messages(d, false);
+ }
+
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "on_get_message");
}
- if (need_update && m->reply_markup != nullptr && m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard &&
- m->reply_markup->is_personal && !td_->auth_manager_->is_bot()) {
+ // set dialog reply markup only after updateNewMessage and updateChatLastMessage are sent
+ if (need_update && m->reply_markup != nullptr && !m->message_id.is_scheduled() &&
+ m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard && m->reply_markup->is_personal &&
+ !td_->auth_manager_->is_bot()) {
set_dialog_reply_markup(d, message_id);
}
return FullMessageId(dialog_id, message_id);
}
-void MessagesManager::set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source) {
+void MessagesManager::set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source,
+ const Message *m) {
+ CHECK(!last_message_id.is_scheduled());
+
LOG(INFO) << "Set " << d->dialog_id << " last message to " << last_message_id << " from " << source;
d->last_message_id = last_message_id;
+ if (m != nullptr) {
+ d->last_media_album_id = m->media_album_id;
+ } else if (!last_message_id.is_valid()) {
+ d->last_media_album_id = 0;
+ } else {
+ m = get_message(d, last_message_id);
+ if (m == nullptr) {
+ LOG(ERROR) << "Failed to find last " << last_message_id << " in " << d->dialog_id;
+ d->last_media_album_id = 0;
+ } else {
+ d->last_media_album_id = m->media_album_id;
+ }
+ }
+ if (!last_message_id.is_valid()) {
+ d->suffix_load_first_message_id_ = MessageId();
+ d->suffix_load_done_ = false;
+ }
if (last_message_id.is_valid() && d->delete_last_message_date != 0) {
d->delete_last_message_date = 0;
d->deleted_last_message_id = MessageId();
d->is_last_message_deleted_locally = false;
on_dialog_updated(d->dialog_id, "update_delete_last_message_date");
}
+ if (d->pending_last_message_date != 0) {
+ d->pending_last_message_date = 0;
+ d->pending_last_message_id = MessageId();
+ }
}
void MessagesManager::set_dialog_first_database_message_id(Dialog *d, MessageId first_database_message_id,
const char *source) {
+ CHECK(!first_database_message_id.is_scheduled());
+ if (first_database_message_id == d->first_database_message_id) {
+ return;
+ }
+
LOG(INFO) << "Set " << d->dialog_id << " first database message to " << first_database_message_id << " from "
<< source;
d->first_database_message_id = first_database_message_id;
+ on_dialog_updated(d->dialog_id, "set_dialog_first_database_message_id");
}
void MessagesManager::set_dialog_last_database_message_id(Dialog *d, MessageId last_database_message_id,
- const char *source) {
+ const char *source, bool is_loaded_from_database) {
+ CHECK(!last_database_message_id.is_scheduled());
+ if (last_database_message_id == d->last_database_message_id) {
+ return;
+ }
+
LOG(INFO) << "Set " << d->dialog_id << " last database message to " << last_database_message_id << " from " << source;
+ d->debug_set_dialog_last_database_message_id = source;
d->last_database_message_id = last_database_message_id;
+ if (!is_loaded_from_database) {
+ on_dialog_updated(d->dialog_id, "set_dialog_last_database_message_id");
+ }
}
-void MessagesManager::set_dialog_last_new_message_id(Dialog *d, MessageId last_new_message_id, const char *source) {
- CHECK(last_new_message_id.get() > d->last_new_message_id.get());
- CHECK(d->dialog_id.get_type() == DialogType::SecretChat || last_new_message_id.is_server());
- if (!d->last_new_message_id.is_valid()) {
- delete_all_dialog_messages_from_database(d->dialog_id, MessageId::max(), "set_dialog_last_new_message_id");
- set_dialog_first_database_message_id(d, MessageId(), "set_dialog_last_new_message_id");
- set_dialog_last_database_message_id(d, MessageId(), "set_dialog_last_new_message_id");
- if (d->dialog_id.get_type() != DialogType::SecretChat) {
- d->have_full_history = false;
- }
- for (auto &first_message_id : d->first_database_message_id_by_index) {
- first_message_id = last_new_message_id;
- }
+void MessagesManager::remove_dialog_newer_messages(Dialog *d, MessageId from_message_id, const char *source) {
+ LOG(INFO) << "Remove messages in " << d->dialog_id << " newer than " << from_message_id << " from " << source;
+ CHECK(!d->last_new_message_id.is_valid());
- MessagesConstIterator it(d, MessageId::max());
- vector<MessageId> to_delete_message_ids;
- while (*it != nullptr) {
- auto message_id = (*it)->message_id;
- if (message_id.get() <= last_new_message_id.get()) {
- break;
- }
- if (!message_id.is_yet_unsent()) {
- to_delete_message_ids.push_back(message_id);
- }
- --it;
- }
- if (!to_delete_message_ids.empty()) {
- LOG(ERROR) << "Delete " << format::as_array(to_delete_message_ids) << " because of received last new "
- << last_new_message_id << " in " << d->dialog_id;
+ delete_all_dialog_messages_from_database(d, MessageId::max(), "remove_dialog_newer_messages");
+ set_dialog_first_database_message_id(d, MessageId(), "remove_dialog_newer_messages");
+ set_dialog_last_database_message_id(d, MessageId(), source);
+ if (d->dialog_id.get_type() != DialogType::SecretChat && !d->is_empty) {
+ d->have_full_history = false;
+ d->have_full_history_source = 0;
+ }
+ invalidate_message_indexes(d);
- vector<int64> deleted_message_ids;
- bool need_update_dialog_pos = false;
- for (auto message_id : to_delete_message_ids) {
- if (delete_message(d, message_id, false, &need_update_dialog_pos, "set_dialog_last_new_message_id") !=
- nullptr) {
- deleted_message_ids.push_back(message_id.get());
- }
- }
- if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "set_dialog_last_new_message_id");
+ vector<MessageId> to_delete_message_ids;
+ find_newer_messages(d->messages.get(), from_message_id, to_delete_message_ids);
+ td::remove_if(to_delete_message_ids, [](MessageId message_id) { return message_id.is_yet_unsent(); });
+ if (!to_delete_message_ids.empty()) {
+ LOG(INFO) << "Delete " << format::as_array(to_delete_message_ids) << " newer than " << from_message_id << " in "
+ << d->dialog_id << " from " << source;
+
+ vector<int64> deleted_message_ids;
+ bool need_update_dialog_pos = false;
+ for (auto message_id : to_delete_message_ids) {
+ auto message = delete_message(d, message_id, false, &need_update_dialog_pos, "remove_dialog_newer_messages");
+ if (message != nullptr) {
+ deleted_message_ids.push_back(message->message_id.get());
}
- send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), false, false);
}
+ if (need_update_dialog_pos) {
+ send_update_chat_last_message(d, "remove_dialog_newer_messages");
+ }
+ send_update_delete_messages(d->dialog_id, std::move(deleted_message_ids), false);
+ }
+}
+
+void MessagesManager::set_dialog_last_new_message_id(Dialog *d, MessageId last_new_message_id, const char *source) {
+ CHECK(!last_new_message_id.is_scheduled());
+
+ LOG_CHECK(last_new_message_id > d->last_new_message_id)
+ << last_new_message_id << " " << d->last_new_message_id << " " << source;
+ CHECK(d->dialog_id.get_type() == DialogType::SecretChat || last_new_message_id.is_server());
+ if (!d->last_new_message_id.is_valid()) {
+ remove_dialog_newer_messages(d, last_new_message_id, source);
auto last_new_message = get_message(d, last_new_message_id);
if (last_new_message != nullptr) {
- add_message_to_database(d, last_new_message, "set_dialog_last_new_message_id");
- set_dialog_first_database_message_id(d, last_new_message_id, "set_dialog_last_new_message_id");
- set_dialog_last_database_message_id(d, last_new_message_id, "set_dialog_last_new_message_id");
+ add_message_to_database(d, last_new_message, source);
+ set_dialog_first_database_message_id(d, last_new_message_id, source);
+ set_dialog_last_database_message_id(d, last_new_message_id, source);
try_restore_dialog_reply_markup(d, last_new_message);
}
}
@@ -10178,7 +15337,13 @@ void MessagesManager::set_dialog_last_new_message_id(Dialog *d, MessageId last_n
}
void MessagesManager::set_dialog_last_clear_history_date(Dialog *d, int32 date, MessageId last_clear_history_message_id,
- const char *source) {
+ const char *source, bool is_loaded_from_database) {
+ CHECK(!last_clear_history_message_id.is_scheduled());
+
+ if (d->last_clear_history_message_id == last_clear_history_message_id && d->last_clear_history_date == date) {
+ return;
+ }
+
LOG(INFO) << "Set " << d->dialog_id << " last clear history date to " << date << " of "
<< last_clear_history_message_id << " from " << source;
if (d->last_clear_history_message_id.is_valid()) {
@@ -10199,6 +15364,9 @@ void MessagesManager::set_dialog_last_clear_history_date(Dialog *d, int32 date,
d->last_clear_history_date = date;
d->last_clear_history_message_id = last_clear_history_message_id;
+ if (!is_loaded_from_database) {
+ on_dialog_updated(d->dialog_id, "set_dialog_last_clear_history_date");
+ }
if (d->last_clear_history_message_id.is_valid()) {
switch (d->dialog_id.get_type()) {
@@ -10217,10 +15385,51 @@ void MessagesManager::set_dialog_last_clear_history_date(Dialog *d, int32 date,
}
}
+void MessagesManager::set_dialog_unread_mention_count(Dialog *d, int32 unread_mention_count) {
+ CHECK(d->unread_mention_count != unread_mention_count);
+ CHECK(unread_mention_count >= 0);
+
+ d->unread_mention_count = unread_mention_count;
+ d->message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadMention)] = unread_mention_count;
+}
+
+void MessagesManager::set_dialog_unread_reaction_count(Dialog *d, int32 unread_reaction_count) {
+ CHECK(d->unread_reaction_count != unread_reaction_count);
+ CHECK(unread_reaction_count >= 0);
+
+ d->unread_reaction_count = unread_reaction_count;
+ d->message_count_by_index[message_search_filter_index(MessageSearchFilter::UnreadReaction)] = unread_reaction_count;
+}
+
void MessagesManager::set_dialog_is_empty(Dialog *d, const char *source) {
LOG(INFO) << "Set " << d->dialog_id << " is_empty to true from " << source;
+ CHECK(d->have_full_history);
d->is_empty = true;
+ if (d->server_unread_count + d->local_unread_count > 0) {
+ MessageId max_message_id =
+ d->last_database_message_id.is_valid() ? d->last_database_message_id : d->last_new_message_id;
+ if (max_message_id.is_valid()) {
+ read_history_inbox(d->dialog_id, max_message_id, -1, "set_dialog_is_empty");
+ }
+ if (d->server_unread_count != 0 || d->local_unread_count != 0) {
+ set_dialog_last_read_inbox_message_id(d, MessageId::min(), 0, 0, true, "set_dialog_is_empty");
+ }
+ }
+ if (d->unread_mention_count > 0) {
+ set_dialog_unread_mention_count(d, 0);
+ send_update_chat_unread_mention_count(d);
+ }
+ if (d->unread_reaction_count > 0) {
+ set_dialog_unread_reaction_count(d, 0);
+ send_update_chat_unread_reaction_count(d, "set_dialog_is_empty");
+ }
+ if (d->reply_markup_message_id != MessageId()) {
+ set_dialog_reply_markup(d, MessageId());
+ }
+ std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), 0);
+ d->notification_id_to_message_id.clear();
+
if (d->delete_last_message_date != 0) {
if (d->is_last_message_deleted_locally && d->last_clear_history_date == 0) {
set_dialog_last_clear_history_date(d, d->delete_last_message_date, d->deleted_last_message_id,
@@ -10232,34 +15441,135 @@ void MessagesManager::set_dialog_is_empty(Dialog *d, const char *source) {
on_dialog_updated(d->dialog_id, "set_dialog_is_empty");
}
+ if (d->pending_last_message_date != 0) {
+ d->pending_last_message_date = 0;
+ d->pending_last_message_id = MessageId();
+ }
+ if (d->last_database_message_id.is_valid()) {
+ set_dialog_first_database_message_id(d, MessageId(), "set_dialog_is_empty");
+ set_dialog_last_database_message_id(d, MessageId(), "set_dialog_is_empty");
+ }
+
+ update_dialog_pos(d, source);
+}
+
+bool MessagesManager::is_dialog_pinned(DialogListId dialog_list_id, DialogId dialog_id) const {
+ if (get_dialog_pinned_order(dialog_list_id, dialog_id) != DEFAULT_ORDER) {
+ return true;
+ }
+ if (dialog_list_id.is_filter()) {
+ const auto *filter = get_dialog_filter(dialog_list_id.get_filter_id());
+ if (filter != nullptr && InputDialogId::contains(filter->pinned_dialog_ids, dialog_id)) {
+ return true;
+ }
+ }
+ return false;
+}
- update_dialog_pos(d, false, source);
+int64 MessagesManager::get_dialog_pinned_order(DialogListId dialog_list_id, DialogId dialog_id) const {
+ return get_dialog_pinned_order(get_dialog_list(dialog_list_id), dialog_id);
}
-void MessagesManager::set_dialog_is_pinned(DialogId dialog_id, bool is_pinned) {
+int64 MessagesManager::get_dialog_pinned_order(const DialogList *list, DialogId dialog_id) {
+ if (list != nullptr && !list->pinned_dialog_id_orders_.empty()) {
+ auto it = list->pinned_dialog_id_orders_.find(dialog_id);
+ if (it != list->pinned_dialog_id_orders_.end()) {
+ return it->second;
+ }
+ }
+ return DEFAULT_ORDER;
+}
+
+bool MessagesManager::set_dialog_is_pinned(DialogId dialog_id, bool is_pinned) {
+ if (td_->auth_manager_->is_bot()) {
+ return false;
+ }
+
Dialog *d = get_dialog(dialog_id);
- set_dialog_is_pinned(d, is_pinned);
- update_dialog_pos(d, false, "set_dialog_is_pinned");
+ CHECK(d != nullptr);
+ return set_dialog_is_pinned(DialogListId(d->folder_id), d, is_pinned);
}
-void MessagesManager::set_dialog_is_pinned(Dialog *d, bool is_pinned) {
+// only removes the Dialog from the dialog list, but changes nothing in the corresponding DialogFilter
+bool MessagesManager::set_dialog_is_pinned(DialogListId dialog_list_id, Dialog *d, bool is_pinned,
+ bool need_update_dialog_lists) {
+ if (td_->auth_manager_->is_bot()) {
+ return false;
+ }
+
CHECK(d != nullptr);
- bool was_pinned = d->pinned_order != DEFAULT_ORDER;
- d->pinned_order = is_pinned ? get_next_pinned_dialog_order() : DEFAULT_ORDER;
- on_dialog_updated(d->dialog_id, "set_dialog_is_pinned");
-
- if (is_pinned != was_pinned) {
- LOG(INFO) << "Set " << d->dialog_id << " is pinned to " << is_pinned;
- CHECK(d == get_dialog(d->dialog_id)) << "Wrong " << d->dialog_id << " in set_dialog_is_pinned";
- update_dialog_pos(d, false, "set_dialog_is_pinned", false);
- DialogDate dialog_date(d->order, d->dialog_id);
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateChatIsPinned>(d->dialog_id.get(), is_pinned,
- dialog_date <= last_dialog_date_ ? d->order : 0));
+ if (d->order == DEFAULT_ORDER && is_pinned) {
+ // the chat can't be pinned
+ return false;
+ }
+
+ auto positions = get_dialog_positions(d);
+ auto *list = get_dialog_list(dialog_list_id);
+ if (list == nullptr) {
+ return false;
+ }
+ if (!list->are_pinned_dialogs_inited_) {
+ return false;
}
+ bool was_pinned = false;
+ for (size_t pos = 0; pos < list->pinned_dialogs_.size(); pos++) {
+ auto &pinned_dialog = list->pinned_dialogs_[pos];
+ if (pinned_dialog.get_dialog_id() == d->dialog_id) {
+ // the dialog was already pinned
+ if (is_pinned) {
+ if (pos == 0) {
+ return false;
+ }
+ auto order = get_next_pinned_dialog_order();
+ pinned_dialog = DialogDate(order, d->dialog_id);
+ std::rotate(list->pinned_dialogs_.begin(), list->pinned_dialogs_.begin() + pos,
+ list->pinned_dialogs_.begin() + pos + 1);
+ list->pinned_dialog_id_orders_[d->dialog_id] = order;
+ } else {
+ list->pinned_dialogs_.erase(list->pinned_dialogs_.begin() + pos);
+ list->pinned_dialog_id_orders_.erase(d->dialog_id);
+ }
+ was_pinned = true;
+ break;
+ }
+ }
+ if (!was_pinned) {
+ if (!is_pinned) {
+ return false;
+ }
+ auto order = get_next_pinned_dialog_order();
+ list->pinned_dialogs_.insert(list->pinned_dialogs_.begin(), {order, d->dialog_id});
+ list->pinned_dialog_id_orders_.emplace(d->dialog_id, order);
+ }
+
+ LOG(INFO) << "Set " << d->dialog_id << " is pinned in " << dialog_list_id << " to " << is_pinned;
+
+ save_pinned_folder_dialog_ids(*list);
+
+ if (need_update_dialog_lists) {
+ update_dialog_lists(d, std::move(positions), true, false, "set_dialog_is_pinned");
+ }
+ return true;
+}
+
+void MessagesManager::save_pinned_folder_dialog_ids(const DialogList &list) const {
+ if (!list.dialog_list_id.is_folder() || !G()->parameters().use_message_db) {
+ return;
+ }
+ G()->td_db()->get_binlog_pmc()->set(
+ PSTRING() << "pinned_dialog_ids" << list.dialog_list_id.get_folder_id().get(),
+ implode(transform(list.pinned_dialogs_,
+ [](auto &pinned_dialog) { return PSTRING() << pinned_dialog.get_dialog_id().get(); }),
+ ','));
}
void MessagesManager::set_dialog_reply_markup(Dialog *d, MessageId message_id) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ CHECK(!message_id.is_scheduled());
+
if (d->reply_markup_message_id != message_id) {
on_dialog_updated(d->dialog_id, "set_dialog_reply_markup");
}
@@ -10267,7 +15577,7 @@ void MessagesManager::set_dialog_reply_markup(Dialog *d, MessageId message_id) {
d->need_restore_reply_markup = false;
if (d->reply_markup_message_id.is_valid() || message_id.is_valid()) {
- CHECK(d == get_dialog(d->dialog_id)) << "Wrong " << d->dialog_id << " in set_dialog_reply_markup";
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_reply_markup";
d->reply_markup_message_id = message_id;
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateChatReplyMarkup>(d->dialog_id.get(), message_id.get()));
@@ -10279,6 +15589,7 @@ void MessagesManager::try_restore_dialog_reply_markup(Dialog *d, const Message *
return;
}
+ CHECK(!m->message_id.is_scheduled());
if (m->had_reply_markup) {
LOG(INFO) << "Restore deleted reply markup in " << d->dialog_id;
set_dialog_reply_markup(d, MessageId());
@@ -10289,12 +15600,133 @@ void MessagesManager::try_restore_dialog_reply_markup(Dialog *d, const Message *
}
}
-// TODO this function needs to be merged with on_send_message_success
+void MessagesManager::set_dialog_pinned_message_notification(Dialog *d, MessageId message_id, const char *source) {
+ CHECK(d != nullptr);
+ CHECK(!message_id.is_scheduled());
+ auto old_message_id = d->pinned_message_notification_message_id;
+ if (old_message_id == message_id) {
+ return;
+ }
+ VLOG(notifications) << "Change pinned message notification in " << d->dialog_id << " from " << old_message_id
+ << " to " << message_id;
+ if (old_message_id.is_valid()) {
+ auto m = get_message_force(d, old_message_id, source);
+ if (m != nullptr && m->notification_id.is_valid() && is_message_notification_active(d, m)) {
+ // Can't remove pinned_message_notification_message_id before the call,
+ // because the notification needs to be still active inside remove_message_notification_id
+ remove_message_notification_id(d, m, true, false, true);
+ on_message_changed(d, m, false, source);
+ } else {
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id,
+ d->mention_notification_group.group_id, old_message_id, false, source);
+ }
+ }
+ d->pinned_message_notification_message_id = message_id;
+ on_dialog_updated(d->dialog_id, source);
+}
+
+void MessagesManager::remove_dialog_pinned_message_notification(Dialog *d, const char *source) {
+ set_dialog_pinned_message_notification(d, MessageId(), source);
+}
+
+void MessagesManager::remove_scope_pinned_message_notifications(NotificationSettingsScope scope) {
+ VLOG(notifications) << "Remove pinned message notifications in " << scope;
+ dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
+ Dialog *d = dialog.get();
+ if (d->notification_settings.use_default_disable_pinned_message_notifications &&
+ d->mention_notification_group.group_id.is_valid() && d->pinned_message_notification_message_id.is_valid() &&
+ get_dialog_notification_setting_scope(dialog_id) == scope) {
+ remove_dialog_pinned_message_notification(d, "remove_scope_pinned_message_notifications");
+ }
+ });
+}
+
+void MessagesManager::on_update_scope_mention_notifications(NotificationSettingsScope scope,
+ bool disable_mention_notifications) {
+ VLOG(notifications) << "Remove mention notifications in " << scope;
+ dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
+ Dialog *d = dialog.get();
+ if (d->notification_settings.use_default_disable_mention_notifications &&
+ get_dialog_notification_setting_scope(dialog_id) == scope) {
+ if (!disable_mention_notifications) {
+ update_dialog_mention_notification_count(d);
+ } else {
+ remove_dialog_mention_notifications(d);
+ }
+ }
+ });
+}
+
+void MessagesManager::remove_dialog_mention_notifications(Dialog *d) {
+ auto notification_group_id = d->mention_notification_group.group_id;
+ if (!notification_group_id.is_valid()) {
+ return;
+ }
+ if (d->unread_mention_count == 0) {
+ return;
+ }
+ CHECK(!d->being_added_message_id.is_valid());
+
+ VLOG(notifications) << "Remove mention notifications in " << d->dialog_id;
+
+ vector<MessageId> message_ids;
+ FlatHashSet<NotificationId, NotificationIdHash> removed_notification_ids_set;
+ find_messages(d->messages.get(), message_ids, [](const Message *m) { return m->contains_unread_mention; });
+ VLOG(notifications) << "Found unread mentions in " << message_ids;
+ for (auto &message_id : message_ids) {
+ auto m = get_message(d, message_id);
+ CHECK(m != nullptr);
+ CHECK(m->message_id.is_valid());
+ if (m->notification_id.is_valid() && is_message_notification_active(d, m) &&
+ is_from_mention_notification_group(m)) {
+ removed_notification_ids_set.insert(m->notification_id);
+ }
+ }
+
+ message_ids = td_->notification_manager_->get_notification_group_message_ids(notification_group_id);
+ VLOG(notifications) << "Found active mention notifications in " << message_ids;
+ for (auto &message_id : message_ids) {
+ CHECK(!message_id.is_scheduled());
+ if (message_id != d->pinned_message_notification_message_id) {
+ auto m = get_message_force(d, message_id, "remove_dialog_mention_notifications");
+ if (m != nullptr && m->notification_id.is_valid() && is_message_notification_active(d, m)) {
+ CHECK(is_from_mention_notification_group(m));
+ removed_notification_ids_set.insert(m->notification_id);
+ }
+ }
+ }
+
+ vector<NotificationId> removed_notification_ids;
+ for (auto notification_id : removed_notification_ids_set) {
+ removed_notification_ids.push_back(notification_id);
+ }
+ for (size_t i = 0; i < removed_notification_ids.size(); i++) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, notification_group_id,
+ removed_notification_ids[i], false, i + 1 == removed_notification_ids.size(), Promise<Unit>(),
+ "remove_dialog_mention_notifications");
+ }
+}
+
+bool MessagesManager::set_dialog_last_notification(DialogId dialog_id, NotificationGroupInfo &group_info,
+ int32 last_notification_date, NotificationId last_notification_id,
+ const char *source) {
+ if (group_info.last_notification_date != last_notification_date ||
+ group_info.last_notification_id != last_notification_id) {
+ VLOG(notifications) << "Set " << group_info.group_id << '/' << dialog_id << " last notification to "
+ << last_notification_id << " sent at " << last_notification_date << " from " << source;
+ group_info.last_notification_date = last_notification_date;
+ group_info.last_notification_id = last_notification_id;
+ group_info.is_changed = true;
+ on_dialog_updated(dialog_id, "set_dialog_last_notification");
+ return true;
+ }
+ return false;
+}
+
void MessagesManager::on_update_sent_text_message(int64 random_id,
tl_object_ptr<telegram_api::MessageMedia> message_media,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities) {
- CHECK(message_media != nullptr);
- int32 message_media_id = message_media->get_id();
+ int32 message_media_id = message_media == nullptr ? telegram_api::messageMediaEmpty::ID : message_media->get_id();
LOG_IF(ERROR, message_media_id != telegram_api::messageMediaWebPage::ID &&
message_media_id != telegram_api::messageMediaEmpty::ID)
<< "Receive non web-page media for text message: " << oneline(to_string(message_media));
@@ -10305,116 +15737,138 @@ void MessagesManager::on_update_sent_text_message(int64 random_id,
return;
}
- auto dialog_id = it->second.get_dialog_id();
- auto m = get_message_force(it->second);
+ auto full_message_id = it->second;
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog(dialog_id);
+ auto m = get_message_force(d, full_message_id.get_message_id(), "on_update_sent_text_message");
if (m == nullptr) {
// message has already been deleted
return;
}
+ CHECK(m->message_id.is_yet_unsent());
+ full_message_id = FullMessageId(dialog_id, m->message_id);
- if (m->content->get_id() != MessageText::ID) {
- LOG(ERROR) << "Text message content has been already changed to " << m->content->get_id();
+ if (m->content->get_type() != MessageContentType::Text) {
+ LOG(ERROR) << "Text message content has been already changed to " << m->content->get_type();
return;
}
- auto message_text = static_cast<const MessageText *>(m->content.get());
- auto new_content = get_message_content(
- get_message_text(message_text->text.text, std::move(entities), m->forward_info ? m->forward_info->date : m->date),
- std::move(message_media), dialog_id, true /*likely ignored*/, UserId() /*likely ignored*/, nullptr /*ignored*/);
- if (new_content->get_id() != MessageText::ID) {
- LOG(ERROR) << "Text message content has changed to " << new_content->get_id();
+ const FormattedText *old_message_text = get_message_content_text(m->content.get());
+ CHECK(old_message_text != nullptr);
+ FormattedText new_message_text = get_message_text(
+ td_->contacts_manager_.get(), old_message_text->text, std::move(entities), true, td_->auth_manager_->is_bot(),
+ m->forward_info ? m->forward_info->date : m->date, m->media_album_id != 0, "on_update_sent_text_message");
+ auto new_content = get_message_content(td_, std::move(new_message_text), std::move(message_media), dialog_id,
+ true /*likely ignored*/, UserId() /*likely ignored*/, nullptr /*ignored*/,
+ nullptr, "on_update_sent_text_message");
+ if (new_content->get_type() != MessageContentType::Text) {
+ LOG(ERROR) << "Text message content has changed to " << new_content->get_type();
return;
}
- auto new_message_text = static_cast<const MessageText *>(new_content.get());
bool need_update = false;
bool is_content_changed = false;
+ merge_message_contents(td_, m->content.get(), new_content.get(), need_message_changed_warning(m), dialog_id, false,
+ is_content_changed, need_update);
- if (message_text->text.entities != new_message_text->text.entities) {
- is_content_changed = true;
- need_update = true;
- }
- if (message_text->web_page_id != new_message_text->web_page_id) {
- LOG(INFO) << "Old: " << message_text->web_page_id << ", new: " << new_message_text->web_page_id;
- is_content_changed = true;
- need_update |= td_->web_pages_manager_->have_web_page(message_text->web_page_id) ||
- td_->web_pages_manager_->have_web_page(new_message_text->web_page_id);
- }
-
- if (is_content_changed) {
+ if (is_content_changed || need_update) {
+ reregister_message_content(td_, m->content.get(), new_content.get(), full_message_id,
+ "on_update_sent_text_message");
m->content = std::move(new_content);
- m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_id());
- }
- if (need_update) {
- send_update_message_content(dialog_id, m->message_id, m->content.get(), m->date, m->is_content_secret,
- "on_update_sent_text_message");
+ m->is_content_secret = is_secret_message_content(m->ttl, MessageContentType::Text);
+
+ if (need_update) {
+ send_update_message_content(d, m, true, "on_update_sent_text_message");
+ }
+ on_message_changed(d, m, need_update, "on_update_sent_text_message");
}
}
-void MessagesManager::on_update_message_web_page(FullMessageId full_message_id, bool have_web_page) {
- waiting_for_web_page_messages_.erase(full_message_id);
+void MessagesManager::delete_pending_message_web_page(FullMessageId full_message_id) {
auto dialog_id = full_message_id.get_dialog_id();
Dialog *d = get_dialog(dialog_id);
- if (d == nullptr) {
- LOG(INFO) << "Can't find " << dialog_id;
- // dialog can be not yet added
- return;
- }
- auto message_id = full_message_id.get_message_id();
- Message *message = get_message(d, message_id);
- if (message == nullptr) {
- // message can be already deleted
- return;
- }
- CHECK(message->date > 0);
- auto content_type = message->content->get_id();
- CHECK(content_type == MessageText::ID);
- auto content = static_cast<MessageText *>(message->content.get());
- if (!content->web_page_id.is_valid()) {
- // webpage has already been received as empty
- LOG_IF(ERROR, have_web_page) << "Receive earlier not received web page";
- return;
- }
+ CHECK(d != nullptr);
+ Message *m = get_message(d, full_message_id.get_message_id());
+ CHECK(m != nullptr);
- if (!have_web_page) {
- content->web_page_id = WebPageId();
- // don't need to send an update
+ MessageContent *content = m->content.get();
+ CHECK(has_message_content_web_page(content));
+ unregister_message_content(td_, content, full_message_id, "delete_pending_message_web_page");
+ remove_message_content_web_page(content);
+ register_message_content(td_, content, full_message_id, "delete_pending_message_web_page");
- on_message_changed(d, message, "on_update_message_web_page");
- return;
- }
+ // don't need to send an updateMessageContent, because the web page was pending
- send_update_message_content(dialog_id, message_id, content, message->date, message->is_content_secret,
- "on_update_message_web_page");
+ on_message_changed(d, m, false, "delete_pending_message_web_page");
}
-void MessagesManager::on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>> &&dialogs, int32 total_count,
- vector<tl_object_ptr<telegram_api::Message>> &&messages, Promise<Unit> &&promise) {
+void MessagesManager::on_get_dialogs(FolderId folder_id, vector<tl_object_ptr<telegram_api::Dialog>> &&dialog_folders,
+ int32 total_count, vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<Unit> &&promise) {
if (td_->updates_manager_->running_get_difference()) {
LOG(INFO) << "Postpone result of getDialogs";
- pending_on_get_dialogs_.push_back(
- PendingOnGetDialogs{std::move(dialogs), total_count, std::move(messages), std::move(promise)});
+ pending_on_get_dialogs_.push_back(PendingOnGetDialogs{folder_id, std::move(dialog_folders), total_count,
+ std::move(messages), std::move(promise)});
return;
}
bool from_dialog_list = total_count >= 0;
bool from_get_dialog = total_count == -1;
bool from_pinned_dialog_list = total_count == -2;
- if (from_get_dialog && dialogs.size() == 1) {
- DialogId dialog_id(dialogs[0]->peer_);
- if (running_get_channel_difference(dialog_id)) {
+ if (from_get_dialog && dialog_folders.size() == 1 && dialog_folders[0]->get_id() == telegram_api::dialog::ID) {
+ DialogId dialog_id(static_cast<const telegram_api::dialog *>(dialog_folders[0].get())->peer_);
+ if (dialog_id.is_valid() && running_get_channel_difference(dialog_id)) {
LOG(INFO) << "Postpone result of channels getDialogs for " << dialog_id;
pending_channel_on_get_dialogs_.emplace(
- dialog_id, PendingOnGetDialogs{std::move(dialogs), total_count, std::move(messages), std::move(promise)});
+ dialog_id, PendingOnGetDialogs{folder_id, std::move(dialog_folders), total_count, std::move(messages),
+ std::move(promise)});
return;
}
}
- LOG(INFO) << "Receive " << dialogs.size() << " dialogs out of " << total_count << " in result of GetDialogsQuery";
- std::unordered_map<FullMessageId, DialogDate, FullMessageIdHash> full_message_id_to_dialog_date;
- std::unordered_map<FullMessageId, tl_object_ptr<telegram_api::Message>, FullMessageIdHash> full_message_id_to_message;
+ vector<tl_object_ptr<telegram_api::dialog>> dialogs;
+ for (auto &dialog_folder : dialog_folders) {
+ switch (dialog_folder->get_id()) {
+ case telegram_api::dialog::ID:
+ dialogs.push_back(telegram_api::move_object_as<telegram_api::dialog>(dialog_folder));
+ break;
+ case telegram_api::dialogFolder::ID: {
+ auto folder = telegram_api::move_object_as<telegram_api::dialogFolder>(dialog_folder);
+ if (from_pinned_dialog_list) {
+ // TODO update unread_muted_peers_count:int unread_unmuted_peers_count:int
+ // unread_muted_messages_count:int unread_unmuted_messages_count:int
+ FolderId folder_folder_id(folder->folder_->id_);
+ if (folder_folder_id == FolderId::archive()) {
+ // archive is expected in pinned dialogs list
+ break;
+ }
+ }
+ LOG(ERROR) << "Receive unexpected " << to_string(folder);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ const char *source = nullptr;
+ if (from_get_dialog) {
+ LOG(INFO) << "Process " << dialogs.size() << " chats";
+ source = "get chat";
+ } else if (from_pinned_dialog_list) {
+ LOG(INFO) << "Process " << dialogs.size() << " pinned chats in " << folder_id;
+ source = "get pinned chats";
+ } else {
+ LOG(INFO) << "Process " << dialogs.size() << " chats out of " << total_count << " in " << folder_id;
+ source = "get chat list";
+ }
+ FlatHashMap<FullMessageId, DialogDate, FullMessageIdHash> full_message_id_to_dialog_date;
+ FlatHashMap<FullMessageId, tl_object_ptr<telegram_api::Message>, FullMessageIdHash> full_message_id_to_message;
for (auto &message : messages) {
- auto full_message_id = get_full_message_id(message);
+ auto full_message_id = get_full_message_id(message, false);
+ if (!full_message_id.get_message_id().is_valid()) {
+ continue;
+ }
if (from_dialog_list) {
auto message_date = get_message_date(message);
int64 order = get_dialog_order(full_message_id.get_message_id(), message_date);
@@ -10444,7 +15898,7 @@ void MessagesManager::on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>>
break;
case DialogType::Channel:
if (!has_pts) {
- LOG(ERROR) << "Receive channel " << dialog_id << "without pts";
+ LOG(ERROR) << "Receive channel " << dialog_id << " without pts";
return promise.set_error(
Status::Error(500, "Wrong query result returned: receive supergroup chat without pts"));
}
@@ -10462,14 +15916,19 @@ void MessagesManager::on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>>
FullMessageId full_message_id(dialog_id, last_message_id);
auto it = full_message_id_to_dialog_date.find(full_message_id);
if (it == full_message_id_to_dialog_date.end()) {
- // can happen for bots, TODO disable getChats for bots
LOG(ERROR) << "Last " << last_message_id << " in " << dialog_id << " not found";
return promise.set_error(Status::Error(500, "Wrong query result returned: last message not found"));
}
+ FolderId dialog_folder_id((dialog->flags_ & DIALOG_FLAG_HAS_FOLDER_ID) != 0 ? dialog->folder_id_ : 0);
+ if (dialog_folder_id != folder_id) {
+ LOG(ERROR) << "Receive " << dialog_id << " in " << dialog_folder_id << " instead of " << folder_id;
+ continue;
+ }
+
DialogDate dialog_date = it->second;
CHECK(dialog_date.get_dialog_id() == dialog_id);
- if (max_dialog_date < dialog_date) {
+ if (dialog_date.get_date() > 0 && max_dialog_date < dialog_date) {
max_dialog_date = dialog_date;
}
} else {
@@ -10479,24 +15938,9 @@ void MessagesManager::on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>>
}
}
- if (from_dialog_list) {
- if (dialogs.empty()) {
- // if there is no more dialogs on the server
- max_dialog_date = MAX_DIALOG_DATE;
- }
- if (last_server_dialog_date_ < max_dialog_date) {
- last_server_dialog_date_ = max_dialog_date;
- update_last_dialog_date();
- } else {
- LOG(ERROR) << "Last server dialog date didn't increased";
- }
- }
- if (from_pinned_dialog_list) {
- max_dialog_date = DialogDate(get_dialog_order(MessageId(), MIN_PINNED_DIALOG_DATE - 1), DialogId());
- if (last_server_dialog_date_ < max_dialog_date) {
- last_server_dialog_date_ = max_dialog_date;
- update_last_dialog_date();
- }
+ if (from_dialog_list && total_count < narrow_cast<int32>(dialogs.size())) {
+ LOG(ERROR) << "Receive chat total_count = " << total_count << ", but " << dialogs.size() << " chats";
+ total_count = narrow_cast<int32>(dialogs.size());
}
vector<DialogId> added_dialog_ids;
@@ -10504,23 +15948,38 @@ void MessagesManager::on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>>
MessageId last_message_id(ServerMessageId(dialog->top_message_));
if (!last_message_id.is_valid() && from_dialog_list) {
// skip dialogs without messages
+ total_count--;
continue;
}
DialogId dialog_id(dialog->peer_);
+ if (td::contains(added_dialog_ids, dialog_id)) {
+ LOG(ERROR) << "Receive " << dialog_id << " twice in result of getChats with total_count = " << total_count;
+ continue;
+ }
added_dialog_ids.push_back(dialog_id);
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, source);
bool need_update_dialog_pos = false;
+ CHECK(!being_added_dialog_id_.is_valid());
+ being_added_dialog_id_ = dialog_id;
if (d == nullptr) {
- d = add_dialog(dialog_id);
+ d = add_dialog(dialog_id, source);
need_update_dialog_pos = true;
} else {
LOG(INFO) << "Receive already created " << dialog_id;
CHECK(d->dialog_id == dialog_id);
}
bool is_new = d->last_new_message_id == MessageId();
+ auto positions = get_dialog_positions(d);
- on_update_notify_settings(dialog_id.get(), std::move(dialog->notify_settings_));
+ set_dialog_folder_id(d, FolderId((dialog->flags_ & DIALOG_FLAG_HAS_FOLDER_ID) != 0 ? dialog->folder_id_ : 0));
+
+ on_update_dialog_notify_settings(dialog_id, std::move(dialog->notify_settings_), source);
+ if (!d->notification_settings.is_synchronized && !td_->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Failed to synchronize settings in " << dialog_id;
+ d->notification_settings.is_synchronized = true;
+ on_dialog_updated(dialog_id, "set notification_settings.is_synchronized");
+ }
if (dialog->unread_count_ < 0) {
LOG(ERROR) << "Receive " << dialog->unread_count_ << " as number of unread messages in " << dialog_id;
@@ -10541,6 +16000,30 @@ void MessagesManager::on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>>
<< dialog_id;
dialog->unread_mentions_count_ = 0;
}
+ if (dialog->unread_reactions_count_ < 0) {
+ LOG(ERROR) << "Receive " << dialog->unread_reactions_count_ << " as number of messages with unread reactions in "
+ << dialog_id;
+ dialog->unread_reactions_count_ = 0;
+ }
+ if (!d->is_is_blocked_inited && !td_->auth_manager_->is_bot()) {
+ // asynchronously get is_blocked from the server
+ // TODO add is_blocked to telegram_api::dialog
+ reload_dialog_info_full(dialog_id, "on_get_dialogs init is_blocked");
+ } else if (!d->is_has_bots_inited && !td_->auth_manager_->is_bot()) {
+ // asynchronously get has_bots from the server
+ // TODO add has_bots to telegram_api::dialog
+ reload_dialog_info_full(dialog_id, "on_get_dialogs init has_bots");
+ } else if (!d->is_theme_name_inited && !td_->auth_manager_->is_bot()) {
+ // asynchronously get theme_name from the server
+ // TODO add theme_name to telegram_api::dialog
+ reload_dialog_info_full(dialog_id, "on_get_dialogs init theme_name");
+ } else if (!d->is_last_pinned_message_id_inited && !td_->auth_manager_->is_bot()) {
+ // asynchronously get dialog pinned message from the server
+ get_dialog_pinned_message(dialog_id, Auto());
+ } else if (!d->is_available_reactions_inited && !td_->auth_manager_->is_bot()) {
+ // asynchronously get dialog available reactions from the server
+ reload_dialog_info_full(dialog_id, "on_get_dialogs init available_reactions");
+ }
need_update_dialog_pos |= update_dialog_draft_message(
d, get_draft_message(td_->contacts_manager_.get(), std::move(dialog->draft_)), true, false);
@@ -10548,71 +16031,178 @@ void MessagesManager::on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>>
bool has_pts = (dialog->flags_ & DIALOG_FLAG_HAS_PTS) != 0;
if (last_message_id.is_valid()) {
FullMessageId full_message_id(dialog_id, last_message_id);
- auto added_full_message_id = on_get_message(std::move(full_message_id_to_message[full_message_id]), false,
- has_pts, false, false, "get chats");
- CHECK(d->last_new_message_id == MessageId());
- set_dialog_last_new_message_id(d, full_message_id.get_message_id(), "on_get_dialogs");
- if (d->last_new_message_id.get() > d->last_message_id.get() &&
- added_full_message_id.get_message_id().is_valid()) {
- CHECK(added_full_message_id.get_message_id() == d->last_new_message_id);
- set_dialog_last_message_id(d, d->last_new_message_id, "on_get_dialogs");
- send_update_chat_last_message(d, "on_get_dialogs");
+ auto it = full_message_id_to_message.find(full_message_id);
+ if (it == full_message_id_to_message.end()) {
+ LOG(ERROR) << "Last " << full_message_id << " not found";
+ } else if (!has_pts || d->pts == 0 || dialog->pts_ <= d->pts || d->is_channel_difference_finished) {
+ auto last_message = std::move(it->second);
+ auto added_full_message_id =
+ on_get_message(std::move(last_message), false, has_pts, false, false, false, source);
+ CHECK(d->last_new_message_id == MessageId());
+ set_dialog_last_new_message_id(d, last_message_id, source);
+ if (d->last_new_message_id > d->last_message_id && added_full_message_id.get_message_id().is_valid()) {
+ CHECK(added_full_message_id.get_message_id() == d->last_new_message_id);
+ set_dialog_last_message_id(d, d->last_new_message_id, source);
+ send_update_chat_last_message(d, source);
+ }
+ } else {
+ get_channel_difference(dialog_id, d->pts, true, source);
}
}
if (has_pts && !running_get_channel_difference(dialog_id)) {
- int32 channel_pts = dialog->pts_;
- LOG_IF(ERROR, channel_pts < d->pts) << "In new " << dialog_id << " pts = " << d->pts
- << ", but pts = " << channel_pts << " received in GetChats";
- set_channel_pts(d, channel_pts, "get channel");
+ set_channel_pts(d, dialog->pts_, source);
}
}
- bool is_pinned = (dialog->flags_ & DIALOG_FLAG_IS_PINNED) != 0;
- bool was_pinned = d->pinned_order != DEFAULT_ORDER;
- if (is_pinned != was_pinned) {
- set_dialog_is_pinned(d, is_pinned);
- need_update_dialog_pos = false;
+ bool is_marked_as_unread = dialog->unread_mark_;
+ if (is_marked_as_unread != d->is_marked_as_unread) {
+ set_dialog_is_marked_as_unread(d, is_marked_as_unread);
}
if (need_update_dialog_pos) {
- update_dialog_pos(d, false, "on_get_dialogs");
- }
-
- if (!G()->parameters().use_message_db || is_new ||
- (!d->is_last_read_inbox_message_id_inited && read_inbox_max_message_id.is_valid())) {
- if (d->server_unread_count != dialog->unread_count_ ||
- d->last_read_inbox_message_id.get() < read_inbox_max_message_id.get()) {
+ update_dialog_pos(d, source);
+ }
+
+ if (!td_->auth_manager_->is_bot() && !from_pinned_dialog_list) {
+ // set is_pinned only after updating dialog pos to ensure that order is initialized
+ bool is_pinned = (dialog->flags_ & DIALOG_FLAG_IS_PINNED) != 0;
+ bool was_pinned = is_dialog_pinned(DialogListId(d->folder_id), dialog_id);
+ if (is_pinned != was_pinned) {
+ set_dialog_is_pinned(DialogListId(d->folder_id), d, is_pinned);
+ }
+ }
+
+ if (!G()->parameters().use_message_db || is_new || !d->is_last_read_inbox_message_id_inited ||
+ d->need_repair_server_unread_count) {
+ if (d->last_read_inbox_message_id.is_valid() && !d->last_read_inbox_message_id.is_server() &&
+ read_inbox_max_message_id == d->last_read_inbox_message_id.get_prev_server_message_id()) {
+ read_inbox_max_message_id = d->last_read_inbox_message_id;
+ }
+ if (d->need_repair_server_unread_count &&
+ (d->last_read_inbox_message_id <= read_inbox_max_message_id || !need_unread_counter(d->order) ||
+ !have_input_peer(dialog_id, AccessRights::Read))) {
+ LOG(INFO) << "Repaired server unread count in " << dialog_id << " from " << d->last_read_inbox_message_id << "/"
+ << d->server_unread_count << " to " << read_inbox_max_message_id << "/" << dialog->unread_count_;
+ d->need_repair_server_unread_count = false;
+ on_dialog_updated(dialog_id, "repaired dialog server unread count");
+ }
+ if (d->need_repair_server_unread_count) {
+ auto &previous_message_id = previous_repaired_read_inbox_max_message_id_[dialog_id];
+ if (previous_message_id >= read_inbox_max_message_id) {
+ // protect from sending the request in a loop
+ if (d->server_unread_count != 0) {
+ LOG(ERROR) << "Failed to repair server unread count in " << dialog_id
+ << ", because receive read_inbox_max_message_id = " << read_inbox_max_message_id << " after "
+ << previous_message_id << ", but messages are read up to " << d->last_read_inbox_message_id;
+ } else {
+ LOG(INFO) << "Failed to repair server unread count in " << dialog_id
+ << ", because receive read_inbox_max_message_id = " << read_inbox_max_message_id
+ << ", but messages are read up to " << d->last_read_inbox_message_id
+ << ". Likely all messages after " << read_inbox_max_message_id << " are outgoing";
+ }
+ d->need_repair_server_unread_count = false;
+ on_dialog_updated(dialog_id, "failed to repair dialog server unread count");
+ } else {
+ LOG(INFO) << "Have last_read_inbox_message_id = " << d->last_read_inbox_message_id << ", but received only "
+ << read_inbox_max_message_id << " from the server, trying to repair server unread count again";
+ previous_message_id = read_inbox_max_message_id;
+ repair_server_unread_count(dialog_id, d->server_unread_count, source);
+ }
+ }
+ if (!d->need_repair_server_unread_count) {
+ previous_repaired_read_inbox_max_message_id_.erase(dialog_id);
+ }
+ if ((d->server_unread_count != dialog->unread_count_ &&
+ d->last_read_inbox_message_id == read_inbox_max_message_id) ||
+ d->last_read_inbox_message_id < read_inbox_max_message_id) {
set_dialog_last_read_inbox_message_id(d, read_inbox_max_message_id, dialog->unread_count_,
- d->local_unread_count, true, "on_get_dialogs");
+ d->local_unread_count, true, source);
+ }
+ if (!d->is_last_read_inbox_message_id_inited) {
+ d->is_last_read_inbox_message_id_inited = true;
+ on_dialog_updated(dialog_id, "set is_last_read_inbox_message_id_inited");
}
}
- if (!G()->parameters().use_message_db || is_new ||
- (!d->is_last_read_outbox_message_id_inited && read_outbox_max_message_id.is_valid())) {
- if (d->last_read_outbox_message_id.get() < read_outbox_max_message_id.get()) {
+ if (!G()->parameters().use_message_db || is_new || !d->is_last_read_outbox_message_id_inited) {
+ if (d->last_read_outbox_message_id < read_outbox_max_message_id) {
set_dialog_last_read_outbox_message_id(d, read_outbox_max_message_id);
}
+ if (!d->is_last_read_outbox_message_id_inited) {
+ d->is_last_read_outbox_message_id_inited = true;
+ on_dialog_updated(dialog_id, "set is_last_read_outbox_message_id_inited");
+ }
}
if (!G()->parameters().use_message_db || is_new) {
if (d->unread_mention_count != dialog->unread_mentions_count_) {
- d->unread_mention_count = dialog->unread_mentions_count_;
- d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] =
- d->unread_mention_count;
+ set_dialog_unread_mention_count(d, dialog->unread_mentions_count_);
+ update_dialog_mention_notification_count(d);
send_update_chat_unread_mention_count(d);
}
+ if (d->unread_reaction_count != dialog->unread_reactions_count_) {
+ set_dialog_unread_reaction_count(d, dialog->unread_reactions_count_);
+ // update_dialog_mention_notification_count(d);
+ send_update_chat_unread_reaction_count(d, source);
+ }
+ }
+
+ being_added_dialog_id_ = DialogId();
+
+ update_dialog_lists(d, std::move(positions), true, false, source);
+ }
+
+ if (from_dialog_list) {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(total_count >= 0);
+
+ auto &folder_list = add_dialog_list(DialogListId(folder_id));
+ if (folder_list.server_dialog_total_count_ != total_count) {
+ auto old_dialog_total_count = get_dialog_total_count(folder_list);
+ folder_list.server_dialog_total_count_ = total_count;
+ if (folder_list.is_dialog_unread_count_inited_) {
+ if (old_dialog_total_count != get_dialog_total_count(folder_list)) {
+ send_update_unread_chat_count(folder_list, DialogId(), true, source);
+ } else {
+ save_unread_chat_count(folder_list);
+ }
+ }
+ }
+
+ auto *folder = get_dialog_folder(folder_id);
+ CHECK(folder != nullptr);
+ if (dialogs.empty()) {
+ // if there are no more dialogs on the server
+ max_dialog_date = MAX_DIALOG_DATE;
+ }
+ if (folder->last_server_dialog_date_ < max_dialog_date) {
+ folder->last_server_dialog_date_ = max_dialog_date;
+ update_last_dialog_date(folder_id);
+ } else if (promise) {
+ LOG(ERROR) << "Last server dialog date didn't increased from " << folder->last_server_dialog_date_ << " to "
+ << max_dialog_date << " after receiving " << dialogs.size() << " chats " << added_dialog_ids
+ << " from " << total_count << " in " << folder_id
+ << ". last_dialog_date = " << folder->folder_last_dialog_date_
+ << ", last_loaded_database_dialog_date = " << folder->last_loaded_database_dialog_date_;
}
}
if (from_pinned_dialog_list) {
- auto pinned_dialog_ids = remove_secret_chat_dialog_ids(get_pinned_dialogs());
- std::reverse(pinned_dialog_ids.begin(), pinned_dialog_ids.end());
+ CHECK(!td_->auth_manager_->is_bot());
+ auto *folder_list = get_dialog_list(DialogListId(folder_id));
+ CHECK(folder_list != nullptr);
+ auto pinned_dialog_ids = remove_secret_chat_dialog_ids(get_pinned_dialog_ids(DialogListId(folder_id)));
+ bool are_pinned_dialogs_saved = folder_list->are_pinned_dialogs_inited_;
+ folder_list->are_pinned_dialogs_inited_ = true;
if (pinned_dialog_ids != added_dialog_ids) {
- LOG(INFO) << "Repair pinned dialogs order from " << format::as_array(pinned_dialog_ids) << " to "
+ LOG(INFO) << "Update pinned chats order from " << format::as_array(pinned_dialog_ids) << " to "
<< format::as_array(added_dialog_ids);
+ FlatHashSet<DialogId, DialogIdHash> old_pinned_dialog_ids;
+ for (auto pinned_dialog_id : pinned_dialog_ids) {
+ old_pinned_dialog_ids.insert(pinned_dialog_id);
+ }
- std::unordered_set<DialogId, DialogIdHash> old_pinned_dialog_ids(pinned_dialog_ids.begin(),
- pinned_dialog_ids.end());
-
+ std::reverse(pinned_dialog_ids.begin(), pinned_dialog_ids.end());
+ std::reverse(added_dialog_ids.begin(), added_dialog_ids.end());
auto old_it = pinned_dialog_ids.begin();
for (auto dialog_id : added_dialog_ids) {
old_pinned_dialog_ids.erase(dialog_id);
@@ -10620,26 +16210,41 @@ void MessagesManager::on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>>
if (*old_it == dialog_id) {
break;
}
- old_it++;
+ ++old_it;
}
if (old_it < pinned_dialog_ids.end()) {
// leave dialog where it is
+ ++old_it;
continue;
}
- set_dialog_is_pinned(dialog_id, true);
+ if (set_dialog_is_pinned(dialog_id, true)) {
+ are_pinned_dialogs_saved = true;
+ }
}
for (auto dialog_id : old_pinned_dialog_ids) {
- set_dialog_is_pinned(dialog_id, false);
+ Dialog *d = get_dialog_force(dialog_id, "on_get_dialogs pinned");
+ if (d == nullptr) {
+ LOG(ERROR) << "Failed to find " << dialog_id << " to unpin in " << folder_id;
+ force_create_dialog(dialog_id, "from_pinned_dialog_list", true);
+ d = get_dialog_force(dialog_id, "on_get_dialogs pinned 2");
+ }
+ if (d != nullptr && set_dialog_is_pinned(DialogListId(folder_id), d, false)) {
+ are_pinned_dialogs_saved = true;
+ }
}
+ } else {
+ LOG(INFO) << "Pinned chats are not changed";
+ }
+ update_list_last_pinned_dialog_date(*folder_list);
+
+ if (!are_pinned_dialogs_saved && G()->parameters().use_message_db) {
+ LOG(INFO) << "Save empty pinned chat list in " << folder_id;
+ G()->td_db()->get_binlog_pmc()->set(PSTRING() << "pinned_dialog_ids" << folder_id.get(), "");
}
}
promise.set_value(Unit());
}
-constexpr bool MessagesManager::is_debug_message_op_enabled() {
- return !LOG_IS_STRIPPED(ERROR) && false;
-}
-
void MessagesManager::dump_debug_message_op(const Dialog *d, int priority) {
if (!is_debug_message_op_enabled()) {
return;
@@ -10663,7 +16268,7 @@ void MessagesManager::dump_debug_message_op(const Dialog *d, int priority) {
break;
}
case Dialog::MessageOp::SetPts: {
- LOG(ERROR) << "MessageOpSetPts at " << op.date << " " << op.content_type << " " << op.source;
+ LOG(ERROR) << "MessageOpSetPts at " << op.date << " " << op.pts << " " << op.source;
break;
}
case Dialog::MessageOp::Delete: {
@@ -10686,21 +16291,33 @@ bool MessagesManager::is_message_unload_enabled() const {
}
bool MessagesManager::can_unload_message(const Dialog *d, const Message *m) const {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ CHECK(m->message_id.is_valid());
// don't want to unload messages from opened dialogs
// don't want to unload messages to which there are replies in yet unsent messages
- // don't want to unload messages with pending web pages
- // can't unload from memory last dialog, last database messages, yet unsent messages and active live locations
+ // don't want to unload message with active reply markup
+ // don't want to unload the newest pinned message
+ // don't want to unload last edited message, because server can send updateEditChannelMessage again
+ // don't want to unload messages from the last album
+ // can't unload from memory last dialog, last database messages, yet unsent messages, being edited media messages and active live locations
+ // can't unload messages in dialog with active suffix load query
FullMessageId full_message_id{d->dialog_id, m->message_id};
return !d->is_opened && m->message_id != d->last_message_id && m->message_id != d->last_database_message_id &&
!m->message_id.is_yet_unsent() && active_live_location_full_message_ids_.count(full_message_id) == 0 &&
- replied_by_yet_unsent_messages_.count(full_message_id) == 0 &&
- waiting_for_web_page_messages_.count(full_message_id) == 0;
+ replied_by_yet_unsent_messages_.count(full_message_id) == 0 && m->edited_content == nullptr &&
+ d->suffix_load_queries_.empty() && m->message_id != d->reply_markup_message_id &&
+ m->message_id != d->last_pinned_message_id && m->message_id != d->last_edited_message_id &&
+ (m->media_album_id != d->last_media_album_id || m->media_album_id == 0);
}
-void MessagesManager::unload_message(Dialog *d, MessageId message_id) {
+unique_ptr<MessagesManager::Message> MessagesManager::unload_message(Dialog *d, MessageId message_id) {
+ CHECK(d != nullptr);
+ CHECK(message_id.is_valid());
bool need_update_dialog_pos = false;
auto m = do_delete_message(d, message_id, false, true, &need_update_dialog_pos, "unload_message");
CHECK(!need_update_dialog_pos);
+ return m;
}
unique_ptr<MessagesManager::Message> MessagesManager::delete_message(Dialog *d, MessageId message_id,
@@ -10709,33 +16326,221 @@ unique_ptr<MessagesManager::Message> MessagesManager::delete_message(Dialog *d,
return do_delete_message(d, message_id, is_permanently_deleted, false, need_update_dialog_pos, source);
}
+void MessagesManager::add_random_id_to_message_id_correspondence(Dialog *d, int64 random_id, MessageId message_id) {
+ CHECK(d != nullptr);
+ CHECK(d->dialog_id.get_type() == DialogType::SecretChat || message_id.is_yet_unsent());
+ auto it = d->random_id_to_message_id.find(random_id);
+ if (it == d->random_id_to_message_id.end() || it->second.get() < message_id.get()) {
+ LOG(INFO) << "Add correspondence from random_id " << random_id << " to " << message_id << " in " << d->dialog_id;
+ d->random_id_to_message_id[random_id] = message_id;
+ }
+}
+
+void MessagesManager::delete_random_id_to_message_id_correspondence(Dialog *d, int64 random_id, MessageId message_id) {
+ CHECK(d != nullptr);
+ CHECK(d->dialog_id.get_type() == DialogType::SecretChat || message_id.is_yet_unsent());
+ auto it = d->random_id_to_message_id.find(random_id);
+ if (it != d->random_id_to_message_id.end() && it->second == message_id) {
+ LOG(INFO) << "Delete correspondence from random_id " << random_id << " to " << message_id << " in " << d->dialog_id;
+ d->random_id_to_message_id.erase(it);
+ }
+}
+
+void MessagesManager::add_notification_id_to_message_id_correspondence(Dialog *d, NotificationId notification_id,
+ MessageId message_id) {
+ CHECK(d != nullptr);
+ CHECK(notification_id.is_valid());
+ CHECK(message_id.is_valid());
+ auto it = d->notification_id_to_message_id.find(notification_id);
+ if (it == d->notification_id_to_message_id.end()) {
+ VLOG(notifications) << "Add correspondence from " << notification_id << " to " << message_id << " in "
+ << d->dialog_id;
+ d->notification_id_to_message_id.emplace(notification_id, message_id);
+ } else if (it->second != message_id) {
+ LOG(ERROR) << "Have duplicated " << notification_id << " in " << d->dialog_id << " in " << message_id << " and "
+ << it->second;
+ if (it->second < message_id) {
+ it->second = message_id;
+ }
+ }
+}
+
+void MessagesManager::delete_notification_id_to_message_id_correspondence(Dialog *d, NotificationId notification_id,
+ MessageId message_id) {
+ CHECK(d != nullptr);
+ CHECK(notification_id.is_valid());
+ CHECK(message_id.is_valid());
+ auto it = d->notification_id_to_message_id.find(notification_id);
+ if (it != d->notification_id_to_message_id.end() && it->second == message_id) {
+ VLOG(notifications) << "Delete correspondence from " << notification_id << " to " << message_id << " in "
+ << d->dialog_id;
+ d->notification_id_to_message_id.erase(it);
+ } else {
+ LOG(ERROR) << "Can't find " << notification_id << " in " << d->dialog_id << " with " << message_id;
+ }
+}
+
+void MessagesManager::remove_message_notification_id(Dialog *d, Message *m, bool is_permanent, bool force_update,
+ bool ignore_pinned_message_notification_removal) {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ CHECK(m->message_id.is_valid());
+ if (!m->notification_id.is_valid()) {
+ return;
+ }
+
+ auto from_mentions = is_from_mention_notification_group(m);
+ auto &group_info = get_notification_group_info(d, m);
+ if (!group_info.group_id.is_valid()) {
+ return;
+ }
+
+ bool had_active_notification = is_message_notification_active(d, m);
+
+ auto notification_id = m->notification_id;
+ VLOG(notifications) << "Remove " << notification_id << " from " << m->message_id << " in " << group_info.group_id
+ << '/' << d->dialog_id << " from database, was_active = " << had_active_notification
+ << ", is_permanent = " << is_permanent;
+ delete_notification_id_to_message_id_correspondence(d, notification_id, m->message_id);
+ m->removed_notification_id = m->notification_id;
+ m->notification_id = NotificationId();
+ if (d->pinned_message_notification_message_id == m->message_id && is_permanent &&
+ !ignore_pinned_message_notification_removal) {
+ remove_dialog_pinned_message_notification(
+ d, "remove_message_notification_id"); // must be called after notification_id is removed
+ }
+ if (group_info.last_notification_id == notification_id) {
+ // last notification is deleted, need to find new last notification
+ fix_dialog_last_notification_id(d, from_mentions, m->message_id);
+ }
+
+ if (is_permanent) {
+ if (had_active_notification) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, group_info.group_id,
+ notification_id, is_permanent, force_update, Promise<Unit>(),
+ "remove_message_notification_id");
+ }
+
+ // on_message_changed will be called by the caller
+ // don't need to call there to not save twice/or to save just deleted message
+ } else {
+ on_message_changed(d, m, false, "remove_message_notification_id");
+ }
+}
+
+void MessagesManager::remove_new_secret_chat_notification(Dialog *d, bool is_permanent) {
+ CHECK(d != nullptr);
+ auto notification_id = d->new_secret_chat_notification_id;
+ CHECK(notification_id.is_valid());
+ VLOG(notifications) << "Remove " << notification_id << " about new secret " << d->dialog_id << " from "
+ << d->message_notification_group.group_id;
+ d->new_secret_chat_notification_id = NotificationId();
+ bool is_fixed = set_dialog_last_notification(d->dialog_id, d->message_notification_group, 0, NotificationId(),
+ "remove_new_secret_chat_notification");
+ CHECK(is_fixed);
+ if (is_permanent) {
+ CHECK(d->message_notification_group.group_id.is_valid());
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification,
+ d->message_notification_group.group_id, notification_id, true, true, Promise<Unit>(),
+ "remove_new_secret_chat_notification");
+ }
+}
+
+void MessagesManager::fix_dialog_last_notification_id(Dialog *d, bool from_mentions, MessageId message_id) {
+ CHECK(d != nullptr);
+ CHECK(!message_id.is_scheduled());
+ MessagesConstIterator it(d, message_id);
+ auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+ VLOG(notifications) << "Trying to fix last notification identifier in " << group_info.group_id << " from "
+ << d->dialog_id << " from " << message_id << "/" << group_info.last_notification_id;
+ if (*it != nullptr && ((*it)->message_id == message_id || (*it)->have_next)) {
+ while (*it != nullptr) {
+ const Message *m = *it;
+ if (is_from_mention_notification_group(m) == from_mentions && m->notification_id.is_valid() &&
+ is_message_notification_active(d, m) && m->message_id != message_id) {
+ bool is_fixed = set_dialog_last_notification(d->dialog_id, group_info, m->date, m->notification_id,
+ "fix_dialog_last_notification_id");
+ CHECK(is_fixed);
+ return;
+ }
+ --it;
+ }
+ }
+ if (G()->parameters().use_message_db) {
+ get_message_notifications_from_database(
+ d->dialog_id, group_info.group_id, group_info.last_notification_id, message_id, 1,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions,
+ prev_last_notification_id = group_info.last_notification_id](Result<vector<Notification>> result) {
+ send_closure(actor_id, &MessagesManager::do_fix_dialog_last_notification_id, dialog_id, from_mentions,
+ prev_last_notification_id, std::move(result));
+ }));
+ }
+}
+
+void MessagesManager::do_fix_dialog_last_notification_id(DialogId dialog_id, bool from_mentions,
+ NotificationId prev_last_notification_id,
+ Result<vector<Notification>> result) {
+ if (result.is_error()) {
+ return;
+ }
+
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+ VLOG(notifications) << "Receive " << result.ok().size() << " message notifications in " << group_info.group_id << '/'
+ << dialog_id << " from " << prev_last_notification_id;
+ if (group_info.last_notification_id != prev_last_notification_id) {
+ // last_notification_id was changed
+ return;
+ }
+
+ auto notifications = result.move_as_ok();
+ CHECK(notifications.size() <= 1);
+
+ int32 last_notification_date = 0;
+ NotificationId last_notification_id;
+ if (!notifications.empty()) {
+ last_notification_date = notifications[0].date;
+ last_notification_id = notifications[0].notification_id;
+ }
+
+ bool is_fixed = set_dialog_last_notification(dialog_id, group_info, last_notification_date, last_notification_id,
+ "do_fix_dialog_last_notification_id");
+ CHECK(is_fixed);
+}
+
// DO NOT FORGET TO ADD ALL CHANGES OF THIS FUNCTION AS WELL TO do_delete_all_dialog_messages
unique_ptr<MessagesManager::Message> MessagesManager::do_delete_message(Dialog *d, MessageId message_id,
bool is_permanently_deleted,
bool only_from_memory,
bool *need_update_dialog_pos,
const char *source) {
+ CHECK(d != nullptr);
if (!message_id.is_valid()) {
+ if (message_id.is_valid_scheduled()) {
+ return do_delete_scheduled_message(d, message_id, is_permanently_deleted, source);
+ }
+
LOG(ERROR) << "Trying to delete " << message_id << " in " << d->dialog_id << " from " << source;
return nullptr;
}
FullMessageId full_message_id(d->dialog_id, message_id);
- unique_ptr<Message> *v = find_message(&d->messages, message_id);
+ unique_ptr<Message> *v = treap_find_message(&d->messages, message_id);
if (*v == nullptr) {
LOG(INFO) << message_id << " is not found in " << d->dialog_id << " to be deleted from " << source;
if (only_from_memory) {
return nullptr;
}
- if (get_message_force(d, message_id) == nullptr) {
+ if (get_message_force(d, message_id, "do_delete_message") == nullptr) {
// currently there may be a race between add_message_to_database and get_message_force,
// so delete a message from database just in case
delete_message_from_database(d, message_id, nullptr, is_permanently_deleted);
if (is_permanently_deleted && d->last_clear_history_message_id == message_id) {
set_dialog_last_clear_history_date(d, 0, MessageId(), "do_delete_message");
- on_dialog_updated(d->dialog_id, "forget last_clear_history_date from do_delete_message");
*need_update_dialog_pos = true;
}
@@ -10743,7 +16548,7 @@ unique_ptr<MessagesManager::Message> MessagesManager::do_delete_message(Dialog *
can't do this because the message may be never received in the dialog, unread count will became negative
// if last_read_inbox_message_id is not known, we can't be sure whether unread_count should be decreased or not
if (message_id.is_valid() && !message_id.is_yet_unsent() && d->is_last_read_inbox_message_id_inited &&
- message_id.get() > d->last_read_inbox_message_id.get() && !td_->auth_manager_->is_bot()) {
+ message_id > d->last_read_inbox_message_id && !td_->auth_manager_->is_bot()) {
int32 server_unread_count = d->server_unread_count;
int32 local_unread_count = d->local_unread_count;
int32 &unread_count = message_id.is_server() ? server_unread_count : local_unread_count;
@@ -10760,28 +16565,35 @@ unique_ptr<MessagesManager::Message> MessagesManager::do_delete_message(Dialog *
*/
return nullptr;
}
- v = find_message(&d->messages, message_id);
+ v = treap_find_message(&d->messages, message_id);
CHECK(*v != nullptr);
}
const Message *m = v->get();
+ CHECK(m->message_id == message_id);
+
if (only_from_memory && !can_unload_message(d, m)) {
return nullptr;
}
+ LOG_CHECK(!d->being_deleted_message_id.is_valid())
+ << d->being_deleted_message_id << " " << message_id << " " << source;
+ d->being_deleted_message_id = message_id;
+
if (is_debug_message_op_enabled()) {
- d->debug_message_op.emplace_back(Dialog::MessageOp::Delete, m->message_id, m->content->get_id(), false,
+ d->debug_message_op.emplace_back(Dialog::MessageOp::Delete, m->message_id, m->content->get_type(), false,
m->have_previous, m->have_next, source);
}
- LOG(INFO) << "Deleting " << full_message_id << " with have_previous = " << m->have_previous
- << " and have_next = " << m->have_next << " from " << source;
-
bool need_get_history = false;
if (!only_from_memory) {
+ LOG(INFO) << "Deleting " << full_message_id << " with have_previous = " << m->have_previous
+ << " and have_next = " << m->have_next << " from " << source;
+
delete_message_from_database(d, message_id, m, is_permanently_deleted);
delete_active_live_location(d->dialog_id, m);
+ remove_message_file_sources(d->dialog_id, m);
if (message_id == d->last_message_id) {
MessagesConstIterator it(d, message_id);
@@ -10789,9 +16601,10 @@ unique_ptr<MessagesManager::Message> MessagesManager::do_delete_message(Dialog *
if ((*it)->have_previous) {
--it;
if (*it != nullptr) {
- set_dialog_last_message_id(d, (*it)->message_id, "do_delete_message");
+ set_dialog_last_message_id(d, (*it)->message_id, "do_delete_message", *it);
} else {
- LOG(ERROR) << "have_previous is true, but there is no previous for " << full_message_id << " from " << source;
+ LOG(ERROR) << "Have have_previous is true, but there is no previous for " << full_message_id << " from "
+ << source;
dump_debug_message_op(d);
set_dialog_last_message_id(d, MessageId(), "do_delete_message");
}
@@ -10818,21 +16631,58 @@ unique_ptr<MessagesManager::Message> MessagesManager::do_delete_message(Dialog *
if (*it != nullptr) {
if (!(*it)->message_id.is_yet_unsent() && (*it)->message_id != d->last_database_message_id) {
- set_dialog_last_database_message_id(d, (*it)->message_id, "do_delete_message");
+ if ((*it)->message_id < d->first_database_message_id && d->dialog_id.get_type() == DialogType::Channel) {
+ // possible if messages was deleted from database, but not from memory after updateChannelTooLong
+ set_dialog_last_database_message_id(d, MessageId(), "do_delete_message 1");
+ } else {
+ set_dialog_last_database_message_id(d, (*it)->message_id, "do_delete_message 2");
+ if (d->last_database_message_id < d->first_database_message_id) {
+ LOG(ERROR) << "Last database " << d->last_database_message_id << " became less than first database "
+ << d->first_database_message_id << " after deletion of " << full_message_id;
+ set_dialog_first_database_message_id(d, d->last_database_message_id, "do_delete_message 2");
+ }
+ }
+ } else if (d->first_database_message_id == d->last_database_message_id) {
+ // database definitely has no more messages
+ set_dialog_last_database_message_id(d, MessageId(), "do_delete_message 3");
} else {
+ LOG(INFO) << "Need to get history to repair last_database_message_id in " << d->dialog_id;
need_get_history = true;
}
} else {
- LOG(ERROR) << "have_previous is true, but there is no previous";
+ LOG(ERROR) << "Have have_previous is true, but there is no previous";
dump_debug_message_op(d);
}
- on_dialog_updated(d->dialog_id, "do delete last database message");
}
if (d->last_database_message_id.is_valid()) {
CHECK(d->first_database_message_id.is_valid());
} else {
set_dialog_first_database_message_id(d, MessageId(), "do_delete_message");
}
+
+ if (message_id == d->suffix_load_first_message_id_) {
+ MessagesConstIterator it(d, message_id);
+ CHECK(*it == m);
+ if ((*it)->have_previous) {
+ --it;
+ if (*it != nullptr) {
+ d->suffix_load_first_message_id_ = (*it)->message_id;
+ } else {
+ LOG(ERROR) << "Have have_previous is true, but there is no previous for " << full_message_id << " from "
+ << source;
+ dump_debug_message_op(d);
+ d->suffix_load_first_message_id_ = MessageId();
+ d->suffix_load_done_ = false;
+ }
+ } else {
+ d->suffix_load_first_message_id_ = MessageId();
+ d->suffix_load_done_ = false;
+ }
+ }
+ }
+ if (only_from_memory && message_id >= d->suffix_load_first_message_id_) {
+ d->suffix_load_first_message_id_ = MessageId();
+ d->suffix_load_done_ = false;
}
if (m->have_previous && (only_from_memory || !m->have_next)) {
@@ -10843,7 +16693,8 @@ unique_ptr<MessagesManager::Message> MessagesManager::do_delete_message(Dialog *
if (prev_m != nullptr) {
prev_m->have_next = false;
} else {
- LOG(ERROR) << "have_previous is true, but there is no previous for " << full_message_id << " from " << source;
+ LOG(ERROR) << "Have have_previous is true, but there is no previous for " << full_message_id << " from "
+ << source;
dump_debug_message_op(d);
}
}
@@ -10855,51 +16706,36 @@ unique_ptr<MessagesManager::Message> MessagesManager::do_delete_message(Dialog *
if (next_m != nullptr) {
next_m->have_previous = false;
} else {
- LOG(ERROR) << "have_next is true, but there is no next for " << full_message_id << " from " << source;
+ LOG(ERROR) << "Have have_next is true, but there is no next for " << full_message_id << " from " << source;
dump_debug_message_op(d);
}
}
- unique_ptr<Message> result = std::move(*v);
- unique_ptr<Message> left = std::move(result->left);
- unique_ptr<Message> right = std::move(result->right);
+ auto result = treap_delete_message(v);
- while (left != nullptr || right != nullptr) {
- if (left == nullptr || (right != nullptr && right->random_y > left->random_y)) {
- *v = std::move(right);
- v = &((*v)->left);
- right = std::move(*v);
- } else {
- *v = std::move(left);
- v = &((*v)->right);
- left = std::move(*v);
- }
- }
- CHECK(*v == nullptr);
+ d->being_deleted_message_id = MessageId();
if (!only_from_memory) {
- if (message_id.is_yet_unsent()) {
- cancel_send_message_query(d->dialog_id, result);
- }
-
if (need_get_history && !td_->auth_manager_->is_bot() && have_input_peer(d->dialog_id, AccessRights::Read)) {
- get_history_from_the_end(d->dialog_id, true, false, Auto());
+ send_closure_later(actor_id(this), &MessagesManager::get_history_from_the_end, d->dialog_id, true, false,
+ Promise<Unit>());
}
if (d->reply_markup_message_id == message_id) {
set_dialog_reply_markup(d, MessageId());
}
// if last_read_inbox_message_id is not known, we can't be sure whether unread_count should be decreased or not
- if (!result->is_outgoing && message_id.get() > d->last_read_inbox_message_id.get() &&
- d->dialog_id != DialogId(td_->contacts_manager_->get_my_id("do_delete_message")) &&
+ if (has_incoming_notification(d->dialog_id, result.get()) && message_id > d->last_read_inbox_message_id &&
d->is_last_read_inbox_message_id_inited && !td_->auth_manager_->is_bot()) {
int32 server_unread_count = d->server_unread_count;
int32 local_unread_count = d->local_unread_count;
int32 &unread_count = message_id.is_server() ? server_unread_count : local_unread_count;
if (unread_count == 0) {
- LOG(ERROR) << "Unread count became negative in " << d->dialog_id << " after deletion of " << message_id
- << ". Last read is " << d->last_read_inbox_message_id;
- dump_debug_message_op(d, 3);
+ if (need_unread_counter(d->order)) {
+ LOG(ERROR) << "Unread count became negative in " << d->dialog_id << " after deletion of " << message_id
+ << ". Last read is " << d->last_read_inbox_message_id;
+ dump_debug_message_op(d, 3);
+ }
} else {
unread_count--;
set_dialog_last_read_inbox_message_id(d, MessageId::min(), server_unread_count, local_unread_count, false,
@@ -10908,118 +16744,211 @@ unique_ptr<MessagesManager::Message> MessagesManager::do_delete_message(Dialog *
}
if (result->contains_unread_mention) {
if (d->unread_mention_count == 0) {
- LOG_IF(ERROR,
- d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] != -1)
- << "Unread mention count became negative in " << d->dialog_id << " after deletion of " << message_id;
+ if (is_dialog_inited(d)) {
+ LOG(ERROR) << "Unread mention count became negative in " << d->dialog_id << " after deletion of "
+ << message_id;
+ }
} else {
- d->unread_mention_count--;
- d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] =
- d->unread_mention_count;
+ set_dialog_unread_mention_count(d, d->unread_mention_count - 1);
send_update_chat_unread_mention_count(d);
}
}
+ if (has_unread_message_reactions(d->dialog_id, result.get())) {
+ if (d->unread_reaction_count == 0) {
+ if (is_dialog_inited(d)) {
+ LOG(ERROR) << "Unread reaction count became negative in " << d->dialog_id << " after deletion of "
+ << message_id;
+ }
+ } else {
+ set_dialog_unread_reaction_count(d, d->unread_reaction_count - 1);
+ send_update_chat_unread_reaction_count(d, "do_delete_message");
+ }
+ }
update_message_count_by_index(d, -1, result.get());
+ update_reply_count_by_message(d, -1, result.get());
}
- switch (d->dialog_id.get_type()) {
+ on_message_deleted(d, result.get(), is_permanently_deleted, source);
+
+ return result;
+}
+
+void MessagesManager::on_message_deleted(Dialog *d, Message *m, bool is_permanently_deleted, const char *source) {
+ // also called for unloaded messages, but not for scheduled messages
+ CHECK(m->message_id.is_valid());
+
+ if (m->message_id.is_yet_unsent() && !m->message_id.is_scheduled() && m->top_thread_message_id.is_valid()) {
+ auto it = d->yet_unsent_thread_message_ids.find(m->top_thread_message_id);
+ CHECK(it != d->yet_unsent_thread_message_ids.end());
+ auto is_deleted = it->second.erase(m->message_id) > 0;
+ CHECK(is_deleted);
+ if (it->second.empty()) {
+ d->yet_unsent_thread_message_ids.erase(it);
+ }
+ }
+ if (d->is_opened) {
+ auto it = dialog_viewed_messages_.find(d->dialog_id);
+ if (it != dialog_viewed_messages_.end()) {
+ auto &info = it->second;
+ CHECK(info != nullptr);
+ auto message_it = info->message_id_to_view_id.find(m->message_id);
+ if (message_it != info->message_id_to_view_id.end()) {
+ info->recently_viewed_messages.erase(message_it->second);
+ info->message_id_to_view_id.erase(message_it);
+ }
+ }
+ }
+
+ cancel_send_deleted_message(d->dialog_id, m, is_permanently_deleted);
+
+ auto dialog_type = d->dialog_id.get_type();
+ switch (dialog_type) {
case DialogType::User:
case DialogType::Chat:
- message_id_to_dialog_id_.erase(message_id);
+ if (m->message_id.is_server()) {
+ message_id_to_dialog_id_.erase(m->message_id);
+ }
break;
case DialogType::Channel:
- // nothing to do
- break;
case DialogType::SecretChat:
- LOG(INFO) << "Delete correspondence random_id " << result->random_id << " to " << message_id << " in "
- << d->dialog_id;
- d->random_id_to_message_id.erase(result->random_id);
+ // nothing to do
break;
case DialogType::None:
default:
UNREACHABLE();
}
- ttl_unregister_message(d->dialog_id, result.get(), Time::now());
+ ttl_unregister_message(d->dialog_id, m, source);
+ ttl_period_unregister_message(d->dialog_id, m);
+ delete_bot_command_message_id(d->dialog_id, m->message_id);
+ unregister_message_content(td_, m->content.get(), {d->dialog_id, m->message_id}, "on_message_deleted");
+ unregister_message_reply(d->dialog_id, m);
+ if (m->notification_id.is_valid()) {
+ delete_notification_id_to_message_id_correspondence(d, m->notification_id, m->message_id);
+ }
+ if (m->message_id.is_yet_unsent() || dialog_type == DialogType::SecretChat) {
+ delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
+ }
+
+ added_message_count_--;
+}
+
+bool MessagesManager::is_deleted_message(const Dialog *d, MessageId message_id) {
+ if (message_id.is_scheduled() && message_id.is_valid_scheduled() && message_id.is_scheduled_server()) {
+ return d->deleted_scheduled_server_message_ids.count(message_id.get_scheduled_server_message_id()) > 0;
+ } else {
+ return d->deleted_message_ids.count(message_id) > 0;
+ }
+}
+
+unique_ptr<MessagesManager::Message> MessagesManager::do_delete_scheduled_message(Dialog *d, MessageId message_id,
+ bool is_permanently_deleted,
+ const char *source) {
+ CHECK(d != nullptr);
+ LOG_CHECK(message_id.is_valid_scheduled()) << d->dialog_id << ' ' << message_id << ' ' << source;
+
+ unique_ptr<Message> *v = treap_find_message(&d->scheduled_messages, message_id);
+ if (*v == nullptr) {
+ LOG(INFO) << message_id << " is not found in " << d->dialog_id << " to be deleted from " << source;
+ auto message = get_message_force(d, message_id, "do_delete_scheduled_message");
+ if (message == nullptr) {
+ // currently there may be a race between add_message_to_database and get_message_force,
+ // so delete a message from database just in case
+ delete_message_from_database(d, message_id, nullptr, is_permanently_deleted);
+ return nullptr;
+ }
+
+ message_id = message->message_id;
+ v = treap_find_message(&d->scheduled_messages, message_id);
+ CHECK(*v != nullptr);
+ }
+
+ const Message *m = v->get();
+ CHECK(m->message_id == message_id);
+
+ LOG(INFO) << "Deleting " << FullMessageId{d->dialog_id, message_id} << " from " << source;
+
+ delete_message_from_database(d, message_id, m, is_permanently_deleted);
+
+ remove_message_file_sources(d->dialog_id, m);
+
+ auto result = treap_delete_message(v);
+
+ if (message_id.is_scheduled_server()) {
+ size_t erased_count = d->scheduled_message_date.erase(message_id.get_scheduled_server_message_id());
+ CHECK(erased_count != 0);
+ }
+
+ cancel_send_deleted_message(d->dialog_id, result.get(), is_permanently_deleted);
+
+ unregister_message_content(td_, result->content.get(), {d->dialog_id, message_id}, "do_delete_scheduled_message");
+ unregister_message_reply(d->dialog_id, m);
+ if (message_id.is_yet_unsent()) {
+ delete_random_id_to_message_id_correspondence(d, result->random_id, result->message_id);
+ }
return result;
}
-void MessagesManager::do_delete_all_dialog_messages(Dialog *d, unique_ptr<Message> &m,
- vector<int64> &deleted_message_ids) {
- if (m == nullptr) {
+void MessagesManager::do_delete_all_dialog_messages(Dialog *d, unique_ptr<Message> &message,
+ bool is_permanently_deleted, vector<int64> &deleted_message_ids) {
+ if (message == nullptr) {
return;
}
+ const Message *m = message.get();
MessageId message_id = m->message_id;
if (is_debug_message_op_enabled()) {
- d->debug_message_op.emplace_back(Dialog::MessageOp::Delete, m->message_id, m->content->get_id(), false,
+ d->debug_message_op.emplace_back(Dialog::MessageOp::Delete, m->message_id, m->content->get_type(), false,
m->have_previous, m->have_next, "delete all messages");
}
LOG(INFO) << "Delete " << message_id;
deleted_message_ids.push_back(message_id.get());
- do_delete_all_dialog_messages(d, m->right, deleted_message_ids);
- do_delete_all_dialog_messages(d, m->left, deleted_message_ids);
-
- delete_active_live_location(d->dialog_id, m.get());
+ do_delete_all_dialog_messages(d, message->right, is_permanently_deleted, deleted_message_ids);
+ do_delete_all_dialog_messages(d, message->left, is_permanently_deleted, deleted_message_ids);
- if (message_id.is_yet_unsent()) {
- cancel_send_message_query(d->dialog_id, m);
- }
+ delete_active_live_location(d->dialog_id, m);
+ remove_message_file_sources(d->dialog_id, m);
- switch (d->dialog_id.get_type()) {
- case DialogType::User:
- case DialogType::Chat:
- message_id_to_dialog_id_.erase(message_id);
- break;
- case DialogType::Channel:
- // nothing to do
- break;
- case DialogType::SecretChat:
- LOG(INFO) << "Delete correspondence random_id " << m->random_id << " to " << message_id << " in " << d->dialog_id;
- d->random_id_to_message_id.erase(m->random_id);
- break;
- case DialogType::None:
- default:
- UNREACHABLE();
- }
- ttl_unregister_message(d->dialog_id, m.get(), Time::now());
+ on_message_deleted(d, message.get(), is_permanently_deleted, "do_delete_all_dialog_messages");
- m = nullptr;
+ message = nullptr;
}
bool MessagesManager::have_dialog(DialogId dialog_id) const {
return dialogs_.count(dialog_id) > 0;
}
-void MessagesManager::load_dialogs(vector<DialogId> dialog_ids, Promise<Unit> &&promise) {
- LOG(INFO) << "Load dialogs " << format::as_array(dialog_ids);
+void MessagesManager::load_dialogs(vector<DialogId> dialog_ids, Promise<vector<DialogId>> &&promise) {
+ LOG(INFO) << "Load chats " << format::as_array(dialog_ids);
Dependencies dependencies;
for (auto dialog_id : dialog_ids) {
if (dialog_id.is_valid() && !have_dialog(dialog_id)) {
- add_dialog_dependencies(dependencies, dialog_id);
+ dependencies.add_dialog_dependencies(dialog_id);
}
}
- resolve_dependencies_force(dependencies);
+ dependencies.resolve_force(td_, "load_dialogs");
+
+ td::remove_if(dialog_ids, [this](DialogId dialog_id) { return !have_dialog_info(dialog_id); });
for (auto dialog_id : dialog_ids) {
- if (dialog_id.is_valid()) {
- force_create_dialog(dialog_id, "load_dialogs");
- }
+ force_create_dialog(dialog_id, "load_dialogs");
}
- promise.set_value(Unit());
+ LOG(INFO) << "Loaded chats " << format::as_array(dialog_ids);
+ promise.set_value(std::move(dialog_ids));
}
bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promise<Unit> &&promise) {
if (!dialog_id.is_valid()) {
- promise.set_error(Status::Error(6, "Invalid chat identifier"));
+ promise.set_error(Status::Error(400, "Invalid chat identifier specified"));
return false;
}
- if (!have_dialog_force(dialog_id)) { // TODO remove _force
+ if (!have_dialog_force(dialog_id, "load_dialog")) { // TODO remove _force
if (G()->parameters().use_message_db) {
// TODO load dialog from database, DialogLoader
// send_closure_later(actor_id(this), &MessagesManager::load_dialog_from_database, dialog_id,
@@ -11052,7 +16981,7 @@ bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promise<Un
break;
}
case DialogType::SecretChat:
- promise.set_error(Status::Error(6, "Chat not found"));
+ promise.set_error(Status::Error(400, "Chat not found"));
return false;
case DialogType::None:
default:
@@ -11062,11 +16991,11 @@ bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promise<Un
return false;
}
- add_dialog(dialog_id);
+ add_dialog(dialog_id, "load_dialog");
return true;
}
- promise.set_error(Status::Error(6, "Chat not found"));
+ promise.set_error(Status::Error(400, "Chat not found"));
return false;
}
@@ -11074,171 +17003,1031 @@ bool MessagesManager::load_dialog(DialogId dialog_id, int left_tries, Promise<Un
return true;
}
-vector<DialogId> MessagesManager::get_dialogs(DialogDate offset, int32 limit, bool force, Promise<Unit> &&promise) {
- LOG(INFO) << "Get chats with offset " << offset << " and limit " << limit << ". Know about order of "
- << ordered_dialogs_.size() << " chat(s). last_dialog_date_ = " << last_dialog_date_
- << ", last_server_dialog_date_ = " << last_server_dialog_date_
- << ", last_loaded_database_dialog_date_ = " << last_loaded_database_dialog_date_;
+void MessagesManager::load_dialog_filter_dialogs(DialogFilterId dialog_filter_id,
+ vector<InputDialogId> &&input_dialog_ids, Promise<Unit> &&promise) {
+ const size_t MAX_SLICE_SIZE = 100; // server side limit
+ MultiPromiseActorSafe mpas{"GetFilterDialogsOnServerMultiPromiseActor"};
+ mpas.add_promise(std::move(promise));
+ auto lock = mpas.get_promise();
+
+ for (size_t i = 0; i < input_dialog_ids.size(); i += MAX_SLICE_SIZE) {
+ auto end_i = i + MAX_SLICE_SIZE;
+ auto end = end_i < input_dialog_ids.size() ? input_dialog_ids.begin() + end_i : input_dialog_ids.end();
+ vector<InputDialogId> slice_input_dialog_ids = {input_dialog_ids.begin() + i, end};
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id,
+ dialog_ids = InputDialogId::get_dialog_ids(slice_input_dialog_ids),
+ promise = mpas.get_promise()](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ send_closure(actor_id, &MessagesManager::on_load_dialog_filter_dialogs, dialog_filter_id, std::move(dialog_ids),
+ std::move(promise));
+ });
+ td_->create_handler<GetDialogsQuery>(std::move(query_promise))->send(std::move(slice_input_dialog_ids));
+ }
+
+ lock.set_value(Unit());
+}
+
+void MessagesManager::on_load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector<DialogId> &&dialog_ids,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ td::remove_if(dialog_ids,
+ [this](DialogId dialog_id) { return have_dialog_force(dialog_id, "on_load_dialog_filter_dialogs"); });
+ if (dialog_ids.empty()) {
+ LOG(INFO) << "All chats from " << dialog_filter_id << " were loaded";
+ return promise.set_value(Unit());
+ }
+
+ LOG(INFO) << "Failed to load chats " << dialog_ids << " from " << dialog_filter_id;
+
+ auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
+ if (old_dialog_filter == nullptr) {
+ return promise.set_value(Unit());
+ }
+ CHECK(is_update_chat_filters_sent_);
+
+ auto new_dialog_filter = td::make_unique<DialogFilter>(*old_dialog_filter);
+ for (auto dialog_id : dialog_ids) {
+ InputDialogId::remove(new_dialog_filter->pinned_dialog_ids, dialog_id);
+ InputDialogId::remove(new_dialog_filter->included_dialog_ids, dialog_id);
+ InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id);
+ }
+
+ if (*new_dialog_filter != *old_dialog_filter) {
+ LOG(INFO) << "Update " << dialog_filter_id << " from " << *old_dialog_filter << " to " << *new_dialog_filter;
+ edit_dialog_filter(std::move(new_dialog_filter), "on_load_dialog_filter_dialogs");
+ save_dialog_filters();
+ send_update_chat_filters();
+
+ synchronize_dialog_filters();
+ }
+
+ promise.set_value(Unit());
+}
+
+void MessagesManager::load_dialog_filter(DialogFilterId dialog_filter_id, bool force, Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (!dialog_filter_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Invalid chat filter identifier specified"));
+ }
+
+ auto filter = get_dialog_filter(dialog_filter_id);
+ if (filter == nullptr) {
+ return promise.set_value(Unit());
+ }
+
+ load_dialog_filter(filter, force, std::move(promise));
+}
+
+void MessagesManager::load_dialog_filter(const DialogFilter *filter, bool force, Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ vector<InputDialogId> needed_dialog_ids;
+ for (auto input_dialog_ids :
+ {&filter->pinned_dialog_ids, &filter->excluded_dialog_ids, &filter->included_dialog_ids}) {
+ for (const auto &input_dialog_id : *input_dialog_ids) {
+ if (!have_dialog(input_dialog_id.get_dialog_id())) {
+ needed_dialog_ids.push_back(input_dialog_id);
+ }
+ }
+ }
+
+ vector<InputDialogId> input_dialog_ids;
+ for (const auto &input_dialog_id : needed_dialog_ids) {
+ auto dialog_id = input_dialog_id.get_dialog_id();
+ // TODO load dialogs asynchronously
+ if (!have_dialog_force(dialog_id, "load_dialog_filter")) {
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ if (have_dialog_info_force(dialog_id)) {
+ force_create_dialog(dialog_id, "load_dialog_filter");
+ }
+ } else {
+ input_dialog_ids.push_back(input_dialog_id);
+ }
+ }
+ }
+
+ if (!input_dialog_ids.empty() && !force) {
+ return load_dialog_filter_dialogs(filter->dialog_filter_id, std::move(input_dialog_ids), std::move(promise));
+ }
+
+ promise.set_value(Unit());
+}
+
+void MessagesManager::get_recommended_dialog_filters(
+ Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
+ Result<vector<tl_object_ptr<telegram_api::dialogFilterSuggested>>> result) mutable {
+ send_closure(actor_id, &MessagesManager::on_get_recommended_dialog_filters, std::move(result),
+ std::move(promise));
+ });
+ td_->create_handler<GetSuggestedDialogFiltersQuery>(std::move(query_promise))->send();
+}
+
+void MessagesManager::on_get_recommended_dialog_filters(
+ Result<vector<tl_object_ptr<telegram_api::dialogFilterSuggested>>> result,
+ Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise) {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ CHECK(!td_->auth_manager_->is_bot());
+ auto suggested_filters = result.move_as_ok();
+
+ MultiPromiseActorSafe mpas{"LoadRecommendedFiltersMultiPromiseActor"};
+ mpas.add_promise(Promise<Unit>());
+ auto lock = mpas.get_promise();
+
+ vector<RecommendedDialogFilter> filters;
+ for (auto &suggested_filter : suggested_filters) {
+ RecommendedDialogFilter filter;
+ filter.dialog_filter = DialogFilter::get_dialog_filter(std::move(suggested_filter->filter_), false);
+ CHECK(filter.dialog_filter != nullptr);
+ filter.dialog_filter->dialog_filter_id = DialogFilterId(); // just in case
+ load_dialog_filter(filter.dialog_filter.get(), false, mpas.get_promise());
+
+ filter.description = std::move(suggested_filter->description_);
+ filters.push_back(std::move(filter));
+ }
+
+ mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), filters = std::move(filters),
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ send_closure(actor_id, &MessagesManager::on_load_recommended_dialog_filters, std::move(result), std::move(filters),
+ std::move(promise));
+ }));
+ lock.set_value(Unit());
+}
+
+void MessagesManager::on_load_recommended_dialog_filters(
+ Result<Unit> &&result, vector<RecommendedDialogFilter> &&filters,
+ Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ CHECK(!td_->auth_manager_->is_bot());
+
+ auto chat_filters = transform(filters, [this](const RecommendedDialogFilter &filter) {
+ return td_api::make_object<td_api::recommendedChatFilter>(get_chat_filter_object(filter.dialog_filter.get()),
+ filter.description);
+ });
+ recommended_dialog_filters_ = std::move(filters);
+ promise.set_value(td_api::make_object<td_api::recommendedChatFilters>(std::move(chat_filters)));
+}
+
+Result<DialogDate> MessagesManager::get_dialog_list_last_date(DialogListId dialog_list_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+
+ auto *list_ptr = get_dialog_list(dialog_list_id);
+ if (list_ptr == nullptr) {
+ return Status::Error(400, "Chat list not found");
+ }
+ return list_ptr->list_last_dialog_date_;
+}
+
+vector<DialogId> MessagesManager::get_dialogs(DialogListId dialog_list_id, DialogDate offset, int32 limit,
+ bool exact_limit, bool force, Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+
+ auto *list_ptr = get_dialog_list(dialog_list_id);
+ if (list_ptr == nullptr) {
+ promise.set_error(Status::Error(400, "Chat list not found"));
+ return {};
+ }
+ auto &list = *list_ptr;
+
+ LOG(INFO) << "Get chats in " << dialog_list_id << " with offset " << offset << " and limit " << limit
+ << ". last_dialog_date = " << list.list_last_dialog_date_
+ << ", last_pinned_dialog_date_ = " << list.last_pinned_dialog_date_
+ << ", are_pinned_dialogs_inited_ = " << list.are_pinned_dialogs_inited_;
- vector<DialogId> result;
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit in getChats must be positive"));
- return result;
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ return {};
}
- if (limit > MAX_GET_DIALOGS) {
- limit = MAX_GET_DIALOGS;
+ vector<DialogId> result;
+ if (dialog_list_id == DialogListId(FolderId::main()) && sponsored_dialog_id_.is_valid()) {
+ auto d = get_dialog(sponsored_dialog_id_);
+ CHECK(d != nullptr);
+ if (is_dialog_sponsored(d)) {
+ DialogDate date(get_dialog_private_order(&list, d), d->dialog_id);
+ if (offset < date) {
+ result.push_back(sponsored_dialog_id_);
+ offset = date;
+ limit--;
+ }
+ }
}
- auto it = ordered_dialogs_.upper_bound(offset);
- auto end = ordered_dialogs_.end();
- while (it != end && limit-- > 0) {
- result.push_back(it->get_dialog_id());
- ++it;
+ if (!list.are_pinned_dialogs_inited_) {
+ if (limit == 0 || force) {
+ promise.set_value(Unit());
+ return result;
+ } else {
+ if (dialog_list_id.is_folder()) {
+ auto &folder = *get_dialog_folder(dialog_list_id.get_folder_id());
+ if (folder.last_loaded_database_dialog_date_ == folder.last_database_server_dialog_date_ &&
+ folder.folder_last_dialog_date_ != MAX_DIALOG_DATE) {
+ load_dialog_list(list, limit, std::move(promise));
+ return {};
+ }
+ }
+ reload_pinned_dialogs(dialog_list_id, std::move(promise));
+ return {};
+ }
}
+ if (dialog_list_id.is_filter()) {
+ auto *filter = get_dialog_filter(dialog_list_id.get_filter_id());
+ CHECK(filter != nullptr);
+ vector<InputDialogId> input_dialog_ids;
+ for (const auto &input_dialog_id : filter->pinned_dialog_ids) {
+ auto dialog_id = input_dialog_id.get_dialog_id();
+ if (!have_dialog_force(dialog_id, "get_dialogs")) {
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ if (have_dialog_info_force(dialog_id)) {
+ force_create_dialog(dialog_id, "get_dialogs");
+ }
+ } else {
+ input_dialog_ids.push_back(input_dialog_id);
+ }
+ }
+ }
+
+ if (!input_dialog_ids.empty()) {
+ if (limit == 0 || force) {
+ promise.set_value(Unit());
+ return result;
+ } else {
+ load_dialog_filter_dialogs(filter->dialog_filter_id, std::move(input_dialog_ids), std::move(promise));
+ return {};
+ }
+ }
+ }
+
+ if (!list.pinned_dialogs_.empty() && offset < list.pinned_dialogs_.back() && limit > 0) {
+ bool need_reload_pinned_dialogs = false;
+ bool need_remove_unknown_secret_chats = false;
+ for (auto &pinned_dialog : list.pinned_dialogs_) {
+ if (offset < pinned_dialog) {
+ auto dialog_id = pinned_dialog.get_dialog_id();
+ auto d = get_dialog_force(dialog_id, "get_dialogs");
+ if (d == nullptr) {
+ LOG(ERROR) << "Failed to load pinned " << dialog_id << " from " << dialog_list_id;
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ need_reload_pinned_dialogs = true;
+ } else {
+ need_remove_unknown_secret_chats = true;
+ }
+ continue;
+ }
+ if (d->order == DEFAULT_ORDER) {
+ LOG(INFO) << "Loaded pinned " << dialog_id << " with default order in " << dialog_list_id;
+ continue;
+ }
+ result.push_back(dialog_id);
+ offset = pinned_dialog;
+ limit--;
+ if (limit == 0) {
+ break;
+ }
+ }
+ }
+ if (need_reload_pinned_dialogs) {
+ reload_pinned_dialogs(dialog_list_id, Auto());
+ }
+ if (need_remove_unknown_secret_chats) {
+ td::remove_if(list.pinned_dialogs_, [this, &list](const DialogDate &dialog_date) {
+ auto dialog_id = dialog_date.get_dialog_id();
+ if (dialog_id.get_type() == DialogType::SecretChat && !have_dialog_force(dialog_id, "get_dialogs 2")) {
+ list.pinned_dialog_id_orders_.erase(dialog_id);
+ return true;
+ }
+ return false;
+ });
+ save_pinned_folder_dialog_ids(list);
+ }
+ }
+ update_list_last_pinned_dialog_date(list);
+
+ vector<const DialogFolder *> folders;
+ vector<std::set<DialogDate>::const_iterator> folder_iterators;
+ for (auto folder_id : get_dialog_list_folder_ids(list)) {
+ folders.push_back(get_dialog_folder(folder_id));
+ folder_iterators.push_back(folders.back()->ordered_dialogs_.upper_bound(offset));
+ }
+ while (limit > 0) {
+ size_t best_pos = 0;
+ DialogDate best_dialog_date = MAX_DIALOG_DATE;
+ for (size_t i = 0; i < folders.size(); i++) {
+ while (folder_iterators[i] != folders[i]->ordered_dialogs_.end() &&
+ *folder_iterators[i] <= list.list_last_dialog_date_ &&
+ (!is_dialog_in_list(get_dialog(folder_iterators[i]->get_dialog_id()), dialog_list_id) ||
+ get_dialog_pinned_order(&list, folder_iterators[i]->get_dialog_id()) != DEFAULT_ORDER)) {
+ ++folder_iterators[i];
+ }
+ if (folder_iterators[i] != folders[i]->ordered_dialogs_.end() &&
+ *folder_iterators[i] <= list.list_last_dialog_date_ && *folder_iterators[i] < best_dialog_date) {
+ best_pos = i;
+ best_dialog_date = *folder_iterators[i];
+ }
+ }
+ if (best_dialog_date == MAX_DIALOG_DATE || best_dialog_date.get_order() == DEFAULT_ORDER) {
+ break;
+ }
+
+ limit--;
+ result.push_back(folder_iterators[best_pos]->get_dialog_id());
+ ++folder_iterators[best_pos];
+ }
+
+ if ((!result.empty() && (!exact_limit || limit == 0)) || force || list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
+ if (limit > 0 && list.list_last_dialog_date_ != MAX_DIALOG_DATE) {
+ LOG(INFO) << "Preload next " << limit << " chats in " << dialog_list_id;
+ load_dialog_list(list, limit, Promise<Unit>());
+ }
- if (limit <= 0 || last_dialog_date_ == MAX_DIALOG_DATE || force) {
promise.set_value(Unit());
return result;
+ } else {
+ if (!result.empty()) {
+ LOG(INFO) << "Have only " << result.size() << " chats, but " << limit << " chats more are needed";
+ }
+ load_dialog_list(list, limit, std::move(promise));
+ return {};
}
+}
- load_dialog_list(std::move(promise));
- return result;
+void MessagesManager::load_dialog_list(DialogList &list, int32 limit, Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (limit > MAX_GET_DIALOGS + 2) {
+ limit = MAX_GET_DIALOGS + 2;
+ }
+ bool is_request_sent = false;
+ for (auto folder_id : get_dialog_list_folder_ids(list)) {
+ const auto &folder = *get_dialog_folder(folder_id);
+ if (folder.folder_last_dialog_date_ != MAX_DIALOG_DATE) {
+ load_folder_dialog_list(folder_id, limit, false);
+ is_request_sent = true;
+ }
+ }
+ if (is_request_sent) {
+ LOG(INFO) << "Wait for loading of " << limit << " chats in " << list.dialog_list_id;
+ list.load_list_queries_.push_back(std::move(promise));
+ } else {
+ LOG(ERROR) << "There is nothing to load for " << list.dialog_list_id << " with folders "
+ << get_dialog_list_folder_ids(list);
+ promise.set_error(Status::Error(404, "Not Found"));
+ }
}
-void MessagesManager::load_dialog_list(Promise<Unit> &&promise) {
- auto &multipromise = load_dialog_list_multipromise_;
- multipromise.add_promise(std::move(promise));
- if (multipromise.promise_count() != 1) {
+void MessagesManager::load_folder_dialog_list(FolderId folder_id, int32 limit, bool only_local) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(!td_->auth_manager_->is_bot());
+ auto &folder = *get_dialog_folder(folder_id);
+ if (folder.folder_last_dialog_date_ == MAX_DIALOG_DATE) {
+ return;
+ }
+
+ bool use_database = G()->parameters().use_message_db &&
+ folder.last_loaded_database_dialog_date_ < folder.last_database_server_dialog_date_;
+ if (only_local && !use_database) {
+ return;
+ }
+
+ auto &multipromise = folder.load_folder_dialog_list_multipromise_;
+ if (multipromise.promise_count() != 0) {
// queries have already been sent, just wait for the result
+ LOG(INFO) << "Skip loading of dialog list in " << folder_id << " with limit " << limit
+ << ", because it is already being loaded";
+ if (use_database && folder.load_dialog_list_limit_max_ != 0) {
+ folder.load_dialog_list_limit_max_ = max(folder.load_dialog_list_limit_max_, limit);
+ }
return;
}
+ LOG(INFO) << "Load chat list in " << folder_id << " with limit " << limit;
+ multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), folder_id](Result<Unit> result) {
+ send_closure_later(actor_id, &MessagesManager::on_load_folder_dialog_list, folder_id, std::move(result));
+ }));
bool is_query_sent = false;
- if (G()->parameters().use_message_db && last_loaded_database_dialog_date_ < last_database_server_dialog_date_) {
- load_dialog_list_from_database(MAX_GET_DIALOGS, multipromise.get_promise());
+ if (use_database) {
+ load_folder_dialog_list_from_database(folder_id, limit, multipromise.get_promise());
is_query_sent = true;
} else {
- LOG(INFO) << "Get dialogs from " << last_server_dialog_date_;
- auto sequence_id = get_sequence_dispatcher_id(DialogId(), -1);
- send_closure(td_->create_net_actor<GetPinnedDialogsQuery>(multipromise.get_promise()), &GetPinnedDialogsQuery::send,
- sequence_id);
- if (last_dialog_date_ == last_server_dialog_date_) {
- send_closure(td_->create_net_actor<GetDialogListQuery>(multipromise.get_promise()), &GetDialogListQuery::send,
- last_server_dialog_date_.get_date(),
- last_server_dialog_date_.get_message_id().get_next_server_message_id().get_server_message_id(),
- last_server_dialog_date_.get_dialog_id(), int32{MAX_GET_DIALOGS}, sequence_id);
+ LOG(INFO) << "Get chats from " << folder.last_server_dialog_date_;
+ multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), folder_id](Result<Unit> result) {
+ if (result.is_ok()) {
+ send_closure(actor_id, &MessagesManager::recalc_unread_count, DialogListId(folder_id), -1, true);
+ }
+ }));
+ auto lock = multipromise.get_promise();
+ reload_pinned_dialogs(DialogListId(folder_id), multipromise.get_promise());
+ if (folder.folder_last_dialog_date_ == folder.last_server_dialog_date_) {
+ td_->create_handler<GetDialogListQuery>(multipromise.get_promise())
+ ->send(folder_id, folder.last_server_dialog_date_.get_date(),
+ folder.last_server_dialog_date_.get_message_id().get_next_server_message_id().get_server_message_id(),
+ folder.last_server_dialog_date_.get_dialog_id(), int32{MAX_GET_DIALOGS});
is_query_sent = true;
}
+ if (folder_id == FolderId::main() && folder.last_server_dialog_date_ == MIN_DIALOG_DATE) {
+ // do not pass promise to not wait for drafts before showing chat list
+ td_->create_handler<GetAllDraftsQuery>()->send();
+ }
+ lock.set_value(Unit());
}
CHECK(is_query_sent);
}
-void MessagesManager::load_dialog_list_from_database(int32 limit, Promise<Unit> &&promise) {
- LOG(INFO) << "Load dialogs from " << last_loaded_database_dialog_date_
- << ", last database server dialog date = " << last_database_server_dialog_date_;
+void MessagesManager::on_load_folder_dialog_list(FolderId folder_id, Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ return;
+ }
+ CHECK(!td_->auth_manager_->is_bot());
+
+ const auto &folder = *get_dialog_folder(folder_id);
+ if (result.is_ok()) {
+ LOG(INFO) << "Successfully loaded chats in " << folder_id;
+ if (folder.last_server_dialog_date_ == MAX_DIALOG_DATE) {
+ return;
+ }
+
+ bool need_new_get_dialog_list = false;
+ for (const auto &list_it : dialog_lists_) {
+ auto &list = list_it.second;
+ if (!list.load_list_queries_.empty() && has_dialogs_from_folder(list, folder)) {
+ LOG(INFO) << "Need to load more chats in " << folder_id << " for " << list_it.first;
+ need_new_get_dialog_list = true;
+ }
+ }
+ if (need_new_get_dialog_list) {
+ load_folder_dialog_list(folder_id, int32{MAX_GET_DIALOGS}, false);
+ }
+ return;
+ }
+
+ LOG(WARNING) << "Failed to load chats in " << folder_id << ": " << result.error();
+ vector<Promise<Unit>> promises;
+ for (auto &list_it : dialog_lists_) {
+ auto &list = list_it.second;
+ if (!list.load_list_queries_.empty() && has_dialogs_from_folder(list, folder)) {
+ append(promises, std::move(list.load_list_queries_));
+ list.load_list_queries_.clear();
+ }
+ }
+
+ fail_promises(promises, result.move_as_error());
+}
+
+void MessagesManager::load_folder_dialog_list_from_database(FolderId folder_id, int32 limit, Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto &folder = *get_dialog_folder(folder_id);
+ LOG(INFO) << "Load " << limit << " chats in " << folder_id << " from database from "
+ << folder.last_loaded_database_dialog_date_
+ << ", last database server dialog date = " << folder.last_database_server_dialog_date_;
+ CHECK(folder.load_dialog_list_limit_max_ == 0);
+ folder.load_dialog_list_limit_max_ = limit;
G()->td_db()->get_dialog_db_async()->get_dialogs(
- last_loaded_database_dialog_date_.get_order(), last_loaded_database_dialog_date_.get_dialog_id(), limit,
- PromiseCreator::lambda([actor_id = actor_id(this),
- promise = std::move(promise)](vector<BufferSlice> result) mutable {
- send_closure(actor_id, &MessagesManager::on_get_dialogs_from_database, std::move(result), std::move(promise));
+ folder_id, folder.last_loaded_database_dialog_date_.get_order(),
+ folder.last_loaded_database_dialog_date_.get_dialog_id(), limit,
+ PromiseCreator::lambda([actor_id = actor_id(this), folder_id, limit,
+ promise = std::move(promise)](DialogDbGetDialogsResult result) mutable {
+ send_closure(actor_id, &MessagesManager::on_get_dialogs_from_database, folder_id, limit, std::move(result),
+ std::move(promise));
}));
}
-void MessagesManager::on_get_dialogs_from_database(vector<BufferSlice> &&dialogs, Promise<Unit> &&promise) {
- LOG(INFO) << "Receive " << dialogs.size() << " dialogs in result of GetDialogsFromDatabase";
- DialogDate max_dialog_date = MIN_DIALOG_DATE;
- for (auto &dialog : dialogs) {
- Dialog *d = on_load_dialog_from_database(std::move(dialog));
- CHECK(d != nullptr);
+void MessagesManager::on_get_dialogs_from_database(FolderId folder_id, int32 limit, DialogDbGetDialogsResult &&dialogs,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ CHECK(!td_->auth_manager_->is_bot());
+ auto &folder = *get_dialog_folder(folder_id);
+ LOG(INFO) << "Receive " << dialogs.dialogs.size() << " from expected " << limit << " chats in " << folder_id
+ << " in from database with next order " << dialogs.next_order << " and next " << dialogs.next_dialog_id;
+ int32 new_get_dialogs_limit = 0;
+ int32 have_more_dialogs_in_database = (limit == static_cast<int32>(dialogs.dialogs.size()));
+ if (have_more_dialogs_in_database && limit < folder.load_dialog_list_limit_max_) {
+ new_get_dialogs_limit = folder.load_dialog_list_limit_max_ - limit;
+ }
+ folder.load_dialog_list_limit_max_ = 0;
+
+ size_t dialogs_skipped = 0;
+ for (auto &dialog : dialogs.dialogs) {
+ Dialog *d = on_load_dialog_from_database(DialogId(), std::move(dialog), "on_get_dialogs_from_database");
+ if (d == nullptr) {
+ dialogs_skipped++;
+ continue;
+ }
+ if (d->folder_id != folder_id) {
+ LOG(WARNING) << "Skip " << d->dialog_id << " received from database, because it is in " << d->folder_id
+ << " instead of " << folder_id;
+ dialogs_skipped++;
+ continue;
+ }
+
+ LOG(INFO) << "Loaded from database " << d->dialog_id << " with order " << d->order;
+ }
+
+ DialogDate max_dialog_date(dialogs.next_order, dialogs.next_dialog_id);
+ if (!have_more_dialogs_in_database) {
+ folder.last_loaded_database_dialog_date_ = MAX_DIALOG_DATE;
+ LOG(INFO) << "Set last loaded database dialog date to " << folder.last_loaded_database_dialog_date_;
+ folder.last_server_dialog_date_ = max(folder.last_server_dialog_date_, folder.last_database_server_dialog_date_);
+ LOG(INFO) << "Set last server dialog date to " << folder.last_server_dialog_date_;
+ update_last_dialog_date(folder_id);
+ } else if (folder.last_loaded_database_dialog_date_ < max_dialog_date) {
+ folder.last_loaded_database_dialog_date_ = min(max_dialog_date, folder.last_database_server_dialog_date_);
+ LOG(INFO) << "Set last loaded database dialog date to " << folder.last_loaded_database_dialog_date_;
+ folder.last_server_dialog_date_ = max(folder.last_server_dialog_date_, folder.last_loaded_database_dialog_date_);
+ LOG(INFO) << "Set last server dialog date to " << folder.last_server_dialog_date_;
+ update_last_dialog_date(folder_id);
- DialogDate dialog_date(d->order, d->dialog_id);
- if (max_dialog_date < dialog_date) {
- max_dialog_date = dialog_date;
+ for (const auto &list_it : dialog_lists_) {
+ auto &list = list_it.second;
+ if (!list.load_list_queries_.empty() && has_dialogs_from_folder(list, folder) && new_get_dialogs_limit < limit) {
+ new_get_dialogs_limit = limit;
+ }
}
- LOG(INFO) << "Chat " << dialog_date << " is loaded from database";
+ } else {
+ LOG(ERROR) << "Last loaded database dialog date didn't increased, skipped " << dialogs_skipped << " chats out of "
+ << dialogs.dialogs.size();
}
- if (dialogs.empty()) {
- // if there is no more dialogs in the database
- last_loaded_database_dialog_date_ = MAX_DIALOG_DATE;
- LOG(INFO) << "Set last loaded database dialog date to " << last_loaded_database_dialog_date_;
- last_server_dialog_date_ = max(last_server_dialog_date_, last_database_server_dialog_date_);
- LOG(INFO) << "Set last server dialog date to " << last_server_dialog_date_;
- update_last_dialog_date();
+ if (!(folder.last_loaded_database_dialog_date_ < folder.last_database_server_dialog_date_)) {
+ // have_more_dialogs_in_database = false;
+ new_get_dialogs_limit = 0;
}
- if (last_loaded_database_dialog_date_ < max_dialog_date) {
- last_loaded_database_dialog_date_ = min(max_dialog_date, last_database_server_dialog_date_);
- LOG(INFO) << "Set last loaded database dialog date to " << last_loaded_database_dialog_date_;
- last_server_dialog_date_ = max(last_server_dialog_date_, last_loaded_database_dialog_date_);
- LOG(INFO) << "Set last server dialog date to " << last_server_dialog_date_;
- update_last_dialog_date();
- } else if (!dialogs.empty()) {
- LOG(ERROR) << "Last loaded database dialog date didn't increased";
+
+ if (new_get_dialogs_limit == 0) {
+ preload_folder_dialog_list_timeout_.add_timeout_in(folder_id.get(), 0.2);
+ promise.set_value(Unit());
+ } else {
+ load_folder_dialog_list_from_database(folder_id, new_get_dialogs_limit, std::move(promise));
+ }
+}
+
+void MessagesManager::preload_folder_dialog_list(FolderId folder_id) {
+ if (G()->close_flag()) {
+ LOG(INFO) << "Skip chat list preload in " << folder_id << " because of closing";
+ return;
}
+ CHECK(!td_->auth_manager_->is_bot());
- if (!preload_dialog_list_timeout_.has_timeout()) {
- LOG(INFO) << "Schedule chat list preload";
- preload_dialog_list_timeout_.set_callback(std::move(MessagesManager::preload_dialog_list));
- preload_dialog_list_timeout_.set_callback_data(static_cast<void *>(this));
+ auto &folder = *get_dialog_folder(folder_id);
+ CHECK(G()->parameters().use_message_db);
+ if (folder.load_folder_dialog_list_multipromise_.promise_count() != 0) {
+ LOG(INFO) << "Skip chat list preload in " << folder_id << ", because there is a pending load chat list request";
+ return;
}
- preload_dialog_list_timeout_.set_timeout_in(0.2);
- promise.set_value(Unit());
+ if (folder.last_loaded_database_dialog_date_ < folder.last_database_server_dialog_date_) {
+ // if there are some dialogs in database, preload some of them
+ load_folder_dialog_list(folder_id, 20, true);
+ } else if (folder.folder_last_dialog_date_ != MAX_DIALOG_DATE) {
+ // otherwise load more dialogs from the server
+ load_folder_dialog_list(folder_id, MAX_GET_DIALOGS, false);
+ } else {
+ recalc_unread_count(DialogListId(folder_id), -1, false);
+ }
+}
+
+void MessagesManager::get_dialogs_from_list(DialogListId dialog_list_id, int32 limit,
+ Promise<td_api::object_ptr<td_api::chats>> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+
+ if (get_dialog_list(dialog_list_id) == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat list not found"));
+ }
+
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ }
+
+ auto task_id = ++current_get_dialogs_task_id_;
+ auto &task = get_dialogs_tasks_[task_id];
+ task.dialog_list_id = dialog_list_id;
+ task.retry_count = 5;
+ task.limit = limit;
+ task.promise = std::move(promise);
+ get_dialogs_from_list_impl(task_id);
+}
+
+void MessagesManager::get_dialogs_from_list_impl(int64 task_id) {
+ auto task_it = get_dialogs_tasks_.find(task_id);
+ CHECK(task_it != get_dialogs_tasks_.end());
+ auto &task = task_it->second;
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), task_id](Result<Unit> &&result) {
+ // on_get_dialogs_from_list can delete get_dialogs_tasks_[task_id], so it must be called later
+ send_closure_later(actor_id, &MessagesManager::on_get_dialogs_from_list, task_id, std::move(result));
+ });
+ auto dialog_ids = get_dialogs(task.dialog_list_id, MIN_DIALOG_DATE, task.limit, true, false, std::move(promise));
+ auto &list = *get_dialog_list(task.dialog_list_id);
+ auto total_count = get_dialog_total_count(list);
+ LOG(INFO) << "Receive " << dialog_ids.size() << " chats instead of " << task.limit << " out of " << total_count
+ << " in " << task.dialog_list_id;
+ CHECK(dialog_ids.size() <= static_cast<size_t>(total_count));
+ CHECK(dialog_ids.size() <= static_cast<size_t>(task.limit));
+ if (dialog_ids.size() == static_cast<size_t>(min(total_count, task.limit)) ||
+ list.list_last_dialog_date_ == MAX_DIALOG_DATE || task.retry_count == 0) {
+ auto task_promise = std::move(task.promise);
+ get_dialogs_tasks_.erase(task_it);
+ if (!task_promise) {
+ dialog_ids.clear();
+ }
+ return task_promise.set_value(get_chats_object(total_count, dialog_ids));
+ }
+ // nor the limit, nor the end of the list were reached; wait for the promise
+}
+
+void MessagesManager::on_get_dialogs_from_list(int64 task_id, Result<Unit> &&result) {
+ auto task_it = get_dialogs_tasks_.find(task_id);
+ if (task_it == get_dialogs_tasks_.end()) {
+ // the task has already been completed
+ LOG(INFO) << "Chat list load task " << task_id << " has already been completed";
+ return;
+ }
+ auto &task = task_it->second;
+ if (result.is_error()) {
+ LOG(INFO) << "Chat list load task " << task_id << " failed with the error " << result.error();
+ auto task_promise = std::move(task.promise);
+ get_dialogs_tasks_.erase(task_it);
+ return task_promise.set_error(result.move_as_error());
+ }
+
+ auto list_ptr = get_dialog_list(task.dialog_list_id);
+ CHECK(list_ptr != nullptr);
+ auto &list = *list_ptr;
+ if (task.last_dialog_date == list.list_last_dialog_date_) {
+ // no new chats were loaded
+ task.retry_count--;
+ } else {
+ CHECK(task.last_dialog_date < list.list_last_dialog_date_);
+ task.last_dialog_date = list.list_last_dialog_date_;
+ task.retry_count = 5;
+ }
+ get_dialogs_from_list_impl(task_id);
+}
+
+vector<DialogId> MessagesManager::get_pinned_dialog_ids(DialogListId dialog_list_id) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (dialog_list_id.is_filter()) {
+ const auto *filter = get_dialog_filter(dialog_list_id.get_filter_id());
+ if (filter == nullptr) {
+ return {};
+ }
+ return InputDialogId::get_dialog_ids(filter->pinned_dialog_ids);
+ }
+
+ auto *list = get_dialog_list(dialog_list_id);
+ if (list == nullptr || !list->are_pinned_dialogs_inited_) {
+ return {};
+ }
+ return transform(list->pinned_dialogs_, [](auto &pinned_dialog) { return pinned_dialog.get_dialog_id(); });
+}
+
+void MessagesManager::reload_pinned_dialogs(DialogListId dialog_list_id, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ CHECK(!td_->auth_manager_->is_bot());
+
+ if (dialog_list_id.is_folder()) {
+ td_->create_handler<GetPinnedDialogsQuery>(std::move(promise))->send(dialog_list_id.get_folder_id());
+ } else if (dialog_list_id.is_filter()) {
+ schedule_dialog_filters_reload(0.0);
+ dialog_filter_reload_queries_.push_back(std::move(promise));
+ }
+}
+
+double MessagesManager::get_dialog_filters_cache_time() {
+ return DIALOG_FILTERS_CACHE_TIME * 0.0001 * Random::fast(9000, 11000);
+}
+
+void MessagesManager::schedule_dialog_filters_reload(double timeout) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+ if (timeout <= 0) {
+ timeout = 0.0;
+ if (dialog_filters_updated_date_ != 0) {
+ dialog_filters_updated_date_ = 0;
+ save_dialog_filters();
+ }
+ }
+ LOG(INFO) << "Schedule reload of chat filters in " << timeout;
+ reload_dialog_filters_timeout_.set_callback(std::move(MessagesManager::on_reload_dialog_filters_timeout));
+ reload_dialog_filters_timeout_.set_callback_data(static_cast<void *>(this));
+ reload_dialog_filters_timeout_.set_timeout_in(timeout);
}
-void MessagesManager::preload_dialog_list(void *messages_manager_void) {
+void MessagesManager::on_reload_dialog_filters_timeout(void *messages_manager_ptr) {
if (G()->close_flag()) {
return;
}
+ auto messages_manager = static_cast<MessagesManager *>(messages_manager_ptr);
+ send_closure_later(messages_manager->actor_id(messages_manager), &MessagesManager::reload_dialog_filters);
+}
- CHECK(messages_manager_void != nullptr);
- auto messages_manager = static_cast<MessagesManager *>(messages_manager_void);
+void MessagesManager::reload_dialog_filters() {
+ if (G()->close_flag()) {
+ return;
+ }
+ CHECK(!td_->auth_manager_->is_bot());
+ if (are_dialog_filters_being_synchronized_ || are_dialog_filters_being_reloaded_) {
+ need_dialog_filters_reload_ = true;
+ return;
+ }
+ LOG(INFO) << "Reload chat filters from server";
+ are_dialog_filters_being_reloaded_ = true;
+ need_dialog_filters_reload_ = false;
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<vector<tl_object_ptr<telegram_api::DialogFilter>>> r_filters) {
+ send_closure(actor_id, &MessagesManager::on_get_dialog_filters, std::move(r_filters), false);
+ });
+ td_->create_handler<GetDialogFiltersQuery>(std::move(promise))->send();
+}
- CHECK(G()->parameters().use_message_db);
- if (messages_manager->load_dialog_list_multipromise_.promise_count() != 0) {
- // do nothing if there is pending load dialog list request
+void MessagesManager::on_get_dialog_filters(Result<vector<tl_object_ptr<telegram_api::DialogFilter>>> r_filters,
+ bool dummy) {
+ if (G()->close_flag()) {
return;
}
- if (messages_manager->ordered_dialogs_.size() > MAX_PRELOADED_DIALOGS) {
- // do nothing if there are more than MAX_PRELOADED_DIALOGS dialogs already loaded
- messages_manager->recalc_unread_message_count();
+ are_dialog_filters_being_reloaded_ = false;
+ CHECK(!td_->auth_manager_->is_bot());
+ auto promises = std::move(dialog_filter_reload_queries_);
+ dialog_filter_reload_queries_.clear();
+ if (r_filters.is_error()) {
+ if (!G()->is_expected_error(r_filters.error())) {
+ LOG(WARNING) << "Receive error " << r_filters.error() << " for GetDialogFiltersQuery";
+ }
+ fail_promises(promises, r_filters.move_as_error());
+ need_dialog_filters_reload_ = false;
+ schedule_dialog_filters_reload(Random::fast(60, 5 * 60));
return;
}
- if (messages_manager->last_loaded_database_dialog_date_ < messages_manager->last_database_server_dialog_date_) {
- // if there are some dialogs in database, preload some of them
- messages_manager->load_dialog_list_from_database(20, Auto());
- } else if (messages_manager->last_dialog_date_ != MAX_DIALOG_DATE) {
- messages_manager->load_dialog_list(PromiseCreator::lambda([messages_manager](Result<Unit> result) {
- if (result.is_ok()) {
- messages_manager->recalc_unread_message_count();
+ auto filters = r_filters.move_as_ok();
+ vector<unique_ptr<DialogFilter>> new_server_dialog_filters;
+ LOG(INFO) << "Receive chat filters from server: " << to_string(filters);
+ std::unordered_set<DialogFilterId, DialogFilterIdHash> new_dialog_filter_ids;
+ int32 server_main_dialog_list_position = -1;
+ int32 position = 0;
+ for (auto &filter : filters) {
+ if (filter->get_id() == telegram_api::dialogFilterDefault::ID) {
+ if (server_main_dialog_list_position == -1) {
+ server_main_dialog_list_position = position;
+ } else {
+ LOG(ERROR) << "Receive duplicate dialogFilterDefault";
}
- }));
- } else {
- messages_manager->recalc_unread_message_count();
+ continue;
+ }
+ auto dialog_filter = DialogFilter::get_dialog_filter(std::move(filter), true);
+ if (dialog_filter == nullptr) {
+ continue;
+ }
+ if (!new_dialog_filter_ids.insert(dialog_filter->dialog_filter_id).second) {
+ LOG(ERROR) << "Receive duplicate " << dialog_filter->dialog_filter_id;
+ continue;
+ }
+
+ sort_dialog_filter_input_dialog_ids(dialog_filter.get(), "on_get_dialog_filters 1");
+ new_server_dialog_filters.push_back(std::move(dialog_filter));
+ position++;
+ }
+ if (server_main_dialog_list_position == -1) {
+ LOG(ERROR) << "Receive no dialogFilterDefault";
+ server_main_dialog_list_position = 0;
+ }
+ if (server_main_dialog_list_position != 0 && !td_->option_manager_->get_option_boolean("is_premium")) {
+ LOG(INFO) << "Ignore server main chat list position " << server_main_dialog_list_position;
+ server_main_dialog_list_position = 0;
}
+
+ bool is_changed = false;
+ dialog_filters_updated_date_ = G()->unix_time();
+ if (server_dialog_filters_ != new_server_dialog_filters) {
+ LOG(INFO) << "Change server chat filters from "
+ << get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_) << " to "
+ << get_dialog_filter_ids(new_server_dialog_filters, server_main_dialog_list_position);
+ FlatHashMap<DialogFilterId, const DialogFilter *, DialogFilterIdHash> old_server_dialog_filters;
+ for (const auto &filter : server_dialog_filters_) {
+ old_server_dialog_filters.emplace(filter->dialog_filter_id, filter.get());
+ }
+ for (const auto &new_server_filter : new_server_dialog_filters) {
+ auto dialog_filter_id = new_server_filter->dialog_filter_id;
+ auto old_filter = get_dialog_filter(dialog_filter_id);
+ auto it = old_server_dialog_filters.find(dialog_filter_id);
+ if (it != old_server_dialog_filters.end()) {
+ auto old_server_filter = it->second;
+ if (*new_server_filter != *old_server_filter) {
+ if (old_filter == nullptr) {
+ // the filter was deleted, don't need to edit it
+ } else {
+ if (DialogFilter::are_equivalent(*old_filter, *new_server_filter)) { // fast path
+ // the filter was edited from this client, nothing to do
+ } else {
+ auto new_filter =
+ DialogFilter::merge_dialog_filter_changes(old_filter, old_server_filter, new_server_filter.get());
+ LOG(INFO) << "Old local filter: " << *old_filter;
+ LOG(INFO) << "Old server filter: " << *old_server_filter;
+ LOG(INFO) << "New server filter: " << *new_server_filter;
+ LOG(INFO) << "New local filter: " << *new_filter;
+ sort_dialog_filter_input_dialog_ids(new_filter.get(), "on_get_dialog_filters 2");
+ if (*new_filter != *old_filter) {
+ is_changed = true;
+ edit_dialog_filter(std::move(new_filter), "on_get_dialog_filters");
+ }
+ }
+ }
+ }
+ old_server_dialog_filters.erase(it);
+ } else {
+ if (old_filter == nullptr) {
+ // the filter was added from another client
+ is_changed = true;
+ add_dialog_filter(make_unique<DialogFilter>(*new_server_filter), false, "on_get_dialog_filters");
+ } else {
+ // the filter was added from this client
+ // after that it could be added from another client, or edited from this client, or edited from another client
+ // prefer local value, so do nothing
+ // effectively, ignore edits from other clients, if didn't receive UpdateDialogFilterQuery response
+ }
+ }
+ }
+ vector<DialogFilterId> left_old_server_dialog_filter_ids;
+ for (const auto &filter : server_dialog_filters_) {
+ if (old_server_dialog_filters.count(filter->dialog_filter_id) == 0) {
+ left_old_server_dialog_filter_ids.push_back(filter->dialog_filter_id);
+ }
+ }
+ LOG(INFO) << "Still existing server chat filters: " << left_old_server_dialog_filter_ids;
+ for (auto &old_server_filter : old_server_dialog_filters) {
+ auto dialog_filter_id = old_server_filter.first;
+ // deleted filter
+ auto old_filter = get_dialog_filter(dialog_filter_id);
+ if (old_filter == nullptr) {
+ // the filter was deleted from this client, nothing to do
+ } else {
+ // the filter was deleted from another client
+ // ignore edits done from the current client and just delete the filter
+ is_changed = true;
+ delete_dialog_filter(dialog_filter_id, "on_get_dialog_filters");
+ }
+ }
+ bool is_order_changed = [&] {
+ vector<DialogFilterId> new_server_dialog_filter_ids = get_dialog_filter_ids(new_server_dialog_filters, -1);
+ CHECK(new_server_dialog_filter_ids.size() >= left_old_server_dialog_filter_ids.size());
+ new_server_dialog_filter_ids.resize(left_old_server_dialog_filter_ids.size());
+ return new_server_dialog_filter_ids != left_old_server_dialog_filter_ids;
+ }();
+ if (is_order_changed) { // if order is changed from this and other clients, prefer order from another client
+ vector<DialogFilterId> new_dialog_filter_order;
+ for (const auto &new_server_filter : new_server_dialog_filters) {
+ auto dialog_filter_id = new_server_filter->dialog_filter_id;
+ if (get_dialog_filter(dialog_filter_id) != nullptr) {
+ new_dialog_filter_order.push_back(dialog_filter_id);
+ }
+ }
+ is_changed = true;
+ set_dialog_filters_order(dialog_filters_, new_dialog_filter_order);
+ }
+
+ server_dialog_filters_ = std::move(new_server_dialog_filters);
+ }
+ if (server_main_dialog_list_position_ != server_main_dialog_list_position) {
+ server_main_dialog_list_position_ = server_main_dialog_list_position;
+
+ int32 main_dialog_list_position = -1;
+ if (server_main_dialog_list_position == 0) {
+ main_dialog_list_position = 0;
+ } else {
+ int32 current_position = 0;
+ int32 current_server_position = 0;
+ for (const auto &dialog_filter : dialog_filters_) {
+ current_position++;
+ if (!dialog_filter->is_empty(true)) {
+ current_server_position++;
+ }
+ if (current_server_position == server_main_dialog_list_position) {
+ main_dialog_list_position = current_position;
+ }
+ }
+ if (main_dialog_list_position == -1) {
+ LOG(INFO) << "Failed to find server position " << server_main_dialog_list_position << " in chat filters";
+ main_dialog_list_position = static_cast<int32>(dialog_filters_.size());
+ }
+ }
+
+ if (main_dialog_list_position != main_dialog_list_position_) {
+ LOG(INFO) << "Change main chat list position from " << main_dialog_list_position_ << " to "
+ << main_dialog_list_position;
+ main_dialog_list_position_ = main_dialog_list_position;
+ is_changed = true;
+ }
+ }
+ if (is_changed || !is_update_chat_filters_sent_) {
+ send_update_chat_filters();
+ }
+ schedule_dialog_filters_reload(get_dialog_filters_cache_time());
+ save_dialog_filters();
+
+ if (need_synchronize_dialog_filters()) {
+ synchronize_dialog_filters();
+ }
+ set_promises(promises);
}
-vector<DialogId> MessagesManager::get_pinned_dialogs() const {
- vector<DialogId> result;
+bool MessagesManager::need_synchronize_dialog_filters() const {
+ CHECK(!td_->auth_manager_->is_bot());
+ size_t server_dialog_filter_count = 0;
+ vector<DialogFilterId> dialog_filter_ids;
+ for (const auto &dialog_filter : dialog_filters_) {
+ if (dialog_filter->is_empty(true)) {
+ continue;
+ }
- auto it = ordered_dialogs_.begin();
- auto end = ordered_dialogs_.end();
- while (it != end && it->get_date() >= MIN_PINNED_DIALOG_DATE) {
- result.push_back(it->get_dialog_id());
- ++it;
+ server_dialog_filter_count++;
+ auto server_dialog_filter = get_server_dialog_filter(dialog_filter->dialog_filter_id);
+ if (server_dialog_filter == nullptr || !DialogFilter::are_equivalent(*server_dialog_filter, *dialog_filter)) {
+ // need update dialog filter on server
+ return true;
+ }
+ dialog_filter_ids.push_back(dialog_filter->dialog_filter_id);
+ }
+ if (server_dialog_filter_count != server_dialog_filters_.size()) {
+ // need delete dialog filter on server
+ return true;
+ }
+ if (dialog_filter_ids != get_dialog_filter_ids(server_dialog_filters_, -1)) {
+ // need reorder dialog filters on server
+ return true;
+ }
+ if (get_server_main_dialog_list_position() != server_main_dialog_list_position_) {
+ // need reorder main chat list on server
+ return true;
}
+ return false;
+}
- return result;
+void MessagesManager::synchronize_dialog_filters() {
+ if (G()->close_flag()) {
+ return;
+ }
+ CHECK(!td_->auth_manager_->is_bot());
+ if (are_dialog_filters_being_synchronized_ || are_dialog_filters_being_reloaded_) {
+ return;
+ }
+ if (need_dialog_filters_reload_) {
+ return reload_dialog_filters();
+ }
+ if (!need_synchronize_dialog_filters()) {
+ // reload filters to repair their order if the server added new filter to the beginning of the list
+ return reload_dialog_filters();
+ }
+
+ LOG(INFO) << "Synchronize chat filter changes with server having local "
+ << get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_) << " and server "
+ << get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_);
+ for (const auto &server_dialog_filter : server_dialog_filters_) {
+ if (get_dialog_filter(server_dialog_filter->dialog_filter_id) == nullptr) {
+ return delete_dialog_filter_on_server(server_dialog_filter->dialog_filter_id);
+ }
+ }
+
+ vector<DialogFilterId> dialog_filter_ids;
+ for (const auto &dialog_filter : dialog_filters_) {
+ if (dialog_filter->is_empty(true)) {
+ continue;
+ }
+
+ auto server_dialog_filter = get_server_dialog_filter(dialog_filter->dialog_filter_id);
+ if (server_dialog_filter == nullptr || !DialogFilter::are_equivalent(*server_dialog_filter, *dialog_filter)) {
+ return update_dialog_filter_on_server(make_unique<DialogFilter>(*dialog_filter));
+ }
+ dialog_filter_ids.push_back(dialog_filter->dialog_filter_id);
+ }
+
+ auto server_main_dialog_list_position = get_server_main_dialog_list_position();
+ if (dialog_filter_ids != get_dialog_filter_ids(server_dialog_filters_, -1) ||
+ server_main_dialog_list_position != server_main_dialog_list_position_) {
+ return reorder_dialog_filters_on_server(std::move(dialog_filter_ids), server_main_dialog_list_position);
+ }
+
+ UNREACHABLE();
}
vector<DialogId> MessagesManager::search_public_dialogs(const string &query, Promise<Unit> &&promise) {
LOG(INFO) << "Search public chats with query = \"" << query << '"';
- if (utf8_length(query) < MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN) {
+ auto query_length = utf8_length(query);
+ if (query_length < MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN ||
+ (query_length == MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN && query[0] == '@')) {
string username = clean_username(query);
if (username[0] == '@') {
username = username.substr(1);
@@ -11247,18 +18036,26 @@ vector<DialogId> MessagesManager::search_public_dialogs(const string &query, Pro
for (auto &short_username : get_valid_short_usernames()) {
if (2 * username.size() > short_username.size() && begins_with(short_username, username)) {
username = short_username.str();
- auto it = resolved_usernames_.find(username);
- if (it == resolved_usernames_.end()) {
+ auto resolved_username = resolved_usernames_.get(username);
+ if (!resolved_username.dialog_id.is_valid()) {
td_->create_handler<ResolveUsernameQuery>(std::move(promise))->send(username);
return {};
}
- if (it->second.expires_at < Time::now()) {
- td_->create_handler<ResolveUsernameQuery>(Promise<>())->send(username);
+ if (resolved_username.expires_at < Time::now()) {
+ td_->create_handler<ResolveUsernameQuery>(Promise<Unit>())->send(username);
}
- auto dialog_id = it->second.dialog_id;
+ auto dialog_id = resolved_username.dialog_id;
force_create_dialog(dialog_id, "public dialogs search");
+
+ auto d = get_dialog(dialog_id);
+ if (d == nullptr || d->order != DEFAULT_ORDER ||
+ (dialog_id.get_type() == DialogType::User &&
+ td_->contacts_manager_->is_user_contact(dialog_id.get_user_id()))) {
+ continue;
+ }
+
promise.set_value(Unit());
return {dialog_id};
}
@@ -11274,10 +18071,11 @@ vector<DialogId> MessagesManager::search_public_dialogs(const string &query, Pro
}
send_search_public_dialogs_query(query, std::move(promise));
- return vector<DialogId>();
+ return {};
}
void MessagesManager::send_search_public_dialogs_query(const string &query, Promise<Unit> &&promise) {
+ CHECK(!query.empty());
auto &promises = search_public_dialogs_queries_[query];
promises.push_back(std::move(promise));
if (promises.size() != 1) {
@@ -11288,23 +18086,17 @@ void MessagesManager::send_search_public_dialogs_query(const string &query, Prom
td_->create_handler<SearchPublicDialogsQuery>()->send(query);
}
-std::pair<size_t, vector<DialogId>> MessagesManager::search_dialogs(const string &query, int32 limit,
- Promise<Unit> &&promise) {
+std::pair<int32, vector<DialogId>> MessagesManager::search_dialogs(const string &query, int32 limit,
+ Promise<Unit> &&promise) {
LOG(INFO) << "Search chats with query \"" << query << "\" and limit " << limit;
+ CHECK(!td_->auth_manager_->is_bot());
if (limit < 0) {
promise.set_error(Status::Error(400, "Limit must be non-negative"));
return {};
}
if (query.empty()) {
- if (!load_recently_found_dialogs(promise)) {
- return {};
- }
-
- promise.set_value(Unit());
- size_t result_size = min(static_cast<size_t>(limit), recently_found_dialog_ids_.size());
- return {recently_found_dialog_ids_.size(),
- vector<DialogId>(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.begin() + result_size)};
+ return recently_found_dialogs_.get_dialogs(limit, std::move(promise));
}
auto result = dialogs_hints_.search(query, limit);
@@ -11315,14 +18107,26 @@ std::pair<size_t, vector<DialogId>> MessagesManager::search_dialogs(const string
}
promise.set_value(Unit());
- return {result.first, std::move(dialog_ids)};
+ return {narrow_cast<int32>(result.first), std::move(dialog_ids)};
+}
+
+std::pair<int32, vector<DialogId>> MessagesManager::get_recently_opened_dialogs(int32 limit, Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ return recently_opened_dialogs_.get_dialogs(limit, std::move(promise));
}
vector<DialogId> MessagesManager::sort_dialogs_by_order(const vector<DialogId> &dialog_ids, int32 limit) const {
- auto dialog_dates = transform(dialog_ids, [this](auto dialog_id) {
- const Dialog *d = this->get_dialog(dialog_id);
+ CHECK(!td_->auth_manager_->is_bot());
+ auto fake_order = static_cast<int64>(dialog_ids.size()) + 1;
+ auto dialog_dates = transform(dialog_ids, [this, &fake_order](DialogId dialog_id) {
+ const Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
- return DialogDate(d->order, dialog_id);
+ auto order = get_dialog_base_order(d);
+ if (is_dialog_inited(d) || order != DEFAULT_ORDER) {
+ return DialogDate(order, dialog_id);
+ }
+ // if the dialog is not inited yet, we need to assume that server knows better and the dialog needs to be returned
+ return DialogDate(fake_order--, dialog_id);
});
if (static_cast<size_t>(limit) >= dialog_dates.size()) {
std::sort(dialog_dates.begin(), dialog_dates.end());
@@ -11330,6 +18134,9 @@ vector<DialogId> MessagesManager::sort_dialogs_by_order(const vector<DialogId> &
std::partial_sort(dialog_dates.begin(), dialog_dates.begin() + limit, dialog_dates.end());
dialog_dates.resize(limit, MAX_DIALOG_DATE);
}
+ while (!dialog_dates.empty() && dialog_dates.back().get_order() == DEFAULT_ORDER) {
+ dialog_dates.pop_back();
+ }
return transform(dialog_dates, [](auto dialog_date) { return dialog_date.get_dialog_id(); });
}
@@ -11356,29 +18163,38 @@ vector<DialogId> MessagesManager::search_dialogs_on_server(const string &query,
}
send_search_public_dialogs_query(query, std::move(promise));
- return vector<DialogId>();
+ return {};
}
-vector<DialogId> MessagesManager::get_common_dialogs(UserId user_id, DialogId offset_dialog_id, int32 limit, bool force,
- Promise<Unit> &&promise) {
- if (!td_->contacts_manager_->have_input_user(user_id)) {
- promise.set_error(Status::Error(6, "Have no access to the user"));
- return vector<DialogId>();
+void MessagesManager::drop_common_dialogs_cache(UserId user_id) {
+ auto it = found_common_dialogs_.find(user_id);
+ if (it != found_common_dialogs_.end()) {
+ it->second.is_outdated = true;
+ }
+}
+
+std::pair<int32, vector<DialogId>> MessagesManager::get_common_dialogs(UserId user_id, DialogId offset_dialog_id,
+ int32 limit, bool force,
+ Promise<Unit> &&promise) {
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ promise.set_error(r_input_user.move_as_error());
+ return {};
}
- if (user_id == td_->contacts_manager_->get_my_id("get_common_dialogs")) {
- promise.set_error(Status::Error(6, "Can't get common chats with self"));
- return vector<DialogId>();
+ if (user_id == td_->contacts_manager_->get_my_id()) {
+ promise.set_error(Status::Error(400, "Can't get common chats with self"));
+ return {};
}
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
- return vector<DialogId>();
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ return {};
}
if (limit > MAX_GET_DIALOGS) {
limit = MAX_GET_DIALOGS;
}
- int32 offset_chat_id = 0;
+ int64 offset_chat_id = 0;
switch (offset_dialog_id.get_type()) {
case DialogType::Chat:
offset_chat_id = offset_dialog_id.get_chat_id().get();
@@ -11393,124 +18209,239 @@ vector<DialogId> MessagesManager::get_common_dialogs(UserId user_id, DialogId of
// fallthrough
case DialogType::User:
case DialogType::SecretChat:
- promise.set_error(Status::Error(6, "Wrong offset_chat_id"));
- return vector<DialogId>();
+ promise.set_error(Status::Error(400, "Wrong offset_chat_id"));
+ return {};
default:
UNREACHABLE();
break;
}
auto it = found_common_dialogs_.find(user_id);
- if (it != found_common_dialogs_.end() && !it->second.empty()) {
- vector<DialogId> &common_dialog_ids = it->second;
- auto offset_it = common_dialog_ids.begin();
- if (offset_dialog_id != DialogId()) {
- offset_it = std::find(common_dialog_ids.begin(), common_dialog_ids.end(), offset_dialog_id);
- if (offset_it == common_dialog_ids.end()) {
- promise.set_error(Status::Error(6, "Wrong offset_chat_id"));
- return vector<DialogId>();
+ if (it != found_common_dialogs_.end() && !it->second.dialog_ids.empty()) {
+ int32 total_count = it->second.total_count;
+ vector<DialogId> &common_dialog_ids = it->second.dialog_ids;
+ bool use_cache = (!it->second.is_outdated && it->second.receive_time >= Time::now() - 3600) || force ||
+ offset_chat_id != 0 || common_dialog_ids.size() >= static_cast<size_t>(MAX_GET_DIALOGS);
+ // use cache if it is up-to-date, or we required to use it or we can't update it
+ if (use_cache) {
+ auto offset_it = common_dialog_ids.begin();
+ if (offset_dialog_id != DialogId()) {
+ offset_it = std::find(common_dialog_ids.begin(), common_dialog_ids.end(), offset_dialog_id);
+ if (offset_it == common_dialog_ids.end()) {
+ promise.set_error(Status::Error(400, "Wrong offset_chat_id"));
+ return {};
+ }
+ ++offset_it;
}
- ++offset_it;
- }
- vector<DialogId> result;
- while (result.size() < static_cast<size_t>(limit)) {
- if (offset_it == common_dialog_ids.end()) {
- break;
+ vector<DialogId> result;
+ while (result.size() < static_cast<size_t>(limit)) {
+ if (offset_it == common_dialog_ids.end()) {
+ break;
+ }
+ auto dialog_id = *offset_it++;
+ if (dialog_id == DialogId()) { // end of the list
+ promise.set_value(Unit());
+ return {total_count, std::move(result)};
+ }
+ result.push_back(dialog_id);
}
- auto dialog_id = *offset_it++;
- if (dialog_id == DialogId()) { // end of the list
+ if (result.size() == static_cast<size_t>(limit) || force) {
promise.set_value(Unit());
- return result;
+ return {total_count, std::move(result)};
}
- result.push_back(dialog_id);
- }
- if (result.size() == static_cast<size_t>(limit) || force) {
- promise.set_value(Unit());
- return result;
}
}
- td_->create_handler<GetCommonDialogsQuery>(std::move(promise))->send(user_id, offset_chat_id, limit);
- return vector<DialogId>();
+ td_->create_handler<GetCommonDialogsQuery>(std::move(promise))
+ ->send(user_id, r_input_user.move_as_ok(), offset_chat_id, MAX_GET_DIALOGS);
+ return {};
}
-void MessagesManager::on_get_common_dialogs(UserId user_id, vector<tl_object_ptr<telegram_api::Chat>> &&chats,
- int32 total_count) {
- auto &result = found_common_dialogs_[user_id];
- if (result.size() > 0 && result.back() == DialogId()) {
+void MessagesManager::on_get_common_dialogs(UserId user_id, int64 offset_chat_id,
+ vector<tl_object_ptr<telegram_api::Chat>> &&chats, int32 total_count) {
+ CHECK(user_id.is_valid());
+ td_->contacts_manager_->on_update_user_common_chat_count(user_id, total_count);
+
+ auto &common_dialogs = found_common_dialogs_[user_id];
+ if (common_dialogs.is_outdated && offset_chat_id == 0 &&
+ common_dialogs.dialog_ids.size() < static_cast<size_t>(MAX_GET_DIALOGS)) {
+ // drop outdated cache if possible
+ common_dialogs = CommonDialogs();
+ }
+ if (common_dialogs.receive_time == 0) {
+ common_dialogs.receive_time = Time::now();
+ }
+ common_dialogs.is_outdated = false;
+ auto &result = common_dialogs.dialog_ids;
+ if (!result.empty() && result.back() == DialogId()) {
return;
}
+ bool is_last = chats.empty() && offset_chat_id == 0;
for (auto &chat : chats) {
- DialogId dialog_id;
- switch (chat->get_id()) {
- case telegram_api::chatEmpty::ID: {
- auto c = static_cast<const telegram_api::chatEmpty *>(chat.get());
- ChatId chat_id(c->id_);
- if (!chat_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << chat_id;
- continue;
- }
- dialog_id = DialogId(chat_id);
- break;
- }
- case telegram_api::chat::ID: {
- auto c = static_cast<const telegram_api::chat *>(chat.get());
- ChatId chat_id(c->id_);
- if (!chat_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << chat_id;
- continue;
- }
- dialog_id = DialogId(chat_id);
- break;
- }
- case telegram_api::chatForbidden::ID: {
- auto c = static_cast<const telegram_api::chatForbidden *>(chat.get());
- ChatId chat_id(c->id_);
- if (!chat_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << chat_id;
- continue;
- }
- dialog_id = DialogId(chat_id);
- break;
- }
- case telegram_api::channel::ID: {
- auto c = static_cast<const telegram_api::channel *>(chat.get());
- ChannelId channel_id(c->id_);
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
- continue;
- }
- dialog_id = DialogId(channel_id);
- break;
- }
- case telegram_api::channelForbidden::ID: {
- auto c = static_cast<const telegram_api::channelForbidden *>(chat.get());
- ChannelId channel_id(c->id_);
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
- continue;
- }
- dialog_id = DialogId(channel_id);
- break;
- }
- default:
- UNREACHABLE();
+ auto dialog_id = ContactsManager::get_dialog_id(chat);
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << to_string(chat);
+ continue;
}
- CHECK(dialog_id.is_valid());
- td_->contacts_manager_->on_get_chat(std::move(chat));
+ td_->contacts_manager_->on_get_chat(std::move(chat), "on_get_common_dialogs");
- if (std::find(result.begin(), result.end(), dialog_id) == result.end()) {
+ if (!td::contains(result, dialog_id)) {
force_create_dialog(dialog_id, "get common dialogs");
result.push_back(dialog_id);
}
}
- if (result.size() >= static_cast<size_t>(total_count)) {
- result.push_back(DialogId());
+ if (result.size() >= static_cast<size_t>(total_count) || is_last) {
+ if (result.size() != static_cast<size_t>(total_count)) {
+ LOG(ERROR) << "Fix total count of common groups with " << user_id << " from " << total_count << " to "
+ << result.size();
+ total_count = narrow_cast<int32>(result.size());
+ td_->contacts_manager_->on_update_user_common_chat_count(user_id, total_count);
+ }
+
+ result.emplace_back();
}
+ common_dialogs.total_count = total_count;
}
-bool MessagesManager::have_message(FullMessageId full_message_id) {
- return get_message_force(full_message_id) != nullptr;
+void MessagesManager::block_message_sender_from_replies(MessageId message_id, bool need_delete_message,
+ bool need_delete_all_messages, bool report_spam,
+ Promise<Unit> &&promise) {
+ auto dialog_id = DialogId(ContactsManager::get_replies_bot_user_id());
+ Dialog *d = get_dialog_force(dialog_id, "block_message_sender_from_replies");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Not enough rights"));
+ }
+
+ auto *m = get_message_force(d, message_id, "block_message_sender_from_replies");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ if (m->is_outgoing || m->message_id.is_scheduled() || !m->message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Wrong message specified"));
+ }
+
+ UserId sender_user_id;
+ if (m->forward_info != nullptr) {
+ sender_user_id = m->forward_info->sender_user_id;
+ }
+ vector<MessageId> message_ids;
+ if (need_delete_all_messages && sender_user_id.is_valid()) {
+ find_messages(d->messages.get(), message_ids, [sender_user_id](const Message *m) {
+ return !m->is_outgoing && m->forward_info != nullptr && m->forward_info->sender_user_id == sender_user_id;
+ });
+ CHECK(td::contains(message_ids, message_id));
+ } else if (need_delete_message) {
+ message_ids.push_back(message_id);
+ }
+
+ delete_dialog_messages(d, message_ids, false, DELETE_MESSAGE_USER_REQUEST_SOURCE);
+
+ block_message_sender_from_replies_on_server(message_id, need_delete_message, need_delete_all_messages, report_spam, 0,
+ std::move(promise));
+}
+
+class MessagesManager::BlockMessageSenderFromRepliesOnServerLogEvent {
+ public:
+ MessageId message_id_;
+ bool delete_message_;
+ bool delete_all_messages_;
+ bool report_spam_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(delete_message_);
+ STORE_FLAG(delete_all_messages_);
+ STORE_FLAG(report_spam_);
+ END_STORE_FLAGS();
+
+ td::store(message_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(delete_message_);
+ PARSE_FLAG(delete_all_messages_);
+ PARSE_FLAG(report_spam_);
+ END_PARSE_FLAGS();
+
+ td::parse(message_id_, parser);
+ }
+};
+
+uint64 MessagesManager::save_block_message_sender_from_replies_on_server_log_event(MessageId message_id,
+ bool need_delete_message,
+ bool need_delete_all_messages,
+ bool report_spam) {
+ BlockMessageSenderFromRepliesOnServerLogEvent log_event{message_id, need_delete_message, need_delete_all_messages,
+ report_spam};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::block_message_sender_from_replies_on_server(MessageId message_id, bool need_delete_message,
+ bool need_delete_all_messages, bool report_spam,
+ uint64 log_event_id, Promise<Unit> &&promise) {
+ if (log_event_id == 0) {
+ log_event_id = save_block_message_sender_from_replies_on_server_log_event(message_id, need_delete_message,
+ need_delete_all_messages, report_spam);
+ }
+
+ td_->create_handler<BlockFromRepliesQuery>(get_erase_log_event_promise(log_event_id, std::move(promise)))
+ ->send(message_id, need_delete_message, need_delete_all_messages, report_spam);
+}
+
+void MessagesManager::get_blocked_dialogs(int32 offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::messageSenders>> &&promise) {
+ if (offset < 0) {
+ return promise.set_error(Status::Error(400, "Parameter offset must be non-negative"));
+ }
+
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ }
+
+ td_->create_handler<GetBlockedDialogsQuery>(std::move(promise))->send(offset, limit);
+}
+
+void MessagesManager::on_get_blocked_dialogs(int32 offset, int32 limit, int32 total_count,
+ vector<tl_object_ptr<telegram_api::peerBlocked>> &&blocked_peers,
+ Promise<td_api::object_ptr<td_api::messageSenders>> &&promise) {
+ LOG(INFO) << "Receive " << blocked_peers.size() << " blocked chats from offset " << offset << " out of "
+ << total_count;
+ auto peers = transform(std::move(blocked_peers), [](tl_object_ptr<telegram_api::peerBlocked> &&blocked_peer) {
+ return std::move(blocked_peer->peer_id_);
+ });
+ auto dialog_ids = get_message_sender_dialog_ids(td_, std::move(peers));
+ if (!dialog_ids.empty() && offset + dialog_ids.size() > static_cast<size_t>(total_count)) {
+ LOG(ERROR) << "Fix total count of blocked chats from " << total_count << " to " << offset + dialog_ids.size();
+ total_count = offset + narrow_cast<int32>(dialog_ids.size());
+ }
+
+ auto senders = transform(dialog_ids, [td = td_](DialogId dialog_id) {
+ return get_message_sender_object(td, dialog_id, "on_get_blocked_dialogs");
+ });
+ promise.set_value(td_api::make_object<td_api::messageSenders>(total_count, std::move(senders)));
+}
+
+DialogId MessagesManager::get_dialog_message_sender(FullMessageId full_message_id) {
+ const auto *m = get_message_force(full_message_id, "get_dialog_message_sender");
+ if (m == nullptr) {
+ return DialogId();
+ }
+ return get_message_sender(m);
+}
+
+bool MessagesManager::have_message_force(FullMessageId full_message_id, const char *source) {
+ return get_message_force(full_message_id, source) != nullptr;
+}
+
+bool MessagesManager::have_message_force(Dialog *d, MessageId message_id, const char *source) {
+ return get_message_force(d, message_id, source) != nullptr;
}
MessagesManager::Message *MessagesManager::get_message(FullMessageId full_message_id) {
@@ -11531,52 +18462,48 @@ const MessagesManager::Message *MessagesManager::get_message(FullMessageId full_
return get_message(d, full_message_id.get_message_id());
}
-MessagesManager::Message *MessagesManager::get_message_force(FullMessageId full_message_id) {
- Dialog *d = get_dialog_force(full_message_id.get_dialog_id());
+MessagesManager::Message *MessagesManager::get_message_force(FullMessageId full_message_id, const char *source) {
+ Dialog *d = get_dialog_force(full_message_id.get_dialog_id(), source);
if (d == nullptr) {
return nullptr;
}
- return get_message_force(d, full_message_id.get_message_id());
+ return get_message_force(d, full_message_id.get_message_id(), source);
}
-MessageId MessagesManager::get_replied_message_id(const Message *m) {
- switch (m->content->get_id()) {
- case MessagePinMessage::ID:
- CHECK(!m->reply_to_message_id.is_valid());
- return static_cast<const MessagePinMessage *>(m->content.get())->message_id;
- case MessageGameScore::ID:
- CHECK(!m->reply_to_message_id.is_valid());
- return static_cast<const MessageGameScore *>(m->content.get())->game_message_id;
- case MessagePaymentSuccessful::ID:
- CHECK(!m->reply_to_message_id.is_valid());
- return static_cast<const MessagePaymentSuccessful *>(m->content.get())->invoice_message_id;
- default:
- return m->reply_to_message_id;
+FullMessageId MessagesManager::get_replied_message_id(DialogId dialog_id, const Message *m) {
+ auto full_message_id = get_message_content_replied_message_id(dialog_id, m->content.get());
+ if (full_message_id.get_message_id().is_valid()) {
+ CHECK(m->reply_to_message_id == MessageId());
+ return full_message_id;
+ }
+ if (m->reply_to_message_id == MessageId()) {
+ if (m->top_thread_message_id.is_valid() && is_service_message_content(m->content->get_type())) {
+ return {dialog_id, m->top_thread_message_id};
+ }
+ return {};
}
+ return {m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id, m->reply_to_message_id};
}
void MessagesManager::get_message_force_from_server(Dialog *d, MessageId message_id, Promise<Unit> &&promise,
tl_object_ptr<telegram_api::InputMessage> input_message) {
- auto m = get_message_force(d, message_id);
- if (m == nullptr && message_id.is_valid() && message_id.is_server()) {
- auto dialog_type = d->dialog_id.get_type();
- if (d->last_new_message_id != MessageId() && message_id.get() > d->last_new_message_id.get()) {
- // message will not be added to the dialog anyway
- if (dialog_type == DialogType::Channel) {
- // so we try to force channel difference first
- CHECK(input_message == nullptr); // replied message can't be older than already added original message
- postponed_get_message_requests_[d->dialog_id].emplace_back(message_id, std::move(promise));
- get_channel_difference(d->dialog_id, d->pts, true, "get_message");
- } else {
- promise.set_value(Unit());
+ LOG(INFO) << "Get " << message_id << " in " << d->dialog_id << " using " << to_string(input_message);
+ auto dialog_type = d->dialog_id.get_type();
+ auto m = get_message_force(d, message_id, "get_message_force_from_server");
+ if (m == nullptr && !is_deleted_message(d, message_id) && dialog_type != DialogType::SecretChat) {
+ if (message_id.is_valid() && message_id.is_server()) {
+ if (d->last_new_message_id != MessageId() && message_id > d->last_new_message_id &&
+ dialog_type != DialogType::Channel) {
+ // message will not be added to the dialog anyway
+ return promise.set_value(Unit());
}
- return;
- }
- if (d->deleted_message_ids.count(message_id) == 0 && dialog_type != DialogType::SecretChat) {
- return get_messages_from_server({FullMessageId(d->dialog_id, message_id)}, std::move(promise),
- std::move(input_message));
+ return get_message_from_server({d->dialog_id, message_id}, std::move(promise), "get_message_force_from_server",
+ std::move(input_message));
+ }
+ if (message_id.is_valid_scheduled() && message_id.is_scheduled_server() && input_message == nullptr) {
+ return get_message_from_server({d->dialog_id, message_id}, std::move(promise), "get_message_force_from_server");
}
}
@@ -11584,86 +18511,595 @@ void MessagesManager::get_message_force_from_server(Dialog *d, MessageId message
}
void MessagesManager::get_message(FullMessageId full_message_id, Promise<Unit> &&promise) {
- Dialog *d = get_dialog_force(full_message_id.get_dialog_id());
+ Dialog *d = get_dialog_force(full_message_id.get_dialog_id(), "get_message");
if (d == nullptr) {
- return promise.set_error(Status::Error(6, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
get_message_force_from_server(d, full_message_id.get_message_id(), std::move(promise));
}
-MessageId MessagesManager::get_replied_message(DialogId dialog_id, MessageId message_id, bool force,
- Promise<Unit> &&promise) {
+FullMessageId MessagesManager::get_replied_message(DialogId dialog_id, MessageId message_id, bool force,
+ Promise<Unit> &&promise) {
LOG(INFO) << "Get replied message to " << message_id << " in " << dialog_id;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "get_replied_message");
if (d == nullptr) {
- promise.set_error(Status::Error(6, "Chat not found"));
- return MessageId();
+ promise.set_error(Status::Error(400, "Chat not found"));
+ return FullMessageId();
}
- auto m = get_message_force(d, message_id);
+ message_id = get_persistent_message_id(d, message_id);
+ auto m = get_message_force(d, message_id, "get_replied_message");
if (m == nullptr) {
if (force) {
promise.set_value(Unit());
} else {
get_message_force_from_server(d, message_id, std::move(promise));
}
- return MessageId();
+ return FullMessageId();
}
tl_object_ptr<telegram_api::InputMessage> input_message;
- if (message_id.is_server()) {
- input_message = make_tl_object<telegram_api::inputMessageReplyTo>(message_id.get_server_message_id().get());
+ auto replied_message_id = get_replied_message_id(dialog_id, m);
+ if (replied_message_id.get_dialog_id() != dialog_id) {
+ dialog_id = replied_message_id.get_dialog_id();
+ if (!have_dialog_info_force(dialog_id)) {
+ promise.set_value(Unit());
+ return {};
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ promise.set_value(Unit());
+ return {};
+ }
+
+ force_create_dialog(dialog_id, "get_replied_message");
+ d = get_dialog_force(dialog_id, "get_replied_message");
+ if (d == nullptr) {
+ promise.set_error(Status::Error(500, "Chat with replied message not found"));
+ return {};
+ }
+ } else if (m->message_id.is_valid() && m->message_id.is_server()) {
+ input_message = make_tl_object<telegram_api::inputMessageReplyTo>(m->message_id.get_server_message_id().get());
}
- auto replied_message_id = get_replied_message_id(m);
- get_message_force_from_server(d, replied_message_id, std::move(promise), std::move(input_message));
+ get_message_force_from_server(d, replied_message_id.get_message_id(), std::move(promise), std::move(input_message));
return replied_message_id;
}
-void MessagesManager::get_dialog_pinned_message(DialogId dialog_id, Promise<MessageId> &&promise) {
- Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr) {
- return promise.set_error(Status::Error(6, "Chat not found"));
+Result<FullMessageId> MessagesManager::get_top_thread_full_message_id(DialogId dialog_id, const Message *m) const {
+ CHECK(m != nullptr);
+ if (m->message_id.is_scheduled()) {
+ return Status::Error(400, "Message is scheduled");
+ }
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return Status::Error(400, "Chat can't have message threads");
+ }
+ if (!m->reply_info.is_empty() && m->reply_info.is_comment_) {
+ if (!is_visible_message_reply_info(dialog_id, m)) {
+ return Status::Error(400, "Message has no comments");
+ }
+ if (m->message_id.is_yet_unsent()) {
+ return Status::Error(400, "Message is not sent yet");
+ }
+ return FullMessageId{DialogId(m->reply_info.channel_id_), m->linked_top_thread_message_id};
+ } else {
+ if (!m->top_thread_message_id.is_valid()) {
+ return Status::Error(400, "Message has no thread");
+ }
+ return FullMessageId{dialog_id, m->top_thread_message_id};
}
+}
+void MessagesManager::get_message_thread(DialogId dialog_id, MessageId message_id,
+ Promise<MessageThreadInfo> &&promise) {
+ LOG(INFO) << "Get message thread from " << message_id << " in " << dialog_id;
+ Dialog *d = get_dialog_force(dialog_id, "get_message_thread");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
if (dialog_id.get_type() != DialogType::Channel) {
- return promise.set_value(MessageId());
+ return promise.set_error(Status::Error(400, "Chat is not a supergroup or a channel"));
+ }
+ if (message_id.is_scheduled()) {
+ return promise.set_error(Status::Error(400, "Scheduled messages can't have message threads"));
}
- auto channel_id = dialog_id.get_channel_id();
- auto message_id = td_->contacts_manager_->get_channel_pinned_message_id(channel_id);
- if (get_message_force(d, message_id) == nullptr) {
- return td_->create_handler<GetChannelPinnedMessageQuery>(std::move(promise))->send(channel_id);
+ message_id = get_persistent_message_id(d, message_id);
+ auto m = get_message_force(d, message_id, "get_message_thread");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ TRY_RESULT_PROMISE(promise, top_thread_full_message_id, get_top_thread_full_message_id(dialog_id, m));
+ if ((m->reply_info.is_empty() || !m->reply_info.is_comment_) &&
+ top_thread_full_message_id.get_message_id() != m->message_id) {
+ CHECK(dialog_id == top_thread_full_message_id.get_dialog_id());
+ // get information about the thread from the top message
+ message_id = top_thread_full_message_id.get_message_id();
+ CHECK(message_id.is_valid());
+ }
+
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, message_id,
+ promise = std::move(promise)](Result<MessageThreadInfo> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ send_closure(actor_id, &MessagesManager::on_get_discussion_message, dialog_id, message_id, result.move_as_ok(),
+ std::move(promise));
+ });
+
+ td_->create_handler<GetDiscussionMessageQuery>(std::move(query_promise))
+ ->send(dialog_id, message_id, top_thread_full_message_id.get_dialog_id(),
+ top_thread_full_message_id.get_message_id());
+}
+
+void MessagesManager::process_discussion_message(
+ telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result, DialogId dialog_id,
+ MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id,
+ Promise<MessageThreadInfo> promise) {
+ LOG(INFO) << "Receive discussion message for " << message_id << " in " << dialog_id << ": " << to_string(result);
+ td_->contacts_manager_->on_get_users(std::move(result->users_), "process_discussion_message");
+ td_->contacts_manager_->on_get_chats(std::move(result->chats_), "process_discussion_message");
+
+ for (auto &message : result->messages_) {
+ auto message_dialog_id = get_message_dialog_id(message);
+ if (message_dialog_id != expected_dialog_id) {
+ return promise.set_error(Status::Error(500, "Expected messages in a different chat"));
+ }
+ }
+
+ for (auto &message : result->messages_) {
+ if (need_channel_difference_to_add_message(expected_dialog_id, message)) {
+ return run_after_channel_difference(
+ expected_dialog_id, PromiseCreator::lambda([actor_id = actor_id(this), result = std::move(result), dialog_id,
+ message_id, expected_dialog_id, expected_message_id,
+ promise = std::move(promise)](Unit ignored) mutable {
+ send_closure(actor_id, &MessagesManager::process_discussion_message_impl, std::move(result), dialog_id,
+ message_id, expected_dialog_id, expected_message_id, std::move(promise));
+ }));
+ }
+ }
+
+ process_discussion_message_impl(std::move(result), dialog_id, message_id, expected_dialog_id, expected_message_id,
+ std::move(promise));
+}
+
+void MessagesManager::process_discussion_message_impl(
+ telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result, DialogId dialog_id,
+ MessageId message_id, DialogId expected_dialog_id, MessageId expected_message_id,
+ Promise<MessageThreadInfo> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ MessageThreadInfo message_thread_info;
+ message_thread_info.dialog_id = expected_dialog_id;
+ message_thread_info.unread_message_count = max(0, result->unread_count_);
+ MessageId top_message_id;
+ for (auto &message : result->messages_) {
+ auto full_message_id =
+ on_get_message(std::move(message), false, true, false, false, false, "process_discussion_message_impl");
+ if (full_message_id.get_message_id().is_valid()) {
+ CHECK(full_message_id.get_dialog_id() == expected_dialog_id);
+ message_thread_info.message_ids.push_back(full_message_id.get_message_id());
+ if (full_message_id.get_message_id() == expected_message_id) {
+ top_message_id = expected_message_id;
+ }
+ }
+ }
+ if (!message_thread_info.message_ids.empty() && !top_message_id.is_valid()) {
+ top_message_id = message_thread_info.message_ids.back();
+ }
+ auto max_message_id = MessageId(ServerMessageId(result->max_id_));
+ auto last_read_inbox_message_id = MessageId(ServerMessageId(result->read_inbox_max_id_));
+ auto last_read_outbox_message_id = MessageId(ServerMessageId(result->read_outbox_max_id_));
+ if (top_message_id.is_valid()) {
+ on_update_read_message_comments(expected_dialog_id, top_message_id, max_message_id, last_read_inbox_message_id,
+ last_read_outbox_message_id);
+ }
+ if (expected_dialog_id != dialog_id) {
+ on_update_read_message_comments(dialog_id, message_id, max_message_id, last_read_inbox_message_id,
+ last_read_outbox_message_id);
+ }
+ promise.set_value(std::move(message_thread_info));
+}
+
+void MessagesManager::on_get_discussion_message(DialogId dialog_id, MessageId message_id,
+ MessageThreadInfo &&message_thread_info,
+ Promise<MessageThreadInfo> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ Dialog *d = get_dialog_force(dialog_id, "on_get_discussion_message");
+ CHECK(d != nullptr);
+
+ CHECK(message_id.is_valid());
+ auto m = get_message_force(d, message_id, "on_get_discussion_message");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ if (message_thread_info.message_ids.empty()) {
+ if (message_thread_info.dialog_id != dialog_id &&
+ !have_input_peer(message_thread_info.dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access message comments"));
+ }
+ return promise.set_error(Status::Error(400, "Message has no thread"));
+ }
+
+ DialogId expected_dialog_id;
+ if (m->reply_info.is_comment_) {
+ if (!is_active_message_reply_info(dialog_id, m->reply_info)) {
+ return promise.set_error(Status::Error(400, "Message has no comments"));
+ }
+ expected_dialog_id = DialogId(m->reply_info.channel_id_);
+ } else {
+ if (!m->top_thread_message_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Message has no thread"));
+ }
+ expected_dialog_id = dialog_id;
+ }
+
+ if (expected_dialog_id != dialog_id && m->reply_info.is_comment_ &&
+ m->linked_top_thread_message_id != message_thread_info.message_ids.back()) {
+ auto linked_d = get_dialog_force(expected_dialog_id, "on_get_discussion_message 2");
+ CHECK(linked_d != nullptr);
+ auto linked_message_id = message_thread_info.message_ids.back();
+ Message *linked_m = get_message_force(linked_d, linked_message_id, "on_get_discussion_message 3");
+ CHECK(linked_m != nullptr && linked_m->message_id.is_server());
+ if (linked_m->top_thread_message_id == linked_m->message_id &&
+ is_active_message_reply_info(expected_dialog_id, linked_m->reply_info)) {
+ if (m->linked_top_thread_message_id.is_valid()) {
+ LOG(ERROR) << "Comment message identifier for " << message_id << " in " << dialog_id << " changed from "
+ << m->linked_top_thread_message_id << " to " << linked_message_id;
+ }
+ m->linked_top_thread_message_id = linked_message_id;
+ on_dialog_updated(dialog_id, "on_get_discussion_message");
+ }
+ }
+ promise.set_value(std::move(message_thread_info));
+}
+
+td_api::object_ptr<td_api::messageThreadInfo> MessagesManager::get_message_thread_info_object(
+ const MessageThreadInfo &info) {
+ if (info.message_ids.empty()) {
+ return nullptr;
+ }
+
+ Dialog *d = get_dialog(info.dialog_id);
+ CHECK(d != nullptr);
+ td_api::object_ptr<td_api::messageReplyInfo> reply_info;
+ vector<td_api::object_ptr<td_api::message>> messages;
+ messages.reserve(info.message_ids.size());
+ bool is_forum_topic = false;
+ for (auto message_id : info.message_ids) {
+ const Message *m = get_message_force(d, message_id, "get_message_thread_info_object");
+ auto message = get_message_object(d->dialog_id, m, "get_message_thread_info_object");
+ if (message != nullptr) {
+ if (message->interaction_info_ != nullptr && message->interaction_info_->reply_info_ != nullptr) {
+ reply_info = m->reply_info.get_message_reply_info_object(td_, d->last_read_inbox_message_id);
+ CHECK(reply_info != nullptr);
+ }
+ is_forum_topic = message->is_topic_message_;
+ messages.push_back(std::move(message));
+ }
+ }
+ if (messages.size() != 1) {
+ is_forum_topic = false;
+ }
+ if (reply_info == nullptr && !is_forum_topic) {
+ return nullptr;
+ }
+
+ MessageId top_thread_message_id;
+ td_api::object_ptr<td_api::draftMessage> draft_message;
+ if (!info.message_ids.empty()) {
+ top_thread_message_id = info.message_ids.back();
+ if (can_send_message(d->dialog_id).is_ok()) {
+ const Message *m = get_message_force(d, top_thread_message_id, "get_message_thread_info_object 2");
+ if (m != nullptr && !m->reply_info.is_comment_ && is_active_message_reply_info(d->dialog_id, m->reply_info)) {
+ draft_message = get_draft_message_object(m->thread_draft_message);
+ }
+ }
+ }
+ return td_api::make_object<td_api::messageThreadInfo>(d->dialog_id.get(), top_thread_message_id.get(),
+ std::move(reply_info), info.unread_message_count,
+ std::move(messages), std::move(draft_message));
+}
+
+Status MessagesManager::can_get_message_viewers(FullMessageId full_message_id) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "get_message_viewers");
+ if (d == nullptr) {
+ return Status::Error(400, "Chat not found");
+ }
+
+ auto m = get_message_force(d, full_message_id.get_message_id(), "get_message_viewers");
+ if (m == nullptr) {
+ return Status::Error(400, "Message not found");
+ }
+
+ return can_get_message_viewers(dialog_id, m);
+}
+
+Status MessagesManager::can_get_message_viewers(DialogId dialog_id, const Message *m) const {
+ if (td_->auth_manager_->is_bot()) {
+ return Status::Error(400, "User is bot");
+ }
+ CHECK(m != nullptr);
+ if (!m->is_outgoing) {
+ return Status::Error(400, "Can't get viewers of incoming messages");
+ }
+ if (G()->unix_time() - m->date >
+ td_->option_manager_->get_option_integer("chat_read_mark_expire_period", 7 * 86400)) {
+ return Status::Error(400, "Message is too old");
+ }
+
+ int32 participant_count = 0;
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return Status::Error(400, "Can't get message viewers in private chats");
+ case DialogType::Chat:
+ if (!td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id())) {
+ return Status::Error(400, "Chat is deactivated");
+ }
+ participant_count = td_->contacts_manager_->get_chat_participant_count(dialog_id.get_chat_id());
+ break;
+ case DialogType::Channel:
+ if (is_broadcast_channel(dialog_id)) {
+ return Status::Error(400, "Can't get message viewers in channel chats");
+ }
+ participant_count = td_->contacts_manager_->get_channel_participant_count(dialog_id.get_channel_id());
+ break;
+ case DialogType::SecretChat:
+ return Status::Error(400, "Can't get message viewers in secret chats");
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ return Status::OK();
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return Status::Error(400, "Can't access the chat");
+ }
+ if (participant_count == 0) {
+ return Status::Error(400, "Chat is empty or have unknown number of members");
+ }
+ if (participant_count > td_->option_manager_->get_option_integer("chat_read_mark_size_threshold", 100)) {
+ return Status::Error(400, "Chat is too big");
+ }
+
+ if (m->message_id.is_scheduled()) {
+ return Status::Error(400, "Scheduled messages can't have viewers");
+ }
+ if (m->message_id.is_yet_unsent()) {
+ return Status::Error(400, "Yet unsent messages can't have viewers");
+ }
+ if (m->message_id.is_local()) {
+ return Status::Error(400, "Local messages can't have viewers");
+ }
+ CHECK(m->message_id.is_server());
+
+ if (m->content->get_type() == MessageContentType::Poll &&
+ get_message_content_poll_is_anonymous(td_, m->content.get())) {
+ return Status::Error(400, "Anonymous poll viewers are unavailable");
+ }
+
+ return Status::OK();
+}
+
+void MessagesManager::get_message_viewers(FullMessageId full_message_id,
+ Promise<td_api::object_ptr<td_api::users>> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_get_message_viewers(full_message_id));
+
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = full_message_id.get_dialog_id(),
+ promise = std::move(promise)](Result<vector<UserId>> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+ send_closure(actor_id, &MessagesManager::on_get_message_viewers, dialog_id, result.move_as_ok(), false,
+ std::move(promise));
+ });
+
+ td_->create_handler<GetMessageReadParticipantsQuery>(std::move(query_promise))
+ ->send(full_message_id.get_dialog_id(), full_message_id.get_message_id());
+}
+
+void MessagesManager::on_get_message_viewers(DialogId dialog_id, vector<UserId> user_ids, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::users>> &&promise) {
+ if (!is_recursive) {
+ bool need_participant_list = false;
+ for (auto user_id : user_ids) {
+ if (!user_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << user_id << " as viewer of a message in " << dialog_id;
+ continue;
+ }
+ if (!td_->contacts_manager_->have_user_force(user_id)) {
+ need_participant_list = true;
+ }
+ }
+ if (need_participant_list) {
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, user_ids = std::move(user_ids),
+ promise = std::move(promise)](Unit result) mutable {
+ send_closure(actor_id, &MessagesManager::on_get_message_viewers, dialog_id, std::move(user_ids), true,
+ std::move(promise));
+ });
+
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat:
+ return td_->contacts_manager_->reload_chat_full(dialog_id.get_chat_id(), std::move(query_promise));
+ case DialogType::Channel:
+ return td_->contacts_manager_->get_channel_participants(
+ dialog_id.get_channel_id(), td_api::make_object<td_api::supergroupMembersFilterRecent>(), string(), 0,
+ 200, 200, PromiseCreator::lambda([query_promise = std::move(query_promise)](DialogParticipants) mutable {
+ query_promise.set_value(Unit());
+ }));
+ default:
+ UNREACHABLE();
+ return;
+ }
+ }
+ }
+ promise.set_value(td_->contacts_manager_->get_users_object(-1, user_ids));
+}
+
+void MessagesManager::translate_text(const string &text, const string &from_language_code,
+ const string &to_language_code,
+ Promise<td_api::object_ptr<td_api::text>> &&promise) {
+ td_->create_handler<TranslateTextQuery>(std::move(promise))->send(text, from_language_code, to_language_code);
+}
+
+void MessagesManager::recognize_speech(FullMessageId full_message_id, Promise<Unit> &&promise) {
+ auto m = get_message_force(full_message_id, "recognize_speech");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
}
- promise.set_value(std::move(message_id));
+ auto message_id = full_message_id.get_message_id();
+ if (message_id.is_scheduled() || !message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Message must be sent already"));
+ }
+
+ recognize_message_content_speech(td_, m->content.get(), full_message_id, std::move(promise));
+}
+
+void MessagesManager::rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise<Unit> &&promise) {
+ auto m = get_message_force(full_message_id, "rate_speech_recognition");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ rate_message_content_speech_recognition(td_, m->content.get(), full_message_id, is_good, std::move(promise));
+}
+
+void MessagesManager::get_dialog_info_full(DialogId dialog_id, Promise<Unit> &&promise, const char *source) {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_user_full, dialog_id.get_user_id(), false,
+ std::move(promise), source);
+ return;
+ case DialogType::Chat:
+ send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_chat_full, dialog_id.get_chat_id(), false,
+ std::move(promise), source);
+ return;
+ case DialogType::Channel:
+ send_closure_later(td_->contacts_manager_actor_, &ContactsManager::load_channel_full, dialog_id.get_channel_id(),
+ false, std::move(promise), source);
+ return;
+ case DialogType::SecretChat:
+ return promise.set_value(Unit());
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ return promise.set_error(Status::Error(500, "Wrong chat type"));
+ }
+}
+
+void MessagesManager::reload_dialog_info_full(DialogId dialog_id, const char *source) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Reload full info about " << dialog_id << " from " << source;
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_user_full, dialog_id.get_user_id(),
+ Promise<Unit>());
+ return;
+ case DialogType::Chat:
+ send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_chat_full, dialog_id.get_chat_id(),
+ Promise<Unit>());
+ return;
+ case DialogType::Channel:
+ send_closure_later(td_->contacts_manager_actor_, &ContactsManager::reload_channel_full,
+ dialog_id.get_channel_id(), Promise<Unit>(), source);
+ return;
+ case DialogType::SecretChat:
+ return;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ return;
+ }
+}
+
+void MessagesManager::on_dialog_info_full_invalidated(DialogId dialog_id) {
+ Dialog *d = get_dialog(dialog_id);
+ if (d != nullptr && d->is_opened) {
+ reload_dialog_info_full(dialog_id, "on_dialog_info_full_invalidated");
+ }
+}
+
+MessageId MessagesManager::get_dialog_pinned_message(DialogId dialog_id, Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "get_dialog_pinned_message");
+ if (d == nullptr) {
+ promise.set_error(Status::Error(400, "Chat not found"));
+ return MessageId();
+ }
+
+ LOG(INFO) << "Get pinned message in " << dialog_id << " with "
+ << (d->is_last_pinned_message_id_inited ? "inited" : "unknown") << " pinned " << d->last_pinned_message_id;
+
+ if (!d->is_last_pinned_message_id_inited) {
+ // must call get_dialog_info_full as expected in fix_new_dialog
+ get_dialog_info_full(dialog_id, std::move(promise), "get_dialog_pinned_message 1");
+ return MessageId();
+ }
+
+ get_dialog_info_full(dialog_id, Auto(), "get_dialog_pinned_message 2");
+
+ if (d->last_pinned_message_id.is_valid()) {
+ tl_object_ptr<telegram_api::InputMessage> input_message;
+ if (dialog_id.get_type() == DialogType::Channel) {
+ input_message = make_tl_object<telegram_api::inputMessagePinned>();
+ }
+ get_message_force_from_server(d, d->last_pinned_message_id, std::move(promise), std::move(input_message));
+ } else {
+ promise.set_value(Unit());
+ }
+
+ return d->last_pinned_message_id;
+}
+
+void MessagesManager::get_callback_query_message(DialogId dialog_id, MessageId message_id, int64 callback_query_id,
+ Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "get_callback_query_message");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!message_id.is_valid() || !message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Invalid message identifier specified"));
+ }
+
+ LOG(INFO) << "Get callback query " << message_id << " in " << dialog_id << " for query " << callback_query_id;
+
+ auto input_message = make_tl_object<telegram_api::inputMessageCallbackQuery>(message_id.get_server_message_id().get(),
+ callback_query_id);
+ get_message_force_from_server(d, message_id, std::move(promise), std::move(input_message));
}
bool MessagesManager::get_messages(DialogId dialog_id, const vector<MessageId> &message_ids, Promise<Unit> &&promise) {
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "get_messages");
if (d == nullptr) {
- promise.set_error(Status::Error(6, "Chat not found"));
+ promise.set_error(Status::Error(400, "Chat not found"));
return false;
}
bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
vector<FullMessageId> missed_message_ids;
for (auto message_id : message_ids) {
- if (!message_id.is_valid()) {
- promise.set_error(Status::Error(6, "Invalid message identifier"));
+ if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
+ promise.set_error(Status::Error(400, "Invalid message identifier"));
return false;
}
- auto message = get_message_force(d, message_id);
- if (message == nullptr && message_id.is_server() && !is_secret) {
+ auto *m = get_message_force(d, message_id, "get_messages");
+ if (m == nullptr && message_id.is_any_server() && !is_secret) {
missed_message_ids.emplace_back(dialog_id, message_id);
continue;
}
}
if (!missed_message_ids.empty()) {
- get_messages_from_server(std::move(missed_message_ids), std::move(promise));
+ get_messages_from_server(std::move(missed_message_ids), std::move(promise), "get_messages");
return false;
}
@@ -11671,11 +19107,20 @@ bool MessagesManager::get_messages(DialogId dialog_id, const vector<MessageId> &
return true;
}
+void MessagesManager::get_message_from_server(FullMessageId full_message_id, Promise<Unit> &&promise,
+ const char *source,
+ tl_object_ptr<telegram_api::InputMessage> input_message) {
+ get_messages_from_server({full_message_id}, std::move(promise), source, std::move(input_message));
+}
+
void MessagesManager::get_messages_from_server(vector<FullMessageId> &&message_ids, Promise<Unit> &&promise,
+ const char *source,
tl_object_ptr<telegram_api::InputMessage> input_message) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
if (message_ids.empty()) {
- LOG(ERROR) << "Empty message_ids";
- return;
+ LOG(ERROR) << "Empty message_ids from " << source;
+ return promise.set_error(Status::Error(500, "There are no messages specified to fetch"));
}
if (input_message != nullptr) {
@@ -11683,26 +19128,32 @@ void MessagesManager::get_messages_from_server(vector<FullMessageId> &&message_i
}
vector<tl_object_ptr<telegram_api::InputMessage>> ordinary_message_ids;
- std::unordered_map<ChannelId, vector<tl_object_ptr<telegram_api::InputMessage>>, ChannelIdHash> channel_message_ids;
+ FlatHashMap<ChannelId, vector<tl_object_ptr<telegram_api::InputMessage>>, ChannelIdHash> channel_message_ids;
+ FlatHashMap<DialogId, vector<int32>, DialogIdHash> scheduled_message_ids;
for (auto &full_message_id : message_ids) {
auto dialog_id = full_message_id.get_dialog_id();
auto message_id = full_message_id.get_message_id();
if (!message_id.is_valid() || !message_id.is_server()) {
+ if (message_id.is_valid_scheduled() && message_id.is_scheduled_server() && dialog_id.is_valid()) {
+ scheduled_message_ids[dialog_id].push_back(message_id.get_scheduled_server_message_id().get());
+ }
continue;
}
+ if (input_message == nullptr) {
+ input_message = make_tl_object<telegram_api::inputMessageID>(message_id.get_server_message_id().get());
+ }
+
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
- ordinary_message_ids.push_back(input_message == nullptr ? get_input_message(message_id)
- : std::move(input_message));
+ ordinary_message_ids.push_back(std::move(input_message));
break;
case DialogType::Channel:
- channel_message_ids[dialog_id.get_channel_id()].push_back(
- input_message == nullptr ? get_input_message(message_id) : std::move(input_message));
+ channel_message_ids[dialog_id.get_channel_id()].push_back(std::move(input_message));
break;
case DialogType::SecretChat:
- LOG(ERROR) << "Can't get secret chat message from server";
+ LOG(ERROR) << "Can't get " << full_message_id << " from server from " << source;
break;
case DialogType::None:
default:
@@ -11711,32 +19162,52 @@ void MessagesManager::get_messages_from_server(vector<FullMessageId> &&message_i
}
}
- // TODO MultiPromise
- size_t query_count = !ordinary_message_ids.empty() + channel_message_ids.size();
- LOG_IF(ERROR, query_count > 1 && promise) << "Promise will be called after first query returns";
+ MultiPromiseActorSafe mpas{"GetMessagesOnServerMultiPromiseActor"};
+ mpas.add_promise(std::move(promise));
+ auto lock = mpas.get_promise();
if (!ordinary_message_ids.empty()) {
- td_->create_handler<GetMessagesQuery>(std::move(promise))->send(std::move(ordinary_message_ids));
+ td_->create_handler<GetMessagesQuery>(mpas.get_promise())->send(std::move(ordinary_message_ids));
+ }
+
+ for (auto &it : scheduled_message_ids) {
+ auto dialog_id = it.first;
+ have_dialog_force(dialog_id, "get_messages_from_server");
+ auto input_peer = get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ LOG(ERROR) << "Can't find info about " << dialog_id << " to get a message from it from " << source;
+ mpas.get_promise().set_error(Status::Error(400, "Can't access the chat"));
+ continue;
+ }
+ td_->create_handler<GetScheduledMessagesQuery>(mpas.get_promise())
+ ->send(dialog_id, std::move(input_peer), std::move(it.second));
}
for (auto &it : channel_message_ids) {
+ td_->contacts_manager_->have_channel_force(it.first);
auto input_channel = td_->contacts_manager_->get_input_channel(it.first);
if (input_channel == nullptr) {
- LOG(ERROR) << "Can't find info about " << it.first << " to get a message from it";
- promise.set_error(Status::Error(6, "Can't access the chat"));
+ LOG(ERROR) << "Can't find info about " << it.first << " to get a message from it from " << source;
+ mpas.get_promise().set_error(Status::Error(400, "Can't access the chat"));
continue;
}
- td_->create_handler<GetChannelMessagesQuery>(std::move(promise))
- ->send(it.first, std::move(input_channel), std::move(it.second));
+ const auto *d = get_dialog_force(DialogId(it.first));
+ td_->create_handler<GetChannelMessagesQuery>(mpas.get_promise())
+ ->send(it.first, std::move(input_channel), std::move(it.second),
+ d == nullptr ? MessageId() : d->last_new_message_id);
}
+ lock.set_value(Unit());
}
bool MessagesManager::is_message_edited_recently(FullMessageId full_message_id, int32 seconds) {
if (seconds < 0) {
return false;
}
+ if (!full_message_id.get_message_id().is_valid()) {
+ return false;
+ }
- auto m = get_message_force(full_message_id);
+ auto m = get_message_force(full_message_id, "is_message_edited_recently");
if (m == nullptr) {
return true;
}
@@ -11744,40 +19215,221 @@ bool MessagesManager::is_message_edited_recently(FullMessageId full_message_id,
return m->edit_date >= G()->unix_time() - seconds;
}
-std::pair<string, string> MessagesManager::get_public_message_link(FullMessageId full_message_id, bool for_group,
- Promise<Unit> &&promise) {
+Status MessagesManager::can_get_media_timestamp_link(DialogId dialog_id, const Message *m) {
+ if (m == nullptr) {
+ return Status::Error(400, "Message not found");
+ }
+
+ if (dialog_id.get_type() != DialogType::Channel) {
+ auto forward_info = m->forward_info.get();
+ if (!can_message_content_have_media_timestamp(m->content.get()) || forward_info == nullptr ||
+ forward_info->is_imported || is_forward_info_sender_hidden(forward_info) ||
+ !forward_info->message_id.is_valid() || !m->forward_info->message_id.is_server() ||
+ !forward_info->sender_dialog_id.is_valid() ||
+ forward_info->sender_dialog_id.get_type() != DialogType::Channel) {
+ return Status::Error(400, "Message links are available only for messages in supergroups and channel chats");
+ }
+ return Status::OK();
+ }
+
+ if (m->message_id.is_yet_unsent()) {
+ return Status::Error(400, "Message is not sent yet");
+ }
+ if (m->message_id.is_scheduled()) {
+ return Status::Error(400, "Message is scheduled");
+ }
+ if (!m->message_id.is_server()) {
+ return Status::Error(400, "Message is local");
+ }
+ return Status::OK();
+}
+
+bool MessagesManager::can_report_message_reactions(DialogId dialog_id, const Message *m) const {
+ CHECK(m != nullptr);
+ if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id) ||
+ !td_->contacts_manager_->is_channel_public(dialog_id.get_channel_id())) {
+ return false;
+ }
+ if (m->message_id.is_scheduled() || !m->message_id.is_server()) {
+ return false;
+ }
+ if (m->message_id.is_scheduled() || !m->message_id.is_server()) {
+ return false;
+ }
+ if (is_discussion_message(dialog_id, m)) {
+ return false;
+ }
+
+ return true;
+}
+
+Result<std::pair<string, bool>> MessagesManager::get_message_link(FullMessageId full_message_id, int32 media_timestamp,
+ bool for_group, bool in_message_thread) {
auto dialog_id = full_message_id.get_dialog_id();
- auto d = get_dialog_force(dialog_id);
+ auto d = get_dialog_force(dialog_id, "get_message_link");
if (d == nullptr) {
- promise.set_error(Status::Error(6, "Chat not found"));
+ return Status::Error(400, "Chat not found");
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return Status::Error(400, "Can't access the chat");
+ }
+
+ auto *m = get_message_force(d, full_message_id.get_message_id(), "get_message_link");
+ TRY_STATUS(can_get_media_timestamp_link(dialog_id, m));
+
+ if (media_timestamp <= 0 || !can_message_content_have_media_timestamp(m->content.get())) {
+ media_timestamp = 0;
+ }
+ if (media_timestamp != 0) {
+ for_group = false;
+ auto duration = get_message_content_media_duration(m->content.get(), td_);
+ if (duration != 0 && media_timestamp > duration) {
+ media_timestamp = 0;
+ }
+ }
+
+ auto message_id = m->message_id;
+ if (dialog_id.get_type() != DialogType::Channel) {
+ CHECK(m->forward_info != nullptr);
+ CHECK(m->forward_info->sender_dialog_id.get_type() == DialogType::Channel);
+
+ dialog_id = m->forward_info->sender_dialog_id;
+ message_id = m->forward_info->message_id;
+ for_group = false;
+ in_message_thread = false;
+ auto channel_message = get_message({dialog_id, message_id});
+ if (channel_message != nullptr && channel_message->media_album_id == 0) {
+ for_group = true; // default is true
+ }
+ } else {
+ if (m->media_album_id == 0) {
+ for_group = true; // default is true
+ }
+ }
+
+ if (!m->top_thread_message_id.is_valid() || !m->top_thread_message_id.is_server() ||
+ is_deleted_message(d, m->top_thread_message_id) || is_broadcast_channel(dialog_id)) {
+ in_message_thread = false;
+ }
+
+ if (!td_->auth_manager_->is_bot()) {
+ td_->create_handler<ExportChannelMessageLinkQuery>(Promise<Unit>())
+ ->send(dialog_id.get_channel_id(), message_id, for_group, true);
+ }
+
+ SliceBuilder sb;
+ sb << td_->option_manager_->get_option_string("t_me_url", "https://t.me/");
+
+ if (in_message_thread) {
+ // try to generate a comment link
+ auto *top_m = get_message_force(d, m->top_thread_message_id, "get_public_message_link");
+ if (is_discussion_message(dialog_id, top_m) && is_active_message_reply_info(dialog_id, top_m->reply_info)) {
+ auto linked_dialog_id = top_m->forward_info->from_dialog_id;
+ auto linked_message_id = top_m->forward_info->from_message_id;
+ auto linked_d = get_dialog(linked_dialog_id);
+ CHECK(linked_d != nullptr);
+ CHECK(linked_dialog_id.get_type() == DialogType::Channel);
+ auto *linked_m = get_message_force(linked_d, linked_message_id, "get_public_message_link");
+ auto channel_username = td_->contacts_manager_->get_channel_first_username(linked_dialog_id.get_channel_id());
+ if (linked_m != nullptr && is_active_message_reply_info(linked_dialog_id, linked_m->reply_info) &&
+ linked_message_id.is_server() && have_input_peer(linked_dialog_id, AccessRights::Read) &&
+ !channel_username.empty()) {
+ sb << channel_username << '/' << linked_message_id.get_server_message_id().get()
+ << "?comment=" << message_id.get_server_message_id().get();
+ if (!for_group) {
+ sb << "&single";
+ }
+ if (media_timestamp > 0) {
+ sb << "&t=" << media_timestamp;
+ }
+ return std::make_pair(sb.as_cslice().str(), true);
+ }
+ }
+ }
+
+ auto dialog_username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id());
+ bool is_public = !dialog_username.empty();
+ if (m->content->get_type() == MessageContentType::VideoNote && is_broadcast_channel(dialog_id) && is_public) {
+ return std::make_pair(
+ PSTRING() << "https://telesco.pe/" << dialog_username << '/' << message_id.get_server_message_id().get(), true);
+ }
+
+ if (is_public) {
+ sb << dialog_username;
+ } else {
+ sb << "c/" << dialog_id.get_channel_id().get();
+ }
+ if (in_message_thread && td_->contacts_manager_->is_forum_channel(dialog_id.get_channel_id())) {
+ if (m->top_thread_message_id != message_id) {
+ sb << '/' << m->top_thread_message_id.get_server_message_id().get();
+ }
+ in_message_thread = false;
+ }
+ sb << '/' << message_id.get_server_message_id().get();
+
+ char separator = '?';
+ if (in_message_thread) {
+ sb << separator << "thread=" << m->top_thread_message_id.get_server_message_id().get();
+ separator = '&';
+ }
+ if (!for_group) {
+ sb << separator << "single";
+ separator = '&';
+ }
+ if (media_timestamp > 0) {
+ sb << separator << "t=" << media_timestamp;
+ separator = '&';
+ }
+
+ return std::make_pair(sb.as_cslice().str(), is_public);
+}
+
+string MessagesManager::get_message_embedding_code(FullMessageId full_message_id, bool for_group,
+ Promise<Unit> &&promise) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ auto d = get_dialog_force(dialog_id, "get_message_embedding_code");
+ if (d == nullptr) {
+ promise.set_error(Status::Error(400, "Chat not found"));
return {};
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- promise.set_error(Status::Error(6, "Can't access the chat"));
+ promise.set_error(Status::Error(400, "Can't access the chat"));
return {};
}
if (dialog_id.get_type() != DialogType::Channel ||
- td_->contacts_manager_->get_channel_username(dialog_id.get_channel_id()).empty()) {
+ td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id()).empty()) {
promise.set_error(Status::Error(
- 6, "Public message links are available only for messages in public supergroups and channel chats"));
+ 400, "Message embedding code is available only for messages in public supergroups and channel chats"));
return {};
}
- auto message_id = full_message_id.get_message_id();
- auto message = get_message_force(d, message_id);
- if (message == nullptr) {
- promise.set_error(Status::Error(6, "Message not found"));
+ auto *m = get_message_force(d, full_message_id.get_message_id(), "get_message_embedding_code");
+ if (m == nullptr) {
+ promise.set_error(Status::Error(400, "Message not found"));
return {};
}
- if (!message_id.is_server()) {
- promise.set_error(Status::Error(6, "Message is local"));
+ if (m->message_id.is_yet_unsent()) {
+ promise.set_error(Status::Error(400, "Message is not sent yet"));
return {};
}
+ if (m->message_id.is_scheduled()) {
+ promise.set_error(Status::Error(400, "Message is scheduled"));
+ return {};
+ }
+ if (!m->message_id.is_server()) {
+ promise.set_error(Status::Error(400, "Message is local"));
+ return {};
+ }
+
+ if (m->media_album_id == 0) {
+ for_group = true; // default is true
+ }
- auto it = public_message_links_[for_group].find(full_message_id);
- if (it == public_message_links_[for_group].end()) {
+ auto &links = message_embedding_codes_[for_group][dialog_id].embedding_codes_;
+ auto it = links.find(m->message_id);
+ if (it == links.end()) {
td_->create_handler<ExportChannelMessageLinkQuery>(std::move(promise))
- ->send(dialog_id.get_channel_id(), message_id, for_group);
+ ->send(dialog_id.get_channel_id(), m->message_id, for_group, false);
return {};
}
@@ -11788,40 +19440,926 @@ std::pair<string, string> MessagesManager::get_public_message_link(FullMessageId
void MessagesManager::on_get_public_message_link(FullMessageId full_message_id, bool for_group, string url,
string html) {
LOG_IF(ERROR, url.empty() && html.empty()) << "Receive empty public link for " << full_message_id;
- public_message_links_[for_group][full_message_id] = {std::move(url), std::move(html)};
+ auto dialog_id = full_message_id.get_dialog_id();
+ auto message_id = full_message_id.get_message_id();
+ message_embedding_codes_[for_group][dialog_id].embedding_codes_[message_id] = std::move(html);
+}
+
+void MessagesManager::get_message_link_info(Slice url, Promise<MessageLinkInfo> &&promise) {
+ auto r_message_link_info = LinkManager::get_message_link_info(url);
+ if (r_message_link_info.is_error()) {
+ return promise.set_error(Status::Error(400, r_message_link_info.error().message()));
+ }
+
+ auto info = r_message_link_info.move_as_ok();
+ CHECK(info.username.empty() == info.channel_id.is_valid());
+
+ bool have_dialog = info.username.empty() ? td_->contacts_manager_->have_channel_force(info.channel_id)
+ : resolve_dialog_username(info.username).is_valid();
+ if (!have_dialog) {
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), info, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ return promise.set_value(std::move(info));
+ }
+ send_closure(actor_id, &MessagesManager::on_get_message_link_dialog, std::move(info), std::move(promise));
+ });
+ if (info.username.empty()) {
+ td_->contacts_manager_->reload_channel(info.channel_id, std::move(query_promise));
+ } else {
+ td_->create_handler<ResolveUsernameQuery>(std::move(query_promise))->send(info.username);
+ }
+ return;
+ }
+
+ return on_get_message_link_dialog(std::move(info), std::move(promise));
+}
+
+void MessagesManager::on_get_message_link_dialog(MessageLinkInfo &&info, Promise<MessageLinkInfo> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ DialogId dialog_id;
+ if (info.username.empty()) {
+ if (!td_->contacts_manager_->have_channel(info.channel_id)) {
+ return promise.set_error(Status::Error(500, "Chat info not found"));
+ }
+
+ dialog_id = DialogId(info.channel_id);
+ force_create_dialog(dialog_id, "on_get_message_link_dialog");
+ } else {
+ dialog_id = resolve_dialog_username(info.username);
+ if (dialog_id.is_valid()) {
+ force_create_dialog(dialog_id, "on_get_message_link_dialog", true);
+ }
+ }
+ Dialog *d = get_dialog_force(dialog_id, "on_get_message_link_dialog");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(500, "Chat not found"));
+ }
+
+ auto message_id = info.message_id;
+ get_message_force_from_server(d, message_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), info = std::move(info), dialog_id,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ return promise.set_value(std::move(info));
+ }
+ send_closure(actor_id, &MessagesManager::on_get_message_link_message, std::move(info),
+ dialog_id, std::move(promise));
+ }));
+}
+
+void MessagesManager::on_get_message_link_message(MessageLinkInfo &&info, DialogId dialog_id,
+ Promise<MessageLinkInfo> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto message_id = info.message_id;
+ Message *m = get_message_force({dialog_id, message_id}, "on_get_message_link_message");
+ if (info.comment_message_id == MessageId() || m == nullptr || !is_broadcast_channel(dialog_id) ||
+ !m->reply_info.is_comment_ || !is_active_message_reply_info(dialog_id, m->reply_info)) {
+ return promise.set_value(std::move(info));
+ }
+
+ if (td_->contacts_manager_->have_channel_force(m->reply_info.channel_id_)) {
+ force_create_dialog(DialogId(m->reply_info.channel_id_), "on_get_message_link_message");
+ on_get_message_link_discussion_message(std::move(info), DialogId(m->reply_info.channel_id_), std::move(promise));
+ return;
+ }
+
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), info = std::move(info),
+ promise = std::move(promise)](Result<MessageThreadInfo> result) mutable {
+ if (result.is_error() || result.ok().message_ids.empty()) {
+ return promise.set_value(std::move(info));
+ }
+ send_closure(actor_id, &MessagesManager::on_get_message_link_discussion_message, std::move(info),
+ result.ok().dialog_id, std::move(promise));
+ });
+
+ td_->create_handler<GetDiscussionMessageQuery>(std::move(query_promise))
+ ->send(dialog_id, message_id, DialogId(m->reply_info.channel_id_), MessageId());
+}
+
+void MessagesManager::on_get_message_link_discussion_message(MessageLinkInfo &&info, DialogId comment_dialog_id,
+ Promise<MessageLinkInfo> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ CHECK(comment_dialog_id.is_valid());
+ info.comment_dialog_id = comment_dialog_id;
+
+ Dialog *d = get_dialog_force(comment_dialog_id, "on_get_message_link_discussion_message");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(500, "Chat not found"));
+ }
+
+ auto comment_message_id = info.comment_message_id;
+ get_message_force_from_server(
+ d, comment_message_id,
+ PromiseCreator::lambda([info = std::move(info), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ return promise.set_value(std::move(info));
+ }));
+}
+
+td_api::object_ptr<td_api::messageLinkInfo> MessagesManager::get_message_link_info_object(
+ const MessageLinkInfo &info) const {
+ CHECK(info.username.empty() == info.channel_id.is_valid());
+
+ bool is_public = !info.username.empty();
+ DialogId dialog_id = info.comment_dialog_id.is_valid()
+ ? info.comment_dialog_id
+ : (is_public ? resolve_dialog_username(info.username) : DialogId(info.channel_id));
+ MessageId top_thread_message_id;
+ MessageId message_id = info.comment_dialog_id.is_valid() ? info.comment_message_id : info.message_id;
+ td_api::object_ptr<td_api::message> message;
+ int32 media_timestamp = 0;
+ bool for_album = false;
+
+ const Dialog *d = get_dialog(dialog_id);
+ if (d == nullptr) {
+ dialog_id = DialogId();
+ top_thread_message_id = MessageId();
+ } else {
+ const Message *m = get_message(d, message_id);
+ if (m != nullptr) {
+ message = get_message_object(dialog_id, m, "get_message_link_info_object");
+ for_album = !info.is_single && m->media_album_id != 0;
+ if (info.comment_dialog_id.is_valid() || info.for_comment || m->is_topic_message) {
+ top_thread_message_id = m->top_thread_message_id;
+ } else {
+ top_thread_message_id = MessageId();
+ }
+ if (can_message_content_have_media_timestamp(m->content.get())) {
+ auto duration = get_message_content_media_duration(m->content.get(), td_);
+ if (duration == 0 || info.media_timestamp <= duration) {
+ media_timestamp = info.media_timestamp;
+ }
+ }
+ if (m->content->get_type() == MessageContentType::TopicCreate && m->top_thread_message_id.is_valid()) {
+ message = nullptr;
+ CHECK(!for_album);
+ CHECK(media_timestamp == 0);
+ }
+ } else if (!info.comment_dialog_id.is_valid() && dialog_id.get_type() == DialogType::Channel &&
+ !is_broadcast_channel(dialog_id)) {
+ top_thread_message_id = info.top_thread_message_id;
+ }
+ }
+
+ return td_api::make_object<td_api::messageLinkInfo>(is_public, dialog_id.get(), top_thread_message_id.get(),
+ std::move(message), media_timestamp, for_album);
+}
+
+InputDialogId MessagesManager::get_input_dialog_id(DialogId dialog_id) const {
+ auto input_peer = get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr || input_peer->get_id() == telegram_api::inputPeerSelf::ID) {
+ return InputDialogId(dialog_id);
+ } else {
+ return InputDialogId(input_peer);
+ }
+}
+
+void MessagesManager::sort_dialog_filter_input_dialog_ids(DialogFilter *dialog_filter, const char *source) const {
+ auto sort_input_dialog_ids = [contacts_manager =
+ td_->contacts_manager_.get()](vector<InputDialogId> &input_dialog_ids) {
+ std::sort(input_dialog_ids.begin(), input_dialog_ids.end(),
+ [contacts_manager](InputDialogId lhs, InputDialogId rhs) {
+ auto get_order = [contacts_manager](InputDialogId input_dialog_id) {
+ auto dialog_id = input_dialog_id.get_dialog_id();
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ return dialog_id.get() * 10;
+ }
+ auto user_id = contacts_manager->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ return DialogId(user_id).get() * 10 + 1;
+ };
+ return get_order(lhs) < get_order(rhs);
+ });
+ };
+
+ if (!dialog_filter->include_contacts && !dialog_filter->include_non_contacts && !dialog_filter->include_bots &&
+ !dialog_filter->include_groups && !dialog_filter->include_channels) {
+ dialog_filter->excluded_dialog_ids.clear();
+ }
+
+ sort_input_dialog_ids(dialog_filter->excluded_dialog_ids);
+ sort_input_dialog_ids(dialog_filter->included_dialog_ids);
+
+ FlatHashSet<DialogId, DialogIdHash> all_dialog_ids;
+ for (auto input_dialog_ids :
+ {&dialog_filter->pinned_dialog_ids, &dialog_filter->excluded_dialog_ids, &dialog_filter->included_dialog_ids}) {
+ for (const auto &input_dialog_id : *input_dialog_ids) {
+ auto dialog_id = input_dialog_id.get_dialog_id();
+ CHECK(dialog_id.is_valid());
+ LOG_CHECK(all_dialog_ids.insert(dialog_id).second) << source << ' ' << dialog_id << ' ' << dialog_filter;
+ }
+ }
+}
+
+Result<unique_ptr<DialogFilter>> MessagesManager::create_dialog_filter(DialogFilterId dialog_filter_id,
+ td_api::object_ptr<td_api::chatFilter> filter) {
+ CHECK(filter != nullptr);
+ for (auto chat_ids : {&filter->pinned_chat_ids_, &filter->excluded_chat_ids_, &filter->included_chat_ids_}) {
+ for (const auto &chat_id : *chat_ids) {
+ DialogId dialog_id(chat_id);
+ if (!dialog_id.is_valid()) {
+ return Status::Error(400, "Invalid chat identifier specified");
+ }
+ const Dialog *d = get_dialog_force(dialog_id, "create_dialog_filter");
+ if (d == nullptr) {
+ return Status::Error(400, "Chat not found");
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return Status::Error(400, "Can't access the chat");
+ }
+ if (d->order == DEFAULT_ORDER) {
+ return Status::Error(400, "Chat is not in the chat list");
+ }
+ }
+ }
+
+ auto dialog_filter = make_unique<DialogFilter>();
+ dialog_filter->dialog_filter_id = dialog_filter_id;
+
+ FlatHashSet<int64> added_dialog_ids;
+ auto add_chats = [this, &added_dialog_ids](vector<InputDialogId> &input_dialog_ids, const vector<int64> &chat_ids) {
+ for (const auto &chat_id : chat_ids) {
+ if (!added_dialog_ids.insert(chat_id).second) {
+ // do not allow duplicate chat_ids
+ continue;
+ }
+
+ input_dialog_ids.push_back(get_input_dialog_id(DialogId(chat_id)));
+ }
+ };
+ add_chats(dialog_filter->pinned_dialog_ids, filter->pinned_chat_ids_);
+ add_chats(dialog_filter->included_dialog_ids, filter->included_chat_ids_);
+ add_chats(dialog_filter->excluded_dialog_ids, filter->excluded_chat_ids_);
+
+ dialog_filter->title = clean_name(std::move(filter->title_), MAX_DIALOG_FILTER_TITLE_LENGTH);
+ if (dialog_filter->title.empty()) {
+ return Status::Error(400, "Title must be non-empty");
+ }
+ dialog_filter->emoji = DialogFilter::get_emoji_by_icon_name(filter->icon_name_);
+ if (dialog_filter->emoji.empty() && !filter->icon_name_.empty()) {
+ return Status::Error(400, "Invalid icon name specified");
+ }
+ dialog_filter->exclude_muted = filter->exclude_muted_;
+ dialog_filter->exclude_read = filter->exclude_read_;
+ dialog_filter->exclude_archived = filter->exclude_archived_;
+ dialog_filter->include_contacts = filter->include_contacts_;
+ dialog_filter->include_non_contacts = filter->include_non_contacts_;
+ dialog_filter->include_bots = filter->include_bots_;
+ dialog_filter->include_groups = filter->include_groups_;
+ dialog_filter->include_channels = filter->include_channels_;
+
+ TRY_STATUS(dialog_filter->check_limits());
+ sort_dialog_filter_input_dialog_ids(dialog_filter.get(), "create_dialog_filter");
+
+ return std::move(dialog_filter);
+}
+
+void MessagesManager::create_dialog_filter(td_api::object_ptr<td_api::chatFilter> filter,
+ Promise<td_api::object_ptr<td_api::chatFilterInfo>> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto max_dialog_filters = clamp(td_->option_manager_->get_option_integer("chat_filter_count_max"),
+ static_cast<int64>(0), static_cast<int64>(100));
+ if (dialog_filters_.size() >= narrow_cast<size_t>(max_dialog_filters)) {
+ return promise.set_error(Status::Error(400, "The maximum number of chat folders exceeded"));
+ }
+ if (!is_update_chat_filters_sent_) {
+ return promise.set_error(Status::Error(400, "Chat folders are not synchronized yet"));
+ }
+
+ DialogFilterId dialog_filter_id;
+ do {
+ auto min_id = static_cast<int>(DialogFilterId::min().get());
+ auto max_id = static_cast<int>(DialogFilterId::max().get());
+ dialog_filter_id = DialogFilterId(static_cast<int32>(Random::fast(min_id, max_id)));
+ } while (get_dialog_filter(dialog_filter_id) != nullptr || get_server_dialog_filter(dialog_filter_id) != nullptr);
+
+ auto r_dialog_filter = create_dialog_filter(dialog_filter_id, std::move(filter));
+ if (r_dialog_filter.is_error()) {
+ return promise.set_error(r_dialog_filter.move_as_error());
+ }
+ auto dialog_filter = r_dialog_filter.move_as_ok();
+ CHECK(dialog_filter != nullptr);
+ auto chat_filter_info = dialog_filter->get_chat_filter_info_object();
+
+ bool at_beginning = false;
+ for (const auto &recommended_dialog_filter : recommended_dialog_filters_) {
+ if (DialogFilter::are_similar(*recommended_dialog_filter.dialog_filter, *dialog_filter)) {
+ at_beginning = true;
+ }
+ }
+
+ add_dialog_filter(std::move(dialog_filter), at_beginning, "create_dialog_filter");
+ if (at_beginning && main_dialog_list_position_ != 0) {
+ main_dialog_list_position_++;
+ }
+ save_dialog_filters();
+ send_update_chat_filters();
+
+ synchronize_dialog_filters();
+ promise.set_value(std::move(chat_filter_info));
+}
+
+void MessagesManager::edit_dialog_filter(DialogFilterId dialog_filter_id, td_api::object_ptr<td_api::chatFilter> filter,
+ Promise<td_api::object_ptr<td_api::chatFilterInfo>> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
+ if (old_dialog_filter == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat filter not found"));
+ }
+ CHECK(is_update_chat_filters_sent_);
+
+ auto r_dialog_filter = create_dialog_filter(dialog_filter_id, std::move(filter));
+ if (r_dialog_filter.is_error()) {
+ return promise.set_error(r_dialog_filter.move_as_error());
+ }
+ auto new_dialog_filter = r_dialog_filter.move_as_ok();
+ CHECK(new_dialog_filter != nullptr);
+ auto chat_filter_info = new_dialog_filter->get_chat_filter_info_object();
+
+ if (*new_dialog_filter == *old_dialog_filter) {
+ return promise.set_value(std::move(chat_filter_info));
+ }
+
+ edit_dialog_filter(std::move(new_dialog_filter), "edit_dialog_filter");
+ save_dialog_filters();
+ send_update_chat_filters();
+
+ synchronize_dialog_filters();
+ promise.set_value(std::move(chat_filter_info));
+}
+
+void MessagesManager::update_dialog_filter_on_server(unique_ptr<DialogFilter> &&dialog_filter) {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(dialog_filter != nullptr);
+ are_dialog_filters_being_synchronized_ = true;
+ dialog_filter->remove_secret_chat_dialog_ids();
+ auto dialog_filter_id = dialog_filter->dialog_filter_id;
+ auto input_dialog_filter = dialog_filter->get_input_dialog_filter();
+
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_filter = std::move(dialog_filter)](Result<Unit> result) mutable {
+ send_closure(actor_id, &MessagesManager::on_update_dialog_filter, std::move(dialog_filter),
+ result.is_error() ? result.move_as_error() : Status::OK());
+ });
+ td_->create_handler<UpdateDialogFilterQuery>(std::move(promise))
+ ->send(dialog_filter_id, std::move(input_dialog_filter));
+}
+
+void MessagesManager::on_update_dialog_filter(unique_ptr<DialogFilter> dialog_filter, Status result) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (result.is_error()) {
+ // TODO rollback dialog_filters_ changes if error isn't 429
+ } else {
+ bool is_edited = false;
+ for (auto &filter : server_dialog_filters_) {
+ if (filter->dialog_filter_id == dialog_filter->dialog_filter_id) {
+ if (*filter != *dialog_filter) {
+ filter = std::move(dialog_filter);
+ }
+ is_edited = true;
+ break;
+ }
+ }
+
+ if (!is_edited) {
+ bool at_beginning = false;
+ for (const auto &recommended_dialog_filter : recommended_dialog_filters_) {
+ if (DialogFilter::are_similar(*recommended_dialog_filter.dialog_filter, *dialog_filter)) {
+ at_beginning = true;
+ }
+ }
+ if (at_beginning) {
+ server_dialog_filters_.insert(server_dialog_filters_.begin(), std::move(dialog_filter));
+ } else {
+ server_dialog_filters_.push_back(std::move(dialog_filter));
+ }
+ if (at_beginning && server_main_dialog_list_position_ != 0) {
+ server_main_dialog_list_position_++;
+ }
+ }
+ save_dialog_filters();
+ }
+
+ are_dialog_filters_being_synchronized_ = false;
+ synchronize_dialog_filters();
+}
+
+void MessagesManager::delete_dialog_filter(DialogFilterId dialog_filter_id, Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto dialog_filter = get_dialog_filter(dialog_filter_id);
+ if (dialog_filter == nullptr) {
+ return promise.set_value(Unit());
+ }
+
+ int32 position = delete_dialog_filter(dialog_filter_id, "delete_dialog_filter");
+ if (main_dialog_list_position_ > position) {
+ main_dialog_list_position_--;
+ }
+ save_dialog_filters();
+ send_update_chat_filters();
+
+ synchronize_dialog_filters();
+ promise.set_value(Unit());
+}
+
+void MessagesManager::delete_dialog_filter_on_server(DialogFilterId dialog_filter_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ are_dialog_filters_being_synchronized_ = true;
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_filter_id](Result<Unit> result) {
+ send_closure(actor_id, &MessagesManager::on_delete_dialog_filter, dialog_filter_id,
+ result.is_error() ? result.move_as_error() : Status::OK());
+ });
+ td_->create_handler<UpdateDialogFilterQuery>(std::move(promise))->send(dialog_filter_id, nullptr);
+}
+
+void MessagesManager::on_delete_dialog_filter(DialogFilterId dialog_filter_id, Status result) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (result.is_error()) {
+ // TODO rollback dialog_filters_ changes if error isn't 429
+ } else {
+ for (auto it = server_dialog_filters_.begin(); it != server_dialog_filters_.end(); ++it) {
+ if ((*it)->dialog_filter_id == dialog_filter_id) {
+ server_dialog_filters_.erase(it);
+ save_dialog_filters();
+ break;
+ }
+ }
+ }
+
+ are_dialog_filters_being_synchronized_ = false;
+ synchronize_dialog_filters();
+}
+
+void MessagesManager::reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids, int32 main_dialog_list_position,
+ Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+
+ for (auto dialog_filter_id : dialog_filter_ids) {
+ auto dialog_filter = get_dialog_filter(dialog_filter_id);
+ if (dialog_filter == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat filter not found"));
+ }
+ }
+ std::unordered_set<DialogFilterId, DialogFilterIdHash> new_dialog_filter_ids_set(dialog_filter_ids.begin(),
+ dialog_filter_ids.end());
+ if (new_dialog_filter_ids_set.size() != dialog_filter_ids.size()) {
+ return promise.set_error(Status::Error(400, "Duplicate chat filters in the new list"));
+ }
+ if (main_dialog_list_position < 0 || main_dialog_list_position > static_cast<int32>(dialog_filters_.size())) {
+ return promise.set_error(Status::Error(400, "Invalid main chat list position specified"));
+ }
+ if (!td_->option_manager_->get_option_boolean("is_premium")) {
+ main_dialog_list_position = 0;
+ }
+
+ if (set_dialog_filters_order(dialog_filters_, dialog_filter_ids) ||
+ main_dialog_list_position != main_dialog_list_position_) {
+ main_dialog_list_position_ = main_dialog_list_position;
+
+ save_dialog_filters();
+ send_update_chat_filters();
+
+ synchronize_dialog_filters();
+ }
+ promise.set_value(Unit());
+}
+
+void MessagesManager::reorder_dialog_filters_on_server(vector<DialogFilterId> dialog_filter_ids,
+ int32 main_dialog_list_position) {
+ CHECK(!td_->auth_manager_->is_bot());
+ are_dialog_filters_being_synchronized_ = true;
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_filter_ids, main_dialog_list_position](Result<Unit> result) mutable {
+ send_closure(actor_id, &MessagesManager::on_reorder_dialog_filters, std::move(dialog_filter_ids),
+ main_dialog_list_position, result.is_error() ? result.move_as_error() : Status::OK());
+ });
+ td_->create_handler<UpdateDialogFiltersOrderQuery>(std::move(promise))
+ ->send(dialog_filter_ids, main_dialog_list_position);
+}
+
+void MessagesManager::on_reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids,
+ int32 main_dialog_list_position, Status result) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (result.is_error()) {
+ // TODO rollback dialog_filters_ changes if error isn't 429
+ } else {
+ if (set_dialog_filters_order(server_dialog_filters_, std::move(dialog_filter_ids)) ||
+ server_main_dialog_list_position_ != main_dialog_list_position) {
+ server_main_dialog_list_position_ = main_dialog_list_position;
+ save_dialog_filters();
+ }
+ }
+
+ are_dialog_filters_being_synchronized_ = false;
+ synchronize_dialog_filters();
+}
+
+bool MessagesManager::set_dialog_filters_order(vector<unique_ptr<DialogFilter>> &dialog_filters,
+ vector<DialogFilterId> dialog_filter_ids) {
+ auto old_dialog_filter_ids = get_dialog_filter_ids(dialog_filters, -1);
+ if (old_dialog_filter_ids == dialog_filter_ids) {
+ return false;
+ }
+ LOG(INFO) << "Reorder chat filters from " << old_dialog_filter_ids << " to " << dialog_filter_ids;
+
+ if (dialog_filter_ids.size() != old_dialog_filter_ids.size()) {
+ for (auto dialog_filter_id : old_dialog_filter_ids) {
+ if (!td::contains(dialog_filter_ids, dialog_filter_id)) {
+ dialog_filter_ids.push_back(dialog_filter_id);
+ }
+ }
+ CHECK(dialog_filter_ids.size() == old_dialog_filter_ids.size());
+ }
+ if (old_dialog_filter_ids == dialog_filter_ids) {
+ return false;
+ }
+
+ CHECK(dialog_filter_ids.size() == dialog_filters.size());
+ for (size_t i = 0; i < dialog_filters.size(); i++) {
+ for (size_t j = i; j < dialog_filters.size(); j++) {
+ if (dialog_filters[j]->dialog_filter_id == dialog_filter_ids[i]) {
+ if (i != j) {
+ std::swap(dialog_filters[i], dialog_filters[j]);
+ }
+ break;
+ }
+ }
+ CHECK(dialog_filters[i]->dialog_filter_id == dialog_filter_ids[i]);
+ }
+ return true;
+}
+
+void MessagesManager::add_dialog_filter(unique_ptr<DialogFilter> dialog_filter, bool at_beginning, const char *source) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ CHECK(dialog_filter != nullptr);
+ auto dialog_filter_id = dialog_filter->dialog_filter_id;
+ LOG(INFO) << "Add " << dialog_filter_id << " from " << source;
+ CHECK(get_dialog_filter(dialog_filter_id) == nullptr);
+ if (at_beginning) {
+ dialog_filters_.insert(dialog_filters_.begin(), std::move(dialog_filter));
+ } else {
+ dialog_filters_.push_back(std::move(dialog_filter));
+ }
+
+ auto dialog_list_id = DialogListId(dialog_filter_id);
+ CHECK(dialog_lists_.count(dialog_list_id) == 0);
+
+ auto &list = add_dialog_list(dialog_list_id);
+ auto folder_ids = get_dialog_list_folder_ids(list);
+ CHECK(!folder_ids.empty());
+
+ for (auto folder_id : folder_ids) {
+ auto *folder = get_dialog_folder(folder_id);
+ CHECK(folder != nullptr);
+ for (const auto &dialog_date : folder->ordered_dialogs_) {
+ if (dialog_date.get_order() == DEFAULT_ORDER) {
+ break;
+ }
+
+ auto dialog_id = dialog_date.get_dialog_id();
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ if (need_dialog_in_list(d, list)) {
+ list.in_memory_dialog_total_count_++;
+
+ add_dialog_to_list(d, dialog_list_id);
+ }
+ }
+ }
+
+ for (const auto &input_dialog_id : reversed(dialog_filters_.back()->pinned_dialog_ids)) {
+ auto dialog_id = input_dialog_id.get_dialog_id();
+ if (!dialog_id.is_valid()) {
+ continue;
+ }
+ auto order = get_next_pinned_dialog_order();
+ list.pinned_dialogs_.emplace_back(order, dialog_id);
+ list.pinned_dialog_id_orders_.emplace(dialog_id, order);
+ }
+ std::reverse(list.pinned_dialogs_.begin(), list.pinned_dialogs_.end());
+ list.are_pinned_dialogs_inited_ = true;
+
+ update_list_last_pinned_dialog_date(list);
+ update_list_last_dialog_date(list);
+}
+
+void MessagesManager::edit_dialog_filter(unique_ptr<DialogFilter> new_dialog_filter, const char *source) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ CHECK(new_dialog_filter != nullptr);
+ LOG(INFO) << "Edit " << new_dialog_filter->dialog_filter_id << " from " << source;
+ for (auto &old_dialog_filter : dialog_filters_) {
+ if (old_dialog_filter->dialog_filter_id == new_dialog_filter->dialog_filter_id) {
+ CHECK(*old_dialog_filter != *new_dialog_filter);
+
+ auto dialog_list_id = DialogListId(old_dialog_filter->dialog_filter_id);
+ auto *old_list_ptr = get_dialog_list(dialog_list_id);
+ CHECK(old_list_ptr != nullptr);
+ auto &old_list = *old_list_ptr;
+
+ disable_get_dialog_filter_ = true; // to ensure crash if get_dialog_filter is called
+
+ auto folder_ids = get_dialog_filter_folder_ids(old_dialog_filter.get());
+ CHECK(!folder_ids.empty());
+ for (auto folder_id : get_dialog_filter_folder_ids(new_dialog_filter.get())) {
+ if (!td::contains(folder_ids, folder_id)) {
+ folder_ids.push_back(folder_id);
+ }
+ }
+
+ DialogList new_list;
+ new_list.dialog_list_id = dialog_list_id;
+
+ auto old_it = old_list.pinned_dialogs_.rbegin();
+ for (const auto &input_dialog_id : reversed(new_dialog_filter->pinned_dialog_ids)) {
+ auto dialog_id = input_dialog_id.get_dialog_id();
+ if (!dialog_id.is_valid()) {
+ continue;
+ }
+ while (old_it < old_list.pinned_dialogs_.rend()) {
+ if (old_it->get_dialog_id() == dialog_id) {
+ break;
+ }
+ ++old_it;
+ }
+
+ int64 order;
+ if (old_it < old_list.pinned_dialogs_.rend()) {
+ order = old_it->get_order();
+ ++old_it;
+ } else {
+ order = get_next_pinned_dialog_order();
+ }
+ new_list.pinned_dialogs_.emplace_back(order, dialog_id);
+ new_list.pinned_dialog_id_orders_.emplace(dialog_id, order);
+ }
+ std::reverse(new_list.pinned_dialogs_.begin(), new_list.pinned_dialogs_.end());
+ new_list.are_pinned_dialogs_inited_ = true;
+
+ do_update_list_last_pinned_dialog_date(new_list);
+ do_update_list_last_dialog_date(new_list, get_dialog_filter_folder_ids(new_dialog_filter.get()));
+
+ new_list.server_dialog_total_count_ = 0;
+ new_list.secret_chat_total_count_ = 0;
+
+ std::map<DialogDate, const Dialog *> updated_position_dialogs;
+ for (auto folder_id : folder_ids) {
+ auto *folder = get_dialog_folder(folder_id);
+ CHECK(folder != nullptr);
+ for (const auto &dialog_date : folder->ordered_dialogs_) {
+ if (dialog_date.get_order() == DEFAULT_ORDER) {
+ break;
+ }
+
+ auto dialog_id = dialog_date.get_dialog_id();
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ const DialogPositionInList old_position = get_dialog_position_in_list(old_list_ptr, d);
+ // can't use get_dialog_position_in_list, because need_dialog_in_list calls get_dialog_filter
+ DialogPositionInList new_position;
+ if (need_dialog_in_filter(d, new_dialog_filter.get())) {
+ new_position.private_order = get_dialog_private_order(&new_list, d);
+ if (new_position.private_order != 0) {
+ new_position.public_order =
+ DialogDate(new_position.private_order, dialog_id) <= new_list.list_last_dialog_date_
+ ? new_position.private_order
+ : 0;
+ new_position.is_pinned = get_dialog_pinned_order(&new_list, dialog_id) != DEFAULT_ORDER;
+ new_position.is_sponsored = is_dialog_sponsored(d);
+ }
+ }
+
+ if (need_send_update_chat_position(old_position, new_position)) {
+ updated_position_dialogs.emplace(DialogDate(new_position.public_order, dialog_id), d);
+ }
+
+ bool was_in_list = old_position.private_order != 0;
+ bool is_in_list = new_position.private_order != 0;
+ if (is_in_list) {
+ if (!was_in_list) {
+ add_dialog_to_list(d, dialog_list_id);
+ }
+
+ new_list.in_memory_dialog_total_count_++;
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ new_list.secret_chat_total_count_++;
+ } else {
+ new_list.server_dialog_total_count_++;
+ }
+
+ auto unread_count = d->server_unread_count + d->local_unread_count;
+ if (unread_count != 0) {
+ new_list.unread_message_total_count_ += unread_count;
+ if (is_dialog_muted(d)) {
+ new_list.unread_message_muted_count_ += unread_count;
+ }
+ }
+ if (unread_count != 0 || d->is_marked_as_unread) {
+ new_list.unread_dialog_total_count_++;
+ if (unread_count == 0 && d->is_marked_as_unread) {
+ new_list.unread_dialog_marked_count_++;
+ }
+ if (is_dialog_muted(d)) {
+ new_list.unread_dialog_muted_count_++;
+ if (unread_count == 0 && d->is_marked_as_unread) {
+ new_list.unread_dialog_muted_marked_count_++;
+ }
+ }
+ }
+ } else {
+ if (was_in_list) {
+ remove_dialog_from_list(d, dialog_list_id);
+ }
+ }
+ }
+ }
+
+ if (new_list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
+ new_list.is_message_unread_count_inited_ = true;
+ new_list.is_dialog_unread_count_inited_ = true;
+ new_list.need_unread_count_recalc_ = false;
+ } else {
+ if (old_list.is_message_unread_count_inited_) { // can't stop sending updates
+ new_list.is_message_unread_count_inited_ = true;
+ }
+ if (old_list.is_dialog_unread_count_inited_) { // can't stop sending updates
+ new_list.is_dialog_unread_count_inited_ = true;
+ }
+ new_list.server_dialog_total_count_ = -1;
+ new_list.secret_chat_total_count_ = -1;
+ }
+
+ bool need_update_unread_message_count =
+ new_list.is_message_unread_count_inited_ &&
+ (old_list.unread_message_total_count_ != new_list.unread_message_total_count_ ||
+ old_list.unread_message_muted_count_ != new_list.unread_message_muted_count_ ||
+ !old_list.is_message_unread_count_inited_);
+ bool need_update_unread_chat_count =
+ new_list.is_dialog_unread_count_inited_ &&
+ (old_list.unread_dialog_total_count_ != new_list.unread_dialog_total_count_ ||
+ old_list.unread_dialog_muted_count_ != new_list.unread_dialog_muted_count_ ||
+ old_list.unread_dialog_marked_count_ != new_list.unread_dialog_marked_count_ ||
+ old_list.unread_dialog_muted_marked_count_ != new_list.unread_dialog_muted_marked_count_ ||
+ get_dialog_total_count(old_list) != get_dialog_total_count(new_list) ||
+ !old_list.is_dialog_unread_count_inited_);
+ bool need_save_unread_chat_count = new_list.is_dialog_unread_count_inited_ &&
+ (old_list.server_dialog_total_count_ != new_list.server_dialog_total_count_ ||
+ old_list.secret_chat_total_count_ != new_list.secret_chat_total_count_);
+
+ auto load_list_promises = std::move(old_list.load_list_queries_);
+
+ disable_get_dialog_filter_ = false;
+
+ old_list = std::move(new_list);
+ old_dialog_filter = std::move(new_dialog_filter);
+
+ if (need_update_unread_message_count) {
+ send_update_unread_message_count(old_list, DialogId(), true, source);
+ }
+ if (need_update_unread_chat_count) {
+ send_update_unread_chat_count(old_list, DialogId(), true, source);
+ } else if (need_save_unread_chat_count) {
+ save_unread_chat_count(old_list);
+ }
+
+ for (const auto &it : updated_position_dialogs) {
+ send_update_chat_position(dialog_list_id, it.second, source);
+ }
+
+ if (old_list.need_unread_count_recalc_) {
+ // repair unread count
+ get_dialogs_from_list(dialog_list_id, static_cast<int32>(old_list.pinned_dialogs_.size() + 2), Auto());
+ }
+
+ if (!load_list_promises.empty()) {
+ LOG(INFO) << "Retry loading of chats in " << dialog_list_id;
+ set_promises(load_list_promises); // try again
+ }
+ return;
+ }
+ }
+ UNREACHABLE();
+}
+
+int32 MessagesManager::delete_dialog_filter(DialogFilterId dialog_filter_id, const char *source) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return -1;
+ }
+
+ LOG(INFO) << "Delete " << dialog_filter_id << " from " << source;
+ for (auto it = dialog_filters_.begin(); it != dialog_filters_.end(); ++it) {
+ if ((*it)->dialog_filter_id == dialog_filter_id) {
+ auto dialog_list_id = DialogListId(dialog_filter_id);
+ auto *list = get_dialog_list(dialog_list_id);
+ CHECK(list != nullptr);
+ auto folder_ids = get_dialog_list_folder_ids(*list);
+ CHECK(!folder_ids.empty());
+
+ for (auto folder_id : folder_ids) {
+ auto *folder = get_dialog_folder(folder_id);
+ CHECK(folder != nullptr);
+ for (const auto &dialog_date : folder->ordered_dialogs_) {
+ if (dialog_date.get_order() == DEFAULT_ORDER) {
+ break;
+ }
+
+ auto dialog_id = dialog_date.get_dialog_id();
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ const DialogPositionInList old_position = get_dialog_position_in_list(list, d);
+
+ if (is_dialog_in_list(d, dialog_list_id)) {
+ remove_dialog_from_list(d, dialog_list_id);
+
+ if (old_position.public_order != 0) {
+ send_update_chat_position(dialog_list_id, d, source);
+ }
+ }
+ }
+ }
+
+ if (G()->parameters().use_message_db) {
+ postponed_unread_message_count_updates_.erase(dialog_list_id);
+ postponed_unread_chat_count_updates_.erase(dialog_list_id);
+
+ if (list->is_message_unread_count_inited_) {
+ list->unread_message_total_count_ = 0;
+ list->unread_message_muted_count_ = 0;
+ send_update_unread_message_count(*list, DialogId(), true, source, true);
+ G()->td_db()->get_binlog_pmc()->erase(PSTRING() << "unread_message_count" << dialog_list_id.get());
+ }
+ if (list->is_dialog_unread_count_inited_) {
+ list->unread_dialog_total_count_ = 0;
+ list->unread_dialog_muted_count_ = 0;
+ list->unread_dialog_marked_count_ = 0;
+ list->unread_dialog_muted_marked_count_ = 0;
+ list->in_memory_dialog_total_count_ = 0;
+ list->server_dialog_total_count_ = 0;
+ list->secret_chat_total_count_ = 0;
+ send_update_unread_chat_count(*list, DialogId(), true, source, true);
+ G()->td_db()->get_binlog_pmc()->erase(PSTRING() << "unread_dialog_count" << dialog_list_id.get());
+ }
+ }
+
+ fail_promises(list->load_list_queries_, Status::Error(400, "Chat list not found"));
+
+ auto position = static_cast<int32>(it - dialog_filters_.begin());
+ dialog_lists_.erase(dialog_list_id);
+ dialog_filters_.erase(it);
+ return position;
+ }
+ }
+ UNREACHABLE();
+ return -1;
}
Status MessagesManager::delete_dialog_reply_markup(DialogId dialog_id, MessageId message_id) {
if (td_->auth_manager_->is_bot()) {
- return Status::Error(6, "Bots can't delete chat reply markup");
+ return Status::Error(400, "Bots can't delete chat reply markup");
+ }
+ if (message_id.is_scheduled()) {
+ return Status::Error(400, "Wrong message identifier specified");
}
if (!message_id.is_valid()) {
- return Status::Error(6, "Invalid message id specified");
+ return Status::Error(400, "Invalid message identifier specified");
}
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "delete_dialog_reply_markup");
if (d == nullptr) {
- return Status::Error(6, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
if (d->reply_markup_message_id != message_id) {
return Status::OK();
}
- const Message *message = get_message_force(d, message_id);
- CHECK(message != nullptr);
- CHECK(message->reply_markup != nullptr);
+ Message *m = get_message_force(d, message_id, "delete_dialog_reply_markup");
+ CHECK(m != nullptr);
+ CHECK(m->reply_markup != nullptr);
- if (message->reply_markup->type == ReplyMarkup::Type::ForceReply) {
+ if (m->reply_markup->type == ReplyMarkup::Type::ForceReply) {
set_dialog_reply_markup(d, MessageId());
- } else if (message->reply_markup->type == ReplyMarkup::Type::ShowKeyboard) {
- if (!message->reply_markup->is_one_time_keyboard) {
- return Status::Error(6, "Do not need to delete non one-time keyboard");
+ } else if (m->reply_markup->type == ReplyMarkup::Type::ShowKeyboard) {
+ if (!m->reply_markup->is_one_time_keyboard) {
+ return Status::Error(400, "Do not need to delete non one-time keyboard");
}
- if (message->reply_markup->is_personal) {
- message->reply_markup->is_personal = false;
+ if (m->reply_markup->is_personal) {
+ m->reply_markup->is_personal = false;
set_dialog_reply_markup(d, message_id);
- on_message_changed(d, message, "delete_dialog_reply_markup");
+ on_message_changed(d, m, true, "delete_dialog_reply_markup");
}
} else {
// non-bots can't have messages with RemoveKeyboard
@@ -11845,56 +20383,56 @@ class MessagesManager::SaveDialogDraftMessageOnServerLogEvent {
}
};
-Status MessagesManager::set_dialog_draft_message(DialogId dialog_id,
+Status MessagesManager::set_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id,
tl_object_ptr<td_api::draftMessage> &&draft_message) {
if (td_->auth_manager_->is_bot()) {
- return Status::Error(6, "Bots can't change chat draft message");
+ return Status::Error(400, "Bots can't change chat draft message");
}
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "set_dialog_draft_message");
if (d == nullptr) {
- return Status::Error(6, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
- unique_ptr<DraftMessage> new_draft_message;
- if (draft_message != nullptr) {
- new_draft_message = make_unique<DraftMessage>();
- new_draft_message->date = G()->unix_time();
- new_draft_message->reply_to_message_id = get_reply_to_message_id(d, MessageId(draft_message->reply_to_message_id_));
+ TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, MessageId()));
- auto input_message_content = std::move(draft_message->input_message_text_);
- if (input_message_content != nullptr) {
- int32 draft_message_content_type = input_message_content->get_id();
- if (draft_message_content_type != td_api::inputMessageText::ID) {
- return Status::Error(5, "Input message content type must be InputMessageText");
- }
- TRY_RESULT(message_content, process_input_message_text(dialog_id, std::move(input_message_content), false, true));
- new_draft_message->input_message_text = std::move(message_content);
- }
+ TRY_RESULT(new_draft_message, get_draft_message(td_, dialog_id, std::move(draft_message)));
+ if (new_draft_message != nullptr) {
+ new_draft_message->reply_to_message_id =
+ get_reply_to_message_id(d, top_thread_message_id, new_draft_message->reply_to_message_id, true);
if (!new_draft_message->reply_to_message_id.is_valid() && new_draft_message->input_message_text.text.text.empty()) {
new_draft_message = nullptr;
}
}
+ if (top_thread_message_id != MessageId()) {
+ CHECK(top_thread_message_id.is_valid());
+ CHECK(top_thread_message_id.is_server());
+ auto m = get_message_force(d, top_thread_message_id, "set_dialog_draft_message");
+ if (m == nullptr || m->reply_info.is_comment_ || !is_active_message_reply_info(dialog_id, m->reply_info)) {
+ return Status::OK();
+ }
+
+ auto &old_draft_message = m->thread_draft_message;
+ if (((new_draft_message == nullptr) != (old_draft_message == nullptr)) ||
+ (new_draft_message != nullptr &&
+ (old_draft_message->reply_to_message_id != new_draft_message->reply_to_message_id ||
+ old_draft_message->input_message_text != new_draft_message->input_message_text))) {
+ old_draft_message = std::move(new_draft_message);
+ on_message_changed(d, m, false, "set_dialog_draft_message");
+ }
+ return Status::OK();
+ }
+
if (update_dialog_draft_message(d, std::move(new_draft_message), false, true)) {
if (dialog_id.get_type() != DialogType::SecretChat) {
if (G()->parameters().use_message_db) {
- LOG(INFO) << "Save draft of " << dialog_id << " to binlog";
- SaveDialogDraftMessageOnServerLogEvent logevent;
- logevent.dialog_id_ = dialog_id;
- auto storer = LogEventStorerImpl<SaveDialogDraftMessageOnServerLogEvent>(logevent);
- if (d->save_draft_message_logevent_id == 0) {
- d->save_draft_message_logevent_id = BinlogHelper::add(
- G()->td_db()->get_binlog(), LogEvent::HandlerType::SaveDialogDraftMessageOnServer, storer);
- LOG(INFO) << "Add draft logevent " << d->save_draft_message_logevent_id;
- } else {
- auto new_logevent_id = BinlogHelper::rewrite(G()->td_db()->get_binlog(), d->save_draft_message_logevent_id,
- LogEvent::HandlerType::SaveDialogDraftMessageOnServer, storer);
- LOG(INFO) << "Rewrite draft logevent " << d->save_draft_message_logevent_id << " with " << new_logevent_id;
- }
- d->save_draft_message_logevent_id_generation++;
+ SaveDialogDraftMessageOnServerLogEvent log_event;
+ log_event.dialog_id_ = dialog_id;
+ add_log_event(d->save_draft_message_log_event_id, get_log_event_storer(log_event),
+ LogEvent::HandlerType::SaveDialogDraftMessageOnServer, "draft");
}
pending_draft_message_timeout_.set_timeout_in(dialog_id.get(), d->is_opened ? MIN_SAVE_DRAFT_DELAY : 0);
@@ -11904,72 +20442,140 @@ Status MessagesManager::set_dialog_draft_message(DialogId dialog_id,
}
void MessagesManager::save_dialog_draft_message_on_server(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
- Promise<> promise;
- if (d->save_draft_message_logevent_id != 0) {
- promise = PromiseCreator::lambda(
- [actor_id = actor_id(this), dialog_id,
- generation = d->save_draft_message_logevent_id_generation](Result<Unit> result) mutable {
- if (!G()->close_flag()) {
- send_closure(actor_id, &MessagesManager::on_saved_dialog_draft_message, dialog_id, generation);
- }
- });
+ Promise<Unit> promise;
+ if (d->save_draft_message_log_event_id.log_event_id != 0) {
+ d->save_draft_message_log_event_id.generation++;
+ promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id,
+ generation = d->save_draft_message_log_event_id.generation](Result<Unit> result) {
+ if (!G()->close_flag()) {
+ send_closure(actor_id, &MessagesManager::on_saved_dialog_draft_message, dialog_id, generation);
+ }
+ });
}
- // TODO do not send two queries simultaneously or use SequenceDispatcher
+ // TODO do not send two queries simultaneously or use InvokeAfter
td_->create_handler<SaveDraftMessageQuery>(std::move(promise))->send(dialog_id, d->draft_message);
}
void MessagesManager::on_saved_dialog_draft_message(DialogId dialog_id, uint64 generation) {
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
- LOG(INFO) << "Saved draft in " << dialog_id << " with logevent " << d->save_draft_message_logevent_id;
- if (d->save_draft_message_logevent_id_generation == generation) {
- CHECK(d->save_draft_message_logevent_id != 0);
- LOG(INFO) << "Delete draft logevent " << d->save_draft_message_logevent_id;
- BinlogHelper::erase(G()->td_db()->get_binlog(), d->save_draft_message_logevent_id);
- d->save_draft_message_logevent_id = 0;
+ delete_log_event(d->save_draft_message_log_event_id, generation, "draft");
+}
+
+void MessagesManager::clear_all_draft_messages(bool exclude_secret_chats, Promise<Unit> &&promise) {
+ if (!exclude_secret_chats) {
+ dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
+ Dialog *d = dialog.get();
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ update_dialog_draft_message(d, nullptr, false, true);
+ }
+ });
}
+ td_->create_handler<ClearAllDraftsQuery>(std::move(promise))->send();
}
-int32 MessagesManager::get_pinned_dialogs_limit() {
- int32 limit = G()->shared_config().get_option_integer("pinned_chat_count_max");
+int32 MessagesManager::get_pinned_dialogs_limit(DialogListId dialog_list_id) const {
+ if (dialog_list_id.is_filter()) {
+ return DialogFilter::get_max_filter_dialogs();
+ }
+
+ Slice key{"pinned_chat_count_max"};
+ int32 default_limit = 5;
+ if (!dialog_list_id.is_folder() || dialog_list_id.get_folder_id() != FolderId::main()) {
+ key = Slice("pinned_archived_chat_count_max");
+ default_limit = 100;
+ }
+ int32 limit = clamp(narrow_cast<int32>(td_->option_manager_->get_option_integer(key)), 0, 1000);
if (limit <= 0) {
- const int32 DEFAULT_PINNED_DIALOGS_LIMIT = 5;
- return DEFAULT_PINNED_DIALOGS_LIMIT;
+ if (td_->option_manager_->get_option_boolean("is_premium")) {
+ default_limit *= 2;
+ }
+ return default_limit;
}
return limit;
}
vector<DialogId> MessagesManager::remove_secret_chat_dialog_ids(vector<DialogId> dialog_ids) {
- dialog_ids.erase(std::remove_if(dialog_ids.begin(), dialog_ids.end(),
- [](DialogId dialog_id) { return dialog_id.get_type() == DialogType::SecretChat; }),
- dialog_ids.end());
+ td::remove_if(dialog_ids, [](DialogId dialog_id) { return dialog_id.get_type() == DialogType::SecretChat; });
return dialog_ids;
}
-Status MessagesManager::toggle_dialog_is_pinned(DialogId dialog_id, bool is_pinned) {
+Status MessagesManager::toggle_dialog_is_pinned(DialogListId dialog_list_id, DialogId dialog_id, bool is_pinned) {
if (td_->auth_manager_->is_bot()) {
- return Status::Error(6, "Bots can't change chat pin state");
+ return Status::Error(400, "Bots can't change chat pin state");
}
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_pinned");
if (d == nullptr) {
- return Status::Error(6, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- return Status::Error(6, "Can't access the chat");
+ return Status::Error(400, "Can't access the chat");
+ }
+ if (d->order == DEFAULT_ORDER && is_pinned) {
+ return Status::Error(400, "The chat can't be pinned");
}
- bool was_pinned = d->pinned_order != DEFAULT_ORDER;
+ auto *list = get_dialog_list(dialog_list_id);
+ if (list == nullptr) {
+ return Status::Error(400, "Chat list not found");
+ }
+ if (!list->are_pinned_dialogs_inited_) {
+ return Status::Error(400, "Pinned chats must be loaded first");
+ }
+
+ bool was_pinned = is_dialog_pinned(dialog_list_id, dialog_id);
if (is_pinned == was_pinned) {
return Status::OK();
}
+ if (dialog_list_id.is_filter()) {
+ CHECK(is_update_chat_filters_sent_);
+ auto dialog_filter_id = dialog_list_id.get_filter_id();
+ auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
+ CHECK(old_dialog_filter != nullptr);
+ auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter);
+ if (is_pinned) {
+ new_dialog_filter->pinned_dialog_ids.insert(new_dialog_filter->pinned_dialog_ids.begin(),
+ get_input_dialog_id(dialog_id));
+ InputDialogId::remove(new_dialog_filter->included_dialog_ids, dialog_id);
+ InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id);
+ } else {
+ bool is_removed = InputDialogId::remove(new_dialog_filter->pinned_dialog_ids, dialog_id);
+ CHECK(is_removed);
+ new_dialog_filter->included_dialog_ids.push_back(get_input_dialog_id(dialog_id));
+ }
+
+ TRY_STATUS(new_dialog_filter->check_limits());
+ sort_dialog_filter_input_dialog_ids(new_dialog_filter.get(), "toggle_dialog_is_pinned");
+
+ edit_dialog_filter(std::move(new_dialog_filter), "toggle_dialog_is_pinned");
+ save_dialog_filters();
+ send_update_chat_filters();
+
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ synchronize_dialog_filters();
+ }
+
+ return Status::OK();
+ }
+
+ CHECK(dialog_list_id.is_folder());
+ auto folder_id = dialog_list_id.get_folder_id();
if (is_pinned) {
- auto pinned_dialog_ids = get_pinned_dialogs();
+ if (d->folder_id != folder_id) {
+ return Status::Error(400, "Chat not in the list");
+ }
+
+ auto pinned_dialog_ids = get_pinned_dialog_ids(dialog_list_id);
auto pinned_dialog_count = pinned_dialog_ids.size();
auto secret_pinned_dialog_count =
std::count_if(pinned_dialog_ids.begin(), pinned_dialog_ids.end(),
@@ -11978,15 +20584,14 @@ Status MessagesManager::toggle_dialog_is_pinned(DialogId dialog_id, bool is_pinn
? secret_pinned_dialog_count
: pinned_dialog_count - secret_pinned_dialog_count;
- if (dialog_count >= static_cast<size_t>(get_pinned_dialogs_limit())) {
- return Status::Error(400, "Maximum number of pinned chats exceeded");
+ if (dialog_count >= static_cast<size_t>(get_pinned_dialogs_limit(dialog_list_id))) {
+ return Status::Error(400, "The maximum number of pinned chats exceeded");
}
}
- set_dialog_is_pinned(d, is_pinned);
- update_dialog_pos(d, false, "toggle_dialog_is_pinned");
-
- toggle_dialog_is_pinned_on_server(dialog_id, is_pinned, 0);
+ if (set_dialog_is_pinned(dialog_list_id, d, is_pinned)) {
+ toggle_dialog_is_pinned_on_server(dialog_id, is_pinned, 0);
+ }
return Status::OK();
}
@@ -12014,44 +20619,48 @@ class MessagesManager::ToggleDialogIsPinnedOnServerLogEvent {
}
};
-void MessagesManager::toggle_dialog_is_pinned_on_server(DialogId dialog_id, bool is_pinned, uint64 logevent_id) {
- if (logevent_id == 0 && G()->parameters().use_message_db) {
- ToggleDialogIsPinnedOnServerLogEvent logevent;
- logevent.dialog_id_ = dialog_id;
- logevent.is_pinned_ = is_pinned;
+uint64 MessagesManager::save_toggle_dialog_is_pinned_on_server_log_event(DialogId dialog_id, bool is_pinned) {
+ ToggleDialogIsPinnedOnServerLogEvent log_event{dialog_id, is_pinned};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsPinnedOnServer,
+ get_log_event_storer(log_event));
+}
- auto storer = LogEventStorerImpl<ToggleDialogIsPinnedOnServerLogEvent>(logevent);
- logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsPinnedOnServer, storer);
+void MessagesManager::toggle_dialog_is_pinned_on_server(DialogId dialog_id, bool is_pinned, uint64 log_event_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (log_event_id == 0 && dialog_id.get_type() == DialogType::SecretChat) {
+ // don't even create new binlog events
+ return;
}
- Promise<> promise;
- if (logevent_id != 0) {
- promise = PromiseCreator::lambda([logevent_id](Result<Unit> result) mutable {
- if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
- }
- });
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_toggle_dialog_is_pinned_on_server_log_event(dialog_id, is_pinned);
}
- td_->create_handler<ToggleDialogPinQuery>(std::move(promise))->send(dialog_id, is_pinned);
+ td_->create_handler<ToggleDialogPinQuery>(get_erase_log_event_promise(log_event_id))->send(dialog_id, is_pinned);
}
-Status MessagesManager::set_pinned_dialogs(vector<DialogId> dialog_ids) {
+Status MessagesManager::set_pinned_dialogs(DialogListId dialog_list_id, vector<DialogId> dialog_ids) {
if (td_->auth_manager_->is_bot()) {
- return Status::Error(6, "Bots can't reorder pinned chats");
+ return Status::Error(400, "Bots can't reorder pinned chats");
}
int32 dialog_count = 0;
int32 secret_dialog_count = 0;
- auto dialog_count_limit = get_pinned_dialogs_limit();
+ auto dialog_count_limit = get_pinned_dialogs_limit(dialog_list_id);
+ FlatHashSet<DialogId, DialogIdHash> new_pinned_dialog_ids;
for (auto dialog_id : dialog_ids) {
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "set_pinned_dialogs");
if (d == nullptr) {
- return Status::Error(6, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- return Status::Error(6, "Can't access the chat");
+ return Status::Error(400, "Can't access the chat");
+ }
+ if (d->order == DEFAULT_ORDER) {
+ return Status::Error(400, "The chat can't be pinned");
+ }
+ if (dialog_list_id.is_folder() && d->folder_id != dialog_list_id.get_folder_id()) {
+ return Status::Error(400, "Chat not in the list");
}
if (dialog_id.get_type() == DialogType::SecretChat) {
secret_dialog_count++;
@@ -12060,28 +20669,72 @@ Status MessagesManager::set_pinned_dialogs(vector<DialogId> dialog_ids) {
}
if (dialog_count > dialog_count_limit || secret_dialog_count > dialog_count_limit) {
- return Status::Error(400, "Maximum number of pinned chats exceeded");
+ return Status::Error(400, "The maximum number of pinned chats exceeded");
}
+
+ new_pinned_dialog_ids.insert(dialog_id);
}
- std::unordered_set<DialogId, DialogIdHash> new_pinned_dialog_ids(dialog_ids.begin(), dialog_ids.end());
if (new_pinned_dialog_ids.size() != dialog_ids.size()) {
return Status::Error(400, "Duplicate chats in the list of pinned chats");
}
- auto pinned_dialog_ids = get_pinned_dialogs();
+ auto *list = get_dialog_list(dialog_list_id);
+ if (list == nullptr) {
+ return Status::Error(400, "Chat list not found");
+ }
+ if (!list->are_pinned_dialogs_inited_) {
+ return Status::Error(400, "Pinned chats must be loaded first");
+ }
+
+ auto pinned_dialog_ids = get_pinned_dialog_ids(dialog_list_id);
if (pinned_dialog_ids == dialog_ids) {
return Status::OK();
}
- LOG(INFO) << "Reorder pinned chats order from " << format::as_array(pinned_dialog_ids) << " to "
- << format::as_array(dialog_ids);
+ LOG(INFO) << "Reorder pinned chats in " << dialog_list_id << " from " << pinned_dialog_ids << " to " << dialog_ids;
auto server_old_dialog_ids = remove_secret_chat_dialog_ids(pinned_dialog_ids);
auto server_new_dialog_ids = remove_secret_chat_dialog_ids(dialog_ids);
+ if (dialog_list_id.is_filter()) {
+ CHECK(is_update_chat_filters_sent_);
+ auto dialog_filter_id = dialog_list_id.get_filter_id();
+ auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
+ CHECK(old_dialog_filter != nullptr);
+ auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter);
+ auto old_pinned_dialog_ids = std::move(new_dialog_filter->pinned_dialog_ids);
+ new_dialog_filter->pinned_dialog_ids =
+ transform(dialog_ids, [this](DialogId dialog_id) { return get_input_dialog_id(dialog_id); });
+ auto is_new_pinned = [&new_pinned_dialog_ids](InputDialogId input_dialog_id) {
+ return new_pinned_dialog_ids.count(input_dialog_id.get_dialog_id()) > 0;
+ };
+ td::remove_if(old_pinned_dialog_ids, is_new_pinned);
+ td::remove_if(new_dialog_filter->included_dialog_ids, is_new_pinned);
+ td::remove_if(new_dialog_filter->excluded_dialog_ids, is_new_pinned);
+ append(new_dialog_filter->included_dialog_ids, old_pinned_dialog_ids);
+
+ TRY_STATUS(new_dialog_filter->check_limits());
+ sort_dialog_filter_input_dialog_ids(new_dialog_filter.get(), "set_pinned_dialogs");
+
+ edit_dialog_filter(std::move(new_dialog_filter), "set_pinned_dialogs");
+ save_dialog_filters();
+ send_update_chat_filters();
+
+ if (server_old_dialog_ids != server_new_dialog_ids) {
+ synchronize_dialog_filters();
+ }
+
+ return Status::OK();
+ }
+
+ CHECK(dialog_list_id.is_folder());
+
std::reverse(pinned_dialog_ids.begin(), pinned_dialog_ids.end());
std::reverse(dialog_ids.begin(), dialog_ids.end());
- std::unordered_set<DialogId, DialogIdHash> old_pinned_dialog_ids(pinned_dialog_ids.begin(), pinned_dialog_ids.end());
+ FlatHashSet<DialogId, DialogIdHash> old_pinned_dialog_ids;
+ for (auto dialog_id : pinned_dialog_ids) {
+ old_pinned_dialog_ids.insert(dialog_id);
+ }
auto old_it = pinned_dialog_ids.begin();
for (auto dialog_id : dialog_ids) {
old_pinned_dialog_ids.erase(dialog_id);
@@ -12089,65 +20742,309 @@ Status MessagesManager::set_pinned_dialogs(vector<DialogId> dialog_ids) {
if (*old_it == dialog_id) {
break;
}
- old_it++;
+ ++old_it;
}
if (old_it < pinned_dialog_ids.end()) {
// leave dialog where it is
+ ++old_it;
continue;
}
set_dialog_is_pinned(dialog_id, true);
}
for (auto dialog_id : old_pinned_dialog_ids) {
- set_dialog_is_pinned(dialog_id, false);
+ Dialog *d = get_dialog_force(dialog_id, "set_pinned_dialogs 2");
+ if (d == nullptr) {
+ LOG(ERROR) << "Failed to find " << dialog_id << " to unpin in " << dialog_list_id;
+ force_create_dialog(dialog_id, "set_pinned_dialogs", true);
+ d = get_dialog_force(dialog_id, "set_pinned_dialogs 3");
+ }
+ if (d != nullptr) {
+ set_dialog_is_pinned(dialog_list_id, d, false);
+ }
}
if (server_old_dialog_ids != server_new_dialog_ids) {
- reorder_pinned_dialogs_on_server(server_new_dialog_ids, 0);
+ reorder_pinned_dialogs_on_server(dialog_list_id.get_folder_id(), server_new_dialog_ids, 0);
}
return Status::OK();
}
class MessagesManager::ReorderPinnedDialogsOnServerLogEvent {
public:
+ FolderId folder_id_;
vector<DialogId> dialog_ids_;
template <class StorerT>
void store(StorerT &storer) const {
+ td::store(folder_id_, storer);
td::store(dialog_ids_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
+ if (parser.version() >= static_cast<int32>(Version::AddFolders)) {
+ td::parse(folder_id_, parser);
+ } else {
+ folder_id_ = FolderId();
+ }
td::parse(dialog_ids_, parser);
}
};
-void MessagesManager::reorder_pinned_dialogs_on_server(const vector<DialogId> &dialog_ids, uint64 logevent_id) {
- if (logevent_id == 0 && G()->parameters().use_message_db) {
- ReorderPinnedDialogsOnServerLogEvent logevent;
- logevent.dialog_ids_ = dialog_ids;
+uint64 MessagesManager::save_reorder_pinned_dialogs_on_server_log_event(FolderId folder_id,
+ const vector<DialogId> &dialog_ids) {
+ ReorderPinnedDialogsOnServerLogEvent log_event{folder_id, dialog_ids};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReorderPinnedDialogsOnServer,
+ get_log_event_storer(log_event));
+}
- auto storer = LogEventStorerImpl<ReorderPinnedDialogsOnServerLogEvent>(logevent);
- logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReorderPinnedDialogsOnServer, storer);
+void MessagesManager::reorder_pinned_dialogs_on_server(FolderId folder_id, const vector<DialogId> &dialog_ids,
+ uint64 log_event_id) {
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_reorder_pinned_dialogs_on_server_log_event(folder_id, dialog_ids);
}
- Promise<> promise;
- if (logevent_id != 0) {
- promise = PromiseCreator::lambda([logevent_id](Result<Unit> result) mutable {
- if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
+ td_->create_handler<ReorderPinnedDialogsQuery>(get_erase_log_event_promise(log_event_id))
+ ->send(folder_id, dialog_ids);
+}
+
+Status MessagesManager::toggle_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) {
+ Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_is_marked_as_unread");
+ if (d == nullptr) {
+ return Status::Error(400, "Chat not found");
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return Status::Error(400, "Can't access the chat");
+ }
+
+ if (is_marked_as_unread == d->is_marked_as_unread) {
+ return Status::OK();
+ }
+
+ set_dialog_is_marked_as_unread(d, is_marked_as_unread);
+
+ toggle_dialog_is_marked_as_unread_on_server(dialog_id, is_marked_as_unread, 0);
+ return Status::OK();
+}
+
+class MessagesManager::ToggleDialogIsMarkedAsUnreadOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+ bool is_marked_as_unread_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_marked_as_unread_);
+ END_STORE_FLAGS();
+
+ td::store(dialog_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_marked_as_unread_);
+ END_PARSE_FLAGS();
+
+ td::parse(dialog_id_, parser);
+ }
+};
+
+uint64 MessagesManager::save_toggle_dialog_is_marked_as_unread_on_server_log_event(DialogId dialog_id,
+ bool is_marked_as_unread) {
+ ToggleDialogIsMarkedAsUnreadOnServerLogEvent log_event{dialog_id, is_marked_as_unread};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::toggle_dialog_is_marked_as_unread_on_server(DialogId dialog_id, bool is_marked_as_unread,
+ uint64 log_event_id) {
+ if (log_event_id == 0 && dialog_id.get_type() == DialogType::SecretChat) {
+ // don't even create new binlog events
+ return;
+ }
+
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_toggle_dialog_is_marked_as_unread_on_server_log_event(dialog_id, is_marked_as_unread);
+ }
+
+ td_->create_handler<ToggleDialogUnreadMarkQuery>(get_erase_log_event_promise(log_event_id))
+ ->send(dialog_id, is_marked_as_unread);
+}
+
+Status MessagesManager::toggle_message_sender_is_blocked(const td_api::object_ptr<td_api::MessageSender> &sender,
+ bool is_blocked) {
+ TRY_RESULT(dialog_id, get_message_sender_dialog_id(td_, sender, true, false));
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ if (dialog_id == get_my_dialog_id()) {
+ return Status::Error(400, is_blocked ? Slice("Can't block self") : Slice("Can't unblock self"));
}
- });
+ break;
+ case DialogType::Chat:
+ return Status::Error(400, "Basic group chats can't be blocked");
+ case DialogType::Channel:
+ // ok
+ break;
+ case DialogType::SecretChat: {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (!user_id.is_valid() || !td_->contacts_manager_->have_user_force(user_id)) {
+ return Status::Error(400, "The secret chat can't be blocked");
+ }
+ dialog_id = DialogId(user_id);
+ break;
+ }
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+
+ Dialog *d = get_dialog_force(dialog_id, "toggle_message_sender_is_blocked");
+ if (!have_input_peer(dialog_id, AccessRights::Know)) {
+ return Status::Error(400, "Message sender isn't accessible");
+ }
+ if (d != nullptr) {
+ if (is_blocked == d->is_blocked) {
+ return Status::OK();
+ }
+ set_dialog_is_blocked(d, is_blocked);
+ } else {
+ CHECK(dialog_id.get_type() == DialogType::User);
+ td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked);
}
- td_->create_handler<ReorderPinnedDialogsQuery>(std::move(promise))->send(dialog_ids);
+ toggle_dialog_is_blocked_on_server(dialog_id, is_blocked, 0);
+ return Status::OK();
+}
+
+class MessagesManager::ToggleDialogIsBlockedOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+ bool is_blocked_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_blocked_);
+ END_STORE_FLAGS();
+
+ td::store(dialog_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_blocked_);
+ END_PARSE_FLAGS();
+
+ td::parse(dialog_id_, parser);
+ }
+};
+
+uint64 MessagesManager::save_toggle_dialog_is_blocked_on_server_log_event(DialogId dialog_id, bool is_blocked) {
+ ToggleDialogIsBlockedOnServerLogEvent log_event{dialog_id, is_blocked};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ToggleDialogIsBlockedOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, uint64 log_event_id) {
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_toggle_dialog_is_blocked_on_server_log_event(dialog_id, is_blocked);
+ }
+
+ td_->create_handler<ToggleDialogIsBlockedQuery>(get_erase_log_event_promise(log_event_id))
+ ->send(dialog_id, is_blocked);
+}
+
+Status MessagesManager::toggle_dialog_silent_send_message(DialogId dialog_id, bool silent_send_message) {
+ CHECK(!td_->auth_manager_->is_bot());
+
+ Dialog *d = get_dialog_force(dialog_id, "toggle_dialog_silent_send_message");
+ if (d == nullptr) {
+ return Status::Error(400, "Chat not found");
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return Status::Error(400, "Can't access the chat");
+ }
+
+ if (update_dialog_silent_send_message(d, silent_send_message)) {
+ update_dialog_notification_settings_on_server(dialog_id, false);
+ }
+
+ return Status::OK();
+}
+
+class MessagesManager::UpdateDialogNotificationSettingsOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(dialog_id_, parser);
+ }
+};
+
+void MessagesManager::update_dialog_notification_settings_on_server(DialogId dialog_id, bool from_binlog) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ if (!from_binlog && td_->notification_settings_manager_->get_input_notify_peer(dialog_id) == nullptr) {
+ // don't even create new binlog events
+ return;
+ }
+
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ if (!from_binlog && G()->parameters().use_message_db) {
+ UpdateDialogNotificationSettingsOnServerLogEvent log_event;
+ log_event.dialog_id_ = dialog_id;
+ add_log_event(d->save_notification_settings_log_event_id, get_log_event_storer(log_event),
+ LogEvent::HandlerType::UpdateDialogNotificationSettingsOnServer, "notification settings");
+ }
+
+ Promise<Unit> promise;
+ if (d->save_notification_settings_log_event_id.log_event_id != 0) {
+ d->save_notification_settings_log_event_id.generation++;
+ promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_id,
+ generation = d->save_notification_settings_log_event_id.generation](Result<Unit> result) {
+ if (!G()->close_flag()) {
+ send_closure(actor_id, &MessagesManager::on_updated_dialog_notification_settings, dialog_id, generation);
+ }
+ });
+ }
+
+ send_update_dialog_notification_settings_query(d, std::move(promise));
+}
+
+void MessagesManager::send_update_dialog_notification_settings_query(const Dialog *d, Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(d != nullptr);
+ // TODO do not send two queries simultaneously or use InvokeAfter
+ td_->notification_settings_manager_->update_dialog_notify_settings(d->dialog_id, d->notification_settings,
+ std::move(promise));
+}
+
+void MessagesManager::on_updated_dialog_notification_settings(DialogId dialog_id, uint64 generation) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ delete_log_event(d->save_notification_settings_log_event_id, generation, "notification settings");
}
Status MessagesManager::set_dialog_client_data(DialogId dialog_id, string &&client_data) {
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "set_dialog_client_data");
if (d == nullptr) {
- return Status::Error(6, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
d->client_data = std::move(client_data);
@@ -12155,22 +21052,64 @@ Status MessagesManager::set_dialog_client_data(DialogId dialog_id, string &&clie
return Status::OK();
}
+bool MessagesManager::is_dialog_inited(const Dialog *d) {
+ return d != nullptr && d->notification_settings.is_synchronized && d->is_last_read_inbox_message_id_inited &&
+ d->is_last_read_outbox_message_id_inited;
+}
+
+int32 MessagesManager::get_dialog_mute_until(const Dialog *d) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(d != nullptr);
+ if (d->notification_settings.use_default_mute_until) {
+ auto scope = get_dialog_notification_setting_scope(d->dialog_id);
+ return td_->notification_settings_manager_->get_scope_mute_until(scope);
+ } else {
+ return d->notification_settings.mute_until;
+ }
+}
+
+bool MessagesManager::is_dialog_muted(const Dialog *d) const {
+ return get_dialog_mute_until(d) != 0;
+}
+
+bool MessagesManager::is_dialog_pinned_message_notifications_disabled(const Dialog *d) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(d != nullptr);
+ if (d->notification_settings.use_default_disable_pinned_message_notifications) {
+ auto scope = get_dialog_notification_setting_scope(d->dialog_id);
+ return td_->notification_settings_manager_->get_scope_disable_pinned_message_notifications(scope);
+ }
+
+ return d->notification_settings.disable_pinned_message_notifications;
+}
+
+bool MessagesManager::is_dialog_mention_notifications_disabled(const Dialog *d) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(d != nullptr);
+ if (d->notification_settings.use_default_disable_mention_notifications) {
+ auto scope = get_dialog_notification_setting_scope(d->dialog_id);
+ return td_->notification_settings_manager_->get_scope_disable_mention_notifications(scope);
+ }
+
+ return d->notification_settings.disable_mention_notifications;
+}
+
void MessagesManager::create_dialog(DialogId dialog_id, bool force, Promise<Unit> &&promise) {
if (!have_input_peer(dialog_id, AccessRights::Read)) {
if (!have_dialog_info_force(dialog_id)) {
- return promise.set_error(Status::Error(6, "Chat info not found"));
+ return promise.set_error(Status::Error(400, "Chat info not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- return promise.set_error(Status::Error(6, "Can't access the chat"));
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
}
}
if (force || td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) {
force_create_dialog(dialog_id, "create dialog");
} else {
- const Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr || !d->notification_settings.is_synchronized) {
- return send_get_dialog_query(dialog_id, std::move(promise));
+ const Dialog *d = get_dialog_force(dialog_id, "create_dialog");
+ if (!is_dialog_inited(d)) {
+ return send_get_dialog_query(dialog_id, std::move(promise), 0, "create_dialog");
}
}
@@ -12192,37 +21131,36 @@ DialogId MessagesManager::create_new_group_chat(const vector<UserId> &user_ids,
created_dialogs_.erase(it);
// set default notification settings to newly created chat
- on_update_notify_settings(dialog_id.get(),
- make_tl_object<telegram_api::peerNotifySettings>(1, true, false, 0, "default"));
+ on_update_dialog_notify_settings(dialog_id, nullptr, "create_new_group_chat");
promise.set_value(Unit());
return dialog_id;
}
if (user_ids.empty()) {
- promise.set_error(Status::Error(3, "Too few users to create basic group chat"));
+ promise.set_error(Status::Error(400, "Too few users to create basic group chat"));
return DialogId();
}
- auto new_title = clean_name(title, MAX_NAME_LENGTH);
+ auto new_title = clean_name(title, MAX_TITLE_LENGTH);
if (new_title.empty()) {
- promise.set_error(Status::Error(3, "Title can't be empty"));
+ promise.set_error(Status::Error(400, "Title must be non-empty"));
return DialogId();
}
vector<tl_object_ptr<telegram_api::InputUser>> input_users;
for (auto user_id : user_ids) {
- auto input_user = td_->contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- promise.set_error(Status::Error(3, "User not found"));
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ promise.set_error(r_input_user.move_as_error());
return DialogId();
}
- input_users.push_back(std::move(input_user));
+ input_users.push_back(r_input_user.move_as_ok());
}
do {
random_id = Random::secure_int64();
- } while (random_id == 0 || created_dialogs_.find(random_id) != created_dialogs_.end());
+ } while (random_id == 0 || created_dialogs_.count(random_id) > 0);
created_dialogs_[random_id]; // reserve place for result
td_->create_handler<CreateChatQuery>(std::move(promise))->send(std::move(input_users), new_title, random_id);
@@ -12230,9 +21168,10 @@ DialogId MessagesManager::create_new_group_chat(const vector<UserId> &user_ids,
}
DialogId MessagesManager::create_new_channel_chat(const string &title, bool is_megagroup, const string &description,
- int64 &random_id, Promise<Unit> &&promise) {
+ const DialogLocation &location, bool for_import, int64 &random_id,
+ Promise<Unit> &&promise) {
LOG(INFO) << "Trying to create " << (is_megagroup ? "supergroup" : "broadcast") << " with title \"" << title
- << "\" and description \"" << description << "\"";
+ << "\", description \"" << description << "\" and " << location;
if (random_id != 0) {
// request has already been sent before
@@ -12245,45 +21184,48 @@ DialogId MessagesManager::create_new_channel_chat(const string &title, bool is_m
created_dialogs_.erase(it);
// set default notification settings to newly created chat
- on_update_notify_settings(dialog_id.get(),
- make_tl_object<telegram_api::peerNotifySettings>(1, true, false, 0, "default"));
+ on_update_dialog_notify_settings(dialog_id, nullptr, "create_new_channel_chat");
promise.set_value(Unit());
return dialog_id;
}
- auto new_title = clean_name(title, MAX_NAME_LENGTH);
+ auto new_title = clean_name(title, MAX_TITLE_LENGTH);
if (new_title.empty()) {
- promise.set_error(Status::Error(3, "Title can't be empty"));
+ promise.set_error(Status::Error(400, "Title must be non-empty"));
return DialogId();
}
do {
random_id = Random::secure_int64();
- } while (random_id == 0 || created_dialogs_.find(random_id) != created_dialogs_.end());
+ } while (random_id == 0 || created_dialogs_.count(random_id) > 0);
created_dialogs_[random_id]; // reserve place for result
td_->create_handler<CreateChannelQuery>(std::move(promise))
- ->send(new_title, is_megagroup, strip_empty_characters(description, MAX_NAME_LENGTH), random_id);
+ ->send(new_title, is_megagroup, strip_empty_characters(description, MAX_DESCRIPTION_LENGTH), location, for_import,
+ random_id);
return DialogId();
}
void MessagesManager::create_new_secret_chat(UserId user_id, Promise<SecretChatId> &&promise) {
- auto user_base = td_->contacts_manager_->get_input_user(user_id);
- if (user_base == nullptr || user_base->get_id() != telegram_api::inputUser::ID) {
- return promise.set_error(Status::Error(6, "User not found"));
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return promise.set_error(r_input_user.move_as_error());
}
- auto user = move_tl_object_as<telegram_api::inputUser>(user_base);
+ if (r_input_user.ok()->get_id() != telegram_api::inputUser::ID) {
+ return promise.set_error(Status::Error(400, "Can't create secret chat with self"));
+ }
+ auto user = static_cast<const telegram_api::inputUser *>(r_input_user.ok().get());
- send_closure(G()->secret_chats_manager(), &SecretChatsManager::create_chat, user->user_id_, user->access_hash_,
- std::move(promise));
+ send_closure(G()->secret_chats_manager(), &SecretChatsManager::create_chat, UserId(user->user_id_),
+ user->access_hash_, std::move(promise));
}
DialogId MessagesManager::migrate_dialog_to_megagroup(DialogId dialog_id, Promise<Unit> &&promise) {
LOG(INFO) << "Trying to convert " << dialog_id << " to supergroup";
if (dialog_id.get_type() != DialogType::Chat) {
- promise.set_error(Status::Error(3, "Only basic group chats can be converted to supergroup"));
+ promise.set_error(Status::Error(400, "Only basic group chats can be converted to supergroup"));
return DialogId();
}
@@ -12294,32 +21236,36 @@ DialogId MessagesManager::migrate_dialog_to_megagroup(DialogId dialog_id, Promis
if (!td_->contacts_manager_->have_channel(channel_id)) {
LOG(ERROR) << "Can't find info about supergroup to which the group has migrated";
- promise.set_error(Status::Error(6, "Supergroup is not found"));
+ promise.set_error(Status::Error(400, "Supergroup is not found"));
return DialogId();
}
auto new_dialog_id = DialogId(channel_id);
- Dialog *d = get_dialog_force(new_dialog_id);
+ Dialog *d = get_dialog_force(new_dialog_id, "migrate_dialog_to_megagroup");
if (d == nullptr) {
- d = add_dialog(new_dialog_id);
+ d = add_dialog(new_dialog_id, "migrate_dialog_to_megagroup");
if (d->pts == 0) {
d->pts = 1;
if (is_debug_message_op_enabled()) {
- d->debug_message_op.emplace_back(Dialog::MessageOp::SetPts, MessageId(), d->pts, false, false, false,
- "migrate");
+ d->debug_message_op.emplace_back(Dialog::MessageOp::SetPts, d->pts, "migrate");
}
}
- update_dialog_pos(d, false, "migrate_dialog_to_megagroup");
+ update_dialog_pos(d, "migrate_dialog_to_megagroup");
}
promise.set_value(Unit());
return new_dialog_id;
}
+bool MessagesManager::is_dialog_opened(DialogId dialog_id) const {
+ const Dialog *d = get_dialog(dialog_id);
+ return d != nullptr && d->is_opened;
+}
+
Status MessagesManager::open_dialog(DialogId dialog_id) {
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "open_dialog");
if (d == nullptr) {
- return Status::Error(3, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
open_dialog(d);
@@ -12327,58 +21273,128 @@ Status MessagesManager::open_dialog(DialogId dialog_id) {
}
Status MessagesManager::close_dialog(DialogId dialog_id) {
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "close_dialog");
if (d == nullptr) {
- return Status::Error(3, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
close_dialog(d);
return Status::OK();
}
-Status MessagesManager::view_messages(DialogId dialog_id, const vector<MessageId> &message_ids, bool force_read) {
- Dialog *d = get_dialog_force(dialog_id);
+DialogId MessagesManager::get_my_dialog_id() const {
+ return DialogId(td_->contacts_manager_->get_my_id());
+}
+
+Status MessagesManager::view_messages(DialogId dialog_id, MessageId top_thread_message_id,
+ const vector<MessageId> &message_ids, bool force_read) {
+ CHECK(!td_->auth_manager_->is_bot());
+
+ Dialog *d = get_dialog_force(dialog_id, "view_messages");
if (d == nullptr) {
- return Status::Error(3, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
for (auto message_id : message_ids) {
- if (!message_id.is_valid()) {
- return Status::Error(3, "Invalid message identifier");
+ if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
+ if (message_id.is_valid_sponsored()) {
+ if (d->is_opened) {
+ td_->sponsored_message_manager_->view_sponsored_message(dialog_id, message_id);
+ }
+ continue;
+ }
+ return Status::Error(400, "Invalid message identifier");
}
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- return Status::Error(5, "Can't access the chat");
+ return Status::Error(400, "Can't access the chat");
+ }
+
+ MessageId max_thread_message_id;
+ if (top_thread_message_id != MessageId()) {
+ if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
+ return Status::Error(400, "Invalid message thread ID specified");
+ }
+ if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
+ return Status::Error(400, "There are no message threads in the chat");
+ }
+ const auto *top_m = get_message_force(d, top_thread_message_id, "view_messages 6");
+ if (top_m != nullptr && !top_m->reply_info.is_comment_) {
+ max_thread_message_id = top_m->reply_info.max_message_id_;
+ }
}
bool need_read = force_read || d->is_opened;
- bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
- MessageId max_incoming_message_id;
- MessageId max_message_id;
+ MessageId max_message_id; // max server or local viewed message_id
vector<MessageId> read_content_message_ids;
+ vector<MessageId> new_viewed_message_ids;
+ vector<MessageId> viewed_reaction_message_ids;
for (auto message_id : message_ids) {
- auto message = get_message_force(d, message_id);
- if (message != nullptr) {
- if (message_id.is_server() && message->views > 0) {
- d->pending_viewed_message_ids.insert(message_id);
+ if (!message_id.is_valid()) {
+ continue;
+ }
+
+ auto *m = get_message_force(d, message_id, "view_messages 1");
+ if (m != nullptr) {
+ if (m->message_id.is_server() && m->view_count > 0) {
+ d->pending_viewed_message_ids.insert(m->message_id);
}
- if (!message_id.is_yet_unsent() && message_id.get() > max_incoming_message_id.get()) {
- if (!message->is_outgoing && (message_id.is_server() || is_secret)) {
- max_incoming_message_id = message_id;
- }
- if (message_id.get() > max_message_id.get()) {
- max_message_id = message_id;
+ if (!m->message_id.is_yet_unsent() && m->message_id > max_message_id) {
+ max_message_id = m->message_id;
+ }
+
+ auto message_content_type = m->content->get_type();
+ if (message_content_type == MessageContentType::LiveLocation) {
+ on_message_live_location_viewed(d, m);
+ }
+
+ if (need_read && message_content_type != MessageContentType::VoiceNote &&
+ message_content_type != MessageContentType::VideoNote &&
+ update_message_contains_unread_mention(d, m, false, "view_messages")) {
+ CHECK(m->message_id.is_server());
+ read_content_message_ids.push_back(m->message_id);
+ on_message_changed(d, m, true, "view_messages");
+ }
+
+ if (need_read && remove_message_unread_reactions(d, m, "view_messages")) {
+ CHECK(m->message_id.is_server());
+ read_content_message_ids.push_back(m->message_id);
+ on_message_changed(d, m, true, "view_messages");
+ }
+
+ auto file_source_id = full_message_id_to_file_source_id_.get({dialog_id, m->message_id});
+ if (file_source_id.is_valid()) {
+ LOG(INFO) << "Have " << file_source_id << " for " << m->message_id;
+ CHECK(file_source_id.is_valid());
+ for (auto file_id : get_message_file_ids(m)) {
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.empty());
+ send_closure(td_->download_manager_actor_, &DownloadManager::update_file_viewed, file_view.get_main_file_id(),
+ file_source_id);
}
}
- if (need_read) {
- auto message_content_type = message->content->get_id();
- if (message_content_type != MessageVoiceNote::ID && message_content_type != MessageVideoNote::ID &&
- update_message_contains_unread_mention(d, message, false, "view_messages")) {
- CHECK(message_id.is_server());
- read_content_message_ids.push_back(message_id);
- on_message_changed(d, message, "view_messages");
+ if (m->message_id.is_server() && d->is_opened) {
+ auto &info = dialog_viewed_messages_[dialog_id];
+ if (info == nullptr) {
+ info = make_unique<ViewedMessagesInfo>();
}
+ auto &view_id = info->message_id_to_view_id[message_id];
+ if (view_id == 0) {
+ new_viewed_message_ids.push_back(message_id);
+ if (need_poll_message_reactions(d, m)) {
+ viewed_reaction_message_ids.push_back(message_id);
+ }
+ } else {
+ info->recently_viewed_messages.erase(view_id);
+ }
+ view_id = ++info->current_view_id;
+ info->recently_viewed_messages[view_id] = message_id;
+ }
+ } else if (!message_id.is_yet_unsent() && message_id > max_message_id) {
+ if (message_id <= d->max_notification_message_id || message_id <= d->last_new_message_id ||
+ message_id <= max_thread_message_id) {
+ max_message_id = message_id;
}
}
}
@@ -12387,71 +21403,152 @@ Status MessagesManager::view_messages(DialogId dialog_id, const vector<MessageId
d->increment_view_counter |= d->is_opened;
}
if (!read_content_message_ids.empty()) {
- read_message_contents_on_server(dialog_id, std::move(read_content_message_ids), 0);
+ read_message_contents_on_server(dialog_id, std::move(read_content_message_ids), 0, Auto());
}
-
- if (need_read && max_message_id.get() > d->last_read_inbox_message_id.get()) {
- MessageId last_read_message_id = d->last_message_id;
- if (!last_read_message_id.is_valid() || last_read_message_id.is_yet_unsent()) {
- if (max_message_id.get() <= d->last_new_message_id.get()) {
- last_read_message_id = d->last_new_message_id;
- } else {
- last_read_message_id = max_message_id;
- }
+ if (!new_viewed_message_ids.empty()) {
+ LOG(INFO) << "Have new viewed " << new_viewed_message_ids;
+ auto &info = dialog_viewed_messages_[dialog_id];
+ CHECK(info != nullptr);
+ CHECK(info->message_id_to_view_id.size() == info->recently_viewed_messages.size());
+ constexpr size_t MAX_RECENTLY_VIEWED_MESSAGES = 50;
+ while (info->recently_viewed_messages.size() > MAX_RECENTLY_VIEWED_MESSAGES) {
+ auto it = info->recently_viewed_messages.begin();
+ info->message_id_to_view_id.erase(it->second);
+ info->recently_viewed_messages.erase(it);
}
- read_history_inbox(d->dialog_id, last_read_message_id, -1, "view_messages");
+ if (!viewed_reaction_message_ids.empty()) {
+ queue_message_reactions_reload(dialog_id, viewed_reaction_message_ids);
+ }
+ }
+ if (td_->is_online() && dialog_viewed_messages_.count(dialog_id) != 0) {
+ update_viewed_messages_timeout_.add_timeout_in(dialog_id.get(), UPDATE_VIEWED_MESSAGES_PERIOD);
+ }
- if (d->last_new_message_id.is_valid()) {
- if (!d->is_last_read_inbox_message_id_inited) {
- // don't know last read inbox message, read history on the server just in case
- read_history_on_server(d->dialog_id, d->last_new_message_id, true, 0);
- } else {
- if (max_incoming_message_id.get() <= d->last_read_inbox_message_id.get()) {
- MessagesConstIterator p(d, d->last_message_id);
- while (*p != nullptr && ((*p)->is_outgoing || !((*p)->message_id.is_server() || is_secret)) &&
- (*p)->message_id.get() > d->last_read_inbox_message_id.get()) {
- --p;
+ if (!need_read) {
+ return Status::OK();
+ }
+
+ if (top_thread_message_id.is_valid() && max_message_id.is_valid()) {
+ MessageId prev_last_read_inbox_message_id;
+ max_thread_message_id = MessageId();
+ Message *top_m = get_message_force(d, top_thread_message_id, "view_messages 2");
+ if (top_m != nullptr && is_active_message_reply_info(dialog_id, top_m->reply_info)) {
+ prev_last_read_inbox_message_id = top_m->reply_info.last_read_inbox_message_id_;
+ if (top_m->reply_info.update_max_message_ids(MessageId(), max_message_id, MessageId())) {
+ on_message_reply_info_changed(dialog_id, top_m);
+ on_message_changed(d, top_m, true, "view_messages 3");
+ }
+ max_thread_message_id = top_m->reply_info.max_message_id_;
+
+ if (is_discussion_message(dialog_id, top_m)) {
+ auto linked_dialog_id = top_m->forward_info->from_dialog_id;
+ auto linked_d = get_dialog(linked_dialog_id);
+ CHECK(linked_d != nullptr);
+ CHECK(linked_dialog_id.get_type() == DialogType::Channel);
+ auto *linked_m = get_message_force(linked_d, top_m->forward_info->from_message_id, "view_messages 4");
+ if (linked_m != nullptr && is_active_message_reply_info(linked_dialog_id, linked_m->reply_info)) {
+ if (linked_m->reply_info.last_read_inbox_message_id_ < prev_last_read_inbox_message_id) {
+ prev_last_read_inbox_message_id = linked_m->reply_info.last_read_inbox_message_id_;
+ }
+ if (linked_m->reply_info.update_max_message_ids(MessageId(), max_message_id, MessageId())) {
+ on_message_reply_info_changed(linked_dialog_id, linked_m);
+ on_message_changed(linked_d, linked_m, true, "view_messages 5");
}
- if (*p != nullptr && !(*p)->is_outgoing && ((*p)->message_id.is_server() || is_secret)) {
- max_incoming_message_id = (*p)->message_id;
+ if (linked_m->reply_info.max_message_id_ > max_thread_message_id) {
+ max_thread_message_id = linked_m->reply_info.max_message_id_;
}
}
+ }
+ }
- if (max_incoming_message_id.get() > d->last_read_inbox_message_id.get()) {
- LOG_IF(ERROR, d->server_unread_count + d->local_unread_count == 0) << "Nave no unread messages";
- read_history_on_server(d->dialog_id, d->last_new_message_id, false,
- 0); // TODO can read messages only up to max_incoming_message_id
- } else {
- // can't find last incoming message, read history on the server just in case
- read_history_on_server(d->dialog_id, d->last_new_message_id, true, 0);
- }
+ if (max_message_id.get_prev_server_message_id().get() >
+ prev_last_read_inbox_message_id.get_prev_server_message_id().get()) {
+ read_message_thread_history_on_server(d, top_thread_message_id, max_message_id.get_prev_server_message_id(),
+ max_thread_message_id.get_prev_server_message_id());
+ }
+
+ return Status::OK();
+ }
+
+ if (max_message_id > d->last_read_inbox_message_id) {
+ const MessageId last_read_message_id = max_message_id;
+ const MessageId prev_last_read_inbox_message_id = d->last_read_inbox_message_id;
+ MessageId read_history_on_server_message_id;
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ if (last_read_message_id.get_prev_server_message_id().get() >
+ prev_last_read_inbox_message_id.get_prev_server_message_id().get()) {
+ read_history_on_server_message_id = last_read_message_id.get_prev_server_message_id();
}
+ } else {
+ if (last_read_message_id > prev_last_read_inbox_message_id) {
+ read_history_on_server_message_id = last_read_message_id;
+ }
+ }
+
+ if (read_history_on_server_message_id.is_valid()) {
+ // add dummy timeout to not try to repair unread_count in read_history_inbox before server request succeeds
+ // the timeout will be overwritten in the read_history_on_server call
+ pending_read_history_timeout_.add_timeout_in(dialog_id.get(), 0);
+ }
+ read_history_inbox(d->dialog_id, last_read_message_id, -1, "view_messages");
+ if (read_history_on_server_message_id.is_valid()) {
+ // call read_history_on_server after read_history_inbox to not have delay before request if all messages are read
+ read_history_on_server(d, read_history_on_server_message_id);
}
}
+ if (d->is_marked_as_unread) {
+ set_dialog_is_marked_as_unread(d, false);
+ }
return Status::OK();
}
+void MessagesManager::finish_get_message_views(DialogId dialog_id, const vector<MessageId> &message_ids) {
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ for (auto message_id : message_ids) {
+ auto *m = get_message(d, message_id);
+ if (m != nullptr) {
+ m->has_get_message_views_query = false;
+ m->need_view_counter_increment = false;
+ }
+ }
+}
+
+void MessagesManager::finish_get_message_extended_media(DialogId dialog_id, const vector<MessageId> &message_ids) {
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ for (auto message_id : message_ids) {
+ auto *m = get_message(d, message_id);
+ if (m != nullptr) {
+ m->has_get_extended_media_query = false;
+ }
+ }
+}
+
Status MessagesManager::open_message_content(FullMessageId full_message_id) {
auto dialog_id = full_message_id.get_dialog_id();
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "open_message_content");
if (d == nullptr) {
- return Status::Error(3, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
- auto message_id = full_message_id.get_message_id();
- auto message = get_message_force(d, message_id);
- if (message == nullptr) {
- return Status::Error(4, "Message not found");
+ auto *m = get_message_force(d, full_message_id.get_message_id(), "open_message_content");
+ if (m == nullptr) {
+ return Status::Error(400, "Message not found");
}
- if (message_id.is_yet_unsent() || message->is_outgoing) {
+ if (m->message_id.is_scheduled() || m->message_id.is_yet_unsent() || m->is_outgoing) {
return Status::OK();
}
- if (read_message_content(d, message, true, "open_message_content") &&
- (message_id.is_server() || dialog_id.get_type() == DialogType::SecretChat)) {
- read_message_contents_on_server(dialog_id, {message_id}, 0);
+ if (read_message_content(d, m, true, "open_message_content") &&
+ (m->message_id.is_server() || dialog_id.get_type() == DialogType::SecretChat)) {
+ read_message_contents_on_server(dialog_id, {m->message_id}, 0, Auto());
+ }
+
+ if (m->content->get_type() == MessageContentType::LiveLocation) {
+ on_message_live_location_viewed(d, m);
}
return Status::OK();
@@ -12475,30 +21572,26 @@ class MessagesManager::ReadMessageContentsOnServerLogEvent {
}
};
+uint64 MessagesManager::save_read_message_contents_on_server_log_event(DialogId dialog_id,
+ const vector<MessageId> &message_ids) {
+ ReadMessageContentsOnServerLogEvent log_event{dialog_id, message_ids};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadMessageContentsOnServer,
+ get_log_event_storer(log_event));
+}
+
void MessagesManager::read_message_contents_on_server(DialogId dialog_id, vector<MessageId> message_ids,
- uint64 logevent_id) {
+ uint64 log_event_id, Promise<Unit> &&promise,
+ bool skip_log_event) {
CHECK(!message_ids.empty());
LOG(INFO) << "Read contents of " << format::as_array(message_ids) << " in " << dialog_id << " on server";
- if (logevent_id == 0 && G()->parameters().use_message_db) {
- ReadMessageContentsOnServerLogEvent logevent;
- logevent.dialog_id_ = dialog_id;
- logevent.message_ids_ = message_ids;
-
- auto storer = LogEventStorerImpl<ReadMessageContentsOnServerLogEvent>(logevent);
- logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadMessageContentsOnServer, storer);
+ if (log_event_id == 0 && G()->parameters().use_message_db && !skip_log_event) {
+ log_event_id = save_read_message_contents_on_server_log_event(dialog_id, message_ids);
}
- Promise<> promise;
- if (logevent_id != 0) {
- promise = PromiseCreator::lambda([logevent_id](Result<Unit> result) mutable {
- if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
- }
- });
- }
+ auto new_promise = get_erase_log_event_promise(log_event_id, std::move(promise));
+ promise = std::move(new_promise); // to prevent self-move
switch (dialog_id.get_type()) {
case DialogType::User:
@@ -12509,60 +21602,162 @@ void MessagesManager::read_message_contents_on_server(DialogId dialog_id, vector
td_->create_handler<ReadChannelMessagesContentsQuery>(std::move(promise))
->send(dialog_id.get_channel_id(), std::move(message_ids));
break;
- case DialogType::SecretChat:
+ case DialogType::SecretChat: {
CHECK(message_ids.size() == 1);
- for (auto message_id : message_ids) {
- auto m = get_message_force({dialog_id, message_id});
- if (m != nullptr) {
- send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_open_message,
- dialog_id.get_secret_chat_id(), m->random_id, std::move(promise));
- }
+ auto m = get_message_force({dialog_id, message_ids[0]}, "read_message_contents_on_server");
+ if (m != nullptr) {
+ send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_open_message,
+ dialog_id.get_secret_chat_id(), m->random_id, std::move(promise));
+ } else {
+ promise.set_error(Status::Error(400, "Message not found"));
}
break;
+ }
case DialogType::None:
default:
UNREACHABLE();
}
}
+void MessagesManager::click_animated_emoji_message(FullMessageId full_message_id,
+ Promise<td_api::object_ptr<td_api::sticker>> &&promise) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "click_animated_emoji_message");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ auto message_id = get_persistent_message_id(d, full_message_id.get_message_id());
+ auto *m = get_message_force(d, message_id, "click_animated_emoji_message");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ if (m->message_id.is_scheduled() || dialog_id.get_type() != DialogType::User || !m->message_id.is_server()) {
+ return promise.set_value(nullptr);
+ }
+
+ get_message_content_animated_emoji_click_sticker(m->content.get(), full_message_id, td_, std::move(promise));
+}
+
void MessagesManager::open_dialog(Dialog *d) {
- if (d->is_opened || !have_input_peer(d->dialog_id, AccessRights::Read)) {
+ CHECK(!td_->auth_manager_->is_bot());
+ DialogId dialog_id = d->dialog_id;
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return;
+ }
+ recently_opened_dialogs_.add_dialog(dialog_id);
+ if (d->is_opened) {
return;
}
d->is_opened = true;
+ d->was_opened = true;
- auto min_message_id = MessageId(ServerMessageId(1)).get();
- if (d->last_message_id == MessageId() && d->last_read_outbox_message_id.get() < min_message_id &&
- d->messages != nullptr && d->messages->message_id.get() < min_message_id) {
+ auto min_message_id = MessageId(ServerMessageId(1));
+ if (d->last_message_id == MessageId() && d->last_read_outbox_message_id < min_message_id && d->messages != nullptr &&
+ d->messages->message_id < min_message_id) {
Message *m = d->messages.get();
while (m->right != nullptr) {
m = m->right.get();
}
- if (m->message_id.get() < min_message_id) {
- read_history_inbox(d->dialog_id, m->message_id, -1, "open_dialog");
+ if (m->message_id < min_message_id) {
+ read_history_inbox(dialog_id, m->message_id, -1, "open_dialog");
}
}
- LOG(INFO) << "Cancel unload timeout for " << d->dialog_id;
- pending_unload_dialog_timeout_.cancel_timeout(d->dialog_id.get());
+ if (d->has_unload_timeout) {
+ LOG(INFO) << "Cancel unload timeout for " << dialog_id;
+ pending_unload_dialog_timeout_.cancel_timeout(dialog_id.get());
+ d->has_unload_timeout = false;
+ }
- switch (d->dialog_id.get_type()) {
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ remove_new_secret_chat_notification(d, true);
+ }
+
+ get_dialog_pinned_message(dialog_id, Auto());
+
+ if (d->active_group_call_id.is_valid()) {
+ td_->group_call_manager_->reload_group_call(d->active_group_call_id, Auto());
+ }
+ if (d->need_drop_default_send_message_as_dialog_id) {
+ CHECK(d->default_send_message_as_dialog_id.is_valid());
+ d->need_drop_default_send_message_as_dialog_id = false;
+ d->default_send_message_as_dialog_id = DialogId();
+ LOG(INFO) << "Set message sender in " << d->dialog_id << " to " << d->default_send_message_as_dialog_id;
+ on_dialog_updated(dialog_id, "open_dialog");
+ send_update_chat_message_sender(d);
+ }
+
+ switch (dialog_id.get_type()) {
case DialogType::User:
break;
- case DialogType::Chat: {
- auto chat_id = d->dialog_id.get_chat_id();
- td_->contacts_manager_->get_chat_full(chat_id, Promise<Unit>());
+ case DialogType::Chat:
+ td_->contacts_manager_->repair_chat_participants(dialog_id.get_chat_id());
+ reget_dialog_action_bar(dialog_id, "open_dialog", false);
break;
- }
- case DialogType::Channel:
- get_channel_difference(d->dialog_id, d->pts, true, "open_dialog");
+ case DialogType::Channel: {
+ auto channel_id = dialog_id.get_channel_id();
+ if (!is_broadcast_channel(dialog_id)) {
+ auto participant_count = td_->contacts_manager_->get_channel_participant_count(channel_id);
+ if (participant_count < 195) { // include unknown participant_count
+ td_->contacts_manager_->get_channel_participants(
+ channel_id, td_api::make_object<td_api::supergroupMembersFilterRecent>(), string(), 0, 200, 200, Auto());
+ }
+ }
+ get_channel_difference(dialog_id, d->pts, true, "open_dialog");
+ reget_dialog_action_bar(dialog_id, "open_dialog", false);
+
+ if (td_->contacts_manager_->get_channel_has_linked_channel(channel_id)) {
+ auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(channel_id);
+ if (!linked_channel_id.is_valid()) {
+ // load linked_channel_id
+ send_closure_later(G()->contacts_manager(), &ContactsManager::load_channel_full, channel_id, false,
+ Promise<Unit>(), "open_dialog");
+ } else {
+ get_dialog_info_full(DialogId(linked_channel_id), Auto(), "open_dialog");
+ }
+ }
break;
- case DialogType::SecretChat:
+ }
+ case DialogType::SecretChat: {
+ // to repair dialog action bar
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (user_id.is_valid()) {
+ td_->contacts_manager_->reload_user_full(user_id, Promise<Unit>());
+ }
break;
+ }
case DialogType::None:
default:
UNREACHABLE();
}
+
+ if (!td_->auth_manager_->is_bot()) {
+ auto online_count_it = dialog_online_member_counts_.find(dialog_id);
+ if (online_count_it != dialog_online_member_counts_.end()) {
+ auto &info = online_count_it->second;
+ CHECK(!info.is_update_sent);
+ if (Time::now() - info.update_time < ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME) {
+ info.is_update_sent = true;
+ send_update_chat_online_member_count(dialog_id, info.online_member_count);
+ }
+ }
+
+ if (d->has_scheduled_database_messages && !d->is_has_scheduled_database_messages_checked) {
+ CHECK(G()->parameters().use_message_db);
+
+ LOG(INFO) << "Send check has_scheduled_database_messages request";
+ d->is_has_scheduled_database_messages_checked = true;
+ G()->td_db()->get_message_db_async()->get_scheduled_messages(
+ dialog_id, 1,
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](vector<MessageDbDialogMessage> messages) {
+ if (messages.empty()) {
+ send_closure(actor_id, &MessagesManager::set_dialog_has_scheduled_database_messages, dialog_id, false);
+ }
+ }));
+ }
+ }
}
void MessagesManager::close_dialog(Dialog *d) {
@@ -12571,36 +21766,53 @@ void MessagesManager::close_dialog(Dialog *d) {
}
d->is_opened = false;
- if (have_input_peer(d->dialog_id, AccessRights::Write)) {
- if (pending_draft_message_timeout_.has_timeout(d->dialog_id.get())) {
- pending_draft_message_timeout_.set_timeout_in(d->dialog_id.get(), 0.0);
+ auto dialog_id = d->dialog_id;
+ if (have_input_peer(dialog_id, AccessRights::Write)) {
+ if (pending_draft_message_timeout_.has_timeout(dialog_id.get())) {
+ pending_draft_message_timeout_.set_timeout_in(dialog_id.get(), 0.0);
}
} else {
- pending_draft_message_timeout_.cancel_timeout(d->dialog_id.get());
+ pending_draft_message_timeout_.cancel_timeout(dialog_id.get());
}
- if (have_input_peer(d->dialog_id, AccessRights::Read)) {
- if (pending_message_views_timeout_.has_timeout(d->dialog_id.get())) {
- pending_message_views_timeout_.set_timeout_in(d->dialog_id.get(), 0.0);
+ if (have_input_peer(dialog_id, AccessRights::Read)) {
+ if (pending_message_views_timeout_.has_timeout(dialog_id.get())) {
+ pending_message_views_timeout_.set_timeout_in(dialog_id.get(), 0.0);
+ }
+ if (pending_read_history_timeout_.has_timeout(dialog_id.get())) {
+ pending_read_history_timeout_.set_timeout_in(dialog_id.get(), 0.0);
}
} else {
- pending_message_views_timeout_.cancel_timeout(d->dialog_id.get());
+ pending_message_views_timeout_.cancel_timeout(dialog_id.get());
d->pending_viewed_message_ids.clear();
d->increment_view_counter = false;
+
+ pending_read_history_timeout_.cancel_timeout(dialog_id.get());
}
if (is_message_unload_enabled()) {
- LOG(INFO) << "Schedule unload of " << d->dialog_id;
- pending_unload_dialog_timeout_.set_timeout_in(d->dialog_id.get(), DIALOG_UNLOAD_DELAY);
+ CHECK(!d->has_unload_timeout);
+ pending_unload_dialog_timeout_.set_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d));
+ d->has_unload_timeout = true;
}
- switch (d->dialog_id.get_type()) {
+ dialog_viewed_messages_.erase(dialog_id);
+ update_viewed_messages_timeout_.cancel_timeout(dialog_id.get());
+
+ for (auto &it : d->pending_viewed_live_locations) {
+ auto live_location_task_id = it.second;
+ auto erased_count = viewed_live_location_tasks_.erase(live_location_task_id);
+ CHECK(erased_count > 0);
+ }
+ d->pending_viewed_live_locations.clear();
+
+ switch (dialog_id.get_type()) {
case DialogType::User:
break;
case DialogType::Chat:
break;
case DialogType::Channel:
- channel_get_difference_timeout_.cancel_timeout(d->dialog_id.get());
+ channel_get_difference_timeout_.cancel_timeout(dialog_id.get());
break;
case DialogType::SecretChat:
break;
@@ -12608,43 +21820,35 @@ void MessagesManager::close_dialog(Dialog *d) {
default:
UNREACHABLE();
}
-}
-tl_object_ptr<td_api::inputMessageText> MessagesManager::get_input_message_text_object(
- const InputMessageText &input_message_text) const {
- return make_tl_object<td_api::inputMessageText>(get_formatted_text_object(input_message_text.text),
- input_message_text.disable_web_page_preview,
- input_message_text.clear_draft);
-}
-
-tl_object_ptr<td_api::draftMessage> MessagesManager::get_draft_message_object(
- const unique_ptr<DraftMessage> &draft_message) const {
- if (draft_message == nullptr) {
- return nullptr;
+ if (!td_->auth_manager_->is_bot()) {
+ auto online_count_it = dialog_online_member_counts_.find(dialog_id);
+ if (online_count_it != dialog_online_member_counts_.end()) {
+ auto &info = online_count_it->second;
+ info.is_update_sent = false;
+ }
+ update_dialog_online_member_count_timeout_.set_timeout_in(dialog_id.get(), ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME);
}
- return make_tl_object<td_api::draftMessage>(draft_message->reply_to_message_id.get(),
- get_input_message_text_object(draft_message->input_message_text));
}
-tl_object_ptr<td_api::ChatType> MessagesManager::get_chat_type_object(DialogId dialog_id) const {
+td_api::object_ptr<td_api::ChatType> MessagesManager::get_chat_type_object(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
- return make_tl_object<td_api::chatTypePrivate>(
+ return td_api::make_object<td_api::chatTypePrivate>(
td_->contacts_manager_->get_user_id_object(dialog_id.get_user_id(), "chatTypePrivate"));
case DialogType::Chat:
- return make_tl_object<td_api::chatTypeBasicGroup>(
+ return td_api::make_object<td_api::chatTypeBasicGroup>(
td_->contacts_manager_->get_basic_group_id_object(dialog_id.get_chat_id(), "chatTypeBasicGroup"));
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
- auto channel_type = td_->contacts_manager_->get_channel_type(channel_id);
- return make_tl_object<td_api::chatTypeSupergroup>(
+ return td_api::make_object<td_api::chatTypeSupergroup>(
td_->contacts_manager_->get_supergroup_id_object(channel_id, "chatTypeSupergroup"),
- channel_type != ChannelType::Megagroup);
+ !td_->contacts_manager_->is_megagroup_channel(channel_id));
}
case DialogType::SecretChat: {
auto secret_chat_id = dialog_id.get_secret_chat_id();
auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id);
- return make_tl_object<td_api::chatTypeSecret>(
+ return td_api::make_object<td_api::chatTypeSecret>(
td_->contacts_manager_->get_secret_chat_id_object(secret_chat_id, "chatTypeSecret"),
td_->contacts_manager_->get_user_id_object(user_id, "chatTypeSecret"));
}
@@ -12655,297 +21859,364 @@ tl_object_ptr<td_api::ChatType> MessagesManager::get_chat_type_object(DialogId d
}
}
-tl_object_ptr<td_api::chat> MessagesManager::get_chat_object(const Dialog *d) {
- if (!td_->auth_manager_->is_bot()) {
- if (d->last_new_message_id.is_valid()) {
- // if (preloaded_dialogs_++ < 0) {
- // preload_older_messages(d, d->last_new_message_id);
- // }
+td_api::object_ptr<td_api::ChatActionBar> MessagesManager::get_chat_action_bar_object(const Dialog *d) const {
+ CHECK(d != nullptr);
+ auto dialog_type = d->dialog_id.get_type();
+ if (dialog_type == DialogType::SecretChat) {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
+ if (!user_id.is_valid()) {
+ return nullptr;
}
- if (!d->notification_settings.is_synchronized && d->dialog_id.get_type() != DialogType::SecretChat &&
- have_input_peer(d->dialog_id, AccessRights::Read)) {
- // asynchronously get dialog from the server
- send_get_dialog_query(d->dialog_id, Auto());
+ const Dialog *user_d = get_dialog(DialogId(user_id));
+ if (user_d == nullptr || user_d->action_bar == nullptr) {
+ return nullptr;
}
+ return user_d->action_bar->get_chat_action_bar_object(DialogType::User, d->folder_id != FolderId::archive());
}
- return make_tl_object<td_api::chat>(
- d->dialog_id.get(), get_chat_type_object(d->dialog_id), get_dialog_title(d->dialog_id),
- get_chat_photo_object(td_->file_manager_.get(), get_dialog_photo(d->dialog_id)),
- get_message_object(d->dialog_id, get_message(d, d->last_message_id)),
- DialogDate(d->order, d->dialog_id) <= last_dialog_date_ ? d->order : 0, d->pinned_order != DEFAULT_ORDER,
- can_report_dialog(d->dialog_id), d->server_unread_count + d->local_unread_count,
- d->last_read_inbox_message_id.get(), d->last_read_outbox_message_id.get(), d->unread_mention_count,
- get_notification_settings_object(&d->notification_settings), d->reply_markup_message_id.get(),
- get_draft_message_object(d->draft_message), d->client_data);
+ if (d->action_bar == nullptr) {
+ return nullptr;
+ }
+ return d->action_bar->get_chat_action_bar_object(dialog_type, false);
}
-tl_object_ptr<td_api::chat> MessagesManager::get_chat_object(DialogId dialog_id) {
- auto d = get_dialog(dialog_id);
+string MessagesManager::get_dialog_theme_name(const Dialog *d) const {
+ CHECK(d != nullptr);
+ if (d->dialog_id.get_type() == DialogType::SecretChat) {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
+ if (!user_id.is_valid()) {
+ return string();
+ }
+ d = get_dialog(DialogId(user_id));
+ if (d == nullptr) {
+ return string();
+ }
+ }
+ return d->theme_name;
+}
+
+td_api::object_ptr<td_api::chatJoinRequestsInfo> MessagesManager::get_chat_join_requests_info_object(
+ const Dialog *d) const {
+ if (d->pending_join_request_count == 0) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::chatJoinRequestsInfo>(
+ d->pending_join_request_count, td_->contacts_manager_->get_user_ids_object(d->pending_join_request_user_ids,
+ "get_chat_join_requests_info_object"));
+}
+
+td_api::object_ptr<td_api::videoChat> MessagesManager::get_video_chat_object(const Dialog *d) const {
+ auto active_group_call_id = td_->group_call_manager_->get_group_call_id(d->active_group_call_id, d->dialog_id);
+ auto default_participant_alias =
+ d->default_join_group_call_as_dialog_id.is_valid()
+ ? get_message_sender_object_const(td_, d->default_join_group_call_as_dialog_id, "get_video_chat_object")
+ : nullptr;
+ return make_tl_object<td_api::videoChat>(active_group_call_id.get(),
+ active_group_call_id.is_valid() ? !d->is_group_call_empty : false,
+ std::move(default_participant_alias));
+}
+
+td_api::object_ptr<td_api::MessageSender> MessagesManager::get_default_message_sender_object(const Dialog *d) const {
+ auto as_dialog_id = d->default_send_message_as_dialog_id;
+ return as_dialog_id.is_valid()
+ ? get_message_sender_object_const(td_, as_dialog_id, "get_default_message_sender_object")
+ : nullptr;
+}
+
+td_api::object_ptr<td_api::chat> MessagesManager::get_chat_object(const Dialog *d) const {
CHECK(d != nullptr);
- return get_chat_object(d);
+
+ auto chat_source = is_dialog_sponsored(d) ? sponsored_dialog_source_.get_chat_source_object() : nullptr;
+ auto can_delete = can_delete_dialog(d);
+ // TODO hide/show draft message when can_send_message(dialog_id) changes
+ auto draft_message = can_send_message(d->dialog_id).is_ok() ? get_draft_message_object(d->draft_message) : nullptr;
+ auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object();
+ return make_tl_object<td_api::chat>(
+ d->dialog_id.get(), get_chat_type_object(d->dialog_id), get_dialog_title(d->dialog_id),
+ get_chat_photo_info_object(td_->file_manager_.get(), get_dialog_photo(d->dialog_id)),
+ get_dialog_default_permissions(d->dialog_id).get_chat_permissions_object(),
+ get_message_object(d->dialog_id, get_message(d, d->last_message_id), "get_chat_object"),
+ get_chat_positions_object(d), get_default_message_sender_object(d),
+ get_dialog_has_protected_content(d->dialog_id), d->is_marked_as_unread, d->is_blocked,
+ get_dialog_has_scheduled_messages(d), can_delete.for_self_, can_delete.for_all_users_,
+ can_report_dialog(d->dialog_id), d->notification_settings.silent_send_message,
+ d->server_unread_count + d->local_unread_count, d->last_read_inbox_message_id.get(),
+ d->last_read_outbox_message_id.get(), d->unread_mention_count, d->unread_reaction_count,
+ get_chat_notification_settings_object(&d->notification_settings), std::move(available_reactions),
+ d->message_ttl.get_message_ttl_object(), get_dialog_theme_name(d), get_chat_action_bar_object(d),
+ get_video_chat_object(d), get_chat_join_requests_info_object(d), d->reply_markup_message_id.get(),
+ std::move(draft_message), d->client_data);
+}
+
+tl_object_ptr<td_api::chat> MessagesManager::get_chat_object(DialogId dialog_id) const {
+ return get_chat_object(get_dialog(dialog_id));
+}
+
+tl_object_ptr<td_api::chats> MessagesManager::get_chats_object(int32 total_count, const vector<DialogId> &dialog_ids) {
+ if (total_count == -1) {
+ total_count = narrow_cast<int32>(dialog_ids.size());
+ }
+ return td_api::make_object<td_api::chats>(total_count,
+ transform(dialog_ids, [](DialogId dialog_id) { return dialog_id.get(); }));
}
-tl_object_ptr<td_api::chats> MessagesManager::get_chats_object(const vector<DialogId> &dialogs) {
- return td_api::make_object<td_api::chats>(transform(dialogs, [](DialogId dialog_id) { return dialog_id.get(); }));
+tl_object_ptr<td_api::chats> MessagesManager::get_chats_object(const std::pair<int32, vector<DialogId>> &dialog_ids) {
+ return get_chats_object(dialog_ids.first, dialog_ids.second);
}
-tl_object_ptr<td_api::NotificationSettingsScope> MessagesManager::get_notification_settings_scope_object(
- NotificationSettingsScope scope) {
- switch (scope) {
- case NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS:
- return make_tl_object<td_api::notificationSettingsScopePrivateChats>();
- case NOTIFICATION_SETTINGS_FOR_GROUP_CHATS:
- return make_tl_object<td_api::notificationSettingsScopeBasicGroupChats>();
- case NOTIFICATION_SETTINGS_FOR_ALL_CHATS:
- return make_tl_object<td_api::notificationSettingsScopeAllChats>();
- default:
- return make_tl_object<td_api::notificationSettingsScopeChat>(scope);
+td_api::object_ptr<td_api::chatFilter> MessagesManager::get_chat_filter_object(DialogFilterId dialog_filter_id) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto filter = get_dialog_filter(dialog_filter_id);
+ if (filter == nullptr) {
+ return nullptr;
}
+
+ return get_chat_filter_object(filter);
+}
+
+td_api::object_ptr<td_api::chatFilter> MessagesManager::get_chat_filter_object(const DialogFilter *filter) const {
+ auto get_chat_ids = [this,
+ dialog_filter_id = filter->dialog_filter_id](const vector<InputDialogId> &input_dialog_ids) {
+ vector<int64> chat_ids;
+ chat_ids.reserve(input_dialog_ids.size());
+ for (auto &input_dialog_id : input_dialog_ids) {
+ auto dialog_id = input_dialog_id.get_dialog_id();
+ const Dialog *d = get_dialog(dialog_id);
+ if (d != nullptr) {
+ if (d->order != DEFAULT_ORDER) {
+ chat_ids.push_back(dialog_id.get());
+ } else {
+ LOG(INFO) << "Skip nonjoined " << dialog_id << " from " << dialog_filter_id;
+ }
+ } else {
+ LOG(ERROR) << "Can't find " << dialog_id << " from " << dialog_filter_id;
+ }
+ }
+ return chat_ids;
+ };
+ return td_api::make_object<td_api::chatFilter>(
+ filter->title, filter->get_icon_name(), get_chat_ids(filter->pinned_dialog_ids),
+ get_chat_ids(filter->included_dialog_ids), get_chat_ids(filter->excluded_dialog_ids), filter->exclude_muted,
+ filter->exclude_read, filter->exclude_archived, filter->include_contacts, filter->include_non_contacts,
+ filter->include_bots, filter->include_groups, filter->include_channels);
}
-tl_object_ptr<td_api::notificationSettings> MessagesManager::get_notification_settings_object(
- const NotificationSettings *notification_settings) {
- return make_tl_object<td_api::notificationSettings>(max(0, notification_settings->mute_until - G()->unix_time()),
- notification_settings->sound,
- notification_settings->show_preview);
+std::pair<bool, int32> MessagesManager::get_dialog_mute_until(DialogId dialog_id, const Dialog *d) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (d == nullptr || !d->notification_settings.is_synchronized) {
+ auto scope = get_dialog_notification_setting_scope(dialog_id);
+ return {false, td_->notification_settings_manager_->get_scope_mute_until(scope)};
+ }
+
+ return {d->notification_settings.is_use_default_fixed, get_dialog_mute_until(d)};
}
-const NotificationSettings *MessagesManager::get_dialog_notification_settings(const Dialog *d,
- DialogId dialog_id) const {
- if (d != nullptr &&
- d->notification_settings.is_synchronized) { // TODO this is wrong check for initialized notification settings
- return &d->notification_settings;
+int64 MessagesManager::get_dialog_notification_ringtone_id(DialogId dialog_id, const Dialog *d) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (d == nullptr || !d->notification_settings.is_synchronized || d->notification_settings.use_default_mute_until) {
+ auto scope = get_dialog_notification_setting_scope(dialog_id);
+ return get_notification_sound_ringtone_id(td_->notification_settings_manager_->get_scope_notification_sound(scope));
}
- const NotificationSettings *notification_settings = nullptr;
+ return get_notification_sound_ringtone_id(d->notification_settings.sound);
+}
+
+NotificationSettingsScope MessagesManager::get_dialog_notification_setting_scope(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::SecretChat:
- notification_settings = &users_notification_settings_;
- break;
+ return NotificationSettingsScope::Private;
case DialogType::Chat:
- notification_settings = &chats_notification_settings_;
- break;
- case DialogType::Channel: {
- ChannelId channel_id = dialog_id.get_channel_id();
- auto channel_type = td_->contacts_manager_->get_channel_type(channel_id);
- if (channel_type == ChannelType::Megagroup) {
- return nullptr;
- }
- notification_settings = &chats_notification_settings_;
- break;
- }
+ return NotificationSettingsScope::Group;
+ case DialogType::Channel:
+ return is_broadcast_channel(dialog_id) ? NotificationSettingsScope::Channel : NotificationSettingsScope::Group;
case DialogType::None:
default:
UNREACHABLE();
- return nullptr;
+ return NotificationSettingsScope::Private;
}
- if (notification_settings->is_synchronized) {
- return notification_settings;
- }
- return &dialogs_notification_settings_;
}
-const NotificationSettings *MessagesManager::get_notification_settings(NotificationSettingsScope scope,
- Promise<Unit> &&promise) {
- const NotificationSettings *notification_settings = get_notification_settings(scope, true);
- if (notification_settings == nullptr) {
- promise.set_error(Status::Error(3, "Chat not found"));
- return nullptr;
+vector<DialogId> MessagesManager::get_dialog_notification_settings_exceptions(NotificationSettingsScope scope,
+ bool filter_scope, bool compare_sound,
+ bool force, Promise<Unit> &&promise) {
+ CHECK(!td_->auth_manager_->is_bot());
+ bool have_all_dialogs = true;
+ for (const auto &list : dialog_folders_) {
+ if (list.second.folder_last_dialog_date_ != MAX_DIALOG_DATE) {
+ have_all_dialogs = false;
+ }
}
- if (!notification_settings->is_synchronized && get_notification_settings(scope, false) != nullptr &&
- !td_->auth_manager_->is_bot()) {
- td_->create_handler<GetNotifySettingsQuery>(std::move(promise))->send(scope);
- return nullptr;
- }
+ if (have_all_dialogs || force) {
+ vector<DialogDate> ordered_dialogs;
+ auto my_dialog_id = get_my_dialog_id();
+ for (const auto &list : dialog_folders_) {
+ for (const auto &dialog_date : list.second.ordered_dialogs_) {
+ auto dialog_id = dialog_date.get_dialog_id();
+ if (filter_scope && get_dialog_notification_setting_scope(dialog_id) != scope) {
+ continue;
+ }
+ if (dialog_id == my_dialog_id) {
+ continue;
+ }
- promise.set_value(Unit());
- return notification_settings;
-}
-
-NotificationSettings *MessagesManager::get_notification_settings(NotificationSettingsScope scope, bool force) {
- switch (scope) {
- case NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS:
- return &users_notification_settings_;
- case NOTIFICATION_SETTINGS_FOR_GROUP_CHATS:
- return &chats_notification_settings_;
- case NOTIFICATION_SETTINGS_FOR_ALL_CHATS:
- return &dialogs_notification_settings_;
- default: {
- DialogId dialog_id(scope);
- auto dialog = get_dialog_force(dialog_id);
- if (dialog == nullptr) {
- return nullptr;
- }
- if (!force && !have_input_peer(dialog_id, AccessRights::Read)) {
- return nullptr;
+ const Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ LOG_CHECK(d->folder_id == list.first)
+ << list.first << ' ' << dialog_id << ' ' << d->folder_id << ' ' << d->order;
+ if (d->order == DEFAULT_ORDER) {
+ break;
+ }
+ if (are_default_dialog_notification_settings(d->notification_settings, compare_sound)) {
+ continue;
+ }
+ if (is_dialog_message_notification_disabled(dialog_id, std::numeric_limits<int32>::max())) {
+ continue;
+ }
+ ordered_dialogs.push_back(DialogDate(get_dialog_base_order(d), dialog_id));
}
- return &dialog->notification_settings;
}
- }
-}
+ std::sort(ordered_dialogs.begin(), ordered_dialogs.end());
-tl_object_ptr<telegram_api::InputNotifyPeer> MessagesManager::get_input_notify_peer(
- NotificationSettingsScope scope) const {
- switch (scope) {
- case NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS:
- return make_tl_object<telegram_api::inputNotifyUsers>();
- case NOTIFICATION_SETTINGS_FOR_GROUP_CHATS:
- return make_tl_object<telegram_api::inputNotifyChats>();
- case NOTIFICATION_SETTINGS_FOR_ALL_CHATS:
- return make_tl_object<telegram_api::inputNotifyAll>();
- default: {
- DialogId dialog_id(scope);
- if (get_dialog(dialog_id) == nullptr) {
- return nullptr;
- }
- auto input_peer = get_input_peer(dialog_id, AccessRights::Read);
- if (input_peer == nullptr) {
- return nullptr;
- }
- return make_tl_object<telegram_api::inputNotifyPeer>(std::move(input_peer));
+ vector<DialogId> result;
+ for (auto &dialog_date : ordered_dialogs) {
+ CHECK(result.empty() || result.back() != dialog_date.get_dialog_id());
+ result.push_back(dialog_date.get_dialog_id());
}
+ promise.set_value(Unit());
+ return result;
}
+
+ for (const auto &folder : dialog_folders_) {
+ load_folder_dialog_list(folder.first, MAX_GET_DIALOGS, true);
+ }
+
+ td_->notification_settings_manager_->get_notify_settings_exceptions(scope, filter_scope, compare_sound,
+ std::move(promise));
+ return {};
}
-Status MessagesManager::set_notification_settings(NotificationSettingsScope scope,
- tl_object_ptr<td_api::notificationSettings> &&notification_settings) {
- if (notification_settings == nullptr) {
- return Status::Error(400, "New notification settings must not be empty");
+DialogNotificationSettings *MessagesManager::get_dialog_notification_settings(DialogId dialog_id, bool force) {
+ Dialog *d = get_dialog_force(dialog_id, "get_dialog_notification_settings");
+ if (d == nullptr) {
+ return nullptr;
}
- if (!clean_input_string(notification_settings->sound_)) {
- return Status::Error(400, "Notification settings sound must be encoded in UTF-8");
+ if (!force && !have_input_peer(dialog_id, AccessRights::Read)) {
+ return nullptr;
}
+ return &d->notification_settings;
+}
- auto current_settings = get_notification_settings(scope, false);
+Status MessagesManager::set_dialog_notification_settings(
+ DialogId dialog_id, tl_object_ptr<td_api::chatNotificationSettings> &&notification_settings) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto current_settings = get_dialog_notification_settings(dialog_id, false);
if (current_settings == nullptr) {
- return Status::Error(6, "Wrong chat identifier specified");
+ return Status::Error(400, "Wrong chat identifier specified");
}
-
- int32 current_time = G()->unix_time();
- if (notification_settings->mute_for_ > std::numeric_limits<int32>::max() - current_time) {
- notification_settings->mute_for_ = std::numeric_limits<int32>::max() - current_time;
+ if (dialog_id == get_my_dialog_id()) {
+ return Status::Error(400, "Notification settings of the Saved Messages chat can't be changed");
}
- int32 mute_until;
- if (notification_settings->mute_for_ <= 0) {
- mute_until = 0;
- } else {
- mute_until = notification_settings->mute_for_ + current_time;
+ TRY_RESULT(new_settings, ::td::get_dialog_notification_settings(std::move(notification_settings),
+ current_settings->silent_send_message));
+ if (is_notification_sound_default(current_settings->sound) && is_notification_sound_default(new_settings.sound)) {
+ new_settings.sound = dup_notification_sound(current_settings->sound);
}
-
- NotificationSettings new_settings(mute_until, std::move(notification_settings->sound_),
- notification_settings->show_preview_, current_settings->silent_send_message);
-
- if (update_notification_settings(scope, current_settings, new_settings)) {
- td_->create_handler<UpdateNotifySettingsQuery>()->send(scope, new_settings);
+ if (update_dialog_notification_settings(dialog_id, current_settings, std::move(new_settings))) {
+ update_dialog_notification_settings_on_server(dialog_id, false);
}
return Status::OK();
}
void MessagesManager::reset_all_notification_settings() {
- NotificationSettings new_settings(0, "default", true, false);
- NotificationSettings new_megagroup_settings(std::numeric_limits<int32>::max(), "default", true, false);
+ CHECK(!td_->auth_manager_->is_bot());
- update_notification_settings(NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS, &users_notification_settings_, new_settings);
- update_notification_settings(NOTIFICATION_SETTINGS_FOR_GROUP_CHATS, &chats_notification_settings_, new_settings);
- update_notification_settings(NOTIFICATION_SETTINGS_FOR_ALL_CHATS, &dialogs_notification_settings_, new_settings);
+ dialogs_.foreach([&](const DialogId &dialog_id, unique_ptr<Dialog> &dialog) {
+ DialogNotificationSettings new_dialog_settings;
+ new_dialog_settings.is_synchronized = true;
+ Dialog *d = dialog.get();
+ update_dialog_notification_settings(dialog_id, &d->notification_settings, std::move(new_dialog_settings));
+ });
- for (auto &dialog : dialogs_) {
- Dialog *d = dialog.second.get();
- auto dialog_id = d->dialog_id;
- bool is_megagroup = dialog_id.get_type() == DialogType::Channel &&
- td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == ChannelType::Megagroup;
+ td_->notification_settings_manager_->reset_scope_notification_settings();
- if (is_megagroup) {
- update_notification_settings(NotificationSettingsScope(dialog_id.get()), &d->notification_settings,
- new_megagroup_settings);
- } else {
- update_notification_settings(NotificationSettingsScope(dialog_id.get()), &d->notification_settings, new_settings);
- }
- }
- td_->create_handler<ResetNotifySettingsQuery>()->send();
+ reset_all_notification_settings_on_server(0);
}
-unique_ptr<DraftMessage> MessagesManager::get_draft_message(
- ContactsManager *contacts_manager, tl_object_ptr<telegram_api::DraftMessage> &&draft_message_ptr) {
- if (draft_message_ptr == nullptr) {
- return nullptr;
+class MessagesManager::ResetAllNotificationSettingsOnServerLogEvent {
+ public:
+ template <class StorerT>
+ void store(StorerT &storer) const {
}
- auto constructor_id = draft_message_ptr->get_id();
- switch (constructor_id) {
- case telegram_api::draftMessageEmpty::ID:
- return nullptr;
- case telegram_api::draftMessage::ID: {
- auto draft = move_tl_object_as<telegram_api::draftMessage>(draft_message_ptr);
- auto flags = draft->flags_;
- auto result = make_unique<DraftMessage>();
- result->date = draft->date_;
- if ((flags & SEND_MESSAGE_FLAG_IS_REPLY) != 0) {
- result->reply_to_message_id = MessageId(ServerMessageId(draft->reply_to_msg_id_));
- if (!result->reply_to_message_id.is_valid()) {
- LOG(ERROR) << "Receive " << result->reply_to_message_id << " as reply_to_message_id in the draft";
- result->reply_to_message_id = MessageId();
- }
- }
- auto entities = get_message_entities(contacts_manager, std::move(draft->entities_), "draftMessage");
- auto status = fix_formatted_text(draft->message_, entities, true, true, true, true);
- if (status.is_error()) {
- LOG(ERROR) << "Receive error " << status << " while parsing draft " << draft->message_;
- if (!clean_input_string(draft->message_)) {
- draft->message_.clear();
- }
- entities.clear();
- }
- result->input_message_text.text = FormattedText{std::move(draft->message_), std::move(entities)};
- result->input_message_text.disable_web_page_preview = (flags & SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW) != 0;
- result->input_message_text.clear_draft = false;
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ }
+};
- return result;
- }
- default:
- UNREACHABLE();
- return nullptr;
+uint64 MessagesManager::save_reset_all_notification_settings_on_server_log_event() {
+ ResetAllNotificationSettingsOnServerLogEvent log_event;
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ResetAllNotificationSettingsOnServer,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::reset_all_notification_settings_on_server(uint64 log_event_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (log_event_id == 0) {
+ log_event_id = save_reset_all_notification_settings_on_server_log_event();
}
+
+ LOG(INFO) << "Reset all notification settings";
+ td_->notification_settings_manager_->reset_notify_settings(get_erase_log_event_promise(log_event_id));
}
tl_object_ptr<td_api::messages> MessagesManager::get_dialog_history(DialogId dialog_id, MessageId from_message_id,
int32 offset, int32 limit, int left_tries,
bool only_local, Promise<Unit> &&promise) {
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return nullptr;
}
if (limit > MAX_GET_HISTORY) {
limit = MAX_GET_HISTORY;
}
- if (limit <= -offset) {
- promise.set_error(Status::Error(5, "Parameter limit must be greater than -offset"));
+ if (offset > 0) {
+ promise.set_error(Status::Error(400, "Parameter offset must be non-positive"));
return nullptr;
}
- if (offset > 0) {
- promise.set_error(Status::Error(5, "Parameter offset must be non-positive"));
+ if (offset <= -MAX_GET_HISTORY) {
+ promise.set_error(Status::Error(400, "Parameter offset must be greater than -100"));
+ return nullptr;
+ }
+ if (offset < -limit) {
+ promise.set_error(Status::Error(400, "Parameter offset must be greater than or equal to -limit"));
return nullptr;
}
+ bool is_limit_increased = false;
+ if (limit == -offset) {
+ limit++;
+ is_limit_increased = true;
+ }
+ CHECK(0 < limit && limit <= MAX_GET_HISTORY);
+ CHECK(-limit < offset && offset <= 0);
if (from_message_id == MessageId() || from_message_id.get() > MessageId::max().get()) {
from_message_id = MessageId::max();
}
if (!from_message_id.is_valid()) {
- promise.set_error(Status::Error(3, "Invalid value of parameter from_message_id specified"));
+ promise.set_error(Status::Error(400, "Invalid value of parameter from_message_id specified"));
return nullptr;
}
- const Dialog *d = get_dialog_force(dialog_id);
+ const Dialog *d = get_dialog_force(dialog_id, "get_dialog_history");
if (d == nullptr) {
- promise.set_error(Status::Error(6, "Chat not found"));
+ promise.set_error(Status::Error(400, "Chat not found"));
return nullptr;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- promise.set_error(Status::Error(5, "Can't access the chat"));
+ promise.set_error(Status::Error(400, "Can't access the chat"));
return nullptr;
}
@@ -12953,12 +22224,13 @@ tl_object_ptr<td_api::messages> MessagesManager::get_dialog_history(DialogId dia
<< " with offset " << offset << " and limit " << limit << ", " << left_tries
<< " tries left. Last read inbox message is " << d->last_read_inbox_message_id
<< ", last read outbox message is " << d->last_read_outbox_message_id
- << ", have_full_history = " << d->have_full_history;
+ << ", have_full_history = " << d->have_full_history
+ << ", have_full_history_source = " << d->have_full_history_source;
MessagesConstIterator p(d, from_message_id);
LOG(DEBUG) << "Iterator points to " << (*p ? (*p)->message_id : MessageId());
- bool from_the_end = (d->last_message_id != MessageId() && from_message_id.get() > d->last_message_id.get()) ||
- from_message_id.get() >= MessageId::max().get();
+ bool from_the_end = (d->last_message_id != MessageId() && from_message_id > d->last_message_id) ||
+ from_message_id >= MessageId::max();
if (from_the_end) {
limit += offset;
@@ -12971,20 +22243,19 @@ tl_object_ptr<td_api::messages> MessagesManager::get_dialog_history(DialogId dia
if (*p == nullptr) {
// there is no gap if from_message_id is less than first message in the dialog
if (left_tries == 0 && d->messages != nullptr && offset < 0) {
- Message *cur = d->messages.get();
+ const Message *cur = d->messages.get();
while (cur->left != nullptr) {
cur = cur->left.get();
}
- CHECK(cur->message_id.get() > from_message_id.get());
+ CHECK(cur->message_id > from_message_id);
from_message_id = cur->message_id;
p = MessagesConstIterator(d, from_message_id);
} else {
have_a_gap = true;
}
} else if ((*p)->message_id != from_message_id) {
- CHECK((*p)->message_id.get() < from_message_id.get());
- if (!(*p)->have_next &&
- (d->last_message_id == MessageId() || (*p)->message_id.get() < d->last_message_id.get())) {
+ CHECK((*p)->message_id < from_message_id);
+ if (!(*p)->have_next && (d->last_message_id == MessageId() || (*p)->message_id < d->last_message_id)) {
have_a_gap = true;
}
}
@@ -13009,7 +22280,7 @@ tl_object_ptr<td_api::messages> MessagesManager::get_dialog_history(DialogId dia
}
}
- if (offset < 0 && ((d->last_message_id != MessageId() && from_message_id.get() >= d->last_message_id.get()) ||
+ if (offset < 0 && ((d->last_message_id != MessageId() && from_message_id >= d->last_message_id) ||
(!have_a_gap && left_tries == 0))) {
CHECK(!have_a_gap);
limit += offset;
@@ -13026,31 +22297,39 @@ tl_object_ptr<td_api::messages> MessagesManager::get_dialog_history(DialogId dia
<< ", offset = " << offset << ", limit = " << limit << ", from_the_end = " << from_the_end;
vector<tl_object_ptr<td_api::message>> messages;
if (*p != nullptr && offset == 0) {
- while (*p != nullptr && limit-- > 0) {
- messages.push_back(get_message_object(dialog_id, *p));
+ while (*p != nullptr && messages.size() < static_cast<size_t>(limit)) {
+ messages.push_back(get_message_object(dialog_id, *p, "get_dialog_history"));
from_message_id = (*p)->message_id;
+ from_the_end = false;
--p;
}
}
- if (messages.size()) {
+ if (!messages.empty()) {
// maybe need some messages
CHECK(offset == 0);
preload_newer_messages(d, MessageId(messages[0]->id_));
preload_older_messages(d, MessageId(messages.back()->id_));
- } else if (limit > 0 && (/*d->first_remote_message_id != -1 && */ left_tries != 0)) {
- // there can be more messages on the server, need to load them
+ } else if (messages.size() < static_cast<size_t>(limit) && left_tries != 0 &&
+ !(d->is_empty && d->have_full_history && left_tries < 3)) {
+ // there can be more messages in the database or on the server, need to load them
if (from_the_end) {
from_message_id = MessageId();
}
- send_closure_later(actor_id(this), &MessagesManager::load_messages, d->dialog_id, from_message_id, offset, limit,
- left_tries, only_local, std::move(promise));
+ send_closure_later(actor_id(this), &MessagesManager::load_messages, dialog_id, from_message_id, offset,
+ limit - static_cast<int32>(messages.size()), left_tries, only_local, std::move(promise));
return nullptr;
}
+ LOG(INFO) << "Have " << messages.size() << " messages out of requested "
+ << (is_limit_increased ? "increased " : "exact ") << limit;
+ if (is_limit_increased && static_cast<size_t>(limit) == messages.size()) {
+ messages.pop_back();
+ }
+
LOG(INFO) << "Return " << messages.size() << " messages in result to getChatHistory";
- promise.set_value(Unit()); // can send some messages
- return get_messages_object(-1, std::move(messages)); // TODO return real total_count of messages in the dialog
+ promise.set_value(Unit()); // can return some messages
+ return get_messages_object(-1, std::move(messages), false); // TODO return real total_count of messages in the dialog
}
class MessagesManager::ReadHistoryOnServerLogEvent {
@@ -13071,31 +22350,169 @@ class MessagesManager::ReadHistoryOnServerLogEvent {
}
};
-void MessagesManager::read_history_on_server(DialogId dialog_id, MessageId max_message_id, bool allow_error,
- uint64 logevent_id) {
+class MessagesManager::ReadHistoryInSecretChatLogEvent {
+ public:
+ DialogId dialog_id_;
+ int32 max_date_ = 0;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
+ td::store(max_date_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(dialog_id_, parser);
+ td::parse(max_date_, parser);
+ }
+};
+
+class MessagesManager::ReadMessageThreadHistoryOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+ MessageId top_thread_message_id_;
+ MessageId max_message_id_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
+ td::store(top_thread_message_id_, storer);
+ td::store(max_message_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(dialog_id_, parser);
+ td::parse(top_thread_message_id_, parser);
+ td::parse(max_message_id_, parser);
+ }
+};
+
+void MessagesManager::read_history_on_server(Dialog *d, MessageId max_message_id) {
if (td_->auth_manager_->is_bot()) {
return;
}
- LOG(INFO) << "Read history in " << dialog_id << " on server up to " << max_message_id;
- if (logevent_id == 0 && G()->parameters().use_message_db) {
- ReadHistoryOnServerLogEvent logevent;
- logevent.dialog_id_ = dialog_id;
- logevent.max_message_id_ = max_message_id;
+ CHECK(d != nullptr);
+ CHECK(!max_message_id.is_scheduled());
+
+ auto dialog_id = d->dialog_id;
+ bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
+ bool need_delay = d->is_opened && !is_secret &&
+ (d->server_unread_count > 0 || (!need_unread_counter(d->order) && d->last_message_id.is_valid() &&
+ max_message_id < d->last_message_id));
+ LOG(INFO) << "Read history in " << dialog_id << " on server up to " << max_message_id << " with"
+ << (need_delay ? "" : "out") << " delay";
- auto storer = LogEventStorerImpl<ReadHistoryOnServerLogEvent>(logevent);
- logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ReadHistoryOnServer, storer);
+ if (is_secret) {
+ auto *m = get_message_force(d, max_message_id, "read_history_on_server");
+ if (m == nullptr) {
+ LOG(ERROR) << "Failed to read history in " << dialog_id << " up to " << max_message_id;
+ return;
+ }
+
+ ReadHistoryInSecretChatLogEvent log_event;
+ log_event.dialog_id_ = dialog_id;
+ log_event.max_date_ = m->date;
+ add_log_event(d->read_history_log_event_ids[0], get_log_event_storer(log_event),
+ LogEvent::HandlerType::ReadHistoryInSecretChat, "read history");
+
+ d->last_read_inbox_message_date = m->date;
+ } else if (G()->parameters().use_message_db) {
+ ReadHistoryOnServerLogEvent log_event;
+ log_event.dialog_id_ = dialog_id;
+ log_event.max_message_id_ = max_message_id;
+ add_log_event(d->read_history_log_event_ids[0], get_log_event_storer(log_event),
+ LogEvent::HandlerType::ReadHistoryOnServer, "read history");
+ }
+
+ d->updated_read_history_message_ids.insert(MessageId());
+
+ pending_read_history_timeout_.set_timeout_in(dialog_id.get(), need_delay ? MIN_READ_HISTORY_DELAY : 0);
+}
+
+void MessagesManager::read_message_thread_history_on_server(Dialog *d, MessageId top_thread_message_id,
+ MessageId max_message_id, MessageId last_message_id) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
}
- Promise<> promise;
- if (logevent_id != 0) {
- promise = PromiseCreator::lambda([logevent_id](Result<Unit> result) mutable {
+ CHECK(d != nullptr);
+ CHECK(top_thread_message_id.is_valid());
+ CHECK(top_thread_message_id.is_server());
+ CHECK(max_message_id.is_server());
+
+ auto dialog_id = d->dialog_id;
+ LOG(INFO) << "Read history in thread of " << top_thread_message_id << " in " << dialog_id << " on server up to "
+ << max_message_id;
+
+ if (G()->parameters().use_message_db) {
+ ReadMessageThreadHistoryOnServerLogEvent log_event;
+ log_event.dialog_id_ = dialog_id;
+ log_event.top_thread_message_id_ = top_thread_message_id;
+ log_event.max_message_id_ = max_message_id;
+ add_log_event(d->read_history_log_event_ids[top_thread_message_id.get()], get_log_event_storer(log_event),
+ LogEvent::HandlerType::ReadMessageThreadHistoryOnServer, "read history");
+ }
+
+ d->updated_read_history_message_ids.insert(top_thread_message_id);
+
+ bool need_delay = d->is_opened && last_message_id.is_valid() && max_message_id < last_message_id;
+ pending_read_history_timeout_.set_timeout_in(dialog_id.get(), need_delay ? MIN_READ_HISTORY_DELAY : 0);
+}
+
+void MessagesManager::do_read_history_on_server(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ for (auto top_thread_message_id : d->updated_read_history_message_ids) {
+ if (!top_thread_message_id.is_valid()) {
+ read_history_on_server_impl(d, MessageId());
+ } else {
+ read_message_thread_history_on_server_impl(d, top_thread_message_id, MessageId());
+ }
+ }
+ reset_to_empty(d->updated_read_history_message_ids);
+}
+
+void MessagesManager::read_history_on_server_impl(Dialog *d, MessageId max_message_id) {
+ CHECK(d != nullptr);
+ auto dialog_id = d->dialog_id;
+
+ {
+ auto message_id = d->last_read_inbox_message_id;
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ message_id = message_id.get_prev_server_message_id();
+ }
+ if (message_id > max_message_id) {
+ max_message_id = message_id;
+ }
+ }
+
+ Promise<Unit> promise;
+ if (d->read_history_log_event_ids[0].log_event_id != 0) {
+ d->read_history_log_event_ids[0].generation++;
+ promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id,
+ generation = d->read_history_log_event_ids[0].generation](Result<Unit> result) {
if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
+ send_closure(actor_id, &MessagesManager::on_read_history_finished, dialog_id, MessageId(), generation);
}
});
}
+ if (d->need_repair_server_unread_count && need_unread_counter(d->order)) {
+ repair_server_unread_count(dialog_id, d->server_unread_count, "read_history_on_server_impl");
+ }
+ if (!max_message_id.is_valid() || !have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_value(Unit());
+ }
+
+ LOG(INFO) << "Send read history request in " << dialog_id << " up to " << max_message_id;
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
@@ -13103,16 +22520,22 @@ void MessagesManager::read_history_on_server(DialogId dialog_id, MessageId max_m
break;
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
- td_->create_handler<ReadChannelHistoryQuery>(std::move(promise))->send(channel_id, max_message_id, allow_error);
+ td_->create_handler<ReadChannelHistoryQuery>(std::move(promise))->send(channel_id, max_message_id);
break;
}
case DialogType::SecretChat: {
auto secret_chat_id = dialog_id.get_secret_chat_id();
- auto *message = get_message_force(FullMessageId(dialog_id, max_message_id));
- if (message != nullptr) {
- send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_read_history, secret_chat_id, message->date,
- std::move(promise));
+ auto date = d->last_read_inbox_message_date;
+ auto *m = get_message_force(d, max_message_id, "read_history_on_server_impl");
+ if (m != nullptr && m->date > date) {
+ date = m->date;
}
+ if (date == 0) {
+ LOG(ERROR) << "Don't know last read inbox message date in " << dialog_id;
+ return promise.set_value(Unit());
+ }
+ send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_read_history, secret_chat_id, date,
+ std::move(promise));
break;
}
case DialogType::None:
@@ -13121,14 +22544,416 @@ void MessagesManager::read_history_on_server(DialogId dialog_id, MessageId max_m
}
}
-std::pair<int32, vector<MessageId>> MessagesManager::search_dialog_messages(
- DialogId dialog_id, const string &query, UserId sender_user_id, MessageId from_message_id, int32 offset,
- int32 limit, const tl_object_ptr<td_api::SearchMessagesFilter> &filter, int64 &random_id, bool use_db,
+void MessagesManager::read_message_thread_history_on_server_impl(Dialog *d, MessageId top_thread_message_id,
+ MessageId max_message_id) {
+ CHECK(d != nullptr);
+ auto dialog_id = d->dialog_id;
+ CHECK(dialog_id.get_type() == DialogType::Channel);
+
+ const Message *m = get_message_force(d, top_thread_message_id, "read_message_thread_history_on_server_impl");
+ if (m != nullptr) {
+ auto message_id = m->reply_info.last_read_inbox_message_id_.get_prev_server_message_id();
+ if (message_id > max_message_id) {
+ max_message_id = message_id;
+ }
+ }
+
+ Promise<Unit> promise;
+ if (d->read_history_log_event_ids[top_thread_message_id.get()].log_event_id != 0) {
+ d->read_history_log_event_ids[top_thread_message_id.get()].generation++;
+ promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_id, top_thread_message_id,
+ generation = d->read_history_log_event_ids[top_thread_message_id.get()].generation](Result<Unit> result) {
+ if (!G()->close_flag()) {
+ send_closure(actor_id, &MessagesManager::on_read_history_finished, dialog_id, top_thread_message_id,
+ generation);
+ }
+ });
+ }
+
+ if (!max_message_id.is_valid() || !have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_value(Unit());
+ }
+
+ LOG(INFO) << "Send read history request in thread of " << top_thread_message_id << " in " << dialog_id << " up to "
+ << max_message_id;
+ td_->create_handler<ReadDiscussionQuery>(std::move(promise))->send(dialog_id, top_thread_message_id, max_message_id);
+}
+
+void MessagesManager::on_read_history_finished(DialogId dialog_id, MessageId top_thread_message_id, uint64 generation) {
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ auto it = d->read_history_log_event_ids.find(top_thread_message_id.get());
+ if (it == d->read_history_log_event_ids.end()) {
+ return;
+ }
+ delete_log_event(it->second, generation, "read history");
+ if (it->second.log_event_id == 0) {
+ d->read_history_log_event_ids.erase(it);
+ }
+}
+
+template <class T, class It>
+vector<MessageId> MessagesManager::get_message_history_slice(const T &begin, It it, const T &end,
+ MessageId from_message_id, int32 offset, int32 limit) {
+ int32 left_offset = -offset;
+ int32 left_limit = limit + offset;
+ while (left_offset > 0 && it != end) {
+ ++it;
+ left_offset--;
+ left_limit++;
+ }
+
+ vector<MessageId> message_ids;
+ while (left_limit > 0 && it != begin) {
+ --it;
+ left_limit--;
+ message_ids.push_back(*it);
+ }
+ return message_ids;
+}
+
+std::pair<DialogId, vector<MessageId>> MessagesManager::get_message_thread_history(
+ DialogId dialog_id, MessageId message_id, MessageId from_message_id, int32 offset, int32 limit, int64 &random_id,
Promise<Unit> &&promise) {
+ if (limit <= 0) {
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ return {};
+ }
+ if (limit > MAX_GET_HISTORY) {
+ limit = MAX_GET_HISTORY;
+ }
+ if (offset > 0) {
+ promise.set_error(Status::Error(400, "Parameter offset must be non-positive"));
+ return {};
+ }
+ if (offset <= -MAX_GET_HISTORY) {
+ promise.set_error(Status::Error(400, "Parameter offset must be greater than -100"));
+ return {};
+ }
+ if (offset < -limit) {
+ promise.set_error(Status::Error(400, "Parameter offset must be greater than or equal to -limit"));
+ return {};
+ }
+ bool is_limit_increased = false;
+ if (limit == -offset) {
+ limit++;
+ is_limit_increased = true;
+ }
+ CHECK(0 < limit && limit <= MAX_GET_HISTORY);
+ CHECK(-limit < offset && offset <= 0);
+
+ Dialog *d = get_dialog_force(dialog_id, "get_message_thread_history");
+ if (d == nullptr) {
+ promise.set_error(Status::Error(400, "Chat not found"));
+ return {};
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ promise.set_error(Status::Error(400, "Can't access the chat"));
+ return {};
+ }
+ if (dialog_id.get_type() != DialogType::Channel) {
+ promise.set_error(Status::Error(400, "Can't get message thread history in the chat"));
+ return {};
+ }
+
+ if (from_message_id == MessageId() || from_message_id.get() > MessageId::max().get()) {
+ from_message_id = MessageId::max();
+ }
+ if (!from_message_id.is_valid()) {
+ promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0"));
+ return {};
+ }
+
+ FullMessageId top_thread_full_message_id;
+ {
+ message_id = get_persistent_message_id(d, message_id);
+ Message *m = get_message_force(d, message_id, "get_message_thread_history 1");
+ if (m == nullptr) {
+ promise.set_error(Status::Error(400, "Message not found"));
+ return {};
+ }
+
+ auto r_top_thread_full_message_id = get_top_thread_full_message_id(dialog_id, m);
+ if (r_top_thread_full_message_id.is_error()) {
+ promise.set_error(r_top_thread_full_message_id.move_as_error());
+ return {};
+ }
+ top_thread_full_message_id = r_top_thread_full_message_id.move_as_ok();
+ if ((m->reply_info.is_empty() || !m->reply_info.is_comment_) &&
+ top_thread_full_message_id.get_message_id() != m->message_id) {
+ CHECK(dialog_id == top_thread_full_message_id.get_dialog_id());
+ // get information about the thread from the top message
+ message_id = top_thread_full_message_id.get_message_id();
+ CHECK(message_id.is_valid());
+ }
+
+ if (!top_thread_full_message_id.get_message_id().is_valid()) {
+ CHECK(m->reply_info.is_comment_);
+ get_message_thread(
+ dialog_id, message_id,
+ PromiseCreator::lambda([promise = std::move(promise)](Result<MessageThreadInfo> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(Unit());
+ }
+ }));
+ return {};
+ }
+ }
+
+ if (random_id != 0) {
+ // request has already been sent before
+ auto it = found_dialog_messages_.find(random_id);
+ CHECK(it != found_dialog_messages_.end());
+ auto result = std::move(it->second.second);
+ found_dialog_messages_.erase(it);
+
+ auto dialog_id_it = found_dialog_messages_dialog_id_.find(random_id);
+ if (dialog_id_it != found_dialog_messages_dialog_id_.end()) {
+ dialog_id = dialog_id_it->second;
+ found_dialog_messages_dialog_id_.erase(dialog_id_it);
+
+ d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ }
+ if (dialog_id != top_thread_full_message_id.get_dialog_id()) {
+ promise.set_error(Status::Error(500, "Receive messages in an unexpected chat"));
+ return {};
+ }
+
+ auto yet_unsent_it = d->yet_unsent_thread_message_ids.find(top_thread_full_message_id.get_message_id());
+ if (yet_unsent_it != d->yet_unsent_thread_message_ids.end()) {
+ const std::set<MessageId> &message_ids = yet_unsent_it->second;
+ auto merge_message_ids = get_message_history_slice(message_ids.begin(), message_ids.lower_bound(from_message_id),
+ message_ids.end(), from_message_id, offset, limit);
+ vector<MessageId> new_result(result.size() + merge_message_ids.size());
+ std::merge(result.begin(), result.end(), merge_message_ids.begin(), merge_message_ids.end(), new_result.begin(),
+ std::greater<>());
+ result = std::move(new_result);
+ }
+
+ Message *top_m = get_message_force(d, top_thread_full_message_id.get_message_id(), "get_message_thread_history 2");
+ if (top_m != nullptr && !top_m->local_thread_message_ids.empty()) {
+ vector<MessageId> &message_ids = top_m->local_thread_message_ids;
+ vector<MessageId> merge_message_ids;
+ while (true) {
+ merge_message_ids = get_message_history_slice(
+ message_ids.begin(), std::lower_bound(message_ids.begin(), message_ids.end(), from_message_id),
+ message_ids.end(), from_message_id, offset, limit);
+ bool found_deleted = false;
+ for (auto local_message_id : merge_message_ids) {
+ Message *local_m = get_message_force(d, local_message_id, "get_message_thread_history 3");
+ if (local_m == nullptr) {
+ auto local_it = std::lower_bound(message_ids.begin(), message_ids.end(), local_message_id);
+ CHECK(local_it != message_ids.end() && *local_it == local_message_id);
+ message_ids.erase(local_it);
+ found_deleted = true;
+ }
+ }
+ if (!found_deleted) {
+ break;
+ }
+ on_message_changed(d, top_m, false, "get_message_thread_history");
+ }
+ vector<MessageId> new_result(result.size() + merge_message_ids.size());
+ std::merge(result.begin(), result.end(), merge_message_ids.begin(), merge_message_ids.end(), new_result.begin(),
+ std::greater<>());
+ result = std::move(new_result);
+ }
+
+ if (is_limit_increased) {
+ limit--;
+ }
+
+ std::reverse(result.begin(), result.end());
+ result = get_message_history_slice(result.begin(), std::lower_bound(result.begin(), result.end(), from_message_id),
+ result.end(), from_message_id, offset, limit);
+
+ LOG(INFO) << "Return " << result.size() << " messages in result to getMessageThreadHistory";
+
+ promise.set_value(Unit());
+ return {dialog_id, std::move(result)};
+ }
+
+ do {
+ random_id = Random::secure_int64();
+ } while (random_id == 0 || found_dialog_messages_.count(random_id) > 0);
+ found_dialog_messages_[random_id]; // reserve place for result
+
+ td_->create_handler<SearchMessagesQuery>(std::move(promise))
+ ->send(dialog_id, string(), DialogId(), from_message_id.get_next_server_message_id(), offset, limit,
+ MessageSearchFilter::Empty, message_id, random_id);
+ return {};
+}
+
+td_api::object_ptr<td_api::messageCalendar> MessagesManager::get_dialog_message_calendar(DialogId dialog_id,
+ MessageId from_message_id,
+ MessageSearchFilter filter,
+ int64 &random_id, bool use_db,
+ Promise<Unit> &&promise) {
+ if (random_id != 0) {
+ // request has already been sent before
+ auto it = found_dialog_message_calendars_.find(random_id);
+ if (it != found_dialog_message_calendars_.end()) {
+ auto result = std::move(it->second);
+ found_dialog_message_calendars_.erase(it);
+ promise.set_value(Unit());
+ return result;
+ }
+ random_id = 0;
+ }
+ LOG(INFO) << "Get message calendar in " << dialog_id << " filtered by " << filter << " from " << from_message_id;
+
+ if (from_message_id.get() > MessageId::max().get()) {
+ from_message_id = MessageId::max();
+ }
+
+ if (!from_message_id.is_valid() && from_message_id != MessageId()) {
+ promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0"));
+ return {};
+ }
+ from_message_id = from_message_id.get_next_server_message_id();
+
+ const Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_calendar");
+ if (d == nullptr) {
+ promise.set_error(Status::Error(400, "Chat not found"));
+ return {};
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ promise.set_error(Status::Error(400, "Can't access the chat"));
+ return {};
+ }
+
+ do {
+ random_id = Random::secure_int64();
+ } while (random_id == 0 || found_dialog_message_calendars_.count(random_id) > 0);
+ found_dialog_message_calendars_[random_id]; // reserve place for result
+
+ CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall);
+ if (filter == MessageSearchFilter::Empty || filter == MessageSearchFilter::Mention ||
+ filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction) {
+ promise.set_error(Status::Error(400, "The filter is not supported"));
+ return {};
+ }
+
+ // Trying to use database
+ if (use_db && G()->parameters().use_message_db) {
+ MessageId first_db_message_id = get_first_database_message_id_by_index(d, filter);
+ int32 message_count = d->message_count_by_index[message_search_filter_index(filter)];
+ auto fixed_from_message_id = from_message_id;
+ if (fixed_from_message_id == MessageId()) {
+ fixed_from_message_id = MessageId::max();
+ }
+ LOG(INFO) << "Get message calendar in " << dialog_id << " from " << fixed_from_message_id << ", have up to "
+ << first_db_message_id << ", message_count = " << message_count;
+ if (first_db_message_id < fixed_from_message_id && message_count != -1) {
+ LOG(INFO) << "Get message calendar from database in " << dialog_id << " from " << fixed_from_message_id;
+ auto new_promise =
+ PromiseCreator::lambda([random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter,
+ promise = std::move(promise)](Result<MessageDbCalendar> r_calendar) mutable {
+ send_closure(G()->messages_manager(), &MessagesManager::on_get_message_calendar_from_database, random_id,
+ dialog_id, fixed_from_message_id, first_db_message_id, filter, std::move(r_calendar),
+ std::move(promise));
+ });
+ MessageDbDialogCalendarQuery db_query;
+ db_query.dialog_id = dialog_id;
+ db_query.filter = filter;
+ db_query.from_message_id = fixed_from_message_id;
+ db_query.tz_offset = static_cast<int32>(td_->option_manager_->get_option_integer("utc_time_offset"));
+ G()->td_db()->get_message_db_async()->get_dialog_message_calendar(db_query, std::move(new_promise));
+ return {};
+ }
+ }
+ if (filter == MessageSearchFilter::FailedToSend) {
+ promise.set_value(Unit());
+ return {};
+ }
+
+ LOG(DEBUG) << "Get message calendar from server in " << dialog_id << " from " << from_message_id;
+
+ switch (dialog_id.get_type()) {
+ case DialogType::None:
+ case DialogType::User:
+ case DialogType::Chat:
+ case DialogType::Channel:
+ td_->create_handler<GetSearchResultCalendarQuery>(std::move(promise))
+ ->send(dialog_id, from_message_id, filter, random_id);
+ break;
+ case DialogType::SecretChat:
+ promise.set_value(Unit());
+ break;
+ default:
+ UNREACHABLE();
+ promise.set_error(Status::Error(500, "Search messages is not supported"));
+ }
+ return {};
+}
+
+void MessagesManager::on_get_message_calendar_from_database(int64 random_id, DialogId dialog_id,
+ MessageId from_message_id, MessageId first_db_message_id,
+ MessageSearchFilter filter,
+ Result<MessageDbCalendar> r_calendar,
+ Promise<Unit> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (r_calendar.is_error()) {
+ LOG(ERROR) << "Failed to get message calendar from the database: " << r_calendar.error();
+ if (first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat &&
+ filter != MessageSearchFilter::FailedToSend) {
+ found_dialog_message_calendars_.erase(random_id);
+ }
+ return promise.set_value(Unit());
+ }
+ CHECK(!from_message_id.is_scheduled());
+ CHECK(!first_db_message_id.is_scheduled());
+
+ auto calendar = r_calendar.move_as_ok();
+
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ auto it = found_dialog_message_calendars_.find(random_id);
+ CHECK(it != found_dialog_message_calendars_.end());
+ CHECK(it->second == nullptr);
+
+ vector<std::pair<MessageId, int32>> periods;
+ periods.reserve(calendar.messages.size());
+ for (size_t i = 0; i < calendar.messages.size(); i++) {
+ auto m = on_get_message_from_database(d, calendar.messages[i], false, "on_get_message_calendar_from_database");
+ if (m != nullptr && first_db_message_id <= m->message_id) {
+ CHECK(!m->message_id.is_scheduled());
+ periods.emplace_back(m->message_id, calendar.total_counts[i]);
+ }
+ }
+
+ if (periods.empty() && first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat) {
+ LOG(INFO) << "No messages found in database";
+ found_dialog_message_calendars_.erase(it);
+ } else {
+ auto total_count = d->message_count_by_index[message_search_filter_index(filter)];
+ vector<td_api::object_ptr<td_api::messageCalendarDay>> days;
+ for (auto &period : periods) {
+ const auto *m = get_message(d, period.first);
+ CHECK(m != nullptr);
+ days.push_back(td_api::make_object<td_api::messageCalendarDay>(
+ period.second, get_message_object(dialog_id, m, "on_get_message_calendar_from_database")));
+ }
+ it->second = td_api::make_object<td_api::messageCalendar>(total_count, std::move(days));
+ }
+ promise.set_value(Unit());
+}
+
+std::pair<int32, vector<MessageId>> MessagesManager::search_dialog_messages(
+ DialogId dialog_id, const string &query, const td_api::object_ptr<td_api::MessageSender> &sender,
+ MessageId from_message_id, int32 offset, int32 limit, MessageSearchFilter filter, MessageId top_thread_message_id,
+ int64 &random_id, bool use_db, Promise<Unit> &&promise) {
if (random_id != 0) {
// request has already been sent before
auto it = found_dialog_messages_.find(random_id);
if (it != found_dialog_messages_.end()) {
+ CHECK(found_dialog_messages_dialog_id_.count(random_id) == 0);
auto result = std::move(it->second);
found_dialog_messages_.erase(it);
promise.set_value(Unit());
@@ -13136,24 +22961,24 @@ std::pair<int32, vector<MessageId>> MessagesManager::search_dialog_messages(
}
random_id = 0;
}
- LOG(INFO) << "Search messages with query \"" << query << "\" in " << dialog_id << " sent by " << sender_user_id
- << " filtered by " << to_string(filter) << " from " << from_message_id << " with offset " << offset
- << " and limit " << limit;
+ LOG(INFO) << "Search messages with query \"" << query << "\" in " << dialog_id << " sent by "
+ << oneline(to_string(sender)) << " in thread of " << top_thread_message_id << " filtered by " << filter
+ << " from " << from_message_id << " with offset " << offset << " and limit " << limit;
std::pair<int32, vector<MessageId>> result;
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return result;
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
if (limit <= -offset) {
- promise.set_error(Status::Error(5, "Parameter limit must be greater than -offset"));
+ promise.set_error(Status::Error(400, "Parameter limit must be greater than -offset"));
return result;
}
if (offset > 0) {
- promise.set_error(Status::Error(5, "Parameter offset must be non-positive"));
+ promise.set_error(Status::Error(400, "Parameter offset must be non-positive"));
return result;
}
@@ -13162,94 +22987,133 @@ std::pair<int32, vector<MessageId>> MessagesManager::search_dialog_messages(
}
if (!from_message_id.is_valid() && from_message_id != MessageId()) {
- promise.set_error(Status::Error(3, "Parameter from_message_id must be identifier of the chat message or 0"));
+ promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0"));
return result;
}
from_message_id = from_message_id.get_next_server_message_id();
- const Dialog *d = get_dialog_force(dialog_id);
+ const Dialog *d = get_dialog_force(dialog_id, "search_dialog_messages");
if (d == nullptr) {
- promise.set_error(Status::Error(6, "Chat not found"));
+ promise.set_error(Status::Error(400, "Chat not found"));
+ return result;
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ promise.set_error(Status::Error(400, "Can't access the chat"));
return result;
}
- auto input_user = td_->contacts_manager_->get_input_user(sender_user_id);
- if (sender_user_id.is_valid() && input_user == nullptr) {
- promise.set_error(Status::Error(6, "Wrong sender user identifier specified"));
+ auto r_sender_dialog_id = get_message_sender_dialog_id(td_, sender, true, true);
+ if (r_sender_dialog_id.is_error()) {
+ promise.set_error(r_sender_dialog_id.move_as_error());
+ return result;
+ }
+ auto sender_dialog_id = r_sender_dialog_id.move_as_ok();
+ if (sender_dialog_id != DialogId() && !have_input_peer(sender_dialog_id, AccessRights::Know)) {
+ promise.set_error(Status::Error(400, "Invalid message sender specified"));
+ return result;
+ }
+ if (sender_dialog_id == dialog_id && is_broadcast_channel(dialog_id)) {
+ sender_dialog_id = DialogId();
+ }
+
+ if (filter == MessageSearchFilter::FailedToSend && sender_dialog_id.is_valid()) {
+ if (sender_dialog_id != get_my_dialog_id()) {
+ promise.set_value(Unit());
+ return result;
+ }
+ sender_dialog_id = DialogId();
+ }
+
+ if (top_thread_message_id != MessageId()) {
+ if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
+ promise.set_error(Status::Error(400, "Invalid message thread ID specified"));
+ return result;
+ }
+ if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
+ promise.set_error(Status::Error(400, "Can't filter by message thread ID in the chat"));
+ return result;
+ }
+ }
+
+ if (sender_dialog_id.get_type() == DialogType::SecretChat) {
+ promise.set_value(Unit());
return result;
}
do {
random_id = Random::secure_int64();
- } while (random_id == 0 || found_dialog_messages_.find(random_id) != found_dialog_messages_.end());
+ } while (random_id == 0 || found_dialog_messages_.count(random_id) > 0);
found_dialog_messages_[random_id]; // reserve place for result
- auto filter_type = get_search_messages_filter(filter);
- if (filter_type == SearchMessagesFilter::UnreadMention) {
+ if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction) {
if (!query.empty()) {
- promise.set_error(Status::Error(6, "Non-empty query is unsupported with the specified filter"));
+ promise.set_error(Status::Error(400, "Non-empty query is unsupported with the specified filter"));
return result;
}
- if (input_user != nullptr) {
- promise.set_error(Status::Error(6, "Non-empty sender user is unsupported with the specified filter"));
+ if (sender_dialog_id.is_valid()) {
+ promise.set_error(Status::Error(400, "Filtering by sender is unsupported with the specified filter"));
return result;
}
}
// Trying to use database
- if (query.empty() && G()->parameters().use_message_db && filter_type != SearchMessagesFilter::Empty &&
- input_user == nullptr) { // TODO support filter by users in the database
- MessageId first_db_message_id = get_first_database_message_id_by_index(d, filter_type);
- int32 message_count = d->message_count_by_index[search_messages_filter_index(filter_type)];
+ if (use_db && query.empty() && G()->parameters().use_message_db && filter != MessageSearchFilter::Empty &&
+ !sender_dialog_id.is_valid() && top_thread_message_id == MessageId()) {
+ MessageId first_db_message_id = get_first_database_message_id_by_index(d, filter);
+ int32 message_count = d->message_count_by_index[message_search_filter_index(filter)];
auto fixed_from_message_id = from_message_id;
if (fixed_from_message_id == MessageId()) {
fixed_from_message_id = MessageId::max();
}
LOG(INFO) << "Search messages in " << dialog_id << " from " << fixed_from_message_id << ", have up to "
<< first_db_message_id << ", message_count = " << message_count;
- if (use_db &&
- (first_db_message_id.get() < fixed_from_message_id.get() ||
- (first_db_message_id.get() == fixed_from_message_id.get() && offset < 0)) &&
+ if ((first_db_message_id < fixed_from_message_id || (first_db_message_id == fixed_from_message_id && offset < 0)) &&
message_count != -1) {
LOG(INFO) << "Search messages in database in " << dialog_id << " from " << fixed_from_message_id
<< " and with limit " << limit;
auto new_promise = PromiseCreator::lambda(
- [random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter_type, offset, limit,
- promise = std::move(promise)](Result<MessagesDbMessagesResult> result) mutable {
- send_closure(G()->messages_manager(), &MessagesManager::on_search_dialog_messages_db_result, random_id,
- dialog_id, fixed_from_message_id, first_db_message_id, filter_type, offset, limit,
- std::move(result), std::move(promise));
+ [random_id, dialog_id, fixed_from_message_id, first_db_message_id, filter, offset, limit,
+ promise = std::move(promise)](Result<vector<MessageDbDialogMessage>> r_messages) mutable {
+ send_closure(G()->messages_manager(), &MessagesManager::on_search_dialog_message_db_result, random_id,
+ dialog_id, fixed_from_message_id, first_db_message_id, filter, offset, limit,
+ std::move(r_messages), std::move(promise));
});
- MessagesDbMessagesQuery db_query;
+ MessageDbMessagesQuery db_query;
db_query.dialog_id = dialog_id;
- db_query.index_mask = search_messages_filter_index_mask(filter_type);
+ db_query.filter = filter;
db_query.from_message_id = fixed_from_message_id;
db_query.offset = offset;
db_query.limit = limit;
- G()->td_db()->get_messages_db_async()->get_messages(db_query, std::move(new_promise));
+ G()->td_db()->get_message_db_async()->get_messages(db_query, std::move(new_promise));
return result;
}
}
+ if (filter == MessageSearchFilter::FailedToSend) {
+ promise.set_value(Unit());
+ return result;
+ }
- LOG(DEBUG) << "Search messages on server in " << dialog_id << " with query \"" << query << "\" from user "
- << sender_user_id << " from " << from_message_id << " and with limit " << limit;
+ LOG(DEBUG) << "Search messages on server in " << dialog_id << " with query \"" << query << "\" from "
+ << sender_dialog_id << " in thread of " << top_thread_message_id << " from " << from_message_id
+ << " and with limit " << limit;
switch (dialog_id.get_type()) {
- case DialogType::None:
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
td_->create_handler<SearchMessagesQuery>(std::move(promise))
- ->send(dialog_id, query, sender_user_id, std::move(input_user), from_message_id, offset, limit, filter_type,
+ ->send(dialog_id, query, sender_dialog_id, from_message_id, offset, limit, filter, top_thread_message_id,
random_id);
break;
case DialogType::SecretChat:
- if (filter_type == SearchMessagesFilter::UnreadMention) {
+ if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::Pinned ||
+ filter == MessageSearchFilter::UnreadReaction) {
promise.set_value(Unit());
} else {
promise.set_error(Status::Error(500, "Search messages in secret chats is not supported"));
}
break;
+ case DialogType::None:
default:
UNREACHABLE();
promise.set_error(Status::Error(500, "Search messages is not supported"));
@@ -13275,7 +23139,7 @@ std::pair<int32, vector<FullMessageId>> MessagesManager::search_call_messages(Me
std::pair<int32, vector<FullMessageId>> result;
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return result;
}
if (limit > MAX_SEARCH_MESSAGES) {
@@ -13287,101 +23151,91 @@ std::pair<int32, vector<FullMessageId>> MessagesManager::search_call_messages(Me
}
if (!from_message_id.is_valid() && from_message_id != MessageId()) {
- promise.set_error(Status::Error(3, "Parameter from_message_id must be identifier of the chat message or 0"));
+ promise.set_error(Status::Error(400, "Parameter from_message_id must be identifier of a chat message or 0"));
return result;
}
from_message_id = from_message_id.get_next_server_message_id();
do {
random_id = Random::secure_int64();
- } while (random_id == 0 || found_call_messages_.find(random_id) != found_call_messages_.end());
+ } while (random_id == 0 || found_call_messages_.count(random_id) > 0);
found_call_messages_[random_id]; // reserve place for result
- auto filter_type = only_missed ? SearchMessagesFilter::MissedCall : SearchMessagesFilter::Call;
-
- // Try to use database
- MessageId first_db_message_id =
- calls_db_state_.first_calls_database_message_id_by_index[search_calls_filter_index(filter_type)];
- int32 message_count = calls_db_state_.message_count_by_index[search_calls_filter_index(filter_type)];
- auto fixed_from_message_id = from_message_id;
- if (fixed_from_message_id == MessageId()) {
- fixed_from_message_id = MessageId::max();
- }
- CHECK(fixed_from_message_id.is_valid() && fixed_from_message_id.is_server());
- LOG(INFO) << "Search call messages from " << fixed_from_message_id << ", have up to " << first_db_message_id
- << ", message_count = " << message_count;
- if (use_db && first_db_message_id.get() < fixed_from_message_id.get() && message_count != -1) {
- LOG(INFO) << "Search messages in database from " << fixed_from_message_id << " and with limit " << limit;
-
- MessagesDbCallsQuery db_query;
- db_query.index_mask = search_messages_filter_index_mask(filter_type);
- db_query.from_unique_message_id = fixed_from_message_id.get_server_message_id().get();
- db_query.limit = limit;
- G()->td_db()->get_messages_db_async()->get_calls(
- db_query, PromiseCreator::lambda([random_id, first_db_message_id, filter_type, promise = std::move(promise)](
- Result<MessagesDbCallsResult> calls_result) mutable {
- send_closure(G()->messages_manager(), &MessagesManager::on_messages_db_calls_result, std::move(calls_result),
- random_id, first_db_message_id, filter_type, std::move(promise));
- }));
- return result;
+ auto filter = only_missed ? MessageSearchFilter::MissedCall : MessageSearchFilter::Call;
+
+ if (use_db && G()->parameters().use_message_db) {
+ // try to use database
+ MessageId first_db_message_id =
+ calls_db_state_.first_calls_database_message_id_by_index[call_message_search_filter_index(filter)];
+ int32 message_count = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)];
+ auto fixed_from_message_id = from_message_id;
+ if (fixed_from_message_id == MessageId()) {
+ fixed_from_message_id = MessageId::max();
+ }
+ CHECK(fixed_from_message_id.is_valid() && fixed_from_message_id.is_server());
+ LOG(INFO) << "Search call messages from " << fixed_from_message_id << ", have up to " << first_db_message_id
+ << ", message_count = " << message_count;
+ if (first_db_message_id < fixed_from_message_id && message_count != -1) {
+ LOG(INFO) << "Search messages in database from " << fixed_from_message_id << " and with limit " << limit;
+
+ MessageDbCallsQuery db_query;
+ db_query.filter = filter;
+ db_query.from_unique_message_id = fixed_from_message_id.get_server_message_id().get();
+ db_query.limit = limit;
+ G()->td_db()->get_message_db_async()->get_calls(
+ db_query, PromiseCreator::lambda([random_id, first_db_message_id, filter, promise = std::move(promise)](
+ Result<MessageDbCallsResult> calls_result) mutable {
+ send_closure(G()->messages_manager(), &MessagesManager::on_message_db_calls_result, std::move(calls_result),
+ random_id, first_db_message_id, filter, std::move(promise));
+ }));
+ return result;
+ }
}
- LOG(DEBUG) << "Search call messages on server from " << from_message_id << " and with limit " << limit;
td_->create_handler<SearchMessagesQuery>(std::move(promise))
- ->send(DialogId(), "", UserId(), nullptr, from_message_id, 0, limit, filter_type, random_id);
+ ->send(DialogId(), "", DialogId(), from_message_id, 0, limit, filter, MessageId(), random_id);
return result;
}
-std::pair<int32, vector<MessageId>> MessagesManager::search_dialog_recent_location_messages(DialogId dialog_id,
- int32 limit,
- int64 &random_id,
- Promise<Unit> &&promise) {
- if (random_id != 0) {
- // request has already been sent before
- auto it = found_dialog_recent_location_messages_.find(random_id);
- CHECK(it != found_dialog_recent_location_messages_.end());
- auto result = std::move(it->second);
- found_dialog_recent_location_messages_.erase(it);
- promise.set_value(Unit());
- return result;
+void MessagesManager::search_outgoing_document_messages(const string &query, int32 limit,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ }
+ if (limit > MAX_SEARCH_MESSAGES) {
+ limit = MAX_SEARCH_MESSAGES;
}
+
+ td_->create_handler<SearchSentMediaQuery>(std::move(promise))->send(query, limit);
+}
+
+void MessagesManager::search_dialog_recent_location_messages(DialogId dialog_id, int32 limit,
+ Promise<td_api::object_ptr<td_api::messages>> &&promise) {
LOG(INFO) << "Search recent location messages in " << dialog_id << " with limit " << limit;
- std::pair<int32, vector<MessageId>> result;
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
- return result;
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
- const Dialog *d = get_dialog_force(dialog_id);
+ const Dialog *d = get_dialog_force(dialog_id, "search_dialog_recent_location_messages");
if (d == nullptr) {
- promise.set_error(Status::Error(6, "Chat not found"));
- return result;
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- do {
- random_id = Random::secure_int64();
- } while (random_id == 0 ||
- found_dialog_recent_location_messages_.find(random_id) != found_dialog_recent_location_messages_.end());
- found_dialog_recent_location_messages_[random_id]; // reserve place for result
-
switch (dialog_id.get_type()) {
case DialogType::User:
case DialogType::Chat:
case DialogType::Channel:
- td_->create_handler<GetRecentLocationsQuery>(std::move(promise))->send(dialog_id, limit, random_id);
- break;
+ return td_->create_handler<GetRecentLocationsQuery>(std::move(promise))->send(dialog_id, limit);
case DialogType::SecretChat:
- promise.set_value(Unit());
- break;
+ return promise.set_value(get_messages_object(0, vector<td_api::object_ptr<td_api::message>>(), true));
default:
UNREACHABLE();
promise.set_error(Status::Error(500, "Search messages is not supported"));
}
- return result;
}
vector<FullMessageId> MessagesManager::get_active_live_location_messages(Promise<Unit> &&promise) {
@@ -13408,9 +23262,14 @@ vector<FullMessageId> MessagesManager::get_active_live_location_messages(Promise
for (auto &full_message_id : active_live_location_full_message_ids_) {
auto m = get_message(full_message_id);
CHECK(m != nullptr);
- CHECK(m->content->get_id() == MessageLiveLocation::ID);
+ CHECK(m->content->get_type() == MessageContentType::LiveLocation);
+ CHECK(!m->message_id.is_scheduled());
+
+ if (m->is_failed_to_send) {
+ continue;
+ }
- auto live_period = static_cast<const MessageLiveLocation *>(m->content.get())->period;
+ auto live_period = get_message_content_live_location_period(m->content.get());
if (live_period <= G()->unix_time() - m->date) { // bool is_expired flag?
// live location is expired
continue;
@@ -13422,9 +23281,16 @@ vector<FullMessageId> MessagesManager::get_active_live_location_messages(Promise
}
void MessagesManager::on_load_active_live_location_full_message_ids_from_database(string value) {
+ if (G()->close_flag()) {
+ return;
+ }
if (value.empty()) {
LOG(INFO) << "Active live location messages aren't found in the database";
on_load_active_live_location_messages_finished();
+
+ if (!active_live_location_full_message_ids_.empty()) {
+ save_active_live_locations();
+ }
return;
}
@@ -13436,41 +23302,41 @@ void MessagesManager::on_load_active_live_location_full_message_ids_from_databas
// TODO asynchronously load messages from database
active_live_location_full_message_ids_.clear();
- for (auto full_message_id : old_full_message_ids) {
- Message *m = get_message_force(full_message_id);
+ for (const auto &full_message_id : old_full_message_ids) {
+ Message *m = get_message_force(full_message_id, "on_load_active_live_location_full_message_ids_from_database");
if (m != nullptr) {
try_add_active_live_location(full_message_id.get_dialog_id(), m);
}
}
- for (auto full_message_id : new_full_message_ids) {
+ for (const auto &full_message_id : new_full_message_ids) {
add_active_live_location(full_message_id);
}
on_load_active_live_location_messages_finished();
- if (!new_full_message_ids.empty()) {
+ if (!new_full_message_ids.empty() || old_full_message_ids.size() != active_live_location_full_message_ids_.size()) {
save_active_live_locations();
}
}
void MessagesManager::on_load_active_live_location_messages_finished() {
are_active_live_location_messages_loaded_ = true;
- auto promises = std::move(load_active_live_location_messages_queries_);
- load_active_live_location_messages_queries_.clear();
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(load_active_live_location_messages_queries_);
}
void MessagesManager::try_add_active_live_location(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
- if (m->content->get_id() != MessageLiveLocation::ID) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ if (m->content->get_type() != MessageContentType::LiveLocation || m->message_id.is_scheduled() ||
+ m->message_id.is_local() || m->via_bot_user_id.is_valid() || m->forward_info != nullptr) {
return;
}
- auto live_period = static_cast<const MessageLiveLocation *>(m->content.get())->period;
+ auto live_period = get_message_content_live_location_period(m->content.get());
if (live_period <= G()->unix_time() - m->date + 1) { // bool is_expired flag?
// live location is expired
return;
@@ -13479,14 +23345,24 @@ void MessagesManager::try_add_active_live_location(DialogId dialog_id, const Mes
}
void MessagesManager::add_active_live_location(FullMessageId full_message_id) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
if (!active_live_location_full_message_ids_.insert(full_message_id).second) {
return;
}
// TODO add timer for live location expiration
+ if (!G()->parameters().use_message_db) {
+ return;
+ }
+
if (are_active_live_location_messages_loaded_) {
save_active_live_locations();
+ } else if (load_active_live_location_messages_queries_.empty()) {
+ // load active live locations and save after that
+ get_active_live_location_messages(Auto());
}
}
@@ -13505,11 +23381,226 @@ void MessagesManager::save_active_live_locations() {
}
}
-MessageId MessagesManager::get_first_database_message_id_by_index(const Dialog *d, SearchMessagesFilter filter) {
+void MessagesManager::on_message_live_location_viewed(Dialog *d, const Message *m) {
CHECK(d != nullptr);
- auto message_id = filter == SearchMessagesFilter::Empty
+ CHECK(m != nullptr);
+ CHECK(m->content->get_type() == MessageContentType::LiveLocation);
+ CHECK(!m->message_id.is_scheduled());
+
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ switch (d->dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::Chat:
+ case DialogType::Channel:
+ // ok
+ break;
+ case DialogType::SecretChat:
+ return;
+ default:
+ UNREACHABLE();
+ return;
+ }
+ if (!d->is_opened) {
+ return;
+ }
+
+ if (m->is_outgoing || !m->message_id.is_server() || m->via_bot_user_id.is_valid() || !m->sender_user_id.is_valid() ||
+ td_->contacts_manager_->is_user_bot(m->sender_user_id) || m->forward_info != nullptr) {
+ return;
+ }
+
+ auto live_period = get_message_content_live_location_period(m->content.get());
+ if (live_period <= G()->unix_time() - m->date + 1) {
+ // live location is expired
+ return;
+ }
+
+ auto &live_location_task_id = d->pending_viewed_live_locations[m->message_id];
+ if (live_location_task_id != 0) {
+ return;
+ }
+
+ live_location_task_id = ++viewed_live_location_task_id_;
+ auto &full_message_id = viewed_live_location_tasks_[live_location_task_id];
+ full_message_id = FullMessageId(d->dialog_id, m->message_id);
+ view_message_live_location_on_server_impl(live_location_task_id, full_message_id);
+}
+
+void MessagesManager::view_message_live_location_on_server(int64 task_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto it = viewed_live_location_tasks_.find(task_id);
+ if (it == viewed_live_location_tasks_.end()) {
+ return;
+ }
+
+ auto full_message_id = it->second;
+ Dialog *d = get_dialog(full_message_id.get_dialog_id());
+ const Message *m = get_message_force(d, full_message_id.get_message_id(), "view_message_live_location_on_server");
+ if (m == nullptr || get_message_content_live_location_period(m->content.get()) <= G()->unix_time() - m->date + 1) {
+ // the message was deleted or live location is expired
+ viewed_live_location_tasks_.erase(it);
+ auto erased_count = d->pending_viewed_live_locations.erase(full_message_id.get_message_id());
+ CHECK(erased_count > 0);
+ return;
+ }
+
+ view_message_live_location_on_server_impl(task_id, full_message_id);
+}
+
+void MessagesManager::view_message_live_location_on_server_impl(int64 task_id, FullMessageId full_message_id) {
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), task_id](Unit result) {
+ send_closure(actor_id, &MessagesManager::on_message_live_location_viewed_on_server, task_id);
+ });
+ read_message_contents_on_server(full_message_id.get_dialog_id(), {full_message_id.get_message_id()}, 0,
+ std::move(promise), true);
+}
+
+void MessagesManager::on_message_live_location_viewed_on_server(int64 task_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto it = viewed_live_location_tasks_.find(task_id);
+ if (it == viewed_live_location_tasks_.end()) {
+ return;
+ }
+
+ pending_message_live_location_view_timeout_.add_timeout_in(task_id, LIVE_LOCATION_VIEW_PERIOD);
+}
+
+void MessagesManager::try_add_bot_command_message_id(DialogId dialog_id, const Message *m) {
+ CHECK(m != nullptr);
+ if (td_->auth_manager_->is_bot() || !is_group_dialog(dialog_id) || m->message_id.is_scheduled() ||
+ !has_bot_commands(get_message_content_text(m->content.get()))) {
+ return;
+ }
+
+ dialog_bot_command_message_ids_[dialog_id].message_ids.insert(m->message_id);
+}
+
+void MessagesManager::delete_bot_command_message_id(DialogId dialog_id, MessageId message_id) {
+ if (message_id.is_scheduled()) {
+ return;
+ }
+ auto it = dialog_bot_command_message_ids_.find(dialog_id);
+ if (it == dialog_bot_command_message_ids_.end()) {
+ return;
+ }
+ if (it->second.message_ids.erase(message_id) && it->second.message_ids.empty()) {
+ dialog_bot_command_message_ids_.erase(it);
+ }
+}
+
+FileSourceId MessagesManager::get_message_file_source_id(FullMessageId full_message_id, bool force) {
+ if (!force) {
+ if (td_->auth_manager_->is_bot()) {
+ return FileSourceId();
+ }
+
+ auto dialog_id = full_message_id.get_dialog_id();
+ auto message_id = full_message_id.get_message_id();
+ if (!dialog_id.is_valid() || !(message_id.is_valid() || message_id.is_valid_scheduled()) ||
+ dialog_id.get_type() == DialogType::SecretChat || !message_id.is_any_server()) {
+ return FileSourceId();
+ }
+ }
+
+ auto &file_source_id = full_message_id_to_file_source_id_[full_message_id];
+ if (!file_source_id.is_valid()) {
+ file_source_id = td_->file_reference_manager_->create_message_file_source(full_message_id);
+ }
+ return file_source_id;
+}
+
+void MessagesManager::add_message_file_sources(DialogId dialog_id, const Message *m) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (dialog_id.get_type() != DialogType::SecretChat && m->is_content_secret) {
+ // return;
+ }
+
+ auto file_ids = get_message_content_file_ids(m->content.get(), td_);
+ if (file_ids.empty()) {
+ return;
+ }
+
+ // do not create file_source_id for messages without file_ids
+ auto file_source_id = get_message_file_source_id(FullMessageId(dialog_id, m->message_id));
+ if (file_source_id.is_valid()) {
+ for (auto file_id : file_ids) {
+ td_->file_manager_->add_file_source(file_id, file_source_id);
+ }
+ }
+}
+
+void MessagesManager::remove_message_file_sources(DialogId dialog_id, const Message *m) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto file_ids = get_message_content_file_ids(m->content.get(), td_);
+ if (file_ids.empty()) {
+ return;
+ }
+
+ // do not create file_source_id for messages without file_ids
+ auto file_source_id = get_message_file_source_id(FullMessageId(dialog_id, m->message_id));
+ if (file_source_id.is_valid()) {
+ for (auto file_id : file_ids) {
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ send_closure(td_->download_manager_actor_, &DownloadManager::remove_file, file_view.get_main_file_id(),
+ file_source_id, false, Promise<Unit>());
+ td_->file_manager_->remove_file_source(file_id, file_source_id);
+ }
+ }
+}
+
+void MessagesManager::change_message_files(DialogId dialog_id, const Message *m, const vector<FileId> &old_file_ids) {
+ if (dialog_id.get_type() != DialogType::SecretChat && m->is_content_secret) {
+ // return;
+ }
+
+ auto new_file_ids = get_message_content_file_ids(m->content.get(), td_);
+ if (new_file_ids == old_file_ids) {
+ return;
+ }
+
+ FullMessageId full_message_id{dialog_id, m->message_id};
+ bool need_delete_files = need_delete_message_files(dialog_id, m);
+ auto file_source_id = get_message_file_source_id(full_message_id);
+ for (auto file_id : old_file_ids) {
+ if (!td::contains(new_file_ids, file_id)) {
+ if (need_delete_files && need_delete_file(full_message_id, file_id)) {
+ send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise<Unit>(), "change_message_files");
+ }
+ if (file_source_id.is_valid()) {
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ send_closure(td_->download_manager_actor_, &DownloadManager::remove_file, file_view.get_main_file_id(),
+ file_source_id, false, Promise<Unit>());
+ }
+ }
+ }
+
+ if (file_source_id.is_valid()) {
+ td_->file_manager_->change_files_source(file_source_id, old_file_ids, new_file_ids);
+ }
+}
+
+MessageId MessagesManager::get_first_database_message_id_by_index(const Dialog *d, MessageSearchFilter filter) {
+ CHECK(d != nullptr);
+ auto message_id = filter == MessageSearchFilter::Empty
? d->first_database_message_id
- : d->first_database_message_id_by_index[search_messages_filter_index(filter)];
+ : d->first_database_message_id_by_index[message_search_filter_index(filter)];
+ CHECK(!message_id.is_scheduled());
if (!message_id.is_valid()) {
if (d->dialog_id.get_type() == DialogType::SecretChat) {
LOG(ERROR) << "Invalid first_database_message_id_by_index in " << d->dialog_id;
@@ -13520,19 +23611,25 @@ MessageId MessagesManager::get_first_database_message_id_by_index(const Dialog *
return message_id;
}
-void MessagesManager::on_search_dialog_messages_db_result(int64 random_id, DialogId dialog_id,
- MessageId from_message_id, MessageId first_db_message_id,
- SearchMessagesFilter filter_type, int32 offset, int32 limit,
- Result<MessagesDbMessagesResult> result, Promise<> promise) {
- if (result.is_error()) {
- LOG(ERROR) << result.error();
- if (first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat) {
+void MessagesManager::on_search_dialog_message_db_result(int64 random_id, DialogId dialog_id, MessageId from_message_id,
+ MessageId first_db_message_id, MessageSearchFilter filter,
+ int32 offset, int32 limit,
+ Result<vector<MessageDbDialogMessage>> r_messages,
+ Promise<Unit> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (r_messages.is_error()) {
+ LOG(ERROR) << "Failed to get messages from the database: " << r_messages.error();
+ if (first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat &&
+ filter != MessageSearchFilter::FailedToSend) {
found_dialog_messages_.erase(random_id);
}
return promise.set_value(Unit());
}
+ CHECK(!from_message_id.is_scheduled());
+ CHECK(!first_db_message_id.is_scheduled());
- auto messages = result.move_as_ok().messages;
+ auto messages = r_messages.move_as_ok();
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
@@ -13543,38 +23640,75 @@ void MessagesManager::on_search_dialog_messages_db_result(int64 random_id, Dialo
res.reserve(messages.size());
for (auto &message : messages) {
- auto m = on_get_message_from_database(dialog_id, d, message);
- if (m != nullptr && first_db_message_id.get() <= m->message_id.get()) {
- res.push_back(m->message_id);
+ auto m = on_get_message_from_database(d, message, false, "on_search_dialog_message_db_result");
+ if (m != nullptr && first_db_message_id <= m->message_id) {
+ if (filter == MessageSearchFilter::UnreadMention && !m->contains_unread_mention) {
+ // skip already read by d->last_read_all_mentions_message_id mentions
+ } else {
+ CHECK(!m->message_id.is_scheduled());
+ res.push_back(m->message_id);
+ }
}
}
- auto &message_count = d->message_count_by_index[search_messages_filter_index(filter_type)];
- int32 result_size = narrow_cast<int32>(res.size());
- if ((message_count < result_size) ||
- (from_message_id == MessageId::max() && first_db_message_id == MessageId::min() && message_count > result_size &&
+ auto &message_count = d->message_count_by_index[message_search_filter_index(filter)];
+ auto result_size = narrow_cast<int32>(res.size());
+ bool from_the_end =
+ from_message_id == MessageId::max() || (offset < 0 && (result_size == 0 || res[0] < from_message_id));
+ if ((message_count != -1 && message_count < result_size) ||
+ (message_count > result_size && from_the_end && first_db_message_id == MessageId::min() &&
result_size < limit + offset)) {
LOG(INFO) << "Fix found message count in " << dialog_id << " from " << message_count << " to " << result_size;
message_count = result_size;
- if (filter_type == SearchMessagesFilter::UnreadMention) {
+ if (filter == MessageSearchFilter::UnreadMention) {
d->unread_mention_count = message_count;
+ update_dialog_mention_notification_count(d);
send_update_chat_unread_mention_count(d);
}
- on_dialog_updated(dialog_id, "on_search_dialog_messages_db_result");
+ if (filter == MessageSearchFilter::UnreadReaction) {
+ d->unread_reaction_count = message_count;
+ // update_dialog_mention_notification_count(d);
+ send_update_chat_unread_reaction_count(d, "on_search_dialog_message_db_result");
+ }
+ on_dialog_updated(dialog_id, "on_search_dialog_message_db_result");
}
it->second.first = message_count;
if (res.empty() && first_db_message_id != MessageId::min() && dialog_id.get_type() != DialogType::SecretChat) {
- LOG(INFO) << "No messages in database found";
+ LOG(INFO) << "No messages found in database";
found_dialog_messages_.erase(it);
} else {
LOG(INFO) << "Found " << res.size() << " messages out of " << message_count << " in database";
+ if (from_the_end && filter == MessageSearchFilter::Pinned) {
+ set_dialog_last_pinned_message_id(d, res.empty() ? MessageId() : res[0]);
+ }
}
promise.set_value(Unit());
}
-std::pair<int64, vector<FullMessageId>> MessagesManager::offline_search_messages(
- DialogId dialog_id, const string &query, int64 from_search_id, int32 limit,
- const tl_object_ptr<td_api::SearchMessagesFilter> &filter, int64 &random_id, Promise<> &&promise) {
+td_api::object_ptr<td_api::foundMessages> MessagesManager::get_found_messages_object(
+ const FoundMessages &found_messages, const char *source) {
+ vector<tl_object_ptr<td_api::message>> result;
+ result.reserve(found_messages.full_message_ids.size());
+ for (const auto &full_message_id : found_messages.full_message_ids) {
+ auto message = get_message_object(full_message_id, source);
+ if (message != nullptr) {
+ result.push_back(std::move(message));
+ }
+ }
+
+ return td_api::make_object<td_api::foundMessages>(found_messages.total_count, std::move(result),
+ found_messages.next_offset);
+}
+
+MessagesManager::FoundMessages MessagesManager::offline_search_messages(DialogId dialog_id, const string &query,
+ string offset, int32 limit,
+ MessageSearchFilter filter, int64 &random_id,
+ Promise<Unit> &&promise) {
+ if (!G()->parameters().use_message_db) {
+ promise.set_error(Status::Error(400, "Message database is required to search messages in secret chats"));
+ return {};
+ }
+
if (random_id != 0) {
// request has already been sent before
auto it = found_fts_messages_.find(random_id);
@@ -13587,44 +23721,55 @@ std::pair<int64, vector<FullMessageId>> MessagesManager::offline_search_messages
if (query.empty()) {
promise.set_value(Unit());
- return Auto();
+ return {};
}
- if (dialog_id != DialogId() && !have_dialog_force(dialog_id)) {
+ if (dialog_id != DialogId() && !have_dialog_force(dialog_id, "offline_search_messages")) {
promise.set_error(Status::Error(400, "Chat not found"));
- return Auto();
+ return {};
}
if (limit <= 0) {
promise.set_error(Status::Error(400, "Limit must be positive"));
- return Auto();
+ return {};
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
}
- MessagesDbFtsQuery fts_query;
+ MessageDbFtsQuery fts_query;
fts_query.query = query;
fts_query.dialog_id = dialog_id;
- fts_query.index_mask = search_messages_filter_index_mask(get_search_messages_filter(filter));
- fts_query.from_search_id = from_search_id;
+ fts_query.filter = filter;
+ if (!offset.empty()) {
+ auto r_from_search_id = to_integer_safe<int64>(offset);
+ if (r_from_search_id.is_error()) {
+ promise.set_error(Status::Error(400, "Invalid offset specified"));
+ return {};
+ }
+ fts_query.from_search_id = r_from_search_id.ok();
+ }
fts_query.limit = limit;
do {
random_id = Random::secure_int64();
- } while (random_id == 0 || found_fts_messages_.find(random_id) != found_fts_messages_.end());
+ } while (random_id == 0 || found_fts_messages_.count(random_id) > 0);
found_fts_messages_[random_id]; // reserve place for result
- G()->td_db()->get_messages_db_async()->get_messages_fts(
+ G()->td_db()->get_message_db_async()->get_messages_fts(
std::move(fts_query),
- PromiseCreator::lambda([random_id, promise = std::move(promise)](Result<MessagesDbFtsResult> fts_result) mutable {
- send_closure(G()->messages_manager(), &MessagesManager::on_messages_db_fts_result, std::move(fts_result),
- random_id, std::move(promise));
+ PromiseCreator::lambda([random_id, offset = std::move(offset), limit,
+ promise = std::move(promise)](Result<MessageDbFtsResult> fts_result) mutable {
+ send_closure(G()->messages_manager(), &MessagesManager::on_message_db_fts_result, std::move(fts_result),
+ std::move(offset), limit, random_id, std::move(promise));
}));
- return Auto();
+ return {};
}
-void MessagesManager::on_messages_db_fts_result(Result<MessagesDbFtsResult> result, int64 random_id,
- Promise<> &&promise) {
+void MessagesManager::on_message_db_fts_result(Result<MessageDbFtsResult> result, string offset, int32 limit,
+ int64 random_id, Promise<Unit> &&promise) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
if (result.is_error()) {
found_fts_messages_.erase(random_id);
return promise.set_error(result.move_as_error());
@@ -13633,24 +23778,30 @@ void MessagesManager::on_messages_db_fts_result(Result<MessagesDbFtsResult> resu
auto it = found_fts_messages_.find(random_id);
CHECK(it != found_fts_messages_.end());
- auto &res = it->second.second;
+ auto &res = it->second.full_message_ids;
res.reserve(fts_result.messages.size());
for (auto &message : fts_result.messages) {
- auto m = on_get_message_from_database(message.dialog_id, get_dialog_force(message.dialog_id), message.data);
+ auto m = on_get_message_from_database(message, false, "on_message_db_fts_result");
if (m != nullptr) {
- res.push_back(FullMessageId(message.dialog_id, m->message_id));
+ res.emplace_back(message.dialog_id, m->message_id);
}
}
- it->second.first = fts_result.next_search_id;
+ it->second.next_offset = fts_result.next_search_id <= 1 ? string() : to_string(fts_result.next_search_id);
+ it->second.total_count = offset.empty() && fts_result.messages.size() < static_cast<size_t>(limit)
+ ? static_cast<int32>(fts_result.messages.size())
+ : -1;
promise.set_value(Unit());
}
-void MessagesManager::on_messages_db_calls_result(Result<MessagesDbCallsResult> result, int64 random_id,
- MessageId first_db_message_id, SearchMessagesFilter filter,
- Promise<> &&promise) {
+void MessagesManager::on_message_db_calls_result(Result<MessageDbCallsResult> result, int64 random_id,
+ MessageId first_db_message_id, MessageSearchFilter filter,
+ Promise<Unit> &&promise) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
if (result.is_error()) {
found_call_messages_.erase(random_id);
return promise.set_error(result.move_as_error());
@@ -13661,28 +23812,29 @@ void MessagesManager::on_messages_db_calls_result(Result<MessagesDbCallsResult>
CHECK(it != found_call_messages_.end());
auto &res = it->second.second;
+ CHECK(!first_db_message_id.is_scheduled());
res.reserve(calls_result.messages.size());
for (auto &message : calls_result.messages) {
- auto m = on_get_message_from_database(message.dialog_id, get_dialog_force(message.dialog_id), message.data);
+ auto m = on_get_message_from_database(message, false, "on_message_db_calls_result");
- if (m != nullptr && first_db_message_id.get() <= m->message_id.get()) {
- res.push_back(FullMessageId(message.dialog_id, m->message_id));
+ if (m != nullptr && first_db_message_id <= m->message_id) {
+ res.emplace_back(message.dialog_id, m->message_id);
}
}
- it->second.first = calls_db_state_.message_count_by_index[search_calls_filter_index(filter)];
+ it->second.first = calls_db_state_.message_count_by_index[call_message_search_filter_index(filter)];
if (res.empty() && first_db_message_id != MessageId::min()) {
- LOG(INFO) << "No messages in database found";
+ LOG(INFO) << "No messages found in database";
found_call_messages_.erase(it);
}
promise.set_value(Unit());
}
-std::pair<int32, vector<FullMessageId>> MessagesManager::search_messages(const string &query, int32 offset_date,
- DialogId offset_dialog_id,
- MessageId offset_message_id, int32 limit,
- int64 &random_id, Promise<Unit> &&promise) {
+std::pair<int32, vector<FullMessageId>> MessagesManager::search_messages(
+ FolderId folder_id, bool ignore_folder_id, const string &query, int32 offset_date, DialogId offset_dialog_id,
+ MessageId offset_message_id, int32 limit, MessageSearchFilter filter, int32 min_date, int32 max_date,
+ int64 &random_id, Promise<Unit> &&promise) {
if (random_id != 0) {
// request has already been sent before
auto it = found_messages_.find(random_id);
@@ -13693,10 +23845,9 @@ std::pair<int32, vector<FullMessageId>> MessagesManager::search_messages(const s
return result;
}
- std::pair<int32, vector<FullMessageId>> result;
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
- return result;
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ return {};
}
if (limit > MAX_SEARCH_MESSAGES) {
limit = MAX_SEARCH_MESSAGES;
@@ -13706,41 +23857,54 @@ std::pair<int32, vector<FullMessageId>> MessagesManager::search_messages(const s
offset_date = std::numeric_limits<int32>::max();
}
if (!offset_message_id.is_valid()) {
+ if (offset_message_id.is_valid_scheduled()) {
+ promise.set_error(Status::Error(400, "Parameter offset_message_id can't be a scheduled message identifier"));
+ return {};
+ }
offset_message_id = MessageId();
}
if (offset_message_id != MessageId() && !offset_message_id.is_server()) {
promise.set_error(
- Status::Error(3, "Parameter offset_message_id must be identifier of the last found message or 0"));
- return result;
+ Status::Error(400, "Parameter offset_message_id must be identifier of the last found message or 0"));
+ return {};
}
- if (query.empty()) {
+ CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall);
+ if (filter == MessageSearchFilter::Mention || filter == MessageSearchFilter::UnreadMention ||
+ filter == MessageSearchFilter::UnreadReaction || filter == MessageSearchFilter::FailedToSend ||
+ filter == MessageSearchFilter::Pinned) {
+ promise.set_error(Status::Error(400, "The filter is not supported"));
+ return {};
+ }
+
+ if (query.empty() && filter == MessageSearchFilter::Empty) {
promise.set_value(Unit());
- return result;
+ return {};
}
do {
random_id = Random::secure_int64();
- } while (random_id == 0 || found_messages_.find(random_id) != found_messages_.end());
+ } while (random_id == 0 || found_messages_.count(random_id) > 0);
found_messages_[random_id]; // reserve place for result
- LOG(DEBUG) << "Search messages globally with query = \"" << query << "\" from date " << offset_date << ", "
- << offset_dialog_id << ", " << offset_message_id << " and with limit " << limit;
+ LOG(DEBUG) << "Search all messages filtered by " << filter << " with query = \"" << query << "\" from date "
+ << offset_date << ", " << offset_dialog_id << ", " << offset_message_id << " and limit " << limit;
td_->create_handler<SearchMessagesGlobalQuery>(std::move(promise))
- ->send(query, offset_date, offset_dialog_id, offset_message_id, limit, random_id);
- return result;
+ ->send(folder_id, ignore_folder_id, query, offset_date, offset_dialog_id, offset_message_id, limit, filter,
+ min_date, max_date, random_id);
+ return {};
}
int64 MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date, Promise<Unit> &&promise) {
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_by_date");
if (d == nullptr) {
- promise.set_error(Status::Error(5, "Chat not found"));
+ promise.set_error(Status::Error(400, "Chat not found"));
return 0;
}
if (!have_input_peer(dialog_id, AccessRights::Read)) {
- promise.set_error(Status::Error(5, "Can't access the chat"));
+ promise.set_error(Status::Error(400, "Can't access the chat"));
return 0;
}
@@ -13751,11 +23915,10 @@ int64 MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date
int64 random_id = 0;
do {
random_id = Random::secure_int64();
- } while (random_id == 0 ||
- get_dialog_message_by_date_results_.find(random_id) != get_dialog_message_by_date_results_.end());
+ } while (random_id == 0 || get_dialog_message_by_date_results_.count(random_id) > 0);
get_dialog_message_by_date_results_[random_id]; // reserve place for result
- auto message_id = find_message_by_date(d->messages, date);
+ auto message_id = find_message_by_date(d->messages.get(), date);
if (message_id.is_valid() && (message_id == d->last_message_id || get_message(d, message_id)->have_next)) {
get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id};
promise.set_value(Unit());
@@ -13764,10 +23927,10 @@ int64 MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date
if (G()->parameters().use_message_db && d->last_database_message_id != MessageId()) {
CHECK(d->first_database_message_id != MessageId());
- G()->td_db()->get_messages_db_async()->get_dialog_message_by_date(
+ G()->td_db()->get_message_db_async()->get_dialog_message_by_date(
dialog_id, d->first_database_message_id, d->last_database_message_id, date,
PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, date, random_id,
- promise = std::move(promise)](Result<BufferSlice> result) mutable {
+ promise = std::move(promise)](Result<MessageDbDialogMessage> result) mutable {
send_closure(actor_id, &MessagesManager::on_get_dialog_message_by_date_from_database, dialog_id, date,
random_id, std::move(result), std::move(promise));
}));
@@ -13777,16 +23940,61 @@ int64 MessagesManager::get_dialog_message_by_date(DialogId dialog_id, int32 date
return random_id;
}
-MessageId MessagesManager::find_message_by_date(const unique_ptr<Message> &m, int32 date) {
+void MessagesManager::run_affected_history_query_until_complete(DialogId dialog_id, AffectedHistoryQuery query,
+ bool get_affected_messages, Promise<Unit> &&promise) {
+ CHECK(!G()->close_flag());
+ auto query_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, query, get_affected_messages,
+ promise = std::move(promise)](Result<AffectedHistory> &&result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+
+ send_closure(actor_id, &MessagesManager::on_get_affected_history, dialog_id, query, get_affected_messages,
+ result.move_as_ok(), std::move(promise));
+ });
+ query(dialog_id, std::move(query_promise));
+}
+
+void MessagesManager::on_get_affected_history(DialogId dialog_id, AffectedHistoryQuery query,
+ bool get_affected_messages, AffectedHistory affected_history,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ LOG(INFO) << "Receive " << (affected_history.is_final_ ? "final " : "partial ")
+ << "affected history with pts = " << affected_history.pts_
+ << " and pts_count = " << affected_history.pts_count_;
+
+ if (affected_history.pts_count_ > 0) {
+ if (get_affected_messages) {
+ affected_history.pts_count_ = 0;
+ }
+ auto update_promise = affected_history.is_final_ ? std::move(promise) : Promise<Unit>();
+ if (dialog_id.get_type() == DialogType::Channel) {
+ add_pending_channel_update(dialog_id, make_tl_object<dummyUpdate>(), affected_history.pts_,
+ affected_history.pts_count_, std::move(update_promise), "on_get_affected_history");
+ } else {
+ td_->updates_manager_->add_pending_pts_update(make_tl_object<dummyUpdate>(), affected_history.pts_,
+ affected_history.pts_count_, Time::now(), std::move(update_promise),
+ "on_get_affected_history");
+ }
+ } else if (affected_history.is_final_) {
+ promise.set_value(Unit());
+ }
+
+ if (!affected_history.is_final_) {
+ run_affected_history_query_until_complete(dialog_id, std::move(query), get_affected_messages, std::move(promise));
+ }
+}
+
+MessageId MessagesManager::find_message_by_date(const Message *m, int32 date) {
if (m == nullptr) {
return MessageId();
}
if (m->date > date) {
- return find_message_by_date(m->left, date);
+ return find_message_by_date(m->left.get(), date);
}
- auto message_id = find_message_by_date(m->right, date);
+ auto message_id = find_message_by_date(m->right.get(), date);
if (message_id.is_valid()) {
return message_id;
}
@@ -13794,14 +24002,34 @@ MessageId MessagesManager::find_message_by_date(const unique_ptr<Message> &m, in
return m->message_id;
}
+void MessagesManager::find_messages_by_date(const Message *m, int32 min_date, int32 max_date,
+ vector<MessageId> &message_ids) {
+ if (m == nullptr) {
+ return;
+ }
+
+ if (m->date >= min_date) {
+ find_messages_by_date(m->left.get(), min_date, max_date, message_ids);
+ if (m->date <= max_date) {
+ message_ids.push_back(m->message_id);
+ }
+ }
+ if (m->date <= max_date) {
+ find_messages_by_date(m->right.get(), min_date, max_date, message_ids);
+ }
+}
+
void MessagesManager::on_get_dialog_message_by_date_from_database(DialogId dialog_id, int32 date, int64 random_id,
- Result<BufferSlice> result, Promise<Unit> promise) {
+ Result<MessageDbDialogMessage> result,
+ Promise<Unit> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
if (result.is_ok()) {
- Message *m = on_get_message_from_database(dialog_id, d, result.ok());
+ Message *m = on_get_message_from_database(d, result.ok(), false, "on_get_dialog_message_by_date_from_database");
if (m != nullptr) {
- auto message_id = find_message_by_date(d->messages, date);
+ auto message_id = find_message_by_date(d->messages.get(), date);
if (!message_id.is_valid()) {
LOG(ERROR) << "Failed to find " << m->message_id << " in " << dialog_id << " by date " << date;
message_id = m->message_id;
@@ -13820,12 +24048,12 @@ void MessagesManager::get_dialog_message_by_date_from_server(const Dialog *d, in
bool after_database_search, Promise<Unit> &&promise) {
CHECK(d != nullptr);
if (d->have_full_history) {
- // request can be always done locally/in memory. There is no need to send request to the server
+ // request can always be done locally/in memory. There is no need to send request to the server
if (after_database_search) {
return promise.set_value(Unit());
}
- auto message_id = find_message_by_date(d->messages, date);
+ auto message_id = find_message_by_date(d->messages.get(), date);
if (message_id.is_valid()) {
get_dialog_message_by_date_results_[random_id] = {d->dialog_id, message_id};
}
@@ -13841,7 +24069,10 @@ void MessagesManager::get_dialog_message_by_date_from_server(const Dialog *d, in
}
void MessagesManager::on_get_dialog_message_by_date_success(DialogId dialog_id, int32 date, int64 random_id,
- vector<tl_object_ptr<telegram_api::Message>> &&messages) {
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
auto it = get_dialog_message_by_date_results_.find(random_id);
CHECK(it != get_dialog_message_by_date_results_.end());
auto &result = it->second;
@@ -13856,26 +24087,28 @@ void MessagesManager::on_get_dialog_message_by_date_success(DialogId dialog_id,
}
if (message_date != 0 && message_date <= date) {
result = on_get_message(std::move(message), false, dialog_id.get_type() == DialogType::Channel, false, false,
- "on_get_dialog_message_by_date_success");
+ false, "on_get_dialog_message_by_date_success");
if (result != FullMessageId()) {
const Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
- auto message_id = find_message_by_date(d->messages, date);
+ auto message_id = find_message_by_date(d->messages.get(), date);
if (!message_id.is_valid()) {
LOG(ERROR) << "Failed to find " << result.get_message_id() << " in " << dialog_id << " by date " << date;
message_id = result.get_message_id();
}
get_dialog_message_by_date_results_[random_id] = {dialog_id, message_id};
// TODO result must be adjusted by local messages
+ promise.set_value(Unit());
return;
}
}
}
+ promise.set_value(Unit());
}
void MessagesManager::on_get_dialog_message_by_date_fail(int64 random_id) {
- auto erased = get_dialog_message_by_date_results_.erase(random_id);
- CHECK(erased > 0);
+ auto erased_count = get_dialog_message_by_date_results_.erase(random_id);
+ CHECK(erased_count > 0);
}
tl_object_ptr<td_api::message> MessagesManager::get_dialog_message_by_date_object(int64 random_id) {
@@ -13883,10 +24116,192 @@ tl_object_ptr<td_api::message> MessagesManager::get_dialog_message_by_date_objec
CHECK(it != get_dialog_message_by_date_results_.end());
auto full_message_id = std::move(it->second);
get_dialog_message_by_date_results_.erase(it);
- return get_message_object(full_message_id);
+ return get_message_object(full_message_id, "get_dialog_message_by_date_object");
+}
+
+void MessagesManager::get_dialog_sparse_message_positions(
+ DialogId dialog_id, MessageSearchFilter filter, MessageId from_message_id, int32 limit,
+ Promise<td_api::object_ptr<td_api::messagePositions>> &&promise) {
+ const Dialog *d = get_dialog_force(dialog_id, "get_dialog_sparse_message_positions");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (limit < 50 || limit > 2000) { // server-side limits
+ return promise.set_error(Status::Error(400, "Invalid limit specified"));
+ }
+
+ CHECK(filter != MessageSearchFilter::Call && filter != MessageSearchFilter::MissedCall);
+ if (filter == MessageSearchFilter::Empty || filter == MessageSearchFilter::Mention ||
+ filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction ||
+ filter == MessageSearchFilter::Pinned) {
+ return promise.set_error(Status::Error(400, "The filter is not supported"));
+ }
+
+ if (from_message_id.is_scheduled()) {
+ return promise.set_error(Status::Error(400, "Invalid from_message_id specified"));
+ }
+ if (!from_message_id.is_valid() || from_message_id > d->last_new_message_id) {
+ if (d->last_new_message_id.is_valid()) {
+ from_message_id = d->last_new_message_id.get_next_message_id(MessageType::Server);
+ } else {
+ from_message_id = MessageId::max();
+ }
+ } else {
+ from_message_id = from_message_id.get_next_server_message_id();
+ }
+
+ if (filter == MessageSearchFilter::FailedToSend || dialog_id.get_type() == DialogType::SecretChat) {
+ if (!G()->parameters().use_message_db) {
+ return promise.set_error(Status::Error(400, "Unsupported without message database"));
+ }
+
+ LOG(INFO) << "Get sparse message positions from database";
+ auto new_promise =
+ PromiseCreator::lambda([promise = std::move(promise)](Result<MessageDbMessagePositions> result) mutable {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+
+ auto positions = result.move_as_ok();
+ promise.set_value(td_api::make_object<td_api::messagePositions>(
+ positions.total_count, transform(positions.positions, [](const MessageDbMessagePosition &position) {
+ return td_api::make_object<td_api::messagePosition>(position.position, position.message_id.get(),
+ position.date);
+ })));
+ });
+ MessageDbGetDialogSparseMessagePositionsQuery db_query;
+ db_query.dialog_id = dialog_id;
+ db_query.filter = filter;
+ db_query.from_message_id = from_message_id;
+ db_query.limit = limit;
+ G()->td_db()->get_message_db_async()->get_dialog_sparse_message_positions(db_query, std::move(new_promise));
+ return;
+ }
+
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::Chat:
+ case DialogType::Channel:
+ td_->create_handler<GetSearchResultPositionsQuery>(std::move(promise))
+ ->send(dialog_id, filter, from_message_id, limit);
+ break;
+ case DialogType::SecretChat:
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+}
+
+void MessagesManager::on_get_dialog_sparse_message_positions(
+ DialogId dialog_id, MessageSearchFilter filter,
+ telegram_api::object_ptr<telegram_api::messages_searchResultsPositions> positions,
+ Promise<td_api::object_ptr<td_api::messagePositions>> &&promise) {
+ auto message_positions = transform(
+ positions->positions_, [](const telegram_api::object_ptr<telegram_api::searchResultPosition> &position) {
+ return td_api::make_object<td_api::messagePosition>(
+ position->offset_, MessageId(ServerMessageId(position->msg_id_)).get(), position->date_);
+ });
+ promise.set_value(td_api::make_object<td_api::messagePositions>(positions->count_, std::move(message_positions)));
+}
+
+void MessagesManager::get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, bool return_local,
+ Promise<int32> &&promise) {
+ LOG(INFO) << "Get " << (return_local ? "local " : "") << "number of messages in " << dialog_id << " filtered by "
+ << filter;
+
+ const Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_count");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (filter == MessageSearchFilter::Empty) {
+ return promise.set_error(Status::Error(400, "Can't use searchMessagesFilterEmpty"));
+ }
+
+ auto dialog_type = dialog_id.get_type();
+ int32 message_count = d->message_count_by_index[message_search_filter_index(filter)];
+ if (message_count == -1 && filter == MessageSearchFilter::UnreadMention) {
+ message_count = d->unread_mention_count;
+ }
+ if (message_count == -1 && filter == MessageSearchFilter::UnreadReaction) {
+ message_count = d->unread_reaction_count;
+ }
+ if (message_count != -1 || return_local || dialog_type == DialogType::SecretChat ||
+ filter == MessageSearchFilter::FailedToSend) {
+ return promise.set_value(std::move(message_count));
+ }
+
+ get_dialog_message_count_from_server(dialog_id, filter, std::move(promise));
+}
+
+void MessagesManager::get_dialog_message_count_from_server(DialogId dialog_id, MessageSearchFilter filter,
+ Promise<int32> &&promise) {
+ LOG(INFO) << "Get number of messages in " << dialog_id << " filtered by " << filter << " from the server";
+
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::Chat:
+ case DialogType::Channel:
+ td_->create_handler<GetSearchCountersQuery>(std::move(promise))->send(dialog_id, filter);
+ break;
+ case DialogType::None:
+ case DialogType::SecretChat:
+ default:
+ UNREACHABLE();
+ }
+}
+
+void MessagesManager::get_dialog_message_position(FullMessageId full_message_id, MessageSearchFilter filter,
+ MessageId top_thread_message_id, Promise<int32> &&promise) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "get_dialog_message_position");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ auto message_id = full_message_id.get_message_id();
+ const Message *m = get_message_force(d, message_id, "get_dialog_message_position");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ if (!m->message_id.is_valid() || !m->message_id.is_server() ||
+ (filter != MessageSearchFilter::Empty &&
+ (get_message_index_mask(d->dialog_id, m) & message_search_filter_index_mask(filter)) == 0)) {
+ return promise.set_error(Status::Error(400, "Message can't be found in the filter"));
+ }
+
+ if (top_thread_message_id != MessageId()) {
+ if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Invalid message thread identifier specified"));
+ }
+ if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
+ return promise.set_error(Status::Error(400, "Can't filter by message thread identifier in the chat"));
+ }
+ if (m->top_thread_message_id != top_thread_message_id ||
+ (m->message_id == top_thread_message_id && !m->is_topic_message)) {
+ return promise.set_error(Status::Error(400, "Message doesn't belong to the message thread"));
+ }
+ }
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ return promise.set_error(Status::Error(400, "The method can't be used in secret chats"));
+ }
+
+ if (filter == MessageSearchFilter::UnreadMention || filter == MessageSearchFilter::UnreadReaction ||
+ filter == MessageSearchFilter::FailedToSend) {
+ return promise.set_error(Status::Error(400, "The filter is not supported"));
+ }
+
+ td_->create_handler<GetMessagePositionQuery>(std::move(promise))
+ ->send(dialog_id, message_id, filter, top_thread_message_id);
}
void MessagesManager::preload_newer_messages(const Dialog *d, MessageId max_message_id) {
+ CHECK(d != nullptr);
+ CHECK(max_message_id.is_valid());
if (td_->auth_manager_->is_bot()) {
return;
}
@@ -13899,14 +24314,16 @@ void MessagesManager::preload_newer_messages(const Dialog *d, MessageId max_mess
max_message_id = (*p)->message_id;
}
}
- if (limit > 0 && (d->last_message_id == MessageId() || max_message_id.get() < d->last_message_id.get())) {
+ if (limit > 0 && (d->last_message_id == MessageId() || max_message_id < d->last_message_id)) {
// need to preload some new messages
LOG(INFO) << "Preloading newer after " << max_message_id;
- load_messages(d->dialog_id, max_message_id, -MAX_GET_HISTORY + 1, MAX_GET_HISTORY, 3, false, Promise<Unit>());
+ load_messages_impl(d, max_message_id, -MAX_GET_HISTORY + 1, MAX_GET_HISTORY, 3, false, Promise<Unit>());
}
}
void MessagesManager::preload_older_messages(const Dialog *d, MessageId min_message_id) {
+ CHECK(d != nullptr);
+ CHECK(min_message_id.is_valid());
if (td_->auth_manager_->is_bot()) {
return;
}
@@ -13926,15 +24343,104 @@ void MessagesManager::preload_older_messages(const Dialog *d, MessageId min_mess
if (limit > 0) {
// need to preload some old messages
LOG(INFO) << "Preloading older before " << min_message_id;
- load_messages(d->dialog_id, min_message_id, 0, MAX_GET_HISTORY / 2, 3, false, Promise<Unit>());
+ load_messages_impl(d, min_message_id, 0, MAX_GET_HISTORY / 2, 3, false, Promise<Unit>());
}
}
-void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId from_message_id, int32 offset,
- int32 limit, bool from_the_end, bool only_local,
- vector<BufferSlice> &&messages, Promise<Unit> &&promise) {
+unique_ptr<MessagesManager::Message> MessagesManager::parse_message(Dialog *d, MessageId expected_message_id,
+ const BufferSlice &value, bool is_scheduled) {
+ CHECK(d != nullptr);
+ auto dialog_id = d->dialog_id;
+ auto m = make_unique<Message>();
+
+ auto status = log_event_parse(*m, value.as_slice());
+ bool is_message_id_valid = [&] {
+ if (is_scheduled) {
+ if (!expected_message_id.is_valid_scheduled()) {
+ return false;
+ }
+ if (m->message_id == expected_message_id) {
+ return true;
+ }
+ return m->message_id.is_valid_scheduled() && expected_message_id.is_scheduled_server() &&
+ m->message_id.is_scheduled_server() &&
+ m->message_id.get_scheduled_server_message_id() == expected_message_id.get_scheduled_server_message_id();
+ } else {
+ if (!expected_message_id.is_valid()) {
+ return false;
+ }
+ return m->message_id == expected_message_id;
+ }
+ }();
+ if (status.is_error() || !is_message_id_valid) {
+ // can't happen unless the database is broken, but has been seen in the wild
+ LOG(ERROR) << "Receive invalid message from database: " << expected_message_id << ' ' << m->message_id << ' '
+ << status << ' ' << format::as_hex_dump<4>(value.as_slice());
+ if (!is_scheduled && dialog_id.get_type() != DialogType::SecretChat) {
+ // trying to repair the message
+ if (expected_message_id.is_valid() && expected_message_id.is_server()) {
+ get_message_from_server({dialog_id, expected_message_id}, Auto(), "parse_message");
+ }
+ if (m->message_id.is_valid() && m->message_id.is_server()) {
+ get_message_from_server({dialog_id, m->message_id}, Auto(), "parse_message");
+ }
+ }
+ return nullptr;
+ }
+ if (m->reactions != nullptr) {
+ if (m->available_reactions_generation < d->available_reactions_generation) {
+ m->reactions = nullptr;
+ m->available_reactions_generation = 0;
+ } else if (m->available_reactions_generation > d->available_reactions_generation &&
+ m->available_reactions_generation - d->available_reactions_generation < 1000000000) {
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat:
+ case DialogType::Channel:
+ LOG(ERROR) << "Fix available_reactions_generation in " << dialog_id << " from "
+ << d->available_reactions_generation << " to " << m->available_reactions_generation;
+ hide_dialog_message_reactions(d);
+ set_dialog_next_available_reactions_generation(d, m->available_reactions_generation);
+ on_dialog_updated(dialog_id, "parse_message");
+ break;
+ case DialogType::User:
+ case DialogType::SecretChat:
+ default:
+ LOG(ERROR) << "Receive available_reactions_generation = " << m->available_reactions_generation << " in "
+ << m->message_id << " in " << dialog_id;
+ break;
+ }
+ }
+ }
+ if (m->history_generation > d->history_generation && m->history_generation - d->history_generation < 1000000000) {
+ switch (dialog_id.get_type()) {
+ case DialogType::Channel:
+ LOG(ERROR) << "Fix history_generation in " << dialog_id << " from " << d->history_generation << " to "
+ << m->history_generation;
+ d->history_generation = m->history_generation + 1;
+ on_dialog_updated(dialog_id, "parse_message");
+ break;
+ case DialogType::Chat:
+ case DialogType::User:
+ case DialogType::SecretChat:
+ default:
+ LOG(ERROR) << "Receive history_generation = " << m->history_generation << " in " << m->message_id << " in "
+ << dialog_id;
+ break;
+ }
+ }
+
+ LOG(INFO) << "Loaded " << m->message_id << " in " << dialog_id << " of size " << value.size() << " from database";
+ return m;
+}
+
+void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId from_message_id,
+ MessageId old_last_database_message_id, int32 offset, int32 limit,
+ bool from_the_end, bool only_local,
+ vector<MessageDbDialogMessage> &&messages, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
CHECK(-limit < offset && offset <= 0);
CHECK(offset < 0 || from_the_end);
+ CHECK(!from_message_id.is_scheduled());
if (!have_input_peer(dialog_id, AccessRights::Read)) {
LOG(WARNING) << "Ignore result of get_history_from_database in " << dialog_id;
@@ -13948,27 +24454,72 @@ void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId
LOG(INFO) << "Receive " << messages.size() << " history messages from database "
<< (from_the_end ? "from the end " : "") << "in " << dialog_id << " from " << from_message_id
<< " with offset " << offset << " and limit " << limit << ". First database message is "
- << d->first_database_message_id << ", have_full_history = " << d->have_full_history;
+ << d->first_database_message_id << ", last database message is " << d->last_database_message_id
+ << ", have_full_history = " << d->have_full_history
+ << ", have_full_history_source = " << d->have_full_history_source;
- if (messages.empty() && from_the_end && d->have_full_history) {
- set_dialog_is_empty(d, "on_get_history_from_database empty");
+ if (old_last_database_message_id < d->last_database_message_id && old_last_database_message_id < from_message_id) {
+ // new messages where added to the database since the request was sent
+ // they should have been received from the database, so we must repeat the request to get them
+ if (from_the_end) {
+ get_history_from_the_end_impl(d, true, only_local, std::move(promise), "on_get_history_from_database 20");
+ } else {
+ get_history_impl(d, from_message_id, offset, limit, true, only_local, std::move(promise));
+ }
+ return;
+ }
+
+ if (messages.empty() && from_the_end && d->messages == nullptr) {
+ if (d->have_full_history) {
+ set_dialog_is_empty(d, "on_get_history_from_database empty");
+ } else if (d->last_database_message_id.is_valid()) {
+ set_dialog_first_database_message_id(d, MessageId(), "on_get_history_from_database empty");
+ set_dialog_last_database_message_id(d, MessageId(), "on_get_history_from_database empty");
+ }
}
bool have_next = false;
bool need_update = false;
bool need_update_dialog_pos = false;
bool added_new_message = false;
+ MessageId first_added_message_id;
MessageId last_added_message_id;
Message *next_message = nullptr;
Dependencies dependencies;
bool is_first = true;
+ bool had_full_history = d->have_full_history;
+ auto debug_first_database_message_id = d->first_database_message_id;
+ auto debug_last_message_id = d->last_message_id;
+ auto debug_last_new_message_id = d->last_new_message_id;
+ auto first_received_message_id = MessageId::max();
+ MessageId last_received_message_id;
+ size_t pos = 0;
for (auto &message_slice : messages) {
if (!d->first_database_message_id.is_valid() && !d->have_full_history) {
break;
}
- auto message = make_unique<Message>();
- log_event_parse(*message, message_slice.as_slice()).ensure();
- if (message->message_id.get() < d->first_database_message_id.get()) {
+ auto message = parse_message(d, message_slice.message_id, message_slice.data, false);
+ if (message == nullptr) {
+ if (d->have_full_history) {
+ d->have_full_history = false;
+ d->have_full_history_source = 0;
+ d->is_empty = false; // just in case
+ on_dialog_updated(dialog_id, "drop have_full_history in on_get_history_from_database");
+ }
+ break;
+ }
+ if (message->message_id >= first_received_message_id) {
+ LOG(ERROR) << "Receive " << message->message_id << " after " << first_received_message_id
+ << " from database in the history of " << dialog_id << " from " << from_message_id << " with offset "
+ << offset << ", limit " << limit << ", from_the_end = " << from_the_end;
+ break;
+ }
+ first_received_message_id = message->message_id;
+ if (!last_received_message_id.is_valid()) {
+ last_received_message_id = message->message_id;
+ }
+
+ if (message->message_id < d->first_database_message_id) {
if (d->have_full_history) {
LOG(ERROR) << "Have full history in the " << dialog_id << " and receive " << message->message_id
<< " from database, but first database message is " << d->first_database_message_id;
@@ -13976,9 +24527,8 @@ void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId
break;
}
}
- if (!have_next &&
- (from_the_end || (is_first && offset < -1 && message->message_id.get() <= from_message_id.get())) &&
- message->message_id.get() < d->last_message_id.get()) {
+ if (!have_next && (from_the_end || (is_first && offset < -1 && message->message_id <= from_message_id)) &&
+ message->message_id < d->last_message_id) {
// last message in the dialog must be attached to the next local message
have_next = true;
}
@@ -13988,69 +24538,153 @@ void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId
message->from_database = true;
auto old_message = get_message(d, message->message_id);
- if (old_message == nullptr && message->content->get_id() == MessageText::ID) {
- auto web_page_id = static_cast<const MessageText *>(message->content.get())->web_page_id;
- if (web_page_id.is_valid()) {
- td_->web_pages_manager_->have_web_page_force(web_page_id);
- }
- }
Message *m = old_message ? old_message
: add_message_to_dialog(d, std::move(message), false, &need_update,
&need_update_dialog_pos, "on_get_history_from_database");
if (m != nullptr) {
- if (!have_next) {
+ first_added_message_id = m->message_id;
+ if (!last_added_message_id.is_valid()) {
last_added_message_id = m->message_id;
}
if (old_message == nullptr) {
- add_message_dependencies(dependencies, dialog_id, m);
+ add_message_dependencies(dependencies, m);
added_new_message = true;
} else if (m->message_id != from_message_id) {
added_new_message = true;
}
if (next_message != nullptr && !next_message->have_previous) {
+ LOG_CHECK(m->message_id < next_message->message_id)
+ << m->message_id << ' ' << next_message->message_id << ' ' << first_received_message_id << ' '
+ << last_received_message_id << ' ' << dialog_id << ' ' << from_message_id << ' ' << offset << ' ' << limit
+ << ' ' << from_the_end << ' ' << only_local << ' ' << messages.size() << ' '
+ << debug_first_database_message_id << ' ' << last_added_message_id << ' ' << added_new_message << ' ' << pos
+ << ' ' << m << ' ' << next_message << ' ' << old_message << ' '
+ << to_string(get_message_object(dialog_id, m, "on_get_history_from_database"))
+ << to_string(get_message_object(dialog_id, next_message, "on_get_history_from_database"));
LOG(INFO) << "Fix have_previous for " << next_message->message_id;
next_message->have_previous = true;
- attach_message_to_previous(d, next_message->message_id);
+ attach_message_to_previous(
+ d, next_message->message_id,
+ (PSLICE() << "on_get_history_from_database 1 " << m->message_id << ' ' << from_message_id << ' ' << offset
+ << ' ' << limit << ' ' << d->first_database_message_id << ' ' << d->have_full_history << ' '
+ << pos)
+ .c_str());
}
have_next = true;
next_message = m;
}
is_first = false;
+ pos++;
+ }
+ dependencies.resolve_force(td_, "on_get_history_from_database");
+
+ if (from_the_end && !last_added_message_id.is_valid() && d->first_database_message_id.is_valid() &&
+ !d->have_full_history) {
+ if (first_received_message_id <= d->first_database_message_id) {
+ // database definitely has no messages from first_database_message_id to last_database_message_id; drop them
+ set_dialog_first_database_message_id(d, MessageId(), "on_get_history_from_database 8");
+ set_dialog_last_database_message_id(d, MessageId(), "on_get_history_from_database 9");
+ } else {
+ CHECK(first_received_message_id.is_valid());
+ // if a message was received, but wasn't added, then it is likely to be already deleted
+ // if it is less than d->last_database_message_id, then we can adjust d->last_database_message_id and
+ // try again database search without chance to loop
+ if (first_received_message_id < d->last_database_message_id) {
+ set_dialog_last_database_message_id(d, first_received_message_id, "on_get_history_from_database 12");
+
+ get_history_from_the_end_impl(d, true, only_local, std::move(promise), "on_get_history_from_database 21");
+ return;
+ }
+
+ if (limit > 1) {
+ // we expected to have messages [first_database_message_id, last_database_message_id] in the database, but
+ // received messages [first_received_message_id, last_received_message_id], none of which can be added
+ // first_database_message_id and last_database_message_id are very wrong, so it is better to drop them,
+ // pretending that the database has no usable messages
+ if (!is_deleted_message(d, d->first_database_message_id) ||
+ !is_deleted_message(d, d->last_database_message_id)) {
+ if (first_received_message_id == MessageId::max()) {
+ CHECK(last_received_message_id == MessageId());
+ LOG(ERROR) << "Receive no usable messages in " << dialog_id
+ << " from database from the end, but expected messages from " << d->first_database_message_id
+ << " up to " << d->last_database_message_id
+ << ". Have old last_database_message_id = " << old_last_database_message_id << " and "
+ << messages.size() << " received messages";
+ } else {
+ LOG(ERROR) << "Receive " << messages.size() << " unusable messages [" << first_received_message_id
+ << " ... " << last_received_message_id << "] in " << dialog_id
+ << " from database from the end, but expected messages from " << d->first_database_message_id
+ << " up to " << d->last_database_message_id;
+ }
+ }
+ set_dialog_first_database_message_id(d, MessageId(), "on_get_history_from_database 13");
+ set_dialog_last_database_message_id(d, MessageId(), "on_get_history_from_database 14");
+ }
+ }
}
- resolve_dependencies_force(dependencies);
if (!added_new_message && !only_local && dialog_id.get_type() != DialogType::SecretChat) {
if (from_the_end) {
from_message_id = MessageId();
}
- load_messages(dialog_id, from_message_id, offset, limit, 1, false, std::move(promise));
+ load_messages_impl(d, from_message_id, offset, limit, 1, false, std::move(promise));
return;
}
if (from_the_end && last_added_message_id.is_valid()) {
+ CHECK(next_message != nullptr);
// CHECK(d->first_database_message_id.is_valid());
- // CHECK(last_added_message_id.get() >= d->first_database_message_id.get());
- if (last_added_message_id.get() > d->last_message_id.get() && d->last_new_message_id.is_valid()) {
- set_dialog_last_message_id(d, last_added_message_id, "on_get_history_from_database");
+ // CHECK(last_added_message_id >= d->first_database_message_id);
+ if ((had_full_history || d->have_full_history) && !d->last_new_message_id.is_valid() &&
+ (last_added_message_id.is_server() || d->dialog_id.get_type() == DialogType::SecretChat)) {
+ LOG(ERROR) << "Trying to hard fix " << d->dialog_id << " last new message to " << last_added_message_id
+ << " from on_get_history_from_database 2";
+ d->last_new_message_id = last_added_message_id;
+ on_dialog_updated(d->dialog_id, "on_get_history_from_database 3");
+ }
+ if (last_added_message_id > d->last_message_id && d->last_new_message_id.is_valid()) {
+ set_dialog_last_message_id(d, last_added_message_id, "on_get_history_from_database 4");
need_update_dialog_pos = true;
}
- if (last_added_message_id.get() != d->last_database_message_id.get()) {
- set_dialog_last_database_message_id(d, last_added_message_id, "on_get_history_from_database");
- if (last_added_message_id.get() < d->first_database_message_id.get() ||
- !d->first_database_message_id.is_valid()) {
+ if (last_added_message_id != d->last_database_message_id && d->last_new_message_id.is_valid()) {
+ auto debug_last_database_message_id = d->last_database_message_id;
+ auto debug_set_dialog_last_database_message_id = d->debug_set_dialog_last_database_message_id;
+ if (!d->first_database_message_id.is_valid() && !d->last_database_message_id.is_valid()) {
+ set_dialog_first_database_message_id(d, next_message->message_id, "on_get_history_from_database 5");
+ }
+ set_dialog_last_database_message_id(d, last_added_message_id, "on_get_history_from_database 5");
+ if (last_added_message_id < d->first_database_message_id || !d->first_database_message_id.is_valid()) {
CHECK(next_message != nullptr);
- CHECK(d->have_full_history);
- LOG(ERROR) << "Fix first database message id in " << dialog_id << " from " << d->first_database_message_id
+ LOG_CHECK(had_full_history || d->have_full_history)
+ << had_full_history << ' ' << d->have_full_history << ' ' << next_message->message_id << ' '
+ << last_added_message_id << ' ' << d->first_database_message_id << ' ' << debug_first_database_message_id
+ << ' ' << d->last_database_message_id << ' ' << debug_last_database_message_id << ' ' << dialog_id << ' '
+ << d->last_new_message_id << ' ' << debug_last_new_message_id << ' ' << d->last_message_id << ' '
+ << debug_last_message_id << ' ' << debug_set_dialog_last_database_message_id << ' '
+ << d->debug_set_dialog_last_database_message_id << ' ' << first_received_message_id << ' '
+ << last_received_message_id << ' ' << d->debug_first_database_message_id << ' '
+ << d->debug_last_database_message_id << ' ' << d->debug_last_new_message_id << ' '
+ << d->have_full_history_source;
+ CHECK(next_message->message_id <= d->last_database_message_id);
+ LOG(ERROR) << "Fix first database message in " << dialog_id << " from " << d->first_database_message_id
<< " to " << next_message->message_id;
- set_dialog_first_database_message_id(d, next_message->message_id, "on_get_history_from_database");
+ set_dialog_first_database_message_id(d, next_message->message_id, "on_get_history_from_database 6");
}
- on_dialog_updated(dialog_id, "on_get_history_from_database");
+ }
+ }
+ if (first_added_message_id.is_valid() && first_added_message_id != d->first_database_message_id &&
+ first_received_message_id < d->first_database_message_id && d->last_new_message_id.is_valid() &&
+ !d->have_full_history) {
+ CHECK(first_added_message_id > d->first_database_message_id);
+ set_dialog_first_database_message_id(d, first_added_message_id, "on_get_history_from_database 10");
+ if (d->last_database_message_id < d->first_database_message_id) {
+ set_dialog_last_database_message_id(d, d->first_database_message_id, "on_get_history_from_database 11");
}
}
if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "on_get_history_from_database");
+ send_update_chat_last_message(d, "on_get_history_from_database 7");
}
promise.set_value(Unit());
@@ -14058,56 +24692,96 @@ void MessagesManager::on_get_history_from_database(DialogId dialog_id, MessageId
void MessagesManager::get_history_from_the_end(DialogId dialog_id, bool from_database, bool only_local,
Promise<Unit> &&promise) {
- CHECK(dialog_id.is_valid());
+ get_history_from_the_end_impl(get_dialog(dialog_id), from_database, only_local, std::move(promise),
+ "get_history_from_the_end");
+}
+
+void MessagesManager::get_history_from_the_end_impl(const Dialog *d, bool from_database, bool only_local,
+ Promise<Unit> &&promise, const char *source) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ CHECK(d != nullptr);
+
+ auto dialog_id = d->dialog_id;
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// can't get history in dialogs without read access
return promise.set_value(Unit());
}
- const int32 limit = MAX_GET_HISTORY;
+ if (!d->first_database_message_id.is_valid() && !d->have_full_history) {
+ from_database = false;
+ }
+ int32 limit = MAX_GET_HISTORY;
if (from_database && G()->parameters().use_message_db) {
- LOG(INFO) << "Get history from the end of " << dialog_id << " from database";
- MessagesDbMessagesQuery db_query;
+ if (!promise) {
+ // repair last database message ID
+ limit = 10;
+ }
+ LOG(INFO) << "Get history from the end of " << dialog_id << " from database from " << source;
+ MessageDbMessagesQuery db_query;
db_query.dialog_id = dialog_id;
db_query.from_message_id = MessageId::max();
db_query.limit = limit;
- G()->td_db()->get_messages_db_async()->get_messages(
- db_query, PromiseCreator::lambda([dialog_id, only_local, limit, actor_id = actor_id(this),
- promise = std::move(promise)](MessagesDbMessagesResult result) mutable {
- send_closure(actor_id, &MessagesManager::on_get_history_from_database, dialog_id, MessageId::max(), 0, limit,
- true, only_local, std::move(result.messages), std::move(promise));
+ G()->td_db()->get_message_db_async()->get_messages(
+ db_query, PromiseCreator::lambda([dialog_id, old_last_database_message_id = d->last_database_message_id,
+ only_local, limit, actor_id = actor_id(this), promise = std::move(promise)](
+ vector<MessageDbDialogMessage> messages) mutable {
+ send_closure(actor_id, &MessagesManager::on_get_history_from_database, dialog_id, MessageId::max(),
+ old_last_database_message_id, 0, limit, true, only_local, std::move(messages),
+ std::move(promise));
}));
} else {
- if (only_local || dialog_id.get_type() == DialogType::SecretChat) {
+ if (only_local || dialog_id.get_type() == DialogType::SecretChat || d->last_message_id.is_valid()) {
+ // if last message is known, there are no reasons to get message history from server from the end
promise.set_value(Unit());
return;
}
+ if (!promise && !G()->parameters().use_message_db) {
+ // repair last message ID
+ limit = 10;
+ }
- LOG(INFO) << "Get history from the end of " << dialog_id << " from server";
- td_->create_handler<GetHistoryQuery>(std::move(promise))->send_get_from_the_end(dialog_id, limit);
+ LOG(INFO) << "Get history from the end of " << dialog_id << " from server from " << source;
+ td_->create_handler<GetHistoryQuery>(std::move(promise))
+ ->send_get_from_the_end(dialog_id, d->last_new_message_id, limit);
}
}
void MessagesManager::get_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit,
bool from_database, bool only_local, Promise<Unit> &&promise) {
- CHECK(dialog_id.is_valid());
+ get_history_impl(get_dialog(dialog_id), from_message_id, offset, limit, from_database, only_local,
+ std::move(promise));
+}
+
+void MessagesManager::get_history_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit,
+ bool from_database, bool only_local, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ CHECK(d != nullptr);
+ CHECK(from_message_id.is_valid());
+
+ auto dialog_id = d->dialog_id;
if (!have_input_peer(dialog_id, AccessRights::Read)) {
// can't get history in dialogs without read access
return promise.set_value(Unit());
}
+ if ((!d->first_database_message_id.is_valid() || from_message_id <= d->first_database_message_id) &&
+ !d->have_full_history) {
+ from_database = false;
+ }
if (from_database && G()->parameters().use_message_db) {
LOG(INFO) << "Get history in " << dialog_id << " from " << from_message_id << " with offset " << offset
<< " and limit " << limit << " from database";
- MessagesDbMessagesQuery db_query;
+ MessageDbMessagesQuery db_query;
db_query.dialog_id = dialog_id;
db_query.from_message_id = from_message_id;
db_query.offset = offset;
db_query.limit = limit;
- G()->td_db()->get_messages_db_async()->get_messages(
+ G()->td_db()->get_message_db_async()->get_messages(
db_query,
- PromiseCreator::lambda([dialog_id, from_message_id, offset, limit, only_local, actor_id = actor_id(this),
- promise = std::move(promise)](MessagesDbMessagesResult result) mutable {
- send_closure(actor_id, &MessagesManager::on_get_history_from_database, dialog_id, from_message_id, offset,
- limit, false, only_local, std::move(result.messages), std::move(promise));
+ PromiseCreator::lambda([dialog_id, from_message_id, old_last_database_message_id = d->last_database_message_id,
+ offset, limit, only_local, actor_id = actor_id(this),
+ promise = std::move(promise)](vector<MessageDbDialogMessage> messages) mutable {
+ send_closure(actor_id, &MessagesManager::on_get_history_from_database, dialog_id, from_message_id,
+ old_last_database_message_id, offset, limit, false, only_local, std::move(messages),
+ std::move(promise));
}));
} else {
if (only_local || dialog_id.get_type() == DialogType::SecretChat) {
@@ -14117,32 +24791,38 @@ void MessagesManager::get_history(DialogId dialog_id, MessageId from_message_id,
LOG(INFO) << "Get history in " << dialog_id << " from " << from_message_id << " with offset " << offset
<< " and limit " << limit << " from server";
td_->create_handler<GetHistoryQuery>(std::move(promise))
- ->send(dialog_id, from_message_id.get_next_server_message_id(), offset, limit);
+ ->send(dialog_id, from_message_id.get_next_server_message_id(), d->last_new_message_id, offset, limit);
}
}
void MessagesManager::load_messages(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit,
int left_tries, bool only_local, Promise<Unit> &&promise) {
- LOG(INFO) << "Load " << (only_local ? "local " : "") << "messages in " << dialog_id << " from " << from_message_id
- << " with offset = " << offset << " and limit = " << limit << ". " << left_tries << " tries left";
+ load_messages_impl(get_dialog(dialog_id), from_message_id, offset, limit, left_tries, only_local, std::move(promise));
+}
+
+void MessagesManager::load_messages_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit,
+ int left_tries, bool only_local, Promise<Unit> &&promise) {
+ CHECK(d != nullptr);
CHECK(offset <= 0);
CHECK(left_tries > 0);
+ auto dialog_id = d->dialog_id;
+ LOG(INFO) << "Load " << (only_local ? "local " : "") << "messages in " << dialog_id << " from " << from_message_id
+ << " with offset = " << offset << " and limit = " << limit << ". " << left_tries << " tries left";
only_local |= dialog_id.get_type() == DialogType::SecretChat;
- if (!only_local) {
- Dialog *d = get_dialog(dialog_id);
- if (d != nullptr && d->have_full_history) {
- LOG(INFO) << "Have full history in " << dialog_id << ", so don't need to get chat history from server";
- only_local = true;
- }
+ if (!only_local && d->have_full_history) {
+ LOG(INFO) << "Have full history in " << dialog_id << ", so don't need to get chat history from server";
+ only_local = true;
}
bool from_database = (left_tries > 2 || only_local) && G()->parameters().use_message_db;
- // TODO do not send requests to database if (from_message_id < d->first_database_message_id ||
- // !d->first_database_message_id.is_valid()) && !d->have_full_history
if (from_message_id == MessageId()) {
- get_history_from_the_end(dialog_id, from_database, only_local, std::move(promise));
+ get_history_from_the_end_impl(d, from_database, only_local, std::move(promise), "load_messages_impl");
return;
}
+ if ((!d->first_database_message_id.is_valid() || from_message_id <= d->first_database_message_id) &&
+ !d->have_full_history) {
+ from_database = false;
+ }
if (offset >= -1) {
// get history before some server or local message
limit = min(max(limit + offset + 1, MAX_GET_HISTORY / 2), MAX_GET_HISTORY);
@@ -14150,659 +24830,933 @@ void MessagesManager::load_messages(DialogId dialog_id, MessageId from_message_i
} else {
// get history around some server or local message
int32 messages_to_load = max(MAX_GET_HISTORY, limit);
- int32 max_add = messages_to_load - limit;
+ int32 max_add = max(messages_to_load - limit - 2, 0);
offset -= max_add;
limit = MAX_GET_HISTORY;
}
- get_history(dialog_id, from_message_id, offset, limit, from_database, only_local, std::move(promise));
-}
-
-tl_object_ptr<td_api::message> MessagesManager::get_message_object(FullMessageId full_message_id) {
- auto dialog_id = full_message_id.get_dialog_id();
- Dialog *d = get_dialog(dialog_id);
- return get_message_object(dialog_id, get_message_force(d, full_message_id.get_message_id()));
+ get_history_impl(d, from_message_id, offset, limit, from_database, only_local, std::move(promise));
}
-tl_object_ptr<td_api::message> MessagesManager::get_message_object(DialogId dialog_id, const Message *message) const {
- if (message == nullptr) {
- return nullptr;
+vector<MessageId> MessagesManager::get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result,
+ Promise<Unit> &&promise) {
+ if (G()->close_flag()) {
+ promise.set_error(Global::request_aborted_error());
+ return {};
}
- // TODO get_message_sending_state_object
- tl_object_ptr<td_api::MessageSendingState> sending_state;
- if (message->is_failed_to_send) {
- sending_state = make_tl_object<td_api::messageSendingStateFailed>();
- } else if (message->message_id.is_yet_unsent()) {
- sending_state = make_tl_object<td_api::messageSendingStatePending>();
+ LOG(INFO) << "Get scheduled messages in " << dialog_id;
+ Dialog *d = get_dialog_force(dialog_id, "get_dialog_scheduled_messages");
+ if (d == nullptr) {
+ promise.set_error(Status::Error(400, "Chat not found"));
+ return {};
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ promise.set_error(Status::Error(400, "Can't access the chat"));
+ return {};
+ }
+ if (is_broadcast_channel(dialog_id) &&
+ !td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_post_messages()) {
+ promise.set_error(Status::Error(400, "Not enough rights to get scheduled messages"));
+ return {};
}
- bool can_delete = true;
- auto dialog_type = dialog_id.get_type();
- if (dialog_type == DialogType::Channel) {
- auto dialog_status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
- can_delete = can_delete_channel_message(dialog_status, message, td_->auth_manager_->is_bot());
+ if (!d->has_loaded_scheduled_messages_from_database) {
+ load_dialog_scheduled_messages(dialog_id, true, 0, std::move(promise));
+ return {};
}
- DialogId my_dialog_id(td_->contacts_manager_->get_my_id("get_message_object"));
- bool can_delete_for_self = false;
- bool can_delete_for_all_users = can_delete && can_revoke_message(dialog_id, message);
- if (can_delete) {
- switch (dialog_type) {
- case DialogType::User:
- case DialogType::Chat:
- // TODO allow to delete yet unsent message just for self
- can_delete_for_self = !message->message_id.is_yet_unsent() || dialog_id == my_dialog_id;
- break;
- case DialogType::Channel:
- case DialogType::SecretChat:
- can_delete_for_self = !can_delete_for_all_users;
+ vector<MessageId> message_ids;
+ find_old_messages(d->scheduled_messages.get(),
+ MessageId(ScheduledServerMessageId(), std::numeric_limits<int32>::max(), true), message_ids);
+ std::reverse(message_ids.begin(), message_ids.end());
+
+ if (G()->parameters().use_message_db) {
+ bool has_scheduled_database_messages = false;
+ for (auto &message_id : message_ids) {
+ CHECK(message_id.is_valid_scheduled());
+ if (!message_id.is_yet_unsent()) {
+ has_scheduled_database_messages = true;
break;
- case DialogType::None:
- default:
- UNREACHABLE();
+ }
}
+ set_dialog_has_scheduled_database_messages(d->dialog_id, has_scheduled_database_messages);
}
- bool is_outgoing = message->is_outgoing || sending_state != nullptr;
- if (dialog_id == my_dialog_id) {
- // in Saved Messages all messages without forward_info->from_dialog_id should be outgoing
- if (message->forward_info == nullptr || !message->forward_info->from_dialog_id.is_valid()) {
- is_outgoing = true;
+ if (d->scheduled_messages_sync_generation != scheduled_messages_sync_generation_) {
+ vector<uint64> numbers;
+ for (auto &message_id : message_ids) {
+ if (!message_id.is_scheduled_server()) {
+ continue;
+ }
+
+ numbers.push_back(message_id.get_scheduled_server_message_id().get());
+ const Message *m = get_message(d, message_id);
+ CHECK(m != nullptr);
+ CHECK(m->message_id.get_scheduled_server_message_id() == message_id.get_scheduled_server_message_id());
+ numbers.push_back(m->edit_date);
+ numbers.push_back(m->date);
}
+ auto hash = get_vector_hash(numbers);
+
+ if (!force && (d->has_scheduled_server_messages ||
+ (d->scheduled_messages_sync_generation == 0 && !G()->parameters().use_message_db))) {
+ load_dialog_scheduled_messages(dialog_id, false, hash, std::move(promise));
+ return {};
+ }
+ load_dialog_scheduled_messages(dialog_id, false, hash, Promise<Unit>());
}
- return make_tl_object<td_api::message>(
- message->message_id.get(), td_->contacts_manager_->get_user_id_object(message->sender_user_id, "sender_user_id"),
- dialog_id.get(), std::move(sending_state), is_outgoing, can_edit_message(dialog_id, message, false, true),
- can_forward_message(dialog_id, message), can_delete_for_self, can_delete_for_all_users, message->is_channel_post,
- message->contains_unread_mention, message->date, message->edit_date,
- get_message_forward_info_object(message->forward_info), message->reply_to_message_id.get(), message->ttl,
- message->ttl_expires_at != 0 ? max(message->ttl_expires_at - Time::now(), 1e-3) : message->ttl,
- td_->contacts_manager_->get_user_id_object(message->via_bot_user_id, "via_bot_user_id"),
- message->author_signature, message->views, message->media_album_id,
- get_message_content_object(message->content.get(), message->date, message->is_content_secret),
- get_reply_markup_object(message->reply_markup));
-}
-tl_object_ptr<td_api::messages> MessagesManager::get_messages_object(int32 total_count, DialogId dialog_id,
- const vector<MessageId> &message_ids) {
- Dialog *d = get_dialog(dialog_id);
- CHECK(d != nullptr);
- return get_messages_object(total_count, transform(message_ids, [this, dialog_id, d](MessageId message_id) {
- return get_message_object(dialog_id, get_message_force(d, message_id));
- }));
-}
+ if (!ignore_result) {
+ d->sent_scheduled_messages = true;
+ }
-tl_object_ptr<td_api::messages> MessagesManager::get_messages_object(int32 total_count,
- const vector<FullMessageId> &full_message_ids) {
- return get_messages_object(total_count, transform(full_message_ids, [this](FullMessageId full_message_id) {
- return get_message_object(full_message_id);
- }));
+ promise.set_value(Unit());
+ return message_ids;
}
-tl_object_ptr<td_api::messages> MessagesManager::get_messages_object(
- int32 total_count, vector<tl_object_ptr<td_api::message>> &&messages) {
- if (total_count == -1) {
- total_count = narrow_cast<int32>(messages.size());
+void MessagesManager::load_dialog_scheduled_messages(DialogId dialog_id, bool from_database, int64 hash,
+ Promise<Unit> &&promise) {
+ if (G()->parameters().use_message_db && from_database) {
+ LOG(INFO) << "Load scheduled messages from database in " << dialog_id;
+ auto &queries = load_scheduled_messages_from_database_queries_[dialog_id];
+ queries.push_back(std::move(promise));
+ if (queries.size() == 1) {
+ G()->td_db()->get_message_db_async()->get_scheduled_messages(
+ dialog_id, 1000,
+ PromiseCreator::lambda([dialog_id, actor_id = actor_id(this)](vector<MessageDbDialogMessage> messages) {
+ send_closure(actor_id, &MessagesManager::on_get_scheduled_messages_from_database, dialog_id,
+ std::move(messages));
+ }));
+ }
+ } else {
+ td_->create_handler<GetAllScheduledMessagesQuery>(std::move(promise))
+ ->send(dialog_id, hash, scheduled_messages_sync_generation_);
}
- return td_api::make_object<td_api::messages>(total_count, std::move(messages));
}
-bool MessagesManager::need_skip_bot_commands(DialogId dialog_id, bool is_bot) const {
- if (is_bot) {
- return false;
+void MessagesManager::on_get_scheduled_messages_from_database(DialogId dialog_id,
+ vector<MessageDbDialogMessage> &&messages) {
+ if (G()->close_flag()) {
+ auto it = load_scheduled_messages_from_database_queries_.find(dialog_id);
+ CHECK(it != load_scheduled_messages_from_database_queries_.end());
+ CHECK(!it->second.empty());
+ auto promises = std::move(it->second);
+ load_scheduled_messages_from_database_queries_.erase(it);
+
+ fail_promises(promises, Global::request_aborted_error());
+ return;
}
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ d->has_loaded_scheduled_messages_from_database = true;
- switch (dialog_id.get_type()) {
- case DialogType::User:
- return !td_->contacts_manager_->is_user_bot(dialog_id.get_user_id());
- case DialogType::SecretChat: {
- auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
- return !td_->contacts_manager_->is_user_bot(user_id);
+ LOG(INFO) << "Receive " << messages.size() << " scheduled messages from database in " << dialog_id;
+
+ Dependencies dependencies;
+ vector<MessageId> added_message_ids;
+ for (auto &message_slice : messages) {
+ auto message = parse_message(d, message_slice.message_id, message_slice.data, true);
+ if (message == nullptr) {
+ continue;
+ }
+ message->from_database = true;
+
+ if (get_message(d, message->message_id) != nullptr) {
+ continue;
+ }
+
+ bool need_update = false;
+ Message *m = add_scheduled_message_to_dialog(d, std::move(message), false, &need_update,
+ "on_get_scheduled_messages_from_database");
+ if (m != nullptr) {
+ add_message_dependencies(dependencies, m);
+ added_message_ids.push_back(m->message_id);
}
- case DialogType::Chat:
- case DialogType::Channel:
- case DialogType::None:
- return false;
- default:
- UNREACHABLE();
- return false;
}
+ dependencies.resolve_force(td_, "on_get_scheduled_messages_from_database");
+
+ // for (auto message_id : added_message_ids) {
+ // send_update_new_message(d, get_message(d, message_id));
+ // }
+ send_update_chat_has_scheduled_messages(d, false);
+
+ auto it = load_scheduled_messages_from_database_queries_.find(dialog_id);
+ CHECK(it != load_scheduled_messages_from_database_queries_.end());
+ CHECK(!it->second.empty());
+ auto promises = std::move(it->second);
+ load_scheduled_messages_from_database_queries_.erase(it);
+
+ set_promises(promises);
}
-Result<FormattedText> MessagesManager::process_input_caption(DialogId dialog_id,
- tl_object_ptr<td_api::formattedText> &&text,
- bool is_bot) const {
- if (text == nullptr) {
- return FormattedText();
+Result<td_api::object_ptr<td_api::availableReactions>> MessagesManager::get_message_available_reactions(
+ FullMessageId full_message_id, int32 row_size) {
+ if (row_size < 5 || row_size > 25) {
+ row_size = 8;
+ }
+
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "get_message_available_reactions");
+ if (d == nullptr) {
+ return Status::Error(400, "Chat not found");
+ }
+
+ const Message *m = get_message_force(d, full_message_id.get_message_id(), "get_message_available_reactions");
+ if (m == nullptr) {
+ return Status::Error(400, "Message not found");
}
- TRY_RESULT(entities, get_message_entities(td_->contacts_manager_.get(), std::move(text->entities_)));
- TRY_STATUS(fix_formatted_text(text->text_, entities, true, false, need_skip_bot_commands(dialog_id, is_bot), false));
- return FormattedText{std::move(text->text_), std::move(entities)};
+
+ auto available_reactions = get_message_available_reactions(d, m, false);
+ bool is_premium = td_->option_manager_->get_option_boolean("is_premium");
+ bool show_premium = is_premium;
+
+ auto recent_reactions = get_recent_reactions(td_);
+ auto top_reactions = get_top_reactions(td_);
+ auto active_reactions = get_message_active_reactions(d, m);
+ LOG(INFO) << "Have available reactions " << available_reactions << " to be sorted by top reactions " << top_reactions
+ << " and recent reactions " << recent_reactions;
+ if (active_reactions.allow_custom_ && active_reactions.allow_all_) {
+ for (auto &reaction : recent_reactions) {
+ if (is_custom_reaction(reaction)) {
+ show_premium = true;
+ }
+ }
+ for (auto &reaction : top_reactions) {
+ if (is_custom_reaction(reaction)) {
+ show_premium = true;
+ }
+ }
+ }
+
+ FlatHashSet<string> all_available_reactions;
+ for (const auto &reaction : available_reactions.reactions_) {
+ CHECK(!reaction.empty());
+ all_available_reactions.insert(reaction);
+ }
+
+ vector<td_api::object_ptr<td_api::availableReaction>> top_reaction_objects;
+ vector<td_api::object_ptr<td_api::availableReaction>> recent_reaction_objects;
+ vector<td_api::object_ptr<td_api::availableReaction>> popular_reaction_objects;
+ vector<td_api::object_ptr<td_api::availableReaction>> last_reaction_objects;
+
+ FlatHashSet<string> added_custom_reactions;
+ auto add_reactions = [&](vector<td_api::object_ptr<td_api::availableReaction>> &reaction_objects,
+ const vector<string> &reactions) {
+ for (auto &reaction : reactions) {
+ if (all_available_reactions.erase(reaction) != 0) {
+ // add available reaction
+ if (is_custom_reaction(reaction)) {
+ added_custom_reactions.insert(reaction);
+ }
+ reaction_objects.push_back(
+ td_api::make_object<td_api::availableReaction>(get_reaction_type_object(reaction), false));
+ } else if (is_custom_reaction(reaction) && available_reactions.allow_custom_ &&
+ added_custom_reactions.insert(reaction).second) {
+ // add implicitly available custom reaction
+ reaction_objects.push_back(
+ td_api::make_object<td_api::availableReaction>(get_reaction_type_object(reaction), !is_premium));
+ } else {
+ // skip the reaction
+ }
+ }
+ };
+ if (show_premium) {
+ if (top_reactions.size() > 2 * static_cast<size_t>(row_size)) {
+ top_reactions.resize(2 * static_cast<size_t>(row_size));
+ }
+ add_reactions(top_reaction_objects, top_reactions);
+
+ if (!recent_reactions.empty()) {
+ add_reactions(recent_reaction_objects, recent_reactions);
+ }
+ } else {
+ add_reactions(top_reaction_objects, top_reactions);
+ }
+ add_reactions(last_reaction_objects, active_reactions_);
+ add_reactions(last_reaction_objects, available_reactions.reactions_);
+
+ if (show_premium) {
+ if (recent_reactions.empty()) {
+ popular_reaction_objects = std::move(last_reaction_objects);
+ } else {
+ auto max_objects = 10 * static_cast<size_t>(row_size);
+ if (recent_reaction_objects.size() + last_reaction_objects.size() > max_objects) {
+ if (last_reaction_objects.size() < max_objects) {
+ recent_reaction_objects.resize(max_objects - last_reaction_objects.size());
+ } else {
+ recent_reaction_objects.clear();
+ }
+ }
+ append(recent_reaction_objects, std::move(last_reaction_objects));
+ }
+ } else {
+ append(top_reaction_objects, std::move(last_reaction_objects));
+ }
+
+ CHECK(all_available_reactions.empty());
+
+ return td_api::make_object<td_api::availableReactions>(
+ std::move(top_reaction_objects), std::move(recent_reaction_objects), std::move(popular_reaction_objects),
+ available_reactions.allow_custom_);
}
-Result<InputMessageText> MessagesManager::process_input_message_text(
- DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content, bool is_bot,
- bool for_draft) const {
- CHECK(input_message_content != nullptr);
- CHECK(input_message_content->get_id() == td_api::inputMessageText::ID);
- auto input_message_text = static_cast<td_api::inputMessageText *>(input_message_content.get());
- if (input_message_text->text_ == nullptr) {
- if (for_draft) {
- return InputMessageText{FormattedText(), input_message_text->disable_web_page_preview_,
- input_message_text->clear_draft_};
+ChatReactions MessagesManager::get_message_available_reactions(const Dialog *d, const Message *m,
+ bool dissalow_custom_for_non_premium) {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ auto active_reactions = get_message_active_reactions(d, m);
+ if (!m->message_id.is_valid() || !m->message_id.is_server() || active_reactions.empty()) {
+ return {};
+ }
+
+ bool can_use_reactions = true;
+ if (d->dialog_id.get_type() == DialogType::Channel) {
+ auto channel_id = d->dialog_id.get_channel_id();
+ if (td_->contacts_manager_->is_megagroup_channel(channel_id) &&
+ !td_->contacts_manager_->get_channel_status(channel_id).is_member() &&
+ can_send_message(d->dialog_id).is_error()) {
+ can_use_reactions = false;
}
+ }
- return Status::Error(400, "Message text can't be empty");
+ int64 reactions_uniq_max = td_->option_manager_->get_option_integer("reactions_uniq_max", 11);
+ bool can_add_new_reactions =
+ m->reactions == nullptr || static_cast<int64>(m->reactions->reactions_.size()) < reactions_uniq_max;
+
+ if (!can_use_reactions || !can_add_new_reactions) {
+ active_reactions = ChatReactions();
}
- TRY_RESULT(entities,
- get_message_entities(td_->contacts_manager_.get(), std::move(input_message_text->text_->entities_)));
- TRY_STATUS(fix_formatted_text(input_message_text->text_->text_, entities, for_draft, false,
- need_skip_bot_commands(dialog_id, is_bot), for_draft));
- return InputMessageText{FormattedText{std::move(input_message_text->text_->text_), std::move(entities)},
- input_message_text->disable_web_page_preview_, input_message_text->clear_draft_};
+ if (active_reactions.allow_all_) {
+ active_reactions.reactions_ = active_reactions_;
+ active_reactions.allow_all_ = false;
+ }
+ if (m->reactions != nullptr) {
+ for (const auto &reaction : m->reactions->reactions_) {
+ // an already used reaction can be added if it is an active reaction
+ const string &reaction_str = reaction.get_reaction();
+ if (can_use_reactions && is_active_reaction(reaction_str, active_reaction_pos_) &&
+ !td::contains(active_reactions.reactions_, reaction_str)) {
+ active_reactions.reactions_.push_back(reaction_str);
+ }
+ }
+ }
+ if (dissalow_custom_for_non_premium && !td_->option_manager_->get_option_boolean("is_premium")) {
+ active_reactions.allow_custom_ = false;
+ }
+ return active_reactions;
}
-Result<std::pair<Location, int32>> MessagesManager::process_input_message_location(
- tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
- CHECK(input_message_content != nullptr);
- CHECK(input_message_content->get_id() == td_api::inputMessageLocation::ID);
- auto input_location = static_cast<const td_api::inputMessageLocation *>(input_message_content.get());
+void MessagesManager::add_message_reaction(FullMessageId full_message_id, string reaction, bool is_big,
+ bool add_to_recent, Promise<Unit> &&promise) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "add_message_reaction");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
- Location location(input_location->location_);
- if (location.empty()) {
- return Status::Error(400, "Wrong location specified");
+ Message *m = get_message_force(d, full_message_id.get_message_id(), "add_message_reaction");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ if (!get_message_available_reactions(d, m, true).is_allowed_reaction(reaction)) {
+ return promise.set_error(Status::Error(400, "The reaction isn't available for the message"));
+ }
+
+ bool have_recent_choosers = !is_broadcast_channel(dialog_id) && !is_discussion_message(dialog_id, m);
+ if (m->reactions == nullptr) {
+ m->reactions = make_unique<MessageReactions>();
+ m->reactions->can_get_added_reactions_ = have_recent_choosers && dialog_id.get_type() != DialogType::User;
+ m->available_reactions_generation = d->available_reactions_generation;
+ }
+
+ if (!m->reactions->add_reaction(reaction, is_big, get_my_dialog_id(), have_recent_choosers)) {
+ return promise.set_value(Unit());
}
- auto period = input_location->live_period_;
- if (period != 0 && (period < MIN_LIVE_LOCATION_PERIOD || period > MAX_LIVE_LOCATION_PERIOD)) {
- return Status::Error(400, "Wrong live location period specified");
+ if (add_to_recent) {
+ add_recent_reaction(td_, reaction);
}
- return std::make_pair(std::move(location), period);
+ set_message_reactions(d, m, is_big, add_to_recent, std::move(promise));
}
-Result<Venue> MessagesManager::process_input_message_venue(
- tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
- CHECK(input_message_content != nullptr);
- CHECK(input_message_content->get_id() == td_api::inputMessageVenue::ID);
- auto venue = std::move(static_cast<td_api::inputMessageVenue *>(input_message_content.get())->venue_);
+void MessagesManager::remove_message_reaction(FullMessageId full_message_id, string reaction, Promise<Unit> &&promise) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "remove_message_reaction");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
- if (venue == nullptr) {
- return Status::Error(400, "Venue can't be empty");
+ Message *m = get_message_force(d, full_message_id.get_message_id(), "remove_message_reaction");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
}
- if (!clean_input_string(venue->title_)) {
- return Status::Error(400, "Venue title must be encoded in UTF-8");
+ if (reaction.empty()) {
+ return promise.set_error(Status::Error(400, "Invalid reaction specified"));
}
- if (!clean_input_string(venue->address_)) {
- return Status::Error(400, "Venue address must be encoded in UTF-8");
+
+ bool have_recent_choosers = !is_broadcast_channel(dialog_id) && !is_discussion_message(dialog_id, m);
+ if (m->reactions == nullptr || !m->reactions->remove_reaction(reaction, get_my_dialog_id(), have_recent_choosers)) {
+ return promise.set_value(Unit());
}
- if (!clean_input_string(venue->provider_)) {
- return Status::Error(400, "Venue provider must be encoded in UTF-8");
+
+ set_message_reactions(d, m, false, false, std::move(promise));
+}
+
+void MessagesManager::set_message_reactions(Dialog *d, Message *m, bool is_big, bool add_to_recent,
+ Promise<Unit> &&promise) {
+ CHECK(m->reactions != nullptr);
+ m->reactions->sort_reactions(active_reaction_pos_);
+
+ LOG(INFO) << "Update message reactions to " << *m->reactions;
+
+ FullMessageId full_message_id{d->dialog_id, m->message_id};
+ pending_reactions_[full_message_id].query_count++;
+
+ send_update_message_interaction_info(d->dialog_id, m);
+ on_message_changed(d, m, true, "set_message_reactions");
+
+ // TODO cancel previous queries, log event
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), full_message_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ send_closure(actor_id, &MessagesManager::on_set_message_reactions, full_message_id, std::move(result),
+ std::move(promise));
+ });
+ send_message_reaction(td_, full_message_id, m->reactions->get_chosen_reactions(), is_big, add_to_recent,
+ std::move(query_promise));
+}
+
+void MessagesManager::on_set_message_reactions(FullMessageId full_message_id, Result<Unit> result,
+ Promise<Unit> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ bool need_reload = result.is_error();
+ auto it = pending_reactions_.find(full_message_id);
+ CHECK(it != pending_reactions_.end());
+ if (--it->second.query_count == 0) {
+ // need_reload |= it->second.was_updated;
+ pending_reactions_.erase(it);
}
- if (!clean_input_string(venue->id_)) {
- return Status::Error(400, "Venue identifier must be encoded in UTF-8");
+
+ if (!have_message_force(full_message_id, "on_set_message_reaction")) {
+ return promise.set_value(Unit());
}
- Venue result(venue);
- if (result.empty()) {
- return Status::Error(400, "Wrong venue location specified");
+ if (need_reload) {
+ queue_message_reactions_reload(full_message_id);
}
- return result;
+ promise.set_result(std::move(result));
}
-Result<Contact> MessagesManager::process_input_message_contact(
- tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
- CHECK(input_message_content != nullptr);
- CHECK(input_message_content->get_id() == td_api::inputMessageContact::ID);
- auto contact = std::move(static_cast<td_api::inputMessageContact *>(input_message_content.get())->contact_);
+void MessagesManager::get_message_public_forwards(FullMessageId full_message_id, string offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
+ auto dc_id_promise = PromiseCreator::lambda([actor_id = actor_id(this), full_message_id, offset = std::move(offset),
+ limit, promise = std::move(promise)](Result<DcId> r_dc_id) mutable {
+ if (r_dc_id.is_error()) {
+ return promise.set_error(r_dc_id.move_as_error());
+ }
+ send_closure(actor_id, &MessagesManager::send_get_message_public_forwards_query, r_dc_id.move_as_ok(),
+ full_message_id, std::move(offset), limit, std::move(promise));
+ });
+ td_->contacts_manager_->get_channel_statistics_dc_id(full_message_id.get_dialog_id(), false,
+ std::move(dc_id_promise));
+}
- if (!clean_input_string(contact->phone_number_)) {
- return Status::Error(400, "Phone number must be encoded in UTF-8");
+void MessagesManager::send_get_message_public_forwards_query(
+ DcId dc_id, FullMessageId full_message_id, string offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "send_get_message_public_forwards_query");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- if (!clean_input_string(contact->first_name_)) {
- return Status::Error(400, "First name must be encoded in UTF-8");
+
+ const Message *m = get_message_force(d, full_message_id.get_message_id(), "send_get_message_public_forwards_query");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
}
- if (!clean_input_string(contact->last_name_)) {
- return Status::Error(400, "Last name must be encoded in UTF-8");
+
+ if (m->view_count == 0 || m->forward_info != nullptr || m->had_forward_info || m->message_id.is_scheduled() ||
+ !m->message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Message forwards are inaccessible"));
}
- return Contact(contact->phone_number_, contact->first_name_, contact->last_name_, contact->user_id_);
-}
-
-Result<Game> MessagesManager::process_input_message_game(
- tl_object_ptr<td_api::InputMessageContent> &&input_message_content) const {
- CHECK(input_message_content != nullptr);
- CHECK(input_message_content->get_id() == td_api::inputMessageGame::ID);
- auto input_message_game = move_tl_object_as<td_api::inputMessageGame>(input_message_content);
-
- UserId bot_user_id(input_message_game->bot_user_id_);
- if (!td_->contacts_manager_->have_input_user(bot_user_id)) {
- return Status::Error(400, "Game owner bot is not accessible");
- }
-
- if (!clean_input_string(input_message_game->game_short_name_)) {
- return Status::Error(400, "Game short name must be encoded in UTF-8");
- }
-
- // TODO validate game_short_name
- if (input_message_game->game_short_name_.empty()) {
- return Status::Error(400, "Game short name must be non-empty");
- }
-
- return Game(bot_user_id, std::move(input_message_game->game_short_name_));
-}
-
-SecretInputMedia MessagesManager::get_secret_input_media(const MessageContent *content,
- tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- BufferSlice thumbnail, int32 layer) {
- switch (content->get_id()) {
- case MessageAnimation::ID: {
- auto m = static_cast<const MessageAnimation *>(content);
- return td_->animations_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
- std::move(thumbnail));
- }
- case MessageAudio::ID: {
- auto m = static_cast<const MessageAudio *>(content);
- return td_->audios_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
- std::move(thumbnail));
- }
- case MessageContact::ID: {
- auto m = static_cast<const MessageContact *>(content);
- return m->contact.get_secret_input_media_contact();
- }
- case MessageDocument::ID: {
- auto m = static_cast<const MessageDocument *>(content);
- return td_->documents_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
- std::move(thumbnail));
- }
- case MessageLocation::ID: {
- auto m = static_cast<const MessageLocation *>(content);
- return m->location.get_secret_input_media_geo_point();
- }
- case MessagePhoto::ID: {
- auto m = static_cast<const MessagePhoto *>(content);
- return photo_get_secret_input_media(td_->file_manager_.get(), m->photo, std::move(input_file), m->caption.text,
- std::move(thumbnail));
- }
- case MessageSticker::ID: {
- auto m = static_cast<const MessageSticker *>(content);
- return td_->stickers_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail));
- }
- case MessageText::ID: {
- CHECK(input_file == nullptr);
- CHECK(thumbnail.empty());
- auto m = static_cast<const MessageText *>(content);
- return td_->web_pages_manager_->get_secret_input_media(m->web_page_id);
- }
- case MessageVenue::ID: {
- auto m = static_cast<const MessageVenue *>(content);
- return m->venue.get_secret_input_media_venue();
- }
- case MessageVideo::ID: {
- auto m = static_cast<const MessageVideo *>(content);
- return td_->videos_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text,
- std::move(thumbnail));
- }
- case MessageVideoNote::ID: {
- auto m = static_cast<const MessageVideoNote *>(content);
- return td_->video_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), std::move(thumbnail),
- layer);
- }
- case MessageVoiceNote::ID: {
- auto m = static_cast<const MessageVoiceNote *>(content);
- return td_->voice_notes_manager_->get_secret_input_media(m->file_id, std::move(input_file), m->caption.text);
- }
- case MessageLiveLocation::ID:
- case MessageGame::ID:
- case MessageInvoice::ID:
- case MessageUnsupported::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- break;
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ }
+ if (limit > MAX_SEARCH_MESSAGES) {
+ limit = MAX_SEARCH_MESSAGES;
+ }
+
+ int32 offset_date = std::numeric_limits<int32>::max();
+ DialogId offset_dialog_id;
+ ServerMessageId offset_message_id;
+
+ if (!offset.empty()) {
+ auto parts = full_split(offset, ',');
+ if (parts.size() != 3) {
+ return promise.set_error(Status::Error(400, "Invalid offset specified"));
+ }
+ auto r_offset_date = to_integer_safe<int32>(parts[0]);
+ auto r_offset_dialog_id = to_integer_safe<int64>(parts[1]);
+ auto r_offset_message_id = to_integer_safe<int32>(parts[2]);
+ if (r_offset_date.is_error() || r_offset_dialog_id.is_error() || r_offset_message_id.is_error()) {
+ return promise.set_error(Status::Error(400, "Invalid offset specified"));
+ }
+
+ offset_date = r_offset_date.ok();
+ offset_dialog_id = DialogId(r_offset_dialog_id.ok());
+ offset_message_id = ServerMessageId(r_offset_message_id.ok());
+ }
+
+ td_->create_handler<GetMessagePublicForwardsQuery>(std::move(promise))
+ ->send(dc_id, full_message_id, offset_date, offset_dialog_id, offset_message_id, limit);
+}
+
+Result<int32> MessagesManager::get_message_schedule_date(
+ td_api::object_ptr<td_api::MessageSchedulingState> &&scheduling_state) {
+ if (scheduling_state == nullptr) {
+ return 0;
+ }
+
+ switch (scheduling_state->get_id()) {
+ case td_api::messageSchedulingStateSendWhenOnline::ID: {
+ auto send_date = SCHEDULE_WHEN_ONLINE_DATE;
+ return send_date;
+ }
+ case td_api::messageSchedulingStateSendAtDate::ID: {
+ auto send_at_date = td_api::move_object_as<td_api::messageSchedulingStateSendAtDate>(scheduling_state);
+ auto send_date = send_at_date->send_date_;
+ if (send_date <= 0) {
+ return Status::Error(400, "Invalid send date specified");
+ }
+ if (send_date <= G()->unix_time() + 10) {
+ return 0;
+ }
+ if (send_date - G()->unix_time() > 367 * 86400) {
+ return Status::Error(400, "Send date is too far in the future");
+ }
+ return send_date;
+ }
default:
UNREACHABLE();
+ return 0;
}
- return SecretInputMedia{};
}
-tl_object_ptr<telegram_api::invoice> MessagesManager::get_input_invoice(const Invoice &invoice) const {
- int32 flags = 0;
- if (invoice.is_test) {
- flags |= telegram_api::invoice::TEST_MASK;
+tl_object_ptr<td_api::MessageSendingState> MessagesManager::get_message_sending_state_object(const Message *m) const {
+ CHECK(m != nullptr);
+ if (m->message_id.is_yet_unsent()) {
+ return td_api::make_object<td_api::messageSendingStatePending>();
}
- if (invoice.need_name) {
- flags |= telegram_api::invoice::NAME_REQUESTED_MASK;
+ if (m->is_failed_to_send) {
+ auto can_retry = can_resend_message(m);
+ auto need_another_sender =
+ can_retry && m->send_error_code == 400 && m->send_error_message == CSlice("SEND_AS_PEER_INVALID");
+ return td_api::make_object<td_api::messageSendingStateFailed>(m->send_error_code, m->send_error_message, can_retry,
+ need_another_sender,
+ max(m->try_resend_at - Time::now(), 0.0));
}
- if (invoice.need_phone_number) {
- flags |= telegram_api::invoice::PHONE_REQUESTED_MASK;
+ return nullptr;
+}
+
+tl_object_ptr<td_api::MessageSchedulingState> MessagesManager::get_message_scheduling_state_object(int32 send_date) {
+ if (send_date == SCHEDULE_WHEN_ONLINE_DATE) {
+ return td_api::make_object<td_api::messageSchedulingStateSendWhenOnline>();
}
- if (invoice.need_email_address) {
- flags |= telegram_api::invoice::EMAIL_REQUESTED_MASK;
+ return td_api::make_object<td_api::messageSchedulingStateSendAtDate>(send_date);
+}
+
+td_api::object_ptr<td_api::message> MessagesManager::get_dialog_event_log_message_object(
+ DialogId dialog_id, tl_object_ptr<telegram_api::Message> &&message, DialogId &sender_dialog_id) {
+ auto dialog_message = create_message(parse_telegram_api_message(std::move(message), false, "dialog_event_log"),
+ dialog_id.get_type() == DialogType::Channel);
+ if (dialog_message.second == nullptr || dialog_message.first != dialog_id) {
+ LOG(ERROR) << "Failed to create event log message in " << dialog_id;
+ return nullptr;
}
- if (invoice.need_shipping_address) {
- flags |= telegram_api::invoice::SHIPPING_ADDRESS_REQUESTED_MASK;
+ sender_dialog_id = get_message_sender(dialog_message.second.get());
+ return get_message_object(dialog_id, dialog_message.second.get(), "get_dialog_event_log_message_object", true);
+}
+
+tl_object_ptr<td_api::message> MessagesManager::get_message_object(FullMessageId full_message_id, const char *source) {
+ return get_message_object(full_message_id.get_dialog_id(), get_message_force(full_message_id, source), source);
+}
+
+tl_object_ptr<td_api::message> MessagesManager::get_message_object(DialogId dialog_id, const Message *m,
+ const char *source, bool for_event_log) const {
+ if (m == nullptr) {
+ return nullptr;
}
- if (invoice.send_phone_number_to_provider) {
- flags |= telegram_api::invoice::PHONE_TO_PROVIDER_MASK;
+ LOG_CHECK(have_dialog(dialog_id)) << source;
+
+ m->is_update_sent = true;
+
+ auto sending_state = get_message_sending_state_object(m);
+
+ if (for_event_log) {
+ CHECK(m->message_id.is_server());
+ CHECK(sending_state == nullptr);
}
- if (invoice.send_email_address_to_provider) {
- flags |= telegram_api::invoice::EMAIL_TO_PROVIDER_MASK;
+
+ bool can_delete = can_delete_message(dialog_id, m);
+ bool is_scheduled = m->message_id.is_scheduled();
+ DialogId my_dialog_id = get_my_dialog_id();
+ bool can_delete_for_self = false;
+ bool can_delete_for_all_users = can_delete && can_revoke_message(dialog_id, m);
+ if (can_delete) {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::Chat:
+ // TODO allow to delete yet unsent message just for self
+ can_delete_for_self = !m->message_id.is_yet_unsent() || dialog_id == my_dialog_id;
+ break;
+ case DialogType::Channel:
+ case DialogType::SecretChat:
+ can_delete_for_self = !can_delete_for_all_users;
+ break;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
}
- if (invoice.is_flexible) {
- flags |= telegram_api::invoice::FLEXIBLE_MASK;
+ if (for_event_log) {
+ can_delete_for_self = false;
+ can_delete_for_all_users = false;
+ } else if (is_scheduled) {
+ can_delete_for_self = (dialog_id == my_dialog_id);
+ can_delete_for_all_users = !can_delete_for_self;
}
- vector<tl_object_ptr<telegram_api::labeledPrice>> prices;
- prices.reserve(invoice.price_parts.size());
- for (auto &price : invoice.price_parts) {
- prices.push_back(make_tl_object<telegram_api::labeledPrice>(price.label, price.amount));
+ bool is_outgoing = m->is_outgoing;
+ if (dialog_id == my_dialog_id) {
+ // in Saved Messages all non-forwarded messages must be outgoing
+ // a forwarded message is outgoing, only if it doesn't have from_dialog_id and its sender isn't hidden
+ // i.e. a message is incoming only if it's a forwarded message with known from_dialog_id or with a hidden sender
+ auto forward_info = m->forward_info.get();
+ is_outgoing = is_scheduled || forward_info == nullptr ||
+ (!forward_info->from_dialog_id.is_valid() && !is_forward_info_sender_hidden(forward_info));
+ }
+
+ int32 ttl = m->ttl;
+ double ttl_expires_in = 0;
+ if (!for_event_log) {
+ if (m->ttl_expires_at != 0) {
+ ttl_expires_in = clamp(m->ttl_expires_at - Time::now(), 1e-3, ttl - 1e-3);
+ } else {
+ ttl_expires_in = ttl;
+ }
+ if (ttl == 0 && m->ttl_period != 0) {
+ ttl = m->ttl_period;
+ ttl_expires_in = clamp(m->date + m->ttl_period - G()->server_time(), 1e-3, ttl - 1e-3);
+ }
+ } else {
+ ttl = 0;
}
+ auto sender = get_message_sender_object_const(td_, m->sender_user_id, m->sender_dialog_id, source);
+ auto scheduling_state = is_scheduled ? get_message_scheduling_state_object(m->date) : nullptr;
+ auto forward_info = get_message_forward_info_object(m->forward_info);
+ auto interaction_info = get_message_interaction_info_object(dialog_id, m);
+ auto unread_reactions = get_unread_reactions_object(dialog_id, m);
+ auto can_be_saved = can_save_message(dialog_id, m);
+ auto can_be_edited = for_event_log ? false : can_edit_message(dialog_id, m, false, td_->auth_manager_->is_bot());
+ auto can_be_forwarded = for_event_log ? false : can_forward_message(dialog_id, m) && can_be_saved;
+ auto can_get_added_reactions =
+ for_event_log ? false : m->reactions != nullptr && m->reactions->can_get_added_reactions_;
+ auto can_get_statistics = for_event_log ? false : can_get_message_statistics(dialog_id, m);
+ auto can_get_message_thread = for_event_log ? false : get_top_thread_full_message_id(dialog_id, m).is_ok();
+ auto can_get_viewers = for_event_log ? false : can_get_message_viewers(dialog_id, m).is_ok();
+ auto can_get_media_timestamp_links = for_event_log ? false : can_get_media_timestamp_link(dialog_id, m).is_ok();
+ auto can_report_reactions = for_event_log ? false : can_report_message_reactions(dialog_id, m);
+ auto via_bot_user_id = td_->contacts_manager_->get_user_id_object(m->via_bot_user_id, "via_bot_user_id");
+ auto media_album_id = for_event_log ? static_cast<int64>(0) : m->media_album_id;
+ auto reply_to_message_id = for_event_log ? static_cast<int64>(0) : m->reply_to_message_id.get();
+ auto reply_in_dialog_id =
+ reply_to_message_id == 0 ? DialogId() : (m->reply_in_dialog_id.is_valid() ? m->reply_in_dialog_id : dialog_id);
+ auto top_thread_message_id = for_event_log || is_scheduled ? static_cast<int64>(0) : m->top_thread_message_id.get();
+ auto contains_unread_mention = for_event_log ? false : m->contains_unread_mention;
+ auto date = is_scheduled ? 0 : m->date;
+ auto edit_date = m->hide_edit_date ? 0 : m->edit_date;
+ auto is_pinned = is_scheduled ? false : m->is_pinned;
+ auto has_timestamped_media = for_event_log || reply_to_message_id == 0 || m->max_own_media_timestamp >= 0;
+ auto reply_markup = get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup);
+ auto live_location_date = m->is_failed_to_send ? 0 : m->date;
+ auto skip_bot_commands = for_event_log ? true : need_skip_bot_commands(dialog_id, m);
+ auto max_media_timestamp =
+ for_event_log ? get_message_own_max_media_timestamp(m) : get_message_max_media_timestamp(m);
+ auto content = get_message_content_object(m->content.get(), td_, dialog_id, live_location_date, m->is_content_secret,
+ skip_bot_commands, max_media_timestamp);
+
+ if (m->is_topic_message && reply_in_dialog_id == dialog_id && reply_to_message_id == top_thread_message_id &&
+ !td_->auth_manager_->is_bot()) {
+ reply_in_dialog_id = DialogId();
+ reply_to_message_id = 0;
+ }
+
+ return make_tl_object<td_api::message>(
+ m->message_id.get(), std::move(sender), dialog_id.get(), std::move(sending_state), std::move(scheduling_state),
+ is_outgoing, is_pinned, can_be_edited, can_be_forwarded, can_be_saved, can_delete_for_self,
+ can_delete_for_all_users, can_get_added_reactions, can_get_statistics, can_get_message_thread, can_get_viewers,
+ can_get_media_timestamp_links, can_report_reactions, has_timestamped_media, m->is_channel_post,
+ m->is_topic_message, contains_unread_mention, date, edit_date, std::move(forward_info),
+ std::move(interaction_info), std::move(unread_reactions), reply_in_dialog_id.get(), reply_to_message_id,
+ top_thread_message_id, ttl, ttl_expires_in, via_bot_user_id, m->author_signature, media_album_id,
+ get_restriction_reason_description(m->restriction_reasons), std::move(content), std::move(reply_markup));
+}
- return make_tl_object<telegram_api::invoice>(
- flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- false /*ignored*/, false /*ignored*/, false /*ignored*/, invoice.currency, std::move(prices));
+tl_object_ptr<td_api::messages> MessagesManager::get_messages_object(int32 total_count, DialogId dialog_id,
+ const vector<MessageId> &message_ids,
+ bool skip_not_found, const char *source) {
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ auto message_objects = transform(message_ids, [this, dialog_id, d, source](MessageId message_id) {
+ return get_message_object(dialog_id, get_message_force(d, message_id, source), source);
+ });
+ return get_messages_object(total_count, std::move(message_objects), skip_not_found);
}
-tl_object_ptr<telegram_api::inputWebDocument> MessagesManager::get_input_web_document(const Photo &photo) const {
- if (photo.id == -2) {
- return nullptr;
+tl_object_ptr<td_api::messages> MessagesManager::get_messages_object(int32 total_count,
+ const vector<FullMessageId> &full_message_ids,
+ bool skip_not_found, const char *source) {
+ auto message_objects = transform(full_message_ids, [this, source](FullMessageId full_message_id) {
+ return get_message_object(full_message_id, source);
+ });
+ return get_messages_object(total_count, std::move(message_objects), skip_not_found);
+}
+
+tl_object_ptr<td_api::messages> MessagesManager::get_messages_object(int32 total_count,
+ vector<tl_object_ptr<td_api::message>> &&messages,
+ bool skip_not_found) {
+ auto message_count = narrow_cast<int32>(messages.size());
+ if (total_count < message_count) {
+ if (total_count != -1) {
+ LOG(ERROR) << "Have wrong total_count = " << total_count << ", while having " << message_count << " messages";
+ }
+ total_count = message_count;
+ }
+ if (skip_not_found && td::remove(messages, nullptr)) {
+ total_count -= message_count - static_cast<int32>(messages.size());
}
+ return td_api::make_object<td_api::messages>(total_count, std::move(messages));
+}
- CHECK(photo.photos.size() == 1);
- const PhotoSize &size = photo.photos[0];
- CHECK(size.file_id.is_valid());
+bool MessagesManager::is_anonymous_administrator(DialogId dialog_id, string *author_signature) const {
+ CHECK(dialog_id.is_valid());
- vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
- if (size.dimensions.width != 0 && size.dimensions.height != 0) {
- attributes.push_back(
- make_tl_object<telegram_api::documentAttributeImageSize>(size.dimensions.width, size.dimensions.height));
+ if (is_broadcast_channel(dialog_id)) {
+ return true;
}
- auto file_view = td_->file_manager_->get_file_view(size.file_id);
- CHECK(file_view.has_url());
+ if (td_->auth_manager_->is_bot()) {
+ return false;
+ }
- auto file_name = get_url_file_name(file_view.url());
- return make_tl_object<telegram_api::inputWebDocument>(
- file_view.url(), size.size, MimeType::from_extension(PathView(file_name).extension(), "image/jpeg"),
- std::move(attributes));
-}
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return false;
+ }
-tl_object_ptr<telegram_api::inputMediaInvoice> MessagesManager::get_input_media_invoice(
- const MessageInvoice *message_invoice) const {
- CHECK(message_invoice != nullptr);
- int32 flags = 0;
- auto input_web_document = get_input_web_document(message_invoice->photo);
- if (input_web_document != nullptr) {
- flags |= telegram_api::inputMediaInvoice::PHOTO_MASK;
- }
-
- return make_tl_object<telegram_api::inputMediaInvoice>(
- flags, message_invoice->title, message_invoice->description, std::move(input_web_document),
- get_input_invoice(message_invoice->invoice), BufferSlice(message_invoice->payload),
- message_invoice->provider_token,
- telegram_api::make_object<telegram_api::dataJSON>(
- message_invoice->provider_data.empty() ? "null" : message_invoice->provider_data),
- message_invoice->start_parameter);
-}
-
-tl_object_ptr<telegram_api::InputMedia> MessagesManager::get_input_media(
- const MessageContent *content, tl_object_ptr<telegram_api::InputFile> input_file,
- tl_object_ptr<telegram_api::InputFile> input_thumbnail, int32 ttl) {
- switch (content->get_id()) {
- case MessageAnimation::ID: {
- auto m = static_cast<const MessageAnimation *>(content);
- return td_->animations_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
- }
- case MessageAudio::ID: {
- auto m = static_cast<const MessageAudio *>(content);
- return td_->audios_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
- }
- case MessageContact::ID: {
- auto m = static_cast<const MessageContact *>(content);
- return m->contact.get_input_media_contact();
- }
- case MessageDocument::ID: {
- auto m = static_cast<const MessageDocument *>(content);
- return td_->documents_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
- }
- case MessageGame::ID: {
- auto m = static_cast<const MessageGame *>(content);
- return m->game.get_input_media_game(td_);
- }
- case MessageInvoice::ID: {
- auto m = static_cast<const MessageInvoice *>(content);
- return get_input_media_invoice(m);
- }
- case MessageLiveLocation::ID: {
- auto m = static_cast<const MessageLiveLocation *>(content);
- return make_tl_object<telegram_api::inputMediaGeoLive>(m->location.get_input_geo_point(), m->period);
- }
- case MessageLocation::ID: {
- auto m = static_cast<const MessageLocation *>(content);
- return m->location.get_input_media_geo_point();
- }
- case MessagePhoto::ID: {
- auto m = static_cast<const MessagePhoto *>(content);
- return photo_get_input_media(td_->file_manager_.get(), m->photo, std::move(input_file), ttl);
- }
- case MessageSticker::ID: {
- auto m = static_cast<const MessageSticker *>(content);
- return td_->stickers_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
- }
- case MessageVenue::ID: {
- auto m = static_cast<const MessageVenue *>(content);
- return m->venue.get_input_media_venue();
- }
- case MessageVideo::ID: {
- auto m = static_cast<const MessageVideo *>(content);
- return td_->videos_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail), ttl);
- }
- case MessageVideoNote::ID: {
- auto m = static_cast<const MessageVideoNote *>(content);
- return td_->video_notes_manager_->get_input_media(m->file_id, std::move(input_file), std::move(input_thumbnail));
- }
- case MessageVoiceNote::ID: {
- auto m = static_cast<const MessageVoiceNote *>(content);
- return td_->voice_notes_manager_->get_input_media(m->file_id, std::move(input_file));
- }
- case MessageText::ID:
- case MessageUnsupported::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- break;
- default:
- UNREACHABLE();
+ auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
+ if (!status.is_anonymous()) {
+ return false;
}
- return nullptr;
-}
-void MessagesManager::delete_message_content_thumbnail(MessageContent *content) {
- switch (content->get_id()) {
- case MessageAnimation::ID: {
- auto m = static_cast<MessageAnimation *>(content);
- return td_->animations_manager_->delete_animation_thumbnail(m->file_id);
- }
- case MessageAudio::ID: {
- auto m = static_cast<MessageAudio *>(content);
- return td_->audios_manager_->delete_audio_thumbnail(m->file_id);
- }
- case MessageDocument::ID: {
- auto m = static_cast<MessageDocument *>(content);
- return td_->documents_manager_->delete_document_thumbnail(m->file_id);
- }
- case MessagePhoto::ID: {
- auto m = static_cast<MessagePhoto *>(content);
- return photo_delete_thumbnail(m->photo);
- }
- case MessageSticker::ID: {
- auto m = static_cast<MessageSticker *>(content);
- return td_->stickers_manager_->delete_sticker_thumbnail(m->file_id);
- }
- case MessageVideo::ID: {
- auto m = static_cast<MessageVideo *>(content);
- return td_->videos_manager_->delete_video_thumbnail(m->file_id);
- }
- case MessageVideoNote::ID: {
- auto m = static_cast<MessageVideoNote *>(content);
- return td_->video_notes_manager_->delete_video_note_thumbnail(m->file_id);
- }
- case MessageContact::ID:
- case MessageGame::ID:
- case MessageInvoice::ID:
- case MessageLiveLocation::ID:
- case MessageLocation::ID:
- case MessageVenue::ID:
- case MessageVoiceNote::ID:
- case MessageText::ID:
- case MessageUnsupported::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- break;
- default:
- UNREACHABLE();
+ if (author_signature != nullptr) {
+ *author_signature = status.get_rank();
}
+ return true;
}
-MessagesManager::Message *MessagesManager::get_message_to_send(Dialog *d, MessageId reply_to_message_id,
- bool disable_notification, bool from_background,
- unique_ptr<MessageContent> &&content,
- bool *need_update_dialog_pos,
- unique_ptr<MessageForwardInfo> forward_info) {
+bool MessagesManager::get_dialog_silent_send_message(DialogId dialog_id) const {
+ auto *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ return d->notification_settings.silent_send_message;
+}
+
+DialogId MessagesManager::get_dialog_default_send_message_as_dialog_id(DialogId dialog_id) const {
+ auto *d = get_dialog(dialog_id);
CHECK(d != nullptr);
- MessageId message_id = get_next_yet_unsent_message_id(d);
+ return d->default_send_message_as_dialog_id;
+}
+
+int64 MessagesManager::generate_new_random_id(const Dialog *d) {
+ int64 random_id;
+ do {
+ random_id = Random::secure_int64();
+ } while (random_id == 0 || being_sent_messages_.count(random_id) > 0 ||
+ d->random_id_to_message_id.count(random_id) > 0);
+ return random_id;
+}
+
+unique_ptr<MessagesManager::Message> MessagesManager::create_message_to_send(
+ Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id, const MessageSendOptions &options,
+ unique_ptr<MessageContent> &&content, bool suppress_reply_info, unique_ptr<MessageForwardInfo> forward_info,
+ bool is_copy, DialogId send_as_dialog_id) const {
+ CHECK(d != nullptr);
+ CHECK(content != nullptr);
+
+ bool is_scheduled = options.schedule_date != 0;
DialogId dialog_id = d->dialog_id;
- LOG(INFO) << "Create " << message_id << " in " << dialog_id;
auto dialog_type = dialog_id.get_type();
- auto my_id = td_->contacts_manager_->get_my_id("get_message_to_send");
+ auto my_id = td_->contacts_manager_->get_my_id();
auto m = make_unique<Message>();
- m->random_y = get_random_y(message_id);
- m->message_id = message_id;
bool is_channel_post = is_broadcast_channel(dialog_id);
if (is_channel_post) {
// sender of the post can be hidden
- if (td_->contacts_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) {
+ if (!is_scheduled && td_->contacts_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) {
m->author_signature = td_->contacts_manager_->get_user_title(my_id);
}
+ m->sender_dialog_id = dialog_id;
} else {
- m->sender_user_id = my_id;
+ if (send_as_dialog_id.is_valid()) {
+ if (send_as_dialog_id.get_type() == DialogType::User) {
+ m->sender_user_id = send_as_dialog_id.get_user_id();
+ } else {
+ m->sender_dialog_id = send_as_dialog_id;
+ }
+ } else if (d->default_send_message_as_dialog_id.is_valid()) {
+ if (d->default_send_message_as_dialog_id.get_type() == DialogType::User) {
+ m->sender_user_id = my_id;
+ } else {
+ m->sender_dialog_id = d->default_send_message_as_dialog_id;
+ }
+ m->has_explicit_sender = true;
+ } else {
+ if (is_anonymous_administrator(dialog_id, &m->author_signature)) {
+ m->sender_dialog_id = dialog_id;
+ } else {
+ m->sender_user_id = my_id;
+ }
+ }
}
- m->date = G()->unix_time();
+ m->send_date = G()->unix_time();
+ m->date = is_scheduled ? options.schedule_date : m->send_date;
m->reply_to_message_id = reply_to_message_id;
+ m->top_thread_message_id = top_thread_message_id;
+ if (reply_to_message_id.is_valid()) {
+ const Message *reply_m = get_message(d, reply_to_message_id);
+ if (reply_m != nullptr && reply_m->top_thread_message_id.is_valid()) {
+ m->top_thread_message_id = reply_m->top_thread_message_id;
+ }
+ if (reply_m != nullptr && m->top_thread_message_id.is_valid()) {
+ m->is_topic_message = reply_m->is_topic_message;
+ }
+ } else if (m->top_thread_message_id.is_valid()) {
+ const Message *top_m = get_message(d, m->top_thread_message_id);
+ if (top_m != nullptr) {
+ m->is_topic_message = top_m->is_topic_message;
+ }
+ }
m->is_channel_post = is_channel_post;
- m->is_outgoing = dialog_id != DialogId(my_id);
- m->from_background = from_background;
- m->views = is_channel_post ? 1 : 0;
+ m->is_outgoing = is_scheduled || dialog_id != DialogId(my_id);
+ m->from_background = options.from_background;
+ m->update_stickersets_order = options.update_stickersets_order;
+ m->noforwards = options.protect_content;
+ m->view_count = is_channel_post && !is_scheduled ? 1 : 0;
+ m->forward_count = 0;
+ if ([&] {
+ if (suppress_reply_info) {
+ return false;
+ }
+ if (is_scheduled) {
+ return false;
+ }
+ if (dialog_type != DialogType::Channel) {
+ return false;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ return false;
+ }
+ if (is_channel_post) {
+ return td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id());
+ }
+ return !reply_to_message_id.is_valid();
+ }()) {
+ m->reply_info.reply_count_ = 0;
+ if (is_channel_post) {
+ auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(dialog_id.get_channel_id());
+ if (linked_channel_id.is_valid()) {
+ m->reply_info.is_comment_ = true;
+ m->reply_info.channel_id_ = linked_channel_id;
+ }
+ }
+ }
m->content = std::move(content);
m->forward_info = std::move(forward_info);
+ m->is_copy = is_copy || m->forward_info != nullptr;
- if (td_->auth_manager_->is_bot()) {
- m->disable_notification = disable_notification;
+ if (td_->auth_manager_->is_bot() || options.disable_notification ||
+ td_->option_manager_->get_option_boolean("ignore_default_disable_notification")) {
+ m->disable_notification = options.disable_notification;
} else {
- auto notification_settings = get_notification_settings(NotificationSettingsScope(dialog_id.get()), true);
- CHECK(notification_settings != nullptr);
- m->disable_notification = notification_settings->silent_send_message;
+ m->disable_notification = d->notification_settings.silent_send_message;
}
if (dialog_type == DialogType::SecretChat) {
+ CHECK(!is_scheduled);
m->ttl = td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id());
- if (is_service_message_content(m->content->get_id())) {
+ if (is_service_message_content(m->content->get_type())) {
m->ttl = 0;
}
- m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_id());
- if (reply_to_message_id.is_valid()) {
- auto *reply_to_message = get_message_force(d, reply_to_message_id);
- if (reply_to_message != nullptr) {
- m->reply_to_random_id = reply_to_message->random_id;
- } else {
- m->reply_to_message_id = MessageId();
- }
+ m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type());
+ }
+ if ((reply_to_message_id.is_valid() || reply_to_message_id.is_valid_scheduled()) &&
+ (dialog_type == DialogType::SecretChat || reply_to_message_id.is_yet_unsent())) {
+ // the message was forcely preloaded in get_reply_to_message_id
+ auto *reply_to_message = get_message(d, reply_to_message_id);
+ if (reply_to_message == nullptr) {
+ m->reply_to_message_id = MessageId();
+ } else {
+ m->reply_to_random_id = reply_to_message->random_id;
}
}
- m->have_previous = true;
- m->have_next = true;
+ return m;
+}
- do {
- m->random_id = Random::secure_int64();
- } while (m->random_id == 0 || message_random_ids_.find(m->random_id) != message_random_ids_.end());
- message_random_ids_.insert(m->random_id);
+MessagesManager::Message *MessagesManager::get_message_to_send(
+ Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id, const MessageSendOptions &options,
+ unique_ptr<MessageContent> &&content, bool *need_update_dialog_pos, bool suppress_reply_info,
+ unique_ptr<MessageForwardInfo> forward_info, bool is_copy, DialogId send_as_dialog_id) {
+ d->was_opened = true;
+
+ auto message = create_message_to_send(d, top_thread_message_id, reply_to_message_id, options, std::move(content),
+ suppress_reply_info, std::move(forward_info), is_copy, send_as_dialog_id);
+
+ MessageId message_id = options.schedule_date != 0 ? get_next_yet_unsent_scheduled_message_id(d, options.schedule_date)
+ : get_next_yet_unsent_message_id(d);
+ set_message_id(message, message_id);
+
+ message->have_previous = true;
+ message->have_next = true;
+
+ message->random_id = generate_new_random_id(d);
bool need_update = false;
- CHECK(have_input_peer(dialog_id, AccessRights::Read));
- auto result = add_message_to_dialog(d, std::move(m), false, &need_update, need_update_dialog_pos, "send message");
- CHECK(result != nullptr);
+ CHECK(have_input_peer(d->dialog_id, AccessRights::Read));
+ auto result =
+ add_message_to_dialog(d, std::move(message), true, &need_update, need_update_dialog_pos, "send message");
+ LOG_CHECK(result != nullptr) << message_id << " " << debug_add_message_to_dialog_fail_reason_;
+ if (result->message_id.is_scheduled()) {
+ send_update_chat_has_scheduled_messages(d, false);
+ }
+ if (options.update_stickersets_order && !td_->auth_manager_->is_bot()) {
+ move_message_content_sticker_set_to_top(td_, result->content.get());
+ }
return result;
}
int64 MessagesManager::begin_send_message(DialogId dialog_id, const Message *m) {
LOG(INFO) << "Begin to send " << FullMessageId(dialog_id, m->message_id) << " with random_id = " << m->random_id;
- CHECK(m->random_id != 0 && being_sent_messages_.find(m->random_id) == being_sent_messages_.end());
- being_sent_messages_[m->random_id] = FullMessageId(dialog_id, m->message_id);
- debug_being_sent_messages_[m->random_id] = dialog_id;
+ CHECK(m->random_id != 0);
+ CHECK(m->message_id.is_yet_unsent());
+ bool is_inserted = being_sent_messages_.emplace(m->random_id, FullMessageId(dialog_id, m->message_id)).second;
+ CHECK(is_inserted);
return m->random_id;
}
@@ -14814,7 +25768,7 @@ Status MessagesManager::can_send_message(DialogId dialog_id) const {
if (dialog_id.get_type() == DialogType::Channel) {
auto channel_id = dialog_id.get_channel_id();
auto channel_type = td_->contacts_manager_->get_channel_type(channel_id);
- auto channel_status = td_->contacts_manager_->get_channel_status(channel_id);
+ auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
switch (channel_type) {
case ChannelType::Unknown:
@@ -14836,183 +25790,8 @@ Status MessagesManager::can_send_message(DialogId dialog_id) const {
return Status::OK();
}
-Status MessagesManager::can_send_message_content(DialogId dialog_id, const MessageContent *content,
- bool is_forward) const {
- auto dialog_type = dialog_id.get_type();
- int32 secret_chat_layer = std::numeric_limits<int32>::max();
- if (dialog_type == DialogType::SecretChat) {
- auto secret_chat_id = dialog_id.get_secret_chat_id();
- secret_chat_layer = td_->contacts_manager_->get_secret_chat_layer(secret_chat_id);
- }
-
- bool can_send_messages = true;
- bool can_send_media = true;
- bool can_send_stickers = true;
- bool can_send_animations = true;
- bool can_send_games = true;
-
- switch (dialog_type) {
- case DialogType::User:
- case DialogType::Chat:
- // ok
- break;
- case DialogType::Channel: {
- auto channel_status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
- can_send_messages = channel_status.can_send_messages();
- can_send_media = channel_status.can_send_media();
- can_send_stickers = channel_status.can_send_stickers();
- can_send_animations = channel_status.can_send_animations();
- can_send_games = channel_status.can_send_games();
- break;
- }
- case DialogType::SecretChat:
- can_send_games = false;
- break;
- case DialogType::None:
- default:
- UNREACHABLE();
- }
-
- switch (content->get_id()) {
- case MessageAnimation::ID:
- if (!can_send_animations) {
- return Status::Error(400, "Not enough rights to send animations to the chat");
- }
- break;
- case MessageAudio::ID:
- if (!can_send_media) {
- return Status::Error(400, "Not enough rights to send audios to the chat");
- }
- break;
- case MessageContact::ID:
- if (!can_send_messages) {
- return Status::Error(400, "Not enough rights to send contacts to the chat");
- }
- break;
- case MessageDocument::ID:
- if (!can_send_media) {
- return Status::Error(400, "Not enough rights to send documents to the chat");
- }
- break;
- case MessageGame::ID:
- switch (dialog_id.get_type()) {
- case DialogType::User:
- case DialogType::Chat:
- // ok
- break;
- case DialogType::Channel: {
- auto channel_type = td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id());
- if (channel_type == ChannelType::Broadcast) {
- return Status::Error(400, "Games can't be sent to channel chats");
- }
- break;
- }
- case DialogType::SecretChat:
- return Status::Error(400, "Games can't be sent to secret chats");
- case DialogType::None:
- default:
- UNREACHABLE();
- }
-
- if (!can_send_games) {
- return Status::Error(400, "Not enough rights to send games to the chat");
- }
- break;
- case MessageInvoice::ID:
- if (!is_forward) {
- switch (dialog_type) {
- case DialogType::User:
- // ok
- break;
- case DialogType::Chat:
- case DialogType::Channel:
- case DialogType::SecretChat:
- return Status::Error(400, "Invoices can be sent only to private chats");
- case DialogType::None:
- default:
- UNREACHABLE();
- }
- }
- break;
- case MessageLiveLocation::ID:
- if (!can_send_messages) {
- return Status::Error(400, "Not enough rights to send live locations to the chat");
- }
- break;
- case MessageLocation::ID:
- if (!can_send_messages) {
- return Status::Error(400, "Not enough rights to send locations to the chat");
- }
- break;
- case MessagePhoto::ID:
- if (!can_send_media) {
- return Status::Error(400, "Not enough rights to send photos to the chat");
- }
- break;
- case MessageSticker::ID:
- if (!can_send_stickers) {
- return Status::Error(400, "Not enough rights to send stickers to the chat");
- }
- break;
- case MessageText::ID:
- if (!can_send_messages) {
- return Status::Error(400, "Not enough rights to send text messages to the chat");
- }
- break;
- case MessageVenue::ID:
- if (!can_send_messages) {
- return Status::Error(400, "Not enough rights to send venues to the chat");
- }
- break;
- case MessageVideo::ID:
- if (!can_send_media) {
- return Status::Error(400, "Not enough rights to send videos to the chat");
- }
- break;
- case MessageVideoNote::ID:
- if (!can_send_media) {
- return Status::Error(400, "Not enough rights to send video notes to the chat");
- }
- if (secret_chat_layer < SecretChatActor::VOICE_NOTES_LAYER) {
- return Status::Error(400, PSLICE()
- << "Video notes can't be sent to secret chat with layer " << secret_chat_layer);
- }
- break;
- case MessageVoiceNote::ID:
- if (!can_send_media) {
- return Status::Error(400, "Not enough rights to send voice notes to the chat");
- }
- break;
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageUnsupported::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- UNREACHABLE();
- }
- return Status::OK();
-}
-
-MessageId MessagesManager::get_persistent_message_id(const Dialog *d, MessageId message_id) const {
- if (!message_id.is_valid()) {
+MessageId MessagesManager::get_persistent_message_id(const Dialog *d, MessageId message_id) {
+ if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
return MessageId();
}
if (message_id.is_yet_unsent()) {
@@ -15027,211 +25806,101 @@ MessageId MessagesManager::get_persistent_message_id(const Dialog *d, MessageId
return message_id;
}
-MessageId MessagesManager::get_reply_to_message_id(Dialog *d, MessageId message_id) {
+MessageId MessagesManager::get_reply_to_message_id(Dialog *d, MessageId top_thread_message_id, MessageId message_id,
+ bool for_draft) {
CHECK(d != nullptr);
+ if (top_thread_message_id.is_valid() && !have_message_force(d, top_thread_message_id, "get_reply_to_message_id 1")) {
+ LOG(INFO) << "Have reply to " << message_id << " in the thread of unknown " << top_thread_message_id;
+ }
+ if (!message_id.is_valid()) {
+ if (!for_draft && message_id == MessageId() && top_thread_message_id.is_valid() &&
+ top_thread_message_id.is_server()) {
+ return top_thread_message_id;
+ }
+ return MessageId();
+ }
message_id = get_persistent_message_id(d, message_id);
- const Message *reply_to_message = get_message_force(d, message_id);
- if (reply_to_message == nullptr || message_id.is_yet_unsent() ||
- (message_id.is_local() && d->dialog_id.get_type() != DialogType::SecretChat)) {
+ const Message *m = get_message_force(d, message_id, "get_reply_to_message_id 2");
+ if (m == nullptr || m->message_id.is_yet_unsent() ||
+ (m->message_id.is_local() && d->dialog_id.get_type() != DialogType::SecretChat)) {
+ if (message_id.is_server() && d->dialog_id.get_type() != DialogType::SecretChat &&
+ message_id > d->last_new_message_id && message_id <= d->max_notification_message_id) {
+ // allow to reply yet unreceived server message
+ return message_id;
+ }
+ if (!for_draft && top_thread_message_id.is_valid() && top_thread_message_id.is_server()) {
+ return top_thread_message_id;
+ }
+
// TODO local replies to local messages can be allowed
// TODO replies to yet unsent messages can be allowed with special handling of them on application restart
return MessageId();
}
- return message_id;
-}
-
-FormattedText MessagesManager::get_message_content_caption(const MessageContent *content) {
- switch (content->get_id()) {
- case MessageAnimation::ID:
- return static_cast<const MessageAnimation *>(content)->caption;
- case MessageAudio::ID:
- return static_cast<const MessageAudio *>(content)->caption;
- case MessageDocument::ID:
- return static_cast<const MessageDocument *>(content)->caption;
- case MessagePhoto::ID:
- return static_cast<const MessagePhoto *>(content)->caption;
- case MessageVideo::ID:
- return static_cast<const MessageVideo *>(content)->caption;
- case MessageVoiceNote::ID:
- return static_cast<const MessageVoiceNote *>(content)->caption;
- default:
- return FormattedText();
- }
+ return m->message_id;
}
-int32 MessagesManager::get_message_content_duration(const MessageContent *content) const {
- CHECK(content != nullptr);
- switch (content->get_id()) {
- case MessageAnimation::ID: {
- auto animation_file_id = static_cast<const MessageAnimation *>(content)->file_id;
- return td_->animations_manager_->get_animation_duration(animation_file_id);
- }
- case MessageAudio::ID: {
- auto audio_file_id = static_cast<const MessageAudio *>(content)->file_id;
- return td_->audios_manager_->get_audio_duration(audio_file_id);
- }
- case MessageVideo::ID: {
- auto video_file_id = static_cast<const MessageVideo *>(content)->file_id;
- return td_->videos_manager_->get_video_duration(video_file_id);
- }
- case MessageVideoNote::ID: {
- auto video_note_file_id = static_cast<const MessageVideoNote *>(content)->file_id;
- return td_->video_notes_manager_->get_video_note_duration(video_note_file_id);
+void MessagesManager::fix_server_reply_to_message_id(DialogId dialog_id, MessageId message_id,
+ DialogId reply_in_dialog_id,
+ MessageId &reply_to_message_id) const {
+ if (!reply_to_message_id.is_valid()) {
+ if (reply_to_message_id.is_valid_scheduled()) {
+ CHECK(message_id.is_scheduled());
+ CHECK(reply_in_dialog_id == DialogId());
+ if (message_id == reply_to_message_id) {
+ LOG(ERROR) << "Receive reply to " << reply_to_message_id << " for " << message_id << " in " << dialog_id;
+ reply_to_message_id = MessageId();
+ }
+ return;
}
- case MessageVoiceNote::ID: {
- auto voice_file_id = static_cast<const MessageVoiceNote *>(content)->file_id;
- return td_->voice_notes_manager_->get_voice_note_duration(voice_file_id);
+ if (reply_to_message_id != MessageId()) {
+ LOG(ERROR) << "Receive reply to " << reply_to_message_id << " for " << message_id << " in " << dialog_id;
+ reply_to_message_id = MessageId();
}
+ return;
}
- return 0;
-}
-FileId MessagesManager::get_message_content_file_id(const MessageContent *content) {
- switch (content->get_id()) {
- case MessageAnimation::ID:
- return static_cast<const MessageAnimation *>(content)->file_id;
- case MessageAudio::ID:
- return static_cast<const MessageAudio *>(content)->file_id;
- case MessageDocument::ID:
- return static_cast<const MessageDocument *>(content)->file_id;
- case MessagePhoto::ID:
- for (auto &size : static_cast<const MessagePhoto *>(content)->photo.photos) {
- if (size.type == 'i') {
- return size.file_id;
- }
- }
- break;
- case MessageSticker::ID:
- return static_cast<const MessageSticker *>(content)->file_id;
- case MessageVideo::ID:
- return static_cast<const MessageVideo *>(content)->file_id;
- case MessageVideoNote::ID:
- return static_cast<const MessageVideoNote *>(content)->file_id;
- case MessageVoiceNote::ID:
- return static_cast<const MessageVoiceNote *>(content)->file_id;
- default:
- break;
- }
- return FileId();
-}
-
-void MessagesManager::update_message_content_file_id_remote(MessageContent *content, FileId file_id) {
- if (file_id.get_remote() == 0) {
- return;
- }
- FileId *old_file_id = [&]() {
- switch (content->get_id()) {
- case MessageAnimation::ID:
- return &static_cast<MessageAnimation *>(content)->file_id;
- case MessageAudio::ID:
- return &static_cast<MessageAudio *>(content)->file_id;
- case MessageDocument::ID:
- return &static_cast<MessageDocument *>(content)->file_id;
- case MessageSticker::ID:
- return &static_cast<MessageSticker *>(content)->file_id;
- case MessageVideo::ID:
- return &static_cast<MessageVideo *>(content)->file_id;
- case MessageVideoNote::ID:
- return &static_cast<MessageVideoNote *>(content)->file_id;
- case MessageVoiceNote::ID:
- return &static_cast<MessageVoiceNote *>(content)->file_id;
- default:
- return static_cast<FileId *>(nullptr);
+ if (!message_id.is_scheduled() && !reply_in_dialog_id.is_valid() && reply_to_message_id >= message_id) {
+ if (!has_qts_messages(dialog_id)) {
+ LOG(ERROR) << "Receive reply to wrong " << reply_to_message_id << " in " << message_id << " in " << dialog_id;
}
- }();
- if (old_file_id != nullptr && *old_file_id == file_id && old_file_id->get_remote() == 0) {
- *old_file_id = file_id;
+ reply_to_message_id = MessageId();
}
}
-FileId MessagesManager::get_message_content_thumbnail_file_id(const MessageContent *content) const {
- switch (content->get_id()) {
- case MessageAnimation::ID:
- return td_->animations_manager_->get_animation_thumbnail_file_id(
- static_cast<const MessageAnimation *>(content)->file_id);
- case MessageAudio::ID:
- return td_->audios_manager_->get_audio_thumbnail_file_id(static_cast<const MessageAudio *>(content)->file_id);
- case MessageDocument::ID:
- return td_->documents_manager_->get_document_thumbnail_file_id(
- static_cast<const MessageDocument *>(content)->file_id);
- case MessagePhoto::ID:
- for (auto &size : static_cast<const MessagePhoto *>(content)->photo.photos) {
- if (size.type == 't') {
- return size.file_id;
- }
- }
- break;
- case MessageSticker::ID:
- return td_->stickers_manager_->get_sticker_thumbnail_file_id(
- static_cast<const MessageSticker *>(content)->file_id);
- case MessageVideo::ID:
- return td_->videos_manager_->get_video_thumbnail_file_id(static_cast<const MessageVideo *>(content)->file_id);
- case MessageVideoNote::ID:
- return td_->video_notes_manager_->get_video_note_thumbnail_file_id(
- static_cast<const MessageVideoNote *>(content)->file_id);
- case MessageVoiceNote::ID:
- return FileId();
- default:
- break;
+vector<FileId> MessagesManager::get_message_file_ids(const Message *m) const {
+ CHECK(m != nullptr);
+ return get_message_content_file_ids(m->content.get(), td_);
+}
+
+void MessagesManager::cancel_upload_message_content_files(const MessageContent *content) {
+ auto file_id = get_message_content_upload_file_id(content);
+ // always cancel file upload, it should be a no-op in the worst case
+ if (being_uploaded_files_.erase(file_id) || file_id.is_valid()) {
+ cancel_upload_file(file_id, "cancel_upload_message_content_files");
}
- return FileId();
-}
-
-vector<FileId> MessagesManager::get_message_file_ids(const Message *message) const {
- auto content = message->content.get();
- switch (content->get_id()) {
- case MessagePhoto::ID:
- return transform(static_cast<const MessagePhoto *>(content)->photo.photos,
- [](auto &size) { return size.file_id; });
- case MessageAnimation::ID:
- case MessageAudio::ID:
- case MessageDocument::ID:
- case MessageSticker::ID:
- case MessageVideo::ID:
- case MessageVideoNote::ID:
- case MessageVoiceNote::ID: {
- vector<FileId> result;
- result.reserve(2);
- FileId file_id = get_message_content_file_id(content);
- if (file_id.is_valid()) {
- result.push_back(file_id);
- }
- FileId thumbnail_file_id = get_message_content_thumbnail_file_id(content);
- if (file_id.is_valid()) {
- result.push_back(thumbnail_file_id);
- }
- return result;
- }
- default:
- return {};
+ file_id = get_message_content_thumbnail_file_id(content, td_);
+ if (being_uploaded_thumbnails_.erase(file_id) || file_id.is_valid()) {
+ cancel_upload_file(file_id, "cancel_upload_message_content_files");
}
}
-void MessagesManager::cancel_send_message_query(DialogId dialog_id, unique_ptr<Message> &m) {
+void MessagesManager::cancel_upload_file(FileId file_id, const char *source) {
+ // send the request later so they doesn't interfere with other actions
+ // for example merge, supposed to happen soon, can auto-cancel the upload
+ LOG(INFO) << "Cancel upload of file " << file_id << " from " << source;
+ send_closure_later(G()->file_manager(), &FileManager::cancel_upload, file_id);
+}
+
+void MessagesManager::cancel_send_message_query(DialogId dialog_id, Message *m) {
CHECK(m != nullptr);
CHECK(m->content != nullptr);
+ CHECK(m->message_id.is_valid() || m->message_id.is_valid_scheduled());
CHECK(m->message_id.is_yet_unsent());
LOG(INFO) << "Cancel send message query for " << m->message_id;
- auto file_id = get_message_content_file_id(m->content.get());
- if (file_id.is_valid()) {
- auto it = being_uploaded_files_.find(file_id);
- if (it != being_uploaded_files_.end()) {
- LOG(INFO) << "Cancel upload file " << file_id << " for " << m->message_id;
- td_->file_manager_->upload(file_id, nullptr, 0, 0);
- being_uploaded_files_.erase(it);
- }
- }
+ cancel_upload_message_content_files(m->content.get());
- auto thumbnail_file_id = get_message_content_thumbnail_file_id(m->content.get());
- if (thumbnail_file_id.is_valid()) {
- auto it = being_uploaded_thumbnails_.find(thumbnail_file_id);
- if (it != being_uploaded_thumbnails_.end()) {
- LOG(INFO) << "Cancel upload thumbnail file " << thumbnail_file_id << " for " << m->message_id;
- td_->file_manager_->upload(thumbnail_file_id, nullptr, 0, 0);
- being_uploaded_thumbnails_.erase(it);
- }
- }
+ CHECK(m->edited_content == nullptr);
if (!m->send_query_ref.empty()) {
LOG(INFO) << "Cancel send query for " << m->message_id;
@@ -15239,10 +25908,10 @@ void MessagesManager::cancel_send_message_query(DialogId dialog_id, unique_ptr<M
m->send_query_ref = NetQueryRef();
}
- if (m->send_message_logevent_id != 0) {
+ if (m->send_message_log_event_id != 0) {
LOG(INFO) << "Delete send message log event for " << m->message_id;
- BinlogHelper::erase(G()->td_db()->get_binlog(), m->send_message_logevent_id);
- m->send_message_logevent_id = 0;
+ binlog_erase(G()->td_db()->get_binlog(), m->send_message_log_event_id);
+ m->send_message_log_event_id = 0;
}
if (m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_yet_unsent()) {
@@ -15254,38 +25923,76 @@ void MessagesManager::cancel_send_message_query(DialogId dialog_id, unique_ptr<M
replied_by_yet_unsent_messages_.erase(it);
}
}
+ if ((m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_valid_scheduled()) &&
+ m->reply_to_message_id.is_yet_unsent()) {
+ auto it = replied_yet_unsent_messages_.find({dialog_id, m->reply_to_message_id});
+ CHECK(it != replied_yet_unsent_messages_.end());
+ size_t erased_count = it->second.erase(m->message_id);
+ CHECK(erased_count > 0);
+ if (it->second.empty()) {
+ replied_yet_unsent_messages_.erase(it);
+ }
+ }
+ {
+ auto it = replied_yet_unsent_messages_.find({dialog_id, m->message_id});
+ if (it != replied_yet_unsent_messages_.end()) {
+ for (auto message_id : it->second) {
+ auto replied_m = get_message({dialog_id, message_id});
+ CHECK(replied_m != nullptr);
+ CHECK(replied_m->reply_to_message_id == m->message_id);
+ unregister_message_reply(dialog_id, replied_m);
+ replied_m->reply_to_message_id = replied_m->top_thread_message_id;
+ replied_m->reply_to_random_id = 0;
+ register_message_reply(dialog_id, replied_m);
+ }
+ replied_yet_unsent_messages_.erase(it);
+ }
+ }
if (m->media_album_id != 0) {
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id,
m->message_id, Status::OK());
}
- if (G()->parameters().use_file_db) { // ResourceManager::Mode::Baseline
- auto queue_id = get_sequence_dispatcher_id(dialog_id, m->content->get_id());
+ if (!m->message_id.is_scheduled() && G()->parameters().use_file_db &&
+ !m->is_copy) { // ResourceManager::Mode::Baseline
+ auto queue_id = ChainId(dialog_id, m->content->get_type()).get();
if (queue_id & 1) {
auto queue_it = yet_unsent_media_queues_.find(queue_id);
if (queue_it != yet_unsent_media_queues_.end()) {
auto &queue = queue_it->second;
LOG(INFO) << "Delete " << m->message_id << " from queue " << queue_id;
- queue.erase(m->message_id.get());
- if (queue.empty()) {
- yet_unsent_media_queues_.erase(queue_it);
- } else {
- on_yet_unsent_media_queue_updated(dialog_id);
+ if (queue.erase(m->message_id) != 0) {
+ if (queue.empty()) {
+ yet_unsent_media_queues_.erase(queue_it);
+ } else {
+ // send later, because do_delete_all_dialog_messages can be called right now
+ send_closure_later(actor_id(this), &MessagesManager::on_yet_unsent_media_queue_updated, dialog_id);
+ }
}
}
}
}
}
-bool MessagesManager::is_message_auto_read(DialogId dialog_id, bool is_outgoing, bool only_content) const {
+void MessagesManager::cancel_send_deleted_message(DialogId dialog_id, Message *m, bool is_permanently_deleted) {
+ CHECK(m != nullptr);
+ if (m->message_id.is_yet_unsent()) {
+ cancel_send_message_query(dialog_id, m);
+ } else if (is_permanently_deleted || !m->message_id.is_scheduled()) {
+ cancel_edit_message_media(dialog_id, m, "Message was deleted");
+ }
+}
+
+bool MessagesManager::is_message_auto_read(DialogId dialog_id, bool is_outgoing) const {
switch (dialog_id.get_type()) {
case DialogType::User: {
auto user_id = dialog_id.get_user_id();
- if (user_id == td_->contacts_manager_->get_my_id("is_message_auto_read")) {
+ if (user_id == td_->contacts_manager_->get_my_id()) {
return true;
}
- if (is_outgoing && td_->contacts_manager_->is_user_bot(user_id)) {
+ if (is_outgoing && td_->contacts_manager_->is_user_bot(user_id) &&
+ !td_->contacts_manager_->is_user_support(user_id)) {
return true;
}
return false;
@@ -15293,13 +26000,8 @@ bool MessagesManager::is_message_auto_read(DialogId dialog_id, bool is_outgoing,
case DialogType::Chat:
// TODO auto_read message content and messages sent to group with bots only
return false;
- case DialogType::Channel: {
- if (only_content) {
- return false;
- }
- auto channel_type = td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id());
- return channel_type != ChannelType::Megagroup;
- }
+ case DialogType::Channel:
+ return is_outgoing && is_broadcast_channel(dialog_id);
case DialogType::SecretChat:
return false;
case DialogType::None:
@@ -15310,184 +26012,161 @@ bool MessagesManager::is_message_auto_read(DialogId dialog_id, bool is_outgoing,
}
}
-void MessagesManager::add_message_dependencies(Dependencies &dependencies, DialogId dialog_id, const Message *m) {
- dependencies.user_ids.insert(m->sender_user_id);
- dependencies.user_ids.insert(m->via_bot_user_id);
+void MessagesManager::add_message_dependencies(Dependencies &dependencies, const Message *m) {
+ dependencies.add(m->sender_user_id);
+ dependencies.add_dialog_and_dependencies(m->sender_dialog_id);
+ dependencies.add_dialog_and_dependencies(m->reply_in_dialog_id);
+ dependencies.add_dialog_and_dependencies(m->real_forward_from_dialog_id);
+ dependencies.add(m->via_bot_user_id);
if (m->forward_info != nullptr) {
- dependencies.user_ids.insert(m->forward_info->sender_user_id);
- if (m->forward_info->dialog_id.is_valid() && dependencies.dialog_ids.insert(m->forward_info->dialog_id).second) {
- add_dialog_dependencies(dependencies, m->forward_info->dialog_id);
- }
- if (m->forward_info->from_dialog_id.is_valid() &&
- dependencies.dialog_ids.insert(m->forward_info->from_dialog_id).second) {
- add_dialog_dependencies(dependencies, m->forward_info->from_dialog_id);
- }
+ dependencies.add(m->forward_info->sender_user_id);
+ dependencies.add_dialog_and_dependencies(m->forward_info->sender_dialog_id);
+ dependencies.add_dialog_and_dependencies(m->forward_info->from_dialog_id);
+ }
+ for (const auto &replier_min_channel : m->reply_info.replier_min_channels_) {
+ LOG(INFO) << "Add min replied " << replier_min_channel.first;
+ td_->contacts_manager_->add_min_channel(replier_min_channel.first, replier_min_channel.second);
+ }
+ for (auto recent_replier_dialog_id : m->reply_info.recent_replier_dialog_ids_) {
+ // don't load the dialog itself
+ // it will be created in get_message_reply_info_object if needed
+ dependencies.add_dialog_dependencies(recent_replier_dialog_id);
+ }
+ if (m->reactions != nullptr) {
+ m->reactions->add_min_channels(td_);
+ m->reactions->add_dependencies(dependencies);
+ }
+ add_message_content_dependencies(dependencies, m->content.get());
+ add_reply_markup_dependencies(dependencies, m->reply_markup.get());
+}
+
+void MessagesManager::get_dialog_send_message_as_dialog_ids(
+ DialogId dialog_id, Promise<td_api::object_ptr<td_api::chatMessageSenders>> &&promise, bool is_recursive) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ const Dialog *d = get_dialog_force(dialog_id, "get_group_call_join_as");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access chat"));
+ }
+ if (!d->default_send_message_as_dialog_id.is_valid()) {
+ return promise.set_value(td_api::make_object<td_api::chatMessageSenders>());
}
- switch (m->content->get_id()) {
- case MessageText::ID: {
- auto content = static_cast<const MessageText *>(m->content.get());
- for (auto &entity : content->text.entities) {
- if (entity.user_id.is_valid()) {
- dependencies.user_ids.insert(entity.user_id);
+ CHECK(dialog_id.get_type() == DialogType::Channel);
+
+ if (created_public_broadcasts_inited_) {
+ auto senders = td_api::make_object<td_api::chatMessageSenders>();
+ if (!created_public_broadcasts_.empty()) {
+ auto add_sender = [&senders, td = td_](DialogId dialog_id, bool needs_premium) {
+ auto sender = get_message_sender_object_const(td, dialog_id, "add_sender");
+ senders->senders_.push_back(td_api::make_object<td_api::chatMessageSender>(std::move(sender), needs_premium));
+ };
+ if (is_anonymous_administrator(dialog_id, nullptr)) {
+ add_sender(dialog_id, false);
+ } else {
+ add_sender(get_my_dialog_id(), false);
+ }
+
+ struct Sender {
+ ChannelId channel_id;
+ bool needs_premium;
+ };
+ std::multimap<int64, Sender> sorted_senders;
+
+ bool is_premium = td_->option_manager_->get_option_boolean("is_premium");
+ auto linked_channel_id = td_->contacts_manager_->get_channel_linked_channel_id(dialog_id.get_channel_id());
+ for (auto channel_id : created_public_broadcasts_) {
+ int64 score = td_->contacts_manager_->get_channel_participant_count(channel_id);
+ bool needs_premium = !is_premium && channel_id != linked_channel_id &&
+ !td_->contacts_manager_->get_channel_is_verified(channel_id);
+ if (needs_premium) {
+ score -= static_cast<int64>(1) << 40;
}
+ if (channel_id == linked_channel_id) {
+ score += static_cast<int64>(1) << 32;
+ }
+ sorted_senders.emplace(-score, Sender{channel_id, needs_premium});
+ };
+
+ for (auto &sender : sorted_senders) {
+ add_sender(DialogId(sender.second.channel_id), sender.second.needs_premium);
}
- dependencies.web_page_ids.insert(content->web_page_id);
- break;
- }
- case MessageAnimation::ID:
- break;
- case MessageAudio::ID:
- break;
- case MessageContact::ID: {
- auto content = static_cast<const MessageContact *>(m->content.get());
- dependencies.user_ids.insert(content->contact.get_user_id());
- break;
- }
- case MessageDocument::ID:
- break;
- case MessageGame::ID: {
- auto content = static_cast<const MessageGame *>(m->content.get());
- dependencies.user_ids.insert(content->game.get_bot_user_id());
- break;
- }
- case MessageInvoice::ID:
- break;
- case MessageLiveLocation::ID:
- break;
- case MessageLocation::ID:
- break;
- case MessagePhoto::ID:
- break;
- case MessageSticker::ID:
- break;
- case MessageVenue::ID:
- break;
- case MessageVideo::ID:
- break;
- case MessageVideoNote::ID:
- break;
- case MessageVoiceNote::ID:
- break;
- case MessageChatCreate::ID: {
- auto content = static_cast<const MessageChatCreate *>(m->content.get());
- dependencies.user_ids.insert(content->participant_user_ids.begin(), content->participant_user_ids.end());
- break;
- }
- case MessageChatChangeTitle::ID:
- break;
- case MessageChatChangePhoto::ID:
- break;
- case MessageChatDeletePhoto::ID:
- break;
- case MessageChatDeleteHistory::ID:
- break;
- case MessageChatAddUsers::ID: {
- auto content = static_cast<const MessageChatAddUsers *>(m->content.get());
- dependencies.user_ids.insert(content->user_ids.begin(), content->user_ids.end());
- break;
- }
- case MessageChatJoinedByLink::ID:
- break;
- case MessageChatDeleteUser::ID: {
- auto content = static_cast<const MessageChatDeleteUser *>(m->content.get());
- dependencies.user_ids.insert(content->user_id);
- break;
- }
- case MessageChatMigrateTo::ID: {
- auto content = static_cast<const MessageChatMigrateTo *>(m->content.get());
- dependencies.channel_ids.insert(content->migrated_to_channel_id);
- break;
}
- case MessageChannelCreate::ID:
- break;
- case MessageChannelMigrateFrom::ID: {
- auto content = static_cast<const MessageChannelMigrateFrom *>(m->content.get());
- dependencies.chat_ids.insert(content->migrated_from_chat_id);
- break;
- }
- case MessagePinMessage::ID:
- break;
- case MessageGameScore::ID:
- break;
- case MessageScreenshotTaken::ID:
- break;
- case MessageChatSetTtl::ID:
- break;
- case MessageUnsupported::ID:
- break;
- case MessageCall::ID:
- break;
- case MessagePaymentSuccessful::ID:
- break;
- case MessageContactRegistered::ID:
- break;
- case MessageExpiredPhoto::ID:
- break;
- case MessageExpiredVideo::ID:
- break;
- case MessageCustomServiceAction::ID:
- break;
- case MessageWebsiteConnected::ID:
- break;
- default:
- UNREACHABLE();
- break;
+ return promise.set_value(std::move(senders));
}
+
+ CHECK(!is_recursive);
+ auto new_promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, promise = std::move(promise)](
+ Result<td_api::object_ptr<td_api::chats>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure_later(actor_id, &MessagesManager::get_dialog_send_message_as_dialog_ids, dialog_id,
+ std::move(promise), true);
+ }
+ });
+ td_->contacts_manager_->get_created_public_dialogs(PublicDialogType::HasUsername, std::move(new_promise), true);
}
-void MessagesManager::add_dialog_dependencies(Dependencies &dependencies, DialogId dialog_id) {
- switch (dialog_id.get_type()) {
+void MessagesManager::set_dialog_default_send_message_as_dialog_id(DialogId dialog_id,
+ DialogId message_sender_dialog_id,
+ Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "set_dialog_default_send_message_as_dialog_id");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!d->default_send_message_as_dialog_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Can't change message sender in the chat"));
+ }
+ // checked in on_update_dialog_default_send_message_as_dialog_id
+ CHECK(dialog_id.get_type() == DialogType::Channel && !is_broadcast_channel(dialog_id));
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ bool is_anonymous = is_anonymous_administrator(dialog_id, nullptr);
+ switch (message_sender_dialog_id.get_type()) {
case DialogType::User:
- dependencies.user_ids.insert(dialog_id.get_user_id());
+ if (message_sender_dialog_id != DialogId(td_->contacts_manager_->get_my_id())) {
+ return promise.set_error(Status::Error(400, "Can't send messages as another user"));
+ }
+ if (is_anonymous) {
+ return promise.set_error(Status::Error(400, "Can't send messages as self"));
+ }
break;
case DialogType::Chat:
- dependencies.chat_ids.insert(dialog_id.get_chat_id());
- break;
case DialogType::Channel:
- dependencies.channel_ids.insert(dialog_id.get_channel_id());
- break;
case DialogType::SecretChat:
- dependencies.secret_chat_ids.insert(dialog_id.get_secret_chat_id());
- break;
- case DialogType::None:
+ if (is_anonymous && dialog_id == message_sender_dialog_id) {
+ break;
+ }
+ if (!is_broadcast_channel(message_sender_dialog_id) ||
+ td_->contacts_manager_->get_channel_first_username(message_sender_dialog_id.get_channel_id()).empty()) {
+ return promise.set_error(Status::Error(400, "Message sender chat must be a public channel"));
+ }
break;
default:
- UNREACHABLE();
+ return promise.set_error(Status::Error(400, "Invalid message sender specified"));
}
-}
-
-void MessagesManager::resolve_dependencies_force(const Dependencies &dependencies) {
- for (auto user_id : dependencies.user_ids) {
- if (user_id.is_valid() && !td_->contacts_manager_->have_user_force(user_id)) {
- LOG(ERROR) << "Can't find " << user_id;
- }
- }
- for (auto chat_id : dependencies.chat_ids) {
- if (chat_id.is_valid() && !td_->contacts_manager_->have_chat_force(chat_id)) {
- LOG(ERROR) << "Can't find " << chat_id;
- }
- }
- for (auto channel_id : dependencies.channel_ids) {
- if (channel_id.is_valid() && !td_->contacts_manager_->have_channel_force(channel_id)) {
- LOG(ERROR) << "Can't find " << channel_id;
- }
- }
- for (auto secret_chat_id : dependencies.secret_chat_ids) {
- if (secret_chat_id.is_valid() && !td_->contacts_manager_->have_secret_chat_force(secret_chat_id)) {
- LOG(ERROR) << "Can't find " << secret_chat_id;
- }
+ if (!have_input_peer(message_sender_dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access specified message sender chat"));
}
- for (auto dialog_id : dependencies.dialog_ids) {
- if (dialog_id.is_valid() && !have_dialog_force(dialog_id)) {
- LOG(ERROR) << "Can't find " << dialog_id;
- force_create_dialog(dialog_id, "resolve_dependencies_force");
- }
- }
- for (auto web_page_id : dependencies.web_page_ids) {
- if (web_page_id.is_valid()) {
- td_->web_pages_manager_->have_web_page_force(web_page_id);
+
+ {
+ auto it = set_typing_query_.find(dialog_id);
+ if (it != set_typing_query_.end()) {
+ if (!it->second.empty()) {
+ cancel_query(it->second);
+ }
+ set_typing_query_.erase(it);
}
}
+
+ td_->create_handler<SaveDefaultSendAsQuery>(std::move(promise))->send(dialog_id, message_sender_dialog_id);
+
+ on_update_dialog_default_send_message_as_dialog_id(dialog_id, message_sender_dialog_id, true);
}
class MessagesManager::SendMessageLogEvent {
@@ -15511,572 +26190,372 @@ class MessagesManager::SendMessageLogEvent {
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id, parser);
- CHECK(m_out == nullptr);
- m_out = make_unique<Message>();
- td::parse(*m_out, parser);
+ td::parse(m_out, parser);
}
};
-Result<MessageId> MessagesManager::send_message(DialogId dialog_id, MessageId reply_to_message_id,
- bool disable_notification, bool from_background,
- tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
- tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
+Result<td_api::object_ptr<td_api::message>> MessagesManager::send_message(
+ DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id,
+ tl_object_ptr<td_api::messageSendOptions> &&options, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
if (input_message_content == nullptr) {
- return Status::Error(5, "Can't send message without content");
+ return Status::Error(400, "Can't send message without content");
}
- LOG(INFO) << "Begin to send message to " << dialog_id << " in reply to " << reply_to_message_id;
- if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) {
- auto input_message = static_cast<const td_api::inputMessageForwarded *>(input_message_content.get());
- return forward_message(dialog_id, DialogId(input_message->from_chat_id_), MessageId(input_message->message_id_),
- disable_notification, from_background, input_message->in_game_share_);
+ Dialog *d = get_dialog_force(dialog_id, "send_message");
+ if (d == nullptr) {
+ return Status::Error(400, "Chat not found");
}
- Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr) {
- return Status::Error(5, "Chat not found");
+ LOG(INFO) << "Begin to send message to " << dialog_id << " in reply to " << reply_to_message_id;
+
+ reply_to_message_id = get_reply_to_message_id(d, top_thread_message_id, reply_to_message_id, false);
+
+ if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) {
+ auto input_message = td_api::move_object_as<td_api::inputMessageForwarded>(input_message_content);
+ TRY_RESULT(copy_options, process_message_copy_options(dialog_id, std::move(input_message->copy_options_)));
+ copy_options.reply_to_message_id = reply_to_message_id;
+ TRY_RESULT_ASSIGN(copy_options.reply_markup, get_dialog_reply_markup(dialog_id, std::move(reply_markup)));
+ return forward_message(dialog_id, top_thread_message_id, DialogId(input_message->from_chat_id_),
+ MessageId(input_message->message_id_), std::move(options), input_message->in_game_share_,
+ std::move(copy_options));
}
TRY_STATUS(can_send_message(dialog_id));
TRY_RESULT(message_reply_markup, get_dialog_reply_markup(dialog_id, std::move(reply_markup)));
TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content)));
+ TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true));
+ TRY_STATUS(can_use_message_send_options(message_send_options, message_content));
+ TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, reply_to_message_id));
// there must be no errors after get_message_to_send call
bool need_update_dialog_pos = false;
- Message *m = get_message_to_send(
- d, get_reply_to_message_id(d, reply_to_message_id), disable_notification, from_background,
- dup_message_content(dialog_id, message_content.content.get(), false), &need_update_dialog_pos);
+ Message *m = get_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options,
+ dup_message_content(td_, dialog_id, message_content.content.get(),
+ MessageContentDupType::Send, MessageCopyOptions()),
+ &need_update_dialog_pos, false, nullptr, message_content.via_bot_user_id.is_valid());
m->reply_markup = std::move(message_reply_markup);
m->via_bot_user_id = message_content.via_bot_user_id;
m->disable_web_page_preview = message_content.disable_web_page_preview;
m->clear_draft = message_content.clear_draft;
if (message_content.ttl > 0) {
m->ttl = message_content.ttl;
- m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_id());
+ m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type());
}
+ m->send_emoji = std::move(message_content.emoji);
- send_update_new_message(d, m, true);
- if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "send_message");
+ if (m->clear_draft) {
+ if (top_thread_message_id.is_valid()) {
+ set_dialog_draft_message(dialog_id, top_thread_message_id, nullptr).ignore();
+ } else {
+ update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos);
+ }
}
- if (message_content.clear_draft) {
- update_dialog_draft_message(d, nullptr, false, true);
+ save_send_message_log_event(dialog_id, m);
+ do_send_message(dialog_id, m);
+
+ send_update_new_message(d, m);
+ if (need_update_dialog_pos) {
+ send_update_chat_last_message(d, "send_message");
}
- auto message_id = m->message_id;
- save_send_message_logevent(dialog_id, m);
- do_send_message(dialog_id, m);
- return message_id;
+ return get_message_object(dialog_id, m, "send_message");
}
-Result<MessagesManager::InputMessageContent> MessagesManager::process_input_message_content(
- DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content) const {
+Result<InputMessageContent> MessagesManager::process_input_message_content(
+ DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
if (input_message_content == nullptr) {
- return Status::Error(5, "Can't send message without content");
+ return Status::Error(400, "Can't send message without content");
}
- bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
- int32 message_type = input_message_content->get_id();
-
- bool have_file = true;
- // TODO: send from secret chat to common
- Result<FileId> r_file_id;
- tl_object_ptr<td_api::inputThumbnail> input_thumbnail;
- vector<FileId> sticker_file_ids;
- switch (message_type) {
- case td_api::inputMessageAnimation::ID: {
- auto input_message = static_cast<td_api::inputMessageAnimation *>(input_message_content.get());
- r_file_id = td_->file_manager_->get_input_file_id(FileType::Animation, input_message->animation_, dialog_id,
- false, is_secret, true);
- input_thumbnail = std::move(input_message->thumbnail_);
- break;
- }
- case td_api::inputMessageAudio::ID: {
- auto input_message = static_cast<td_api::inputMessageAudio *>(input_message_content.get());
- r_file_id =
- td_->file_manager_->get_input_file_id(FileType::Audio, input_message->audio_, dialog_id, false, is_secret);
- input_thumbnail = std::move(input_message->album_cover_thumbnail_);
- break;
- }
- case td_api::inputMessageDocument::ID: {
- auto input_message = static_cast<td_api::inputMessageDocument *>(input_message_content.get());
- r_file_id = td_->file_manager_->get_input_file_id(FileType::Document, input_message->document_, dialog_id, false,
- is_secret, true);
- input_thumbnail = std::move(input_message->thumbnail_);
- break;
- }
- case td_api::inputMessagePhoto::ID: {
- auto input_message = static_cast<td_api::inputMessagePhoto *>(input_message_content.get());
- r_file_id =
- td_->file_manager_->get_input_file_id(FileType::Photo, input_message->photo_, dialog_id, false, is_secret);
- input_thumbnail = std::move(input_message->thumbnail_);
- if (!input_message->added_sticker_file_ids_.empty()) {
- sticker_file_ids =
- td_->stickers_manager_->get_attached_sticker_file_ids(input_message->added_sticker_file_ids_);
- }
- break;
- }
- case td_api::inputMessageSticker::ID: {
- auto input_message = static_cast<td_api::inputMessageSticker *>(input_message_content.get());
- r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_message->sticker_, dialog_id, false,
- is_secret);
- input_thumbnail = std::move(input_message->thumbnail_);
- break;
+ if (input_message_content->get_id() == td_api::inputMessageForwarded::ID) {
+ // for sendMessageAlbum/editMessageMedia/addLocalMessage
+ auto input_message = td_api::move_object_as<td_api::inputMessageForwarded>(input_message_content);
+ TRY_RESULT(copy_options, process_message_copy_options(dialog_id, std::move(input_message->copy_options_)));
+ if (!copy_options.send_copy) {
+ return Status::Error(400, "Can't use forwarded message");
}
- case td_api::inputMessageVideo::ID: {
- auto input_message = static_cast<td_api::inputMessageVideo *>(input_message_content.get());
- r_file_id =
- td_->file_manager_->get_input_file_id(FileType::Video, input_message->video_, dialog_id, false, is_secret);
- input_thumbnail = std::move(input_message->thumbnail_);
- if (!input_message->added_sticker_file_ids_.empty()) {
- sticker_file_ids =
- td_->stickers_manager_->get_attached_sticker_file_ids(input_message->added_sticker_file_ids_);
- }
- break;
+
+ DialogId from_dialog_id(input_message->from_chat_id_);
+ Dialog *from_dialog = get_dialog_force(from_dialog_id, "send_message copy");
+ if (from_dialog == nullptr) {
+ return Status::Error(400, "Chat to copy message from not found");
}
- case td_api::inputMessageVideoNote::ID: {
- auto input_message = static_cast<td_api::inputMessageVideoNote *>(input_message_content.get());
- r_file_id = td_->file_manager_->get_input_file_id(FileType::VideoNote, input_message->video_note_, dialog_id,
- false, is_secret);
- input_thumbnail = std::move(input_message->thumbnail_);
- break;
+ if (!have_input_peer(from_dialog_id, AccessRights::Read)) {
+ return Status::Error(400, "Can't access the chat to copy message from");
}
- case td_api::inputMessageVoiceNote::ID: {
- auto input_message = static_cast<td_api::inputMessageVoiceNote *>(input_message_content.get());
- r_file_id = td_->file_manager_->get_input_file_id(FileType::VoiceNote, input_message->voice_note_, dialog_id,
- false, is_secret);
- break;
+ if (from_dialog_id.get_type() == DialogType::SecretChat) {
+ return Status::Error(400, "Can't copy message from secret chats");
}
- default:
- have_file = false;
- break;
- }
- // TODO is path of files must be stored in bytes instead of UTF-8 string?
-
- FileId file_id;
- FileView file_view;
- string file_name;
- string mime_type;
- if (have_file) {
- if (r_file_id.is_error()) {
- return Status::Error(7, r_file_id.error().message());
- }
- file_id = r_file_id.ok();
- CHECK(file_id.is_valid());
- file_view = td_->file_manager_->get_file_view(file_id);
- auto suggested_name = file_view.suggested_name();
- const PathView path_view(suggested_name);
- file_name = path_view.file_name().str();
- mime_type = MimeType::from_extension(path_view.extension());
- }
-
- FileId thumbnail_file_id;
- PhotoSize thumbnail;
- if (input_thumbnail != nullptr) {
- auto r_thumbnail_file_id =
- td_->file_manager_->get_input_thumbnail_file_id(input_thumbnail->thumbnail_, dialog_id, is_secret);
- if (r_thumbnail_file_id.is_error()) {
- LOG(WARNING) << "Ignore thumbnail file: " << r_thumbnail_file_id.error().message();
- } else {
- thumbnail_file_id = r_thumbnail_file_id.ok();
- CHECK(thumbnail_file_id.is_valid());
+ MessageId message_id = get_persistent_message_id(from_dialog, MessageId(input_message->message_id_));
- thumbnail.type = 't';
- thumbnail.dimensions = get_dimensions(input_thumbnail->width_, input_thumbnail->height_);
- thumbnail.file_id = thumbnail_file_id;
-
- FileView thumbnail_file_view = td_->file_manager_->get_file_view(thumbnail_file_id);
- if (thumbnail_file_view.has_remote_location()) {
- // TODO td->file_manager_->delete_remote_location(thumbnail_file_id);
- }
+ const Message *copied_message = get_message_force(from_dialog, message_id, "process_input_message_content");
+ if (copied_message == nullptr) {
+ return Status::Error(400, "Can't find message to copy");
}
- }
-
- LOG(INFO) << "Send file " << file_id << " and thumbnail " << thumbnail_file_id;
-
- bool disable_web_page_preview = false;
- bool clear_draft = false;
- unique_ptr<MessageContent> content;
- UserId via_bot_user_id;
- int32 ttl = 0;
- bool is_bot = td_->auth_manager_->is_bot();
- switch (message_type) {
- case td_api::inputMessageText::ID: {
- TRY_RESULT(input_message_text, process_input_message_text(dialog_id, std::move(input_message_content), is_bot));
- disable_web_page_preview = input_message_text.disable_web_page_preview;
- clear_draft = input_message_text.clear_draft;
-
- WebPageId web_page_id;
- if (!disable_web_page_preview &&
- (dialog_id.get_type() != DialogType::Channel ||
- td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_add_web_page_previews())) {
- web_page_id = td_->web_pages_manager_->get_web_page_by_url(
- get_first_url(input_message_text.text.text, input_message_text.text.entities));
- }
- content = make_unique<MessageText>(std::move(input_message_text.text), web_page_id);
- break;
+ if (!can_forward_message(from_dialog_id, copied_message)) {
+ return Status::Error(400, "Can't copy message");
}
- case td_api::inputMessageAnimation::ID: {
- auto input_animation = static_cast<td_api::inputMessageAnimation *>(input_message_content.get());
-
- TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_animation->caption_), is_bot));
-
- td_->animations_manager_->create_animation(
- file_id, thumbnail, std::move(file_name), std::move(mime_type), input_animation->duration_,
- get_dimensions(input_animation->width_, input_animation->height_), false);
-
- content = make_unique<MessageAnimation>(file_id, std::move(caption));
- break;
- }
- case td_api::inputMessageAudio::ID: {
- auto input_audio = static_cast<td_api::inputMessageAudio *>(input_message_content.get());
-
- if (!clean_input_string(input_audio->title_)) {
- return Status::Error(400, "Audio title must be encoded in UTF-8");
- }
- if (!clean_input_string(input_audio->performer_)) {
- return Status::Error(400, "Audio performer must be encoded in UTF-8");
- }
- TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_audio->caption_), is_bot));
-
- td_->audios_manager_->create_audio(file_id, thumbnail, std::move(file_name), std::move(mime_type),
- input_audio->duration_, std::move(input_audio->title_),
- std::move(input_audio->performer_), false);
-
- content = make_unique<MessageAudio>(file_id, std::move(caption));
- break;
+ if (!can_save_message(from_dialog_id, copied_message) && !td_->auth_manager_->is_bot()) {
+ return Status::Error(400, "Message copying is restricted");
}
- case td_api::inputMessageDocument::ID: {
- auto input_document = static_cast<td_api::inputMessageDocument *>(input_message_content.get());
-
- TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_document->caption_), is_bot));
-
- td_->documents_manager_->create_document(file_id, thumbnail, std::move(file_name), std::move(mime_type), false);
- content = make_unique<MessageDocument>(file_id, std::move(caption));
- break;
+ unique_ptr<MessageContent> content = dup_message_content(td_, dialog_id, copied_message->content.get(),
+ MessageContentDupType::Copy, std::move(copy_options));
+ if (content == nullptr) {
+ return Status::Error(400, "Can't copy message content");
}
- case td_api::inputMessagePhoto::ID: {
- auto input_photo = static_cast<td_api::inputMessagePhoto *>(input_message_content.get());
- TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_photo->caption_), is_bot));
- if (input_photo->width_ < 0 || input_photo->width_ > 10000) {
- return Status::Error(400, "Wrong photo width");
- }
- if (input_photo->height_ < 0 || input_photo->height_ > 10000) {
- return Status::Error(400, "Wrong photo height");
- }
- ttl = input_photo->ttl_;
-
- auto message_photo = make_unique<MessagePhoto>();
-
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
- message_photo->photo.id = file_view.remote_location().get_id();
- }
- message_photo->photo.date = G()->unix_time();
+ return InputMessageContent(std::move(content), get_message_disable_web_page_preview(copied_message), false, 0,
+ UserId(), copied_message->send_emoji);
+ }
- PhotoSize s;
- s.type = 'i';
- s.dimensions = get_dimensions(input_photo->width_, input_photo->height_);
- s.size = static_cast<int32>(file_view.size());
- s.file_id = file_id;
+ bool is_premium = td_->option_manager_->get_option_boolean("is_premium");
+ TRY_RESULT(content, get_input_message_content(dialog_id, std::move(input_message_content), td_, is_premium));
- if (thumbnail_file_id.is_valid()) {
- message_photo->photo.photos.push_back(thumbnail);
- }
+ if (content.ttl < 0 || content.ttl > MAX_PRIVATE_MESSAGE_TTL) {
+ return Status::Error(400, "Invalid message content TTL specified");
+ }
+ if (content.ttl > 0 && dialog_id.get_type() != DialogType::User) {
+ return Status::Error(400, "Message content TTL can be specified only in private chats");
+ }
- message_photo->photo.photos.push_back(s);
+ if (dialog_id != DialogId()) {
+ TRY_STATUS(can_send_message_content(dialog_id, content.content.get(), false, td_));
+ }
- message_photo->photo.has_stickers = !sticker_file_ids.empty();
- message_photo->photo.sticker_file_ids = std::move(sticker_file_ids);
+ return std::move(content);
+}
- message_photo->caption = std::move(caption);
+Result<MessageCopyOptions> MessagesManager::process_message_copy_options(
+ DialogId dialog_id, tl_object_ptr<td_api::messageCopyOptions> &&options) const {
+ if (options == nullptr || !options->send_copy_) {
+ return MessageCopyOptions();
+ }
+ MessageCopyOptions result;
+ result.send_copy = true;
+ result.replace_caption = options->replace_caption_;
+ if (result.replace_caption) {
+ TRY_RESULT_ASSIGN(result.new_caption, get_formatted_text(td_, dialog_id, std::move(options->new_caption_),
+ td_->auth_manager_->is_bot(), true, false, false));
+ }
+ return std::move(result);
+}
- content = std::move(message_photo);
- break;
+Result<MessagesManager::MessageSendOptions> MessagesManager::process_message_send_options(
+ DialogId dialog_id, tl_object_ptr<td_api::messageSendOptions> &&options,
+ bool allow_update_stickersets_order) const {
+ MessageSendOptions result;
+ if (options != nullptr) {
+ result.disable_notification = options->disable_notification_;
+ result.from_background = options->from_background_;
+ if (allow_update_stickersets_order) {
+ result.update_stickersets_order = options->update_order_of_installed_sticker_sets_;
}
- case td_api::inputMessageSticker::ID: {
- auto input_sticker = static_cast<td_api::inputMessageSticker *>(input_message_content.get());
- td_->stickers_manager_->create_sticker(
- file_id, thumbnail, get_dimensions(input_sticker->width_, input_sticker->height_), true, nullptr, nullptr);
+ result.protect_content = options->protect_content_;
+ TRY_RESULT_ASSIGN(result.schedule_date, get_message_schedule_date(std::move(options->scheduling_state_)));
+ }
- content = make_unique<MessageSticker>(file_id);
- break;
+ auto dialog_type = dialog_id.get_type();
+ if (result.schedule_date != 0) {
+ if (dialog_type == DialogType::SecretChat) {
+ return Status::Error(400, "Can't schedule messages in secret chats");
}
- case td_api::inputMessageVideo::ID: {
- auto input_video = static_cast<td_api::inputMessageVideo *>(input_message_content.get());
-
- TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_video->caption_), is_bot));
- ttl = input_video->ttl_;
-
- bool has_stickers = !sticker_file_ids.empty();
- td_->videos_manager_->create_video(file_id, thumbnail, has_stickers, std::move(sticker_file_ids),
- std::move(file_name), std::move(mime_type), input_video->duration_,
- get_dimensions(input_video->width_, input_video->height_),
- input_video->supports_streaming_, false);
-
- content = make_unique<MessageVideo>(file_id, std::move(caption));
- break;
+ if (td_->auth_manager_->is_bot()) {
+ return Status::Error(400, "Bots can't send scheduled messages");
}
- case td_api::inputMessageVideoNote::ID: {
- auto input_video_note = static_cast<td_api::inputMessageVideoNote *>(input_message_content.get());
-
- auto length = input_video_note->length_;
- if (length < 0 || length >= 640) {
- return Status::Error(400, "Wrong video note length");
- }
-
- td_->video_notes_manager_->create_video_note(file_id, thumbnail, input_video_note->duration_,
- get_dimensions(length, length), false);
-
- content = make_unique<MessageVideoNote>(file_id, false);
- break;
+ }
+ if (result.schedule_date == SCHEDULE_WHEN_ONLINE_DATE) {
+ if (dialog_type != DialogType::User) {
+ return Status::Error(400, "Messages can be scheduled till online only in private chats");
+ }
+ if (dialog_id == get_my_dialog_id()) {
+ return Status::Error(400, "Can't scheduled till online messages in chat with self");
}
- case td_api::inputMessageVoiceNote::ID: {
- auto input_voice_note = static_cast<td_api::inputMessageVoiceNote *>(input_message_content.get());
+ }
- TRY_RESULT(caption, process_input_caption(dialog_id, std::move(input_voice_note->caption_), is_bot));
+ if (result.protect_content && !td_->auth_manager_->is_bot()) {
+ result.protect_content = false;
+ }
- td_->voice_notes_manager_->create_voice_note(file_id, std::move(mime_type), input_voice_note->duration_,
- std::move(input_voice_note->waveform_), false);
+ return result;
+}
- content = make_unique<MessageVoiceNote>(file_id, std::move(caption), false);
- break;
- }
- case td_api::inputMessageLocation::ID: {
- TRY_RESULT(location, process_input_message_location(std::move(input_message_content)));
- if (location.second == 0) {
- content = make_unique<MessageLocation>(std::move(location.first));
- } else {
- content = make_unique<MessageLiveLocation>(std::move(location.first), location.second);
- }
- break;
- }
- case td_api::inputMessageVenue::ID: {
- TRY_RESULT(venue, process_input_message_venue(std::move(input_message_content)));
- content = make_unique<MessageVenue>(std::move(venue));
- break;
- }
- case td_api::inputMessageContact::ID: {
- TRY_RESULT(contact, process_input_message_contact(std::move(input_message_content)));
- content = make_unique<MessageContact>(std::move(contact));
- break;
+Status MessagesManager::can_use_message_send_options(const MessageSendOptions &options,
+ const unique_ptr<MessageContent> &content, int32 ttl) {
+ if (options.schedule_date != 0) {
+ if (ttl > 0) {
+ return Status::Error(400, "Can't send scheduled self-destructing messages");
}
- case td_api::inputMessageGame::ID: {
- TRY_RESULT(game, process_input_message_game(std::move(input_message_content)));
- via_bot_user_id = game.get_bot_user_id();
- if (via_bot_user_id == td_->contacts_manager_->get_my_id("send_message")) {
- via_bot_user_id = UserId();
- }
-
- content = make_unique<MessageGame>(std::move(game));
- break;
+ if (content->get_type() == MessageContentType::LiveLocation) {
+ return Status::Error(400, "Can't send scheduled live location messages");
}
- case td_api::inputMessageInvoice::ID: {
- if (!is_bot) {
- return Status::Error(400, "Invoices can be sent only by bots");
- }
-
- auto input_message_invoice = move_tl_object_as<td_api::inputMessageInvoice>(input_message_content);
- if (!clean_input_string(input_message_invoice->title_)) {
- return Status::Error(400, "Invoice title must be encoded in UTF-8");
- }
- if (!clean_input_string(input_message_invoice->description_)) {
- return Status::Error(400, "Invoice description must be encoded in UTF-8");
- }
- if (!clean_input_string(input_message_invoice->photo_url_)) {
- return Status::Error(400, "Invoice photo URL must be encoded in UTF-8");
- }
- if (!clean_input_string(input_message_invoice->start_parameter_)) {
- return Status::Error(400, "Invoice bot start parameter must be encoded in UTF-8");
- }
- if (!clean_input_string(input_message_invoice->provider_token_)) {
- return Status::Error(400, "Invoice provider token must be encoded in UTF-8");
- }
- if (!clean_input_string(input_message_invoice->provider_data_)) {
- return Status::Error(400, "Invoice provider data must be encoded in UTF-8");
- }
- if (!clean_input_string(input_message_invoice->invoice_->currency_)) {
- return Status::Error(400, "Invoice currency must be encoded in UTF-8");
- }
-
- auto message_invoice = make_unique<MessageInvoice>();
- message_invoice->title = std::move(input_message_invoice->title_);
- message_invoice->description = std::move(input_message_invoice->description_);
+ }
- auto r_http_url = parse_url(input_message_invoice->photo_url_);
- if (r_http_url.is_error()) {
- message_invoice->photo.id = -2;
- } else {
- auto url = r_http_url.ok().get_url();
- auto r_invoice_file_id = td_->file_manager_->from_persistent_id(url, FileType::Temp);
- if (r_invoice_file_id.is_error()) {
- LOG(ERROR) << "Can't register url " << url;
- message_invoice->photo.id = -2;
- } else {
- auto invoice_file_id = r_invoice_file_id.move_as_ok();
+ return Status::OK();
+}
- PhotoSize s;
- s.type = 'u';
- s.dimensions = get_dimensions(input_message_invoice->photo_width_, input_message_invoice->photo_height_);
- s.size = input_message_invoice->photo_size_; // TODO use invoice_file_id size
- s.file_id = invoice_file_id;
+Status MessagesManager::can_use_message_send_options(const MessageSendOptions &options,
+ const InputMessageContent &content) {
+ return can_use_message_send_options(options, content.content, content.ttl);
+}
- message_invoice->photo.photos.push_back(s);
- }
- }
- message_invoice->start_parameter = std::move(input_message_invoice->start_parameter_);
+Status MessagesManager::can_use_top_thread_message_id(Dialog *d, MessageId top_thread_message_id,
+ MessageId reply_to_message_id) {
+ if (top_thread_message_id == MessageId()) {
+ return Status::OK();
+ }
- message_invoice->invoice.currency = std::move(input_message_invoice->invoice_->currency_);
- message_invoice->invoice.price_parts.reserve(input_message_invoice->invoice_->price_parts_.size());
- int64 total_amount = 0;
- const int64 MAX_AMOUNT = 9999'9999'9999;
- for (auto &price : input_message_invoice->invoice_->price_parts_) {
- if (!clean_input_string(price->label_)) {
- return Status::Error(400, "Invoice price label must be encoded in UTF-8");
- }
- message_invoice->invoice.price_parts.emplace_back(std::move(price->label_), price->amount_);
- if (price->amount_ < -MAX_AMOUNT || price->amount_ > MAX_AMOUNT) {
- return Status::Error(400, "Too big amount of currency specified");
- }
- total_amount += price->amount_;
- }
- if (total_amount <= 0) {
- return Status::Error(400, "Total price must be positive");
- }
- if (total_amount > MAX_AMOUNT) {
- return Status::Error(400, "Total price is too big");
+ if (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server()) {
+ return Status::Error(400, "Invalid message thread identifier specified");
+ }
+ if (d->dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(d->dialog_id)) {
+ return Status::Error(400, "Chat doesn't have threads");
+ }
+ if (reply_to_message_id.is_valid()) {
+ const Message *reply_m = get_message_force(d, reply_to_message_id, "can_use_top_thread_message_id 1");
+ if (reply_m != nullptr && top_thread_message_id != reply_m->top_thread_message_id) {
+ if (reply_m->top_thread_message_id.is_valid() || reply_m->media_album_id == 0) {
+ return Status::Error(400, "The message to reply is not in the specified message thread");
}
- message_invoice->total_amount = total_amount;
- message_invoice->invoice.is_test = input_message_invoice->invoice_->is_test_;
- message_invoice->invoice.need_name = input_message_invoice->invoice_->need_name_;
- message_invoice->invoice.need_phone_number = input_message_invoice->invoice_->need_phone_number_;
- message_invoice->invoice.need_email_address = input_message_invoice->invoice_->need_email_address_;
- message_invoice->invoice.need_shipping_address = input_message_invoice->invoice_->need_shipping_address_;
- message_invoice->invoice.send_phone_number_to_provider =
- input_message_invoice->invoice_->send_phone_number_to_provider_;
- message_invoice->invoice.send_email_address_to_provider =
- input_message_invoice->invoice_->send_email_address_to_provider_;
- message_invoice->invoice.is_flexible = input_message_invoice->invoice_->is_flexible_;
- if (message_invoice->invoice.send_phone_number_to_provider) {
- message_invoice->invoice.need_phone_number = true;
+ // if the message is in an album and not in the thread, it can be in the album of top_thread_message_id
+ const Message *top_m = get_message_force(d, top_thread_message_id, "can_use_top_thread_message_id 2");
+ if (top_m != nullptr &&
+ (top_m->media_album_id != reply_m->media_album_id || top_m->top_thread_message_id != top_m->message_id)) {
+ return Status::Error(400, "The message to reply is not in the specified message thread root album");
}
- if (message_invoice->invoice.send_email_address_to_provider) {
- message_invoice->invoice.need_email_address = true;
- }
- if (message_invoice->invoice.is_flexible) {
- message_invoice->invoice.need_shipping_address = true;
- }
-
- message_invoice->payload = std::move(input_message_invoice->payload_);
- message_invoice->provider_token = std::move(input_message_invoice->provider_token_);
- message_invoice->provider_data = std::move(input_message_invoice->provider_data_);
-
- content = std::move(message_invoice);
- break;
}
- default:
- UNREACHABLE();
- }
- if (ttl < 0 || ttl > MAX_PRIVATE_MESSAGE_TTL) {
- return Status::Error(10, "Wrong message TTL specified");
- }
- if (ttl > 0 && dialog_id.get_type() != DialogType::User) {
- return Status::Error(10, "Message TTL can be specified only in private chats");
}
- TRY_STATUS(can_send_message_content(dialog_id, content.get(), false));
+ return Status::OK();
+}
- return InputMessageContent{std::move(content), disable_web_page_preview, clear_draft, ttl, via_bot_user_id};
+int64 MessagesManager::generate_new_media_album_id() {
+ int64 media_album_id = 0;
+ do {
+ media_album_id = Random::secure_int64();
+ } while (media_album_id >= 0 || pending_message_group_sends_.count(media_album_id) != 0);
+ return media_album_id;
}
-Result<vector<MessageId>> MessagesManager::send_message_group(
- DialogId dialog_id, MessageId reply_to_message_id, bool disable_notification, bool from_background,
- vector<tl_object_ptr<td_api::InputMessageContent>> &&input_message_contents) {
+Result<td_api::object_ptr<td_api::messages>> MessagesManager::send_message_group(
+ DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id,
+ tl_object_ptr<td_api::messageSendOptions> &&options,
+ vector<tl_object_ptr<td_api::InputMessageContent>> &&input_message_contents, bool only_preview) {
if (input_message_contents.size() > MAX_GROUPED_MESSAGES) {
- return Status::Error(4, "Too much messages to send as an album");
+ return Status::Error(400, "Too many messages to send as an album");
}
if (input_message_contents.empty()) {
- return Status::Error(4, "There is no messages to send");
+ return Status::Error(400, "There are no messages to send");
}
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "send_message_group");
if (d == nullptr) {
- return Status::Error(5, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
+ TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), true));
vector<std::pair<unique_ptr<MessageContent>, int32>> message_contents;
+ std::unordered_set<MessageContentType, MessageContentTypeHash> message_content_types;
for (auto &input_message_content : input_message_contents) {
TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content)));
- if (!is_allowed_media_group_content(message_content.content->get_id())) {
- return Status::Error(5, "Wrong message content type");
+ TRY_STATUS(can_use_message_send_options(message_send_options, message_content));
+ auto message_content_type = message_content.content->get_type();
+ if (!is_allowed_media_group_content(message_content_type)) {
+ return Status::Error(400, "Invalid message content type");
}
+ message_content_types.insert(message_content_type);
message_contents.emplace_back(std::move(message_content.content), message_content.ttl);
}
+ if (message_content_types.size() > 1) {
+ for (auto message_content_type : message_content_types) {
+ if (is_homogenous_media_group_content(message_content_type)) {
+ return Status::Error(400, PSLICE() << message_content_type << " can't be mixed with other media types");
+ }
+ }
+ }
- reply_to_message_id = get_reply_to_message_id(d, reply_to_message_id);
+ reply_to_message_id = get_reply_to_message_id(d, top_thread_message_id, reply_to_message_id, false);
+ TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, reply_to_message_id));
int64 media_album_id = 0;
if (message_contents.size() > 1) {
- do {
- media_album_id = Random::secure_int64();
- } while (media_album_id >= 0 || pending_message_group_sends_.count(media_album_id) != 0);
+ media_album_id = generate_new_media_album_id();
}
// there must be no errors after get_message_to_send calls
- vector<MessageId> result;
+ vector<tl_object_ptr<td_api::message>> result;
bool need_update_dialog_pos = false;
- for (auto &message_content : message_contents) {
- Message *m = get_message_to_send(d, reply_to_message_id, disable_notification, from_background,
- dup_message_content(dialog_id, message_content.first.get(), false),
- &need_update_dialog_pos);
- result.push_back(m->message_id);
+ for (size_t i = 0; i < message_contents.size(); i++) {
+ auto &message_content = message_contents[i];
+ unique_ptr<Message> message;
+ Message *m;
+ if (only_preview) {
+ message = create_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options,
+ std::move(message_content.first), i != 0, nullptr, false, DialogId());
+ MessageId new_message_id = message_send_options.schedule_date != 0
+ ? get_next_yet_unsent_scheduled_message_id(d, message_send_options.schedule_date)
+ : get_next_yet_unsent_message_id(d);
+ set_message_id(message, new_message_id);
+ m = message.get();
+ } else {
+ m = get_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options,
+ dup_message_content(td_, dialog_id, message_content.first.get(),
+ MessageContentDupType::Send, MessageCopyOptions()),
+ &need_update_dialog_pos, i != 0);
+ }
+
auto ttl = message_content.second;
if (ttl > 0) {
m->ttl = ttl;
- m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_id());
+ m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type());
}
m->media_album_id = media_album_id;
- save_send_message_logevent(dialog_id, m);
- do_send_message(dialog_id, m);
+ result.push_back(get_message_object(dialog_id, m, "send_message_group"));
- send_update_new_message(d, m, true);
+ if (!only_preview) {
+ save_send_message_log_event(dialog_id, m);
+ do_send_message(dialog_id, m);
+
+ send_update_new_message(d, m);
+ }
}
if (need_update_dialog_pos) {
+ CHECK(!only_preview);
send_update_chat_last_message(d, "send_message_group");
}
- return result;
+ return get_messages_object(-1, std::move(result), false);
}
-void MessagesManager::save_send_message_logevent(DialogId dialog_id, Message *m) {
+void MessagesManager::save_send_message_log_event(DialogId dialog_id, const Message *m) {
if (!G()->parameters().use_message_db) {
return;
}
CHECK(m != nullptr);
LOG(INFO) << "Save " << FullMessageId(dialog_id, m->message_id) << " to binlog";
- auto logevent = SendMessageLogEvent(dialog_id, m);
- auto storer = LogEventStorerImpl<SendMessageLogEvent>(logevent);
- CHECK(m->send_message_logevent_id == 0);
- m->send_message_logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendMessage, storer);
+ auto log_event = SendMessageLogEvent(dialog_id, m);
+ CHECK(m->send_message_log_event_id == 0);
+ m->send_message_log_event_id =
+ binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendMessage, get_log_event_storer(log_event));
}
-void MessagesManager::do_send_message(DialogId dialog_id, Message *m, vector<int> bad_parts) {
- LOG(INFO) << "Do send " << FullMessageId(dialog_id, m->message_id);
+void MessagesManager::do_send_message(DialogId dialog_id, const Message *m, vector<int> bad_parts) {
+ bool is_edit = m->message_id.is_any_server();
+ LOG(INFO) << "Do " << (is_edit ? "edit" : "send") << ' ' << FullMessageId(dialog_id, m->message_id);
bool is_secret = dialog_id.get_type() == DialogType::SecretChat;
- if (m->media_album_id != 0 && bad_parts.empty() && !is_secret) {
+ if (m->media_album_id != 0 && bad_parts.empty() && !is_secret && !is_edit) {
auto &request = pending_message_group_sends_[m->media_album_id];
request.dialog_id = dialog_id;
request.message_ids.push_back(m->message_id);
@@ -16085,71 +26564,99 @@ void MessagesManager::do_send_message(DialogId dialog_id, Message *m, vector<int
request.results.push_back(Status::OK());
}
- auto content = m->content.get();
- auto content_type = content->get_id();
- if (content_type == MessageText::ID) {
- auto message_text = static_cast<const MessageText *>(content);
-
- int64 random_id = begin_send_message(dialog_id, m);
- if (is_secret) {
- auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
- send_closure(td_->create_net_actor<SendSecretMessageActor>(), &SendSecretMessageActor::send, dialog_id,
- m->reply_to_random_id, m->ttl, message_text->text.text,
- get_secret_input_media(content, nullptr, BufferSlice(), layer),
- get_input_secret_message_entities(message_text->text.entities), m->via_bot_user_id,
- m->media_album_id, random_id);
- } else {
- send_closure(td_->create_net_actor<SendMessageActor>(), &SendMessageActor::send, get_message_flags(m), dialog_id,
- m->reply_to_message_id, get_input_reply_markup(m->reply_markup),
- get_input_message_entities(td_->contacts_manager_.get(), message_text->text.entities),
- message_text->text.text, random_id, &m->send_query_ref,
- get_sequence_dispatcher_id(dialog_id, content_type));
- }
+ auto content = is_edit ? m->edited_content.get() : m->content.get();
+ CHECK(content != nullptr);
+ auto content_type = content->get_type();
+ if (content_type == MessageContentType::Text) {
+ CHECK(!is_edit);
+ send_closure_later(actor_id(this), &MessagesManager::on_text_message_ready_to_send, dialog_id, m->message_id);
return;
}
- FileId file_id = get_message_content_file_id(content);
+ FileId file_id = get_message_content_any_file_id(content); // any_file_id, because it could be a photo sent by ID
FileView file_view = td_->file_manager_->get_file_view(file_id);
- FileId thumbnail_file_id = get_message_content_thumbnail_file_id(content);
+ FileId thumbnail_file_id = get_message_content_thumbnail_file_id(content, td_);
+ LOG(DEBUG) << "Need to send file " << file_id << " with thumbnail " << thumbnail_file_id;
if (is_secret) {
+ CHECK(!is_edit);
auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
- auto secret_input_media = get_secret_input_media(content, nullptr, BufferSlice(), layer);
+ auto secret_input_media = get_secret_input_media(content, td_, nullptr, BufferSlice(), layer);
if (secret_input_media.empty()) {
- CHECK(file_view.is_encrypted());
- CHECK(file_id.is_valid());
- being_uploaded_files_[file_id] = {FullMessageId(dialog_id, m->message_id), thumbnail_file_id};
LOG(INFO) << "Ask to upload encrypted file " << file_id;
+ CHECK(file_view.is_encrypted_secret());
+ CHECK(file_id.is_valid());
+ bool is_inserted =
+ being_uploaded_files_
+ .emplace(file_id, std::make_pair(FullMessageId(dialog_id, m->message_id), thumbnail_file_id))
+ .second;
+ CHECK(is_inserted);
// need to call resume_upload synchronously to make upload process consistent with being_uploaded_files_
td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, m->message_id.get());
} else {
- on_secret_message_media_uploaded(dialog_id, m, std::move(secret_input_media), FileId(), FileId());
+ on_secret_message_media_uploaded(dialog_id, m, std::move(secret_input_media), file_id, thumbnail_file_id);
}
} else {
- auto input_media = get_input_media(content, nullptr, nullptr, m->ttl);
+ auto input_media =
+ get_input_media(content, td_, m->ttl, m->send_emoji, td_->auth_manager_->is_bot() && bad_parts.empty());
if (input_media == nullptr) {
- if (content_type == MessagePhoto::ID) {
+ if (content_type == MessageContentType::Game || content_type == MessageContentType::Poll) {
+ return;
+ }
+ if (file_view.get_type() == FileType::Photo) {
thumbnail_file_id = FileId();
}
+ LOG(INFO) << "Ask to upload file " << file_id << " with bad parts " << bad_parts;
CHECK(file_id.is_valid());
- being_uploaded_files_[file_id] = {FullMessageId(dialog_id, m->message_id), thumbnail_file_id};
- LOG(INFO) << "Ask to upload file " << file_id;
+ bool is_inserted =
+ being_uploaded_files_
+ .emplace(file_id, std::make_pair(FullMessageId(dialog_id, m->message_id), thumbnail_file_id))
+ .second;
+ CHECK(is_inserted);
// need to call resume_upload synchronously to make upload process consistent with being_uploaded_files_
td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_media_callback_, 1, m->message_id.get());
} else {
- on_message_media_uploaded(dialog_id, m, std::move(input_media), FileId(), FileId());
+ on_message_media_uploaded(dialog_id, m, std::move(input_media), file_id, thumbnail_file_id);
}
}
}
-void MessagesManager::on_message_media_uploaded(DialogId dialog_id, Message *m,
+void MessagesManager::on_message_media_uploaded(DialogId dialog_id, const Message *m,
tl_object_ptr<telegram_api::InputMedia> &&input_media, FileId file_id,
FileId thumbnail_file_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
CHECK(m != nullptr);
CHECK(input_media != nullptr);
+ auto message_id = m->message_id;
+ if (message_id.is_any_server()) {
+ const FormattedText *caption = get_message_content_caption(m->edited_content.get());
+ auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), m->edited_reply_markup);
+ bool was_uploaded = FileManager::extract_was_uploaded(input_media);
+ bool was_thumbnail_uploaded = FileManager::extract_was_thumbnail_uploaded(input_media);
+
+ LOG(INFO) << "Edit media from " << message_id << " in " << dialog_id;
+ auto schedule_date = get_message_schedule_date(m);
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_id, message_id, file_id, thumbnail_file_id, schedule_date,
+ generation = m->edit_generation, was_uploaded, was_thumbnail_uploaded,
+ file_reference = FileManager::extract_file_reference(input_media)](Result<int32> result) mutable {
+ send_closure(actor_id, &MessagesManager::on_message_media_edited, dialog_id, message_id, file_id,
+ thumbnail_file_id, was_uploaded, was_thumbnail_uploaded, std::move(file_reference),
+ schedule_date, generation, std::move(result));
+ });
+ td_->create_handler<EditMessageQuery>(std::move(promise))
+ ->send(1 << 11, dialog_id, message_id, caption == nullptr ? "" : caption->text,
+ get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_message_media"),
+ std::move(input_media), std::move(input_reply_markup), schedule_date);
+ return;
+ }
+
if (m->media_album_id == 0) {
- on_media_message_ready_to_send(
- dialog_id, m->message_id,
+ send_closure_later(
+ actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id, message_id,
PromiseCreator::lambda([this, dialog_id, input_media = std::move(input_media), file_id,
thumbnail_file_id](Result<Message *> result) mutable {
if (result.is_error() || G()->close_flag()) {
@@ -16160,16 +26667,17 @@ void MessagesManager::on_message_media_uploaded(DialogId dialog_id, Message *m,
CHECK(m != nullptr);
CHECK(input_media != nullptr);
- auto caption = get_message_content_caption(m->content.get());
-
+ const FormattedText *caption = get_message_content_caption(m->content.get());
LOG(INFO) << "Send media from " << m->message_id << " in " << dialog_id << " in reply to "
<< m->reply_to_message_id;
int64 random_id = begin_send_message(dialog_id, m);
- send_closure(td_->create_net_actor<SendMediaActor>(), &SendMediaActor::send, file_id, thumbnail_file_id,
- get_message_flags(m), dialog_id, m->reply_to_message_id, get_input_reply_markup(m->reply_markup),
- get_input_message_entities(td_->contacts_manager_.get(), caption.entities), caption.text,
- std::move(input_media), random_id, &m->send_query_ref,
- get_sequence_dispatcher_id(dialog_id, m->content->get_id()));
+ td_->create_handler<SendMediaQuery>()->send(
+ file_id, thumbnail_file_id, get_message_flags(m), dialog_id, get_send_message_as_input_peer(m),
+ m->reply_to_message_id, m->top_thread_message_id, get_message_schedule_date(m),
+ get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup),
+ get_input_message_entities(td_->contacts_manager_.get(), caption, "on_message_media_uploaded"),
+ caption == nullptr ? "" : caption->text, std::move(input_media), m->content->get_type(), m->is_copy,
+ random_id, &m->send_query_ref);
}));
} else {
switch (input_media->get_id()) {
@@ -16180,28 +26688,40 @@ void MessagesManager::on_message_media_uploaded(DialogId dialog_id, Message *m,
case telegram_api::inputMediaUploadedPhoto::ID:
case telegram_api::inputMediaDocumentExternal::ID:
case telegram_api::inputMediaPhotoExternal::ID:
- LOG(INFO) << "Upload media from " << m->message_id << " in " << dialog_id;
- td_->create_handler<UploadMediaQuery>()->send(dialog_id, m->message_id, file_id, thumbnail_file_id,
+ LOG(INFO) << "Upload media from " << message_id << " in " << dialog_id;
+ td_->create_handler<UploadMediaQuery>()->send(dialog_id, message_id, file_id, thumbnail_file_id,
std::move(input_media));
break;
case telegram_api::inputMediaDocument::ID:
case telegram_api::inputMediaPhoto::ID:
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id,
- dialog_id, m->message_id, Status::OK());
+ dialog_id, message_id, Status::OK());
break;
default:
LOG(ERROR) << "Have wrong input media " << to_string(input_media);
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id,
- dialog_id, m->message_id, Status::Error(400, "Wrong input media"));
+ dialog_id, message_id, Status::Error(400, "Invalid input media"));
}
}
}
-void MessagesManager::on_secret_message_media_uploaded(DialogId dialog_id, Message *m,
+void MessagesManager::on_secret_message_media_uploaded(DialogId dialog_id, const Message *m,
SecretInputMedia &&secret_input_media, FileId file_id,
FileId thumbnail_file_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
CHECK(m != nullptr);
- CHECK(!secret_input_media.empty());
+ CHECK(m->message_id.is_valid());
+ if (secret_input_media.empty()) {
+ // the media can't be sent to the chat
+ LOG(INFO) << "Can't send a media message to " << dialog_id;
+
+ fail_send_message({dialog_id, m->message_id}, Status::Error(400, "The file can't be sent to the secret chat"));
+ return;
+ }
+
/*
if (m->media_album_id != 0) {
switch (secret_input_media->input_file_->get_id()) {
@@ -16215,30 +26735,65 @@ void MessagesManager::on_secret_message_media_uploaded(DialogId dialog_id, Messa
default:
LOG(ERROR) << "Have wrong secret input media " << to_string(secret_input_media->input_file_);
return send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id,
- dialog_id, m->message_id, Status::Error(400, "Wrong input media"));
+ dialog_id, m->message_id, Status::Error(400, "Invalid input media"));
}
-*/
- // TODO use file_id, thumbnail_file_id, invalidate partial remote location for file_id in case of failed upload
- // even message has already been deleted
- on_media_message_ready_to_send(
- dialog_id, m->message_id,
- PromiseCreator::lambda(
- [this, dialog_id, secret_input_media = std::move(secret_input_media)](Result<Message *> result) mutable {
- if (result.is_error() || G()->close_flag()) {
- return;
- }
+ */
+ // TODO use file_id, thumbnail_file_id, was_uploaded, was_thumbnail_uploaded,
+ // invalidate partial remote location for file_id in case of failed upload even message has already been deleted
+ send_closure_later(actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id, m->message_id,
+ PromiseCreator::lambda([this, dialog_id, secret_input_media = std::move(secret_input_media)](
+ Result<Message *> result) mutable {
+ if (result.is_error() || G()->close_flag()) {
+ return;
+ }
+
+ auto m = result.move_as_ok();
+ CHECK(m != nullptr);
+ CHECK(!secret_input_media.empty());
+ send_secret_message(dialog_id, m, std::move(secret_input_media));
+ }));
+}
+
+void MessagesManager::send_secret_message(DialogId dialog_id, const Message *m, SecretInputMedia media) {
+ CHECK(dialog_id.get_type() == DialogType::SecretChat);
+ int64 random_id = begin_send_message(dialog_id, m);
- auto m = result.move_as_ok();
- CHECK(m != nullptr);
- CHECK(!secret_input_media.empty());
- LOG(INFO) << "Send secret media from " << m->message_id << " in " << dialog_id << " in reply to "
- << m->reply_to_message_id;
- int64 random_id = begin_send_message(dialog_id, m);
- send_closure(td_->create_net_actor<SendSecretMessageActor>(), &SendSecretMessageActor::send, dialog_id,
- m->reply_to_random_id, m->ttl, "", std::move(secret_input_media),
- vector<tl_object_ptr<secret_api::MessageEntity>>(), m->via_bot_user_id, m->media_album_id,
- random_id);
- }));
+ auto text = get_message_content_text(m->content.get());
+ vector<tl_object_ptr<secret_api::MessageEntity>> entities;
+ if (text != nullptr && !text->entities.empty()) {
+ auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
+ entities = get_input_secret_message_entities(text->entities, layer);
+ }
+
+ int32 flags = 0;
+ if (m->reply_to_random_id != 0) {
+ flags |= secret_api::decryptedMessage::REPLY_TO_RANDOM_ID_MASK;
+ }
+ if (m->via_bot_user_id.is_valid()) {
+ flags |= secret_api::decryptedMessage::VIA_BOT_NAME_MASK;
+ }
+ if (!media.empty()) {
+ flags |= secret_api::decryptedMessage::MEDIA_MASK;
+ }
+ if (!entities.empty()) {
+ flags |= secret_api::decryptedMessage::ENTITIES_MASK;
+ }
+ if (m->media_album_id != 0) {
+ CHECK(m->media_album_id < 0);
+ flags |= secret_api::decryptedMessage::GROUPED_ID_MASK;
+ }
+ if (m->disable_notification) {
+ flags |= secret_api::decryptedMessage::SILENT_MASK;
+ }
+
+ send_closure(
+ td_->secret_chats_manager_, &SecretChatsManager::send_message, dialog_id.get_secret_chat_id(),
+ make_tl_object<secret_api::decryptedMessage>(
+ flags, false /*ignored*/, random_id, m->ttl,
+ m->content->get_type() == MessageContentType::Text ? text->text : string(), std::move(media.decrypted_media_),
+ std::move(entities), td_->contacts_manager_->get_user_first_username(m->via_bot_user_id),
+ m->reply_to_random_id, -m->media_album_id),
+ std::move(media.input_file_), Promise<Unit>());
}
void MessagesManager::on_upload_message_media_success(DialogId dialog_id, MessageId message_id,
@@ -16246,12 +26801,14 @@ void MessagesManager::on_upload_message_media_success(DialogId dialog_id, Messag
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
+ CHECK(message_id.is_valid() || message_id.is_valid_scheduled());
+ CHECK(message_id.is_yet_unsent());
Message *m = get_message(d, message_id);
if (m == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
- LOG(INFO) << "Fail to send already deleted by the user or sent to inaccessible chat "
+ LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat "
<< FullMessageId{dialog_id, message_id};
return;
}
@@ -16260,19 +26817,27 @@ void MessagesManager::on_upload_message_media_success(DialogId dialog_id, Messag
return; // the message should be deleted soon
}
- auto content = get_message_content(get_message_content_caption(m->content.get()), std::move(media), dialog_id, false,
- UserId(), nullptr);
+ auto caption = get_message_content_caption(m->content.get());
+ auto content = get_message_content(td_, caption == nullptr ? FormattedText() : *caption, std::move(media), dialog_id,
+ false, UserId(), nullptr, nullptr, "on_upload_message_media_success");
- update_message_content(dialog_id, m, m->content, std::move(content), true);
+ bool is_content_changed = false;
+ bool need_update = update_message_content(dialog_id, m, std::move(content), true, true, is_content_changed);
+ if (need_update) {
+ send_update_message_content(d, m, true, "on_upload_message_media_success");
+ }
+ if (is_content_changed || need_update) {
+ on_message_changed(d, m, need_update, "on_upload_message_media_success");
+ }
- auto input_media = get_input_media(m->content.get(), nullptr, nullptr, m->ttl);
+ auto input_media = get_input_media(m->content.get(), td_, m->ttl, m->send_emoji, true);
Status result;
if (input_media == nullptr) {
result = Status::Error(400, "Failed to upload file");
}
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id,
- message_id, std::move(result));
+ m->message_id, std::move(result));
}
void MessagesManager::on_upload_message_media_file_part_missing(DialogId dialog_id, MessageId message_id,
@@ -16285,7 +26850,7 @@ void MessagesManager::on_upload_message_media_file_part_missing(DialogId dialog_
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
- LOG(INFO) << "Fail to send already deleted by the user or sent to inaccessible chat "
+ LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat "
<< FullMessageId{dialog_id, message_id};
return;
}
@@ -16310,7 +26875,7 @@ void MessagesManager::on_upload_message_media_fail(DialogId dialog_id, MessageId
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
- LOG(INFO) << "Fail to send already deleted by the user or sent to inaccessible chat "
+ LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat "
<< FullMessageId{dialog_id, message_id};
return;
}
@@ -16324,7 +26889,7 @@ void MessagesManager::on_upload_message_media_fail(DialogId dialog_id, MessageId
CHECK(dialog_id.get_type() != DialogType::SecretChat);
send_closure_later(actor_id(this), &MessagesManager::on_upload_message_media_finished, m->media_album_id, dialog_id,
- message_id, std::move(error));
+ m->message_id, std::move(error));
}
void MessagesManager::on_upload_message_media_finished(int64 media_album_id, DialogId dialog_id, MessageId message_id,
@@ -16338,44 +26903,67 @@ void MessagesManager::on_upload_message_media_finished(int64 media_album_id, Dia
auto &request = it->second;
CHECK(request.dialog_id == dialog_id);
auto message_it = std::find(request.message_ids.begin(), request.message_ids.end(), message_id);
- CHECK(message_it != request.message_ids.end());
+ if (message_it == request.message_ids.end()) {
+ // the message may be already deleted and the album is recreated without it
+ CHECK(message_id.is_yet_unsent());
+ LOG_CHECK(get_message({dialog_id, message_id}) == nullptr)
+ << dialog_id << ' ' << request.message_ids << ' ' << message_id << ' ' << request.finished_count << ' '
+ << request.is_finished << ' ' << request.results;
+ return;
+ }
auto pos = static_cast<size_t>(message_it - request.message_ids.begin());
if (request.is_finished[pos]) {
+ LOG(INFO) << "Upload media of " << message_id << " in " << dialog_id << " from group " << media_album_id
+ << " at pos " << pos << " was already finished";
return;
}
- LOG(INFO) << "Finish to upload media of " << message_id << " in " << dialog_id << " from group " << media_album_id;
+ LOG(INFO) << "Finish to upload media of " << message_id << " in " << dialog_id << " from group " << media_album_id
+ << " at pos " << pos << " with result " << result
+ << " and previous finished_count = " << request.finished_count;
request.results[pos] = std::move(result);
request.is_finished[pos] = true;
request.finished_count++;
if (request.finished_count == request.message_ids.size() || request.results[pos].is_error()) {
- // send later, because some messages may be being deleted now
- for (auto request_message_id : request.message_ids) {
+ // must use send_closure_later if some messages may be being deleted now
+ // but this function is called only through send_closure_later, so there should be no being deleted messages
+ // we must use synchronous calls to keep the correct message order during copying of multiple messages
+ // but "request" iterator can be invalidated by do_send_message_group, so it must not be used below
+ auto message_ids = request.message_ids;
+ for (auto request_message_id : message_ids) {
LOG(INFO) << "Send on_media_message_ready_to_send for " << request_message_id << " in " << dialog_id;
- send_closure_later(actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id,
- request_message_id,
- PromiseCreator::lambda([actor_id = actor_id(this)](Result<Message *> result) mutable {
- if (result.is_error() || G()->close_flag()) {
- return;
- }
+ auto promise = PromiseCreator::lambda([this, media_album_id](Result<Message *> result) {
+ if (result.is_error() || G()->close_flag()) {
+ return;
+ }
- auto m = result.move_as_ok();
- CHECK(m != nullptr);
- send_closure_later(actor_id, &MessagesManager::do_send_message_group, m->media_album_id);
- }));
+ auto m = result.move_as_ok();
+ CHECK(m != nullptr);
+ CHECK(m->media_album_id == media_album_id);
+ do_send_message_group(media_album_id);
+ // send_closure_later(actor_id, &MessagesManager::do_send_message_group, media_album_id);
+ });
+ // send_closure_later(actor_id(this), &MessagesManager::on_media_message_ready_to_send, dialog_id,
+ // request_message_id, std::move(promise));
+ on_media_message_ready_to_send(dialog_id, request_message_id, std::move(promise));
}
}
}
void MessagesManager::do_send_message_group(int64 media_album_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
CHECK(media_album_id < 0);
auto it = pending_message_group_sends_.find(media_album_id);
if (it == pending_message_group_sends_.end()) {
// the group may be already sent or failed to be sent
return;
}
+
auto &request = it->second;
auto dialog_id = request.dialog_id;
@@ -16384,10 +26972,15 @@ void MessagesManager::do_send_message_group(int64 media_album_id) {
auto default_status = can_send_message(dialog_id);
bool success = default_status.is_ok();
+ vector<FileId> file_ids;
vector<int64> random_ids;
vector<tl_object_ptr<telegram_api::inputSingleMedia>> input_single_media;
+ tl_object_ptr<telegram_api::InputPeer> as_input_peer;
MessageId reply_to_message_id;
+ MessageId top_thread_message_id;
int32 flags = 0;
+ int32 schedule_date = 0;
+ bool is_copy = false;
for (size_t i = 0; i < request.message_ids.size(); i++) {
auto *m = get_message(d, request.message_ids[i]);
if (m == nullptr) {
@@ -16397,22 +26990,48 @@ void MessagesManager::do_send_message_group(int64 media_album_id) {
}
reply_to_message_id = m->reply_to_message_id;
+ top_thread_message_id = m->top_thread_message_id;
flags = get_message_flags(m);
+ schedule_date = get_message_schedule_date(m);
+ is_copy = m->is_copy;
+ as_input_peer = get_send_message_as_input_peer(m);
+ file_ids.push_back(get_message_content_any_file_id(m->content.get()));
random_ids.push_back(begin_send_message(dialog_id, m));
- auto caption = get_message_content_caption(m->content.get());
- auto input_media = get_input_media(m->content.get(), nullptr, nullptr, m->ttl);
- auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption.entities);
+
+ LOG(INFO) << "Have file " << file_ids.back() << " in " << m->message_id << " with result " << request.results[i]
+ << " and is_finished = " << static_cast<bool>(request.is_finished[i]);
+
+ if (request.results[i].is_error() || !request.is_finished[i]) {
+ success = false;
+ continue;
+ }
+
+ const FormattedText *caption = get_message_content_caption(m->content.get());
+ auto input_media = get_input_media(m->content.get(), td_, m->ttl, m->send_emoji, true);
+ if (input_media == nullptr) {
+ // TODO return CHECK
+ auto file_id = get_message_content_any_file_id(m->content.get());
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ bool has_remote = file_view.has_remote_location();
+ bool is_web = has_remote ? file_view.remote_location().is_web() : false;
+ LOG(FATAL) << request.dialog_id << " " << request.finished_count << " " << i << " " << request.message_ids << " "
+ << request.is_finished << " " << request.results << " " << m->ttl << " " << has_remote << " "
+ << file_view.has_alive_remote_location() << " " << file_view.has_active_upload_remote_location() << " "
+ << file_view.has_active_download_remote_location() << " " << file_view.is_encrypted() << " " << is_web
+ << " " << file_view.has_url() << " "
+ << to_string(get_message_content_object(m->content.get(), td_, dialog_id, m->date,
+ m->is_content_secret, false, -1));
+ }
+ auto entities = get_input_message_entities(td_->contacts_manager_.get(), caption, "do_send_message_group");
int32 input_single_media_flags = 0;
if (!entities.empty()) {
input_single_media_flags |= telegram_api::inputSingleMedia::ENTITIES_MASK;
}
input_single_media.push_back(make_tl_object<telegram_api::inputSingleMedia>(
- input_single_media_flags, std::move(input_media), random_ids.back(), caption.text, std::move(entities)));
- if (request.results[i].is_error()) {
- success = false;
- }
+ input_single_media_flags, std::move(input_media), random_ids.back(), caption == nullptr ? "" : caption->text,
+ std::move(entities)));
}
if (!success) {
@@ -16428,7 +27047,8 @@ void MessagesManager::do_send_message_group(int64 media_album_id) {
pending_message_group_sends_.erase(it);
return;
}
- CHECK(request.finished_count == request.message_ids.size());
+ LOG_CHECK(request.finished_count == request.message_ids.size())
+ << request.finished_count << " " << request.message_ids.size();
pending_message_group_sends_.erase(it);
LOG(INFO) << "Begin to send media group " << media_album_id << " to " << dialog_id;
@@ -16436,16 +27056,51 @@ void MessagesManager::do_send_message_group(int64 media_album_id) {
if (input_single_media.empty()) {
LOG(INFO) << "Media group " << media_album_id << " from " << dialog_id << " is empty";
}
- send_closure(td_->create_net_actor<SendMultiMediaActor>(), &SendMultiMediaActor::send, flags, dialog_id,
- reply_to_message_id, std::move(input_single_media),
- get_sequence_dispatcher_id(dialog_id, MessagePhoto::ID));
+ td_->create_handler<SendMultiMediaQuery>()->send(flags, dialog_id, std::move(as_input_peer), reply_to_message_id,
+ top_thread_message_id, schedule_date, std::move(file_ids),
+ std::move(input_single_media), is_copy);
+}
+
+void MessagesManager::on_text_message_ready_to_send(DialogId dialog_id, MessageId message_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Ready to send " << message_id << " to " << dialog_id;
+
+ auto m = get_message({dialog_id, message_id});
+ if (m == nullptr) {
+ return;
+ }
+
+ CHECK(message_id.is_yet_unsent());
+
+ auto content = m->content.get();
+ CHECK(content != nullptr);
+
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ CHECK(!message_id.is_scheduled());
+ auto layer = td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id());
+ send_secret_message(dialog_id, m, get_secret_input_media(content, td_, nullptr, BufferSlice(), layer));
+ } else {
+ const FormattedText *message_text = get_message_content_text(content);
+ CHECK(message_text != nullptr);
+
+ int64 random_id = begin_send_message(dialog_id, m);
+ td_->create_handler<SendMessageQuery>()->send(
+ get_message_flags(m), dialog_id, get_send_message_as_input_peer(m), m->reply_to_message_id,
+ m->top_thread_message_id, get_message_schedule_date(m),
+ get_input_reply_markup(td_->contacts_manager_.get(), m->reply_markup),
+ get_input_message_entities(td_->contacts_manager_.get(), message_text->entities, "do_send_message"),
+ message_text->text, m->is_copy, random_id, &m->send_query_ref);
+ }
}
void MessagesManager::on_media_message_ready_to_send(DialogId dialog_id, MessageId message_id,
Promise<Message *> &&promise) {
LOG(INFO) << "Ready to send " << message_id << " to " << dialog_id;
CHECK(promise);
- if (!G()->parameters().use_file_db) { // ResourceManager::Mode::Greedy
+ if (!G()->parameters().use_file_db || message_id.is_scheduled()) { // ResourceManager::Mode::Greedy
auto m = get_message({dialog_id, message_id});
if (m != nullptr) {
promise.set_value(std::move(m));
@@ -16453,10 +27108,10 @@ void MessagesManager::on_media_message_ready_to_send(DialogId dialog_id, Message
return;
}
- auto queue_id = get_sequence_dispatcher_id(dialog_id, MessagePhoto::ID);
+ auto queue_id = ChainId(dialog_id, MessageContentType::Photo).get();
CHECK(queue_id & 1);
auto &queue = yet_unsent_media_queues_[queue_id];
- auto it = queue.find(message_id.get());
+ auto it = queue.find(message_id);
if (it == queue.end()) {
if (queue.empty()) {
yet_unsent_media_queues_.erase(queue_id);
@@ -16479,91 +27134,98 @@ void MessagesManager::on_media_message_ready_to_send(DialogId dialog_id, Message
}
void MessagesManager::on_yet_unsent_media_queue_updated(DialogId dialog_id) {
- auto queue_id = get_sequence_dispatcher_id(dialog_id, MessagePhoto::ID);
+ auto queue_id = ChainId(dialog_id, MessageContentType::Photo).get();
CHECK(queue_id & 1);
- auto &queue = yet_unsent_media_queues_[queue_id];
- LOG(INFO) << "Queue for " << dialog_id << " is updated to size of " << queue.size();
- while (!queue.empty()) {
+ while (true) {
+ auto it = yet_unsent_media_queues_.find(queue_id);
+ if (it == yet_unsent_media_queues_.end()) {
+ return;
+ }
+ auto &queue = it->second;
+ if (queue.empty()) {
+ yet_unsent_media_queues_.erase(it);
+ return;
+ }
auto first_it = queue.begin();
if (!first_it->second) {
- break;
+ return;
}
- auto m = get_message({dialog_id, MessageId(first_it->first)});
+ auto m = get_message({dialog_id, first_it->first});
+ auto promise = std::move(first_it->second);
+ queue.erase(first_it);
+ LOG(INFO) << "Queue for " << dialog_id << " now has size " << queue.size();
+
+ // don't use it/queue/first_it after promise is called
if (m != nullptr) {
LOG(INFO) << "Can send " << FullMessageId{dialog_id, m->message_id};
- first_it->second.set_value(std::move(m));
+ promise.set_value(std::move(m));
+ } else {
+ promise.set_error(Status::Error(400, "Message not found"));
}
- queue.erase(first_it);
- }
- LOG(INFO) << "Queue for " << dialog_id << " now has size " << queue.size();
- if (queue.empty()) {
- yet_unsent_media_queues_.erase(queue_id);
}
}
Result<MessageId> MessagesManager::send_bot_start_message(UserId bot_user_id, DialogId dialog_id,
const string &parameter) {
LOG(INFO) << "Begin to send bot start message to " << dialog_id;
- if (td_->auth_manager_->is_bot()) {
- return Status::Error(5, "Bot can't send start message to another bot");
- }
+ CHECK(!td_->auth_manager_->is_bot());
TRY_RESULT(bot_data, td_->contacts_manager_->get_bot_data(bot_user_id));
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "send_bot_start_message");
if (d == nullptr) {
- return Status::Error(5, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
bool is_chat_with_bot = false;
switch (dialog_id.get_type()) {
case DialogType::User:
if (dialog_id.get_user_id() != bot_user_id) {
- return Status::Error(5, "Can't send start message to a private chat other than chat with the bot");
+ return Status::Error(400, "Can't send start message to a private chat other than chat with the bot");
}
is_chat_with_bot = true;
break;
case DialogType::Chat: {
if (!bot_data.can_join_groups) {
- return Status::Error(5, "Bot can't join groups");
+ return Status::Error(400, "Bot can't join groups");
}
auto chat_id = dialog_id.get_chat_id();
if (!td_->contacts_manager_->have_input_peer_chat(chat_id, AccessRights::Write)) {
- return Status::Error(3, "Can't access the chat");
+ return Status::Error(400, "Can't access the chat");
}
- auto status = td_->contacts_manager_->get_chat_status(chat_id);
+ auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_invite_users()) {
- return Status::Error(3, "Need administrator rights to invite a bot to the group chat");
+ return Status::Error(400, "Need administrator rights to invite a bot to the group chat");
}
break;
}
case DialogType::Channel: {
auto channel_id = dialog_id.get_channel_id();
if (!td_->contacts_manager_->have_input_peer_channel(channel_id, AccessRights::Write)) {
- return Status::Error(3, "Can't access the chat");
+ return Status::Error(400, "Can't access the chat");
}
switch (td_->contacts_manager_->get_channel_type(channel_id)) {
case ChannelType::Megagroup:
if (!bot_data.can_join_groups) {
- return Status::Error(5, "The bot can't join groups");
+ return Status::Error(400, "The bot can't join groups");
}
break;
case ChannelType::Broadcast:
- return Status::Error(3, "Bots can't be invited to channel chats. Add them as administrators instead");
+ return Status::Error(400, "Bots can't be invited to channel chats. Add them as administrators instead");
case ChannelType::Unknown:
default:
UNREACHABLE();
}
- auto status = td_->contacts_manager_->get_channel_status(channel_id);
+ auto status = td_->contacts_manager_->get_channel_permissions(channel_id);
if (!status.can_invite_users()) {
- return Status::Error(3, "Need administrator rights to invite a bot to the supergroup chat");
+ return Status::Error(400, "Need administrator rights to invite a bot to the supergroup chat");
}
break;
}
case DialogType::SecretChat:
- return Status::Error(5, "Can't send bot start message to a secret chat");
+ return Status::Error(400, "Can't send bot start message to a secret chat");
case DialogType::None:
default:
UNREACHABLE();
@@ -16574,22 +27236,27 @@ Result<MessageId> MessagesManager::send_bot_start_message(UserId bot_user_id, Di
text += bot_data.username;
}
+ vector<MessageEntity> text_entities;
+ text_entities.emplace_back(MessageEntity::Type::BotCommand, 0, narrow_cast<int32>(text.size()));
bool need_update_dialog_pos = false;
- Message *m = get_message_to_send(
- d, MessageId(), false, false,
- make_unique<MessageText>(
- FormattedText{text, vector<MessageEntity>{MessageEntity(MessageEntity::Type::BotCommand, 0,
- narrow_cast<int32>(text.size()))}},
- WebPageId()),
- &need_update_dialog_pos);
-
- send_update_new_message(d, m, true);
+ Message *m = get_message_to_send(d, MessageId(), MessageId(), MessageSendOptions(),
+ create_text_message_content(text, std::move(text_entities), WebPageId()),
+ &need_update_dialog_pos);
+ m->is_bot_start_message = true;
+
+ send_update_new_message(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "send_bot_start_message");
}
- save_send_bot_start_message_logevent(bot_user_id, dialog_id, parameter, m);
- do_send_bot_start_message(bot_user_id, dialog_id, parameter, m);
+ if (parameter.empty() && is_chat_with_bot) {
+ save_send_message_log_event(dialog_id, m);
+ do_send_message(dialog_id, m);
+ } else {
+ save_send_bot_start_message_log_event(bot_user_id, dialog_id, parameter, m);
+ send_closure_later(actor_id(this), &MessagesManager::do_send_bot_start_message, bot_user_id, dialog_id,
+ m->message_id, parameter);
+ }
return m->message_id;
}
@@ -16614,62 +27281,71 @@ class MessagesManager::SendBotStartMessageLogEvent {
td::parse(bot_user_id, parser);
td::parse(dialog_id, parser);
td::parse(parameter, parser);
- CHECK(m_out == nullptr);
- m_out = make_unique<Message>();
- td::parse(*m_out, parser);
+ td::parse(m_out, parser);
}
};
-void MessagesManager::save_send_bot_start_message_logevent(UserId bot_user_id, DialogId dialog_id,
- const string &parameter, Message *m) {
+void MessagesManager::save_send_bot_start_message_log_event(UserId bot_user_id, DialogId dialog_id,
+ const string &parameter, const Message *m) {
if (!G()->parameters().use_message_db) {
return;
}
CHECK(m != nullptr);
LOG(INFO) << "Save " << FullMessageId(dialog_id, m->message_id) << " to binlog";
- SendBotStartMessageLogEvent logevent;
- logevent.bot_user_id = bot_user_id;
- logevent.dialog_id = dialog_id;
- logevent.parameter = parameter;
- logevent.m_in = m;
- auto storer = LogEventStorerImpl<SendBotStartMessageLogEvent>(logevent);
- CHECK(m->send_message_logevent_id == 0);
- m->send_message_logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendBotStartMessage, storer);
-}
+ SendBotStartMessageLogEvent log_event;
+ log_event.bot_user_id = bot_user_id;
+ log_event.dialog_id = dialog_id;
+ log_event.parameter = parameter;
+ log_event.m_in = m;
+ CHECK(m->send_message_log_event_id == 0);
+ m->send_message_log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendBotStartMessage,
+ get_log_event_storer(log_event));
+}
+
+void MessagesManager::do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, MessageId message_id,
+ const string &parameter) {
+ if (G()->close_flag()) {
+ return;
+ }
-void MessagesManager::do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, const string &parameter,
- Message *m) {
- LOG(INFO) << "Do send bot start " << FullMessageId(dialog_id, m->message_id) << " to bot " << bot_user_id;
+ LOG(INFO) << "Do send bot start " << FullMessageId(dialog_id, message_id) << " to bot " << bot_user_id;
+
+ auto m = get_message({dialog_id, message_id});
+ if (m == nullptr) {
+ return;
+ }
int64 random_id = begin_send_message(dialog_id, m);
telegram_api::object_ptr<telegram_api::InputPeer> input_peer = dialog_id.get_type() == DialogType::User
? make_tl_object<telegram_api::inputPeerEmpty>()
: get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
- return on_send_message_fail(random_id, Status::Error(400, "Have no info about the chat"));
+ return on_send_message_fail(random_id, Status::Error(400, "Chat is not accessible"));
}
- auto bot_input_user = td_->contacts_manager_->get_input_user(bot_user_id);
- if (bot_input_user == nullptr) {
- return on_send_message_fail(random_id, Status::Error(400, "Have no info about the bot"));
+ auto r_bot_input_user = td_->contacts_manager_->get_input_user(bot_user_id);
+ if (r_bot_input_user.is_error()) {
+ return on_send_message_fail(random_id, r_bot_input_user.move_as_error());
}
- m->send_query_ref = td_->create_handler<StartBotQuery>()->send(std::move(bot_input_user), dialog_id,
+ m->send_query_ref = td_->create_handler<StartBotQuery>()->send(r_bot_input_user.move_as_ok(), dialog_id,
std::move(input_peer), parameter, random_id);
}
-Result<MessageId> MessagesManager::send_inline_query_result_message(DialogId dialog_id, MessageId reply_to_message_id,
- bool disable_notification, bool from_background,
- int64 query_id, const string &result_id) {
+Result<MessageId> MessagesManager::send_inline_query_result_message(DialogId dialog_id, MessageId top_thread_message_id,
+ MessageId reply_to_message_id,
+ tl_object_ptr<td_api::messageSendOptions> &&options,
+ int64 query_id, const string &result_id,
+ bool hide_via_bot) {
LOG(INFO) << "Begin to send inline query result message to " << dialog_id << " in reply to " << reply_to_message_id;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "send_inline_query_result_message");
if (d == nullptr) {
- return Status::Error(5, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
+ TRY_RESULT(message_send_options, process_message_send_options(dialog_id, std::move(options), false));
bool to_secret = false;
switch (dialog_id.get_type()) {
case DialogType::User:
@@ -16677,7 +27353,7 @@ Result<MessageId> MessagesManager::send_inline_query_result_message(DialogId dia
// ok
break;
case DialogType::Channel: {
- auto channel_status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
+ auto channel_status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
if (!channel_status.can_use_inline_bots()) {
return Status::Error(400, "Can't use inline bots in the chat");
}
@@ -16692,44 +27368,53 @@ Result<MessageId> MessagesManager::send_inline_query_result_message(DialogId dia
UNREACHABLE();
}
- const MessageContent *message_content;
- const ReplyMarkup *reply_markup;
- bool disable_web_page_preview;
- std::tie(message_content, reply_markup, disable_web_page_preview) =
- td_->inline_queries_manager_->get_inline_message_content(query_id, result_id);
- if (message_content == nullptr) {
- return Status::Error(5, "Inline query result not found");
+ const InlineMessageContent *content = td_->inline_queries_manager_->get_inline_message_content(query_id, result_id);
+ if (content == nullptr) {
+ return Status::Error(400, "Inline query result not found");
}
- TRY_STATUS(can_send_message_content(dialog_id, message_content, false));
+ reply_to_message_id = get_reply_to_message_id(d, top_thread_message_id, reply_to_message_id, false);
+ TRY_STATUS(can_use_message_send_options(message_send_options, content->message_content, 0));
+ TRY_STATUS(can_send_message_content(dialog_id, content->message_content.get(), false, td_));
+ TRY_STATUS(can_use_top_thread_message_id(d, top_thread_message_id, reply_to_message_id));
bool need_update_dialog_pos = false;
- Message *m =
- get_message_to_send(d, get_reply_to_message_id(d, reply_to_message_id), disable_notification, from_background,
- dup_message_content(dialog_id, message_content, false), &need_update_dialog_pos);
- m->via_bot_user_id = td_->inline_queries_manager_->get_inline_bot_user_id(query_id);
- if (reply_markup != nullptr && !to_secret) {
- m->reply_markup = make_unique<ReplyMarkup>(*reply_markup);
+ Message *m = get_message_to_send(d, top_thread_message_id, reply_to_message_id, message_send_options,
+ dup_message_content(td_, dialog_id, content->message_content.get(),
+ MessageContentDupType::SendViaBot, MessageCopyOptions()),
+ &need_update_dialog_pos, false, nullptr, true);
+ m->hide_via_bot = hide_via_bot;
+ if (!hide_via_bot) {
+ m->via_bot_user_id = td_->inline_queries_manager_->get_inline_bot_user_id(query_id);
}
- m->disable_web_page_preview = disable_web_page_preview;
- m->clear_draft = true;
+ if (content->message_reply_markup != nullptr && !to_secret) {
+ m->reply_markup = make_unique<ReplyMarkup>(*content->message_reply_markup);
+ }
+ m->disable_web_page_preview = content->disable_web_page_preview;
+ m->clear_draft = !hide_via_bot;
- update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos);
+ if (m->clear_draft) {
+ if (top_thread_message_id.is_valid()) {
+ set_dialog_draft_message(dialog_id, top_thread_message_id, nullptr).ignore();
+ } else {
+ update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos);
+ }
+ }
- send_update_new_message(d, m, true);
+ send_update_new_message(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "send_inline_query_result_message");
}
if (to_secret) {
- save_send_message_logevent(dialog_id, m);
- auto message_id = m->message_id;
+ save_send_message_log_event(dialog_id, m);
do_send_message(dialog_id, m);
- return message_id;
+ return m->message_id;
}
- save_send_inline_query_result_message_logevent(dialog_id, m, query_id, result_id);
- do_send_inline_query_result_message(dialog_id, m, query_id, result_id);
+ save_send_inline_query_result_message_log_event(dialog_id, m, query_id, result_id);
+ send_closure_later(actor_id(this), &MessagesManager::do_send_inline_query_result_message, dialog_id, m->message_id,
+ query_id, result_id);
return m->message_id;
}
@@ -16754,38 +27439,64 @@ class MessagesManager::SendInlineQueryResultMessageLogEvent {
td::parse(dialog_id, parser);
td::parse(query_id, parser);
td::parse(result_id, parser);
- CHECK(m_out == nullptr);
- m_out = make_unique<Message>();
- td::parse(*m_out, parser);
+ td::parse(m_out, parser);
}
};
-void MessagesManager::save_send_inline_query_result_message_logevent(DialogId dialog_id, Message *m, int64 query_id,
- const string &result_id) {
+void MessagesManager::save_send_inline_query_result_message_log_event(DialogId dialog_id, const Message *m,
+ int64 query_id, const string &result_id) {
if (!G()->parameters().use_message_db) {
return;
}
CHECK(m != nullptr);
LOG(INFO) << "Save " << FullMessageId(dialog_id, m->message_id) << " to binlog";
- SendInlineQueryResultMessageLogEvent logevent;
- logevent.dialog_id = dialog_id;
- logevent.query_id = query_id;
- logevent.result_id = result_id;
- logevent.m_in = m;
- auto storer = LogEventStorerImpl<SendInlineQueryResultMessageLogEvent>(logevent);
- CHECK(m->send_message_logevent_id == 0);
- m->send_message_logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendInlineQueryResultMessage, storer);
-}
-
-void MessagesManager::do_send_inline_query_result_message(DialogId dialog_id, Message *m, int64 query_id,
+ SendInlineQueryResultMessageLogEvent log_event;
+ log_event.dialog_id = dialog_id;
+ log_event.query_id = query_id;
+ log_event.result_id = result_id;
+ log_event.m_in = m;
+ CHECK(m->send_message_log_event_id == 0);
+ m->send_message_log_event_id = binlog_add(
+ G()->td_db()->get_binlog(), LogEvent::HandlerType::SendInlineQueryResultMessage, get_log_event_storer(log_event));
+}
+
+void MessagesManager::do_send_inline_query_result_message(DialogId dialog_id, MessageId message_id, int64 query_id,
const string &result_id) {
- LOG(INFO) << "Do send inline query result " << FullMessageId(dialog_id, m->message_id);
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Do send inline query result " << FullMessageId(dialog_id, message_id);
+
+ auto m = get_message({dialog_id, message_id});
+ if (m == nullptr) {
+ return;
+ }
int64 random_id = begin_send_message(dialog_id, m);
+ auto flags = get_message_flags(m);
+ if (!m->via_bot_user_id.is_valid() || m->hide_via_bot) {
+ flags |= telegram_api::messages_sendInlineBotResult::HIDE_VIA_MASK;
+ }
m->send_query_ref = td_->create_handler<SendInlineBotResultQuery>()->send(
- get_message_flags(m), dialog_id, m->reply_to_message_id, random_id, query_id, result_id);
+ flags, dialog_id, get_send_message_as_input_peer(m), m->reply_to_message_id, m->top_thread_message_id,
+ get_message_schedule_date(m), random_id, query_id, result_id);
+}
+
+bool MessagesManager::has_qts_messages(DialogId dialog_id) const {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::Chat:
+ return td_->option_manager_->get_option_integer("session_count") > 1;
+ case DialogType::Channel:
+ case DialogType::SecretChat:
+ return false;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ return false;
+ }
}
bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, bool is_editing,
@@ -16799,37 +27510,36 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo
if (m->message_id.is_local()) {
return false;
}
- if (m->forward_info != nullptr) {
+ if (m->forward_info != nullptr || m->had_forward_info) {
return false;
}
- bool is_bot = td_->auth_manager_->is_bot();
if (m->had_reply_markup) {
return false;
}
- if (!is_bot && m->reply_markup != nullptr) {
- return false;
- }
if (m->reply_markup != nullptr && m->reply_markup->type != ReplyMarkup::Type::InlineKeyboard) {
return false;
}
- auto my_id = td_->contacts_manager_->get_my_id("can_edit_message");
- if (m->via_bot_user_id.is_valid() && m->via_bot_user_id != my_id) {
+ auto my_id = td_->contacts_manager_->get_my_id();
+ if (m->via_bot_user_id.is_valid() && (m->via_bot_user_id != my_id || m->message_id.is_scheduled())) {
return false;
}
+ bool is_bot = td_->auth_manager_->is_bot();
+ auto content_type = m->content->get_type();
DialogId my_dialog_id(my_id);
- bool has_edit_time_limit = !(is_bot && m->is_outgoing) && dialog_id != my_dialog_id;
-
+ bool has_edit_time_limit = !(is_bot && m->is_outgoing) && dialog_id != my_dialog_id &&
+ content_type != MessageContentType::Poll &&
+ content_type != MessageContentType::LiveLocation && !m->message_id.is_scheduled();
switch (dialog_id.get_type()) {
case DialogType::User:
- if (!m->is_outgoing && dialog_id != my_dialog_id) {
+ if (!m->is_outgoing && dialog_id != my_dialog_id && !m->via_bot_user_id.is_valid()) {
return false;
}
break;
case DialogType::Chat:
- if (!m->is_outgoing) {
+ if (!m->is_outgoing && !m->via_bot_user_id.is_valid()) {
return false;
}
break;
@@ -16840,10 +27550,22 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo
}
auto channel_id = dialog_id.get_channel_id();
- auto channel_status = td_->contacts_manager_->get_channel_status(channel_id);
+ auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
if (m->is_channel_post) {
- if (!channel_status.can_edit_messages() && !(channel_status.can_post_messages() && m->is_outgoing)) {
- return false;
+ if (m->message_id.is_scheduled()) {
+ if (!channel_status.can_post_messages()) {
+ return false;
+ }
+ } else {
+ if (!channel_status.can_edit_messages() && !(channel_status.can_post_messages() && m->is_outgoing)) {
+ return false;
+ }
+ if (channel_status.can_edit_messages()) {
+ has_edit_time_limit = false;
+ }
+ }
+ if (is_bot && only_reply_markup) {
+ has_edit_time_limit = false;
}
} else {
if (!m->is_outgoing) {
@@ -16863,60 +27585,84 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo
return false;
}
- const int32 DEFAULT_EDIT_TIME_LIMIT = 2 * 86400;
- int32 edit_time_limit = G()->shared_config().get_option_integer("edit_time_limit", DEFAULT_EDIT_TIME_LIMIT);
- if (has_edit_time_limit && G()->unix_time_cached() - m->date >= edit_time_limit + (is_editing ? 300 : 0)) {
- return false;
+ if (has_edit_time_limit) {
+ const int32 DEFAULT_EDIT_TIME_LIMIT = 2 * 86400;
+ int64 edit_time_limit = td_->option_manager_->get_option_integer("edit_time_limit", DEFAULT_EDIT_TIME_LIMIT);
+ if (G()->unix_time_cached() - m->date - (is_editing ? 300 : 0) >= edit_time_limit) {
+ return false;
+ }
}
- switch (m->content->get_id()) {
- case MessageAnimation::ID:
- case MessageAudio::ID:
- case MessageDocument::ID:
- case MessageGame::ID:
- case MessagePhoto::ID:
- case MessageText::ID:
- case MessageVideo::ID:
- case MessageVoiceNote::ID:
+ switch (content_type) {
+ case MessageContentType::Animation:
+ case MessageContentType::Audio:
+ case MessageContentType::Document:
+ case MessageContentType::Game:
+ case MessageContentType::Photo:
+ case MessageContentType::Text:
+ case MessageContentType::Video:
+ case MessageContentType::VoiceNote:
return true;
- case MessageLiveLocation::ID: {
- if (td_->auth_manager_->is_bot() && only_reply_markup) {
+ case MessageContentType::LiveLocation: {
+ if (is_bot && only_reply_markup) {
// there is no caption to edit, but bot can edit inline reply_markup
return true;
}
- return G()->unix_time_cached() - m->date < static_cast<const MessageLiveLocation *>(m->content.get())->period;
+ return G()->unix_time_cached() - m->date < get_message_content_live_location_period(m->content.get());
+ }
+ case MessageContentType::Poll: {
+ if (is_bot && only_reply_markup) {
+ // there is no caption to edit, but bot can edit inline reply_markup
+ return true;
+ }
+ if (m->message_id.is_scheduled()) {
+ return false;
+ }
+ return !get_message_content_poll_is_closed(td_, m->content.get());
}
- case MessageContact::ID:
- case MessageLocation::ID:
- case MessageSticker::ID:
- case MessageVenue::ID:
- case MessageVideoNote::ID:
+ case MessageContentType::Contact:
+ case MessageContentType::Dice:
+ case MessageContentType::Location:
+ case MessageContentType::Sticker:
+ case MessageContentType::Venue:
+ case MessageContentType::VideoNote:
// there is no caption to edit, but bot can edit inline reply_markup
- return td_->auth_manager_->is_bot() && only_reply_markup;
- case MessageInvoice::ID:
- case MessageUnsupported::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
+ return is_bot && only_reply_markup;
+ case MessageContentType::Invoice:
+ case MessageContentType::Unsupported:
+ case MessageContentType::ChatCreate:
+ case MessageContentType::ChatChangeTitle:
+ case MessageContentType::ChatChangePhoto:
+ case MessageContentType::ChatDeletePhoto:
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatAddUsers:
+ case MessageContentType::ChatJoinedByLink:
+ case MessageContentType::ChatDeleteUser:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::ChannelCreate:
+ case MessageContentType::ChannelMigrateFrom:
+ case MessageContentType::PinMessage:
+ case MessageContentType::GameScore:
+ case MessageContentType::ScreenshotTaken:
+ case MessageContentType::ChatSetTtl:
+ case MessageContentType::Call:
+ case MessageContentType::PaymentSuccessful:
+ case MessageContentType::ContactRegistered:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::CustomServiceAction:
+ case MessageContentType::WebsiteConnected:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::ProximityAlertTriggered:
+ case MessageContentType::GroupCall:
+ case MessageContentType::InviteToGroupCall:
+ case MessageContentType::ChatSetTheme:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ case MessageContentType::GiftPremium:
+ case MessageContentType::TopicCreate:
+ case MessageContentType::TopicEdit:
return false;
default:
UNREACHABLE();
@@ -16925,12 +27671,108 @@ bool MessagesManager::can_edit_message(DialogId dialog_id, const Message *m, boo
return false;
}
+bool MessagesManager::can_resend_message(const Message *m) const {
+ if (m->send_error_code != 429 && m->send_error_message != "Message is too old to be re-sent automatically" &&
+ m->send_error_message != "SCHEDULE_TOO_MUCH" && m->send_error_message != "SEND_AS_PEER_INVALID") {
+ return false;
+ }
+ if (m->is_bot_start_message) {
+ return false;
+ }
+ if (m->forward_info != nullptr || m->real_forward_from_dialog_id.is_valid()) {
+ // TODO implement resending of forwarded messages
+ return false;
+ }
+ auto content_type = m->content->get_type();
+ if (m->via_bot_user_id.is_valid() || m->hide_via_bot) {
+ // via bot message
+ if (!can_have_input_media(td_, m->content.get(), false)) {
+ return false;
+ }
+
+ // resend via_bot message as an ordinary message if error code is 429
+ // TODO support other error codes
+ }
+
+ if (content_type == MessageContentType::ChatSetTtl || content_type == MessageContentType::ScreenshotTaken) {
+ // TODO implement resending of ChatSetTtl and ScreenshotTaken messages
+ return false;
+ }
+ return true;
+}
+
+bool MessagesManager::is_group_dialog(DialogId dialog_id) const {
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat:
+ return true;
+ case DialogType::Channel:
+ return td_->contacts_manager_->is_megagroup_channel(dialog_id.get_channel_id());
+ default:
+ return false;
+ }
+}
+
bool MessagesManager::is_broadcast_channel(DialogId dialog_id) const {
if (dialog_id.get_type() != DialogType::Channel) {
return false;
}
- return td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id()) == ChannelType::Broadcast;
+ return td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id());
+}
+
+bool MessagesManager::is_deleted_secret_chat(DialogId dialog_id) const {
+ return is_deleted_secret_chat(get_dialog(dialog_id));
+}
+
+bool MessagesManager::is_deleted_secret_chat(const Dialog *d) const {
+ if (d == nullptr) {
+ return true;
+ }
+ if (d->dialog_id.get_type() != DialogType::SecretChat) {
+ return false;
+ }
+
+ if (d->order != DEFAULT_ORDER || d->messages != nullptr) {
+ return false;
+ }
+
+ auto state = td_->contacts_manager_->get_secret_chat_state(d->dialog_id.get_secret_chat_id());
+ if (state != SecretChatState::Closed) {
+ return false;
+ }
+
+ return true;
+}
+
+int32 MessagesManager::get_message_schedule_date(const Message *m) {
+ CHECK(m != nullptr);
+ if (!m->message_id.is_scheduled()) {
+ return 0;
+ }
+ if (m->edited_schedule_date != 0) {
+ return m->edited_schedule_date;
+ }
+ return m->date;
+}
+
+DialogId MessagesManager::get_message_original_sender(const Message *m) {
+ CHECK(m != nullptr);
+ if (m->forward_info != nullptr) {
+ auto forward_info = m->forward_info.get();
+ if (forward_info->is_imported || is_forward_info_sender_hidden(forward_info)) {
+ return DialogId();
+ }
+ if (forward_info->message_id.is_valid() || forward_info->sender_dialog_id.is_valid()) {
+ return forward_info->sender_dialog_id;
+ }
+ return DialogId(forward_info->sender_user_id);
+ }
+ return get_message_sender(m);
+}
+
+DialogId MessagesManager::get_message_sender(const Message *m) {
+ CHECK(m != nullptr);
+ return m->sender_dialog_id.is_valid() ? m->sender_dialog_id : DialogId(m->sender_user_id);
}
void MessagesManager::edit_message_text(FullMessageId full_message_id,
@@ -16938,113 +27780,310 @@ void MessagesManager::edit_message_text(FullMessageId full_message_id,
tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
Promise<Unit> &&promise) {
if (input_message_content == nullptr) {
- return promise.set_error(Status::Error(5, "Can't edit message without new content"));
+ return promise.set_error(Status::Error(400, "Can't edit message without new content"));
}
int32 new_message_content_type = input_message_content->get_id();
if (new_message_content_type != td_api::inputMessageText::ID) {
- return promise.set_error(Status::Error(5, "Input message content type must be InputMessageText"));
+ return promise.set_error(Status::Error(400, "Input message content type must be InputMessageText"));
}
LOG(INFO) << "Begin to edit text of " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "edit_message_text");
if (d == nullptr) {
- return promise.set_error(Status::Error(5, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
- return promise.set_error(Status::Error(5, "Can't access the chat"));
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
}
- auto message_id = full_message_id.get_message_id();
- const Message *message = get_message_force(d, message_id);
- if (message == nullptr) {
- return promise.set_error(Status::Error(5, "Message not found"));
+ const Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_text");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
}
- if (!can_edit_message(dialog_id, message, true)) {
- return promise.set_error(Status::Error(5, "Message can't be edited"));
+ if (!can_edit_message(dialog_id, m, true)) {
+ return promise.set_error(Status::Error(400, "Message can't be edited"));
}
- int32 old_message_content_type = message->content->get_id();
- if (old_message_content_type != MessageText::ID && old_message_content_type != MessageGame::ID) {
- return promise.set_error(Status::Error(5, "There is no text in the message to edit"));
+ MessageContentType old_message_content_type = m->content->get_type();
+ if (old_message_content_type != MessageContentType::Text && old_message_content_type != MessageContentType::Game) {
+ return promise.set_error(Status::Error(400, "There is no text in the message to edit"));
}
auto r_input_message_text =
- process_input_message_text(dialog_id, std::move(input_message_content), td_->auth_manager_->is_bot());
+ process_input_message_text(td_, dialog_id, std::move(input_message_content), td_->auth_manager_->is_bot());
if (r_input_message_text.is_error()) {
return promise.set_error(r_input_message_text.move_as_error());
}
InputMessageText input_message_text = r_input_message_text.move_as_ok();
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
- !is_broadcast_channel(dialog_id));
+ has_message_sender_user_id(dialog_id, m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
- auto input_reply_markup = get_input_reply_markup(r_new_reply_markup.ok());
+ auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok());
int32 flags = 0;
if (input_message_text.disable_web_page_preview) {
flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW;
}
- send_closure(td_->create_net_actor<EditMessageActor>(std::move(promise)), &EditMessageActor::send, flags, dialog_id,
- message_id, input_message_text.text.text,
- get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities), nullptr,
- std::move(input_reply_markup), get_sequence_dispatcher_id(dialog_id, -1));
+ td_->create_handler<EditMessageQuery>(std::move(promise))
+ ->send(flags, dialog_id, m->message_id, input_message_text.text.text,
+ get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities,
+ "edit_message_text"),
+ nullptr, std::move(input_reply_markup), get_message_schedule_date(m));
}
void MessagesManager::edit_message_live_location(FullMessageId full_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
- tl_object_ptr<td_api::location> &&input_location,
- Promise<Unit> &&promise) {
+ tl_object_ptr<td_api::location> &&input_location, int32 heading,
+ int32 proximity_alert_radius, Promise<Unit> &&promise) {
LOG(INFO) << "Begin to edit live location of " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "edit_message_live_location");
if (d == nullptr) {
- return promise.set_error(Status::Error(5, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
- return promise.set_error(Status::Error(5, "Can't access the chat"));
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
}
- auto message_id = full_message_id.get_message_id();
- const Message *message = get_message_force(d, message_id);
- if (message == nullptr) {
- return promise.set_error(Status::Error(5, "Message not found"));
+ const Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_live_location");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
}
- if (!can_edit_message(dialog_id, message, true)) {
- return promise.set_error(Status::Error(5, "Message can't be edited"));
+ if (!can_edit_message(dialog_id, m, true)) {
+ return promise.set_error(Status::Error(400, "Message can't be edited"));
}
- int32 old_message_content_type = message->content->get_id();
- if (old_message_content_type != MessageLiveLocation::ID) {
- return promise.set_error(Status::Error(5, "There is no live location in the message to edit"));
+ MessageContentType old_message_content_type = m->content->get_type();
+ if (old_message_content_type != MessageContentType::LiveLocation) {
+ return promise.set_error(Status::Error(400, "There is no live location in the message to edit"));
+ }
+ if (m->message_id.is_scheduled()) {
+ LOG(ERROR) << "Have " << full_message_id << " with live location";
+ return promise.set_error(Status::Error(400, "Can't edit live location in scheduled message"));
}
Location location(input_location);
if (location.empty() && input_location != nullptr) {
- return promise.set_error(Status::Error(400, "Wrong location specified"));
+ return promise.set_error(Status::Error(400, "Invalid location specified"));
}
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
- !is_broadcast_channel(dialog_id));
+ has_message_sender_user_id(dialog_id, m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
- auto input_reply_markup = get_input_reply_markup(r_new_reply_markup.ok());
+ auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok());
int32 flags = 0;
if (location.empty()) {
- flags |= telegram_api::messages_editMessage::STOP_GEO_LIVE_MASK;
+ flags |= telegram_api::inputMediaGeoLive::STOPPED_MASK;
+ }
+ if (heading != 0) {
+ flags |= telegram_api::inputMediaGeoLive::HEADING_MASK;
}
- send_closure(td_->create_net_actor<EditMessageActor>(std::move(promise)), &EditMessageActor::send, flags, dialog_id,
- message_id, string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(),
- location.empty() ? nullptr : location.get_input_geo_point(), std::move(input_reply_markup),
- get_sequence_dispatcher_id(dialog_id, -1));
+ flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK;
+ auto input_media = telegram_api::make_object<telegram_api::inputMediaGeoLive>(
+ flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius);
+ td_->create_handler<EditMessageQuery>(std::move(promise))
+ ->send(0, dialog_id, m->message_id, string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(),
+ std::move(input_media), std::move(input_reply_markup), get_message_schedule_date(m));
+}
+
+void MessagesManager::cancel_edit_message_media(DialogId dialog_id, Message *m, Slice error_message) {
+ if (m->edited_content == nullptr) {
+ return;
+ }
+
+ cancel_upload_message_content_files(m->edited_content.get());
+
+ m->edited_content = nullptr;
+ m->edited_reply_markup = nullptr;
+ m->edit_generation = 0;
+ m->edit_promise.set_error(Status::Error(400, error_message));
+}
+
+void MessagesManager::on_message_media_edited(DialogId dialog_id, MessageId message_id, FileId file_id,
+ FileId thumbnail_file_id, bool was_uploaded, bool was_thumbnail_uploaded,
+ string file_reference, int32 schedule_date, uint64 generation,
+ Result<int32> &&result) {
+ // must not run getDifference
+
+ CHECK(message_id.is_any_server());
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ auto m = get_message(d, message_id);
+ if (m == nullptr || m->edit_generation != generation) {
+ // message is already deleted or was edited again
+ return;
+ }
+
+ CHECK(m->edited_content != nullptr);
+ if (result.is_ok()) {
+ // message content has already been replaced from updateEdit{Channel,}Message
+ // need only merge files from edited_content with their uploaded counterparts
+ // updateMessageContent was already sent and needs to be sent again,
+ // only if 'i' and 't' sizes from edited_content was added to the photo
+ auto pts = result.ok();
+ LOG(INFO) << "Successfully edited " << message_id << " in " << dialog_id << " with pts = " << pts
+ << " and last edit pts = " << m->last_edit_pts;
+ std::swap(m->content, m->edited_content);
+ bool need_send_update_message_content = m->edited_content->get_type() == MessageContentType::Photo &&
+ m->content->get_type() == MessageContentType::Photo;
+ bool need_merge_files = pts != 0 && pts == m->last_edit_pts;
+ bool is_content_changed = false;
+ bool need_update =
+ update_message_content(dialog_id, m, std::move(m->edited_content), need_merge_files, true, is_content_changed);
+ if (need_send_update_message_content) {
+ if (need_update) {
+ send_update_message_content(d, m, true, "on_message_media_edited");
+ }
+ if (is_content_changed || need_update) {
+ on_message_changed(d, m, need_update, "on_message_media_edited");
+ }
+ }
+ } else {
+ LOG(INFO) << "Failed to edit " << message_id << " in " << dialog_id << ": " << result.error();
+ if (was_uploaded) {
+ if (was_thumbnail_uploaded) {
+ CHECK(thumbnail_file_id.is_valid());
+ // always delete partial remote location for the thumbnail, because it can't be reused anyway
+ td_->file_manager_->delete_partial_remote_location(thumbnail_file_id);
+ }
+ CHECK(file_id.is_valid());
+ auto error_message = result.error().message();
+ if (begins_with(error_message, "FILE_PART_") && ends_with(error_message, "_MISSING")) {
+ do_send_message(dialog_id, m, {to_integer<int32>(error_message.substr(10))});
+ return;
+ }
+
+ if (result.error().code() != 429 && result.error().code() < 500 && !G()->close_flag()) {
+ td_->file_manager_->delete_partial_remote_location(file_id);
+ }
+ } else if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(result.error())) {
+ if (file_id.is_valid()) {
+ VLOG(file_references) << "Receive " << result.error() << " for " << file_id;
+ td_->file_manager_->delete_file_reference(file_id, file_reference);
+ do_send_message(dialog_id, m, {-1});
+ return;
+ } else {
+ LOG(ERROR) << "Receive file reference error, but have no file_id";
+ }
+ }
+
+ cancel_upload_message_content_files(m->edited_content.get());
+
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ get_message_from_server({dialog_id, m->message_id}, Auto(), "on_message_media_edited");
+ }
+ }
+
+ if (m->edited_schedule_date == schedule_date) {
+ m->edited_schedule_date = 0;
+ }
+ m->edited_content = nullptr;
+ m->edited_reply_markup = nullptr;
+ m->edit_generation = 0;
+ if (result.is_ok()) {
+ m->edit_promise.set_value(Unit());
+ } else {
+ m->edit_promise.set_error(result.move_as_error());
+ }
+}
+
+void MessagesManager::edit_message_media(FullMessageId full_message_id,
+ tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
+ Promise<Unit> &&promise) {
+ if (input_message_content == nullptr) {
+ return promise.set_error(Status::Error(400, "Can't edit message without new content"));
+ }
+ int32 new_message_content_type = input_message_content->get_id();
+ if (new_message_content_type != td_api::inputMessageAnimation::ID &&
+ new_message_content_type != td_api::inputMessageAudio::ID &&
+ new_message_content_type != td_api::inputMessageDocument::ID &&
+ new_message_content_type != td_api::inputMessagePhoto::ID &&
+ new_message_content_type != td_api::inputMessageVideo::ID) {
+ return promise.set_error(Status::Error(400, "Unsupported input message content type"));
+ }
+
+ LOG(INFO) << "Begin to edit media of " << full_message_id;
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "edit_message_media");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (!have_input_peer(dialog_id, AccessRights::Edit)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_media");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ if (!can_edit_message(dialog_id, m, true)) {
+ return promise.set_error(Status::Error(400, "Message can't be edited"));
+ }
+ CHECK(m->message_id.is_any_server());
+
+ MessageContentType old_message_content_type = m->content->get_type();
+ if (old_message_content_type != MessageContentType::Animation &&
+ old_message_content_type != MessageContentType::Audio &&
+ old_message_content_type != MessageContentType::Document &&
+ old_message_content_type != MessageContentType::Photo && old_message_content_type != MessageContentType::Video) {
+ return promise.set_error(Status::Error(400, "There is no media in the message to edit"));
+ }
+ if (m->ttl > 0) {
+ return promise.set_error(Status::Error(400, "Can't edit media in self-destructing message"));
+ }
+
+ auto r_input_message_content = process_input_message_content(dialog_id, std::move(input_message_content));
+ if (r_input_message_content.is_error()) {
+ return promise.set_error(r_input_message_content.move_as_error());
+ }
+ InputMessageContent content = r_input_message_content.move_as_ok();
+ if (content.ttl > 0) {
+ return promise.set_error(Status::Error(400, "Can't enable self-destruction for media"));
+ }
+
+ if (m->media_album_id != 0) {
+ auto new_content_type = content.content->get_type();
+ if (old_message_content_type != new_content_type) {
+ if (!is_allowed_media_group_content(new_content_type)) {
+ return promise.set_error(Status::Error(400, "Message content type can't be used in an album"));
+ }
+ if (is_homogenous_media_group_content(old_message_content_type) ||
+ is_homogenous_media_group_content(new_content_type)) {
+ return promise.set_error(Status::Error(400, "Can't change media type in the album"));
+ }
+ }
+ }
+
+ auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
+ has_message_sender_user_id(dialog_id, m));
+ if (r_new_reply_markup.is_error()) {
+ return promise.set_error(r_new_reply_markup.move_as_error());
+ }
+
+ cancel_edit_message_media(dialog_id, m, "Canceled by new editMessageMedia request");
+
+ m->edited_content =
+ dup_message_content(td_, dialog_id, content.content.get(), MessageContentDupType::Send, MessageCopyOptions());
+ CHECK(m->edited_content != nullptr);
+ m->edited_reply_markup = r_new_reply_markup.move_as_ok();
+ m->edit_generation = ++current_message_edit_generation_;
+ m->edit_promise = std::move(promise);
+
+ do_send_message(dialog_id, m);
}
void MessagesManager::edit_message_caption(FullMessageId full_message_id,
@@ -17054,84 +28093,84 @@ void MessagesManager::edit_message_caption(FullMessageId full_message_id,
LOG(INFO) << "Begin to edit caption of " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "edit_message_caption");
if (d == nullptr) {
- return promise.set_error(Status::Error(5, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
- return promise.set_error(Status::Error(5, "Can't access the chat"));
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
}
- auto message_id = full_message_id.get_message_id();
- const Message *message = get_message_force(d, message_id);
- if (message == nullptr) {
- return promise.set_error(Status::Error(5, "Message not found"));
+ const Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_caption");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
}
- if (!can_edit_message(dialog_id, message, true)) {
- return promise.set_error(Status::Error(5, "Message can't be edited"));
+ if (!can_edit_message(dialog_id, m, true)) {
+ return promise.set_error(Status::Error(400, "Message can't be edited"));
}
- if (!can_have_message_content_caption(message->content->get_id())) {
+ if (!can_have_message_content_caption(m->content->get_type())) {
return promise.set_error(Status::Error(400, "There is no caption in the message to edit"));
}
- auto r_caption = process_input_caption(dialog_id, std::move(input_caption), td_->auth_manager_->is_bot());
+ auto r_caption =
+ get_formatted_text(td_, dialog_id, std::move(input_caption), td_->auth_manager_->is_bot(), true, false, false);
if (r_caption.is_error()) {
return promise.set_error(r_caption.move_as_error());
}
auto caption = r_caption.move_as_ok();
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
- !is_broadcast_channel(dialog_id));
+ has_message_sender_user_id(dialog_id, m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
- auto input_reply_markup = get_input_reply_markup(r_new_reply_markup.ok());
+ auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok());
- send_closure(td_->create_net_actor<EditMessageActor>(std::move(promise)), &EditMessageActor::send, 1 << 11, dialog_id,
- message_id, caption.text, get_input_message_entities(td_->contacts_manager_.get(), caption.entities),
- nullptr, std::move(input_reply_markup), get_sequence_dispatcher_id(dialog_id, -1));
+ td_->create_handler<EditMessageQuery>(std::move(promise))
+ ->send(1 << 11, dialog_id, m->message_id, caption.text,
+ get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_message_caption"),
+ nullptr, std::move(input_reply_markup), get_message_schedule_date(m));
}
void MessagesManager::edit_message_reply_markup(FullMessageId full_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise) {
if (!td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(3, "Method is available only for bots"));
+ return promise.set_error(Status::Error(400, "Method is available only for bots"));
}
LOG(INFO) << "Begin to edit reply markup of " << full_message_id;
auto dialog_id = full_message_id.get_dialog_id();
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "edit_message_reply_markup");
if (d == nullptr) {
- return promise.set_error(Status::Error(5, "Chat not found"));
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
if (!have_input_peer(dialog_id, AccessRights::Edit)) {
- return promise.set_error(Status::Error(5, "Can't access the chat"));
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
}
- auto message_id = full_message_id.get_message_id();
- const Message *message = get_message_force(d, message_id);
- if (message == nullptr) {
- return promise.set_error(Status::Error(5, "Message not found"));
+ const Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_reply_markup");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
}
- if (!can_edit_message(dialog_id, message, true, true)) {
- return promise.set_error(Status::Error(5, "Message can't be edited"));
+ if (!can_edit_message(dialog_id, m, true, true)) {
+ return promise.set_error(Status::Error(400, "Message can't be edited"));
}
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
- !is_broadcast_channel(dialog_id));
+ has_message_sender_user_id(dialog_id, m));
if (r_new_reply_markup.is_error()) {
return promise.set_error(r_new_reply_markup.move_as_error());
}
- auto input_reply_markup = get_input_reply_markup(r_new_reply_markup.ok());
- send_closure(td_->create_net_actor<EditMessageActor>(std::move(promise)), &EditMessageActor::send, 0, dialog_id,
- message_id, string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(), nullptr,
- std::move(input_reply_markup), get_sequence_dispatcher_id(dialog_id, -1));
+ auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok());
+ td_->create_handler<EditMessageQuery>(std::move(promise))
+ ->send(0, dialog_id, m->message_id, string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(), nullptr,
+ std::move(input_reply_markup), get_message_schedule_date(m));
}
void MessagesManager::edit_inline_message_text(const string &inline_message_id,
@@ -17139,19 +28178,19 @@ void MessagesManager::edit_inline_message_text(const string &inline_message_id,
tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
Promise<Unit> &&promise) {
if (!td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(3, "Method is available only for bots"));
+ return promise.set_error(Status::Error(400, "Method is available only for bots"));
}
if (input_message_content == nullptr) {
- return promise.set_error(Status::Error(5, "Can't edit message without new content"));
+ return promise.set_error(Status::Error(400, "Can't edit message without new content"));
}
int32 new_message_content_type = input_message_content->get_id();
if (new_message_content_type != td_api::inputMessageText::ID) {
- return promise.set_error(Status::Error(5, "Input message content type must be InputMessageText"));
+ return promise.set_error(Status::Error(400, "Input message content type must be InputMessageText"));
}
auto r_input_message_text =
- process_input_message_text(DialogId(), std::move(input_message_content), td_->auth_manager_->is_bot());
+ process_input_message_text(td_, DialogId(), std::move(input_message_content), td_->auth_manager_->is_bot());
if (r_input_message_text.is_error()) {
return promise.set_error(r_input_message_text.move_as_error());
}
@@ -17164,7 +28203,7 @@ void MessagesManager::edit_inline_message_text(const string &inline_message_id,
auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
if (input_bot_inline_message_id == nullptr) {
- return promise.set_error(Status::Error(400, "Wrong inline message identifier specified"));
+ return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
}
int32 flags = 0;
@@ -17173,16 +28212,17 @@ void MessagesManager::edit_inline_message_text(const string &inline_message_id,
}
td_->create_handler<EditInlineMessageQuery>(std::move(promise))
->send(flags, std::move(input_bot_inline_message_id), input_message_text.text.text,
- get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities), nullptr,
- get_input_reply_markup(r_new_reply_markup.ok()));
+ get_input_message_entities(td_->contacts_manager_.get(), input_message_text.text.entities,
+ "edit_inline_message_text"),
+ nullptr, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
}
void MessagesManager::edit_inline_message_live_location(const string &inline_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
- tl_object_ptr<td_api::location> &&input_location,
- Promise<Unit> &&promise) {
+ tl_object_ptr<td_api::location> &&input_location, int32 heading,
+ int32 proximity_alert_radius, Promise<Unit> &&promise) {
if (!td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(3, "Method is available only for bots"));
+ return promise.set_error(Status::Error(400, "Method is available only for bots"));
}
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true);
@@ -17192,22 +28232,79 @@ void MessagesManager::edit_inline_message_live_location(const string &inline_mes
auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
if (input_bot_inline_message_id == nullptr) {
- return promise.set_error(Status::Error(400, "Wrong inline message identifier specified"));
+ return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
}
Location location(input_location);
if (location.empty() && input_location != nullptr) {
- return promise.set_error(Status::Error(400, "Wrong location specified"));
+ return promise.set_error(Status::Error(400, "Invalid location specified"));
}
int32 flags = 0;
if (location.empty()) {
- flags |= telegram_api::messages_editMessage::STOP_GEO_LIVE_MASK;
+ flags |= telegram_api::inputMediaGeoLive::STOPPED_MASK;
+ }
+ if (heading != 0) {
+ flags |= telegram_api::inputMediaGeoLive::HEADING_MASK;
+ }
+ flags |= telegram_api::inputMediaGeoLive::PROXIMITY_NOTIFICATION_RADIUS_MASK;
+ auto input_media = telegram_api::make_object<telegram_api::inputMediaGeoLive>(
+ flags, false /*ignored*/, location.get_input_geo_point(), heading, 0, proximity_alert_radius);
+ td_->create_handler<EditInlineMessageQuery>(std::move(promise))
+ ->send(0, std::move(input_bot_inline_message_id), "", vector<tl_object_ptr<telegram_api::MessageEntity>>(),
+ std::move(input_media), get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
+}
+
+void MessagesManager::edit_inline_message_media(const string &inline_message_id,
+ tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
+ Promise<Unit> &&promise) {
+ if (!td_->auth_manager_->is_bot()) {
+ return promise.set_error(Status::Error(400, "Method is available only for bots"));
}
+
+ if (input_message_content == nullptr) {
+ return promise.set_error(Status::Error(400, "Can't edit message without new content"));
+ }
+ int32 new_message_content_type = input_message_content->get_id();
+ if (new_message_content_type != td_api::inputMessageAnimation::ID &&
+ new_message_content_type != td_api::inputMessageAudio::ID &&
+ new_message_content_type != td_api::inputMessageDocument::ID &&
+ new_message_content_type != td_api::inputMessagePhoto::ID &&
+ new_message_content_type != td_api::inputMessageVideo::ID) {
+ return promise.set_error(Status::Error(400, "Unsupported input message content type"));
+ }
+
+ auto r_input_message_content = process_input_message_content(DialogId(), std::move(input_message_content));
+ if (r_input_message_content.is_error()) {
+ return promise.set_error(r_input_message_content.move_as_error());
+ }
+ InputMessageContent content = r_input_message_content.move_as_ok();
+ if (content.ttl > 0) {
+ LOG(ERROR) << "Have message content with TTL " << content.ttl;
+ return promise.set_error(Status::Error(400, "Can't enable self-destruction for media"));
+ }
+
+ auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true);
+ if (r_new_reply_markup.is_error()) {
+ return promise.set_error(r_new_reply_markup.move_as_error());
+ }
+
+ auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
+ if (input_bot_inline_message_id == nullptr) {
+ return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
+ }
+
+ auto input_media = get_input_media(content.content.get(), td_, 0, string(), true);
+ if (input_media == nullptr) {
+ return promise.set_error(Status::Error(400, "Invalid message content specified"));
+ }
+
+ const FormattedText *caption = get_message_content_caption(content.content.get());
td_->create_handler<EditInlineMessageQuery>(std::move(promise))
- ->send(flags, std::move(input_bot_inline_message_id), "", vector<tl_object_ptr<telegram_api::MessageEntity>>(),
- location.empty() ? nullptr : location.get_input_geo_point(),
- get_input_reply_markup(r_new_reply_markup.ok()));
+ ->send(1 << 11, std::move(input_bot_inline_message_id), caption == nullptr ? "" : caption->text,
+ get_input_message_entities(td_->contacts_manager_.get(), caption, "edit_inline_message_media"),
+ std::move(input_media), get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
}
void MessagesManager::edit_inline_message_caption(const string &inline_message_id,
@@ -17215,10 +28312,11 @@ void MessagesManager::edit_inline_message_caption(const string &inline_message_i
tl_object_ptr<td_api::formattedText> &&input_caption,
Promise<Unit> &&promise) {
if (!td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(3, "Method is available only for bots"));
+ return promise.set_error(Status::Error(400, "Method is available only for bots"));
}
- auto r_caption = process_input_caption(DialogId(), std::move(input_caption), td_->auth_manager_->is_bot());
+ auto r_caption =
+ get_formatted_text(td_, DialogId(), std::move(input_caption), td_->auth_manager_->is_bot(), true, false, false);
if (r_caption.is_error()) {
return promise.set_error(r_caption.move_as_error());
}
@@ -17231,20 +28329,20 @@ void MessagesManager::edit_inline_message_caption(const string &inline_message_i
auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
if (input_bot_inline_message_id == nullptr) {
- return promise.set_error(Status::Error(400, "Wrong inline message identifier specified"));
+ return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
}
td_->create_handler<EditInlineMessageQuery>(std::move(promise))
->send(1 << 11, std::move(input_bot_inline_message_id), caption.text,
- get_input_message_entities(td_->contacts_manager_.get(), caption.entities), nullptr,
- get_input_reply_markup(r_new_reply_markup.ok()));
+ get_input_message_entities(td_->contacts_manager_.get(), caption.entities, "edit_inline_message_caption"),
+ nullptr, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
}
void MessagesManager::edit_inline_message_reply_markup(const string &inline_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
Promise<Unit> &&promise) {
if (!td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(3, "Method is available only for bots"));
+ return promise.set_error(Status::Error(400, "Method is available only for bots"));
}
auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false, true);
@@ -17254,19 +28352,256 @@ void MessagesManager::edit_inline_message_reply_markup(const string &inline_mess
auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
if (input_bot_inline_message_id == nullptr) {
- return promise.set_error(Status::Error(400, "Wrong inline message identifier specified"));
+ return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
}
td_->create_handler<EditInlineMessageQuery>(std::move(promise))
->send(0, std::move(input_bot_inline_message_id), string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(),
- nullptr, get_input_reply_markup(r_new_reply_markup.ok()));
+ nullptr, get_input_reply_markup(td_->contacts_manager_.get(), r_new_reply_markup.ok()));
+}
+
+void MessagesManager::edit_message_scheduling_state(
+ FullMessageId full_message_id, td_api::object_ptr<td_api::MessageSchedulingState> &&scheduling_state,
+ Promise<Unit> &&promise) {
+ auto r_schedule_date = get_message_schedule_date(std::move(scheduling_state));
+ if (r_schedule_date.is_error()) {
+ return promise.set_error(r_schedule_date.move_as_error());
+ }
+ auto schedule_date = r_schedule_date.move_as_ok();
+
+ LOG(INFO) << "Begin to reschedule " << full_message_id << " to " << schedule_date;
+
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog_force(dialog_id, "edit_message_scheduling_state");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ if (!have_input_peer(dialog_id, AccessRights::Edit)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ Message *m = get_message_force(d, full_message_id.get_message_id(), "edit_message_scheduling_state");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+
+ if (!m->message_id.is_scheduled()) {
+ return promise.set_error(Status::Error(400, "Message is not scheduled"));
+ }
+ if (!m->message_id.is_scheduled_server()) {
+ return promise.set_error(Status::Error(400, "Can't reschedule the message"));
+ }
+
+ if (get_message_schedule_date(m) == schedule_date) {
+ return promise.set_value(Unit());
+ }
+ m->edited_schedule_date = schedule_date;
+
+ if (schedule_date > 0) {
+ td_->create_handler<EditMessageQuery>(std::move(promise))
+ ->send(0, dialog_id, m->message_id, string(), vector<tl_object_ptr<telegram_api::MessageEntity>>(), nullptr,
+ nullptr, schedule_date);
+ } else {
+ td_->create_handler<SendScheduledMessageQuery>(std::move(promise))->send(dialog_id, m->message_id);
+ }
+}
+
+bool MessagesManager::is_discussion_message(DialogId dialog_id, const Message *m) const {
+ if (m == nullptr || m->forward_info == nullptr) {
+ return false;
+ }
+ if (m->sender_user_id.is_valid()) {
+ if (!td_->auth_manager_->is_bot() || m->sender_user_id != ContactsManager::get_service_notifications_user_id()) {
+ return false;
+ }
+ }
+ if (!m->forward_info->from_dialog_id.is_valid() || !m->forward_info->from_message_id.is_valid()) {
+ return false;
+ }
+ if (dialog_id.get_type() != DialogType::Channel || is_broadcast_channel(dialog_id)) {
+ return false;
+ }
+ if (m->forward_info->from_dialog_id == dialog_id) {
+ return false;
+ }
+ if (m->forward_info->from_dialog_id.get_type() != DialogType::Channel) {
+ return false;
+ }
+ return true;
+}
+
+bool MessagesManager::has_message_sender_user_id(DialogId dialog_id, const Message *m) const {
+ if (!m->sender_user_id.is_valid()) {
+ return false;
+ }
+ if (td_->auth_manager_->is_bot() && is_discussion_message(dialog_id, m)) {
+ return false;
+ }
+ return true;
+}
+
+int32 MessagesManager::get_message_own_max_media_timestamp(const Message *m) const {
+ auto duration = get_message_content_media_duration(m->content.get(), td_);
+ return duration == 0 ? std::numeric_limits<int32>::max() : duration;
+}
+
+int32 MessagesManager::get_message_max_media_timestamp(const Message *m) {
+ return m->max_own_media_timestamp >= 0 ? m->max_own_media_timestamp : m->max_reply_media_timestamp;
+}
+
+void MessagesManager::update_message_max_reply_media_timestamp(const Dialog *d, Message *m,
+ bool need_send_update_message_content) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto new_max_reply_media_timestamp = -1;
+ if (m->reply_to_message_id.is_valid() && !m->reply_to_message_id.is_yet_unsent()) {
+ auto replied_m = get_message(d, m->reply_to_message_id);
+ if (replied_m != nullptr) {
+ new_max_reply_media_timestamp = get_message_own_max_media_timestamp(replied_m);
+ } else if (!is_deleted_message(d, m->reply_to_message_id) &&
+ m->reply_to_message_id > d->last_clear_history_message_id &&
+ m->reply_to_message_id > d->max_unavailable_message_id) {
+ // replied message isn't deleted and isn't loaded yet
+ return;
+ }
+ }
+
+ if (m->max_reply_media_timestamp == new_max_reply_media_timestamp) {
+ return;
+ }
+
+ LOG(INFO) << "Set max_reply_media_timestamp in " << m->message_id << " in " << d->dialog_id << " to "
+ << new_max_reply_media_timestamp;
+ auto old_max_media_timestamp = get_message_max_media_timestamp(m);
+ m->max_reply_media_timestamp = new_max_reply_media_timestamp;
+ auto new_max_media_timestamp = get_message_max_media_timestamp(m);
+ if (need_send_update_message_content && old_max_media_timestamp != new_max_media_timestamp) {
+ if (old_max_media_timestamp > new_max_media_timestamp) {
+ std::swap(old_max_media_timestamp, new_max_media_timestamp);
+ }
+
+ if (has_media_timestamps(get_message_content_text(m->content.get()), old_max_media_timestamp + 1,
+ new_max_media_timestamp)) {
+ send_update_message_content_impl(d->dialog_id, m, "update_message_max_reply_media_timestamp");
+ }
+ }
+}
+
+void MessagesManager::update_message_max_own_media_timestamp(const Dialog *d, Message *m) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto new_max_own_media_timestamp = get_message_own_max_media_timestamp(m);
+ if (m->max_own_media_timestamp == new_max_own_media_timestamp) {
+ return;
+ }
+
+ LOG(INFO) << "Set max_own_media_timestamp in " << m->message_id << " in " << d->dialog_id << " to "
+ << new_max_own_media_timestamp;
+ m->max_own_media_timestamp = new_max_own_media_timestamp;
+
+ update_message_max_reply_media_timestamp_in_replied_messages(d->dialog_id, m->message_id);
+}
+
+void MessagesManager::update_message_max_reply_media_timestamp_in_replied_messages(DialogId dialog_id,
+ MessageId reply_to_message_id) {
+ if (reply_to_message_id.is_scheduled()) {
+ return;
+ }
+ CHECK(reply_to_message_id.is_valid());
+ if (reply_to_message_id.is_yet_unsent()) {
+ return;
+ }
+
+ FullMessageId full_message_id{dialog_id, reply_to_message_id};
+ auto it = replied_by_media_timestamp_messages_.find(full_message_id);
+ if (it == replied_by_media_timestamp_messages_.end()) {
+ return;
+ }
+
+ LOG(INFO) << "Update max_reply_media_timestamp for replies of " << reply_to_message_id << " in " << dialog_id;
+
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ for (auto message_id : it->second) {
+ auto m = get_message(d, message_id);
+ CHECK(m != nullptr);
+ CHECK(m->reply_to_message_id == reply_to_message_id);
+ update_message_max_reply_media_timestamp(d, m, true);
+ }
+}
+
+void MessagesManager::register_message_reply(DialogId dialog_id, const Message *m) {
+ if (!m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_yet_unsent() || td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (has_media_timestamps(get_message_content_text(m->content.get()), 0, std::numeric_limits<int32>::max())) {
+ LOG(INFO) << "Register " << m->message_id << " in " << dialog_id << " as reply to " << m->reply_to_message_id;
+ FullMessageId full_message_id{dialog_id, m->reply_to_message_id};
+ bool is_inserted = replied_by_media_timestamp_messages_[full_message_id].insert(m->message_id).second;
+ CHECK(is_inserted);
+ }
+}
+
+void MessagesManager::reregister_message_reply(DialogId dialog_id, const Message *m) {
+ if (!m->reply_to_message_id.is_valid() || m->reply_to_message_id.is_yet_unsent() || td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto it = replied_by_media_timestamp_messages_.find({dialog_id, m->reply_to_message_id});
+ bool was_registered = it != replied_by_media_timestamp_messages_.end() && it->second.count(m->message_id) > 0;
+ bool need_register =
+ has_media_timestamps(get_message_content_text(m->content.get()), 0, std::numeric_limits<int32>::max());
+ if (was_registered == need_register) {
+ return;
+ }
+ if (was_registered) {
+ unregister_message_reply(dialog_id, m);
+ } else {
+ register_message_reply(dialog_id, m);
+ }
+}
+
+void MessagesManager::unregister_message_reply(DialogId dialog_id, const Message *m) {
+ auto it = replied_by_media_timestamp_messages_.find({dialog_id, m->reply_to_message_id});
+ if (it == replied_by_media_timestamp_messages_.end()) {
+ return;
+ }
+
+ auto is_deleted = it->second.erase(m->message_id) > 0;
+ if (is_deleted) {
+ LOG(INFO) << "Unregister " << m->message_id << " in " << dialog_id << " as reply to " << m->reply_to_message_id;
+ if (it->second.empty()) {
+ replied_by_media_timestamp_messages_.erase(it);
+ }
+ }
+}
+
+bool MessagesManager::get_message_disable_web_page_preview(const Message *m) {
+ if (m->content->get_type() != MessageContentType::Text) {
+ return false;
+ }
+ if (has_message_content_web_page(m->content.get())) {
+ return false;
+ }
+ return m->disable_web_page_preview;
}
int32 MessagesManager::get_message_flags(const Message *m) {
int32 flags = 0;
if (m->reply_to_message_id.is_valid()) {
+ CHECK(m->reply_to_message_id.is_server());
flags |= SEND_MESSAGE_FLAG_IS_REPLY;
}
+ if (m->top_thread_message_id.is_valid()) {
+ CHECK(m->top_thread_message_id.is_server());
+ flags |= SEND_MESSAGE_FLAG_IS_FROM_THREAD;
+ }
if (m->disable_web_page_preview) {
flags |= SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW;
}
@@ -17282,20 +28617,46 @@ int32 MessagesManager::get_message_flags(const Message *m) {
if (m->clear_draft) {
flags |= SEND_MESSAGE_FLAG_CLEAR_DRAFT;
}
+ if (m->message_id.is_scheduled()) {
+ flags |= SEND_MESSAGE_FLAG_HAS_SCHEDULE_DATE;
+ }
+ if (m->noforwards) {
+ flags |= SEND_MESSAGE_FLAG_NOFORWARDS;
+ }
+ if (m->update_stickersets_order) {
+ flags |= SEND_MESSAGE_FLAG_UPDATE_STICKER_SETS_ORDER;
+ }
return flags;
}
+tl_object_ptr<telegram_api::InputPeer> MessagesManager::get_send_message_as_input_peer(const Message *m) const {
+ if (!m->has_explicit_sender) {
+ return nullptr;
+ }
+ return get_input_peer(get_message_sender(m), AccessRights::Write);
+}
+
+bool MessagesManager::can_set_game_score(FullMessageId full_message_id) const {
+ return can_set_game_score(full_message_id.get_dialog_id(), get_message(full_message_id));
+}
+
bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) const {
if (m == nullptr) {
return false;
}
+ if (m->content->get_type() != MessageContentType::Game) {
+ return false;
+ }
+ if (m->message_id.is_scheduled()) {
+ return false;
+ }
if (m->message_id.is_yet_unsent()) {
return false;
}
if (m->message_id.is_local()) {
return false;
}
- if (m->via_bot_user_id.is_valid() && !m->is_outgoing) {
+ if (m->via_bot_user_id.is_valid() && m->via_bot_user_id != td_->contacts_manager_->get_my_id()) {
return false;
}
@@ -17307,10 +28668,9 @@ bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) c
return false;
}
- DialogId my_dialog_id(td_->contacts_manager_->get_my_id("can_set_game_score"));
switch (dialog_id.get_type()) {
case DialogType::User:
- if (!m->is_outgoing && dialog_id != my_dialog_id) {
+ if (!m->is_outgoing && dialog_id != get_my_dialog_id()) {
return false;
}
break;
@@ -17325,9 +28685,9 @@ bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) c
break;
}
auto channel_id = dialog_id.get_channel_id();
- auto channel_status = td_->contacts_manager_->get_channel_status(channel_id);
+ auto channel_status = td_->contacts_manager_->get_channel_permissions(channel_id);
if (m->is_channel_post) {
- if (!channel_status.can_edit_messages() && (!channel_status.can_post_messages() || !m->is_outgoing)) {
+ if (!channel_status.can_edit_messages() && !(channel_status.can_post_messages() && m->is_outgoing)) {
return false;
}
} else {
@@ -17345,187 +28705,24 @@ bool MessagesManager::can_set_game_score(DialogId dialog_id, const Message *m) c
return false;
}
- return m->content->get_id() == MessageGame::ID;
-}
-
-void MessagesManager::set_game_score(FullMessageId full_message_id, bool edit_message, UserId user_id, int32 score,
- bool force, Promise<Unit> &&promise) {
- if (!td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(3, "Method is available only for bots"));
- }
-
- LOG(INFO) << "Begin to set game score of " << user_id << " in " << full_message_id;
- auto dialog_id = full_message_id.get_dialog_id();
- Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr) {
- return promise.set_error(Status::Error(5, "Chat not found"));
- }
-
- if (!have_input_peer(dialog_id, AccessRights::Edit)) {
- return promise.set_error(Status::Error(5, "Can't access the chat"));
- }
-
- auto message_id = full_message_id.get_message_id();
- const Message *message = get_message_force(d, message_id);
- if (message == nullptr) {
- return promise.set_error(Status::Error(5, "Message not found"));
- }
-
- auto input_user = td_->contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(400, "Wrong user identifier specified"));
- }
-
- if (!can_set_game_score(dialog_id, message)) {
- return promise.set_error(Status::Error(5, "Game score can't be set"));
- }
-
- send_closure(td_->create_net_actor<SetGameScoreActor>(std::move(promise)), &SetGameScoreActor::send, dialog_id,
- message_id, edit_message, std::move(input_user), score, force,
- get_sequence_dispatcher_id(dialog_id, -1));
-}
-
-void MessagesManager::set_inline_game_score(const string &inline_message_id, bool edit_message, UserId user_id,
- int32 score, bool force, Promise<Unit> &&promise) {
- if (!td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(3, "Method is available only for bots"));
- }
-
- auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
- if (input_bot_inline_message_id == nullptr) {
- return promise.set_error(Status::Error(400, "Wrong inline message identifier specified"));
- }
-
- auto input_user = td_->contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(400, "Wrong user identifier specified"));
- }
-
- td_->create_handler<SetInlineGameScoreQuery>(std::move(promise))
- ->send(std::move(input_bot_inline_message_id), edit_message, std::move(input_user), score, force);
-}
-
-int64 MessagesManager::get_game_high_scores(FullMessageId full_message_id, UserId user_id, Promise<Unit> &&promise) {
- if (!td_->auth_manager_->is_bot()) {
- promise.set_error(Status::Error(3, "Method is available only for bots"));
- return 0;
- }
-
- LOG(INFO) << "Begin to get game high scores of " << user_id << " in " << full_message_id;
- auto dialog_id = full_message_id.get_dialog_id();
- Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr) {
- promise.set_error(Status::Error(5, "Chat not found"));
- return 0;
- }
-
- if (!have_input_peer(dialog_id, AccessRights::Read)) {
- promise.set_error(Status::Error(5, "Can't access the chat"));
- return 0;
- }
-
- auto message_id = full_message_id.get_message_id();
- const Message *message = get_message_force(d, message_id);
- if (message == nullptr) {
- promise.set_error(Status::Error(5, "Message not found"));
- return 0;
- }
- if (!message_id.is_server()) {
- promise.set_error(Status::Error(5, "Wrong message identifier specified"));
- return 0;
- }
-
- auto input_user = td_->contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- promise.set_error(Status::Error(400, "Wrong user identifier specified"));
- return 0;
- }
-
- int64 random_id = 0;
- do {
- random_id = Random::secure_int64();
- } while (random_id == 0 || game_high_scores_.find(random_id) != game_high_scores_.end());
- game_high_scores_[random_id]; // reserve place for result
-
- td_->create_handler<GetGameHighScoresQuery>(std::move(promise))
- ->send(dialog_id, message_id, std::move(input_user), random_id);
- return random_id;
-}
-
-int64 MessagesManager::get_inline_game_high_scores(const string &inline_message_id, UserId user_id,
- Promise<Unit> &&promise) {
- if (!td_->auth_manager_->is_bot()) {
- promise.set_error(Status::Error(3, "Method is available only for bots"));
- return 0;
- }
-
- auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
- if (input_bot_inline_message_id == nullptr) {
- promise.set_error(Status::Error(400, "Wrong inline message identifier specified"));
- return 0;
- }
-
- auto input_user = td_->contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- promise.set_error(Status::Error(400, "Wrong user identifier specified"));
- return 0;
- }
-
- int64 random_id = 0;
- do {
- random_id = Random::secure_int64();
- } while (random_id == 0 || game_high_scores_.find(random_id) != game_high_scores_.end());
- game_high_scores_[random_id]; // reserve place for result
-
- td_->create_handler<GetInlineGameHighScoresQuery>(std::move(promise))
- ->send(std::move(input_bot_inline_message_id), std::move(input_user), random_id);
- return random_id;
+ return true;
}
-void MessagesManager::on_get_game_high_scores(int64 random_id,
- tl_object_ptr<telegram_api::messages_highScores> &&high_scores) {
- auto it = game_high_scores_.find(random_id);
- CHECK(it != game_high_scores_.end());
- auto &result = it->second;
- CHECK(result == nullptr);
-
- if (high_scores == nullptr) {
- game_high_scores_.erase(it);
- return;
+bool MessagesManager::is_forward_info_sender_hidden(const MessageForwardInfo *forward_info) {
+ CHECK(forward_info != nullptr);
+ if (forward_info->is_imported) {
+ return false;
}
-
- td_->contacts_manager_->on_get_users(std::move(high_scores->users_));
-
- result = make_tl_object<td_api::gameHighScores>();
-
- for (auto &high_score : high_scores->scores_) {
- int32 position = high_score->pos_;
- if (position <= 0) {
- LOG(ERROR) << "Receive wrong position = " << position;
- continue;
- }
- UserId user_id(high_score->user_id_);
- LOG_IF(ERROR, !td_->contacts_manager_->have_user(user_id)) << "Have no info about " << user_id;
- int32 score = high_score->score_;
- if (score < 0) {
- LOG(ERROR) << "Receive wrong score = " << score;
- continue;
- }
- result->scores_.push_back(make_tl_object<td_api::gameHighScore>(
- position, td_->contacts_manager_->get_user_id_object(user_id, "gameHighScore"), score));
+ if (!forward_info->sender_name.empty()) {
+ return true;
}
-}
-
-tl_object_ptr<td_api::gameHighScores> MessagesManager::get_game_high_scores_object(int64 random_id) {
- auto it = game_high_scores_.find(random_id);
- CHECK(it != game_high_scores_.end());
- auto result = std::move(it->second);
- game_high_scores_.erase(it);
- return result;
+ DialogId hidden_sender_dialog_id(ChannelId(static_cast<int64>(G()->is_test_dc() ? 10460537 : 1228946795)));
+ return forward_info->sender_dialog_id == hidden_sender_dialog_id && !forward_info->author_signature.empty() &&
+ !forward_info->message_id.is_valid();
}
unique_ptr<MessagesManager::MessageForwardInfo> MessagesManager::get_message_forward_info(
- tl_object_ptr<telegram_api::messageFwdHeader> &&forward_header) {
+ tl_object_ptr<telegram_api::messageFwdHeader> &&forward_header, FullMessageId full_message_id) {
if (forward_header == nullptr) {
return nullptr;
}
@@ -17536,36 +28733,29 @@ unique_ptr<MessagesManager::MessageForwardInfo> MessagesManager::get_message_for
}
auto flags = forward_header->flags_;
- UserId sender_user_id;
- ChannelId channel_id;
+ DialogId sender_dialog_id;
MessageId message_id;
- string author_signature;
+ string author_signature = std::move(forward_header->post_author_);
DialogId from_dialog_id;
MessageId from_message_id;
- if ((flags & MESSAGE_FORWARD_HEADER_FLAG_HAS_AUTHOR_ID) != 0) {
- sender_user_id = UserId(forward_header->from_id_);
- if (!sender_user_id.is_valid()) {
- LOG(ERROR) << "Receive invalid sender id in message forward header: " << oneline(to_string(forward_header));
- sender_user_id = UserId();
- }
- }
- if ((flags & MESSAGE_FORWARD_HEADER_FLAG_HAS_CHANNEL_ID) != 0) {
- channel_id = ChannelId(forward_header->channel_id_);
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid channel id in message forward header: " << oneline(to_string(forward_header));
+ string sender_name = std::move(forward_header->from_name_);
+ bool is_imported = forward_header->imported_;
+ if (forward_header->from_id_ != nullptr) {
+ sender_dialog_id = DialogId(forward_header->from_id_);
+ if (!sender_dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid sender identifier in message forward header: "
+ << oneline(to_string(forward_header));
+ sender_dialog_id = DialogId();
}
}
- if ((flags & MESSAGE_FORWARD_HEADER_FLAG_HAS_MESSAGE_ID) != 0) {
+ if ((flags & telegram_api::messageFwdHeader::CHANNEL_POST_MASK) != 0) {
message_id = MessageId(ServerMessageId(forward_header->channel_post_));
if (!message_id.is_valid()) {
LOG(ERROR) << "Receive " << message_id << " in message forward header: " << oneline(to_string(forward_header));
message_id = MessageId();
}
}
- if ((flags & MESSAGE_FORWARD_HEADER_FLAG_HAS_AUTHOR_SIGNATURE) != 0) {
- author_signature = std::move(forward_header->post_author_);
- }
- if ((flags & MESSAGE_FORWARD_HEADER_FLAG_HAS_SAVED_FROM) != 0) {
+ if (forward_header->saved_from_peer_ != nullptr) {
from_dialog_id = DialogId(forward_header->saved_from_peer_);
from_message_id = MessageId(ServerMessageId(forward_header->saved_from_msg_id_));
if (!from_dialog_id.is_valid() || !from_message_id.is_valid()) {
@@ -17576,48 +28766,75 @@ unique_ptr<MessagesManager::MessageForwardInfo> MessagesManager::get_message_for
}
}
- DialogId dialog_id;
- if (!channel_id.is_valid()) {
+ UserId sender_user_id;
+ if (sender_dialog_id.get_type() == DialogType::User) {
+ sender_user_id = sender_dialog_id.get_user_id();
+ sender_dialog_id = DialogId();
+ }
+ if (!sender_dialog_id.is_valid()) {
if (sender_user_id.is_valid()) {
if (message_id.is_valid()) {
- LOG(ERROR) << "Receive non-empty message id in message forward header: " << oneline(to_string(forward_header));
+ LOG(ERROR) << "Receive non-empty message identifier in message forward header: "
+ << oneline(to_string(forward_header));
message_id = MessageId();
}
- } else {
+ } else if (sender_name.empty()) {
LOG(ERROR) << "Receive wrong message forward header: " << oneline(to_string(forward_header));
return nullptr;
}
+ } else if (sender_dialog_id.get_type() != DialogType::Channel) {
+ LOG(ERROR) << "Receive wrong message forward header with non-channel sender: "
+ << oneline(to_string(forward_header));
+ return nullptr;
} else {
- LOG_IF(ERROR, td_->contacts_manager_->have_min_channel(channel_id)) << "Receive forward from min channel";
- dialog_id = DialogId(channel_id);
- force_create_dialog(dialog_id, "message forward info");
- if (sender_user_id.is_valid()) {
- LOG(ERROR) << "Receive valid sender user id in message forward header: " << oneline(to_string(forward_header));
- sender_user_id = UserId();
+ auto channel_id = sender_dialog_id.get_channel_id();
+ if (!td_->contacts_manager_->have_channel(channel_id)) {
+ LOG(ERROR) << "Receive forward from "
+ << (td_->contacts_manager_->have_min_channel(channel_id) ? "min" : "unknown") << ' ' << channel_id
+ << " in " << full_message_id;
}
+ force_create_dialog(sender_dialog_id, "message forward info", true);
+ CHECK(!sender_user_id.is_valid());
}
if (from_dialog_id.is_valid()) {
- force_create_dialog(from_dialog_id, "message forward from info");
+ force_create_dialog(from_dialog_id, "message forward from info", true);
}
- return make_unique<MessageForwardInfo>(sender_user_id, forward_header->date_, dialog_id, message_id, author_signature,
- from_dialog_id, from_message_id);
+ return td::make_unique<MessageForwardInfo>(sender_user_id, forward_header->date_, sender_dialog_id, message_id,
+ std::move(author_signature), std::move(sender_name), from_dialog_id,
+ from_message_id, std::move(forward_header->psa_type_), is_imported);
}
-tl_object_ptr<td_api::MessageForwardInfo> MessagesManager::get_message_forward_info_object(
+td_api::object_ptr<td_api::messageForwardInfo> MessagesManager::get_message_forward_info_object(
const unique_ptr<MessageForwardInfo> &forward_info) const {
if (forward_info == nullptr) {
return nullptr;
}
- if (forward_info->dialog_id.is_valid()) {
- return make_tl_object<td_api::messageForwardedPost>(
- forward_info->dialog_id.get(), forward_info->author_signature, forward_info->date,
- forward_info->message_id.get(), forward_info->from_dialog_id.get(), forward_info->from_message_id.get());
- }
- return make_tl_object<td_api::messageForwardedFromUser>(
- td_->contacts_manager_->get_user_id_object(forward_info->sender_user_id, "messageForwardedFromUser"),
- forward_info->date, forward_info->from_dialog_id.get(), forward_info->from_message_id.get());
+ auto origin = [&]() -> td_api::object_ptr<td_api::MessageForwardOrigin> {
+ if (forward_info->is_imported) {
+ return td_api::make_object<td_api::messageForwardOriginMessageImport>(forward_info->sender_name);
+ }
+ if (is_forward_info_sender_hidden(forward_info.get())) {
+ return td_api::make_object<td_api::messageForwardOriginHiddenUser>(
+ forward_info->sender_name.empty() ? forward_info->author_signature : forward_info->sender_name);
+ }
+ if (forward_info->message_id.is_valid()) {
+ return td_api::make_object<td_api::messageForwardOriginChannel>(
+ forward_info->sender_dialog_id.get(), forward_info->message_id.get(), forward_info->author_signature);
+ }
+ if (forward_info->sender_dialog_id.is_valid()) {
+ return td_api::make_object<td_api::messageForwardOriginChat>(
+ forward_info->sender_dialog_id.get(),
+ forward_info->sender_name.empty() ? forward_info->author_signature : forward_info->sender_name);
+ }
+ return td_api::make_object<td_api::messageForwardOriginUser>(
+ td_->contacts_manager_->get_user_id_object(forward_info->sender_user_id, "messageForwardOriginUser"));
+ }();
+
+ return td_api::make_object<td_api::messageForwardInfo>(std::move(origin), forward_info->date, forward_info->psa_type,
+ forward_info->from_dialog_id.get(),
+ forward_info->from_message_id.get());
}
Result<unique_ptr<ReplyMarkup>> MessagesManager::get_dialog_reply_markup(
@@ -17627,15 +28844,15 @@ Result<unique_ptr<ReplyMarkup>> MessagesManager::get_dialog_reply_markup(
}
auto dialog_type = dialog_id.get_type();
- bool is_broadcast = is_broadcast_channel(dialog_id);
+ bool is_anonymous = is_anonymous_administrator(dialog_id, nullptr);
- bool only_inline_keyboard = is_broadcast;
+ bool only_inline_keyboard = is_anonymous;
bool request_buttons_allowed = dialog_type == DialogType::User;
- bool switch_inline_current_chat_buttons_allowed = !is_broadcast;
+ bool switch_inline_buttons_allowed = !is_anonymous;
TRY_RESULT(reply_markup,
get_reply_markup(std::move(reply_markup_ptr), td_->auth_manager_->is_bot(), only_inline_keyboard,
- request_buttons_allowed, switch_inline_current_chat_buttons_allowed));
+ request_buttons_allowed, switch_inline_buttons_allowed));
if (reply_markup == nullptr) {
return nullptr;
}
@@ -17665,58 +28882,69 @@ class MessagesManager::ForwardMessagesLogEvent {
DialogId from_dialog_id;
vector<MessageId> message_ids;
vector<Message *> messages_in;
+ bool drop_author;
+ bool drop_media_captions;
vector<unique_ptr<Message>> messages_out;
template <class StorerT>
void store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(drop_author);
+ STORE_FLAG(drop_media_captions);
+ END_STORE_FLAGS();
td::store(to_dialog_id, storer);
td::store(from_dialog_id, storer);
td::store(message_ids, storer);
-
- td::store(narrow_cast<int32>(messages_in.size()), storer);
- for (auto m : messages_in) {
- td::store(*m, storer);
- }
+ td::store(messages_in, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
+ if (parser.version() >= static_cast<int32>(Version::UseServerForwardAsCopy)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(drop_author);
+ PARSE_FLAG(drop_media_captions);
+ END_PARSE_FLAGS();
+ } else {
+ drop_author = false;
+ drop_media_captions = false;
+ }
td::parse(to_dialog_id, parser);
td::parse(from_dialog_id, parser);
td::parse(message_ids, parser);
-
- CHECK(messages_out.empty());
- int32 size = parser.fetch_int();
- messages_out.resize(size);
- for (auto &m_out : messages_out) {
- m_out = make_unique<Message>();
- td::parse(*m_out, parser);
- }
+ td::parse(messages_out, parser);
}
};
+uint64 MessagesManager::save_forward_messages_log_event(DialogId to_dialog_id, DialogId from_dialog_id,
+ const vector<Message *> &messages,
+ const vector<MessageId> &message_ids, bool drop_author,
+ bool drop_media_captions) {
+ ForwardMessagesLogEvent log_event{to_dialog_id, from_dialog_id, message_ids, messages,
+ drop_author, drop_media_captions, Auto()};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ForwardMessages,
+ get_log_event_storer(log_event));
+}
+
void MessagesManager::do_forward_messages(DialogId to_dialog_id, DialogId from_dialog_id,
const vector<Message *> &messages, const vector<MessageId> &message_ids,
- int64 logevent_id) {
+ bool drop_author, bool drop_media_captions, uint64 log_event_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
CHECK(messages.size() == message_ids.size());
if (messages.empty()) {
return;
}
- if (logevent_id == 0 && G()->parameters().use_message_db) {
- auto logevent = ForwardMessagesLogEvent{to_dialog_id, from_dialog_id, message_ids, messages, Auto()};
- auto storer = LogEventStorerImpl<ForwardMessagesLogEvent>(logevent);
- logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::ForwardMessages, storer);
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_forward_messages_log_event(to_dialog_id, from_dialog_id, messages, message_ids, drop_author,
+ drop_media_captions);
}
- Promise<> promise;
- if (logevent_id != 0) {
- promise = PromiseCreator::lambda([logevent_id](Result<Unit> result) mutable {
- if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
- }
- });
- }
+ auto schedule_date = get_message_schedule_date(messages[0]);
+ auto as_input_peer = get_send_message_as_input_peer(messages[0]);
int32 flags = 0;
if (messages[0]->disable_notification) {
@@ -17725,274 +28953,670 @@ void MessagesManager::do_forward_messages(DialogId to_dialog_id, DialogId from_d
if (messages[0]->from_background) {
flags |= SEND_MESSAGE_FLAG_FROM_BACKGROUND;
}
- if (messages[0]->media_album_id != 0) {
- flags |= SEND_MESSAGE_FLAG_GROUP_MEDIA;
- }
if (messages[0]->in_game_share) {
flags |= SEND_MESSAGE_FLAG_WITH_MY_SCORE;
}
+ if (schedule_date != 0) {
+ flags |= SEND_MESSAGE_FLAG_HAS_SCHEDULE_DATE;
+ }
+ if (as_input_peer != nullptr) {
+ flags |= SEND_MESSAGE_FLAG_HAS_SEND_AS;
+ }
+ if (messages[0]->noforwards) {
+ flags |= SEND_MESSAGE_FLAG_NOFORWARDS;
+ }
+ if (drop_author) {
+ flags |= telegram_api::messages_forwardMessages::DROP_AUTHOR_MASK;
+ }
+ if (drop_media_captions) {
+ flags |= telegram_api::messages_forwardMessages::DROP_MEDIA_CAPTIONS_MASK;
+ }
vector<int64> random_ids =
transform(messages, [this, to_dialog_id](const Message *m) { return begin_send_message(to_dialog_id, m); });
- send_closure(td_->create_net_actor<ForwardMessagesActor>(std::move(promise)), &ForwardMessagesActor::send, flags,
- to_dialog_id, from_dialog_id, message_ids, std::move(random_ids),
- get_sequence_dispatcher_id(to_dialog_id, -1));
+ send_closure_later(actor_id(this), &MessagesManager::send_forward_message_query, flags, to_dialog_id,
+ messages[0]->top_thread_message_id, from_dialog_id, std::move(as_input_peer), message_ids,
+ std::move(random_ids), schedule_date, get_erase_log_event_promise(log_event_id));
+}
+
+void MessagesManager::send_forward_message_query(int32 flags, DialogId to_dialog_id, MessageId top_thread_message_id,
+ DialogId from_dialog_id,
+ tl_object_ptr<telegram_api::InputPeer> as_input_peer,
+ vector<MessageId> message_ids, vector<int64> random_ids,
+ int32 schedule_date, Promise<Unit> promise) {
+ td_->create_handler<ForwardMessagesQuery>(std::move(promise))
+ ->send(flags, to_dialog_id, top_thread_message_id, from_dialog_id, std::move(as_input_peer), message_ids,
+ std::move(random_ids), schedule_date);
+}
+
+Result<td_api::object_ptr<td_api::message>> MessagesManager::forward_message(
+ DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, MessageId message_id,
+ tl_object_ptr<td_api::messageSendOptions> &&options, bool in_game_share, MessageCopyOptions &&copy_options) {
+ bool need_copy = copy_options.send_copy;
+ vector<MessageCopyOptions> all_copy_options;
+ all_copy_options.push_back(std::move(copy_options));
+ TRY_RESULT(result, forward_messages(to_dialog_id, top_thread_message_id, from_dialog_id, {message_id},
+ std::move(options), in_game_share, std::move(all_copy_options), false));
+ CHECK(result->messages_.size() == 1);
+ if (result->messages_[0] == nullptr) {
+ return Status::Error(400,
+ need_copy ? Slice("The message can't be copied") : Slice("The message can't be forwarded"));
+ }
+ return std::move(result->messages_[0]);
+}
+
+unique_ptr<MessagesManager::MessageForwardInfo> MessagesManager::create_message_forward_info(
+ DialogId from_dialog_id, DialogId to_dialog_id, const Message *forwarded_message) const {
+ auto content_type = forwarded_message->content->get_type();
+ if (content_type == MessageContentType::Game || content_type == MessageContentType::Audio) {
+ return nullptr;
+ }
+
+ auto my_id = td_->contacts_manager_->get_my_id();
+ auto message_id = forwarded_message->message_id;
+
+ DialogId saved_from_dialog_id;
+ MessageId saved_from_message_id;
+ if (to_dialog_id == DialogId(my_id)) {
+ saved_from_dialog_id = from_dialog_id;
+ saved_from_message_id = message_id;
+ }
+
+ if (forwarded_message->forward_info != nullptr) {
+ auto forward_info = make_unique<MessageForwardInfo>(*forwarded_message->forward_info);
+ forward_info->from_dialog_id = saved_from_dialog_id;
+ forward_info->from_message_id = saved_from_message_id;
+ return forward_info;
+ }
+
+ if (from_dialog_id != DialogId(my_id) || content_type == MessageContentType::Dice) {
+ if (forwarded_message->is_channel_post) {
+ if (is_broadcast_channel(from_dialog_id)) {
+ auto author_signature = forwarded_message->sender_user_id.is_valid()
+ ? td_->contacts_manager_->get_user_title(forwarded_message->sender_user_id)
+ : forwarded_message->author_signature;
+ return td::make_unique<MessageForwardInfo>(UserId(), forwarded_message->date, from_dialog_id,
+ forwarded_message->message_id, std::move(author_signature), "",
+ saved_from_dialog_id, saved_from_message_id, "", false);
+ } else {
+ LOG(ERROR) << "Don't know how to forward a channel post not from a channel";
+ }
+ } else if (forwarded_message->sender_user_id.is_valid() || forwarded_message->sender_dialog_id.is_valid()) {
+ return td::make_unique<MessageForwardInfo>(
+ forwarded_message->sender_user_id, forwarded_message->date, forwarded_message->sender_dialog_id, MessageId(),
+ "", forwarded_message->author_signature, saved_from_dialog_id, saved_from_message_id, "", false);
+ } else {
+ LOG(ERROR) << "Don't know how to forward a non-channel post message without forward info and sender";
+ }
+ }
+ return nullptr;
}
-Result<MessageId> MessagesManager::forward_message(DialogId to_dialog_id, DialogId from_dialog_id, MessageId message_id,
- bool disable_notification, bool from_background,
- bool in_game_share) {
- TRY_RESULT(result, forward_messages(to_dialog_id, from_dialog_id, {message_id}, disable_notification, from_background,
- in_game_share, false));
- CHECK(result.size() == 1);
- auto sent_message_id = result[0];
- if (sent_message_id == MessageId()) {
- return Status::Error(11, "Message can't be forwarded");
+void MessagesManager::fix_forwarded_message(Message *m, DialogId to_dialog_id, const Message *forwarded_message,
+ int64 media_album_id, bool drop_author) const {
+ if (m->content->get_type() == MessageContentType::Audio) {
+ drop_author = true;
+ }
+ bool is_game = m->content->get_type() == MessageContentType::Game;
+ if (!drop_author || is_game) {
+ m->via_bot_user_id = forwarded_message->via_bot_user_id;
+ }
+ m->media_album_id = media_album_id;
+ if (!drop_author && forwarded_message->view_count > 0 && m->forward_info != nullptr && m->view_count == 0 &&
+ !(m->message_id.is_scheduled() && is_broadcast_channel(to_dialog_id))) {
+ m->view_count = forwarded_message->view_count;
+ m->forward_count = forwarded_message->forward_count;
+ m->interaction_info_update_date = G()->unix_time();
+ }
+
+ if (m->content->get_type() == MessageContentType::Game) {
+ // via_bot_user_id in games is present unless the message is sent by the bot
+ if (m->via_bot_user_id == UserId()) {
+ // if there is no via_bot_user_id, then the original message was sent by the game owner
+ m->via_bot_user_id = forwarded_message->sender_user_id;
+ }
+ if (m->via_bot_user_id == td_->contacts_manager_->get_my_id()) {
+ // if via_bot_user_id is the current bot user, then there should be
+ m->via_bot_user_id = UserId();
+ }
+ }
+ if (forwarded_message->reply_markup != nullptr &&
+ forwarded_message->reply_markup->type == ReplyMarkup::Type::InlineKeyboard &&
+ to_dialog_id.get_type() != DialogType::SecretChat) {
+ bool need_reply_markup = true;
+ for (auto &row : forwarded_message->reply_markup->inline_keyboard) {
+ for (auto &button : row) {
+ if (button.type == InlineKeyboardButton::Type::Url || button.type == InlineKeyboardButton::Type::UrlAuth) {
+ // ok
+ continue;
+ }
+ if (m->via_bot_user_id.is_valid() && (button.type == InlineKeyboardButton::Type::SwitchInline ||
+ button.type == InlineKeyboardButton::Type::SwitchInlineCurrentDialog)) {
+ // ok
+ continue;
+ }
+
+ need_reply_markup = false;
+ }
+ }
+ if (need_reply_markup) {
+ m->reply_markup = make_unique<ReplyMarkup>(*forwarded_message->reply_markup);
+ for (auto &row : m->reply_markup->inline_keyboard) {
+ for (auto &button : row) {
+ if (button.type == InlineKeyboardButton::Type::SwitchInlineCurrentDialog) {
+ button.type = InlineKeyboardButton::Type::SwitchInline;
+ }
+ if (!button.forward_text.empty()) {
+ button.text = std::move(button.forward_text);
+ button.forward_text.clear();
+ }
+ }
+ }
+ }
}
- return sent_message_id;
}
-Result<vector<MessageId>> MessagesManager::forward_messages(DialogId to_dialog_id, DialogId from_dialog_id,
- vector<MessageId> message_ids, bool disable_notification,
- bool from_background, bool in_game_share, bool as_album) {
+Result<MessagesManager::ForwardedMessages> MessagesManager::get_forwarded_messages(
+ DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id,
+ const vector<MessageId> &message_ids, tl_object_ptr<td_api::messageSendOptions> &&options, bool in_game_share,
+ vector<MessageCopyOptions> &&copy_options) {
+ CHECK(copy_options.size() == message_ids.size());
if (message_ids.size() > 100) { // TODO replace with const from config or implement mass-forward
- return Status::Error(4, "Too much messages to forward");
+ return Status::Error(400, "Too many messages to forward");
}
if (message_ids.empty()) {
- return Status::Error(4, "There is no messages to forward");
+ return Status::Error(400, "There are no messages to forward");
}
- Dialog *from_dialog = get_dialog_force(from_dialog_id);
+ Dialog *from_dialog = get_dialog_force(from_dialog_id, "forward_messages from");
if (from_dialog == nullptr) {
- return Status::Error(5, "Chat to forward messages from not found");
+ return Status::Error(400, "Chat to forward messages from not found");
}
if (!have_input_peer(from_dialog_id, AccessRights::Read)) {
- return Status::Error(5, "Can't access the chat to forward messages from");
+ return Status::Error(400, "Can't access the chat to forward messages from");
}
if (from_dialog_id.get_type() == DialogType::SecretChat) {
- return Status::Error(7, "Can't forward messages from secret chats");
+ return Status::Error(400, "Can't forward messages from secret chats");
+ }
+ if (get_dialog_has_protected_content(from_dialog_id)) {
+ for (const auto &copy_option : copy_options) {
+ if (!copy_option.send_copy || !td_->auth_manager_->is_bot()) {
+ return Status::Error(400, "Message has protected content and can't be forwarded");
+ }
+ }
}
- Dialog *to_dialog = get_dialog_force(to_dialog_id);
+ Dialog *to_dialog = get_dialog_force(to_dialog_id, "forward_messages to");
if (to_dialog == nullptr) {
- return Status::Error(5, "Chat to forward messages to not found");
+ return Status::Error(400, "Chat to forward messages to not found");
}
TRY_STATUS(can_send_message(to_dialog_id));
+ TRY_RESULT(message_send_options, process_message_send_options(to_dialog_id, std::move(options), false));
+ TRY_STATUS(can_use_top_thread_message_id(to_dialog, top_thread_message_id, MessageId()));
- for (auto message_id : message_ids) {
- if (!message_id.is_valid()) {
- return Status::Error(5, "Invalid message identifier");
+ {
+ MessageId last_message_id;
+ for (auto message_id : message_ids) {
+ if (message_id.is_valid_scheduled()) {
+ return Status::Error(400, "Can't forward scheduled messages");
+ }
+ if (message_id.is_scheduled() || !message_id.is_valid()) {
+ return Status::Error(400, "Invalid message identifier");
+ }
+
+ if (message_id <= last_message_id) {
+ return Status::Error(400, "Message identifiers must be in a strictly increasing order");
+ }
+ last_message_id = message_id;
}
}
- int64 media_album_id = 0;
- if (as_album && message_ids.size() > 1 && message_ids.size() <= MAX_GROUPED_MESSAGES) {
- do {
- media_album_id = Random::secure_int64();
- } while (media_album_id >= 0 || pending_message_group_sends_.count(media_album_id) != 0);
+ bool to_secret = to_dialog_id.get_type() == DialogType::SecretChat;
+
+ bool can_use_server_forward = true;
+ for (auto &copy_option : copy_options) {
+ if (!copy_option.is_supported_server_side()) {
+ can_use_server_forward = false;
+ break;
+ }
+ }
+ CHECK(can_use_server_forward || copy_options.size() == 1);
+ if (to_secret) {
+ can_use_server_forward = false;
}
- bool to_secret = to_dialog_id.get_type() == DialogType::SecretChat;
+ ForwardedMessages result;
+ result.to_dialog = to_dialog;
+ result.from_dialog = from_dialog;
+ result.message_send_options = message_send_options;
+ auto &copied_messages = result.copied_messages;
+ auto &forwarded_message_contents = result.forwarded_message_contents;
+ result.drop_author = can_use_server_forward && copy_options[0].send_copy;
+ result.drop_media_captions = can_use_server_forward && copy_options[0].replace_caption;
+
+ std::unordered_map<int64, std::pair<int64, int32>, Hash<int64>> new_copied_media_album_ids;
+ std::unordered_map<int64, std::pair<int64, int32>, Hash<int64>> new_forwarded_media_album_ids;
- vector<MessageId> result(message_ids.size());
- vector<Message *> forwarded_messages;
- vector<MessageId> forwarded_message_ids;
- vector<unique_ptr<MessageContent>> unforwarded_message_contents(message_ids.size());
- vector<bool> unforwarded_message_disable_web_page_previews(message_ids.size());
- auto my_id = td_->contacts_manager_->get_my_id("forward_message");
- bool need_update_dialog_pos = false;
for (size_t i = 0; i < message_ids.size(); i++) {
MessageId message_id = get_persistent_message_id(from_dialog, message_ids[i]);
- const Message *forwarded_message = get_message_force(from_dialog, message_id);
+ const Message *forwarded_message = get_message_force(from_dialog, message_id, "get_forwarded_messages");
if (forwarded_message == nullptr) {
LOG(INFO) << "Can't find " << message_id << " to forward";
continue;
}
+ CHECK(message_id.is_valid());
+ CHECK(message_id == forwarded_message->message_id);
if (!can_forward_message(from_dialog_id, forwarded_message)) {
LOG(INFO) << "Can't forward " << message_id;
continue;
}
- unique_ptr<MessageContent> content = dup_message_content(to_dialog_id, forwarded_message->content.get(), true);
+ bool is_broken_server_copy = [&] {
+ switch (forwarded_message->content->get_type()) {
+ case MessageContentType::Dice:
+ return true;
+ default:
+ return false;
+ }
+ }();
+
+ bool need_copy = !message_id.is_server() || to_secret || copy_options[i].send_copy;
+ bool is_local_copy = need_copy && !(message_id.is_server() && can_use_server_forward && !is_broken_server_copy);
+ if (!(need_copy && td_->auth_manager_->is_bot()) && !can_save_message(from_dialog_id, forwarded_message)) {
+ LOG(INFO) << "Forward of " << message_id << " is restricted";
+ continue;
+ }
+
+ auto type = need_copy ? (is_local_copy ? MessageContentDupType::Copy : MessageContentDupType::ServerCopy)
+ : MessageContentDupType::Forward;
+ auto reply_to_message_id = copy_options[i].reply_to_message_id;
+ auto reply_markup = std::move(copy_options[i].reply_markup);
+ unique_ptr<MessageContent> content =
+ dup_message_content(td_, to_dialog_id, forwarded_message->content.get(), type, std::move(copy_options[i]));
if (content == nullptr) {
- LOG(INFO) << "Can't forward " << message_id;
+ LOG(INFO) << "Can't forward content of " << message_id;
continue;
}
- auto can_send_status = can_send_message_content(to_dialog_id, content.get(), true);
+ reply_to_message_id = get_reply_to_message_id(to_dialog, top_thread_message_id, reply_to_message_id, false);
+
+ auto can_send_status = can_send_message_content(to_dialog_id, content.get(), !is_local_copy, td_);
if (can_send_status.is_error()) {
LOG(INFO) << "Can't forward " << message_id << ": " << can_send_status.message();
continue;
}
- if (!message_id.is_server() || to_secret) {
- unforwarded_message_contents[i] = std::move(content);
- unforwarded_message_disable_web_page_previews[i] = forwarded_message->disable_web_page_preview;
+ auto can_use_options_status = can_use_message_send_options(message_send_options, content, 0);
+ if (can_use_options_status.is_error()) {
+ LOG(INFO) << "Can't forward " << message_id << ": " << can_send_status.message();
continue;
}
- auto content_id = content->get_id();
- if (media_album_id != 0 && !is_allowed_media_group_content(content_id)) {
- media_album_id = 0;
- for (auto m : forwarded_messages) {
- m->media_album_id = 0;
- }
+ if (can_use_top_thread_message_id(to_dialog, top_thread_message_id, reply_to_message_id).is_error()) {
+ LOG(INFO) << "Ignore invalid message thread ID " << top_thread_message_id;
+ top_thread_message_id = MessageId();
}
- bool is_game = content_id == MessageGame::ID;
- unique_ptr<MessageForwardInfo> forward_info;
- if (!is_game && content_id != MessageAudio::ID) {
- DialogId saved_from_dialog_id;
- MessageId saved_from_message_id;
- if (to_dialog_id == DialogId(my_id)) {
- saved_from_dialog_id = from_dialog_id;
- saved_from_message_id = message_id;
+ if (forwarded_message->media_album_id != 0) {
+ auto &new_media_album_id = is_local_copy ? new_copied_media_album_ids[forwarded_message->media_album_id]
+ : new_forwarded_media_album_ids[forwarded_message->media_album_id];
+ new_media_album_id.second++;
+ if (new_media_album_id.second == 2) { // have at least 2 messages in the new album
+ CHECK(new_media_album_id.first == 0);
+ new_media_album_id.first = generate_new_media_album_id();
}
-
- if (forwarded_message->forward_info != nullptr) {
- forward_info = make_unique<MessageForwardInfo>(*forwarded_message->forward_info);
- forward_info->from_dialog_id = saved_from_dialog_id;
- forward_info->from_message_id = saved_from_message_id;
- } else {
- if (from_dialog_id != DialogId(my_id)) {
- if (!forwarded_message->is_channel_post) {
- forward_info =
- make_unique<MessageForwardInfo>(forwarded_message->sender_user_id, forwarded_message->date, DialogId(),
- MessageId(), "", saved_from_dialog_id, saved_from_message_id);
- } else {
- CHECK(from_dialog_id.get_type() == DialogType::Channel);
- MessageId forwarded_message_id = forwarded_message->message_id;
- auto author_signature = forwarded_message->sender_user_id.is_valid()
- ? td_->contacts_manager_->get_user_title(forwarded_message->sender_user_id)
- : forwarded_message->author_signature;
- forward_info = make_unique<MessageForwardInfo>(UserId(), forwarded_message->date, from_dialog_id,
- forwarded_message_id, std::move(author_signature),
- saved_from_dialog_id, saved_from_message_id);
- }
- }
+ if (new_media_album_id.second == MAX_GROUPED_MESSAGES + 1) {
+ CHECK(new_media_album_id.first != 0);
+ new_media_album_id.first = 0; // just in case
}
}
- Message *m = get_message_to_send(to_dialog, MessageId(), disable_notification, from_background, std::move(content),
- &need_update_dialog_pos, std::move(forward_info));
- m->debug_forward_from = from_dialog_id;
- m->via_bot_user_id = forwarded_message->via_bot_user_id;
- m->media_album_id = media_album_id;
- m->in_game_share = in_game_share;
- if (forwarded_message->views > 0) {
- m->views = forwarded_message->views;
+ if (is_local_copy) {
+ copied_messages.push_back({std::move(content), reply_to_message_id, forwarded_message->message_id,
+ forwarded_message->reply_to_message_id, std::move(reply_markup),
+ forwarded_message->media_album_id,
+ get_message_disable_web_page_preview(forwarded_message), i});
+ } else {
+ forwarded_message_contents.push_back({std::move(content), forwarded_message->media_album_id, i});
}
+ }
+ result.top_thread_message_id = top_thread_message_id;
- if (is_game) {
- if (m->via_bot_user_id == my_id) {
- m->via_bot_user_id = UserId();
- } else if (m->via_bot_user_id == UserId()) {
- m->via_bot_user_id = forwarded_message->sender_user_id;
+ if (2 <= forwarded_message_contents.size() && forwarded_message_contents.size() <= MAX_GROUPED_MESSAGES) {
+ std::unordered_set<MessageContentType, MessageContentTypeHash> message_content_types;
+ std::unordered_set<DialogId, DialogIdHash> sender_dialog_ids;
+ for (auto &message_content : forwarded_message_contents) {
+ message_content_types.insert(message_content.content->get_type());
+
+ MessageId message_id = get_persistent_message_id(from_dialog, message_ids[message_content.index]);
+ sender_dialog_ids.insert(get_message_original_sender(get_message(from_dialog, message_id)));
+ }
+ if (message_content_types.size() == 1 && is_homogenous_media_group_content(*message_content_types.begin()) &&
+ sender_dialog_ids.size() == 1 && *sender_dialog_ids.begin() != DialogId()) {
+ new_forwarded_media_album_ids[0].first = generate_new_media_album_id();
+ for (auto &message : forwarded_message_contents) {
+ message.media_album_id = 0;
}
}
+ }
+ for (auto &message : forwarded_message_contents) {
+ message.media_album_id = new_forwarded_media_album_ids[message.media_album_id].first;
+ }
- result[i] = m->message_id;
- forwarded_messages.push_back(m);
- forwarded_message_ids.push_back(message_id);
+ if (2 <= copied_messages.size() && copied_messages.size() <= MAX_GROUPED_MESSAGES) {
+ std::unordered_set<MessageContentType, MessageContentTypeHash> message_content_types;
+ for (auto &copied_message : copied_messages) {
+ message_content_types.insert(copied_message.content->get_type());
+ }
+ if (message_content_types.size() == 1 && is_homogenous_media_group_content(*message_content_types.begin())) {
+ new_copied_media_album_ids[0].first = generate_new_media_album_id();
+ for (auto &message : copied_messages) {
+ message.media_album_id = 0;
+ }
+ }
+ }
+ for (auto &message : copied_messages) {
+ message.media_album_id = new_copied_media_album_ids[message.media_album_id].first;
+ }
+ return std::move(result);
+}
+
+Result<td_api::object_ptr<td_api::messages>> MessagesManager::forward_messages(
+ DialogId to_dialog_id, MessageId top_thread_message_id, DialogId from_dialog_id, vector<MessageId> message_ids,
+ tl_object_ptr<td_api::messageSendOptions> &&options, bool in_game_share, vector<MessageCopyOptions> &&copy_options,
+ bool only_preview) {
+ TRY_RESULT(forwarded_messages_info,
+ get_forwarded_messages(to_dialog_id, top_thread_message_id, from_dialog_id, message_ids,
+ std::move(options), in_game_share, std::move(copy_options)));
+ auto from_dialog = forwarded_messages_info.from_dialog;
+ auto to_dialog = forwarded_messages_info.to_dialog;
+ auto message_send_options = forwarded_messages_info.message_send_options;
+ auto &copied_messages = forwarded_messages_info.copied_messages;
+ auto &forwarded_message_contents = forwarded_messages_info.forwarded_message_contents;
+ auto drop_author = forwarded_messages_info.drop_author;
+ auto drop_media_captions = forwarded_messages_info.drop_media_captions;
+ top_thread_message_id = forwarded_messages_info.top_thread_message_id;
- send_update_new_message(to_dialog, m, true);
+ FlatHashMap<MessageId, MessageId, MessageIdHash> forwarded_message_id_to_new_message_id;
+ vector<td_api::object_ptr<td_api::message>> result(message_ids.size());
+ vector<Message *> forwarded_messages;
+ vector<MessageId> forwarded_message_ids;
+ bool need_update_dialog_pos = false;
+ for (size_t j = 0; j < forwarded_message_contents.size(); j++) {
+ MessageId message_id = get_persistent_message_id(from_dialog, message_ids[forwarded_message_contents[j].index]);
+ const Message *forwarded_message = get_message(from_dialog, message_id);
+ CHECK(forwarded_message != nullptr);
+
+ auto content = std::move(forwarded_message_contents[j].content);
+ auto forward_info =
+ drop_author ? nullptr : create_message_forward_info(from_dialog_id, to_dialog_id, forwarded_message);
+ if (forward_info != nullptr && !forward_info->is_imported && !is_forward_info_sender_hidden(forward_info.get()) &&
+ !forward_info->message_id.is_valid() && !forward_info->sender_dialog_id.is_valid() &&
+ forward_info->sender_user_id.is_valid()) {
+ auto private_forward_name = td_->contacts_manager_->get_user_private_forward_name(forward_info->sender_user_id);
+ if (!private_forward_name.empty()) {
+ forward_info->sender_user_id = UserId();
+ forward_info->sender_name = std::move(private_forward_name);
+ }
+ }
+ MessageId reply_to_message_id;
+ if (forwarded_message->reply_to_message_id.is_valid()) {
+ auto it = forwarded_message_id_to_new_message_id.find(forwarded_message->reply_to_message_id);
+ if (it != forwarded_message_id_to_new_message_id.end()) {
+ reply_to_message_id = it->second;
+ }
+ }
+
+ unique_ptr<Message> message;
+ Message *m;
+ if (only_preview) {
+ message = create_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options,
+ std::move(content), j + 1 != forwarded_message_contents.size(),
+ std::move(forward_info), false, DialogId());
+ MessageId new_message_id =
+ message_send_options.schedule_date != 0
+ ? get_next_yet_unsent_scheduled_message_id(to_dialog, message_send_options.schedule_date)
+ : get_next_yet_unsent_message_id(to_dialog);
+ set_message_id(message, new_message_id);
+ m = message.get();
+ } else {
+ m = get_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options,
+ std::move(content), &need_update_dialog_pos, j + 1 != forwarded_message_contents.size(),
+ std::move(forward_info));
+ }
+ fix_forwarded_message(m, to_dialog_id, forwarded_message, forwarded_message_contents[j].media_album_id,
+ drop_author);
+ m->in_game_share = in_game_share;
+ m->real_forward_from_dialog_id = from_dialog_id;
+ m->real_forward_from_message_id = message_id;
+ forwarded_message_id_to_new_message_id.emplace(message_id, m->message_id);
+
+ if (!only_preview) {
+ send_update_new_message(to_dialog, m);
+ forwarded_messages.push_back(m);
+ forwarded_message_ids.push_back(message_id);
+ }
+
+ result[forwarded_message_contents[j].index] = get_message_object(to_dialog_id, m, "forward_messages");
}
if (!forwarded_messages.empty()) {
- do_forward_messages(to_dialog_id, from_dialog_id, forwarded_messages, forwarded_message_ids, 0);
+ CHECK(!only_preview);
+ do_forward_messages(to_dialog_id, from_dialog_id, forwarded_messages, forwarded_message_ids, drop_author,
+ drop_media_captions, 0);
}
- for (size_t i = 0; i < unforwarded_message_contents.size(); i++) {
- if (unforwarded_message_contents[i] != nullptr) {
- Message *m = get_message_to_send(to_dialog, MessageId(), disable_notification, from_background,
- std::move(unforwarded_message_contents[i]), &need_update_dialog_pos);
- m->disable_web_page_preview = unforwarded_message_disable_web_page_previews[i];
- if (to_secret) {
- m->media_album_id = media_album_id;
+ bool is_secret = to_dialog_id.get_type() == DialogType::SecretChat;
+ bool is_copy = !is_secret;
+ for (const auto &copied_message : copied_messages) {
+ if (forwarded_message_id_to_new_message_id.count(copied_message.original_reply_to_message_id) > 0) {
+ is_copy = true;
+ break;
+ }
+ forwarded_message_id_to_new_message_id.emplace(copied_message.original_message_id, MessageId());
+ }
+ for (auto &copied_message : copied_messages) {
+ MessageId reply_to_message_id = copied_message.reply_to_message_id;
+ if (!reply_to_message_id.is_valid() && copied_message.original_reply_to_message_id.is_valid() && is_secret) {
+ auto it = forwarded_message_id_to_new_message_id.find(copied_message.original_reply_to_message_id);
+ if (it != forwarded_message_id_to_new_message_id.end()) {
+ reply_to_message_id = it->second;
}
+ }
- save_send_message_logevent(to_dialog_id, m);
- do_send_message(to_dialog_id, m);
- result[i] = m->message_id;
+ unique_ptr<Message> message;
+ Message *m;
+ if (only_preview) {
+ message = create_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options,
+ std::move(copied_message.content), false, nullptr, is_copy, DialogId());
+ MessageId new_message_id =
+ message_send_options.schedule_date != 0
+ ? get_next_yet_unsent_scheduled_message_id(to_dialog, message_send_options.schedule_date)
+ : get_next_yet_unsent_message_id(to_dialog);
+ set_message_id(message, new_message_id);
+ m = message.get();
+ } else {
+ m = get_message_to_send(to_dialog, top_thread_message_id, reply_to_message_id, message_send_options,
+ std::move(copied_message.content), &need_update_dialog_pos, false, nullptr, is_copy);
+ }
+ m->disable_web_page_preview = copied_message.disable_web_page_preview;
+ m->media_album_id = copied_message.media_album_id;
+ m->reply_markup = std::move(copied_message.reply_markup);
+ forwarded_message_id_to_new_message_id[copied_message.original_message_id] = m->message_id;
- send_update_new_message(to_dialog, m, true);
+ if (!only_preview) {
+ save_send_message_log_event(to_dialog_id, m);
+ do_send_message(to_dialog_id, m);
+ send_update_new_message(to_dialog, m);
}
+
+ result[copied_message.index] = get_message_object(to_dialog_id, m, "forward_messages");
}
if (need_update_dialog_pos) {
+ CHECK(!only_preview);
send_update_chat_last_message(to_dialog, "forward_messages");
}
- return result;
+ return get_messages_object(-1, std::move(result), false);
}
-Result<MessageId> MessagesManager::send_dialog_set_ttl_message(DialogId dialog_id, int32 ttl) {
- if (dialog_id.get_type() != DialogType::SecretChat) {
- return Status::Error(5, "Can't set chat ttl in non-secret chat");
+Result<vector<MessageId>> MessagesManager::resend_messages(DialogId dialog_id, vector<MessageId> message_ids) {
+ if (message_ids.empty()) {
+ return Status::Error(400, "There are no messages to resend");
}
- if (ttl < 0) {
- return Status::Error(5, "Message ttl can't be negative");
+ Dialog *d = get_dialog_force(dialog_id, "resend_messages");
+ if (d == nullptr) {
+ return Status::Error(400, "Chat not found");
}
- LOG(INFO) << "Begin to set ttl in " << dialog_id << " to " << ttl;
+ TRY_STATUS(can_send_message(dialog_id));
- Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr) {
- return Status::Error(5, "Chat not found");
+ MessageId last_message_id;
+ for (auto &message_id : message_ids) {
+ message_id = get_persistent_message_id(d, message_id);
+ const Message *m = get_message_force(d, message_id, "resend_messages");
+ if (m == nullptr) {
+ return Status::Error(400, "Message not found");
+ }
+ if (!m->is_failed_to_send) {
+ return Status::Error(400, "Message is not failed to send");
+ }
+ if (!can_resend_message(m)) {
+ return Status::Error(400, "Message can't be re-sent");
+ }
+ if (m->try_resend_at > Time::now()) {
+ return Status::Error(400, "Message can't be re-sent yet");
+ }
+ if (last_message_id != MessageId()) {
+ if (m->message_id.is_scheduled() != last_message_id.is_scheduled()) {
+ return Status::Error(400, "Messages must be all scheduled or ordinary");
+ }
+ if (m->message_id <= last_message_id) {
+ return Status::Error(400, "Message identifiers must be in a strictly increasing order");
+ }
+ }
+ last_message_id = m->message_id;
}
- TRY_STATUS(can_send_message(dialog_id));
- bool need_update_dialog_pos = false;
- Message *m =
- get_message_to_send(d, MessageId(), false, false, make_unique<MessageChatSetTtl>(ttl), &need_update_dialog_pos);
+ vector<unique_ptr<MessageContent>> new_contents(message_ids.size());
+ std::unordered_map<int64, std::pair<int64, int32>, Hash<int64>> new_media_album_ids;
+ for (size_t i = 0; i < message_ids.size(); i++) {
+ MessageId message_id = message_ids[i];
+ const Message *m = get_message(d, message_id);
+ CHECK(m != nullptr);
- send_update_new_message(d, m, true);
- if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "send_dialog_set_ttl_message");
+ unique_ptr<MessageContent> content =
+ dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::Send, MessageCopyOptions());
+ if (content == nullptr) {
+ LOG(INFO) << "Can't resend " << m->message_id;
+ continue;
+ }
+
+ auto can_send_status = can_send_message_content(dialog_id, content.get(), false, td_);
+ if (can_send_status.is_error()) {
+ LOG(INFO) << "Can't resend " << m->message_id << ": " << can_send_status.message();
+ continue;
+ }
+
+ new_contents[i] = std::move(content);
+
+ if (m->media_album_id != 0) {
+ auto &new_media_album_id = new_media_album_ids[m->media_album_id];
+ new_media_album_id.second++;
+ if (new_media_album_id.second == 2) { // have at least 2 messages in the new album
+ CHECK(new_media_album_id.first == 0);
+ new_media_album_id.first = generate_new_media_album_id();
+ }
+ if (new_media_album_id.second == MAX_GROUPED_MESSAGES + 1) {
+ CHECK(new_media_album_id.first != 0);
+ new_media_album_id.first = 0; // just in case
+ }
+ }
}
- int64 random_id = begin_send_message(dialog_id, m);
+ vector<MessageId> result(message_ids.size());
+ bool need_update_dialog_pos = false;
+ for (size_t i = 0; i < message_ids.size(); i++) {
+ if (new_contents[i] == nullptr) {
+ continue;
+ }
- send_closure(td_->secret_chats_manager_, &SecretChatsManager::send_set_ttl_message, dialog_id.get_secret_chat_id(),
- ttl, random_id, Promise<>()); // TODO Promise
+ being_readded_message_id_ = {dialog_id, message_ids[i]};
+ unique_ptr<Message> message = delete_message(d, message_ids[i], true, &need_update_dialog_pos, "resend_messages");
+ CHECK(message != nullptr);
+ send_update_delete_messages(dialog_id, {message->message_id.get()}, true);
+
+ auto need_another_sender =
+ message->send_error_code == 400 && message->send_error_message == CSlice("SEND_AS_PEER_INVALID");
+ MessageSendOptions options(message->disable_notification, message->from_background,
+ message->update_stickersets_order, message->noforwards,
+ get_message_schedule_date(message.get()));
+ Message *m = get_message_to_send(
+ d, message->top_thread_message_id,
+ get_reply_to_message_id(d, message->top_thread_message_id, message->reply_to_message_id, false), options,
+ std::move(new_contents[i]), &need_update_dialog_pos, false, nullptr, message->is_copy,
+ need_another_sender ? DialogId() : get_message_sender(message.get()));
+ m->reply_markup = std::move(message->reply_markup);
+ m->via_bot_user_id = message->via_bot_user_id;
+ m->disable_web_page_preview = message->disable_web_page_preview;
+ m->clear_draft = false; // never clear draft in resend
+ m->ttl = message->ttl;
+ m->is_content_secret = message->is_content_secret;
+ m->media_album_id = new_media_album_ids[message->media_album_id].first;
+ m->send_emoji = message->send_emoji;
+ m->has_explicit_sender |= message->has_explicit_sender;
+
+ save_send_message_log_event(dialog_id, m);
+ do_send_message(dialog_id, m);
- return m->message_id;
+ send_update_new_message(d, m);
+
+ result[i] = m->message_id;
+ being_readded_message_id_ = FullMessageId();
+ }
+
+ if (need_update_dialog_pos) {
+ send_update_chat_last_message(d, "resend_messages");
+ }
+
+ return result;
}
Status MessagesManager::send_screenshot_taken_notification_message(DialogId dialog_id) {
auto dialog_type = dialog_id.get_type();
if (dialog_type != DialogType::User && dialog_type != DialogType::SecretChat) {
- return Status::Error(5, "Notification about taken screenshot can be sent only in private and secret chats");
+ return Status::Error(400, "Notification about taken screenshot can be sent only in private and secret chats");
}
LOG(INFO) << "Begin to send notification about taken screenshot in " << dialog_id;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "send_screenshot_taken_notification_message");
if (d == nullptr) {
- return Status::Error(5, "Chat not found");
+ return Status::Error(400, "Chat not found");
}
TRY_STATUS(can_send_message(dialog_id));
if (dialog_type == DialogType::User) {
bool need_update_dialog_pos = false;
- const Message *m = get_message_to_send(d, MessageId(), false, false, make_unique<MessageScreenshotTaken>(),
- &need_update_dialog_pos);
+ const Message *m = get_message_to_send(d, MessageId(), MessageId(), MessageSendOptions(),
+ create_screenshot_taken_message_content(), &need_update_dialog_pos);
do_send_screenshot_taken_notification_message(dialog_id, m, 0);
- send_update_new_message(d, m, true);
+ send_update_new_message(d, m);
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "send_screenshot_taken_notification_message");
}
} else {
send_closure(td_->secret_chats_manager_, &SecretChatsManager::notify_screenshot_taken,
dialog_id.get_secret_chat_id(),
- Promise<>()); // TODO Promise
+ Promise<Unit>()); // TODO Promise
}
return Status::OK();
@@ -18013,77 +29637,358 @@ class MessagesManager::SendScreenshotTakenNotificationMessageLogEvent {
template <class ParserT>
void parse(ParserT &parser) {
td::parse(dialog_id, parser);
- CHECK(m_out == nullptr);
- m_out = make_unique<Message>();
- td::parse(*m_out, parser);
+ td::parse(m_out, parser);
}
};
-uint64 MessagesManager::save_send_screenshot_taken_notification_message_logevent(DialogId dialog_id, const Message *m) {
+uint64 MessagesManager::save_send_screenshot_taken_notification_message_log_event(DialogId dialog_id,
+ const Message *m) {
if (!G()->parameters().use_message_db) {
return 0;
}
CHECK(m != nullptr);
LOG(INFO) << "Save " << FullMessageId(dialog_id, m->message_id) << " to binlog";
- SendScreenshotTakenNotificationMessageLogEvent logevent;
- logevent.dialog_id = dialog_id;
- logevent.m_in = m;
- auto storer = LogEventStorerImpl<SendScreenshotTakenNotificationMessageLogEvent>(logevent);
- return BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendScreenshotTakenNotificationMessage,
- storer);
+ SendScreenshotTakenNotificationMessageLogEvent log_event;
+ log_event.dialog_id = dialog_id;
+ log_event.m_in = m;
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SendScreenshotTakenNotificationMessage,
+ get_log_event_storer(log_event));
}
void MessagesManager::do_send_screenshot_taken_notification_message(DialogId dialog_id, const Message *m,
- uint64 logevent_id) {
+ uint64 log_event_id) {
LOG(INFO) << "Do send screenshot taken notification " << FullMessageId(dialog_id, m->message_id);
CHECK(dialog_id.get_type() == DialogType::User);
- if (logevent_id == 0) {
- logevent_id = save_send_screenshot_taken_notification_message_logevent(dialog_id, m);
+ if (log_event_id == 0) {
+ log_event_id = save_send_screenshot_taken_notification_message_log_event(dialog_id, m);
}
- Promise<> promise;
- if (logevent_id != 0) {
- promise = PromiseCreator::lambda([logevent_id](Result<Unit> result) mutable {
- LOG(INFO) << "Erase logevent_id " << logevent_id;
- if (!G()->close_flag()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
+ int64 random_id = begin_send_message(dialog_id, m);
+ td_->create_handler<SendScreenshotNotificationQuery>(get_erase_log_event_promise(log_event_id))
+ ->send(dialog_id, random_id);
+}
+
+Result<MessageId> MessagesManager::add_local_message(
+ DialogId dialog_id, td_api::object_ptr<td_api::MessageSender> &&sender, MessageId reply_to_message_id,
+ bool disable_notification, tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
+ if (input_message_content == nullptr) {
+ return Status::Error(400, "Can't add local message without content");
+ }
+
+ LOG(INFO) << "Begin to add local message to " << dialog_id << " in reply to " << reply_to_message_id;
+ Dialog *d = get_dialog_force(dialog_id, "add_local_message");
+ if (d == nullptr) {
+ return Status::Error(400, "Chat not found");
+ }
+
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return Status::Error(400, "Can't access the chat");
+ }
+ TRY_RESULT(message_content, process_input_message_content(dialog_id, std::move(input_message_content)));
+ if (message_content.content->get_type() == MessageContentType::Poll) {
+ return Status::Error(400, "Can't add local poll message");
+ }
+ if (message_content.content->get_type() == MessageContentType::Game) {
+ return Status::Error(400, "Can't add local game message");
+ }
+ if (message_content.content->get_type() == MessageContentType::Dice) {
+ return Status::Error(400, "Can't add local dice message");
+ }
+
+ bool is_channel_post = is_broadcast_channel(dialog_id);
+ UserId sender_user_id;
+ DialogId sender_dialog_id;
+ if (sender != nullptr) {
+ TRY_RESULT_ASSIGN(sender_dialog_id, get_message_sender_dialog_id(td_, sender, true, false));
+ auto sender_dialog_type = sender_dialog_id.get_type();
+ if (sender_dialog_type == DialogType::User) {
+ sender_user_id = sender_dialog_id.get_user_id();
+ sender_dialog_id = DialogId();
+ } else if (sender_dialog_type != DialogType::Channel) {
+ return Status::Error(400, "Sender chat must be a supergroup or channel");
+ }
+ } else if (is_channel_post) {
+ sender_dialog_id = dialog_id;
+ } else {
+ return Status::Error(400, "The message must have a sender");
+ }
+ if (is_channel_post && sender_user_id.is_valid()) {
+ return Status::Error(400, "Channel post can't have a sender user");
+ }
+ if (is_channel_post && sender_dialog_id != dialog_id) {
+ return Status::Error(400, "Channel post must have the channel as a sender");
+ }
+
+ auto dialog_type = dialog_id.get_type();
+ auto my_id = td_->contacts_manager_->get_my_id();
+ if (sender_user_id != my_id) {
+ if (dialog_type == DialogType::User && DialogId(sender_user_id) != dialog_id) {
+ return Status::Error(400, "Wrong sender user");
+ }
+ if (dialog_type == DialogType::SecretChat) {
+ auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (!peer_user_id.is_valid() || sender_user_id != peer_user_id) {
+ return Status::Error(400, "Wrong sender user");
}
- });
+ }
}
- int64 random_id = begin_send_message(dialog_id, m);
- td_->create_handler<SendScreenshotNotificationQuery>(std::move(promise))->send(dialog_id, random_id);
-}
+ MessageId message_id = get_next_local_message_id(d);
-bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_message_id, const string &source) {
- if (!new_message_id.is_valid()) {
- LOG(ERROR) << "Receive " << new_message_id << " in update message id with random_id " << random_id << " from "
- << source;
- auto it = debug_being_sent_messages_.find(random_id);
- if (it == debug_being_sent_messages_.end()) {
- LOG(ERROR) << "Message with random_id " << random_id << " was not sent";
- return false;
+ auto m = make_unique<Message>();
+ set_message_id(m, message_id);
+ if (is_channel_post) {
+ // sender of the post can be hidden
+ if (td_->contacts_manager_->get_channel_sign_messages(dialog_id.get_channel_id())) {
+ m->author_signature = td_->contacts_manager_->get_user_title(sender_user_id);
}
- auto dialog_id = it->second;
- if (!dialog_id.is_valid()) {
- LOG(ERROR) << "Sent message is in invalid " << dialog_id;
- return false;
+ m->sender_dialog_id = sender_dialog_id;
+ } else {
+ m->sender_user_id = sender_user_id;
+ m->sender_dialog_id = sender_dialog_id;
+ }
+ m->date = G()->unix_time();
+ m->reply_to_message_id = get_reply_to_message_id(d, MessageId(), reply_to_message_id, false);
+ if (m->reply_to_message_id.is_valid() && !message_id.is_scheduled()) {
+ const Message *reply_m = get_message(d, m->reply_to_message_id);
+ if (reply_m != nullptr) {
+ m->top_thread_message_id = reply_m->top_thread_message_id;
+ if (m->top_thread_message_id.is_valid()) {
+ m->is_topic_message = reply_m->is_topic_message;
+ }
}
- if (!have_dialog(dialog_id)) {
- LOG(ERROR) << "Sent message is in not found " << dialog_id;
- return false;
+ }
+ m->is_channel_post = is_channel_post;
+ m->is_outgoing = dialog_id != DialogId(my_id) && sender_user_id == my_id;
+ m->disable_notification = disable_notification;
+ m->from_background = false;
+ m->update_stickersets_order = false;
+ m->view_count = 0;
+ m->forward_count = 0;
+ m->content = std::move(message_content.content);
+ m->disable_web_page_preview = message_content.disable_web_page_preview;
+ m->clear_draft = message_content.clear_draft;
+ if (dialog_type == DialogType::SecretChat) {
+ m->ttl = td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id());
+ if (is_service_message_content(m->content->get_type())) {
+ m->ttl = 0;
}
- LOG(ERROR) << "Receive " << new_message_id << " in update message id with random_id " << random_id << " in "
- << dialog_id;
+ } else if (message_content.ttl > 0) {
+ m->ttl = message_content.ttl;
+ }
+ m->is_content_secret = is_secret_message_content(m->ttl, m->content->get_type());
+ m->send_emoji = std::move(message_content.emoji);
+
+ m->have_previous = true;
+ m->have_next = true;
+
+ bool need_update = true;
+ bool need_update_dialog_pos = false;
+ auto result =
+ add_message_to_dialog(d, std::move(m), true, &need_update, &need_update_dialog_pos, "add local message");
+ LOG_CHECK(result != nullptr) << message_id << " " << debug_add_message_to_dialog_fail_reason_;
+ register_new_local_message_id(d, result);
+
+ if (is_message_auto_read(dialog_id, result->is_outgoing)) {
+ if (result->is_outgoing) {
+ read_history_outbox(dialog_id, message_id);
+ } else {
+ read_history_inbox(dialog_id, message_id, 0, "add_local_message");
+ }
+ }
+
+ if (m->clear_draft) {
+ update_dialog_draft_message(d, nullptr, false, !need_update_dialog_pos);
+ }
+
+ send_update_new_message(d, result);
+ if (need_update_dialog_pos) {
+ send_update_chat_last_message(d, "add_local_message");
+ }
+
+ return message_id;
+}
+
+void MessagesManager::get_message_file_type(const string &message_file_head,
+ Promise<td_api::object_ptr<td_api::MessageFileType>> &&promise) {
+ td_->create_handler<CheckHistoryImportQuery>(std::move(promise))->send(message_file_head);
+}
+
+Status MessagesManager::can_import_messages(DialogId dialog_id) {
+ if (!have_dialog_force(dialog_id, "can_import_messages")) {
+ return Status::Error(400, "Chat not found");
+ }
+
+ TRY_STATUS(can_send_message(dialog_id));
+
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ if (!td_->contacts_manager_->is_user_contact(dialog_id.get_user_id(), true)) {
+ return Status::Error(400, "User must be a mutual contact");
+ }
+ break;
+ case DialogType::Chat:
+ return Status::Error(400, "Basic groups must be updagraded to supergroups first");
+ case DialogType::Channel:
+ if (is_broadcast_channel(dialog_id)) {
+ return Status::Error(400, "Can't import messages to channels");
+ }
+ if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).can_change_info_and_settings()) {
+ return Status::Error(400, "Not enough rights to import messages");
+ }
+ break;
+ case DialogType::SecretChat:
+ return Status::Error(400, "Can't import messages to secret chats");
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+
+ return Status::OK();
+}
+
+void MessagesManager::get_message_import_confirmation_text(DialogId dialog_id, Promise<string> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_import_messages(dialog_id));
+
+ td_->create_handler<CheckHistoryImportPeerQuery>(std::move(promise))->send(dialog_id);
+}
+
+void MessagesManager::import_messages(DialogId dialog_id, const td_api::object_ptr<td_api::InputFile> &message_file,
+ const vector<td_api::object_ptr<td_api::InputFile>> &attached_files,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, can_import_messages(dialog_id));
+
+ auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Document, message_file, dialog_id, false, false);
+ if (r_file_id.is_error()) {
+ // TODO TRY_RESULT_PROMISE(promise, ...);
+ return promise.set_error(Status::Error(400, r_file_id.error().message()));
+ }
+ FileId file_id = r_file_id.ok();
+
+ vector<FileId> attached_file_ids;
+ attached_file_ids.reserve(attached_files.size());
+ for (auto &attached_file : attached_files) {
+ auto file_type = td_->file_manager_->guess_file_type(attached_file);
+ if (file_type != FileType::Animation && file_type != FileType::Audio && file_type != FileType::Document &&
+ file_type != FileType::Photo && file_type != FileType::Sticker && file_type != FileType::Video &&
+ file_type != FileType::VoiceNote) {
+ LOG(INFO) << "Skip attached file of type " << file_type;
+ continue;
+ }
+ auto r_attached_file_id = td_->file_manager_->get_input_file_id(file_type, attached_file, dialog_id, false, false);
+ if (r_attached_file_id.is_error()) {
+ // TODO TRY_RESULT_PROMISE(promise, ...);
+ return promise.set_error(Status::Error(400, r_attached_file_id.error().message()));
+ }
+ attached_file_ids.push_back(r_attached_file_id.ok());
+ }
+
+ upload_imported_messages(dialog_id, td_->file_manager_->dup_file_id(file_id, "import_messages"),
+ std::move(attached_file_ids), false, std::move(promise));
+}
+
+void MessagesManager::upload_imported_messages(DialogId dialog_id, FileId file_id, vector<FileId> attached_file_ids,
+ bool is_reupload, Promise<Unit> &&promise, vector<int> bad_parts) {
+ CHECK(file_id.is_valid());
+ LOG(INFO) << "Ask to upload imported messages file " << file_id;
+ auto info = td::make_unique<UploadedImportedMessagesInfo>(dialog_id, std::move(attached_file_ids), is_reupload,
+ std::move(promise));
+ bool is_inserted = being_uploaded_imported_messages_.emplace(file_id, std::move(info)).second;
+ CHECK(is_inserted);
+ // TODO use force_reupload if is_reupload
+ td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_imported_messages_callback_, 1, 0, false,
+ true);
+}
+
+void MessagesManager::start_import_messages(DialogId dialog_id, int64 import_id, vector<FileId> &&attached_file_ids,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ TRY_STATUS_PROMISE(promise, can_send_message(dialog_id));
+
+ auto pending_message_import = make_unique<PendingMessageImport>();
+ pending_message_import->dialog_id = dialog_id;
+ pending_message_import->import_id = import_id;
+ pending_message_import->promise = std::move(promise);
+
+ auto &multipromise = pending_message_import->upload_files_multipromise;
+
+ int64 random_id;
+ do {
+ random_id = Random::secure_int64();
+ } while (random_id == 0 || pending_message_imports_.count(random_id) > 0);
+ pending_message_imports_[random_id] = std::move(pending_message_import);
+
+ multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), random_id](Result<Unit> result) {
+ send_closure_later(actor_id, &MessagesManager::on_imported_message_attachments_uploaded, random_id,
+ std::move(result));
+ }));
+ auto lock_promise = multipromise.get_promise();
+
+ for (auto attached_file_id : attached_file_ids) {
+ upload_imported_message_attachment(dialog_id, import_id,
+ td_->file_manager_->dup_file_id(attached_file_id, "start_import_messages"),
+ false, multipromise.get_promise());
+ }
+
+ lock_promise.set_value(Unit());
+}
+
+void MessagesManager::upload_imported_message_attachment(DialogId dialog_id, int64 import_id, FileId file_id,
+ bool is_reupload, Promise<Unit> &&promise,
+ vector<int> bad_parts) {
+ CHECK(file_id.is_valid());
+ LOG(INFO) << "Ask to upload improted message attached file " << file_id;
+ auto info =
+ td::make_unique<UploadedImportedMessageAttachmentInfo>(dialog_id, import_id, is_reupload, std::move(promise));
+ bool is_inserted = being_uploaded_imported_message_attachments_.emplace(file_id, std::move(info)).second;
+ CHECK(is_inserted);
+ // TODO use force_reupload if is_reupload
+ td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_imported_message_attachment_callback_, 1, 0,
+ false, true);
+}
+
+void MessagesManager::on_imported_message_attachments_uploaded(int64 random_id, Result<Unit> &&result) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
+
+ auto it = pending_message_imports_.find(random_id);
+ CHECK(it != pending_message_imports_.end());
+
+ auto pending_message_import = std::move(it->second);
+ CHECK(pending_message_import != nullptr);
+
+ pending_message_imports_.erase(it);
+
+ if (result.is_error()) {
+ pending_message_import->promise.set_error(result.move_as_error());
+ return;
+ }
+
+ CHECK(pending_message_import->upload_files_multipromise.promise_count() == 0);
+
+ auto promise = std::move(pending_message_import->promise);
+ auto dialog_id = pending_message_import->dialog_id;
+
+ TRY_STATUS_PROMISE(promise, can_send_message(dialog_id));
+
+ td_->create_handler<StartImportHistoryQuery>(std::move(promise))->send(dialog_id, pending_message_import->import_id);
+}
+
+bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_message_id, const string &source) {
+ if (!new_message_id.is_valid() && !new_message_id.is_valid_scheduled()) {
+ LOG(ERROR) << "Receive " << new_message_id << " in updateMessageId with random_id " << random_id << " from "
+ << source;
return false;
}
+ CHECK(new_message_id.is_any_server());
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
- // update about new message sent from other device or service message
- LOG(INFO) << "Receive not send outgoing " << new_message_id << " with random_id = " << random_id;
+ // update about a new message sent from other device or a service message
+ LOG(INFO) << "Receive not sent outgoing " << new_message_id << " with random_id = " << random_id;
return true;
}
@@ -18092,21 +29997,31 @@ bool MessagesManager::on_update_message_id(int64 random_id, MessageId new_messag
being_sent_messages_.erase(it);
- update_message_ids_[FullMessageId(dialog_id, new_message_id)] = old_message_id;
+ if (!have_message_force({dialog_id, old_message_id}, "on_update_message_id")) {
+ delete_sent_message_on_server(dialog_id, new_message_id, old_message_id);
+ return true;
+ }
+
+ LOG(INFO) << "Save correspondence from " << new_message_id << " in " << dialog_id << " to " << old_message_id;
+ CHECK(old_message_id.is_yet_unsent());
+ if (new_message_id.is_scheduled()) {
+ update_scheduled_message_ids_[dialog_id][new_message_id.get_scheduled_server_message_id()] = old_message_id;
+ } else {
+ update_message_ids_[FullMessageId(dialog_id, new_message_id)] = old_message_id;
+ }
return true;
}
-bool MessagesManager::on_get_dialog_error(DialogId dialog_id, const Status &status, const string &source) {
- if (status.message() == CSlice("SESSION_REVOKED") || status.message() == CSlice("USER_DEACTIVATED")) {
- // authorization is lost
+bool MessagesManager::on_get_dialog_error(DialogId dialog_id, const Status &status, const char *source) {
+ if (status.message() == CSlice("BOT_METHOD_INVALID")) {
+ LOG(ERROR) << "Receive BOT_METHOD_INVALID from " << source;
return true;
}
- if (status.code() == 420 || status.code() == 429) {
- // flood wait
+ if (G()->is_expected_error(status)) {
return true;
}
- if (status.message() == CSlice("BOT_METHOD_INVALID")) {
- LOG(ERROR) << "Receive BOT_METHOD_INVALID from " << source;
+ if (status.message() == CSlice("SEND_AS_PEER_INVALID")) {
+ reload_dialog_info_full(dialog_id, "SEND_AS_PEER_INVALID");
return true;
}
@@ -18134,236 +30049,1845 @@ void MessagesManager::on_dialog_updated(DialogId dialog_id, const char *source)
}
}
-void MessagesManager::send_update_new_message(Dialog *d, const Message *m, bool force) {
+void MessagesManager::send_update_new_message(const Dialog *d, const Message *m) {
CHECK(d != nullptr);
CHECK(m != nullptr);
+ CHECK(d->is_update_new_chat_sent);
+ send_closure(
+ G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateNewMessage>(get_message_object(d->dialog_id, m, "send_update_new_message")));
+}
- DialogId my_dialog_id(td_->contacts_manager_->get_my_id("send_update_new_message"));
- bool disable_notification =
- m->disable_notification || m->is_outgoing || d->dialog_id == my_dialog_id || td_->auth_manager_->is_bot();
- if (m->message_id.get() <= d->last_read_inbox_message_id.get()) {
- LOG(INFO) << "Disable notification for read " << m->message_id << " in " << d->dialog_id;
- disable_notification = true;
- }
- if (!disable_notification && d->dialog_id.get_type() == DialogType::Channel) {
- if (!td_->contacts_manager_->get_channel_status(d->dialog_id.get_channel_id()).is_member()) {
- disable_notification = true;
- }
- }
- bool have_settings = true;
- DialogId settings_dialog_id;
- if (!disable_notification) {
- Dialog *settings_dialog;
- if (!m->contains_mention || !m->sender_user_id.is_valid()) {
- // use notification settings from the dialog
- settings_dialog_id = d->dialog_id;
- settings_dialog = d;
- } else {
- // have a mention, so use notification settings from the dialog with the sender
- settings_dialog_id = DialogId(m->sender_user_id);
- settings_dialog = get_dialog_force(settings_dialog_id);
+MessagesManager::NotificationGroupInfo &MessagesManager::get_notification_group_info(Dialog *d, const Message *m) {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ return is_from_mention_notification_group(m) ? d->mention_notification_group : d->message_notification_group;
+}
+
+NotificationGroupId MessagesManager::get_dialog_notification_group_id(DialogId dialog_id,
+ NotificationGroupInfo &group_info) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return NotificationGroupId();
+ }
+ if (!group_info.group_id.is_valid()) {
+ NotificationGroupId next_notification_group_id;
+ do {
+ next_notification_group_id = td_->notification_manager_->get_next_notification_group_id();
+ if (!next_notification_group_id.is_valid()) {
+ return NotificationGroupId();
+ }
+ } while (get_message_notification_group_force(next_notification_group_id).dialog_id.is_valid());
+ group_info.group_id = next_notification_group_id;
+ group_info.is_changed = true;
+ VLOG(notifications) << "Assign " << next_notification_group_id << " to " << dialog_id;
+ on_dialog_updated(dialog_id, "get_dialog_notification_group_id");
+
+ notification_group_id_to_dialog_id_.emplace(next_notification_group_id, dialog_id);
+
+ if (running_get_channel_difference(dialog_id) || get_channel_difference_to_log_event_id_.count(dialog_id) != 0) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference,
+ next_notification_group_id);
}
+ }
+
+ CHECK(group_info.group_id.is_valid());
+
+ // notification group must be preloaded to guarantee that there is no race between
+ // get_message_notifications_from_database_force and new notifications added right now
+ td_->notification_manager_->load_group_force(group_info.group_id);
+
+ return group_info.group_id;
+}
- auto notification_settings = get_dialog_notification_settings(settings_dialog, settings_dialog_id);
- if (notification_settings == nullptr || // unknown megagroup without mention
- notification_settings->mute_until > G()->unix_time()) {
- disable_notification = true;
+Result<MessagesManager::MessagePushNotificationInfo> MessagesManager::get_message_push_notification_info(
+ DialogId dialog_id, MessageId message_id, int64 random_id, UserId sender_user_id, DialogId sender_dialog_id,
+ int32 date, bool is_from_scheduled, bool contains_mention, bool is_pinned, bool is_from_binlog) {
+ if (!is_from_scheduled && dialog_id == get_my_dialog_id()) {
+ return Status::Error("Ignore notification in chat with self");
+ }
+ if (td_->auth_manager_->is_bot()) {
+ return Status::Error("Ignore notification sent to bot");
+ }
+
+ Dialog *d = get_dialog_force(dialog_id, "get_message_push_notification_info");
+ if (d == nullptr) {
+ return Status::Error(406, "Ignore notification in unknown chat");
+ }
+ if (sender_dialog_id.is_valid() && !have_dialog_force(sender_dialog_id, "get_message_push_notification_info")) {
+ return Status::Error(406, "Ignore notification sent by unknown chat");
+ }
+
+ if (is_from_scheduled && dialog_id != get_my_dialog_id() &&
+ td_->option_manager_->get_option_boolean("disable_sent_scheduled_message_notifications")) {
+ return Status::Error("Ignore notification about sent scheduled message");
+ }
+
+ bool is_new_pinned = is_pinned && message_id.is_valid() && message_id > d->max_notification_message_id;
+ CHECK(!message_id.is_scheduled());
+ if (message_id.is_valid()) {
+ if (message_id <= d->last_new_message_id) {
+ return Status::Error("Ignore notification about known message");
+ }
+ if (!is_from_binlog && message_id == d->max_notification_message_id) {
+ return Status::Error("Ignore previously added message push notification");
+ }
+ if (!is_from_binlog && message_id < d->max_notification_message_id) {
+ return Status::Error("Ignore out of order message push notification");
+ }
+ if (message_id <= d->last_read_inbox_message_id) {
+ return Status::Error("Ignore notification about read message");
}
- if (settings_dialog == nullptr || !settings_dialog->notification_settings.is_synchronized) {
- have_settings = false;
+ if (message_id <= d->last_clear_history_message_id) {
+ return Status::Error("Ignore notification about message from cleared chat history");
+ }
+ if (is_deleted_message(d, message_id)) {
+ return Status::Error("Ignore notification about deleted message");
+ }
+ if (message_id <= d->max_unavailable_message_id) {
+ return Status::Error("Ignore notification about unavailable message");
+ }
+ }
+ if (random_id != 0) {
+ CHECK(dialog_id.get_type() == DialogType::SecretChat);
+ if (get_message_id_by_random_id(d, random_id, "get_message_push_notification_info").is_valid()) {
+ return Status::Error(406, "Ignore notification about known secret message");
}
}
- if (!force && (!have_settings || !d->pending_update_new_messages.empty())) {
- LOG(INFO) << "Delay update new message for " << m->message_id << " in " << d->dialog_id;
- if (d->pending_update_new_messages.empty()) {
- create_actor<SleepActor>(
- "FlushPendingUpdateNewMessagesSleepActor", 5.0,
- PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id](Result<Unit> result) {
- send_closure(actor_id, &MessagesManager::flush_pending_update_new_messages, dialog_id);
- }))
- .release();
+ if (is_pinned) {
+ contains_mention = !is_dialog_pinned_message_notifications_disabled(d);
+ } else if (contains_mention && is_dialog_mention_notifications_disabled(d)) {
+ contains_mention = false;
+ }
+ if (dialog_id.get_type() == DialogType::User) {
+ contains_mention = false;
+ }
+
+ DialogId settings_dialog_id = dialog_id;
+ Dialog *settings_dialog = d;
+ if (contains_mention) {
+ auto real_sender_dialog_id = sender_dialog_id.is_valid() ? sender_dialog_id : DialogId(sender_user_id);
+ if (real_sender_dialog_id.is_valid()) {
+ settings_dialog_id = real_sender_dialog_id;
+ settings_dialog = get_dialog_force(settings_dialog_id, "get_message_push_notification_info");
}
- d->pending_update_new_messages.push_back(m->message_id);
- auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id](Result<Unit> result) {
- send_closure(actor_id, &MessagesManager::flush_pending_update_new_messages, dialog_id);
- });
- send_get_dialog_query(settings_dialog_id, std::move(promise)); // TODO use GetNotifySettingsQuery when possible
+ }
+
+ bool have_settings;
+ int32 mute_until;
+ std::tie(have_settings, mute_until) = get_dialog_mute_until(settings_dialog_id, settings_dialog);
+ if (have_settings && mute_until > date) {
+ if (is_new_pinned) {
+ remove_dialog_pinned_message_notification(d, "get_message_push_notification_info");
+ }
+ return Status::Error("Ignore notification in muted chat");
+ }
+
+ if (is_dialog_message_notification_disabled(settings_dialog_id, date)) {
+ if (is_new_pinned) {
+ remove_dialog_pinned_message_notification(d, "get_message_push_notification_info");
+ }
+ return Status::Error("Ignore notification in chat, because notifications are disabled in the chat");
+ }
+
+ auto group_id = get_dialog_notification_group_id(
+ dialog_id, contains_mention ? d->mention_notification_group : d->message_notification_group);
+ if (!group_id.is_valid()) {
+ return Status::Error("Can't assign notification group ID");
+ }
+
+ if (message_id.is_valid() && message_id > d->max_notification_message_id) {
+ if (is_new_pinned) {
+ set_dialog_pinned_message_notification(d, contains_mention ? message_id : MessageId(),
+ "get_message_push_notification_info");
+ }
+ d->max_notification_message_id = message_id;
+ on_dialog_updated(dialog_id, "set_max_notification_message_id");
+ }
+
+ MessagePushNotificationInfo result;
+ result.group_id = group_id;
+ result.group_type = contains_mention ? NotificationGroupType::Mentions : NotificationGroupType::Messages;
+ result.settings_dialog_id = settings_dialog_id;
+ return result;
+}
+
+NotificationId MessagesManager::get_next_notification_id(Dialog *d, NotificationGroupId notification_group_id,
+ MessageId message_id) {
+ CHECK(d != nullptr);
+ CHECK(!message_id.is_scheduled());
+ NotificationId notification_id;
+ do {
+ notification_id = td_->notification_manager_->get_next_notification_id();
+ if (!notification_id.is_valid()) {
+ return NotificationId();
+ }
+ } while (d->notification_id_to_message_id.count(notification_id) != 0 ||
+ d->new_secret_chat_notification_id == notification_id ||
+ notification_id.get() <= d->message_notification_group.last_notification_id.get() ||
+ notification_id.get() <= d->message_notification_group.max_removed_notification_id.get() ||
+ notification_id.get() <= d->mention_notification_group.last_notification_id.get() ||
+ notification_id.get() <= d->mention_notification_group.max_removed_notification_id.get()); // just in case
+ if (message_id.is_valid()) {
+ add_notification_id_to_message_id_correspondence(d, notification_id, message_id);
+ }
+ return notification_id;
+}
+
+MessagesManager::MessageNotificationGroup MessagesManager::get_message_notification_group_force(
+ NotificationGroupId group_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(group_id.is_valid());
+ Dialog *d = nullptr;
+ auto it = notification_group_id_to_dialog_id_.find(group_id);
+ if (it != notification_group_id_to_dialog_id_.end()) {
+ d = get_dialog(it->second);
+ CHECK(d != nullptr);
+ } else if (G()->parameters().use_message_db) {
+ auto *dialog_db = G()->td_db()->get_dialog_db_sync();
+ dialog_db->begin_read_transaction().ensure();
+ auto r_value = dialog_db->get_notification_group(group_id);
+ if (r_value.is_ok()) {
+ VLOG(notifications) << "Loaded " << r_value.ok() << " from database by " << group_id;
+ d = get_dialog_force(r_value.ok().dialog_id, "get_message_notification_group_force");
+ } else {
+ LOG_CHECK(r_value.error().message() == "Not found") << r_value.error();
+ VLOG(notifications) << "Failed to load " << group_id << " from database";
+ }
+ dialog_db->commit_transaction().ensure();
+ }
+
+ if (d == nullptr) {
+ return MessageNotificationGroup();
+ }
+ if (d->message_notification_group.group_id != group_id && d->mention_notification_group.group_id != group_id) {
+ if (d->dialog_id.get_type() == DialogType::SecretChat && !d->message_notification_group.group_id.is_valid() &&
+ !d->mention_notification_group.group_id.is_valid()) {
+ // the group was reused, but wasn't deleted from the database, trying to resave it
+ auto &group_info = d->message_notification_group;
+ group_info.group_id = group_id;
+ group_info.is_changed = true;
+ group_info.try_reuse = true;
+ save_dialog_to_database(d->dialog_id);
+ group_info.group_id = NotificationGroupId();
+ group_info.is_changed = false;
+ group_info.try_reuse = false;
+ }
+ }
+
+ LOG_CHECK(d->message_notification_group.group_id == group_id || d->mention_notification_group.group_id == group_id);
+
+ bool from_mentions = d->mention_notification_group.group_id == group_id;
+ auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+
+ MessageNotificationGroup result;
+ VLOG(notifications) << "Found " << (from_mentions ? "Mentions " : "Messages ") << group_info.group_id << '/'
+ << d->dialog_id << " by " << group_id << " with " << d->unread_mention_count
+ << " unread mentions, " << d->unread_reaction_count << " unread reactions, pinned "
+ << d->pinned_message_notification_message_id << ", new secret chat "
+ << d->new_secret_chat_notification_id << " and " << d->server_unread_count + d->local_unread_count
+ << " unread messages";
+ result.dialog_id = d->dialog_id;
+ result.total_count = get_dialog_pending_notification_count(d, from_mentions);
+ auto pending_notification_count =
+ from_mentions ? d->pending_new_mention_notifications.size() : d->pending_new_message_notifications.size();
+ result.total_count -= static_cast<int32>(pending_notification_count);
+ if (result.total_count < 0) {
+ LOG(ERROR) << "Total notification count is " << result.total_count << " in " << d->dialog_id << " with "
+ << pending_notification_count << " pending new notifications";
+ result.total_count = 0;
+ }
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ CHECK(d->dialog_id.get_type() == DialogType::SecretChat);
+ result.type = NotificationGroupType::SecretChat;
+ result.notifications.emplace_back(d->new_secret_chat_notification_id,
+ td_->contacts_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id()),
+ false, create_new_secret_chat_notification());
+ } else {
+ result.type = from_mentions ? NotificationGroupType::Mentions : NotificationGroupType::Messages;
+ result.notifications = get_message_notifications_from_database_force(
+ d, from_mentions, static_cast<int32>(td_->notification_manager_->get_max_notification_group_size()));
+ }
+
+ int32 last_notification_date = 0;
+ NotificationId last_notification_id;
+ if (!result.notifications.empty()) {
+ last_notification_date = result.notifications[0].date;
+ last_notification_id = result.notifications[0].notification_id;
+ }
+ if (last_notification_date != group_info.last_notification_date ||
+ last_notification_id != group_info.last_notification_id) {
+ LOG(ERROR) << "Fix last notification date in " << d->dialog_id << " from " << group_info.last_notification_date
+ << " to " << last_notification_date << " and last notification identifier from "
+ << group_info.last_notification_id << " to " << last_notification_id << " in " << group_id << " of type "
+ << result.type;
+ set_dialog_last_notification(d->dialog_id, group_info, last_notification_date, last_notification_id,
+ "get_message_notification_group_force");
+ }
+
+ std::reverse(result.notifications.begin(), result.notifications.end());
+
+ return result;
+}
+
+bool MessagesManager::get_dialog_show_preview(const Dialog *d) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(d != nullptr);
+ if (d->notification_settings.use_default_show_preview) {
+ auto scope = get_dialog_notification_setting_scope(d->dialog_id);
+ return td_->notification_settings_manager_->get_scope_show_preview(scope);
+ } else {
+ return d->notification_settings.show_preview;
+ }
+}
+
+bool MessagesManager::is_message_preview_enabled(const Dialog *d, const Message *m, bool from_mentions) {
+ if (!get_dialog_show_preview(d)) {
+ return false;
+ }
+ if (!from_mentions) {
+ return true;
+ }
+ auto sender_dialog_id = get_message_sender(m);
+ if (!sender_dialog_id.is_valid()) {
+ return true;
+ }
+ d = get_dialog_force(sender_dialog_id, "is_message_preview_enabled");
+ if (d == nullptr) {
+ auto scope = get_dialog_notification_setting_scope(sender_dialog_id);
+ return td_->notification_settings_manager_->get_scope_show_preview(scope);
+ }
+ return get_dialog_show_preview(d);
+}
+
+bool MessagesManager::is_from_mention_notification_group(const Message *m) {
+ return m->contains_mention && !m->is_mention_notification_disabled;
+}
+
+bool MessagesManager::is_message_notification_active(const Dialog *d, const Message *m) {
+ CHECK(!m->message_id.is_scheduled());
+ if (is_from_mention_notification_group(m)) {
+ return m->notification_id.get() > d->mention_notification_group.max_removed_notification_id.get() &&
+ m->message_id > d->mention_notification_group.max_removed_message_id &&
+ (m->contains_unread_mention || m->message_id == d->pinned_message_notification_message_id);
+ } else {
+ return m->notification_id.get() > d->message_notification_group.max_removed_notification_id.get() &&
+ m->message_id > d->message_notification_group.max_removed_message_id &&
+ m->message_id > d->last_read_inbox_message_id;
+ }
+}
+
+void MessagesManager::try_add_pinned_message_notification(Dialog *d, vector<Notification> &res,
+ NotificationId max_notification_id, int32 limit) {
+ CHECK(d != nullptr);
+ auto message_id = d->pinned_message_notification_message_id;
+ if (!message_id.is_valid() || message_id > d->last_new_message_id) {
+ CHECK(!message_id.is_scheduled());
return;
}
- LOG_IF(WARNING, !have_settings) << "Have no notification settings for " << settings_dialog_id;
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateNewMessage>(get_message_object(d->dialog_id, m), disable_notification,
- m->contains_mention));
+ auto m = get_message_force(d, message_id, "try_add_pinned_message_notification");
+ if (m != nullptr && m->notification_id.get() > d->mention_notification_group.max_removed_notification_id.get() &&
+ m->message_id > d->mention_notification_group.max_removed_message_id &&
+ m->message_id > d->last_read_inbox_message_id && !is_dialog_pinned_message_notifications_disabled(d)) {
+ if (m->notification_id.get() < max_notification_id.get()) {
+ VLOG(notifications) << "Add " << m->notification_id << " about pinned " << message_id << " in " << d->dialog_id;
+ auto pinned_message_id = get_message_content_pinned_message_id(m->content.get());
+ if (pinned_message_id.is_valid()) {
+ get_message_force(d, pinned_message_id, "try_add_pinned_message_notification 2"); // preload pinned message
+ }
+
+ auto pos = res.size();
+ res.emplace_back(m->notification_id, m->date, m->disable_notification,
+ create_new_message_notification(message_id, is_message_preview_enabled(d, m, true)));
+ while (pos > 0 && res[pos - 1].type->get_message_id() < message_id) {
+ std::swap(res[pos - 1], res[pos]);
+ pos--;
+ }
+ if (pos > 0 && res[pos - 1].type->get_message_id() == message_id) {
+ res.erase(res.begin() + pos); // notification was already there
+ }
+ if (res.size() > static_cast<size_t>(limit)) {
+ res.pop_back();
+ CHECK(res.size() == static_cast<size_t>(limit));
+ }
+ }
+ } else {
+ remove_dialog_pinned_message_notification(d, "try_add_pinned_message_notification");
+ }
}
-void MessagesManager::flush_pending_update_new_messages(DialogId dialog_id) {
+vector<Notification> MessagesManager::get_message_notifications_from_database_force(Dialog *d, bool from_mentions,
+ int32 limit) {
+ CHECK(d != nullptr);
+ if (!G()->parameters().use_message_db || td_->auth_manager_->is_bot()) {
+ return {};
+ }
+
+ auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+ auto from_notification_id = NotificationId::max();
+ auto from_message_id = MessageId::max();
+ vector<Notification> res;
+ if (!from_mentions && from_message_id <= d->last_read_inbox_message_id) {
+ return res;
+ }
+ while (true) {
+ auto messages = do_get_message_notifications_from_database_force(d, from_mentions, from_notification_id,
+ from_message_id, limit);
+ if (messages.empty()) {
+ break;
+ }
+
+ bool is_found = false;
+ VLOG(notifications) << "Loaded " << messages.size() << (from_mentions ? " mention" : "")
+ << " messages with notifications from database in " << group_info.group_id << '/'
+ << d->dialog_id;
+ for (auto &message : messages) {
+ auto m = on_get_message_from_database(d, message, false, "get_message_notifications_from_database_force");
+ if (m == nullptr) {
+ VLOG(notifications) << "Receive from database a broken message";
+ continue;
+ }
+
+ auto notification_id = m->notification_id.is_valid() ? m->notification_id : m->removed_notification_id;
+ if (!notification_id.is_valid()) {
+ LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id
+ << " with from_mentions = " << from_mentions;
+ continue;
+ }
+ CHECK(m->message_id.is_valid());
+
+ bool is_correct = true;
+ if (notification_id.get() >= from_notification_id.get()) {
+ // possible if two messages have the same notification_id
+ LOG(ERROR) << "Have nonmonotonic notification identifiers: " << d->dialog_id << " " << m->message_id << " "
+ << notification_id << " " << from_message_id << " " << from_notification_id;
+ is_correct = false;
+ } else {
+ from_notification_id = notification_id;
+ is_found = true;
+ }
+ if (m->message_id >= from_message_id) {
+ LOG(ERROR) << "Have nonmonotonic message identifiers: " << d->dialog_id << " " << m->message_id << " "
+ << notification_id << " " << from_message_id << " " << from_notification_id;
+ is_correct = false;
+ } else {
+ from_message_id = m->message_id;
+ is_found = true;
+ }
+
+ if (notification_id.get() <= group_info.max_removed_notification_id.get() ||
+ m->message_id <= group_info.max_removed_message_id ||
+ (!from_mentions && m->message_id <= d->last_read_inbox_message_id)) {
+ // if message still has notification_id, but it was removed via max_removed_notification_id,
+ // or max_removed_message_id, or last_read_inbox_message_id,
+ // then there will be no more messages with active notifications
+ is_found = false;
+ break;
+ }
+
+ if (!m->notification_id.is_valid()) {
+ // notification_id can be empty if it is deleted in memory, but not in the database
+ VLOG(notifications) << "Receive from database " << m->message_id << " with removed "
+ << m->removed_notification_id;
+ continue;
+ }
+
+ if (is_from_mention_notification_group(m) != from_mentions) {
+ VLOG(notifications) << "Receive from database " << m->message_id << " with " << m->notification_id
+ << " from another group";
+ continue;
+ }
+
+ if (!is_message_notification_active(d, m)) {
+ CHECK(from_mentions);
+ CHECK(!m->contains_unread_mention);
+ CHECK(m->message_id != d->pinned_message_notification_message_id);
+ // skip read mentions
+ continue;
+ }
+
+ if (is_correct) {
+ // skip mention messages returned among unread messages
+ res.emplace_back(
+ m->notification_id, m->date, m->disable_notification,
+ create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions)));
+ } else {
+ remove_message_notification_id(d, m, true, false);
+ on_message_changed(d, m, false, "get_message_notifications_from_database_force");
+ }
+ }
+ if (!res.empty() || !is_found) {
+ break;
+ }
+ }
+ if (from_mentions) {
+ try_add_pinned_message_notification(d, res, NotificationId::max(), limit);
+ }
+ return res;
+}
+
+vector<MessageDbDialogMessage> MessagesManager::do_get_message_notifications_from_database_force(
+ Dialog *d, bool from_mentions, NotificationId from_notification_id, MessageId from_message_id, int32 limit) {
+ CHECK(G()->parameters().use_message_db);
+ CHECK(!from_message_id.is_scheduled());
+
+ auto *db = G()->td_db()->get_message_db_sync();
+ if (!from_mentions) {
+ CHECK(from_message_id > d->last_read_inbox_message_id);
+ VLOG(notifications) << "Trying to load " << limit << " messages with notifications in "
+ << d->message_notification_group.group_id << '/' << d->dialog_id << " from "
+ << from_notification_id;
+ return db->get_messages_from_notification_id(d->dialog_id, from_notification_id, limit);
+ } else {
+ VLOG(notifications) << "Trying to load " << limit << " messages with unread mentions in "
+ << d->mention_notification_group.group_id << '/' << d->dialog_id << " from " << from_message_id;
+
+ // ignore first_db_message_id, notifications can be nonconsecutive
+ MessageDbMessagesQuery db_query;
+ db_query.dialog_id = d->dialog_id;
+ db_query.filter = MessageSearchFilter::UnreadMention;
+ db_query.from_message_id = from_message_id;
+ db_query.offset = 0;
+ db_query.limit = limit;
+ return db->get_messages(db_query);
+ }
+}
+
+vector<NotificationGroupKey> MessagesManager::get_message_notification_group_keys_from_database(
+ NotificationGroupKey from_group_key, int32 limit) {
+ if (!G()->parameters().use_message_db) {
+ return {};
+ }
+
+ VLOG(notifications) << "Trying to load " << limit << " message notification groups from database from "
+ << from_group_key;
+
+ auto *dialog_db = G()->td_db()->get_dialog_db_sync();
+ dialog_db->begin_read_transaction().ensure();
+ auto group_keys = dialog_db->get_notification_groups_by_last_notification_date(from_group_key, limit);
+ vector<NotificationGroupKey> result;
+ for (auto &group_key : group_keys) {
+ CHECK(group_key.group_id.is_valid());
+ CHECK(group_key.dialog_id.is_valid());
+ const Dialog *d = get_dialog_force(group_key.dialog_id, "get_message_notification_group_keys_from_database");
+ if (d == nullptr || (d->message_notification_group.group_id != group_key.group_id &&
+ d->mention_notification_group.group_id != group_key.group_id)) {
+ continue;
+ }
+
+ CHECK(d->dialog_id == group_key.dialog_id);
+ CHECK(notification_group_id_to_dialog_id_[group_key.group_id] == d->dialog_id);
+
+ VLOG(notifications) << "Loaded " << group_key << " from database";
+ result.push_back(group_key);
+ }
+ dialog_db->commit_transaction().ensure();
+ return result;
+}
+
+void MessagesManager::get_message_notifications_from_database(DialogId dialog_id, NotificationGroupId group_id,
+ NotificationId from_notification_id,
+ MessageId from_message_id, int32 limit,
+ Promise<vector<Notification>> promise) {
+ if (!G()->parameters().use_message_db) {
+ return promise.set_error(Status::Error(500, "There is no message database"));
+ }
+ if (td_->auth_manager_->is_bot()) {
+ return promise.set_error(Status::Error(500, "Bots have no notifications"));
+ }
+
+ CHECK(dialog_id.is_valid());
+ CHECK(group_id.is_valid());
+ CHECK(!from_message_id.is_scheduled());
+ CHECK(limit > 0);
+
auto d = get_dialog(dialog_id);
CHECK(d != nullptr);
- if (d->pending_update_new_messages.empty()) {
+ if (d->message_notification_group.group_id != group_id && d->mention_notification_group.group_id != group_id) {
+ return promise.set_value(vector<Notification>());
+ }
+
+ VLOG(notifications) << "Get " << limit << " message notifications from database in " << group_id << " from "
+ << dialog_id << " from " << from_notification_id << "/" << from_message_id;
+ bool from_mentions = d->mention_notification_group.group_id == group_id;
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ CHECK(dialog_id.get_type() == DialogType::SecretChat);
+ vector<Notification> notifications;
+ if (!from_mentions && d->new_secret_chat_notification_id.get() < from_notification_id.get()) {
+ auto date = td_->contacts_manager_->get_secret_chat_date(dialog_id.get_secret_chat_id());
+ if (date <= 0) {
+ remove_new_secret_chat_notification(d, true);
+ } else {
+ notifications.emplace_back(d->new_secret_chat_notification_id, date, false,
+ create_new_secret_chat_notification());
+ }
+ }
+ return promise.set_value(std::move(notifications));
+ }
+
+ do_get_message_notifications_from_database(d, from_mentions, from_notification_id, from_notification_id,
+ from_message_id, limit, std::move(promise));
+}
+
+void MessagesManager::do_get_message_notifications_from_database(Dialog *d, bool from_mentions,
+ NotificationId initial_from_notification_id,
+ NotificationId from_notification_id,
+ MessageId from_message_id, int32 limit,
+ Promise<vector<Notification>> promise) {
+ CHECK(G()->parameters().use_message_db);
+ CHECK(!from_message_id.is_scheduled());
+
+ auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+ if (from_notification_id.get() <= group_info.max_removed_notification_id.get() ||
+ from_message_id <= group_info.max_removed_message_id ||
+ (!from_mentions && from_message_id <= d->last_read_inbox_message_id)) {
+ return promise.set_value(vector<Notification>());
+ }
+
+ auto dialog_id = d->dialog_id;
+ auto new_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id, from_mentions, initial_from_notification_id, limit,
+ promise = std::move(promise)](Result<vector<MessageDbDialogMessage>> result) mutable {
+ send_closure(actor_id, &MessagesManager::on_get_message_notifications_from_database, dialog_id, from_mentions,
+ initial_from_notification_id, limit, std::move(result), std::move(promise));
+ });
+
+ auto *db = G()->td_db()->get_message_db_async();
+ if (!from_mentions) {
+ VLOG(notifications) << "Trying to load " << limit << " messages with notifications in " << group_info.group_id
+ << '/' << dialog_id << " from " << from_notification_id;
+ return db->get_messages_from_notification_id(d->dialog_id, from_notification_id, limit, std::move(new_promise));
+ } else {
+ VLOG(notifications) << "Trying to load " << limit << " messages with unread mentions in " << group_info.group_id
+ << '/' << dialog_id << " from " << from_message_id;
+
+ // ignore first_db_message_id, notifications can be nonconsecutive
+ MessageDbMessagesQuery db_query;
+ db_query.dialog_id = dialog_id;
+ db_query.filter = MessageSearchFilter::UnreadMention;
+ db_query.from_message_id = from_message_id;
+ db_query.offset = 0;
+ db_query.limit = limit;
+ return db->get_messages(db_query, std::move(new_promise));
+ }
+}
+
+void MessagesManager::on_get_message_notifications_from_database(DialogId dialog_id, bool from_mentions,
+ NotificationId initial_from_notification_id,
+ int32 limit,
+ Result<vector<MessageDbDialogMessage>> result,
+ Promise<vector<Notification>> promise) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+ if (!group_info.group_id.is_valid()) {
+ return promise.set_error(Status::Error("Notification group was deleted"));
+ }
+
+ auto messages = result.move_as_ok();
+ vector<Notification> res;
+ res.reserve(messages.size());
+ NotificationId from_notification_id;
+ MessageId from_message_id;
+ VLOG(notifications) << "Loaded " << messages.size() << " messages with notifications in " << group_info.group_id
+ << '/' << dialog_id << " from database";
+ for (auto &message : messages) {
+ auto m = on_get_message_from_database(d, message, false, "on_get_message_notifications_from_database");
+ if (m == nullptr) {
+ VLOG(notifications) << "Receive from database a broken message";
+ continue;
+ }
+
+ auto notification_id = m->notification_id.is_valid() ? m->notification_id : m->removed_notification_id;
+ if (!notification_id.is_valid()) {
+ LOG(ERROR) << "Can't find notification identifier for " << m->message_id << " in " << d->dialog_id
+ << " with from_mentions = " << from_mentions;
+ continue;
+ }
+ CHECK(m->message_id.is_valid());
+
+ bool is_correct = true;
+ if (from_notification_id.is_valid() && notification_id.get() >= from_notification_id.get()) {
+ LOG(ERROR) << "Receive " << m->message_id << "/" << notification_id << " after " << from_message_id << "/"
+ << from_notification_id;
+ is_correct = false;
+ } else {
+ from_notification_id = notification_id;
+ }
+ if (from_message_id.is_valid() && m->message_id >= from_message_id) {
+ LOG(ERROR) << "Receive " << m->message_id << "/" << notification_id << " after " << from_message_id << "/"
+ << from_notification_id;
+ is_correct = false;
+ } else {
+ from_message_id = m->message_id;
+ }
+
+ if (notification_id.get() <= group_info.max_removed_notification_id.get() ||
+ m->message_id <= group_info.max_removed_message_id ||
+ (!from_mentions && m->message_id <= d->last_read_inbox_message_id)) {
+ // if message still has notification_id, but it was removed via max_removed_notification_id,
+ // or max_removed_message_id, or last_read_inbox_message_id,
+ // then there will be no more messages with active notifications
+ from_notification_id = NotificationId(); // stop requesting database
+ break;
+ }
+
+ if (!m->notification_id.is_valid()) {
+ // notification_id can be empty if it is deleted in memory, but not in the database
+ VLOG(notifications) << "Receive from database " << m->message_id << " with removed "
+ << m->removed_notification_id;
+ continue;
+ }
+
+ if (is_from_mention_notification_group(m) != from_mentions) {
+ VLOG(notifications) << "Receive from database " << m->message_id << " with " << m->notification_id
+ << " from another category";
+ continue;
+ }
+
+ if (!is_message_notification_active(d, m)) {
+ CHECK(from_mentions);
+ CHECK(!m->contains_unread_mention);
+ CHECK(m->message_id != d->pinned_message_notification_message_id);
+ // skip read mentions
+ continue;
+ }
+
+ if (is_correct) {
+ // skip mention messages returned among unread messages
+ CHECK(m->date > 0);
+ res.emplace_back(m->notification_id, m->date, m->disable_notification,
+ create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions)));
+ } else {
+ remove_message_notification_id(d, m, true, false);
+ on_message_changed(d, m, false, "on_get_message_notifications_from_database");
+ }
+ }
+ if (!res.empty() || !from_notification_id.is_valid() || static_cast<size_t>(limit) > messages.size()) {
+ if (from_mentions) {
+ try_add_pinned_message_notification(d, res, initial_from_notification_id, limit);
+ }
+
+ std::reverse(res.begin(), res.end());
+ return promise.set_value(std::move(res));
+ }
+
+ // try again from adjusted from_notification_id and from_message_id
+ do_get_message_notifications_from_database(d, from_mentions, initial_from_notification_id, from_notification_id,
+ from_message_id, limit, std::move(promise));
+}
+
+void MessagesManager::remove_message_notification(DialogId dialog_id, NotificationGroupId group_id,
+ NotificationId notification_id) {
+ Dialog *d = get_dialog_force(dialog_id, "remove_message_notification");
+ if (d == nullptr) {
+ LOG(ERROR) << "Can't find " << dialog_id;
+ return;
+ }
+ if (d->message_notification_group.group_id != group_id && d->mention_notification_group.group_id != group_id) {
+ LOG(ERROR) << "There is no " << group_id << " in " << dialog_id;
return;
}
- auto message_ids = std::move(d->pending_update_new_messages);
- reset_to_empty(d->pending_update_new_messages);
+ if (notification_id == NotificationId::max() || !notification_id.is_valid()) {
+ return; // there can be no notification with this ID
+ }
+
+ bool from_mentions = d->mention_notification_group.group_id == group_id;
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ if (!from_mentions && d->new_secret_chat_notification_id == notification_id) {
+ return remove_new_secret_chat_notification(d, false);
+ }
+ return;
+ }
+
+ auto it = d->notification_id_to_message_id.find(notification_id);
+ if (it != d->notification_id_to_message_id.end()) {
+ auto m = get_message(d, it->second);
+ CHECK(m != nullptr);
+ CHECK(m->notification_id == notification_id);
+ CHECK(!m->message_id.is_scheduled());
+ if (is_from_mention_notification_group(m) == from_mentions && is_message_notification_active(d, m)) {
+ remove_message_notification_id(d, m, false, false);
+ }
+ return;
+ }
+
+ if (G()->parameters().use_message_db) {
+ G()->td_db()->get_message_db_async()->get_messages_from_notification_id(
+ dialog_id, NotificationId(notification_id.get() + 1), 1,
+ PromiseCreator::lambda([dialog_id, from_mentions, notification_id,
+ actor_id = actor_id(this)](vector<MessageDbDialogMessage> result) {
+ send_closure(actor_id, &MessagesManager::do_remove_message_notification, dialog_id, from_mentions,
+ notification_id, std::move(result));
+ }));
+ }
+}
+
+void MessagesManager::remove_message_notifications_by_message_ids(DialogId dialog_id,
+ const vector<MessageId> &message_ids) {
+ VLOG(notifications) << "Trying to remove notification about " << message_ids << " in " << dialog_id;
+ Dialog *d = get_dialog_force(dialog_id, "remove_message_notifications_by_message_ids");
+ if (d == nullptr) {
+ return;
+ }
+
+ bool need_update_dialog_pos = false;
+ vector<int64> deleted_message_ids;
for (auto message_id : message_ids) {
- auto m = get_message(d, message_id);
- if (m != nullptr) {
- send_update_new_message(d, m, true);
+ CHECK(!message_id.is_scheduled());
+ // can't remove just notification_id, because total_count will stay wrong after restart
+ // delete whole message
+ auto message =
+ delete_message(d, message_id, true, &need_update_dialog_pos, "remove_message_notifications_by_message_ids");
+ if (message == nullptr) {
+ LOG(INFO) << "Can't delete " << message_id << " because it is not found";
+ // call synchronously to remove them before ProcessPush returns
+ td_->notification_manager_->remove_temporary_notification_by_message_id(
+ d->message_notification_group.group_id, message_id, true, "remove_message_notifications_by_message_ids");
+ td_->notification_manager_->remove_temporary_notification_by_message_id(
+ d->mention_notification_group.group_id, message_id, true, "remove_message_notifications_by_message_ids");
+ continue;
+ }
+ deleted_message_ids.push_back(message->message_id.get());
+ }
+
+ if (need_update_dialog_pos) {
+ send_update_chat_last_message(d, "remove_message_notifications_by_message_ids");
+ }
+ send_update_delete_messages(dialog_id, std::move(deleted_message_ids), true);
+}
+
+void MessagesManager::do_remove_message_notification(DialogId dialog_id, bool from_mentions,
+ NotificationId notification_id,
+ vector<MessageDbDialogMessage> result) {
+ if (result.empty() || G()->close_flag()) {
+ return;
+ }
+ CHECK(result.size() == 1);
+
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ auto m = on_get_message_from_database(d, result[0], false, "do_remove_message_notification");
+ if (m != nullptr && m->notification_id == notification_id && is_from_mention_notification_group(m) == from_mentions &&
+ is_message_notification_active(d, m)) {
+ remove_message_notification_id(d, m, false, false);
+ }
+}
+
+void MessagesManager::remove_message_notifications(DialogId dialog_id, NotificationGroupId group_id,
+ NotificationId max_notification_id, MessageId max_message_id) {
+ Dialog *d = get_dialog_force(dialog_id, "remove_message_notifications");
+ if (d == nullptr) {
+ LOG(ERROR) << "Can't find " << dialog_id;
+ return;
+ }
+ if (d->message_notification_group.group_id != group_id && d->mention_notification_group.group_id != group_id) {
+ LOG(ERROR) << "There is no " << group_id << " in " << dialog_id;
+ return;
+ }
+ if (!max_notification_id.is_valid()) {
+ return;
+ }
+ CHECK(!max_message_id.is_scheduled());
+
+ bool from_mentions = d->mention_notification_group.group_id == group_id;
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ if (!from_mentions && d->new_secret_chat_notification_id.get() <= max_notification_id.get()) {
+ return remove_new_secret_chat_notification(d, false);
+ }
+ return;
+ }
+ auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+ if (max_notification_id.get() <= group_info.max_removed_notification_id.get()) {
+ return;
+ }
+ if (max_message_id > group_info.max_removed_message_id) {
+ VLOG(notifications) << "Set max_removed_message_id in " << group_info.group_id << '/' << dialog_id << " to "
+ << max_message_id;
+ group_info.max_removed_message_id = max_message_id.get_prev_server_message_id();
+ }
+
+ VLOG(notifications) << "Set max_removed_notification_id in " << group_info.group_id << '/' << dialog_id << " to "
+ << max_notification_id;
+ group_info.max_removed_notification_id = max_notification_id;
+ on_dialog_updated(dialog_id, "remove_message_notifications");
+
+ if (group_info.last_notification_id.is_valid() &&
+ max_notification_id.get() >= group_info.last_notification_id.get()) {
+ bool is_changed =
+ set_dialog_last_notification(dialog_id, group_info, 0, NotificationId(), "remove_message_notifications");
+ CHECK(is_changed);
+ }
+}
+
+int32 MessagesManager::get_dialog_pending_notification_count(const Dialog *d, bool from_mentions) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(d != nullptr);
+ if (from_mentions) {
+ bool has_pinned_message = d->pinned_message_notification_message_id.is_valid() &&
+ d->pinned_message_notification_message_id <= d->last_new_message_id;
+ return d->unread_mention_count + static_cast<int32>(has_pinned_message);
+ } else {
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ return 1;
+ }
+ if (is_dialog_muted(d)) {
+ return narrow_cast<int32>(d->pending_new_message_notifications.size()); // usually 0
+ }
+
+ return d->server_unread_count + d->local_unread_count;
+ }
+}
+
+void MessagesManager::update_dialog_mention_notification_count(const Dialog *d) {
+ CHECK(d != nullptr);
+ if (td_->auth_manager_->is_bot() || !d->mention_notification_group.group_id.is_valid()) {
+ return;
+ }
+ auto total_count =
+ get_dialog_pending_notification_count(d, true) - static_cast<int32>(d->pending_new_mention_notifications.size());
+ if (total_count < 0) {
+ LOG(ERROR) << "Total mention notification count is " << total_count << " in " << d->dialog_id << " with "
+ << d->pending_new_mention_notifications << " pending new mention notifications";
+ total_count = 0;
+ }
+ send_closure_later(G()->notification_manager(), &NotificationManager::set_notification_total_count,
+ d->mention_notification_group.group_id, total_count);
+}
+
+bool MessagesManager::is_message_notification_disabled(const Dialog *d, const Message *m) const {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+
+ if (!has_incoming_notification(d->dialog_id, m) || td_->auth_manager_->is_bot()) {
+ return true;
+ }
+ if (m->is_from_scheduled && d->dialog_id != get_my_dialog_id() &&
+ td_->option_manager_->get_option_boolean("disable_sent_scheduled_message_notifications")) {
+ return true;
+ }
+ if (m->forward_info != nullptr && m->forward_info->is_imported) {
+ return true;
+ }
+
+ switch (m->content->get_type()) {
+ case MessageContentType::ChatDeleteHistory:
+ case MessageContentType::ChatMigrateTo:
+ case MessageContentType::Unsupported:
+ case MessageContentType::ExpiredPhoto:
+ case MessageContentType::ExpiredVideo:
+ case MessageContentType::PassportDataSent:
+ case MessageContentType::PassportDataReceived:
+ case MessageContentType::WebViewDataSent:
+ case MessageContentType::WebViewDataReceived:
+ VLOG(notifications) << "Disable notification for " << m->message_id << " in " << d->dialog_id
+ << " with content of type " << m->content->get_type();
+ return true;
+ case MessageContentType::ContactRegistered:
+ if (m->disable_notification) {
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return is_dialog_message_notification_disabled(d->dialog_id, m->date);
+}
+
+bool MessagesManager::is_dialog_message_notification_disabled(DialogId dialog_id, int32 message_date) const {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ break;
+ case DialogType::Chat:
+ if (!td_->contacts_manager_->get_chat_is_active(dialog_id.get_chat_id())) {
+ return true;
+ }
+ break;
+ case DialogType::Channel:
+ if (!td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id()).is_member() ||
+ message_date < td_->contacts_manager_->get_channel_date(dialog_id.get_channel_id())) {
+ return true;
+ }
+ break;
+ case DialogType::SecretChat:
+ if (td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) == SecretChatState::Closed) {
+ return true;
+ }
+ break;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+ if (message_date < authorization_date_) {
+ return true;
+ }
+
+ return false;
+}
+
+bool MessagesManager::may_need_message_notification(const Dialog *d, const Message *m) const {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ CHECK(m->message_id.is_valid());
+
+ if (is_message_notification_disabled(d, m)) {
+ return false;
+ }
+
+ if (is_from_mention_notification_group(m)) {
+ return true;
+ }
+
+ bool have_settings;
+ int32 mute_until;
+ std::tie(have_settings, mute_until) = get_dialog_mute_until(d->dialog_id, d);
+ return !have_settings || mute_until <= m->date;
+}
+
+bool MessagesManager::add_new_message_notification(Dialog *d, Message *m, bool force) {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ CHECK(m->message_id.is_valid());
+
+ if (!force) {
+ if (d->message_notification_group.group_id.is_valid()) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notifications,
+ d->message_notification_group.group_id, "add_new_message_notification 1");
+ }
+ if (d->mention_notification_group.group_id.is_valid()) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notifications,
+ d->mention_notification_group.group_id, "add_new_message_notification 2");
+ }
+ }
+
+ CHECK(!m->notification_id.is_valid());
+ if (is_message_notification_disabled(d, m)) {
+ return false;
+ }
+
+ auto from_mentions = is_from_mention_notification_group(m);
+ bool is_pinned = m->content->get_type() == MessageContentType::PinMessage;
+ bool is_active =
+ from_mentions ? m->contains_unread_mention || is_pinned : m->message_id > d->last_read_inbox_message_id;
+ if (is_active) {
+ auto &group = from_mentions ? d->mention_notification_group : d->message_notification_group;
+ if (group.max_removed_message_id >= m->message_id) {
+ is_active = false;
+ }
+ }
+ if (!is_active) {
+ VLOG(notifications) << "Disable inactive notification for " << m->message_id << " in " << d->dialog_id;
+ if (is_pinned) {
+ remove_dialog_pinned_message_notification(d, "add_new_message_notification");
+ }
+ return false;
+ }
+
+ VLOG(notifications) << "Trying to " << (force ? "forcely " : "") << "add new message notification for "
+ << m->message_id << " in " << d->dialog_id
+ << (m->disable_notification ? " silently" : " with sound");
+
+ DialogId settings_dialog_id = d->dialog_id;
+ Dialog *settings_dialog = d;
+ if (is_from_mention_notification_group(m)) {
+ // have a mention, so use notification settings from the dialog with the sender
+ auto sender_dialog_id = get_message_sender(m);
+ if (sender_dialog_id.is_valid()) {
+ settings_dialog_id = sender_dialog_id;
+ settings_dialog = get_dialog_force(settings_dialog_id, "add_new_message_notification");
+ }
+ }
+
+ bool have_settings;
+ int32 mute_until;
+ std::tie(have_settings, mute_until) = get_dialog_mute_until(settings_dialog_id, settings_dialog);
+ if (mute_until > m->date && (have_settings || force)) {
+ VLOG(notifications) << "Disable notification, because " << settings_dialog_id << " is muted";
+ if (is_pinned) {
+ remove_dialog_pinned_message_notification(d, "add_new_message_notification");
+ }
+ return false;
+ }
+
+ MessageId missing_pinned_message_id;
+ if (is_pinned) {
+ auto message_id = get_message_content_pinned_message_id(m->content.get());
+ if (message_id.is_valid() &&
+ !have_message_force(d, message_id,
+ force ? "add_new_message_notification force" : "add_new_message_notification not force")) {
+ missing_pinned_message_id = message_id;
+ }
+ }
+
+ auto &pending_notifications =
+ from_mentions ? d->pending_new_mention_notifications : d->pending_new_message_notifications;
+ if (!force && (!have_settings || !pending_notifications.empty() || missing_pinned_message_id.is_valid())) {
+ VLOG(notifications) << "Delay new message notification for " << m->message_id << " in " << d->dialog_id << " with "
+ << pending_notifications.size() << " already waiting messages";
+ if (pending_notifications.empty()) {
+ VLOG(notifications) << "Create FlushPendingNewMessageNotificationsSleepActor for " << d->dialog_id;
+ create_actor<SleepActor>("FlushPendingNewMessageNotificationsSleepActor", 5.0,
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id,
+ from_mentions](Result<Unit> result) {
+ VLOG(notifications)
+ << "Pending notifications timeout in " << dialog_id << " has expired";
+ send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications,
+ dialog_id, from_mentions, DialogId());
+ }))
+ .release();
+ }
+ auto last_settings_dialog_id = (pending_notifications.empty() ? DialogId() : pending_notifications.back().first);
+ pending_notifications.emplace_back((have_settings ? DialogId() : settings_dialog_id), m->message_id);
+ if (!have_settings && last_settings_dialog_id != settings_dialog_id) {
+ VLOG(notifications) << "Fetch notification settings for " << settings_dialog_id;
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions,
+ settings_dialog_id](Result<Unit> result) {
+ send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications, dialog_id, from_mentions,
+ settings_dialog_id);
+ });
+ if (settings_dialog == nullptr && have_input_peer(settings_dialog_id, AccessRights::Read)) {
+ force_create_dialog(settings_dialog_id, "add_new_message_notification 2");
+ settings_dialog = get_dialog(settings_dialog_id);
+ }
+ if (settings_dialog != nullptr) {
+ td_->notification_settings_manager_->send_get_dialog_notification_settings_query(settings_dialog_id,
+ std::move(promise));
+ } else {
+ send_get_dialog_query(settings_dialog_id, std::move(promise), 0, "add_new_message_notification");
+ }
+ }
+ if (missing_pinned_message_id.is_valid()) {
+ VLOG(notifications) << "Fetch pinned " << missing_pinned_message_id;
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), dialog_id = d->dialog_id, from_mentions](Result<Unit> result) {
+ send_closure(actor_id, &MessagesManager::flush_pending_new_message_notifications, dialog_id, from_mentions,
+ dialog_id);
+ });
+ get_message_from_server({d->dialog_id, missing_pinned_message_id}, std::move(promise),
+ "add_new_message_notification");
+ }
+ return false;
+ }
+
+ LOG_IF(WARNING, !have_settings) << "Have no notification settings for " << settings_dialog_id
+ << ", but forced to send notification about " << m->message_id << " in "
+ << d->dialog_id;
+ auto &group_info = get_notification_group_info(d, m);
+ auto notification_group_id = get_dialog_notification_group_id(d->dialog_id, group_info);
+ if (!notification_group_id.is_valid()) {
+ return false;
+ }
+ // if !force, then add_message_to_dialog will add the correspondence
+ m->notification_id = get_next_notification_id(d, notification_group_id, force ? m->message_id : MessageId());
+ if (!m->notification_id.is_valid()) {
+ return false;
+ }
+ bool is_changed = set_dialog_last_notification(d->dialog_id, group_info, m->date, m->notification_id,
+ "add_new_message_notification 3");
+ CHECK(is_changed);
+ if (is_pinned) {
+ set_dialog_pinned_message_notification(d, from_mentions ? m->message_id : MessageId(),
+ "add_new_message_notification");
+ }
+ if (!m->notification_id.is_valid()) {
+ // protection from accidental notification_id removal in set_dialog_pinned_message_notification
+ return false;
+ }
+ VLOG(notifications) << "Create " << m->notification_id << " with " << m->message_id << " in " << group_info.group_id
+ << '/' << d->dialog_id;
+ int32 min_delay_ms = 0;
+ if (need_delay_message_content_notification(m->content.get(), td_->contacts_manager_->get_my_id())) {
+ min_delay_ms = 3000; // 3 seconds
+ } else if (td_->is_online() && d->is_opened) {
+ min_delay_ms = 1000; // 1 second
+ }
+ auto ringtone_id = get_dialog_notification_ringtone_id(settings_dialog_id, settings_dialog);
+ bool is_silent = m->disable_notification || m->message_id <= d->max_notification_message_id;
+ send_closure_later(G()->notification_manager(), &NotificationManager::add_notification, notification_group_id,
+ from_mentions ? NotificationGroupType::Mentions : NotificationGroupType::Messages, d->dialog_id,
+ m->date, settings_dialog_id, m->disable_notification, is_silent ? 0 : ringtone_id, min_delay_ms,
+ m->notification_id,
+ create_new_message_notification(m->message_id, is_message_preview_enabled(d, m, from_mentions)),
+ "add_new_message_notification");
+ return true;
+}
+
+void MessagesManager::flush_pending_new_message_notifications(DialogId dialog_id, bool from_mentions,
+ DialogId settings_dialog_id) {
+ // flush pending notifications even while closing
+
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ auto &pending_notifications =
+ from_mentions ? d->pending_new_mention_notifications : d->pending_new_message_notifications;
+ if (pending_notifications.empty()) {
+ VLOG(notifications) << "Have no pending notifications in " << dialog_id << " to flush";
+ return;
+ }
+ for (auto &it : pending_notifications) {
+ if (it.first == settings_dialog_id || !settings_dialog_id.is_valid()) {
+ it.first = DialogId();
}
}
+
+ VLOG(notifications) << "Flush pending notifications in " << dialog_id
+ << " because of received notification settings in " << settings_dialog_id;
+ auto it = pending_notifications.begin();
+ while (it != pending_notifications.end() && it->first == DialogId()) {
+ auto m = get_message(d, it->second);
+ if (m != nullptr && add_new_message_notification(d, m, true)) {
+ on_message_changed(d, m, false, "flush_pending_new_message_notifications");
+ }
+ ++it;
+ }
+
+ if (it == pending_notifications.end()) {
+ reset_to_empty(pending_notifications);
+ } else {
+ pending_notifications.erase(pending_notifications.begin(), it);
+ }
+}
+
+void MessagesManager::remove_all_dialog_notifications(Dialog *d, bool from_mentions, const char *source) {
+ // removes up to group_info.last_notification_id
+ NotificationGroupInfo &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+ if (group_info.group_id.is_valid() && group_info.last_notification_id.is_valid() &&
+ group_info.max_removed_notification_id != group_info.last_notification_id) {
+ VLOG(notifications) << "Set max_removed_notification_id in " << group_info.group_id << '/' << d->dialog_id << " to "
+ << group_info.last_notification_id << " from " << source;
+ group_info.max_removed_notification_id = group_info.last_notification_id;
+ if (d->max_notification_message_id > group_info.max_removed_message_id) {
+ group_info.max_removed_message_id = d->max_notification_message_id.get_prev_server_message_id();
+ }
+ if (!d->pending_new_message_notifications.empty()) {
+ for (auto &it : d->pending_new_message_notifications) {
+ it.first = DialogId();
+ }
+ flush_pending_new_message_notifications(d->dialog_id, from_mentions, DialogId(UserId(static_cast<int64>(2))));
+ }
+ // remove_message_notifications will be called by NotificationManager
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group,
+ group_info.group_id, group_info.last_notification_id, MessageId(), 0, true, Promise<Unit>());
+ if (d->new_secret_chat_notification_id.is_valid() && &group_info == &d->message_notification_group) {
+ remove_new_secret_chat_notification(d, false);
+ } else {
+ bool is_changed = set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(), source);
+ CHECK(is_changed);
+ }
+ }
+}
+
+void MessagesManager::remove_message_dialog_notifications(Dialog *d, MessageId max_message_id, bool from_mentions,
+ const char *source) {
+ // removes up to max_message_id
+ CHECK(!max_message_id.is_scheduled());
+ NotificationGroupInfo &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+ if (!group_info.group_id.is_valid()) {
+ return;
+ }
+
+ VLOG(notifications) << "Remove message dialog notifications in " << group_info.group_id << '/' << d->dialog_id
+ << " up to " << max_message_id << " from " << source;
+
+ if (!d->pending_new_message_notifications.empty()) {
+ for (auto &it : d->pending_new_message_notifications) {
+ if (it.second <= max_message_id) {
+ it.first = DialogId();
+ }
+ }
+ flush_pending_new_message_notifications(d->dialog_id, from_mentions, DialogId(UserId(static_cast<int64>(3))));
+ }
+
+ auto max_notification_message_id = max_message_id;
+ if (d->last_message_id.is_valid() && max_notification_message_id >= d->last_message_id) {
+ max_notification_message_id = d->last_message_id;
+ set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(),
+ "remove_message_dialog_notifications 1");
+ } else if (max_notification_message_id == MessageId::max()) {
+ max_notification_message_id = get_next_local_message_id(d);
+ set_dialog_last_notification(d->dialog_id, group_info, 0, NotificationId(),
+ "remove_message_dialog_notifications 2");
+ } else {
+ LOG(FATAL) << "TODO support notification deletion up to " << max_message_id << " if it would be ever needed";
+ }
+
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification_group, group_info.group_id,
+ NotificationId(), max_notification_message_id, 0, true, Promise<Unit>());
}
void MessagesManager::send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m) const {
CHECK(m != nullptr);
- d->yet_unsent_message_id_to_persistent_message_id.emplace(old_message_id, m->message_id);
- send_closure(
- G()->td(), &Td::send_update,
- make_tl_object<td_api::updateMessageSendSucceeded>(get_message_object(d->dialog_id, m), old_message_id.get()));
+ CHECK(d->is_update_new_chat_sent);
+ if (!td_->auth_manager_->is_bot()) {
+ d->yet_unsent_message_id_to_persistent_message_id.emplace(old_message_id, m->message_id);
+ }
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateMessageSendSucceeded>(
+ get_message_object(d->dialog_id, m, "send_update_message_send_succeeded"), old_message_id.get()));
}
-void MessagesManager::send_update_message_content(DialogId dialog_id, MessageId message_id,
- const MessageContent *content, int32 message_date,
- bool is_content_secret, const char *source) const {
- LOG(INFO) << "Send updateMessageContent for " << message_id << " in " << dialog_id << " from " << source;
- CHECK(have_dialog(dialog_id)) << "Send updateMessageContent in unknown " << dialog_id << " from " << source
- << " with load count " << loaded_dialogs_.count(dialog_id);
- send_closure(
- G()->td(), &Td::send_update,
- make_tl_object<td_api::updateMessageContent>(
- dialog_id.get(), message_id.get(), get_message_content_object(content, message_date, is_content_secret)));
+void MessagesManager::send_update_message_content(const Dialog *d, Message *m, bool is_message_in_dialog,
+ const char *source) {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ if (is_message_in_dialog) {
+ delete_bot_command_message_id(d->dialog_id, m->message_id);
+ try_add_bot_command_message_id(d->dialog_id, m);
+ reregister_message_reply(d->dialog_id, m);
+ update_message_max_reply_media_timestamp(d, m, false); // because the message reply can be just registered
+ update_message_max_own_media_timestamp(d, m);
+ }
+ send_update_message_content_impl(d->dialog_id, m, source);
}
-void MessagesManager::send_update_message_edited(FullMessageId full_message_id) {
- return send_update_message_edited(full_message_id.get_dialog_id(), get_message(full_message_id));
+void MessagesManager::send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const {
+ CHECK(m != nullptr);
+ if (!m->is_update_sent) {
+ LOG(INFO) << "Skip updateMessageContent for " << m->message_id << " in " << dialog_id << " from " << source;
+ return;
+ }
+ LOG(INFO) << "Send updateMessageContent for " << m->message_id << " in " << dialog_id << " from " << source;
+ auto content_object = get_message_content_object(m->content.get(), td_, dialog_id, m->is_failed_to_send ? 0 : m->date,
+ m->is_content_secret, need_skip_bot_commands(dialog_id, m),
+ get_message_max_media_timestamp(m));
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateMessageContent>(dialog_id.get(), m->message_id.get(),
+ std::move(content_object)));
}
void MessagesManager::send_update_message_edited(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
- cancel_user_dialog_action(dialog_id, m);
+ cancel_dialog_action(dialog_id, m);
+ auto edit_date = m->hide_edit_date ? 0 : m->edit_date;
send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateMessageEdited>(dialog_id.get(), m->message_id.get(), m->edit_date,
- get_reply_markup_object(m->reply_markup)));
+ make_tl_object<td_api::updateMessageEdited>(
+ dialog_id.get(), m->message_id.get(), edit_date,
+ get_reply_markup_object(td_->contacts_manager_.get(), m->reply_markup)));
}
-void MessagesManager::send_update_delete_messages(DialogId dialog_id, vector<int64> &&message_ids, bool is_permanent,
- bool from_cache) const {
- if (!message_ids.empty()) {
- CHECK(have_dialog(dialog_id)) << "Wrong " << dialog_id << " in send_update_delete_messages";
+void MessagesManager::send_update_message_interaction_info(DialogId dialog_id, const Message *m) const {
+ CHECK(m != nullptr);
+ if (td_->auth_manager_->is_bot() || !m->is_update_sent) {
+ return;
+ }
+
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateMessageInteractionInfo>(dialog_id.get(), m->message_id.get(),
+ get_message_interaction_info_object(dialog_id, m)));
+}
+
+void MessagesManager::send_update_message_unread_reactions(DialogId dialog_id, const Message *m,
+ int32 unread_reaction_count) const {
+ CHECK(m != nullptr);
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ if (!m->is_update_sent) {
+ LOG(INFO) << "Update unread reaction message count in " << dialog_id << " to " << unread_reaction_count;
send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateDeleteMessages>(dialog_id.get(), std::move(message_ids), is_permanent,
- from_cache));
+ make_tl_object<td_api::updateChatUnreadReactionCount>(dialog_id.get(), unread_reaction_count));
+ return;
}
+
+ send_closure(
+ G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateMessageUnreadReactions>(
+ dialog_id.get(), m->message_id.get(), get_unread_reactions_object(dialog_id, m), unread_reaction_count));
+}
+
+void MessagesManager::send_update_message_live_location_viewed(FullMessageId full_message_id) {
+ CHECK(get_message(full_message_id) != nullptr);
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateMessageLiveLocationViewed>(full_message_id.get_dialog_id().get(),
+ full_message_id.get_message_id().get()));
+}
+
+void MessagesManager::send_update_delete_messages(DialogId dialog_id, vector<int64> &&message_ids,
+ bool is_permanent) const {
+ if (message_ids.empty()) {
+ return;
+ }
+
+ LOG_CHECK(have_dialog(dialog_id)) << "Wrong " << dialog_id << " in send_update_delete_messages";
+ send_closure(
+ G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateDeleteMessages>(dialog_id.get(), std::move(message_ids), is_permanent, false));
}
-void MessagesManager::send_update_chat(Dialog *d) {
+void MessagesManager::send_update_new_chat(Dialog *d) {
CHECK(d != nullptr);
CHECK(d->messages == nullptr);
- send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateNewChat>(get_chat_object(d)));
+ if ((d->dialog_id.get_type() == DialogType::User || d->dialog_id.get_type() == DialogType::SecretChat) &&
+ td_->auth_manager_->is_bot()) {
+ (void)get_dialog_photo(d->dialog_id); // to apply pending user photo
+ }
+ d->is_update_new_chat_being_sent = true;
+ auto chat_object = get_chat_object(d);
+ bool has_action_bar = chat_object->action_bar_ != nullptr;
+ bool has_theme = !chat_object->theme_name_.empty();
+ d->last_sent_has_scheduled_messages = chat_object->has_scheduled_messages_;
+ send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateNewChat>(std::move(chat_object)));
+ d->is_update_new_chat_sent = true;
+ d->is_update_new_chat_being_sent = false;
+
+ if (has_action_bar) {
+ send_update_secret_chats_with_user_action_bar(d);
+ }
+ if (has_theme) {
+ send_update_secret_chats_with_user_theme(d);
+ }
}
void MessagesManager::send_update_chat_draft_message(const Dialog *d) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
CHECK(d != nullptr);
- CHECK(d == get_dialog(d->dialog_id)) << "Wrong " << d->dialog_id << " in send_update_chat_draft_message";
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_draft_message";
on_dialog_updated(d->dialog_id, "send_update_chat_draft_message");
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateChatDraftMessage>(
- d->dialog_id.get(), get_draft_message_object(d->draft_message),
- DialogDate(d->order, d->dialog_id) <= last_dialog_date_ ? d->order : 0));
+ if (d->draft_message == nullptr || can_send_message(d->dialog_id).is_ok()) {
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatDraftMessage>(
+ d->dialog_id.get(), get_draft_message_object(d->draft_message), get_chat_positions_object(d)));
+ }
}
void MessagesManager::send_update_chat_last_message(Dialog *d, const char *source) {
- update_dialog_pos(d, false, source, false);
+ update_dialog_pos(d, source, false);
send_update_chat_last_message_impl(d, source);
}
void MessagesManager::send_update_chat_last_message_impl(const Dialog *d, const char *source) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
CHECK(d != nullptr);
- CHECK(d == get_dialog(d->dialog_id)) << "Wrong " << d->dialog_id << " in send_update_chat_last_message from "
- << source;
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_last_message from "
+ << source;
LOG(INFO) << "Send updateChatLastMessage in " << d->dialog_id << " to " << d->last_message_id << " from " << source;
- auto update = make_tl_object<td_api::updateChatLastMessage>(
- d->dialog_id.get(), get_message_object(d->dialog_id, get_message(d, d->last_message_id)),
- DialogDate(d->order, d->dialog_id) <= last_dialog_date_ ? d->order : 0);
+ const auto *m = get_message(d, d->last_message_id);
+ auto message_object = get_message_object(d->dialog_id, m, "send_update_chat_last_message_impl");
+ auto positions_object = get_chat_positions_object(d);
+ auto update = td_api::make_object<td_api::updateChatLastMessage>(d->dialog_id.get(), std::move(message_object),
+ std::move(positions_object));
send_closure(G()->td(), &Td::send_update, std::move(update));
}
-void MessagesManager::send_update_unread_message_count(DialogId dialog_id, bool force, const char *source) {
- if (!td_->auth_manager_->is_bot() && G()->parameters().use_message_db) {
- CHECK(is_unread_count_inited_);
- if (unread_message_total_count_ < 0 || unread_message_muted_count_ < 0 ||
- unread_message_muted_count_ > unread_message_total_count_) {
- LOG(ERROR) << "Unread messafe count became invalid: " << unread_message_total_count_ << '/'
- << unread_message_total_count_ - unread_message_muted_count_ << " from " << source << " and "
- << dialog_id;
- if (unread_message_total_count_ < 0) {
- unread_message_total_count_ = 0;
- }
- if (unread_message_muted_count_ < 0) {
- unread_message_muted_count_ = 0;
- }
- if (unread_message_muted_count_ > unread_message_total_count_) {
- unread_message_muted_count_ = unread_message_total_count_;
- }
+void MessagesManager::send_update_chat_filters() {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ is_update_chat_filters_sent_ = true;
+ send_closure(G()->td(), &Td::send_update, get_update_chat_filters_object());
+}
+
+void MessagesManager::save_dialog_filters() {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ DialogFiltersLogEvent log_event;
+ log_event.server_main_dialog_list_position = server_main_dialog_list_position_;
+ log_event.main_dialog_list_position = main_dialog_list_position_;
+ log_event.updated_date = dialog_filters_updated_date_;
+ log_event.server_dialog_filters_in = &server_dialog_filters_;
+ log_event.dialog_filters_in = &dialog_filters_;
+
+ LOG(INFO) << "Save server chat filters "
+ << get_dialog_filter_ids(server_dialog_filters_, server_main_dialog_list_position_)
+ << " and local chat filters " << get_dialog_filter_ids(dialog_filters_, main_dialog_list_position_);
+
+ G()->td_db()->get_binlog_pmc()->set("dialog_filters", log_event_store(log_event).as_slice().str());
+}
+
+void MessagesManager::send_update_unread_message_count(DialogList &list, DialogId dialog_id, bool force,
+ const char *source, bool from_database) {
+ if (td_->auth_manager_->is_bot() || !G()->parameters().use_message_db) {
+ return;
+ }
+
+ auto dialog_list_id = list.dialog_list_id;
+ CHECK(list.is_message_unread_count_inited_);
+ if (list.unread_message_muted_count_ < 0 || list.unread_message_muted_count_ > list.unread_message_total_count_) {
+ LOG(ERROR) << "Unread message count became invalid in " << dialog_list_id << ": "
+ << list.unread_message_total_count_ << '/'
+ << list.unread_message_total_count_ - list.unread_message_muted_count_ << " from " << source << " and "
+ << dialog_id;
+ if (list.unread_message_muted_count_ < 0) {
+ list.unread_message_muted_count_ = 0;
}
- G()->td_db()->get_binlog_pmc()->set("unread_message_count",
- PSTRING() << unread_message_total_count_ << ' ' << unread_message_muted_count_);
- int32 unread_unmuted_count = unread_message_total_count_ - unread_message_muted_count_;
- if (!force && running_get_difference_) {
- LOG(INFO) << "Postpone updateUnreadMessageCount to " << unread_message_total_count_ << '/' << unread_unmuted_count
- << " from " << source << " and " << dialog_id;
- have_postponed_unread_message_count_update_ = true;
- } else {
- have_postponed_unread_message_count_update_ = false;
- LOG(INFO) << "Send updateUnreadMessageCount to " << unread_message_total_count_ << '/' << unread_unmuted_count
- << " from " << source << " and " << dialog_id;
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateUnreadMessageCount>(unread_message_total_count_, unread_unmuted_count));
+ if (list.unread_message_muted_count_ > list.unread_message_total_count_) {
+ list.unread_message_total_count_ = list.unread_message_muted_count_;
}
}
+
+ if (!from_database) {
+ LOG(INFO) << "Save unread message count in " << dialog_list_id;
+ G()->td_db()->get_binlog_pmc()->set(
+ PSTRING() << "unread_message_count" << dialog_list_id.get(),
+ PSTRING() << list.unread_message_total_count_ << ' ' << list.unread_message_muted_count_);
+ }
+
+ int32 unread_unmuted_count = list.unread_message_total_count_ - list.unread_message_muted_count_;
+ if (!force && running_get_difference_) {
+ LOG(INFO) << "Postpone updateUnreadMessageCount in " << dialog_list_id << " to " << list.unread_message_total_count_
+ << '/' << unread_unmuted_count << " from " << source << " and " << dialog_id;
+ postponed_unread_message_count_updates_.insert(dialog_list_id);
+ } else {
+ postponed_unread_message_count_updates_.erase(dialog_list_id);
+ LOG(INFO) << "Send updateUnreadMessageCount in " << dialog_list_id << " to " << list.unread_message_total_count_
+ << '/' << unread_unmuted_count << " from " << source << " and " << dialog_id;
+ send_closure(G()->td(), &Td::send_update, get_update_unread_message_count_object(list));
+ }
+}
+
+void MessagesManager::send_update_unread_chat_count(DialogList &list, DialogId dialog_id, bool force,
+ const char *source, bool from_database) {
+ if (td_->auth_manager_->is_bot() || !G()->parameters().use_message_db) {
+ return;
+ }
+
+ auto dialog_list_id = list.dialog_list_id;
+ CHECK(list.is_dialog_unread_count_inited_);
+ if (list.unread_dialog_muted_marked_count_ < 0 ||
+ list.unread_dialog_marked_count_ < list.unread_dialog_muted_marked_count_ ||
+ list.unread_dialog_muted_count_ < list.unread_dialog_muted_marked_count_ ||
+ list.unread_dialog_total_count_ + list.unread_dialog_muted_marked_count_ <
+ list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_) {
+ LOG(ERROR) << "Unread chat count became invalid in " << dialog_list_id << ": " << list.unread_dialog_total_count_
+ << '/' << list.unread_dialog_total_count_ - list.unread_dialog_muted_count_ << '/'
+ << list.unread_dialog_marked_count_ << '/'
+ << list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_ << " from " << source
+ << " and " << dialog_id;
+ if (list.unread_dialog_muted_marked_count_ < 0) {
+ list.unread_dialog_muted_marked_count_ = 0;
+ }
+ if (list.unread_dialog_marked_count_ < list.unread_dialog_muted_marked_count_) {
+ list.unread_dialog_marked_count_ = list.unread_dialog_muted_marked_count_;
+ }
+ if (list.unread_dialog_muted_count_ < list.unread_dialog_muted_marked_count_) {
+ list.unread_dialog_muted_count_ = list.unread_dialog_muted_marked_count_;
+ }
+ if (list.unread_dialog_total_count_ + list.unread_dialog_muted_marked_count_ <
+ list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_) {
+ list.unread_dialog_total_count_ =
+ list.unread_dialog_muted_count_ + list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_;
+ }
+ }
+
+ if (!from_database) {
+ save_unread_chat_count(list);
+ }
+
+ bool need_postpone = !force && running_get_difference_;
+ int32 unread_unmuted_count = list.unread_dialog_total_count_ - list.unread_dialog_muted_count_;
+ int32 unread_unmuted_marked_count = list.unread_dialog_marked_count_ - list.unread_dialog_muted_marked_count_;
+ LOG(INFO) << (need_postpone ? "Postpone" : "Send") << " updateUnreadChatCount in " << dialog_list_id << " to "
+ << list.in_memory_dialog_total_count_ << '/' << list.server_dialog_total_count_ << '+'
+ << list.secret_chat_total_count_ << '/' << list.unread_dialog_total_count_ << '/' << unread_unmuted_count
+ << '/' << list.unread_dialog_marked_count_ << '/' << unread_unmuted_marked_count << " from " << source
+ << " and " << dialog_id;
+ if (need_postpone) {
+ postponed_unread_chat_count_updates_.insert(dialog_list_id);
+ } else {
+ postponed_unread_chat_count_updates_.erase(dialog_list_id);
+ send_closure(G()->td(), &Td::send_update, get_update_unread_chat_count_object(list));
+ }
+}
+
+void MessagesManager::save_unread_chat_count(const DialogList &list) {
+ LOG(INFO) << "Save unread chat count in " << list.dialog_list_id;
+ G()->td_db()->get_binlog_pmc()->set(
+ PSTRING() << "unread_dialog_count" << list.dialog_list_id.get(),
+ PSTRING() << list.unread_dialog_total_count_ << ' ' << list.unread_dialog_muted_count_ << ' '
+ << list.unread_dialog_marked_count_ << ' ' << list.unread_dialog_muted_marked_count_ << ' '
+ << list.server_dialog_total_count_ << ' ' << list.secret_chat_total_count_);
}
void MessagesManager::send_update_chat_read_inbox(const Dialog *d, bool force, const char *source) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
CHECK(d != nullptr);
- if (!td_->auth_manager_->is_bot()) {
- CHECK(d == get_dialog(d->dialog_id)) << "Wrong " << d->dialog_id << " in send_update_chat_read_inbox from "
- << source;
- on_dialog_updated(d->dialog_id, source);
- if (!force && (running_get_difference_ || running_get_channel_difference(d->dialog_id))) {
- LOG(INFO) << "Postpone updateChatReadInbox in " << d->dialog_id << "(" << get_dialog_title(d->dialog_id)
- << ") to " << d->server_unread_count << " + " << d->local_unread_count << " from " << source;
- postponed_chat_read_inbox_updates_.insert(d->dialog_id);
- } else {
- postponed_chat_read_inbox_updates_.erase(d->dialog_id);
- LOG(INFO) << "Send updateChatReadInbox in " << d->dialog_id << "(" << get_dialog_title(d->dialog_id) << ") to "
- << d->server_unread_count << " + " << d->local_unread_count << " from " << source;
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateChatReadInbox>(d->dialog_id.get(), d->last_read_inbox_message_id.get(),
- d->server_unread_count + d->local_unread_count));
- }
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_read_inbox from "
+ << source;
+ on_dialog_updated(d->dialog_id, source);
+ if (!force && (running_get_difference_ || running_get_channel_difference(d->dialog_id) ||
+ get_channel_difference_to_log_event_id_.count(d->dialog_id) != 0)) {
+ LOG(INFO) << "Postpone updateChatReadInbox in " << d->dialog_id << "(" << get_dialog_title(d->dialog_id) << ") to "
+ << d->server_unread_count << " + " << d->local_unread_count << " from " << source;
+ postponed_chat_read_inbox_updates_.insert(d->dialog_id);
+ } else {
+ postponed_chat_read_inbox_updates_.erase(d->dialog_id);
+ LOG(INFO) << "Send updateChatReadInbox in " << d->dialog_id << "(" << get_dialog_title(d->dialog_id) << ") to "
+ << d->server_unread_count << " + " << d->local_unread_count << " from " << source;
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatReadInbox>(d->dialog_id.get(), d->last_read_inbox_message_id.get(),
+ d->server_unread_count + d->local_unread_count));
}
}
void MessagesManager::send_update_chat_read_outbox(const Dialog *d) {
- CHECK(d != nullptr);
- if (!td_->auth_manager_->is_bot()) {
- CHECK(d == get_dialog(d->dialog_id)) << "Wrong " << d->dialog_id << " in send_update_chat_read_outbox";
- on_dialog_updated(d->dialog_id, "send_update_chat_read_outbox");
- send_closure(
- G()->td(), &Td::send_update,
- make_tl_object<td_api::updateChatReadOutbox>(d->dialog_id.get(), d->last_read_outbox_message_id.get()));
+ if (td_->auth_manager_->is_bot()) {
+ return;
}
+
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_read_outbox";
+ on_dialog_updated(d->dialog_id, "send_update_chat_read_outbox");
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatReadOutbox>(d->dialog_id.get(), d->last_read_outbox_message_id.get()));
}
void MessagesManager::send_update_chat_unread_mention_count(const Dialog *d) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
CHECK(d != nullptr);
- if (!td_->auth_manager_->is_bot()) {
- CHECK(d == get_dialog(d->dialog_id)) << "Wrong " << d->dialog_id << " in send_update_chat_unread_mention_count";
- LOG(INFO) << "Update unread mention message count in " << d->dialog_id << " to " << d->unread_mention_count;
- on_dialog_updated(d->dialog_id, "send_update_chat_unread_mention_count");
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateChatUnreadMentionCount>(d->dialog_id.get(), d->unread_mention_count));
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_unread_mention_count";
+ LOG(INFO) << "Update unread mention message count in " << d->dialog_id << " to " << d->unread_mention_count;
+ on_dialog_updated(d->dialog_id, "send_update_chat_unread_mention_count");
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatUnreadMentionCount>(d->dialog_id.get(), d->unread_mention_count));
+}
+
+void MessagesManager::send_update_chat_unread_reaction_count(const Dialog *d, const char *source) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id
+ << " in send_update_chat_unread_reaction_count from " << source;
+ LOG(INFO) << "Update unread reaction message count in " << d->dialog_id << " to " << d->unread_reaction_count
+ << " from " << source;
+ on_dialog_updated(d->dialog_id, "send_update_chat_unread_reaction_count");
+ send_closure(
+ G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatUnreadReactionCount>(d->dialog_id.get(), d->unread_reaction_count));
+}
+
+void MessagesManager::send_update_chat_position(DialogListId dialog_list_id, const Dialog *d,
+ const char *source) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_position";
+ LOG(INFO) << "Send updateChatPosition for " << d->dialog_id << " in " << dialog_list_id << " from " << source;
+ auto position = get_chat_position_object(dialog_list_id, d);
+ if (position == nullptr) {
+ position = td_api::make_object<td_api::chatPosition>(dialog_list_id.get_chat_list_object(), 0, false, nullptr);
+ }
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatPosition>(d->dialog_id.get(), std::move(position)));
+}
+
+void MessagesManager::send_update_chat_online_member_count(DialogId dialog_id, int32 online_member_count) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
}
+
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatOnlineMemberCount>(dialog_id.get(), online_member_count));
+}
+
+void MessagesManager::send_update_secret_chats_with_user_action_bar(const Dialog *d) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (d->dialog_id.get_type() != DialogType::User) {
+ return;
+ }
+
+ td_->contacts_manager_->for_each_secret_chat_with_user(
+ d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) {
+ DialogId dialog_id(secret_chat_id);
+ auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog
+ if (secret_chat_d != nullptr && secret_chat_d->is_update_new_chat_sent) {
+ send_closure(
+ G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatActionBar>(dialog_id.get(), get_chat_action_bar_object(user_d)));
+ }
+ });
+}
+
+void MessagesManager::send_update_chat_action_bar(Dialog *d) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ if (d->action_bar != nullptr && d->action_bar->is_empty()) {
+ d->action_bar = nullptr;
+ }
+
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_action_bar";
+ on_dialog_updated(d->dialog_id, "send_update_chat_action_bar");
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatActionBar>(d->dialog_id.get(), get_chat_action_bar_object(d)));
+
+ send_update_secret_chats_with_user_action_bar(d);
+}
+
+void MessagesManager::send_update_chat_available_reactions(const Dialog *d) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_available_reactions";
+ auto available_reactions = get_dialog_active_reactions(d).get_chat_available_reactions_object();
+ send_closure(
+ G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatAvailableReactions>(d->dialog_id.get(), std::move(available_reactions)));
+}
+
+void MessagesManager::send_update_secret_chats_with_user_theme(const Dialog *d) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (d->dialog_id.get_type() != DialogType::User) {
+ return;
+ }
+
+ td_->contacts_manager_->for_each_secret_chat_with_user(
+ d->dialog_id.get_user_id(), [this, user_d = d](SecretChatId secret_chat_id) {
+ DialogId dialog_id(secret_chat_id);
+ auto secret_chat_d = get_dialog(dialog_id); // must not create the dialog
+ if (secret_chat_d != nullptr && secret_chat_d->is_update_new_chat_sent) {
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatTheme>(dialog_id.get(), user_d->theme_name));
+ }
+ });
+}
+
+void MessagesManager::send_update_chat_theme(const Dialog *d) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ CHECK(d != nullptr);
+ CHECK(d->dialog_id.get_type() != DialogType::SecretChat);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_theme";
+ on_dialog_updated(d->dialog_id, "send_update_chat_theme");
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatTheme>(d->dialog_id.get(), d->theme_name));
+
+ send_update_secret_chats_with_user_theme(d);
+}
+
+void MessagesManager::send_update_chat_pending_join_requests(const Dialog *d) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_pending_join_requests";
+ on_dialog_updated(d->dialog_id, "send_update_chat_pending_join_requests");
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatPendingJoinRequests>(d->dialog_id.get(),
+ get_chat_join_requests_info_object(d)));
+}
+
+void MessagesManager::send_update_chat_video_chat(const Dialog *d) {
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_video_chat";
+ on_dialog_updated(d->dialog_id, "send_update_chat_video_chat");
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatVideoChat>(d->dialog_id.get(), get_video_chat_object(d)));
+}
+
+void MessagesManager::send_update_chat_message_sender(const Dialog *d) {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_message_sender";
+ send_closure(
+ G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatMessageSender>(d->dialog_id.get(), get_default_message_sender_object(d)));
+}
+
+void MessagesManager::send_update_chat_message_ttl(const Dialog *d) {
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_message_ttl";
+ on_dialog_updated(d->dialog_id, "send_update_chat_message_ttl");
+ send_closure(
+ G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatMessageTtl>(d->dialog_id.get(), d->message_ttl.get_message_ttl_object()));
+}
+
+void MessagesManager::send_update_chat_has_scheduled_messages(Dialog *d, bool from_deletion) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (d->scheduled_messages == nullptr) {
+ if (d->has_scheduled_database_messages) {
+ if (d->has_loaded_scheduled_messages_from_database) {
+ set_dialog_has_scheduled_database_messages_impl(d, false);
+ } else {
+ CHECK(G()->parameters().use_message_db);
+ repair_dialog_scheduled_messages(d);
+ }
+ }
+ if (d->has_scheduled_server_messages) {
+ if (from_deletion && d->scheduled_messages_sync_generation > 0) {
+ set_dialog_has_scheduled_server_messages(d, false);
+ } else {
+ d->last_repair_scheduled_messages_generation = 0;
+ repair_dialog_scheduled_messages(d);
+ }
+ }
+ }
+
+ LOG(INFO) << "In " << d->dialog_id << " have scheduled messages on server = " << d->has_scheduled_server_messages
+ << ", in database = " << d->has_scheduled_database_messages
+ << " and in memory = " << (d->scheduled_messages != nullptr)
+ << "; was loaded from database = " << d->has_loaded_scheduled_messages_from_database;
+ bool has_scheduled_messages = get_dialog_has_scheduled_messages(d);
+ if (has_scheduled_messages == d->last_sent_has_scheduled_messages) {
+ return;
+ }
+ d->last_sent_has_scheduled_messages = has_scheduled_messages;
+
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in send_update_chat_has_scheduled_messages";
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatHasScheduledMessages>(d->dialog_id.get(), has_scheduled_messages));
+}
+
+void MessagesManager::send_update_chat_action(DialogId dialog_id, MessageId top_thread_message_id,
+ DialogId typing_dialog_id, const DialogAction &action) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ LOG(DEBUG) << "Send " << action << " of " << typing_dialog_id << " in thread of " << top_thread_message_id << " in "
+ << dialog_id;
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatAction>(
+ dialog_id.get(), top_thread_message_id.get(),
+ get_message_sender_object(td_, typing_dialog_id, "send_update_chat_action"),
+ action.get_chat_action_object()));
}
void MessagesManager::on_send_message_get_quick_ack(int64 random_id) {
@@ -18384,90 +31908,127 @@ void MessagesManager::check_send_message_result(int64 random_id, DialogId dialog
const telegram_api::Updates *updates_ptr, const char *source) {
CHECK(updates_ptr != nullptr);
CHECK(source != nullptr);
- auto sent_messages = td_->updates_manager_->get_new_messages(updates_ptr);
- auto sent_messages_random_ids = td_->updates_manager_->get_sent_messages_random_ids(updates_ptr);
+ auto sent_messages = UpdatesManager::get_new_messages(updates_ptr);
+ auto sent_messages_random_ids = UpdatesManager::get_sent_messages_random_ids(updates_ptr);
+
+ auto is_invalid_poll_message = [](const telegram_api::Message *message) {
+ CHECK(message != nullptr);
+ auto constructor_id = message->get_id();
+ if (constructor_id == telegram_api::messageEmpty::ID) {
+ return true;
+ }
+ if (constructor_id != telegram_api::message::ID) {
+ return false;
+ }
+
+ auto media = static_cast<const telegram_api::message *>(message)->media_.get();
+ if (media == nullptr || media->get_id() != telegram_api::messageMediaPoll::ID) {
+ return false;
+ }
+
+ auto poll = static_cast<const telegram_api::messageMediaPoll *>(media)->poll_.get();
+ return !PollId(poll->id_).is_valid();
+ };
+
if (sent_messages.size() != 1u || sent_messages_random_ids.size() != 1u ||
- *sent_messages_random_ids.begin() != random_id || get_message_dialog_id(*sent_messages[0]) != dialog_id) {
+ *sent_messages_random_ids.begin() != random_id || get_message_dialog_id(*sent_messages[0]) != dialog_id ||
+ is_invalid_poll_message(sent_messages[0]->get())) {
LOG(ERROR) << "Receive wrong result for sending message with random_id " << random_id << " from " << source
<< " to " << dialog_id << ": " << oneline(to_string(*updates_ptr));
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
if (dialog_id.get_type() == DialogType::Channel) {
- Dialog *d = get_dialog(dialog_id);
- CHECK(d != nullptr);
get_channel_difference(dialog_id, d->pts, true, "check_send_message_result");
} else {
td_->updates_manager_->schedule_get_difference("check_send_message_result");
}
+ repair_dialog_scheduled_messages(d);
}
}
+void MessagesManager::update_reply_to_message_id(DialogId dialog_id, MessageId old_message_id, MessageId new_message_id,
+ bool have_new_message, const char *source) {
+ LOG(INFO) << "Update replies of " << FullMessageId{dialog_id, old_message_id} << " to " << new_message_id << " from "
+ << source;
+ auto it = replied_yet_unsent_messages_.find({dialog_id, old_message_id});
+ if (it == replied_yet_unsent_messages_.end()) {
+ return;
+ }
+ CHECK(old_message_id.is_yet_unsent());
+
+ for (auto message_id : it->second) {
+ CHECK(message_id.is_yet_unsent());
+ FullMessageId full_message_id{dialog_id, message_id};
+ auto replied_m = get_message(full_message_id);
+ CHECK(replied_m != nullptr);
+ CHECK(replied_m->reply_to_message_id == old_message_id);
+ LOG(INFO) << "Update replied message in " << full_message_id << " from " << old_message_id << " to "
+ << new_message_id;
+ unregister_message_reply(dialog_id, replied_m);
+ replied_m->reply_to_message_id = new_message_id;
+ // TODO rewrite send message log event
+ register_message_reply(dialog_id, replied_m);
+ }
+ if (have_new_message) {
+ CHECK(!new_message_id.is_yet_unsent());
+ replied_by_yet_unsent_messages_[FullMessageId{dialog_id, new_message_id}] = static_cast<int32>(it->second.size());
+ } else {
+ replied_by_yet_unsent_messages_.erase(FullMessageId{dialog_id, new_message_id});
+ }
+ replied_yet_unsent_messages_.erase(it);
+}
+
FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageId new_message_id, int32 date,
- FileId new_file_id, const char *source) {
+ int32 ttl_period, FileId new_file_id, const char *source) {
CHECK(source != nullptr);
// do not try to run getDifference from this function
- if (DROP_UPDATES) {
+ if (DROP_SEND_MESSAGE_UPDATES) {
return {};
}
if (!new_message_id.is_valid()) {
LOG(ERROR) << "Receive " << new_message_id << " as sent message from " << source;
- on_send_message_fail(random_id,
- Status::Error(500, "Internal server error: receive invalid message id as sent message id"));
+ on_send_message_fail(
+ random_id,
+ Status::Error(500, "Internal Server Error: receive invalid message identifier as sent message identifier"));
return {};
}
if (new_message_id.is_yet_unsent()) {
LOG(ERROR) << "Receive " << new_message_id << " as sent message from " << source;
on_send_message_fail(random_id,
- Status::Error(500, "Internal server error: receive yet unsent message as sent message"));
+ Status::Error(500, "Internal Server Error: receive yet unsent message as sent message"));
return {};
}
auto it = being_sent_messages_.find(random_id);
if (it == being_sent_messages_.end()) {
LOG(ERROR) << "Result from sendMessage for " << new_message_id << " with random_id " << random_id << " sent at "
- << date << " comes from " << source << " after updateNewMessageId, but was not discarded by pts. "
- << td_->updates_manager_->get_state();
- if (debug_being_sent_messages_.count(random_id) == 0) {
- LOG(ERROR) << "Message with random_id " << random_id << " was mot sent";
- return {};
- }
- auto dialog_id = debug_being_sent_messages_[random_id];
- if (!dialog_id.is_valid()) {
- LOG(ERROR) << "Sent message is in invalid " << dialog_id;
- return {};
- }
- auto d = get_dialog(dialog_id);
- if (d == nullptr) {
- LOG(ERROR) << "Sent message is in not found " << dialog_id;
- return {};
- }
- dump_debug_message_op(d, 7);
- auto m = get_message_force(d, new_message_id);
- if (m == nullptr) {
- LOG(ERROR) << new_message_id << " in " << dialog_id << " not found";
- return {};
- }
- LOG(ERROR) << "Result from sent " << (m->is_outgoing ? "outgoing" : "incoming")
- << (m->forward_info == nullptr ? " not" : "") << " forwarded " << new_message_id
- << " with content of the type " << m->content->get_id() << " in " << dialog_id
- << " comes after updateNewMessageId, current last new is " << d->last_new_message_id << ", last is "
- << d->last_message_id << ". " << td_->updates_manager_->get_state();
+ << date << " comes from " << source << " after updateNewMessageId, but was not discarded by pts";
return {};
}
auto dialog_id = it->second.get_dialog_id();
auto old_message_id = it->second.get_message_id();
+ if (new_message_id.is_local() && dialog_id.get_type() != DialogType::SecretChat) {
+ LOG(ERROR) << "Receive " << new_message_id << " as sent message from " << source;
+ on_send_message_fail(random_id, Status::Error(500, "Internal Server Error: receive local as sent message"));
+ return {};
+ }
+
being_sent_messages_.erase(it);
+ // must be called before delete_message
+ update_reply_to_message_id(dialog_id, old_message_id, new_message_id, true, "on_send_message_success");
+
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
bool need_update_dialog_pos = false;
+ being_readded_message_id_ = {dialog_id, old_message_id};
unique_ptr<Message> sent_message = delete_message(d, old_message_id, false, &need_update_dialog_pos, source);
if (sent_message == nullptr) {
- // message has already been deleted by the user or sent to inaccessible channel
- // don't need to send update to the user, because the message has already been deleted
- LOG(INFO) << "Delete already deleted sent " << new_message_id << " from server";
- delete_messages_from_server(dialog_id, {new_message_id}, true, 0, Auto());
+ delete_sent_message_on_server(dialog_id, new_message_id, old_message_id);
+ being_readded_message_id_ = FullMessageId();
return {};
}
@@ -18476,158 +32037,64 @@ FullMessageId MessagesManager::on_send_message_success(int64 random_id, MessageI
// dump_debug_message_op(d, 5);
}
- // imitation of update_message(d, sent_message, std::move(new_message), true, &need_update_dialog_pos);
+ // imitation of update_message(d, sent_message.get(), std::move(new_message), &need_update_dialog_pos, false);
if (date <= 0) {
- LOG(ERROR) << "Receive " << new_message_id << " in " << dialog_id << " with wrong date " << date;
+ LOG(ERROR) << "Receive " << new_message_id << " in " << dialog_id << " with wrong date " << date << " from "
+ << source;
} else {
- CHECK(sent_message->date > 0);
+ LOG_CHECK(sent_message->date > 0) << old_message_id << ' ' << sent_message->message_id << ' ' << new_message_id
+ << ' ' << sent_message->date << ' ' << date << ' ' << source;
sent_message->date = date;
CHECK(d->last_message_id != old_message_id);
}
- // reply_to message may be already deleted
- // but can't use get_message for check, because the message can be already unloaded from the memory
- // if (get_message_force(d, sent_message->reply_to_message_id) == nullptr) {
- // sent_message->reply_to_message_id = MessageId();
- // }
+ sent_message->ttl_period = ttl_period;
- bool is_content_changed = false;
- if (new_file_id.is_valid()) {
- int32 content_type = sent_message->content->get_id();
- switch (content_type) {
- case MessageAnimation::ID: {
- auto content = static_cast<MessageAnimation *>(sent_message->content.get());
- if (new_file_id != content->file_id) {
- td_->animations_manager_->merge_animations(new_file_id, content->file_id, false);
- content->file_id = new_file_id;
- is_content_changed = true;
- }
- break;
- }
- case MessageAudio::ID: {
- auto content = static_cast<MessageAudio *>(sent_message->content.get());
- if (new_file_id != content->file_id) {
- td_->audios_manager_->merge_audios(new_file_id, content->file_id, false);
- content->file_id = new_file_id;
- is_content_changed = true;
- }
- break;
- }
- case MessageDocument::ID: {
- auto content = static_cast<MessageDocument *>(sent_message->content.get());
- if (new_file_id != content->file_id) {
- td_->documents_manager_->merge_documents(new_file_id, content->file_id, false);
- content->file_id = new_file_id;
- is_content_changed = true;
- }
- break;
- }
- case MessagePhoto::ID: {
- auto content = static_cast<MessagePhoto *>(sent_message->content.get());
- Photo *photo = &content->photo;
- if (!photo->photos.empty() && photo->photos.back().type == 'i') {
- FileId &old_file_id = photo->photos.back().file_id;
- if (old_file_id != new_file_id) {
- LOG_STATUS(td_->file_manager_->merge(new_file_id, old_file_id));
- old_file_id = new_file_id;
- is_content_changed = true;
- }
- }
- break;
- }
- case MessageSticker::ID: {
- auto content = static_cast<MessageSticker *>(sent_message->content.get());
- if (new_file_id != content->file_id) {
- td_->stickers_manager_->merge_stickers(new_file_id, content->file_id, false);
- content->file_id = new_file_id;
- is_content_changed = true;
- }
- break;
- }
- case MessageVideo::ID: {
- auto content = static_cast<MessageVideo *>(sent_message->content.get());
- if (new_file_id != content->file_id) {
- td_->videos_manager_->merge_videos(new_file_id, content->file_id, false);
- content->file_id = new_file_id;
- is_content_changed = true;
- }
- break;
- }
- case MessageVideoNote::ID: {
- auto content = static_cast<MessageVideoNote *>(sent_message->content.get());
- if (new_file_id != content->file_id) {
- td_->video_notes_manager_->merge_video_notes(new_file_id, content->file_id, false);
- content->file_id = new_file_id;
- is_content_changed = true;
- }
- break;
- }
- case MessageVoiceNote::ID: {
- auto content = static_cast<MessageVoiceNote *>(sent_message->content.get());
- if (new_file_id != content->file_id) {
- td_->voice_notes_manager_->merge_voice_notes(new_file_id, content->file_id, false);
- content->file_id = new_file_id;
- is_content_changed = true;
- }
- break;
- }
- case MessageContact::ID:
- case MessageGame::ID:
- case MessageInvoice::ID:
- case MessageLiveLocation::ID:
- case MessageLocation::ID:
- case MessageText::ID:
- case MessageVenue::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageUnsupported::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- LOG(ERROR) << "Receive new file " << new_file_id << " in a sent message of the type " << content_type;
- break;
- default:
- UNREACHABLE();
- break;
- }
+ if (merge_message_content_file_id(td_, sent_message->content.get(), new_file_id)) {
+ send_update_message_content(d, sent_message.get(), false, source);
}
- if (is_content_changed) {
- send_update_message_content(dialog_id, old_message_id, sent_message->content.get(), sent_message->date,
- sent_message->is_content_secret, source);
+
+ if (old_message_id.is_valid() && new_message_id < old_message_id && !has_qts_messages(dialog_id) &&
+ !d->had_yet_unsent_message_id_overflow) {
+ LOG(ERROR) << "Sent " << old_message_id << " to " << dialog_id << " as " << new_message_id;
}
- sent_message->message_id = new_message_id;
- sent_message->random_y = get_random_y(sent_message->message_id);
+ set_message_id(sent_message, new_message_id);
+ sent_message->from_database = false;
sent_message->have_previous = true;
sent_message->have_next = true;
+ if (sent_message->reply_to_message_id != MessageId() && sent_message->reply_to_message_id.is_yet_unsent()) {
+ LOG(INFO) << "Drop reply to " << sent_message->reply_to_message_id;
+ sent_message->reply_to_message_id = MessageId();
+ }
+
+ send_update_message_send_succeeded(d, old_message_id, sent_message.get());
+
bool need_update = true;
Message *m = add_message_to_dialog(d, std::move(sent_message), true, &need_update, &need_update_dialog_pos, source);
- CHECK(m != nullptr);
-
- send_update_message_send_succeeded(d, old_message_id, m);
if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "on_send_message_success");
+ send_update_chat_last_message(d, source);
}
+
+ if (m == nullptr) {
+ if (!(old_message_id.is_valid() && new_message_id < old_message_id) &&
+ !(ttl_period > 0 && date + ttl_period <= G()->server_time())) {
+ // if message ID has decreased, which could happen if some messages were lost,
+ // or the message has already been deleted after TTL period, then the error is expected
+ LOG(ERROR) << "Failed to add just sent " << old_message_id << " to " << dialog_id << " as " << new_message_id
+ << " from " << source << ": " << debug_add_message_to_dialog_fail_reason_;
+ }
+ send_update_delete_messages(dialog_id, {new_message_id.get()}, true);
+ being_readded_message_id_ = FullMessageId();
+ return {};
+ }
+
try_add_active_live_location(dialog_id, m);
+ update_reply_count_by_message(d, +1, m);
+ update_forward_count(dialog_id, m);
+ being_readded_message_id_ = FullMessageId();
return {dialog_id, new_message_id};
}
@@ -18650,7 +32117,7 @@ void MessagesManager::on_send_message_file_part_missing(int64 random_id, int bad
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
- LOG(INFO) << "Fail to send already deleted by the user or sent to inaccessible chat " << full_message_id;
+ LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << full_message_id;
return;
}
@@ -18660,30 +32127,136 @@ void MessagesManager::on_send_message_file_part_missing(int64 random_id, int bad
// dump_debug_message_op(get_dialog(dialog_id), 5);
}
- Dialog *d = get_dialog(dialog_id);
- CHECK(d != nullptr);
if (dialog_id.get_type() == DialogType::SecretChat) {
+ CHECK(!m->message_id.is_scheduled());
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
+
// need to change message random_id before resending
- do {
- m->random_id = Random::secure_int64();
- } while (m->random_id == 0 || message_random_ids_.find(m->random_id) != message_random_ids_.end());
- message_random_ids_.insert(m->random_id);
+ m->random_id = generate_new_random_id(d);
- LOG(INFO) << "Replace random_id from " << random_id << " to " << m->random_id << " in " << m->message_id << " in "
- << dialog_id;
- d->random_id_to_message_id.erase(random_id);
- d->random_id_to_message_id[m->random_id] = m->message_id;
+ add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
- auto logevent = SendMessageLogEvent(dialog_id, m);
- auto storer = LogEventStorerImpl<SendMessageLogEvent>(logevent);
- CHECK(m->send_message_logevent_id != 0);
- BinlogHelper::rewrite(G()->td_db()->get_binlog(), m->send_message_logevent_id, LogEvent::HandlerType::SendMessage,
- storer);
+ auto log_event = SendMessageLogEvent(dialog_id, m);
+ CHECK(m->send_message_log_event_id != 0);
+ binlog_rewrite(G()->td_db()->get_binlog(), m->send_message_log_event_id, LogEvent::HandlerType::SendMessage,
+ get_log_event_storer(log_event));
}
do_send_message(dialog_id, m, {bad_part});
}
+void MessagesManager::on_send_message_file_reference_error(int64 random_id) {
+ auto it = being_sent_messages_.find(random_id);
+ if (it == being_sent_messages_.end()) {
+ // we can't receive fail more than once
+ // but message can be successfully sent before
+ LOG(WARNING) << "Receive file reference invalid error about successfully sent message with random_id = "
+ << random_id;
+ return;
+ }
+
+ auto full_message_id = it->second;
+
+ being_sent_messages_.erase(it);
+
+ Message *m = get_message(full_message_id);
+ if (m == nullptr) {
+ // message has already been deleted by the user or sent to inaccessible channel
+ // don't need to send error to the user, because the message has already been deleted
+ // and there is nothing to be deleted from the server
+ LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << full_message_id;
+ return;
+ }
+
+ auto dialog_id = full_message_id.get_dialog_id();
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ // LOG(ERROR) << "Found " << m->message_id << " in inaccessible " << dialog_id;
+ // dump_debug_message_op(get_dialog(dialog_id), 5);
+ }
+
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ CHECK(!m->message_id.is_scheduled());
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ delete_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
+
+ // need to change message random_id before resending
+ m->random_id = generate_new_random_id(d);
+
+ add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
+
+ auto log_event = SendMessageLogEvent(dialog_id, m);
+ CHECK(m->send_message_log_event_id != 0);
+ binlog_rewrite(G()->td_db()->get_binlog(), m->send_message_log_event_id, LogEvent::HandlerType::SendMessage,
+ get_log_event_storer(log_event));
+ }
+
+ do_send_message(dialog_id, m, {-1});
+}
+
+void MessagesManager::on_send_media_group_file_reference_error(DialogId dialog_id, vector<int64> random_ids) {
+ int64 media_album_id = 0;
+ vector<MessageId> message_ids;
+ vector<Message *> messages;
+ for (auto &random_id : random_ids) {
+ auto it = being_sent_messages_.find(random_id);
+ if (it == being_sent_messages_.end()) {
+ // we can't receive fail more than once
+ // but message can be successfully sent before
+ LOG(ERROR) << "Receive file reference invalid error about successfully sent message with random_id = "
+ << random_id;
+ continue;
+ }
+
+ auto full_message_id = it->second;
+
+ being_sent_messages_.erase(it);
+
+ Message *m = get_message(full_message_id);
+ if (m == nullptr) {
+ // message has already been deleted by the user or sent to inaccessible channel
+ // don't need to send error to the user, because the message has already been deleted
+ // and there is nothing to be deleted from the server
+ LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << full_message_id;
+ continue;
+ }
+
+ CHECK(m->media_album_id != 0);
+ CHECK(media_album_id == 0 || media_album_id == m->media_album_id);
+ media_album_id = m->media_album_id;
+
+ CHECK(dialog_id == full_message_id.get_dialog_id());
+ message_ids.push_back(m->message_id);
+ messages.push_back(m);
+ }
+
+ CHECK(dialog_id.get_type() != DialogType::SecretChat);
+
+ if (message_ids.empty()) {
+ // all messages was deleted, nothing to do
+ return;
+ }
+
+ auto &request = pending_message_group_sends_[media_album_id];
+ CHECK(!request.dialog_id.is_valid());
+ CHECK(request.finished_count == 0);
+ CHECK(request.results.empty());
+ request.dialog_id = dialog_id;
+ request.message_ids = std::move(message_ids);
+ request.is_finished.resize(request.message_ids.size(), false);
+ for (size_t i = 0; i < request.message_ids.size(); i++) {
+ request.results.push_back(Status::OK());
+ }
+
+ for (auto m : messages) {
+ do_send_message(dialog_id, m, {-1});
+ }
+}
+
void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
CHECK(error.is_error());
@@ -18691,24 +32264,8 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
if (it == being_sent_messages_.end()) {
// we can't receive fail more than once
// but message can be successfully sent before
- if (error.code() != NetQuery::Cancelled) {
- auto debug_it = debug_being_sent_messages_.find(random_id);
- if (debug_it == debug_being_sent_messages_.end()) {
- LOG(ERROR) << "Message with random_id " << random_id << " was not sent";
- return;
- }
- auto dialog_id = debug_it->second;
- if (!dialog_id.is_valid()) {
- LOG(ERROR) << "Sent message is in invalid " << dialog_id;
- return;
- }
- if (!have_dialog(dialog_id)) {
- LOG(ERROR) << "Sent message is in not found " << dialog_id;
- return;
- }
- LOG(ERROR) << "Receive error " << error << " about successfully sent message with random_id = " << random_id
- << " in " << dialog_id;
- dump_debug_message_op(get_dialog(dialog_id), 7);
+ if (error.code() != NetQuery::Canceled) {
+ LOG(ERROR) << "Receive error " << error << " about successfully sent message with random_id = " << random_id;
}
return;
}
@@ -18722,10 +32279,10 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send error to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
- LOG(INFO) << "Fail to send already deleted by the user or sent to inaccessible chat " << full_message_id;
+ LOG(INFO) << "Don't need to send already deleted by the user or sent to an inaccessible chat " << full_message_id;
return;
}
- LOG_IF(ERROR, error.code() == NetQuery::Cancelled)
+ LOG_IF(ERROR, error.code() == NetQuery::Canceled)
<< "Receive error " << error << " about sent message with random_id = " << random_id;
auto dialog_id = full_message_id.get_dialog_id();
@@ -18749,7 +32306,8 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
case 400:
if (error.message() == "MESSAGE_TOO_LONG") {
error_message = "Message is too long";
- // TODO move check to send_message
+ } else if (error.message() == "MEDIA_CAPTION_TOO_LONG") {
+ error_message = "Message caption is too long";
} else if (error.message() == "INPUT_USER_DEACTIVATED") {
error_code = 403;
error_message = "User is deactivated";
@@ -18758,14 +32316,13 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
if (td_->auth_manager_->is_bot()) {
switch (dialog_id.get_type()) {
case DialogType::User:
+ case DialogType::SecretChat:
error_message = "Bot was blocked by the user";
break;
case DialogType::Chat:
case DialogType::Channel:
error_message = "Bot was kicked from the chat";
break;
- case DialogType::SecretChat:
- break;
case DialogType::None:
default:
UNREACHABLE();
@@ -18773,14 +32330,13 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
} else {
switch (dialog_id.get_type()) {
case DialogType::User:
+ case DialogType::SecretChat:
error_message = "User was blocked by the other user";
break;
case DialogType::Chat:
case DialogType::Channel:
error_message = "User is not in the chat";
break;
- case DialogType::SecretChat:
- break;
case DialogType::None:
default:
UNREACHABLE();
@@ -18788,24 +32344,40 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
}
// TODO add check to send_message
} else if (error.message() == "USER_IS_BOT") {
- error_code = 403;
- error_message = "Bot can't send messages to bots";
- // TODO move check to send_message
+ if (td_->auth_manager_->is_bot() &&
+ (dialog_id.get_type() == DialogType::User || dialog_id.get_type() == DialogType::SecretChat)) {
+ error_code = 403;
+ if (td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) {
+ error_message = "Bot can't send messages to bots";
+ } else {
+ error_message = "Bot can't send messages to the user";
+ }
+ // TODO move check to send_message
+ }
} else if (error.message() == "PEER_ID_INVALID") {
error_code = 403;
- error_message = "Bot can't initiate conversation with a user";
+ if (td_->auth_manager_->is_bot() &&
+ (dialog_id.get_type() == DialogType::User || dialog_id.get_type() == DialogType::SecretChat)) {
+ error_message = "Bot can't initiate conversation with a user";
+ }
} else if (error.message() == "WC_CONVERT_URL_INVALID" || error.message() == "EXTERNAL_URL_INVALID") {
error_message = "Wrong HTTP URL specified";
} else if (error.message() == "WEBPAGE_CURL_FAILED") {
error_message = "Failed to get HTTP URL content";
} else if (error.message() == "WEBPAGE_MEDIA_EMPTY") {
error_message = "Wrong type of the web page content";
+ } else if (error.message() == "CHAT_FORWARDS_RESTRICTED") {
+ error_message = "Message has protected content and can't be forwarded";
} else if (error.message() == "MEDIA_EMPTY") {
- auto content_type = m->content->get_id();
- if (content_type == MessageGame::ID) {
+ auto content_type = m->content->get_type();
+ if (content_type == MessageContentType::Game) {
error_message = "Wrong game short name specified";
- } else if (content_type == MessageInvoice::ID) {
+ } else if (content_type == MessageContentType::Invoice) {
error_message = "Wrong invoice information specified";
+ } else if (content_type == MessageContentType::Poll) {
+ error_message = "Wrong poll data specified";
+ } else if (content_type == MessageContentType::Contact) {
+ error_message = "Wrong phone number specified";
} else {
error_message = "Wrong file identifier/HTTP URL specified";
}
@@ -18817,6 +32389,11 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
if (error.message() == "MESSAGE_DELETE_FORBIDDEN") {
error_code = 400;
error_message = "Message can't be deleted";
+ } else if (error.message() == "CHAT_GUEST_SEND_FORBIDDEN") {
+ error_code = 400;
+ if (dialog_id.get_type() == DialogType::Channel) {
+ td_->contacts_manager_->reload_channel(dialog_id.get_channel_id(), Promise<Unit>());
+ }
} else if (error.message() != "CHANNEL_PUBLIC_GROUP_NA" && error.message() != "USER_IS_BLOCKED" &&
error.message() != "USER_BOT_INVALID" && error.message() != "USER_DELETED") {
error_code = 400;
@@ -18828,44 +32405,73 @@ void MessagesManager::on_send_message_fail(int64 random_id, Status error) {
}
if (error.message() == "REPLY_MARKUP_INVALID") {
if (m->reply_markup == nullptr) {
- LOG(ERROR) << "Receive " << error.message() << " for " << oneline(to_string(get_message_object(dialog_id, m)));
+ LOG(ERROR) << "Receive " << error.message() << " for "
+ << oneline(to_string(get_message_object(dialog_id, m, "on_send_message_fail")));
} else {
LOG(ERROR) << "Receive " << error.message() << " for " << full_message_id << " with keyboard "
<< *m->reply_markup;
}
}
- LOG_IF(WARNING, error_code != 403) << "Fail to send " << full_message_id << " with the error " << error;
+ if (error_code != 403 && !(error_code == 500 && G()->close_flag())) {
+ LOG(WARNING) << "Failed to send " << full_message_id << " with the error " << error;
+ }
if (error_code <= 0) {
error_code = 500;
}
fail_send_message(full_message_id, error_code, error_message);
}
-MessageId MessagesManager::get_next_message_id(Dialog *d, int32 type) {
+MessageId MessagesManager::get_next_message_id(Dialog *d, MessageType type) {
CHECK(d != nullptr);
- int64 last = std::max({d->last_message_id.get(), d->last_new_message_id.get(), d->last_database_message_id.get(),
- d->last_assigned_message_id.get(), d->last_clear_history_message_id.get(),
- d->deleted_last_message_id.get(), d->max_unavailable_message_id.get()});
- if (last < d->last_read_inbox_message_id.get() &&
- d->last_read_inbox_message_id.get() < d->last_new_message_id.get() + MessageId::FULL_TYPE_MASK) {
- last = d->last_read_inbox_message_id.get();
+ MessageId last_message_id =
+ std::max({d->last_message_id, d->last_new_message_id, d->last_database_message_id, d->last_assigned_message_id,
+ d->last_clear_history_message_id, d->deleted_last_message_id, d->max_unavailable_message_id,
+ d->max_added_message_id});
+ if (last_message_id < d->last_read_inbox_message_id &&
+ d->last_read_inbox_message_id < d->last_new_message_id.get_next_server_message_id()) {
+ last_message_id = d->last_read_inbox_message_id;
}
- if (last < d->last_read_outbox_message_id.get() &&
- d->last_read_outbox_message_id.get() < d->last_new_message_id.get() + MessageId::FULL_TYPE_MASK) {
- last = d->last_read_outbox_message_id.get();
+ if (last_message_id < d->last_read_outbox_message_id &&
+ d->last_read_outbox_message_id < d->last_new_message_id.get_next_server_message_id()) {
+ last_message_id = d->last_read_outbox_message_id;
}
- int64 base = (last + MessageId::TYPE_MASK + 1) & ~MessageId::TYPE_MASK;
- d->last_assigned_message_id = MessageId(base + type);
+ d->last_assigned_message_id = last_message_id.get_next_message_id(type);
+ if (d->last_assigned_message_id > MessageId::max()) {
+ LOG(FATAL) << "Force restart because of message_id overflow: " << d->last_assigned_message_id;
+ }
+ CHECK(d->last_assigned_message_id.is_valid());
+ if (d->last_assigned_message_id.get_prev_server_message_id() != last_message_id.get_prev_server_message_id()) {
+ d->had_yet_unsent_message_id_overflow = true;
+ }
return d->last_assigned_message_id;
}
MessageId MessagesManager::get_next_yet_unsent_message_id(Dialog *d) {
- return get_next_message_id(d, MessageId::TYPE_YET_UNSENT);
+ return get_next_message_id(d, MessageType::YetUnsent);
}
MessageId MessagesManager::get_next_local_message_id(Dialog *d) {
- return get_next_message_id(d, MessageId::TYPE_LOCAL);
+ return get_next_message_id(d, MessageType::Local);
+}
+
+MessageId MessagesManager::get_next_yet_unsent_scheduled_message_id(Dialog *d, int32 date) {
+ CHECK(date > 0);
+
+ MessageId message_id(ScheduledServerMessageId(1), date);
+
+ auto it = MessagesConstIterator(d, MessageId(ScheduledServerMessageId(), date + 1, true));
+ if (*it != nullptr && (*it)->message_id > message_id) {
+ message_id = (*it)->message_id;
+ }
+
+ auto &last_assigned_message_id = d->last_assigned_scheduled_message_id[date];
+ if (last_assigned_message_id != MessageId() && last_assigned_message_id > message_id) {
+ message_id = last_assigned_message_id;
+ }
+
+ last_assigned_message_id = message_id.get_next_message_id(MessageType::YetUnsent);
+ return last_assigned_message_id;
}
void MessagesManager::fail_send_message(FullMessageId full_message_id, int error_code, const string &error_message) {
@@ -18873,14 +32479,19 @@ void MessagesManager::fail_send_message(FullMessageId full_message_id, int error
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
MessageId old_message_id = full_message_id.get_message_id();
+ CHECK(old_message_id.is_valid() || old_message_id.is_valid_scheduled());
CHECK(old_message_id.is_yet_unsent());
+ update_reply_to_message_id(dialog_id, old_message_id, MessageId(), false, "fail_send_message");
+
bool need_update_dialog_pos = false;
+ being_readded_message_id_ = full_message_id;
unique_ptr<Message> message = delete_message(d, old_message_id, false, &need_update_dialog_pos, "fail send message");
if (message == nullptr) {
// message has already been deleted by the user or sent to inaccessible channel
// don't need to send update to the user, because the message has already been deleted
// and there is nothing to be deleted from the server
+ being_readded_message_id_ = FullMessageId();
return;
}
@@ -18889,47 +32500,112 @@ void MessagesManager::fail_send_message(FullMessageId full_message_id, int error
// dump_debug_message_op(d, 5);
}
- auto new_message_id = MessageId(old_message_id.get() - MessageId::TYPE_YET_UNSENT + MessageId::TYPE_LOCAL);
- if (get_message_force(d, new_message_id) != nullptr || d->deleted_message_ids.count(new_message_id)) {
- new_message_id = get_next_local_message_id(d);
+ MessageId new_message_id = old_message_id.get_next_message_id(MessageType::Local); // trying to keep message position
+ if (!old_message_id.is_scheduled()) {
+ if (get_message_force(d, new_message_id, "fail_send_message") != nullptr || is_deleted_message(d, new_message_id) ||
+ new_message_id <= d->last_clear_history_message_id) {
+ new_message_id = get_next_local_message_id(d);
+ } else if (new_message_id > d->last_assigned_message_id) {
+ d->last_assigned_message_id = new_message_id;
+ }
+ } else {
+ while (get_message_force(d, new_message_id, "fail_send_message") != nullptr ||
+ is_deleted_message(d, new_message_id)) {
+ new_message_id = new_message_id.get_next_message_id(MessageType::Local);
+ }
}
- message->message_id = new_message_id;
- CHECK(message->message_id.is_valid());
- message->random_y = get_random_y(message->message_id);
+ set_message_id(message, new_message_id);
+ if (old_message_id.is_scheduled()) {
+ CHECK(message->message_id.is_valid_scheduled());
+ } else {
+ CHECK(message->message_id.is_valid());
+ }
+ if (message->forward_info == nullptr && message->view_count == 1) {
+ message->view_count = 0;
+ }
message->is_failed_to_send = true;
+ message->send_error_code = error_code;
+ message->send_error_message = error_message;
+ message->try_resend_at = 0.0;
+ auto retry_after = Global::get_retry_after(error_code, error_message);
+ if (retry_after > 0) {
+ message->try_resend_at = Time::now() + retry_after;
+ }
+ update_failed_to_send_message_content(td_, message->content);
+ message->from_database = false;
message->have_previous = true;
message->have_next = true;
bool need_update = false;
Message *m = add_message_to_dialog(dialog_id, std::move(message), false, &need_update, &need_update_dialog_pos,
"fail_send_message");
- CHECK(m != nullptr) << "Failed to add failed to send " << new_message_id << " to " << dialog_id << " due to "
- << debug_add_message_to_dialog_fail_reason;
+ LOG_CHECK(m != nullptr) << "Failed to add failed to send " << new_message_id << " to " << dialog_id << " due to "
+ << debug_add_message_to_dialog_fail_reason_;
+ if (!m->message_id.is_scheduled()) {
+ // add_message_to_dialog will not update counts, because need_update == false
+ update_message_count_by_index(d, +1, m);
+ update_reply_count_by_message(d, +1, m); // no-op because the message isn't server
+ }
+ register_new_local_message_id(d, m);
LOG(INFO) << "Send updateMessageSendFailed for " << full_message_id;
- d->yet_unsent_message_id_to_persistent_message_id.emplace(old_message_id, m->message_id);
+ if (!td_->auth_manager_->is_bot()) {
+ d->yet_unsent_message_id_to_persistent_message_id.emplace(old_message_id, m->message_id);
+ }
send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateMessageSendFailed>(get_message_object(dialog_id, m), old_message_id.get(),
- error_code, error_message));
+ make_tl_object<td_api::updateMessageSendFailed>(get_message_object(dialog_id, m, "fail_send_message"),
+ old_message_id.get(), error_code, error_message));
if (need_update_dialog_pos) {
send_update_chat_last_message(d, "fail_send_message");
}
+ being_readded_message_id_ = FullMessageId();
+}
+
+void MessagesManager::fail_send_message(FullMessageId full_message_id, Status error) {
+ fail_send_message(full_message_id, error.code(), error.message().str());
+}
+
+void MessagesManager::fail_edit_message_media(FullMessageId full_message_id, Status &&error) {
+ auto dialog_id = full_message_id.get_dialog_id();
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ MessageId message_id = full_message_id.get_message_id();
+ CHECK(message_id.is_any_server());
+
+ auto m = get_message(d, message_id);
+ if (m == nullptr) {
+ // message has already been deleted by the user or sent to inaccessible channel
+ return;
+ }
+ CHECK(m->edited_content != nullptr);
+ m->edit_promise.set_error(std::move(error));
+ cancel_edit_message_media(dialog_id, m, "Failed to edit message. MUST BE IGNORED");
}
-void MessagesManager::on_update_dialog_draft_message(DialogId dialog_id,
+void MessagesManager::on_update_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id,
tl_object_ptr<telegram_api::DraftMessage> &&draft_message) {
if (!dialog_id.is_valid()) {
LOG(ERROR) << "Receive update chat draft in invalid " << dialog_id;
return;
}
- auto d = get_dialog_force(dialog_id);
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_draft_message");
if (d == nullptr) {
LOG(INFO) << "Ignore update chat draft in unknown " << dialog_id;
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ LOG(ERROR) << "Have no read access to " << dialog_id << " to repair chat draft message";
+ } else {
+ send_get_dialog_query(dialog_id, Auto(), 0, "on_update_dialog_draft_message");
+ }
return;
}
- update_dialog_draft_message(d, get_draft_message(td_->contacts_manager_.get(), std::move(draft_message)), true, true);
+ auto draft = get_draft_message(td_->contacts_manager_.get(), std::move(draft_message));
+ if (top_thread_message_id.is_valid()) {
+ // TODO update thread message draft
+ return;
+ }
+ update_dialog_draft_message(d, std::move(draft), true, true);
}
bool MessagesManager::update_dialog_draft_message(Dialog *d, unique_ptr<DraftMessage> &&draft_message, bool from_update,
@@ -18943,7 +32619,7 @@ bool MessagesManager::update_dialog_draft_message(Dialog *d, unique_ptr<DraftMes
if (d->draft_message != nullptr) {
d->draft_message = nullptr;
if (need_update_dialog_pos) {
- update_dialog_pos(d, false, "update_dialog_draft_message", false);
+ update_dialog_pos(d, "update_dialog_draft_message", false);
}
send_update_chat_draft_message(d);
return true;
@@ -18952,17 +32628,18 @@ bool MessagesManager::update_dialog_draft_message(Dialog *d, unique_ptr<DraftMes
if (d->draft_message != nullptr && d->draft_message->reply_to_message_id == draft_message->reply_to_message_id &&
d->draft_message->input_message_text == draft_message->input_message_text) {
if (d->draft_message->date < draft_message->date) {
+ d->draft_message->date = draft_message->date;
if (need_update_dialog_pos) {
- update_dialog_pos(d, false, "update_dialog_draft_message 2");
+ update_dialog_pos(d, "update_dialog_draft_message 2", false);
}
- d->draft_message->date = draft_message->date;
+ send_update_chat_draft_message(d);
return true;
}
} else {
if (!from_update || d->draft_message == nullptr || d->draft_message->date <= draft_message->date) {
d->draft_message = std::move(draft_message);
if (need_update_dialog_pos) {
- update_dialog_pos(d, false, "update_dialog_draft_message 3", false);
+ update_dialog_pos(d, "update_dialog_draft_message 3", false);
}
send_update_chat_draft_message(d);
return true;
@@ -18972,36 +32649,707 @@ bool MessagesManager::update_dialog_draft_message(Dialog *d, unique_ptr<DraftMes
return false;
}
-void MessagesManager::on_update_dialog_pinned(DialogId dialog_id, bool is_pinned) {
+void MessagesManager::on_update_dialog_is_pinned(FolderId folder_id, DialogId dialog_id, bool is_pinned) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
if (!dialog_id.is_valid()) {
- LOG(ERROR) << "Receive pinn of invalid " << dialog_id;
+ LOG(ERROR) << "Receive pin of invalid " << dialog_id;
+ return;
+ }
+
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_is_pinned");
+ if (d == nullptr) {
+ LOG(INFO) << "Can't apply updateDialogPinned in " << folder_id << " with unknown " << dialog_id;
+ on_update_pinned_dialogs(folder_id);
+ return;
+ }
+ if (d->order == DEFAULT_ORDER) {
+ // the chat can't be pinned or is already unpinned
+ // don't change it's folder_id
+ LOG(INFO) << "Can't apply updateDialogPinned in " << folder_id << " with " << dialog_id;
+ return;
+ }
+
+ auto *list = get_dialog_list(DialogListId(folder_id));
+ CHECK(list != nullptr);
+ if (!list->are_pinned_dialogs_inited_) {
+ return;
+ }
+
+ set_dialog_folder_id(d, folder_id);
+ set_dialog_is_pinned(DialogListId(folder_id), d, is_pinned);
+}
+
+void MessagesManager::on_update_pinned_dialogs(FolderId folder_id) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ // TODO log event + delete_log_event_promise
+
+ auto *list = get_dialog_list(DialogListId(folder_id));
+ if (list == nullptr || !list->are_pinned_dialogs_inited_) {
+ return;
+ }
+ // preload all pinned dialogs
+ int32 limit = narrow_cast<int32>(list->pinned_dialogs_.size()) +
+ (folder_id == FolderId::main() && sponsored_dialog_id_.is_valid() ? 1 : 0);
+ get_dialogs_from_list(DialogListId(folder_id), limit, Auto());
+ reload_pinned_dialogs(DialogListId(folder_id), Auto());
+}
+
+void MessagesManager::on_update_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive marking as unread of invalid " << dialog_id;
+ return;
+ }
+
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_is_marked_as_unread");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ if (is_marked_as_unread == d->is_marked_as_unread) {
+ return;
+ }
+
+ set_dialog_is_marked_as_unread(d, is_marked_as_unread);
+}
+
+void MessagesManager::set_dialog_is_marked_as_unread(Dialog *d, bool is_marked_as_unread) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ CHECK(d != nullptr);
+ CHECK(d->is_marked_as_unread != is_marked_as_unread);
+ d->is_marked_as_unread = is_marked_as_unread;
+ on_dialog_updated(d->dialog_id, "set_dialog_is_marked_as_unread");
+
+ LOG(INFO) << "Set " << d->dialog_id << " is marked as unread to " << is_marked_as_unread;
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_marked_as_unread";
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatIsMarkedAsUnread>(d->dialog_id.get(), is_marked_as_unread));
+
+ if (d->server_unread_count + d->local_unread_count == 0 && need_unread_counter(d->order)) {
+ int32 delta = d->is_marked_as_unread ? 1 : -1;
+ for (auto &list : get_dialog_lists(d)) {
+ if (list.is_dialog_unread_count_inited_) {
+ list.unread_dialog_total_count_ += delta;
+ list.unread_dialog_marked_count_ += delta;
+ if (is_dialog_muted(d)) {
+ list.unread_dialog_muted_count_ += delta;
+ list.unread_dialog_muted_marked_count_ += delta;
+ }
+ send_update_unread_chat_count(list, d->dialog_id, true, "set_dialog_is_marked_as_unread");
+ }
+ }
+
+ if (!dialog_filters_.empty()) {
+ update_dialog_lists(d, get_dialog_positions(d), true, false, "set_dialog_is_marked_as_unread");
+ }
+ }
+}
+
+void MessagesManager::on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked) {
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive pinned message in invalid " << dialog_id;
+ return;
+ }
+ if (dialog_id.get_type() == DialogType::User) {
+ td_->contacts_manager_->on_update_user_is_blocked(dialog_id.get_user_id(), is_blocked);
+ }
+
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_is_blocked");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ if (d->is_blocked == is_blocked) {
+ if (!d->is_is_blocked_inited) {
+ CHECK(is_blocked == false);
+ d->is_is_blocked_inited = true;
+ on_dialog_updated(dialog_id, "on_update_dialog_is_blocked");
+ }
+ return;
+ }
+
+ set_dialog_is_blocked(d, is_blocked);
+}
+
+void MessagesManager::set_dialog_is_blocked(Dialog *d, bool is_blocked) {
+ CHECK(d != nullptr);
+ CHECK(d->is_blocked != is_blocked);
+ d->is_blocked = is_blocked;
+ d->is_is_blocked_inited = true;
+ on_dialog_updated(d->dialog_id, "set_dialog_is_blocked");
+
+ LOG(INFO) << "Set " << d->dialog_id << " is_blocked to " << is_blocked;
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_is_blocked";
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatIsBlocked>(d->dialog_id.get(), is_blocked));
+
+ if (d->dialog_id.get_type() == DialogType::User) {
+ td_->contacts_manager_->on_update_user_is_blocked(d->dialog_id.get_user_id(), is_blocked);
+
+ if (d->know_action_bar) {
+ if (is_blocked) {
+ if (d->action_bar != nullptr) {
+ d->action_bar = nullptr;
+ send_update_chat_action_bar(d);
+ }
+ } else {
+ repair_dialog_action_bar(d, "on_dialog_user_is_blocked_updated");
+ }
+ }
+
+ td_->contacts_manager_->for_each_secret_chat_with_user(
+ d->dialog_id.get_user_id(), [this, is_blocked](SecretChatId secret_chat_id) {
+ DialogId dialog_id(secret_chat_id);
+ auto d = get_dialog(dialog_id); // must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent && d->is_blocked != is_blocked) {
+ set_dialog_is_blocked(d, is_blocked);
+ }
+ });
+ }
+}
+
+void MessagesManager::on_update_dialog_last_pinned_message_id(DialogId dialog_id, MessageId pinned_message_id) {
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive pinned message in invalid " << dialog_id;
+ return;
+ }
+ if (!pinned_message_id.is_valid() && pinned_message_id != MessageId()) {
+ LOG(ERROR) << "Receive as pinned message " << pinned_message_id;
+ return;
+ }
+
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_last_pinned_message_id");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ set_dialog_last_pinned_message_id(d, pinned_message_id);
+}
+
+void MessagesManager::set_dialog_last_pinned_message_id(Dialog *d, MessageId pinned_message_id) {
+ CHECK(d != nullptr);
+ Message *m = get_message_force(d, pinned_message_id, "set_dialog_last_pinned_message_id");
+ if (m != nullptr && update_message_is_pinned(d, m, true, "set_dialog_last_pinned_message_id")) {
+ on_message_changed(d, m, true, "set_dialog_last_pinned_message_id");
+ }
+
+ if (d->is_last_pinned_message_id_inited && d->last_pinned_message_id == pinned_message_id) {
+ return;
+ }
+ d->last_pinned_message_id = pinned_message_id;
+ d->is_last_pinned_message_id_inited = true;
+ on_dialog_updated(d->dialog_id, "set_dialog_last_pinned_message_id");
+
+ LOG(INFO) << "Set " << d->dialog_id << " pinned message to " << pinned_message_id;
+}
+
+void MessagesManager::drop_dialog_last_pinned_message_id(Dialog *d) {
+ d->last_pinned_message_id = MessageId();
+ d->is_last_pinned_message_id_inited = false;
+ on_dialog_updated(d->dialog_id, "drop_dialog_last_pinned_message_id");
+
+ LOG(INFO) << "Drop " << d->dialog_id << " pinned message";
+
+ create_actor<SleepActor>(
+ "ReloadDialogFullInfoActor", 1.0,
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id](Result<Unit> result) {
+ send_closure(actor_id, &MessagesManager::reload_dialog_info_full, dialog_id,
+ "drop_dialog_last_pinned_message_id");
+ }))
+ .release();
+}
+
+void MessagesManager::on_update_dialog_theme_name(DialogId dialog_id, string theme_name) {
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive theme in invalid " << dialog_id;
+ return;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_theme_name");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ set_dialog_theme_name(d, std::move(theme_name));
+}
+
+void MessagesManager::set_dialog_theme_name(Dialog *d, string theme_name) {
+ CHECK(d != nullptr);
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ bool is_changed = d->theme_name != theme_name;
+ if (!is_changed && d->is_theme_name_inited) {
+ return;
+ }
+ d->theme_name = std::move(theme_name);
+ d->is_theme_name_inited = true;
+
+ if (is_changed) {
+ LOG(INFO) << "Set " << d->dialog_id << " theme to \"" << d->theme_name << '"';
+ send_update_chat_theme(d);
+ } else {
+ on_dialog_updated(d->dialog_id, "set_dialog_theme_name");
+ }
+}
+
+void MessagesManager::drop_dialog_pending_join_requests(DialogId dialog_id) {
+ CHECK(dialog_id.is_valid());
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+ auto d = get_dialog(dialog_id); // called from update_chat/channel, must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent) {
+ set_dialog_pending_join_requests(d, 0, {});
+ }
+}
+
+void MessagesManager::on_update_dialog_pending_join_requests(DialogId dialog_id, int32 pending_join_request_count,
+ vector<int64> pending_requesters) {
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Receive pending join request count in invalid " << dialog_id;
+ return;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_pending_join_request_count");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ set_dialog_pending_join_requests(d, pending_join_request_count, UserId::get_user_ids(pending_requesters));
+}
+
+void MessagesManager::fix_pending_join_requests(DialogId dialog_id, int32 &pending_join_request_count,
+ vector<UserId> &pending_join_request_user_ids) const {
+ td::remove_if(pending_join_request_user_ids, [](UserId user_id) { return !user_id.is_valid(); });
+
+ bool need_drop_pending_join_requests = [&] {
+ if (pending_join_request_count < 0) {
+ return true;
+ }
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::SecretChat:
+ return true;
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ auto status = td_->contacts_manager_->get_chat_status(chat_id);
+ if (!status.can_manage_invite_links()) {
+ return true;
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ auto channel_id = dialog_id.get_channel_id();
+ auto status = td_->contacts_manager_->get_channel_permissions(channel_id);
+ if (!status.can_manage_invite_links()) {
+ return true;
+ }
+ break;
+ }
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+ return false;
+ }();
+ if (need_drop_pending_join_requests) {
+ pending_join_request_count = 0;
+ pending_join_request_user_ids.clear();
+ } else if (static_cast<size_t>(pending_join_request_count) < pending_join_request_user_ids.size()) {
+ LOG(ERROR) << "Fix pending join request count from " << pending_join_request_count << " to "
+ << pending_join_request_user_ids.size();
+ pending_join_request_count = narrow_cast<int32>(pending_join_request_user_ids.size());
+ }
+
+ static constexpr size_t MAX_PENDING_JOIN_REQUESTS = 3;
+ if (pending_join_request_user_ids.size() > MAX_PENDING_JOIN_REQUESTS) {
+ pending_join_request_user_ids.resize(MAX_PENDING_JOIN_REQUESTS);
+ }
+}
+
+void MessagesManager::set_dialog_pending_join_requests(Dialog *d, int32 pending_join_request_count,
+ vector<UserId> pending_join_request_user_ids) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ CHECK(d != nullptr);
+ fix_pending_join_requests(d->dialog_id, pending_join_request_count, pending_join_request_user_ids);
+ if (d->pending_join_request_count == pending_join_request_count &&
+ d->pending_join_request_user_ids == pending_join_request_user_ids) {
+ return;
+ }
+ d->pending_join_request_count = pending_join_request_count;
+ d->pending_join_request_user_ids = std::move(pending_join_request_user_ids);
+ send_update_chat_pending_join_requests(d);
+}
+
+void MessagesManager::repair_dialog_scheduled_messages(Dialog *d) {
+ if (td_->auth_manager_->is_bot() || d->dialog_id.get_type() == DialogType::SecretChat) {
+ return;
+ }
+
+ if (d->last_repair_scheduled_messages_generation == scheduled_messages_sync_generation_) {
+ return;
+ }
+ d->last_repair_scheduled_messages_generation = scheduled_messages_sync_generation_;
+
+ // TODO create log event
+ auto dialog_id = d->dialog_id;
+ LOG(INFO) << "Repair scheduled messages in " << dialog_id << " with generation "
+ << d->last_repair_scheduled_messages_generation;
+ get_dialog_scheduled_messages(dialog_id, false, true,
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Unit) {
+ send_closure(G()->messages_manager(), &MessagesManager::get_dialog_scheduled_messages,
+ dialog_id, true, true, Promise<Unit>());
+ }));
+}
+
+void MessagesManager::on_update_dialog_has_scheduled_server_messages(DialogId dialog_id,
+ bool has_scheduled_server_messages) {
+ CHECK(dialog_id.is_valid());
+ if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) {
+ return;
+ }
+
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_has_scheduled_server_messages");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ LOG(INFO) << "Receive has_scheduled_server_messages = " << has_scheduled_server_messages << " in " << dialog_id;
+ if (d->has_scheduled_server_messages != has_scheduled_server_messages) {
+ set_dialog_has_scheduled_server_messages(d, has_scheduled_server_messages);
+ } else if (has_scheduled_server_messages !=
+ (d->has_scheduled_database_messages || d->scheduled_messages != nullptr)) {
+ repair_dialog_scheduled_messages(d);
+ }
+}
+
+void MessagesManager::set_dialog_has_scheduled_server_messages(Dialog *d, bool has_scheduled_server_messages) {
+ CHECK(d != nullptr);
+ CHECK(d->has_scheduled_server_messages != has_scheduled_server_messages);
+ d->has_scheduled_server_messages = has_scheduled_server_messages;
+ repair_dialog_scheduled_messages(d);
+ on_dialog_updated(d->dialog_id, "set_dialog_has_scheduled_server_messages");
+
+ LOG(INFO) << "Set " << d->dialog_id << " has_scheduled_server_messages to " << has_scheduled_server_messages;
+
+ send_update_chat_has_scheduled_messages(d, false);
+}
+
+void MessagesManager::set_dialog_has_scheduled_database_messages(DialogId dialog_id,
+ bool has_scheduled_database_messages) {
+ if (G()->close_flag()) {
+ return;
+ }
+ return set_dialog_has_scheduled_database_messages_impl(get_dialog(dialog_id), has_scheduled_database_messages);
+}
+
+void MessagesManager::set_dialog_has_scheduled_database_messages_impl(Dialog *d, bool has_scheduled_database_messages) {
+ CHECK(d != nullptr);
+ if (d->has_scheduled_database_messages == has_scheduled_database_messages) {
+ return;
+ }
+
+ if (d->has_scheduled_database_messages && d->scheduled_messages != nullptr &&
+ !d->scheduled_messages->message_id.is_yet_unsent()) {
+ // to prevent race between add_message_to_database and check of has_scheduled_database_messages
+ return;
+ }
+
+ CHECK(G()->parameters().use_message_db);
+
+ d->has_scheduled_database_messages = has_scheduled_database_messages;
+ on_dialog_updated(d->dialog_id, "set_dialog_has_scheduled_database_messages");
+}
+
+void MessagesManager::on_update_dialog_folder_id(DialogId dialog_id, FolderId folder_id) {
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_folder_id");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ set_dialog_folder_id(d, folder_id);
+}
+
+void MessagesManager::set_dialog_folder_id(Dialog *d, FolderId folder_id) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ CHECK(d != nullptr);
+
+ if (d->folder_id == folder_id) {
+ if (!d->is_folder_id_inited) {
+ LOG(INFO) << "Folder of " << d->dialog_id << " is still " << folder_id;
+ do_set_dialog_folder_id(d, folder_id);
+ }
+ return;
+ }
+
+ LOG(INFO) << "Change " << d->dialog_id << " folder from " << d->folder_id << " to " << folder_id;
+
+ auto dialog_positions = get_dialog_positions(d);
+
+ if (get_dialog_pinned_order(DialogListId(d->folder_id), d->dialog_id) != DEFAULT_ORDER) {
+ set_dialog_is_pinned(DialogListId(d->folder_id), d, false, false);
+ }
+
+ DialogDate dialog_date(d->order, d->dialog_id);
+ if (get_dialog_folder(d->folder_id)->ordered_dialogs_.erase(dialog_date) == 0) {
+ LOG_IF(ERROR, d->order != DEFAULT_ORDER) << d->dialog_id << " not found in the chat list";
+ }
+
+ do_set_dialog_folder_id(d, folder_id);
+
+ get_dialog_folder(d->folder_id)->ordered_dialogs_.insert(dialog_date);
+
+ update_dialog_lists(d, std::move(dialog_positions), true, false, "set_dialog_folder_id");
+}
+
+void MessagesManager::do_set_dialog_folder_id(Dialog *d, FolderId folder_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (d->folder_id == folder_id && d->is_folder_id_inited) {
return;
}
- auto d = get_dialog_force(dialog_id);
+
+ d->folder_id = folder_id;
+ d->is_folder_id_inited = true;
+
+ if (d->dialog_id.get_type() == DialogType::SecretChat) {
+ // need to change action bar only for the secret chat and keep unarchive for the main chat
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
+ if (d->is_update_new_chat_sent && user_id.is_valid()) {
+ const Dialog *user_d = get_dialog(DialogId(user_id));
+ if (user_d != nullptr && user_d->action_bar != nullptr && user_d->action_bar->can_unarchive()) {
+ send_closure(
+ G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatActionBar>(d->dialog_id.get(), get_chat_action_bar_object(d)));
+ }
+ }
+ } else if (folder_id != FolderId::archive() && d->action_bar != nullptr && d->action_bar->on_dialog_unarchived()) {
+ send_update_chat_action_bar(d);
+ }
+
+ on_dialog_updated(d->dialog_id, "do_set_dialog_folder_id");
+}
+
+void MessagesManager::on_update_dialog_group_call(DialogId dialog_id, bool has_active_group_call,
+ bool is_group_call_empty, const char *source, bool force) {
+ LOG(INFO) << "Update voice chat in " << dialog_id << " with has_active_voice_chat = " << has_active_group_call
+ << " and is_voice_chat_empty = " << is_group_call_empty << " from " << source;
+ CHECK(dialog_id.is_valid());
+ Dialog *d = get_dialog(dialog_id); // must not create the Dialog, because it is called from on_get_chat
if (d == nullptr) {
- LOG(WARNING) << "Can't apply updateDialogPinned with " << dialog_id;
- // TODO logevent + promise
- send_closure(td_->create_net_actor<GetPinnedDialogsQuery>(Promise<>()), &GetPinnedDialogsQuery::send,
- get_sequence_dispatcher_id(DialogId(), -1));
+ LOG(INFO) << "Can't find " << dialog_id;
+ pending_dialog_group_call_updates_[dialog_id] = {has_active_group_call, is_group_call_empty};
return;
}
- if (!is_pinned && d->pinned_order == DEFAULT_ORDER) {
+
+ if (!has_active_group_call) {
+ is_group_call_empty = false;
+ }
+ if (d->active_group_call_id.is_valid() && has_active_group_call && is_group_call_empty &&
+ (td_->group_call_manager_->is_group_call_being_joined(d->active_group_call_id) ||
+ td_->group_call_manager_->is_group_call_joined(d->active_group_call_id))) {
+ LOG(INFO) << "Fix is_group_call_empty to false";
+ is_group_call_empty = false;
+ }
+ if (d->has_active_group_call == has_active_group_call && d->is_group_call_empty == is_group_call_empty) {
return;
}
- set_dialog_is_pinned(d, is_pinned);
- update_dialog_pos(d, false, "on_update_dialog_pinned");
+ if (!force && d->active_group_call_id.is_valid() && has_active_group_call &&
+ td_->group_call_manager_->is_group_call_being_joined(d->active_group_call_id)) {
+ LOG(INFO) << "Ignore update in a being joined group call";
+ return;
+ }
+
+ if (d->has_active_group_call && !has_active_group_call && d->active_group_call_id.is_valid()) {
+ d->active_group_call_id = InputGroupCallId();
+ d->has_active_group_call = false;
+ d->is_group_call_empty = false;
+ send_update_chat_video_chat(d);
+ } else if (d->has_active_group_call && has_active_group_call) {
+ d->is_group_call_empty = is_group_call_empty;
+ send_update_chat_video_chat(d);
+ } else {
+ d->has_active_group_call = has_active_group_call;
+ d->is_group_call_empty = is_group_call_empty;
+ on_dialog_updated(dialog_id, "on_update_dialog_group_call");
+
+ if (has_active_group_call && !d->active_group_call_id.is_valid() && !td_->auth_manager_->is_bot()) {
+ repair_dialog_active_group_call_id(dialog_id);
+ }
+ }
}
-void MessagesManager::on_update_pinned_dialogs() {
- // TODO logevent + promise
- send_closure(td_->create_net_actor<GetPinnedDialogsQuery>(Promise<>()), &GetPinnedDialogsQuery::send,
- get_sequence_dispatcher_id(DialogId(), -1));
+void MessagesManager::on_update_dialog_group_call_id(DialogId dialog_id, InputGroupCallId input_group_call_id) {
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_group_call_id");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ if (d->active_group_call_id != input_group_call_id) {
+ LOG(INFO) << "Update active group call in " << dialog_id << " to " << input_group_call_id;
+ d->active_group_call_id = input_group_call_id;
+ bool has_active_group_call = input_group_call_id.is_valid();
+ if (has_active_group_call != d->has_active_group_call) {
+ d->has_active_group_call = has_active_group_call;
+ if (!has_active_group_call) {
+ d->is_group_call_empty = false;
+ }
+ }
+ send_update_chat_video_chat(d);
+ }
+}
+
+void MessagesManager::on_update_dialog_default_join_group_call_as_dialog_id(DialogId dialog_id,
+ DialogId default_join_as_dialog_id,
+ bool force) {
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_default_join_group_call_as_dialog_id");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ if (!force && d->active_group_call_id.is_valid() &&
+ td_->group_call_manager_->is_group_call_being_joined(d->active_group_call_id)) {
+ LOG(INFO) << "Ignore default_join_as_dialog_id update in a being joined group call";
+ return;
+ }
+
+ if (default_join_as_dialog_id.is_valid()) {
+ if (default_join_as_dialog_id.get_type() != DialogType::User) {
+ force_create_dialog(default_join_as_dialog_id, "on_update_dialog_default_join_group_call_as_dialog_id");
+ } else if (!td_->contacts_manager_->have_user_force(default_join_as_dialog_id.get_user_id()) ||
+ default_join_as_dialog_id != get_my_dialog_id()) {
+ default_join_as_dialog_id = DialogId();
+ }
+ }
+
+ if (d->default_join_group_call_as_dialog_id != default_join_as_dialog_id) {
+ d->default_join_group_call_as_dialog_id = default_join_as_dialog_id;
+ send_update_chat_video_chat(d);
+ }
+}
+
+void MessagesManager::on_update_dialog_default_send_message_as_dialog_id(DialogId dialog_id,
+ DialogId default_send_as_dialog_id,
+ bool force) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+ auto dialog_type = dialog_id.get_type();
+ if (dialog_type != DialogType::Channel || is_broadcast_channel(dialog_id)) {
+ if (default_send_as_dialog_id != DialogId()) {
+ LOG(ERROR) << "Receive message sender " << default_send_as_dialog_id << " in " << dialog_id;
+ }
+ return;
+ }
+
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_default_send_message_as_dialog_id");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ if (!force) {
+ // TODO ignore update if have being sent messages
+ }
+
+ if (default_send_as_dialog_id.is_valid()) {
+ if (default_send_as_dialog_id.get_type() != DialogType::User) {
+ force_create_dialog(default_send_as_dialog_id, "on_update_dialog_default_send_message_as_dialog_id");
+ } else if (!td_->contacts_manager_->have_user_force(default_send_as_dialog_id.get_user_id()) ||
+ default_send_as_dialog_id != get_my_dialog_id()) {
+ default_send_as_dialog_id = DialogId();
+ }
+ }
+
+ if (d->default_send_message_as_dialog_id != default_send_as_dialog_id) {
+ if (force || default_send_as_dialog_id.is_valid() ||
+ (created_public_broadcasts_inited_ && created_public_broadcasts_.empty())) {
+ LOG(INFO) << "Set message sender in " << dialog_id << " to " << default_send_as_dialog_id;
+ d->need_drop_default_send_message_as_dialog_id = false;
+ d->default_send_message_as_dialog_id = default_send_as_dialog_id;
+ send_update_chat_message_sender(d);
+ } else {
+ LOG(INFO) << "Postpone removal of message sender in " << dialog_id;
+ d->need_drop_default_send_message_as_dialog_id = true;
+ }
+ on_dialog_updated(d->dialog_id, "on_update_dialog_default_send_message_as_dialog_id");
+ } else if (default_send_as_dialog_id.is_valid() && d->need_drop_default_send_message_as_dialog_id) {
+ LOG(INFO) << "Don't remove message sender in " << dialog_id;
+ d->need_drop_default_send_message_as_dialog_id = false;
+ on_dialog_updated(d->dialog_id, "on_update_dialog_default_send_message_as_dialog_id");
+ }
+}
+
+void MessagesManager::on_update_dialog_message_ttl(DialogId dialog_id, MessageTtl message_ttl) {
+ auto d = get_dialog_force(dialog_id, "on_update_dialog_message_ttl");
+ if (d == nullptr) {
+ // nothing to do
+ return;
+ }
+
+ if (d->message_ttl != message_ttl) {
+ d->message_ttl = message_ttl;
+ d->is_message_ttl_inited = true;
+ send_update_chat_message_ttl(d);
+ }
+ if (!d->is_message_ttl_inited) {
+ d->is_message_ttl_inited = true;
+ on_dialog_updated(dialog_id, "on_update_dialog_message_ttl");
+ }
+}
+
+void MessagesManager::on_update_dialog_filters() {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ schedule_dialog_filters_reload(0.0);
}
void MessagesManager::on_create_new_dialog_success(int64 random_id, tl_object_ptr<telegram_api::Updates> &&updates,
DialogType expected_type, Promise<Unit> &&promise) {
- auto sent_messages = td_->updates_manager_->get_new_messages(updates.get());
- auto sent_messages_random_ids = td_->updates_manager_->get_sent_messages_random_ids(updates.get());
+ auto sent_messages = UpdatesManager::get_new_messages(updates.get());
+ auto sent_messages_random_ids = UpdatesManager::get_sent_messages_random_ids(updates.get());
if (sent_messages.size() != 1u || sent_messages_random_ids.size() != 1u) {
LOG(ERROR) << "Receive wrong result for create group or channel chat " << oneline(to_string(updates));
return on_create_new_dialog_fail(random_id, Status::Error(500, "Unsupported server response"), std::move(promise));
@@ -19031,14 +33379,14 @@ void MessagesManager::on_create_new_dialog_success(int64 random_id, tl_object_pt
return promise.set_value(Unit());
}
- if (pending_created_dialogs_.find(dialog_id) == pending_created_dialogs_.end()) {
+ if (pending_created_dialogs_.count(dialog_id) == 0) {
pending_created_dialogs_.emplace(dialog_id, std::move(promise));
} else {
- LOG(ERROR) << dialog_id << " returned twice as result of chat creation";
+ LOG(ERROR) << "Receive twice " << dialog_id << " as result of chat creation";
return on_create_new_dialog_fail(random_id, Status::Error(500, "Chat was created earlier"), std::move(promise));
}
- td_->updates_manager_->on_get_updates(std::move(updates));
+ td_->updates_manager_->on_get_updates(std::move(updates), Promise<Unit>());
}
void MessagesManager::on_create_new_dialog_fail(int64 random_id, Status error, Promise<Unit> &&promise) {
@@ -19055,35 +33403,201 @@ void MessagesManager::on_create_new_dialog_fail(int64 random_id, Status error, P
td_->updates_manager_->get_difference("on_create_new_dialog_fail");
}
+void MessagesManager::on_dialog_bots_updated(DialogId dialog_id, vector<UserId> bot_user_ids, bool from_database) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto d = from_database ? get_dialog(dialog_id) : get_dialog_force(dialog_id, "on_dialog_bots_updated");
+ if (d == nullptr) {
+ return;
+ }
+
+ bool has_bots = !bot_user_ids.empty();
+ if (!d->is_has_bots_inited || d->has_bots != has_bots) {
+ set_dialog_has_bots(d, has_bots);
+ on_dialog_updated(dialog_id, "on_dialog_bots_updated");
+ }
+
+ if (d->reply_markup_message_id != MessageId()) {
+ const Message *m = get_message_force(d, d->reply_markup_message_id, "on_dialog_bots_updated");
+ if (m == nullptr || (m->sender_user_id.is_valid() && !td::contains(bot_user_ids, m->sender_user_id))) {
+ LOG(INFO) << "Remove reply markup in " << dialog_id << ", because bot "
+ << (m == nullptr ? UserId() : m->sender_user_id) << " isn't a member of the chat";
+ set_dialog_reply_markup(d, MessageId());
+ }
+ }
+}
+
+void MessagesManager::set_dialog_has_bots(Dialog *d, bool has_bots) {
+ CHECK(d != nullptr);
+ LOG_CHECK(d->is_update_new_chat_sent) << "Wrong " << d->dialog_id << " in set_dialog_has_bots";
+
+ LOG(INFO) << "Set " << d->dialog_id << " has_bots to " << has_bots;
+
+ auto old_skip_bot_commands = need_skip_bot_commands(d->dialog_id, nullptr);
+ d->has_bots = has_bots;
+ d->is_has_bots_inited = true;
+ auto new_skip_bot_commands = need_skip_bot_commands(d->dialog_id, nullptr);
+ if (old_skip_bot_commands != new_skip_bot_commands) {
+ auto it = dialog_bot_command_message_ids_.find(d->dialog_id);
+ if (it != dialog_bot_command_message_ids_.end()) {
+ for (auto message_id : it->second.message_ids) {
+ auto m = get_message(d, message_id);
+ LOG_CHECK(m != nullptr) << d->dialog_id << ' ' << message_id;
+ send_update_message_content_impl(d->dialog_id, m, "set_dialog_has_bots");
+ }
+ }
+ }
+}
+
void MessagesManager::on_dialog_photo_updated(DialogId dialog_id) {
- if (have_dialog(dialog_id)) {
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateChatPhoto>(
- dialog_id.get(), get_chat_photo_object(td_->file_manager_.get(), get_dialog_photo(dialog_id))));
+ auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent) {
+ send_closure(
+ G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatPhoto>(
+ dialog_id.get(), get_chat_photo_info_object(td_->file_manager_.get(), get_dialog_photo(dialog_id))));
+ } else if (d != nullptr && d->is_update_new_chat_being_sent) {
+ const auto *photo = get_dialog_photo(dialog_id);
+ if (photo == nullptr) {
+ LOG(ERROR) << "Removed photo of " << dialog_id << " while the chat is being added";
+ } else {
+ LOG(ERROR) << "Changed photo of " << dialog_id << " while the chat is being added to " << *photo;
+ }
}
}
void MessagesManager::on_dialog_title_updated(DialogId dialog_id) {
- auto d = get_dialog(dialog_id);
+ auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
if (d != nullptr) {
update_dialogs_hints(d);
+ if (d->is_update_new_chat_sent) {
+ send_closure(G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateChatTitle>(dialog_id.get(), get_dialog_title(dialog_id)));
+ }
+ }
+}
+
+void MessagesManager::on_dialog_default_permissions_updated(DialogId dialog_id) {
+ auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent) {
send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateChatTitle>(dialog_id.get(), get_dialog_title(dialog_id)));
+ td_api::make_object<td_api::updateChatPermissions>(
+ dialog_id.get(), get_dialog_default_permissions(dialog_id).get_chat_permissions_object()));
}
}
-DialogId MessagesManager::resolve_dialog_username(const string &username) {
- auto it = resolved_usernames_.find(clean_username(username));
- if (it != resolved_usernames_.end()) {
- return it->second.dialog_id;
+void MessagesManager::on_dialog_has_protected_content_updated(DialogId dialog_id) {
+ auto d = get_dialog(dialog_id); // called from update_chat, must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent) {
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateChatHasProtectedContent>(
+ dialog_id.get(), get_dialog_has_protected_content(dialog_id)));
}
+}
+
+void MessagesManager::on_dialog_user_is_contact_updated(DialogId dialog_id, bool is_contact) {
+ CHECK(dialog_id.get_type() == DialogType::User);
+ auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent) {
+ if (d->know_action_bar) {
+ if (is_contact) {
+ if (d->action_bar != nullptr && d->action_bar->on_user_contact_added()) {
+ send_update_chat_action_bar(d);
+ }
+ } else {
+ repair_dialog_action_bar(d, "on_dialog_user_is_contact_updated");
+ }
+ }
- auto it2 = unaccessible_resolved_usernames_.find(clean_username(username));
- if (it2 != unaccessible_resolved_usernames_.end()) {
- return it2->second;
+ if (!dialog_filters_.empty() && d->order != DEFAULT_ORDER) {
+ update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_contact_updated");
+ td_->contacts_manager_->for_each_secret_chat_with_user(
+ d->dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) {
+ DialogId dialog_id(secret_chat_id);
+ auto d = get_dialog(dialog_id); // must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent && d->order != DEFAULT_ORDER) {
+ update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_contact_updated");
+ }
+ });
+ }
}
+}
- return DialogId();
+void MessagesManager::on_dialog_user_is_deleted_updated(DialogId dialog_id, bool is_deleted) {
+ CHECK(dialog_id.get_type() == DialogType::User);
+ auto d = get_dialog(dialog_id); // called from update_user, must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent) {
+ if (d->know_action_bar) {
+ if (is_deleted) {
+ if (d->action_bar != nullptr && d->action_bar->on_user_deleted()) {
+ send_update_chat_action_bar(d);
+ }
+ } else {
+ repair_dialog_action_bar(d, "on_dialog_user_is_deleted_updated");
+ }
+ }
+
+ if (!dialog_filters_.empty() && d->order != DEFAULT_ORDER) {
+ update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_deleted_updated");
+ td_->contacts_manager_->for_each_secret_chat_with_user(
+ dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) {
+ DialogId dialog_id(secret_chat_id);
+ auto d = get_dialog(dialog_id); // must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent && d->order != DEFAULT_ORDER) {
+ update_dialog_lists(d, get_dialog_positions(d), true, false, "on_dialog_user_is_deleted_updated");
+ }
+ });
+ }
+
+ if (is_deleted && d->has_bots) {
+ set_dialog_has_bots(d, false);
+ td_->contacts_manager_->for_each_secret_chat_with_user(
+ dialog_id.get_user_id(), [this](SecretChatId secret_chat_id) {
+ DialogId dialog_id(secret_chat_id);
+ auto d = get_dialog(dialog_id); // must not create the dialog
+ if (d != nullptr && d->is_update_new_chat_sent && d->has_bots) {
+ set_dialog_has_bots(d, false);
+ }
+ });
+ }
+ }
+}
+
+void MessagesManager::on_dialog_linked_channel_updated(DialogId dialog_id, ChannelId old_linked_channel_id,
+ ChannelId new_linked_channel_id) const {
+ CHECK(dialog_id.get_type() == DialogType::Channel);
+ if (td_->auth_manager_->is_bot() || !is_broadcast_channel(dialog_id)) {
+ return;
+ }
+ auto d = get_dialog(dialog_id); // no need to create the dialog
+ if (d == nullptr || !d->is_update_new_chat_sent) {
+ return;
+ }
+
+ vector<MessageId> message_ids;
+ find_messages(d->messages.get(), message_ids, [old_linked_channel_id, new_linked_channel_id](const Message *m) {
+ return !m->reply_info.is_empty() && m->reply_info.channel_id_.is_valid() &&
+ (m->reply_info.channel_id_ == old_linked_channel_id || m->reply_info.channel_id_ == new_linked_channel_id);
+ });
+ LOG(INFO) << "Found discussion messages " << message_ids;
+ for (auto message_id : message_ids) {
+ send_update_message_interaction_info(dialog_id, get_message(d, message_id));
+ if (message_id == d->last_message_id) {
+ send_update_chat_last_message_impl(d, "on_dialog_linked_channel_updated");
+ }
+ }
+}
+
+DialogId MessagesManager::resolve_dialog_username(const string &username) const {
+ auto cleaned_username = clean_username(username);
+ auto resolved_username = resolved_usernames_.get(cleaned_username);
+ if (resolved_username.dialog_id.is_valid()) {
+ return resolved_username.dialog_id;
+ }
+
+ return inaccessible_resolved_usernames_.get(cleaned_username);
}
DialogId MessagesManager::search_public_dialog(const string &username_to_search, bool force, Promise<Unit> &&promise) {
@@ -19097,27 +33611,33 @@ DialogId MessagesManager::search_public_dialog(const string &username_to_search,
}
DialogId dialog_id;
- auto it = resolved_usernames_.find(username);
- if (it != resolved_usernames_.end()) {
- if (it->second.expires_at < Time::now()) {
- td_->create_handler<ResolveUsernameQuery>(Promise<>())->send(username);
+ auto resolved_username = resolved_usernames_.get(username);
+ if (resolved_username.dialog_id.is_valid()) {
+ if (resolved_username.expires_at < Time::now()) {
+ td_->create_handler<ResolveUsernameQuery>(Promise<Unit>())->send(username);
}
- dialog_id = it->second.dialog_id;
+ dialog_id = resolved_username.dialog_id;
} else {
- auto it2 = unaccessible_resolved_usernames_.find(username);
- if (it2 != unaccessible_resolved_usernames_.end()) {
- dialog_id = it2->second;
- }
+ dialog_id = inaccessible_resolved_usernames_.get(username);
}
if (dialog_id.is_valid()) {
if (have_input_peer(dialog_id, AccessRights::Read)) {
+ if (!force && reload_voice_chat_on_search_usernames_.count(username)) {
+ reload_voice_chat_on_search_usernames_.erase(username);
+ if (dialog_id.get_type() == DialogType::Channel) {
+ td_->contacts_manager_->reload_channel_full(dialog_id.get_channel_id(), std::move(promise),
+ "search_public_dialog");
+ return DialogId();
+ }
+ }
+
if (td_->auth_manager_->is_bot()) {
- force_create_dialog(dialog_id, "search public dialog");
+ force_create_dialog(dialog_id, "search_public_dialog", true);
} else {
- const Dialog *d = get_dialog_force(dialog_id);
- if (d == nullptr || !d->notification_settings.is_synchronized) {
- send_get_dialog_query(dialog_id, std::move(promise));
+ const Dialog *d = get_dialog_force(dialog_id, "search_public_dialog");
+ if (!is_dialog_inited(d)) {
+ send_get_dialog_query(dialog_id, std::move(promise), 0, "search_public_dialog");
return DialogId();
}
}
@@ -19127,7 +33647,7 @@ DialogId MessagesManager::search_public_dialog(const string &username_to_search,
} else {
// bot username maybe known despite there is no access_hash
if (force || dialog_id.get_type() != DialogType::User) {
- force_create_dialog(dialog_id, "search public dialog");
+ force_create_dialog(dialog_id, "search_public_dialog", true);
promise.set_value(Unit());
return dialog_id;
}
@@ -19138,77 +33658,135 @@ DialogId MessagesManager::search_public_dialog(const string &username_to_search,
return DialogId();
}
-void MessagesManager::send_get_dialog_query(DialogId dialog_id, Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
+void MessagesManager::reload_voice_chat_on_search(const string &username) {
+ if (!td_->auth_manager_->is_authorized()) {
return;
}
+
+ auto cleaned_username = clean_username(username);
+ if (!cleaned_username.empty()) {
+ reload_voice_chat_on_search_usernames_.insert(cleaned_username);
+ }
+}
+
+class MessagesManager::RegetDialogLogEvent {
+ public:
+ DialogId dialog_id_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(dialog_id_, parser);
+ }
+};
+
+uint64 MessagesManager::save_reget_dialog_log_event(DialogId dialog_id) {
+ RegetDialogLogEvent log_event{dialog_id};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::RegetDialog, get_log_event_storer(log_event));
+}
+
+void MessagesManager::send_get_dialog_query(DialogId dialog_id, Promise<Unit> &&promise, uint64 log_event_id,
+ const char *source) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) {
+ if (log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ }
+ return promise.set_error(Status::Error(500, "Wrong getDialog query"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ if (log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ }
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
auto &promises = get_dialog_queries_[dialog_id];
promises.push_back(std::move(promise));
if (promises.size() != 1) {
+ if (log_event_id != 0) {
+ LOG(INFO) << "Duplicate getDialog query for " << dialog_id << " from " << source;
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ }
// query has already been sent, just wait for the result
return;
}
- td_->create_handler<GetDialogQuery>()->send(dialog_id);
-}
-
-void MessagesManager::on_get_dialog_success(DialogId dialog_id) {
- auto it = get_dialog_queries_.find(dialog_id);
- CHECK(it != get_dialog_queries_.end());
- CHECK(it->second.size() > 0);
- auto promises = std::move(it->second);
- get_dialog_queries_.erase(it);
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_reget_dialog_log_event(dialog_id);
+ }
+ if (log_event_id != 0) {
+ auto result = get_dialog_query_log_event_id_.emplace(dialog_id, log_event_id);
+ CHECK(result.second);
+ }
- for (auto &promise : promises) {
- promise.set_value(Unit());
+ if (!G()->close_flag()) {
+ LOG(INFO) << "Send get " << dialog_id << " query from " << source;
+ td_->create_handler<GetDialogQuery>()->send(dialog_id);
+ } else {
+ // request will be sent after restart
}
}
-void MessagesManager::on_get_dialog_fail(DialogId dialog_id, Status &&error) {
+void MessagesManager::on_get_dialog_query_finished(DialogId dialog_id, Status &&status) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Finished getting " << dialog_id << " with result " << status;
auto it = get_dialog_queries_.find(dialog_id);
CHECK(it != get_dialog_queries_.end());
- CHECK(it->second.size() > 0);
+ CHECK(!it->second.empty());
auto promises = std::move(it->second);
get_dialog_queries_.erase(it);
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+ auto log_event_it = get_dialog_query_log_event_id_.find(dialog_id);
+ if (log_event_it != get_dialog_query_log_event_id_.end()) {
+ if (!G()->close_flag()) {
+ binlog_erase(G()->td_db()->get_binlog(), log_event_it->second);
+ }
+ get_dialog_query_log_event_id_.erase(log_event_it);
}
-}
-bool MessagesManager::is_update_about_username_change_received(DialogId dialog_id) const {
- switch (dialog_id.get_type()) {
- case DialogType::User:
- return td_->contacts_manager_->is_update_about_username_change_received(dialog_id.get_user_id());
- case DialogType::Chat:
- return true;
- case DialogType::Channel: {
- auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
- return status.is_member();
- }
- case DialogType::SecretChat:
- return true;
- case DialogType::None:
- default:
- UNREACHABLE();
- return false;
+ if (status.is_ok()) {
+ set_promises(promises);
+ } else {
+ fail_promises(promises, std::move(status));
}
}
-void MessagesManager::on_dialog_username_updated(DialogId dialog_id, const string &old_username,
- const string &new_username) {
- auto d = get_dialog(dialog_id);
+void MessagesManager::on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames,
+ const Usernames &new_usernames) {
+ CHECK(dialog_id.is_valid());
+ const auto *d = get_dialog(dialog_id);
if (d != nullptr) {
update_dialogs_hints(d);
}
- if (!old_username.empty() && old_username != new_username) {
- resolved_usernames_.erase(clean_username(old_username));
- unaccessible_resolved_usernames_.erase(clean_username(old_username));
+ if (old_usernames != new_usernames) {
+ message_embedding_codes_[0].erase(dialog_id);
+ message_embedding_codes_[1].erase(dialog_id);
+
+ LOG(INFO) << "Update usernames in " << dialog_id << " from " << old_usernames << " to " << new_usernames;
+ }
+ if (!old_usernames.is_empty() && old_usernames != new_usernames) {
+ for (auto &username : old_usernames.get_active_usernames()) {
+ auto cleaned_username = clean_username(username);
+ resolved_usernames_.erase(cleaned_username);
+ inaccessible_resolved_usernames_.erase(cleaned_username);
+ }
}
- if (!new_username.empty()) {
- auto cache_time = is_update_about_username_change_received(dialog_id) ? USERNAME_CACHE_EXPIRE_TIME
- : USERNAME_CACHE_EXPIRE_TIME_SHORT;
- resolved_usernames_[clean_username(new_username)] = ResolvedUsername{dialog_id, Time::now() + cache_time};
+ if (!new_usernames.is_empty()) {
+ for (auto &username : new_usernames.get_active_usernames()) {
+ auto cleaned_username = clean_username(username);
+ if (!cleaned_username.empty()) {
+ resolved_usernames_[cleaned_username] = ResolvedUsername{dialog_id, Time::now() + USERNAME_CACHE_EXPIRE_TIME};
+ }
+ }
}
}
@@ -19218,31 +33796,40 @@ void MessagesManager::on_resolved_username(const string &username, DialogId dial
return;
}
- auto it = resolved_usernames_.find(clean_username(username));
- if (it != resolved_usernames_.end()) {
- LOG_IF(ERROR, it->second.dialog_id != dialog_id)
- << "Resolve username \"" << username << "\" to " << dialog_id << ", but have it in " << it->second.dialog_id;
+ auto cleaned_username = clean_username(username);
+ if (cleaned_username.empty()) {
+ return;
+ }
+
+ auto resolved_username = resolved_usernames_.get(cleaned_username);
+ if (resolved_username.dialog_id.is_valid()) {
+ LOG_IF(ERROR, resolved_username.dialog_id != dialog_id)
+ << "Resolve username \"" << username << "\" to " << dialog_id << ", but have it in "
+ << resolved_username.dialog_id;
return;
}
- unaccessible_resolved_usernames_[clean_username(username)] = dialog_id;
+ inaccessible_resolved_usernames_[cleaned_username] = dialog_id;
}
void MessagesManager::drop_username(const string &username) {
- unaccessible_resolved_usernames_.erase(clean_username(username));
-
- auto it = resolved_usernames_.find(clean_username(username));
- if (it == resolved_usernames_.end()) {
+ auto cleaned_username = clean_username(username);
+ if (cleaned_username.empty()) {
return;
}
- auto dialog_id = it->second.dialog_id;
- if (have_input_peer(dialog_id, AccessRights::Read)) {
- CHECK(dialog_id.get_type() != DialogType::SecretChat);
- send_get_dialog_query(dialog_id, Auto());
- }
+ inaccessible_resolved_usernames_.erase(cleaned_username);
- resolved_usernames_.erase(it);
+ auto resolved_username = resolved_usernames_.get(cleaned_username);
+ if (resolved_username.dialog_id.is_valid()) {
+ auto dialog_id = resolved_username.dialog_id;
+ if (have_input_peer(dialog_id, AccessRights::Read)) {
+ CHECK(dialog_id.get_type() != DialogType::SecretChat);
+ send_get_dialog_query(dialog_id, Auto(), 0, "drop_username");
+ }
+
+ resolved_usernames_.erase(cleaned_username);
+ }
}
const DialogPhoto *MessagesManager::get_dialog_photo(DialogId dialog_id) const {
@@ -19279,153 +33866,150 @@ string MessagesManager::get_dialog_title(DialogId dialog_id) const {
}
}
-string MessagesManager::get_dialog_username(DialogId dialog_id) const {
+RestrictedRights MessagesManager::get_dialog_default_permissions(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
case DialogType::User:
- return td_->contacts_manager_->get_user_username(dialog_id.get_user_id());
+ return td_->contacts_manager_->get_user_default_permissions(dialog_id.get_user_id());
case DialogType::Chat:
- return string();
+ return td_->contacts_manager_->get_chat_default_permissions(dialog_id.get_chat_id());
case DialogType::Channel:
- return td_->contacts_manager_->get_channel_username(dialog_id.get_channel_id());
+ return td_->contacts_manager_->get_channel_default_permissions(dialog_id.get_channel_id());
case DialogType::SecretChat:
- return td_->contacts_manager_->get_secret_chat_username(dialog_id.get_secret_chat_id());
+ return td_->contacts_manager_->get_secret_chat_default_permissions(dialog_id.get_secret_chat_id());
case DialogType::None:
default:
UNREACHABLE();
- return string();
+ return RestrictedRights(false, false, false, false, false, false, false, false, false, false, false, false);
}
}
-void MessagesManager::send_dialog_action(DialogId dialog_id, const tl_object_ptr<td_api::ChatAction> &action,
- Promise<Unit> &&promise) {
- if (action == nullptr) {
- return promise.set_error(Status::Error(5, "Action must not be empty"));
+bool MessagesManager::get_dialog_has_protected_content(DialogId dialog_id) const {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ return false;
+ case DialogType::Chat:
+ return td_->contacts_manager_->get_chat_has_protected_content(dialog_id.get_chat_id());
+ case DialogType::Channel:
+ return td_->contacts_manager_->get_channel_has_protected_content(dialog_id.get_channel_id());
+ case DialogType::SecretChat:
+ return false;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ return true;
}
+}
- if (!have_dialog_force(dialog_id)) {
- return promise.set_error(Status::Error(5, "Chat not found"));
+bool MessagesManager::get_dialog_has_scheduled_messages(const Dialog *d) const {
+ if (!have_input_peer(d->dialog_id, AccessRights::Read)) {
+ return false;
}
-
- auto can_send_status = can_send_message(dialog_id);
- if (can_send_status.is_error()) {
- return promise.set_error(can_send_status.move_as_error());
+ if (is_broadcast_channel(d->dialog_id) &&
+ !td_->contacts_manager_->get_channel_status(d->dialog_id.get_channel_id()).can_post_messages()) {
+ return false;
}
+ // TODO send updateChatHasScheduledMessage when can_post_messages changes
- if (dialog_id.get_type() == DialogType::SecretChat) {
- tl_object_ptr<secret_api::SendMessageAction> send_action;
- switch (action->get_id()) {
- case td_api::chatActionCancel::ID:
- send_action = make_tl_object<secret_api::sendMessageCancelAction>();
- break;
- case td_api::chatActionTyping::ID:
- send_action = make_tl_object<secret_api::sendMessageTypingAction>();
- break;
- case td_api::chatActionRecordingVideo::ID:
- send_action = make_tl_object<secret_api::sendMessageRecordVideoAction>();
- break;
- case td_api::chatActionUploadingVideo::ID:
- send_action = make_tl_object<secret_api::sendMessageUploadVideoAction>();
- break;
- case td_api::chatActionRecordingVoiceNote::ID:
- send_action = make_tl_object<secret_api::sendMessageRecordAudioAction>();
- break;
- case td_api::chatActionUploadingVoiceNote::ID:
- send_action = make_tl_object<secret_api::sendMessageUploadAudioAction>();
- break;
- case td_api::chatActionUploadingPhoto::ID:
- send_action = make_tl_object<secret_api::sendMessageUploadPhotoAction>();
- break;
- case td_api::chatActionUploadingDocument::ID:
- send_action = make_tl_object<secret_api::sendMessageUploadDocumentAction>();
- break;
- case td_api::chatActionChoosingLocation::ID:
- send_action = make_tl_object<secret_api::sendMessageGeoLocationAction>();
- break;
- case td_api::chatActionChoosingContact::ID:
- send_action = make_tl_object<secret_api::sendMessageChooseContactAction>();
- break;
- case td_api::chatActionRecordingVideoNote::ID:
- send_action = make_tl_object<secret_api::sendMessageRecordRoundAction>();
- break;
- case td_api::chatActionUploadingVideoNote::ID:
- send_action = make_tl_object<secret_api::sendMessageUploadRoundAction>();
- break;
- case td_api::chatActionStartPlayingGame::ID:
- return promise.set_error(Status::Error(5, "Games are unsupported in secret chats"));
- default:
- UNREACHABLE();
- }
- send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_message_action, dialog_id.get_secret_chat_id(),
- std::move(send_action));
- promise.set_value(Unit());
- return;
+ return d->has_scheduled_server_messages || d->has_scheduled_database_messages || d->scheduled_messages != nullptr;
+}
+
+bool MessagesManager::is_dialog_action_unneeded(DialogId dialog_id) const {
+ if (is_anonymous_administrator(dialog_id, nullptr)) {
+ return true;
}
- tl_object_ptr<telegram_api::SendMessageAction> send_action;
- switch (action->get_id()) {
- case td_api::chatActionCancel::ID:
- send_action = make_tl_object<telegram_api::sendMessageCancelAction>();
- break;
- case td_api::chatActionTyping::ID:
- send_action = make_tl_object<telegram_api::sendMessageTypingAction>();
- break;
- case td_api::chatActionRecordingVideo::ID:
- send_action = make_tl_object<telegram_api::sendMessageRecordVideoAction>();
- break;
- case td_api::chatActionUploadingVideo::ID: {
- auto progress = static_cast<td_api::chatActionUploadingVideo &>(*action).progress_;
- send_action = make_tl_object<telegram_api::sendMessageUploadVideoAction>(progress);
- break;
+ auto dialog_type = dialog_id.get_type();
+ if (dialog_type == DialogType::User || dialog_type == DialogType::SecretChat) {
+ UserId user_id = dialog_type == DialogType::User
+ ? dialog_id.get_user_id()
+ : td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (td_->contacts_manager_->is_user_deleted(user_id)) {
+ return true;
}
- case td_api::chatActionRecordingVoiceNote::ID:
- send_action = make_tl_object<telegram_api::sendMessageRecordAudioAction>();
- break;
- case td_api::chatActionUploadingVoiceNote::ID: {
- auto progress = static_cast<td_api::chatActionUploadingVoiceNote &>(*action).progress_;
- send_action = make_tl_object<telegram_api::sendMessageUploadAudioAction>(progress);
- break;
+ if (td_->contacts_manager_->is_user_bot(user_id) && !td_->contacts_manager_->is_user_support(user_id)) {
+ return true;
}
- case td_api::chatActionUploadingPhoto::ID: {
- auto progress = static_cast<td_api::chatActionUploadingPhoto &>(*action).progress_;
- send_action = make_tl_object<telegram_api::sendMessageUploadPhotoAction>(progress);
- break;
+ if (user_id == td_->contacts_manager_->get_my_id()) {
+ return true;
}
- case td_api::chatActionUploadingDocument::ID: {
- auto progress = static_cast<td_api::chatActionUploadingDocument &>(*action).progress_;
- send_action = make_tl_object<telegram_api::sendMessageUploadDocumentAction>(progress);
- break;
+
+ if (!td_->auth_manager_->is_bot()) {
+ if (td_->contacts_manager_->is_user_status_exact(user_id)) {
+ if (!td_->contacts_manager_->is_user_online(user_id, 30)) {
+ return true;
+ }
+ } else {
+ // return true;
+ }
}
- case td_api::chatActionChoosingLocation::ID:
- send_action = make_tl_object<telegram_api::sendMessageGeoLocationAction>();
- break;
- case td_api::chatActionChoosingContact::ID:
- send_action = make_tl_object<telegram_api::sendMessageChooseContactAction>();
- break;
- case td_api::chatActionStartPlayingGame::ID:
- send_action = make_tl_object<telegram_api::sendMessageGamePlayAction>();
- break;
- case td_api::chatActionRecordingVideoNote::ID:
- send_action = make_tl_object<telegram_api::sendMessageRecordRoundAction>();
- break;
- case td_api::chatActionUploadingVideoNote::ID: {
- auto progress = static_cast<td_api::chatActionUploadingVideoNote &>(*action).progress_;
- send_action = make_tl_object<telegram_api::sendMessageUploadRoundAction>(progress);
- break;
+ }
+ return false;
+}
+
+void MessagesManager::send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action,
+ Promise<Unit> &&promise) {
+ if (!have_dialog_force(dialog_id, "send_dialog_action")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (top_thread_message_id != MessageId() &&
+ (!top_thread_message_id.is_valid() || !top_thread_message_id.is_server())) {
+ return promise.set_error(Status::Error(400, "Invalid message thread specified"));
+ }
+
+ tl_object_ptr<telegram_api::InputPeer> input_peer;
+ if (action == DialogAction::get_speaking_action()) {
+ input_peer = get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return promise.set_error(Status::Error(400, "Have no access to the chat"));
}
- default:
- UNREACHABLE();
+ } else {
+ auto can_send_status = can_send_message(dialog_id);
+ if (can_send_status.is_error()) {
+ if (td_->auth_manager_->is_bot()) {
+ return promise.set_error(std::move(can_send_status));
+ }
+ return promise.set_value(Unit());
+ }
+
+ if (is_dialog_action_unneeded(dialog_id)) {
+ LOG(INFO) << "Skip unneeded " << action << " in " << dialog_id;
+ return promise.set_value(Unit());
+ }
+
+ input_peer = get_input_peer(dialog_id, AccessRights::Write);
+ }
+
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ send_closure(G()->secret_chats_manager(), &SecretChatsManager::send_message_action, dialog_id.get_secret_chat_id(),
+ action.get_secret_input_send_message_action());
+ promise.set_value(Unit());
+ return;
+ }
+
+ auto new_query_ref =
+ td_->create_handler<SetTypingQuery>(std::move(promise))
+ ->send(dialog_id, std::move(input_peer), top_thread_message_id, action.get_input_send_message_action());
+ if (td_->auth_manager_->is_bot()) {
+ return;
}
auto &query_ref = set_typing_query_[dialog_id];
- if (!query_ref.empty() && !td_->auth_manager_->is_bot()) {
- LOG(INFO) << "Cancel previous set typing query";
+ if (!query_ref.empty()) {
+ LOG(INFO) << "Cancel previous send chat action query";
cancel_query(query_ref);
}
- query_ref = td_->create_handler<SetTypingQuery>(std::move(promise))->send(dialog_id, std::move(send_action));
+ query_ref = std::move(new_query_ref);
+}
+
+void MessagesManager::after_set_typing_query(DialogId dialog_id, int32 generation) {
+ auto it = set_typing_query_.find(dialog_id);
+ if (it != set_typing_query_.end() && (!it->second.is_alive() || it->second.generation() == generation)) {
+ set_typing_query_.erase(it);
+ }
}
void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) {
- LOG(INFO) << "Receive send_dialog_action timeout in " << dialog_id;
+ LOG(INFO) << "Receive send_chat_action timeout in " << dialog_id;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
@@ -19433,7 +34017,7 @@ void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) {
return;
}
- auto queue_id = get_sequence_dispatcher_id(dialog_id, MessagePhoto::ID);
+ auto queue_id = ChainId(dialog_id, MessageContentType::Photo).get();
CHECK(queue_id & 1);
auto queue_it = yet_unsent_media_queues_.find(queue_id);
@@ -19444,19 +34028,21 @@ void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) {
pending_send_dialog_action_timeout_.add_timeout_in(dialog_id.get(), 4.0);
CHECK(!queue_it->second.empty());
- MessageId message_id(queue_it->second.begin()->first);
- const Message *m = get_message(d, message_id);
+ const Message *m = get_message(d, queue_it->second.begin()->first);
if (m == nullptr) {
return;
}
- if (m->forward_info != nullptr) {
+ CHECK(m->message_id.is_yet_unsent());
+ if (m->forward_info != nullptr || m->had_forward_info || m->is_copy || m->message_id.is_scheduled() ||
+ m->sender_dialog_id.is_valid()) {
return;
}
- auto file_id = get_message_content_file_id(m->content.get());
+ auto file_id = get_message_content_upload_file_id(m->content.get());
if (!file_id.is_valid()) {
LOG(ERROR) << "Have no file in "
- << to_string(get_message_content_object(m->content.get(), m->date, m->is_content_secret));
+ << to_string(get_message_content_object(m->content.get(), td_, dialog_id, m->date, m->is_content_secret,
+ false, -1));
return;
}
auto file_view = td_->file_manager_->get_file_view(file_id);
@@ -19473,38 +34059,16 @@ void MessagesManager::on_send_dialog_action_timeout(DialogId dialog_id) {
progress = static_cast<int32>(100 * uploaded_size / total_size);
}
- td_api::object_ptr<td_api::ChatAction> action;
- switch (m->content->get_id()) {
- case MessageAnimation::ID:
- case MessageAudio::ID:
- case MessageDocument::ID:
- action = td_api::make_object<td_api::chatActionUploadingDocument>(progress);
- break;
- case MessagePhoto::ID:
- action = td_api::make_object<td_api::chatActionUploadingPhoto>(progress);
- break;
- case MessageVideo::ID:
- action = td_api::make_object<td_api::chatActionUploadingVideo>(progress);
- break;
- case MessageVideoNote::ID:
- action = td_api::make_object<td_api::chatActionUploadingVideoNote>(progress);
- break;
- case MessageVoiceNote::ID:
- action = td_api::make_object<td_api::chatActionUploadingVoiceNote>(progress);
- break;
- default:
- return;
+ DialogAction action = DialogAction::get_uploading_action(m->content->get_type(), progress);
+ if (action == DialogAction()) {
+ return;
}
- CHECK(action != nullptr);
- LOG(INFO) << "Send action in " << dialog_id << ": " << to_string(action);
- send_dialog_action(dialog_id, std::move(action), Auto());
+ LOG(INFO) << "Send " << action << " in " << dialog_id;
+ send_dialog_action(dialog_id, m->top_thread_message_id, std::move(action), Promise<Unit>());
}
void MessagesManager::on_active_dialog_action_timeout(DialogId dialog_id) {
LOG(DEBUG) << "Receive active dialog action timeout in " << dialog_id;
- Dialog *d = get_dialog(dialog_id);
- CHECK(d != nullptr);
-
auto actions_it = active_dialog_actions_.find(dialog_id);
if (actions_it == active_dialog_actions_.end()) {
return;
@@ -19512,8 +34076,12 @@ void MessagesManager::on_active_dialog_action_timeout(DialogId dialog_id) {
CHECK(!actions_it->second.empty());
auto now = Time::now();
+ DialogId prev_typing_dialog_id;
while (actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT < now + 0.1) {
- on_user_dialog_action(dialog_id, actions_it->second[0].user_id, nullptr);
+ CHECK(actions_it->second[0].typing_dialog_id != prev_typing_dialog_id);
+ prev_typing_dialog_id = actions_it->second[0].typing_dialog_id;
+ on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id,
+ DialogAction(), 0);
actions_it = active_dialog_actions_.find(dialog_id);
if (actions_it == active_dialog_actions_.end()) {
@@ -19527,203 +34095,328 @@ void MessagesManager::on_active_dialog_action_timeout(DialogId dialog_id) {
actions_it->second[0].start_time + DIALOG_ACTION_TIMEOUT - now);
}
-tl_object_ptr<telegram_api::InputChatPhoto> MessagesManager::get_input_chat_photo(FileId file_id) const {
- if (!file_id.is_valid()) {
- return make_tl_object<telegram_api::inputChatPhotoEmpty>();
+void MessagesManager::clear_active_dialog_actions(DialogId dialog_id) {
+ LOG(DEBUG) << "Clear active dialog actions in " << dialog_id;
+ auto actions_it = active_dialog_actions_.find(dialog_id);
+ while (actions_it != active_dialog_actions_.end()) {
+ CHECK(!actions_it->second.empty());
+ on_dialog_action(dialog_id, actions_it->second[0].top_thread_message_id, actions_it->second[0].typing_dialog_id,
+ DialogAction(), 0);
+ actions_it = active_dialog_actions_.find(dialog_id);
}
+}
- FileView file_view = td_->file_manager_->get_file_view(file_id);
- CHECK(!file_view.is_encrypted());
- if (file_view.has_remote_location()) {
- if (file_view.remote_location().is_web()) {
- return nullptr;
- }
- return make_tl_object<telegram_api::inputChatPhoto>(file_view.remote_location().as_input_photo());
+vector<DialogListId> MessagesManager::get_dialog_lists_to_add_dialog(DialogId dialog_id) {
+ vector<DialogListId> result;
+ const Dialog *d = get_dialog_force(dialog_id, "get_dialog_lists_to_add_dialog");
+ if (d == nullptr || d->order == DEFAULT_ORDER || !have_input_peer(dialog_id, AccessRights::Read)) {
+ return result;
}
- return nullptr;
+ if (dialog_id != get_my_dialog_id() && dialog_id != DialogId(ContactsManager::get_service_notifications_user_id())) {
+ result.push_back(DialogListId(d->folder_id == FolderId::archive() ? FolderId::main() : FolderId::archive()));
+ }
+
+ for (const auto &dialog_filter : dialog_filters_) {
+ auto dialog_filter_id = dialog_filter->dialog_filter_id;
+ if (!InputDialogId::contains(dialog_filter->included_dialog_ids, dialog_id) &&
+ !InputDialogId::contains(dialog_filter->pinned_dialog_ids, dialog_id)) {
+ // the dialog isn't added yet to the dialog list
+ // check that it can be actually added
+ if (dialog_filter->included_dialog_ids.size() + dialog_filter->pinned_dialog_ids.size() <
+ narrow_cast<size_t>(DialogFilter::get_max_filter_dialogs())) {
+ // fast path
+ result.push_back(DialogListId(dialog_filter_id));
+ continue;
+ }
+
+ auto new_dialog_filter = make_unique<DialogFilter>(*dialog_filter);
+ new_dialog_filter->included_dialog_ids.push_back(get_input_dialog_id(dialog_id));
+ InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id);
+
+ if (new_dialog_filter->check_limits().is_ok()) {
+ result.push_back(DialogListId(dialog_filter_id));
+ }
+ }
+ }
+ return result;
}
-tl_object_ptr<telegram_api::MessagesFilter> MessagesManager::get_input_messages_filter(SearchMessagesFilter filter) {
- switch (filter) {
- case SearchMessagesFilter::Empty:
- return make_tl_object<telegram_api::inputMessagesFilterEmpty>();
- case SearchMessagesFilter::Animation:
- return make_tl_object<telegram_api::inputMessagesFilterGif>();
- case SearchMessagesFilter::Audio:
- return make_tl_object<telegram_api::inputMessagesFilterMusic>();
- case SearchMessagesFilter::Document:
- return make_tl_object<telegram_api::inputMessagesFilterDocument>();
- case SearchMessagesFilter::Photo:
- return make_tl_object<telegram_api::inputMessagesFilterPhotos>();
- case SearchMessagesFilter::Video:
- return make_tl_object<telegram_api::inputMessagesFilterVideo>();
- case SearchMessagesFilter::VoiceNote:
- return make_tl_object<telegram_api::inputMessagesFilterVoice>();
- case SearchMessagesFilter::PhotoAndVideo:
- return make_tl_object<telegram_api::inputMessagesFilterPhotoVideo>();
- case SearchMessagesFilter::Url:
- return make_tl_object<telegram_api::inputMessagesFilterUrl>();
- case SearchMessagesFilter::ChatPhoto:
- return make_tl_object<telegram_api::inputMessagesFilterChatPhotos>();
- case SearchMessagesFilter::Call:
- return make_tl_object<telegram_api::inputMessagesFilterPhoneCalls>(0, false /*ignored*/);
- case SearchMessagesFilter::MissedCall:
- return make_tl_object<telegram_api::inputMessagesFilterPhoneCalls>(
- telegram_api::inputMessagesFilterPhoneCalls::MISSED_MASK, false /*ignored*/);
- case SearchMessagesFilter::VideoNote:
- return make_tl_object<telegram_api::inputMessagesFilterRoundVideo>();
- case SearchMessagesFilter::VoiceAndVideoNote:
- return make_tl_object<telegram_api::inputMessagesFilterRoundVoice>();
- case SearchMessagesFilter::Mention:
- return make_tl_object<telegram_api::inputMessagesFilterMyMentions>();
- default:
- UNREACHABLE();
- return nullptr;
+void MessagesManager::add_dialog_to_list(DialogId dialog_id, DialogListId dialog_list_id, Promise<Unit> &&promise) {
+ LOG(INFO) << "Receive addChatToList request to add " << dialog_id << " to " << dialog_list_id;
+ CHECK(!td_->auth_manager_->is_bot());
+
+ Dialog *d = get_dialog_force(dialog_id, "add_dialog_to_list");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ if (d->order == DEFAULT_ORDER) {
+ return promise.set_error(Status::Error(400, "Chat is not in a chat list"));
+ }
+
+ if (get_dialog_list(dialog_list_id) == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat list not found"));
}
+
+ if (dialog_list_id.is_filter()) {
+ CHECK(is_update_chat_filters_sent_);
+ auto dialog_filter_id = dialog_list_id.get_filter_id();
+ auto old_dialog_filter = get_dialog_filter(dialog_filter_id);
+ CHECK(old_dialog_filter != nullptr);
+ if (InputDialogId::contains(old_dialog_filter->included_dialog_ids, dialog_id) ||
+ InputDialogId::contains(old_dialog_filter->pinned_dialog_ids, dialog_id)) {
+ return promise.set_value(Unit());
+ }
+
+ auto new_dialog_filter = make_unique<DialogFilter>(*old_dialog_filter);
+ new_dialog_filter->included_dialog_ids.push_back(get_input_dialog_id(dialog_id));
+ InputDialogId::remove(new_dialog_filter->excluded_dialog_ids, dialog_id);
+
+ auto status = new_dialog_filter->check_limits();
+ if (status.is_error()) {
+ return promise.set_error(std::move(status));
+ }
+ sort_dialog_filter_input_dialog_ids(new_dialog_filter.get(), "add_dialog_to_list");
+
+ edit_dialog_filter(std::move(new_dialog_filter), "add_dialog_to_list");
+ save_dialog_filters();
+ send_update_chat_filters();
+
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ synchronize_dialog_filters();
+ }
+
+ return promise.set_value(Unit());
+ }
+
+ CHECK(dialog_list_id.is_folder());
+ auto folder_id = dialog_list_id.get_folder_id();
+ if (d->folder_id == folder_id) {
+ return promise.set_value(Unit());
+ }
+
+ if (folder_id == FolderId::archive() &&
+ (dialog_id == get_my_dialog_id() ||
+ dialog_id == DialogId(ContactsManager::get_service_notifications_user_id()))) {
+ return promise.set_error(Status::Error(400, "Chat can't be archived"));
+ }
+
+ set_dialog_folder_id(d, folder_id);
+
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ set_dialog_folder_id_on_server(dialog_id, false);
+ }
+ promise.set_value(Unit());
}
-SearchMessagesFilter MessagesManager::get_search_messages_filter(
- const tl_object_ptr<td_api::SearchMessagesFilter> &filter) {
- if (filter == nullptr) {
- return SearchMessagesFilter::Empty;
- }
- switch (filter->get_id()) {
- case td_api::searchMessagesFilterEmpty::ID:
- return SearchMessagesFilter::Empty;
- case td_api::searchMessagesFilterAnimation::ID:
- return SearchMessagesFilter::Animation;
- case td_api::searchMessagesFilterAudio::ID:
- return SearchMessagesFilter::Audio;
- case td_api::searchMessagesFilterDocument::ID:
- return SearchMessagesFilter::Document;
- case td_api::searchMessagesFilterPhoto::ID:
- return SearchMessagesFilter::Photo;
- case td_api::searchMessagesFilterVideo::ID:
- return SearchMessagesFilter::Video;
- case td_api::searchMessagesFilterVoiceNote::ID:
- return SearchMessagesFilter::VoiceNote;
- case td_api::searchMessagesFilterPhotoAndVideo::ID:
- return SearchMessagesFilter::PhotoAndVideo;
- case td_api::searchMessagesFilterUrl::ID:
- return SearchMessagesFilter::Url;
- case td_api::searchMessagesFilterChatPhoto::ID:
- return SearchMessagesFilter::ChatPhoto;
- case td_api::searchMessagesFilterCall::ID:
- return SearchMessagesFilter::Call;
- case td_api::searchMessagesFilterMissedCall::ID:
- return SearchMessagesFilter::MissedCall;
- case td_api::searchMessagesFilterVideoNote::ID:
- return SearchMessagesFilter::VideoNote;
- case td_api::searchMessagesFilterVoiceAndVideoNote::ID:
- return SearchMessagesFilter::VoiceAndVideoNote;
- case td_api::searchMessagesFilterMention::ID:
- return SearchMessagesFilter::Mention;
- case td_api::searchMessagesFilterUnreadMention::ID:
- return SearchMessagesFilter::UnreadMention;
- default:
- UNREACHABLE();
- return SearchMessagesFilter::Empty;
+class MessagesManager::SetDialogFolderIdOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
+ FolderId folder_id_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
+ td::store(folder_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(dialog_id_, parser);
+ td::parse(folder_id_, parser);
}
+};
+
+void MessagesManager::set_dialog_folder_id_on_server(DialogId dialog_id, bool from_binlog) {
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+
+ if (!from_binlog && G()->parameters().use_message_db) {
+ SetDialogFolderIdOnServerLogEvent log_event;
+ log_event.dialog_id_ = dialog_id;
+ log_event.folder_id_ = d->folder_id;
+ add_log_event(d->set_folder_id_log_event_id, get_log_event_storer(log_event),
+ LogEvent::HandlerType::SetDialogFolderIdOnServer, "set chat folder");
+ }
+
+ Promise<Unit> promise;
+ if (d->set_folder_id_log_event_id.log_event_id != 0) {
+ d->set_folder_id_log_event_id.generation++;
+ promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id,
+ generation = d->set_folder_id_log_event_id.generation](Result<Unit> result) {
+ if (!G()->close_flag()) {
+ send_closure(actor_id, &MessagesManager::on_updated_dialog_folder_id, dialog_id, generation);
+ }
+ });
+ }
+
+ // TODO do not send two queries simultaneously or use InvokeAfter
+ td_->create_handler<EditPeerFoldersQuery>(std::move(promise))->send(dialog_id, d->folder_id);
}
-void MessagesManager::set_dialog_photo(DialogId dialog_id, const tl_object_ptr<td_api::InputFile> &photo,
- Promise<Unit> &&promise) {
- LOG(INFO) << "Receive SetChatPhoto request to change photo of " << dialog_id;
+void MessagesManager::on_updated_dialog_folder_id(DialogId dialog_id, uint64 generation) {
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ delete_log_event(d->set_folder_id_log_event_id, generation, "set chat folder");
+}
- if (!have_dialog_force(dialog_id)) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+void MessagesManager::set_dialog_photo(DialogId dialog_id, const tl_object_ptr<td_api::InputChatPhoto> &input_photo,
+ Promise<Unit> &&promise) {
+ if (!have_dialog_force(dialog_id, "set_dialog_photo")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
- return promise.set_error(Status::Error(3, "Can't change private chat photo"));
+ return promise.set_error(Status::Error(400, "Can't change private chat photo"));
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
- auto status = td_->contacts_manager_->get_chat_status(chat_id);
+ auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_change_info_and_settings() ||
(td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) {
- return promise.set_error(Status::Error(3, "Not enough rights to change chat photo"));
+ return promise.set_error(Status::Error(400, "Not enough rights to change chat photo"));
}
break;
}
case DialogType::Channel: {
- auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
+ auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
if (!status.can_change_info_and_settings()) {
- return promise.set_error(Status::Error(3, "Not enough rights to change chat photo"));
+ return promise.set_error(Status::Error(400, "Not enough rights to change chat photo"));
}
break;
}
case DialogType::SecretChat:
- return promise.set_error(Status::Error(3, "Can't change secret chat photo"));
+ return promise.set_error(Status::Error(400, "Can't change secret chat photo"));
case DialogType::None:
default:
UNREACHABLE();
}
- auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Photo, photo, dialog_id, true, false);
+ const td_api::object_ptr<td_api::InputFile> *input_file = nullptr;
+ double main_frame_timestamp = 0.0;
+ bool is_animation = false;
+ if (input_photo != nullptr) {
+ switch (input_photo->get_id()) {
+ case td_api::inputChatPhotoPrevious::ID: {
+ auto photo = static_cast<const td_api::inputChatPhotoPrevious *>(input_photo.get());
+ auto file_id = td_->contacts_manager_->get_profile_photo_file_id(photo->chat_photo_id_);
+ if (!file_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Unknown profile photo ID specified"));
+ }
+
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ auto input_chat_photo =
+ make_tl_object<telegram_api::inputChatPhoto>(file_view.main_remote_location().as_input_photo());
+ send_edit_dialog_photo_query(dialog_id, file_id, std::move(input_chat_photo), std::move(promise));
+ return;
+ }
+ case td_api::inputChatPhotoStatic::ID: {
+ auto photo = static_cast<const td_api::inputChatPhotoStatic *>(input_photo.get());
+ input_file = &photo->photo_;
+ break;
+ }
+ case td_api::inputChatPhotoAnimation::ID: {
+ auto photo = static_cast<const td_api::inputChatPhotoAnimation *>(input_photo.get());
+ input_file = &photo->animation_;
+ main_frame_timestamp = photo->main_frame_timestamp_;
+ is_animation = true;
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+ if (input_file == nullptr) {
+ send_edit_dialog_photo_query(dialog_id, FileId(), make_tl_object<telegram_api::inputChatPhotoEmpty>(),
+ std::move(promise));
+ return;
+ }
+
+ const double MAX_ANIMATION_DURATION = 10.0;
+ if (main_frame_timestamp < 0.0 || main_frame_timestamp > MAX_ANIMATION_DURATION) {
+ return promise.set_error(Status::Error(400, "Wrong main frame timestamp specified"));
+ }
+
+ auto file_type = is_animation ? FileType::Animation : FileType::Photo;
+ auto r_file_id = td_->file_manager_->get_input_file_id(file_type, *input_file, dialog_id, true, false);
if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(7, r_file_id.error().message()));
+ // TODO promise.set_error(std::move(status));
+ return promise.set_error(Status::Error(400, r_file_id.error().message()));
}
FileId file_id = r_file_id.ok();
-
- auto input_chat_photo = get_input_chat_photo(file_id);
- if (input_chat_photo != nullptr) {
- // file has already been uploaded, just send change photo request
- // TODO invoke after
- td_->create_handler<EditDialogPhotoQuery>(std::move(promise))
- ->send(FileId(), dialog_id, std::move(input_chat_photo));
+ if (!file_id.is_valid()) {
+ send_edit_dialog_photo_query(dialog_id, FileId(), make_tl_object<telegram_api::inputChatPhotoEmpty>(),
+ std::move(promise));
return;
}
- // need to upload file first
- auto upload_file_id = td_->file_manager_->dup_file_id(file_id);
- CHECK(upload_file_id.is_valid());
- CHECK(uploaded_dialog_photos_.find(upload_file_id) == uploaded_dialog_photos_.end());
- uploaded_dialog_photos_[upload_file_id] = {std::move(promise), dialog_id};
- LOG(INFO) << "Ask to upload chat photo " << upload_file_id;
- td_->file_manager_->upload(upload_file_id, upload_dialog_photo_callback_, 1, 0);
+ upload_dialog_photo(dialog_id, td_->file_manager_->dup_file_id(file_id, "set_dialog_photo"), is_animation,
+ main_frame_timestamp, false, std::move(promise));
}
-void MessagesManager::set_dialog_title(DialogId dialog_id, const string &title, Promise<Unit> &&promise) {
- LOG(INFO) << "Receive SetChatTitle request to change title of " << dialog_id << " to \"" << title << '"';
+void MessagesManager::send_edit_dialog_photo_query(DialogId dialog_id, FileId file_id,
+ tl_object_ptr<telegram_api::InputChatPhoto> &&input_chat_photo,
+ Promise<Unit> &&promise) {
+ // TODO invoke after
+ td_->create_handler<EditDialogPhotoQuery>(std::move(promise))->send(dialog_id, file_id, std::move(input_chat_photo));
+}
- if (!have_dialog_force(dialog_id)) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+void MessagesManager::upload_dialog_photo(DialogId dialog_id, FileId file_id, bool is_animation,
+ double main_frame_timestamp, bool is_reupload, Promise<Unit> &&promise,
+ vector<int> bad_parts) {
+ CHECK(file_id.is_valid());
+ LOG(INFO) << "Ask to upload chat photo " << file_id;
+ bool is_inserted = being_uploaded_dialog_photos_
+ .emplace(file_id, UploadedDialogPhotoInfo{dialog_id, main_frame_timestamp, is_animation,
+ is_reupload, std::move(promise)})
+ .second;
+ CHECK(is_inserted);
+ // TODO use force_reupload if is_reupload
+ td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_dialog_photo_callback_, 32, 0);
+}
+
+void MessagesManager::set_dialog_title(DialogId dialog_id, const string &title, Promise<Unit> &&promise) {
+ if (!have_dialog_force(dialog_id, "set_dialog_title")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- auto new_title = clean_name(title, MAX_NAME_LENGTH);
+ auto new_title = clean_name(title, MAX_TITLE_LENGTH);
if (new_title.empty()) {
- return promise.set_error(Status::Error(3, "Title can't be empty"));
+ return promise.set_error(Status::Error(400, "Title must be non-empty"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
- return promise.set_error(Status::Error(3, "Can't change private chat title"));
+ return promise.set_error(Status::Error(400, "Can't change private chat title"));
case DialogType::Chat: {
auto chat_id = dialog_id.get_chat_id();
- auto status = td_->contacts_manager_->get_chat_status(chat_id);
+ auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
if (!status.can_change_info_and_settings() ||
(td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) {
- return promise.set_error(Status::Error(3, "Not enough rights to change chat title"));
+ return promise.set_error(Status::Error(400, "Not enough rights to change chat title"));
}
break;
}
case DialogType::Channel: {
- auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
+ auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
if (!status.can_change_info_and_settings()) {
- return promise.set_error(Status::Error(3, "Not enough rights to change chat title"));
+ return promise.set_error(Status::Error(400, "Not enough rights to change chat title"));
}
break;
}
case DialogType::SecretChat:
- return promise.set_error(Status::Error(3, "Can't change secret chat title"));
+ return promise.set_error(Status::Error(400, "Can't change secret chat title"));
case DialogType::None:
default:
UNREACHABLE();
}
- // TODO this can be wrong if there was previous change title requests
+ // TODO this can be wrong if there were previous change title requests
if (get_dialog_title(dialog_id) == new_title) {
return promise.set_value(Unit());
}
@@ -19732,557 +34425,529 @@ void MessagesManager::set_dialog_title(DialogId dialog_id, const string &title,
td_->create_handler<EditDialogTitleQuery>(std::move(promise))->send(dialog_id, new_title);
}
-void MessagesManager::add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit,
- Promise<Unit> &&promise) {
- LOG(INFO) << "Receive AddChatParticipant request to add " << user_id << " to " << dialog_id;
- if (!have_dialog_force(dialog_id)) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+void MessagesManager::set_dialog_available_reactions(
+ DialogId dialog_id, td_api::object_ptr<td_api::ChatAvailableReactions> &&available_reactions_ptr,
+ Promise<Unit> &&promise) {
+ Dialog *d = get_dialog_force(dialog_id, "set_dialog_available_reactions");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+
+ ChatReactions available_reactions(std::move(available_reactions_ptr), !is_broadcast_channel(dialog_id));
+ auto active_reactions = get_active_reactions(available_reactions);
+ if (active_reactions.reactions_.size() != available_reactions.reactions_.size()) {
+ return promise.set_error(Status::Error(400, "Invalid reactions specified"));
}
+ available_reactions = std::move(active_reactions);
switch (dialog_id.get_type()) {
case DialogType::User:
- return promise.set_error(Status::Error(3, "Can't add members to a private chat"));
- case DialogType::Chat:
- return td_->contacts_manager_->add_chat_participant(dialog_id.get_chat_id(), user_id, forward_limit,
- std::move(promise));
- case DialogType::Channel:
- return td_->contacts_manager_->add_channel_participant(dialog_id.get_channel_id(), user_id, std::move(promise));
+ return promise.set_error(Status::Error(400, "Can't change private chat available reactions"));
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
+ if (!status.can_change_info_and_settings() ||
+ (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) {
+ return promise.set_error(Status::Error(400, "Not enough rights to change chat available reactions"));
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
+ if (!status.can_change_info_and_settings()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to change chat available reactions"));
+ }
+ break;
+ }
case DialogType::SecretChat:
- return promise.set_error(Status::Error(3, "Can't add members to a secret chat"));
+ return promise.set_error(Status::Error(400, "Can't change secret chat available reactions"));
case DialogType::None:
default:
UNREACHABLE();
}
+
+ bool is_changed = d->available_reactions != available_reactions;
+
+ set_dialog_available_reactions(d, ChatReactions(available_reactions));
+
+ if (!is_changed) {
+ return promise.set_value(Unit());
+ }
+
+ // TODO invoke after
+ td_->create_handler<SetChatAvailableReactionsQuery>(std::move(promise))
+ ->send(dialog_id, std::move(available_reactions));
}
-void MessagesManager::add_dialog_participants(DialogId dialog_id, const vector<UserId> &user_ids,
- Promise<Unit> &&promise) {
- LOG(INFO) << "Receive AddChatParticipants request to add " << format::as_array(user_ids) << " to " << dialog_id;
- if (td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(3, "Method is not available for bots"));
+void MessagesManager::set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Promise<Unit> &&promise) {
+ if (ttl < 0) {
+ return promise.set_error(Status::Error(400, "Message auto-delete time can't be negative"));
}
- if (!have_dialog_force(dialog_id)) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+ Dialog *d = get_dialog_force(dialog_id, "set_dialog_message_ttl");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Write)) {
+ return promise.set_error(Status::Error(400, "Have no write access to the chat"));
}
+ LOG(INFO) << "Begin to set message TTL in " << dialog_id << " to " << ttl;
+
switch (dialog_id.get_type()) {
case DialogType::User:
- return promise.set_error(Status::Error(3, "Can't add members to a private chat"));
- case DialogType::Chat:
- return promise.set_error(Status::Error(3, "Can't add many members at once to a basic group chat"));
- case DialogType::Channel:
- return td_->contacts_manager_->add_channel_participants(dialog_id.get_channel_id(), user_ids, std::move(promise));
+ if (dialog_id == get_my_dialog_id() ||
+ dialog_id == DialogId(ContactsManager::get_service_notifications_user_id())) {
+ return promise.set_error(Status::Error(400, "Message auto-delete time in the chat can't be changed"));
+ }
+ break;
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
+ if (!status.can_delete_messages()) {
+ return promise.set_error(
+ Status::Error(400, "Not enough rights to change message auto-delete time in the chat"));
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
+ if (!status.can_change_info_and_settings()) {
+ return promise.set_error(
+ Status::Error(400, "Not enough rights to change message auto-delete time in the chat"));
+ }
+ break;
+ }
case DialogType::SecretChat:
- return promise.set_error(Status::Error(3, "Can't add members to a secret chat"));
+ break;
case DialogType::None:
default:
UNREACHABLE();
}
-}
-void MessagesManager::set_dialog_participant_status(DialogId dialog_id, UserId user_id,
- const tl_object_ptr<td_api::ChatMemberStatus> &chat_member_status,
- Promise<Unit> &&promise) {
- auto status = get_dialog_participant_status(chat_member_status);
- LOG(INFO) << "Receive SetChatMemberStatus request with " << user_id << " and " << dialog_id << " to " << status;
- if (!have_dialog_force(dialog_id)) {
- return promise.set_error(Status::Error(3, "Chat not found"));
- }
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ // TODO invoke after
+ td_->create_handler<SetHistoryTtlQuery>(std::move(promise))->send(dialog_id, ttl);
+ } else {
+ bool need_update_dialog_pos = false;
+ Message *m = get_message_to_send(d, MessageId(), MessageId(), MessageSendOptions(),
+ create_chat_set_ttl_message_content(ttl), &need_update_dialog_pos);
- switch (dialog_id.get_type()) {
- case DialogType::User:
- return promise.set_error(Status::Error(3, "Chat member status can't be changed in private chats"));
- case DialogType::Chat:
- return td_->contacts_manager_->change_chat_participant_status(dialog_id.get_chat_id(), user_id, status,
- std::move(promise));
- case DialogType::Channel:
- return td_->contacts_manager_->change_channel_participant_status(dialog_id.get_channel_id(), user_id, status,
- std::move(promise));
- case DialogType::SecretChat:
- return promise.set_error(Status::Error(3, "Chat member status can't be changed in secret chats"));
- case DialogType::None:
- default:
- UNREACHABLE();
+ send_update_new_message(d, m);
+ if (need_update_dialog_pos) {
+ send_update_chat_last_message(d, "set_dialog_message_ttl");
+ }
+
+ int64 random_id = begin_send_message(dialog_id, m);
+
+ send_closure(td_->secret_chats_manager_, &SecretChatsManager::send_set_ttl_message, dialog_id.get_secret_chat_id(),
+ ttl, random_id, std::move(promise));
}
}
-DialogParticipant MessagesManager::get_dialog_participant(DialogId dialog_id, UserId user_id, int64 &random_id,
- bool force, Promise<Unit> &&promise) {
- LOG(INFO) << "Receive GetChatMember request to get " << user_id << " in " << dialog_id;
- if (!have_dialog_force(dialog_id)) {
- promise.set_error(Status::Error(3, "Chat not found"));
- return DialogParticipant();
+void MessagesManager::set_dialog_permissions(DialogId dialog_id,
+ const td_api::object_ptr<td_api::chatPermissions> &permissions,
+ Promise<Unit> &&promise) {
+ if (!have_dialog_force(dialog_id, "set_dialog_permissions")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Write)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ if (permissions == nullptr) {
+ return promise.set_error(Status::Error(400, "New permissions must be non-empty"));
}
switch (dialog_id.get_type()) {
- case DialogType::User: {
- auto peer_user_id = dialog_id.get_user_id();
- if (user_id == td_->contacts_manager_->get_my_id("get_dialog_participant")) {
- promise.set_value(Unit());
- return {user_id, peer_user_id, 0, DialogParticipantStatus::Member()};
- }
- if (user_id == peer_user_id) {
- promise.set_value(Unit());
- return {peer_user_id, user_id, 0, DialogParticipantStatus::Member()};
+ case DialogType::User:
+ return promise.set_error(Status::Error(400, "Can't change private chat permissions"));
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
+ if (!status.can_restrict_members()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to change chat permissions"));
}
-
- promise.set_error(Status::Error(3, "User is not a member of the private chat"));
break;
}
- case DialogType::Chat:
- return td_->contacts_manager_->get_chat_participant(dialog_id.get_chat_id(), user_id, force, std::move(promise));
- case DialogType::Channel:
- return td_->contacts_manager_->get_channel_participant(dialog_id.get_channel_id(), user_id, random_id, force,
- std::move(promise));
- case DialogType::SecretChat: {
- auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
- if (user_id == td_->contacts_manager_->get_my_id("get_dialog_participant")) {
- promise.set_value(Unit());
- return {user_id, peer_user_id, 0, DialogParticipantStatus::Member()};
+ case DialogType::Channel: {
+ if (is_broadcast_channel(dialog_id)) {
+ return promise.set_error(Status::Error(400, "Can't change channel chat permissions"));
}
- if (user_id == peer_user_id) {
- promise.set_value(Unit());
- return {peer_user_id, user_id, 0, DialogParticipantStatus::Member()};
+ auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
+ if (!status.can_restrict_members()) {
+ return promise.set_error(Status::Error(400, "Not enough rights to change chat permissions"));
}
-
- promise.set_error(Status::Error(3, "User is not a member of the secret chat"));
break;
}
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Can't change secret chat permissions"));
case DialogType::None:
default:
UNREACHABLE();
- promise.set_error(Status::Error(500, "Wrong chat type"));
}
- return DialogParticipant();
-}
-std::pair<int32, vector<DialogParticipant>> MessagesManager::search_private_chat_participants(UserId my_user_id,
- UserId peer_user_id,
- const string &query,
- int32 limit) const {
- auto result = td_->contacts_manager_->search_among_users({my_user_id, peer_user_id}, query, limit);
- return {result.first, transform(result.second, [&](UserId user_id) {
- return DialogParticipant(user_id, user_id == my_user_id ? peer_user_id : my_user_id, 0,
- DialogParticipantStatus::Member());
- })};
+ RestrictedRights new_permissions(permissions);
+
+ // TODO this can be wrong if there were previous change permissions requests
+ if (get_dialog_default_permissions(dialog_id) == new_permissions) {
+ return promise.set_value(Unit());
+ }
+
+ // TODO invoke after
+ td_->create_handler<EditChatDefaultBannedRightsQuery>(std::move(promise))->send(dialog_id, new_permissions);
}
-std::pair<int32, vector<DialogParticipant>> MessagesManager::search_dialog_participants(
- DialogId dialog_id, const string &query, int32 limit, int64 &random_id, bool force, Promise<Unit> &&promise) {
- LOG(INFO) << "Receive SearchChatMembers request to search for " << query << " in " << dialog_id;
- if (!have_dialog_force(dialog_id)) {
- promise.set_error(Status::Error(3, "Chat not found"));
- return {};
+void MessagesManager::toggle_dialog_has_protected_content(DialogId dialog_id, bool has_protected_content,
+ Promise<Unit> &&promise) {
+ if (!have_dialog_force(dialog_id, "toggle_dialog_has_protected_content")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
- if (limit < 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be non-negative"));
- return {};
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
- promise.set_value(Unit());
- return search_private_chat_participants(td_->contacts_manager_->get_my_id("search_dialog_participants"),
- dialog_id.get_user_id(), query, limit);
- case DialogType::Chat:
- return td_->contacts_manager_->search_chat_participants(dialog_id.get_chat_id(), query, limit, force,
- std::move(promise));
- case DialogType::Channel:
- return td_->contacts_manager_->get_channel_participants(
- dialog_id.get_channel_id(), td_api::make_object<td_api::supergroupMembersFilterSearch>(query), 0, limit,
- random_id, force, std::move(promise));
- case DialogType::SecretChat: {
- promise.set_value(Unit());
- auto peer_user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
- return search_private_chat_participants(td_->contacts_manager_->get_my_id("search_dialog_participants"),
- peer_user_id, query, limit);
+ case DialogType::SecretChat:
+ return promise.set_error(Status::Error(400, "Can't restrict saving content in the chat"));
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ auto status = td_->contacts_manager_->get_chat_status(chat_id);
+ if (!status.is_creator()) {
+ return promise.set_error(Status::Error(400, "Only owner can restrict saving content"));
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
+ if (!status.is_creator()) {
+ return promise.set_error(Status::Error(400, "Only owner can restrict saving content"));
+ }
+ break;
}
case DialogType::None:
default:
UNREACHABLE();
- promise.set_error(Status::Error(500, "Wrong chat type"));
}
- return {};
+
+ // TODO this can be wrong if there were previous toggle_dialog_has_protected_content requests
+ if (get_dialog_has_protected_content(dialog_id) == has_protected_content) {
+ return promise.set_value(Unit());
+ }
+
+ // TODO invoke after
+ td_->create_handler<ToggleNoForwardsQuery>(std::move(promise))->send(dialog_id, has_protected_content);
}
-vector<UserId> MessagesManager::get_dialog_administrators(DialogId dialog_id, int left_tries, Promise<Unit> &&promise) {
- LOG(INFO) << "Receive GetChatAdministrators request in " << dialog_id;
- if (!have_dialog_force(dialog_id)) {
- promise.set_error(Status::Error(3, "Chat not found"));
- return {};
+void MessagesManager::set_dialog_theme(DialogId dialog_id, const string &theme_name, Promise<Unit> &&promise) {
+ auto d = get_dialog_force(dialog_id, "set_dialog_theme");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Write)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
- case DialogType::SecretChat:
- promise.set_value(Unit());
break;
case DialogType::Chat:
case DialogType::Channel:
- return td_->contacts_manager_->get_dialog_administrators(dialog_id, left_tries, std::move(promise));
+ return promise.set_error(Status::Error(400, "Can't change theme in the chat"));
+ case DialogType::SecretChat: {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (!user_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Can't access the user"));
+ }
+ dialog_id = DialogId(user_id);
+ break;
+ }
case DialogType::None:
default:
UNREACHABLE();
- promise.set_error(Status::Error(500, "Wrong chat type"));
}
- return {};
+
+ // TODO this can be wrong if there were previous change theme requests
+ if (get_dialog_theme_name(d) == theme_name) {
+ return promise.set_value(Unit());
+ }
+
+ // TODO invoke after
+ td_->create_handler<SetChatThemeQuery>(std::move(promise))->send(dialog_id, theme_name);
}
-void MessagesManager::export_dialog_invite_link(DialogId dialog_id, Promise<Unit> &&promise) {
- LOG(INFO) << "Receive ExportDialogInviteLink request for " << dialog_id;
- if (!have_dialog_force(dialog_id)) {
- return promise.set_error(Status::Error(3, "Chat not found"));
+void MessagesManager::set_dialog_description(DialogId dialog_id, const string &description, Promise<Unit> &&promise) {
+ if (!have_dialog_force(dialog_id, "set_dialog_description")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
switch (dialog_id.get_type()) {
case DialogType::User:
- return promise.set_error(Status::Error(3, "Can't invite members to a private chat"));
+ return promise.set_error(Status::Error(400, "Can't change private chat description"));
case DialogType::Chat:
- return td_->contacts_manager_->export_chat_invite_link(dialog_id.get_chat_id(), std::move(promise));
+ return td_->contacts_manager_->set_chat_description(dialog_id.get_chat_id(), description, std::move(promise));
case DialogType::Channel:
- return td_->contacts_manager_->export_channel_invite_link(dialog_id.get_channel_id(), std::move(promise));
+ return td_->contacts_manager_->set_channel_description(dialog_id.get_channel_id(), description,
+ std::move(promise));
case DialogType::SecretChat:
- return promise.set_error(Status::Error(3, "Can't invite members to a secret chat"));
+ return promise.set_error(Status::Error(400, "Can't change secret chat description"));
case DialogType::None:
default:
UNREACHABLE();
}
}
-string MessagesManager::get_dialog_invite_link(DialogId dialog_id) {
+Status MessagesManager::can_pin_messages(DialogId dialog_id) const {
switch (dialog_id.get_type()) {
- case DialogType::Chat:
- return td_->contacts_manager_->get_chat_invite_link(dialog_id.get_chat_id());
- case DialogType::Channel:
- return td_->contacts_manager_->get_channel_invite_link(dialog_id.get_channel_id());
case DialogType::User:
+ break;
+ case DialogType::Chat: {
+ auto chat_id = dialog_id.get_chat_id();
+ auto status = td_->contacts_manager_->get_chat_permissions(chat_id);
+ if (!status.can_pin_messages() ||
+ (td_->auth_manager_->is_bot() && !td_->contacts_manager_->is_appointed_chat_administrator(chat_id))) {
+ return Status::Error(400, "Not enough rights to manage pinned messages in the chat");
+ }
+ break;
+ }
+ case DialogType::Channel: {
+ auto status = td_->contacts_manager_->get_channel_permissions(dialog_id.get_channel_id());
+ bool can_pin = is_broadcast_channel(dialog_id) ? status.can_edit_messages() : status.can_pin_messages();
+ if (!can_pin) {
+ return Status::Error(400, "Not enough rights to manage pinned messages in the chat");
+ }
+ break;
+ }
case DialogType::SecretChat:
+ return Status::Error(400, "Secret chats can't have pinned messages");
case DialogType::None:
- return string();
default:
UNREACHABLE();
- return string();
}
+ if (!have_input_peer(dialog_id, AccessRights::Write)) {
+ return Status::Error(400, "Not enough rights");
+ }
+
+ return Status::OK();
}
-tl_object_ptr<telegram_api::channelAdminLogEventsFilter> MessagesManager::get_channel_admin_log_events_filter(
- const tl_object_ptr<td_api::chatEventLogFilters> &filters) {
- if (filters == nullptr) {
- return nullptr;
+void MessagesManager::pin_dialog_message(DialogId dialog_id, MessageId message_id, bool disable_notification,
+ bool only_for_self, bool is_unpin, Promise<Unit> &&promise) {
+ auto d = get_dialog_force(dialog_id, "pin_dialog_message");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
+ TRY_STATUS_PROMISE(promise, can_pin_messages(dialog_id));
- int32 flags = 0;
- if (filters->message_edits_) {
- flags |= telegram_api::channelAdminLogEventsFilter::EDIT_MASK;
- }
- if (filters->message_deletions_) {
- flags |= telegram_api::channelAdminLogEventsFilter::DELETE_MASK;
- }
- if (filters->message_pins_) {
- flags |= telegram_api::channelAdminLogEventsFilter::PINNED_MASK;
- }
- if (filters->member_joins_) {
- flags |= telegram_api::channelAdminLogEventsFilter::JOIN_MASK;
- }
- if (filters->member_leaves_) {
- flags |= telegram_api::channelAdminLogEventsFilter::LEAVE_MASK;
- }
- if (filters->member_invites_) {
- flags |= telegram_api::channelAdminLogEventsFilter::INVITE_MASK;
+ const Message *m = get_message_force(d, message_id, "pin_dialog_message");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
}
- if (filters->member_promotions_) {
- flags |= telegram_api::channelAdminLogEventsFilter::PROMOTE_MASK;
- flags |= telegram_api::channelAdminLogEventsFilter::DEMOTE_MASK;
+ if (message_id.is_scheduled()) {
+ return promise.set_error(Status::Error(400, "Scheduled message can't be pinned"));
}
- if (filters->member_restrictions_) {
- flags |= telegram_api::channelAdminLogEventsFilter::BAN_MASK;
- flags |= telegram_api::channelAdminLogEventsFilter::UNBAN_MASK;
- flags |= telegram_api::channelAdminLogEventsFilter::KICK_MASK;
- flags |= telegram_api::channelAdminLogEventsFilter::UNKICK_MASK;
+ if (!message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Message can't be pinned"));
}
- if (filters->info_changes_) {
- flags |= telegram_api::channelAdminLogEventsFilter::INFO_MASK;
+
+ if (is_service_message_content(m->content->get_type())) {
+ return promise.set_error(Status::Error(400, "A service message can't be pinned"));
}
- if (filters->setting_changes_) {
- flags |= telegram_api::channelAdminLogEventsFilter::SETTINGS_MASK;
+
+ if (only_for_self && dialog_id.get_type() != DialogType::User) {
+ return promise.set_error(Status::Error(400, "Messages can't be pinned only for self in the chat"));
}
- return make_tl_object<telegram_api::channelAdminLogEventsFilter>(
- flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- false /*ignored*/, false /*ignored*/, false /*ignored*/);
+ // TODO log event
+ td_->create_handler<UpdateDialogPinnedMessageQuery>(std::move(promise))
+ ->send(dialog_id, message_id, is_unpin, disable_notification, only_for_self);
}
-int64 MessagesManager::get_dialog_event_log(DialogId dialog_id, const string &query, int64 from_event_id, int32 limit,
- const tl_object_ptr<td_api::chatEventLogFilters> &filters,
- const vector<UserId> &user_ids, Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- promise.set_error(Status::Error(3, "Method is not available for bots"));
- return 0;
+void MessagesManager::unpin_all_dialog_messages(DialogId dialog_id, MessageId top_thread_message_id,
+ Promise<Unit> &&promise) {
+ auto d = get_dialog_force(dialog_id, "unpin_all_dialog_messages");
+ if (d == nullptr) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
}
+ TRY_STATUS_PROMISE(promise, can_pin_messages(dialog_id));
+ TRY_STATUS_PROMISE(promise, can_use_top_thread_message_id(d, top_thread_message_id, MessageId()));
- if (!have_dialog_force(dialog_id)) {
- promise.set_error(Status::Error(3, "Chat not found"));
- return 0;
- }
+ if (!td_->auth_manager_->is_bot()) {
+ vector<MessageId> message_ids;
+ find_messages(d->messages.get(), message_ids, [top_thread_message_id](const Message *m) {
+ return m->is_pinned && (!top_thread_message_id.is_valid() ||
+ (m->is_topic_message && m->top_thread_message_id == top_thread_message_id));
+ });
- if (dialog_id.get_type() != DialogType::Channel) {
- promise.set_error(Status::Error(3, "Chat is not a supergroup chat"));
- return 0;
- }
+ vector<int64> deleted_message_ids;
+ for (auto message_id : message_ids) {
+ auto m = get_message(d, message_id);
+ CHECK(m != nullptr);
- auto channel_id = dialog_id.get_channel_id();
- if (!td_->contacts_manager_->have_channel(channel_id)) {
- promise.set_error(Status::Error(3, "Chat info not found"));
- return 0;
+ m->is_pinned = false;
+ send_closure(
+ G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateMessageIsPinned>(d->dialog_id.get(), m->message_id.get(), m->is_pinned));
+ on_message_changed(d, m, true, "unpin_all_dialog_messages");
+ }
}
- if (!td_->contacts_manager_->get_channel_status(channel_id).is_administrator()) {
- promise.set_error(Status::Error(3, "Not enough rights to get event log"));
- return 0;
+ if (top_thread_message_id.is_valid()) {
+ AffectedHistoryQuery query = [td = td_, top_thread_message_id](DialogId dialog_id,
+ Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<UnpinAllMessagesQuery>(std::move(query_promise))->send(dialog_id, top_thread_message_id);
+ };
+ run_affected_history_query_until_complete(dialog_id, std::move(query), true, std::move(promise));
+ return;
}
- vector<tl_object_ptr<telegram_api::InputUser>> input_users;
- for (auto user_id : user_ids) {
- auto input_user = td_->contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- promise.set_error(Status::Error(3, "User not found"));
- return 0;
- }
- input_users.push_back(std::move(input_user));
+ set_dialog_last_pinned_message_id(d, MessageId());
+ if (d->message_count_by_index[message_search_filter_index(MessageSearchFilter::Pinned)] != 0) {
+ d->message_count_by_index[message_search_filter_index(MessageSearchFilter::Pinned)] = 0;
+ on_dialog_updated(dialog_id, "unpin_all_dialog_messages");
}
- int64 random_id = 0;
- do {
- random_id = Random::secure_int64();
- } while (random_id == 0 || chat_events_.find(random_id) != chat_events_.end());
- chat_events_[random_id]; // reserve place for result
-
- td_->create_handler<GetChannelAdminLogQuery>(std::move(promise))
- ->send(channel_id, query, from_event_id, limit, get_channel_admin_log_events_filter(filters),
- std::move(input_users), random_id);
-
- return random_id;
+ unpin_all_dialog_messages_on_server(dialog_id, 0, std::move(promise));
}
-tl_object_ptr<td_api::ChatEventAction> MessagesManager::get_chat_event_action_object(
- tl_object_ptr<telegram_api::ChannelAdminLogEventAction> &&action_ptr) {
- CHECK(action_ptr != nullptr);
- switch (action_ptr->get_id()) {
- case telegram_api::channelAdminLogEventActionParticipantJoin::ID:
- return make_tl_object<td_api::chatEventMemberJoined>();
- case telegram_api::channelAdminLogEventActionParticipantLeave::ID:
- return make_tl_object<td_api::chatEventMemberLeft>();
- case telegram_api::channelAdminLogEventActionParticipantInvite::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantInvite>(action_ptr);
- auto member = td_->contacts_manager_->get_dialog_participant(ChannelId(), std::move(action->participant_));
- return make_tl_object<td_api::chatEventMemberInvited>(
- td_->contacts_manager_->get_user_id_object(member.user_id, "chatEventMemberInvited"),
- member.status.get_chat_member_status_object());
- }
- case telegram_api::channelAdminLogEventActionParticipantToggleBan::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantToggleBan>(action_ptr);
- auto old_member =
- td_->contacts_manager_->get_dialog_participant(ChannelId(), std::move(action->prev_participant_));
- auto new_member =
- td_->contacts_manager_->get_dialog_participant(ChannelId(), std::move(action->new_participant_));
- if (old_member.user_id != new_member.user_id) {
- LOG(ERROR) << old_member.user_id << " VS " << new_member.user_id;
- return nullptr;
- }
- return make_tl_object<td_api::chatEventMemberRestricted>(
- td_->contacts_manager_->get_user_id_object(old_member.user_id, "chatEventMemberRestricted"),
- old_member.status.get_chat_member_status_object(), new_member.status.get_chat_member_status_object());
- }
- case telegram_api::channelAdminLogEventActionParticipantToggleAdmin::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionParticipantToggleAdmin>(action_ptr);
- auto old_member =
- td_->contacts_manager_->get_dialog_participant(ChannelId(), std::move(action->prev_participant_));
- auto new_member =
- td_->contacts_manager_->get_dialog_participant(ChannelId(), std::move(action->new_participant_));
- if (old_member.user_id != new_member.user_id) {
- LOG(ERROR) << old_member.user_id << " VS " << new_member.user_id;
- return nullptr;
- }
- return make_tl_object<td_api::chatEventMemberPromoted>(
- td_->contacts_manager_->get_user_id_object(old_member.user_id, "chatEventMemberPromoted"),
- old_member.status.get_chat_member_status_object(), new_member.status.get_chat_member_status_object());
- }
- case telegram_api::channelAdminLogEventActionChangeTitle::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeTitle>(action_ptr);
- return make_tl_object<td_api::chatEventTitleChanged>(std::move(action->prev_value_),
- std::move(action->new_value_));
- }
- case telegram_api::channelAdminLogEventActionChangeAbout::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeAbout>(action_ptr);
- return make_tl_object<td_api::chatEventDescriptionChanged>(std::move(action->prev_value_),
- std::move(action->new_value_));
- }
- case telegram_api::channelAdminLogEventActionChangeUsername::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeUsername>(action_ptr);
- return make_tl_object<td_api::chatEventUsernameChanged>(std::move(action->prev_value_),
- std::move(action->new_value_));
- }
- case telegram_api::channelAdminLogEventActionChangePhoto::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangePhoto>(action_ptr);
- auto file_manager = td_->file_manager_.get();
- auto old_photo = td::get_dialog_photo(file_manager, std::move(action->prev_photo_));
- auto new_photo = td::get_dialog_photo(file_manager, std::move(action->new_photo_));
- return make_tl_object<td_api::chatEventPhotoChanged>(get_chat_photo_object(file_manager, &old_photo),
- get_chat_photo_object(file_manager, &new_photo));
- }
- case telegram_api::channelAdminLogEventActionToggleInvites::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleInvites>(action_ptr);
- return make_tl_object<td_api::chatEventInvitesToggled>(action->new_value_);
- }
- case telegram_api::channelAdminLogEventActionToggleSignatures::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionToggleSignatures>(action_ptr);
- return make_tl_object<td_api::chatEventSignMessagesToggled>(action->new_value_);
- }
- case telegram_api::channelAdminLogEventActionUpdatePinned::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionUpdatePinned>(action_ptr);
- auto message = create_message(
- parse_telegram_api_message(std::move(action->message_), "channelAdminLogEventActionUpdatePinned"), true);
- if (message.second == nullptr) {
- return make_tl_object<td_api::chatEventMessageUnpinned>();
- }
- return make_tl_object<td_api::chatEventMessagePinned>(get_message_object(message.first, message.second.get()));
- }
- case telegram_api::channelAdminLogEventActionEditMessage::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionEditMessage>(action_ptr);
- auto old_message = create_message(
- parse_telegram_api_message(std::move(action->prev_message_), "prev channelAdminLogEventActionEditMessage"),
- true);
- auto new_message = create_message(
- parse_telegram_api_message(std::move(action->new_message_), "new channelAdminLogEventActionEditMessage"),
- true);
- if (old_message.second == nullptr || new_message.second == nullptr || old_message.first != new_message.first) {
- LOG(ERROR) << "Failed to get edited message";
- return nullptr;
- }
- return make_tl_object<td_api::chatEventMessageEdited>(
- get_message_object(old_message.first, old_message.second.get()),
- get_message_object(new_message.first, new_message.second.get()));
- }
- case telegram_api::channelAdminLogEventActionDeleteMessage::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionDeleteMessage>(action_ptr);
- auto message = create_message(
- parse_telegram_api_message(std::move(action->message_), "channelAdminLogEventActionDeleteMessage"), true);
- if (message.second == nullptr) {
- LOG(ERROR) << "Failed to get deleted message";
- return nullptr;
- }
- return make_tl_object<td_api::chatEventMessageDeleted>(get_message_object(message.first, message.second.get()));
- }
- case telegram_api::channelAdminLogEventActionChangeStickerSet::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionChangeStickerSet>(action_ptr);
- auto old_sticker_set_id = td_->stickers_manager_->add_sticker_set(std::move(action->prev_stickerset_));
- auto new_sticker_set_id = td_->stickers_manager_->add_sticker_set(std::move(action->new_stickerset_));
- return make_tl_object<td_api::chatEventStickerSetChanged>(old_sticker_set_id, new_sticker_set_id);
- }
- case telegram_api::channelAdminLogEventActionTogglePreHistoryHidden::ID: {
- auto action = move_tl_object_as<telegram_api::channelAdminLogEventActionTogglePreHistoryHidden>(action_ptr);
- return make_tl_object<td_api::chatEventIsAllHistoryAvailableToggled>(!action->new_value_);
- }
- default:
- UNREACHABLE();
- return nullptr;
- }
-}
-
-void MessagesManager::on_get_event_log(int64 random_id,
- tl_object_ptr<telegram_api::channels_adminLogResults> &&events) {
- auto it = chat_events_.find(random_id);
- CHECK(it != chat_events_.end());
- auto &result = it->second;
- CHECK(result == nullptr);
+class MessagesManager::UnpinAllDialogMessagesOnServerLogEvent {
+ public:
+ DialogId dialog_id_;
- if (events == nullptr) {
- chat_events_.erase(it);
- return;
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(dialog_id_, storer);
}
- LOG(INFO) << "Receive " << to_string(events);
-
- td_->contacts_manager_->on_get_users(std::move(events->users_));
- td_->contacts_manager_->on_get_chats(std::move(events->chats_));
-
- result = make_tl_object<td_api::chatEvents>();
- result->events_.reserve(events->events_.size());
- for (auto &event : events->events_) {
- if (event->date_ <= 0) {
- LOG(ERROR) << "Receive wrong event date = " << event->date_;
- event->date_ = 0;
- }
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(dialog_id_, parser);
+ }
+};
- UserId user_id(event->user_id_);
- if (!user_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << user_id;
- continue;
- }
- LOG_IF(ERROR, !td_->contacts_manager_->have_user(user_id)) << "Have no info about " << user_id;
+uint64 MessagesManager::save_unpin_all_dialog_messages_on_server_log_event(DialogId dialog_id) {
+ UnpinAllDialogMessagesOnServerLogEvent log_event{dialog_id};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::UnpinAllDialogMessagesOnServer,
+ get_log_event_storer(log_event));
+}
- auto action = get_chat_event_action_object(std::move(event->action_));
- if (action == nullptr) {
- continue;
- }
- result->events_.push_back(make_tl_object<td_api::chatEvent>(
- event->id_, event->date_, td_->contacts_manager_->get_user_id_object(user_id, "chatEvent"), std::move(action)));
+void MessagesManager::unpin_all_dialog_messages_on_server(DialogId dialog_id, uint64 log_event_id,
+ Promise<Unit> &&promise) {
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ log_event_id = save_unpin_all_dialog_messages_on_server_log_event(dialog_id);
}
-}
-tl_object_ptr<td_api::chatEvents> MessagesManager::get_chat_events_object(int64 random_id) {
- auto it = chat_events_.find(random_id);
- CHECK(it != chat_events_.end());
- auto result = std::move(it->second);
- chat_events_.erase(it);
- return result;
+ AffectedHistoryQuery query = [td = td_](DialogId dialog_id, Promise<AffectedHistory> &&query_promise) {
+ td->create_handler<UnpinAllMessagesQuery>(std::move(query_promise))->send(dialog_id, MessageId());
+ };
+ run_affected_history_query_until_complete(dialog_id, std::move(query), true,
+ get_erase_log_event_promise(log_event_id, std::move(promise)));
}
-unique_ptr<MessagesManager::Message> *MessagesManager::find_message(unique_ptr<Message> *v, MessageId message_id) {
- return const_cast<unique_ptr<Message> *>(find_message(static_cast<const unique_ptr<Message> *>(v), message_id));
+unique_ptr<MessagesManager::Message> *MessagesManager::treap_find_message(unique_ptr<Message> *v,
+ MessageId message_id) {
+ return const_cast<unique_ptr<Message> *>(treap_find_message(static_cast<const unique_ptr<Message> *>(v), message_id));
}
-const unique_ptr<MessagesManager::Message> *MessagesManager::find_message(const unique_ptr<Message> *v,
- MessageId message_id) {
- LOG(DEBUG) << "Searching for " << message_id << " in " << static_cast<const void *>(v->get());
+const unique_ptr<MessagesManager::Message> *MessagesManager::treap_find_message(const unique_ptr<Message> *v,
+ MessageId message_id) {
while (*v != nullptr) {
- // LOG(DEBUG) << "Pass " << (*v)->message_id;
if ((*v)->message_id.get() < message_id.get()) {
- // LOG(DEBUG) << "Go right";
v = &(*v)->right;
} else if ((*v)->message_id.get() > message_id.get()) {
- // LOG(DEBUG) << "Go left";
v = &(*v)->left;
} else {
- LOG(DEBUG) << "Message found";
break;
}
}
return v;
}
+MessagesManager::Message *MessagesManager::treap_insert_message(unique_ptr<Message> *v, unique_ptr<Message> message) {
+ auto message_id = message->message_id;
+ while (*v != nullptr && (*v)->random_y >= message->random_y) {
+ if ((*v)->message_id.get() < message_id.get()) {
+ v = &(*v)->right;
+ } else if ((*v)->message_id == message_id) {
+ UNREACHABLE();
+ } else {
+ v = &(*v)->left;
+ }
+ }
+
+ unique_ptr<Message> *left = &message->left;
+ unique_ptr<Message> *right = &message->right;
+
+ unique_ptr<Message> cur = std::move(*v);
+ while (cur != nullptr) {
+ if (cur->message_id.get() < message_id.get()) {
+ *left = std::move(cur);
+ left = &((*left)->right);
+ cur = std::move(*left);
+ } else {
+ *right = std::move(cur);
+ right = &((*right)->left);
+ cur = std::move(*right);
+ }
+ }
+ CHECK(*left == nullptr);
+ CHECK(*right == nullptr);
+ *v = std::move(message);
+ return v->get();
+}
+
+unique_ptr<MessagesManager::Message> MessagesManager::treap_delete_message(unique_ptr<Message> *v) {
+ unique_ptr<Message> result = std::move(*v);
+ unique_ptr<Message> left = std::move(result->left);
+ unique_ptr<Message> right = std::move(result->right);
+
+ while (left != nullptr || right != nullptr) {
+ if (left == nullptr || (right != nullptr && right->random_y > left->random_y)) {
+ *v = std::move(right);
+ v = &((*v)->left);
+ right = std::move(*v);
+ } else {
+ *v = std::move(left);
+ v = &((*v)->right);
+ left = std::move(*v);
+ }
+ }
+ CHECK(*v == nullptr);
+
+ return result;
+}
+
MessagesManager::Message *MessagesManager::get_message(Dialog *d, MessageId message_id) {
return const_cast<Message *>(get_message(static_cast<const Dialog *>(d), message_id));
}
const MessagesManager::Message *MessagesManager::get_message(const Dialog *d, MessageId message_id) {
- if (!message_id.is_valid()) {
+ if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
return nullptr;
}
CHECK(d != nullptr);
- LOG(DEBUG) << "Search for " << message_id << " in " << d->dialog_id;
- auto result = find_message(&d->messages, message_id)->get();
- if (result != nullptr) {
+ bool is_scheduled = message_id.is_scheduled();
+ if (is_scheduled && message_id.is_scheduled_server()) {
+ auto server_message_id = message_id.get_scheduled_server_message_id();
+ auto it = d->scheduled_message_date.find(server_message_id);
+ if (it != d->scheduled_message_date.end()) {
+ int32 date = it->second;
+ message_id = MessageId(server_message_id, date);
+ CHECK(message_id.is_scheduled_server());
+ }
+ }
+ auto result = treap_find_message(is_scheduled ? &d->scheduled_messages : &d->messages, message_id)->get();
+ if (result != nullptr && !is_scheduled) {
result->last_access_date = G()->unix_time_cached();
}
+ LOG(INFO) << "Search for " << message_id << " in " << d->dialog_id << " found " << result;
return result;
}
-MessagesManager::Message *MessagesManager::get_message_force(Dialog *d, MessageId message_id) {
- if (!message_id.is_valid()) {
+MessagesManager::Message *MessagesManager::get_message_force(Dialog *d, MessageId message_id, const char *source) {
+ if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
return nullptr;
}
@@ -20291,1063 +34956,125 @@ MessagesManager::Message *MessagesManager::get_message_force(Dialog *d, MessageI
return result;
}
- if (!G()->parameters().use_message_db || message_id.is_yet_unsent()) {
+ if (!G()->parameters().use_message_db || message_id.is_yet_unsent() || is_deleted_message(d, message_id)) {
return nullptr;
}
- if (d->deleted_message_ids.count(message_id)) {
+ if (message_id.is_scheduled() && d->has_loaded_scheduled_messages_from_database) {
return nullptr;
}
- LOG(INFO) << "Try to load " << FullMessageId{d->dialog_id, message_id} << " from database";
- auto r_value = G()->td_db()->get_messages_db_sync()->get_message({d->dialog_id, message_id});
+ LOG(INFO) << "Trying to load " << FullMessageId{d->dialog_id, message_id} << " from database from " << source;
+
+ auto r_value = G()->td_db()->get_message_db_sync()->get_message({d->dialog_id, message_id});
if (r_value.is_error()) {
return nullptr;
}
- return on_get_message_from_database(d->dialog_id, d, r_value.ok());
+ return on_get_message_from_database(d, r_value.ok(), message_id.is_scheduled(), source);
}
-MessagesManager::Message *MessagesManager::on_get_message_from_database(DialogId dialog_id, Dialog *d,
- const BufferSlice &value) {
- if (value.empty()) {
+MessagesManager::Message *MessagesManager::on_get_message_from_database(const MessageDbMessage &message,
+ bool is_scheduled, const char *source) {
+ if (message.data.empty()) {
return nullptr;
}
- auto m = make_unique<Message>();
- log_event_parse(*m, value.as_slice()).ensure();
-
+ auto dialog_id = message.dialog_id;
+ Dialog *d = get_dialog_force(dialog_id, source);
if (d == nullptr) {
- LOG(ERROR) << "Can't find " << dialog_id << ", but have a message from it";
+ LOG(ERROR) << "Can't find " << dialog_id << ", but have a message from it from " << source;
if (!dialog_id.is_valid()) {
- LOG(ERROR) << "Got message in invalid " << dialog_id;
- return nullptr;
- }
-
- get_messages_from_server({FullMessageId{dialog_id, m->message_id}}, Auto());
-
- force_create_dialog(dialog_id, "on_get_message_from_database");
- d = get_dialog_force(dialog_id);
- CHECK(d != nullptr);
- }
-
- if (!have_input_peer(d->dialog_id, AccessRights::Read)) {
- return nullptr;
- }
-
- auto old_message = get_message(d, m->message_id);
- if (old_message != nullptr) {
- // data in the database is always outdated, so return a message from the memory
- return old_message;
- }
-
- Dependencies dependencies;
- add_message_dependencies(dependencies, d->dialog_id, m.get());
- resolve_dependencies_force(dependencies);
-
- m->have_next = false;
- m->have_previous = false;
- m->from_database = true;
- bool need_update = false;
- bool need_update_dialog_pos = false;
- auto result = add_message_to_dialog(d, std::move(m), false, &need_update, &need_update_dialog_pos,
- "on_get_message_from_database");
- if (need_update_dialog_pos) {
- LOG(ERROR) << "Need update dialog pos after load " << (result == nullptr ? MessageId() : result->message_id)
- << " in " << d->dialog_id << " from database";
- send_update_chat_last_message(d, "on_get_message_from_database");
- }
- return result;
-}
-
-NotificationSettings MessagesManager::get_notification_settings(
- tl_object_ptr<telegram_api::PeerNotifySettings> &&notification_settings) {
- int32 constructor_id = notification_settings->get_id();
- if (constructor_id == telegram_api::peerNotifySettingsEmpty::ID) {
- LOG(ERROR) << "Empty notify settings received";
- return {};
- }
- CHECK(constructor_id == telegram_api::peerNotifySettings::ID);
- auto settings = static_cast<const telegram_api::peerNotifySettings *>(notification_settings.get());
- auto mute_until = (settings->mute_until_ <= G()->unix_time() ? 0 : settings->mute_until_);
- return {mute_until, settings->sound_, (settings->flags_ & telegram_api::peerNotifySettings::SHOW_PREVIEWS_MASK) != 0,
- (settings->flags_ & telegram_api::peerNotifySettings::SILENT_MASK) != 0};
-}
-
-unique_ptr<MessageContent> MessagesManager::get_secret_message_document(
- tl_object_ptr<telegram_api::encryptedFile> file,
- tl_object_ptr<secret_api::decryptedMessageMediaDocument> &&document,
- vector<tl_object_ptr<telegram_api::DocumentAttribute>> &&attributes, DialogId owner_dialog_id,
- FormattedText &&caption, bool is_opened) const {
- return get_message_document(td_->documents_manager_->on_get_document(
- {std::move(file), std::move(document), std::move(attributes)}, owner_dialog_id),
- std::move(caption), is_opened);
-}
-
-unique_ptr<MessageContent> MessagesManager::get_message_document(tl_object_ptr<telegram_api::document> &&document,
- DialogId owner_dialog_id, FormattedText &&caption,
- bool is_opened,
- MultiPromiseActor *load_data_multipromise_ptr) const {
- return get_message_document(
- td_->documents_manager_->on_get_document(std::move(document), owner_dialog_id, load_data_multipromise_ptr),
- std::move(caption), is_opened);
-}
-
-unique_ptr<MessageContent> MessagesManager::get_message_document(
- std::pair<DocumentsManager::DocumentType, FileId> &&parsed_document, FormattedText &&caption,
- bool is_opened) const {
- auto document_type = parsed_document.first;
- auto file_id = parsed_document.second;
- if (document_type != DocumentsManager::DocumentType::Unknown) {
- CHECK(file_id.is_valid());
- }
- switch (document_type) {
- case DocumentsManager::DocumentType::Animation:
- return make_unique<MessageAnimation>(file_id, std::move(caption));
- case DocumentsManager::DocumentType::Audio:
- return make_unique<MessageAudio>(file_id, std::move(caption));
- case DocumentsManager::DocumentType::General:
- return make_unique<MessageDocument>(file_id, std::move(caption));
- case DocumentsManager::DocumentType::Sticker:
- return make_unique<MessageSticker>(file_id);
- case DocumentsManager::DocumentType::Unknown:
- return make_unique<MessageUnsupported>();
- case DocumentsManager::DocumentType::Video:
- return make_unique<MessageVideo>(file_id, std::move(caption));
- case DocumentsManager::DocumentType::VideoNote:
- return make_unique<MessageVideoNote>(file_id, is_opened);
- case DocumentsManager::DocumentType::VoiceNote:
- return make_unique<MessageVoiceNote>(file_id, std::move(caption), is_opened);
- default:
- UNREACHABLE();
+ LOG(ERROR) << "Got message in invalid " << dialog_id << " from " << source;
return nullptr;
- }
-}
-
-unique_ptr<MessagePhoto> MessagesManager::get_message_photo(tl_object_ptr<telegram_api::photo> &&photo,
- DialogId owner_dialog_id, FormattedText &&caption) const {
- auto m = make_unique<MessagePhoto>();
-
- m->photo = get_photo(td_->file_manager_.get(), std::move(photo), owner_dialog_id);
- m->caption = std::move(caption);
-
- return m;
-}
-
-FormattedText MessagesManager::get_secret_media_caption(string &&message_text, string &&message_caption) {
- FormattedText caption;
- if (message_text.empty()) {
- caption.text = std::move(message_caption);
- } else if (message_caption.empty()) {
- caption.text = std::move(message_text);
- } else {
- caption.text = message_text + "\n\n" + message_caption;
- }
-
- caption.entities = find_entities(caption.text, false);
- return caption;
-}
-
-FormattedText MessagesManager::get_message_text(string message_text,
- vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
- int32 send_date) const {
- auto entities = get_message_entities(td_->contacts_manager_.get(), std::move(server_entities), "get_message_text");
- auto status = fix_formatted_text(message_text, entities, true, true, true, false);
- if (status.is_error()) {
- if (send_date == 0 || send_date > 1497000000) { // approximate fix date
- LOG(ERROR) << "Receive error " << status << " while parsing message content \"" << message_text << "\" sent at "
- << send_date << " with entities " << format::as_array(entities);
}
- if (!clean_input_string(message_text)) {
- message_text.clear();
- }
- entities.clear();
- }
- return FormattedText{std::move(message_text), std::move(entities)};
-}
-
-template <class ToT, class FromT>
-static tl_object_ptr<ToT> secret_to_telegram(FromT &from);
-
-// fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileLocation;
-static auto secret_to_telegram(secret_api::fileLocationUnavailable &file_location) {
- return make_tl_object<telegram_api::fileLocationUnavailable>(file_location.volume_id_, file_location.local_id_,
- file_location.secret_);
-}
-
-// fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation;
-static auto secret_to_telegram(secret_api::fileLocation &file_location) {
- return make_tl_object<telegram_api::fileLocation>(file_location.dc_id_, file_location.volume_id_,
- file_location.local_id_, file_location.secret_);
-}
-
-// photoSizeEmpty#e17e23c type:string = PhotoSize;
-static auto secret_to_telegram(secret_api::photoSizeEmpty &empty) {
- if (!clean_input_string(empty.type_)) {
- empty.type_.clear();
- }
- return make_tl_object<telegram_api::photoSizeEmpty>(empty.type_);
-}
-// photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
-static auto secret_to_telegram(secret_api::photoSize &photo_size) {
- if (!clean_input_string(photo_size.type_)) {
- photo_size.type_.clear();
- }
- return make_tl_object<telegram_api::photoSize>(photo_size.type_,
- secret_to_telegram<telegram_api::FileLocation>(*photo_size.location_),
- photo_size.w_, photo_size.h_, photo_size.size_);
-}
-
-// photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
-static auto secret_to_telegram(secret_api::photoCachedSize &photo_size) {
- if (!clean_input_string(photo_size.type_)) {
- photo_size.type_.clear();
- }
- return make_tl_object<telegram_api::photoCachedSize>(
- photo_size.type_, secret_to_telegram<telegram_api::FileLocation>(*photo_size.location_), photo_size.w_,
- photo_size.h_, photo_size.bytes_.clone());
-}
-
-// documentAttributeImageSize #6c37c15c w:int h:int = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeImageSize &image_size) {
- return make_tl_object<telegram_api::documentAttributeImageSize>(image_size.w_, image_size.h_);
-}
-
-// documentAttributeAnimated #11b58939 = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeAnimated &animated) {
- return make_tl_object<telegram_api::documentAttributeAnimated>();
-}
-
-// documentAttributeSticker23 #fb0a5727 = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeSticker23 &sticker) {
- return make_tl_object<telegram_api::documentAttributeSticker>(
- 0, false /*ignored*/, "", make_tl_object<telegram_api::inputStickerSetEmpty>(), nullptr);
-}
-static auto secret_to_telegram(secret_api::inputStickerSetEmpty &sticker_set) {
- return make_tl_object<telegram_api::inputStickerSetEmpty>();
-}
-static auto secret_to_telegram(secret_api::inputStickerSetShortName &sticker_set) {
- if (!clean_input_string(sticker_set.short_name_)) {
- sticker_set.short_name_.clear();
- }
- return make_tl_object<telegram_api::inputStickerSetShortName>(sticker_set.short_name_);
-}
-
-// documentAttributeSticker #3a556302 alt:string stickerset:InputStickerSet = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeSticker &sticker) {
- if (!clean_input_string(sticker.alt_)) {
- sticker.alt_.clear();
- }
- return make_tl_object<telegram_api::documentAttributeSticker>(
- 0, false /*ignored*/, sticker.alt_, secret_to_telegram<telegram_api::InputStickerSet>(*sticker.stickerset_),
- nullptr);
-}
-
-// documentAttributeVideo #5910cccb duration:int w:int h:int = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeVideo &video) {
- return make_tl_object<telegram_api::documentAttributeVideo>(0, false /*ignored*/, false /*ignored*/, video.duration_,
- video.w_, video.h_);
-}
-
-// documentAttributeFilename #15590068 file_name:string = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeFilename &filename) {
- if (!clean_input_string(filename.file_name_)) {
- filename.file_name_.clear();
- }
- return make_tl_object<telegram_api::documentAttributeFilename>(filename.file_name_);
-}
-
-// documentAttributeVideo66#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeVideo66 &video) {
- return make_tl_object<telegram_api::documentAttributeVideo>(
- (video.flags_ & secret_api::documentAttributeVideo66::ROUND_MESSAGE_MASK) != 0
- ? telegram_api::documentAttributeVideo::ROUND_MESSAGE_MASK
- : 0,
- video.round_message_, false, video.duration_, video.w_, video.h_);
-}
-
-static auto telegram_documentAttributeAudio(bool is_voice_note, int duration, string title, string performer,
- BufferSlice waveform) {
- if (!clean_input_string(title)) {
- title.clear();
- }
- if (!clean_input_string(performer)) {
- performer.clear();
- }
-
- int32 flags = 0;
- if (is_voice_note) {
- flags |= telegram_api::documentAttributeAudio::VOICE_MASK;
- }
- if (!title.empty()) {
- flags |= telegram_api::documentAttributeAudio::TITLE_MASK;
- }
- if (!performer.empty()) {
- flags |= telegram_api::documentAttributeAudio::PERFORMER_MASK;
- }
- if (waveform.size()) {
- flags |= telegram_api::documentAttributeAudio::WAVEFORM_MASK;
- }
- return make_tl_object<telegram_api::documentAttributeAudio>(flags, is_voice_note, duration, std::move(title),
- std::move(performer), std::move(waveform));
-}
-
-// documentAttributeAudio23 #51448e5 duration:int = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeAudio23 &audio) {
- return telegram_documentAttributeAudio(false, audio.duration_, "", "", Auto());
-}
-// documentAttributeAudio45 #ded218e0 duration:int title:string performer:string = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeAudio45 &audio) {
- return telegram_documentAttributeAudio(false, audio.duration_, audio.title_, audio.performer_, Auto());
-}
-
-// documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string
-// performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
-static auto secret_to_telegram(secret_api::documentAttributeAudio &audio) {
- return telegram_documentAttributeAudio((audio.flags_ & secret_api::documentAttributeAudio::VOICE_MASK) != 0,
- audio.duration_, audio.title_, audio.performer_, audio.waveform_.clone());
-}
-
-static auto secret_to_telegram(std::vector<tl_object_ptr<secret_api::DocumentAttribute>> &attributes) {
- std::vector<tl_object_ptr<telegram_api::DocumentAttribute>> res;
- for (auto &attribute : attributes) {
- auto telegram_attribute = secret_to_telegram<telegram_api::DocumentAttribute>(*attribute);
- if (telegram_attribute) {
- res.push_back(std::move(telegram_attribute));
+ bool is_valid_server_message_id =
+ (is_scheduled ? message.message_id.is_valid_scheduled() && message.message_id.is_scheduled_server()
+ : message.message_id.is_valid() && message.message_id.is_server());
+ if (is_valid_server_message_id &&
+ (dialog_id.get_type() == DialogType::User || dialog_id.get_type() == DialogType::Chat)) {
+ get_message_from_server({dialog_id, message.message_id}, Auto(), "on_get_message_from_database 1");
}
- }
- return res;
-}
-// decryptedMessageMediaExternalDocument#fa95b0dd id:long access_hash:long date:int mime_type:string size:int
-// thumb:PhotoSize dc_id:int attributes:Vector<DocumentAttribute> = DecryptedMessageMedia;
-static auto secret_to_telegram_document(secret_api::decryptedMessageMediaExternalDocument &from) {
- if (!clean_input_string(from.mime_type_)) {
- from.mime_type_.clear();
+ force_create_dialog(dialog_id, source);
+ d = get_dialog_force(dialog_id, source);
+ CHECK(d != nullptr);
}
- return make_tl_object<telegram_api::document>(from.id_, from.access_hash_, from.date_, from.mime_type_, from.size_,
- secret_to_telegram<telegram_api::PhotoSize>(*from.thumb_), from.dc_id_,
- 0, secret_to_telegram(from.attributes_));
-}
-template <class ToT, class FromT>
-static tl_object_ptr<ToT> secret_to_telegram(FromT &from) {
- tl_object_ptr<ToT> res;
- downcast_call(from, [&](auto &p) { res = secret_to_telegram(p); });
- return res;
+ return on_get_message_from_database(d, message.message_id, message.data, is_scheduled, source);
}
-Photo MessagesManager::get_web_document_photo(tl_object_ptr<telegram_api::WebDocument> web_document,
- DialogId owner_dialog_id) const {
- PhotoSize s =
- get_web_document_photo_size(td_->file_manager_.get(), FileType::Photo, owner_dialog_id, std::move(web_document));
- Photo photo;
- if (!s.file_id.is_valid()) {
- photo.id = -2;
- } else {
- photo.id = 0;
- photo.photos.push_back(s);
- }
- return photo;
+MessagesManager::Message *MessagesManager::on_get_message_from_database(Dialog *d,
+ const MessageDbDialogMessage &message,
+ bool is_scheduled, const char *source) {
+ return on_get_message_from_database(d, message.message_id, message.data, is_scheduled, source);
}
-unique_ptr<MessageContent> MessagesManager::get_secret_message_content(
- string message_text, tl_object_ptr<telegram_api::encryptedFile> file,
- tl_object_ptr<secret_api::DecryptedMessageMedia> &&media,
- vector<tl_object_ptr<secret_api::MessageEntity>> &&secret_entities, DialogId owner_dialog_id,
- MultiPromiseActor &load_data_multipromise) const {
- auto entities = get_message_entities(std::move(secret_entities));
- auto status = fix_formatted_text(message_text, entities, true, false, true, false);
- if (status.is_error()) {
- LOG(WARNING) << "Receive error " << status << " while parsing secret message \"" << message_text
- << "\" with entities " << format::as_array(entities);
- if (!clean_input_string(message_text)) {
- message_text.clear();
- }
- entities.clear();
- }
-
- if (media == nullptr) {
- return make_unique<MessageText>(FormattedText{std::move(message_text), std::move(entities)}, WebPageId());
- }
-
- int32 constructor_id = media->get_id();
- if (message_text.size()) {
- if (constructor_id != secret_api::decryptedMessageMediaEmpty::ID) {
- LOG(INFO) << "Receive non-empty message text and media";
- } else {
- return make_unique<MessageText>(FormattedText{std::move(message_text), std::move(entities)}, WebPageId());
- }
- }
-
- // support of old layer and old constructions
- switch (constructor_id) {
- case secret_api::decryptedMessageMediaVideo::ID: {
- auto video = move_tl_object_as<secret_api::decryptedMessageMediaVideo>(media);
- std::vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
- attributes.emplace_back(
- make_tl_object<secret_api::documentAttributeVideo>(video->duration_, video->w_, video->h_));
- media = make_tl_object<secret_api::decryptedMessageMediaDocument>(
- std::move(video->thumb_), video->thumb_w_, video->thumb_h_, video->mime_type_, video->size_,
- std::move(video->key_), std::move(video->iv_), std::move(attributes), std::move(video->caption_));
-
- constructor_id = secret_api::decryptedMessageMediaDocument::ID;
- break;
- }
+MessagesManager::Message *MessagesManager::on_get_message_from_database(Dialog *d, MessageId expected_message_id,
+ const BufferSlice &value, bool is_scheduled,
+ const char *source) {
+ if (value.empty()) {
+ return nullptr;
}
- bool is_media_empty = false;
- switch (constructor_id) {
- case secret_api::decryptedMessageMediaEmpty::ID:
- LOG(ERROR) << "Receive empty message text and media";
- is_media_empty = true;
- break;
- case secret_api::decryptedMessageMediaGeoPoint::ID: {
- auto message_geo_point = move_tl_object_as<secret_api::decryptedMessageMediaGeoPoint>(media);
-
- auto m = make_unique<MessageLocation>(Location(std::move(message_geo_point)));
- if (m->location.empty()) {
- is_media_empty = true;
- break;
- }
-
- return std::move(m);
- }
- case secret_api::decryptedMessageMediaVenue::ID: {
- auto message_venue = move_tl_object_as<secret_api::decryptedMessageMediaVenue>(media);
-
- if (!clean_input_string(message_venue->title_)) {
- message_venue->title_.clear();
- }
- if (!clean_input_string(message_venue->address_)) {
- message_venue->address_.clear();
- }
- if (!clean_input_string(message_venue->provider_)) {
- message_venue->provider_.clear();
- }
- if (!clean_input_string(message_venue->venue_id_)) {
- message_venue->venue_id_.clear();
- }
-
- auto m =
- make_unique<MessageVenue>(Venue(Location(message_venue->lat_, message_venue->long_),
- std::move(message_venue->title_), std::move(message_venue->address_),
- std::move(message_venue->provider_), std::move(message_venue->venue_id_)));
- if (m->venue.empty()) {
- is_media_empty = true;
- break;
- }
-
- return std::move(m);
- }
- case secret_api::decryptedMessageMediaContact::ID: {
- auto message_contact = move_tl_object_as<secret_api::decryptedMessageMediaContact>(media);
- if (!clean_input_string(message_contact->phone_number_)) {
- message_contact->phone_number_.clear();
- }
- if (!clean_input_string(message_contact->first_name_)) {
- message_contact->first_name_.clear();
- }
- if (!clean_input_string(message_contact->last_name_)) {
- message_contact->last_name_.clear();
- }
- return make_unique<MessageContact>(Contact(message_contact->phone_number_, message_contact->first_name_,
- message_contact->last_name_, message_contact->user_id_));
- }
- case secret_api::decryptedMessageMediaWebPage::ID: {
- auto media_web_page = move_tl_object_as<secret_api::decryptedMessageMediaWebPage>(media);
- if (!clean_input_string(media_web_page->url_)) {
- media_web_page->url_.clear();
- }
- auto r_http_url = parse_url(media_web_page->url_);
- if (r_http_url.is_error()) {
- is_media_empty = true;
- break;
- }
- auto url = r_http_url.ok().get_url();
-
- auto web_page_id = td_->web_pages_manager_->get_web_page_by_url(url, load_data_multipromise.get_promise());
- auto result = make_unique<MessageText>(FormattedText{std::move(message_text), std::move(entities)}, web_page_id);
- if (!result->web_page_id.is_valid()) {
- load_data_multipromise.add_promise(
- PromiseCreator::lambda([td = td_, url, &web_page_id = result->web_page_id](Result<Unit> result) {
- if (result.is_ok()) {
- web_page_id = td->web_pages_manager_->get_web_page_by_url(url);
- }
- }));
- }
- return std::move(result);
- }
- case secret_api::decryptedMessageMediaExternalDocument::ID: {
- auto external_document = move_tl_object_as<secret_api::decryptedMessageMediaExternalDocument>(media);
- auto document = secret_to_telegram_document(*external_document);
- return get_message_document(std::move(document), owner_dialog_id,
- FormattedText{std::move(message_text), std::move(entities)}, false,
- &load_data_multipromise);
- }
- }
- if (file == nullptr && !is_media_empty) {
- LOG(ERROR) << "Received secret message with media, but without a file";
- is_media_empty = true;
- }
- if (is_media_empty) {
- return make_unique<MessageText>(FormattedText{std::move(message_text), std::move(entities)}, WebPageId());
- }
- switch (constructor_id) {
- case secret_api::decryptedMessageMediaPhoto::ID: {
- auto message_photo = move_tl_object_as<secret_api::decryptedMessageMediaPhoto>(media);
- if (!clean_input_string(message_photo->caption_)) {
- message_photo->caption_.clear();
- }
- return make_unique<MessagePhoto>(
- get_photo(td_->file_manager_.get(), std::move(file), std::move(message_photo), owner_dialog_id),
- get_secret_media_caption(std::move(message_text), std::move(message_photo->caption_)));
- }
- case secret_api::decryptedMessageMediaDocument::ID: {
- auto message_document = move_tl_object_as<secret_api::decryptedMessageMediaDocument>(media);
- if (!clean_input_string(message_document->caption_)) {
- message_document->caption_.clear();
- }
- if (!clean_input_string(message_document->mime_type_)) {
- message_document->mime_type_.clear();
- }
- auto attributes = secret_to_telegram(message_document->attributes_);
- message_document->attributes_.clear();
- return get_secret_message_document(
- std::move(file), std::move(message_document), std::move(attributes), owner_dialog_id,
- get_secret_media_caption(std::move(message_text), std::move(message_document->caption_)), false);
- }
- default:
- LOG(ERROR) << "Unsupported: " << to_string(media);
- return make_unique<MessageUnsupported>();
+ auto m = parse_message(d, expected_message_id, value, is_scheduled);
+ if (m == nullptr) {
+ return nullptr;
}
-}
-unique_ptr<MessageContent> MessagesManager::get_message_content(FormattedText message,
- tl_object_ptr<telegram_api::MessageMedia> &&media,
- DialogId owner_dialog_id, bool is_content_read,
- UserId via_bot_user_id, int32 *ttl) const {
- if (media == nullptr) {
- return make_unique<MessageText>(std::move(message), WebPageId());
+ CHECK(d != nullptr);
+ auto dialog_id = d->dialog_id;
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ return nullptr;
}
- int32 constructor_id = media->get_id();
- if (message.text.size()) {
- if (constructor_id != telegram_api::messageMediaEmpty::ID) {
- LOG(INFO) << "Receive non-empty message text and media for message from " << owner_dialog_id;
- } else {
- return make_unique<MessageText>(std::move(message), WebPageId());
- }
- }
- switch (constructor_id) {
- case telegram_api::messageMediaEmpty::ID:
- LOG(ERROR) << "Receive empty message text and media for message from " << owner_dialog_id;
- return make_unique<MessageText>(std::move(message), WebPageId());
- case telegram_api::messageMediaPhoto::ID: {
- auto message_photo = move_tl_object_as<telegram_api::messageMediaPhoto>(media);
- if ((message_photo->flags_ & telegram_api::messageMediaPhoto::PHOTO_MASK) == 0) {
- if ((message_photo->flags_ & telegram_api::messageMediaPhoto::TTL_SECONDS_MASK) == 0) {
- LOG(ERROR) << "Receive messageMediaPhoto without photo and TTL: " << oneline(to_string(message_photo));
- break;
- }
-
- return make_unique<MessageExpiredPhoto>();
- }
-
- auto photo_ptr = std::move(message_photo->photo_);
- int32 photo_id = photo_ptr->get_id();
- if (photo_id == telegram_api::photoEmpty::ID) {
- return make_unique<MessageExpiredPhoto>();
- }
- CHECK(photo_id == telegram_api::photo::ID);
-
- if (ttl != nullptr && (message_photo->flags_ & telegram_api::messageMediaPhoto::TTL_SECONDS_MASK) != 0) {
- *ttl = message_photo->ttl_seconds_;
- }
- return get_message_photo(move_tl_object_as<telegram_api::photo>(photo_ptr), owner_dialog_id, std::move(message));
- }
- case telegram_api::messageMediaGeo::ID: {
- auto message_geo_point = move_tl_object_as<telegram_api::messageMediaGeo>(media);
-
- auto m = make_unique<MessageLocation>(Location(std::move(message_geo_point->geo_)));
- if (m->location.empty()) {
- break;
- }
-
- return std::move(m);
- }
- case telegram_api::messageMediaGeoLive::ID: {
- auto message_geo_point_live = move_tl_object_as<telegram_api::messageMediaGeoLive>(media);
- int32 period = message_geo_point_live->period_;
- auto location = Location(std::move(message_geo_point_live->geo_));
- if (location.empty()) {
- break;
- }
-
- if (period <= 0) {
- LOG(ERROR) << "Receive wrong live location period = " << period;
- return make_unique<MessageLocation>(std::move(location));
- }
- return make_unique<MessageLiveLocation>(std::move(location), period);
- }
- case telegram_api::messageMediaVenue::ID: {
- auto message_venue = move_tl_object_as<telegram_api::messageMediaVenue>(media);
-
- auto m = make_unique<MessageVenue>(Venue(message_venue->geo_, std::move(message_venue->title_),
- std::move(message_venue->address_), std::move(message_venue->provider_),
- std::move(message_venue->venue_id_)));
- if (m->venue.empty()) {
- break;
- }
-
- return std::move(m);
- }
- case telegram_api::messageMediaContact::ID: {
- auto message_contact = move_tl_object_as<telegram_api::messageMediaContact>(media);
- if (message_contact->user_id_ != 0) {
- td_->contacts_manager_->get_user_id_object(UserId(message_contact->user_id_),
- "messageMediaContact"); // to ensure updateUser
- }
- return make_unique<MessageContact>(Contact(message_contact->phone_number_, message_contact->first_name_,
- message_contact->last_name_, message_contact->user_id_));
- }
- case telegram_api::messageMediaDocument::ID: {
- auto message_document = move_tl_object_as<telegram_api::messageMediaDocument>(media);
- if ((message_document->flags_ & telegram_api::messageMediaDocument::DOCUMENT_MASK) == 0) {
- if ((message_document->flags_ & telegram_api::messageMediaDocument::TTL_SECONDS_MASK) == 0) {
- LOG(ERROR) << "Receive messageMediaDocument without document and TTL: "
- << oneline(to_string(message_document));
- break;
- }
-
- return make_unique<MessageExpiredVideo>();
- }
-
- auto document_ptr = std::move(message_document->document_);
- int32 document_id = document_ptr->get_id();
- if (document_id == telegram_api::documentEmpty::ID) {
- break;
- }
- CHECK(document_id == telegram_api::document::ID);
-
- if (ttl != nullptr && (message_document->flags_ & telegram_api::messageMediaDocument::TTL_SECONDS_MASK) != 0) {
- *ttl = message_document->ttl_seconds_;
- }
- return get_message_document(move_tl_object_as<telegram_api::document>(document_ptr), owner_dialog_id,
- std::move(message), is_content_read, nullptr);
- }
- case telegram_api::messageMediaGame::ID: {
- auto message_game = move_tl_object_as<telegram_api::messageMediaGame>(media);
-
- auto m = make_unique<MessageGame>(Game(td_, std::move(message_game->game_), owner_dialog_id));
- if (m->game.empty()) {
- break;
- }
-
- m->game.set_bot_user_id(via_bot_user_id);
- m->game.set_message_text(std::move(message));
-
- return std::move(m);
+ auto old_message = get_message(d, m->message_id);
+ if (old_message != nullptr) {
+ // data in the database is always outdated, so return a message from the memory
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ CHECK(!is_scheduled);
+ // just in case restore random_id to message_id corespondence
+ // can be needed if there was newer unloaded message with the same random_id
+ add_random_id_to_message_id_correspondence(d, old_message->random_id, old_message->message_id);
}
- case telegram_api::messageMediaInvoice::ID: {
- auto message_invoice = move_tl_object_as<telegram_api::messageMediaInvoice>(media);
- MessageId receipt_message_id;
- if ((message_invoice->flags_ & telegram_api::messageMediaInvoice::RECEIPT_MSG_ID_MASK) != 0) {
- receipt_message_id = MessageId(ServerMessageId(message_invoice->receipt_msg_id_));
- if (!receipt_message_id.is_valid()) {
- LOG(ERROR) << "Receive as receipt message " << receipt_message_id << " in " << owner_dialog_id;
- receipt_message_id = MessageId();
- }
- }
- bool need_shipping_address =
- (message_invoice->flags_ & telegram_api::messageMediaInvoice::SHIPPING_ADDRESS_REQUESTED_MASK) != 0;
- bool is_test = (message_invoice->flags_ & telegram_api::messageMediaInvoice::TEST_MASK) != 0;
- return make_unique<MessageInvoice>(std::move(message_invoice->title_), std::move(message_invoice->description_),
- get_web_document_photo(std::move(message_invoice->photo_), owner_dialog_id),
- std::move(message_invoice->start_param_), message_invoice->total_amount_,
- std::move(message_invoice->currency_), is_test, need_shipping_address,
- receipt_message_id);
- }
- case telegram_api::messageMediaWebPage::ID: {
- auto media_web_page = move_tl_object_as<telegram_api::messageMediaWebPage>(media);
- auto web_page_id = td_->web_pages_manager_->on_get_web_page(std::move(media_web_page->webpage_), owner_dialog_id);
- return make_unique<MessageText>(std::move(message), web_page_id);
+ if (old_message->notification_id.is_valid() && !is_scheduled) {
+ add_notification_id_to_message_id_correspondence(d, old_message->notification_id, old_message->message_id);
}
- case telegram_api::messageMediaUnsupported::ID: {
- return make_unique<MessageUnsupported>();
- }
- default:
- UNREACHABLE();
+ return old_message;
}
- // explicit empty media message
- return make_unique<MessageText>(std::move(message), WebPageId());
-}
-
-unique_ptr<MessageContent> MessagesManager::dup_message_content(DialogId dialog_id, const MessageContent *content,
- bool for_forward) {
- CHECK(content != nullptr);
-
- bool to_secret = dialog_id.get_type() == DialogType::SecretChat;
- auto fix_file_id = [dialog_id, to_secret, file_manager = td_->file_manager_.get()](FileId file_id) {
- auto file_view = file_manager->get_file_view(file_id);
- if (to_secret && !file_view.is_encrypted()) {
- auto download_file_id = file_manager->dup_file_id(file_id);
- file_id = file_manager
- ->register_generate(FileType::Encrypted, FileLocationSource::FromServer, "",
- PSTRING() << "#file_id#" << download_file_id.get(), dialog_id, file_view.size())
- .ok();
- }
- return file_manager->dup_file_id(file_id);
- };
-
- FileId thumbnail_file_id;
- if (to_secret) {
- thumbnail_file_id = get_message_content_thumbnail_file_id(content);
- }
- switch (content->get_id()) {
- case MessageAnimation::ID: {
- auto result = make_unique<MessageAnimation>(*static_cast<const MessageAnimation *>(content));
- if (td_->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
- return std::move(result);
- }
- result->file_id = td_->animations_manager_->dup_animation(fix_file_id(result->file_id), result->file_id);
- CHECK(result->file_id.is_valid());
- return std::move(result);
- }
- case MessageAudio::ID: {
- auto result = make_unique<MessageAudio>(*static_cast<const MessageAudio *>(content));
- if (td_->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
- return std::move(result);
- }
- result->file_id = td_->audios_manager_->dup_audio(fix_file_id(result->file_id), result->file_id);
- CHECK(result->file_id.is_valid());
- return std::move(result);
- }
- case MessageContact::ID:
- return make_unique<MessageContact>(*static_cast<const MessageContact *>(content));
- case MessageDocument::ID: {
- auto result = make_unique<MessageDocument>(*static_cast<const MessageDocument *>(content));
- if (td_->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
- return std::move(result);
- }
- result->file_id = td_->documents_manager_->dup_document(fix_file_id(result->file_id), result->file_id);
- CHECK(result->file_id.is_valid());
- return std::move(result);
- }
- case MessageGame::ID:
- return make_unique<MessageGame>(*static_cast<const MessageGame *>(content));
- case MessageInvoice::ID:
- return make_unique<MessageInvoice>(*static_cast<const MessageInvoice *>(content));
- case MessageLiveLocation::ID:
- if (to_secret || for_forward) {
- return make_unique<MessageLocation>(Location(static_cast<const MessageLiveLocation *>(content)->location));
- } else {
- return make_unique<MessageLiveLocation>(*static_cast<const MessageLiveLocation *>(content));
- }
- case MessageLocation::ID:
- return make_unique<MessageLocation>(*static_cast<const MessageLocation *>(content));
- case MessagePhoto::ID: {
- auto result = make_unique<MessagePhoto>(*static_cast<const MessagePhoto *>(content));
-
- if (result->photo.photos.size() > 2 && !to_secret) {
- // already sent photo
- return std::move(result);
- }
-
- // Find 'i' or largest
- CHECK(!result->photo.photos.empty());
- PhotoSize photo;
- for (const auto &size : result->photo.photos) {
- if (size.type == 'i') {
- photo = size;
- }
- }
- if (photo.type == 0) {
- for (const auto &size : result->photo.photos) {
- if (photo.type == 0 || photo.size < size.size) {
- photo = size;
- }
- }
- }
-
- // Find 't' or smallest
- PhotoSize thumbnail;
- for (const auto &size : result->photo.photos) {
- if (size.type == 't') {
- thumbnail = size;
- }
- }
- if (thumbnail.type == 0) {
- for (const auto &size : result->photo.photos) {
- if (size.type != photo.type && (thumbnail.type == 0 || thumbnail.size > size.size)) {
- thumbnail = size;
- }
- }
- }
-
- result->photo.photos.clear();
- if (thumbnail.type != 0) {
- thumbnail.type = 't';
- result->photo.photos.push_back(std::move(thumbnail));
- }
- photo.type = 'i';
- result->photo.photos.push_back(std::move(photo));
-
- if (photo_has_input_media(td_->file_manager_.get(), result->photo, to_secret)) {
- return std::move(result);
- }
-
- result->photo.photos.back().file_id = fix_file_id(result->photo.photos.back().file_id);
- if (thumbnail.type != 0) {
- result->photo.photos[0].file_id = td_->file_manager_->dup_file_id(result->photo.photos[0].file_id);
- }
- return std::move(result);
- }
- case MessageSticker::ID: {
- auto result = make_unique<MessageSticker>(*static_cast<const MessageSticker *>(content));
- if (td_->stickers_manager_->has_input_media(result->file_id, to_secret)) {
- return std::move(result);
- }
- result->file_id = td_->stickers_manager_->dup_sticker(fix_file_id(result->file_id), result->file_id);
- CHECK(result->file_id.is_valid());
- return std::move(result);
- }
- case MessageText::ID:
- return make_unique<MessageText>(*static_cast<const MessageText *>(content));
- case MessageVenue::ID:
- return make_unique<MessageVenue>(*static_cast<const MessageVenue *>(content));
- case MessageVideo::ID: {
- auto result = make_unique<MessageVideo>(*static_cast<const MessageVideo *>(content));
- if (td_->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
- return std::move(result);
- }
- result->file_id = td_->videos_manager_->dup_video(fix_file_id(result->file_id), result->file_id);
- CHECK(result->file_id.is_valid());
- return std::move(result);
- }
- case MessageVideoNote::ID: {
- auto result = make_unique<MessageVideoNote>(*static_cast<const MessageVideoNote *>(content));
- result->is_viewed = false;
- if (td_->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
- return std::move(result);
- }
- result->file_id = td_->video_notes_manager_->dup_video_note(fix_file_id(result->file_id), result->file_id);
- CHECK(result->file_id.is_valid());
- return std::move(result);
- }
- case MessageVoiceNote::ID: {
- auto result = make_unique<MessageVoiceNote>(*static_cast<const MessageVoiceNote *>(content));
- result->is_listened = false;
- if (td_->documents_manager_->has_input_media(result->file_id, thumbnail_file_id, to_secret)) {
- return std::move(result);
- }
- result->file_id = td_->voice_notes_manager_->dup_voice_note(fix_file_id(result->file_id), result->file_id);
- CHECK(result->file_id.is_valid());
- return std::move(result);
- }
- case MessageUnsupported::ID:
- case MessageChatCreate::ID:
- case MessageChatChangeTitle::ID:
- case MessageChatChangePhoto::ID:
- case MessageChatDeletePhoto::ID:
- case MessageChatDeleteHistory::ID:
- case MessageChatAddUsers::ID:
- case MessageChatJoinedByLink::ID:
- case MessageChatDeleteUser::ID:
- case MessageChatMigrateTo::ID:
- case MessageChannelCreate::ID:
- case MessageChannelMigrateFrom::ID:
- case MessagePinMessage::ID:
- case MessageGameScore::ID:
- case MessageScreenshotTaken::ID:
- case MessageChatSetTtl::ID:
- case MessageCall::ID:
- case MessagePaymentSuccessful::ID:
- case MessageContactRegistered::ID:
- case MessageExpiredPhoto::ID:
- case MessageExpiredVideo::ID:
- case MessageCustomServiceAction::ID:
- case MessageWebsiteConnected::ID:
- return nullptr;
- default:
- UNREACHABLE();
+ Dependencies dependencies;
+ add_message_dependencies(dependencies, m.get());
+ if (!dependencies.resolve_force(td_, "on_get_message_from_database") &&
+ dialog_id.get_type() != DialogType::SecretChat) {
+ get_message_from_server({dialog_id, m->message_id}, Auto(), "on_get_message_from_database 2");
}
- UNREACHABLE();
- return nullptr;
-}
-
-unique_ptr<MessageContent> MessagesManager::get_message_action_content(
- tl_object_ptr<telegram_api::MessageAction> &&action, DialogId owner_dialog_id,
- MessageId reply_to_message_id) const {
- CHECK(action != nullptr);
-
- switch (action->get_id()) {
- case telegram_api::messageActionEmpty::ID:
- LOG(ERROR) << "Receive empty message action";
- break;
- case telegram_api::messageActionChatCreate::ID: {
- auto chat_create = move_tl_object_as<telegram_api::messageActionChatCreate>(action);
-
- vector<UserId> participant_user_ids;
- participant_user_ids.reserve(chat_create->users_.size());
- for (auto &user : chat_create->users_) {
- UserId user_id(user);
- if (user_id.is_valid()) {
- participant_user_ids.push_back(user_id);
- } else {
- LOG(ERROR) << "Receive invalid " << user_id;
- }
- }
-
- return make_unique<MessageChatCreate>(std::move(chat_create->title_), std::move(participant_user_ids));
- }
- case telegram_api::messageActionChatEditTitle::ID: {
- auto chat_edit_title = move_tl_object_as<telegram_api::messageActionChatEditTitle>(action);
- return make_unique<MessageChatChangeTitle>(std::move(chat_edit_title->title_));
- }
- case telegram_api::messageActionChatEditPhoto::ID: {
- auto chat_edit_photo = move_tl_object_as<telegram_api::messageActionChatEditPhoto>(action);
-
- auto photo_ptr = std::move(chat_edit_photo->photo_);
- int32 photo_id = photo_ptr->get_id();
- if (photo_id == telegram_api::photoEmpty::ID) {
- break;
- }
- CHECK(photo_id == telegram_api::photo::ID);
- return make_unique<MessageChatChangePhoto>(
- get_photo(td_->file_manager_.get(), move_tl_object_as<telegram_api::photo>(photo_ptr), owner_dialog_id));
- }
- case telegram_api::messageActionChatDeletePhoto::ID: {
- return make_unique<MessageChatDeletePhoto>();
- }
- case telegram_api::messageActionHistoryClear::ID: {
- return make_unique<MessageChatDeleteHistory>();
- }
- case telegram_api::messageActionChatAddUser::ID: {
- auto chat_add_user = move_tl_object_as<telegram_api::messageActionChatAddUser>(action);
-
- vector<UserId> user_ids;
- user_ids.reserve(chat_add_user->users_.size());
- for (auto &user : chat_add_user->users_) {
- UserId user_id(user);
- if (user_id.is_valid()) {
- user_ids.push_back(user_id);
- } else {
- LOG(ERROR) << "Receive invalid " << user_id;
- }
- }
-
- return make_unique<MessageChatAddUsers>(std::move(user_ids));
- }
- case telegram_api::messageActionChatJoinedByLink::ID:
- return make_unique<MessageChatJoinedByLink>();
- case telegram_api::messageActionChatDeleteUser::ID: {
- auto chat_delete_user = move_tl_object_as<telegram_api::messageActionChatDeleteUser>(action);
-
- UserId user_id(chat_delete_user->user_id_);
- if (!user_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << user_id;
- break;
- }
-
- return make_unique<MessageChatDeleteUser>(user_id);
- }
- case telegram_api::messageActionChatMigrateTo::ID: {
- auto chat_migrate_to = move_tl_object_as<telegram_api::messageActionChatMigrateTo>(action);
-
- ChannelId migrated_to_channel_id(chat_migrate_to->channel_id_);
- if (!migrated_to_channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << migrated_to_channel_id;
- break;
- }
-
- return make_unique<MessageChatMigrateTo>(migrated_to_channel_id);
- }
- case telegram_api::messageActionChannelCreate::ID: {
- auto channel_create = move_tl_object_as<telegram_api::messageActionChannelCreate>(action);
- return make_unique<MessageChannelCreate>(std::move(channel_create->title_));
- }
- case telegram_api::messageActionChannelMigrateFrom::ID: {
- auto channel_migrate_from = move_tl_object_as<telegram_api::messageActionChannelMigrateFrom>(action);
-
- ChatId chat_id(channel_migrate_from->chat_id_);
- LOG_IF(ERROR, !chat_id.is_valid()) << "Receive invalid " << chat_id;
-
- return make_unique<MessageChannelMigrateFrom>(std::move(channel_migrate_from->title_), chat_id);
- }
- case telegram_api::messageActionPinMessage::ID: {
- if (!reply_to_message_id.is_valid()) {
- LOG(ERROR) << "Receive pinned message with " << reply_to_message_id;
- reply_to_message_id = MessageId();
- }
- return make_unique<MessagePinMessage>(reply_to_message_id);
- }
- case telegram_api::messageActionGameScore::ID: {
- if (!reply_to_message_id.is_valid()) {
- LOG_IF(ERROR, !td_->auth_manager_->is_bot()) << "Receive game score with " << reply_to_message_id;
- reply_to_message_id = MessageId();
- }
- auto game_score = move_tl_object_as<telegram_api::messageActionGameScore>(action);
- return make_unique<MessageGameScore>(reply_to_message_id, game_score->game_id_, game_score->score_);
- }
- case telegram_api::messageActionPhoneCall::ID: {
- auto phone_call = move_tl_object_as<telegram_api::messageActionPhoneCall>(action);
- auto duration =
- (phone_call->flags_ & telegram_api::messageActionPhoneCall::DURATION_MASK) != 0 ? phone_call->duration_ : 0;
- return make_unique<MessageCall>(phone_call->call_id_, duration, get_call_discard_reason(phone_call->reason_));
- }
- case telegram_api::messageActionPaymentSent::ID: {
- LOG_IF(ERROR, td_->auth_manager_->is_bot()) << "Receive MessageActionPaymentSent";
- if (!reply_to_message_id.is_valid()) {
- LOG(ERROR) << "Receive succesful payment message with " << reply_to_message_id;
- reply_to_message_id = MessageId();
- }
- auto payment_sent = move_tl_object_as<telegram_api::messageActionPaymentSent>(action);
- return make_unique<MessagePaymentSuccessful>(reply_to_message_id, std::move(payment_sent->currency_),
- payment_sent->total_amount_);
- }
- case telegram_api::messageActionPaymentSentMe::ID: {
- LOG_IF(ERROR, !td_->auth_manager_->is_bot()) << "Receive MessageActionPaymentSentMe";
- if (!reply_to_message_id.is_valid()) {
- LOG(ERROR) << "Receive succesful payment message with " << reply_to_message_id;
- reply_to_message_id = MessageId();
- }
- auto payment_sent = move_tl_object_as<telegram_api::messageActionPaymentSentMe>(action);
- auto result = make_unique<MessagePaymentSuccessful>(reply_to_message_id, std::move(payment_sent->currency_),
- payment_sent->total_amount_);
- result->invoice_payload = payment_sent->payload_.as_slice().str();
- result->shipping_option_id = std::move(payment_sent->shipping_option_id_);
- result->order_info = get_order_info(std::move(payment_sent->info_));
- result->telegram_payment_charge_id = std::move(payment_sent->charge_->id_);
- result->provider_payment_charge_id = std::move(payment_sent->charge_->provider_charge_id_);
- return std::move(result);
- }
- case telegram_api::messageActionScreenshotTaken::ID: {
- return make_unique<MessageScreenshotTaken>();
- }
- case telegram_api::messageActionCustomAction::ID: {
- auto custom_action = move_tl_object_as<telegram_api::messageActionCustomAction>(action);
- return make_unique<MessageCustomServiceAction>(std::move(custom_action->message_));
- }
- case telegram_api::messageActionBotAllowed::ID: {
- auto bot_allowed = move_tl_object_as<telegram_api::messageActionBotAllowed>(action);
- return make_unique<MessageWebsiteConnected>(std::move(bot_allowed->domain_));
- }
- default:
- UNREACHABLE();
+ m->have_previous = false;
+ m->have_next = false;
+ m->from_database = true;
+ bool need_update = false;
+ bool need_update_dialog_pos = false;
+ auto result = add_message_to_dialog(d, std::move(m), false, &need_update, &need_update_dialog_pos, source);
+ if (need_update_dialog_pos) {
+ LOG(ERROR) << "Need update dialog pos after load " << (result == nullptr ? MessageId() : result->message_id)
+ << " in " << dialog_id << " from " << source;
+ send_update_chat_last_message(d, source);
}
- // explicit empty or wrong action
- return make_unique<MessageText>(FormattedText(), WebPageId());
+ return result;
}
int32 MessagesManager::get_random_y(MessageId message_id) {
return static_cast<int32>(static_cast<uint32>(message_id.get() * 2101234567u));
}
+void MessagesManager::set_message_id(unique_ptr<Message> &message, MessageId message_id) {
+ message->message_id = message_id;
+ message->random_y = get_random_y(message_id);
+}
+
MessagesManager::Message *MessagesManager::add_message_to_dialog(DialogId dialog_id, unique_ptr<Message> message,
bool from_update, bool *need_update,
bool *need_update_dialog_pos, const char *source) {
@@ -21356,23 +35083,28 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(DialogId dialog
CHECK(need_update_dialog_pos != nullptr);
MessageId message_id = message->message_id;
- if (!message_id.is_valid()) {
+ if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " from " << source;
- debug_add_message_to_dialog_fail_reason = "invalid message id";
+ debug_add_message_to_dialog_fail_reason_ = "invalid message identifier";
return nullptr;
}
- // TODO remove creation of dialog from this function, use cgc or cpc or something else
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, source);
if (d == nullptr) {
- d = add_dialog(dialog_id);
+ if (from_update) {
+ CHECK(!being_added_by_new_message_dialog_id_.is_valid());
+ being_added_by_new_message_dialog_id_ = dialog_id;
+ }
+ d = add_dialog(dialog_id, source);
*need_update_dialog_pos = true;
+ being_added_by_new_message_dialog_id_ = DialogId();
} else {
CHECK(d->dialog_id == dialog_id);
}
return add_message_to_dialog(d, std::move(message), from_update, need_update, need_update_dialog_pos, source);
}
+// keep synced with add_scheduled_message_to_dialog
MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, unique_ptr<Message> message,
bool from_update, bool *need_update,
bool *need_update_dialog_pos, const char *source) {
@@ -21381,107 +35113,170 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
CHECK(need_update != nullptr);
CHECK(need_update_dialog_pos != nullptr);
CHECK(source != nullptr);
+ debug_add_message_to_dialog_fail_reason_ = "success";
+
+ auto debug_have_previous = message->have_previous;
+ auto debug_have_next = message->have_next;
DialogId dialog_id = d->dialog_id;
MessageId message_id = message->message_id;
- if (d->deleted_message_ids.count(message_id)) {
- LOG(INFO) << "Skip adding deleted " << message_id << " to " << dialog_id << " from " << source;
- debug_add_message_to_dialog_fail_reason = "adding deleted message";
- return nullptr;
+ if (!has_message_sender_user_id(dialog_id, message.get()) && !message->sender_dialog_id.is_valid()) {
+ if (is_broadcast_channel(dialog_id)) {
+ message->sender_dialog_id = dialog_id;
+ } else {
+ if (is_discussion_message(dialog_id, message.get())) {
+ message->sender_dialog_id = message->forward_info->from_dialog_id;
+ } else {
+ LOG(ERROR) << "Failed to repair sender chat in " << message_id << " in " << dialog_id;
+ }
+ }
+ }
+ auto dialog_type = dialog_id.get_type();
+ if (message->sender_user_id == ContactsManager::get_anonymous_bot_user_id() &&
+ !message->sender_dialog_id.is_valid() && dialog_type == DialogType::Channel && !is_broadcast_channel(dialog_id)) {
+ message->sender_user_id = UserId();
+ message->sender_dialog_id = dialog_id;
}
- if (message_id.get() <= d->last_clear_history_message_id.get()) {
+
+ if (!message->from_database && message_id.is_valid()) {
+ switch (dialog_type) {
+ case DialogType::Chat:
+ case DialogType::Channel: {
+ message->available_reactions_generation = d->available_reactions_generation;
+ break;
+ }
+ case DialogType::User:
+ case DialogType::SecretChat:
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ message->history_generation = d->history_generation;
+ }
+
+ if (!message->top_thread_message_id.is_valid() && is_thread_message(dialog_id, message.get())) {
+ message->top_thread_message_id = message_id;
+ }
+
+ if (!message_id.is_scheduled() && message_id <= d->last_clear_history_message_id) {
LOG(INFO) << "Skip adding cleared " << message_id << " to " << dialog_id << " from " << source;
- debug_add_message_to_dialog_fail_reason = "cleared full history";
+ if (message->from_database) {
+ delete_message_from_database(d, message_id, message.get(), true);
+ }
+ debug_add_message_to_dialog_fail_reason_ = "cleared full history";
return nullptr;
}
- if (d->deleted_message_ids.count(message->reply_to_message_id)) {
- // LOG(INFO) << "Remove reply to deleted " << message->reply_to_message_id << " in " << message_id << " from " << dialog_id << " from " << source;
- // we don't want to lose information that the message was a reply for now
- // message->reply_to_message_id = MessageId();
- }
- LOG(INFO) << "Adding " << message_id << " of type " << message->content->get_id() << " to " << dialog_id << " from "
+ LOG(INFO) << "Adding " << message_id << " of type " << message->content->get_type() << " to " << dialog_id << " from "
<< source << ". Last new is " << d->last_new_message_id << ", last is " << d->last_message_id
<< ", from_update = " << from_update << ", have_previous = " << message->have_previous
<< ", have_next = " << message->have_next;
if (!message_id.is_valid()) {
+ if (message_id.is_valid_scheduled()) {
+ return add_scheduled_message_to_dialog(d, std::move(message), from_update, need_update, source);
+ }
LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " from " << source;
CHECK(!message->from_database);
- debug_add_message_to_dialog_fail_reason = "invalid message id";
+ debug_add_message_to_dialog_fail_reason_ = "invalid message identifier";
+ return nullptr;
+ }
+
+ if (*need_update) {
+ CHECK(from_update);
+ }
+
+ if (is_deleted_message(d, message_id)) {
+ LOG(INFO) << "Skip adding deleted " << message_id << " to " << dialog_id << " from " << source;
+ debug_add_message_to_dialog_fail_reason_ = "adding deleted message";
return nullptr;
}
- auto message_content_id = message->content->get_id();
+ auto message_content_type = message->content->get_type();
if (is_debug_message_op_enabled()) {
- d->debug_message_op.emplace_back(Dialog::MessageOp::Add, message_id, message_content_id, from_update,
+ d->debug_message_op.emplace_back(Dialog::MessageOp::Add, message_id, message_content_type, from_update,
message->have_previous, message->have_next, source);
}
message->last_access_date = G()->unix_time_cached();
- if (*need_update) {
- CHECK(from_update);
- }
if (from_update) {
CHECK(message->have_next);
CHECK(message->have_previous);
- CHECK(!message_id.is_yet_unsent());
- if (message_id.get() <= d->last_new_message_id.get() && d->dialog_id.get_type() != DialogType::Channel) {
+ if (message_id <= d->last_new_message_id && dialog_type != DialogType::Channel) {
if (!G()->parameters().use_message_db) {
- LOG(ERROR) << "New " << message_id << " in " << dialog_id << " from " << source
- << " has id less than last_new_message_id = " << d->last_new_message_id;
- dump_debug_message_op(d);
+ if (td_->auth_manager_->is_bot() && Time::now() > start_time_ + 300 &&
+ MessageId(ServerMessageId(100)) <= message_id && message_id <= MessageId(ServerMessageId(1000)) &&
+ d->last_new_message_id >= MessageId(ServerMessageId(2147483000))) {
+ LOG(FATAL) << "Force restart because of message_id overflow in " << dialog_id << " from "
+ << d->last_new_message_id << " to " << message_id;
+ }
+ if (!has_qts_messages(dialog_id)) {
+ LOG(ERROR) << "New " << message_id << " in " << dialog_id << " from " << source
+ << " has identifier less than last_new_message_id = " << d->last_new_message_id;
+ dump_debug_message_op(d);
+ }
}
}
}
- if (!from_update && message_id.is_server() && d->last_new_message_id != MessageId() &&
- message_id.get() > d->last_new_message_id.get()) {
- if (!message->from_database) {
- LOG(ERROR) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source
- << ". Last new is " << d->last_new_message_id << ", "
- << to_string(get_message_object(dialog_id, message.get()));
- dump_debug_message_op(d, 3);
- if (dialog_id.get_type() == DialogType::Channel && have_input_peer(dialog_id, AccessRights::Read)) {
- channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
+ if (!from_update && !message->is_failed_to_send) {
+ MessageId max_message_id;
+ if (message_id.is_server()) {
+ if (d->being_added_message_id.is_valid()) {
+ // if a too new message not from update has failed to preload before being_added_message_id was set,
+ // then it should fail to load even after it is set and last_new_message_id has changed
+ max_message_id = d->being_updated_last_new_message_id;
+ } else {
+ max_message_id = d->last_new_message_id;
+ }
+ } else if (message_id.is_local()) {
+ if (d->being_added_message_id.is_valid()) {
+ max_message_id = d->being_updated_last_database_message_id;
+ } else {
+ max_message_id = d->last_database_message_id;
}
- } else {
- LOG(INFO) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source;
}
- debug_add_message_to_dialog_fail_reason = "too new message not from update";
- return nullptr;
+ if (max_message_id != MessageId() && message_id > max_message_id) {
+ if (!message->from_database) {
+ if (!has_qts_messages(dialog_id)) {
+ LOG(ERROR) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source
+ << ". The maximum allowed is " << max_message_id << ", last is " << d->last_message_id
+ << ", being added message is " << d->being_added_message_id << ", channel difference "
+ << debug_channel_difference_dialog_ << " "
+ << to_string(get_message_object(dialog_id, message.get(), "add_message_to_dialog"));
+ dump_debug_message_op(d, 3);
+ }
+
+ if (need_channel_difference_to_add_message(dialog_id, nullptr)) {
+ LOG(INFO) << "Schedule getDifference in " << dialog_id.get_channel_id();
+ channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
+ }
+ } else {
+ LOG(INFO) << "Ignore " << message_id << " in " << dialog_id << " received not through update from " << source;
+ }
+ debug_add_message_to_dialog_fail_reason_ = "too new message not from update";
+ return nullptr;
+ }
}
- if ((message_id.is_server() || (message_id.is_local() && dialog_id.get_type() == DialogType::SecretChat)) &&
- message_id.get() <= d->max_unavailable_message_id.get()) {
+ if ((message_id.is_server() || (message_id.is_local() && dialog_type == DialogType::SecretChat)) &&
+ message_id <= d->max_unavailable_message_id) {
LOG(INFO) << "Can't add an unavailable " << message_id << " to " << dialog_id << " from " << source;
if (message->from_database) {
delete_message_from_database(d, message_id, message.get(), true);
}
- debug_add_message_to_dialog_fail_reason = "ignore unavailable message";
+ debug_add_message_to_dialog_fail_reason_ = "ignore unavailable message";
return nullptr;
}
- if (message_content_id == MessageText::ID) {
- auto web_page_id = static_cast<const MessageText *>(message->content.get())->web_page_id;
- if (web_page_id.is_valid() && !td_->web_pages_manager_->have_web_page(web_page_id)) {
- waiting_for_web_page_messages_.emplace(dialog_id, message_id);
- send_closure(G()->web_pages_manager(), &WebPagesManager::wait_for_pending_web_page, dialog_id, message_id,
- web_page_id);
- }
- }
-
- if (*need_update && message_id.get() <= d->last_new_message_id.get()) {
- *need_update = false;
- }
-
- bool auto_attach = message->have_previous && message->have_next &&
- (from_update || message_id.is_local() || message_id.is_yet_unsent());
- if (message_content_id == MessageChatDeleteHistory::ID) {
- auto m = delete_message(d, message_id, true, need_update_dialog_pos, "message chat delete history");
- if (m != nullptr) {
- send_update_delete_messages(dialog_id, {m->message_id.get()}, true, false);
+ if (message_content_type == MessageContentType::ChatDeleteHistory) {
+ {
+ auto m = delete_message(d, message_id, true, need_update_dialog_pos, "message chat delete history");
+ if (m != nullptr) {
+ send_update_delete_messages(dialog_id, {m->message_id.get()}, true);
+ }
}
int32 last_message_date = 0;
if (d->last_message_id != MessageId()) {
@@ -21493,29 +35288,31 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
}
if (message->date > last_message_date) {
set_dialog_last_clear_history_date(d, message->date, message_id, "update_last_clear_history_date");
- on_dialog_updated(dialog_id, "update_last_clear_history_date");
*need_update_dialog_pos = true;
}
LOG(INFO) << "Process MessageChatDeleteHistory in " << message_id << " in " << dialog_id << " with date "
<< message->date << " from " << source;
+ if (message_id > d->max_unavailable_message_id) {
+ set_dialog_max_unavailable_message_id(dialog_id, message_id, false, "message chat delete history");
+ }
CHECK(!message->from_database);
- debug_add_message_to_dialog_fail_reason = "skip adding MessageChatDeleteHistory";
+ debug_add_message_to_dialog_fail_reason_ = "skip adding MessageChatDeleteHistory";
return nullptr;
}
- if (!message->from_database) {
- // load message from database before updating it
- get_message_force(d, message_id);
+ if (*need_update && message_id <= d->last_new_message_id && !td_->auth_manager_->is_bot()) {
+ *need_update = false;
}
if (message->reply_markup != nullptr &&
(message->reply_markup->type == ReplyMarkup::Type::RemoveKeyboard ||
(message->reply_markup->type == ReplyMarkup::Type::ForceReply && !message->reply_markup->is_personal)) &&
!td_->auth_manager_->is_bot()) {
- if (*need_update && message->reply_markup->is_personal) { // if this keyboard is for us
- if (d->reply_markup_message_id != MessageId()) {
- const Message *old_message = get_message_force(d, d->reply_markup_message_id);
- if (old_message == nullptr || old_message->sender_user_id == message->sender_user_id) {
+ if (from_update && message->reply_markup->is_personal) { // if this keyboard is for us
+ if (d->reply_markup_message_id != MessageId() && message_id > d->reply_markup_message_id) {
+ const Message *old_message = get_message_force(d, d->reply_markup_message_id, "add_message_to_dialog 1");
+ if (old_message == nullptr ||
+ (old_message->sender_user_id.is_valid() && old_message->sender_user_id == message->sender_user_id)) {
set_dialog_reply_markup(d, MessageId());
}
}
@@ -21524,25 +35321,28 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
message->reply_markup = nullptr;
}
- if (from_update) {
- cancel_user_dialog_action(dialog_id, message.get());
- }
+ bool auto_attach = message->have_previous && message->have_next &&
+ (from_update || message_id.is_local() || message_id.is_yet_unsent());
- unique_ptr<Message> *v = &d->messages;
- while (*v != nullptr && (*v)->random_y >= message->random_y) {
- if ((*v)->message_id.get() < message_id.get()) {
- v = &(*v)->right;
- } else if ((*v)->message_id == message_id) {
+ {
+ Message *m = message->from_database ? get_message(d, message_id)
+ : get_message_force(d, message_id, "add_message_to_dialog 2");
+ if (m != nullptr) {
+ CHECK(m->message_id == message_id);
+ CHECK(message->message_id == message_id);
LOG(INFO) << "Adding already existing " << message_id << " in " << dialog_id << " from " << source;
if (*need_update) {
*need_update = false;
if (!G()->parameters().use_message_db) {
- LOG(ERROR) << "Receive again " << (message->is_outgoing ? "outgoing" : "incoming")
- << (message->forward_info == nullptr ? " not" : "") << " forwarded " << message_id
- << " with content of type " << message_content_id << " in " << dialog_id << " from " << source
- << ", current last new is " << d->last_new_message_id << ", last is " << d->last_message_id << ". "
- << td_->updates_manager_->get_state();
- dump_debug_message_op(d, 1);
+ // can happen if the message is received first through getMessage in an unknown chat without
+ // last_new_message_id and only after that received through getDifference or getChannelDifference
+ if (d->last_new_message_id.is_valid()) {
+ LOG(ERROR) << "Receive again " << (message->is_outgoing ? "outgoing" : "incoming")
+ << (message->forward_info == nullptr ? " not" : "") << " forwarded " << message_id
+ << " with content of type " << message_content_type << " in " << dialog_id << " from " << source
+ << ", current last new is " << d->last_new_message_id << ", last is " << d->last_message_id;
+ dump_debug_message_op(d, 1);
+ }
}
}
if (auto_attach) {
@@ -21551,56 +35351,170 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
message->have_previous = false;
message->have_next = false;
}
- if (!message->from_database) {
- bool was_deleted = delete_active_live_location(dialog_id, v->get());
- update_message(d, *v, std::move(message), true, need_update_dialog_pos);
+ if (!message->from_database && (from_update || message->edit_date >= m->edit_date)) {
+ const int32 INDEX_MASK_MASK = ~(message_search_filter_index_mask(MessageSearchFilter::UnreadMention) |
+ message_search_filter_index_mask(MessageSearchFilter::UnreadReaction));
+ auto old_index_mask = get_message_index_mask(dialog_id, m) & INDEX_MASK_MASK;
+ bool was_deleted = delete_active_live_location(dialog_id, m);
+ auto old_file_ids = get_message_content_file_ids(m->content.get(), td_);
+ bool need_send_update = update_message(d, m, std::move(message), need_update_dialog_pos, true);
+ if (!need_send_update) {
+ LOG(INFO) << message_id << " in " << dialog_id << " is not changed";
+ }
+ auto new_index_mask = get_message_index_mask(dialog_id, m) & INDEX_MASK_MASK;
if (was_deleted) {
- try_add_active_live_location(dialog_id, v->get());
+ try_add_active_live_location(dialog_id, m);
+ }
+ change_message_files(dialog_id, m, old_file_ids);
+ if (need_send_update) {
+ on_message_notification_changed(d, m, source);
}
+ update_message_count_by_index(d, -1, old_index_mask & ~new_index_mask);
+ update_message_count_by_index(d, +1, new_index_mask & ~old_index_mask);
}
- return v->get();
- } else {
- v = &(*v)->left;
+ return m;
+ }
+ }
+
+ if (*need_update && !td_->auth_manager_->is_bot()) {
+ if (message_content_type == MessageContentType::PinMessage) {
+ if (is_dialog_pinned_message_notifications_disabled(d) ||
+ !get_message_content_pinned_message_id(message->content.get()).is_valid()) {
+ // treat message pin without pinned message as an ordinary message
+ message->contains_mention = false;
+ }
+ } else if (message->contains_mention && is_dialog_mention_notifications_disabled(d)) {
+ // disable mention notification
+ message->is_mention_notification_disabled = true;
}
}
+ if (message->contains_unread_mention && message_id <= d->last_read_all_mentions_message_id) {
+ LOG(INFO) << "Ignore unread mention in " << message_id;
+ message->contains_unread_mention = false;
+ if (message->from_database) {
+ on_message_changed(d, message.get(), false, "add already read mention message to dialog");
+ }
+ }
+
+ if (*need_update && may_need_message_notification(d, message.get())) {
+ // notification group must be created here because it may force adding new messages from database
+ // in get_message_notification_group_force
+ get_dialog_notification_group_id(d->dialog_id, get_notification_group_info(d, message.get()));
+ }
+ if (*need_update || (!d->last_new_message_id.is_valid() && !message_id.is_yet_unsent() && from_update)) {
+ auto pinned_message_id = get_message_content_pinned_message_id(message->content.get());
+ if (pinned_message_id.is_valid() && pinned_message_id < message_id &&
+ have_message_force(d, pinned_message_id, "preload pinned message")) {
+ LOG(INFO) << "Preloaded pinned " << pinned_message_id << " from database";
+ }
+
+ if (d->pinned_message_notification_message_id.is_valid() &&
+ d->pinned_message_notification_message_id != message_id &&
+ have_message_force(d, d->pinned_message_notification_message_id, "preload previously pinned message")) {
+ LOG(INFO) << "Preloaded previously pinned " << d->pinned_message_notification_message_id << " from database";
+ }
+ }
+ if (from_update && message->top_thread_message_id.is_valid() && message->top_thread_message_id != message_id &&
+ message_id.is_server() && have_message_force(d, message->top_thread_message_id, "preload top reply message")) {
+ LOG(INFO) << "Preloaded top thread " << message->top_thread_message_id << " from database";
+
+ Message *top_m = get_message(d, message->top_thread_message_id);
+ CHECK(top_m != nullptr);
+ if (is_active_message_reply_info(dialog_id, top_m->reply_info) && is_discussion_message(dialog_id, top_m)) {
+ FullMessageId top_full_message_id{top_m->forward_info->from_dialog_id, top_m->forward_info->from_message_id};
+ if (have_message_force(top_full_message_id, "preload discussed message")) {
+ LOG(INFO) << "Preloaded discussed " << top_full_message_id << " from database";
+ }
+ }
+ }
+
+ // there must be no two recursive calls to add_message_to_dialog
+ LOG_CHECK(!d->being_added_message_id.is_valid())
+ << d->dialog_id << " " << d->being_added_message_id << " " << message_id << " " << *need_update << " "
+ << d->pinned_message_notification_message_id << " " << d->last_new_message_id << " " << source;
+ LOG_CHECK(!d->being_deleted_message_id.is_valid())
+ << d->being_deleted_message_id << " " << message_id << " " << source;
+
+ d->being_added_message_id = message_id;
+ d->being_updated_last_new_message_id = d->last_new_message_id;
+ d->being_updated_last_database_message_id = d->last_database_message_id;
+
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ remove_new_secret_chat_notification(d, true);
+ }
+
+ if (message->message_id > d->max_added_message_id) {
+ d->max_added_message_id = message->message_id;
+ }
+
if (d->have_full_history && !message->from_database && !from_update && !message_id.is_local() &&
!message_id.is_yet_unsent()) {
- LOG(ERROR) << "Have full history in " << dialog_id << ", but receive unknown " << message_id << " from " << source
- << ". Last new is " << d->last_new_message_id << ", last is " << d->last_message_id
- << ", first database is " << d->first_database_message_id << ", last database is "
- << d->last_database_message_id << ", last read inbox is " << d->last_read_inbox_message_id
- << ", last read outbox is " << d->last_read_inbox_message_id << ", last read all mentions is "
+ LOG(ERROR) << "Have full history in " << dialog_id << ", but receive unknown " << message_id
+ << " with content of type " << message_content_type << " from " << source << ". Last new is "
+ << d->last_new_message_id << ", last is " << d->last_message_id << ", first database is "
+ << d->first_database_message_id << ", last database is " << d->last_database_message_id
+ << ", last read inbox is " << d->last_read_inbox_message_id << ", last read outbox is "
+ << d->last_read_inbox_message_id << ", last read all mentions is "
<< d->last_read_all_mentions_message_id << ", max unavailable is " << d->max_unavailable_message_id
- << ", last assigned is " << d->last_assigned_message_id;
+ << ", last assigned is " << d->last_assigned_message_id << ", last clear history date is "
+ << d->last_clear_history_date << ", last clear history is " << d->last_clear_history_message_id
+ << ", last delete is " << d->deleted_last_message_id << ", delete last message date is "
+ << d->delete_last_message_date << ", have_full_history source = " << d->have_full_history_source;
d->have_full_history = false;
+ d->have_full_history_source = 0;
on_dialog_updated(dialog_id, "drop have_full_history");
}
- if (!d->is_opened && d->messages != nullptr && is_message_unload_enabled()) {
+ if (!d->is_opened && d->messages != nullptr && is_message_unload_enabled() && !d->has_unload_timeout) {
LOG(INFO) << "Schedule unload of " << dialog_id;
- pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), DIALOG_UNLOAD_DELAY);
+ pending_unload_dialog_timeout_.add_timeout_in(dialog_id.get(), get_next_unload_dialog_delay(d));
+ d->has_unload_timeout = true;
}
if (message->ttl > 0 && message->ttl_expires_at != 0) {
auto now = Time::now();
if (message->ttl_expires_at <= now) {
- if (d->dialog_id.get_type() == DialogType::SecretChat) {
+ if (dialog_type == DialogType::SecretChat) {
LOG(INFO) << "Can't add " << message_id << " with expired TTL to " << dialog_id << " from " << source;
delete_message_from_database(d, message_id, message.get(), true);
- debug_add_message_to_dialog_fail_reason = "delete expired by TTL message";
+ debug_add_message_to_dialog_fail_reason_ = "delete expired by TTL message";
+ d->being_added_message_id = MessageId();
return nullptr;
} else {
on_message_ttl_expired_impl(d, message.get());
- message_content_id = message->content->get_id();
+ message_content_type = message->content->get_type();
if (message->from_database) {
- add_message_to_database(d, message.get(), "add expired message to dialog");
+ on_message_changed(d, message.get(), false, "add expired message to dialog");
}
}
} else {
ttl_register_message(dialog_id, message.get(), now);
}
}
+ if (message->ttl_period > 0) {
+ CHECK(dialog_type != DialogType::SecretChat);
+ auto server_time = G()->server_time();
+ if (message->date + message->ttl_period <= server_time) {
+ LOG(INFO) << "Can't add " << message_id << " with expired TTL period to " << dialog_id << " from " << source;
+ delete_message_from_database(d, message_id, message.get(), true);
+ debug_add_message_to_dialog_fail_reason_ = "delete expired by TTL period message";
+ d->being_added_message_id = MessageId();
+ return nullptr;
+ } else {
+ ttl_period_register_message(dialog_id, message.get(), server_time);
+ }
+ }
+
+ if (message->from_database && !message->are_media_timestamp_entities_found) {
+ auto text = get_message_content_text_mutable(message->content.get());
+ if (text != nullptr) {
+ fix_formatted_text(text->text, text->entities, true, true, true, false, false).ensure();
+ // always call to save are_media_timestamp_entities_found flag
+ on_message_changed(d, message.get(), false, "save media timestamp entities");
+ }
+ }
+ message->are_media_timestamp_entities_found = true;
LOG(INFO) << "Adding not found " << message_id << " to " << dialog_id << " from " << source;
if (d->is_empty) {
@@ -21608,52 +35522,35 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
*need_update_dialog_pos = true;
}
- if (dialog_id.get_type() == DialogType::Channel && !message->contains_unread_mention &&
- message->date <
- G()->unix_time_cached() - G()->shared_config().get_option_integer("channels_read_media_period",
- (G()->is_test_dc() ? 300 : 7 * 86400))) {
- update_opened_message_content(message.get());
- }
-
- if (message->contains_unread_mention && message_id.get() <= d->last_read_all_mentions_message_id.get()) {
- message->contains_unread_mention = false;
- }
-
- if (message_id.is_yet_unsent() && message->reply_to_message_id.is_valid() &&
- !message->reply_to_message_id.is_yet_unsent()) {
- replied_by_yet_unsent_messages_[FullMessageId{dialog_id, message->reply_to_message_id}]++;
+ if (dialog_type == DialogType::Channel && !message->contains_unread_mention) {
+ auto channel_read_media_period =
+ td_->option_manager_->get_option_integer("channels_read_media_period", (G()->is_test_dc() ? 300 : 7 * 86400));
+ if (message->date < G()->unix_time_cached() - channel_read_media_period) {
+ update_opened_message_content(message->content.get());
+ }
}
- if (G()->parameters().use_file_db && message_id.is_yet_unsent() && !message->via_bot_user_id.is_valid()) {
- auto queue_id = get_sequence_dispatcher_id(dialog_id, message_content_id);
+ if (G()->parameters().use_file_db && message_id.is_yet_unsent() && !message->via_bot_user_id.is_valid() &&
+ !message->hide_via_bot && !message->is_copy) {
+ auto queue_id = ChainId(dialog_id, message_content_type).get();
if (queue_id & 1) {
LOG(INFO) << "Add " << message_id << " from " << source << " to queue " << queue_id;
- yet_unsent_media_queues_[queue_id][message_id.get()]; // reserve place for promise
- if (!td_->auth_manager_->is_bot() && !is_broadcast_channel(dialog_id)) {
+ yet_unsent_media_queues_[queue_id][message_id]; // reserve place for promise
+ if (!td_->auth_manager_->is_bot()) {
pending_send_dialog_action_timeout_.add_timeout_in(dialog_id.get(), 1.0);
}
}
}
- if (from_update && message_id.get() > d->last_new_message_id.get()) {
- CHECK(!message_id.is_yet_unsent());
- if (d->dialog_id.get_type() == DialogType::SecretChat || message_id.is_server()) {
- // can delete messages, therefore must be called before message attaching/adding
- set_dialog_last_new_message_id(d, message_id, "add_message_to_dialog");
- }
- }
if (!(d->have_full_history && auto_attach) && d->last_message_id.is_valid() &&
- d->last_message_id.get() < MessageId(ServerMessageId(1)).get() &&
- message_id.get() >= MessageId(ServerMessageId(1)).get()) {
+ d->last_message_id < MessageId(ServerMessageId(1)) && message_id >= MessageId(ServerMessageId(1))) {
set_dialog_last_message_id(d, MessageId(), "add_message_to_dialog");
set_dialog_first_database_message_id(d, MessageId(), "add_message_to_dialog");
- set_dialog_last_database_message_id(d, MessageId(), "add_message_to_dialog");
+ set_dialog_last_database_message_id(d, MessageId(), source);
d->have_full_history = false;
- for (auto &first_message_id : d->first_database_message_id_by_index) {
- first_message_id = MessageId();
- }
- std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), -1);
+ d->have_full_history_source = 0;
+ invalidate_message_indexes(d);
d->local_unread_count = 0; // read all local messages. They will not be reachable anymore
on_dialog_updated(dialog_id, "add gap to dialog");
@@ -21662,21 +35559,26 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
*need_update_dialog_pos = false;
}
+ if (from_update && message_id > d->last_new_message_id && !message_id.is_yet_unsent()) {
+ if (dialog_type == DialogType::SecretChat || message_id.is_server()) {
+ // can delete messages, therefore must be called before message attaching/adding
+ set_dialog_last_new_message_id(d, message_id, "add_message_to_dialog");
+ }
+ }
+
bool is_attached = false;
if (auto_attach) {
- LOG(INFO) << "Trying to auto attach " << message_id;
auto it = MessagesIterator(d, message_id);
Message *previous_message = *it;
if (previous_message != nullptr) {
auto previous_message_id = previous_message->message_id;
- CHECK(previous_message_id.get() < message_id.get());
- if (previous_message->have_next ||
- (d->last_message_id.is_valid() && previous_message_id.get() >= d->last_message_id.get())) {
+ CHECK(previous_message_id < message_id);
+ if (previous_message->have_next || (d->last_message_id.is_valid() && previous_message_id >= d->last_message_id)) {
if (message_id.is_server() && previous_message_id.is_server() && previous_message->have_next) {
++it;
auto next_message = *it;
if (next_message != nullptr) {
- if (next_message->message_id.is_server()) {
+ if (next_message->message_id.is_server() && !has_qts_messages(dialog_id)) {
LOG(ERROR) << "Attach " << message_id << " from " << source << " before " << next_message->message_id
<< " and after " << previous_message_id << " in " << dialog_id;
dump_debug_message_op(d);
@@ -21688,7 +35590,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
}
}
- LOG(INFO) << "Attach " << message_id << " to the previous " << previous_message_id;
+ LOG(INFO) << "Attach " << message_id << " to the previous " << previous_message_id << " in " << dialog_id;
message->have_previous = true;
message->have_next = previous_message->have_next;
previous_message->have_next = true;
@@ -21700,7 +35602,7 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
Message *cur = d->messages.get();
Message *next_message = nullptr;
while (cur != nullptr) {
- if (cur->message_id.get() < message_id.get()) {
+ if (cur->message_id < message_id) {
cur = cur->right.get();
} else {
next_message = cur;
@@ -21709,9 +35611,11 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
}
if (next_message != nullptr) {
CHECK(!next_message->have_previous);
- LOG(INFO) << "Attach " << message_id << " to the next " << next_message->message_id;
- LOG_IF(ERROR, from_update) << "Attach " << message_id << " from " << source << " to the next "
- << next_message->message_id << " in " << dialog_id;
+ LOG(INFO) << "Attach " << message_id << " to the next " << next_message->message_id << " in " << dialog_id;
+ if (from_update && !next_message->message_id.is_yet_unsent() && !has_qts_messages(dialog_id)) {
+ LOG(ERROR) << "Attach " << message_id << " from " << source << " to the next " << next_message->message_id
+ << " in " << dialog_id;
+ }
message->have_next = true;
message->have_previous = next_message->have_previous;
next_message->have_previous = true;
@@ -21719,16 +35623,41 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
}
}
if (!is_attached) {
- LOG(INFO) << "Can't attach " << message_id;
+ LOG(INFO) << "Can't auto-attach " << message_id << " in " << dialog_id;
message->have_previous = false;
message->have_next = false;
}
}
- UserId my_user_id(td_->contacts_manager_->get_my_id("add_message_to_dialog"));
- DialogId my_dialog_id(my_user_id);
- if (*need_update && message_id.get() > d->last_read_inbox_message_id.get() && !td_->auth_manager_->is_bot()) {
- if (!message->is_outgoing && dialog_id != my_dialog_id) {
+ if (!td_->auth_manager_->is_bot()) {
+ if (*need_update) {
+ // notification must be added before updating unread_count to have correct total notification count
+ // in get_message_notification_group_force
+ add_new_message_notification(d, message.get(), false);
+ } else {
+ if (message->from_database && message->notification_id.is_valid() &&
+ is_from_mention_notification_group(message.get()) && is_message_notification_active(d, message.get()) &&
+ is_dialog_mention_notifications_disabled(d) && message_id != d->pinned_message_notification_message_id) {
+ auto notification_id = message->notification_id;
+ VLOG(notifications) << "Remove mention " << notification_id << " in " << message_id << " in " << dialog_id;
+ message->notification_id = NotificationId();
+ if (d->mention_notification_group.last_notification_id == notification_id) {
+ // last notification is deleted, need to find new last notification
+ fix_dialog_last_notification_id(d, true, message_id);
+ }
+
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification,
+ d->mention_notification_group.group_id, notification_id, false, false, Promise<Unit>(),
+ "remove disabled mention notification");
+
+ on_message_changed(d, message.get(), false, "remove_mention_notification");
+ }
+ }
+ }
+
+ const Message *m = message.get();
+ if (*need_update && message_id > d->last_read_inbox_message_id && !td_->auth_manager_->is_bot()) {
+ if (has_incoming_notification(dialog_id, m)) {
int32 server_unread_count = d->server_unread_count;
int32 local_unread_count = d->local_unread_count;
if (message_id.is_server()) {
@@ -21739,46 +35668,80 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
set_dialog_last_read_inbox_message_id(d, MessageId::min(), server_unread_count, local_unread_count, false,
source);
} else {
- // if outgoing message has id one greater than last_read_inbox_message_id than definitely there is no
- // unread incoming message before it
+ // if non-scheduled outgoing message has identifier one greater than last_read_inbox_message_id,
+ // then definitely there are no unread incoming messages before it
if (message_id.is_server() && d->last_read_inbox_message_id.is_valid() &&
d->last_read_inbox_message_id.is_server() &&
- message_id.get_server_message_id().get() == d->last_read_inbox_message_id.get_server_message_id().get() + 1) {
- read_history_inbox(d->dialog_id, message_id, 0, "add_message_to_dialog");
+ message_id == d->last_read_inbox_message_id.get_next_message_id(MessageType::Server)) {
+ read_history_inbox(dialog_id, message_id, 0, "add_message_to_dialog");
}
}
}
- if (*need_update && message->contains_unread_mention) {
- d->unread_mention_count++;
- d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] =
- d->unread_mention_count;
+ if (*need_update && m->contains_unread_mention) {
+ set_dialog_unread_mention_count(d, d->unread_mention_count + 1);
send_update_chat_unread_mention_count(d);
}
+ if (*need_update && has_unread_message_reactions(dialog_id, m)) {
+ set_dialog_unread_reaction_count(d, d->unread_reaction_count + 1);
+ send_update_chat_unread_reaction_count(d, "add_message_to_dialog");
+ }
if (*need_update) {
- update_message_count_by_index(d, +1, message.get());
+ update_message_count_by_index(d, +1, m);
}
- if (auto_attach && message_id.get() > d->last_message_id.get() && message_id.get() >= d->last_new_message_id.get()) {
- set_dialog_last_message_id(d, message_id, "add_message_to_dialog");
+ if (auto_attach && message_id > d->last_message_id && message_id >= d->last_new_message_id) {
+ set_dialog_last_message_id(d, message_id, "add_message_to_dialog", m);
*need_update_dialog_pos = true;
}
- if (auto_attach && !message_id.is_yet_unsent() && message_id.get() >= d->last_new_message_id.get() &&
- (d->last_new_message_id.is_valid() || (message_id.is_local() && message_id.get() >= d->last_message_id.get()))) {
- CHECK(message_id.get() <= d->last_message_id.get());
- if (message_id.get() > d->last_database_message_id.get()) {
+ if (auto_attach && !message_id.is_yet_unsent() && message_id >= d->last_new_message_id &&
+ (d->last_new_message_id.is_valid() ||
+ (message_id.is_local() && d->last_message_id.is_valid() &&
+ (message_id >= d->last_message_id ||
+ (d->last_database_message_id.is_valid() && message_id > d->last_database_message_id))))) {
+ CHECK(message_id <= d->last_message_id);
+ if (message_id > d->last_database_message_id) {
set_dialog_last_database_message_id(d, message_id, "add_message_to_dialog");
if (!d->first_database_message_id.is_valid()) {
set_dialog_first_database_message_id(d, message_id, "add_message_to_dialog");
- try_restore_dialog_reply_markup(d, message.get());
+ try_restore_dialog_reply_markup(d, m);
+ }
+ }
+ }
+
+ if (m->message_id.is_yet_unsent() && m->reply_to_message_id != MessageId()) {
+ if (!m->reply_to_message_id.is_yet_unsent()) {
+ if (!m->reply_to_message_id.is_scheduled()) {
+ replied_by_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}]++;
}
- on_dialog_updated(dialog_id, "update_last_database_message_id");
+ } else {
+ replied_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}].insert(m->message_id);
}
}
- if (!message->from_database && !message_id.is_yet_unsent()) {
- add_message_to_database(d, message.get(), "add_message_to_dialog");
+
+ if (!m->from_database && !m->message_id.is_yet_unsent()) {
+ add_message_to_database(d, m, "add_message_to_dialog");
}
- if (!is_attached && !message->have_next && !message->have_previous) {
- MessagesIterator it(d, message_id);
+ if (from_update && dialog_type == DialogType::Channel) {
+ auto now = max(G()->unix_time_cached(), m->date);
+ if (m->date < now - 2 * 86400 && Slice(source) == Slice("updateNewChannelMessage")) {
+ // if the message is pretty old, we might have missed the update that the message has already been read
+ repair_channel_server_unread_count(d);
+ }
+ if (m->date + 3600 >= now && m->is_outgoing) {
+ auto channel_id = dialog_id.get_channel_id();
+ auto slow_mode_delay = td_->contacts_manager_->get_channel_slow_mode_delay(channel_id);
+ auto status = td_->contacts_manager_->get_channel_status(dialog_id.get_channel_id());
+ if (m->date + slow_mode_delay > now && !status.is_administrator()) {
+ td_->contacts_manager_->on_update_channel_slow_mode_next_send_date(channel_id, m->date + slow_mode_delay);
+ }
+ }
+ if (m->date > now - 14 * 86400) {
+ td_->contacts_manager_->remove_inactive_channel(dialog_id.get_channel_id());
+ }
+ }
+
+ if (!is_attached && !m->have_next && !m->have_previous) {
+ MessagesIterator it(d, m->message_id);
if (*it != nullptr && (*it)->have_next) {
// need to drop a connection between messages
auto previous_message = *it;
@@ -21786,8 +35749,9 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
auto next_message = *it;
if (next_message != nullptr) {
if (next_message->message_id.is_server() &&
- !(td_->auth_manager_->is_bot() && Slice(source) == Slice("get channel messages"))) {
- LOG(ERROR) << "Can't attach " << message_id << " from " << source << " before " << next_message->message_id
+ !(td_->auth_manager_->is_bot() && Slice(source) == Slice("GetRepliedChannelMessageQuery"))) {
+ LOG(ERROR) << "Can't attach " << m->message_id << " of type " << m->content->get_type() << " from " << source
+ << " from " << (m->from_database ? "database" : "server") << " before " << next_message->message_id
<< " and after " << previous_message->message_id << " in " << dialog_id;
dump_debug_message_op(d);
}
@@ -21799,178 +35763,331 @@ MessagesManager::Message *MessagesManager::add_message_to_dialog(Dialog *d, uniq
<< " from " << source << " in " << dialog_id;
dump_debug_message_op(d);
}
- }
- }
+ } else if (m->message_id.is_server() && d->last_message_id.is_valid() && m->message_id > d->last_message_id) {
+ LOG(INFO) << "Receive " << m->message_id << ", which is newer than the last " << d->last_message_id
+ << " not from update";
+ set_dialog_last_message_id(d, MessageId(), source);
+ if (m->message_id > d->deleted_last_message_id) {
+ d->delete_last_message_date = m->date;
+ d->deleted_last_message_id = message_id;
+ }
- if (!td_->auth_manager_->is_bot() && from_update && d->reply_markup_message_id != MessageId() &&
- message_content_id == MessageChatDeleteUser::ID) {
- auto deleted_user_id = static_cast<const MessageChatDeleteUser *>(message->content.get())->user_id;
- if (td_->contacts_manager_->is_user_bot(deleted_user_id)) {
- const Message *old_message = get_message_force(d, d->reply_markup_message_id);
- if (old_message == nullptr || old_message->sender_user_id == deleted_user_id) {
- set_dialog_reply_markup(d, MessageId());
+ set_dialog_first_database_message_id(d, MessageId(), source);
+ set_dialog_last_database_message_id(d, MessageId(), source);
+ d->have_full_history = false;
+ d->have_full_history_source = 0;
+ invalidate_message_indexes(d);
+
+ on_dialog_updated(dialog_id, source);
+
+ send_update_chat_last_message(d, source);
+ *need_update_dialog_pos = false;
+
+ on_dialog_updated(d->dialog_id, "do delete last message");
+
+ if (!td_->auth_manager_->is_bot()) {
+ send_closure_later(actor_id(this), &MessagesManager::get_history_from_the_end, d->dialog_id, false, false,
+ Promise<Unit>());
}
}
}
- if (message_content_id == MessageContactRegistered::ID && !d->has_contact_registered_message) {
+ if (message_content_type == MessageContentType::ContactRegistered && !d->has_contact_registered_message) {
d->has_contact_registered_message = true;
on_dialog_updated(dialog_id, "update_has_contact_registered_message");
}
- if (from_update && dialog_id.get_type() == DialogType::Channel) {
- int32 new_participant_count = 0;
- switch (message_content_id) {
- case MessageChatAddUsers::ID:
- new_participant_count =
- narrow_cast<int32>(static_cast<const MessageChatAddUsers *>(message->content.get())->user_ids.size());
- break;
- case MessageChatJoinedByLink::ID:
- new_participant_count = 1;
- break;
- case MessageChatDeleteUser::ID:
- new_participant_count = -1;
- break;
- case MessagePinMessage::ID:
- td_->contacts_manager_->on_update_channel_pinned_message(
- dialog_id.get_channel_id(), static_cast<const MessagePinMessage *>(message->content.get())->message_id);
- break;
- }
- if (new_participant_count != 0) {
- td_->contacts_manager_->speculative_add_channel_participants(dialog_id.get_channel_id(), new_participant_count,
- message->sender_user_id == my_user_id);
+ reget_message_from_server_if_needed(dialog_id, m);
+
+ add_message_file_sources(dialog_id, m);
+
+ register_message_content(td_, m->content.get(), {dialog_id, m->message_id}, "add_message_to_dialog");
+
+ register_message_reply(dialog_id, m);
+
+ if (*need_update && m->message_id.is_server() && message_content_type == MessageContentType::PinMessage) {
+ auto pinned_message_id = get_message_content_pinned_message_id(m->content.get());
+ if (d->is_last_pinned_message_id_inited && pinned_message_id > d->last_pinned_message_id) {
+ set_dialog_last_pinned_message_id(d, pinned_message_id);
}
}
- if (!td_->auth_manager_->is_bot() && (from_update || message_id.is_yet_unsent()) &&
- message->forward_info == nullptr && (message->is_outgoing || dialog_id == my_dialog_id)) {
- switch (message_content_id) {
- case MessageAnimation::ID:
- if (dialog_id.get_type() != DialogType::SecretChat) {
- td_->animations_manager_->add_saved_animation_by_id(get_message_content_file_id(message->content.get()));
- }
- break;
- case MessageSticker::ID:
- if (dialog_id.get_type() != DialogType::SecretChat) {
- td_->stickers_manager_->add_recent_sticker_by_id(false, get_message_content_file_id(message->content.get()));
- }
- break;
- case MessageText::ID:
- update_used_hashtags(dialog_id, message.get());
- break;
- }
+ if (*need_update && m->message_id.is_server() && message_content_type == MessageContentType::ChatSetTheme) {
+ set_dialog_theme_name(d, get_message_content_theme_name(m->content.get()));
}
- if (!td_->auth_manager_->is_bot() && from_update && (message->is_outgoing || dialog_id == my_dialog_id) &&
- dialog_id.get_type() != DialogType::SecretChat) {
- if (message->via_bot_user_id.is_valid() && message->forward_info == nullptr) {
- // forwarded game messages can't be distinguished from sent via bot game messages, so increase rating anyway
- send_closure(G()->top_dialog_manager(), &TopDialogManager::on_dialog_used, TopDialogCategory::BotInline,
- DialogId(message->via_bot_user_id), message->date);
- }
- TopDialogCategory category = TopDialogCategory::Size;
- switch (dialog_id.get_type()) {
- case DialogType::User: {
- if (td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) {
- category = TopDialogCategory::BotPM;
- } else {
- category = TopDialogCategory::Correspondent;
+ if (from_update) {
+ speculatively_update_active_group_call_id(d, m);
+ speculatively_update_channel_participants(dialog_id, m);
+ update_forum_topic_info_by_service_message_content(td_, m->content.get(), dialog_id, m->top_thread_message_id);
+ update_sent_message_contents(dialog_id, m);
+ update_used_hashtags(dialog_id, m);
+ update_top_dialogs(dialog_id, m);
+ cancel_dialog_action(dialog_id, m);
+ update_has_outgoing_messages(dialog_id, m);
+
+ if (!td_->auth_manager_->is_bot() && d->messages == nullptr && !m->is_outgoing && dialog_id != get_my_dialog_id()) {
+ switch (dialog_type) {
+ case DialogType::User:
+ td_->contacts_manager_->invalidate_user_full(dialog_id.get_user_id());
+ td_->contacts_manager_->reload_user_full(dialog_id.get_user_id(), Promise<Unit>());
+ break;
+ case DialogType::Chat:
+ case DialogType::Channel:
+ // nothing to do
+ break;
+ case DialogType::SecretChat: {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (user_id.is_valid()) {
+ td_->contacts_manager_->invalidate_user_full(user_id);
+ td_->contacts_manager_->reload_user_full(user_id, Promise<Unit>());
+ }
+ break;
}
- break;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
}
- case DialogType::Chat:
- category = TopDialogCategory::Group;
- break;
- case DialogType::Channel:
- switch (td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id())) {
- case ChannelType::Broadcast:
- category = TopDialogCategory::Channel;
- break;
- case ChannelType::Megagroup:
- category = TopDialogCategory::Group;
- break;
- case ChannelType::Unknown:
- break;
- default:
- UNREACHABLE();
- break;
- }
- break;
- case DialogType::SecretChat:
- case DialogType::None:
- default:
- UNREACHABLE();
- }
- if (category != TopDialogCategory::Size) {
- send_closure(G()->top_dialog_manager(), &TopDialogManager::on_dialog_used, category, dialog_id, message->date);
}
}
- // TODO function
- v = &d->messages;
- while (*v != nullptr && (*v)->random_y >= message->random_y) {
- if ((*v)->message_id.get() < message_id.get()) {
- v = &(*v)->right;
- } else if ((*v)->message_id == message_id) {
- UNREACHABLE();
- } else {
- v = &(*v)->left;
- }
- }
-
- unique_ptr<Message> *left = &message->left;
- unique_ptr<Message> *right = &message->right;
-
- unique_ptr<Message> cur = std::move(*v);
- while (cur != nullptr) {
- if (cur->message_id.get() < message_id.get()) {
- *left = std::move(cur);
- left = &((*left)->right);
- cur = std::move(*left);
- } else {
- *right = std::move(cur);
- right = &((*right)->left);
- cur = std::move(*right);
- }
- }
- CHECK(*left == nullptr);
- CHECK(*right == nullptr);
- *v = std::move(message);
-
+ Message *result_message = treap_insert_message(&d->messages, std::move(message));
+ CHECK(result_message != nullptr);
+ CHECK(result_message == m);
CHECK(d->messages != nullptr);
if (!is_attached) {
- if ((*v)->have_next) {
- CHECK(!(*v)->have_previous);
- attach_message_to_next(d, message_id);
- } else if ((*v)->have_previous) {
- attach_message_to_previous(d, message_id);
+ if (m->have_next) {
+ LOG_CHECK(!m->have_previous) << auto_attach << " " << dialog_id << " " << m->message_id << " " << from_update
+ << " " << *need_update << " " << d->being_updated_last_new_message_id << " "
+ << d->last_new_message_id << " " << d->being_updated_last_database_message_id << " "
+ << d->last_database_message_id << " " << debug_have_previous << " "
+ << debug_have_next << " " << source;
+ attach_message_to_next(d, m->message_id, source);
+ } else if (m->have_previous) {
+ attach_message_to_previous(d, m->message_id, source);
}
}
- switch (dialog_id.get_type()) {
+ if (m->message_id.is_yet_unsent() && !m->message_id.is_scheduled() && m->top_thread_message_id.is_valid()) {
+ auto is_inserted = d->yet_unsent_thread_message_ids[m->top_thread_message_id].insert(m->message_id).second;
+ CHECK(is_inserted);
+ }
+
+ switch (dialog_type) {
case DialogType::User:
case DialogType::Chat:
- message_id_to_dialog_id_[message_id] = dialog_id;
+ if (m->message_id.is_server()) {
+ message_id_to_dialog_id_.set(m->message_id, dialog_id);
+ }
break;
case DialogType::Channel:
// nothing to do
break;
case DialogType::SecretChat:
- LOG(INFO) << "Add correspondence random_id " << (*v)->random_id << " to " << message_id << " in " << dialog_id;
- d->random_id_to_message_id[(*v)->random_id] = message_id;
break;
case DialogType::None:
default:
UNREACHABLE();
}
- return v->get();
+ if (m->notification_id.is_valid()) {
+ add_notification_id_to_message_id_correspondence(d, m->notification_id, m->message_id);
+ }
+ if (m->message_id.is_yet_unsent() || dialog_type == DialogType::SecretChat) {
+ add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
+ }
+
+ try_add_bot_command_message_id(dialog_id, m);
+
+ // must be called after the message is added to correctly update replies
+ update_message_max_reply_media_timestamp(d, result_message, false);
+ update_message_max_own_media_timestamp(d, result_message);
+
+ result_message->debug_source = source;
+ d->being_added_message_id = MessageId();
+
+ if (!td_->auth_manager_->is_bot() && from_update && d->reply_markup_message_id != MessageId()) {
+ auto deleted_user_id = get_message_content_deleted_user_id(m->content.get());
+ if (deleted_user_id.is_valid()) { // do not check for is_user_bot to allow deleted bots
+ const Message *old_message = get_message_force(d, d->reply_markup_message_id, "add_message_to_dialog 3");
+ if (old_message == nullptr || old_message->sender_user_id == deleted_user_id) {
+ LOG(INFO) << "Remove reply markup in " << dialog_id << ", because bot " << deleted_user_id
+ << " isn't a member of the chat";
+ set_dialog_reply_markup(d, MessageId());
+ }
+ }
+ }
+
+ added_message_count_++;
+
+ return result_message;
+}
+
+MessagesManager::Message *MessagesManager::add_scheduled_message_to_dialog(Dialog *d, unique_ptr<Message> message,
+ bool from_update, bool *need_update,
+ const char *source) {
+ CHECK(message != nullptr);
+ CHECK(d != nullptr);
+ CHECK(need_update != nullptr);
+ CHECK(source != nullptr);
+
+ DialogId dialog_id = d->dialog_id;
+ MessageId message_id = message->message_id;
+ CHECK(message_id.is_valid_scheduled());
+ CHECK(!message->notification_id.is_valid());
+ CHECK(!message->removed_notification_id.is_valid());
+
+ if (!message_id.is_yet_unsent()) {
+ message->top_thread_message_id = MessageId();
+ }
+
+ if (is_deleted_message(d, message_id)) {
+ LOG(INFO) << "Skip adding deleted " << message_id << " to " << dialog_id << " from " << source;
+ debug_add_message_to_dialog_fail_reason_ = "adding deleted scheduled message";
+ return nullptr;
+ }
+
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ LOG(ERROR) << "Tried to add " << message_id << " to " << dialog_id << " from " << source;
+ debug_add_message_to_dialog_fail_reason_ = "skip adding scheduled message to secret chat";
+ return nullptr;
+ }
+ if (message->ttl != 0 || message->ttl_expires_at != 0) {
+ LOG(ERROR) << "Tried to add " << message_id << " with TTL " << message->ttl << "/" << message->ttl_expires_at
+ << " to " << dialog_id << " from " << source;
+ debug_add_message_to_dialog_fail_reason_ = "skip adding secret scheduled message";
+ return nullptr;
+ }
+ if (message->ttl_period != 0) {
+ LOG(ERROR) << "Tried to add " << message_id << " with TTL period " << message->ttl_period << " to " << dialog_id
+ << " from " << source;
+ debug_add_message_to_dialog_fail_reason_ = "skip adding auto-deleting scheduled message";
+ return nullptr;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Bot tried to add " << message_id << " to " << dialog_id << " from " << source;
+ debug_add_message_to_dialog_fail_reason_ = "skip adding scheduled message by bot";
+ return nullptr;
+ }
+
+ auto message_content_type = message->content->get_type();
+ if (is_service_message_content(message_content_type) || message_content_type == MessageContentType::LiveLocation ||
+ message_content_type == MessageContentType::ExpiredPhoto ||
+ message_content_type == MessageContentType::ExpiredVideo) {
+ LOG(ERROR) << "Tried to add " << message_id << " of type " << message_content_type << " to " << dialog_id
+ << " from " << source;
+ debug_add_message_to_dialog_fail_reason_ = "skip adding message of unexpected type";
+ return nullptr;
+ }
+
+ {
+ Message *m = message->from_database ? get_message(d, message_id)
+ : get_message_force(d, message_id, "add_scheduled_message_to_dialog");
+ if (m != nullptr) {
+ auto old_message_id = m->message_id;
+ LOG(INFO) << "Adding already existing " << old_message_id << " in " << dialog_id << " from " << source;
+ set_message_id(message, old_message_id);
+ if (!message->from_database) {
+ auto old_file_ids = get_message_content_file_ids(m->content.get(), td_);
+ bool need_update_dialog_pos = false;
+ update_message(d, m, std::move(message), &need_update_dialog_pos, true);
+ CHECK(need_update_dialog_pos == false);
+ change_message_files(dialog_id, m, old_file_ids);
+ }
+ if (old_message_id != message_id) {
+ being_readded_message_id_ = {dialog_id, old_message_id};
+ message = do_delete_scheduled_message(d, old_message_id, false, "add_scheduled_message_to_dialog");
+ CHECK(message != nullptr);
+ send_update_delete_messages(dialog_id, {message->message_id.get()}, false);
+ set_message_id(message, message_id);
+ message->from_database = false;
+ } else {
+ *need_update = false;
+ return m;
+ }
+ }
+ }
+
+ LOG(INFO) << "Adding not found " << message_id << " to " << dialog_id << " from " << source;
+
+ const Message *m = message.get();
+ if (m->message_id.is_yet_unsent() && m->reply_to_message_id != MessageId()) {
+ if (!m->reply_to_message_id.is_yet_unsent()) {
+ if (!m->reply_to_message_id.is_scheduled()) {
+ replied_by_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}]++;
+ }
+ } else {
+ replied_yet_unsent_messages_[FullMessageId{dialog_id, m->reply_to_message_id}].insert(m->message_id);
+ }
+ }
+
+ if (!m->from_database && !m->message_id.is_yet_unsent()) {
+ add_message_to_database(d, m, "add_scheduled_message_to_dialog");
+ }
+
+ reget_message_from_server_if_needed(dialog_id, m);
+
+ add_message_file_sources(dialog_id, m);
+
+ register_message_content(td_, m->content.get(), {dialog_id, m->message_id}, "add_scheduled_message_to_dialog");
+
+ if (m->message_id.is_yet_unsent()) {
+ add_random_id_to_message_id_correspondence(d, m->random_id, m->message_id);
+ }
+
+ // must be called after register_message_content, which loads web page
+ update_message_max_reply_media_timestamp(d, message.get(), false);
+ update_message_max_own_media_timestamp(d, message.get());
+
+ register_message_reply(dialog_id, m);
+
+ if (from_update) {
+ update_sent_message_contents(dialog_id, m);
+ update_used_hashtags(dialog_id, m);
+ update_has_outgoing_messages(dialog_id, m);
+ }
+
+ if (m->message_id.is_scheduled_server()) {
+ int32 &date = d->scheduled_message_date[m->message_id.get_scheduled_server_message_id()];
+ CHECK(date == 0);
+ date = m->date;
+ }
+
+ Message *result_message = treap_insert_message(&d->scheduled_messages, std::move(message));
+ CHECK(result_message != nullptr);
+ CHECK(d->scheduled_messages != nullptr);
+ being_readded_message_id_ = FullMessageId();
+ return result_message;
}
-void MessagesManager::on_message_changed(const Dialog *d, const Message *m, const char *source) {
+void MessagesManager::register_new_local_message_id(Dialog *d, const Message *m) {
+ if (m == nullptr) {
+ return;
+ }
+ if (m->message_id.is_scheduled()) {
+ return;
+ }
+ CHECK(m->message_id.is_local());
+ if (m->top_thread_message_id.is_valid() && m->top_thread_message_id != m->message_id) {
+ Message *top_m = get_message_force(d, m->top_thread_message_id, "register_new_local_message_id");
+ if (top_m != nullptr && top_m->top_thread_message_id == top_m->message_id) {
+ auto it = std::lower_bound(top_m->local_thread_message_ids.begin(), top_m->local_thread_message_ids.end(),
+ m->message_id);
+ if (it == top_m->local_thread_message_ids.end() || *it != m->message_id) {
+ top_m->local_thread_message_ids.insert(it, m->message_id);
+ if (top_m->local_thread_message_ids.size() >= 1000) {
+ top_m->local_thread_message_ids.erase(top_m->local_thread_message_ids.begin());
+ }
+ on_message_changed(d, top_m, false, "register_new_local_message_id");
+ }
+ }
+ }
+}
+
+void MessagesManager::on_message_changed(const Dialog *d, const Message *m, bool need_send_update, const char *source) {
CHECK(d != nullptr);
CHECK(m != nullptr);
- if (m->message_id == d->last_message_id) {
+ if (need_send_update && m->message_id == d->last_message_id) {
send_update_chat_last_message_impl(d, source);
}
@@ -21983,6 +36100,32 @@ void MessagesManager::on_message_changed(const Dialog *d, const Message *m, cons
}
}
+void MessagesManager::on_message_notification_changed(Dialog *d, const Message *m, const char *source) {
+ CHECK(d != nullptr);
+ CHECK(m != nullptr);
+ if (m->notification_id.is_valid() && is_message_notification_active(d, m)) {
+ auto &group_info = get_notification_group_info(d, m);
+ if (group_info.group_id.is_valid()) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::edit_notification, group_info.group_id,
+ m->notification_id,
+ create_new_message_notification(
+ m->message_id, is_message_preview_enabled(d, m, is_from_mention_notification_group(m))));
+ }
+ }
+ if (m->is_pinned && d->pinned_message_notification_message_id.is_valid() &&
+ d->mention_notification_group.group_id.is_valid()) {
+ auto pinned_message = get_message_force(d, d->pinned_message_notification_message_id, "after update_message");
+ if (pinned_message != nullptr && pinned_message->notification_id.is_valid() &&
+ is_message_notification_active(d, pinned_message) &&
+ get_message_content_pinned_message_id(pinned_message->content.get()) == m->message_id) {
+ send_closure_later(
+ G()->notification_manager(), &NotificationManager::edit_notification, d->mention_notification_group.group_id,
+ pinned_message->notification_id,
+ create_new_message_notification(pinned_message->message_id, is_message_preview_enabled(d, m, true)));
+ }
+ }
+}
+
void MessagesManager::add_message_to_database(const Dialog *d, const Message *m, const char *source) {
if (!G()->parameters().use_message_db) {
return;
@@ -21991,7 +36134,16 @@ void MessagesManager::add_message_to_database(const Dialog *d, const Message *m,
CHECK(d != nullptr);
CHECK(m != nullptr);
MessageId message_id = m->message_id;
- CHECK(message_id.is_server() || message_id.is_local()) << source;
+
+ if (message_id.is_scheduled()) {
+ LOG(INFO) << "Add " << FullMessageId(d->dialog_id, message_id) << " to database from " << source;
+
+ set_dialog_has_scheduled_database_messages(d->dialog_id, true);
+ G()->td_db()->get_message_db_async()->add_scheduled_message({d->dialog_id, message_id}, log_event_store(*m),
+ Auto()); // TODO Promise
+ return;
+ }
+ LOG_CHECK(message_id.is_server() || message_id.is_local()) << source;
LOG(INFO) << "Add " << FullMessageId(d->dialog_id, message_id) << " to database from " << source;
@@ -22005,17 +36157,17 @@ void MessagesManager::add_message_to_database(const Dialog *d, const Message *m,
if (message_id.is_server()) {
unique_message_id = message_id.get_server_message_id();
}
- //FOR DEBUG
- //text = get_search_text(m);
- //if (!text.empty()) {
- //search_id = (static_cast<int64>(m->date) << 32) | static_cast<uint32>(Random::secure_int32());
- //}
+ // FOR DEBUG
+ // text = get_message_search_text(m);
+ // if (!text.empty()) {
+ // search_id = (static_cast<int64>(m->date) << 32) | static_cast<uint32>(Random::secure_int32());
+ // }
break;
case DialogType::Channel:
break;
case DialogType::SecretChat:
random_id = m->random_id;
- text = get_search_text(m);
+ text = get_message_search_text(m);
if (!text.empty()) {
search_id = (static_cast<int64>(m->date) << 32) | static_cast<uint32>(m->random_id);
}
@@ -22027,32 +36179,46 @@ void MessagesManager::add_message_to_database(const Dialog *d, const Message *m,
int32 ttl_expires_at = 0;
if (m->ttl_expires_at != 0) {
- ttl_expires_at = static_cast<int32>(m->ttl_expires_at - Time::now() + G()->server_time());
+ ttl_expires_at = static_cast<int32>(m->ttl_expires_at - Time::now() + G()->server_time()) + 1;
+ }
+ if (m->ttl_period != 0 && (ttl_expires_at == 0 || m->date + m->ttl_period < ttl_expires_at)) {
+ ttl_expires_at = m->date + m->ttl_period;
}
- G()->td_db()->get_messages_db_async()->add_message({d->dialog_id, message_id}, unique_message_id, m->sender_user_id,
- random_id, ttl_expires_at, get_message_index_mask(d->dialog_id, m),
- search_id, text, log_event_store(*m), Auto()); // TODO Promise
+ G()->td_db()->get_message_db_async()->add_message({d->dialog_id, message_id}, unique_message_id,
+ get_message_sender(m), random_id, ttl_expires_at,
+ get_message_index_mask(d->dialog_id, m), search_id, text,
+ m->notification_id, m->top_thread_message_id, log_event_store(*m),
+ Auto()); // TODO Promise
}
-void MessagesManager::delete_all_dialog_messages_from_database(DialogId dialog_id, MessageId message_id,
+void MessagesManager::delete_all_dialog_messages_from_database(Dialog *d, MessageId max_message_id,
const char *source) {
- if (!G()->parameters().use_message_db) {
- return;
+ CHECK(d != nullptr);
+ CHECK(max_message_id.is_valid());
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ remove_new_secret_chat_notification(d, true);
}
+ if (d->pinned_message_notification_message_id.is_valid() &&
+ d->pinned_message_notification_message_id <= max_message_id) {
+ remove_dialog_pinned_message_notification(d, source);
+ }
+ remove_message_dialog_notifications(d, max_message_id, false, source);
+ remove_message_dialog_notifications(d, max_message_id, true, source);
- CHECK(dialog_id.is_valid());
- if (!message_id.is_valid()) {
+ if (!G()->parameters().use_message_db) {
return;
}
- LOG(INFO) << "Delete all messages in " << dialog_id << " from database up to " << message_id << " from " << source;
+ auto dialog_id = d->dialog_id;
+ LOG(INFO) << "Delete all messages in " << dialog_id << " from database up to " << max_message_id << " from "
+ << source;
/*
- if (dialog_id.get_type() == DialogType::User && message_id.is_server()) {
+ if (dialog_id.get_type() == DialogType::User && max_message_id.is_server()) {
bool need_save = false;
int i = 0;
for (auto &first_message_id : calls_db_state_.first_calls_database_message_id_by_index) {
- if (first_message_id.get() <= message_id.get()) {
- first_message_id = message_id.get_next_server_message_id();
+ if (first_message_id <= max_message_id) {
+ first_message_id = max_message_id.get_next_server_message_id();
calls_db_state_.message_count_by_index[i] = -1;
need_save = true;
}
@@ -22062,8 +36228,9 @@ void MessagesManager::delete_all_dialog_messages_from_database(DialogId dialog_i
save_calls_db_state();
}
}
-*/
- G()->td_db()->get_messages_db_async()->delete_all_dialog_messages(dialog_id, message_id, Auto()); // TODO Promise
+ */
+ G()->td_db()->get_message_db_async()->delete_all_dialog_messages(dialog_id, max_message_id,
+ Auto()); // TODO Promise
}
class MessagesManager::DeleteMessageLogEvent {
@@ -22099,236 +36266,590 @@ class MessagesManager::DeleteMessageLogEvent {
}
};
-void MessagesManager::delete_message_files(const Message *m) const {
+void MessagesManager::delete_message_files(DialogId dialog_id, const Message *m) const {
for (auto file_id : get_message_file_ids(m)) {
- send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise<>(),
- "delete_message_files"); // TODO add log event
+ if (need_delete_file({dialog_id, m->message_id}, file_id)) {
+ send_closure(G()->file_manager(), &FileManager::delete_file, file_id, Promise<Unit>(), "delete_message_files");
+ }
}
}
+bool MessagesManager::need_delete_file(FullMessageId full_message_id, FileId file_id) const {
+ if (being_readded_message_id_ == full_message_id || td_->auth_manager_->is_bot()) {
+ return false;
+ }
+
+ auto main_file_id = td_->file_manager_->get_file_view(file_id).get_main_file_id();
+ auto full_message_ids = td_->file_reference_manager_->get_some_message_file_sources(main_file_id);
+ LOG(INFO) << "Receive " << full_message_ids << " as sources for file " << main_file_id << "/" << file_id << " from "
+ << full_message_id;
+ for (const auto &other_full_messsage_id : full_message_ids) {
+ if (other_full_messsage_id != full_message_id) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool MessagesManager::need_delete_message_files(DialogId dialog_id, const Message *m) const {
+ if (m == nullptr || td_->auth_manager_->is_bot()) {
+ return false;
+ }
+
+ auto dialog_type = dialog_id.get_type();
+ if (!m->message_id.is_scheduled() && !m->message_id.is_server() && dialog_type != DialogType::SecretChat) {
+ return false;
+ }
+ if (being_readded_message_id_ == FullMessageId{dialog_id, m->message_id}) {
+ return false;
+ }
+
+ if (m->forward_info != nullptr && m->forward_info->from_dialog_id.is_valid() &&
+ m->forward_info->from_message_id.is_valid()) {
+ // this function must not try to load the message, because it can be called from
+ // do_delete_message or add_scheduled_message_to_dialog
+ const Message *old_m = get_message({m->forward_info->from_dialog_id, m->forward_info->from_message_id});
+ if (old_m != nullptr && get_message_file_ids(old_m) == get_message_file_ids(m)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
void MessagesManager::delete_message_from_database(Dialog *d, MessageId message_id, const Message *m,
- bool is_permanently_deleted) const {
- if (is_permanently_deleted) {
- d->deleted_message_ids.insert(message_id);
+ bool is_permanently_deleted) {
+ CHECK(d != nullptr);
+ if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
+ return;
}
if (message_id.is_yet_unsent()) {
return;
}
- auto is_secret = d->dialog_id.get_type() == DialogType::SecretChat;
- if (m != nullptr && m->ttl > 0) {
- delete_message_files(m);
+ if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_local() &&
+ m->top_thread_message_id.is_valid() && m->top_thread_message_id != m->message_id) {
+ // must not load the message from the database
+ Message *top_m = get_message(d, m->top_thread_message_id);
+ if (top_m != nullptr && top_m->top_thread_message_id == top_m->message_id) {
+ auto it = std::lower_bound(top_m->local_thread_message_ids.begin(), top_m->local_thread_message_ids.end(),
+ m->message_id);
+ if (it != top_m->local_thread_message_ids.end() && *it == m->message_id) {
+ top_m->local_thread_message_ids.erase(it);
+ on_message_changed(d, top_m, false, "delete_message_from_database");
+ }
+ }
+ }
+
+ if (is_permanently_deleted) {
+ if (message_id.is_scheduled() && message_id.is_valid_scheduled() && message_id.is_scheduled_server()) {
+ d->deleted_scheduled_server_message_ids.insert(message_id.get_scheduled_server_message_id());
+ } else {
+ // don't store failed to send message identifiers for bots to reduce memory usage
+ if (m == nullptr || !td_->auth_manager_->is_bot() || !m->is_failed_to_send) {
+ d->deleted_message_ids.insert(message_id);
+ send_closure_later(actor_id(this),
+ &MessagesManager::update_message_max_reply_media_timestamp_in_replied_messages, d->dialog_id,
+ message_id);
+ }
+ }
+
+ if (message_id.is_any_server()) {
+ auto old_message_id = find_old_message_id(d->dialog_id, message_id);
+ if (old_message_id.is_valid()) {
+ bool have_old_message = get_message(d, old_message_id) != nullptr;
+ LOG(WARNING) << "Sent " << FullMessageId{d->dialog_id, message_id}
+ << " was deleted before it was received. Have old " << old_message_id << " = " << have_old_message;
+ send_closure_later(actor_id(this), &MessagesManager::delete_messages, d->dialog_id,
+ vector<MessageId>{old_message_id}, false, Promise<Unit>());
+ delete_update_message_id(d->dialog_id, message_id);
+ }
+ }
+ }
+
+ if (m != nullptr && m->notification_id.is_valid()) {
+ CHECK(!message_id.is_scheduled());
+ auto from_mentions = is_from_mention_notification_group(m);
+ auto &group_info = from_mentions ? d->mention_notification_group : d->message_notification_group;
+
+ if (group_info.group_id.is_valid()) {
+ if (group_info.last_notification_id == m->notification_id) {
+ // last notification is deleted, need to find new last notification
+ fix_dialog_last_notification_id(d, from_mentions, m->message_id);
+ }
+ if (is_message_notification_active(d, m)) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_notification, group_info.group_id,
+ m->notification_id, true, false, Promise<Unit>(), "delete_message_from_database");
+ }
+ }
+ } else if (!message_id.is_scheduled() && message_id > d->last_new_message_id) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id,
+ d->message_notification_group.group_id, message_id, false, "delete_message_from_database");
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id,
+ d->mention_notification_group.group_id, message_id, false, "delete_message_from_database");
+ }
+
+ auto need_delete_files = need_delete_message_files(d->dialog_id, m);
+ if (need_delete_files) {
+ delete_message_files(d->dialog_id, m);
}
if (!G()->parameters().use_message_db) {
- // TODO message files should be deleted anyway
- // TODO message should be deleted anyway after restart
return;
}
- DeleteMessageLogEvent logevent;
+ DeleteMessageLogEvent log_event;
- logevent.full_message_id_ = {d->dialog_id, message_id};
+ log_event.full_message_id_ = {d->dialog_id, message_id};
- if (is_secret && m != nullptr) {
- logevent.file_ids_ = get_message_file_ids(m);
+ if (need_delete_files) {
+ log_event.file_ids_ = get_message_file_ids(m);
}
- do_delete_message_logevent(logevent);
+ do_delete_message_log_event(log_event);
}
-void MessagesManager::do_delete_message_logevent(const DeleteMessageLogEvent &logevent) const {
+void MessagesManager::do_delete_message_log_event(const DeleteMessageLogEvent &log_event) const {
+ CHECK(G()->parameters().use_message_db);
+
Promise<Unit> db_promise;
- if (!logevent.file_ids_.empty()) {
- auto logevent_id = logevent.id_;
- if (!logevent_id) {
- auto storer = LogEventStorerImpl<DeleteMessageLogEvent>(logevent);
- logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteMessage, storer);
- }
+ if (!log_event.file_ids_.empty()) {
+ auto log_event_id = log_event.id_;
+ if (log_event_id == 0) {
+ log_event_id =
+ binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::DeleteMessage, get_log_event_storer(log_event));
+ }
+
+ MultiPromiseActorSafe mpas{"DeleteMessageMultiPromiseActor"};
+ mpas.add_promise(
+ PromiseCreator::lambda([log_event_id, context_weak_ptr = get_context_weak_ptr()](Result<Unit> result) {
+ auto context = context_weak_ptr.lock();
+ if (result.is_error() || context == nullptr) {
+ return;
+ }
+ CHECK(context->get_id() == Global::ID);
+ auto global = static_cast<Global *>(context.get());
+ if (global->close_flag()) {
+ return;
+ }
- MultiPromiseActorSafe mpas;
- mpas.add_promise(PromiseCreator::lambda([logevent_id](Result<Unit> result) {
- if (result.is_error()) {
- return;
- }
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
- }));
+ binlog_erase(global->td_db()->get_binlog(), log_event_id);
+ }));
auto lock = mpas.get_promise();
- for (auto file_id : logevent.file_ids_) {
- send_closure(G()->file_manager(), &FileManager::delete_file, file_id, mpas.get_promise(),
- "do_delete_message_logevent");
+ for (auto file_id : log_event.file_ids_) {
+ if (need_delete_file(log_event.full_message_id_, file_id)) {
+ send_closure(G()->file_manager(), &FileManager::delete_file, file_id, mpas.get_promise(),
+ "do_delete_message_log_event");
+ }
}
db_promise = mpas.get_promise();
lock.set_value(Unit());
}
// message may not exist in the dialog
- LOG(INFO) << "Delete " << logevent.full_message_id_ << " from database";
- G()->td_db()->get_messages_db_async()->delete_message(logevent.full_message_id_, std::move(db_promise));
+ LOG(INFO) << "Delete " << log_event.full_message_id_ << " from database";
+ G()->td_db()->get_message_db_async()->delete_message(log_event.full_message_id_, std::move(db_promise));
}
-void MessagesManager::attach_message_to_previous(Dialog *d, MessageId message_id) {
+void MessagesManager::attach_message_to_previous(Dialog *d, MessageId message_id, const char *source) {
CHECK(d != nullptr);
+ CHECK(message_id.is_valid());
MessagesIterator it(d, message_id);
- Message *message = *it;
- CHECK(message != nullptr);
- CHECK(message->message_id == message_id);
- CHECK(message->have_previous);
+ Message *m = *it;
+ CHECK(m != nullptr);
+ CHECK(m->message_id == message_id);
+ LOG_CHECK(m->have_previous) << d->dialog_id << " " << message_id << " " << source;
--it;
- CHECK(*it != nullptr);
- LOG(INFO) << "Attach " << message_id << " to the previous " << (*it)->message_id;
+ LOG_CHECK(*it != nullptr) << d->dialog_id << " " << message_id << " " << source;
+ LOG(INFO) << "Attach " << message_id << " to the previous " << (*it)->message_id << " in " << d->dialog_id;
if ((*it)->have_next) {
- message->have_next = true;
+ m->have_next = true;
} else {
(*it)->have_next = true;
}
}
-void MessagesManager::attach_message_to_next(Dialog *d, MessageId message_id) {
+void MessagesManager::attach_message_to_next(Dialog *d, MessageId message_id, const char *source) {
CHECK(d != nullptr);
+ CHECK(message_id.is_valid());
MessagesIterator it(d, message_id);
- Message *message = *it;
- CHECK(message != nullptr);
- CHECK(message->message_id == message_id);
- CHECK(message->have_next);
+ Message *m = *it;
+ CHECK(m != nullptr);
+ CHECK(m->message_id == message_id);
+ LOG_CHECK(m->have_next) << d->dialog_id << " " << message_id << " " << source;
++it;
- CHECK(*it != nullptr);
- LOG(INFO) << "Attach " << message_id << " to the next " << (*it)->message_id;
+ LOG_CHECK(*it != nullptr) << d->dialog_id << " " << message_id << " " << source;
+ LOG(INFO) << "Attach " << message_id << " to the next " << (*it)->message_id << " in " << d->dialog_id;
if ((*it)->have_previous) {
- message->have_previous = true;
+ m->have_previous = true;
} else {
(*it)->have_previous = true;
}
}
-void MessagesManager::update_message(Dialog *d, unique_ptr<Message> &old_message, unique_ptr<Message> new_message,
- bool need_send_update_message_content, bool *need_update_dialog_pos) {
+bool MessagesManager::update_message(Dialog *d, Message *old_message, unique_ptr<Message> new_message,
+ bool *need_update_dialog_pos, bool is_message_in_dialog) {
CHECK(d != nullptr);
CHECK(old_message != nullptr);
CHECK(new_message != nullptr);
- CHECK(old_message->message_id == new_message->message_id);
+ LOG_CHECK(old_message->message_id == new_message->message_id)
+ << d->dialog_id << ' ' << old_message->message_id << ' ' << new_message->message_id << ' '
+ << is_message_in_dialog;
CHECK(old_message->random_y == new_message->random_y);
CHECK(need_update_dialog_pos != nullptr);
DialogId dialog_id = d->dialog_id;
MessageId message_id = old_message->message_id;
- bool is_changed = false;
+ auto old_content_type = old_message->content->get_type();
+ auto new_content_type = new_message->content->get_type();
+ bool is_scheduled = message_id.is_scheduled();
+ bool need_send_update = false;
+ bool is_new_available =
+ new_content_type != MessageContentType::ChatDeleteHistory && new_message->restriction_reasons.empty();
+ bool replace_legacy = (old_message->legacy_layer != 0 &&
+ (new_message->legacy_layer == 0 || old_message->legacy_layer < new_message->legacy_layer)) ||
+ old_content_type == MessageContentType::Unsupported;
+ bool was_visible_message_reply_info = is_visible_message_reply_info(dialog_id, old_message);
if (old_message->date != new_message->date) {
if (new_message->date > 0) {
- LOG_IF(ERROR,
- !new_message->is_outgoing && dialog_id != DialogId(td_->contacts_manager_->get_my_id("update_message")))
- << "Date has changed for incoming " << message_id << " in " << dialog_id << " from " << old_message->date
- << " to " << new_message->date;
+ if (!(is_scheduled || message_id.is_yet_unsent() ||
+ (message_id.is_server() && message_id.get_server_message_id().get() == 1) ||
+ old_content_type == MessageContentType::ChannelMigrateFrom ||
+ old_content_type == MessageContentType::ChannelCreate)) {
+ LOG(ERROR) << "Date has changed for " << message_id << " in " << dialog_id << " from " << old_message->date
+ << " to " << new_message->date << ", message content type is " << old_content_type << '/'
+ << new_content_type;
+ }
CHECK(old_message->date > 0);
+ LOG(DEBUG) << "Message date has changed from " << old_message->date << " to " << new_message->date;
old_message->date = new_message->date;
- if (d->last_message_id == message_id) {
+ if (!is_scheduled && d->last_message_id == message_id) {
*need_update_dialog_pos = true;
}
- is_changed = true;
+ need_send_update = true;
} else {
- LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with wrong date " << new_message->date;
+ LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with wrong date " << new_message->date
+ << ", message content type is " << old_content_type << '/' << new_content_type;
}
}
+ if (old_message->date == old_message->edited_schedule_date) {
+ old_message->edited_schedule_date = 0;
+ }
bool is_edited = false;
+ int32 old_shown_edit_date = old_message->hide_edit_date ? 0 : old_message->edit_date;
if (old_message->edit_date != new_message->edit_date) {
if (new_message->edit_date > 0) {
if (new_message->edit_date > old_message->edit_date) {
+ LOG(DEBUG) << "Message edit date has changed from " << old_message->edit_date << " to "
+ << new_message->edit_date;
old_message->edit_date = new_message->edit_date;
- is_edited = true;
- is_changed = true;
}
} else {
- LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " with wrong edit date "
- << new_message->edit_date << ", old edit date = " << old_message->edit_date;
+ LOG(ERROR) << "Receive " << message_id << " in " << dialog_id << " of type " << old_content_type << "/"
+ << new_content_type << " with wrong edit date " << new_message->edit_date
+ << ", old edit date = " << old_message->edit_date;
}
}
if (old_message->author_signature != new_message->author_signature) {
- LOG_IF(INFO, !old_message->sender_user_id.is_valid() || new_message->sender_user_id.is_valid())
- << "Author signature has changed for " << message_id << " in " << dialog_id << " sent by "
- << old_message->sender_user_id << "/" << new_message->sender_user_id << " from "
- << old_message->author_signature << " to " << new_message->author_signature;
+ LOG(DEBUG) << "Author signature has changed for " << message_id << " in " << dialog_id << " sent by "
+ << old_message->sender_user_id << "/" << new_message->sender_user_id << " or "
+ << old_message->sender_dialog_id << "/" << new_message->sender_dialog_id << " from "
+ << old_message->author_signature << " to " << new_message->author_signature;
old_message->author_signature = std::move(new_message->author_signature);
- is_changed = true;
+ need_send_update = true;
}
if (old_message->sender_user_id != new_message->sender_user_id) {
- // there can be race for sent signed posts
+ // there can be race for sent signed posts or changed anonymous flag
LOG_IF(ERROR, old_message->sender_user_id != UserId() && new_message->sender_user_id != UserId())
<< message_id << " in " << dialog_id << " has changed sender from " << old_message->sender_user_id << " to "
- << new_message->sender_user_id;
+ << new_message->sender_user_id << ", message content type is " << old_content_type << '/' << new_content_type;
- LOG_IF(WARNING, new_message->sender_user_id.is_valid() || old_message->author_signature.empty())
+ LOG_IF(WARNING, (new_message->sender_user_id.is_valid() || old_message->author_signature.empty()) &&
+ !old_message->sender_dialog_id.is_valid() && !new_message->sender_dialog_id.is_valid())
<< "Update message sender from " << old_message->sender_user_id << " to " << new_message->sender_user_id
<< " in " << dialog_id;
+ LOG(DEBUG) << "Change message sender";
old_message->sender_user_id = new_message->sender_user_id;
- is_changed = true;
+ need_send_update = true;
+ }
+ if (old_message->sender_dialog_id != new_message->sender_dialog_id) {
+ // there can be race for changed anonymous flag
+ LOG_IF(ERROR, old_message->sender_dialog_id != DialogId() && new_message->sender_dialog_id != DialogId())
+ << message_id << " in " << dialog_id << " has changed sender from " << old_message->sender_dialog_id << " to "
+ << new_message->sender_dialog_id << ", message content type is " << old_content_type << '/' << new_content_type;
+
+ LOG(DEBUG) << "Change message sender";
+ old_message->sender_dialog_id = new_message->sender_dialog_id;
+ need_send_update = true;
}
if (old_message->forward_info == nullptr) {
if (new_message->forward_info != nullptr) {
- LOG(ERROR) << message_id << " in " << dialog_id << " has received forward info " << *new_message->forward_info;
+ if (!replace_legacy) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has received forward info " << *new_message->forward_info
+ << ", really forwarded from " << old_message->real_forward_from_message_id << " in "
+ << old_message->real_forward_from_dialog_id << ", message content type is " << old_content_type
+ << '/' << new_content_type;
+ } else {
+ LOG(DEBUG) << "Message forward has changed to " << *new_message->forward_info;
+ }
+ old_message->forward_info = std::move(new_message->forward_info);
+ need_send_update = true;
}
} else {
if (new_message->forward_info != nullptr) {
- if (*old_message->forward_info != *new_message->forward_info &&
- (!old_message->forward_info->sender_user_id.is_valid() ||
- new_message->forward_info->sender_user_id.is_valid())) {
+ if (old_message->forward_info->author_signature != new_message->forward_info->author_signature) {
old_message->forward_info->author_signature = new_message->forward_info->author_signature;
- LOG_IF(ERROR, *old_message->forward_info != *new_message->forward_info)
- << message_id << " in " << dialog_id << " has changed forward info from " << *old_message->forward_info
- << " to " << *new_message->forward_info << ", really forwarded from " << old_message->debug_forward_from;
+ LOG(DEBUG) << "Change message signature";
+ need_send_update = true;
}
- old_message->forward_info = std::move(new_message->forward_info);
- is_changed = true;
- } else {
- LOG(ERROR) << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id
- << " has lost forward info " << *old_message->forward_info << ", really forwarded from "
- << old_message->debug_forward_from;
+ if (*old_message->forward_info != *new_message->forward_info) {
+ bool need_warning = [&] {
+ if (replace_legacy) {
+ return false;
+ }
+ if (!is_scheduled && !message_id.is_yet_unsent()) {
+ return true;
+ }
+ return !is_forward_info_sender_hidden(new_message->forward_info.get()) &&
+ !is_forward_info_sender_hidden(old_message->forward_info.get());
+ }();
+ if (need_warning) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has changed forward info from "
+ << *old_message->forward_info << " to " << *new_message->forward_info << ", really forwarded from "
+ << old_message->real_forward_from_message_id << " in " << old_message->real_forward_from_dialog_id
+ << ", message content type is " << old_content_type << '/' << new_content_type;
+ } else {
+ LOG(DEBUG) << "Message forward info has changed from " << *old_message->forward_info << " to "
+ << *new_message->forward_info;
+ }
+ old_message->forward_info = std::move(new_message->forward_info);
+ need_send_update = true;
+ }
+ } else if (is_new_available) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id << "/"
+ << old_message->sender_dialog_id << " has lost forward info " << *old_message->forward_info
+ << ", really forwarded from " << old_message->real_forward_from_message_id << " in "
+ << old_message->real_forward_from_dialog_id << ", message content type is " << old_content_type << '/'
+ << new_content_type;
old_message->forward_info = nullptr;
- is_changed = true;
+ need_send_update = true;
+ }
+ }
+ if (old_message->had_forward_info != new_message->had_forward_info) {
+ LOG(DEBUG) << "Message had_forward_info has changed from " << old_message->had_forward_info << " to "
+ << new_message->had_forward_info;
+ old_message->had_forward_info = new_message->had_forward_info;
+ }
+ if (old_message->notification_id != new_message->notification_id) {
+ CHECK(!is_scheduled);
+ if (old_message->notification_id.is_valid()) {
+ if (new_message->notification_id.is_valid()) {
+ LOG(ERROR) << "Notification identifier for " << message_id << " in " << dialog_id
+ << " has tried to change from " << old_message->notification_id << " to "
+ << new_message->notification_id << ", message content type is " << old_content_type << '/'
+ << new_content_type;
+ }
+ } else {
+ CHECK(new_message->notification_id.is_valid());
+ add_notification_id_to_message_id_correspondence(d, new_message->notification_id, message_id);
+ old_message->notification_id = new_message->notification_id;
+ }
+ }
+ if (new_message->is_mention_notification_disabled) {
+ old_message->is_mention_notification_disabled = true;
+ }
+ if (!new_message->from_database) {
+ old_message->from_database = false;
+ }
+
+ if (old_message->ttl_period != new_message->ttl_period) {
+ if (old_message->ttl_period != 0 || !message_id.is_yet_unsent()) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has changed TTL period from " << old_message->ttl_period
+ << " to " << new_message->ttl_period;
+ } else {
+ LOG(DEBUG) << "Change message TTL period";
+ old_message->ttl_period = new_message->ttl_period;
+ need_send_update = true;
}
}
if (old_message->reply_to_message_id != new_message->reply_to_message_id) {
- if (new_message->reply_to_message_id == MessageId() &&
- get_message_force(d, old_message->reply_to_message_id) == nullptr) {
+ // Can't check "&& get_message_force(d, old_message->reply_to_message_id, "update_message") == nullptr", because it
+ // can change message tree and invalidate reference to old_message
+ if (new_message->reply_to_message_id == MessageId() || replace_legacy) {
+ LOG(DEBUG) << "Drop message reply_to_message_id";
+ unregister_message_reply(dialog_id, old_message);
old_message->reply_to_message_id = MessageId();
- is_changed = true;
+ old_message->reply_in_dialog_id = DialogId();
+ update_message_max_reply_media_timestamp(d, old_message, is_message_in_dialog);
+ need_send_update = true;
+ } else if (is_new_available) {
+ if (message_id.is_yet_unsent() && old_message->reply_to_message_id == MessageId() &&
+ new_message->reply_in_dialog_id == DialogId() && is_deleted_message(d, new_message->reply_to_message_id) &&
+ get_message(d, new_message->reply_to_message_id) == nullptr && !is_message_in_dialog) {
+ LOG(INFO) << "Update replied message from " << old_message->reply_to_message_id << " to deleted "
+ << new_message->reply_to_message_id;
+ old_message->reply_to_message_id = new_message->reply_to_message_id;
+ old_message->reply_in_dialog_id = DialogId();
+ update_message_max_reply_media_timestamp(d, old_message, is_message_in_dialog);
+ need_send_update = true;
+ } else if (old_message->reply_to_message_id.is_valid_scheduled() &&
+ old_message->reply_to_message_id.is_scheduled_server() &&
+ new_message->reply_to_message_id.is_valid_scheduled() &&
+ new_message->reply_to_message_id.is_scheduled_server() &&
+ old_message->reply_to_message_id.get_scheduled_server_message_id() ==
+ new_message->reply_to_message_id.get_scheduled_server_message_id() &&
+ new_message->reply_in_dialog_id == DialogId()) {
+ // schedule date has changed
+ old_message->reply_to_message_id = new_message->reply_to_message_id;
+ old_message->reply_in_dialog_id = DialogId();
+ need_send_update = true;
+ } else if (message_id.is_yet_unsent() && old_message->top_thread_message_id == new_message->reply_to_message_id &&
+ new_message->reply_in_dialog_id == DialogId()) {
+ LOG(INFO) << "Update replied message from " << old_message->reply_to_message_id << " to top thread "
+ << new_message->reply_to_message_id;
+ unregister_message_reply(dialog_id, old_message);
+ old_message->reply_to_message_id = new_message->reply_to_message_id;
+ old_message->reply_in_dialog_id = DialogId();
+ register_message_reply(dialog_id, old_message);
+ need_send_update = true;
+ } else {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has changed it is replied message from "
+ << old_message->reply_to_message_id << " to " << new_message->reply_to_message_id
+ << ", message content type is " << old_content_type << '/' << new_content_type;
+ }
+ }
+ }
+ if (old_message->reply_in_dialog_id != new_message->reply_in_dialog_id) {
+ if (new_message->reply_in_dialog_id == DialogId() || replace_legacy) {
+ LOG(DEBUG) << "Drop message reply_in_dialog_id";
+ old_message->reply_in_dialog_id = DialogId();
+ need_send_update = true;
+ } else if (is_new_available && old_message->reply_in_dialog_id.is_valid()) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has changed dialog it is reply in from "
+ << old_message->reply_in_dialog_id << " to " << new_message->reply_in_dialog_id
+ << ", message content type is " << old_content_type << '/' << new_content_type;
+ }
+ }
+ if (old_message->top_thread_message_id != new_message->top_thread_message_id) {
+ if (new_message->top_thread_message_id == MessageId() || old_message->top_thread_message_id == MessageId()) {
+ LOG(DEBUG) << "Change message thread from " << old_message->top_thread_message_id << " to "
+ << new_message->top_thread_message_id;
+ old_message->top_thread_message_id = new_message->top_thread_message_id;
+ need_send_update = true;
+ } else if (is_new_available) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has changed message thread from "
+ << old_message->top_thread_message_id << " to " << new_message->top_thread_message_id
+ << ", message content type is " << old_content_type << '/' << new_content_type;
+ }
+ }
+ if (old_message->via_bot_user_id != new_message->via_bot_user_id) {
+ if ((!message_id.is_yet_unsent() || old_message->via_bot_user_id.is_valid()) && is_new_available &&
+ !replace_legacy) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has changed bot via it is sent from "
+ << old_message->via_bot_user_id << " to " << new_message->via_bot_user_id
+ << ", message content type is " << old_content_type << '/' << new_content_type;
+ }
+ LOG(DEBUG) << "Change message via_bot from " << old_message->via_bot_user_id << " to "
+ << new_message->via_bot_user_id;
+ old_message->via_bot_user_id = new_message->via_bot_user_id;
+ need_send_update = true;
+
+ if (old_message->hide_via_bot && old_message->via_bot_user_id.is_valid()) {
+ // wrongly set hide_via_bot
+ old_message->hide_via_bot = false;
+ }
+ }
+ if (old_message->is_outgoing != new_message->is_outgoing && is_new_available) {
+ if (!replace_legacy && !(message_id.is_scheduled() && dialog_id == get_my_dialog_id())) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has changed is_outgoing from " << old_message->is_outgoing
+ << " to " << new_message->is_outgoing << ", message content type is " << old_content_type << '/'
+ << new_content_type;
} else {
- LOG(ERROR) << message_id << " in " << dialog_id << " has changed message it is reply to from "
- << old_message->reply_to_message_id << " to " << new_message->reply_to_message_id;
- dump_debug_message_op(d);
+ LOG(DEBUG) << "Message is_outgoing has changed from " << old_message->is_outgoing << " to "
+ << new_message->is_outgoing;
}
+ old_message->is_outgoing = new_message->is_outgoing;
+ need_send_update = true;
}
- LOG_IF(ERROR, old_message->via_bot_user_id != new_message->via_bot_user_id)
- << message_id << " in " << dialog_id << " has changed bot via it is sent from " << old_message->via_bot_user_id
- << " to " << new_message->via_bot_user_id;
- LOG_IF(ERROR, old_message->is_outgoing != new_message->is_outgoing)
- << message_id << " in " << dialog_id << " has changed is_outgoing from " << old_message->is_outgoing << " to "
- << new_message->is_outgoing;
LOG_IF(ERROR, old_message->is_channel_post != new_message->is_channel_post)
<< message_id << " in " << dialog_id << " has changed is_channel_post from " << old_message->is_channel_post
- << " to " << new_message->is_channel_post;
- LOG_IF(ERROR, old_message->contains_mention != new_message->contains_mention && old_message->edit_date == 0)
- << message_id << " in " << dialog_id << " has changed contains_mention from " << old_message->contains_mention
- << " to " << new_message->contains_mention;
- LOG_IF(ERROR, old_message->disable_notification != new_message->disable_notification && old_message->edit_date == 0)
- << "Disable_notification has changed from " << old_message->disable_notification << " to "
- << new_message->disable_notification
- << ". Old message: " << to_string(get_message_object(dialog_id, old_message.get()))
- << ". New message: " << to_string(get_message_object(dialog_id, new_message.get()));
-
- if (update_message_contains_unread_mention(d, old_message.get(), new_message->contains_unread_mention,
- "update_message")) {
- is_changed = true;
- }
- if (update_message_views(dialog_id, old_message.get(), new_message->views)) {
- is_changed = true;
+ << " to " << new_message->is_channel_post << ", message content type is " << old_content_type << '/'
+ << new_content_type;
+ if (!old_message->top_thread_message_id.is_valid()) {
+ new_message->is_topic_message = false;
+ }
+ if (old_message->is_topic_message != new_message->is_topic_message) {
+ LOG_IF(ERROR, !message_id.is_yet_unsent() && !replace_legacy)
+ << message_id << " in " << dialog_id << " has changed is_topic_message to " << new_message->is_topic_message;
+ old_message->is_topic_message = new_message->is_topic_message;
+ need_send_update = true;
+ }
+ if (old_message->contains_mention != new_message->contains_mention) {
+ if (old_message->edit_date == 0 && is_new_available && old_content_type != MessageContentType::PinMessage &&
+ old_content_type != MessageContentType::ExpiredPhoto && old_content_type != MessageContentType::ExpiredVideo &&
+ !replace_legacy) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has changed contains_mention from "
+ << old_message->contains_mention << " to " << new_message->contains_mention
+ << ", is_outgoing = " << old_message->is_outgoing << ", message content type is " << old_content_type
+ << '/' << new_content_type;
+ }
+ // contains_mention flag shouldn't be changed, because the message will not be added to unread mention list
+ // and we are unable to show/hide message notification
+ // old_message->contains_mention = new_message->contains_mention;
+ // need_send_update = true;
+ }
+ if (old_message->disable_notification != new_message->disable_notification) {
+ LOG_IF(ERROR, old_message->edit_date == 0 && is_new_available && !replace_legacy)
+ << "Disable_notification has changed from " << old_message->disable_notification << " to "
+ << new_message->disable_notification
+ << ". Old message: " << to_string(get_message_object(dialog_id, old_message, "update_message"))
+ << ". New message: " << to_string(get_message_object(dialog_id, new_message.get(), "update_message"));
+ // disable_notification flag shouldn't be changed, because we are unable to show/hide message notification
+ // old_message->disable_notification = new_message->disable_notification;
+ // need_send_update = true;
+ }
+ if (old_message->disable_web_page_preview != new_message->disable_web_page_preview) {
+ old_message->disable_web_page_preview = new_message->disable_web_page_preview;
+ }
+
+ if (old_message->noforwards != new_message->noforwards) {
+ LOG(DEBUG) << "Message can_be_saved has changed from " << !old_message->noforwards << " to "
+ << !old_message->noforwards;
+ old_message->noforwards = new_message->noforwards;
+ need_send_update = true;
+ }
+ if (old_message->restriction_reasons != new_message->restriction_reasons) {
+ LOG(DEBUG) << "Message restriction_reasons have changed from " << old_message->restriction_reasons << " to "
+ << old_message->restriction_reasons;
+ old_message->restriction_reasons = std::move(new_message->restriction_reasons);
+ need_send_update = true;
+ }
+ if (old_message->legacy_layer != new_message->legacy_layer) {
+ old_message->legacy_layer = new_message->legacy_layer;
}
if ((old_message->media_album_id == 0 || td_->auth_manager_->is_bot()) && new_message->media_album_id != 0) {
old_message->media_album_id = new_message->media_album_id;
- is_changed = true;
+ LOG(DEBUG) << "Update message media_album_id";
+ need_send_update = true;
+ }
+ if (old_message->hide_edit_date != new_message->hide_edit_date) {
+ old_message->hide_edit_date = new_message->hide_edit_date;
+ }
+ int32 new_shown_edit_date = old_message->hide_edit_date ? 0 : old_message->edit_date;
+ if (new_shown_edit_date != old_shown_edit_date) {
+ LOG(DEBUG) << "Message edit_date has changed";
+ is_edited = true;
+ need_send_update = true;
+ }
+
+ if (old_message->is_from_scheduled != new_message->is_from_scheduled) {
+ // is_from_scheduled flag shouldn't be changed, because we are unable to show/hide message notification
+ // old_message->is_from_scheduled = new_message->is_from_scheduled;
}
if (old_message->edit_date > 0) {
@@ -22341,31 +36862,53 @@ void MessagesManager::update_message(Dialog *d, unique_ptr<Message> &old_message
new_message->reply_markup == nullptr) {
set_dialog_reply_markup(d, MessageId());
}
+ LOG(DEBUG) << "Update message reply keyboard";
old_message->reply_markup = std::move(new_message->reply_markup);
is_edited = true;
- is_changed = true;
+ need_send_update = true;
}
old_message->had_reply_markup = false;
} else {
if (old_message->reply_markup == nullptr) {
if (new_message->reply_markup != nullptr) {
- auto content_type = old_message->content->get_id();
// MessageGame and MessageInvoice reply markup can be generated server side
- LOG_IF(ERROR, content_type != MessageGame::ID && content_type != MessageInvoice::ID)
- << message_id << " in " << dialog_id << " has received reply markup " << *new_message->reply_markup;
+ // some forwards retain their reply markup
+ if (old_content_type != MessageContentType::Game && old_content_type != MessageContentType::Invoice &&
+ need_message_changed_warning(old_message) && !replace_legacy) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " has received reply markup " << *new_message->reply_markup
+ << ", message content type is " << old_content_type << '/' << new_content_type;
+ } else {
+ LOG(DEBUG) << "Add message reply keyboard";
+ }
old_message->had_reply_markup = false;
old_message->reply_markup = std::move(new_message->reply_markup);
- is_changed = true;
+ need_send_update = true;
}
} else {
if (new_message->reply_markup != nullptr) {
- LOG_IF(WARNING, *old_message->reply_markup != *new_message->reply_markup)
- << message_id << " in " << dialog_id << " has changed reply_markup from " << *old_message->reply_markup
- << " to " << *new_message->reply_markup;
+ if (replace_legacy ||
+ (message_id.is_yet_unsent() && old_message->reply_markup->type == ReplyMarkup::Type::InlineKeyboard &&
+ new_message->reply_markup->type == ReplyMarkup::Type::InlineKeyboard)) {
+ // allow the server to update inline keyboard for sent messages
+ // this is needed to get correct button_id for UrlAuth buttons
+ old_message->had_reply_markup = false;
+ old_message->reply_markup = std::move(new_message->reply_markup);
+ need_send_update = true;
+ } else if (need_message_changed_warning(old_message) && is_new_available) {
+ LOG_IF(WARNING, *old_message->reply_markup != *new_message->reply_markup)
+ << message_id << " in " << dialog_id << " has changed reply_markup from " << *old_message->reply_markup
+ << " to " << *new_message->reply_markup;
+ }
} else {
- LOG(ERROR) << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id
- << " has lost reply markup " << *old_message->reply_markup;
+ // if the message is not accessible anymore, then we don't need a warning
+ if (need_message_changed_warning(old_message) && is_new_available) {
+ LOG(ERROR) << message_id << " in " << dialog_id << " sent by " << old_message->sender_user_id << "/"
+ << old_message->sender_dialog_id << " has lost reply markup " << *old_message->reply_markup
+ << ". Old message: " << to_string(get_message_object(dialog_id, old_message, "update_message"))
+ << ". New message: "
+ << to_string(get_message_object(dialog_id, new_message.get(), "update_message"));
+ }
}
}
}
@@ -22373,85 +36916,109 @@ void MessagesManager::update_message(Dialog *d, unique_ptr<Message> &old_message
if (old_message->last_access_date < new_message->last_access_date) {
old_message->last_access_date = new_message->last_access_date;
}
+ if (old_message->history_generation < new_message->history_generation) {
+ old_message->history_generation = new_message->history_generation;
+ }
+ if (new_message->is_update_sent || is_edited) {
+ old_message->is_update_sent = true;
+ }
- CHECK(!new_message->have_previous || !new_message->have_next);
- if (new_message->have_previous && !old_message->have_previous) {
- old_message->have_previous = true;
- attach_message_to_previous(d, message_id);
- } else if (new_message->have_next && !old_message->have_next) {
- old_message->have_next = true;
- attach_message_to_next(d, message_id);
+ if (!is_scheduled && update_message_is_pinned(d, old_message, new_message->is_pinned, "update_message")) {
+ need_send_update = true;
+ }
+ if (!is_scheduled &&
+ update_message_contains_unread_mention(d, old_message, new_message->contains_unread_mention, "update_message")) {
+ need_send_update = true;
+ }
+ // update_message_interaction_info must be called after top_thread_message_id is updated
+ if (update_message_interaction_info(d, old_message, new_message->view_count, new_message->forward_count, true,
+ std::move(new_message->reply_info), true, std::move(new_message->reactions),
+ "update_message")) {
+ need_send_update = true;
}
- if (update_message_content(dialog_id, old_message.get(), old_message->content, std::move(new_message->content),
- need_send_update_message_content)) {
- is_changed = true;
+ if (!is_scheduled) {
+ CHECK(!new_message->have_previous || !new_message->have_next);
+ if (new_message->have_previous && !old_message->have_previous) {
+ old_message->have_previous = true;
+ attach_message_to_previous(d, message_id, "update_message");
+ } else if (new_message->have_next && !old_message->have_next) {
+ old_message->have_next = true;
+ attach_message_to_next(d, message_id, "update_message");
+ }
}
- // TODO update can be send only if the message has already been returned to the user
+
+ bool is_content_changed = false;
+ if (update_message_content(dialog_id, old_message, std::move(new_message->content),
+ message_id.is_yet_unsent() && new_message->edit_date == 0, is_message_in_dialog,
+ is_content_changed)) {
+ send_update_message_content(d, old_message, is_message_in_dialog, "update_message");
+ need_send_update = true;
+ }
+
+ if (was_visible_message_reply_info && !is_visible_message_reply_info(dialog_id, old_message)) {
+ send_update_message_interaction_info(dialog_id, old_message);
+ }
+
if (is_edited && !td_->auth_manager_->is_bot()) {
- send_update_message_edited(dialog_id, old_message.get());
+ send_update_message_edited(dialog_id, old_message);
}
- (void)is_changed;
- // need to save message always, because it might be added to some message index
- // if (is_changed) {
- on_message_changed(d, old_message.get(), "update_message");
- // }
+ if (!was_visible_message_reply_info && is_visible_message_reply_info(dialog_id, old_message)) {
+ send_update_message_interaction_info(dialog_id, old_message);
+ }
+
+ on_message_changed(d, old_message, need_send_update, "update_message");
+ return need_send_update;
}
-bool MessagesManager::need_message_text_changed_warning(const Message *old_message, const MessageText *old_content,
- const MessageText *new_content) {
+bool MessagesManager::need_message_changed_warning(const Message *old_message) {
if (old_message->edit_date > 0) {
// message was edited
return false;
}
- if (old_message->message_id.is_yet_unsent() && old_message->forward_info != nullptr) {
+ if (old_message->message_id.is_yet_unsent() &&
+ (old_message->forward_info != nullptr || old_message->had_forward_info ||
+ old_message->real_forward_from_dialog_id.is_valid())) {
// original message may be edited
return false;
}
- if (new_content->text.text == "Unsupported characters" ||
- new_content->text.text == "This channel is blocked because it was used to spread pornographic content.") {
- // message contained unsupported characters, text is replaced
+ if (old_message->ttl > 0) {
+ // message can expire
return false;
}
- if (old_message->message_id.is_yet_unsent() && !old_content->text.entities.empty() &&
- old_content->text.entities[0].offset == 0 &&
- (new_content->text.entities.empty() || new_content->text.entities[0].offset != 0) &&
- old_content->text.text != new_content->text.text && ends_with(old_content->text.text, new_content->text.text)) {
- // server has deleted first entity and ltrim the message
+ if (!old_message->restriction_reasons.empty()) {
return false;
}
- for (auto &entity : new_content->text.entities) {
- if (entity.type == MessageEntity::Type::PhoneNumber) {
- // TODO remove after find_phone_numbers is implemented
- return false;
- }
- }
return true;
}
bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_message,
- unique_ptr<MessageContent> &old_content,
- unique_ptr<MessageContent> new_content,
- bool need_send_update_message_content) {
- bool is_content_changed = false;
+ unique_ptr<MessageContent> new_content, bool need_merge_files,
+ bool is_message_in_dialog, bool &is_content_changed) {
+ is_content_changed = false;
bool need_update = false;
- int32 old_content_type = old_content->get_id();
- int32 new_content_type = new_content->get_id();
- bool can_delete_old_document = old_message->message_id.is_yet_unsent() && false;
- if (old_content_type != new_content_type) {
- need_update = true;
- LOG(INFO) << "Message content has changed its type from " << old_content_type << " to " << new_content_type;
+ unique_ptr<MessageContent> &old_content = old_message->content;
+ MessageContentType old_content_type = old_content->get_type();
+ MessageContentType new_content_type = new_content->get_type();
- old_message->is_content_secret = is_secret_message_content(old_message->ttl, new_content->get_id());
+ auto old_file_id = get_message_content_any_file_id(old_content.get());
+ bool need_finish_upload = old_file_id.is_valid() && need_merge_files;
+ if (old_content_type != new_content_type) {
+ if (old_message->ttl > 0 && old_message->ttl_expires_at > 0 &&
+ ((new_content_type == MessageContentType::ExpiredPhoto && old_content_type == MessageContentType::Photo) ||
+ (new_content_type == MessageContentType::ExpiredVideo && old_content_type == MessageContentType::Video))) {
+ LOG(INFO) << "Do not apply expired message content early";
+ } else {
+ need_update = true;
+ LOG(INFO) << "Message content has changed its type from " << old_content_type << " to " << new_content_type;
- auto old_file_id = get_message_content_file_id(old_content.get());
- if (old_file_id.is_valid()) {
- // cancel file upload of the main file to allow next upload with the same file to succeed
- td_->file_manager_->upload(old_file_id, nullptr, 0, 0);
+ old_message->is_content_secret = is_secret_message_content(old_message->ttl, new_content->get_type());
+ }
- auto new_file_id = get_message_content_file_id(new_content.get());
+ if (need_merge_files && old_file_id.is_valid()) {
+ auto new_file_id = get_message_content_any_file_id(new_content.get());
if (new_file_id.is_valid()) {
FileView old_file_view = td_->file_manager_->get_file_view(old_file_id);
FileView new_file_view = td_->file_manager_->get_file_view(new_file_id);
@@ -22461,20 +37028,6 @@ bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_me
old_file_view.size() == new_file_view.size()) {
auto old_file_type = old_file_view.get_type();
auto new_file_type = new_file_view.get_type();
- auto is_document_file_type = [](FileType file_type) {
- switch (file_type) {
- case FileType::Video:
- case FileType::VoiceNote:
- case FileType::Document:
- case FileType::Sticker:
- case FileType::Audio:
- case FileType::Animation:
- case FileType::VideoNote:
- return true;
- default:
- return false;
- }
- };
if (is_document_file_type(old_file_type) && is_document_file_type(new_file_type)) {
auto &old_location = old_file_view.local_location();
@@ -22489,405 +37042,62 @@ bool MessagesManager::update_message_content(DialogId dialog_id, Message *old_me
}
}
} else {
- switch (new_content_type) {
- case MessageText::ID: {
- auto old_ = static_cast<const MessageText *>(old_content.get());
- auto new_ = static_cast<const MessageText *>(new_content.get());
- if (old_->text.text != new_->text.text) {
- if (need_message_text_changed_warning(old_message, old_, new_)) {
- LOG(ERROR) << "Message text has changed for " << to_string(get_message_object(dialog_id, old_message))
- << ". New content is "
- << to_string(get_message_content_object(new_content.get(), old_message->date,
- old_message->is_content_secret));
- }
- need_update = true;
- }
- if (old_->text.entities != new_->text.entities) {
- const int32 MAX_CUSTOM_ENTITIES_COUNT = 100; // server-size limit
- if (need_message_text_changed_warning(old_message, old_, new_) &&
- old_->text.entities.size() <= MAX_CUSTOM_ENTITIES_COUNT) {
- LOG(WARNING) << "Entities has changed for " << to_string(get_message_object(dialog_id, old_message))
- << ". New content is "
- << to_string(get_message_content_object(new_content.get(), old_message->date,
- old_message->is_content_secret));
- }
- need_update = true;
- }
- if (old_->web_page_id != new_->web_page_id) {
- LOG(INFO) << "Old: " << old_->web_page_id << ", new: " << new_->web_page_id;
- is_content_changed = true;
- need_update |= td_->web_pages_manager_->have_web_page(old_->web_page_id) ||
- td_->web_pages_manager_->have_web_page(new_->web_page_id);
- }
- break;
- }
- case MessageAnimation::ID: {
- auto old_ = static_cast<const MessageAnimation *>(old_content.get());
- auto new_ = static_cast<const MessageAnimation *>(new_content.get());
- if (td_->animations_manager_->merge_animations(new_->file_id, old_->file_id, can_delete_old_document)) {
- need_update = true;
- }
- if (old_->caption != new_->caption) {
- need_update = true;
- }
- break;
- }
- case MessageAudio::ID: {
- auto old_ = static_cast<const MessageAudio *>(old_content.get());
- auto new_ = static_cast<const MessageAudio *>(new_content.get());
- if (td_->audios_manager_->merge_audios(new_->file_id, old_->file_id, can_delete_old_document)) {
- need_update = true;
- }
- if (old_->caption != new_->caption) {
- need_update = true;
- }
- break;
- }
- case MessageContact::ID: {
- auto old_ = static_cast<const MessageContact *>(old_content.get());
- auto new_ = static_cast<const MessageContact *>(new_content.get());
- if (old_->contact != new_->contact) {
- need_update = true;
- }
- break;
- }
- case MessageDocument::ID: {
- auto old_ = static_cast<const MessageDocument *>(old_content.get());
- auto new_ = static_cast<const MessageDocument *>(new_content.get());
- if (td_->documents_manager_->merge_documents(new_->file_id, old_->file_id, can_delete_old_document)) {
- need_update = true;
- }
- if (old_->caption != new_->caption) {
- need_update = true;
- }
- break;
- }
- case MessageGame::ID: {
- auto old_ = static_cast<const MessageGame *>(old_content.get());
- auto new_ = static_cast<const MessageGame *>(new_content.get());
- if (old_->game != new_->game) {
- need_update = true;
- }
- break;
- }
- case MessageInvoice::ID: {
- auto old_ = static_cast<const MessageInvoice *>(old_content.get());
- auto new_ = static_cast<const MessageInvoice *>(new_content.get());
- if (old_->title != new_->title || old_->description != new_->description || old_->photo != new_->photo ||
- old_->start_parameter != new_->start_parameter || old_->invoice != new_->invoice ||
- old_->total_amount != new_->total_amount || old_->receipt_message_id != new_->receipt_message_id) {
- need_update = true;
- }
- if (old_->payload != new_->payload || old_->provider_token != new_->provider_token ||
- old_->provider_data != new_->provider_data) {
- is_content_changed = true;
- }
- break;
- }
- case MessageLiveLocation::ID: {
- auto old_ = static_cast<const MessageLiveLocation *>(old_content.get());
- auto new_ = static_cast<const MessageLiveLocation *>(new_content.get());
- if (old_->location != new_->location) {
- need_update = true;
- }
- if (old_->period != new_->period) {
- need_update = true;
- }
- break;
- }
- case MessageLocation::ID: {
- auto old_ = static_cast<const MessageLocation *>(old_content.get());
- auto new_ = static_cast<const MessageLocation *>(new_content.get());
- if (old_->location != new_->location) {
- need_update = true;
- }
- break;
- }
- case MessagePhoto::ID: {
- auto old_ = static_cast<const MessagePhoto *>(old_content.get());
- auto new_ = static_cast<MessagePhoto *>(new_content.get());
- const Photo *old_photo = &old_->photo;
- Photo *new_photo = &new_->photo;
- if (old_photo->date != new_photo->date) {
- is_content_changed = true;
- }
- if (old_photo->id != new_photo->id || old_->caption != new_->caption) {
- need_update = true;
- }
- if (old_photo->photos != new_photo->photos) {
- if ((old_photo->photos.size() == 1 || (old_photo->photos.size() == 2 && old_photo->photos[0].type == 't')) &&
- old_photo->photos.back().type == 'i' && new_photo->photos.size() > 0) {
- // first time get info about sent photo
- if (old_photo->photos.size() == 2) {
- new_photo->photos.push_back(old_photo->photos[0]);
- }
- new_photo->photos.push_back(old_photo->photos.back());
- FileId old_file_id = old_photo->photos.back().file_id;
- FileView old_file_view = td_->file_manager_->get_file_view(old_file_id);
- FileId new_file_id = new_photo->photos[0].file_id;
- FileView new_file_view = td_->file_manager_->get_file_view(new_file_id);
- if (!old_file_view.has_remote_location()) {
- CHECK(new_file_view.has_remote_location());
- CHECK(!new_file_view.remote_location().is_web());
- FileId file_id = td_->file_manager_->register_remote(
- FullRemoteFileLocation(FileType::Photo, new_file_view.remote_location().get_id(),
- new_file_view.remote_location().get_access_hash(), 0, 0, 0, DcId::invalid()),
- FileLocationSource::FromServer, dialog_id, old_photo->photos.back().size, 0, "");
- LOG_STATUS(td_->file_manager_->merge(file_id, old_file_id));
- }
- }
- if ((old_photo->photos.size() == 1 + new_photo->photos.size() ||
- (old_photo->photos.size() == 2 + new_photo->photos.size() &&
- old_photo->photos[new_photo->photos.size()].type == 't')) &&
- old_photo->photos.back().type == 'i') {
- // get sent photo again
- if (old_photo->photos.size() == 2 + new_photo->photos.size()) {
- new_photo->photos.push_back(old_photo->photos[new_photo->photos.size()]);
- }
- new_photo->photos.push_back(old_photo->photos.back());
- }
- if (old_photo->photos != new_photo->photos) {
- need_update = true;
- }
- }
- break;
- }
- case MessageSticker::ID: {
- auto old_ = static_cast<const MessageSticker *>(old_content.get());
- auto new_ = static_cast<const MessageSticker *>(new_content.get());
- if (td_->stickers_manager_->merge_stickers(new_->file_id, old_->file_id, can_delete_old_document)) {
- need_update = true;
- }
- break;
- }
- case MessageVenue::ID: {
- auto old_ = static_cast<const MessageVenue *>(old_content.get());
- auto new_ = static_cast<const MessageVenue *>(new_content.get());
- if (old_->venue != new_->venue) {
- need_update = true;
- }
- break;
- }
- case MessageVideo::ID: {
- auto old_ = static_cast<const MessageVideo *>(old_content.get());
- auto new_ = static_cast<const MessageVideo *>(new_content.get());
- if (td_->videos_manager_->merge_videos(new_->file_id, old_->file_id, can_delete_old_document)) {
- need_update = true;
- }
- if (old_->caption != new_->caption) {
- need_update = true;
- }
- break;
- }
- case MessageVideoNote::ID: {
- auto old_ = static_cast<const MessageVideoNote *>(old_content.get());
- auto new_ = static_cast<const MessageVideoNote *>(new_content.get());
- if (td_->video_notes_manager_->merge_video_notes(new_->file_id, old_->file_id, can_delete_old_document)) {
- need_update = true;
- }
- if (old_->is_viewed != new_->is_viewed) {
- need_update = true;
- }
- break;
- }
- case MessageVoiceNote::ID: {
- auto old_ = static_cast<const MessageVoiceNote *>(old_content.get());
- auto new_ = static_cast<const MessageVoiceNote *>(new_content.get());
- if (td_->voice_notes_manager_->merge_voice_notes(new_->file_id, old_->file_id, can_delete_old_document)) {
- need_update = true;
- }
- if (old_->caption != new_->caption) {
- need_update = true;
- }
- if (old_->is_listened != new_->is_listened) {
- need_update = true;
- }
- break;
- }
- case MessageChatCreate::ID: {
- auto old_ = static_cast<const MessageChatCreate *>(old_content.get());
- auto new_ = static_cast<const MessageChatCreate *>(new_content.get());
- if (old_->title != new_->title || old_->participant_user_ids != new_->participant_user_ids) {
- need_update = true;
- }
- break;
- }
- case MessageChatChangeTitle::ID: {
- auto old_ = static_cast<const MessageChatChangeTitle *>(old_content.get());
- auto new_ = static_cast<const MessageChatChangeTitle *>(new_content.get());
- if (old_->title != new_->title) {
- need_update = true;
- }
- break;
- }
- case MessageChatChangePhoto::ID: {
- auto old_ = static_cast<const MessageChatChangePhoto *>(old_content.get());
- auto new_ = static_cast<const MessageChatChangePhoto *>(new_content.get());
- if (old_->photo != new_->photo) {
- need_update = true;
- }
- break;
- }
- case MessageChatDeletePhoto::ID:
- break;
- case MessageChatDeleteHistory::ID:
- break;
- case MessageChatAddUsers::ID: {
- auto old_ = static_cast<const MessageChatAddUsers *>(old_content.get());
- auto new_ = static_cast<const MessageChatAddUsers *>(new_content.get());
- if (old_->user_ids != new_->user_ids) {
- need_update = true;
- }
- break;
- }
- case MessageChatJoinedByLink::ID:
- break;
- case MessageChatDeleteUser::ID: {
- auto old_ = static_cast<const MessageChatDeleteUser *>(old_content.get());
- auto new_ = static_cast<const MessageChatDeleteUser *>(new_content.get());
- if (old_->user_id != new_->user_id) {
- need_update = true;
- }
- break;
- }
- case MessageChatMigrateTo::ID: {
- auto old_ = static_cast<const MessageChatMigrateTo *>(old_content.get());
- auto new_ = static_cast<const MessageChatMigrateTo *>(new_content.get());
- if (old_->migrated_to_channel_id != new_->migrated_to_channel_id) {
- need_update = true;
- }
- break;
- }
- case MessageChannelCreate::ID: {
- auto old_ = static_cast<const MessageChannelCreate *>(old_content.get());
- auto new_ = static_cast<const MessageChannelCreate *>(new_content.get());
- if (old_->title != new_->title) {
- need_update = true;
- }
- break;
- }
- case MessageChannelMigrateFrom::ID: {
- auto old_ = static_cast<const MessageChannelMigrateFrom *>(old_content.get());
- auto new_ = static_cast<const MessageChannelMigrateFrom *>(new_content.get());
- if (old_->title != new_->title || old_->migrated_from_chat_id != new_->migrated_from_chat_id) {
- need_update = true;
- }
- break;
- }
- case MessagePinMessage::ID: {
- auto old_ = static_cast<const MessagePinMessage *>(old_content.get());
- auto new_ = static_cast<const MessagePinMessage *>(new_content.get());
- if (old_->message_id != new_->message_id) {
- need_update = true;
- }
- break;
- }
- case MessageGameScore::ID: {
- auto old_ = static_cast<const MessageGameScore *>(old_content.get());
- auto new_ = static_cast<const MessageGameScore *>(new_content.get());
- if (old_->game_message_id != new_->game_message_id || old_->game_id != new_->game_id ||
- old_->score != new_->score) {
- need_update = true;
- }
- break;
- }
- case MessageScreenshotTaken::ID:
- break;
- case MessageChatSetTtl::ID: {
- auto old_ = static_cast<const MessageChatSetTtl *>(old_content.get());
- auto new_ = static_cast<const MessageChatSetTtl *>(new_content.get());
- if (old_->ttl != new_->ttl) {
- LOG(ERROR) << "Ttl has changed from " << old_->ttl << " to " << new_->ttl;
- need_update = true;
- }
- break;
- }
- case MessageCall::ID: {
- auto old_ = static_cast<const MessageCall *>(old_content.get());
- auto new_ = static_cast<const MessageCall *>(new_content.get());
- if (old_->call_id != new_->call_id) {
- is_content_changed = true;
- }
- if (old_->duration != new_->duration || old_->discard_reason != new_->discard_reason) {
- need_update = true;
- }
- break;
- }
- case MessagePaymentSuccessful::ID: {
- auto old_ = static_cast<const MessagePaymentSuccessful *>(old_content.get());
- auto new_ = static_cast<const MessagePaymentSuccessful *>(new_content.get());
- if (old_->invoice_message_id != new_->invoice_message_id || old_->currency != new_->currency ||
- old_->total_amount != new_->total_amount || old_->invoice_payload != new_->invoice_payload ||
- old_->shipping_option_id != new_->shipping_option_id ||
- old_->telegram_payment_charge_id != new_->telegram_payment_charge_id ||
- old_->provider_payment_charge_id != new_->provider_payment_charge_id ||
- ((old_->order_info != nullptr || new_->order_info != nullptr) &&
- (old_->order_info == nullptr || new_->order_info == nullptr || *old_->order_info != *new_->order_info))) {
- need_update = true;
- }
- break;
- }
- case MessageContactRegistered::ID:
- break;
- case MessageExpiredPhoto::ID:
- break;
- case MessageExpiredVideo::ID:
- break;
- case MessageCustomServiceAction::ID: {
- auto old_ = static_cast<const MessageCustomServiceAction *>(old_content.get());
- auto new_ = static_cast<const MessageCustomServiceAction *>(new_content.get());
- if (old_->message != new_->message) {
- need_update = true;
- }
- break;
- }
- case MessageWebsiteConnected::ID: {
- auto old_ = static_cast<const MessageWebsiteConnected *>(old_content.get());
- auto new_ = static_cast<const MessageWebsiteConnected *>(new_content.get());
- if (old_->domain_name != new_->domain_name) {
- need_update = true;
- }
- break;
- }
- case MessageUnsupported::ID:
- break;
- default:
- UNREACHABLE();
- break;
- }
+ merge_message_contents(td_, old_content.get(), new_content.get(), need_message_changed_warning(old_message),
+ dialog_id, need_merge_files, is_content_changed, need_update);
+ }
+ if (need_finish_upload) {
+ // the file is likely to be already merged with a server file, but if not we need to
+ // cancel file upload of the main file to allow next upload with the same file to succeed
+ cancel_upload_file(old_file_id, "update_message_content");
}
if (is_content_changed || need_update) {
- auto old_file_id = get_message_content_file_id(old_content.get());
+ if (is_message_in_dialog) {
+ reregister_message_content(td_, old_content.get(), new_content.get(), {dialog_id, old_message->message_id},
+ "update_message_content");
+ }
old_content = std::move(new_content);
+ old_message->last_edit_pts = 0;
update_message_content_file_id_remote(old_content.get(), old_file_id);
} else {
- update_message_content_file_id_remote(old_content.get(), get_message_content_file_id(new_content.get()));
+ update_message_content_file_id_remote(old_content.get(), get_message_content_any_file_id(new_content.get()));
}
if (is_content_changed && !need_update) {
LOG(INFO) << "Content of " << old_message->message_id << " in " << dialog_id << " has changed";
}
- if (need_update && need_send_update_message_content) {
- send_update_message_content(dialog_id, old_message->message_id, old_content.get(), old_message->date,
- old_message->is_content_secret, "update_message_content");
+ if (need_update) {
+ auto file_ids = get_message_content_file_ids(old_content.get(), td_);
+ if (!file_ids.empty()) {
+ auto file_source_id = get_message_file_source_id(FullMessageId(dialog_id, old_message->message_id));
+ if (file_source_id.is_valid()) {
+ auto search_text = get_message_search_text(old_message);
+ for (auto file_id : file_ids) {
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ send_closure(td_->download_manager_actor_, &DownloadManager::change_search_text, file_view.get_main_file_id(),
+ file_source_id, search_text);
+ }
+ }
+ }
}
- return is_content_changed || need_update;
+ return need_update;
}
MessagesManager::Dialog *MessagesManager::get_dialog_by_message_id(MessageId message_id) {
CHECK(message_id.is_valid() && message_id.is_server());
- auto it = message_id_to_dialog_id_.find(message_id);
- if (it == message_id_to_dialog_id_.end()) {
+ auto dialog_id = message_id_to_dialog_id_.get(message_id);
+ if (dialog_id == DialogId()) {
if (G()->parameters().use_message_db) {
auto r_value =
- G()->td_db()->get_messages_db_sync()->get_message_by_unique_message_id(message_id.get_server_message_id());
+ G()->td_db()->get_message_db_sync()->get_message_by_unique_message_id(message_id.get_server_message_id());
if (r_value.is_ok()) {
- DialogId dialog_id(r_value.ok().first);
- Message *m = on_get_message_from_database(dialog_id, get_dialog_force(dialog_id), r_value.ok().second);
+ Message *m = on_get_message_from_database(r_value.ok(), false, "get_dialog_by_message_id");
if (m != nullptr) {
+ dialog_id = r_value.ok().dialog_id;
CHECK(m->message_id == message_id);
- CHECK(message_id_to_dialog_id_[message_id] == dialog_id);
+ LOG_CHECK(message_id_to_dialog_id_.get(message_id) == dialog_id)
+ << message_id << ' ' << dialog_id << ' ' << message_id_to_dialog_id_.get(message_id) << ' '
+ << m->debug_source;
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
return d;
@@ -22895,140 +37105,225 @@ MessagesManager::Dialog *MessagesManager::get_dialog_by_message_id(MessageId mes
}
}
+ LOG(INFO) << "Can't find the chat by " << message_id;
return nullptr;
}
- return get_dialog(it->second);
+ return get_dialog(dialog_id);
}
-MessageId MessagesManager::get_message_id_by_random_id(Dialog *d, int64 random_id) {
+MessageId MessagesManager::get_message_id_by_random_id(Dialog *d, int64 random_id, const char *source) {
CHECK(d != nullptr);
- CHECK(d->dialog_id.get_type() == DialogType::SecretChat);
if (random_id == 0) {
return MessageId();
}
auto it = d->random_id_to_message_id.find(random_id);
if (it == d->random_id_to_message_id.end()) {
- if (G()->parameters().use_message_db) {
- auto r_value = G()->td_db()->get_messages_db_sync()->get_message_by_random_id(d->dialog_id, random_id);
+ if (G()->parameters().use_message_db && d->dialog_id.get_type() == DialogType::SecretChat) {
+ auto r_value = G()->td_db()->get_message_db_sync()->get_message_by_random_id(d->dialog_id, random_id);
if (r_value.is_ok()) {
- Message *m = on_get_message_from_database(d->dialog_id, d, r_value.ok());
+ debug_add_message_to_dialog_fail_reason_ = "not called";
+ Message *m = on_get_message_from_database(d, r_value.ok(), false, "get_message_id_by_random_id");
if (m != nullptr) {
- CHECK(m->random_id == random_id);
- CHECK(d->random_id_to_message_id[random_id] == m->message_id);
+ LOG_CHECK(m->random_id == random_id)
+ << random_id << " " << m->random_id << " " << d->random_id_to_message_id[random_id] << " "
+ << d->random_id_to_message_id[m->random_id] << " " << m->message_id << " " << source << " "
+ << m->from_database << get_message(d, m->message_id) << " " << m << " "
+ << debug_add_message_to_dialog_fail_reason_;
+ LOG_CHECK(d->random_id_to_message_id.count(random_id))
+ << source << " " << random_id << " " << m->message_id << " " << m->is_failed_to_send << " "
+ << m->is_outgoing << " " << m->from_database << " " << get_message(d, m->message_id) << " " << m << " "
+ << debug_add_message_to_dialog_fail_reason_;
+ LOG_CHECK(d->random_id_to_message_id[random_id] == m->message_id)
+ << source << " " << random_id << " " << d->random_id_to_message_id[random_id] << " " << m->message_id
+ << " " << m->is_failed_to_send << " " << m->is_outgoing << " " << m->from_database << " "
+ << get_message(d, m->message_id) << " " << m << " " << debug_add_message_to_dialog_fail_reason_;
+ LOG(INFO) << "Found " << FullMessageId{d->dialog_id, m->message_id} << " by random_id " << random_id
+ << " from " << source;
return m->message_id;
}
}
}
+ LOG(INFO) << "Found no message by random_id " << random_id << " from " << source;
return MessageId();
}
+ LOG(INFO) << "Found " << FullMessageId{d->dialog_id, it->second} << " by random_id " << random_id << " from "
+ << source;
return it->second;
}
-void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source, bool force_update_dialog_pos) {
- CHECK(dialog_id.is_valid());
- Dialog *d = get_dialog_force(dialog_id);
+void MessagesManager::force_create_dialog(DialogId dialog_id, const char *source, bool expect_no_access,
+ bool force_update_dialog_pos) {
+ LOG_CHECK(dialog_id.is_valid()) << source;
+ LOG_CHECK(is_inited_) << dialog_id << ' ' << source << ' ' << expect_no_access << ' ' << force_update_dialog_pos;
+ Dialog *d = get_dialog_force(dialog_id, source);
if (d == nullptr) {
LOG(INFO) << "Force create " << dialog_id << " from " << source;
if (loaded_dialogs_.count(dialog_id) > 0) {
+ LOG(INFO) << "Skip creation of " << dialog_id << ", because it is being loaded now";
return;
}
- d = add_dialog(dialog_id);
- update_dialog_pos(d, false, "force_create_dialog");
+ d = add_dialog(dialog_id, source);
+ update_dialog_pos(d, source);
- if (have_input_peer(dialog_id, AccessRights::Read)) {
- if (dialog_id.get_type() != DialogType::SecretChat && !d->notification_settings.is_synchronized) {
- // asynchronously preload information about the dialog
- send_get_dialog_query(dialog_id, Auto());
+ if (dialog_id.get_type() == DialogType::SecretChat && !d->notification_settings.is_synchronized &&
+ td_->contacts_manager_->get_secret_chat_state(dialog_id.get_secret_chat_id()) != SecretChatState::Closed) {
+ // secret chat is being created
+ // let's copy notification settings from main chat if available
+ VLOG(notifications) << "Create new secret " << dialog_id << " from " << source;
+ auto secret_chat_id = dialog_id.get_secret_chat_id();
+ {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id);
+ Dialog *user_d = get_dialog_force(DialogId(user_id), source);
+ if (user_d != nullptr && user_d->notification_settings.is_synchronized) {
+ VLOG(notifications) << "Copy notification settings from " << user_d->dialog_id << " to " << dialog_id;
+ auto user_settings = &user_d->notification_settings;
+ auto new_notification_settings = DialogNotificationSettings(
+ user_settings->use_default_mute_until, user_settings->mute_until,
+ dup_notification_sound(user_settings->sound), true /*use_default_show_preview*/, false /*show_preview*/,
+ user_settings->silent_send_message, true, false, true, false);
+ new_notification_settings.is_secret_chat_show_preview_fixed = true;
+ update_dialog_notification_settings(dialog_id, &d->notification_settings,
+ std::move(new_notification_settings));
+ } else {
+ d->notification_settings.is_synchronized = true;
+ }
}
- } else {
+
+ if (G()->parameters().use_message_db && !td_->auth_manager_->is_bot() &&
+ !td_->contacts_manager_->get_secret_chat_is_outbound(secret_chat_id)) {
+ auto notification_group_id = get_dialog_notification_group_id(dialog_id, d->message_notification_group);
+ if (notification_group_id.is_valid()) {
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ LOG(ERROR) << "Found previously created " << d->new_secret_chat_notification_id << " in " << d->dialog_id
+ << ", when creating it from " << source;
+ } else {
+ d->new_secret_chat_notification_id = get_next_notification_id(d, notification_group_id, MessageId());
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ auto date = td_->contacts_manager_->get_secret_chat_date(secret_chat_id);
+ bool is_changed = set_dialog_last_notification(dialog_id, d->message_notification_group, date,
+ d->new_secret_chat_notification_id, "add_new_secret_chat");
+ CHECK(is_changed);
+ VLOG(notifications) << "Create " << d->new_secret_chat_notification_id << " with " << secret_chat_id;
+ auto ringtone_id = get_dialog_notification_ringtone_id(dialog_id, d);
+ send_closure_later(G()->notification_manager(), &NotificationManager::add_notification,
+ notification_group_id, NotificationGroupType::SecretChat, dialog_id, date, dialog_id,
+ false, ringtone_id, 0, d->new_secret_chat_notification_id,
+ create_new_secret_chat_notification(), "add_new_secret_chat_notification");
+ }
+ }
+ }
+ }
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
if (!have_dialog_info(dialog_id)) {
- LOG(ERROR) << "Have no info about " << dialog_id << " received from " << source << ", but forced to create it";
- } else {
- LOG_IF(ERROR, Slice(source) != Slice("message forward info") &&
- Slice(source) != Slice("on_new_callback_query") &&
- Slice(source) != Slice("search public dialog") &&
- Slice(source) != Slice("create new secret chat") && !force_update_dialog_pos)
- << "Have no access to " << dialog_id << " received from " << source << ", but forced to create it";
+ if (expect_no_access && dialog_id.get_type() == DialogType::Channel &&
+ td_->contacts_manager_->have_min_channel(dialog_id.get_channel_id())) {
+ LOG(INFO) << "Created " << dialog_id << " for min-channel from " << source;
+ } else {
+ LOG(ERROR) << "Forced to create unknown " << dialog_id << " from " << source;
+ }
+ } else if (!expect_no_access) {
+ LOG(ERROR) << "Have no access to " << dialog_id << " received from " << source << ", but forced to create it";
}
}
} else if (force_update_dialog_pos) {
- update_dialog_pos(d, false, "force update dialog pos");
+ update_dialog_pos(d, "force update dialog pos");
}
}
-MessagesManager::Dialog *MessagesManager::add_dialog(DialogId dialog_id) {
- LOG(DEBUG) << "Creating " << dialog_id;
+MessagesManager::Dialog *MessagesManager::add_dialog(DialogId dialog_id, const char *source) {
+ LOG(DEBUG) << "Creating " << dialog_id << " from " << source;
CHECK(!have_dialog(dialog_id));
+ LOG_CHECK(dialog_id.is_valid()) << source;
if (G()->parameters().use_message_db) {
// TODO preload dialog asynchronously, remove loading from this function
- LOG(INFO) << "Synchronously load " << dialog_id << " from database";
auto r_value = G()->td_db()->get_dialog_db_sync()->get_dialog(dialog_id);
if (r_value.is_ok()) {
- return add_new_dialog(parse_dialog(dialog_id, r_value.ok()), true);
+ LOG(INFO) << "Synchronously loaded " << dialog_id << " from database from " << source;
+ return add_new_dialog(parse_dialog(dialog_id, r_value.ok(), source), true, source);
}
}
- auto d = make_unique<Dialog>();
- std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), -1);
- d->dialog_id = dialog_id;
+ auto dialog = make_unique<Dialog>();
+ dialog->dialog_id = dialog_id;
+ invalidate_message_indexes(dialog.get());
- return add_new_dialog(std::move(d), false);
+ return add_new_dialog(std::move(dialog), false, source);
}
-MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr<Dialog> &&d, bool is_loaded_from_database) {
+MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr<Dialog> &&dialog, bool is_loaded_from_database,
+ const char *source) {
+ Dialog *d = dialog.get();
auto dialog_id = d->dialog_id;
+ LOG_CHECK(is_inited_) << dialog_id << ' ' << is_loaded_from_database << ' ' << source;
+ LOG_CHECK(!have_dialog(dialog_id)) << dialog_id << ' ' << is_loaded_from_database << ' ' << source;
switch (dialog_id.get_type()) {
case DialogType::User:
+ if (dialog_id == get_my_dialog_id() && d->last_read_inbox_message_id == MessageId::max() &&
+ d->last_read_outbox_message_id == MessageId::max()) {
+ d->last_read_inbox_message_id = d->last_new_message_id;
+ d->last_read_outbox_message_id = d->last_new_message_id;
+ }
+ d->has_bots = dialog_id.get_user_id() != ContactsManager::get_replies_bot_user_id() &&
+ td_->contacts_manager_->is_user_bot(dialog_id.get_user_id());
+ d->is_has_bots_inited = true;
+ d->is_available_reactions_inited = true;
+ break;
case DialogType::Chat:
+ d->is_is_blocked_inited = true;
break;
case DialogType::Channel: {
- auto channel_type = td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id());
- if (channel_type == ChannelType::Broadcast) {
+ if (td_->contacts_manager_->is_broadcast_channel(dialog_id.get_channel_id())) {
d->last_read_outbox_message_id = MessageId::max();
d->is_last_read_outbox_message_id_inited = true;
}
- if (!d->notification_settings.is_synchronized && channel_type == ChannelType::Megagroup) {
- d->notification_settings.mute_until = std::numeric_limits<int32>::max();
- }
auto pts = load_channel_pts(dialog_id);
if (pts > 0) {
d->pts = pts;
if (is_debug_message_op_enabled()) {
- d->debug_message_op.emplace_back(Dialog::MessageOp::SetPts, MessageId(), pts, false, false, false,
- "add_new_dialog");
+ d->debug_message_op.emplace_back(Dialog::MessageOp::SetPts, pts, "add_new_dialog");
}
}
break;
}
case DialogType::SecretChat:
- if (!d->last_new_message_id.is_valid()) {
- LOG(INFO) << "Set " << d->dialog_id << " last new message id in add_new_dialog";
- // TODO use date << MessageId::SERVER_ID_SHIFT;
- d->last_new_message_id = MessageId::min();
- }
- for (auto &first_message_id : d->first_database_message_id_by_index) {
- first_message_id = MessageId::min();
+ if (d->last_new_message_id.get() <= MessageId::min().get()) {
+ LOG(INFO) << "Set " << dialog_id << " last new message in add_new_dialog from " << source;
+ d->last_new_message_id = MessageId::min().get_next_message_id(MessageType::Local);
}
- for (auto &message_count : d->message_count_by_index) {
- if (message_count == -1) {
- message_count = 0;
- }
+
+ if (!d->notification_settings.is_secret_chat_show_preview_fixed) {
+ d->notification_settings.use_default_show_preview = true;
+ d->notification_settings.show_preview = false;
+ d->notification_settings.is_secret_chat_show_preview_fixed = true;
+ on_dialog_updated(dialog_id, "fix secret chat show preview");
}
d->have_full_history = true;
+ d->have_full_history_source = 4;
d->need_restore_reply_markup = false;
- d->notification_settings.is_synchronized = true;
- d->know_can_report_spam = true;
- if (!is_loaded_from_database) {
- d->can_report_spam =
- td_->contacts_manager_->default_can_report_spam_in_secret_chat(dialog_id.get_secret_chat_id());
- }
+ d->is_last_read_inbox_message_id_inited = true;
+ d->is_last_read_outbox_message_id_inited = true;
+ d->is_last_pinned_message_id_inited = true;
+ d->is_theme_name_inited = true;
+ d->is_is_blocked_inited = true;
+ if (!d->is_folder_id_inited && !td_->auth_manager_->is_bot()) {
+ do_set_dialog_folder_id(
+ d, td_->contacts_manager_->get_secret_chat_initial_folder_id(dialog_id.get_secret_chat_id()));
+ }
+ d->message_ttl = MessageTtl(td_->contacts_manager_->get_secret_chat_ttl(dialog_id.get_secret_chat_id()));
+ d->is_message_ttl_inited = true;
+ d->has_bots = td_->contacts_manager_->is_user_bot(
+ td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id()));
+ d->is_has_bots_inited = true;
+ d->is_available_reactions_inited = true;
+
break;
case DialogType::None:
default:
@@ -23037,122 +37332,385 @@ MessagesManager::Dialog *MessagesManager::add_new_dialog(unique_ptr<Dialog> &&d,
if (!is_loaded_from_database) {
on_dialog_updated(dialog_id, "add_new_dialog");
}
+ if (td_->auth_manager_->is_bot()) {
+ d->notification_settings.is_synchronized = true;
+ }
+ if (is_channel_difference_finished_.erase(dialog_id)) {
+ d->is_channel_difference_finished = true;
+ }
unique_ptr<Message> last_database_message = std::move(d->messages);
+ MessageId last_database_message_id = d->last_database_message_id;
+ d->last_database_message_id = MessageId();
int64 order = d->order;
d->order = DEFAULT_ORDER;
int32 last_clear_history_date = d->last_clear_history_date;
MessageId last_clear_history_message_id = d->last_clear_history_message_id;
d->last_clear_history_date = 0;
d->last_clear_history_message_id = MessageId();
+ DialogId default_join_group_call_as_dialog_id = d->default_join_group_call_as_dialog_id;
+ if (default_join_group_call_as_dialog_id != dialog_id &&
+ default_join_group_call_as_dialog_id.get_type() != DialogType::User &&
+ !have_dialog(default_join_group_call_as_dialog_id)) {
+ d->default_join_group_call_as_dialog_id = DialogId();
+ }
+ DialogId default_send_message_as_dialog_id = d->default_send_message_as_dialog_id;
+ bool need_drop_default_send_message_as_dialog_id = d->need_drop_default_send_message_as_dialog_id;
+ if (default_send_message_as_dialog_id != dialog_id &&
+ default_send_message_as_dialog_id.get_type() != DialogType::User &&
+ !have_dialog(default_send_message_as_dialog_id)) {
+ d->need_drop_default_send_message_as_dialog_id = false;
+ d->default_send_message_as_dialog_id = DialogId();
+ }
+
+ if (d->message_notification_group.group_id.is_valid()) {
+ notification_group_id_to_dialog_id_.emplace(d->message_notification_group.group_id, dialog_id);
+ }
+ if (d->mention_notification_group.group_id.is_valid()) {
+ notification_group_id_to_dialog_id_.emplace(d->mention_notification_group.group_id, dialog_id);
+ }
+ if (pending_dialog_group_call_updates_.count(dialog_id) > 0) {
+ auto it = pending_dialog_group_call_updates_.find(dialog_id);
+ bool has_active_group_call = it->second.first;
+ bool is_group_call_empty = it->second.second;
+ pending_dialog_group_call_updates_.erase(it);
+ if (d->has_active_group_call != has_active_group_call || d->is_group_call_empty != is_group_call_empty) {
+ if (!has_active_group_call) {
+ d->active_group_call_id = InputGroupCallId();
+ }
+ d->has_active_group_call = has_active_group_call;
+ d->is_group_call_empty = is_group_call_empty;
+ on_dialog_updated(dialog_id, "pending update_dialog_group_call");
+ }
+ }
+ fix_pending_join_requests(dialog_id, d->pending_join_request_count, d->pending_join_request_user_ids);
if (!is_loaded_from_database) {
CHECK(order == DEFAULT_ORDER);
CHECK(last_database_message == nullptr);
}
- auto dialog_it = dialogs_.emplace(dialog_id, std::move(d)).first;
+ CHECK(!have_dialog(dialog_id));
+ dialogs_.set(dialog_id, std::move(dialog));
+
+ CHECK(!being_added_new_dialog_id_.is_valid());
+ being_added_new_dialog_id_ = dialog_id;
loaded_dialogs_.erase(dialog_id);
- Dialog *dialog = dialog_it->second.get();
- send_update_chat(dialog);
+ fix_dialog_action_bar(d, d->action_bar.get());
- fix_new_dialog(dialog, std::move(last_database_message), order, last_clear_history_date,
- last_clear_history_message_id);
+ send_update_new_chat(d);
- return dialog;
+ being_added_new_dialog_id_ = DialogId();
+
+ LOG_CHECK(d->messages == nullptr) << d->messages->message_id << ' ' << d->last_message_id << ' '
+ << d->last_database_message_id << ' '
+ << d->debug_set_dialog_last_database_message_id << ' ' << d->messages->debug_source;
+
+ fix_new_dialog(d, std::move(last_database_message), last_database_message_id, order, last_clear_history_date,
+ last_clear_history_message_id, default_join_group_call_as_dialog_id, default_send_message_as_dialog_id,
+ need_drop_default_send_message_as_dialog_id, is_loaded_from_database, source);
+
+ return d;
}
-void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr<Message> &&last_database_message, int64 order,
- int32 last_clear_history_date, MessageId last_clear_history_message_id) {
+void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr<Message> &&last_database_message,
+ MessageId last_database_message_id, int64 order, int32 last_clear_history_date,
+ MessageId last_clear_history_message_id,
+ DialogId default_join_group_call_as_dialog_id,
+ DialogId default_send_message_as_dialog_id,
+ bool need_drop_default_send_message_as_dialog_id, bool is_loaded_from_database,
+ const char *source) {
CHECK(d != nullptr);
auto dialog_id = d->dialog_id;
+ auto dialog_type = dialog_id.get_type();
+
+ if (!td_->auth_manager_->is_bot() && dialog_type == DialogType::SecretChat) {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (user_id.is_valid()) {
+ force_create_dialog(DialogId(user_id), "add chat with user to load/store action_bar and is_blocked");
+
+ Dialog *user_d = get_dialog_force(DialogId(user_id), "fix_new_dialog");
+ if (user_d != nullptr && d->is_blocked != user_d->is_blocked) {
+ set_dialog_is_blocked(d, user_d->is_blocked);
+ }
+ }
+ }
- if (d->notification_settings.mute_until <= G()->unix_time()) {
+ if (d->is_empty && !d->have_full_history) {
+ LOG(ERROR) << "Drop invalid flag is_empty";
+ d->is_empty = false;
+ }
+
+ if (being_added_dialog_id_ != dialog_id && !td_->auth_manager_->is_bot() && !is_dialog_inited(d) &&
+ dialog_type != DialogType::SecretChat && have_input_peer(dialog_id, AccessRights::Read)) {
+ // asynchronously get dialog from the server
+ send_get_dialog_query(dialog_id, Auto(), 0, "fix_new_dialog 20");
+ }
+
+ if (being_added_dialog_id_ != dialog_id && !d->is_is_blocked_inited && !td_->auth_manager_->is_bot()) {
+ // asynchronously get is_blocked from the server
+ reload_dialog_info_full(dialog_id, "fix_new_dialog init is_blocked");
+ } else if (being_added_dialog_id_ != dialog_id && !d->is_has_bots_inited && !td_->auth_manager_->is_bot()) {
+ // asynchronously get has_bots from the server
+ reload_dialog_info_full(dialog_id, "fix_new_dialog init has_bots");
+ } else if (being_added_dialog_id_ != dialog_id && !d->is_theme_name_inited && !td_->auth_manager_->is_bot()) {
+ // asynchronously get dialog theme identifier from the server
+ reload_dialog_info_full(dialog_id, "fix_new_dialog init theme_name");
+ } else if (being_added_dialog_id_ != dialog_id && !d->is_last_pinned_message_id_inited &&
+ !td_->auth_manager_->is_bot()) {
+ // asynchronously get dialog pinned message from the server
+ get_dialog_pinned_message(dialog_id, Auto());
+ } else if (being_added_dialog_id_ != dialog_id && !d->is_folder_id_inited && !td_->auth_manager_->is_bot() &&
+ order != DEFAULT_ORDER) {
+ // asynchronously get dialog folder identifier from the server
+ reload_dialog_info_full(dialog_id, "fix_new_dialog init folder_id");
+ } else if (!d->is_message_ttl_inited && !td_->auth_manager_->is_bot() &&
+ have_input_peer(dialog_id, AccessRights::Write)) {
+ // asynchronously get dialog message TTL from the server
+ reload_dialog_info_full(dialog_id, "fix_new_dialog init message_ttl");
+ } else if (being_added_dialog_id_ != dialog_id && !d->is_available_reactions_inited &&
+ !td_->auth_manager_->is_bot()) {
+ // asynchronously get dialog available reactions from the server
+ reload_dialog_info_full(dialog_id, "fix_new_dialog init available_reactions");
+ }
+ if ((!d->know_action_bar || d->need_repair_action_bar) && !td_->auth_manager_->is_bot() &&
+ dialog_type != DialogType::SecretChat && dialog_id != get_my_dialog_id() &&
+ have_input_peer(dialog_id, AccessRights::Read)) {
+ // asynchronously get action bar from the server
+ reget_dialog_action_bar(dialog_id, "fix_new_dialog", false);
+ }
+ if (d->has_active_group_call && !d->active_group_call_id.is_valid() && !td_->auth_manager_->is_bot()) {
+ repair_dialog_active_group_call_id(dialog_id);
+ }
+
+ if (d->notification_settings.is_synchronized && !d->notification_settings.is_use_default_fixed &&
+ have_input_peer(dialog_id, AccessRights::Read) && !td_->auth_manager_->is_bot()) {
+ LOG(INFO) << "Reget notification settings of " << dialog_id;
+ if (dialog_type == DialogType::SecretChat) {
+ d->notification_settings.is_use_default_fixed = true;
+ on_dialog_updated(dialog_id, "reget notification settings");
+ } else {
+ td_->notification_settings_manager_->send_get_dialog_notification_settings_query(dialog_id, Promise<Unit>());
+ }
+ }
+ if (td_->auth_manager_->is_bot() || d->notification_settings.use_default_mute_until ||
+ d->notification_settings.mute_until <= G()->unix_time()) {
d->notification_settings.mute_until = 0;
} else {
- update_dialog_unmute_timeout(d, -1, d->notification_settings.mute_until);
+ schedule_dialog_unmute(dialog_id, false, d->notification_settings.mute_until);
+ }
+ if (d->pinned_message_notification_message_id.is_valid()) {
+ auto pinned_message_id = d->pinned_message_notification_message_id;
+ if (!d->mention_notification_group.group_id.is_valid()) {
+ LOG(ERROR) << "Have pinned message notification in " << pinned_message_id << " in " << dialog_id
+ << ", but there is no mention notification group";
+ d->pinned_message_notification_message_id = MessageId();
+ on_dialog_updated(dialog_id, "fix pinned message notification");
+ } else if (is_dialog_pinned_message_notifications_disabled(d) ||
+ pinned_message_id <= d->last_read_inbox_message_id ||
+ pinned_message_id <= d->mention_notification_group.max_removed_message_id) {
+ VLOG(notifications) << "Remove disabled pinned message notification in " << pinned_message_id << " in "
+ << dialog_id;
+ send_closure_later(G()->notification_manager(), &NotificationManager::remove_temporary_notification_by_message_id,
+ d->mention_notification_group.group_id, pinned_message_id, true,
+ "fix pinned message notification");
+ d->pinned_message_notification_message_id = MessageId();
+ on_dialog_updated(dialog_id, "fix pinned message notification 2");
+ }
+ }
+ if (d->new_secret_chat_notification_id.is_valid()) {
+ auto &group_info = d->message_notification_group;
+ if (d->new_secret_chat_notification_id.get() <= group_info.max_removed_notification_id.get() ||
+ (group_info.last_notification_date == 0 && group_info.max_removed_notification_id.get() == 0)) {
+ VLOG(notifications) << "Fix removing new secret chat " << d->new_secret_chat_notification_id << " in "
+ << dialog_id;
+ d->new_secret_chat_notification_id = NotificationId();
+ on_dialog_updated(dialog_id, "fix new secret chat notification identifier");
+ }
+ }
+
+ {
+ auto it = pending_add_dialog_last_database_message_dependent_dialogs_.find(dialog_id);
+ if (it != pending_add_dialog_last_database_message_dependent_dialogs_.end()) {
+ auto pending_dialog_ids = std::move(it->second);
+ pending_add_dialog_last_database_message_dependent_dialogs_.erase(it);
+
+ for (auto &pending_dialog_id : pending_dialog_ids) {
+ auto &counter_message = pending_add_dialog_last_database_message_[pending_dialog_id];
+ CHECK(counter_message.first > 0);
+ counter_message.first--;
+ if (counter_message.first == 0) {
+ LOG(INFO) << "Add postponed last database message in " << pending_dialog_id;
+ add_dialog_last_database_message(get_dialog(pending_dialog_id), std::move(counter_message.second));
+ pending_add_dialog_last_database_message_.erase(pending_dialog_id);
+ }
+ }
+ }
}
- auto pending_it = pending_add_dialog_last_database_message_dependent_dialogs_.find(dialog_id);
- if (pending_it != pending_add_dialog_last_database_message_dependent_dialogs_.end()) {
- auto pending_dialogs = std::move(pending_it->second);
- pending_add_dialog_last_database_message_dependent_dialogs_.erase(pending_it);
+ {
+ auto it = pending_add_default_join_group_call_as_dialog_id_.find(dialog_id);
+ if (it != pending_add_default_join_group_call_as_dialog_id_.end()) {
+ auto pending_dialog_ids = std::move(it->second);
+ pending_add_default_join_group_call_as_dialog_id_.erase(it);
- for (auto &pending_dialog_id : pending_dialogs) {
- auto &counter_message = pending_add_dialog_last_database_message_[pending_dialog_id];
- CHECK(counter_message.first > 0);
- counter_message.first--;
- if (counter_message.first == 0) {
- add_dialog_last_database_message(get_dialog(pending_dialog_id), std::move(counter_message.second));
- pending_add_dialog_last_database_message_.erase(pending_dialog_id);
+ for (auto &pending_dialog_id : pending_dialog_ids) {
+ on_update_dialog_default_join_group_call_as_dialog_id(pending_dialog_id, dialog_id, false);
}
}
}
- set_dialog_last_clear_history_date(d, last_clear_history_date, last_clear_history_message_id, "add_new_dialog");
+ {
+ auto it = pending_add_default_send_message_as_dialog_id_.find(dialog_id);
+ if (it != pending_add_default_send_message_as_dialog_id_.end()) {
+ auto pending_dialog_ids = std::move(it->second);
+ pending_add_default_send_message_as_dialog_id_.erase(it);
- set_dialog_order(d, order, false);
+ for (auto &pending_dialog_id : pending_dialog_ids) {
+ Dialog *pending_d = get_dialog(pending_dialog_id.first);
+ CHECK(pending_d != nullptr);
+ if (!pending_d->default_send_message_as_dialog_id.is_valid()) {
+ LOG(INFO) << "Set postponed message sender in " << pending_dialog_id << " to " << dialog_id;
+ pending_d->need_drop_default_send_message_as_dialog_id = pending_dialog_id.second;
+ pending_d->default_send_message_as_dialog_id = dialog_id;
+ send_update_chat_message_sender(pending_d);
+ }
+ }
+ }
+ }
+
+ if (dialog_id != being_added_dialog_id_) {
+ set_dialog_last_clear_history_date(d, last_clear_history_date, last_clear_history_message_id, "fix_new_dialog 8",
+ is_loaded_from_database);
- if (dialog_id.get_type() != DialogType::SecretChat && d->last_new_message_id.is_valid() &&
+ set_dialog_order(d, order, false, is_loaded_from_database, "fix_new_dialog 9");
+ }
+
+ if (dialog_type != DialogType::SecretChat && d->last_new_message_id.is_valid() &&
!d->last_new_message_id.is_server()) {
// fix wrong last_new_message_id
- d->last_new_message_id = MessageId(d->last_new_message_id.get() & ~MessageId::FULL_TYPE_MASK);
+ d->last_new_message_id = d->last_new_message_id.get_prev_server_message_id();
+ on_dialog_updated(dialog_id, "fix_new_dialog 10");
}
+ bool need_get_history = true;
+
// add last database message to dialog
+ MessageId last_message_id;
if (last_database_message != nullptr) {
- auto message_id = last_database_message->message_id;
- if (!d->first_database_message_id.is_valid()) {
- LOG(ERROR) << "Bugfixing wrong first_database_message_id to " << message_id << " in " << dialog_id;
- set_dialog_first_database_message_id(d, message_id, "add_new_dialog");
- }
- set_dialog_last_database_message_id(d, message_id, "add_new_dialog");
- if ((message_id.is_server() || dialog_id.get_type() == DialogType::SecretChat) &&
+ need_get_history = false;
+ last_message_id = last_database_message->message_id;
+ } else if (last_database_message_id.is_valid()) {
+ last_message_id = last_database_message_id;
+ }
+
+ if (!d->last_new_message_id.is_valid() && d->last_new_message_id != MessageId()) {
+ LOG(ERROR) << "Drop invalid last_new_message_id " << d->last_new_message_id << " in " << dialog_id;
+ d->last_new_message_id = MessageId();
+ }
+ if (last_message_id.is_valid()) {
+ if ((last_message_id.is_server() || dialog_type == DialogType::SecretChat) && !last_message_id.is_yet_unsent() &&
!d->last_new_message_id.is_valid()) {
- // is it even possible?
- LOG(ERROR) << "Bugfixing wrong last_new_message_id to " << message_id << " in " << dialog_id;
- set_dialog_last_new_message_id(d, message_id, "add_new_dialog");
+ LOG(ERROR) << "Bugfixing wrong last_new_message_id to " << last_message_id << " in " << dialog_id;
+ // must be called before set_dialog_first_database_message_id and set_dialog_last_database_message_id
+ set_dialog_last_new_message_id(d, last_message_id, "fix_new_dialog 1");
+ }
+ if (!d->first_database_message_id.is_valid() || d->first_database_message_id > last_message_id) {
+ LOG(ERROR) << "Bugfixing wrong first_database_message_id from " << d->first_database_message_id << " to "
+ << last_message_id << " in " << dialog_id;
+ set_dialog_first_database_message_id(d, last_message_id, "fix_new_dialog 2");
+ }
+ set_dialog_last_database_message_id(d, last_message_id, "fix_new_dialog 3", is_loaded_from_database);
+ } else if (d->first_database_message_id.is_valid()) {
+ // ensure that first_database_message_id <= last_database_message_id
+ if (d->first_database_message_id <= d->last_new_message_id) {
+ set_dialog_last_database_message_id(d, d->last_new_message_id, "fix_new_dialog 4");
+ } else {
+ // can't fix last_database_message_id, drop first_database_message_id; it shouldn't happen anyway
+ set_dialog_first_database_message_id(d, MessageId(), "fix_new_dialog 5");
}
+ }
+ d->debug_first_database_message_id = d->first_database_message_id;
+ d->debug_last_database_message_id = d->last_database_message_id;
+ d->debug_last_new_message_id = d->last_new_message_id;
+
+ if (last_database_message != nullptr) {
+ Dependencies dependencies;
+ add_message_dependencies(dependencies, last_database_message.get());
int32 dependent_dialog_count = 0;
- if (last_database_message->forward_info != nullptr) {
- auto other_dialog_id = last_database_message->forward_info->dialog_id;
- if (other_dialog_id.is_valid() && !have_dialog(other_dialog_id)) {
- LOG(INFO) << "Postpone adding of last message in " << dialog_id << " because of cyclic dependency with "
- << other_dialog_id;
- pending_add_dialog_last_database_message_dependent_dialogs_[other_dialog_id].push_back(dialog_id);
- dependent_dialog_count++;
- }
- other_dialog_id = last_database_message->forward_info->from_dialog_id;
+ for (const auto &other_dialog_id : dependencies.get_dialog_ids()) {
if (other_dialog_id.is_valid() && !have_dialog(other_dialog_id)) {
LOG(INFO) << "Postpone adding of last message in " << dialog_id << " because of cyclic dependency with "
<< other_dialog_id;
pending_add_dialog_last_database_message_dependent_dialogs_[other_dialog_id].push_back(dialog_id);
dependent_dialog_count++;
}
- }
+ };
+ auto last_message_date = last_database_message->date;
if (dependent_dialog_count == 0) {
- add_dialog_last_database_message(d, std::move(last_database_message));
+ if (!add_dialog_last_database_message(d, std::move(last_database_message))) {
+ // failed to add last message; keep the current position and get history from the database
+ d->pending_last_message_date = last_message_date;
+ d->pending_last_message_id = last_message_id;
+ }
} else {
- // can't add message immediately, because needs to notify first about adding of dependent dialogs
+ // can't add message immediately, because need to notify first about adding of dependent dialogs
+ d->pending_last_message_date = last_message_date;
+ d->pending_last_message_id = last_message_id;
pending_add_dialog_last_database_message_[dialog_id] = {dependent_dialog_count, std::move(last_database_message)};
}
+ } else if (last_database_message_id.is_valid()) {
+ auto date = DialogDate(order, dialog_id).get_date();
+ if (date < MIN_PINNED_DIALOG_DATE) {
+ d->pending_last_message_date = date;
+ d->pending_last_message_id = last_message_id;
+ }
}
- switch (dialog_id.get_type()) {
+ if (default_join_group_call_as_dialog_id != d->default_join_group_call_as_dialog_id) {
+ CHECK(default_join_group_call_as_dialog_id.is_valid());
+ CHECK(default_join_group_call_as_dialog_id.get_type() != DialogType::User);
+ CHECK(!d->default_join_group_call_as_dialog_id.is_valid());
+ if (!have_dialog(default_join_group_call_as_dialog_id)) {
+ LOG(INFO) << "Postpone adding of default join voice chat as " << default_join_group_call_as_dialog_id << " in "
+ << dialog_id;
+ pending_add_default_join_group_call_as_dialog_id_[default_join_group_call_as_dialog_id].push_back(dialog_id);
+ } else {
+ on_update_dialog_default_join_group_call_as_dialog_id(dialog_id, default_join_group_call_as_dialog_id, false);
+ }
+ }
+
+ if (default_send_message_as_dialog_id != d->default_send_message_as_dialog_id) {
+ CHECK(default_send_message_as_dialog_id.is_valid());
+ CHECK(default_send_message_as_dialog_id.get_type() != DialogType::User);
+ CHECK(!d->default_send_message_as_dialog_id.is_valid());
+ if (!have_dialog(default_send_message_as_dialog_id)) {
+ LOG(INFO) << "Postpone setting of message sender " << default_send_message_as_dialog_id << " in " << dialog_id;
+ pending_add_default_send_message_as_dialog_id_[default_send_message_as_dialog_id].emplace_back(
+ dialog_id, need_drop_default_send_message_as_dialog_id);
+ } else {
+ LOG(INFO) << "Set message sender in " << dialog_id << " to " << default_send_message_as_dialog_id;
+ d->need_drop_default_send_message_as_dialog_id = need_drop_default_send_message_as_dialog_id;
+ d->default_send_message_as_dialog_id = default_send_message_as_dialog_id;
+ send_update_chat_message_sender(d);
+ }
+ }
+
+ switch (dialog_type) {
case DialogType::User:
break;
case DialogType::Chat:
- if (d->last_read_inbox_message_id.get() < d->last_read_outbox_message_id.get()) {
- LOG(INFO) << "Have last read outbox message " << d->last_read_outbox_message_id << " in " << dialog_id
+ if (d->last_read_inbox_message_id < d->last_read_outbox_message_id) {
+ LOG(INFO) << "Last read outbox message is " << d->last_read_outbox_message_id << " in " << dialog_id
<< ", but last read inbox message is " << d->last_read_inbox_message_id;
// can't fix last_read_inbox_message_id by last_read_outbox_message_id because last_read_outbox_message_id is
- // just a message id not less than last read outgoing message and less than first unread outgoing message, so
- // it may not point to the outgoing message
- // read_history_inbox(dialog_id, d->last_read_outbox_message_id, d->server_unread_count, "add_new_dialog");
+ // just a message identifier not less than an identifier of last read outgoing message and less than
+ // an identifier of first unread outgoing message, so it may not point to the outgoing message
+ // read_history_inbox(dialog_id, d->last_read_outbox_message_id, d->server_unread_count, "fix_new_dialog 6");
}
break;
case DialogType::Channel:
@@ -23164,9 +37722,6 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr<Message> &&last_datab
UNREACHABLE();
}
- update_dialogs_hints(d);
-
- bool need_get_history = false;
if (d->delete_last_message_date != 0) {
if (d->last_message_id.is_valid()) {
LOG(ERROR) << "Last " << d->deleted_last_message_id << " in " << dialog_id << " was deleted at "
@@ -23179,51 +37734,144 @@ void MessagesManager::fix_new_dialog(Dialog *d, unique_ptr<Message> &&last_datab
need_get_history = true;
}
}
- if (!d->last_database_message_id.is_valid()) {
- need_get_history = true;
+
+ if (!G()->parameters().use_message_db) {
+ d->has_loaded_scheduled_messages_from_database = true;
}
- if (need_get_history && !td_->auth_manager_->is_bot() && have_input_peer(dialog_id, AccessRights::Read) &&
- d->order != DEFAULT_ORDER) {
- get_history_from_the_end(dialog_id, true, false, Auto());
+ if (dialog_id != being_added_dialog_id_) {
+ update_dialog_pos(d, "fix_new_dialog 7", true, is_loaded_from_database);
+ }
+ if (is_loaded_from_database && d->order != order && order < MAX_ORDINARY_DIALOG_ORDER &&
+ !td_->contacts_manager_->is_dialog_info_received_from_server(dialog_id) && !d->had_last_yet_unsent_message &&
+ !td_->auth_manager_->is_bot()) {
+ LOG(ERROR) << dialog_id << " has order " << d->order << " instead of saved to database order " << order;
+ }
+
+ LOG(INFO) << "Loaded " << dialog_id << " with last new " << d->last_new_message_id << ", first database "
+ << d->first_database_message_id << ", last database " << d->last_database_message_id << ", last "
+ << d->last_message_id << " with order " << d->order;
+ VLOG(notifications) << "Have " << dialog_id << " with message " << d->message_notification_group.group_id
+ << " with last " << d->message_notification_group.last_notification_id << " sent at "
+ << d->message_notification_group.last_notification_date << ", max removed "
+ << d->message_notification_group.max_removed_notification_id << "/"
+ << d->message_notification_group.max_removed_message_id << " and new secret chat "
+ << d->new_secret_chat_notification_id;
+ VLOG(notifications) << "Have " << dialog_id << " with mention " << d->mention_notification_group.group_id
+ << " with last " << d->mention_notification_group.last_notification_id << " sent at "
+ << d->mention_notification_group.last_notification_date << ", max removed "
+ << d->mention_notification_group.max_removed_notification_id << "/"
+ << d->mention_notification_group.max_removed_message_id << " and pinned "
+ << d->pinned_message_notification_message_id;
+ VLOG(notifications) << "In " << dialog_id << " have last_read_inbox_message_id = " << d->last_read_inbox_message_id
+ << ", last_new_message_id = " << d->last_new_message_id
+ << ", max_notification_message_id = " << d->max_notification_message_id;
+
+ if (d->messages != nullptr) {
+ if (d->messages->message_id != last_message_id || d->messages->left != nullptr || d->messages->right != nullptr) {
+ auto common_data =
+ PSTRING() << ' ' << last_message_id << ' ' << d->last_message_id << ' ' << d->last_database_message_id << ' '
+ << d->debug_set_dialog_last_database_message_id << ' ' << d->messages->debug_source << ' '
+ << is_loaded_from_database << ' ' << source << ' ' << being_added_dialog_id_ << ' '
+ << being_added_new_dialog_id_ << ' ' << dialog_id << ' ' << d->is_channel_difference_finished << ' '
+ << debug_last_get_channel_difference_dialog_id_ << ' ' << debug_last_get_channel_difference_source_
+ << ' ' << G()->parameters().use_message_db;
+ LOG_CHECK(d->messages->message_id == last_message_id) << d->messages->message_id << common_data;
+ LOG_CHECK(d->messages->left == nullptr)
+ << d->messages->left->message_id << ' ' << d->messages->message_id << ' ' << d->messages->left->message_id
+ << ' ' << d->messages->left->debug_source << common_data;
+ LOG_CHECK(d->messages->right == nullptr)
+ << d->messages->right->message_id << ' ' << d->messages->message_id << ' ' << d->messages->right->message_id
+ << ' ' << d->messages->right->debug_source << common_data;
+ }
+ }
+
+ // must be after update_dialog_pos, because uses d->order
+ // must be after checks that dialog has at most one message, because read_history_inbox can load
+ // pinned message to remove its notification
+ if (d->pending_read_channel_inbox_pts != 0 && !td_->auth_manager_->is_bot() &&
+ have_input_peer(dialog_id, AccessRights::Read) && need_unread_counter(d->order)) {
+ if (d->pts == d->pending_read_channel_inbox_pts) {
+ d->pending_read_channel_inbox_pts = 0;
+ read_history_inbox(dialog_id, d->pending_read_channel_inbox_max_message_id,
+ d->pending_read_channel_inbox_server_unread_count, "fix_new_dialog 12");
+ on_dialog_updated(dialog_id, "fix_new_dialog 13");
+ } else if (d->pts > d->pending_read_channel_inbox_pts) {
+ d->need_repair_channel_server_unread_count = true;
+ d->pending_read_channel_inbox_pts = 0;
+ on_dialog_updated(dialog_id, "fix_new_dialog 14");
+ } else {
+ channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
+ }
+ } else {
+ d->pending_read_channel_inbox_pts = 0;
+ }
+ if (need_get_history && !td_->auth_manager_->is_bot() && dialog_id != being_added_dialog_id_ &&
+ dialog_id != being_added_by_new_message_dialog_id_ && have_input_peer(dialog_id, AccessRights::Read) &&
+ (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) {
+ get_history_from_the_end_impl(d, true, false, Auto(), "fix_new_dialog");
+ }
+ if (d->need_repair_server_unread_count && need_unread_counter(d->order)) {
+ CHECK(dialog_type != DialogType::SecretChat);
+ repair_server_unread_count(dialog_id, d->server_unread_count, "fix_new_dialog");
+ }
+ if (d->need_repair_channel_server_unread_count) {
+ repair_channel_server_unread_count(d);
}
}
-void MessagesManager::add_dialog_last_database_message(Dialog *d, unique_ptr<Message> &&last_database_message) {
+bool MessagesManager::add_dialog_last_database_message(Dialog *d, unique_ptr<Message> &&last_database_message) {
CHECK(d != nullptr);
CHECK(last_database_message != nullptr);
CHECK(last_database_message->left == nullptr);
CHECK(last_database_message->right == nullptr);
+ auto dialog_id = d->dialog_id;
auto message_id = last_database_message->message_id;
- CHECK(d->last_database_message_id == message_id) << message_id << " " << d->last_database_message_id;
-
- if (!have_input_peer(d->dialog_id, AccessRights::Read)) {
- // do not add last message to inaccessible dialog
- return;
- }
+ CHECK(message_id.is_valid());
+ LOG_CHECK(d->last_database_message_id == message_id)
+ << message_id << " " << d->last_database_message_id << " " << d->debug_set_dialog_last_database_message_id;
- bool need_update = false;
bool need_update_dialog_pos = false;
- last_database_message->have_previous = false;
- last_database_message->have_next = false;
- last_database_message->from_database = true;
- Message *m = add_message_to_dialog(d, std::move(last_database_message), false, &need_update, &need_update_dialog_pos,
- "add_dialog_last_database_message");
+ const Message *m = nullptr;
+ if (have_input_peer(dialog_id, AccessRights::Read)) {
+ bool need_update = false;
+ last_database_message->have_previous = false;
+ last_database_message->have_next = false;
+ last_database_message->from_database = true;
+ m = add_message_to_dialog(d, std::move(last_database_message), false, &need_update, &need_update_dialog_pos,
+ "add_dialog_last_database_message 1");
+ if (need_update_dialog_pos) {
+ LOG(ERROR) << "Need to update pos in " << dialog_id;
+ }
+ }
if (m != nullptr) {
- set_dialog_last_message_id(d, message_id, "add_new_dialog");
- send_update_chat_last_message(d, "add_new_dialog");
+ set_dialog_last_message_id(d, m->message_id, "add_dialog_last_database_message 2", m);
+ send_update_chat_last_message(d, "add_dialog_last_database_message 3");
+ } else {
+ if (d->pending_last_message_date != 0) {
+ d->pending_last_message_date = 0;
+ d->pending_last_message_id = MessageId();
+ need_update_dialog_pos = true;
+ }
+ on_dialog_updated(dialog_id, "add_dialog_last_database_message 4"); // resave without last database message
+
+ if (!td_->auth_manager_->is_bot() && dialog_id != being_added_dialog_id_ &&
+ dialog_id != being_added_by_new_message_dialog_id_ && have_input_peer(dialog_id, AccessRights::Read) &&
+ (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) {
+ get_history_from_the_end_impl(d, true, false, Auto(), "add_dialog_last_database_message 5");
+ }
}
if (need_update_dialog_pos) {
- LOG(ERROR) << "Update pos in " << d->dialog_id;
- update_dialog_pos(d, false, "add_new_dialog");
+ update_dialog_pos(d, "add_dialog_last_database_message 6");
}
+ return m != nullptr;
}
void MessagesManager::update_dialogs_hints(const Dialog *d) {
if (!td_->auth_manager_->is_bot() && d->order != DEFAULT_ORDER) {
- dialogs_hints_.add(-d->dialog_id.get(), get_dialog_title(d->dialog_id) + ' ' + get_dialog_username(d->dialog_id));
+ dialogs_hints_.add(-d->dialog_id.get(), td_->contacts_manager_->get_dialog_search_text(d->dialog_id));
}
}
@@ -23232,71 +37880,132 @@ void MessagesManager::update_dialogs_hints_rating(const Dialog *d) {
return;
}
if (d->order == DEFAULT_ORDER) {
+ LOG(INFO) << "Remove " << d->dialog_id << " from chats search";
dialogs_hints_.remove(-d->dialog_id.get());
} else {
- dialogs_hints_.set_rating(-d->dialog_id.get(), -d->order);
+ LOG(INFO) << "Change position of " << d->dialog_id << " in chats search";
+ dialogs_hints_.set_rating(-d->dialog_id.get(), -get_dialog_base_order(d));
}
}
int64 MessagesManager::get_dialog_order(MessageId message_id, int32 message_date) {
- return (static_cast<int64>(message_date) << 32) + narrow_cast<int32>(message_id.get() >> MessageId::SERVER_ID_SHIFT);
+ CHECK(!message_id.is_scheduled());
+ return (static_cast<int64>(message_date) << 32) +
+ message_id.get_prev_server_message_id().get_server_message_id().get();
}
-int64 MessagesManager::get_next_pinned_dialog_order() {
- if (current_pinned_dialog_order_ == DEFAULT_ORDER) {
- string res_str = G()->td_db()->get_binlog_pmc()->get("dialog_pinned_current_order");
- if (res_str.empty()) {
- current_pinned_dialog_order_ = static_cast<int64>(MIN_PINNED_DIALOG_DATE) << 32;
- } else {
- current_pinned_dialog_order_ = to_integer<int64>(res_str);
+bool MessagesManager::is_dialog_sponsored(const Dialog *d) const {
+ return d->order == DEFAULT_ORDER && d->dialog_id == sponsored_dialog_id_;
+}
+
+int64 MessagesManager::get_dialog_base_order(const Dialog *d) const {
+ if (td_->auth_manager_->is_bot()) {
+ return 0; // to not call get_dialog_list
+ }
+ if (is_dialog_sponsored(d)) {
+ return SPONSORED_DIALOG_ORDER;
+ }
+ if (d->order == DEFAULT_ORDER) {
+ return 0;
+ }
+ auto pinned_order = get_dialog_pinned_order(DialogListId(FolderId::main()), d->dialog_id);
+ if (pinned_order != DEFAULT_ORDER) {
+ return pinned_order;
+ }
+ return d->order;
+}
+
+int64 MessagesManager::get_dialog_private_order(const DialogList *list, const Dialog *d) const {
+ if (list == nullptr || td_->auth_manager_->is_bot()) {
+ return 0;
+ }
+
+ if (is_dialog_sponsored(d) && list->dialog_list_id == DialogListId(FolderId::main())) {
+ return SPONSORED_DIALOG_ORDER;
+ }
+ if (d->order == DEFAULT_ORDER) {
+ return 0;
+ }
+ auto pinned_order = get_dialog_pinned_order(list, d->dialog_id);
+ if (pinned_order != DEFAULT_ORDER) {
+ return pinned_order;
+ }
+ return d->order;
+}
+
+td_api::object_ptr<td_api::chatPosition> MessagesManager::get_chat_position_object(DialogListId dialog_list_id,
+ const Dialog *d) const {
+ if (td_->auth_manager_->is_bot()) {
+ return nullptr;
+ }
+
+ auto *list = get_dialog_list(dialog_list_id);
+ if (list == nullptr) {
+ return nullptr;
+ }
+
+ auto position = get_dialog_position_in_list(list, d);
+ if (position.public_order == 0) {
+ return nullptr;
+ }
+
+ auto chat_source = position.is_sponsored ? sponsored_dialog_source_.get_chat_source_object() : nullptr;
+ return td_api::make_object<td_api::chatPosition>(dialog_list_id.get_chat_list_object(), position.public_order,
+ position.is_pinned, std::move(chat_source));
+}
+
+vector<td_api::object_ptr<td_api::chatPosition>> MessagesManager::get_chat_positions_object(const Dialog *d) const {
+ vector<td_api::object_ptr<td_api::chatPosition>> positions;
+ if (!td_->auth_manager_->is_bot()) {
+ for (auto dialog_list_id : get_dialog_list_ids(d)) {
+ auto position = get_chat_position_object(dialog_list_id, d);
+ if (position != nullptr) {
+ positions.push_back(std::move(position));
+ }
+ }
+ if (is_dialog_sponsored(d)) {
+ CHECK(positions.empty());
+ positions.push_back(get_chat_position_object(DialogListId(FolderId::main()), d));
}
}
- CHECK(current_pinned_dialog_order_ != DEFAULT_ORDER);
+ return positions;
+}
+int64 MessagesManager::get_next_pinned_dialog_order() {
current_pinned_dialog_order_++;
- G()->td_db()->get_binlog_pmc()->set("dialog_pinned_current_order", to_string(current_pinned_dialog_order_));
LOG(INFO) << "Assign pinned_order = " << current_pinned_dialog_order_;
return current_pinned_dialog_order_;
}
-void MessagesManager::update_dialog_pos(Dialog *d, bool remove_from_dialog_list, const char *source,
- bool need_send_update_chat_order) {
- CHECK(d != nullptr);
- LOG(INFO) << "Trying to update " << d->dialog_id << " order from " << source;
- auto dialog_type = d->dialog_id.get_type();
-
- switch (dialog_type) {
+bool MessagesManager::is_removed_from_dialog_list(const Dialog *d) const {
+ switch (d->dialog_id.get_type()) {
case DialogType::User:
break;
- case DialogType::Chat: {
- auto chat_id = d->dialog_id.get_chat_id();
- if (!td_->contacts_manager_->get_chat_is_active(chat_id)) {
- remove_from_dialog_list = true;
- }
- break;
- }
- case DialogType::Channel: {
- auto channel_id = d->dialog_id.get_channel_id();
- auto status = td_->contacts_manager_->get_channel_status(channel_id);
- if (!status.is_member()) {
- remove_from_dialog_list = true;
- }
- break;
- }
+ case DialogType::Chat:
+ return !td_->contacts_manager_->get_chat_is_active(d->dialog_id.get_chat_id());
+ case DialogType::Channel:
+ return !td_->contacts_manager_->get_channel_status(d->dialog_id.get_channel_id()).is_member();
case DialogType::SecretChat:
break;
case DialogType::None:
default:
UNREACHABLE();
- return;
+ break;
+ }
+ return false;
+}
+
+void MessagesManager::update_dialog_pos(Dialog *d, const char *source, bool need_send_update,
+ bool is_loaded_from_database) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
}
+ CHECK(d != nullptr);
+ LOG(INFO) << "Trying to update " << d->dialog_id << " order from " << source;
+
int64 new_order = DEFAULT_ORDER;
- if (!remove_from_dialog_list) {
- if (d->pinned_order != DEFAULT_ORDER) {
- LOG(INFO) << "Pin at " << d->pinned_order << " found";
- new_order = d->pinned_order;
- }
+ if (!is_removed_from_dialog_list(d)) {
if (d->last_message_id != MessageId()) {
auto m = get_message(d, d->last_message_id);
CHECK(m != nullptr);
@@ -23318,311 +38027,978 @@ void MessagesManager::update_dialog_pos(Dialog *d, bool remove_from_dialog_list,
new_order = clear_order;
}
}
- if (d->draft_message != nullptr) {
+ if (d->pending_last_message_date > 0) {
+ LOG(INFO) << "Pending last " << d->pending_last_message_id << " at " << d->pending_last_message_date << " found";
+ int64 pending_order = get_dialog_order(d->pending_last_message_id, d->pending_last_message_date);
+ if (pending_order > new_order) {
+ new_order = pending_order;
+ }
+ }
+ if (d->draft_message != nullptr && can_send_message(d->dialog_id).is_ok()) {
LOG(INFO) << "Draft message at " << d->draft_message->date << " found";
int64 draft_order = get_dialog_order(MessageId(), d->draft_message->date);
if (draft_order > new_order) {
new_order = draft_order;
}
}
- if (dialog_type == DialogType::Channel) {
- auto date = td_->contacts_manager_->get_channel_date(d->dialog_id.get_channel_id());
- LOG(INFO) << "Join of channel at " << date << " found";
- int64 join_order = get_dialog_order(MessageId(), date);
- if (join_order > new_order) {
- new_order = join_order;
+ switch (d->dialog_id.get_type()) {
+ case DialogType::Chat: {
+ auto chat_id = d->dialog_id.get_chat_id();
+ auto date = td_->contacts_manager_->get_chat_date(chat_id);
+ LOG(INFO) << "Creation at " << date << " found";
+ int64 join_order = get_dialog_order(MessageId(), date);
+ if (join_order > new_order && td_->contacts_manager_->get_chat_status(chat_id).is_member()) {
+ new_order = join_order;
+ }
+ break;
}
- }
- if (dialog_type == DialogType::SecretChat) {
- auto secret_chat_id = d->dialog_id.get_secret_chat_id();
- auto date = td_->contacts_manager_->get_secret_chat_date(secret_chat_id);
- auto state = td_->contacts_manager_->get_secret_chat_state(secret_chat_id);
- // do not return removed from the chat list closed secret chats
- if (date != 0 && (d->order != DEFAULT_ORDER || state != SecretChatState::Closed)) {
- LOG(INFO) << "Creation of secret chat at " << date << " found";
- int64 creation_order = get_dialog_order(MessageId(), date);
- if (creation_order > new_order) {
- new_order = creation_order;
+ case DialogType::Channel: {
+ auto date = td_->contacts_manager_->get_channel_date(d->dialog_id.get_channel_id());
+ LOG(INFO) << "Join at " << date << " found";
+ int64 join_order = get_dialog_order(MessageId(), date);
+ if (join_order > new_order) {
+ new_order = join_order;
}
+ break;
+ }
+ case DialogType::SecretChat: {
+ auto date = td_->contacts_manager_->get_secret_chat_date(d->dialog_id.get_secret_chat_id());
+ if (date != 0 && !is_deleted_secret_chat(d)) {
+ LOG(INFO) << "Creation at " << date << " found";
+ int64 creation_order = get_dialog_order(MessageId(), date);
+ if (creation_order > new_order) {
+ new_order = creation_order;
+ }
+ }
+ break;
}
+ default:
+ break;
}
if (new_order == DEFAULT_ORDER && !d->is_empty) {
- // if there is no known messages in the dialog, just leave it where it is
- LOG(INFO) << "There is no known messages in the dialog";
- return;
+ LOG(INFO) << "There are no known messages in the chat, just leave it where it is";
+ new_order = d->order;
}
}
- if (set_dialog_order(d, new_order, need_send_update_chat_order)) {
+ if (set_dialog_order(d, new_order, need_send_update, is_loaded_from_database, source)) {
on_dialog_updated(d->dialog_id, "update_dialog_pos");
}
}
-bool MessagesManager::set_dialog_order(Dialog *d, int64 new_order, bool need_send_update_chat_order) {
+bool MessagesManager::set_dialog_order(Dialog *d, int64 new_order, bool need_send_update, bool is_loaded_from_database,
+ const char *source) {
+ if (td_->auth_manager_->is_bot()) {
+ return false;
+ }
+
CHECK(d != nullptr);
- DialogDate old_date(d->order, d->dialog_id);
- DialogDate new_date(new_order, d->dialog_id);
+ DialogId dialog_id = d->dialog_id;
+ DialogDate old_date(d->order, dialog_id);
+ DialogDate new_date(new_order, dialog_id);
- std::set<DialogDate> *ordered_dialogs_set = nullptr;
- switch (d->dialog_id.get_type()) {
- case DialogType::User:
- case DialogType::Chat:
- case DialogType::Channel:
- case DialogType::SecretChat:
- ordered_dialogs_set = &ordered_server_dialogs_;
- break;
- case DialogType::None:
- default:
- UNREACHABLE();
- return false;
+ if (old_date == new_date) {
+ LOG(INFO) << "Order of " << d->dialog_id << " from " << d->folder_id << " is still " << new_order << " from "
+ << source;
+ } else {
+ LOG(INFO) << "Update order of " << dialog_id << " from " << d->folder_id << " from " << d->order << " to "
+ << new_order << " from " << source;
}
+ auto folder_ptr = get_dialog_folder(d->folder_id);
+ LOG_CHECK(folder_ptr != nullptr) << is_inited_ << ' ' << G()->close_flag() << ' ' << dialog_id << ' ' << d->folder_id
+ << ' ' << is_loaded_from_database << ' ' << td_->auth_manager_->is_authorized()
+ << ' ' << td_->auth_manager_->was_authorized() << ' ' << source;
+ auto &folder = *folder_ptr;
if (old_date == new_date) {
- if (new_order == DEFAULT_ORDER && ordered_dialogs_set->count(old_date) == 0) {
- ordered_dialogs_set->insert(new_date);
+ if (new_order == DEFAULT_ORDER) {
+ // first addition of a new left dialog
+ if (folder.ordered_dialogs_.insert(new_date).second) {
+ for (const auto &dialog_list : dialog_lists_) {
+ if (get_dialog_pinned_order(&dialog_list.second, d->dialog_id) != DEFAULT_ORDER) {
+ set_dialog_is_pinned(dialog_list.first, d, false);
+ }
+ }
+ }
}
- LOG(INFO) << "Dialog order is not changed: " << new_order;
+
return false;
}
- LOG(INFO) << "Update order of " << d->dialog_id << " from " << d->order << " to " << new_order;
- bool need_update = false;
- if (old_date <= last_dialog_date_) {
- if (ordered_dialogs_.erase(old_date) == 0) {
- UNREACHABLE();
+ auto dialog_positions = get_dialog_positions(d);
+
+ if (folder.ordered_dialogs_.erase(old_date) == 0) {
+ LOG_IF(ERROR, d->order != DEFAULT_ORDER) << dialog_id << " not found in the chat list from " << source;
+ }
+
+ folder.ordered_dialogs_.insert(new_date);
+
+ bool is_added = (d->order == DEFAULT_ORDER);
+ bool is_removed = (new_order == DEFAULT_ORDER);
+
+ d->order = new_order;
+
+ if (is_added) {
+ update_dialogs_hints(d);
+ }
+ update_dialogs_hints_rating(d);
+
+ update_dialog_lists(d, std::move(dialog_positions), need_send_update, is_loaded_from_database, source);
+
+ if (!is_loaded_from_database) {
+ auto dialog_type = dialog_id.get_type();
+ if (dialog_type == DialogType::Channel && is_added && being_added_dialog_id_ != dialog_id) {
+ repair_channel_server_unread_count(d);
+ LOG(INFO) << "Schedule getDifference in " << dialog_id.get_channel_id();
+ channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), 0.001);
+ }
+ if (dialog_type == DialogType::Channel && is_removed) {
+ remove_all_dialog_notifications(d, false, source);
+ remove_all_dialog_notifications(d, true, source);
+ clear_active_dialog_actions(dialog_id);
}
- need_update = true;
}
- bool is_new = ordered_dialogs_set->erase(old_date) == 0;
- LOG_IF(ERROR, is_new && d->order != DEFAULT_ORDER) << d->dialog_id << " not found in the chat list";
- int64 updated_to = 0;
- if (new_date <= last_dialog_date_) {
- ordered_dialogs_.insert(new_date);
- need_update = true;
- updated_to = new_order;
+ return true;
+}
+
+void MessagesManager::update_dialog_lists(
+ Dialog *d, std::unordered_map<DialogListId, DialogPositionInList, DialogListIdHash> &&old_positions,
+ bool need_send_update, bool is_loaded_from_database, const char *source) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
}
- ordered_dialogs_set->insert(new_date);
- bool add_to_hints = (d->order == DEFAULT_ORDER);
+ CHECK(d != nullptr);
+ auto dialog_id = d->dialog_id;
+ if (being_added_dialog_id_ == dialog_id) {
+ // do not try to update dialog lists, while the dialog isn't inited
+ return;
+ }
- if (!is_new && (d->order == DEFAULT_ORDER || new_order == DEFAULT_ORDER)) {
- auto unread_count = d->server_unread_count + d->local_unread_count;
- if (unread_count != 0 && is_unread_count_inited_) {
- const char *source = "on_dialog_join";
- if (d->order != DEFAULT_ORDER) {
- unread_count = -unread_count;
- source = "on_dialog_leave";
- } else {
- CHECK(new_order != DEFAULT_ORDER);
+ LOG(INFO) << "Update lists of " << dialog_id << " from " << source;
+
+ if (d->order == DEFAULT_ORDER) {
+ for (auto &old_position : old_positions) {
+ if (old_position.second.is_pinned) {
+ set_dialog_is_pinned(old_position.first, d, false, false);
}
+ }
+
+ if (d->folder_id != FolderId::main()) {
+ LOG(INFO) << "Change folder of " << dialog_id << " to " << FolderId::main();
+ DialogDate dialog_date(d->order, dialog_id);
+ get_dialog_folder(d->folder_id)->ordered_dialogs_.erase(dialog_date);
+ do_set_dialog_folder_id(d, FolderId::main());
+ get_dialog_folder(d->folder_id)->ordered_dialogs_.insert(dialog_date);
+ }
+ }
+
+ for (auto &dialog_list : dialog_lists_) {
+ auto dialog_list_id = dialog_list.first;
+ auto &list = dialog_list.second;
+
+ const DialogPositionInList &old_position = old_positions[dialog_list_id];
+ const DialogPositionInList new_position = get_dialog_position_in_list(&list, d, true);
+
+ // sponsored chat is never "in list"
+ bool was_in_list = old_position.order != DEFAULT_ORDER && old_position.private_order != 0;
+ bool is_in_list = new_position.order != DEFAULT_ORDER && new_position.private_order != 0;
+ CHECK(was_in_list == is_dialog_in_list(d, dialog_list_id));
- unread_message_total_count_ += unread_count;
- if (d->notification_settings.mute_until != 0) {
- unread_message_muted_count_ += unread_count;
+ LOG(DEBUG) << "Update position of " << dialog_id << " in " << dialog_list_id << " from " << old_position << " to "
+ << new_position;
+
+ bool need_update_unread_chat_count = false;
+ if (was_in_list != is_in_list) {
+ const int32 delta = was_in_list ? -1 : 1;
+ list.in_memory_dialog_total_count_ += delta;
+ if (!is_loaded_from_database) {
+ int32 &total_count = dialog_id.get_type() == DialogType::SecretChat ? list.secret_chat_total_count_
+ : list.server_dialog_total_count_;
+ if (total_count != -1) {
+ total_count += delta;
+ if (total_count < 0) {
+ LOG(ERROR) << "Total chat count in " << dialog_list_id << " became negative after removing " << dialog_id;
+ total_count = 0;
+ }
+ }
}
- send_update_unread_message_count(d->dialog_id, true, source);
+
+ if (!is_loaded_from_database) {
+ need_update_unread_chat_count =
+ list.is_dialog_unread_count_inited_ && old_position.total_dialog_count != get_dialog_total_count(list);
+ auto unread_count = d->server_unread_count + d->local_unread_count;
+ const char *change_source = was_in_list ? "on_dialog_remove" : "on_dialog_add";
+ if (unread_count != 0 && list.is_message_unread_count_inited_) {
+ unread_count *= delta;
+
+ list.unread_message_total_count_ += unread_count;
+ if (is_dialog_muted(d)) {
+ list.unread_message_muted_count_ += unread_count;
+ }
+ send_update_unread_message_count(list, dialog_id, true, change_source);
+ }
+ if ((unread_count != 0 || d->is_marked_as_unread) && list.is_dialog_unread_count_inited_) {
+ list.unread_dialog_total_count_ += delta;
+ if (unread_count == 0 && d->is_marked_as_unread) {
+ list.unread_dialog_marked_count_ += delta;
+ }
+ if (is_dialog_muted(d)) {
+ list.unread_dialog_muted_count_ += delta;
+ if (unread_count == 0 && d->is_marked_as_unread) {
+ list.unread_dialog_muted_marked_count_ += delta;
+ }
+ }
+ need_update_unread_chat_count = true;
+ }
+ if (need_update_unread_chat_count) {
+ send_update_unread_chat_count(list, dialog_id, true, change_source);
+ }
+ }
+
+ if (was_in_list) {
+ remove_dialog_from_list(d, dialog_list_id);
+ } else {
+ add_dialog_to_list(d, dialog_list_id);
+ }
+ }
+ if (!need_update_unread_chat_count && list.is_dialog_unread_count_inited_ &&
+ old_position.total_dialog_count != get_dialog_total_count(list)) {
+ send_update_unread_chat_count(list, dialog_id, true, "changed total count");
+ }
+
+ if (need_send_update && need_send_update_chat_position(old_position, new_position)) {
+ send_update_chat_position(dialog_list_id, d, source);
+ }
+
+ if (!is_loaded_from_database && !old_position.is_sponsored && new_position.is_sponsored) {
+ // a chat is sponsored only if user isn't a chat member
+ remove_all_dialog_notifications(d, false, "update_dialog_lists 3");
+ remove_all_dialog_notifications(d, true, "update_dialog_lists 4");
}
}
+}
- d->order = new_order;
+void MessagesManager::update_last_dialog_date(FolderId folder_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto *folder = get_dialog_folder(folder_id);
+ CHECK(folder != nullptr);
+ auto old_last_dialog_date = folder->folder_last_dialog_date_;
+ folder->folder_last_dialog_date_ = folder->last_server_dialog_date_;
+ CHECK(old_last_dialog_date <= folder->folder_last_dialog_date_);
- if (add_to_hints) {
- update_dialogs_hints(d);
+ LOG(INFO) << "Update last dialog date in " << folder_id << " from " << old_last_dialog_date << " to "
+ << folder->folder_last_dialog_date_;
+ LOG(INFO) << "Know about " << folder->ordered_dialogs_.size() << " chats";
+
+ if (old_last_dialog_date != folder->folder_last_dialog_date_) {
+ for (auto &dialog_list : dialog_lists_) {
+ update_list_last_pinned_dialog_date(dialog_list.second);
+ update_list_last_dialog_date(dialog_list.second);
+ }
}
- update_dialogs_hints_rating(d);
- if (need_update && need_send_update_chat_order) {
- send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateChatOrder>(d->dialog_id.get(), updated_to));
+ if (G()->parameters().use_message_db &&
+ folder->last_database_server_dialog_date_ < folder->last_server_dialog_date_) {
+ auto last_server_dialog_date_string = PSTRING() << folder->last_server_dialog_date_.get_order() << ' '
+ << folder->last_server_dialog_date_.get_dialog_id().get();
+ G()->td_db()->get_binlog_pmc()->set(PSTRING() << "last_server_dialog_date" << folder_id.get(),
+ last_server_dialog_date_string);
+ LOG(INFO) << "Save last server dialog date " << folder->last_server_dialog_date_;
+ folder->last_database_server_dialog_date_ = folder->last_server_dialog_date_;
+ folder->last_loaded_database_dialog_date_ = folder->last_server_dialog_date_;
}
- return true;
}
-void MessagesManager::update_last_dialog_date() {
- auto old_last_dialog_date = last_dialog_date_;
- last_dialog_date_ = last_server_dialog_date_; // std::min
- CHECK(old_last_dialog_date <= last_dialog_date_);
-
- LOG(INFO) << "Update last dialog date from " << old_last_dialog_date << " to " << last_dialog_date_;
- LOG(INFO) << "Know about " << ordered_server_dialogs_.size() << " chats";
+// must not call get_dialog_filter
+bool MessagesManager::do_update_list_last_pinned_dialog_date(DialogList &list) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (list.last_pinned_dialog_date_ == MAX_DIALOG_DATE) {
+ return false;
+ }
+ if (!list.are_pinned_dialogs_inited_) {
+ return false;
+ }
- if (old_last_dialog_date != last_dialog_date_) {
- for (auto it = ordered_server_dialogs_.upper_bound(old_last_dialog_date);
- it != ordered_server_dialogs_.end() && *it <= last_dialog_date_; ++it) {
- auto dialog_id = it->get_dialog_id();
- auto d = get_dialog(dialog_id);
- CHECK(d != nullptr);
- ordered_dialogs_.insert(DialogDate(d->order, d->dialog_id));
- send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateChatOrder>(d->dialog_id.get(), d->order));
+ DialogDate max_dialog_date = MIN_DIALOG_DATE;
+ for (const auto &pinned_dialog : list.pinned_dialogs_) {
+ if (!have_dialog(pinned_dialog.get_dialog_id())) {
+ break;
}
- if (last_dialog_date_ == MAX_DIALOG_DATE) {
- recalc_unread_message_count();
+ max_dialog_date = pinned_dialog;
+ }
+ if (list.pinned_dialogs_.empty() || max_dialog_date == list.pinned_dialogs_.back()) {
+ max_dialog_date = MAX_DIALOG_DATE;
+ }
+ if (list.last_pinned_dialog_date_ < max_dialog_date) {
+ LOG(INFO) << "Update last pinned dialog date in " << list.dialog_list_id << " from "
+ << list.last_pinned_dialog_date_ << " to " << max_dialog_date;
+ list.last_pinned_dialog_date_ = max_dialog_date;
+ return true;
+ }
+ return false;
+}
+
+void MessagesManager::update_list_last_pinned_dialog_date(DialogList &list) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (do_update_list_last_pinned_dialog_date(list)) {
+ update_list_last_dialog_date(list);
+ }
+}
+
+// must not call get_dialog_filter
+bool MessagesManager::do_update_list_last_dialog_date(DialogList &list, const vector<FolderId> &folder_ids) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto new_last_dialog_date = list.last_pinned_dialog_date_;
+ for (auto folder_id : folder_ids) {
+ const auto &folder = *get_dialog_folder(folder_id);
+ if (folder.folder_last_dialog_date_ < new_last_dialog_date) {
+ new_last_dialog_date = folder.folder_last_dialog_date_;
}
}
- if (G()->parameters().use_message_db && last_database_server_dialog_date_ < last_server_dialog_date_) {
- auto last_server_dialog_date_string = to_string(last_server_dialog_date_.get_order()) + " " +
- to_string(last_server_dialog_date_.get_dialog_id().get());
- G()->td_db()->get_binlog_pmc()->set("last_server_dialog_date", last_server_dialog_date_string);
- LOG(INFO) << "Save last server dialog date " << last_server_dialog_date_string;
- last_database_server_dialog_date_ = last_server_dialog_date_;
- last_loaded_database_dialog_date_ = last_server_dialog_date_;
+ if (list.list_last_dialog_date_ != new_last_dialog_date) {
+ auto old_last_dialog_date = list.list_last_dialog_date_;
+ LOG(INFO) << "Update last dialog date in " << list.dialog_list_id << " from " << old_last_dialog_date << " to "
+ << new_last_dialog_date;
+ LOG_CHECK(old_last_dialog_date < new_last_dialog_date)
+ << list.dialog_list_id << " " << old_last_dialog_date << " " << new_last_dialog_date << " "
+ << get_dialog_list_folder_ids(list) << " " << list.last_pinned_dialog_date_ << " "
+ << get_dialog_folder(FolderId::main())->folder_last_dialog_date_ << " "
+ << get_dialog_folder(FolderId::archive())->folder_last_dialog_date_ << " " << list.load_list_queries_.size()
+ << " " << list.pinned_dialogs_;
+ list.list_last_dialog_date_ = new_last_dialog_date;
+ return true;
}
+ return false;
}
-uint64 MessagesManager::get_sequence_dispatcher_id(DialogId dialog_id, int32 message_content_type) {
- switch (message_content_type) {
- case MessageAnimation::ID:
- case MessageAudio::ID:
- case MessageDocument::ID:
- case MessagePhoto::ID:
- case MessageSticker::ID:
- case MessageVideo::ID:
- case MessageVideoNote::ID:
- case MessageVoiceNote::ID:
- return static_cast<uint64>(dialog_id.get() * 2 + 1);
- default:
- return static_cast<uint64>(dialog_id.get() * 2 + 2);
+void MessagesManager::update_list_last_dialog_date(DialogList &list) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto old_dialog_total_count = get_dialog_total_count(list);
+ auto old_last_dialog_date = list.list_last_dialog_date_;
+ if (!do_update_list_last_dialog_date(list, get_dialog_list_folder_ids(list))) {
+ LOG(INFO) << "Don't need to update last dialog date in " << list.dialog_list_id;
+ return;
+ }
+
+ for (auto it = std::upper_bound(list.pinned_dialogs_.begin(), list.pinned_dialogs_.end(), old_last_dialog_date);
+ it != list.pinned_dialogs_.end() && *it <= list.list_last_dialog_date_; ++it) {
+ auto dialog_id = it->get_dialog_id();
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ send_update_chat_position(list.dialog_list_id, d, "update_list_last_dialog_date");
+ }
+
+ bool is_list_further_loaded = list.list_last_dialog_date_ == MAX_DIALOG_DATE;
+ for (auto folder_id : get_dialog_list_folder_ids(list)) {
+ const auto &folder = *get_dialog_folder(folder_id);
+ for (auto it = folder.ordered_dialogs_.upper_bound(old_last_dialog_date);
+ it != folder.ordered_dialogs_.end() && *it <= folder.folder_last_dialog_date_; ++it) {
+ if (it->get_order() == DEFAULT_ORDER) {
+ break;
+ }
+ auto dialog_id = it->get_dialog_id();
+ if (get_dialog_pinned_order(&list, dialog_id) == DEFAULT_ORDER) {
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ if (is_dialog_in_list(d, list.dialog_list_id)) {
+ send_update_chat_position(list.dialog_list_id, d, "update_list_last_dialog_date 2");
+ is_list_further_loaded = true;
+ }
+ }
+ }
+ }
+
+ if (list.list_last_dialog_date_ == MAX_DIALOG_DATE) {
+ recalc_unread_count(list.dialog_list_id, old_dialog_total_count, true);
+ }
+
+ LOG(INFO) << "After updating last dialog date in " << list.dialog_list_id << " to " << list.list_last_dialog_date_
+ << " have is_list_further_loaded == " << is_list_further_loaded << " and " << list.load_list_queries_.size()
+ << " pending load list queries";
+ if (is_list_further_loaded && !list.load_list_queries_.empty()) {
+ set_promises(list.load_list_queries_);
}
}
MessagesManager::Dialog *MessagesManager::get_dialog(DialogId dialog_id) {
- auto it = dialogs_.find(dialog_id);
- return it == dialogs_.end() ? nullptr : it->second.get();
+ return dialogs_.get_pointer(dialog_id);
}
const MessagesManager::Dialog *MessagesManager::get_dialog(DialogId dialog_id) const {
- auto it = dialogs_.find(dialog_id);
- return it == dialogs_.end() ? nullptr : it->second.get();
+ return dialogs_.get_pointer(dialog_id);
}
-bool MessagesManager::have_dialog_force(DialogId dialog_id) {
- return loaded_dialogs_.count(dialog_id) > 0 || get_dialog_force(dialog_id) != nullptr;
+bool MessagesManager::have_dialog_force(DialogId dialog_id, const char *source) {
+ return loaded_dialogs_.count(dialog_id) > 0 || get_dialog_force(dialog_id, source) != nullptr;
}
-MessagesManager::Dialog *MessagesManager::get_dialog_force(DialogId dialog_id) {
- // TODO remove most usages of that function, preload dialog asynchronously if possible
- auto it = dialogs_.find(dialog_id);
- if (it != dialogs_.end()) {
- return it->second.get();
+MessagesManager::Dialog *MessagesManager::get_dialog_force(DialogId dialog_id, const char *source) {
+ init();
+
+ Dialog *d = get_dialog(dialog_id);
+ if (d != nullptr) {
+ LOG_CHECK(d->dialog_id == dialog_id) << d->dialog_id << ' ' << dialog_id;
+ return d;
}
if (!dialog_id.is_valid() || !G()->parameters().use_message_db || loaded_dialogs_.count(dialog_id) > 0) {
return nullptr;
}
- LOG(INFO) << "Try to load " << dialog_id << " from database";
- auto d = on_load_dialog_from_database(G()->td_db()->get_dialog_db_sync()->get_dialog(dialog_id));
- CHECK(d == nullptr || d->dialog_id == dialog_id);
+ auto r_value = G()->td_db()->get_dialog_db_sync()->get_dialog(dialog_id);
+ if (r_value.is_ok()) {
+ LOG(INFO) << "Loaded " << dialog_id << " from database from " << source;
+ d = on_load_dialog_from_database(dialog_id, r_value.move_as_ok(), source);
+ LOG_CHECK(d == nullptr || d->dialog_id == dialog_id) << d->dialog_id << ' ' << dialog_id;
+ } else {
+ LOG(INFO) << "Failed to load " << dialog_id << " from database from " << source << ": "
+ << r_value.error().message();
+ }
return d;
}
-unique_ptr<MessagesManager::Dialog> MessagesManager::parse_dialog(DialogId dialog_id, const BufferSlice &value) {
- LOG(INFO) << "Loaded " << dialog_id << " from database";
- auto d = make_unique<Dialog>();
- std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), -1);
+unique_ptr<MessagesManager::Dialog> MessagesManager::parse_dialog(DialogId dialog_id, const BufferSlice &value,
+ const char *source) {
+ LOG(INFO) << "Loaded " << dialog_id << " of size " << value.size() << " from database from " << source;
+ CHECK(dialog_id.is_valid());
+ auto dialog = make_unique<Dialog>();
+ Dialog *d = dialog.get();
+ d->dialog_id = dialog_id;
+ invalidate_message_indexes(d); // must initialize indexes, because some of them could be not parsed
loaded_dialogs_.insert(dialog_id);
- log_event_parse(*d, value.as_slice()).ensure();
+ auto status = log_event_parse(*d, value.as_slice());
+ if (status.is_error() || !d->dialog_id.is_valid() || d->dialog_id != dialog_id) {
+ // can't happen unless database is broken, but has been seen in the wild
+ // if dialog_id is invalid, we can't repair the dialog
+ LOG_CHECK(dialog_id.is_valid()) << "Can't repair " << dialog_id << ' ' << d->dialog_id << ' ' << status << ' '
+ << source << ' ' << format::as_hex_dump<4>(value.as_slice());
+
+ LOG(ERROR) << "Repair broken " << dialog_id << ": " << status << ' ' << format::as_hex_dump<4>(value.as_slice());
+
+ // just clean all known data about the dialog
+ dialog = make_unique<Dialog>();
+ d = dialog.get();
+ d->dialog_id = dialog_id;
+ invalidate_message_indexes(d);
+
+ // and try to reget it from the server if possible
+ have_dialog_info_force(dialog_id);
+ if (have_input_peer(dialog_id, AccessRights::Read)) {
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ send_get_dialog_query(dialog_id, Auto(), 0, source);
+ }
+ } else {
+ LOG(ERROR) << "Can't repair unknown " << dialog_id << " from " << source;
+ }
+ }
CHECK(dialog_id == d->dialog_id);
Dependencies dependencies;
- add_dialog_dependencies(dependencies, dialog_id);
+ dependencies.add_dialog_dependencies(dialog_id);
+ if (d->default_join_group_call_as_dialog_id != dialog_id) {
+ dependencies.add_message_sender_dependencies(d->default_join_group_call_as_dialog_id);
+ }
+ if (d->default_send_message_as_dialog_id != dialog_id) {
+ dependencies.add_message_sender_dependencies(d->default_send_message_as_dialog_id);
+ }
if (d->messages != nullptr) {
- add_message_dependencies(dependencies, dialog_id, d->messages.get());
+ add_message_dependencies(dependencies, d->messages.get());
+ }
+ if (d->draft_message != nullptr) {
+ add_formatted_text_dependencies(dependencies, &d->draft_message->input_message_text.text);
+ }
+ for (auto user_id : d->pending_join_request_user_ids) {
+ dependencies.add(user_id);
+ }
+ if (!dependencies.resolve_force(td_, source)) {
+ send_get_dialog_query(dialog_id, Auto(), 0, source);
}
- resolve_dependencies_force(dependencies);
- return d;
+ auto dialog_type = d->dialog_id.get_type();
+ switch (dialog_type) {
+ case DialogType::Chat:
+ case DialogType::Channel:
+ if (get_active_reactions(d->available_reactions).empty() != ((d->available_reactions_generation & 1) == 1)) {
+ set_dialog_next_available_reactions_generation(d, d->available_reactions_generation);
+ }
+ break;
+ case DialogType::User:
+ case DialogType::SecretChat:
+ default:
+ break;
+ }
+ if (!d->need_drop_default_send_message_as_dialog_id && d->default_send_message_as_dialog_id.is_valid() &&
+ dialog_type == DialogType::Channel && !td_->contacts_manager_->is_channel_public(dialog_id.get_channel_id()) &&
+ !td_->contacts_manager_->get_channel_has_linked_channel(dialog_id.get_channel_id())) {
+ LOG(INFO) << "Drop message sender in " << dialog_id;
+ d->need_drop_default_send_message_as_dialog_id = true;
+ }
+
+ return dialog;
}
-MessagesManager::Dialog *MessagesManager::on_load_dialog_from_database(const Result<BufferSlice> &r_value) {
+MessagesManager::Dialog *MessagesManager::on_load_dialog_from_database(DialogId dialog_id, BufferSlice &&value,
+ const char *source) {
CHECK(G()->parameters().use_message_db);
- if (!r_value.is_ok()) {
- return nullptr;
- }
+ if (!dialog_id.is_valid()) {
+ // hack
+ LogEventParser dialog_id_parser(value.as_slice());
+ int32 flags;
+ parse(flags, dialog_id_parser);
+ parse(dialog_id, dialog_id_parser);
- // hack
- LogEventParser dialog_id_parser(r_value.ok().as_slice());
- int32 flags;
- DialogId dialog_id;
- parse(flags, dialog_id_parser);
- parse(dialog_id, dialog_id_parser);
+ if (!dialog_id.is_valid()) {
+ LOG(ERROR) << "Failed to parse dialog_id from blob. Database is broken";
+ return nullptr;
+ }
+ }
auto old_d = get_dialog(dialog_id);
if (old_d != nullptr) {
return old_d;
}
- return add_new_dialog(parse_dialog(dialog_id, r_value.ok()), true);
+ LOG(INFO) << "Add new " << dialog_id << " from database from " << source;
+ return add_new_dialog(parse_dialog(dialog_id, value, source), true, source);
}
-void MessagesManager::load_notification_settings() {
- if (td_->auth_manager_->is_bot()) {
- return;
+const DialogFilter *MessagesManager::get_server_dialog_filter(DialogFilterId dialog_filter_id) const {
+ CHECK(!disable_get_dialog_filter_);
+ for (const auto &filter : server_dialog_filters_) {
+ if (filter->dialog_filter_id == dialog_filter_id) {
+ return filter.get();
+ }
+ }
+ return nullptr;
+}
+
+DialogFilter *MessagesManager::get_dialog_filter(DialogFilterId dialog_filter_id) {
+ CHECK(!disable_get_dialog_filter_);
+ for (auto &filter : dialog_filters_) {
+ if (filter->dialog_filter_id == dialog_filter_id) {
+ return filter.get();
+ }
+ }
+ return nullptr;
+}
+
+const DialogFilter *MessagesManager::get_dialog_filter(DialogFilterId dialog_filter_id) const {
+ CHECK(!disable_get_dialog_filter_);
+ for (const auto &filter : dialog_filters_) {
+ if (filter->dialog_filter_id == dialog_filter_id) {
+ return filter.get();
+ }
+ }
+ return nullptr;
+}
+
+int32 MessagesManager::get_server_main_dialog_list_position() const {
+ int32 current_position = 0;
+ int32 current_server_position = 0;
+ if (current_position == main_dialog_list_position_) {
+ return current_server_position;
+ }
+ for (const auto &dialog_filter : dialog_filters_) {
+ current_position++;
+ if (!dialog_filter->is_empty(true)) {
+ current_server_position++;
+ }
+ if (current_position == main_dialog_list_position_) {
+ return current_server_position;
+ }
+ }
+ LOG(WARNING) << "Failed to find server position for " << main_dialog_list_position_ << " in chat filters";
+ return current_server_position;
+}
+
+vector<DialogFilterId> MessagesManager::get_dialog_filter_ids(const vector<unique_ptr<DialogFilter>> &dialog_filters,
+ int32 main_dialog_list_position) {
+ auto result = transform(dialog_filters, [](const auto &dialog_filter) { return dialog_filter->dialog_filter_id; });
+ if (static_cast<size_t>(main_dialog_list_position) <= result.size()) {
+ result.insert(result.begin() + main_dialog_list_position, DialogFilterId());
+ }
+ return result;
+}
+
+vector<FolderId> MessagesManager::get_dialog_filter_folder_ids(const DialogFilter *filter) {
+ CHECK(filter != nullptr);
+ if (filter->exclude_archived && filter->pinned_dialog_ids.empty() && filter->included_dialog_ids.empty()) {
+ return {FolderId::main()};
+ }
+ return {FolderId::main(), FolderId::archive()};
+}
+
+vector<FolderId> MessagesManager::get_dialog_list_folder_ids(const DialogList &list) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (list.dialog_list_id.is_folder()) {
+ return {list.dialog_list_id.get_folder_id()};
+ }
+ if (list.dialog_list_id.is_filter()) {
+ auto dialog_filter_id = list.dialog_list_id.get_filter_id();
+ return get_dialog_filter_folder_ids(get_dialog_filter(dialog_filter_id));
+ }
+ UNREACHABLE();
+ return {};
+}
+
+bool MessagesManager::has_dialogs_from_folder(const DialogList &list, const DialogFolder &folder) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (list.dialog_list_id.is_folder()) {
+ return list.dialog_list_id.get_folder_id() == folder.folder_id;
+ }
+ if (list.dialog_list_id.is_filter()) {
+ auto dialog_filter_id = list.dialog_list_id.get_filter_id();
+ auto *filter = get_dialog_filter(dialog_filter_id);
+ CHECK(filter != nullptr);
+ if (filter->exclude_archived && filter->pinned_dialog_ids.empty() && filter->included_dialog_ids.empty()) {
+ return folder.folder_id == FolderId::main();
+ }
+ return true;
+ }
+ UNREACHABLE();
+ return false;
+}
+
+bool MessagesManager::is_dialog_in_list(const Dialog *d, DialogListId dialog_list_id) {
+ return td::contains(d->dialog_list_ids, dialog_list_id);
+}
+
+void MessagesManager::add_dialog_to_list(Dialog *d, DialogListId dialog_list_id) {
+ LOG(INFO) << "Add " << d->dialog_id << " to " << dialog_list_id;
+ CHECK(!is_dialog_in_list(d, dialog_list_id));
+ d->dialog_list_ids.push_back(dialog_list_id);
+}
+
+void MessagesManager::remove_dialog_from_list(Dialog *d, DialogListId dialog_list_id) {
+ LOG(INFO) << "Remove " << d->dialog_id << " from " << dialog_list_id;
+ bool is_removed = td::remove(d->dialog_list_ids, dialog_list_id);
+ CHECK(is_removed);
+}
+
+bool MessagesManager::need_dialog_in_filter(const Dialog *d, const DialogFilter *filter) const {
+ CHECK(d != nullptr);
+ CHECK(filter != nullptr);
+ CHECK(d->order != DEFAULT_ORDER);
+
+ if (InputDialogId::contains(filter->pinned_dialog_ids, d->dialog_id)) {
+ return true;
+ }
+ if (InputDialogId::contains(filter->included_dialog_ids, d->dialog_id)) {
+ return true;
+ }
+ if (InputDialogId::contains(filter->excluded_dialog_ids, d->dialog_id)) {
+ return false;
+ }
+ if (d->dialog_id.get_type() == DialogType::SecretChat) {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
+ if (user_id.is_valid()) {
+ auto dialog_id = DialogId(user_id);
+ if (InputDialogId::contains(filter->pinned_dialog_ids, dialog_id)) {
+ return true;
+ }
+ if (InputDialogId::contains(filter->included_dialog_ids, dialog_id)) {
+ return true;
+ }
+ if (InputDialogId::contains(filter->excluded_dialog_ids, dialog_id)) {
+ return false;
+ }
+ }
+ }
+ if (d->unread_mention_count == 0 || is_dialog_mention_notifications_disabled(d)) {
+ if (filter->exclude_muted && is_dialog_muted(d)) {
+ return false;
+ }
+ if (filter->exclude_read && d->server_unread_count + d->local_unread_count == 0 && !d->is_marked_as_unread) {
+ return false;
+ }
+ }
+ if (filter->exclude_archived && d->folder_id == FolderId::archive()) {
+ return false;
+ }
+ switch (d->dialog_id.get_type()) {
+ case DialogType::User: {
+ auto user_id = d->dialog_id.get_user_id();
+ if (td_->contacts_manager_->is_user_bot(user_id)) {
+ return filter->include_bots;
+ }
+ if (user_id == td_->contacts_manager_->get_my_id() || td_->contacts_manager_->is_user_contact(user_id)) {
+ return filter->include_contacts;
+ }
+ return filter->include_non_contacts;
+ }
+ case DialogType::Chat:
+ return filter->include_groups;
+ case DialogType::Channel:
+ return is_broadcast_channel(d->dialog_id) ? filter->include_channels : filter->include_groups;
+ case DialogType::SecretChat: {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(d->dialog_id.get_secret_chat_id());
+ if (td_->contacts_manager_->is_user_bot(user_id)) {
+ return filter->include_bots;
+ }
+ if (td_->contacts_manager_->is_user_contact(user_id)) {
+ return filter->include_contacts;
+ }
+ return filter->include_non_contacts;
+ }
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+bool MessagesManager::need_dialog_in_list(const Dialog *d, const DialogList &list) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (d->order == DEFAULT_ORDER) {
+ return false;
+ }
+ if (list.dialog_list_id.is_folder()) {
+ return d->folder_id == list.dialog_list_id.get_folder_id();
+ }
+ if (list.dialog_list_id.is_filter()) {
+ auto dialog_filter_id = list.dialog_list_id.get_filter_id();
+ return need_dialog_in_filter(d, get_dialog_filter(dialog_filter_id));
+ }
+ UNREACHABLE();
+ return false;
+}
+
+bool MessagesManager::need_send_update_chat_position(const DialogPositionInList &old_position,
+ const DialogPositionInList &new_position) {
+ if (old_position.public_order != new_position.public_order) {
+ return true;
+ }
+ if (old_position.public_order == 0) {
+ return false;
+ }
+ return old_position.is_pinned != new_position.is_pinned || old_position.is_sponsored != new_position.is_sponsored;
+}
+
+MessagesManager::DialogPositionInList MessagesManager::get_dialog_position_in_list(const DialogList *list,
+ const Dialog *d, bool actual) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(list != nullptr);
+ CHECK(d != nullptr);
+
+ DialogPositionInList position;
+ position.order = d->order;
+ if (is_dialog_sponsored(d) || (actual ? need_dialog_in_list(d, *list) : is_dialog_in_list(d, list->dialog_list_id))) {
+ position.private_order = get_dialog_private_order(list, d);
+ }
+ if (position.private_order != 0) {
+ position.public_order =
+ DialogDate(position.private_order, d->dialog_id) <= list->list_last_dialog_date_ ? position.private_order : 0;
+ position.is_pinned = get_dialog_pinned_order(list, d->dialog_id) != DEFAULT_ORDER;
+ position.is_sponsored = is_dialog_sponsored(d);
+ }
+ position.total_dialog_count = get_dialog_total_count(*list);
+ return position;
+}
+
+std::unordered_map<DialogListId, MessagesManager::DialogPositionInList, DialogListIdHash>
+MessagesManager::get_dialog_positions(const Dialog *d) const {
+ CHECK(d != nullptr);
+ std::unordered_map<DialogListId, MessagesManager::DialogPositionInList, DialogListIdHash> positions;
+ if (!td_->auth_manager_->is_bot()) {
+ for (const auto &dialog_list : dialog_lists_) {
+ positions.emplace(dialog_list.first, get_dialog_position_in_list(&dialog_list.second, d));
+ }
+ }
+ return positions;
+}
+
+vector<DialogListId> MessagesManager::get_dialog_list_ids(const Dialog *d) {
+ return d->dialog_list_ids;
+}
+
+MessagesManager::DialogListView MessagesManager::get_dialog_lists(const Dialog *d) {
+ return DialogListView(this, get_dialog_list_ids(d));
+}
+
+MessagesManager::DialogList &MessagesManager::add_dialog_list(DialogListId dialog_list_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (dialog_list_id.is_folder() && dialog_list_id.get_folder_id() != FolderId::archive()) {
+ dialog_list_id = DialogListId(FolderId::main());
+ }
+ if (dialog_lists_.count(dialog_list_id) == 0) {
+ LOG(INFO) << "Create " << dialog_list_id;
+ }
+ auto &list = dialog_lists_[dialog_list_id];
+ list.dialog_list_id = dialog_list_id;
+ return list;
+}
+
+MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId dialog_list_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (dialog_list_id.is_folder() && dialog_list_id.get_folder_id() != FolderId::archive()) {
+ dialog_list_id = DialogListId(FolderId::main());
+ }
+ auto it = dialog_lists_.find(dialog_list_id);
+ if (it == dialog_lists_.end()) {
+ return nullptr;
+ }
+ return &it->second;
+}
+
+const MessagesManager::DialogList *MessagesManager::get_dialog_list(DialogListId dialog_list_id) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (dialog_list_id.is_folder() && dialog_list_id.get_folder_id() != FolderId::archive()) {
+ dialog_list_id = DialogListId(FolderId::main());
}
- if (!users_notification_settings_.is_synchronized) {
- td_->create_handler<GetNotifySettingsQuery>(Promise<>())->send(NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS);
+ auto it = dialog_lists_.find(dialog_list_id);
+ if (it == dialog_lists_.end()) {
+ return nullptr;
+ }
+ return &it->second;
+}
+
+MessagesManager::DialogFolder *MessagesManager::get_dialog_folder(FolderId folder_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (folder_id != FolderId::archive()) {
+ folder_id = FolderId::main();
}
- if (!chats_notification_settings_.is_synchronized) {
- td_->create_handler<GetNotifySettingsQuery>(Promise<>())->send(NOTIFICATION_SETTINGS_FOR_GROUP_CHATS);
+ auto it = dialog_folders_.find(folder_id);
+ if (it == dialog_folders_.end()) {
+ return nullptr;
+ }
+ return &it->second;
+}
+
+const MessagesManager::DialogFolder *MessagesManager::get_dialog_folder(FolderId folder_id) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (folder_id != FolderId::archive()) {
+ folder_id = FolderId::main();
}
- if (!dialogs_notification_settings_.is_synchronized) {
- td_->create_handler<GetNotifySettingsQuery>(Promise<>())->send(NOTIFICATION_SETTINGS_FOR_ALL_CHATS);
+ auto it = dialog_folders_.find(folder_id);
+ if (it == dialog_folders_.end()) {
+ return nullptr;
}
+ return &it->second;
}
string MessagesManager::get_channel_pts_key(DialogId dialog_id) {
CHECK(dialog_id.get_type() == DialogType::Channel);
auto channel_id = dialog_id.get_channel_id();
- return "ch.p" + to_string(channel_id.get());
+ return PSTRING() << "ch.p" << channel_id.get();
}
int32 MessagesManager::load_channel_pts(DialogId dialog_id) const {
+ if (G()->ignore_background_updates() || !have_input_peer(dialog_id, AccessRights::Read)) {
+ G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(dialog_id)); // just in case
+ return 0;
+ }
auto pts = to_integer<int32>(G()->td_db()->get_binlog_pmc()->get(get_channel_pts_key(dialog_id)));
LOG(INFO) << "Load " << dialog_id << " pts = " << pts;
return pts;
}
-void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *source) const {
+void MessagesManager::set_channel_pts(Dialog *d, int32 new_pts, const char *source) {
CHECK(d != nullptr);
CHECK(d->dialog_id.get_type() == DialogType::Channel);
LOG_IF(ERROR, running_get_channel_difference(d->dialog_id))
- << "Set pts of " << d->dialog_id << " to " << new_pts << " while running getChannelDifference";
+ << "Set pts of " << d->dialog_id << " to " << new_pts << " from " << source
+ << " while running getChannelDifference";
if (is_debug_message_op_enabled()) {
- d->debug_message_op.emplace_back(Dialog::MessageOp::SetPts, MessageId(), new_pts, false, false, false, source);
+ d->debug_message_op.emplace_back(Dialog::MessageOp::SetPts, new_pts, source);
}
// TODO delete_first_messages support in channels
if (new_pts == std::numeric_limits<int32>::max()) {
- LOG(ERROR) << "Update " << d->dialog_id << " pts to -1";
+ LOG(ERROR) << "Update " << d->dialog_id << " pts to -1 from " << source;
G()->td_db()->get_binlog_pmc()->erase(get_channel_pts_key(d->dialog_id));
d->pts = std::numeric_limits<int32>::max();
+ if (d->pending_read_channel_inbox_pts != 0) {
+ d->pending_read_channel_inbox_pts = 0;
+ }
return;
}
if (new_pts > d->pts || (0 < new_pts && new_pts < d->pts - 99999)) { // pts can only go up or drop cardinally
if (new_pts < d->pts - 99999) {
- LOG(WARNING) << "Pts of " << d->dialog_id << " decreases from " << d->pts << " to " << new_pts;
+ LOG(WARNING) << "Pts of " << d->dialog_id << " decreases from " << d->pts << " to " << new_pts << " from "
+ << source;
} else {
- LOG(INFO) << "Update " << d->dialog_id << " pts to " << new_pts;
+ LOG(INFO) << "Update " << d->dialog_id << " pts to " << new_pts << " from " << source;
}
d->pts = new_pts;
- G()->td_db()->get_binlog_pmc()->set(get_channel_pts_key(d->dialog_id), to_string(new_pts));
+ if (d->pending_read_channel_inbox_pts != 0 && d->pending_read_channel_inbox_pts <= d->pts) {
+ auto pts = d->pending_read_channel_inbox_pts;
+ d->pending_read_channel_inbox_pts = 0;
+ on_dialog_updated(d->dialog_id, "set_channel_pts");
+ if (d->pts == pts) {
+ read_history_inbox(d->dialog_id, d->pending_read_channel_inbox_max_message_id,
+ d->pending_read_channel_inbox_server_unread_count, "set_channel_pts");
+ } else if (d->pts > pts) {
+ repair_channel_server_unread_count(d);
+ }
+ }
+ if (!G()->ignore_background_updates() && have_input_peer(d->dialog_id, AccessRights::Read)) {
+ G()->td_db()->get_binlog_pmc()->set(get_channel_pts_key(d->dialog_id), to_string(new_pts));
+ }
} else if (new_pts < d->pts) {
- LOG(ERROR) << "Receive wrong pts " << new_pts << " in " << d->dialog_id << " . Current pts is " << d->pts;
+ LOG(ERROR) << "Receive wrong pts " << new_pts << " in " << d->dialog_id << " from " << source << ". Current pts is "
+ << d->pts;
}
}
+bool MessagesManager::need_channel_difference_to_add_message(DialogId dialog_id,
+ const tl_object_ptr<telegram_api::Message> &message_ptr) {
+ if (dialog_id.get_type() != DialogType::Channel || !have_input_peer(dialog_id, AccessRights::Read) ||
+ dialog_id == debug_channel_difference_dialog_) {
+ return false;
+ }
+ if (message_ptr == nullptr) {
+ return true;
+ }
+ if (get_message_dialog_id(message_ptr) != dialog_id) {
+ return false;
+ }
+
+ Dialog *d = get_dialog_force(dialog_id, "need_channel_difference_to_add_message");
+ if (d == nullptr) {
+ return load_channel_pts(dialog_id) > 0 && !is_channel_difference_finished_.count(dialog_id);
+ }
+ if (d->last_new_message_id == MessageId()) {
+ return d->pts > 0 && !d->is_channel_difference_finished;
+ }
+
+ return get_message_id(message_ptr, false) > d->last_new_message_id;
+}
+
+void MessagesManager::run_after_channel_difference(DialogId dialog_id, Promise<Unit> &&promise) {
+ CHECK(dialog_id.get_type() == DialogType::Channel);
+ CHECK(have_input_peer(dialog_id, AccessRights::Read));
+
+ run_after_get_channel_difference_[dialog_id].push_back(std::move(promise));
+
+ const Dialog *d = get_dialog(dialog_id);
+ get_channel_difference(dialog_id, d == nullptr ? load_channel_pts(dialog_id) : d->pts, true,
+ "run_after_channel_difference");
+}
+
bool MessagesManager::running_get_channel_difference(DialogId dialog_id) const {
return active_get_channel_differencies_.count(dialog_id) > 0;
}
+void MessagesManager::on_channel_get_difference_timeout(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(dialog_id.get_type() == DialogType::Channel);
+ auto d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ get_channel_difference(dialog_id, d->pts, true, "on_channel_get_difference_timeout");
+}
+
class MessagesManager::GetChannelDifferenceLogEvent {
public:
ChannelId channel_id;
@@ -23648,38 +39024,54 @@ class MessagesManager::GetChannelDifferenceLogEvent {
}
};
-void MessagesManager::get_channel_difference(DialogId dialog_id, int32 pts, bool force, const char *source) {
+void MessagesManager::get_channel_difference(DialogId dialog_id, int32 pts, bool force, const char *source,
+ bool is_old) {
if (channel_get_difference_retry_timeout_.has_timeout(dialog_id.get())) {
LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source
<< " because it is scheduled for later time";
return;
}
+ LOG_CHECK(dialog_id.get_type() == DialogType::Channel) << dialog_id << " " << source;
+
+ if (active_get_channel_differencies_.count(dialog_id)) {
+ LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source
+ << " because it has already been run";
+ return;
+ }
+
+ debug_last_get_channel_difference_dialog_id_ = dialog_id;
+ debug_last_get_channel_difference_source_ = source;
auto input_channel = td_->contacts_manager_->get_input_channel(dialog_id.get_channel_id());
if (input_channel == nullptr) {
LOG(ERROR) << "Skip running channels.getDifference for " << dialog_id << " from " << source
- << " because have no info about the chat";
+ << " because the channel is unknown";
+ after_get_channel_difference(dialog_id, false);
+ return;
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ LOG(INFO) << "Skip running channels.getDifference for " << dialog_id << " from " << source
+ << " because have no read access to it";
after_get_channel_difference(dialog_id, false);
return;
}
- if (force && get_channel_difference_to_logevent_id_.count(dialog_id) == 0) {
+ if (force && get_channel_difference_to_log_event_id_.count(dialog_id) == 0 && !G()->ignore_background_updates()) {
auto channel_id = dialog_id.get_channel_id();
CHECK(input_channel->get_id() == telegram_api::inputChannel::ID);
auto access_hash = static_cast<const telegram_api::inputChannel &>(*input_channel).access_hash_;
- auto logevent = GetChannelDifferenceLogEvent(channel_id, access_hash);
- auto storer = LogEventStorerImpl<GetChannelDifferenceLogEvent>(logevent);
- auto logevent_id =
- BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::GetChannelDifference, storer);
+ auto log_event = GetChannelDifferenceLogEvent(channel_id, access_hash);
+ auto log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::GetChannelDifference,
+ get_log_event_storer(log_event));
- get_channel_difference_to_logevent_id_.emplace(dialog_id, logevent_id);
+ get_channel_difference_to_log_event_id_.emplace(dialog_id, log_event_id);
}
- return do_get_channel_difference(dialog_id, pts, force, std::move(input_channel), source);
+ return do_get_channel_difference(dialog_id, pts, force, std::move(input_channel), is_old, source);
}
void MessagesManager::do_get_channel_difference(DialogId dialog_id, int32 pts, bool force,
- tl_object_ptr<telegram_api::InputChannel> &&input_channel,
+ tl_object_ptr<telegram_api::InputChannel> &&input_channel, bool is_old,
const char *source) {
auto inserted = active_get_channel_differencies_.emplace(dialog_id, source);
if (!inserted.second) {
@@ -23687,8 +39079,22 @@ void MessagesManager::do_get_channel_difference(DialogId dialog_id, int32 pts, b
<< " because it has already been run";
return;
}
+ // must work even we know nothing about the dialog
+
+ // can be called multiple times before after_get_channel_difference
+ const Dialog *d = get_dialog(dialog_id);
+ if (d != nullptr) {
+ if (d->message_notification_group.group_id.is_valid()) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference,
+ d->message_notification_group.group_id);
+ }
+ if (d->mention_notification_group.group_id.is_valid()) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::before_get_chat_difference,
+ d->mention_notification_group.group_id);
+ }
+ }
- int32 limit = td_->auth_manager_->is_bot() ? MAX_BOT_CHANNEL_DIFFERENCE : MAX_CHANNEL_DIFFERENCE;
+ int32 limit = td_->auth_manager_->is_bot() && !is_old ? MAX_BOT_CHANNEL_DIFFERENCE : MAX_CHANNEL_DIFFERENCE;
if (pts <= 0) {
pts = 1;
limit = MIN_CHANNEL_DIFFERENCE;
@@ -23701,48 +39107,181 @@ void MessagesManager::do_get_channel_difference(DialogId dialog_id, int32 pts, b
}
void MessagesManager::process_get_channel_difference_updates(
- DialogId dialog_id, vector<tl_object_ptr<telegram_api::Message>> &&new_messages,
+ DialogId dialog_id, int32 new_pts, vector<tl_object_ptr<telegram_api::Message>> &&new_messages,
vector<tl_object_ptr<telegram_api::Update>> &&other_updates) {
LOG(INFO) << "In get channel difference for " << dialog_id << " receive " << new_messages.size() << " messages and "
<< other_updates.size() << " other updates";
- for (auto &update : other_updates) {
- if (update->get_id() == telegram_api::updateMessageID::ID) {
- auto sent_message_update = move_tl_object_as<telegram_api::updateMessageID>(update);
- on_update_message_id(sent_message_update->random_id_, MessageId(ServerMessageId(sent_message_update->id_)),
- "get_channel_difference");
- update = nullptr;
+ CHECK(!debug_channel_difference_dialog_.is_valid());
+ debug_channel_difference_dialog_ = dialog_id;
+
+ // identifiers of edited and deleted messages
+ FlatHashSet<MessageId, MessageIdHash> changed_message_ids;
+ for (auto &update_ptr : other_updates) {
+ bool is_good_update = true;
+ switch (update_ptr->get_id()) {
+ case telegram_api::updateMessageID::ID: {
+ // in channels.getDifference updateMessageID can't be received for scheduled messages
+ auto sent_message_update = move_tl_object_as<telegram_api::updateMessageID>(update_ptr);
+ on_update_message_id(sent_message_update->random_id_, MessageId(ServerMessageId(sent_message_update->id_)),
+ "get_channel_difference");
+ update_ptr = nullptr;
+ break;
+ }
+ case telegram_api::updateDeleteChannelMessages::ID: {
+ auto *update = static_cast<const telegram_api::updateDeleteChannelMessages *>(update_ptr.get());
+ if (DialogId(ChannelId(update->channel_id_)) != dialog_id) {
+ is_good_update = false;
+ } else {
+ for (auto &message : update->messages_) {
+ changed_message_ids.insert(MessageId(ServerMessageId(message)));
+ }
+ }
+ break;
+ }
+ case telegram_api::updateEditChannelMessage::ID: {
+ auto *update = static_cast<const telegram_api::updateEditChannelMessage *>(update_ptr.get());
+ auto full_message_id = get_full_message_id(update->message_, false);
+ if (full_message_id.get_dialog_id() != dialog_id) {
+ is_good_update = false;
+ } else {
+ changed_message_ids.insert(full_message_id.get_message_id());
+ }
+ break;
+ }
+ case telegram_api::updatePinnedChannelMessages::ID: {
+ auto *update = static_cast<const telegram_api::updatePinnedChannelMessages *>(update_ptr.get());
+ if (DialogId(ChannelId(update->channel_id_)) != dialog_id) {
+ is_good_update = false;
+ }
+ break;
+ }
+ default:
+ is_good_update = false;
+ break;
+ }
+ if (!is_good_update) {
+ LOG(ERROR) << "Receive wrong update in channelDifference of " << dialog_id << ": " << to_string(update_ptr);
+ update_ptr = nullptr;
}
}
+ auto is_edited_message = [](const tl_object_ptr<telegram_api::Message> &message) {
+ if (message->get_id() != telegram_api::message::ID) {
+ return false;
+ }
+ return static_cast<const telegram_api::message *>(message.get())->edit_date_ > 0;
+ };
+
for (auto &message : new_messages) {
- on_get_message(std::move(message), true, true, true, true, "get channel difference");
+ if (is_edited_message(message)) {
+ auto message_id = get_message_id(message, false);
+ if (message_id.is_valid()) {
+ changed_message_ids.insert(message_id);
+ }
+ }
+ }
+
+ // extract awaited sent messages, which were edited or deleted after that
+ auto postponed_updates_it = postponed_channel_updates_.find(dialog_id);
+ struct AwaitedMessage {
+ tl_object_ptr<telegram_api::Message> message;
+ Promise<Unit> promise;
+ };
+ std::map<MessageId, AwaitedMessage> awaited_messages;
+ if (postponed_updates_it != postponed_channel_updates_.end()) {
+ auto &updates = postponed_updates_it->second;
+ while (!updates.empty()) {
+ auto it = updates.begin();
+ auto update_pts = it->second.pts;
+ if (update_pts > new_pts) {
+ break;
+ }
+
+ auto update = std::move(it->second.update);
+ auto promise = std::move(it->second.promise);
+ updates.erase(it);
+
+ if (update->get_id() == telegram_api::updateNewChannelMessage::ID) {
+ auto update_new_channel_message = static_cast<telegram_api::updateNewChannelMessage *>(update.get());
+ auto message_id = get_message_id(update_new_channel_message->message_, false);
+ FullMessageId full_message_id(dialog_id, message_id);
+ if (update_message_ids_.count(full_message_id) > 0 && changed_message_ids.count(message_id) > 0) {
+ changed_message_ids.erase(message_id);
+ AwaitedMessage awaited_message;
+ awaited_message.message = std::move(update_new_channel_message->message_);
+ awaited_message.promise = std::move(promise);
+ awaited_messages.emplace(message_id, std::move(awaited_message));
+ continue;
+ }
+ }
+
+ LOG(INFO) << "Skip to be applied from getChannelDifference " << to_string(update);
+ promise.set_value(Unit());
+ }
+ if (updates.empty()) {
+ postponed_channel_updates_.erase(postponed_updates_it);
+ }
+ }
+
+ // if last message is pretty old, we might have missed the update
+ bool need_repair_unread_count =
+ !new_messages.empty() && get_message_date(new_messages[0]) < G()->unix_time() - 2 * 86400;
+
+ auto it = awaited_messages.begin();
+ for (auto &message : new_messages) {
+ auto message_id = get_message_id(message, false);
+ while (it != awaited_messages.end() && it->first < message_id) {
+ on_get_message(std::move(it->second.message), true, true, false, true, true, "postponed channel update");
+ it->second.promise.set_value(Unit());
+ ++it;
+ }
+ Promise<Unit> promise;
+ if (it != awaited_messages.end() && it->first == message_id) {
+ if (is_edited_message(message)) {
+ // the new message is edited, apply postponed one and move this to updateEditChannelMessage
+ other_updates.push_back(make_tl_object<telegram_api::updateEditChannelMessage>(std::move(message), new_pts, 0));
+ message = std::move(it->second.message);
+ promise = std::move(it->second.promise);
+ } else {
+ it->second.promise.set_value(Unit());
+ }
+ ++it;
+ }
+ on_get_message(std::move(message), true, true, false, true, true, "get channel difference");
+ promise.set_value(Unit());
+ }
+ while (it != awaited_messages.end()) {
+ on_get_message(std::move(it->second.message), true, true, false, true, true, "postponed channel update 2");
+ it->second.promise.set_value(Unit());
+ ++it;
}
for (auto &update : other_updates) {
if (update != nullptr) {
- switch (update->get_id()) {
- case telegram_api::updateDeleteChannelMessages::ID:
- process_channel_update(std::move(update));
- break;
- case telegram_api::updateEditChannelMessage::ID:
- process_channel_update(std::move(update));
- break;
- default:
- LOG(ERROR) << "Unsupported update received in getChannelDifference: " << oneline(to_string(update));
- break;
- }
+ process_channel_update(std::move(update));
}
}
- CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differencies_[dialog_id] << '"';
+ LOG_CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differencies_[dialog_id] << '"';
+
+ if (need_repair_unread_count) {
+ repair_channel_server_unread_count(get_dialog(dialog_id));
+ }
+
+ CHECK(debug_channel_difference_dialog_ == dialog_id);
+ debug_channel_difference_dialog_ = DialogId();
}
void MessagesManager::on_get_channel_dialog(DialogId dialog_id, MessageId last_message_id,
MessageId read_inbox_max_message_id, int32 server_unread_count,
- int32 unread_mention_count, MessageId read_outbox_max_message_id,
+ int32 unread_mention_count, int32 unread_reaction_count,
+ MessageId read_outbox_max_message_id,
vector<tl_object_ptr<telegram_api::Message>> &&messages) {
- std::unordered_map<FullMessageId, tl_object_ptr<telegram_api::Message>, FullMessageIdHash> full_message_id_to_message;
+ FlatHashMap<FullMessageId, tl_object_ptr<telegram_api::Message>, FullMessageIdHash> full_message_id_to_message;
for (auto &message : messages) {
- auto message_id = get_message_id(message);
+ auto message_id = get_message_id(message, false);
+ if (!message_id.is_valid()) {
+ continue;
+ }
auto message_dialog_id = get_message_dialog_id(message);
if (!message_dialog_id.is_valid()) {
message_dialog_id = dialog_id;
@@ -23760,19 +39299,16 @@ void MessagesManager::on_get_channel_dialog(DialogId dialog_id, MessageId last_m
}
return;
}
- } else {
- LOG(ERROR) << "Receive as last " << last_message_id;
- return;
}
+ CHECK(!last_message_id.is_scheduled());
Dialog *d = get_dialog(dialog_id);
CHECK(d != nullptr);
- // TODO gaps support
// There are many ways of handling a gap in a channel:
// 1) Delete all known messages in the chat, begin from scratch. It is easy to implement, but suddenly disappearing
// messages looks awful for the user.
- // 2) Save all messages loaded in the memory until application restart, but delete all messages from database.
+ // 2) Save all messages loaded in the memory until application restart, but delete all messages from the database.
// Messages left in the memory must be lazily updated using calls to getHistory. It looks much smoothly for the
// user, he will need to redownload messages only after client restart. Unsynchronized messages left in the
// memory shouldn't be saved to database, results of getHistory and getMessage must be used to update state of
@@ -23785,50 +39321,67 @@ void MessagesManager::on_get_channel_dialog(DialogId dialog_id, MessageId last_m
// as results of getChatHistory and (if implemented continuous ranges support for searching shared media)
// searchChatMessages. The messages should still be lazily checked using getHistory, but they are still available
// offline. It is the best way for gaps support, but it is pretty hard to implement correctly.
- // It should be also noted that some messages like live location messages shouldn't be deleted.
- // delete_all_dialog_messages_from_database(dialog_id, d->last_database_message_id, "on_get_channel_dialog");
+ // It should be also noted that some messages like outgoing live location messages shouldn't be deleted.
- set_dialog_first_database_message_id(d, MessageId(), "on_get_channel_dialog");
- set_dialog_last_database_message_id(d, MessageId(), "on_get_channel_dialog");
- d->have_full_history = false;
- for (auto &first_message_id : d->first_database_message_id_by_index) {
- first_message_id = MessageId();
+ if (last_message_id > d->last_new_message_id) {
+ // TODO properly support last_message_id <= d->last_new_message_id
+ set_dialog_first_database_message_id(d, MessageId(), "on_get_channel_dialog 6");
+ set_dialog_last_database_message_id(d, MessageId(), "on_get_channel_dialog 7");
+ d->have_full_history = false;
+ d->have_full_history_source = 0;
+ d->is_empty = false;
}
- std::fill(d->message_count_by_index.begin(), d->message_count_by_index.end(), -1);
+ invalidate_message_indexes(d);
+ // d->history_generation++;
on_dialog_updated(dialog_id, "on_get_channel_dialog 10");
- // TODO support last_message_id.get() < d->last_new_message_id.get()
- if (last_message_id.get() > d->last_new_message_id.get()) { // if last message is really a new message
+ // TODO properly support last_message_id <= d->last_new_message_id
+ if (last_message_id > d->last_new_message_id) { // if last message is really a new message
+ if (!d->last_new_message_id.is_valid() && last_message_id <= d->max_added_message_id) {
+ auto prev_message_id = MessageId(ServerMessageId(last_message_id.get_server_message_id().get() - 1));
+ remove_dialog_newer_messages(d, prev_message_id, "on_get_channel_dialog 15");
+ }
d->last_new_message_id = MessageId();
set_dialog_last_message_id(d, MessageId(), "on_get_channel_dialog 20");
send_update_chat_last_message(d, "on_get_channel_dialog 30");
- auto added_full_message_id = on_get_message(std::move(full_message_id_to_message[last_full_message_id]), true, true,
- true, true, "channel difference too long");
+ FullMessageId added_full_message_id;
+ if (last_full_message_id.get_message_id().is_valid()) {
+ last_full_message_id = on_get_message(std::move(full_message_id_to_message[last_full_message_id]), true, true,
+ false, true, true, "channel difference too long");
+ }
if (added_full_message_id.get_message_id().is_valid()) {
if (added_full_message_id.get_message_id() == d->last_new_message_id) {
CHECK(last_full_message_id == added_full_message_id);
CHECK(d->last_message_id == d->last_new_message_id);
} else {
- LOG(ERROR) << added_full_message_id << " doesn't became last new message";
+ LOG(ERROR) << added_full_message_id << " doesn't became last new message, which is " << d->last_new_message_id;
dump_debug_message_op(d, 2);
}
- } else {
- set_dialog_last_new_message_id(d, last_full_message_id.get_message_id(),
+ } else if (last_message_id > d->last_new_message_id) {
+ set_dialog_last_new_message_id(d, last_message_id,
"on_get_channel_dialog 40"); // skip updates about some messages
}
}
+ if (d->last_read_inbox_message_id.is_valid() && !d->last_read_inbox_message_id.is_server() &&
+ read_inbox_max_message_id == d->last_read_inbox_message_id.get_prev_server_message_id()) {
+ read_inbox_max_message_id = d->last_read_inbox_message_id;
+ }
if (d->server_unread_count != server_unread_count || d->last_read_inbox_message_id != read_inbox_max_message_id) {
set_dialog_last_read_inbox_message_id(d, read_inbox_max_message_id, server_unread_count, d->local_unread_count,
false, "on_get_channel_dialog 50");
}
if (d->unread_mention_count != unread_mention_count) {
- d->unread_mention_count = unread_mention_count;
- d->message_count_by_index[search_messages_filter_index(SearchMessagesFilter::UnreadMention)] =
- d->unread_mention_count;
+ set_dialog_unread_mention_count(d, unread_mention_count);
+ update_dialog_mention_notification_count(d);
send_update_chat_unread_mention_count(d);
}
+ if (d->unread_reaction_count != unread_reaction_count) {
+ set_dialog_unread_reaction_count(d, unread_reaction_count);
+ // update_dialog_mention_notification_count(d);
+ send_update_chat_unread_reaction_count(d, "on_get_channel_dialog 60");
+ }
if (d->last_read_outbox_message_id != read_outbox_max_message_id) {
set_dialog_last_read_outbox_message_id(d, read_outbox_max_message_id);
@@ -23841,16 +39394,23 @@ void MessagesManager::on_get_channel_difference(
LOG(INFO) << "----- END GET CHANNEL DIFFERENCE----- for " << dialog_id;
CHECK(active_get_channel_differencies_.count(dialog_id) == 1);
active_get_channel_differencies_.erase(dialog_id);
- auto d = get_dialog_force(dialog_id);
+ auto d = get_dialog_force(dialog_id, "on_get_channel_difference");
if (difference_ptr == nullptr) {
bool have_access = have_input_peer(dialog_id, AccessRights::Read);
if (have_access) {
- CHECK(d != nullptr);
- channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(), d->retry_get_difference_timeout);
- d->retry_get_difference_timeout *= 2;
- if (d->retry_get_difference_timeout > 60) {
- d->retry_get_difference_timeout = Random::fast(60, 80);
+ if (d == nullptr) {
+ force_create_dialog(dialog_id, "on_get_channel_difference failed");
+ }
+ auto &delay = channel_get_difference_retry_timeouts_[dialog_id];
+ if (delay == 0) {
+ delay = 1;
+ }
+ channel_get_difference_retry_timeout_.add_timeout_in(dialog_id.get(),
+ Random::fast(delay * 1000, delay * 1500) * 1e-3);
+ delay *= 2;
+ if (delay > 60) {
+ delay = Random::fast(60, 80);
}
} else {
after_get_channel_difference(dialog_id, false);
@@ -23858,9 +39418,46 @@ void MessagesManager::on_get_channel_difference(
return;
}
+ channel_get_difference_retry_timeouts_.erase(dialog_id);
+
+ LOG(INFO) << "Receive result of getChannelDifference for " << dialog_id << " with pts = " << request_pts
+ << " and limit = " << request_limit << ": " << to_string(difference_ptr);
+
+ bool have_new_messages = false;
+ switch (difference_ptr->get_id()) {
+ case telegram_api::updates_channelDifferenceEmpty::ID:
+ if (d == nullptr) {
+ // no need to create the dialog
+ after_get_channel_difference(dialog_id, true);
+ return;
+ }
+ break;
+ case telegram_api::updates_channelDifference::ID: {
+ auto difference = static_cast<telegram_api::updates_channelDifference *>(difference_ptr.get());
+ have_new_messages = !difference->new_messages_.empty();
+ td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.channelDifference");
+ td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifference");
+ break;
+ }
+ case telegram_api::updates_channelDifferenceTooLong::ID: {
+ auto difference = static_cast<telegram_api::updates_channelDifferenceTooLong *>(difference_ptr.get());
+ have_new_messages = difference->dialog_->get_id() == telegram_api::dialog::ID && !difference->messages_.empty();
+ td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.channelDifferenceTooLong");
+ td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.channelDifferenceTooLong");
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
bool need_update_dialog_pos = false;
if (d == nullptr) {
- d = add_dialog(dialog_id);
+ if (have_new_messages) {
+ CHECK(!being_added_by_new_message_dialog_id_.is_valid());
+ being_added_by_new_message_dialog_id_ = dialog_id;
+ }
+ d = add_dialog(dialog_id, "on_get_channel_difference");
+ being_added_by_new_message_dialog_id_ = DialogId();
need_update_dialog_pos = true;
}
@@ -23868,12 +39465,8 @@ void MessagesManager::on_get_channel_difference(
LOG_IF(ERROR, cur_pts != request_pts) << "Channel pts has changed from " << request_pts << " to " << d->pts << " in "
<< dialog_id << " during getChannelDifference";
- LOG(INFO) << "Receive result of getChannelDifference for " << dialog_id << " with pts = " << request_pts
- << " and limit = " << request_limit << ": " << to_string(difference_ptr);
-
- d->retry_get_difference_timeout = 1;
-
bool is_final = true;
+ bool is_old = false;
int32 timeout = 0;
switch (difference_ptr->get_id()) {
case telegram_api::updates_channelDifferenceEmpty::ID: {
@@ -23888,10 +39481,13 @@ void MessagesManager::on_get_channel_difference(
}
// bots can receive channelDifferenceEmpty with pts bigger than known pts
- LOG_IF(ERROR, request_pts != difference->pts_ && !td_->auth_manager_->is_bot())
- << "Receive channelDifferenceEmpty as result of getChannelDifference with pts = " << request_pts
- << " and limit = " << request_limit << " in " << dialog_id << ", but pts has changed from " << request_pts
- << " to " << difference->pts_;
+ // also, this can happen for deleted channels
+ if (request_pts != difference->pts_ && !td_->auth_manager_->is_bot() &&
+ have_input_peer(dialog_id, AccessRights::Read)) {
+ LOG(ERROR) << "Receive channelDifferenceEmpty as result of getChannelDifference with pts = " << request_pts
+ << " and limit = " << request_limit << " in " << dialog_id << ", but pts has changed to "
+ << difference->pts_;
+ }
set_channel_pts(d, difference->pts_, "channel difference empty");
break;
}
@@ -23905,17 +39501,36 @@ void MessagesManager::on_get_channel_difference(
}
auto new_pts = difference->pts_;
- if (request_pts >= new_pts) {
+ if (request_pts >= new_pts && request_pts > 1 && (request_pts > new_pts || !td_->auth_manager_->is_bot())) {
LOG(ERROR) << "Receive channelDifference as result of getChannelDifference with pts = " << request_pts
<< " and limit = " << request_limit << " in " << dialog_id << ", but pts has changed from " << d->pts
<< " to " << new_pts << ". Difference: " << oneline(to_string(difference));
new_pts = request_pts + 1;
}
- td_->contacts_manager_->on_get_users(std::move(difference->users_));
- td_->contacts_manager_->on_get_chats(std::move(difference->chats_));
+ if (difference->new_messages_.size() > 1) {
+ // check that new messages are received in increasing message_id order
+ MessageId cur_message_id;
+ for (const auto &message : difference->new_messages_) {
+ auto message_id = get_message_id(message, false);
+ if (message_id <= cur_message_id) {
+ LOG(ERROR) << "Receive " << cur_message_id << " after " << message_id << " in channelDifference of "
+ << dialog_id << " with pts " << request_pts << " and limit " << request_limit << ": "
+ << to_string(difference);
+ after_get_channel_difference(dialog_id, false);
+ return;
+ }
+ cur_message_id = message_id;
+ }
+ }
+ if (!is_final && !difference->new_messages_.empty() && td_->auth_manager_->is_bot()) {
+ auto date = get_message_date(difference->new_messages_.back());
+ if (0 < date && date < G()->unix_time() - 2 * 86400) {
+ is_old = true;
+ }
+ }
- process_get_channel_difference_updates(dialog_id, std::move(difference->new_messages_),
+ process_get_channel_difference_updates(dialog_id, new_pts, std::move(difference->new_messages_),
std::move(difference->other_updates_));
set_channel_pts(d, new_pts, "channel difference");
@@ -23924,14 +39539,32 @@ void MessagesManager::on_get_channel_difference(
case telegram_api::updates_channelDifferenceTooLong::ID: {
auto difference = move_tl_object_as<telegram_api::updates_channelDifferenceTooLong>(difference_ptr);
+ telegram_api::dialog *dialog = nullptr;
+ switch (difference->dialog_->get_id()) {
+ case telegram_api::dialog::ID:
+ dialog = static_cast<telegram_api::dialog *>(difference->dialog_.get());
+ break;
+ case telegram_api::dialogFolder::ID:
+ return after_get_channel_difference(dialog_id, false);
+ default:
+ UNREACHABLE();
+ return;
+ }
+
+ CHECK(dialog != nullptr);
+ if ((dialog->flags_ & telegram_api::dialog::PTS_MASK) == 0) {
+ LOG(ERROR) << "Receive " << dialog_id << " without pts";
+ return after_get_channel_difference(dialog_id, false);
+ }
+
int32 flags = difference->flags_;
is_final = (flags & CHANNEL_DIFFERENCE_FLAG_IS_FINAL) != 0;
if (flags & CHANNEL_DIFFERENCE_FLAG_HAS_TIMEOUT) {
timeout = difference->timeout_;
}
- auto new_pts = difference->pts_;
- if (request_pts + request_limit > new_pts) {
+ auto new_pts = dialog->pts_;
+ if (request_pts > new_pts - request_limit) {
LOG(ERROR) << "Receive channelDifferenceTooLong as result of getChannelDifference with pts = " << request_pts
<< " and limit = " << request_limit << " in " << dialog_id << ", but pts has changed from " << d->pts
<< " to " << new_pts << ". Difference: " << oneline(to_string(difference));
@@ -23940,15 +39573,33 @@ void MessagesManager::on_get_channel_difference(
}
}
- td_->contacts_manager_->on_get_users(std::move(difference->users_));
- td_->contacts_manager_->on_get_chats(std::move(difference->chats_));
+ set_dialog_folder_id(d, FolderId((dialog->flags_ & DIALOG_FLAG_HAS_FOLDER_ID) != 0 ? dialog->folder_id_ : 0));
- on_get_channel_dialog(dialog_id, MessageId(ServerMessageId(difference->top_message_)),
- MessageId(ServerMessageId(difference->read_inbox_max_id_)), difference->unread_count_,
- difference->unread_mentions_count_,
- MessageId(ServerMessageId(difference->read_outbox_max_id_)),
- std::move(difference->messages_));
- need_update_dialog_pos = true;
+ on_update_dialog_notify_settings(dialog_id, std::move(dialog->notify_settings_),
+ "updates.channelDifferenceTooLong");
+
+ bool is_marked_as_unread = dialog->unread_mark_;
+ if (is_marked_as_unread != d->is_marked_as_unread) {
+ set_dialog_is_marked_as_unread(d, is_marked_as_unread);
+ }
+
+ update_dialog_draft_message(d, get_draft_message(td_->contacts_manager_.get(), std::move(dialog->draft_)), true,
+ false);
+
+ on_get_channel_dialog(dialog_id, MessageId(ServerMessageId(dialog->top_message_)),
+ MessageId(ServerMessageId(dialog->read_inbox_max_id_)), dialog->unread_count_,
+ dialog->unread_mentions_count_, dialog->unread_reactions_count_,
+ MessageId(ServerMessageId(dialog->read_outbox_max_id_)), std::move(difference->messages_));
+ update_dialog_pos(d, "updates.channelDifferenceTooLong");
+
+ if (!td_->auth_manager_->is_bot()) {
+ // set is_pinned only after updating dialog pos to ensure that order is initialized
+ bool is_pinned = (dialog->flags_ & DIALOG_FLAG_IS_PINNED) != 0;
+ bool was_pinned = is_dialog_pinned(DialogListId(d->folder_id), dialog_id);
+ if (is_pinned != was_pinned) {
+ set_dialog_is_pinned(DialogListId(d->folder_id), d, is_pinned);
+ }
+ }
set_channel_pts(d, new_pts, "channel difference too long");
break;
@@ -23958,12 +39609,12 @@ void MessagesManager::on_get_channel_difference(
}
if (need_update_dialog_pos) {
- update_dialog_pos(d, false, "on_get_channel_difference");
+ update_dialog_pos(d, "on_get_channel_difference");
}
if (!is_final) {
- LOG_IF(ERROR, timeout > 0) << "Have timeout in not final ChannelDifference in " << dialog_id;
- get_channel_difference(dialog_id, d->pts, true, "on_get_channel_difference");
+ LOG_IF(ERROR, timeout > 0) << "Have timeout in nonfinal ChannelDifference in " << dialog_id;
+ get_channel_difference(dialog_id, d->pts, true, "on_get_channel_difference", is_old);
return;
}
@@ -23975,67 +39626,93 @@ void MessagesManager::on_get_channel_difference(
}
void MessagesManager::after_get_channel_difference(DialogId dialog_id, bool success) {
- CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differencies_[dialog_id] << '"';
+ LOG(INFO) << "After " << (success ? "" : "un") << "successful get channel difference in " << dialog_id;
+ LOG_CHECK(!running_get_channel_difference(dialog_id)) << '"' << active_get_channel_differencies_[dialog_id] << '"';
- auto logevent_it = get_channel_difference_to_logevent_id_.find(dialog_id);
- if (logevent_it != get_channel_difference_to_logevent_id_.end()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_it->second);
- get_channel_difference_to_logevent_id_.erase(logevent_it);
+ auto log_event_it = get_channel_difference_to_log_event_id_.find(dialog_id);
+ if (log_event_it != get_channel_difference_to_log_event_id_.end()) {
+ if (!G()->close_flag()) {
+ binlog_erase(G()->td_db()->get_binlog(), log_event_it->second);
+ }
+ get_channel_difference_to_log_event_id_.erase(log_event_it);
}
auto d = get_dialog(dialog_id);
bool have_access = have_input_peer(dialog_id, AccessRights::Read);
- if (!have_access) {
- if (d != nullptr) {
- d->postponed_channel_updates.clear();
- }
- } else if (d->postponed_channel_updates.size()) {
- LOG(INFO) << "Begin to apply postponed channel updates";
- while (!d->postponed_channel_updates.empty()) {
- auto it = d->postponed_channel_updates.begin();
+ auto pts = d != nullptr ? d->pts : load_channel_pts(dialog_id);
+ auto postponed_updates_it = postponed_channel_updates_.find(dialog_id);
+ if (postponed_updates_it != postponed_channel_updates_.end()) {
+ auto &updates = postponed_updates_it->second;
+ LOG(INFO) << "Begin to apply " << updates.size() << " postponed channel updates";
+ while (!updates.empty()) {
+ auto it = updates.begin();
auto update = std::move(it->second.update);
auto update_pts = it->second.pts;
auto update_pts_count = it->second.pts_count;
- d->postponed_channel_updates.erase(it);
- auto old_size = d->postponed_channel_updates.size();
+ auto promise = std::move(it->second.promise);
+ updates.erase(it);
+
+ auto old_size = updates.size();
auto update_id = update->get_id();
- add_pending_channel_update(dialog_id, std::move(update), update_pts, update_pts_count,
- "apply postponed channel updates", true);
- if (d->postponed_channel_updates.size() != old_size || running_get_channel_difference(dialog_id)) {
- if (success && update_pts < d->pts + 10000 && update_pts_count == 1) {
+ if (have_access) {
+ add_pending_channel_update(dialog_id, std::move(update), update_pts, update_pts_count, std::move(promise),
+ "apply postponed channel updates", true);
+ } else {
+ promise.set_value(Unit());
+ }
+ if (updates.size() != old_size || running_get_channel_difference(dialog_id)) {
+ if (success && update_pts - 10000 < pts && update_pts_count == 1) {
// if getChannelDifference was successful and update pts is near channel pts,
// we hope that the update eventually can be applied
LOG(INFO) << "Can't apply postponed channel updates";
} else {
- // otherwise we protecting from getChannelDifference repeating calls by dropping pending updates
- LOG(ERROR) << "Failed to apply postponed updates of type " << update_id << " in " << dialog_id << " with pts "
- << d->pts << ", update pts is " << update_pts << ", update pts count is " << update_pts_count;
- d->postponed_channel_updates.clear();
+ // otherwise protect from getChannelDifference repeating calls by dropping postponed updates
+ LOG(WARNING) << "Failed to apply postponed updates of type " << update_id << " in " << dialog_id
+ << " with pts " << pts << ", update pts is " << update_pts << ", update pts count is "
+ << update_pts_count;
+ vector<Promise<Unit>> update_promises;
+ for (auto &postponed_update : updates) {
+ update_promises.push_back(std::move(postponed_update.second.promise));
+ }
+ updates.clear();
+ for (auto &update_promise : update_promises) {
+ update_promise.set_value(Unit());
+ }
}
break;
}
}
+ if (updates.empty()) {
+ postponed_channel_updates_.erase(postponed_updates_it);
+ }
LOG(INFO) << "Finish to apply postponed channel updates";
}
+ if (d != nullptr) {
+ d->is_channel_difference_finished = true;
+
+ if (d->message_notification_group.group_id.is_valid()) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::after_get_chat_difference,
+ d->message_notification_group.group_id);
+ }
+ if (d->mention_notification_group.group_id.is_valid()) {
+ send_closure_later(G()->notification_manager(), &NotificationManager::after_get_chat_difference,
+ d->mention_notification_group.group_id);
+ }
+ } else {
+ is_channel_difference_finished_.insert(dialog_id);
+ }
+
if (postponed_chat_read_inbox_updates_.erase(dialog_id) > 0) {
send_update_chat_read_inbox(d, true, "after_get_channel_difference");
}
- auto it_get_message_requests = postponed_get_message_requests_.find(dialog_id);
- if (it_get_message_requests != postponed_get_message_requests_.end()) {
- CHECK(d != nullptr);
- for (auto &request : it_get_message_requests->second) {
- auto message_id = request.first;
- LOG(INFO) << "Run postponed getMessage request for " << message_id << " in " << dialog_id;
- if (d->last_new_message_id != MessageId() && message_id.get() > d->last_new_message_id.get()) {
- // message will not be added to the dialog anyway, get channel difference didn't help
- request.second.set_value(Unit());
- } else {
- get_messages_from_server({FullMessageId{dialog_id, message_id}}, std::move(request.second));
- }
- }
- postponed_get_message_requests_.erase(it_get_message_requests);
+ auto promise_it = run_after_get_channel_difference_.find(dialog_id);
+ if (promise_it != run_after_get_channel_difference_.end()) {
+ auto promises = std::move(promise_it->second);
+ run_after_get_channel_difference_.erase(promise_it);
+
+ set_promises(promises);
}
auto it = pending_channel_on_get_dialogs_.find(dialog_id);
@@ -24044,96 +39721,316 @@ void MessagesManager::after_get_channel_difference(DialogId dialog_id, bool succ
PendingOnGetDialogs res = std::move(it->second);
pending_channel_on_get_dialogs_.erase(it);
- on_get_dialogs(std::move(res.dialogs), res.total_count, std::move(res.messages), std::move(res.promise));
+ on_get_dialogs(res.folder_id, std::move(res.dialogs), res.total_count, std::move(res.messages),
+ std::move(res.promise));
+ }
+
+ if (d != nullptr && !td_->auth_manager_->is_bot() && have_access && !d->last_message_id.is_valid() && !d->is_empty &&
+ (d->order != DEFAULT_ORDER || is_dialog_sponsored(d))) {
+ get_history_from_the_end_impl(d, true, false, Auto(), "after_get_channel_difference");
+ }
+}
+
+void MessagesManager::reget_message_from_server_if_needed(DialogId dialog_id, const Message *m) {
+ if (!m->message_id.is_any_server() || dialog_id.get_type() == DialogType::SecretChat) {
+ return;
+ }
+
+ if (need_reget_message_content(m->content.get()) || (m->legacy_layer != 0 && m->legacy_layer < MTPROTO_LAYER) ||
+ m->reply_info.need_reget(td_)) {
+ FullMessageId full_message_id{dialog_id, m->message_id};
+ LOG(INFO) << "Reget from server " << full_message_id;
+ get_message_from_server(full_message_id, Auto(), "reget_message_from_server_if_needed");
+ }
+}
+
+void MessagesManager::speculatively_update_active_group_call_id(Dialog *d, const Message *m) {
+ CHECK(m != nullptr);
+ if (!m->message_id.is_any_server() || m->content->get_type() != MessageContentType::GroupCall) {
+ return;
+ }
+
+ InputGroupCallId input_group_call_id;
+ bool is_ended;
+ std::tie(input_group_call_id, is_ended) = get_message_content_group_call_info(m->content.get());
+ d->has_expected_active_group_call_id = true;
+ if (is_ended) {
+ d->expected_active_group_call_id = InputGroupCallId();
+ if (d->active_group_call_id == input_group_call_id) {
+ on_update_dialog_group_call_id(d->dialog_id, InputGroupCallId());
+ }
+ } else {
+ d->expected_active_group_call_id = input_group_call_id;
+ if (d->active_group_call_id != input_group_call_id && !td_->auth_manager_->is_bot()) {
+ repair_dialog_active_group_call_id(d->dialog_id);
+ }
+ }
+}
+
+void MessagesManager::speculatively_update_channel_participants(DialogId dialog_id, const Message *m) {
+ CHECK(m != nullptr);
+ if (!m->message_id.is_any_server() || dialog_id.get_type() != DialogType::Channel || !m->sender_user_id.is_valid()) {
+ return;
+ }
+
+ auto channel_id = dialog_id.get_channel_id();
+ UserId my_user_id(td_->contacts_manager_->get_my_id());
+ bool by_me = m->sender_user_id == my_user_id;
+ switch (m->content->get_type()) {
+ case MessageContentType::ChatAddUsers:
+ send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_add_channel_participants, channel_id,
+ get_message_content_added_user_ids(m->content.get()), m->sender_user_id, m->date, by_me);
+ break;
+ case MessageContentType::ChatJoinedByLink:
+ send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_add_channel_participants, channel_id,
+ vector<UserId>{m->sender_user_id}, m->sender_user_id, m->date, by_me);
+ break;
+ case MessageContentType::ChatDeleteUser:
+ send_closure_later(G()->contacts_manager(), &ContactsManager::speculative_delete_channel_participant, channel_id,
+ get_message_content_deleted_user_id(m->content.get()), by_me);
+ break;
+ default:
+ break;
+ }
+}
+
+void MessagesManager::update_sent_message_contents(DialogId dialog_id, const Message *m) {
+ CHECK(m != nullptr);
+ if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id()) ||
+ dialog_id.get_type() == DialogType::SecretChat || m->message_id.is_local() || m->forward_info != nullptr ||
+ m->had_forward_info) {
+ return;
}
- // TODO resend some messages
+ on_sent_message_content(td_, m->content.get());
}
void MessagesManager::update_used_hashtags(DialogId dialog_id, const Message *m) {
CHECK(m != nullptr);
- if (m->via_bot_user_id.is_valid() || m->content->get_id() != MessageText::ID) {
+ if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id()) ||
+ m->via_bot_user_id.is_valid() || m->hide_via_bot || m->forward_info != nullptr || m->had_forward_info) {
return;
}
- auto message_text = static_cast<const MessageText *>(m->content.get());
- const unsigned char *ptr = Slice(message_text->text.text).ubegin();
- const unsigned char *end = Slice(message_text->text.text).uend();
- int32 utf16_pos = 0;
- for (auto &entity : message_text->text.entities) {
- if (entity.type != MessageEntity::Type::Hashtag) {
- continue;
+
+ ::td::update_used_hashtags(td_, m->content.get());
+}
+
+void MessagesManager::update_top_dialogs(DialogId dialog_id, const Message *m) {
+ CHECK(m != nullptr);
+ auto dialog_type = dialog_id.get_type();
+ if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id()) ||
+ dialog_type == DialogType::SecretChat || !m->message_id.is_any_server()) {
+ return;
+ }
+
+ bool is_forward = m->forward_info != nullptr || m->had_forward_info;
+ if (m->via_bot_user_id.is_valid() && !is_forward) {
+ // forwarded game messages can't be distinguished from sent via bot game messages, so increase rating anyway
+ on_dialog_used(TopDialogCategory::BotInline, DialogId(m->via_bot_user_id), m->date);
+ }
+
+ if (is_forward) {
+ auto &last_forward_date = last_outgoing_forwarded_message_date_[dialog_id];
+ if (last_forward_date < m->date) {
+ TopDialogCategory category =
+ dialog_type == DialogType::User ? TopDialogCategory::ForwardUsers : TopDialogCategory::ForwardChats;
+ on_dialog_used(category, dialog_id, m->date);
+ last_forward_date = m->date;
+ }
+ }
+
+ TopDialogCategory category = TopDialogCategory::Size;
+ switch (dialog_type) {
+ case DialogType::User: {
+ if (td_->contacts_manager_->is_user_bot(dialog_id.get_user_id())) {
+ category = TopDialogCategory::BotPM;
+ } else {
+ category = TopDialogCategory::Correspondent;
+ }
+ break;
+ }
+ case DialogType::Chat:
+ category = TopDialogCategory::Group;
+ break;
+ case DialogType::Channel:
+ switch (td_->contacts_manager_->get_channel_type(dialog_id.get_channel_id())) {
+ case ChannelType::Broadcast:
+ category = TopDialogCategory::Channel;
+ break;
+ case ChannelType::Megagroup:
+ category = TopDialogCategory::Group;
+ break;
+ case ChannelType::Unknown:
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ break;
+ case DialogType::SecretChat:
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+ if (category != TopDialogCategory::Size) {
+ on_dialog_used(category, dialog_id, m->date);
+ }
+}
+
+void MessagesManager::update_forward_count(DialogId dialog_id, const Message *m) {
+ if (!td_->auth_manager_->is_bot() && m->forward_info != nullptr && m->forward_info->sender_dialog_id.is_valid() &&
+ m->forward_info->message_id.is_valid() &&
+ (!is_discussion_message(dialog_id, m) || m->forward_info->sender_dialog_id != m->forward_info->from_dialog_id ||
+ m->forward_info->message_id != m->forward_info->from_message_id)) {
+ update_forward_count(m->forward_info->sender_dialog_id, m->forward_info->message_id, m->date);
+ }
+}
+
+void MessagesManager::update_forward_count(DialogId dialog_id, MessageId message_id, int32 update_date) {
+ CHECK(!td_->auth_manager_->is_bot());
+ Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ Message *m = get_message_force(d, message_id, "update_forward_count");
+ if (m != nullptr && !m->message_id.is_scheduled() && m->message_id.is_server() && m->view_count > 0 &&
+ m->interaction_info_update_date < update_date) {
+ if (m->forward_count == 0) {
+ m->forward_count++;
+ send_update_message_interaction_info(dialog_id, m);
+ on_message_changed(d, m, true, "update_forward_count");
}
- while (utf16_pos < entity.offset && ptr < end) {
- utf16_pos += 1 + (ptr[0] >= 0xf0);
- ptr = next_utf8_unsafe(ptr, nullptr);
+
+ if (d->pending_viewed_message_ids.insert(m->message_id).second) {
+ pending_message_views_timeout_.add_timeout_in(dialog_id.get(), 0.0);
}
- CHECK(utf16_pos == entity.offset);
- auto from = ptr;
+ }
+}
- while (utf16_pos < entity.offset + entity.length && ptr < end) {
- utf16_pos += 1 + (ptr[0] >= 0xf0);
- ptr = next_utf8_unsafe(ptr, nullptr);
+void MessagesManager::update_has_outgoing_messages(DialogId dialog_id, const Message *m) {
+ CHECK(m != nullptr);
+ if (td_->auth_manager_->is_bot() || (!m->is_outgoing && dialog_id != get_my_dialog_id())) {
+ return;
+ }
+
+ Dialog *d = nullptr;
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ d = get_dialog(dialog_id);
+ break;
+ case DialogType::Chat:
+ case DialogType::Channel:
+ break;
+ case DialogType::SecretChat: {
+ auto user_id = td_->contacts_manager_->get_secret_chat_user_id(dialog_id.get_secret_chat_id());
+ if (user_id.is_valid()) {
+ d = get_dialog_force(DialogId(user_id), "update_has_outgoing_messages");
+ }
+ break;
}
- CHECK(utf16_pos == entity.offset + entity.length);
- auto to = ptr;
+ default:
+ UNREACHABLE();
+ }
+ if (d == nullptr || d->has_outgoing_messages) {
+ return;
+ }
- send_closure(td_->hashtag_hints_, &HashtagHints::hashtag_used, Slice(from + 1, to).str());
+ d->has_outgoing_messages = true;
+ on_dialog_updated(dialog_id, "update_has_outgoing_messages");
+
+ if (d->action_bar != nullptr && d->action_bar->on_outgoing_message()) {
+ send_update_chat_action_bar(d);
+ }
+}
+
+void MessagesManager::restore_message_reply_to_message_id(Dialog *d, Message *m) {
+ if (m->reply_to_message_id == MessageId() || !m->reply_to_message_id.is_yet_unsent()) {
+ return;
+ }
+
+ auto message_id = get_message_id_by_random_id(d, m->reply_to_random_id, "restore_message_reply_to_message_id");
+ if (!message_id.is_valid() && !message_id.is_valid_scheduled()) {
+ LOG(INFO) << "Failed to find replied " << m->reply_to_message_id << " with random_id = " << m->reply_to_random_id;
+ m->reply_to_message_id = m->top_thread_message_id;
+ m->reply_to_random_id = 0;
+ } else {
+ LOG(INFO) << "Restore message reply to " << message_id << " with random_id = " << m->reply_to_random_id;
+ m->reply_to_message_id = message_id;
}
}
MessagesManager::Message *MessagesManager::continue_send_message(DialogId dialog_id, unique_ptr<Message> &&m,
- uint64 logevent_id) {
- Dialog *d = get_dialog_force(dialog_id);
+ uint64 log_event_id) {
+ CHECK(log_event_id != 0);
+ CHECK(m != nullptr);
+ CHECK(m->content != nullptr);
+
+ Dialog *d = get_dialog_force(dialog_id, "continue_send_message");
if (d == nullptr) {
- LOG(ERROR) << "Can't find " << dialog_id << " to resend a message";
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
+ LOG(ERROR) << "Can't find " << dialog_id << " to continue send a message";
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ return nullptr;
+ }
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
return nullptr;
}
- m->message_id = get_next_yet_unsent_message_id(d);
- m->random_y = get_random_y(m->message_id);
- m->date = G()->unix_time();
- m->have_previous = true;
- m->have_next = true;
+ LOG(INFO) << "Continue to send " << m->message_id << " to " << dialog_id << " initially sent at " << m->send_date
+ << " from binlog";
- LOG(INFO) << "Continue to send " << m->message_id << " to " << dialog_id << " from binlog";
+ d->was_opened = true;
- if (!have_input_peer(dialog_id, AccessRights::Read)) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), logevent_id);
- return nullptr;
+ auto now = G()->unix_time();
+ if (m->message_id.is_scheduled()) {
+ set_message_id(m, get_next_yet_unsent_scheduled_message_id(d, m->date));
+ } else {
+ set_message_id(m, get_next_yet_unsent_message_id(d));
+ m->date = now;
}
+ m->have_previous = true;
+ m->have_next = true;
- message_random_ids_.insert(m->random_id);
+ restore_message_reply_to_message_id(d, m.get());
bool need_update = false;
bool need_update_dialog_pos = false;
auto result_message =
- add_message_to_dialog(d, std::move(m), false, &need_update, &need_update_dialog_pos, "resend message");
+ add_message_to_dialog(d, std::move(m), true, &need_update, &need_update_dialog_pos, "continue_send_message");
CHECK(result_message != nullptr);
- // CHECK(need_update_dialog_pos == true);
+
+ if (result_message->message_id.is_scheduled()) {
+ send_update_chat_has_scheduled_messages(d, false);
+ }
+
+ send_update_new_message(d, result_message);
+ if (need_update_dialog_pos) {
+ send_update_chat_last_message(d, "continue_send_message");
+ }
auto can_send_status = can_send_message(dialog_id);
+ if (can_send_status.is_ok() && result_message->send_date < now - MAX_RESEND_DELAY &&
+ dialog_id != get_my_dialog_id()) {
+ can_send_status = Status::Error(400, "Message is too old to be re-sent automatically");
+ }
if (can_send_status.is_error()) {
- LOG(INFO) << "Can't resend a message to " << dialog_id << ": " << can_send_status.error();
+ LOG(INFO) << "Can't continue to send a message to " << dialog_id << ": " << can_send_status;
- int64 random_id = begin_send_message(dialog_id, result_message);
- on_send_message_fail(random_id, can_send_status.move_as_error());
+ fail_send_message({dialog_id, result_message->message_id}, std::move(can_send_status));
return nullptr;
}
- send_update_new_message(d, result_message);
- if (need_update_dialog_pos) {
- send_update_chat_last_message(d, "on_resend_message");
- }
return result_message;
}
void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
+ if (G()->close_flag()) {
+ return;
+ }
for (auto &event : events) {
+ CHECK(event.id_ != 0);
switch (event.type_) {
case LogEvent::HandlerType::SendMessage: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24142,30 +40039,32 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
auto dialog_id = log_event.dialog_id;
auto m = std::move(log_event.m_out);
- m->send_message_logevent_id = event.id_;
+ m->send_message_log_event_id = event.id_;
- if (m->content->get_id() == MessageUnsupported::ID) {
+ if (m->content->get_type() == MessageContentType::Unsupported) {
LOG(ERROR) << "Message content is invalid: " << format::as_hex_dump<4>(Slice(event.data_));
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
Dependencies dependencies;
- add_dialog_dependencies(dependencies, dialog_id);
- add_message_dependencies(dependencies, dialog_id, m.get());
- resolve_dependencies_force(dependencies);
+ dependencies.add_dialog_dependencies(dialog_id);
+ add_message_dependencies(dependencies, m.get());
+ dependencies.resolve_force(td_, "SendMessageLogEvent");
- m->content = dup_message_content(dialog_id, m->content.get(), false);
+ m->content =
+ dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::Send, MessageCopyOptions());
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
do_send_message(dialog_id, result_message);
}
+
break;
}
case LogEvent::HandlerType::SendBotStartMessage: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24174,31 +40073,32 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
auto dialog_id = log_event.dialog_id;
auto m = std::move(log_event.m_out);
- m->send_message_logevent_id = event.id_;
+ m->send_message_log_event_id = event.id_;
- CHECK(m->content->get_id() == MessageText::ID);
+ CHECK(m->content->get_type() == MessageContentType::Text);
Dependencies dependencies;
- add_dialog_dependencies(dependencies, dialog_id);
- add_message_dependencies(dependencies, dialog_id, m.get());
- resolve_dependencies_force(dependencies);
+ dependencies.add_dialog_dependencies(dialog_id);
+ add_message_dependencies(dependencies, m.get());
+ dependencies.resolve_force(td_, "SendBotStartMessageLogEvent");
auto bot_user_id = log_event.bot_user_id;
if (!td_->contacts_manager_->have_user_force(bot_user_id)) {
LOG(ERROR) << "Can't find bot " << bot_user_id;
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
- do_send_bot_start_message(bot_user_id, dialog_id, log_event.parameter, result_message);
+ send_closure_later(actor_id(this), &MessagesManager::do_send_bot_start_message, bot_user_id, dialog_id,
+ result_message->message_id, log_event.parameter);
}
break;
}
case LogEvent::HandlerType::SendInlineQueryResultMessage: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24207,30 +40107,32 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
auto dialog_id = log_event.dialog_id;
auto m = std::move(log_event.m_out);
- m->send_message_logevent_id = event.id_;
+ m->send_message_log_event_id = event.id_;
- if (m->content->get_id() == MessageUnsupported::ID) {
+ if (m->content->get_type() == MessageContentType::Unsupported) {
LOG(ERROR) << "Message content is invalid: " << format::as_hex_dump<4>(Slice(event.data_));
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
Dependencies dependencies;
- add_dialog_dependencies(dependencies, dialog_id);
- add_message_dependencies(dependencies, dialog_id, m.get());
- resolve_dependencies_force(dependencies);
+ dependencies.add_dialog_dependencies(dialog_id);
+ add_message_dependencies(dependencies, m.get());
+ dependencies.resolve_force(td_, "SendInlineQueryResultMessageLogEvent");
- m->content = dup_message_content(dialog_id, m->content.get(), false);
+ m->content = dup_message_content(td_, dialog_id, m->content.get(), MessageContentDupType::SendViaBot,
+ MessageCopyOptions());
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
- do_send_inline_query_result_message(dialog_id, result_message, log_event.query_id, log_event.result_id);
+ send_closure_later(actor_id(this), &MessagesManager::do_send_inline_query_result_message, dialog_id,
+ result_message->message_id, log_event.query_id, log_event.result_id);
}
break;
}
case LogEvent::HandlerType::SendScreenshotTakenNotificationMessage: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24239,14 +40141,14 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
auto dialog_id = log_event.dialog_id;
auto m = std::move(log_event.m_out);
- m->send_message_logevent_id = 0;
+ m->send_message_log_event_id = 0; // to not allow event deletion by message deletion
- CHECK(m->content->get_id() == MessageScreenshotTaken::ID);
+ CHECK(m->content->get_type() == MessageContentType::ScreenshotTaken);
Dependencies dependencies;
- add_dialog_dependencies(dependencies, dialog_id);
- add_message_dependencies(dependencies, dialog_id, m.get());
- resolve_dependencies_force(dependencies);
+ dependencies.add_dialog_dependencies(dialog_id);
+ add_message_dependencies(dependencies, m.get());
+ dependencies.resolve_force(td_, "SendScreenshotTakenNotificationMessageLogEvent");
auto result_message = continue_send_message(dialog_id, std::move(m), event.id_);
if (result_message != nullptr) {
@@ -24256,8 +40158,8 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
}
case LogEvent::HandlerType::ForwardMessages: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
- break;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ continue;
}
ForwardMessagesLogEvent log_event;
@@ -24268,137 +40170,247 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
auto messages = std::move(log_event.messages_out);
Dependencies dependencies;
- add_dialog_dependencies(dependencies, to_dialog_id);
- add_dialog_dependencies(dependencies, from_dialog_id);
+ dependencies.add_dialog_dependencies(to_dialog_id);
+ dependencies.add_dialog_dependencies(from_dialog_id);
for (auto &m : messages) {
- add_message_dependencies(dependencies, to_dialog_id, m.get());
+ add_message_dependencies(dependencies, m.get());
}
- resolve_dependencies_force(dependencies);
+ dependencies.resolve_force(td_, "ForwardMessagesLogEvent");
- Dialog *to_dialog = get_dialog_force(to_dialog_id);
+ Dialog *to_dialog = get_dialog_force(to_dialog_id, "ForwardMessagesLogEvent to");
if (to_dialog == nullptr) {
LOG(ERROR) << "Can't find " << to_dialog_id << " to forward messages to";
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
- Dialog *from_dialog = get_dialog_force(from_dialog_id);
+ Dialog *from_dialog = get_dialog_force(from_dialog_id, "ForwardMessagesLogEvent from");
if (from_dialog == nullptr) {
LOG(ERROR) << "Can't find " << from_dialog_id << " to forward messages from";
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
continue;
}
- for (auto &m : messages) {
- m->message_id = get_next_yet_unsent_message_id(to_dialog);
- m->random_y = get_random_y(m->message_id);
- m->date = G()->unix_time();
- m->content = dup_message_content(to_dialog_id, m->content.get(), true);
- m->have_previous = true;
- m->have_next = true;
- }
- LOG(INFO) << "Continue to forward " << messages.size() << " messages to " << to_dialog_id << " from binlog";
+ to_dialog->was_opened = true;
- if (!have_input_peer(from_dialog_id, AccessRights::Read) || can_send_message(to_dialog_id).is_error()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
- break;
+ auto now = G()->unix_time();
+ if (!have_input_peer(from_dialog_id, AccessRights::Read) || can_send_message(to_dialog_id).is_error() ||
+ messages.empty() ||
+ (messages[0]->send_date < now - MAX_RESEND_DELAY && to_dialog_id != get_my_dialog_id())) {
+ LOG(WARNING) << "Can't continue forwarding " << messages.size() << " message(s) to " << to_dialog_id;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ continue;
}
+ LOG(INFO) << "Continue to forward " << messages.size() << " message(s) to " << to_dialog_id << " from binlog";
+
bool need_update = false;
bool need_update_dialog_pos = false;
vector<Message *> forwarded_messages;
for (auto &m : messages) {
- message_random_ids_.insert(m->random_id);
- forwarded_messages.push_back(add_message_to_dialog(to_dialog, std::move(m), false, &need_update,
+ if (m->message_id.is_scheduled()) {
+ set_message_id(m, get_next_yet_unsent_scheduled_message_id(to_dialog, m->date));
+ } else {
+ set_message_id(m, get_next_yet_unsent_message_id(to_dialog));
+ m->date = now;
+ }
+ m->content = dup_message_content(td_, to_dialog_id, m->content.get(), MessageContentDupType::Forward,
+ MessageCopyOptions());
+ CHECK(m->content != nullptr);
+ m->have_previous = true;
+ m->have_next = true;
+
+ restore_message_reply_to_message_id(to_dialog, m.get());
+
+ forwarded_messages.push_back(add_message_to_dialog(to_dialog, std::move(m), true, &need_update,
&need_update_dialog_pos, "forward message again"));
send_update_new_message(to_dialog, forwarded_messages.back());
}
+
+ send_update_chat_has_scheduled_messages(to_dialog, false);
+
if (need_update_dialog_pos) {
send_update_chat_last_message(to_dialog, "on_reforward_message");
}
- do_forward_messages(to_dialog_id, from_dialog_id, forwarded_messages, log_event.message_ids, event.id_);
+ do_forward_messages(to_dialog_id, from_dialog_id, forwarded_messages, log_event.message_ids,
+ log_event.drop_author, log_event.drop_media_captions, event.id_);
break;
}
case LogEvent::HandlerType::DeleteMessage: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
DeleteMessageLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
log_event.id_ = event.id_;
- do_delete_message_logevent(log_event);
+
+ Dialog *d = get_dialog_force(log_event.full_message_id_.get_dialog_id(), "DeleteMessageLogEvent");
+ if (d != nullptr) {
+ auto message_id = log_event.full_message_id_.get_message_id();
+ if (message_id.is_valid_scheduled() && message_id.is_scheduled_server()) {
+ d->deleted_scheduled_server_message_ids.insert(message_id.get_scheduled_server_message_id());
+ } else if (message_id != MessageId()) {
+ d->deleted_message_ids.insert(message_id);
+ }
+ }
+
+ do_delete_message_log_event(log_event);
+ break;
+ }
+ case LogEvent::HandlerType::DeleteMessagesOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ DeleteMessagesOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.dialog_id_;
+ Dialog *d = get_dialog_force(dialog_id, "DeleteMessagesOnServerLogEvent");
+ if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ for (auto message_id : log_event.message_ids_) {
+ CHECK(message_id.is_valid());
+ d->deleted_message_ids.insert(message_id);
+ }
+
+ delete_messages_on_server(dialog_id, std::move(log_event.message_ids_), log_event.revoke_, event.id_, Auto());
+ break;
+ }
+ case LogEvent::HandlerType::DeleteScheduledMessagesOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ DeleteScheduledMessagesOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.dialog_id_;
+ Dialog *d = get_dialog_force(dialog_id, "DeleteScheduledMessagesOnServerLogEvent");
+ if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ for (auto message_id : log_event.message_ids_) {
+ CHECK(message_id.is_scheduled_server());
+ d->deleted_scheduled_server_message_ids.insert(message_id.get_scheduled_server_message_id());
+ }
+
+ delete_scheduled_messages_on_server(dialog_id, std::move(log_event.message_ids_), event.id_, Auto());
break;
}
- case LogEvent::HandlerType::DeleteMessagesFromServer: {
+ case LogEvent::HandlerType::DeleteDialogHistoryOnServer: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- DeleteMessagesFromServerLogEvent log_event;
+ DeleteDialogHistoryOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "DeleteDialogHistoryOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- delete_messages_from_server(dialog_id, std::move(log_event.message_ids_), log_event.revoke_, event.id_, Auto());
+ delete_dialog_history_on_server(dialog_id, log_event.max_message_id_, log_event.remove_from_dialog_list_,
+ log_event.revoke_, true, event.id_, Auto());
break;
}
- case LogEvent::HandlerType::DeleteDialogHistoryFromServer: {
+ case LogEvent::HandlerType::DeleteTopicHistoryOnServer: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- DeleteDialogHistoryFromServerLogEvent log_event;
+ DeleteTopicHistoryOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "DeleteTopicHistoryOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- delete_dialog_history_from_server(dialog_id, log_event.max_message_id_, log_event.remove_from_dialog_list_,
- true, event.id_, Auto());
+ delete_topic_history_on_server(dialog_id, log_event.top_thread_message_id_, event.id_, Auto());
+ break;
+ }
+ case LogEvent::HandlerType::DeleteAllCallMessagesOnServer: {
+ DeleteAllCallMessagesOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ delete_all_call_messages_on_server(log_event.revoke_, event.id_, Auto());
+ break;
+ }
+ case LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer: {
+ BlockMessageSenderFromRepliesOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ block_message_sender_from_replies_on_server(log_event.message_id_, log_event.delete_message_,
+ log_event.delete_all_messages_, log_event.report_spam_, event.id_,
+ Auto());
break;
}
- case LogEvent::HandlerType::DeleteAllChannelMessagesFromUserOnServer: {
+ case LogEvent::HandlerType::DeleteAllChannelMessagesFromSenderOnServer: {
if (!G()->parameters().use_chat_info_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- DeleteAllChannelMessagesFromUserOnServerLogEvent log_event;
+ DeleteAllChannelMessagesFromSenderOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
auto channel_id = log_event.channel_id_;
if (!td_->contacts_manager_->have_channel_force(channel_id)) {
LOG(ERROR) << "Can't find " << channel_id;
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- auto user_id = log_event.user_id_;
- if (!td_->contacts_manager_->have_user_force(user_id)) {
- LOG(ERROR) << "Can't find user " << user_id;
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ auto sender_dialog_id = log_event.sender_dialog_id_;
+ if (!have_dialog_info_force(sender_dialog_id) || !have_input_peer(sender_dialog_id, AccessRights::Know)) {
+ LOG(ERROR) << "Can't find " << sender_dialog_id;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ delete_all_channel_messages_by_sender_on_server(channel_id, sender_dialog_id, event.id_, Auto());
+ break;
+ }
+ case LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ DeleteDialogMessagesByDateOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.dialog_id_;
+ Dialog *d = get_dialog_force(dialog_id, "DeleteDialogMessagesByDateOnServerLogEvent");
+ if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- delete_all_channel_messages_from_user_on_server(channel_id, user_id, event.id_, Auto());
+ delete_dialog_messages_by_date_on_server(dialog_id, log_event.min_date_, log_event.max_date_, log_event.revoke_,
+ event.id_, Auto());
break;
}
case LogEvent::HandlerType::ReadHistoryOnServer: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24406,18 +40418,76 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "ReadHistoryOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
+ if (d->read_history_log_event_ids[0].log_event_id != 0) {
+ // we need only latest read history event
+ binlog_erase(G()->td_db()->get_binlog(), d->read_history_log_event_ids[0].log_event_id);
+ }
+ d->read_history_log_event_ids[0].log_event_id = event.id_;
+
+ read_history_on_server_impl(d, log_event.max_message_id_);
+ break;
+ }
+ case LogEvent::HandlerType::ReadHistoryInSecretChat: {
+ ReadHistoryInSecretChatLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.dialog_id_;
+ CHECK(dialog_id.get_type() == DialogType::SecretChat);
+ if (!td_->contacts_manager_->have_secret_chat_force(dialog_id.get_secret_chat_id())) {
+ LOG(ERROR) << "Can't read history in unknown " << dialog_id;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+ force_create_dialog(dialog_id, "ReadHistoryInSecretChatLogEvent");
+ Dialog *d = get_dialog(dialog_id);
+ if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+ if (d->read_history_log_event_ids[0].log_event_id != 0) {
+ // we need only latest read history event
+ binlog_erase(G()->td_db()->get_binlog(), d->read_history_log_event_ids[0].log_event_id);
+ }
+ d->read_history_log_event_ids[0].log_event_id = event.id_;
+ d->last_read_inbox_message_date = log_event.max_date_;
+
+ read_history_on_server_impl(d, MessageId());
+ break;
+ }
+ case LogEvent::HandlerType::ReadMessageThreadHistoryOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ ReadMessageThreadHistoryOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
- read_history_on_server(dialog_id, log_event.max_message_id_, true, event.id_);
+ auto dialog_id = log_event.dialog_id_;
+ Dialog *d = get_dialog_force(dialog_id, "ReadMessageThreadHistoryOnServerLogEvent");
+ if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+ auto top_thread_message_id = log_event.top_thread_message_id_;
+ if (d->read_history_log_event_ids[top_thread_message_id.get()].log_event_id != 0) {
+ // we need only latest read history event
+ binlog_erase(G()->td_db()->get_binlog(),
+ d->read_history_log_event_ids[top_thread_message_id.get()].log_event_id);
+ }
+ d->read_history_log_event_ids[top_thread_message_id.get()].log_event_id = event.id_;
+
+ read_message_thread_history_on_server_impl(d, top_thread_message_id, log_event.max_message_id_);
break;
}
case LogEvent::HandlerType::ReadMessageContentsOnServer: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24425,18 +40495,18 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "ReadMessageContentsOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- read_message_contents_on_server(dialog_id, std::move(log_event.message_ids_), event.id_);
+ read_message_contents_on_server(dialog_id, std::move(log_event.message_ids_), event.id_, Auto());
break;
}
case LogEvent::HandlerType::ReadAllDialogMentionsOnServer: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24444,18 +40514,37 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "ReadAllDialogMentionsOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
read_all_dialog_mentions_on_server(dialog_id, event.id_, Promise<Unit>());
break;
}
+ case LogEvent::HandlerType::ReadAllDialogReactionsOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ ReadAllDialogReactionsOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.dialog_id_;
+ Dialog *d = get_dialog_force(dialog_id, "ReadAllDialogReactionsOnServerLogEvent");
+ if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ read_all_dialog_reactions_on_server(dialog_id, event.id_, Promise<Unit>());
+ break;
+ }
case LogEvent::HandlerType::ToggleDialogIsPinnedOnServer: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24463,9 +40552,9 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "ToggleDialogIsPinnedOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24474,7 +40563,7 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
}
case LogEvent::HandlerType::ReorderPinnedDialogsOnServer: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24483,22 +40572,62 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
vector<DialogId> dialog_ids;
for (auto &dialog_id : log_event.dialog_ids_) {
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "ReorderPinnedDialogsOnServerLogEvent");
if (d != nullptr && have_input_peer(dialog_id, AccessRights::Read)) {
dialog_ids.push_back(dialog_id);
}
}
if (dialog_ids.empty()) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- reorder_pinned_dialogs_on_server(dialog_ids, event.id_);
+ reorder_pinned_dialogs_on_server(log_event.folder_id_, dialog_ids, event.id_);
+ break;
+ }
+ case LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ ToggleDialogIsMarkedAsUnreadOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.dialog_id_;
+ bool have_info = dialog_id.get_type() == DialogType::User
+ ? td_->contacts_manager_->have_user_force(dialog_id.get_user_id())
+ : have_dialog_force(dialog_id, "ToggleDialogIsMarkedAsUnreadOnServerLogEvent");
+ if (!have_info || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ toggle_dialog_is_marked_as_unread_on_server(dialog_id, log_event.is_marked_as_unread_, event.id_);
+ break;
+ }
+ case LogEvent::HandlerType::ToggleDialogIsBlockedOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ ToggleDialogIsBlockedOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.dialog_id_;
+ if (dialog_id.get_type() == DialogType::SecretChat || !have_dialog_info_force(dialog_id) ||
+ !have_input_peer(dialog_id, AccessRights::Know)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ toggle_dialog_is_blocked_on_server(dialog_id, log_event.is_blocked_, event.id_);
break;
}
case LogEvent::HandlerType::SaveDialogDraftMessageOnServer: {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
@@ -24506,198 +40635,167 @@ void MessagesManager::on_binlog_events(vector<BinlogEvent> &&events) {
log_event_parse(log_event, event.data_).ensure();
auto dialog_id = log_event.dialog_id_;
- Dialog *d = get_dialog_force(dialog_id);
+ Dialog *d = get_dialog_force(dialog_id, "SaveDialogDraftMessageOnServerLogEvent");
if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Write)) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
break;
}
- d->save_draft_message_logevent_id = event.id_;
+ d->save_draft_message_log_event_id.log_event_id = event.id_;
save_dialog_draft_message_on_server(dialog_id);
break;
}
- case LogEvent::HandlerType::GetChannelDifference: {
- GetChannelDifferenceLogEvent log_event;
+ case LogEvent::HandlerType::UpdateDialogNotificationSettingsOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ UpdateDialogNotificationSettingsOnServerLogEvent log_event;
log_event_parse(log_event, event.data_).ensure();
- DialogId dialog_id(log_event.channel_id);
- LOG(INFO) << "Continue to run getChannelDifference in " << dialog_id;
- get_channel_difference_to_logevent_id_.emplace(dialog_id, event.id_);
- do_get_channel_difference(
- dialog_id, load_channel_pts(dialog_id), true,
- telegram_api::make_object<telegram_api::inputChannel>(log_event.channel_id.get(), log_event.access_hash),
- "LogEvent::HandlerType::GetChannelDifference");
+ auto dialog_id = log_event.dialog_id_;
+ Dialog *d = get_dialog_force(dialog_id, "UpdateDialogNotificationSettingsOnServerLogEvent");
+ if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+ d->save_notification_settings_log_event_id.log_event_id = event.id_;
+
+ update_dialog_notification_settings_on_server(dialog_id, true);
break;
}
- default:
- LOG(FATAL) << "Unsupported logevent type " << event.type_;
- }
- }
-}
+ case LogEvent::HandlerType::ResetAllNotificationSettingsOnServer: {
+ ResetAllNotificationSettingsOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
-void MessagesManager::save_recently_found_dialogs() {
- if (recently_found_dialogs_loaded_ < 2) {
- return;
- }
+ reset_all_notification_settings_on_server(event.id_);
+ break;
+ }
+ case LogEvent::HandlerType::ToggleDialogReportSpamStateOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
- string value;
- for (auto &dialog_id : recently_found_dialog_ids_) {
- if (!value.empty()) {
- value += ',';
- }
- if (!G()->parameters().use_message_db) {
- // if there is no dialog database, prefer to save dialogs by username
- auto username = get_dialog_username(dialog_id);
- if (dialog_id.get_type() != DialogType::SecretChat && !username.empty()) {
- value += '@';
- value += username;
- continue;
+ ToggleDialogReportSpamStateOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.dialog_id_;
+ Dialog *d = get_dialog_force(dialog_id, "ToggleDialogReportSpamStateOnServerLogEvent");
+ if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ toggle_dialog_report_spam_state_on_server(dialog_id, log_event.is_spam_dialog_, event.id_, Promise<Unit>());
+ break;
}
- }
- value += to_string(dialog_id.get());
- }
- G()->td_db()->get_binlog_pmc()->set("recently_found_dialog_usernames_and_ids", value);
-}
+ case LogEvent::HandlerType::SetDialogFolderIdOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
-bool MessagesManager::load_recently_found_dialogs(Promise<Unit> &promise) {
- if (recently_found_dialogs_loaded_ >= 2) {
- return true;
- }
+ SetDialogFolderIdOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
- string found_dialogs_str = G()->td_db()->get_binlog_pmc()->get("recently_found_dialog_usernames_and_ids");
- auto found_dialogs = full_split(found_dialogs_str, ',');
- if (found_dialogs.empty()) {
- recently_found_dialogs_loaded_ = 2;
- if (!recently_found_dialog_ids_.empty()) {
- save_recently_found_dialogs();
- }
- return true;
- }
+ auto dialog_id = log_event.dialog_id_;
+ Dialog *d = get_dialog_force(dialog_id, "SetDialogFolderIdOnServerLogEvent");
+ if (d == nullptr || !have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+ d->set_folder_id_log_event_id.log_event_id = event.id_;
- if (recently_found_dialogs_loaded_ == 1 && resolve_recent_found_dialogs_multipromise_.promise_count() == 0) {
- // queries was sent and have already been finished
- auto newly_found_dialogs = std::move(recently_found_dialog_ids_);
- recently_found_dialog_ids_.clear();
+ set_dialog_folder_id(d, log_event.folder_id_);
- for (auto it = found_dialogs.rbegin(); it != found_dialogs.rend(); ++it) {
- if ((*it)[0] == '@') {
- auto dialog_id = resolve_dialog_username(it->substr(1));
- if (dialog_id.is_valid() && have_input_peer(dialog_id, AccessRights::Read)) {
- force_create_dialog(dialog_id, "recently found resolved dialog");
- add_recently_found_dialog_internal(dialog_id);
+ set_dialog_folder_id_on_server(dialog_id, true);
+ break;
+ }
+ case LogEvent::HandlerType::RegetDialog: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
}
- } else {
- auto dialog_id = DialogId(to_integer<int64>(*it));
- CHECK(dialog_id.is_valid());
- if (have_input_peer(dialog_id, AccessRights::Read)) {
- force_create_dialog(dialog_id, "recently found dialog");
- add_recently_found_dialog_internal(dialog_id);
+
+ RegetDialogLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.dialog_id_;
+ Dependencies dependencies;
+ dependencies.add_dialog_dependencies(dialog_id);
+ dependencies.resolve_force(td_, "RegetDialogLogEvent");
+
+ get_dialog_force(dialog_id, "RegetDialogLogEvent"); // load it if exists
+
+ if (!have_input_peer(dialog_id, AccessRights::Read)) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
}
- }
- }
- for (auto it = newly_found_dialogs.rbegin(); it != newly_found_dialogs.rend(); ++it) {
- add_recently_found_dialog_internal(*it);
- }
- recently_found_dialogs_loaded_ = 2;
- if (!newly_found_dialogs.empty()) {
- save_recently_found_dialogs();
- }
- return true;
- }
- resolve_recent_found_dialogs_multipromise_.add_promise(std::move(promise));
- if (recently_found_dialogs_loaded_ == 0) {
- recently_found_dialogs_loaded_ = 1;
+ send_get_dialog_query(dialog_id, Auto(), event.id_, "RegetDialogLogEvent");
+ break;
+ }
+ case LogEvent::HandlerType::UnpinAllDialogMessagesOnServer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
- resolve_recent_found_dialogs_multipromise_.set_ignore_errors(true);
+ UnpinAllDialogMessagesOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
- for (auto &found_dialog : found_dialogs) {
- if (found_dialog[0] == '@') {
- search_public_dialog(found_dialog, false, resolve_recent_found_dialogs_multipromise_.get_promise());
+ unpin_all_dialog_messages_on_server(log_event.dialog_id_, event.id_, Auto());
+ break;
}
- }
- if (G()->parameters().use_message_db) {
- for (auto &found_dialog : found_dialogs) {
- if (found_dialog[0] != '@') {
- auto dialog_id = DialogId(to_integer<int64>(found_dialog));
- CHECK(dialog_id.is_valid());
- // TODO use asynchronous load
- // get_dialog(dialog_id, resolve_recent_found_dialogs_multipromise_.get_promise());
- get_dialog_force(dialog_id);
+ case LogEvent::HandlerType::GetChannelDifference: {
+ if (G()->ignore_background_updates()) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
}
+
+ GetChannelDifferenceLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ DialogId dialog_id(log_event.channel_id);
+ if (dialog_id.get_type() != DialogType::Channel) {
+ LOG(ERROR) << "Trying to run GetChannelDifference in " << dialog_id;
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+ LOG(INFO) << "Continue to run getChannelDifference in " << dialog_id;
+ get_channel_difference_to_log_event_id_.emplace(dialog_id, event.id_);
+ do_get_channel_difference(
+ dialog_id, load_channel_pts(dialog_id), true,
+ telegram_api::make_object<telegram_api::inputChannel>(log_event.channel_id.get(), log_event.access_hash),
+ false, "LogEvent::HandlerType::GetChannelDifference");
+ break;
}
- resolve_recent_found_dialogs_multipromise_.get_promise().set_value(Unit());
- } else {
- get_dialogs(MIN_DIALOG_DATE, MAX_GET_DIALOGS, false, resolve_recent_found_dialogs_multipromise_.get_promise());
- td_->contacts_manager_->search_contacts("", 1, resolve_recent_found_dialogs_multipromise_.get_promise());
+ default:
+ LOG(FATAL) << "Unsupported log event type " << event.type_;
}
}
- return false;
}
Status MessagesManager::add_recently_found_dialog(DialogId dialog_id) {
- if (!have_dialog_force(dialog_id)) {
- return Status::Error(5, "Chat not found");
- }
- if (add_recently_found_dialog_internal(dialog_id)) {
- save_recently_found_dialogs();
+ if (!have_dialog_force(dialog_id, "add_recently_found_dialog")) {
+ return Status::Error(400, "Chat not found");
}
-
+ recently_found_dialogs_.add_dialog(dialog_id);
return Status::OK();
}
Status MessagesManager::remove_recently_found_dialog(DialogId dialog_id) {
- if (!have_dialog_force(dialog_id)) {
- return Status::Error(5, "Chat not found");
- }
- if (remove_recently_found_dialog_internal(dialog_id)) {
- save_recently_found_dialogs();
+ if (!have_dialog_force(dialog_id, "remove_recently_found_dialog")) {
+ return Status::Error(400, "Chat not found");
}
-
+ recently_found_dialogs_.remove_dialog(dialog_id);
return Status::OK();
}
void MessagesManager::clear_recently_found_dialogs() {
- recently_found_dialogs_loaded_ = 2;
- if (recently_found_dialog_ids_.empty()) {
- return;
- }
-
- recently_found_dialog_ids_.clear();
- save_recently_found_dialogs();
-}
-
-bool MessagesManager::add_recently_found_dialog_internal(DialogId dialog_id) {
- CHECK(have_dialog(dialog_id));
-
- if (!recently_found_dialog_ids_.empty() && recently_found_dialog_ids_[0] == dialog_id) {
- return false;
- }
-
- // TODO create function
- auto it = std::find(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.end(), dialog_id);
- if (it == recently_found_dialog_ids_.end()) {
- if (narrow_cast<int32>(recently_found_dialog_ids_.size()) == MAX_RECENT_FOUND_DIALOGS) {
- CHECK(!recently_found_dialog_ids_.empty());
- recently_found_dialog_ids_.back() = dialog_id;
- } else {
- recently_found_dialog_ids_.push_back(dialog_id);
- }
- it = recently_found_dialog_ids_.end() - 1;
- }
- std::rotate(recently_found_dialog_ids_.begin(), it, it + 1);
- return true;
-}
-
-bool MessagesManager::remove_recently_found_dialog_internal(DialogId dialog_id) {
- CHECK(have_dialog(dialog_id));
-
- auto it = std::find(recently_found_dialog_ids_.begin(), recently_found_dialog_ids_.end(), dialog_id);
- if (it == recently_found_dialog_ids_.end()) {
- return false;
- }
- recently_found_dialog_ids_.erase(it);
- return true;
+ recently_found_dialogs_.clear_dialogs();
}
void MessagesManager::suffix_load_loop(Dialog *d) {
@@ -24710,57 +40808,56 @@ void MessagesManager::suffix_load_loop(Dialog *d) {
}
CHECK(!d->suffix_load_done_);
- // Send db query
- LOG(INFO) << "suffix_load send query " << d->dialog_id;
- auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id = d->dialog_id](Result<Unit> result) {
+ auto dialog_id = d->dialog_id;
+ auto from_message_id = d->suffix_load_first_message_id_;
+ LOG(INFO) << "Send suffix load query in " << dialog_id << " from " << from_message_id;
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), dialog_id](Result<Unit> result) {
send_closure(actor_id, &MessagesManager::suffix_load_query_ready, dialog_id);
});
- d->suffix_load_query_message_id_ = d->suffix_load_first_message_id_;
- if (d->suffix_load_first_message_id_.is_valid()) {
- get_history(d->dialog_id, d->suffix_load_first_message_id_, -1, 100, true, true, std::move(promise));
+ d->suffix_load_has_query_ = true;
+ d->suffix_load_query_message_id_ = from_message_id;
+ if (from_message_id.is_valid()) {
+ get_history_impl(d, from_message_id, -1, 100, true, true, std::move(promise));
} else {
- get_history_from_the_end(d->dialog_id, true, true, std::move(promise));
+ CHECK(from_message_id == MessageId());
+ get_history_from_the_end_impl(d, true, true, std::move(promise), "suffix_load_loop");
}
- d->suffix_load_has_query_ = true;
}
void MessagesManager::suffix_load_update_first_message_id(Dialog *d) {
- // Update first_message_id
if (!d->suffix_load_first_message_id_.is_valid()) {
+ if (!d->last_message_id.is_valid()) {
+ return;
+ }
+
d->suffix_load_first_message_id_ = d->last_message_id;
}
- if (!d->suffix_load_first_message_id_.is_valid() && !d->suffix_load_was_query_) {
- return;
- }
- auto it = d->suffix_load_first_message_id_.is_valid() ? MessagesConstIterator(d, d->suffix_load_first_message_id_)
- : MessagesConstIterator(d, MessageId::max());
- while (*it && (*it)->have_previous) {
+ auto it = MessagesConstIterator(d, d->suffix_load_first_message_id_);
+ CHECK(*it != nullptr);
+ CHECK((*it)->message_id == d->suffix_load_first_message_id_);
+ while ((*it)->have_previous) {
--it;
}
- if (*it) {
- d->suffix_load_first_message_id_ = (*it)->message_id;
- } else {
- d->suffix_load_first_message_id_ = MessageId();
- }
+ d->suffix_load_first_message_id_ = (*it)->message_id;
}
void MessagesManager::suffix_load_query_ready(DialogId dialog_id) {
- LOG(INFO) << "suffix_load_query_ready " << dialog_id;
+ LOG(INFO) << "Finished suffix load query in " << dialog_id;
auto *d = get_dialog(dialog_id);
CHECK(d != nullptr);
- d->suffix_load_was_query_ = true;
+ bool is_unchanged = d->suffix_load_first_message_id_ == d->suffix_load_query_message_id_;
suffix_load_update_first_message_id(d);
- if (d->suffix_load_first_message_id_ == d->suffix_load_query_message_id_) {
- LOG(INFO) << "suffix_load done " << dialog_id;
+ if (is_unchanged && d->suffix_load_first_message_id_ == d->suffix_load_query_message_id_) {
+ LOG(INFO) << "Finished suffix load in " << dialog_id;
d->suffix_load_done_ = true;
}
d->suffix_load_has_query_ = false;
// Remove ready queries
- auto *m = get_message_force(d, d->suffix_load_first_message_id_);
+ auto *m = get_message_force(d, d->suffix_load_first_message_id_, "suffix_load_query_ready");
auto ready_it = std::partition(d->suffix_load_queries_.begin(), d->suffix_load_queries_.end(),
[&](auto &value) { return !(d->suffix_load_done_ || value.second(m)); });
- for (auto it = ready_it; it != d->suffix_load_queries_.end(); it++) {
+ for (auto it = ready_it; it != d->suffix_load_queries_.end(); ++it) {
it->first.set_value(Unit());
}
d->suffix_load_queries_.erase(ready_it, d->suffix_load_queries_.end());
@@ -24769,9 +40866,9 @@ void MessagesManager::suffix_load_query_ready(DialogId dialog_id) {
}
void MessagesManager::suffix_load_add_query(Dialog *d,
- std::pair<Promise<>, std::function<bool(const Message *)>> query) {
+ std::pair<Promise<Unit>, std::function<bool(const Message *)>> query) {
suffix_load_update_first_message_id(d);
- auto *m = get_message_force(d, d->suffix_load_first_message_id_);
+ auto *m = get_message_force(d, d->suffix_load_first_message_id_, "suffix_load_add_query");
if (d->suffix_load_done_ || query.second(m)) {
query.first.set_value(Unit());
} else {
@@ -24780,83 +40877,377 @@ void MessagesManager::suffix_load_add_query(Dialog *d,
}
}
-void MessagesManager::suffix_load_till_date(Dialog *d, int32 date, Promise<> promise) {
- LOG(INFO) << "suffix_load_till_date " << d->dialog_id << tag("date", date);
- auto condition = [date](const Message *m) { return m && m->date < date; };
+void MessagesManager::suffix_load_till_date(Dialog *d, int32 date, Promise<Unit> promise) {
+ LOG(INFO) << "Load suffix of " << d->dialog_id << " till date " << date;
+ auto condition = [date](const Message *m) {
+ return m != nullptr && m->date < date;
+ };
suffix_load_add_query(d, std::make_pair(std::move(promise), std::move(condition)));
}
-void MessagesManager::suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise<> promise) {
- LOG(INFO) << "suffix_load_till_message_id " << d->dialog_id << " " << message_id;
- auto condition = [message_id](const Message *m) { return m && m->message_id.get() < message_id.get(); };
+void MessagesManager::suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise<Unit> promise) {
+ LOG(INFO) << "Load suffix of " << d->dialog_id << " till " << message_id;
+ auto condition = [message_id](const Message *m) {
+ return m != nullptr && m->message_id < message_id;
+ };
suffix_load_add_query(d, std::make_pair(std::move(promise), std::move(condition)));
}
+void MessagesManager::set_poll_answer(FullMessageId full_message_id, vector<int32> &&option_ids,
+ Promise<Unit> &&promise) {
+ auto m = get_message_force(full_message_id, "set_poll_answer");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ if (!have_input_peer(full_message_id.get_dialog_id(), AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+ if (m->content->get_type() != MessageContentType::Poll) {
+ return promise.set_error(Status::Error(400, "Message is not a poll"));
+ }
+ if (m->message_id.is_scheduled()) {
+ return promise.set_error(Status::Error(400, "Can't answer polls from scheduled messages"));
+ }
+ if (!m->message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Poll can't be answered"));
+ }
+
+ set_message_content_poll_answer(td_, m->content.get(), full_message_id, std::move(option_ids), std::move(promise));
+}
+
+void MessagesManager::get_poll_voters(FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit,
+ Promise<std::pair<int32, vector<UserId>>> &&promise) {
+ auto m = get_message_force(full_message_id, "get_poll_voters");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ if (!have_input_peer(full_message_id.get_dialog_id(), AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+ if (m->content->get_type() != MessageContentType::Poll) {
+ return promise.set_error(Status::Error(400, "Message is not a poll"));
+ }
+ if (m->message_id.is_scheduled()) {
+ return promise.set_error(Status::Error(400, "Can't get poll results from scheduled messages"));
+ }
+ if (!m->message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Poll results can't be received"));
+ }
+
+ get_message_content_poll_voters(td_, m->content.get(), full_message_id, option_id, offset, limit, std::move(promise));
+}
+
+void MessagesManager::stop_poll(FullMessageId full_message_id, td_api::object_ptr<td_api::ReplyMarkup> &&reply_markup,
+ Promise<Unit> &&promise) {
+ auto m = get_message_force(full_message_id, "stop_poll");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ if (!have_input_peer(full_message_id.get_dialog_id(), AccessRights::Read)) {
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+ if (m->content->get_type() != MessageContentType::Poll) {
+ return promise.set_error(Status::Error(400, "Message is not a poll"));
+ }
+ if (get_message_content_poll_is_closed(td_, m->content.get())) {
+ return promise.set_error(Status::Error(400, "Poll has already been closed"));
+ }
+ if (!can_edit_message(full_message_id.get_dialog_id(), m, true)) {
+ return promise.set_error(Status::Error(400, "Poll can't be stopped"));
+ }
+ if (m->message_id.is_scheduled()) {
+ return promise.set_error(Status::Error(400, "Can't stop polls from scheduled messages"));
+ }
+ if (!m->message_id.is_server()) {
+ return promise.set_error(Status::Error(400, "Poll can't be stopped"));
+ }
+
+ auto r_new_reply_markup = get_reply_markup(std::move(reply_markup), td_->auth_manager_->is_bot(), true, false,
+ has_message_sender_user_id(full_message_id.get_dialog_id(), m));
+ if (r_new_reply_markup.is_error()) {
+ return promise.set_error(r_new_reply_markup.move_as_error());
+ }
+
+ stop_message_content_poll(td_, m->content.get(), full_message_id, r_new_reply_markup.move_as_ok(),
+ std::move(promise));
+}
+
Result<ServerMessageId> MessagesManager::get_invoice_message_id(FullMessageId full_message_id) {
- auto message = get_message_force(full_message_id);
- if (message == nullptr) {
- return Status::Error(5, "Message not found");
+ auto m = get_message_force(full_message_id, "get_invoice_message_id");
+ if (m == nullptr) {
+ return Status::Error(400, "Message not found");
}
- if (message->content->get_id() != MessageInvoice::ID) {
- return Status::Error(5, "Message has no invoice");
+ if (m->content->get_type() != MessageContentType::Invoice) {
+ return Status::Error(400, "Message has no invoice");
}
- auto message_id = full_message_id.get_message_id();
- if (!message_id.is_server()) {
- return Status::Error(5, "Wrong message identifier");
+ if (m->message_id.is_scheduled()) {
+ return Status::Error(400, "Wrong scheduled message identifier");
+ }
+ if (!m->message_id.is_server()) {
+ return Status::Error(400, "Wrong message identifier");
+ }
+ if (m->reply_markup == nullptr || m->reply_markup->inline_keyboard.empty() ||
+ m->reply_markup->inline_keyboard[0].empty() ||
+ m->reply_markup->inline_keyboard[0][0].type != InlineKeyboardButton::Type::Buy) {
+ return Status::Error(400, "Message has no Pay button");
}
- // TODO need to check that message is not forwarded
- return message_id.get_server_message_id();
+ return m->message_id.get_server_message_id();
}
-void MessagesManager::get_payment_form(FullMessageId full_message_id,
- Promise<tl_object_ptr<td_api::paymentForm>> &&promise) {
- auto r_message_id = get_invoice_message_id(full_message_id);
- if (r_message_id.is_error()) {
- return promise.set_error(r_message_id.move_as_error());
+Result<ServerMessageId> MessagesManager::get_payment_successful_message_id(FullMessageId full_message_id) {
+ auto m = get_message_force(full_message_id, "get_payment_successful_message_id");
+ if (m == nullptr) {
+ return Status::Error(400, "Message not found");
+ }
+ if (m->content->get_type() != MessageContentType::PaymentSuccessful) {
+ return Status::Error(400, "Message has wrong type");
}
+ if (m->message_id.is_scheduled()) {
+ return Status::Error(400, "Wrong scheduled message identifier");
+ }
+ if (!m->message_id.is_server()) {
+ return Status::Error(400, "Wrong message identifier");
+ }
+
+ return m->message_id.get_server_message_id();
+}
+
+void MessagesManager::remove_sponsored_dialog() {
+ set_sponsored_dialog(DialogId(), DialogSource());
+}
+
+void MessagesManager::on_get_sponsored_dialog(tl_object_ptr<telegram_api::Peer> peer, DialogSource source,
+ vector<tl_object_ptr<telegram_api::User>> users,
+ vector<tl_object_ptr<telegram_api::Chat>> chats) {
+ CHECK(peer != nullptr);
- td::get_payment_form(r_message_id.ok(), std::move(promise));
+ td_->contacts_manager_->on_get_users(std::move(users), "on_get_sponsored_dialog");
+ td_->contacts_manager_->on_get_chats(std::move(chats), "on_get_sponsored_dialog");
+
+ set_sponsored_dialog(DialogId(peer), std::move(source));
}
-void MessagesManager::validate_order_info(FullMessageId full_message_id, tl_object_ptr<td_api::orderInfo> order_info,
- bool allow_save,
- Promise<tl_object_ptr<td_api::validatedOrderInfo>> &&promise) {
- auto r_message_id = get_invoice_message_id(full_message_id);
- if (r_message_id.is_error()) {
- return promise.set_error(r_message_id.move_as_error());
+void MessagesManager::add_sponsored_dialog(const Dialog *d, DialogSource source) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
}
- td::validate_order_info(r_message_id.ok(), std::move(order_info), allow_save, std::move(promise));
+ CHECK(!sponsored_dialog_id_.is_valid());
+ sponsored_dialog_id_ = d->dialog_id;
+ sponsored_dialog_source_ = std::move(source);
+
+ // update last_pinned_dialog_date in any case, because all chats before SPONSORED_DIALOG_ORDER are known
+ auto dialog_list_id = DialogListId(FolderId::main());
+ auto *list = get_dialog_list(dialog_list_id);
+ CHECK(list != nullptr);
+ DialogDate max_dialog_date(SPONSORED_DIALOG_ORDER, d->dialog_id);
+ if (list->last_pinned_dialog_date_ < max_dialog_date) {
+ list->last_pinned_dialog_date_ = max_dialog_date;
+ update_list_last_dialog_date(*list);
+ }
+
+ if (is_dialog_sponsored(d)) {
+ send_update_chat_position(dialog_list_id, d, "add_sponsored_dialog");
+ // the sponsored dialog must not be saved there
+ }
}
-void MessagesManager::send_payment_form(FullMessageId full_message_id, const string &order_info_id,
- const string &shipping_option_id,
- const tl_object_ptr<td_api::InputCredentials> &credentials,
- Promise<tl_object_ptr<td_api::paymentResult>> &&promise) {
- auto r_message_id = get_invoice_message_id(full_message_id);
- if (r_message_id.is_error()) {
- return promise.set_error(r_message_id.move_as_error());
+void MessagesManager::save_sponsored_dialog() {
+ if (!G()->parameters().use_message_db) {
+ return;
}
- td::send_payment_form(r_message_id.ok(), order_info_id, shipping_option_id, credentials, std::move(promise));
+ LOG(INFO) << "Save sponsored " << sponsored_dialog_id_ << " with source " << sponsored_dialog_source_;
+ if (sponsored_dialog_id_.is_valid()) {
+ G()->td_db()->get_binlog_pmc()->set(
+ "sponsored_dialog_id",
+ PSTRING() << sponsored_dialog_id_.get() << ' ' << sponsored_dialog_source_.DialogSource::serialize());
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase("sponsored_dialog_id");
+ }
}
-void MessagesManager::get_payment_receipt(FullMessageId full_message_id,
- Promise<tl_object_ptr<td_api::paymentReceipt>> &&promise) {
- auto message = get_message_force(full_message_id);
- if (message == nullptr) {
- return promise.set_error(Status::Error(5, "Message not found"));
+void MessagesManager::set_sponsored_dialog(DialogId dialog_id, DialogSource source) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
}
- if (message->content->get_id() != MessagePaymentSuccessful::ID) {
- return promise.set_error(Status::Error(5, "Message has wrong type"));
+ LOG(INFO) << "Change sponsored chat from " << sponsored_dialog_id_ << " to " << dialog_id;
+ if (removed_sponsored_dialog_id_.is_valid() && dialog_id == removed_sponsored_dialog_id_) {
+ return;
}
- auto message_id = full_message_id.get_message_id();
- if (!message_id.is_server()) {
- return promise.set_error(Status::Error(5, "Wrong message identifier"));
+
+ if (sponsored_dialog_id_ == dialog_id) {
+ if (sponsored_dialog_source_ != source) {
+ CHECK(sponsored_dialog_id_.is_valid());
+ sponsored_dialog_source_ = std::move(source);
+ const Dialog *d = get_dialog(sponsored_dialog_id_);
+ CHECK(d != nullptr);
+ send_update_chat_position(DialogListId(FolderId::main()), d, "set_sponsored_dialog");
+ save_sponsored_dialog();
+ }
+ return;
+ }
+
+ bool need_update_total_chat_count = false;
+ if (sponsored_dialog_id_.is_valid()) {
+ const Dialog *d = get_dialog(sponsored_dialog_id_);
+ CHECK(d != nullptr);
+ bool was_sponsored = is_dialog_sponsored(d);
+ sponsored_dialog_id_ = DialogId();
+ sponsored_dialog_source_ = DialogSource();
+ if (was_sponsored) {
+ send_update_chat_position(DialogListId(FolderId::main()), d, "set_sponsored_dialog 2");
+ need_update_total_chat_count = true;
+ }
+ }
+
+ if (dialog_id.is_valid()) {
+ force_create_dialog(dialog_id, "set_sponsored_dialog_id");
+ const Dialog *d = get_dialog(dialog_id);
+ CHECK(d != nullptr);
+ add_sponsored_dialog(d, std::move(source));
+ if (is_dialog_sponsored(d)) {
+ need_update_total_chat_count = !need_update_total_chat_count;
+ }
+ }
+
+ if (need_update_total_chat_count) {
+ auto dialog_list_id = DialogListId(FolderId::main());
+ auto *list = get_dialog_list(dialog_list_id);
+ CHECK(list != nullptr);
+ if (list->is_dialog_unread_count_inited_) {
+ send_update_unread_chat_count(*list, DialogId(), true, "set_sponsored_dialog_id");
+ }
+ }
+
+ save_sponsored_dialog();
+}
+
+td_api::object_ptr<td_api::updateChatFilters> MessagesManager::get_update_chat_filters_object() const {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto update = td_api::make_object<td_api::updateChatFilters>();
+ for (const auto &filter : dialog_filters_) {
+ update->chat_filters_.push_back(filter->get_chat_filter_info_object());
+ }
+ update->main_chat_list_position_ = main_dialog_list_position_;
+ return update;
+}
+
+td_api::object_ptr<td_api::updateUnreadMessageCount> MessagesManager::get_update_unread_message_count_object(
+ const DialogList &list) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(list.is_message_unread_count_inited_);
+ int32 unread_count = list.unread_message_total_count_;
+ int32 unread_unmuted_count = list.unread_message_total_count_ - list.unread_message_muted_count_;
+ CHECK(unread_count >= 0);
+ CHECK(unread_unmuted_count >= 0);
+ return td_api::make_object<td_api::updateUnreadMessageCount>(list.dialog_list_id.get_chat_list_object(), unread_count,
+ unread_unmuted_count);
+}
+
+td_api::object_ptr<td_api::updateUnreadChatCount> MessagesManager::get_update_unread_chat_count_object(
+ const DialogList &list) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(list.is_dialog_unread_count_inited_);
+ int32 unread_count = list.unread_dialog_total_count_;
+ int32 unread_unmuted_count = unread_count - list.unread_dialog_muted_count_;
+ int32 unread_marked_count = list.unread_dialog_marked_count_;
+ int32 unread_unmuted_marked_count = unread_marked_count - list.unread_dialog_muted_marked_count_;
+ CHECK(unread_count >= 0);
+ CHECK(unread_unmuted_count >= 0);
+ CHECK(unread_marked_count >= 0);
+ CHECK(unread_unmuted_marked_count >= 0);
+ return td_api::make_object<td_api::updateUnreadChatCount>(
+ list.dialog_list_id.get_chat_list_object(), get_dialog_total_count(list), unread_count, unread_unmuted_count,
+ unread_marked_count, unread_unmuted_marked_count);
+}
+
+void MessagesManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ if (!td_->auth_manager_->is_bot()) {
+ if (!dialog_filters_.empty()) {
+ updates.push_back(get_update_chat_filters_object());
+ }
+ if (G()->parameters().use_message_db) {
+ for (const auto &it : dialog_lists_) {
+ auto &list = it.second;
+ if (list.is_message_unread_count_inited_) {
+ updates.push_back(get_update_unread_message_count_object(list));
+ }
+ if (list.is_dialog_unread_count_inited_) {
+ updates.push_back(get_update_unread_chat_count_object(list));
+ }
+ }
+ }
}
- td::get_payment_receipt(message_id.get_server_message_id(), std::move(promise));
+ vector<td_api::object_ptr<td_api::Update>> last_message_updates;
+ dialogs_.foreach([&](const DialogId &dialog_id, const unique_ptr<Dialog> &dialog) {
+ const Dialog *d = dialog.get();
+ auto update = td_api::make_object<td_api::updateNewChat>(get_chat_object(d));
+ if (update->chat_->last_message_ != nullptr && update->chat_->last_message_->forward_info_ != nullptr) {
+ last_message_updates.push_back(td_api::make_object<td_api::updateChatLastMessage>(
+ dialog_id.get(), std::move(update->chat_->last_message_), get_chat_positions_object(d)));
+ }
+ updates.push_back(std::move(update));
+
+ if (d->is_opened) {
+ auto info_it = dialog_online_member_counts_.find(dialog_id);
+ if (info_it != dialog_online_member_counts_.end() && info_it->second.is_update_sent) {
+ updates.push_back(td_api::make_object<td_api::updateChatOnlineMemberCount>(
+ dialog_id.get(), info_it->second.online_member_count));
+ }
+ }
+ });
+ append(updates, std::move(last_message_updates));
+}
+
+void MessagesManager::add_message_file_to_downloads(FullMessageId full_message_id, FileId file_id, int32 priority,
+ Promise<td_api::object_ptr<td_api::file>> promise) {
+ auto m = get_message_force(full_message_id, "add_message_file_to_downloads");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(400, "Message not found"));
+ }
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ if (file_view.empty()) {
+ return promise.set_error(Status::Error(400, "File not found"));
+ }
+ file_id = file_view.get_main_file_id();
+ bool is_found = false;
+ for (auto message_file_id : get_message_file_ids(m)) {
+ auto message_file_view = td_->file_manager_->get_file_view(message_file_id);
+ CHECK(!message_file_view.empty());
+ if (message_file_view.get_main_file_id() == file_id) {
+ is_found = true;
+ }
+ }
+ if (!is_found) {
+ return promise.set_error(Status::Error(400, "Message has no specified file"));
+ }
+ if (m->message_id.is_yet_unsent()) {
+ return promise.set_error(Status::Error(400, "Yet unsent messages can't be added to Downloads"));
+ }
+ auto search_text = get_message_search_text(m);
+ auto file_source_id = get_message_file_source_id(full_message_id, true);
+ CHECK(file_source_id.is_valid());
+ send_closure(td_->download_manager_actor_, &DownloadManager::add_file, file_id, file_source_id,
+ std::move(search_text), static_cast<int8>(priority), std::move(promise));
+}
+
+void MessagesManager::get_message_file_search_text(FullMessageId full_message_id, string unique_file_id,
+ Promise<string> promise) {
+ auto m = get_message_force(full_message_id, "get_message_file_search_text");
+ if (m == nullptr) {
+ return promise.set_error(Status::Error(200, "Message not found"));
+ }
+ for (auto file_id : get_message_file_ids(m)) {
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.empty());
+ if (file_view.get_unique_file_id() == unique_file_id) {
+ return promise.set_value(get_message_search_text(m));
+ }
+ }
+ return promise.set_error(Status::Error(200, "File not found"));
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MessagesManager.h b/protocols/Telegram/tdlib/td/td/telegram/MessagesManager.h
index 7b4d84254f..e67e50cdb7 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/MessagesManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/MessagesManager.h
@@ -1,59 +1,91 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/MultiPromise.h"
-#include "td/actor/PromiseFuture.h"
-#include "td/actor/SignalSlot.h"
-#include "td/actor/Timeout.h"
-
-#include "td/db/binlog/BinlogEvent.h"
-
#include "td/telegram/AccessRights.h"
-#include "td/telegram/CallDiscardReason.h"
+#include "td/telegram/AffectedHistory.h"
#include "td/telegram/ChannelId.h"
-#include "td/telegram/ChatId.h"
-#include "td/telegram/Contact.h"
+#include "td/telegram/ChatReactions.h"
+#include "td/telegram/DialogAction.h"
+#include "td/telegram/DialogDate.h"
+#include "td/telegram/DialogDb.h"
+#include "td/telegram/DialogFilterId.h"
#include "td/telegram/DialogId.h"
+#include "td/telegram/DialogListId.h"
+#include "td/telegram/DialogLocation.h"
+#include "td/telegram/DialogNotificationSettings.h"
#include "td/telegram/DialogParticipant.h"
-#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/DialogSource.h"
+#include "td/telegram/EncryptedFile.h"
#include "td/telegram/files/FileId.h"
-#include "td/telegram/Game.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/FolderId.h"
+#include "td/telegram/FullMessageId.h"
#include "td/telegram/Global.h"
-#include "td/telegram/Location.h"
-#include "td/telegram/MessageEntity.h"
+#include "td/telegram/InputDialogId.h"
+#include "td/telegram/InputGroupCallId.h"
+#include "td/telegram/logevent/LogEventHelper.h"
+#include "td/telegram/MessageContentType.h"
+#include "td/telegram/MessageCopyOptions.h"
+#include "td/telegram/MessageDb.h"
#include "td/telegram/MessageId.h"
-#include "td/telegram/MessagesDb.h"
+#include "td/telegram/MessageLinkInfo.h"
+#include "td/telegram/MessageReplyHeader.h"
+#include "td/telegram/MessageReplyInfo.h"
+#include "td/telegram/MessageSearchFilter.h"
+#include "td/telegram/MessageThreadInfo.h"
+#include "td/telegram/MessageTtl.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQuery.h"
-#include "td/telegram/Payments.h"
+#include "td/telegram/Notification.h"
+#include "td/telegram/NotificationGroupId.h"
+#include "td/telegram/NotificationGroupKey.h"
+#include "td/telegram/NotificationGroupType.h"
+#include "td/telegram/NotificationId.h"
+#include "td/telegram/NotificationSettingsScope.h"
#include "td/telegram/Photo.h"
+#include "td/telegram/RecentDialogList.h"
#include "td/telegram/ReplyMarkup.h"
+#include "td/telegram/ReportReason.h"
+#include "td/telegram/RestrictionReason.h"
+#include "td/telegram/ScheduledServerMessageId.h"
+#include "td/telegram/secret_api.h"
#include "td/telegram/SecretChatId.h"
#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
-#include "td/telegram/WebPageId.h"
+#include "td/telegram/Usernames.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/MultiPromise.h"
+#include "td/actor/MultiTimeout.h"
+#include "td/actor/SignalSlot.h"
+#include "td/actor/Timeout.h"
#include "td/utils/buffer.h"
#include "td/utils/ChangesProcessor.h"
#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/Heap.h"
#include "td/utils/Hints.h"
#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
-#include "td/utils/tl_storers.h"
+#include "td/utils/WaitFreeHashMap.h"
+#include "td/utils/WaitFreeHashSet.h"
#include <array>
#include <functional>
-#include <limits>
#include <map>
#include <memory>
#include <set>
@@ -63,734 +95,17 @@
namespace td {
+struct BinlogEvent;
+class Dependencies;
+class DialogActionBar;
+class DialogFilter;
+class DraftMessage;
+struct InputMessageContent;
+class MessageContent;
+struct MessageReactions;
class Td;
-class MultiSequenceDispatcher;
-
-// Do not forget to update MessagesManager::update_message_content when one of the inheritors of this class changes
-class MessageContent {
- public:
- virtual int32 get_id() const = 0;
- virtual ~MessageContent() = default;
-};
-
-class MessageText : public MessageContent {
- public:
- FormattedText text;
- WebPageId web_page_id;
-
- MessageText() = default;
- MessageText(FormattedText text, WebPageId web_page_id) : text(std::move(text)), web_page_id(web_page_id) {
- }
-
- static const int32 ID = 0;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageAnimation : public MessageContent {
- public:
- FileId file_id;
-
- FormattedText caption;
-
- MessageAnimation() = default;
- MessageAnimation(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
- }
-
- static const int32 ID = 1;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageAudio : public MessageContent {
- public:
- FileId file_id;
-
- FormattedText caption;
-
- MessageAudio() = default;
- MessageAudio(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
- }
-
- static const int32 ID = 2;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageDocument : public MessageContent {
- public:
- FileId file_id;
-
- FormattedText caption;
-
- MessageDocument() = default;
- MessageDocument(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
- }
-
- static const int32 ID = 3;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessagePhoto : public MessageContent {
- public:
- Photo photo;
-
- FormattedText caption;
-
- MessagePhoto() = default;
- MessagePhoto(Photo &&photo, FormattedText &&caption) : photo(std::move(photo)), caption(std::move(caption)) {
- }
-
- static const int32 ID = 4;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageSticker : public MessageContent {
- public:
- FileId file_id;
-
- MessageSticker() = default;
- explicit MessageSticker(FileId file_id) : file_id(file_id) {
- }
-
- static const int32 ID = 5;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageVideo : public MessageContent {
- public:
- FileId file_id;
-
- FormattedText caption;
-
- MessageVideo() = default;
- MessageVideo(FileId file_id, FormattedText &&caption) : file_id(file_id), caption(std::move(caption)) {
- }
-
- static const int32 ID = 6;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageVoiceNote : public MessageContent {
- public:
- FileId file_id;
-
- FormattedText caption;
- bool is_listened;
-
- MessageVoiceNote() = default;
- MessageVoiceNote(FileId file_id, FormattedText &&caption, bool is_listened)
- : file_id(file_id), caption(std::move(caption)), is_listened(is_listened) {
- }
-
- static const int32 ID = 7;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageContact : public MessageContent {
- public:
- Contact contact;
-
- MessageContact() = default;
- explicit MessageContact(Contact &&contact) : contact(std::move(contact)) {
- }
-
- static const int32 ID = 8;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageLocation : public MessageContent {
- public:
- Location location;
-
- MessageLocation() = default;
- explicit MessageLocation(Location &&location) : location(std::move(location)) {
- }
-
- static const int32 ID = 9;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageVenue : public MessageContent {
- public:
- Venue venue;
-
- MessageVenue() = default;
- explicit MessageVenue(Venue &&venue) : venue(std::move(venue)) {
- }
-
- static const int32 ID = 10;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatCreate : public MessageContent {
- public:
- string title;
- vector<UserId> participant_user_ids;
-
- MessageChatCreate() = default;
- MessageChatCreate(string &&title, vector<UserId> &&participant_user_ids)
- : title(std::move(title)), participant_user_ids(std::move(participant_user_ids)) {
- }
-
- static const int32 ID = 11;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatChangeTitle : public MessageContent {
- public:
- string title;
-
- MessageChatChangeTitle() = default;
- explicit MessageChatChangeTitle(string &&title) : title(std::move(title)) {
- }
-
- static const int32 ID = 12;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatChangePhoto : public MessageContent {
- public:
- Photo photo;
-
- MessageChatChangePhoto() = default;
- explicit MessageChatChangePhoto(Photo &&photo) : photo(std::move(photo)) {
- }
-
- static const int32 ID = 13;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatDeletePhoto : public MessageContent {
- public:
- static const int32 ID = 14;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatDeleteHistory : public MessageContent {
- public:
- static const int32 ID = 15;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatAddUsers : public MessageContent {
- public:
- vector<UserId> user_ids;
-
- MessageChatAddUsers() = default;
- explicit MessageChatAddUsers(vector<UserId> &&user_ids) : user_ids(std::move(user_ids)) {
- }
-
- static const int32 ID = 16;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatJoinedByLink : public MessageContent {
- public:
- static const int32 ID = 17;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatDeleteUser : public MessageContent {
- public:
- UserId user_id;
-
- MessageChatDeleteUser() = default;
- explicit MessageChatDeleteUser(UserId user_id) : user_id(user_id) {
- }
-
- static const int32 ID = 18;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatMigrateTo : public MessageContent {
- public:
- ChannelId migrated_to_channel_id;
-
- MessageChatMigrateTo() = default;
- explicit MessageChatMigrateTo(ChannelId migrated_to_channel_id) : migrated_to_channel_id(migrated_to_channel_id) {
- }
-
- static const int32 ID = 19;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChannelCreate : public MessageContent {
- public:
- string title;
-
- MessageChannelCreate() = default;
- explicit MessageChannelCreate(string &&title) : title(std::move(title)) {
- }
-
- static const int32 ID = 20;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChannelMigrateFrom : public MessageContent {
- public:
- string title;
- ChatId migrated_from_chat_id;
-
- MessageChannelMigrateFrom() = default;
- MessageChannelMigrateFrom(string &&title, ChatId migrated_from_chat_id)
- : title(std::move(title)), migrated_from_chat_id(migrated_from_chat_id) {
- }
-
- static const int32 ID = 21;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessagePinMessage : public MessageContent {
- public:
- MessageId message_id;
-
- MessagePinMessage() = default;
- explicit MessagePinMessage(MessageId message_id) : message_id(message_id) {
- }
-
- static const int32 ID = 22;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageGame : public MessageContent {
- public:
- Game game;
-
- MessageGame() = default;
- explicit MessageGame(Game &&game) : game(std::move(game)) {
- }
-
- static const int32 ID = 23;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageGameScore : public MessageContent {
- public:
- MessageId game_message_id;
- int64 game_id;
- int32 score;
-
- MessageGameScore() = default;
- MessageGameScore(MessageId game_message_id, int64 game_id, int32 score)
- : game_message_id(game_message_id), game_id(game_id), score(score) {
- }
-
- static const int32 ID = 24;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageScreenshotTaken : public MessageContent {
- public:
- static const int32 ID = 25;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageChatSetTtl : public MessageContent {
- public:
- int32 ttl;
-
- MessageChatSetTtl() = default;
- explicit MessageChatSetTtl(int32 ttl) : ttl(ttl) {
- }
-
- static const int32 ID = 26;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageUnsupported
- : public MessageContent { // TODO save a layer in which the message was received to
- // automatically reget it if the layer changes
- public:
- static const int32 ID = 27;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageCall : public MessageContent {
- public:
- int64 call_id;
- int32 duration;
- CallDiscardReason discard_reason;
-
- MessageCall() = default;
- MessageCall(int64 call_id, int32 duration, CallDiscardReason discard_reason)
- : call_id(call_id), duration(duration), discard_reason(discard_reason) {
- }
-
- static const int32 ID = 28;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageInvoice : public MessageContent {
- public:
- string title;
- string description;
- Photo photo;
- string start_parameter;
-
- // InputMessageInvoice
- Invoice invoice;
- string payload;
- string provider_token;
- string provider_data;
-
- // MessageInvoice
- int64 total_amount = 0;
- MessageId receipt_message_id;
-
- MessageInvoice() = default;
- MessageInvoice(string &&title, string &&description, Photo &&photo, string &&start_parameter, int64 total_amount,
- string &&currency, bool is_test, bool need_shipping_address, MessageId receipt_message_id)
- : title(std::move(title))
- , description(std::move(description))
- , photo(std::move(photo))
- , start_parameter(std::move(start_parameter))
- , invoice(std::move(currency), is_test, need_shipping_address)
- , payload()
- , provider_token()
- , provider_data()
- , total_amount(total_amount)
- , receipt_message_id(receipt_message_id) {
- }
-
- static const int32 ID = 29;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessagePaymentSuccessful : public MessageContent {
- public:
- MessageId invoice_message_id;
- string currency;
- int64 total_amount = 0;
-
- // bots only part
- string invoice_payload;
- string shipping_option_id;
- unique_ptr<OrderInfo> order_info;
- string telegram_payment_charge_id;
- string provider_payment_charge_id;
-
- MessagePaymentSuccessful() = default;
- MessagePaymentSuccessful(MessageId invoice_message_id, string &&currency, int64 total_amount)
- : invoice_message_id(invoice_message_id), currency(std::move(currency)), total_amount(total_amount) {
- }
-
- static const int32 ID = 30;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageVideoNote : public MessageContent {
- public:
- FileId file_id;
-
- bool is_viewed = false;
-
- MessageVideoNote() = default;
- MessageVideoNote(FileId file_id, bool is_viewed) : file_id(file_id), is_viewed(is_viewed) {
- }
-
- static const int32 ID = 31;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageContactRegistered : public MessageContent {
- public:
- static const int32 ID = 32;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageExpiredPhoto : public MessageContent {
- public:
- MessageExpiredPhoto() = default;
-
- static const int32 ID = 33;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageExpiredVideo : public MessageContent {
- public:
- MessageExpiredVideo() = default;
-
- static const int32 ID = 34;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageLiveLocation : public MessageContent {
- public:
- Location location;
- int32 period;
-
- MessageLiveLocation() = default;
- MessageLiveLocation(Location &&location, int32 period) : location(std::move(location)), period(period) {
- }
-
- static const int32 ID = 35;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageCustomServiceAction : public MessageContent {
- public:
- string message;
-
- MessageCustomServiceAction() = default;
- explicit MessageCustomServiceAction(string &&message) : message(std::move(message)) {
- }
-
- static const int32 ID = 36;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class MessageWebsiteConnected : public MessageContent {
- public:
- string domain_name;
-
- MessageWebsiteConnected() = default;
- explicit MessageWebsiteConnected(string &&domain_name) : domain_name(std::move(domain_name)) {
- }
-
- static const int32 ID = 37;
- int32 get_id() const override {
- return ID;
- }
-};
-
-class InputMessageText {
- public:
- FormattedText text;
- bool disable_web_page_preview = false;
- bool clear_draft = false;
- InputMessageText() = default;
- InputMessageText(FormattedText text, bool disable_web_page_preview, bool clear_draft)
- : text(std::move(text)), disable_web_page_preview(disable_web_page_preview), clear_draft(clear_draft) {
- }
-};
-
-bool operator==(const InputMessageText &lhs, const InputMessageText &rhs);
-bool operator!=(const InputMessageText &lhs, const InputMessageText &rhs);
-
-class DraftMessage {
- public:
- int32 date;
- MessageId reply_to_message_id;
- InputMessageText input_message_text;
-};
-
-using NotificationSettingsScope = int64;
-constexpr NotificationSettingsScope NOTIFICATION_SETTINGS_FOR_PRIVATE_CHATS =
- std::numeric_limits<NotificationSettingsScope>::min();
-constexpr NotificationSettingsScope NOTIFICATION_SETTINGS_FOR_GROUP_CHATS =
- std::numeric_limits<NotificationSettingsScope>::min() + 1;
-constexpr NotificationSettingsScope NOTIFICATION_SETTINGS_FOR_ALL_CHATS =
- std::numeric_limits<NotificationSettingsScope>::min() + 2;
-
-class NotificationSettings {
- public:
- int32 mute_until = 0;
- string sound = "default";
- bool show_preview = true;
- bool silent_send_message = false;
- bool is_synchronized = false;
-
- NotificationSettings() = default;
-
- NotificationSettings(int32 mute_until, string sound, bool show_preview, bool silent_send_message)
- : mute_until(mute_until)
- , sound(std::move(sound))
- , show_preview(show_preview)
- , silent_send_message(silent_send_message)
- , is_synchronized(true) {
- }
-};
-
-inline StringBuilder &operator<<(StringBuilder &string_builder, NotificationSettings notification_settings) {
- return string_builder << "[" << notification_settings.mute_until << ", " << notification_settings.sound << ", "
- << notification_settings.show_preview << ", " << notification_settings.silent_send_message
- << ", " << notification_settings.is_synchronized << "]";
-}
-
-inline constexpr size_t search_messages_filter_size() {
- return static_cast<int32>(SearchMessagesFilter::Size) - 1;
-}
-
-inline int32 search_messages_filter_index(SearchMessagesFilter filter) {
- CHECK(filter != SearchMessagesFilter::Empty);
- return static_cast<int32>(filter) - 1;
-}
-
-inline int32 search_messages_filter_index_mask(SearchMessagesFilter filter) {
- if (filter == SearchMessagesFilter::Empty) {
- return 0;
- }
- return 1 << search_messages_filter_index(filter);
-}
-
-inline int32 search_calls_filter_index(SearchMessagesFilter filter) {
- CHECK(filter == SearchMessagesFilter::Call || filter == SearchMessagesFilter::MissedCall);
- return static_cast<int32>(filter) - static_cast<int32>(SearchMessagesFilter::Call);
-}
-
-class DialogDate {
- int64 order;
- DialogId dialog_id;
-
- public:
- DialogDate(int64 order, DialogId dialog_id) : order(order), dialog_id(dialog_id) {
- }
-
- bool operator<(const DialogDate &other) const {
- return order > other.order || (order == other.order && dialog_id.get() > other.dialog_id.get());
- }
-
- bool operator<=(const DialogDate &other) const {
- return order >= other.order && (order != other.order || dialog_id.get() >= other.dialog_id.get());
- }
-
- bool operator==(const DialogDate &other) const {
- return order == other.order && dialog_id == other.dialog_id;
- }
-
- bool operator!=(const DialogDate &other) const {
- return order != other.order || dialog_id != other.dialog_id;
- }
-
- int64 get_order() const {
- return order;
- }
- DialogId get_dialog_id() const {
- return dialog_id;
- }
- int32 get_date() const {
- return static_cast<int32>((order >> 32) & 0x7FFFFFFF);
- }
- MessageId get_message_id() const {
- return MessageId(ServerMessageId(static_cast<int32>(order & 0x7FFFFFFF)));
- }
-
- friend struct DialogDateHash;
-
- friend StringBuilder &operator<<(StringBuilder &string_builder, DialogDate dialog_date);
-};
-
-const DialogDate MIN_DIALOG_DATE(std::numeric_limits<int64>::max(), DialogId());
-const DialogDate MAX_DIALOG_DATE(0, DialogId());
-const int64 DEFAULT_ORDER = -1;
-
-struct DialogDateHash {
- std::size_t operator()(const DialogDate &dialog_date) const {
- return std::hash<int64>()(dialog_date.order) * 2023654985u + DialogIdHash()(dialog_date.dialog_id);
- }
-};
-
-inline StringBuilder &operator<<(StringBuilder &string_builder, DialogDate dialog_date) {
- return string_builder << "[" << dialog_date.order << ", " << dialog_date.dialog_id.get() << "]";
-}
-
-class dummyUpdate : public telegram_api::Update {
- public:
- static constexpr int32 ID = 1234567891;
- int32 get_id() const override {
- return ID;
- }
-
- void store(TlStorerUnsafe &s) const override {
- UNREACHABLE();
- }
-
- void store(TlStorerCalcLength &s) const override {
- UNREACHABLE();
- }
-
- void store(TlStorerToString &s, const char *field_name) const override;
-};
-
-class Dependencies {
- public:
- std::unordered_set<UserId, UserIdHash> user_ids;
- std::unordered_set<ChatId, ChatIdHash> chat_ids;
- std::unordered_set<ChannelId, ChannelIdHash> channel_ids;
- std::unordered_set<SecretChatId, SecretChatIdHash> secret_chat_ids;
- std::unordered_set<DialogId, DialogIdHash> dialog_ids;
- std::unordered_set<WebPageId, WebPageIdHash> web_page_ids;
-};
-
-struct CallsDbState {
- std::array<MessageId, 2> first_calls_database_message_id_by_index;
- std::array<int32, 2> message_count_by_index;
-};
-
-class MessagesManager : public Actor {
+class MessagesManager final : public Actor {
public:
// static constexpr int32 MESSAGE_FLAG_IS_UNREAD = 1 << 0;
static constexpr int32 MESSAGE_FLAG_IS_OUT = 1 << 1;
@@ -802,19 +117,22 @@ class MessagesManager : public Actor {
static constexpr int32 MESSAGE_FLAG_HAS_ENTITIES = 1 << 7;
static constexpr int32 MESSAGE_FLAG_HAS_FROM_ID = 1 << 8;
static constexpr int32 MESSAGE_FLAG_HAS_MEDIA = 1 << 9;
- static constexpr int32 MESSAGE_FLAG_HAS_VIEWS = 1 << 10;
+ static constexpr int32 MESSAGE_FLAG_HAS_INTERACTION_INFO = 1 << 10;
static constexpr int32 MESSAGE_FLAG_IS_SENT_VIA_BOT = 1 << 11;
static constexpr int32 MESSAGE_FLAG_IS_SILENT = 1 << 13;
static constexpr int32 MESSAGE_FLAG_IS_POST = 1 << 14;
static constexpr int32 MESSAGE_FLAG_HAS_EDIT_DATE = 1 << 15;
static constexpr int32 MESSAGE_FLAG_HAS_AUTHOR_SIGNATURE = 1 << 16;
static constexpr int32 MESSAGE_FLAG_HAS_MEDIA_ALBUM_ID = 1 << 17;
-
- static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_AUTHOR_ID = 1 << 0;
- static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_CHANNEL_ID = 1 << 1;
- static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_MESSAGE_ID = 1 << 2;
- static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_AUTHOR_SIGNATURE = 1 << 3;
- static constexpr int32 MESSAGE_FORWARD_HEADER_FLAG_HAS_SAVED_FROM = 1 << 4;
+ static constexpr int32 MESSAGE_FLAG_IS_FROM_SCHEDULED = 1 << 18;
+ static constexpr int32 MESSAGE_FLAG_IS_LEGACY = 1 << 19;
+ static constexpr int32 MESSAGE_FLAG_HAS_REACTIONS = 1 << 20;
+ static constexpr int32 MESSAGE_FLAG_HIDE_EDIT_DATE = 1 << 21;
+ static constexpr int32 MESSAGE_FLAG_IS_RESTRICTED = 1 << 22;
+ static constexpr int32 MESSAGE_FLAG_HAS_REPLY_INFO = 1 << 23;
+ static constexpr int32 MESSAGE_FLAG_IS_PINNED = 1 << 24;
+ static constexpr int32 MESSAGE_FLAG_HAS_TTL_PERIOD = 1 << 25;
+ static constexpr int32 MESSAGE_FLAG_NOFORWARDS = 1 << 26;
static constexpr int32 SEND_MESSAGE_FLAG_IS_REPLY = 1 << 0;
static constexpr int32 SEND_MESSAGE_FLAG_DISABLE_WEB_PAGE_PREVIEW = 1 << 1;
@@ -825,39 +143,45 @@ class MessagesManager : public Actor {
static constexpr int32 SEND_MESSAGE_FLAG_FROM_BACKGROUND = 1 << 6;
static constexpr int32 SEND_MESSAGE_FLAG_CLEAR_DRAFT = 1 << 7;
static constexpr int32 SEND_MESSAGE_FLAG_WITH_MY_SCORE = 1 << 8;
- static constexpr int32 SEND_MESSAGE_FLAG_GROUP_MEDIA = 1 << 9;
+ static constexpr int32 SEND_MESSAGE_FLAG_IS_FROM_THREAD = 1 << 9;
+ static constexpr int32 SEND_MESSAGE_FLAG_HAS_SCHEDULE_DATE = 1 << 10;
static constexpr int32 SEND_MESSAGE_FLAG_HAS_MESSAGE = 1 << 11;
+ static constexpr int32 SEND_MESSAGE_FLAG_HAS_SEND_AS = 1 << 13;
+ static constexpr int32 SEND_MESSAGE_FLAG_NOFORWARDS = 1 << 14;
+ static constexpr int32 SEND_MESSAGE_FLAG_UPDATE_STICKER_SETS_ORDER = 1 << 15;
+
+ static constexpr int32 ONLINE_MEMBER_COUNT_CACHE_EXPIRE_TIME = 30 * 60;
MessagesManager(Td *td, ActorShared<> parent);
MessagesManager(const MessagesManager &) = delete;
MessagesManager &operator=(const MessagesManager &) = delete;
MessagesManager(MessagesManager &&) = delete;
MessagesManager &operator=(MessagesManager &&) = delete;
- ~MessagesManager() override;
+ ~MessagesManager() final;
static vector<MessageId> get_message_ids(const vector<int64> &input_message_ids);
static vector<int32> get_server_message_ids(const vector<MessageId> &message_ids);
- static tl_object_ptr<telegram_api::InputMessage> get_input_message(MessageId message_id);
-
- static MessageId get_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr);
+ static vector<int32> get_scheduled_server_message_ids(const vector<MessageId> &message_ids);
- DialogId get_message_dialog_id(const tl_object_ptr<telegram_api::Message> &message_ptr) const;
+ static MessageId get_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr, bool is_scheduled);
- FullMessageId get_full_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr) const;
+ static DialogId get_message_dialog_id(const tl_object_ptr<telegram_api::Message> &message_ptr);
- static int32 get_message_date(const tl_object_ptr<telegram_api::Message> &message_ptr);
+ static FullMessageId get_full_message_id(const tl_object_ptr<telegram_api::Message> &message_ptr, bool is_scheduled);
tl_object_ptr<telegram_api::InputPeer> get_input_peer(DialogId dialog_id, AccessRights access_rights) const;
+ static tl_object_ptr<telegram_api::InputPeer> get_input_peer_force(DialogId dialog_id);
+
vector<tl_object_ptr<telegram_api::InputPeer>> get_input_peers(const vector<DialogId> &dialog_ids,
AccessRights access_rights) const;
- tl_object_ptr<telegram_api::inputDialogPeer> get_input_dialog_peer(DialogId dialog_id,
+ tl_object_ptr<telegram_api::InputDialogPeer> get_input_dialog_peer(DialogId dialog_id,
AccessRights access_rights) const;
- vector<tl_object_ptr<telegram_api::inputDialogPeer>> get_input_dialog_peers(const vector<DialogId> &dialog_ids,
+ vector<tl_object_ptr<telegram_api::InputDialogPeer>> get_input_dialog_peers(const vector<DialogId> &dialog_ids,
AccessRights access_rights) const;
tl_object_ptr<telegram_api::inputEncryptedChat> get_input_encrypted_chat(DialogId dialog_id,
@@ -865,82 +189,151 @@ class MessagesManager : public Actor {
bool have_input_peer(DialogId dialog_id, AccessRights access_rights) const;
+ void on_get_empty_messages(DialogId dialog_id, const vector<MessageId> &empty_message_ids);
+
+ struct MessagesInfo {
+ vector<tl_object_ptr<telegram_api::Message>> messages;
+ int32 total_count = 0;
+ bool is_channel_messages = false;
+ };
+ MessagesInfo get_messages_info(tl_object_ptr<telegram_api::messages_Messages> &&messages_ptr, const char *source);
+
+ void get_channel_difference_if_needed(DialogId dialog_id, MessagesInfo &&messages_info,
+ Promise<MessagesInfo> &&promise);
+
+ void get_channel_differences_if_needed(MessagesInfo &&messages_info, Promise<MessagesInfo> &&promise);
+
void on_get_messages(vector<tl_object_ptr<telegram_api::Message>> &&messages, bool is_channel_message,
- const char *source);
+ bool is_scheduled, Promise<Unit> &&promise, const char *source);
- void on_get_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, bool from_the_end,
- vector<tl_object_ptr<telegram_api::Message>> &&messages);
+ void on_get_history(DialogId dialog_id, MessageId from_message_id, MessageId old_last_new_message_id, int32 offset,
+ int32 limit, bool from_the_end, vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<Unit> &&promise);
void on_get_public_dialogs_search_result(const string &query, vector<tl_object_ptr<telegram_api::Peer>> &&my_peers,
vector<tl_object_ptr<telegram_api::Peer>> &&peers);
void on_failed_public_dialogs_search(const string &query, Status &&error);
- void on_get_dialog_messages_search_result(DialogId dialog_id, const string &query, UserId sender_user_id,
+ void on_get_message_search_result_calendar(DialogId dialog_id, MessageId from_message_id, MessageSearchFilter filter,
+ int64 random_id, int32 total_count,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ vector<tl_object_ptr<telegram_api::searchResultsCalendarPeriod>> &&periods,
+ Promise<Unit> &&promise);
+ void on_failed_get_message_search_result_calendar(DialogId dialog_id, int64 random_id);
+
+ void on_get_dialog_messages_search_result(DialogId dialog_id, const string &query, DialogId sender_dialog_id,
MessageId from_message_id, int32 offset, int32 limit,
- SearchMessagesFilter filter, int64 random_id, int32 total_count,
- vector<tl_object_ptr<telegram_api::Message>> &&messages);
+ MessageSearchFilter filter, MessageId top_thread_message_id,
+ int64 random_id, int32 total_count,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<Unit> &&promise);
void on_failed_dialog_messages_search(DialogId dialog_id, int64 random_id);
+ void on_get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, int32 total_count,
+ Promise<int32> &&promise);
+
void on_get_messages_search_result(const string &query, int32 offset_date, DialogId offset_dialog_id,
- MessageId offset_message_id, int32 limit, int64 random_id, int32 total_count,
- vector<tl_object_ptr<telegram_api::Message>> &&messages);
+ MessageId offset_message_id, int32 limit, MessageSearchFilter filter,
+ int32 min_date, int32 max_date, int64 random_id, int32 total_count,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages, Promise<Unit> &&promise);
void on_failed_messages_search(int64 random_id);
- void on_get_recent_locations(DialogId dialog_id, int32 limit, int64 random_id, int32 total_count,
- vector<tl_object_ptr<telegram_api::Message>> &&messages);
- void on_get_recent_locations_failed(int64 random_id);
+ void on_get_outgoing_document_messages(vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise);
+
+ void on_get_scheduled_server_messages(DialogId dialog_id, uint32 generation,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages, bool is_not_modified);
+
+ void on_get_recent_locations(DialogId dialog_id, int32 limit, int32 total_count,
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<td_api::object_ptr<td_api::messages>> &&promise);
- // if message is from_update, flags have_previous and have_next are ignored and should be both true
+ void on_get_message_public_forwards(int32 total_count, vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise);
+
+ // if message is from_update, flags have_previous and have_next are ignored and must be both true
FullMessageId on_get_message(tl_object_ptr<telegram_api::Message> message_ptr, bool from_update,
- bool is_channel_message, bool have_previous, bool have_next, const char *source);
+ bool is_channel_message, bool is_scheduled, bool have_previous, bool have_next,
+ const char *source);
- void open_secret_message(SecretChatId secret_chat_id, int64 random_id, Promise<>);
+ void open_secret_message(SecretChatId secret_chat_id, int64 random_id, Promise<Unit>);
- void on_send_secret_message_success(int64 random_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::EncryptedFile> file_ptr, Promise<> promise);
- void on_send_secret_message_error(int64 random_id, Status error, Promise<> promise);
+ void on_send_secret_message_success(int64 random_id, MessageId message_id, int32 date, unique_ptr<EncryptedFile> file,
+ Promise<Unit> promise);
+ void on_send_secret_message_error(int64 random_id, Status error, Promise<Unit> promise);
- void delete_secret_messages(SecretChatId secret_chat_id, std::vector<int64> random_ids, Promise<> promise);
+ void delete_secret_messages(SecretChatId secret_chat_id, std::vector<int64> random_ids, Promise<Unit> promise);
- void delete_secret_chat_history(SecretChatId secret_chat_id, MessageId last_message_id, Promise<> promise);
+ void delete_secret_chat_history(SecretChatId secret_chat_id, bool remove_from_dialog_list, MessageId last_message_id,
+ Promise<Unit> promise);
void read_secret_chat_outbox(SecretChatId secret_chat_id, int32 up_to_date, int32 read_date);
+ void on_update_secret_chat_state(SecretChatId secret_chat_id, SecretChatState state);
+
void on_get_secret_message(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::encryptedFile> file,
- tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise);
+ unique_ptr<EncryptedFile> file, tl_object_ptr<secret_api::decryptedMessage> message,
+ Promise<Unit> promise);
void on_secret_chat_screenshot_taken(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date,
- int64 random_id, Promise<> promise);
+ int64 random_id, Promise<Unit> promise);
void on_secret_chat_ttl_changed(SecretChatId secret_chat_id, UserId user_id, MessageId message_id, int32 date,
- int32 ttl, int64 random_id, Promise<> promise);
+ int32 ttl, int64 random_id, Promise<Unit> promise);
void on_update_sent_text_message(int64 random_id, tl_object_ptr<telegram_api::MessageMedia> message_media,
vector<tl_object_ptr<telegram_api::MessageEntity>> &&entities);
- void on_update_message_web_page(FullMessageId full_message_id, bool have_web_page);
+ void delete_pending_message_web_page(FullMessageId full_message_id);
- void on_get_dialogs(vector<tl_object_ptr<telegram_api::dialog>> &&dialogs, int32 total_count,
- vector<tl_object_ptr<telegram_api::Message>> &&messages, Promise<Unit> &&promise);
+ void on_get_dialogs(FolderId folder_id, vector<tl_object_ptr<telegram_api::Dialog>> &&dialog_folders,
+ int32 total_count, vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<Unit> &&promise);
- void on_get_common_dialogs(UserId user_id, vector<tl_object_ptr<telegram_api::Chat>> &&chats, int32 total_count);
+ void on_get_common_dialogs(UserId user_id, int64 offset_chat_id, vector<tl_object_ptr<telegram_api::Chat>> &&chats,
+ int32 total_count);
bool on_update_message_id(int64 random_id, MessageId new_message_id, const string &source);
- void on_update_dialog_draft_message(DialogId dialog_id, tl_object_ptr<telegram_api::DraftMessage> &&draft_message);
+ void on_update_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id,
+ tl_object_ptr<telegram_api::DraftMessage> &&draft_message);
+
+ void on_update_dialog_is_pinned(FolderId folder_id, DialogId dialog_id, bool is_pinned);
- void on_update_dialog_pinned(DialogId dialog_id, bool is_pinned);
+ void on_update_pinned_dialogs(FolderId folder_id);
- void on_update_pinned_dialogs();
+ void on_update_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread);
- void on_update_service_notification(tl_object_ptr<telegram_api::updateServiceNotification> &&update);
+ void on_update_dialog_is_blocked(DialogId dialog_id, bool is_blocked);
- void on_update_contact_registered(tl_object_ptr<telegram_api::updateContactRegistered> &&update);
+ void on_update_dialog_last_pinned_message_id(DialogId dialog_id, MessageId last_pinned_message_id);
- void on_update_new_channel_message(tl_object_ptr<telegram_api::updateNewChannelMessage> &&update);
+ void on_update_dialog_theme_name(DialogId dialog_id, string theme_name);
- void on_update_edit_channel_message(tl_object_ptr<telegram_api::updateEditChannelMessage> &&update);
+ void on_update_dialog_pending_join_requests(DialogId dialog_id, int32 pending_join_request_count,
+ vector<int64> pending_requesters);
+
+ void on_update_dialog_has_scheduled_server_messages(DialogId dialog_id, bool has_scheduled_server_messages);
+
+ void on_update_dialog_folder_id(DialogId dialog_id, FolderId folder_id);
+
+ void on_update_dialog_group_call(DialogId dialog_id, bool has_active_group_call, bool is_group_call_empty,
+ const char *source, bool force = false);
+
+ void on_update_dialog_group_call_id(DialogId dialog_id, InputGroupCallId input_group_call_id);
+
+ void on_update_dialog_default_join_group_call_as_dialog_id(DialogId dialog_id, DialogId default_join_as_dialog_id,
+ bool force);
+
+ void on_update_dialog_default_send_message_as_dialog_id(DialogId dialog_id, DialogId default_send_as_dialog_id,
+ bool force);
+
+ void on_update_dialog_message_ttl(DialogId dialog_id, MessageTtl message_ttl);
+
+ void on_update_dialog_filters();
+
+ void on_update_service_notification(tl_object_ptr<telegram_api::updateServiceNotification> &&update,
+ bool skip_new_entities, Promise<Unit> &&promise);
void on_update_read_channel_inbox(tl_object_ptr<telegram_api::updateReadChannelInbox> &&update);
@@ -949,28 +342,83 @@ class MessagesManager : public Actor {
void on_update_read_channel_messages_contents(
tl_object_ptr<telegram_api::updateChannelReadMessagesContents> &&update);
+ void on_update_read_message_comments(DialogId dialog_id, MessageId message_id, MessageId max_message_id,
+ MessageId last_read_inbox_message_id, MessageId last_read_outbox_message_id);
+
void on_update_channel_too_long(tl_object_ptr<telegram_api::updateChannelTooLong> &&update, bool force_apply);
- void on_update_message_views(FullMessageId full_message_id, int32 views);
+ void on_update_message_view_count(FullMessageId full_message_id, int32 view_count);
+
+ void on_update_message_forward_count(FullMessageId full_message_id, int32 forward_count);
+
+ void on_update_message_reactions(FullMessageId full_message_id,
+ tl_object_ptr<telegram_api::messageReactions> &&reactions, Promise<Unit> &&promise);
+
+ void update_message_reactions(FullMessageId full_message_id, unique_ptr<MessageReactions> &&reactions);
+
+ void try_reload_message_reactions(DialogId dialog_id, bool is_finished);
+
+ void on_get_message_reaction_list(FullMessageId full_message_id, const string &reaction,
+ FlatHashMap<string, vector<DialogId>> reactions, int32 total_count);
+
+ void on_update_message_interaction_info(FullMessageId full_message_id, int32 view_count, int32 forward_count,
+ bool has_reply_info,
+ tl_object_ptr<telegram_api::messageReplies> &&reply_info);
- void on_read_channel_inbox(ChannelId channel_id, MessageId max_message_id, int32 server_unread_count);
+ void on_update_live_location_viewed(FullMessageId full_message_id);
+
+ void on_update_some_live_location_viewed(Promise<Unit> &&promise);
+
+ void on_update_message_extended_media(FullMessageId full_message_id,
+ telegram_api::object_ptr<telegram_api::MessageExtendedMedia> extended_media);
+
+ void on_external_update_message_content(FullMessageId full_message_id);
+
+ void on_update_message_content(FullMessageId full_message_id);
+
+ void on_read_channel_inbox(ChannelId channel_id, MessageId max_message_id, int32 server_unread_count, int32 pts,
+ const char *source);
void on_read_channel_outbox(ChannelId channel_id, MessageId max_message_id);
void on_update_channel_max_unavailable_message_id(ChannelId channel_id, MessageId max_unavailable_message_id);
- void on_user_dialog_action(DialogId dialog_id, UserId user_id, tl_object_ptr<td_api::ChatAction> &&action,
- int32 message_content_id = -1);
+ void on_update_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, bool is_from_server);
+
+ void on_update_delete_scheduled_messages(DialogId dialog_id, vector<ScheduledServerMessageId> &&server_message_ids);
+
+ void on_update_created_public_broadcasts(vector<ChannelId> channel_ids);
+
+ void on_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id,
+ DialogAction action, int32 date,
+ MessageContentType message_content_type = MessageContentType::None);
+
+ void read_history_inbox(DialogId dialog_id, MessageId max_message_id, int32 unread_count, const char *source);
void delete_messages(DialogId dialog_id, const vector<MessageId> &message_ids, bool revoke, Promise<Unit> &&promise);
- void delete_dialog_history(DialogId dialog_id, bool remove_from_dialog_list, Promise<Unit> &&promise);
+ void on_failed_message_deletion(DialogId dialog_id, const vector<int32> &server_message_ids);
+
+ void on_failed_scheduled_message_deletion(DialogId dialog_id, const vector<MessageId> &message_ids);
+
+ void delete_dialog_history(DialogId dialog_id, bool remove_from_dialog_list, bool revoke, Promise<Unit> &&promise);
+
+ void delete_topic_history(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> &&promise);
+
+ void delete_all_call_messages(bool revoke, Promise<Unit> &&promise);
+
+ void delete_dialog_messages_by_sender(DialogId dialog_id, DialogId sender_dialog_id, Promise<Unit> &&promise);
+
+ void delete_dialog_messages_by_date(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke,
+ Promise<Unit> &&promise);
- void delete_dialog_messages_from_user(DialogId dialog_id, UserId user_id, Promise<Unit> &&promise);
+ void on_dialog_deleted(DialogId dialog_id, Promise<Unit> &&promise);
- void delete_dialog(DialogId dialog_id);
+ void on_update_dialog_group_call_rights(DialogId dialog_id);
- void read_all_dialog_mentions(DialogId dialog_id, Promise<Unit> &&promise);
+ void read_all_dialog_mentions(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> &&promise);
+
+ void read_all_dialog_reactions(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> &&promise);
Status add_recently_found_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT;
@@ -978,65 +426,83 @@ class MessagesManager : public Actor {
void clear_recently_found_dialogs();
- DialogId resolve_dialog_username(const string &username);
+ std::pair<int32, vector<DialogId>> get_recently_opened_dialogs(int32 limit, Promise<Unit> &&promise);
+
+ DialogId resolve_dialog_username(const string &username) const;
DialogId search_public_dialog(const string &username_to_search, bool force, Promise<Unit> &&promise);
- Result<FormattedText> process_input_caption(DialogId dialog_id, tl_object_ptr<td_api::formattedText> &&text,
- bool is_bot) const;
+ void reload_voice_chat_on_search(const string &username);
- Result<InputMessageText> process_input_message_text(
- DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content, bool is_bot,
- bool for_draft = false) const TD_WARN_UNUSED_RESULT;
+ void get_dialog_send_message_as_dialog_ids(DialogId dialog_id,
+ Promise<td_api::object_ptr<td_api::chatMessageSenders>> &&promise,
+ bool is_recursive = false);
- static Result<std::pair<Location, int32>> process_input_message_location(
- tl_object_ptr<td_api::InputMessageContent> &&input_message_content) TD_WARN_UNUSED_RESULT;
+ void set_dialog_default_send_message_as_dialog_id(DialogId dialog_id, DialogId message_sender_dialog_id,
+ Promise<Unit> &&promise);
- static Result<Venue> process_input_message_venue(tl_object_ptr<td_api::InputMessageContent> &&input_message_content)
- TD_WARN_UNUSED_RESULT;
+ bool get_dialog_silent_send_message(DialogId dialog_id) const;
+
+ DialogId get_dialog_default_send_message_as_dialog_id(DialogId dialog_id) const;
- static Result<Contact> process_input_message_contact(
+ Result<td_api::object_ptr<td_api::message>> send_message(
+ DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id,
+ tl_object_ptr<td_api::messageSendOptions> &&options, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::InputMessageContent> &&input_message_content) TD_WARN_UNUSED_RESULT;
- Result<Game> process_input_message_game(tl_object_ptr<td_api::InputMessageContent> &&input_message_content) const
- TD_WARN_UNUSED_RESULT;
+ Result<td_api::object_ptr<td_api::messages>> send_message_group(
+ DialogId dialog_id, MessageId top_thread_message_id, MessageId reply_to_message_id,
+ tl_object_ptr<td_api::messageSendOptions> &&options,
+ vector<tl_object_ptr<td_api::InputMessageContent>> &&input_message_contents,
+ bool only_preview) TD_WARN_UNUSED_RESULT;
+
+ Result<MessageId> send_bot_start_message(UserId bot_user_id, DialogId dialog_id,
+ const string &parameter) TD_WARN_UNUSED_RESULT;
- bool need_skip_bot_commands(DialogId dialog_id, bool is_bot) const;
+ Result<MessageId> send_inline_query_result_message(DialogId dialog_id, MessageId top_thread_message_id,
+ MessageId reply_to_message_id,
+ tl_object_ptr<td_api::messageSendOptions> &&options,
+ int64 query_id, const string &result_id,
+ bool hide_via_bot) TD_WARN_UNUSED_RESULT;
- FormattedText get_message_text(string message_text,
- vector<tl_object_ptr<telegram_api::MessageEntity>> &&server_entities,
- int32 send_date) const;
+ Result<td_api::object_ptr<td_api::messages>> forward_messages(DialogId to_dialog_id, MessageId top_thread_message_id,
+ DialogId from_dialog_id, vector<MessageId> message_ids,
+ tl_object_ptr<td_api::messageSendOptions> &&options,
+ bool in_game_share,
+ vector<MessageCopyOptions> &&copy_options,
+ bool only_preview) TD_WARN_UNUSED_RESULT;
- Result<MessageId> send_message(DialogId dialog_id, MessageId reply_to_message_id, bool disable_notification,
- bool from_background, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
- tl_object_ptr<td_api::InputMessageContent> &&input_message_content)
- TD_WARN_UNUSED_RESULT;
+ Result<vector<MessageId>> resend_messages(DialogId dialog_id, vector<MessageId> message_ids) TD_WARN_UNUSED_RESULT;
- Result<vector<MessageId>> send_message_group(
- DialogId dialog_id, MessageId reply_to_message_id, bool disable_notification, bool from_background,
- vector<tl_object_ptr<td_api::InputMessageContent>> &&input_message_contents) TD_WARN_UNUSED_RESULT;
+ void set_dialog_message_ttl(DialogId dialog_id, int32 ttl, Promise<Unit> &&promise);
- Result<MessageId> send_bot_start_message(UserId bot_user_id, DialogId dialog_id,
- const string &parameter) TD_WARN_UNUSED_RESULT;
+ Status send_screenshot_taken_notification_message(DialogId dialog_id);
- Result<MessageId> send_inline_query_result_message(DialogId dialog_id, MessageId reply_to_message_id,
- bool disable_notification, bool from_background, int64 query_id,
- const string &result_id) TD_WARN_UNUSED_RESULT;
+ Result<MessageId> add_local_message(DialogId dialog_id, td_api::object_ptr<td_api::MessageSender> &&sender,
+ MessageId reply_to_message_id, bool disable_notification,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content)
+ TD_WARN_UNUSED_RESULT;
- Result<vector<MessageId>> forward_messages(DialogId to_dialog_id, DialogId from_dialog_id,
- vector<MessageId> message_ids, bool disable_notification,
- bool from_background, bool in_game_share,
- bool as_album) TD_WARN_UNUSED_RESULT;
+ void get_message_file_type(const string &message_file_head,
+ Promise<td_api::object_ptr<td_api::MessageFileType>> &&promise);
- Result<MessageId> send_dialog_set_ttl_message(DialogId dialog_id, int32 ttl);
+ void get_message_import_confirmation_text(DialogId dialog_id, Promise<string> &&promise);
- Status send_screenshot_taken_notification_message(DialogId dialog_id);
+ void import_messages(DialogId dialog_id, const td_api::object_ptr<td_api::InputFile> &message_file,
+ const vector<td_api::object_ptr<td_api::InputFile>> &attached_files, Promise<Unit> &&promise);
+
+ void start_import_messages(DialogId dialog_id, int64 import_id, vector<FileId> &&attached_file_ids,
+ Promise<Unit> &&promise);
void edit_message_text(FullMessageId full_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::InputMessageContent> &&input_message_content, Promise<Unit> &&promise);
void edit_message_live_location(FullMessageId full_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
- tl_object_ptr<td_api::location> &&input_location, Promise<Unit> &&promise);
+ tl_object_ptr<td_api::location> &&input_location, int32 heading,
+ int32 proximity_alert_radius, Promise<Unit> &&promise);
+
+ void edit_message_media(FullMessageId full_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content, Promise<Unit> &&promise);
void edit_message_caption(FullMessageId full_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::formattedText> &&input_caption, Promise<Unit> &&promise);
@@ -1050,7 +516,12 @@ class MessagesManager : public Actor {
void edit_inline_message_live_location(const string &inline_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
- tl_object_ptr<td_api::location> &&input_location, Promise<Unit> &&promise);
+ tl_object_ptr<td_api::location> &&input_location, int32 heading,
+ int32 proximity_alert_radius, Promise<Unit> &&promise);
+
+ void edit_inline_message_media(const string &inline_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
+ tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
+ Promise<Unit> &&promise);
void edit_inline_message_caption(const string &inline_message_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup,
tl_object_ptr<td_api::formattedText> &&input_caption, Promise<Unit> &&promise);
@@ -1058,101 +529,177 @@ class MessagesManager : public Actor {
void edit_inline_message_reply_markup(const string &inline_message_id,
tl_object_ptr<td_api::ReplyMarkup> &&reply_markup, Promise<Unit> &&promise);
- void set_game_score(FullMessageId full_message_id, bool edit_message, UserId user_id, int32 score, bool force,
- Promise<Unit> &&promise);
+ void edit_message_scheduling_state(FullMessageId full_message_id,
+ td_api::object_ptr<td_api::MessageSchedulingState> &&scheduling_state,
+ Promise<Unit> &&promise);
- void set_inline_game_score(const string &inline_message_id, bool edit_message, UserId user_id, int32 score,
- bool force, Promise<Unit> &&promise);
+ void send_dialog_action(DialogId dialog_id, MessageId top_thread_message_id, DialogAction action,
+ Promise<Unit> &&promise);
- int64 get_game_high_scores(FullMessageId full_message_id, UserId user_id, Promise<Unit> &&promise);
+ void after_set_typing_query(DialogId dialog_id, int32 generation);
- int64 get_inline_game_high_scores(const string &inline_message_id, UserId user_id, Promise<Unit> &&promise);
+ vector<DialogListId> get_dialog_lists_to_add_dialog(DialogId dialog_id);
- void on_get_game_high_scores(int64 random_id, tl_object_ptr<telegram_api::messages_highScores> &&high_scores);
+ void add_dialog_to_list(DialogId dialog_id, DialogListId dialog_list_id, Promise<Unit> &&promise);
- tl_object_ptr<td_api::gameHighScores> get_game_high_scores_object(int64 random_id);
+ void set_dialog_photo(DialogId dialog_id, const tl_object_ptr<td_api::InputChatPhoto> &input_photo,
+ Promise<Unit> &&promise);
- void send_dialog_action(DialogId dialog_id, const tl_object_ptr<td_api::ChatAction> &action, Promise<Unit> &&promise);
+ void set_dialog_title(DialogId dialog_id, const string &title, Promise<Unit> &&promise);
- void set_dialog_photo(DialogId dialog_id, const tl_object_ptr<td_api::InputFile> &photo, Promise<Unit> &&promise);
+ void set_dialog_description(DialogId dialog_id, const string &description, Promise<Unit> &&promise);
- void set_dialog_title(DialogId dialog_id, const string &title, Promise<Unit> &&promise);
+ void set_active_reactions(vector<string> active_reactions);
- void add_dialog_participant(DialogId dialog_id, UserId user_id, int32 forward_limit, Promise<Unit> &&promise);
+ void set_dialog_available_reactions(DialogId dialog_id,
+ td_api::object_ptr<td_api::ChatAvailableReactions> &&available_reactions_ptr,
+ Promise<Unit> &&promise);
- void add_dialog_participants(DialogId dialog_id, const vector<UserId> &user_ids, Promise<Unit> &&promise);
+ void set_dialog_permissions(DialogId dialog_id, const td_api::object_ptr<td_api::chatPermissions> &permissions,
+ Promise<Unit> &&promise);
- void set_dialog_participant_status(DialogId dialog_id, UserId user_id,
- const tl_object_ptr<td_api::ChatMemberStatus> &chat_member_status,
- Promise<Unit> &&promise);
+ void toggle_dialog_has_protected_content(DialogId dialog_id, bool has_protected_content, Promise<Unit> &&promise);
- DialogParticipant get_dialog_participant(DialogId dialog_id, UserId user_id, int64 &random_id, bool force,
- Promise<Unit> &&promise);
+ void set_dialog_theme(DialogId dialog_id, const string &theme_name, Promise<Unit> &&promise);
- std::pair<int32, vector<DialogParticipant>> search_dialog_participants(DialogId dialog_id, const string &query,
- int32 limit, int64 &random_id, bool force,
- Promise<Unit> &&promise);
+ void pin_dialog_message(DialogId dialog_id, MessageId message_id, bool disable_notification, bool only_for_self,
+ bool is_unpin, Promise<Unit> &&promise);
- vector<UserId> get_dialog_administrators(DialogId dialog_id, int left_tries, Promise<Unit> &&promise);
+ void unpin_all_dialog_messages(DialogId dialog_id, MessageId top_thread_message_id, Promise<Unit> &&promise);
- void export_dialog_invite_link(DialogId dialog_id, Promise<Unit> &&promise);
+ void get_dialog_info_full(DialogId dialog_id, Promise<Unit> &&promise, const char *source);
- string get_dialog_invite_link(DialogId dialog_id);
+ string get_dialog_title(DialogId dialog_id) const;
- int64 get_dialog_event_log(DialogId dialog_id, const string &query, int64 from_event_id, int32 limit,
- const tl_object_ptr<td_api::chatEventLogFilters> &filters, const vector<UserId> &user_ids,
- Promise<Unit> &&promise);
+ bool have_dialog(DialogId dialog_id) const;
+ bool have_dialog_force(DialogId dialog_id, const char *source);
- void on_get_event_log(int64 random_id, tl_object_ptr<telegram_api::channels_adminLogResults> &&events);
+ bool have_dialog_info(DialogId dialog_id) const;
+ bool have_dialog_info_force(DialogId dialog_id) const;
- tl_object_ptr<td_api::chatEvents> get_chat_events_object(int64 random_id);
+ void reload_dialog_info_full(DialogId dialog_id, const char *source);
- bool have_dialog(DialogId dialog_id) const;
- bool have_dialog_force(DialogId dialog_id);
+ void on_dialog_info_full_invalidated(DialogId dialog_id);
bool load_dialog(DialogId dialog_id, int left_tries, Promise<Unit> &&promise);
- void load_dialogs(vector<DialogId> dialog_ids, Promise<Unit> &&promise);
+ void load_dialogs(vector<DialogId> dialog_ids, Promise<vector<DialogId>> &&promise);
+
+ void load_dialog_filter(DialogFilterId dialog_filter_id, bool force, Promise<Unit> &&promise);
+
+ void get_recommended_dialog_filters(Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise);
+
+ Result<DialogDate> get_dialog_list_last_date(DialogListId dialog_list_id);
- vector<DialogId> get_dialogs(DialogDate offset, int32 limit, bool force, Promise<Unit> &&promise);
+ vector<DialogId> get_dialogs(DialogListId dialog_list_id, DialogDate offset, int32 limit, bool exact_limit,
+ bool force, Promise<Unit> &&promise);
+
+ void get_dialogs_from_list(DialogListId dialog_list_id, int32 limit,
+ Promise<td_api::object_ptr<td_api::chats>> &&promise);
vector<DialogId> search_public_dialogs(const string &query, Promise<Unit> &&promise);
- std::pair<size_t, vector<DialogId>> search_dialogs(const string &query, int32 limit, Promise<Unit> &&promise);
+ std::pair<int32, vector<DialogId>> search_dialogs(const string &query, int32 limit, Promise<Unit> &&promise);
vector<DialogId> search_dialogs_on_server(const string &query, int32 limit, Promise<Unit> &&promise);
- vector<DialogId> get_common_dialogs(UserId user_id, DialogId offset_dialog_id, int32 limit, bool force,
- Promise<Unit> &&promise);
+ void drop_common_dialogs_cache(UserId user_id);
+
+ std::pair<int32, vector<DialogId>> get_common_dialogs(UserId user_id, DialogId offset_dialog_id, int32 limit,
+ bool force, Promise<Unit> &&promise);
+
+ void block_message_sender_from_replies(MessageId message_id, bool need_delete_message, bool need_delete_all_messages,
+ bool report_spam, Promise<Unit> &&promise);
- bool have_message(FullMessageId full_message_id);
+ void get_blocked_dialogs(int32 offset, int32 limit, Promise<td_api::object_ptr<td_api::messageSenders>> &&promise);
+
+ void on_get_blocked_dialogs(int32 offset, int32 limit, int32 total_count,
+ vector<tl_object_ptr<telegram_api::peerBlocked>> &&blocked_peers,
+ Promise<td_api::object_ptr<td_api::messageSenders>> &&promise);
+
+ bool can_get_message_statistics(FullMessageId full_message_id);
+
+ DialogId get_dialog_message_sender(FullMessageId full_message_id);
+
+ bool have_message_force(FullMessageId full_message_id, const char *source);
void get_message(FullMessageId full_message_id, Promise<Unit> &&promise);
- MessageId get_replied_message(DialogId dialog_id, MessageId message_id, bool force, Promise<Unit> &&promise);
+ FullMessageId get_replied_message(DialogId dialog_id, MessageId message_id, bool force, Promise<Unit> &&promise);
- void get_dialog_pinned_message(DialogId dialog_id, Promise<MessageId> &&promise);
+ MessageId get_dialog_pinned_message(DialogId dialog_id, Promise<Unit> &&promise);
+
+ void get_callback_query_message(DialogId dialog_id, MessageId message_id, int64 callback_query_id,
+ Promise<Unit> &&promise);
bool get_messages(DialogId dialog_id, const vector<MessageId> &message_ids, Promise<Unit> &&promise);
- void get_messages_from_server(vector<FullMessageId> &&message_ids, Promise<Unit> &&promise,
+ void get_message_from_server(FullMessageId full_message_id, Promise<Unit> &&promise, const char *source,
+ tl_object_ptr<telegram_api::InputMessage> input_message = nullptr);
+
+ void get_messages_from_server(vector<FullMessageId> &&message_ids, Promise<Unit> &&promise, const char *source,
tl_object_ptr<telegram_api::InputMessage> input_message = nullptr);
+ void get_message_thread(DialogId dialog_id, MessageId message_id, Promise<MessageThreadInfo> &&promise);
+
+ td_api::object_ptr<td_api::messageThreadInfo> get_message_thread_info_object(const MessageThreadInfo &info);
+
+ void process_discussion_message(telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result,
+ DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id,
+ MessageId expected_message_id, Promise<MessageThreadInfo> promise);
+
+ void get_message_viewers(FullMessageId full_message_id, Promise<td_api::object_ptr<td_api::users>> &&promise);
+
+ void translate_text(const string &text, const string &from_language_code, const string &to_language_code,
+ Promise<td_api::object_ptr<td_api::text>> &&promise);
+
+ void recognize_speech(FullMessageId full_message_id, Promise<Unit> &&promise);
+
+ void rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise<Unit> &&promise);
+
bool is_message_edited_recently(FullMessageId full_message_id, int32 seconds);
- std::pair<string, string> get_public_message_link(FullMessageId full_message_id, bool for_group,
- Promise<Unit> &&promise);
+ bool is_deleted_secret_chat(DialogId dialog_id) const;
+
+ Result<std::pair<string, bool>> get_message_link(FullMessageId full_message_id, int32 media_timestamp, bool for_group,
+ bool in_message_thread);
+
+ string get_message_embedding_code(FullMessageId full_message_id, bool for_group, Promise<Unit> &&promise);
void on_get_public_message_link(FullMessageId full_message_id, bool for_group, string url, string html);
+ void get_message_link_info(Slice url, Promise<MessageLinkInfo> &&promise);
+
+ td_api::object_ptr<td_api::messageLinkInfo> get_message_link_info_object(const MessageLinkInfo &info) const;
+
+ void create_dialog_filter(td_api::object_ptr<td_api::chatFilter> filter,
+ Promise<td_api::object_ptr<td_api::chatFilterInfo>> &&promise);
+
+ void edit_dialog_filter(DialogFilterId dialog_filter_id, td_api::object_ptr<td_api::chatFilter> filter,
+ Promise<td_api::object_ptr<td_api::chatFilterInfo>> &&promise);
+
+ void delete_dialog_filter(DialogFilterId dialog_filter_id, Promise<Unit> &&promise);
+
+ void reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids, int32 main_dialog_list_position,
+ Promise<Unit> &&promise);
+
Status delete_dialog_reply_markup(DialogId dialog_id, MessageId message_id) TD_WARN_UNUSED_RESULT;
- Status set_dialog_draft_message(DialogId dialog_id,
+ Status set_dialog_draft_message(DialogId dialog_id, MessageId top_thread_message_id,
tl_object_ptr<td_api::draftMessage> &&draft_message) TD_WARN_UNUSED_RESULT;
- Status toggle_dialog_is_pinned(DialogId dialog_id, bool is_pinned) TD_WARN_UNUSED_RESULT;
+ void clear_all_draft_messages(bool exclude_secret_chats, Promise<Unit> &&promise);
+
+ Status toggle_dialog_is_pinned(DialogListId dialog_list_id, DialogId dialog_id, bool is_pinned) TD_WARN_UNUSED_RESULT;
+
+ Status toggle_dialog_is_marked_as_unread(DialogId dialog_id, bool is_marked_as_unread) TD_WARN_UNUSED_RESULT;
+
+ Status toggle_message_sender_is_blocked(const td_api::object_ptr<td_api::MessageSender> &sender,
+ bool is_blocked) TD_WARN_UNUSED_RESULT;
- Status set_pinned_dialogs(vector<DialogId> dialog_ids) TD_WARN_UNUSED_RESULT;
+ Status toggle_dialog_silent_send_message(DialogId dialog_id, bool silent_send_message) TD_WARN_UNUSED_RESULT;
+
+ Status set_pinned_dialogs(DialogListId dialog_list_id, vector<DialogId> dialog_ids) TD_WARN_UNUSED_RESULT;
Status set_dialog_client_data(DialogId dialog_id, string &&client_data) TD_WARN_UNUSED_RESULT;
@@ -1161,136 +708,224 @@ class MessagesManager : public Actor {
DialogId create_new_group_chat(const vector<UserId> &user_ids, const string &title, int64 &random_id,
Promise<Unit> &&promise);
- DialogId create_new_channel_chat(const string &title, bool is_megagroup, const string &description, int64 &random_id,
+ DialogId create_new_channel_chat(const string &title, bool is_megagroup, const string &description,
+ const DialogLocation &location, bool for_import, int64 &random_id,
Promise<Unit> &&promise);
void create_new_secret_chat(UserId user_id, Promise<SecretChatId> &&promise);
DialogId migrate_dialog_to_megagroup(DialogId dialog_id, Promise<Unit> &&promise);
+ bool is_dialog_opened(DialogId dialog_id) const;
+
Status open_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT;
Status close_dialog(DialogId dialog_id) TD_WARN_UNUSED_RESULT;
- Status view_messages(DialogId dialog_id, const vector<MessageId> &message_ids, bool force_read) TD_WARN_UNUSED_RESULT;
+ Status view_messages(DialogId dialog_id, MessageId top_thread_message_id, const vector<MessageId> &message_ids,
+ bool force_read) TD_WARN_UNUSED_RESULT;
- Status open_message_content(FullMessageId full_message_id) TD_WARN_UNUSED_RESULT;
+ void finish_get_message_views(DialogId dialog_id, const vector<MessageId> &message_ids);
+
+ void finish_get_message_extended_media(DialogId dialog_id, const vector<MessageId> &message_ids);
- static tl_object_ptr<td_api::NotificationSettingsScope> get_notification_settings_scope_object(
- NotificationSettingsScope scope);
+ Status open_message_content(FullMessageId full_message_id) TD_WARN_UNUSED_RESULT;
- static tl_object_ptr<td_api::notificationSettings> get_notification_settings_object(
- const NotificationSettings *notification_settings);
+ void click_animated_emoji_message(FullMessageId full_message_id,
+ Promise<td_api::object_ptr<td_api::sticker>> &&promise);
- const NotificationSettings *get_notification_settings(NotificationSettingsScope scope, Promise<Unit> &&promise);
+ vector<DialogId> get_dialog_notification_settings_exceptions(NotificationSettingsScope scope, bool filter_scope,
+ bool compare_sound, bool force, Promise<Unit> &&promise);
- Status set_notification_settings(NotificationSettingsScope scope,
- tl_object_ptr<td_api::notificationSettings> &&notification_settings)
+ Status set_dialog_notification_settings(DialogId dialog_id,
+ tl_object_ptr<td_api::chatNotificationSettings> &&notification_settings)
TD_WARN_UNUSED_RESULT;
void reset_all_notification_settings();
- tl_object_ptr<td_api::chat> get_chat_object(DialogId dialog_id);
+ tl_object_ptr<td_api::chat> get_chat_object(DialogId dialog_id) const;
+
+ static tl_object_ptr<td_api::chats> get_chats_object(int32 total_count, const vector<DialogId> &dialog_ids);
- static tl_object_ptr<td_api::chats> get_chats_object(const vector<DialogId> &dialogs);
+ static tl_object_ptr<td_api::chats> get_chats_object(const std::pair<int32, vector<DialogId>> &dialog_ids);
+
+ tl_object_ptr<td_api::chatFilter> get_chat_filter_object(DialogFilterId dialog_filter_id) const;
tl_object_ptr<td_api::messages> get_dialog_history(DialogId dialog_id, MessageId from_message_id, int32 offset,
int32 limit, int left_tries, bool only_local,
Promise<Unit> &&promise);
+ std::pair<DialogId, vector<MessageId>> get_message_thread_history(DialogId dialog_id, MessageId message_id,
+ MessageId from_message_id, int32 offset,
+ int32 limit, int64 &random_id,
+ Promise<Unit> &&promise);
+
+ td_api::object_ptr<td_api::messageCalendar> get_dialog_message_calendar(DialogId dialog_id, MessageId from_message_id,
+ MessageSearchFilter filter, int64 &random_id,
+ bool use_db, Promise<Unit> &&promise);
+
std::pair<int32, vector<MessageId>> search_dialog_messages(DialogId dialog_id, const string &query,
- UserId sender_user_id, MessageId from_message_id,
- int32 offset, int32 limit,
- const tl_object_ptr<td_api::SearchMessagesFilter> &filter,
- int64 &random_id, bool use_db, Promise<Unit> &&promise);
+ const td_api::object_ptr<td_api::MessageSender> &sender,
+ MessageId from_message_id, int32 offset, int32 limit,
+ MessageSearchFilter filter,
+ MessageId top_thread_message_id, int64 &random_id,
+ bool use_db, Promise<Unit> &&promise);
+
+ struct FoundMessages {
+ vector<FullMessageId> full_message_ids;
+ string next_offset;
+ int32 total_count = 0;
+ };
- std::pair<int64, vector<FullMessageId>> offline_search_messages(
- DialogId dialog_id, const string &query, int64 from_search_id, int32 limit,
- const tl_object_ptr<td_api::SearchMessagesFilter> &filter, int64 &random_id, Promise<> &&promise);
+ td_api::object_ptr<td_api::foundMessages> get_found_messages_object(const FoundMessages &found_messages,
+ const char *source);
- std::pair<int32, vector<FullMessageId>> search_messages(const string &query, int32 offset_date,
+ FoundMessages offline_search_messages(DialogId dialog_id, const string &query, string offset, int32 limit,
+ MessageSearchFilter filter, int64 &random_id, Promise<Unit> &&promise);
+
+ std::pair<int32, vector<FullMessageId>> search_messages(FolderId folder_id, bool ignore_folder_id,
+ const string &query, int32 offset_date,
DialogId offset_dialog_id, MessageId offset_message_id,
- int32 limit, int64 &random_id, Promise<Unit> &&promise);
+ int32 limit, MessageSearchFilter filter, int32 min_date,
+ int32 max_date, int64 &random_id, Promise<Unit> &&promise);
std::pair<int32, vector<FullMessageId>> search_call_messages(MessageId from_message_id, int32 limit, bool only_missed,
int64 &random_id, bool use_db, Promise<Unit> &&promise);
- std::pair<int32, vector<MessageId>> search_dialog_recent_location_messages(DialogId dialog_id, int32 limit,
- int64 &random_id, Promise<Unit> &&promise);
+ void search_outgoing_document_messages(const string &query, int32 limit,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise);
+
+ void search_dialog_recent_location_messages(DialogId dialog_id, int32 limit,
+ Promise<td_api::object_ptr<td_api::messages>> &&promise);
vector<FullMessageId> get_active_live_location_messages(Promise<Unit> &&promise);
int64 get_dialog_message_by_date(DialogId dialog_id, int32 date, Promise<Unit> &&promise);
void on_get_dialog_message_by_date_success(DialogId dialog_id, int32 date, int64 random_id,
- vector<tl_object_ptr<telegram_api::Message>> &&messages);
+ vector<tl_object_ptr<telegram_api::Message>> &&messages,
+ Promise<Unit> &&promise);
void on_get_dialog_message_by_date_fail(int64 random_id);
+ void get_dialog_sparse_message_positions(DialogId dialog_id, MessageSearchFilter filter, MessageId from_message_id,
+ int32 limit,
+ Promise<td_api::object_ptr<td_api::messagePositions>> &&promise);
+
+ void on_get_dialog_sparse_message_positions(
+ DialogId dialog_id, MessageSearchFilter filter,
+ telegram_api::object_ptr<telegram_api::messages_searchResultsPositions> positions,
+ Promise<td_api::object_ptr<td_api::messagePositions>> &&promise);
+
+ void get_dialog_message_count(DialogId dialog_id, MessageSearchFilter filter, bool return_local,
+ Promise<int32> &&promise);
+
+ void get_dialog_message_position(FullMessageId full_message_id, MessageSearchFilter filter,
+ MessageId top_thread_message_id, Promise<int32> &&promise);
+
+ vector<MessageId> get_dialog_scheduled_messages(DialogId dialog_id, bool force, bool ignore_result,
+ Promise<Unit> &&promise);
+
+ Result<td_api::object_ptr<td_api::availableReactions>> get_message_available_reactions(FullMessageId full_message_id,
+ int32 row_size);
+
+ void add_message_reaction(FullMessageId full_message_id, string reaction, bool is_big, bool add_to_recent,
+ Promise<Unit> &&promise);
+
+ void remove_message_reaction(FullMessageId full_message_id, string reaction, Promise<Unit> &&promise);
+
+ void get_message_public_forwards(FullMessageId full_message_id, string offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise);
+
tl_object_ptr<td_api::message> get_dialog_message_by_date_object(int64 random_id);
- tl_object_ptr<td_api::message> get_message_object(FullMessageId full_message_id);
+ td_api::object_ptr<td_api::message> get_dialog_event_log_message_object(
+ DialogId dialog_id, tl_object_ptr<telegram_api::Message> &&message, DialogId &sender_dialog_id);
+
+ tl_object_ptr<td_api::message> get_message_object(FullMessageId full_message_id, const char *source);
tl_object_ptr<td_api::messages> get_messages_object(int32 total_count, DialogId dialog_id,
- const vector<MessageId> &message_ids);
+ const vector<MessageId> &message_ids, bool skip_not_found,
+ const char *source);
+
+ tl_object_ptr<td_api::messages> get_messages_object(int32 total_count, const vector<FullMessageId> &full_message_ids,
+ bool skip_not_found, const char *source);
- tl_object_ptr<td_api::messages> get_messages_object(int32 total_count, const vector<FullMessageId> &full_message_ids);
+ void process_pts_update(tl_object_ptr<telegram_api::Update> &&update_ptr);
- void add_pending_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 pts_count,
- bool force_apply, const char *source);
+ void skip_old_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 old_pts,
+ int32 pts_count, const char *source);
void add_pending_channel_update(DialogId dialog_id, tl_object_ptr<telegram_api::Update> &&update, int32 new_pts,
- int32 pts_count, const char *source, bool is_postponed_update = false);
+ int32 pts_count, Promise<Unit> &&promise, const char *source,
+ bool is_postponed_update = false);
- bool is_update_about_username_change_received(DialogId dialog_id) const;
+ bool is_old_channel_update(DialogId dialog_id, int32 new_pts);
+
+ void on_dialog_bots_updated(DialogId dialog_id, vector<UserId> bot_user_ids, bool from_database);
void on_dialog_photo_updated(DialogId dialog_id);
void on_dialog_title_updated(DialogId dialog_id);
- void on_dialog_username_updated(DialogId dialog_id, const string &old_username, const string &new_username);
+ void on_dialog_usernames_updated(DialogId dialog_id, const Usernames &old_usernames, const Usernames &new_usernames);
+ void on_dialog_default_permissions_updated(DialogId dialog_id);
+ void on_dialog_has_protected_content_updated(DialogId dialog_id);
+
+ void on_dialog_user_is_contact_updated(DialogId dialog_id, bool is_contact);
+ void on_dialog_user_is_deleted_updated(DialogId dialog_id, bool is_deleted);
+
+ void on_dialog_linked_channel_updated(DialogId dialog_id, ChannelId old_linked_channel_id,
+ ChannelId new_linked_channel_id) const;
+
+ void drop_dialog_pending_join_requests(DialogId dialog_id);
void on_resolved_username(const string &username, DialogId dialog_id);
void drop_username(const string &username);
- static tl_object_ptr<telegram_api::MessagesFilter> get_input_messages_filter(SearchMessagesFilter filter);
+ void on_update_notification_scope_is_muted(NotificationSettingsScope scope, bool is_muted);
- static SearchMessagesFilter get_search_messages_filter(const tl_object_ptr<td_api::SearchMessagesFilter> &filter);
+ void on_update_dialog_notify_settings(DialogId dialog_id,
+ tl_object_ptr<telegram_api::peerNotifySettings> &&peer_notify_settings,
+ const char *source);
- tl_object_ptr<telegram_api::InputNotifyPeer> get_input_notify_peer(NotificationSettingsScope scope) const;
+ void on_update_dialog_available_reactions(
+ DialogId dialog_id, telegram_api::object_ptr<telegram_api::ChatReactions> &&available_reactions);
- NotificationSettingsScope get_notification_settings_scope(
- tl_object_ptr<telegram_api::NotifyPeer> &&notify_peer_ptr) const;
+ void hide_dialog_action_bar(DialogId dialog_id);
- NotificationSettingsScope get_notification_settings_scope(
- const tl_object_ptr<td_api::NotificationSettingsScope> &scope) const;
+ void remove_dialog_action_bar(DialogId dialog_id, Promise<Unit> &&promise);
- void on_update_notify_settings(NotificationSettingsScope scope,
- tl_object_ptr<telegram_api::PeerNotifySettings> &&peer_notify_settings);
+ void reget_dialog_action_bar(DialogId dialog_id, const char *source, bool is_repair = true);
- bool get_dialog_report_spam_state(DialogId dialog_id, Promise<Unit> &&promise);
+ void report_dialog(DialogId dialog_id, const vector<MessageId> &message_ids, ReportReason &&reason,
+ Promise<Unit> &&promise);
- void change_dialog_report_spam_state(DialogId dialog_id, bool is_spam_dialog, Promise<Unit> &&promise);
+ void report_dialog_photo(DialogId dialog_id, FileId file_id, ReportReason &&reason, Promise<Unit> &&promise);
- void report_dialog(DialogId dialog_id, const tl_object_ptr<td_api::ChatReportReason> &reason,
- const vector<MessageId> &message_ids, Promise<Unit> &&promise);
+ void on_get_peer_settings(DialogId dialog_id, tl_object_ptr<telegram_api::peerSettings> &&peer_settings,
+ bool ignore_privacy_exception = false);
- void on_get_peer_settings(DialogId dialog_id, tl_object_ptr<telegram_api::peerSettings> &&peer_settings);
+ void on_authorization_success();
void before_get_difference();
void after_get_difference();
- bool on_get_dialog_error(DialogId dialog_id, const Status &status, const string &source);
+ bool on_get_dialog_error(DialogId dialog_id, const Status &status, const char *source);
void on_send_message_get_quick_ack(int64 random_id);
void check_send_message_result(int64 random_id, DialogId dialog_id, const telegram_api::Updates *updates_ptr,
const char *source);
- FullMessageId on_send_message_success(int64 random_id, MessageId new_message_id, int32 date, FileId new_file_id,
- const char *source);
+ FullMessageId on_send_message_success(int64 random_id, MessageId new_message_id, int32 date, int32 ttl_period,
+ FileId new_file_id, const char *source);
void on_send_message_file_part_missing(int64 random_id, int bad_part);
+ void on_send_message_file_reference_error(int64 random_id);
+
+ void on_send_media_group_file_reference_error(DialogId dialog_id, vector<int64> random_ids);
+
void on_send_message_fail(int64 random_id, Status error);
void on_upload_message_media_success(DialogId dialog_id, MessageId message_id,
@@ -1308,26 +943,83 @@ class MessagesManager : public Actor {
void on_get_channel_difference(DialogId dialog_id, int32 request_pts, int32 request_limit,
tl_object_ptr<telegram_api::updates_ChannelDifference> &&difference_ptr);
- void force_create_dialog(DialogId dialog_id, const char *source, bool force_update_dialog_pos = false);
+ void force_create_dialog(DialogId dialog_id, const char *source, bool expect_no_access = false,
+ bool force_update_dialog_pos = false);
+
+ void on_get_dialog_query_finished(DialogId dialog_id, Status &&status);
+
+ void remove_sponsored_dialog();
- void on_get_dialog_success(DialogId dialog_id);
+ void on_get_sponsored_dialog(tl_object_ptr<telegram_api::Peer> peer, DialogSource source,
+ vector<tl_object_ptr<telegram_api::User>> users,
+ vector<tl_object_ptr<telegram_api::Chat>> chats);
- void on_get_dialog_fail(DialogId dialog_id, Status &&error);
+ FileSourceId get_message_file_source_id(FullMessageId full_message_id, bool force = false);
+
+ struct MessagePushNotificationInfo {
+ NotificationGroupId group_id;
+ NotificationGroupType group_type = NotificationGroupType::Calls;
+ DialogId settings_dialog_id;
+ };
+ Result<MessagePushNotificationInfo> get_message_push_notification_info(DialogId dialog_id, MessageId message_id,
+ int64 random_id, UserId sender_user_id,
+ DialogId sender_dialog_id, int32 date,
+ bool is_from_scheduled, bool contains_mention,
+ bool is_pinned, bool is_from_binlog);
+
+ struct MessageNotificationGroup {
+ DialogId dialog_id;
+ NotificationGroupType type = NotificationGroupType::Calls;
+ int32 total_count = 0;
+ vector<Notification> notifications;
+ };
+ MessageNotificationGroup get_message_notification_group_force(NotificationGroupId group_id);
+
+ vector<NotificationGroupKey> get_message_notification_group_keys_from_database(NotificationGroupKey from_group_key,
+ int32 limit);
+
+ void get_message_notifications_from_database(DialogId dialog_id, NotificationGroupId group_id,
+ NotificationId from_notification_id, MessageId from_message_id,
+ int32 limit, Promise<vector<Notification>> promise);
+
+ void remove_message_notification(DialogId dialog_id, NotificationGroupId group_id, NotificationId notification_id);
+
+ void remove_message_notifications_by_message_ids(DialogId dialog_id, const vector<MessageId> &message_ids);
+
+ void remove_message_notifications(DialogId dialog_id, NotificationGroupId group_id,
+ NotificationId max_notification_id, MessageId max_message_id);
+
+ void remove_scope_pinned_message_notifications(NotificationSettingsScope scope);
+
+ void on_update_scope_mention_notifications(NotificationSettingsScope scope, bool disable_mention_notifications);
+
+ void upload_dialog_photo(DialogId dialog_id, FileId file_id, bool is_animation, double main_frame_timestamp,
+ bool is_reupload, Promise<Unit> &&promise, vector<int> bad_parts = {});
void on_binlog_events(vector<BinlogEvent> &&events);
- void get_payment_form(FullMessageId full_message_id, Promise<tl_object_ptr<td_api::paymentForm>> &&promise);
+ void set_poll_answer(FullMessageId full_message_id, vector<int32> &&option_ids, Promise<Unit> &&promise);
+
+ void get_poll_voters(FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit,
+ Promise<std::pair<int32, vector<UserId>>> &&promise);
+
+ void stop_poll(FullMessageId full_message_id, td_api::object_ptr<td_api::ReplyMarkup> &&reply_markup,
+ Promise<Unit> &&promise);
+
+ Result<string> get_login_button_url(FullMessageId full_message_id, int64 button_id);
- void validate_order_info(FullMessageId full_message_id, tl_object_ptr<td_api::orderInfo> order_info, bool allow_save,
- Promise<tl_object_ptr<td_api::validatedOrderInfo>> &&promise);
+ Result<ServerMessageId> get_invoice_message_id(FullMessageId full_message_id);
+
+ Result<ServerMessageId> get_payment_successful_message_id(FullMessageId full_message_id);
+
+ bool can_set_game_score(FullMessageId full_message_id) const;
- void send_payment_form(FullMessageId full_message_id, const string &order_info_id, const string &shipping_option_id,
- const tl_object_ptr<td_api::InputCredentials> &credentials,
- Promise<tl_object_ptr<td_api::paymentResult>> &&promise);
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
- void get_payment_receipt(FullMessageId full_message_id, Promise<tl_object_ptr<td_api::paymentReceipt>> &&promise);
+ void add_message_file_to_downloads(FullMessageId full_message_id, FileId file_id, int32 priority,
+ Promise<td_api::object_ptr<td_api::file>> promise);
- ActorOwn<MultiSequenceDispatcher> sequence_dispatcher_;
+ void get_message_file_search_text(FullMessageId full_message_id, string unique_file_id, Promise<string> promise);
private:
class PendingPtsUpdate {
@@ -1335,9 +1027,10 @@ class MessagesManager : public Actor {
tl_object_ptr<telegram_api::Update> update;
int32 pts;
int32 pts_count;
+ Promise<Unit> promise;
- PendingPtsUpdate(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count)
- : update(std::move(update)), pts(pts), pts_count(pts_count) {
+ PendingPtsUpdate(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count, Promise<Unit> &&promise)
+ : update(std::move(update)), pts(pts), pts_count(pts_count), promise(std::move(promise)) {
}
};
@@ -1345,15 +1038,22 @@ class MessagesManager : public Actor {
DialogId dialog_id;
MessageId message_id;
UserId sender_user_id;
- int32 date;
+ DialogId sender_dialog_id;
+ int32 date = 0;
+ int32 ttl_period = 0;
int32 ttl = 0;
+ bool disable_web_page_preview = false;
int64 random_id = 0;
tl_object_ptr<telegram_api::messageFwdHeader> forward_header;
- MessageId reply_to_message_id;
+ MessageReplyHeader reply_header;
UserId via_bot_user_id;
- int32 views = 0;
+ int32 view_count = 0;
+ int32 forward_count = 0;
+ tl_object_ptr<telegram_api::messageReplies> reply_info;
+ tl_object_ptr<telegram_api::messageReactions> reactions;
int32 flags = 0;
int32 edit_date = 0;
+ vector<RestrictionReason> restriction_reasons;
string author_signature;
int64 media_album_id = 0;
@@ -1364,29 +1064,37 @@ class MessagesManager : public Actor {
struct MessageForwardInfo {
UserId sender_user_id;
int32 date = 0;
- DialogId dialog_id;
+ DialogId sender_dialog_id;
MessageId message_id;
string author_signature;
+ string sender_name;
DialogId from_dialog_id;
MessageId from_message_id;
+ string psa_type;
+ bool is_imported = false;
MessageForwardInfo() = default;
- MessageForwardInfo(UserId sender_user_id, int32 date, DialogId dialog_id, MessageId message_id,
- string author_signature, DialogId from_dialog_id, MessageId from_message_id)
+ MessageForwardInfo(UserId sender_user_id, int32 date, DialogId sender_dialog_id, MessageId message_id,
+ string author_signature, string sender_name, DialogId from_dialog_id, MessageId from_message_id,
+ string psa_type, bool is_imported)
: sender_user_id(sender_user_id)
, date(date)
- , dialog_id(dialog_id)
+ , sender_dialog_id(sender_dialog_id)
, message_id(message_id)
, author_signature(std::move(author_signature))
+ , sender_name(std::move(sender_name))
, from_dialog_id(from_dialog_id)
- , from_message_id(from_message_id) {
+ , from_message_id(from_message_id)
+ , psa_type(std::move(psa_type))
+ , is_imported(is_imported) {
}
bool operator==(const MessageForwardInfo &rhs) const {
- return sender_user_id == rhs.sender_user_id && date == rhs.date && dialog_id == rhs.dialog_id &&
+ return sender_user_id == rhs.sender_user_id && date == rhs.date && sender_dialog_id == rhs.sender_dialog_id &&
message_id == rhs.message_id && author_signature == rhs.author_signature &&
- from_dialog_id == rhs.from_dialog_id && from_message_id == rhs.from_message_id;
+ sender_name == rhs.sender_name && from_dialog_id == rhs.from_dialog_id &&
+ from_message_id == rhs.from_message_id && psa_type == rhs.psa_type && is_imported == rhs.is_imported;
}
bool operator!=(const MessageForwardInfo &rhs) const {
@@ -1394,21 +1102,39 @@ class MessagesManager : public Actor {
}
friend StringBuilder &operator<<(StringBuilder &string_builder, const MessageForwardInfo &forward_info) {
- return string_builder << "MessageForwardInfo[sender " << forward_info.sender_user_id << "("
- << forward_info.author_signature << "), source " << forward_info.dialog_id << ", source "
- << forward_info.message_id << ", from " << forward_info.from_dialog_id << ", from "
- << forward_info.from_message_id << " at " << forward_info.date << "]";
+ string_builder << "MessageForwardInfo[" << (forward_info.is_imported ? "imported " : "") << "sender "
+ << forward_info.sender_user_id;
+ if (!forward_info.author_signature.empty() || !forward_info.sender_name.empty()) {
+ string_builder << '(' << forward_info.author_signature << '/' << forward_info.sender_name << ')';
+ }
+ if (!forward_info.psa_type.empty()) {
+ string_builder << ", psa_type " << forward_info.psa_type;
+ }
+ if (forward_info.sender_dialog_id.is_valid()) {
+ string_builder << ", source ";
+ if (forward_info.message_id.is_valid()) {
+ string_builder << FullMessageId(forward_info.sender_dialog_id, forward_info.message_id);
+ } else {
+ string_builder << forward_info.sender_dialog_id;
+ }
+ }
+ if (forward_info.from_dialog_id.is_valid() || forward_info.from_message_id.is_valid()) {
+ string_builder << ", from " << FullMessageId(forward_info.from_dialog_id, forward_info.from_message_id);
+ }
+ return string_builder << " at " << forward_info.date << ']';
}
};
- // Do not forget to update MessagesManager::update_message when this class is changed
+ // Do not forget to update MessagesManager::update_message and all make_unique<Message> when this class is changed
struct Message {
- int32 random_y;
+ int32 random_y = 0;
MessageId message_id;
UserId sender_user_id;
+ DialogId sender_dialog_id;
int32 date = 0;
int32 edit_date = 0;
+ int32 send_date = 0;
int64 random_id = 0;
@@ -1416,33 +1142,82 @@ class MessagesManager : public Actor {
MessageId reply_to_message_id;
int64 reply_to_random_id = 0; // for send_message
+ DialogId reply_in_dialog_id;
+ MessageId top_thread_message_id;
+ MessageId linked_top_thread_message_id;
+ vector<MessageId> local_thread_message_ids;
UserId via_bot_user_id;
+ vector<RestrictionReason> restriction_reasons;
+
string author_signature;
bool is_channel_post = false;
+ bool is_topic_message = false;
bool is_outgoing = false;
- bool is_failed_to_send = false; // TODO replace with error_code
+ bool is_failed_to_send = false;
bool disable_notification = false;
bool contains_mention = false;
bool contains_unread_mention = false;
- bool had_reply_markup = false; // had non-inline reply markup?
- bool is_content_secret = false; // should be shown only while tapped
-
+ bool hide_edit_date = false;
+ bool had_reply_markup = false; // had non-inline reply markup?
+ bool had_forward_info = false;
+ bool is_content_secret = false; // must be shown only while tapped
+ bool is_mention_notification_disabled = false;
+ bool is_from_scheduled = false;
+ bool is_pinned = false;
+ bool are_media_timestamp_entities_found = false;
+ bool noforwards = false;
+
+ bool has_explicit_sender = false; // for send_message
+ bool is_copy = false; // for send_message
bool from_background = false; // for send_message
+ bool update_stickersets_order = false; // for send_message
bool disable_web_page_preview = false; // for send_message
bool clear_draft = false; // for send_message
bool in_game_share = false; // for send_message
+ bool hide_via_bot = false; // for resend_message
+ bool is_bot_start_message = false; // for resend_message
bool have_previous = false;
bool have_next = false;
bool from_database = false;
- int32 views = 0;
+ bool has_get_message_views_query = false;
+ bool need_view_counter_increment = false;
- int32 ttl = 0;
- double ttl_expires_at = 0;
+ bool has_get_extended_media_query = false;
+
+ DialogId real_forward_from_dialog_id; // for resend_message
+ MessageId real_forward_from_message_id; // for resend_message
+
+ string send_emoji; // for send_message
+
+ NotificationId notification_id;
+ NotificationId removed_notification_id;
+
+ int32 max_reply_media_timestamp = -1;
+ int32 max_own_media_timestamp = -2; // to update replied messages on the first load
+
+ int32 view_count = 0;
+ int32 forward_count = 0;
+ MessageReplyInfo reply_info;
+ unique_ptr<MessageReactions> reactions;
+ unique_ptr<DraftMessage> thread_draft_message;
+ uint32 available_reactions_generation = 0;
+ int32 interaction_info_update_date = 0;
+ uint32 history_generation = 0;
+
+ int32 legacy_layer = 0;
+
+ int32 send_error_code = 0;
+ string send_error_message;
+ double try_resend_at = 0;
+
+ int32 ttl_period = 0; // counted from message send date
+ int32 ttl = 0; // counted from message content view date
+ double ttl_expires_at = 0; // only for TTL
int64 media_album_id = 0;
@@ -1450,16 +1225,41 @@ class MessagesManager : public Actor {
unique_ptr<ReplyMarkup> reply_markup;
+ int32 edited_schedule_date = 0;
+ unique_ptr<MessageContent> edited_content;
+ unique_ptr<ReplyMarkup> edited_reply_markup;
+ uint64 edit_generation = 0;
+ Promise<Unit> edit_promise;
+
+ int32 last_edit_pts = 0;
+
+ const char *debug_source = "null";
+
unique_ptr<Message> left;
unique_ptr<Message> right;
- int32 last_access_date = 0;
+ mutable int32 last_access_date = 0;
+ mutable bool is_update_sent = false; // whether the message is known to the app
+
+ mutable uint64 send_message_log_event_id = 0;
- uint64 send_message_logevent_id = 0;
+ mutable NetQueryRef send_query_ref;
- NetQueryRef send_query_ref;
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
- DialogId debug_forward_from;
+ struct NotificationGroupInfo {
+ NotificationGroupId group_id;
+ int32 last_notification_date = 0; // date of last notification in the group
+ NotificationId last_notification_id; // identifier of last notification in the group
+ NotificationId max_removed_notification_id; // notification identifier, up to which all notifications are removed
+ MessageId max_removed_message_id; // message identifier, up to which all notifications are removed
+ bool is_changed = false; // true, if the group needs to be saved to database
+ bool try_reuse = false; // true, if the group needs to be deleted from database and tried to be reused
template <class StorerT>
void store(StorerT &storer) const;
@@ -1472,101 +1272,189 @@ class MessagesManager : public Actor {
DialogId dialog_id;
MessageId last_new_message_id; // identifier of the last known server message received from update, there should be
// no server messages after it
- MessageId last_message_id; // identifier of the message after which currently there is no any message, i.e. a
+ MessageId last_message_id; // identifier of the message after which currently there are no messages, i.e. a
// message without a gap after it, memory only
MessageId first_database_message_id; // identifier of the first message in the database, needed
- // until there is no gaps in the database
+ // until there are no gaps in the database
MessageId last_database_message_id; // identifier of the last local or server message, if last_database_message_id
- // is known and last_message_id is known then last_database_message_id <=
+ // is known and last_message_id is known, then last_database_message_id <=
// last_message_id
- std::array<MessageId, search_messages_filter_size()> first_database_message_id_by_index;
+ std::array<MessageId, message_search_filter_count()> first_database_message_id_by_index;
// use struct Count?
- std::array<int32, search_messages_filter_size()> message_count_by_index;
+ std::array<int32, message_search_filter_count()> message_count_by_index{{0}};
int32 server_unread_count = 0;
int32 local_unread_count = 0;
int32 unread_mention_count = 0;
+ int32 unread_reaction_count = 0;
+ int32 last_read_inbox_message_date = 0; // secret chats only
MessageId last_read_inbox_message_id;
MessageId last_read_outbox_message_id;
+ MessageId last_pinned_message_id;
MessageId reply_markup_message_id;
- NotificationSettings notification_settings;
+ DialogNotificationSettings notification_settings;
+ ChatReactions available_reactions;
+ uint32 available_reactions_generation = 0;
+ MessageTtl message_ttl;
unique_ptr<DraftMessage> draft_message;
- uint64 save_draft_message_logevent_id = 0;
- uint64 save_draft_message_logevent_id_generation = 0;
+ unique_ptr<DialogActionBar> action_bar;
+ LogEventIdWithGeneration save_draft_message_log_event_id;
+ LogEventIdWithGeneration save_notification_settings_log_event_id;
+ std::unordered_map<int64, LogEventIdWithGeneration, Hash<int64>> read_history_log_event_ids;
+ std::unordered_set<MessageId, MessageIdHash> updated_read_history_message_ids;
+ LogEventIdWithGeneration set_folder_id_log_event_id;
+ InputGroupCallId active_group_call_id;
+ InputGroupCallId expected_active_group_call_id;
+ DialogId default_join_group_call_as_dialog_id;
+ DialogId default_send_message_as_dialog_id;
+ string theme_name;
+ int32 pending_join_request_count = 0;
+ vector<UserId> pending_join_request_user_ids;
+ int32 have_full_history_source = 0;
+ int32 unload_dialog_delay_seed = 0;
+ int64 last_media_album_id = 0;
+ uint32 history_generation = 0;
+
+ FolderId folder_id;
+ vector<DialogListId> dialog_list_ids; // TODO replace with mask
MessageId
- last_read_all_mentions_message_id; // all mentions with a message id not greater than it are implicitly read
+ last_read_all_mentions_message_id; // all mentions with a message identifier not greater than it are implicitly read
MessageId
- max_unavailable_message_id; // maximal unavailable message id for dialogs with cleared/unavailable history
+ max_unavailable_message_id; // maximum unavailable message identifier for dialogs with cleared/unavailable history
int32 last_clear_history_date = 0;
MessageId last_clear_history_message_id;
int64 order = DEFAULT_ORDER;
- int64 pinned_order = DEFAULT_ORDER;
- int32 delete_last_message_date = 0;
MessageId deleted_last_message_id;
+ int32 delete_last_message_date = 0;
+ int32 pending_last_message_date = 0;
+ MessageId pending_last_message_id;
+ MessageId max_notification_message_id;
+ MessageId last_edited_message_id;
+ uint32 scheduled_messages_sync_generation = 0;
+ uint32 last_repair_scheduled_messages_generation = 0;
+
+ MessageId max_added_message_id;
+ MessageId being_added_message_id;
+ MessageId being_updated_last_new_message_id;
+ MessageId being_updated_last_database_message_id;
+ MessageId being_deleted_message_id;
+
+ NotificationGroupInfo message_notification_group;
+ NotificationGroupInfo mention_notification_group;
+ NotificationId new_secret_chat_notification_id; // secret chats only
+ MessageId pinned_message_notification_message_id;
bool has_contact_registered_message = false;
bool is_last_message_deleted_locally = false;
- bool know_can_report_spam = false;
- bool can_report_spam = false;
+ bool need_repair_action_bar = false;
+ bool know_action_bar = false;
+ bool has_outgoing_messages = false;
bool is_opened = false;
+ bool was_opened = false;
bool need_restore_reply_markup = true;
+ bool need_drop_default_send_message_as_dialog_id = false;
bool have_full_history = false;
bool is_empty = false;
bool is_last_read_inbox_message_id_inited = false;
bool is_last_read_outbox_message_id_inited = false;
+ bool is_last_pinned_message_id_inited = false;
+ bool is_folder_id_inited = false;
+ bool need_repair_server_unread_count = false;
+ bool need_repair_channel_server_unread_count = false;
+ bool is_marked_as_unread = false;
+ bool is_blocked = false;
+ bool is_is_blocked_inited = false;
+ bool last_sent_has_scheduled_messages = false;
+ bool has_scheduled_server_messages = false;
+ bool has_scheduled_database_messages = false;
+ bool is_has_scheduled_database_messages_checked = false;
+ bool has_loaded_scheduled_messages_from_database = false;
+ bool sent_scheduled_messages = false;
+ bool had_last_yet_unsent_message = false; // whether the dialog was stored to database without last message
+ bool has_active_group_call = false;
+ bool is_group_call_empty = false;
+ bool is_message_ttl_inited = false;
+ bool has_expected_active_group_call_id = false;
+ bool has_bots = false;
+ bool is_has_bots_inited = false;
+ bool is_theme_name_inited = false;
+ bool is_available_reactions_inited = false;
+ bool had_yet_unsent_message_id_overflow = false;
bool increment_view_counter = false;
- int32 pts = 0; // for channels only
- std::multimap<int32, PendingPtsUpdate> postponed_channel_updates; // for channels only
- int32 retry_get_difference_timeout = 1; // for channels only
- std::unordered_map<int64, MessageId> random_id_to_message_id; // for secret chats only
+ bool is_update_new_chat_sent = false;
+ bool is_update_new_chat_being_sent = false;
+ bool has_unload_timeout = false;
+ bool is_channel_difference_finished = false;
+
+ bool suffix_load_done_ = false;
+ bool suffix_load_has_query_ = false;
+
+ int32 pts = 0; // for channels only
+ int32 pending_read_channel_inbox_pts = 0; // for channels only
+ int32 pending_read_channel_inbox_server_unread_count = 0; // for channels only
+ MessageId pending_read_channel_inbox_max_message_id; // for channels only
+ std::unordered_map<int64, MessageId, Hash<int64>>
+ random_id_to_message_id; // for secret chats and yet unsent messages only
MessageId last_assigned_message_id; // identifier of the last local or yet unsent message, assigned after
// application start, used to guarantee that all assigned message identifiers
// are different
- std::unordered_map<MessageId, MessageId, MessageIdHash> yet_unsent_message_id_to_persistent_message_id;
+ FlatHashMap<MessageId, std::set<MessageId>, MessageIdHash>
+ yet_unsent_thread_message_ids; // top_thread_message_id -> yet unsent message IDs
+
+ FlatHashMap<ScheduledServerMessageId, int32, ScheduledServerMessageIdHash> scheduled_message_date;
- std::unordered_set<MessageId, MessageIdHash> deleted_message_ids;
+ FlatHashMap<MessageId, MessageId, MessageIdHash> yet_unsent_message_id_to_persistent_message_id;
- std::vector<MessageId> pending_update_new_messages;
+ FlatHashMap<int32, MessageId> last_assigned_scheduled_message_id; // date -> message_id
+
+ WaitFreeHashSet<MessageId, MessageIdHash> deleted_message_ids;
+ FlatHashSet<ScheduledServerMessageId, ScheduledServerMessageIdHash> deleted_scheduled_server_message_ids;
+
+ vector<std::pair<DialogId, MessageId>> pending_new_message_notifications;
+ vector<std::pair<DialogId, MessageId>> pending_new_mention_notifications;
+
+ FlatHashMap<NotificationId, MessageId, NotificationIdHash> notification_id_to_message_id;
string client_data;
// Load from newest to oldest message
- MessageId suffix_load_first_message_id_;
+ MessageId suffix_load_first_message_id_; // identifier of some message such all suffix messages in range
+ // [suffix_load_first_message_id_, last_message_id] are loaded
MessageId suffix_load_query_message_id_;
- std::vector<std::pair<Promise<>, std::function<bool(const Message *)>>> suffix_load_queries_;
- bool suffix_load_done_ = false;
- bool suffix_load_has_query_ = false;
- bool suffix_load_was_query_ = false;
+ std::vector<std::pair<Promise<Unit>, std::function<bool(const Message *)>>> suffix_load_queries_;
- std::unordered_set<MessageId, MessageIdHash> pending_viewed_message_ids;
+ FlatHashMap<MessageId, int64, MessageIdHash> pending_viewed_live_locations; // message_id -> task_id
+ FlatHashSet<MessageId, MessageIdHash> pending_viewed_message_ids;
- unique_ptr<Message> messages = nullptr;
+ unique_ptr<Message> messages;
+ unique_ptr<Message> scheduled_messages;
struct MessageOp {
enum : int8 { Add, SetPts, Delete, DeleteAll } type;
- bool from_update;
- bool have_previous;
- bool have_next;
- int32 content_type;
+ bool from_update = false;
+ bool have_previous = false;
+ bool have_next = false;
+ MessageContentType content_type = MessageContentType::None;
+ int32 pts = 0;
MessageId message_id;
- const char *source;
- double date;
+ const char *source = nullptr;
+ double date = 0;
- MessageOp(decltype(type) type, MessageId message_id, int32 content_type, bool from_update, bool have_previous,
- bool have_next, const char *source)
+ MessageOp(decltype(type) type, MessageId message_id, MessageContentType content_type, bool from_update,
+ bool have_previous, bool have_next, const char *source)
: type(type)
, from_update(from_update)
, have_previous(have_previous)
@@ -1576,16 +1464,26 @@ class MessagesManager : public Actor {
, source(source)
, date(G()->server_time()) {
}
+
+ MessageOp(decltype(type) type, int32 pts, const char *source)
+ : type(type), pts(pts), source(source), date(G()->server_time()) {
+ }
};
+ const char *debug_set_dialog_last_database_message_id = "Unknown"; // to be removed soon
vector<MessageOp> debug_message_op;
+ // message identifiers loaded from database
+ MessageId debug_last_new_message_id;
+ MessageId debug_first_database_message_id;
+ MessageId debug_last_database_message_id;
+
Dialog() = default;
Dialog(const Dialog &) = delete;
Dialog &operator=(const Dialog &) = delete;
Dialog(Dialog &&other) = delete;
Dialog &operator=(Dialog &&other) = delete;
- ~Dialog();
+ ~Dialog() = default;
template <class StorerT>
void store(StorerT &storer) const;
@@ -1594,6 +1492,117 @@ class MessagesManager : public Actor {
void parse(ParserT &parser);
};
+ struct RecommendedDialogFilter {
+ unique_ptr<DialogFilter> dialog_filter;
+ string description;
+ };
+
+ struct DialogList {
+ DialogListId dialog_list_id;
+ bool is_message_unread_count_inited_ = false;
+ bool is_dialog_unread_count_inited_ = false;
+ bool need_unread_count_recalc_ = true;
+ int32 unread_message_total_count_ = 0;
+ int32 unread_message_muted_count_ = 0;
+ int32 unread_dialog_total_count_ = 0;
+ int32 unread_dialog_muted_count_ = 0;
+ int32 unread_dialog_marked_count_ = 0;
+ int32 unread_dialog_muted_marked_count_ = 0;
+ int32 in_memory_dialog_total_count_ = 0;
+ int32 server_dialog_total_count_ = -1;
+ int32 secret_chat_total_count_ = -1;
+
+ vector<Promise<Unit>> load_list_queries_;
+
+ FlatHashMap<DialogId, int64, DialogIdHash> pinned_dialog_id_orders_;
+ vector<DialogDate> pinned_dialogs_;
+ bool are_pinned_dialogs_inited_ = false;
+
+ DialogDate last_pinned_dialog_date_ = MIN_DIALOG_DATE; // in memory
+
+ // date of the last loaded dialog
+ // min(folder1_last_dialog_date_, folder2_last_dialog_date, last_pinned_dialog_date_)
+ DialogDate list_last_dialog_date_ = MIN_DIALOG_DATE; // in memory
+ };
+
+ struct DialogFolder {
+ FolderId folder_id;
+ // date of the last loaded dialog in the folder
+ DialogDate folder_last_dialog_date_{MAX_ORDINARY_DIALOG_ORDER, DialogId()}; // in memory
+
+ std::set<DialogDate> ordered_dialogs_; // all known dialogs, including with default order
+
+ // date of last known user/group/channel dialog in the right order
+ DialogDate last_server_dialog_date_{MAX_ORDINARY_DIALOG_ORDER, DialogId()};
+ DialogDate last_loaded_database_dialog_date_{MAX_ORDINARY_DIALOG_ORDER, DialogId()};
+ DialogDate last_database_server_dialog_date_{MAX_ORDINARY_DIALOG_ORDER, DialogId()};
+
+ MultiPromiseActor load_folder_dialog_list_multipromise_{
+ "LoadDialogListMultiPromiseActor"}; // must be defined before pending_on_get_dialogs_
+ int32 load_dialog_list_limit_max_ = 0;
+ };
+
+ class DialogListViewIterator {
+ MessagesManager *messages_manager_;
+ const DialogListId *dialog_list_id_;
+
+ public:
+ DialogListViewIterator(MessagesManager *messages_manager, const DialogListId *dialog_list_id)
+ : messages_manager_(messages_manager), dialog_list_id_(dialog_list_id) {
+ }
+
+ DialogList &operator*() const {
+ auto dialog_list_ptr = messages_manager_->get_dialog_list(*dialog_list_id_);
+ CHECK(dialog_list_ptr != nullptr);
+ return *dialog_list_ptr;
+ }
+
+ bool operator!=(const DialogListViewIterator &other) const {
+ return dialog_list_id_ != other.dialog_list_id_;
+ }
+
+ void operator++() {
+ dialog_list_id_++;
+ }
+ };
+
+ class DialogListView {
+ MessagesManager *messages_manager_;
+ // TODO can be optimized to store only mask of dialog lists
+ vector<DialogListId> dialog_list_ids_;
+
+ public:
+ DialogListView(MessagesManager *messages_manager, vector<DialogListId> dialog_list_ids)
+ : messages_manager_(messages_manager), dialog_list_ids_(std::move(dialog_list_ids)) {
+ }
+
+ DialogListViewIterator begin() {
+ return DialogListViewIterator(messages_manager_, dialog_list_ids_.empty() ? nullptr : &dialog_list_ids_[0]);
+ }
+
+ DialogListViewIterator end() {
+ return DialogListViewIterator(
+ messages_manager_, dialog_list_ids_.empty() ? nullptr : &dialog_list_ids_[0] + dialog_list_ids_.size());
+ }
+ };
+
+ struct DialogPositionInList {
+ int64 order = DEFAULT_ORDER;
+ int64 private_order = 0;
+ int64 public_order = 0;
+ bool is_pinned = false;
+ bool is_sponsored = false;
+
+ int32 total_dialog_count = 0;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const DialogPositionInList &order) {
+ return string_builder << "order = " << order.order << ", private_order = " << order.private_order
+ << ", public_order = " << order.public_order << ", is_pinned = " << order.is_pinned
+ << ", is_sponsored = " << order.is_sponsored
+ << ", total_dialog_count = " << order.total_dialog_count;
+ }
+ };
+
class MessagesIteratorBase {
vector<const Message *> stack_;
@@ -1604,9 +1613,9 @@ class MessagesManager : public Actor {
MessagesIteratorBase(const Message *root, MessageId message_id) {
size_t last_right_pos = 0;
while (root != nullptr) {
- // LOG(DEBUG) << "root->message_id = " << root->message_id;
+ // LOG(DEBUG) << "Have root->message_id = " << root->message_id;
stack_.push_back(root);
- if (root->message_id.get() <= message_id.get()) {
+ if (root->message_id <= message_id) {
// LOG(DEBUG) << "Go right";
last_right_pos = stack_.size();
root = root->right.get();
@@ -1693,11 +1702,13 @@ class MessagesManager : public Actor {
}
};
- class MessagesIterator : public MessagesIteratorBase {
+ class MessagesIterator final : public MessagesIteratorBase {
public:
MessagesIterator() = default;
- MessagesIterator(Dialog *d, MessageId message_id) : MessagesIteratorBase(d->messages.get(), message_id) {
+ MessagesIterator(Dialog *d, MessageId message_id)
+ : MessagesIteratorBase(message_id.is_scheduled() ? d->scheduled_messages.get() : d->messages.get(),
+ message_id) {
}
Message *operator*() const {
@@ -1705,11 +1716,13 @@ class MessagesManager : public Actor {
}
};
- class MessagesConstIterator : public MessagesIteratorBase {
+ class MessagesConstIterator final : public MessagesIteratorBase {
public:
MessagesConstIterator() = default;
- MessagesConstIterator(const Dialog *d, MessageId message_id) : MessagesIteratorBase(d->messages.get(), message_id) {
+ MessagesConstIterator(const Dialog *d, MessageId message_id)
+ : MessagesIteratorBase(message_id.is_scheduled() ? d->scheduled_messages.get() : d->messages.get(),
+ message_id) {
}
const Message *operator*() const {
@@ -1718,58 +1731,96 @@ class MessagesManager : public Actor {
};
struct PendingSecretMessage {
+ enum class Type : int32 { NewMessage, DeleteMessages, DeleteHistory };
+ Type type = Type::NewMessage;
+
+ // for NewMessage
MessageInfo message_info;
- MultiPromiseActor load_data_multipromise;
- Promise<> success_promise;
- };
+ MultiPromiseActor load_data_multipromise{"LoadPendingSecretMessageDataMultiPromiseActor"};
- struct InputMessageContent {
- unique_ptr<MessageContent> content;
- bool disable_web_page_preview = false;
- bool clear_draft = false;
- int32 ttl = 0;
- UserId via_bot_user_id;
+ // for DeleteMessages/DeleteHistory
+ DialogId dialog_id;
+ vector<int64> random_ids;
+ MessageId last_message_id;
+ bool remove_from_dialog_list = false;
- InputMessageContent(unique_ptr<MessageContent> &&content, bool disable_web_page_preview, bool clear_draft,
- int32 ttl, UserId via_bot_user_id)
- : content(std::move(content))
- , disable_web_page_preview(disable_web_page_preview)
- , clear_draft(clear_draft)
- , ttl(ttl)
- , via_bot_user_id(via_bot_user_id) {
+ Promise<Unit> success_promise;
+ };
+
+ struct MessageSendOptions {
+ bool disable_notification = false;
+ bool from_background = false;
+ bool update_stickersets_order = false;
+ bool protect_content = false;
+ int32 schedule_date = 0;
+
+ MessageSendOptions() = default;
+ MessageSendOptions(bool disable_notification, bool from_background, bool update_stickersets_order,
+ bool protect_content, int32 schedule_date)
+ : disable_notification(disable_notification)
+ , from_background(from_background)
+ , update_stickersets_order(update_stickersets_order)
+ , protect_content(protect_content)
+ , schedule_date(schedule_date) {
}
};
- class DeleteAllChannelMessagesFromUserOnServerLogEvent;
- class DeleteDialogHistoryFromServerLogEvent;
+ class BlockMessageSenderFromRepliesOnServerLogEvent;
+ class DeleteAllCallMessagesOnServerLogEvent;
+ class DeleteAllChannelMessagesFromSenderOnServerLogEvent;
+ class DeleteDialogHistoryOnServerLogEvent;
+ class DeleteDialogMessagesByDateOnServerLogEvent;
class DeleteMessageLogEvent;
- class DeleteMessagesFromServerLogEvent;
+ class DeleteMessagesOnServerLogEvent;
+ class DeleteScheduledMessagesOnServerLogEvent;
+ class DeleteTopicHistoryOnServerLogEvent;
class ForwardMessagesLogEvent;
class GetChannelDifferenceLogEvent;
class ReadAllDialogMentionsOnServerLogEvent;
+ class ReadAllDialogReactionsOnServerLogEvent;
+ class ReadHistoryInSecretChatLogEvent;
class ReadHistoryOnServerLogEvent;
class ReadMessageContentsOnServerLogEvent;
+ class ReadMessageThreadHistoryOnServerLogEvent;
+ class RegetDialogLogEvent;
class ReorderPinnedDialogsOnServerLogEvent;
+ class ResetAllNotificationSettingsOnServerLogEvent;
class SaveDialogDraftMessageOnServerLogEvent;
class SendBotStartMessageLogEvent;
class SendInlineQueryResultMessageLogEvent;
class SendMessageLogEvent;
class SendScreenshotTakenNotificationMessageLogEvent;
+ class SetDialogFolderIdOnServerLogEvent;
+ class ToggleDialogIsBlockedOnServerLogEvent;
+ class ToggleDialogIsMarkedAsUnreadOnServerLogEvent;
class ToggleDialogIsPinnedOnServerLogEvent;
+ class ToggleDialogReportSpamStateOnServerLogEvent;
+ class UnpinAllDialogMessagesOnServerLogEvent;
+ class UpdateDialogNotificationSettingsOnServerLogEvent;
+
+ class DialogFiltersLogEvent;
static constexpr size_t MAX_GROUPED_MESSAGES = 10; // server side limit
static constexpr int32 MAX_GET_DIALOGS = 100; // server side limit
static constexpr int32 MAX_GET_HISTORY = 100; // server side limit
static constexpr int32 MAX_SEARCH_MESSAGES = 100; // server side limit
- static constexpr int32 MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN = 5; // server side limit
- static constexpr int32 MIN_CHANNEL_DIFFERENCE = 10;
+ static constexpr int32 MIN_SEARCH_PUBLIC_DIALOG_PREFIX_LEN = 4; // server side limit
+ static constexpr int32 MIN_CHANNEL_DIFFERENCE = 1;
static constexpr int32 MAX_CHANNEL_DIFFERENCE = 100;
- static constexpr int32 MAX_BOT_CHANNEL_DIFFERENCE = 100000; // server side limit
- static constexpr int32 MAX_RECENT_FOUND_DIALOGS = 20; // some reasonable value
- static constexpr size_t MAX_CAPTION_LENGTH = 200; // server side limit
- static constexpr size_t MAX_NAME_LENGTH = 255; // server side limit for title and description
+ static constexpr int32 MAX_BOT_CHANNEL_DIFFERENCE = 100000; // server side limit
+ static constexpr int32 MAX_RECENT_DIALOGS = 50; // some reasonable value
+ static constexpr size_t MAX_TITLE_LENGTH = 128; // server side limit for chat title
+ static constexpr size_t MAX_DESCRIPTION_LENGTH = 255; // server side limit for chat description
+ static constexpr size_t MAX_DIALOG_FILTER_TITLE_LENGTH = 12; // server side limit for dialog filter title
+ static constexpr int32 MAX_PRIVATE_MESSAGE_TTL = 60; // server side limit
+ static constexpr int32 DIALOG_FILTERS_CACHE_TIME = 86400;
+ static constexpr size_t MIN_DELETED_ASYNCHRONOUSLY_MESSAGES = 10;
+ static constexpr size_t MAX_UNLOADED_MESSAGES = 5000;
+
+ static constexpr int64 SPONSORED_DIALOG_ORDER = static_cast<int64>(2147483647) << 32;
static constexpr int32 MIN_PINNED_DIALOG_DATE = 2147000000; // some big date
- static constexpr int32 MAX_PRIVATE_MESSAGE_TTL = 60; // server side limit
+ static constexpr int64 MAX_ORDINARY_DIALOG_ORDER =
+ 9221294780217032704; // == get_dialog_order(MessageId(), MIN_PINNED_DIALOG_DATE - 1)
static constexpr int32 UPDATE_CHANNEL_TO_LONG_FLAG_HAS_PTS = 1 << 0;
@@ -1779,102 +1830,227 @@ class MessagesManager : public Actor {
static constexpr int32 DIALOG_FLAG_HAS_PTS = 1 << 0;
static constexpr int32 DIALOG_FLAG_HAS_DRAFT = 1 << 1;
static constexpr int32 DIALOG_FLAG_IS_PINNED = 1 << 2;
+ static constexpr int32 DIALOG_FLAG_HAS_FOLDER_ID = 1 << 4;
static constexpr int32 MAX_MESSAGE_VIEW_DELAY = 1; // seconds
static constexpr int32 MIN_SAVE_DRAFT_DELAY = 1; // seconds
+ static constexpr int32 MIN_READ_HISTORY_DELAY = 3; // seconds
static constexpr int32 MAX_SAVE_DIALOG_DELAY = 0; // seconds
- static constexpr int32 DIALOG_UNLOAD_DELAY = 60; // seconds
- static constexpr int32 USERNAME_CACHE_EXPIRE_TIME = 3 * 86400;
- static constexpr int32 USERNAME_CACHE_EXPIRE_TIME_SHORT = 900;
+ static constexpr int32 LIVE_LOCATION_VIEW_PERIOD = 60; // seconds, server-side limit
+ static constexpr int32 UPDATE_VIEWED_MESSAGES_PERIOD = 15; // seconds
- static constexpr int32 MIN_LIVE_LOCATION_PERIOD = 60; // seconds, server side limit
- static constexpr int32 MAX_LIVE_LOCATION_PERIOD = 86400; // seconds, server side limit
+ static constexpr int32 USERNAME_CACHE_EXPIRE_TIME = 86400;
+ static constexpr int32 AUTH_NOTIFICATION_ID_CACHE_TIME = 7 * 86400;
+ static constexpr size_t MAX_SAVED_AUTH_NOTIFICATION_IDS = 100;
- static constexpr int32 MAX_PRELOADED_DIALOGS = 1000;
+ static constexpr int32 ONLINE_MEMBER_COUNT_UPDATE_TIME = 5 * 60;
+
+ static constexpr int32 MAX_RESEND_DELAY = 86400; // seconds, some resonable limit
+
+ static constexpr int32 SCHEDULE_WHEN_ONLINE_DATE = 2147483646;
static constexpr double DIALOG_ACTION_TIMEOUT = 5.5;
static constexpr const char *DELETE_MESSAGE_USER_REQUEST_SOURCE = "user request";
- static constexpr bool DROP_UPDATES = false;
+ static constexpr bool DROP_SEND_MESSAGE_UPDATES = false;
+
+ static int32 get_message_date(const tl_object_ptr<telegram_api::Message> &message_ptr);
+
+ static bool is_dialog_inited(const Dialog *d);
+
+ int32 get_dialog_mute_until(const Dialog *d) const;
+
+ bool is_dialog_muted(const Dialog *d) const;
+
+ bool is_dialog_pinned_message_notifications_disabled(const Dialog *d) const;
+
+ bool is_dialog_mention_notifications_disabled(const Dialog *d) const;
+
+ bool is_dialog_pinned(DialogListId dialog_list_id, DialogId dialog_id) const;
+
+ int64 get_dialog_pinned_order(DialogListId dialog_list_id, DialogId dialog_id) const;
+
+ static int64 get_dialog_pinned_order(const DialogList *list, DialogId dialog_id);
void open_dialog(Dialog *d);
void close_dialog(Dialog *d);
- void add_secret_message(unique_ptr<PendingSecretMessage> pending_secret_message, Promise<Unit> lock_promise = Auto());
+ DialogId get_my_dialog_id() const;
+
+ void on_resolve_secret_chat_message_via_bot_username(const string &via_bot_username, MessageInfo *message_info_ptr,
+ Promise<Unit> &&promise);
+
+ void add_secret_message(unique_ptr<PendingSecretMessage> pending_secret_message, Promise<Unit> lock_promise = {});
+
+ void on_add_secret_message_ready(int64 token);
void finish_add_secret_message(unique_ptr<PendingSecretMessage> pending_secret_message);
- void fix_message_info_dialog_id(MessageInfo &message_info) const;
+ void finish_delete_secret_messages(DialogId dialog_id, std::vector<int64> random_ids, Promise<Unit> promise);
+
+ void finish_delete_secret_chat_history(DialogId dialog_id, bool remove_from_dialog_list, MessageId last_message_id,
+ Promise<Unit> promise);
- MessageInfo parse_telegram_api_message(tl_object_ptr<telegram_api::Message> message_ptr, const char *source) const;
+ MessageInfo parse_telegram_api_message(tl_object_ptr<telegram_api::Message> message_ptr, bool is_scheduled,
+ const char *source) const;
std::pair<DialogId, unique_ptr<Message>> create_message(MessageInfo &&message_info, bool is_channel_message);
+ MessageId find_old_message_id(DialogId dialog_id, MessageId message_id) const;
+
+ void delete_update_message_id(DialogId dialog_id, MessageId message_id);
+
+ void get_dialog_message_count_from_server(DialogId dialog_id, MessageSearchFilter filter, Promise<int32> &&promise);
+
FullMessageId on_get_message(MessageInfo &&message_info, bool from_update, bool is_channel_message,
bool have_previous, bool have_next, const char *source);
Result<InputMessageContent> process_input_message_content(
- DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content) const;
+ DialogId dialog_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content);
+
+ Result<MessageCopyOptions> process_message_copy_options(DialogId dialog_id,
+ tl_object_ptr<td_api::messageCopyOptions> &&options) const;
- Message *get_message_to_send(Dialog *d, MessageId reply_to_message_id, bool disable_notification,
- bool from_background, unique_ptr<MessageContent> &&content, bool *need_update_dialog_pos,
- unique_ptr<MessageForwardInfo> forward_info = nullptr);
+ Result<MessageSendOptions> process_message_send_options(DialogId dialog_id,
+ tl_object_ptr<td_api::messageSendOptions> &&options,
+ bool allow_update_stickersets_order) const;
+
+ static Status can_use_message_send_options(const MessageSendOptions &options,
+ const unique_ptr<MessageContent> &content, int32 ttl);
+ static Status can_use_message_send_options(const MessageSendOptions &options, const InputMessageContent &content);
+
+ Status can_use_top_thread_message_id(Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id);
+
+ bool is_anonymous_administrator(DialogId dialog_id, string *author_signature) const;
+
+ int64 generate_new_random_id(const Dialog *d);
+
+ unique_ptr<Message> create_message_to_send(Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id,
+ const MessageSendOptions &options, unique_ptr<MessageContent> &&content,
+ bool suppress_reply_info, unique_ptr<MessageForwardInfo> forward_info,
+ bool is_copy, DialogId send_as_dialog_id) const;
+
+ Message *get_message_to_send(Dialog *d, MessageId top_thread_message_id, MessageId reply_to_message_id,
+ const MessageSendOptions &options, unique_ptr<MessageContent> &&content,
+ bool *need_update_dialog_pos, bool suppress_reply_info = false,
+ unique_ptr<MessageForwardInfo> forward_info = nullptr, bool is_copy = false,
+ DialogId sender_dialog_id = DialogId());
int64 begin_send_message(DialogId dialog_id, const Message *m);
Status can_send_message(DialogId dialog_id) const TD_WARN_UNUSED_RESULT;
- Status can_send_message_content(DialogId dialog_id, const MessageContent *content,
- bool is_forward) const TD_WARN_UNUSED_RESULT;
+ bool can_resend_message(const Message *m) const;
bool can_edit_message(DialogId dialog_id, const Message *m, bool is_editing, bool only_reply_markup = false) const;
+ bool has_qts_messages(DialogId dialog_id) const;
+
bool can_report_dialog(DialogId dialog_id) const;
- MessageId get_persistent_message_id(const Dialog *d, MessageId message_id) const;
+ Status can_pin_messages(DialogId dialog_id) const;
- static MessageId get_replied_message_id(const Message *m);
+ static Status can_get_media_timestamp_link(DialogId dialog_id, const Message *m);
- MessageId get_reply_to_message_id(Dialog *d, MessageId message_id);
+ bool can_report_message_reactions(DialogId dialog_id, const Message *m) const;
- bool can_set_game_score(DialogId dialog_id, const Message *m) const;
+ Status can_get_message_viewers(FullMessageId full_message_id) TD_WARN_UNUSED_RESULT;
- bool check_update_dialog_id(const tl_object_ptr<telegram_api::Update> &update, DialogId dialog_id);
+ Status can_get_message_viewers(DialogId dialog_id, const Message *m) const TD_WARN_UNUSED_RESULT;
- void process_update(tl_object_ptr<telegram_api::Update> &&update);
+ void cancel_edit_message_media(DialogId dialog_id, Message *m, Slice error_message);
- void process_channel_update(tl_object_ptr<telegram_api::Update> &&update);
+ void on_message_media_edited(DialogId dialog_id, MessageId message_id, FileId file_id, FileId thumbnail_file_id,
+ bool was_uploaded, bool was_thumbnail_uploaded, string file_reference,
+ int32 schedule_date, uint64 generation, Result<int32> &&result);
- void delete_messages_from_updates(const vector<MessageId> &message_ids);
+ static MessageId get_persistent_message_id(const Dialog *d, MessageId message_id);
- void delete_dialog_messages_from_updates(DialogId dialog_id, const vector<MessageId> &message_ids);
+ static FullMessageId get_replied_message_id(DialogId dialog_id, const Message *m);
- void do_forward_messages(DialogId to_dialog_id, DialogId from_dialog_id, const vector<Message *> &messages,
- const vector<MessageId> &message_ids, int64 logevent_id);
+ MessageId get_reply_to_message_id(Dialog *d, MessageId top_thread_message_id, MessageId message_id, bool for_draft);
+
+ void fix_server_reply_to_message_id(DialogId dialog_id, MessageId message_id, DialogId reply_in_dialog_id,
+ MessageId &reply_to_message_id) const;
+
+ bool can_set_game_score(DialogId dialog_id, const Message *m) const;
+
+ void add_postponed_channel_update(DialogId dialog_id, tl_object_ptr<telegram_api::Update> &&update, int32 new_pts,
+ int32 pts_count, Promise<Unit> &&promise);
+
+ bool process_channel_update(tl_object_ptr<telegram_api::Update> &&update_ptr);
+
+ void on_message_edited(FullMessageId full_message_id, int32 pts, bool had_message);
- Result<MessageId> forward_message(DialogId to_dialog_id, DialogId from_dialog_id, MessageId message_id,
- bool disable_notification, bool from_background,
- bool in_game_share) TD_WARN_UNUSED_RESULT;
+ void delete_messages_from_updates(const vector<MessageId> &message_ids);
- SecretInputMedia get_secret_input_media(const MessageContent *content,
- tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- BufferSlice thumbnail, int32 layer);
+ void delete_dialog_messages(DialogId dialog_id, const vector<MessageId> &message_ids,
+ bool force_update_for_not_found_messages, const char *source);
- tl_object_ptr<telegram_api::invoice> get_input_invoice(const Invoice &invoice) const;
+ void delete_dialog_messages(Dialog *d, const vector<MessageId> &message_ids, bool force_update_for_not_found_messages,
+ const char *source);
- tl_object_ptr<telegram_api::inputWebDocument> get_input_web_document(const Photo &photo) const;
+ void update_dialog_pinned_messages_from_updates(DialogId dialog_id, const vector<MessageId> &message_ids,
+ bool is_pin);
- tl_object_ptr<telegram_api::inputMediaInvoice> get_input_media_invoice(const MessageInvoice *message_invoice) const;
+ bool update_message_is_pinned(Dialog *d, Message *m, bool is_pin, const char *source);
- tl_object_ptr<telegram_api::InputMedia> get_input_media(const MessageContent *content,
- tl_object_ptr<telegram_api::InputFile> input_file,
- tl_object_ptr<telegram_api::InputFile> input_thumbnail,
- int32 ttl);
+ void do_forward_messages(DialogId to_dialog_id, DialogId from_dialog_id, const vector<Message *> &messages,
+ const vector<MessageId> &message_ids, bool drop_author, bool drop_media_captions,
+ uint64 log_event_id);
+
+ void send_forward_message_query(int32 flags, DialogId to_dialog_id, MessageId top_thread_message_id,
+ DialogId from_dialog_id, tl_object_ptr<telegram_api::InputPeer> as_input_peer,
+ vector<MessageId> message_ids, vector<int64> random_ids, int32 schedule_date,
+ Promise<Unit> promise);
+
+ Result<td_api::object_ptr<td_api::message>> forward_message(DialogId to_dialog_id, MessageId top_thread_message_id,
+ DialogId from_dialog_id, MessageId message_id,
+ tl_object_ptr<td_api::messageSendOptions> &&options,
+ bool in_game_share,
+ MessageCopyOptions &&copy_options) TD_WARN_UNUSED_RESULT;
+
+ unique_ptr<MessageForwardInfo> create_message_forward_info(DialogId from_dialog_id, DialogId to_dialog_id,
+ const Message *forwarded_message) const;
+
+ void fix_forwarded_message(Message *m, DialogId to_dialog_id, const Message *forwarded_message, int64 media_album_id,
+ bool drop_author) const;
+
+ struct ForwardedMessages {
+ struct CopiedMessage {
+ unique_ptr<MessageContent> content;
+ MessageId reply_to_message_id;
+ MessageId original_message_id;
+ MessageId original_reply_to_message_id;
+ unique_ptr<ReplyMarkup> reply_markup;
+ int64 media_album_id;
+ bool disable_web_page_preview;
+ size_t index;
+ };
+ vector<CopiedMessage> copied_messages;
- void delete_message_content_thumbnail(MessageContent *content);
+ struct ForwardedMessageContent {
+ unique_ptr<MessageContent> content;
+ int64 media_album_id;
+ size_t index;
+ };
+ vector<ForwardedMessageContent> forwarded_message_contents;
+ bool drop_author = false;
+ bool drop_media_captions = false;
+
+ Dialog *from_dialog;
+ MessageId top_thread_message_id;
+ Dialog *to_dialog;
+ MessageSendOptions message_send_options;
+ };
+
+ Result<ForwardedMessages> get_forwarded_messages(DialogId to_dialog_id, MessageId top_thread_message_id,
+ DialogId from_dialog_id, const vector<MessageId> &message_ids,
+ tl_object_ptr<td_api::messageSendOptions> &&options,
+ bool in_game_share, vector<MessageCopyOptions> &&copy_options);
void do_send_media(DialogId dialog_id, Message *m, FileId file_id, FileId thumbnail_file_id,
tl_object_ptr<telegram_api::InputFile> input_file,
@@ -1884,59 +2060,74 @@ class MessagesManager : public Actor {
tl_object_ptr<telegram_api::InputEncryptedFile> input_encrypted_file,
BufferSlice thumbnail);
- void do_send_message(DialogId dialog_id, Message *m, vector<int> bad_parts = {});
+ void do_send_message(DialogId dialog_id, const Message *m, vector<int> bad_parts = {});
- void on_message_media_uploaded(DialogId dialog_id, Message *m, tl_object_ptr<telegram_api::InputMedia> &&input_media,
- FileId file_id, FileId thumbnail_file_id);
+ void on_message_media_uploaded(DialogId dialog_id, const Message *m,
+ tl_object_ptr<telegram_api::InputMedia> &&input_media, FileId file_id,
+ FileId thumbnail_file_id);
- void on_secret_message_media_uploaded(DialogId dialog_id, Message *m, SecretInputMedia &&secret_input_media,
+ void on_secret_message_media_uploaded(DialogId dialog_id, const Message *m, SecretInputMedia &&secret_input_media,
FileId file_id, FileId thumbnail_file_id);
void on_upload_message_media_finished(int64 media_album_id, DialogId dialog_id, MessageId message_id, Status result);
void do_send_message_group(int64 media_album_id);
+ void on_text_message_ready_to_send(DialogId dialog_id, MessageId message_id);
+
void on_media_message_ready_to_send(DialogId dialog_id, MessageId message_id, Promise<Message *> &&promise);
+ void send_secret_message(DialogId dialog_id, const Message *m, SecretInputMedia media);
+
void on_yet_unsent_media_queue_updated(DialogId dialog_id);
- void save_send_bot_start_message_logevent(UserId bot_user_id, DialogId dialog_id, const string &parameter,
- Message *m);
+ static void save_send_bot_start_message_log_event(UserId bot_user_id, DialogId dialog_id, const string &parameter,
+ const Message *m);
- void do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, const string &parameter, Message *m);
+ void do_send_bot_start_message(UserId bot_user_id, DialogId dialog_id, MessageId message_id, const string &parameter);
- void save_send_inline_query_result_message_logevent(DialogId dialog_id, Message *m, int64 query_id,
- const string &result_id);
+ static void save_send_inline_query_result_message_log_event(DialogId dialog_id, const Message *m, int64 query_id,
+ const string &result_id);
- void do_send_inline_query_result_message(DialogId dialog_id, Message *m, int64 query_id, const string &result_id);
+ void do_send_inline_query_result_message(DialogId dialog_id, MessageId message_id, int64 query_id,
+ const string &result_id);
- uint64 save_send_screenshot_taken_notification_message_logevent(DialogId dialog_id, const Message *m);
+ static uint64 save_send_screenshot_taken_notification_message_log_event(DialogId dialog_id, const Message *m);
- void do_send_screenshot_taken_notification_message(DialogId dialog_id, const Message *m, uint64 logevent_id);
+ void do_send_screenshot_taken_notification_message(DialogId dialog_id, const Message *m, uint64 log_event_id);
- Message *continue_send_message(DialogId dialog_id, unique_ptr<Message> &&m, uint64 logevent_id);
+ void restore_message_reply_to_message_id(Dialog *d, Message *m);
- tl_object_ptr<telegram_api::InputChatPhoto> get_input_chat_photo(FileId file_id) const;
+ Message *continue_send_message(DialogId dialog_id, unique_ptr<Message> &&m, uint64 log_event_id);
bool is_message_unload_enabled() const;
- static bool is_allowed_media_group_content(int32 content_type);
+ int64 generate_new_media_album_id();
static bool can_forward_message(DialogId from_dialog_id, const Message *m);
- static bool is_secret_message_content(int32 ttl, int32 content_type);
+ bool can_save_message(DialogId dialog_id, const Message *m) const;
- static bool is_service_message_content(int32 content_type);
+ bool can_get_message_statistics(DialogId dialog_id, const Message *m) const;
- static bool can_have_message_content_caption(int32 content_type);
+ struct CanDeleteDialog {
+ bool for_self_;
+ bool for_all_users_;
- static bool can_delete_channel_message(DialogParticipantStatus status, const Message *m, bool is_bot);
+ CanDeleteDialog(bool for_self, bool for_all_users) : for_self_(for_self), for_all_users_(for_all_users) {
+ }
+ };
+ CanDeleteDialog can_delete_dialog(const Dialog *d) const;
+
+ static bool can_delete_channel_message(const DialogParticipantStatus &status, const Message *m, bool is_bot);
+
+ bool can_delete_message(DialogId dialog_id, const Message *m) const;
bool can_revoke_message(DialogId dialog_id, const Message *m) const;
bool can_unload_message(const Dialog *d, const Message *m) const;
- void unload_message(Dialog *d, MessageId message_id);
+ unique_ptr<Message> unload_message(Dialog *d, MessageId message_id);
unique_ptr<Message> delete_message(Dialog *d, MessageId message_id, bool is_permanently_deleted,
bool *need_update_dialog_pos, const char *source);
@@ -1944,39 +2135,116 @@ class MessagesManager : public Actor {
unique_ptr<Message> do_delete_message(Dialog *d, MessageId message_id, bool is_permanently_deleted,
bool only_from_memory, bool *need_update_dialog_pos, const char *source);
+ unique_ptr<Message> do_delete_scheduled_message(Dialog *d, MessageId message_id, bool is_permanently_deleted,
+ const char *source);
+
+ void on_message_deleted(Dialog *d, Message *m, bool is_permanently_deleted, const char *source);
+
+ static bool is_deleted_message(const Dialog *d, MessageId message_id);
+
+ int32 get_unload_dialog_delay() const;
+
+ double get_next_unload_dialog_delay(Dialog *d) const;
+
void unload_dialog(DialogId dialog_id);
- void delete_all_dialog_messages(Dialog *d, bool remove_from_dialog_list, bool is_permanent);
+ void delete_all_dialog_messages(Dialog *d, bool remove_from_dialog_list, bool is_permanently_deleted);
- void do_delete_all_dialog_messages(Dialog *d, unique_ptr<Message> &m, vector<int64> &deleted_message_ids);
+ void do_delete_all_dialog_messages(Dialog *d, unique_ptr<Message> &message, bool is_permanently_deleted,
+ vector<int64> &deleted_message_ids);
- void delete_messages_from_server(DialogId dialog_id, vector<MessageId> message_ids, bool revoke, uint64 logevent_id,
- Promise<Unit> &&promise);
+ void erase_delete_messages_log_event(uint64 log_event_id);
- void delete_dialog_history_from_server(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list,
- bool allow_error, uint64 logevent_id, Promise<Unit> &&promise);
+ void delete_sent_message_on_server(DialogId dialog_id, MessageId message_id, MessageId old_message_id);
- void delete_all_channel_messages_from_user_on_server(ChannelId channel_id, UserId user_id, uint64 logevent_id,
- Promise<Unit> &&promise);
+ void delete_messages_on_server(DialogId dialog_id, vector<MessageId> message_ids, bool revoke, uint64 log_event_id,
+ Promise<Unit> &&promise);
+
+ void delete_scheduled_messages_on_server(DialogId dialog_id, vector<MessageId> message_ids, uint64 log_event_id,
+ Promise<Unit> &&promise);
+
+ void delete_dialog_history_on_server(DialogId dialog_id, MessageId max_message_id, bool remove_from_dialog_list,
+ bool revoke, bool allow_error, uint64 log_event_id, Promise<Unit> &&promise);
+
+ void delete_topic_history_on_server(DialogId dialog_id, MessageId top_thread_message_id, uint64 log_event_id,
+ Promise<Unit> &&promise);
+
+ void delete_all_call_messages_on_server(bool revoke, uint64 log_event_id, Promise<Unit> &&promise);
+
+ void block_message_sender_from_replies_on_server(MessageId message_id, bool need_delete_message,
+ bool need_delete_all_messages, bool report_spam, uint64 log_event_id,
+ Promise<Unit> &&promise);
+
+ void delete_all_channel_messages_by_sender_on_server(ChannelId channel_id, DialogId sender_dialog_id,
+ uint64 log_event_id, Promise<Unit> &&promise);
+
+ void delete_dialog_messages_by_date_on_server(DialogId dialog_id, int32 min_date, int32 max_date, bool revoke,
+ uint64 log_event_id, Promise<Unit> &&promise);
+
+ void read_all_dialog_mentions_on_server(DialogId dialog_id, uint64 log_event_id, Promise<Unit> &&promise);
+
+ void read_all_dialog_reactions_on_server(DialogId dialog_id, uint64 log_event_id, Promise<Unit> &&promise);
+
+ void unpin_all_dialog_messages_on_server(DialogId dialog_id, uint64 log_event_id, Promise<Unit> &&promise);
+
+ using AffectedHistoryQuery = std::function<void(DialogId, Promise<AffectedHistory>)>;
- void read_all_dialog_mentions_on_server(DialogId dialog_id, uint64 logevent_id, Promise<Unit> &&promise);
+ void run_affected_history_query_until_complete(DialogId dialog_id, AffectedHistoryQuery query,
+ bool get_affected_messages, Promise<Unit> &&promise);
- static MessageId find_message_by_date(const unique_ptr<Message> &m, int32 date);
+ void on_get_affected_history(DialogId dialog_id, AffectedHistoryQuery query, bool get_affected_messages,
+ AffectedHistory affected_history, Promise<Unit> &&promise);
- static void find_messages_from_user(const unique_ptr<Message> &m, UserId user_id, vector<MessageId> &message_ids);
+ static MessageId find_message_by_date(const Message *m, int32 date);
- static void find_unread_mentions(const unique_ptr<Message> &m, vector<MessageId> &message_ids);
+ static void find_messages_by_date(const Message *m, int32 min_date, int32 max_date, vector<MessageId> &message_ids);
- static void find_old_messages(const unique_ptr<Message> &m, MessageId max_message_id, vector<MessageId> &message_ids);
+ static void find_messages(const Message *m, vector<MessageId> &message_ids,
+ const std::function<bool(const Message *)> &condition);
- void find_unloadable_messages(const Dialog *d, int32 unload_before_date, const unique_ptr<Message> &m,
- vector<MessageId> &message_ids, int32 &left_to_unload) const;
+ static void find_old_messages(const Message *m, MessageId max_message_id, vector<MessageId> &message_ids);
- bool update_message_views(DialogId dialog_id, Message *m, int32 views);
+ static void find_newer_messages(const Message *m, MessageId min_message_id, vector<MessageId> &message_ids);
+
+ void find_unloadable_messages(const Dialog *d, int32 unload_before_date, const Message *m,
+ vector<MessageId> &message_ids, bool &has_left_to_unload_messages) const;
+
+ void on_pending_message_views_timeout(DialogId dialog_id);
+
+ void update_message_interaction_info(FullMessageId full_message_id, int32 view_count, int32 forward_count,
+ bool has_reply_info, tl_object_ptr<telegram_api::messageReplies> &&reply_info,
+ bool has_reactions, unique_ptr<MessageReactions> &&reactions);
+
+ bool is_thread_message(DialogId dialog_id, const Message *m) const;
+
+ bool is_thread_message(DialogId dialog_id, MessageId message_id, const MessageReplyInfo &reply_info,
+ MessageContentType content_type) const;
+
+ bool is_active_message_reply_info(DialogId dialog_id, const MessageReplyInfo &reply_info) const;
+
+ bool is_visible_message_reply_info(DialogId dialog_id, const Message *m) const;
+
+ bool is_visible_message_reactions(DialogId dialog_id, const Message *m) const;
+
+ bool has_unread_message_reactions(DialogId dialog_id, const Message *m) const;
+
+ void on_message_reply_info_changed(DialogId dialog_id, const Message *m) const;
+
+ Result<FullMessageId> get_top_thread_full_message_id(DialogId dialog_id, const Message *m) const;
+
+ td_api::object_ptr<td_api::messageInteractionInfo> get_message_interaction_info_object(DialogId dialog_id,
+ const Message *m) const;
+
+ vector<td_api::object_ptr<td_api::unreadReaction>> get_unread_reactions_object(DialogId dialog_id,
+ const Message *m) const;
+
+ bool update_message_interaction_info(Dialog *d, Message *m, int32 view_count, int32 forward_count,
+ bool has_reply_info, MessageReplyInfo &&reply_info, bool has_reactions,
+ unique_ptr<MessageReactions> &&reactions, const char *source);
bool update_message_contains_unread_mention(Dialog *d, Message *m, bool contains_unread_mention, const char *source);
- static bool update_opened_message_content(Message *m);
+ bool remove_message_unread_reactions(Dialog *d, Message *m, const char *source);
void read_message_content_from_updates(MessageId message_id);
@@ -1984,62 +2252,135 @@ class MessagesManager : public Actor {
bool read_message_content(Dialog *d, Message *m, bool is_local_read, const char *source);
- void read_message_contents_on_server(DialogId dialog_id, vector<MessageId> message_ids, uint64 logevent_id);
+ void read_message_contents_on_server(DialogId dialog_id, vector<MessageId> message_ids, uint64 log_event_id,
+ Promise<Unit> &&promise, bool skip_log_event = false);
- void read_history_inbox(DialogId dialog_id, MessageId max_message_id, int32 unread_count, const char *source);
+ bool has_incoming_notification(DialogId dialog_id, const Message *m) const;
+
+ int32 calc_new_unread_count_from_last_unread(Dialog *d, MessageId max_message_id, MessageType type) const;
+
+ int32 calc_new_unread_count_from_the_end(Dialog *d, MessageId max_message_id, MessageType type,
+ int32 hint_unread_count) const;
+
+ int32 calc_new_unread_count(Dialog *d, MessageId max_message_id, MessageType type, int32 hint_unread_count) const;
+
+ void repair_server_unread_count(DialogId dialog_id, int32 unread_count, const char *source);
+
+ void repair_channel_server_unread_count(Dialog *d);
void read_history_outbox(DialogId dialog_id, MessageId max_message_id, int32 read_date = -1);
- void read_history_on_server(DialogId dialog_id, MessageId max_message_id, bool allow_error, uint64 logevent_id);
+ void read_history_on_server(Dialog *d, MessageId max_message_id);
+
+ void do_read_history_on_server(DialogId dialog_id);
+
+ void read_history_on_server_impl(Dialog *d, MessageId max_message_id);
+
+ void read_message_thread_history_on_server_impl(Dialog *d, MessageId top_thread_message_id, MessageId max_message_id);
+
+ void on_read_history_finished(DialogId dialog_id, MessageId top_thread_message_id, uint64 generation);
+
+ void read_message_thread_history_on_server(Dialog *d, MessageId top_thread_message_id, MessageId max_message_id,
+ MessageId last_message_id);
void read_secret_chat_outbox_inner(DialogId dialog_id, int32 up_to_date, int32 read_date);
void set_dialog_max_unavailable_message_id(DialogId dialog_id, MessageId max_unavailable_message_id, bool from_update,
const char *source);
+ void set_dialog_online_member_count(DialogId dialog_id, int32 online_member_count, bool is_from_server,
+ const char *source);
+
+ void on_update_dialog_online_member_count_timeout(DialogId dialog_id);
+
+ void on_update_viewed_messages_timeout(DialogId dialog_id);
+
+ bool delete_newer_server_messages_at_the_end(Dialog *d, MessageId max_message_id);
+
+ template <class T, class It>
+ vector<MessageId> get_message_history_slice(const T &begin, It it, const T &end, MessageId from_message_id,
+ int32 offset, int32 limit);
+
void preload_newer_messages(const Dialog *d, MessageId max_message_id);
void preload_older_messages(const Dialog *d, MessageId min_message_id);
- void on_get_history_from_database(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit,
- bool from_the_end, bool only_local, vector<BufferSlice> &&messages,
+ void on_get_history_from_database(DialogId dialog_id, MessageId from_message_id,
+ MessageId old_last_database_message_id, int32 offset, int32 limit,
+ bool from_the_end, bool only_local, vector<MessageDbDialogMessage> &&messages,
Promise<Unit> &&promise);
void get_history_from_the_end(DialogId dialog_id, bool from_database, bool only_local, Promise<Unit> &&promise);
+ void get_history_from_the_end_impl(const Dialog *d, bool from_database, bool only_local, Promise<Unit> &&promise,
+ const char *source);
+
void get_history(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, bool from_database,
bool only_local, Promise<Unit> &&promise);
+ void get_history_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit, bool from_database,
+ bool only_local, Promise<Unit> &&promise);
+
void load_messages(DialogId dialog_id, MessageId from_message_id, int32 offset, int32 limit, int left_tries,
bool only_local, Promise<Unit> &&promise);
+ void load_messages_impl(const Dialog *d, MessageId from_message_id, int32 offset, int32 limit, int left_tries,
+ bool only_local, Promise<Unit> &&promise);
+
+ void load_dialog_scheduled_messages(DialogId dialog_id, bool from_database, int64 hash, Promise<Unit> &&promise);
+
+ void on_get_scheduled_messages_from_database(DialogId dialog_id, vector<MessageDbDialogMessage> &&messages);
+
static int32 get_random_y(MessageId message_id);
- bool is_allowed_useless_update(const tl_object_ptr<telegram_api::Update> &update) const;
+ static void set_message_id(unique_ptr<Message> &message, MessageId message_id);
+
+ static bool is_allowed_useless_update(const tl_object_ptr<telegram_api::Update> &update);
- bool is_message_auto_read(DialogId dialog_id, bool is_outgoing, bool only_content) const;
+ bool is_message_auto_read(DialogId dialog_id, bool is_outgoing) const;
void fail_send_message(FullMessageId full_message_id, int error_code, const string &error_message);
+ void fail_send_message(FullMessageId full_message_id, Status error);
+
+ void fail_edit_message_media(FullMessageId full_message_id, Status &&error);
+
void on_dialog_updated(DialogId dialog_id, const char *source);
- BufferSlice get_dialog_database_value(const Dialog *d);
+ static BufferSlice get_dialog_database_value(const Dialog *d);
void save_dialog_to_database(DialogId dialog_id);
- void on_save_dialog_to_database(DialogId dialog_id, bool success);
+ void on_save_dialog_to_database(DialogId dialog_id, bool can_reuse_notification_group, bool success);
+
+ void try_reuse_notification_group(NotificationGroupInfo &group_info);
+
+ void load_dialog_list(DialogList &list, int32 limit, Promise<Unit> &&promise);
+
+ void load_folder_dialog_list(FolderId folder_id, int32 limit, bool only_local);
+
+ void on_load_folder_dialog_list(FolderId folder_id, Result<Unit> &&result);
- void load_dialog_list(Promise<Unit> &&promise);
+ void load_folder_dialog_list_from_database(FolderId folder_id, int32 limit, Promise<Unit> &&promise);
- void load_dialog_list_from_database(int32 limit, Promise<Unit> &&promise);
+ void preload_folder_dialog_list(FolderId folder_id);
- static void preload_dialog_list(void *messages_manager_void);
+ void get_dialogs_from_list_impl(int64 task_id);
+
+ void on_get_dialogs_from_list(int64 task_id, Result<Unit> &&result);
+
+ static void invalidate_message_indexes(Dialog *d);
void update_message_count_by_index(Dialog *d, int diff, const Message *m);
+ void update_message_count_by_index(Dialog *d, int diff, int32 index_mask);
+
int32 get_message_index_mask(DialogId dialog_id, const Message *m) const;
- int32 get_message_content_index_mask(const MessageContent *content, bool is_secret, bool is_outgoing) const;
+ void update_reply_count_by_message(Dialog *d, int diff, const Message *m);
+
+ void update_message_reply_count(Dialog *d, MessageId message_id, DialogId replier_dialog_id,
+ MessageId reply_message_id, int32 update_date, int diff, bool is_recursive = false);
Message *add_message_to_dialog(DialogId dialog_id, unique_ptr<Message> message, bool from_update, bool *need_update,
bool *need_update_dialog_pos, const char *source);
@@ -2047,49 +2388,151 @@ class MessagesManager : public Actor {
Message *add_message_to_dialog(Dialog *d, unique_ptr<Message> message, bool from_update, bool *need_update,
bool *need_update_dialog_pos, const char *source);
- void on_message_changed(const Dialog *d, const Message *m, const char *source);
+ Message *add_scheduled_message_to_dialog(Dialog *d, unique_ptr<Message> message, bool from_update, bool *need_update,
+ const char *source);
+
+ void register_new_local_message_id(Dialog *d, const Message *m);
+
+ void on_message_changed(const Dialog *d, const Message *m, bool need_send_update, const char *source);
+
+ void on_message_notification_changed(Dialog *d, const Message *m, const char *source);
+
+ bool need_delete_file(FullMessageId full_message_id, FileId file_id) const;
+
+ bool need_delete_message_files(DialogId dialog_id, const Message *m) const;
void add_message_to_database(const Dialog *d, const Message *m, const char *source);
- void delete_all_dialog_messages_from_database(DialogId dialog_id, MessageId message_id, const char *source);
+ void delete_all_dialog_messages_from_database(Dialog *d, MessageId max_message_id, const char *source);
+
+ void delete_message_from_database(Dialog *d, MessageId message_id, const Message *m, bool is_permanently_deleted);
+
+ void update_reply_to_message_id(DialogId dialog_id, MessageId old_message_id, MessageId new_message_id,
+ bool have_new_message, const char *source);
+
+ void delete_message_files(DialogId dialog_id, const Message *m) const;
+
+ static void add_random_id_to_message_id_correspondence(Dialog *d, int64 random_id, MessageId message_id);
+
+ static void delete_random_id_to_message_id_correspondence(Dialog *d, int64 random_id, MessageId message_id);
+
+ static void add_notification_id_to_message_id_correspondence(Dialog *d, NotificationId notification_id,
+ MessageId message_id);
+
+ static void delete_notification_id_to_message_id_correspondence(Dialog *d, NotificationId notification_id,
+ MessageId message_id);
+
+ void remove_message_notification_id(Dialog *d, Message *m, bool is_permanent, bool force_update,
+ bool ignore_pinned_message_notification_removal = false);
+
+ void remove_new_secret_chat_notification(Dialog *d, bool is_permanent);
+
+ void fix_dialog_last_notification_id(Dialog *d, bool from_mentions, MessageId message_id);
+
+ void do_fix_dialog_last_notification_id(DialogId dialog_id, bool from_mentions,
+ NotificationId prev_last_notification_id,
+ Result<vector<Notification>> result);
+
+ void do_delete_message_log_event(const DeleteMessageLogEvent &log_event) const;
+
+ static void attach_message_to_previous(Dialog *d, MessageId message_id, const char *source);
+
+ static void attach_message_to_next(Dialog *d, MessageId message_id, const char *source);
+
+ bool update_message(Dialog *d, Message *old_message, unique_ptr<Message> new_message, bool *need_update_dialog_pos,
+ bool is_message_in_dialog);
- void delete_message_from_database(Dialog *d, MessageId message_id, const Message *m,
- bool is_permanently_deleted) const;
+ static bool need_message_changed_warning(const Message *old_message);
- void delete_message_files(const Message *m) const;
+ bool update_message_content(DialogId dialog_id, Message *old_message, unique_ptr<MessageContent> new_content,
+ bool need_merge_files, bool is_message_in_dialog, bool &is_content_changed);
- void do_delete_message_logevent(const DeleteMessageLogEvent &logevent) const;
+ void update_message_max_reply_media_timestamp(const Dialog *d, Message *m, bool need_send_update_message_content);
- void attach_message_to_previous(Dialog *d, MessageId message_id);
+ void update_message_max_own_media_timestamp(const Dialog *d, Message *m);
- void attach_message_to_next(Dialog *d, MessageId message_id);
+ void update_message_max_reply_media_timestamp_in_replied_messages(DialogId dialog_id, MessageId reply_to_message_id);
- void update_message(Dialog *d, unique_ptr<Message> &old_message, unique_ptr<Message> new_message,
- bool need_send_update_message_content, bool *need_update_dialog_pos);
+ void register_message_reply(DialogId dialog_id, const Message *m);
- bool need_message_text_changed_warning(const Message *old_message, const MessageText *old_content,
- const MessageText *new_content);
+ void reregister_message_reply(DialogId dialog_id, const Message *m);
- bool update_message_content(DialogId dialog_id, Message *old_message, unique_ptr<MessageContent> &old_content,
- unique_ptr<MessageContent> new_content, bool need_send_update_message_content);
+ void unregister_message_reply(DialogId dialog_id, const Message *m);
- void send_update_new_message(Dialog *d, const Message *m, bool force = false);
+ void send_update_new_message(const Dialog *d, const Message *m);
- void flush_pending_update_new_messages(DialogId dialog_id);
+ bool get_dialog_show_preview(const Dialog *d) const;
+
+ bool is_message_preview_enabled(const Dialog *d, const Message *m, bool from_mentions);
+
+ static bool is_from_mention_notification_group(const Message *m);
+
+ static bool is_message_notification_active(const Dialog *d, const Message *m);
+
+ static NotificationGroupInfo &get_notification_group_info(Dialog *d, const Message *m);
+
+ NotificationGroupId get_dialog_notification_group_id(DialogId dialog_id, NotificationGroupInfo &group_info);
+
+ NotificationId get_next_notification_id(Dialog *d, NotificationGroupId notification_group_id, MessageId message_id);
+
+ void try_add_pinned_message_notification(Dialog *d, vector<Notification> &res, NotificationId max_notification_id,
+ int32 limit);
+
+ vector<Notification> get_message_notifications_from_database_force(Dialog *d, bool from_mentions, int32 limit);
+
+ static vector<MessageDbDialogMessage> do_get_message_notifications_from_database_force(
+ Dialog *d, bool from_mentions, NotificationId from_notification_id, MessageId from_message_id, int32 limit);
+
+ void do_get_message_notifications_from_database(Dialog *d, bool from_mentions,
+ NotificationId initial_from_notification_id,
+ NotificationId from_notification_id, MessageId from_message_id,
+ int32 limit, Promise<vector<Notification>> promise);
+
+ void on_get_message_notifications_from_database(DialogId dialog_id, bool from_mentions,
+ NotificationId initial_from_notification_id, int32 limit,
+ Result<vector<MessageDbDialogMessage>> result,
+ Promise<vector<Notification>> promise);
+
+ void do_remove_message_notification(DialogId dialog_id, bool from_mentions, NotificationId notification_id,
+ vector<MessageDbDialogMessage> result);
+
+ int32 get_dialog_pending_notification_count(const Dialog *d, bool from_mentions) const;
+
+ void update_dialog_mention_notification_count(const Dialog *d);
+
+ bool is_message_notification_disabled(const Dialog *d, const Message *m) const;
+
+ bool is_dialog_message_notification_disabled(DialogId dialog_id, int32 message_date) const;
+
+ bool may_need_message_notification(const Dialog *d, const Message *m) const;
+
+ bool add_new_message_notification(Dialog *d, Message *m, bool force);
+
+ void flush_pending_new_message_notifications(DialogId dialog_id, bool from_mentions, DialogId settings_dialog_id);
+
+ void remove_all_dialog_notifications(Dialog *d, bool from_mentions, const char *source);
+
+ void remove_message_dialog_notifications(Dialog *d, MessageId max_message_id, bool from_mentions, const char *source);
+
+ bool need_skip_bot_commands(DialogId dialog_id, const Message *m) const;
void send_update_message_send_succeeded(Dialog *d, MessageId old_message_id, const Message *m) const;
- void send_update_message_content(DialogId dialog_id, MessageId message_id, const MessageContent *content,
- int32 message_date, bool is_content_secret, const char *source) const;
+ void send_update_message_content(const Dialog *d, Message *m, bool is_message_in_dialog, const char *source);
- void send_update_message_edited(FullMessageId full_message_id);
+ void send_update_message_content_impl(DialogId dialog_id, const Message *m, const char *source) const;
void send_update_message_edited(DialogId dialog_id, const Message *m);
- void send_update_delete_messages(DialogId dialog_id, vector<int64> &&message_ids, bool is_permanent,
- bool from_cache) const;
+ void send_update_message_interaction_info(DialogId dialog_id, const Message *m) const;
+
+ void send_update_message_unread_reactions(DialogId dialog_id, const Message *m, int32 unread_reaction_count) const;
+
+ void send_update_message_live_location_viewed(FullMessageId full_message_id);
+
+ void send_update_delete_messages(DialogId dialog_id, vector<int64> &&message_ids, bool is_permanent) const;
- void send_update_chat(Dialog *d);
+ void send_update_new_chat(Dialog *d);
void send_update_chat_draft_message(const Dialog *d);
@@ -2097,7 +2540,13 @@ class MessagesManager : public Actor {
void send_update_chat_last_message_impl(const Dialog *d, const char *source) const;
- void send_update_unread_message_count(DialogId dialog_id, bool force, const char *source);
+ void send_update_chat_filters();
+
+ void send_update_unread_message_count(DialogList &list, DialogId dialog_id, bool force, const char *source,
+ bool from_database = false);
+
+ void send_update_unread_chat_count(DialogList &list, DialogId dialog_id, bool force, const char *source,
+ bool from_database = false);
void send_update_chat_read_inbox(const Dialog *d, bool force, const char *source);
@@ -2105,116 +2554,381 @@ class MessagesManager : public Actor {
void send_update_chat_unread_mention_count(const Dialog *d);
- tl_object_ptr<td_api::message> get_message_object(DialogId dialog_id, const Message *message) const;
+ void send_update_chat_unread_reaction_count(const Dialog *d, const char *source);
+
+ void send_update_chat_position(DialogListId dialog_list_id, const Dialog *d, const char *source) const;
+
+ void send_update_chat_online_member_count(DialogId dialog_id, int32 online_member_count) const;
+
+ void send_update_secret_chats_with_user_action_bar(const Dialog *d) const;
+
+ void send_update_chat_action_bar(Dialog *d);
+
+ void send_update_chat_available_reactions(const Dialog *d);
+
+ void send_update_secret_chats_with_user_theme(const Dialog *d) const;
+
+ void send_update_chat_theme(const Dialog *d);
+
+ void send_update_chat_pending_join_requests(const Dialog *d);
+
+ void send_update_chat_video_chat(const Dialog *d);
+
+ void send_update_chat_message_sender(const Dialog *d);
+
+ void send_update_chat_message_ttl(const Dialog *d);
+
+ void send_update_chat_has_scheduled_messages(Dialog *d, bool from_deletion);
+
+ void send_update_chat_action(DialogId dialog_id, MessageId top_thread_message_id, DialogId typing_dialog_id,
+ const DialogAction &action);
+
+ void repair_dialog_action_bar(Dialog *d, const char *source);
+
+ void hide_dialog_action_bar(Dialog *d);
+
+ void repair_dialog_active_group_call_id(DialogId dialog_id);
+
+ void do_repair_dialog_active_group_call_id(DialogId dialog_id);
+
+ static Result<int32> get_message_schedule_date(td_api::object_ptr<td_api::MessageSchedulingState> &&scheduling_state);
+
+ tl_object_ptr<td_api::MessageSendingState> get_message_sending_state_object(const Message *m) const;
+
+ static tl_object_ptr<td_api::MessageSchedulingState> get_message_scheduling_state_object(int32 send_date);
+
+ tl_object_ptr<td_api::message> get_message_object(DialogId dialog_id, const Message *m, const char *source,
+ bool for_event_log = false) const;
static tl_object_ptr<td_api::messages> get_messages_object(int32 total_count,
- vector<tl_object_ptr<td_api::message>> &&messages);
+ vector<tl_object_ptr<td_api::message>> &&messages,
+ bool skip_not_found);
vector<DialogId> sort_dialogs_by_order(const vector<DialogId> &dialog_ids, int32 limit) const;
vector<DialogId> get_peers_dialog_ids(vector<tl_object_ptr<telegram_api::Peer>> &&peers);
- void recalc_unread_message_count();
+ static bool need_unread_counter(int64 dialog_order);
+
+ int32 get_dialog_total_count(const DialogList &list) const;
+
+ void repair_server_dialog_total_count(DialogListId dialog_list_id);
+
+ void repair_secret_chat_total_count(DialogListId dialog_list_id);
+
+ void on_get_secret_chat_total_count(DialogListId dialog_list_id, int32 total_count);
+
+ void recalc_unread_count(DialogListId dialog_list_id, int32 old_dialog_total_count, bool force);
+
+ td_api::object_ptr<td_api::updateChatFilters> get_update_chat_filters_object() const;
+
+ td_api::object_ptr<td_api::updateUnreadMessageCount> get_update_unread_message_count_object(
+ const DialogList &list) const;
+
+ td_api::object_ptr<td_api::updateUnreadChatCount> get_update_unread_chat_count_object(const DialogList &list) const;
+
+ static void save_unread_chat_count(const DialogList &list);
void set_dialog_last_read_inbox_message_id(Dialog *d, MessageId message_id, int32 server_unread_count,
int32 local_unread_count, bool force_update, const char *source);
void set_dialog_last_read_outbox_message_id(Dialog *d, MessageId message_id);
- void set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source);
+ void set_dialog_last_message_id(Dialog *d, MessageId last_message_id, const char *source, const Message *m = nullptr);
void set_dialog_first_database_message_id(Dialog *d, MessageId first_database_message_id, const char *source);
- void set_dialog_last_database_message_id(Dialog *d, MessageId last_database_message_id, const char *source);
+ void set_dialog_last_database_message_id(Dialog *d, MessageId last_database_message_id, const char *source,
+ bool is_loaded_from_database = false);
void set_dialog_last_new_message_id(Dialog *d, MessageId last_new_message_id, const char *source);
void set_dialog_last_clear_history_date(Dialog *d, int32 date, MessageId last_clear_history_message_id,
- const char *source);
+ const char *source, bool is_loaded_from_database = false);
+
+ static void set_dialog_unread_mention_count(Dialog *d, int32 unread_mention_count);
+
+ static void set_dialog_unread_reaction_count(Dialog *d, int32 unread_reaction_count);
void set_dialog_is_empty(Dialog *d, const char *source);
- static int32 get_pinned_dialogs_limit();
+ void remove_dialog_newer_messages(Dialog *d, MessageId from_message_id, const char *source);
+
+ int32 get_pinned_dialogs_limit(DialogListId dialog_list_id) const;
static vector<DialogId> remove_secret_chat_dialog_ids(vector<DialogId> dialog_ids);
- void set_dialog_is_pinned(DialogId dialog_id, bool is_pinned);
+ bool set_dialog_is_pinned(DialogId dialog_id, bool is_pinned);
+
+ bool set_dialog_is_pinned(DialogListId dialog_list_id, Dialog *d, bool is_pinned,
+ bool need_update_dialog_lists = true);
+
+ void save_pinned_folder_dialog_ids(const DialogList &list) const;
+
+ void set_dialog_is_marked_as_unread(Dialog *d, bool is_marked_as_unread);
+
+ void set_dialog_is_blocked(Dialog *d, bool is_blocked);
+
+ void set_dialog_has_bots(Dialog *d, bool has_bots);
+
+ void set_dialog_last_pinned_message_id(Dialog *d, MessageId last_pinned_message_id);
- void set_dialog_is_pinned(Dialog *d, bool is_pinned);
+ void drop_dialog_last_pinned_message_id(Dialog *d);
- void toggle_dialog_is_pinned_on_server(DialogId dialog_id, bool is_pinned, uint64 logevent_id);
+ void set_dialog_theme_name(Dialog *d, string theme_name);
- void reorder_pinned_dialogs_on_server(const vector<DialogId> &dialog_ids, uint64 logevent_id);
+ void fix_pending_join_requests(DialogId dialog_id, int32 &pending_join_request_count,
+ vector<UserId> &pending_join_request_user_ids) const;
+
+ void set_dialog_pending_join_requests(Dialog *d, int32 pending_join_request_count,
+ vector<UserId> pending_join_request_user_ids);
+
+ void repair_dialog_scheduled_messages(Dialog *d);
+
+ void set_dialog_has_scheduled_server_messages(Dialog *d, bool has_scheduled_server_messages);
+
+ void set_dialog_has_scheduled_database_messages(DialogId dialog_id, bool has_scheduled_database_messages);
+
+ void set_dialog_has_scheduled_database_messages_impl(Dialog *d, bool has_scheduled_database_messages);
+
+ void set_dialog_folder_id(Dialog *d, FolderId folder_id);
+
+ void do_set_dialog_folder_id(Dialog *d, FolderId folder_id);
+
+ void toggle_dialog_is_pinned_on_server(DialogId dialog_id, bool is_pinned, uint64 log_event_id);
+
+ void toggle_dialog_is_marked_as_unread_on_server(DialogId dialog_id, bool is_marked_as_unread, uint64 log_event_id);
+
+ void toggle_dialog_is_blocked_on_server(DialogId dialog_id, bool is_blocked, uint64 log_event_id);
+
+ void reorder_pinned_dialogs_on_server(FolderId folder_id, const vector<DialogId> &dialog_ids, uint64 log_event_id);
void set_dialog_reply_markup(Dialog *d, MessageId message_id);
void try_restore_dialog_reply_markup(Dialog *d, const Message *m);
- static string get_notification_settings_scope_database_key(NotificationSettingsScope scope);
+ void set_dialog_pinned_message_notification(Dialog *d, MessageId message_id, const char *source);
+
+ void remove_dialog_pinned_message_notification(Dialog *d, const char *source);
+
+ void remove_dialog_mention_notifications(Dialog *d);
- bool update_notification_settings(NotificationSettingsScope scope, NotificationSettings *current_settings,
- const NotificationSettings &new_settings);
+ bool set_dialog_last_notification(DialogId dialog_id, NotificationGroupInfo &group_info, int32 last_notification_date,
+ NotificationId last_notification_id, const char *source);
- void update_dialog_unmute_timeout(Dialog *d, int32 old_mute_until, int32 new_mute_until);
+ bool update_dialog_notification_settings(DialogId dialog_id, DialogNotificationSettings *current_settings,
+ DialogNotificationSettings &&new_settings);
+
+ void schedule_dialog_unmute(DialogId dialog_id, bool use_default, int32 mute_until);
+
+ void update_dialog_unmute_timeout(Dialog *d, bool &old_use_default, int32 &old_mute_until, bool new_use_default,
+ int32 new_mute_until);
void on_dialog_unmute(DialogId dialog_id);
+ bool update_dialog_silent_send_message(Dialog *d, bool silent_send_message);
+
+ ChatReactions get_message_available_reactions(const Dialog *d, const Message *m,
+ bool dissalow_custom_for_non_premium);
+
+ void set_message_reactions(Dialog *d, Message *m, bool is_big, bool add_to_recent, Promise<Unit> &&promise);
+
+ void on_set_message_reactions(FullMessageId full_message_id, Result<Unit> result, Promise<Unit> promise);
+
+ void set_dialog_available_reactions(Dialog *d, ChatReactions &&available_reactions);
+
+ void set_dialog_next_available_reactions_generation(Dialog *d, uint32 generation);
+
+ void hide_dialog_message_reactions(Dialog *d);
+
+ ChatReactions get_active_reactions(const ChatReactions &available_reactions) const;
+
+ ChatReactions get_dialog_active_reactions(const Dialog *d) const;
+
+ ChatReactions get_message_active_reactions(const Dialog *d, const Message *m) const;
+
+ static bool need_poll_dialog_message_reactions(const Dialog *d);
+
+ static bool need_poll_message_reactions(const Dialog *d, const Message *m);
+
+ void queue_message_reactions_reload(FullMessageId full_message_id);
+
+ void queue_message_reactions_reload(DialogId dialog_id, const vector<MessageId> &message_ids);
+
+ bool is_dialog_action_unneeded(DialogId dialog_id) const;
+
void on_send_dialog_action_timeout(DialogId dialog_id);
void on_active_dialog_action_timeout(DialogId dialog_id);
- static bool need_cancel_user_dialog_action(int32 action_id, int32 message_content_id);
+ void clear_active_dialog_actions(DialogId dialog_id);
- void cancel_user_dialog_action(DialogId dialog_id, const Message *m);
+ void cancel_dialog_action(DialogId dialog_id, const Message *m);
Dialog *get_dialog_by_message_id(MessageId message_id);
- MessageId get_message_id_by_random_id(Dialog *d, int64 random_id);
+ MessageId get_message_id_by_random_id(Dialog *d, int64 random_id, const char *source);
- Dialog *add_dialog(DialogId dialog_id);
+ Dialog *add_dialog(DialogId dialog_id, const char *source);
- Dialog *add_new_dialog(unique_ptr<Dialog> &&d, bool is_loaded_from_database);
+ Dialog *add_new_dialog(unique_ptr<Dialog> &&dialog, bool is_loaded_from_database, const char *source);
- void fix_new_dialog(Dialog *d, unique_ptr<Message> &&last_database_message, int64 order,
- int32 last_clear_history_date, MessageId last_clear_history_message_id);
+ void fix_new_dialog(Dialog *d, unique_ptr<Message> &&last_database_message, MessageId last_database_message_id,
+ int64 order, int32 last_clear_history_date, MessageId last_clear_history_message_id,
+ DialogId default_join_group_call_as_dialog_id, DialogId default_send_message_as_dialog_id,
+ bool need_drop_default_send_message_as_dialog_id, bool is_loaded_from_database,
+ const char *source);
- void add_dialog_last_database_message(Dialog *d, unique_ptr<Message> &&last_database_message);
+ bool add_dialog_last_database_message(Dialog *d, unique_ptr<Message> &&last_database_message);
- tl_object_ptr<td_api::inputMessageText> get_input_message_text_object(
- const InputMessageText &input_message_text) const;
+ void fix_dialog_action_bar(const Dialog *d, DialogActionBar *action_bar);
- tl_object_ptr<td_api::draftMessage> get_draft_message_object(const unique_ptr<DraftMessage> &draft_message) const;
+ td_api::object_ptr<td_api::ChatType> get_chat_type_object(DialogId dialog_id) const;
- tl_object_ptr<td_api::ChatType> get_chat_type_object(DialogId dialog_id) const;
+ td_api::object_ptr<td_api::ChatActionBar> get_chat_action_bar_object(const Dialog *d) const;
- tl_object_ptr<td_api::chat> get_chat_object(const Dialog *d);
+ string get_dialog_theme_name(const Dialog *d) const;
- bool have_dialog_info(DialogId dialog_id) const;
- bool have_dialog_info_force(DialogId dialog_id) const;
+ td_api::object_ptr<td_api::chatJoinRequestsInfo> get_chat_join_requests_info_object(const Dialog *d) const;
+
+ td_api::object_ptr<td_api::videoChat> get_video_chat_object(const Dialog *d) const;
+
+ td_api::object_ptr<td_api::MessageSender> get_default_message_sender_object(const Dialog *d) const;
+
+ td_api::object_ptr<td_api::chat> get_chat_object(const Dialog *d) const;
Dialog *get_dialog(DialogId dialog_id);
const Dialog *get_dialog(DialogId dialog_id) const;
- Dialog *get_dialog_force(DialogId dialog_id);
+ Dialog *get_dialog_force(DialogId dialog_id, const char *source = "get_dialog_force");
- Dialog *on_load_dialog_from_database(const Result<BufferSlice> &r_value);
+ Dialog *on_load_dialog_from_database(DialogId dialog_id, BufferSlice &&value, const char *source);
- void on_get_dialogs_from_database(vector<BufferSlice> &&dialogs, Promise<Unit> &&promise);
+ void on_get_dialogs_from_database(FolderId folder_id, int32 limit, DialogDbGetDialogsResult &&dialogs,
+ Promise<Unit> &&promise);
- void send_get_dialog_query(DialogId dialog_id, Promise<Unit> &&promise);
+ void send_get_dialog_query(DialogId dialog_id, Promise<Unit> &&promise, uint64 log_event_id, const char *source);
void send_search_public_dialogs_query(const string &query, Promise<Unit> &&promise);
- vector<DialogId> get_pinned_dialogs() const;
+ vector<DialogId> get_pinned_dialog_ids(DialogListId dialog_list_id) const;
+
+ void reload_pinned_dialogs(DialogListId dialog_list_id, Promise<Unit> &&promise);
+
+ static double get_dialog_filters_cache_time();
+
+ void schedule_dialog_filters_reload(double timeout);
+
+ static void on_reload_dialog_filters_timeout(void *messages_manager_ptr);
+
+ void reload_dialog_filters();
+
+ void on_get_dialog_filters(Result<vector<tl_object_ptr<telegram_api::DialogFilter>>> r_filters, bool dummy);
+
+ bool need_synchronize_dialog_filters() const;
+
+ void synchronize_dialog_filters();
void update_dialogs_hints(const Dialog *d);
void update_dialogs_hints_rating(const Dialog *d);
- std::pair<int32, vector<DialogParticipant>> search_private_chat_participants(UserId my_user_id, UserId peer_user_id,
- const string &query, int32 limit) const;
+ td_api::object_ptr<td_api::chatFilter> get_chat_filter_object(const DialogFilter *filter) const;
+
+ void load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector<InputDialogId> &&input_dialog_ids,
+ Promise<Unit> &&promise);
+
+ void on_load_dialog_filter_dialogs(DialogFilterId dialog_filter_id, vector<DialogId> &&dialog_ids,
+ Promise<Unit> &&promise);
+
+ void load_dialog_filter(const DialogFilter *filter, bool force, Promise<Unit> &&promise);
+
+ void on_get_recommended_dialog_filters(Result<vector<tl_object_ptr<telegram_api::dialogFilterSuggested>>> result,
+ Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise);
+
+ void on_load_recommended_dialog_filters(Result<Unit> &&result, vector<RecommendedDialogFilter> &&filters,
+ Promise<td_api::object_ptr<td_api::recommendedChatFilters>> &&promise);
+
+ InputDialogId get_input_dialog_id(DialogId dialog_id) const;
+
+ void sort_dialog_filter_input_dialog_ids(DialogFilter *dialog_filter, const char *source) const;
+
+ Result<unique_ptr<DialogFilter>> create_dialog_filter(DialogFilterId dialog_filter_id,
+ td_api::object_ptr<td_api::chatFilter> filter);
+
+ void update_dialog_filter_on_server(unique_ptr<DialogFilter> &&dialog_filter);
+
+ void on_update_dialog_filter(unique_ptr<DialogFilter> dialog_filter, Status result);
+
+ void delete_dialog_filter_on_server(DialogFilterId dialog_filter_id);
+
+ void on_delete_dialog_filter(DialogFilterId dialog_filter_id, Status result);
+
+ void reorder_dialog_filters_on_server(vector<DialogFilterId> dialog_filter_ids, int32 main_dialog_list_position);
+
+ void on_reorder_dialog_filters(vector<DialogFilterId> dialog_filter_ids, int32 main_dialog_list_position,
+ Status result);
+
+ void save_dialog_filters();
+
+ void add_dialog_filter(unique_ptr<DialogFilter> dialog_filter, bool at_beginning, const char *source);
+
+ void edit_dialog_filter(unique_ptr<DialogFilter> new_dialog_filter, const char *source);
+
+ int32 delete_dialog_filter(DialogFilterId dialog_filter_id, const char *source);
+
+ static bool set_dialog_filters_order(vector<unique_ptr<DialogFilter>> &dialog_filters,
+ vector<DialogFilterId> dialog_filter_ids);
+
+ const DialogFilter *get_server_dialog_filter(DialogFilterId dialog_filter_id) const;
+
+ DialogFilter *get_dialog_filter(DialogFilterId dialog_filter_id);
+ const DialogFilter *get_dialog_filter(DialogFilterId dialog_filter_id) const;
- static unique_ptr<Message> *find_message(unique_ptr<Message> *v, MessageId message_id);
- static const unique_ptr<Message> *find_message(const unique_ptr<Message> *v, MessageId message_id);
+ int32 get_server_main_dialog_list_position() const;
+
+ static vector<DialogFilterId> get_dialog_filter_ids(const vector<unique_ptr<DialogFilter>> &dialog_filters,
+ int32 main_dialog_list_position);
+
+ static vector<FolderId> get_dialog_filter_folder_ids(const DialogFilter *filter);
+
+ vector<FolderId> get_dialog_list_folder_ids(const DialogList &list) const;
+
+ bool has_dialogs_from_folder(const DialogList &list, const DialogFolder &folder) const;
+
+ static bool is_dialog_in_list(const Dialog *d, DialogListId dialog_list_id);
+
+ static void add_dialog_to_list(Dialog *d, DialogListId dialog_list_id);
+
+ static void remove_dialog_from_list(Dialog *d, DialogListId dialog_list_id);
+
+ bool need_dialog_in_filter(const Dialog *d, const DialogFilter *filter) const;
+
+ bool need_dialog_in_list(const Dialog *d, const DialogList &list) const;
+
+ static bool need_send_update_chat_position(const DialogPositionInList &old_position,
+ const DialogPositionInList &new_position);
+
+ DialogPositionInList get_dialog_position_in_list(const DialogList *list, const Dialog *d, bool actual = false) const;
+
+ std::unordered_map<DialogListId, DialogPositionInList, DialogListIdHash> get_dialog_positions(const Dialog *d) const;
+
+ static vector<DialogListId> get_dialog_list_ids(const Dialog *d);
+
+ DialogListView get_dialog_lists(const Dialog *d);
+
+ DialogList &add_dialog_list(DialogListId dialog_list_id);
+
+ DialogList *get_dialog_list(DialogListId dialog_list_id);
+ const DialogList *get_dialog_list(DialogListId dialog_list_id) const;
+
+ DialogFolder *get_dialog_folder(FolderId folder_id);
+ const DialogFolder *get_dialog_folder(FolderId folder_id) const;
+
+ static unique_ptr<Message> *treap_find_message(unique_ptr<Message> *v, MessageId message_id);
+ static const unique_ptr<Message> *treap_find_message(const unique_ptr<Message> *v, MessageId message_id);
+
+ static Message *treap_insert_message(unique_ptr<Message> *v, unique_ptr<Message> message);
+ static unique_ptr<Message> treap_delete_message(unique_ptr<Message> *v);
static Message *get_message(Dialog *d, MessageId message_id);
static const Message *get_message(const Dialog *d, MessageId message_id);
@@ -2222,125 +2936,136 @@ class MessagesManager : public Actor {
Message *get_message(FullMessageId full_message_id);
const Message *get_message(FullMessageId full_message_id) const;
- Message *get_message_force(Dialog *d, MessageId message_id);
+ bool have_message_force(Dialog *d, MessageId message_id, const char *source);
- Message *get_message_force(FullMessageId full_message_id);
+ Message *get_message_force(Dialog *d, MessageId message_id, const char *source);
+
+ Message *get_message_force(FullMessageId full_message_id, const char *source);
void get_message_force_from_server(Dialog *d, MessageId message_id, Promise<Unit> &&promise,
tl_object_ptr<telegram_api::InputMessage> input_message = nullptr);
- Message *on_get_message_from_database(DialogId dialog_id, Dialog *d, const BufferSlice &value);
+ Message *on_get_message_from_database(const MessageDbMessage &message, bool is_scheduled, const char *source);
+
+ Message *on_get_message_from_database(Dialog *d, const MessageDbDialogMessage &message, bool is_scheduled,
+ const char *source);
+
+ Message *on_get_message_from_database(Dialog *d, MessageId message_id, const BufferSlice &value, bool is_scheduled,
+ const char *source);
void get_dialog_message_by_date_from_server(const Dialog *d, int32 date, int64 random_id, bool after_database_search,
Promise<Unit> &&promise);
void on_get_dialog_message_by_date_from_database(DialogId dialog_id, int32 date, int64 random_id,
- Result<BufferSlice> result, Promise<Unit> promise);
-
- static NotificationSettings get_notification_settings(
- tl_object_ptr<telegram_api::PeerNotifySettings> &&notification_settings);
+ Result<MessageDbDialogMessage> result, Promise<Unit> promise);
- const NotificationSettings *get_dialog_notification_settings(const Dialog *d, DialogId dialog_id) const;
+ std::pair<bool, int32> get_dialog_mute_until(DialogId dialog_id, const Dialog *d) const;
- NotificationSettings *get_notification_settings(NotificationSettingsScope scope, bool force);
+ int64 get_dialog_notification_ringtone_id(DialogId dialog_id, const Dialog *d) const;
- static unique_ptr<DraftMessage> get_draft_message(ContactsManager *contacts_manager,
- tl_object_ptr<telegram_api::DraftMessage> &&draft_message_ptr);
+ NotificationSettingsScope get_dialog_notification_setting_scope(DialogId dialog_id) const;
- static FormattedText get_secret_media_caption(string &&message_text, string &&message_caption);
+ DialogNotificationSettings *get_dialog_notification_settings(DialogId dialog_id, bool force);
- Photo get_web_document_photo(tl_object_ptr<telegram_api::WebDocument> web_document, DialogId owner_dialog_id) const;
+ vector<FileId> get_message_file_ids(const Message *m) const;
- unique_ptr<MessageContent> get_secret_message_document(
- tl_object_ptr<telegram_api::encryptedFile> file,
- tl_object_ptr<secret_api::decryptedMessageMediaDocument> &&document,
- vector<tl_object_ptr<telegram_api::DocumentAttribute>> &&attributes, DialogId owner_dialog_id,
- FormattedText &&caption, bool is_opened) const;
+ void cancel_upload_message_content_files(const MessageContent *content);
- unique_ptr<MessageContent> get_message_document(tl_object_ptr<telegram_api::document> &&document,
- DialogId owner_dialog_id, FormattedText &&caption, bool is_opened,
- MultiPromiseActor *load_data_multipromise_ptr) const;
+ static void cancel_upload_file(FileId file_id, const char *source);
- unique_ptr<MessageContent> get_message_document(std::pair<DocumentsManager::DocumentType, FileId> &&parsed_document,
- FormattedText &&caption, bool is_opened) const;
+ void cancel_send_message_query(DialogId dialog_id, Message *m);
- unique_ptr<MessagePhoto> get_message_photo(tl_object_ptr<telegram_api::photo> &&photo, DialogId owner_dialog_id,
- FormattedText &&caption) const;
+ void cancel_send_deleted_message(DialogId dialog_id, Message *m, bool is_permanently_deleted);
- unique_ptr<MessageContent> get_secret_message_content(
- string message_text, tl_object_ptr<telegram_api::encryptedFile> file,
- tl_object_ptr<secret_api::DecryptedMessageMedia> &&media,
- vector<tl_object_ptr<secret_api::MessageEntity>> &&secret_entities, DialogId owner_dialog_id,
- MultiPromiseActor &load_data_multipromise) const;
+ bool is_discussion_message(DialogId dialog_id, const Message *m) const;
- unique_ptr<MessageContent> get_message_content(FormattedText message_text,
- tl_object_ptr<telegram_api::MessageMedia> &&media,
- DialogId owner_dialog_id, bool is_content_read, UserId via_bot_user_id,
- int32 *ttl) const;
+ bool has_message_sender_user_id(DialogId dialog_id, const Message *m) const;
- unique_ptr<MessageContent> dup_message_content(DialogId dialog_id, const MessageContent *content, bool for_forward);
+ int32 get_message_own_max_media_timestamp(const Message *m) const;
- unique_ptr<MessageContent> get_message_action_content(tl_object_ptr<telegram_api::MessageAction> &&action,
- DialogId owner_dialog_id, MessageId reply_to_message_id) const;
+ static int32 get_message_max_media_timestamp(const Message *m);
- tl_object_ptr<td_api::MessageContent> get_message_content_object(const MessageContent *content, int32 message_date,
- bool is_content_secret) const;
+ static bool get_message_disable_web_page_preview(const Message *m);
- static FormattedText get_message_content_caption(const MessageContent *content);
-
- int32 get_message_content_duration(const MessageContent *content) const;
-
- static FileId get_message_content_file_id(const MessageContent *content);
-
- static void update_message_content_file_id_remote(MessageContent *content, FileId file_id);
-
- FileId get_message_content_thumbnail_file_id(const MessageContent *content) const;
-
- vector<FileId> get_message_file_ids(const Message *message) const;
+ static int32 get_message_flags(const Message *m);
- void cancel_send_message_query(DialogId dialog_id, unique_ptr<Message> &m);
+ tl_object_ptr<telegram_api::InputPeer> get_send_message_as_input_peer(const Message *m) const;
- static int32 get_message_flags(const Message *m);
+ static bool is_forward_info_sender_hidden(const MessageForwardInfo *forward_info);
unique_ptr<MessageForwardInfo> get_message_forward_info(
- tl_object_ptr<telegram_api::messageFwdHeader> &&forward_header);
+ tl_object_ptr<telegram_api::messageFwdHeader> &&forward_header, FullMessageId full_message_id);
- tl_object_ptr<td_api::MessageForwardInfo> get_message_forward_info_object(
+ td_api::object_ptr<td_api::messageForwardInfo> get_message_forward_info_object(
const unique_ptr<MessageForwardInfo> &forward_info) const;
- void ttl_read_history_inbox(DialogId dialog_id, MessageId from_message_id, MessageId till_message_id,
- double timestamp);
- void ttl_read_history_outbox(DialogId dialog_id, MessageId from_message_id, MessageId till_message_id,
- double timestamp);
- void ttl_on_view(const Dialog *d, Message *message, double view_date, double now);
- bool ttl_on_open(Dialog *d, Message *message, double now, bool is_local_read);
- void ttl_register_message(DialogId dialog_id, const Message *message, double now);
- void ttl_unregister_message(DialogId dialog_id, const Message *message, double now);
+ void ttl_read_history(Dialog *d, bool is_outgoing, MessageId from_message_id, MessageId till_message_id,
+ double view_date);
+ void ttl_read_history_impl(DialogId dialog_id, bool is_outgoing, MessageId from_message_id, MessageId till_message_id,
+ double view_date);
+ void ttl_on_view(const Dialog *d, Message *m, double view_date, double now);
+ bool ttl_on_open(Dialog *d, Message *m, double now, bool is_local_read);
+ void ttl_register_message(DialogId dialog_id, const Message *m, double now);
+ void ttl_unregister_message(DialogId dialog_id, const Message *m, const char *source);
+ void ttl_period_register_message(DialogId dialog_id, const Message *m, double server_time);
+ void ttl_period_unregister_message(DialogId dialog_id, const Message *m);
void ttl_loop(double now);
void ttl_update_timeout(double now);
- void on_message_ttl_expired(Dialog *d, Message *message);
- void on_message_ttl_expired_impl(Dialog *d, Message *message);
+ void on_message_ttl_expired(Dialog *d, Message *m);
+ void on_message_ttl_expired_impl(Dialog *d, Message *m);
+
+ void start_up() final;
- void start_up() override;
- void loop() override;
- void tear_down() override;
+ void loop() final;
+
+ void tear_down() final;
+
+ void hangup() final;
+
+ void create_folders();
+ void init();
void ttl_db_loop_start(double server_now);
void ttl_db_loop(double server_now);
- void ttl_db_on_result(Result<std::pair<std::vector<std::pair<DialogId, BufferSlice>>, int32>> r_result, bool dummy);
+ void ttl_db_on_result(Result<std::pair<std::vector<MessageDbMessage>, int32>> r_result, bool dummy);
+
+ void on_restore_missing_message_after_get_difference(FullMessageId full_message_id, MessageId old_message_id,
+ Result<Unit> result);
+
+ void on_get_message_link_dialog(MessageLinkInfo &&info, Promise<MessageLinkInfo> &&promise);
+
+ void on_get_message_link_message(MessageLinkInfo &&info, DialogId dialog_id, Promise<MessageLinkInfo> &&promise);
+
+ void on_get_message_link_discussion_message(MessageLinkInfo &&info, DialogId comment_dialog_id,
+ Promise<MessageLinkInfo> &&promise);
+
+ void process_discussion_message_impl(telegram_api::object_ptr<telegram_api::messages_discussionMessage> &&result,
+ DialogId dialog_id, MessageId message_id, DialogId expected_dialog_id,
+ MessageId expected_message_id, Promise<MessageThreadInfo> promise);
+
+ void on_get_discussion_message(DialogId dialog_id, MessageId message_id, MessageThreadInfo &&message_thread_info,
+ Promise<MessageThreadInfo> &&promise);
- static MessageId get_first_database_message_id_by_index(const Dialog *d, SearchMessagesFilter filter);
+ void on_get_message_viewers(DialogId dialog_id, vector<UserId> user_ids, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::users>> &&promise);
- void on_search_dialog_messages_db_result(int64 random_id, DialogId dialog_id, MessageId from_message_id,
- MessageId first_db_message_id, SearchMessagesFilter filter_type,
- int32 offset, int32 limit, Result<MessagesDbMessagesResult> result,
- Promise<> promise);
+ static MessageId get_first_database_message_id_by_index(const Dialog *d, MessageSearchFilter filter);
- void on_messages_db_fts_result(Result<MessagesDbFtsResult> result, int64 random_id, Promise<> &&promise);
+ void on_get_message_calendar_from_database(int64 random_id, DialogId dialog_id, MessageId from_message_id,
+ MessageId first_db_message_id, MessageSearchFilter filter,
+ Result<MessageDbCalendar> r_calendar, Promise<Unit> promise);
- void on_messages_db_calls_result(Result<MessagesDbCallsResult> result, int64 random_id, MessageId first_db_message_id,
- SearchMessagesFilter filter, Promise<> &&promise);
+ void on_search_dialog_message_db_result(int64 random_id, DialogId dialog_id, MessageId from_message_id,
+ MessageId first_db_message_id, MessageSearchFilter filter, int32 offset,
+ int32 limit, Result<vector<MessageDbDialogMessage>> r_messages,
+ Promise<Unit> promise);
+
+ void on_message_db_fts_result(Result<MessageDbFtsResult> result, string offset, int32 limit, int64 random_id,
+ Promise<Unit> &&promise);
+
+ void on_message_db_calls_result(Result<MessageDbCallsResult> result, int64 random_id, MessageId first_db_message_id,
+ MessageSearchFilter filter, Promise<Unit> &&promise);
void on_load_active_live_location_full_message_ids_from_database(string value);
@@ -2354,17 +3079,47 @@ class MessagesManager : public Actor {
void save_active_live_locations();
+ void on_message_live_location_viewed(Dialog *d, const Message *m);
+
+ void view_message_live_location_on_server(int64 task_id);
+
+ void view_message_live_location_on_server_impl(int64 task_id, FullMessageId full_message_id);
+
+ void on_message_live_location_viewed_on_server(int64 task_id);
+
+ void try_add_bot_command_message_id(DialogId dialog_id, const Message *m);
+
+ void delete_bot_command_message_id(DialogId dialog_id, MessageId message_id);
+
+ void add_message_file_sources(DialogId dialog_id, const Message *m);
+
+ void remove_message_file_sources(DialogId dialog_id, const Message *m);
+
+ void change_message_files(DialogId dialog_id, const Message *m, const vector<FileId> &old_file_ids);
+
Result<unique_ptr<ReplyMarkup>> get_dialog_reply_markup(
DialogId dialog_id, tl_object_ptr<td_api::ReplyMarkup> &&reply_markup_ptr) const TD_WARN_UNUSED_RESULT;
const DialogPhoto *get_dialog_photo(DialogId dialog_id) const;
- string get_dialog_title(DialogId dialog_id) const;
+ RestrictedRights get_dialog_default_permissions(DialogId dialog_id) const;
+
+ bool get_dialog_has_protected_content(DialogId dialog_id) const;
- string get_dialog_username(DialogId dialog_id) const;
+ bool get_dialog_has_scheduled_messages(const Dialog *d) const;
static int64 get_dialog_order(MessageId message_id, int32 message_date);
+ bool is_dialog_sponsored(const Dialog *d) const;
+
+ int64 get_dialog_base_order(const Dialog *d) const;
+
+ int64 get_dialog_private_order(const DialogList *list, const Dialog *d) const;
+
+ td_api::object_ptr<td_api::chatPosition> get_chat_position_object(DialogListId dialog_list_id, const Dialog *d) const;
+
+ vector<td_api::object_ptr<td_api::chatPosition>> get_chat_positions_object(const Dialog *d) const;
+
bool update_dialog_draft_message(Dialog *d, unique_ptr<DraftMessage> &&draft_message, bool from_update,
bool need_update_dialog_pos);
@@ -2372,45 +3127,72 @@ class MessagesManager : public Actor {
void on_saved_dialog_draft_message(DialogId dialog_id, uint64 generation);
+ void update_dialog_notification_settings_on_server(DialogId dialog_id, bool from_binlog);
+
+ void send_update_dialog_notification_settings_query(const Dialog *d, Promise<Unit> &&promise);
+
+ void on_updated_dialog_notification_settings(DialogId dialog_id, uint64 generation);
+
+ void reset_all_notification_settings_on_server(uint64 log_event_id);
+
+ void toggle_dialog_report_spam_state_on_server(DialogId dialog_id, bool is_spam_dialog, uint64 log_event_id,
+ Promise<Unit> &&promise);
+
+ void set_dialog_folder_id_on_server(DialogId dialog_id, bool from_binlog);
+
+ void on_updated_dialog_folder_id(DialogId dialog_id, uint64 generation);
+
int64 get_next_pinned_dialog_order();
- void update_dialog_pos(Dialog *d, bool remove_from_dialog_list, const char *source,
- bool need_send_update_chat_order = true);
+ bool is_removed_from_dialog_list(const Dialog *d) const;
+
+ void update_dialog_pos(Dialog *d, const char *source, bool need_send_update = true,
+ bool is_loaded_from_database = false);
- bool set_dialog_order(Dialog *d, int64 new_order, bool need_send_update_chat_order);
+ bool set_dialog_order(Dialog *d, int64 new_order, bool need_send_update, bool is_loaded_from_database,
+ const char *source);
- void update_last_dialog_date();
+ void update_dialog_lists(Dialog *d,
+ std::unordered_map<DialogListId, DialogPositionInList, DialogListIdHash> &&old_positions,
+ bool need_send_update, bool is_loaded_from_database, const char *source);
- void load_notification_settings();
+ void update_last_dialog_date(FolderId folder_id);
- void set_get_difference_timeout(double timeout);
+ bool do_update_list_last_pinned_dialog_date(DialogList &list) const;
- void skip_old_pending_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 old_pts,
- int32 pts_count, const char *source);
+ void update_list_last_pinned_dialog_date(DialogList &list);
- void process_pending_updates();
+ bool do_update_list_last_dialog_date(DialogList &list, const vector<FolderId> &folder_ids) const;
- void drop_pending_updates();
+ void update_list_last_dialog_date(DialogList &list);
static string get_channel_pts_key(DialogId dialog_id);
int32 load_channel_pts(DialogId dialog_id) const;
- void set_channel_pts(Dialog *d, int32 new_pts, const char *source) const;
+ void set_channel_pts(Dialog *d, int32 new_pts, const char *source);
+
+ bool need_channel_difference_to_add_message(DialogId dialog_id,
+ const tl_object_ptr<telegram_api::Message> &message_ptr);
+
+ void run_after_channel_difference(DialogId dialog_id, Promise<Unit> &&promise);
bool running_get_channel_difference(DialogId dialog_id) const;
- void get_channel_difference(DialogId dialog_id, int32 pts, bool force, const char *source);
+ void on_channel_get_difference_timeout(DialogId dialog_id);
+
+ void get_channel_difference(DialogId dialog_id, int32 pts, bool force, const char *source, bool is_old = false);
void do_get_channel_difference(DialogId dialog_id, int32 pts, bool force,
- tl_object_ptr<telegram_api::InputChannel> &&input_channel, const char *source);
+ tl_object_ptr<telegram_api::InputChannel> &&input_channel, bool is_old,
+ const char *source);
- void process_get_channel_difference_updates(DialogId dialog_id,
+ void process_get_channel_difference_updates(DialogId dialog_id, int32 new_pts,
vector<tl_object_ptr<telegram_api::Message>> &&new_messages,
vector<tl_object_ptr<telegram_api::Update>> &&other_updates);
void on_get_channel_dialog(DialogId dialog_id, MessageId last_message_id, MessageId read_inbox_max_message_id,
- int32 server_unread_count, int32 unread_mention_count,
+ int32 server_unread_count, int32 unread_mention_count, int32 unread_reaction_count,
MessageId read_outbox_max_message_id,
vector<tl_object_ptr<telegram_api::Message>> &&messages);
@@ -2420,8 +3202,12 @@ class MessagesManager : public Actor {
static void on_pending_message_views_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);
+ static void on_pending_message_live_location_view_timeout_callback(void *messages_manager_ptr, int64 task_id);
+
static void on_pending_draft_message_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);
+ static void on_pending_read_history_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);
+
static void on_pending_updated_dialog_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);
static void on_pending_unload_dialog_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);
@@ -2432,13 +3218,13 @@ class MessagesManager : public Actor {
static void on_active_dialog_action_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);
- void load_secret_thumbnail(FileId thumbnail_file_id);
+ static void on_update_dialog_online_member_count_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);
+
+ static void on_preload_folder_dialog_list_timeout_callback(void *messages_manager_ptr, int64 folder_id_int);
- static tl_object_ptr<telegram_api::channelAdminLogEventsFilter> get_channel_admin_log_events_filter(
- const tl_object_ptr<td_api::chatEventLogFilters> &filters);
+ static void on_update_viewed_messages_timeout_callback(void *messages_manager_ptr, int64 dialog_id_int);
- tl_object_ptr<td_api::ChatEventAction> get_chat_event_action_object(
- tl_object_ptr<telegram_api::ChannelAdminLogEventAction> &&action_ptr);
+ void load_secret_thumbnail(FileId thumbnail_file_id);
void on_upload_media(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file,
tl_object_ptr<telegram_api::InputEncryptedFile> input_encrypted_file);
@@ -2450,95 +3236,198 @@ class MessagesManager : public Actor {
void on_upload_dialog_photo(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file);
void on_upload_dialog_photo_error(FileId file_id, Status status);
- static uint64 get_sequence_dispatcher_id(DialogId dialog_id, int32 message_content_type);
+ void send_edit_dialog_photo_query(DialogId dialog_id, FileId file_id,
+ tl_object_ptr<telegram_api::InputChatPhoto> &&input_chat_photo,
+ Promise<Unit> &&promise);
+
+ void upload_imported_messages(DialogId dialog_id, FileId file_id, vector<FileId> attached_file_ids, bool is_reupload,
+ Promise<Unit> &&promise, vector<int> bad_parts = {});
+
+ void on_upload_imported_messages(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file);
+ void on_upload_imported_messages_error(FileId file_id, Status status);
+
+ void upload_imported_message_attachment(DialogId dialog_id, int64 import_id, FileId file_id, bool is_reupload,
+ Promise<Unit> &&promise, vector<int> bad_parts = {});
+
+ void on_upload_imported_message_attachment(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file);
+ void on_upload_imported_message_attachment_error(FileId file_id, Status status);
+
+ void on_imported_message_attachments_uploaded(int64 random_id, Result<Unit> &&result);
+
+ Status can_import_messages(DialogId dialog_id);
+
+ void send_get_message_public_forwards_query(DcId dc_id, FullMessageId full_message_id, string offset, int32 limit,
+ Promise<td_api::object_ptr<td_api::foundMessages>> &&promise);
+
+ void add_sponsored_dialog(const Dialog *d, DialogSource source);
+
+ void save_sponsored_dialog();
+
+ void set_sponsored_dialog(DialogId dialog_id, DialogSource source);
Dialog *get_service_notifications_dialog();
- static MessageId get_next_message_id(Dialog *d, int32 type);
+ void save_auth_notification_ids();
+
+ static MessageId get_next_message_id(Dialog *d, MessageType type);
static MessageId get_next_local_message_id(Dialog *d);
static MessageId get_next_yet_unsent_message_id(Dialog *d);
- bool add_recently_found_dialog_internal(DialogId dialog_id);
+ static MessageId get_next_yet_unsent_scheduled_message_id(Dialog *d, int32 date);
+
+ void reget_message_from_server_if_needed(DialogId dialog_id, const Message *m);
- bool remove_recently_found_dialog_internal(DialogId dialog_id);
+ void speculatively_update_active_group_call_id(Dialog *d, const Message *m);
- void save_recently_found_dialogs();
- bool load_recently_found_dialogs(Promise<Unit> &promise);
+ void speculatively_update_channel_participants(DialogId dialog_id, const Message *m);
+
+ void update_sent_message_contents(DialogId dialog_id, const Message *m);
void update_used_hashtags(DialogId dialog_id, const Message *m);
- string get_search_text(const Message *m);
+ void update_top_dialogs(DialogId dialog_id, const Message *m);
+
+ void update_forward_count(DialogId dialog_id, const Message *m);
+
+ void update_forward_count(DialogId dialog_id, MessageId message_id, int32 update_date);
+
+ void update_has_outgoing_messages(DialogId dialog_id, const Message *m);
- unique_ptr<Dialog> parse_dialog(DialogId dialog_id, const BufferSlice &value);
+ string get_message_search_text(const Message *m) const;
+
+ unique_ptr<Message> parse_message(Dialog *d, MessageId expected_message_id, const BufferSlice &value,
+ bool is_scheduled);
+
+ unique_ptr<Dialog> parse_dialog(DialogId dialog_id, const BufferSlice &value, const char *source);
void load_calls_db_state();
void save_calls_db_state();
- static constexpr bool is_debug_message_op_enabled();
+ static constexpr bool is_debug_message_op_enabled() {
+ return !LOG_IS_STRIPPED(ERROR) && false;
+ }
+
+ void add_message_dependencies(Dependencies &dependencies, const Message *m);
static void dump_debug_message_op(const Dialog *d, int priority = 0);
- void add_message_dependencies(Dependencies &dependencies, DialogId dialog_id, const Message *m);
+ static void save_send_message_log_event(DialogId dialog_id, const Message *m);
- void add_dialog_dependencies(Dependencies &dependencies, DialogId dialog_id);
+ static uint64 save_toggle_dialog_report_spam_state_on_server_log_event(DialogId dialog_id, bool is_spam_dialog);
- void resolve_dependencies_force(const Dependencies &dependencies);
+ static uint64 save_delete_messages_on_server_log_event(DialogId dialog_id, const vector<MessageId> &message_ids,
+ bool revoke);
- void save_send_message_logevent(DialogId dialog_id, Message *m);
+ static uint64 save_delete_scheduled_messages_on_server_log_event(DialogId dialog_id,
+ const vector<MessageId> &message_ids);
+
+ static uint64 save_delete_dialog_history_on_server_log_event(DialogId dialog_id, MessageId max_message_id,
+ bool remove_from_dialog_list, bool revoke);
+
+ static uint64 save_delete_topic_history_on_server_log_event(DialogId dialog_id, MessageId top_thread_message_id);
+
+ static uint64 save_delete_all_call_messages_on_server_log_event(bool revoke);
+
+ static uint64 save_block_message_sender_from_replies_on_server_log_event(MessageId message_id,
+ bool need_delete_message,
+ bool need_delete_all_messages,
+ bool report_spam);
+
+ static uint64 save_delete_all_channel_messages_by_sender_on_server_log_event(ChannelId channel_id,
+ DialogId sender_dialog_id);
+
+ static uint64 save_delete_dialog_messages_by_date_on_server_log_event(DialogId dialog_id, int32 min_date,
+ int32 max_date, bool revoke);
+
+ static uint64 save_read_all_dialog_mentions_on_server_log_event(DialogId dialog_id);
+
+ static uint64 save_read_all_dialog_reactions_on_server_log_event(DialogId dialog_id);
+
+ static uint64 save_toggle_dialog_is_pinned_on_server_log_event(DialogId dialog_id, bool is_pinned);
+
+ static uint64 save_reorder_pinned_dialogs_on_server_log_event(FolderId folder_id, const vector<DialogId> &dialog_ids);
+
+ static uint64 save_toggle_dialog_is_marked_as_unread_on_server_log_event(DialogId dialog_id,
+ bool is_marked_as_unread);
+
+ static uint64 save_toggle_dialog_is_blocked_on_server_log_event(DialogId dialog_id, bool is_blocked);
+
+ static uint64 save_read_message_contents_on_server_log_event(DialogId dialog_id,
+ const vector<MessageId> &message_ids);
+
+ static uint64 save_reset_all_notification_settings_on_server_log_event();
+
+ static uint64 save_reget_dialog_log_event(DialogId dialog_id);
+
+ static uint64 save_forward_messages_log_event(DialogId to_dialog_id, DialogId from_dialog_id,
+ const vector<Message *> &messages, const vector<MessageId> &message_ids,
+ bool drop_author, bool drop_media_captions);
+
+ static uint64 save_unpin_all_dialog_messages_on_server_log_event(DialogId dialog_id);
void suffix_load_loop(Dialog *d);
- void suffix_load_update_first_message_id(Dialog *d);
+ static void suffix_load_update_first_message_id(Dialog *d);
void suffix_load_query_ready(DialogId dialog_id);
- void suffix_load_add_query(Dialog *d, std::pair<Promise<>, std::function<bool(const Message *)>> query);
- void suffix_load_till_date(Dialog *d, int32 date, Promise<> promise);
- void suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise<> promise);
+ void suffix_load_add_query(Dialog *d, std::pair<Promise<Unit>, std::function<bool(const Message *)>> query);
+ void suffix_load_till_date(Dialog *d, int32 date, Promise<Unit> promise);
+ void suffix_load_till_message_id(Dialog *d, MessageId message_id, Promise<Unit> promise);
- Result<ServerMessageId> get_invoice_message_id(FullMessageId full_message_id);
+ bool is_group_dialog(DialogId dialog_id) const;
bool is_broadcast_channel(DialogId dialog_id) const;
- int32 recently_found_dialogs_loaded_ = 0; // 0 - not loaded, 1 - load request was sent, 2 - loaded
- MultiPromiseActor resolve_recent_found_dialogs_multipromise_;
+ bool is_deleted_secret_chat(const Dialog *d) const;
+
+ static int32 get_message_schedule_date(const Message *m);
+
+ static DialogId get_message_original_sender(const Message *m);
+
+ static DialogId get_message_sender(const Message *m);
- vector<DialogId> recently_found_dialog_ids_;
+ RecentDialogList recently_found_dialogs_;
+ RecentDialogList recently_opened_dialogs_;
class UploadMediaCallback;
class UploadThumbnailCallback;
class UploadDialogPhotoCallback;
+ class UploadImportedMessagesCallback;
+ class UploadImportedMessageAttachmentCallback;
std::shared_ptr<UploadMediaCallback> upload_media_callback_;
std::shared_ptr<UploadThumbnailCallback> upload_thumbnail_callback_;
std::shared_ptr<UploadDialogPhotoCallback> upload_dialog_photo_callback_;
+ std::shared_ptr<UploadImportedMessagesCallback> upload_imported_messages_callback_;
+ std::shared_ptr<UploadImportedMessageAttachmentCallback> upload_imported_message_attachment_callback_;
- int32 accumulated_pts_count_ = 0;
- int32 accumulated_pts_ = -1;
- Timeout pts_gap_timeout_;
+ double last_channel_pts_jump_warning_time_ = 0;
- std::unordered_map<FileId, std::pair<FullMessageId, FileId>, FileIdHash>
+ FlatHashMap<FileId, std::pair<FullMessageId, FileId>, FileIdHash>
being_uploaded_files_; // file_id -> message, thumbnail_file_id
struct UploadedThumbnailInfo {
FullMessageId full_message_id;
FileId file_id; // original file file_id
tl_object_ptr<telegram_api::InputFile> input_file; // original file InputFile
};
- std::unordered_map<FileId, UploadedThumbnailInfo, FileIdHash> being_uploaded_thumbnails_; // thumbnail_file_id -> ...
+ FlatHashMap<FileId, UploadedThumbnailInfo, FileIdHash> being_uploaded_thumbnails_; // thumbnail_file_id -> ...
struct UploadedSecretThumbnailInfo {
FullMessageId full_message_id;
FileId file_id; // original file file_id
tl_object_ptr<telegram_api::InputEncryptedFile> input_file; // original file InputEncryptedFile
};
- std::unordered_map<FileId, UploadedSecretThumbnailInfo, FileIdHash>
+ FlatHashMap<FileId, UploadedSecretThumbnailInfo, FileIdHash>
being_loaded_secret_thumbnails_; // thumbnail_file_id -> ...
// TTL
- class TtlNode : private HeapNode {
+ class TtlNode final : private HeapNode {
public:
- TtlNode(DialogId dialog_id, MessageId message_id) : full_message_id(dialog_id, message_id) {
+ TtlNode(DialogId dialog_id, MessageId message_id, bool by_ttl_period)
+ : full_message_id_(dialog_id, message_id), by_ttl_period_(by_ttl_period) {
}
- FullMessageId full_message_id;
+ FullMessageId full_message_id_;
+ bool by_ttl_period_;
HeapNode *as_heap_node() const {
return const_cast<HeapNode *>(static_cast<const HeapNode *>(this));
@@ -2548,38 +3437,88 @@ class MessagesManager : public Actor {
}
bool operator==(const TtlNode &other) const {
- return full_message_id == other.full_message_id;
+ return full_message_id_ == other.full_message_id_;
}
};
struct TtlNodeHash {
- std::size_t operator()(const TtlNode &ttl_node) const {
- return FullMessageIdHash()(ttl_node.full_message_id);
+ uint32 operator()(const TtlNode &ttl_node) const {
+ return FullMessageIdHash()(ttl_node.full_message_id_) * 2 + static_cast<uint32>(ttl_node.by_ttl_period_);
}
};
std::unordered_set<TtlNode, TtlNodeHash> ttl_nodes_;
KHeap<double> ttl_heap_;
Slot ttl_slot_;
- enum YieldType { None, Ttl, TtlDb }; // None must be first
- int32 ttl_db_expire_from_;
- int32 ttl_db_expire_till_;
+ enum YieldType : int32 { None, TtlDb }; // None must be first
+ int32 ttl_db_expires_from_;
+ int32 ttl_db_expires_till_;
bool ttl_db_has_query_;
Slot ttl_db_slot_;
- std::unordered_set<int64> message_random_ids_;
- std::unordered_map<int64, FullMessageId> being_sent_messages_; // message_random_id -> message
+ FlatHashMap<int64, FullMessageId> being_sent_messages_; // message_random_id -> message
- std::unordered_map<FullMessageId, MessageId, FullMessageIdHash>
- update_message_ids_; // full_message_id -> temporary_id
- std::unordered_map<int64, DialogId> debug_being_sent_messages_; // message_random_id -> dialog_id
+ FlatHashMap<FullMessageId, MessageId, FullMessageIdHash> update_message_ids_; // new_message_id -> temporary_id
+ FlatHashMap<DialogId, FlatHashMap<ScheduledServerMessageId, MessageId, ScheduledServerMessageIdHash>,
+ DialogIdHash>
+ update_scheduled_message_ids_; // new_message_id -> temporary_id
- const char *debug_add_message_to_dialog_fail_reason = "";
+ const char *debug_add_message_to_dialog_fail_reason_ = "";
struct UploadedDialogPhotoInfo {
+ DialogId dialog_id;
+ double main_frame_timestamp;
+ bool is_animation;
+ bool is_reupload;
+ Promise<Unit> promise;
+
+ UploadedDialogPhotoInfo(DialogId dialog_id, double main_frame_timestamp, bool is_animation, bool is_reupload,
+ Promise<Unit> promise)
+ : dialog_id(dialog_id)
+ , main_frame_timestamp(main_frame_timestamp)
+ , is_animation(is_animation)
+ , is_reupload(is_reupload)
+ , promise(std::move(promise)) {
+ }
+ };
+ FlatHashMap<FileId, UploadedDialogPhotoInfo, FileIdHash> being_uploaded_dialog_photos_;
+
+ struct UploadedImportedMessagesInfo {
+ DialogId dialog_id;
+ vector<FileId> attached_file_ids;
+ bool is_reupload;
+ Promise<Unit> promise;
+
+ UploadedImportedMessagesInfo(DialogId dialog_id, vector<FileId> &&attached_file_ids, bool is_reupload,
+ Promise<Unit> &&promise)
+ : dialog_id(dialog_id)
+ , attached_file_ids(std::move(attached_file_ids))
+ , is_reupload(is_reupload)
+ , promise(std::move(promise)) {
+ }
+ };
+ FlatHashMap<FileId, unique_ptr<UploadedImportedMessagesInfo>, FileIdHash> being_uploaded_imported_messages_;
+
+ struct UploadedImportedMessageAttachmentInfo {
+ DialogId dialog_id;
+ int64 import_id;
+ bool is_reupload;
Promise<Unit> promise;
+
+ UploadedImportedMessageAttachmentInfo(DialogId dialog_id, int64 import_id, bool is_reupload,
+ Promise<Unit> &&promise)
+ : dialog_id(dialog_id), import_id(import_id), is_reupload(is_reupload), promise(std::move(promise)) {
+ }
+ };
+ FlatHashMap<FileId, unique_ptr<UploadedImportedMessageAttachmentInfo>, FileIdHash>
+ being_uploaded_imported_message_attachments_;
+
+ struct PendingMessageImport {
+ MultiPromiseActor upload_files_multipromise{"UploadAttachedFilesMultiPromiseActor"};
DialogId dialog_id;
+ int64 import_id = 0;
+ Promise<Unit> promise;
};
- std::unordered_map<FileId, UploadedDialogPhotoInfo, FileIdHash> uploaded_dialog_photos_; // file_id -> ...
+ FlatHashMap<int64, unique_ptr<PendingMessageImport>> pending_message_imports_;
struct PendingMessageGroupSend {
DialogId dialog_id;
@@ -2588,150 +3527,275 @@ class MessagesManager : public Actor {
vector<bool> is_finished;
vector<Status> results;
};
- std::unordered_map<int64, PendingMessageGroupSend> pending_message_group_sends_; // media_album_id -> ...
+ FlatHashMap<int64, PendingMessageGroupSend> pending_message_group_sends_; // media_album_id -> ...
- std::unordered_map<MessageId, DialogId, MessageIdHash> message_id_to_dialog_id_;
- std::unordered_map<MessageId, DialogId, MessageIdHash> last_clear_history_message_id_to_dialog_id_;
+ WaitFreeHashMap<MessageId, DialogId, MessageIdHash> message_id_to_dialog_id_;
+ FlatHashMap<MessageId, DialogId, MessageIdHash> last_clear_history_message_id_to_dialog_id_;
- std::unordered_map<int64, DialogId> created_dialogs_; // random_id -> dialog_id
- std::unordered_map<DialogId, Promise<Unit>, DialogIdHash> pending_created_dialogs_; // dialog_id -> promise
+ bool created_public_broadcasts_inited_ = false;
+ vector<ChannelId> created_public_broadcasts_;
+
+ FlatHashMap<int64, DialogId> created_dialogs_; // random_id -> dialog_id
+ FlatHashMap<DialogId, Promise<Unit>, DialogIdHash> pending_created_dialogs_; // dialog_id -> promise
bool running_get_difference_ = false; // true after before_get_difference and false after after_get_difference
- std::unordered_map<DialogId, unique_ptr<Dialog>, DialogIdHash> dialogs_;
- std::multimap<int32, PendingPtsUpdate> pending_updates_;
- std::multimap<int32, PendingPtsUpdate> postponed_pts_updates_;
+ WaitFreeHashMap<DialogId, unique_ptr<Dialog>, DialogIdHash> dialogs_;
+ int64 added_message_count_ = 0;
- std::unordered_set<DialogId, DialogIdHash>
- loaded_dialogs_; // dialogs loaded from database, but not added to dialogs_
+ FlatHashSet<DialogId, DialogIdHash> loaded_dialogs_; // dialogs loaded from database, but not added to dialogs_
- std::unordered_set<DialogId, DialogIdHash> postponed_chat_read_inbox_updates_;
- std::unordered_map<DialogId, vector<std::pair<MessageId, Promise<Unit>>>, DialogIdHash>
- postponed_get_message_requests_;
+ FlatHashSet<DialogId, DialogIdHash> postponed_chat_read_inbox_updates_;
- std::unordered_map<string, vector<Promise<Unit>>> search_public_dialogs_queries_;
- std::unordered_map<string, vector<DialogId>> found_public_dialogs_; // TODO time bound cache
- std::unordered_map<string, vector<DialogId>> found_on_server_dialogs_; // TODO time bound cache
+ FlatHashMap<string, vector<Promise<Unit>>> search_public_dialogs_queries_;
+ FlatHashMap<string, vector<DialogId>> found_public_dialogs_; // TODO time bound cache
+ FlatHashMap<string, vector<DialogId>> found_on_server_dialogs_; // TODO time bound cache
- std::unordered_map<UserId, vector<DialogId>, UserIdHash> found_common_dialogs_; // TODO time bound cache
+ struct CommonDialogs {
+ vector<DialogId> dialog_ids;
+ double receive_time = 0;
+ int32 total_count = 0;
+ bool is_outdated = false;
+ };
+ FlatHashMap<UserId, CommonDialogs, UserIdHash> found_common_dialogs_;
- std::unordered_map<int64, FullMessageId> get_dialog_message_by_date_results_;
+ FlatHashMap<int64, FullMessageId> get_dialog_message_by_date_results_;
- std::unordered_map<int64, std::pair<int32, vector<MessageId>>>
- found_dialog_messages_; // random_id -> [total_count, [message_id]...]
- std::unordered_map<int64, std::pair<int32, vector<FullMessageId>>>
+ FlatHashMap<int64, td_api::object_ptr<td_api::messageCalendar>> found_dialog_message_calendars_;
+ FlatHashMap<int64, std::pair<int32, vector<MessageId>>>
+ found_dialog_messages_; // random_id -> [total_count, [message_id]...]
+ FlatHashMap<int64, DialogId> found_dialog_messages_dialog_id_; // random_id -> dialog_id
+ FlatHashMap<int64, std::pair<int32, vector<FullMessageId>>>
found_messages_; // random_id -> [total_count, [full_message_id]...]
- std::unordered_map<int64, std::pair<int32, vector<FullMessageId>>>
+ FlatHashMap<int64, std::pair<int32, vector<FullMessageId>>>
found_call_messages_; // random_id -> [total_count, [full_message_id]...]
- std::unordered_map<int64, std::pair<int32, vector<MessageId>>>
- found_dialog_recent_location_messages_; // random_id -> [total_count, [message_id]...]
- std::unordered_map<int64, std::pair<int64, vector<FullMessageId>>>
- found_fts_messages_; // random_id -> [from_search_id, [full_message_id]...]
+ FlatHashMap<int64, FoundMessages> found_fts_messages_; // random_id -> FoundMessages
- std::unordered_map<FullMessageId, std::pair<string, string>, FullMessageIdHash> public_message_links_[2];
-
- std::unordered_map<int64, tl_object_ptr<td_api::chatEvents>> chat_events_; // random_id -> chat events
-
- std::unordered_map<int64, tl_object_ptr<td_api::gameHighScores>> game_high_scores_; // random_id -> high scores
+ struct MessageEmbeddingCodes {
+ FlatHashMap<MessageId, string, MessageIdHash> embedding_codes_;
+ };
+ FlatHashMap<DialogId, MessageEmbeddingCodes, DialogIdHash> message_embedding_codes_[2];
- std::unordered_map<DialogId, vector<Promise<Unit>>, DialogIdHash> get_dialog_queries_;
+ FlatHashMap<DialogId, vector<Promise<Unit>>, DialogIdHash> get_dialog_queries_;
+ FlatHashMap<DialogId, uint64, DialogIdHash> get_dialog_query_log_event_id_;
- std::unordered_map<FullMessageId, int32, FullMessageIdHash> replied_by_yet_unsent_messages_;
+ FlatHashMap<FullMessageId, int32, FullMessageIdHash> replied_by_yet_unsent_messages_;
+ FlatHashMap<FullMessageId, FlatHashSet<MessageId, MessageIdHash>, FullMessageIdHash> replied_yet_unsent_messages_;
- std::unordered_set<FullMessageId, FullMessageIdHash> waiting_for_web_page_messages_;
+ // full_message_id -> replies with media timestamps
+ FlatHashMap<FullMessageId, FlatHashSet<MessageId, MessageIdHash>, FullMessageIdHash>
+ replied_by_media_timestamp_messages_;
struct ActiveDialogAction {
- UserId user_id;
- int32 action_id;
- int32 progress;
+ MessageId top_thread_message_id;
+ DialogId typing_dialog_id;
+ DialogAction action;
double start_time;
- ActiveDialogAction(UserId user_id, int32 action_id, double start_time)
- : user_id(user_id), action_id(action_id), start_time(start_time) {
+ ActiveDialogAction(MessageId top_thread_message_id, DialogId typing_dialog_id, DialogAction action,
+ double start_time)
+ : top_thread_message_id(top_thread_message_id)
+ , typing_dialog_id(typing_dialog_id)
+ , action(std::move(action))
+ , start_time(start_time) {
}
};
- std::unordered_map<DialogId, std::vector<ActiveDialogAction>, DialogIdHash> active_dialog_actions_;
-
- NotificationSettings users_notification_settings_;
- NotificationSettings chats_notification_settings_;
- NotificationSettings dialogs_notification_settings_;
-
- bool have_postponed_unread_message_count_update_ = false;
- bool is_unread_count_inited_ = false;
- bool need_unread_count_recalc_ = true;
- int32 unread_message_total_count_ = 0;
- int32 unread_message_muted_count_ = 0;
-
- // uint32 preloaded_dialogs_ = 0; // TODO remove variable
-
- int64 current_pinned_dialog_order_ = DEFAULT_ORDER;
-
- std::set<DialogDate> ordered_dialogs_;
- std::set<DialogDate> ordered_server_dialogs_;
-
- // date of last dialog in the dialog list
- // last_dialog_date_ == min(last_server_dialog_date_, last_secret_chat_dialog_date_)
- DialogDate last_dialog_date_ = MIN_DIALOG_DATE; // in memory
-
- // date of last known user/group/channel dialog in the right order
- DialogDate last_server_dialog_date_ = MIN_DIALOG_DATE;
- DialogDate last_loaded_database_dialog_date_ = MIN_DIALOG_DATE;
- DialogDate last_database_server_dialog_date_ = MIN_DIALOG_DATE;
-
- MultiPromiseActor load_dialog_list_multipromise_; // should be defined before pending_on_get_dialogs_
- Timeout preload_dialog_list_timeout_;
-
- std::unordered_map<DialogId, string, DialogIdHash> active_get_channel_differencies_;
- std::unordered_map<DialogId, uint64, DialogIdHash> get_channel_difference_to_logevent_id_;
-
- MultiTimeout channel_get_difference_timeout_;
- MultiTimeout channel_get_difference_retry_timeout_;
- MultiTimeout pending_message_views_timeout_;
- MultiTimeout pending_draft_message_timeout_;
- MultiTimeout pending_updated_dialog_timeout_;
- MultiTimeout pending_unload_dialog_timeout_;
- MultiTimeout dialog_unmute_timeout_;
- MultiTimeout pending_send_dialog_action_timeout_;
- MultiTimeout active_dialog_action_timeout_;
-
- Hints dialogs_hints_; // search dialogs by title and username
-
- std::unordered_set<FullMessageId, FullMessageIdHash> active_live_location_full_message_ids_;
+ FlatHashMap<DialogId, std::vector<ActiveDialogAction>, DialogIdHash> active_dialog_actions_;
+
+ FlatHashMap<NotificationGroupId, DialogId, NotificationGroupIdHash> notification_group_id_to_dialog_id_;
+
+ uint64 current_message_edit_generation_ = 0;
+
+ std::unordered_set<DialogListId, DialogListIdHash> postponed_unread_message_count_updates_;
+ std::unordered_set<DialogListId, DialogListIdHash> postponed_unread_chat_count_updates_;
+
+ int64 current_pinned_dialog_order_ = static_cast<int64>(MIN_PINNED_DIALOG_DATE) << 32;
+
+ std::unordered_map<DialogListId, DialogList, DialogListIdHash> dialog_lists_;
+ std::unordered_map<FolderId, DialogFolder, FolderIdHash> dialog_folders_;
+
+ bool are_dialog_filters_being_synchronized_ = false;
+ bool are_dialog_filters_being_reloaded_ = false;
+ bool need_dialog_filters_reload_ = false;
+ bool disable_get_dialog_filter_ = false;
+ bool is_update_chat_filters_sent_ = false;
+ int32 dialog_filters_updated_date_ = 0;
+ vector<unique_ptr<DialogFilter>> server_dialog_filters_;
+ vector<unique_ptr<DialogFilter>> dialog_filters_;
+ vector<RecommendedDialogFilter> recommended_dialog_filters_;
+ vector<Promise<Unit>> dialog_filter_reload_queries_;
+ int32 server_main_dialog_list_position_ = 0; // position of the main dialog list stored on the server
+ int32 main_dialog_list_position_ = 0; // local position of the main dialog list stored on the server
+
+ FlatHashMap<DialogId, string, DialogIdHash> active_get_channel_differencies_;
+ FlatHashMap<DialogId, uint64, DialogIdHash> get_channel_difference_to_log_event_id_;
+ FlatHashMap<DialogId, int32, DialogIdHash> channel_get_difference_retry_timeouts_;
+ FlatHashMap<DialogId, std::multimap<int32, PendingPtsUpdate>, DialogIdHash> postponed_channel_updates_;
+ FlatHashSet<DialogId, DialogIdHash> is_channel_difference_finished_;
+
+ MultiTimeout channel_get_difference_timeout_{"ChannelGetDifferenceTimeout"};
+ MultiTimeout channel_get_difference_retry_timeout_{"ChannelGetDifferenceRetryTimeout"};
+ MultiTimeout pending_message_views_timeout_{"PendingMessageViewsTimeout"};
+ MultiTimeout pending_message_live_location_view_timeout_{"PendingMessageLiveLocationViewTimeout"};
+ MultiTimeout pending_draft_message_timeout_{"PendingDraftMessageTimeout"};
+ MultiTimeout pending_read_history_timeout_{"PendingReadHistoryTimeout"};
+ MultiTimeout pending_updated_dialog_timeout_{"PendingUpdatedDialogTimeout"};
+ MultiTimeout pending_unload_dialog_timeout_{"PendingUnloadDialogTimeout"};
+ MultiTimeout dialog_unmute_timeout_{"DialogUnmuteTimeout"};
+ MultiTimeout pending_send_dialog_action_timeout_{"PendingSendDialogActionTimeout"};
+ MultiTimeout active_dialog_action_timeout_{"ActiveDialogActionTimeout"};
+ MultiTimeout update_dialog_online_member_count_timeout_{"UpdateDialogOnlineMemberCountTimeout"};
+ MultiTimeout preload_folder_dialog_list_timeout_{"PreloadFolderDialogListTimeout"};
+ MultiTimeout update_viewed_messages_timeout_{"UpdateViewedMessagesTimeout"};
+
+ Timeout reload_dialog_filters_timeout_;
+
+ Hints dialogs_hints_; // search dialogs by title and usernames
+
+ FlatHashSet<FullMessageId, FullMessageIdHash> active_live_location_full_message_ids_;
bool are_active_live_location_messages_loaded_ = false;
vector<Promise<Unit>> load_active_live_location_messages_queries_;
+ FlatHashMap<DialogId, vector<Promise<Unit>>, DialogIdHash> load_scheduled_messages_from_database_queries_;
+
struct ResolvedUsername {
DialogId dialog_id;
- double expires_at;
+ double expires_at = 0.0;
+
+ ResolvedUsername() = default;
+ ResolvedUsername(DialogId dialog_id, double expires_at) : dialog_id(dialog_id), expires_at(expires_at) {
+ }
+ };
+
+ WaitFreeHashMap<string, ResolvedUsername> resolved_usernames_;
+ WaitFreeHashMap<string, DialogId> inaccessible_resolved_usernames_;
+ FlatHashSet<string> reload_voice_chat_on_search_usernames_;
+
+ struct GetDialogsTask {
+ DialogListId dialog_list_id;
+ int32 limit;
+ int32 retry_count;
+ DialogDate last_dialog_date = MIN_DIALOG_DATE;
+ Promise<td_api::object_ptr<td_api::chats>> promise;
};
- std::unordered_map<string, ResolvedUsername> resolved_usernames_;
- std::unordered_map<string, DialogId> unaccessible_resolved_usernames_;
+ FlatHashMap<int64, GetDialogsTask> get_dialogs_tasks_;
+ int64 current_get_dialogs_task_id_ = 0;
struct PendingOnGetDialogs {
- vector<tl_object_ptr<telegram_api::dialog>> dialogs;
+ FolderId folder_id;
+ vector<tl_object_ptr<telegram_api::Dialog>> dialogs;
int32 total_count;
vector<tl_object_ptr<telegram_api::Message>> messages;
Promise<Unit> promise;
};
vector<PendingOnGetDialogs> pending_on_get_dialogs_;
- std::unordered_map<DialogId, PendingOnGetDialogs, DialogIdHash> pending_channel_on_get_dialogs_;
+ FlatHashMap<DialogId, PendingOnGetDialogs, DialogIdHash> pending_channel_on_get_dialogs_;
+
+ FlatHashMap<DialogId, vector<Promise<Unit>>, DialogIdHash> run_after_get_channel_difference_;
ChangesProcessor<unique_ptr<PendingSecretMessage>> pending_secret_messages_;
- std::unordered_map<DialogId, vector<DialogId>, DialogIdHash>
- pending_add_dialog_last_database_message_dependent_dialogs_;
- std::unordered_map<DialogId, std::pair<int32, unique_ptr<Message>>, DialogIdHash>
+ FlatHashMap<DialogId, FlatHashMap<int64, MessageId>, DialogIdHash>
+ pending_secret_message_ids_; // random_id -> message_id
+
+ FlatHashMap<DialogId, vector<DialogId>, DialogIdHash> pending_add_dialog_last_database_message_dependent_dialogs_;
+ FlatHashMap<DialogId, std::pair<int32, unique_ptr<Message>>, DialogIdHash>
pending_add_dialog_last_database_message_; // dialog -> dependency counter + message
+ FlatHashMap<DialogId, vector<DialogId>, DialogIdHash>
+ pending_add_default_join_group_call_as_dialog_id_; // dialog_id -> dependent dialogs
+
+ FlatHashMap<DialogId, vector<std::pair<DialogId, bool>>, DialogIdHash>
+ pending_add_default_send_message_as_dialog_id_; // dialog_id -> [dependent dialog, need_drop]
+
+ struct MessageIds {
+ FlatHashSet<MessageId, MessageIdHash> message_ids;
+ };
+ FlatHashMap<DialogId, MessageIds, DialogIdHash> dialog_bot_command_message_ids_;
+
+ struct CallsDbState {
+ std::array<MessageId, 2> first_calls_database_message_id_by_index;
+ std::array<int32, 2> message_count_by_index;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
CallsDbState calls_db_state_;
- std::unordered_map<uint64, std::map<int64, Promise<Message *>>> yet_unsent_media_queues_;
+ int64 viewed_live_location_task_id_ = 0;
+ FlatHashMap<int64, FullMessageId> viewed_live_location_tasks_; // task_id -> task
+
+ FlatHashMap<uint64, std::map<MessageId, Promise<Message *>>> yet_unsent_media_queues_;
+
+ FlatHashMap<DialogId, NetQueryRef, DialogIdHash> set_typing_query_;
+
+ WaitFreeHashMap<FullMessageId, FileSourceId, FullMessageIdHash> full_message_id_to_file_source_id_;
+
+ FlatHashMap<DialogId, int32, DialogIdHash> last_outgoing_forwarded_message_date_;
+
+ struct ViewedMessagesInfo {
+ FlatHashMap<MessageId, uint64, MessageIdHash> message_id_to_view_id;
+ std::map<uint64, MessageId> recently_viewed_messages;
+ uint64 current_view_id = 0;
+ };
+ FlatHashMap<DialogId, unique_ptr<ViewedMessagesInfo>, DialogIdHash> dialog_viewed_messages_;
+
+ struct OnlineMemberCountInfo {
+ int32 online_member_count = 0;
+ double update_time = 0;
+ bool is_update_sent = false;
+ };
+ FlatHashMap<DialogId, OnlineMemberCountInfo, DialogIdHash> dialog_online_member_counts_;
+
+ struct ReactionsToReload {
+ FlatHashSet<MessageId, MessageIdHash> message_ids;
+ bool is_request_sent = false;
+ };
+ FlatHashMap<DialogId, ReactionsToReload, DialogIdHash> being_reloaded_reactions_;
+
+ FlatHashMap<DialogId, std::pair<bool, bool>, DialogIdHash> pending_dialog_group_call_updates_;
+
+ FlatHashMap<string, int32> auth_notification_id_date_;
+
+ FlatHashMap<DialogId, MessageId, DialogIdHash> previous_repaired_read_inbox_max_message_id_;
+
+ struct PendingReaction {
+ int32 query_count = 0;
+ bool was_updated = false;
+ };
+ FlatHashMap<FullMessageId, PendingReaction, FullMessageIdHash> pending_reactions_;
+
+ vector<string> active_reactions_;
+ FlatHashMap<string, size_t> active_reaction_pos_;
+
+ uint32 scheduled_messages_sync_generation_ = 1;
+
+ int64 authorization_date_ = 0;
+
+ DialogId removed_sponsored_dialog_id_;
+ DialogId sponsored_dialog_id_;
+ DialogSource sponsored_dialog_source_;
+
+ FullMessageId being_readded_message_id_;
+
+ DialogId being_added_dialog_id_;
+ DialogId being_added_by_new_message_dialog_id_;
+ DialogId being_added_new_dialog_id_;
+
+ DialogId debug_channel_difference_dialog_;
+ DialogId debug_last_get_channel_difference_dialog_id_;
+ const char *debug_last_get_channel_difference_source_ = "unknown";
- std::unordered_map<DialogId, NetQueryRef, DialogIdHash> set_typing_query_;
+ double start_time_ = 0;
+ bool is_inited_ = false;
Td *td_;
ActorShared<> parent_;
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MinChannel.h b/protocols/Telegram/tdlib/td/td/telegram/MinChannel.h
new file mode 100644
index 0000000000..17ae3ac3a0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MinChannel.h
@@ -0,0 +1,21 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/Photo.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+struct MinChannel {
+ string title_;
+ DialogPhoto photo_;
+ bool is_megagroup_ = false;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/MinChannel.hpp b/protocols/Telegram/tdlib/td/td/telegram/MinChannel.hpp
new file mode 100644
index 0000000000..ab43b463b0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/MinChannel.hpp
@@ -0,0 +1,51 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MinChannel.h"
+#include "td/telegram/Photo.hpp"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const MinChannel &min_channel, StorerT &storer) {
+ bool has_title = !min_channel.title_.empty();
+ bool has_photo = min_channel.photo_.small_file_id.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_title);
+ STORE_FLAG(has_photo);
+ STORE_FLAG(min_channel.is_megagroup_);
+ END_STORE_FLAGS();
+ if (has_title) {
+ store(min_channel.title_, storer);
+ }
+ if (has_photo) {
+ store(min_channel.photo_, storer);
+ }
+}
+
+template <class ParserT>
+void parse(MinChannel &min_channel, ParserT &parser) {
+ bool has_title;
+ bool has_photo;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_title);
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(min_channel.is_megagroup_);
+ END_PARSE_FLAGS();
+ if (has_title) {
+ parse(min_channel.title_, parser);
+ }
+ if (has_photo) {
+ parse(min_channel.photo_, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NewPasswordState.cpp b/protocols/Telegram/tdlib/td/td/telegram/NewPasswordState.cpp
new file mode 100644
index 0000000000..505cfc7521
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NewPasswordState.cpp
@@ -0,0 +1,58 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/NewPasswordState.h"
+
+namespace td {
+
+Result<NewPasswordState> get_new_password_state(tl_object_ptr<telegram_api::PasswordKdfAlgo> new_algo,
+ tl_object_ptr<telegram_api::SecurePasswordKdfAlgo> new_secure_algo) {
+ NewPasswordState state;
+ CHECK(new_algo != nullptr);
+ switch (new_algo->get_id()) {
+ case telegram_api::passwordKdfAlgoUnknown::ID:
+ return Status::Error(400, "Please update client to continue");
+ case telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow::ID: {
+ auto algo =
+ move_tl_object_as<telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow>(new_algo);
+ state.client_salt = algo->salt1_.as_slice().str();
+ state.server_salt = algo->salt2_.as_slice().str();
+ state.srp_g = algo->g_;
+ state.srp_p = algo->p_.as_slice().str();
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ CHECK(new_secure_algo != nullptr);
+ switch (new_secure_algo->get_id()) {
+ case telegram_api::securePasswordKdfAlgoUnknown::ID:
+ return Status::Error(400, "Please update client to continue");
+ case telegram_api::securePasswordKdfAlgoSHA512::ID:
+ return Status::Error(500, "Server has sent outdated secret encryption mode");
+ case telegram_api::securePasswordKdfAlgoPBKDF2HMACSHA512iter100000::ID: {
+ auto algo = move_tl_object_as<telegram_api::securePasswordKdfAlgoPBKDF2HMACSHA512iter100000>(new_secure_algo);
+ state.secure_salt = algo->salt_.as_slice().str();
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+
+ static constexpr size_t MIN_NEW_SECURE_SALT_SIZE = 8;
+ if (state.secure_salt.size() < MIN_NEW_SECURE_SALT_SIZE) {
+ return Status::Error(500, "New secure salt length too small");
+ }
+
+ static constexpr size_t MIN_NEW_SALT_SIZE = 8;
+ if (state.client_salt.size() < MIN_NEW_SALT_SIZE) {
+ return Status::Error(500, "New salt length too small");
+ }
+ return state;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NewPasswordState.h b/protocols/Telegram/tdlib/td/td/telegram/NewPasswordState.h
new file mode 100644
index 0000000000..5697c94feb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NewPasswordState.h
@@ -0,0 +1,27 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+struct NewPasswordState {
+ string client_salt;
+ string server_salt;
+ string srp_p;
+ string secure_salt;
+ int32 srp_g = 0;
+};
+
+Result<NewPasswordState> get_new_password_state(tl_object_ptr<telegram_api::PasswordKdfAlgo> new_algo,
+ tl_object_ptr<telegram_api::SecurePasswordKdfAlgo> new_secure_algo);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Notification.h b/protocols/Telegram/tdlib/td/td/telegram/Notification.h
new file mode 100644
index 0000000000..837945b703
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Notification.h
@@ -0,0 +1,47 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/NotificationId.h"
+#include "td/telegram/NotificationType.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class Notification {
+ public:
+ NotificationId notification_id;
+ int32 date = 0;
+ bool disable_notification = false;
+ unique_ptr<NotificationType> type;
+
+ Notification(NotificationId notification_id, int32 date, bool disable_notification, unique_ptr<NotificationType> type)
+ : notification_id(notification_id)
+ , date(date)
+ , disable_notification(disable_notification)
+ , type(std::move(type)) {
+ }
+};
+
+inline td_api::object_ptr<td_api::notification> get_notification_object(DialogId dialog_id,
+ const Notification &notification) {
+ CHECK(notification.type != nullptr);
+ return td_api::make_object<td_api::notification>(notification.notification_id.get(), notification.date,
+ notification.disable_notification,
+ notification.type->get_notification_type_object(dialog_id));
+}
+
+inline StringBuilder &operator<<(StringBuilder &sb, const Notification &notification) {
+ return sb << "notification[" << notification.notification_id << ", " << notification.date << ", "
+ << notification.disable_notification << ", " << *notification.type << ']';
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationGroupId.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationGroupId.h
new file mode 100644
index 0000000000..cc12713091
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationGroupId.h
@@ -0,0 +1,67 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class NotificationGroupId {
+ public:
+ NotificationGroupId() = default;
+
+ explicit NotificationGroupId(int32 group_id) : id(group_id) {
+ }
+
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ NotificationGroupId(T group_id) = delete;
+
+ bool is_valid() const {
+ return id > 0;
+ }
+
+ int32 get() const {
+ return id;
+ }
+
+ bool operator==(const NotificationGroupId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const NotificationGroupId &other) const {
+ return id != other.id;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ storer.store_int(id);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ id = parser.fetch_int();
+ }
+
+ private:
+ int32 id{0};
+};
+
+struct NotificationGroupIdHash {
+ uint32 operator()(NotificationGroupId group_id) const {
+ return Hash<int32>()(group_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &sb, const NotificationGroupId group_id) {
+ return sb << "notification group " << group_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationGroupKey.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationGroupKey.h
new file mode 100644
index 0000000000..495dc4c522
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationGroupKey.h
@@ -0,0 +1,43 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/NotificationGroupId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+struct NotificationGroupKey {
+ NotificationGroupId group_id;
+ DialogId dialog_id;
+ int32 last_notification_date = 0;
+
+ NotificationGroupKey() = default;
+ NotificationGroupKey(NotificationGroupId group_id, DialogId dialog_id, int32 last_notification_date)
+ : group_id(group_id), dialog_id(dialog_id), last_notification_date(last_notification_date) {
+ }
+
+ bool operator<(const NotificationGroupKey &other) const {
+ if (last_notification_date != other.last_notification_date) {
+ return last_notification_date > other.last_notification_date;
+ }
+ if (dialog_id != other.dialog_id) {
+ return dialog_id.get() > other.dialog_id.get();
+ }
+ return group_id.get() > other.group_id.get();
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, const NotificationGroupKey &group_key) {
+ return string_builder << '[' << group_key.group_id << ',' << group_key.dialog_id << ','
+ << group_key.last_notification_date << ']';
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationGroupType.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationGroupType.h
new file mode 100644
index 0000000000..63d3bd5ccc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationGroupType.h
@@ -0,0 +1,69 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+enum class NotificationGroupType : int8 { Messages, Mentions, SecretChat, Calls };
+
+inline td_api::object_ptr<td_api::NotificationGroupType> get_notification_group_type_object(
+ NotificationGroupType type) {
+ switch (type) {
+ case NotificationGroupType::Messages:
+ return td_api::make_object<td_api::notificationGroupTypeMessages>();
+ case NotificationGroupType::Mentions:
+ return td_api::make_object<td_api::notificationGroupTypeMentions>();
+ case NotificationGroupType::SecretChat:
+ return td_api::make_object<td_api::notificationGroupTypeSecretChat>();
+ case NotificationGroupType::Calls:
+ return td_api::make_object<td_api::notificationGroupTypeCalls>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+inline NotificationGroupType get_notification_group_type(
+ const td_api::object_ptr<td_api::NotificationGroupType> &type) {
+ CHECK(type != nullptr);
+ switch (type->get_id()) {
+ case td_api::notificationGroupTypeMessages::ID:
+ return NotificationGroupType::Messages;
+ case td_api::notificationGroupTypeMentions::ID:
+ return NotificationGroupType::Mentions;
+ case td_api::notificationGroupTypeSecretChat::ID:
+ return NotificationGroupType::SecretChat;
+ case td_api::notificationGroupTypeCalls::ID:
+ return NotificationGroupType::Calls;
+ default:
+ UNREACHABLE();
+ return NotificationGroupType::Calls;
+ }
+}
+
+inline StringBuilder &operator<<(StringBuilder &sb, const NotificationGroupType &type) {
+ switch (type) {
+ case NotificationGroupType::Messages:
+ return sb << "Messages";
+ case NotificationGroupType::Mentions:
+ return sb << "Mentions";
+ case NotificationGroupType::SecretChat:
+ return sb << "SecretChat";
+ case NotificationGroupType::Calls:
+ return sb << "Calls";
+ default:
+ UNREACHABLE();
+ return sb;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationId.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationId.h
new file mode 100644
index 0000000000..7c16507b45
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationId.h
@@ -0,0 +1,72 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <limits>
+#include <type_traits>
+
+namespace td {
+
+class NotificationId {
+ public:
+ NotificationId() = default;
+
+ explicit constexpr NotificationId(int32 notification_id) : id(notification_id) {
+ }
+
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ NotificationId(T notification_id) = delete;
+
+ static constexpr NotificationId max() {
+ return NotificationId(std::numeric_limits<int32>::max());
+ }
+
+ bool is_valid() const {
+ return id > 0;
+ }
+
+ int32 get() const {
+ return id;
+ }
+
+ bool operator==(const NotificationId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const NotificationId &other) const {
+ return id != other.id;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ storer.store_int(id);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ id = parser.fetch_int();
+ }
+
+ private:
+ int32 id{0};
+};
+
+struct NotificationIdHash {
+ uint32 operator()(NotificationId notification_id) const {
+ return Hash<int32>()(notification_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &sb, const NotificationId notification_id) {
+ return sb << "notification " << notification_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/NotificationManager.cpp
new file mode 100644
index 0000000000..3f0df1b7b4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationManager.cpp
@@ -0,0 +1,4227 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/NotificationManager.h"
+
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DeviceTokenManager.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/Document.hpp"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/net/ConnectionCreator.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/Photo.hpp"
+#include "td/telegram/SecretChatId.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/StateManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/mtproto/AuthKey.h"
+#include "td/mtproto/mtproto_api.h"
+#include "td/mtproto/PacketInfo.h"
+#include "td/mtproto/Transport.h"
+
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+
+#include "td/actor/SleepActor.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/as.h"
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/format.h"
+#include "td/utils/Gzip.h"
+#include "td/utils/JsonBuilder.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Time.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/tl_parsers.h"
+#include "td/utils/utf8.h"
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+
+namespace td {
+
+int VERBOSITY_NAME(notifications) = VERBOSITY_NAME(INFO);
+
+class SetContactSignUpNotificationQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SetContactSignUpNotificationQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(bool is_disabled) {
+ send_query(G()->net_query_creator().create(telegram_api::account_setContactSignUpNotification(is_disabled)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_setContactSignUpNotification>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for set contact sign up notification: " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetContactSignUpNotificationQuery final : public Td::ResultHandler {
+ Promise<bool> promise_;
+
+ public:
+ explicit GetContactSignUpNotificationQuery(Promise<bool> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_getContactSignUpNotification()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getContactSignUpNotification>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for get contact sign up notification: " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+NotificationManager::NotificationManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+ flush_pending_notifications_timeout_.set_callback(on_flush_pending_notifications_timeout_callback);
+ flush_pending_notifications_timeout_.set_callback_data(static_cast<void *>(this));
+
+ flush_pending_updates_timeout_.set_callback(on_flush_pending_updates_timeout_callback);
+ flush_pending_updates_timeout_.set_callback_data(static_cast<void *>(this));
+}
+
+void NotificationManager::on_flush_pending_notifications_timeout_callback(void *notification_manager_ptr,
+ int64 group_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto notification_manager = static_cast<NotificationManager *>(notification_manager_ptr);
+ VLOG(notifications) << "Ready to flush pending notifications for notification group " << group_id_int;
+ if (group_id_int > 0) {
+ send_closure_later(notification_manager->actor_id(notification_manager),
+ &NotificationManager::flush_pending_notifications,
+ NotificationGroupId(narrow_cast<int32>(group_id_int)));
+ } else if (group_id_int == 0) {
+ send_closure_later(notification_manager->actor_id(notification_manager),
+ &NotificationManager::after_get_difference_impl);
+ } else {
+ send_closure_later(notification_manager->actor_id(notification_manager),
+ &NotificationManager::after_get_chat_difference_impl,
+ NotificationGroupId(narrow_cast<int32>(-group_id_int)));
+ }
+}
+
+void NotificationManager::on_flush_pending_updates_timeout_callback(void *notification_manager_ptr,
+ int64 group_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto notification_manager = static_cast<NotificationManager *>(notification_manager_ptr);
+ send_closure_later(notification_manager->actor_id(notification_manager), &NotificationManager::flush_pending_updates,
+ narrow_cast<int32>(group_id_int), "timeout");
+}
+
+bool NotificationManager::is_disabled() const {
+ return !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot() || G()->close_flag();
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const NotificationManager::ActiveNotificationsUpdate &update) {
+ if (update.update == nullptr) {
+ return string_builder << "null";
+ }
+ string_builder << "update[\n";
+ for (auto &group : update.update->groups_) {
+ vector<int32> added_notification_ids;
+ for (auto &notification : group->notifications_) {
+ added_notification_ids.push_back(notification->id_);
+ }
+
+ string_builder << " [" << NotificationGroupId(group->id_) << " of type "
+ << get_notification_group_type(group->type_) << " from " << DialogId(group->chat_id_)
+ << "; total_count = " << group->total_count_ << ", restore " << added_notification_ids << "]\n";
+ }
+ return string_builder << ']';
+}
+
+NotificationManager::ActiveNotificationsUpdate NotificationManager::as_active_notifications_update(
+ const td_api::updateActiveNotifications *update) {
+ return ActiveNotificationsUpdate{update};
+}
+
+string NotificationManager::get_is_contact_registered_notifications_synchronized_key() {
+ return "notifications_contact_registered_sync_state";
+}
+
+void NotificationManager::start_up() {
+ init();
+}
+
+void NotificationManager::init() {
+ if (is_disabled() || is_inited_) {
+ return;
+ }
+
+ disable_contact_registered_notifications_ =
+ td_->option_manager_->get_option_boolean("disable_contact_registered_notifications");
+ auto sync_state = G()->td_db()->get_binlog_pmc()->get(get_is_contact_registered_notifications_synchronized_key());
+ if (sync_state.empty()) {
+ sync_state = "00";
+ }
+ contact_registered_notifications_sync_state_ = static_cast<SyncState>(sync_state[0] - '0');
+ VLOG(notifications) << "Loaded disable_contact_registered_notifications = "
+ << disable_contact_registered_notifications_ << " in state " << sync_state;
+ if (contact_registered_notifications_sync_state_ != SyncState::Completed ||
+ sync_state[1] != static_cast<int32>(disable_contact_registered_notifications_) + '0') {
+ run_contact_registered_notifications_sync();
+ }
+
+ current_notification_id_ =
+ NotificationId(to_integer<int32>(G()->td_db()->get_binlog_pmc()->get("notification_id_current")));
+ current_notification_group_id_ =
+ NotificationGroupId(to_integer<int32>(G()->td_db()->get_binlog_pmc()->get("notification_group_id_current")));
+
+ VLOG(notifications) << "Loaded current " << current_notification_id_ << " and " << current_notification_group_id_;
+
+ on_notification_group_count_max_changed(false);
+ on_notification_group_size_max_changed();
+
+ on_online_cloud_timeout_changed();
+ on_notification_cloud_delay_changed();
+ on_notification_default_delay_changed();
+
+ last_loaded_notification_group_key_.last_notification_date = std::numeric_limits<int32>::max();
+ if (max_notification_group_count_ != 0) {
+ int32 loaded_groups = 0;
+ auto needed_groups = static_cast<int32>(max_notification_group_count_);
+ do {
+ loaded_groups += load_message_notification_groups_from_database(needed_groups, false);
+ } while (loaded_groups < needed_groups && last_loaded_notification_group_key_.last_notification_date != 0);
+ }
+
+ auto call_notification_group_ids_string = G()->td_db()->get_binlog_pmc()->get("notification_call_group_ids");
+ if (!call_notification_group_ids_string.empty()) {
+ auto call_notification_group_ids = transform(full_split(call_notification_group_ids_string, ','), [](Slice str) {
+ return NotificationGroupId{to_integer_safe<int32>(str).ok()};
+ });
+ VLOG(notifications) << "Load call_notification_group_ids = " << call_notification_group_ids;
+ for (auto &group_id : call_notification_group_ids) {
+ if (!group_id.is_valid()) {
+ continue;
+ }
+ if (group_id.get() > current_notification_group_id_.get()) {
+ LOG(ERROR) << "Fix current notification group identifier from " << current_notification_group_id_ << " to "
+ << group_id;
+ current_notification_group_id_ = group_id;
+ G()->td_db()->get_binlog_pmc()->set("notification_group_id_current",
+ to_string(current_notification_group_id_.get()));
+ }
+ auto it = get_group_force(group_id);
+ if (it != groups_.end()) {
+ LOG(ERROR) << "Have " << it->first << " " << it->second << " as a call notification group";
+ } else {
+ call_notification_group_ids_.push_back(group_id);
+ available_call_notification_group_ids_.insert(group_id);
+ }
+ }
+ }
+
+ auto notification_announcement_ids_string = G()->td_db()->get_binlog_pmc()->get("notification_announcement_ids");
+ if (!notification_announcement_ids_string.empty()) {
+ VLOG(notifications) << "Load announcement ids = " << notification_announcement_ids_string;
+ auto ids = transform(full_split(notification_announcement_ids_string, ','),
+ [](Slice str) { return to_integer_safe<int32>(str).ok(); });
+ CHECK(ids.size() % 2 == 0);
+ bool is_changed = false;
+ auto min_date = G()->unix_time() - ANNOUNCEMENT_ID_CACHE_TIME;
+ for (size_t i = 0; i < ids.size(); i += 2) {
+ auto id = ids[i];
+ auto date = ids[i + 1];
+ if (date < min_date || id == 0) {
+ is_changed = true;
+ continue;
+ }
+ announcement_id_date_.emplace(id, date);
+ }
+ if (is_changed) {
+ save_announcement_ids();
+ }
+ }
+
+ class StateCallback final : public StateManager::Callback {
+ public:
+ explicit StateCallback(ActorId<NotificationManager> parent) : parent_(std::move(parent)) {
+ }
+ bool on_online(bool is_online) final {
+ if (is_online) {
+ send_closure(parent_, &NotificationManager::flush_all_pending_notifications);
+ }
+ return parent_.is_alive();
+ }
+
+ private:
+ ActorId<NotificationManager> parent_;
+ };
+ send_closure(G()->state_manager(), &StateManager::add_callback, make_unique<StateCallback>(actor_id(this)));
+
+ is_inited_ = true;
+ try_send_update_active_notifications();
+}
+
+void NotificationManager::save_announcement_ids() {
+ auto min_date = G()->unix_time() - ANNOUNCEMENT_ID_CACHE_TIME;
+ vector<int32> ids;
+ for (auto &it : announcement_id_date_) {
+ auto id = it.first;
+ auto date = it.second;
+ if (date < min_date) {
+ continue;
+ }
+ ids.push_back(id);
+ ids.push_back(date);
+ }
+
+ VLOG(notifications) << "Save announcement ids " << ids;
+ if (ids.empty()) {
+ G()->td_db()->get_binlog_pmc()->erase("notification_announcement_ids");
+ return;
+ }
+
+ auto notification_announcement_ids_string = implode(transform(ids, [](int32 id) { return to_string(id); }), ',');
+ G()->td_db()->get_binlog_pmc()->set("notification_announcement_ids", notification_announcement_ids_string);
+}
+
+td_api::object_ptr<td_api::updateActiveNotifications> NotificationManager::get_update_active_notifications() const {
+ auto needed_groups = max_notification_group_count_;
+ vector<td_api::object_ptr<td_api::notificationGroup>> groups;
+ for (auto &group : groups_) {
+ if (needed_groups == 0 || group.first.last_notification_date == 0) {
+ break;
+ }
+ needed_groups--;
+
+ vector<td_api::object_ptr<td_api::notification>> notifications;
+ for (auto &notification : reversed(group.second.notifications)) {
+ auto notification_object = get_notification_object(group.first.dialog_id, notification);
+ if (notification_object->type_ != nullptr) {
+ notifications.push_back(std::move(notification_object));
+ }
+ if (notifications.size() == max_notification_group_size_) {
+ break;
+ }
+ }
+ if (!notifications.empty()) {
+ std::reverse(notifications.begin(), notifications.end());
+ groups.push_back(td_api::make_object<td_api::notificationGroup>(
+ group.first.group_id.get(), get_notification_group_type_object(group.second.type),
+ group.first.dialog_id.get(), group.second.total_count, std::move(notifications)));
+ }
+ }
+
+ return td_api::make_object<td_api::updateActiveNotifications>(std::move(groups));
+}
+
+void NotificationManager::tear_down() {
+ parent_.reset();
+}
+
+NotificationManager::NotificationGroups::iterator NotificationManager::add_group(NotificationGroupKey &&group_key,
+ NotificationGroup &&group,
+ const char *source) {
+ if (group.notifications.empty()) {
+ LOG_CHECK(group_key.last_notification_date == 0) << "Trying to add empty " << group_key << " from " << source;
+ }
+ bool is_inserted = group_keys_.emplace(group_key.group_id, group_key).second;
+ CHECK(is_inserted);
+ return groups_.emplace(std::move(group_key), std::move(group)).first;
+}
+
+NotificationManager::NotificationGroups::iterator NotificationManager::get_group(NotificationGroupId group_id) {
+ auto group_keys_it = group_keys_.find(group_id);
+ if (group_keys_it != group_keys_.end()) {
+ return groups_.find(group_keys_it->second);
+ }
+ return groups_.end();
+}
+
+void NotificationManager::load_group_force(NotificationGroupId group_id) {
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ return;
+ }
+
+ auto group_it = get_group_force(group_id, true);
+ CHECK(group_it != groups_.end());
+}
+
+NotificationManager::NotificationGroups::iterator NotificationManager::get_group_force(NotificationGroupId group_id,
+ bool send_update) {
+ auto group_it = get_group(group_id);
+ if (group_it != groups_.end()) {
+ return group_it;
+ }
+
+ if (td::contains(call_notification_group_ids_, group_id)) {
+ return groups_.end();
+ }
+
+ auto message_group = td_->messages_manager_->get_message_notification_group_force(group_id);
+ if (!message_group.dialog_id.is_valid()) {
+ return groups_.end();
+ }
+
+ NotificationGroupKey group_key(group_id, message_group.dialog_id, 0);
+ for (auto &notification : message_group.notifications) {
+ if (notification.date > group_key.last_notification_date) {
+ group_key.last_notification_date = notification.date;
+ }
+ if (notification.notification_id.get() > current_notification_id_.get()) {
+ LOG(ERROR) << "Fix current notification identifier from " << current_notification_id_ << " to "
+ << notification.notification_id;
+ current_notification_id_ = notification.notification_id;
+ G()->td_db()->get_binlog_pmc()->set("notification_id_current", to_string(current_notification_id_.get()));
+ }
+ }
+ if (group_id.get() > current_notification_group_id_.get()) {
+ LOG(ERROR) << "Fix current notification group identifier from " << current_notification_group_id_ << " to "
+ << group_id;
+ current_notification_group_id_ = group_id;
+ G()->td_db()->get_binlog_pmc()->set("notification_group_id_current",
+ to_string(current_notification_group_id_.get()));
+ }
+
+ NotificationGroup group;
+ group.type = message_group.type;
+ group.total_count = message_group.total_count;
+ group.notifications = std::move(message_group.notifications);
+
+ VLOG(notifications) << "Finish to load " << group_id << " of type " << message_group.type << " with total_count "
+ << message_group.total_count << " and notifications " << group.notifications;
+
+ if (send_update && group_key.last_notification_date != 0) {
+ auto last_group_key = get_last_updated_group_key();
+ if (group_key < last_group_key) {
+ if (last_group_key.last_notification_date != 0) {
+ send_remove_group_update(last_group_key, groups_[last_group_key], vector<int32>());
+ }
+ send_add_group_update(group_key, group, "get_group_force");
+ }
+ }
+ return add_group(std::move(group_key), std::move(group), "get_group_force");
+}
+
+void NotificationManager::delete_group(NotificationGroups::iterator &&group_it) {
+ auto erased_count = group_keys_.erase(group_it->first.group_id);
+ CHECK(erased_count > 0);
+ groups_.erase(group_it);
+}
+
+int32 NotificationManager::load_message_notification_groups_from_database(int32 limit, bool send_update) {
+ CHECK(limit > 0);
+ if (last_loaded_notification_group_key_.last_notification_date == 0) {
+ // everything was already loaded
+ return 0;
+ }
+
+ VLOG(notifications) << "Trying to load up to " << limit << " notification groups from database";
+ vector<NotificationGroupKey> group_keys = td_->messages_manager_->get_message_notification_group_keys_from_database(
+ last_loaded_notification_group_key_, limit);
+ last_loaded_notification_group_key_ =
+ group_keys.size() == static_cast<size_t>(limit) ? group_keys.back() : NotificationGroupKey();
+
+ int32 result = 0;
+ for (auto &group_key : group_keys) {
+ auto group_it = get_group_force(group_key.group_id, send_update);
+ LOG_CHECK(group_it != groups_.end()) << call_notification_group_ids_ << " " << group_keys << " "
+ << current_notification_group_id_ << " " << limit;
+ CHECK(group_it->first.dialog_id.is_valid());
+ if (!(last_loaded_notification_group_key_ < group_it->first)) {
+ result++;
+ }
+ }
+ return result;
+}
+
+NotificationId NotificationManager::get_first_notification_id(const NotificationGroup &group) {
+ if (!group.notifications.empty()) {
+ return group.notifications[0].notification_id;
+ }
+ if (!group.pending_notifications.empty()) {
+ return group.pending_notifications[0].notification_id;
+ }
+ return NotificationId();
+}
+
+NotificationId NotificationManager::get_last_notification_id(const NotificationGroup &group) {
+ if (!group.pending_notifications.empty()) {
+ return group.pending_notifications.back().notification_id;
+ }
+ if (!group.notifications.empty()) {
+ return group.notifications.back().notification_id;
+ }
+ return NotificationId();
+}
+
+MessageId NotificationManager::get_first_message_id(const NotificationGroup &group) {
+ // it's fine to return MessageId() if first notification has no message_id, because
+ // non-message notification can't be mixed with message notifications
+ if (!group.notifications.empty()) {
+ return group.notifications[0].type->get_message_id();
+ }
+ if (!group.pending_notifications.empty()) {
+ return group.pending_notifications[0].type->get_message_id();
+ }
+ return MessageId();
+}
+
+MessageId NotificationManager::get_last_message_id(const NotificationGroup &group) {
+ // it's fine to return MessageId() if last notification has no message_id, because
+ // non-message notification can't be mixed with message notifications
+ if (!group.pending_notifications.empty()) {
+ return group.pending_notifications.back().type->get_message_id();
+ }
+ if (!group.notifications.empty()) {
+ return group.notifications.back().type->get_message_id();
+ }
+ return MessageId();
+}
+
+MessageId NotificationManager::get_last_message_id_by_notification_id(const NotificationGroup &group,
+ NotificationId max_notification_id) {
+ for (auto &notification : reversed(group.pending_notifications)) {
+ if (notification.notification_id.get() <= max_notification_id.get()) {
+ auto message_id = notification.type->get_message_id();
+ if (message_id.is_valid()) {
+ return message_id;
+ }
+ }
+ }
+ for (auto &notification : reversed(group.notifications)) {
+ if (notification.notification_id.get() <= max_notification_id.get()) {
+ auto message_id = notification.type->get_message_id();
+ if (message_id.is_valid()) {
+ return message_id;
+ }
+ }
+ }
+ return MessageId();
+}
+
+void NotificationManager::load_message_notifications_from_database(const NotificationGroupKey &group_key,
+ NotificationGroup &group, size_t desired_size) {
+ if (!G()->parameters().use_message_db) {
+ return;
+ }
+ if (group.is_loaded_from_database || group.is_being_loaded_from_database ||
+ group.type == NotificationGroupType::Calls) {
+ return;
+ }
+ if (group.total_count == 0) {
+ return;
+ }
+
+ VLOG(notifications) << "Trying to load up to " << desired_size << " notifications in " << group_key.group_id
+ << " with " << group.notifications.size() << " current notifications";
+
+ group.is_being_loaded_from_database = true;
+
+ CHECK(desired_size > group.notifications.size());
+ size_t limit = desired_size - group.notifications.size();
+ auto first_notification_id = get_first_notification_id(group);
+ auto from_notification_id = first_notification_id.is_valid() ? first_notification_id : NotificationId::max();
+ auto first_message_id = get_first_message_id(group);
+ auto from_message_id = first_message_id.is_valid() ? first_message_id : MessageId::max();
+ send_closure(G()->messages_manager(), &MessagesManager::get_message_notifications_from_database, group_key.dialog_id,
+ group_key.group_id, from_notification_id, from_message_id, static_cast<int32>(limit),
+ PromiseCreator::lambda([actor_id = actor_id(this), group_id = group_key.group_id,
+ limit](Result<vector<Notification>> r_notifications) {
+ send_closure_later(actor_id, &NotificationManager::on_get_message_notifications_from_database,
+ group_id, limit, std::move(r_notifications));
+ }));
+}
+
+void NotificationManager::on_get_message_notifications_from_database(NotificationGroupId group_id, size_t limit,
+ Result<vector<Notification>> r_notifications) {
+ auto group_it = get_group(group_id);
+ CHECK(group_it != groups_.end());
+ auto &group = group_it->second;
+ CHECK(group.is_being_loaded_from_database == true);
+ group.is_being_loaded_from_database = false;
+
+ if (r_notifications.is_error()) {
+ group.is_loaded_from_database = true; // do not try again to load it
+ return;
+ }
+ auto notifications = r_notifications.move_as_ok();
+
+ CHECK(limit > 0);
+ if (notifications.empty()) {
+ group.is_loaded_from_database = true;
+ }
+
+ auto first_notification_id = get_first_notification_id(group);
+ if (first_notification_id.is_valid()) {
+ while (!notifications.empty() && notifications.back().notification_id.get() >= first_notification_id.get()) {
+ // possible if notifications was added after the database request was sent
+ notifications.pop_back();
+ }
+ }
+ auto first_message_id = get_first_message_id(group);
+ if (first_message_id.is_valid()) {
+ while (!notifications.empty() && notifications.back().type->get_message_id() >= first_message_id) {
+ // possible if notifications was added after the database request was sent
+ notifications.pop_back();
+ }
+ }
+
+ add_notifications_to_group_begin(std::move(group_it), std::move(notifications));
+
+ group_it = get_group(group_id);
+ CHECK(group_it != groups_.end());
+ if (max_notification_group_size_ > group_it->second.notifications.size()) {
+ load_message_notifications_from_database(group_it->first, group_it->second, keep_notification_group_size_);
+ }
+}
+
+void NotificationManager::add_notifications_to_group_begin(NotificationGroups::iterator group_it,
+ vector<Notification> notifications) {
+ CHECK(group_it != groups_.end());
+
+ td::remove_if(notifications, [dialog_id = group_it->first.dialog_id](const Notification &notification) {
+ return notification.type->get_notification_type_object(dialog_id) == nullptr;
+ });
+
+ if (notifications.empty()) {
+ return;
+ }
+ VLOG(notifications) << "Add to beginning of " << group_it->first << " of size "
+ << group_it->second.notifications.size() << ' ' << notifications;
+
+ auto group_key = group_it->first;
+ auto final_group_key = group_key;
+ for (auto &notification : notifications) {
+ if (notification.date > final_group_key.last_notification_date) {
+ final_group_key.last_notification_date = notification.date;
+ }
+ }
+ LOG_CHECK(final_group_key.last_notification_date != 0) << final_group_key << ' ' << *group_it << ' ' << notifications;
+
+ bool is_position_changed = final_group_key.last_notification_date != group_key.last_notification_date;
+
+ NotificationGroup group = std::move(group_it->second);
+ if (is_position_changed) {
+ VLOG(notifications) << "Position of notification group is changed from " << group_key << " to " << final_group_key;
+ delete_group(std::move(group_it));
+ }
+
+ auto last_group_key = get_last_updated_group_key();
+ bool was_updated = false;
+ bool is_updated = false;
+ if (is_position_changed) {
+ was_updated = group_key.last_notification_date != 0 && group_key < last_group_key;
+ is_updated = final_group_key.last_notification_date != 0 && final_group_key < last_group_key;
+ } else {
+ CHECK(group_key.last_notification_date != 0);
+ was_updated = is_updated = !(last_group_key < group_key);
+ }
+
+ if (!is_updated) {
+ CHECK(!was_updated);
+ VLOG(notifications) << "There is no need to send updateNotificationGroup in " << group_key
+ << ", because of newer notification groups";
+ group.notifications.insert(group.notifications.begin(), std::make_move_iterator(notifications.begin()),
+ std::make_move_iterator(notifications.end()));
+ } else {
+ if (!was_updated) {
+ if (last_group_key.last_notification_date != 0) {
+ // need to remove last notification group to not exceed max_notification_group_count_
+ send_remove_group_update(last_group_key, groups_[last_group_key], vector<int32>());
+ }
+ send_add_group_update(group_key, group, "add_notifications_to_group_begin");
+ }
+
+ vector<Notification> new_notifications;
+ vector<td_api::object_ptr<td_api::notification>> added_notifications;
+ new_notifications.reserve(notifications.size());
+ added_notifications.reserve(notifications.size());
+ for (auto &notification : notifications) {
+ added_notifications.push_back(get_notification_object(group_key.dialog_id, notification));
+ CHECK(added_notifications.back()->type_ != nullptr);
+ new_notifications.push_back(std::move(notification));
+ }
+ notifications = std::move(new_notifications);
+
+ size_t old_notification_count = group.notifications.size();
+ auto updated_notification_count = old_notification_count < max_notification_group_size_
+ ? max_notification_group_size_ - old_notification_count
+ : 0;
+ if (added_notifications.size() > updated_notification_count) {
+ added_notifications.erase(added_notifications.begin(), added_notifications.end() - updated_notification_count);
+ }
+ auto new_notification_count = old_notification_count < keep_notification_group_size_
+ ? keep_notification_group_size_ - old_notification_count
+ : 0;
+ if (new_notification_count > notifications.size()) {
+ new_notification_count = notifications.size();
+ }
+ if (new_notification_count != 0) {
+ VLOG(notifications) << "Add " << new_notification_count << " notifications to " << group_key.group_id
+ << " with current size " << group.notifications.size();
+ group.notifications.insert(group.notifications.begin(),
+ std::make_move_iterator(notifications.end() - new_notification_count),
+ std::make_move_iterator(notifications.end()));
+ }
+
+ if (!added_notifications.empty()) {
+ add_update_notification_group(td_api::make_object<td_api::updateNotificationGroup>(
+ group_key.group_id.get(), get_notification_group_type_object(group.type), group_key.dialog_id.get(), 0, 0,
+ group.total_count, std::move(added_notifications), vector<int32>()));
+ }
+ }
+
+ if (is_position_changed) {
+ add_group(std::move(final_group_key), std::move(group), "add_notifications_to_group_begin");
+ } else {
+ CHECK(group_it->first.last_notification_date == 0 || !group.notifications.empty());
+ group_it->second = std::move(group);
+ }
+}
+
+size_t NotificationManager::get_max_notification_group_size() const {
+ return max_notification_group_size_;
+}
+
+NotificationId NotificationManager::get_max_notification_id() const {
+ return current_notification_id_;
+}
+
+NotificationId NotificationManager::get_next_notification_id() {
+ if (is_disabled()) {
+ return NotificationId();
+ }
+ if (current_notification_id_.get() == std::numeric_limits<int32>::max()) {
+ LOG(ERROR) << "Notification identifier overflowed";
+ return NotificationId();
+ }
+
+ current_notification_id_ = NotificationId(current_notification_id_.get() + 1);
+ G()->td_db()->get_binlog_pmc()->set("notification_id_current", to_string(current_notification_id_.get()));
+ return current_notification_id_;
+}
+
+NotificationGroupId NotificationManager::get_next_notification_group_id() {
+ if (is_disabled()) {
+ return NotificationGroupId();
+ }
+ if (current_notification_group_id_.get() == std::numeric_limits<int32>::max()) {
+ LOG(ERROR) << "Notification group identifier overflowed";
+ return NotificationGroupId();
+ }
+
+ current_notification_group_id_ = NotificationGroupId(current_notification_group_id_.get() + 1);
+ G()->td_db()->get_binlog_pmc()->set("notification_group_id_current", to_string(current_notification_group_id_.get()));
+ return current_notification_group_id_;
+}
+
+void NotificationManager::try_reuse_notification_group_id(NotificationGroupId group_id) {
+ if (is_disabled()) {
+ return;
+ }
+ if (!group_id.is_valid()) {
+ return;
+ }
+
+ VLOG(notifications) << "Trying to reuse " << group_id;
+ if (group_id != current_notification_group_id_) {
+ // may be implemented in the future
+ return;
+ }
+
+ auto group_it = get_group(group_id);
+ if (group_it != groups_.end()) {
+ LOG_CHECK(group_it->first.last_notification_date == 0 && group_it->second.total_count == 0)
+ << running_get_difference_ << " " << delayed_notification_update_count_ << " "
+ << unreceived_notification_update_count_ << " " << pending_updates_[group_id.get()].size() << " "
+ << group_it->first << " " << group_it->second;
+ CHECK(group_it->second.notifications.empty());
+ CHECK(group_it->second.pending_notifications.empty());
+ CHECK(!group_it->second.is_being_loaded_from_database);
+ delete_group(std::move(group_it));
+
+ CHECK(running_get_chat_difference_.count(group_id.get()) == 0);
+
+ flush_pending_notifications_timeout_.cancel_timeout(group_id.get());
+ flush_pending_updates_timeout_.cancel_timeout(group_id.get());
+ if (pending_updates_.erase(group_id.get()) == 1) {
+ on_delayed_notification_update_count_changed(-1, group_id.get(), "try_reuse_notification_group_id");
+ }
+ }
+
+ current_notification_group_id_ = NotificationGroupId(current_notification_group_id_.get() - 1);
+ G()->td_db()->get_binlog_pmc()->set("notification_group_id_current", to_string(current_notification_group_id_.get()));
+}
+
+NotificationGroupKey NotificationManager::get_last_updated_group_key() const {
+ size_t left = max_notification_group_count_;
+ auto it = groups_.begin();
+ while (it != groups_.end() && left > 1) {
+ ++it;
+ left--;
+ }
+ if (it == groups_.end()) {
+ return NotificationGroupKey();
+ }
+ return it->first;
+}
+
+int32 NotificationManager::get_notification_delay_ms(DialogId dialog_id, const PendingNotification &notification,
+ int32 min_delay_ms) const {
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ return MIN_NOTIFICATION_DELAY_MS; // there is no reason to delay notifications in secret chats
+ }
+ if (!notification.type->can_be_delayed()) {
+ return MIN_NOTIFICATION_DELAY_MS;
+ }
+
+ auto delay_ms = [&] {
+ auto online_info = td_->contacts_manager_->get_my_online_status();
+ if (!online_info.is_online_local && online_info.is_online_remote) {
+ // If we are offline, but online from some other client, then delay notification
+ // for 'notification_cloud_delay' seconds.
+ return notification_cloud_delay_ms_;
+ }
+
+ if (!online_info.is_online_local &&
+ online_info.was_online_remote > max(static_cast<double>(online_info.was_online_local),
+ G()->server_time_cached() - online_cloud_timeout_ms_ * 1e-3)) {
+ // If we are offline, but was online from some other client in last 'online_cloud_timeout' seconds
+ // after we had gone offline, then delay notification for 'notification_cloud_delay' seconds.
+ return notification_cloud_delay_ms_;
+ }
+
+ if (online_info.is_online_remote) {
+ // If some other client is online, then delay notification for 'notification_default_delay' seconds.
+ return notification_default_delay_ms_;
+ }
+
+ // otherwise send update without additional delay
+ return 0;
+ }();
+
+ auto passed_time_ms =
+ static_cast<int32>(clamp(G()->server_time_cached() - notification.date - 1, 0.0, 1000000.0) * 1000);
+ return max(max(min_delay_ms, delay_ms) - passed_time_ms, MIN_NOTIFICATION_DELAY_MS);
+}
+
+void NotificationManager::add_notification(NotificationGroupId group_id, NotificationGroupType group_type,
+ DialogId dialog_id, int32 date, DialogId notification_settings_dialog_id,
+ bool disable_notification, int64 ringtone_id, int32 min_delay_ms,
+ NotificationId notification_id, unique_ptr<NotificationType> type,
+ const char *source) {
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ on_notification_removed(notification_id);
+ return;
+ }
+
+ CHECK(group_id.is_valid());
+ CHECK(dialog_id.is_valid());
+ CHECK(notification_settings_dialog_id.is_valid());
+ LOG_CHECK(notification_id.is_valid()) << notification_id << " " << source;
+ CHECK(type != nullptr);
+ VLOG(notifications) << "Add " << notification_id << " to " << group_id << " of type " << group_type << " in "
+ << dialog_id << " with settings from " << notification_settings_dialog_id
+ << (ringtone_id == 0 ? " silently" : " with sound") << ": " << *type;
+
+ if (!type->is_temporary()) {
+ remove_temporary_notifications(group_id, "add_notification");
+ }
+
+ auto group_it = get_group_force(group_id);
+ if (group_it == groups_.end()) {
+ group_it = add_group(NotificationGroupKey(group_id, dialog_id, 0), NotificationGroup(), "add_notification");
+ }
+ if (group_it->second.notifications.empty() && group_it->second.pending_notifications.empty()) {
+ group_it->second.type = group_type;
+ }
+ CHECK(group_it->second.type == group_type);
+
+ NotificationGroup &group = group_it->second;
+ if (notification_id.get() <= get_last_notification_id(group).get()) {
+ LOG(ERROR) << "Failed to add " << notification_id << " to " << group_id << " of type " << group_type << " in "
+ << dialog_id << ", because have already added " << get_last_notification_id(group);
+ on_notification_removed(notification_id);
+ return;
+ }
+ auto message_id = type->get_message_id();
+ if (message_id.is_valid() && message_id <= get_last_message_id(group)) {
+ LOG(ERROR) << "Failed to add " << notification_id << " of type " << *type << " to " << group_id << " of type "
+ << group_type << " in " << dialog_id << ", because have already added notification about "
+ << get_last_message_id(group);
+ on_notification_removed(notification_id);
+ return;
+ }
+
+ PendingNotification notification;
+ notification.date = date;
+ notification.settings_dialog_id = notification_settings_dialog_id;
+ notification.disable_notification = disable_notification;
+ notification.ringtone_id = disable_notification ? 0 : ringtone_id;
+ notification.notification_id = notification_id;
+ notification.type = std::move(type);
+
+ auto delay_ms = get_notification_delay_ms(dialog_id, notification, min_delay_ms);
+ VLOG(notifications) << "Delay " << notification_id << " for " << delay_ms << " milliseconds";
+ auto flush_time = delay_ms * 0.001 + Time::now();
+
+ if (group.pending_notifications_flush_time == 0 || flush_time < group.pending_notifications_flush_time) {
+ group.pending_notifications_flush_time = flush_time;
+ flush_pending_notifications_timeout_.set_timeout_at(group_id.get(), group.pending_notifications_flush_time);
+ }
+ if (group.pending_notifications.empty()) {
+ on_delayed_notification_update_count_changed(1, group_id.get(), source);
+ }
+ group.pending_notifications.push_back(std::move(notification));
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const NotificationManager::NotificationUpdate &update) {
+ if (update.update == nullptr) {
+ return string_builder << "null";
+ }
+ switch (update.update->get_id()) {
+ case td_api::updateNotification::ID: {
+ auto p = static_cast<const td_api::updateNotification *>(update.update);
+ return string_builder << "update[" << NotificationId(p->notification_->id_) << " from "
+ << NotificationGroupId(p->notification_group_id_) << ']';
+ }
+ case td_api::updateNotificationGroup::ID: {
+ auto p = static_cast<const td_api::updateNotificationGroup *>(update.update);
+ vector<int32> added_notification_ids;
+ for (auto &notification : p->added_notifications_) {
+ added_notification_ids.push_back(notification->id_);
+ }
+
+ return string_builder << "update[" << NotificationGroupId(p->notification_group_id_) << " of type "
+ << get_notification_group_type(p->type_) << " from " << DialogId(p->chat_id_)
+ << " with settings from " << DialogId(p->notification_settings_chat_id_)
+ << (p->notification_sound_id_ == 0 ? " silently" : " with sound")
+ << "; total_count = " << p->total_count_ << ", add " << added_notification_ids
+ << ", remove " << p->removed_notification_ids_;
+ }
+ default:
+ UNREACHABLE();
+ return string_builder << "unknown";
+ }
+}
+
+NotificationManager::NotificationUpdate NotificationManager::as_notification_update(const td_api::Update *update) {
+ return NotificationUpdate{update};
+}
+
+void NotificationManager::add_update(int32 group_id, td_api::object_ptr<td_api::Update> update) {
+ if (!is_binlog_processed_ || !is_inited_) {
+ return;
+ }
+ VLOG(notifications) << "Add " << as_notification_update(update.get());
+ auto &updates = pending_updates_[group_id];
+ if (updates.empty()) {
+ on_delayed_notification_update_count_changed(1, group_id, "add_update");
+ }
+ updates.push_back(std::move(update));
+ if (!running_get_difference_ && running_get_chat_difference_.count(group_id) == 0) {
+ flush_pending_updates_timeout_.add_timeout_in(group_id, MIN_UPDATE_DELAY_MS * 1e-3);
+ } else {
+ flush_pending_updates_timeout_.set_timeout_in(group_id, MAX_UPDATE_DELAY_MS * 1e-3);
+ }
+}
+
+void NotificationManager::add_update_notification_group(td_api::object_ptr<td_api::updateNotificationGroup> update) {
+ auto group_id = update->notification_group_id_;
+ if (update->notification_settings_chat_id_ == 0) {
+ update->notification_settings_chat_id_ = update->chat_id_;
+ }
+ if (!update->added_notifications_.empty() && !update->removed_notification_ids_.empty()) {
+ // just in case
+ td::remove_if(update->added_notifications_, [&](const td_api::object_ptr<td_api::notification> &notification) {
+ CHECK(notification != nullptr);
+ if (td::contains(update->removed_notification_ids_, notification->id_)) {
+ LOG(ERROR) << "Have the same notification as added and removed";
+ return true;
+ }
+ return false;
+ });
+ }
+ add_update(group_id, std::move(update));
+}
+
+void NotificationManager::add_update_notification(NotificationGroupId notification_group_id, DialogId dialog_id,
+ const Notification &notification) {
+ auto notification_object = get_notification_object(dialog_id, notification);
+ if (notification_object->type_ == nullptr) {
+ return;
+ }
+
+ add_update(notification_group_id.get(), td_api::make_object<td_api::updateNotification>(
+ notification_group_id.get(), std::move(notification_object)));
+ if (!notification.type->can_be_delayed()) {
+ force_flush_pending_updates(notification_group_id, "add_update_notification");
+ }
+}
+
+void NotificationManager::flush_pending_updates(int32 group_id, const char *source) {
+ // no check for G()->close_flag() to flush pending notifications even while closing
+ auto it = pending_updates_.find(group_id);
+ if (it == pending_updates_.end()) {
+ return;
+ }
+
+ auto updates = std::move(it->second);
+ pending_updates_.erase(it);
+
+ if (is_destroyed_) {
+ return;
+ }
+
+ VLOG(notifications) << "Send " << updates.size() << " pending updates in " << NotificationGroupId(group_id)
+ << " from " << source;
+ for (auto &update : updates) {
+ VLOG(notifications) << "Have " << as_notification_update(update.get());
+ }
+
+ td::remove_if(updates, [](auto &update) { return update == nullptr; });
+
+ // if a notification was added, then deleted and then re-added we need to keep
+ // first addition, because it can be with sound,
+ // deletion, because number of notification should never exceed max_notification_group_size_,
+ // and second addition, because we has kept the deletion
+
+ // calculate last state of all notifications
+ FlatHashSet<int32> added_notification_ids;
+ FlatHashSet<int32> edited_notification_ids;
+ FlatHashSet<int32> removed_notification_ids;
+ for (auto &update : updates) {
+ CHECK(update != nullptr);
+ if (update->get_id() == td_api::updateNotificationGroup::ID) {
+ auto update_ptr = static_cast<td_api::updateNotificationGroup *>(update.get());
+ for (auto &notification : update_ptr->added_notifications_) {
+ auto notification_id = notification->id_;
+ CHECK(notification_id != 0);
+ bool is_inserted = added_notification_ids.insert(notification_id).second;
+ CHECK(is_inserted); // there must be no additions after addition
+ CHECK(edited_notification_ids.count(notification_id) == 0); // there must be no additions after edit
+ removed_notification_ids.erase(notification_id);
+ }
+ for (auto &notification_id : update_ptr->removed_notification_ids_) {
+ CHECK(notification_id != 0);
+ added_notification_ids.erase(notification_id);
+ edited_notification_ids.erase(notification_id);
+ if (!removed_notification_ids.insert(notification_id).second) {
+ // sometimes there can be deletion of notification without previous addition, because the notification
+ // has already been deleted at the time of addition and get_notification_object_type was nullptr
+ VLOG(notifications) << "Remove duplicated deletion of " << notification_id;
+ notification_id = 0;
+ }
+ }
+ td::remove_if(update_ptr->removed_notification_ids_, [](auto &notification_id) { return notification_id == 0; });
+ } else {
+ CHECK(update->get_id() == td_api::updateNotification::ID);
+ auto update_ptr = static_cast<td_api::updateNotification *>(update.get());
+ auto notification_id = update_ptr->notification_->id_;
+ CHECK(notification_id != 0);
+ CHECK(removed_notification_ids.count(notification_id) == 0); // there must be no edits of deleted notifications
+ added_notification_ids.erase(notification_id);
+ edited_notification_ids.insert(notification_id);
+ }
+ }
+
+ // we need to keep only additions of notifications from added_notification_ids/edited_notification_ids and
+ // all edits of notifications from edited_notification_ids
+ // deletions of a notification can be removed, only if the addition of the notification has already been deleted
+ // deletions of all unkept notifications can be moved to the first updateNotificationGroup
+ // after that at every moment there are no more active notifications than in the last moment,
+ // so left deletions after add/edit can be safely removed and following additions can be treated as edits
+ // we still need to keep deletions coming first, because we can't have 2 consequent additions
+ // from all additions of the same notification, we need to preserve the first, because it can be with sound,
+ // all other additions and edits can be merged to the first addition/edit
+ // i.e. in edit+delete+add chain we want to remove deletion and merge addition to the edit
+
+ auto group_key = group_keys_[NotificationGroupId(group_id)];
+ bool is_hidden = group_key.last_notification_date == 0 || get_last_updated_group_key() < group_key;
+ bool is_changed = true;
+ while (is_changed) {
+ is_changed = false;
+
+ size_t cur_pos = 0;
+ FlatHashMap<int32, size_t> first_add_notification_pos;
+ FlatHashMap<int32, size_t> first_edit_notification_pos;
+ FlatHashSet<int32> can_be_deleted_notification_ids;
+ vector<int32> moved_deleted_notification_ids;
+ size_t first_notification_group_pos = 0;
+
+ for (auto &update : updates) {
+ cur_pos++;
+
+ CHECK(update != nullptr);
+ if (update->get_id() == td_api::updateNotificationGroup::ID) {
+ auto update_ptr = static_cast<td_api::updateNotificationGroup *>(update.get());
+
+ for (auto &notification : update_ptr->added_notifications_) {
+ auto notification_id = notification->id_;
+ CHECK(notification_id != 0);
+ bool is_needed =
+ added_notification_ids.count(notification_id) != 0 || edited_notification_ids.count(notification_id) != 0;
+ if (!is_needed) {
+ VLOG(notifications) << "Remove unneeded addition of " << notification_id << " in update " << cur_pos;
+ can_be_deleted_notification_ids.insert(notification_id);
+ notification = nullptr;
+ is_changed = true;
+ continue;
+ }
+
+ auto edit_it = first_edit_notification_pos.find(notification_id);
+ if (edit_it != first_edit_notification_pos.end()) {
+ VLOG(notifications) << "Move addition of " << notification_id << " in update " << cur_pos
+ << " to edit in update " << edit_it->second;
+ CHECK(edit_it->second < cur_pos);
+ auto previous_update_ptr = static_cast<td_api::updateNotification *>(updates[edit_it->second - 1].get());
+ CHECK(previous_update_ptr->notification_->id_ == notification_id);
+ previous_update_ptr->notification_->type_ = std::move(notification->type_);
+ is_changed = true;
+ notification = nullptr;
+ continue;
+ }
+ auto add_it = first_add_notification_pos.find(notification_id);
+ if (add_it != first_add_notification_pos.end()) {
+ VLOG(notifications) << "Move addition of " << notification_id << " in update " << cur_pos << " to update "
+ << add_it->second;
+ CHECK(add_it->second < cur_pos);
+ auto previous_update_ptr =
+ static_cast<td_api::updateNotificationGroup *>(updates[add_it->second - 1].get());
+ bool is_found = false;
+ for (auto &prev_notification : previous_update_ptr->added_notifications_) {
+ if (prev_notification->id_ == notification_id) {
+ prev_notification->type_ = std::move(notification->type_);
+ is_found = true;
+ break;
+ }
+ }
+ CHECK(is_found);
+ is_changed = true;
+ notification = nullptr;
+ continue;
+ }
+
+ // it is a first addition/edit of needed notification
+ first_add_notification_pos[notification_id] = cur_pos;
+ }
+ td::remove_if(update_ptr->added_notifications_, [](auto &notification) { return notification == nullptr; });
+ if (update_ptr->added_notifications_.empty() && update_ptr->notification_sound_id_ != 0) {
+ update_ptr->notification_sound_id_ = 0;
+ is_changed = true;
+ }
+
+ for (auto &notification_id : update_ptr->removed_notification_ids_) {
+ bool is_needed =
+ added_notification_ids.count(notification_id) != 0 || edited_notification_ids.count(notification_id) != 0;
+ if (can_be_deleted_notification_ids.count(notification_id) == 1) {
+ CHECK(!is_needed);
+ VLOG(notifications) << "Remove unneeded deletion of " << notification_id << " in update " << cur_pos;
+ notification_id = 0;
+ is_changed = true;
+ continue;
+ }
+ if (!is_needed) {
+ if (first_notification_group_pos != 0) {
+ VLOG(notifications) << "Need to keep deletion of " << notification_id << " in update " << cur_pos
+ << ", but can move it to the first updateNotificationGroup at pos "
+ << first_notification_group_pos;
+ moved_deleted_notification_ids.push_back(notification_id);
+ notification_id = 0;
+ is_changed = true;
+ }
+ continue;
+ }
+
+ if (first_add_notification_pos.count(notification_id) != 0 ||
+ first_edit_notification_pos.count(notification_id) != 0) {
+ // the notification will be re-added, and we will be able to merge the addition with previous update, so we can just remove the deletion
+ VLOG(notifications) << "Remove unneeded deletion in update " << cur_pos;
+ notification_id = 0;
+ is_changed = true;
+ continue;
+ }
+
+ // we need to keep the deletion, because otherwise we will have 2 consequent additions
+ }
+ td::remove_if(update_ptr->removed_notification_ids_,
+ [](auto &notification_id) { return notification_id == 0; });
+
+ if (update_ptr->removed_notification_ids_.empty() && update_ptr->added_notifications_.empty()) {
+ for (size_t i = cur_pos - 1; i > 0; i--) {
+ if (updates[i - 1] != nullptr && updates[i - 1]->get_id() == td_api::updateNotificationGroup::ID) {
+ VLOG(notifications) << "Move total_count from empty update " << cur_pos << " to update " << i;
+ auto previous_update_ptr = static_cast<td_api::updateNotificationGroup *>(updates[i - 1].get());
+ previous_update_ptr->type_ = std::move(update_ptr->type_);
+ previous_update_ptr->total_count_ = update_ptr->total_count_;
+ is_changed = true;
+ update = nullptr;
+ break;
+ }
+ }
+ if (update != nullptr && cur_pos == 1) {
+ bool is_empty_group =
+ added_notification_ids.empty() && edited_notification_ids.empty() && update_ptr->total_count_ == 0;
+ if (updates.size() > 1 || (is_hidden && !is_empty_group)) {
+ VLOG(notifications) << "Remove empty update " << cur_pos;
+ CHECK(moved_deleted_notification_ids.empty());
+ is_changed = true;
+ update = nullptr;
+ }
+ }
+ }
+
+ if (first_notification_group_pos == 0 && update != nullptr) {
+ first_notification_group_pos = cur_pos;
+ }
+ } else {
+ CHECK(update->get_id() == td_api::updateNotification::ID);
+ auto update_ptr = static_cast<td_api::updateNotification *>(update.get());
+ auto notification_id = update_ptr->notification_->id_;
+ bool is_needed =
+ added_notification_ids.count(notification_id) != 0 || edited_notification_ids.count(notification_id) != 0;
+ if (!is_needed) {
+ VLOG(notifications) << "Remove unneeded update " << cur_pos;
+ is_changed = true;
+ update = nullptr;
+ continue;
+ }
+ auto edit_it = first_edit_notification_pos.find(notification_id);
+ if (edit_it != first_edit_notification_pos.end()) {
+ VLOG(notifications) << "Move edit of " << notification_id << " in update " << cur_pos << " to update "
+ << edit_it->second;
+ CHECK(edit_it->second < cur_pos);
+ auto previous_update_ptr = static_cast<td_api::updateNotification *>(updates[edit_it->second - 1].get());
+ CHECK(previous_update_ptr->notification_->id_ == notification_id);
+ previous_update_ptr->notification_->type_ = std::move(update_ptr->notification_->type_);
+ is_changed = true;
+ update = nullptr;
+ continue;
+ }
+ auto add_it = first_add_notification_pos.find(notification_id);
+ if (add_it != first_add_notification_pos.end()) {
+ VLOG(notifications) << "Move edit of " << notification_id << " in update " << cur_pos << " to update "
+ << add_it->second;
+ CHECK(add_it->second < cur_pos);
+ auto previous_update_ptr = static_cast<td_api::updateNotificationGroup *>(updates[add_it->second - 1].get());
+ bool is_found = false;
+ for (auto &notification : previous_update_ptr->added_notifications_) {
+ if (notification->id_ == notification_id) {
+ notification->type_ = std::move(update_ptr->notification_->type_);
+ is_found = true;
+ break;
+ }
+ }
+ CHECK(is_found);
+ is_changed = true;
+ update = nullptr;
+ continue;
+ }
+
+ // it is a first addition/edit of needed notification
+ first_edit_notification_pos[notification_id] = cur_pos;
+ }
+ }
+ if (!moved_deleted_notification_ids.empty()) {
+ CHECK(first_notification_group_pos != 0);
+ auto &update = updates[first_notification_group_pos - 1];
+ CHECK(update->get_id() == td_api::updateNotificationGroup::ID);
+ auto update_ptr = static_cast<td_api::updateNotificationGroup *>(update.get());
+ append(update_ptr->removed_notification_ids_, std::move(moved_deleted_notification_ids));
+ auto old_size = update_ptr->removed_notification_ids_.size();
+ td::unique(update_ptr->removed_notification_ids_);
+ CHECK(old_size == update_ptr->removed_notification_ids_.size());
+ }
+
+ td::remove_if(updates, [](auto &update) { return update == nullptr; });
+ if (updates.empty()) {
+ VLOG(notifications) << "There are no updates to send in " << NotificationGroupId(group_id);
+ break;
+ }
+
+ auto has_common_notifications = [](const vector<td_api::object_ptr<td_api::notification>> &notifications,
+ const vector<int32> &notification_ids) {
+ for (auto &notification : notifications) {
+ if (td::contains(notification_ids, notification->id_)) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ size_t last_update_pos = 0;
+ for (size_t i = 1; i < updates.size(); i++) {
+ if (updates[last_update_pos]->get_id() == td_api::updateNotificationGroup::ID &&
+ updates[i]->get_id() == td_api::updateNotificationGroup::ID) {
+ auto last_update_ptr = static_cast<td_api::updateNotificationGroup *>(updates[last_update_pos].get());
+ auto update_ptr = static_cast<td_api::updateNotificationGroup *>(updates[i].get());
+ if ((last_update_ptr->notification_settings_chat_id_ == update_ptr->notification_settings_chat_id_ ||
+ last_update_ptr->added_notifications_.empty()) &&
+ !has_common_notifications(last_update_ptr->added_notifications_, update_ptr->removed_notification_ids_) &&
+ !has_common_notifications(update_ptr->added_notifications_, last_update_ptr->removed_notification_ids_) &&
+ last_update_ptr->notification_sound_id_ == update_ptr->notification_sound_id_) {
+ // combine updates
+ VLOG(notifications) << "Combine " << as_notification_update(last_update_ptr) << " and "
+ << as_notification_update(update_ptr);
+ CHECK(last_update_ptr->notification_group_id_ == update_ptr->notification_group_id_);
+ CHECK(last_update_ptr->chat_id_ == update_ptr->chat_id_);
+ last_update_ptr->notification_settings_chat_id_ = update_ptr->notification_settings_chat_id_;
+ last_update_ptr->type_ = std::move(update_ptr->type_);
+ last_update_ptr->total_count_ = update_ptr->total_count_;
+ append(last_update_ptr->added_notifications_, std::move(update_ptr->added_notifications_));
+ append(last_update_ptr->removed_notification_ids_, std::move(update_ptr->removed_notification_ids_));
+ updates[i] = nullptr;
+ is_changed = true;
+ continue;
+ }
+ }
+ last_update_pos++;
+ if (last_update_pos != i) {
+ updates[last_update_pos] = std::move(updates[i]);
+ }
+ }
+ updates.resize(last_update_pos + 1);
+ }
+
+ for (auto &update : updates) {
+ CHECK(update != nullptr);
+ if (update->get_id() == td_api::updateNotificationGroup::ID) {
+ auto update_ptr = static_cast<td_api::updateNotificationGroup *>(update.get());
+ std::sort(update_ptr->added_notifications_.begin(), update_ptr->added_notifications_.end(),
+ [](const auto &lhs, const auto &rhs) { return lhs->id_ < rhs->id_; });
+ std::sort(update_ptr->removed_notification_ids_.begin(), update_ptr->removed_notification_ids_.end());
+ }
+ VLOG(notifications) << "Send " << as_notification_update(update.get());
+ send_closure(G()->td(), &Td::send_update, std::move(update));
+ }
+ on_delayed_notification_update_count_changed(-1, group_id, "flush_pending_updates");
+
+ auto group_it = get_group_force(NotificationGroupId(group_id));
+ CHECK(group_it != groups_.end());
+ NotificationGroup &group = group_it->second;
+ for (auto &notification : group.notifications) {
+ on_notification_processed(notification.notification_id);
+ }
+}
+
+void NotificationManager::force_flush_pending_updates(NotificationGroupId group_id, const char *source) {
+ flush_pending_updates_timeout_.cancel_timeout(group_id.get());
+ flush_pending_updates(group_id.get(), source);
+}
+
+void NotificationManager::flush_all_pending_updates(bool include_delayed_chats, const char *source) {
+ VLOG(notifications) << "Flush all pending notification updates "
+ << (include_delayed_chats ? "with delayed chats " : "") << "from " << source;
+ if (!include_delayed_chats && running_get_difference_) {
+ return;
+ }
+
+ vector<NotificationGroupKey> ready_group_keys;
+ for (const auto &it : pending_updates_) {
+ if (include_delayed_chats || running_get_chat_difference_.count(it.first) == 0) {
+ auto group_it = get_group(NotificationGroupId(it.first));
+ CHECK(group_it != groups_.end());
+ ready_group_keys.push_back(group_it->first);
+ }
+ }
+
+ // flush groups in reverse order to not exceed max_notification_group_count_
+ VLOG(notifications) << "Flush pending updates in " << ready_group_keys.size() << " notification groups";
+ std::sort(ready_group_keys.begin(), ready_group_keys.end());
+ for (const auto &group_key : reversed(ready_group_keys)) {
+ force_flush_pending_updates(group_key.group_id, "flush_all_pending_updates");
+ }
+ if (include_delayed_chats) {
+ CHECK(pending_updates_.empty());
+ }
+}
+
+bool NotificationManager::do_flush_pending_notifications(NotificationGroupKey &group_key, NotificationGroup &group,
+ vector<PendingNotification> &pending_notifications) {
+ // no check for G()->close_flag() to flush pending notifications even while closing
+ if (pending_notifications.empty()) {
+ return false;
+ }
+
+ VLOG(notifications) << "Do flush " << pending_notifications.size() << " pending notifications in " << group_key
+ << " with known " << group.notifications.size() << " from total of " << group.total_count
+ << " notifications";
+
+ size_t old_notification_count = group.notifications.size();
+ size_t shown_notification_count = min(old_notification_count, max_notification_group_size_);
+
+ bool force_update = false;
+ vector<td_api::object_ptr<td_api::notification>> added_notifications;
+ added_notifications.reserve(pending_notifications.size());
+ for (auto &pending_notification : pending_notifications) {
+ Notification notification(pending_notification.notification_id, pending_notification.date,
+ pending_notification.disable_notification, std::move(pending_notification.type));
+ added_notifications.push_back(get_notification_object(group_key.dialog_id, notification));
+ CHECK(added_notifications.back()->type_ != nullptr);
+
+ if (!notification.type->can_be_delayed()) {
+ force_update = true;
+ }
+ group.notifications.push_back(std::move(notification));
+ }
+ group.total_count += narrow_cast<int32>(added_notifications.size());
+ if (added_notifications.size() > max_notification_group_size_) {
+ added_notifications.erase(added_notifications.begin(), added_notifications.end() - max_notification_group_size_);
+ }
+
+ vector<int32> removed_notification_ids;
+ if (shown_notification_count + added_notifications.size() > max_notification_group_size_) {
+ auto removed_notification_count =
+ shown_notification_count + added_notifications.size() - max_notification_group_size_;
+ removed_notification_ids.reserve(removed_notification_count);
+ for (size_t i = 0; i < removed_notification_count; i++) {
+ removed_notification_ids.push_back(
+ group.notifications[old_notification_count - shown_notification_count + i].notification_id.get());
+ }
+ }
+
+ if (!added_notifications.empty()) {
+ add_update_notification_group(td_api::make_object<td_api::updateNotificationGroup>(
+ group_key.group_id.get(), get_notification_group_type_object(group.type), group_key.dialog_id.get(),
+ pending_notifications[0].settings_dialog_id.get(), pending_notifications[0].ringtone_id, group.total_count,
+ std::move(added_notifications), std::move(removed_notification_ids)));
+ } else {
+ CHECK(removed_notification_ids.empty());
+ }
+ pending_notifications.clear();
+ return force_update;
+}
+
+td_api::object_ptr<td_api::updateNotificationGroup> NotificationManager::get_remove_group_update(
+ const NotificationGroupKey &group_key, const NotificationGroup &group,
+ vector<int32> &&removed_notification_ids) const {
+ auto total_size = group.notifications.size();
+ CHECK(removed_notification_ids.size() <= max_notification_group_size_);
+ auto removed_size = min(total_size, max_notification_group_size_ - removed_notification_ids.size());
+ removed_notification_ids.reserve(removed_size + removed_notification_ids.size());
+ for (size_t i = total_size - removed_size; i < total_size; i++) {
+ removed_notification_ids.push_back(group.notifications[i].notification_id.get());
+ }
+
+ if (removed_notification_ids.empty()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::updateNotificationGroup>(
+ group_key.group_id.get(), get_notification_group_type_object(group.type), group_key.dialog_id.get(),
+ group_key.dialog_id.get(), 0, group.total_count, vector<td_api::object_ptr<td_api::notification>>(),
+ std::move(removed_notification_ids));
+}
+
+void NotificationManager::send_remove_group_update(const NotificationGroupKey &group_key,
+ const NotificationGroup &group,
+ vector<int32> &&removed_notification_ids) {
+ VLOG(notifications) << "Remove " << group_key.group_id;
+ auto update = get_remove_group_update(group_key, group, std::move(removed_notification_ids));
+ if (update != nullptr) {
+ add_update_notification_group(std::move(update));
+ }
+}
+
+void NotificationManager::send_add_group_update(const NotificationGroupKey &group_key, const NotificationGroup &group,
+ const char *source) {
+ VLOG(notifications) << "Add " << group_key.group_id << " from " << source;
+ auto total_size = group.notifications.size();
+ auto added_size = min(total_size, max_notification_group_size_);
+ vector<td_api::object_ptr<td_api::notification>> added_notifications;
+ added_notifications.reserve(added_size);
+ for (size_t i = total_size - added_size; i < total_size; i++) {
+ added_notifications.push_back(get_notification_object(group_key.dialog_id, group.notifications[i]));
+ if (added_notifications.back()->type_ == nullptr) {
+ added_notifications.pop_back();
+ }
+ }
+
+ if (!added_notifications.empty()) {
+ add_update_notification_group(td_api::make_object<td_api::updateNotificationGroup>(
+ group_key.group_id.get(), get_notification_group_type_object(group.type), group_key.dialog_id.get(), 0, 0,
+ group.total_count, std::move(added_notifications), vector<int32>()));
+ }
+}
+
+void NotificationManager::flush_pending_notifications(NotificationGroupId group_id) {
+ auto group_it = get_group(group_id);
+ if (group_it == groups_.end()) {
+ return;
+ }
+
+ td::remove_if(group_it->second.pending_notifications,
+ [dialog_id = group_it->first.dialog_id](const PendingNotification &pending_notification) {
+ return pending_notification.type->get_notification_type_object(dialog_id) == nullptr;
+ });
+
+ if (group_it->second.pending_notifications.empty()) {
+ return;
+ }
+
+ auto group_key = group_it->first;
+ auto group = std::move(group_it->second);
+
+ delete_group(std::move(group_it));
+
+ auto final_group_key = group_key;
+ for (auto &pending_notification : group.pending_notifications) {
+ if (pending_notification.date >= final_group_key.last_notification_date) {
+ final_group_key.last_notification_date = pending_notification.date;
+ }
+ }
+ CHECK(final_group_key.last_notification_date != 0);
+
+ VLOG(notifications) << "Flush pending notifications in " << group_key << " up to "
+ << final_group_key.last_notification_date;
+
+ auto last_group_key = get_last_updated_group_key();
+ bool was_updated = group_key.last_notification_date != 0 && group_key < last_group_key;
+ bool is_updated = final_group_key < last_group_key;
+ bool force_update = false;
+
+ NotificationGroupId removed_group_id;
+ if (!is_updated) {
+ CHECK(!was_updated);
+ VLOG(notifications) << "There is no need to send updateNotificationGroup in " << group_key
+ << ", because of newer notification groups";
+ group.total_count += narrow_cast<int32>(group.pending_notifications.size());
+ for (auto &pending_notification : group.pending_notifications) {
+ group.notifications.emplace_back(pending_notification.notification_id, pending_notification.date,
+ pending_notification.disable_notification, std::move(pending_notification.type));
+ }
+ } else {
+ if (!was_updated) {
+ if (last_group_key.last_notification_date != 0) {
+ // need to remove last notification group to not exceed max_notification_group_count_
+ removed_group_id = last_group_key.group_id;
+ send_remove_group_update(last_group_key, groups_[last_group_key], vector<int32>());
+ }
+ send_add_group_update(group_key, group, "flush_pending_notifications");
+ }
+
+ DialogId notification_settings_dialog_id;
+ int64 ringtone_id = -1;
+
+ // split notifications by groups with common settings
+ vector<PendingNotification> grouped_notifications;
+ for (auto &pending_notification : group.pending_notifications) {
+ if (notification_settings_dialog_id != pending_notification.settings_dialog_id ||
+ ringtone_id != pending_notification.ringtone_id) {
+ if (do_flush_pending_notifications(group_key, group, grouped_notifications)) {
+ force_update = true;
+ }
+ notification_settings_dialog_id = pending_notification.settings_dialog_id;
+ ringtone_id = pending_notification.ringtone_id;
+ }
+ grouped_notifications.push_back(std::move(pending_notification));
+ }
+ if (do_flush_pending_notifications(group_key, group, grouped_notifications)) {
+ force_update = true;
+ }
+ }
+
+ group.pending_notifications_flush_time = 0;
+ group.pending_notifications.clear();
+ on_delayed_notification_update_count_changed(-1, group_id.get(), "flush_pending_notifications");
+ // if we can delete a lot of notifications simultaneously
+ if (group.notifications.size() > keep_notification_group_size_ + EXTRA_GROUP_SIZE &&
+ group.type != NotificationGroupType::Calls) {
+ // keep only keep_notification_group_size_ last notifications in memory
+ for (auto it = group.notifications.begin(); it != group.notifications.end() - keep_notification_group_size_; ++it) {
+ on_notification_removed(it->notification_id);
+ }
+ group.notifications.erase(group.notifications.begin(), group.notifications.end() - keep_notification_group_size_);
+ group.is_loaded_from_database = false;
+ }
+
+ add_group(std::move(final_group_key), std::move(group), "flush_pending_notifications");
+
+ if (force_update) {
+ if (removed_group_id.is_valid()) {
+ force_flush_pending_updates(removed_group_id, "flush_pending_notifications 1");
+ }
+ force_flush_pending_updates(group_key.group_id, "flush_pending_notifications 2");
+ }
+}
+
+void NotificationManager::flush_all_pending_notifications() {
+ std::multimap<int32, NotificationGroupId> group_ids;
+ for (auto &group_it : groups_) {
+ if (!group_it.second.pending_notifications.empty()) {
+ group_ids.emplace(group_it.second.pending_notifications.back().date, group_it.first.group_id);
+ }
+ }
+
+ // flush groups in order of last notification date
+ VLOG(notifications) << "Flush pending notifications in " << group_ids.size() << " notification groups";
+ for (auto &it : group_ids) {
+ flush_pending_notifications_timeout_.cancel_timeout(it.second.get());
+ flush_pending_notifications(it.second);
+ }
+}
+
+void NotificationManager::edit_notification(NotificationGroupId group_id, NotificationId notification_id,
+ unique_ptr<NotificationType> type) {
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ return;
+ }
+ if (!group_id.is_valid()) {
+ return;
+ }
+
+ CHECK(notification_id.is_valid());
+ CHECK(type != nullptr);
+ VLOG(notifications) << "Edit " << notification_id << ": " << *type;
+
+ auto group_it = get_group(group_id);
+ if (group_it == groups_.end()) {
+ return;
+ }
+ auto &group = group_it->second;
+ for (size_t i = 0; i < group.notifications.size(); i++) {
+ auto &notification = group.notifications[i];
+ if (notification.notification_id == notification_id) {
+ if (notification.type->get_message_id() != type->get_message_id() ||
+ notification.type->is_temporary() != type->is_temporary()) {
+ LOG(ERROR) << "Ignore edit of " << notification_id << " with " << *type << ", because previous type is "
+ << *notification.type;
+ return;
+ }
+
+ notification.type = std::move(type);
+ if (i + max_notification_group_size_ >= group.notifications.size() &&
+ !(get_last_updated_group_key() < group_it->first)) {
+ CHECK(group_it->first.last_notification_date != 0);
+ add_update_notification(group_it->first.group_id, group_it->first.dialog_id, notification);
+ }
+ return;
+ }
+ }
+ for (auto &notification : group.pending_notifications) {
+ if (notification.notification_id == notification_id) {
+ if (notification.type->get_message_id() != type->get_message_id() ||
+ notification.type->is_temporary() != type->is_temporary()) {
+ LOG(ERROR) << "Ignore edit of " << notification_id << " with " << *type << ", because previous type is "
+ << *notification.type;
+ return;
+ }
+
+ notification.type = std::move(type);
+ return;
+ }
+ }
+}
+
+void NotificationManager::on_notification_processed(NotificationId notification_id) {
+ auto promise_it = push_notification_promises_.find(notification_id);
+ if (promise_it != push_notification_promises_.end()) {
+ auto promises = std::move(promise_it->second);
+ push_notification_promises_.erase(promise_it);
+
+ set_promises(promises);
+ }
+}
+
+void NotificationManager::on_notification_removed(NotificationId notification_id) {
+ VLOG(notifications) << "In on_notification_removed with " << notification_id;
+
+ auto add_it = temporary_notification_log_event_ids_.find(notification_id);
+ if (add_it == temporary_notification_log_event_ids_.end()) {
+ return;
+ }
+
+ auto edit_it = temporary_edit_notification_log_event_ids_.find(notification_id);
+ if (edit_it != temporary_edit_notification_log_event_ids_.end()) {
+ VLOG(notifications) << "Remove from binlog edit of " << notification_id << " with log event " << edit_it->second;
+ if (!is_being_destroyed_) {
+ binlog_erase(G()->td_db()->get_binlog(), edit_it->second);
+ }
+ temporary_edit_notification_log_event_ids_.erase(edit_it);
+ }
+
+ VLOG(notifications) << "Remove from binlog " << notification_id << " with log event " << add_it->second;
+ if (!is_being_destroyed_) {
+ binlog_erase(G()->td_db()->get_binlog(), add_it->second);
+ }
+ temporary_notification_log_event_ids_.erase(add_it);
+
+ auto erased_notification_count = temporary_notifications_.erase(temporary_notification_message_ids_[notification_id]);
+ auto erased_message_id_count = temporary_notification_message_ids_.erase(notification_id);
+ CHECK(erased_notification_count > 0);
+ CHECK(erased_message_id_count > 0);
+
+ on_notification_processed(notification_id);
+}
+
+void NotificationManager::on_notifications_removed(
+ NotificationGroups::iterator &&group_it, vector<td_api::object_ptr<td_api::notification>> &&added_notifications,
+ vector<int32> &&removed_notification_ids, bool force_update) {
+ VLOG(notifications) << "In on_notifications_removed for " << group_it->first.group_id << " with "
+ << added_notifications.size() << " added notifications and " << removed_notification_ids.size()
+ << " removed notifications, new total_count = " << group_it->second.total_count;
+ auto group_key = group_it->first;
+ auto final_group_key = group_key;
+ final_group_key.last_notification_date = 0;
+ for (auto &notification : group_it->second.notifications) {
+ if (notification.date > final_group_key.last_notification_date) {
+ final_group_key.last_notification_date = notification.date;
+ }
+ }
+
+ bool is_position_changed = final_group_key.last_notification_date != group_key.last_notification_date;
+
+ NotificationGroup group = std::move(group_it->second);
+ if (is_position_changed) {
+ VLOG(notifications) << "Position of notification group is changed from " << group_key << " to " << final_group_key;
+ delete_group(std::move(group_it));
+ }
+
+ auto last_group_key = get_last_updated_group_key();
+ bool was_updated = false;
+ bool is_updated = false;
+ if (is_position_changed) {
+ was_updated = group_key.last_notification_date != 0 && group_key < last_group_key;
+ is_updated = final_group_key.last_notification_date != 0 && final_group_key < last_group_key;
+ } else {
+ was_updated = is_updated = group_key.last_notification_date != 0 && !(last_group_key < group_key);
+ }
+
+ if (!was_updated) {
+ CHECK(!is_updated);
+ if (final_group_key.last_notification_date == 0 && group.total_count == 0) {
+ // send update about empty invisible group anyway
+ add_update_notification_group(td_api::make_object<td_api::updateNotificationGroup>(
+ group_key.group_id.get(), get_notification_group_type_object(group.type), group_key.dialog_id.get(), 0, 0, 0,
+ vector<td_api::object_ptr<td_api::notification>>(), vector<int32>()));
+ } else {
+ VLOG(notifications) << "There is no need to send updateNotificationGroup about " << group_key.group_id;
+ }
+ } else {
+ if (is_updated) {
+ // group is still visible
+ add_update_notification_group(td_api::make_object<td_api::updateNotificationGroup>(
+ group_key.group_id.get(), get_notification_group_type_object(group.type), group_key.dialog_id.get(), 0, 0,
+ group.total_count, std::move(added_notifications), std::move(removed_notification_ids)));
+ } else {
+ // group needs to be removed
+ send_remove_group_update(group_key, group, std::move(removed_notification_ids));
+ if (last_group_key.last_notification_date != 0) {
+ // need to add new last notification group
+ send_add_group_update(last_group_key, groups_[last_group_key], "on_notifications_removed");
+ }
+ }
+ }
+
+ if (is_position_changed) {
+ add_group(std::move(final_group_key), std::move(group), "on_notifications_removed");
+
+ last_group_key = get_last_updated_group_key();
+ } else {
+ CHECK(group_it->first.last_notification_date == 0 || !group.notifications.empty());
+ group_it->second = std::move(group);
+ }
+
+ if (force_update) {
+ force_flush_pending_updates(group_key.group_id, "on_notifications_removed");
+ }
+
+ if (last_loaded_notification_group_key_ < last_group_key) {
+ auto limit = td::max(static_cast<int32>(max_notification_group_count_), static_cast<int32>(10)) / 2;
+ load_message_notification_groups_from_database(limit, true);
+ }
+}
+
+void NotificationManager::remove_added_notifications_from_pending_updates(
+ NotificationGroupId group_id,
+ const std::function<bool(const td_api::object_ptr<td_api::notification> &notification)> &is_removed) {
+ auto it = pending_updates_.find(group_id.get());
+ if (it == pending_updates_.end()) {
+ return;
+ }
+
+ FlatHashSet<int32> removed_notification_ids;
+ for (auto &update : it->second) {
+ if (update == nullptr) {
+ continue;
+ }
+ if (update->get_id() == td_api::updateNotificationGroup::ID) {
+ auto update_ptr = static_cast<td_api::updateNotificationGroup *>(update.get());
+ if (!removed_notification_ids.empty() && !update_ptr->removed_notification_ids_.empty()) {
+ td::remove_if(update_ptr->removed_notification_ids_, [&removed_notification_ids](auto &notification_id) {
+ return removed_notification_ids.count(notification_id) == 1;
+ });
+ }
+ for (auto &notification : update_ptr->added_notifications_) {
+ if (is_removed(notification)) {
+ CHECK(notification->id_ != 0);
+ removed_notification_ids.insert(notification->id_);
+ VLOG(notifications) << "Remove " << NotificationId(notification->id_) << " in " << group_id;
+ notification = nullptr;
+ }
+ }
+ td::remove_if(update_ptr->added_notifications_, [](auto &notification) { return notification == nullptr; });
+ } else {
+ CHECK(update->get_id() == td_api::updateNotification::ID);
+ auto update_ptr = static_cast<td_api::updateNotification *>(update.get());
+ if (is_removed(update_ptr->notification_)) {
+ CHECK(update_ptr->notification_->id_ != 0);
+ removed_notification_ids.insert(update_ptr->notification_->id_);
+ VLOG(notifications) << "Remove " << NotificationId(update_ptr->notification_->id_) << " in " << group_id;
+ update = nullptr;
+ }
+ }
+ }
+}
+
+void NotificationManager::remove_notification(NotificationGroupId group_id, NotificationId notification_id,
+ bool is_permanent, bool force_update, Promise<Unit> &&promise,
+ const char *source) {
+ if (!group_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Notification group identifier is invalid"));
+ }
+ if (!notification_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Notification identifier is invalid"));
+ }
+
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ return promise.set_value(Unit());
+ }
+
+ VLOG(notifications) << "Remove " << notification_id << " from " << group_id << " with is_permanent = " << is_permanent
+ << ", force_update = " << force_update << " from " << source;
+
+ auto group_it = get_group_force(group_id);
+ if (group_it == groups_.end()) {
+ return promise.set_value(Unit());
+ }
+
+ if (!is_permanent && group_it->second.type != NotificationGroupType::Calls) {
+ td_->messages_manager_->remove_message_notification(group_it->first.dialog_id, group_id, notification_id);
+ }
+
+ for (auto it = group_it->second.pending_notifications.begin(); it != group_it->second.pending_notifications.end();
+ ++it) {
+ if (it->notification_id == notification_id) {
+ // notification is still pending, just delete it
+ on_notification_removed(notification_id);
+ group_it->second.pending_notifications.erase(it);
+ if (group_it->second.pending_notifications.empty()) {
+ group_it->second.pending_notifications_flush_time = 0;
+ flush_pending_notifications_timeout_.cancel_timeout(group_id.get());
+ on_delayed_notification_update_count_changed(-1, group_id.get(), "remove_notification");
+ }
+ return promise.set_value(Unit());
+ }
+ }
+
+ bool is_found = false;
+ auto old_group_size = group_it->second.notifications.size();
+ size_t notification_pos = old_group_size;
+ for (size_t pos = 0; pos < notification_pos; pos++) {
+ if (group_it->second.notifications[pos].notification_id == notification_id) {
+ on_notification_removed(notification_id);
+ notification_pos = pos;
+ is_found = true;
+ }
+ }
+
+ bool have_all_notifications = group_it->second.type == NotificationGroupType::Calls ||
+ group_it->second.type == NotificationGroupType::SecretChat;
+ bool is_total_count_changed = false;
+ if ((!have_all_notifications && is_permanent) || (have_all_notifications && is_found)) {
+ if (group_it->second.total_count == 0) {
+ LOG(ERROR) << "Total notification count became negative in " << group_it->second << " after removing "
+ << notification_id << " with is_permanent = " << is_permanent << ", is_found = " << is_found
+ << ", force_update = " << force_update << " from " << source;
+ } else {
+ group_it->second.total_count--;
+ is_total_count_changed = true;
+ }
+ }
+ if (is_found) {
+ group_it->second.notifications.erase(group_it->second.notifications.begin() + notification_pos);
+ }
+
+ vector<td_api::object_ptr<td_api::notification>> added_notifications;
+ vector<int32> removed_notification_ids;
+ CHECK(max_notification_group_size_ > 0);
+ if (is_found && notification_pos + max_notification_group_size_ >= old_group_size) {
+ removed_notification_ids.push_back(notification_id.get());
+ if (old_group_size >= max_notification_group_size_ + 1) {
+ added_notifications.push_back(
+ get_notification_object(group_it->first.dialog_id,
+ group_it->second.notifications[old_group_size - max_notification_group_size_ - 1]));
+ if (added_notifications.back()->type_ == nullptr) {
+ added_notifications.pop_back();
+ }
+ }
+ if (added_notifications.empty() && max_notification_group_size_ > group_it->second.notifications.size()) {
+ load_message_notifications_from_database(group_it->first, group_it->second, keep_notification_group_size_);
+ }
+ }
+
+ if (is_total_count_changed || !removed_notification_ids.empty()) {
+ on_notifications_removed(std::move(group_it), std::move(added_notifications), std::move(removed_notification_ids),
+ force_update);
+ }
+
+ remove_added_notifications_from_pending_updates(
+ group_id, [notification_id](const td_api::object_ptr<td_api::notification> &notification) {
+ return notification->id_ == notification_id.get();
+ });
+
+ promise.set_value(Unit());
+}
+
+void NotificationManager::remove_temporary_notification_by_message_id(NotificationGroupId group_id,
+ MessageId message_id, bool force_update,
+ const char *source) {
+ if (!group_id.is_valid()) {
+ return;
+ }
+
+ VLOG(notifications) << "Remove notification for " << message_id << " in " << group_id << " from " << source;
+ CHECK(message_id.is_valid());
+
+ auto group_it = get_group(group_id);
+ if (group_it == groups_.end()) {
+ return;
+ }
+
+ auto remove_notification_by_message_id = [&](auto &notifications) {
+ for (auto &notification : notifications) {
+ if (notification.type->get_message_id() == message_id) {
+ for (auto file_id : notification.type->get_file_ids(td_)) {
+ this->td_->file_manager_->delete_file(file_id, Promise<>(), "remove_temporary_notification_by_message_id");
+ }
+ return this->remove_notification(group_id, notification.notification_id, true, force_update, Auto(),
+ "remove_temporary_notification_by_message_id");
+ }
+ }
+ };
+
+ remove_notification_by_message_id(group_it->second.pending_notifications);
+ remove_notification_by_message_id(group_it->second.notifications);
+}
+
+void NotificationManager::remove_notification_group(NotificationGroupId group_id, NotificationId max_notification_id,
+ MessageId max_message_id, int32 new_total_count, bool force_update,
+ Promise<Unit> &&promise) {
+ if (!group_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Group identifier is invalid"));
+ }
+ if (!max_notification_id.is_valid() && !max_message_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "Notification identifier is invalid"));
+ }
+
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ return promise.set_value(Unit());
+ }
+
+ if (new_total_count == 0) {
+ remove_temporary_notifications(group_id, "remove_notification_group");
+ }
+
+ VLOG(notifications) << "Remove " << group_id << " up to " << max_notification_id << " or " << max_message_id
+ << " with new_total_count = " << new_total_count << " and force_update = " << force_update;
+
+ auto group_it = get_group_force(group_id);
+ if (group_it == groups_.end()) {
+ VLOG(notifications) << "Can't find " << group_id;
+ return promise.set_value(Unit());
+ }
+
+ if (max_notification_id.is_valid()) {
+ if (max_notification_id.get() > current_notification_id_.get()) {
+ max_notification_id = current_notification_id_;
+ }
+ if (group_it->second.type != NotificationGroupType::Calls) {
+ td_->messages_manager_->remove_message_notifications(
+ group_it->first.dialog_id, group_id, max_notification_id,
+ get_last_message_id_by_notification_id(group_it->second, max_notification_id));
+ }
+ }
+
+ auto pending_delete_end = group_it->second.pending_notifications.begin();
+ for (auto it = group_it->second.pending_notifications.begin(); it != group_it->second.pending_notifications.end();
+ ++it) {
+ if (it->notification_id.get() <= max_notification_id.get() ||
+ (max_message_id.is_valid() && it->type->get_message_id() <= max_message_id)) {
+ pending_delete_end = it + 1;
+ on_notification_removed(it->notification_id);
+ }
+ }
+ if (pending_delete_end != group_it->second.pending_notifications.begin()) {
+ group_it->second.pending_notifications.erase(group_it->second.pending_notifications.begin(), pending_delete_end);
+ if (group_it->second.pending_notifications.empty()) {
+ group_it->second.pending_notifications_flush_time = 0;
+ flush_pending_notifications_timeout_.cancel_timeout(group_id.get());
+ on_delayed_notification_update_count_changed(-1, group_id.get(), "remove_notification_group");
+ }
+ }
+ if (new_total_count != -1) {
+ new_total_count += get_temporary_notification_total_count(group_it->second);
+ new_total_count -= static_cast<int32>(group_it->second.pending_notifications.size());
+ if (new_total_count < 0) {
+ LOG(ERROR) << "Have wrong new_total_count " << new_total_count << " + "
+ << group_it->second.pending_notifications.size();
+ }
+ }
+
+ auto old_group_size = group_it->second.notifications.size();
+ auto notification_delete_end = old_group_size;
+ for (size_t pos = 0; pos < notification_delete_end; pos++) {
+ auto &notification = group_it->second.notifications[pos];
+ if (notification.notification_id.get() > max_notification_id.get() &&
+ (!max_message_id.is_valid() || notification.type->get_message_id() > max_message_id)) {
+ notification_delete_end = pos;
+ } else {
+ on_notification_removed(notification.notification_id);
+ }
+ }
+
+ bool is_found = notification_delete_end != 0;
+
+ vector<int32> removed_notification_ids;
+ if (is_found && notification_delete_end + max_notification_group_size_ > old_group_size) {
+ for (size_t i = old_group_size >= max_notification_group_size_ ? old_group_size - max_notification_group_size_ : 0;
+ i < notification_delete_end; i++) {
+ removed_notification_ids.push_back(group_it->second.notifications[i].notification_id.get());
+ }
+ }
+
+ VLOG(notifications) << "Need to delete " << notification_delete_end << " from "
+ << group_it->second.notifications.size() << " notifications";
+ if (is_found) {
+ group_it->second.notifications.erase(group_it->second.notifications.begin(),
+ group_it->second.notifications.begin() + notification_delete_end);
+ }
+ if (group_it->second.type == NotificationGroupType::Calls ||
+ group_it->second.type == NotificationGroupType::SecretChat) {
+ new_total_count = static_cast<int32>(group_it->second.notifications.size());
+ }
+ if (group_it->second.total_count == new_total_count) {
+ new_total_count = -1;
+ }
+ if (new_total_count != -1) {
+ group_it->second.total_count = new_total_count;
+ }
+
+ if (new_total_count != -1 || !removed_notification_ids.empty()) {
+ on_notifications_removed(std::move(group_it), vector<td_api::object_ptr<td_api::notification>>(),
+ std::move(removed_notification_ids), force_update);
+ } else {
+ VLOG(notifications) << "Have new_total_count = " << new_total_count << ", " << removed_notification_ids.size()
+ << " removed notifications and force_update = " << force_update;
+ if (force_update) {
+ force_flush_pending_updates(group_id, "remove_notification_group");
+ }
+ }
+
+ if (max_notification_id.is_valid()) {
+ remove_added_notifications_from_pending_updates(
+ group_id, [max_notification_id](const td_api::object_ptr<td_api::notification> &notification) {
+ return notification->id_ <= max_notification_id.get();
+ });
+ } else {
+ remove_added_notifications_from_pending_updates(
+ group_id, [max_message_id](const td_api::object_ptr<td_api::notification> &notification) {
+ return notification->type_->get_id() == td_api::notificationTypeNewMessage::ID &&
+ static_cast<const td_api::notificationTypeNewMessage *>(notification->type_.get())->message_->id_ <=
+ max_message_id.get();
+ });
+ }
+
+ promise.set_value(Unit());
+}
+
+void NotificationManager::remove_temporary_notifications(NotificationGroupId group_id, const char *source) {
+ CHECK(group_id.is_valid());
+
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ return;
+ }
+
+ auto group_it = get_group(group_id);
+ if (group_it == groups_.end()) {
+ return;
+ }
+
+ if (get_temporary_notification_total_count(group_it->second) == 0) {
+ return;
+ }
+
+ VLOG(notifications) << "Remove temporary notifications in " << group_id << " from " << source;
+
+ auto &group = group_it->second;
+ while (!group.pending_notifications.empty() && group.pending_notifications.back().type->is_temporary()) {
+ VLOG(notifications) << "Remove temporary " << group.pending_notifications.back() << " from " << group_id;
+ // notification is still pending, just delete it
+ on_notification_removed(group.pending_notifications.back().notification_id);
+ group.pending_notifications.pop_back();
+ if (group.pending_notifications.empty()) {
+ group.pending_notifications_flush_time = 0;
+ flush_pending_notifications_timeout_.cancel_timeout(group_id.get());
+ on_delayed_notification_update_count_changed(-1, group_id.get(), "remove_temporary_notifications");
+ }
+ }
+
+ auto old_group_size = group.notifications.size();
+ size_t notification_pos = old_group_size;
+ for (size_t pos = 0; pos < notification_pos; pos++) {
+ if (group.notifications[pos].type->is_temporary()) {
+ notification_pos = pos;
+ }
+ }
+ auto removed_notification_count = narrow_cast<int32>(old_group_size - notification_pos);
+ if (removed_notification_count == 0) {
+ CHECK(get_temporary_notification_total_count(group_it->second) == 0);
+ return;
+ }
+
+ if (group.total_count < removed_notification_count) {
+ LOG(ERROR) << "Total notification count became negative in " << group_id << " after removing "
+ << removed_notification_count << " temporary notificaitions";
+ group.total_count = 0;
+ } else {
+ group.total_count -= removed_notification_count;
+ }
+
+ vector<int32> removed_notification_ids;
+ for (auto i = notification_pos; i < old_group_size; i++) {
+ LOG_CHECK(group.notifications[i].type->is_temporary())
+ << notification_pos << ' ' << i << ' ' << old_group_size << ' ' << removed_notification_count << ' '
+ << group.notifications[i] << ' ' << group << ' ' << group_it->first;
+ VLOG(notifications) << "Remove temporary " << group.notifications[i] << " from " << group_id;
+ auto notification_id = group.notifications[i].notification_id;
+ on_notification_removed(notification_id);
+ if (i + max_notification_group_size_ >= old_group_size) {
+ removed_notification_ids.push_back(notification_id.get());
+ }
+ }
+ group.notifications.erase(group.notifications.begin() + notification_pos, group.notifications.end());
+ CHECK(!removed_notification_ids.empty());
+
+ vector<td_api::object_ptr<td_api::notification>> added_notifications;
+ if (old_group_size >= max_notification_group_size_) {
+ size_t added_notification_count = 0;
+ for (size_t i = min(old_group_size - max_notification_group_size_, notification_pos);
+ i-- > 0 && added_notification_count++ < removed_notification_ids.size();) {
+ added_notifications.push_back(get_notification_object(group_it->first.dialog_id, group.notifications[i]));
+ if (added_notifications.back()->type_ == nullptr) {
+ added_notifications.pop_back();
+ }
+ }
+ if (added_notification_count < removed_notification_ids.size() &&
+ max_notification_group_size_ > group.notifications.size()) {
+ load_message_notifications_from_database(group_it->first, group, keep_notification_group_size_);
+ }
+ std::reverse(added_notifications.begin(), added_notifications.end());
+ }
+ CHECK(get_temporary_notification_total_count(group_it->second) == 0);
+
+ on_notifications_removed(std::move(group_it), std::move(added_notifications), std::move(removed_notification_ids),
+ false);
+
+ remove_added_notifications_from_pending_updates(
+ group_id, [](const td_api::object_ptr<td_api::notification> &notification) {
+ return notification->get_id() == td_api::notificationTypeNewPushMessage::ID;
+ });
+}
+
+int32 NotificationManager::get_temporary_notification_total_count(const NotificationGroup &group) {
+ int32 result = 0;
+ for (auto &notification : reversed(group.notifications)) {
+ if (!notification.type->is_temporary()) {
+ break;
+ }
+ result++;
+ }
+ for (auto &pending_notification : reversed(group.pending_notifications)) {
+ if (!pending_notification.type->is_temporary()) {
+ break;
+ }
+ result++;
+ }
+ return result;
+}
+
+void NotificationManager::set_notification_total_count(NotificationGroupId group_id, int32 new_total_count) {
+ if (!group_id.is_valid()) {
+ return;
+ }
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ return;
+ }
+
+ auto group_it = get_group_force(group_id);
+ if (group_it == groups_.end()) {
+ VLOG(notifications) << "Can't find " << group_id;
+ return;
+ }
+
+ new_total_count += get_temporary_notification_total_count(group_it->second);
+ new_total_count -= static_cast<int32>(group_it->second.pending_notifications.size());
+ if (new_total_count < 0) {
+ LOG(ERROR) << "Have wrong new_total_count " << new_total_count << " after removing "
+ << group_it->second.pending_notifications.size() << " pending notifications";
+ return;
+ }
+ if (new_total_count < static_cast<int32>(group_it->second.notifications.size())) {
+ LOG(ERROR) << "Have wrong new_total_count " << new_total_count << " less than number of known notifications "
+ << group_it->second.notifications.size();
+ return;
+ }
+
+ CHECK(group_it->second.type != NotificationGroupType::Calls);
+ if (group_it->second.total_count == new_total_count) {
+ return;
+ }
+
+ VLOG(notifications) << "Set total_count in " << group_id << " to " << new_total_count;
+ group_it->second.total_count = new_total_count;
+
+ on_notifications_removed(std::move(group_it), vector<td_api::object_ptr<td_api::notification>>(), vector<int32>(),
+ false);
+}
+
+vector<MessageId> NotificationManager::get_notification_group_message_ids(NotificationGroupId group_id) {
+ CHECK(group_id.is_valid());
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ return {};
+ }
+
+ auto group_it = get_group_force(group_id);
+ if (group_it == groups_.end()) {
+ return {};
+ }
+
+ vector<MessageId> message_ids;
+ for (auto &notification : group_it->second.notifications) {
+ auto message_id = notification.type->get_message_id();
+ if (message_id.is_valid()) {
+ message_ids.push_back(message_id);
+ }
+ }
+ for (auto &notification : group_it->second.pending_notifications) {
+ auto message_id = notification.type->get_message_id();
+ if (message_id.is_valid()) {
+ message_ids.push_back(message_id);
+ }
+ }
+
+ return message_ids;
+}
+
+NotificationGroupId NotificationManager::get_call_notification_group_id(DialogId dialog_id) {
+ auto it = dialog_id_to_call_notification_group_id_.find(dialog_id);
+ if (it != dialog_id_to_call_notification_group_id_.end()) {
+ return it->second;
+ }
+ if (!dialog_id.is_valid()) {
+ return {};
+ }
+
+ if (available_call_notification_group_ids_.empty()) {
+ // need to reserve new group_id for calls
+ if (call_notification_group_ids_.size() >= MAX_CALL_NOTIFICATION_GROUPS) {
+ return {};
+ }
+ NotificationGroupId last_group_id;
+ if (!call_notification_group_ids_.empty()) {
+ last_group_id = call_notification_group_ids_.back();
+ }
+ NotificationGroupId next_notification_group_id;
+ do {
+ next_notification_group_id = get_next_notification_group_id();
+ if (!next_notification_group_id.is_valid()) {
+ return {};
+ }
+ } while (last_group_id.get() >= next_notification_group_id.get()); // just in case
+ VLOG(notifications) << "Add call " << next_notification_group_id;
+
+ call_notification_group_ids_.push_back(next_notification_group_id);
+ auto call_notification_group_ids_string = implode(
+ transform(call_notification_group_ids_, [](NotificationGroupId group_id) { return to_string(group_id.get()); }),
+ ',');
+ G()->td_db()->get_binlog_pmc()->set("notification_call_group_ids", call_notification_group_ids_string);
+ available_call_notification_group_ids_.insert(next_notification_group_id);
+ }
+
+ auto available_it = available_call_notification_group_ids_.begin();
+ auto group_id = *available_it;
+ available_call_notification_group_ids_.erase(available_it);
+ dialog_id_to_call_notification_group_id_[dialog_id] = group_id;
+ return group_id;
+}
+
+void NotificationManager::add_call_notification(DialogId dialog_id, CallId call_id) {
+ CHECK(dialog_id.is_valid());
+ CHECK(call_id.is_valid());
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ return;
+ }
+
+ auto group_id = get_call_notification_group_id(dialog_id);
+ if (!group_id.is_valid()) {
+ VLOG(notifications) << "Ignore notification about " << call_id << " in " << dialog_id;
+ return;
+ }
+
+ G()->td().get_actor_unsafe()->messages_manager_->force_create_dialog(dialog_id, "add_call_notification");
+
+ auto &active_notifications = active_call_notifications_[dialog_id];
+ if (active_notifications.size() >= MAX_CALL_NOTIFICATIONS) {
+ VLOG(notifications) << "Ignore notification about " << call_id << " in " << dialog_id << " and " << group_id;
+ return;
+ }
+
+ auto notification_id = get_next_notification_id();
+ if (!notification_id.is_valid()) {
+ return;
+ }
+ active_notifications.push_back(ActiveCallNotification{call_id, notification_id});
+
+ add_notification(group_id, NotificationGroupType::Calls, dialog_id, G()->unix_time() + 120, dialog_id, false, -1, 0,
+ notification_id, create_new_call_notification(call_id), "add_call_notification");
+}
+
+void NotificationManager::remove_call_notification(DialogId dialog_id, CallId call_id) {
+ CHECK(dialog_id.is_valid());
+ CHECK(call_id.is_valid());
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ return;
+ }
+
+ auto group_id_it = dialog_id_to_call_notification_group_id_.find(dialog_id);
+ if (group_id_it == dialog_id_to_call_notification_group_id_.end()) {
+ VLOG(notifications) << "Ignore removing notification about " << call_id << " in " << dialog_id;
+ return;
+ }
+ auto group_id = group_id_it->second;
+ CHECK(group_id.is_valid());
+
+ auto &active_notifications = active_call_notifications_[dialog_id];
+ for (auto it = active_notifications.begin(); it != active_notifications.end(); ++it) {
+ if (it->call_id == call_id) {
+ remove_notification(group_id, it->notification_id, true, true, Promise<Unit>(), "remove_call_notification");
+ active_notifications.erase(it);
+ if (active_notifications.empty()) {
+ VLOG(notifications) << "Reuse call " << group_id;
+ active_call_notifications_.erase(dialog_id);
+ available_call_notification_group_ids_.insert(group_id);
+ dialog_id_to_call_notification_group_id_.erase(dialog_id);
+
+ flush_pending_notifications_timeout_.cancel_timeout(group_id.get());
+ flush_pending_notifications(group_id);
+ force_flush_pending_updates(group_id, "reuse call group_id");
+
+ auto group_it = get_group(group_id);
+ LOG_CHECK(group_it->first.dialog_id == dialog_id)
+ << group_id << ' ' << dialog_id << ' ' << group_it->first << ' ' << group_it->second;
+ CHECK(group_it->first.last_notification_date == 0);
+ CHECK(group_it->second.total_count == 0);
+ CHECK(group_it->second.notifications.empty());
+ CHECK(group_it->second.pending_notifications.empty());
+ CHECK(group_it->second.type == NotificationGroupType::Calls);
+ CHECK(!group_it->second.is_being_loaded_from_database);
+ CHECK(pending_updates_.count(group_id.get()) == 0);
+ delete_group(std::move(group_it));
+ }
+ return;
+ }
+ }
+
+ VLOG(notifications) << "Failed to find " << call_id << " in " << dialog_id << " and " << group_id;
+}
+
+void NotificationManager::on_notification_group_count_max_changed(bool send_updates) {
+ if (is_disabled()) {
+ return;
+ }
+
+ auto new_max_notification_group_count = narrow_cast<int32>(
+ td_->option_manager_->get_option_integer("notification_group_count_max", DEFAULT_GROUP_COUNT_MAX));
+ CHECK(MIN_NOTIFICATION_GROUP_COUNT_MAX <= new_max_notification_group_count &&
+ new_max_notification_group_count <= MAX_NOTIFICATION_GROUP_COUNT_MAX);
+
+ auto new_max_notification_group_count_size_t = static_cast<size_t>(new_max_notification_group_count);
+ if (new_max_notification_group_count_size_t == max_notification_group_count_) {
+ return;
+ }
+
+ VLOG(notifications) << "Change max notification group count from " << max_notification_group_count_ << " to "
+ << new_max_notification_group_count;
+
+ bool is_increased = new_max_notification_group_count_size_t > max_notification_group_count_;
+ if (send_updates) {
+ flush_all_notifications();
+
+ size_t cur_pos = 0;
+ size_t min_group_count = min(new_max_notification_group_count_size_t, max_notification_group_count_);
+ size_t max_group_count = max(new_max_notification_group_count_size_t, max_notification_group_count_);
+ for (auto it = groups_.begin(); it != groups_.end() && cur_pos < max_group_count; ++it, cur_pos++) {
+ if (cur_pos < min_group_count) {
+ continue;
+ }
+
+ auto &group_key = it->first;
+ auto &group = it->second;
+ CHECK(group.pending_notifications.empty());
+ CHECK(pending_updates_.count(group_key.group_id.get()) == 0);
+
+ if (group_key.last_notification_date == 0) {
+ break;
+ }
+
+ if (is_increased) {
+ send_add_group_update(group_key, group, "on_notification_group_count_max_changed");
+ } else {
+ send_remove_group_update(group_key, group, vector<int32>());
+ }
+ }
+
+ flush_all_pending_updates(true, "on_notification_group_size_max_changed end");
+
+ if (new_max_notification_group_count == 0) {
+ last_loaded_notification_group_key_ = NotificationGroupKey();
+ last_loaded_notification_group_key_.last_notification_date = std::numeric_limits<int32>::max();
+ CHECK(pending_updates_.empty());
+ groups_.clear();
+ group_keys_.clear();
+ }
+ }
+
+ max_notification_group_count_ = new_max_notification_group_count_size_t;
+ if (is_increased && last_loaded_notification_group_key_ < get_last_updated_group_key()) {
+ auto limit = td::max(new_max_notification_group_count, static_cast<int32>(5));
+ load_message_notification_groups_from_database(limit, true);
+ }
+}
+
+void NotificationManager::on_notification_group_size_max_changed() {
+ if (is_disabled()) {
+ return;
+ }
+
+ auto new_max_notification_group_size = narrow_cast<int32>(
+ td_->option_manager_->get_option_integer("notification_group_size_max", DEFAULT_GROUP_SIZE_MAX));
+ CHECK(MIN_NOTIFICATION_GROUP_SIZE_MAX <= new_max_notification_group_size &&
+ new_max_notification_group_size <= MAX_NOTIFICATION_GROUP_SIZE_MAX);
+
+ auto new_max_notification_group_size_size_t = static_cast<size_t>(new_max_notification_group_size);
+ if (new_max_notification_group_size_size_t == max_notification_group_size_) {
+ return;
+ }
+
+ auto new_keep_notification_group_size =
+ new_max_notification_group_size_size_t +
+ clamp(new_max_notification_group_size_size_t, EXTRA_GROUP_SIZE / 2, EXTRA_GROUP_SIZE);
+
+ VLOG(notifications) << "Change max notification group size from " << max_notification_group_size_ << " to "
+ << new_max_notification_group_size;
+
+ if (max_notification_group_size_ != 0) {
+ flush_all_notifications();
+
+ size_t left = max_notification_group_count_;
+ for (auto it = groups_.begin(); it != groups_.end() && left > 0; ++it, left--) {
+ auto &group_key = it->first;
+ auto &group = it->second;
+ CHECK(group.pending_notifications.empty());
+ CHECK(pending_updates_.count(group_key.group_id.get()) == 0);
+
+ if (group_key.last_notification_date == 0) {
+ break;
+ }
+
+ vector<td_api::object_ptr<td_api::notification>> added_notifications;
+ vector<int32> removed_notification_ids;
+ auto notification_count = group.notifications.size();
+ if (new_max_notification_group_size_size_t < max_notification_group_size_) {
+ if (notification_count <= new_max_notification_group_size_size_t) {
+ VLOG(notifications) << "There is no need to update " << group_key.group_id;
+ continue;
+ }
+ for (size_t i = notification_count - min(notification_count, max_notification_group_size_);
+ i < notification_count - new_max_notification_group_size_size_t; i++) {
+ removed_notification_ids.push_back(group.notifications[i].notification_id.get());
+ }
+ CHECK(!removed_notification_ids.empty());
+ } else {
+ if (new_max_notification_group_size_size_t > notification_count) {
+ load_message_notifications_from_database(group_key, group, new_keep_notification_group_size);
+ }
+ if (notification_count <= max_notification_group_size_) {
+ VLOG(notifications) << "There is no need to update " << group_key.group_id;
+ continue;
+ }
+ for (size_t i = notification_count - min(notification_count, new_max_notification_group_size_size_t);
+ i < notification_count - max_notification_group_size_; i++) {
+ added_notifications.push_back(get_notification_object(group_key.dialog_id, group.notifications[i]));
+ if (added_notifications.back()->type_ == nullptr) {
+ added_notifications.pop_back();
+ }
+ }
+ if (added_notifications.empty()) {
+ continue;
+ }
+ }
+ if (!is_destroyed_) {
+ auto update = td_api::make_object<td_api::updateNotificationGroup>(
+ group_key.group_id.get(), get_notification_group_type_object(group.type), group_key.dialog_id.get(),
+ group_key.dialog_id.get(), 0, group.total_count, std::move(added_notifications),
+ std::move(removed_notification_ids));
+ VLOG(notifications) << "Send " << as_notification_update(update.get());
+ send_closure(G()->td(), &Td::send_update, std::move(update));
+ }
+ }
+ }
+
+ max_notification_group_size_ = new_max_notification_group_size_size_t;
+ keep_notification_group_size_ = new_keep_notification_group_size;
+}
+
+void NotificationManager::on_online_cloud_timeout_changed() {
+ if (is_disabled()) {
+ return;
+ }
+
+ online_cloud_timeout_ms_ = narrow_cast<int32>(
+ td_->option_manager_->get_option_integer("online_cloud_timeout_ms", DEFAULT_ONLINE_CLOUD_TIMEOUT_MS));
+ VLOG(notifications) << "Set online_cloud_timeout_ms to " << online_cloud_timeout_ms_;
+}
+
+void NotificationManager::on_notification_cloud_delay_changed() {
+ if (is_disabled()) {
+ return;
+ }
+
+ notification_cloud_delay_ms_ = narrow_cast<int32>(
+ td_->option_manager_->get_option_integer("notification_cloud_delay_ms", DEFAULT_ONLINE_CLOUD_DELAY_MS));
+ VLOG(notifications) << "Set notification_cloud_delay_ms to " << notification_cloud_delay_ms_;
+}
+
+void NotificationManager::on_notification_default_delay_changed() {
+ if (is_disabled()) {
+ return;
+ }
+
+ notification_default_delay_ms_ = narrow_cast<int32>(
+ td_->option_manager_->get_option_integer("notification_default_delay_ms", DEFAULT_DEFAULT_DELAY_MS));
+ VLOG(notifications) << "Set notification_default_delay_ms to " << notification_default_delay_ms_;
+}
+
+void NotificationManager::on_disable_contact_registered_notifications_changed() {
+ if (is_disabled()) {
+ return;
+ }
+
+ auto is_disabled = td_->option_manager_->get_option_boolean("disable_contact_registered_notifications");
+ if (is_disabled == disable_contact_registered_notifications_) {
+ return;
+ }
+
+ disable_contact_registered_notifications_ = is_disabled;
+ if (contact_registered_notifications_sync_state_ == SyncState::Completed) {
+ run_contact_registered_notifications_sync();
+ }
+}
+
+void NotificationManager::on_get_disable_contact_registered_notifications(bool is_disabled, Promise<Unit> &&promise) {
+ if (G()->close_flag() || disable_contact_registered_notifications_ == is_disabled) {
+ return promise.set_value(Unit());
+ }
+ disable_contact_registered_notifications_ = is_disabled;
+
+ if (is_disabled) {
+ td_->option_manager_->set_option_boolean("disable_contact_registered_notifications", is_disabled);
+ } else {
+ td_->option_manager_->set_option_empty("disable_contact_registered_notifications");
+ }
+ promise.set_value(Unit());
+}
+
+void NotificationManager::set_contact_registered_notifications_sync_state(SyncState new_state) {
+ if (is_disabled()) {
+ return;
+ }
+
+ contact_registered_notifications_sync_state_ = new_state;
+ string value;
+ value += static_cast<char>(static_cast<int32>(new_state) + '0');
+ value += static_cast<char>(static_cast<int32>(disable_contact_registered_notifications_) + '0');
+ G()->td_db()->get_binlog_pmc()->set(get_is_contact_registered_notifications_synchronized_key(), value);
+}
+
+void NotificationManager::run_contact_registered_notifications_sync() {
+ if (is_disabled()) {
+ return;
+ }
+
+ auto is_disabled = disable_contact_registered_notifications_;
+ if (contact_registered_notifications_sync_state_ == SyncState::NotSynced && !is_disabled) {
+ set_contact_registered_notifications_sync_state(SyncState::Completed);
+ return;
+ }
+ if (contact_registered_notifications_sync_state_ != SyncState::Pending) {
+ set_contact_registered_notifications_sync_state(SyncState::Pending);
+ }
+
+ VLOG(notifications) << "Send SetContactSignUpNotificationQuery with " << is_disabled;
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), is_disabled](Result<Unit> result) {
+ send_closure(actor_id, &NotificationManager::on_contact_registered_notifications_sync, is_disabled,
+ std::move(result));
+ });
+ td_->create_handler<SetContactSignUpNotificationQuery>(std::move(promise))->send(is_disabled);
+}
+
+void NotificationManager::on_contact_registered_notifications_sync(bool is_disabled, Result<Unit> result) {
+ CHECK(contact_registered_notifications_sync_state_ == SyncState::Pending);
+ if (is_disabled != disable_contact_registered_notifications_) {
+ return run_contact_registered_notifications_sync();
+ }
+ if (result.is_ok()) {
+ // everything is synchronized
+ set_contact_registered_notifications_sync_state(SyncState::Completed);
+ } else {
+ // let's resend the query forever
+ run_contact_registered_notifications_sync();
+ }
+}
+
+void NotificationManager::get_disable_contact_registered_notifications(Promise<Unit> &&promise) {
+ if (is_disabled()) {
+ promise.set_value(Unit());
+ return;
+ }
+
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](Result<bool> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &NotificationManager::on_get_disable_contact_registered_notifications, result.ok(),
+ std::move(promise));
+ }
+ });
+ td_->create_handler<GetContactSignUpNotificationQuery>(std::move(query_promise))->send();
+}
+
+void NotificationManager::process_push_notification(string payload, Promise<Unit> &&user_promise) {
+ auto promise = PromiseCreator::lambda([user_promise = std::move(user_promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ if (result.error().code() == 200) {
+ user_promise.set_value(Unit());
+ } else {
+ user_promise.set_error(result.move_as_error());
+ }
+ } else {
+ create_actor<SleepActor>("FinishProcessPushNotificationActor", 0.01, std::move(user_promise)).release();
+ }
+ });
+
+ if (is_disabled() || payload == "{}") {
+ return promise.set_error(Status::Error(200, "Immediate success"));
+ }
+
+ auto r_receiver_id = get_push_receiver_id(payload);
+ if (r_receiver_id.is_error()) {
+ VLOG(notifications) << "Failed to get push notification receiver from \"" << format::escaped(payload)
+ << "\":" << r_receiver_id.is_error();
+ return promise.set_error(r_receiver_id.move_as_error());
+ }
+
+ auto receiver_id = r_receiver_id.move_as_ok();
+ auto encryption_keys = td_->device_token_manager_.get_actor_unsafe()->get_encryption_keys();
+ VLOG(notifications) << "Process push notification \"" << format::escaped(payload)
+ << "\" with receiver_id = " << receiver_id << " and " << encryption_keys.size()
+ << " encryption keys";
+ bool was_encrypted = false;
+ for (auto &key : encryption_keys) {
+ VLOG(notifications) << "Have key " << key.first;
+ // VLOG(notifications) << "Have key " << key.first << ": \"" << format::escaped(key.second) << '"';
+ if (key.first == receiver_id) {
+ if (!key.second.empty()) {
+ auto r_payload = decrypt_push(key.first, key.second.str(), std::move(payload));
+ if (r_payload.is_error()) {
+ LOG(ERROR) << "Failed to decrypt push: " << r_payload.error();
+ return promise.set_error(Status::Error(400, "Failed to decrypt push payload"));
+ }
+ payload = r_payload.move_as_ok();
+ was_encrypted = true;
+ }
+ receiver_id = 0;
+ break;
+ }
+ }
+
+ if (!td_->is_online()) {
+ // reset online flag to false to immediately check all connections aliveness
+ send_closure(G()->state_manager(), &StateManager::on_online, false);
+ }
+
+ if (receiver_id == 0 || receiver_id == td_->option_manager_->get_option_integer("my_id")) {
+ auto status = process_push_notification_payload(payload, was_encrypted, promise);
+ if (status.is_error()) {
+ if (status.code() == 406 || status.code() == 200) {
+ return promise.set_error(std::move(status));
+ }
+
+ LOG(ERROR) << "Receive error " << status << ", while parsing push payload " << payload;
+ return promise.set_error(Status::Error(400, status.message()));
+ }
+ // promise will be set after updateNotificationGroup is sent to the client
+ return;
+ }
+
+ VLOG(notifications) << "Failed to process push notification";
+ promise.set_error(Status::Error(200, "Immediate success"));
+}
+
+string NotificationManager::convert_loc_key(const string &loc_key) {
+ if (loc_key.size() <= 8) {
+ if (loc_key == "MESSAGES" || loc_key == "ALBUM") {
+ return "MESSAGES";
+ }
+ return string();
+ }
+ switch (loc_key[8]) {
+ case 'A':
+ if (loc_key == "PINNED_GAME") {
+ return "PINNED_MESSAGE_GAME";
+ }
+ if (loc_key == "PINNED_GAME_SCORE") {
+ return "PINNED_MESSAGE_GAME_SCORE";
+ }
+ if (loc_key == "CHAT_CREATED") {
+ return "MESSAGE_BASIC_GROUP_CHAT_CREATE";
+ }
+ if (loc_key == "MESSAGE_AUDIO") {
+ return "MESSAGE_VOICE_NOTE";
+ }
+ break;
+ case 'C':
+ if (loc_key == "MESSAGE_CONTACT") {
+ return "MESSAGE_CONTACT";
+ }
+ break;
+ case 'D':
+ if (loc_key == "MESSAGE_DOC") {
+ return "MESSAGE_DOCUMENT";
+ }
+ if (loc_key == "MESSAGE_DOCS") {
+ return "MESSAGE_DOCUMENTS";
+ }
+ if (loc_key == "ENCRYPTED_MESSAGE") {
+ return "MESSAGE";
+ }
+ break;
+ case 'E':
+ if (loc_key == "PINNED_GEO") {
+ return "PINNED_MESSAGE_LOCATION";
+ }
+ if (loc_key == "PINNED_GEOLIVE") {
+ return "PINNED_MESSAGE_LIVE_LOCATION";
+ }
+ if (loc_key == "CHAT_DELETE_MEMBER") {
+ return "MESSAGE_CHAT_DELETE_MEMBER";
+ }
+ if (loc_key == "CHAT_DELETE_YOU") {
+ return "MESSAGE_CHAT_DELETE_MEMBER_YOU";
+ }
+ if (loc_key == "PINNED_TEXT") {
+ return "PINNED_MESSAGE_TEXT";
+ }
+ break;
+ case 'F':
+ if (loc_key == "MESSAGE_FWDS") {
+ return "MESSAGE_FORWARDS";
+ }
+ break;
+ case 'G':
+ if (loc_key == "MESSAGE_GAME") {
+ return "MESSAGE_GAME";
+ }
+ if (loc_key == "MESSAGE_GAME_SCORE") {
+ return "MESSAGE_GAME_SCORE";
+ }
+ if (loc_key == "MESSAGE_GEO") {
+ return "MESSAGE_LOCATION";
+ }
+ if (loc_key == "MESSAGE_GEOLIVE") {
+ return "MESSAGE_LIVE_LOCATION";
+ }
+ if (loc_key == "MESSAGE_GIF") {
+ return "MESSAGE_ANIMATION";
+ }
+ break;
+ case 'H':
+ if (loc_key == "PINNED_PHOTO") {
+ return "PINNED_MESSAGE_PHOTO";
+ }
+ break;
+ case 'I':
+ if (loc_key == "PINNED_VIDEO") {
+ return "PINNED_MESSAGE_VIDEO";
+ }
+ if (loc_key == "PINNED_GIF") {
+ return "PINNED_MESSAGE_ANIMATION";
+ }
+ if (loc_key == "MESSAGE_INVOICE") {
+ return "MESSAGE_INVOICE";
+ }
+ break;
+ case 'J':
+ if (loc_key == "CONTACT_JOINED") {
+ return "MESSAGE_CONTACT_REGISTERED";
+ }
+ break;
+ case 'L':
+ if (loc_key == "CHAT_TITLE_EDITED") {
+ return "MESSAGE_CHAT_CHANGE_TITLE";
+ }
+ break;
+ case 'N':
+ if (loc_key == "CHAT_JOINED") {
+ return "MESSAGE_CHAT_JOIN_BY_LINK";
+ }
+ if (loc_key == "MESSAGE_NOTEXT") {
+ return "MESSAGE";
+ }
+ if (loc_key == "MESSAGE_NOTHEME") {
+ return "MESSAGE_CHAT_CHANGE_THEME";
+ }
+ if (loc_key == "PINNED_INVOICE") {
+ return "PINNED_MESSAGE_INVOICE";
+ }
+ break;
+ case 'O':
+ if (loc_key == "PINNED_DOC") {
+ return "PINNED_MESSAGE_DOCUMENT";
+ }
+ if (loc_key == "PINNED_POLL") {
+ return "PINNED_MESSAGE_POLL";
+ }
+ if (loc_key == "PINNED_CONTACT") {
+ return "PINNED_MESSAGE_CONTACT";
+ }
+ if (loc_key == "PINNED_NOTEXT") {
+ return "PINNED_MESSAGE";
+ }
+ if (loc_key == "PINNED_ROUND") {
+ return "PINNED_MESSAGE_VIDEO_NOTE";
+ }
+ break;
+ case 'P':
+ if (loc_key == "MESSAGE_PHOTO") {
+ return "MESSAGE_PHOTO";
+ }
+ if (loc_key == "MESSAGE_PHOTOS") {
+ return "MESSAGE_PHOTOS";
+ }
+ if (loc_key == "MESSAGE_PHOTO_SECRET") {
+ return "MESSAGE_SECRET_PHOTO";
+ }
+ if (loc_key == "MESSAGE_PLAYLIST") {
+ return "MESSAGE_AUDIOS";
+ }
+ if (loc_key == "MESSAGE_POLL") {
+ return "MESSAGE_POLL";
+ }
+ break;
+ case 'Q':
+ if (loc_key == "MESSAGE_QUIZ") {
+ return "MESSAGE_QUIZ";
+ }
+ break;
+ case 'R':
+ if (loc_key == "MESSAGE_ROUND") {
+ return "MESSAGE_VIDEO_NOTE";
+ }
+ if (loc_key == "MESSAGE_RECURRING_PAY") {
+ return "MESSAGE_RECURRING_PAYMENT";
+ }
+ break;
+ case 'S':
+ if (loc_key == "MESSAGE_SCREENSHOT") {
+ return "MESSAGE_SCREENSHOT_TAKEN";
+ }
+ if (loc_key == "MESSAGE_STICKER") {
+ return "MESSAGE_STICKER";
+ }
+ break;
+ case 'T':
+ if (loc_key == "CHAT_LEFT") {
+ return "MESSAGE_CHAT_DELETE_MEMBER_LEFT";
+ }
+ if (loc_key == "MESSAGE_TEXT") {
+ return "MESSAGE_TEXT";
+ }
+ if (loc_key == "PINNED_STICKER") {
+ return "PINNED_MESSAGE_STICKER";
+ }
+ if (loc_key == "CHAT_PHOTO_EDITED") {
+ return "MESSAGE_CHAT_CHANGE_PHOTO";
+ }
+ if (loc_key == "MESSAGE_THEME") {
+ return "MESSAGE_CHAT_CHANGE_THEME";
+ }
+ break;
+ case 'U':
+ if (loc_key == "PINNED_AUDIO") {
+ return "PINNED_MESSAGE_VOICE_NOTE";
+ }
+ if (loc_key == "PINNED_QUIZ") {
+ return "PINNED_MESSAGE_QUIZ";
+ }
+ if (loc_key == "CHAT_RETURNED") {
+ return "MESSAGE_CHAT_ADD_MEMBERS_RETURNED";
+ }
+ break;
+ case 'V':
+ if (loc_key == "MESSAGE_VIDEO") {
+ return "MESSAGE_VIDEO";
+ }
+ if (loc_key == "MESSAGE_VIDEOS") {
+ return "MESSAGE_VIDEOS";
+ }
+ if (loc_key == "MESSAGE_VIDEO_SECRET") {
+ return "MESSAGE_SECRET_VIDEO";
+ }
+ break;
+ case '_':
+ if (loc_key == "CHAT_ADD_MEMBER") {
+ return "MESSAGE_CHAT_ADD_MEMBERS";
+ }
+ if (loc_key == "CHAT_ADD_YOU") {
+ return "MESSAGE_CHAT_ADD_MEMBERS_YOU";
+ }
+ if (loc_key == "CHAT_REQ_JOINED") {
+ return "MESSAGE_CHAT_JOIN_BY_REQUEST";
+ }
+ break;
+ }
+ return string();
+}
+
+Status NotificationManager::process_push_notification_payload(string payload, bool was_encrypted,
+ Promise<Unit> &promise) {
+ VLOG(notifications) << "Process push notification payload " << payload;
+ auto r_json_value = json_decode(payload);
+ if (r_json_value.is_error()) {
+ return Status::Error("Failed to parse payload as JSON object");
+ }
+
+ auto json_value = r_json_value.move_as_ok();
+ if (json_value.type() != JsonValue::Type::Object) {
+ return Status::Error("Expected a JSON object as push payload");
+ }
+
+ auto data = std::move(json_value.get_object());
+ int32 sent_date = G()->unix_time();
+ if (has_json_object_field(data, "data")) {
+ TRY_RESULT(date, get_json_object_int_field(data, "date", true, sent_date));
+ if (sent_date - 28 * 86400 <= date && date <= sent_date + 5) {
+ sent_date = date;
+ }
+ TRY_RESULT(data_data, get_json_object_field(data, "data", JsonValue::Type::Object, false));
+ data = std::move(data_data.get_object());
+ }
+
+ string loc_key;
+ JsonObject custom;
+ string announcement_message_text;
+ vector<string> loc_args;
+ string sender_name;
+ for (auto &field_value : data) {
+ if (field_value.first == "loc_key") {
+ if (field_value.second.type() != JsonValue::Type::String) {
+ return Status::Error("Expected loc_key as a String");
+ }
+ loc_key = field_value.second.get_string().str();
+ } else if (field_value.first == "loc_args") {
+ if (field_value.second.type() != JsonValue::Type::Array) {
+ return Status::Error("Expected loc_args as an Array");
+ }
+ loc_args.reserve(field_value.second.get_array().size());
+ for (auto &arg : field_value.second.get_array()) {
+ if (arg.type() != JsonValue::Type::String) {
+ return Status::Error("Expected loc_arg as a String");
+ }
+ loc_args.push_back(arg.get_string().str());
+ }
+ } else if (field_value.first == "custom") {
+ if (field_value.second.type() != JsonValue::Type::Object) {
+ return Status::Error("Expected custom as an Object");
+ }
+ custom = std::move(field_value.second.get_object());
+ } else if (field_value.first == "message") {
+ if (field_value.second.type() != JsonValue::Type::String) {
+ return Status::Error("Expected announcement message text as a String");
+ }
+ announcement_message_text = field_value.second.get_string().str();
+ } else if (field_value.first == "google.sent_time") {
+ TRY_RESULT(google_sent_time, get_json_object_long_field(data, "google.sent_time"));
+ google_sent_time /= 1000;
+ if (sent_date - 28 * 86400 <= google_sent_time && google_sent_time <= sent_date + 5) {
+ sent_date = narrow_cast<int32>(google_sent_time);
+ }
+ }
+ }
+
+ if (!clean_input_string(loc_key)) {
+ return Status::Error(PSLICE() << "Receive invalid loc_key " << format::escaped(loc_key));
+ }
+ if (loc_key.empty()) {
+ return Status::Error("Receive empty loc_key");
+ }
+ for (auto &loc_arg : loc_args) {
+ if (!clean_input_string(loc_arg)) {
+ return Status::Error(PSLICE() << "Receive invalid loc_arg " << format::escaped(loc_arg));
+ }
+ }
+
+ if (loc_key == "MESSAGE_ANNOUNCEMENT") {
+ if (announcement_message_text.empty()) {
+ return Status::Error("Receive empty announcement message text");
+ }
+ TRY_RESULT(announcement_id, get_json_object_int_field(custom, "announcement"));
+ if (announcement_id == 0) {
+ return Status::Error(200, "Receive unsupported announcement ID");
+ }
+ auto &date = announcement_id_date_[announcement_id];
+ auto now = G()->unix_time();
+ if (date >= now - ANNOUNCEMENT_ID_CACHE_TIME) {
+ VLOG(notifications) << "Ignore duplicate announcement " << announcement_id;
+ return Status::Error(200, "Immediate success");
+ }
+ date = now;
+
+ auto update = telegram_api::make_object<telegram_api::updateServiceNotification>(
+ telegram_api::updateServiceNotification::INBOX_DATE_MASK, false, G()->unix_time(), string(),
+ announcement_message_text, nullptr, vector<telegram_api::object_ptr<telegram_api::MessageEntity>>());
+ send_closure(G()->messages_manager(), &MessagesManager::on_update_service_notification, std::move(update), false,
+ std::move(promise));
+ save_announcement_ids();
+ return Status::OK();
+ }
+ if (!announcement_message_text.empty()) {
+ LOG(ERROR) << "Have non-empty announcement message text with loc_key = " << loc_key;
+ }
+
+ if (loc_key == "DC_UPDATE") {
+ TRY_RESULT(dc_id, get_json_object_int_field(custom, "dc", false));
+ TRY_RESULT(addr, get_json_object_string_field(custom, "addr", false));
+ if (!DcId::is_valid(dc_id)) {
+ return Status::Error("Invalid datacenter ID");
+ }
+ if (!clean_input_string(addr)) {
+ return Status::Error(PSLICE() << "Receive invalid addr " << format::escaped(addr));
+ }
+ send_closure(G()->connection_creator(), &ConnectionCreator::on_dc_update, DcId::internal(dc_id), std::move(addr),
+ std::move(promise));
+ return Status::OK();
+ }
+
+ if (loc_key == "SESSION_REVOKE") {
+ if (was_encrypted) {
+ G()->log_out("SESSION_REVOKE");
+ } else {
+ LOG(ERROR) << "Receive unencrypted SESSION_REVOKE push notification";
+ }
+ promise.set_value(Unit());
+ return Status::OK();
+ }
+
+ if (loc_key == "LOCKED_MESSAGE") {
+ return Status::Error(200, "Immediate success");
+ }
+
+ if (loc_key == "GEO_LIVE_PENDING") {
+ td_->messages_manager_->on_update_some_live_location_viewed(std::move(promise));
+ return Status::OK();
+ }
+
+ if (loc_key == "AUTH_REGION" || loc_key == "AUTH_UNKNOWN") {
+ // TODO
+ return Status::Error(200, "Immediate success");
+ }
+
+ DialogId dialog_id;
+ if (has_json_object_field(custom, "from_id")) {
+ TRY_RESULT(user_id_int, get_json_object_long_field(custom, "from_id"));
+ UserId user_id(user_id_int);
+ if (!user_id.is_valid()) {
+ return Status::Error("Receive invalid user_id");
+ }
+ dialog_id = DialogId(user_id);
+ }
+ if (has_json_object_field(custom, "chat_id")) {
+ TRY_RESULT(chat_id_int, get_json_object_long_field(custom, "chat_id"));
+ ChatId chat_id(chat_id_int);
+ if (!chat_id.is_valid()) {
+ return Status::Error("Receive invalid chat_id");
+ }
+ dialog_id = DialogId(chat_id);
+ }
+ if (has_json_object_field(custom, "channel_id")) {
+ TRY_RESULT(channel_id_int, get_json_object_long_field(custom, "channel_id"));
+ ChannelId channel_id(channel_id_int);
+ if (!channel_id.is_valid()) {
+ return Status::Error("Receive invalid channel_id");
+ }
+ dialog_id = DialogId(channel_id);
+ }
+ if (has_json_object_field(custom, "encryption_id")) {
+ TRY_RESULT(secret_chat_id_int, get_json_object_int_field(custom, "encryption_id"));
+ SecretChatId secret_chat_id(secret_chat_id_int);
+ if (!secret_chat_id.is_valid()) {
+ return Status::Error("Receive invalid secret_chat_id");
+ }
+ dialog_id = DialogId(secret_chat_id);
+ }
+ if (!dialog_id.is_valid()) {
+ if (loc_key == "ENCRYPTED_MESSAGE" || loc_key == "MESSAGE_MUTED") {
+ return Status::Error(406, "Force loading data from the server");
+ }
+ return Status::Error("Can't find dialog_id");
+ }
+
+ if (loc_key == "READ_HISTORY") {
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ return Status::Error("Receive read history in a secret chat");
+ }
+
+ TRY_RESULT(max_id, get_json_object_int_field(custom, "max_id"));
+ ServerMessageId max_server_message_id(max_id);
+ if (!max_server_message_id.is_valid()) {
+ return Status::Error("Receive invalid max_id");
+ }
+
+ td_->messages_manager_->read_history_inbox(dialog_id, MessageId(max_server_message_id), -1,
+ "process_push_notification_payload");
+ promise.set_value(Unit());
+ return Status::OK();
+ }
+
+ if (loc_key == "MESSAGE_DELETED") {
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ return Status::Error("Receive MESSAGE_DELETED in a secret chat");
+ }
+ TRY_RESULT(server_message_ids_str, get_json_object_string_field(custom, "messages", false));
+ auto server_message_ids = full_split(server_message_ids_str, ',');
+ vector<MessageId> message_ids;
+ for (const auto &server_message_id_str : server_message_ids) {
+ TRY_RESULT(server_message_id_int, to_integer_safe<int32>(server_message_id_str));
+ ServerMessageId server_message_id(server_message_id_int);
+ if (!server_message_id.is_valid()) {
+ return Status::Error("Receive invalid message_id");
+ }
+ message_ids.push_back(MessageId(server_message_id));
+ }
+ td_->messages_manager_->remove_message_notifications_by_message_ids(dialog_id, message_ids);
+ promise.set_value(Unit());
+ return Status::OK();
+ }
+
+ if (loc_key == "MESSAGE_MUTED") {
+ return Status::Error(406, "Notifications about muted messages force loading data from the server");
+ }
+
+ TRY_RESULT(msg_id, get_json_object_int_field(custom, "msg_id"));
+ ServerMessageId server_message_id(msg_id);
+ if (server_message_id != ServerMessageId() && !server_message_id.is_valid()) {
+ return Status::Error("Receive invalid msg_id");
+ }
+
+ TRY_RESULT(random_id, get_json_object_long_field(custom, "random_id"));
+
+ UserId sender_user_id;
+ DialogId sender_dialog_id;
+ if (has_json_object_field(custom, "chat_from_broadcast_id")) {
+ TRY_RESULT(sender_channel_id_int, get_json_object_long_field(custom, "chat_from_broadcast_id"));
+ sender_dialog_id = DialogId(ChannelId(sender_channel_id_int));
+ if (!sender_dialog_id.is_valid()) {
+ return Status::Error("Receive invalid chat_from_broadcast_id");
+ }
+ } else if (has_json_object_field(custom, "chat_from_group_id")) {
+ TRY_RESULT(sender_channel_id_int, get_json_object_long_field(custom, "chat_from_group_id"));
+ sender_dialog_id = DialogId(ChannelId(sender_channel_id_int));
+ if (!sender_dialog_id.is_valid()) {
+ return Status::Error("Receive invalid chat_from_group_id");
+ }
+ } else if (has_json_object_field(custom, "chat_from_id")) {
+ TRY_RESULT(sender_user_id_int, get_json_object_long_field(custom, "chat_from_id"));
+ sender_user_id = UserId(sender_user_id_int);
+ if (!sender_user_id.is_valid()) {
+ return Status::Error("Receive invalid chat_from_id");
+ }
+ } else if (dialog_id.get_type() == DialogType::User) {
+ sender_user_id = dialog_id.get_user_id();
+ } else if (dialog_id.get_type() == DialogType::Channel) {
+ sender_dialog_id = dialog_id;
+ }
+
+ TRY_RESULT(contains_mention_int, get_json_object_int_field(custom, "mention"));
+ bool contains_mention = contains_mention_int != 0;
+
+ if (begins_with(loc_key, "CHANNEL_MESSAGE") || loc_key == "CHANNEL_ALBUM") {
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return Status::Error("Receive wrong chat type");
+ }
+ loc_key = loc_key.substr(8);
+ }
+ if (begins_with(loc_key, "CHAT_")) {
+ auto dialog_type = dialog_id.get_type();
+ if (dialog_type != DialogType::Chat && dialog_type != DialogType::Channel) {
+ return Status::Error("Receive wrong chat type");
+ }
+
+ if (begins_with(loc_key, "CHAT_MESSAGE") || begins_with(loc_key, "CHAT_REACT") || loc_key == "CHAT_ALBUM") {
+ loc_key = loc_key.substr(5);
+ }
+ if (loc_args.empty()) {
+ return Status::Error("Expect sender name as first argument");
+ }
+ sender_name = std::move(loc_args[0]);
+ loc_args.erase(loc_args.begin());
+ }
+ if (begins_with(loc_key, "MESSAGE") && !server_message_id.is_valid()) {
+ return Status::Error("Receive no message ID");
+ }
+ if (begins_with(loc_key, "ENCRYPT") || random_id != 0) {
+ if (dialog_id.get_type() != DialogType::SecretChat) {
+ return Status::Error("Receive wrong chat type");
+ }
+ }
+ if (server_message_id.is_valid() && dialog_id.get_type() == DialogType::SecretChat) {
+ return Status::Error("Receive message ID in secret chat push");
+ }
+
+ if (begins_with(loc_key, "ENCRYPTION_")) {
+ // TODO ENCRYPTION_REQUEST/ENCRYPTION_ACCEPT notifications
+ return Status::Error(406, "New secret chat notification is not supported");
+ }
+
+ if (begins_with(loc_key, "PHONE_CALL_") || begins_with(loc_key, "VIDEO_CALL_")) {
+ // TODO PHONE_CALL_REQUEST/PHONE_CALL_DECLINE/PHONE_CALL_MISSED/VIDEO_CALL_REQUEST/VIDEO_CALL_MISSED notifications
+ return Status::Error(406, "Phone call notification is not supported");
+ }
+
+ if (begins_with(loc_key, "REACT_") || loc_key == "READ_REACTION") {
+ // TODO REACT_* notifications
+ return Status::Error(406, "Reaction notifications are unsupported");
+ }
+
+ loc_key = convert_loc_key(loc_key);
+ if (loc_key.empty()) {
+ return Status::Error("Push type is unknown");
+ }
+
+ if (loc_args.empty()) {
+ return Status::Error("Expected chat name as next argument");
+ }
+ if (dialog_id.get_type() == DialogType::User) {
+ sender_name = std::move(loc_args[0]);
+ } else if ((sender_user_id.is_valid() || sender_dialog_id.is_valid()) && begins_with(loc_key, "PINNED_")) {
+ if (loc_args.size() < 2) {
+ return Status::Error("Expected chat title as the last argument");
+ }
+ loc_args.pop_back();
+ }
+ // chat title for CHAT_*, CHANNEL_* and ENCRYPTED_MESSAGE, sender name for MESSAGE_* and CONTACT_JOINED
+ // chat title or sender name for PINNED_*
+ loc_args.erase(loc_args.begin());
+
+ string arg;
+ if (loc_key == "MESSAGE_GAME_SCORE") {
+ if (loc_args.size() != 2) {
+ return Status::Error("Expected 2 arguments for MESSAGE_GAME_SCORE");
+ }
+ TRY_RESULT(score, to_integer_safe<int32>(loc_args[1]));
+ if (score < 0) {
+ return Status::Error("Expected score to be non-negative");
+ }
+ arg = PSTRING() << loc_args[1] << ' ' << loc_args[0];
+ loc_args.clear();
+ }
+ if (loc_args.size() > 1) {
+ return Status::Error("Receive too many arguments");
+ }
+
+ if (loc_args.size() == 1) {
+ arg = std::move(loc_args[0]);
+ }
+
+ if (sender_user_id.is_valid() && !td_->contacts_manager_->have_user_force(sender_user_id)) {
+ int64 sender_access_hash = -1;
+ telegram_api::object_ptr<telegram_api::UserProfilePhoto> sender_photo;
+ TRY_RESULT(mtpeer, get_json_object_field(custom, "mtpeer", JsonValue::Type::Object));
+ if (mtpeer.type() != JsonValue::Type::Null) {
+ TRY_RESULT(ah, get_json_object_string_field(mtpeer.get_object(), "ah"));
+ if (!ah.empty()) {
+ TRY_RESULT_ASSIGN(sender_access_hash, to_integer_safe<int64>(ah));
+ }
+ TRY_RESULT(ph, get_json_object_field(mtpeer.get_object(), "ph", JsonValue::Type::Object));
+ if (ph.type() != JsonValue::Type::Null) {
+ // TODO parse sender photo
+ }
+ }
+
+ int32 flags = USER_FLAG_IS_INACCESSIBLE;
+ if (sender_access_hash != -1) {
+ // set phone number flag to show that this is a full access hash
+ flags |= USER_FLAG_HAS_ACCESS_HASH | USER_FLAG_HAS_PHONE_NUMBER;
+ }
+ auto user_name = sender_user_id.get() == 136817688 ? "Channel" : sender_name;
+ auto user = telegram_api::make_object<telegram_api::user>(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, sender_user_id.get(), sender_access_hash, user_name,
+ string(), string(), string(), std::move(sender_photo), nullptr, 0, Auto(), string(), string(), nullptr,
+ vector<telegram_api::object_ptr<telegram_api::username>>());
+ td_->contacts_manager_->on_get_user(std::move(user), "process_push_notification_payload");
+ }
+
+ Photo attached_photo;
+ Document attached_document;
+ if (has_json_object_field(custom, "attachb64")) {
+ TRY_RESULT(attachb64, get_json_object_string_field(custom, "attachb64", false));
+ TRY_RESULT(attach, base64url_decode(attachb64));
+
+ TlParser gzip_parser(attach);
+ int32 id = gzip_parser.fetch_int();
+ if (gzip_parser.get_error()) {
+ return Status::Error(PSLICE() << "Failed to parse attach: " << gzip_parser.get_error());
+ }
+ BufferSlice buffer;
+ if (id == mtproto_api::gzip_packed::ID) {
+ mtproto_api::gzip_packed gzip(gzip_parser);
+ gzip_parser.fetch_end();
+ if (gzip_parser.get_error()) {
+ return Status::Error(PSLICE() << "Failed to parse mtproto_api::gzip_packed in attach: "
+ << gzip_parser.get_error());
+ }
+ buffer = gzdecode(gzip.packed_data_);
+ if (buffer.empty()) {
+ return Status::Error("Failed to uncompress attach");
+ }
+ } else {
+ buffer = BufferSlice(attach);
+ }
+
+ TlBufferParser parser(&buffer);
+ auto result = telegram_api::Object::fetch(parser);
+ parser.fetch_end();
+ const char *error = parser.get_error();
+ if (error != nullptr) {
+ LOG(ERROR) << "Can't parse attach: " << Slice(error) << " at " << parser.get_error_pos() << ": "
+ << format::as_hex_dump<4>(Slice(attach));
+ } else {
+ switch (result->get_id()) {
+ case telegram_api::photo::ID:
+ if (ends_with(loc_key, "MESSAGE_PHOTO") || ends_with(loc_key, "MESSAGE_TEXT")) {
+ VLOG(notifications) << "Have attached photo";
+ loc_key.resize(loc_key.rfind('_') + 1);
+ loc_key += "PHOTO";
+ attached_photo = get_photo(td_->file_manager_.get(),
+ telegram_api::move_object_as<telegram_api::photo>(result), dialog_id);
+ } else {
+ LOG(ERROR) << "Receive attached photo for " << loc_key;
+ }
+ break;
+ case telegram_api::document::ID: {
+ if (ends_with(loc_key, "MESSAGE_ANIMATION") || ends_with(loc_key, "MESSAGE_AUDIO") ||
+ ends_with(loc_key, "MESSAGE_DOCUMENT") || ends_with(loc_key, "MESSAGE_STICKER") ||
+ ends_with(loc_key, "MESSAGE_VIDEO") || ends_with(loc_key, "MESSAGE_VIDEO_NOTE") ||
+ ends_with(loc_key, "MESSAGE_VOICE_NOTE") || ends_with(loc_key, "MESSAGE_TEXT")) {
+ VLOG(notifications) << "Have attached document";
+ attached_document = td_->documents_manager_->on_get_document(
+ telegram_api::move_object_as<telegram_api::document>(result), dialog_id);
+ if (!attached_document.empty()) {
+ if (ends_with(loc_key, "_NOTE")) {
+ loc_key.resize(loc_key.rfind('_'));
+ }
+ loc_key.resize(loc_key.rfind('_') + 1);
+
+ auto type = [attached_document] {
+ switch (attached_document.type) {
+ case Document::Type::Animation:
+ return "ANIMATION";
+ case Document::Type::Audio:
+ return "AUDIO";
+ case Document::Type::General:
+ return "DOCUMENT";
+ case Document::Type::Sticker:
+ return "STICKER";
+ case Document::Type::Video:
+ return "VIDEO";
+ case Document::Type::VideoNote:
+ return "VIDEO_NOTE";
+ case Document::Type::VoiceNote:
+ return "VOICE_NOTE";
+ case Document::Type::Unknown:
+ default:
+ UNREACHABLE();
+ return "UNREACHABLE";
+ }
+ }();
+
+ loc_key += type;
+ }
+ } else {
+ LOG(ERROR) << "Receive attached document for " << loc_key;
+ }
+ break;
+ }
+ default:
+ LOG(ERROR) << "Receive unexpected attached " << to_string(result);
+ }
+ }
+ }
+ if (!arg.empty()) {
+ uint32 emoji = [&] {
+ if (ends_with(loc_key, "PHOTO")) {
+ return 0x1F5BC;
+ }
+ if (ends_with(loc_key, "ANIMATION")) {
+ return 0x1F3AC;
+ }
+ if (ends_with(loc_key, "DOCUMENT")) {
+ return 0x1F4CE;
+ }
+ if (ends_with(loc_key, "VIDEO")) {
+ return 0x1F4F9;
+ }
+ return 0;
+ }();
+ if (emoji != 0) {
+ string prefix;
+ append_utf8_character(prefix, emoji);
+ prefix += ' ';
+ if (begins_with(arg, prefix)) {
+ arg = arg.substr(prefix.size());
+ }
+ }
+ }
+
+ if (has_json_object_field(custom, "edit_date")) {
+ if (random_id != 0) {
+ return Status::Error("Receive edit of secret message");
+ }
+ TRY_RESULT(edit_date, get_json_object_int_field(custom, "edit_date"));
+ if (edit_date <= 0) {
+ return Status::Error("Receive wrong edit date");
+ }
+ edit_message_push_notification(dialog_id, MessageId(server_message_id), edit_date, std::move(loc_key),
+ std::move(arg), std::move(attached_photo), std::move(attached_document), 0,
+ std::move(promise));
+ } else {
+ bool is_from_scheduled = has_json_object_field(custom, "schedule");
+ bool disable_notification = false;
+ int64 ringtone_id = -1;
+ if (has_json_object_field(custom, "silent")) {
+ disable_notification = true;
+ ringtone_id = 0;
+ } else if (has_json_object_field(custom, "ringtone")) {
+ TRY_RESULT_ASSIGN(ringtone_id, get_json_object_long_field(custom, "ringtone"));
+ }
+ add_message_push_notification(dialog_id, MessageId(server_message_id), random_id, sender_user_id, sender_dialog_id,
+ std::move(sender_name), sent_date, is_from_scheduled, contains_mention,
+ disable_notification, ringtone_id, std::move(loc_key), std::move(arg),
+ std::move(attached_photo), std::move(attached_document), NotificationId(), 0,
+ std::move(promise));
+ }
+ return Status::OK();
+}
+
+class NotificationManager::AddMessagePushNotificationLogEvent {
+ public:
+ DialogId dialog_id_;
+ MessageId message_id_;
+ int64 random_id_;
+ UserId sender_user_id_;
+ DialogId sender_dialog_id_;
+ string sender_name_;
+ int32 date_;
+ bool is_from_scheduled_;
+ bool contains_mention_;
+ bool disable_notification_;
+ int64 ringtone_id_;
+ string loc_key_;
+ string arg_;
+ Photo photo_;
+ Document document_;
+ NotificationId notification_id_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ bool has_message_id = message_id_.is_valid();
+ bool has_random_id = random_id_ != 0;
+ bool has_sender = sender_user_id_.is_valid();
+ bool has_sender_name = !sender_name_.empty();
+ bool has_arg = !arg_.empty();
+ bool has_photo = !photo_.is_empty();
+ bool has_document = !document_.empty();
+ bool has_sender_dialog_id = sender_dialog_id_.is_valid();
+ bool has_ringtone_id = !disable_notification_ && ringtone_id_ != -1;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(contains_mention_);
+ STORE_FLAG(disable_notification_);
+ STORE_FLAG(has_message_id);
+ STORE_FLAG(has_random_id);
+ STORE_FLAG(has_sender);
+ STORE_FLAG(has_sender_name);
+ STORE_FLAG(has_arg);
+ STORE_FLAG(has_photo);
+ STORE_FLAG(has_document);
+ STORE_FLAG(is_from_scheduled_);
+ STORE_FLAG(has_sender_dialog_id);
+ STORE_FLAG(has_ringtone_id);
+ END_STORE_FLAGS();
+ td::store(dialog_id_, storer);
+ if (has_message_id) {
+ td::store(message_id_, storer);
+ }
+ if (has_random_id) {
+ td::store(random_id_, storer);
+ }
+ if (has_sender) {
+ td::store(sender_user_id_, storer);
+ }
+ if (has_sender_name) {
+ td::store(sender_name_, storer);
+ }
+ td::store(date_, storer);
+ td::store(loc_key_, storer);
+ if (has_arg) {
+ td::store(arg_, storer);
+ }
+ if (has_photo) {
+ td::store(photo_, storer);
+ }
+ if (has_document) {
+ td::store(document_, storer);
+ }
+ td::store(notification_id_, storer);
+ if (has_sender_dialog_id) {
+ td::store(sender_dialog_id_, storer);
+ }
+ if (has_ringtone_id) {
+ td::store(ringtone_id_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ bool has_message_id;
+ bool has_random_id;
+ bool has_sender;
+ bool has_sender_name;
+ bool has_arg;
+ bool has_photo;
+ bool has_document;
+ bool has_sender_dialog_id;
+ bool has_ringtone_id;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(contains_mention_);
+ PARSE_FLAG(disable_notification_);
+ PARSE_FLAG(has_message_id);
+ PARSE_FLAG(has_random_id);
+ PARSE_FLAG(has_sender);
+ PARSE_FLAG(has_sender_name);
+ PARSE_FLAG(has_arg);
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(has_document);
+ PARSE_FLAG(is_from_scheduled_);
+ PARSE_FLAG(has_sender_dialog_id);
+ PARSE_FLAG(has_ringtone_id);
+ END_PARSE_FLAGS();
+ td::parse(dialog_id_, parser);
+ if (has_message_id) {
+ td::parse(message_id_, parser);
+ }
+ if (has_random_id) {
+ td::parse(random_id_, parser);
+ } else {
+ random_id_ = 0;
+ }
+ if (has_sender) {
+ td::parse(sender_user_id_, parser);
+ }
+ if (has_sender_name) {
+ td::parse(sender_name_, parser);
+ }
+ td::parse(date_, parser);
+ td::parse(loc_key_, parser);
+ if (has_arg) {
+ td::parse(arg_, parser);
+ }
+ if (has_photo) {
+ td::parse(photo_, parser);
+ }
+ if (has_document) {
+ td::parse(document_, parser);
+ }
+ td::parse(notification_id_, parser);
+ if (has_sender_dialog_id) {
+ td::parse(sender_dialog_id_, parser);
+ }
+ if (has_ringtone_id) {
+ td::parse(ringtone_id_, parser);
+ } else {
+ ringtone_id_ = disable_notification_ ? 0 : -1;
+ }
+ }
+};
+
+void NotificationManager::add_message_push_notification(DialogId dialog_id, MessageId message_id, int64 random_id,
+ UserId sender_user_id, DialogId sender_dialog_id,
+ string sender_name, int32 date, bool is_from_scheduled,
+ bool contains_mention, bool disable_notification,
+ int64 ringtone_id, string loc_key, string arg, Photo photo,
+ Document document, NotificationId notification_id,
+ uint64 log_event_id, Promise<Unit> promise) {
+ auto is_pinned = begins_with(loc_key, "PINNED_");
+ auto r_info = td_->messages_manager_->get_message_push_notification_info(
+ dialog_id, message_id, random_id, sender_user_id, sender_dialog_id, date, is_from_scheduled, contains_mention,
+ is_pinned, log_event_id != 0);
+ if (r_info.is_error()) {
+ VLOG(notifications) << "Don't need message push notification for " << message_id << "/" << random_id << " from "
+ << dialog_id << " sent by " << sender_user_id << "/" << sender_dialog_id << " at " << date
+ << ": " << r_info.error();
+ if (log_event_id != 0) {
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ }
+ if (r_info.error().code() == 406) {
+ promise.set_error(r_info.move_as_error());
+ } else {
+ promise.set_error(Status::Error(200, "Immediate success"));
+ }
+ return;
+ }
+
+ auto info = r_info.move_as_ok();
+ CHECK(info.group_id.is_valid());
+
+ if (dialog_id.get_type() == DialogType::SecretChat) {
+ VLOG(notifications) << "Skip notification in secret " << dialog_id;
+ // TODO support secret chat notifications
+ // main problem: there is no message_id yet
+ // also don't forget to delete newSecretChat notification
+ CHECK(log_event_id == 0);
+ return promise.set_error(Status::Error(406, "Secret chat push notifications are unsupported"));
+ }
+ CHECK(random_id == 0);
+
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ CHECK(log_event_id == 0);
+ return promise.set_error(Status::Error(200, "Immediate success"));
+ }
+
+ if (!notification_id.is_valid()) {
+ CHECK(log_event_id == 0);
+ notification_id = get_next_notification_id();
+ if (!notification_id.is_valid()) {
+ return promise.set_value(Unit());
+ }
+ } else {
+ CHECK(log_event_id != 0);
+ }
+
+ if (sender_user_id.is_valid() && !td_->contacts_manager_->have_user_force(sender_user_id)) {
+ int32 flags = USER_FLAG_IS_INACCESSIBLE;
+ auto user_name = sender_user_id.get() == 136817688 ? "Channel" : sender_name;
+ auto user = telegram_api::make_object<telegram_api::user>(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, 0, sender_user_id.get(), 0, user_name, string(),
+ string(), string(), nullptr, nullptr, 0, Auto(), string(), string(), nullptr,
+ vector<telegram_api::object_ptr<telegram_api::username>>());
+ td_->contacts_manager_->on_get_user(std::move(user), "add_message_push_notification");
+ }
+
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ AddMessagePushNotificationLogEvent log_event{dialog_id,
+ message_id,
+ random_id,
+ sender_user_id,
+ sender_dialog_id,
+ sender_name,
+ date,
+ is_from_scheduled,
+ contains_mention,
+ disable_notification,
+ ringtone_id,
+ loc_key,
+ arg,
+ photo,
+ document,
+ notification_id};
+ log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::AddMessagePushNotification,
+ get_log_event_storer(log_event));
+ }
+
+ auto group_id = info.group_id;
+ CHECK(group_id.is_valid());
+
+ bool is_outgoing =
+ sender_user_id.is_valid() ? td_->contacts_manager_->get_my_id() == sender_user_id : is_from_scheduled;
+ if (log_event_id != 0) {
+ VLOG(notifications) << "Register temporary " << notification_id << " with log event " << log_event_id;
+ temporary_notification_log_event_ids_[notification_id] = log_event_id;
+ temporary_notifications_[FullMessageId(dialog_id, message_id)] = {group_id, notification_id, sender_user_id,
+ sender_dialog_id, sender_name, is_outgoing};
+ temporary_notification_message_ids_[notification_id] = FullMessageId(dialog_id, message_id);
+ }
+ push_notification_promises_[notification_id].push_back(std::move(promise));
+
+ auto group_type = info.group_type;
+ auto settings_dialog_id = info.settings_dialog_id;
+ VLOG(notifications) << "Add message push " << notification_id << " of type " << loc_key << " for " << message_id
+ << "/" << random_id << " in " << dialog_id << ", sent by " << sender_user_id << "/"
+ << sender_dialog_id << "/\"" << sender_name << "\" at " << date << " with arg " << arg
+ << ", photo " << photo << " and document " << document << " to " << group_id << " of type "
+ << group_type << " with settings from " << settings_dialog_id;
+
+ add_notification(
+ group_id, group_type, dialog_id, date, settings_dialog_id, disable_notification, ringtone_id, 0, notification_id,
+ create_new_push_message_notification(sender_user_id, sender_dialog_id, sender_name, is_outgoing, message_id,
+ std::move(loc_key), std::move(arg), std::move(photo), std::move(document)),
+ "add_message_push_notification");
+}
+
+class NotificationManager::EditMessagePushNotificationLogEvent {
+ public:
+ DialogId dialog_id_;
+ MessageId message_id_;
+ int32 edit_date_;
+ string loc_key_;
+ string arg_;
+ Photo photo_;
+ Document document_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ bool has_message_id = message_id_.is_valid();
+ bool has_arg = !arg_.empty();
+ bool has_photo = !photo_.is_empty();
+ bool has_document = !document_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_message_id);
+ STORE_FLAG(has_arg);
+ STORE_FLAG(has_photo);
+ STORE_FLAG(has_document);
+ END_STORE_FLAGS();
+ td::store(dialog_id_, storer);
+ if (has_message_id) {
+ td::store(message_id_, storer);
+ }
+ td::store(edit_date_, storer);
+ td::store(loc_key_, storer);
+ if (has_arg) {
+ td::store(arg_, storer);
+ }
+ if (has_photo) {
+ td::store(photo_, storer);
+ }
+ if (has_document) {
+ td::store(document_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ bool has_message_id;
+ bool has_arg;
+ bool has_photo;
+ bool has_document;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_message_id);
+ PARSE_FLAG(has_arg);
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(has_document);
+ END_PARSE_FLAGS();
+ td::parse(dialog_id_, parser);
+ if (has_message_id) {
+ td::parse(message_id_, parser);
+ }
+ td::parse(edit_date_, parser);
+ td::parse(loc_key_, parser);
+ if (has_arg) {
+ td::parse(arg_, parser);
+ }
+ if (has_photo) {
+ td::parse(photo_, parser);
+ }
+ if (has_document) {
+ td::parse(document_, parser);
+ }
+ }
+};
+
+void NotificationManager::edit_message_push_notification(DialogId dialog_id, MessageId message_id, int32 edit_date,
+ string loc_key, string arg, Photo photo, Document document,
+ uint64 log_event_id, Promise<Unit> promise) {
+ if (is_disabled() || max_notification_group_count_ == 0) {
+ CHECK(log_event_id == 0);
+ return promise.set_error(Status::Error(200, "Immediate success"));
+ }
+
+ auto it = temporary_notifications_.find(FullMessageId(dialog_id, message_id));
+ if (it == temporary_notifications_.end()) {
+ VLOG(notifications) << "Ignore edit of message push notification for " << message_id << " in " << dialog_id
+ << " edited at " << edit_date;
+ return promise.set_error(Status::Error(200, "Immediate success"));
+ }
+
+ auto group_id = it->second.group_id;
+ auto notification_id = it->second.notification_id;
+ auto sender_user_id = it->second.sender_user_id;
+ auto sender_dialog_id = it->second.sender_dialog_id;
+ auto sender_name = it->second.sender_name;
+ auto is_outgoing = it->second.is_outgoing;
+ CHECK(group_id.is_valid());
+ CHECK(notification_id.is_valid());
+
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ EditMessagePushNotificationLogEvent log_event{dialog_id, message_id, edit_date, loc_key, arg, photo, document};
+ auto storer = get_log_event_storer(log_event);
+ auto &cur_log_event_id = temporary_edit_notification_log_event_ids_[notification_id];
+ if (cur_log_event_id == 0) {
+ log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::EditMessagePushNotification, storer);
+ cur_log_event_id = log_event_id;
+ VLOG(notifications) << "Add edit message push notification log event " << log_event_id;
+ } else {
+ auto new_log_event_id = binlog_rewrite(G()->td_db()->get_binlog(), cur_log_event_id,
+ LogEvent::HandlerType::EditMessagePushNotification, storer);
+ VLOG(notifications) << "Rewrite edit message push notification log event " << cur_log_event_id << " with "
+ << new_log_event_id;
+ }
+ } else if (log_event_id != 0) {
+ VLOG(notifications) << "Register edit of temporary " << notification_id << " with log event " << log_event_id;
+ temporary_edit_notification_log_event_ids_[notification_id] = log_event_id;
+ }
+
+ push_notification_promises_[notification_id].push_back(std::move(promise));
+
+ edit_notification(group_id, notification_id,
+ create_new_push_message_notification(sender_user_id, sender_dialog_id, std::move(sender_name),
+ is_outgoing, message_id, std::move(loc_key), std::move(arg),
+ std::move(photo), std::move(document)));
+}
+
+Result<int64> NotificationManager::get_push_receiver_id(string payload) {
+ if (payload == "{}") {
+ return static_cast<int64>(0);
+ }
+
+ auto r_json_value = json_decode(payload);
+ if (r_json_value.is_error()) {
+ return Status::Error(400, "Failed to parse payload as JSON object");
+ }
+
+ auto json_value = r_json_value.move_as_ok();
+ if (json_value.type() != JsonValue::Type::Object) {
+ return Status::Error(400, "Expected JSON object");
+ }
+
+ auto data = std::move(json_value.get_object());
+ if (has_json_object_field(data, "data")) {
+ auto r_data_data = get_json_object_field(data, "data", JsonValue::Type::Object, false);
+ if (r_data_data.is_error()) {
+ return Status::Error(400, r_data_data.error().message());
+ }
+ auto data_data = r_data_data.move_as_ok();
+ data = std::move(data_data.get_object());
+ }
+
+ for (auto &field_value : data) {
+ if (field_value.first == "p") {
+ auto encrypted_payload = std::move(field_value.second);
+ if (encrypted_payload.type() != JsonValue::Type::String) {
+ return Status::Error(400, "Expected encrypted payload as a String");
+ }
+ Slice encrypted_data = encrypted_payload.get_string();
+ if (encrypted_data.size() < 12) {
+ return Status::Error(400, "Encrypted payload is too small");
+ }
+ auto r_decoded = base64url_decode(encrypted_data.substr(0, 12));
+ if (r_decoded.is_error()) {
+ return Status::Error(400, "Failed to base64url-decode payload");
+ }
+ CHECK(r_decoded.ok().size() == 9);
+ return as<int64>(r_decoded.ok().c_str());
+ }
+ if (field_value.first == "user_id") {
+ auto user_id = std::move(field_value.second);
+ if (user_id.type() != JsonValue::Type::String && user_id.type() != JsonValue::Type::Number) {
+ return Status::Error(400, "Expected user_id as a String or a Number");
+ }
+ Slice user_id_str = user_id.type() == JsonValue::Type::String ? user_id.get_string() : user_id.get_number();
+ auto r_user_id = to_integer_safe<int64>(user_id_str);
+ if (r_user_id.is_error()) {
+ return Status::Error(400, PSLICE() << "Failed to get user_id from " << user_id_str);
+ }
+ if (r_user_id.ok() <= 0) {
+ return Status::Error(400, PSLICE() << "Receive wrong user_id " << user_id_str);
+ }
+ return r_user_id.ok();
+ }
+ }
+
+ return static_cast<int64>(0);
+}
+
+Result<string> NotificationManager::decrypt_push(int64 encryption_key_id, string encryption_key, string push) {
+ auto r_json_value = json_decode(push);
+ if (r_json_value.is_error()) {
+ return Status::Error(400, "Failed to parse payload as JSON object");
+ }
+
+ auto json_value = r_json_value.move_as_ok();
+ if (json_value.type() != JsonValue::Type::Object) {
+ return Status::Error(400, "Expected JSON object");
+ }
+
+ for (auto &field_value : json_value.get_object()) {
+ if (field_value.first == "p") {
+ auto encrypted_payload = std::move(field_value.second);
+ if (encrypted_payload.type() != JsonValue::Type::String) {
+ return Status::Error(400, "Expected encrypted payload as a String");
+ }
+ Slice encrypted_data = encrypted_payload.get_string();
+ if (encrypted_data.size() < 12) {
+ return Status::Error(400, "Encrypted payload is too small");
+ }
+ auto r_decoded = base64url_decode(encrypted_data);
+ if (r_decoded.is_error()) {
+ return Status::Error(400, "Failed to base64url-decode payload");
+ }
+ return decrypt_push_payload(encryption_key_id, std::move(encryption_key), r_decoded.move_as_ok());
+ }
+ }
+ return Status::Error(400, "No 'p'(payload) field found in push");
+}
+
+Result<string> NotificationManager::decrypt_push_payload(int64 encryption_key_id, string encryption_key,
+ string payload) {
+ mtproto::AuthKey auth_key(encryption_key_id, std::move(encryption_key));
+ mtproto::PacketInfo packet_info;
+ packet_info.version = 2;
+ packet_info.type = mtproto::PacketInfo::EndToEnd;
+ packet_info.is_creator = true;
+ packet_info.check_mod4 = false;
+
+ TRY_RESULT(result, mtproto::Transport::read(payload, auth_key, &packet_info));
+ if (result.type() != mtproto::Transport::ReadResult::Packet) {
+ return Status::Error(400, "Wrong packet type");
+ }
+ if (result.packet().size() < 4) {
+ return Status::Error(400, "Packet is too small");
+ }
+ return result.packet().substr(4).str();
+}
+
+void NotificationManager::before_get_difference() {
+ if (is_disabled()) {
+ return;
+ }
+ if (running_get_difference_) {
+ return;
+ }
+
+ running_get_difference_ = true;
+ on_unreceived_notification_update_count_changed(1, 0, "before_get_difference");
+}
+
+void NotificationManager::after_get_difference() {
+ if (is_disabled()) {
+ return;
+ }
+
+ CHECK(running_get_difference_);
+ running_get_difference_ = false;
+ on_unreceived_notification_update_count_changed(-1, 0, "after_get_difference");
+ flush_pending_notifications_timeout_.set_timeout_in(0, MIN_NOTIFICATION_DELAY_MS * 1e-3);
+}
+
+void NotificationManager::after_get_difference_impl() {
+ if (running_get_difference_) {
+ return;
+ }
+
+ VLOG(notifications) << "After get difference";
+
+ vector<NotificationGroupId> to_remove_temporary_notifications_group_ids;
+ for (auto &group_it : groups_) {
+ const auto &group_key = group_it.first;
+ const auto &group = group_it.second;
+ if (running_get_chat_difference_.count(group_key.group_id.get()) == 0 &&
+ get_temporary_notification_total_count(group) > 0) {
+ to_remove_temporary_notifications_group_ids.push_back(group_key.group_id);
+ }
+ }
+ for (auto group_id : reversed(to_remove_temporary_notifications_group_ids)) {
+ remove_temporary_notifications(group_id, "after_get_difference");
+ }
+
+ flush_all_pending_updates(false, "after_get_difference");
+}
+
+void NotificationManager::before_get_chat_difference(NotificationGroupId group_id) {
+ if (is_disabled()) {
+ return;
+ }
+
+ VLOG(notifications) << "Before get chat difference in " << group_id;
+ CHECK(group_id.is_valid());
+ if (running_get_chat_difference_.insert(group_id.get()).second) {
+ on_unreceived_notification_update_count_changed(1, group_id.get(), "before_get_chat_difference");
+ }
+}
+
+void NotificationManager::after_get_chat_difference(NotificationGroupId group_id) {
+ if (is_disabled()) {
+ return;
+ }
+
+ VLOG(notifications) << "After get chat difference in " << group_id;
+ CHECK(group_id.is_valid());
+ auto erased_count = running_get_chat_difference_.erase(group_id.get());
+ if (erased_count == 1) {
+ flush_pending_notifications_timeout_.set_timeout_in(-group_id.get(), MIN_NOTIFICATION_DELAY_MS * 1e-3);
+ on_unreceived_notification_update_count_changed(-1, group_id.get(), "after_get_chat_difference");
+ }
+}
+
+void NotificationManager::after_get_chat_difference_impl(NotificationGroupId group_id) {
+ if (running_get_chat_difference_.count(group_id.get()) == 1) {
+ return;
+ }
+
+ VLOG(notifications) << "Flush updates after get chat difference in " << group_id;
+ CHECK(group_id.is_valid());
+ if (!running_get_difference_ && pending_updates_.count(group_id.get()) == 1) {
+ remove_temporary_notifications(group_id, "after_get_chat_difference");
+ force_flush_pending_updates(group_id, "after_get_chat_difference");
+ }
+}
+
+void NotificationManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ if (is_disabled() || max_notification_group_count_ == 0 || is_destroyed_) {
+ return;
+ }
+
+ updates.push_back(get_update_active_notifications());
+ updates.push_back(get_update_have_pending_notifications());
+}
+
+void NotificationManager::flush_all_notifications() {
+ flush_all_pending_notifications();
+ flush_all_pending_updates(true, "flush_all_notifications");
+}
+
+void NotificationManager::destroy_all_notifications() {
+ if (is_destroyed_) {
+ return;
+ }
+ is_being_destroyed_ = true;
+
+ size_t cur_pos = 0;
+ for (auto it = groups_.begin(); it != groups_.end() && cur_pos < max_notification_group_count_; ++it, cur_pos++) {
+ auto &group_key = it->first;
+ auto &group = it->second;
+
+ if (group_key.last_notification_date == 0) {
+ break;
+ }
+
+ VLOG(notifications) << "Destroy " << group_key.group_id;
+ send_remove_group_update(group_key, group, vector<int32>());
+ }
+
+ flush_all_pending_updates(true, "destroy_all_notifications");
+ if (delayed_notification_update_count_ != 0) {
+ on_delayed_notification_update_count_changed(-delayed_notification_update_count_, 0, "destroy_all_notifications");
+ }
+ if (unreceived_notification_update_count_ != 0) {
+ on_unreceived_notification_update_count_changed(-unreceived_notification_update_count_, 0,
+ "destroy_all_notifications");
+ }
+
+ while (!push_notification_promises_.empty()) {
+ on_notification_processed(push_notification_promises_.begin()->first);
+ }
+
+ is_destroyed_ = true;
+}
+
+td_api::object_ptr<td_api::updateHavePendingNotifications> NotificationManager::get_update_have_pending_notifications()
+ const {
+ return td_api::make_object<td_api::updateHavePendingNotifications>(delayed_notification_update_count_ != 0,
+ unreceived_notification_update_count_ != 0);
+}
+
+void NotificationManager::send_update_have_pending_notifications() const {
+ if (is_destroyed_ || !is_inited_ || !is_binlog_processed_) {
+ return;
+ }
+
+ auto update = get_update_have_pending_notifications();
+ VLOG(notifications) << "Send " << oneline(to_string(update));
+ send_closure(G()->td(), &Td::send_update, std::move(update));
+}
+
+void NotificationManager::on_delayed_notification_update_count_changed(int32 diff, int32 notification_group_id,
+ const char *source) {
+ bool had_delayed = delayed_notification_update_count_ != 0;
+ delayed_notification_update_count_ += diff;
+ CHECK(delayed_notification_update_count_ >= 0);
+ VLOG(notifications) << "Update delayed notification count with diff " << diff << " to "
+ << delayed_notification_update_count_ << " from group " << notification_group_id << " and "
+ << source;
+ bool have_delayed = delayed_notification_update_count_ != 0;
+ if (had_delayed != have_delayed) {
+ send_update_have_pending_notifications();
+ }
+}
+
+void NotificationManager::on_unreceived_notification_update_count_changed(int32 diff, int32 notification_group_id,
+ const char *source) {
+ bool had_unreceived = unreceived_notification_update_count_ != 0;
+ unreceived_notification_update_count_ += diff;
+ CHECK(unreceived_notification_update_count_ >= 0);
+ VLOG(notifications) << "Update unreceived notification count with diff " << diff << " to "
+ << unreceived_notification_update_count_ << " from group " << notification_group_id << " and "
+ << source;
+ bool have_unreceived = unreceived_notification_update_count_ != 0;
+ if (had_unreceived != have_unreceived) {
+ send_update_have_pending_notifications();
+ }
+}
+
+void NotificationManager::try_send_update_active_notifications() {
+ if (max_notification_group_count_ == 0) {
+ return;
+ }
+ if (!is_binlog_processed_ || !is_inited_) {
+ return;
+ }
+
+ auto update = get_update_active_notifications();
+ VLOG(notifications) << "Send " << as_active_notifications_update(update.get());
+ send_closure(G()->td(), &Td::send_update, std::move(update));
+
+ while (!push_notification_promises_.empty()) {
+ on_notification_processed(push_notification_promises_.begin()->first);
+ }
+}
+
+void NotificationManager::on_binlog_events(vector<BinlogEvent> &&events) {
+ if (G()->close_flag()) {
+ return;
+ }
+ VLOG(notifications) << "Begin to process " << events.size() << " binlog events";
+ for (auto &event : events) {
+ if (!G()->parameters().use_message_db || is_disabled() || max_notification_group_count_ == 0) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ switch (event.type_) {
+ case LogEvent::HandlerType::AddMessagePushNotification: {
+ CHECK(is_inited_);
+ AddMessagePushNotificationLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ add_message_push_notification(
+ log_event.dialog_id_, log_event.message_id_, log_event.random_id_, log_event.sender_user_id_,
+ log_event.sender_dialog_id_, log_event.sender_name_, log_event.date_, log_event.is_from_scheduled_,
+ log_event.contains_mention_, log_event.disable_notification_, 0, log_event.loc_key_, log_event.arg_,
+ log_event.photo_, log_event.document_, log_event.notification_id_, event.id_,
+ PromiseCreator::lambda([](Result<Unit> result) {
+ if (result.is_error() && result.error().code() != 200 && result.error().code() != 406) {
+ LOG(ERROR) << "Receive error " << result.error() << ", while processing message push notification";
+ }
+ }));
+ break;
+ }
+ case LogEvent::HandlerType::EditMessagePushNotification: {
+ CHECK(is_inited_);
+ EditMessagePushNotificationLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ edit_message_push_notification(
+ log_event.dialog_id_, log_event.message_id_, log_event.edit_date_, log_event.loc_key_, log_event.arg_,
+ log_event.photo_, log_event.document_, event.id_, PromiseCreator::lambda([](Result<Unit> result) {
+ if (result.is_error() && result.error().code() != 200 && result.error().code() != 406) {
+ LOG(ERROR) << "Receive error " << result.error() << ", while processing edit message push notification";
+ }
+ }));
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unsupported log event type " << event.type_;
+ }
+ }
+ if (is_inited_) {
+ flush_all_pending_notifications();
+ }
+ is_binlog_processed_ = true;
+ try_send_update_active_notifications();
+ VLOG(notifications) << "Finish processing binlog events";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationManager.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationManager.h
new file mode 100644
index 0000000000..02bdab0432
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationManager.h
@@ -0,0 +1,417 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/CallId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/Notification.h"
+#include "td/telegram/NotificationGroupId.h"
+#include "td/telegram/NotificationGroupKey.h"
+#include "td/telegram/NotificationGroupType.h"
+#include "td/telegram/NotificationId.h"
+#include "td/telegram/NotificationType.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/MultiTimeout.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/Time.h"
+
+#include <functional>
+#include <map>
+
+namespace td {
+
+extern int VERBOSITY_NAME(notifications);
+
+struct BinlogEvent;
+
+class Td;
+
+class NotificationManager final : public Actor {
+ public:
+ static constexpr int32 MIN_NOTIFICATION_GROUP_COUNT_MAX = 0;
+ static constexpr int32 MAX_NOTIFICATION_GROUP_COUNT_MAX = 25;
+ static constexpr int32 MIN_NOTIFICATION_GROUP_SIZE_MAX = 1;
+ static constexpr int32 MAX_NOTIFICATION_GROUP_SIZE_MAX = 25;
+
+ NotificationManager(Td *td, ActorShared<> parent);
+
+ void init();
+
+ size_t get_max_notification_group_size() const;
+
+ NotificationId get_max_notification_id() const;
+
+ NotificationId get_next_notification_id();
+
+ NotificationGroupId get_next_notification_group_id();
+
+ void try_reuse_notification_group_id(NotificationGroupId group_id);
+
+ void load_group_force(NotificationGroupId group_id);
+
+ void add_notification(NotificationGroupId group_id, NotificationGroupType group_type, DialogId dialog_id, int32 date,
+ DialogId notification_settings_dialog_id, bool disable_notification, int64 ringtone_id,
+ int32 min_delay_ms, NotificationId notification_id, unique_ptr<NotificationType> type,
+ const char *source);
+
+ void edit_notification(NotificationGroupId group_id, NotificationId notification_id,
+ unique_ptr<NotificationType> type);
+
+ void remove_notification(NotificationGroupId group_id, NotificationId notification_id, bool is_permanent,
+ bool force_update, Promise<Unit> &&promise, const char *source);
+
+ void remove_temporary_notifications(NotificationGroupId group_id, const char *source);
+
+ void remove_temporary_notification_by_message_id(NotificationGroupId group_id, MessageId message_id,
+ bool force_update, const char *source);
+
+ void remove_notification_group(NotificationGroupId group_id, NotificationId max_notification_id,
+ MessageId max_message_id, int32 new_total_count, bool force_update,
+ Promise<Unit> &&promise);
+
+ void set_notification_total_count(NotificationGroupId group_id, int32 new_total_count);
+
+ vector<MessageId> get_notification_group_message_ids(NotificationGroupId group_id);
+
+ void add_call_notification(DialogId dialog_id, CallId call_id);
+
+ void remove_call_notification(DialogId dialog_id, CallId call_id);
+
+ void get_disable_contact_registered_notifications(Promise<Unit> &&promise);
+
+ void on_notification_group_count_max_changed(bool send_updates);
+
+ void on_notification_group_size_max_changed();
+
+ void on_online_cloud_timeout_changed();
+
+ void on_notification_cloud_delay_changed();
+
+ void on_notification_default_delay_changed();
+
+ void on_disable_contact_registered_notifications_changed();
+
+ void process_push_notification(string payload, Promise<Unit> &&user_promise);
+
+ static Result<int64> get_push_receiver_id(string payload);
+
+ static Result<string> decrypt_push(int64 encryption_key_id, string encryption_key, string push); // public for tests
+
+ void before_get_difference();
+
+ void after_get_difference();
+
+ void before_get_chat_difference(NotificationGroupId group_id);
+
+ void after_get_chat_difference(NotificationGroupId group_id);
+
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
+ void flush_all_notifications();
+
+ void destroy_all_notifications();
+
+ void on_binlog_events(vector<BinlogEvent> &&events);
+
+ private:
+ static constexpr int32 DEFAULT_GROUP_COUNT_MAX = 0;
+ static constexpr int32 DEFAULT_GROUP_SIZE_MAX = 10;
+ static constexpr size_t EXTRA_GROUP_SIZE = 10;
+
+ static constexpr size_t MAX_CALL_NOTIFICATION_GROUPS = 10;
+ static constexpr size_t MAX_CALL_NOTIFICATIONS = 10;
+
+ static constexpr int32 DEFAULT_ONLINE_CLOUD_TIMEOUT_MS = 300000;
+ static constexpr int32 DEFAULT_ONLINE_CLOUD_DELAY_MS = 30000;
+ static constexpr int32 DEFAULT_DEFAULT_DELAY_MS = 1500;
+
+ static constexpr int32 MIN_NOTIFICATION_DELAY_MS = 1;
+
+ static constexpr int32 MIN_UPDATE_DELAY_MS = 50;
+ static constexpr int32 MAX_UPDATE_DELAY_MS = 60000;
+
+ static constexpr int32 ANNOUNCEMENT_ID_CACHE_TIME = 7 * 86400;
+
+ static constexpr int32 USER_FLAG_HAS_ACCESS_HASH = 1 << 0;
+ static constexpr int32 USER_FLAG_HAS_PHONE_NUMBER = 1 << 4;
+ static constexpr int32 USER_FLAG_IS_INACCESSIBLE = 1 << 20;
+
+ class AddMessagePushNotificationLogEvent;
+ class EditMessagePushNotificationLogEvent;
+
+ struct PendingNotification {
+ int32 date = 0;
+ DialogId settings_dialog_id;
+ bool disable_notification = false;
+ int64 ringtone_id = -1;
+ NotificationId notification_id;
+ unique_ptr<NotificationType> type;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const PendingNotification &pending_notification) {
+ return string_builder << "PendingNotification[" << pending_notification.notification_id << " of type "
+ << pending_notification.type << " sent at " << pending_notification.date
+ << " with settings from " << pending_notification.settings_dialog_id
+ << ", ringtone_id = " << pending_notification.ringtone_id << "]";
+ }
+ };
+
+ struct NotificationGroup {
+ int32 total_count = 0;
+ NotificationGroupType type = NotificationGroupType::Calls;
+ bool is_loaded_from_database = false;
+ bool is_being_loaded_from_database = false;
+
+ vector<Notification> notifications;
+
+ double pending_notifications_flush_time = 0;
+ vector<PendingNotification> pending_notifications;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const NotificationGroup &notification_group) {
+ return string_builder << "NotificationGroup[" << notification_group.type << " with total "
+ << notification_group.total_count << " notifications " << notification_group.notifications
+ << " + " << notification_group.pending_notifications
+ << ", is_loaded_from_database = " << notification_group.is_loaded_from_database
+ << ", is_being_loaded_from_database = " << notification_group.is_being_loaded_from_database
+ << ", pending_notifications_flush_time = "
+ << notification_group.pending_notifications_flush_time << ", now = " << Time::now() << "]";
+ }
+ };
+
+ struct ActiveNotificationsUpdate {
+ const td_api::updateActiveNotifications *update;
+ };
+
+ struct NotificationUpdate {
+ const td_api::Update *update;
+ };
+
+ enum class SyncState : int32 { NotSynced, Pending, Completed };
+
+ using NotificationGroups = std::map<NotificationGroupKey, NotificationGroup>;
+
+ static void on_flush_pending_notifications_timeout_callback(void *notification_manager_ptr, int64 group_id_int);
+
+ static void on_flush_pending_updates_timeout_callback(void *notification_manager_ptr, int64 group_id_int);
+
+ bool is_disabled() const;
+
+ void start_up() final;
+ void tear_down() final;
+
+ void add_update(int32 group_id, td_api::object_ptr<td_api::Update> update);
+
+ void add_update_notification_group(td_api::object_ptr<td_api::updateNotificationGroup> update);
+
+ void add_update_notification(NotificationGroupId notification_group_id, DialogId dialog_id,
+ const Notification &notification);
+
+ NotificationGroups::iterator add_group(NotificationGroupKey &&group_key, NotificationGroup &&group,
+ const char *source);
+
+ NotificationGroups::iterator get_group(NotificationGroupId group_id);
+
+ NotificationGroups::iterator get_group_force(NotificationGroupId group_id, bool send_update = true);
+
+ void delete_group(NotificationGroups::iterator &&group_it);
+
+ static NotificationId get_first_notification_id(const NotificationGroup &group);
+
+ static NotificationId get_last_notification_id(const NotificationGroup &group);
+
+ static MessageId get_first_message_id(const NotificationGroup &group);
+
+ static MessageId get_last_message_id(const NotificationGroup &group);
+
+ static MessageId get_last_message_id_by_notification_id(const NotificationGroup &group,
+ NotificationId max_notification_id);
+
+ static int32 get_temporary_notification_total_count(const NotificationGroup &group);
+
+ int32 load_message_notification_groups_from_database(int32 limit, bool send_update);
+
+ void load_message_notifications_from_database(const NotificationGroupKey &group_key, NotificationGroup &group,
+ size_t desired_size);
+
+ void on_get_message_notifications_from_database(NotificationGroupId group_id, size_t limit,
+ Result<vector<Notification>> r_notifications);
+
+ void add_notifications_to_group_begin(NotificationGroups::iterator group_it, vector<Notification> notifications);
+
+ NotificationGroupKey get_last_updated_group_key() const;
+
+ void try_send_update_active_notifications();
+
+ void send_update_have_pending_notifications() const;
+
+ td_api::object_ptr<td_api::updateHavePendingNotifications> get_update_have_pending_notifications() const;
+
+ td_api::object_ptr<td_api::updateActiveNotifications> get_update_active_notifications() const;
+
+ td_api::object_ptr<td_api::updateNotificationGroup> get_remove_group_update(
+ const NotificationGroupKey &group_key, const NotificationGroup &group,
+ vector<int32> &&removed_notification_ids) const;
+
+ void send_remove_group_update(const NotificationGroupKey &group_key, const NotificationGroup &group,
+ vector<int32> &&removed_notification_ids);
+
+ void send_add_group_update(const NotificationGroupKey &group_key, const NotificationGroup &group, const char *source);
+
+ int32 get_notification_delay_ms(DialogId dialog_id, const PendingNotification &notification,
+ int32 min_delay_ms) const;
+
+ bool do_flush_pending_notifications(NotificationGroupKey &group_key, NotificationGroup &group,
+ vector<PendingNotification> &pending_notifications) TD_WARN_UNUSED_RESULT;
+
+ void flush_pending_notifications(NotificationGroupId group_id);
+
+ void flush_all_pending_notifications();
+
+ void on_notification_processed(NotificationId notification_id);
+
+ void on_notification_removed(NotificationId notification_id);
+
+ void on_notifications_removed(NotificationGroups::iterator &&group_it,
+ vector<td_api::object_ptr<td_api::notification>> &&added_notifications,
+ vector<int32> &&removed_notification_ids, bool force_update);
+
+ void remove_added_notifications_from_pending_updates(
+ NotificationGroupId group_id,
+ const std::function<bool(const td_api::object_ptr<td_api::notification> &notification)> &is_removed);
+
+ void flush_pending_updates(int32 group_id, const char *source);
+
+ void force_flush_pending_updates(NotificationGroupId group_id, const char *source);
+
+ void flush_all_pending_updates(bool include_delayed_chats, const char *source);
+
+ NotificationGroupId get_call_notification_group_id(DialogId dialog_id);
+
+ static Result<string> decrypt_push_payload(int64 encryption_key_id, string encryption_key, string payload);
+
+ static string convert_loc_key(const string &loc_key);
+
+ Status process_push_notification_payload(string payload, bool was_encrypted, Promise<Unit> &promise);
+
+ void add_message_push_notification(DialogId dialog_id, MessageId message_id, int64 random_id, UserId sender_user_id,
+ DialogId sender_dialog_id, string sender_name, int32 date, bool is_from_scheduled,
+ bool contains_mention, bool disable_notification, int64 ringtone_id,
+ string loc_key, string arg, Photo photo, Document document,
+ NotificationId notification_id, uint64 log_event_id, Promise<Unit> promise);
+
+ void edit_message_push_notification(DialogId dialog_id, MessageId message_id, int32 edit_date, string loc_key,
+ string arg, Photo photo, Document document, uint64 log_event_id,
+ Promise<Unit> promise);
+
+ void after_get_difference_impl();
+
+ void after_get_chat_difference_impl(NotificationGroupId group_id);
+
+ void on_delayed_notification_update_count_changed(int32 diff, int32 notification_group_id, const char *source);
+
+ void on_unreceived_notification_update_count_changed(int32 diff, int32 notification_group_id, const char *source);
+
+ static string get_is_contact_registered_notifications_synchronized_key();
+
+ void set_contact_registered_notifications_sync_state(SyncState new_state);
+
+ void run_contact_registered_notifications_sync();
+
+ void on_contact_registered_notifications_sync(bool is_disabled, Result<Unit> result);
+
+ void on_get_disable_contact_registered_notifications(bool is_disabled, Promise<Unit> &&promise);
+
+ void save_announcement_ids();
+
+ static ActiveNotificationsUpdate as_active_notifications_update(const td_api::updateActiveNotifications *update);
+
+ static NotificationUpdate as_notification_update(const td_api::Update *update);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const ActiveNotificationsUpdate &update);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const NotificationUpdate &update);
+
+ NotificationId current_notification_id_;
+ NotificationGroupId current_notification_group_id_;
+
+ size_t max_notification_group_count_ = 0;
+ size_t max_notification_group_size_ = 0;
+ size_t keep_notification_group_size_ = 0;
+
+ int32 online_cloud_timeout_ms_ = DEFAULT_ONLINE_CLOUD_TIMEOUT_MS;
+ int32 notification_cloud_delay_ms_ = DEFAULT_ONLINE_CLOUD_DELAY_MS;
+ int32 notification_default_delay_ms_ = DEFAULT_DEFAULT_DELAY_MS;
+
+ int32 delayed_notification_update_count_ = 0;
+ int32 unreceived_notification_update_count_ = 0;
+
+ NotificationGroupKey last_loaded_notification_group_key_;
+
+ SyncState contact_registered_notifications_sync_state_ = SyncState::NotSynced;
+ bool disable_contact_registered_notifications_ = false;
+
+ bool is_being_destroyed_ = false;
+ bool is_destroyed_ = false;
+
+ bool is_inited_ = false;
+ bool is_binlog_processed_ = false;
+
+ bool running_get_difference_ = false;
+ FlatHashSet<int32> running_get_chat_difference_;
+
+ NotificationGroups groups_;
+ FlatHashMap<NotificationGroupId, NotificationGroupKey, NotificationGroupIdHash> group_keys_;
+
+ FlatHashMap<int32, vector<td_api::object_ptr<td_api::Update>>> pending_updates_;
+
+ MultiTimeout flush_pending_notifications_timeout_{"FlushPendingNotificationsTimeout"};
+ MultiTimeout flush_pending_updates_timeout_{"FlushPendingUpdatesTimeout"};
+
+ vector<NotificationGroupId> call_notification_group_ids_;
+ FlatHashSet<NotificationGroupId, NotificationGroupIdHash> available_call_notification_group_ids_;
+ FlatHashMap<DialogId, NotificationGroupId, DialogIdHash> dialog_id_to_call_notification_group_id_;
+
+ FlatHashMap<NotificationId, uint64, NotificationIdHash> temporary_notification_log_event_ids_;
+ FlatHashMap<NotificationId, uint64, NotificationIdHash> temporary_edit_notification_log_event_ids_;
+ struct TemporaryNotification {
+ NotificationGroupId group_id;
+ NotificationId notification_id;
+ UserId sender_user_id;
+ DialogId sender_dialog_id;
+ string sender_name;
+ bool is_outgoing;
+ };
+ FlatHashMap<FullMessageId, TemporaryNotification, FullMessageIdHash> temporary_notifications_;
+ FlatHashMap<NotificationId, FullMessageId, NotificationIdHash> temporary_notification_message_ids_;
+ FlatHashMap<NotificationId, vector<Promise<Unit>>, NotificationIdHash> push_notification_promises_;
+
+ struct ActiveCallNotification {
+ CallId call_id;
+ NotificationId notification_id;
+ };
+ FlatHashMap<DialogId, vector<ActiveCallNotification>, DialogIdHash> active_call_notifications_;
+
+ FlatHashMap<int32, int32> announcement_id_date_;
+
+ Td *td_;
+ ActorShared<> parent_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsManager.cpp
new file mode 100644
index 0000000000..092c64be9c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsManager.cpp
@@ -0,0 +1,1504 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/NotificationSettingsManager.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/AudiosManager.h"
+#include "td/telegram/AudiosManager.hpp"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/logevent/LogEventHelper.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/NotificationManager.h"
+#include "td/telegram/NotificationSound.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/ScopeNotificationSettings.hpp"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UpdatesManager.h"
+#include "td/telegram/VoiceNotesManager.h"
+
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/MimeType.h"
+#include "td/utils/misc.h"
+#include "td/utils/PathView.h"
+#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+#include <algorithm>
+
+namespace td {
+
+class UploadRingtoneQuery final : public Td::ResultHandler {
+ FileId file_id_;
+ Promise<telegram_api::object_ptr<telegram_api::Document>> promise_;
+
+ public:
+ explicit UploadRingtoneQuery(Promise<telegram_api::object_ptr<telegram_api::Document>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(FileId file_id, tl_object_ptr<telegram_api::InputFile> &&input_file, const string &file_name,
+ const string &mime_type) {
+ CHECK(input_file != nullptr);
+ file_id_ = file_id;
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::account_uploadRingtone(std::move(input_file), file_name, mime_type), {{"ringtone"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_uploadRingtone>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for UploadRingtoneQuery: " << to_string(result);
+ promise_.set_value(std::move(result));
+ }
+
+ void on_error(Status status) final {
+ if (FileReferenceManager::is_file_reference_error(status)) {
+ LOG(ERROR) << "Receive file reference error " << status;
+ }
+ if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
+ // TODO support FILE_PART_*_MISSING
+ }
+
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+ td_->notification_settings_manager_->reload_saved_ringtones(Auto());
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SaveRingtoneQuery final : public Td::ResultHandler {
+ FileId file_id_;
+ string file_reference_;
+ bool unsave_ = false;
+
+ Promise<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> promise_;
+
+ public:
+ explicit SaveRingtoneQuery(Promise<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(FileId file_id, tl_object_ptr<telegram_api::inputDocument> &&input_document, bool unsave) {
+ CHECK(input_document != nullptr);
+ CHECK(file_id.is_valid());
+ file_id_ = file_id;
+ file_reference_ = input_document->file_reference_.as_slice().str();
+ unsave_ = unsave;
+
+ send_query(G()->net_query_creator().create(telegram_api::account_saveRingtone(std::move(input_document), unsave),
+ {{"ringtone"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_saveRingtone>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SaveRingtoneQuery: " << to_string(result);
+ promise_.set_value(std::move(result));
+ }
+
+ void on_error(Status status) final {
+ if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ VLOG(file_references) << "Receive " << status << " for " << file_id_;
+ td_->file_manager_->delete_file_reference(file_id_, file_reference_);
+ td_->file_reference_manager_->repair_file_reference(
+ file_id_, PromiseCreator::lambda([ringtone_id = file_id_, unsave = unsave_,
+ promise = std::move(promise_)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(Status::Error(400, "Failed to find the ringtone"));
+ }
+
+ send_closure(G()->notification_settings_manager(), &NotificationSettingsManager::send_save_ringtone_query,
+ ringtone_id, unsave, std::move(promise));
+ }));
+ return;
+ }
+
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for SaveRingtoneQuery: " << status;
+ }
+ td_->notification_settings_manager_->reload_saved_ringtones(Auto());
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetSavedRingtonesQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> promise_;
+
+ public:
+ explicit GetSavedRingtonesQuery(Promise<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(int64 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::account_getSavedRingtones(hash), {{"ringtone"}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getSavedRingtones>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetSavedRingtonesQuery: " << to_string(ptr);
+
+ promise_.set_value(std::move(ptr));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetDialogNotifySettingsQuery final : public Td::ResultHandler {
+ DialogId dialog_id_;
+
+ public:
+ void send(DialogId dialog_id) {
+ dialog_id_ = dialog_id;
+ auto input_notify_peer = td_->notification_settings_manager_->get_input_notify_peer(dialog_id);
+ CHECK(input_notify_peer != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::account_getNotifySettings(std::move(input_notify_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getNotifySettings>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ td_->messages_manager_->on_update_dialog_notify_settings(dialog_id_, std::move(ptr),
+ "GetDialogNotifySettingsQuery");
+ td_->notification_settings_manager_->on_get_dialog_notification_settings_query_finished(dialog_id_, Status::OK());
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetDialogNotifySettingsQuery");
+ td_->notification_settings_manager_->on_get_dialog_notification_settings_query_finished(dialog_id_,
+ std::move(status));
+ }
+};
+
+class GetNotifySettingsExceptionsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit GetNotifySettingsExceptionsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(NotificationSettingsScope scope, bool filter_scope, bool compare_sound) {
+ int32 flags = 0;
+ tl_object_ptr<telegram_api::InputNotifyPeer> input_notify_peer;
+ if (filter_scope) {
+ flags |= telegram_api::account_getNotifyExceptions::PEER_MASK;
+ input_notify_peer = get_input_notify_peer(scope);
+ }
+ if (compare_sound) {
+ flags |= telegram_api::account_getNotifyExceptions::COMPARE_SOUND_MASK;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::account_getNotifyExceptions(flags, false /*ignored*/, std::move(input_notify_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getNotifyExceptions>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto updates_ptr = result_ptr.move_as_ok();
+ auto dialog_ids = UpdatesManager::get_update_notify_settings_dialog_ids(updates_ptr.get());
+ vector<tl_object_ptr<telegram_api::User>> users;
+ vector<tl_object_ptr<telegram_api::Chat>> chats;
+ switch (updates_ptr->get_id()) {
+ case telegram_api::updatesCombined::ID: {
+ auto updates = static_cast<telegram_api::updatesCombined *>(updates_ptr.get());
+ users = std::move(updates->users_);
+ chats = std::move(updates->chats_);
+ reset_to_empty(updates->users_);
+ reset_to_empty(updates->chats_);
+ break;
+ }
+ case telegram_api::updates::ID: {
+ auto updates = static_cast<telegram_api::updates *>(updates_ptr.get());
+ users = std::move(updates->users_);
+ chats = std::move(updates->chats_);
+ reset_to_empty(updates->users_);
+ reset_to_empty(updates->chats_);
+ break;
+ }
+ }
+ td_->contacts_manager_->on_get_users(std::move(users), "GetNotifySettingsExceptionsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(chats), "GetNotifySettingsExceptionsQuery");
+ for (auto &dialog_id : dialog_ids) {
+ td_->messages_manager_->force_create_dialog(dialog_id, "GetNotifySettingsExceptionsQuery");
+ }
+ td_->updates_manager_->on_get_updates(std::move(updates_ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetScopeNotifySettingsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ NotificationSettingsScope scope_;
+
+ public:
+ explicit GetScopeNotifySettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(NotificationSettingsScope scope) {
+ scope_ = scope;
+ auto input_notify_peer = get_input_notify_peer(scope);
+ CHECK(input_notify_peer != nullptr);
+ send_query(G()->net_query_creator().create(telegram_api::account_getNotifySettings(std::move(input_notify_peer))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getNotifySettings>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ td_->notification_settings_manager_->on_update_scope_notify_settings(scope_, std::move(ptr));
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class UpdateDialogNotifySettingsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit UpdateDialogNotifySettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(DialogId dialog_id, const DialogNotificationSettings &new_settings) {
+ dialog_id_ = dialog_id;
+
+ auto input_notify_peer = td_->notification_settings_manager_->get_input_notify_peer(dialog_id);
+ if (input_notify_peer == nullptr) {
+ return on_error(Status::Error(500, "Can't update chat notification settings"));
+ }
+
+ int32 flags = 0;
+ if (!new_settings.use_default_mute_until) {
+ flags |= telegram_api::inputPeerNotifySettings::MUTE_UNTIL_MASK;
+ }
+ if (new_settings.sound != nullptr) {
+ flags |= telegram_api::inputPeerNotifySettings::SOUND_MASK;
+ }
+ if (!new_settings.use_default_show_preview) {
+ flags |= telegram_api::inputPeerNotifySettings::SHOW_PREVIEWS_MASK;
+ }
+ if (new_settings.silent_send_message) {
+ flags |= telegram_api::inputPeerNotifySettings::SILENT_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::account_updateNotifySettings(
+ std::move(input_notify_peer), make_tl_object<telegram_api::inputPeerNotifySettings>(
+ flags, new_settings.show_preview, new_settings.silent_send_message,
+ new_settings.mute_until, get_input_notification_sound(new_settings.sound)))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_updateNotifySettings>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ if (!result) {
+ return on_error(Status::Error(400, "Receive false as result"));
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "UpdateDialogNotifySettingsQuery")) {
+ LOG(INFO) << "Receive error for set chat notification settings: " << status;
+ }
+
+ if (!td_->auth_manager_->is_bot() &&
+ td_->notification_settings_manager_->get_input_notify_peer(dialog_id_) != nullptr) {
+ // trying to repair notification settings for this dialog
+ td_->notification_settings_manager_->send_get_dialog_notification_settings_query(dialog_id_, Promise<>());
+ }
+
+ promise_.set_error(std::move(status));
+ }
+};
+
+class UpdateScopeNotifySettingsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ NotificationSettingsScope scope_;
+
+ public:
+ explicit UpdateScopeNotifySettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(NotificationSettingsScope scope, const ScopeNotificationSettings &new_settings) {
+ auto input_notify_peer = get_input_notify_peer(scope);
+ CHECK(input_notify_peer != nullptr);
+ int32 flags = telegram_api::inputPeerNotifySettings::MUTE_UNTIL_MASK |
+ telegram_api::inputPeerNotifySettings::SHOW_PREVIEWS_MASK;
+ if (new_settings.sound != nullptr) {
+ flags |= telegram_api::inputPeerNotifySettings::SOUND_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::account_updateNotifySettings(
+ std::move(input_notify_peer), make_tl_object<telegram_api::inputPeerNotifySettings>(
+ flags, new_settings.show_preview, false, new_settings.mute_until,
+ get_input_notification_sound(new_settings.sound)))));
+ scope_ = scope;
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_updateNotifySettings>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ if (!result) {
+ return on_error(Status::Error(400, "Receive false as result"));
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for set notification settings: " << status;
+
+ if (!td_->auth_manager_->is_bot()) {
+ // trying to repair notification settings for this scope
+ td_->notification_settings_manager_->send_get_scope_notification_settings_query(scope_, Promise<>());
+ }
+
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ResetNotifySettingsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ResetNotifySettingsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::account_resetNotifySettings()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_resetNotifySettings>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ if (!result) {
+ return on_error(Status::Error(400, "Receive false as result"));
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for reset notification settings: " << status;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class NotificationSettingsManager::UploadRingtoneCallback final : public FileManager::UploadCallback {
+ public:
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
+ send_closure_later(G()->notification_settings_manager(), &NotificationSettingsManager::on_upload_ringtone, file_id,
+ std::move(input_file));
+ }
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
+ UNREACHABLE();
+ }
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
+ UNREACHABLE();
+ }
+ void on_upload_error(FileId file_id, Status error) final {
+ send_closure_later(G()->notification_settings_manager(), &NotificationSettingsManager::on_upload_ringtone_error,
+ file_id, std::move(error));
+ }
+};
+
+class NotificationSettingsManager::RingtoneListLogEvent {
+ public:
+ int64 hash_;
+ vector<FileId> ringtone_file_ids_;
+
+ RingtoneListLogEvent() = default;
+
+ RingtoneListLogEvent(int64 hash, vector<FileId> ringtone_file_ids)
+ : hash_(hash), ringtone_file_ids_(std::move(ringtone_file_ids)) {
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(hash_, storer);
+ AudiosManager *audios_manager = storer.context()->td().get_actor_unsafe()->audios_manager_.get();
+ td::store(narrow_cast<int32>(ringtone_file_ids_.size()), storer);
+ for (auto ringtone_file_id : ringtone_file_ids_) {
+ audios_manager->store_audio(ringtone_file_id, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(hash_, parser);
+ AudiosManager *audios_manager = parser.context()->td().get_actor_unsafe()->audios_manager_.get();
+ int32 size = parser.fetch_int();
+ ringtone_file_ids_.resize(size);
+ for (auto &ringtone_file_id : ringtone_file_ids_) {
+ ringtone_file_id = audios_manager->parse_audio(parser);
+ }
+ }
+};
+
+NotificationSettingsManager::NotificationSettingsManager(Td *td, ActorShared<> parent)
+ : td_(td), parent_(std::move(parent)) {
+ upload_ringtone_callback_ = std::make_shared<UploadRingtoneCallback>();
+
+ scope_unmute_timeout_.set_callback(on_scope_unmute_timeout_callback);
+ scope_unmute_timeout_.set_callback_data(static_cast<void *>(this));
+}
+
+NotificationSettingsManager::~NotificationSettingsManager() = default;
+
+void NotificationSettingsManager::tear_down() {
+ parent_.reset();
+}
+
+void NotificationSettingsManager::start_up() {
+ init();
+}
+
+void NotificationSettingsManager::init() {
+ if (is_inited_) {
+ return;
+ }
+ is_inited_ = true;
+
+ bool is_authorized = td_->auth_manager_->is_authorized();
+ bool was_authorized_user = td_->auth_manager_->was_authorized() && !td_->auth_manager_->is_bot();
+ if (was_authorized_user) {
+ for (auto scope :
+ {NotificationSettingsScope::Private, NotificationSettingsScope::Group, NotificationSettingsScope::Channel}) {
+ auto notification_settings_string =
+ G()->td_db()->get_binlog_pmc()->get(get_notification_settings_scope_database_key(scope));
+ if (!notification_settings_string.empty()) {
+ auto current_settings = get_scope_notification_settings(scope);
+ CHECK(current_settings != nullptr);
+ log_event_parse(*current_settings, notification_settings_string).ensure();
+
+ VLOG(notifications) << "Loaded notification settings in " << scope << ": " << *current_settings;
+
+ schedule_scope_unmute(scope, current_settings->mute_until);
+
+ send_closure(G()->td(), &Td::send_update, get_update_scope_notification_settings_object(scope));
+ }
+ }
+ if (!channels_notification_settings_.is_synchronized && is_authorized) {
+ channels_notification_settings_ = ScopeNotificationSettings(
+ chats_notification_settings_.mute_until, dup_notification_sound(chats_notification_settings_.sound),
+ chats_notification_settings_.show_preview, false, false);
+ channels_notification_settings_.is_synchronized = false;
+ send_get_scope_notification_settings_query(NotificationSettingsScope::Channel, Promise<>());
+ }
+ }
+ G()->td_db()->get_binlog_pmc()->erase("nsfac");
+}
+
+void NotificationSettingsManager::on_scope_unmute_timeout_callback(void *notification_settings_manager_ptr,
+ int64 scope_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(1 <= scope_int && scope_int <= 3);
+ auto notification_settings_manager = static_cast<NotificationSettingsManager *>(notification_settings_manager_ptr);
+ send_closure_later(notification_settings_manager->actor_id(notification_settings_manager),
+ &NotificationSettingsManager::on_scope_unmute,
+ static_cast<NotificationSettingsScope>(scope_int - 1));
+}
+
+void NotificationSettingsManager::timeout_expired() {
+ reload_saved_ringtones(Promise<Unit>());
+}
+
+int32 NotificationSettingsManager::get_scope_mute_until(NotificationSettingsScope scope) const {
+ return get_scope_notification_settings(scope)->mute_until;
+}
+
+const unique_ptr<NotificationSound> &NotificationSettingsManager::get_scope_notification_sound(
+ NotificationSettingsScope scope) const {
+ return get_scope_notification_settings(scope)->sound;
+}
+
+bool NotificationSettingsManager::get_scope_show_preview(NotificationSettingsScope scope) const {
+ return get_scope_notification_settings(scope)->show_preview;
+}
+
+bool NotificationSettingsManager::get_scope_disable_pinned_message_notifications(
+ NotificationSettingsScope scope) const {
+ return get_scope_notification_settings(scope)->disable_pinned_message_notifications;
+}
+
+bool NotificationSettingsManager::get_scope_disable_mention_notifications(NotificationSettingsScope scope) const {
+ return get_scope_notification_settings(scope)->disable_mention_notifications;
+}
+
+tl_object_ptr<telegram_api::InputNotifyPeer> NotificationSettingsManager::get_input_notify_peer(
+ DialogId dialog_id) const {
+ if (!td_->messages_manager_->have_dialog(dialog_id)) {
+ return nullptr;
+ }
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return nullptr;
+ }
+ return make_tl_object<telegram_api::inputNotifyPeer>(std::move(input_peer));
+}
+
+ScopeNotificationSettings *NotificationSettingsManager::get_scope_notification_settings(
+ NotificationSettingsScope scope) {
+ switch (scope) {
+ case NotificationSettingsScope::Private:
+ return &users_notification_settings_;
+ case NotificationSettingsScope::Group:
+ return &chats_notification_settings_;
+ case NotificationSettingsScope::Channel:
+ return &channels_notification_settings_;
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+const ScopeNotificationSettings *NotificationSettingsManager::get_scope_notification_settings(
+ NotificationSettingsScope scope) const {
+ switch (scope) {
+ case NotificationSettingsScope::Private:
+ return &users_notification_settings_;
+ case NotificationSettingsScope::Group:
+ return &chats_notification_settings_;
+ case NotificationSettingsScope::Channel:
+ return &channels_notification_settings_;
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+td_api::object_ptr<td_api::updateScopeNotificationSettings>
+NotificationSettingsManager::get_update_scope_notification_settings_object(NotificationSettingsScope scope) const {
+ auto notification_settings = get_scope_notification_settings(scope);
+ CHECK(notification_settings != nullptr);
+ return td_api::make_object<td_api::updateScopeNotificationSettings>(
+ get_notification_settings_scope_object(scope), get_scope_notification_settings_object(notification_settings));
+}
+
+void NotificationSettingsManager::on_scope_unmute(NotificationSettingsScope scope) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ auto notification_settings = get_scope_notification_settings(scope);
+ CHECK(notification_settings != nullptr);
+
+ if (notification_settings->mute_until == 0) {
+ return;
+ }
+
+ auto now = G()->unix_time();
+ if (notification_settings->mute_until > now) {
+ LOG(ERROR) << "Failed to unmute " << scope << " in " << now << ", will be unmuted in "
+ << notification_settings->mute_until;
+ schedule_scope_unmute(scope, notification_settings->mute_until);
+ return;
+ }
+
+ LOG(INFO) << "Unmute " << scope;
+ update_scope_unmute_timeout(scope, notification_settings->mute_until, 0);
+ send_closure(G()->td(), &Td::send_update, get_update_scope_notification_settings_object(scope));
+ save_scope_notification_settings(scope, *notification_settings);
+}
+
+string NotificationSettingsManager::get_notification_settings_scope_database_key(NotificationSettingsScope scope) {
+ switch (scope) {
+ case NotificationSettingsScope::Private:
+ return "nsfpc";
+ case NotificationSettingsScope::Group:
+ return "nsfgc";
+ case NotificationSettingsScope::Channel:
+ return "nsfcc";
+ default:
+ UNREACHABLE();
+ return "";
+ }
+}
+
+void NotificationSettingsManager::save_scope_notification_settings(NotificationSettingsScope scope,
+ const ScopeNotificationSettings &new_settings) {
+ string key = get_notification_settings_scope_database_key(scope);
+ G()->td_db()->get_binlog_pmc()->set(key, log_event_store(new_settings).as_slice().str());
+}
+
+void NotificationSettingsManager::on_update_scope_notify_settings(
+ NotificationSettingsScope scope, tl_object_ptr<telegram_api::peerNotifySettings> &&peer_notify_settings) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto old_notification_settings = get_scope_notification_settings(scope);
+ CHECK(old_notification_settings != nullptr);
+
+ ScopeNotificationSettings notification_settings = ::td::get_scope_notification_settings(
+ std::move(peer_notify_settings), old_notification_settings->disable_pinned_message_notifications,
+ old_notification_settings->disable_mention_notifications);
+ if (!notification_settings.is_synchronized) {
+ return;
+ }
+
+ update_scope_notification_settings(scope, old_notification_settings, std::move(notification_settings));
+}
+
+bool NotificationSettingsManager::update_scope_notification_settings(NotificationSettingsScope scope,
+ ScopeNotificationSettings *current_settings,
+ ScopeNotificationSettings &&new_settings) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return false;
+ }
+
+ bool need_update_server = current_settings->mute_until != new_settings.mute_until ||
+ !are_equivalent_notification_sounds(current_settings->sound, new_settings.sound) ||
+ current_settings->show_preview != new_settings.show_preview;
+ bool need_update_local =
+ current_settings->disable_pinned_message_notifications != new_settings.disable_pinned_message_notifications ||
+ current_settings->disable_mention_notifications != new_settings.disable_mention_notifications;
+ bool was_inited = current_settings->is_synchronized;
+ bool is_inited = new_settings.is_synchronized;
+ if (was_inited && !is_inited) {
+ return false; // just in case
+ }
+ bool is_changed = need_update_server || need_update_local || was_inited != is_inited ||
+ are_different_equivalent_notification_sounds(current_settings->sound, new_settings.sound);
+ if (is_changed) {
+ save_scope_notification_settings(scope, new_settings);
+
+ VLOG(notifications) << "Update notification settings in " << scope << " from " << *current_settings << " to "
+ << new_settings;
+
+ update_scope_unmute_timeout(scope, current_settings->mute_until, new_settings.mute_until);
+
+ if (!current_settings->disable_pinned_message_notifications && new_settings.disable_pinned_message_notifications) {
+ td_->messages_manager_->remove_scope_pinned_message_notifications(scope);
+ }
+ if (current_settings->disable_mention_notifications != new_settings.disable_mention_notifications) {
+ td_->messages_manager_->on_update_scope_mention_notifications(scope, new_settings.disable_mention_notifications);
+ }
+
+ *current_settings = std::move(new_settings);
+
+ send_closure(G()->td(), &Td::send_update, get_update_scope_notification_settings_object(scope));
+ }
+ return need_update_server;
+}
+
+void NotificationSettingsManager::schedule_scope_unmute(NotificationSettingsScope scope, int32 mute_until) {
+ auto now = G()->unix_time_cached();
+ if (mute_until >= now && mute_until < now + 366 * 86400) {
+ scope_unmute_timeout_.set_timeout_in(static_cast<int64>(scope) + 1, mute_until - now + 1);
+ } else {
+ scope_unmute_timeout_.cancel_timeout(static_cast<int64>(scope) + 1);
+ }
+}
+
+void NotificationSettingsManager::update_scope_unmute_timeout(NotificationSettingsScope scope, int32 &old_mute_until,
+ int32 new_mute_until) {
+ if (td_->auth_manager_->is_bot()) {
+ // just in case
+ return;
+ }
+
+ LOG(INFO) << "Update " << scope << " unmute timeout from " << old_mute_until << " to " << new_mute_until;
+ if (old_mute_until == new_mute_until) {
+ return;
+ }
+ CHECK(old_mute_until >= 0);
+
+ schedule_scope_unmute(scope, new_mute_until);
+
+ auto was_muted = old_mute_until != 0;
+ auto is_muted = new_mute_until != 0;
+
+ old_mute_until = new_mute_until;
+
+ if (was_muted != is_muted) {
+ td_->messages_manager_->on_update_notification_scope_is_muted(scope, is_muted);
+ }
+}
+
+void NotificationSettingsManager::reset_scope_notification_settings() {
+ CHECK(!td_->auth_manager_->is_bot());
+
+ for (auto scope :
+ {NotificationSettingsScope::Private, NotificationSettingsScope::Group, NotificationSettingsScope::Channel}) {
+ auto current_settings = get_scope_notification_settings(scope);
+ CHECK(current_settings != nullptr);
+ ScopeNotificationSettings new_scope_settings;
+ new_scope_settings.is_synchronized = true;
+ update_scope_notification_settings(scope, current_settings, std::move(new_scope_settings));
+ }
+}
+
+bool NotificationSettingsManager::is_active() const {
+ return !G()->close_flag() && td_->auth_manager_->is_authorized() && !td_->auth_manager_->is_bot();
+}
+
+FileId NotificationSettingsManager::get_saved_ringtone(int64 ringtone_id, Promise<Unit> &&promise) {
+ if (!are_saved_ringtones_loaded_) {
+ load_saved_ringtones(std::move(promise));
+ return {};
+ }
+
+ promise.set_value(Unit());
+ for (auto &file_id : saved_ringtone_file_ids_) {
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.empty());
+ CHECK(file_view.get_type() == FileType::Ringtone);
+ CHECK(file_view.has_remote_location());
+ if (file_view.remote_location().get_id() == ringtone_id) {
+ return file_view.get_main_file_id();
+ }
+ }
+ return {};
+}
+
+vector<FileId> NotificationSettingsManager::get_saved_ringtones(Promise<Unit> &&promise) {
+ if (!are_saved_ringtones_loaded_) {
+ load_saved_ringtones(std::move(promise));
+ return {};
+ }
+
+ promise.set_value(Unit());
+ return saved_ringtone_file_ids_;
+}
+
+void NotificationSettingsManager::send_save_ringtone_query(
+ FileId ringtone_file_id, bool unsave,
+ Promise<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ // TODO log event
+ auto file_view = td_->file_manager_->get_file_view(ringtone_file_id);
+ CHECK(!file_view.empty());
+ CHECK(file_view.has_remote_location());
+ CHECK(file_view.remote_location().is_document());
+ CHECK(!file_view.remote_location().is_web());
+ td_->create_handler<SaveRingtoneQuery>(std::move(promise))
+ ->send(ringtone_file_id, file_view.remote_location().as_input_document(), unsave);
+}
+
+void NotificationSettingsManager::add_saved_ringtone(td_api::object_ptr<td_api::InputFile> &&input_file,
+ Promise<td_api::object_ptr<td_api::notificationSound>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (!are_saved_ringtones_loaded_) {
+ load_saved_ringtones(PromiseCreator::lambda([actor_id = actor_id(this), input_file = std::move(input_file),
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+
+ send_closure(actor_id, &NotificationSettingsManager::add_saved_ringtone, std::move(input_file),
+ std::move(promise));
+ }));
+ return;
+ }
+
+ auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Ringtone, input_file, DialogId(), false, false);
+ if (r_file_id.is_error()) {
+ // TODO promise.set_error(r_file_id.move_as_error());
+ return promise.set_error(Status::Error(400, r_file_id.error().message()));
+ }
+ FileId file_id = r_file_id.ok();
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.empty());
+ if (file_view.size() > td_->option_manager_->get_option_integer("notification_sound_size_max")) {
+ return promise.set_error(Status::Error(400, "Notification sound file is too big"));
+ }
+ auto file_type = file_view.get_type();
+ int32 duration = 0;
+ switch (file_type) {
+ case FileType::Audio:
+ duration = td_->audios_manager_->get_audio_duration(file_id);
+ break;
+ case FileType::VoiceNote:
+ duration = td_->voice_notes_manager_->get_voice_note_duration(file_id);
+ break;
+ default:
+ break;
+ }
+ if (duration > td_->option_manager_->get_option_integer("notification_sound_duration_max")) {
+ return promise.set_error(Status::Error(400, "Notification sound is too long"));
+ }
+ if (file_view.has_remote_location() && !file_view.is_encrypted()) {
+ CHECK(file_view.remote_location().is_document());
+ if (file_view.main_remote_location().is_web()) {
+ return promise.set_error(Status::Error(400, "Can't use web document as notification sound"));
+ }
+
+ FileId ringtone_file_id = file_view.get_main_file_id();
+ if (file_type != FileType::Ringtone) {
+ if (file_type != FileType::Audio && file_type != FileType::VoiceNote) {
+ return promise.set_error(Status::Error(400, "Unsupported file specified"));
+ }
+ auto &remote = file_view.main_remote_location();
+ ringtone_file_id = td_->file_manager_->register_remote(
+ FullRemoteFileLocation(FileType::Ringtone, remote.get_id(), remote.get_access_hash(), remote.get_dc_id(),
+ remote.get_file_reference().str()),
+ FileLocationSource::FromServer, DialogId(), file_view.size(), file_view.expected_size(),
+ file_view.suggested_path());
+ }
+
+ if (file_type != FileType::VoiceNote) {
+ for (auto &saved_ringtone_file_id : saved_ringtone_file_ids_) {
+ if (ringtone_file_id == saved_ringtone_file_id) {
+ return promise.set_value(td_->audios_manager_->get_notification_sound_object(ringtone_file_id));
+ }
+ }
+ }
+
+ send_save_ringtone_query(
+ file_view.get_main_file_id(), false,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), file_id = ringtone_file_id, promise = std::move(promise)](
+ Result<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &NotificationSettingsManager::on_add_saved_ringtone, file_id,
+ result.move_as_ok(), std::move(promise));
+ }
+ }));
+ return;
+ }
+
+ auto download_file_id = td_->file_manager_->dup_file_id(file_id, "add_saved_ringtone");
+ file_id = td_->file_manager_
+ ->register_generate(FileType::Ringtone, FileLocationSource::FromServer, file_view.suggested_path(),
+ PSTRING() << "#file_id#" << download_file_id.get(), DialogId(), file_view.size())
+ .ok();
+
+ upload_ringtone(file_id, false, std::move(promise));
+}
+
+void NotificationSettingsManager::upload_ringtone(FileId file_id, bool is_reupload,
+ Promise<td_api::object_ptr<td_api::notificationSound>> &&promise,
+ vector<int> bad_parts) {
+ CHECK(file_id.is_valid());
+ LOG(INFO) << "Ask to upload ringtone " << file_id;
+ bool is_inserted =
+ being_uploaded_ringtones_.emplace(file_id, UploadedRingtone{is_reupload, std::move(promise)}).second;
+ CHECK(is_inserted);
+ // TODO use force_reupload if is_reupload
+ td_->file_manager_->resume_upload(file_id, std::move(bad_parts), upload_ringtone_callback_, 32, 0);
+}
+
+void NotificationSettingsManager::on_upload_ringtone(FileId file_id,
+ tl_object_ptr<telegram_api::InputFile> input_file) {
+ LOG(INFO) << "File " << file_id << " has been uploaded";
+
+ auto it = being_uploaded_ringtones_.find(file_id);
+ if (it == being_uploaded_ringtones_.end()) {
+ // just in case, as in on_upload_media
+ return;
+ }
+
+ bool is_reupload = it->second.is_reupload;
+ auto promise = std::move(it->second.promise);
+
+ being_uploaded_ringtones_.erase(it);
+
+ FileView file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.is_encrypted());
+ CHECK(file_view.get_type() == FileType::Ringtone);
+ if (input_file == nullptr && file_view.has_remote_location()) {
+ if (file_view.main_remote_location().is_web()) {
+ return promise.set_error(Status::Error(400, "Can't use web document as notification sound"));
+ }
+ if (is_reupload) {
+ return promise.set_error(Status::Error(400, "Failed to reupload the file"));
+ }
+
+ send_save_ringtone_query(
+ file_view.get_main_file_id(), false,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), file_id = file_view.get_main_file_id(), promise = std::move(promise)](
+ Result<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &NotificationSettingsManager::on_add_saved_ringtone, file_id,
+ result.move_as_ok(), std::move(promise));
+ }
+ }));
+ return;
+ }
+ CHECK(input_file != nullptr);
+ CHECK(input_file->get_id() == telegram_api::inputFile::ID);
+ const PathView path_view(static_cast<const telegram_api::inputFile *>(input_file.get())->name_);
+ auto file_name = path_view.file_name().str();
+ auto mime_type = MimeType::from_extension(path_view.extension());
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
+ Result<telegram_api::object_ptr<telegram_api::Document>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &NotificationSettingsManager::on_upload_saved_ringtone, result.move_as_ok(),
+ std::move(promise));
+ }
+ });
+
+ td_->create_handler<UploadRingtoneQuery>(std::move(query_promise))
+ ->send(file_id, std::move(input_file), file_name, mime_type);
+}
+
+void NotificationSettingsManager::on_upload_ringtone_error(FileId file_id, Status status) {
+ LOG(INFO) << "File " << file_id << " has upload error " << status;
+ CHECK(status.is_error());
+
+ auto it = being_uploaded_ringtones_.find(file_id);
+ if (it == being_uploaded_ringtones_.end()) {
+ // just in case
+ return;
+ }
+
+ auto promise = std::move(it->second.promise);
+
+ being_uploaded_ringtones_.erase(it);
+
+ promise.set_error(std::move(status));
+}
+
+void NotificationSettingsManager::on_upload_saved_ringtone(
+ telegram_api::object_ptr<telegram_api::Document> &&saved_ringtone,
+ Promise<td_api::object_ptr<td_api::notificationSound>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ TRY_RESULT_PROMISE(promise, file_id, get_ringtone(std::move(saved_ringtone)));
+
+ reload_saved_ringtones(PromiseCreator::lambda([actor_id = actor_id(this), file_id,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &NotificationSettingsManager::on_add_saved_ringtone, file_id, nullptr, std::move(promise));
+ }
+ }));
+}
+
+void NotificationSettingsManager::on_add_saved_ringtone(
+ FileId file_id, telegram_api::object_ptr<telegram_api::account_SavedRingtone> &&saved_ringtone,
+ Promise<td_api::object_ptr<td_api::notificationSound>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (saved_ringtone != nullptr && saved_ringtone->get_id() == telegram_api::account_savedRingtoneConverted::ID) {
+ auto ringtone = move_tl_object_as<telegram_api::account_savedRingtoneConverted>(saved_ringtone);
+ TRY_RESULT_PROMISE_ASSIGN(promise, file_id, get_ringtone(std::move(ringtone->document_)));
+ } else {
+ for (auto &saved_ringtone_file_id : saved_ringtone_file_ids_) {
+ if (file_id == saved_ringtone_file_id) {
+ return promise.set_value(td_->audios_manager_->get_notification_sound_object(file_id));
+ }
+ }
+ if (saved_ringtone == nullptr) {
+ return promise.set_error(Status::Error(500, "Failed to find saved notification sound"));
+ }
+ }
+
+ reload_saved_ringtones(PromiseCreator::lambda([actor_id = actor_id(this), file_id,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &NotificationSettingsManager::on_add_saved_ringtone, file_id, nullptr, std::move(promise));
+ }
+ }));
+}
+
+void NotificationSettingsManager::remove_saved_ringtone(int64 ringtone_id, Promise<Unit> &&promise) {
+ if (!are_saved_ringtones_loaded_) {
+ load_saved_ringtones(std::move(promise));
+ return;
+ }
+
+ for (auto &file_id : saved_ringtone_file_ids_) {
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.empty());
+ CHECK(file_view.get_type() == FileType::Ringtone);
+ CHECK(file_view.has_remote_location());
+ if (file_view.remote_location().get_id() == ringtone_id) {
+ send_save_ringtone_query(
+ file_view.get_main_file_id(), true,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), ringtone_id, promise = std::move(promise)](
+ Result<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &NotificationSettingsManager::on_remove_saved_ringtone, ringtone_id,
+ std::move(promise));
+ }
+ }));
+ return;
+ }
+ }
+
+ promise.set_value(Unit());
+}
+
+void NotificationSettingsManager::on_remove_saved_ringtone(int64 ringtone_id, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ CHECK(are_saved_ringtones_loaded_);
+
+ auto max_count = td_->option_manager_->get_option_integer("notification_sound_count_max");
+ if (saved_ringtone_file_ids_.size() >= static_cast<uint64>(max_count)) {
+ // reload all saved ringtones to get ringtones besides the limit
+ return reload_saved_ringtones(PromiseCreator::lambda([promise = std::move(promise)](Result<Unit> &&result) mutable {
+ // ignore errors
+ promise.set_value(Unit());
+ }));
+ }
+
+ for (auto it = saved_ringtone_file_ids_.begin(); it != saved_ringtone_file_ids_.end(); ++it) {
+ auto file_view = td_->file_manager_->get_file_view(*it);
+ CHECK(!file_view.empty());
+ CHECK(file_view.get_type() == FileType::Ringtone);
+ CHECK(file_view.has_remote_location());
+ if (file_view.remote_location().get_id() == ringtone_id) {
+ saved_ringtone_file_ids_.erase(it);
+ saved_ringtone_hash_ = 0;
+ on_saved_ringtones_updated(false);
+ break;
+ }
+ }
+
+ promise.set_value(Unit());
+}
+
+Result<FileId> NotificationSettingsManager::get_ringtone(
+ telegram_api::object_ptr<telegram_api::Document> &&ringtone) const {
+ int32 document_id = ringtone->get_id();
+ if (document_id == telegram_api::documentEmpty::ID) {
+ return Status::Error("Received an empty ringtone");
+ }
+ CHECK(document_id == telegram_api::document::ID);
+
+ auto parsed_document =
+ td_->documents_manager_->on_get_document(move_tl_object_as<telegram_api::document>(ringtone), DialogId(), nullptr,
+ Document::Type::Audio, false, false, true);
+ if (parsed_document.type != Document::Type::Audio) {
+ return Status::Error("Receive ringtone of a wrong type");
+ }
+ return parsed_document.file_id;
+}
+
+void NotificationSettingsManager::load_saved_ringtones(Promise<Unit> &&promise) {
+ CHECK(!are_saved_ringtones_loaded_);
+ auto saved_ringtones_string = G()->td_db()->get_binlog_pmc()->get(get_saved_ringtones_database_key());
+ if (saved_ringtones_string.empty()) {
+ return reload_saved_ringtones(std::move(promise));
+ }
+
+ RingtoneListLogEvent saved_ringtones_log_event;
+ bool is_valid = log_event_parse(saved_ringtones_log_event, saved_ringtones_string).is_ok();
+
+ for (auto &ringtone_file_id : saved_ringtones_log_event.ringtone_file_ids_) {
+ if (!ringtone_file_id.is_valid()) {
+ is_valid = false;
+ break;
+ }
+ }
+ if (is_valid) {
+ saved_ringtone_hash_ = saved_ringtones_log_event.hash_;
+ saved_ringtone_file_ids_ = std::move(saved_ringtones_log_event.ringtone_file_ids_);
+ are_saved_ringtones_loaded_ = true;
+
+ if (!saved_ringtone_file_ids_.empty()) {
+ on_saved_ringtones_updated(true);
+ }
+
+ // the promis must not be set synchronously
+ send_closure_later(actor_id(this), &NotificationSettingsManager::on_load_saved_ringtones, std::move(promise));
+ reload_saved_ringtones(Auto());
+ } else {
+ LOG(ERROR) << "Ignore invalid saved notification sounds log event";
+ reload_saved_ringtones(std::move(promise));
+ }
+}
+
+void NotificationSettingsManager::on_load_saved_ringtones(Promise<Unit> &&promise) {
+ promise.set_value(Unit());
+}
+
+void NotificationSettingsManager::reload_saved_ringtones(Promise<Unit> &&promise) {
+ if (!is_active()) {
+ return promise.set_error(Status::Error(400, "Don't need to reload saved notification sounds"));
+ }
+ reload_saved_ringtones_queries_.push_back(std::move(promise));
+ if (reload_saved_ringtones_queries_.size() == 1) {
+ are_saved_ringtones_reloaded_ = true;
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> &&result) {
+ send_closure(actor_id, &NotificationSettingsManager::on_reload_saved_ringtones, false, std::move(result));
+ });
+ td_->create_handler<GetSavedRingtonesQuery>(std::move(query_promise))->send(saved_ringtone_hash_);
+ }
+}
+
+void NotificationSettingsManager::repair_saved_ringtones(Promise<Unit> &&promise) {
+ if (!is_active()) {
+ return promise.set_error(Status::Error(400, "Don't need to repair saved notification sounds"));
+ }
+
+ repair_saved_ringtones_queries_.push_back(std::move(promise));
+ if (repair_saved_ringtones_queries_.size() == 1u) {
+ are_saved_ringtones_reloaded_ = true;
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> &&result) {
+ send_closure(actor_id, &NotificationSettingsManager::on_reload_saved_ringtones, true, std::move(result));
+ });
+ td_->create_handler<GetSavedRingtonesQuery>(std::move(query_promise))->send(0);
+ }
+}
+
+void NotificationSettingsManager::on_reload_saved_ringtones(
+ bool is_repair, Result<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> &&result) {
+ if (!is_active()) {
+ are_saved_ringtones_loaded_ = true;
+ set_promises(reload_saved_ringtones_queries_);
+ set_promises(repair_saved_ringtones_queries_);
+ return;
+ }
+ if (result.is_error()) {
+ if (is_repair) {
+ fail_promises(repair_saved_ringtones_queries_, result.move_as_error());
+ } else {
+ fail_promises(reload_saved_ringtones_queries_, result.move_as_error());
+ set_timeout_in(Random::fast(60, 120));
+ }
+ return;
+ }
+
+ if (!is_repair) {
+ set_timeout_in(Random::fast(3600, 4800));
+ }
+
+ auto saved_ringtones_ptr = result.move_as_ok();
+ auto constructor_id = saved_ringtones_ptr->get_id();
+ if (constructor_id == telegram_api::account_savedRingtonesNotModified::ID) {
+ if (is_repair) {
+ fail_promises(repair_saved_ringtones_queries_, Status::Error(500, "Failed to repair saved animations"));
+ } else {
+ are_saved_ringtones_loaded_ = true;
+ set_promises(reload_saved_ringtones_queries_);
+ }
+ return;
+ }
+ CHECK(constructor_id == telegram_api::account_savedRingtones::ID);
+ auto saved_ringtones = move_tl_object_as<telegram_api::account_savedRingtones>(saved_ringtones_ptr);
+
+ auto new_hash = saved_ringtones->hash_;
+ vector<FileId> new_saved_ringtone_file_ids;
+
+ for (auto &ringtone : saved_ringtones->ringtones_) {
+ auto r_ringtone = get_ringtone(std::move(ringtone));
+ if (r_ringtone.is_error()) {
+ LOG(ERROR) << r_ringtone.error().message();
+ new_hash = 0;
+ continue;
+ }
+
+ new_saved_ringtone_file_ids.push_back(r_ringtone.move_as_ok());
+ }
+
+ bool need_update = new_saved_ringtone_file_ids != saved_ringtone_file_ids_;
+ are_saved_ringtones_loaded_ = true;
+ if (need_update || saved_ringtone_hash_ != new_hash) {
+ saved_ringtone_hash_ = new_hash;
+ saved_ringtone_file_ids_ = std::move(new_saved_ringtone_file_ids);
+
+ if (need_update) {
+ on_saved_ringtones_updated(false);
+ }
+ }
+ if (is_repair) {
+ set_promises(repair_saved_ringtones_queries_);
+ } else {
+ set_promises(reload_saved_ringtones_queries_);
+ }
+}
+
+td_api::object_ptr<td_api::updateSavedNotificationSounds>
+NotificationSettingsManager::get_update_saved_notification_sounds_object() const {
+ auto ringtone_ids = transform(saved_ringtone_file_ids_, [file_manager = td_->file_manager_.get()](FileId file_id) {
+ auto file_view = file_manager->get_file_view(file_id);
+ CHECK(!file_view.empty());
+ CHECK(file_view.get_type() == FileType::Ringtone);
+ CHECK(file_view.has_remote_location());
+ return file_view.remote_location().get_id();
+ });
+ return td_api::make_object<td_api::updateSavedNotificationSounds>(std::move(ringtone_ids));
+}
+
+string NotificationSettingsManager::get_saved_ringtones_database_key() {
+ return "ringtones";
+}
+
+void NotificationSettingsManager::save_saved_ringtones_to_database() const {
+ RingtoneListLogEvent ringtone_list_log_event{saved_ringtone_hash_, saved_ringtone_file_ids_};
+ G()->td_db()->get_binlog_pmc()->set(get_saved_ringtones_database_key(),
+ log_event_store(ringtone_list_log_event).as_slice().str());
+}
+
+void NotificationSettingsManager::on_saved_ringtones_updated(bool from_database) {
+ CHECK(are_saved_ringtones_loaded_);
+ vector<FileId> new_sorted_saved_ringtone_file_ids = saved_ringtone_file_ids_;
+ std::sort(new_sorted_saved_ringtone_file_ids.begin(), new_sorted_saved_ringtone_file_ids.end());
+ if (new_sorted_saved_ringtone_file_ids != sorted_saved_ringtone_file_ids_) {
+ td_->file_manager_->change_files_source(get_saved_ringtones_file_source_id(), sorted_saved_ringtone_file_ids_,
+ new_sorted_saved_ringtone_file_ids);
+ sorted_saved_ringtone_file_ids_ = std::move(new_sorted_saved_ringtone_file_ids);
+ }
+
+ if (!from_database) {
+ save_saved_ringtones_to_database();
+ }
+
+ send_closure(G()->td(), &Td::send_update, get_update_saved_notification_sounds_object());
+}
+
+FileSourceId NotificationSettingsManager::get_saved_ringtones_file_source_id() {
+ if (!saved_ringtones_file_source_id_.is_valid()) {
+ saved_ringtones_file_source_id_ = td_->file_reference_manager_->create_saved_ringtones_file_source();
+ }
+ return saved_ringtones_file_source_id_;
+}
+
+void NotificationSettingsManager::send_get_dialog_notification_settings_query(DialogId dialog_id,
+ Promise<Unit> &&promise) {
+ if (td_->auth_manager_->is_bot() || dialog_id.get_type() == DialogType::SecretChat) {
+ LOG(WARNING) << "Can't get notification settings for " << dialog_id;
+ return promise.set_error(Status::Error(500, "Wrong getDialogNotificationSettings query"));
+ }
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ LOG(WARNING) << "Have no access to " << dialog_id << " to get notification settings";
+ return promise.set_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ auto &promises = get_dialog_notification_settings_queries_[dialog_id];
+ promises.push_back(std::move(promise));
+ if (promises.size() != 1) {
+ // query has already been sent, just wait for the result
+ return;
+ }
+
+ td_->create_handler<GetDialogNotifySettingsQuery>()->send(dialog_id);
+}
+
+const ScopeNotificationSettings *NotificationSettingsManager::get_scope_notification_settings(
+ NotificationSettingsScope scope, Promise<Unit> &&promise) {
+ const ScopeNotificationSettings *notification_settings = get_scope_notification_settings(scope);
+ CHECK(notification_settings != nullptr);
+ if (!notification_settings->is_synchronized && !td_->auth_manager_->is_bot()) {
+ send_get_scope_notification_settings_query(scope, std::move(promise));
+ return nullptr;
+ }
+
+ promise.set_value(Unit());
+ return notification_settings;
+}
+
+void NotificationSettingsManager::send_get_scope_notification_settings_query(NotificationSettingsScope scope,
+ Promise<Unit> &&promise) {
+ if (td_->auth_manager_->is_bot()) {
+ LOG(ERROR) << "Can't get notification settings for " << scope;
+ return promise.set_error(Status::Error(500, "Wrong getScopeNotificationSettings query"));
+ }
+
+ td_->create_handler<GetScopeNotifySettingsQuery>(std::move(promise))->send(scope);
+}
+
+void NotificationSettingsManager::on_get_dialog_notification_settings_query_finished(DialogId dialog_id,
+ Status &&status) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto it = get_dialog_notification_settings_queries_.find(dialog_id);
+ CHECK(it != get_dialog_notification_settings_queries_.end());
+ CHECK(!it->second.empty());
+ auto promises = std::move(it->second);
+ get_dialog_notification_settings_queries_.erase(it);
+
+ if (status.is_ok()) {
+ set_promises(promises);
+ } else {
+ fail_promises(promises, std::move(status));
+ }
+}
+
+void NotificationSettingsManager::update_dialog_notify_settings(DialogId dialog_id,
+ const DialogNotificationSettings &new_settings,
+ Promise<Unit> &&promise) {
+ td_->create_handler<UpdateDialogNotifySettingsQuery>(std::move(promise))->send(dialog_id, new_settings);
+}
+
+Status NotificationSettingsManager::set_scope_notification_settings(
+ NotificationSettingsScope scope, td_api::object_ptr<td_api::scopeNotificationSettings> &&notification_settings) {
+ CHECK(!td_->auth_manager_->is_bot());
+ auto *current_settings = get_scope_notification_settings(scope);
+ CHECK(current_settings != nullptr);
+ TRY_RESULT(new_settings, ::td::get_scope_notification_settings(std::move(notification_settings)));
+ if (is_notification_sound_default(current_settings->sound) && is_notification_sound_default(new_settings.sound)) {
+ new_settings.sound = dup_notification_sound(current_settings->sound);
+ }
+ if (update_scope_notification_settings(scope, current_settings, std::move(new_settings))) {
+ update_scope_notification_settings_on_server(scope, 0);
+ }
+ return Status::OK();
+}
+
+class NotificationSettingsManager::UpdateScopeNotificationSettingsOnServerLogEvent {
+ public:
+ NotificationSettingsScope scope_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(scope_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(scope_, parser);
+ }
+};
+
+uint64 NotificationSettingsManager::save_update_scope_notification_settings_on_server_log_event(
+ NotificationSettingsScope scope) {
+ UpdateScopeNotificationSettingsOnServerLogEvent log_event{scope};
+ return binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::UpdateScopeNotificationSettingsOnServer,
+ get_log_event_storer(log_event));
+}
+
+void NotificationSettingsManager::update_scope_notification_settings_on_server(NotificationSettingsScope scope,
+ uint64 log_event_id) {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (log_event_id == 0) {
+ log_event_id = save_update_scope_notification_settings_on_server_log_event(scope);
+ }
+
+ LOG(INFO) << "Update " << scope << " notification settings on server with log_event " << log_event_id;
+ td_->create_handler<UpdateScopeNotifySettingsQuery>(get_erase_log_event_promise(log_event_id))
+ ->send(scope, *get_scope_notification_settings(scope));
+}
+
+void NotificationSettingsManager::reset_notify_settings(Promise<Unit> &&promise) {
+ td_->create_handler<ResetNotifySettingsQuery>(std::move(promise))->send();
+}
+
+void NotificationSettingsManager::get_notify_settings_exceptions(NotificationSettingsScope scope, bool filter_scope,
+ bool compare_sound, Promise<Unit> &&promise) {
+ td_->create_handler<GetNotifySettingsExceptionsQuery>(std::move(promise))->send(scope, filter_scope, compare_sound);
+}
+
+void NotificationSettingsManager::on_binlog_events(vector<BinlogEvent> &&events) {
+ if (G()->close_flag()) {
+ return;
+ }
+ for (auto &event : events) {
+ CHECK(event.id_ != 0);
+ switch (event.type_) {
+ case LogEvent::HandlerType::UpdateScopeNotificationSettingsOnServer: {
+ UpdateScopeNotificationSettingsOnServerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ update_scope_notification_settings_on_server(log_event.scope_, event.id_);
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unsupported log event type " << event.type_;
+ }
+ }
+}
+
+void NotificationSettingsManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ for (auto scope :
+ {NotificationSettingsScope::Private, NotificationSettingsScope::Group, NotificationSettingsScope::Channel}) {
+ auto current_settings = get_scope_notification_settings(scope);
+ CHECK(current_settings != nullptr);
+ if (current_settings->is_synchronized) {
+ updates.push_back(get_update_scope_notification_settings_object(scope));
+ }
+ }
+
+ if (are_saved_ringtones_loaded_) {
+ updates.push_back(get_update_saved_notification_sounds_object());
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsManager.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsManager.h
new file mode 100644
index 0000000000..14712d16f8
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsManager.h
@@ -0,0 +1,217 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/DialogNotificationSettings.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/NotificationSettingsScope.h"
+#include "td/telegram/ScopeNotificationSettings.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/MultiTimeout.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+#include <memory>
+
+namespace td {
+
+struct BinlogEvent;
+
+class NotificationSound;
+
+class Td;
+
+class NotificationSettingsManager final : public Actor {
+ public:
+ NotificationSettingsManager(Td *td, ActorShared<> parent);
+ NotificationSettingsManager(const NotificationSettingsManager &) = delete;
+ NotificationSettingsManager &operator=(const NotificationSettingsManager &) = delete;
+ NotificationSettingsManager(NotificationSettingsManager &&) = delete;
+ NotificationSettingsManager &operator=(NotificationSettingsManager &&) = delete;
+ ~NotificationSettingsManager() final;
+
+ int32 get_scope_mute_until(NotificationSettingsScope scope) const;
+
+ const unique_ptr<NotificationSound> &get_scope_notification_sound(NotificationSettingsScope scope) const;
+
+ bool get_scope_show_preview(NotificationSettingsScope scope) const;
+
+ bool get_scope_disable_pinned_message_notifications(NotificationSettingsScope scope) const;
+
+ bool get_scope_disable_mention_notifications(NotificationSettingsScope scope) const;
+
+ tl_object_ptr<telegram_api::InputNotifyPeer> get_input_notify_peer(DialogId dialog_id) const;
+
+ void on_update_scope_notify_settings(NotificationSettingsScope scope,
+ tl_object_ptr<telegram_api::peerNotifySettings> &&peer_notify_settings);
+
+ void add_saved_ringtone(td_api::object_ptr<td_api::InputFile> &&input_file,
+ Promise<td_api::object_ptr<td_api::notificationSound>> &&promise);
+
+ FileId get_saved_ringtone(int64 ringtone_id, Promise<Unit> &&promise);
+
+ vector<FileId> get_saved_ringtones(Promise<Unit> &&promise);
+
+ void remove_saved_ringtone(int64 ringtone_id, Promise<Unit> &&promise);
+
+ void reload_saved_ringtones(Promise<Unit> &&promise);
+
+ void repair_saved_ringtones(Promise<Unit> &&promise);
+
+ FileSourceId get_saved_ringtones_file_source_id();
+
+ void send_save_ringtone_query(FileId ringtone_file_id, bool unsave,
+ Promise<telegram_api::object_ptr<telegram_api::account_SavedRingtone>> &&promise);
+
+ void send_get_dialog_notification_settings_query(DialogId dialog_id, Promise<Unit> &&promise);
+
+ const ScopeNotificationSettings *get_scope_notification_settings(NotificationSettingsScope scope,
+ Promise<Unit> &&promise);
+ void send_get_scope_notification_settings_query(NotificationSettingsScope scope, Promise<Unit> &&promise);
+
+ void on_get_dialog_notification_settings_query_finished(DialogId dialog_id, Status &&status);
+
+ void update_dialog_notify_settings(DialogId dialog_id, const DialogNotificationSettings &new_settings,
+ Promise<Unit> &&promise);
+
+ Status set_scope_notification_settings(NotificationSettingsScope scope,
+ td_api::object_ptr<td_api::scopeNotificationSettings> &&notification_settings)
+ TD_WARN_UNUSED_RESULT;
+
+ void reset_scope_notification_settings();
+
+ void reset_notify_settings(Promise<Unit> &&promise);
+
+ void get_notify_settings_exceptions(NotificationSettingsScope scope, bool filter_scope, bool compare_sound,
+ Promise<Unit> &&promise);
+
+ void init();
+
+ void on_binlog_events(vector<BinlogEvent> &&events);
+
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
+ private:
+ class UpdateScopeNotificationSettingsOnServerLogEvent;
+
+ class RingtoneListLogEvent;
+
+ class UploadRingtoneCallback;
+
+ void start_up() final;
+
+ void tear_down() final;
+
+ void timeout_expired() final;
+
+ bool is_active() const;
+
+ static void on_scope_unmute_timeout_callback(void *notification_settings_manager_ptr, int64 scope_int);
+
+ Result<FileId> get_ringtone(telegram_api::object_ptr<telegram_api::Document> &&ringtone) const;
+
+ void upload_ringtone(FileId file_id, bool is_reupload,
+ Promise<td_api::object_ptr<td_api::notificationSound>> &&promise, vector<int> bad_parts = {});
+
+ void on_upload_ringtone(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file);
+
+ void on_upload_ringtone_error(FileId file_id, Status status);
+
+ void on_upload_saved_ringtone(telegram_api::object_ptr<telegram_api::Document> &&saved_ringtone,
+ Promise<td_api::object_ptr<td_api::notificationSound>> &&promise);
+
+ void on_add_saved_ringtone(FileId file_id,
+ telegram_api::object_ptr<telegram_api::account_SavedRingtone> &&saved_ringtone,
+ Promise<td_api::object_ptr<td_api::notificationSound>> &&promise);
+
+ void on_remove_saved_ringtone(int64 ringtone_id, Promise<Unit> &&promise);
+
+ void on_reload_saved_ringtones(bool is_repair,
+ Result<telegram_api::object_ptr<telegram_api::account_SavedRingtones>> &&result);
+
+ static string get_saved_ringtones_database_key();
+
+ void load_saved_ringtones(Promise<Unit> &&promise);
+
+ void on_load_saved_ringtones(Promise<Unit> &&promise);
+
+ void save_saved_ringtones_to_database() const;
+
+ void on_saved_ringtones_updated(bool from_database);
+
+ ScopeNotificationSettings *get_scope_notification_settings(NotificationSettingsScope scope);
+
+ const ScopeNotificationSettings *get_scope_notification_settings(NotificationSettingsScope scope) const;
+
+ td_api::object_ptr<td_api::updateScopeNotificationSettings> get_update_scope_notification_settings_object(
+ NotificationSettingsScope scope) const;
+
+ td_api::object_ptr<td_api::updateSavedNotificationSounds> get_update_saved_notification_sounds_object() const;
+
+ void on_scope_unmute(NotificationSettingsScope scope);
+
+ static string get_notification_settings_scope_database_key(NotificationSettingsScope scope);
+
+ static void save_scope_notification_settings(NotificationSettingsScope scope,
+ const ScopeNotificationSettings &new_settings);
+
+ bool update_scope_notification_settings(NotificationSettingsScope scope, ScopeNotificationSettings *current_settings,
+ ScopeNotificationSettings &&new_settings);
+
+ static uint64 save_update_scope_notification_settings_on_server_log_event(NotificationSettingsScope scope);
+
+ void update_scope_notification_settings_on_server(NotificationSettingsScope scope, uint64 log_event_id);
+
+ void schedule_scope_unmute(NotificationSettingsScope scope, int32 mute_until);
+
+ void update_scope_unmute_timeout(NotificationSettingsScope scope, int32 &old_mute_until, int32 new_mute_until);
+
+ Td *td_;
+ ActorShared<> parent_;
+
+ bool is_inited_ = false;
+ bool are_saved_ringtones_loaded_ = false;
+ bool are_saved_ringtones_reloaded_ = false;
+
+ ScopeNotificationSettings users_notification_settings_;
+ ScopeNotificationSettings chats_notification_settings_;
+ ScopeNotificationSettings channels_notification_settings_;
+
+ MultiTimeout scope_unmute_timeout_{"ScopeUnmuteTimeout"};
+
+ int64 saved_ringtone_hash_ = 0;
+ vector<FileId> saved_ringtone_file_ids_;
+ vector<FileId> sorted_saved_ringtone_file_ids_;
+ FileSourceId saved_ringtones_file_source_id_;
+
+ std::shared_ptr<UploadRingtoneCallback> upload_ringtone_callback_;
+
+ struct UploadedRingtone {
+ bool is_reupload;
+ Promise<td_api::object_ptr<td_api::notificationSound>> promise;
+
+ UploadedRingtone(bool is_reupload, Promise<td_api::object_ptr<td_api::notificationSound>> promise)
+ : is_reupload(is_reupload), promise(std::move(promise)) {
+ }
+ };
+ FlatHashMap<FileId, UploadedRingtone, FileIdHash> being_uploaded_ringtones_;
+
+ vector<Promise<Unit>> reload_saved_ringtones_queries_;
+ vector<Promise<Unit>> repair_saved_ringtones_queries_;
+
+ FlatHashMap<DialogId, vector<Promise<Unit>>, DialogIdHash> get_dialog_notification_settings_queries_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsScope.cpp b/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsScope.cpp
new file mode 100644
index 0000000000..ea1d68bed5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsScope.cpp
@@ -0,0 +1,69 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/NotificationSettingsScope.h"
+
+namespace td {
+
+StringBuilder &operator<<(StringBuilder &string_builder, NotificationSettingsScope scope) {
+ switch (scope) {
+ case NotificationSettingsScope::Private:
+ return string_builder << "notification settings for private chats";
+ case NotificationSettingsScope::Group:
+ return string_builder << "notification settings for group chats";
+ case NotificationSettingsScope::Channel:
+ return string_builder << "notification settings for channel chats";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+td_api::object_ptr<td_api::NotificationSettingsScope> get_notification_settings_scope_object(
+ NotificationSettingsScope scope) {
+ switch (scope) {
+ case NotificationSettingsScope::Private:
+ return td_api::make_object<td_api::notificationSettingsScopePrivateChats>();
+ case NotificationSettingsScope::Group:
+ return td_api::make_object<td_api::notificationSettingsScopeGroupChats>();
+ case NotificationSettingsScope::Channel:
+ return td_api::make_object<td_api::notificationSettingsScopeChannelChats>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+telegram_api::object_ptr<telegram_api::InputNotifyPeer> get_input_notify_peer(NotificationSettingsScope scope) {
+ switch (scope) {
+ case NotificationSettingsScope::Private:
+ return telegram_api::make_object<telegram_api::inputNotifyUsers>();
+ case NotificationSettingsScope::Group:
+ return telegram_api::make_object<telegram_api::inputNotifyChats>();
+ case NotificationSettingsScope::Channel:
+ return telegram_api::make_object<telegram_api::inputNotifyBroadcasts>();
+ default:
+ return nullptr;
+ }
+}
+
+NotificationSettingsScope get_notification_settings_scope(
+ const td_api::object_ptr<td_api::NotificationSettingsScope> &scope) {
+ CHECK(scope != nullptr);
+ switch (scope->get_id()) {
+ case td_api::notificationSettingsScopePrivateChats::ID:
+ return NotificationSettingsScope::Private;
+ case td_api::notificationSettingsScopeGroupChats::ID:
+ return NotificationSettingsScope::Group;
+ case td_api::notificationSettingsScopeChannelChats::ID:
+ return NotificationSettingsScope::Channel;
+ default:
+ UNREACHABLE();
+ return NotificationSettingsScope::Private;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsScope.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsScope.h
new file mode 100644
index 0000000000..039176efa3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationSettingsScope.h
@@ -0,0 +1,29 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+enum class NotificationSettingsScope : int32 { Private, Group, Channel };
+
+StringBuilder &operator<<(StringBuilder &string_builder, NotificationSettingsScope scope);
+
+td_api::object_ptr<td_api::NotificationSettingsScope> get_notification_settings_scope_object(
+ NotificationSettingsScope scope);
+
+telegram_api::object_ptr<telegram_api::InputNotifyPeer> get_input_notify_peer(NotificationSettingsScope scope);
+
+NotificationSettingsScope get_notification_settings_scope(
+ const td_api::object_ptr<td_api::NotificationSettingsScope> &scope);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationSound.cpp b/protocols/Telegram/tdlib/td/td/telegram/NotificationSound.cpp
new file mode 100644
index 0000000000..10abbfc100
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationSound.cpp
@@ -0,0 +1,313 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/NotificationSound.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class NotificationSoundNone final : public NotificationSound {
+ public:
+ NotificationSoundNone() = default;
+
+ NotificationSoundType get_type() const final {
+ return NotificationSoundType::None;
+ }
+};
+
+class NotificationSoundLocal final : public NotificationSound {
+ public:
+ string title_;
+ string data_;
+
+ NotificationSoundLocal() = default;
+ NotificationSoundLocal(string title, string data) : title_(std::move(title)), data_(std::move(data)) {
+ }
+
+ NotificationSoundType get_type() const final {
+ return NotificationSoundType::Local;
+ }
+};
+
+class NotificationSoundRingtone final : public NotificationSound {
+ public:
+ int64 ringtone_id_;
+
+ NotificationSoundRingtone() = default;
+ explicit NotificationSoundRingtone(int64 ringtone_id) : ringtone_id_(ringtone_id) {
+ }
+
+ NotificationSoundType get_type() const final {
+ return NotificationSoundType::Ringtone;
+ }
+};
+
+template <class StorerT>
+static void store(const NotificationSound *notification_sound, StorerT &storer) {
+ CHECK(notification_sound != nullptr);
+
+ auto sound_type = notification_sound->get_type();
+ store(sound_type, storer);
+
+ switch (sound_type) {
+ case NotificationSoundType::None:
+ break;
+ case NotificationSoundType::Local: {
+ const auto *sound = static_cast<const NotificationSoundLocal *>(notification_sound);
+ store(sound->title_, storer);
+ store(sound->data_, storer);
+ break;
+ }
+ case NotificationSoundType::Ringtone: {
+ const auto *sound = static_cast<const NotificationSoundRingtone *>(notification_sound);
+ store(sound->ringtone_id_, storer);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+template <class ParserT>
+static void parse(unique_ptr<NotificationSound> &notification_sound, ParserT &parser) {
+ NotificationSoundType sound_type;
+ parse(sound_type, parser);
+
+ switch (sound_type) {
+ case NotificationSoundType::None:
+ notification_sound = make_unique<NotificationSoundNone>();
+ break;
+ case NotificationSoundType::Local: {
+ auto sound = make_unique<NotificationSoundLocal>();
+ parse(sound->title_, parser);
+ parse(sound->data_, parser);
+ notification_sound = std::move(sound);
+ break;
+ }
+ case NotificationSoundType::Ringtone: {
+ auto sound = make_unique<NotificationSoundRingtone>();
+ parse(sound->ringtone_id_, parser);
+ notification_sound = std::move(sound);
+ break;
+ }
+ default:
+ LOG(FATAL) << "Have unknown notification sound type " << static_cast<int32>(sound_type);
+ }
+}
+
+void store_notification_sound(const NotificationSound *notification_sound, LogEventStorerCalcLength &storer) {
+ store(notification_sound, storer);
+}
+
+void store_notification_sound(const NotificationSound *notification_sound, LogEventStorerUnsafe &storer) {
+ store(notification_sound, storer);
+}
+
+void parse_notification_sound(unique_ptr<NotificationSound> &notification_sound, LogEventParser &parser) {
+ parse(notification_sound, parser);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr<NotificationSound> &notification_sound) {
+ if (notification_sound == nullptr) {
+ return string_builder << "DefaultSound";
+ }
+ switch (notification_sound->get_type()) {
+ case NotificationSoundType::None:
+ return string_builder << "NoSound";
+ case NotificationSoundType::Local: {
+ const auto *sound = static_cast<const NotificationSoundLocal *>(notification_sound.get());
+ return string_builder << "LocalSound[" << sound->title_ << '|' << sound->data_ << ']';
+ }
+ case NotificationSoundType::Ringtone: {
+ const auto *sound = static_cast<const NotificationSoundRingtone *>(notification_sound.get());
+ return string_builder << "Ringtone[" << sound->ringtone_id_ << ']';
+ }
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+bool is_notification_sound_default(const unique_ptr<NotificationSound> &notification_sound) {
+ if (notification_sound == nullptr) {
+ return true;
+ }
+ return notification_sound->get_type() == NotificationSoundType::Local;
+}
+
+bool are_equivalent_notification_sounds(const unique_ptr<NotificationSound> &lhs,
+ const unique_ptr<NotificationSound> &rhs) {
+ if (is_notification_sound_default(lhs)) {
+ return is_notification_sound_default(rhs);
+ }
+ if (is_notification_sound_default(rhs)) {
+ return false;
+ }
+
+ auto sound_type = lhs->get_type();
+ if (sound_type != rhs->get_type()) {
+ return false;
+ }
+
+ switch (sound_type) {
+ case NotificationSoundType::None:
+ return true;
+ case NotificationSoundType::Ringtone:
+ return static_cast<const NotificationSoundRingtone *>(lhs.get())->ringtone_id_ ==
+ static_cast<const NotificationSoundRingtone *>(rhs.get())->ringtone_id_;
+ case NotificationSoundType::Local:
+ default:
+ UNREACHABLE();
+ break;
+ }
+ return false;
+}
+
+bool are_different_equivalent_notification_sounds(const unique_ptr<NotificationSound> &lhs,
+ const unique_ptr<NotificationSound> &rhs) {
+ if (lhs == nullptr) {
+ return rhs != nullptr && rhs->get_type() == NotificationSoundType::Local;
+ }
+ if (rhs == nullptr) {
+ return lhs->get_type() == NotificationSoundType::Local;
+ }
+ if (lhs->get_type() != NotificationSoundType::Local || rhs->get_type() != NotificationSoundType::Local) {
+ return false;
+ }
+
+ const auto *lhs_local = static_cast<const NotificationSoundLocal *>(lhs.get());
+ const auto *rhs_local = static_cast<const NotificationSoundLocal *>(rhs.get());
+ return lhs_local->title_ != rhs_local->title_ || lhs_local->data_ != rhs_local->data_;
+}
+
+int64 get_notification_sound_ringtone_id(const unique_ptr<NotificationSound> &notification_sound) {
+ if (notification_sound == nullptr) {
+ return -1;
+ }
+ switch (notification_sound->get_type()) {
+ case NotificationSoundType::None:
+ return 0;
+ case NotificationSoundType::Local:
+ return -1;
+ case NotificationSoundType::Ringtone: {
+ return static_cast<const NotificationSoundRingtone *>(notification_sound.get())->ringtone_id_;
+ default:
+ UNREACHABLE();
+ return -1;
+ }
+ }
+}
+
+unique_ptr<NotificationSound> get_legacy_notification_sound(const string &sound) {
+ if (sound == "default") {
+ return nullptr;
+ }
+ if (sound.empty()) {
+ return make_unique<NotificationSoundNone>();
+ }
+ return td::make_unique<NotificationSoundLocal>(sound, sound);
+}
+
+unique_ptr<NotificationSound> get_notification_sound(bool use_default_sound, int64 ringtone_id) {
+ if (use_default_sound || ringtone_id == -1) {
+ return nullptr;
+ }
+ if (ringtone_id == 0) {
+ return make_unique<NotificationSoundNone>();
+ }
+ return td::make_unique<NotificationSoundRingtone>(ringtone_id);
+}
+
+static unique_ptr<NotificationSound> get_notification_sound(telegram_api::NotificationSound *notification_sound) {
+ if (notification_sound == nullptr) {
+ return nullptr;
+ }
+
+ switch (notification_sound->get_id()) {
+ case telegram_api::notificationSoundDefault::ID:
+ return nullptr;
+ case telegram_api::notificationSoundNone::ID:
+ return make_unique<NotificationSoundNone>();
+ case telegram_api::notificationSoundLocal::ID: {
+ const auto *sound = static_cast<telegram_api::notificationSoundLocal *>(notification_sound);
+ return td::make_unique<NotificationSoundLocal>(std::move(sound->title_), std::move(sound->data_));
+ }
+ case telegram_api::notificationSoundRingtone::ID: {
+ const auto *sound = static_cast<telegram_api::notificationSoundRingtone *>(notification_sound);
+ if (sound->id_ == 0 || sound->id_ == -1) {
+ LOG(ERROR) << "Receive ringtone with ID = " << sound->id_;
+ return make_unique<NotificationSoundNone>();
+ }
+ return td::make_unique<NotificationSoundRingtone>(sound->id_);
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+unique_ptr<NotificationSound> get_notification_sound(telegram_api::peerNotifySettings *settings) {
+ CHECK(settings != nullptr);
+ telegram_api::NotificationSound *sound =
+#if TD_ANDROID
+ settings->android_sound_.get();
+#elif TD_DARWIN_IOS || TD_DARWIN_TV_OS || TD_DARWIN_WATCH_OS
+ settings->ios_sound_.get();
+#else
+ settings->other_sound_.get();
+#endif
+ return get_notification_sound(sound);
+}
+
+telegram_api::object_ptr<telegram_api::NotificationSound> get_input_notification_sound(
+ const unique_ptr<NotificationSound> &notification_sound) {
+ if (notification_sound == nullptr) {
+ return nullptr;
+ }
+
+ // must not return nullptr if notification_sound != nullptr
+ switch (notification_sound->get_type()) {
+ case NotificationSoundType::None:
+ return telegram_api::make_object<telegram_api::notificationSoundNone>();
+ case NotificationSoundType::Local: {
+ const auto *sound = static_cast<const NotificationSoundLocal *>(notification_sound.get());
+ return telegram_api::make_object<telegram_api::notificationSoundLocal>(sound->title_, sound->data_);
+ }
+ case NotificationSoundType::Ringtone: {
+ const auto *sound = static_cast<const NotificationSoundRingtone *>(notification_sound.get());
+ return telegram_api::make_object<telegram_api::notificationSoundRingtone>(sound->ringtone_id_);
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+unique_ptr<NotificationSound> dup_notification_sound(const unique_ptr<NotificationSound> &notification_sound) {
+ if (notification_sound == nullptr) {
+ return nullptr;
+ }
+
+ switch (notification_sound->get_type()) {
+ case NotificationSoundType::None:
+ return make_unique<NotificationSoundNone>();
+ case NotificationSoundType::Local: {
+ const auto *sound = static_cast<const NotificationSoundLocal *>(notification_sound.get());
+ return td::make_unique<NotificationSoundLocal>(sound->title_, sound->data_);
+ }
+ case NotificationSoundType::Ringtone: {
+ const auto *sound = static_cast<const NotificationSoundRingtone *>(notification_sound.get());
+ return td::make_unique<NotificationSoundRingtone>(sound->ringtone_id_);
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationSound.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationSound.h
new file mode 100644
index 0000000000..72db725cd9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationSound.h
@@ -0,0 +1,67 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/NotificationSoundType.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class NotificationSound {
+ public:
+ NotificationSound() = default;
+ NotificationSound(const NotificationSound &) = delete;
+ NotificationSound &operator=(const NotificationSound &) = delete;
+ NotificationSound(NotificationSound &&) = delete;
+ NotificationSound &operator=(NotificationSound &&) = delete;
+
+ virtual NotificationSoundType get_type() const = 0;
+ virtual ~NotificationSound() = default;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr<NotificationSound> &notification_sound);
+
+void store_notification_sound(const NotificationSound *notification_sound, LogEventStorerCalcLength &storer);
+
+void store_notification_sound(const NotificationSound *notification_sound, LogEventStorerUnsafe &storer);
+
+template <class StorerT>
+void NotificationSound::store(StorerT &storer) const {
+ store_notification_sound(this, storer);
+}
+
+void parse_notification_sound(unique_ptr<NotificationSound> &notification_sound, LogEventParser &parser);
+
+bool is_notification_sound_default(const unique_ptr<NotificationSound> &notification_sound);
+
+bool are_equivalent_notification_sounds(const unique_ptr<NotificationSound> &lhs,
+ const unique_ptr<NotificationSound> &rhs);
+
+bool are_different_equivalent_notification_sounds(const unique_ptr<NotificationSound> &lhs,
+ const unique_ptr<NotificationSound> &rhs);
+
+int64 get_notification_sound_ringtone_id(const unique_ptr<NotificationSound> &notification_sound);
+
+unique_ptr<NotificationSound> get_legacy_notification_sound(const string &sound);
+
+unique_ptr<NotificationSound> get_notification_sound(bool use_default_sound, int64 ringtone_id);
+
+unique_ptr<NotificationSound> get_notification_sound(telegram_api::peerNotifySettings *settings);
+
+telegram_api::object_ptr<telegram_api::NotificationSound> get_input_notification_sound(
+ const unique_ptr<NotificationSound> &notification_sound);
+
+unique_ptr<NotificationSound> dup_notification_sound(const unique_ptr<NotificationSound> &notification_sound);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationSoundType.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationSoundType.h
new file mode 100644
index 0000000000..374139eea6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationSoundType.h
@@ -0,0 +1,15 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+namespace td {
+
+enum class NotificationSoundType : int32 { None, Local, Ringtone };
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationType.cpp b/protocols/Telegram/tdlib/td/td/telegram/NotificationType.cpp
new file mode 100644
index 0000000000..557ec4bee3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationType.cpp
@@ -0,0 +1,406 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/NotificationType.h"
+
+#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/AudiosManager.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessageSender.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/VideoNotesManager.h"
+#include "td/telegram/VideosManager.h"
+#include "td/telegram/VoiceNotesManager.h"
+
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+
+#include <tuple>
+
+namespace td {
+
+class NotificationTypeMessage final : public NotificationType {
+ bool can_be_delayed() const final {
+ return message_id_.is_valid() && message_id_.is_server();
+ }
+
+ bool is_temporary() const final {
+ return false;
+ }
+
+ MessageId get_message_id() const final {
+ return message_id_;
+ }
+
+ vector<FileId> get_file_ids(const Td *td) const final {
+ return {};
+ }
+
+ td_api::object_ptr<td_api::NotificationType> get_notification_type_object(DialogId dialog_id) const final {
+ auto message_object = G()->td().get_actor_unsafe()->messages_manager_->get_message_object(
+ {dialog_id, message_id_}, "get_notification_type_object");
+ if (message_object == nullptr) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::notificationTypeNewMessage>(std::move(message_object), show_preview_);
+ }
+
+ StringBuilder &to_string_builder(StringBuilder &string_builder) const final {
+ return string_builder << "NewMessageNotification[" << message_id_ << ']';
+ }
+
+ MessageId message_id_;
+ bool show_preview_;
+
+ public:
+ NotificationTypeMessage(MessageId message_id, bool show_preview)
+ : message_id_(message_id), show_preview_(show_preview) {
+ }
+};
+
+class NotificationTypeSecretChat final : public NotificationType {
+ bool can_be_delayed() const final {
+ return false;
+ }
+
+ bool is_temporary() const final {
+ return false;
+ }
+
+ MessageId get_message_id() const final {
+ return MessageId();
+ }
+
+ vector<FileId> get_file_ids(const Td *td) const final {
+ return {};
+ }
+
+ td_api::object_ptr<td_api::NotificationType> get_notification_type_object(DialogId dialog_id) const final {
+ return td_api::make_object<td_api::notificationTypeNewSecretChat>();
+ }
+
+ StringBuilder &to_string_builder(StringBuilder &string_builder) const final {
+ return string_builder << "NewSecretChatNotification[]";
+ }
+
+ public:
+ NotificationTypeSecretChat() {
+ }
+};
+
+class NotificationTypeCall final : public NotificationType {
+ bool can_be_delayed() const final {
+ return false;
+ }
+
+ bool is_temporary() const final {
+ return false;
+ }
+
+ MessageId get_message_id() const final {
+ return MessageId::max();
+ }
+
+ vector<FileId> get_file_ids(const Td *td) const final {
+ return {};
+ }
+
+ td_api::object_ptr<td_api::NotificationType> get_notification_type_object(DialogId dialog_id) const final {
+ return td_api::make_object<td_api::notificationTypeNewCall>(call_id_.get());
+ }
+
+ StringBuilder &to_string_builder(StringBuilder &string_builder) const final {
+ return string_builder << "NewCallNotification[" << call_id_ << ']';
+ }
+
+ CallId call_id_;
+
+ public:
+ explicit NotificationTypeCall(CallId call_id) : call_id_(call_id) {
+ }
+};
+
+class NotificationTypePushMessage final : public NotificationType {
+ bool can_be_delayed() const final {
+ return false;
+ }
+
+ bool is_temporary() const final {
+ return true;
+ }
+
+ MessageId get_message_id() const final {
+ return message_id_;
+ }
+
+ vector<FileId> get_file_ids(const Td *td) const final {
+ if (!document_.empty()) {
+ return document_.get_file_ids(td);
+ }
+
+ return photo_get_file_ids(photo_);
+ }
+
+ static td_api::object_ptr<td_api::PushMessageContent> get_push_message_content_object(Slice key, const string &arg,
+ const Photo &photo,
+ const Document &document) {
+ bool is_pinned = false;
+ if (begins_with(key, "PINNED_")) {
+ is_pinned = true;
+ key = key.substr(7);
+ }
+ if (key == "MESSAGE") {
+ return td_api::make_object<td_api::pushMessageContentHidden>(is_pinned);
+ }
+ if (key == "MESSAGES") {
+ return td_api::make_object<td_api::pushMessageContentMediaAlbum>(to_integer<int32>(arg), true, true, false,
+ false);
+ }
+ CHECK(key.size() > 8);
+ switch (key[8]) {
+ case 'A':
+ if (key == "MESSAGE_ANIMATION") {
+ auto animations_manager = G()->td().get_actor_unsafe()->animations_manager_.get();
+ return td_api::make_object<td_api::pushMessageContentAnimation>(
+ animations_manager->get_animation_object(document.file_id), arg, is_pinned);
+ }
+ if (key == "MESSAGE_AUDIO") {
+ auto audios_manager = G()->td().get_actor_unsafe()->audios_manager_.get();
+ return td_api::make_object<td_api::pushMessageContentAudio>(
+ audios_manager->get_audio_object(document.file_id), is_pinned);
+ }
+ if (key == "MESSAGE_AUDIOS") {
+ return td_api::make_object<td_api::pushMessageContentMediaAlbum>(to_integer<int32>(arg), false, false, true,
+ false);
+ }
+ break;
+ case 'B':
+ if (key == "MESSAGE_BASIC_GROUP_CHAT_CREATE") {
+ return td_api::make_object<td_api::pushMessageContentBasicGroupChatCreate>();
+ }
+ break;
+ case 'C':
+ if (key == "MESSAGE_CHAT_ADD_MEMBERS") {
+ return td_api::make_object<td_api::pushMessageContentChatAddMembers>(arg, false, false);
+ }
+ if (key == "MESSAGE_CHAT_ADD_MEMBERS_RETURNED") {
+ return td_api::make_object<td_api::pushMessageContentChatAddMembers>(arg, false, true);
+ }
+ if (key == "MESSAGE_CHAT_ADD_MEMBERS_YOU") {
+ return td_api::make_object<td_api::pushMessageContentChatAddMembers>(arg, true, false);
+ }
+ if (key == "MESSAGE_CHAT_CHANGE_PHOTO") {
+ return td_api::make_object<td_api::pushMessageContentChatChangePhoto>();
+ }
+ if (key == "MESSAGE_CHAT_CHANGE_THEME") {
+ return td_api::make_object<td_api::pushMessageContentChatSetTheme>(arg);
+ }
+ if (key == "MESSAGE_CHAT_CHANGE_TITLE") {
+ return td_api::make_object<td_api::pushMessageContentChatChangeTitle>(arg);
+ }
+ if (key == "MESSAGE_CHAT_DELETE_MEMBER") {
+ return td_api::make_object<td_api::pushMessageContentChatDeleteMember>(arg, false, false);
+ }
+ if (key == "MESSAGE_CHAT_DELETE_MEMBER_LEFT") {
+ return td_api::make_object<td_api::pushMessageContentChatDeleteMember>(arg, false, true);
+ }
+ if (key == "MESSAGE_CHAT_DELETE_MEMBER_YOU") {
+ return td_api::make_object<td_api::pushMessageContentChatDeleteMember>(arg, true, false);
+ }
+ if (key == "MESSAGE_CHAT_JOIN_BY_LINK") {
+ return td_api::make_object<td_api::pushMessageContentChatJoinByLink>();
+ }
+ if (key == "MESSAGE_CHAT_JOIN_BY_REQUEST") {
+ return td_api::make_object<td_api::pushMessageContentChatJoinByRequest>();
+ }
+ if (key == "MESSAGE_CONTACT") {
+ return td_api::make_object<td_api::pushMessageContentContact>(arg, is_pinned);
+ }
+ if (key == "MESSAGE_CONTACT_REGISTERED") {
+ return td_api::make_object<td_api::pushMessageContentContactRegistered>();
+ }
+ break;
+ case 'D':
+ if (key == "MESSAGE_DOCUMENT") {
+ auto documents_manager = G()->td().get_actor_unsafe()->documents_manager_.get();
+ return td_api::make_object<td_api::pushMessageContentDocument>(
+ documents_manager->get_document_object(document.file_id, PhotoFormat::Jpeg), is_pinned);
+ }
+ if (key == "MESSAGE_DOCUMENTS") {
+ return td_api::make_object<td_api::pushMessageContentMediaAlbum>(to_integer<int32>(arg), false, false, false,
+ true);
+ }
+ break;
+ case 'F':
+ if (key == "MESSAGE_FORWARDS") {
+ return td_api::make_object<td_api::pushMessageContentMessageForwards>(to_integer<int32>(arg));
+ }
+ break;
+ case 'G':
+ if (key == "MESSAGE_GAME") {
+ return td_api::make_object<td_api::pushMessageContentGame>(arg, is_pinned);
+ }
+ if (key == "MESSAGE_GAME_SCORE") {
+ int32 score = 0;
+ string title;
+ if (!is_pinned) {
+ string score_str;
+ std::tie(score_str, title) = split(arg);
+ score = to_integer<int32>(score_str);
+ }
+ return td_api::make_object<td_api::pushMessageContentGameScore>(title, score, is_pinned);
+ }
+ break;
+ case 'I':
+ if (key == "MESSAGE_INVOICE") {
+ return td_api::make_object<td_api::pushMessageContentInvoice>(arg, is_pinned);
+ }
+ break;
+ case 'L':
+ if (key == "MESSAGE_LIVE_LOCATION") {
+ return td_api::make_object<td_api::pushMessageContentLocation>(false, is_pinned);
+ }
+ if (key == "MESSAGE_LOCATION") {
+ return td_api::make_object<td_api::pushMessageContentLocation>(true, is_pinned);
+ }
+ break;
+ case 'P':
+ if (key == "MESSAGE_PHOTO") {
+ auto file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ return td_api::make_object<td_api::pushMessageContentPhoto>(get_photo_object(file_manager, photo), arg, false,
+ is_pinned);
+ }
+ if (key == "MESSAGE_PHOTOS") {
+ return td_api::make_object<td_api::pushMessageContentMediaAlbum>(to_integer<int32>(arg), true, false, false,
+ false);
+ }
+ if (key == "MESSAGE_POLL") {
+ return td_api::make_object<td_api::pushMessageContentPoll>(arg, true, is_pinned);
+ }
+ break;
+ case 'Q':
+ if (key == "MESSAGE_QUIZ") {
+ return td_api::make_object<td_api::pushMessageContentPoll>(arg, false, is_pinned);
+ }
+ break;
+ case 'R':
+ if (key == "MESSAGE_RECURRING_PAYMENT") {
+ return td_api::make_object<td_api::pushMessageContentRecurringPayment>(arg);
+ }
+ break;
+ case 'S':
+ if (key == "MESSAGE_SECRET_PHOTO") {
+ return td_api::make_object<td_api::pushMessageContentPhoto>(nullptr, arg, true, false);
+ }
+ if (key == "MESSAGE_SECRET_VIDEO") {
+ return td_api::make_object<td_api::pushMessageContentVideo>(nullptr, arg, true, false);
+ }
+ if (key == "MESSAGE_SCREENSHOT_TAKEN") {
+ return td_api::make_object<td_api::pushMessageContentScreenshotTaken>();
+ }
+ if (key == "MESSAGE_STICKER") {
+ auto stickers_manager = G()->td().get_actor_unsafe()->stickers_manager_.get();
+ return td_api::make_object<td_api::pushMessageContentSticker>(
+ stickers_manager->get_sticker_object(document.file_id), trim(arg), is_pinned);
+ }
+ break;
+ case 'T':
+ if (key == "MESSAGE_TEXT") {
+ return td_api::make_object<td_api::pushMessageContentText>(arg, is_pinned);
+ }
+ break;
+ case 'V':
+ if (key == "MESSAGE_VIDEO") {
+ auto videos_manager = G()->td().get_actor_unsafe()->videos_manager_.get();
+ return td_api::make_object<td_api::pushMessageContentVideo>(
+ videos_manager->get_video_object(document.file_id), arg, false, is_pinned);
+ }
+ if (key == "MESSAGE_VIDEO_NOTE") {
+ auto video_notes_manager = G()->td().get_actor_unsafe()->video_notes_manager_.get();
+ return td_api::make_object<td_api::pushMessageContentVideoNote>(
+ video_notes_manager->get_video_note_object(document.file_id), is_pinned);
+ }
+ if (key == "MESSAGE_VIDEOS") {
+ return td_api::make_object<td_api::pushMessageContentMediaAlbum>(to_integer<int32>(arg), false, true, false,
+ false);
+ }
+ if (key == "MESSAGE_VOICE_NOTE") {
+ auto voice_notes_manager = G()->td().get_actor_unsafe()->voice_notes_manager_.get();
+ return td_api::make_object<td_api::pushMessageContentVoiceNote>(
+ voice_notes_manager->get_voice_note_object(document.file_id), is_pinned);
+ }
+ break;
+ default:
+ break;
+ }
+ UNREACHABLE();
+ }
+
+ td_api::object_ptr<td_api::NotificationType> get_notification_type_object(DialogId dialog_id) const final {
+ auto sender = get_message_sender_object(G()->td().get_actor_unsafe(), sender_user_id_, sender_dialog_id_,
+ "get_notification_type_object");
+ return td_api::make_object<td_api::notificationTypeNewPushMessage>(
+ message_id_.get(), std::move(sender), sender_name_, is_outgoing_,
+ get_push_message_content_object(key_, arg_, photo_, document_));
+ }
+
+ StringBuilder &to_string_builder(StringBuilder &string_builder) const final {
+ return string_builder << "NewPushMessageNotification[" << sender_user_id_ << "/" << sender_dialog_id_ << "/\""
+ << sender_name_ << "\", " << message_id_ << ", " << key_ << ", " << arg_ << ", " << photo_
+ << ", " << document_ << ']';
+ }
+
+ UserId sender_user_id_;
+ DialogId sender_dialog_id_;
+ MessageId message_id_;
+ string sender_name_;
+ string key_;
+ string arg_;
+ Photo photo_;
+ Document document_;
+ bool is_outgoing_;
+
+ public:
+ NotificationTypePushMessage(UserId sender_user_id, DialogId sender_dialog_id, string sender_name, bool is_outgoing,
+ MessageId message_id, string key, string arg, Photo photo, Document document)
+ : sender_user_id_(sender_user_id)
+ , sender_dialog_id_(sender_dialog_id)
+ , message_id_(message_id)
+ , sender_name_(std::move(sender_name))
+ , key_(std::move(key))
+ , arg_(std::move(arg))
+ , photo_(std::move(photo))
+ , document_(std::move(document))
+ , is_outgoing_(is_outgoing) {
+ }
+};
+
+unique_ptr<NotificationType> create_new_message_notification(MessageId message_id, bool show_preview) {
+ return make_unique<NotificationTypeMessage>(message_id, show_preview);
+}
+
+unique_ptr<NotificationType> create_new_secret_chat_notification() {
+ return make_unique<NotificationTypeSecretChat>();
+}
+
+unique_ptr<NotificationType> create_new_call_notification(CallId call_id) {
+ return make_unique<NotificationTypeCall>(call_id);
+}
+
+unique_ptr<NotificationType> create_new_push_message_notification(UserId sender_user_id, DialogId sender_dialog_id,
+ string sender_name, bool is_outgoing,
+ MessageId message_id, string key, string arg,
+ Photo photo, Document document) {
+ return td::make_unique<NotificationTypePushMessage>(sender_user_id, sender_dialog_id, std::move(sender_name),
+ is_outgoing, message_id, std::move(key), std::move(arg),
+ std::move(photo), std::move(document));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/NotificationType.h b/protocols/Telegram/tdlib/td/td/telegram/NotificationType.h
new file mode 100644
index 0000000000..d3689213f9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/NotificationType.h
@@ -0,0 +1,67 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/CallId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class NotificationType {
+ public:
+ NotificationType() = default;
+ NotificationType(const NotificationType &) = delete;
+ NotificationType &operator=(const NotificationType &) = delete;
+ NotificationType(NotificationType &&) = delete;
+ NotificationType &operator=(NotificationType &&) = delete;
+ virtual ~NotificationType() = default;
+
+ virtual bool can_be_delayed() const = 0;
+
+ virtual bool is_temporary() const = 0;
+
+ virtual MessageId get_message_id() const = 0;
+
+ virtual vector<FileId> get_file_ids(const Td *td) const = 0;
+
+ virtual td_api::object_ptr<td_api::NotificationType> get_notification_type_object(DialogId dialog_id) const = 0;
+
+ virtual StringBuilder &to_string_builder(StringBuilder &string_builder) const = 0;
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, const NotificationType &notification_type) {
+ return notification_type.to_string_builder(string_builder);
+}
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, const unique_ptr<NotificationType> &notification_type) {
+ if (notification_type == nullptr) {
+ return string_builder << "null";
+ }
+ return string_builder << *notification_type;
+}
+
+unique_ptr<NotificationType> create_new_message_notification(MessageId message_id, bool show_preview);
+
+unique_ptr<NotificationType> create_new_secret_chat_notification();
+
+unique_ptr<NotificationType> create_new_call_notification(CallId call_id);
+
+unique_ptr<NotificationType> create_new_push_message_notification(UserId sender_user_id, DialogId sender_dialog_id,
+ string sender_name, bool is_outgoing,
+ MessageId message_id, string key, string arg,
+ Photo photo, Document document);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/OptionManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/OptionManager.cpp
new file mode 100644
index 0000000000..966a292202
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/OptionManager.cpp
@@ -0,0 +1,863 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/OptionManager.h"
+
+#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/AttachMenuManager.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/GitCommitHash.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/JsonValue.h"
+#include "td/telegram/LanguagePackManager.h"
+#include "td/telegram/MessageReaction.h"
+#include "td/telegram/net/MtprotoHeader.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/NotificationManager.h"
+#include "td/telegram/StateManager.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/StorageManager.h"
+#include "td/telegram/SuggestedAction.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TopDialogManager.h"
+
+#include "td/db/KeyValueSyncInterface.h"
+#include "td/db/TsSeqKeyValue.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/Clocks.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+
+#include <cmath>
+#include <functional>
+#include <limits>
+
+namespace td {
+
+OptionManager::OptionManager(Td *td)
+ : td_(td)
+ , current_scheduler_id_(Scheduler::instance()->sched_id())
+ , options_(td::make_unique<TsSeqKeyValue>())
+ , option_pmc_(G()->td_db()->get_config_pmc_shared()) {
+ send_unix_time_update();
+
+ auto all_options = option_pmc_->get_all();
+ all_options["utc_time_offset"] = PSTRING() << 'I' << Clocks::tz_offset();
+ for (const auto &name_value : all_options) {
+ const string &name = name_value.first;
+ options_->set(name, name_value.second);
+ if (!is_internal_option(name)) {
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateOption>(name, get_option_value_object(name_value.second)));
+ } else if (name == "otherwise_relogin_days") {
+ auto days = narrow_cast<int32>(get_option_integer(name));
+ if (days > 0) {
+ vector<SuggestedAction> added_actions{SuggestedAction{SuggestedAction::Type::SetPassword, DialogId(), days}};
+ send_closure(G()->td(), &Td::send_update, get_update_suggested_actions_object(added_actions, {}));
+ }
+ } else if (name == "default_reaction") {
+ auto value = get_option_string(name);
+ if (value.empty()) {
+ // legacy
+ set_option_empty(name);
+ } else {
+ send_update_default_reaction_type(value);
+ }
+ }
+ }
+
+ if (!have_option("message_text_length_max")) {
+ set_option_integer("message_text_length_max", 4096);
+ }
+ if (!have_option("message_caption_length_max")) {
+ set_option_integer("message_caption_length_max", 1024);
+ }
+ if (!have_option("bio_length_max")) {
+ set_option_integer("bio_length_max", 70);
+ }
+ if (!have_option("suggested_video_note_length")) {
+ set_option_integer("suggested_video_note_length", 384);
+ }
+ if (!have_option("suggested_video_note_video_bitrate")) {
+ set_option_integer("suggested_video_note_video_bitrate", 1000);
+ }
+ if (!have_option("suggested_video_note_audio_bitrate")) {
+ set_option_integer("suggested_video_note_audio_bitrate", 64);
+ }
+ if (!have_option("notification_sound_duration_max")) {
+ set_option_integer("notification_sound_duration_max", 5);
+ }
+ if (!have_option("notification_sound_size_max")) {
+ set_option_integer("notification_sound_size_max", 307200);
+ }
+ if (!have_option("notification_sound_count_max")) {
+ set_option_integer("notification_sound_count_max", G()->is_test_dc() ? 5 : 100);
+ }
+ if (!have_option("chat_filter_count_max")) {
+ set_option_integer("chat_filter_count_max", G()->is_test_dc() ? 3 : 10);
+ }
+ if (!have_option("chat_filter_chosen_chat_count_max")) {
+ set_option_integer("chat_filter_chosen_chat_count_max", G()->is_test_dc() ? 5 : 100);
+ }
+ if (!have_option("themed_emoji_statuses_sticker_set_id")) {
+ auto sticker_set_id =
+ G()->is_test_dc() ? static_cast<int64>(2964141614563343) : static_cast<int64>(773947703670341676);
+ set_option_integer("themed_emoji_statuses_sticker_set_id", sticker_set_id);
+ }
+ if (!have_option("forum_member_count_min")) {
+ set_option_integer("forum_member_count_min", 200);
+ }
+}
+
+OptionManager::~OptionManager() = default;
+
+void OptionManager::on_td_inited() {
+ is_td_inited_ = true;
+
+ for (auto &request : pending_get_options_) {
+ get_option(request.first, std::move(request.second));
+ }
+ reset_to_empty(pending_get_options_);
+}
+
+void OptionManager::set_option_boolean(Slice name, bool value) {
+ set_option(name, value ? Slice("Btrue") : Slice("Bfalse"));
+}
+
+void OptionManager::set_option_empty(Slice name) {
+ set_option(name, Slice());
+}
+
+void OptionManager::set_option_integer(Slice name, int64 value) {
+ set_option(name, PSLICE() << 'I' << value);
+}
+
+void OptionManager::set_option_string(Slice name, Slice value) {
+ set_option(name, PSLICE() << 'S' << value);
+}
+
+bool OptionManager::have_option(Slice name) const {
+ return options_->isset(name.str());
+}
+
+bool OptionManager::get_option_boolean(Slice name, bool default_value) const {
+ auto value = get_option(name);
+ if (value.empty()) {
+ return default_value;
+ }
+ if (value == "Btrue") {
+ return true;
+ }
+ if (value == "Bfalse") {
+ return false;
+ }
+ LOG(ERROR) << "Found \"" << value << "\" instead of boolean option " << name;
+ return default_value;
+}
+
+int64 OptionManager::get_option_integer(Slice name, int64 default_value) const {
+ auto value = get_option(name);
+ if (value.empty()) {
+ return default_value;
+ }
+ if (value[0] != 'I') {
+ LOG(ERROR) << "Found \"" << value << "\" instead of integer option " << name;
+ return default_value;
+ }
+ return to_integer<int64>(value.substr(1));
+}
+
+string OptionManager::get_option_string(Slice name, string default_value) const {
+ auto value = get_option(name);
+ if (value.empty()) {
+ return default_value;
+ }
+ if (value[0] != 'S') {
+ LOG(ERROR) << "Found \"" << value << "\" instead of string option " << name;
+ return default_value;
+ }
+ return value.substr(1);
+}
+
+void OptionManager::set_option(Slice name, Slice value) {
+ CHECK(!name.empty());
+ CHECK(Scheduler::instance()->sched_id() == current_scheduler_id_);
+ if (value.empty()) {
+ if (option_pmc_->erase(name.str()) == 0) {
+ return;
+ }
+ option_pmc_->erase(name.str());
+ } else {
+ if (options_->set(name, value) == 0) {
+ return;
+ }
+ option_pmc_->set(name.str(), value.str());
+ }
+
+ if (!G()->close_flag() && is_td_inited_) {
+ on_option_updated(name);
+ }
+
+ if (!is_internal_option(name)) {
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateOption>(name.str(), get_option_value_object(get_option(name))));
+ }
+}
+
+string OptionManager::get_option(Slice name) const {
+ return options_->get(name.str());
+}
+
+td_api::object_ptr<td_api::OptionValue> OptionManager::get_unix_time_option_value_object() {
+ return td_api::make_object<td_api::optionValueInteger>(G()->unix_time());
+}
+
+void OptionManager::send_unix_time_update() {
+ last_sent_server_time_difference_ = G()->get_server_time_difference();
+ td_->send_update(td_api::make_object<td_api::updateOption>("unix_time", get_unix_time_option_value_object()));
+}
+
+void OptionManager::on_update_server_time_difference() {
+ // can be called from any thread
+ if (std::abs(G()->get_server_time_difference() - last_sent_server_time_difference_) < 0.5) {
+ return;
+ }
+
+ send_unix_time_update();
+}
+
+bool OptionManager::is_internal_option(Slice name) {
+ switch (name[0]) {
+ case 'a':
+ return name == "about_length_limit_default" || name == "about_length_limit_premium" ||
+ name == "animated_emoji_zoom" || name == "animation_search_emojis" || name == "animation_search_provider";
+ case 'b':
+ return name == "base_language_pack_version";
+ case 'c':
+ return name == "call_receive_timeout_ms" || name == "call_ring_timeout_ms" ||
+ name == "caption_length_limit_default" || name == "caption_length_limit_premium" ||
+ name == "channels_limit_default" || name == "channels_limit_premium" ||
+ name == "channels_public_limit_default" || name == "channels_public_limit_premium" ||
+ name == "channels_read_media_period" || name == "chat_read_mark_expire_period" ||
+ name == "chat_read_mark_size_threshold";
+ case 'd':
+ return name == "dc_txt_domain_name" || name == "default_reaction" || name == "default_reaction_needs_sync" ||
+ name == "dialog_filters_chats_limit_default" || name == "dialog_filters_chats_limit_premium" ||
+ name == "dialog_filters_limit_default" || name == "dialog_filters_limit_premium" ||
+ name == "dialogs_folder_pinned_limit_default" || name == "dialogs_folder_pinned_limit_premium" ||
+ name == "dialogs_pinned_limit_default" || name == "dialogs_pinned_limit_premium" ||
+ name == "dice_emojis" || name == "dice_success_values";
+ case 'e':
+ return name == "edit_time_limit" || name == "emoji_sounds";
+ case 'i':
+ return name == "ignored_restriction_reasons";
+ case 'l':
+ return name == "language_pack_version";
+ case 'm':
+ return name == "my_phone_number";
+ case 'n':
+ return name == "notification_cloud_delay_ms" || name == "notification_default_delay_ms";
+ case 'o':
+ return name == "online_cloud_timeout_ms" || name == "online_update_period_ms" || name == "otherwise_relogin_days";
+ case 'p':
+ return name == "premium_bot_username" || name == "premium_features" || name == "premium_invoice_slug";
+ case 'r':
+ return name == "rating_e_decay" || name == "reactions_uniq_max" || name == "reactions_user_max_default" ||
+ name == "reactions_user_max_premium" || name == "recent_stickers_limit" || name == "revoke_pm_inbox" ||
+ name == "revoke_time_limit" || name == "revoke_pm_time_limit";
+ case 's':
+ return name == "saved_animations_limit" || name == "saved_gifs_limit_default" ||
+ name == "saved_gifs_limit_premium" || name == "session_count" || name == "stickers_faved_limit_default" ||
+ name == "stickers_faved_limit_premium" || name == "stickers_normal_by_emoji_per_premium_num" ||
+ name == "stickers_premium_by_emoji_num";
+ case 'v':
+ return name == "video_note_size_max";
+ case 'w':
+ return name == "webfile_dc_id";
+ default:
+ return false;
+ }
+}
+
+const vector<Slice> &OptionManager::get_synchronous_options() {
+ static const vector<Slice> options{"version", "commit_hash"};
+ return options;
+}
+
+bool OptionManager::is_synchronous_option(Slice name) {
+ return td::contains(get_synchronous_options(), name);
+}
+
+void OptionManager::on_option_updated(Slice name) {
+ switch (name[0]) {
+ case 'a':
+ if (name == "animated_emoji_zoom") {
+ // nothing to do: animated emoji zoom is updated only at launch
+ }
+ if (name == "animation_search_emojis") {
+ td_->animations_manager_->on_update_animation_search_emojis();
+ }
+ if (name == "animation_search_provider") {
+ td_->animations_manager_->on_update_animation_search_provider();
+ }
+ break;
+ case 'b':
+ if (name == "base_language_pack_version") {
+ send_closure(td_->language_pack_manager_, &LanguagePackManager::on_language_pack_version_changed, true, -1);
+ }
+ break;
+ case 'c':
+ if (name == "connection_parameters") {
+ if (G()->mtproto_header().set_parameters(get_option_string(name))) {
+ G()->net_query_dispatcher().update_mtproto_header();
+ }
+ }
+ break;
+ case 'd':
+ if (name == "default_reaction") {
+ send_update_default_reaction_type(get_option_string(name));
+ }
+ if (name == "dice_emojis") {
+ send_closure(td_->stickers_manager_actor_, &StickersManager::on_update_dice_emojis);
+ }
+ if (name == "dice_success_values") {
+ send_closure(td_->stickers_manager_actor_, &StickersManager::on_update_dice_success_values);
+ }
+ if (name == "disable_animated_emoji") {
+ td_->stickers_manager_->on_update_disable_animated_emojis();
+ }
+ if (name == "disable_contact_registered_notifications") {
+ send_closure(td_->notification_manager_actor_,
+ &NotificationManager::on_disable_contact_registered_notifications_changed);
+ }
+ if (name == "disable_top_chats") {
+ send_closure(td_->top_dialog_manager_actor_, &TopDialogManager::update_is_enabled, !get_option_boolean(name));
+ }
+ break;
+ case 'e':
+ if (name == "emoji_sounds") {
+ send_closure(td_->stickers_manager_actor_, &StickersManager::on_update_emoji_sounds);
+ }
+ break;
+ case 'f':
+ if (name == "favorite_stickers_limit") {
+ td_->stickers_manager_->on_update_favorite_stickers_limit();
+ }
+ break;
+ case 'i':
+ if (name == "ignored_restriction_reasons") {
+ send_closure(td_->contacts_manager_actor_, &ContactsManager::on_ignored_restriction_reasons_changed);
+ }
+ if (name == "is_emulator") {
+ if (G()->mtproto_header().set_is_emulator(get_option_boolean(name))) {
+ G()->net_query_dispatcher().update_mtproto_header();
+ }
+ }
+ break;
+ case 'l':
+ if (name == "language_pack_id") {
+ send_closure(td_->language_pack_manager_, &LanguagePackManager::on_language_code_changed);
+ if (G()->mtproto_header().set_language_code(get_option_string(name))) {
+ G()->net_query_dispatcher().update_mtproto_header();
+ }
+ send_closure(td_->attach_menu_manager_actor_, &AttachMenuManager::reload_attach_menu_bots, Promise<Unit>());
+ }
+ if (name == "language_pack_version") {
+ send_closure(td_->language_pack_manager_, &LanguagePackManager::on_language_pack_version_changed, false, -1);
+ }
+ if (name == "localization_target") {
+ send_closure(td_->language_pack_manager_, &LanguagePackManager::on_language_pack_changed);
+ if (G()->mtproto_header().set_language_pack(get_option_string(name))) {
+ G()->net_query_dispatcher().update_mtproto_header();
+ }
+ }
+ break;
+ case 'n':
+ if (name == "notification_cloud_delay_ms") {
+ send_closure(td_->notification_manager_actor_, &NotificationManager::on_notification_cloud_delay_changed);
+ }
+ if (name == "notification_default_delay_ms") {
+ send_closure(td_->notification_manager_actor_, &NotificationManager::on_notification_default_delay_changed);
+ }
+ if (name == "notification_group_count_max") {
+ send_closure(td_->notification_manager_actor_, &NotificationManager::on_notification_group_count_max_changed,
+ true);
+ }
+ if (name == "notification_group_size_max") {
+ send_closure(td_->notification_manager_actor_, &NotificationManager::on_notification_group_size_max_changed);
+ }
+ break;
+ case 'o':
+ if (name == "online_cloud_timeout_ms") {
+ send_closure(td_->notification_manager_actor_, &NotificationManager::on_online_cloud_timeout_changed);
+ }
+ if (name == "otherwise_relogin_days") {
+ auto days = narrow_cast<int32>(get_option_integer(name));
+ if (days > 0) {
+ vector<SuggestedAction> added_actions{SuggestedAction{SuggestedAction::Type::SetPassword, DialogId(), days}};
+ send_closure(G()->td(), &Td::send_update, get_update_suggested_actions_object(added_actions, {}));
+ }
+ }
+ break;
+ case 'r':
+ if (name == "rating_e_decay") {
+ send_closure(td_->top_dialog_manager_actor_, &TopDialogManager::update_rating_e_decay);
+ }
+ if (name == "recent_stickers_limit") {
+ td_->stickers_manager_->on_update_recent_stickers_limit();
+ }
+ break;
+ case 's':
+ if (name == "saved_animations_limit") {
+ td_->animations_manager_->on_update_saved_animations_limit();
+ }
+ if (name == "session_count") {
+ G()->net_query_dispatcher().update_session_count();
+ }
+ break;
+ case 'u':
+ if (name == "use_pfs") {
+ G()->net_query_dispatcher().update_use_pfs();
+ }
+ if (name == "use_storage_optimizer") {
+ send_closure(td_->storage_manager_, &StorageManager::update_use_storage_optimizer);
+ }
+ if (name == "utc_time_offset") {
+ if (G()->mtproto_header().set_tz_offset(static_cast<int32>(get_option_integer(name)))) {
+ G()->net_query_dispatcher().update_mtproto_header();
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void OptionManager::get_option(const string &name, Promise<td_api::object_ptr<td_api::OptionValue>> &&promise) {
+ bool is_bot = td_->auth_manager_ != nullptr && td_->auth_manager_->is_authorized() && td_->auth_manager_->is_bot();
+ auto wrap_promise = [this, &promise, &name] {
+ return PromiseCreator::lambda([this, promise = std::move(promise), name](Unit result) mutable {
+ // the option is already updated on success, ignore errors
+ promise.set_value(get_option_value_object(get_option(name)));
+ });
+ };
+ switch (name[0]) {
+ // all these options should be added to getCurrentState
+ case 'a':
+ if (!is_bot && name == "archive_and_mute_new_chats_from_unknown_users") {
+ return send_closure_later(td_->config_manager_, &ConfigManager::get_global_privacy_settings, wrap_promise());
+ }
+ break;
+ case 'c':
+ if (!is_bot && name == "can_ignore_sensitive_content_restrictions") {
+ return send_closure_later(td_->config_manager_, &ConfigManager::get_content_settings, wrap_promise());
+ }
+ break;
+ case 'd':
+ if (!is_bot && name == "disable_contact_registered_notifications") {
+ if (is_td_inited_) {
+ send_closure_later(td_->notification_manager_actor_,
+ &NotificationManager::get_disable_contact_registered_notifications, wrap_promise());
+ } else {
+ pending_get_options_.emplace_back(name, std::move(promise));
+ }
+ return;
+ }
+ break;
+ case 'i':
+ if (!is_bot && name == "ignore_sensitive_content_restrictions") {
+ return send_closure_later(td_->config_manager_, &ConfigManager::get_content_settings, wrap_promise());
+ }
+ if (!is_bot && name == "is_location_visible") {
+ if (is_td_inited_) {
+ send_closure_later(td_->contacts_manager_actor_, &ContactsManager::get_is_location_visible, wrap_promise());
+ } else {
+ pending_get_options_.emplace_back(name, std::move(promise));
+ }
+ return;
+ }
+ break;
+ case 'o':
+ if (name == "online") {
+ return promise.set_value(td_api::make_object<td_api::optionValueBoolean>(td_->is_online()));
+ }
+ break;
+ case 'u':
+ if (name == "unix_time") {
+ return promise.set_value(get_unix_time_option_value_object());
+ }
+ break;
+ }
+ wrap_promise().set_value(Unit());
+}
+
+td_api::object_ptr<td_api::OptionValue> OptionManager::get_option_synchronously(Slice name) {
+ CHECK(!name.empty());
+ switch (name[0]) {
+ case 'c':
+ if (name == "commit_hash") {
+ return td_api::make_object<td_api::optionValueString>(get_git_commit_hash());
+ }
+ break;
+ case 'v':
+ if (name == "version") {
+ return td_api::make_object<td_api::optionValueString>("1.8.8");
+ }
+ break;
+ }
+ UNREACHABLE();
+}
+
+void OptionManager::set_option(const string &name, td_api::object_ptr<td_api::OptionValue> &&value,
+ Promise<Unit> &&promise) {
+ int32 value_constructor_id = value == nullptr ? td_api::optionValueEmpty::ID : value->get_id();
+
+ auto set_integer_option = [&](Slice option_name, int64 min_value = 0,
+ int64 max_value = std::numeric_limits<int32>::max()) {
+ if (name != option_name) {
+ return false;
+ }
+ if (value_constructor_id == td_api::optionValueEmpty::ID) {
+ set_option_empty(option_name);
+ } else {
+ if (value_constructor_id != td_api::optionValueInteger::ID) {
+ promise.set_error(Status::Error(400, PSLICE() << "Option \"" << name << "\" must have integer value"));
+ return false;
+ }
+
+ int64 int_value = static_cast<td_api::optionValueInteger *>(value.get())->value_;
+ if (int_value < min_value || int_value > max_value) {
+ promise.set_error(Status::Error(400, PSLICE() << "Option's \"" << name << "\" value " << int_value
+ << " is outside of the valid range [" << min_value << ", "
+ << max_value << "]"));
+ return false;
+ }
+ set_option_integer(name, int_value);
+ }
+ promise.set_value(Unit());
+ return true;
+ };
+
+ auto set_boolean_option = [&](Slice option_name) {
+ if (name != option_name) {
+ return false;
+ }
+ if (value_constructor_id == td_api::optionValueEmpty::ID) {
+ set_option_empty(name);
+ } else {
+ if (value_constructor_id != td_api::optionValueBoolean::ID) {
+ promise.set_error(Status::Error(400, PSLICE() << "Option \"" << name << "\" must have boolean value"));
+ return false;
+ }
+
+ bool bool_value = static_cast<td_api::optionValueBoolean *>(value.get())->value_;
+ set_option_boolean(name, bool_value);
+ }
+ promise.set_value(Unit());
+ return true;
+ };
+
+ auto set_string_option = [&](Slice option_name, std::function<bool(Slice)> check_value) {
+ if (name != option_name) {
+ return false;
+ }
+ if (value_constructor_id == td_api::optionValueEmpty::ID) {
+ set_option_empty(name);
+ } else {
+ if (value_constructor_id != td_api::optionValueString::ID) {
+ promise.set_error(Status::Error(400, PSLICE() << "Option \"" << name << "\" must have string value"));
+ return false;
+ }
+
+ const string &str_value = static_cast<td_api::optionValueString *>(value.get())->value_;
+ if (str_value.empty()) {
+ set_option_empty(name);
+ } else {
+ if (check_value(str_value)) {
+ set_option_string(name, str_value);
+ } else {
+ promise.set_error(Status::Error(400, PSLICE() << "Option \"" << name << "\" can't have specified value"));
+ return false;
+ }
+ }
+ }
+ promise.set_value(Unit());
+ return true;
+ };
+
+ bool is_bot = td_->auth_manager_ != nullptr && td_->auth_manager_->is_authorized() && td_->auth_manager_->is_bot();
+ switch (name[0]) {
+ case 'a':
+ if (set_boolean_option("always_parse_markdown")) {
+ return;
+ }
+ if (!is_bot && name == "archive_and_mute_new_chats_from_unknown_users") {
+ if (value_constructor_id != td_api::optionValueBoolean::ID &&
+ value_constructor_id != td_api::optionValueEmpty::ID) {
+ return promise.set_error(
+ Status::Error(400, "Option \"archive_and_mute_new_chats_from_unknown_users\" must have boolean value"));
+ }
+
+ auto archive_and_mute = value_constructor_id == td_api::optionValueBoolean::ID &&
+ static_cast<td_api::optionValueBoolean *>(value.get())->value_;
+ send_closure_later(td_->config_manager_, &ConfigManager::set_archive_and_mute, archive_and_mute,
+ std::move(promise));
+ return;
+ }
+ break;
+ case 'c':
+ if (!is_bot && set_string_option("connection_parameters", [](Slice value) {
+ string value_copy = value.str();
+ auto r_json_value = get_json_value(value_copy);
+ if (r_json_value.is_error()) {
+ return false;
+ }
+ return r_json_value.ok()->get_id() == td_api::jsonValueObject::ID;
+ })) {
+ return;
+ }
+ break;
+ case 'd':
+ if (!is_bot && set_boolean_option("disable_animated_emoji")) {
+ return;
+ }
+ if (!is_bot && set_boolean_option("disable_contact_registered_notifications")) {
+ return;
+ }
+ if (!is_bot && set_boolean_option("disable_sent_scheduled_message_notifications")) {
+ return;
+ }
+ if (!is_bot && set_boolean_option("disable_top_chats")) {
+ return;
+ }
+ if (set_boolean_option("disable_persistent_network_statistics")) {
+ return;
+ }
+ if (set_boolean_option("disable_time_adjustment_protection")) {
+ return;
+ }
+ if (name == "drop_notification_ids") {
+ G()->td_db()->get_binlog_pmc()->erase("notification_id_current");
+ G()->td_db()->get_binlog_pmc()->erase("notification_group_id_current");
+ return promise.set_value(Unit());
+ }
+ break;
+ case 'i':
+ if (set_boolean_option("ignore_background_updates")) {
+ return;
+ }
+ if (set_boolean_option("ignore_default_disable_notification")) {
+ return;
+ }
+ if (set_boolean_option("ignore_inline_thumbnails")) {
+ return;
+ }
+ if (set_boolean_option("ignore_platform_restrictions")) {
+ return;
+ }
+ if (set_boolean_option("is_emulator")) {
+ return;
+ }
+ if (!is_bot && name == "ignore_sensitive_content_restrictions") {
+ if (!get_option_boolean("can_ignore_sensitive_content_restrictions")) {
+ return promise.set_error(
+ Status::Error(400, "Option \"ignore_sensitive_content_restrictions\" can't be changed by the user"));
+ }
+
+ if (value_constructor_id != td_api::optionValueBoolean::ID &&
+ value_constructor_id != td_api::optionValueEmpty::ID) {
+ return promise.set_error(
+ Status::Error(400, "Option \"ignore_sensitive_content_restrictions\" must have boolean value"));
+ }
+
+ auto ignore_sensitive_content_restrictions = value_constructor_id == td_api::optionValueBoolean::ID &&
+ static_cast<td_api::optionValueBoolean *>(value.get())->value_;
+ send_closure_later(td_->config_manager_, &ConfigManager::set_content_settings,
+ ignore_sensitive_content_restrictions, std::move(promise));
+ return;
+ }
+ if (!is_bot && set_boolean_option("is_location_visible")) {
+ ContactsManager::set_location_visibility(td_);
+ return;
+ }
+ break;
+ case 'l':
+ if (!is_bot && set_string_option("language_pack_database_path", [](Slice value) { return true; })) {
+ return;
+ }
+ if (!is_bot && set_string_option("localization_target", LanguagePackManager::check_language_pack_name)) {
+ return;
+ }
+ if (!is_bot && set_string_option("language_pack_id", LanguagePackManager::check_language_code_name)) {
+ return;
+ }
+ break;
+ case 'm':
+ if (set_integer_option("message_unload_delay", 60, 86400)) {
+ return;
+ }
+ break;
+ case 'n':
+ if (!is_bot &&
+ set_integer_option("notification_group_count_max", NotificationManager::MIN_NOTIFICATION_GROUP_COUNT_MAX,
+ NotificationManager::MAX_NOTIFICATION_GROUP_COUNT_MAX)) {
+ return;
+ }
+ if (!is_bot &&
+ set_integer_option("notification_group_size_max", NotificationManager::MIN_NOTIFICATION_GROUP_SIZE_MAX,
+ NotificationManager::MAX_NOTIFICATION_GROUP_SIZE_MAX)) {
+ return;
+ }
+ break;
+ case 'o':
+ if (name == "online") {
+ if (value_constructor_id != td_api::optionValueBoolean::ID &&
+ value_constructor_id != td_api::optionValueEmpty::ID) {
+ return promise.set_error(Status::Error(400, "Option \"online\" must have boolean value"));
+ }
+ bool is_online = value_constructor_id == td_api::optionValueEmpty::ID ||
+ static_cast<const td_api::optionValueBoolean *>(value.get())->value_;
+ td_->set_is_online(is_online);
+ if (!is_bot) {
+ send_closure(td_->state_manager_, &StateManager::on_online, is_online);
+ }
+ return promise.set_value(Unit());
+ }
+ break;
+ case 'p':
+ if (set_boolean_option("prefer_ipv6")) {
+ send_closure(td_->state_manager_, &StateManager::on_network_updated);
+ return;
+ }
+ break;
+ case 'r':
+ // temporary option
+ if (set_boolean_option("reuse_uploaded_photos_by_hash")) {
+ return;
+ }
+ break;
+ case 's':
+ if (set_integer_option("storage_max_files_size")) {
+ return;
+ }
+ if (set_integer_option("storage_max_time_from_last_access")) {
+ return;
+ }
+ if (set_integer_option("storage_max_file_count")) {
+ return;
+ }
+ if (set_integer_option("storage_immunity_delay")) {
+ return;
+ }
+ if (set_boolean_option("store_all_files_in_files_directory")) {
+ return;
+ }
+ break;
+ case 't':
+ if (set_boolean_option("test_flood_wait")) {
+ return;
+ }
+ break;
+ case 'u':
+ if (set_boolean_option("use_pfs")) {
+ return;
+ }
+ if (set_boolean_option("use_quick_ack")) {
+ return;
+ }
+ if (set_boolean_option("use_storage_optimizer")) {
+ return;
+ }
+ if (set_integer_option("utc_time_offset", -12 * 60 * 60, 14 * 60 * 60)) {
+ return;
+ }
+ break;
+ case 'X':
+ case 'x': {
+ if (name.size() > 255) {
+ return promise.set_error(Status::Error(400, "Option name is too long"));
+ }
+ switch (value_constructor_id) {
+ case td_api::optionValueBoolean::ID:
+ set_option_boolean(name, static_cast<const td_api::optionValueBoolean *>(value.get())->value_);
+ break;
+ case td_api::optionValueEmpty::ID:
+ set_option_empty(name);
+ break;
+ case td_api::optionValueInteger::ID:
+ set_option_integer(name, static_cast<const td_api::optionValueInteger *>(value.get())->value_);
+ break;
+ case td_api::optionValueString::ID:
+ set_option_string(name, static_cast<const td_api::optionValueString *>(value.get())->value_);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return promise.set_value(Unit());
+ }
+ }
+
+ if (promise) {
+ promise.set_error(Status::Error(400, "Option can't be set"));
+ }
+}
+
+td_api::object_ptr<td_api::OptionValue> OptionManager::get_option_value_object(Slice value) {
+ if (value.empty()) {
+ return td_api::make_object<td_api::optionValueEmpty>();
+ }
+
+ switch (value[0]) {
+ case 'B':
+ if (value == "Btrue") {
+ return td_api::make_object<td_api::optionValueBoolean>(true);
+ }
+ if (value == "Bfalse") {
+ return td_api::make_object<td_api::optionValueBoolean>(false);
+ }
+ break;
+ case 'I':
+ return td_api::make_object<td_api::optionValueInteger>(to_integer<int64>(value.substr(1)));
+ case 'S':
+ return td_api::make_object<td_api::optionValueString>(value.substr(1).str());
+ }
+
+ return td_api::make_object<td_api::optionValueString>(value.str());
+}
+
+void OptionManager::get_common_state(vector<td_api::object_ptr<td_api::Update>> &updates) {
+ for (auto option_name : get_synchronous_options()) {
+ updates.push_back(
+ td_api::make_object<td_api::updateOption>(option_name.str(), get_option_synchronously(option_name)));
+ }
+}
+
+void OptionManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ get_common_state(updates);
+
+ updates.push_back(td_api::make_object<td_api::updateOption>(
+ "online", td_api::make_object<td_api::optionValueBoolean>(td_->is_online())));
+
+ updates.push_back(td_api::make_object<td_api::updateOption>("unix_time", get_unix_time_option_value_object()));
+
+ for (const auto &option : options_->get_all()) {
+ if (!is_internal_option(option.first)) {
+ updates.push_back(
+ td_api::make_object<td_api::updateOption>(option.first, get_option_value_object(option.second)));
+ }
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/OptionManager.h b/protocols/Telegram/tdlib/td/td/telegram/OptionManager.h
new file mode 100644
index 0000000000..5bed87c63c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/OptionManager.h
@@ -0,0 +1,94 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+
+#include <atomic>
+#include <memory>
+#include <utility>
+
+namespace td {
+
+class KeyValueSyncInterface;
+class Td;
+class TsSeqKeyValue;
+
+class OptionManager {
+ public:
+ explicit OptionManager(Td *td);
+ OptionManager(const OptionManager &) = delete;
+ OptionManager &operator=(const OptionManager &) = delete;
+ OptionManager(OptionManager &&) = delete;
+ OptionManager &operator=(OptionManager &&) = delete;
+ ~OptionManager();
+
+ void on_td_inited();
+
+ void set_option_boolean(Slice name, bool value);
+
+ void set_option_empty(Slice name);
+
+ void set_option_integer(Slice name, int64 value);
+
+ void set_option_string(Slice name, Slice value);
+
+ bool have_option(Slice name) const;
+
+ bool get_option_boolean(Slice name, bool default_value = false) const;
+
+ int64 get_option_integer(Slice name, int64 default_value = 0) const;
+
+ string get_option_string(Slice name, string default_value = "") const;
+
+ void on_update_server_time_difference();
+
+ void get_option(const string &name, Promise<td_api::object_ptr<td_api::OptionValue>> &&promise);
+
+ void set_option(const string &name, td_api::object_ptr<td_api::OptionValue> &&value, Promise<Unit> &&promise);
+
+ static bool is_synchronous_option(Slice name);
+
+ static td_api::object_ptr<td_api::OptionValue> get_option_synchronously(Slice name);
+
+ static void get_common_state(vector<td_api::object_ptr<td_api::Update>> &updates);
+
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
+ private:
+ void set_option(Slice name, Slice value);
+
+ void on_option_updated(Slice name);
+
+ string get_option(Slice name) const;
+
+ static bool is_internal_option(Slice name);
+
+ static const vector<Slice> &get_synchronous_options();
+
+ static td_api::object_ptr<td_api::OptionValue> get_unix_time_option_value_object();
+
+ static td_api::object_ptr<td_api::OptionValue> get_option_value_object(Slice value);
+
+ void send_unix_time_update();
+
+ Td *td_;
+ bool is_td_inited_ = false;
+ vector<std::pair<string, Promise<td_api::object_ptr<td_api::OptionValue>>>> pending_get_options_;
+
+ int32 current_scheduler_id_ = -1;
+ unique_ptr<TsSeqKeyValue> options_;
+ std::shared_ptr<KeyValueSyncInterface> option_pmc_;
+
+ std::atomic<double> last_sent_server_time_difference_{1e100};
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/OrderInfo.cpp b/protocols/Telegram/tdlib/td/td/telegram/OrderInfo.cpp
new file mode 100644
index 0000000000..cd00061f40
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/OrderInfo.cpp
@@ -0,0 +1,190 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/OrderInfo.h"
+
+#include "td/telegram/misc.h"
+
+#include "td/utils/format.h"
+#include "td/utils/JsonBuilder.h"
+
+namespace td {
+
+bool operator==(const Address &lhs, const Address &rhs) {
+ return lhs.country_code == rhs.country_code && lhs.state == rhs.state && lhs.city == rhs.city &&
+ lhs.street_line1 == rhs.street_line1 && lhs.street_line2 == rhs.street_line2 &&
+ lhs.postal_code == rhs.postal_code;
+}
+
+bool operator!=(const Address &lhs, const Address &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Address &address) {
+ return string_builder << "[Address " << tag("country_code", address.country_code) << tag("state", address.state)
+ << tag("city", address.city) << tag("street_line1", address.street_line1)
+ << tag("street_line2", address.street_line2) << tag("postal_code", address.postal_code) << "]";
+}
+
+unique_ptr<Address> get_address(tl_object_ptr<telegram_api::postAddress> &&address) {
+ if (address == nullptr) {
+ return nullptr;
+ }
+ return td::make_unique<Address>(std::move(address->country_iso2_), std::move(address->state_),
+ std::move(address->city_), std::move(address->street_line1_),
+ std::move(address->street_line2_), std::move(address->post_code_));
+}
+
+static bool is_capital_alpha(char c) {
+ return 'A' <= c && c <= 'Z';
+}
+
+Status check_country_code(string &country_code) {
+ if (!clean_input_string(country_code)) {
+ return Status::Error(400, "Country code must be encoded in UTF-8");
+ }
+ if (country_code.size() != 2 || !is_capital_alpha(country_code[0]) || !is_capital_alpha(country_code[1])) {
+ return Status::Error(400, "Wrong country code specified");
+ }
+ return Status::OK();
+}
+
+static Status check_state(string &state) {
+ if (!clean_input_string(state)) {
+ return Status::Error(400, "State must be encoded in UTF-8");
+ }
+ return Status::OK();
+}
+
+static Status check_city(string &city) {
+ if (!clean_input_string(city)) {
+ return Status::Error(400, "City must be encoded in UTF-8");
+ }
+ return Status::OK();
+}
+
+static Status check_street_line(string &street_line) {
+ if (!clean_input_string(street_line)) {
+ return Status::Error(400, "Street line must be encoded in UTF-8");
+ }
+ return Status::OK();
+}
+
+static Status check_postal_code(string &postal_code) {
+ if (!clean_input_string(postal_code)) {
+ return Status::Error(400, "Postal code must be encoded in UTF-8");
+ }
+ return Status::OK();
+}
+
+Result<Address> get_address(td_api::object_ptr<td_api::address> &&address) {
+ if (address == nullptr) {
+ return Status::Error(400, "Address must be non-empty");
+ }
+ TRY_STATUS(check_country_code(address->country_code_));
+ TRY_STATUS(check_state(address->state_));
+ TRY_STATUS(check_city(address->city_));
+ TRY_STATUS(check_street_line(address->street_line1_));
+ TRY_STATUS(check_street_line(address->street_line2_));
+ TRY_STATUS(check_postal_code(address->postal_code_));
+
+ return Address(std::move(address->country_code_), std::move(address->state_), std::move(address->city_),
+ std::move(address->street_line1_), std::move(address->street_line2_),
+ std::move(address->postal_code_));
+}
+
+tl_object_ptr<td_api::address> get_address_object(const unique_ptr<Address> &address) {
+ if (address == nullptr) {
+ return nullptr;
+ }
+ return get_address_object(*address);
+}
+
+tl_object_ptr<td_api::address> get_address_object(const Address &address) {
+ return make_tl_object<td_api::address>(address.country_code, address.state, address.city, address.street_line1,
+ address.street_line2, address.postal_code);
+}
+
+string address_to_json(const Address &address) {
+ return json_encode<std::string>(json_object([&](auto &o) {
+ o("country_code", address.country_code);
+ o("state", address.state);
+ o("city", address.city);
+ o("street_line1", address.street_line1);
+ o("street_line2", address.street_line2);
+ o("post_code", address.postal_code);
+ }));
+}
+
+Result<Address> address_from_json(Slice json) {
+ auto json_copy = json.str();
+ auto r_value = json_decode(json_copy);
+ if (r_value.is_error()) {
+ return Status::Error(400, "Can't parse address JSON object");
+ }
+
+ auto value = r_value.move_as_ok();
+ if (value.type() != JsonValue::Type::Object) {
+ return Status::Error(400, "Address must be an Object");
+ }
+
+ auto &object = value.get_object();
+ TRY_RESULT(country_code, get_json_object_string_field(object, "country_code", true));
+ TRY_RESULT(state, get_json_object_string_field(object, "state", true));
+ TRY_RESULT(city, get_json_object_string_field(object, "city", true));
+ TRY_RESULT(street_line1, get_json_object_string_field(object, "street_line1", true));
+ TRY_RESULT(street_line2, get_json_object_string_field(object, "street_line2", true));
+ TRY_RESULT(postal_code, get_json_object_string_field(object, "post_code", true));
+
+ TRY_STATUS(check_country_code(country_code));
+ TRY_STATUS(check_state(state));
+ TRY_STATUS(check_city(city));
+ TRY_STATUS(check_street_line(street_line1));
+ TRY_STATUS(check_street_line(street_line2));
+ TRY_STATUS(check_postal_code(postal_code));
+
+ return Address(std::move(country_code), std::move(state), std::move(city), std::move(street_line1),
+ std::move(street_line2), std::move(postal_code));
+}
+
+bool operator==(const OrderInfo &lhs, const OrderInfo &rhs) {
+ return lhs.name == rhs.name && lhs.phone_number == rhs.phone_number && lhs.email_address == rhs.email_address &&
+ ((lhs.shipping_address == nullptr && rhs.shipping_address == nullptr) ||
+ (lhs.shipping_address != nullptr && rhs.shipping_address != nullptr &&
+ *lhs.shipping_address == *rhs.shipping_address));
+}
+
+bool operator!=(const OrderInfo &lhs, const OrderInfo &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const OrderInfo &order_info) {
+ string_builder << "[OrderInfo " << tag("name", order_info.name) << tag("phone_number", order_info.phone_number)
+ << tag("email_address", order_info.email_address);
+ if (order_info.shipping_address != nullptr) {
+ string_builder << *order_info.shipping_address;
+ }
+ return string_builder << "]";
+}
+
+unique_ptr<OrderInfo> get_order_info(tl_object_ptr<telegram_api::paymentRequestedInfo> order_info) {
+ if (order_info == nullptr || order_info->flags_ == 0) {
+ return nullptr;
+ }
+ return td::make_unique<OrderInfo>(std::move(order_info->name_), std::move(order_info->phone_),
+ std::move(order_info->email_),
+ get_address(std::move(order_info->shipping_address_)));
+}
+
+tl_object_ptr<td_api::orderInfo> get_order_info_object(const unique_ptr<OrderInfo> &order_info) {
+ if (order_info == nullptr) {
+ return nullptr;
+ }
+ return make_tl_object<td_api::orderInfo>(order_info->name, order_info->phone_number, order_info->email_address,
+ get_address_object(order_info->shipping_address));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/OrderInfo.h b/protocols/Telegram/tdlib/td/td/telegram/OrderInfo.h
new file mode 100644
index 0000000000..8f840fad9f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/OrderInfo.h
@@ -0,0 +1,82 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+struct Address {
+ string country_code;
+ string state;
+ string city;
+ string street_line1;
+ string street_line2;
+ string postal_code;
+
+ Address() = default;
+ Address(string &&country_code, string &&state, string &&city, string &&street_line1, string &&street_line2,
+ string &&postal_code)
+ : country_code(std::move(country_code))
+ , state(std::move(state))
+ , city(std::move(city))
+ , street_line1(std::move(street_line1))
+ , street_line2(std::move(street_line2))
+ , postal_code(std::move(postal_code)) {
+ }
+};
+
+struct OrderInfo {
+ string name;
+ string phone_number;
+ string email_address;
+ unique_ptr<Address> shipping_address;
+
+ OrderInfo() = default;
+ OrderInfo(string &&name, string &&phone_number, string &&email_address, unique_ptr<Address> &&shipping_address)
+ : name(std::move(name))
+ , phone_number(std::move(phone_number))
+ , email_address(std::move(email_address))
+ , shipping_address(std::move(shipping_address)) {
+ }
+};
+
+bool operator==(const Address &lhs, const Address &rhs);
+bool operator!=(const Address &lhs, const Address &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Address &address);
+
+unique_ptr<Address> get_address(tl_object_ptr<telegram_api::postAddress> &&address);
+
+Result<Address> get_address(td_api::object_ptr<td_api::address> &&address);
+
+tl_object_ptr<td_api::address> get_address_object(const unique_ptr<Address> &address);
+
+tl_object_ptr<td_api::address> get_address_object(const Address &address);
+
+string address_to_json(const Address &address);
+
+Result<Address> address_from_json(Slice json);
+
+Status check_country_code(string &country_code);
+
+bool operator==(const OrderInfo &lhs, const OrderInfo &rhs);
+bool operator!=(const OrderInfo &lhs, const OrderInfo &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const OrderInfo &order_info);
+
+unique_ptr<OrderInfo> get_order_info(tl_object_ptr<telegram_api::paymentRequestedInfo> order_info);
+
+tl_object_ptr<td_api::orderInfo> get_order_info_object(const unique_ptr<OrderInfo> &order_info);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/OrderInfo.hpp b/protocols/Telegram/tdlib/td/td/telegram/OrderInfo.hpp
new file mode 100644
index 0000000000..b3559dd18a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/OrderInfo.hpp
@@ -0,0 +1,87 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/OrderInfo.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const Address &address, StorerT &storer) {
+ store(address.country_code, storer);
+ store(address.state, storer);
+ store(address.city, storer);
+ store(address.street_line1, storer);
+ store(address.street_line2, storer);
+ store(address.postal_code, storer);
+}
+
+template <class ParserT>
+void parse(Address &address, ParserT &parser) {
+ parse(address.country_code, parser);
+ parse(address.state, parser);
+ parse(address.city, parser);
+ parse(address.street_line1, parser);
+ parse(address.street_line2, parser);
+ parse(address.postal_code, parser);
+}
+
+template <class StorerT>
+void store(const OrderInfo &order_info, StorerT &storer) {
+ bool has_name = !order_info.name.empty();
+ bool has_phone_number = !order_info.phone_number.empty();
+ bool has_email_address = !order_info.email_address.empty();
+ bool has_shipping_address = order_info.shipping_address != nullptr;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_name);
+ STORE_FLAG(has_phone_number);
+ STORE_FLAG(has_email_address);
+ STORE_FLAG(has_shipping_address);
+ END_STORE_FLAGS();
+ if (has_name) {
+ store(order_info.name, storer);
+ }
+ if (has_phone_number) {
+ store(order_info.phone_number, storer);
+ }
+ if (has_email_address) {
+ store(order_info.email_address, storer);
+ }
+ if (has_shipping_address) {
+ store(order_info.shipping_address, storer);
+ }
+}
+
+template <class ParserT>
+void parse(OrderInfo &order_info, ParserT &parser) {
+ bool has_name;
+ bool has_phone_number;
+ bool has_email_address;
+ bool has_shipping_address;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_name);
+ PARSE_FLAG(has_phone_number);
+ PARSE_FLAG(has_email_address);
+ PARSE_FLAG(has_shipping_address);
+ END_PARSE_FLAGS();
+ if (has_name) {
+ parse(order_info.name, parser);
+ }
+ if (has_phone_number) {
+ parse(order_info.phone_number, parser);
+ }
+ if (has_email_address) {
+ parse(order_info.email_address, parser);
+ }
+ if (has_shipping_address) {
+ parse(order_info.shipping_address, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PasswordManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/PasswordManager.cpp
index 7fbff2ba0e..20f44b80d3 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/PasswordManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/PasswordManager.cpp
@@ -1,42 +1,166 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/PasswordManager.h"
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/DhCache.h"
+#include "td/telegram/DialogId.h"
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/SuggestedAction.h"
+#include "td/telegram/TdDb.h"
+#include "td/mtproto/DhHandshake.h"
+
+#include "td/utils/BigNum.h"
#include "td/utils/buffer.h"
#include "td/utils/crypto.h"
+#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/Random.h"
-
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h" // TODO: this file is already included. Why?
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Time.h"
namespace td {
-tl_object_ptr<td_api::temporaryPasswordState> TempPasswordState::as_td_api() const {
+tl_object_ptr<td_api::temporaryPasswordState> TempPasswordState::get_temporary_password_state_object() const {
if (!has_temp_password || valid_until <= G()->unix_time()) {
return make_tl_object<td_api::temporaryPasswordState>(false, 0);
}
return make_tl_object<td_api::temporaryPasswordState>(true, valid_until - G()->unix_time_cached());
}
-static BufferSlice calc_password_hash(const string &password, const string &salt) {
- if (password.empty()) {
- return BufferSlice();
- }
+static void hash_sha256(Slice data, Slice salt, MutableSlice dest) {
+ sha256(PSLICE() << salt << data << salt, dest);
+}
+
+BufferSlice PasswordManager::calc_password_hash(Slice password, Slice client_salt, Slice server_salt) {
+ LOG(INFO) << "Begin password hash calculation";
BufferSlice buf(32);
- string salted_password = salt + password + salt;
- sha256(salted_password, buf.as_slice());
+ hash_sha256(password, client_salt, buf.as_slice());
+ hash_sha256(buf.as_slice(), server_salt, buf.as_slice());
+ BufferSlice hash(64);
+ pbkdf2_sha512(buf.as_slice(), client_salt, 100000, hash.as_slice());
+ hash_sha256(hash.as_slice(), server_salt, buf.as_slice());
+ LOG(INFO) << "End password hash calculation";
return buf;
}
+Result<BufferSlice> PasswordManager::calc_password_srp_hash(Slice password, Slice client_salt, Slice server_salt,
+ int32 g, Slice p) {
+ LOG(INFO) << "Begin password SRP hash calculation";
+ TRY_STATUS(mtproto::DhHandshake::check_config(g, p, DhCache::instance()));
+
+ auto hash = calc_password_hash(password, client_salt, server_salt);
+ auto p_bn = BigNum::from_binary(p);
+ BigNum g_bn;
+ g_bn.set_value(g);
+ auto x_bn = BigNum::from_binary(hash.as_slice());
+
+ BigNumContext ctx;
+ BigNum v_bn;
+ BigNum::mod_exp(v_bn, g_bn, x_bn, p_bn, ctx);
+
+ BufferSlice result(v_bn.to_binary(256));
+ LOG(INFO) << "End password SRP hash calculation";
+ return std::move(result);
+}
+
+tl_object_ptr<telegram_api::InputCheckPasswordSRP> PasswordManager::get_input_check_password(
+ Slice password, Slice client_salt, Slice server_salt, int32 g, Slice p, Slice B, int64 id) {
+ if (password.empty()) {
+ return make_tl_object<telegram_api::inputCheckPasswordEmpty>();
+ }
+
+ if (mtproto::DhHandshake::check_config(g, p, DhCache::instance()).is_error()) {
+ LOG(ERROR) << "Receive invalid config " << g << " " << format::escaped(p);
+ return make_tl_object<telegram_api::inputCheckPasswordEmpty>();
+ }
+
+ auto p_bn = BigNum::from_binary(p);
+ auto B_bn = BigNum::from_binary(B);
+ auto zero = BigNum::from_decimal("0").move_as_ok();
+ if (BigNum::compare(zero, B_bn) != -1 || BigNum::compare(B_bn, p_bn) != -1 || B.size() < 248 || B.size() > 256) {
+ LOG(ERROR) << "Receive invalid value of B(" << B.size() << "): " << B_bn << " " << p_bn;
+ return make_tl_object<telegram_api::inputCheckPasswordEmpty>();
+ }
+
+ LOG(INFO) << "Begin input password SRP hash calculation";
+ BigNum g_bn;
+ g_bn.set_value(g);
+ auto g_padded = g_bn.to_binary(256);
+
+ auto x = calc_password_hash(password, client_salt, server_salt);
+ auto x_bn = BigNum::from_binary(x.as_slice());
+
+ BufferSlice a(2048 / 8);
+ Random::secure_bytes(a.as_slice());
+ auto a_bn = BigNum::from_binary(a.as_slice());
+
+ BigNumContext ctx;
+ BigNum A_bn;
+ BigNum::mod_exp(A_bn, g_bn, a_bn, p_bn, ctx);
+ string A = A_bn.to_binary(256);
+
+ string B_pad(256 - B.size(), '\0');
+ string u = sha256(PSLICE() << A << B_pad << B);
+ auto u_bn = BigNum::from_binary(u);
+ string k = sha256(PSLICE() << p << g_padded);
+ auto k_bn = BigNum::from_binary(k);
+
+ BigNum v_bn;
+ BigNum::mod_exp(v_bn, g_bn, x_bn, p_bn, ctx);
+ BigNum kv_bn;
+ BigNum::mod_mul(kv_bn, k_bn, v_bn, p_bn, ctx);
+ BigNum t_bn;
+ BigNum::sub(t_bn, B_bn, kv_bn);
+ if (BigNum::compare(t_bn, zero) == -1) {
+ BigNum::add(t_bn, t_bn, p_bn);
+ }
+ BigNum exp_bn;
+ BigNum::mul(exp_bn, u_bn, x_bn, ctx);
+ BigNum::add(exp_bn, exp_bn, a_bn);
+
+ BigNum S_bn;
+ BigNum::mod_exp(S_bn, t_bn, exp_bn, p_bn, ctx);
+ string S = S_bn.to_binary(256);
+ auto K = sha256(S);
+
+ auto h1 = sha256(p);
+ auto h2 = sha256(g_padded);
+ for (size_t i = 0; i < h1.size(); i++) {
+ h1[i] = static_cast<char>(static_cast<unsigned char>(h1[i]) ^ static_cast<unsigned char>(h2[i]));
+ }
+ auto M = sha256(PSLICE() << h1 << sha256(client_salt) << sha256(server_salt) << A << B_pad << B << K);
+
+ LOG(INFO) << "End input password SRP hash calculation";
+ return make_tl_object<telegram_api::inputCheckPasswordSRP>(id, BufferSlice(A), BufferSlice(M));
+}
+
+tl_object_ptr<telegram_api::InputCheckPasswordSRP> PasswordManager::get_input_check_password(
+ Slice password, const PasswordState &state) {
+ return get_input_check_password(password, state.current_client_salt, state.current_server_salt, state.current_srp_g,
+ state.current_srp_p, state.current_srp_B, state.current_srp_id);
+}
+
+void PasswordManager::get_input_check_password_srp(
+ string password, Promise<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> &&promise) {
+ do_get_state(PromiseCreator::lambda(
+ [promise = std::move(promise), password = std::move(password)](Result<PasswordState> r_state) mutable {
+ if (r_state.is_error()) {
+ return promise.set_error(r_state.move_as_error());
+ }
+ promise.set_value(PasswordManager::get_input_check_password(password, r_state.move_as_ok()));
+ }));
+}
+
void PasswordManager::set_password(string current_password, string new_password, string new_hint,
bool set_recovery_email_address, string recovery_email_address,
Promise<State> promise) {
@@ -44,6 +168,7 @@ void PasswordManager::set_password(string current_password, string new_password,
update_settings.current_password = std::move(current_password);
update_settings.update_password = true;
+ //update_settings.update_secure_secret = true;
update_settings.new_password = std::move(new_password);
update_settings.new_hint = std::move(new_hint);
@@ -54,6 +179,47 @@ void PasswordManager::set_password(string current_password, string new_password,
update_password_settings(std::move(update_settings), std::move(promise));
}
+
+void PasswordManager::set_login_email_address(string new_login_email_address, Promise<SentEmailCode> promise) {
+ last_set_login_email_address_ = new_login_email_address;
+ auto query = G()->net_query_creator().create(telegram_api::account_sendVerifyEmailCode(
+ make_tl_object<telegram_api::emailVerifyPurposeLoginChange>(), std::move(new_login_email_address)));
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::account_sendVerifyEmailCode>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+ return promise.set_value(SentEmailCode(r_result.move_as_ok()));
+ }));
+}
+
+void PasswordManager::resend_login_email_address_code(Promise<SentEmailCode> promise) {
+ if (last_set_login_email_address_.empty()) {
+ return promise.set_error(Status::Error(400, "No login email address code was sent"));
+ }
+ set_login_email_address(last_set_login_email_address_, std::move(promise));
+}
+
+void PasswordManager::check_login_email_address_code(EmailVerification &&code, Promise<Unit> promise) {
+ if (last_set_login_email_address_.empty()) {
+ return promise.set_error(Status::Error(400, "No login email address code was sent"));
+ }
+ if (code.is_empty()) {
+ return promise.set_error(Status::Error(400, "Verification code must be non-empty"));
+ }
+ auto query = G()->net_query_creator().create(telegram_api::account_verifyEmail(
+ make_tl_object<telegram_api::emailVerifyPurposeLoginChange>(), code.get_input_email_verification()));
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::account_verifyEmail>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+ return promise.set_value(Unit());
+ }));
+}
+
void PasswordManager::set_recovery_email_address(string password, string new_recovery_email_address,
Promise<State> promise) {
UpdateSettings update_settings;
@@ -64,8 +230,55 @@ void PasswordManager::set_recovery_email_address(string password, string new_rec
update_password_settings(std::move(update_settings), std::move(promise));
}
+void PasswordManager::get_secure_secret(string password, Promise<secure_storage::Secret> promise) {
+ return do_get_secure_secret(true, std::move(password), std::move(promise));
+}
+
+void PasswordManager::do_get_secure_secret(bool allow_recursive, string password,
+ Promise<secure_storage::Secret> promise) {
+ if (secret_) {
+ return promise.set_value(secret_.value().clone());
+ }
+ if (password.empty()) {
+ return promise.set_error(Status::Error(400, "PASSWORD_HASH_INVALID"));
+ }
+ get_full_state(
+ password, PromiseCreator::lambda([password, allow_recursive, promise = std::move(promise),
+ actor_id = actor_id(this)](Result<PasswordFullState> r_state) mutable {
+ if (r_state.is_error()) {
+ return promise.set_error(r_state.move_as_error());
+ }
+ auto state = r_state.move_as_ok();
+ if (!state.state.has_password) {
+ return promise.set_error(Status::Error(400, "2-step verification is disabled"));
+ }
+ if (state.private_state.secret) {
+ send_closure(actor_id, &PasswordManager::cache_secret, state.private_state.secret.value().clone());
+ return promise.set_value(std::move(state.private_state.secret.value()));
+ }
+ if (!allow_recursive) {
+ return promise.set_error(Status::Error(400, "Failed to get Telegram Passport secret"));
+ }
+
+ auto new_promise =
+ PromiseCreator::lambda([password, promise = std::move(promise), actor_id](Result<bool> r_ok) mutable {
+ if (r_ok.is_error()) {
+ return promise.set_error(r_ok.move_as_error());
+ }
+ send_closure(actor_id, &PasswordManager::do_get_secure_secret, false, std::move(password),
+ std::move(promise));
+ });
+
+ UpdateSettings update_settings;
+ update_settings.current_password = password;
+ update_settings.update_secure_secret = true;
+ send_closure(actor_id, &PasswordManager::do_update_password_settings, std::move(update_settings),
+ std::move(state), std::move(new_promise));
+ }));
+}
+
void PasswordManager::get_temp_password_state(Promise<TempState> promise) /*const*/ {
- promise.set_value(temp_password_state_.as_td_api());
+ promise.set_value(temp_password_state_.get_temporary_password_state_object());
}
TempPasswordState PasswordManager::get_temp_password_state_sync() {
@@ -105,13 +318,10 @@ void PasswordManager::drop_temp_password() {
void PasswordManager::do_create_temp_password(string password, int32 timeout, PasswordState &&password_state,
Promise<TempPasswordState> promise) {
- send_with_promise(G()->net_query_creator().create(create_storer(telegram_api::account_getTmpPassword(
- calc_password_hash(password, password_state.current_salt), timeout))),
+ auto hash = get_input_check_password(password, password_state);
+ send_with_promise(G()->net_query_creator().create(telegram_api::account_getTmpPassword(std::move(hash), timeout)),
PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
- if (r_query.is_error()) {
- return promise.set_error(r_query.move_as_error());
- }
- auto r_result = fetch_result<telegram_api::account_getTmpPassword>(r_query.move_as_ok());
+ auto r_result = fetch_result<telegram_api::account_getTmpPassword>(std::move(r_query));
if (r_result.is_error()) {
return promise.set_error(r_result.move_as_error());
}
@@ -132,44 +342,227 @@ void PasswordManager::on_finish_create_temp_password(Result<TempPasswordState> r
}
temp_password_state_ = result.move_as_ok();
G()->td_db()->get_binlog_pmc()->set("temp_password", log_event_store(temp_password_state_).as_slice().str());
- create_temp_password_promise_.set_value(temp_password_state_.as_td_api());
+ create_temp_password_promise_.set_value(temp_password_state_.get_temporary_password_state_object());
}
-void PasswordManager::get_recovery_email_address(string password,
- Promise<tl_object_ptr<td_api::recoveryEmailAddress>> promise) {
+void PasswordManager::get_full_state(string password, Promise<PasswordFullState> promise) {
+ send_closure(G()->config_manager(), &ConfigManager::hide_suggested_action,
+ SuggestedAction{SuggestedAction::Type::CheckPassword});
+
do_get_state(PromiseCreator::lambda([password = std::move(password), promise = std::move(promise),
actor_id = actor_id(this)](Result<PasswordState> r_state) mutable {
if (r_state.is_error()) {
return promise.set_error(r_state.move_as_error());
}
- send_closure(actor_id, &PasswordManager::do_get_recovery_email_address, std::move(password), r_state.move_as_ok(),
+ send_closure(actor_id, &PasswordManager::do_get_full_state, std::move(password), r_state.move_as_ok(),
std::move(promise));
}));
}
-void PasswordManager::request_password_recovery(Promise<tl_object_ptr<td_api::passwordRecoveryInfo>> promise) {
- send_with_promise(G()->net_query_creator().create(create_storer(telegram_api::auth_requestPasswordRecovery())),
+Result<secure_storage::Secret> PasswordManager::decrypt_secure_secret(
+ Slice password, tl_object_ptr<telegram_api::SecurePasswordKdfAlgo> algo_ptr, Slice secret, int64 secret_id) {
+ TRY_RESULT(encrypted_secret, secure_storage::EncryptedSecret::create(secret));
+
+ CHECK(algo_ptr != nullptr);
+ BufferSlice salt;
+ secure_storage::EnryptionAlgorithm algorithm = secure_storage::EnryptionAlgorithm::Pbkdf2;
+ switch (algo_ptr->get_id()) {
+ case telegram_api::securePasswordKdfAlgoUnknown::ID:
+ return Status::Error(400, "Unsupported algorithm");
+ case telegram_api::securePasswordKdfAlgoSHA512::ID: {
+ auto algo = move_tl_object_as<telegram_api::securePasswordKdfAlgoSHA512>(algo_ptr);
+ salt = std::move(algo->salt_);
+ algorithm = secure_storage::EnryptionAlgorithm::Sha512;
+ break;
+ }
+ case telegram_api::securePasswordKdfAlgoPBKDF2HMACSHA512iter100000::ID: {
+ auto algo = move_tl_object_as<telegram_api::securePasswordKdfAlgoPBKDF2HMACSHA512iter100000>(algo_ptr);
+ salt = std::move(algo->salt_);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ TRY_RESULT(result, encrypted_secret.decrypt(password, salt.as_slice(), algorithm));
+ if (secret_id != result.get_hash()) {
+ return Status::Error("Secret hash mismatch");
+ }
+ return result;
+}
+
+void PasswordManager::do_get_full_state(string password, PasswordState state, Promise<PasswordFullState> promise) {
+ if (!state.has_password) {
+ PasswordFullState result;
+ result.state = std::move(state);
+ return promise.set_value(std::move(result));
+ }
+
+ auto hash = get_input_check_password(password, state);
+ send_with_promise(G()->net_query_creator().create(telegram_api::account_getPasswordSettings(std::move(hash))),
+ PromiseCreator::lambda([promise = std::move(promise), state = std::move(state),
+ password](Result<NetQueryPtr> r_query) mutable {
+ promise.set_result([&]() -> Result<PasswordFullState> {
+ TRY_RESULT(result, fetch_result<telegram_api::account_getPasswordSettings>(std::move(r_query)));
+ LOG(INFO) << "Receive password settings: " << to_string(result);
+ PasswordPrivateState private_state;
+ private_state.email = std::move(result->email_);
+
+ if (result->secure_settings_ != nullptr) {
+ auto r_secret =
+ decrypt_secure_secret(password, std::move(result->secure_settings_->secure_algo_),
+ result->secure_settings_->secure_secret_.as_slice(),
+ result->secure_settings_->secure_secret_id_);
+ if (r_secret.is_ok()) {
+ private_state.secret = r_secret.move_as_ok();
+ }
+ }
+
+ return PasswordFullState{std::move(state), std::move(private_state)};
+ }());
+ }));
+}
+
+void PasswordManager::get_recovery_email_address(string password,
+ Promise<tl_object_ptr<td_api::recoveryEmailAddress>> promise) {
+ get_full_state(
+ password,
+ PromiseCreator::lambda([password, promise = std::move(promise)](Result<PasswordFullState> r_state) mutable {
+ if (r_state.is_error()) {
+ return promise.set_error(r_state.move_as_error());
+ }
+ auto state = r_state.move_as_ok();
+ return promise.set_value(make_tl_object<td_api::recoveryEmailAddress>(state.private_state.email));
+ }));
+}
+
+void PasswordManager::check_recovery_email_address_code(string code, Promise<State> promise) {
+ auto query = G()->net_query_creator().create(telegram_api::account_confirmPasswordEmail(std::move(code)));
+ send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
+ Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::account_confirmPasswordEmail>(std::move(r_query));
+ if (r_result.is_error() && r_result.error().message() != "EMAIL_HASH_EXPIRED" &&
+ r_result.error().message() != "CODE_INVALID") {
+ return promise.set_error(r_result.move_as_error());
+ }
+ send_closure(actor_id, &PasswordManager::get_state, std::move(promise));
+ }));
+}
+
+void PasswordManager::resend_recovery_email_address_code(Promise<State> promise) {
+ auto query = G()->net_query_creator().create(telegram_api::account_resendPasswordEmail());
+ send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
+ Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::account_resendPasswordEmail>(std::move(r_query));
+ if (r_result.is_error() && r_result.error().message() != "EMAIL_HASH_EXPIRED") {
+ return promise.set_error(r_result.move_as_error());
+ }
+ send_closure(actor_id, &PasswordManager::get_state, std::move(promise));
+ }));
+}
+
+void PasswordManager::send_email_address_verification_code(string email, Promise<SentEmailCode> promise) {
+ last_verified_email_address_ = email;
+ auto query = G()->net_query_creator().create(telegram_api::account_sendVerifyEmailCode(
+ make_tl_object<telegram_api::emailVerifyPurposePassport>(), std::move(email)));
+ send_with_promise(std::move(query),
PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
- if (r_query.is_error()) {
- return promise.set_error(r_query.move_as_error());
+ auto r_result = fetch_result<telegram_api::account_sendVerifyEmailCode>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
}
- auto r_result = fetch_result<telegram_api::auth_requestPasswordRecovery>(r_query.move_as_ok());
+ return promise.set_value(SentEmailCode(r_result.move_as_ok()));
+ }));
+}
+
+void PasswordManager::resend_email_address_verification_code(Promise<SentEmailCode> promise) {
+ if (last_verified_email_address_.empty()) {
+ return promise.set_error(Status::Error(400, "No email address verification was sent"));
+ }
+ send_email_address_verification_code(last_verified_email_address_, std::move(promise));
+}
+
+void PasswordManager::check_email_address_verification_code(string code, Promise<Unit> promise) {
+ if (last_verified_email_address_.empty()) {
+ return promise.set_error(Status::Error(400, "No email address verification was sent"));
+ }
+ auto verification_code = make_tl_object<telegram_api::emailVerificationCode>(std::move(code));
+ auto query = G()->net_query_creator().create(telegram_api::account_verifyEmail(
+ make_tl_object<telegram_api::emailVerifyPurposePassport>(), std::move(verification_code)));
+ send_with_promise(std::move(query),
+ PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::account_verifyEmail>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+ return promise.set_value(Unit());
+ }));
+}
+
+void PasswordManager::request_password_recovery(Promise<SentEmailCode> promise) {
+ // is called only after authorization
+ send_with_promise(G()->net_query_creator().create(telegram_api::auth_requestPasswordRecovery()),
+ PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::auth_requestPasswordRecovery>(std::move(r_query));
if (r_result.is_error()) {
return promise.set_error(r_result.move_as_error());
}
auto result = r_result.move_as_ok();
- return promise.set_value(make_tl_object<td_api::passwordRecoveryInfo>(result->email_pattern_));
+ return promise.set_value(SentEmailCode(std::move(result->email_pattern_), 0));
}));
}
-void PasswordManager::recover_password(string code, Promise<State> promise) {
- send_with_promise(G()->net_query_creator().create(create_storer(telegram_api::auth_recoverPassword(std::move(code)))),
+void PasswordManager::check_password_recovery_code(string code, Promise<Unit> promise) {
+ // is called only after authorization
+ send_with_promise(G()->net_query_creator().create(telegram_api::auth_checkRecoveryPassword(code)),
+ PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::auth_checkRecoveryPassword>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+ if (!r_result.ok()) {
+ return promise.set_error(Status::Error(400, "Invalid recovery code"));
+ }
+ return promise.set_value(Unit());
+ }));
+}
+
+void PasswordManager::recover_password(string code, string new_password, string new_hint, Promise<State> promise) {
+ // is called only after authorization
+ if (new_password.empty()) {
+ return do_recover_password(std::move(code), nullptr, std::move(promise));
+ }
+
+ UpdateSettings update_settings;
+ update_settings.update_password = true;
+ update_settings.new_password = std::move(new_password);
+ update_settings.new_hint = std::move(new_hint);
+
+ do_get_state(PromiseCreator::lambda([actor_id = actor_id(this), code = std::move(code),
+ update_settings = std::move(update_settings),
+ promise = std::move(promise)](Result<PasswordState> r_state) mutable {
+ if (r_state.is_error()) {
+ return promise.set_error(r_state.move_as_error());
+ }
+
+ TRY_RESULT_PROMISE(
+ promise, new_settings,
+ get_password_input_settings(update_settings, r_state.ok().has_password, r_state.ok().new_state, nullptr));
+
+ send_closure(actor_id, &PasswordManager::do_recover_password, std::move(code), std::move(new_settings),
+ std::move(promise));
+ }));
+}
+
+void PasswordManager::do_recover_password(string code, PasswordInputSettings &&new_settings, Promise<State> &&promise) {
+ int32 flags = 0;
+ if (new_settings != nullptr) {
+ flags |= telegram_api::auth_recoverPassword::NEW_SETTINGS_MASK;
+ }
+ send_with_promise(G()->net_query_creator().create(
+ telegram_api::auth_recoverPassword(flags, std::move(code), std::move(new_settings))),
PromiseCreator::lambda(
[actor_id = actor_id(this), promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
- if (r_query.is_error()) {
- return promise.set_error(r_query.move_as_error());
- }
- auto r_result = fetch_result<telegram_api::auth_recoverPassword>(r_query.move_as_ok());
+ auto r_result = fetch_result<telegram_api::auth_recoverPassword>(std::move(r_query));
if (r_result.is_error()) {
return promise.set_error(r_result.move_as_error());
}
@@ -177,20 +570,41 @@ void PasswordManager::recover_password(string code, Promise<State> promise) {
}));
}
-void PasswordManager::do_get_recovery_email_address(string password, PasswordState state,
- Promise<tl_object_ptr<td_api::recoveryEmailAddress>> promise) {
- send_with_promise(G()->net_query_creator().create(create_storer(
- telegram_api::account_getPasswordSettings(calc_password_hash(password, state.current_salt)))),
+void PasswordManager::reset_password(Promise<ResetPasswordResult> promise) {
+ send_with_promise(
+ G()->net_query_creator().create(telegram_api::account_resetPassword()),
+ PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ auto r_result = fetch_result<telegram_api::account_resetPassword>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+ auto result_ptr = r_result.move_as_ok();
+ switch (result_ptr->get_id()) {
+ case telegram_api::account_resetPasswordOk::ID:
+ return promise.set_value(td_api::make_object<td_api::resetPasswordResultOk>());
+ case telegram_api::account_resetPasswordRequestedWait::ID: {
+ auto result = move_tl_object_as<telegram_api::account_resetPasswordRequestedWait>(result_ptr);
+ return promise.set_value(td_api::make_object<td_api::resetPasswordResultPending>(result->until_date_));
+ }
+ case telegram_api::account_resetPasswordFailedWait::ID: {
+ auto result = move_tl_object_as<telegram_api::account_resetPasswordFailedWait>(result_ptr);
+ return promise.set_value(td_api::make_object<td_api::resetPasswordResultDeclined>(result->retry_date_));
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }));
+}
+
+void PasswordManager::cancel_password_reset(Promise<Unit> promise) {
+ send_with_promise(G()->net_query_creator().create(telegram_api::account_declinePasswordReset()),
PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
- if (r_query.is_error()) {
- return promise.set_error(r_query.move_as_error());
- }
- auto r_result = fetch_result<telegram_api::account_getPasswordSettings>(r_query.move_as_ok());
- if (r_result.is_error()) {
+ auto r_result = fetch_result<telegram_api::account_declinePasswordReset>(std::move(r_query));
+ if (r_result.is_error() && r_result.error().message() != "RESET_REQUEST_MISSING") {
return promise.set_error(r_result.move_as_error());
}
- auto result = r_result.move_as_ok();
- return promise.set_value(make_tl_object<td_api::recoveryEmailAddress>(result->email_));
+ return promise.set_value(Unit());
}));
}
@@ -198,65 +612,142 @@ void PasswordManager::update_password_settings(UpdateSettings update_settings, P
auto result_promise = PromiseCreator::lambda(
[actor_id = actor_id(this), promise = std::move(promise)](Result<bool> r_update_settings) mutable {
if (r_update_settings.is_error()) {
- promise.set_error(r_update_settings.move_as_error());
- return;
+ return promise.set_error(r_update_settings.move_as_error());
}
if (!r_update_settings.ok()) {
- promise.set_error(Status::Error(5, "account_updatePasswordSettings returned false"));
- return;
+ return promise.set_error(Status::Error(400, "account_updatePasswordSettings returned false"));
}
send_closure(actor_id, &PasswordManager::get_state, std::move(promise));
});
- do_get_state(
- PromiseCreator::lambda([=, actor_id = actor_id(this), result_promise = std::move(result_promise),
- update_settings = std::move(update_settings)](Result<PasswordState> r_state) mutable {
+ auto password = update_settings.current_password;
+ get_full_state(
+ std::move(password),
+ PromiseCreator::lambda([actor_id = actor_id(this), result_promise = std::move(result_promise),
+ update_settings = std::move(update_settings)](Result<PasswordFullState> r_state) mutable {
if (r_state.is_error()) {
- result_promise.set_error(r_state.move_as_error());
- return;
+ return result_promise.set_error(r_state.move_as_error());
}
send_closure(actor_id, &PasswordManager::do_update_password_settings, std::move(update_settings),
r_state.move_as_ok(), std::move(result_promise));
}));
}
-void PasswordManager::do_update_password_settings(UpdateSettings update_settings, PasswordState state,
+static BufferSlice create_salt(Slice salt_prefix) {
+ static constexpr size_t ADDED_SALT_SIZE = 32;
+ BufferSlice new_salt(salt_prefix.size() + ADDED_SALT_SIZE);
+ new_salt.as_slice().copy_from(salt_prefix);
+ Random::secure_bytes(new_salt.as_slice().substr(salt_prefix.size()));
+ return new_salt;
+}
+
+void PasswordManager::do_update_password_settings(UpdateSettings update_settings, PasswordFullState full_state,
Promise<bool> promise) {
- auto new_settings = make_tl_object<telegram_api::account_passwordInputSettings>();
+ // PasswordState has already been used to get PasswordPrivateState and need to be reget
+ do_get_state(PromiseCreator::lambda([actor_id = actor_id(this), update_settings = std::move(update_settings),
+ private_state = std::move(full_state.private_state),
+ promise = std::move(promise)](Result<PasswordState> r_state) mutable {
+ if (r_state.is_error()) {
+ return promise.set_error(r_state.move_as_error());
+ }
+ send_closure(actor_id, &PasswordManager::do_update_password_settings_impl, std::move(update_settings),
+ r_state.move_as_ok(), std::move(private_state), std::move(promise));
+ }));
+}
+
+Result<PasswordManager::PasswordInputSettings> PasswordManager::get_password_input_settings(
+ string new_password, string new_hint, const NewPasswordState &state) {
+ UpdateSettings update_settings;
+ update_settings.update_password = true;
+ update_settings.new_password = std::move(new_password);
+ update_settings.new_hint = std::move(new_hint);
+
+ return get_password_input_settings(update_settings, true, state, nullptr);
+}
+
+Result<PasswordManager::PasswordInputSettings> PasswordManager::get_password_input_settings(
+ const UpdateSettings &update_settings, bool has_password, const NewPasswordState &state,
+ const PasswordPrivateState *private_state) {
+ bool have_secret = private_state != nullptr && private_state->secret;
+ auto update_secure_secret = update_settings.update_secure_secret;
+ int32 flags = 0;
+ BufferSlice new_password_hash;
+ tl_object_ptr<telegram_api::PasswordKdfAlgo> new_algo;
+ string new_hint;
if (update_settings.update_password) {
- new_settings->flags_ |= telegram_api::account_passwordInputSettings::NEW_PASSWORD_HASH_MASK;
- new_settings->flags_ |= telegram_api::account_passwordInputSettings::NEW_SALT_MASK;
- new_settings->flags_ |= telegram_api::account_passwordInputSettings::HINT_MASK;
+ flags |= telegram_api::account_passwordInputSettings::NEW_PASSWORD_HASH_MASK;
+ flags |= telegram_api::account_passwordInputSettings::NEW_ALGO_MASK;
+ flags |= telegram_api::account_passwordInputSettings::HINT_MASK;
if (!update_settings.new_password.empty()) {
- BufferSlice new_salt(state.new_salt.size() * 2);
- new_salt.as_slice().copy_from(state.new_salt);
- Random::secure_bytes(new_salt.as_slice().remove_prefix(state.new_salt.size()));
-
- new_settings->new_salt_ = std::move(new_salt);
- new_settings->new_password_hash_ =
- calc_password_hash(update_settings.new_password, new_settings->new_salt_.as_slice().str());
- new_settings->hint_ = std::move(update_settings.new_hint);
+ auto new_client_salt = create_salt(state.client_salt);
+
+ auto new_hash = calc_password_srp_hash(update_settings.new_password, new_client_salt.as_slice(),
+ state.server_salt, state.srp_g, state.srp_p);
+ if (new_hash.is_error()) {
+ return Status::Error(400, "Unable to change password, because it may be unsafe");
+ }
+ new_password_hash = new_hash.move_as_ok();
+ new_algo = make_tl_object<telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow>(
+ std::move(new_client_salt), BufferSlice(state.server_salt), state.srp_g, BufferSlice(state.srp_p));
+ new_hint = update_settings.new_hint;
+ if (have_secret) {
+ update_secure_secret = true;
+ }
+ } else {
+ new_algo = make_tl_object<telegram_api::passwordKdfAlgoUnknown>();
}
}
- if (update_settings.update_recovery_email_address) {
- new_settings->flags_ |= telegram_api::account_passwordInputSettings::EMAIL_MASK;
- new_settings->email_ = std::move(update_settings.recovery_email_address);
+
+ // have no password and not setting one
+ if (!update_settings.update_password && !has_password) {
+ update_secure_secret = false;
}
- BufferSlice current_hash;
- if (state.has_password) {
- current_hash = calc_password_hash(update_settings.current_password, state.current_salt);
+
+ // setting an empty password
+ if (update_settings.update_password && update_settings.new_password.empty()) {
+ update_secure_secret = false;
+ }
+
+ tl_object_ptr<telegram_api::secureSecretSettings> new_secure_settings;
+ if (update_secure_secret) {
+ auto secret = have_secret ? private_state->secret.value() : secure_storage::Secret::create_new();
+ auto algorithm =
+ make_tl_object<telegram_api::securePasswordKdfAlgoPBKDF2HMACSHA512iter100000>(create_salt(state.secure_salt));
+ auto encrypted_secret = secret.encrypt(
+ update_settings.update_password ? update_settings.new_password : update_settings.current_password,
+ algorithm->salt_.as_slice(), secure_storage::EnryptionAlgorithm::Pbkdf2);
+
+ flags |= telegram_api::account_passwordInputSettings::NEW_SECURE_SETTINGS_MASK;
+ new_secure_settings = make_tl_object<telegram_api::secureSecretSettings>(
+ std::move(algorithm), BufferSlice(encrypted_secret.as_slice()), secret.get_hash());
}
+ if (update_settings.update_recovery_email_address) {
+ flags |= telegram_api::account_passwordInputSettings::EMAIL_MASK;
+ }
+ return make_tl_object<telegram_api::account_passwordInputSettings>(
+ flags, std::move(new_algo), std::move(new_password_hash), new_hint, update_settings.recovery_email_address,
+ std::move(new_secure_settings));
+}
+
+void PasswordManager::do_update_password_settings_impl(UpdateSettings update_settings, PasswordState state,
+ PasswordPrivateState private_state, Promise<bool> promise) {
+ TRY_RESULT_PROMISE(promise, new_settings,
+ get_password_input_settings(update_settings, state.has_password, state.new_state, &private_state));
+ auto current_hash = get_input_check_password(state.has_password ? update_settings.current_password : Slice(), state);
auto query = G()->net_query_creator().create(
- create_storer(telegram_api::account_updatePasswordSettings(std::move(current_hash), std::move(new_settings))));
+ telegram_api::account_updatePasswordSettings(std::move(current_hash), std::move(new_settings)));
send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
Result<NetQueryPtr> r_query) mutable {
- if (r_query.is_error()) {
- return promise.set_error(r_query.move_as_error());
- }
- auto r_result = fetch_result<telegram_api::account_updatePasswordSettings>(r_query.move_as_ok());
+ auto r_result = fetch_result<telegram_api::account_updatePasswordSettings>(std::move(r_query));
if (r_result.is_error()) {
- if (r_result.error().code() == 400 && r_result.error().message() == "EMAIL_UNCONFIRMED") {
+ Slice prefix("EMAIL_UNCONFIRMED");
+ Slice message = r_result.error().message();
+ if (r_result.error().code() == 400 && begins_with(message, prefix)) {
+ if (message.size() >= prefix.size() + 2 && message[prefix.size()] == '_') {
+ send_closure(actor_id, &PasswordManager::on_get_code_length,
+ to_integer<int32>(message.substr(prefix.size() + 1)));
+ }
return promise.set_value(true);
}
return promise.set_error(r_result.move_as_error());
@@ -265,59 +756,118 @@ void PasswordManager::do_update_password_settings(UpdateSettings update_settings
}));
}
+void PasswordManager::on_get_code_length(int32 code_length) {
+ if (code_length <= 0 || code_length > 100) {
+ LOG(ERROR) << "Receive invalid code length " << code_length;
+ return;
+ }
+
+ LOG(INFO) << "Set code length to " << code_length;
+ last_code_length_ = code_length;
+}
+
void PasswordManager::get_state(Promise<State> promise) {
do_get_state(PromiseCreator::lambda([promise = std::move(promise)](Result<PasswordState> r_state) mutable {
if (r_state.is_error()) {
- promise.set_error(r_state.move_as_error());
- return;
+ return promise.set_error(r_state.move_as_error());
}
- promise.set_value(r_state.move_as_ok().as_td_api());
+ promise.set_value(r_state.move_as_ok().get_password_state_object());
}));
}
void PasswordManager::do_get_state(Promise<PasswordState> promise) {
- auto query = G()->net_query_creator().create(create_storer(telegram_api::account_getPassword()));
- send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)](
- Result<NetQueryPtr> r_query) mutable {
- if (r_query.is_error()) {
- promise.set_error(r_query.move_as_error());
- return;
- }
- auto r_result = fetch_result<telegram_api::account_getPassword>(r_query.move_as_ok());
- if (r_result.is_error()) {
- promise.set_error(r_result.move_as_error());
- return;
- }
- auto result = r_result.move_as_ok();
+ auto query = G()->net_query_creator().create(telegram_api::account_getPassword());
+ send_with_promise(
+ std::move(query), PromiseCreator::lambda([actor_id = actor_id(this), code_length = last_code_length_,
+ promise = std::move(promise)](Result<NetQueryPtr> r_query) mutable {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto r_result = fetch_result<telegram_api::account_getPassword>(std::move(r_query));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+ auto password = r_result.move_as_ok();
+ LOG(INFO) << "Receive password info: " << to_string(password);
+ Random::add_seed(password->secure_random_.as_slice());
+
+ PasswordState state;
+ if (password->current_algo_ != nullptr) {
+ state.has_password = true;
+
+ switch (password->current_algo_->get_id()) {
+ case telegram_api::passwordKdfAlgoUnknown::ID:
+ return promise.set_error(Status::Error(400, "Please update client to continue"));
+ case telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow::ID: {
+ auto algo =
+ move_tl_object_as<telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow>(
+ password->current_algo_);
+ state.current_client_salt = algo->salt1_.as_slice().str();
+ state.current_server_salt = algo->salt2_.as_slice().str();
+ state.current_srp_g = algo->g_;
+ state.current_srp_p = algo->p_.as_slice().str();
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ state.current_srp_B = password->srp_B_.as_slice().str();
+ state.current_srp_id = password->srp_id_;
+ state.password_hint = std::move(password->hint_);
+ state.has_recovery_email_address = password->has_recovery_;
+ state.has_secure_values = password->has_secure_values_;
+
+ auto days = narrow_cast<int32>(G()->get_option_integer("otherwise_relogin_days"));
+ if (days > 0) {
+ dismiss_suggested_action(SuggestedAction{SuggestedAction::Type::SetPassword, DialogId(), days},
+ Promise<Unit>());
+ }
+ } else {
+ state.has_password = false;
+ send_closure(actor_id, &PasswordManager::drop_cached_secret);
+ }
+ state.unconfirmed_recovery_email_code = {std::move(password->email_unconfirmed_pattern_), code_length};
+ state.login_email_pattern = std::move(password->login_email_pattern_);
- PasswordState state;
- if (result->get_id() == telegram_api::account_noPassword::ID) {
- auto no_password = move_tl_object_as<telegram_api::account_noPassword>(result);
- state.has_password = false;
- state.password_hint = "";
- state.current_salt = "";
- state.new_salt = no_password->new_salt_.as_slice().str();
- state.has_recovery_email_address = false;
- state.unconfirmed_recovery_email_address_pattern = no_password->email_unconfirmed_pattern_;
- } else if (result->get_id() == telegram_api::account_password::ID) {
- auto password = move_tl_object_as<telegram_api::account_password>(result);
- state.has_password = true;
- state.password_hint = password->hint_;
- state.current_salt = password->current_salt_.as_slice().str();
- state.new_salt = password->new_salt_.as_slice().str();
- state.has_recovery_email_address = password->has_recovery_;
- state.unconfirmed_recovery_email_address_pattern = password->email_unconfirmed_pattern_;
- } else {
- UNREACHABLE();
- }
- promise.set_value(std::move(state));
- }));
+ if (password->flags_ & telegram_api::account_password::PENDING_RESET_DATE_MASK) {
+ state.pending_reset_date = td::max(password->pending_reset_date_, 0);
+ }
+
+ auto &new_state = state.new_state;
+ TRY_RESULT_PROMISE_ASSIGN(
+ promise, new_state,
+ get_new_password_state(std::move(password->new_algo_), std::move(password->new_secure_algo_)));
+
+ promise.set_value(std::move(state));
+ }));
+}
+
+void PasswordManager::cache_secret(secure_storage::Secret secret) {
+ LOG(INFO) << "Cache passport secret";
+ secret_ = std::move(secret);
+
+ const int32 max_cache_time = 3600;
+ secret_expire_time_ = Time::now() + max_cache_time;
+ set_timeout_at(secret_expire_time_);
+}
+
+void PasswordManager::drop_cached_secret() {
+ LOG(INFO) << "Drop passport secret";
+ secret_ = optional<secure_storage::Secret>();
+}
+
+void PasswordManager::timeout_expired() {
+ if (Time::now() >= secret_expire_time_) {
+ drop_cached_secret();
+ } else {
+ set_timeout_at(secret_expire_time_);
+ }
}
void PasswordManager::on_result(NetQueryPtr query) {
auto token = get_link_token();
container_.extract(token).set_value(std::move(query));
}
+
void PasswordManager::send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> promise) {
auto id = container_.create(std::move(promise));
G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, id));
@@ -327,4 +877,10 @@ void PasswordManager::start_up() {
temp_password_state_ = get_temp_password_state_sync();
}
+void PasswordManager::hangup() {
+ container_.for_each(
+ [](auto id, Promise<NetQueryPtr> &promise) { promise.set_error(Global::request_aborted_error()); });
+ stop();
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PasswordManager.h b/protocols/Telegram/tdlib/td/td/telegram/PasswordManager.h
index fb67d46f6e..3698dca0f2 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/PasswordManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/PasswordManager.h
@@ -1,20 +1,30 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/EmailVerification.h"
#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/NewPasswordState.h"
+#include "td/telegram/SecureStorage.h"
+#include "td/telegram/SentEmailCode.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/Container.h"
-#include "td/utils/logging.h"
+#include "td/utils/optional.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/tl_helpers.h"
-#include "td/telegram/td_api.h"
-
namespace td {
struct TempPasswordState {
@@ -22,18 +32,18 @@ struct TempPasswordState {
string temp_password;
int32 valid_until = 0; // unix_time
- tl_object_ptr<td_api::temporaryPasswordState> as_td_api() const;
+ tl_object_ptr<td_api::temporaryPasswordState> get_temporary_password_state_object() const;
- template <class T>
- void store(T &storer) const {
+ template <class StorerT>
+ void store(StorerT &storer) const {
using ::td::store;
CHECK(has_temp_password);
store(temp_password, storer);
store(valid_until, storer);
}
- template <class T>
- void parse(T &parser) {
+ template <class ParserT>
+ void parse(ParserT &parser) {
using ::td::parse;
has_temp_password = true;
parse(temp_password, parser);
@@ -41,25 +51,55 @@ struct TempPasswordState {
}
};
-class PasswordManager : public NetQueryCallback {
+class PasswordManager final : public NetQueryCallback {
public:
using State = tl_object_ptr<td_api::passwordState>;
using TempState = tl_object_ptr<td_api::temporaryPasswordState>;
+ using ResetPasswordResult = tl_object_ptr<td_api::ResetPasswordResult>;
+ using PasswordInputSettings = tl_object_ptr<telegram_api::account_passwordInputSettings>;
explicit PasswordManager(ActorShared<> parent) : parent_(std::move(parent)) {
}
+
+ static tl_object_ptr<telegram_api::InputCheckPasswordSRP> get_input_check_password(Slice password, Slice client_salt,
+ Slice server_salt, int32 g,
+ Slice p, Slice B, int64 id);
+
+ static Result<PasswordInputSettings> get_password_input_settings(string new_password, string new_hint,
+ const NewPasswordState &state);
+
void get_state(Promise<State> promise);
void set_password(string current_password, string new_password, string new_hint, bool set_recovery_email_address,
string recovery_email_address, Promise<State> promise);
+
+ void set_login_email_address(string new_login_email_address, Promise<SentEmailCode> promise);
+ void resend_login_email_address_code(Promise<SentEmailCode> promise);
+ void check_login_email_address_code(EmailVerification &&code, Promise<Unit> promise);
+
void set_recovery_email_address(string password, string new_recovery_email_address, Promise<State> promise);
void get_recovery_email_address(string password, Promise<tl_object_ptr<td_api::recoveryEmailAddress>> promise);
+ void check_recovery_email_address_code(string code, Promise<State> promise);
+ void resend_recovery_email_address_code(Promise<State> promise);
+
+ void send_email_address_verification_code(string email, Promise<SentEmailCode> promise);
+ void resend_email_address_verification_code(Promise<SentEmailCode> promise);
+ void check_email_address_verification_code(string code, Promise<Unit> promise);
- void request_password_recovery(Promise<tl_object_ptr<td_api::passwordRecoveryInfo>> promise);
- void recover_password(string code, Promise<State> promise);
+ void request_password_recovery(Promise<SentEmailCode> promise);
+ void check_password_recovery_code(string code, Promise<Unit> promise);
+ void recover_password(string code, string new_password, string new_hint, Promise<State> promise);
+
+ void reset_password(Promise<ResetPasswordResult> promise);
+ void cancel_password_reset(Promise<Unit> promise);
+
+ void get_secure_secret(string password, Promise<secure_storage::Secret> promise);
+ void get_input_check_password_srp(string password,
+ Promise<tl_object_ptr<telegram_api::InputCheckPasswordSRP>> &&promise);
void get_temp_password_state(Promise<TempState> promise) /*const*/;
void create_temp_password(string password, int32 timeout, Promise<TempState> promise);
void drop_temp_password();
+ void drop_cached_secret();
static TempPasswordState get_temp_password_state_sync();
@@ -70,15 +110,36 @@ class PasswordManager : public NetQueryCallback {
bool has_password = false;
string password_hint;
bool has_recovery_email_address = false;
- string unconfirmed_recovery_email_address_pattern = "";
+ bool has_secure_values = false;
+ SentEmailCode unconfirmed_recovery_email_code;
+ string login_email_pattern;
+ int32 pending_reset_date = 0;
+
+ string current_client_salt;
+ string current_server_salt;
+ int32 current_srp_g = 0;
+ string current_srp_p;
+ string current_srp_B;
+ int64 current_srp_id = 0;
+
+ NewPasswordState new_state;
+
+ State get_password_state_object() const {
+ return td_api::make_object<td_api::passwordState>(
+ has_password, password_hint, has_recovery_email_address, has_secure_values,
+ unconfirmed_recovery_email_code.get_email_address_authentication_code_info_object(), login_email_pattern,
+ pending_reset_date);
+ }
+ };
- string current_salt;
- string new_salt;
+ struct PasswordPrivateState {
+ string email;
+ optional<secure_storage::Secret> secret;
+ };
- State as_td_api() const {
- return td_api::make_object<td_api::passwordState>(has_password, password_hint, has_recovery_email_address,
- unconfirmed_recovery_email_address_pattern);
- }
+ struct PasswordFullState {
+ PasswordState state;
+ PasswordPrivateState private_state;
};
struct UpdateSettings {
@@ -88,26 +149,60 @@ class PasswordManager : public NetQueryCallback {
string new_password;
string new_hint;
+ bool update_secure_secret = false;
+
bool update_recovery_email_address = false;
string recovery_email_address;
};
+ optional<secure_storage::Secret> secret_;
+ double secret_expire_time_ = 0;
+
TempPasswordState temp_password_state_;
Promise<TempState> create_temp_password_promise_;
+ string last_set_login_email_address_;
+ string last_verified_email_address_;
+
+ int32 last_code_length_ = 0;
+
+ static Result<secure_storage::Secret> decrypt_secure_secret(
+ Slice password, tl_object_ptr<telegram_api::SecurePasswordKdfAlgo> algo_ptr, Slice secret, int64 secret_id);
+
+ static BufferSlice calc_password_hash(Slice password, Slice client_salt, Slice server_salt);
+
+ static Result<BufferSlice> calc_password_srp_hash(Slice password, Slice client_salt, Slice server_salt, int32 g,
+ Slice p);
+
+ static tl_object_ptr<telegram_api::InputCheckPasswordSRP> get_input_check_password(Slice password,
+ const PasswordState &state);
+
+ static Result<PasswordInputSettings> get_password_input_settings(const UpdateSettings &update_settings,
+ bool has_password, const NewPasswordState &state,
+ const PasswordPrivateState *private_state);
+
+ void do_recover_password(string code, PasswordInputSettings &&new_settings, Promise<State> &&promise);
+
void update_password_settings(UpdateSettings update_settings, Promise<State> promise);
- void do_update_password_settings(UpdateSettings update_settings, PasswordState state, Promise<bool> promise);
+ void do_update_password_settings(UpdateSettings update_settings, PasswordFullState full_state, Promise<bool> promise);
+ void do_update_password_settings_impl(UpdateSettings update_settings, PasswordState state,
+ PasswordPrivateState private_state, Promise<bool> promise);
+ void on_get_code_length(int32 code_length);
void do_get_state(Promise<PasswordState> promise);
- void do_get_recovery_email_address(string password, PasswordState state,
- Promise<tl_object_ptr<td_api::recoveryEmailAddress>> promise);
+ void get_full_state(string password, Promise<PasswordFullState> promise);
+ void do_get_secure_secret(bool allow_recursive, string password, Promise<secure_storage::Secret> promise);
+ void do_get_full_state(string password, PasswordState state, Promise<PasswordFullState> promise);
+ void cache_secret(secure_storage::Secret secret);
void do_create_temp_password(string password, int32 timeout, PasswordState &&password_state,
Promise<TempPasswordState> promise);
void on_finish_create_temp_password(Result<TempPasswordState> result, bool dummy);
- void on_result(NetQueryPtr query) override;
+ void on_result(NetQueryPtr query) final;
- void start_up() override;
+ void start_up() final;
+ void timeout_expired() final;
+ void hangup() final;
Container<Promise<NetQueryPtr>> container_;
void send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> promise);
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Payments.cpp b/protocols/Telegram/tdlib/td/td/telegram/Payments.cpp
index 2dc2d61913..69e9fce243 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Payments.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Payments.cpp
@@ -1,33 +1,82 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Payments.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
+#include "td/telegram/AccessRights.h"
#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
#include "td/telegram/Global.h"
+#include "td/telegram/InputInvoice.h"
+#include "td/telegram/MessageEntity.h"
#include "td/telegram/MessageId.h"
+#include "td/telegram/MessagesManager.h"
#include "td/telegram/misc.h"
#include "td/telegram/PasswordManager.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/ServerMessageId.h"
#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/ThemeManager.h"
#include "td/telegram/UpdatesManager.h"
+#include "td/telegram/UserId.h"
+#include "td/utils/algorithm.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
-#include "td/utils/format.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
-#include "td/utils/misc.h"
#include "td/utils/Status.h"
namespace td {
-class SetBotShippingAnswerQuery : public Td::ResultHandler {
+namespace {
+
+struct InputInvoiceInfo {
+ DialogId dialog_id_;
+ telegram_api::object_ptr<telegram_api::InputInvoice> input_invoice_;
+};
+
+Result<InputInvoiceInfo> get_input_invoice_info(Td *td, td_api::object_ptr<td_api::InputInvoice> &&input_invoice) {
+ if (input_invoice == nullptr) {
+ return Status::Error(400, "Input invoice must be non-empty");
+ }
+
+ InputInvoiceInfo result;
+ switch (input_invoice->get_id()) {
+ case td_api::inputInvoiceMessage::ID: {
+ auto invoice = td_api::move_object_as<td_api::inputInvoiceMessage>(input_invoice);
+ DialogId dialog_id(invoice->chat_id_);
+ MessageId message_id(invoice->message_id_);
+ TRY_RESULT(server_message_id, td->messages_manager_->get_invoice_message_id({dialog_id, message_id}));
+
+ auto input_peer = td->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return Status::Error(400, "Can't access the chat");
+ }
+
+ result.dialog_id_ = dialog_id;
+ result.input_invoice_ =
+ make_tl_object<telegram_api::inputInvoiceMessage>(std::move(input_peer), server_message_id.get());
+ break;
+ }
+ case td_api::inputInvoiceName::ID: {
+ auto invoice = td_api::move_object_as<td_api::inputInvoiceName>(input_invoice);
+ result.input_invoice_ = make_tl_object<telegram_api::inputInvoiceSlug>(invoice->name_);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ return std::move(result);
+}
+
+} // namespace
+
+class SetBotShippingAnswerQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -43,14 +92,14 @@ class SetBotShippingAnswerQuery : public Td::ResultHandler {
if (!shipping_options.empty()) {
flags |= telegram_api::messages_setBotShippingResults::SHIPPING_OPTIONS_MASK;
}
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_setBotShippingResults(
- flags, shipping_query_id, error_message, std::move(shipping_options)))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_setBotShippingResults(
+ flags, shipping_query_id, error_message, std::move(shipping_options))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setBotShippingResults>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
@@ -60,12 +109,12 @@ class SetBotShippingAnswerQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class SetBotPreCheckoutAnswerQuery : public Td::ResultHandler {
+class SetBotPreCheckoutAnswerQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -80,14 +129,14 @@ class SetBotPreCheckoutAnswerQuery : public Td::ResultHandler {
flags |= telegram_api::messages_setBotPrecheckoutResults::SUCCESS_MASK;
}
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_setBotPrecheckoutResults(
- flags, false /*ignored*/, pre_checkout_query_id, error_message))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_setBotPrecheckoutResults(
+ flags, false /*ignored*/, pre_checkout_query_id, error_message)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_setBotPrecheckoutResults>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
@@ -97,21 +146,25 @@ class SetBotPreCheckoutAnswerQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
+static tl_object_ptr<td_api::labeledPricePart> convert_labeled_price(
+ tl_object_ptr<telegram_api::labeledPrice> labeled_price) {
+ CHECK(labeled_price != nullptr);
+ if (!check_currency_amount(labeled_price->amount_)) {
+ LOG(ERROR) << "Receive invalid labeled price amount " << labeled_price->amount_;
+ labeled_price->amount_ = (labeled_price->amount_ < 0 ? -1 : 1) * (static_cast<int64>(1) << 40);
+ }
+ return make_tl_object<td_api::labeledPricePart>(std::move(labeled_price->label_), labeled_price->amount_);
+}
+
static tl_object_ptr<td_api::invoice> convert_invoice(tl_object_ptr<telegram_api::invoice> invoice) {
CHECK(invoice != nullptr);
- vector<tl_object_ptr<td_api::labeledPricePart>> labeled_prices;
- labeled_prices.reserve(invoice->prices_.size());
- for (auto &labeled_price : invoice->prices_) {
- labeled_prices.push_back(
- make_tl_object<td_api::labeledPricePart>(std::move(labeled_price->label_), labeled_price->amount_));
- }
-
+ auto labeled_prices = transform(std::move(invoice->prices_), convert_labeled_price);
bool is_test = (invoice->flags_ & telegram_api::invoice::TEST_MASK) != 0;
bool need_name = (invoice->flags_ & telegram_api::invoice::NAME_REQUESTED_MASK) != 0;
bool need_phone_number = (invoice->flags_ & telegram_api::invoice::PHONE_REQUESTED_MASK) != 0;
@@ -130,22 +183,60 @@ static tl_object_ptr<td_api::invoice> convert_invoice(tl_object_ptr<telegram_api
need_shipping_address = true;
}
- return make_tl_object<td_api::invoice>(std::move(invoice->currency_), std::move(labeled_prices), is_test, need_name,
+ if (invoice->max_tip_amount_ < 0 || !check_currency_amount(invoice->max_tip_amount_)) {
+ LOG(ERROR) << "Receive invalid maximum tip amount " << invoice->max_tip_amount_;
+ invoice->max_tip_amount_ = 0;
+ }
+ td::remove_if(invoice->suggested_tip_amounts_,
+ [](int64 amount) { return amount < 0 || !check_currency_amount(amount); });
+ if (invoice->suggested_tip_amounts_.size() > 4) {
+ invoice->suggested_tip_amounts_.resize(4);
+ }
+
+ return make_tl_object<td_api::invoice>(std::move(invoice->currency_), std::move(labeled_prices),
+ invoice->max_tip_amount_, std::move(invoice->suggested_tip_amounts_),
+ std::move(invoice->recurring_terms_url_), is_test, need_name,
need_phone_number, need_email_address, need_shipping_address,
send_phone_number_to_provider, send_email_address_to_provider, is_flexible);
}
-static tl_object_ptr<td_api::paymentsProviderStripe> convert_payment_provider(
+static tl_object_ptr<td_api::PaymentProvider> convert_payment_provider(
const string &native_provider_name, tl_object_ptr<telegram_api::dataJSON> native_parameters) {
if (native_parameters == nullptr) {
return nullptr;
}
+ if (native_provider_name == "smartglocal") {
+ string data = native_parameters->data_;
+ auto r_value = json_decode(data);
+ if (r_value.is_error()) {
+ LOG(ERROR) << "Can't parse JSON object \"" << native_parameters->data_ << "\": " << r_value.error();
+ return nullptr;
+ }
+
+ auto value = r_value.move_as_ok();
+ if (value.type() != JsonValue::Type::Object) {
+ LOG(ERROR) << "Wrong JSON data \"" << native_parameters->data_ << '"';
+ return nullptr;
+ }
+
+ auto r_public_token = get_json_object_string_field(value.get_object(), "public_token", false);
+
+ if (r_public_token.is_error()) {
+ LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"';
+ return nullptr;
+ }
+ if (value.get_object().size() != 1) {
+ LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"';
+ }
+
+ return make_tl_object<td_api::paymentProviderSmartGlocal>(r_public_token.move_as_ok());
+ }
if (native_provider_name == "stripe") {
string data = native_parameters->data_;
auto r_value = json_decode(data);
if (r_value.is_error()) {
- LOG(ERROR) << "Can't parse json object \"" << native_parameters->data_ << "\": " << r_value.error();
+ LOG(ERROR) << "Can't parse JSON object \"" << native_parameters->data_ << "\": " << r_value.error();
return nullptr;
}
@@ -159,39 +250,41 @@ static tl_object_ptr<td_api::paymentsProviderStripe> convert_payment_provider(
auto r_need_postal_code = get_json_object_bool_field(value.get_object(), "need_zip", false);
auto r_need_cardholder_name = get_json_object_bool_field(value.get_object(), "need_cardholder_name", false);
auto r_publishable_key = get_json_object_string_field(value.get_object(), "publishable_key", false);
+ // TODO support "gpay_parameters":{"gateway":"stripe","stripe:publishableKey":"...","stripe:version":"..."}
- if (value.get_object().size() != 4 || r_need_country.is_error() || r_need_postal_code.is_error() ||
- r_need_cardholder_name.is_error() || r_publishable_key.is_error()) {
- LOG(WARNING) << "Unsupported JSON data \"" << native_parameters->data_ << '"';
+ if (r_need_country.is_error() || r_need_postal_code.is_error() || r_need_cardholder_name.is_error() ||
+ r_publishable_key.is_error()) {
+ LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"';
return nullptr;
}
+ if (value.get_object().size() != 5) {
+ LOG(ERROR) << "Unsupported JSON data \"" << native_parameters->data_ << '"';
+ }
- return make_tl_object<td_api::paymentsProviderStripe>(r_publishable_key.move_as_ok(), r_need_country.move_as_ok(),
- r_need_postal_code.move_as_ok(),
- r_need_cardholder_name.move_as_ok());
+ return make_tl_object<td_api::paymentProviderStripe>(r_publishable_key.move_as_ok(), r_need_country.move_as_ok(),
+ r_need_postal_code.move_as_ok(),
+ r_need_cardholder_name.move_as_ok());
}
return nullptr;
}
-static tl_object_ptr<td_api::shippingAddress> convert_shipping_address(
- tl_object_ptr<telegram_api::postAddress> address) {
+static tl_object_ptr<td_api::address> convert_address(tl_object_ptr<telegram_api::postAddress> address) {
if (address == nullptr) {
return nullptr;
}
- return make_tl_object<td_api::shippingAddress>(std::move(address->country_iso2_), std::move(address->state_),
- std::move(address->city_), std::move(address->street_line1_),
- std::move(address->street_line2_), std::move(address->post_code_));
+ return make_tl_object<td_api::address>(std::move(address->country_iso2_), std::move(address->state_),
+ std::move(address->city_), std::move(address->street_line1_),
+ std::move(address->street_line2_), std::move(address->post_code_));
}
-static tl_object_ptr<telegram_api::postAddress> convert_shipping_address(
- tl_object_ptr<td_api::shippingAddress> address) {
+static tl_object_ptr<telegram_api::postAddress> convert_address(tl_object_ptr<td_api::address> address) {
if (address == nullptr) {
return nullptr;
}
- return make_tl_object<telegram_api::postAddress>(std::move(address->country_code_), std::move(address->state_),
- std::move(address->city_), std::move(address->street_line1_),
- std::move(address->street_line2_), std::move(address->postal_code_));
+ return make_tl_object<telegram_api::postAddress>(std::move(address->street_line1_), std::move(address->street_line2_),
+ std::move(address->city_), std::move(address->state_),
+ std::move(address->country_code_), std::move(address->postal_code_));
}
static tl_object_ptr<td_api::orderInfo> convert_order_info(
@@ -201,13 +294,7 @@ static tl_object_ptr<td_api::orderInfo> convert_order_info(
}
return make_tl_object<td_api::orderInfo>(std::move(order_info->name_), std::move(order_info->phone_),
std::move(order_info->email_),
- convert_shipping_address(std::move(order_info->shipping_address_)));
-}
-
-static tl_object_ptr<td_api::labeledPricePart> convert_labeled_price(
- tl_object_ptr<telegram_api::labeledPrice> labeled_price) {
- CHECK(labeled_price != nullptr);
- return make_tl_object<td_api::labeledPricePart>(std::move(labeled_price->label_), labeled_price->amount_);
+ convert_address(std::move(order_info->shipping_address_)));
}
static tl_object_ptr<td_api::shippingOption> convert_shipping_option(
@@ -240,66 +327,99 @@ static tl_object_ptr<telegram_api::paymentRequestedInfo> convert_order_info(
}
return make_tl_object<telegram_api::paymentRequestedInfo>(
flags, std::move(order_info->name_), std::move(order_info->phone_number_), std::move(order_info->email_address_),
- convert_shipping_address(std::move(order_info->shipping_address_)));
+ convert_address(std::move(order_info->shipping_address_)));
}
-static tl_object_ptr<td_api::savedCredentials> convert_saved_credentials(
- tl_object_ptr<telegram_api::paymentSavedCredentialsCard> saved_credentials) {
- if (saved_credentials == nullptr) {
- return nullptr;
- }
- return make_tl_object<td_api::savedCredentials>(std::move(saved_credentials->id_),
- std::move(saved_credentials->title_));
+static vector<tl_object_ptr<td_api::savedCredentials>> convert_saved_credentials(
+ vector<tl_object_ptr<telegram_api::paymentSavedCredentialsCard>> saved_credentials) {
+ return transform(
+ std::move(saved_credentials), [](tl_object_ptr<telegram_api::paymentSavedCredentialsCard> &&credentials) {
+ return make_tl_object<td_api::savedCredentials>(std::move(credentials->id_), std::move(credentials->title_));
+ });
}
-class GetPaymentFormQuery : public Td::ResultHandler {
+class GetPaymentFormQuery final : public Td::ResultHandler {
Promise<tl_object_ptr<td_api::paymentForm>> promise_;
+ DialogId dialog_id_;
public:
explicit GetPaymentFormQuery(Promise<tl_object_ptr<td_api::paymentForm>> &&promise) : promise_(std::move(promise)) {
}
- void send(ServerMessageId server_message_id) {
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::payments_getPaymentForm(server_message_id.get()))));
+ void send(InputInvoiceInfo &&input_invoice_info, tl_object_ptr<telegram_api::dataJSON> &&theme_parameters) {
+ dialog_id_ = input_invoice_info.dialog_id_;
+
+ int32 flags = 0;
+ if (theme_parameters != nullptr) {
+ flags |= telegram_api::payments_getPaymentForm::THEME_PARAMS_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::payments_getPaymentForm(
+ flags, std::move(input_invoice_info.input_invoice_), std::move(theme_parameters))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::payments_getPaymentForm>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto payment_form = result_ptr.move_as_ok();
- LOG(INFO) << "Receive payment form: " << to_string(payment_form);
+ LOG(INFO) << "Receive result for GetPaymentFormQuery: " << to_string(payment_form);
- td->contacts_manager_->on_get_users(std::move(payment_form->users_));
+ td_->contacts_manager_->on_get_users(std::move(payment_form->users_), "GetPaymentFormQuery");
- bool can_save_credentials =
- (payment_form->flags_ & telegram_api::payments_paymentForm::CAN_SAVE_CREDENTIALS_MASK) != 0;
- bool need_password = (payment_form->flags_ & telegram_api::payments_paymentForm::PASSWORD_MISSING_MASK) != 0;
+ UserId payments_provider_user_id(payment_form->provider_id_);
+ if (!payments_provider_user_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid payments provider " << payments_provider_user_id;
+ return on_error(Status::Error(500, "Receive invalid payments provider identifier"));
+ }
+ UserId seller_bot_user_id(payment_form->bot_id_);
+ if (!seller_bot_user_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid seller " << seller_bot_user_id;
+ return on_error(Status::Error(500, "Receive invalid seller identifier"));
+ }
+ bool can_save_credentials = payment_form->can_save_credentials_;
+ bool need_password = payment_form->password_missing_;
+ auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_form->photo_), dialog_id_);
+ auto payment_provider =
+ convert_payment_provider(payment_form->native_provider_, std::move(payment_form->native_params_));
+ if (payment_provider == nullptr) {
+ payment_provider = td_api::make_object<td_api::paymentProviderOther>(std::move(payment_form->url_));
+ }
+ auto additional_payment_options = transform(
+ payment_form->additional_methods_, [](const telegram_api::object_ptr<telegram_api::paymentFormMethod> &method) {
+ return td_api::make_object<td_api::paymentOption>(method->title_, method->url_);
+ });
promise_.set_value(make_tl_object<td_api::paymentForm>(
- convert_invoice(std::move(payment_form->invoice_)), std::move(payment_form->url_),
- convert_payment_provider(payment_form->native_provider_, std::move(payment_form->native_params_)),
+ payment_form->form_id_, convert_invoice(std::move(payment_form->invoice_)),
+ td_->contacts_manager_->get_user_id_object(seller_bot_user_id, "paymentForm seller"),
+ td_->contacts_manager_->get_user_id_object(payments_provider_user_id, "paymentForm provider"),
+ std::move(payment_provider), std::move(additional_payment_options),
convert_order_info(std::move(payment_form->saved_info_)),
- convert_saved_credentials(std::move(payment_form->saved_credentials_)), can_save_credentials, need_password));
+ convert_saved_credentials(std::move(payment_form->saved_credentials_)), can_save_credentials, need_password,
+ payment_form->title_, get_product_description_object(payment_form->description_),
+ get_photo_object(td_->file_manager_.get(), photo)));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPaymentFormQuery");
promise_.set_error(std::move(status));
}
};
-class ValidateRequestedInfoQuery : public Td::ResultHandler {
+class ValidateRequestedInfoQuery final : public Td::ResultHandler {
Promise<tl_object_ptr<td_api::validatedOrderInfo>> promise_;
+ DialogId dialog_id_;
public:
explicit ValidateRequestedInfoQuery(Promise<tl_object_ptr<td_api::validatedOrderInfo>> &&promise)
: promise_(std::move(promise)) {
}
- void send(ServerMessageId server_message_id, tl_object_ptr<telegram_api::paymentRequestedInfo> requested_info,
+ void send(InputInvoiceInfo &&input_invoice_info, tl_object_ptr<telegram_api::paymentRequestedInfo> requested_info,
bool allow_save) {
+ dialog_id_ = input_invoice_info.dialog_id_;
+
int32 flags = 0;
if (allow_save) {
flags |= telegram_api::payments_validateRequestedInfo::SAVE_MASK;
@@ -308,40 +428,46 @@ class ValidateRequestedInfoQuery : public Td::ResultHandler {
requested_info = make_tl_object<telegram_api::paymentRequestedInfo>();
requested_info->flags_ = 0;
}
- send_query(G()->net_query_creator().create(create_storer(telegram_api::payments_validateRequestedInfo(
- flags, false /*ignored*/, server_message_id.get(), std::move(requested_info)))));
+ send_query(G()->net_query_creator().create(telegram_api::payments_validateRequestedInfo(
+ flags, false /*ignored*/, std::move(input_invoice_info.input_invoice_), std::move(requested_info))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::payments_validateRequestedInfo>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto validated_order_info = result_ptr.move_as_ok();
- LOG(INFO) << "Receive validated order info: " << to_string(validated_order_info);
+ LOG(INFO) << "Receive result for ValidateRequestedInfoQuery: " << to_string(validated_order_info);
promise_.set_value(make_tl_object<td_api::validatedOrderInfo>(
std::move(validated_order_info->id_),
transform(std::move(validated_order_info->shipping_options_), convert_shipping_option)));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ValidateRequestedInfoQuery");
promise_.set_error(std::move(status));
}
};
-class SendPaymentFormQuery : public Td::ResultHandler {
+class SendPaymentFormQuery final : public Td::ResultHandler {
Promise<tl_object_ptr<td_api::paymentResult>> promise_;
+ DialogId dialog_id_;
public:
explicit SendPaymentFormQuery(Promise<tl_object_ptr<td_api::paymentResult>> &&promise)
: promise_(std::move(promise)) {
}
- void send(ServerMessageId server_message_id, const string &order_info_id, const string &shipping_option_id,
- tl_object_ptr<telegram_api::InputPaymentCredentials> input_credentials) {
+ void send(InputInvoiceInfo &&input_invoice_info, int64 payment_form_id, const string &order_info_id,
+ const string &shipping_option_id, tl_object_ptr<telegram_api::InputPaymentCredentials> input_credentials,
+ int64 tip_amount) {
CHECK(input_credentials != nullptr);
+
+ dialog_id_ = input_invoice_info.dialog_id_;
+
int32 flags = 0;
if (!order_info_id.empty()) {
flags |= telegram_api::payments_sendPaymentForm::REQUESTED_INFO_ID_MASK;
@@ -349,28 +475,34 @@ class SendPaymentFormQuery : public Td::ResultHandler {
if (!shipping_option_id.empty()) {
flags |= telegram_api::payments_sendPaymentForm::SHIPPING_OPTION_ID_MASK;
}
- send_query(G()->net_query_creator().create(create_storer(telegram_api::payments_sendPaymentForm(
- flags, server_message_id.get(), order_info_id, shipping_option_id, std::move(input_credentials)))));
+ if (tip_amount != 0) {
+ flags |= telegram_api::payments_sendPaymentForm::TIP_AMOUNT_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::payments_sendPaymentForm(
+ flags, payment_form_id, std::move(input_invoice_info.input_invoice_), order_info_id, shipping_option_id,
+ std::move(input_credentials), tip_amount)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::payments_sendPaymentForm>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto payment_result = result_ptr.move_as_ok();
- LOG(INFO) << "Receive payment result: " << to_string(payment_result);
+ LOG(INFO) << "Receive result for SendPaymentFormQuery: " << to_string(payment_result);
switch (payment_result->get_id()) {
case telegram_api::payments_paymentResult::ID: {
auto result = move_tl_object_as<telegram_api::payments_paymentResult>(payment_result);
- G()->td().get_actor_unsafe()->updates_manager_->on_get_updates(std::move(result->updates_));
- promise_.set_value(make_tl_object<td_api::paymentResult>(true, string()));
+ td_->updates_manager_->on_get_updates(
+ std::move(result->updates_), PromiseCreator::lambda([promise = std::move(promise_)](Unit) mutable {
+ promise.set_value(make_tl_object<td_api::paymentResult>(true, string()));
+ }));
return;
}
- case telegram_api::payments_paymentVerficationNeeded::ID: {
- auto result = move_tl_object_as<telegram_api::payments_paymentVerficationNeeded>(payment_result);
+ case telegram_api::payments_paymentVerificationNeeded::ID: {
+ auto result = move_tl_object_as<telegram_api::payments_paymentVerificationNeeded>(payment_result);
promise_.set_value(make_tl_object<td_api::paymentResult>(false, std::move(result->url_)));
return;
}
@@ -379,56 +511,76 @@ class SendPaymentFormQuery : public Td::ResultHandler {
}
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendPaymentFormQuery");
promise_.set_error(std::move(status));
}
};
-class GetPaymentReceiptQuery : public Td::ResultHandler {
+class GetPaymentReceiptQuery final : public Td::ResultHandler {
Promise<tl_object_ptr<td_api::paymentReceipt>> promise_;
+ DialogId dialog_id_;
public:
explicit GetPaymentReceiptQuery(Promise<tl_object_ptr<td_api::paymentReceipt>> &&promise)
: promise_(std::move(promise)) {
}
- void send(ServerMessageId server_message_id) {
+ void send(DialogId dialog_id, ServerMessageId server_message_id) {
+ dialog_id_ = dialog_id;
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::payments_getPaymentReceipt(server_message_id.get()))));
+ telegram_api::payments_getPaymentReceipt(std::move(input_peer), server_message_id.get())));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::payments_getPaymentReceipt>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto payment_receipt = result_ptr.move_as_ok();
- LOG(INFO) << "Receive payment receipt: " << to_string(payment_receipt);
+ LOG(INFO) << "Receive result for GetPaymentReceiptQuery: " << to_string(payment_receipt);
- td->contacts_manager_->on_get_users(std::move(payment_receipt->users_));
+ td_->contacts_manager_->on_get_users(std::move(payment_receipt->users_), "GetPaymentReceiptQuery");
UserId payments_provider_user_id(payment_receipt->provider_id_);
if (!payments_provider_user_id.is_valid()) {
LOG(ERROR) << "Receive invalid payments provider " << payments_provider_user_id;
- payments_provider_user_id = UserId();
+ return on_error(Status::Error(500, "Receive invalid payments provider identifier"));
+ }
+ UserId seller_bot_user_id(payment_receipt->bot_id_);
+ if (!seller_bot_user_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid seller " << seller_bot_user_id;
+ return on_error(Status::Error(500, "Receive invalid seller identifier"));
+ }
+ auto photo = get_web_document_photo(td_->file_manager_.get(), std::move(payment_receipt->photo_), dialog_id_);
+ if (payment_receipt->tip_amount_ < 0 || !check_currency_amount(payment_receipt->tip_amount_)) {
+ LOG(ERROR) << "Receive invalid tip amount " << payment_receipt->tip_amount_;
+ payment_receipt->tip_amount_ = 0;
}
promise_.set_value(make_tl_object<td_api::paymentReceipt>(
- payment_receipt->date_,
- G()->td().get_actor_unsafe()->contacts_manager_->get_user_id_object(payments_provider_user_id,
- "paymentReceipt"),
+ payment_receipt->title_, get_product_description_object(payment_receipt->description_),
+ get_photo_object(td_->file_manager_.get(), photo), payment_receipt->date_,
+ td_->contacts_manager_->get_user_id_object(seller_bot_user_id, "paymentReceipt seller"),
+ td_->contacts_manager_->get_user_id_object(payments_provider_user_id, "paymentReceipt provider"),
convert_invoice(std::move(payment_receipt->invoice_)), convert_order_info(std::move(payment_receipt->info_)),
- convert_shipping_option(std::move(payment_receipt->shipping_)),
- std::move(payment_receipt->credentials_title_)));
+ convert_shipping_option(std::move(payment_receipt->shipping_)), std::move(payment_receipt->credentials_title_),
+ payment_receipt->tip_amount_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPaymentReceiptQuery");
promise_.set_error(std::move(status));
}
};
-class GetSavedInfoQuery : public Td::ResultHandler {
+class GetSavedInfoQuery final : public Td::ResultHandler {
Promise<tl_object_ptr<td_api::orderInfo>> promise_;
public:
@@ -436,26 +588,26 @@ class GetSavedInfoQuery : public Td::ResultHandler {
}
void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::payments_getSavedInfo())));
+ send_query(G()->net_query_creator().create(telegram_api::payments_getSavedInfo()));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::payments_getSavedInfo>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto saved_info = result_ptr.move_as_ok();
- LOG(INFO) << "Receive saved info: " << to_string(saved_info);
+ LOG(INFO) << "Receive result for GetSavedInfoQuery: " << to_string(saved_info);
promise_.set_value(convert_order_info(std::move(saved_info->saved_info_)));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class ClearSavedInfoQuery : public Td::ResultHandler {
+class ClearSavedInfoQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -472,155 +624,90 @@ class ClearSavedInfoQuery : public Td::ResultHandler {
flags |= telegram_api::payments_clearSavedInfo::INFO_MASK;
}
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::payments_clearSavedInfo(flags, false /*ignored*/, false /*ignored*/))));
+ telegram_api::payments_clearSavedInfo(flags, false /*ignored*/, false /*ignored*/)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::payments_clearSavedInfo>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-bool operator==(const LabeledPricePart &lhs, const LabeledPricePart &rhs) {
- return lhs.label == rhs.label && lhs.amount == rhs.amount;
-}
+class ExportInvoiceQuery final : public Td::ResultHandler {
+ Promise<string> promise_;
-bool operator!=(const LabeledPricePart &lhs, const LabeledPricePart &rhs) {
- return !(lhs == rhs);
-}
-
-StringBuilder &operator<<(StringBuilder &string_builder, const LabeledPricePart &labeled_price_part) {
- return string_builder << "[" << labeled_price_part.label << ": " << labeled_price_part.amount << "]";
-}
-
-bool operator==(const Invoice &lhs, const Invoice &rhs) {
- return lhs.is_test == rhs.is_test && lhs.need_name == rhs.need_name &&
- lhs.need_phone_number == rhs.need_phone_number && lhs.need_email_address == rhs.need_email_address &&
- lhs.need_shipping_address == rhs.need_shipping_address &&
- lhs.send_phone_number_to_provider == rhs.send_phone_number_to_provider &&
- lhs.send_email_address_to_provider == rhs.send_email_address_to_provider &&
- lhs.is_flexible == rhs.is_flexible && lhs.currency == rhs.currency && lhs.price_parts == rhs.price_parts;
-}
-
-bool operator!=(const Invoice &lhs, const Invoice &rhs) {
- return !(lhs == rhs);
-}
-
-StringBuilder &operator<<(StringBuilder &string_builder, const Invoice &invoice) {
- return string_builder << "[" << (invoice.is_flexible ? "Flexible" : "") << (invoice.is_test ? "Test" : "")
- << "Invoice" << (invoice.need_name ? ", needs name" : "")
- << (invoice.need_phone_number ? ", needs phone number" : "")
- << (invoice.need_email_address ? ", needs email address" : "")
- << (invoice.need_shipping_address ? ", needs shipping address" : "")
- << (invoice.send_phone_number_to_provider ? ", sends phone number to provider" : "")
- << (invoice.send_email_address_to_provider ? ", sends email address to provider" : "") << " in "
- << invoice.currency << " with price parts " << format::as_array(invoice.price_parts) << "]";
-}
-
-bool operator==(const ShippingAddress &lhs, const ShippingAddress &rhs) {
- return lhs.country_code == rhs.country_code && lhs.state == rhs.state && lhs.city == rhs.city &&
- lhs.street_line1 == rhs.street_line1 && lhs.street_line2 == rhs.street_line2 &&
- lhs.postal_code == rhs.postal_code;
-}
-
-bool operator!=(const ShippingAddress &lhs, const ShippingAddress &rhs) {
- return !(lhs == rhs);
-}
-
-StringBuilder &operator<<(StringBuilder &string_builder, const ShippingAddress &shipping_address) {
- return string_builder << "[Address " << tag("country_code", shipping_address.country_code)
- << tag("state", shipping_address.state) << tag("city", shipping_address.city)
- << tag("street_line1", shipping_address.street_line1)
- << tag("street_line2", shipping_address.street_line2)
- << tag("postal_code", shipping_address.postal_code) << "]";
-}
-
-unique_ptr<ShippingAddress> get_shipping_address(tl_object_ptr<telegram_api::postAddress> address) {
- if (address == nullptr) {
- return nullptr;
+ public:
+ explicit ExportInvoiceQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
}
- return make_unique<ShippingAddress>(std::move(address->country_iso2_), std::move(address->state_),
- std::move(address->city_), std::move(address->street_line1_),
- std::move(address->street_line2_), std::move(address->post_code_));
-}
-tl_object_ptr<td_api::shippingAddress> get_shipping_address_object(
- const unique_ptr<ShippingAddress> &shipping_address) {
- if (shipping_address == nullptr) {
- return nullptr;
+ void send(tl_object_ptr<telegram_api::inputMediaInvoice> &&input_media_invoice) {
+ send_query(G()->net_query_creator().create(telegram_api::payments_exportInvoice(std::move(input_media_invoice))));
}
- return make_tl_object<td_api::shippingAddress>(shipping_address->country_code, shipping_address->state,
- shipping_address->city, shipping_address->street_line1,
- shipping_address->street_line2, shipping_address->postal_code);
-}
-bool operator==(const OrderInfo &lhs, const OrderInfo &rhs) {
- return lhs.name == rhs.name && lhs.phone_number == rhs.phone_number && lhs.email_address == rhs.email_address &&
- ((lhs.shipping_address == nullptr && rhs.shipping_address == nullptr) ||
- (lhs.shipping_address != nullptr && rhs.shipping_address != nullptr &&
- *lhs.shipping_address == *rhs.shipping_address));
-}
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::payments_exportInvoice>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
-bool operator!=(const OrderInfo &lhs, const OrderInfo &rhs) {
- return !(lhs == rhs);
-}
+ auto link = result_ptr.move_as_ok();
+ promise_.set_value(std::move(link->url_));
+ }
-StringBuilder &operator<<(StringBuilder &string_builder, const OrderInfo &order_info) {
- string_builder << "[OrderInfo " << tag("name", order_info.name) << tag("phone_number", order_info.phone_number)
- << tag("email_address", order_info.email_address);
- if (order_info.shipping_address != nullptr) {
- string_builder << *order_info.shipping_address;
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
}
- return string_builder << "]";
-}
+};
-unique_ptr<OrderInfo> get_order_info(tl_object_ptr<telegram_api::paymentRequestedInfo> order_info) {
- if (order_info == nullptr || order_info->flags_ == 0) {
- return nullptr;
+class GetBankCardInfoQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::bankCardInfo>> promise_;
+
+ public:
+ explicit GetBankCardInfoQuery(Promise<td_api::object_ptr<td_api::bankCardInfo>> &&promise)
+ : promise_(std::move(promise)) {
}
- return make_unique<OrderInfo>(std::move(order_info->name_), std::move(order_info->phone_),
- std::move(order_info->email_),
- get_shipping_address(std::move(order_info->shipping_address_)));
-}
-tl_object_ptr<td_api::orderInfo> get_order_info_object(const unique_ptr<OrderInfo> &order_info) {
- if (order_info == nullptr) {
- return nullptr;
+ void send(const string &bank_card_number) {
+ send_query(G()->net_query_creator().create(telegram_api::payments_getBankCardData(bank_card_number), {},
+ G()->get_webfile_dc_id()));
}
- return make_tl_object<td_api::orderInfo>(order_info->name, order_info->phone_number, order_info->email_address,
- get_shipping_address_object(order_info->shipping_address));
-}
-bool operator==(const ShippingOption &lhs, const ShippingOption &rhs) {
- return lhs.id == rhs.id && lhs.title == rhs.title && lhs.price_parts == rhs.price_parts;
-}
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::payments_getBankCardData>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
-bool operator!=(const ShippingOption &lhs, const ShippingOption &rhs) {
- return !(lhs == rhs);
-}
+ auto response = result_ptr.move_as_ok();
+ auto actions = transform(response->open_urls_, [](auto &open_url) {
+ return td_api::make_object<td_api::bankCardActionOpenUrl>(open_url->name_, open_url->url_);
+ });
+ promise_.set_value(td_api::make_object<td_api::bankCardInfo>(response->title_, std::move(actions)));
+ }
-StringBuilder &operator<<(StringBuilder &string_builder, const ShippingOption &shipping_option) {
- return string_builder << "[ShippingOption " << shipping_option.id << " " << shipping_option.title
- << " with price parts " << format::as_array(shipping_option.price_parts) << "]";
-}
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
-void answer_shipping_query(int64 shipping_query_id, vector<tl_object_ptr<td_api::shippingOption>> &&shipping_options,
+void answer_shipping_query(Td *td, int64 shipping_query_id,
+ vector<tl_object_ptr<td_api::shippingOption>> &&shipping_options,
const string &error_message, Promise<Unit> &&promise) {
vector<tl_object_ptr<telegram_api::shippingOption>> options;
for (auto &option : shipping_options) {
if (option == nullptr) {
- return promise.set_error(Status::Error(400, "Shipping option must not be empty"));
+ return promise.set_error(Status::Error(400, "Shipping option must be non-empty"));
}
if (!clean_input_string(option->id_)) {
- return promise.set_error(Status::Error(400, "Shipping option id must be encoded in UTF-8"));
+ return promise.set_error(Status::Error(400, "Shipping option identifier must be encoded in UTF-8"));
}
if (!clean_input_string(option->title_)) {
return promise.set_error(Status::Error(400, "Shipping option title must be encoded in UTF-8"));
@@ -629,11 +716,14 @@ void answer_shipping_query(int64 shipping_query_id, vector<tl_object_ptr<td_api:
vector<tl_object_ptr<telegram_api::labeledPrice>> prices;
for (auto &price_part : option->price_parts_) {
if (price_part == nullptr) {
- return promise.set_error(Status::Error(400, "Shipping option price part must not be empty"));
+ return promise.set_error(Status::Error(400, "Shipping option price part must be non-empty"));
}
if (!clean_input_string(price_part->label_)) {
return promise.set_error(Status::Error(400, "Shipping option price part label must be encoded in UTF-8"));
}
+ if (!check_currency_amount(price_part->amount_)) {
+ return promise.set_error(Status::Error(400, "Too big amount of the currency specified"));
+ }
prices.push_back(make_tl_object<telegram_api::labeledPrice>(std::move(price_part->label_), price_part->amount_));
}
@@ -642,25 +732,34 @@ void answer_shipping_query(int64 shipping_query_id, vector<tl_object_ptr<td_api:
std::move(prices)));
}
- G()->td()
- .get_actor_unsafe()
- ->create_handler<SetBotShippingAnswerQuery>(std::move(promise))
+ td->create_handler<SetBotShippingAnswerQuery>(std::move(promise))
->send(shipping_query_id, error_message, std::move(options));
}
-void answer_pre_checkout_query(int64 pre_checkout_query_id, const string &error_message, Promise<Unit> &&promise) {
- G()->td()
- .get_actor_unsafe()
- ->create_handler<SetBotPreCheckoutAnswerQuery>(std::move(promise))
- ->send(pre_checkout_query_id, error_message);
+void answer_pre_checkout_query(Td *td, int64 pre_checkout_query_id, const string &error_message,
+ Promise<Unit> &&promise) {
+ td->create_handler<SetBotPreCheckoutAnswerQuery>(std::move(promise))->send(pre_checkout_query_id, error_message);
}
-void get_payment_form(ServerMessageId server_message_id, Promise<tl_object_ptr<td_api::paymentForm>> &&promise) {
- G()->td().get_actor_unsafe()->create_handler<GetPaymentFormQuery>(std::move(promise))->send(server_message_id);
+void get_payment_form(Td *td, td_api::object_ptr<td_api::InputInvoice> &&input_invoice,
+ const td_api::object_ptr<td_api::themeParameters> &theme,
+ Promise<tl_object_ptr<td_api::paymentForm>> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_invoice_info, get_input_invoice_info(td, std::move(input_invoice)));
+
+ tl_object_ptr<telegram_api::dataJSON> theme_parameters;
+ if (theme != nullptr) {
+ theme_parameters = make_tl_object<telegram_api::dataJSON>(string());
+ theme_parameters->data_ = ThemeManager::get_theme_parameters_json_string(theme, false);
+ }
+ td->create_handler<GetPaymentFormQuery>(std::move(promise))
+ ->send(std::move(input_invoice_info), std::move(theme_parameters));
}
-void validate_order_info(ServerMessageId server_message_id, tl_object_ptr<td_api::orderInfo> order_info,
- bool allow_save, Promise<tl_object_ptr<td_api::validatedOrderInfo>> &&promise) {
+void validate_order_info(Td *td, td_api::object_ptr<td_api::InputInvoice> &&input_invoice,
+ td_api::object_ptr<td_api::orderInfo> &&order_info, bool allow_save,
+ Promise<td_api::object_ptr<td_api::validatedOrderInfo>> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_invoice_info, get_input_invoice_info(td, std::move(input_invoice)));
+
if (order_info != nullptr) {
if (!clean_input_string(order_info->name_)) {
return promise.set_error(Status::Error(400, "Name must be encoded in UTF-8"));
@@ -693,16 +792,19 @@ void validate_order_info(ServerMessageId server_message_id, tl_object_ptr<td_api
}
}
- G()->td()
- .get_actor_unsafe()
- ->create_handler<ValidateRequestedInfoQuery>(std::move(promise))
- ->send(server_message_id, convert_order_info(std::move(order_info)), allow_save);
+ td->create_handler<ValidateRequestedInfoQuery>(std::move(promise))
+ ->send(std::move(input_invoice_info), convert_order_info(std::move(order_info)), allow_save);
}
-void send_payment_form(ServerMessageId server_message_id, const string &order_info_id, const string &shipping_option_id,
- const tl_object_ptr<td_api::InputCredentials> &credentials,
- Promise<tl_object_ptr<td_api::paymentResult>> &&promise) {
- CHECK(credentials != nullptr);
+void send_payment_form(Td *td, td_api::object_ptr<td_api::InputInvoice> &&input_invoice, int64 payment_form_id,
+ const string &order_info_id, const string &shipping_option_id,
+ const td_api::object_ptr<td_api::InputCredentials> &credentials, int64 tip_amount,
+ Promise<td_api::object_ptr<td_api::paymentResult>> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_invoice_info, get_input_invoice_info(td, std::move(input_invoice)));
+
+ if (credentials == nullptr) {
+ return promise.set_error(Status::Error(400, "Input payment credentials must be non-empty"));
+ }
tl_object_ptr<telegram_api::InputPaymentCredentials> input_credentials;
switch (credentials->get_id()) {
@@ -710,10 +812,9 @@ void send_payment_form(ServerMessageId server_message_id, const string &order_in
auto credentials_saved = static_cast<const td_api::inputCredentialsSaved *>(credentials.get());
auto credentials_id = credentials_saved->saved_credentials_id_;
if (!clean_input_string(credentials_id)) {
- return promise.set_error(Status::Error(400, "Credentials id must be encoded in UTF-8"));
+ return promise.set_error(Status::Error(400, "Credentials identifier must be encoded in UTF-8"));
}
- auto temp_password_state =
- G()->td().get_actor_unsafe()->password_manager_->get_actor_unsafe()->get_temp_password_state_sync();
+ auto temp_password_state = PasswordManager::get_temp_password_state_sync();
if (!temp_password_state.has_temp_password) {
return promise.set_error(Status::Error(400, "Temporary password required to use saved credentials"));
}
@@ -733,10 +834,10 @@ void send_payment_form(ServerMessageId server_message_id, const string &order_in
flags, false /*ignored*/, make_tl_object<telegram_api::dataJSON>(credentials_new->data_));
break;
}
- case td_api::inputCredentialsAndroidPay::ID: {
- auto credentials_android_pay = static_cast<const td_api::inputCredentialsAndroidPay *>(credentials.get());
- input_credentials = make_tl_object<telegram_api::inputPaymentCredentialsAndroidPay>(
- make_tl_object<telegram_api::dataJSON>(credentials_android_pay->data_), string());
+ case td_api::inputCredentialsGooglePay::ID: {
+ auto credentials_google_pay = static_cast<const td_api::inputCredentialsGooglePay *>(credentials.get());
+ input_credentials = make_tl_object<telegram_api::inputPaymentCredentialsGooglePay>(
+ make_tl_object<telegram_api::dataJSON>(credentials_google_pay->data_));
break;
}
case td_api::inputCredentialsApplePay::ID: {
@@ -749,26 +850,45 @@ void send_payment_form(ServerMessageId server_message_id, const string &order_in
UNREACHABLE();
}
- G()->td()
- .get_actor_unsafe()
- ->create_handler<SendPaymentFormQuery>(std::move(promise))
- ->send(server_message_id, order_info_id, shipping_option_id, std::move(input_credentials));
+ td->create_handler<SendPaymentFormQuery>(std::move(promise))
+ ->send(std::move(input_invoice_info), payment_form_id, order_info_id, shipping_option_id,
+ std::move(input_credentials), tip_amount);
+}
+
+void get_payment_receipt(Td *td, FullMessageId full_message_id,
+ Promise<tl_object_ptr<td_api::paymentReceipt>> &&promise) {
+ TRY_RESULT_PROMISE(promise, server_message_id,
+ td->messages_manager_->get_payment_successful_message_id(full_message_id));
+ td->create_handler<GetPaymentReceiptQuery>(std::move(promise))
+ ->send(full_message_id.get_dialog_id(), server_message_id);
+}
+
+void get_saved_order_info(Td *td, Promise<tl_object_ptr<td_api::orderInfo>> &&promise) {
+ td->create_handler<GetSavedInfoQuery>(std::move(promise))->send();
}
-void get_payment_receipt(ServerMessageId server_message_id, Promise<tl_object_ptr<td_api::paymentReceipt>> &&promise) {
- G()->td().get_actor_unsafe()->create_handler<GetPaymentReceiptQuery>(std::move(promise))->send(server_message_id);
+void delete_saved_order_info(Td *td, Promise<Unit> &&promise) {
+ td->create_handler<ClearSavedInfoQuery>(std::move(promise))->send(false, true);
}
-void get_saved_order_info(Promise<tl_object_ptr<td_api::orderInfo>> &&promise) {
- G()->td().get_actor_unsafe()->create_handler<GetSavedInfoQuery>(std::move(promise))->send();
+void delete_saved_credentials(Td *td, Promise<Unit> &&promise) {
+ td->create_handler<ClearSavedInfoQuery>(std::move(promise))->send(true, false);
}
-void delete_saved_order_info(Promise<Unit> &&promise) {
- G()->td().get_actor_unsafe()->create_handler<ClearSavedInfoQuery>(std::move(promise))->send(false, true);
+void export_invoice(Td *td, td_api::object_ptr<td_api::InputMessageContent> &&invoice, Promise<string> &&promise) {
+ if (invoice == nullptr) {
+ return promise.set_error(Status::Error(400, "Invoice must be non-empty"));
+ }
+ TRY_RESULT_PROMISE(promise, input_invoice,
+ InputInvoice::process_input_message_invoice(std::move(invoice), td, DialogId(), false));
+ auto input_media = input_invoice.get_input_media_invoice(td, nullptr, nullptr);
+ CHECK(input_media != nullptr);
+ td->create_handler<ExportInvoiceQuery>(std::move(promise))->send(std::move(input_media));
}
-void delete_saved_credentials(Promise<Unit> &&promise) {
- G()->td().get_actor_unsafe()->create_handler<ClearSavedInfoQuery>(std::move(promise))->send(true, false);
+void get_bank_card_info(Td *td, const string &bank_card_number,
+ Promise<td_api::object_ptr<td_api::bankCardInfo>> &&promise) {
+ td->create_handler<GetBankCardInfoQuery>(std::move(promise))->send(bank_card_number);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Payments.h b/protocols/Telegram/tdlib/td/td/telegram/Payments.h
index 8b61bb145f..a074b23df0 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Payments.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Payments.h
@@ -1,146 +1,54 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/PromiseFuture.h"
-
-#include "td/telegram/MessageId.h"
-#include "td/telegram/Photo.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/td_api.h"
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
#include "td/utils/StringBuilder.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
namespace td {
-struct LabeledPricePart {
- string label;
- int64 amount = 0;
-
- LabeledPricePart() = default;
- LabeledPricePart(string &&label, int64 amount) : label(std::move(label)), amount(amount) {
- }
-};
-
-struct Invoice {
- string currency;
- vector<LabeledPricePart> price_parts;
- bool is_test = false;
- bool need_name = false;
- bool need_phone_number = false;
- bool need_email_address = false;
- bool need_shipping_address = false;
- bool send_phone_number_to_provider = false;
- bool send_email_address_to_provider = false;
- bool is_flexible = false;
-
- Invoice() = default;
- Invoice(string &&currency, bool is_test, bool need_shipping_address)
- : currency(std::move(currency)), is_test(is_test), need_shipping_address(need_shipping_address) {
- }
-};
-
-struct ShippingAddress {
- string country_code;
- string state;
- string city;
- string street_line1;
- string street_line2;
- string postal_code;
-
- ShippingAddress() = default;
- ShippingAddress(string &&country_code, string &&state, string &&city, string &&street_line1, string &&street_line2,
- string &&postal_code)
- : country_code(std::move(country_code))
- , state(std::move(state))
- , city(std::move(city))
- , street_line1(std::move(street_line1))
- , street_line2(std::move(street_line2))
- , postal_code(std::move(postal_code)) {
- }
-};
-
-struct OrderInfo {
- string name;
- string phone_number;
- string email_address;
- unique_ptr<ShippingAddress> shipping_address;
-
- OrderInfo() = default;
- OrderInfo(string &&name, string &&phone_number, string &&email_address,
- unique_ptr<ShippingAddress> &&shipping_address)
- : name(std::move(name))
- , phone_number(std::move(phone_number))
- , email_address(std::move(email_address))
- , shipping_address(std::move(shipping_address)) {
- }
-};
-
-struct ShippingOption {
- string id;
- string title;
- vector<LabeledPricePart> price_parts;
-};
-
-bool operator==(const LabeledPricePart &lhs, const LabeledPricePart &rhs);
-bool operator!=(const LabeledPricePart &lhs, const LabeledPricePart &rhs);
-
-StringBuilder &operator<<(StringBuilder &string_builder, const LabeledPricePart &labeled_price_part);
-
-bool operator==(const Invoice &lhs, const Invoice &rhs);
-bool operator!=(const Invoice &lhs, const Invoice &rhs);
-
-StringBuilder &operator<<(StringBuilder &string_builder, const Invoice &invoice);
-
-bool operator==(const ShippingAddress &lhs, const ShippingAddress &rhs);
-bool operator!=(const ShippingAddress &lhs, const ShippingAddress &rhs);
-
-StringBuilder &operator<<(StringBuilder &string_builder, const ShippingAddress &shipping_address);
-
-unique_ptr<ShippingAddress> get_shipping_address(tl_object_ptr<telegram_api::postAddress> address);
-
-tl_object_ptr<td_api::shippingAddress> get_shipping_address_object(const unique_ptr<ShippingAddress> &shipping_address);
-
-bool operator==(const OrderInfo &lhs, const OrderInfo &rhs);
-bool operator!=(const OrderInfo &lhs, const OrderInfo &rhs);
-
-StringBuilder &operator<<(StringBuilder &string_builder, const OrderInfo &order_info);
-
-unique_ptr<OrderInfo> get_order_info(tl_object_ptr<telegram_api::paymentRequestedInfo> order_info);
-
-tl_object_ptr<td_api::orderInfo> get_order_info_object(const unique_ptr<OrderInfo> &order_info);
-
-bool operator==(const ShippingOption &lhs, const ShippingOption &rhs);
-bool operator!=(const ShippingOption &lhs, const ShippingOption &rhs);
-
-StringBuilder &operator<<(StringBuilder &string_builder, const ShippingOption &shipping_option);
-
-void answer_shipping_query(int64 shipping_query_id, vector<tl_object_ptr<td_api::shippingOption>> &&shipping_options,
+class Td;
+
+void answer_shipping_query(Td *td, int64 shipping_query_id,
+ vector<tl_object_ptr<td_api::shippingOption>> &&shipping_options,
const string &error_message, Promise<Unit> &&promise);
-void answer_pre_checkout_query(int64 pre_checkout_query_id, const string &error_message, Promise<Unit> &&promise);
+void answer_pre_checkout_query(Td *td, int64 pre_checkout_query_id, const string &error_message,
+ Promise<Unit> &&promise);
+
+void get_payment_form(Td *td, td_api::object_ptr<td_api::InputInvoice> &&input_invoice,
+ const td_api::object_ptr<td_api::themeParameters> &theme,
+ Promise<tl_object_ptr<td_api::paymentForm>> &&promise);
+
+void validate_order_info(Td *td, td_api::object_ptr<td_api::InputInvoice> &&input_invoice,
+ td_api::object_ptr<td_api::orderInfo> &&order_info, bool allow_save,
+ Promise<td_api::object_ptr<td_api::validatedOrderInfo>> &&promise);
-void get_payment_form(ServerMessageId server_message_id, Promise<tl_object_ptr<td_api::paymentForm>> &&promise);
+void send_payment_form(Td *td, td_api::object_ptr<td_api::InputInvoice> &&input_invoice, int64 payment_form_id,
+ const string &order_info_id, const string &shipping_option_id,
+ const td_api::object_ptr<td_api::InputCredentials> &credentials, int64 tip_amount,
+ Promise<td_api::object_ptr<td_api::paymentResult>> &&promise);
-void validate_order_info(ServerMessageId server_message_id, tl_object_ptr<td_api::orderInfo> order_info,
- bool allow_save, Promise<tl_object_ptr<td_api::validatedOrderInfo>> &&promise);
+void get_payment_receipt(Td *td, FullMessageId full_message_id,
+ Promise<tl_object_ptr<td_api::paymentReceipt>> &&promise);
-void send_payment_form(ServerMessageId server_message_id, const string &order_info_id, const string &shipping_option_id,
- const tl_object_ptr<td_api::InputCredentials> &credentials,
- Promise<tl_object_ptr<td_api::paymentResult>> &&promise);
+void get_saved_order_info(Td *td, Promise<tl_object_ptr<td_api::orderInfo>> &&promise);
-void get_payment_receipt(ServerMessageId server_message_id, Promise<tl_object_ptr<td_api::paymentReceipt>> &&promise);
+void delete_saved_order_info(Td *td, Promise<Unit> &&promise);
-void get_saved_order_info(Promise<tl_object_ptr<td_api::orderInfo>> &&promise);
+void delete_saved_credentials(Td *td, Promise<Unit> &&promise);
-void delete_saved_order_info(Promise<Unit> &&promise);
+void export_invoice(Td *td, td_api::object_ptr<td_api::InputMessageContent> &&invoice, Promise<string> &&promise);
-void delete_saved_credentials(Promise<Unit> &&promise);
+void get_bank_card_info(Td *td, const string &bank_card_number,
+ Promise<td_api::object_ptr<td_api::bankCardInfo>> &&promise);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Payments.hpp b/protocols/Telegram/tdlib/td/td/telegram/Payments.hpp
deleted file mode 100644
index e37773ca57..0000000000
--- a/protocols/Telegram/tdlib/td/td/telegram/Payments.hpp
+++ /dev/null
@@ -1,134 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/telegram/Payments.h"
-
-#include "td/telegram/Photo.hpp"
-
-#include "td/utils/tl_helpers.h"
-
-namespace td {
-
-template <class StorerT>
-void store(const LabeledPricePart &labeled_price_part, StorerT &storer) {
- store(labeled_price_part.label, storer);
- store(labeled_price_part.amount, storer);
-}
-
-template <class ParserT>
-void parse(LabeledPricePart &labeled_price_part, ParserT &parser) {
- parse(labeled_price_part.label, parser);
- parse(labeled_price_part.amount, parser);
-}
-
-template <class StorerT>
-void store(const Invoice &invoice, StorerT &storer) {
- BEGIN_STORE_FLAGS();
- STORE_FLAG(invoice.is_test);
- STORE_FLAG(invoice.need_name);
- STORE_FLAG(invoice.need_phone_number);
- STORE_FLAG(invoice.need_email_address);
- STORE_FLAG(invoice.need_shipping_address);
- STORE_FLAG(invoice.is_flexible);
- STORE_FLAG(invoice.send_phone_number_to_provider);
- STORE_FLAG(invoice.send_email_address_to_provider);
- END_STORE_FLAGS();
- store(invoice.currency, storer);
- store(invoice.price_parts, storer);
-}
-
-template <class ParserT>
-void parse(Invoice &invoice, ParserT &parser) {
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(invoice.is_test);
- PARSE_FLAG(invoice.need_name);
- PARSE_FLAG(invoice.need_phone_number);
- PARSE_FLAG(invoice.need_email_address);
- PARSE_FLAG(invoice.need_shipping_address);
- PARSE_FLAG(invoice.is_flexible);
- PARSE_FLAG(invoice.send_phone_number_to_provider);
- PARSE_FLAG(invoice.send_email_address_to_provider);
- END_PARSE_FLAGS();
- parse(invoice.currency, parser);
- parse(invoice.price_parts, parser);
-}
-
-template <class StorerT>
-void store(const ShippingAddress &shipping_address, StorerT &storer) {
- store(shipping_address.country_code, storer);
- store(shipping_address.state, storer);
- store(shipping_address.city, storer);
- store(shipping_address.street_line1, storer);
- store(shipping_address.street_line2, storer);
- store(shipping_address.postal_code, storer);
-}
-
-template <class ParserT>
-void parse(ShippingAddress &shipping_address, ParserT &parser) {
- parse(shipping_address.country_code, parser);
- parse(shipping_address.state, parser);
- parse(shipping_address.city, parser);
- parse(shipping_address.street_line1, parser);
- parse(shipping_address.street_line2, parser);
- parse(shipping_address.postal_code, parser);
-}
-
-template <class StorerT>
-void store(const OrderInfo &order_info, StorerT &storer) {
- bool has_name = !order_info.name.empty();
- bool has_phone_number = !order_info.phone_number.empty();
- bool has_email_address = !order_info.email_address.empty();
- bool has_shipping_address = order_info.shipping_address != nullptr;
- BEGIN_STORE_FLAGS();
- STORE_FLAG(has_name);
- STORE_FLAG(has_phone_number);
- STORE_FLAG(has_email_address);
- STORE_FLAG(has_shipping_address);
- END_STORE_FLAGS();
- if (has_name) {
- store(order_info.name, storer);
- }
- if (has_phone_number) {
- store(order_info.phone_number, storer);
- }
- if (has_email_address) {
- store(order_info.email_address, storer);
- }
- if (has_shipping_address) {
- store(*order_info.shipping_address, storer);
- }
-}
-
-template <class ParserT>
-void parse(OrderInfo &order_info, ParserT &parser) {
- bool has_name;
- bool has_phone_number;
- bool has_email_address;
- bool has_shipping_address;
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(has_name);
- PARSE_FLAG(has_phone_number);
- PARSE_FLAG(has_email_address);
- PARSE_FLAG(has_shipping_address);
- END_PARSE_FLAGS();
- if (has_name) {
- parse(order_info.name, parser);
- }
- if (has_phone_number) {
- parse(order_info.phone_number, parser);
- }
- if (has_email_address) {
- parse(order_info.email_address, parser);
- }
- if (has_shipping_address) {
- order_info.shipping_address = make_unique<ShippingAddress>();
- parse(*order_info.shipping_address, parser);
- }
-}
-
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PhoneNumberManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/PhoneNumberManager.cpp
new file mode 100644
index 0000000000..8ef10aca17
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PhoneNumberManager.cpp
@@ -0,0 +1,263 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/PhoneNumberManager.h"
+
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/SuggestedAction.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/ScopeGuard.h"
+
+namespace td {
+
+void PhoneNumberManager::get_state(uint64 query_id) {
+ tl_object_ptr<td_api::Object> obj;
+ switch (state_) {
+ case State::Ok:
+ obj = make_tl_object<td_api::ok>();
+ break;
+ case State::WaitCode:
+ obj = send_code_helper_.get_authentication_code_info_object();
+ break;
+ }
+ CHECK(obj);
+ send_closure(G()->td(), &Td::send_result, query_id, std::move(obj));
+}
+
+PhoneNumberManager::PhoneNumberManager(PhoneNumberManager::Type type, ActorShared<> parent)
+ : type_(type), parent_(std::move(parent)) {
+}
+
+void PhoneNumberManager::send_new_send_code_query(uint64 query_id, const telegram_api::Function &send_code) {
+ on_new_query(query_id);
+ start_net_query(NetQueryType::SendCode, G()->net_query_creator().create(send_code));
+}
+
+void PhoneNumberManager::set_phone_number(uint64 query_id, string phone_number, Settings settings) {
+ if (phone_number.empty()) {
+ return on_query_error(query_id, Status::Error(400, "Phone number must be non-empty"));
+ }
+
+ switch (type_) {
+ case Type::ChangePhone:
+ send_closure(G()->config_manager(), &ConfigManager::hide_suggested_action,
+ SuggestedAction{SuggestedAction::Type::CheckPhoneNumber});
+ return send_new_send_code_query(query_id, send_code_helper_.send_change_phone_code(phone_number, settings));
+ case Type::VerifyPhone:
+ return send_new_send_code_query(query_id, send_code_helper_.send_verify_phone_code(phone_number, settings));
+ case Type::ConfirmPhone:
+ default:
+ UNREACHABLE();
+ }
+}
+
+void PhoneNumberManager::set_phone_number_and_hash(uint64 query_id, string hash, string phone_number,
+ Settings settings) {
+ if (phone_number.empty()) {
+ return on_query_error(query_id, Status::Error(400, "Phone number must be non-empty"));
+ }
+ if (hash.empty()) {
+ return on_query_error(query_id, Status::Error(400, "Hash must be non-empty"));
+ }
+
+ switch (type_) {
+ case Type::ConfirmPhone:
+ return send_new_send_code_query(query_id,
+ send_code_helper_.send_confirm_phone_code(hash, phone_number, settings));
+ case Type::ChangePhone:
+ case Type::VerifyPhone:
+ default:
+ UNREACHABLE();
+ }
+}
+
+void PhoneNumberManager::resend_authentication_code(uint64 query_id) {
+ if (state_ != State::WaitCode) {
+ return on_query_error(query_id, Status::Error(400, "resendAuthenticationCode unexpected"));
+ }
+
+ auto r_resend_code = send_code_helper_.resend_code();
+ if (r_resend_code.is_error()) {
+ return on_query_error(query_id, r_resend_code.move_as_error());
+ }
+
+ on_new_query(query_id);
+
+ start_net_query(NetQueryType::SendCode, G()->net_query_creator().create_unauth(r_resend_code.move_as_ok()));
+}
+
+void PhoneNumberManager::send_new_check_code_query(const telegram_api::Function &check_code) {
+ start_net_query(NetQueryType::CheckCode, G()->net_query_creator().create(check_code));
+}
+
+void PhoneNumberManager::check_code(uint64 query_id, string code) {
+ if (state_ != State::WaitCode) {
+ return on_query_error(query_id, Status::Error(400, "checkAuthenticationCode unexpected"));
+ }
+
+ on_new_query(query_id);
+
+ switch (type_) {
+ case Type::ChangePhone:
+ return send_new_check_code_query(telegram_api::account_changePhone(
+ send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code));
+ case Type::ConfirmPhone:
+ return send_new_check_code_query(
+ telegram_api::account_confirmPhone(send_code_helper_.phone_code_hash().str(), code));
+ case Type::VerifyPhone:
+ return send_new_check_code_query(telegram_api::account_verifyPhone(
+ send_code_helper_.phone_number().str(), send_code_helper_.phone_code_hash().str(), code));
+ default:
+ UNREACHABLE();
+ }
+}
+
+void PhoneNumberManager::on_new_query(uint64 query_id) {
+ if (query_id_ != 0) {
+ on_query_error(Status::Error(400, "Another authorization query has started"));
+ }
+ net_query_id_ = 0;
+ net_query_type_ = NetQueryType::None;
+ query_id_ = query_id;
+ // TODO: cancel older net_query
+}
+
+void PhoneNumberManager::on_query_error(Status status) {
+ CHECK(query_id_ != 0);
+ auto id = query_id_;
+ query_id_ = 0;
+ net_query_id_ = 0;
+ net_query_type_ = NetQueryType::None;
+ on_query_error(id, std::move(status));
+}
+
+void PhoneNumberManager::on_query_error(uint64 id, Status status) {
+ send_closure(G()->td(), &Td::send_error, id, std::move(status));
+}
+
+void PhoneNumberManager::on_query_ok() {
+ CHECK(query_id_ != 0);
+ auto id = query_id_;
+ net_query_id_ = 0;
+ net_query_type_ = NetQueryType::None;
+ query_id_ = 0;
+ get_state(id);
+}
+
+void PhoneNumberManager::start_net_query(NetQueryType net_query_type, NetQueryPtr net_query) {
+ // TODO: cancel old net_query?
+ net_query_type_ = net_query_type;
+ net_query_id_ = net_query->id();
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this));
+}
+
+void PhoneNumberManager::process_check_code_result(Result<tl_object_ptr<telegram_api::User>> &&result) {
+ if (result.is_error()) {
+ return on_query_error(result.move_as_error());
+ }
+ send_closure(G()->contacts_manager(), &ContactsManager::on_get_user, result.move_as_ok(), "process_check_code_result",
+ true);
+ state_ = State::Ok;
+ on_query_ok();
+}
+
+void PhoneNumberManager::process_check_code_result(Result<bool> &&result) {
+ if (result.is_error()) {
+ return on_query_error(result.move_as_error());
+ }
+ state_ = State::Ok;
+ on_query_ok();
+}
+
+void PhoneNumberManager::on_check_code_result(NetQueryPtr &result) {
+ switch (type_) {
+ case Type::ChangePhone:
+ return process_check_code_result(fetch_result<telegram_api::account_changePhone>(result->ok()));
+ case Type::VerifyPhone:
+ return process_check_code_result(fetch_result<telegram_api::account_verifyPhone>(result->ok()));
+ case Type::ConfirmPhone:
+ return process_check_code_result(fetch_result<telegram_api::account_confirmPhone>(result->ok()));
+ default:
+ UNREACHABLE();
+ }
+}
+
+void PhoneNumberManager::on_send_code_result(NetQueryPtr &result) {
+ auto r_sent_code = [&] {
+ switch (type_) {
+ case Type::ChangePhone:
+ return fetch_result<telegram_api::account_sendChangePhoneCode>(result->ok());
+ case Type::VerifyPhone:
+ return fetch_result<telegram_api::account_sendVerifyPhoneCode>(result->ok());
+ case Type::ConfirmPhone:
+ return fetch_result<telegram_api::account_sendConfirmPhoneCode>(result->ok());
+ default:
+ UNREACHABLE();
+ return fetch_result<telegram_api::account_sendChangePhoneCode>(result->ok());
+ }
+ }();
+ if (r_sent_code.is_error()) {
+ return on_query_error(r_sent_code.move_as_error());
+ }
+ auto sent_code = r_sent_code.move_as_ok();
+
+ LOG(INFO) << "Receive " << to_string(sent_code);
+
+ switch (sent_code->type_->get_id()) {
+ case telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID:
+ case telegram_api::auth_sentCodeTypeEmailCode::ID:
+ return on_query_error(Status::Error(500, "Receive incorrect response"));
+ default:
+ break;
+ }
+
+ send_code_helper_.on_sent_code(std::move(sent_code));
+
+ state_ = State::WaitCode;
+ on_query_ok();
+}
+
+void PhoneNumberManager::on_result(NetQueryPtr result) {
+ SCOPE_EXIT {
+ result->clear();
+ };
+ NetQueryType type = NetQueryType::None;
+ if (result->id() == net_query_id_) {
+ net_query_id_ = 0;
+ type = net_query_type_;
+ net_query_type_ = NetQueryType::None;
+ if (result->is_error()) {
+ if (query_id_ != 0) {
+ on_query_error(std::move(result->error()));
+ }
+ return;
+ }
+ }
+ switch (type) {
+ case NetQueryType::None:
+ result->ignore();
+ break;
+ case NetQueryType::SendCode:
+ on_send_code_result(result);
+ break;
+ case NetQueryType::CheckCode:
+ on_check_code_result(result);
+ break;
+ }
+}
+
+void PhoneNumberManager::tear_down() {
+ parent_.reset();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PhoneNumberManager.h b/protocols/Telegram/tdlib/td/td/telegram/PhoneNumberManager.h
new file mode 100644
index 0000000000..5104f413e4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PhoneNumberManager.h
@@ -0,0 +1,76 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/net/NetActor.h"
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/SendCodeHelper.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class PhoneNumberManager final : public NetActor {
+ public:
+ enum class Type : int32 { ChangePhone, VerifyPhone, ConfirmPhone };
+ PhoneNumberManager(Type type, ActorShared<> parent);
+ void get_state(uint64 query_id);
+
+ using Settings = td_api::object_ptr<td_api::phoneNumberAuthenticationSettings>;
+
+ void set_phone_number(uint64 query_id, string phone_number, Settings settings);
+ void set_phone_number_and_hash(uint64 query_id, string hash, string phone_number, Settings settings);
+
+ void resend_authentication_code(uint64 query_id);
+ void check_code(uint64 query_id, string code);
+
+ private:
+ Type type_;
+
+ enum class State : int32 { Ok, WaitCode } state_ = State::Ok;
+ enum class NetQueryType : int32 { None, SendCode, CheckCode };
+
+ ActorShared<> parent_;
+ uint64 query_id_ = 0;
+ uint64 net_query_id_ = 0;
+ NetQueryType net_query_type_ = NetQueryType::None;
+
+ SendCodeHelper send_code_helper_;
+
+ void on_new_query(uint64 query_id);
+
+ void on_query_ok();
+
+ void on_query_error(Status status);
+
+ static void on_query_error(uint64 id, Status status);
+
+ void start_net_query(NetQueryType net_query_type, NetQueryPtr net_query);
+
+ void send_new_send_code_query(uint64 query_id, const telegram_api::Function &send_code);
+
+ void send_new_check_code_query(const telegram_api::Function &check_code);
+
+ void process_check_code_result(Result<tl_object_ptr<telegram_api::User>> &&result);
+
+ void process_check_code_result(Result<bool> &&result);
+
+ void on_result(NetQueryPtr result) final;
+
+ void on_send_code_result(NetQueryPtr &result);
+
+ void on_check_code_result(NetQueryPtr &result);
+
+ void tear_down() final;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Photo.cpp b/protocols/Telegram/tdlib/td/td/telegram/Photo.cpp
index f08007a87a..002f6d8875 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Photo.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Photo.cpp
@@ -1,103 +1,31 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Photo.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/telegram_api.h"
-
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/files/FileEncryptionKey.h"
+#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/FileManager.h"
-#include "td/telegram/Global.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/PhotoSizeSource.h"
+#include "td/utils/algorithm.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
-#include "td/utils/HttpUrl.h"
#include "td/utils/logging.h"
-#include "td/utils/misc.h"
-#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
#include <algorithm>
-#include <limits>
namespace td {
-static uint16 get_dimension(int32 size) {
- if (size < 0 || size > 65535) {
- LOG(ERROR) << "Wrong image dimension = " << size;
- return 0;
- }
- return narrow_cast<uint16>(size);
-}
-
-Dimensions get_dimensions(int32 width, int32 height) {
- Dimensions result;
- result.width = get_dimension(width);
- result.height = get_dimension(height);
- if (result.width == 0 || result.height == 0) {
- result.width = 0;
- result.height = 0;
- }
- return result;
-}
-
-bool operator==(const Dimensions &lhs, const Dimensions &rhs) {
- return lhs.width == rhs.width && lhs.height == rhs.height;
-}
-
-bool operator!=(const Dimensions &lhs, const Dimensions &rhs) {
- return !(lhs == rhs);
-}
-
-StringBuilder &operator<<(StringBuilder &string_builder, const Dimensions &dimensions) {
- return string_builder << "(" << dimensions.width << ", " << dimensions.height << ")";
-}
-
-static FileId register_photo(FileManager *file_manager, FileType file_type, int64 id, int64 access_hash,
- tl_object_ptr<telegram_api::FileLocation> &&location_ptr, DialogId owner_dialog_id,
- int32 file_size) {
- int32 location_id = location_ptr->get_id();
- DcId dc_id;
- int32 local_id;
- int64 volume_id;
- int64 secret;
- switch (location_id) {
- case telegram_api::fileLocationUnavailable::ID: {
- auto location = move_tl_object_as<telegram_api::fileLocationUnavailable>(location_ptr);
- dc_id = DcId::invalid();
- local_id = location->local_id_;
- volume_id = location->volume_id_;
- secret = location->secret_;
- break;
- }
- case telegram_api::fileLocation::ID: {
- auto location = move_tl_object_as<telegram_api::fileLocation>(location_ptr);
- if (!DcId::is_valid(location->dc_id_)) {
- dc_id = DcId::invalid();
- } else {
- dc_id = DcId::internal(location->dc_id_);
- }
- local_id = location->local_id_;
- volume_id = location->volume_id_;
- secret = location->secret_;
- break;
- }
- default:
- UNREACHABLE();
- break;
- }
-
- LOG(DEBUG) << "Receive photo of type " << static_cast<int8>(file_type) << " in [" << dc_id << "," << volume_id << ","
- << local_id << "]. Id: (" << id << ", " << access_hash << ")";
- auto suggested_name = PSTRING() << static_cast<uint64>(volume_id) << "_" << static_cast<uint64>(local_id) << ".jpg";
- return file_manager->register_remote(
- FullRemoteFileLocation(file_type, id, access_hash, local_id, volume_id, secret, dc_id),
- FileLocationSource::FromServer, owner_dialog_id, file_size, 0, std::move(suggested_name));
-}
-
-ProfilePhoto get_profile_photo(FileManager *file_manager,
+ProfilePhoto get_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash,
tl_object_ptr<telegram_api::UserProfilePhoto> &&profile_photo_ptr) {
ProfilePhoto result;
int32 profile_photo_id =
@@ -108,11 +36,16 @@ ProfilePhoto get_profile_photo(FileManager *file_manager,
case telegram_api::userProfilePhoto::ID: {
auto profile_photo = move_tl_object_as<telegram_api::userProfilePhoto>(profile_photo_ptr);
+ auto dc_id = DcId::create(profile_photo->dc_id_);
+ result.has_animation = profile_photo->has_video_;
result.id = profile_photo->photo_id_;
- result.small_file_id = register_photo(file_manager, FileType::ProfilePhoto, result.id, 0,
- std::move(profile_photo->photo_small_), DialogId(), 0);
- result.big_file_id = register_photo(file_manager, FileType::ProfilePhoto, result.id, 0,
- std::move(profile_photo->photo_big_), DialogId(), 0);
+ result.minithumbnail = profile_photo->stripped_thumb_.as_slice().str();
+ result.small_file_id = register_photo_size(
+ file_manager, PhotoSizeSource::dialog_photo(DialogId(user_id), user_access_hash, false), result.id,
+ 0 /*access_hash*/, "" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg);
+ result.big_file_id = register_photo_size(
+ file_manager, PhotoSizeSource::dialog_photo(DialogId(user_id), user_access_hash, true), result.id,
+ 0 /*access_hash*/, "" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg);
break;
}
default:
@@ -124,43 +57,28 @@ ProfilePhoto get_profile_photo(FileManager *file_manager,
}
tl_object_ptr<td_api::profilePhoto> get_profile_photo_object(FileManager *file_manager,
- const ProfilePhoto *profile_photo) {
- if (profile_photo == nullptr || !profile_photo->small_file_id.is_valid()) {
+ const ProfilePhoto &profile_photo) {
+ if (!profile_photo.small_file_id.is_valid()) {
return nullptr;
}
- return make_tl_object<td_api::profilePhoto>(profile_photo->id,
- file_manager->get_file_object(profile_photo->small_file_id),
- file_manager->get_file_object(profile_photo->big_file_id));
+ return td_api::make_object<td_api::profilePhoto>(
+ profile_photo.id, file_manager->get_file_object(profile_photo.small_file_id),
+ file_manager->get_file_object(profile_photo.big_file_id), get_minithumbnail_object(profile_photo.minithumbnail),
+ profile_photo.has_animation);
}
-bool operator==(const ProfilePhoto &lhs, const ProfilePhoto &rhs) {
- bool location_differs = lhs.small_file_id != rhs.small_file_id || lhs.big_file_id != rhs.big_file_id;
- bool id_differs;
- if (lhs.id == -1 && rhs.id == -1) {
- // group chat photo
- id_differs = location_differs;
- } else {
- id_differs = lhs.id != rhs.id;
- }
-
- if (location_differs) {
- LOG_IF(ERROR, !id_differs) << "location_differs = true, but id_differs = false. First profilePhoto: " << lhs
- << ", second profilePhoto: " << rhs;
- return false;
- }
- return true;
-}
-
-bool operator!=(const ProfilePhoto &lhs, const ProfilePhoto &rhs) {
- return !(lhs == rhs);
+bool need_update_profile_photo(const ProfilePhoto &from, const ProfilePhoto &to) {
+ return from.id != to.id || need_update_dialog_photo(from, to);
}
StringBuilder &operator<<(StringBuilder &string_builder, const ProfilePhoto &profile_photo) {
- return string_builder << "<id = " << profile_photo.id << ", small_file_id = " << profile_photo.small_file_id
- << ", big_file_id = " << profile_photo.big_file_id << ">";
+ return string_builder << "<ID = " << profile_photo.id << ", small_file_id = " << profile_photo.small_file_id
+ << ", big_file_id = " << profile_photo.big_file_id
+ << ", has_animation = " << profile_photo.has_animation << ">";
}
-DialogPhoto get_dialog_photo(FileManager *file_manager, tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr) {
+DialogPhoto get_dialog_photo(FileManager *file_manager, DialogId dialog_id, int64 dialog_access_hash,
+ tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr) {
int32 chat_photo_id = chat_photo_ptr == nullptr ? telegram_api::chatPhotoEmpty::ID : chat_photo_ptr->get_id();
DialogPhoto result;
@@ -170,10 +88,15 @@ DialogPhoto get_dialog_photo(FileManager *file_manager, tl_object_ptr<telegram_a
case telegram_api::chatPhoto::ID: {
auto chat_photo = move_tl_object_as<telegram_api::chatPhoto>(chat_photo_ptr);
- result.small_file_id = register_photo(file_manager, FileType::ProfilePhoto, 0, 0,
- std::move(chat_photo->photo_small_), DialogId(), 0);
+ auto dc_id = DcId::create(chat_photo->dc_id_);
+ result.has_animation = chat_photo->has_video_;
+ result.minithumbnail = chat_photo->stripped_thumb_.as_slice().str();
+ result.small_file_id =
+ register_photo_size(file_manager, PhotoSizeSource::dialog_photo(dialog_id, dialog_access_hash, false),
+ chat_photo->photo_id_, 0, "", DialogId(), 0, dc_id, PhotoFormat::Jpeg);
result.big_file_id =
- register_photo(file_manager, FileType::ProfilePhoto, 0, 0, std::move(chat_photo->photo_big_), DialogId(), 0);
+ register_photo_size(file_manager, PhotoSizeSource::dialog_photo(dialog_id, dialog_access_hash, true),
+ chat_photo->photo_id_, 0, "", DialogId(), 0, dc_id, PhotoFormat::Jpeg);
break;
}
@@ -185,248 +108,165 @@ DialogPhoto get_dialog_photo(FileManager *file_manager, tl_object_ptr<telegram_a
return result;
}
-tl_object_ptr<td_api::chatPhoto> get_chat_photo_object(FileManager *file_manager, const DialogPhoto *dialog_photo) {
+tl_object_ptr<td_api::chatPhotoInfo> get_chat_photo_info_object(FileManager *file_manager,
+ const DialogPhoto *dialog_photo) {
if (dialog_photo == nullptr || !dialog_photo->small_file_id.is_valid()) {
return nullptr;
}
- return make_tl_object<td_api::chatPhoto>(file_manager->get_file_object(dialog_photo->small_file_id),
- file_manager->get_file_object(dialog_photo->big_file_id));
+ return td_api::make_object<td_api::chatPhotoInfo>(file_manager->get_file_object(dialog_photo->small_file_id),
+ file_manager->get_file_object(dialog_photo->big_file_id),
+ get_minithumbnail_object(dialog_photo->minithumbnail),
+ dialog_photo->has_animation);
}
-bool operator==(const DialogPhoto &lhs, const DialogPhoto &rhs) {
- return lhs.small_file_id == rhs.small_file_id && lhs.big_file_id == rhs.big_file_id;
-}
-
-bool operator!=(const DialogPhoto &lhs, const DialogPhoto &rhs) {
- return !(lhs == rhs);
-}
-
-StringBuilder &operator<<(StringBuilder &string_builder, const DialogPhoto &dialog_photo) {
- return string_builder << "<small_file_id = " << dialog_photo.small_file_id
- << ", big_file_id = " << dialog_photo.big_file_id << ">";
-}
-
-PhotoSize get_thumbnail_photo_size(FileManager *file_manager, BufferSlice bytes, DialogId owner_dialog_id, int32 width,
- int32 height) {
- if (bytes.empty()) {
- return PhotoSize();
- }
- PhotoSize res;
- res.type = 't';
- res.dimensions = get_dimensions(width, height);
- res.size = narrow_cast<int32>(bytes.size());
-
- // generate some random remote location to save
- auto dc_id = DcId::invalid();
- auto local_id = Random::secure_int32();
- auto volume_id = Random::secure_int64();
- auto secret = 0;
- res.file_id = file_manager->register_remote(
- FullRemoteFileLocation(FileType::EncryptedThumbnail, 0, 0, local_id, volume_id, secret, dc_id),
- FileLocationSource::FromServer, owner_dialog_id, res.size, 0,
- PSTRING() << static_cast<uint64>(volume_id) << "_" << static_cast<uint64>(local_id) << ".jpg");
- file_manager->set_content(res.file_id, std::move(bytes));
-
- return res;
+vector<FileId> dialog_photo_get_file_ids(const DialogPhoto &dialog_photo) {
+ vector<FileId> result;
+ if (dialog_photo.small_file_id.is_valid()) {
+ result.push_back(dialog_photo.small_file_id);
+ }
+ if (dialog_photo.big_file_id.is_valid()) {
+ result.push_back(dialog_photo.big_file_id);
+ }
+ return result;
}
-PhotoSize get_photo_size(FileManager *file_manager, FileType file_type, int64 id, int64 access_hash,
- DialogId owner_dialog_id, tl_object_ptr<telegram_api::PhotoSize> &&size_ptr) {
- tl_object_ptr<telegram_api::FileLocation> location_ptr;
- string type;
-
- PhotoSize res;
- BufferSlice content;
-
- int32 size_id = size_ptr->get_id();
- switch (size_id) {
- case telegram_api::photoSizeEmpty::ID:
- return res;
- case telegram_api::photoSize::ID: {
- auto size = move_tl_object_as<telegram_api::photoSize>(size_ptr);
-
- type = std::move(size->type_);
- location_ptr = std::move(size->location_);
- res.dimensions = get_dimensions(size->w_, size->h_);
- res.size = size->size_;
-
- break;
+DialogPhoto as_fake_dialog_photo(const Photo &photo, DialogId dialog_id) {
+ DialogPhoto result;
+ if (!photo.is_empty()) {
+ for (auto &size : photo.photos) {
+ if (size.type == 'a') {
+ result.small_file_id = size.file_id;
+ } else if (size.type == 'c') {
+ result.big_file_id = size.file_id;
+ }
}
- case telegram_api::photoCachedSize::ID: {
- auto size = move_tl_object_as<telegram_api::photoCachedSize>(size_ptr);
-
- type = std::move(size->type_);
- location_ptr = std::move(size->location_);
- CHECK(size->bytes_.size() <= static_cast<size_t>(std::numeric_limits<int32>::max()));
- res.dimensions = get_dimensions(size->w_, size->h_);
- res.size = static_cast<int32>(size->bytes_.size());
-
- content = std::move(size->bytes_);
-
- break;
+ result.minithumbnail = photo.minithumbnail;
+ result.has_animation = !photo.animations.empty();
+ if (!result.small_file_id.is_valid() || !result.big_file_id.is_valid()) {
+ LOG(ERROR) << "Failed to convert " << photo << " to chat photo of " << dialog_id;
+ return DialogPhoto();
}
-
- default:
- UNREACHABLE();
- break;
}
+ return result;
+}
- res.file_id =
- register_photo(file_manager, file_type, id, access_hash, std::move(location_ptr), owner_dialog_id, res.size);
-
- if (!content.empty()) {
- file_manager->set_content(res.file_id, std::move(content));
+DialogPhoto as_dialog_photo(FileManager *file_manager, DialogId dialog_id, int64 dialog_access_hash,
+ const Photo &photo) {
+ DialogPhoto result;
+ static_cast<DialogPhoto &>(result) = as_fake_dialog_photo(photo, dialog_id);
+ if (!result.small_file_id.is_valid()) {
+ return result;
}
- if (type.size() != 1) {
- res.type = 0;
- LOG(ERROR) << "Wrong photoSize " << res;
- } else {
- res.type = static_cast<int32>(type[0]);
- }
+ auto reregister_photo = [&](bool is_big, FileId file_id) {
+ auto file_view = file_manager->get_file_view(file_id);
+ CHECK(file_view.has_remote_location());
+ auto remote = file_view.remote_location();
+ CHECK(remote.is_photo());
+ CHECK(!remote.is_web());
+ remote.set_source(PhotoSizeSource::dialog_photo(dialog_id, dialog_access_hash, is_big));
+ return file_manager->register_remote(std::move(remote), FileLocationSource::FromServer, DialogId(), 0, 0,
+ file_view.remote_name());
+ };
+
+ result.small_file_id = reregister_photo(false, result.small_file_id);
+ result.big_file_id = reregister_photo(true, result.big_file_id);
- return res;
+ return result;
}
-PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_type, DialogId owner_dialog_id,
- tl_object_ptr<telegram_api::WebDocument> web_document_ptr) {
- if (web_document_ptr == nullptr) {
- return {};
+ProfilePhoto as_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash, const Photo &photo) {
+ ProfilePhoto result;
+ static_cast<DialogPhoto &>(result) = as_dialog_photo(file_manager, DialogId(user_id), user_access_hash, photo);
+ if (result.small_file_id.is_valid()) {
+ result.id = photo.id.get();
}
+ return result;
+}
- FileId file_id;
- vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
- int32 size = 0;
- switch (web_document_ptr->get_id()) {
- case telegram_api::webDocument::ID: {
- auto web_document = move_tl_object_as<telegram_api::webDocument>(web_document_ptr);
- if (!DcId::is_valid(web_document->dc_id_)) {
- LOG(ERROR) << "Wrong dc_id = " << web_document->dc_id_;
- return {};
- }
-
- auto r_http_url = parse_url(web_document->url_);
- if (r_http_url.is_error()) {
- LOG(ERROR) << "Can't parse URL " << web_document->url_;
- return {};
- }
- auto http_url = r_http_url.move_as_ok();
- auto url = http_url.get_url();
- file_id = file_manager->register_remote(
- FullRemoteFileLocation(file_type, url, web_document->access_hash_, DcId::internal(web_document->dc_id_)),
- FileLocationSource::FromServer, owner_dialog_id, 0, web_document->size_,
- get_url_query_file_name(http_url.query_));
-
- size = web_document->size_;
- attributes = std::move(web_document->attributes_);
- break;
- }
- case telegram_api::webDocumentNoProxy::ID: {
- auto web_document = move_tl_object_as<telegram_api::webDocumentNoProxy>(web_document_ptr);
- if (web_document->url_.find('.') == string::npos) {
- LOG(ERROR) << "Receive invalid URL " << web_document->url_;
- return {};
- }
-
- auto r_file_id = file_manager->from_persistent_id(web_document->url_, file_type);
- if (r_file_id.is_error()) {
- LOG(ERROR) << "Can't register URL: " << r_file_id.error();
- return {};
- }
- file_id = r_file_id.move_as_ok();
-
- size = web_document->size_;
- attributes = std::move(web_document->attributes_);
- break;
- }
- default:
- UNREACHABLE();
- }
- CHECK(file_id.is_valid());
+bool is_same_dialog_photo(FileManager *file_manager, DialogId dialog_id, const Photo &photo,
+ const DialogPhoto &dialog_photo) {
+ auto get_unique_file_id = [file_manager](FileId file_id) {
+ return file_manager->get_file_view(file_id).get_unique_file_id();
+ };
+ auto fake_photo = as_fake_dialog_photo(photo, dialog_id);
+ return get_unique_file_id(fake_photo.small_file_id) == get_unique_file_id(dialog_photo.small_file_id) &&
+ get_unique_file_id(fake_photo.big_file_id) == get_unique_file_id(dialog_photo.big_file_id);
+}
- Dimensions dimensions;
- for (auto &attribute : attributes) {
- switch (attribute->get_id()) {
- case telegram_api::documentAttributeImageSize::ID: {
- auto image_size = move_tl_object_as<telegram_api::documentAttributeImageSize>(attribute);
- dimensions = get_dimensions(image_size->w_, image_size->h_);
- break;
- }
- case telegram_api::documentAttributeAnimated::ID:
- case telegram_api::documentAttributeHasStickers::ID:
- case telegram_api::documentAttributeSticker::ID:
- case telegram_api::documentAttributeVideo::ID:
- case telegram_api::documentAttributeAudio::ID:
- LOG(ERROR) << "Unexpected web document attribute " << to_string(attribute);
- break;
- case telegram_api::documentAttributeFilename::ID:
- break;
- default:
- UNREACHABLE();
- }
- }
+bool need_update_dialog_photo(const DialogPhoto &from, const DialogPhoto &to) {
+ return from.small_file_id != to.small_file_id || from.big_file_id != to.big_file_id ||
+ from.has_animation != to.has_animation;
+}
- PhotoSize s;
- s.type = file_type == FileType::Thumbnail ? 't' : 'u';
- s.dimensions = dimensions;
- s.size = size;
- s.file_id = file_id;
- return s;
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogPhoto &dialog_photo) {
+ return string_builder << "<small_file_id = " << dialog_photo.small_file_id
+ << ", big_file_id = " << dialog_photo.big_file_id
+ << ", has_animation = " << dialog_photo.has_animation << ">";
}
-tl_object_ptr<td_api::photoSize> get_photo_size_object(FileManager *file_manager, const PhotoSize *photo_size) {
+static tl_object_ptr<td_api::photoSize> get_photo_size_object(FileManager *file_manager, const PhotoSize *photo_size) {
if (photo_size == nullptr || !photo_size->file_id.is_valid()) {
return nullptr;
}
- return make_tl_object<td_api::photoSize>(
+ return td_api::make_object<td_api::photoSize>(
photo_size->type ? std::string(1, static_cast<char>(photo_size->type))
: std::string(), // TODO replace string type with integer type
- file_manager->get_file_object(photo_size->file_id), photo_size->dimensions.width, photo_size->dimensions.height);
+ file_manager->get_file_object(photo_size->file_id), photo_size->dimensions.width, photo_size->dimensions.height,
+ vector<int32>(photo_size->progressive_sizes));
}
-void sort_photo_sizes(vector<td_api::object_ptr<td_api::photoSize>> &sizes) {
- std::sort(sizes.begin(), sizes.end(), [](const auto &lhs, const auto &rhs) {
+static vector<td_api::object_ptr<td_api::photoSize>> get_photo_sizes_object(FileManager *file_manager,
+ const vector<PhotoSize> &photo_sizes) {
+ auto sizes = transform(photo_sizes, [file_manager](const PhotoSize &photo_size) {
+ return get_photo_size_object(file_manager, &photo_size);
+ });
+ std::stable_sort(sizes.begin(), sizes.end(), [](const auto &lhs, const auto &rhs) {
if (lhs->photo_->expected_size_ != rhs->photo_->expected_size_) {
return lhs->photo_->expected_size_ < rhs->photo_->expected_size_;
}
- return lhs->width_ * lhs->height_ < rhs->width_ * rhs->height_;
+ return static_cast<uint32>(lhs->width_) * static_cast<uint32>(lhs->height_) <
+ static_cast<uint32>(rhs->width_) * static_cast<uint32>(rhs->height_);
});
+ td::remove_if(sizes, [](const auto &size) {
+ return !size->photo_->local_->can_be_downloaded_ && !size->photo_->local_->is_downloading_completed_;
+ });
+ return sizes;
}
-bool operator==(const PhotoSize &lhs, const PhotoSize &rhs) {
- return lhs.type == rhs.type && lhs.dimensions == rhs.dimensions && lhs.size == rhs.size && lhs.file_id == rhs.file_id;
-}
-
-bool operator!=(const PhotoSize &lhs, const PhotoSize &rhs) {
- return !(lhs == rhs);
-}
+static tl_object_ptr<td_api::animatedChatPhoto> get_animated_chat_photo_object(FileManager *file_manager,
+ const AnimationSize *animation_size) {
+ if (animation_size == nullptr || !animation_size->file_id.is_valid()) {
+ return nullptr;
+ }
-StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSize &photo_size) {
- return string_builder << "{type = " << photo_size.type << ", dimensions = " << photo_size.dimensions
- << ", size = " << photo_size.size << ", file_id = " << photo_size.file_id << "}";
+ return td_api::make_object<td_api::animatedChatPhoto>(animation_size->dimensions.width,
+ file_manager->get_file_object(animation_size->file_id),
+ animation_size->main_frame_timestamp);
}
-Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::encryptedFile> &&file,
- tl_object_ptr<secret_api::decryptedMessageMediaPhoto> &&photo, DialogId owner_dialog_id) {
- CHECK(DcId::is_valid(file->dc_id_));
+Photo get_encrypted_file_photo(FileManager *file_manager, unique_ptr<EncryptedFile> &&file,
+ tl_object_ptr<secret_api::decryptedMessageMediaPhoto> &&photo,
+ DialogId owner_dialog_id) {
FileId file_id = file_manager->register_remote(
- FullRemoteFileLocation(FileType::Encrypted, file->id_, file->access_hash_, DcId::internal(file->dc_id_)),
+ FullRemoteFileLocation(FileType::Encrypted, file->id_, file->access_hash_, DcId::create(file->dc_id_), string()),
FileLocationSource::FromServer, owner_dialog_id, photo->size_, 0,
PSTRING() << static_cast<uint64>(file->id_) << ".jpg");
file_manager->set_encryption_key(file_id, FileEncryptionKey{photo->key_.as_slice(), photo->iv_.as_slice()});
Photo res;
+ res.id = 0;
res.date = 0;
if (!photo->thumb_.empty()) {
- res.photos.push_back(get_thumbnail_photo_size(file_manager, std::move(photo->thumb_), owner_dialog_id,
- photo->thumb_w_, photo->thumb_h_));
+ res.photos.push_back(get_secret_thumbnail_photo_size(file_manager, std::move(photo->thumb_), owner_dialog_id,
+ photo->thumb_w_, photo->thumb_h_));
}
PhotoSize s;
s.type = 'i';
- s.dimensions = get_dimensions(photo->w_, photo->h_);
+ s.dimensions = get_dimensions(photo->w_, photo->h_, nullptr);
s.size = photo->size_;
s.file_id = file_id;
res.photos.push_back(s);
@@ -434,43 +274,111 @@ Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::encrypted
return res;
}
+Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::Photo> &&photo, DialogId owner_dialog_id) {
+ if (photo == nullptr || photo->get_id() == telegram_api::photoEmpty::ID) {
+ return Photo();
+ }
+ CHECK(photo->get_id() == telegram_api::photo::ID);
+ return get_photo(file_manager, move_tl_object_as<telegram_api::photo>(photo), owner_dialog_id);
+}
+
Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::photo> &&photo, DialogId owner_dialog_id) {
+ CHECK(photo != nullptr);
Photo res;
res.id = photo->id_;
res.date = photo->date_;
- res.has_stickers = (photo->flags_ & telegram_api::photo::HAS_STICKERS_MASK) != 0;
+ res.has_stickers = photo->has_stickers_;
+ if (res.is_empty()) {
+ LOG(ERROR) << "Receive photo with identifier " << res.id.get();
+ res.id = -3;
+ }
+
+ DcId dc_id = DcId::create(photo->dc_id_);
for (auto &size_ptr : photo->sizes_) {
- res.photos.push_back(get_photo_size(file_manager, FileType::Photo, photo->id_, photo->access_hash_, owner_dialog_id,
- std::move(size_ptr)));
+ auto photo_size = get_photo_size(file_manager, PhotoSizeSource::thumbnail(FileType::Photo, 0), photo->id_,
+ photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id,
+ owner_dialog_id, std::move(size_ptr), PhotoFormat::Jpeg);
+ if (photo_size.get_offset() == 0) {
+ PhotoSize &size = photo_size.get<0>();
+ if (size.type == 0 || size.type == 't' || size.type == 'i' || size.type == 'p' || size.type == 'u' ||
+ size.type == 'v') {
+ LOG(ERROR) << "Skip unallowed photo size " << size;
+ continue;
+ }
+ res.photos.push_back(std::move(size));
+ } else {
+ res.minithumbnail = std::move(photo_size.get<1>());
+ }
+ }
+
+ for (auto &size_ptr : photo->video_sizes_) {
+ auto animation = get_animation_size(file_manager, PhotoSizeSource::thumbnail(FileType::Photo, 0), photo->id_,
+ photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id,
+ owner_dialog_id, std::move(size_ptr));
+ if (animation.type != 0 && animation.dimensions.width == animation.dimensions.height) {
+ res.animations.push_back(std::move(animation));
+ }
}
return res;
}
-tl_object_ptr<td_api::photo> get_photo_object(FileManager *file_manager, const Photo *photo) {
- if (photo == nullptr || photo->id == -2) {
+Photo get_web_document_photo(FileManager *file_manager, tl_object_ptr<telegram_api::WebDocument> web_document,
+ DialogId owner_dialog_id) {
+ PhotoSize s = get_web_document_photo_size(file_manager, FileType::Photo, owner_dialog_id, std::move(web_document));
+ Photo photo;
+ if (s.file_id.is_valid() && s.type != 'v' && s.type != 'g') {
+ photo.id = 0;
+ photo.photos.push_back(s);
+ }
+ return photo;
+}
+
+tl_object_ptr<td_api::photo> get_photo_object(FileManager *file_manager, const Photo &photo) {
+ if (photo.is_empty()) {
+ return nullptr;
+ }
+
+ return td_api::make_object<td_api::photo>(photo.has_stickers, get_minithumbnail_object(photo.minithumbnail),
+ get_photo_sizes_object(file_manager, photo.photos));
+}
+
+tl_object_ptr<td_api::chatPhoto> get_chat_photo_object(FileManager *file_manager, const Photo &photo) {
+ if (photo.is_empty()) {
return nullptr;
}
- vector<td_api::object_ptr<td_api::photoSize>> photos;
- for (auto &photo_size : photo->photos) {
- photos.push_back(get_photo_size_object(file_manager, &photo_size));
+ const AnimationSize *small_animation = nullptr;
+ const AnimationSize *big_animation = nullptr;
+ for (auto &animation : photo.animations) {
+ if (animation.type == 'p') {
+ small_animation = &animation;
+ } else if (animation.type == 'u') {
+ big_animation = &animation;
+ }
}
- sort_photo_sizes(photos);
- return make_tl_object<td_api::photo>(photo->id, photo->has_stickers, std::move(photos));
+ if (big_animation == nullptr && small_animation != nullptr) {
+ LOG(ERROR) << "Have small animation without big animation in " << photo;
+ small_animation = nullptr;
+ }
+ return td_api::make_object<td_api::chatPhoto>(
+ photo.id.get(), photo.date, get_minithumbnail_object(photo.minithumbnail),
+ get_photo_sizes_object(file_manager, photo.photos), get_animated_chat_photo_object(file_manager, big_animation),
+ get_animated_chat_photo_object(file_manager, small_animation));
}
void photo_delete_thumbnail(Photo &photo) {
for (size_t i = 0; i < photo.photos.size(); i++) {
if (photo.photos[i].type == 't') {
photo.photos.erase(photo.photos.begin() + i);
+ return;
}
}
}
-bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool is_secret) {
+bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool is_secret, bool is_bot) {
if (photo.photos.empty() || photo.photos.back().type != 'i') {
LOG(ERROR) << "Wrong photo: " << photo;
return false;
@@ -478,7 +386,7 @@ bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool i
auto file_id = photo.photos.back().file_id;
auto file_view = file_manager->get_file_view(file_id);
if (is_secret) {
- if (file_view.encryption_key().empty() || !file_view.has_remote_location()) {
+ if (!file_view.is_encrypted_secret() || !file_view.has_remote_location()) {
return false;
}
@@ -493,7 +401,10 @@ bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool i
if (file_view.is_encrypted()) {
return false;
}
- return file_view.has_remote_location() || file_view.has_url();
+ if (is_bot && file_view.has_remote_location()) {
+ return true;
+ }
+ return /* file_view.has_remote_location() || */ file_view.has_url();
}
}
@@ -506,22 +417,25 @@ tl_object_ptr<telegram_api::InputMedia> photo_get_input_media(FileManager *file_
if (file_view.is_encrypted()) {
return nullptr;
}
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
+ if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
int32 flags = 0;
if (ttl != 0) {
flags |= telegram_api::inputMediaPhoto::TTL_SECONDS_MASK;
}
- return make_tl_object<telegram_api::inputMediaPhoto>(flags, file_view.remote_location().as_input_photo(), ttl);
+ return make_tl_object<telegram_api::inputMediaPhoto>(flags, file_view.main_remote_location().as_input_photo(),
+ ttl);
}
if (file_view.has_url()) {
int32 flags = 0;
if (ttl != 0) {
flags |= telegram_api::inputMediaPhotoExternal::TTL_SECONDS_MASK;
}
- LOG(INFO) << "Create inputMediaPhotoExternal with a URL " << file_view.url() << " and ttl " << ttl;
+ LOG(INFO) << "Create inputMediaPhotoExternal with a URL " << file_view.url() << " and TTL " << ttl;
return make_tl_object<telegram_api::inputMediaPhotoExternal>(flags, file_view.url(), ttl);
}
- CHECK(!file_view.has_remote_location());
+ if (input_file == nullptr) {
+ CHECK(!file_view.has_remote_location());
+ }
}
if (input_file != nullptr) {
int32 flags = 0;
@@ -568,12 +482,12 @@ SecretInputMedia photo_get_secret_input_media(FileManager *file_manager, const P
}
auto file_view = file_manager->get_file_view(file_id);
auto &encryption_key = file_view.encryption_key();
- if (encryption_key.empty()) {
+ if (!file_view.is_encrypted_secret() || encryption_key.empty()) {
return {};
}
if (file_view.has_remote_location()) {
- LOG(INFO) << "HAS REMOTE LOCATION";
- input_file = file_view.remote_location().as_input_encrypted_file();
+ LOG(INFO) << "Photo has remote location";
+ input_file = file_view.main_remote_location().as_input_encrypted_file();
}
if (input_file == nullptr) {
return {};
@@ -581,16 +495,55 @@ SecretInputMedia photo_get_secret_input_media(FileManager *file_manager, const P
if (thumbnail_file_id.is_valid() && thumbnail.empty()) {
return {};
}
+ auto size = file_view.size();
+ if (size < 0 || size >= 1000000000) {
+ size = 0;
+ }
return SecretInputMedia{
std::move(input_file),
make_tl_object<secret_api::decryptedMessageMediaPhoto>(
- std::move(thumbnail), thumbnail_width, thumbnail_height, width, height, narrow_cast<int32>(file_view.size()),
+ std::move(thumbnail), thumbnail_width, thumbnail_height, width, height, static_cast<int32>(size),
BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()), caption)};
}
+vector<FileId> photo_get_file_ids(const Photo &photo) {
+ auto result = transform(photo.photos, [](auto &size) { return size.file_id; });
+ if (!photo.animations.empty()) {
+ // photo file IDs must be first
+ append(result, transform(photo.animations, [](auto &size) { return size.file_id; }));
+ }
+ return result;
+}
+
+FileId get_photo_upload_file_id(const Photo &photo) {
+ for (auto &size : photo.photos) {
+ if (size.type == 'i') {
+ return size.file_id;
+ }
+ }
+ return FileId();
+}
+
+FileId get_photo_any_file_id(const Photo &photo) {
+ const auto &sizes = photo.photos;
+ if (!sizes.empty()) {
+ return sizes.back().file_id;
+ }
+ return FileId();
+}
+
+FileId get_photo_thumbnail_file_id(const Photo &photo) {
+ for (auto &size : photo.photos) {
+ if (size.type == 't') {
+ return size.file_id;
+ }
+ }
+ return FileId();
+}
+
bool operator==(const Photo &lhs, const Photo &rhs) {
- return lhs.id == rhs.id && lhs.photos == rhs.photos;
+ return lhs.id.get() == rhs.id.get() && lhs.photos == rhs.photos && lhs.animations == rhs.animations;
}
bool operator!=(const Photo &lhs, const Photo &rhs) {
@@ -598,7 +551,64 @@ bool operator!=(const Photo &lhs, const Photo &rhs) {
}
StringBuilder &operator<<(StringBuilder &string_builder, const Photo &photo) {
- return string_builder << "[id = " << photo.id << ", photos = " << format::as_array(photo.photos) << "]";
+ string_builder << "[ID = " << photo.id.get() << ", photos = " << format::as_array(photo.photos);
+ if (!photo.animations.empty()) {
+ string_builder << ", animations = " << format::as_array(photo.animations);
+ }
+ return string_builder << ']';
+}
+
+tl_object_ptr<telegram_api::userProfilePhoto> convert_photo_to_profile_photo(
+ const tl_object_ptr<telegram_api::photo> &photo) {
+ if (photo == nullptr) {
+ return nullptr;
+ }
+
+ bool have_photo_small = false;
+ bool have_photo_big = false;
+ for (auto &size_ptr : photo->sizes_) {
+ switch (size_ptr->get_id()) {
+ case telegram_api::photoSizeEmpty::ID:
+ break;
+ case telegram_api::photoSize::ID: {
+ auto size = static_cast<const telegram_api::photoSize *>(size_ptr.get());
+ if (size->type_ == "a") {
+ have_photo_small = true;
+ } else if (size->type_ == "c") {
+ have_photo_big = true;
+ }
+ break;
+ }
+ case telegram_api::photoCachedSize::ID: {
+ auto size = static_cast<const telegram_api::photoCachedSize *>(size_ptr.get());
+ if (size->type_ == "a") {
+ have_photo_small = true;
+ } else if (size->type_ == "c") {
+ have_photo_big = true;
+ }
+ break;
+ }
+ case telegram_api::photoStrippedSize::ID:
+ break;
+ case telegram_api::photoSizeProgressive::ID: {
+ auto size = static_cast<const telegram_api::photoSizeProgressive *>(size_ptr.get());
+ if (size->type_ == "a") {
+ have_photo_small = true;
+ } else if (size->type_ == "c") {
+ have_photo_big = true;
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+ if (!have_photo_small || !have_photo_big) {
+ return nullptr;
+ }
+ bool has_video = !photo->video_sizes_.empty();
+ return make_tl_object<telegram_api::userProfilePhoto>(0, has_video, photo->id_, BufferSlice(), photo->dc_id_);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Photo.h b/protocols/Telegram/tdlib/td/td/telegram/Photo.h
index f91911fd68..43a22ff5c7 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Photo.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Photo.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,99 +7,111 @@
#pragma once
#include "td/telegram/DialogId.h"
+#include "td/telegram/EncryptedFile.h"
#include "td/telegram/files/FileId.h"
-#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/secret_api.h"
#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/MovableValue.h"
#include "td/utils/StringBuilder.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
namespace td {
class FileManager;
-struct Dimensions {
- uint16 width = 0;
- uint16 height = 0;
-};
-
struct DialogPhoto {
FileId small_file_id;
FileId big_file_id;
+ string minithumbnail;
+ bool has_animation = false;
};
-struct ProfilePhoto : public DialogPhoto {
+struct ProfilePhoto final : public DialogPhoto {
int64 id = 0;
};
-struct PhotoSize {
- int32 type = 0; // TODO remove
- Dimensions dimensions;
- int32 size = 0;
- FileId file_id;
-};
-
struct Photo {
- int64 id = 0;
+ MovableValue<int64, -2> id;
int32 date = 0;
+ string minithumbnail;
vector<PhotoSize> photos;
+ vector<AnimationSize> animations;
+
bool has_stickers = false;
vector<FileId> sticker_file_ids;
-};
-
-Dimensions get_dimensions(int32 width, int32 height);
-bool operator==(const Dimensions &lhs, const Dimensions &rhs);
-bool operator!=(const Dimensions &lhs, const Dimensions &rhs);
-
-StringBuilder &operator<<(StringBuilder &string_builder, const Dimensions &dimensions);
+ bool is_empty() const {
+ return id.get() == -2;
+ }
+
+ bool is_bad() const {
+ if (is_empty()) {
+ return true;
+ }
+ for (auto &photo_size : photos) {
+ if (!photo_size.file_id.is_valid()) {
+ return true;
+ }
+ }
+ return false;
+ }
+};
-ProfilePhoto get_profile_photo(FileManager *file_manager,
+ProfilePhoto get_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash,
tl_object_ptr<telegram_api::UserProfilePhoto> &&profile_photo_ptr);
tl_object_ptr<td_api::profilePhoto> get_profile_photo_object(FileManager *file_manager,
- const ProfilePhoto *profile_photo);
+ const ProfilePhoto &profile_photo);
-bool operator==(const ProfilePhoto &lhs, const ProfilePhoto &rhs);
-bool operator!=(const ProfilePhoto &lhs, const ProfilePhoto &rhs);
+bool need_update_profile_photo(const ProfilePhoto &from, const ProfilePhoto &to);
StringBuilder &operator<<(StringBuilder &string_builder, const ProfilePhoto &profile_photo);
-DialogPhoto get_dialog_photo(FileManager *file_manager, tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr);
-tl_object_ptr<td_api::chatPhoto> get_chat_photo_object(FileManager *file_manager, const DialogPhoto *dialog_photo);
+DialogPhoto get_dialog_photo(FileManager *file_manager, DialogId dialog_id, int64 dialog_access_hash,
+ tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr);
+tl_object_ptr<td_api::chatPhotoInfo> get_chat_photo_info_object(FileManager *file_manager,
+ const DialogPhoto *dialog_photo);
-bool operator==(const DialogPhoto &lhs, const DialogPhoto &rhs);
-bool operator!=(const DialogPhoto &lhs, const DialogPhoto &rhs);
+DialogPhoto as_fake_dialog_photo(const Photo &photo, DialogId dialog_id);
-StringBuilder &operator<<(StringBuilder &string_builder, const DialogPhoto &dialog_photo);
+DialogPhoto as_dialog_photo(FileManager *file_manager, DialogId dialog_id, int64 dialog_access_hash,
+ const Photo &photo);
+
+ProfilePhoto as_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash, const Photo &photo);
-PhotoSize get_thumbnail_photo_size(FileManager *file_manager, BufferSlice bytes, DialogId owner_dialog_id, int32 width,
- int32 height);
-PhotoSize get_photo_size(FileManager *file_manager, FileType file_type, int64 id, int64 access_hash,
- DialogId owner_dialog_id, tl_object_ptr<telegram_api::PhotoSize> &&size_ptr);
-PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_type, DialogId owner_dialog_id,
- tl_object_ptr<telegram_api::WebDocument> web_document_ptr);
-tl_object_ptr<td_api::photoSize> get_photo_size_object(FileManager *file_manager, const PhotoSize *photo_size);
-void sort_photo_sizes(vector<td_api::object_ptr<td_api::photoSize>> &sizes);
+bool is_same_dialog_photo(FileManager *file_manager, DialogId dialog_id, const Photo &photo,
+ const DialogPhoto &dialog_photo);
-bool operator==(const PhotoSize &lhs, const PhotoSize &rhs);
-bool operator!=(const PhotoSize &lhs, const PhotoSize &rhs);
+vector<FileId> dialog_photo_get_file_ids(const DialogPhoto &dialog_photo);
-StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSize &photo_size);
+bool need_update_dialog_photo(const DialogPhoto &from, const DialogPhoto &to);
+StringBuilder &operator<<(StringBuilder &string_builder, const DialogPhoto &dialog_photo);
+
+Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::Photo> &&photo, DialogId owner_dialog_id);
Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::photo> &&photo, DialogId owner_dialog_id);
-Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::encryptedFile> &&file,
- tl_object_ptr<secret_api::decryptedMessageMediaPhoto> &&photo, DialogId owner_dialog_id);
-tl_object_ptr<td_api::photo> get_photo_object(FileManager *file_manager, const Photo *photo);
+Photo get_encrypted_file_photo(FileManager *file_manager, unique_ptr<EncryptedFile> &&file,
+ tl_object_ptr<secret_api::decryptedMessageMediaPhoto> &&photo, DialogId owner_dialog_id);
+Photo get_web_document_photo(FileManager *file_manager, tl_object_ptr<telegram_api::WebDocument> web_document,
+ DialogId owner_dialog_id);
+tl_object_ptr<td_api::photo> get_photo_object(FileManager *file_manager, const Photo &photo);
+tl_object_ptr<td_api::chatPhoto> get_chat_photo_object(FileManager *file_manager, const Photo &photo);
void photo_delete_thumbnail(Photo &photo);
-bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool is_secret);
+bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool is_secret, bool is_bot);
+
+FileId get_photo_upload_file_id(const Photo &photo);
+
+FileId get_photo_any_file_id(const Photo &photo);
+
+FileId get_photo_thumbnail_file_id(const Photo &photo);
SecretInputMedia photo_get_secret_input_media(FileManager *file_manager, const Photo &photo,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
@@ -109,9 +121,14 @@ tl_object_ptr<telegram_api::InputMedia> photo_get_input_media(FileManager *file_
tl_object_ptr<telegram_api::InputFile> input_file,
int32 ttl);
+vector<FileId> photo_get_file_ids(const Photo &photo);
+
bool operator==(const Photo &lhs, const Photo &rhs);
bool operator!=(const Photo &lhs, const Photo &rhs);
StringBuilder &operator<<(StringBuilder &string_builder, const Photo &photo);
+tl_object_ptr<telegram_api::userProfilePhoto> convert_photo_to_profile_photo(
+ const tl_object_ptr<telegram_api::photo> &photo);
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Photo.hpp b/protocols/Telegram/tdlib/td/td/telegram/Photo.hpp
index 0b16d9c3c5..1003e95296 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Photo.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Photo.hpp
@@ -1,43 +1,56 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/Photo.h"
-
#include "td/telegram/files/FileId.hpp"
+#include "td/telegram/Photo.h"
+#include "td/telegram/PhotoSize.hpp"
+#include "td/telegram/Version.h"
-#include "td/utils/logging.h"
#include "td/utils/tl_helpers.h"
namespace td {
template <class StorerT>
-void store(Dimensions dimensions, StorerT &storer) {
- store(static_cast<uint32>((static_cast<uint32>(dimensions.width) << 16) | dimensions.height), storer);
-}
-
-template <class ParserT>
-void parse(Dimensions &dimensions, ParserT &parser) {
- uint32 width_height;
- parse(width_height, parser);
- dimensions.width = static_cast<uint16>(width_height >> 16);
- dimensions.height = static_cast<uint16>(width_height & 0xFFFF);
-}
-
-template <class StorerT>
void store(const DialogPhoto &dialog_photo, StorerT &storer) {
- store(dialog_photo.small_file_id, storer);
- store(dialog_photo.big_file_id, storer);
+ bool has_file_ids = dialog_photo.small_file_id.is_valid() || dialog_photo.big_file_id.is_valid();
+ bool has_minithumbnail = !dialog_photo.minithumbnail.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_file_ids);
+ STORE_FLAG(dialog_photo.has_animation);
+ STORE_FLAG(has_minithumbnail);
+ END_STORE_FLAGS();
+ if (has_file_ids) {
+ store(dialog_photo.small_file_id, storer);
+ store(dialog_photo.big_file_id, storer);
+ }
+ if (has_minithumbnail) {
+ store(dialog_photo.minithumbnail, storer);
+ }
}
template <class ParserT>
void parse(DialogPhoto &dialog_photo, ParserT &parser) {
- parse(dialog_photo.small_file_id, parser);
- parse(dialog_photo.big_file_id, parser);
+ bool has_file_ids = true;
+ bool has_minithumbnail = false;
+ if (parser.version() >= static_cast<int32>(Version::AddDialogPhotoHasAnimation)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_file_ids);
+ PARSE_FLAG(dialog_photo.has_animation);
+ PARSE_FLAG(has_minithumbnail);
+ END_PARSE_FLAGS();
+ }
+ if (has_file_ids) {
+ parse(dialog_photo.small_file_id, parser);
+ parse(dialog_photo.big_file_id, parser);
+ }
+ if (has_minithumbnail) {
+ parse(dialog_photo.minithumbnail, parser);
+ }
}
template <class StorerT>
@@ -53,47 +66,51 @@ void parse(ProfilePhoto &profile_photo, ParserT &parser) {
}
template <class StorerT>
-void store(const PhotoSize &photo_size, StorerT &storer) {
- LOG(DEBUG) << "Store photo size " << photo_size;
- store(photo_size.type, storer);
- store(photo_size.dimensions, storer);
- store(photo_size.size, storer);
- store(photo_size.file_id, storer);
-}
-
-template <class ParserT>
-void parse(PhotoSize &photo_size, ParserT &parser) {
- parse(photo_size.type, parser);
- parse(photo_size.dimensions, parser);
- parse(photo_size.size, parser);
- parse(photo_size.file_id, parser);
- LOG(DEBUG) << "Parsed photo size " << photo_size;
-}
-
-template <class StorerT>
void store(const Photo &photo, StorerT &storer) {
+ bool has_minithumbnail = !photo.minithumbnail.empty();
+ bool has_animations = !photo.animations.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(photo.has_stickers);
+ STORE_FLAG(has_minithumbnail);
+ STORE_FLAG(has_animations);
END_STORE_FLAGS();
- store(photo.id, storer);
+ store(photo.id.get(), storer);
store(photo.date, storer);
store(photo.photos, storer);
if (photo.has_stickers) {
store(photo.sticker_file_ids, storer);
}
+ if (has_minithumbnail) {
+ store(photo.minithumbnail, storer);
+ }
+ if (has_animations) {
+ store(photo.animations, storer);
+ }
}
template <class ParserT>
void parse(Photo &photo, ParserT &parser) {
+ bool has_minithumbnail;
+ bool has_animations;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(photo.has_stickers);
+ PARSE_FLAG(has_minithumbnail);
+ PARSE_FLAG(has_animations);
END_PARSE_FLAGS();
- parse(photo.id, parser);
+ int64 id;
+ parse(id, parser);
+ photo.id = id;
parse(photo.date, parser);
parse(photo.photos, parser);
if (photo.has_stickers) {
parse(photo.sticker_file_ids, parser);
}
+ if (has_minithumbnail) {
+ parse(photo.minithumbnail, parser);
+ }
+ if (has_animations) {
+ parse(photo.animations, parser);
+ }
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PhotoFormat.h b/protocols/Telegram/tdlib/td/td/telegram/PhotoFormat.h
new file mode 100644
index 0000000000..0df63d1cc1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PhotoFormat.h
@@ -0,0 +1,15 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+namespace td {
+
+enum class PhotoFormat : int32 { Jpeg, Png, Webp, Gif, Tgs, Mpeg4, Webm };
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PhotoSize.cpp b/protocols/Telegram/tdlib/td/td/telegram/PhotoSize.cpp
new file mode 100644
index 0000000000..d5de4dbbd9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PhotoSize.cpp
@@ -0,0 +1,447 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/PhotoSize.h"
+
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileManager.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/HttpUrl.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+namespace td {
+
+static int32 get_minithumbnail_size(const string &packed) {
+ if (packed.size() < 3) {
+ return 0;
+ }
+ if (packed[0] == '\x01') {
+ return max(static_cast<unsigned char>(packed[1]), static_cast<unsigned char>(packed[2]));
+ }
+ return 0;
+}
+
+bool need_update_dialog_photo_minithumbnail(const string &from, const string &to) {
+ if (from == to) {
+ return false;
+ }
+
+ auto from_size = get_minithumbnail_size(from);
+ auto to_size = get_minithumbnail_size(to);
+ // dialog photo minithumbnail is expected to be 8x8
+ return to_size != 0 && (to_size <= 8 || from_size > 8);
+}
+
+td_api::object_ptr<td_api::minithumbnail> get_minithumbnail_object(const string &packed) {
+ if (packed.size() < 3) {
+ return nullptr;
+ }
+ if (packed[0] == '\x01') {
+ static const string header =
+ base64_decode(
+ "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////"
+ "m8H///"
+ "/6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/"
+ "wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/"
+ "8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0R"
+ "FRkd"
+ "ISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2"
+ "uHi4"
+ "+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/"
+ "8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkN"
+ "ERUZ"
+ "HSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2"
+ "Nna4"
+ "uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=")
+ .move_as_ok();
+ static const string footer = base64_decode("/9k=").move_as_ok();
+ auto result = td_api::make_object<td_api::minithumbnail>();
+ result->height_ = static_cast<unsigned char>(packed[1]);
+ result->width_ = static_cast<unsigned char>(packed[2]);
+ result->data_ = PSTRING() << header.substr(0, 164) << packed[1] << header[165] << packed[2] << header.substr(167)
+ << packed.substr(3) << footer;
+ return result;
+ }
+ return nullptr;
+}
+
+static td_api::object_ptr<td_api::ThumbnailFormat> get_thumbnail_format_object(PhotoFormat format) {
+ switch (format) {
+ case PhotoFormat::Jpeg:
+ return td_api::make_object<td_api::thumbnailFormatJpeg>();
+ case PhotoFormat::Png:
+ return td_api::make_object<td_api::thumbnailFormatPng>();
+ case PhotoFormat::Webp:
+ return td_api::make_object<td_api::thumbnailFormatWebp>();
+ case PhotoFormat::Gif:
+ return td_api::make_object<td_api::thumbnailFormatGif>();
+ case PhotoFormat::Tgs:
+ return td_api::make_object<td_api::thumbnailFormatTgs>();
+ case PhotoFormat::Mpeg4:
+ return td_api::make_object<td_api::thumbnailFormatMpeg4>();
+ case PhotoFormat::Webm:
+ return td_api::make_object<td_api::thumbnailFormatWebm>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+static StringBuilder &operator<<(StringBuilder &string_builder, PhotoFormat format) {
+ switch (format) {
+ case PhotoFormat::Jpeg:
+ return string_builder << "jpg";
+ case PhotoFormat::Png:
+ return string_builder << "png";
+ case PhotoFormat::Webp:
+ return string_builder << "webp";
+ case PhotoFormat::Gif:
+ return string_builder << "gif";
+ case PhotoFormat::Tgs:
+ return string_builder << "tgs";
+ case PhotoFormat::Mpeg4:
+ return string_builder << "mp4";
+ case PhotoFormat::Webm:
+ return string_builder << "webm";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+FileId register_photo_size(FileManager *file_manager, const PhotoSizeSource &source, int64 id, int64 access_hash,
+ string file_reference, DialogId owner_dialog_id, int32 file_size, DcId dc_id,
+ PhotoFormat format) {
+ LOG(DEBUG) << "Receive " << format << " photo " << id << " of type " << source.get_file_type("register_photo_size")
+ << " from " << dc_id;
+ auto suggested_name = PSTRING() << source.get_unique_name(id) << '.' << format;
+ auto file_location_source = owner_dialog_id.get_type() == DialogType::SecretChat ? FileLocationSource::FromUser
+ : FileLocationSource::FromServer;
+ return file_manager->register_remote(
+ FullRemoteFileLocation(source, id, access_hash, dc_id, std::move(file_reference)), file_location_source,
+ owner_dialog_id, file_size, 0, std::move(suggested_name));
+}
+
+PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice bytes, DialogId owner_dialog_id,
+ int32 width, int32 height) {
+ if (bytes.empty()) {
+ return PhotoSize();
+ }
+ PhotoSize res;
+ res.type = 't';
+ res.dimensions = get_dimensions(width, height, nullptr);
+ res.size = narrow_cast<int32>(bytes.size());
+
+ // generate some random remote location to save
+ auto dc_id = DcId::invalid();
+ auto photo_id = -(Random::secure_int64() & std::numeric_limits<int64>::max());
+
+ res.file_id = file_manager->register_remote(
+ FullRemoteFileLocation(PhotoSizeSource::thumbnail(FileType::EncryptedThumbnail, 't'), photo_id, 0, dc_id,
+ string()),
+ FileLocationSource::FromServer, owner_dialog_id, res.size, 0,
+ PSTRING() << static_cast<uint64>(photo_id) << ".jpg");
+ file_manager->set_content(res.file_id, std::move(bytes));
+
+ return res;
+}
+
+Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSource source, int64 id,
+ int64 access_hash, std::string file_reference, DcId dc_id,
+ DialogId owner_dialog_id, tl_object_ptr<telegram_api::PhotoSize> &&size_ptr,
+ PhotoFormat format) {
+ CHECK(size_ptr != nullptr);
+
+ string type;
+ PhotoSize res;
+ BufferSlice content;
+ switch (size_ptr->get_id()) {
+ case telegram_api::photoSizeEmpty::ID:
+ return std::move(res);
+ case telegram_api::photoSize::ID: {
+ auto size = move_tl_object_as<telegram_api::photoSize>(size_ptr);
+
+ type = std::move(size->type_);
+ res.dimensions = get_dimensions(size->w_, size->h_, "photoSize");
+ res.size = size->size_;
+
+ break;
+ }
+ case telegram_api::photoCachedSize::ID: {
+ auto size = move_tl_object_as<telegram_api::photoCachedSize>(size_ptr);
+
+ type = std::move(size->type_);
+ CHECK(size->bytes_.size() <= static_cast<size_t>(std::numeric_limits<int32>::max()));
+ res.dimensions = get_dimensions(size->w_, size->h_, "photoCachedSize");
+ res.size = static_cast<int32>(size->bytes_.size());
+
+ content = std::move(size->bytes_);
+
+ break;
+ }
+ case telegram_api::photoStrippedSize::ID: {
+ auto size = move_tl_object_as<telegram_api::photoStrippedSize>(size_ptr);
+ if (format != PhotoFormat::Jpeg) {
+ LOG(ERROR) << "Receive unexpected JPEG minithumbnail in photo " << id << " from " << source << " of format "
+ << format;
+ return std::move(res);
+ }
+ return size->bytes_.as_slice().str();
+ }
+ case telegram_api::photoSizeProgressive::ID: {
+ auto size = move_tl_object_as<telegram_api::photoSizeProgressive>(size_ptr);
+
+ if (size->sizes_.empty()) {
+ LOG(ERROR) << "Receive photo " << id << " from " << source << " with empty size " << to_string(size);
+ return std::move(res);
+ }
+ std::sort(size->sizes_.begin(), size->sizes_.end());
+
+ type = std::move(size->type_);
+ res.dimensions = get_dimensions(size->w_, size->h_, "photoSizeProgressive");
+ res.size = size->sizes_.back();
+ size->sizes_.pop_back();
+ res.progressive_sizes = std::move(size->sizes_);
+
+ break;
+ }
+ case telegram_api::photoPathSize::ID: {
+ auto size = move_tl_object_as<telegram_api::photoPathSize>(size_ptr);
+ if (format != PhotoFormat::Tgs && format != PhotoFormat::Webp && format != PhotoFormat::Webm) {
+ LOG(ERROR) << "Receive unexpected SVG minithumbnail in photo " << id << " from " << source << " of format "
+ << format;
+ return std::move(res);
+ }
+ return size->bytes_.as_slice().str();
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+
+ if (type.size() != 1) {
+ LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res;
+ res.type = 0;
+ } else {
+ res.type = static_cast<uint8>(type[0]);
+ if (res.type >= 128) {
+ LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res;
+ res.type = 0;
+ }
+ }
+ if (source.get_type("get_photo_size") == PhotoSizeSource::Type::Thumbnail) {
+ source.thumbnail().thumbnail_type = res.type;
+ }
+ if (res.size < 0 || res.size > 1000000000) {
+ LOG(ERROR) << "Receive photo of size " << res.size;
+ res.size = 0;
+ }
+
+ res.file_id = register_photo_size(file_manager, source, id, access_hash, std::move(file_reference), owner_dialog_id,
+ res.size, dc_id, format);
+
+ if (!content.empty()) {
+ file_manager->set_content(res.file_id, std::move(content));
+ }
+
+ return std::move(res);
+}
+
+AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource source, int64 id, int64 access_hash,
+ std::string file_reference, DcId dc_id, DialogId owner_dialog_id,
+ tl_object_ptr<telegram_api::videoSize> &&size) {
+ CHECK(size != nullptr);
+ AnimationSize res;
+ if (size->type_ != "p" && size->type_ != "u" && size->type_ != "v") {
+ LOG(ERROR) << "Unsupported videoSize \"" << size->type_ << "\" in " << to_string(size);
+ }
+ res.type = static_cast<uint8>(size->type_[0]);
+ if (res.type >= 128) {
+ LOG(ERROR) << "Wrong videoSize \"" << res.type << "\" " << res;
+ res.type = 0;
+ }
+ res.dimensions = get_dimensions(size->w_, size->h_, "get_animation_size");
+ res.size = size->size_;
+ if ((size->flags_ & telegram_api::videoSize::VIDEO_START_TS_MASK) != 0) {
+ res.main_frame_timestamp = size->video_start_ts_;
+ }
+
+ if (source.get_type("get_animation_size") == PhotoSizeSource::Type::Thumbnail) {
+ source.thumbnail().thumbnail_type = res.type;
+ }
+ if (res.size < 0 || res.size > 1000000000) {
+ LOG(ERROR) << "Receive animation of size " << res.size;
+ res.size = 0;
+ }
+
+ res.file_id = register_photo_size(file_manager, source, id, access_hash, std::move(file_reference), owner_dialog_id,
+ res.size, dc_id, PhotoFormat::Mpeg4);
+ return res;
+}
+
+PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_type, DialogId owner_dialog_id,
+ tl_object_ptr<telegram_api::WebDocument> web_document_ptr) {
+ if (web_document_ptr == nullptr) {
+ return {};
+ }
+
+ FileId file_id;
+ vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
+ int32 size = 0;
+ string mime_type;
+ switch (web_document_ptr->get_id()) {
+ case telegram_api::webDocument::ID: {
+ auto web_document = move_tl_object_as<telegram_api::webDocument>(web_document_ptr);
+ auto r_http_url = parse_url(web_document->url_);
+ if (r_http_url.is_error()) {
+ LOG(ERROR) << "Can't parse URL " << web_document->url_;
+ return {};
+ }
+ auto http_url = r_http_url.move_as_ok();
+ auto url = http_url.get_url();
+ file_id = file_manager->register_remote(
+ FullRemoteFileLocation(file_type, url, web_document->access_hash_), FileLocationSource::FromServer,
+ owner_dialog_id, 0, static_cast<uint32>(web_document->size_), get_url_query_file_name(http_url.query_));
+ size = web_document->size_;
+ mime_type = std::move(web_document->mime_type_);
+ attributes = std::move(web_document->attributes_);
+ break;
+ }
+ case telegram_api::webDocumentNoProxy::ID: {
+ auto web_document = move_tl_object_as<telegram_api::webDocumentNoProxy>(web_document_ptr);
+ if (web_document->url_.find('.') == string::npos) {
+ LOG(ERROR) << "Receive invalid URL " << web_document->url_;
+ return {};
+ }
+
+ auto r_file_id = file_manager->from_persistent_id(web_document->url_, file_type);
+ if (r_file_id.is_error()) {
+ LOG(ERROR) << "Can't register URL: " << r_file_id.error();
+ return {};
+ }
+ file_id = r_file_id.move_as_ok();
+
+ size = web_document->size_;
+ mime_type = std::move(web_document->mime_type_);
+ attributes = std::move(web_document->attributes_);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ CHECK(file_id.is_valid());
+ bool is_animation = mime_type == "video/mp4";
+ bool is_gif = mime_type == "image/gif";
+
+ Dimensions dimensions;
+ for (auto &attribute : attributes) {
+ switch (attribute->get_id()) {
+ case telegram_api::documentAttributeImageSize::ID: {
+ auto image_size = move_tl_object_as<telegram_api::documentAttributeImageSize>(attribute);
+ dimensions = get_dimensions(image_size->w_, image_size->h_, "web documentAttributeImageSize");
+ break;
+ }
+ case telegram_api::documentAttributeAnimated::ID:
+ case telegram_api::documentAttributeHasStickers::ID:
+ case telegram_api::documentAttributeSticker::ID:
+ case telegram_api::documentAttributeVideo::ID:
+ case telegram_api::documentAttributeAudio::ID:
+ case telegram_api::documentAttributeCustomEmoji::ID:
+ LOG(ERROR) << "Unexpected web document attribute " << to_string(attribute);
+ break;
+ case telegram_api::documentAttributeFilename::ID:
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ PhotoSize s;
+ s.type = is_animation ? 'v' : (is_gif ? 'g' : (file_type == FileType::Thumbnail ? 't' : 'n'));
+ s.dimensions = dimensions;
+ s.size = size;
+ s.file_id = file_id;
+
+ if (s.size < 0 || s.size > 1000000000) {
+ LOG(ERROR) << "Receive web photo of size " << s.size;
+ s.size = 0;
+ }
+ return s;
+}
+
+td_api::object_ptr<td_api::thumbnail> get_thumbnail_object(FileManager *file_manager, const PhotoSize &photo_size,
+ PhotoFormat format) {
+ if (!photo_size.file_id.is_valid()) {
+ return nullptr;
+ }
+
+ if (format == PhotoFormat::Jpeg && photo_size.type == 'g') {
+ format = PhotoFormat::Gif;
+ }
+
+ return td_api::make_object<td_api::thumbnail>(get_thumbnail_format_object(format), photo_size.dimensions.width,
+ photo_size.dimensions.height,
+ file_manager->get_file_object(photo_size.file_id));
+}
+
+bool operator==(const PhotoSize &lhs, const PhotoSize &rhs) {
+ return lhs.type == rhs.type && lhs.dimensions == rhs.dimensions && lhs.size == rhs.size &&
+ lhs.file_id == rhs.file_id && lhs.progressive_sizes == rhs.progressive_sizes;
+}
+
+bool operator!=(const PhotoSize &lhs, const PhotoSize &rhs) {
+ return !(lhs == rhs);
+}
+
+bool operator<(const PhotoSize &lhs, const PhotoSize &rhs) {
+ if (lhs.size != rhs.size) {
+ return lhs.size < rhs.size;
+ }
+ auto lhs_pixels = get_dimensions_pixel_count(lhs.dimensions);
+ auto rhs_pixels = get_dimensions_pixel_count(rhs.dimensions);
+ if (lhs_pixels != rhs_pixels) {
+ return lhs_pixels < rhs_pixels;
+ }
+ int32 lhs_type = lhs.type == 't' ? -1 : lhs.type;
+ int32 rhs_type = rhs.type == 't' ? -1 : rhs.type;
+ if (lhs_type != rhs_type) {
+ return lhs_type < rhs_type;
+ }
+ if (lhs.file_id != rhs.file_id) {
+ return lhs.file_id.get() < rhs.file_id.get();
+ }
+ return lhs.dimensions.width < rhs.dimensions.width;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSize &photo_size) {
+ return string_builder << "{type = " << photo_size.type << ", dimensions = " << photo_size.dimensions
+ << ", size = " << photo_size.size << ", file_id = " << photo_size.file_id
+ << ", progressive_sizes = " << photo_size.progressive_sizes << "}";
+}
+
+bool operator==(const AnimationSize &lhs, const AnimationSize &rhs) {
+ return static_cast<const PhotoSize &>(lhs) == static_cast<const PhotoSize &>(rhs) &&
+ fabs(lhs.main_frame_timestamp - rhs.main_frame_timestamp) < 1e-3;
+}
+
+bool operator!=(const AnimationSize &lhs, const AnimationSize &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const AnimationSize &animation_size) {
+ return string_builder << static_cast<const PhotoSize &>(animation_size) << " from "
+ << animation_size.main_frame_timestamp;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PhotoSize.h b/protocols/Telegram/tdlib/td/td/telegram/PhotoSize.h
new file mode 100644
index 0000000000..bf64f474d8
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PhotoSize.h
@@ -0,0 +1,78 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/PhotoSizeSource.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/Variant.h"
+
+namespace td {
+
+class FileManager;
+
+struct PhotoSize {
+ int32 type = 0;
+ int32 size = 0;
+ Dimensions dimensions;
+ FileId file_id;
+ vector<int32> progressive_sizes;
+};
+
+struct AnimationSize final : public PhotoSize {
+ double main_frame_timestamp = 0.0;
+};
+
+bool need_update_dialog_photo_minithumbnail(const string &from, const string &to);
+
+td_api::object_ptr<td_api::minithumbnail> get_minithumbnail_object(const string &packed);
+
+FileId register_photo_size(FileManager *file_manager, const PhotoSizeSource &source, int64 id, int64 access_hash,
+ string file_reference, DialogId owner_dialog_id, int32 file_size, DcId dc_id,
+ PhotoFormat format);
+
+PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice bytes, DialogId owner_dialog_id,
+ int32 width, int32 height);
+
+Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSource source, int64 id,
+ int64 access_hash, string file_reference, DcId dc_id,
+ DialogId owner_dialog_id, tl_object_ptr<telegram_api::PhotoSize> &&size_ptr,
+ PhotoFormat format);
+
+AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource source, int64 id, int64 access_hash,
+ string file_reference, DcId dc_id, DialogId owner_dialog_id,
+ tl_object_ptr<telegram_api::videoSize> &&size);
+
+PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_type, DialogId owner_dialog_id,
+ tl_object_ptr<telegram_api::WebDocument> web_document_ptr);
+
+td_api::object_ptr<td_api::thumbnail> get_thumbnail_object(FileManager *file_manager, const PhotoSize &photo_size,
+ PhotoFormat format);
+
+bool operator==(const PhotoSize &lhs, const PhotoSize &rhs);
+bool operator!=(const PhotoSize &lhs, const PhotoSize &rhs);
+
+bool operator<(const PhotoSize &lhs, const PhotoSize &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSize &photo_size);
+
+bool operator==(const AnimationSize &lhs, const AnimationSize &rhs);
+bool operator!=(const AnimationSize &lhs, const AnimationSize &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const AnimationSize &animation_size);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PhotoSize.hpp b/protocols/Telegram/tdlib/td/td/telegram/PhotoSize.hpp
new file mode 100644
index 0000000000..0104a8f0d3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PhotoSize.hpp
@@ -0,0 +1,60 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/Dimensions.hpp"
+#include "td/telegram/files/FileId.hpp"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/Version.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const PhotoSize &photo_size, StorerT &storer) {
+ store(photo_size.type, storer);
+ store(photo_size.dimensions, storer);
+ store(photo_size.size, storer);
+ store(photo_size.file_id, storer);
+ store(photo_size.progressive_sizes, storer);
+}
+
+template <class ParserT>
+void parse(PhotoSize &photo_size, ParserT &parser) {
+ parse(photo_size.type, parser);
+ parse(photo_size.dimensions, parser);
+ parse(photo_size.size, parser);
+ parse(photo_size.file_id, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddPhotoProgressiveSizes)) {
+ parse(photo_size.progressive_sizes, parser);
+ } else {
+ photo_size.progressive_sizes.clear();
+ }
+ if (photo_size.type < 0 || photo_size.type >= 128) {
+ parser.set_error("Wrong PhotoSize type");
+ return;
+ }
+}
+
+template <class StorerT>
+void store(const AnimationSize &animation_size, StorerT &storer) {
+ store(static_cast<const PhotoSize &>(animation_size), storer);
+ store(animation_size.main_frame_timestamp, storer);
+}
+
+template <class ParserT>
+void parse(AnimationSize &animation_size, ParserT &parser) {
+ parse(static_cast<PhotoSize &>(animation_size), parser);
+ if (parser.version() >= static_cast<int32>(Version::AddDialogPhotoHasAnimation)) {
+ parse(animation_size.main_frame_timestamp, parser);
+ } else {
+ animation_size.main_frame_timestamp = 0;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.cpp b/protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.cpp
new file mode 100644
index 0000000000..f2c2b3e2b7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.cpp
@@ -0,0 +1,261 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/PhotoSizeSource.h"
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StackAllocator.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/tl_storers.h"
+
+namespace td {
+
+tl_object_ptr<telegram_api::InputPeer> PhotoSizeSource::DialogPhoto::get_input_peer() const {
+ switch (dialog_id.get_type()) {
+ case DialogType::User: {
+ UserId user_id = dialog_id.get_user_id();
+ return make_tl_object<telegram_api::inputPeerUser>(user_id.get(), dialog_access_hash);
+ }
+ case DialogType::Chat: {
+ ChatId chat_id = dialog_id.get_chat_id();
+ return make_tl_object<telegram_api::inputPeerChat>(chat_id.get());
+ }
+ case DialogType::Channel: {
+ ChannelId channel_id = dialog_id.get_channel_id();
+ return make_tl_object<telegram_api::inputPeerChannel>(channel_id.get(), dialog_access_hash);
+ }
+ case DialogType::SecretChat:
+ return nullptr;
+ case DialogType::None:
+ return make_tl_object<telegram_api::inputPeerEmpty>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+FileType PhotoSizeSource::get_file_type(const char *source) const {
+ switch (get_type(source)) {
+ case Type::Thumbnail:
+ return thumbnail().file_type;
+ case Type::DialogPhotoSmall:
+ case Type::DialogPhotoBig:
+ case Type::DialogPhotoSmallLegacy:
+ case Type::DialogPhotoBigLegacy:
+ return FileType::ProfilePhoto;
+ case Type::StickerSetThumbnail:
+ case Type::StickerSetThumbnailLegacy:
+ case Type::StickerSetThumbnailVersion:
+ return FileType::Thumbnail;
+ case Type::Legacy:
+ case Type::FullLegacy:
+ default:
+ UNREACHABLE();
+ return FileType::Thumbnail;
+ }
+}
+
+string PhotoSizeSource::get_unique() const {
+ auto ptr = StackAllocator::alloc(16);
+ MutableSlice data = ptr.as_slice();
+ TlStorerUnsafe storer(data.ubegin());
+ switch (get_type("get_unique")) {
+ case Type::Legacy:
+ UNREACHABLE();
+ break;
+ case Type::Thumbnail: {
+ auto type = thumbnail().thumbnail_type;
+ CHECK(0 <= type && type <= 127);
+ if (type == 'a') {
+ type = 0;
+ } else if (type == 'c') {
+ type = 1;
+ } else {
+ type += 5;
+ }
+ return string(1, static_cast<char>(type));
+ }
+ case Type::DialogPhotoSmall:
+ // it doesn't matter to which Dialog the photo belongs
+ return string(1, '\x00');
+ case Type::DialogPhotoBig:
+ // it doesn't matter to which Dialog the photo belongs
+ return string(1, '\x01');
+ case Type::StickerSetThumbnail:
+ UNREACHABLE();
+ break;
+ case Type::FullLegacy: {
+ auto &legacy = full_legacy();
+ td::store(legacy.volume_id, storer);
+ td::store(legacy.local_id, storer);
+ break;
+ }
+ case Type::DialogPhotoSmallLegacy:
+ case Type::DialogPhotoBigLegacy: {
+ auto &legacy = dialog_photo_legacy();
+ td::store(legacy.volume_id, storer);
+ td::store(legacy.local_id, storer);
+ break;
+ }
+ case Type::StickerSetThumbnailLegacy: {
+ auto &legacy = sticker_set_thumbnail_legacy();
+ td::store(legacy.volume_id, storer);
+ td::store(legacy.local_id, storer);
+ break;
+ }
+ case Type::StickerSetThumbnailVersion: {
+ auto &thumbnail = sticker_set_thumbnail_version();
+ storer.store_slice(Slice("\x02"));
+ td::store(thumbnail.sticker_set_id, storer);
+ td::store(thumbnail.version, storer);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+ auto size = storer.get_buf() - data.ubegin();
+ CHECK(size <= 13);
+ return string(data.begin(), size);
+}
+
+string PhotoSizeSource::get_unique_name(int64 photo_id) const {
+ switch (get_type("get_unique_name")) {
+ case Type::Thumbnail:
+ CHECK(0 <= thumbnail().thumbnail_type && thumbnail().thumbnail_type <= 127);
+ return PSTRING() << photo_id << '_' << thumbnail().thumbnail_type;
+ case Type::DialogPhotoSmall:
+ return to_string(photo_id);
+ case Type::DialogPhotoBig:
+ return PSTRING() << photo_id << '_' << 1;
+ case Type::StickerSetThumbnailVersion:
+ return PSTRING() << sticker_set_thumbnail_version().sticker_set_id << '_'
+ << static_cast<uint32>(sticker_set_thumbnail_version().version);
+ case Type::Legacy:
+ case Type::StickerSetThumbnail:
+ case Type::FullLegacy:
+ case Type::DialogPhotoSmallLegacy:
+ case Type::DialogPhotoBigLegacy:
+ case Type::StickerSetThumbnailLegacy:
+ default:
+ UNREACHABLE();
+ break;
+ }
+ return string();
+}
+
+static bool operator==(const PhotoSizeSource::Legacy &lhs, const PhotoSizeSource::Legacy &rhs) {
+ UNREACHABLE();
+ return false;
+}
+
+static bool operator==(const PhotoSizeSource::Thumbnail &lhs, const PhotoSizeSource::Thumbnail &rhs) {
+ return lhs.file_type == rhs.file_type && lhs.thumbnail_type == rhs.thumbnail_type;
+}
+
+static bool operator==(const PhotoSizeSource::DialogPhoto &lhs, const PhotoSizeSource::DialogPhoto &rhs) {
+ return lhs.dialog_id == rhs.dialog_id && lhs.dialog_access_hash == rhs.dialog_access_hash;
+}
+
+static bool operator==(const PhotoSizeSource::DialogPhotoSmall &lhs, const PhotoSizeSource::DialogPhotoSmall &rhs) {
+ return static_cast<const PhotoSizeSource::DialogPhoto &>(lhs) ==
+ static_cast<const PhotoSizeSource::DialogPhoto &>(rhs);
+}
+
+static bool operator==(const PhotoSizeSource::DialogPhotoBig &lhs, const PhotoSizeSource::DialogPhotoBig &rhs) {
+ return static_cast<const PhotoSizeSource::DialogPhoto &>(lhs) ==
+ static_cast<const PhotoSizeSource::DialogPhoto &>(rhs);
+}
+
+static bool operator==(const PhotoSizeSource::StickerSetThumbnail &lhs,
+ const PhotoSizeSource::StickerSetThumbnail &rhs) {
+ return lhs.sticker_set_id == rhs.sticker_set_id && lhs.sticker_set_access_hash == rhs.sticker_set_access_hash;
+}
+
+static bool operator==(const PhotoSizeSource::FullLegacy &lhs, const PhotoSizeSource::FullLegacy &rhs) {
+ return lhs.volume_id == rhs.volume_id && lhs.local_id == rhs.local_id && lhs.secret == rhs.secret;
+}
+
+static bool operator==(const PhotoSizeSource::DialogPhotoLegacy &lhs, const PhotoSizeSource::DialogPhotoLegacy &rhs) {
+ return static_cast<const PhotoSizeSource::DialogPhoto &>(lhs) ==
+ static_cast<const PhotoSizeSource::DialogPhoto &>(rhs) &&
+ lhs.volume_id == rhs.volume_id && lhs.local_id == rhs.local_id;
+}
+
+static bool operator==(const PhotoSizeSource::DialogPhotoSmallLegacy &lhs,
+ const PhotoSizeSource::DialogPhotoSmallLegacy &rhs) {
+ return static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(lhs) ==
+ static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(rhs);
+}
+
+static bool operator==(const PhotoSizeSource::DialogPhotoBigLegacy &lhs,
+ const PhotoSizeSource::DialogPhotoBigLegacy &rhs) {
+ return static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(lhs) ==
+ static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(rhs);
+}
+
+static bool operator==(const PhotoSizeSource::StickerSetThumbnailLegacy &lhs,
+ const PhotoSizeSource::StickerSetThumbnailLegacy &rhs) {
+ return static_cast<const PhotoSizeSource::StickerSetThumbnail &>(lhs) ==
+ static_cast<const PhotoSizeSource::StickerSetThumbnail &>(rhs) &&
+ lhs.volume_id == rhs.volume_id && lhs.local_id == rhs.local_id;
+}
+
+static bool operator==(const PhotoSizeSource::StickerSetThumbnailVersion &lhs,
+ const PhotoSizeSource::StickerSetThumbnailVersion &rhs) {
+ return static_cast<const PhotoSizeSource::StickerSetThumbnail &>(lhs) ==
+ static_cast<const PhotoSizeSource::StickerSetThumbnail &>(rhs) &&
+ lhs.version == rhs.version;
+}
+
+bool operator==(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs) {
+ return lhs.variant_ == rhs.variant_;
+}
+
+bool operator!=(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSizeSource &source) {
+ switch (source.get_type("operator<<")) {
+ case PhotoSizeSource::Type::Legacy:
+ return string_builder << "PhotoSizeSourceLegacy[]";
+ case PhotoSizeSource::Type::Thumbnail:
+ return string_builder << "PhotoSizeSourceThumbnail[" << source.thumbnail().file_type
+ << ", type = " << source.thumbnail().thumbnail_type << ']';
+ case PhotoSizeSource::Type::DialogPhotoSmall:
+ return string_builder << "PhotoSizeSourceChatPhotoSmall[" << source.dialog_photo().dialog_id << ']';
+ case PhotoSizeSource::Type::DialogPhotoBig:
+ return string_builder << "PhotoSizeSourceChatPhotoBig[" << source.dialog_photo().dialog_id << ']';
+ case PhotoSizeSource::Type::StickerSetThumbnail:
+ return string_builder << "PhotoSizeSourceStickerSetThumbnail[" << source.sticker_set_thumbnail().sticker_set_id
+ << ']';
+ case PhotoSizeSource::Type::FullLegacy:
+ return string_builder << "PhotoSizeSourceFullLegacy[]";
+ case PhotoSizeSource::Type::DialogPhotoSmallLegacy:
+ return string_builder << "PhotoSizeSourceChatPhotoSmallLegacy[" << source.dialog_photo().dialog_id << ']';
+ case PhotoSizeSource::Type::DialogPhotoBigLegacy:
+ return string_builder << "PhotoSizeSourceChatPhotoBigLegacy[" << source.dialog_photo().dialog_id << ']';
+ case PhotoSizeSource::Type::StickerSetThumbnailLegacy:
+ return string_builder << "PhotoSizeSourceStickerSetThumbnailLegacy["
+ << source.sticker_set_thumbnail().sticker_set_id << ']';
+ case PhotoSizeSource::Type::StickerSetThumbnailVersion:
+ return string_builder << "PhotoSizeSourceStickerSetThumbnailVersion["
+ << source.sticker_set_thumbnail().sticker_set_id << '_'
+ << source.sticker_set_thumbnail_version().version << ']';
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.h b/protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.h
new file mode 100644
index 0000000000..03a534b743
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.h
@@ -0,0 +1,272 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/Variant.h"
+
+namespace td {
+
+struct PhotoSizeSource {
+ enum class Type : int32 {
+ Legacy,
+ Thumbnail,
+ DialogPhotoSmall,
+ DialogPhotoBig,
+ StickerSetThumbnail,
+ FullLegacy,
+ DialogPhotoSmallLegacy,
+ DialogPhotoBigLegacy,
+ StickerSetThumbnailLegacy,
+ StickerSetThumbnailVersion
+ };
+
+ // for legacy photos with secret
+ struct Legacy {
+ Legacy() = default;
+ explicit Legacy(int64 secret) : secret(secret) {
+ }
+
+ int64 secret = 0;
+ };
+
+ // for photos, document thumbnails, encrypted thumbnails
+ struct Thumbnail {
+ Thumbnail() = default;
+ Thumbnail(FileType file_type, int32 thumbnail_type) : file_type(file_type), thumbnail_type(thumbnail_type) {
+ }
+
+ FileType file_type = FileType::None;
+ int32 thumbnail_type = 0;
+ };
+
+ // for dialog photos
+ struct DialogPhoto {
+ DialogPhoto() = default;
+ DialogPhoto(DialogId dialog_id, int64 dialog_access_hash)
+ : dialog_id(dialog_id), dialog_access_hash(dialog_access_hash) {
+ }
+
+ tl_object_ptr<telegram_api::InputPeer> get_input_peer() const;
+
+ DialogId dialog_id;
+ int64 dialog_access_hash = 0;
+ };
+
+ struct DialogPhotoSmall final : public DialogPhoto {
+ using DialogPhoto::DialogPhoto;
+ };
+
+ struct DialogPhotoBig final : public DialogPhoto {
+ using DialogPhoto::DialogPhoto;
+ };
+
+ // for sticker set thumbnails
+ struct StickerSetThumbnail {
+ int64 sticker_set_id = 0;
+ int64 sticker_set_access_hash = 0;
+
+ StickerSetThumbnail() = default;
+ StickerSetThumbnail(int64 sticker_set_id, int64 sticker_set_access_hash)
+ : sticker_set_id(sticker_set_id), sticker_set_access_hash(sticker_set_access_hash) {
+ }
+
+ tl_object_ptr<telegram_api::InputStickerSet> get_input_sticker_set() const {
+ return make_tl_object<telegram_api::inputStickerSetID>(sticker_set_id, sticker_set_access_hash);
+ }
+ };
+
+ // for legacy photos with volume_id, local_id, secret
+ struct FullLegacy {
+ FullLegacy() = default;
+ FullLegacy(int64 volume_id, int32 local_id, int64 secret)
+ : volume_id(volume_id), local_id(local_id), secret(secret) {
+ }
+
+ int64 volume_id = 0;
+ int32 local_id = 0;
+ int64 secret = 0;
+ };
+
+ // for legacy dialog photos
+ struct DialogPhotoLegacy : public DialogPhoto {
+ DialogPhotoLegacy() = default;
+ DialogPhotoLegacy(DialogId dialog_id, int64 dialog_access_hash, int64 volume_id, int32 local_id)
+ : DialogPhoto(dialog_id, dialog_access_hash), volume_id(volume_id), local_id(local_id) {
+ }
+
+ int64 volume_id = 0;
+ int32 local_id = 0;
+ };
+
+ struct DialogPhotoSmallLegacy final : public DialogPhotoLegacy {
+ using DialogPhotoLegacy::DialogPhotoLegacy;
+ };
+
+ struct DialogPhotoBigLegacy final : public DialogPhotoLegacy {
+ using DialogPhotoLegacy::DialogPhotoLegacy;
+ };
+
+ // for legacy sticker set thumbnails
+ struct StickerSetThumbnailLegacy final : public StickerSetThumbnail {
+ StickerSetThumbnailLegacy() = default;
+ StickerSetThumbnailLegacy(int64 sticker_set_id, int64 sticker_set_access_hash, int64 volume_id, int32 local_id)
+ : StickerSetThumbnail(sticker_set_id, sticker_set_access_hash), volume_id(volume_id), local_id(local_id) {
+ }
+
+ int64 volume_id = 0;
+ int32 local_id = 0;
+ };
+
+ // for sticker set thumbnails identified by version
+ struct StickerSetThumbnailVersion final : public StickerSetThumbnail {
+ StickerSetThumbnailVersion() = default;
+ StickerSetThumbnailVersion(int64 sticker_set_id, int64 sticker_set_access_hash, int32 version)
+ : StickerSetThumbnail(sticker_set_id, sticker_set_access_hash), version(version) {
+ }
+
+ int32 version = 0;
+ };
+
+ PhotoSizeSource() = default;
+
+ static PhotoSizeSource thumbnail(FileType file_type, int32 thumbnail_type) {
+ return PhotoSizeSource(Thumbnail(file_type, thumbnail_type));
+ }
+
+ static PhotoSizeSource dialog_photo(DialogId dialog_id, int64 dialog_access_hash, bool is_big) {
+ if (is_big) {
+ return PhotoSizeSource(DialogPhotoBig(dialog_id, dialog_access_hash));
+ } else {
+ return PhotoSizeSource(DialogPhotoSmall(dialog_id, dialog_access_hash));
+ }
+ }
+
+ static PhotoSizeSource sticker_set_thumbnail(int64 sticker_set_id, int64 sticker_set_access_hash) {
+ return PhotoSizeSource(StickerSetThumbnail(sticker_set_id, sticker_set_access_hash));
+ }
+
+ static PhotoSizeSource full_legacy(int64 volume_id, int32 local_id, int64 secret) {
+ return PhotoSizeSource(FullLegacy(volume_id, local_id, secret));
+ }
+
+ static PhotoSizeSource dialog_photo_legacy(DialogId dialog_id, int64 dialog_access_hash, bool is_big, int64 volume_id,
+ int32 local_id) {
+ if (is_big) {
+ return PhotoSizeSource(DialogPhotoBigLegacy(dialog_id, dialog_access_hash, volume_id, local_id));
+ } else {
+ return PhotoSizeSource(DialogPhotoSmallLegacy(dialog_id, dialog_access_hash, volume_id, local_id));
+ }
+ }
+
+ static PhotoSizeSource sticker_set_thumbnail_legacy(int64 sticker_set_id, int64 sticker_set_access_hash,
+ int64 volume_id, int32 local_id) {
+ return PhotoSizeSource(StickerSetThumbnailLegacy(sticker_set_id, sticker_set_access_hash, volume_id, local_id));
+ }
+
+ static PhotoSizeSource sticker_set_thumbnail(int64 sticker_set_id, int64 sticker_set_access_hash, int32 version) {
+ return PhotoSizeSource(StickerSetThumbnailVersion(sticker_set_id, sticker_set_access_hash, version));
+ }
+
+ Type get_type(const char *source) const {
+ auto offset = variant_.get_offset();
+ LOG_CHECK(offset >= 0) << offset << ' ' << source;
+ return static_cast<Type>(offset);
+ }
+
+ FileType get_file_type(const char *source) const;
+
+ Thumbnail &thumbnail() {
+ return variant_.get<Thumbnail>();
+ }
+
+ const Legacy &legacy() const {
+ return variant_.get<Legacy>();
+ }
+ const Thumbnail &thumbnail() const {
+ return variant_.get<Thumbnail>();
+ }
+ const DialogPhoto &dialog_photo() const {
+ switch (variant_.get_offset()) {
+ case 2:
+ return variant_.get<DialogPhotoSmall>();
+ case 3:
+ return variant_.get<DialogPhotoBig>();
+ case 6:
+ return variant_.get<DialogPhotoSmallLegacy>();
+ case 7:
+ return variant_.get<DialogPhotoBigLegacy>();
+ default:
+ UNREACHABLE();
+ return variant_.get<DialogPhotoSmall>();
+ }
+ }
+ const StickerSetThumbnail &sticker_set_thumbnail() const {
+ switch (variant_.get_offset()) {
+ case 4:
+ return variant_.get<StickerSetThumbnail>();
+ case 8:
+ return variant_.get<StickerSetThumbnailLegacy>();
+ case 9:
+ return variant_.get<StickerSetThumbnailVersion>();
+ default:
+ UNREACHABLE();
+ return variant_.get<StickerSetThumbnail>();
+ }
+ }
+ const FullLegacy &full_legacy() const {
+ return variant_.get<FullLegacy>();
+ }
+ const DialogPhotoLegacy &dialog_photo_legacy() const {
+ if (variant_.get_offset() == 6) {
+ return variant_.get<DialogPhotoSmallLegacy>();
+ } else {
+ return variant_.get<DialogPhotoBigLegacy>();
+ }
+ }
+ const StickerSetThumbnailLegacy &sticker_set_thumbnail_legacy() const {
+ return variant_.get<StickerSetThumbnailLegacy>();
+ }
+ const StickerSetThumbnailVersion &sticker_set_thumbnail_version() const {
+ return variant_.get<StickerSetThumbnailVersion>();
+ }
+
+ // returns unique representation of the source
+ string get_unique() const;
+
+ // can't be called for Legacy sources
+ string get_unique_name(int64 photo_id) const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+ template <class ParserT>
+ void parse(ParserT &parser);
+
+ friend bool operator==(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs);
+
+ private:
+ Variant<Legacy, Thumbnail, DialogPhotoSmall, DialogPhotoBig, StickerSetThumbnail, FullLegacy, DialogPhotoSmallLegacy,
+ DialogPhotoBigLegacy, StickerSetThumbnailLegacy, StickerSetThumbnailVersion>
+ variant_;
+
+ template <class T>
+ explicit PhotoSizeSource(const T &variant) : variant_(variant) {
+ }
+};
+
+bool operator==(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs);
+bool operator!=(const PhotoSizeSource &lhs, const PhotoSizeSource &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSizeSource &source);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.hpp b/protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.hpp
new file mode 100644
index 0000000000..cf70d42a67
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PhotoSizeSource.hpp
@@ -0,0 +1,191 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/PhotoSizeSource.h"
+
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const PhotoSizeSource::Legacy &source, StorerT &storer) {
+ UNREACHABLE();
+ store(source.secret, storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::Legacy &source, ParserT &parser) {
+ parse(source.secret, parser);
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::Thumbnail &source, StorerT &storer) {
+ store(source.file_type, storer);
+ store(source.thumbnail_type, storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::Thumbnail &source, ParserT &parser) {
+ int32 raw_type;
+ parse(raw_type, parser);
+ if (raw_type < 0 || raw_type >= static_cast<int32>(FileType::Size)) {
+ return parser.set_error("Wrong file type in PhotoSizeSource::Thumbnail");
+ }
+ source.file_type = static_cast<FileType>(raw_type);
+
+ parse(source.thumbnail_type, parser);
+ if (source.thumbnail_type < 0 || source.thumbnail_type > 127) {
+ parser.set_error("Wrong thumbnail type");
+ }
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::DialogPhoto &source, StorerT &storer) {
+ store(source.dialog_id, storer);
+ store(source.dialog_access_hash, storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::DialogPhoto &source, ParserT &parser) {
+ parse(source.dialog_id, parser);
+ parse(source.dialog_access_hash, parser);
+
+ switch (source.dialog_id.get_type()) {
+ case DialogType::SecretChat:
+ case DialogType::None:
+ return parser.set_error(PSTRING() << "Invalid chat identifier " << source.dialog_id.get());
+ default:
+ break;
+ }
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::DialogPhotoSmall &source, StorerT &storer) {
+ store(static_cast<const PhotoSizeSource::DialogPhoto &>(source), storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::DialogPhotoSmall &source, ParserT &parser) {
+ parse(static_cast<PhotoSizeSource::DialogPhoto &>(source), parser);
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::DialogPhotoBig &source, StorerT &storer) {
+ store(static_cast<const PhotoSizeSource::DialogPhoto &>(source), storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::DialogPhotoBig &source, ParserT &parser) {
+ parse(static_cast<PhotoSizeSource::DialogPhoto &>(source), parser);
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::StickerSetThumbnail &source, StorerT &storer) {
+ store(source.sticker_set_id, storer);
+ store(source.sticker_set_access_hash, storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::StickerSetThumbnail &source, ParserT &parser) {
+ parse(source.sticker_set_id, parser);
+ parse(source.sticker_set_access_hash, parser);
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::FullLegacy &source, StorerT &storer) {
+ store(source.volume_id, storer);
+ store(source.secret, storer);
+ store(source.local_id, storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::FullLegacy &source, ParserT &parser) {
+ parse(source.volume_id, parser);
+ parse(source.secret, parser);
+ parse(source.local_id, parser);
+ // source.local_id can be negative in secret chat thumbnails
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::DialogPhotoLegacy &source, StorerT &storer) {
+ store(static_cast<const PhotoSizeSource::DialogPhoto &>(source), storer);
+ store(source.volume_id, storer);
+ store(source.local_id, storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::DialogPhotoLegacy &source, ParserT &parser) {
+ parse(static_cast<PhotoSizeSource::DialogPhoto &>(source), parser);
+ parse(source.volume_id, parser);
+ parse(source.local_id, parser);
+ if (source.local_id < 0) {
+ parser.set_error("Wrong local_id");
+ }
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::DialogPhotoSmallLegacy &source, StorerT &storer) {
+ store(static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(source), storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::DialogPhotoSmallLegacy &source, ParserT &parser) {
+ parse(static_cast<PhotoSizeSource::DialogPhotoLegacy &>(source), parser);
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::DialogPhotoBigLegacy &source, StorerT &storer) {
+ store(static_cast<const PhotoSizeSource::DialogPhotoLegacy &>(source), storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::DialogPhotoBigLegacy &source, ParserT &parser) {
+ parse(static_cast<PhotoSizeSource::DialogPhotoLegacy &>(source), parser);
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::StickerSetThumbnailLegacy &source, StorerT &storer) {
+ store(static_cast<const PhotoSizeSource::StickerSetThumbnail &>(source), storer);
+ store(source.volume_id, storer);
+ store(source.local_id, storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::StickerSetThumbnailLegacy &source, ParserT &parser) {
+ parse(static_cast<PhotoSizeSource::StickerSetThumbnail &>(source), parser);
+ parse(source.volume_id, parser);
+ parse(source.local_id, parser);
+ if (source.local_id < 0) {
+ parser.set_error("Wrong local_id");
+ }
+}
+
+template <class StorerT>
+void store(const PhotoSizeSource::StickerSetThumbnailVersion &source, StorerT &storer) {
+ store(static_cast<const PhotoSizeSource::StickerSetThumbnail &>(source), storer);
+ store(source.version, storer);
+}
+
+template <class ParserT>
+void parse(PhotoSizeSource::StickerSetThumbnailVersion &source, ParserT &parser) {
+ parse(static_cast<PhotoSizeSource::StickerSetThumbnail &>(source), parser);
+ parse(source.version, parser);
+}
+
+template <class StorerT>
+void PhotoSizeSource::store(StorerT &storer) const {
+ td::store(variant_, storer);
+}
+
+template <class ParserT>
+void PhotoSizeSource::parse(ParserT &parser) {
+ td::parse(variant_, parser);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PollId.h b/protocols/Telegram/tdlib/td/td/telegram/PollId.h
new file mode 100644
index 0000000000..8e08bc68b6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PollId.h
@@ -0,0 +1,55 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class PollId {
+ int64 id = 0;
+
+ public:
+ PollId() = default;
+
+ explicit PollId(int64 poll_id) : id(poll_id) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
+ PollId(T poll_id) = delete;
+
+ int64 get() const {
+ return id;
+ }
+
+ bool operator==(const PollId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const PollId &other) const {
+ return id != other.id;
+ }
+
+ bool is_valid() const {
+ return id != 0;
+ }
+};
+
+struct PollIdHash {
+ uint32 operator()(PollId poll_id) const {
+ return Hash<int64>()(poll_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, PollId poll_id) {
+ return string_builder << "poll " << poll_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PollId.hpp b/protocols/Telegram/tdlib/td/td/telegram/PollId.hpp
new file mode 100644
index 0000000000..ff646f9e49
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PollId.hpp
@@ -0,0 +1,27 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/PollId.h"
+
+#include "td/telegram/PollManager.h"
+#include "td/telegram/PollManager.hpp"
+#include "td/telegram/Td.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const PollId &poll_id, StorerT &storer) {
+ storer.context()->td().get_actor_unsafe()->poll_manager_->store_poll(poll_id, storer);
+}
+
+template <class ParserT>
+void parse(PollId &poll_id, ParserT &parser) {
+ poll_id = parser.context()->td().get_actor_unsafe()->poll_manager_->parse_poll(parser);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PollManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/PollManager.cpp
new file mode 100644
index 0000000000..651326285e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PollManager.cpp
@@ -0,0 +1,1862 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/PollManager.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/ChainId.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Dependencies.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/PollId.hpp"
+#include "td/telegram/PollManager.hpp"
+#include "td/telegram/StateManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/telegram/telegram_api.hpp"
+#include "td/telegram/UpdatesManager.h"
+
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/SqliteKeyValue.h"
+#include "td/db/SqliteKeyValueAsync.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+#include "td/utils/tl_helpers.h"
+
+#include <algorithm>
+#include <limits>
+
+namespace td {
+
+class GetPollResultsQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::Updates>> promise_;
+ PollId poll_id_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetPollResultsQuery(Promise<tl_object_ptr<telegram_api::Updates>> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(PollId poll_id, FullMessageId full_message_id) {
+ poll_id_ = poll_id;
+ dialog_id_ = full_message_id.get_dialog_id();
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ LOG(INFO) << "Can't reget poll, because have no read access to " << dialog_id_;
+ return promise_.set_value(nullptr);
+ }
+
+ auto message_id = full_message_id.get_message_id().get_server_message_id().get();
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getPollResults(std::move(input_peer), message_id)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getPollResults>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPollResultsQuery") &&
+ status.message() != "MESSAGE_ID_INVALID") {
+ LOG(ERROR) << "Receive " << status << ", while trying to get results of " << poll_id_;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetPollVotersQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::messages_votesList>> promise_;
+ PollId poll_id_;
+ DialogId dialog_id_;
+
+ public:
+ explicit GetPollVotersQuery(Promise<tl_object_ptr<telegram_api::messages_votesList>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(PollId poll_id, FullMessageId full_message_id, BufferSlice &&option, const string &offset, int32 limit) {
+ poll_id_ = poll_id;
+ dialog_id_ = full_message_id.get_dialog_id();
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ LOG(INFO) << "Can't get poll, because have no read access to " << dialog_id_;
+ return promise_.set_error(Status::Error(400, "Chat is not accessible"));
+ }
+
+ CHECK(!option.empty());
+ int32 flags = telegram_api::messages_getPollVotes::OPTION_MASK;
+ if (!offset.empty()) {
+ flags |= telegram_api::messages_getPollVotes::OFFSET_MASK;
+ }
+
+ auto message_id = full_message_id.get_message_id().get_server_message_id().get();
+ send_query(G()->net_query_creator().create(telegram_api::messages_getPollVotes(
+ flags, std::move(input_peer), message_id, std::move(option), offset, limit)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getPollVotes>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetPollVotersQuery") &&
+ status.message() != "MESSAGE_ID_INVALID") {
+ LOG(ERROR) << "Receive " << status << ", while trying to get voters of " << poll_id_;
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SendVoteQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::Updates>> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit SendVoteQuery(Promise<tl_object_ptr<telegram_api::Updates>> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(FullMessageId full_message_id, vector<BufferSlice> &&options, PollId poll_id, uint64 generation,
+ NetQueryRef *query_ref) {
+ dialog_id_ = full_message_id.get_dialog_id();
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ LOG(INFO) << "Can't set poll answer, because have no read access to " << dialog_id_;
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ auto message_id = full_message_id.get_message_id().get_server_message_id().get();
+ auto query = G()->net_query_creator().create(
+ telegram_api::messages_sendVote(std::move(input_peer), message_id, std::move(options)),
+ {{poll_id}, {dialog_id_}});
+ *query_ref = query.get_weak();
+ send_query(std::move(query));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_sendVote>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for SendVoteQuery: " << to_string(result);
+ promise_.set_value(std::move(result));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendVoteQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class StopPollQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit StopPollQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup, PollId poll_id) {
+ dialog_id_ = full_message_id.get_dialog_id();
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Edit);
+ if (input_peer == nullptr) {
+ LOG(INFO) << "Can't close poll, because have no edit access to " << dialog_id_;
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+
+ int32 flags = telegram_api::messages_editMessage::MEDIA_MASK;
+ auto input_reply_markup = get_input_reply_markup(td_->contacts_manager_.get(), reply_markup);
+ if (input_reply_markup != nullptr) {
+ flags |= telegram_api::messages_editMessage::REPLY_MARKUP_MASK;
+ }
+
+ auto message_id = full_message_id.get_message_id().get_server_message_id().get();
+ auto poll = telegram_api::make_object<telegram_api::poll>();
+ poll->flags_ |= telegram_api::poll::CLOSED_MASK;
+ auto input_media = telegram_api::make_object<telegram_api::inputMediaPoll>(0, std::move(poll),
+ vector<BufferSlice>(), string(), Auto());
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_editMessage(flags, false /*ignored*/, std::move(input_peer), message_id, string(),
+ std::move(input_media), std::move(input_reply_markup),
+ vector<tl_object_ptr<telegram_api::MessageEntity>>(), 0),
+ {{poll_id}, {dialog_id_}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_editMessage>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for StopPollQuery: " << to_string(result);
+ td_->updates_manager_->on_get_updates(std::move(result), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ if (!td_->auth_manager_->is_bot() && status.message() == "MESSAGE_NOT_MODIFIED") {
+ return promise_.set_value(Unit());
+ }
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "StopPollQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+PollManager::PollManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+ update_poll_timeout_.set_callback(on_update_poll_timeout_callback);
+ update_poll_timeout_.set_callback_data(static_cast<void *>(this));
+
+ close_poll_timeout_.set_callback(on_close_poll_timeout_callback);
+ close_poll_timeout_.set_callback_data(static_cast<void *>(this));
+
+ unload_poll_timeout_.set_callback(on_unload_poll_timeout_callback);
+ unload_poll_timeout_.set_callback_data(static_cast<void *>(this));
+}
+
+void PollManager::start_up() {
+ class StateCallback final : public StateManager::Callback {
+ public:
+ explicit StateCallback(ActorId<PollManager> parent) : parent_(std::move(parent)) {
+ }
+ bool on_online(bool is_online) final {
+ if (is_online) {
+ send_closure(parent_, &PollManager::on_online);
+ }
+ return parent_.is_alive();
+ }
+
+ private:
+ ActorId<PollManager> parent_;
+ };
+ send_closure(G()->state_manager(), &StateManager::add_callback, make_unique<StateCallback>(actor_id(this)));
+}
+
+void PollManager::tear_down() {
+ parent_.reset();
+}
+
+PollManager::~PollManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), polls_, server_poll_messages_,
+ other_poll_messages_, poll_voters_, loaded_from_database_polls_);
+}
+
+void PollManager::on_update_poll_timeout_callback(void *poll_manager_ptr, int64 poll_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto poll_manager = static_cast<PollManager *>(poll_manager_ptr);
+ send_closure_later(poll_manager->actor_id(poll_manager), &PollManager::on_update_poll_timeout, PollId(poll_id_int));
+}
+
+void PollManager::on_close_poll_timeout_callback(void *poll_manager_ptr, int64 poll_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto poll_manager = static_cast<PollManager *>(poll_manager_ptr);
+ send_closure_later(poll_manager->actor_id(poll_manager), &PollManager::on_close_poll_timeout, PollId(poll_id_int));
+}
+
+void PollManager::on_unload_poll_timeout_callback(void *poll_manager_ptr, int64 poll_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto poll_manager = static_cast<PollManager *>(poll_manager_ptr);
+ send_closure_later(poll_manager->actor_id(poll_manager), &PollManager::on_unload_poll_timeout, PollId(poll_id_int));
+}
+
+bool PollManager::is_local_poll_id(PollId poll_id) {
+ return poll_id.get() < 0 && poll_id.get() > std::numeric_limits<int32>::min();
+}
+
+const PollManager::Poll *PollManager::get_poll(PollId poll_id) const {
+ return polls_.get_pointer(poll_id);
+}
+
+const PollManager::Poll *PollManager::get_poll(PollId poll_id) {
+ auto p = polls_.get_pointer(poll_id);
+ if (p != nullptr) {
+ schedule_poll_unload(poll_id);
+ }
+ return p;
+}
+
+PollManager::Poll *PollManager::get_poll_editable(PollId poll_id) {
+ auto p = polls_.get_pointer(poll_id);
+ if (p != nullptr) {
+ schedule_poll_unload(poll_id);
+ }
+ return p;
+}
+
+bool PollManager::have_poll(PollId poll_id) const {
+ return get_poll(poll_id) != nullptr;
+}
+
+void PollManager::notify_on_poll_update(PollId poll_id) {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (server_poll_messages_.count(poll_id) > 0) {
+ server_poll_messages_[poll_id].foreach([&](const FullMessageId &full_message_id) {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ });
+ }
+
+ if (other_poll_messages_.count(poll_id) > 0) {
+ other_poll_messages_[poll_id].foreach([&](const FullMessageId &full_message_id) {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ });
+ }
+}
+
+string PollManager::get_poll_database_key(PollId poll_id) {
+ return PSTRING() << "poll" << poll_id.get();
+}
+
+void PollManager::save_poll(const Poll *poll, PollId poll_id) {
+ CHECK(!is_local_poll_id(poll_id));
+ poll->was_saved = true;
+
+ if (!G()->parameters().use_message_db) {
+ return;
+ }
+
+ LOG(INFO) << "Save " << poll_id << " to database";
+ CHECK(poll != nullptr);
+ G()->td_db()->get_sqlite_pmc()->set(get_poll_database_key(poll_id), log_event_store(*poll).as_slice().str(), Auto());
+}
+
+void PollManager::on_load_poll_from_database(PollId poll_id, string value) {
+ CHECK(poll_id.is_valid());
+ loaded_from_database_polls_.insert(poll_id);
+
+ LOG(INFO) << "Successfully loaded " << poll_id << " of size " << value.size() << " from database";
+ // G()->td_db()->get_sqlite_pmc()->erase(get_poll_database_key(poll_id), Auto());
+ // return;
+
+ CHECK(!have_poll(poll_id));
+ if (!value.empty()) {
+ auto poll = make_unique<Poll>();
+ auto status = log_event_parse(*poll, value);
+ if (status.is_error()) {
+ LOG(FATAL) << status << ": " << format::as_hex_dump<4>(Slice(value));
+ }
+ for (auto &user_id : poll->recent_voter_user_ids) {
+ td_->contacts_manager_->have_user_force(user_id);
+ }
+ if (!poll->is_closed && poll->close_date != 0) {
+ if (poll->close_date <= G()->server_time()) {
+ poll->is_closed = true;
+ } else {
+ CHECK(!is_local_poll_id(poll_id));
+ close_poll_timeout_.set_timeout_in(poll_id.get(), poll->close_date - G()->server_time() + 1e-3);
+ }
+ }
+ polls_[poll_id] = std::move(poll);
+ }
+}
+
+bool PollManager::have_poll_force(PollId poll_id) {
+ return get_poll_force(poll_id) != nullptr;
+}
+
+PollManager::Poll *PollManager::get_poll_force(PollId poll_id) {
+ auto poll = get_poll_editable(poll_id);
+ if (poll != nullptr) {
+ return poll;
+ }
+ if (!G()->parameters().use_message_db) {
+ return nullptr;
+ }
+ if (!poll_id.is_valid() || loaded_from_database_polls_.count(poll_id)) {
+ return nullptr;
+ }
+
+ LOG(INFO) << "Trying to load " << poll_id << " from database";
+ on_load_poll_from_database(poll_id, G()->td_db()->get_sqlite_sync_pmc()->get(get_poll_database_key(poll_id)));
+ return get_poll_editable(poll_id);
+}
+
+td_api::object_ptr<td_api::pollOption> PollManager::get_poll_option_object(const PollOption &poll_option) {
+ return td_api::make_object<td_api::pollOption>(poll_option.text, poll_option.voter_count, 0, poll_option.is_chosen,
+ false);
+}
+
+vector<int32> PollManager::get_vote_percentage(const vector<int32> &voter_counts, int32 total_voter_count) {
+ int32 sum = 0;
+ for (auto voter_count : voter_counts) {
+ CHECK(0 <= voter_count);
+ CHECK(voter_count <= std::numeric_limits<int32>::max() - sum);
+ sum += voter_count;
+ }
+ if (total_voter_count > sum) {
+ if (sum != 0) {
+ LOG(ERROR) << "Have total_voter_count = " << total_voter_count << ", but votes sum = " << sum << ": "
+ << voter_counts;
+ }
+ total_voter_count = sum;
+ }
+
+ vector<int32> result(voter_counts.size(), 0);
+ if (total_voter_count == 0) {
+ return result;
+ }
+ if (total_voter_count != sum) {
+ // just round to the nearest
+ for (size_t i = 0; i < result.size(); i++) {
+ result[i] =
+ static_cast<int32>((static_cast<int64>(voter_counts[i]) * 200 + total_voter_count) / total_voter_count / 2);
+ }
+ return result;
+ }
+
+ // make sure that options with equal votes have equal percent and total sum is less than 100%
+ int32 percent_sum = 0;
+ vector<int32> gap(voter_counts.size(), 0);
+ for (size_t i = 0; i < result.size(); i++) {
+ auto multiplied_voter_count = static_cast<int64>(voter_counts[i]) * 100;
+ result[i] = static_cast<int32>(multiplied_voter_count / total_voter_count);
+ CHECK(0 <= result[i] && result[i] <= 100);
+ gap[i] = static_cast<int32>(static_cast<int64>(result[i] + 1) * total_voter_count - multiplied_voter_count);
+ CHECK(0 <= gap[i] && gap[i] <= total_voter_count);
+ percent_sum += result[i];
+ }
+ CHECK(0 <= percent_sum && percent_sum <= 100);
+ if (percent_sum == 100) {
+ return result;
+ }
+
+ // now we need to choose up to (100 - percent_sum) options with a minimum total gap, such that
+ // any two options with the same voter_count are chosen or not chosen simultaneously
+ struct Option {
+ int32 pos = -1;
+ int32 count = 0;
+ };
+ FlatHashMap<int32, Option> options;
+ for (size_t i = 0; i < result.size(); i++) {
+ auto &option = options[voter_counts[i] + 1];
+ if (option.pos == -1) {
+ option.pos = narrow_cast<int32>(i);
+ }
+ option.count++;
+ }
+ vector<Option> sorted_options;
+ for (const auto &it : options) {
+ const auto &option = it.second;
+ auto pos = option.pos;
+ if (gap[pos] > total_voter_count / 2) {
+ // do not round to wrong direction
+ continue;
+ }
+ if (total_voter_count % 2 == 0 && gap[pos] == total_voter_count / 2 && result[pos] >= 50) {
+ // round halves to the 50%
+ continue;
+ }
+ sorted_options.push_back(option);
+ }
+ std::sort(sorted_options.begin(), sorted_options.end(), [&](const Option &lhs, const Option &rhs) {
+ if (gap[lhs.pos] != gap[rhs.pos]) {
+ // prefer options with smallest gap
+ return gap[lhs.pos] < gap[rhs.pos];
+ }
+ if (lhs.count != rhs.count) {
+ // prefer more popular options
+ return lhs.count > rhs.count;
+ }
+ return lhs.pos < rhs.pos; // prefer the first encountered option
+ });
+
+ // dynamic programming or brute force can give perfect result, but for now we use simple gready approach
+ int32 left_percent = 100 - percent_sum;
+ for (auto option : sorted_options) {
+ if (option.count <= left_percent) {
+ left_percent -= option.count;
+
+ auto pos = option.pos;
+ for (size_t i = 0; i < result.size(); i++) {
+ if (voter_counts[i] == voter_counts[pos]) {
+ result[i]++;
+ }
+ }
+ if (left_percent == 0) {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+td_api::object_ptr<td_api::poll> PollManager::get_poll_object(PollId poll_id) const {
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+ return get_poll_object(poll_id, poll);
+}
+
+td_api::object_ptr<td_api::poll> PollManager::get_poll_object(PollId poll_id, const Poll *poll) const {
+ vector<td_api::object_ptr<td_api::pollOption>> poll_options;
+ auto it = pending_answers_.find(poll_id);
+ int32 voter_count_diff = 0;
+ if (it == pending_answers_.end()) {
+ poll_options = transform(poll->options, get_poll_option_object);
+ } else {
+ auto &chosen_options = it->second.options_;
+ for (auto &poll_option : poll->options) {
+ auto is_being_chosen = td::contains(chosen_options, poll_option.data);
+ if (poll_option.is_chosen) {
+ voter_count_diff = -1;
+ }
+ poll_options.push_back(td_api::make_object<td_api::pollOption>(
+ poll_option.text, poll_option.voter_count - static_cast<int32>(poll_option.is_chosen), 0, false,
+ is_being_chosen));
+ }
+ }
+
+ auto total_voter_count = poll->total_voter_count + voter_count_diff;
+ bool is_voted = false;
+ for (auto &poll_option : poll_options) {
+ is_voted |= poll_option->is_chosen_;
+ }
+ if (!is_voted && !poll->is_closed && !td_->auth_manager_->is_bot()) {
+ // hide the voter counts
+ for (auto &poll_option : poll_options) {
+ poll_option->voter_count_ = 0;
+ }
+ } else {
+ // calculate vote percentage and fix total_voter_count
+ auto voter_counts = transform(poll_options, [](auto &poll_option) { return poll_option->voter_count_; });
+ auto voter_count_sum = 0;
+ for (auto voter_count : voter_counts) {
+ if (total_voter_count < voter_count) {
+ LOG(ERROR) << "Fix total voter count from " << poll->total_voter_count << " + " << voter_count_diff << " to "
+ << voter_count << " in " << poll_id;
+ total_voter_count = voter_count;
+ }
+ voter_count_sum += voter_count;
+ }
+ if (voter_count_sum < total_voter_count && voter_count_sum != 0) {
+ LOG(ERROR) << "Fix total voter count from " << poll->total_voter_count << " + " << voter_count_diff << " to "
+ << voter_count_sum << " in " << poll_id;
+ total_voter_count = voter_count_sum;
+ }
+
+ auto vote_percentage = get_vote_percentage(voter_counts, total_voter_count);
+ CHECK(poll_options.size() == vote_percentage.size());
+ for (size_t i = 0; i < poll_options.size(); i++) {
+ poll_options[i]->vote_percentage_ = vote_percentage[i];
+ }
+ }
+ td_api::object_ptr<td_api::PollType> poll_type;
+ if (poll->is_quiz) {
+ auto correct_option_id = is_local_poll_id(poll_id) ? -1 : poll->correct_option_id;
+ poll_type = td_api::make_object<td_api::pollTypeQuiz>(
+ correct_option_id,
+ get_formatted_text_object(is_local_poll_id(poll_id) ? FormattedText() : poll->explanation, true, -1));
+ } else {
+ poll_type = td_api::make_object<td_api::pollTypeRegular>(poll->allow_multiple_answers);
+ }
+
+ auto open_period = poll->open_period;
+ auto close_date = poll->close_date;
+ if (open_period != 0 && close_date == 0) {
+ close_date = G()->unix_time() + open_period;
+ }
+ if (open_period == 0 && close_date != 0) {
+ auto now = G()->unix_time();
+ if (close_date < now + 5) {
+ close_date = 0;
+ } else {
+ open_period = close_date - now;
+ }
+ }
+ if (poll->is_closed) {
+ open_period = 0;
+ close_date = 0;
+ }
+ return td_api::make_object<td_api::poll>(
+ poll_id.get(), poll->question, std::move(poll_options), total_voter_count,
+ td_->contacts_manager_->get_user_ids_object(poll->recent_voter_user_ids, "get_poll_object"), poll->is_anonymous,
+ std::move(poll_type), open_period, close_date, poll->is_closed);
+}
+
+telegram_api::object_ptr<telegram_api::pollAnswer> PollManager::get_input_poll_option(const PollOption &poll_option) {
+ return telegram_api::make_object<telegram_api::pollAnswer>(poll_option.text, BufferSlice(poll_option.data));
+}
+
+PollId PollManager::create_poll(string &&question, vector<string> &&options, bool is_anonymous,
+ bool allow_multiple_answers, bool is_quiz, int32 correct_option_id,
+ FormattedText &&explanation, int32 open_period, int32 close_date, bool is_closed) {
+ auto poll = make_unique<Poll>();
+ poll->question = std::move(question);
+ int pos = '0';
+ for (auto &option_text : options) {
+ PollOption option;
+ option.text = std::move(option_text);
+ option.data = string(1, narrow_cast<char>(pos++));
+ poll->options.push_back(std::move(option));
+ }
+ poll->is_anonymous = is_anonymous;
+ poll->allow_multiple_answers = allow_multiple_answers;
+ poll->is_quiz = is_quiz;
+ poll->correct_option_id = correct_option_id;
+ poll->explanation = std::move(explanation);
+ poll->open_period = open_period;
+ poll->close_date = close_date;
+ poll->is_closed = is_closed;
+
+ PollId poll_id(--current_local_poll_id_);
+ CHECK(is_local_poll_id(poll_id));
+ polls_.set(poll_id, std::move(poll));
+ return poll_id;
+}
+
+void PollManager::register_poll(PollId poll_id, FullMessageId full_message_id, const char *source) {
+ CHECK(have_poll(poll_id));
+ if (full_message_id.get_message_id().is_scheduled() || !full_message_id.get_message_id().is_server()) {
+ other_poll_messages_[poll_id].insert(full_message_id);
+ unload_poll_timeout_.cancel_timeout(poll_id.get());
+ return;
+ }
+ LOG(INFO) << "Register " << poll_id << " from " << full_message_id << " from " << source;
+ server_poll_messages_[poll_id].insert(full_message_id);
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+ if (!td_->auth_manager_->is_bot() && !is_local_poll_id(poll_id) &&
+ !(poll->is_closed && poll->is_updated_after_close)) {
+ update_poll_timeout_.add_timeout_in(poll_id.get(), 0);
+ }
+ unload_poll_timeout_.cancel_timeout(poll_id.get());
+}
+
+void PollManager::unregister_poll(PollId poll_id, FullMessageId full_message_id, const char *source) {
+ CHECK(have_poll(poll_id));
+ if (full_message_id.get_message_id().is_scheduled() || !full_message_id.get_message_id().is_server()) {
+ auto &message_ids = other_poll_messages_[poll_id];
+ auto is_deleted = message_ids.erase(full_message_id) > 0;
+ LOG_CHECK(is_deleted) << source << ' ' << poll_id << ' ' << full_message_id;
+ if (is_local_poll_id(poll_id)) {
+ CHECK(message_ids.empty());
+ forget_local_poll(poll_id);
+ }
+ if (message_ids.empty()) {
+ other_poll_messages_.erase(poll_id);
+
+ schedule_poll_unload(poll_id);
+ }
+ return;
+ }
+ LOG(INFO) << "Unregister " << poll_id << " from " << full_message_id << " from " << source;
+ auto &message_ids = server_poll_messages_[poll_id];
+ auto is_deleted = message_ids.erase(full_message_id) > 0;
+ LOG_CHECK(is_deleted) << source << ' ' << poll_id << ' ' << full_message_id;
+ if (is_local_poll_id(poll_id)) {
+ CHECK(message_ids.empty());
+ forget_local_poll(poll_id);
+ }
+ if (message_ids.empty()) {
+ server_poll_messages_.erase(poll_id);
+ update_poll_timeout_.cancel_timeout(poll_id.get());
+
+ schedule_poll_unload(poll_id);
+ }
+}
+
+bool PollManager::can_unload_poll(PollId poll_id) {
+ if (is_local_poll_id(poll_id) || server_poll_messages_.count(poll_id) != 0 ||
+ other_poll_messages_.count(poll_id) != 0 || pending_answers_.count(poll_id) != 0 ||
+ being_closed_polls_.count(poll_id) != 0) {
+ return false;
+ }
+
+ auto it = poll_voters_.find(poll_id);
+ if (it != poll_voters_.end() && !it->second.empty()) {
+ for (auto &voters : it->second) {
+ if (!voters.pending_queries.empty()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+void PollManager::schedule_poll_unload(PollId poll_id) {
+ if (can_unload_poll(poll_id)) {
+ unload_poll_timeout_.set_timeout_in(poll_id.get(), UNLOAD_POLL_DELAY);
+ }
+}
+
+bool PollManager::get_poll_is_closed(PollId poll_id) const {
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+ return poll->is_closed;
+}
+
+bool PollManager::get_poll_is_anonymous(PollId poll_id) const {
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+ return poll->is_anonymous;
+}
+
+string PollManager::get_poll_search_text(PollId poll_id) const {
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+
+ string result = poll->question;
+ for (auto &option : poll->options) {
+ result += ' ';
+ result += option.text;
+ }
+ return result;
+}
+
+void PollManager::set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector<int32> &&option_ids,
+ Promise<Unit> &&promise) {
+ td::unique(option_ids);
+
+ if (is_local_poll_id(poll_id)) {
+ return promise.set_error(Status::Error(400, "Poll can't be answered"));
+ }
+
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+ if (poll->is_closed) {
+ return promise.set_error(Status::Error(400, "Can't answer closed poll"));
+ }
+ if (!poll->allow_multiple_answers && option_ids.size() > 1) {
+ return promise.set_error(Status::Error(400, "Can't choose more than 1 option in the poll"));
+ }
+ if (poll->is_quiz && option_ids.empty()) {
+ return promise.set_error(Status::Error(400, "Can't retract vote in a quiz"));
+ }
+ if (poll->is_quiz && pending_answers_.count(poll_id) != 0) {
+ return promise.set_error(Status::Error(400, "Can't revote in a quiz"));
+ }
+
+ FlatHashMap<uint64, int> affected_option_ids;
+ vector<string> options;
+ for (auto &option_id : option_ids) {
+ auto index = static_cast<size_t>(option_id);
+ if (index >= poll->options.size()) {
+ return promise.set_error(Status::Error(400, "Invalid option ID specified"));
+ }
+ options.push_back(poll->options[index].data);
+
+ affected_option_ids[index + 1]++;
+ }
+ for (size_t option_index = 0; option_index < poll->options.size(); option_index++) {
+ if (poll->options[option_index].is_chosen) {
+ if (poll->is_quiz) {
+ return promise.set_error(Status::Error(400, "Can't revote in a quiz"));
+ }
+ affected_option_ids[option_index + 1]++;
+ }
+ }
+ for (const auto &it : affected_option_ids) {
+ if (it.second == 1) {
+ invalidate_poll_option_voters(poll, poll_id, static_cast<size_t>(it.first - 1));
+ }
+ }
+
+ do_set_poll_answer(poll_id, full_message_id, std::move(options), 0, std::move(promise));
+}
+
+class PollManager::SetPollAnswerLogEvent {
+ public:
+ PollId poll_id_;
+ FullMessageId full_message_id_;
+ vector<string> options_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(poll_id_, storer);
+ td::store(full_message_id_, storer);
+ td::store(options_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(poll_id_, parser);
+ td::parse(full_message_id_, parser);
+ td::parse(options_, parser);
+ }
+};
+
+void PollManager::do_set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector<string> &&options,
+ uint64 log_event_id, Promise<Unit> &&promise) {
+ LOG(INFO) << "Set answer in " << poll_id << " from " << full_message_id;
+ if (!poll_id.is_valid() || !full_message_id.get_dialog_id().is_valid() ||
+ !full_message_id.get_message_id().is_valid()) {
+ CHECK(log_event_id != 0);
+ LOG(ERROR) << "Invalid SetPollAnswer log event";
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ return;
+ }
+ unload_poll_timeout_.cancel_timeout(poll_id.get());
+
+ auto &pending_answer = pending_answers_[poll_id];
+ if (!pending_answer.promises_.empty() && pending_answer.options_ == options) {
+ pending_answer.promises_.push_back(std::move(promise));
+ return;
+ }
+
+ if (pending_answer.log_event_id_ != 0 && log_event_id != 0) {
+ LOG(ERROR) << "Duplicate SetPollAnswer log event: " << pending_answer.log_event_id_ << " and " << log_event_id;
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ return;
+ }
+ if (log_event_id == 0 && G()->parameters().use_message_db) {
+ SetPollAnswerLogEvent log_event;
+ log_event.poll_id_ = poll_id;
+ log_event.full_message_id_ = full_message_id;
+ log_event.options_ = options;
+ auto storer = get_log_event_storer(log_event);
+ if (pending_answer.generation_ == 0) {
+ CHECK(pending_answer.log_event_id_ == 0);
+ log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::SetPollAnswer, storer);
+ LOG(INFO) << "Add set poll answer log event " << log_event_id;
+ } else {
+ CHECK(pending_answer.log_event_id_ != 0);
+ log_event_id = pending_answer.log_event_id_;
+ auto new_log_event_id = binlog_rewrite(G()->td_db()->get_binlog(), pending_answer.log_event_id_,
+ LogEvent::HandlerType::SetPollAnswer, storer);
+ LOG(INFO) << "Rewrite set poll answer log event " << log_event_id << " with " << new_log_event_id;
+ }
+ }
+
+ if (!pending_answer.promises_.empty()) {
+ CHECK(!pending_answer.query_ref_.empty());
+ cancel_query(pending_answer.query_ref_);
+ pending_answer.query_ref_ = NetQueryRef();
+
+ auto promises = std::move(pending_answer.promises_);
+ pending_answer.promises_.clear();
+ for (auto &old_promise : promises) {
+ old_promise.set_value(Unit());
+ }
+ }
+
+ vector<BufferSlice> sent_options;
+ for (auto &option : options) {
+ sent_options.emplace_back(option);
+ }
+
+ auto generation = ++current_generation_;
+
+ pending_answer.options_ = std::move(options);
+ pending_answer.promises_.push_back(std::move(promise));
+ pending_answer.generation_ = generation;
+ pending_answer.log_event_id_ = log_event_id;
+
+ notify_on_poll_update(poll_id);
+
+ auto query_promise = PromiseCreator::lambda(
+ [poll_id, generation, actor_id = actor_id(this)](Result<tl_object_ptr<telegram_api::Updates>> &&result) {
+ send_closure(actor_id, &PollManager::on_set_poll_answer, poll_id, generation, std::move(result));
+ });
+ td_->create_handler<SendVoteQuery>(std::move(query_promise))
+ ->send(full_message_id, std::move(sent_options), poll_id, generation, &pending_answer.query_ref_);
+}
+
+void PollManager::on_set_poll_answer(PollId poll_id, uint64 generation,
+ Result<tl_object_ptr<telegram_api::Updates>> &&result) {
+ if (G()->close_flag() && result.is_error()) {
+ // request will be re-sent after restart
+ return;
+ }
+ auto it = pending_answers_.find(poll_id);
+ if (it == pending_answers_.end()) {
+ // can happen if this is an answer with mismatched generation and server has ignored invoke-after
+ return;
+ }
+
+ auto &pending_answer = it->second;
+ CHECK(!pending_answer.promises_.empty());
+ if (pending_answer.generation_ != generation) {
+ return;
+ }
+
+ if (pending_answer.log_event_id_ != 0) {
+ LOG(INFO) << "Delete set poll answer log event " << pending_answer.log_event_id_;
+ binlog_erase(G()->td_db()->get_binlog(), pending_answer.log_event_id_);
+ }
+
+ auto promises = std::move(pending_answer.promises_);
+ pending_answers_.erase(it);
+
+ auto poll = get_poll(poll_id);
+ if (poll != nullptr) {
+ poll->was_saved = false;
+ }
+ if (result.is_ok()) {
+ td_->updates_manager_->on_get_updates(
+ result.move_as_ok(), PromiseCreator::lambda([actor_id = actor_id(this), poll_id,
+ promises = std::move(promises)](Result<Unit> &&result) mutable {
+ send_closure(actor_id, &PollManager::on_set_poll_answer_finished, poll_id, Unit(), std::move(promises));
+ }));
+ } else {
+ on_set_poll_answer_finished(poll_id, result.move_as_error(), std::move(promises));
+ }
+}
+
+void PollManager::on_set_poll_answer_finished(PollId poll_id, Result<Unit> &&result, vector<Promise<Unit>> &&promises) {
+ if (!G()->close_flag()) {
+ auto poll = get_poll(poll_id);
+ if (poll != nullptr && !poll->was_saved) {
+ // no updates was sent during updates processing, so send them
+ // poll wasn't changed, so there is no reason to actually save it
+ if (!(poll->is_closed && poll->is_updated_after_close)) {
+ LOG(INFO) << "Schedule updating of " << poll_id << " soon";
+ update_poll_timeout_.set_timeout_in(poll_id.get(), 0.0);
+ }
+
+ notify_on_poll_update(poll_id);
+ poll->was_saved = true;
+ }
+ }
+
+ if (result.is_ok()) {
+ set_promises(promises);
+ } else {
+ fail_promises(promises, result.move_as_error());
+ }
+}
+
+void PollManager::invalidate_poll_voters(const Poll *poll, PollId poll_id) {
+ if (poll->is_anonymous) {
+ return;
+ }
+
+ auto it = poll_voters_.find(poll_id);
+ if (it == poll_voters_.end()) {
+ return;
+ }
+
+ for (auto &voters : it->second) {
+ voters.was_invalidated = true;
+ }
+}
+
+void PollManager::invalidate_poll_option_voters(const Poll *poll, PollId poll_id, size_t option_index) {
+ if (poll->is_anonymous) {
+ return;
+ }
+
+ auto it = poll_voters_.find(poll_id);
+ if (it == poll_voters_.end()) {
+ return;
+ }
+
+ auto &poll_voters = it->second;
+ CHECK(poll_voters.size() == poll->options.size());
+ CHECK(option_index < poll_voters.size());
+ poll_voters[option_index].was_invalidated = true;
+}
+
+PollManager::PollOptionVoters &PollManager::get_poll_option_voters(const Poll *poll, PollId poll_id, int32 option_id) {
+ auto &poll_voters = poll_voters_[poll_id];
+ if (poll_voters.empty()) {
+ poll_voters.resize(poll->options.size());
+ }
+ auto index = narrow_cast<size_t>(option_id);
+ CHECK(index < poll_voters.size());
+ return poll_voters[index];
+}
+
+void PollManager::get_poll_voters(PollId poll_id, FullMessageId full_message_id, int32 option_id, int32 offset,
+ int32 limit, Promise<std::pair<int32, vector<UserId>>> &&promise) {
+ if (is_local_poll_id(poll_id)) {
+ return promise.set_error(Status::Error(400, "Poll results can't be received"));
+ }
+ if (offset < 0) {
+ return promise.set_error(Status::Error(400, "Invalid offset specified"));
+ }
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
+ }
+ if (limit > MAX_GET_POLL_VOTERS) {
+ limit = MAX_GET_POLL_VOTERS;
+ }
+
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+ if (option_id < 0 || static_cast<size_t>(option_id) >= poll->options.size()) {
+ return promise.set_error(Status::Error(400, "Invalid option ID specified"));
+ }
+ if (poll->is_anonymous) {
+ return promise.set_error(Status::Error(400, "Poll is anonymous"));
+ }
+
+ auto &voters = get_poll_option_voters(poll, poll_id, option_id);
+ if (voters.pending_queries.empty() && voters.was_invalidated && offset == 0) {
+ voters.voter_user_ids.clear();
+ voters.next_offset.clear();
+ voters.was_invalidated = false;
+ }
+
+ auto cur_offset = narrow_cast<int32>(voters.voter_user_ids.size());
+
+ if (offset > cur_offset) {
+ return promise.set_error(Status::Error(400, "Too big offset specified; voters can be received only consequently"));
+ }
+ if (offset < cur_offset) {
+ vector<UserId> result;
+ for (int32 i = offset; i != cur_offset && i - offset < limit; i++) {
+ result.push_back(voters.voter_user_ids[i]);
+ }
+ return promise.set_value({max(poll->options[option_id].voter_count, cur_offset), std::move(result)});
+ }
+
+ if (poll->options[option_id].voter_count == 0 || (voters.next_offset.empty() && cur_offset > 0)) {
+ return promise.set_value({0, vector<UserId>()});
+ }
+
+ voters.pending_queries.push_back(std::move(promise));
+ if (voters.pending_queries.size() > 1) {
+ return;
+ }
+
+ unload_poll_timeout_.cancel_timeout(poll_id.get());
+
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), poll_id, option_id, offset = voters.next_offset,
+ limit](Result<tl_object_ptr<telegram_api::messages_votesList>> &&result) mutable {
+ send_closure(actor_id, &PollManager::on_get_poll_voters, poll_id, option_id, std::move(offset), limit,
+ std::move(result));
+ });
+ td_->create_handler<GetPollVotersQuery>(std::move(query_promise))
+ ->send(poll_id, full_message_id, BufferSlice(poll->options[option_id].data), voters.next_offset, max(limit, 10));
+}
+
+void PollManager::on_get_poll_voters(PollId poll_id, int32 option_id, string offset, int32 limit,
+ Result<tl_object_ptr<telegram_api::messages_votesList>> &&result) {
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+ if (option_id < 0 || static_cast<size_t>(option_id) >= poll->options.size()) {
+ LOG(ERROR) << "Can't process voters for option " << option_id << " in " << poll_id << ", because it has only "
+ << poll->options.size() << " options";
+ return;
+ }
+ if (poll->is_anonymous) {
+ // just in case
+ result = Status::Error(400, "Poll is anonymous");
+ }
+
+ auto &voters = get_poll_option_voters(poll, poll_id, option_id);
+ if (voters.next_offset != offset) {
+ LOG(ERROR) << "Expected results for option " << option_id << " in " << poll_id << " with offset "
+ << voters.next_offset << ", but received with " << offset;
+ return;
+ }
+ auto promises = std::move(voters.pending_queries);
+ if (promises.empty()) {
+ LOG(ERROR) << "Have no waiting promises for option " << option_id << " in " << poll_id;
+ return;
+ }
+ if (result.is_error()) {
+ fail_promises(promises, result.move_as_error());
+ return;
+ }
+
+ auto vote_list = result.move_as_ok();
+ td_->contacts_manager_->on_get_users(std::move(vote_list->users_), "on_get_poll_voters");
+
+ voters.next_offset = std::move(vote_list->next_offset_);
+ if (poll->options[option_id].voter_count != vote_list->count_) {
+ ++current_generation_;
+ update_poll_timeout_.set_timeout_in(poll_id.get(), 0.0);
+ }
+
+ vector<UserId> user_ids;
+ for (auto &user_vote : vote_list->votes_) {
+ UserId user_id;
+ downcast_call(*user_vote, [&user_id](auto &voter) { user_id = UserId(voter.user_id_); });
+ if (!user_id.is_valid()) {
+ LOG(ERROR) << "Receive " << user_id << " as voter in " << poll_id;
+ continue;
+ }
+
+ switch (user_vote->get_id()) {
+ case telegram_api::messageUserVote::ID: {
+ auto voter = telegram_api::move_object_as<telegram_api::messageUserVote>(user_vote);
+ if (voter->option_ != poll->options[option_id].data) {
+ continue;
+ }
+
+ user_ids.push_back(user_id);
+ break;
+ }
+ case telegram_api::messageUserVoteInputOption::ID:
+ user_ids.push_back(user_id);
+ break;
+ case telegram_api::messageUserVoteMultiple::ID: {
+ auto voter = telegram_api::move_object_as<telegram_api::messageUserVoteMultiple>(user_vote);
+ if (!td::contains(voter->options_, poll->options[option_id].data)) {
+ continue;
+ }
+
+ user_ids.push_back(user_id);
+ break;
+ }
+ }
+ }
+ voters.voter_user_ids.insert(voters.voter_user_ids.end(), user_ids.begin(), user_ids.end());
+ if (static_cast<int32>(user_ids.size()) > limit) {
+ user_ids.resize(limit);
+ }
+ auto known_voter_count = narrow_cast<int32>(voters.voter_user_ids.size());
+ if (voters.next_offset.empty() && known_voter_count != vote_list->count_) {
+ // invalidate_poll_option_voters(poll, poll_id, option_id);
+ voters.was_invalidated = true;
+ }
+
+ for (auto &promise : promises) {
+ promise.set_value({max(vote_list->count_, known_voter_count), vector<UserId>(user_ids)});
+ }
+}
+
+void PollManager::stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
+ Promise<Unit> &&promise) {
+ if (is_local_poll_id(poll_id)) {
+ LOG(ERROR) << "Receive local " << poll_id << " from " << full_message_id << " in stop_poll";
+ stop_local_poll(poll_id);
+ promise.set_value(Unit());
+ return;
+ }
+ auto poll = get_poll_editable(poll_id);
+ CHECK(poll != nullptr);
+ if (poll->is_closed) {
+ promise.set_value(Unit());
+ return;
+ }
+
+ ++current_generation_;
+
+ poll->is_closed = true;
+ notify_on_poll_update(poll_id);
+ save_poll(poll, poll_id);
+
+ do_stop_poll(poll_id, full_message_id, std::move(reply_markup), 0, std::move(promise));
+}
+
+class PollManager::StopPollLogEvent {
+ public:
+ PollId poll_id_;
+ FullMessageId full_message_id_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(poll_id_, storer);
+ td::store(full_message_id_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(poll_id_, parser);
+ td::parse(full_message_id_, parser);
+ }
+};
+
+void PollManager::do_stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
+ uint64 log_event_id, Promise<Unit> &&promise) {
+ LOG(INFO) << "Stop " << poll_id << " from " << full_message_id;
+ CHECK(poll_id.is_valid());
+
+ if (log_event_id == 0 && G()->parameters().use_message_db && reply_markup == nullptr) {
+ StopPollLogEvent log_event{poll_id, full_message_id};
+ log_event_id =
+ binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::StopPoll, get_log_event_storer(log_event));
+ }
+
+ unload_poll_timeout_.cancel_timeout(poll_id.get());
+
+ bool is_inserted = being_closed_polls_.insert(poll_id).second;
+ CHECK(is_inserted);
+ auto new_promise = PromiseCreator::lambda([actor_id = actor_id(this), poll_id, full_message_id, log_event_id,
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ send_closure(actor_id, &PollManager::on_stop_poll_finished, poll_id, full_message_id, log_event_id,
+ std::move(result), std::move(promise));
+ });
+
+ td_->create_handler<StopPollQuery>(std::move(new_promise))->send(full_message_id, std::move(reply_markup), poll_id);
+}
+
+void PollManager::on_stop_poll_finished(PollId poll_id, FullMessageId full_message_id, uint64 log_event_id,
+ Result<Unit> &&result, Promise<Unit> &&promise) {
+ being_closed_polls_.erase(poll_id);
+
+ if (log_event_id != 0 && !G()->close_flag()) {
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ }
+
+ if (td_->auth_manager_->is_bot()) {
+ if ((server_poll_messages_.count(poll_id) > 0 && server_poll_messages_[poll_id].count(full_message_id) > 0) ||
+ (other_poll_messages_.count(poll_id) > 0 && other_poll_messages_[poll_id].count(full_message_id) > 0)) {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ }
+ }
+
+ promise.set_result(std::move(result));
+}
+
+void PollManager::stop_local_poll(PollId poll_id) {
+ CHECK(is_local_poll_id(poll_id));
+ auto poll = get_poll_editable(poll_id);
+ CHECK(poll != nullptr);
+ if (poll->is_closed) {
+ return;
+ }
+
+ poll->is_closed = true;
+ notify_on_poll_update(poll_id);
+}
+
+double PollManager::get_polling_timeout() const {
+ double result = td_->is_online() ? 60 : 30 * 60;
+ return result * Random::fast(70, 100) * 0.01;
+}
+
+void PollManager::on_update_poll_timeout(PollId poll_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+ CHECK(!td_->auth_manager_->is_bot());
+ CHECK(!is_local_poll_id(poll_id));
+
+ auto poll = get_poll(poll_id);
+ if (poll == nullptr || (poll->is_closed && poll->is_updated_after_close)) {
+ return;
+ }
+ if (pending_answers_.count(poll_id) > 0) {
+ LOG(INFO) << "Skip fetching results of " << poll_id << ", because it is being voted now";
+ return;
+ }
+
+ if (server_poll_messages_.count(poll_id) == 0) {
+ return;
+ }
+
+ auto full_message_id = server_poll_messages_[poll_id].get_random();
+ LOG(INFO) << "Fetching results of " << poll_id << " from " << full_message_id;
+ auto query_promise = PromiseCreator::lambda([poll_id, generation = current_generation_, actor_id = actor_id(this)](
+ Result<tl_object_ptr<telegram_api::Updates>> &&result) {
+ send_closure(actor_id, &PollManager::on_get_poll_results, poll_id, generation, std::move(result));
+ });
+ td_->create_handler<GetPollResultsQuery>(std::move(query_promise))->send(poll_id, full_message_id);
+}
+
+void PollManager::on_close_poll_timeout(PollId poll_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+ CHECK(!is_local_poll_id(poll_id));
+
+ auto poll = get_poll_editable(poll_id);
+ if (poll == nullptr || poll->is_closed || poll->close_date == 0) {
+ return;
+ }
+
+ LOG(INFO) << "Trying to close " << poll_id << " by timer";
+ if (poll->close_date <= G()->server_time()) {
+ poll->is_closed = true;
+ notify_on_poll_update(poll_id);
+ save_poll(poll, poll_id);
+
+ // don't send updatePoll for bots, because there is no way to guarantee it
+
+ if (!td_->auth_manager_->is_bot()) {
+ update_poll_timeout_.set_timeout_in(poll_id.get(), 1.0);
+ }
+ } else {
+ close_poll_timeout_.set_timeout_in(poll_id.get(), poll->close_date - G()->server_time() + 1e-3);
+ }
+}
+
+void PollManager::on_unload_poll_timeout(PollId poll_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (is_local_poll_id(poll_id)) {
+ LOG(INFO) << "Forget " << poll_id;
+
+ auto is_deleted = polls_.erase(poll_id) > 0;
+ CHECK(is_deleted);
+
+ CHECK(poll_voters_.count(poll_id) == 0);
+ CHECK(loaded_from_database_polls_.count(poll_id) == 0);
+ return;
+ }
+
+ if (!can_unload_poll(poll_id)) {
+ return;
+ }
+ if (!have_poll(poll_id)) {
+ return;
+ }
+
+ LOG(INFO) << "Unload " << poll_id;
+
+ update_poll_timeout_.cancel_timeout(poll_id.get());
+ close_poll_timeout_.cancel_timeout(poll_id.get());
+
+ auto is_deleted = polls_.erase(poll_id) > 0;
+ CHECK(is_deleted);
+
+ poll_voters_.erase(poll_id);
+ loaded_from_database_polls_.erase(poll_id);
+ unload_poll_timeout_.cancel_timeout(poll_id.get());
+}
+
+void PollManager::forget_local_poll(PollId poll_id) {
+ CHECK(is_local_poll_id(poll_id));
+ unload_poll_timeout_.set_timeout_in(poll_id.get(), UNLOAD_POLL_DELAY);
+}
+
+void PollManager::on_get_poll_results(PollId poll_id, uint64 generation,
+ Result<tl_object_ptr<telegram_api::Updates>> result) {
+ auto poll = get_poll(poll_id);
+ if (poll == nullptr) {
+ return;
+ }
+ if (result.is_error()) {
+ if (!(poll->is_closed && poll->is_updated_after_close) && !G()->close_flag() && !td_->auth_manager_->is_bot()) {
+ auto timeout = get_polling_timeout();
+ LOG(INFO) << "Schedule updating of " << poll_id << " in " << timeout;
+ update_poll_timeout_.add_timeout_in(poll_id.get(), timeout);
+ }
+ return;
+ }
+ if (result.ok() == nullptr) {
+ return;
+ }
+ if (generation != current_generation_) {
+ LOG(INFO) << "Receive possibly outdated result of " << poll_id << ", reget it";
+ if (!(poll->is_closed && poll->is_updated_after_close) && !G()->close_flag() && !td_->auth_manager_->is_bot()) {
+ update_poll_timeout_.set_timeout_in(poll_id.get(), 0.0);
+ }
+ return;
+ }
+
+ td_->updates_manager_->on_get_updates(result.move_as_ok(), Promise<Unit>());
+}
+
+void PollManager::on_online() {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ server_poll_messages_.foreach([&](const PollId &poll_id, WaitFreeHashSet<FullMessageId, FullMessageIdHash> &) {
+ if (update_poll_timeout_.has_timeout(poll_id.get())) {
+ auto timeout = Random::fast(3, 30);
+ LOG(INFO) << "Schedule updating of " << poll_id << " in " << timeout;
+ update_poll_timeout_.set_timeout_in(poll_id.get(), timeout);
+ }
+ });
+}
+
+PollId PollManager::dup_poll(PollId poll_id) {
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+
+ auto question = poll->question;
+ auto options = transform(poll->options, [](auto &option) { return option.text; });
+ auto explanation = poll->explanation;
+ return create_poll(std::move(question), std::move(options), poll->is_anonymous, poll->allow_multiple_answers,
+ poll->is_quiz, poll->correct_option_id, std::move(explanation), poll->open_period,
+ poll->open_period == 0 ? 0 : G()->unix_time(), false);
+}
+
+bool PollManager::has_input_media(PollId poll_id) const {
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+ return !poll->is_quiz || poll->correct_option_id >= 0;
+}
+
+tl_object_ptr<telegram_api::InputMedia> PollManager::get_input_media(PollId poll_id) const {
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+
+ int32 poll_flags = 0;
+ if (!poll->is_anonymous) {
+ poll_flags |= telegram_api::poll::PUBLIC_VOTERS_MASK;
+ }
+ if (poll->allow_multiple_answers) {
+ poll_flags |= telegram_api::poll::MULTIPLE_CHOICE_MASK;
+ }
+ if (poll->is_quiz) {
+ poll_flags |= telegram_api::poll::QUIZ_MASK;
+ }
+ if (poll->open_period != 0) {
+ poll_flags |= telegram_api::poll::CLOSE_PERIOD_MASK;
+ }
+ if (poll->close_date != 0) {
+ poll_flags |= telegram_api::poll::CLOSE_DATE_MASK;
+ }
+ if (poll->is_closed) {
+ poll_flags |= telegram_api::poll::CLOSED_MASK;
+ }
+
+ int32 flags = 0;
+ vector<BufferSlice> correct_answers;
+ if (poll->is_quiz) {
+ flags |= telegram_api::inputMediaPoll::CORRECT_ANSWERS_MASK;
+ CHECK(poll->correct_option_id >= 0);
+ CHECK(static_cast<size_t>(poll->correct_option_id) < poll->options.size());
+ correct_answers.push_back(BufferSlice(poll->options[poll->correct_option_id].data));
+
+ if (!poll->explanation.text.empty()) {
+ flags |= telegram_api::inputMediaPoll::SOLUTION_MASK;
+ }
+ }
+ return telegram_api::make_object<telegram_api::inputMediaPoll>(
+ flags,
+ telegram_api::make_object<telegram_api::poll>(
+ 0, poll_flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, poll->question,
+ transform(poll->options, get_input_poll_option), poll->open_period, poll->close_date),
+ std::move(correct_answers), poll->explanation.text,
+ get_input_message_entities(td_->contacts_manager_.get(), poll->explanation.entities, "get_input_media_poll"));
+}
+
+vector<PollManager::PollOption> PollManager::get_poll_options(
+ vector<tl_object_ptr<telegram_api::pollAnswer>> &&poll_options) {
+ return transform(std::move(poll_options), [](tl_object_ptr<telegram_api::pollAnswer> &&poll_option) {
+ PollOption option;
+ option.text = std::move(poll_option->text_);
+ option.data = poll_option->option_.as_slice().str();
+ return option;
+ });
+}
+
+PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll> &&poll_server,
+ tl_object_ptr<telegram_api::pollResults> &&poll_results, const char *source) {
+ bool is_bot = td_->auth_manager_->is_bot();
+ bool need_update_poll = poll_id.is_valid() && is_bot;
+ if (!poll_id.is_valid() && poll_server != nullptr) {
+ poll_id = PollId(poll_server->id_);
+ }
+ if (!poll_id.is_valid() || is_local_poll_id(poll_id)) {
+ LOG(ERROR) << "Receive " << poll_id << " from " << source << ": " << oneline(to_string(poll_server)) << " "
+ << oneline(to_string(poll_results));
+ return PollId();
+ }
+ if (poll_server != nullptr && poll_server->id_ != poll_id.get()) {
+ LOG(ERROR) << "Receive poll " << poll_server->id_ << " instead of " << poll_id << " from " << source;
+ return PollId();
+ }
+ constexpr size_t MAX_POLL_OPTIONS = 10; // server-side limit
+ if (poll_server != nullptr &&
+ (poll_server->answers_.size() <= 1 || poll_server->answers_.size() > 10 * MAX_POLL_OPTIONS)) {
+ LOG(ERROR) << "Receive " << poll_id << " from " << source
+ << " with wrong number of answers: " << to_string(poll_server);
+ return PollId();
+ }
+ if (poll_server != nullptr) {
+ FlatHashSet<Slice, SliceHash> option_data;
+ for (auto &answer : poll_server->answers_) {
+ if (answer->option_.empty()) {
+ LOG(ERROR) << "Receive " << poll_id << " from " << source
+ << " with an empty option data: " << to_string(poll_server);
+ return PollId();
+ }
+ option_data.insert(answer->option_.as_slice());
+ }
+ if (option_data.size() != poll_server->answers_.size()) {
+ LOG(ERROR) << "Receive " << poll_id << " from " << source
+ << " with duplicate options: " << to_string(poll_server);
+ return PollId();
+ }
+ }
+
+ auto poll = get_poll_force(poll_id);
+ bool is_changed = false;
+ bool need_save_to_database = false;
+ if (poll == nullptr) {
+ if (poll_server == nullptr) {
+ LOG(INFO) << "Ignore " << poll_id << ", because have no data about it";
+ return PollId();
+ }
+
+ auto p = make_unique<Poll>();
+ poll = p.get();
+ polls_.set(poll_id, std::move(p));
+ }
+ CHECK(poll != nullptr);
+
+ bool poll_server_is_closed = false;
+ if (poll_server != nullptr) {
+ string correct_option_data;
+ if (poll->correct_option_id != -1) {
+ CHECK(0 <= poll->correct_option_id && poll->correct_option_id < static_cast<int32>(poll->options.size()));
+ correct_option_data = poll->options[poll->correct_option_id].data;
+ }
+ bool are_options_changed = false;
+ if (poll->options.size() != poll_server->answers_.size()) {
+ poll->options = get_poll_options(std::move(poll_server->answers_));
+ are_options_changed = true;
+ } else {
+ for (size_t i = 0; i < poll->options.size(); i++) {
+ if (poll->options[i].text != poll_server->answers_[i]->text_) {
+ poll->options[i].text = std::move(poll_server->answers_[i]->text_);
+ is_changed = true;
+ }
+ if (poll->options[i].data != poll_server->answers_[i]->option_.as_slice()) {
+ poll->options[i].data = poll_server->answers_[i]->option_.as_slice().str();
+ poll->options[i].voter_count = 0;
+ poll->options[i].is_chosen = false;
+ are_options_changed = true;
+ }
+ }
+ }
+ if (are_options_changed) {
+ if (!correct_option_data.empty()) {
+ poll->correct_option_id = -1;
+ for (size_t i = 0; i < poll->options.size(); i++) {
+ if (poll->options[i].data == correct_option_data) {
+ poll->correct_option_id = static_cast<int32>(i);
+ break;
+ }
+ }
+ }
+ auto it = poll_voters_.find(poll_id);
+ if (it != poll_voters_.end()) {
+ for (auto &voters : it->second) {
+ fail_promises(voters.pending_queries, Status::Error(500, "The poll was changed"));
+ }
+ poll_voters_.erase(it);
+ }
+ is_changed = true;
+ }
+ if (poll->question != poll_server->question_) {
+ poll->question = std::move(poll_server->question_);
+ is_changed = true;
+ }
+ poll_server_is_closed = (poll_server->flags_ & telegram_api::poll::CLOSED_MASK) != 0;
+ if (poll_server_is_closed && !poll->is_closed) {
+ poll->is_closed = poll_server_is_closed;
+ is_changed = true;
+ }
+ if (poll_server_is_closed && !poll->is_updated_after_close) {
+ poll->is_updated_after_close = true;
+ is_changed = true;
+ }
+ int32 open_period =
+ (poll_server->flags_ & telegram_api::poll::CLOSE_PERIOD_MASK) != 0 ? poll_server->close_period_ : 0;
+ int32 close_date = (poll_server->flags_ & telegram_api::poll::CLOSE_DATE_MASK) != 0 ? poll_server->close_date_ : 0;
+ if (close_date == 0 || open_period == 0) {
+ close_date = 0;
+ open_period = 0;
+ }
+ if (open_period != poll->open_period) {
+ poll->open_period = open_period;
+ if (!poll->is_closed) {
+ is_changed = true;
+ } else {
+ need_save_to_database = true;
+ }
+ }
+ if (close_date != poll->close_date) {
+ poll->close_date = close_date;
+ if (!poll->is_closed) {
+ is_changed = true;
+ if (close_date != 0) {
+ if (close_date <= G()->server_time()) {
+ poll->is_closed = true;
+ } else {
+ close_poll_timeout_.set_timeout_in(poll_id.get(), close_date - G()->server_time() + 1e-3);
+ }
+ } else {
+ close_poll_timeout_.cancel_timeout(poll_id.get());
+ }
+ } else {
+ need_save_to_database = true;
+ }
+ }
+ bool is_anonymous = (poll_server->flags_ & telegram_api::poll::PUBLIC_VOTERS_MASK) == 0;
+ if (is_anonymous != poll->is_anonymous) {
+ poll->is_anonymous = is_anonymous;
+ is_changed = true;
+ }
+ bool allow_multiple_answers = (poll_server->flags_ & telegram_api::poll::MULTIPLE_CHOICE_MASK) != 0;
+ bool is_quiz = (poll_server->flags_ & telegram_api::poll::QUIZ_MASK) != 0;
+ if (is_quiz && allow_multiple_answers) {
+ LOG(ERROR) << "Receive quiz " << poll_id << " from " << source << " allowing multiple answers";
+ allow_multiple_answers = false;
+ }
+ if (allow_multiple_answers != poll->allow_multiple_answers) {
+ poll->allow_multiple_answers = allow_multiple_answers;
+ is_changed = true;
+ }
+ if (is_quiz != poll->is_quiz) {
+ poll->is_quiz = is_quiz;
+ is_changed = true;
+ }
+ }
+
+ CHECK(poll_results != nullptr);
+ bool is_min = poll_results->min_;
+ bool has_total_voters = (poll_results->flags_ & telegram_api::pollResults::TOTAL_VOTERS_MASK) != 0;
+ if (has_total_voters && poll_results->total_voters_ != poll->total_voter_count) {
+ poll->total_voter_count = poll_results->total_voters_;
+ if (poll->total_voter_count < 0) {
+ LOG(ERROR) << "Receive " << poll->total_voter_count << " voters in " << poll_id << " from " << source;
+ poll->total_voter_count = 0;
+ }
+ is_changed = true;
+ }
+ int32 correct_option_id = -1;
+ for (auto &poll_result : poll_results->results_) {
+ Slice data = poll_result->option_.as_slice();
+ for (size_t option_index = 0; option_index < poll->options.size(); option_index++) {
+ auto &option = poll->options[option_index];
+ if (option.data != data) {
+ continue;
+ }
+ if (!is_min) {
+ bool is_chosen = poll_result->chosen_;
+ if (is_chosen != option.is_chosen) {
+ option.is_chosen = is_chosen;
+ is_changed = true;
+ }
+ }
+ if (!is_min || poll_server_is_closed) {
+ bool is_correct = poll_result->correct_;
+ if (is_correct) {
+ if (correct_option_id != -1) {
+ LOG(ERROR) << "Receive more than 1 correct answers " << correct_option_id << " and " << option_index
+ << " in " << poll_id << " from " << source;
+ }
+ correct_option_id = static_cast<int32>(option_index);
+ }
+ } else {
+ correct_option_id = poll->correct_option_id;
+ }
+
+ if (poll_result->voters_ < 0) {
+ LOG(ERROR) << "Receive " << poll_result->voters_ << " voters for an option in " << poll_id << " from "
+ << source;
+ poll_result->voters_ = 0;
+ }
+ if (option.is_chosen && poll_result->voters_ == 0) {
+ LOG(ERROR) << "Receive 0 voters for the chosen option in " << poll_id << " from " << source;
+ poll_result->voters_ = 1;
+ }
+ if (poll_result->voters_ > poll->total_voter_count) {
+ LOG(ERROR) << "Have only " << poll->total_voter_count << " poll voters, but there are " << poll_result->voters_
+ << " voters for an option in " << poll_id << " from " << source;
+ poll->total_voter_count = poll_result->voters_;
+ }
+ auto max_voter_count = std::numeric_limits<int32>::max() / narrow_cast<int32>(poll->options.size()) - 2;
+ if (poll_result->voters_ > max_voter_count) {
+ LOG(ERROR) << "Have too many " << poll_result->voters_ << " poll voters for an option in " << poll_id
+ << " from " << source;
+ poll_result->voters_ = max_voter_count;
+ }
+ if (poll_result->voters_ != option.voter_count) {
+ invalidate_poll_option_voters(poll, poll_id, option_index);
+ option.voter_count = poll_result->voters_;
+ is_changed = true;
+ }
+ }
+ }
+ if (!poll_results->results_.empty() && has_total_voters) {
+ int32 max_total_voter_count = 0;
+ for (auto &option : poll->options) {
+ max_total_voter_count += option.voter_count;
+ }
+ if (poll->total_voter_count > max_total_voter_count && max_total_voter_count != 0) {
+ LOG(ERROR) << "Have only " << max_total_voter_count << " total poll voters, but there are "
+ << poll->total_voter_count << " voters in " << poll_id << " from " << source;
+ poll->total_voter_count = max_total_voter_count;
+ }
+ }
+
+ auto entities =
+ get_message_entities(td_->contacts_manager_.get(), std::move(poll_results->solution_entities_), source);
+ auto status = fix_formatted_text(poll_results->solution_, entities, true, true, true, true, false);
+ if (status.is_error()) {
+ if (!clean_input_string(poll_results->solution_)) {
+ poll_results->solution_.clear();
+ }
+ entities = find_entities(poll_results->solution_, true, true);
+ }
+ FormattedText explanation{std::move(poll_results->solution_), std::move(entities)};
+
+ if (poll->is_quiz) {
+ if (poll->correct_option_id != correct_option_id) {
+ if (correct_option_id == -1 && poll->correct_option_id != -1) {
+ LOG(ERROR) << "Can't change correct option of " << poll_id << " from " << poll->correct_option_id << " to "
+ << correct_option_id << " from " << source;
+ } else {
+ poll->correct_option_id = correct_option_id;
+ is_changed = true;
+ }
+ }
+ if (poll->explanation != explanation && (!is_min || poll_server_is_closed)) {
+ if (explanation.text.empty() && !poll->explanation.text.empty()) {
+ LOG(ERROR) << "Can't change known " << poll_id << " explanation to empty from " << source;
+ } else {
+ poll->explanation = std::move(explanation);
+ is_changed = true;
+ }
+ }
+ } else {
+ if (correct_option_id != -1) {
+ LOG(ERROR) << "Receive correct option " << correct_option_id << " in non-quiz " << poll_id << " from " << source;
+ }
+ if (!explanation.text.empty()) {
+ LOG(ERROR) << "Receive explanation " << explanation << " in non-quiz " << poll_id << " from " << source;
+ }
+ }
+
+ vector<UserId> recent_voter_user_ids;
+ if (!is_bot) {
+ for (auto &user_id_int : poll_results->recent_voters_) {
+ UserId user_id(user_id_int);
+ if (user_id.is_valid()) {
+ recent_voter_user_ids.push_back(user_id);
+ } else {
+ LOG(ERROR) << "Receive " << user_id << " as recent voter in " << poll_id << " from " << source;
+ }
+ }
+ }
+ if (poll->is_anonymous && !recent_voter_user_ids.empty()) {
+ LOG(ERROR) << "Receive anonymous " << poll_id << " with recent voters " << recent_voter_user_ids << " from "
+ << source;
+ recent_voter_user_ids.clear();
+ }
+ if (recent_voter_user_ids != poll->recent_voter_user_ids) {
+ poll->recent_voter_user_ids = std::move(recent_voter_user_ids);
+ invalidate_poll_voters(poll, poll_id);
+ is_changed = true;
+ }
+
+ if (!is_bot && !poll->is_closed) {
+ auto timeout = get_polling_timeout();
+ LOG(INFO) << "Schedule updating of " << poll_id << " in " << timeout;
+ update_poll_timeout_.set_timeout_in(poll_id.get(), timeout);
+ }
+ if (is_changed) {
+ notify_on_poll_update(poll_id);
+ }
+ if (is_changed || need_save_to_database) {
+ save_poll(poll, poll_id);
+ }
+ if (need_update_poll && (is_changed || (poll->is_closed && being_closed_polls_.erase(poll_id) != 0))) {
+ send_closure(G()->td(), &Td::send_update, td_api::make_object<td_api::updatePoll>(get_poll_object(poll_id, poll)));
+
+ schedule_poll_unload(poll_id);
+ }
+ return poll_id;
+}
+
+void PollManager::on_get_poll_vote(PollId poll_id, UserId user_id, vector<BufferSlice> &&options) {
+ if (!poll_id.is_valid()) {
+ LOG(ERROR) << "Receive updateMessagePollVote about invalid " << poll_id;
+ return;
+ }
+ if (!user_id.is_valid()) {
+ LOG(ERROR) << "Receive updateMessagePollVote from invalid " << user_id;
+ return;
+ }
+ if (!td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ vector<int32> option_ids;
+ for (auto &option : options) {
+ auto slice = option.as_slice();
+ if (slice.size() != 1 || slice[0] < '0' || slice[0] > '9') {
+ LOG(INFO) << "Receive updateMessagePollVote with unexpected option \"" << format::escaped(slice) << '"';
+ return;
+ }
+ option_ids.push_back(static_cast<int32>(slice[0] - '0'));
+ }
+
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updatePollAnswer>(
+ poll_id.get(), td_->contacts_manager_->get_user_id_object(user_id, "on_get_poll_vote"),
+ std::move(option_ids)));
+}
+
+void PollManager::on_binlog_events(vector<BinlogEvent> &&events) {
+ if (G()->close_flag()) {
+ return;
+ }
+ for (auto &event : events) {
+ switch (event.type_) {
+ case LogEvent::HandlerType::SetPollAnswer: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ SetPollAnswerLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.full_message_id_.get_dialog_id();
+
+ Dependencies dependencies;
+ dependencies.add_dialog_dependencies(dialog_id); // do not load the dialog itself
+ dependencies.resolve_force(td_, "SetPollAnswerLogEvent");
+
+ do_set_poll_answer(log_event.poll_id_, log_event.full_message_id_, std::move(log_event.options_), event.id_,
+ Auto());
+ break;
+ }
+ case LogEvent::HandlerType::StopPoll: {
+ if (!G()->parameters().use_message_db) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ break;
+ }
+
+ StopPollLogEvent log_event;
+ log_event_parse(log_event, event.data_).ensure();
+
+ auto dialog_id = log_event.full_message_id_.get_dialog_id();
+
+ Dependencies dependencies;
+ dependencies.add_dialog_dependencies(dialog_id); // do not load the dialog itself
+ dependencies.resolve_force(td_, "StopPollLogEvent");
+
+ do_stop_poll(log_event.poll_id_, log_event.full_message_id_, nullptr, event.id_, Auto());
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unsupported log event type " << event.type_;
+ }
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PollManager.h b/protocols/Telegram/tdlib/td/td/telegram/PollManager.h
new file mode 100644
index 0000000000..fb38dfaac8
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PollManager.h
@@ -0,0 +1,254 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/PollId.h"
+#include "td/telegram/ReplyMarkup.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/MultiTimeout.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+#include "td/utils/WaitFreeHashMap.h"
+#include "td/utils/WaitFreeHashSet.h"
+
+#include <utility>
+
+namespace td {
+
+struct BinlogEvent;
+
+class Td;
+
+class PollManager final : public Actor {
+ public:
+ PollManager(Td *td, ActorShared<> parent);
+
+ PollManager(const PollManager &) = delete;
+ PollManager &operator=(const PollManager &) = delete;
+ PollManager(PollManager &&) = delete;
+ PollManager &operator=(PollManager &&) = delete;
+ ~PollManager() final;
+
+ static bool is_local_poll_id(PollId poll_id);
+
+ PollId create_poll(string &&question, vector<string> &&options, bool is_anonymous, bool allow_multiple_answers,
+ bool is_quiz, int32 correct_option_id, FormattedText &&explanation, int32 open_period,
+ int32 close_date, bool is_closed);
+
+ void register_poll(PollId poll_id, FullMessageId full_message_id, const char *source);
+
+ void unregister_poll(PollId poll_id, FullMessageId full_message_id, const char *source);
+
+ bool get_poll_is_closed(PollId poll_id) const;
+
+ bool get_poll_is_anonymous(PollId poll_id) const;
+
+ string get_poll_search_text(PollId poll_id) const;
+
+ void set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector<int32> &&option_ids,
+ Promise<Unit> &&promise);
+
+ void get_poll_voters(PollId poll_id, FullMessageId full_message_id, int32 option_id, int32 offset, int32 limit,
+ Promise<std::pair<int32, vector<UserId>>> &&promise);
+
+ void stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
+ Promise<Unit> &&promise);
+
+ void stop_local_poll(PollId poll_id);
+
+ PollId dup_poll(PollId poll_id);
+
+ bool has_input_media(PollId poll_id) const;
+
+ tl_object_ptr<telegram_api::InputMedia> get_input_media(PollId poll_id) const;
+
+ PollId on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll> &&poll_server,
+ tl_object_ptr<telegram_api::pollResults> &&poll_results, const char *source);
+
+ void on_get_poll_vote(PollId poll_id, UserId user_id, vector<BufferSlice> &&options);
+
+ td_api::object_ptr<td_api::poll> get_poll_object(PollId poll_id) const;
+
+ void on_binlog_events(vector<BinlogEvent> &&events);
+
+ static vector<int32> get_vote_percentage(const vector<int32> &voter_counts, int32 total_voter_count);
+
+ template <class StorerT>
+ void store_poll(PollId poll_id, StorerT &storer) const;
+
+ template <class ParserT>
+ PollId parse_poll(ParserT &parser);
+
+ private:
+ struct PollOption {
+ string text;
+ string data;
+ int32 voter_count = 0;
+ bool is_chosen = false;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ struct Poll {
+ string question;
+ vector<PollOption> options;
+ vector<UserId> recent_voter_user_ids;
+ FormattedText explanation;
+ int32 total_voter_count = 0;
+ int32 correct_option_id = -1;
+ int32 open_period = 0;
+ int32 close_date = 0;
+ bool is_anonymous = true;
+ bool allow_multiple_answers = false;
+ bool is_quiz = false;
+ bool is_closed = false;
+ bool is_updated_after_close = false;
+ mutable bool was_saved = false;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ struct PollOptionVoters {
+ vector<UserId> voter_user_ids;
+ string next_offset;
+ vector<Promise<std::pair<int32, vector<UserId>>>> pending_queries;
+ bool was_invalidated = false; // the list needs to be invalidated when voters are changed
+ };
+
+ static constexpr int32 MAX_GET_POLL_VOTERS = 50; // server side limit
+ static constexpr int32 UNLOAD_POLL_DELAY = 600; // some reasonable value
+
+ class SetPollAnswerLogEvent;
+ class StopPollLogEvent;
+
+ void start_up() final;
+ void tear_down() final;
+
+ static void on_update_poll_timeout_callback(void *poll_manager_ptr, int64 poll_id_int);
+
+ static void on_close_poll_timeout_callback(void *poll_manager_ptr, int64 poll_id_int);
+
+ static void on_unload_poll_timeout_callback(void *poll_manager_ptr, int64 poll_id_int);
+
+ static td_api::object_ptr<td_api::pollOption> get_poll_option_object(const PollOption &poll_option);
+
+ static telegram_api::object_ptr<telegram_api::pollAnswer> get_input_poll_option(const PollOption &poll_option);
+
+ static vector<PollOption> get_poll_options(vector<tl_object_ptr<telegram_api::pollAnswer>> &&poll_options);
+
+ bool have_poll(PollId poll_id) const;
+
+ bool have_poll_force(PollId poll_id);
+
+ const Poll *get_poll(PollId poll_id) const;
+
+ const Poll *get_poll(PollId poll_id);
+
+ Poll *get_poll_editable(PollId poll_id);
+
+ bool can_unload_poll(PollId poll_id);
+
+ void schedule_poll_unload(PollId poll_id);
+
+ void notify_on_poll_update(PollId poll_id);
+
+ static string get_poll_database_key(PollId poll_id);
+
+ static void save_poll(const Poll *poll, PollId poll_id);
+
+ void on_load_poll_from_database(PollId poll_id, string value);
+
+ double get_polling_timeout() const;
+
+ void on_update_poll_timeout(PollId poll_id);
+
+ void on_close_poll_timeout(PollId poll_id);
+
+ void on_unload_poll_timeout(PollId poll_id);
+
+ void on_online();
+
+ Poll *get_poll_force(PollId poll_id);
+
+ td_api::object_ptr<td_api::poll> get_poll_object(PollId poll_id, const Poll *poll) const;
+
+ void on_get_poll_results(PollId poll_id, uint64 generation, Result<tl_object_ptr<telegram_api::Updates>> result);
+
+ void do_set_poll_answer(PollId poll_id, FullMessageId full_message_id, vector<string> &&options, uint64 log_event_id,
+ Promise<Unit> &&promise);
+
+ void on_set_poll_answer(PollId poll_id, uint64 generation, Result<tl_object_ptr<telegram_api::Updates>> &&result);
+
+ void on_set_poll_answer_finished(PollId poll_id, Result<Unit> &&result, vector<Promise<Unit>> &&promises);
+
+ void invalidate_poll_voters(const Poll *poll, PollId poll_id);
+
+ void invalidate_poll_option_voters(const Poll *poll, PollId poll_id, size_t option_index);
+
+ PollOptionVoters &get_poll_option_voters(const Poll *poll, PollId poll_id, int32 option_id);
+
+ void on_get_poll_voters(PollId poll_id, int32 option_id, string offset, int32 limit,
+ Result<tl_object_ptr<telegram_api::messages_votesList>> &&result);
+
+ void do_stop_poll(PollId poll_id, FullMessageId full_message_id, unique_ptr<ReplyMarkup> &&reply_markup,
+ uint64 log_event_id, Promise<Unit> &&promise);
+
+ void on_stop_poll_finished(PollId poll_id, FullMessageId full_message_id, uint64 log_event_id, Result<Unit> &&result,
+ Promise<Unit> &&promise);
+
+ void forget_local_poll(PollId poll_id);
+
+ MultiTimeout update_poll_timeout_{"UpdatePollTimeout"};
+ MultiTimeout close_poll_timeout_{"ClosePollTimeout"};
+ MultiTimeout unload_poll_timeout_{"UnloadPollTimeout"};
+
+ Td *td_;
+ ActorShared<> parent_;
+ WaitFreeHashMap<PollId, unique_ptr<Poll>, PollIdHash> polls_;
+
+ WaitFreeHashMap<PollId, WaitFreeHashSet<FullMessageId, FullMessageIdHash>, PollIdHash> server_poll_messages_;
+ WaitFreeHashMap<PollId, WaitFreeHashSet<FullMessageId, FullMessageIdHash>, PollIdHash> other_poll_messages_;
+
+ struct PendingPollAnswer {
+ vector<string> options_;
+ vector<Promise<Unit>> promises_;
+ uint64 generation_ = 0;
+ uint64 log_event_id_ = 0;
+ NetQueryRef query_ref_;
+ };
+ FlatHashMap<PollId, PendingPollAnswer, PollIdHash> pending_answers_;
+
+ FlatHashMap<PollId, vector<PollOptionVoters>, PollIdHash> poll_voters_;
+
+ int64 current_local_poll_id_ = 0;
+
+ uint64 current_generation_ = 0;
+
+ FlatHashSet<PollId, PollIdHash> loaded_from_database_polls_;
+
+ FlatHashSet<PollId, PollIdHash> being_closed_polls_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PollManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/PollManager.hpp
new file mode 100644
index 0000000000..1c9f6b1a32
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PollManager.hpp
@@ -0,0 +1,224 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/PollManager.h"
+#include "td/telegram/Version.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void PollManager::PollOption::store(StorerT &storer) const {
+ using ::td::store;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_chosen);
+ END_STORE_FLAGS();
+
+ store(text, storer);
+ store(data, storer);
+ store(voter_count, storer);
+}
+
+template <class ParserT>
+void PollManager::PollOption::parse(ParserT &parser) {
+ using ::td::parse;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_chosen);
+ END_PARSE_FLAGS();
+
+ parse(text, parser);
+ parse(data, parser);
+ parse(voter_count, parser);
+}
+
+template <class StorerT>
+void PollManager::Poll::store(StorerT &storer) const {
+ using ::td::store;
+ bool is_public = !is_anonymous;
+ bool has_recent_voters = !recent_voter_user_ids.empty();
+ bool has_open_period = open_period != 0;
+ bool has_close_date = close_date != 0;
+ bool has_explanation = !explanation.text.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_closed);
+ STORE_FLAG(is_public);
+ STORE_FLAG(allow_multiple_answers);
+ STORE_FLAG(is_quiz);
+ STORE_FLAG(has_recent_voters);
+ STORE_FLAG(has_open_period);
+ STORE_FLAG(has_close_date);
+ STORE_FLAG(has_explanation);
+ STORE_FLAG(is_updated_after_close);
+ END_STORE_FLAGS();
+
+ store(question, storer);
+ store(options, storer);
+ store(total_voter_count, storer);
+ if (is_quiz) {
+ store(correct_option_id, storer);
+ }
+ if (has_recent_voters) {
+ store(recent_voter_user_ids, storer);
+ }
+ if (has_open_period) {
+ store(open_period, storer);
+ }
+ if (has_close_date) {
+ store(close_date, storer);
+ }
+ if (has_explanation) {
+ store(explanation, storer);
+ }
+}
+
+template <class ParserT>
+void PollManager::Poll::parse(ParserT &parser) {
+ using ::td::parse;
+ bool is_public;
+ bool has_recent_voters;
+ bool has_open_period;
+ bool has_close_date;
+ bool has_explanation;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_closed);
+ PARSE_FLAG(is_public);
+ PARSE_FLAG(allow_multiple_answers);
+ PARSE_FLAG(is_quiz);
+ PARSE_FLAG(has_recent_voters);
+ PARSE_FLAG(has_open_period);
+ PARSE_FLAG(has_close_date);
+ PARSE_FLAG(has_explanation);
+ PARSE_FLAG(is_updated_after_close);
+ END_PARSE_FLAGS();
+ is_anonymous = !is_public;
+
+ parse(question, parser);
+ parse(options, parser);
+ parse(total_voter_count, parser);
+ if (is_quiz) {
+ parse(correct_option_id, parser);
+ if (correct_option_id < -1 || correct_option_id >= static_cast<int32>(options.size())) {
+ parser.set_error("Wrong correct_option_id");
+ }
+ }
+ if (has_recent_voters) {
+ parse(recent_voter_user_ids, parser);
+ }
+ if (has_open_period) {
+ parse(open_period, parser);
+ }
+ if (has_close_date) {
+ parse(close_date, parser);
+ }
+ if (has_explanation) {
+ parse(explanation, parser);
+ }
+}
+
+template <class StorerT>
+void PollManager::store_poll(PollId poll_id, StorerT &storer) const {
+ td::store(poll_id.get(), storer);
+ if (is_local_poll_id(poll_id)) {
+ auto poll = get_poll(poll_id);
+ CHECK(poll != nullptr);
+ bool has_open_period = poll->open_period != 0;
+ bool has_close_date = poll->close_date != 0;
+ bool has_explanation = !poll->explanation.text.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(poll->is_closed);
+ STORE_FLAG(poll->is_anonymous);
+ STORE_FLAG(poll->allow_multiple_answers);
+ STORE_FLAG(poll->is_quiz);
+ STORE_FLAG(has_open_period);
+ STORE_FLAG(has_close_date);
+ STORE_FLAG(has_explanation);
+ END_STORE_FLAGS();
+ store(poll->question, storer);
+ vector<string> options = transform(poll->options, [](const PollOption &option) { return option.text; });
+ store(options, storer);
+ if (poll->is_quiz) {
+ store(poll->correct_option_id, storer);
+ }
+ if (has_open_period) {
+ store(poll->open_period, storer);
+ }
+ if (has_close_date) {
+ store(poll->close_date, storer);
+ }
+ if (has_explanation) {
+ store(poll->explanation, storer);
+ }
+ }
+}
+
+template <class ParserT>
+PollId PollManager::parse_poll(ParserT &parser) {
+ int64 poll_id_int;
+ td::parse(poll_id_int, parser);
+ PollId poll_id(poll_id_int);
+ if (is_local_poll_id(poll_id)) {
+ string question;
+ vector<string> options;
+ FormattedText explanation;
+ int32 open_period = 0;
+ int32 close_date = 0;
+ bool is_closed = false;
+ bool is_anonymous = true;
+ bool allow_multiple_answers = false;
+ bool is_quiz = false;
+ bool has_open_period = false;
+ bool has_close_date = false;
+ bool has_explanation = false;
+ int32 correct_option_id = -1;
+
+ if (parser.version() >= static_cast<int32>(Version::SupportPolls2_0)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_closed);
+ PARSE_FLAG(is_anonymous);
+ PARSE_FLAG(allow_multiple_answers);
+ PARSE_FLAG(is_quiz);
+ PARSE_FLAG(has_open_period);
+ PARSE_FLAG(has_close_date);
+ PARSE_FLAG(has_explanation);
+ END_PARSE_FLAGS();
+ }
+ parse(question, parser);
+ parse(options, parser);
+ if (is_quiz) {
+ parse(correct_option_id, parser);
+ if (correct_option_id < -1 || correct_option_id >= static_cast<int32>(options.size())) {
+ parser.set_error("Wrong correct_option_id");
+ }
+ }
+ if (has_open_period) {
+ parse(open_period, parser);
+ }
+ if (has_close_date) {
+ parse(close_date, parser);
+ }
+ if (has_explanation) {
+ parse(explanation, parser);
+ }
+ if (parser.get_error() != nullptr) {
+ return PollId();
+ }
+ return create_poll(std::move(question), std::move(options), is_anonymous, allow_multiple_answers, is_quiz,
+ correct_option_id, std::move(explanation), open_period, close_date, is_closed);
+ }
+
+ auto poll = get_poll_force(poll_id);
+ if (poll == nullptr) {
+ return PollId();
+ }
+ return poll_id;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Premium.cpp b/protocols/Telegram/tdlib/td/td/telegram/Premium.cpp
new file mode 100644
index 0000000000..6707d4a50a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Premium.cpp
@@ -0,0 +1,545 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Premium.h"
+
+#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/Application.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/PremiumGiftOption.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UpdatesManager.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/JsonBuilder.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+static td_api::object_ptr<td_api::PremiumFeature> get_premium_feature_object(Slice premium_feature) {
+ if (premium_feature == "double_limits") {
+ return td_api::make_object<td_api::premiumFeatureIncreasedLimits>();
+ }
+ if (premium_feature == "more_upload") {
+ return td_api::make_object<td_api::premiumFeatureIncreasedUploadFileSize>();
+ }
+ if (premium_feature == "faster_download") {
+ return td_api::make_object<td_api::premiumFeatureImprovedDownloadSpeed>();
+ }
+ if (premium_feature == "voice_to_text") {
+ return td_api::make_object<td_api::premiumFeatureVoiceRecognition>();
+ }
+ if (premium_feature == "no_ads") {
+ return td_api::make_object<td_api::premiumFeatureDisabledAds>();
+ }
+ if (premium_feature == "unique_reactions" || premium_feature == "infinite_reactions") {
+ return td_api::make_object<td_api::premiumFeatureUniqueReactions>();
+ }
+ if (premium_feature == "premium_stickers") {
+ return td_api::make_object<td_api::premiumFeatureUniqueStickers>();
+ }
+ if (premium_feature == "animated_emoji") {
+ return td_api::make_object<td_api::premiumFeatureCustomEmoji>();
+ }
+ if (premium_feature == "advanced_chat_management") {
+ return td_api::make_object<td_api::premiumFeatureAdvancedChatManagement>();
+ }
+ if (premium_feature == "profile_badge") {
+ return td_api::make_object<td_api::premiumFeatureProfileBadge>();
+ }
+ if (premium_feature == "emoji_status") {
+ return td_api::make_object<td_api::premiumFeatureEmojiStatus>();
+ }
+ if (premium_feature == "animated_userpics") {
+ return td_api::make_object<td_api::premiumFeatureAnimatedProfilePhoto>();
+ }
+ if (premium_feature == "forum_topic_icon") {
+ return td_api::make_object<td_api::premiumFeatureForumTopicIcon>();
+ }
+ if (premium_feature == "app_icons") {
+ return td_api::make_object<td_api::premiumFeatureAppIcons>();
+ }
+ return nullptr;
+}
+
+static Result<tl_object_ptr<telegram_api::InputStorePaymentPurpose>> get_input_store_payment_purpose(
+ Td *td, const td_api::object_ptr<td_api::StorePaymentPurpose> &purpose) {
+ if (purpose == nullptr) {
+ return Status::Error(400, "Purchase purpose must be non-empty");
+ }
+
+ switch (purpose->get_id()) {
+ case td_api::storePaymentPurposePremiumSubscription::ID: {
+ auto p = static_cast<const td_api::storePaymentPurposePremiumSubscription *>(purpose.get());
+ int32 flags = 0;
+ if (p->is_restore_) {
+ flags |= telegram_api::inputStorePaymentPremiumSubscription::RESTORE_MASK;
+ }
+ return make_tl_object<telegram_api::inputStorePaymentPremiumSubscription>(flags, false /*ignored*/);
+ }
+ case td_api::storePaymentPurposeGiftedPremium::ID: {
+ auto p = static_cast<const td_api::storePaymentPurposeGiftedPremium *>(purpose.get());
+ UserId user_id(p->user_id_);
+ TRY_RESULT(input_user, td->contacts_manager_->get_input_user(user_id));
+ if (p->amount_ <= 0 || !check_currency_amount(p->amount_)) {
+ return Status::Error(400, "Invalid amount of the currency specified");
+ }
+ return make_tl_object<telegram_api::inputStorePaymentGiftPremium>(std::move(input_user), p->currency_,
+ p->amount_);
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+class GetPremiumPromoQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::premiumState>> promise_;
+
+ public:
+ explicit GetPremiumPromoQuery(Promise<td_api::object_ptr<td_api::premiumState>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::help_getPremiumPromo()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_getPremiumPromo>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto promo = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetPremiumPromoQuery: " << to_string(promo);
+
+ td_->contacts_manager_->on_get_users(std::move(promo->users_), "GetPremiumPromoQuery");
+
+ auto state = get_message_text(td_->contacts_manager_.get(), std::move(promo->status_text_),
+ std::move(promo->status_entities_), true, true, 0, false, "GetPremiumPromoQuery");
+
+ if (promo->video_sections_.size() != promo->videos_.size()) {
+ return on_error(Status::Error(500, "Receive wrong number of videos"));
+ }
+
+ vector<td_api::object_ptr<td_api::premiumFeaturePromotionAnimation>> animations;
+ for (size_t i = 0; i < promo->video_sections_.size(); i++) {
+ auto feature = get_premium_feature_object(promo->video_sections_[i]);
+ if (feature == nullptr) {
+ continue;
+ }
+
+ auto video = std::move(promo->videos_[i]);
+ if (video->get_id() != telegram_api::document::ID) {
+ LOG(ERROR) << "Receive " << to_string(video) << " for " << promo->video_sections_[i];
+ continue;
+ }
+
+ auto parsed_document = td_->documents_manager_->on_get_document(move_tl_object_as<telegram_api::document>(video),
+ DialogId(), nullptr, Document::Type::Animation);
+
+ if (parsed_document.type != Document::Type::Animation) {
+ LOG(ERROR) << "Receive " << parsed_document.type << " for " << promo->video_sections_[i];
+ continue;
+ }
+
+ auto animation_object = td_->animations_manager_->get_animation_object(parsed_document.file_id);
+ animations.push_back(td_api::make_object<td_api::premiumFeaturePromotionAnimation>(std::move(feature),
+ std::move(animation_object)));
+ }
+
+ auto period_options = get_premium_gift_options(std::move(promo->period_options_));
+ promise_.set_value(td_api::make_object<td_api::premiumState>(get_formatted_text_object(state, true, 0),
+ get_premium_payment_options_object(period_options),
+ std::move(animations)));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class CanPurchasePremiumQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit CanPurchasePremiumQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose) {
+ auto r_input_purpose = get_input_store_payment_purpose(td_, purpose);
+ if (r_input_purpose.is_error()) {
+ return on_error(r_input_purpose.move_as_error());
+ }
+
+ send_query(
+ G()->net_query_creator().create(telegram_api::payments_canPurchasePremium(r_input_purpose.move_as_ok())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::payments_canPurchasePremium>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ if (result) {
+ return promise_.set_value(Unit());
+ }
+ on_error(Status::Error(400, "Premium can't be purchased"));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class AssignAppStoreTransactionQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit AssignAppStoreTransactionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(const string &receipt, td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose) {
+ auto r_input_purpose = get_input_store_payment_purpose(td_, purpose);
+ if (r_input_purpose.is_error()) {
+ return on_error(r_input_purpose.move_as_error());
+ }
+
+ send_query(G()->net_query_creator().create(
+ telegram_api::payments_assignAppStoreTransaction(BufferSlice(receipt), r_input_purpose.move_as_ok())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::payments_assignAppStoreTransaction>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for AssignAppStoreTransactionQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class AssignPlayMarketTransactionQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit AssignPlayMarketTransactionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(const string &package_name, const string &store_product_id, const string &purchase_token,
+ td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose) {
+ auto r_input_purpose = get_input_store_payment_purpose(td_, purpose);
+ if (r_input_purpose.is_error()) {
+ return on_error(r_input_purpose.move_as_error());
+ }
+ auto receipt = make_tl_object<telegram_api::dataJSON>(string());
+ receipt->data_ = json_encode<string>(json_object([&](auto &o) {
+ o("packageName", package_name);
+ o("purchaseToken", purchase_token);
+ o("productId", store_product_id);
+ }));
+ send_query(G()->net_query_creator().create(
+ telegram_api::payments_assignPlayMarketTransaction(std::move(receipt), r_input_purpose.move_as_ok())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::payments_assignPlayMarketTransaction>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for AssignPlayMarketTransactionQuery: " << to_string(ptr);
+ td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+const vector<Slice> &get_premium_limit_keys() {
+ static const vector<Slice> limit_keys{"channels",
+ "saved_gifs",
+ "stickers_faved",
+ "dialog_filters",
+ "dialog_filters_chats",
+ "dialogs_pinned",
+ "dialogs_folder_pinned",
+ "channels_public",
+ "caption_length",
+ "about_length"};
+ return limit_keys;
+}
+
+static Slice get_limit_type_key(const td_api::PremiumLimitType *limit_type) {
+ CHECK(limit_type != nullptr);
+ switch (limit_type->get_id()) {
+ case td_api::premiumLimitTypeSupergroupCount::ID:
+ return Slice("channels");
+ case td_api::premiumLimitTypeSavedAnimationCount::ID:
+ return Slice("saved_gifs");
+ case td_api::premiumLimitTypeFavoriteStickerCount::ID:
+ return Slice("stickers_faved");
+ case td_api::premiumLimitTypeChatFilterCount::ID:
+ return Slice("dialog_filters");
+ case td_api::premiumLimitTypeChatFilterChosenChatCount::ID:
+ return Slice("dialog_filters_chats");
+ case td_api::premiumLimitTypePinnedChatCount::ID:
+ return Slice("dialogs_pinned");
+ case td_api::premiumLimitTypePinnedArchivedChatCount::ID:
+ return Slice("dialogs_folder_pinned");
+ case td_api::premiumLimitTypeCreatedPublicChatCount::ID:
+ return Slice("channels_public");
+ case td_api::premiumLimitTypeCaptionLength::ID:
+ return Slice("caption_length");
+ case td_api::premiumLimitTypeBioLength::ID:
+ return Slice("about_length");
+ default:
+ UNREACHABLE();
+ return Slice();
+ }
+}
+
+static string get_premium_source(const td_api::PremiumLimitType *limit_type) {
+ if (limit_type == nullptr) {
+ return string();
+ }
+
+ return PSTRING() << "double_limits__" << get_limit_type_key(limit_type);
+}
+
+static string get_premium_source(const td_api::PremiumFeature *feature) {
+ if (feature == nullptr) {
+ return string();
+ }
+
+ switch (feature->get_id()) {
+ case td_api::premiumFeatureIncreasedLimits::ID:
+ return "double_limits";
+ case td_api::premiumFeatureIncreasedUploadFileSize::ID:
+ return "more_upload";
+ case td_api::premiumFeatureImprovedDownloadSpeed::ID:
+ return "faster_download";
+ case td_api::premiumFeatureVoiceRecognition::ID:
+ return "voice_to_text";
+ case td_api::premiumFeatureDisabledAds::ID:
+ return "no_ads";
+ case td_api::premiumFeatureUniqueReactions::ID:
+ return "infinite_reactions";
+ case td_api::premiumFeatureUniqueStickers::ID:
+ return "premium_stickers";
+ case td_api::premiumFeatureCustomEmoji::ID:
+ return "animated_emoji";
+ case td_api::premiumFeatureAdvancedChatManagement::ID:
+ return "advanced_chat_management";
+ case td_api::premiumFeatureProfileBadge::ID:
+ return "profile_badge";
+ case td_api::premiumFeatureEmojiStatus::ID:
+ return "emoji_status";
+ case td_api::premiumFeatureAnimatedProfilePhoto::ID:
+ return "animated_userpics";
+ case td_api::premiumFeatureForumTopicIcon::ID:
+ return "forum_topic_icon";
+ case td_api::premiumFeatureAppIcons::ID:
+ return "app_icons";
+ default:
+ UNREACHABLE();
+ }
+ return string();
+}
+
+static string get_premium_source(const td_api::object_ptr<td_api::PremiumSource> &source) {
+ if (source == nullptr) {
+ return string();
+ }
+ switch (source->get_id()) {
+ case td_api::premiumSourceLimitExceeded::ID: {
+ auto *limit_type = static_cast<const td_api::premiumSourceLimitExceeded *>(source.get())->limit_type_.get();
+ return get_premium_source(limit_type);
+ }
+ case td_api::premiumSourceFeature::ID: {
+ auto *feature = static_cast<const td_api::premiumSourceFeature *>(source.get())->feature_.get();
+ return get_premium_source(feature);
+ }
+ case td_api::premiumSourceLink::ID: {
+ auto &referrer = static_cast<const td_api::premiumSourceLink *>(source.get())->referrer_;
+ if (referrer.empty()) {
+ return "deeplink";
+ }
+ return PSTRING() << "deeplink_" << referrer;
+ }
+ case td_api::premiumSourceSettings::ID:
+ return "settings";
+ default:
+ UNREACHABLE();
+ return string();
+ }
+}
+
+static td_api::object_ptr<td_api::premiumLimit> get_premium_limit_object(Slice key) {
+ auto default_limit = static_cast<int32>(G()->get_option_integer(PSLICE() << key << "_limit_default"));
+ auto premium_limit = static_cast<int32>(G()->get_option_integer(PSLICE() << key << "_limit_premium"));
+ if (default_limit <= 0 || premium_limit <= default_limit) {
+ return nullptr;
+ }
+ auto type = [&]() -> td_api::object_ptr<td_api::PremiumLimitType> {
+ if (key == "channels") {
+ return td_api::make_object<td_api::premiumLimitTypeSupergroupCount>();
+ }
+ if (key == "saved_gifs") {
+ return td_api::make_object<td_api::premiumLimitTypeSavedAnimationCount>();
+ }
+ if (key == "stickers_faved") {
+ return td_api::make_object<td_api::premiumLimitTypeFavoriteStickerCount>();
+ }
+ if (key == "dialog_filters") {
+ return td_api::make_object<td_api::premiumLimitTypeChatFilterCount>();
+ }
+ if (key == "dialog_filters_chats") {
+ return td_api::make_object<td_api::premiumLimitTypeChatFilterChosenChatCount>();
+ }
+ if (key == "dialogs_pinned") {
+ return td_api::make_object<td_api::premiumLimitTypePinnedChatCount>();
+ }
+ if (key == "dialogs_folder_pinned") {
+ return td_api::make_object<td_api::premiumLimitTypePinnedArchivedChatCount>();
+ }
+ if (key == "channels_public") {
+ return td_api::make_object<td_api::premiumLimitTypeCreatedPublicChatCount>();
+ }
+ if (key == "caption_length") {
+ return td_api::make_object<td_api::premiumLimitTypeCaptionLength>();
+ }
+ if (key == "about_length") {
+ return td_api::make_object<td_api::premiumLimitTypeBioLength>();
+ }
+ UNREACHABLE();
+ return nullptr;
+ }();
+ return td_api::make_object<td_api::premiumLimit>(std::move(type), default_limit, premium_limit);
+}
+
+void get_premium_limit(const td_api::object_ptr<td_api::PremiumLimitType> &limit_type,
+ Promise<td_api::object_ptr<td_api::premiumLimit>> &&promise) {
+ if (limit_type == nullptr) {
+ return promise.set_error(Status::Error(400, "Limit type must be non-empty"));
+ }
+
+ promise.set_value(get_premium_limit_object(get_limit_type_key(limit_type.get())));
+}
+
+void get_premium_features(Td *td, const td_api::object_ptr<td_api::PremiumSource> &source,
+ Promise<td_api::object_ptr<td_api::premiumFeatures>> &&promise) {
+ auto premium_features = full_split(
+ G()->get_option_string(
+ "premium_features",
+ "double_limits,more_upload,faster_download,voice_to_text,no_ads,infinite_reactions,premium_stickers,"
+ "animated_emoji,advanced_chat_management,profile_badge,emoji_status,animated_userpics,app_icons"),
+ ',');
+ vector<td_api::object_ptr<td_api::PremiumFeature>> features;
+ for (const auto &premium_feature : premium_features) {
+ auto feature = get_premium_feature_object(premium_feature);
+ if (feature != nullptr) {
+ features.push_back(std::move(feature));
+ }
+ }
+
+ auto limits = transform(get_premium_limit_keys(), get_premium_limit_object);
+ td::remove_if(limits, [](auto &limit) { return limit == nullptr; });
+
+ auto source_str = get_premium_source(source);
+ if (!source_str.empty()) {
+ vector<tl_object_ptr<telegram_api::jsonObjectValue>> data;
+ vector<tl_object_ptr<telegram_api::JSONValue>> promo_order;
+ for (const auto &premium_feature : premium_features) {
+ promo_order.push_back(make_tl_object<telegram_api::jsonString>(premium_feature));
+ }
+ data.push_back(make_tl_object<telegram_api::jsonObjectValue>(
+ "premium_promo_order", make_tl_object<telegram_api::jsonArray>(std::move(promo_order))));
+ data.push_back(
+ make_tl_object<telegram_api::jsonObjectValue>("source", make_tl_object<telegram_api::jsonString>(source_str)));
+ save_app_log(td, "premium.promo_screen_show", DialogId(), make_tl_object<telegram_api::jsonObject>(std::move(data)),
+ Promise<Unit>());
+ }
+
+ td_api::object_ptr<td_api::InternalLinkType> payment_link;
+ auto premium_bot_username = G()->get_option_string("premium_bot_username");
+ if (!premium_bot_username.empty()) {
+ payment_link = td_api::make_object<td_api::internalLinkTypeBotStart>(premium_bot_username, source_str, true);
+ } else {
+ auto premium_invoice_slug = G()->get_option_string("premium_invoice_slug");
+ if (!premium_invoice_slug.empty()) {
+ payment_link = td_api::make_object<td_api::internalLinkTypeInvoice>(premium_invoice_slug);
+ }
+ }
+
+ promise.set_value(
+ td_api::make_object<td_api::premiumFeatures>(std::move(features), std::move(limits), std::move(payment_link)));
+}
+
+void view_premium_feature(Td *td, const td_api::object_ptr<td_api::PremiumFeature> &feature, Promise<Unit> &&promise) {
+ auto source = get_premium_source(feature.get());
+ if (source.empty()) {
+ return promise.set_error(Status::Error(400, "Feature must be non-empty"));
+ }
+
+ vector<tl_object_ptr<telegram_api::jsonObjectValue>> data;
+ data.push_back(
+ make_tl_object<telegram_api::jsonObjectValue>("item", make_tl_object<telegram_api::jsonString>(source)));
+ save_app_log(td, "premium.promo_screen_tap", DialogId(), make_tl_object<telegram_api::jsonObject>(std::move(data)),
+ std::move(promise));
+}
+
+void click_premium_subscription_button(Td *td, Promise<Unit> &&promise) {
+ vector<tl_object_ptr<telegram_api::jsonObjectValue>> data;
+ save_app_log(td, "premium.promo_screen_accept", DialogId(), make_tl_object<telegram_api::jsonObject>(std::move(data)),
+ std::move(promise));
+}
+
+void get_premium_state(Td *td, Promise<td_api::object_ptr<td_api::premiumState>> &&promise) {
+ td->create_handler<GetPremiumPromoQuery>(std::move(promise))->send();
+}
+
+void can_purchase_premium(Td *td, td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose, Promise<Unit> &&promise) {
+ td->create_handler<CanPurchasePremiumQuery>(std::move(promise))->send(std::move(purpose));
+}
+
+void assign_app_store_transaction(Td *td, const string &receipt,
+ td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose, Promise<Unit> &&promise) {
+ td->create_handler<AssignAppStoreTransactionQuery>(std::move(promise))->send(receipt, std::move(purpose));
+}
+
+void assign_play_market_transaction(Td *td, const string &package_name, const string &store_product_id,
+ const string &purchase_token,
+ td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose,
+ Promise<Unit> &&promise) {
+ td->create_handler<AssignPlayMarketTransactionQuery>(std::move(promise))
+ ->send(package_name, store_product_id, purchase_token, std::move(purpose));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Premium.h b/protocols/Telegram/tdlib/td/td/telegram/Premium.h
new file mode 100644
index 0000000000..a385788bfc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Premium.h
@@ -0,0 +1,42 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+class Td;
+
+const vector<Slice> &get_premium_limit_keys();
+
+void get_premium_limit(const td_api::object_ptr<td_api::PremiumLimitType> &limit_type,
+ Promise<td_api::object_ptr<td_api::premiumLimit>> &&promise);
+
+void get_premium_features(Td *td, const td_api::object_ptr<td_api::PremiumSource> &source,
+ Promise<td_api::object_ptr<td_api::premiumFeatures>> &&promise);
+
+void view_premium_feature(Td *td, const td_api::object_ptr<td_api::PremiumFeature> &feature, Promise<Unit> &&promise);
+
+void click_premium_subscription_button(Td *td, Promise<Unit> &&promise);
+
+void get_premium_state(Td *td, Promise<td_api::object_ptr<td_api::premiumState>> &&promise);
+
+void can_purchase_premium(Td *td, td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose, Promise<Unit> &&promise);
+
+void assign_app_store_transaction(Td *td, const string &receipt,
+ td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose, Promise<Unit> &&promise);
+
+void assign_play_market_transaction(Td *td, const string &package_name, const string &store_product_id,
+ const string &purchase_token,
+ td_api::object_ptr<td_api::StorePaymentPurpose> &&purpose, Promise<Unit> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.cpp b/protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.cpp
new file mode 100644
index 0000000000..cabdf1e803
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.cpp
@@ -0,0 +1,109 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/PremiumGiftOption.h"
+
+#include "td/telegram/LinkManager.h"
+#include "td/telegram/misc.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+
+#include <algorithm>
+#include <tuple>
+
+namespace td {
+
+PremiumGiftOption::PremiumGiftOption(telegram_api::object_ptr<telegram_api::premiumGiftOption> &&option)
+ : months_(option->months_)
+ , currency_(std::move(option->currency_))
+ , amount_(option->amount_)
+ , bot_url_(std::move(option->bot_url_))
+ , store_product_(std::move(option->store_product_)) {
+}
+
+PremiumGiftOption::PremiumGiftOption(telegram_api::object_ptr<telegram_api::premiumSubscriptionOption> &&option)
+ : months_(option->months_)
+ , currency_(std::move(option->currency_))
+ , amount_(option->amount_)
+ , bot_url_(std::move(option->bot_url_))
+ , store_product_(std::move(option->store_product_)) {
+}
+
+bool PremiumGiftOption::is_valid() const {
+ if (amount_ <= 0 || !check_currency_amount(amount_)) {
+ LOG(ERROR) << "Receive invalid premium payment option amount " << amount_;
+ return false;
+ }
+ if (currency_.size() != 3) {
+ LOG(ERROR) << "Receive invalid premium payment option currency " << currency_;
+ return false;
+ }
+ return true;
+}
+
+double PremiumGiftOption::get_monthly_price() const {
+ return static_cast<double>(amount_) / static_cast<double>(months_);
+}
+
+td_api::object_ptr<td_api::premiumPaymentOption> PremiumGiftOption::get_premium_payment_option_object(
+ const PremiumGiftOption &base_option) const {
+ auto link_type = LinkManager::parse_internal_link(bot_url_, true);
+ int32 discount_percentage = 0;
+ if (base_option.months_ > 0 && months_ > 0 && base_option.amount_ > 0 && amount_ > 0) {
+ double relative_price = get_monthly_price() / base_option.get_monthly_price();
+ if (relative_price < 1.0) {
+ discount_percentage = static_cast<int32>(100 * (1.0 - relative_price));
+ }
+ }
+ return td_api::make_object<td_api::premiumPaymentOption>(
+ currency_, amount_, discount_percentage, months_, store_product_,
+ link_type == nullptr ? nullptr : link_type->get_internal_link_type_object());
+}
+
+bool operator<(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs) {
+ return std::tie(lhs.months_, lhs.amount_, lhs.currency_, lhs.store_product_, lhs.bot_url_) <
+ std::tie(rhs.months_, rhs.amount_, rhs.currency_, rhs.store_product_, rhs.bot_url_);
+}
+
+bool operator==(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs) {
+ return lhs.months_ == rhs.months_ && lhs.currency_ == rhs.currency_ && lhs.amount_ == rhs.amount_ &&
+ lhs.bot_url_ == rhs.bot_url_ && lhs.store_product_ == rhs.store_product_;
+}
+
+bool operator!=(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs) {
+ return !(lhs == rhs);
+}
+
+vector<PremiumGiftOption> get_premium_gift_options(
+ vector<telegram_api::object_ptr<telegram_api::premiumGiftOption>> &&options) {
+ auto premium_gift_options = transform(
+ std::move(options), [](auto &&premium_gift_option) { return PremiumGiftOption(std::move(premium_gift_option)); });
+ td::remove_if(premium_gift_options, [](const auto &premium_gift_option) { return !premium_gift_option.is_valid(); });
+ return premium_gift_options;
+}
+
+vector<PremiumGiftOption> get_premium_gift_options(
+ vector<telegram_api::object_ptr<telegram_api::premiumSubscriptionOption>> &&options) {
+ auto premium_gift_options = transform(
+ std::move(options), [](auto &&premium_gift_option) { return PremiumGiftOption(std::move(premium_gift_option)); });
+ td::remove_if(premium_gift_options, [](const auto &premium_gift_option) { return !premium_gift_option.is_valid(); });
+ return premium_gift_options;
+}
+
+vector<td_api::object_ptr<td_api::premiumPaymentOption>> get_premium_payment_options_object(
+ const vector<PremiumGiftOption> &options) {
+ if (options.empty()) {
+ return {};
+ }
+ auto base_premium_option_it = std::min_element(options.begin(), options.end());
+ return transform(options, [&base_premium_option_it](const auto &option) {
+ return option.get_premium_payment_option_object(*base_premium_option_it);
+ });
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.h b/protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.h
new file mode 100644
index 0000000000..09b8be7d11
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.h
@@ -0,0 +1,58 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+
+namespace td {
+
+class PremiumGiftOption {
+ int32 months_ = 0;
+ string currency_;
+ int64 amount_ = 0;
+ string bot_url_;
+ string store_product_;
+
+ friend bool operator<(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs);
+
+ friend bool operator==(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs);
+
+ double get_monthly_price() const;
+
+ public:
+ PremiumGiftOption() = default;
+ explicit PremiumGiftOption(telegram_api::object_ptr<telegram_api::premiumGiftOption> &&option);
+ explicit PremiumGiftOption(telegram_api::object_ptr<telegram_api::premiumSubscriptionOption> &&option);
+
+ td_api::object_ptr<td_api::premiumPaymentOption> get_premium_payment_option_object(
+ const PremiumGiftOption &base_option) const;
+
+ bool is_valid() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+bool operator==(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs);
+bool operator!=(const PremiumGiftOption &lhs, const PremiumGiftOption &rhs);
+
+vector<PremiumGiftOption> get_premium_gift_options(
+ vector<telegram_api::object_ptr<telegram_api::premiumGiftOption>> &&options);
+
+vector<PremiumGiftOption> get_premium_gift_options(
+ vector<telegram_api::object_ptr<telegram_api::premiumSubscriptionOption>> &&options);
+
+vector<td_api::object_ptr<td_api::premiumPaymentOption>> get_premium_payment_options_object(
+ const vector<PremiumGiftOption> &options);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.hpp b/protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.hpp
new file mode 100644
index 0000000000..507b669947
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PremiumGiftOption.hpp
@@ -0,0 +1,77 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/PremiumGiftOption.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void PremiumGiftOption::store(StorerT &storer) const {
+ bool has_months = months_ != 0;
+ bool has_currency = !currency_.empty();
+ bool has_amount = amount_ != 0;
+ bool has_bot_url = !bot_url_.empty();
+ bool has_store_product = !store_product_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_months);
+ STORE_FLAG(has_currency);
+ STORE_FLAG(has_amount);
+ STORE_FLAG(has_bot_url);
+ STORE_FLAG(has_store_product);
+ END_STORE_FLAGS();
+ if (has_months) {
+ td::store(months_, storer);
+ }
+ if (has_currency) {
+ td::store(currency_, storer);
+ }
+ if (has_amount) {
+ td::store(amount_, storer);
+ }
+ if (has_bot_url) {
+ td::store(bot_url_, storer);
+ }
+ if (has_store_product) {
+ td::store(store_product_, storer);
+ }
+}
+
+template <class ParserT>
+void PremiumGiftOption::parse(ParserT &parser) {
+ bool has_months;
+ bool has_currency;
+ bool has_amount;
+ bool has_bot_url;
+ bool has_store_product;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_months);
+ PARSE_FLAG(has_currency);
+ PARSE_FLAG(has_amount);
+ PARSE_FLAG(has_bot_url);
+ PARSE_FLAG(has_store_product);
+ END_PARSE_FLAGS();
+ if (has_months) {
+ td::parse(months_, parser);
+ }
+ if (has_currency) {
+ td::parse(currency_, parser);
+ }
+ if (has_amount) {
+ td::parse(amount_, parser);
+ }
+ if (has_bot_url) {
+ td::parse(bot_url_, parser);
+ }
+ if (has_store_product) {
+ td::parse(store_product_, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.cpp
index 389d07cabf..2874dec919 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.cpp
@@ -1,36 +1,41 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/PrivacyManager.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
#include "td/telegram/Global.h"
+#include "td/telegram/MessagesManager.h"
#include "td/telegram/net/NetQueryCreator.h"
#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/Td.h"
+#include "td/utils/algorithm.h"
#include "td/utils/logging.h"
-#include "td/utils/misc.h"
+
+#include <algorithm>
+#include <iterator>
namespace td {
-Result<PrivacyManager::UserPrivacySetting> PrivacyManager::UserPrivacySetting::from_td_api(
+Result<PrivacyManager::UserPrivacySetting> PrivacyManager::UserPrivacySetting::get_user_privacy_setting(
tl_object_ptr<td_api::UserPrivacySetting> key) {
- if (!key) {
- return Status::Error(5, "UserPrivacySetting should not be empty");
+ if (key == nullptr) {
+ return Status::Error(400, "UserPrivacySetting must be non-empty");
}
return UserPrivacySetting(*key);
}
+
PrivacyManager::UserPrivacySetting::UserPrivacySetting(const telegram_api::PrivacyKey &key) {
switch (key.get_id()) {
case telegram_api::privacyKeyStatusTimestamp::ID:
- type_ = Type::UserState;
+ type_ = Type::UserStatus;
break;
case telegram_api::privacyKeyChatInvite::ID:
type_ = Type::ChatInvite;
@@ -38,32 +43,75 @@ PrivacyManager::UserPrivacySetting::UserPrivacySetting(const telegram_api::Priva
case telegram_api::privacyKeyPhoneCall::ID:
type_ = Type::Call;
break;
+ case telegram_api::privacyKeyPhoneP2P::ID:
+ type_ = Type::PeerToPeerCall;
+ break;
+ case telegram_api::privacyKeyForwards::ID:
+ type_ = Type::LinkInForwardedMessages;
+ break;
+ case telegram_api::privacyKeyProfilePhoto::ID:
+ type_ = Type::UserProfilePhoto;
+ break;
+ case telegram_api::privacyKeyPhoneNumber::ID:
+ type_ = Type::UserPhoneNumber;
+ break;
+ case telegram_api::privacyKeyAddedByPhone::ID:
+ type_ = Type::FindByPhoneNumber;
+ break;
+ case telegram_api::privacyKeyVoiceMessages::ID:
+ type_ = Type::VoiceMessages;
+ break;
default:
UNREACHABLE();
- type_ = Type::UserState;
+ type_ = Type::UserStatus;
}
}
-tl_object_ptr<td_api::UserPrivacySetting> PrivacyManager::UserPrivacySetting::as_td_api() const {
+
+tl_object_ptr<td_api::UserPrivacySetting> PrivacyManager::UserPrivacySetting::get_user_privacy_setting_object() const {
switch (type_) {
- case Type::UserState:
+ case Type::UserStatus:
return make_tl_object<td_api::userPrivacySettingShowStatus>();
case Type::ChatInvite:
return make_tl_object<td_api::userPrivacySettingAllowChatInvites>();
case Type::Call:
return make_tl_object<td_api::userPrivacySettingAllowCalls>();
+ case Type::PeerToPeerCall:
+ return make_tl_object<td_api::userPrivacySettingAllowPeerToPeerCalls>();
+ case Type::LinkInForwardedMessages:
+ return make_tl_object<td_api::userPrivacySettingShowLinkInForwardedMessages>();
+ case Type::UserProfilePhoto:
+ return make_tl_object<td_api::userPrivacySettingShowProfilePhoto>();
+ case Type::UserPhoneNumber:
+ return make_tl_object<td_api::userPrivacySettingShowPhoneNumber>();
+ case Type::FindByPhoneNumber:
+ return make_tl_object<td_api::userPrivacySettingAllowFindingByPhoneNumber>();
+ case Type::VoiceMessages:
+ return make_tl_object<td_api::userPrivacySettingAllowPrivateVoiceAndVideoNoteMessages>();
default:
UNREACHABLE();
return nullptr;
}
}
-tl_object_ptr<telegram_api::InputPrivacyKey> PrivacyManager::UserPrivacySetting::as_telegram_api() const {
+tl_object_ptr<telegram_api::InputPrivacyKey> PrivacyManager::UserPrivacySetting::get_input_privacy_key() const {
switch (type_) {
- case Type::UserState:
+ case Type::UserStatus:
return make_tl_object<telegram_api::inputPrivacyKeyStatusTimestamp>();
case Type::ChatInvite:
return make_tl_object<telegram_api::inputPrivacyKeyChatInvite>();
case Type::Call:
return make_tl_object<telegram_api::inputPrivacyKeyPhoneCall>();
+ case Type::PeerToPeerCall:
+ return make_tl_object<telegram_api::inputPrivacyKeyPhoneP2P>();
+ case Type::LinkInForwardedMessages:
+ return make_tl_object<telegram_api::inputPrivacyKeyForwards>();
+ case Type::UserProfilePhoto:
+ return make_tl_object<telegram_api::inputPrivacyKeyProfilePhoto>();
+ case Type::UserPhoneNumber:
+ return make_tl_object<telegram_api::inputPrivacyKeyPhoneNumber>();
+ case Type::FindByPhoneNumber:
+ return make_tl_object<telegram_api::inputPrivacyKeyAddedByPhone>();
+ case Type::VoiceMessages:
+ return make_tl_object<telegram_api::inputPrivacyKeyVoiceMessages>();
default:
UNREACHABLE();
return nullptr;
@@ -73,7 +121,7 @@ tl_object_ptr<telegram_api::InputPrivacyKey> PrivacyManager::UserPrivacySetting:
PrivacyManager::UserPrivacySetting::UserPrivacySetting(const td_api::UserPrivacySetting &key) {
switch (key.get_id()) {
case td_api::userPrivacySettingShowStatus::ID:
- type_ = Type::UserState;
+ type_ = Type::UserStatus;
break;
case td_api::userPrivacySettingAllowChatInvites::ID:
type_ = Type::ChatInvite;
@@ -81,9 +129,56 @@ PrivacyManager::UserPrivacySetting::UserPrivacySetting(const td_api::UserPrivacy
case td_api::userPrivacySettingAllowCalls::ID:
type_ = Type::Call;
break;
+ case td_api::userPrivacySettingAllowPeerToPeerCalls::ID:
+ type_ = Type::PeerToPeerCall;
+ break;
+ case td_api::userPrivacySettingShowLinkInForwardedMessages::ID:
+ type_ = Type::LinkInForwardedMessages;
+ break;
+ case td_api::userPrivacySettingShowProfilePhoto::ID:
+ type_ = Type::UserProfilePhoto;
+ break;
+ case td_api::userPrivacySettingShowPhoneNumber::ID:
+ type_ = Type::UserPhoneNumber;
+ break;
+ case td_api::userPrivacySettingAllowFindingByPhoneNumber::ID:
+ type_ = Type::FindByPhoneNumber;
+ break;
+ case td_api::userPrivacySettingAllowPrivateVoiceAndVideoNoteMessages::ID:
+ type_ = Type::VoiceMessages;
+ break;
default:
UNREACHABLE();
- type_ = Type::UserState;
+ type_ = Type::UserStatus;
+ }
+}
+
+void PrivacyManager::UserPrivacySettingRule::set_chat_ids(const vector<int64> &dialog_ids) {
+ chat_ids_.clear();
+ auto td = G()->td().get_actor_unsafe();
+ for (auto dialog_id_int : dialog_ids) {
+ DialogId dialog_id(dialog_id_int);
+ if (!td->messages_manager_->have_dialog_force(dialog_id, "UserPrivacySettingRule::set_chat_ids")) {
+ LOG(ERROR) << "Ignore not found " << dialog_id;
+ continue;
+ }
+
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat:
+ chat_ids_.push_back(dialog_id.get_chat_id().get());
+ break;
+ case DialogType::Channel: {
+ auto channel_id = dialog_id.get_channel_id();
+ if (!td->contacts_manager_->is_megagroup_channel(channel_id)) {
+ LOG(ERROR) << "Ignore broadcast " << channel_id;
+ break;
+ }
+ chat_ids_.push_back(channel_id.get());
+ break;
+ }
+ default:
+ LOG(ERROR) << "Ignore " << dialog_id;
+ }
}
}
@@ -97,7 +192,11 @@ PrivacyManager::UserPrivacySettingRule::UserPrivacySettingRule(const td_api::Use
break;
case td_api::userPrivacySettingRuleAllowUsers::ID:
type_ = Type::AllowUsers;
- user_ids_ = static_cast<const td_api::userPrivacySettingRuleAllowUsers &>(rule).user_ids_;
+ user_ids_ = UserId::get_user_ids(static_cast<const td_api::userPrivacySettingRuleAllowUsers &>(rule).user_ids_);
+ break;
+ case td_api::userPrivacySettingRuleAllowChatMembers::ID:
+ type_ = Type::AllowChatParticipants;
+ set_chat_ids(static_cast<const td_api::userPrivacySettingRuleAllowChatMembers &>(rule).chat_ids_);
break;
case td_api::userPrivacySettingRuleRestrictContacts::ID:
type_ = Type::RestrictContacts;
@@ -107,7 +206,12 @@ PrivacyManager::UserPrivacySettingRule::UserPrivacySettingRule(const td_api::Use
break;
case td_api::userPrivacySettingRuleRestrictUsers::ID:
type_ = Type::RestrictUsers;
- user_ids_ = static_cast<const td_api::userPrivacySettingRuleRestrictUsers &>(rule).user_ids_;
+ user_ids_ =
+ UserId::get_user_ids(static_cast<const td_api::userPrivacySettingRuleRestrictUsers &>(rule).user_ids_);
+ break;
+ case td_api::userPrivacySettingRuleRestrictChatMembers::ID:
+ type_ = Type::RestrictChatParticipants;
+ set_chat_ids(static_cast<const td_api::userPrivacySettingRuleRestrictChatMembers &>(rule).chat_ids_);
break;
default:
UNREACHABLE();
@@ -124,7 +228,11 @@ PrivacyManager::UserPrivacySettingRule::UserPrivacySettingRule(const telegram_ap
break;
case telegram_api::privacyValueAllowUsers::ID:
type_ = Type::AllowUsers;
- user_ids_ = static_cast<const telegram_api::privacyValueAllowUsers &>(rule).users_;
+ user_ids_ = UserId::get_user_ids(static_cast<const telegram_api::privacyValueAllowUsers &>(rule).users_);
+ break;
+ case telegram_api::privacyValueAllowChatParticipants::ID:
+ type_ = Type::AllowChatParticipants;
+ chat_ids_ = static_cast<const telegram_api::privacyValueAllowChatParticipants &>(rule).chats_;
break;
case telegram_api::privacyValueDisallowContacts::ID:
type_ = Type::RestrictContacts;
@@ -134,193 +242,251 @@ PrivacyManager::UserPrivacySettingRule::UserPrivacySettingRule(const telegram_ap
break;
case telegram_api::privacyValueDisallowUsers::ID:
type_ = Type::RestrictUsers;
- user_ids_ = static_cast<const telegram_api::privacyValueDisallowUsers &>(rule).users_;
+ user_ids_ = UserId::get_user_ids(static_cast<const telegram_api::privacyValueDisallowUsers &>(rule).users_);
+ break;
+ case telegram_api::privacyValueDisallowChatParticipants::ID:
+ type_ = Type::RestrictChatParticipants;
+ chat_ids_ = static_cast<const telegram_api::privacyValueDisallowChatParticipants &>(rule).chats_;
break;
default:
UNREACHABLE();
}
}
-tl_object_ptr<td_api::UserPrivacySettingRule> PrivacyManager::UserPrivacySettingRule::as_td_api() const {
+tl_object_ptr<td_api::UserPrivacySettingRule>
+PrivacyManager::UserPrivacySettingRule::get_user_privacy_setting_rule_object() const {
switch (type_) {
case Type::AllowContacts:
return make_tl_object<td_api::userPrivacySettingRuleAllowContacts>();
case Type::AllowAll:
return make_tl_object<td_api::userPrivacySettingRuleAllowAll>();
case Type::AllowUsers:
- return make_tl_object<td_api::userPrivacySettingRuleAllowUsers>(user_ids_as_td_api());
+ return make_tl_object<td_api::userPrivacySettingRuleAllowUsers>(UserId::get_input_user_ids(user_ids_));
+ case Type::AllowChatParticipants:
+ return make_tl_object<td_api::userPrivacySettingRuleAllowChatMembers>(chat_ids_as_dialog_ids());
case Type::RestrictContacts:
return make_tl_object<td_api::userPrivacySettingRuleRestrictContacts>();
case Type::RestrictAll:
return make_tl_object<td_api::userPrivacySettingRuleRestrictAll>();
case Type::RestrictUsers:
- return make_tl_object<td_api::userPrivacySettingRuleRestrictUsers>(user_ids_as_td_api());
+ return make_tl_object<td_api::userPrivacySettingRuleRestrictUsers>(UserId::get_input_user_ids(user_ids_));
+ case Type::RestrictChatParticipants:
+ return make_tl_object<td_api::userPrivacySettingRuleRestrictChatMembers>(chat_ids_as_dialog_ids());
default:
UNREACHABLE();
}
}
-tl_object_ptr<telegram_api::InputPrivacyRule> PrivacyManager::UserPrivacySettingRule::as_telegram_api() const {
+tl_object_ptr<telegram_api::InputPrivacyRule> PrivacyManager::UserPrivacySettingRule::get_input_privacy_rule() const {
switch (type_) {
case Type::AllowContacts:
return make_tl_object<telegram_api::inputPrivacyValueAllowContacts>();
case Type::AllowAll:
return make_tl_object<telegram_api::inputPrivacyValueAllowAll>();
case Type::AllowUsers:
- return make_tl_object<telegram_api::inputPrivacyValueAllowUsers>(user_ids_as_telegram_api());
+ return make_tl_object<telegram_api::inputPrivacyValueAllowUsers>(get_input_users());
+ case Type::AllowChatParticipants:
+ return make_tl_object<telegram_api::inputPrivacyValueAllowChatParticipants>(vector<int64>{chat_ids_});
case Type::RestrictContacts:
return make_tl_object<telegram_api::inputPrivacyValueDisallowContacts>();
case Type::RestrictAll:
return make_tl_object<telegram_api::inputPrivacyValueDisallowAll>();
case Type::RestrictUsers:
- return make_tl_object<telegram_api::inputPrivacyValueDisallowUsers>(user_ids_as_telegram_api());
+ return make_tl_object<telegram_api::inputPrivacyValueDisallowUsers>(get_input_users());
+ case Type::RestrictChatParticipants:
+ return make_tl_object<telegram_api::inputPrivacyValueDisallowChatParticipants>(vector<int64>{chat_ids_});
default:
UNREACHABLE();
}
}
-Result<PrivacyManager::UserPrivacySettingRule> PrivacyManager::UserPrivacySettingRule::from_telegram_api(
+Result<PrivacyManager::UserPrivacySettingRule> PrivacyManager::UserPrivacySettingRule::get_user_privacy_setting_rule(
tl_object_ptr<telegram_api::PrivacyRule> rule) {
CHECK(rule != nullptr);
- UserPrivacySettingRule res(*rule);
- for (auto user_id : res.user_ids_) {
- if (!G()->td().get_actor_unsafe()->contacts_manager_->have_user(UserId(user_id))) {
- return Status::Error(500, "Got unaccessible user from the server");
+ UserPrivacySettingRule result(*rule);
+ auto td = G()->td().get_actor_unsafe();
+ for (auto user_id : result.user_ids_) {
+ if (!td->contacts_manager_->have_user(user_id)) {
+ return Status::Error(500, "Got inaccessible user from the server");
}
}
- return res;
+ for (auto chat_id_int : result.chat_ids_) {
+ ChatId chat_id(chat_id_int);
+ DialogId dialog_id(chat_id);
+ if (!td->contacts_manager_->have_chat(chat_id)) {
+ ChannelId channel_id(chat_id_int);
+ dialog_id = DialogId(channel_id);
+ if (!td->contacts_manager_->have_channel(channel_id)) {
+ return Status::Error(500, "Got inaccessible chat from the server");
+ }
+ }
+ td->messages_manager_->force_create_dialog(dialog_id, "UserPrivacySettingRule");
+ }
+ return result;
}
-vector<tl_object_ptr<telegram_api::InputUser>> PrivacyManager::UserPrivacySettingRule::user_ids_as_telegram_api()
- const {
- vector<tl_object_ptr<telegram_api::InputUser>> res;
+vector<tl_object_ptr<telegram_api::InputUser>> PrivacyManager::UserPrivacySettingRule::get_input_users() const {
+ vector<tl_object_ptr<telegram_api::InputUser>> result;
for (auto user_id : user_ids_) {
- auto input_user = G()->td().get_actor_unsafe()->contacts_manager_->get_input_user(UserId(user_id));
- if (input_user != nullptr) {
- res.push_back(std::move(input_user));
+ auto r_input_user = G()->td().get_actor_unsafe()->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_ok()) {
+ result.push_back(r_input_user.move_as_ok());
} else {
LOG(ERROR) << "Have no access to " << user_id;
}
}
- return res;
+ return result;
}
-vector<int32> PrivacyManager::UserPrivacySettingRule::user_ids_as_td_api() const {
- return user_ids_;
+vector<int64> PrivacyManager::UserPrivacySettingRule::chat_ids_as_dialog_ids() const {
+ vector<int64> result;
+ auto td = G()->td().get_actor_unsafe();
+ for (auto chat_id_int : chat_ids_) {
+ ChatId chat_id(chat_id_int);
+ DialogId dialog_id(chat_id);
+ if (!td->contacts_manager_->have_chat(chat_id)) {
+ ChannelId channel_id(chat_id_int);
+ dialog_id = DialogId(channel_id);
+ CHECK(td->contacts_manager_->have_channel(channel_id));
+ }
+ CHECK(td->messages_manager_->have_dialog(dialog_id));
+ result.push_back(dialog_id.get());
+ }
+ return result;
}
-Result<PrivacyManager::UserPrivacySettingRules> PrivacyManager::UserPrivacySettingRules::from_telegram_api(
+vector<UserId> PrivacyManager::UserPrivacySettingRule::get_restricted_user_ids() const {
+ if (type_ == Type::RestrictUsers) {
+ return user_ids_;
+ }
+ return {};
+}
+
+Result<PrivacyManager::UserPrivacySettingRules> PrivacyManager::UserPrivacySettingRules::get_user_privacy_setting_rules(
tl_object_ptr<telegram_api::account_privacyRules> rules) {
- G()->td().get_actor_unsafe()->contacts_manager_->on_get_users(std::move(rules->users_));
- return from_telegram_api(std::move(rules->rules_));
+ G()->td().get_actor_unsafe()->contacts_manager_->on_get_users(std::move(rules->users_), "on get privacy rules");
+ G()->td().get_actor_unsafe()->contacts_manager_->on_get_chats(std::move(rules->chats_), "on get privacy rules");
+ return get_user_privacy_setting_rules(std::move(rules->rules_));
}
-Result<PrivacyManager::UserPrivacySettingRules> PrivacyManager::UserPrivacySettingRules::from_telegram_api(
+Result<PrivacyManager::UserPrivacySettingRules> PrivacyManager::UserPrivacySettingRules::get_user_privacy_setting_rules(
vector<tl_object_ptr<telegram_api::PrivacyRule>> rules) {
- UserPrivacySettingRules res;
+ UserPrivacySettingRules result;
for (auto &rule : rules) {
- TRY_RESULT(new_rule, UserPrivacySettingRule::from_telegram_api(std::move(rule)));
- res.rules_.push_back(new_rule);
+ TRY_RESULT(new_rule, UserPrivacySettingRule::get_user_privacy_setting_rule(std::move(rule)));
+ result.rules_.push_back(new_rule);
+ }
+ if (!result.rules_.empty() && result.rules_.back().get_user_privacy_setting_rule_object()->get_id() ==
+ td_api::userPrivacySettingRuleRestrictAll::ID) {
+ result.rules_.pop_back();
}
- return res;
+ return result;
}
-Result<PrivacyManager::UserPrivacySettingRules> PrivacyManager::UserPrivacySettingRules::from_td_api(
+Result<PrivacyManager::UserPrivacySettingRules> PrivacyManager::UserPrivacySettingRules::get_user_privacy_setting_rules(
tl_object_ptr<td_api::userPrivacySettingRules> rules) {
- if (!rules) {
- return Status::Error(5, "UserPrivacySettingRules should not be empty");
+ if (rules == nullptr) {
+ return Status::Error(400, "UserPrivacySettingRules must be non-empty");
}
- UserPrivacySettingRules res;
+ UserPrivacySettingRules result;
for (auto &rule : rules->rules_) {
- if (!rule) {
- return Status::Error(5, "UserPrivacySettingRule should not be empty");
+ if (rule == nullptr) {
+ return Status::Error(400, "UserPrivacySettingRule must be non-empty");
}
- res.rules_.emplace_back(*rule);
+ result.rules_.emplace_back(*rule);
}
- return res;
+ return result;
}
-tl_object_ptr<td_api::userPrivacySettingRules> PrivacyManager::UserPrivacySettingRules::as_td_api() const {
+tl_object_ptr<td_api::userPrivacySettingRules>
+PrivacyManager::UserPrivacySettingRules::get_user_privacy_setting_rules_object() const {
return make_tl_object<td_api::userPrivacySettingRules>(
- transform(rules_, [](const auto &rule) { return rule.as_td_api(); }));
+ transform(rules_, [](const auto &rule) { return rule.get_user_privacy_setting_rule_object(); }));
+}
+
+vector<tl_object_ptr<telegram_api::InputPrivacyRule>> PrivacyManager::UserPrivacySettingRules::get_input_privacy_rules()
+ const {
+ auto result = transform(rules_, [](const auto &rule) { return rule.get_input_privacy_rule(); });
+ if (!result.empty() && result.back()->get_id() == telegram_api::inputPrivacyValueDisallowAll::ID) {
+ result.pop_back();
+ }
+ return result;
}
-vector<tl_object_ptr<telegram_api::InputPrivacyRule>> PrivacyManager::UserPrivacySettingRules::as_telegram_api() const {
- return transform(rules_, [](const auto &rule) { return rule.as_telegram_api(); });
+vector<UserId> PrivacyManager::UserPrivacySettingRules::get_restricted_user_ids() const {
+ vector<UserId> result;
+ for (auto &rule : rules_) {
+ combine(result, rule.get_restricted_user_ids());
+ }
+ std::sort(result.begin(), result.end(), [](UserId lhs, UserId rhs) { return lhs.get() < rhs.get(); });
+ result.erase(std::unique(result.begin(), result.end()), result.end());
+ return result;
}
void PrivacyManager::get_privacy(tl_object_ptr<td_api::UserPrivacySetting> key,
Promise<tl_object_ptr<td_api::userPrivacySettingRules>> promise) {
- auto r_user_privacy_setting = UserPrivacySetting::from_td_api(std::move(key));
+ auto r_user_privacy_setting = UserPrivacySetting::get_user_privacy_setting(std::move(key));
if (r_user_privacy_setting.is_error()) {
return promise.set_error(r_user_privacy_setting.move_as_error());
}
auto user_privacy_setting = r_user_privacy_setting.move_as_ok();
auto &info = get_info(user_privacy_setting);
if (info.is_synchronized) {
- return promise.set_value(info.rules.as_td_api());
+ return promise.set_value(info.rules.get_user_privacy_setting_rules_object());
}
info.get_promises.push_back(std::move(promise));
if (info.get_promises.size() > 1u) {
// query has already been sent, just wait for the result
return;
}
- auto net_query = G()->net_query_creator().create(
- create_storer(telegram_api::account_getPrivacy(user_privacy_setting.as_telegram_api())));
+ auto net_query =
+ G()->net_query_creator().create(telegram_api::account_getPrivacy(user_privacy_setting.get_input_privacy_key()));
send_with_promise(std::move(net_query),
- PromiseCreator::lambda([this, user_privacy_setting](Result<NetQueryPtr> x_net_query) mutable {
+ PromiseCreator::lambda([this, user_privacy_setting](Result<NetQueryPtr> x_net_query) {
on_get_result(user_privacy_setting, [&]() -> Result<UserPrivacySettingRules> {
TRY_RESULT(net_query, std::move(x_net_query));
TRY_RESULT(rules, fetch_result<telegram_api::account_getPrivacy>(std::move(net_query)));
- return UserPrivacySettingRules::from_telegram_api(std::move(rules));
+ LOG(INFO) << "Receive " << to_string(rules);
+ return UserPrivacySettingRules::get_user_privacy_setting_rules(std::move(rules));
}());
}));
}
void PrivacyManager::set_privacy(tl_object_ptr<td_api::UserPrivacySetting> key,
- tl_object_ptr<td_api::userPrivacySettingRules> rules,
- Promise<tl_object_ptr<td_api::ok>> promise) {
- auto r_user_privacy_setting = UserPrivacySetting::from_td_api(std::move(key));
- if (r_user_privacy_setting.is_error()) {
- return promise.set_error(r_user_privacy_setting.move_as_error());
- }
- auto user_privacy_setting = r_user_privacy_setting.move_as_ok();
-
- auto r_privacy_rules = UserPrivacySettingRules::from_td_api(std::move(rules));
- if (r_privacy_rules.is_error()) {
- return promise.set_error(r_privacy_rules.move_as_error());
- }
- auto privacy_rules = r_privacy_rules.move_as_ok();
+ tl_object_ptr<td_api::userPrivacySettingRules> rules, Promise<Unit> promise) {
+ TRY_RESULT_PROMISE(promise, user_privacy_setting, UserPrivacySetting::get_user_privacy_setting(std::move(key)));
+ TRY_RESULT_PROMISE(promise, privacy_rules, UserPrivacySettingRules::get_user_privacy_setting_rules(std::move(rules)));
auto &info = get_info(user_privacy_setting);
if (info.has_set_query) {
// TODO cancel previous query
- return promise.set_error(Status::Error(5, "Another set_privacy query is active"));
+ return promise.set_error(Status::Error(400, "Another set_privacy query is active"));
}
- auto net_query = G()->net_query_creator().create(create_storer(
- telegram_api::account_setPrivacy(user_privacy_setting.as_telegram_api(), privacy_rules.as_telegram_api())));
+ auto net_query = G()->net_query_creator().create(telegram_api::account_setPrivacy(
+ user_privacy_setting.get_input_privacy_key(), privacy_rules.get_input_privacy_rules()));
info.has_set_query = true;
- send_with_promise(std::move(net_query),
- PromiseCreator::lambda([this, user_privacy_setting,
- promise = std::move(promise)](Result<NetQueryPtr> x_net_query) mutable {
- promise.set_result([&]() -> Result<tl_object_ptr<td_api::ok>> {
- TRY_RESULT(net_query, std::move(x_net_query));
- TRY_RESULT(rules, fetch_result<telegram_api::account_setPrivacy>(std::move(net_query)));
- TRY_RESULT(privacy_rules, UserPrivacySettingRules::from_telegram_api(std::move(rules)));
- get_info(user_privacy_setting).has_set_query = false;
- do_update_privacy(user_privacy_setting, std::move(privacy_rules), true);
- return make_tl_object<td_api::ok>();
- }());
- }));
+ send_with_promise(
+ std::move(net_query), PromiseCreator::lambda([this, user_privacy_setting, promise = std::move(promise)](
+ Result<NetQueryPtr> x_net_query) mutable {
+ promise.set_result([&]() -> Result<Unit> {
+ get_info(user_privacy_setting).has_set_query = false;
+ TRY_RESULT(net_query, std::move(x_net_query));
+ TRY_RESULT(rules, fetch_result<telegram_api::account_setPrivacy>(std::move(net_query)));
+ LOG(INFO) << "Receive " << to_string(rules);
+ TRY_RESULT(privacy_rules, UserPrivacySettingRules::get_user_privacy_setting_rules(std::move(rules)));
+ do_update_privacy(user_privacy_setting, std::move(privacy_rules), true);
+ return Unit();
+ }());
+ }));
}
void PrivacyManager::update_privacy(tl_object_ptr<telegram_api::updatePrivacy> update) {
CHECK(update != nullptr);
CHECK(update->key_ != nullptr);
UserPrivacySetting user_privacy_setting(*update->key_);
- auto r_privacy_rules = UserPrivacySettingRules::from_telegram_api(std::move(update->rules_));
+ auto r_privacy_rules = UserPrivacySettingRules::get_user_privacy_setting_rules(std::move(update->rules_));
if (r_privacy_rules.is_error()) {
LOG(INFO) << "Skip updatePrivacy: " << r_privacy_rules.error().message();
auto &info = get_info(user_privacy_setting);
@@ -331,19 +497,19 @@ void PrivacyManager::update_privacy(tl_object_ptr<telegram_api::updatePrivacy> u
}
void PrivacyManager::on_get_result(UserPrivacySetting user_privacy_setting,
- Result<UserPrivacySettingRules> privacy_rules) {
+ Result<UserPrivacySettingRules> r_privacy_rules) {
auto &info = get_info(user_privacy_setting);
auto promises = std::move(info.get_promises);
reset_to_empty(info.get_promises);
for (auto &promise : promises) {
- if (privacy_rules.is_error()) {
- promise.set_error(privacy_rules.error().clone());
+ if (r_privacy_rules.is_error()) {
+ promise.set_error(r_privacy_rules.error().clone());
} else {
- promise.set_value(privacy_rules.ok().as_td_api());
+ promise.set_value(r_privacy_rules.ok().get_user_privacy_setting_rules_object());
}
}
- if (privacy_rules.is_ok()) {
- do_update_privacy(user_privacy_setting, privacy_rules.move_as_ok(), false);
+ if (r_privacy_rules.is_ok()) {
+ do_update_privacy(user_privacy_setting, r_privacy_rules.move_as_ok(), false);
}
}
@@ -354,14 +520,39 @@ void PrivacyManager::do_update_privacy(UserPrivacySetting user_privacy_setting,
info.is_synchronized = true;
if (!(info.rules == privacy_rules)) {
- info.rules = std::move(privacy_rules);
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateUserPrivacySettingRules>(user_privacy_setting.as_td_api(),
- info.rules.as_td_api()));
-
- if ((from_update || was_synchronized) && user_privacy_setting.type() == UserPrivacySetting::Type::UserState) {
- send_closure(G()->contacts_manager(), &ContactsManager::on_update_online_status_privacy);
+ if ((from_update || was_synchronized) && !G()->close_flag()) {
+ switch (user_privacy_setting.type()) {
+ case UserPrivacySetting::Type::UserStatus: {
+ send_closure_later(G()->contacts_manager(), &ContactsManager::on_update_online_status_privacy);
+
+ auto old_restricted = info.rules.get_restricted_user_ids();
+ auto new_restricted = privacy_rules.get_restricted_user_ids();
+ if (old_restricted != new_restricted) {
+ // if a user was unrestricted, it is not received from the server anymore
+ // we need to reget their online status manually
+ std::vector<UserId> unrestricted;
+ std::set_difference(old_restricted.begin(), old_restricted.end(), new_restricted.begin(),
+ new_restricted.end(), std::back_inserter(unrestricted),
+ [](UserId lhs, UserId rhs) { return lhs.get() < rhs.get(); });
+ for (auto &user_id : unrestricted) {
+ send_closure_later(G()->contacts_manager(), &ContactsManager::reload_user, user_id, Promise<Unit>());
+ }
+ }
+ break;
+ }
+ case UserPrivacySetting::Type::UserPhoneNumber:
+ send_closure_later(G()->contacts_manager(), &ContactsManager::on_update_phone_number_privacy);
+ break;
+ default:
+ break;
+ }
}
+
+ info.rules = std::move(privacy_rules);
+ send_closure(
+ G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateUserPrivacySettingRules>(user_privacy_setting.get_user_privacy_setting_object(),
+ info.rules.get_user_privacy_setting_rules_object()));
}
}
@@ -375,4 +566,10 @@ void PrivacyManager::send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> p
G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, id));
}
+void PrivacyManager::hangup() {
+ container_.for_each(
+ [](auto id, Promise<NetQueryPtr> &promise) { promise.set_error(Global::request_aborted_error()); });
+ stop();
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.h b/protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.h
index 820e435afb..18d81bd6cf 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/PrivacyManager.h
@@ -1,27 +1,28 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/net/NetQuery.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
-#include "td/actor/PromiseFuture.h"
-
-#include "td/telegram/net/NetQuery.h"
+#include "td/actor/actor.h"
#include "td/utils/common.h"
#include "td/utils/Container.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
#include <array>
namespace td {
-class PrivacyManager : public NetQueryCallback {
+class PrivacyManager final : public NetQueryCallback {
public:
explicit PrivacyManager(ActorShared<> parent) : parent_(std::move(parent)) {
}
@@ -30,19 +31,33 @@ class PrivacyManager : public NetQueryCallback {
Promise<tl_object_ptr<td_api::userPrivacySettingRules>> promise);
void set_privacy(tl_object_ptr<td_api::UserPrivacySetting> key, tl_object_ptr<td_api::userPrivacySettingRules> rules,
- Promise<tl_object_ptr<td_api::ok>> promise);
+ Promise<Unit> promise);
void update_privacy(tl_object_ptr<telegram_api::updatePrivacy> update);
private:
class UserPrivacySetting {
public:
- enum class Type : int32 { UserState, ChatInvite, Call, Size };
+ enum class Type : int32 {
+ UserStatus,
+ ChatInvite,
+ Call,
+ PeerToPeerCall,
+ LinkInForwardedMessages,
+ UserProfilePhoto,
+ UserPhoneNumber,
+ FindByPhoneNumber,
+ VoiceMessages,
+ Size
+ };
- static Result<UserPrivacySetting> from_td_api(tl_object_ptr<td_api::UserPrivacySetting> key);
explicit UserPrivacySetting(const telegram_api::PrivacyKey &key);
- tl_object_ptr<td_api::UserPrivacySetting> as_td_api() const;
- tl_object_ptr<telegram_api::InputPrivacyKey> as_telegram_api() const;
+
+ static Result<UserPrivacySetting> get_user_privacy_setting(tl_object_ptr<td_api::UserPrivacySetting> key);
+
+ tl_object_ptr<td_api::UserPrivacySetting> get_user_privacy_setting_object() const;
+
+ tl_object_ptr<telegram_api::InputPrivacyKey> get_input_privacy_key() const;
Type type() const {
return type_;
@@ -57,30 +72,41 @@ class PrivacyManager : public NetQueryCallback {
class UserPrivacySettingRule {
public:
UserPrivacySettingRule() = default;
- static Result<UserPrivacySettingRule> from_telegram_api(tl_object_ptr<telegram_api::PrivacyRule> rule);
+
explicit UserPrivacySettingRule(const td_api::UserPrivacySettingRule &rule);
- tl_object_ptr<td_api::UserPrivacySettingRule> as_td_api() const;
- tl_object_ptr<telegram_api::InputPrivacyRule> as_telegram_api() const;
+
+ static Result<UserPrivacySettingRule> get_user_privacy_setting_rule(tl_object_ptr<telegram_api::PrivacyRule> rule);
+
+ tl_object_ptr<td_api::UserPrivacySettingRule> get_user_privacy_setting_rule_object() const;
+
+ tl_object_ptr<telegram_api::InputPrivacyRule> get_input_privacy_rule() const;
bool operator==(const UserPrivacySettingRule &other) const {
- return type_ == other.type_ && user_ids_ == other.user_ids_;
+ return type_ == other.type_ && user_ids_ == other.user_ids_ && chat_ids_ == other.chat_ids_;
}
+ vector<UserId> get_restricted_user_ids() const;
+
private:
- enum class Type {
+ enum class Type : int32 {
AllowContacts,
AllowAll,
AllowUsers,
+ AllowChatParticipants,
RestrictContacts,
RestrictAll,
- RestrictUsers
+ RestrictUsers,
+ RestrictChatParticipants
} type_ = Type::RestrictAll;
- vector<int32> user_ids_;
+ vector<UserId> user_ids_;
+ vector<int64> chat_ids_;
+
+ vector<tl_object_ptr<telegram_api::InputUser>> get_input_users() const;
- vector<int32> user_ids_as_td_api() const;
+ void set_chat_ids(const vector<int64> &dialog_ids);
- vector<tl_object_ptr<telegram_api::InputUser>> user_ids_as_telegram_api() const;
+ vector<int64> chat_ids_as_dialog_ids() const;
explicit UserPrivacySettingRule(const telegram_api::PrivacyRule &rule);
};
@@ -88,16 +114,26 @@ class PrivacyManager : public NetQueryCallback {
class UserPrivacySettingRules {
public:
UserPrivacySettingRules() = default;
- static Result<UserPrivacySettingRules> from_telegram_api(tl_object_ptr<telegram_api::account_privacyRules> rules);
- static Result<UserPrivacySettingRules> from_telegram_api(vector<tl_object_ptr<telegram_api::PrivacyRule>> rules);
- static Result<UserPrivacySettingRules> from_td_api(tl_object_ptr<td_api::userPrivacySettingRules> rules);
- tl_object_ptr<td_api::userPrivacySettingRules> as_td_api() const;
- vector<tl_object_ptr<telegram_api::InputPrivacyRule>> as_telegram_api() const;
+
+ static Result<UserPrivacySettingRules> get_user_privacy_setting_rules(
+ tl_object_ptr<telegram_api::account_privacyRules> rules);
+
+ static Result<UserPrivacySettingRules> get_user_privacy_setting_rules(
+ vector<tl_object_ptr<telegram_api::PrivacyRule>> rules);
+
+ static Result<UserPrivacySettingRules> get_user_privacy_setting_rules(
+ tl_object_ptr<td_api::userPrivacySettingRules> rules);
+
+ tl_object_ptr<td_api::userPrivacySettingRules> get_user_privacy_setting_rules_object() const;
+
+ vector<tl_object_ptr<telegram_api::InputPrivacyRule>> get_input_privacy_rules() const;
bool operator==(const UserPrivacySettingRules &other) const {
return rules_ == other.rules_;
}
+ vector<UserId> get_restricted_user_ids() const;
+
private:
vector<UserPrivacySettingRule> rules_;
};
@@ -116,12 +152,15 @@ class PrivacyManager : public NetQueryCallback {
return info_[static_cast<size_t>(key.type())];
}
- void on_get_result(UserPrivacySetting user_privacy_setting, Result<UserPrivacySettingRules> privacy_rules);
+ void on_get_result(UserPrivacySetting user_privacy_setting, Result<UserPrivacySettingRules> r_privacy_rules);
void do_update_privacy(UserPrivacySetting user_privacy_setting, UserPrivacySettingRules &&privacy_rules,
bool from_update);
- void on_result(NetQueryPtr query) override;
+ void on_result(NetQueryPtr query) final;
Container<Promise<NetQueryPtr>> container_;
void send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> promise);
+
+ void hangup() final;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PtsManager.h b/protocols/Telegram/tdlib/td/td/telegram/PtsManager.h
index c7c50409cc..bf97e6c26e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/PtsManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/PtsManager.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,9 +7,10 @@
#pragma once
#include "td/utils/ChangesProcessor.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
namespace td {
+
// It is not about handling gaps.
// It is about finding mem processed pts.
// All checks must be done before.
@@ -25,8 +26,7 @@ class PtsManager {
// 0 if not a checkpoint
PtsId add_pts(int32 pts) {
- CHECK(pts >= 0);
- if (pts != 0) {
+ if (pts > 0) {
mem_pts_ = pts;
}
return state_helper_.add(pts);
@@ -54,4 +54,5 @@ class PtsManager {
int32 mem_pts_ = -1;
ChangesProcessor<int32> state_helper_;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/PublicDialogType.h b/protocols/Telegram/tdlib/td/td/telegram/PublicDialogType.h
new file mode 100644
index 0000000000..fd2d4894a0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/PublicDialogType.h
@@ -0,0 +1,22 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+
+namespace td {
+
+enum class PublicDialogType : int32 { HasUsername, IsLocationBased };
+
+inline PublicDialogType get_public_dialog_type(const td_api::object_ptr<td_api::PublicChatType> &type) {
+ if (type == nullptr || type->get_id() == td_api::publicChatTypeHasUsername::ID) {
+ return PublicDialogType::HasUsername;
+ }
+ return PublicDialogType::IsLocationBased;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/QueryCombiner.cpp b/protocols/Telegram/tdlib/td/td/telegram/QueryCombiner.cpp
new file mode 100644
index 0000000000..b3461924be
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/QueryCombiner.cpp
@@ -0,0 +1,109 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/QueryCombiner.h"
+
+#include "td/telegram/Global.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/Time.h"
+
+namespace td {
+
+QueryCombiner::QueryCombiner(Slice name, double min_delay) : next_query_time_(Time::now()), min_delay_(min_delay) {
+ register_actor(name, this).release();
+}
+
+void QueryCombiner::add_query(int64 query_id, Promise<Promise<Unit>> &&send_query, Promise<Unit> &&promise) {
+ LOG(INFO) << "Add query " << query_id << " with" << (promise ? "" : "out") << " promise";
+ CHECK(query_id != 0);
+ auto &query = queries_[query_id];
+ if (promise) {
+ query.promises.push_back(std::move(promise));
+ } else if (min_delay_ > 0 && !query.is_sent) {
+ // if there is no promise, then noone waits for response
+ // we can delay query to not exceed any flood limit
+ if (query.send_query) {
+ // the query is already delayed
+ return;
+ }
+ query.send_query = std::move(send_query);
+ delayed_queries_.push(query_id);
+ loop();
+ return;
+ }
+ if (query.is_sent) {
+ // just wait for the result
+ return;
+ }
+
+ if (!query.send_query) {
+ query.send_query = std::move(send_query);
+ }
+ do_send_query(query_id, query);
+}
+
+void QueryCombiner::do_send_query(int64 query_id, QueryInfo &query) {
+ LOG(INFO) << "Send query " << query_id;
+ CHECK(query.send_query);
+ query.is_sent = true;
+ auto send_query = std::move(query.send_query);
+ CHECK(!query.send_query);
+ next_query_time_ = Time::now() + min_delay_;
+ query_count_++;
+ send_query.set_value(PromiseCreator::lambda([actor_id = actor_id(this), query_id](Result<Unit> &&result) {
+ send_closure(actor_id, &QueryCombiner::on_get_query_result, query_id, std::move(result));
+ }));
+}
+
+void QueryCombiner::on_get_query_result(int64 query_id, Result<Unit> &&result) {
+ LOG(INFO) << "Get result of query " << query_id << (result.is_error() ? " error" : " success");
+ query_count_--;
+ auto it = queries_.find(query_id);
+ CHECK(it != queries_.end());
+ CHECK(it->second.is_sent);
+ auto promises = std::move(it->second.promises);
+ queries_.erase(it);
+
+ if (result.is_ok()) {
+ set_promises(promises);
+ } else {
+ fail_promises(promises, result.move_as_error());
+ }
+ loop();
+}
+
+void QueryCombiner::loop() {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto now = Time::now();
+ if (now < next_query_time_) {
+ set_timeout_in(next_query_time_ - now + 0.001);
+ return;
+ }
+ if (query_count_ != 0) {
+ return;
+ }
+
+ while (!delayed_queries_.empty()) {
+ auto query_id = delayed_queries_.front();
+ delayed_queries_.pop();
+ auto it = queries_.find(query_id);
+ if (it == queries_.end()) {
+ continue;
+ }
+ auto &query = it->second;
+ if (query.is_sent) {
+ continue;
+ }
+ do_send_query(query_id, query);
+ break;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/QueryCombiner.h b/protocols/Telegram/tdlib/td/td/telegram/QueryCombiner.h
new file mode 100644
index 0000000000..7d1542715d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/QueryCombiner.h
@@ -0,0 +1,51 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+#include <queue>
+
+namespace td {
+
+// combines identical queries into one request
+class QueryCombiner final : public Actor {
+ public:
+ QueryCombiner(Slice name, double min_delay);
+
+ void add_query(int64 query_id, Promise<Promise<Unit>> &&send_query, Promise<Unit> &&promise);
+
+ private:
+ struct QueryInfo {
+ vector<Promise<Unit>> promises;
+ bool is_sent = false;
+ Promise<Promise<Unit>> send_query;
+ };
+
+ int32 query_count_ = 0;
+
+ double next_query_time_;
+ double min_delay_;
+
+ std::queue<int64> delayed_queries_;
+
+ FlatHashMap<int64, QueryInfo> queries_;
+
+ void do_send_query(int64 query_id, QueryInfo &query);
+
+ void on_get_query_result(int64 query_id, Result<Unit> &&result);
+
+ void loop() final;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/RecentDialogList.cpp b/protocols/Telegram/tdlib/td/td/telegram/RecentDialogList.cpp
new file mode 100644
index 0000000000..56c873cc33
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/RecentDialogList.cpp
@@ -0,0 +1,275 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/RecentDialogList.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogListId.h"
+#include "td/telegram/FolderId.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+
+#include "td/actor/MultiPromise.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+
+#include <algorithm>
+
+namespace td {
+
+RecentDialogList::RecentDialogList(Td *td, const char *name, size_t max_size)
+ : td_(td), name_(name), max_size_(max_size) {
+ register_actor(PSLICE() << name << "_chats", this).release();
+}
+
+string RecentDialogList::get_binlog_key() const {
+ return PSTRING() << name_ << "_dialog_usernames_and_ids";
+}
+
+void RecentDialogList::save_dialogs() const {
+ if (!is_loaded_) {
+ return;
+ }
+ CHECK(removed_dialog_ids_.empty());
+
+ SliceBuilder sb;
+ for (auto &dialog_id : dialog_ids_) {
+ sb << ',';
+ if (!G()->parameters().use_chat_info_db) {
+ // if there is no dialog info database, prefer to save dialogs by username
+ string username;
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ if (!td_->contacts_manager_->is_user_contact(dialog_id.get_user_id())) {
+ username = td_->contacts_manager_->get_user_first_username(dialog_id.get_user_id());
+ }
+ break;
+ case DialogType::Chat:
+ break;
+ case DialogType::Channel:
+ username = td_->contacts_manager_->get_channel_first_username(dialog_id.get_channel_id());
+ break;
+ case DialogType::SecretChat:
+ break;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ }
+ if (!username.empty() && username.find(',') == string::npos) {
+ sb << '@' << username;
+ continue;
+ }
+ }
+ sb << dialog_id.get();
+ }
+ auto result = sb.as_cslice();
+ if (!result.empty()) {
+ result.remove_prefix(1);
+ }
+ G()->td_db()->get_binlog_pmc()->set(get_binlog_key(), result.str());
+}
+
+void RecentDialogList::load_dialogs(Promise<Unit> &&promise) {
+ if (is_loaded_) {
+ return promise.set_value(Unit());
+ }
+
+ load_list_queries_.push_back(std::move(promise));
+ if (load_list_queries_.size() != 1) {
+ return;
+ }
+
+ auto found_dialogs = full_split(G()->td_db()->get_binlog_pmc()->get(get_binlog_key()), ',');
+ MultiPromiseActorSafe mpas{"LoadRecentDialogListMultiPromiseActor"};
+ mpas.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), found_dialogs](Unit) mutable {
+ send_closure(actor_id, &RecentDialogList::on_load_dialogs, std::move(found_dialogs));
+ }));
+ mpas.set_ignore_errors(true);
+ auto lock = mpas.get_promise();
+
+ vector<DialogId> dialog_ids;
+ for (auto &found_dialog : found_dialogs) {
+ if (found_dialog[0] == '@') {
+ td_->messages_manager_->search_public_dialog(found_dialog, false, mpas.get_promise());
+ } else {
+ dialog_ids.push_back(DialogId(to_integer<int64>(found_dialog)));
+ }
+ }
+ if (!dialog_ids.empty()) {
+ if (G()->parameters().use_chat_info_db) {
+ td_->messages_manager_->load_dialogs(
+ std::move(dialog_ids),
+ PromiseCreator::lambda(
+ [promise = mpas.get_promise()](vector<DialogId> dialog_ids) mutable { promise.set_value(Unit()); }));
+ } else {
+ td_->messages_manager_->get_dialogs_from_list(
+ DialogListId(FolderId::main()), 102,
+ PromiseCreator::lambda([promise = mpas.get_promise()](td_api::object_ptr<td_api::chats> &&chats) mutable {
+ promise.set_value(Unit());
+ }));
+ td_->contacts_manager_->search_contacts("", 1, mpas.get_promise());
+ }
+ }
+
+ lock.set_value(Unit());
+}
+
+void RecentDialogList::on_load_dialogs(vector<string> &&found_dialogs) {
+ auto promises = std::move(load_list_queries_);
+ CHECK(!promises.empty());
+
+ if (G()->close_flag()) {
+ return fail_promises(promises, Global::request_aborted_error());
+ }
+
+ auto newly_found_dialogs = std::move(dialog_ids_);
+ reset_to_empty(dialog_ids_);
+
+ for (auto it = found_dialogs.rbegin(); it != found_dialogs.rend(); ++it) {
+ DialogId dialog_id;
+ if ((*it)[0] == '@') {
+ dialog_id = td_->messages_manager_->resolve_dialog_username(it->substr(1));
+ } else {
+ dialog_id = DialogId(to_integer<int64>(*it));
+ }
+ if (dialog_id.is_valid() && removed_dialog_ids_.count(dialog_id) == 0 &&
+ td_->messages_manager_->have_dialog_info(dialog_id) &&
+ td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
+ td_->messages_manager_->force_create_dialog(dialog_id, "recent dialog");
+ do_add_dialog(dialog_id);
+ }
+ }
+ for (auto it = newly_found_dialogs.rbegin(); it != newly_found_dialogs.rend(); ++it) {
+ do_add_dialog(*it);
+ }
+ is_loaded_ = true;
+ removed_dialog_ids_.clear();
+ if (!newly_found_dialogs.empty()) {
+ save_dialogs();
+ }
+
+ set_promises(promises);
+}
+
+void RecentDialogList::add_dialog(DialogId dialog_id) {
+ if (!is_loaded_) {
+ load_dialogs(Promise<Unit>());
+ }
+ if (do_add_dialog(dialog_id)) {
+ save_dialogs();
+ }
+}
+
+bool RecentDialogList::do_add_dialog(DialogId dialog_id) {
+ if (!dialog_ids_.empty() && dialog_ids_[0] == dialog_id) {
+ return false;
+ }
+
+ // TODO create function
+ auto it = std::find(dialog_ids_.begin(), dialog_ids_.end(), dialog_id);
+ if (it == dialog_ids_.end()) {
+ if (dialog_ids_.size() == max_size_) {
+ CHECK(!dialog_ids_.empty());
+ dialog_ids_.back() = dialog_id;
+ } else {
+ dialog_ids_.push_back(dialog_id);
+ }
+ it = dialog_ids_.end() - 1;
+ }
+ std::rotate(dialog_ids_.begin(), it, it + 1);
+ removed_dialog_ids_.erase(dialog_id);
+ return true;
+}
+
+void RecentDialogList::remove_dialog(DialogId dialog_id) {
+ if (!dialog_id.is_valid()) {
+ return;
+ }
+ if (!is_loaded_) {
+ load_dialogs(Promise<Unit>());
+ }
+ if (td::remove(dialog_ids_, dialog_id)) {
+ save_dialogs();
+ } else if (!is_loaded_) {
+ removed_dialog_ids_.insert(dialog_id);
+ }
+}
+
+void RecentDialogList::update_dialogs() {
+ CHECK(is_loaded_);
+ vector<DialogId> dialog_ids;
+ for (auto dialog_id : dialog_ids_) {
+ if (!td_->messages_manager_->have_dialog(dialog_id)) {
+ continue;
+ }
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ // always keep
+ break;
+ case DialogType::Chat: {
+ auto channel_id = td_->contacts_manager_->get_chat_migrated_to_channel_id(dialog_id.get_chat_id());
+ if (channel_id.is_valid() && td_->messages_manager_->have_dialog(DialogId(channel_id))) {
+ dialog_id = DialogId(channel_id);
+ }
+ break;
+ }
+ case DialogType::Channel:
+ // always keep
+ break;
+ case DialogType::SecretChat:
+ if (td_->messages_manager_->is_deleted_secret_chat(dialog_id)) {
+ dialog_id = DialogId();
+ }
+ break;
+ case DialogType::None:
+ default:
+ UNREACHABLE();
+ break;
+ }
+ if (dialog_id.is_valid()) {
+ dialog_ids.push_back(dialog_id);
+ }
+ }
+
+ if (dialog_ids != dialog_ids_) {
+ dialog_ids_ = std::move(dialog_ids);
+ save_dialogs();
+ }
+}
+
+std::pair<int32, vector<DialogId>> RecentDialogList::get_dialogs(int32 limit, Promise<Unit> &&promise) {
+ load_dialogs(std::move(promise));
+ if (!is_loaded_) {
+ return {};
+ }
+
+ update_dialogs();
+
+ CHECK(limit >= 0);
+ auto total_count = narrow_cast<int32>(dialog_ids_.size());
+ return {total_count, vector<DialogId>(dialog_ids_.begin(), dialog_ids_.begin() + min(limit, total_count))};
+}
+
+void RecentDialogList::clear_dialogs() {
+ if (dialog_ids_.empty() && is_loaded_) {
+ return;
+ }
+
+ is_loaded_ = true;
+ dialog_ids_.clear();
+ removed_dialog_ids_.clear();
+ save_dialogs();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/RecentDialogList.h b/protocols/Telegram/tdlib/td/td/telegram/RecentDialogList.h
new file mode 100644
index 0000000000..1163c73aa2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/RecentDialogList.h
@@ -0,0 +1,59 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/Promise.h"
+
+#include <utility>
+
+namespace td {
+
+class Td;
+
+// stores list of Dialog identifiers of a limited size
+class RecentDialogList final : public Actor {
+ public:
+ RecentDialogList(Td *td, const char *name, size_t max_size);
+
+ void add_dialog(DialogId dialog_id);
+
+ void remove_dialog(DialogId dialog_id);
+
+ std::pair<int32, vector<DialogId>> get_dialogs(int32 limit, Promise<Unit> &&promise);
+
+ void clear_dialogs();
+
+ private:
+ Td *td_;
+ const char *name_;
+ size_t max_size_;
+ vector<DialogId> dialog_ids_;
+ FlatHashSet<DialogId, DialogIdHash> removed_dialog_ids_;
+
+ bool is_loaded_ = false;
+ vector<Promise<Unit>> load_list_queries_;
+
+ void load_dialogs(Promise<Unit> &&promise);
+
+ void on_load_dialogs(vector<string> &&found_dialogs);
+
+ bool do_add_dialog(DialogId dialog_id);
+
+ string get_binlog_key() const;
+
+ void update_dialogs();
+
+ void save_dialogs() const;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.cpp b/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.cpp
index 110331f2bc..f3771a20f1 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.cpp
@@ -1,26 +1,32 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/ReplyMarkup.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Dependencies.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/LinkManager.h"
+#include "td/telegram/misc.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
-#include "td/telegram/misc.h"
-
#include "td/utils/buffer.h"
#include "td/utils/format.h"
-#include "td/utils/HttpUrl.h"
#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
+
+#include <limits>
namespace td {
static constexpr int32 REPLY_MARKUP_FLAG_NEED_RESIZE_KEYBOARD = 1 << 0;
static constexpr int32 REPLY_MARKUP_FLAG_IS_ONE_TIME_KEYBOARD = 1 << 1;
static constexpr int32 REPLY_MARKUP_FLAG_IS_PERSONAL = 1 << 2;
+static constexpr int32 REPLY_MARKUP_FLAG_HAS_PLACEHOLDER = 1 << 3;
static bool operator==(const KeyboardButton &lhs, const KeyboardButton &rhs) {
return lhs.type == rhs.type && lhs.text == rhs.text;
@@ -38,6 +44,18 @@ static StringBuilder &operator<<(StringBuilder &string_builder, const KeyboardBu
case KeyboardButton::Type::RequestLocation:
string_builder << "RequestLocation";
break;
+ case KeyboardButton::Type::RequestPoll:
+ string_builder << "RequestPoll";
+ break;
+ case KeyboardButton::Type::RequestPollQuiz:
+ string_builder << "RequestPollQuiz";
+ break;
+ case KeyboardButton::Type::RequestPollRegular:
+ string_builder << "RequestPollRegular";
+ break;
+ case KeyboardButton::Type::WebView:
+ string_builder << "WebView";
+ break;
default:
UNREACHABLE();
}
@@ -45,7 +63,7 @@ static StringBuilder &operator<<(StringBuilder &string_builder, const KeyboardBu
}
static bool operator==(const InlineKeyboardButton &lhs, const InlineKeyboardButton &rhs) {
- return lhs.type == rhs.type && lhs.text == rhs.text && lhs.data == rhs.data;
+ return lhs.type == rhs.type && lhs.text == rhs.text && lhs.data == rhs.data && lhs.id == rhs.id;
}
static StringBuilder &operator<<(StringBuilder &string_builder, const InlineKeyboardButton &keyboard_button) {
@@ -69,6 +87,18 @@ static StringBuilder &operator<<(StringBuilder &string_builder, const InlineKeyb
case InlineKeyboardButton::Type::Buy:
string_builder << "Buy";
break;
+ case InlineKeyboardButton::Type::UrlAuth:
+ string_builder << "UrlAuth, ID = " << keyboard_button.id;
+ break;
+ case InlineKeyboardButton::Type::CallbackWithPassword:
+ string_builder << "CallbackWithPassword";
+ break;
+ case InlineKeyboardButton::Type::User:
+ string_builder << "User " << keyboard_button.user_id.get();
+ break;
+ case InlineKeyboardButton::Type::WebView:
+ string_builder << "WebView";
+ break;
default:
UNREACHABLE();
}
@@ -86,6 +116,9 @@ bool operator==(const ReplyMarkup &lhs, const ReplyMarkup &rhs) {
if (lhs.is_personal != rhs.is_personal) {
return false;
}
+ if (lhs.placeholder != rhs.placeholder) {
+ return false;
+ }
if (lhs.type != ReplyMarkup::Type::ShowKeyboard) {
return true;
}
@@ -118,6 +151,9 @@ StringBuilder &ReplyMarkup::print(StringBuilder &string_builder) const {
if (is_personal) {
string_builder << ", personal";
}
+ if (!placeholder.empty()) {
+ string_builder << ", placeholder \"" << placeholder << '"';
+ }
if (type == ReplyMarkup::Type::ShowKeyboard) {
if (need_resize_keyboard) {
@@ -169,6 +205,33 @@ static KeyboardButton get_keyboard_button(tl_object_ptr<telegram_api::KeyboardBu
button.text = std::move(keyboard_button->text_);
break;
}
+ case telegram_api::keyboardButtonRequestPoll::ID: {
+ auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonRequestPoll>(keyboard_button_ptr);
+ if (keyboard_button->flags_ & telegram_api::keyboardButtonRequestPoll::QUIZ_MASK) {
+ if (keyboard_button->quiz_) {
+ button.type = KeyboardButton::Type::RequestPollQuiz;
+ } else {
+ button.type = KeyboardButton::Type::RequestPollRegular;
+ }
+ } else {
+ button.type = KeyboardButton::Type::RequestPoll;
+ }
+ button.text = std::move(keyboard_button->text_);
+ break;
+ }
+ case telegram_api::keyboardButtonSimpleWebView::ID: {
+ auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonSimpleWebView>(keyboard_button_ptr);
+ auto r_url = LinkManager::check_link(keyboard_button->url_);
+ if (r_url.is_error()) {
+ LOG(ERROR) << "Keyboard Web App " << r_url.error().message();
+ break;
+ }
+
+ button.type = KeyboardButton::Type::WebView;
+ button.text = std::move(keyboard_button->text_);
+ button.url = r_url.move_as_ok();
+ break;
+ }
default:
LOG(ERROR) << "Unsupported keyboard button: " << to_string(keyboard_button_ptr);
}
@@ -183,14 +246,20 @@ static InlineKeyboardButton get_inline_keyboard_button(
switch (keyboard_button_ptr->get_id()) {
case telegram_api::keyboardButtonUrl::ID: {
auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonUrl>(keyboard_button_ptr);
+ auto r_url = LinkManager::check_link(keyboard_button->url_);
+ if (r_url.is_error()) {
+ LOG(ERROR) << "Inline keyboard " << r_url.error().message();
+ break;
+ }
button.type = InlineKeyboardButton::Type::Url;
button.text = std::move(keyboard_button->text_);
- button.data = std::move(keyboard_button->url_);
+ button.data = r_url.move_as_ok();
break;
}
case telegram_api::keyboardButtonCallback::ID: {
auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonCallback>(keyboard_button_ptr);
- button.type = InlineKeyboardButton::Type::Callback;
+ button.type = keyboard_button->requires_password_ ? InlineKeyboardButton::Type::CallbackWithPassword
+ : InlineKeyboardButton::Type::Callback;
button.text = std::move(keyboard_button->text_);
button.data = keyboard_button->data_.as_slice().str();
break;
@@ -216,6 +285,44 @@ static InlineKeyboardButton get_inline_keyboard_button(
button.text = std::move(keyboard_button->text_);
break;
}
+ case telegram_api::keyboardButtonUrlAuth::ID: {
+ auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonUrlAuth>(keyboard_button_ptr);
+ auto r_url = LinkManager::check_link(keyboard_button->url_);
+ if (r_url.is_error()) {
+ LOG(ERROR) << "Inline keyboard Login " << r_url.error().message();
+ break;
+ }
+ button.type = InlineKeyboardButton::Type::UrlAuth;
+ button.id = keyboard_button->button_id_;
+ button.text = std::move(keyboard_button->text_);
+ button.forward_text = std::move(keyboard_button->fwd_text_);
+ button.data = r_url.move_as_ok();
+ break;
+ }
+ case telegram_api::keyboardButtonUserProfile::ID: {
+ auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonUserProfile>(keyboard_button_ptr);
+ auto user_id = UserId(keyboard_button->user_id_);
+ if (!user_id.is_valid()) {
+ LOG(ERROR) << "Receive " << user_id << " in inline keyboard";
+ break;
+ }
+ button.type = InlineKeyboardButton::Type::User;
+ button.text = std::move(keyboard_button->text_);
+ button.user_id = user_id;
+ break;
+ }
+ case telegram_api::keyboardButtonWebView::ID: {
+ auto keyboard_button = move_tl_object_as<telegram_api::keyboardButtonWebView>(keyboard_button_ptr);
+ auto r_url = LinkManager::check_link(keyboard_button->url_);
+ if (r_url.is_error()) {
+ LOG(ERROR) << "Inline keyboard Web App " << r_url.error().message();
+ break;
+ }
+ button.type = InlineKeyboardButton::Type::WebView;
+ button.text = std::move(keyboard_button->text_);
+ button.data = r_url.move_as_ok();
+ break;
+ }
default:
LOG(ERROR) << "Unsupported inline keyboard button: " << to_string(keyboard_button_ptr);
}
@@ -263,6 +370,7 @@ unique_ptr<ReplyMarkup> get_reply_markup(tl_object_ptr<telegram_api::ReplyMarkup
reply_markup->need_resize_keyboard = (keyboard_markup->flags_ & REPLY_MARKUP_FLAG_NEED_RESIZE_KEYBOARD) != 0;
reply_markup->is_one_time_keyboard = (keyboard_markup->flags_ & REPLY_MARKUP_FLAG_IS_ONE_TIME_KEYBOARD) != 0;
reply_markup->is_personal = (keyboard_markup->flags_ & REPLY_MARKUP_FLAG_IS_PERSONAL) != 0;
+ reply_markup->placeholder = std::move(keyboard_markup->placeholder_);
reply_markup->keyboard.reserve(keyboard_markup->rows_.size());
for (auto &row : keyboard_markup->rows_) {
vector<KeyboardButton> buttons;
@@ -292,6 +400,7 @@ unique_ptr<ReplyMarkup> get_reply_markup(tl_object_ptr<telegram_api::ReplyMarkup
auto force_reply_markup = move_tl_object_as<telegram_api::replyKeyboardForceReply>(reply_markup_ptr);
reply_markup->type = ReplyMarkup::Type::ForceReply;
reply_markup->is_personal = (force_reply_markup->flags_ & REPLY_MARKUP_FLAG_IS_PERSONAL) != 0;
+ reply_markup->placeholder = std::move(force_reply_markup->placeholder_);
break;
}
default:
@@ -329,16 +438,51 @@ static Result<KeyboardButton> get_keyboard_button(tl_object_ptr<td_api::keyboard
break;
case td_api::keyboardButtonTypeRequestPhoneNumber::ID:
if (!request_buttons_allowed) {
- return Status::Error(400, "Phone number can be requested in a private chats only");
+ return Status::Error(400, "Phone number can be requested in private chats only");
}
current_button.type = KeyboardButton::Type::RequestPhoneNumber;
break;
case td_api::keyboardButtonTypeRequestLocation::ID:
if (!request_buttons_allowed) {
- return Status::Error(400, "Location can be requested in a private chats only");
+ return Status::Error(400, "Location can be requested in private chats only");
}
current_button.type = KeyboardButton::Type::RequestLocation;
break;
+ case td_api::keyboardButtonTypeRequestPoll::ID: {
+ if (!request_buttons_allowed) {
+ return Status::Error(400, "Poll can be requested in private chats only");
+ }
+ auto *request_poll = static_cast<const td_api::keyboardButtonTypeRequestPoll *>(button->type_.get());
+ if (request_poll->force_quiz_ && request_poll->force_regular_) {
+ return Status::Error(400, "Can't force quiz mode and regular poll simultaneously");
+ }
+ if (request_poll->force_quiz_) {
+ current_button.type = KeyboardButton::Type::RequestPollQuiz;
+ } else if (request_poll->force_regular_) {
+ current_button.type = KeyboardButton::Type::RequestPollRegular;
+ } else {
+ current_button.type = KeyboardButton::Type::RequestPoll;
+ }
+ break;
+ }
+ case td_api::keyboardButtonTypeWebApp::ID: {
+ if (!request_buttons_allowed) {
+ return Status::Error(400, "Web App buttons can be used in private chats only");
+ }
+
+ auto button_type = move_tl_object_as<td_api::keyboardButtonTypeWebApp>(button->type_);
+ auto user_id = LinkManager::get_link_user_id(button_type->url_);
+ if (user_id.is_valid()) {
+ return Status::Error(400, "Link to a user can't be used in Web App URL buttons");
+ }
+ auto r_url = LinkManager::check_link(button_type->url_, true, !G()->is_test_dc());
+ if (r_url.is_error()) {
+ return Status::Error(400, PSLICE() << "Keyboard button Web App " << r_url.error().message());
+ }
+ current_button.type = KeyboardButton::Type::WebView;
+ current_button.url = std::move(button_type->url_);
+ break;
+ }
default:
UNREACHABLE();
}
@@ -346,7 +490,7 @@ static Result<KeyboardButton> get_keyboard_button(tl_object_ptr<td_api::keyboard
}
static Result<InlineKeyboardButton> get_inline_keyboard_button(tl_object_ptr<td_api::inlineKeyboardButton> &&button,
- bool switch_inline_current_chat_buttons_allowed) {
+ bool switch_inline_buttons_allowed) {
CHECK(button != nullptr);
if (!clean_input_string(button->text_)) {
return Status::Error(400, "Keyboard button text must be encoded in UTF-8");
@@ -356,38 +500,54 @@ static Result<InlineKeyboardButton> get_inline_keyboard_button(tl_object_ptr<td_
current_button.text = std::move(button->text_);
if (button->type_ == nullptr) {
- return Status::Error(400, "Inline keyboard button type can't be empty");
+ return Status::Error(400, "Inline keyboard button type must be non-empty");
}
int32 button_type_id = button->type_->get_id();
switch (button_type_id) {
case td_api::inlineKeyboardButtonTypeUrl::ID: {
+ auto button_type = move_tl_object_as<td_api::inlineKeyboardButtonTypeUrl>(button->type_);
+ auto user_id = LinkManager::get_link_user_id(button_type->url_);
+ if (user_id.is_valid()) {
+ current_button.type = InlineKeyboardButton::Type::User;
+ current_button.user_id = user_id;
+ break;
+ }
+ auto r_url = LinkManager::check_link(button_type->url_);
+ if (r_url.is_error()) {
+ return Status::Error(400, PSLICE() << "Inline keyboard button " << r_url.error().message());
+ }
current_button.type = InlineKeyboardButton::Type::Url;
- TRY_RESULT(http_url, parse_url(static_cast<td_api::inlineKeyboardButtonTypeUrl *>(button->type_.get())->url_));
- current_button.data = http_url.get_url();
+ current_button.data = r_url.move_as_ok();
if (!clean_input_string(current_button.data)) {
- return Status::Error(400, "Inline keyboard button url must be encoded in UTF-8");
+ return Status::Error(400, "Inline keyboard button URL must be encoded in UTF-8");
}
break;
}
- case td_api::inlineKeyboardButtonTypeCallback::ID:
+ case td_api::inlineKeyboardButtonTypeCallback::ID: {
+ auto button_type = move_tl_object_as<td_api::inlineKeyboardButtonTypeCallback>(button->type_);
current_button.type = InlineKeyboardButton::Type::Callback;
- current_button.data =
- std::move(static_cast<td_api::inlineKeyboardButtonTypeCallback *>(button->type_.get())->data_);
+ current_button.data = std::move(button_type->data_);
break;
+ }
case td_api::inlineKeyboardButtonTypeCallbackGame::ID:
current_button.type = InlineKeyboardButton::Type::CallbackGame;
break;
+ case td_api::inlineKeyboardButtonTypeCallbackWithPassword::ID:
+ return Status::Error(400, "Can't use CallbackWithPassword inline button");
case td_api::inlineKeyboardButtonTypeSwitchInline::ID: {
- auto switch_inline_button = move_tl_object_as<td_api::inlineKeyboardButtonTypeSwitchInline>(button->type_);
- if (!switch_inline_current_chat_buttons_allowed && switch_inline_button->in_current_chat_) {
- return Status::Error(400, "Can't use switch_inline_query_current_chat in a channel chat");
+ auto button_type = move_tl_object_as<td_api::inlineKeyboardButtonTypeSwitchInline>(button->type_);
+ if (!switch_inline_buttons_allowed) {
+ const char *button_name =
+ button_type->in_current_chat_ ? "switch_inline_query_current_chat" : "switch_inline_query";
+ return Status::Error(400, PSLICE() << "Can't use " << button_name
+ << " in a channel chat, because a user will not be able to use the button "
+ "without knowing bot's username");
}
- current_button.type = switch_inline_button->in_current_chat_
- ? InlineKeyboardButton::Type::SwitchInlineCurrentDialog
- : InlineKeyboardButton::Type::SwitchInline;
- current_button.data = std::move(switch_inline_button->query_);
+ current_button.type = button_type->in_current_chat_ ? InlineKeyboardButton::Type::SwitchInlineCurrentDialog
+ : InlineKeyboardButton::Type::SwitchInline;
+ current_button.data = std::move(button_type->query_);
if (!clean_input_string(current_button.data)) {
return Status::Error(400, "Inline keyboard button switch inline query must be encoded in UTF-8");
}
@@ -396,6 +556,58 @@ static Result<InlineKeyboardButton> get_inline_keyboard_button(tl_object_ptr<td_
case td_api::inlineKeyboardButtonTypeBuy::ID:
current_button.type = InlineKeyboardButton::Type::Buy;
break;
+ case td_api::inlineKeyboardButtonTypeLoginUrl::ID: {
+ auto button_type = td_api::move_object_as<td_api::inlineKeyboardButtonTypeLoginUrl>(button->type_);
+ auto user_id = LinkManager::get_link_user_id(button_type->url_);
+ if (user_id.is_valid()) {
+ return Status::Error(400, "Link to a user can't be used in login URL buttons");
+ }
+ auto r_url = LinkManager::check_link(button_type->url_, true, !G()->is_test_dc());
+ if (r_url.is_error()) {
+ return Status::Error(400, PSLICE() << "Inline keyboard button login " << r_url.error().message());
+ }
+ current_button.type = InlineKeyboardButton::Type::UrlAuth;
+ current_button.data = r_url.move_as_ok();
+ current_button.forward_text = std::move(button_type->forward_text_);
+ if (!clean_input_string(current_button.data)) {
+ return Status::Error(400, "Inline keyboard button login URL must be encoded in UTF-8");
+ }
+ if (!clean_input_string(current_button.forward_text)) {
+ return Status::Error(400, "Inline keyboard button forward text must be encoded in UTF-8");
+ }
+ current_button.id = button_type->id_;
+ if (current_button.id == std::numeric_limits<int64>::min() ||
+ !UserId(current_button.id >= 0 ? current_button.id : -current_button.id).is_valid()) {
+ return Status::Error(400, "Invalid bot_user_id specified");
+ }
+ break;
+ }
+ case td_api::inlineKeyboardButtonTypeUser::ID: {
+ auto button_type = td_api::move_object_as<td_api::inlineKeyboardButtonTypeUser>(button->type_);
+ current_button.type = InlineKeyboardButton::Type::User;
+ current_button.user_id = UserId(button_type->user_id_);
+ if (!current_button.user_id.is_valid()) {
+ return Status::Error(400, "Invalid user_id specified");
+ }
+ break;
+ }
+ case td_api::inlineKeyboardButtonTypeWebApp::ID: {
+ auto button_type = move_tl_object_as<td_api::inlineKeyboardButtonTypeWebApp>(button->type_);
+ auto user_id = LinkManager::get_link_user_id(button_type->url_);
+ if (user_id.is_valid()) {
+ return Status::Error(400, "Link to a user can't be used in Web App URL buttons");
+ }
+ auto r_url = LinkManager::check_link(button_type->url_, true, !G()->is_test_dc());
+ if (r_url.is_error()) {
+ return Status::Error(400, PSLICE() << "Inline keyboard button Web App " << r_url.error().message());
+ }
+ current_button.type = InlineKeyboardButton::Type::WebView;
+ current_button.data = r_url.move_as_ok();
+ if (!clean_input_string(current_button.data)) {
+ return Status::Error(400, "Inline keyboard button Web App URL must be encoded in UTF-8");
+ }
+ break;
+ }
default:
UNREACHABLE();
}
@@ -405,7 +617,7 @@ static Result<InlineKeyboardButton> get_inline_keyboard_button(tl_object_ptr<td_
Result<unique_ptr<ReplyMarkup>> get_reply_markup(tl_object_ptr<td_api::ReplyMarkup> &&reply_markup_ptr, bool is_bot,
bool only_inline_keyboard, bool request_buttons_allowed,
- bool switch_inline_current_chat_buttons_allowed) {
+ bool switch_inline_buttons_allowed) {
CHECK(!only_inline_keyboard || !request_buttons_allowed);
if (reply_markup_ptr == nullptr || !is_bot) {
return nullptr;
@@ -424,6 +636,7 @@ Result<unique_ptr<ReplyMarkup>> get_reply_markup(tl_object_ptr<td_api::ReplyMark
reply_markup->need_resize_keyboard = show_keyboard_markup->resize_keyboard_;
reply_markup->is_one_time_keyboard = show_keyboard_markup->one_time_;
reply_markup->is_personal = show_keyboard_markup->is_personal_;
+ reply_markup->placeholder = std::move(show_keyboard_markup->input_field_placeholder_);
reply_markup->keyboard.reserve(show_keyboard_markup->rows_.size());
int32 total_button_count = 0;
@@ -474,8 +687,7 @@ Result<unique_ptr<ReplyMarkup>> get_reply_markup(tl_object_ptr<td_api::ReplyMark
continue;
}
- TRY_RESULT(current_button,
- get_inline_keyboard_button(std::move(button), switch_inline_current_chat_buttons_allowed));
+ TRY_RESULT(current_button, get_inline_keyboard_button(std::move(button), switch_inline_buttons_allowed));
row_buttons.push_back(std::move(current_button));
row_button_count++;
@@ -506,6 +718,7 @@ Result<unique_ptr<ReplyMarkup>> get_reply_markup(tl_object_ptr<td_api::ReplyMark
auto force_reply_markup = move_tl_object_as<td_api::replyMarkupForceReply>(reply_markup_ptr);
reply_markup->type = ReplyMarkup::Type::ForceReply;
reply_markup->is_personal = force_reply_markup->is_personal_;
+ reply_markup->placeholder = std::move(force_reply_markup->input_field_placeholder_);
break;
}
default:
@@ -515,7 +728,7 @@ Result<unique_ptr<ReplyMarkup>> get_reply_markup(tl_object_ptr<td_api::ReplyMark
return std::move(reply_markup);
}
-static tl_object_ptr<telegram_api::KeyboardButton> get_keyboard_button(const KeyboardButton &keyboard_button) {
+static tl_object_ptr<telegram_api::KeyboardButton> get_input_keyboard_button(const KeyboardButton &keyboard_button) {
switch (keyboard_button.type) {
case KeyboardButton::Type::Text:
return make_tl_object<telegram_api::keyboardButton>(keyboard_button.text);
@@ -523,19 +736,27 @@ static tl_object_ptr<telegram_api::KeyboardButton> get_keyboard_button(const Key
return make_tl_object<telegram_api::keyboardButtonRequestPhone>(keyboard_button.text);
case KeyboardButton::Type::RequestLocation:
return make_tl_object<telegram_api::keyboardButtonRequestGeoLocation>(keyboard_button.text);
+ case KeyboardButton::Type::RequestPoll:
+ return make_tl_object<telegram_api::keyboardButtonRequestPoll>(0, false, keyboard_button.text);
+ case KeyboardButton::Type::RequestPollQuiz:
+ return make_tl_object<telegram_api::keyboardButtonRequestPoll>(1, true, keyboard_button.text);
+ case KeyboardButton::Type::RequestPollRegular:
+ return make_tl_object<telegram_api::keyboardButtonRequestPoll>(1, false, keyboard_button.text);
+ case KeyboardButton::Type::WebView:
+ return make_tl_object<telegram_api::keyboardButtonSimpleWebView>(keyboard_button.text, keyboard_button.url);
default:
UNREACHABLE();
return nullptr;
}
}
-static tl_object_ptr<telegram_api::KeyboardButton> get_inline_keyboard_button(
- const InlineKeyboardButton &keyboard_button) {
+static tl_object_ptr<telegram_api::KeyboardButton> get_input_keyboard_button(
+ ContactsManager *contacts_manager, const InlineKeyboardButton &keyboard_button) {
switch (keyboard_button.type) {
case InlineKeyboardButton::Type::Url:
return make_tl_object<telegram_api::keyboardButtonUrl>(keyboard_button.text, keyboard_button.data);
case InlineKeyboardButton::Type::Callback:
- return make_tl_object<telegram_api::keyboardButtonCallback>(keyboard_button.text,
+ return make_tl_object<telegram_api::keyboardButtonCallback>(0, false /*ignored*/, keyboard_button.text,
BufferSlice(keyboard_button.data));
case InlineKeyboardButton::Type::CallbackGame:
return make_tl_object<telegram_api::keyboardButtonGame>(keyboard_button.text);
@@ -550,13 +771,47 @@ static tl_object_ptr<telegram_api::KeyboardButton> get_inline_keyboard_button(
}
case InlineKeyboardButton::Type::Buy:
return make_tl_object<telegram_api::keyboardButtonBuy>(keyboard_button.text);
+ case InlineKeyboardButton::Type::UrlAuth: {
+ int32 flags = 0;
+ int64 bot_user_id = keyboard_button.id;
+ if (bot_user_id > 0) {
+ flags |= telegram_api::inputKeyboardButtonUrlAuth::REQUEST_WRITE_ACCESS_MASK;
+ } else {
+ bot_user_id = -bot_user_id;
+ }
+ if (!keyboard_button.forward_text.empty()) {
+ flags |= telegram_api::inputKeyboardButtonUrlAuth::FWD_TEXT_MASK;
+ }
+ auto r_input_user = contacts_manager->get_input_user(UserId(bot_user_id));
+ if (r_input_user.is_error()) {
+ LOG(ERROR) << "Failed to get InputUser for " << bot_user_id << ": " << r_input_user.error();
+ return make_tl_object<telegram_api::keyboardButtonUrl>(keyboard_button.text, keyboard_button.data);
+ }
+ return make_tl_object<telegram_api::inputKeyboardButtonUrlAuth>(flags, false /*ignored*/, keyboard_button.text,
+ keyboard_button.forward_text,
+ keyboard_button.data, r_input_user.move_as_ok());
+ }
+ case InlineKeyboardButton::Type::CallbackWithPassword:
+ UNREACHABLE();
+ break;
+ case InlineKeyboardButton::Type::User: {
+ auto r_input_user = contacts_manager->get_input_user(keyboard_button.user_id);
+ if (r_input_user.is_error()) {
+ LOG(ERROR) << "Failed to get InputUser for " << keyboard_button.user_id << ": " << r_input_user.error();
+ r_input_user = make_tl_object<telegram_api::inputUserEmpty>();
+ }
+ return make_tl_object<telegram_api::inputKeyboardButtonUserProfile>(keyboard_button.text,
+ r_input_user.move_as_ok());
+ }
+ case InlineKeyboardButton::Type::WebView:
+ return make_tl_object<telegram_api::keyboardButtonWebView>(keyboard_button.text, keyboard_button.data);
default:
UNREACHABLE();
return nullptr;
}
}
-tl_object_ptr<telegram_api::ReplyMarkup> ReplyMarkup::get_input_reply_markup() const {
+tl_object_ptr<telegram_api::ReplyMarkup> ReplyMarkup::get_input_reply_markup(ContactsManager *contacts_manager) const {
LOG(DEBUG) << "Send " << *this;
switch (type) {
case ReplyMarkup::Type::InlineKeyboard: {
@@ -566,7 +821,7 @@ tl_object_ptr<telegram_api::ReplyMarkup> ReplyMarkup::get_input_reply_markup() c
vector<tl_object_ptr<telegram_api::KeyboardButton>> buttons;
buttons.reserve(row.size());
for (auto &button : row) {
- buttons.push_back(get_inline_keyboard_button(button));
+ buttons.push_back(get_input_keyboard_button(contacts_manager, button));
}
rows.push_back(make_tl_object<telegram_api::keyboardButtonRow>(std::move(buttons)));
}
@@ -580,7 +835,7 @@ tl_object_ptr<telegram_api::ReplyMarkup> ReplyMarkup::get_input_reply_markup() c
vector<tl_object_ptr<telegram_api::KeyboardButton>> buttons;
buttons.reserve(row.size());
for (auto &button : row) {
- buttons.push_back(get_keyboard_button(button));
+ buttons.push_back(get_input_keyboard_button(button));
}
rows.push_back(make_tl_object<telegram_api::keyboardButtonRow>(std::move(buttons)));
}
@@ -588,13 +843,14 @@ tl_object_ptr<telegram_api::ReplyMarkup> ReplyMarkup::get_input_reply_markup() c
return make_tl_object<telegram_api::replyKeyboardMarkup>(
need_resize_keyboard * REPLY_MARKUP_FLAG_NEED_RESIZE_KEYBOARD +
is_one_time_keyboard * REPLY_MARKUP_FLAG_IS_ONE_TIME_KEYBOARD +
- is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL,
- false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(rows));
+ is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL + (!placeholder.empty()) * REPLY_MARKUP_FLAG_HAS_PLACEHOLDER,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(rows), placeholder);
}
case ReplyMarkup::Type::ForceReply:
LOG(DEBUG) << "Return replyKeyboardForceReply to send it";
- return make_tl_object<telegram_api::replyKeyboardForceReply>(is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL,
- false /*ignored*/, false /*ignored*/);
+ return make_tl_object<telegram_api::replyKeyboardForceReply>(
+ is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL + (!placeholder.empty()) * REPLY_MARKUP_FLAG_HAS_PLACEHOLDER,
+ false /*ignored*/, false /*ignored*/, placeholder);
case ReplyMarkup::Type::RemoveKeyboard:
LOG(DEBUG) << "Return replyKeyboardHide to send it";
return make_tl_object<telegram_api::replyKeyboardHide>(is_personal * REPLY_MARKUP_FLAG_IS_PERSONAL,
@@ -617,6 +873,18 @@ static tl_object_ptr<td_api::keyboardButton> get_keyboard_button_object(const Ke
case KeyboardButton::Type::RequestLocation:
type = make_tl_object<td_api::keyboardButtonTypeRequestLocation>();
break;
+ case KeyboardButton::Type::RequestPoll:
+ type = make_tl_object<td_api::keyboardButtonTypeRequestPoll>(false, false);
+ break;
+ case KeyboardButton::Type::RequestPollQuiz:
+ type = make_tl_object<td_api::keyboardButtonTypeRequestPoll>(false, true);
+ break;
+ case KeyboardButton::Type::RequestPollRegular:
+ type = make_tl_object<td_api::keyboardButtonTypeRequestPoll>(true, false);
+ break;
+ case KeyboardButton::Type::WebView:
+ type = make_tl_object<td_api::keyboardButtonTypeWebApp>(keyboard_button.url);
+ break;
default:
UNREACHABLE();
return nullptr;
@@ -625,7 +893,7 @@ static tl_object_ptr<td_api::keyboardButton> get_keyboard_button_object(const Ke
}
static tl_object_ptr<td_api::inlineKeyboardButton> get_inline_keyboard_button_object(
- const InlineKeyboardButton &keyboard_button) {
+ ContactsManager *contacts_manager, const InlineKeyboardButton &keyboard_button) {
tl_object_ptr<td_api::InlineKeyboardButtonType> type;
switch (keyboard_button.type) {
case InlineKeyboardButton::Type::Url:
@@ -646,6 +914,24 @@ static tl_object_ptr<td_api::inlineKeyboardButton> get_inline_keyboard_button_ob
case InlineKeyboardButton::Type::Buy:
type = make_tl_object<td_api::inlineKeyboardButtonTypeBuy>();
break;
+ case InlineKeyboardButton::Type::UrlAuth:
+ type = make_tl_object<td_api::inlineKeyboardButtonTypeLoginUrl>(keyboard_button.data, keyboard_button.id,
+ keyboard_button.forward_text);
+ break;
+ case InlineKeyboardButton::Type::CallbackWithPassword:
+ type = make_tl_object<td_api::inlineKeyboardButtonTypeCallbackWithPassword>(keyboard_button.data);
+ break;
+ case InlineKeyboardButton::Type::User: {
+ bool need_user = contacts_manager != nullptr && !contacts_manager->is_user_bot(contacts_manager->get_my_id());
+ auto user_id =
+ need_user ? contacts_manager->get_user_id_object(keyboard_button.user_id, "get_inline_keyboard_button_object")
+ : keyboard_button.user_id.get();
+ type = make_tl_object<td_api::inlineKeyboardButtonTypeUser>(user_id);
+ break;
+ }
+ case InlineKeyboardButton::Type::WebView:
+ type = make_tl_object<td_api::inlineKeyboardButtonTypeWebApp>(keyboard_button.data);
+ break;
default:
UNREACHABLE();
return nullptr;
@@ -653,7 +939,7 @@ static tl_object_ptr<td_api::inlineKeyboardButton> get_inline_keyboard_button_ob
return make_tl_object<td_api::inlineKeyboardButton>(keyboard_button.text, std::move(type));
}
-tl_object_ptr<td_api::ReplyMarkup> ReplyMarkup::get_reply_markup_object() const {
+tl_object_ptr<td_api::ReplyMarkup> ReplyMarkup::get_reply_markup_object(ContactsManager *contacts_manager) const {
switch (type) {
case ReplyMarkup::Type::InlineKeyboard: {
vector<vector<tl_object_ptr<td_api::inlineKeyboardButton>>> rows;
@@ -662,7 +948,7 @@ tl_object_ptr<td_api::ReplyMarkup> ReplyMarkup::get_reply_markup_object() const
vector<tl_object_ptr<td_api::inlineKeyboardButton>> buttons;
buttons.reserve(row.size());
for (auto &button : row) {
- buttons.push_back(get_inline_keyboard_button_object(button));
+ buttons.push_back(get_inline_keyboard_button_object(contacts_manager, button));
}
rows.push_back(std::move(buttons));
}
@@ -682,32 +968,47 @@ tl_object_ptr<td_api::ReplyMarkup> ReplyMarkup::get_reply_markup_object() const
}
return make_tl_object<td_api::replyMarkupShowKeyboard>(std::move(rows), need_resize_keyboard,
- is_one_time_keyboard, is_personal);
+ is_one_time_keyboard, is_personal, placeholder);
}
case ReplyMarkup::Type::RemoveKeyboard:
return make_tl_object<td_api::replyMarkupRemoveKeyboard>(is_personal);
case ReplyMarkup::Type::ForceReply:
- return make_tl_object<td_api::replyMarkupForceReply>(is_personal);
+ return make_tl_object<td_api::replyMarkupForceReply>(is_personal, placeholder);
default:
UNREACHABLE();
return nullptr;
}
}
-tl_object_ptr<telegram_api::ReplyMarkup> get_input_reply_markup(const unique_ptr<ReplyMarkup> &reply_markup) {
+tl_object_ptr<telegram_api::ReplyMarkup> get_input_reply_markup(ContactsManager *contacts_manager,
+ const unique_ptr<ReplyMarkup> &reply_markup) {
if (reply_markup == nullptr) {
return nullptr;
}
- return reply_markup->get_input_reply_markup();
+ return reply_markup->get_input_reply_markup(contacts_manager);
}
-tl_object_ptr<td_api::ReplyMarkup> get_reply_markup_object(const unique_ptr<ReplyMarkup> &reply_markup) {
+tl_object_ptr<td_api::ReplyMarkup> get_reply_markup_object(ContactsManager *contacts_manager,
+ const unique_ptr<ReplyMarkup> &reply_markup) {
if (reply_markup == nullptr) {
return nullptr;
}
- return reply_markup->get_reply_markup_object();
+ return reply_markup->get_reply_markup_object(contacts_manager);
+}
+
+void add_reply_markup_dependencies(Dependencies &dependencies, const ReplyMarkup *reply_markup) {
+ if (reply_markup == nullptr) {
+ return;
+ }
+ for (auto &row : reply_markup->inline_keyboard) {
+ for (auto &button : row) {
+ if (button.user_id.is_valid()) {
+ dependencies.add(button.user_id);
+ }
+ }
+ }
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.h b/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.h
index c1ac9849ce..e1d7b6bba3 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,6 +8,7 @@
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
#include "td/utils/common.h"
#include "td/utils/Status.h"
@@ -15,18 +16,44 @@
namespace td {
+class ContactsManager;
+class Dependencies;
+
struct KeyboardButton {
// append only
- enum class Type : int32 { Text, RequestPhoneNumber, RequestLocation };
+ enum class Type : int32 {
+ Text,
+ RequestPhoneNumber,
+ RequestLocation,
+ RequestPoll,
+ RequestPollQuiz,
+ RequestPollRegular,
+ WebView
+ };
Type type;
string text;
+ string url; // WebView only
};
struct InlineKeyboardButton {
// append only
- enum class Type : int32 { Url, Callback, CallbackGame, SwitchInline, SwitchInlineCurrentDialog, Buy };
+ enum class Type : int32 {
+ Url,
+ Callback,
+ CallbackGame,
+ SwitchInline,
+ SwitchInlineCurrentDialog,
+ Buy,
+ UrlAuth,
+ CallbackWithPassword,
+ User,
+ WebView
+ };
Type type;
+ int64 id = 0; // UrlAuth only, button_id or (2 * request_write_access - 1) * bot_user_id
+ UserId user_id; // User only
string text;
+ string forward_text; // UrlAuth only
string data;
};
@@ -40,14 +67,15 @@ struct ReplyMarkup {
bool need_resize_keyboard = false; // for ShowKeyboard
bool is_one_time_keyboard = false; // for ShowKeyboard
vector<vector<KeyboardButton>> keyboard; // for ShowKeyboard
+ string placeholder; // for ShowKeyboard, ForceReply
vector<vector<InlineKeyboardButton>> inline_keyboard; // for InlineKeyboard
StringBuilder &print(StringBuilder &string_builder) const;
- tl_object_ptr<telegram_api::ReplyMarkup> get_input_reply_markup() const;
+ tl_object_ptr<telegram_api::ReplyMarkup> get_input_reply_markup(ContactsManager *contacts_manager) const;
- tl_object_ptr<td_api::ReplyMarkup> get_reply_markup_object() const;
+ tl_object_ptr<td_api::ReplyMarkup> get_reply_markup_object(ContactsManager *contacts_manager) const;
};
bool operator==(const ReplyMarkup &lhs, const ReplyMarkup &rhs);
@@ -60,10 +88,14 @@ unique_ptr<ReplyMarkup> get_reply_markup(tl_object_ptr<telegram_api::ReplyMarkup
Result<unique_ptr<ReplyMarkup>> get_reply_markup(tl_object_ptr<td_api::ReplyMarkup> &&reply_markup_ptr, bool is_bot,
bool only_inline_keyboard, bool request_buttons_allowed,
- bool switch_inline_current_chat_buttons_allowed) TD_WARN_UNUSED_RESULT;
+ bool switch_inline_buttons_allowed) TD_WARN_UNUSED_RESULT;
+
+tl_object_ptr<telegram_api::ReplyMarkup> get_input_reply_markup(ContactsManager *contacts_manager,
+ const unique_ptr<ReplyMarkup> &reply_markup);
-tl_object_ptr<telegram_api::ReplyMarkup> get_input_reply_markup(const unique_ptr<ReplyMarkup> &reply_markup);
+tl_object_ptr<td_api::ReplyMarkup> get_reply_markup_object(ContactsManager *contacts_manager,
+ const unique_ptr<ReplyMarkup> &reply_markup);
-tl_object_ptr<td_api::ReplyMarkup> get_reply_markup_object(const unique_ptr<ReplyMarkup> &reply_markup);
+void add_reply_markup_dependencies(Dependencies &dependencies, const ReplyMarkup *reply_markup);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.hpp b/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.hpp
index 4b8bf15ba5..50d0997c87 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/ReplyMarkup.hpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,6 +7,7 @@
#pragma once
#include "td/telegram/ReplyMarkup.h"
+#include "td/telegram/Version.h"
#include "td/utils/tl_helpers.h"
@@ -14,40 +15,117 @@ namespace td {
template <class StorerT>
void store(KeyboardButton button, StorerT &storer) {
+ bool has_url = !button.url.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_url);
+ END_STORE_FLAGS();
store(button.type, storer);
store(button.text, storer);
+ if (has_url) {
+ store(button.url, storer);
+ }
}
template <class ParserT>
void parse(KeyboardButton &button, ParserT &parser) {
+ bool has_url;
+ if (parser.version() >= static_cast<int32>(Version::AddKeyboardButtonFlags)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_url);
+ END_PARSE_FLAGS();
+ } else {
+ has_url = false;
+ }
parse(button.type, parser);
parse(button.text, parser);
+ if (has_url) {
+ parse(button.url, parser);
+ }
}
template <class StorerT>
void store(InlineKeyboardButton button, StorerT &storer) {
+ bool has_id = button.id != 0;
+ bool has_user_id = button.user_id.is_valid();
+ bool has_forward_text = !button.forward_text.empty();
+ bool has_data = !button.data.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_id);
+ STORE_FLAG(has_user_id);
+ STORE_FLAG(has_forward_text);
+ STORE_FLAG(has_data);
+ END_STORE_FLAGS();
store(button.type, storer);
+ if (has_id) {
+ store(button.id, storer);
+ }
+ if (has_user_id) {
+ store(button.user_id, storer);
+ }
store(button.text, storer);
- store(button.data, storer);
+ if (has_forward_text) {
+ store(button.forward_text, storer);
+ }
+ if (has_data) {
+ store(button.data, storer);
+ }
}
template <class ParserT>
void parse(InlineKeyboardButton &button, ParserT &parser) {
- parse(button.type, parser);
- parse(button.text, parser);
- parse(button.data, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddKeyboardButtonFlags)) {
+ bool has_id;
+ bool has_user_id;
+ bool has_forward_text;
+ bool has_data;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_id);
+ PARSE_FLAG(has_user_id);
+ PARSE_FLAG(has_forward_text);
+ PARSE_FLAG(has_data);
+ END_PARSE_FLAGS();
+ parse(button.type, parser);
+ if (has_id) {
+ parse(button.id, parser);
+ }
+ if (has_user_id) {
+ parse(button.user_id, parser);
+ }
+ parse(button.text, parser);
+ if (has_forward_text) {
+ parse(button.forward_text, parser);
+ }
+ if (has_data) {
+ parse(button.data, parser);
+ }
+ } else {
+ parse(button.type, parser);
+ if (button.type == InlineKeyboardButton::Type::UrlAuth) {
+ if (parser.version() >= static_cast<int32>(Version::Support64BitIds)) {
+ parse(button.id, parser);
+ } else {
+ int32 old_id;
+ parse(old_id, parser);
+ button.id = old_id;
+ }
+ }
+ parse(button.text, parser);
+ parse(button.data, parser);
+ }
}
template <class StorerT>
void store(const ReplyMarkup &reply_markup, StorerT &storer) {
bool has_keyboard = !reply_markup.keyboard.empty();
bool has_inline_keyboard = !reply_markup.inline_keyboard.empty();
+ bool has_placeholder = !reply_markup.placeholder.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(reply_markup.is_personal);
STORE_FLAG(reply_markup.need_resize_keyboard);
STORE_FLAG(reply_markup.is_one_time_keyboard);
STORE_FLAG(has_keyboard);
STORE_FLAG(has_inline_keyboard);
+ STORE_FLAG(has_placeholder);
END_STORE_FLAGS();
store(reply_markup.type, storer);
if (has_keyboard) {
@@ -56,18 +134,23 @@ void store(const ReplyMarkup &reply_markup, StorerT &storer) {
if (has_inline_keyboard) {
store(reply_markup.inline_keyboard, storer);
}
+ if (has_placeholder) {
+ store(reply_markup.placeholder, storer);
+ }
}
template <class ParserT>
void parse(ReplyMarkup &reply_markup, ParserT &parser) {
- bool has_keyboard = !reply_markup.keyboard.empty();
- bool has_inline_keyboard = !reply_markup.inline_keyboard.empty();
+ bool has_keyboard;
+ bool has_inline_keyboard;
+ bool has_placeholder;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(reply_markup.is_personal);
PARSE_FLAG(reply_markup.need_resize_keyboard);
PARSE_FLAG(reply_markup.is_one_time_keyboard);
PARSE_FLAG(has_keyboard);
PARSE_FLAG(has_inline_keyboard);
+ PARSE_FLAG(has_placeholder);
END_PARSE_FLAGS();
parse(reply_markup.type, parser);
if (has_keyboard) {
@@ -76,6 +159,9 @@ void parse(ReplyMarkup &reply_markup, ParserT &parser) {
if (has_inline_keyboard) {
parse(reply_markup.inline_keyboard, parser);
}
+ if (has_placeholder) {
+ parse(reply_markup.placeholder, parser);
+ }
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ReportReason.cpp b/protocols/Telegram/tdlib/td/td/telegram/ReportReason.cpp
new file mode 100644
index 0000000000..e33920d59b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ReportReason.cpp
@@ -0,0 +1,109 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ReportReason.h"
+
+#include "td/telegram/misc.h"
+
+namespace td {
+
+Result<ReportReason> ReportReason::get_report_reason(td_api::object_ptr<td_api::ChatReportReason> reason,
+ string &&message) {
+ if (reason == nullptr) {
+ return Status::Error(400, "Chat report reason must be non-empty");
+ }
+ if (!clean_input_string(message)) {
+ return Status::Error(400, "Report text must be encoded in UTF-8");
+ }
+
+ auto type = [&] {
+ switch (reason->get_id()) {
+ case td_api::chatReportReasonSpam::ID:
+ return ReportReason::Type::Spam;
+ case td_api::chatReportReasonViolence::ID:
+ return ReportReason::Type::Violence;
+ case td_api::chatReportReasonPornography::ID:
+ return ReportReason::Type::Pornography;
+ case td_api::chatReportReasonChildAbuse::ID:
+ return ReportReason::Type::ChildAbuse;
+ case td_api::chatReportReasonCopyright::ID:
+ return ReportReason::Type::Copyright;
+ case td_api::chatReportReasonUnrelatedLocation::ID:
+ return ReportReason::Type::UnrelatedLocation;
+ case td_api::chatReportReasonFake::ID:
+ return ReportReason::Type::Fake;
+ case td_api::chatReportReasonIllegalDrugs::ID:
+ return ReportReason::Type::IllegalDrugs;
+ case td_api::chatReportReasonPersonalDetails::ID:
+ return ReportReason::Type::PersonalDetails;
+ case td_api::chatReportReasonCustom::ID:
+ return ReportReason::Type::Custom;
+ default:
+ UNREACHABLE();
+ return ReportReason::Type::Custom;
+ }
+ }();
+ return ReportReason(type, std::move(message));
+}
+
+tl_object_ptr<telegram_api::ReportReason> ReportReason::get_input_report_reason() const {
+ switch (type_) {
+ case ReportReason::Type::Spam:
+ return make_tl_object<telegram_api::inputReportReasonSpam>();
+ case ReportReason::Type::Violence:
+ return make_tl_object<telegram_api::inputReportReasonViolence>();
+ case ReportReason::Type::Pornography:
+ return make_tl_object<telegram_api::inputReportReasonPornography>();
+ case ReportReason::Type::ChildAbuse:
+ return make_tl_object<telegram_api::inputReportReasonChildAbuse>();
+ case ReportReason::Type::Copyright:
+ return make_tl_object<telegram_api::inputReportReasonCopyright>();
+ case ReportReason::Type::UnrelatedLocation:
+ return make_tl_object<telegram_api::inputReportReasonGeoIrrelevant>();
+ case ReportReason::Type::Fake:
+ return make_tl_object<telegram_api::inputReportReasonFake>();
+ case ReportReason::Type::IllegalDrugs:
+ return make_tl_object<telegram_api::inputReportReasonIllegalDrugs>();
+ case ReportReason::Type::PersonalDetails:
+ return make_tl_object<telegram_api::inputReportReasonPersonalDetails>();
+ case ReportReason::Type::Custom:
+ return make_tl_object<telegram_api::inputReportReasonOther>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ReportReason &report_reason) {
+ string_builder << "ReportReason";
+ switch (report_reason.type_) {
+ case ReportReason::Type::Spam:
+ return string_builder << "Spam";
+ case ReportReason::Type::Violence:
+ return string_builder << "Violence";
+ case ReportReason::Type::Pornography:
+ return string_builder << "Pornography";
+ case ReportReason::Type::ChildAbuse:
+ return string_builder << "ChildAbuse";
+ case ReportReason::Type::Copyright:
+ return string_builder << "Copyright";
+ case ReportReason::Type::UnrelatedLocation:
+ return string_builder << "UnrelatedLocation";
+ case ReportReason::Type::Fake:
+ return string_builder << "Fake";
+ case ReportReason::Type::IllegalDrugs:
+ return string_builder << "IllegalDrugs";
+ case ReportReason::Type::PersonalDetails:
+ return string_builder << "PersonalDetails";
+ case ReportReason::Type::Custom:
+ return string_builder << "Custom";
+ default:
+ UNREACHABLE();
+ }
+ return string_builder;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ReportReason.h b/protocols/Telegram/tdlib/td/td/telegram/ReportReason.h
new file mode 100644
index 0000000000..4a9f2319af
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ReportReason.h
@@ -0,0 +1,61 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class ReportReason {
+ enum class Type : int32 {
+ Spam,
+ Violence,
+ Pornography,
+ ChildAbuse,
+ Copyright,
+ UnrelatedLocation,
+ Fake,
+ IllegalDrugs,
+ PersonalDetails,
+ Custom
+ };
+ Type type_ = Type::Spam;
+ string message_;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const ReportReason &report_reason);
+
+ ReportReason(Type type, string &&message) : type_(type), message_(std::move(message)) {
+ }
+
+ public:
+ ReportReason() = default;
+
+ static Result<ReportReason> get_report_reason(td_api::object_ptr<td_api::ChatReportReason> reason, string &&message);
+
+ tl_object_ptr<telegram_api::ReportReason> get_input_report_reason() const;
+
+ const string &get_message() const {
+ return message_;
+ }
+
+ bool is_spam() const {
+ return type_ == Type::Spam;
+ }
+
+ bool is_unrelated_location() const {
+ return type_ == Type::UnrelatedLocation;
+ }
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ReportReason &report_reason);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/RequestActor.h b/protocols/Telegram/tdlib/td/td/telegram/RequestActor.h
new file mode 100644
index 0000000000..3463e2994f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/RequestActor.h
@@ -0,0 +1,160 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/Global.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+#include <type_traits>
+
+namespace td {
+
+template <class T = Unit>
+class RequestActor : public Actor {
+ public:
+ RequestActor(ActorShared<Td> td_id, uint64 request_id)
+ : td_id_(std::move(td_id)), td_(td_id_.get().get_actor_unsafe()), request_id_(request_id) {
+ }
+
+ void loop() override {
+ if (G()->close_flag()) {
+ return do_send_error(Global::request_aborted_error());
+ }
+
+ PromiseActor<T> promise_actor;
+ FutureActor<T> future;
+ init_promise_future(&promise_actor, &future);
+
+ auto promise = create_promise_from_promise_actor(std::move(promise_actor));
+ do_run(std::move(promise));
+
+ if (future.is_ready()) {
+ CHECK(!promise);
+ if (future.is_error()) {
+ do_send_error(future.move_as_error());
+ } else {
+ do_set_result(future.move_as_ok());
+ do_send_result();
+ }
+ stop();
+ } else {
+ CHECK(!future.empty());
+ CHECK(future.get_state() == FutureActor<T>::State::Waiting);
+ if (--tries_left_ == 0) {
+ future.close();
+ do_send_error(Status::Error(500, "Requested data is inaccessible"));
+ return stop();
+ }
+
+ future.set_event(EventCreator::raw(actor_id(), nullptr));
+ future_ = std::move(future);
+ }
+ }
+
+ void raw_event(const Event::Raw &event) final {
+ if (future_.is_error()) {
+ auto error = future_.move_as_error();
+ if (error == Status::Error<FutureActor<T>::HANGUP_ERROR_CODE>()) {
+ // dropping query due to closing or lost promise
+ if (G()->close_flag()) {
+ do_send_error(Global::request_aborted_error());
+ } else {
+ LOG(ERROR) << "Promise was lost";
+ do_send_error(Status::Error(500, "Query can't be answered due to a bug in TDLib"));
+ }
+ return stop();
+ }
+
+ do_send_error(std::move(error));
+ stop();
+ } else {
+ do_set_result(future_.move_as_ok());
+ loop();
+ }
+ }
+
+ void on_start_migrate(int32 /*sched_id*/) final {
+ UNREACHABLE();
+ }
+ void on_finish_migrate() final {
+ UNREACHABLE();
+ }
+
+ int get_tries() const {
+ return tries_left_;
+ }
+
+ void set_tries(int32 tries) {
+ tries_left_ = tries;
+ }
+
+ protected:
+ ActorShared<Td> td_id_;
+ Td *td_;
+
+ void send_result(tl_object_ptr<td_api::Object> &&result) {
+ send_closure(td_id_, &Td::send_result, request_id_, std::move(result));
+ }
+
+ void send_error(Status &&status) {
+ LOG(INFO) << "Receive error for query: " << status;
+ send_closure(td_id_, &Td::send_error, request_id_, std::move(status));
+ }
+
+ private:
+ virtual void do_run(Promise<T> &&promise) = 0;
+
+ virtual void do_send_result() {
+ send_result(make_tl_object<td_api::ok>());
+ }
+
+ virtual void do_send_error(Status &&status) {
+ send_error(std::move(status));
+ }
+
+ virtual void do_set_result(T &&result) {
+ CHECK((std::is_same<T, Unit>::value)); // all other results should be implicitly handled by overriding this method
+ }
+
+ void hangup() final {
+ do_send_error(Global::request_aborted_error());
+ stop();
+ }
+
+ friend class RequestOnceActor;
+
+ uint64 request_id_;
+ int tries_left_ = 2;
+ FutureActor<T> future_;
+};
+
+class RequestOnceActor : public RequestActor<> {
+ public:
+ RequestOnceActor(ActorShared<Td> td_id, uint64 request_id) : RequestActor(std::move(td_id), request_id) {
+ }
+
+ void loop() final {
+ if (get_tries() < 2) {
+ do_send_result();
+ stop();
+ return;
+ }
+
+ RequestActor::loop();
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/RestrictionReason.cpp b/protocols/Telegram/tdlib/td/td/telegram/RestrictionReason.cpp
new file mode 100644
index 0000000000..8b9ef8dd6f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/RestrictionReason.cpp
@@ -0,0 +1,101 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/RestrictionReason.h"
+
+#include "td/telegram/Global.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+
+#include <tuple>
+
+namespace td {
+
+string get_restriction_reason_description(const vector<RestrictionReason> &restriction_reasons) {
+ if (restriction_reasons.empty()) {
+ return string();
+ }
+
+ auto ignored_restriction_reasons = full_split(G()->get_option_string("ignored_restriction_reasons"), ',');
+ auto restriction_add_platforms = full_split(G()->get_option_string("restriction_add_platforms"), ',');
+ auto platform = [] {
+#if TD_ANDROID
+ return Slice("android");
+#elif TD_WINDOWS
+ return Slice("ms");
+#elif TD_DARWIN
+ return Slice("ios");
+#else
+ return Slice();
+#endif
+ }();
+
+ if (G()->get_option_boolean("ignore_platform_restrictions")) {
+ platform = Slice();
+ restriction_add_platforms.clear();
+ }
+
+ if (!platform.empty()) {
+ // first find restriction for the current platform
+ for (auto &restriction_reason : restriction_reasons) {
+ if (restriction_reason.platform_ == platform &&
+ !td::contains(ignored_restriction_reasons, restriction_reason.reason_)) {
+ return restriction_reason.description_;
+ }
+ }
+ }
+
+ if (!restriction_add_platforms.empty()) {
+ // then find restriction for added platforms
+ for (auto &restriction_reason : restriction_reasons) {
+ if (td::contains(restriction_add_platforms, restriction_reason.platform_) &&
+ !td::contains(ignored_restriction_reasons, restriction_reason.reason_)) {
+ return restriction_reason.description_;
+ }
+ }
+ }
+
+ // then find restriction for all platforms
+ for (auto &restriction_reason : restriction_reasons) {
+ if (restriction_reason.platform_ == "all" &&
+ !td::contains(ignored_restriction_reasons, restriction_reason.reason_)) {
+ return restriction_reason.description_;
+ }
+ }
+
+ return string();
+}
+
+vector<RestrictionReason> get_restriction_reasons(Slice legacy_restriction_reason) {
+ Slice type;
+ Slice description;
+ std::tie(type, description) = split(legacy_restriction_reason, ':');
+ auto parts = full_split(type, '-');
+ description = trim(description);
+
+ vector<RestrictionReason> result;
+ if (parts.size() <= 1) {
+ return result;
+ }
+ for (size_t i = 1; i < parts.size(); i++) {
+ result.emplace_back(parts[i].str(), parts[0].str(), description.str());
+ }
+ return result;
+}
+
+vector<RestrictionReason> get_restriction_reasons(
+ vector<telegram_api::object_ptr<telegram_api::restrictionReason>> &&restriction_reasons) {
+ return transform(std::move(restriction_reasons),
+ [](telegram_api::object_ptr<telegram_api::restrictionReason> &&restriction_reason) {
+ return RestrictionReason(std::move(restriction_reason->platform_),
+ std::move(restriction_reason->reason_),
+ std::move(restriction_reason->text_));
+ });
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/RestrictionReason.h b/protocols/Telegram/tdlib/td/td/telegram/RestrictionReason.h
new file mode 100644
index 0000000000..d71803df88
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/RestrictionReason.h
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class RestrictionReason {
+ string platform_;
+ string reason_;
+ string description_;
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const RestrictionReason &reason) {
+ return string_builder << "RestrictionReason[" << reason.platform_ << ", " << reason.reason_ << ", "
+ << reason.description_ << "]";
+ }
+
+ friend bool operator==(const RestrictionReason &lhs, const RestrictionReason &rhs) {
+ return lhs.platform_ == rhs.platform_ && lhs.reason_ == rhs.reason_ && lhs.description_ == rhs.description_;
+ }
+
+ friend string get_restriction_reason_description(const vector<RestrictionReason> &restriction_reasons);
+
+ public:
+ RestrictionReason() = default;
+
+ RestrictionReason(string &&platform, string &&reason, string &&description)
+ : platform_(std::move(platform)), reason_(std::move(reason)), description_(std::move(description)) {
+ if (description_.empty()) {
+ description_ = reason_;
+ }
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(platform_, storer);
+ td::store(reason_, storer);
+ td::store(description_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(platform_, parser);
+ td::parse(reason_, parser);
+ td::parse(description_, parser);
+ }
+};
+
+inline bool operator!=(const RestrictionReason &lhs, const RestrictionReason &rhs) {
+ return !(lhs == rhs);
+}
+
+string get_restriction_reason_description(const vector<RestrictionReason> &restriction_reasons);
+
+vector<RestrictionReason> get_restriction_reasons(Slice legacy_restriction_reason);
+
+vector<RestrictionReason> get_restriction_reasons(
+ vector<telegram_api::object_ptr<telegram_api::restrictionReason>> &&restriction_reasons);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ScheduledServerMessageId.h b/protocols/Telegram/tdlib/td/td/telegram/ScheduledServerMessageId.h
new file mode 100644
index 0000000000..8d5f591efc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ScheduledServerMessageId.h
@@ -0,0 +1,65 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class ScheduledServerMessageId {
+ int32 id = 0;
+
+ public:
+ ScheduledServerMessageId() = default;
+
+ explicit ScheduledServerMessageId(int32 message_id) : id(message_id) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ ScheduledServerMessageId(T message_id) = delete;
+
+ bool is_valid() const {
+ return id > 0 && id < (1 << 18);
+ }
+
+ int32 get() const {
+ return id;
+ }
+
+ bool operator==(const ScheduledServerMessageId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const ScheduledServerMessageId &other) const {
+ return id != other.id;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ storer.store_int(id);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ id = parser.fetch_int();
+ }
+};
+
+struct ScheduledServerMessageIdHash {
+ uint32 operator()(ScheduledServerMessageId message_id) const {
+ return Hash<int32>()(message_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, ScheduledServerMessageId message_id) {
+ return string_builder << "scheduled server message " << message_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.cpp b/protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.cpp
new file mode 100644
index 0000000000..eb2ba9636d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.cpp
@@ -0,0 +1,74 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ScopeNotificationSettings.h"
+
+#include "td/telegram/Global.h"
+
+#include <limits>
+
+namespace td {
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ScopeNotificationSettings &notification_settings) {
+ return string_builder << "[" << notification_settings.mute_until << ", " << notification_settings.sound << ", "
+ << notification_settings.show_preview << ", " << notification_settings.is_synchronized << ", "
+ << notification_settings.disable_pinned_message_notifications << ", "
+ << notification_settings.disable_mention_notifications << "]";
+}
+
+td_api::object_ptr<td_api::scopeNotificationSettings> get_scope_notification_settings_object(
+ const ScopeNotificationSettings *notification_settings) {
+ CHECK(notification_settings != nullptr);
+ return td_api::make_object<td_api::scopeNotificationSettings>(
+ max(0, notification_settings->mute_until - G()->unix_time()),
+ get_notification_sound_ringtone_id(notification_settings->sound), notification_settings->show_preview,
+ notification_settings->disable_pinned_message_notifications,
+ notification_settings->disable_mention_notifications);
+}
+
+static int32 get_mute_until(int32 mute_for) {
+ if (mute_for <= 0) {
+ return 0;
+ }
+
+ const int32 MAX_PRECISE_MUTE_FOR = 366 * 86400;
+ int32 current_time = G()->unix_time();
+ if (mute_for > MAX_PRECISE_MUTE_FOR || mute_for >= std::numeric_limits<int32>::max() - current_time) {
+ return std::numeric_limits<int32>::max();
+ }
+ return mute_for + current_time;
+}
+
+Result<ScopeNotificationSettings> get_scope_notification_settings(
+ td_api::object_ptr<td_api::scopeNotificationSettings> &&notification_settings) {
+ if (notification_settings == nullptr) {
+ return Status::Error(400, "New notification settings must be non-empty");
+ }
+
+ auto mute_until = get_mute_until(notification_settings->mute_for_);
+ return ScopeNotificationSettings(mute_until, get_notification_sound(false, notification_settings->sound_id_),
+ notification_settings->show_preview_,
+ notification_settings->disable_pinned_message_notifications_,
+ notification_settings->disable_mention_notifications_);
+}
+
+ScopeNotificationSettings get_scope_notification_settings(tl_object_ptr<telegram_api::peerNotifySettings> &&settings,
+ bool old_disable_pinned_message_notifications,
+ bool old_disable_mention_notifications) {
+ if (settings == nullptr) {
+ return ScopeNotificationSettings();
+ }
+ auto mute_until = (settings->flags_ & telegram_api::peerNotifySettings::MUTE_UNTIL_MASK) == 0 ||
+ settings->mute_until_ <= G()->unix_time()
+ ? 0
+ : settings->mute_until_;
+ auto show_preview =
+ (settings->flags_ & telegram_api::peerNotifySettings::SHOW_PREVIEWS_MASK) == 0 ? false : settings->show_previews_;
+ return {mute_until, get_notification_sound(settings.get()), show_preview, old_disable_pinned_message_notifications,
+ old_disable_mention_notifications};
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.h b/protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.h
new file mode 100644
index 0000000000..d1942ec845
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.h
@@ -0,0 +1,55 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/NotificationSound.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class ScopeNotificationSettings {
+ public:
+ int32 mute_until = 0;
+ unique_ptr<NotificationSound> sound;
+ bool show_preview = true;
+ bool is_synchronized = false;
+
+ // local settings
+ bool disable_pinned_message_notifications = false;
+ bool disable_mention_notifications = false;
+
+ ScopeNotificationSettings() = default;
+
+ ScopeNotificationSettings(int32 mute_until, unique_ptr<NotificationSound> &&sound, bool show_preview,
+ bool disable_pinned_message_notifications, bool disable_mention_notifications)
+ : mute_until(mute_until)
+ , sound(std::move(sound))
+ , show_preview(show_preview)
+ , is_synchronized(true)
+ , disable_pinned_message_notifications(disable_pinned_message_notifications)
+ , disable_mention_notifications(disable_mention_notifications) {
+ }
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const ScopeNotificationSettings &notification_settings);
+
+td_api::object_ptr<td_api::scopeNotificationSettings> get_scope_notification_settings_object(
+ const ScopeNotificationSettings *notification_settings);
+
+Result<ScopeNotificationSettings> get_scope_notification_settings(
+ td_api::object_ptr<td_api::scopeNotificationSettings> &&notification_settings);
+
+ScopeNotificationSettings get_scope_notification_settings(tl_object_ptr<telegram_api::peerNotifySettings> &&settings,
+ bool old_disable_pinned_message_notifications,
+ bool old_disable_mention_notifications);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.hpp b/protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.hpp
new file mode 100644
index 0000000000..3aee6e418c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ScopeNotificationSettings.hpp
@@ -0,0 +1,71 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/Global.h"
+#include "td/telegram/NotificationSound.h"
+#include "td/telegram/ScopeNotificationSettings.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const ScopeNotificationSettings &notification_settings, StorerT &storer) {
+ bool is_muted = notification_settings.mute_until != 0 && notification_settings.mute_until > G()->unix_time();
+ bool has_sound = notification_settings.sound != nullptr;
+ bool has_ringtone_support = true;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_muted);
+ STORE_FLAG(has_sound);
+ STORE_FLAG(notification_settings.show_preview);
+ STORE_FLAG(false);
+ STORE_FLAG(notification_settings.is_synchronized);
+ STORE_FLAG(notification_settings.disable_pinned_message_notifications);
+ STORE_FLAG(notification_settings.disable_mention_notifications);
+ STORE_FLAG(has_ringtone_support);
+ END_STORE_FLAGS();
+ if (is_muted) {
+ store(notification_settings.mute_until, storer);
+ }
+ if (has_sound) {
+ store(notification_settings.sound, storer);
+ }
+}
+
+template <class ParserT>
+void parse(ScopeNotificationSettings &notification_settings, ParserT &parser) {
+ bool is_muted;
+ bool has_sound;
+ bool silent_send_message_ignored;
+ bool has_ringtone_support;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_muted);
+ PARSE_FLAG(has_sound);
+ PARSE_FLAG(notification_settings.show_preview);
+ PARSE_FLAG(silent_send_message_ignored);
+ PARSE_FLAG(notification_settings.is_synchronized);
+ PARSE_FLAG(notification_settings.disable_pinned_message_notifications);
+ PARSE_FLAG(notification_settings.disable_mention_notifications);
+ PARSE_FLAG(has_ringtone_support);
+ END_PARSE_FLAGS();
+ (void)silent_send_message_ignored;
+ if (is_muted) {
+ parse(notification_settings.mute_until, parser);
+ }
+ if (has_sound) {
+ if (has_ringtone_support) {
+ parse_notification_sound(notification_settings.sound, parser);
+ } else {
+ string sound;
+ parse(sound, parser);
+ notification_settings.sound = get_legacy_notification_sound(sound);
+ }
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.cpp b/protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.cpp
index 11688cb1d3..21d145453d 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.cpp
@@ -1,31 +1,37 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/SecretChatActor.h"
-#include "td/mtproto/PacketStorer.h"
-
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQueryCreator.h"
-#include "td/telegram/SecretChatId.h"
-#include "td/telegram/UniqueId.h"
-
#include "td/telegram/secret_api.hpp"
+#include "td/telegram/ServerMessageId.h"
#include "td/telegram/telegram_api.hpp"
+#include "td/telegram/UniqueId.h"
+
+#include "td/mtproto/PacketInfo.h"
+#include "td/mtproto/PacketStorer.h"
+#include "td/mtproto/Transport.h"
+#include "td/mtproto/utils.h"
#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/binlog/BinlogInterface.h"
#include "td/actor/MultiPromise.h"
+#include "td/utils/as.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
-#include "td/utils/overloaded.h"
#include "td/utils/Random.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StorerBase.h"
#include "td/utils/Time.h"
#include "td/utils/tl_parsers.h"
@@ -33,18 +39,21 @@
#include <tuple>
#include <type_traits>
-#define G GLOBAL_SHOULD_NOT_BE_USED_HERE
+//#define G GLOBAL_SHOULD_NOT_BE_USED_HERE
+#undef G
namespace td {
+
inline TLObjectStorer<secret_api::Object> create_storer(const secret_api::Object &object) {
return TLObjectStorer<secret_api::Object>(object);
}
+
class SecretImpl {
public:
explicit SecretImpl(const Storer &data) : data(data) {
}
- template <class T>
- void do_store(T &storer) const {
+ template <class StorerT>
+ void do_store(StorerT &storer) const {
storer.store_binary(static_cast<int32>(data.size()));
storer.store_storer(data);
}
@@ -52,11 +61,19 @@ class SecretImpl {
private:
const Storer &data;
};
-SecretChatActor::SecretChatActor(int32 id, std::unique_ptr<Context> context, bool can_be_empty)
+
+SecretChatActor::SecretChatActor(int32 id, unique_ptr<Context> context, bool can_be_empty)
: context_(std::move(context)), can_be_empty_(can_be_empty) {
auth_state_.id = id;
}
+template <class T>
+NetQueryPtr SecretChatActor::create_net_query(QueryType type, const T &function) {
+ return context_->net_query_creator().create(UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(type)),
+ function, {}, DcId::main(), NetQuery::Type::Common,
+ NetQuery::AuthFlag::On);
+}
+
void SecretChatActor::update_chat(telegram_api::object_ptr<telegram_api::EncryptedChat> chat) {
if (close_flag_) {
return;
@@ -65,7 +82,7 @@ void SecretChatActor::update_chat(telegram_api::object_ptr<telegram_api::Encrypt
loop();
}
-void SecretChatActor::create_chat(int32 user_id, int64 user_access_hash, int32 random_id,
+void SecretChatActor::create_chat(UserId user_id, int64 user_access_hash, int32 random_id,
Promise<SecretChatId> promise) {
if (close_flag_) {
promise.set_error(Status::Error(400, "Chat is closed"));
@@ -78,22 +95,26 @@ void SecretChatActor::create_chat(int32 user_id, int64 user_access_hash, int32 r
return;
}
- auto event = std::make_unique<logevent::CreateSecretChat>();
+ auto event = make_unique<log_event::CreateSecretChat>();
event->user_id = user_id;
event->user_access_hash = user_access_hash;
event->random_id = random_id;
- event->set_logevent_id(
- BinlogHelper::add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*event)));
+ event->set_log_event_id(binlog_add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*event)));
do_create_chat_impl(std::move(event));
promise.set_value(SecretChatId(random_id));
loop();
}
+
void SecretChatActor::on_result_resendable(NetQueryPtr net_query, Promise<NetQueryPtr> promise) {
- LOG(INFO) << "on_result_resendable: " << net_query;
+ LOG(INFO) << "In on_result_resendable: " << net_query << " " << close_flag_;
+ if (context_->close_flag()) {
+ return;
+ }
+
auto key = UniqueId::extract_key(net_query->id());
if (close_flag_) {
if (key == static_cast<uint8>(QueryType::DiscardEncryption)) {
- on_discard_encryption_result(std::move(net_query));
+ discard_encryption_promise_.set_value(Unit());
}
return;
}
@@ -104,31 +125,36 @@ void SecretChatActor::on_result_resendable(NetQueryPtr net_query, Promise<NetQue
case static_cast<uint8>(QueryType::EncryptedChat):
return on_update_chat(std::move(net_query));
case static_cast<uint8>(QueryType::Message):
- return on_outbound_send_message_result(std::move(net_query), std::move(promise)), Status::OK();
+ on_outbound_send_message_result(std::move(net_query), std::move(promise));
+ return Status::OK();
+ case static_cast<uint8>(QueryType::ReadHistory):
+ return on_read_history(std::move(net_query));
case static_cast<uint8>(QueryType::Ignore):
return Status::OK();
+ default:
+ UNREACHABLE();
+ return Status::OK();
}
- UNREACHABLE();
}());
loop();
}
-void SecretChatActor::replay_close_chat(std::unique_ptr<logevent::CloseSecretChat> event) {
- if (close_flag_) {
- return;
- }
- do_close_chat_impl(std::move(event));
+
+void SecretChatActor::replay_close_chat(unique_ptr<log_event::CloseSecretChat> event) {
+ do_close_chat_impl(event->delete_history, event->is_already_discarded, event->log_event_id(), Promise<Unit>());
}
-void SecretChatActor::replay_create_chat(std::unique_ptr<logevent::CreateSecretChat> event) {
+
+void SecretChatActor::replay_create_chat(unique_ptr<log_event::CreateSecretChat> event) {
if (close_flag_) {
return;
}
do_create_chat_impl(std::move(event));
}
-void SecretChatActor::add_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message) {
+
+void SecretChatActor::add_inbound_message(unique_ptr<log_event::InboundSecretMessage> message) {
SCOPE_EXIT {
if (message) {
- message->qts_ack.set_value(Unit());
+ message->promise.set_value(Unit());
}
};
if (close_flag_) {
@@ -142,7 +168,7 @@ void SecretChatActor::add_inbound_message(std::unique_ptr<logevent::InboundSecre
loop();
}
-void SecretChatActor::replay_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message) {
+void SecretChatActor::replay_inbound_message(unique_ptr<log_event::InboundSecretMessage> message) {
if (close_flag_) {
return;
}
@@ -154,10 +180,10 @@ void SecretChatActor::replay_inbound_message(std::unique_ptr<logevent::InboundSe
CHECK(!binlog_replay_finish_flag_);
CHECK(message->decrypted_message_layer); // from binlog
if (message->is_pending) { // wait for gaps?
- // check_status(do_inbound_message_decrypted_unchecked(std::move(message)));
+ // check_status(do_inbound_message_decrypted_unchecked(std::move(message)), -1);
do_inbound_message_decrypted_pending(std::move(message));
} else { // just replay
- CHECK(message->message_id > last_binlog_message_id_)
+ LOG_CHECK(message->message_id > last_binlog_message_id_)
<< tag("last_binlog_message_id", last_binlog_message_id_) << tag("message_id", message->message_id);
last_binlog_message_id_ = message->message_id;
check_status(do_inbound_message_decrypted(std::move(message)));
@@ -165,7 +191,7 @@ void SecretChatActor::replay_inbound_message(std::unique_ptr<logevent::InboundSe
loop();
}
-void SecretChatActor::replay_outbound_message(std::unique_ptr<logevent::OutboundSecretMessage> message) {
+void SecretChatActor::replay_outbound_message(unique_ptr<log_event::OutboundSecretMessage> message) {
if (close_flag_) {
return;
}
@@ -174,7 +200,7 @@ void SecretChatActor::replay_outbound_message(std::unique_ptr<logevent::Outbound
return;
}
CHECK(!binlog_replay_finish_flag_);
- CHECK(message->message_id > last_binlog_message_id_)
+ LOG_CHECK(message->message_id > last_binlog_message_id_)
<< tag("last_binlog_message_id", last_binlog_message_id_) << tag("message_id", message->message_id);
last_binlog_message_id_ = message->message_id;
do_outbound_message_impl(std::move(message), Promise<>());
@@ -182,31 +208,23 @@ void SecretChatActor::replay_outbound_message(std::unique_ptr<logevent::Outbound
}
// NB: my_seq_no is just after message is sent, i.e. my_out_seq_no is already incremented
-Result<BufferSlice> SecretChatActor::create_encrypted_message(int32 layer, int32 my_in_seq_no, int32 my_out_seq_no,
+Result<BufferSlice> SecretChatActor::create_encrypted_message(int32 my_in_seq_no, int32 my_out_seq_no,
tl_object_ptr<secret_api::DecryptedMessage> &message) {
- if (message->get_id() == secret_api::decryptedMessage::ID && layer < MTPROTO_2_LAYER) {
- auto old = secret_api::move_object_as<secret_api::decryptedMessage>(message);
- old->flags_ &= ~secret_api::decryptedMessage::GROUPED_ID_MASK;
- message = secret_api::make_object<secret_api::decryptedMessage46>(
- old->flags_, old->random_id_, old->ttl_, std::move(old->message_), std::move(old->media_),
- std::move(old->entities_), std::move(old->via_bot_name_), old->reply_to_random_id_);
- }
-
mtproto::AuthKey *auth_key = &pfs_state_.auth_key;
auto in_seq_no = my_in_seq_no * 2 + auth_state_.x;
auto out_seq_no = my_out_seq_no * 2 - 1 - auth_state_.x;
- BufferSlice random_bytes(32);
+ auto layer = current_layer();
+ BufferSlice random_bytes(31);
Random::secure_bytes(random_bytes.as_slice().ubegin(), random_bytes.size());
auto message_with_layer = secret_api::make_object<secret_api::decryptedMessageLayer>(
std::move(random_bytes), layer, in_seq_no, out_seq_no, std::move(message));
- LOG(INFO) << to_string(message_with_layer);
+ LOG(INFO) << "Create message " << to_string(message_with_layer);
auto storer = create_storer(*message_with_layer);
auto new_storer = mtproto::PacketStorer<SecretImpl>(storer);
mtproto::PacketInfo info;
info.type = mtproto::PacketInfo::EndToEnd;
- // Send with mtproto 2.0 if current layer is at least MTPROTO_2_LAYER
- info.version = layer >= MTPROTO_2_LAYER ? 2 : 1;
+ info.version = 2;
info.is_creator = auth_state_.x == 0;
auto packet_writer = BufferWriter{mtproto::Transport::write(new_storer, *auth_key, &info), 0, 0};
mtproto::Transport::write(new_storer, *auth_key, &info, packet_writer.as_slice());
@@ -223,63 +241,6 @@ void SecretChatActor::send_message(tl_object_ptr<secret_api::DecryptedMessage> m
send_message_impl(std::move(message), std::move(file), SendFlag::External | SendFlag::Push, std::move(promise));
}
-static int32 get_min_layer(const secret_api::decryptedMessageActionTyping &message) {
- switch (message.action_->get_id()) {
- case secret_api::sendMessageRecordRoundAction::ID:
- case secret_api::sendMessageUploadRoundAction::ID:
- return SecretChatActor::VOICE_NOTES_LAYER;
- }
- return 0;
-}
-static int32 get_min_layer(const secret_api::decryptedMessageService &message) {
- switch (message.action_->get_id()) {
- case secret_api::decryptedMessageActionTyping::ID:
- return get_min_layer(static_cast<const secret_api::decryptedMessageActionTyping &>(*message.action_));
- default:
- return 0;
- }
-}
-static int32 get_min_layer(const secret_api::DocumentAttribute &attribute) {
- switch (attribute.get_id()) {
- case secret_api::documentAttributeVideo66::ID:
- return SecretChatActor::VOICE_NOTES_LAYER;
- default:
- return 0;
- }
-}
-static int32 get_min_layer(const secret_api::decryptedMessageMediaDocument &message) {
- int32 res = 0;
- for (auto &attribute : message.attributes_) {
- auto attrirbute_layer = get_min_layer(*attribute);
- if (attrirbute_layer > res) {
- res = attrirbute_layer;
- }
- return res;
- }
- return res;
-}
-static int32 get_min_layer(const secret_api::decryptedMessage &message) {
- if (!message.media_) {
- return 0;
- }
- switch (message.media_->get_id()) {
- case secret_api::decryptedMessageMediaDocument::ID:
- return get_min_layer(static_cast<const secret_api::decryptedMessageMediaDocument &>(*message.media_));
- default:
- return 0;
- }
-}
-static int32 get_min_layer(const secret_api::DecryptedMessage &message) {
- switch (message.get_id()) {
- case secret_api::decryptedMessageService::ID:
- return get_min_layer(static_cast<const secret_api::decryptedMessageService &>(message));
- case secret_api::decryptedMessage::ID:
- return get_min_layer(static_cast<const secret_api::decryptedMessage &>(message));
- default:
- return 0;
- }
-}
-
void SecretChatActor::send_message_impl(tl_object_ptr<secret_api::DecryptedMessage> message,
tl_object_ptr<telegram_api::InputEncryptedFile> file, int32 flags,
Promise<> promise) {
@@ -291,34 +252,31 @@ void SecretChatActor::send_message_impl(tl_object_ptr<secret_api::DecryptedMessa
LOG(ERROR) << "Ignore send_message: " << tag("message", to_string(message)) << tag("file", to_string(file));
return promise.set_error(Status::Error(400, "Chat is not accessible"));
}
- if (get_min_layer(*message) > config_state_.his_layer) {
- return promise.set_error(Status::Error(400, "Message is not supported by the other side"));
- }
- CHECK(binlog_replay_finish_flag_) << "Trying to send message before binlog replay is finished: "
- << to_string(*message) << to_string(file);
+ LOG_CHECK(binlog_replay_finish_flag_) << "Trying to send message before binlog replay is finished: "
+ << to_string(*message) << to_string(file);
int64 random_id = 0;
downcast_call(*message, [&](auto &x) { random_id = x.random_id_; });
- LOG(INFO) << "Send message: " << to_string(*message) << to_string(file);
-
auto it = random_id_to_outbound_message_state_token_.find(random_id);
- if (it != end(random_id_to_outbound_message_state_token_)) {
+ if (it != random_id_to_outbound_message_state_token_.end()) {
return on_outbound_outer_send_message_promise(it->second, std::move(promise));
}
- auto binlog_event = std::make_unique<logevent::OutboundSecretMessage>();
+ auto binlog_event = make_unique<log_event::OutboundSecretMessage>();
binlog_event->chat_id = auth_state_.id;
binlog_event->random_id = random_id;
- binlog_event->file = logevent::EncryptedInputFile::from_input_encrypted_file(file);
+ binlog_event->file = log_event::EncryptedInputFile::from_input_encrypted_file(file);
binlog_event->message_id = seq_no_state_.message_id + 1;
binlog_event->my_in_seq_no = seq_no_state_.my_in_seq_no;
binlog_event->my_out_seq_no = seq_no_state_.my_out_seq_no + 1;
binlog_event->his_in_seq_no = seq_no_state_.his_in_seq_no;
binlog_event->encrypted_message =
- create_encrypted_message(current_layer(), binlog_event->my_in_seq_no, binlog_event->my_out_seq_no, message)
- .move_as_ok();
- binlog_event->is_service = (flags & SendFlag::Push) == 0;
+ create_encrypted_message(binlog_event->my_in_seq_no, binlog_event->my_out_seq_no, message).move_as_ok();
+ binlog_event->need_notify_user = (flags & SendFlag::Push) == 0;
binlog_event->is_external = (flags & SendFlag::External) != 0;
+ binlog_event->is_silent = (message->get_id() == secret_api::decryptedMessage::ID &&
+ (static_cast<const secret_api::decryptedMessage *>(message.get())->flags_ &
+ secret_api::decryptedMessage::SILENT_MASK) != 0);
if (message->get_id() == secret_api::decryptedMessageService::ID) {
binlog_event->is_rewritable = false;
auto service_message = move_tl_object_as<secret_api::decryptedMessageService>(message);
@@ -340,9 +298,8 @@ void SecretChatActor::send_message_action(tl_object_ptr<secret_api::SendMessageA
}
bool flag = action->get_id() != secret_api::sendMessageCancelAction::ID;
- auto net_query = context_->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::Ignore)),
- create_storer(telegram_api::messages_setEncryptedTyping(get_input_chat(), flag)));
+ auto net_query =
+ create_net_query(QueryType::Ignore, telegram_api::messages_setEncryptedTyping(get_input_chat(), flag));
if (!set_typing_query_.empty()) {
LOG(INFO) << "Cancel previous set typing query";
cancel_query(set_typing_query_);
@@ -350,6 +307,7 @@ void SecretChatActor::send_message_action(tl_object_ptr<secret_api::SendMessageA
set_typing_query_ = net_query.get_weak();
context_->send_net_query(std::move(net_query), actor_shared(this), false);
}
+
void SecretChatActor::send_read_history(int32 date, Promise<> promise) {
if (close_flag_) {
promise.set_error(Status::Error(400, "Chat is closed"));
@@ -360,12 +318,26 @@ void SecretChatActor::send_read_history(int32 date, Promise<> promise) {
promise.set_error(Status::Error(400, "Can't access the chat"));
return;
}
- // TODO: use promise
- context_->send_net_query(context_->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::Ignore)),
- create_storer(telegram_api::messages_readEncryptedHistory(get_input_chat(), date))),
- actor_shared(this), false);
+
+ if (date <= last_read_history_date_) {
+ return promise.set_value(Unit());
+ }
+
+ if (read_history_promise_) {
+ LOG(INFO) << "Cancel previous read history request in secret chat " << auth_state_.id;
+ read_history_promise_.set_value(Unit());
+ cancel_query(read_history_query_);
+ }
+
+ auto net_query =
+ create_net_query(QueryType::ReadHistory, telegram_api::messages_readEncryptedHistory(get_input_chat(), date));
+ read_history_query_ = net_query.get_weak();
+ last_read_history_date_ = date;
+ read_history_promise_ = std::move(promise);
+ LOG(INFO) << "Send read history request with date " << date << " in secret chat " << auth_state_.id;
+ context_->send_net_query(std::move(net_query), actor_shared(this), false);
}
+
void SecretChatActor::send_open_message(int64 random_id, Promise<> promise) {
if (close_flag_) {
promise.set_error(Status::Error(400, "Chat is closed"));
@@ -381,6 +353,10 @@ void SecretChatActor::send_open_message(int64 random_id, Promise<> promise) {
}
void SecretChatActor::delete_message(int64 random_id, Promise<> promise) {
+ if (auth_state_.state == State::Closed) {
+ promise.set_value(Unit());
+ return;
+ }
if (close_flag_) {
promise.set_error(Status::Error(400, "Chat is closed"));
return;
@@ -393,6 +369,10 @@ void SecretChatActor::delete_message(int64 random_id, Promise<> promise) {
}
void SecretChatActor::delete_messages(std::vector<int64> random_ids, Promise<> promise) {
+ if (auth_state_.state == State::Closed) {
+ promise.set_value(Unit());
+ return;
+ }
if (close_flag_) {
promise.set_error(Status::Error(400, "Chat is closed"));
return;
@@ -405,6 +385,10 @@ void SecretChatActor::delete_messages(std::vector<int64> random_ids, Promise<> p
std::move(promise));
}
void SecretChatActor::delete_all_messages(Promise<> promise) {
+ if (auth_state_.state == State::Closed) {
+ promise.set_value(Unit());
+ return;
+ }
if (close_flag_) {
promise.set_error(Status::Error(400, "Chat is closed"));
return;
@@ -425,7 +409,7 @@ void SecretChatActor::notify_screenshot_taken(Promise<> promise) {
promise.set_error(Status::Error(400, "Can't access the chat"));
return;
}
- send_action(make_tl_object<secret_api::decryptedMessageActionScreenshotMessages>(), SendFlag::Push,
+ send_action(make_tl_object<secret_api::decryptedMessageActionScreenshotMessages>(vector<int64>()), SendFlag::Push,
std::move(promise));
}
@@ -452,13 +436,13 @@ void SecretChatActor::send_action(tl_object_ptr<secret_api::DecryptedMessageActi
void SecretChatActor::binlog_replay_finish() {
on_his_in_seq_no_updated();
- LOG(INFO) << "Binlog replay is finished";
- LOG(INFO) << "binlog_replay_finish with SeqNoState=" << seq_no_state_;
- LOG(INFO) << "binlog_replay_finish with PfsState=" << pfs_state_;
+ LOG(INFO) << "Binlog replay is finished with SeqNoState " << seq_no_state_;
+ LOG(INFO) << "Binlog replay is finished with PfsState " << pfs_state_;
binlog_replay_finish_flag_ = true;
if (auth_state_.state == State::Ready) {
- if (config_state_.my_layer < MY_LAYER) {
- send_action(secret_api::make_object<secret_api::decryptedMessageActionNotifyLayer>(MY_LAYER), SendFlag::None,
+ auto my_layer = static_cast<int32>(SecretChatLayer::Current);
+ if (config_state_.my_layer < my_layer) {
+ send_action(secret_api::make_object<secret_api::decryptedMessageActionNotifyLayer>(my_layer), SendFlag::None,
Promise<>());
}
}
@@ -521,13 +505,9 @@ Status SecretChatActor::run_auth() {
return Status::OK();
}
// messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat;
- telegram_api::messages_requestEncryption tl_query;
- tl_query.user_id_ = get_input_user();
- tl_query.random_id_ = auth_state_.random_id;
- tl_query.g_a_ = BufferSlice(auth_state_.handshake.get_g_b());
- auto query = context_->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::EncryptedChat)),
- create_storer(tl_query));
+ auto query = create_net_query(QueryType::EncryptedChat, telegram_api::messages_requestEncryption(
+ get_input_user(), auth_state_.random_id,
+ BufferSlice(auth_state_.handshake.get_g_b())));
context_->send_net_query(std::move(query), actor_shared(this), false);
auth_state_.state = State::WaitRequestResponse;
return Status::OK();
@@ -536,19 +516,15 @@ Status SecretChatActor::run_auth() {
if (!auth_state_.handshake.has_config()) {
return Status::OK();
}
- TRY_STATUS(auth_state_.handshake.run_checks(context_->dh_callback()));
+ TRY_STATUS(auth_state_.handshake.run_checks(true, context_->dh_callback()));
auto id_and_key = auth_state_.handshake.gen_key();
pfs_state_.auth_key = mtproto::AuthKey(id_and_key.first, std::move(id_and_key.second));
calc_key_hash();
- // messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long =
- // EncryptedChat;
- telegram_api::messages_acceptEncryption tl_query;
- tl_query.peer_ = get_input_chat();
- tl_query.g_b_ = BufferSlice(auth_state_.handshake.get_g_b());
- tl_query.key_fingerprint_ = pfs_state_.auth_key.id();
- auto query = context_->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::EncryptedChat)),
- create_storer(tl_query));
+ // messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat;
+ auto query = create_net_query(
+ QueryType::EncryptedChat,
+ telegram_api::messages_acceptEncryption(get_input_chat(), BufferSlice(auth_state_.handshake.get_g_b()),
+ pfs_state_.auth_key.id()));
context_->send_net_query(std::move(query), actor_shared(this), false);
auth_state_.state = State::WaitAcceptResponse;
return Status::OK();
@@ -571,8 +547,8 @@ void SecretChatActor::run_fill_gaps() {
LOG(INFO) << "Replay pending event: " << tag("seq_no", next_seq_no);
auto message = std::move(begin->second);
pending_inbound_messages_.erase(begin);
- check_status(do_inbound_message_decrypted_unchecked(std::move(message)));
- CHECK(pending_inbound_messages_.find(next_seq_no) == pending_inbound_messages_.end());
+ check_status(do_inbound_message_decrypted_unchecked(std::move(message), -1));
+ CHECK(pending_inbound_messages_.count(next_seq_no) == 0);
} else {
break;
}
@@ -601,7 +577,7 @@ void SecretChatActor::run_fill_gaps() {
void SecretChatActor::run_pfs() {
while (true) {
- LOG(INFO) << "Run pfs loop: " << pfs_state_;
+ LOG(INFO) << "Run PFS loop: " << pfs_state_;
if (pfs_state_.state == PfsState::Empty &&
(pfs_state_.last_message_id + 100 < seq_no_state_.message_id ||
pfs_state_.last_timestamp + 60 * 60 * 24 * 7 < Time::now()) &&
@@ -657,80 +633,114 @@ void SecretChatActor::check_status(Status status) {
if (status.code() == 1) {
LOG(WARNING) << "Non-fatal error: " << status;
} else {
- on_fatal_error(std::move(status));
+ on_fatal_error(std::move(status), false);
}
}
}
-void SecretChatActor::on_fatal_error(Status status) {
- LOG(ERROR) << "Fatal error: " << status;
- cancel_chat(Promise<>());
+void SecretChatActor::on_fatal_error(Status status, bool is_expected) {
+ if (!is_expected) {
+ LOG(ERROR) << "Fatal error: " << status;
+ }
+ cancel_chat(false, false, Promise<>());
}
-void SecretChatActor::cancel_chat(Promise<> promise) {
+void SecretChatActor::cancel_chat(bool delete_history, bool is_already_discarded, Promise<> promise) {
if (close_flag_) {
- promise.set_error(Status::Error(400, "Chat is already closing"));
+ promise.set_value(Unit());
return;
}
close_flag_ = true;
- std::vector<logevent::LogEvent::Id> to_delete;
+ std::vector<log_event::LogEvent::Id> to_delete;
outbound_message_states_.for_each(
- [&](auto state_id, auto &state) { to_delete.push_back(state.message->logevent_id()); });
- inbound_message_states_.for_each([&](auto state_id, auto &state) { to_delete.push_back(state.logevent_id); });
+ [&](auto state_id, auto &state) { to_delete.push_back(state.message->log_event_id()); });
+ inbound_message_states_.for_each([&](auto state_id, auto &state) { to_delete.push_back(state.log_event_id); });
// TODO: It must be a transaction
for (auto id : to_delete) {
- BinlogHelper::erase(context_->binlog(), id);
+ binlog_erase(context_->binlog(), id);
}
- if (create_logevent_id_ != 0) {
- BinlogHelper::erase(context_->binlog(), create_logevent_id_);
- create_logevent_id_ = 0;
+ if (create_log_event_id_ != 0) {
+ binlog_erase(context_->binlog(), create_log_event_id_);
+ create_log_event_id_ = 0;
}
- auto event = std::make_unique<logevent::CloseSecretChat>();
+ auto event = make_unique<log_event::CloseSecretChat>();
event->chat_id = auth_state_.id;
- event->set_logevent_id(
- BinlogHelper::add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*event)));
+ auto log_event_id = binlog_add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*event));
- auto on_sync = PromiseCreator::lambda(
- [actor_id = actor_id(this), event = std::move(event), promise = std::move(promise)](Result<Unit> result) mutable {
- if (result.is_ok()) {
- send_closure(actor_id, &SecretChatActor::do_close_chat_impl, std::move(event));
- promise.set_value(Unit());
- } else {
- promise.set_error(result.error().clone());
- send_closure(actor_id, &SecretChatActor::on_promise_error, result.move_as_error(), "do_close_chat_impl");
- }
- });
+ auto on_sync = PromiseCreator::lambda([actor_id = actor_id(this), delete_history, is_already_discarded, log_event_id,
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ if (result.is_ok()) {
+ send_closure(actor_id, &SecretChatActor::do_close_chat_impl, delete_history, is_already_discarded, log_event_id,
+ std::move(promise));
+ } else {
+ promise.set_error(result.error().clone());
+ send_closure(actor_id, &SecretChatActor::on_promise_error, result.move_as_error(), "cancel_chat");
+ }
+ });
context_->binlog()->force_sync(std::move(on_sync));
yield();
}
-void SecretChatActor::do_close_chat_impl(std::unique_ptr<logevent::CloseSecretChat> event) {
+void SecretChatActor::do_close_chat_impl(bool delete_history, bool is_already_discarded, uint64 log_event_id,
+ Promise<Unit> &&promise) {
close_flag_ = true;
- close_logevent_id_ = event->logevent_id();
- LOG(INFO) << "Send messages.discardEncryption";
- context_->secret_chat_db()->erase_value(auth_state_);
+ auth_state_.state = State::Closed;
+ context_->secret_chat_db()->set_value(auth_state_);
context_->secret_chat_db()->erase_value(config_state_);
context_->secret_chat_db()->erase_value(pfs_state_);
context_->secret_chat_db()->erase_value(seq_no_state_);
- telegram_api::messages_discardEncryption tl_query(auth_state_.id);
- auto query = context_->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::DiscardEncryption)),
- create_storer(tl_query));
- auth_state_.state = State::Closed;
+ MultiPromiseActorSafe mpas{"CloseSecretChatMultiPromiseActor"};
+ mpas.add_promise(
+ PromiseCreator::lambda([actor_id = actor_id(this), log_event_id, promise = std::move(promise)](Unit) mutable {
+ send_closure(actor_id, &SecretChatActor::on_closed, log_event_id, std::move(promise));
+ }));
+
+ auto lock = mpas.get_promise();
+
+ if (delete_history) {
+ context_->on_flush_history(true, MessageId::max(), mpas.get_promise());
+ }
+
send_update_secret_chat();
- context_->send_net_query(std::move(query), actor_shared(this), true);
+ if (!is_already_discarded) {
+ int32 flags = 0;
+ if (delete_history) {
+ flags |= telegram_api::messages_discardEncryption::DELETE_HISTORY_MASK;
+ }
+ auto query = create_net_query(QueryType::DiscardEncryption,
+ telegram_api::messages_discardEncryption(flags, false /*ignored*/, auth_state_.id));
+ query->total_timeout_limit_ = 60 * 60 * 24 * 365;
+ context_->send_net_query(std::move(query), actor_shared(this), true);
+ discard_encryption_promise_ = mpas.get_promise();
+ }
+
+ lock.set_value(Unit());
+}
+
+void SecretChatActor::on_closed(uint64 log_event_id, Promise<Unit> &&promise) {
+ CHECK(close_flag_);
+ if (context_->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Finish closing";
+ context_->secret_chat_db()->erase_value(auth_state_);
+ binlog_erase(context_->binlog(), log_event_id);
+ promise.set_value(Unit());
+ // skip flush
+ stop();
}
-void SecretChatActor::do_create_chat_impl(std::unique_ptr<logevent::CreateSecretChat> event) {
+void SecretChatActor::do_create_chat_impl(unique_ptr<log_event::CreateSecretChat> event) {
LOG(INFO) << *event;
CHECK(event->random_id == auth_state_.id);
- create_logevent_id_ = event->logevent_id();
+ create_log_event_id_ = event->log_event_id();
if (auth_state_.state == State::Empty) {
auth_state_.user_id = event->user_id;
@@ -743,20 +753,13 @@ void SecretChatActor::do_create_chat_impl(std::unique_ptr<logevent::CreateSecret
} else if (auth_state_.state == State::SendRequest) {
} else if (auth_state_.state == State::WaitRequestResponse) {
} else {
- BinlogHelper::erase(context_->binlog(), create_logevent_id_);
- create_logevent_id_ = 0;
+ binlog_erase(context_->binlog(), create_log_event_id_);
+ create_log_event_id_ = 0;
}
}
-void SecretChatActor::on_discard_encryption_result(NetQueryPtr result) {
- CHECK(close_flag_);
- LOG(INFO) << "Got result for messages.discardEncryption";
- BinlogHelper::erase(context_->binlog(), close_logevent_id_);
- // skip flush
- stop();
-}
telegram_api::object_ptr<telegram_api::inputUser> SecretChatActor::get_input_user() {
- return telegram_api::make_object<telegram_api::inputUser>(auth_state_.user_id, auth_state_.user_access_hash);
+ return telegram_api::make_object<telegram_api::inputUser>(auth_state_.user_id.get(), auth_state_.user_access_hash);
}
telegram_api::object_ptr<telegram_api::inputEncryptedChat> SecretChatActor::get_input_chat() {
return telegram_api::make_object<telegram_api::inputEncryptedChat>(auth_state_.id, auth_state_.access_hash);
@@ -780,17 +783,11 @@ Result<std::tuple<uint64, BufferSlice, int32>> SecretChatActor::decrypt(BufferSl
<< tag("crc", crc64(encrypted_message.as_slice())));
}
- // expect that message is encrypted with mtproto 2.0 if his layer is at least MTPROTO_2_LAYER
- std::array<int, 2> versions{{1, 2}};
- if (config_state_.his_layer >= MTPROTO_2_LAYER) {
- std::swap(versions[0], versions[1]);
- }
-
+ std::array<int, 2> versions{{2, 1}};
BufferSlice encrypted_message_copy;
int32 mtproto_version = -1;
- int32 error_code = 0;
+ Result<mtproto::Transport::ReadResult> r_read_result;
for (size_t i = 0; i < versions.size(); i++) {
- bool is_last = i + 1 == versions.size();
encrypted_message_copy = encrypted_message.copy();
data = encrypted_message_copy.as_slice();
CHECK(is_aligned_pointer<4>(data.data()));
@@ -800,20 +797,31 @@ Result<std::tuple<uint64, BufferSlice, int32>> SecretChatActor::decrypt(BufferSl
mtproto_version = versions[i];
info.version = mtproto_version;
info.is_creator = auth_state_.x == 0;
- auto status = mtproto::Transport::read(data, *auth_key, &info, &data, &error_code);
- if (is_last) {
- TRY_STATUS(std::move(status));
- } else if (status.is_error()) {
- LOG(WARNING) << tag("mtproto", mtproto_version) << " decryption failed " << status;
+ r_read_result = mtproto::Transport::read(data, *auth_key, &info);
+ if (i + 1 != versions.size() && r_read_result.is_error()) {
+ if (config_state_.his_layer >= static_cast<int32>(SecretChatLayer::Mtproto2)) {
+ LOG(WARNING) << tag("mtproto", mtproto_version) << " decryption failed " << r_read_result.error();
+ }
continue;
}
break;
}
-
- if (error_code) {
- return Status::Error(PSLICE() << "Got mtproto error code: " << error_code);
+ TRY_RESULT(read_result, std::move(r_read_result));
+ switch (read_result.type()) {
+ case mtproto::Transport::ReadResult::Quickack:
+ return Status::Error("Got quickack instead of a message");
+ case mtproto::Transport::ReadResult::Error:
+ return Status::Error(PSLICE() << "Got MTProto error code instead of a message: " << read_result.error());
+ case mtproto::Transport::ReadResult::Nop:
+ return Status::Error("Got nop instead of a message");
+ case mtproto::Transport::ReadResult::Packet:
+ data = read_result.packet();
+ break;
+ default:
+ UNREACHABLE();
}
- auto len = as<int32>(data.begin());
+
+ int32 len = as<int32>(data.begin());
data = data.substr(4, len);
if (!is_aligned_pointer<4>(data.data())) {
return std::make_tuple(auth_key_id, BufferSlice(data), mtproto_version);
@@ -822,10 +830,10 @@ Result<std::tuple<uint64, BufferSlice, int32>> SecretChatActor::decrypt(BufferSl
}
}
-Status SecretChatActor::do_inbound_message_encrypted(std::unique_ptr<logevent::InboundSecretMessage> message) {
+Status SecretChatActor::do_inbound_message_encrypted(unique_ptr<log_event::InboundSecretMessage> message) {
SCOPE_EXIT {
if (message) {
- message->qts_ack.set_value(Unit());
+ message->promise.set_value(Unit());
}
};
TRY_RESULT(decrypted, decrypt(message->encrypted_message));
@@ -839,11 +847,12 @@ Status SecretChatActor::do_inbound_message_encrypted(std::unique_ptr<logevent::I
Status status;
if (id == secret_api::decryptedMessageLayer::ID) {
auto message_with_layer = secret_api::decryptedMessageLayer::fetch(parser);
+ parser.fetch_end();
if (!parser.get_error()) {
auto layer = message_with_layer->layer_;
- if (layer < DEFAULT_LAYER && false /*TODO: fix android app bug? */) {
- LOG(ERROR) << "All or nothing, " << tag("layer", layer) << " is not supported, drop message "
- << to_string(message_with_layer);
+ if (layer < static_cast<int32>(SecretChatLayer::Default) &&
+ false /* old Android app could send such messages */) {
+ LOG(ERROR) << "Layer " << layer << " is not supported, drop message " << to_string(message_with_layer);
return Status::OK();
}
if (config_state_.his_layer < layer) {
@@ -851,33 +860,35 @@ Status SecretChatActor::do_inbound_message_encrypted(std::unique_ptr<logevent::I
context_->secret_chat_db()->set_value(config_state_);
send_update_secret_chat();
}
- if (layer >= MTPROTO_2_LAYER && mtproto_version < 2) {
- return Status::Error(PSLICE() << "Mtproto 1.0 encryption is forbidden for this layer");
+ if (layer >= static_cast<int32>(SecretChatLayer::Mtproto2) && mtproto_version < 2) {
+ return Status::Error("MTProto 1.0 encryption is forbidden for this layer");
}
if (message_with_layer->in_seq_no_ < 0) {
return Status::Error(PSLICE() << "Invalid seq_no: " << to_string(message_with_layer));
}
message->decrypted_message_layer = std::move(message_with_layer);
- return do_inbound_message_decrypted_unchecked(std::move(message));
+ return do_inbound_message_decrypted_unchecked(std::move(message), mtproto_version);
} else {
status = Status::Error(PSLICE() << parser.get_error() << format::as_hex_dump<4>(data_buffer.as_slice()));
}
} else {
- status = Status::Error(PSLICE() << "Unknown constructor " << tag("ID", format::as_hex(id)));
+ status = Status::Error(PSLICE() << "Unknown constructor " << format::as_hex(id));
}
// support for older layer
- LOG(WARNING) << "Failed to Fetch update: " << status;
- send_action(secret_api::make_object<secret_api::decryptedMessageActionNotifyLayer>(MY_LAYER), SendFlag::None,
- Promise<>());
+ LOG(WARNING) << "Failed to fetch update: " << status;
+ send_action(secret_api::make_object<secret_api::decryptedMessageActionNotifyLayer>(
+ static_cast<int32>(SecretChatLayer::Current)),
+ SendFlag::None, Promise<>());
if (config_state_.his_layer == 8) {
TlBufferParser new_parser(&data_buffer);
auto message_without_layer = secret_api::DecryptedMessage::fetch(new_parser);
+ parser.fetch_end();
if (!new_parser.get_error()) {
message->decrypted_message_layer = secret_api::make_object<secret_api::decryptedMessageLayer>(
BufferSlice(), config_state_.his_layer, -1, -1, std::move(message_without_layer));
- return do_inbound_message_decrypted_unchecked(std::move(message));
+ return do_inbound_message_decrypted_unchecked(std::move(message), mtproto_version);
}
LOG(ERROR) << "Failed to fetch update (DecryptedMessage): " << new_parser.get_error()
<< format::as_hex_dump<4>(data_buffer.as_slice());
@@ -886,7 +897,7 @@ Status SecretChatActor::do_inbound_message_encrypted(std::unique_ptr<logevent::I
return status;
}
-Status SecretChatActor::check_seq_no(int in_seq_no, int out_seq_no) {
+Status SecretChatActor::check_seq_no(int in_seq_no, int out_seq_no, int32 his_layer) {
if (in_seq_no < 0) {
return Status::OK();
}
@@ -907,23 +918,26 @@ Status SecretChatActor::check_seq_no(int in_seq_no, int out_seq_no) {
if (seq_no_state_.my_out_seq_no < in_seq_no) {
return Status::Error("in_seq_no is bigger than seq_no_state_.my_out_seq_no");
}
+ if (his_layer < seq_no_state_.his_layer) {
+ return Status::Error("his_layer is not monotonic");
+ }
return Status::OK();
}
-Status SecretChatActor::do_inbound_message_decrypted_unchecked(
- std::unique_ptr<logevent::InboundSecretMessage> message) {
+Status SecretChatActor::do_inbound_message_decrypted_unchecked(unique_ptr<log_event::InboundSecretMessage> message,
+ int32 mtproto_version) {
SCOPE_EXIT {
- LOG_IF(FATAL, message && message->qts_ack) << "Lost qts_promise";
+ CHECK(message == nullptr || !message->promise);
};
auto in_seq_no = message->decrypted_message_layer->in_seq_no_;
auto out_seq_no = message->decrypted_message_layer->out_seq_no_;
- auto status = check_seq_no(in_seq_no, out_seq_no);
+ auto status = check_seq_no(in_seq_no, out_seq_no, message->his_layer());
if (status.is_error() && status.code() != 2 /* not gap found */) {
- message->qts_ack.set_value(Unit());
- if (message->logevent_id()) {
- LOG(INFO) << "Erase binlog event: " << tag("logevent_id", message->logevent_id());
- BinlogHelper::erase(context_->binlog(), message->logevent_id());
+ message->promise.set_value(Unit());
+ if (message->log_event_id()) {
+ LOG(INFO) << "Erase binlog event: " << tag("log_event_id", message->log_event_id());
+ binlog_erase(context_->binlog(), message->log_event_id());
}
auto warning_message = PSTRING() << status << tag("seq_no_state_.my_in_seq_no", seq_no_state_.my_in_seq_no)
<< tag("seq_no_state_.my_out_seq_no", seq_no_state_.my_out_seq_no)
@@ -938,6 +952,15 @@ Status SecretChatActor::do_inbound_message_decrypted_unchecked(
return status;
}
+ LOG(INFO) << "Receive message encrypted with MTProto " << mtproto_version << ": "
+ << to_string(message->decrypted_message_layer);
+
+ if (message->decrypted_message_layer->message_->get_id() == secret_api::decryptedMessageService8::ID) {
+ auto old = move_tl_object_as<secret_api::decryptedMessageService8>(message->decrypted_message_layer->message_);
+ message->decrypted_message_layer->message_ =
+ secret_api::make_object<secret_api::decryptedMessageService>(old->random_id_, std::move(old->action_));
+ }
+
// Process ActionResend.
if (message->decrypted_message_layer->message_->get_id() == secret_api::decryptedMessageService::ID) {
auto *decrypted_message_service =
@@ -946,17 +969,17 @@ Status SecretChatActor::do_inbound_message_decrypted_unchecked(
auto *action_resend =
static_cast<secret_api::decryptedMessageActionResend *>(decrypted_message_service->action_.get());
- uint32 start_seq_no = static_cast<uint32>(action_resend->start_seq_no_ / 2);
- uint32 finish_seq_no = static_cast<uint32>(action_resend->end_seq_no_ / 2);
+ auto start_seq_no = static_cast<uint32>(action_resend->start_seq_no_ / 2);
+ auto finish_seq_no = static_cast<uint32>(action_resend->end_seq_no_ / 2);
if (start_seq_no + MAX_RESEND_COUNT < finish_seq_no) {
- message->qts_ack.set_value(Unit());
+ message->promise.set_value(Unit());
return Status::Error(PSLICE() << "Won't resend more than " << MAX_RESEND_COUNT << " messages");
}
LOG(INFO) << "ActionResend: " << tag("start", start_seq_no) << tag("finish_seq_no", finish_seq_no);
for (auto seq_no = start_seq_no; seq_no <= finish_seq_no; seq_no++) {
auto it = out_seq_no_to_outbound_message_state_token_.find(seq_no);
if (it == out_seq_no_to_outbound_message_state_token_.end()) {
- message->qts_ack.set_value(Unit());
+ message->promise.set_value(Unit());
return Status::Error(PSLICE() << "Can't resend query " << tag("seq_no", seq_no));
}
auto state_id = it->second;
@@ -967,8 +990,6 @@ Status SecretChatActor::do_inbound_message_decrypted_unchecked(
}
}
- LOG(INFO) << "GOT MESSAGE " << to_string(message->decrypted_message_layer);
-
if (status.is_error()) {
CHECK(status.code() == 2); // gap found
do_inbound_message_decrypted_pending(std::move(message));
@@ -985,12 +1006,12 @@ Status SecretChatActor::do_inbound_message_decrypted_unchecked(
return do_inbound_message_decrypted(std::move(message));
}
-void SecretChatActor::do_outbound_message_impl(std::unique_ptr<logevent::OutboundSecretMessage> binlog_event,
+void SecretChatActor::do_outbound_message_impl(unique_ptr<log_event::OutboundSecretMessage> binlog_event,
Promise<> promise) {
binlog_event->crc = crc64(binlog_event->encrypted_message.as_slice());
LOG(INFO) << "Do outbound message: " << *binlog_event << tag("crc", binlog_event->crc);
auto &state_id_ref = random_id_to_outbound_message_state_token_[binlog_event->random_id];
- CHECK(state_id_ref == 0) << "Random id collision";
+ LOG_CHECK(state_id_ref == 0) << "Random ID collision";
state_id_ref = outbound_message_states_.create();
const uint64 state_id = state_id_ref;
auto *state = outbound_message_states_.get(state_id);
@@ -1000,13 +1021,13 @@ void SecretChatActor::do_outbound_message_impl(std::unique_ptr<logevent::Outboun
// OutboundSecretMessage
//
- // 1. [] => Save logevent. [save_logevent]
- // 2. [save_logevent] => Save SeqNoState [save_changes]
- // 3. [save_logevent] => Send NetQuery [send_message]
+ // 1. [] => Save log_event. [save_log_event]
+ // 2. [save_log_event] => Save SeqNoState [save_changes]
+ // 3. [save_log_event] => Send NetQuery [send_message]
// Note: we have to force binlog to flush
// 4.0 [send_message]:Fail => rewrite
- // 4. [save_changes; send_message] => Mark logevent as sent [rewrite_logevent]
- // 5. [save_changes; send_message; ack] => [remove_logevent]
+ // 4. [save_changes; send_message] => Mark log event as sent [rewrite_log_event]
+ // 5. [save_changes; send_message; ack] => [remove_log_event]
auto message = state->message.get();
@@ -1016,7 +1037,7 @@ void SecretChatActor::do_outbound_message_impl(std::unique_ptr<logevent::Outboun
send_closure(actor_id, &SecretChatActor::on_outbound_send_message_start, state_id);
} else {
send_closure(actor_id, &SecretChatActor::on_promise_error, result.move_as_error(),
- "on_oubound_send_message_start");
+ "on_outbound_send_message_start");
}
});
@@ -1048,26 +1069,25 @@ void SecretChatActor::do_outbound_message_impl(std::unique_ptr<logevent::Outboun
out_seq_no_to_outbound_message_state_token_[out_seq_no] = state_id;
}
- // save_logevent => [send_message; save_changes]
- auto save_logevent_finish = PromiseCreator::join(std::move(send_message_start), std::move(save_changes_start));
+ // save_log_event => [send_message; save_changes]
+ auto save_log_event_finish = PromiseCreator::join(std::move(send_message_start), std::move(save_changes_start));
- auto logevent_id = state->message->logevent_id();
- if (logevent_id == 0) {
- logevent_id =
- BinlogHelper::add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*state->message));
- LOG(INFO) << "Outbound secret message [save_logevent] start " << tag("logevent_id", logevent_id);
- context_->binlog()->force_sync(std::move(save_logevent_finish));
- state->message->set_logevent_id(logevent_id);
+ auto log_event_id = state->message->log_event_id();
+ if (log_event_id == 0) {
+ log_event_id = binlog_add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*state->message));
+ LOG(INFO) << "Outbound secret message [save_log_event] start " << tag("log_event_id", log_event_id);
+ context_->binlog()->force_sync(std::move(save_log_event_finish));
+ state->message->set_log_event_id(log_event_id);
} else {
- LOG(INFO) << "Outbound secret message [save_logevent] skip " << tag("logevent_id", logevent_id);
- save_logevent_finish.set_value(Unit());
+ LOG(INFO) << "Outbound secret message [save_log_event] skip " << tag("log_event_id", log_event_id);
+ save_log_event_finish.set_value(Unit());
}
- promise.set_value(Unit()); // logevent was sent to binlog;
+ promise.set_value(Unit()); // log event was sent to binlog
}
void SecretChatActor::on_his_in_seq_no_updated() {
- auto it = begin(out_seq_no_to_outbound_message_state_token_);
- while (it != end(out_seq_no_to_outbound_message_state_token_) && it->first < seq_no_state_.his_in_seq_no) {
+ auto it = out_seq_no_to_outbound_message_state_token_.begin();
+ while (it != out_seq_no_to_outbound_message_state_token_.end() && it->first < seq_no_state_.his_in_seq_no) {
auto token = it->second;
it = out_seq_no_to_outbound_message_state_token_.erase(it);
on_outbound_ack(token);
@@ -1078,9 +1098,10 @@ void SecretChatActor::on_seq_no_state_changed() {
}
void SecretChatActor::on_pfs_state_changed() {
- LOG(INFO) << "on_pfs_state_changed: " << pfs_state_;
+ LOG(INFO) << "In on_pfs_state_changed: " << pfs_state_;
pfs_state_changed_ = true;
}
+
Promise<> SecretChatActor::add_changes(Promise<> save_changes_finish) {
StateChange change;
if (seq_no_state_changed_) {
@@ -1115,10 +1136,15 @@ void SecretChatActor::update_seq_no_state(const T &new_seq_no_state) {
}
seq_no_state_.message_id = new_seq_no_state.message_id;
if (new_seq_no_state.my_in_seq_no != -1) {
- LOG(INFO) << "my_in_seq_no: " << seq_no_state_.my_in_seq_no << "--->" << new_seq_no_state.my_in_seq_no;
+ LOG(INFO) << "Have my_in_seq_no: " << seq_no_state_.my_in_seq_no << "--->" << new_seq_no_state.my_in_seq_no;
seq_no_state_.my_in_seq_no = new_seq_no_state.my_in_seq_no;
seq_no_state_.my_out_seq_no = new_seq_no_state.my_out_seq_no;
+ auto new_his_layer = new_seq_no_state.his_layer();
+ if (new_his_layer != -1) {
+ seq_no_state_.his_layer = new_his_layer;
+ }
+
if (seq_no_state_.his_in_seq_no != new_seq_no_state.his_in_seq_no) {
seq_no_state_.his_in_seq_no = new_seq_no_state.his_in_seq_no;
on_his_in_seq_no_updated();
@@ -1128,68 +1154,66 @@ void SecretChatActor::update_seq_no_state(const T &new_seq_no_state) {
return on_seq_no_state_changed();
}
-Status SecretChatActor::do_inbound_message_decrypted_pending(std::unique_ptr<logevent::InboundSecretMessage> message) {
- // Just save logevent if necessary
- auto logevent_id = message->logevent_id();
+void SecretChatActor::do_inbound_message_decrypted_pending(unique_ptr<log_event::InboundSecretMessage> message) {
+ // Just save log event if necessary
+ auto log_event_id = message->log_event_id();
// qts
- auto qts_promise = std::move(message->qts_ack);
+ auto qts_promise = std::move(message->promise);
- if (logevent_id == 0) {
+ if (log_event_id == 0) {
message->is_pending = true;
- message->set_logevent_id(BinlogHelper::add(context_->binlog(), LogEvent::HandlerType::SecretChats,
- create_storer(*message), std::move(qts_promise)));
- LOG(INFO) << "Inbound PENDING secret message [save_logevent] start (do not expect finish) "
- << tag("logevent_id", message->logevent_id());
+ message->set_log_event_id(binlog_add(context_->binlog(), LogEvent::HandlerType::SecretChats,
+ create_storer(*message), std::move(qts_promise)));
+ LOG(INFO) << "Inbound PENDING secret message [save_log_event] start (do not expect finish) "
+ << tag("log_event_id", message->log_event_id());
} else {
- LOG(INFO) << "Inbound PENDING secret message [save_logevent] skip " << tag("logevent_id", logevent_id);
+ LOG(INFO) << "Inbound PENDING secret message [save_log_event] skip " << tag("log_event_id", log_event_id);
CHECK(!qts_promise);
}
- LOG(INFO) << "Inbound PENDING secret message start " << tag("logevent_id", logevent_id) << tag("message", *message);
+ LOG(INFO) << "Inbound PENDING secret message start " << tag("log_event_id", log_event_id) << tag("message", *message);
auto seq_no = message->decrypted_message_layer->out_seq_no_ / 2;
pending_inbound_messages_[seq_no] = std::move(message);
-
- return Status::OK();
}
-Status SecretChatActor::do_inbound_message_decrypted(std::unique_ptr<logevent::InboundSecretMessage> message) {
+Status SecretChatActor::do_inbound_message_decrypted(unique_ptr<log_event::InboundSecretMessage> message) {
// InboundSecretMessage
//
- // 1. [] => Add logevent. [save_logevent]
- // 2. [save_logevent] => Save SeqNoState [save_changes]
- // 3. [save_logevent] => Add message to MessageManager [save_message]
- // Note: if we are able to add message by random_id, we may not wait for (logevent). Otherwise we should force
+ // 1. [] => Add log event. [save_log_event]
+ // 2. [save_log_event] => Save SeqNoState [save_changes]
+ // 3. [save_log_event] => Add message to MessageManager [save_message]
+ // Note: if we are able to add message by random_id, we may not wait for (log event). Otherwise we should force
// binlog flush.
- // 4. [save_logevent] => Update qts [qts]
- // 5. [save_changes; save_message; ?qts) => Remove logevent [remove_logevent]
+ // 4. [save_log_event] => Update qts [qts]
+ // 5. [save_changes; save_message; ?qts) => Remove log event [remove_log_event]
// Note: It is easier not to wait for qts. In the worst case old update will be handled again after restart.
auto state_id = inbound_message_states_.create();
InboundMessageState &state = *inbound_message_states_.get(state_id);
- // save logevent
- auto logevent_id = message->logevent_id();
+ // save log event
+ auto log_event_id = message->log_event_id();
bool need_sync = false;
- if (logevent_id == 0) {
- logevent_id = BinlogHelper::add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*message));
- LOG(INFO) << "Inbound secret message [save_logevent] start " << tag("logevent_id", logevent_id);
+ if (log_event_id == 0) {
+ log_event_id = binlog_add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*message));
+ LOG(INFO) << "Inbound secret message [save_log_event] start " << tag("log_event_id", log_event_id);
need_sync = true;
} else {
if (message->is_pending) {
message->is_pending = false;
- auto old_logevent_id = logevent_id;
- logevent_id = BinlogHelper::add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*message));
- BinlogHelper::erase(context_->binlog(), old_logevent_id);
- LOG(INFO) << "Inbound secret message [save_logevent] rewrite (after pending state) "
- << tag("logevent_id", logevent_id) << tag("old_logevent_id", old_logevent_id);
+ auto old_log_event_id = log_event_id;
+ log_event_id = binlog_add(context_->binlog(), LogEvent::HandlerType::SecretChats, create_storer(*message));
+ binlog_erase(context_->binlog(), old_log_event_id);
+ LOG(INFO) << "Inbound secret message [save_log_event] rewrite (after pending state) "
+ << tag("log_event_id", log_event_id) << tag("old_log_event_id", old_log_event_id);
need_sync = true;
} else {
- LOG(INFO) << "Inbound secret message [save_logevent] skip " << tag("logevent_id", logevent_id);
+ LOG(INFO) << "Inbound secret message [save_log_event] skip " << tag("log_event_id", log_event_id);
}
}
- LOG(INFO) << "Inbound secret message start " << tag("logevent_id", logevent_id) << tag("message", *message);
- state.logevent_id = logevent_id;
+ LOG(INFO) << "Inbound secret message start " << tag("log_event_id", log_event_id) << tag("message", *message);
+ state.log_event_id = log_event_id;
// save_message
auto save_message_finish = PromiseCreator::lambda([actor_id = actor_id(this), state_id](Result<> result) {
@@ -1213,33 +1237,34 @@ Status SecretChatActor::do_inbound_message_decrypted(std::unique_ptr<logevent::I
}
// qts
- auto qts_promise = std::move(message->qts_ack);
+ auto qts_promise = std::move(message->promise);
// process message
- tl_object_ptr<telegram_api::encryptedFile> file;
- if (message->has_encrypted_file) {
- file = message->file.as_encrypted_file();
- }
-
if (message->decrypted_message_layer->message_->get_id() == secret_api::decryptedMessage46::ID) {
auto old = move_tl_object_as<secret_api::decryptedMessage46>(message->decrypted_message_layer->message_);
old->flags_ &= ~secret_api::decryptedMessage::GROUPED_ID_MASK; // just in case
message->decrypted_message_layer->message_ = secret_api::make_object<secret_api::decryptedMessage>(
- old->flags_, old->random_id_, old->ttl_, std::move(old->message_), std::move(old->media_),
+ old->flags_, false /*ignored*/, old->random_id_, old->ttl_, std::move(old->message_), std::move(old->media_),
std::move(old->entities_), std::move(old->via_bot_name_), old->reply_to_random_id_, 0);
}
+ if (message->decrypted_message_layer->message_->get_id() == secret_api::decryptedMessageService8::ID) {
+ auto old = move_tl_object_as<secret_api::decryptedMessageService8>(message->decrypted_message_layer->message_);
+ message->decrypted_message_layer->message_ =
+ secret_api::make_object<secret_api::decryptedMessageService>(old->random_id_, std::move(old->action_));
+ }
// NB: message is invalid after this 'move_as'
// Send update through context_
// Note, that update may be sent multiple times and should be somehow protected from replay.
// Luckily all updates seems to be idempotent.
- // We could use ChangesProcessor to mark logevent as sent to context_, but I don't see any advantages of this
+ // We could use ChangesProcessor to mark log event as sent to context_, but I don't see any advantages of this
// approach.
if (message->decrypted_message_layer->message_->get_id() == secret_api::decryptedMessage::ID) {
auto decrypted_message =
move_tl_object_as<secret_api::decryptedMessage>(message->decrypted_message_layer->message_);
context_->on_inbound_message(get_user_id(), MessageId(ServerMessageId(message->message_id)), message->date,
- std::move(file), std::move(decrypted_message), std::move(save_message_finish));
+ std::move(message->file), std::move(decrypted_message),
+ std::move(save_message_finish));
} else if (message->decrypted_message_layer->message_->get_id() == secret_api::decryptedMessageService::ID) {
auto decrypted_message_service =
move_tl_object_as<secret_api::decryptedMessageService>(message->decrypted_message_layer->message_);
@@ -1247,20 +1272,22 @@ Status SecretChatActor::do_inbound_message_decrypted(std::unique_ptr<logevent::I
auto action = std::move(decrypted_message_service->action_);
switch (action->get_id()) {
case secret_api::decryptedMessageActionDeleteMessages::ID:
- // Corresponding logevent won't be deleted before promise returned by add_changes is set.
+ // Corresponding log event won't be deleted before promise returned by add_changes is set.
context_->on_delete_messages(
- std::move(static_cast<secret_api::decryptedMessageActionDeleteMessages &>(*action).random_ids_),
+ static_cast<const secret_api::decryptedMessageActionDeleteMessages &>(*action).random_ids_,
std::move(save_message_finish));
break;
case secret_api::decryptedMessageActionFlushHistory::ID:
- context_->on_flush_history(MessageId(ServerMessageId(message->message_id)), std::move(save_message_finish));
+ context_->on_flush_history(false, MessageId(ServerMessageId(message->message_id)),
+ std::move(save_message_finish));
break;
case secret_api::decryptedMessageActionReadMessages::ID: {
- auto &random_ids = static_cast<secret_api::decryptedMessageActionReadMessages &>(*action).random_ids_;
+ const auto &random_ids =
+ static_cast<const secret_api::decryptedMessageActionReadMessages &>(*action).random_ids_;
if (random_ids.size() == 1) {
context_->on_read_message(random_ids[0], std::move(save_message_finish));
} else { // probably never happens
- MultiPromiseActorSafe mpas;
+ MultiPromiseActorSafe mpas{"ReadSecretMessagesMultiPromiseActor"};
mpas.add_promise(std::move(save_message_finish));
auto lock = mpas.get_promise();
for (auto random_id : random_ids) {
@@ -1276,20 +1303,10 @@ Status SecretChatActor::do_inbound_message_decrypted(std::unique_ptr<logevent::I
break;
case secret_api::decryptedMessageActionSetMessageTTL::ID:
context_->on_set_ttl(get_user_id(), MessageId(ServerMessageId(message->message_id)), message->date,
- static_cast<secret_api::decryptedMessageActionSetMessageTTL &>(*action).ttl_seconds_,
+ static_cast<const secret_api::decryptedMessageActionSetMessageTTL &>(*action).ttl_seconds_,
decrypted_message_service->random_id_, std::move(save_message_finish));
break;
default:
- /*
-decryptedMessageActionResend#511110b0 start_seq_no:int end_seq_no:int = DecryptedMessageAction;
-decryptedMessageActionNotifyLayer#f3048883 layer:int = DecryptedMessageAction;
-decryptedMessageActionTyping#ccb27641 action:SendMessageAction = DecryptedMessageAction;
-decryptedMessageActionRequestKey#f3c9611b exchange_id:long g_a:bytes = DecryptedMessageAction;
-decryptedMessageActionAcceptKey#6fe1735b exchange_id:long g_b:bytes key_fingerprint:long = DecryptedMessageAction;
-decryptedMessageActionAbortKey#dd05ec6b exchange_id:long = DecryptedMessageAction;
-decryptedMessageActionCommitKey#ec2e0b9b exchange_id:long key_fingerprint:long = DecryptedMessageAction;
-decryptedMessageActionNoop#a82fdd63 = DecryptedMessageAction;
- */
save_message_finish.set_value(Unit());
break;
}
@@ -1312,13 +1329,13 @@ decryptedMessageActionNoop#a82fdd63 = DecryptedMessageAction;
});
auto save_changes_start = add_changes(std::move(save_changes_finish));
- // save_logevent
- auto save_logevent_finish = PromiseCreator::join(std::move(save_changes_start), std::move(qts_promise));
+ // save_log_event
+ auto save_log_event_finish = PromiseCreator::join(std::move(save_changes_start), std::move(qts_promise));
if (need_sync) {
// TODO: lazy sync is enough
- context_->binlog()->force_sync(std::move(save_logevent_finish));
+ context_->binlog()->force_sync(std::move(save_log_event_finish));
} else {
- save_logevent_finish.set_value(Unit());
+ save_log_event_finish.set_value(Unit());
}
return Status::OK();
}
@@ -1348,19 +1365,19 @@ void SecretChatActor::on_save_changes_start(ChangesProcessor<StateChange>::Id sa
saved_pfs_state_message_id_ = pfs_state_change.message_id;
context_->secret_chat_db()->set_value(pfs_state_change);
}
- // NB: we may not wait till db is flushed, because every other change will be in the same binlog
+ // NB: we may not wait till database is flushed, because every other change will be in the same binlog
for (auto &save_changes_finish : save_changes_finish_promises) {
save_changes_finish.set_value(Unit());
}
}
void SecretChatActor::on_inbound_save_message_finish(uint64 state_id) {
- if (close_flag_) {
+ if (close_flag_ || context_->close_flag()) {
return;
}
auto *state = inbound_message_states_.get(state_id);
CHECK(state);
- LOG(INFO) << "Inbound message [save_message] finish " << tag("logevent_id", state->logevent_id);
+ LOG(INFO) << "Inbound message [save_message] finish " << tag("log_event_id", state->log_event_id);
state->save_message_finish = true;
inbound_loop(state, state_id);
}
@@ -1371,7 +1388,7 @@ void SecretChatActor::on_inbound_save_changes_finish(uint64 state_id) {
}
auto *state = inbound_message_states_.get(state_id);
CHECK(state);
- LOG(INFO) << "Inbound message [save_changes] finish " << tag("logevent_id", state->logevent_id);
+ LOG(INFO) << "Inbound message [save_changes] finish " << tag("log_event_id", state->log_event_id);
state->save_changes_finish = true;
inbound_loop(state, state_id);
}
@@ -1383,47 +1400,53 @@ void SecretChatActor::inbound_loop(InboundMessageState *state, uint64 state_id)
if (!state->save_changes_finish || !state->save_message_finish) {
return;
}
- LOG(INFO) << "Inbound message [remove_logevent] start " << tag("logevent_id", state->logevent_id);
- BinlogHelper::erase(context_->binlog(), state->logevent_id);
+ LOG(INFO) << "Inbound message [remove_log_event] start " << tag("log_event_id", state->log_event_id);
+ binlog_erase(context_->binlog(), state->log_event_id);
inbound_message_states_.erase(state_id);
}
-NetQueryPtr SecretChatActor::create_net_query(const logevent::OutboundSecretMessage &message) {
+NetQueryPtr SecretChatActor::create_net_query(const log_event::OutboundSecretMessage &message) {
NetQueryPtr query;
- if (message.is_service) {
+ if (message.need_notify_user) {
CHECK(message.file.empty());
- query = context_->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::Message)),
- create_storer(telegram_api::messages_sendEncryptedService(get_input_chat(), message.random_id,
- message.encrypted_message.clone())));
- query->total_timeout_limit = 1000000000; // inf. We will re-sent it immediately anyway.
+ query = create_net_query(QueryType::Message,
+ telegram_api::messages_sendEncryptedService(get_input_chat(), message.random_id,
+ message.encrypted_message.clone()));
} else if (message.file.empty()) {
- query = context_->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::Message)),
- create_storer(telegram_api::messages_sendEncrypted(get_input_chat(), message.random_id,
- message.encrypted_message.clone())));
+ int32 flags = 0;
+ if (message.is_silent) {
+ flags |= telegram_api::messages_sendEncrypted::SILENT_MASK;
+ }
+ query = create_net_query(
+ QueryType::Message, telegram_api::messages_sendEncrypted(flags, false /*ignored*/, get_input_chat(),
+ message.random_id, message.encrypted_message.clone()));
} else {
- query = context_->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::Message)),
- create_storer(telegram_api::messages_sendEncryptedFile(get_input_chat(), message.random_id,
- message.encrypted_message.clone(),
- message.file.as_input_encrypted_file())));
+ int32 flags = 0;
+ if (message.is_silent) {
+ flags |= telegram_api::messages_sendEncryptedFile::SILENT_MASK;
+ }
+ query = create_net_query(QueryType::Message,
+ telegram_api::messages_sendEncryptedFile(
+ flags, false /*ignored*/, get_input_chat(), message.random_id,
+ message.encrypted_message.clone(), message.file.as_input_encrypted_file()));
+ }
+ if (!message.is_rewritable) {
+ query->total_timeout_limit_ = 1000000000; // inf. We will re-sent it immediately anyway
}
if (message.is_external && context_->get_config_option_boolean("use_quick_ack")) {
query->quick_ack_promise_ =
- PromiseCreator::lambda([actor_id = actor_id(this), random_id = message.random_id](
- Unit) { send_closure(actor_id, &SecretChatActor::on_send_message_ack, random_id); },
- PromiseCreator::Ignore());
+ PromiseCreator::lambda([actor_id = actor_id(this), random_id = message.random_id](Result<Unit> result) {
+ if (result.is_ok()) {
+ send_closure(actor_id, &SecretChatActor::on_send_message_ack, random_id);
+ }
+ });
}
return query;
}
void SecretChatActor::on_outbound_send_message_start(uint64 state_id) {
- if (close_flag_) {
- return;
- }
auto *state = outbound_message_states_.get(state_id);
if (state == nullptr) {
LOG(INFO) << "Outbound message [send_message] start ignored (unknown state_id) " << tag("state_id", state_id);
@@ -1433,14 +1456,14 @@ void SecretChatActor::on_outbound_send_message_start(uint64 state_id) {
auto *message = state->message.get();
if (!message->is_sent) {
- LOG(INFO) << "Outbound message [send_message] start " << tag("logevent_id", state->message->logevent_id());
+ LOG(INFO) << "Outbound message [send_message] start " << tag("log_event_id", state->message->log_event_id());
auto query = create_net_query(*message);
state->net_query_id = query->id();
state->net_query_ref = query.get_weak();
state->net_query_may_fail = state->message->is_rewritable;
context_->send_net_query(std::move(query), actor_shared(this, state_id), true);
} else {
- LOG(INFO) << "Outbound message [send_message] start dummy " << tag("logevent_id", state->message->logevent_id());
+ LOG(INFO) << "Outbound message [send_message] start dummy " << tag("log_event_id", state->message->log_event_id());
on_outbound_send_message_finish(state_id);
}
}
@@ -1455,11 +1478,11 @@ void SecretChatActor::outbound_resend(uint64 state_id) {
state->message->is_sent = false;
state->net_query_id = 0;
state->net_query_ref = NetQueryRef();
- LOG(INFO) << "Oubound message [resend] " << tag("logevent_id", state->message->logevent_id())
+ LOG(INFO) << "Outbound message [resend] " << tag("log_event_id", state->message->log_event_id())
<< tag("state_id", state_id);
- BinlogHelper::rewrite(context_->binlog(), state->message->logevent_id(), LogEvent::HandlerType::SecretChats,
- create_storer(*state->message));
+ binlog_rewrite(context_->binlog(), state->message->log_event_id(), LogEvent::HandlerType::SecretChats,
+ create_storer(*state->message));
auto send_message_start = PromiseCreator::lambda([actor_id = actor_id(this), state_id](Result<> result) {
if (result.is_ok()) {
send_closure(actor_id, &SecretChatActor::on_outbound_send_message_start, state_id);
@@ -1484,21 +1507,22 @@ Status SecretChatActor::outbound_rewrite_with_empty(uint64 state_id) {
MutableSlice data = state->message->encrypted_message.as_slice();
CHECK(is_aligned_pointer<4>(data.data()));
- // Rewrite with delete itself.
+ // Rewrite with delete itself
tl_object_ptr<secret_api::DecryptedMessage> message = secret_api::make_object<secret_api::decryptedMessageService>(
state->message->random_id, secret_api::make_object<secret_api::decryptedMessageActionDeleteMessages>(
std::vector<int64>{static_cast<int64>(state->message->random_id)}));
- TRY_RESULT(encrypted_message, create_encrypted_message(current_layer(), state->message->my_in_seq_no,
- state->message->my_out_seq_no, message));
+ TRY_RESULT(encrypted_message,
+ create_encrypted_message(state->message->my_in_seq_no, state->message->my_out_seq_no, message));
state->message->encrypted_message = std::move(encrypted_message);
LOG(INFO) << tag("crc", crc64(state->message->encrypted_message.as_slice()));
state->message->is_rewritable = false;
state->message->is_external = false;
- state->message->is_service = true;
- state->message->file = logevent::EncryptedInputFile::from_input_encrypted_file(nullptr);
- BinlogHelper::rewrite(context_->binlog(), state->message->logevent_id(), LogEvent::HandlerType::SecretChats,
- create_storer(*state->message));
+ state->message->need_notify_user = false;
+ state->message->is_silent = true;
+ state->message->file = log_event::EncryptedInputFile();
+ binlog_rewrite(context_->binlog(), state->message->log_event_id(), LogEvent::HandlerType::SecretChats,
+ create_storer(*state->message));
return Status::OK();
}
@@ -1515,7 +1539,7 @@ void SecretChatActor::on_outbound_send_message_result(NetQueryPtr query, Promise
}
CHECK(state);
if (state->net_query_id != query->id()) {
- LOG(INFO) << "Ignore old net query result " << tag("logevent_id", state->message->logevent_id())
+ LOG(INFO) << "Ignore old net query result " << tag("log_event_id", state->message->log_event_id())
<< tag("query_id", query->id()) << tag("state_query_id", state->net_query_id) << query;
query->clear();
return;
@@ -1542,16 +1566,16 @@ void SecretChatActor::on_outbound_send_message_result(NetQueryPtr query, Promise
if (state->message->is_external) {
LOG(INFO) << "Outbound secret message [send_message] failed, rewrite it with dummy "
- << tag("logevent_id", state->message->logevent_id()) << tag("error", error);
+ << tag("log_event_id", state->message->log_event_id()) << tag("error", error);
state->send_result_ = [this, random_id = state->message->random_id, error_code = error.code(),
- error_message = error.message()](Promise<> promise) {
- this->context_->on_send_message_error(random_id, Status::Error(error_code, error_message), std::move(promise));
+ error_message = error.message().str()](Promise<> promise) {
+ context_->on_send_message_error(random_id, Status::Error(error_code, error_message), std::move(promise));
};
state->send_result_(std::move(send_message_error_promise));
} else {
// Just resend.
LOG(INFO) << "Outbound secret message [send_message] failed, resend it "
- << tag("logevent_id", state->message->logevent_id()) << tag("error", error);
+ << tag("log_event_id", state->message->log_event_id()) << tag("error", error);
send_message_error_promise.set_value(Unit());
}
return;
@@ -1577,36 +1601,30 @@ void SecretChatActor::on_outbound_send_message_result(NetQueryPtr query, Promise
state->send_result_ = [this, random_id = state->message->random_id,
message_id = MessageId(ServerMessageId(state->message->message_id)),
date = sent->date_](Promise<> promise) {
- this->context_->on_send_message_ok(random_id, message_id, date, nullptr, std::move(promise));
+ context_->on_send_message_ok(random_id, message_id, date, nullptr, std::move(promise));
};
state->send_result_(std::move(send_message_finish_promise));
return;
}
case telegram_api::messages_sentEncryptedFile::ID: {
auto sent = move_tl_object_as<telegram_api::messages_sentEncryptedFile>(result);
- std::function<telegram_api::object_ptr<telegram_api::EncryptedFile>()> get_file;
- telegram_api::downcast_call(
- *sent->file_, overloaded(
- [&](telegram_api::encryptedFileEmpty &) {
- state->message->file = logevent::EncryptedInputFile::from_input_encrypted_file(
- telegram_api::inputEncryptedFileEmpty());
- get_file = [] { return telegram_api::make_object<telegram_api::encryptedFileEmpty>(); };
- },
- [&](telegram_api::encryptedFile &file) {
- state->message->file = logevent::EncryptedInputFile::from_input_encrypted_file(
- telegram_api::inputEncryptedFile(file.id_, file.access_hash_));
- get_file = [id = file.id_, access_hash = file.access_hash_, size = file.size_,
- dc_id = file.dc_id_, key_fingerprint = file.key_fingerprint_] {
- return telegram_api::make_object<telegram_api::encryptedFile>(id, access_hash, size,
- dc_id, key_fingerprint);
- };
- }));
-
- state->send_result_ = [this, random_id = state->message->random_id,
- message_id = MessageId(ServerMessageId(state->message->message_id)), date = sent->date_,
- get_file = std::move(get_file)](Promise<> promise) {
- this->context_->on_send_message_ok(random_id, message_id, date, get_file(), std::move(promise));
- };
+ auto file = EncryptedFile::get_encrypted_file(std::move(sent->file_));
+ if (file == nullptr) {
+ state->message->file = log_event::EncryptedInputFile();
+ state->send_result_ = [this, random_id = state->message->random_id,
+ message_id = MessageId(ServerMessageId(state->message->message_id)),
+ date = sent->date_](Promise<> promise) {
+ context_->on_send_message_ok(random_id, message_id, date, nullptr, std::move(promise));
+ };
+ } else {
+ state->message->file = {log_event::EncryptedInputFile::Location, file->id_, file->access_hash_, 0, 0};
+ state->send_result_ = [this, random_id = state->message->random_id,
+ message_id = MessageId(ServerMessageId(state->message->message_id)),
+ date = sent->date_, file = *file](Promise<> promise) {
+ context_->on_send_message_ok(random_id, message_id, date, make_unique<EncryptedFile>(file),
+ std::move(promise));
+ };
+ }
state->send_result_(std::move(send_message_finish_promise));
return;
}
@@ -1620,6 +1638,9 @@ void SecretChatActor::on_outbound_send_message_error(uint64 state_id, Status err
if (close_flag_) {
return;
}
+ if (context_->close_flag()) {
+ return;
+ }
auto *state = outbound_message_states_.get(state_id);
if (!state) {
return;
@@ -1633,19 +1654,9 @@ void SecretChatActor::on_outbound_send_message_error(uint64 state_id, Status err
state = outbound_message_states_.get(state_id);
need_sync = true;
}
- } else {
- bool should_fail = false;
- if (error.code() == 429) {
- should_fail = false;
- } else if (error.code() == 400 && error.message() == "ENCRYPTION_DECLINED") {
- should_fail = true;
- } else {
- LOG(ERROR) << "Got unknown error for encrypted service message: " << error;
- should_fail = true;
- }
- if (should_fail) {
- return on_fatal_error(std::move(error));
- }
+ } else if (error.code() != 429) {
+ return on_fatal_error(std::move(error),
+ (error.code() == 400 && error.message() == "ENCRYPTION_DECLINED") || error.code() == 403);
}
auto query = create_net_query(*state->message);
state->net_query_id = query->id();
@@ -1675,7 +1686,7 @@ void SecretChatActor::on_outbound_send_message_finish(uint64 state_id) {
if (!state) {
return;
}
- LOG(INFO) << "Outbound secret message [send_message] finish " << tag("logevent_id", state->message->logevent_id());
+ LOG(INFO) << "Outbound secret message [send_message] finish " << tag("log_event_id", state->message->log_event_id());
state->send_message_finish_flag = true;
state->outer_send_message_finish.set_value(Unit());
@@ -1688,7 +1699,7 @@ void SecretChatActor::on_outbound_save_changes_finish(uint64 state_id) {
}
auto *state = outbound_message_states_.get(state_id);
CHECK(state);
- LOG(INFO) << "Outbound secret message [save_changes] finish " << tag("logevent_id", state->message->logevent_id());
+ LOG(INFO) << "Outbound secret message [save_changes] finish " << tag("log_event_id", state->message->log_event_id());
state->save_changes_finish_flag = true;
outbound_loop(state, state_id);
}
@@ -1699,7 +1710,7 @@ void SecretChatActor::on_outbound_ack(uint64 state_id) {
}
auto *state = outbound_message_states_.get(state_id);
CHECK(state);
- LOG(INFO) << "Outbound secret message [ack] finish " << tag("logevent_id", state->message->logevent_id());
+ LOG(INFO) << "Outbound secret message [ack] finish " << tag("log_event_id", state->message->log_event_id());
state->ack_flag = true;
outbound_loop(state, state_id);
}
@@ -1711,11 +1722,11 @@ void SecretChatActor::on_outbound_outer_send_message_promise(uint64 state_id, Pr
}
auto *state = outbound_message_states_.get(state_id);
CHECK(state);
- LOG(INFO) << "Outbound secret message [TODO] " << tag("logevent_id", state->message->logevent_id());
+ LOG(INFO) << "Outbound secret message " << tag("log_event_id", state->message->log_event_id());
promise.set_value(Unit()); // Seems like this message is at least stored to binlog already
if (state->send_result_) {
state->send_result_({});
- } else {
+ } else if (state->message->is_sent) {
context_->on_send_message_error(state->message->random_id, Status::Error(400, "Message has already been sent"),
Auto());
}
@@ -1726,21 +1737,21 @@ void SecretChatActor::outbound_loop(OutboundMessageState *state, uint64 state_id
return;
}
if (state->save_changes_finish_flag /*&& state->send_message_finish_flag*/ && state->ack_flag) {
- LOG(INFO) << "Outbound message [remove_logevent] start " << tag("logevent_id", state->message->logevent_id());
- BinlogHelper::erase(context_->binlog(), state->message->logevent_id());
+ LOG(INFO) << "Outbound message [remove_log_event] start " << tag("log_event_id", state->message->log_event_id());
+ binlog_erase(context_->binlog(), state->message->log_event_id());
random_id_to_outbound_message_state_token_.erase(state->message->random_id);
- LOG(INFO) << "Outbound message finish (lazy) " << tag("logevent_id", state->message->logevent_id());
+ LOG(INFO) << "Outbound message finish (lazy) " << tag("log_event_id", state->message->log_event_id());
outbound_message_states_.erase(state_id);
return;
}
if (state->save_changes_finish_flag && state->send_message_finish_flag &&
- !state->message->is_sent) { // [rewrite_logevent]
- LOG(INFO) << "Outbound message [rewrite_logevent] start " << tag("logevent_id", state->message->logevent_id());
+ !state->message->is_sent) { // [rewrite_log_event]
+ LOG(INFO) << "Outbound message [rewrite_log_event] start " << tag("log_event_id", state->message->log_event_id());
state->message->is_sent = true;
- BinlogHelper::rewrite(context_->binlog(), state->message->logevent_id(), LogEvent::HandlerType::SecretChats,
- create_storer(*state->message));
+ binlog_rewrite(context_->binlog(), state->message->log_event_id(), LogEvent::HandlerType::SecretChats,
+ create_storer(*state->message));
}
}
@@ -1756,15 +1767,19 @@ Status SecretChatActor::save_common_info(T &update) {
Status SecretChatActor::on_update_chat(telegram_api::encryptedChatRequested &update) {
if (auth_state_.state != State::Empty) {
- LOG(WARNING) << "Unexpected ChatRequested ignored: " << to_string(update);
+ LOG(INFO) << "Unexpected encryptedChatRequested ignored: " << to_string(update);
return Status::OK();
}
auth_state_.state = State::SendAccept;
auth_state_.x = 1;
- auth_state_.user_id = update.admin_id_;
+ auth_state_.user_id = UserId(update.admin_id_);
auth_state_.date = context_->unix_time();
TRY_STATUS(save_common_info(update));
auth_state_.handshake.set_g_a(update.g_a_.as_slice());
+ if ((update.flags_ & telegram_api::encryptedChatRequested::FOLDER_ID_MASK) != 0) {
+ auth_state_.initial_folder_id = FolderId(update.folder_id_);
+ }
+
send_update_secret_chat();
return Status::OK();
}
@@ -1773,7 +1788,7 @@ Status SecretChatActor::on_update_chat(telegram_api::encryptedChatEmpty &update)
}
Status SecretChatActor::on_update_chat(telegram_api::encryptedChatWaiting &update) {
if (auth_state_.state != State::WaitRequestResponse && auth_state_.state != State::WaitAcceptResponse) {
- LOG(WARNING) << "Unexpected ChatWaiting ignored";
+ LOG(INFO) << "Unexpected encryptedChatWaiting ignored";
return Status::OK();
}
TRY_STATUS(save_common_info(update));
@@ -1782,13 +1797,13 @@ Status SecretChatActor::on_update_chat(telegram_api::encryptedChatWaiting &updat
}
Status SecretChatActor::on_update_chat(telegram_api::encryptedChat &update) {
if (auth_state_.state != State::WaitRequestResponse && auth_state_.state != State::WaitAcceptResponse) {
- LOG(WARNING) << "Unexpected Chat ignored";
+ LOG(INFO) << "Unexpected encryptedChat ignored";
return Status::OK();
}
TRY_STATUS(save_common_info(update));
if (auth_state_.state == State::WaitRequestResponse) {
auth_state_.handshake.set_g_a(update.g_a_or_b_.as_slice());
- TRY_STATUS(auth_state_.handshake.run_checks(context_->dh_callback()));
+ TRY_STATUS(auth_state_.handshake.run_checks(true, context_->dh_callback()));
auto id_and_key = auth_state_.handshake.gen_key();
pfs_state_.auth_key = mtproto::AuthKey(id_and_key.first, std::move(id_and_key.second));
calc_key_hash();
@@ -1797,22 +1812,23 @@ Status SecretChatActor::on_update_chat(telegram_api::encryptedChat &update) {
return Status::Error("Key fingerprint mismatch");
}
auth_state_.state = State::Ready;
- if (create_logevent_id_ != 0) {
- BinlogHelper::erase(context_->binlog(), create_logevent_id_);
- create_logevent_id_ = 0;
+ if (create_log_event_id_ != 0) {
+ binlog_erase(context_->binlog(), create_log_event_id_);
+ create_log_event_id_ = 0;
}
// NB: order is important
context_->secret_chat_db()->set_value(pfs_state_);
context_->secret_chat_db()->set_value(auth_state_);
- LOG(INFO) << "OK! Ready!";
send_update_secret_chat();
- send_action(secret_api::make_object<secret_api::decryptedMessageActionNotifyLayer>(MY_LAYER), SendFlag::None,
- Promise<>());
+ send_action(secret_api::make_object<secret_api::decryptedMessageActionNotifyLayer>(
+ static_cast<int32>(SecretChatLayer::Current)),
+ SendFlag::None, Promise<>());
return Status::OK();
}
Status SecretChatActor::on_update_chat(telegram_api::encryptedChatDiscarded &update) {
- return Status::Error("Chat discarded");
+ cancel_chat(update.history_deleted_, true, Promise<Unit>());
+ return Status::OK();
}
Status SecretChatActor::on_update_chat(NetQueryPtr query) {
@@ -1834,11 +1850,13 @@ Status SecretChatActor::on_update_chat(telegram_api::object_ptr<telegram_api::En
return res;
}
-// DH CONFIG
-// messages.dhConfigNotModified#c0e24635 random:bytes = messages.DhConfig;
-// messages.dhConfig#2c221edd g:int p:bytes version:int random:bytes = messages.DhConfig;
-//---functions---
-// messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig;
+Status SecretChatActor::on_read_history(NetQueryPtr query) {
+ if (query.generation() == read_history_query_.generation()) {
+ read_history_query_ = NetQueryRef();
+ read_history_promise_.set_value(Unit());
+ }
+ return Status::OK();
+}
void SecretChatActor::start_up() {
LOG(INFO) << "SecretChatActor: start_up";
@@ -1848,9 +1866,12 @@ void SecretChatActor::start_up() {
auth_state_ = r_auth_state.move_as_ok();
}
if (!can_be_empty_ && auth_state_.state == State::Empty) {
- LOG(WARNING) << "Close Secret chat because it is empty";
+ LOG(INFO) << "Skip creation of empty secret chat " << auth_state_.id;
return stop();
}
+ if (auth_state_.state == State::Closed) {
+ close_flag_ = true;
+ }
auto r_seq_no_state = context_->secret_chat_db()->get_value<SeqNoState>();
if (r_seq_no_state.is_ok()) {
seq_no_state_ = r_seq_no_state.move_as_ok();
@@ -1871,8 +1892,8 @@ void SecretChatActor::start_up() {
// auto end = Time::now();
// CHECK(end - start < 0.2);
- LOG(INFO) << "start_up with SeqNoState=" << seq_no_state_;
- LOG(INFO) << "start_up with PfsState=" << pfs_state_;
+ LOG(INFO) << "In start_up with SeqNoState " << seq_no_state_;
+ LOG(INFO) << "In start_up with PfsState " << pfs_state_;
}
void SecretChatActor::get_dh_config() {
@@ -1886,30 +1907,31 @@ void SecretChatActor::get_dh_config() {
}
auto version = auth_state_.dh_config.version;
- int random_length = 0;
- telegram_api::messages_getDhConfig tl_query(version, random_length);
-
- auto query = context_->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::DhConfig)), create_storer(tl_query));
+ int32 random_length = 256; // ignored server-side, always returns 256 random bytes
+ auto query = create_net_query(QueryType::DhConfig, telegram_api::messages_getDhConfig(version, random_length));
context_->send_net_query(std::move(query), actor_shared(this), false);
}
Status SecretChatActor::on_dh_config(NetQueryPtr query) {
- LOG(INFO) << "Got dh config";
+ LOG(INFO) << "Got DH config";
TRY_RESULT(config, fetch_result<telegram_api::messages_getDhConfig>(std::move(query)));
downcast_call(*config, [&](auto &obj) { this->on_dh_config(obj); });
+ TRY_STATUS(mtproto::DhHandshake::check_config(auth_state_.dh_config.g, auth_state_.dh_config.prime,
+ context_->dh_callback()));
auth_state_.handshake.set_config(auth_state_.dh_config.g, auth_state_.dh_config.prime);
return Status::OK();
}
+
void SecretChatActor::on_dh_config(telegram_api::messages_dhConfigNotModified &dh_not_modified) {
- // TODO: use random_
+ Random::add_seed(dh_not_modified.random_.as_slice());
}
+
void SecretChatActor::on_dh_config(telegram_api::messages_dhConfig &dh) {
auto dh_config = std::make_shared<DhConfig>();
dh_config->version = dh.version_;
dh_config->prime = dh.p_.as_slice().str();
dh_config->g = dh.g_;
- // TODO: use random_
+ Random::add_seed(dh.random_.as_slice());
auth_state_.dh_config = *dh_config;
context_->set_dh_config(dh_config);
}
@@ -1923,7 +1945,7 @@ void SecretChatActor::calc_key_hash() {
auto sha256_slice = MutableSlice(sha256_buf, 32);
sha256(pfs_state_.auth_key.key(), sha256_slice);
- auth_state_.key_hash = sha1_slice.truncate(16).str() + sha256_slice.truncate(20).str();
+ auth_state_.key_hash = PSTRING() << sha1_slice.substr(0, 16) << sha256_slice.substr(0, 20);
}
void SecretChatActor::send_update_secret_chat() {
@@ -1939,7 +1961,8 @@ void SecretChatActor::send_update_secret_chat() {
state = SecretChatState::Waiting;
}
context_->on_update_secret_chat(auth_state_.access_hash, get_user_id(), state, auth_state_.x == 0, config_state_.ttl,
- auth_state_.date, auth_state_.key_hash, current_layer());
+ auth_state_.date, auth_state_.key_hash, current_layer(),
+ auth_state_.initial_folder_id);
}
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionSetMessageTTL &set_ttl) {
@@ -1947,29 +1970,36 @@ void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionSetMe
context_->secret_chat_db()->set_value(config_state_);
send_update_secret_chat();
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionReadMessages &read_messages) {
// TODO
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionDeleteMessages &delete_messages) {
- // Corresponding logevent won't be deleted before promise returned by add_changes is set.
+ // Corresponding log event won't be deleted before promise returned by add_changes is set.
on_delete_messages(delete_messages.random_ids_).ensure();
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionScreenshotMessages &screenshot) {
- // noting to do
+ // nothing to do
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionFlushHistory &flush_history) {
on_flush_history(pfs_state_.message_id).ensure();
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionResend &resend) {
if (seq_no_state_.resend_end_seq_no < resend.end_seq_no_ / 2) { // replay protection
seq_no_state_.resend_end_seq_no = resend.end_seq_no_ / 2;
on_seq_no_state_changed();
}
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionNotifyLayer &notify_layer) {
config_state_.my_layer = notify_layer.layer_;
context_->secret_chat_db()->set_value(config_state_);
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionTyping &typing) {
// noop
}
@@ -1980,23 +2010,29 @@ Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionSetM
send_update_secret_chat();
return Status::OK();
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionReadMessages &read_messages) {
// TODO
return Status::OK();
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionDeleteMessages &delete_messages) {
return on_delete_messages(delete_messages.random_ids_);
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionScreenshotMessages &screenshot) {
// TODO
return Status::OK();
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionFlushHistory &screenshot) {
return on_flush_history(pfs_state_.message_id);
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionResend &resend) {
return Status::OK();
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionNotifyLayer &notify_layer) {
if (notify_layer.layer_ > config_state_.his_layer) {
config_state_.his_layer = notify_layer.layer_;
@@ -2005,6 +2041,7 @@ Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionNoti
}
return Status::OK();
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionTyping &typing) {
// noop
return Status::OK();
@@ -2012,20 +2049,23 @@ Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionTypi
// Perfect Forward Secrecy
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionRequestKey &request_key) {
- CHECK(pfs_state_.state == PfsState::WaitSendRequest || pfs_state_.state == PfsState::SendRequest);
+ LOG_CHECK(pfs_state_.state == PfsState::WaitSendRequest || pfs_state_.state == PfsState::SendRequest) << pfs_state_;
pfs_state_.state = PfsState::WaitRequestResponse;
on_pfs_state_changed();
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionAcceptKey &accept_key) {
CHECK(pfs_state_.state == PfsState::WaitSendAccept || pfs_state_.state == PfsState::SendAccept);
pfs_state_.state = PfsState::WaitAcceptResponse;
- pfs_state_.handshake = DhHandshake();
+ pfs_state_.handshake = mtproto::DhHandshake();
on_pfs_state_changed();
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionAbortKey &abort_key) {
// TODO
LOG(FATAL) << "TODO";
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionCommitKey &commit_key) {
CHECK(pfs_state_.state == PfsState::WaitSendCommit || pfs_state_.state == PfsState::SendCommit);
@@ -2040,15 +2080,15 @@ void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionCommi
on_pfs_state_changed();
}
+
void SecretChatActor::on_outbound_action(secret_api::decryptedMessageActionNoop &noop) {
// noop
}
-// decryptedMessageActionRequestKey#f3c9611b exchange_id:long g_a:bytes = DecryptedMessageAction;
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionRequestKey &request_key) {
if (pfs_state_.state == PfsState::WaitRequestResponse || pfs_state_.state == PfsState::SendRequest) {
if (pfs_state_.exchange_id > request_key.exchange_id_) {
- LOG(INFO) << "RequestKey: silently abort his request";
+ LOG(INFO) << "RequestKey: silently abort their request";
return Status::OK();
} else {
pfs_state_.state = PfsState::Empty;
@@ -2063,13 +2103,16 @@ Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionRequ
if (pfs_state_.state != PfsState::Empty) {
return Status::Error("Unexpected RequestKey");
}
- CHECK(pfs_state_.other_auth_key.empty()) << "TODO: got requestKey, before old key is dropped";
+ if (!pfs_state_.other_auth_key.empty()) {
+ LOG_CHECK(pfs_state_.can_forget_other_key) << "TODO: got requestKey, before old key is dropped";
+ return Status::Error("Unexpected RequestKey (old key is used)");
+ }
pfs_state_.state = PfsState::SendAccept;
- pfs_state_.handshake = DhHandshake();
+ pfs_state_.handshake = mtproto::DhHandshake();
pfs_state_.exchange_id = request_key.exchange_id_;
pfs_state_.handshake.set_config(auth_state_.dh_config.g, auth_state_.dh_config.prime);
pfs_state_.handshake.set_g_a(request_key.g_a_.as_slice());
- TRY_STATUS(pfs_state_.handshake.run_checks(context_->dh_callback()));
+ TRY_STATUS(pfs_state_.handshake.run_checks(true, context_->dh_callback()));
auto id_and_key = pfs_state_.handshake.gen_key();
pfs_state_.other_auth_key = mtproto::AuthKey(id_and_key.first, std::move(id_and_key.second));
@@ -2080,7 +2123,6 @@ Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionRequ
return Status::OK();
}
-// decryptedMessageActionAcceptKey#6fe1735b exchange_id:long g_b:bytes key_fingerprint:long = DecryptedMessageAction;
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionAcceptKey &accept_key) {
if (pfs_state_.state != PfsState::WaitRequestResponse) {
return Status::Error("AcceptKey: unexpected");
@@ -2089,13 +2131,13 @@ Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionAcce
return Status::Error("AcceptKey: exchange_id mismatch");
}
pfs_state_.handshake.set_g_a(accept_key.g_b_.as_slice());
- TRY_STATUS(pfs_state_.handshake.run_checks(context_->dh_callback()));
+ TRY_STATUS(pfs_state_.handshake.run_checks(true, context_->dh_callback()));
auto id_and_key = pfs_state_.handshake.gen_key();
if (static_cast<int64>(id_and_key.first) != accept_key.key_fingerprint_) {
return Status::Error("AcceptKey: key_fingerprint mismatch");
}
pfs_state_.state = PfsState::SendCommit;
- pfs_state_.handshake = DhHandshake();
+ pfs_state_.handshake = mtproto::DhHandshake();
CHECK(pfs_state_.can_forget_other_key || static_cast<int64>(pfs_state_.other_auth_key.id()) == id_and_key.first);
pfs_state_.other_auth_key = mtproto::AuthKey(id_and_key.first, std::move(id_and_key.second));
pfs_state_.can_forget_other_key = false;
@@ -2104,6 +2146,7 @@ Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionAcce
on_pfs_state_changed();
return Status::OK();
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionAbortKey &abort_key) {
if (pfs_state_.exchange_id != abort_key.exchange_id_) {
LOG(INFO) << "AbortKey: exchange_id mismatch: " << tag("my exchange_id", pfs_state_.exchange_id)
@@ -2114,11 +2157,12 @@ Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionAbor
return Status::Error("AbortKey: unexpected");
}
pfs_state_.state = PfsState::Empty;
- pfs_state_.handshake = DhHandshake();
+ pfs_state_.handshake = mtproto::DhHandshake();
on_pfs_state_changed();
return Status::OK();
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionCommitKey &commit_key) {
if (pfs_state_.state != PfsState::WaitAcceptResponse) {
return Status::Error("CommitKey: unexpected");
@@ -2142,6 +2186,7 @@ Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionComm
on_pfs_state_changed();
return Status::OK();
}
+
Status SecretChatActor::on_inbound_action(secret_api::decryptedMessageActionNoop &noop) {
// noop
return Status::OK();
@@ -2161,14 +2206,14 @@ Status SecretChatActor::on_inbound_action(secret_api::DecryptedMessageAction &ac
// Also, if SeqNoState with message_id greater than current message_id is not saved, then corresponding action will be
// replayed.
//
- // This works only for ttl, not for pfs. Same ttl action may be processed twice.
+ // This works only for TTL, not for PFS. Same TTL action may be processed twice.
if (message_id < seq_no_state_.message_id) {
- LOG(INFO) << "Drop old inbound DecryptedMessageAction (non-pfs action): " << to_string(action);
+ LOG(INFO) << "Drop old inbound DecryptedMessageAction (non-PFS action): " << to_string(action);
return Status::OK();
}
pfs_state_.message_id = message_id; // replay protection
- LOG(INFO) << "on_inbound_action: " << to_string(action);
+ LOG(INFO) << "In on_inbound_action: " << to_string(action);
Status res;
downcast_call(action, [&](auto &obj) { res = this->on_inbound_action(obj); });
return res;
@@ -2183,21 +2228,20 @@ void SecretChatActor::on_outbound_action(secret_api::DecryptedMessageAction &act
// see comment in on_inbound_action
if (message_id < seq_no_state_.message_id) {
- LOG(INFO) << "Drop old outbound DecryptedMessageAction (non-pfs action): " << to_string(action);
+ LOG(INFO) << "Drop old outbound DecryptedMessageAction (non-PFS action): " << to_string(action);
return;
}
pfs_state_.message_id = message_id; // replay protection
- LOG(INFO) << "on_outbound_action: " << to_string(action);
+ LOG(INFO) << "In on_outbound_action: " << to_string(action);
downcast_call(action, [&](auto &obj) { this->on_outbound_action(obj); });
}
-// decryptedMessageActionRequestKey#f3c9611b exchange_id:long g_a:bytes = DecryptedMessageAction;
void SecretChatActor::request_new_key() {
CHECK(!auth_state_.dh_config.empty());
pfs_state_.state = PfsState::SendRequest;
- pfs_state_.handshake = DhHandshake();
+ pfs_state_.handshake = mtproto::DhHandshake();
pfs_state_.handshake.set_config(auth_state_.dh_config.g, auth_state_.dh_config.prime);
pfs_state_.exchange_id = Random::secure_int64();
@@ -2208,11 +2252,12 @@ void SecretChatActor::request_new_key() {
void SecretChatActor::on_promise_error(Status error, string desc) {
if (context_->close_flag()) {
- // ignore
- LOG(ERROR) << "IGNORE";
+ LOG(DEBUG) << "Ignore " << tag("promise", desc) << error;
return;
}
LOG(FATAL) << "Failed: " << tag("promise", desc) << error;
}
+constexpr int32 SecretChatActor::MAX_RESEND_COUNT;
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.h b/protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.h
index 79f73981e1..34224a2da3 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretChatActor.h
@@ -1,34 +1,37 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/secret_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
-#include "td/mtproto/AuthKey.h"
-#include "td/mtproto/crypto.h"
-#include "td/mtproto/Transport.h"
-
#include "td/telegram/DhConfig.h"
+#include "td/telegram/EncryptedFile.h"
+#include "td/telegram/FolderId.h"
#include "td/telegram/logevent/SecretChatEvent.h"
#include "td/telegram/MessageId.h"
-#include "td/telegram/PtsManager.h"
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/secret_api.h"
#include "td/telegram/SecretChatDb.h"
#include "td/telegram/SecretChatId.h"
+#include "td/telegram/SecretChatLayer.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
+#include "td/mtproto/AuthKey.h"
+#include "td/mtproto/DhCallback.h"
+#include "td/mtproto/DhHandshake.h"
+
+#include "td/actor/actor.h"
+
#include "td/utils/buffer.h"
#include "td/utils/ChangesProcessor.h"
+#include "td/utils/common.h"
#include "td/utils/Container.h"
#include "td/utils/format.h"
#include "td/utils/port/Clocks.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
@@ -42,23 +45,19 @@
#include <utility>
namespace td {
+
class BinlogInterface;
-class DhCache;
class NetQueryCreator;
-class SequenceDispatcher;
-class SecretChatActor : public NetQueryCallback {
+class SecretChatActor final : public NetQueryCallback {
public:
- // do not change DEFAULT_LAYER, unless all it's usages are fixed
- enum : int32 { DEFAULT_LAYER = 46, VOICE_NOTES_LAYER = 66, MTPROTO_2_LAYER = 73, MY_LAYER = MTPROTO_2_LAYER };
-
class Context {
public:
Context() = default;
Context(const Context &) = delete;
Context &operator=(const Context &) = delete;
virtual ~Context() = default;
- virtual DhCallback *dh_callback() = 0;
+ virtual mtproto::DhCallback *dh_callback() = 0;
virtual BinlogInterface *binlog() = 0;
virtual SecretChatDb *secret_chat_db() = 0;
@@ -73,23 +72,23 @@ class SecretChatActor : public NetQueryCallback {
virtual bool close_flag() = 0;
// We don't want to expose the whole NetQueryDispatcher, MessagesManager and ContactsManager.
- // So it is more clear which parts of MessagesManager is really used. And it is much easier to create tests.
+ // So it is more clear which parts of MessagesManager are really used. And it is much easier to create tests.
virtual void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) = 0;
virtual void on_update_secret_chat(int64 access_hash, UserId user_id, SecretChatState state, bool is_outbound,
- int32 ttl, int32 date, string key_hash, int32 layer) = 0;
+ int32 ttl, int32 date, string key_hash, int32 layer,
+ FolderId initial_folder_id) = 0;
// Promise must be set only after the update is processed.
//
// For example, one may set promise, after update was sent to binlog. It is ok, because SecretChatsActor will delete
- // this update thought binlog too. So it wouldn't be deleted before update is saved.
+ // this update through binlog too. So it wouldn't be deleted before update is saved.
// inbound messages
- virtual void on_inbound_message(UserId user_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::encryptedFile> file,
+ virtual void on_inbound_message(UserId user_id, MessageId message_id, int32 date, unique_ptr<EncryptedFile> file,
tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise) = 0;
virtual void on_delete_messages(std::vector<int64> random_id, Promise<> promise) = 0;
- virtual void on_flush_history(MessageId message_id, Promise<> promise) = 0;
+ virtual void on_flush_history(bool remove_from_dialog_list, MessageId message_id, Promise<> promise) = 0;
virtual void on_read_message(int64 random_id, Promise<> promise) = 0;
virtual void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id,
Promise<> promise) = 0;
@@ -98,24 +97,25 @@ class SecretChatActor : public NetQueryCallback {
// outbound messages
virtual void on_send_message_ack(int64 random_id) = 0;
- virtual void on_send_message_ok(int64 random_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::EncryptedFile> file, Promise<> promise) = 0;
+ virtual void on_send_message_ok(int64 random_id, MessageId message_id, int32 date, unique_ptr<EncryptedFile> file,
+ Promise<> promise) = 0;
virtual void on_send_message_error(int64 random_id, Status error, Promise<> promise) = 0;
};
- SecretChatActor(int32 id, std::unique_ptr<Context> context, bool can_be_empty);
+ SecretChatActor(int32 id, unique_ptr<Context> context, bool can_be_empty);
- // First query to new chat must be on of this two
+ // First query to new chat must be one of these two
void update_chat(telegram_api::object_ptr<telegram_api::EncryptedChat> chat);
- void create_chat(int32 user_id, int64 user_access_hash, int32 random_id, Promise<SecretChatId> promise);
- void cancel_chat(Promise<> promise);
+ void create_chat(UserId user_id, int64 user_access_hash, int32 random_id, Promise<SecretChatId> promise);
+
+ void cancel_chat(bool delete_history, bool is_already_discarded, Promise<> promise);
// Inbound messages
// Logevent is created by SecretChatsManager, because it must contain qts
- void add_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message);
+ void add_inbound_message(unique_ptr<log_event::InboundSecretMessage> message);
// Outbound messages
- // Promise will be set just after correspoiding logevent will be SENT to binlog.
+ // Promise will be set just after corresponding log event will be SENT to binlog.
void send_message(tl_object_ptr<secret_api::DecryptedMessage> message,
tl_object_ptr<telegram_api::InputEncryptedFile> file, Promise<> promise);
void send_message_action(tl_object_ptr<secret_api::SendMessageAction> action);
@@ -129,18 +129,18 @@ class SecretChatActor : public NetQueryCallback {
void send_set_ttl_message(int32 ttl, int64 random_id, Promise<> promise);
// Binlog replay interface
- void replay_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message);
- void replay_outbound_message(std::unique_ptr<logevent::OutboundSecretMessage> message);
- void replay_close_chat(std::unique_ptr<logevent::CloseSecretChat> event);
- void replay_create_chat(std::unique_ptr<logevent::CreateSecretChat> event);
+ void replay_inbound_message(unique_ptr<log_event::InboundSecretMessage> message);
+ void replay_outbound_message(unique_ptr<log_event::OutboundSecretMessage> message);
+ void replay_close_chat(unique_ptr<log_event::CloseSecretChat> event);
+ void replay_create_chat(unique_ptr<log_event::CreateSecretChat> event);
void binlog_replay_finish();
private:
- enum class State { Empty, SendRequest, SendAccept, WaitRequestResponse, WaitAcceptResponse, Ready, Closed };
- enum { MAX_RESEND_COUNT = 1000 };
+ enum class State : int32 { Empty, SendRequest, SendAccept, WaitRequestResponse, WaitAcceptResponse, Ready, Closed };
+ static constexpr int32 MAX_RESEND_COUNT = 1000;
- // We have git state that should be shynchronized with db.
- // It is splitted into several parts because:
+ // We have git state that should be synchronized with the database.
+ // It is split into several parts because:
// 1. Some parts are BIG (auth_key, for example) and are rarely updated.
// 2. Other are frequently updated, so probably should be as small as possible.
// 3. Some parts must be updated atomically.
@@ -149,6 +149,7 @@ class SecretChatActor : public NetQueryCallback {
int32 my_in_seq_no = 0;
int32 my_out_seq_no = 0;
int32 his_in_seq_no = 0;
+ int32 his_layer = 0;
int32 resend_end_seq_no = -1;
@@ -157,11 +158,12 @@ class SecretChatActor : public NetQueryCallback {
}
template <class StorerT>
void store(StorerT &storer) const {
- storer.store_int(message_id);
+ storer.store_int(message_id | HAS_LAYER);
storer.store_int(my_in_seq_no);
storer.store_int(my_out_seq_no);
storer.store_int(his_in_seq_no);
storer.store_int(resend_end_seq_no);
+ storer.store_int(his_layer);
}
template <class ParserT>
@@ -171,7 +173,14 @@ class SecretChatActor : public NetQueryCallback {
my_out_seq_no = parser.fetch_int();
his_in_seq_no = parser.fetch_int();
resend_end_seq_no = parser.fetch_int();
+
+ bool has_layer = (message_id & HAS_LAYER) != 0;
+ if (has_layer) {
+ message_id &= static_cast<int32>(~HAS_LAYER);
+ his_layer = parser.fetch_int();
+ }
}
+ static constexpr uint32 HAS_LAYER = 1u << 31;
};
struct ConfigState {
@@ -198,7 +207,7 @@ class SecretChatActor : public NetQueryCallback {
ttl = parser.fetch_int();
bool has_flags = (his_layer & HAS_FLAGS) != 0;
if (has_flags) {
- his_layer &= ~HAS_FLAGS;
+ his_layer &= static_cast<int32>(~HAS_FLAGS);
my_layer = parser.fetch_int();
// for future usage
BEGIN_PARSE_FLAGS();
@@ -206,7 +215,7 @@ class SecretChatActor : public NetQueryCallback {
}
}
- enum { HAS_FLAGS = 1 << 31 };
+ static constexpr uint32 HAS_FLAGS = 1u << 31;
};
// PfsAction
@@ -234,7 +243,7 @@ class SecretChatActor : public NetQueryCallback {
int32 last_message_id = 0;
double last_timestamp = 0;
int32 last_out_seq_no = 0;
- DhHandshake handshake;
+ mtproto::DhHandshake handshake;
static Slice key() {
return Slice("pfs_state");
@@ -356,47 +365,56 @@ class SecretChatActor : public NetQueryCallback {
int32 id = 0;
int64 access_hash = 0;
- int32 user_id = 0;
+ UserId user_id;
int64 user_access_hash = 0;
int32 random_id = 0;
int32 date = 0;
+ FolderId initial_folder_id;
+
DhConfig dh_config;
- DhHandshake handshake;
+ mtproto::DhHandshake handshake;
static Slice key() {
return Slice("auth_state");
}
template <class StorerT>
void store(StorerT &storer) const {
- uint32 flags = 0;
- bool date_flag = date != 0;
- bool key_hash_flag = true;
- if (date_flag) {
+ uint32 flags = 8;
+ bool has_date = date != 0;
+ bool has_key_hash = true;
+ bool has_initial_folder_id = initial_folder_id != FolderId();
+ if (has_date) {
flags |= 1;
}
- if (key_hash_flag) {
+ if (has_key_hash) {
flags |= 2;
}
+ if (has_initial_folder_id) {
+ flags |= 4;
+ }
storer.store_int((flags << 8) | static_cast<int32>(state));
storer.store_int(x);
storer.store_int(id);
storer.store_long(access_hash);
- storer.store_int(user_id);
+ storer.store_long(user_id.get());
storer.store_long(user_access_hash);
storer.store_int(random_id);
- if (date_flag) {
+ if (has_date) {
storer.store_int(date);
}
- if (key_hash_flag) {
+ if (has_key_hash) {
storer.store_string(key_hash);
}
dh_config.store(storer);
if (state == State::SendRequest || state == State::WaitRequestResponse) {
handshake.store(storer);
}
+ if (has_initial_folder_id) {
+ initial_folder_id.store(storer);
+ }
}
template <class ParserT>
@@ -404,45 +422,54 @@ class SecretChatActor : public NetQueryCallback {
uint32 tmp = parser.fetch_int();
state = static_cast<State>(tmp & 255);
uint32 flags = tmp >> 8;
- bool date_flag = (flags & 1) != 0;
- bool key_hash_flag = (flags & 2) != 0;
+ bool has_date = (flags & 1) != 0;
+ bool has_key_hash = (flags & 2) != 0;
+ bool has_initial_folder_id = (flags & 4) != 0;
+ bool has_64bit_user_id = (flags & 8) != 0;
x = parser.fetch_int();
id = parser.fetch_int();
access_hash = parser.fetch_long();
- user_id = parser.fetch_int();
+ if (has_64bit_user_id) {
+ user_id = UserId(parser.fetch_long());
+ } else {
+ user_id = UserId(static_cast<int64>(parser.fetch_int()));
+ }
user_access_hash = parser.fetch_long();
random_id = parser.fetch_int();
- if (date_flag) {
+ if (has_date) {
date = parser.fetch_int();
}
- if (key_hash_flag) {
+ if (has_key_hash) {
key_hash = parser.template fetch_string<std::string>();
}
dh_config.parse(parser);
if (state == State::SendRequest || state == State::WaitRequestResponse) {
handshake.parse(parser);
}
+ if (has_initial_folder_id) {
+ initial_folder_id.parse(parser);
+ }
}
};
std::shared_ptr<SecretChatDb> db_;
- std::unique_ptr<Context> context_;
+ unique_ptr<Context> context_;
bool binlog_replay_finish_flag_ = false;
bool close_flag_ = false;
- LogEvent::Id close_logevent_id_ = 0;
+ Promise<Unit> discard_encryption_promise_;
- LogEvent::Id create_logevent_id_ = 0;
+ LogEvent::Id create_log_event_id_ = 0;
- enum class QueryType : uint8 { DhConfig, EncryptedChat, Message, Ignore, DiscardEncryption };
+ enum class QueryType : uint8 { DhConfig, EncryptedChat, Message, Ignore, DiscardEncryption, ReadHistory };
bool can_be_empty_;
AuthState auth_state_;
ConfigState config_state_;
- // Turns out, that all changes should be made made through StateChange.
+ // Turns out, that all changes should be made through StateChange.
//
// The problem is the time between the moment we made decision about change and
// the moment we actually apply the change to memory.
@@ -452,23 +479,24 @@ class SecretChatActor : public NetQueryCallback {
// This is completly flawed.
// (A-start_save_to_binlog ----> B-start_save_to_binlog+change_memory ----> A-finish_save_to_binlog+surprise)
//
- // Instead I suggest general solution that is already used with SeqNoState and Qts
- // 1. We APPLY CHANGE to memory immidiately AFTER correspoding EVENT is SENT to the binlog.
- // 2. We SEND CHANGE to db only after correspoiding EVENT is SAVED to the binlog.
- // 3. The we are able to ERASE EVENT just AFTER the CHANGE is SAVED to the binlog.
+ // Instead I suggest general solution that is already used with SeqNoState and qts
+ // 1. We APPLY CHANGE to memory immediately AFTER corresponding EVENT is SENT to the binlog.
+ // 2. We SEND CHANGE to database only after corresponding EVENT is SAVED to the binlog.
+ // 3. Then we are able to ERASE EVENT just AFTER the CHANGE is SAVED to the binlog.
//
- // Actually the change will be saved do binlog too.
- // So we can do it immidiatelly after EVENT is SENT to the binlog, because SEND CHANGE and ERASE EVENT will be
+ // Actually the change will be saved to binlog too.
+ // So we can do it immediatelly after EVENT is SENT to the binlog, because SEND CHANGE and ERASE EVENT will be
// ordered automatically.
//
- // We will use common ChangeProcessor for all changes (inside one SecretChatActor).
- // So all changes will be saved in exactly the same order as they are applied
+ // We will use common ChangesProcessor for all changes (inside one SecretChatActor).
+ // So all changes will be saved in exactly the same order as they are applied.
template <class StateT>
class Change {
public:
- Change() = default;
- explicit operator bool() const {
+ Change() : message_id() {
+ }
+ explicit operator bool() const noexcept {
return !data.empty();
}
explicit Change(const StateT &state) {
@@ -510,13 +538,13 @@ class SecretChatActor : public NetQueryCallback {
};
ChangesProcessor<StateChange> changes_processor_;
- int32 saved_pfs_state_message_id_;
+ int32 saved_pfs_state_message_id_ = 0;
SeqNoState seq_no_state_;
bool seq_no_state_changed_ = false;
int32 last_binlog_message_id_ = -1;
- Status check_seq_no(int in_seq_no, int out_seq_no) TD_WARN_UNUSED_RESULT;
+ Status check_seq_no(int in_seq_no, int out_seq_no, int32 his_layer) TD_WARN_UNUSED_RESULT;
void on_his_in_seq_no_updated();
void on_seq_no_state_changed();
@@ -531,19 +559,20 @@ class SecretChatActor : public NetQueryCallback {
struct InboundMessageState {
bool save_changes_finish = false;
bool save_message_finish = false;
- LogEvent::Id logevent_id = 0;
- int32 message_id;
+ LogEvent::Id log_event_id = 0;
+ int32 message_id = 0;
};
Container<InboundMessageState> inbound_message_states_;
- std::map<int32, std::unique_ptr<logevent::InboundSecretMessage>> pending_inbound_messages_;
+ std::map<int32, unique_ptr<log_event::InboundSecretMessage>> pending_inbound_messages_;
Result<std::tuple<uint64, BufferSlice, int32>> decrypt(BufferSlice &encrypted_message);
- Status do_inbound_message_encrypted(std::unique_ptr<logevent::InboundSecretMessage> message);
- Status do_inbound_message_decrypted_unchecked(std::unique_ptr<logevent::InboundSecretMessage> message);
- Status do_inbound_message_decrypted(std::unique_ptr<logevent::InboundSecretMessage> message);
- Status do_inbound_message_decrypted_pending(std::unique_ptr<logevent::InboundSecretMessage> message);
+ Status do_inbound_message_encrypted(unique_ptr<log_event::InboundSecretMessage> message);
+ Status do_inbound_message_decrypted_unchecked(unique_ptr<log_event::InboundSecretMessage> message,
+ int32 mtproto_version);
+ Status do_inbound_message_decrypted(unique_ptr<log_event::InboundSecretMessage> message);
+ void do_inbound_message_decrypted_pending(unique_ptr<log_event::InboundSecretMessage> message);
void on_inbound_save_message_finish(uint64 state_id);
void on_inbound_save_changes_finish(uint64 state_id);
@@ -551,7 +580,7 @@ class SecretChatActor : public NetQueryCallback {
// OutboundMessage
struct OutboundMessageState {
- std::unique_ptr<logevent::OutboundSecretMessage> message;
+ unique_ptr<log_event::OutboundSecretMessage> message;
Promise<> outer_send_message_finish;
Promise<> send_message_finish;
@@ -572,8 +601,14 @@ class SecretChatActor : public NetQueryCallback {
Container<OutboundMessageState> outbound_message_states_;
NetQueryRef set_typing_query_;
+ NetQueryRef read_history_query_;
+ int32 last_read_history_date_ = -1;
+ Promise<Unit> read_history_promise_;
+
+ template <class T>
+ NetQueryPtr create_net_query(QueryType type, const T &function);
- enum SendFlag {
+ enum SendFlag : int32 {
None = 0,
External = 1,
Push = 2,
@@ -583,11 +618,12 @@ class SecretChatActor : public NetQueryCallback {
void send_message_impl(tl_object_ptr<secret_api::DecryptedMessage> message,
tl_object_ptr<telegram_api::InputEncryptedFile> file, int32 flags, Promise<> promise);
- void do_outbound_message_impl(std::unique_ptr<logevent::OutboundSecretMessage>, Promise<> promise);
- Result<BufferSlice> create_encrypted_message(int32 layer, int32 my_in_seq_no, int32 my_out_seq_no,
+ void do_outbound_message_impl(unique_ptr<log_event::OutboundSecretMessage>, Promise<> promise);
+
+ Result<BufferSlice> create_encrypted_message(int32 my_in_seq_no, int32 my_out_seq_no,
tl_object_ptr<secret_api::DecryptedMessage> &message);
- NetQueryPtr create_net_query(const logevent::OutboundSecretMessage &message);
+ NetQueryPtr create_net_query(const log_event::OutboundSecretMessage &message);
void outbound_resend(uint64 state_id);
Status outbound_rewrite_with_empty(uint64 state_id);
@@ -601,21 +637,21 @@ class SecretChatActor : public NetQueryCallback {
void outbound_loop(OutboundMessageState *state, uint64 state_id);
// DiscardEncryption
- void on_fatal_error(Status status);
- void do_close_chat_impl(std::unique_ptr<logevent::CloseSecretChat> event);
- void on_discard_encryption_result(NetQueryPtr result);
+ void on_fatal_error(Status status, bool is_expected);
+ void do_close_chat_impl(bool delete_history, bool is_already_discarded, uint64 log_event_id, Promise<Unit> &&promise);
+ void on_closed(uint64 log_event_id, Promise<Unit> &&promise);
// Other
template <class T>
Status save_common_info(T &update);
int32 current_layer() const {
- int32 layer = MY_LAYER;
+ auto layer = static_cast<int32>(SecretChatLayer::Current);
if (config_state_.his_layer < layer) {
layer = config_state_.his_layer;
}
- if (layer < DEFAULT_LAYER) {
- layer = DEFAULT_LAYER;
+ if (layer < static_cast<int32>(SecretChatLayer::Default)) {
+ layer = static_cast<int32>(SecretChatLayer::Default);
}
return layer;
}
@@ -623,12 +659,12 @@ class SecretChatActor : public NetQueryCallback {
void ask_on_binlog_replay_finish();
void check_status(Status status);
- void start_up() override;
- void loop() override;
+ void start_up() final;
+ void loop() final;
Status do_loop();
- void tear_down() override;
+ void tear_down() final;
- void on_result_resendable(NetQueryPtr net_query, Promise<NetQueryPtr> promise) override;
+ void on_result_resendable(NetQueryPtr net_query, Promise<NetQueryPtr> promise) final;
Status run_auth();
void run_pfs();
@@ -649,6 +685,8 @@ class SecretChatActor : public NetQueryCallback {
Status on_update_chat(NetQueryPtr query) TD_WARN_UNUSED_RESULT;
Status on_update_chat(telegram_api::object_ptr<telegram_api::EncryptedChat> chat) TD_WARN_UNUSED_RESULT;
+ Status on_read_history(NetQueryPtr query) TD_WARN_UNUSED_RESULT;
+
void on_promise_error(Status error, string desc);
void get_dh_config();
@@ -656,13 +694,13 @@ class SecretChatActor : public NetQueryCallback {
void on_dh_config(telegram_api::messages_dhConfigNotModified &dh_not_modified);
void on_dh_config(telegram_api::messages_dhConfig &dh);
- void do_create_chat_impl(std::unique_ptr<logevent::CreateSecretChat> event);
+ void do_create_chat_impl(unique_ptr<log_event::CreateSecretChat> event);
SecretChatId get_secret_chat_id() {
return SecretChatId(auth_state_.id);
}
UserId get_user_id() {
- return UserId(auth_state_.user_id);
+ return auth_state_.user_id;
}
void send_update_ttl(int32 ttl);
void send_update_secret_chat();
@@ -673,4 +711,5 @@ class SecretChatActor : public NetQueryCallback {
<< tag("his_in_seq_no", state.his_in_seq_no) << "]";
}
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.cpp b/protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.cpp
index 0b3558c9fd..7f89126053 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.h b/protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.h
index df971777a2..3b28c37765 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretChatDb.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,16 +7,15 @@
#pragma once
#include "td/db/KeyValueSyncInterface.h"
-#include "td/db/Pmc.h"
-#include "td/db/SqliteDb.h"
-#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/tl_helpers.h"
#include <memory>
namespace td {
+
class SecretChatDb {
public:
SecretChatDb(std::shared_ptr<KeyValueSyncInterface> pmc, int32 chat_id);
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretChatId.h b/protocols/Telegram/tdlib/td/td/telegram/SecretChatId.h
index b0e9d6277d..3cfa786ea4 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SecretChatId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretChatId.h
@@ -1,14 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/StringBuilder.h"
-#include <functional>
#include <type_traits>
namespace td {
@@ -54,8 +55,8 @@ class SecretChatId {
};
struct SecretChatIdHash {
- std::size_t operator()(SecretChatId secret_chat_id) const {
- return std::hash<int32>()(secret_chat_id.get());
+ uint32 operator()(SecretChatId secret_chat_id) const {
+ return Hash<int32>()(secret_chat_id.get());
}
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretChatLayer.h b/protocols/Telegram/tdlib/td/td/telegram/SecretChatLayer.h
new file mode 100644
index 0000000000..7ed8bc225b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretChatLayer.h
@@ -0,0 +1,21 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+namespace td {
+
+enum class SecretChatLayer : int32 {
+ Default = 73,
+ Mtproto2 = 73,
+ NewEntities = 101,
+ DeleteMessagesOnClose = 123,
+ SupportBigFiles = 143,
+ SpoilerAndCustomEmojiEntities = 144,
+ Current = SpoilerAndCustomEmojiEntities
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.cpp
index 2ba15119ee..4883c09f42 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.cpp
@@ -1,49 +1,44 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/SecretChatsManager.h"
-#include "td/actor/PromiseFuture.h"
-
-#include "td/telegram/ConfigShared.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/DhCache.h"
+#include "td/telegram/EncryptedFile.h"
+#include "td/telegram/FolderId.h"
+#include "td/telegram/Global.h"
#include "td/telegram/logevent/SecretChatEvent.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/SequenceDispatcher.h"
-#include "td/telegram/Td.h"
-
-#include "td/telegram/secret_api.h"
+#include "td/telegram/StateManager.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
#include "td/telegram/telegram_api.hpp"
+#include "td/mtproto/DhCallback.h"
+
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/binlog/BinlogInterface.h"
+
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
-#include "td/utils/misc.h"
#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/Time.h"
#include <memory>
namespace td {
-//
-// qts and seq_no
-// Each EncryptedMessage (update_message) has qts.
-// Such updates must be handled in order of qts
-//
-// Qts should be handled on level of SecretChatsManager
-// 1. Each update can be received by SecretChatsManager multiple times.
-// 2. Each update should be sent to SecretChatActor only once. (Thought SecretChatActor mustn't rely it)
-// 3. Updates must be send in order of qts, without gaps.
-// 4. SecretChatActor must notify SecretChatManager when update is processed (saved in db)
-// 5. Only after all updates <= qts are processed by SecretChatActor, UpdatesManager should be
-// notified about new qts.
-//
+
// seq_no
// 1.
// x_in = 0 if we initiated secret chat.
@@ -74,9 +69,6 @@ namespace td {
// 5. Handling gaps.
// TODO
// Just fail chat.
-//
-//
-//
SecretChatsManager::SecretChatsManager(ActorShared<> parent) : parent_(std::move(parent)) {
}
@@ -86,18 +78,12 @@ void SecretChatsManager::start_up() {
dummy_mode_ = true;
return;
}
- // TODO: use db wrapper
- auto pmc = G()->td_db()->get_binlog_pmc();
- auto qts_str = pmc->get("updates.qts");
- if (!qts_str.empty()) {
- init_qts(to_integer<int32>(qts_str));
- }
- class StateCallback : public StateManager::Callback {
+ class StateCallback final : public StateManager::Callback {
public:
explicit StateCallback(ActorId<SecretChatsManager> parent) : parent_(std::move(parent)) {
}
- bool on_online(bool online_flag) override {
+ bool on_online(bool online_flag) final {
send_closure(parent_, &SecretChatsManager::on_online, online_flag);
return parent_.is_alive();
}
@@ -108,26 +94,7 @@ void SecretChatsManager::start_up() {
send_closure(G()->state_manager(), &StateManager::add_callback, make_unique<StateCallback>(actor_id(this)));
}
-void SecretChatsManager::init_qts(int qts) {
- if (dummy_mode_ || close_flag_) {
- return;
- }
- has_qts_ = true;
- qts_manager_.init(qts);
- LOG(INFO) << "Init secret chats qts " << tag("qts", qts);
-}
-
-void SecretChatsManager::update_qts(int qts) {
- if (dummy_mode_ || close_flag_ || qts < 0) {
- return;
- }
- LOG(INFO) << "update qts: " << qts;
- add_qts(qts).set_value(Unit());
- has_qts_ = true;
- LOG(INFO) << "Update secret chats qts " << tag("qts", qts);
-}
-
-void SecretChatsManager::create_chat(int32 user_id, int64 user_access_hash, Promise<SecretChatId> promise) {
+void SecretChatsManager::create_chat(UserId user_id, int64 user_access_hash, Promise<SecretChatId> promise) {
int32 random_id;
ActorId<SecretChatActor> actor;
do {
@@ -137,15 +104,15 @@ void SecretChatsManager::create_chat(int32 user_id, int64 user_access_hash, Prom
send_closure(actor, &SecretChatActor::create_chat, user_id, user_access_hash, random_id, std::move(promise));
}
-void SecretChatsManager::cancel_chat(SecretChatId secret_chat_id, Promise<> promise) {
+void SecretChatsManager::cancel_chat(SecretChatId secret_chat_id, bool delete_history, Promise<> promise) {
auto actor = get_chat_actor(secret_chat_id.get());
auto safe_promise = SafePromise<>(std::move(promise), Unit());
- send_closure(actor, &SecretChatActor::cancel_chat, std::move(safe_promise));
+ send_closure(actor, &SecretChatActor::cancel_chat, delete_history, false, std::move(safe_promise));
}
void SecretChatsManager::send_message(SecretChatId secret_chat_id, tl_object_ptr<secret_api::decryptedMessage> message,
tl_object_ptr<telegram_api::InputEncryptedFile> file, Promise<> promise) {
- // message->message_ = Random::fast(0, 1) ? string(1, static_cast<char>(0x80)) : "a";
+ // message->message_ = Random::fast_bool() ? string(1, static_cast<char>(0x80)) : "a";
auto actor = get_chat_actor(secret_chat_id.get());
auto safe_promise = SafePromise<>(std::move(promise), Status::Error(400, "Can't find secret chat"));
send_closure(actor, &SecretChatActor::send_message, std::move(message), std::move(file), std::move(safe_promise));
@@ -171,13 +138,13 @@ void SecretChatsManager::send_open_message(SecretChatId secret_chat_id, int64 ra
void SecretChatsManager::delete_messages(SecretChatId secret_chat_id, vector<int64> random_ids, Promise<> promise) {
auto actor = get_chat_actor(secret_chat_id.get());
- auto safe_promise = SafePromise<>(std::move(promise), Status::Error(400, "Can't find secret chat"));
+ auto safe_promise = SafePromise<>(std::move(promise), Unit());
send_closure(actor, &SecretChatActor::delete_messages, std::move(random_ids), std::move(safe_promise));
}
void SecretChatsManager::delete_all_messages(SecretChatId secret_chat_id, Promise<> promise) {
auto actor = get_chat_actor(secret_chat_id.get());
- auto safe_promise = SafePromise<>(std::move(promise), Status::Error(400, "Can't find secret chat"));
+ auto safe_promise = SafePromise<>(std::move(promise), Unit());
send_closure(actor, &SecretChatActor::delete_all_messages, std::move(safe_promise));
}
@@ -194,26 +161,12 @@ void SecretChatsManager::send_set_ttl_message(SecretChatId secret_chat_id, int32
send_closure(actor, &SecretChatActor::send_set_ttl_message, ttl, random_id, std::move(safe_promise));
}
-void SecretChatsManager::before_get_difference(int32 qts) {
- if (dummy_mode_ || close_flag_) {
- return;
- }
- last_get_difference_qts_ = qts;
- // We will receive all updates later than qts anyway.
-}
-
-void SecretChatsManager::after_get_difference() {
- if (dummy_mode_ || close_flag_) {
- return;
- }
-}
-
void SecretChatsManager::on_update_chat(tl_object_ptr<telegram_api::updateEncryption> update) {
if (dummy_mode_ || close_flag_) {
return;
}
bool chat_requested = update->chat_->get_id() == telegram_api::encryptedChatRequested::ID;
- pending_chat_updates_.push_back({Timestamp::in(chat_requested ? 1 : 0), std::move(update)});
+ pending_chat_updates_.emplace_back(Timestamp::in(chat_requested ? 1 : 0), std::move(update));
flush_pending_chat_updates();
}
@@ -226,88 +179,53 @@ void SecretChatsManager::do_update_chat(tl_object_ptr<telegram_api::updateEncryp
&SecretChatActor::update_chat, std::move(update->chat_));
}
-void SecretChatsManager::on_update_message(tl_object_ptr<telegram_api::updateNewEncryptedMessage> update,
- bool force_apply) {
+void SecretChatsManager::on_new_message(tl_object_ptr<telegram_api::EncryptedMessage> &&message_ptr,
+ Promise<Unit> &&promise) {
if (dummy_mode_ || close_flag_) {
return;
}
- // UpdatesManager MUST postpone updates during GetDifference
- auto qts = update->qts_;
- if (!force_apply) {
- if (!has_qts_) {
- LOG(INFO) << "Got update, don't know current qts. Force get_difference";
- force_get_difference();
- return;
- }
- if (qts <= last_get_difference_qts_) {
- LOG(WARNING) << "Got updates with " << tag("qts", qts) << " lower or equal than "
- << tag("last get difference qts", last_get_difference_qts_);
- force_get_difference();
- return;
- }
- auto mem_qts = qts_manager_.mem_pts();
- if (qts <= mem_qts) {
- LOG(WARNING) << "Duplicated update " << tag("qts", qts) << tag("mem_qts", mem_qts);
- return;
- }
- if (qts != mem_qts + 1) {
- LOG(WARNING) << "Got gap in qts " << mem_qts << " ... " << qts;
- force_get_difference();
- // TODO wait 1 second?
- return;
- }
- }
+ CHECK(message_ptr != nullptr);
- auto event = std::make_unique<logevent::InboundSecretMessage>();
- event->qts = qts;
- downcast_call(*update->message_, [&](auto &x) {
+ auto event = make_unique<log_event::InboundSecretMessage>();
+ event->promise = std::move(promise);
+ downcast_call(*message_ptr, [&](auto &x) {
event->chat_id = x.chat_id_;
event->date = x.date_;
event->encrypted_message = std::move(x.bytes_);
});
- if (update->message_->get_id() == telegram_api::encryptedMessage::ID) {
- auto message = move_tl_object_as<telegram_api::encryptedMessage>(update->message_);
- if (message->file_->get_id() == telegram_api::encryptedFile::ID) {
- auto file = move_tl_object_as<telegram_api::encryptedFile>(message->file_);
-
- event->file.id = file->id_;
- event->file.access_hash = file->access_hash_;
- event->file.size = file->size_;
- event->file.dc_id = file->dc_id_;
- event->file.key_fingerprint = file->key_fingerprint_;
-
- event->has_encrypted_file = true;
- }
+ if (message_ptr->get_id() == telegram_api::encryptedMessage::ID) {
+ auto message = move_tl_object_as<telegram_api::encryptedMessage>(message_ptr);
+ event->file = EncryptedFile::get_encrypted_file(std::move(message->file_));
}
add_inbound_message(std::move(event));
}
-Promise<> SecretChatsManager::add_qts(int32 qts) {
- auto id = qts_manager_.add_pts(qts);
- return PromiseCreator::event(self_closure(this, &SecretChatsManager::on_qts_ack, id));
-}
-
void SecretChatsManager::replay_binlog_event(BinlogEvent &&binlog_event) {
- auto r_message = logevent::SecretChatEvent::from_buffer_slice(binlog_event.data_as_buffer_slice());
+ if (dummy_mode_) {
+ binlog_erase(G()->td_db()->get_binlog(), binlog_event.id_);
+ return;
+ }
+ auto r_message = log_event::SecretChatEvent::from_buffer_slice(binlog_event.data_as_buffer_slice());
LOG_IF(FATAL, r_message.is_error()) << "Failed to deserialize event: " << r_message.error();
auto message = r_message.move_as_ok();
- message->set_logevent_id(binlog_event.id_);
+ message->set_log_event_id(binlog_event.id_);
LOG(INFO) << "Process binlog event " << *message;
switch (message->get_type()) {
- case logevent::SecretChatEvent::Type::InboundSecretMessage:
- return replay_inbound_message(std::unique_ptr<logevent::InboundSecretMessage>(
- static_cast<logevent::InboundSecretMessage *>(message.release())));
- case logevent::SecretChatEvent::Type::OutboundSecretMessage:
- return replay_outbound_message(std::unique_ptr<logevent::OutboundSecretMessage>(
- static_cast<logevent::OutboundSecretMessage *>(message.release())));
- case logevent::SecretChatEvent::Type::CloseSecretChat:
+ case log_event::SecretChatEvent::Type::InboundSecretMessage:
+ return replay_inbound_message(unique_ptr<log_event::InboundSecretMessage>(
+ static_cast<log_event::InboundSecretMessage *>(message.release())));
+ case log_event::SecretChatEvent::Type::OutboundSecretMessage:
+ return replay_outbound_message(unique_ptr<log_event::OutboundSecretMessage>(
+ static_cast<log_event::OutboundSecretMessage *>(message.release())));
+ case log_event::SecretChatEvent::Type::CloseSecretChat:
return replay_close_chat(
- std::unique_ptr<logevent::CloseSecretChat>(static_cast<logevent::CloseSecretChat *>(message.release())));
- case logevent::SecretChatEvent::Type::CreateSecretChat:
+ unique_ptr<log_event::CloseSecretChat>(static_cast<log_event::CloseSecretChat *>(message.release())));
+ case log_event::SecretChatEvent::Type::CreateSecretChat:
return replay_create_chat(
- std::unique_ptr<logevent::CreateSecretChat>(static_cast<logevent::CreateSecretChat *>(message.release())));
+ unique_ptr<log_event::CreateSecretChat>(static_cast<log_event::CreateSecretChat *>(message.release())));
+ default:
+ LOG(FATAL) << "Unknown log event type " << tag("type", format::as_hex(static_cast<int32>(message->get_type())));
}
- LOG(FATAL) << "Unknown logevent type " << tag("type", format::as_hex(static_cast<int32>(message->get_type())));
}
void SecretChatsManager::binlog_replay_finish() {
@@ -317,42 +235,40 @@ void SecretChatsManager::binlog_replay_finish() {
}
}
-void SecretChatsManager::replay_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message) {
- LOG(INFO) << "Process inbound secret message " << tag("qts", message->qts);
+void SecretChatsManager::replay_inbound_message(unique_ptr<log_event::InboundSecretMessage> message) {
+ LOG(INFO) << "Replay inbound secret message in chat " << message->chat_id;
auto actor = get_chat_actor(message->chat_id);
send_closure_later(actor, &SecretChatActor::replay_inbound_message, std::move(message));
}
-void SecretChatsManager::add_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message) {
- LOG(INFO) << "Process inbound secret message " << tag("qts", message->qts);
- message->qts_ack = add_qts(message->qts);
+
+void SecretChatsManager::add_inbound_message(unique_ptr<log_event::InboundSecretMessage> message) {
+ LOG(INFO) << "Process inbound secret message in chat " << message->chat_id;
auto actor = get_chat_actor(message->chat_id);
send_closure(actor, &SecretChatActor::add_inbound_message, std::move(message));
}
-void SecretChatsManager::replay_close_chat(std::unique_ptr<logevent::CloseSecretChat> message) {
- LOG(INFO) << "Process close secret chat " << tag("id", message->chat_id);
+void SecretChatsManager::replay_close_chat(unique_ptr<log_event::CloseSecretChat> message) {
+ LOG(INFO) << "Replay close secret chat " << message->chat_id;
auto actor = get_chat_actor(message->chat_id);
send_closure_later(actor, &SecretChatActor::replay_close_chat, std::move(message));
}
-void SecretChatsManager::replay_create_chat(std::unique_ptr<logevent::CreateSecretChat> message) {
- LOG(INFO) << "Process create secret chat " << tag("id", message->random_id);
+
+void SecretChatsManager::replay_create_chat(unique_ptr<log_event::CreateSecretChat> message) {
+ LOG(INFO) << "Replay create secret chat " << message->random_id;
auto actor = create_chat_actor(message->random_id);
send_closure_later(actor, &SecretChatActor::replay_create_chat, std::move(message));
}
-void SecretChatsManager::replay_outbound_message(std::unique_ptr<logevent::OutboundSecretMessage> message) {
+void SecretChatsManager::replay_outbound_message(unique_ptr<log_event::OutboundSecretMessage> message) {
+ LOG(INFO) << "Replay outbound secret message in chat " << message->chat_id;
+
auto actor = get_chat_actor(message->chat_id);
send_closure_later(actor, &SecretChatActor::replay_outbound_message, std::move(message));
}
-void SecretChatsManager::force_get_difference() {
- LOG(INFO) << "Force get difference";
- send_closure(G()->td(), &Td::force_get_difference);
-}
-
ActorId<SecretChatActor> SecretChatsManager::get_chat_actor(int32 id) {
return create_chat_actor_impl(id, false);
}
@@ -361,10 +277,10 @@ ActorId<SecretChatActor> SecretChatsManager::create_chat_actor(int32 id) {
return create_chat_actor_impl(id, true);
}
-std::unique_ptr<SecretChatActor::Context> SecretChatsManager::make_secret_chat_context(int32 id) {
- class Context : public SecretChatActor::Context {
+unique_ptr<SecretChatActor::Context> SecretChatsManager::make_secret_chat_context(int32 id) {
+ class Context final : public SecretChatActor::Context {
public:
- Context(int32 id, ActorShared<SecretChatsManager> parent, std::unique_ptr<SecretChatDb> secret_chat_db)
+ Context(int32 id, ActorShared<SecretChatsManager> parent, unique_ptr<SecretChatDb> secret_chat_db)
: secret_chat_id_(SecretChatId(id)), parent_(std::move(parent)), secret_chat_db_(std::move(secret_chat_db)) {
sequence_dispatcher_ = create_actor<SequenceDispatcher>("SecretChat SequenceDispatcher");
}
@@ -372,29 +288,29 @@ std::unique_ptr<SecretChatActor::Context> SecretChatsManager::make_secret_chat_c
Context &operator=(const Context &other) = delete;
Context(Context &&other) = delete;
Context &operator=(Context &&other) = delete;
- ~Context() override {
+ ~Context() final {
send_closure(std::move(sequence_dispatcher_), &SequenceDispatcher::close_silent);
}
- DhCallback *dh_callback() override {
+ mtproto::DhCallback *dh_callback() final {
return DhCache::instance();
}
- NetQueryCreator &net_query_creator() override {
+ NetQueryCreator &net_query_creator() final {
return G()->net_query_creator();
}
- BinlogInterface *binlog() override {
+ BinlogInterface *binlog() final {
return G()->td_db()->get_binlog();
}
- SecretChatDb *secret_chat_db() override {
+ SecretChatDb *secret_chat_db() final {
return secret_chat_db_.get();
}
- std::shared_ptr<DhConfig> dh_config() override {
+ std::shared_ptr<DhConfig> dh_config() final {
return G()->get_dh_config();
}
- void set_dh_config(std::shared_ptr<DhConfig> dh_config) override {
+ void set_dh_config(std::shared_ptr<DhConfig> dh_config) final {
G()->set_dh_config(std::move(dh_config));
}
- void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) override {
+ void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) final {
if (ordered) {
send_closure(sequence_dispatcher_, &SequenceDispatcher::send_with_callback, std::move(query),
std::move(callback));
@@ -403,75 +319,74 @@ std::unique_ptr<SecretChatActor::Context> SecretChatsManager::make_secret_chat_c
}
}
- bool get_config_option_boolean(const string &name) const override {
- return G()->shared_config().get_option_boolean(name);
+ bool get_config_option_boolean(const string &name) const final {
+ return G()->get_option_boolean(name);
}
- int32 unix_time() override {
+ int32 unix_time() final {
return G()->unix_time();
}
- bool close_flag() override {
+ bool close_flag() final {
return G()->close_flag();
}
void on_update_secret_chat(int64 access_hash, UserId user_id, SecretChatState state, bool is_outbound, int32 ttl,
- int32 date, string key_hash, int32 layer) override {
+ int32 date, string key_hash, int32 layer, FolderId initial_folder_id) final {
send_closure(G()->contacts_manager(), &ContactsManager::on_update_secret_chat, secret_chat_id_, access_hash,
- user_id, state, is_outbound, ttl, date, key_hash, layer);
+ user_id, state, is_outbound, ttl, date, key_hash, layer, initial_folder_id);
}
- void on_inbound_message(UserId user_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::encryptedFile> file,
- tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise) override {
- send_closure(G()->messages_manager(), &MessagesManager::on_get_secret_message, secret_chat_id_, user_id,
- message_id, date, std::move(file), std::move(message), std::move(promise));
+ void on_inbound_message(UserId user_id, MessageId message_id, int32 date, unique_ptr<EncryptedFile> file,
+ tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_get_secret_message, secret_chat_id_, user_id,
+ message_id, date, std::move(file), std::move(message), std::move(promise));
}
- void on_send_message_error(int64 random_id, Status error, Promise<> promise) override {
- send_closure(G()->messages_manager(), &MessagesManager::on_send_secret_message_error, random_id, std::move(error),
- std::move(promise));
+ void on_send_message_error(int64 random_id, Status error, Promise<> promise) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_send_secret_message_error, random_id,
+ std::move(error), std::move(promise));
}
- void on_send_message_ack(int64 random_id) override {
- send_closure(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
+ void on_send_message_ack(int64 random_id) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_send_message_get_quick_ack, random_id);
}
- void on_send_message_ok(int64 random_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::EncryptedFile> file, Promise<> promise) override {
- send_closure(G()->messages_manager(), &MessagesManager::on_send_secret_message_success, random_id, message_id,
- date, std::move(file), std::move(promise));
+ void on_send_message_ok(int64 random_id, MessageId message_id, int32 date, unique_ptr<EncryptedFile> file,
+ Promise<> promise) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_send_secret_message_success, random_id,
+ message_id, date, std::move(file), std::move(promise));
}
- void on_delete_messages(std::vector<int64> random_ids, Promise<> promise) override {
- send_closure(G()->messages_manager(), &MessagesManager::delete_secret_messages, secret_chat_id_,
- std::move(random_ids), std::move(promise));
+ void on_delete_messages(std::vector<int64> random_ids, Promise<> promise) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::delete_secret_messages, secret_chat_id_,
+ std::move(random_ids), std::move(promise));
}
- void on_flush_history(MessageId message_id, Promise<> promise) override {
- send_closure(G()->messages_manager(), &MessagesManager::delete_secret_chat_history, secret_chat_id_, message_id,
- std::move(promise));
+ void on_flush_history(bool remove_from_dialog_list, MessageId message_id, Promise<> promise) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::delete_secret_chat_history, secret_chat_id_,
+ remove_from_dialog_list, message_id, std::move(promise));
}
- void on_read_message(int64 random_id, Promise<> promise) override {
- send_closure(G()->messages_manager(), &MessagesManager::open_secret_message, secret_chat_id_, random_id,
- std::move(promise));
+ void on_read_message(int64 random_id, Promise<> promise) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::open_secret_message, secret_chat_id_, random_id,
+ std::move(promise));
}
void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id,
- Promise<> promise) override {
- send_closure(G()->messages_manager(), &MessagesManager::on_secret_chat_screenshot_taken, secret_chat_id_, user_id,
- message_id, date, random_id, std::move(promise));
+ Promise<> promise) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_secret_chat_screenshot_taken, secret_chat_id_,
+ user_id, message_id, date, random_id, std::move(promise));
}
void on_set_ttl(UserId user_id, MessageId message_id, int32 date, int32 ttl, int64 random_id,
- Promise<> promise) override {
- send_closure(G()->messages_manager(), &MessagesManager::on_secret_chat_ttl_changed, secret_chat_id_, user_id,
- message_id, date, ttl, random_id, std::move(promise));
+ Promise<> promise) final {
+ send_closure_later(G()->messages_manager(), &MessagesManager::on_secret_chat_ttl_changed, secret_chat_id_,
+ user_id, message_id, date, ttl, random_id, std::move(promise));
}
private:
SecretChatId secret_chat_id_;
ActorOwn<SequenceDispatcher> sequence_dispatcher_;
ActorShared<SecretChatsManager> parent_;
- std::unique_ptr<SecretChatDb> secret_chat_db_;
+ unique_ptr<SecretChatDb> secret_chat_db_;
};
- return std::make_unique<Context>(id, actor_shared(this, id),
- std::make_unique<SecretChatDb>(G()->td_db()->get_binlog_pmc_shared(), id));
+ return make_unique<Context>(id, actor_shared(this, id),
+ td::make_unique<SecretChatDb>(G()->td_db()->get_binlog_pmc_shared(), id));
}
ActorId<SecretChatActor> SecretChatsManager::create_chat_actor_impl(int32 id, bool can_be_empty) {
@@ -486,22 +401,8 @@ ActorId<SecretChatActor> SecretChatsManager::create_chat_actor_impl(int32 id, bo
if (binlog_replay_finish_flag_) {
send_closure(it_flag.first->second, &SecretChatActor::binlog_replay_finish);
}
- return it_flag.first->second.get();
- } else {
- return it_flag.first->second.get();
- }
-}
-void SecretChatsManager::on_qts_ack(PtsManager::PtsId qts_ack_token) {
- auto old_qts = qts_manager_.db_pts();
- auto new_qts = qts_manager_.finish(qts_ack_token);
- if (old_qts != new_qts) {
- save_qts();
}
-}
-
-void SecretChatsManager::save_qts() {
- LOG(INFO) << "Save " << tag("qts", qts_manager_.db_pts());
- send_closure(G()->td(), &Td::update_qts, qts_manager_.db_pts());
+ return it_flag.first->second.get();
}
void SecretChatsManager::hangup() {
@@ -510,7 +411,7 @@ void SecretChatsManager::hangup() {
return stop();
}
for (auto &it : id_to_actor_) {
- LOG(INFO) << "Ask close SecretChatActor " << tag("id", it.first);
+ LOG(INFO) << "Ask to close SecretChatActor " << tag("id", it.first);
it.second.reset();
}
if (id_to_actor_.empty()) {
@@ -522,13 +423,10 @@ void SecretChatsManager::hangup_shared() {
CHECK(!dummy_mode_);
auto token = get_link_token();
auto it = id_to_actor_.find(static_cast<int32>(token));
- if (it != id_to_actor_.end()) {
- LOG(INFO) << "Close SecretChatActor " << tag("id", it->first);
- it->second.release();
- id_to_actor_.erase(it);
- } else {
- LOG(FATAL) << "Unknown SecretChatActor hangup " << tag("id", static_cast<int32>(token));
- }
+ CHECK(it != id_to_actor_.end());
+ LOG(INFO) << "Close SecretChatActor " << tag("id", it->first);
+ it->second.release();
+ id_to_actor_.erase(it);
if (close_flag_ && id_to_actor_.empty()) {
stop();
}
@@ -545,7 +443,7 @@ void SecretChatsManager::flush_pending_chat_updates() {
auto it = pending_chat_updates_.begin();
while (it != pending_chat_updates_.end() && (it->first.is_in_past() || is_online_)) {
do_update_chat(std::move(it->second));
- it++;
+ ++it;
}
if (it != pending_chat_updates_.end()) {
set_timeout_at(it->first.at());
@@ -554,6 +452,10 @@ void SecretChatsManager::flush_pending_chat_updates() {
}
void SecretChatsManager::on_online(bool is_online) {
+ if (is_online_ == is_online) {
+ return;
+ }
+
is_online_ = is_online;
flush_pending_chat_updates();
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.h b/protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.h
index c05af851f4..554456ebcd 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretChatsManager.h
@@ -1,47 +1,41 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/SecretChatActor.h"
-
-#include "td/telegram/secret_api.h"
-#include "td/telegram/telegram_api.h"
-
#include "td/telegram/logevent/SecretChatEvent.h"
-#include "td/telegram/PtsManager.h"
+#include "td/telegram/secret_api.h"
+#include "td/telegram/SecretChatActor.h"
#include "td/telegram/SecretChatId.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
#include "td/actor/actor.h"
-#include "td/db/binlog/BinlogEvent.h"
-
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
#include "td/utils/Time.h"
#include <map>
#include <utility>
namespace td {
-class SecretChatsManager : public Actor {
+
+struct BinlogEvent;
+
+class SecretChatsManager final : public Actor {
public:
explicit SecretChatsManager(ActorShared<> parent);
- void init_qts(int32 qts);
- void update_qts(int32 qts);
- // we can forget all pending_updates after start_get_difference they will be received after this point anyway
- // It is not necessary, but it will help.
- void before_get_difference(int32 qts);
- void after_get_difference();
-
- // Proxy query to corrensponding SecretChatActor.
- // Look for more info in SecretChatActor.h
+
+ // proxy query to corresponding SecretChatActor
void on_update_chat(tl_object_ptr<telegram_api::updateEncryption> update);
- void on_update_message(tl_object_ptr<telegram_api::updateNewEncryptedMessage> update, bool force_apply);
+ void on_new_message(tl_object_ptr<telegram_api::EncryptedMessage> &&message_ptr, Promise<Unit> &&promise);
- void create_chat(int32 user_id, int64 user_access_hash, Promise<SecretChatId> promise);
- void cancel_chat(SecretChatId, Promise<> promise);
+ void create_chat(UserId user_id, int64 user_access_hash, Promise<SecretChatId> promise);
+ void cancel_chat(SecretChatId secret_chat_id, bool delete_history, Promise<> promise);
void send_message(SecretChatId secret_chat_id, tl_object_ptr<secret_api::decryptedMessage> message,
tl_object_ptr<telegram_api::InputEncryptedFile> file, Promise<> promise);
void send_message_action(SecretChatId secret_chat_id, tl_object_ptr<secret_api::SendMessageAction> action);
@@ -52,7 +46,7 @@ class SecretChatsManager : public Actor {
void notify_screenshot_taken(SecretChatId secret_chat_id, Promise<> promise);
void send_set_ttl_message(SecretChatId secret_chat_id, int32 ttl, int64 random_id, Promise<> promise);
- // Binlog replay
+ // binlog replay
void replay_binlog_event(BinlogEvent &&binlog_event);
void binlog_replay_finish();
@@ -60,38 +54,30 @@ class SecretChatsManager : public Actor {
bool binlog_replay_finish_flag_ = false;
bool dummy_mode_ = false;
bool close_flag_ = false;
- bool has_qts_ = false;
ActorShared<> parent_;
std::map<int32, ActorOwn<SecretChatActor>> id_to_actor_;
- PtsManager qts_manager_;
- int32 last_get_difference_qts_ = -1;
-
bool is_online_{false};
std::vector<std::pair<Timestamp, telegram_api::object_ptr<telegram_api::updateEncryption>>> pending_chat_updates_;
void flush_pending_chat_updates();
void do_update_chat(tl_object_ptr<telegram_api::updateEncryption> update);
- void replay_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message);
- void add_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message);
- void replay_outbound_message(std::unique_ptr<logevent::OutboundSecretMessage> message);
- void replay_close_chat(std::unique_ptr<logevent::CloseSecretChat> message);
- void replay_create_chat(std::unique_ptr<logevent::CreateSecretChat> message);
+ void replay_inbound_message(unique_ptr<log_event::InboundSecretMessage> message);
+ void add_inbound_message(unique_ptr<log_event::InboundSecretMessage> message);
+ void replay_outbound_message(unique_ptr<log_event::OutboundSecretMessage> message);
+ void replay_close_chat(unique_ptr<log_event::CloseSecretChat> message);
+ void replay_create_chat(unique_ptr<log_event::CreateSecretChat> message);
- std::unique_ptr<SecretChatActor::Context> make_secret_chat_context(int32 id);
+ unique_ptr<SecretChatActor::Context> make_secret_chat_context(int32 id);
ActorId<SecretChatActor> get_chat_actor(int32 id);
ActorId<SecretChatActor> create_chat_actor(int32 id);
ActorId<SecretChatActor> create_chat_actor_impl(int32 id, bool can_be_empty);
- Promise<> add_qts(int32 qts);
- void on_qts_ack(PtsManager::PtsId qts_ack_token);
- void save_qts();
- void force_get_difference();
-
- void start_up() override;
- void hangup() override;
- void hangup_shared() override;
- void timeout_expired() override;
+
+ void start_up() final;
+ void hangup() final;
+ void hangup_shared() final;
+ void timeout_expired() final;
void on_online(bool is_online);
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretInputMedia.cpp b/protocols/Telegram/tdlib/td/td/telegram/SecretInputMedia.cpp
new file mode 100644
index 0000000000..562b0404bb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretInputMedia.cpp
@@ -0,0 +1,40 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SecretInputMedia.h"
+
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/SecretChatLayer.h"
+
+#include "td/utils/misc.h"
+
+namespace td {
+
+SecretInputMedia::SecretInputMedia(tl_object_ptr<telegram_api::InputEncryptedFile> input_file, BufferSlice &&thumbnail,
+ Dimensions thumbnail_dimensions, const string &mime_type, const FileView &file_view,
+ vector<tl_object_ptr<secret_api::DocumentAttribute>> &&attributes,
+ const string &caption, int32 layer)
+ : input_file_(std::move(input_file)) {
+ auto &encryption_key = file_view.encryption_key();
+ auto size = file_view.size();
+ if (layer >= static_cast<int32>(SecretChatLayer::SupportBigFiles)) {
+ decrypted_media_ = secret_api::make_object<secret_api::decryptedMessageMediaDocument>(
+ std::move(thumbnail), thumbnail_dimensions.width, thumbnail_dimensions.height, mime_type, size,
+ BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()), std::move(attributes),
+ caption);
+ return;
+ }
+ if (size <= (2000 << 20)) {
+ decrypted_media_ = secret_api::make_object<secret_api::decryptedMessageMediaDocument46>(
+ std::move(thumbnail), thumbnail_dimensions.width, thumbnail_dimensions.height, mime_type,
+ narrow_cast<int32>(size), BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()),
+ std::move(attributes), caption);
+ return;
+ }
+ input_file_ = nullptr;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecretInputMedia.h b/protocols/Telegram/tdlib/td/td/telegram/SecretInputMedia.h
index 109343b8f6..5621e04c2f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SecretInputMedia.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecretInputMedia.h
@@ -1,16 +1,22 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/Dimensions.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+
namespace td {
+class FileView;
+
struct SecretInputMedia {
tl_object_ptr<telegram_api::InputEncryptedFile> input_file_;
tl_object_ptr<secret_api::DecryptedMessageMedia> decrypted_media_;
@@ -22,6 +28,11 @@ struct SecretInputMedia {
: input_file_(std::move(input_file)), decrypted_media_(std::move(decrypted_media)) {
}
+ SecretInputMedia(tl_object_ptr<telegram_api::InputEncryptedFile> input_file, BufferSlice &&thumbnail,
+ Dimensions thumbnail_dimensions, const string &mime_type, const FileView &file_view,
+ vector<tl_object_ptr<secret_api::DocumentAttribute>> &&attributes, const string &caption,
+ int32 layer);
+
bool empty() const {
return decrypted_media_ == nullptr;
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecureManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/SecureManager.cpp
new file mode 100644
index 0000000000..7933e8da01
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecureManager.cpp
@@ -0,0 +1,1332 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SecureManager.h"
+
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/PasswordManager.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/optional.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+
+#include <limits>
+#include <memory>
+
+namespace td {
+
+class GetSecureValue final : public NetQueryCallback {
+ public:
+ GetSecureValue(ActorShared<SecureManager> parent, std::string password, SecureValueType type,
+ Promise<SecureValueWithCredentials> promise);
+
+ private:
+ ActorShared<SecureManager> parent_;
+ string password_;
+ SecureValueType type_;
+ Promise<SecureValueWithCredentials> promise_;
+ optional<EncryptedSecureValue> encrypted_secure_value_;
+ optional<secure_storage::Secret> secret_;
+
+ void on_error(Status error);
+ void on_secret(Result<secure_storage::Secret> r_secret, bool dummy);
+ void loop() final;
+ void start_up() final;
+
+ void on_result(NetQueryPtr query) final;
+};
+
+class GetAllSecureValues final : public NetQueryCallback {
+ public:
+ GetAllSecureValues(ActorShared<SecureManager> parent, std::string password, Promise<TdApiSecureValues> promise);
+
+ private:
+ ActorShared<SecureManager> parent_;
+ string password_;
+ Promise<TdApiSecureValues> promise_;
+ optional<vector<EncryptedSecureValue>> encrypted_secure_values_;
+ optional<secure_storage::Secret> secret_;
+
+ void on_error(Status error);
+ void on_secret(Result<secure_storage::Secret> r_secret, bool dummy);
+ void loop() final;
+ void start_up() final;
+
+ void on_result(NetQueryPtr query) final;
+};
+
+class SetSecureValue final : public NetQueryCallback {
+ public:
+ SetSecureValue(ActorShared<SecureManager> parent, string password, SecureValue secure_value,
+ Promise<SecureValueWithCredentials> promise);
+
+ private:
+ ActorShared<SecureManager> parent_;
+ string password_;
+ SecureValue secure_value_;
+ Promise<SecureValueWithCredentials> promise_;
+ optional<secure_storage::Secret> secret_;
+
+ size_t files_left_to_upload_ = 0;
+ uint32 upload_generation_{0};
+ vector<SecureInputFile> files_to_upload_;
+ vector<SecureInputFile> translations_to_upload_;
+ optional<SecureInputFile> front_side_;
+ optional<SecureInputFile> reverse_side_;
+ optional<SecureInputFile> selfie_;
+
+ class UploadCallback;
+ std::shared_ptr<UploadCallback> upload_callback_;
+
+ enum class State : int32 { WaitSecret, WaitSetValue } state_ = State::WaitSecret;
+
+ class UploadCallback final : public FileManager::UploadCallback {
+ public:
+ UploadCallback(ActorId<SetSecureValue> actor_id, uint32 upload_generation);
+
+ private:
+ ActorId<SetSecureValue> actor_id_;
+ uint32 upload_generation_;
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final;
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final;
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final;
+ void on_upload_error(FileId file_id, Status error) final;
+ };
+
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file, uint32 upload_generation);
+ void on_upload_error(FileId file_id, Status error, uint32 upload_generation);
+
+ void on_error(Status error);
+
+ void on_secret(Result<secure_storage::Secret> r_secret, bool x);
+
+ void start_up() final;
+ void hangup() final;
+ void tear_down() final;
+
+ void loop() final;
+ void on_result(NetQueryPtr query) final;
+
+ void load_secret();
+ void cancel_upload();
+ void start_upload_all();
+ void start_upload(FileManager *file_manager, FileId &file_id, SecureInputFile &info);
+ static void merge(FileManager *file_manager, FileId file_id, EncryptedSecureFile &encrypted_file);
+};
+
+class SetSecureValueErrorsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit SetSecureValueErrorsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(tl_object_ptr<telegram_api::InputUser> input_user,
+ vector<tl_object_ptr<telegram_api::SecureValueError>> input_errors) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::users_setSecureValueErrors(std::move(input_user), std::move(input_errors))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::users_setSecureValueErrors>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool ptr = result_ptr.move_as_ok();
+ LOG(DEBUG) << "Receive result for SetSecureValueErrorsQuery: " << ptr;
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (status.code() != 0) {
+ promise_.set_error(std::move(status));
+ } else {
+ promise_.set_error(Status::Error(400, status.message()));
+ }
+ }
+};
+
+GetSecureValue::GetSecureValue(ActorShared<SecureManager> parent, std::string password, SecureValueType type,
+ Promise<SecureValueWithCredentials> promise)
+ : parent_(std::move(parent)), password_(std::move(password)), type_(type), promise_(std::move(promise)) {
+}
+
+void GetSecureValue::on_error(Status error) {
+ if (error.message() == "SECURE_SECRET_REQUIRED") {
+ send_closure(G()->password_manager(), &PasswordManager::drop_cached_secret);
+ }
+ if (error.code() > 0) {
+ promise_.set_error(std::move(error));
+ } else {
+ promise_.set_error(Status::Error(400, error.message()));
+ }
+ stop();
+}
+
+void GetSecureValue::on_secret(Result<secure_storage::Secret> r_secret, bool dummy) {
+ if (r_secret.is_error()) {
+ if (!G()->is_expected_error(r_secret.error())) {
+ LOG(ERROR) << "Receive error instead of secret: " << r_secret.error();
+ }
+ return on_error(r_secret.move_as_error());
+ }
+ secret_ = r_secret.move_as_ok();
+ loop();
+}
+
+void GetSecureValue::loop() {
+ if (!encrypted_secure_value_ || !secret_) {
+ return;
+ }
+
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ auto r_secure_value = decrypt_secure_value(file_manager, *secret_, *encrypted_secure_value_);
+ if (r_secure_value.is_error()) {
+ return on_error(r_secure_value.move_as_error());
+ }
+
+ send_closure(parent_, &SecureManager::on_get_secure_value, r_secure_value.ok());
+
+ promise_.set_value(r_secure_value.move_as_ok());
+ stop();
+}
+
+void GetSecureValue::start_up() {
+ std::vector<telegram_api::object_ptr<telegram_api::SecureValueType>> types;
+ types.push_back(get_input_secure_value_type(type_));
+
+ auto query = G()->net_query_creator().create(telegram_api::account_getSecureValue(std::move(types)));
+
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this));
+
+ send_closure(G()->password_manager(), &PasswordManager::get_secure_secret, password_,
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<secure_storage::Secret> r_secret) {
+ send_closure(actor_id, &GetSecureValue::on_secret, std::move(r_secret), true);
+ }));
+}
+
+void GetSecureValue::on_result(NetQueryPtr query) {
+ auto r_result = fetch_result<telegram_api::account_getSecureValue>(std::move(query));
+ if (r_result.is_error()) {
+ return on_error(r_result.move_as_error());
+ }
+ auto result = r_result.move_as_ok();
+ if (result.empty()) {
+ return on_error(Status::Error(404, "Not Found"));
+ }
+ if (result.size() != 1) {
+ return on_error(Status::Error(PSLICE() << "Expected vector of size 1 got " << result.size()));
+ }
+ encrypted_secure_value_ =
+ get_encrypted_secure_value(G()->td().get_actor_unsafe()->file_manager_.get(), std::move(result[0]));
+ if (encrypted_secure_value_.value().type == SecureValueType::None) {
+ return on_error(Status::Error(404, "Not Found"));
+ }
+ loop();
+}
+
+GetAllSecureValues::GetAllSecureValues(ActorShared<SecureManager> parent, std::string password,
+ Promise<TdApiSecureValues> promise)
+ : parent_(std::move(parent)), password_(std::move(password)), promise_(std::move(promise)) {
+}
+
+void GetAllSecureValues::on_error(Status error) {
+ if (error.message() == "SECURE_SECRET_REQUIRED") {
+ send_closure(G()->password_manager(), &PasswordManager::drop_cached_secret);
+ }
+ if (error.code() > 0) {
+ promise_.set_error(std::move(error));
+ } else {
+ promise_.set_error(Status::Error(400, error.message()));
+ }
+ stop();
+}
+
+void GetAllSecureValues::on_secret(Result<secure_storage::Secret> r_secret, bool dummy) {
+ if (r_secret.is_error()) {
+ if (!G()->is_expected_error(r_secret.error())) {
+ LOG(ERROR) << "Receive error instead of secret: " << r_secret.error();
+ }
+ return on_error(r_secret.move_as_error());
+ }
+ secret_ = r_secret.move_as_ok();
+ loop();
+}
+
+void GetAllSecureValues::loop() {
+ if (!encrypted_secure_values_ || !secret_) {
+ return;
+ }
+
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ auto r_secure_values = decrypt_secure_values(file_manager, *secret_, *encrypted_secure_values_);
+ if (r_secure_values.is_error()) {
+ return on_error(r_secure_values.move_as_error());
+ }
+
+ for (auto &secure_value : r_secure_values.ok()) {
+ send_closure(parent_, &SecureManager::on_get_secure_value, secure_value);
+ }
+
+ auto secure_values = transform(r_secure_values.move_as_ok(),
+ [](SecureValueWithCredentials &&value) { return std::move(value.value); });
+ promise_.set_value(get_passport_elements_object(file_manager, secure_values));
+ stop();
+}
+
+void GetAllSecureValues::start_up() {
+ auto query = G()->net_query_creator().create(telegram_api::account_getAllSecureValues());
+
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this));
+
+ send_closure(G()->password_manager(), &PasswordManager::get_secure_secret, password_,
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<secure_storage::Secret> r_secret) {
+ send_closure(actor_id, &GetAllSecureValues::on_secret, std::move(r_secret), true);
+ }));
+}
+
+void GetAllSecureValues::on_result(NetQueryPtr query) {
+ auto r_result = fetch_result<telegram_api::account_getAllSecureValues>(std::move(query));
+ if (r_result.is_error()) {
+ return on_error(r_result.move_as_error());
+ }
+ encrypted_secure_values_ =
+ get_encrypted_secure_values(G()->td().get_actor_unsafe()->file_manager_.get(), r_result.move_as_ok());
+ loop();
+}
+
+SetSecureValue::SetSecureValue(ActorShared<SecureManager> parent, string password, SecureValue secure_value,
+ Promise<SecureValueWithCredentials> promise)
+ : parent_(std::move(parent))
+ , password_(std::move(password))
+ , secure_value_(std::move(secure_value))
+ , promise_(std::move(promise)) {
+}
+
+SetSecureValue::UploadCallback::UploadCallback(ActorId<SetSecureValue> actor_id, uint32 upload_generation)
+ : actor_id_(actor_id), upload_generation_(upload_generation) {
+}
+
+void SetSecureValue::UploadCallback::on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) {
+ CHECK(input_file == nullptr);
+ send_closure_later(actor_id_, &SetSecureValue::on_upload_ok, file_id, nullptr, upload_generation_);
+}
+
+void SetSecureValue::UploadCallback::on_upload_encrypted_ok(
+ FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) {
+ UNREACHABLE();
+}
+
+void SetSecureValue::UploadCallback::on_upload_secure_ok(FileId file_id,
+ tl_object_ptr<telegram_api::InputSecureFile> input_file) {
+ send_closure_later(actor_id_, &SetSecureValue::on_upload_ok, file_id, std::move(input_file), upload_generation_);
+}
+
+void SetSecureValue::UploadCallback::on_upload_error(FileId file_id, Status error) {
+ send_closure_later(actor_id_, &SetSecureValue::on_upload_error, file_id, std::move(error), upload_generation_);
+}
+
+void SetSecureValue::on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file,
+ uint32 upload_generation) {
+ if (upload_generation_ != upload_generation) {
+ return;
+ }
+ SecureInputFile *info_ptr = nullptr;
+ for (auto &info : files_to_upload_) {
+ if (info.file_id == file_id) {
+ info_ptr = &info;
+ break;
+ }
+ }
+ for (auto &info : translations_to_upload_) {
+ if (info.file_id == file_id) {
+ info_ptr = &info;
+ break;
+ }
+ }
+ if (front_side_ && front_side_.value().file_id == file_id) {
+ info_ptr = &front_side_.value();
+ }
+ if (reverse_side_ && reverse_side_.value().file_id == file_id) {
+ info_ptr = &reverse_side_.value();
+ }
+ if (selfie_ && selfie_.value().file_id == file_id) {
+ info_ptr = &selfie_.value();
+ }
+ CHECK(info_ptr);
+ auto &info = *info_ptr;
+ CHECK(!info.input_file);
+ info.input_file = std::move(input_file);
+ CHECK(files_left_to_upload_ != 0);
+ files_left_to_upload_--;
+ loop();
+}
+
+void SetSecureValue::on_upload_error(FileId file_id, Status error, uint32 upload_generation) {
+ if (upload_generation_ != upload_generation) {
+ return;
+ }
+ on_error(std::move(error));
+}
+
+void SetSecureValue::on_error(Status error) {
+ if (error.code() > 0) {
+ promise_.set_error(std::move(error));
+ } else {
+ promise_.set_error(Status::Error(400, error.message()));
+ }
+ stop();
+}
+
+void SetSecureValue::on_secret(Result<secure_storage::Secret> r_secret, bool x) {
+ if (r_secret.is_error()) {
+ if (!G()->is_expected_error(r_secret.error())) {
+ LOG(ERROR) << "Receive error instead of secret: " << r_secret.error();
+ }
+ return on_error(r_secret.move_as_error());
+ }
+ secret_ = r_secret.move_as_ok();
+ loop();
+}
+
+void SetSecureValue::start_up() {
+ load_secret();
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+
+ // Remove duplicate files
+ FileId front_side_file_id;
+ if (secure_value_.front_side.file_id.is_valid()) {
+ front_side_file_id = file_manager->get_file_view(secure_value_.front_side.file_id).get_main_file_id();
+ front_side_ = SecureInputFile();
+ }
+ FileId reverse_side_file_id;
+ if (secure_value_.reverse_side.file_id.is_valid()) {
+ reverse_side_file_id = file_manager->get_file_view(secure_value_.reverse_side.file_id).get_main_file_id();
+ reverse_side_ = SecureInputFile();
+ if (front_side_file_id == reverse_side_file_id) {
+ return on_error(Status::Error(400, "Front side and reverse side must be different"));
+ }
+ }
+ FileId selfie_file_id;
+ if (secure_value_.selfie.file_id.is_valid()) {
+ selfie_file_id = file_manager->get_file_view(secure_value_.selfie.file_id).get_main_file_id();
+ selfie_ = SecureInputFile();
+ if (front_side_file_id == selfie_file_id) {
+ return on_error(Status::Error(400, "Front side and selfie must be different"));
+ }
+ if (reverse_side_file_id == selfie_file_id) {
+ return on_error(Status::Error(400, "Reverse side and selfie must be different"));
+ }
+ }
+
+ if (!secure_value_.files.empty()) {
+ CHECK(!front_side_file_id.is_valid());
+ CHECK(!reverse_side_file_id.is_valid());
+ CHECK(!selfie_file_id.is_valid());
+ for (auto it = secure_value_.files.begin(); it != secure_value_.files.end();) {
+ auto file_id = file_manager->get_file_view(it->file_id).get_main_file_id();
+ bool is_duplicate = false;
+ for (auto other_it = secure_value_.files.begin(); other_it != it; ++other_it) {
+ if (file_id == file_manager->get_file_view(other_it->file_id).get_main_file_id()) {
+ is_duplicate = true;
+ break;
+ }
+ }
+ if (is_duplicate) {
+ it = secure_value_.files.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ if (!secure_value_.translations.empty()) {
+ for (auto it = secure_value_.translations.begin(); it != secure_value_.translations.end();) {
+ auto file_id = file_manager->get_file_view(it->file_id).get_main_file_id();
+ bool is_duplicate = file_id == front_side_file_id || file_id == reverse_side_file_id || file_id == selfie_file_id;
+ for (auto other_it = secure_value_.translations.begin(); other_it != it; ++other_it) {
+ if (file_id == file_manager->get_file_view(other_it->file_id).get_main_file_id()) {
+ is_duplicate = true;
+ break;
+ }
+ }
+ for (auto &dated_file : secure_value_.files) {
+ if (file_id == file_manager->get_file_view(dated_file.file_id).get_main_file_id()) {
+ is_duplicate = true;
+ break;
+ }
+ }
+ if (is_duplicate) {
+ it = secure_value_.translations.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+
+ start_upload_all();
+}
+
+void SetSecureValue::load_secret() {
+ secret_ = {};
+ send_closure(G()->password_manager(), &PasswordManager::get_secure_secret, password_,
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<secure_storage::Secret> r_secret) {
+ send_closure(actor_id, &SetSecureValue::on_secret, std::move(r_secret), true);
+ }));
+}
+
+void SetSecureValue::cancel_upload() {
+ upload_generation_++;
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ if (file_manager == nullptr) {
+ return;
+ }
+ for (auto &file_info : files_to_upload_) {
+ file_manager->cancel_upload(file_info.file_id);
+ }
+ for (auto &file_info : translations_to_upload_) {
+ file_manager->cancel_upload(file_info.file_id);
+ }
+ if (front_side_) {
+ file_manager->cancel_upload(front_side_.value().file_id);
+ }
+ if (reverse_side_) {
+ file_manager->cancel_upload(reverse_side_.value().file_id);
+ }
+ if (selfie_) {
+ file_manager->cancel_upload(selfie_.value().file_id);
+ }
+ files_left_to_upload_ = 0;
+}
+
+void SetSecureValue::start_upload_all() {
+ if (files_left_to_upload_ != 0) {
+ cancel_upload();
+ }
+ upload_generation_++;
+ upload_callback_ = std::make_shared<UploadCallback>(actor_id(this), upload_generation_);
+
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ files_to_upload_.resize(secure_value_.files.size());
+ for (size_t i = 0; i < files_to_upload_.size(); i++) {
+ start_upload(file_manager, secure_value_.files[i].file_id, files_to_upload_[i]);
+ }
+ translations_to_upload_.resize(secure_value_.translations.size());
+ for (size_t i = 0; i < translations_to_upload_.size(); i++) {
+ start_upload(file_manager, secure_value_.translations[i].file_id, translations_to_upload_[i]);
+ }
+ if (front_side_) {
+ start_upload(file_manager, secure_value_.front_side.file_id, front_side_.value());
+ }
+ if (reverse_side_) {
+ start_upload(file_manager, secure_value_.reverse_side.file_id, reverse_side_.value());
+ }
+ if (selfie_) {
+ start_upload(file_manager, secure_value_.selfie.file_id, selfie_.value());
+ }
+}
+
+void SetSecureValue::start_upload(FileManager *file_manager, FileId &file_id, SecureInputFile &info) {
+ auto file_view = file_manager->get_file_view(file_id);
+ bool force = false;
+ if (info.file_id.empty()) {
+ if (!file_view.is_encrypted_secure()) {
+ auto download_file_id = file_manager->dup_file_id(file_id, "SetSecureValue");
+ file_id =
+ file_manager
+ ->register_generate(FileType::SecureEncrypted, FileLocationSource::FromServer, file_view.suggested_path(),
+ PSTRING() << "#file_id#" << download_file_id.get(), DialogId(), file_view.size())
+ .ok();
+ }
+
+ info.file_id = file_manager->dup_file_id(file_id, "SetSecureValue");
+ } else {
+ force = true;
+ }
+ file_manager->resume_upload(info.file_id, {}, upload_callback_, 1, 0, force);
+ files_left_to_upload_++;
+}
+
+void SetSecureValue::loop() {
+ if (state_ == State::WaitSecret) {
+ if (!secret_) {
+ return;
+ }
+ if (files_left_to_upload_ != 0) {
+ return;
+ }
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ auto input_secure_value =
+ get_input_secure_value_object(file_manager, encrypt_secure_value(file_manager, *secret_, secure_value_),
+ files_to_upload_, front_side_, reverse_side_, selfie_, translations_to_upload_);
+ auto save_secure_value =
+ telegram_api::account_saveSecureValue(std::move(input_secure_value), secret_.value().get_hash());
+ auto query = G()->net_query_creator().create(save_secure_value);
+
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this));
+ state_ = State::WaitSetValue;
+ }
+}
+
+void SetSecureValue::hangup() {
+ on_error(Status::Error(406, "Request canceled"));
+}
+
+void SetSecureValue::tear_down() {
+ cancel_upload();
+}
+
+void SetSecureValue::on_result(NetQueryPtr query) {
+ auto r_result = fetch_result<telegram_api::account_saveSecureValue>(std::move(query));
+ if (r_result.is_error()) {
+ if (r_result.error().message() == "SECURE_SECRET_REQUIRED") {
+ state_ = State::WaitSecret;
+ send_closure(G()->password_manager(), &PasswordManager::drop_cached_secret);
+ load_secret();
+ return loop();
+ }
+ if (r_result.error().message() == "SECURE_SECRET_INVALID") {
+ state_ = State::WaitSecret;
+ start_upload_all();
+ return loop();
+ }
+ return on_error(r_result.move_as_error());
+ }
+ auto result = r_result.move_as_ok();
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ auto encrypted_secure_value = get_encrypted_secure_value(file_manager, std::move(result));
+ if (encrypted_secure_value.type == SecureValueType::None) {
+ return on_error(Status::Error(500, "Receive invalid Telegram Passport element"));
+ }
+ if (secure_value_.files.size() != encrypted_secure_value.files.size()) {
+ return on_error(Status::Error(500, "Different file count"));
+ }
+ for (size_t i = 0; i < secure_value_.files.size(); i++) {
+ merge(file_manager, secure_value_.files[i].file_id, encrypted_secure_value.files[i]);
+ }
+ if (secure_value_.front_side.file_id.is_valid() && encrypted_secure_value.front_side.file.file_id.is_valid()) {
+ merge(file_manager, secure_value_.front_side.file_id, encrypted_secure_value.front_side);
+ }
+ if (secure_value_.reverse_side.file_id.is_valid() && encrypted_secure_value.reverse_side.file.file_id.is_valid()) {
+ merge(file_manager, secure_value_.reverse_side.file_id, encrypted_secure_value.reverse_side);
+ }
+ if (secure_value_.selfie.file_id.is_valid() && encrypted_secure_value.selfie.file.file_id.is_valid()) {
+ merge(file_manager, secure_value_.selfie.file_id, encrypted_secure_value.selfie);
+ }
+ for (size_t i = 0; i < secure_value_.translations.size(); i++) {
+ merge(file_manager, secure_value_.translations[i].file_id, encrypted_secure_value.translations[i]);
+ }
+ auto r_secure_value = decrypt_secure_value(file_manager, *secret_, encrypted_secure_value);
+ if (r_secure_value.is_error()) {
+ return on_error(r_secure_value.move_as_error());
+ }
+
+ send_closure(parent_, &SecureManager::on_get_secure_value, r_secure_value.ok());
+
+ promise_.set_value(r_secure_value.move_as_ok());
+ stop();
+}
+
+void SetSecureValue::merge(FileManager *file_manager, FileId file_id, EncryptedSecureFile &encrypted_file) {
+ auto file_view = file_manager->get_file_view(file_id);
+ CHECK(!file_view.empty());
+ CHECK(file_view.encryption_key().has_value_hash());
+ if (file_view.encryption_key().value_hash().as_slice() != encrypted_file.file_hash) {
+ LOG(ERROR) << "Hash mismatch";
+ return;
+ }
+ LOG_STATUS(file_manager->merge(encrypted_file.file.file_id, file_id));
+}
+
+class DeleteSecureValue final : public NetQueryCallback {
+ public:
+ DeleteSecureValue(ActorShared<SecureManager> parent, SecureValueType type, Promise<Unit> promise)
+ : parent_(std::move(parent)), type_(std::move(type)), promise_(std::move(promise)) {
+ }
+
+ private:
+ ActorShared<SecureManager> parent_;
+ SecureValueType type_;
+ Promise<Unit> promise_;
+
+ void start_up() final {
+ std::vector<telegram_api::object_ptr<telegram_api::SecureValueType>> types;
+ types.push_back(get_input_secure_value_type(type_));
+ auto query = G()->net_query_creator().create(telegram_api::account_deleteSecureValue(std::move(types)));
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this));
+ }
+
+ void on_result(NetQueryPtr query) final {
+ auto r_result = fetch_result<telegram_api::account_deleteSecureValue>(std::move(query));
+ if (r_result.is_error()) {
+ promise_.set_error(r_result.move_as_error());
+ } else {
+ promise_.set_value(Unit());
+ }
+ stop();
+ }
+};
+
+class GetPassportAuthorizationForm final : public NetQueryCallback {
+ public:
+ GetPassportAuthorizationForm(ActorShared<SecureManager> parent, UserId bot_user_id, string scope, string public_key,
+ Promise<telegram_api::object_ptr<telegram_api::account_authorizationForm>> promise)
+ : parent_(std::move(parent))
+ , bot_user_id_(bot_user_id)
+ , scope_(std::move(scope))
+ , public_key_(std::move(public_key))
+ , promise_(std::move(promise)) {
+ }
+
+ private:
+ ActorShared<SecureManager> parent_;
+ UserId bot_user_id_;
+ string scope_;
+ string public_key_;
+ Promise<telegram_api::object_ptr<telegram_api::account_authorizationForm>> promise_;
+
+ void on_error(Status error) {
+ if (error.code() > 0) {
+ promise_.set_error(std::move(error));
+ } else {
+ promise_.set_error(Status::Error(400, error.message()));
+ }
+ stop();
+ }
+
+ void start_up() final {
+ auto account_get_authorization_form =
+ telegram_api::account_getAuthorizationForm(bot_user_id_.get(), std::move(scope_), std::move(public_key_));
+ auto query = G()->net_query_creator().create(account_get_authorization_form);
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this));
+ }
+
+ void on_result(NetQueryPtr query) final {
+ auto r_result = fetch_result<telegram_api::account_getAuthorizationForm>(std::move(query));
+ if (r_result.is_error()) {
+ return on_error(r_result.move_as_error());
+ }
+ promise_.set_value(r_result.move_as_ok());
+ stop();
+ }
+};
+
+SecureManager::SecureManager(ActorShared<> parent) : parent_(std::move(parent)) {
+}
+
+void SecureManager::get_secure_value(std::string password, SecureValueType type, Promise<TdApiSecureValue> promise) {
+ auto new_promise =
+ PromiseCreator::lambda([promise = std::move(promise)](Result<SecureValueWithCredentials> r_secure_value) mutable {
+ if (r_secure_value.is_error()) {
+ return promise.set_error(r_secure_value.move_as_error());
+ }
+
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ if (file_manager == nullptr) {
+ return promise.set_value(nullptr);
+ }
+ auto r_passport_element = get_passport_element_object(file_manager, r_secure_value.move_as_ok().value);
+ if (r_passport_element.is_error()) {
+ LOG(ERROR) << "Failed to get passport element object: " << r_passport_element.error();
+ return promise.set_value(nullptr);
+ }
+ promise.set_value(r_passport_element.move_as_ok());
+ });
+
+ refcnt_++;
+ create_actor<GetSecureValue>("GetSecureValue", actor_shared(this), std::move(password), type, std::move(new_promise))
+ .release();
+}
+
+class GetPassportConfig final : public NetQueryCallback {
+ public:
+ GetPassportConfig(ActorShared<SecureManager> parent, string country_code,
+ Promise<td_api::object_ptr<td_api::text>> promise)
+ : parent_(std::move(parent)), country_code_(std::move(country_code)), promise_(std::move(promise)) {
+ }
+
+ private:
+ ActorShared<SecureManager> parent_;
+ string country_code_;
+ Promise<td_api::object_ptr<td_api::text>> promise_;
+
+ void start_up() final {
+ auto query = G()->net_query_creator().create(telegram_api::help_getPassportConfig(0));
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this));
+ }
+
+ void on_result(NetQueryPtr query) final {
+ auto r_result = fetch_result<telegram_api::help_getPassportConfig>(std::move(query));
+ if (r_result.is_error()) {
+ promise_.set_error(r_result.move_as_error());
+ stop();
+ return;
+ }
+
+ auto config = r_result.move_as_ok();
+ switch (config->get_id()) {
+ case telegram_api::help_passportConfigNotModified::ID:
+ promise_.set_error(Status::Error(500, "Wrong server response"));
+ break;
+ case telegram_api::help_passportConfig::ID: {
+ const string &data =
+ static_cast<const telegram_api::help_passportConfig *>(config.get())->countries_langs_->data_;
+ auto begin_pos = data.find((PSLICE() << '"' << country_code_ << "\":\"").c_str());
+ if (begin_pos == string::npos) {
+ promise_.set_value(nullptr);
+ break;
+ }
+
+ begin_pos += 4 + country_code_.size();
+ auto end_pos = data.find('"', begin_pos);
+ if (end_pos == string::npos) {
+ return promise_.set_error(Status::Error(500, "Wrong server response"));
+ }
+ promise_.set_value(td_api::make_object<td_api::text>(data.substr(begin_pos, end_pos - begin_pos)));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ stop();
+ }
+};
+
+void SecureManager::on_get_secure_value(SecureValueWithCredentials value) {
+ auto type = value.value.type;
+ secure_value_cache_[type] = std::move(value);
+}
+
+void SecureManager::get_all_secure_values(std::string password, Promise<TdApiSecureValues> promise) {
+ refcnt_++;
+ create_actor<GetAllSecureValues>("GetAllSecureValues", actor_shared(this), std::move(password), std::move(promise))
+ .release();
+}
+
+void SecureManager::set_secure_value(string password, SecureValue secure_value, Promise<TdApiSecureValue> promise) {
+ refcnt_++;
+ auto type = secure_value.type;
+ auto new_promise =
+ PromiseCreator::lambda([promise = std::move(promise)](Result<SecureValueWithCredentials> r_secure_value) mutable {
+ if (r_secure_value.is_error()) {
+ return promise.set_error(r_secure_value.move_as_error());
+ }
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ auto r_passport_element = get_passport_element_object(file_manager, r_secure_value.move_as_ok().value);
+ if (r_passport_element.is_error()) {
+ LOG(ERROR) << "Failed to get passport element object: " << r_passport_element.error();
+ return promise.set_error(Status::Error(500, "Failed to get passport element object"));
+ }
+ promise.set_value(r_passport_element.move_as_ok());
+ });
+ set_secure_value_queries_[type] = create_actor<SetSecureValue>(
+ "SetSecureValue", actor_shared(this), std::move(password), std::move(secure_value), std::move(new_promise));
+}
+
+void SecureManager::delete_secure_value(SecureValueType type, Promise<Unit> promise) {
+ refcnt_++;
+ auto new_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), type, promise = std::move(promise)](Result<Unit> result) mutable {
+ send_closure(actor_id, &SecureManager::on_delete_secure_value, type, std::move(promise), std::move(result));
+ });
+ create_actor<DeleteSecureValue>("DeleteSecureValue", actor_shared(this), type, std::move(new_promise)).release();
+}
+
+void SecureManager::on_delete_secure_value(SecureValueType type, Promise<Unit> promise, Result<Unit> result) {
+ if (result.is_error()) {
+ return promise.set_error(result.move_as_error());
+ }
+
+ secure_value_cache_.erase(type);
+ promise.set_value(Unit());
+}
+
+void SecureManager::set_secure_value_errors(Td *td, tl_object_ptr<telegram_api::InputUser> input_user,
+ vector<tl_object_ptr<td_api::inputPassportElementError>> errors,
+ Promise<Unit> promise) {
+ CHECK(td != nullptr);
+ CHECK(input_user != nullptr);
+ vector<tl_object_ptr<telegram_api::SecureValueError>> input_errors;
+ for (auto &error : errors) {
+ if (error == nullptr) {
+ return promise.set_error(Status::Error(400, "Error must be non-empty"));
+ }
+ if (error->type_ == nullptr) {
+ return promise.set_error(Status::Error(400, "Type must be non-empty"));
+ }
+ if (!clean_input_string(error->message_)) {
+ return promise.set_error(Status::Error(400, "Error message must be encoded in UTF-8"));
+ }
+ if (error->source_ == nullptr) {
+ return promise.set_error(Status::Error(400, "Error source must be non-empty"));
+ }
+
+ auto type = get_input_secure_value_type(get_secure_value_type_td_api(error->type_));
+ switch (error->source_->get_id()) {
+ case td_api::inputPassportElementErrorSourceUnspecified::ID: {
+ auto source = td_api::move_object_as<td_api::inputPassportElementErrorSourceUnspecified>(error->source_);
+ input_errors.push_back(make_tl_object<telegram_api::secureValueError>(
+ std::move(type), BufferSlice(source->element_hash_), error->message_));
+ break;
+ }
+ case td_api::inputPassportElementErrorSourceDataField::ID: {
+ auto source = td_api::move_object_as<td_api::inputPassportElementErrorSourceDataField>(error->source_);
+ if (!clean_input_string(source->field_name_)) {
+ return promise.set_error(Status::Error(400, "Field name must be encoded in UTF-8"));
+ }
+
+ input_errors.push_back(make_tl_object<telegram_api::secureValueErrorData>(
+ std::move(type), BufferSlice(source->data_hash_), source->field_name_, error->message_));
+ break;
+ }
+ case td_api::inputPassportElementErrorSourceFrontSide::ID: {
+ auto source = td_api::move_object_as<td_api::inputPassportElementErrorSourceFrontSide>(error->source_);
+ input_errors.push_back(make_tl_object<telegram_api::secureValueErrorFrontSide>(
+ std::move(type), BufferSlice(source->file_hash_), error->message_));
+ break;
+ }
+ case td_api::inputPassportElementErrorSourceReverseSide::ID: {
+ auto source = td_api::move_object_as<td_api::inputPassportElementErrorSourceReverseSide>(error->source_);
+ input_errors.push_back(make_tl_object<telegram_api::secureValueErrorReverseSide>(
+ std::move(type), BufferSlice(source->file_hash_), error->message_));
+ break;
+ }
+ case td_api::inputPassportElementErrorSourceSelfie::ID: {
+ auto source = td_api::move_object_as<td_api::inputPassportElementErrorSourceSelfie>(error->source_);
+ input_errors.push_back(make_tl_object<telegram_api::secureValueErrorSelfie>(
+ std::move(type), BufferSlice(source->file_hash_), error->message_));
+ break;
+ }
+ case td_api::inputPassportElementErrorSourceTranslationFile::ID: {
+ auto source = td_api::move_object_as<td_api::inputPassportElementErrorSourceTranslationFile>(error->source_);
+ input_errors.push_back(make_tl_object<telegram_api::secureValueErrorTranslationFile>(
+ std::move(type), BufferSlice(source->file_hash_), error->message_));
+ break;
+ }
+ case td_api::inputPassportElementErrorSourceTranslationFiles::ID: {
+ auto source = td_api::move_object_as<td_api::inputPassportElementErrorSourceTranslationFiles>(error->source_);
+ if (source->file_hashes_.empty()) {
+ return promise.set_error(Status::Error(400, "File hashes must be non-empty"));
+ }
+ auto file_hashes = transform(source->file_hashes_, [](Slice hash) { return BufferSlice(hash); });
+ input_errors.push_back(make_tl_object<telegram_api::secureValueErrorTranslationFiles>(
+ std::move(type), std::move(file_hashes), error->message_));
+ break;
+ }
+ case td_api::inputPassportElementErrorSourceFile::ID: {
+ auto source = td_api::move_object_as<td_api::inputPassportElementErrorSourceFile>(error->source_);
+ input_errors.push_back(make_tl_object<telegram_api::secureValueErrorFile>(
+ std::move(type), BufferSlice(source->file_hash_), error->message_));
+ break;
+ }
+ case td_api::inputPassportElementErrorSourceFiles::ID: {
+ auto source = td_api::move_object_as<td_api::inputPassportElementErrorSourceFiles>(error->source_);
+ if (source->file_hashes_.empty()) {
+ return promise.set_error(Status::Error(400, "File hashes must be non-empty"));
+ }
+ auto file_hashes = transform(source->file_hashes_, [](Slice hash) { return BufferSlice(hash); });
+ input_errors.push_back(make_tl_object<telegram_api::secureValueErrorFiles>(
+ std::move(type), std::move(file_hashes), error->message_));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+ td->create_handler<SetSecureValueErrorsQuery>(std::move(promise))
+ ->send(std::move(input_user), std::move(input_errors));
+}
+
+void SecureManager::get_passport_authorization_form(UserId bot_user_id, string scope, string public_key, string nonce,
+ Promise<TdApiAuthorizationForm> promise) {
+ refcnt_++;
+ CHECK(max_authorization_form_id_ < std::numeric_limits<int32>::max());
+ auto authorization_form_id = ++max_authorization_form_id_;
+ auto &form_ptr = authorization_forms_[authorization_form_id];
+ if (form_ptr == nullptr) {
+ form_ptr = make_unique<AuthorizationForm>();
+ }
+ auto &form = *form_ptr;
+ form.bot_user_id = bot_user_id;
+ form.scope = scope;
+ form.public_key = public_key;
+ form.nonce = std::move(nonce);
+ auto new_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), authorization_form_id, promise = std::move(promise)](
+ Result<telegram_api::object_ptr<telegram_api::account_authorizationForm>> r_authorization_form) mutable {
+ send_closure(actor_id, &SecureManager::on_get_passport_authorization_form, authorization_form_id,
+ std::move(promise), std::move(r_authorization_form));
+ });
+ create_actor<GetPassportAuthorizationForm>("GetPassportAuthorizationForm", actor_shared(this), bot_user_id,
+ std::move(scope), std::move(public_key), std::move(new_promise))
+ .release();
+}
+
+void SecureManager::on_get_passport_authorization_form(
+ int32 authorization_form_id, Promise<TdApiAuthorizationForm> promise,
+ Result<telegram_api::object_ptr<telegram_api::account_authorizationForm>> r_authorization_form) {
+ auto it = authorization_forms_.find(authorization_form_id);
+ CHECK(it != authorization_forms_.end());
+ CHECK(it->second != nullptr);
+ CHECK(!it->second->is_received);
+ if (r_authorization_form.is_error()) {
+ authorization_forms_.erase(it);
+ return promise.set_error(r_authorization_form.move_as_error());
+ }
+
+ auto authorization_form = r_authorization_form.move_as_ok();
+ LOG(INFO) << "Receive " << to_string(authorization_form);
+ G()->td().get_actor_unsafe()->contacts_manager_->on_get_users(std::move(authorization_form->users_),
+ "on_get_passport_authorization_form");
+
+ vector<vector<SuitableSecureValue>> required_types;
+ std::map<SecureValueType, SuitableSecureValue> all_types;
+ for (auto &type_ptr : authorization_form->required_types_) {
+ CHECK(type_ptr != nullptr);
+ vector<SuitableSecureValue> required_type;
+ switch (type_ptr->get_id()) {
+ case telegram_api::secureRequiredType::ID: {
+ auto value = get_suitable_secure_value(move_tl_object_as<telegram_api::secureRequiredType>(type_ptr));
+ all_types.emplace(value.type, value);
+ required_type.push_back(std::move(value));
+ break;
+ }
+ case telegram_api::secureRequiredTypeOneOf::ID: {
+ auto type_one_of = move_tl_object_as<telegram_api::secureRequiredTypeOneOf>(type_ptr);
+ for (auto &type : type_one_of->types_) {
+ if (type->get_id() == telegram_api::secureRequiredType::ID) {
+ auto value = get_suitable_secure_value(move_tl_object_as<telegram_api::secureRequiredType>(type));
+ all_types.emplace(value.type, value);
+ required_type.push_back(std::move(value));
+ } else {
+ LOG(ERROR) << to_string(type);
+ }
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ if (!required_type.empty()) {
+ required_types.push_back(required_type);
+ }
+ }
+
+ it->second->options = std::move(all_types);
+ it->second->values = std::move(authorization_form->values_);
+ it->second->errors = std::move(authorization_form->errors_);
+ it->second->is_received = true;
+
+ promise.set_value(td_api::make_object<td_api::passportAuthorizationForm>(
+ authorization_form_id, get_passport_required_elements_object(required_types),
+ authorization_form->privacy_policy_url_));
+}
+
+void SecureManager::get_passport_authorization_form_available_elements(int32 authorization_form_id, string password,
+ Promise<TdApiSecureValuesWithErrors> promise) {
+ auto it = authorization_forms_.find(authorization_form_id);
+ if (it == authorization_forms_.end()) {
+ return promise.set_error(Status::Error(400, "Unknown authorization_form_id"));
+ }
+ CHECK(it->second != nullptr);
+ if (!it->second->is_received) {
+ return promise.set_error(Status::Error(400, "Authorization form isn't received yet"));
+ }
+
+ refcnt_++;
+ send_closure(G()->password_manager(), &PasswordManager::get_secure_secret, password,
+ PromiseCreator::lambda([self = actor_shared(this), authorization_form_id,
+ promise = std::move(promise)](Result<secure_storage::Secret> r_secret) mutable {
+ send_closure(self, &SecureManager::on_get_passport_authorization_form_secret, authorization_form_id,
+ std::move(promise), std::move(r_secret));
+ }));
+}
+
+void SecureManager::on_get_passport_authorization_form_secret(int32 authorization_form_id,
+ Promise<TdApiSecureValuesWithErrors> promise,
+ Result<secure_storage::Secret> r_secret) {
+ auto it = authorization_forms_.find(authorization_form_id);
+ if (it == authorization_forms_.end()) {
+ return promise.set_error(Status::Error(400, "Authorization form has already been sent"));
+ }
+ CHECK(it->second != nullptr);
+ CHECK(it->second->is_received);
+ if (it->second->is_decrypted) {
+ return promise.set_error(Status::Error(400, "Authorization form has already been decrypted"));
+ }
+
+ if (r_secret.is_error()) {
+ auto error = r_secret.move_as_error();
+ if (!G()->is_expected_error(error)) {
+ LOG(ERROR) << "Receive error instead of secret: " << error;
+ }
+ if (error.code() <= 0) {
+ error = Status::Error(400, error.message()); // TODO error.set_code(400) ?
+ }
+ return promise.set_error(std::move(error));
+ }
+ auto secret = r_secret.move_as_ok();
+
+ it->second->is_decrypted = true;
+
+ auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get();
+ std::vector<TdApiSecureValue> values;
+ std::map<SecureValueType, SecureValueCredentials> all_credentials;
+ for (const auto &suitable_type : it->second->options) {
+ auto type = suitable_type.first;
+ for (auto &value : it->second->values) {
+ if (value == nullptr) {
+ continue;
+ }
+ auto value_type = get_secure_value_type(value->type_);
+ if (value_type != type) {
+ continue;
+ }
+
+ auto r_secure_value =
+ decrypt_secure_value(file_manager, secret, get_encrypted_secure_value(file_manager, std::move(value)));
+ value = nullptr;
+ if (r_secure_value.is_error()) {
+ LOG(ERROR) << "Failed to decrypt secure value: " << r_secure_value.error();
+ break;
+ }
+
+ on_get_secure_value(r_secure_value.ok());
+
+ auto secure_value = r_secure_value.move_as_ok();
+ auto r_passport_element = get_passport_element_object(file_manager, secure_value.value);
+ if (r_passport_element.is_error()) {
+ LOG(ERROR) << "Failed to get passport element object: " << r_passport_element.error();
+ break;
+ }
+ values.push_back(r_passport_element.move_as_ok());
+ all_credentials.emplace(type, std::move(secure_value.credentials));
+
+ break;
+ }
+ }
+
+ auto get_file_index = [](const vector<SecureFileCredentials> &file_credentials, Slice file_hash) -> int32 {
+ for (size_t i = 0; i < file_credentials.size(); i++) {
+ if (file_credentials[i].hash == file_hash) {
+ return narrow_cast<int32>(i);
+ }
+ }
+ return -1;
+ };
+
+ vector<td_api::object_ptr<td_api::passportElementError>> errors;
+ for (auto &error_ptr : it->second->errors) {
+ CHECK(error_ptr != nullptr);
+ SecureValueType type = SecureValueType::None;
+ td_api::object_ptr<td_api::PassportElementErrorSource> source;
+ string message;
+ switch (error_ptr->get_id()) {
+ case telegram_api::secureValueError::ID: {
+ auto error = move_tl_object_as<telegram_api::secureValueError>(error_ptr);
+ type = get_secure_value_type(error->type_);
+ message = std::move(error->text_);
+ source = td_api::make_object<td_api::passportElementErrorSourceUnspecified>();
+ break;
+ }
+ case telegram_api::secureValueErrorData::ID: {
+ auto error = move_tl_object_as<telegram_api::secureValueErrorData>(error_ptr);
+ type = get_secure_value_type(error->type_);
+ message = std::move(error->text_);
+ string field_name = get_secure_value_data_field_name(type, error->field_);
+ if (field_name.empty()) {
+ break;
+ }
+ source = td_api::make_object<td_api::passportElementErrorSourceDataField>(std::move(field_name));
+ break;
+ }
+ case telegram_api::secureValueErrorFile::ID: {
+ auto error = move_tl_object_as<telegram_api::secureValueErrorFile>(error_ptr);
+ type = get_secure_value_type(error->type_);
+ message = std::move(error->text_);
+ int32 file_index = get_file_index(all_credentials[type].files, error->file_hash_.as_slice());
+ if (file_index == -1) {
+ LOG(ERROR) << "Can't find file with error";
+ break;
+ }
+ source = td_api::make_object<td_api::passportElementErrorSourceFile>(file_index);
+ break;
+ }
+ case telegram_api::secureValueErrorFiles::ID: {
+ auto error = move_tl_object_as<telegram_api::secureValueErrorFiles>(error_ptr);
+ type = get_secure_value_type(error->type_);
+ message = std::move(error->text_);
+ source = td_api::make_object<td_api::passportElementErrorSourceFiles>();
+ break;
+ }
+ case telegram_api::secureValueErrorFrontSide::ID: {
+ auto error = move_tl_object_as<telegram_api::secureValueErrorFrontSide>(error_ptr);
+ type = get_secure_value_type(error->type_);
+ message = std::move(error->text_);
+ source = td_api::make_object<td_api::passportElementErrorSourceFrontSide>();
+ break;
+ }
+ case telegram_api::secureValueErrorReverseSide::ID: {
+ auto error = move_tl_object_as<telegram_api::secureValueErrorReverseSide>(error_ptr);
+ type = get_secure_value_type(error->type_);
+ message = std::move(error->text_);
+ source = td_api::make_object<td_api::passportElementErrorSourceReverseSide>();
+ break;
+ }
+ case telegram_api::secureValueErrorSelfie::ID: {
+ auto error = move_tl_object_as<telegram_api::secureValueErrorSelfie>(error_ptr);
+ type = get_secure_value_type(error->type_);
+ message = std::move(error->text_);
+ source = td_api::make_object<td_api::passportElementErrorSourceSelfie>();
+ break;
+ }
+ case telegram_api::secureValueErrorTranslationFile::ID: {
+ auto error = move_tl_object_as<telegram_api::secureValueErrorTranslationFile>(error_ptr);
+ type = get_secure_value_type(error->type_);
+ message = std::move(error->text_);
+ int32 file_index = get_file_index(all_credentials[type].translations, error->file_hash_.as_slice());
+ if (file_index == -1) {
+ LOG(ERROR) << "Can't find translation file with error";
+ break;
+ }
+ source = td_api::make_object<td_api::passportElementErrorSourceTranslationFile>(file_index);
+ break;
+ }
+ case telegram_api::secureValueErrorTranslationFiles::ID: {
+ auto error = move_tl_object_as<telegram_api::secureValueErrorTranslationFiles>(error_ptr);
+ type = get_secure_value_type(error->type_);
+ message = std::move(error->text_);
+ source = td_api::make_object<td_api::passportElementErrorSourceTranslationFiles>();
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ if (source == nullptr) {
+ continue;
+ }
+
+ errors.push_back(td_api::make_object<td_api::passportElementError>(get_passport_element_type_object(type), message,
+ std::move(source)));
+ }
+
+ promise.set_value(td_api::make_object<td_api::passportElementsWithErrors>(std::move(values), std::move(errors)));
+}
+
+void SecureManager::send_passport_authorization_form(int32 authorization_form_id, std::vector<SecureValueType> types,
+ Promise<> promise) {
+ auto it = authorization_forms_.find(authorization_form_id);
+ if (it == authorization_forms_.end()) {
+ return promise.set_error(Status::Error(400, "Unknown authorization_form_id"));
+ }
+ CHECK(it->second != nullptr);
+ if (!it->second->is_received) {
+ return promise.set_error(Status::Error(400, "Authorization form isn't received yet"));
+ }
+ // there is no need to check for is_decrypted
+ if (types.empty()) {
+ return promise.set_error(Status::Error(400, "Types must be non-empty"));
+ }
+
+ std::vector<SecureValueCredentials> credentials;
+ credentials.reserve(types.size());
+ for (auto type : types) {
+ auto value_it = secure_value_cache_.find(type);
+ if (value_it == secure_value_cache_.end()) {
+ return promise.set_error(Status::Error(400, "Passport Element with the specified type is not found"));
+ }
+ credentials.push_back(value_it->second.credentials);
+ }
+
+ std::vector<telegram_api::object_ptr<telegram_api::secureValueHash>> hashes;
+ for (auto &c : credentials) {
+ hashes.push_back(telegram_api::make_object<telegram_api::secureValueHash>(get_input_secure_value_type(c.type),
+ BufferSlice(c.hash)));
+ auto options_it = it->second->options.find(c.type);
+ if (options_it == it->second->options.end()) {
+ return promise.set_error(Status::Error(400, "Passport Element with the specified type was not requested"));
+ }
+ auto &options = options_it->second;
+ if (!options.is_selfie_required) {
+ c.selfie = optional<SecureFileCredentials>();
+ }
+ if (!options.is_translation_required) {
+ c.translations.clear();
+ }
+ }
+
+ auto r_encrypted_credentials =
+ get_encrypted_credentials(credentials, it->second->nonce, it->second->public_key,
+ it->second->scope[0] == '{' && it->second->scope.back() == '}');
+ if (r_encrypted_credentials.is_error()) {
+ return promise.set_error(r_encrypted_credentials.move_as_error());
+ }
+
+ auto td_query = telegram_api::account_acceptAuthorization(
+ it->second->bot_user_id.get(), it->second->scope, it->second->public_key, std::move(hashes),
+ get_secure_credentials_encrypted_object(r_encrypted_credentials.move_as_ok()));
+ auto query = G()->net_query_creator().create(td_query);
+ auto new_promise =
+ PromiseCreator::lambda([promise = std::move(promise)](Result<NetQueryPtr> r_net_query_ptr) mutable {
+ auto r_result = fetch_result<telegram_api::account_acceptAuthorization>(std::move(r_net_query_ptr));
+ if (r_result.is_error()) {
+ return promise.set_error(r_result.move_as_error());
+ }
+ promise.set_value(Unit());
+ });
+ send_with_promise(std::move(query), std::move(new_promise));
+}
+
+void SecureManager::get_preferred_country_language(string country_code,
+ Promise<td_api::object_ptr<td_api::text>> promise) {
+ refcnt_++;
+ for (auto &c : country_code) {
+ c = to_upper(c);
+ }
+ create_actor<GetPassportConfig>("GetPassportConfig", actor_shared(this), std::move(country_code), std::move(promise))
+ .release();
+}
+
+void SecureManager::hangup() {
+ container_.for_each(
+ [](auto id, Promise<NetQueryPtr> &promise) { promise.set_error(Global::request_aborted_error()); });
+ dec_refcnt();
+}
+
+void SecureManager::hangup_shared() {
+ dec_refcnt();
+}
+
+void SecureManager::dec_refcnt() {
+ refcnt_--;
+ if (refcnt_ == 0) {
+ stop();
+ }
+}
+
+void SecureManager::on_result(NetQueryPtr query) {
+ auto token = get_link_token();
+ container_.extract(token).set_value(std::move(query));
+}
+
+void SecureManager::send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> promise) {
+ auto id = container_.create(std::move(promise));
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, id));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecureManager.h b/protocols/Telegram/tdlib/td/td/telegram/SecureManager.h
new file mode 100644
index 0000000000..c895f43813
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecureManager.h
@@ -0,0 +1,99 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/SecureStorage.h"
+#include "td/telegram/SecureValue.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Container.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+#include <map>
+#include <utility>
+
+namespace td {
+
+class Td;
+
+using TdApiSecureValue = td_api::object_ptr<td_api::PassportElement>;
+using TdApiSecureValues = td_api::object_ptr<td_api::passportElements>;
+using TdApiSecureValuesWithErrors = td_api::object_ptr<td_api::passportElementsWithErrors>;
+using TdApiAuthorizationForm = td_api::object_ptr<td_api::passportAuthorizationForm>;
+
+class SecureManager final : public NetQueryCallback {
+ public:
+ explicit SecureManager(ActorShared<> parent);
+
+ void get_secure_value(std::string password, SecureValueType type, Promise<TdApiSecureValue> promise);
+
+ void get_all_secure_values(std::string password, Promise<TdApiSecureValues> promise);
+
+ void set_secure_value(string password, SecureValue secure_value, Promise<TdApiSecureValue> promise);
+
+ void delete_secure_value(SecureValueType type, Promise<Unit> promise);
+
+ void set_secure_value_errors(Td *td, tl_object_ptr<telegram_api::InputUser> input_user,
+ vector<tl_object_ptr<td_api::inputPassportElementError>> errors, Promise<Unit> promise);
+
+ void on_get_secure_value(SecureValueWithCredentials value);
+
+ void get_passport_authorization_form(UserId bot_user_id, string scope, string public_key, string nonce,
+ Promise<TdApiAuthorizationForm> promise);
+ void get_passport_authorization_form_available_elements(int32 authorization_form_id, string password,
+ Promise<TdApiSecureValuesWithErrors> promise);
+ void send_passport_authorization_form(int32 authorization_form_id, std::vector<SecureValueType> types,
+ Promise<> promise);
+
+ void get_preferred_country_language(string country_code, Promise<td_api::object_ptr<td_api::text>> promise);
+
+ private:
+ ActorShared<> parent_;
+ int32 refcnt_{1};
+ std::map<SecureValueType, ActorOwn<>> set_secure_value_queries_;
+ std::map<SecureValueType, SecureValueWithCredentials> secure_value_cache_;
+
+ struct AuthorizationForm {
+ UserId bot_user_id;
+ string scope;
+ string public_key;
+ string nonce;
+ bool is_received = false;
+ bool is_decrypted = false;
+ std::map<SecureValueType, SuitableSecureValue> options;
+ vector<telegram_api::object_ptr<telegram_api::secureValue>> values;
+ vector<telegram_api::object_ptr<telegram_api::SecureValueError>> errors;
+ };
+
+ FlatHashMap<int32, unique_ptr<AuthorizationForm>> authorization_forms_;
+ int32 max_authorization_form_id_{0};
+
+ void hangup() final;
+ void hangup_shared() final;
+ void dec_refcnt();
+ void on_delete_secure_value(SecureValueType type, Promise<Unit> promise, Result<Unit> result);
+ void on_get_passport_authorization_form(
+ int32 authorization_form_id, Promise<TdApiAuthorizationForm> promise,
+ Result<telegram_api::object_ptr<telegram_api::account_authorizationForm>> r_authorization_form);
+ void on_get_passport_authorization_form_secret(int32 authorization_form_id,
+ Promise<TdApiSecureValuesWithErrors> promise,
+ Result<secure_storage::Secret> r_secret);
+
+ void on_result(NetQueryPtr query) final;
+ Container<Promise<NetQueryPtr>> container_;
+ void send_with_promise(NetQueryPtr query, Promise<NetQueryPtr> promise);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecureStorage.cpp b/protocols/Telegram/tdlib/td/td/telegram/SecureStorage.cpp
new file mode 100644
index 0000000000..253f620edc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecureStorage.cpp
@@ -0,0 +1,410 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SecureStorage.h"
+
+#include "td/utils/as.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/Random.h"
+#include "td/utils/SharedSlice.h"
+#include "td/utils/SliceBuilder.h"
+
+namespace td {
+namespace secure_storage {
+
+// Helpers
+Result<ValueHash> ValueHash::create(Slice data) {
+ UInt256 hash;
+ if (data.size() != ::td::as_slice(hash).size()) {
+ return Status::Error(PSLICE() << "Wrong hash size " << data.size());
+ }
+ ::td::as_slice(hash).copy_from(data);
+ return ValueHash{hash};
+}
+
+static AesCbcState calc_aes_cbc_state_hash(Slice hash) {
+ CHECK(hash.size() == 64);
+ SecureString key(32);
+ as_mutable_slice(key).copy_from(hash.substr(0, 32));
+ SecureString iv(16);
+ as_mutable_slice(iv).copy_from(hash.substr(32, 16));
+ LOG(INFO) << "End AES CBC state calculation";
+ return AesCbcState{key, iv};
+}
+
+AesCbcState calc_aes_cbc_state_pbkdf2(Slice secret, Slice salt) {
+ LOG(INFO) << "Begin AES CBC state calculation";
+ UInt<512> hash;
+ auto hash_slice = as_slice(hash);
+ pbkdf2_sha512(secret, salt, 100000, hash_slice);
+ return calc_aes_cbc_state_hash(hash_slice);
+}
+
+AesCbcState calc_aes_cbc_state_sha512(Slice seed) {
+ LOG(INFO) << "Begin AES CBC state calculation";
+ UInt<512> hash;
+ auto hash_slice = as_slice(hash);
+ sha512(seed, hash_slice);
+ return calc_aes_cbc_state_hash(hash_slice);
+}
+
+template <class F>
+static Status data_view_for_each(const DataView &data, F &&f) {
+ const int64 step = 128 << 10;
+ for (int64 i = 0, size = data.size(); i < size; i += step) {
+ TRY_RESULT(bytes, data.pread(i, min(step, size - i)));
+ TRY_STATUS(f(std::move(bytes)));
+ }
+ return Status::OK();
+}
+
+Result<ValueHash> calc_value_hash(const DataView &data_view) {
+ Sha256State state;
+ state.init();
+ TRY_STATUS(data_view_for_each(data_view, [&state](BufferSlice bytes) {
+ state.feed(bytes.as_slice());
+ return Status::OK();
+ }));
+ UInt256 res;
+ state.extract(as_slice(res));
+ return ValueHash{res};
+}
+
+ValueHash calc_value_hash(Slice data) {
+ UInt256 res;
+ sha256(data, as_slice(res));
+ return ValueHash{res};
+}
+
+BufferSlice gen_random_prefix(int64 data_size) {
+ BufferSlice buff(narrow_cast<size_t>(((32 + 15 + data_size) & -16) - data_size));
+ Random::secure_bytes(buff.as_slice());
+ buff.as_slice()[0] = narrow_cast<uint8>(buff.size());
+ CHECK((buff.size() + data_size) % 16 == 0);
+ return buff;
+}
+
+class FileDataView final : public DataView {
+ public:
+ FileDataView(FileFd &fd, int64 size);
+
+ int64 size() const final;
+ Result<BufferSlice> pread(int64 offset, int64 size) const final;
+
+ private:
+ FileFd &fd_;
+ int64 size_;
+};
+
+FileDataView::FileDataView(FileFd &fd, int64 size) : fd_(fd), size_(size) {
+}
+
+int64 FileDataView::size() const {
+ return size_;
+}
+
+Result<BufferSlice> FileDataView::pread(int64 offset, int64 size) const {
+ auto slice = BufferSlice(narrow_cast<size_t>(size));
+ TRY_RESULT(actual_size, fd_.pread(slice.as_slice(), offset));
+ if (static_cast<int64>(actual_size) != size) {
+ return Status::Error("Not enough data in file");
+ }
+ return std::move(slice);
+}
+
+BufferSliceDataView::BufferSliceDataView(BufferSlice buffer_slice) : buffer_slice_(std::move(buffer_slice)) {
+}
+
+int64 BufferSliceDataView::size() const {
+ return narrow_cast<int64>(buffer_slice_.size());
+}
+
+Result<BufferSlice> BufferSliceDataView::pread(int64 offset, int64 size) const {
+ auto end_offset = size + offset;
+ if (this->size() < end_offset) {
+ return Status::Error("Not enough data in BufferSlice");
+ }
+ return BufferSlice(buffer_slice_.as_slice().substr(narrow_cast<size_t>(offset), narrow_cast<size_t>(size)));
+}
+
+ConcatDataView::ConcatDataView(const DataView &left, const DataView &right) : left_(left), right_(right) {
+}
+
+int64 ConcatDataView::size() const {
+ return left_.size() + right_.size();
+}
+
+Result<BufferSlice> ConcatDataView::pread(int64 offset, int64 size) const {
+ auto end_offset = size + offset;
+ if (this->size() < end_offset) {
+ return Status::Error("Not enough data in ConcatDataView");
+ }
+
+ auto substr = [](const DataView &slice, int64 offset, int64 size) -> Result<BufferSlice> {
+ auto l = max(int64{0}, offset);
+ auto r = min(slice.size(), offset + size);
+ if (l >= r) {
+ return BufferSlice();
+ }
+ return slice.pread(l, r - l);
+ };
+
+ TRY_RESULT(a, substr(left_, offset, size));
+ TRY_RESULT(b, substr(right_, offset - left_.size(), size));
+
+ if (a.empty()) {
+ return std::move(b);
+ }
+ if (b.empty()) {
+ return std::move(a);
+ }
+
+ BufferSlice res(a.size() + b.size());
+ res.as_slice().copy_from(a.as_slice());
+ res.as_slice().substr(a.size()).copy_from(b.as_slice());
+ return std::move(res);
+}
+
+Password::Password(std::string password) : password_(std::move(password)) {
+}
+
+Slice Password::as_slice() const {
+ return password_;
+}
+
+static uint8 secret_checksum(Slice secret) {
+ uint32 sum = 0;
+ for (uint8 c : secret) {
+ sum += c;
+ }
+ return static_cast<uint8>((255 + 239 - sum % 255) % 255);
+}
+
+Result<Secret> Secret::create(Slice secret) {
+ if (secret.size() != 32) {
+ return Status::Error("Wrong secret size");
+ }
+ uint32 checksum = secret_checksum(secret);
+ if (checksum != 0) {
+ return Status::Error(PSLICE() << "Wrong checksum " << checksum);
+ }
+ UInt256 res;
+ ::td::as_slice(res).copy_from(secret);
+
+ UInt256 secret_sha256;
+ sha256(secret, ::td::as_slice(secret_sha256));
+ int64 hash = as<int64>(secret_sha256.raw);
+ return Secret{res, hash};
+}
+
+Secret Secret::create_new() {
+ UInt256 secret;
+ auto secret_slice = ::td::as_slice(secret);
+ Random::secure_bytes(secret_slice);
+ auto checksum_diff = secret_checksum(secret_slice);
+ auto new_byte = static_cast<uint8>((static_cast<uint32>(secret_slice.ubegin()[0]) + checksum_diff) % 255);
+ secret_slice.ubegin()[0] = new_byte;
+ return create(secret_slice).move_as_ok();
+}
+
+Slice Secret::as_slice() const {
+ return ::td::as_slice(secret_);
+}
+
+int64 Secret::get_hash() const {
+ return hash_;
+}
+
+Secret Secret::clone() const {
+ return {secret_, hash_};
+}
+
+EncryptedSecret Secret::encrypt(Slice key, Slice salt, EnryptionAlgorithm algorithm) {
+ auto aes_cbc_state = [&] {
+ switch (algorithm) {
+ case EnryptionAlgorithm::Sha512:
+ return calc_aes_cbc_state_sha512(PSLICE() << salt << key << salt);
+ case EnryptionAlgorithm::Pbkdf2:
+ return calc_aes_cbc_state_pbkdf2(key, salt);
+ default:
+ UNREACHABLE();
+ return AesCbcState(Slice(), Slice());
+ }
+ }();
+
+ UInt256 res;
+ aes_cbc_state.encrypt(as_slice(), ::td::as_slice(res));
+ return EncryptedSecret::create(::td::as_slice(res)).move_as_ok();
+}
+
+Secret::Secret(UInt256 secret, int64 hash) : secret_(secret), hash_(hash) {
+}
+
+Result<EncryptedSecret> EncryptedSecret::create(Slice encrypted_secret) {
+ if (encrypted_secret.size() != 32) {
+ return Status::Error("Wrong encrypted secret size");
+ }
+ UInt256 res;
+ ::td::as_slice(res).copy_from(encrypted_secret);
+ return EncryptedSecret{res};
+}
+
+Result<Secret> EncryptedSecret::decrypt(Slice key, Slice salt, EnryptionAlgorithm algorithm) {
+ auto aes_cbc_state = [&] {
+ switch (algorithm) {
+ case EnryptionAlgorithm::Sha512:
+ return calc_aes_cbc_state_sha512(PSLICE() << salt << key << salt);
+ case EnryptionAlgorithm::Pbkdf2:
+ return calc_aes_cbc_state_pbkdf2(key, salt);
+ default:
+ UNREACHABLE();
+ return AesCbcState(Slice(), Slice());
+ }
+ }();
+
+ UInt256 res;
+ aes_cbc_state.decrypt(::td::as_slice(encrypted_secret_), ::td::as_slice(res));
+ return Secret::create(::td::as_slice(res));
+}
+
+Slice EncryptedSecret::as_slice() const {
+ return ::td::as_slice(encrypted_secret_);
+}
+
+EncryptedSecret::EncryptedSecret(UInt256 encrypted_secret) : encrypted_secret_(encrypted_secret) {
+}
+
+Decryptor::Decryptor(AesCbcState aes_cbc_state) : aes_cbc_state_(std::move(aes_cbc_state)) {
+ sha256_state_.init();
+}
+
+Result<BufferSlice> Decryptor::append(BufferSlice data) {
+ if (data.empty()) {
+ return BufferSlice();
+ }
+ if (data.size() % 16 != 0) {
+ return Status::Error("Part size must be divisible by 16");
+ }
+ aes_cbc_state_.decrypt(data.as_slice(), data.as_slice());
+ sha256_state_.feed(data.as_slice());
+ if (!skipped_prefix_) {
+ to_skip_ = data.as_slice().ubegin()[0];
+ size_t to_skip = min(to_skip_, data.size());
+ if (to_skip_ > data.size()) {
+ to_skip_ = 0; // to fail final to_skip check
+ }
+ skipped_prefix_ = true;
+ data = data.from_slice(data.as_slice().remove_prefix(to_skip));
+ }
+ return std::move(data);
+}
+
+Result<ValueHash> Decryptor::finish() {
+ if (!skipped_prefix_) {
+ return Status::Error("No data was given");
+ }
+ if (to_skip_ < 32) {
+ return Status::Error("Too small random prefix");
+ }
+
+ UInt256 res;
+ sha256_state_.extract(as_slice(res), true);
+ return ValueHash{res};
+}
+
+Encryptor::Encryptor(AesCbcState aes_cbc_state, const DataView &data_view)
+ : aes_cbc_state_(std::move(aes_cbc_state)), data_view_(data_view) {
+}
+
+int64 Encryptor::size() const {
+ return data_view_.size();
+}
+
+Result<BufferSlice> Encryptor::pread(int64 offset, int64 size) const {
+ if (offset != current_offset_) {
+ return Status::Error("Arbitrary offset is not supported");
+ }
+ if (size % 16 != 0) {
+ return Status::Error("Part size must be divisible by 16");
+ }
+ TRY_RESULT(part, data_view_.pread(offset, size));
+ aes_cbc_state_.encrypt(part.as_slice(), part.as_slice());
+ current_offset_ += size;
+ return std::move(part);
+}
+
+Result<EncryptedValue> encrypt_value(const Secret &secret, Slice data) {
+ BufferSliceDataView random_prefix_view{gen_random_prefix(data.size())};
+ BufferSliceDataView data_view{BufferSlice(data)};
+ ConcatDataView full_view{random_prefix_view, data_view};
+
+ TRY_RESULT(hash, calc_value_hash(full_view));
+
+ auto aes_cbc_state = calc_aes_cbc_state_sha512(PSLICE() << secret.as_slice() << hash.as_slice());
+ Encryptor encryptor(std::move(aes_cbc_state), full_view);
+ TRY_RESULT(encrypted_data, encryptor.pread(0, encryptor.size()));
+ return EncryptedValue{std::move(encrypted_data), std::move(hash)};
+}
+
+Result<BufferSlice> decrypt_value(const Secret &secret, const ValueHash &hash, Slice data) {
+ auto aes_cbc_state = calc_aes_cbc_state_sha512(PSLICE() << secret.as_slice() << hash.as_slice());
+ Decryptor decryptor(std::move(aes_cbc_state));
+ TRY_RESULT(decrypted_value, decryptor.append(BufferSlice(data)));
+ TRY_RESULT(got_hash, decryptor.finish());
+ if (got_hash.as_slice() != hash.as_slice()) {
+ return Status::Error(PSLICE() << "Hash mismatch " << format::as_hex_dump<4>(got_hash.as_slice()) << " "
+ << format::as_hex_dump<4>(hash.as_slice()));
+ }
+ return std::move(decrypted_value);
+}
+
+Result<ValueHash> encrypt_file(const Secret &secret, const string &src, const string &dest) {
+ TRY_RESULT(src_file, FileFd::open(src, FileFd::Flags::Read));
+ TRY_RESULT(dest_file, FileFd::open(dest, FileFd::Flags::Truncate | FileFd::Flags::Write | FileFd::Create));
+ TRY_RESULT(src_file_size, src_file.get_size());
+
+ BufferSliceDataView random_prefix_view(gen_random_prefix(src_file_size));
+ FileDataView data_view(src_file, src_file_size);
+ ConcatDataView full_view(random_prefix_view, data_view);
+
+ TRY_RESULT(hash, calc_value_hash(full_view));
+
+ auto aes_cbc_state = calc_aes_cbc_state_sha512(PSLICE() << secret.as_slice() << hash.as_slice());
+ Encryptor encryptor(std::move(aes_cbc_state), full_view);
+ TRY_STATUS(
+ data_view_for_each(encryptor, [&dest_file](BufferSlice bytes) { return dest_file.write(bytes.as_slice()); }));
+ return std::move(hash);
+}
+
+Status decrypt_file(const Secret &secret, const ValueHash &hash, const string &src, const string &dest) {
+ TRY_RESULT(src_file, FileFd::open(src, FileFd::Flags::Read));
+ TRY_RESULT(dest_file, FileFd::open(dest, FileFd::Flags::Truncate | FileFd::Flags::Write | FileFd::Create));
+ TRY_RESULT(src_file_size, src_file.get_size());
+
+ FileDataView src_file_view(src_file, src_file_size);
+
+ auto aes_cbc_state = calc_aes_cbc_state_sha512(PSLICE() << secret.as_slice() << hash.as_slice());
+ Decryptor decryptor(std::move(aes_cbc_state));
+ TRY_STATUS(data_view_for_each(src_file_view, [&decryptor, &dest_file](BufferSlice bytes) {
+ TRY_RESULT(decrypted_bytes, decryptor.append(std::move(bytes)));
+ TRY_STATUS(dest_file.write(decrypted_bytes.as_slice()));
+ return Status::OK();
+ }));
+
+ TRY_RESULT(got_hash, decryptor.finish());
+
+ if (hash.as_slice() != got_hash.as_slice()) {
+ return Status::Error("Hash mismatch");
+ }
+
+ return Status::OK();
+}
+
+} // namespace secure_storage
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecureStorage.h b/protocols/Telegram/tdlib/td/td/telegram/SecureStorage.h
new file mode 100644
index 0000000000..f4dc6d9f20
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecureStorage.h
@@ -0,0 +1,195 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/UInt.h"
+
+namespace td {
+// Types
+// Password
+// Secret - 32 bytes with sum % 255 == 239
+// EncryptedSecret - encrypted secret
+// ValueHash - 32 bytes, sha256 from value
+//
+// ValueFull = ValueText? ValueData? ValueFile* = [Value]
+// Value = ValueText | ValueData | ValueFile
+//
+// ValueMeta = random_prefix, secret, hash
+//
+// Helpers
+// calc_aes_cbc_state :: ValueSecret -> ValueHash -> AesCbcState
+//
+// Encryption.
+// To encrypt data:
+// RandomPrefix, ValueSecret, Value:
+// calc_value_hash :: RandomPrefix -> Value -> ValueHash
+// do_encrypt :: RandomPrefix -> Value -> AesCbcState -> EncryptedValue // async
+// encrypt :: (ValueSecret, RandomPrefix, Value) -> (EncryptedValue, ValueHash)
+//
+// To decrypt data:
+// ValueSecret, ValueHash, EncryptedValue
+// do_decrypt :: EncryptedValue -> AesCbcState -> (RandomPrefix, Value, ValueHash) // async
+// decrypt :: (ValueSecret, ValueHash, EncryptedValue) -> Value
+//
+// To encrypt FullValue:
+// ValueSecret, [(RandomPrefix, Value)]
+// (ValueSecret, [(RandomPrefix, Value)]) -> [(ValueSecret, RandomPrefix, Value)]
+// [(ValueSecret, RandomPrefix, Value)] -> [(EncryptedValue, ValueHash)]
+//
+
+namespace secure_storage {
+// Helpers
+class ValueHash {
+ public:
+ explicit ValueHash(UInt256 hash) : hash_(hash) {
+ }
+ static Result<ValueHash> create(Slice data);
+ Slice as_slice() const {
+ return ::td::as_slice(hash_);
+ }
+
+ private:
+ UInt256 hash_;
+};
+
+class DataView {
+ public:
+ DataView() = default;
+ DataView(const DataView &) = delete;
+ DataView &operator=(const DataView &) = delete;
+ DataView(DataView &&) = delete;
+ DataView &operator=(DataView &&) = delete;
+
+ virtual int64 size() const = 0;
+ virtual Result<BufferSlice> pread(int64 offset, int64 size) const = 0;
+ virtual ~DataView() = default;
+};
+
+class BufferSliceDataView final : public DataView {
+ public:
+ explicit BufferSliceDataView(BufferSlice buffer_slice);
+ int64 size() const final;
+ Result<BufferSlice> pread(int64 offset, int64 size) const final;
+
+ private:
+ BufferSlice buffer_slice_;
+};
+
+class ConcatDataView final : public DataView {
+ public:
+ ConcatDataView(const DataView &left, const DataView &right);
+ int64 size() const final;
+ Result<BufferSlice> pread(int64 offset, int64 size) const final;
+
+ private:
+ const DataView &left_;
+ const DataView &right_;
+};
+
+AesCbcState calc_aes_cbc_state_pbkdf2(Slice secret, Slice salt);
+AesCbcState calc_aes_cbc_state_sha512(Slice seed);
+Result<ValueHash> calc_value_hash(const DataView &data_view);
+ValueHash calc_value_hash(Slice data);
+BufferSlice gen_random_prefix(int64 data_size);
+
+class Password {
+ public:
+ explicit Password(std::string password);
+ Slice as_slice() const;
+
+ private:
+ std::string password_;
+};
+
+class EncryptedSecret;
+
+enum class EnryptionAlgorithm : int32 { Sha512, Pbkdf2 };
+
+class Secret {
+ public:
+ static Result<Secret> create(Slice secret);
+ static Secret create_new();
+
+ Slice as_slice() const;
+ EncryptedSecret encrypt(Slice key, Slice salt, EnryptionAlgorithm algorithm);
+
+ int64 get_hash() const;
+ Secret clone() const;
+
+ static constexpr size_t size() {
+ return sizeof(secret_.raw);
+ }
+
+ private:
+ Secret(UInt256 secret, int64 hash);
+ UInt256 secret_;
+ int64 hash_;
+};
+
+class EncryptedSecret {
+ public:
+ static Result<EncryptedSecret> create(Slice encrypted_secret);
+ Result<Secret> decrypt(Slice key, Slice salt, EnryptionAlgorithm algorithm);
+ Slice as_slice() const;
+
+ private:
+ explicit EncryptedSecret(UInt256 encrypted_secret);
+ UInt256 encrypted_secret_;
+};
+
+// Decryption
+class Decryptor {
+ public:
+ explicit Decryptor(AesCbcState aes_cbc_state);
+ Result<BufferSlice> append(BufferSlice data);
+ Result<ValueHash> finish();
+
+ private:
+ AesCbcState aes_cbc_state_;
+ Sha256State sha256_state_;
+ bool skipped_prefix_{false};
+ size_t to_skip_{0};
+};
+
+// Encryption
+class Encryptor final : public DataView {
+ public:
+ Encryptor(AesCbcState aes_cbc_state, const DataView &data_view);
+ int64 size() const final;
+ Result<BufferSlice> pread(int64 offset, int64 size) const final;
+
+ private:
+ mutable AesCbcState aes_cbc_state_;
+ mutable int64 current_offset_{0};
+ const DataView &data_view_;
+};
+
+// Main functions
+
+// decrypt :: (ValueSecret, ValueHash, EncryptedValue) -> Value
+// encrypt :: (ValueSecret, RandomPrefix, Value) -> (EncryptedValue, ValueHash)
+
+struct EncryptedValue {
+ BufferSlice data;
+ ValueHash hash;
+};
+
+Result<EncryptedValue> encrypt_value(const Secret &secret, Slice data);
+
+Result<BufferSlice> decrypt_value(const Secret &secret, const ValueHash &hash, Slice data);
+
+Result<ValueHash> encrypt_file(const Secret &secret, const string &src, const string &dest);
+
+Status decrypt_file(const Secret &secret, const ValueHash &hash, const string &src, const string &dest);
+
+} // namespace secure_storage
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecureValue.cpp b/protocols/Telegram/tdlib/td/td/telegram/SecureValue.cpp
new file mode 100644
index 0000000000..2128c75937
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecureValue.cpp
@@ -0,0 +1,1470 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SecureValue.h"
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileEncryptionKey.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/OrderInfo.h"
+#include "td/telegram/telegram_api.hpp"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/crypto.h"
+#include "td/utils/JsonBuilder.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/overloaded.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/utf8.h"
+
+#include <limits>
+
+namespace td {
+
+StringBuilder &operator<<(StringBuilder &string_builder, const SecureValueType &type) {
+ switch (type) {
+ case SecureValueType::PersonalDetails:
+ return string_builder << "PersonalDetails";
+ case SecureValueType::Passport:
+ return string_builder << "Passport";
+ case SecureValueType::DriverLicense:
+ return string_builder << "DriverLicense";
+ case SecureValueType::IdentityCard:
+ return string_builder << "IdentityCard";
+ case SecureValueType::InternalPassport:
+ return string_builder << "InternalPassport";
+ case SecureValueType::Address:
+ return string_builder << "Address";
+ case SecureValueType::UtilityBill:
+ return string_builder << "UtilityBill";
+ case SecureValueType::BankStatement:
+ return string_builder << "BankStatement";
+ case SecureValueType::RentalAgreement:
+ return string_builder << "RentalAgreement";
+ case SecureValueType::PassportRegistration:
+ return string_builder << "PassportRegistration";
+ case SecureValueType::TemporaryRegistration:
+ return string_builder << "TemporaryRegistration";
+ case SecureValueType::PhoneNumber:
+ return string_builder << "PhoneNumber";
+ case SecureValueType::EmailAddress:
+ return string_builder << "EmailAddress";
+ case SecureValueType::None:
+ return string_builder << "None";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+SecureValueType get_secure_value_type(const tl_object_ptr<telegram_api::SecureValueType> &secure_value_type) {
+ CHECK(secure_value_type != nullptr);
+ switch (secure_value_type->get_id()) {
+ case telegram_api::secureValueTypePersonalDetails::ID:
+ return SecureValueType::PersonalDetails;
+ case telegram_api::secureValueTypePassport::ID:
+ return SecureValueType::Passport;
+ case telegram_api::secureValueTypeDriverLicense::ID:
+ return SecureValueType::DriverLicense;
+ case telegram_api::secureValueTypeIdentityCard::ID:
+ return SecureValueType::IdentityCard;
+ case telegram_api::secureValueTypeInternalPassport::ID:
+ return SecureValueType::InternalPassport;
+ case telegram_api::secureValueTypeAddress::ID:
+ return SecureValueType::Address;
+ case telegram_api::secureValueTypeUtilityBill::ID:
+ return SecureValueType::UtilityBill;
+ case telegram_api::secureValueTypeBankStatement::ID:
+ return SecureValueType::BankStatement;
+ case telegram_api::secureValueTypeRentalAgreement::ID:
+ return SecureValueType::RentalAgreement;
+ case telegram_api::secureValueTypePassportRegistration::ID:
+ return SecureValueType::PassportRegistration;
+ case telegram_api::secureValueTypeTemporaryRegistration::ID:
+ return SecureValueType::TemporaryRegistration;
+ case telegram_api::secureValueTypePhone::ID:
+ return SecureValueType::PhoneNumber;
+ case telegram_api::secureValueTypeEmail::ID:
+ return SecureValueType::EmailAddress;
+ default:
+ UNREACHABLE();
+ return SecureValueType::None;
+ }
+}
+
+SecureValueType get_secure_value_type_td_api(const tl_object_ptr<td_api::PassportElementType> &passport_element_type) {
+ CHECK(passport_element_type != nullptr);
+ switch (passport_element_type->get_id()) {
+ case td_api::passportElementTypePersonalDetails::ID:
+ return SecureValueType::PersonalDetails;
+ case td_api::passportElementTypePassport::ID:
+ return SecureValueType::Passport;
+ case td_api::passportElementTypeDriverLicense::ID:
+ return SecureValueType::DriverLicense;
+ case td_api::passportElementTypeIdentityCard::ID:
+ return SecureValueType::IdentityCard;
+ case td_api::passportElementTypeInternalPassport::ID:
+ return SecureValueType::InternalPassport;
+ case td_api::passportElementTypeAddress::ID:
+ return SecureValueType::Address;
+ case td_api::passportElementTypeUtilityBill::ID:
+ return SecureValueType::UtilityBill;
+ case td_api::passportElementTypeBankStatement::ID:
+ return SecureValueType::BankStatement;
+ case td_api::passportElementTypeRentalAgreement::ID:
+ return SecureValueType::RentalAgreement;
+ case td_api::passportElementTypePassportRegistration::ID:
+ return SecureValueType::PassportRegistration;
+ case td_api::passportElementTypeTemporaryRegistration::ID:
+ return SecureValueType::TemporaryRegistration;
+ case td_api::passportElementTypePhoneNumber::ID:
+ return SecureValueType::PhoneNumber;
+ case td_api::passportElementTypeEmailAddress::ID:
+ return SecureValueType::EmailAddress;
+ default:
+ UNREACHABLE();
+ return SecureValueType::None;
+ }
+}
+
+vector<SecureValueType> unique_secure_value_types(vector<SecureValueType> types) {
+ size_t size = types.size();
+ for (size_t i = 0; i < size; i++) {
+ for (size_t j = 0; j < i; j++) {
+ if (types[i] == types[j]) {
+ LOG(ERROR) << "Have duplicate passport element type " << types[i] << " at positions " << i << " and " << j;
+ types[i--] = types[--size];
+ break;
+ }
+ }
+ }
+ types.resize(size);
+ return types;
+}
+
+vector<SecureValueType> get_secure_value_types(
+ const vector<tl_object_ptr<telegram_api::SecureValueType>> &secure_value_types) {
+ return unique_secure_value_types(transform(secure_value_types, get_secure_value_type));
+}
+
+vector<SecureValueType> get_secure_value_types_td_api(
+ const vector<tl_object_ptr<td_api::PassportElementType>> &secure_value_types) {
+ return unique_secure_value_types(transform(secure_value_types, get_secure_value_type_td_api));
+}
+
+td_api::object_ptr<td_api::PassportElementType> get_passport_element_type_object(SecureValueType type) {
+ switch (type) {
+ case SecureValueType::PersonalDetails:
+ return td_api::make_object<td_api::passportElementTypePersonalDetails>();
+ case SecureValueType::Passport:
+ return td_api::make_object<td_api::passportElementTypePassport>();
+ case SecureValueType::DriverLicense:
+ return td_api::make_object<td_api::passportElementTypeDriverLicense>();
+ case SecureValueType::IdentityCard:
+ return td_api::make_object<td_api::passportElementTypeIdentityCard>();
+ case SecureValueType::InternalPassport:
+ return td_api::make_object<td_api::passportElementTypeInternalPassport>();
+ case SecureValueType::Address:
+ return td_api::make_object<td_api::passportElementTypeAddress>();
+ case SecureValueType::UtilityBill:
+ return td_api::make_object<td_api::passportElementTypeUtilityBill>();
+ case SecureValueType::BankStatement:
+ return td_api::make_object<td_api::passportElementTypeBankStatement>();
+ case SecureValueType::RentalAgreement:
+ return td_api::make_object<td_api::passportElementTypeRentalAgreement>();
+ case SecureValueType::PassportRegistration:
+ return td_api::make_object<td_api::passportElementTypePassportRegistration>();
+ case SecureValueType::TemporaryRegistration:
+ return td_api::make_object<td_api::passportElementTypeTemporaryRegistration>();
+ case SecureValueType::PhoneNumber:
+ return td_api::make_object<td_api::passportElementTypePhoneNumber>();
+ case SecureValueType::EmailAddress:
+ return td_api::make_object<td_api::passportElementTypeEmailAddress>();
+ case SecureValueType::None:
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+td_api::object_ptr<telegram_api::SecureValueType> get_input_secure_value_type(SecureValueType type) {
+ switch (type) {
+ case SecureValueType::PersonalDetails:
+ return telegram_api::make_object<telegram_api::secureValueTypePersonalDetails>();
+ case SecureValueType::Passport:
+ return telegram_api::make_object<telegram_api::secureValueTypePassport>();
+ case SecureValueType::DriverLicense:
+ return telegram_api::make_object<telegram_api::secureValueTypeDriverLicense>();
+ case SecureValueType::IdentityCard:
+ return telegram_api::make_object<telegram_api::secureValueTypeIdentityCard>();
+ case SecureValueType::InternalPassport:
+ return telegram_api::make_object<telegram_api::secureValueTypeInternalPassport>();
+ case SecureValueType::Address:
+ return telegram_api::make_object<telegram_api::secureValueTypeAddress>();
+ case SecureValueType::UtilityBill:
+ return telegram_api::make_object<telegram_api::secureValueTypeUtilityBill>();
+ case SecureValueType::BankStatement:
+ return telegram_api::make_object<telegram_api::secureValueTypeBankStatement>();
+ case SecureValueType::RentalAgreement:
+ return telegram_api::make_object<telegram_api::secureValueTypeRentalAgreement>();
+ case SecureValueType::PassportRegistration:
+ return telegram_api::make_object<telegram_api::secureValueTypePassportRegistration>();
+ case SecureValueType::TemporaryRegistration:
+ return telegram_api::make_object<telegram_api::secureValueTypeTemporaryRegistration>();
+ case SecureValueType::PhoneNumber:
+ return telegram_api::make_object<telegram_api::secureValueTypePhone>();
+ case SecureValueType::EmailAddress:
+ return telegram_api::make_object<telegram_api::secureValueTypeEmail>();
+ case SecureValueType::None:
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+vector<td_api::object_ptr<td_api::PassportElementType>> get_passport_element_types_object(
+ const vector<SecureValueType> &types) {
+ return transform(types, get_passport_element_type_object);
+}
+
+SuitableSecureValue get_suitable_secure_value(
+ const tl_object_ptr<telegram_api::secureRequiredType> &secure_required_type) {
+ SuitableSecureValue result;
+ result.type = get_secure_value_type(secure_required_type->type_);
+ result.is_selfie_required = secure_required_type->selfie_required_;
+ result.is_translation_required = secure_required_type->translation_required_;
+ result.is_native_name_required = secure_required_type->native_names_;
+ return result;
+}
+
+td_api::object_ptr<td_api::passportSuitableElement> get_passport_suitable_element_object(
+ const SuitableSecureValue &element) {
+ return td_api::make_object<td_api::passportSuitableElement>(
+ get_passport_element_type_object(element.type), element.is_selfie_required, element.is_translation_required,
+ element.is_native_name_required);
+}
+
+td_api::object_ptr<td_api::passportRequiredElement> get_passport_required_element_object(
+ const vector<SuitableSecureValue> &required_element) {
+ return td_api::make_object<td_api::passportRequiredElement>(
+ transform(required_element, get_passport_suitable_element_object));
+}
+
+vector<td_api::object_ptr<td_api::passportRequiredElement>> get_passport_required_elements_object(
+ const vector<vector<SuitableSecureValue>> &required_elements) {
+ return transform(required_elements, get_passport_required_element_object);
+}
+
+string get_secure_value_data_field_name(SecureValueType type, string field_name) {
+ switch (type) {
+ case SecureValueType::PersonalDetails:
+ if (field_name == "first_name" || field_name == "middle_name" || field_name == "last_name" ||
+ field_name == "gender" || field_name == "country_code" || field_name == "residence_country_code") {
+ return field_name;
+ }
+ if (field_name == "first_name_native") {
+ return "native_first_name";
+ }
+ if (field_name == "middle_name_native") {
+ return "native_middle_name";
+ }
+ if (field_name == "last_name_native") {
+ return "native_last_name";
+ }
+ if (field_name == "birth_date") {
+ return "birthdate";
+ }
+ break;
+ case SecureValueType::Passport:
+ case SecureValueType::DriverLicense:
+ case SecureValueType::IdentityCard:
+ case SecureValueType::InternalPassport:
+ if (field_name == "expiry_date") {
+ return field_name;
+ }
+ if (field_name == "document_no") {
+ return "number";
+ }
+ break;
+ case SecureValueType::Address:
+ if (field_name == "state" || field_name == "city" || field_name == "street_line1" ||
+ field_name == "street_line2" || field_name == "country_code") {
+ return field_name;
+ }
+ if (field_name == "post_code") {
+ return "postal_code";
+ }
+ break;
+ case SecureValueType::UtilityBill:
+ case SecureValueType::BankStatement:
+ case SecureValueType::RentalAgreement:
+ case SecureValueType::PassportRegistration:
+ case SecureValueType::TemporaryRegistration:
+ case SecureValueType::PhoneNumber:
+ case SecureValueType::EmailAddress:
+ break;
+ case SecureValueType::None:
+ default:
+ UNREACHABLE();
+ break;
+ }
+ LOG(ERROR) << "Receive error about unknown field \"" << field_name << "\" in type " << type;
+ return string();
+}
+
+bool operator==(const DatedFile &lhs, const DatedFile &rhs) {
+ return lhs.file_id == rhs.file_id && lhs.date == rhs.date;
+}
+
+bool operator!=(const DatedFile &lhs, const DatedFile &rhs) {
+ return !(lhs == rhs);
+}
+
+bool operator==(const EncryptedSecureFile &lhs, const EncryptedSecureFile &rhs) {
+ return lhs.file == rhs.file && lhs.file_hash == rhs.file_hash && lhs.encrypted_secret == rhs.encrypted_secret;
+}
+
+bool operator!=(const EncryptedSecureFile &lhs, const EncryptedSecureFile &rhs) {
+ return !(lhs == rhs);
+}
+
+EncryptedSecureFile get_encrypted_secure_file(FileManager *file_manager,
+ tl_object_ptr<telegram_api::SecureFile> &&secure_file_ptr) {
+ CHECK(secure_file_ptr != nullptr);
+ EncryptedSecureFile result;
+ switch (secure_file_ptr->get_id()) {
+ case telegram_api::secureFileEmpty::ID:
+ break;
+ case telegram_api::secureFile::ID: {
+ auto secure_file = telegram_api::move_object_as<telegram_api::secureFile>(secure_file_ptr);
+ auto dc_id = secure_file->dc_id_;
+ if (!DcId::is_valid(dc_id)) {
+ LOG(ERROR) << "Wrong dc_id = " << dc_id;
+ break;
+ }
+ result.file.file_id = file_manager->register_remote(
+ FullRemoteFileLocation(FileType::SecureEncrypted, secure_file->id_, secure_file->access_hash_,
+ DcId::internal(dc_id), ""),
+ FileLocationSource::FromServer, DialogId(), secure_file->size_, 0, PSTRING() << secure_file->id_ << ".jpg");
+ result.file.date = secure_file->date_;
+ if (result.file.date < 0) {
+ LOG(ERROR) << "Receive wrong date " << result.file.date;
+ result.file.date = 0;
+ }
+ result.encrypted_secret = secure_file->secret_.as_slice().str();
+ result.file_hash = secure_file->file_hash_.as_slice().str();
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ return result;
+}
+
+vector<EncryptedSecureFile> get_encrypted_secure_files(FileManager *file_manager,
+ vector<tl_object_ptr<telegram_api::SecureFile>> &&secure_files) {
+ vector<EncryptedSecureFile> results;
+ results.reserve(secure_files.size());
+ for (auto &secure_file : secure_files) {
+ auto result = get_encrypted_secure_file(file_manager, std::move(secure_file));
+ if (result.file.file_id.is_valid()) {
+ results.push_back(std::move(result));
+ }
+ }
+ return results;
+}
+
+telegram_api::object_ptr<telegram_api::InputSecureFile> get_input_secure_file_object(FileManager *file_manager,
+ const EncryptedSecureFile &file,
+ SecureInputFile &input_file) {
+ if (!file.file.file_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid EncryptedSecureFile";
+ return nullptr;
+ }
+ CHECK(input_file.file_id.is_valid());
+ CHECK(file_manager->get_file_view(file.file.file_id).get_main_file_id() ==
+ file_manager->get_file_view(input_file.file_id).get_main_file_id());
+ auto res = std::move(input_file.input_file);
+ if (res == nullptr) {
+ return file_manager->get_file_view(file.file.file_id).remote_location().as_input_secure_file();
+ }
+ telegram_api::downcast_call(*res, overloaded(
+ [&](telegram_api::inputSecureFileUploaded &uploaded) {
+ uploaded.secret_ = BufferSlice(file.encrypted_secret);
+ uploaded.file_hash_ = BufferSlice(file.file_hash);
+ },
+ [&](telegram_api::inputSecureFile &) { UNREACHABLE(); }));
+ return res;
+}
+
+static td_api::object_ptr<td_api::datedFile> get_dated_file_object(FileManager *file_manager, DatedFile file) {
+ return td_api::make_object<td_api::datedFile>(file_manager->get_file_object(file.file_id), file.date);
+}
+
+static vector<td_api::object_ptr<td_api::datedFile>> get_dated_files_object(FileManager *file_manager,
+ const vector<DatedFile> &files) {
+ return transform(files, [file_manager](const DatedFile &file) { return get_dated_file_object(file_manager, file); });
+}
+
+static td_api::object_ptr<td_api::datedFile> get_dated_file_object(FileManager *file_manager,
+ const EncryptedSecureFile &file) {
+ DatedFile dated_file = file.file;
+ auto file_id = dated_file.file_id;
+ CHECK(file_id.is_valid());
+ auto file_view = file_manager->get_file_view(file_id);
+ if (!file_view.has_remote_location() || file_view.remote_location().is_web()) {
+ LOG(ERROR) << "Have wrong file in get_dated_file_object";
+ return nullptr;
+ }
+ if (file_view.get_type() != FileType::SecureEncrypted) {
+ LOG(ERROR) << "Have file of a wrong type in get_dated_file_object";
+ } else if (file_view.encryption_key().empty()) {
+ return get_dated_file_object(file_manager, dated_file);
+ }
+ dated_file.file_id = file_manager->register_remote(
+ FullRemoteFileLocation(FileType::SecureDecrypted, file_view.remote_location().get_id(),
+ file_view.remote_location().get_access_hash(), file_view.remote_location().get_dc_id(),
+ ""),
+ FileLocationSource::FromServer, DialogId(), 0, file_view.expected_size(), file_view.suggested_path());
+ return get_dated_file_object(file_manager, dated_file);
+}
+
+static vector<td_api::object_ptr<td_api::datedFile>> get_dated_files_object(FileManager *file_manager,
+ const vector<EncryptedSecureFile> &files) {
+ return transform(
+ files, [file_manager](const EncryptedSecureFile &file) { return get_dated_file_object(file_manager, file); });
+}
+
+vector<telegram_api::object_ptr<telegram_api::InputSecureFile>> get_input_secure_files_object(
+ FileManager *file_manager, const vector<EncryptedSecureFile> &files, vector<SecureInputFile> &input_files) {
+ CHECK(files.size() == input_files.size());
+ vector<telegram_api::object_ptr<telegram_api::InputSecureFile>> results;
+ results.reserve(files.size());
+ for (size_t i = 0; i < files.size(); i++) {
+ auto result = get_input_secure_file_object(file_manager, files[i], input_files[i]);
+ if (result != nullptr) {
+ results.push_back(std::move(result));
+ }
+ }
+ return results;
+}
+
+bool operator==(const EncryptedSecureData &lhs, const EncryptedSecureData &rhs) {
+ return lhs.data == rhs.data && lhs.hash == rhs.hash && lhs.encrypted_secret == rhs.encrypted_secret;
+}
+
+bool operator!=(const EncryptedSecureData &lhs, const EncryptedSecureData &rhs) {
+ return !(lhs == rhs);
+}
+
+EncryptedSecureData get_encrypted_secure_data(tl_object_ptr<telegram_api::secureData> &&secure_data) {
+ CHECK(secure_data != nullptr);
+ EncryptedSecureData result;
+ result.data = secure_data->data_.as_slice().str();
+ result.hash = secure_data->data_hash_.as_slice().str();
+ result.encrypted_secret = secure_data->secret_.as_slice().str();
+ return result;
+}
+
+telegram_api::object_ptr<telegram_api::secureData> get_secure_data_object(const EncryptedSecureData &data) {
+ return telegram_api::make_object<telegram_api::secureData>(BufferSlice(data.data), BufferSlice(data.hash),
+ BufferSlice(data.encrypted_secret));
+}
+
+bool operator==(const EncryptedSecureValue &lhs, const EncryptedSecureValue &rhs) {
+ return lhs.type == rhs.type && lhs.data == rhs.data && lhs.files == rhs.files && lhs.front_side == rhs.front_side &&
+ lhs.reverse_side == rhs.reverse_side && lhs.selfie == rhs.selfie && lhs.translations == rhs.translations;
+}
+
+bool operator!=(const EncryptedSecureValue &lhs, const EncryptedSecureValue &rhs) {
+ return !(lhs == rhs);
+}
+
+static bool check_encrypted_secure_value(const EncryptedSecureValue &value) {
+ bool has_encrypted_data = !value.data.hash.empty();
+ bool has_plain_data = !has_encrypted_data && !value.data.data.empty();
+ bool has_files = !value.files.empty();
+ bool has_front_side = value.front_side.file.file_id.is_valid();
+ bool has_reverse_side = value.reverse_side.file.file_id.is_valid();
+ bool has_selfie = value.selfie.file.file_id.is_valid();
+ bool has_translations = !value.translations.empty();
+ switch (value.type) {
+ case SecureValueType::PersonalDetails:
+ case SecureValueType::Address:
+ return has_encrypted_data && !has_files && !has_front_side && !has_reverse_side && !has_selfie &&
+ !has_translations;
+ case SecureValueType::Passport:
+ case SecureValueType::InternalPassport:
+ return has_encrypted_data && !has_files && has_front_side && !has_reverse_side;
+ case SecureValueType::DriverLicense:
+ case SecureValueType::IdentityCard:
+ return has_encrypted_data && !has_files && has_front_side && has_reverse_side;
+ case SecureValueType::UtilityBill:
+ case SecureValueType::BankStatement:
+ case SecureValueType::RentalAgreement:
+ case SecureValueType::PassportRegistration:
+ case SecureValueType::TemporaryRegistration:
+ return !has_encrypted_data && !has_plain_data && has_files && !has_front_side && !has_reverse_side && !has_selfie;
+ case SecureValueType::PhoneNumber:
+ case SecureValueType::EmailAddress:
+ return has_plain_data && !has_files && !has_front_side && !has_reverse_side && !has_selfie && !has_translations;
+ case SecureValueType::None:
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+EncryptedSecureValue get_encrypted_secure_value(FileManager *file_manager,
+ tl_object_ptr<telegram_api::secureValue> &&secure_value) {
+ EncryptedSecureValue result;
+ CHECK(secure_value != nullptr);
+ result.type = get_secure_value_type(secure_value->type_);
+ if (secure_value->plain_data_ != nullptr) {
+ switch (secure_value->plain_data_->get_id()) {
+ case telegram_api::securePlainPhone::ID:
+ result.data.data =
+ std::move(static_cast<telegram_api::securePlainPhone *>(secure_value->plain_data_.get())->phone_);
+ break;
+ case telegram_api::securePlainEmail::ID:
+ result.data.data =
+ std::move(static_cast<telegram_api::securePlainEmail *>(secure_value->plain_data_.get())->email_);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+ if (secure_value->data_ != nullptr) {
+ result.data = get_encrypted_secure_data(std::move(secure_value->data_));
+ }
+ result.files = get_encrypted_secure_files(file_manager, std::move(secure_value->files_));
+ if (secure_value->front_side_ != nullptr) {
+ result.front_side = get_encrypted_secure_file(file_manager, std::move(secure_value->front_side_));
+ }
+ if (secure_value->reverse_side_ != nullptr) {
+ result.reverse_side = get_encrypted_secure_file(file_manager, std::move(secure_value->reverse_side_));
+ }
+ if (secure_value->selfie_ != nullptr) {
+ result.selfie = get_encrypted_secure_file(file_manager, std::move(secure_value->selfie_));
+ }
+ result.translations = get_encrypted_secure_files(file_manager, std::move(secure_value->translation_));
+ result.hash = secure_value->hash_.as_slice().str();
+ if (!check_encrypted_secure_value(result)) {
+ LOG(ERROR) << "Receive invalid encrypted secure value of type " << result.type;
+ return EncryptedSecureValue();
+ }
+ return result;
+}
+
+vector<EncryptedSecureValue> get_encrypted_secure_values(
+ FileManager *file_manager, vector<tl_object_ptr<telegram_api::secureValue>> &&secure_values) {
+ vector<EncryptedSecureValue> results;
+ results.reserve(secure_values.size());
+ for (auto &secure_value : secure_values) {
+ auto result = get_encrypted_secure_value(file_manager, std::move(secure_value));
+ if (result.type != SecureValueType::None) {
+ results.push_back(std::move(result));
+ }
+ }
+ return results;
+}
+
+td_api::object_ptr<td_api::encryptedPassportElement> get_encrypted_passport_element_object(
+ FileManager *file_manager, const EncryptedSecureValue &value) {
+ bool is_plain = value.data.hash.empty();
+ return td_api::make_object<td_api::encryptedPassportElement>(
+ get_passport_element_type_object(value.type), is_plain ? string() : value.data.data,
+ value.front_side.file.file_id.is_valid() ? get_dated_file_object(file_manager, value.front_side) : nullptr,
+ value.reverse_side.file.file_id.is_valid() ? get_dated_file_object(file_manager, value.reverse_side) : nullptr,
+ value.selfie.file.file_id.is_valid() ? get_dated_file_object(file_manager, value.selfie) : nullptr,
+ get_dated_files_object(file_manager, value.translations), get_dated_files_object(file_manager, value.files),
+ is_plain ? value.data.data : string(), value.hash);
+}
+
+telegram_api::object_ptr<telegram_api::inputSecureValue> get_input_secure_value_object(
+ FileManager *file_manager, const EncryptedSecureValue &value, std::vector<SecureInputFile> &files,
+ optional<SecureInputFile> &front_side, optional<SecureInputFile> &reverse_side, optional<SecureInputFile> &selfie,
+ std::vector<SecureInputFile> &translations) {
+ bool is_plain = value.type == SecureValueType::PhoneNumber || value.type == SecureValueType::EmailAddress;
+ bool has_front_side = value.front_side.file.file_id.is_valid();
+ bool has_reverse_side = value.reverse_side.file.file_id.is_valid();
+ bool has_selfie = value.selfie.file.file_id.is_valid();
+ int32 flags = 0;
+ tl_object_ptr<telegram_api::SecurePlainData> plain_data;
+ if (is_plain) {
+ if (value.type == SecureValueType::PhoneNumber) {
+ plain_data = make_tl_object<telegram_api::securePlainPhone>(value.data.data);
+ } else {
+ plain_data = make_tl_object<telegram_api::securePlainEmail>(value.data.data);
+ }
+ flags |= telegram_api::inputSecureValue::PLAIN_DATA_MASK;
+ } else {
+ flags |= telegram_api::inputSecureValue::DATA_MASK;
+ }
+ if (!value.files.empty()) {
+ flags |= telegram_api::inputSecureValue::FILES_MASK;
+ }
+ if (has_front_side) {
+ flags |= telegram_api::inputSecureValue::FRONT_SIDE_MASK;
+ CHECK(front_side);
+ }
+ if (has_reverse_side) {
+ flags |= telegram_api::inputSecureValue::REVERSE_SIDE_MASK;
+ CHECK(reverse_side);
+ }
+ if (has_selfie) {
+ flags |= telegram_api::inputSecureValue::SELFIE_MASK;
+ CHECK(selfie);
+ }
+ if (!value.translations.empty()) {
+ flags |= telegram_api::inputSecureValue::TRANSLATION_MASK;
+ }
+ return telegram_api::make_object<telegram_api::inputSecureValue>(
+ flags, get_input_secure_value_type(value.type), is_plain ? nullptr : get_secure_data_object(value.data),
+ has_front_side ? get_input_secure_file_object(file_manager, value.front_side, *front_side) : nullptr,
+ has_reverse_side ? get_input_secure_file_object(file_manager, value.reverse_side, *reverse_side) : nullptr,
+ has_selfie ? get_input_secure_file_object(file_manager, value.selfie, *selfie) : nullptr,
+ get_input_secure_files_object(file_manager, value.translations, translations),
+ get_input_secure_files_object(file_manager, value.files, files), std::move(plain_data));
+}
+
+vector<td_api::object_ptr<td_api::encryptedPassportElement>> get_encrypted_passport_element_object(
+ FileManager *file_manager, const vector<EncryptedSecureValue> &values) {
+ return transform(values, [file_manager](const EncryptedSecureValue &value) {
+ return get_encrypted_passport_element_object(file_manager, value);
+ });
+}
+
+bool operator==(const EncryptedSecureCredentials &lhs, const EncryptedSecureCredentials &rhs) {
+ return lhs.data == rhs.data && lhs.hash == rhs.hash && lhs.encrypted_secret == rhs.encrypted_secret;
+}
+
+bool operator!=(const EncryptedSecureCredentials &lhs, const EncryptedSecureCredentials &rhs) {
+ return !(lhs == rhs);
+}
+
+telegram_api::object_ptr<telegram_api::secureCredentialsEncrypted> get_secure_credentials_encrypted_object(
+ const EncryptedSecureCredentials &credentials) {
+ return telegram_api::make_object<telegram_api::secureCredentialsEncrypted>(
+ BufferSlice(credentials.data), BufferSlice(credentials.hash), BufferSlice(credentials.encrypted_secret));
+}
+
+EncryptedSecureCredentials get_encrypted_secure_credentials(
+ tl_object_ptr<telegram_api::secureCredentialsEncrypted> &&credentials) {
+ CHECK(credentials != nullptr);
+ EncryptedSecureCredentials result;
+ result.data = credentials->data_.as_slice().str();
+ result.hash = credentials->hash_.as_slice().str();
+ result.encrypted_secret = credentials->secret_.as_slice().str();
+ return result;
+}
+
+td_api::object_ptr<td_api::encryptedCredentials> get_encrypted_credentials_object(
+ const EncryptedSecureCredentials &credentials) {
+ return td_api::make_object<td_api::encryptedCredentials>(credentials.data, credentials.hash,
+ credentials.encrypted_secret);
+}
+
+// TODO tests
+static Status check_date(int32 day, int32 month, int32 year) {
+ if (day < 1 || day > 31) {
+ return Status::Error(400, "Wrong day number specified");
+ }
+ if (month < 1 || month > 12) {
+ return Status::Error(400, "Wrong month number specified");
+ }
+ if (year < 1 || year > 9999) {
+ return Status::Error(400, "Wrong year number specified");
+ }
+
+ bool is_leap = month == 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
+ const int32 days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ if (day > days_in_month[month - 1] + static_cast<int32>(is_leap)) {
+ return Status::Error(400, "Wrong day in month number specified");
+ }
+
+ return Status::OK();
+}
+
+static Result<string> get_date(td_api::object_ptr<td_api::date> &&date) {
+ if (date == nullptr) {
+ return string();
+ }
+ TRY_STATUS(check_date(date->day_, date->month_, date->year_));
+
+ return PSTRING() << lpad0(to_string(date->day_), 2) << '.' << lpad0(to_string(date->month_), 2) << '.'
+ << lpad0(to_string(date->year_), 4);
+}
+
+static Result<int32> to_int32(Slice str) {
+ CHECK(str.size() <= static_cast<size_t>(std::numeric_limits<int32>::digits10));
+ int32 integer_value = 0;
+ for (auto c : str) {
+ if (!is_digit(c)) {
+ return Status::Error(400, PSLICE() << "Can't parse \"" << utf8_encode(str.str()) << "\" as number");
+ }
+ integer_value = integer_value * 10 + c - '0';
+ }
+ return integer_value;
+}
+
+static Result<td_api::object_ptr<td_api::date>> get_date_object(Slice date) {
+ if (date.empty()) {
+ return nullptr;
+ }
+ if (date.size() > 10u || date.size() < 8u) {
+ return Status::Error(400, PSLICE() << "Date \"" << utf8_encode(date.str()) << "\" has wrong length");
+ }
+ auto parts = full_split(date, '.');
+ if (parts.size() != 3 || parts[0].size() > 2 || parts[1].size() > 2 || parts[2].size() != 4 || parts[0].empty() ||
+ parts[1].empty()) {
+ return Status::Error(400, PSLICE() << "Date \"" << utf8_encode(date.str()) << "\" has wrong parts");
+ }
+ TRY_RESULT(day, to_int32(parts[0]));
+ TRY_RESULT(month, to_int32(parts[1]));
+ TRY_RESULT(year, to_int32(parts[2]));
+ TRY_STATUS(check_date(day, month, year));
+
+ return td_api::make_object<td_api::date>(day, month, year);
+}
+
+static Status check_name(string &name) {
+ if (!clean_input_string(name)) {
+ return Status::Error(400, "Name must be encoded in UTF-8");
+ }
+ if (utf8_length(name) > 255) {
+ return Status::Error(400, "Name is too long");
+ }
+ return Status::OK();
+}
+
+static Status check_gender(string &gender) {
+ if (gender != "male" && gender != "female") {
+ return Status::Error(400, "Unsupported gender specified");
+ }
+ return Status::OK();
+}
+
+static Result<string> get_personal_details(td_api::object_ptr<td_api::personalDetails> &&personal_details) {
+ if (personal_details == nullptr) {
+ return Status::Error(400, "Personal details must be non-empty");
+ }
+ TRY_STATUS(check_name(personal_details->first_name_));
+ TRY_STATUS(check_name(personal_details->middle_name_));
+ TRY_STATUS(check_name(personal_details->last_name_));
+ TRY_STATUS(check_name(personal_details->native_first_name_));
+ TRY_STATUS(check_name(personal_details->native_middle_name_));
+ TRY_STATUS(check_name(personal_details->native_last_name_));
+ TRY_RESULT(birthdate, get_date(std::move(personal_details->birthdate_)));
+ if (birthdate.empty()) {
+ return Status::Error(400, "Birthdate must be non-empty");
+ }
+ TRY_STATUS(check_gender(personal_details->gender_));
+ TRY_STATUS(check_country_code(personal_details->country_code_));
+ TRY_STATUS(check_country_code(personal_details->residence_country_code_));
+
+ return json_encode<std::string>(json_object([&](auto &o) {
+ o("first_name", personal_details->first_name_);
+ o("middle_name", personal_details->middle_name_);
+ o("last_name", personal_details->last_name_);
+ o("first_name_native", personal_details->native_first_name_);
+ o("middle_name_native", personal_details->native_middle_name_);
+ o("last_name_native", personal_details->native_last_name_);
+ o("birth_date", birthdate);
+ o("gender", personal_details->gender_);
+ o("country_code", personal_details->country_code_);
+ o("residence_country_code", personal_details->residence_country_code_);
+ }));
+}
+
+static Result<td_api::object_ptr<td_api::personalDetails>> get_personal_details_object(Slice personal_details) {
+ auto personal_details_copy = personal_details.str();
+ auto r_value = json_decode(personal_details_copy);
+ if (r_value.is_error()) {
+ return Status::Error(400, "Can't parse personal details JSON object");
+ }
+
+ auto value = r_value.move_as_ok();
+ if (value.type() != JsonValue::Type::Object) {
+ return Status::Error(400, "Personal details must be an Object");
+ }
+
+ auto &object = value.get_object();
+ TRY_RESULT(first_name, get_json_object_string_field(object, "first_name", true));
+ TRY_RESULT(middle_name, get_json_object_string_field(object, "middle_name", true));
+ TRY_RESULT(last_name, get_json_object_string_field(object, "last_name", true));
+ TRY_RESULT(native_first_name, get_json_object_string_field(object, "first_name_native", true));
+ TRY_RESULT(native_middle_name, get_json_object_string_field(object, "middle_name_native", true));
+ TRY_RESULT(native_last_name, get_json_object_string_field(object, "last_name_native", true));
+ TRY_RESULT(birthdate, get_json_object_string_field(object, "birth_date", true));
+ if (birthdate.empty()) {
+ return Status::Error(400, "Birthdate must be non-empty");
+ }
+ TRY_RESULT(gender, get_json_object_string_field(object, "gender", true));
+ TRY_RESULT(country_code, get_json_object_string_field(object, "country_code", true));
+ TRY_RESULT(residence_country_code, get_json_object_string_field(object, "residence_country_code", true));
+
+ TRY_STATUS(check_name(first_name));
+ TRY_STATUS(check_name(middle_name));
+ TRY_STATUS(check_name(last_name));
+ TRY_STATUS(check_name(native_first_name));
+ TRY_STATUS(check_name(native_middle_name));
+ TRY_STATUS(check_name(native_last_name));
+ TRY_RESULT(date, get_date_object(birthdate));
+ TRY_STATUS(check_gender(gender));
+ TRY_STATUS(check_country_code(country_code));
+ TRY_STATUS(check_country_code(residence_country_code));
+
+ return td_api::make_object<td_api::personalDetails>(
+ std::move(first_name), std::move(middle_name), std::move(last_name), std::move(native_first_name),
+ std::move(native_middle_name), std::move(native_last_name), std::move(date), std::move(gender),
+ std::move(country_code), std::move(residence_country_code));
+}
+
+static Status check_document_number(string &number) {
+ if (!clean_input_string(number)) {
+ return Status::Error(400, "Document number must be encoded in UTF-8");
+ }
+ if (number.empty()) {
+ return Status::Error(400, "Document number must be non-empty");
+ }
+ if (utf8_length(number) > 24) {
+ return Status::Error(400, "Document number is too long");
+ }
+ return Status::OK();
+}
+
+static Result<DatedFile> get_secure_file(FileManager *file_manager, td_api::object_ptr<td_api::InputFile> &&file) {
+ TRY_RESULT(file_id,
+ file_manager->get_input_file_id(FileType::SecureEncrypted, file, DialogId(), false, false, false, true));
+ DatedFile result;
+ result.file_id = file_id;
+ result.date = G()->unix_time();
+ return std::move(result);
+}
+
+static Result<vector<DatedFile>> get_secure_files(FileManager *file_manager,
+ vector<td_api::object_ptr<td_api::InputFile>> &&files) {
+ vector<DatedFile> result;
+ for (auto &file : files) {
+ TRY_RESULT(dated_file, get_secure_file(file_manager, std::move(file)));
+ result.push_back(std::move(dated_file));
+ }
+ return result;
+}
+
+static Result<SecureValue> get_identity_document(SecureValueType type, FileManager *file_manager,
+ td_api::object_ptr<td_api::inputIdentityDocument> &&identity_document,
+ bool need_reverse_side) {
+ if (identity_document == nullptr) {
+ return Status::Error(400, "Identity document must be non-empty");
+ }
+ TRY_STATUS(check_document_number(identity_document->number_));
+ TRY_RESULT(date, get_date(std::move(identity_document->expiry_date_)));
+
+ SecureValue res;
+ res.type = type;
+ res.data = json_encode<std::string>(json_object([&](auto &o) {
+ o("document_no", identity_document->number_);
+ o("expiry_date", date);
+ }));
+
+ if (identity_document->front_side_ == nullptr) {
+ return Status::Error(400, "Document's front side is required");
+ }
+ if (identity_document->reverse_side_ == nullptr) {
+ if (need_reverse_side) {
+ return Status::Error(400, "Document's reverse side is required");
+ }
+ } else {
+ if (!need_reverse_side) {
+ return Status::Error(400, "Document can't have a reverse side");
+ }
+ }
+
+ TRY_RESULT_ASSIGN(res.front_side, get_secure_file(file_manager, std::move(identity_document->front_side_)));
+ if (identity_document->reverse_side_ != nullptr) {
+ TRY_RESULT_ASSIGN(res.reverse_side, get_secure_file(file_manager, std::move(identity_document->reverse_side_)));
+ }
+ if (identity_document->selfie_ != nullptr) {
+ TRY_RESULT_ASSIGN(res.selfie, get_secure_file(file_manager, std::move(identity_document->selfie_)));
+ }
+ if (!identity_document->translation_.empty()) {
+ TRY_RESULT_ASSIGN(res.translations, get_secure_files(file_manager, std::move(identity_document->translation_)));
+ }
+ return res;
+}
+
+static Result<td_api::object_ptr<td_api::identityDocument>> get_identity_document_object(FileManager *file_manager,
+ const SecureValue &value) {
+ CHECK(value.files.empty());
+
+ td_api::object_ptr<td_api::datedFile> front_side;
+ td_api::object_ptr<td_api::datedFile> reverse_side;
+ td_api::object_ptr<td_api::datedFile> selfie;
+ if (value.front_side.file_id.is_valid()) {
+ front_side = get_dated_file_object(file_manager, value.front_side);
+ }
+ if (value.reverse_side.file_id.is_valid()) {
+ reverse_side = get_dated_file_object(file_manager, value.reverse_side);
+ }
+ if (value.selfie.file_id.is_valid()) {
+ selfie = get_dated_file_object(file_manager, value.selfie);
+ }
+
+ auto data_copy = value.data;
+ auto r_value = json_decode(data_copy);
+ if (r_value.is_error()) {
+ return Status::Error(400, "Can't parse identity document JSON object");
+ }
+
+ auto json_value = r_value.move_as_ok();
+ if (json_value.type() != JsonValue::Type::Object) {
+ return Status::Error(400, "Identity document must be an Object");
+ }
+
+ auto &object = json_value.get_object();
+ TRY_RESULT(number, get_json_object_string_field(object, "document_no", true));
+ TRY_RESULT(expiry_date, get_json_object_string_field(object, "expiry_date", true));
+
+ TRY_STATUS(check_document_number(number));
+ TRY_RESULT(date, get_date_object(expiry_date));
+
+ auto translations = get_dated_files_object(file_manager, value.translations);
+ return td_api::make_object<td_api::identityDocument>(std::move(number), std::move(date), std::move(front_side),
+ std::move(reverse_side), std::move(selfie),
+ std::move(translations));
+}
+
+static Result<SecureValue> get_personal_document(
+ SecureValueType type, FileManager *file_manager,
+ td_api::object_ptr<td_api::inputPersonalDocument> &&personal_document) {
+ if (personal_document == nullptr) {
+ return Status::Error(400, "Personal document must be non-empty");
+ }
+
+ SecureValue res;
+ res.type = type;
+ if (personal_document->files_.empty()) {
+ return Status::Error(400, "Document's files are required");
+ }
+ TRY_RESULT_ASSIGN(res.files, get_secure_files(file_manager, std::move(personal_document->files_)));
+ if (!personal_document->translation_.empty()) {
+ TRY_RESULT_ASSIGN(res.translations, get_secure_files(file_manager, std::move(personal_document->translation_)));
+ }
+ return res;
+}
+
+static td_api::object_ptr<td_api::personalDocument> get_personal_document_object(FileManager *file_manager,
+ const SecureValue &value) {
+ return td_api::make_object<td_api::personalDocument>(get_dated_files_object(file_manager, value.files),
+ get_dated_files_object(file_manager, value.translations));
+}
+
+static Status check_phone_number(string &phone_number) {
+ if (!clean_input_string(phone_number)) {
+ return Status::Error(400, "Phone number must be encoded in UTF-8");
+ }
+ return Status::OK();
+}
+
+static Status check_email_address(string &email_address) {
+ if (!clean_input_string(email_address)) {
+ return Status::Error(400, "Email address must be encoded in UTF-8");
+ }
+ return Status::OK();
+}
+
+Result<SecureValue> get_secure_value(FileManager *file_manager,
+ td_api::object_ptr<td_api::InputPassportElement> &&input_passport_element) {
+ if (input_passport_element == nullptr) {
+ return Status::Error(400, "InputPassportElement must be non-empty");
+ }
+
+ SecureValue res;
+ switch (input_passport_element->get_id()) {
+ case td_api::inputPassportElementPersonalDetails::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementPersonalDetails>(input_passport_element);
+ res.type = SecureValueType::PersonalDetails;
+ TRY_RESULT_ASSIGN(res.data, get_personal_details(std::move(input->personal_details_)));
+ break;
+ }
+ case td_api::inputPassportElementPassport::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementPassport>(input_passport_element);
+ return get_identity_document(SecureValueType::Passport, file_manager, std::move(input->passport_), false);
+ }
+ case td_api::inputPassportElementDriverLicense::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementDriverLicense>(input_passport_element);
+ return get_identity_document(SecureValueType::DriverLicense, file_manager, std::move(input->driver_license_),
+ true);
+ }
+ case td_api::inputPassportElementIdentityCard::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementIdentityCard>(input_passport_element);
+ return get_identity_document(SecureValueType::IdentityCard, file_manager, std::move(input->identity_card_), true);
+ }
+ case td_api::inputPassportElementInternalPassport::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementInternalPassport>(input_passport_element);
+ return get_identity_document(SecureValueType::InternalPassport, file_manager,
+ std::move(input->internal_passport_), false);
+ }
+ case td_api::inputPassportElementAddress::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementAddress>(input_passport_element);
+ res.type = SecureValueType::Address;
+ TRY_RESULT(address, get_address(std::move(input->address_)));
+ res.data = address_to_json(address);
+ break;
+ }
+ case td_api::inputPassportElementUtilityBill::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementUtilityBill>(input_passport_element);
+ return get_personal_document(SecureValueType::UtilityBill, file_manager, std::move(input->utility_bill_));
+ }
+ case td_api::inputPassportElementBankStatement::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementBankStatement>(input_passport_element);
+ return get_personal_document(SecureValueType::BankStatement, file_manager, std::move(input->bank_statement_));
+ }
+ case td_api::inputPassportElementRentalAgreement::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementRentalAgreement>(input_passport_element);
+ return get_personal_document(SecureValueType::RentalAgreement, file_manager, std::move(input->rental_agreement_));
+ }
+ case td_api::inputPassportElementPassportRegistration::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementPassportRegistration>(input_passport_element);
+ return get_personal_document(SecureValueType::PassportRegistration, file_manager,
+ std::move(input->passport_registration_));
+ }
+ case td_api::inputPassportElementTemporaryRegistration::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementTemporaryRegistration>(input_passport_element);
+ return get_personal_document(SecureValueType::TemporaryRegistration, file_manager,
+ std::move(input->temporary_registration_));
+ }
+ case td_api::inputPassportElementPhoneNumber::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementPhoneNumber>(input_passport_element);
+ res.type = SecureValueType::PhoneNumber;
+ TRY_STATUS(check_phone_number(input->phone_number_));
+ res.data = std::move(input->phone_number_);
+ break;
+ }
+ case td_api::inputPassportElementEmailAddress::ID: {
+ auto input = td_api::move_object_as<td_api::inputPassportElementEmailAddress>(input_passport_element);
+ res.type = SecureValueType::EmailAddress;
+ TRY_STATUS(check_email_address(input->email_address_));
+ res.data = std::move(input->email_address_);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ return res;
+}
+
+Result<td_api::object_ptr<td_api::PassportElement>> get_passport_element_object(FileManager *file_manager,
+ const SecureValue &value) {
+ switch (value.type) {
+ case SecureValueType::PersonalDetails: {
+ TRY_RESULT(personal_details, get_personal_details_object(value.data));
+ return td_api::make_object<td_api::passportElementPersonalDetails>(std::move(personal_details));
+ }
+ case SecureValueType::Passport: {
+ TRY_RESULT(passport, get_identity_document_object(file_manager, value));
+ return td_api::make_object<td_api::passportElementPassport>(std::move(passport));
+ }
+ case SecureValueType::DriverLicense: {
+ TRY_RESULT(driver_license, get_identity_document_object(file_manager, value));
+ return td_api::make_object<td_api::passportElementDriverLicense>(std::move(driver_license));
+ }
+ case SecureValueType::IdentityCard: {
+ TRY_RESULT(identity_card, get_identity_document_object(file_manager, value));
+ return td_api::make_object<td_api::passportElementIdentityCard>(std::move(identity_card));
+ }
+ case SecureValueType::InternalPassport: {
+ TRY_RESULT(internal_passport, get_identity_document_object(file_manager, value));
+ return td_api::make_object<td_api::passportElementInternalPassport>(std::move(internal_passport));
+ }
+ case SecureValueType::Address: {
+ TRY_RESULT(address, address_from_json(value.data));
+ return td_api::make_object<td_api::passportElementAddress>(get_address_object(address));
+ }
+ case SecureValueType::UtilityBill:
+ case SecureValueType::BankStatement:
+ case SecureValueType::RentalAgreement:
+ case SecureValueType::PassportRegistration:
+ case SecureValueType::TemporaryRegistration: {
+ auto document = get_personal_document_object(file_manager, value);
+ if (value.type == SecureValueType::UtilityBill) {
+ return td_api::make_object<td_api::passportElementUtilityBill>(std::move(document));
+ }
+ if (value.type == SecureValueType::BankStatement) {
+ return td_api::make_object<td_api::passportElementBankStatement>(std::move(document));
+ }
+ if (value.type == SecureValueType::RentalAgreement) {
+ return td_api::make_object<td_api::passportElementRentalAgreement>(std::move(document));
+ }
+ if (value.type == SecureValueType::PassportRegistration) {
+ return td_api::make_object<td_api::passportElementPassportRegistration>(std::move(document));
+ }
+ if (value.type == SecureValueType::TemporaryRegistration) {
+ return td_api::make_object<td_api::passportElementTemporaryRegistration>(std::move(document));
+ }
+ UNREACHABLE();
+ break;
+ }
+ case SecureValueType::PhoneNumber:
+ return td_api::make_object<td_api::passportElementPhoneNumber>(value.data);
+ case SecureValueType::EmailAddress:
+ return td_api::make_object<td_api::passportElementEmailAddress>(value.data);
+ case SecureValueType::None:
+ default:
+ UNREACHABLE();
+ return Status::Error(400, "Wrong value type");
+ }
+}
+
+td_api::object_ptr<td_api::passportElements> get_passport_elements_object(FileManager *file_manager,
+ const vector<SecureValue> &values) {
+ vector<td_api::object_ptr<td_api::PassportElement>> result;
+ result.reserve(values.size());
+ for (auto &value : values) {
+ auto r_obj = get_passport_element_object(file_manager, value);
+ if (r_obj.is_error()) {
+ LOG(ERROR) << "Can't get passport element object: " << r_obj.error();
+ continue;
+ }
+
+ result.push_back(r_obj.move_as_ok());
+ }
+
+ return td_api::make_object<td_api::passportElements>(std::move(result));
+}
+
+static Result<std::pair<DatedFile, SecureFileCredentials>> decrypt_secure_file(
+ FileManager *file_manager, const secure_storage::Secret &master_secret, const EncryptedSecureFile &secure_file) {
+ if (!secure_file.file.file_id.is_valid()) {
+ return std::make_pair(DatedFile(), SecureFileCredentials());
+ }
+ TRY_RESULT(hash, secure_storage::ValueHash::create(secure_file.file_hash));
+ TRY_RESULT(encrypted_secret, secure_storage::EncryptedSecret::create(secure_file.encrypted_secret));
+ TRY_RESULT(secret, encrypted_secret.decrypt(PSLICE() << master_secret.as_slice() << hash.as_slice(), "",
+ secure_storage::EnryptionAlgorithm::Sha512));
+ FileEncryptionKey key{secret};
+ key.set_value_hash(hash);
+ file_manager->set_encryption_key(secure_file.file.file_id, std::move(key));
+ return std::make_pair(secure_file.file, SecureFileCredentials{secret.as_slice().str(), hash.as_slice().str()});
+}
+
+static Result<std::pair<vector<DatedFile>, vector<SecureFileCredentials>>> decrypt_secure_files(
+ FileManager *file_manager, const secure_storage::Secret &secret, const vector<EncryptedSecureFile> &secure_files) {
+ vector<DatedFile> result;
+ vector<SecureFileCredentials> credentials;
+ result.reserve(secure_files.size());
+ credentials.reserve(secure_files.size());
+ for (auto &file : secure_files) {
+ TRY_RESULT(decrypted_file, decrypt_secure_file(file_manager, secret, file));
+ result.push_back(std::move(decrypted_file.first));
+ credentials.push_back(std::move(decrypted_file.second));
+ }
+
+ return std::make_pair(std::move(result), std::move(credentials));
+}
+
+static Result<std::pair<string, SecureDataCredentials>> decrypt_secure_data(const secure_storage::Secret &master_secret,
+ const EncryptedSecureData &secure_data) {
+ TRY_RESULT(hash, secure_storage::ValueHash::create(secure_data.hash));
+ TRY_RESULT(encrypted_secret, secure_storage::EncryptedSecret::create(secure_data.encrypted_secret));
+ TRY_RESULT(secret, encrypted_secret.decrypt(PSLICE() << master_secret.as_slice() << hash.as_slice(), "",
+ secure_storage::EnryptionAlgorithm::Sha512));
+ TRY_RESULT(value, secure_storage::decrypt_value(secret, hash, secure_data.data));
+ return std::make_pair(value.as_slice().str(), SecureDataCredentials{secret.as_slice().str(), hash.as_slice().str()});
+}
+
+Result<SecureValueWithCredentials> decrypt_secure_value(FileManager *file_manager, const secure_storage::Secret &secret,
+ const EncryptedSecureValue &encrypted_secure_value) {
+ SecureValue res;
+ SecureValueCredentials res_credentials;
+ res.type = encrypted_secure_value.type;
+ res_credentials.type = res.type;
+ res_credentials.hash = encrypted_secure_value.hash;
+ switch (encrypted_secure_value.type) {
+ case SecureValueType::None:
+ return Status::Error(400, "Receive invalid Telegram Passport element");
+ case SecureValueType::EmailAddress:
+ case SecureValueType::PhoneNumber:
+ res.data = encrypted_secure_value.data.data;
+ break;
+ case SecureValueType::UtilityBill:
+ case SecureValueType::BankStatement:
+ case SecureValueType::RentalAgreement:
+ case SecureValueType::PassportRegistration:
+ case SecureValueType::TemporaryRegistration: {
+ TRY_RESULT(files, decrypt_secure_files(file_manager, secret, encrypted_secure_value.files));
+ res.files = std::move(files.first);
+ res_credentials.files = std::move(files.second);
+ TRY_RESULT(translations, decrypt_secure_files(file_manager, secret, encrypted_secure_value.translations));
+ res.translations = std::move(translations.first);
+ res_credentials.translations = std::move(translations.second);
+ break;
+ }
+ default: {
+ TRY_RESULT(data, decrypt_secure_data(secret, encrypted_secure_value.data));
+ res.data = std::move(data.first);
+ if (!res.data.empty()) {
+ res_credentials.data = std::move(data.second);
+ }
+ CHECK(encrypted_secure_value.files.empty());
+ TRY_RESULT(front_side, decrypt_secure_file(file_manager, secret, encrypted_secure_value.front_side));
+ res.front_side = std::move(front_side.first);
+ if (res.front_side.file_id.is_valid()) {
+ res_credentials.front_side = std::move(front_side.second);
+ }
+ TRY_RESULT(reverse_side, decrypt_secure_file(file_manager, secret, encrypted_secure_value.reverse_side));
+ res.reverse_side = std::move(reverse_side.first);
+ if (res.reverse_side.file_id.is_valid()) {
+ res_credentials.reverse_side = std::move(reverse_side.second);
+ }
+ TRY_RESULT(selfie, decrypt_secure_file(file_manager, secret, encrypted_secure_value.selfie));
+ res.selfie = std::move(selfie.first);
+ if (res.selfie.file_id.is_valid()) {
+ res_credentials.selfie = std::move(selfie.second);
+ }
+ TRY_RESULT(translations, decrypt_secure_files(file_manager, secret, encrypted_secure_value.translations));
+ res.translations = std::move(translations.first);
+ res_credentials.translations = std::move(translations.second);
+ break;
+ }
+ }
+ return SecureValueWithCredentials{std::move(res), std::move(res_credentials)};
+}
+
+Result<vector<SecureValueWithCredentials>> decrypt_secure_values(
+ FileManager *file_manager, const secure_storage::Secret &secret,
+ const vector<EncryptedSecureValue> &encrypted_secure_values) {
+ vector<SecureValueWithCredentials> result;
+ result.reserve(encrypted_secure_values.size());
+ for (auto &encrypted_secure_value : encrypted_secure_values) {
+ auto r_secure_value_with_credentials = decrypt_secure_value(file_manager, secret, encrypted_secure_value);
+ if (r_secure_value_with_credentials.is_ok()) {
+ result.push_back(r_secure_value_with_credentials.move_as_ok());
+ } else {
+ LOG(ERROR) << "Cannot decrypt secure value: " << r_secure_value_with_credentials.error();
+ }
+ }
+ return std::move(result);
+}
+
+static EncryptedSecureFile encrypt_secure_file(FileManager *file_manager, const secure_storage::Secret &master_secret,
+ DatedFile file, string &to_hash) {
+ auto file_view = file_manager->get_file_view(file.file_id);
+ if (file_view.empty()) {
+ return EncryptedSecureFile();
+ }
+ if (!file_view.encryption_key().is_secure()) {
+ LOG(ERROR) << "File " << file.file_id << " has no encryption key";
+ return EncryptedSecureFile();
+ }
+ if (!file_view.encryption_key().has_value_hash()) {
+ LOG(ERROR) << "File " << file.file_id << " has no hash";
+ return EncryptedSecureFile();
+ }
+ auto value_hash = file_view.encryption_key().value_hash();
+ auto secret = file_view.encryption_key().secret();
+ EncryptedSecureFile res;
+ res.file = file;
+ res.file_hash = value_hash.as_slice().str();
+ res.encrypted_secret = secret
+ .encrypt(PSLICE() << master_secret.as_slice() << value_hash.as_slice(), "",
+ secure_storage::EnryptionAlgorithm::Sha512)
+ .as_slice()
+ .str();
+
+ to_hash.append(res.file_hash);
+ to_hash.append(secret.as_slice().str());
+ return res;
+}
+
+static vector<EncryptedSecureFile> encrypt_secure_files(FileManager *file_manager,
+ const secure_storage::Secret &master_secret,
+ const vector<DatedFile> &files, string &to_hash) {
+ return transform(
+ files, [&](auto dated_file) { return encrypt_secure_file(file_manager, master_secret, dated_file, to_hash); });
+ /*
+ vector<EncryptedSecureFile> result;
+ result.reserve(files.size());
+ for (auto &file : files) {
+ auto encrypted_secure_file = encrypt_secure_file(file_manager, master_secret, file, to_hash);
+ if (encrypted_secure_file.file.file_id.is_valid()) {
+ result.push_back(std::move(encrypted_secure_file));
+ }
+ }
+ return result;
+*/
+}
+
+static EncryptedSecureData encrypt_secure_data(const secure_storage::Secret &master_secret, Slice data,
+ string &to_hash) {
+ auto secret = secure_storage::Secret::create_new();
+ auto encrypted = encrypt_value(secret, data).move_as_ok();
+ EncryptedSecureData res;
+ res.encrypted_secret = secret
+ .encrypt(PSLICE() << master_secret.as_slice() << encrypted.hash.as_slice(), "",
+ secure_storage::EnryptionAlgorithm::Sha512)
+ .as_slice()
+ .str();
+ res.data = encrypted.data.as_slice().str();
+ res.hash = encrypted.hash.as_slice().str();
+ to_hash.append(res.hash);
+ to_hash.append(secret.as_slice().str());
+ return res;
+}
+
+EncryptedSecureValue encrypt_secure_value(FileManager *file_manager, const secure_storage::Secret &master_secret,
+ const SecureValue &secure_value) {
+ EncryptedSecureValue res;
+ res.type = secure_value.type;
+ switch (res.type) {
+ case SecureValueType::EmailAddress:
+ case SecureValueType::PhoneNumber:
+ res.data = EncryptedSecureData{secure_value.data, "", ""};
+ res.hash = secure_storage::calc_value_hash(secure_value.data).as_slice().str();
+ break;
+ case SecureValueType::UtilityBill:
+ case SecureValueType::BankStatement:
+ case SecureValueType::RentalAgreement:
+ case SecureValueType::PassportRegistration:
+ case SecureValueType::TemporaryRegistration: {
+ string to_hash;
+ res.files = encrypt_secure_files(file_manager, master_secret, secure_value.files, to_hash);
+ res.translations = encrypt_secure_files(file_manager, master_secret, secure_value.translations, to_hash);
+ res.hash = secure_storage::calc_value_hash(to_hash).as_slice().str();
+ break;
+ }
+ default: {
+ string to_hash;
+ res.data = encrypt_secure_data(master_secret, secure_value.data, to_hash);
+ CHECK(secure_value.files.empty());
+ res.front_side = encrypt_secure_file(file_manager, master_secret, secure_value.front_side, to_hash);
+ res.reverse_side = encrypt_secure_file(file_manager, master_secret, secure_value.reverse_side, to_hash);
+ res.selfie = encrypt_secure_file(file_manager, master_secret, secure_value.selfie, to_hash);
+ res.translations = encrypt_secure_files(file_manager, master_secret, secure_value.translations, to_hash);
+ res.hash = secure_storage::calc_value_hash(to_hash).as_slice().str();
+ break;
+ }
+ }
+ return res;
+}
+
+static auto as_jsonable_data(const SecureDataCredentials &credentials) {
+ return json_object([&credentials](auto &o) {
+ o("data_hash", base64_encode(credentials.hash));
+ o("secret", base64_encode(credentials.secret));
+ });
+}
+
+static auto as_jsonable_file(const SecureFileCredentials &credentials) {
+ return json_object([&credentials](auto &o) {
+ o("file_hash", base64_encode(credentials.hash));
+ o("secret", base64_encode(credentials.secret));
+ });
+}
+
+static auto as_jsonable_files(const vector<SecureFileCredentials> &files) {
+ return json_array(files, as_jsonable_file);
+}
+
+static Slice secure_value_type_as_slice(SecureValueType type) {
+ switch (type) {
+ case SecureValueType::PersonalDetails:
+ return Slice("personal_details");
+ case SecureValueType::Passport:
+ return Slice("passport");
+ case SecureValueType::DriverLicense:
+ return Slice("driver_license");
+ case SecureValueType::IdentityCard:
+ return Slice("identity_card");
+ case SecureValueType::InternalPassport:
+ return Slice("internal_passport");
+ case SecureValueType::Address:
+ return Slice("address");
+ case SecureValueType::UtilityBill:
+ return Slice("utility_bill");
+ case SecureValueType::BankStatement:
+ return Slice("bank_statement");
+ case SecureValueType::RentalAgreement:
+ return Slice("rental_agreement");
+ case SecureValueType::PassportRegistration:
+ return Slice("passport_registration");
+ case SecureValueType::TemporaryRegistration:
+ return Slice("temporary_registration");
+ case SecureValueType::PhoneNumber:
+ return Slice("phone_number");
+ case SecureValueType::EmailAddress:
+ return Slice("email");
+ default:
+ case SecureValueType::None:
+ UNREACHABLE();
+ return Slice("none");
+ }
+}
+
+static auto credentials_as_jsonable(const std::vector<SecureValueCredentials> &credentials, Slice nonce,
+ bool rename_payload_to_nonce) {
+ return json_object([&credentials, nonce, rename_payload_to_nonce](auto &o) {
+ o("secure_data", json_object([&credentials](auto &o) {
+ for (auto &cred : credentials) {
+ if (cred.type == SecureValueType::PhoneNumber || cred.type == SecureValueType::EmailAddress) {
+ continue;
+ }
+
+ o(secure_value_type_as_slice(cred.type), json_object([&cred](auto &o) {
+ if (cred.data) {
+ o("data", as_jsonable_data(cred.data.value()));
+ }
+ if (!cred.files.empty()) {
+ o("files", as_jsonable_files(cred.files));
+ }
+ if (cred.front_side) {
+ o("front_side", as_jsonable_file(cred.front_side.value()));
+ }
+ if (cred.reverse_side) {
+ o("reverse_side", as_jsonable_file(cred.reverse_side.value()));
+ }
+ if (cred.selfie) {
+ o("selfie", as_jsonable_file(cred.selfie.value()));
+ }
+ if (!cred.translations.empty()) {
+ o("translation", as_jsonable_files(cred.translations));
+ }
+ }));
+ }
+ }));
+ o(rename_payload_to_nonce ? Slice("nonce") : Slice("payload"), nonce);
+ });
+}
+
+Result<EncryptedSecureCredentials> get_encrypted_credentials(const std::vector<SecureValueCredentials> &credentials,
+ Slice nonce, Slice public_key,
+ bool rename_payload_to_nonce) {
+ auto encoded_credentials =
+ json_encode<std::string>(credentials_as_jsonable(credentials, nonce, rename_payload_to_nonce));
+ LOG(INFO) << "Created credentials " << encoded_credentials;
+
+ auto secret = secure_storage::Secret::create_new();
+ auto encrypted_value = secure_storage::encrypt_value(secret, encoded_credentials).move_as_ok();
+ EncryptedSecureCredentials res;
+ res.data = encrypted_value.data.as_slice().str();
+ res.hash = encrypted_value.hash.as_slice().str();
+ TRY_RESULT(encrypted_secret, rsa_encrypt_pkcs1_oaep(public_key, secret.as_slice()));
+ res.encrypted_secret = encrypted_secret.as_slice().str();
+ return res;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecureValue.h b/protocols/Telegram/tdlib/td/td/telegram/SecureValue.h
new file mode 100644
index 0000000000..afc2cb2aaf
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecureValue.h
@@ -0,0 +1,234 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/SecureStorage.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/optional.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+#include <utility>
+
+namespace td {
+
+class FileManager;
+
+enum class SecureValueType : int32 {
+ None,
+ PersonalDetails,
+ Passport,
+ DriverLicense,
+ IdentityCard,
+ InternalPassport,
+ Address,
+ UtilityBill,
+ BankStatement,
+ RentalAgreement,
+ PassportRegistration,
+ TemporaryRegistration,
+ PhoneNumber,
+ EmailAddress
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, const SecureValueType &type);
+
+vector<SecureValueType> unique_secure_value_types(vector<SecureValueType> types);
+
+SecureValueType get_secure_value_type(const tl_object_ptr<telegram_api::SecureValueType> &secure_value_type);
+SecureValueType get_secure_value_type_td_api(const tl_object_ptr<td_api::PassportElementType> &passport_element_type);
+
+vector<SecureValueType> get_secure_value_types(
+ const vector<tl_object_ptr<telegram_api::SecureValueType>> &secure_value_types);
+vector<SecureValueType> get_secure_value_types_td_api(
+ const vector<tl_object_ptr<td_api::PassportElementType>> &secure_value_types);
+
+td_api::object_ptr<td_api::PassportElementType> get_passport_element_type_object(SecureValueType type);
+td_api::object_ptr<telegram_api::SecureValueType> get_input_secure_value_type(SecureValueType type);
+
+vector<td_api::object_ptr<td_api::PassportElementType>> get_passport_element_types_object(
+ const vector<SecureValueType> &types);
+
+struct SuitableSecureValue {
+ SecureValueType type;
+ bool is_selfie_required;
+ bool is_translation_required;
+ bool is_native_name_required;
+};
+
+SuitableSecureValue get_suitable_secure_value(
+ const tl_object_ptr<telegram_api::secureRequiredType> &secure_required_type);
+
+td_api::object_ptr<td_api::passportSuitableElement> get_passport_suitable_element_object(
+ const SuitableSecureValue &required_element);
+
+td_api::object_ptr<td_api::passportRequiredElement> get_passport_required_element_object(
+ const vector<SuitableSecureValue> &required_element);
+
+vector<td_api::object_ptr<td_api::passportRequiredElement>> get_passport_required_elements_object(
+ const vector<vector<SuitableSecureValue>> &required_elements);
+
+string get_secure_value_data_field_name(SecureValueType type, string field_name);
+
+struct DatedFile {
+ FileId file_id;
+ int32 date = 0;
+};
+
+bool operator==(const DatedFile &lhs, const DatedFile &rhs);
+bool operator!=(const DatedFile &lhs, const DatedFile &rhs);
+
+struct EncryptedSecureFile {
+ DatedFile file;
+ string file_hash;
+ string encrypted_secret;
+};
+
+bool operator==(const EncryptedSecureFile &lhs, const EncryptedSecureFile &rhs);
+bool operator!=(const EncryptedSecureFile &lhs, const EncryptedSecureFile &rhs);
+
+EncryptedSecureFile get_encrypted_secure_file(FileManager *file_manager,
+ tl_object_ptr<telegram_api::SecureFile> &&secure_file_ptr);
+
+vector<EncryptedSecureFile> get_encrypted_secure_files(FileManager *file_manager,
+ vector<tl_object_ptr<telegram_api::SecureFile>> &&secure_files);
+
+struct SecureInputFile {
+ FileId file_id;
+ tl_object_ptr<telegram_api::InputSecureFile> input_file;
+};
+telegram_api::object_ptr<telegram_api::InputSecureFile> get_input_secure_file_object(FileManager *file_manager,
+ const EncryptedSecureFile &file,
+ SecureInputFile &input_file);
+
+vector<telegram_api::object_ptr<telegram_api::InputSecureFile>> get_input_secure_files_object(
+ FileManager *file_manager, const vector<EncryptedSecureFile> &file, vector<SecureInputFile> &input_files);
+
+struct EncryptedSecureData {
+ string data;
+ string hash;
+ string encrypted_secret;
+};
+
+bool operator==(const EncryptedSecureData &lhs, const EncryptedSecureData &rhs);
+bool operator!=(const EncryptedSecureData &lhs, const EncryptedSecureData &rhs);
+
+EncryptedSecureData get_encrypted_secure_data(tl_object_ptr<telegram_api::secureData> &&secure_data);
+
+telegram_api::object_ptr<telegram_api::secureData> get_secure_data_object(const EncryptedSecureData &data);
+
+struct EncryptedSecureValue {
+ SecureValueType type = SecureValueType::None;
+ EncryptedSecureData data;
+ vector<EncryptedSecureFile> files;
+ EncryptedSecureFile front_side;
+ EncryptedSecureFile reverse_side;
+ EncryptedSecureFile selfie;
+ vector<EncryptedSecureFile> translations;
+ string hash;
+};
+
+bool operator==(const EncryptedSecureValue &lhs, const EncryptedSecureValue &rhs);
+bool operator!=(const EncryptedSecureValue &lhs, const EncryptedSecureValue &rhs);
+
+EncryptedSecureValue get_encrypted_secure_value(FileManager *file_manager,
+ tl_object_ptr<telegram_api::secureValue> &&secure_value);
+
+vector<EncryptedSecureValue> get_encrypted_secure_values(
+ FileManager *file_manager, vector<tl_object_ptr<telegram_api::secureValue>> &&secure_values);
+
+td_api::object_ptr<td_api::encryptedPassportElement> get_encrypted_passport_element_object(
+ FileManager *file_manager, const EncryptedSecureValue &value);
+telegram_api::object_ptr<telegram_api::inputSecureValue> get_input_secure_value_object(
+ FileManager *file_manager, const EncryptedSecureValue &value, vector<SecureInputFile> &files,
+ optional<SecureInputFile> &front_side, optional<SecureInputFile> &reverse_side, optional<SecureInputFile> &selfie,
+ vector<SecureInputFile> &translations);
+
+vector<td_api::object_ptr<td_api::encryptedPassportElement>> get_encrypted_passport_element_object(
+ FileManager *file_manager, const vector<EncryptedSecureValue> &values);
+
+struct EncryptedSecureCredentials {
+ string data;
+ string hash;
+ string encrypted_secret;
+};
+
+bool operator==(const EncryptedSecureCredentials &lhs, const EncryptedSecureCredentials &rhs);
+bool operator!=(const EncryptedSecureCredentials &lhs, const EncryptedSecureCredentials &rhs);
+
+EncryptedSecureCredentials get_encrypted_secure_credentials(
+ tl_object_ptr<telegram_api::secureCredentialsEncrypted> &&credentials);
+
+telegram_api::object_ptr<telegram_api::secureCredentialsEncrypted> get_secure_credentials_encrypted_object(
+ const EncryptedSecureCredentials &credentials);
+td_api::object_ptr<td_api::encryptedCredentials> get_encrypted_credentials_object(
+ const EncryptedSecureCredentials &credentials);
+
+struct SecureDataCredentials {
+ string secret;
+ string hash;
+};
+struct SecureFileCredentials {
+ string secret;
+ string hash;
+};
+
+struct SecureValueCredentials {
+ SecureValueType type = SecureValueType::None;
+ string hash;
+ optional<SecureDataCredentials> data;
+ std::vector<SecureFileCredentials> files;
+ optional<SecureFileCredentials> front_side;
+ optional<SecureFileCredentials> reverse_side;
+ optional<SecureFileCredentials> selfie;
+ std::vector<SecureFileCredentials> translations;
+};
+
+Result<EncryptedSecureCredentials> get_encrypted_credentials(const std::vector<SecureValueCredentials> &credentials,
+ Slice nonce, Slice public_key,
+ bool rename_payload_to_nonce);
+
+class SecureValue {
+ public:
+ SecureValueType type = SecureValueType::None;
+ string data;
+ vector<DatedFile> files;
+ DatedFile front_side;
+ DatedFile reverse_side;
+ DatedFile selfie;
+ vector<DatedFile> translations;
+};
+
+struct SecureValueWithCredentials {
+ SecureValue value;
+ SecureValueCredentials credentials;
+};
+
+Result<SecureValue> get_secure_value(FileManager *file_manager,
+ td_api::object_ptr<td_api::InputPassportElement> &&input_passport_element);
+
+Result<td_api::object_ptr<td_api::PassportElement>> get_passport_element_object(FileManager *file_manager,
+ const SecureValue &value);
+
+td_api::object_ptr<td_api::passportElements> get_passport_elements_object(FileManager *file_manager,
+ const vector<SecureValue> &values);
+
+Result<SecureValueWithCredentials> decrypt_secure_value(FileManager *file_manager, const secure_storage::Secret &secret,
+ const EncryptedSecureValue &encrypted_secure_value);
+Result<vector<SecureValueWithCredentials>> decrypt_secure_values(
+ FileManager *file_manager, const secure_storage::Secret &secret,
+ const vector<EncryptedSecureValue> &encrypted_secure_values);
+
+EncryptedSecureValue encrypt_secure_value(FileManager *file_manager, const secure_storage::Secret &master_secret,
+ const SecureValue &secure_value);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SecureValue.hpp b/protocols/Telegram/tdlib/td/td/telegram/SecureValue.hpp
new file mode 100644
index 0000000000..54e10d8a6d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SecureValue.hpp
@@ -0,0 +1,159 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/SecureValue.h"
+
+#include "td/telegram/files/FileId.hpp"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void store(DatedFile file, StorerT &storer) {
+ store(file.file_id, storer);
+ store(file.date, storer);
+}
+
+template <class ParserT>
+void parse(DatedFile &file, ParserT &parser) {
+ parse(file.file_id, parser);
+ parse(file.date, parser);
+}
+
+template <class StorerT>
+void store(EncryptedSecureFile file, StorerT &storer) {
+ store(file.file, storer);
+ store(file.file_hash, storer);
+ store(file.encrypted_secret, storer);
+}
+
+template <class ParserT>
+void parse(EncryptedSecureFile &file, ParserT &parser) {
+ parse(file.file, parser);
+ parse(file.file_hash, parser);
+ parse(file.encrypted_secret, parser);
+}
+
+template <class StorerT>
+void store(const EncryptedSecureData &data, StorerT &storer) {
+ store(data.data, storer);
+ store(data.hash, storer);
+ store(data.encrypted_secret, storer);
+}
+
+template <class ParserT>
+void parse(EncryptedSecureData &data, ParserT &parser) {
+ parse(data.data, parser);
+ parse(data.hash, parser);
+ parse(data.encrypted_secret, parser);
+}
+
+template <class StorerT>
+void store(const EncryptedSecureCredentials &credentials, StorerT &storer) {
+ store(credentials.data, storer);
+ store(credentials.hash, storer);
+ store(credentials.encrypted_secret, storer);
+}
+
+template <class ParserT>
+void parse(EncryptedSecureCredentials &credentials, ParserT &parser) {
+ parse(credentials.data, parser);
+ parse(credentials.hash, parser);
+ parse(credentials.encrypted_secret, parser);
+}
+
+template <class StorerT>
+void store(const EncryptedSecureValue &value, StorerT &storer) {
+ bool has_data_hash = !value.data.hash.empty();
+ bool has_files = !value.files.empty();
+ bool has_front_side = value.front_side.file.file_id.is_valid();
+ bool has_reverse_side = value.reverse_side.file.file_id.is_valid();
+ bool has_selfie = value.selfie.file.file_id.is_valid();
+ bool has_hash = !value.hash.empty();
+ bool has_translations = !value.translations.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_data_hash);
+ STORE_FLAG(has_files);
+ STORE_FLAG(has_front_side);
+ STORE_FLAG(has_reverse_side);
+ STORE_FLAG(has_selfie);
+ STORE_FLAG(has_hash);
+ STORE_FLAG(has_translations);
+ END_STORE_FLAGS();
+ store(value.type, storer);
+ if (has_data_hash) {
+ store(value.data, storer);
+ } else {
+ store(value.data.data, storer);
+ }
+ if (has_files) {
+ store(value.files, storer);
+ }
+ if (has_front_side) {
+ store(value.front_side, storer);
+ }
+ if (has_reverse_side) {
+ store(value.reverse_side, storer);
+ }
+ if (has_selfie) {
+ store(value.selfie, storer);
+ }
+ if (has_hash) {
+ store(value.hash, storer);
+ }
+ if (has_translations) {
+ store(value.translations, storer);
+ }
+}
+
+template <class ParserT>
+void parse(EncryptedSecureValue &value, ParserT &parser) {
+ bool has_data_hash;
+ bool has_files;
+ bool has_front_side;
+ bool has_reverse_side;
+ bool has_selfie;
+ bool has_hash;
+ bool has_translations;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_data_hash);
+ PARSE_FLAG(has_files);
+ PARSE_FLAG(has_front_side);
+ PARSE_FLAG(has_reverse_side);
+ PARSE_FLAG(has_selfie);
+ PARSE_FLAG(has_hash);
+ PARSE_FLAG(has_translations);
+ END_PARSE_FLAGS();
+ parse(value.type, parser);
+ if (has_data_hash) {
+ parse(value.data, parser);
+ } else {
+ parse(value.data.data, parser);
+ }
+ if (has_files) {
+ parse(value.files, parser);
+ }
+ if (has_front_side) {
+ parse(value.front_side, parser);
+ }
+ if (has_reverse_side) {
+ parse(value.reverse_side, parser);
+ }
+ if (has_selfie) {
+ parse(value.selfie, parser);
+ }
+ if (has_hash) {
+ parse(value.hash, parser);
+ }
+ if (has_translations) {
+ parse(value.translations, parser);
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.cpp b/protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.cpp
new file mode 100644
index 0000000000..179e3ddbde
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.cpp
@@ -0,0 +1,189 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SendCodeHelper.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/Time.h"
+
+namespace td {
+
+void SendCodeHelper::on_sent_code(telegram_api::object_ptr<telegram_api::auth_sentCode> sent_code) {
+ phone_code_hash_ = std::move(sent_code->phone_code_hash_);
+ sent_code_info_ = get_sent_authentication_code_info(std::move(sent_code->type_));
+ next_code_info_ = get_authentication_code_info(std::move(sent_code->next_type_));
+ next_code_timestamp_ =
+ Time::now() + ((sent_code->flags_ & SENT_CODE_FLAG_HAS_TIMEOUT) != 0 ? sent_code->timeout_ : 0);
+}
+
+void SendCodeHelper::on_phone_code_hash(string &&phone_code_hash) {
+ phone_code_hash_ = std::move(phone_code_hash);
+}
+
+td_api::object_ptr<td_api::authorizationStateWaitCode> SendCodeHelper::get_authorization_state_wait_code() const {
+ return make_tl_object<td_api::authorizationStateWaitCode>(get_authentication_code_info_object());
+}
+
+td_api::object_ptr<td_api::authenticationCodeInfo> SendCodeHelper::get_authentication_code_info_object() const {
+ return make_tl_object<td_api::authenticationCodeInfo>(
+ phone_number_, get_authentication_code_type_object(sent_code_info_),
+ get_authentication_code_type_object(next_code_info_),
+ max(static_cast<int32>(next_code_timestamp_ - Time::now() + 1 - 1e-9), 0));
+}
+
+Result<telegram_api::auth_resendCode> SendCodeHelper::resend_code() const {
+ if (next_code_info_.type == AuthenticationCodeInfo::Type::None) {
+ return Status::Error(400, "Authentication code can't be resend");
+ }
+ return telegram_api::auth_resendCode(phone_number_, phone_code_hash_);
+}
+
+telegram_api::object_ptr<telegram_api::codeSettings> SendCodeHelper::get_input_code_settings(const Settings &settings) {
+ int32 flags = 0;
+ vector<BufferSlice> logout_tokens;
+ if (settings != nullptr) {
+ if (settings->allow_flash_call_) {
+ flags |= telegram_api::codeSettings::ALLOW_FLASHCALL_MASK;
+ }
+ if (settings->allow_missed_call_) {
+ flags |= telegram_api::codeSettings::ALLOW_MISSED_CALL_MASK;
+ }
+ if (settings->is_current_phone_number_) {
+ flags |= telegram_api::codeSettings::CURRENT_NUMBER_MASK;
+ }
+ if (settings->allow_sms_retriever_api_) {
+ flags |= telegram_api::codeSettings::ALLOW_APP_HASH_MASK;
+ }
+ constexpr size_t MAX_LOGOUT_TOKENS = 20; // server-side limit
+ for (const auto &token : settings->authentication_tokens_) {
+ auto r_logout_token = base64url_decode(token);
+ if (r_logout_token.is_ok()) {
+ logout_tokens.push_back(BufferSlice(r_logout_token.ok()));
+ if (logout_tokens.size() >= MAX_LOGOUT_TOKENS) {
+ break;
+ }
+ }
+ }
+ if (!logout_tokens.empty()) {
+ flags |= telegram_api::codeSettings::LOGOUT_TOKENS_MASK;
+ }
+ }
+ return telegram_api::make_object<telegram_api::codeSettings>(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, std::move(logout_tokens));
+}
+
+telegram_api::auth_sendCode SendCodeHelper::send_code(string phone_number, const Settings &settings, int32 api_id,
+ const string &api_hash) {
+ phone_number_ = std::move(phone_number);
+ return telegram_api::auth_sendCode(phone_number_, api_id, api_hash, get_input_code_settings(settings));
+}
+
+telegram_api::account_sendVerifyEmailCode SendCodeHelper::send_verify_email_code(const string &email_address) {
+ return telegram_api::account_sendVerifyEmailCode(get_email_verify_purpose_login_setup(), email_address);
+}
+
+telegram_api::account_sendChangePhoneCode SendCodeHelper::send_change_phone_code(Slice phone_number,
+ const Settings &settings) {
+ phone_number_ = phone_number.str();
+ return telegram_api::account_sendChangePhoneCode(phone_number_, get_input_code_settings(settings));
+}
+
+telegram_api::account_sendVerifyPhoneCode SendCodeHelper::send_verify_phone_code(Slice phone_number,
+ const Settings &settings) {
+ phone_number_ = phone_number.str();
+ return telegram_api::account_sendVerifyPhoneCode(phone_number_, get_input_code_settings(settings));
+}
+
+telegram_api::account_sendConfirmPhoneCode SendCodeHelper::send_confirm_phone_code(const string &hash,
+ Slice phone_number,
+ const Settings &settings) {
+ phone_number_ = phone_number.str();
+ return telegram_api::account_sendConfirmPhoneCode(hash, get_input_code_settings(settings));
+}
+
+SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_authentication_code_info(
+ tl_object_ptr<telegram_api::auth_CodeType> &&code_type_ptr) {
+ if (code_type_ptr == nullptr) {
+ return AuthenticationCodeInfo();
+ }
+
+ switch (code_type_ptr->get_id()) {
+ case telegram_api::auth_codeTypeSms::ID:
+ return {AuthenticationCodeInfo::Type::Sms, 0, string()};
+ case telegram_api::auth_codeTypeCall::ID:
+ return {AuthenticationCodeInfo::Type::Call, 0, string()};
+ case telegram_api::auth_codeTypeFlashCall::ID:
+ return {AuthenticationCodeInfo::Type::FlashCall, 0, string()};
+ case telegram_api::auth_codeTypeMissedCall::ID:
+ return {AuthenticationCodeInfo::Type::MissedCall, 0, string()};
+ default:
+ UNREACHABLE();
+ return AuthenticationCodeInfo();
+ }
+}
+
+SendCodeHelper::AuthenticationCodeInfo SendCodeHelper::get_sent_authentication_code_info(
+ tl_object_ptr<telegram_api::auth_SentCodeType> &&sent_code_type_ptr) {
+ CHECK(sent_code_type_ptr != nullptr);
+ switch (sent_code_type_ptr->get_id()) {
+ case telegram_api::auth_sentCodeTypeApp::ID: {
+ auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeApp>(sent_code_type_ptr);
+ return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Message, code_type->length_, ""};
+ }
+ case telegram_api::auth_sentCodeTypeSms::ID: {
+ auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeSms>(sent_code_type_ptr);
+ return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Sms, code_type->length_, ""};
+ }
+ case telegram_api::auth_sentCodeTypeCall::ID: {
+ auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeCall>(sent_code_type_ptr);
+ return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::Call, code_type->length_, ""};
+ }
+ case telegram_api::auth_sentCodeTypeFlashCall::ID: {
+ auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeFlashCall>(sent_code_type_ptr);
+ return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::FlashCall, 0, std::move(code_type->pattern_)};
+ }
+ case telegram_api::auth_sentCodeTypeMissedCall::ID: {
+ auto code_type = move_tl_object_as<telegram_api::auth_sentCodeTypeMissedCall>(sent_code_type_ptr);
+ return AuthenticationCodeInfo{AuthenticationCodeInfo::Type::MissedCall, code_type->length_,
+ std::move(code_type->prefix_)};
+ }
+ case telegram_api::auth_sentCodeTypeEmailCode::ID:
+ case telegram_api::auth_sentCodeTypeSetUpEmailRequired::ID:
+ default:
+ UNREACHABLE();
+ return AuthenticationCodeInfo();
+ }
+}
+
+td_api::object_ptr<td_api::AuthenticationCodeType> SendCodeHelper::get_authentication_code_type_object(
+ const AuthenticationCodeInfo &authentication_code_info) {
+ switch (authentication_code_info.type) {
+ case AuthenticationCodeInfo::Type::None:
+ return nullptr;
+ case AuthenticationCodeInfo::Type::Message:
+ return td_api::make_object<td_api::authenticationCodeTypeTelegramMessage>(authentication_code_info.length);
+ case AuthenticationCodeInfo::Type::Sms:
+ return td_api::make_object<td_api::authenticationCodeTypeSms>(authentication_code_info.length);
+ case AuthenticationCodeInfo::Type::Call:
+ return td_api::make_object<td_api::authenticationCodeTypeCall>(authentication_code_info.length);
+ case AuthenticationCodeInfo::Type::FlashCall:
+ return td_api::make_object<td_api::authenticationCodeTypeFlashCall>(authentication_code_info.pattern);
+ case AuthenticationCodeInfo::Type::MissedCall:
+ return td_api::make_object<td_api::authenticationCodeTypeMissedCall>(authentication_code_info.pattern,
+ authentication_code_info.length);
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+telegram_api::object_ptr<telegram_api::emailVerifyPurposeLoginSetup>
+SendCodeHelper::get_email_verify_purpose_login_setup() const {
+ return telegram_api::make_object<telegram_api::emailVerifyPurposeLoginSetup>(phone_number_, phone_code_hash_);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.h b/protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.h
new file mode 100644
index 0000000000..017e814549
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.h
@@ -0,0 +1,98 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class SendCodeHelper {
+ public:
+ void on_sent_code(telegram_api::object_ptr<telegram_api::auth_sentCode> sent_code);
+
+ void on_phone_code_hash(string &&phone_code_hash);
+
+ td_api::object_ptr<td_api::authorizationStateWaitCode> get_authorization_state_wait_code() const;
+
+ td_api::object_ptr<td_api::authenticationCodeInfo> get_authentication_code_info_object() const;
+
+ Result<telegram_api::auth_resendCode> resend_code() const;
+
+ using Settings = td_api::object_ptr<td_api::phoneNumberAuthenticationSettings>;
+
+ telegram_api::auth_sendCode send_code(string phone_number, const Settings &settings, int32 api_id,
+ const string &api_hash);
+
+ telegram_api::account_sendVerifyEmailCode send_verify_email_code(const string &email_address);
+
+ telegram_api::account_sendChangePhoneCode send_change_phone_code(Slice phone_number, const Settings &settings);
+
+ telegram_api::account_sendVerifyPhoneCode send_verify_phone_code(Slice phone_number, const Settings &settings);
+
+ telegram_api::account_sendConfirmPhoneCode send_confirm_phone_code(const string &hash, Slice phone_number,
+ const Settings &settings);
+
+ telegram_api::object_ptr<telegram_api::emailVerifyPurposeLoginSetup> get_email_verify_purpose_login_setup() const;
+
+ Slice phone_number() const {
+ return phone_number_;
+ }
+ Slice phone_code_hash() const {
+ return phone_code_hash_;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+ template <class ParserT>
+ void parse(ParserT &parser);
+
+ private:
+ static constexpr int32 SENT_CODE_FLAG_IS_USER_REGISTERED = 1 << 0;
+ static constexpr int32 SENT_CODE_FLAG_HAS_NEXT_TYPE = 1 << 1;
+ static constexpr int32 SENT_CODE_FLAG_HAS_TIMEOUT = 1 << 2;
+
+ struct AuthenticationCodeInfo {
+ enum class Type : int32 { None, Message, Sms, Call, FlashCall, MissedCall };
+ Type type = Type::None;
+ int32 length = 0;
+ string pattern;
+
+ AuthenticationCodeInfo() = default;
+ AuthenticationCodeInfo(Type type, int length, string pattern)
+ : type(type), length(length), pattern(std::move(pattern)) {
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ string phone_number_;
+ string phone_code_hash_;
+
+ SendCodeHelper::AuthenticationCodeInfo sent_code_info_;
+ SendCodeHelper::AuthenticationCodeInfo next_code_info_;
+ double next_code_timestamp_ = 0.0;
+
+ static AuthenticationCodeInfo get_authentication_code_info(
+ tl_object_ptr<telegram_api::auth_CodeType> &&code_type_ptr);
+ static AuthenticationCodeInfo get_sent_authentication_code_info(
+ tl_object_ptr<telegram_api::auth_SentCodeType> &&sent_code_type_ptr);
+
+ static td_api::object_ptr<td_api::AuthenticationCodeType> get_authentication_code_type_object(
+ const AuthenticationCodeInfo &authentication_code_info);
+
+ static telegram_api::object_ptr<telegram_api::codeSettings> get_input_code_settings(const Settings &settings);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.hpp b/protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.hpp
new file mode 100644
index 0000000000..1fab66ac51
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SendCodeHelper.hpp
@@ -0,0 +1,55 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/logevent/LogEventHelper.h"
+#include "td/telegram/SendCodeHelper.h"
+
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void SendCodeHelper::AuthenticationCodeInfo::store(StorerT &storer) const {
+ using td::store;
+ store(type, storer);
+ store(length, storer);
+ store(pattern, storer);
+}
+
+template <class ParserT>
+void SendCodeHelper::AuthenticationCodeInfo::parse(ParserT &parser) {
+ using td::parse;
+ parse(type, parser);
+ parse(length, parser);
+ parse(pattern, parser);
+}
+
+template <class StorerT>
+void SendCodeHelper::store(StorerT &storer) const {
+ using td::store;
+ store(phone_number_, storer);
+ store(true, storer);
+ store(phone_code_hash_, storer);
+ store(sent_code_info_, storer);
+ store(next_code_info_, storer);
+ store_time(next_code_timestamp_, storer);
+}
+
+template <class ParserT>
+void SendCodeHelper::parse(ParserT &parser) {
+ using td::parse;
+ parse(phone_number_, parser);
+ bool legacy_is_registered;
+ parse(legacy_is_registered, parser);
+ parse(phone_code_hash_, parser);
+ parse(sent_code_info_, parser);
+ parse(next_code_info_, parser);
+ parse_time(next_code_timestamp_, parser);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SentEmailCode.cpp b/protocols/Telegram/tdlib/td/td/telegram/SentEmailCode.cpp
new file mode 100644
index 0000000000..8e3abd3036
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SentEmailCode.cpp
@@ -0,0 +1,29 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SentEmailCode.h"
+
+#include "td/utils/logging.h"
+
+namespace td {
+
+SentEmailCode::SentEmailCode(telegram_api::object_ptr<telegram_api::account_sentEmailCode> &&email_code)
+ : email_address_pattern_(std::move(email_code->email_pattern_)), code_length_(email_code->length_) {
+ if (code_length_ < 0 || code_length_ >= 100) {
+ LOG(ERROR) << "Receive wrong email code length " << code_length_;
+ code_length_ = 0;
+ }
+}
+
+td_api::object_ptr<td_api::emailAddressAuthenticationCodeInfo>
+SentEmailCode::get_email_address_authentication_code_info_object() const {
+ if (is_empty()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::emailAddressAuthenticationCodeInfo>(email_address_pattern_, code_length_);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SentEmailCode.h b/protocols/Telegram/tdlib/td/td/telegram/SentEmailCode.h
new file mode 100644
index 0000000000..564bbff88d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SentEmailCode.h
@@ -0,0 +1,50 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class SentEmailCode {
+ string email_address_pattern_;
+ int32 code_length_ = 0;
+
+ public:
+ SentEmailCode() = default;
+
+ SentEmailCode(string &&email_address_pattern, int32 code_length)
+ : email_address_pattern_(std::move(email_address_pattern)), code_length_(code_length) {
+ }
+
+ explicit SentEmailCode(telegram_api::object_ptr<telegram_api::account_sentEmailCode> &&email_code);
+
+ td_api::object_ptr<td_api::emailAddressAuthenticationCodeInfo> get_email_address_authentication_code_info_object()
+ const;
+
+ bool is_empty() const {
+ return email_address_pattern_.empty();
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(email_address_pattern_, storer);
+ td::store(code_length_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ td::parse(email_address_pattern_, parser);
+ td::parse(code_length_, parser);
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.cpp b/protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.cpp
index 723ebed04b..09bb0d3753 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,15 +8,24 @@
#include "td/telegram/Global.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/Td.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/ChainScheduler.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/Promise.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
#include <limits>
namespace td {
+
/*** Sequence Dispatcher ***/
// Sends queries with invokeAfter.
//
@@ -38,7 +47,7 @@ void SequenceDispatcher::send_with_callback(NetQueryPtr query, ActorShared<NetQu
cancel_timeout();
query->debug("Waiting at SequenceDispatcher");
auto query_weak_ref = query.get_weak();
- data_.push_back(Data{State::Start, std::move(query_weak_ref), std::move(query), std::move(callback), 0, 0.0, 0.0});
+ data_.push_back(Data{State::Start, std::move(query_weak_ref), std::move(query), std::move(callback), 0, 0, 0});
loop();
}
@@ -46,11 +55,13 @@ void SequenceDispatcher::check_timeout(Data &data) {
if (data.state_ != State::Start) {
return;
}
- data.query_->total_timeout += data.total_timeout_;
+ data.query_->total_timeout_ += data.total_timeout_;
data.total_timeout_ = 0;
- if (data.query_->total_timeout > data.query_->total_timeout_limit) {
- data.query_->set_error(Status::Error(
- 429, PSLICE() << "Too Many Requests: retry after " << static_cast<int32>(data.last_timeout_ + 0.999)));
+ if (data.query_->total_timeout_ > data.query_->total_timeout_limit_) {
+ LOG(WARNING) << "Fail " << data.query_ << " to " << data.query_->source_ << " because total_timeout "
+ << data.query_->total_timeout_ << " is greater than total_timeout_limit "
+ << data.query_->total_timeout_limit_;
+ data.query_->set_error(Status::Error(429, PSLICE() << "Too Many Requests: retry after " << data.last_timeout_));
data.state_ = State::Dummy;
try_resend_query(data, std::move(data.query_));
}
@@ -63,7 +74,13 @@ void SequenceDispatcher::try_resend_query(Data &data, NetQueryPtr query) {
data.state_ = State::Wait;
wait_cnt_++;
auto token = pos + id_offset_;
- // TODO: is query is ok, use NetQueryCallback::on_result
+ // TODO: if query is ok, use NetQueryCallback::on_result
+ if (data.callback_.empty()) {
+ do_finish(data);
+ send_closure_later(G()->td(), &Td::on_result, std::move(query));
+ loop();
+ return;
+ }
auto promise = PromiseCreator::lambda([&, self = actor_shared(this, token)](NetQueryPtr query) mutable {
if (!query.empty()) {
send_closure(std::move(self), &SequenceDispatcher::on_resend_ok, std::move(query));
@@ -123,16 +140,18 @@ void SequenceDispatcher::on_result(NetQueryPtr query) {
size_t pos = &data - &data_[0];
CHECK(pos < data_.size());
- if (query->last_timeout != 0) {
+ if (query->last_timeout_ != 0) {
for (auto i = pos + 1; i < data_.size(); i++) {
- data_[i].total_timeout_ += query->last_timeout;
- data_[i].last_timeout_ = query->last_timeout;
+ data_[i].total_timeout_ += query->last_timeout_;
+ data_[i].last_timeout_ = query->last_timeout_;
check_timeout(data_[i]);
}
+ query->last_timeout_ = 0;
}
if (query->is_error() && (query->error().code() == NetQuery::ResendInvokeAfter ||
- (query->error().code() == 400 && query->error().message() == "MSG_WAIT_FAILED"))) {
+ (query->error().code() == 400 && (query->error().message() == "MSG_WAIT_FAILED" ||
+ query->error().message() == "MSG_WAIT_TIMEOUT")))) {
VLOG(net_query) << "Resend " << query;
query->resend();
query->debug("Waiting at SequenceDispatcher");
@@ -159,13 +178,16 @@ void SequenceDispatcher::loop() {
if (last_sent_i_ != std::numeric_limits<size_t>::max() && data_[last_sent_i_].state_ == State::Wait) {
invoke_after = data_[last_sent_i_].net_query_ref_;
}
- data_[next_i_].query_->set_invoke_after(invoke_after);
- data_[next_i_].query_->last_timeout = 0;
+ if (!invoke_after.empty()) {
+ data_[next_i_].query_->set_invoke_after({invoke_after});
+ } else {
+ data_[next_i_].query_->set_invoke_after({});
+ }
+ data_[next_i_].query_->last_timeout_ = 0;
VLOG(net_query) << "Send " << data_[next_i_].query_;
data_[next_i_].query_->debug("send to Td::send_with_callback");
- data_[next_i_].query_->set_session_rand(session_rand_);
G()->net_query_dispatcher().dispatch_with_callback(std::move(data_[next_i_].query_),
actor_shared(this, next_i_ + id_offset_));
data_[next_i_].state_ = State::Wait;
@@ -218,7 +240,7 @@ void SequenceDispatcher::tear_down() {
continue;
}
data.state_ = State::Dummy;
- data.query_->set_error(Status::Error(500, "Internal Server Error: closing"));
+ data.query_->set_error(Global::request_aborted_error());
do_finish(data);
}
}
@@ -232,28 +254,32 @@ void SequenceDispatcher::close_silent() {
stop();
}
-/*** MultiSequenceDispatcher ***/
-void MultiSequenceDispatcher::send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback,
- uint64 sequence_id) {
- CHECK(sequence_id != 0);
+void MultiSequenceDispatcherOld::send(NetQueryPtr query) {
+ auto callback = query->move_callback();
+ auto chain_ids = query->get_chain_ids();
+ query->set_in_sequence_dispatcher(true);
+ CHECK(all_of(chain_ids, [](auto chain_id) { return chain_id != 0; }));
+ CHECK(!chain_ids.empty());
+ auto sequence_id = chain_ids[0];
+
auto it_ok = dispatchers_.emplace(sequence_id, Data{0, ActorOwn<SequenceDispatcher>()});
auto &data = it_ok.first->second;
if (it_ok.second) {
LOG(DEBUG) << "Create SequenceDispatcher" << sequence_id;
- data.dispatcher_ = create_actor<SequenceDispatcher>("sequence dispatcher", actor_shared(this, sequence_id));
+ data.dispatcher_ = create_actor<SequenceDispatcher>("SequenceDispatcher", actor_shared(this, sequence_id));
}
data.cnt_++;
query->debug(PSTRING() << "send to SequenceDispatcher " << tag("sequence_id", sequence_id));
send_closure(data.dispatcher_, &SequenceDispatcher::send_with_callback, std::move(query), std::move(callback));
}
-void MultiSequenceDispatcher::on_result() {
+void MultiSequenceDispatcherOld::on_result() {
auto it = dispatchers_.find(get_link_token());
CHECK(it != dispatchers_.end());
it->second.cnt_--;
}
-void MultiSequenceDispatcher::ready_to_close() {
+void MultiSequenceDispatcherOld::ready_to_close() {
auto it = dispatchers_.find(get_link_token());
CHECK(it != dispatchers_.end());
if (it->second.cnt_ == 0) {
@@ -261,4 +287,177 @@ void MultiSequenceDispatcher::ready_to_close() {
dispatchers_.erase(it);
}
}
+
+class MultiSequenceDispatcherImpl final : public MultiSequenceDispatcher {
+ public:
+ void send(NetQueryPtr query) final {
+ auto callback = query->move_callback();
+ auto chain_ids = query->get_chain_ids();
+ query->set_in_sequence_dispatcher(true);
+ CHECK(all_of(chain_ids, [](auto chain_id) { return chain_id != 0; }));
+ Node node;
+ node.net_query = std::move(query);
+ node.net_query->debug("Waiting at SequenceDispatcher");
+ node.net_query_ref = node.net_query.get_weak();
+ node.callback = std::move(callback);
+ scheduler_.create_task(chain_ids, std::move(node));
+ loop();
+ }
+
+ private:
+ struct Node {
+ NetQueryRef net_query_ref;
+ NetQueryPtr net_query;
+ int32 total_timeout{0};
+ int32 last_timeout{0};
+ ActorShared<NetQueryCallback> callback;
+ friend StringBuilder &operator<<(StringBuilder &sb, const Node &node) {
+ return sb << node.net_query;
+ }
+ };
+ ChainScheduler<Node> scheduler_;
+
+ using TaskId = ChainScheduler<Node>::TaskId;
+
+ bool check_timeout(Node &node) {
+ auto &net_query = node.net_query;
+ if (net_query.empty() || net_query->is_ready()) {
+ return false;
+ }
+ if (node.total_timeout > 0) {
+ net_query->total_timeout_ += node.total_timeout;
+ LOG(INFO) << "Set total_timeout to " << net_query->total_timeout_ << " for " << net_query->id();
+ node.total_timeout = 0;
+
+ if (net_query->total_timeout_ > net_query->total_timeout_limit_) {
+ LOG(WARNING) << "Fail " << net_query << " to " << net_query->source_ << " because total_timeout "
+ << net_query->total_timeout_ << " is greater than total_timeout_limit "
+ << net_query->total_timeout_limit_;
+ net_query->set_error(Status::Error(429, PSLICE() << "Too Many Requests: retry after " << node.last_timeout));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void on_result(NetQueryPtr query) final {
+ auto task_id = TaskId(get_link_token());
+ auto &node = *scheduler_.get_task_extra(task_id);
+
+ if (query->last_timeout_ != 0) {
+ vector<TaskId> to_check_timeout;
+
+ auto tl_constructor = query->tl_constructor();
+ scheduler_.for_each_dependent(task_id, [&](TaskId child_task_id) {
+ auto &child_node = *scheduler_.get_task_extra(child_task_id);
+ if (child_node.net_query_ref->tl_constructor() == tl_constructor && child_task_id != task_id) {
+ child_node.total_timeout += query->last_timeout_;
+ child_node.last_timeout = query->last_timeout_;
+ to_check_timeout.push_back(child_task_id);
+ }
+ });
+ query->last_timeout_ = 0;
+
+ for (auto dependent_task_id : to_check_timeout) {
+ auto &child_node = *scheduler_.get_task_extra(dependent_task_id);
+ if (check_timeout(child_node)) {
+ scheduler_.pause_task(dependent_task_id);
+ try_resend(dependent_task_id);
+ }
+ }
+ }
+
+ if (query->is_error() && (query->error().code() == NetQuery::ResendInvokeAfter ||
+ (query->error().code() == 400 && (query->error().message() == "MSG_WAIT_FAILED" ||
+ query->error().message() == "MSG_WAIT_TIMEOUT")))) {
+ VLOG(net_query) << "Resend " << query;
+ query->resend();
+ do_resend(task_id, node, std::move(query));
+ loop();
+ return;
+ }
+ node.net_query = std::move(query);
+ try_resend(task_id);
+ }
+
+ void try_resend(TaskId task_id) {
+ auto &node = *scheduler_.get_task_extra(task_id);
+ if (node.callback.empty()) {
+ auto query = std::move(node.net_query);
+ scheduler_.finish_task(task_id);
+ send_closure_later(G()->td(), &Td::on_result, std::move(query));
+ loop();
+ return;
+ }
+ auto promise = promise_send_closure(actor_shared(this, task_id), &MultiSequenceDispatcherImpl::on_resend);
+ send_closure(node.callback, &NetQueryCallback::on_result_resendable, std::move(node.net_query), std::move(promise));
+ }
+
+ void on_resend(Result<NetQueryPtr> r_query) {
+ auto task_id = TaskId(get_link_token());
+ auto &node = *scheduler_.get_task_extra(task_id);
+ if (r_query.is_error()) {
+ scheduler_.finish_task(task_id);
+ } else {
+ do_resend(task_id, node, r_query.move_as_ok());
+ }
+ loop();
+ }
+
+ void do_resend(TaskId task_id, Node &node, NetQueryPtr &&query) {
+ node.net_query = std::move(query);
+ node.net_query->debug("Waiting at SequenceDispatcher");
+ node.net_query_ref = node.net_query.get_weak();
+ if (check_timeout(node)) {
+ scheduler_.pause_task(task_id);
+ try_resend(task_id);
+ } else {
+ scheduler_.reset_task(task_id);
+ }
+ }
+
+ void loop() final {
+ flush_pending_queries();
+ }
+
+ void tear_down() final {
+ // Leaves scheduler_ in an invalid state, but we are closing anyway
+ scheduler_.for_each([](Node &node) {
+ if (node.net_query.empty()) {
+ return;
+ }
+ node.net_query->set_error(Global::request_aborted_error());
+ });
+ }
+
+ void flush_pending_queries() {
+ while (true) {
+ auto o_task = scheduler_.start_next_task();
+ if (!o_task) {
+ break;
+ }
+ auto task = o_task.unwrap();
+ auto &node = *scheduler_.get_task_extra(task.task_id);
+ CHECK(!node.net_query.empty());
+
+ auto query = std::move(node.net_query);
+ vector<NetQueryRef> parents;
+ for (auto parent_id : task.parents) {
+ auto &parent_node = *scheduler_.get_task_extra(parent_id);
+ parents.push_back(parent_node.net_query_ref);
+ CHECK(!parent_node.net_query_ref.empty());
+ }
+
+ query->set_invoke_after(std::move(parents));
+ query->last_timeout_ = 0;
+ query->debug("dispatch_with_callback");
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, task.task_id));
+ }
+ }
+};
+
+ActorOwn<MultiSequenceDispatcher> MultiSequenceDispatcher::create(Slice name) {
+ return ActorOwn<MultiSequenceDispatcher>(create_actor<MultiSequenceDispatcherImpl>(name));
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.h b/protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.h
index 5ce46068aa..e4e7fa9349 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/SequenceDispatcher.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,14 +8,18 @@
#include "td/telegram/net/NetQuery.h"
+#include "td/actor/actor.h"
+
#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
#include <limits>
-#include <unordered_map>
namespace td {
-class SequenceDispatcher : public NetQueryCallback {
+
+class SequenceDispatcher final : public NetQueryCallback {
public:
class Parent : public Actor {
public:
@@ -26,24 +30,24 @@ class SequenceDispatcher : public NetQueryCallback {
explicit SequenceDispatcher(ActorShared<Parent> parent) : parent_(std::move(parent)) {
}
void send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback);
- void on_result(NetQueryPtr query) override;
+ void on_result(NetQueryPtr query) final;
void close_silent();
private:
- enum class State { Start, Wait, Finish, Dummy };
+ enum class State : int32 { Start, Wait, Finish, Dummy };
struct Data {
State state_;
NetQueryRef net_query_ref_;
NetQueryPtr query_;
ActorShared<NetQueryCallback> callback_;
uint64 generation_;
- double total_timeout_;
- double last_timeout_;
+ int32 total_timeout_;
+ int32 last_timeout_;
};
ActorShared<Parent> parent_;
size_t id_offset_ = 1;
- std::vector<Data> data_;
+ vector<Data> data_;
size_t finish_i_ = 0; // skip state_ == State::Finish
size_t next_i_ = 0;
size_t last_sent_i_ = std::numeric_limits<size_t>::max();
@@ -62,25 +66,35 @@ class SequenceDispatcher : public NetQueryCallback {
void do_resend(Data &data);
void do_finish(Data &data);
- void loop() override;
+ void loop() final;
void try_shrink();
- void timeout_expired() override;
- void hangup() override;
- void tear_down() override;
+ void timeout_expired() final;
+ void hangup() final;
+ void tear_down() final;
};
-class MultiSequenceDispatcher : public SequenceDispatcher::Parent {
+class MultiSequenceDispatcherOld final : public SequenceDispatcher::Parent {
public:
- void send_with_callback(NetQueryPtr query, ActorShared<NetQueryCallback> callback, uint64 sequence_id);
+ void send(NetQueryPtr query);
+ static ActorOwn<MultiSequenceDispatcherOld> create(Slice name) {
+ return create_actor<MultiSequenceDispatcherOld>(name);
+ }
private:
struct Data {
int32 cnt_;
ActorOwn<SequenceDispatcher> dispatcher_;
};
- std::unordered_map<uint64, Data> dispatchers_;
- void on_result() override;
- void ready_to_close() override;
+ FlatHashMap<uint64, Data> dispatchers_;
+ void on_result() final;
+ void ready_to_close() final;
};
+
+class MultiSequenceDispatcher : public NetQueryCallback {
+ public:
+ virtual void send(NetQueryPtr query) = 0;
+ static ActorOwn<MultiSequenceDispatcher> create(Slice name);
+};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ServerMessageId.h b/protocols/Telegram/tdlib/td/td/telegram/ServerMessageId.h
new file mode 100644
index 0000000000..0352c414e4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ServerMessageId.h
@@ -0,0 +1,43 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#include <type_traits>
+
+namespace td {
+
+class ServerMessageId {
+ int32 id = 0;
+
+ public:
+ ServerMessageId() = default;
+
+ explicit ServerMessageId(int32 message_id) : id(message_id) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ ServerMessageId(T message_id) = delete;
+
+ bool is_valid() const {
+ return id > 0;
+ }
+
+ int32 get() const {
+ return id;
+ }
+
+ bool operator==(const ServerMessageId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const ServerMessageId &other) const {
+ return id != other.id;
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SetWithPosition.h b/protocols/Telegram/tdlib/td/td/telegram/SetWithPosition.h
new file mode 100644
index 0000000000..8690bf81fd
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SetWithPosition.h
@@ -0,0 +1,228 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+
+namespace td {
+
+template <class T>
+class FastSetWithPosition {
+ public:
+ std::vector<T> get_some_elements() const {
+ std::vector<T> res;
+ res.reserve(4);
+ if (!checked_.empty()) {
+ res.push_back(*checked_.begin());
+ res.push_back(*checked_.rbegin());
+ }
+ if (!not_checked_.empty()) {
+ res.push_back(*not_checked_.begin());
+ res.push_back(*not_checked_.rbegin());
+ }
+ td::unique(res);
+ if (res.size() > 2) {
+ res.erase(res.begin() + 1, res.end() - 1);
+ }
+ return res;
+ }
+
+ bool add(T x) {
+ if (checked_.count(x) != 0) {
+ return false;
+ }
+ return not_checked_.insert(x).second;
+ }
+
+ bool remove(T x) {
+ return checked_.erase(x) != 0 || not_checked_.erase(x) != 0;
+ }
+
+ bool has_next() const {
+ return !not_checked_.empty();
+ }
+
+ void reset_position() {
+ if (not_checked_.empty()) {
+ not_checked_ = std::move(checked_);
+ } else {
+ not_checked_.insert(checked_.begin(), checked_.end());
+ }
+ reset_to_empty(checked_);
+ }
+
+ T next() {
+ CHECK(has_next());
+ auto it = not_checked_.begin();
+ auto res = *it;
+ not_checked_.erase(it);
+ checked_.insert(res);
+ return res;
+ }
+
+ void merge(FastSetWithPosition &&other) {
+ if (this == &other) {
+ return;
+ }
+
+ if (size() < other.size()) {
+ std::swap(*this, other);
+ }
+ for (auto x : other.checked_) {
+ not_checked_.erase(x);
+ checked_.insert(x);
+ }
+
+ for (auto x : other.not_checked_) {
+ if (checked_.count(x) != 0) {
+ continue;
+ }
+ not_checked_.insert(x);
+ }
+ }
+
+ size_t size() const {
+ return checked_.size() + not_checked_.size();
+ }
+
+ bool empty() const {
+ return size() == 0;
+ }
+
+ private:
+ std::set<T> checked_;
+ std::set<T> not_checked_;
+};
+
+template <class T>
+class SetWithPosition {
+ public:
+ std::vector<T> get_some_elements() const {
+ if (fast_) {
+ return fast_->get_some_elements();
+ }
+ if (has_value_) {
+ return {value_};
+ }
+ return {};
+ }
+
+ bool add(T x) {
+ if (fast_) {
+ return fast_->add(x);
+ }
+ if (!has_value_) {
+ value_ = x;
+ has_value_ = true;
+ is_checked_ = false;
+ return true;
+ }
+ if (value_ == x) {
+ return false;
+ }
+ make_fast();
+ return fast_->add(x);
+ }
+
+ bool remove(T x) {
+ if (fast_) {
+ return fast_->remove(x);
+ }
+ if (has_value_ && value_ == x) {
+ has_value_ = false;
+ is_checked_ = false;
+ return true;
+ }
+ return false;
+ }
+
+ bool has_next() const {
+ if (fast_) {
+ return fast_->has_next();
+ }
+ return has_value_ && !is_checked_;
+ }
+
+ void reset_position() {
+ if (fast_) {
+ fast_->reset_position();
+ return;
+ }
+ is_checked_ = false;
+ }
+
+ T next() {
+ CHECK(has_next());
+ if (fast_) {
+ return fast_->next();
+ }
+ is_checked_ = true;
+ return value_;
+ }
+
+ void merge(SetWithPosition &&other) {
+ if (this == &other) {
+ return;
+ }
+ if (size() < other.size()) {
+ std::swap(*this, other);
+ }
+ if (other.size() == 0) {
+ return;
+ }
+ if (other.fast_ == nullptr && fast_ == nullptr && value_ == other.value_) {
+ is_checked_ |= other.is_checked_;
+ other.value_ = T();
+ other.has_value_ = false;
+ other.is_checked_ = false;
+ return;
+ }
+ make_fast();
+ other.make_fast();
+ fast_->merge(std::move(*other.fast_));
+ reset_to_empty(other);
+ }
+
+ size_t size() const {
+ if (fast_) {
+ return fast_->size();
+ }
+ return static_cast<size_t>(has_value_);
+ }
+
+ bool empty() const {
+ if (fast_) {
+ return false;
+ }
+ return !has_value_;
+ }
+
+ private:
+ T value_{};
+ bool has_value_{false};
+ bool is_checked_{false};
+ unique_ptr<FastSetWithPosition<T>> fast_;
+
+ void make_fast() {
+ if (fast_) {
+ return;
+ }
+ fast_ = make_unique<FastSetWithPosition<T>>();
+ CHECK(has_value_);
+ fast_->add(value_);
+ if (is_checked_) {
+ fast_->next();
+ }
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SpecialStickerSetType.cpp b/protocols/Telegram/tdlib/td/td/telegram/SpecialStickerSetType.cpp
new file mode 100644
index 0000000000..83e4591717
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SpecialStickerSetType.cpp
@@ -0,0 +1,111 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SpecialStickerSetType.h"
+
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+
+namespace td {
+
+SpecialStickerSetType SpecialStickerSetType::animated_emoji() {
+ return SpecialStickerSetType("animated_emoji_sticker_set");
+}
+
+SpecialStickerSetType SpecialStickerSetType::animated_emoji_click() {
+ return SpecialStickerSetType("animated_emoji_click_sticker_set");
+}
+
+SpecialStickerSetType SpecialStickerSetType::animated_dice(const string &emoji) {
+ CHECK(!emoji.empty());
+ return SpecialStickerSetType(PSTRING() << "animated_dice_sticker_set#" << emoji);
+}
+
+SpecialStickerSetType SpecialStickerSetType::premium_gifts() {
+ return SpecialStickerSetType("premium_gifts_sticker_set");
+}
+
+SpecialStickerSetType SpecialStickerSetType::generic_animations() {
+ return SpecialStickerSetType("generic_animations_sticker_set");
+}
+
+SpecialStickerSetType SpecialStickerSetType::default_statuses() {
+ return SpecialStickerSetType("default_statuses_sticker_set");
+}
+
+SpecialStickerSetType SpecialStickerSetType::default_topic_icons() {
+ return SpecialStickerSetType("default_topic_icons_sticker_set");
+}
+
+SpecialStickerSetType::SpecialStickerSetType(
+ const telegram_api::object_ptr<telegram_api::InputStickerSet> &input_sticker_set) {
+ CHECK(input_sticker_set != nullptr);
+ switch (input_sticker_set->get_id()) {
+ case telegram_api::inputStickerSetAnimatedEmoji::ID:
+ *this = animated_emoji();
+ break;
+ case telegram_api::inputStickerSetAnimatedEmojiAnimations::ID:
+ *this = animated_emoji_click();
+ break;
+ case telegram_api::inputStickerSetDice::ID:
+ *this = animated_dice(static_cast<const telegram_api::inputStickerSetDice *>(input_sticker_set.get())->emoticon_);
+ break;
+ case telegram_api::inputStickerSetPremiumGifts::ID:
+ *this = premium_gifts();
+ break;
+ case telegram_api::inputStickerSetEmojiGenericAnimations::ID:
+ *this = generic_animations();
+ break;
+ case telegram_api::inputStickerSetEmojiDefaultStatuses::ID:
+ *this = default_statuses();
+ break;
+ case telegram_api::inputStickerSetEmojiDefaultTopicIcons::ID:
+ *this = default_topic_icons();
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+string SpecialStickerSetType::get_dice_emoji() const {
+ auto prefix = Slice("animated_dice_sticker_set#");
+ if (begins_with(type_, prefix)) {
+ return type_.substr(prefix.size());
+ }
+ return string();
+}
+
+telegram_api::object_ptr<telegram_api::InputStickerSet> SpecialStickerSetType::get_input_sticker_set() const {
+ if (*this == animated_emoji()) {
+ return telegram_api::make_object<telegram_api::inputStickerSetAnimatedEmoji>();
+ }
+ if (*this == animated_emoji_click()) {
+ return telegram_api::make_object<telegram_api::inputStickerSetAnimatedEmojiAnimations>();
+ }
+ if (*this == premium_gifts()) {
+ return telegram_api::make_object<telegram_api::inputStickerSetPremiumGifts>();
+ }
+ if (*this == generic_animations()) {
+ return telegram_api::make_object<telegram_api::inputStickerSetEmojiGenericAnimations>();
+ }
+ if (*this == default_statuses()) {
+ return telegram_api::make_object<telegram_api::inputStickerSetEmojiDefaultStatuses>();
+ }
+ if (*this == default_topic_icons()) {
+ return telegram_api::make_object<telegram_api::inputStickerSetEmojiDefaultTopicIcons>();
+ }
+ auto emoji = get_dice_emoji();
+ if (!emoji.empty()) {
+ return telegram_api::make_object<telegram_api::inputStickerSetDice>(emoji);
+ }
+
+ UNREACHABLE();
+ return nullptr;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SpecialStickerSetType.h b/protocols/Telegram/tdlib/td/td/telegram/SpecialStickerSetType.h
new file mode 100644
index 0000000000..bade724464
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SpecialStickerSetType.h
@@ -0,0 +1,66 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+
+namespace td {
+
+class SpecialStickerSetType {
+ explicit SpecialStickerSetType(string type) : type_(type) {
+ }
+
+ friend struct SpecialStickerSetTypeHash;
+
+ public:
+ string type_;
+
+ static SpecialStickerSetType animated_emoji();
+
+ static SpecialStickerSetType animated_emoji_click();
+
+ static SpecialStickerSetType animated_dice(const string &emoji);
+
+ static SpecialStickerSetType premium_gifts();
+
+ static SpecialStickerSetType generic_animations();
+
+ static SpecialStickerSetType default_statuses();
+
+ static SpecialStickerSetType default_topic_icons();
+
+ string get_dice_emoji() const;
+
+ bool is_empty() const {
+ return type_.empty();
+ }
+
+ SpecialStickerSetType() = default;
+
+ explicit SpecialStickerSetType(const telegram_api::object_ptr<telegram_api::InputStickerSet> &input_sticker_set);
+
+ telegram_api::object_ptr<telegram_api::InputStickerSet> get_input_sticker_set() const;
+};
+
+inline bool operator==(const SpecialStickerSetType &lhs, const SpecialStickerSetType &rhs) {
+ return lhs.type_ == rhs.type_;
+}
+
+inline bool operator!=(const SpecialStickerSetType &lhs, const SpecialStickerSetType &rhs) {
+ return !(lhs == rhs);
+}
+
+struct SpecialStickerSetTypeHash {
+ uint32 operator()(SpecialStickerSetType type) const {
+ return Hash<string>()(type.type_);
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SponsoredMessageManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/SponsoredMessageManager.cpp
new file mode 100644
index 0000000000..d77768649c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SponsoredMessageManager.cpp
@@ -0,0 +1,370 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SponsoredMessageManager.h"
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/LinkManager.h"
+#include "td/telegram/MessageContent.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/net/NetQueryCreator.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class GetSponsoredMessagesQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::messages_SponsoredMessages>> promise_;
+ ChannelId channel_id_;
+
+ public:
+ explicit GetSponsoredMessagesQuery(
+ Promise<telegram_api::object_ptr<telegram_api::messages_SponsoredMessages>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(ChannelId channel_id) {
+ channel_id_ = channel_id;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ if (input_channel == nullptr) {
+ return promise_.set_error(Status::Error(400, "Chat info not found"));
+ }
+ send_query(G()->net_query_creator().create(telegram_api::channels_getSponsoredMessages(std::move(input_channel))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_getSponsoredMessages>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "GetSponsoredMessagesQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ViewSponsoredMessageQuery final : public Td::ResultHandler {
+ ChannelId channel_id_;
+
+ public:
+ void send(ChannelId channel_id, const string &message_id) {
+ channel_id_ = channel_id;
+ auto input_channel = td_->contacts_manager_->get_input_channel(channel_id);
+ if (input_channel == nullptr) {
+ return;
+ }
+ send_query(G()->net_query_creator().create(
+ telegram_api::channels_viewSponsoredMessage(std::move(input_channel), BufferSlice(message_id))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::channels_viewSponsoredMessage>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+ }
+
+ void on_error(Status status) final {
+ td_->contacts_manager_->on_get_channel_error(channel_id_, status, "ViewSponsoredMessageQuery");
+ }
+};
+
+struct SponsoredMessageManager::SponsoredMessage {
+ int64 local_id = 0;
+ bool is_recommended = false;
+ bool show_dialog_photo = false;
+ DialogId sponsor_dialog_id;
+ ServerMessageId server_message_id;
+ string start_param;
+ string invite_hash;
+ unique_ptr<MessageContent> content;
+
+ SponsoredMessage(int64 local_id, bool is_recommended, bool show_dialog_photo, DialogId sponsor_dialog_id,
+ ServerMessageId server_message_id, string start_param, string invite_hash,
+ unique_ptr<MessageContent> content)
+ : local_id(local_id)
+ , is_recommended(is_recommended)
+ , show_dialog_photo(show_dialog_photo)
+ , sponsor_dialog_id(sponsor_dialog_id)
+ , server_message_id(server_message_id)
+ , start_param(std::move(start_param))
+ , invite_hash(std::move(invite_hash))
+ , content(std::move(content)) {
+ }
+};
+
+struct SponsoredMessageManager::DialogSponsoredMessages {
+ vector<Promise<td_api::object_ptr<td_api::sponsoredMessages>>> promises;
+ vector<SponsoredMessage> messages;
+ FlatHashMap<int64, string> message_random_ids;
+ int32 messages_between = 0;
+ bool is_premium = false;
+};
+
+SponsoredMessageManager::SponsoredMessageManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+ delete_cached_sponsored_messages_timeout_.set_callback(on_delete_cached_sponsored_messages_timeout_callback);
+ delete_cached_sponsored_messages_timeout_.set_callback_data(static_cast<void *>(this));
+}
+
+SponsoredMessageManager::~SponsoredMessageManager() = default;
+
+void SponsoredMessageManager::tear_down() {
+ parent_.reset();
+}
+
+void SponsoredMessageManager::on_delete_cached_sponsored_messages_timeout_callback(void *sponsored_message_manager_ptr,
+ int64 dialog_id_int) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto sponsored_message_manager = static_cast<SponsoredMessageManager *>(sponsored_message_manager_ptr);
+ send_closure_later(sponsored_message_manager->actor_id(sponsored_message_manager),
+ &SponsoredMessageManager::delete_cached_sponsored_messages, DialogId(dialog_id_int));
+}
+
+void SponsoredMessageManager::delete_cached_sponsored_messages(DialogId dialog_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto it = dialog_sponsored_messages_.find(dialog_id);
+ if (it != dialog_sponsored_messages_.end() && it->second->promises.empty()) {
+ dialog_sponsored_messages_.erase(it);
+ }
+}
+
+td_api::object_ptr<td_api::sponsoredMessage> SponsoredMessageManager::get_sponsored_message_object(
+ DialogId dialog_id, const SponsoredMessage &sponsored_message) const {
+ td_api::object_ptr<td_api::chatInviteLinkInfo> chat_invite_link_info;
+ td_api::object_ptr<td_api::InternalLinkType> link;
+ switch (sponsored_message.sponsor_dialog_id.get_type()) {
+ case DialogType::User: {
+ auto user_id = sponsored_message.sponsor_dialog_id.get_user_id();
+ if (!td_->contacts_manager_->is_user_bot(user_id)) {
+ break;
+ }
+ auto bot_username = td_->contacts_manager_->get_user_first_username(user_id);
+ if (bot_username.empty()) {
+ break;
+ }
+ link = td_api::make_object<td_api::internalLinkTypeBotStart>(bot_username, sponsored_message.start_param, false);
+ break;
+ }
+ case DialogType::Channel:
+ if (sponsored_message.server_message_id.is_valid()) {
+ auto channel_id = sponsored_message.sponsor_dialog_id.get_channel_id();
+ auto t_me = td_->option_manager_->get_option_string("t_me_url", "https://t.me/");
+ link = td_api::make_object<td_api::internalLinkTypeMessage>(
+ PSTRING() << t_me << "c/" << channel_id.get() << '/' << sponsored_message.server_message_id.get());
+ }
+ break;
+ case DialogType::None: {
+ CHECK(!sponsored_message.invite_hash.empty());
+ auto invite_link = LinkManager::get_dialog_invite_link(sponsored_message.invite_hash, false);
+ chat_invite_link_info = td_->contacts_manager_->get_chat_invite_link_info_object(invite_link);
+ if (chat_invite_link_info == nullptr) {
+ LOG(ERROR) << "Failed to get invite link info for " << invite_link;
+ return nullptr;
+ }
+ link = td_api::make_object<td_api::internalLinkTypeChatInvite>(
+ LinkManager::get_dialog_invite_link(sponsored_message.invite_hash, true));
+ }
+ default:
+ break;
+ }
+ return td_api::make_object<td_api::sponsoredMessage>(
+ sponsored_message.local_id, sponsored_message.is_recommended, sponsored_message.sponsor_dialog_id.get(),
+ std::move(chat_invite_link_info), sponsored_message.show_dialog_photo, std::move(link),
+ get_message_content_object(sponsored_message.content.get(), td_, dialog_id, 0, false, true, -1));
+}
+
+td_api::object_ptr<td_api::sponsoredMessages> SponsoredMessageManager::get_sponsored_messages_object(
+ DialogId dialog_id, const DialogSponsoredMessages &sponsored_messages) const {
+ auto messages = transform(sponsored_messages.messages, [this, dialog_id](const SponsoredMessage &message) {
+ return get_sponsored_message_object(dialog_id, message);
+ });
+ return td_api::make_object<td_api::sponsoredMessages>(std::move(messages), sponsored_messages.messages_between);
+}
+
+void SponsoredMessageManager::get_dialog_sponsored_messages(
+ DialogId dialog_id, Promise<td_api::object_ptr<td_api::sponsoredMessages>> &&promise) {
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "get_dialog_sponsored_message")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (dialog_id.get_type() != DialogType::Channel) {
+ return promise.set_value(td_api::make_object<td_api::sponsoredMessages>());
+ }
+
+ auto &messages = dialog_sponsored_messages_[dialog_id];
+ if (messages != nullptr && messages->promises.empty()) {
+ if (messages->is_premium == td_->option_manager_->get_option_boolean("is_premium", false)) {
+ // use cached value
+ return promise.set_value(get_sponsored_messages_object(dialog_id, *messages));
+ } else {
+ // drop cache
+ messages = nullptr;
+ delete_cached_sponsored_messages_timeout_.cancel_timeout(dialog_id.get());
+ }
+ }
+
+ if (messages == nullptr) {
+ messages = make_unique<DialogSponsoredMessages>();
+ }
+ messages->promises.push_back(std::move(promise));
+ if (messages->promises.size() == 1) {
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this),
+ dialog_id](Result<telegram_api::object_ptr<telegram_api::messages_SponsoredMessages>> &&result) mutable {
+ send_closure(actor_id, &SponsoredMessageManager::on_get_dialog_sponsored_messages, dialog_id,
+ std::move(result));
+ });
+ td_->create_handler<GetSponsoredMessagesQuery>(std::move(query_promise))->send(dialog_id.get_channel_id());
+ }
+}
+
+void SponsoredMessageManager::on_get_dialog_sponsored_messages(
+ DialogId dialog_id, Result<telegram_api::object_ptr<telegram_api::messages_SponsoredMessages>> &&result) {
+ if (result.is_ok() && G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
+
+ auto &messages = dialog_sponsored_messages_[dialog_id];
+ CHECK(messages != nullptr);
+ auto promises = std::move(messages->promises);
+ reset_to_empty(messages->promises);
+ CHECK(messages->messages.empty());
+ CHECK(messages->message_random_ids.empty());
+
+ if (result.is_error()) {
+ dialog_sponsored_messages_.erase(dialog_id);
+ fail_promises(promises, result.move_as_error());
+ return;
+ }
+
+ auto sponsored_messages_ptr = result.move_as_ok();
+ switch (sponsored_messages_ptr->get_id()) {
+ case telegram_api::messages_sponsoredMessages::ID: {
+ auto sponsored_messages =
+ telegram_api::move_object_as<telegram_api::messages_sponsoredMessages>(sponsored_messages_ptr);
+
+ td_->contacts_manager_->on_get_users(std::move(sponsored_messages->users_), "on_get_dialog_sponsored_messages");
+ td_->contacts_manager_->on_get_chats(std::move(sponsored_messages->chats_), "on_get_dialog_sponsored_messages");
+
+ for (auto &sponsored_message : sponsored_messages->messages_) {
+ DialogId sponsor_dialog_id;
+ ServerMessageId server_message_id;
+ string invite_hash;
+ if (sponsored_message->from_id_ != nullptr) {
+ sponsor_dialog_id = DialogId(sponsored_message->from_id_);
+ if (!sponsor_dialog_id.is_valid() || !td_->messages_manager_->have_dialog_info_force(sponsor_dialog_id)) {
+ LOG(ERROR) << "Receive unknown sponsor " << sponsor_dialog_id;
+ continue;
+ }
+ server_message_id = ServerMessageId(sponsored_message->channel_post_);
+ if (!server_message_id.is_valid() && server_message_id != ServerMessageId()) {
+ LOG(ERROR) << "Receive invalid channel post in " << to_string(sponsored_message);
+ server_message_id = ServerMessageId();
+ }
+ td_->messages_manager_->force_create_dialog(sponsor_dialog_id, "on_get_dialog_sponsored_messages");
+ } else if (sponsored_message->chat_invite_ != nullptr && !sponsored_message->chat_invite_hash_.empty()) {
+ auto invite_link = LinkManager::get_dialog_invite_link(sponsored_message->chat_invite_hash_, false);
+ if (invite_link.empty()) {
+ LOG(ERROR) << "Receive invalid invite link hash in " << to_string(sponsored_message);
+ continue;
+ }
+ auto chat_invite = to_string(sponsored_message->chat_invite_);
+ td_->contacts_manager_->on_get_dialog_invite_link_info(
+ invite_link, std::move(sponsored_message->chat_invite_), Promise<Unit>());
+ auto chat_invite_link_info = td_->contacts_manager_->get_chat_invite_link_info_object(invite_link);
+ if (chat_invite_link_info == nullptr) {
+ LOG(ERROR) << "Failed to get invite link info from " << chat_invite << " for "
+ << to_string(sponsored_message);
+ continue;
+ }
+ invite_hash = std::move(sponsored_message->chat_invite_hash_);
+ } else {
+ LOG(ERROR) << "Receive " << to_string(sponsored_message);
+ continue;
+ }
+
+ auto message_text = get_message_text(td_->contacts_manager_.get(), std::move(sponsored_message->message_),
+ std::move(sponsored_message->entities_), true, true, 0, false,
+ "on_get_dialog_sponsored_messages");
+ int32 ttl = 0;
+ bool disable_web_page_preview = false;
+ auto content = get_message_content(td_, std::move(message_text), nullptr, sponsor_dialog_id, true, UserId(),
+ &ttl, &disable_web_page_preview, "on_get_dialog_sponsored_messages");
+ if (ttl != 0) {
+ LOG(ERROR) << "Receive sponsored message with TTL " << ttl;
+ continue;
+ }
+ CHECK(disable_web_page_preview);
+
+ current_sponsored_message_id_ = current_sponsored_message_id_.get_next_message_id(MessageType::Local);
+ if (!current_sponsored_message_id_.is_valid_sponsored()) {
+ LOG(ERROR) << "Sponsored message ID overflowed";
+ current_sponsored_message_id_ = MessageId::max().get_next_message_id(MessageType::Local);
+ CHECK(current_sponsored_message_id_.is_valid_sponsored());
+ }
+ auto local_id = current_sponsored_message_id_.get();
+ CHECK(!current_sponsored_message_id_.is_valid());
+ CHECK(!current_sponsored_message_id_.is_scheduled());
+ auto is_inserted =
+ messages->message_random_ids.emplace(local_id, sponsored_message->random_id_.as_slice().str()).second;
+ CHECK(is_inserted);
+ messages->messages.emplace_back(
+ local_id, sponsored_message->recommended_, sponsored_message->show_peer_photo_, sponsor_dialog_id,
+ server_message_id, std::move(sponsored_message->start_param_), std::move(invite_hash), std::move(content));
+ }
+ messages->messages_between = sponsored_messages->posts_between_;
+ break;
+ }
+ case telegram_api::messages_sponsoredMessagesEmpty::ID:
+ break;
+ default:
+ UNREACHABLE();
+ }
+ messages->is_premium = td_->option_manager_->get_option_boolean("is_premium", false);
+
+ for (auto &promise : promises) {
+ promise.set_value(get_sponsored_messages_object(dialog_id, *messages));
+ }
+ delete_cached_sponsored_messages_timeout_.set_timeout_in(dialog_id.get(), 300.0);
+}
+
+void SponsoredMessageManager::view_sponsored_message(DialogId dialog_id, MessageId sponsored_message_id) {
+ auto it = dialog_sponsored_messages_.find(dialog_id);
+ if (it == dialog_sponsored_messages_.end()) {
+ return;
+ }
+ auto random_id_it = it->second->message_random_ids.find(sponsored_message_id.get());
+ if (random_id_it == it->second->message_random_ids.end()) {
+ return;
+ }
+
+ auto random_id = std::move(random_id_it->second);
+ it->second->message_random_ids.erase(random_id_it);
+ td_->create_handler<ViewSponsoredMessageQuery>()->send(dialog_id.get_channel_id(), random_id);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SponsoredMessageManager.h b/protocols/Telegram/tdlib/td/td/telegram/SponsoredMessageManager.h
new file mode 100644
index 0000000000..42b226823b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SponsoredMessageManager.h
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/MultiTimeout.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class Td;
+
+class SponsoredMessageManager final : public Actor {
+ public:
+ SponsoredMessageManager(Td *td, ActorShared<> parent);
+ SponsoredMessageManager(const SponsoredMessageManager &) = delete;
+ SponsoredMessageManager &operator=(const SponsoredMessageManager &) = delete;
+ SponsoredMessageManager(SponsoredMessageManager &&) = delete;
+ SponsoredMessageManager &operator=(SponsoredMessageManager &&) = delete;
+ ~SponsoredMessageManager() final;
+
+ void get_dialog_sponsored_messages(DialogId dialog_id,
+ Promise<td_api::object_ptr<td_api::sponsoredMessages>> &&promise);
+
+ void view_sponsored_message(DialogId dialog_id, MessageId sponsored_message_id);
+
+ private:
+ struct SponsoredMessage;
+ struct DialogSponsoredMessages;
+
+ void tear_down() final;
+
+ static void on_delete_cached_sponsored_messages_timeout_callback(void *sponsored_message_manager_ptr,
+ int64 dialog_id_int);
+
+ void delete_cached_sponsored_messages(DialogId dialog_id);
+
+ td_api::object_ptr<td_api::sponsoredMessage> get_sponsored_message_object(
+ DialogId dialog_id, const SponsoredMessage &sponsored_message) const;
+
+ td_api::object_ptr<td_api::sponsoredMessages> get_sponsored_messages_object(
+ DialogId dialog_id, const DialogSponsoredMessages &sponsored_messages) const;
+
+ void on_get_dialog_sponsored_messages(
+ DialogId dialog_id, Result<telegram_api::object_ptr<telegram_api::messages_SponsoredMessages>> &&result);
+
+ FlatHashMap<DialogId, unique_ptr<DialogSponsoredMessages>, DialogIdHash> dialog_sponsored_messages_;
+
+ MessageId current_sponsored_message_id_ = MessageId::max();
+
+ MultiTimeout delete_cached_sponsored_messages_timeout_{"DeleteCachedSponsoredMessagesTimeout"};
+
+ Td *td_;
+ ActorShared<> parent_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StateManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/StateManager.cpp
index 6777f44154..b967a25c66 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/StateManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/StateManager.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,25 +10,9 @@
#include "td/actor/SleepActor.h"
#include "td/utils/logging.h"
-#include "td/utils/misc.h"
#include "td/utils/Time.h"
namespace td {
-void StateManager::inc_connect() {
- auto &cnt = get_link_token() == 1 ? connect_cnt_ : connect_proxy_cnt_;
- cnt++;
- if (cnt == 1) {
- loop();
- }
-}
-void StateManager::dec_connect() {
- auto &cnt = get_link_token() == 1 ? connect_cnt_ : connect_proxy_cnt_;
- CHECK(cnt > 0);
- cnt--;
- if (cnt == 0) {
- loop();
- }
-}
void StateManager::on_synchronized(bool is_synchronized) {
if (sync_flag_ != is_synchronized) {
@@ -37,17 +21,18 @@ void StateManager::on_synchronized(bool is_synchronized) {
}
if (sync_flag_ && !was_sync_) {
was_sync_ = true;
- auto promises = std::move(wait_first_sync_);
- reset_to_empty(wait_first_sync_);
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(wait_first_sync_);
}
}
+void StateManager::on_network_updated() {
+ do_on_network(network_type_, true /*inc_generation*/);
+}
+
void StateManager::on_network(NetType new_network_type) {
do_on_network(new_network_type, true /*inc_generation*/);
}
+
void StateManager::do_on_network(NetType new_network_type, bool inc_generation) {
bool new_network_flag = new_network_type != NetType::None;
if (network_flag_ != new_network_flag) {
@@ -58,12 +43,12 @@ void StateManager::do_on_network(NetType new_network_type, bool inc_generation)
if (inc_generation) {
network_generation_++;
}
- notify_flags(NetworkFlag);
+ notify_flag(Flag::Network);
}
void StateManager::on_online(bool is_online) {
online_flag_ = is_online;
- notify_flags(OnlineFlag);
+ notify_flag(Flag::Online);
}
void StateManager::on_proxy(bool use_proxy) {
@@ -72,9 +57,14 @@ void StateManager::on_proxy(bool use_proxy) {
loop();
}
+void StateManager::on_logging_out(bool is_logging_out) {
+ is_logging_out_ = is_logging_out;
+ notify_flag(Flag::LoggingOut);
+}
+
void StateManager::add_callback(unique_ptr<Callback> callback) {
if (callback->on_network(network_type_, network_generation_) && callback->on_online(online_flag_) &&
- callback->on_state(get_real_state())) {
+ callback->on_state(get_real_state()) && callback->on_logging_out(is_logging_out_)) {
callbacks_.push_back(std::move(callback));
}
}
@@ -89,34 +79,39 @@ void StateManager::close() {
stop();
}
-StateManager::State StateManager::get_real_state() const {
+ConnectionState StateManager::get_real_state() const {
if (!network_flag_) {
- return State::WaitingForNetwork;
+ return ConnectionState::WaitingForNetwork;
}
if (!connect_cnt_) {
if (use_proxy_ && !connect_proxy_cnt_) {
- return State::ConnectingToProxy;
+ return ConnectionState::ConnectingToProxy;
}
- return State::Connecting;
+ return ConnectionState::Connecting;
}
if (!sync_flag_) {
- return State::Updating;
+ return ConnectionState::Updating;
}
- return State::Ready;
+ return ConnectionState::Ready;
}
-void StateManager::notify_flags(int32 flags) {
+void StateManager::notify_flag(Flag flag) {
for (auto it = callbacks_.begin(); it != callbacks_.end();) {
- bool ok = true;
- if (flags & OnlineFlag) {
- ok &= (*it)->on_online(online_flag_);
- }
- if (flags & StateFlag) {
- ok &= (*it)->on_state(flush_state_);
- }
- if (flags & NetworkFlag) {
- ok &= (*it)->on_network(network_type_, network_generation_);
- }
+ bool ok = [&] {
+ switch (flag) {
+ case Flag::Online:
+ return (*it)->on_online(online_flag_);
+ case Flag::State:
+ return (*it)->on_state(flush_state_);
+ case Flag::Network:
+ return (*it)->on_network(network_type_, network_generation_);
+ case Flag::LoggingOut:
+ return (*it)->on_logging_out(is_logging_out_);
+ default:
+ UNREACHABLE();
+ return true;
+ }
+ }();
if (ok) {
++it;
} else {
@@ -133,7 +128,7 @@ void StateManager::on_network_soft() {
}
void StateManager::start_up() {
- create_actor<SleepActor>("SleepActor", 1, PromiseCreator::event(self_closure(this, &StateManager::on_network_soft)))
+ create_actor<SleepActor>("SleepActor", 1, create_event_promise(self_closure(this, &StateManager::on_network_soft)))
.release();
loop();
}
@@ -150,7 +145,7 @@ void StateManager::loop() {
}
if (pending_state_ != flush_state_) {
double delay = 0;
- if (flush_state_ != State::Empty) {
+ if (flush_state_ != ConnectionState::Empty) {
if (static_cast<int32>(pending_state_) > static_cast<int32>(flush_state_)) {
delay = UP_DELAY;
} else {
@@ -165,7 +160,7 @@ void StateManager::loop() {
if (now >= pending_timestamp_ + delay) {
has_timestamp_ = false;
flush_state_ = pending_state_;
- notify_flags(StateFlag);
+ notify_flag(Flag::State);
} else {
set_timeout_at(pending_timestamp_ + delay);
}
@@ -173,4 +168,5 @@ void StateManager::loop() {
has_timestamp_ = false;
}
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StateManager.h b/protocols/Telegram/tdlib/td/td/telegram/StateManager.h
index cbe24c22d8..b32448f80e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/StateManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/StateManager.h
@@ -1,30 +1,32 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
+#include "td/telegram/ConnectionState.h"
#include "td/telegram/net/NetType.h"
+#include "td/mtproto/ConnectionManager.h"
+
+#include "td/actor/actor.h"
+
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
namespace td {
-class StateManager final : public Actor {
- public:
- enum class State : int32 { WaitingForNetwork, ConnectingToProxy, Connecting, Updating, Ready, Empty };
+class StateManager final : public mtproto::ConnectionManager {
+ public:
class Callback {
public:
Callback() = default;
Callback(const Callback &) = delete;
Callback &operator=(const Callback &) = delete;
virtual ~Callback() = default;
- virtual bool on_state(State state) {
+ virtual bool on_state(ConnectionState state) {
return true;
}
virtual bool on_network(NetType network_type, uint32 generation) {
@@ -33,102 +35,68 @@ class StateManager final : public Actor {
virtual bool on_online(bool is_online) {
return true;
}
+ virtual bool on_logging_out(bool is_logging_out) {
+ return true;
+ }
};
+ explicit StateManager(ActorShared<> parent) : parent_(std::move(parent)) {
+ }
+
void on_synchronized(bool is_synchronized);
+ void on_network_updated();
+
void on_network(NetType new_network_type);
void on_online(bool is_online);
void on_proxy(bool use_proxy);
+ void on_logging_out(bool is_logging_out);
+
void add_callback(unique_ptr<Callback> net_callback);
void wait_first_sync(Promise<> promise);
void close();
- class ConnectionToken {
- public:
- ConnectionToken() = default;
- explicit ConnectionToken(ActorShared<StateManager> state_manager) : state_manager_(std::move(state_manager)) {
- }
- ConnectionToken(const ConnectionToken &) = delete;
- ConnectionToken &operator=(const ConnectionToken &) = delete;
- ConnectionToken(ConnectionToken &&) = default;
- ConnectionToken &operator=(ConnectionToken &&other) {
- reset();
- state_manager_ = std::move(other.state_manager_);
- return *this;
- }
- ~ConnectionToken() {
- reset();
- }
-
- void reset() {
- if (!state_manager_.empty()) {
- send_closure(state_manager_, &StateManager::dec_connect);
- state_manager_.reset();
- }
- }
-
- bool empty() const {
- return state_manager_.empty();
- }
-
- private:
- ActorShared<StateManager> state_manager_;
- };
-
- static ConnectionToken connection(ActorId<StateManager> state_manager) {
- return connection_impl(state_manager, 1);
- }
- static ConnectionToken connection_proxy(ActorId<StateManager> state_manager) {
- return connection_impl(state_manager, 2);
- }
-
private:
- uint32 connect_cnt_ = 0;
- uint32 connect_proxy_cnt_ = 0;
+ ActorShared<> parent_;
bool sync_flag_ = true;
bool network_flag_ = true;
NetType network_type_ = NetType::Unknown;
uint32 network_generation_ = 1;
bool online_flag_ = false;
bool use_proxy_ = false;
+ bool is_logging_out_ = false;
static constexpr double UP_DELAY = 0.05;
static constexpr double DOWN_DELAY = 0.3;
- State pending_state_ = State::Empty;
+ ConnectionState pending_state_ = ConnectionState::Empty;
bool has_timestamp_ = false;
double pending_timestamp_ = 0;
- State flush_state_ = State::Empty;
+ ConnectionState flush_state_ = ConnectionState::Empty;
vector<unique_ptr<Callback>> callbacks_;
bool was_sync_ = false;
- std::vector<Promise<>> wait_first_sync_;
+ vector<Promise<>> wait_first_sync_;
void inc_connect();
void dec_connect();
- enum Flags { OnlineFlag = 1, StateFlag = 2, NetworkFlag = 4 };
- void notify_flags(int32 flags);
+ enum class Flag : int32 { Online, State, Network, LoggingOut };
+ void notify_flag(Flag flag);
- void start_up() override;
- void loop() override;
+ void start_up() final;
+ void loop() final;
void on_network_soft();
void do_on_network(NetType new_network_type, bool inc_generation);
- State get_real_state() const;
-
- static ConnectionToken connection_impl(ActorId<StateManager> state_manager, int mode) {
- auto actor = ActorShared<StateManager>(state_manager, mode);
- send_closure(actor, &StateManager::inc_connect);
- return ConnectionToken(std::move(actor));
- }
+ ConnectionState get_real_state() const;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StickerFormat.cpp b/protocols/Telegram/tdlib/td/td/telegram/StickerFormat.cpp
new file mode 100644
index 0000000000..9ec9b60ae0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/StickerFormat.cpp
@@ -0,0 +1,181 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/StickerFormat.h"
+
+#include "td/utils/logging.h"
+
+namespace td {
+
+StickerFormat get_sticker_format(const td_api::object_ptr<td_api::StickerFormat> &format) {
+ CHECK(format != nullptr);
+ switch (format->get_id()) {
+ case td_api::stickerFormatWebp::ID:
+ return StickerFormat::Webp;
+ case td_api::stickerFormatTgs::ID:
+ return StickerFormat::Tgs;
+ case td_api::stickerFormatWebm::ID:
+ return StickerFormat::Webm;
+ default:
+ UNREACHABLE();
+ return StickerFormat::Unknown;
+ }
+}
+
+StickerFormat get_sticker_format_by_mime_type(Slice mime_type) {
+ if (mime_type == "application/x-tgsticker") {
+ return StickerFormat::Tgs;
+ }
+ if (mime_type == "image/webp") {
+ return StickerFormat::Webp;
+ }
+ if (mime_type == "video/webm") {
+ return StickerFormat::Webm;
+ }
+ return StickerFormat::Unknown;
+}
+
+StickerFormat get_sticker_format_by_extension(Slice extension) {
+ if (extension == "tgs") {
+ return StickerFormat::Tgs;
+ }
+ if (extension == "webp") {
+ return StickerFormat::Webp;
+ }
+ if (extension == "webm") {
+ return StickerFormat::Webm;
+ }
+ return StickerFormat::Unknown;
+}
+
+td_api::object_ptr<td_api::StickerFormat> get_sticker_format_object(StickerFormat sticker_format) {
+ switch (sticker_format) {
+ case StickerFormat::Unknown:
+ LOG(ERROR) << "Have a sticker of unknown format";
+ return td_api::make_object<td_api::stickerFormatWebp>();
+ case StickerFormat::Webp:
+ return td_api::make_object<td_api::stickerFormatWebp>();
+ case StickerFormat::Tgs:
+ return td_api::make_object<td_api::stickerFormatTgs>();
+ case StickerFormat::Webm:
+ return td_api::make_object<td_api::stickerFormatWebm>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+string get_sticker_format_mime_type(StickerFormat sticker_format) {
+ switch (sticker_format) {
+ case StickerFormat::Unknown:
+ case StickerFormat::Webp:
+ return "image/webp";
+ case StickerFormat::Tgs:
+ return "application/x-tgsticker";
+ case StickerFormat::Webm:
+ return "video/webm";
+ default:
+ UNREACHABLE();
+ return string();
+ }
+}
+
+Slice get_sticker_format_extension(StickerFormat sticker_format) {
+ switch (sticker_format) {
+ case StickerFormat::Unknown:
+ return Slice();
+ case StickerFormat::Webp:
+ return Slice(".webp");
+ case StickerFormat::Tgs:
+ return Slice(".tgs");
+ case StickerFormat::Webm:
+ return Slice(".webm");
+ default:
+ UNREACHABLE();
+ return Slice();
+ }
+}
+
+PhotoFormat get_sticker_format_photo_format(StickerFormat sticker_format) {
+ switch (sticker_format) {
+ case StickerFormat::Unknown:
+ case StickerFormat::Webp:
+ return PhotoFormat::Webp;
+ case StickerFormat::Tgs:
+ return PhotoFormat::Tgs;
+ case StickerFormat::Webm:
+ return PhotoFormat::Webm;
+ default:
+ UNREACHABLE();
+ return PhotoFormat::Webp;
+ }
+}
+
+bool is_sticker_format_animated(StickerFormat sticker_format) {
+ switch (sticker_format) {
+ case StickerFormat::Unknown:
+ return false;
+ case StickerFormat::Webp:
+ return false;
+ case StickerFormat::Tgs:
+ return true;
+ case StickerFormat::Webm:
+ return true;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+bool is_sticker_format_vector(StickerFormat sticker_format) {
+ switch (sticker_format) {
+ case StickerFormat::Unknown:
+ return false;
+ case StickerFormat::Webp:
+ return false;
+ case StickerFormat::Tgs:
+ return true;
+ case StickerFormat::Webm:
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+int64 get_max_sticker_file_size(StickerFormat sticker_format, StickerType sticker_type, bool for_thumbnail) {
+ bool is_custom_emoji = sticker_type == StickerType::CustomEmoji;
+ switch (sticker_format) {
+ case StickerFormat::Unknown:
+ case StickerFormat::Webp:
+ return for_thumbnail ? (1 << 17) : (is_custom_emoji ? (1 << 17) : (1 << 19));
+ case StickerFormat::Tgs:
+ return for_thumbnail ? (1 << 15) : (1 << 16);
+ case StickerFormat::Webm:
+ return for_thumbnail ? (1 << 15) : (is_custom_emoji ? (1 << 16) : (1 << 18));
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, StickerFormat sticker_format) {
+ switch (sticker_format) {
+ case StickerFormat::Unknown:
+ return string_builder << "unknown";
+ case StickerFormat::Webp:
+ return string_builder << "WEBP";
+ case StickerFormat::Tgs:
+ return string_builder << "TGS";
+ case StickerFormat::Webm:
+ return string_builder << "WEBM";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StickerFormat.h b/protocols/Telegram/tdlib/td/td/telegram/StickerFormat.h
new file mode 100644
index 0000000000..27dfa929d1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/StickerFormat.h
@@ -0,0 +1,44 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/StickerType.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+// update store_sticker/store_sticker_set when this type changes
+enum class StickerFormat : int32 { Unknown, Webp, Tgs, Webm };
+
+StickerFormat get_sticker_format(const td_api::object_ptr<td_api::StickerFormat> &format);
+
+StickerFormat get_sticker_format_by_mime_type(Slice mime_type);
+
+StickerFormat get_sticker_format_by_extension(Slice extension);
+
+td_api::object_ptr<td_api::StickerFormat> get_sticker_format_object(StickerFormat sticker_format);
+
+string get_sticker_format_mime_type(StickerFormat sticker_format);
+
+Slice get_sticker_format_extension(StickerFormat sticker_format);
+
+PhotoFormat get_sticker_format_photo_format(StickerFormat sticker_format);
+
+bool is_sticker_format_animated(StickerFormat sticker_format);
+
+bool is_sticker_format_vector(StickerFormat sticker_format);
+
+int64 get_max_sticker_file_size(StickerFormat sticker_format, StickerType sticker_type, bool for_thumbnail);
+
+StringBuilder &operator<<(StringBuilder &string_builder, StickerFormat sticker_format);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StickerSetId.h b/protocols/Telegram/tdlib/td/td/telegram/StickerSetId.h
new file mode 100644
index 0000000000..d72eb115ef
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/StickerSetId.h
@@ -0,0 +1,55 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class StickerSetId {
+ int64 id = 0;
+
+ public:
+ StickerSetId() = default;
+
+ explicit constexpr StickerSetId(int64 sticker_set_id) : id(sticker_set_id) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
+ StickerSetId(T sticker_set_id) = delete;
+
+ bool is_valid() const {
+ return id != 0;
+ }
+
+ int64 get() const {
+ return id;
+ }
+
+ bool operator==(const StickerSetId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const StickerSetId &other) const {
+ return id != other.id;
+ }
+};
+
+struct StickerSetIdHash {
+ uint32 operator()(StickerSetId sticker_set_id) const {
+ return Hash<int64>()(sticker_set_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, StickerSetId sticker_set_id) {
+ return string_builder << "sticker set " << sticker_set_id.get();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StickerSetId.hpp b/protocols/Telegram/tdlib/td/td/telegram/StickerSetId.hpp
new file mode 100644
index 0000000000..088f4bad3c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/StickerSetId.hpp
@@ -0,0 +1,27 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/StickerSetId.h"
+
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/StickersManager.hpp"
+#include "td/telegram/Td.h"
+
+namespace td {
+
+template <class StorerT>
+void store(const StickerSetId &sticker_set_id, StorerT &storer) {
+ storer.context()->td().get_actor_unsafe()->stickers_manager_->store_sticker_set_id(sticker_set_id, storer);
+}
+
+template <class ParserT>
+void parse(StickerSetId &sticker_set_id, ParserT &parser) {
+ parser.context()->td().get_actor_unsafe()->stickers_manager_->parse_sticker_set_id(sticker_set_id, parser);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StickerType.cpp b/protocols/Telegram/tdlib/td/td/telegram/StickerType.cpp
new file mode 100644
index 0000000000..4b17753ba6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/StickerType.cpp
@@ -0,0 +1,66 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/StickerType.h"
+
+namespace td {
+
+StickerType get_sticker_type(bool is_mask, bool is_custom_emoji) {
+ if (is_custom_emoji) {
+ return StickerType::CustomEmoji;
+ }
+ if (is_mask) {
+ return StickerType::Mask;
+ }
+ return StickerType::Regular;
+}
+
+StickerType get_sticker_type(const td_api::object_ptr<td_api::StickerType> &type) {
+ if (type == nullptr) {
+ return StickerType::Regular;
+ }
+ switch (type->get_id()) {
+ case td_api::stickerTypeRegular::ID:
+ return StickerType::Regular;
+ case td_api::stickerTypeMask::ID:
+ return StickerType::Mask;
+ case td_api::stickerTypeCustomEmoji::ID:
+ return StickerType::CustomEmoji;
+ default:
+ UNREACHABLE();
+ return StickerType::Regular;
+ }
+}
+
+td_api::object_ptr<td_api::StickerType> get_sticker_type_object(StickerType sticker_type) {
+ switch (sticker_type) {
+ case StickerType::Regular:
+ return td_api::make_object<td_api::stickerTypeRegular>();
+ case StickerType::Mask:
+ return td_api::make_object<td_api::stickerTypeMask>();
+ case StickerType::CustomEmoji:
+ return td_api::make_object<td_api::stickerTypeCustomEmoji>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, StickerType sticker_type) {
+ switch (sticker_type) {
+ case StickerType::Regular:
+ return string_builder << "Regular";
+ case StickerType::Mask:
+ return string_builder << "Mask";
+ case StickerType::CustomEmoji:
+ return string_builder << "CustomEmoji";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StickerType.h b/protocols/Telegram/tdlib/td/td/telegram/StickerType.h
new file mode 100644
index 0000000000..d4e2f18d15
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/StickerType.h
@@ -0,0 +1,29 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+// update store_sticker/store_sticker_set when this type changes
+enum class StickerType : int32 { Regular, Mask, CustomEmoji };
+
+static constexpr int32 MAX_STICKER_TYPE = 3;
+
+StickerType get_sticker_type(bool is_mask, bool is_custom_emoji);
+
+StickerType get_sticker_type(const td_api::object_ptr<td_api::StickerType> &type);
+
+td_api::object_ptr<td_api::StickerType> get_sticker_type_object(StickerType sticker_type);
+
+StringBuilder &operator<<(StringBuilder &string_builder, StickerType sticker_type);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StickersManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/StickersManager.cpp
index 8606d0d032..dc6a1e8f7a 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/StickersManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/StickersManager.cpp
@@ -1,205 +1,518 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/StickersManager.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/MultiPromise.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/AccessRights.h"
#include "td/telegram/AuthManager.h"
+#include "td/telegram/ConfigManager.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/DialogId.h"
+#include "td/telegram/Document.h"
#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
+#include "td/telegram/LanguagePackManager.h"
#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/logevent/LogEventHelper.h"
+#include "td/telegram/MessageReaction.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/misc.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/net/MtprotoHeader.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/PhotoSizeSource.h"
+#include "td/telegram/secret_api.h"
+#include "td/telegram/SecretChatLayer.h"
+#include "td/telegram/StickerSetId.hpp"
#include "td/telegram/StickersManager.hpp"
#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/Version.h"
+
+#include "td/db/SqliteKeyValue.h"
+#include "td/db/SqliteKeyValueAsync.h"
+
+#include "td/actor/SleepActor.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/base64.h"
+#include "td/utils/emoji.h"
#include "td/utils/format.h"
+#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
+#include "td/utils/MimeType.h"
#include "td/utils/misc.h"
+#include "td/utils/PathView.h"
#include "td/utils/Random.h"
+#include "td/utils/ScopeGuard.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StackAllocator.h"
+#include "td/utils/StringBuilder.h"
#include "td/utils/Time.h"
#include "td/utils/tl_helpers.h"
+#include "td/utils/utf8.h"
#include <algorithm>
+#include <cmath>
+#include <limits>
#include <type_traits>
-#include <unordered_set>
namespace td {
-class GetAllStickersQuery : public Td::ResultHandler {
- bool is_masks_;
+class GetAvailableReactionsQuery final : public Td::ResultHandler {
+ public:
+ void send(int32 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getAvailableReactions(hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getAvailableReactions>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetAvailableReactionsQuery: " << to_string(ptr);
+ td_->stickers_manager_->on_get_available_reactions(std::move(ptr));
+ }
+
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for GetAvailableReactionsQuery: " << status;
+ td_->stickers_manager_->on_get_available_reactions(nullptr);
+ }
+};
+
+class GetRecentReactionsQuery final : public Td::ResultHandler {
public:
- void send(bool is_masks, int32 hash) {
- is_masks_ = is_masks;
- if (is_masks) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getMaskStickers(hash))));
- } else {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getAllStickers(hash))));
+ void send(int32 limit, int64 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getRecentReactions(limit, hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getRecentReactions>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
}
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetRecentReactionsQuery: " << to_string(ptr);
+ td_->stickers_manager_->on_get_recent_reactions(std::move(ptr));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for GetRecentReactionsQuery: " << status;
+ td_->stickers_manager_->on_get_recent_reactions(nullptr);
+ }
+};
+
+class GetTopReactionsQuery final : public Td::ResultHandler {
+ public:
+ void send(int64 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getTopReactions(50, hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getTopReactions>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for GetTopReactionsQuery: " << to_string(ptr);
+ td_->stickers_manager_->on_get_top_reactions(std::move(ptr));
+ }
+
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for GetTopReactionsQuery: " << status;
+ td_->stickers_manager_->on_get_top_reactions(nullptr);
+ }
+};
+
+class ClearRecentReactionsQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ClearRecentReactionsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ send_query(G()->net_query_creator().create(telegram_api::messages_clearRecentReactions()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_clearRecentReactions>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ td_->stickers_manager_->reload_recent_reactions();
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for clear recent reactions: " << status;
+ }
+ td_->stickers_manager_->reload_recent_reactions();
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetAllStickersQuery final : public Td::ResultHandler {
+ StickerType sticker_type_;
+
+ public:
+ void send(StickerType sticker_type, int64 hash) {
+ sticker_type_ = sticker_type;
+ switch (sticker_type) {
+ case StickerType::Regular:
+ return send_query(G()->net_query_creator().create(telegram_api::messages_getAllStickers(hash)));
+ case StickerType::Mask:
+ return send_query(G()->net_query_creator().create(telegram_api::messages_getMaskStickers(hash)));
+ case StickerType::CustomEmoji:
+ return send_query(G()->net_query_creator().create(telegram_api::messages_getEmojiStickers(hash)));
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ void on_result(BufferSlice packet) final {
static_assert(std::is_same<telegram_api::messages_getMaskStickers::ReturnType,
telegram_api::messages_getAllStickers::ReturnType>::value,
"");
auto result_ptr = fetch_result<telegram_api::messages_getAllStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(DEBUG) << "Receive result for get all " << (is_masks_ ? "masks" : "stickers") << ": " << to_string(ptr);
- td->stickers_manager_->on_get_installed_sticker_sets(is_masks_, std::move(ptr));
+ LOG(DEBUG) << "Receive result for get all " << sticker_type_ << " stickers: " << to_string(ptr);
+ td_->stickers_manager_->on_get_installed_sticker_sets(sticker_type_, std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for get all stickers: " << status;
- td->stickers_manager_->on_get_installed_sticker_sets_failed(is_masks_, std::move(status));
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for get all stickers: " << status;
+ }
+ td_->stickers_manager_->on_get_installed_sticker_sets_failed(sticker_type_, std::move(status));
}
};
-class SearchStickersQuery : public Td::ResultHandler {
+class SearchStickersQuery final : public Td::ResultHandler {
string emoji_;
public:
- void send(string emoji) {
+ void send(string emoji, int64 hash) {
emoji_ = std::move(emoji);
- int32 flags = 0;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_getStickers(flags, false /*ignored*/, emoji_, ""))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_getStickers(emoji_, hash)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for search stickers: " << to_string(ptr);
- td->stickers_manager_->on_find_stickers_success(emoji_, std::move(ptr));
+ td_->stickers_manager_->on_find_stickers_success(emoji_, std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for search stickers: " << status;
- td->stickers_manager_->on_find_stickers_fail(emoji_, std::move(status));
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for search stickers: " << status;
+ }
+ td_->stickers_manager_->on_find_stickers_fail(emoji_, std::move(status));
}
};
-class GetArchivedStickerSetsQuery : public Td::ResultHandler {
+class GetEmojiKeywordsLanguageQuery final : public Td::ResultHandler {
+ Promise<vector<string>> promise_;
+
+ public:
+ explicit GetEmojiKeywordsLanguageQuery(Promise<vector<string>> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(vector<string> &&language_codes) {
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getEmojiKeywordsLanguages(std::move(language_codes))));
+ }
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getEmojiKeywordsLanguages>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result =
+ transform(result_ptr.move_as_ok(), [](auto &&emoji_language) { return std::move(emoji_language->lang_code_); });
+ promise_.set_value(std::move(result));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetEmojiKeywordsQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> promise_;
+
+ public:
+ explicit GetEmojiKeywordsQuery(Promise<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(const string &language_code) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getEmojiKeywords(language_code)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getEmojiKeywords>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetEmojiKeywordsDifferenceQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> promise_;
+
+ public:
+ explicit GetEmojiKeywordsDifferenceQuery(
+ Promise<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(const string &language_code, int32 version) {
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getEmojiKeywordsDifference(language_code, version)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getEmojiKeywordsDifference>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetEmojiUrlQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::emojiURL>> promise_;
+
+ public:
+ explicit GetEmojiUrlQuery(Promise<telegram_api::object_ptr<telegram_api::emojiURL>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(const string &language_code) {
+ send_query(G()->net_query_creator().create(telegram_api::messages_getEmojiURL(language_code)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getEmojiURL>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class GetArchivedStickerSetsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- bool is_masks_;
+ StickerSetId offset_sticker_set_id_;
+ StickerType sticker_type_;
public:
explicit GetArchivedStickerSetsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(bool is_masks, int64 offset_sticker_set_id, int32 limit) {
- is_masks_ = is_masks;
- LOG(INFO) << "Get archived " << (is_masks ? "mask" : "sticker") << " sets from " << offset_sticker_set_id
- << " with limit " << limit;
+ void send(StickerType sticker_type, StickerSetId offset_sticker_set_id, int32 limit) {
+ offset_sticker_set_id_ = offset_sticker_set_id;
+ sticker_type_ = sticker_type;
int32 flags = 0;
- if (is_masks) {
+ if (sticker_type_ == StickerType::Mask) {
flags |= telegram_api::messages_getArchivedStickers::MASKS_MASK;
}
- is_masks_ = is_masks;
-
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::messages_getArchivedStickers(flags, is_masks /*ignored*/, offset_sticker_set_id, limit))));
+ if (sticker_type_ == StickerType::CustomEmoji) {
+ flags |= telegram_api::messages_getArchivedStickers::EMOJIS_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_getArchivedStickers(
+ flags, false /*ignored*/, false /*ignored*/, offset_sticker_set_id.get(), limit)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getArchivedStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetArchivedStickerSetsQuery " << to_string(ptr);
- td->stickers_manager_->on_get_archived_sticker_sets(is_masks_, std::move(ptr->sets_), ptr->count_);
+ LOG(INFO) << "Receive result for GetArchivedStickerSetsQuery: " << to_string(ptr);
+ td_->stickers_manager_->on_get_archived_sticker_sets(sticker_type_, offset_sticker_set_id_, std::move(ptr->sets_),
+ ptr->count_);
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class GetFeaturedStickerSetsQuery : public Td::ResultHandler {
+class GetFeaturedStickerSetsQuery final : public Td::ResultHandler {
+ StickerType sticker_type_;
+
public:
- void send(int32 hash) {
- LOG(INFO) << "Get featured sticker sets with hash " << hash;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getFeaturedStickers(hash))));
+ void send(StickerType sticker_type, int64 hash) {
+ sticker_type_ = sticker_type;
+ switch (sticker_type) {
+ case StickerType::Regular:
+ send_query(G()->net_query_creator().create(telegram_api::messages_getFeaturedStickers(hash)));
+ break;
+ case StickerType::CustomEmoji:
+ send_query(G()->net_query_creator().create(telegram_api::messages_getFeaturedEmojiStickers(hash)));
+ break;
+ default:
+ UNREACHABLE();
+ }
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
+ static_assert(std::is_same<telegram_api::messages_getFeaturedStickers::ReturnType,
+ telegram_api::messages_getFeaturedEmojiStickers::ReturnType>::value,
+ "");
auto result_ptr = fetch_result<telegram_api::messages_getFeaturedStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(DEBUG) << "Receive result for GetFeaturedStickerSetsQuery " << to_string(ptr);
- td->stickers_manager_->on_get_featured_sticker_sets(std::move(ptr));
+ LOG(DEBUG) << "Receive result for GetFeaturedStickerSetsQuery: " << to_string(ptr);
+ td_->stickers_manager_->on_get_featured_sticker_sets(sticker_type_, -1, -1, 0, std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- td->stickers_manager_->on_get_featured_sticker_sets_failed(std::move(status));
+ void on_error(Status status) final {
+ td_->stickers_manager_->on_get_featured_sticker_sets_failed(sticker_type_, -1, -1, 0, std::move(status));
}
};
-class GetAttachedStickerSetsQuery : public Td::ResultHandler {
+class GetOldFeaturedStickerSetsQuery final : public Td::ResultHandler {
+ int32 offset_;
+ int32 limit_;
+ uint32 generation_;
+
+ public:
+ void send(StickerType sticker_type, int32 offset, int32 limit, uint32 generation) {
+ CHECK(sticker_type == StickerType::Regular);
+ offset_ = offset;
+ limit_ = limit;
+ generation_ = generation;
+ send_query(G()->net_query_creator().create(telegram_api::messages_getOldFeaturedStickers(offset, limit, 0)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getOldFeaturedStickers>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ LOG(DEBUG) << "Receive result for GetOldFeaturedStickerSetsQuery: " << to_string(ptr);
+ td_->stickers_manager_->on_get_featured_sticker_sets(StickerType::Regular, offset_, limit_, generation_,
+ std::move(ptr));
+ }
+
+ void on_error(Status status) final {
+ td_->stickers_manager_->on_get_featured_sticker_sets_failed(StickerType::Regular, offset_, limit_, generation_,
+ std::move(status));
+ }
+};
+
+class GetAttachedStickerSetsQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
FileId file_id_;
+ string file_reference_;
public:
explicit GetAttachedStickerSetsQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(FileId file_id, tl_object_ptr<telegram_api::InputStickeredMedia> &&input_stickered_media) {
+ void send(FileId file_id, string &&file_reference,
+ tl_object_ptr<telegram_api::InputStickeredMedia> &&input_stickered_media) {
file_id_ = file_id;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_getAttachedStickers(std::move(input_stickered_media)))));
+ file_reference_ = std::move(file_reference);
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getAttachedStickers(std::move(input_stickered_media))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getAttachedStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->stickers_manager_->on_get_attached_sticker_sets(file_id_, result_ptr.move_as_ok());
+ td_->stickers_manager_->on_get_attached_sticker_sets(file_id_, result_ptr.move_as_ok());
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ VLOG(file_references) << "Receive " << status << " for " << file_id_;
+ td_->file_manager_->delete_file_reference(file_id_, file_reference_);
+ td_->file_reference_manager_->repair_file_reference(
+ file_id_,
+ PromiseCreator::lambda([file_id = file_id_, promise = std::move(promise_)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(Status::Error(400, "Failed to find the file"));
+ }
+
+ send_closure(G()->stickers_manager(), &StickersManager::send_get_attached_stickers_query, file_id,
+ std::move(promise));
+ }));
+ return;
+ }
+
promise_.set_error(std::move(status));
}
};
-class GetRecentStickersQuery : public Td::ResultHandler {
- bool is_attached_;
+class GetRecentStickersQuery final : public Td::ResultHandler {
+ bool is_repair_ = false;
+ bool is_attached_ = false;
public:
- void send(bool is_attached, int32 hash) {
+ void send(bool is_repair, bool is_attached, int64 hash) {
+ is_repair_ = is_repair;
is_attached_ = is_attached;
int32 flags = 0;
if (is_attached) {
@@ -207,36 +520,47 @@ class GetRecentStickersQuery : public Td::ResultHandler {
}
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_getRecentStickers(flags, is_attached /*ignored*/, hash))));
+ telegram_api::messages_getRecentStickers(flags, is_attached /*ignored*/, hash)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getRecentStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(DEBUG) << "Receive result for get recent " << (is_attached_ ? "attached " : "")
<< "stickers: " << to_string(ptr);
- td->stickers_manager_->on_get_recent_stickers(is_attached_, std::move(ptr));
+ td_->stickers_manager_->on_get_recent_stickers(is_repair_, is_attached_, std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for get recent stickers: " << status;
- td->stickers_manager_->on_get_recent_stickers_failed(is_attached_, std::move(status));
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for get recent " << (is_attached_ ? "attached " : "") << "stickers: " << status;
+ }
+ td_->stickers_manager_->on_get_recent_stickers_failed(is_repair_, is_attached_, std::move(status));
}
};
-class SaveRecentStickerQuery : public Td::ResultHandler {
+class SaveRecentStickerQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
+ FileId file_id_;
+ string file_reference_;
+ bool unsave_ = false;
bool is_attached_;
public:
explicit SaveRecentStickerQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(bool is_attached, tl_object_ptr<telegram_api::InputDocument> &&input_document, bool unsave) {
+ void send(bool is_attached, FileId file_id, tl_object_ptr<telegram_api::inputDocument> &&input_document,
+ bool unsave) {
+ CHECK(input_document != nullptr);
+ CHECK(file_id.is_valid());
+ file_id_ = file_id;
+ file_reference_ = input_document->file_reference_.as_slice().str();
+ unsave_ = unsave;
is_attached_ = is_attached;
int32 flags = 0;
@@ -244,33 +568,51 @@ class SaveRecentStickerQuery : public Td::ResultHandler {
flags |= telegram_api::messages_saveRecentSticker::ATTACHED_MASK;
}
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::messages_saveRecentSticker(flags, is_attached /*ignored*/, std::move(input_document), unsave))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_saveRecentSticker(flags, is_attached /*ignored*/, std::move(input_document), unsave)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_saveRecentSticker>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for save recent sticker: " << result;
+ LOG(INFO) << "Receive result for save recent " << (is_attached_ ? "attached " : "") << "sticker: " << result;
if (!result) {
- td->stickers_manager_->reload_recent_stickers(is_attached_, true);
+ td_->stickers_manager_->reload_recent_stickers(is_attached_, true);
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for save recent sticker: " << status;
- td->stickers_manager_->reload_recent_stickers(is_attached_, true);
+ void on_error(Status status) final {
+ if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ VLOG(file_references) << "Receive " << status << " for " << file_id_;
+ td_->file_manager_->delete_file_reference(file_id_, file_reference_);
+ td_->file_reference_manager_->repair_file_reference(
+ file_id_, PromiseCreator::lambda([sticker_id = file_id_, is_attached = is_attached_, unsave = unsave_,
+ promise = std::move(promise_)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(Status::Error(400, "Failed to find the sticker"));
+ }
+
+ send_closure(G()->stickers_manager(), &StickersManager::send_save_recent_sticker_query, is_attached,
+ sticker_id, unsave, std::move(promise));
+ }));
+ return;
+ }
+
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for save recent " << (is_attached_ ? "attached " : "") << "sticker: " << status;
+ }
+ td_->stickers_manager_->reload_recent_stickers(is_attached_, true);
promise_.set_error(std::move(status));
}
};
-class ClearRecentStickersQuery : public Td::ResultHandler {
+class ClearRecentStickersQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
bool is_attached_;
@@ -286,279 +628,382 @@ class ClearRecentStickersQuery : public Td::ResultHandler {
flags |= telegram_api::messages_clearRecentStickers::ATTACHED_MASK;
}
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_clearRecentStickers(flags, is_attached /*ignored*/))));
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_clearRecentStickers(flags, is_attached /*ignored*/)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_clearRecentStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for clear recent stickers: " << result;
+ LOG(INFO) << "Receive result for clear recent " << (is_attached_ ? "attached " : "") << "stickers: " << result;
if (!result) {
- td->stickers_manager_->reload_recent_stickers(is_attached_, true);
+ td_->stickers_manager_->reload_recent_stickers(is_attached_, true);
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for clear recent stickers: " << status;
- td->stickers_manager_->reload_recent_stickers(is_attached_, true);
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for clear recent " << (is_attached_ ? "attached " : "") << "stickers: " << status;
+ }
+ td_->stickers_manager_->reload_recent_stickers(is_attached_, true);
promise_.set_error(std::move(status));
}
};
-class GetFavedStickersQuery : public Td::ResultHandler {
+class GetFavedStickersQuery final : public Td::ResultHandler {
+ bool is_repair_ = false;
+
public:
- void send(int32 hash) {
- LOG(INFO) << "Send get favorite stickers request with hash = " << hash;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getFavedStickers(hash))));
+ void send(bool is_repair, int64 hash) {
+ is_repair_ = is_repair;
+ send_query(G()->net_query_creator().create(telegram_api::messages_getFavedStickers(hash)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getFavedStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- td->stickers_manager_->on_get_favorite_stickers(std::move(ptr));
+ td_->stickers_manager_->on_get_favorite_stickers(is_repair_, std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for get favorite stickers: " << status;
- td->stickers_manager_->on_get_favorite_stickers_failed(std::move(status));
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for get favorite stickers: " << status;
+ }
+ td_->stickers_manager_->on_get_favorite_stickers_failed(is_repair_, std::move(status));
}
};
-class FaveStickerQuery : public Td::ResultHandler {
+class FaveStickerQuery final : public Td::ResultHandler {
+ FileId file_id_;
+ string file_reference_;
+ bool unsave_ = false;
+
Promise<Unit> promise_;
public:
explicit FaveStickerQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(tl_object_ptr<telegram_api::InputDocument> &&input_document, bool unsave) {
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_faveSticker(std::move(input_document), unsave))));
+ void send(FileId file_id, tl_object_ptr<telegram_api::inputDocument> &&input_document, bool unsave) {
+ CHECK(input_document != nullptr);
+ CHECK(file_id.is_valid());
+ file_id_ = file_id;
+ file_reference_ = input_document->file_reference_.as_slice().str();
+ unsave_ = unsave;
+
+ send_query(G()->net_query_creator().create(telegram_api::messages_faveSticker(std::move(input_document), unsave)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_faveSticker>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for fave sticker: " << result;
if (!result) {
- td->stickers_manager_->reload_favorite_stickers(true);
+ td_->stickers_manager_->reload_favorite_stickers(true);
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for fave sticker: " << status;
- td->stickers_manager_->reload_favorite_stickers(true);
+ void on_error(Status status) final {
+ if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
+ VLOG(file_references) << "Receive " << status << " for " << file_id_;
+ td_->file_manager_->delete_file_reference(file_id_, file_reference_);
+ td_->file_reference_manager_->repair_file_reference(
+ file_id_, PromiseCreator::lambda([sticker_id = file_id_, unsave = unsave_,
+ promise = std::move(promise_)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(Status::Error(400, "Failed to find the sticker"));
+ }
+
+ send_closure(G()->stickers_manager(), &StickersManager::send_fave_sticker_query, sticker_id, unsave,
+ std::move(promise));
+ }));
+ return;
+ }
+
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for fave sticker: " << status;
+ }
+ td_->stickers_manager_->reload_favorite_stickers(true);
promise_.set_error(std::move(status));
}
};
-class ReorderStickerSetsQuery : public Td::ResultHandler {
- bool is_masks_;
+class ReorderStickerSetsQuery final : public Td::ResultHandler {
+ StickerType sticker_type_;
public:
- void send(bool is_masks, vector<int64> sticker_set_ids) {
- is_masks_ = is_masks;
+ void send(StickerType sticker_type, const vector<StickerSetId> &sticker_set_ids) {
+ sticker_type_ = sticker_type;
int32 flags = 0;
- if (is_masks) {
+ if (sticker_type == StickerType::Mask) {
flags |= telegram_api::messages_reorderStickerSets::MASKS_MASK;
}
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::messages_reorderStickerSets(flags, is_masks /*ignored*/, std::move(sticker_set_ids)))));
+ if (sticker_type == StickerType::CustomEmoji) {
+ flags |= telegram_api::messages_reorderStickerSets::EMOJIS_MASK;
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_reorderStickerSets(
+ flags, false /*ignored*/, false /*ignored*/, StickersManager::convert_sticker_set_ids(sticker_set_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_reorderStickerSets>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
if (!result) {
- return on_error(id, Status::Error(400, "Result is false"));
+ return on_error(Status::Error(400, "Result is false"));
}
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for ReorderStickerSetsQuery: " << status;
- td->stickers_manager_->reload_installed_sticker_sets(is_masks_, true);
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for ReorderStickerSetsQuery: " << status;
+ }
+ td_->stickers_manager_->reload_installed_sticker_sets(sticker_type_, true);
}
};
-class GetStickerSetQuery : public Td::ResultHandler {
+class GetStickerSetQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- int64 sticker_set_id_;
+ StickerSetId sticker_set_id_;
+ string sticker_set_name_;
public:
explicit GetStickerSetQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(int64 sticker_set_id, tl_object_ptr<telegram_api::InputStickerSet> &&input_sticker_set) {
+ void send(StickerSetId sticker_set_id, tl_object_ptr<telegram_api::InputStickerSet> &&input_sticker_set, int32 hash) {
sticker_set_id_ = sticker_set_id;
- LOG(INFO) << "Load sticker set " << sticker_set_id << " from server: " << to_string(input_sticker_set);
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_getStickerSet(std::move(input_sticker_set)))));
+ if (input_sticker_set->get_id() == telegram_api::inputStickerSetShortName::ID) {
+ sticker_set_name_ =
+ static_cast<const telegram_api::inputStickerSetShortName *>(input_sticker_set.get())->short_name_;
+ }
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getStickerSet(std::move(input_sticker_set), hash)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getStickerSet>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto set_ptr = result_ptr.move_as_ok();
+ if (set_ptr->get_id() == telegram_api::messages_stickerSet::ID) {
+ auto set = static_cast<telegram_api::messages_stickerSet *>(set_ptr.get());
+ constexpr int64 GREAT_MINDS_COLOR_SET_ID = 151353307481243663;
+ if (set->set_->id_ == GREAT_MINDS_COLOR_SET_ID) {
+ string great_minds_name = "TelegramGreatMinds";
+ if (sticker_set_id_.get() == StickersManager::GREAT_MINDS_SET_ID ||
+ trim(to_lower(sticker_set_name_)) == to_lower(great_minds_name)) {
+ set->set_->id_ = StickersManager::GREAT_MINDS_SET_ID;
+ set->set_->short_name_ = std::move(great_minds_name);
+ }
+ }
}
- auto ptr = result_ptr.move_as_ok();
- // LOG(DEBUG) << "Receive result for get sticker set " << to_string(ptr);
- td->stickers_manager_->on_get_messages_sticker_set(sticker_set_id_, std::move(ptr), true);
+ td_->stickers_manager_->on_get_messages_sticker_set(sticker_set_id_, std::move(set_ptr), true,
+ "GetStickerSetQuery");
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
- LOG(INFO) << "Receive error for getStickerSet: " << status;
- td->stickers_manager_->on_load_sticker_set_fail(sticker_set_id_, status);
+ void on_error(Status status) final {
+ LOG(INFO) << "Receive error for GetStickerSetQuery: " << status;
+ td_->stickers_manager_->on_load_sticker_set_fail(sticker_set_id_, status);
promise_.set_error(std::move(status));
}
};
-class SearchStickerSetsQuery : public Td::ResultHandler {
+class ReloadSpecialStickerSetQuery final : public Td::ResultHandler {
+ StickerSetId sticker_set_id_;
+ SpecialStickerSetType type_;
+
+ public:
+ void send(StickerSetId sticker_set_id, SpecialStickerSetType type, int32 hash) {
+ sticker_set_id_ = sticker_set_id;
+ type_ = std::move(type);
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getStickerSet(type_.get_input_sticker_set(), hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getStickerSet>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto set_ptr = result_ptr.move_as_ok();
+ if (set_ptr->get_id() == telegram_api::messages_stickerSet::ID) {
+ // sticker_set_id_ must be replaced always, because it could have been changed
+ // we must not pass sticker_set_id_ in order to allow its change
+ sticker_set_id_ = td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), std::move(set_ptr), true,
+ "ReloadSpecialStickerSetQuery");
+ } else {
+ CHECK(set_ptr->get_id() == telegram_api::messages_stickerSetNotModified::ID);
+ // we received telegram_api::messages_stickerSetNotModified, and must pass sticker_set_id_ to handle it
+ // sticker_set_id_ can't be changed by this call
+ td_->stickers_manager_->on_get_messages_sticker_set(sticker_set_id_, std::move(set_ptr), false,
+ "ReloadSpecialStickerSetQuery");
+ }
+ if (sticker_set_id_.is_valid()) {
+ td_->stickers_manager_->on_get_special_sticker_set(type_, sticker_set_id_);
+ } else {
+ on_error(Status::Error(500, "Failed to add special sticker set"));
+ }
+ }
+
+ void on_error(Status status) final {
+ LOG(WARNING) << "Receive error for ReloadSpecialStickerSetQuery: " << status;
+ td_->stickers_manager_->on_load_special_sticker_set(type_, std::move(status));
+ }
+};
+
+class SearchStickerSetsQuery final : public Td::ResultHandler {
string query_;
public:
void send(string query) {
query_ = std::move(query);
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_searchStickerSets(0, false /*ignored*/, query_, 0))));
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_searchStickerSets(0, false /*ignored*/, query_, 0)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_searchStickerSets>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
LOG(INFO) << "Receive result for search sticker sets: " << to_string(ptr);
- td->stickers_manager_->on_find_sticker_sets_success(query_, std::move(ptr));
+ td_->stickers_manager_->on_find_sticker_sets_success(query_, std::move(ptr));
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for search sticker sets: " << status;
- td->stickers_manager_->on_find_sticker_sets_fail(query_, std::move(status));
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for search sticker sets: " << status;
+ }
+ td_->stickers_manager_->on_find_sticker_sets_fail(query_, std::move(status));
}
};
-class InstallStickerSetQuery : public Td::ResultHandler {
+class InstallStickerSetQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- int64 set_id_;
+ StickerSetId set_id_;
bool is_archived_;
public:
explicit InstallStickerSetQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(int64 set_id, tl_object_ptr<telegram_api::InputStickerSet> &&input_set, bool is_archived) {
+ void send(StickerSetId set_id, tl_object_ptr<telegram_api::InputStickerSet> &&input_set, bool is_archived) {
set_id_ = set_id;
is_archived_ = is_archived;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_installStickerSet(std::move(input_set), is_archived))));
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_installStickerSet(std::move(input_set), is_archived)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_installStickerSet>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->stickers_manager_->on_install_sticker_set(set_id_, is_archived_, result_ptr.move_as_ok());
+ td_->stickers_manager_->on_install_sticker_set(set_id_, is_archived_, result_ptr.move_as_ok());
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
-class UninstallStickerSetQuery : public Td::ResultHandler {
+class UninstallStickerSetQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
- int64 set_id_;
+ StickerSetId set_id_;
public:
explicit UninstallStickerSetQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(int64 set_id, tl_object_ptr<telegram_api::InputStickerSet> &&input_set) {
+ void send(StickerSetId set_id, tl_object_ptr<telegram_api::InputStickerSet> &&input_set) {
set_id_ = set_id;
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_uninstallStickerSet(std::move(input_set)))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_uninstallStickerSet(std::move(input_set))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_uninstallStickerSet>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
if (!result) {
LOG(WARNING) << "Receive false in result to uninstallStickerSet";
} else {
- td->stickers_manager_->on_uninstall_sticker_set(set_id_);
+ td_->stickers_manager_->on_uninstall_sticker_set(set_id_);
}
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
-class ReadFeaturedStickerSetsQuery : public Td::ResultHandler {
+class ReadFeaturedStickerSetsQuery final : public Td::ResultHandler {
public:
- void send(vector<int64> sticker_set_ids) {
- LOG(INFO) << "Read featured sticker sets " << format::as_array(sticker_set_ids);
+ void send(const vector<StickerSetId> &sticker_set_ids) {
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_readFeaturedStickers(std::move(sticker_set_ids)))));
+ telegram_api::messages_readFeaturedStickers(StickersManager::convert_sticker_set_ids(sticker_set_ids))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_readFeaturedStickers>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.move_as_ok();
(void)result;
}
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "Receive error for ReadFeaturedStickerSetsQuery: " << status;
- td->stickers_manager_->reload_featured_sticker_sets(true);
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(ERROR) << "Receive error for ReadFeaturedStickerSetsQuery: " << status;
+ }
+ td_->stickers_manager_->reload_featured_sticker_sets(StickerType::Regular, true);
+ td_->stickers_manager_->reload_featured_sticker_sets(StickerType::CustomEmoji, true);
}
};
-class UploadStickerFileQuery : public Td::ResultHandler {
+class UploadStickerFileQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
FileId file_id_;
+ bool was_uploaded_ = false;
public:
explicit UploadStickerFileQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
@@ -569,149 +1014,376 @@ class UploadStickerFileQuery : public Td::ResultHandler {
CHECK(input_peer != nullptr);
CHECK(input_media != nullptr);
file_id_ = file_id;
+ was_uploaded_ = FileManager::extract_was_uploaded(input_media);
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_uploadMedia(std::move(input_peer), std::move(input_media)))));
+ telegram_api::messages_uploadMedia(std::move(input_peer), std::move(input_media))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_uploadMedia>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->stickers_manager_->on_uploaded_sticker_file(file_id_, result_ptr.move_as_ok(), std::move(promise_));
+ td_->stickers_manager_->on_uploaded_sticker_file(file_id_, result_ptr.move_as_ok(), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
CHECK(status.is_error());
+ if (was_uploaded_) {
+ CHECK(file_id_.is_valid());
+ if (begins_with(status.message(), "FILE_PART_") && ends_with(status.message(), "_MISSING")) {
+ // TODO td_->stickers_manager_->on_upload_sticker_file_part_missing(file_id_, to_integer<int32>(status.message().substr(10)));
+ // return;
+ } else {
+ if (status.code() != 429 && status.code() < 500 && !G()->close_flag()) {
+ td_->file_manager_->delete_partial_remote_location(file_id_);
+ }
+ }
+ } else if (FileReferenceManager::is_file_reference_error(status)) {
+ LOG(ERROR) << "Receive file reference error for UploadStickerFileQuery";
+ }
+ td_->file_manager_->cancel_upload(file_id_);
promise_.set_error(std::move(status));
}
};
-class CreateNewStickerSetQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class SuggestStickerSetShortNameQuery final : public Td::ResultHandler {
+ Promise<string> promise_;
+
+ public:
+ explicit SuggestStickerSetShortNameQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(const string &title) {
+ send_query(G()->net_query_creator().create(telegram_api::stickers_suggestShortName(title)));
+ }
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::stickers_suggestShortName>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto ptr = result_ptr.move_as_ok();
+ promise_.set_value(std::move(ptr->short_name_));
+ }
+
+ void on_error(Status status) final {
+ if (status.message() == "TITLE_INVALID") {
+ return promise_.set_value(string());
+ }
+ promise_.set_error(std::move(status));
+ }
+};
+
+class CheckStickerSetShortNameQuery final : public Td::ResultHandler {
+ Promise<bool> promise_;
+
+ public:
+ explicit CheckStickerSetShortNameQuery(Promise<bool> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(const string &short_name) {
+ send_query(G()->net_query_creator().create(telegram_api::stickers_checkShortName(short_name)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::stickers_checkShortName>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class CreateNewStickerSetQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::stickerSet>> promise_;
public:
- explicit CreateNewStickerSetQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit CreateNewStickerSetQuery(Promise<td_api::object_ptr<td_api::stickerSet>> &&promise)
+ : promise_(std::move(promise)) {
}
void send(tl_object_ptr<telegram_api::InputUser> &&input_user, const string &title, const string &short_name,
- bool is_masks, vector<tl_object_ptr<telegram_api::inputStickerSetItem>> &&input_stickers) {
+ StickerType sticker_type, StickerFormat sticker_format,
+ vector<tl_object_ptr<telegram_api::inputStickerSetItem>> &&input_stickers, const string &software) {
CHECK(input_user != nullptr);
int32 flags = 0;
- if (is_masks) {
+ if (sticker_type == StickerType::Mask) {
flags |= telegram_api::stickers_createStickerSet::MASKS_MASK;
}
+ if (sticker_type == StickerType::CustomEmoji) {
+ // flags |= telegram_api::stickers_createStickerSet::EMOJIS_MASK;
+ return on_error(Status::Error(400, "Can't create custom emoji sets"));
+ }
+ if (sticker_format == StickerFormat::Tgs) {
+ flags |= telegram_api::stickers_createStickerSet::ANIMATED_MASK;
+ }
+ if (sticker_format == StickerFormat::Webm) {
+ flags |= telegram_api::stickers_createStickerSet::VIDEOS_MASK;
+ }
+ if (!software.empty()) {
+ flags |= telegram_api::stickers_createStickerSet::SOFTWARE_MASK;
+ }
- send_query(G()->net_query_creator().create(create_storer(telegram_api::stickers_createStickerSet(
- flags, false /*ignored*/, std::move(input_user), title, short_name, std::move(input_stickers)))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::stickers_createStickerSet(flags, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ std::move(input_user), title, short_name, nullptr,
+ std::move(input_stickers), software),
+ {{short_name}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::stickers_createStickerSet>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->stickers_manager_->on_get_messages_sticker_set(0, result_ptr.move_as_ok(), true);
-
- promise_.set_value(Unit());
+ auto sticker_set_id = td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(),
+ true, "CreateNewStickerSetQuery");
+ if (!sticker_set_id.is_valid()) {
+ return on_error(Status::Error(500, "Created sticker set not found"));
+ }
+ promise_.set_value(td_->stickers_manager_->get_sticker_set_object(sticker_set_id));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
-class AddStickerToSetQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class AddStickerToSetQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::stickerSet>> promise_;
public:
- explicit AddStickerToSetQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit AddStickerToSetQuery(Promise<td_api::object_ptr<td_api::stickerSet>> &&promise)
+ : promise_(std::move(promise)) {
}
void send(const string &short_name, tl_object_ptr<telegram_api::inputStickerSetItem> &&input_sticker) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::stickers_addStickerToSet(
- make_tl_object<telegram_api::inputStickerSetShortName>(short_name), std::move(input_sticker)))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::stickers_addStickerToSet(make_tl_object<telegram_api::inputStickerSetShortName>(short_name),
+ std::move(input_sticker)),
+ {{short_name}}));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::stickers_addStickerToSet>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->stickers_manager_->on_get_messages_sticker_set(0, result_ptr.move_as_ok(), true);
+ auto sticker_set_id = td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(),
+ true, "AddStickerToSetQuery");
+ if (!sticker_set_id.is_valid()) {
+ return on_error(Status::Error(500, "Sticker set not found"));
+ }
+ promise_.set_value(td_->stickers_manager_->get_sticker_set_object(sticker_set_id));
+ }
- promise_.set_value(Unit());
+ void on_error(Status status) final {
+ CHECK(status.is_error());
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SetStickerSetThumbnailQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::stickerSet>> promise_;
+
+ public:
+ explicit SetStickerSetThumbnailQuery(Promise<td_api::object_ptr<td_api::stickerSet>> &&promise)
+ : promise_(std::move(promise)) {
}
- void on_error(uint64 id, Status status) override {
+ void send(const string &short_name, tl_object_ptr<telegram_api::InputDocument> &&input_document) {
+ send_query(G()->net_query_creator().create(
+ telegram_api::stickers_setStickerSetThumb(make_tl_object<telegram_api::inputStickerSetShortName>(short_name),
+ std::move(input_document)),
+ {{short_name}}));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::stickers_setStickerSetThumb>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto sticker_set_id = td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(),
+ true, "SetStickerSetThumbnailQuery");
+ if (!sticker_set_id.is_valid()) {
+ return on_error(Status::Error(500, "Sticker set not found"));
+ }
+ promise_.set_value(td_->stickers_manager_->get_sticker_set_object(sticker_set_id));
+ }
+
+ void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
-class SetStickerPositionQuery : public Td::ResultHandler {
+class SetStickerPositionQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit SetStickerPositionQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(tl_object_ptr<telegram_api::InputDocument> &&input_document, int32 position) {
+ void send(const string &short_name, tl_object_ptr<telegram_api::inputDocument> &&input_document, int32 position) {
+ vector<ChainId> chain_ids;
+ if (!short_name.empty()) {
+ chain_ids.emplace_back(short_name);
+ }
send_query(G()->net_query_creator().create(
- create_storer(telegram_api::stickers_changeStickerPosition(std::move(input_document), position))));
+ telegram_api::stickers_changeStickerPosition(std::move(input_document), position), std::move(chain_ids)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::stickers_changeStickerPosition>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->stickers_manager_->on_get_messages_sticker_set(0, result_ptr.move_as_ok(), true);
+ td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(), true,
+ "SetStickerPositionQuery");
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
-class DeleteStickerFromSetQuery : public Td::ResultHandler {
+class DeleteStickerFromSetQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
explicit DeleteStickerFromSetQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
- void send(tl_object_ptr<telegram_api::InputDocument> &&input_document) {
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::stickers_removeStickerFromSet(std::move(input_document)))));
+ void send(const string &short_name, tl_object_ptr<telegram_api::inputDocument> &&input_document) {
+ vector<ChainId> chain_ids;
+ if (!short_name.empty()) {
+ chain_ids.emplace_back(short_name);
+ }
+ send_query(G()->net_query_creator().create(telegram_api::stickers_removeStickerFromSet(std::move(input_document)),
+ std::move(chain_ids)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::stickers_removeStickerFromSet>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->stickers_manager_->on_get_messages_sticker_set(0, result_ptr.move_as_ok(), true);
+ td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), result_ptr.move_as_ok(), true,
+ "DeleteStickerFromSetQuery");
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
CHECK(status.is_error());
promise_.set_error(std::move(status));
}
};
+class GetCustomEmojiDocumentsQuery final : public Td::ResultHandler {
+ Promise<vector<telegram_api::object_ptr<telegram_api::Document>>> promise_;
+
+ public:
+ explicit GetCustomEmojiDocumentsQuery(Promise<vector<telegram_api::object_ptr<telegram_api::Document>>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(vector<CustomEmojiId> &&custom_emoji_ids) {
+ auto document_ids =
+ transform(custom_emoji_ids, [](CustomEmojiId custom_emoji_id) { return custom_emoji_id.get(); });
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getCustomEmojiDocuments(std::move(document_ids))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_getCustomEmojiDocuments>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class SendAnimatedEmojiClicksQuery final : public Td::ResultHandler {
+ DialogId dialog_id_;
+ string emoji_;
+
+ public:
+ void send(DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> &&input_peer,
+ tl_object_ptr<telegram_api::sendMessageEmojiInteraction> &&action) {
+ dialog_id_ = dialog_id;
+ CHECK(input_peer != nullptr);
+ CHECK(action != nullptr);
+ emoji_ = action->emoticon_;
+
+ int32 flags = 0;
+ send_query(G()->net_query_creator().create(
+ telegram_api::messages_setTyping(flags, std::move(input_peer), 0, std::move(action))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_setTyping>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ // ignore result
+ }
+
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SendAnimatedEmojiClicksQuery")) {
+ LOG(INFO) << "Receive error for send animated emoji clicks: " << status;
+ }
+
+ td_->stickers_manager_->on_send_animated_emoji_clicks(dialog_id_, emoji_);
+ }
+};
+
+template <class StorerT>
+void StickersManager::FoundStickers::store(StorerT &storer) const {
+ StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get();
+ td::store(narrow_cast<int32>(sticker_ids_.size()), storer);
+ for (auto sticker_id : sticker_ids_) {
+ stickers_manager->store_sticker(sticker_id, false, storer, "FoundStickers");
+ }
+ td::store(cache_time_, storer);
+ store_time(next_reload_time_, storer);
+}
+
+template <class ParserT>
+void StickersManager::FoundStickers::parse(ParserT &parser) {
+ StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get();
+ int32 size = parser.fetch_int();
+ sticker_ids_.resize(size);
+ for (auto &sticker_id : sticker_ids_) {
+ sticker_id = stickers_manager->parse_sticker(false, parser);
+ }
+ td::parse(cache_time_, parser);
+ parse_time(next_reload_time_, parser);
+}
+
class StickersManager::StickerListLogEvent {
public:
vector<FileId> sticker_ids;
@@ -726,7 +1398,7 @@ class StickersManager::StickerListLogEvent {
StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get();
td::store(narrow_cast<int32>(sticker_ids.size()), storer);
for (auto sticker_id : sticker_ids) {
- stickers_manager->store_sticker(sticker_id, false, storer);
+ stickers_manager->store_sticker(sticker_id, false, storer, "StickerListLogEvent");
}
}
@@ -743,45 +1415,50 @@ class StickersManager::StickerListLogEvent {
class StickersManager::StickerSetListLogEvent {
public:
- vector<int64> sticker_set_ids;
+ vector<StickerSetId> sticker_set_ids_;
+ bool is_premium_ = false;
StickerSetListLogEvent() = default;
- explicit StickerSetListLogEvent(vector<int64> sticker_set_ids) : sticker_set_ids(std::move(sticker_set_ids)) {
+ StickerSetListLogEvent(vector<StickerSetId> sticker_set_ids, bool is_premium)
+ : sticker_set_ids_(std::move(sticker_set_ids)), is_premium_(is_premium) {
}
template <class StorerT>
void store(StorerT &storer) const {
- StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get();
- td::store(narrow_cast<int32>(sticker_set_ids.size()), storer);
- for (auto sticker_set_id : sticker_set_ids) {
- stickers_manager->store_sticker_set_id(sticker_set_id, storer);
- }
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_premium_);
+ END_STORE_FLAGS();
+ td::store(sticker_set_ids_, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
- StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get();
- int32 size = parser.fetch_int();
- sticker_set_ids.resize(size);
- for (auto &sticker_set_id : sticker_set_ids) {
- stickers_manager->parse_sticker_set_id(sticker_set_id, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddStickerSetListFlags)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_premium_);
+ END_PARSE_FLAGS();
}
+ td::parse(sticker_set_ids_, parser);
}
};
-class StickersManager::UploadStickerFileCallback : public FileManager::UploadCallback {
+class StickersManager::UploadStickerFileCallback final : public FileManager::UploadCallback {
public:
- void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) override {
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
send_closure_later(G()->stickers_manager(), &StickersManager::on_upload_sticker_file, file_id,
std::move(input_file));
}
- void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) override {
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
+ UNREACHABLE();
+ }
+
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
UNREACHABLE();
}
- void on_upload_error(FileId file_id, Status error) override {
+ void on_upload_error(FileId file_id, Status error) final {
send_closure_later(G()->stickers_manager(), &StickersManager::on_upload_sticker_file_error, file_id,
std::move(error));
}
@@ -789,6 +1466,423 @@ class StickersManager::UploadStickerFileCallback : public FileManager::UploadCal
StickersManager::StickersManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
upload_sticker_file_callback_ = std::make_shared<UploadStickerFileCallback>();
+
+ on_update_animated_emoji_zoom();
+ on_update_recent_stickers_limit();
+ on_update_favorite_stickers_limit();
+
+ next_click_animated_emoji_message_time_ = Time::now();
+ next_update_animated_emoji_clicked_time_ = Time::now();
+}
+
+StickersManager::~StickersManager() {
+ Scheduler::instance()->destroy_on_scheduler(
+ G()->get_gc_scheduler_id(), stickers_, sticker_sets_, short_name_to_sticker_set_id_, attached_sticker_sets_,
+ found_stickers_, found_sticker_sets_, emoji_language_codes_, emoji_language_code_versions_,
+ emoji_language_code_last_difference_times_, reloaded_emoji_keywords_, premium_gift_messages_, dice_messages_,
+ emoji_messages_, custom_emoji_messages_, custom_emoji_to_sticker_id_);
+}
+
+void StickersManager::start_up() {
+ init();
+}
+
+void StickersManager::init() {
+ if (is_inited_ || !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot() || G()->close_flag()) {
+ return;
+ }
+ LOG(INFO) << "Init StickersManager";
+ is_inited_ = true;
+
+ {
+ auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji());
+ if (G()->is_test_dc()) {
+ init_special_sticker_set(sticker_set, 1258816259751954, 4879754868529595811, "emojies");
+ } else {
+ init_special_sticker_set(sticker_set, 1258816259751983, 5100237018658464041, "AnimatedEmojies");
+ }
+ load_special_sticker_set_info_from_binlog(sticker_set);
+ }
+ if (!G()->is_test_dc()) {
+ auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji_click());
+ load_special_sticker_set_info_from_binlog(sticker_set);
+ }
+ {
+ auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::premium_gifts());
+ load_special_sticker_set_info_from_binlog(sticker_set);
+ }
+ {
+ auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::generic_animations());
+ load_special_sticker_set_info_from_binlog(sticker_set);
+ }
+ {
+ auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::default_statuses());
+ load_special_sticker_set_info_from_binlog(sticker_set);
+ }
+ {
+ auto &sticker_set = add_special_sticker_set(SpecialStickerSetType::default_topic_icons());
+ load_special_sticker_set_info_from_binlog(sticker_set);
+ }
+
+ dice_emojis_str_ =
+ td_->option_manager_->get_option_string("dice_emojis", "🎲\x01🎯\x01🏀\x01⚽\x01⚽️\x01🎰\x01🎳");
+ dice_emojis_ = full_split(dice_emojis_str_, '\x01');
+ for (auto &dice_emoji : dice_emojis_) {
+ auto &animated_dice_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_dice(dice_emoji));
+ load_special_sticker_set_info_from_binlog(animated_dice_sticker_set);
+ }
+ send_closure(G()->td(), &Td::send_update, get_update_dice_emojis_object());
+
+ load_active_reactions();
+
+ on_update_dice_success_values();
+ on_update_dice_emojis();
+
+ on_update_emoji_sounds();
+
+ on_update_disable_animated_emojis();
+ if (!disable_animated_emojis_) {
+ load_special_sticker_set(add_special_sticker_set(SpecialStickerSetType::animated_emoji()));
+ }
+ load_special_sticker_set(add_special_sticker_set(SpecialStickerSetType::premium_gifts()));
+
+ if (G()->parameters().use_file_db) {
+ auto old_featured_sticker_set_count_str = G()->td_db()->get_binlog_pmc()->get("old_featured_sticker_set_count");
+ if (!old_featured_sticker_set_count_str.empty()) {
+ old_featured_sticker_set_count_[static_cast<int32>(StickerType::Regular)] =
+ to_integer<int32>(old_featured_sticker_set_count_str);
+ }
+ if (!G()->td_db()->get_binlog_pmc()->get("invalidate_old_featured_sticker_sets").empty()) {
+ invalidate_old_featured_sticker_sets(StickerType::Regular);
+ }
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase("old_featured_sticker_set_count");
+ G()->td_db()->get_binlog_pmc()->erase("invalidate_old_featured_sticker_sets");
+ }
+
+ G()->td_db()->get_binlog_pmc()->erase("animated_dice_sticker_set"); // legacy
+ td_->option_manager_->set_option_empty("animated_dice_sticker_set_name"); // legacy
+ td_->option_manager_->set_option_empty("animated_emoji_sticker_set_name"); // legacy
+}
+
+td_api::object_ptr<td_api::emojiReaction> StickersManager::get_emoji_reaction_object(const string &emoji) const {
+ for (auto &reaction : reactions_.reactions_) {
+ if (reaction.reaction_ == emoji) {
+ return td_api::make_object<td_api::emojiReaction>(
+ reaction.reaction_, reaction.title_, reaction.is_active_, get_sticker_object(reaction.static_icon_),
+ get_sticker_object(reaction.appear_animation_), get_sticker_object(reaction.select_animation_),
+ get_sticker_object(reaction.activate_animation_), get_sticker_object(reaction.effect_animation_),
+ get_sticker_object(reaction.around_animation_), get_sticker_object(reaction.center_animation_));
+ }
+ }
+ return nullptr;
+}
+
+void StickersManager::get_emoji_reaction(const string &emoji,
+ Promise<td_api::object_ptr<td_api::emojiReaction>> &&promise) {
+ load_reactions();
+ if (reactions_.reactions_.empty() && reactions_.are_being_reloaded_) {
+ pending_get_emoji_reaction_queries_.emplace_back(emoji, std::move(promise));
+ return;
+ }
+ promise.set_value(get_emoji_reaction_object(emoji));
+}
+
+vector<string> StickersManager::get_recent_reactions() {
+ load_recent_reactions();
+ return recent_reactions_.reactions_;
+}
+
+vector<string> StickersManager::get_top_reactions() {
+ load_top_reactions();
+ return top_reactions_.reactions_;
+}
+
+void StickersManager::add_recent_reaction(const string &reaction) {
+ load_recent_reactions();
+
+ auto &reactions = recent_reactions_.reactions_;
+ if (!reactions.empty() && reactions[0] == reaction) {
+ return;
+ }
+
+ auto it = std::find(reactions.begin(), reactions.end(), reaction);
+ if (it == reactions.end()) {
+ if (static_cast<int32>(reactions.size()) == MAX_RECENT_REACTIONS) {
+ reactions.back() = reaction;
+ } else {
+ reactions.push_back(reaction);
+ }
+ it = reactions.end() - 1;
+ }
+ std::rotate(reactions.begin(), it, it + 1);
+
+ recent_reactions_.hash_ = get_reactions_hash(reactions);
+}
+
+void StickersManager::clear_recent_reactions(Promise<Unit> &&promise) {
+ load_recent_reactions();
+
+ if (recent_reactions_.reactions_.empty()) {
+ return promise.set_value(Unit());
+ }
+
+ recent_reactions_.hash_ = 0;
+ recent_reactions_.reactions_.clear();
+
+ td_->create_handler<ClearRecentReactionsQuery>(std::move(promise))->send();
+}
+
+void StickersManager::reload_reactions() {
+ if (G()->close_flag() || reactions_.are_being_reloaded_) {
+ return;
+ }
+ CHECK(!td_->auth_manager_->is_bot());
+ reactions_.are_being_reloaded_ = true;
+ load_reactions(); // must be after are_being_reloaded_ is set to true to avoid recursion
+ td_->create_handler<GetAvailableReactionsQuery>()->send(reactions_.hash_);
+}
+
+void StickersManager::reload_recent_reactions() {
+ if (G()->close_flag() || recent_reactions_.is_being_reloaded_) {
+ return;
+ }
+ CHECK(!td_->auth_manager_->is_bot());
+ recent_reactions_.is_being_reloaded_ = true;
+ load_recent_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion
+ td_->create_handler<GetRecentReactionsQuery>()->send(MAX_RECENT_REACTIONS, recent_reactions_.hash_);
+}
+
+void StickersManager::reload_top_reactions() {
+ if (G()->close_flag() || top_reactions_.is_being_reloaded_) {
+ return;
+ }
+ CHECK(!td_->auth_manager_->is_bot());
+ top_reactions_.is_being_reloaded_ = true;
+ load_top_reactions(); // must be after is_being_reloaded_ is set to true to avoid recursion
+ td_->create_handler<GetTopReactionsQuery>()->send(top_reactions_.hash_);
+}
+
+StickersManager::SpecialStickerSet &StickersManager::add_special_sticker_set(const SpecialStickerSetType &type) {
+ CHECK(!type.is_empty());
+ auto &result_ptr = special_sticker_sets_[type];
+ if (result_ptr == nullptr) {
+ result_ptr = make_unique<SpecialStickerSet>();
+ }
+ auto &result = *result_ptr;
+ if (result.type_.is_empty()) {
+ result.type_ = type;
+ } else {
+ CHECK(result.type_ == type);
+ }
+ return result;
+}
+
+void StickersManager::init_special_sticker_set(SpecialStickerSet &sticker_set, int64 sticker_set_id, int64 access_hash,
+ string name) {
+ sticker_set.id_ = StickerSetId(sticker_set_id);
+ sticker_set.access_hash_ = access_hash;
+ sticker_set.short_name_ = std::move(name);
+}
+
+void StickersManager::load_special_sticker_set_info_from_binlog(SpecialStickerSet &sticker_set) {
+ if (G()->parameters().use_file_db) {
+ string sticker_set_string = G()->td_db()->get_binlog_pmc()->get(sticker_set.type_.type_);
+ if (!sticker_set_string.empty()) {
+ auto parts = full_split(sticker_set_string);
+ if (parts.size() != 3) {
+ LOG(ERROR) << "Can't parse " << sticker_set_string;
+ } else {
+ auto r_sticker_set_id = to_integer_safe<int64>(parts[0]);
+ auto r_sticker_set_access_hash = to_integer_safe<int64>(parts[1]);
+ auto sticker_set_name = parts[2];
+ if (r_sticker_set_id.is_error() || r_sticker_set_access_hash.is_error() ||
+ clean_username(sticker_set_name) != sticker_set_name || sticker_set_name.empty()) {
+ LOG(ERROR) << "Can't parse " << sticker_set_string;
+ } else {
+ init_special_sticker_set(sticker_set, r_sticker_set_id.ok(), r_sticker_set_access_hash.ok(),
+ std::move(sticker_set_name));
+ }
+ }
+ }
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase(sticker_set.type_.type_);
+ }
+
+ if (!sticker_set.id_.is_valid()) {
+ return;
+ }
+
+ add_sticker_set(sticker_set.id_, sticker_set.access_hash_);
+ auto cleaned_username = clean_username(sticker_set.short_name_);
+ if (!cleaned_username.empty()) {
+ short_name_to_sticker_set_id_.set(cleaned_username, sticker_set.id_);
+ }
+}
+
+void StickersManager::load_special_sticker_set_by_type(SpecialStickerSetType type) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto &sticker_set = add_special_sticker_set(type);
+ if (!sticker_set.is_being_loaded_) {
+ return;
+ }
+ sticker_set.is_being_loaded_ = false;
+ load_special_sticker_set(sticker_set);
+}
+
+void StickersManager::load_special_sticker_set(SpecialStickerSet &sticker_set) {
+ CHECK(!td_->auth_manager_->is_bot() || sticker_set.type_ == SpecialStickerSetType::default_topic_icons());
+ if (sticker_set.is_being_loaded_) {
+ return;
+ }
+ sticker_set.is_being_loaded_ = true;
+ LOG(INFO) << "Load " << sticker_set.type_.type_ << " " << sticker_set.id_;
+ if (sticker_set.id_.is_valid()) {
+ auto s = get_sticker_set(sticker_set.id_);
+ CHECK(s != nullptr);
+ if (s->was_loaded_) {
+ reload_special_sticker_set(sticker_set, s->is_loaded_ ? s->hash_ : 0);
+ return;
+ }
+
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), type = sticker_set.type_](Result<Unit> &&result) {
+ send_closure(actor_id, &StickersManager::on_load_special_sticker_set, type,
+ result.is_ok() ? Status::OK() : result.move_as_error());
+ });
+ load_sticker_sets({sticker_set.id_}, std::move(promise));
+ } else {
+ reload_special_sticker_set(sticker_set, 0);
+ }
+}
+
+void StickersManager::reload_special_sticker_set_by_type(SpecialStickerSetType type, bool is_recursive) {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (disable_animated_emojis_ &&
+ (type == SpecialStickerSetType::animated_emoji() || type == SpecialStickerSetType::animated_emoji_click())) {
+ return;
+ }
+
+ auto &sticker_set = add_special_sticker_set(type);
+ if (sticker_set.is_being_reloaded_) {
+ return;
+ }
+
+ if (!sticker_set.id_.is_valid()) {
+ return reload_special_sticker_set(sticker_set, 0);
+ }
+
+ const auto *s = get_sticker_set(sticker_set.id_);
+ if (s != nullptr && s->is_inited_ && s->was_loaded_) {
+ return reload_special_sticker_set(sticker_set, s->is_loaded_ ? s->hash_ : 0);
+ }
+ if (!is_recursive) {
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), type = std::move(type)](Unit result) mutable {
+ send_closure(actor_id, &StickersManager::reload_special_sticker_set_by_type, std::move(type), true);
+ });
+ return load_sticker_sets({sticker_set.id_}, std::move(promise));
+ }
+
+ reload_special_sticker_set(sticker_set, 0);
+}
+
+void StickersManager::reload_special_sticker_set(SpecialStickerSet &sticker_set, int32 hash) {
+ if (sticker_set.is_being_reloaded_) {
+ return;
+ }
+ sticker_set.is_being_reloaded_ = true;
+ td_->create_handler<ReloadSpecialStickerSetQuery>()->send(sticker_set.id_, sticker_set.type_, hash);
+}
+
+void StickersManager::on_load_special_sticker_set(const SpecialStickerSetType &type, Status result) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto &special_sticker_set = add_special_sticker_set(type);
+ special_sticker_set.is_being_reloaded_ = false;
+ if (!special_sticker_set.is_being_loaded_) {
+ return;
+ }
+
+ if (result.is_error()) {
+ LOG(INFO) << "Failed to load special sticker set " << type.type_ << ": " << result.error();
+
+ // failed to load the special sticker set; repeat after some time
+ create_actor<SleepActor>("RetryLoadSpecialStickerSetActor", Random::fast(300, 600),
+ PromiseCreator::lambda([actor_id = actor_id(this), type](Result<Unit> result) mutable {
+ send_closure(actor_id, &StickersManager::load_special_sticker_set_by_type,
+ std::move(type));
+ }))
+ .release();
+ return;
+ }
+
+ special_sticker_set.is_being_loaded_ = false;
+
+ if (type == SpecialStickerSetType::animated_emoji()) {
+ set_promises(pending_get_animated_emoji_queries_);
+ try_update_animated_emoji_messages();
+ return;
+ }
+ if (type == SpecialStickerSetType::premium_gifts()) {
+ set_promises(pending_get_premium_gift_option_sticker_queries_);
+ try_update_premium_gift_messages();
+ return;
+ }
+ if (type == SpecialStickerSetType::generic_animations()) {
+ set_promises(pending_get_generic_animations_queries_);
+ return;
+ }
+ if (type == SpecialStickerSetType::default_statuses()) {
+ set_promises(pending_get_default_statuses_queries_);
+ return;
+ }
+ if (type == SpecialStickerSetType::default_topic_icons()) {
+ set_promises(pending_get_default_topic_icons_queries_);
+ return;
+ }
+
+ CHECK(special_sticker_set.id_.is_valid());
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ CHECK(sticker_set != nullptr);
+ CHECK(sticker_set->was_loaded_);
+
+ if (type == SpecialStickerSetType::animated_emoji_click()) {
+ auto pending_get_requests = std::move(pending_get_animated_emoji_click_stickers_);
+ reset_to_empty(pending_get_animated_emoji_click_stickers_);
+ for (auto &pending_request : pending_get_requests) {
+ choose_animated_emoji_click_sticker(sticker_set, std::move(pending_request.message_text_),
+ pending_request.full_message_id_, pending_request.start_time_,
+ std::move(pending_request.promise_));
+ }
+ auto pending_click_requests = std::move(pending_on_animated_emoji_message_clicked_);
+ reset_to_empty(pending_on_animated_emoji_message_clicked_);
+ for (auto &pending_request : pending_click_requests) {
+ schedule_update_animated_emoji_clicked(sticker_set, pending_request.emoji_, pending_request.full_message_id_,
+ std::move(pending_request.clicks_));
+ }
+ return;
+ }
+
+ auto emoji = type.get_dice_emoji();
+ CHECK(!emoji.empty());
+
+ auto it = dice_messages_.find(emoji);
+ if (it == dice_messages_.end()) {
+ return;
+ }
+
+ vector<FullMessageId> full_message_ids;
+ it->second.foreach([&](const FullMessageId &full_message_id) { full_message_ids.push_back(full_message_id); });
+ CHECK(!full_message_ids.empty());
+ for (const auto &full_message_id : full_message_ids) {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ }
}
void StickersManager::tear_down() {
@@ -811,67 +1905,490 @@ tl_object_ptr<td_api::MaskPoint> StickersManager::get_mask_point_object(int32 po
}
}
-tl_object_ptr<td_api::sticker> StickersManager::get_sticker_object(FileId file_id) {
+StickerType StickersManager::get_sticker_type(FileId file_id) const {
+ const auto *sticker = get_sticker(file_id);
+ CHECK(sticker != nullptr);
+ return sticker->type_;
+}
+
+bool StickersManager::is_premium_custom_emoji(CustomEmojiId custom_emoji_id, bool default_result) const {
+ auto sticker_id = custom_emoji_to_sticker_id_.get(custom_emoji_id);
+ if (!sticker_id.is_valid()) {
+ return default_result;
+ }
+ const Sticker *s = get_sticker(sticker_id);
+ CHECK(s != nullptr);
+ return s->is_premium_;
+}
+
+CustomEmojiId StickersManager::get_custom_emoji_id(FileId sticker_id) const {
+ auto sticker_file_view = td_->file_manager_->get_file_view(sticker_id);
+ if (sticker_file_view.is_encrypted() || !sticker_file_view.has_remote_location() ||
+ !sticker_file_view.remote_location().is_document()) {
+ return CustomEmojiId();
+ }
+ return CustomEmojiId(sticker_file_view.remote_location().get_id());
+}
+
+vector<td_api::object_ptr<td_api::closedVectorPath>> StickersManager::get_sticker_minithumbnail(
+ CSlice path, StickerSetId sticker_set_id, int64 document_id, double zoom) {
+ if (path.empty()) {
+ return {};
+ }
+
+ auto buf = StackAllocator::alloc(1 << 9);
+ StringBuilder sb(buf.as_slice(), true);
+
+ sb << 'M';
+ for (unsigned char c : path) {
+ if (c >= 128 + 64) {
+ sb << "AACAAAAHAAALMAAAQASTAVAAAZaacaaaahaaalmaaaqastava.az0123456789-,"[c - 128 - 64];
+ } else {
+ if (c >= 128) {
+ sb << ',';
+ } else if (c >= 64) {
+ sb << '-';
+ }
+ sb << (c & 63);
+ }
+ }
+ sb << 'z';
+
+ CHECK(!sb.is_error());
+ path = sb.as_cslice();
+ LOG(DEBUG) << "Transform SVG path " << path;
+
+ size_t pos = 0;
+ auto skip_commas = [&path, &pos] {
+ while (path[pos] == ',') {
+ pos++;
+ }
+ };
+ auto get_number = [&] {
+ skip_commas();
+ int sign = 1;
+ if (path[pos] == '-') {
+ sign = -1;
+ pos++;
+ }
+ double res = 0;
+ while (is_digit(path[pos])) {
+ res = res * 10 + path[pos++] - '0';
+ }
+ if (path[pos] == '.') {
+ pos++;
+ double mul = 0.1;
+ while (is_digit(path[pos])) {
+ res += (path[pos] - '0') * mul;
+ mul *= 0.1;
+ pos++;
+ }
+ }
+ return sign * res;
+ };
+ auto make_point = [zoom](double x, double y) {
+ return td_api::make_object<td_api::point>(x * zoom, y * zoom);
+ };
+
+ vector<td_api::object_ptr<td_api::closedVectorPath>> result;
+ double x = 0;
+ double y = 0;
+ while (path[pos] != '\0') {
+ skip_commas();
+ if (path[pos] == '\0') {
+ break;
+ }
+
+ while (path[pos] == 'm' || path[pos] == 'M') {
+ auto command = path[pos++];
+ do {
+ if (command == 'm') {
+ x += get_number();
+ y += get_number();
+ } else {
+ x = get_number();
+ y = get_number();
+ }
+ skip_commas();
+ } while (path[pos] != '\0' && !is_alpha(path[pos]));
+ }
+
+ double start_x = x;
+ double start_y = y;
+
+ vector<td_api::object_ptr<td_api::VectorPathCommand>> commands;
+ bool have_last_end_control_point = false;
+ double last_end_control_point_x = 0;
+ double last_end_control_point_y = 0;
+ bool is_closed = false;
+ char command = '-';
+ while (!is_closed) {
+ skip_commas();
+ if (path[pos] == '\0') {
+ LOG(ERROR) << "Receive unclosed path " << path << " in a sticker " << document_id << " from " << sticker_set_id;
+ return {};
+ }
+ if (is_alpha(path[pos])) {
+ command = path[pos++];
+ }
+ switch (command) {
+ case 'l':
+ case 'L':
+ case 'h':
+ case 'H':
+ case 'v':
+ case 'V':
+ if (command == 'l' || command == 'h') {
+ x += get_number();
+ } else if (command == 'L' || command == 'H') {
+ x = get_number();
+ }
+ if (command == 'l' || command == 'v') {
+ y += get_number();
+ } else if (command == 'L' || command == 'V') {
+ y = get_number();
+ }
+ commands.push_back(td_api::make_object<td_api::vectorPathCommandLine>(make_point(x, y)));
+ have_last_end_control_point = false;
+ break;
+ case 'C':
+ case 'c':
+ case 'S':
+ case 's': {
+ double start_control_point_x;
+ double start_control_point_y;
+ if (command == 'S' || command == 's') {
+ if (have_last_end_control_point) {
+ start_control_point_x = 2 * x - last_end_control_point_x;
+ start_control_point_y = 2 * y - last_end_control_point_y;
+ } else {
+ start_control_point_x = x;
+ start_control_point_y = y;
+ }
+ } else {
+ start_control_point_x = get_number();
+ start_control_point_y = get_number();
+ if (command == 'c') {
+ start_control_point_x += x;
+ start_control_point_y += y;
+ }
+ }
+
+ last_end_control_point_x = get_number();
+ last_end_control_point_y = get_number();
+ if (command == 'c' || command == 's') {
+ last_end_control_point_x += x;
+ last_end_control_point_y += y;
+ }
+ have_last_end_control_point = true;
+
+ if (command == 'c' || command == 's') {
+ x += get_number();
+ y += get_number();
+ } else {
+ x = get_number();
+ y = get_number();
+ }
+
+ commands.push_back(td_api::make_object<td_api::vectorPathCommandCubicBezierCurve>(
+ make_point(start_control_point_x, start_control_point_y),
+ make_point(last_end_control_point_x, last_end_control_point_y), make_point(x, y)));
+ break;
+ }
+ case 'm':
+ case 'M':
+ pos--;
+ // fallthrough
+ case 'z':
+ case 'Z':
+ if (x != start_x || y != start_y) {
+ x = start_x;
+ y = start_y;
+ commands.push_back(td_api::make_object<td_api::vectorPathCommandLine>(make_point(x, y)));
+ }
+ if (!commands.empty()) {
+ result.push_back(td_api::make_object<td_api::closedVectorPath>(std::move(commands)));
+ commands.clear();
+ }
+ is_closed = true;
+ break;
+ default:
+ LOG(ERROR) << "Receive invalid command " << command << " at pos " << pos << " in a sticker " << document_id
+ << " from " << sticker_set_id << ": " << path;
+ return {};
+ }
+ }
+ }
+ /*
+ string svg;
+ for (const auto &vector_path : result) {
+ CHECK(!vector_path->commands_.empty());
+ svg += 'M';
+ auto add_point = [&](const td_api::object_ptr<td_api::point> &p) {
+ svg += to_string(static_cast<int>(p->x_));
+ svg += ',';
+ svg += to_string(static_cast<int>(p->y_));
+ svg += ',';
+ };
+ auto last_command = vector_path->commands_.back().get();
+ switch (last_command->get_id()) {
+ case td_api::vectorPathCommandLine::ID:
+ add_point(static_cast<const td_api::vectorPathCommandLine *>(last_command)->end_point_);
+ break;
+ case td_api::vectorPathCommandCubicBezierCurve::ID:
+ add_point(static_cast<const td_api::vectorPathCommandCubicBezierCurve *>(last_command)->end_point_);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ for (auto &command : vector_path->commands_) {
+ switch (command->get_id()) {
+ case td_api::vectorPathCommandLine::ID: {
+ auto line = static_cast<const td_api::vectorPathCommandLine *>(command.get());
+ svg += 'L';
+ add_point(line->end_point_);
+ break;
+ }
+ case td_api::vectorPathCommandCubicBezierCurve::ID: {
+ auto curve = static_cast<const td_api::vectorPathCommandCubicBezierCurve *>(command.get());
+ svg += 'C';
+ add_point(curve->start_control_point_);
+ add_point(curve->end_control_point_);
+ add_point(curve->end_point_);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+ svg += 'z';
+ }
+ */
+
+ return result;
+}
+
+tl_object_ptr<td_api::sticker> StickersManager::get_sticker_object(FileId file_id, bool for_animated_emoji,
+ bool for_clicked_animated_emoji) const {
if (!file_id.is_valid()) {
return nullptr;
}
- auto &sticker = stickers_[file_id];
+ const auto *sticker = get_sticker(file_id);
CHECK(sticker != nullptr);
- sticker->is_changed = false;
-
- auto mask_position = sticker->point >= 0
- ? make_tl_object<td_api::maskPosition>(get_mask_point_object(sticker->point),
- sticker->x_shift, sticker->y_shift, sticker->scale)
+ auto mask_position = sticker->point_ >= 0
+ ? make_tl_object<td_api::maskPosition>(get_mask_point_object(sticker->point_),
+ sticker->x_shift_, sticker->y_shift_, sticker->scale_)
: nullptr;
- const PhotoSize &thumbnail =
- sticker->sticker_thumbnail.file_id.is_valid() ? sticker->sticker_thumbnail : sticker->message_thumbnail;
- return make_tl_object<td_api::sticker>(sticker->set_id, sticker->dimensions.width, sticker->dimensions.height,
- sticker->alt, sticker->is_mask, std::move(mask_position),
- get_photo_size_object(td_->file_manager_.get(), &thumbnail),
- td_->file_manager_->get_file_object(file_id));
+ const PhotoSize &thumbnail = sticker->m_thumbnail_.file_id.is_valid() ? sticker->m_thumbnail_ : sticker->s_thumbnail_;
+ auto thumbnail_format = PhotoFormat::Webp;
+ int64 document_id = 0;
+ CustomEmojiId custom_emoji_id;
+ if (!sticker->set_id_.is_valid()) {
+ auto sticker_file_view = td_->file_manager_->get_file_view(sticker->file_id_);
+ if (sticker_file_view.is_encrypted()) {
+ // uploaded to secret chats stickers have JPEG thumbnail instead of server-generated WEBP
+ thumbnail_format = PhotoFormat::Jpeg;
+ } else {
+ if (sticker_file_view.has_remote_location() && sticker_file_view.remote_location().is_document()) {
+ document_id = sticker_file_view.remote_location().get_id();
+ }
+
+ if (thumbnail.file_id.is_valid()) {
+ auto thumbnail_file_view = td_->file_manager_->get_file_view(thumbnail.file_id);
+ if (ends_with(thumbnail_file_view.suggested_path(), ".jpg")) {
+ thumbnail_format = PhotoFormat::Jpeg;
+ }
+ }
+ }
+ } else if (sticker->type_ == StickerType::CustomEmoji) {
+ custom_emoji_id = get_custom_emoji_id(sticker->file_id_);
+ }
+ auto thumbnail_object = get_thumbnail_object(td_->file_manager_.get(), thumbnail, thumbnail_format);
+ int32 width = sticker->dimensions_.width;
+ int32 height = sticker->dimensions_.height;
+ double zoom = 1.0;
+ if ((is_sticker_format_vector(sticker->format_) || sticker->type_ == StickerType::CustomEmoji) &&
+ (for_animated_emoji || for_clicked_animated_emoji)) {
+ if (sticker->type_ == StickerType::CustomEmoji && max(width, height) <= 100) {
+ zoom *= 5.12;
+ }
+ width = static_cast<int32>(width * zoom + 0.5);
+ height = static_cast<int32>(height * zoom + 0.5);
+ if (for_clicked_animated_emoji) {
+ zoom *= 3;
+ width *= 3;
+ height *= 3;
+ }
+ }
+ auto premium_animation_object = sticker->premium_animation_file_id_.is_valid()
+ ? td_->file_manager_->get_file_object(sticker->premium_animation_file_id_)
+ : nullptr;
+ return td_api::make_object<td_api::sticker>(
+ sticker->set_id_.get(), width, height, sticker->alt_, get_sticker_format_object(sticker->format_),
+ get_sticker_type_object(sticker->type_), std::move(mask_position), custom_emoji_id.get(),
+ get_sticker_minithumbnail(sticker->minithumbnail_, sticker->set_id_, document_id, zoom),
+ std::move(thumbnail_object), sticker->is_premium_, std::move(premium_animation_object),
+ td_->file_manager_->get_file_object(file_id));
}
-tl_object_ptr<td_api::stickers> StickersManager::get_stickers_object(const vector<FileId> &sticker_ids) {
- auto result = make_tl_object<td_api::stickers>();
- result->stickers_.reserve(sticker_ids.size());
- for (auto sticker_id : sticker_ids) {
- result->stickers_.push_back(get_sticker_object(sticker_id));
+tl_object_ptr<td_api::stickers> StickersManager::get_stickers_object(const vector<FileId> &sticker_ids) const {
+ return td_api::make_object<td_api::stickers>(
+ transform(sticker_ids, [&](FileId sticker_id) { return get_sticker_object(sticker_id); }));
+}
+
+tl_object_ptr<td_api::DiceStickers> StickersManager::get_dice_stickers_object(const string &emoji, int32 value) const {
+ if (td_->auth_manager_->is_bot()) {
+ return nullptr;
}
- return result;
+ if (!td::contains(dice_emojis_, emoji)) {
+ return nullptr;
+ }
+
+ auto it = special_sticker_sets_.find(SpecialStickerSetType::animated_dice(emoji));
+ if (it == special_sticker_sets_.end()) {
+ return nullptr;
+ }
+
+ auto sticker_set_id = it->second->id_;
+ if (!sticker_set_id.is_valid()) {
+ return nullptr;
+ }
+
+ auto sticker_set = get_sticker_set(sticker_set_id);
+ CHECK(sticker_set != nullptr);
+ if (!sticker_set->was_loaded_) {
+ return nullptr;
+ }
+
+ auto get_sticker = [&](int32 value) {
+ return get_sticker_object(sticker_set->sticker_ids_[value], true);
+ };
+
+ if (emoji == "🎰") {
+ if (sticker_set->sticker_ids_.size() < 21 || value < 0 || value > 64) {
+ return nullptr;
+ }
+
+ int32 background_id = value == 1 || value == 22 || value == 43 || value == 64 ? 1 : 0;
+ int32 lever_id = 2;
+ int32 left_reel_id = value == 64 ? 3 : 8;
+ int32 center_reel_id = value == 64 ? 9 : 14;
+ int32 right_reel_id = value == 64 ? 15 : 20;
+ if (value != 0 && value != 64) {
+ left_reel_id = 4 + (value % 4);
+ center_reel_id = 10 + ((value + 3) / 4 % 4);
+ right_reel_id = 16 + ((value + 15) / 16 % 4);
+ }
+ return td_api::make_object<td_api::diceStickersSlotMachine>(get_sticker(background_id), get_sticker(lever_id),
+ get_sticker(left_reel_id), get_sticker(center_reel_id),
+ get_sticker(right_reel_id));
+ }
+
+ if (value >= 0 && value < static_cast<int32>(sticker_set->sticker_ids_.size())) {
+ return td_api::make_object<td_api::diceStickersRegular>(get_sticker(value));
+ }
+ return nullptr;
+}
+
+int32 StickersManager::get_dice_success_animation_frame_number(const string &emoji, int32 value) const {
+ if (td_->auth_manager_->is_bot()) {
+ return std::numeric_limits<int32>::max();
+ }
+ if (value == 0 || !td::contains(dice_emojis_, emoji)) {
+ return std::numeric_limits<int32>::max();
+ }
+ auto pos = static_cast<size_t>(std::find(dice_emojis_.begin(), dice_emojis_.end(), emoji) - dice_emojis_.begin());
+ if (pos >= dice_success_values_.size()) {
+ return std::numeric_limits<int32>::max();
+ }
+
+ auto &result = dice_success_values_[pos];
+ return result.first == value ? result.second : std::numeric_limits<int32>::max();
+}
+
+PhotoFormat StickersManager::get_sticker_set_thumbnail_format(StickerFormat sticker_format) {
+ switch (sticker_format) {
+ case StickerFormat::Unknown:
+ return PhotoFormat::Webp;
+ case StickerFormat::Webp:
+ return PhotoFormat::Webp;
+ case StickerFormat::Tgs:
+ return PhotoFormat::Tgs;
+ case StickerFormat::Webm:
+ return PhotoFormat::Webm;
+ default:
+ UNREACHABLE();
+ return PhotoFormat::Webp;
+ }
+}
+
+double StickersManager::get_sticker_set_minithumbnail_zoom(const StickerSet *sticker_set) {
+ if (sticker_set->sticker_format_ == StickerFormat::Tgs) {
+ return 100.0 / 512.0;
+ }
+ return 1.0;
}
-tl_object_ptr<td_api::stickerSet> StickersManager::get_sticker_set_object(int64 sticker_set_id) {
+td_api::object_ptr<td_api::thumbnail> StickersManager::get_sticker_set_thumbnail_object(
+ const StickerSet *sticker_set) const {
+ CHECK(sticker_set != nullptr);
+ if (sticker_set->thumbnail_document_id_ != 0 && sticker_set->sticker_type_ == StickerType::CustomEmoji) {
+ for (auto sticker_id : sticker_set->sticker_ids_) {
+ auto file_view = td_->file_manager_->get_file_view(sticker_id);
+ if (file_view.has_remote_location() && !file_view.remote_location().is_web() &&
+ file_view.remote_location().get_id() == sticker_set->thumbnail_document_id_) {
+ const Sticker *s = get_sticker(sticker_id);
+ auto thumbnail_format = get_sticker_set_thumbnail_format(s->format_);
+ PhotoSize thumbnail;
+ thumbnail.type = 't';
+ thumbnail.size = static_cast<int32>(file_view.size());
+ thumbnail.dimensions = s->dimensions_;
+ thumbnail.file_id = s->file_id_;
+ return get_thumbnail_object(td_->file_manager_.get(), thumbnail, thumbnail_format);
+ }
+ }
+ }
+ auto thumbnail_format = get_sticker_set_thumbnail_format(sticker_set->sticker_format_);
+ return get_thumbnail_object(td_->file_manager_.get(), sticker_set->thumbnail_, thumbnail_format);
+}
+
+tl_object_ptr<td_api::stickerSet> StickersManager::get_sticker_set_object(StickerSetId sticker_set_id) const {
const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- CHECK(sticker_set->was_loaded);
+ CHECK(sticker_set->was_loaded_);
+ sticker_set->was_update_sent_ = true;
std::vector<tl_object_ptr<td_api::sticker>> stickers;
- std::vector<tl_object_ptr<td_api::stickerEmojis>> emojis;
- for (auto sticker_id : sticker_set->sticker_ids) {
+ std::vector<tl_object_ptr<td_api::emojis>> emojis;
+ for (auto sticker_id : sticker_set->sticker_ids_) {
stickers.push_back(get_sticker_object(sticker_id));
+ vector<string> sticker_emojis;
auto it = sticker_set->sticker_emojis_map_.find(sticker_id);
- if (it == sticker_set->sticker_emojis_map_.end()) {
- emojis.push_back(Auto());
- } else {
- emojis.push_back(make_tl_object<td_api::stickerEmojis>(vector<string>(it->second)));
+ if (it != sticker_set->sticker_emojis_map_.end()) {
+ sticker_emojis = it->second;
}
+ emojis.push_back(make_tl_object<td_api::emojis>(std::move(sticker_emojis)));
}
- return make_tl_object<td_api::stickerSet>(sticker_set->id, sticker_set->title, sticker_set->short_name,
- sticker_set->is_installed && !sticker_set->is_archived,
- sticker_set->is_archived, sticker_set->is_official, sticker_set->is_masks,
- sticker_set->is_viewed, std::move(stickers), std::move(emojis));
+ return make_tl_object<td_api::stickerSet>(
+ sticker_set->id_.get(), sticker_set->title_, sticker_set->short_name_,
+ get_sticker_set_thumbnail_object(sticker_set),
+ get_sticker_minithumbnail(sticker_set->minithumbnail_, sticker_set->id_, -2,
+ get_sticker_set_minithumbnail_zoom(sticker_set)),
+ sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, sticker_set->is_official_,
+ get_sticker_format_object(sticker_set->sticker_format_), get_sticker_type_object(sticker_set->sticker_type_),
+ sticker_set->is_viewed_, std::move(stickers), std::move(emojis));
}
tl_object_ptr<td_api::stickerSets> StickersManager::get_sticker_sets_object(int32 total_count,
- const vector<int64> &sticker_set_ids,
- size_t covers_limit) {
+ const vector<StickerSetId> &sticker_set_ids,
+ size_t covers_limit) const {
vector<tl_object_ptr<td_api::stickerSetInfo>> result;
result.reserve(sticker_set_ids.size());
for (auto sticker_set_id : sticker_set_ids) {
- auto sticker_set_info = get_sticker_set_info_object(sticker_set_id, covers_limit);
+ auto sticker_set_info = get_sticker_set_info_object(sticker_set_id, covers_limit, false);
if (sticker_set_info->size_ != 0) {
result.push_back(std::move(sticker_set_info));
}
@@ -887,29 +2404,252 @@ tl_object_ptr<td_api::stickerSets> StickersManager::get_sticker_sets_object(int3
return make_tl_object<td_api::stickerSets>(total_count, std::move(result));
}
-tl_object_ptr<td_api::stickerSetInfo> StickersManager::get_sticker_set_info_object(int64 sticker_set_id,
- size_t covers_limit) {
+tl_object_ptr<td_api::stickerSetInfo> StickersManager::get_sticker_set_info_object(StickerSetId sticker_set_id,
+ size_t covers_limit,
+ bool prefer_premium) const {
const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- CHECK(sticker_set->is_inited);
-
- std::vector<tl_object_ptr<td_api::sticker>> stickers;
- for (auto sticker_id : sticker_set->sticker_ids) {
- stickers.push_back(get_sticker_object(sticker_id));
- if (stickers.size() >= covers_limit) {
- break;
+ CHECK(sticker_set->is_inited_);
+ sticker_set->was_update_sent_ = true;
+
+ vector<td_api::object_ptr<td_api::sticker>> stickers;
+ if (prefer_premium) {
+ CHECK(!td_->auth_manager_->is_bot());
+ vector<FileId> regular_sticker_ids;
+ vector<FileId> premium_sticker_ids;
+ std::tie(regular_sticker_ids, premium_sticker_ids) = split_stickers_by_premium(sticker_set);
+ auto is_premium = td_->option_manager_->get_option_boolean("is_premium");
+ size_t max_premium_stickers = is_premium ? covers_limit : 1;
+ if (premium_sticker_ids.size() > max_premium_stickers) {
+ premium_sticker_ids.resize(max_premium_stickers);
+ }
+ CHECK(premium_sticker_ids.size() <= covers_limit);
+ if (regular_sticker_ids.size() > covers_limit - premium_sticker_ids.size()) {
+ regular_sticker_ids.resize(covers_limit - premium_sticker_ids.size());
+ }
+ if (!is_premium) {
+ std::swap(premium_sticker_ids, regular_sticker_ids);
+ }
+
+ append(premium_sticker_ids, regular_sticker_ids);
+ for (auto sticker_id : premium_sticker_ids) {
+ stickers.push_back(get_sticker_object(sticker_id));
+ if (stickers.size() >= covers_limit) {
+ break;
+ }
+ }
+ } else {
+ for (auto sticker_id : sticker_set->sticker_ids_) {
+ stickers.push_back(get_sticker_object(sticker_id));
+ if (stickers.size() >= covers_limit) {
+ break;
+ }
}
}
+ auto actual_count = narrow_cast<int32>(sticker_set->sticker_ids_.size());
return make_tl_object<td_api::stickerSetInfo>(
- sticker_set->id, sticker_set->title, sticker_set->short_name,
- sticker_set->is_installed && !sticker_set->is_archived, sticker_set->is_archived, sticker_set->is_official,
- sticker_set->is_masks, sticker_set->is_viewed,
- sticker_set->was_loaded ? narrow_cast<int32>(sticker_set->sticker_ids.size()) : sticker_set->sticker_count,
+ sticker_set->id_.get(), sticker_set->title_, sticker_set->short_name_,
+ get_sticker_set_thumbnail_object(sticker_set),
+ get_sticker_minithumbnail(sticker_set->minithumbnail_, sticker_set->id_, -3,
+ get_sticker_set_minithumbnail_zoom(sticker_set)),
+ sticker_set->is_installed_ && !sticker_set->is_archived_, sticker_set->is_archived_, sticker_set->is_official_,
+ get_sticker_format_object(sticker_set->sticker_format_), get_sticker_type_object(sticker_set->sticker_type_),
+ sticker_set->is_viewed_, sticker_set->was_loaded_ ? actual_count : max(actual_count, sticker_set->sticker_count_),
std::move(stickers));
}
-tl_object_ptr<telegram_api::InputStickerSet> StickersManager::get_input_sticker_set(int64 sticker_set_id) const {
+td_api::object_ptr<td_api::sticker> StickersManager::get_premium_gift_sticker_object(int32 month_count) {
+ auto it = premium_gift_messages_.find(month_count);
+ if (it == premium_gift_messages_.end()) {
+ return get_sticker_object(get_premium_gift_option_sticker_id(month_count));
+ } else {
+ return get_sticker_object(it->second->sticker_id_);
+ }
+}
+
+const StickersManager::StickerSet *StickersManager::get_premium_gift_sticker_set() {
+ if (td_->auth_manager_->is_bot()) {
+ return nullptr;
+ }
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::premium_gifts());
+ if (!special_sticker_set.id_.is_valid()) {
+ load_special_sticker_set(special_sticker_set);
+ return nullptr;
+ }
+
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ CHECK(sticker_set != nullptr);
+ if (!sticker_set->was_loaded_) {
+ load_special_sticker_set(special_sticker_set);
+ return nullptr;
+ }
+
+ return sticker_set;
+}
+
+FileId StickersManager::get_premium_gift_option_sticker_id(const StickerSet *sticker_set, int32 month_count) {
+ if (sticker_set == nullptr || sticker_set->sticker_ids_.empty() || month_count <= 0) {
+ return {};
+ }
+
+ int32 number = [month_count] {
+ switch (month_count) {
+ case 1:
+ return 1;
+ case 3:
+ return 2;
+ case 6:
+ return 3;
+ case 12:
+ return 4;
+ case 24:
+ return 5;
+ default:
+ return -1;
+ }
+ }();
+
+ for (auto sticker_id : sticker_set->sticker_ids_) {
+ auto it = sticker_set->sticker_emojis_map_.find(sticker_id);
+ if (it != sticker_set->sticker_emojis_map_.end()) {
+ for (auto &emoji : it->second) {
+ if (get_emoji_number(emoji) == number) {
+ return sticker_id;
+ }
+ }
+ }
+ }
+
+ // there is no match; return the first sticker
+ return sticker_set->sticker_ids_[0];
+}
+
+FileId StickersManager::get_premium_gift_option_sticker_id(int32 month_count) {
+ return get_premium_gift_option_sticker_id(get_premium_gift_sticker_set(), month_count);
+}
+
+const StickersManager::StickerSet *StickersManager::get_animated_emoji_sticker_set() {
+ if (td_->auth_manager_->is_bot() || disable_animated_emojis_) {
+ return nullptr;
+ }
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji());
+ if (!special_sticker_set.id_.is_valid()) {
+ load_special_sticker_set(special_sticker_set);
+ return nullptr;
+ }
+
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ CHECK(sticker_set != nullptr);
+ if (!sticker_set->was_loaded_) {
+ load_special_sticker_set(special_sticker_set);
+ return nullptr;
+ }
+
+ return sticker_set;
+}
+
+std::pair<FileId, int> StickersManager::get_animated_emoji_sticker(const StickerSet *sticker_set, const string &emoji) {
+ if (sticker_set == nullptr) {
+ return {};
+ }
+
+ auto emoji_without_modifiers = remove_emoji_modifiers(emoji);
+ auto it = sticker_set->emoji_stickers_map_.find(emoji_without_modifiers);
+ if (it == sticker_set->emoji_stickers_map_.end()) {
+ return {};
+ }
+
+ auto emoji_without_selectors = remove_emoji_selectors(emoji);
+ // trying to find full emoji match
+ for (const auto &sticker_id : it->second) {
+ auto emoji_it = sticker_set->sticker_emojis_map_.find(sticker_id);
+ CHECK(emoji_it != sticker_set->sticker_emojis_map_.end());
+ for (auto &sticker_emoji : emoji_it->second) {
+ if (remove_emoji_selectors(sticker_emoji) == emoji_without_selectors) {
+ return {sticker_id, 0};
+ }
+ }
+ }
+
+ // trying to find match without Fitzpatrick modifiers
+ int modifier_id = get_fitzpatrick_modifier(emoji_without_selectors);
+ if (modifier_id > 0) {
+ for (const auto &sticker_id : it->second) {
+ auto emoji_it = sticker_set->sticker_emojis_map_.find(sticker_id);
+ CHECK(emoji_it != sticker_set->sticker_emojis_map_.end());
+ for (auto &sticker_emoji : emoji_it->second) {
+ if (remove_emoji_selectors(sticker_emoji) == Slice(emoji_without_selectors).remove_suffix(4)) {
+ return {sticker_id, modifier_id};
+ }
+ }
+ }
+ }
+
+ // there is no match
+ return {};
+}
+
+std::pair<FileId, int> StickersManager::get_animated_emoji_sticker(const string &emoji) {
+ return get_animated_emoji_sticker(get_animated_emoji_sticker_set(), emoji);
+}
+
+FileId StickersManager::get_animated_emoji_sound_file_id(const string &emoji) const {
+ auto it = emoji_sounds_.find(remove_fitzpatrick_modifier(emoji).str());
+ if (it == emoji_sounds_.end()) {
+ return {};
+ }
+ return it->second;
+}
+
+FileId StickersManager::get_custom_animated_emoji_sticker_id(CustomEmojiId custom_emoji_id) const {
+ if (disable_animated_emojis_) {
+ return {};
+ }
+
+ return custom_emoji_to_sticker_id_.get(custom_emoji_id);
+}
+
+td_api::object_ptr<td_api::animatedEmoji> StickersManager::get_animated_emoji_object(const string &emoji,
+ CustomEmojiId custom_emoji_id) {
+ if (td_->auth_manager_->is_bot() || disable_animated_emojis_) {
+ return nullptr;
+ }
+
+ if (custom_emoji_id.is_valid()) {
+ auto it = custom_emoji_messages_.find(custom_emoji_id);
+ auto sticker_id = it == custom_emoji_messages_.end() ? get_custom_animated_emoji_sticker_id(custom_emoji_id)
+ : it->second->sticker_id_;
+ auto sticker = get_sticker_object(sticker_id, true);
+ auto default_custom_emoji_dimension = static_cast<int32>(512 * animated_emoji_zoom_ + 0.5);
+ auto sticker_width = sticker == nullptr ? default_custom_emoji_dimension : sticker->width_;
+ auto sticker_height = sticker == nullptr ? default_custom_emoji_dimension : sticker->height_;
+ return td_api::make_object<td_api::animatedEmoji>(std::move(sticker), sticker_width, sticker_height, 0, nullptr);
+ }
+
+ auto it = emoji_messages_.find(emoji);
+ if (it == emoji_messages_.end()) {
+ return get_animated_emoji_object(get_animated_emoji_sticker(emoji), get_animated_emoji_sound_file_id(emoji));
+ } else {
+ return get_animated_emoji_object(it->second->animated_emoji_sticker_, it->second->sound_file_id_);
+ }
+}
+
+td_api::object_ptr<td_api::animatedEmoji> StickersManager::get_animated_emoji_object(
+ std::pair<FileId, int> animated_sticker, FileId sound_file_id) const {
+ if (!animated_sticker.first.is_valid()) {
+ return nullptr;
+ }
+ auto sticker = get_sticker_object(animated_sticker.first, true);
+ CHECK(sticker != nullptr);
+ auto sticker_width = sticker->width_;
+ auto sticker_height = sticker->height_;
+ return td_api::make_object<td_api::animatedEmoji>(
+ std::move(sticker), sticker_width, sticker_height, animated_sticker.second,
+ sound_file_id.is_valid() ? td_->file_manager_->get_file_object(sound_file_id) : nullptr);
+}
+
+tl_object_ptr<telegram_api::InputStickerSet> StickersManager::get_input_sticker_set(StickerSetId sticker_set_id) const {
auto sticker_set = get_sticker_set(sticker_set_id);
if (sticker_set == nullptr) {
return nullptr;
@@ -918,61 +2658,160 @@ tl_object_ptr<telegram_api::InputStickerSet> StickersManager::get_input_sticker_
return get_input_sticker_set(sticker_set);
}
-FileId StickersManager::on_get_sticker(std::unique_ptr<Sticker> new_sticker, bool replace) {
- auto file_id = new_sticker->file_id;
- LOG(INFO) << "Receive sticker " << file_id;
- auto &s = stickers_[file_id];
+class StickersManager::CustomEmojiLogEvent {
+ public:
+ FileId sticker_id;
+
+ CustomEmojiLogEvent() = default;
+
+ explicit CustomEmojiLogEvent(FileId sticker_id) : sticker_id(sticker_id) {
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ END_STORE_FLAGS();
+ StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get();
+ stickers_manager->store_sticker(sticker_id, false, storer, "CustomEmoji");
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ END_PARSE_FLAGS();
+ StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get();
+ sticker_id = stickers_manager->parse_sticker(false, parser);
+ }
+};
+
+string StickersManager::get_custom_emoji_database_key(CustomEmojiId custom_emoji_id) {
+ return PSTRING() << "emoji" << custom_emoji_id.get();
+}
+
+FileId StickersManager::on_get_sticker(unique_ptr<Sticker> new_sticker, bool replace) {
+ auto file_id = new_sticker->file_id_;
+ CHECK(file_id.is_valid());
+ CustomEmojiId updated_custom_emoji_id;
+ auto *s = get_sticker(file_id);
if (s == nullptr) {
- s = std::move(new_sticker);
+ s = new_sticker.get();
+ stickers_.set(file_id, std::move(new_sticker));
} else if (replace) {
- CHECK(s->file_id == file_id);
- if (s->dimensions != new_sticker->dimensions && new_sticker->dimensions.width != 0) {
- LOG(DEBUG) << "Sticker " << file_id << " dimensions has changed";
- s->dimensions = new_sticker->dimensions;
- s->is_changed = true;
- }
- if (s->set_id != new_sticker->set_id && new_sticker->set_id != 0) {
- LOG_IF(ERROR, s->set_id != 0) << "Sticker " << file_id << " set_id has changed";
- s->set_id = new_sticker->set_id;
- s->is_changed = true;
- }
- if (s->alt != new_sticker->alt && !new_sticker->alt.empty()) {
- LOG(DEBUG) << "Sticker " << file_id << " emoji has changed";
- s->alt = new_sticker->alt;
- s->is_changed = true;
- }
- if (s->message_thumbnail != new_sticker->message_thumbnail && new_sticker->message_thumbnail.file_id.is_valid()) {
- LOG_IF(INFO, s->message_thumbnail.file_id.is_valid())
- << "Sticker " << file_id << " message thumbnail has changed from " << s->message_thumbnail << " to "
- << new_sticker->message_thumbnail;
- s->message_thumbnail = new_sticker->message_thumbnail;
- s->is_changed = true;
+ CHECK(s->file_id_ == file_id);
+
+ if (s->type_ == StickerType::CustomEmoji) {
+ auto custom_emoji_id = get_custom_emoji_id(file_id);
+ if (custom_emoji_id.is_valid() && custom_emoji_to_sticker_id_.get(custom_emoji_id) == file_id) {
+ custom_emoji_to_sticker_id_.erase(custom_emoji_id);
+ updated_custom_emoji_id = custom_emoji_id;
+ }
}
- if (s->sticker_thumbnail != new_sticker->sticker_thumbnail && new_sticker->sticker_thumbnail.file_id.is_valid()) {
- LOG_IF(INFO, s->sticker_thumbnail.file_id.is_valid())
- << "Sticker " << file_id << " thumbnail has changed from " << s->sticker_thumbnail << " to "
- << new_sticker->sticker_thumbnail;
- s->sticker_thumbnail = new_sticker->sticker_thumbnail;
- s->is_changed = true;
+
+ bool is_changed = false;
+ if (s->dimensions_ != new_sticker->dimensions_ && new_sticker->dimensions_.width != 0) {
+ LOG(DEBUG) << "Sticker " << file_id << " dimensions have changed";
+ s->dimensions_ = new_sticker->dimensions_;
+ is_changed = true;
}
- if (s->is_mask != new_sticker->is_mask && new_sticker->is_mask) {
- s->is_mask = new_sticker->is_mask;
- s->is_changed = true;
+ if (s->set_id_ != new_sticker->set_id_ && new_sticker->set_id_.is_valid()) {
+ LOG_IF(ERROR, s->set_id_.is_valid()) << "Sticker " << file_id << " set_id has changed";
+ s->set_id_ = new_sticker->set_id_;
+ is_changed = true;
}
- if (s->point != new_sticker->point && new_sticker->point != -1) {
- s->point = new_sticker->point;
- s->x_shift = new_sticker->x_shift;
- s->y_shift = new_sticker->y_shift;
- s->scale = new_sticker->scale;
- s->is_changed = true;
+ if (s->alt_ != new_sticker->alt_ && !new_sticker->alt_.empty()) {
+ LOG(DEBUG) << "Sticker " << file_id << " emoji has changed";
+ s->alt_ = std::move(new_sticker->alt_);
+ is_changed = true;
+ }
+ if (s->minithumbnail_ != new_sticker->minithumbnail_) {
+ LOG(DEBUG) << "Sticker " << file_id << " minithumbnail has changed";
+ s->minithumbnail_ = std::move(new_sticker->minithumbnail_);
+ is_changed = true;
+ }
+ if (s->s_thumbnail_ != new_sticker->s_thumbnail_ && new_sticker->s_thumbnail_.file_id.is_valid()) {
+ LOG_IF(INFO, s->s_thumbnail_.file_id.is_valid()) << "Sticker " << file_id << " s thumbnail has changed from "
+ << s->s_thumbnail_ << " to " << new_sticker->s_thumbnail_;
+ s->s_thumbnail_ = std::move(new_sticker->s_thumbnail_);
+ is_changed = true;
+ }
+ if (s->m_thumbnail_ != new_sticker->m_thumbnail_ && new_sticker->m_thumbnail_.file_id.is_valid()) {
+ LOG_IF(INFO, s->m_thumbnail_.file_id.is_valid()) << "Sticker " << file_id << " m thumbnail has changed from "
+ << s->m_thumbnail_ << " to " << new_sticker->m_thumbnail_;
+ s->m_thumbnail_ = std::move(new_sticker->m_thumbnail_);
+ is_changed = true;
+ }
+ s->is_premium_ = new_sticker->is_premium_;
+ s->premium_animation_file_id_ = new_sticker->premium_animation_file_id_;
+ if (s->format_ != new_sticker->format_ && new_sticker->format_ != StickerFormat::Unknown) {
+ s->format_ = new_sticker->format_;
+ is_changed = true;
+ }
+ if (s->type_ != new_sticker->type_ && new_sticker->type_ != StickerType::Regular) {
+ s->type_ = new_sticker->type_;
+ is_changed = true;
+ }
+ if (s->point_ != new_sticker->point_ && new_sticker->point_ != -1) {
+ s->point_ = new_sticker->point_;
+ s->x_shift_ = new_sticker->x_shift_;
+ s->y_shift_ = new_sticker->y_shift_;
+ s->scale_ = new_sticker->scale_;
+ is_changed = true;
+ }
+ if (s->emoji_receive_date_ < new_sticker->emoji_receive_date_) {
+ LOG(DEBUG) << "Update custom emoji file " << file_id << " receive date";
+ s->emoji_receive_date_ = new_sticker->emoji_receive_date_;
+ is_changed = true;
+ }
+
+ if (is_changed) {
+ s->is_from_database_ = false;
+ }
+ }
+
+ if (s->type_ == StickerType::CustomEmoji) {
+ s->is_being_reloaded_ = false;
+ auto custom_emoji_id = get_custom_emoji_id(file_id);
+ if (custom_emoji_id.is_valid()) {
+ custom_emoji_to_sticker_id_.set(custom_emoji_id, file_id);
+ CHECK(updated_custom_emoji_id == custom_emoji_id || !updated_custom_emoji_id.is_valid());
+ updated_custom_emoji_id = custom_emoji_id;
+ if (!s->is_from_database_ && G()->parameters().use_file_db && !G()->close_flag()) {
+ LOG(INFO) << "Save " << custom_emoji_id << " to database";
+ s->is_from_database_ = true;
+
+ CustomEmojiLogEvent log_event(file_id);
+ G()->td_db()->get_sqlite_pmc()->set(get_custom_emoji_database_key(custom_emoji_id),
+ log_event_store(log_event).as_slice().str(), Auto());
+ }
}
}
-
+ if (updated_custom_emoji_id.is_valid()) {
+ try_update_custom_emoji_messages(updated_custom_emoji_id);
+ }
return file_id;
}
+bool StickersManager::has_webp_thumbnail(const vector<tl_object_ptr<telegram_api::PhotoSize>> &thumbnails) {
+ // server tries to always replace user-provided thumbnail with server-side WEBP thumbnail
+ // but there can be some old sticker documents or some big stickers
+ for (auto &size : thumbnails) {
+ switch (size->get_id()) {
+ case telegram_api::photoStrippedSize::ID:
+ case telegram_api::photoSizeProgressive::ID:
+ // WEBP thumbnail can't have stripped size or be progressive
+ return false;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
std::pair<int64, FileId> StickersManager::on_get_sticker_document(tl_object_ptr<telegram_api::Document> &&document_ptr,
- bool from_message) {
+ StickerFormat expected_format) {
+ if (document_ptr == nullptr) {
+ return {};
+ }
int32 document_constructor_id = document_ptr->get_id();
if (document_constructor_id == telegram_api::documentEmpty::ID) {
LOG(ERROR) << "Empty sticker document received";
@@ -985,86 +2824,139 @@ std::pair<int64, FileId> StickersManager::on_get_sticker_document(tl_object_ptr<
LOG(ERROR) << "Wrong dc_id = " << document->dc_id_ << " in document " << to_string(document);
return {};
}
+ auto dc_id = DcId::internal(document->dc_id_);
Dimensions dimensions;
tl_object_ptr<telegram_api::documentAttributeSticker> sticker;
+ tl_object_ptr<telegram_api::documentAttributeCustomEmoji> custom_emoji;
for (auto &attribute : document->attributes_) {
switch (attribute->get_id()) {
+ case telegram_api::documentAttributeVideo::ID: {
+ auto video = move_tl_object_as<telegram_api::documentAttributeVideo>(attribute);
+ dimensions = get_dimensions(video->w_, video->h_, "sticker documentAttributeVideo");
+ break;
+ }
case telegram_api::documentAttributeImageSize::ID: {
auto image_size = move_tl_object_as<telegram_api::documentAttributeImageSize>(attribute);
- dimensions = get_dimensions(image_size->w_, image_size->h_);
+ dimensions = get_dimensions(image_size->w_, image_size->h_, "sticker documentAttributeImageSize");
break;
}
case telegram_api::documentAttributeSticker::ID:
sticker = move_tl_object_as<telegram_api::documentAttributeSticker>(attribute);
break;
+ case telegram_api::documentAttributeCustomEmoji::ID:
+ custom_emoji = move_tl_object_as<telegram_api::documentAttributeCustomEmoji>(attribute);
+ break;
default:
continue;
}
}
- if (sticker == nullptr) {
- LOG(ERROR) << "Have no attributeSticker in sticker " << to_string(document);
+ if (sticker == nullptr && custom_emoji == nullptr) {
+ if (document->mime_type_ != "application/x-bad-tgsticker") {
+ LOG(ERROR) << "Have no attributeSticker in sticker " << to_string(document);
+ }
return {};
}
+ auto format = get_sticker_format_by_mime_type(document->mime_type_);
+ if (format == StickerFormat::Unknown || (expected_format != StickerFormat::Unknown && format != expected_format)) {
+ LOG(ERROR) << "Expected sticker of the type " << expected_format << ", but received of the type " << format;
+ return {};
+ }
int64 document_id = document->id_;
- FileId sticker_id = td_->file_manager_->register_remote(
- FullRemoteFileLocation(FileType::Sticker, document_id, document->access_hash_, DcId::internal(document->dc_id_)),
- FileLocationSource::FromServer, DialogId(), document->size_, 0, to_string(document_id) + ".webp");
-
- PhotoSize thumbnail =
- get_photo_size(td_->file_manager_.get(), FileType::Thumbnail, 0, 0, DialogId(), std::move(document->thumb_));
+ FileId sticker_id =
+ td_->file_manager_->register_remote(FullRemoteFileLocation(FileType::Sticker, document_id, document->access_hash_,
+ dc_id, document->file_reference_.as_slice().str()),
+ FileLocationSource::FromServer, DialogId(), document->size_, 0,
+ PSTRING() << document_id << get_sticker_format_extension(format));
+
+ PhotoSize thumbnail;
+ string minithumbnail;
+ auto thumbnail_format = has_webp_thumbnail(document->thumbs_) ? PhotoFormat::Webp : PhotoFormat::Jpeg;
+ FileId premium_animation_file_id;
+ for (auto &thumb : document->thumbs_) {
+ auto photo_size = get_photo_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 0),
+ document_id, document->access_hash_, document->file_reference_.as_slice().str(),
+ dc_id, DialogId(), std::move(thumb), thumbnail_format);
+ if (photo_size.get_offset() == 0) {
+ if (!thumbnail.file_id.is_valid()) {
+ thumbnail = std::move(photo_size.get<0>());
+ }
+ break;
+ } else {
+ if (thumbnail_format == PhotoFormat::Webp) {
+ minithumbnail = std::move(photo_size.get<1>());
+ }
+ }
+ }
+ for (auto &thumb : document->video_thumbs_) {
+ if (thumb->type_ == "f") {
+ if (!premium_animation_file_id.is_valid()) {
+ premium_animation_file_id =
+ register_photo_size(td_->file_manager_.get(), PhotoSizeSource::thumbnail(FileType::Thumbnail, 'f'),
+ document_id, document->access_hash_, document->file_reference_.as_slice().str(),
+ DialogId(), thumb->size_, dc_id, get_sticker_format_photo_format(format));
+ }
+ }
+ }
- create_sticker(sticker_id, std::move(thumbnail), dimensions, from_message, std::move(sticker), nullptr);
+ create_sticker(sticker_id, premium_animation_file_id, std::move(minithumbnail), std::move(thumbnail), dimensions,
+ std::move(sticker), std::move(custom_emoji), format, nullptr);
return {document_id, sticker_id};
}
StickersManager::Sticker *StickersManager::get_sticker(FileId file_id) {
- auto sticker = stickers_.find(file_id);
- if (sticker == stickers_.end()) {
- return nullptr;
- }
-
- CHECK(sticker->second->file_id == file_id);
- return sticker->second.get();
+ return stickers_.get_pointer(file_id);
}
const StickersManager::Sticker *StickersManager::get_sticker(FileId file_id) const {
- auto sticker = stickers_.find(file_id);
- if (sticker == stickers_.end()) {
- return nullptr;
- }
-
- CHECK(sticker->second->file_id == file_id);
- return sticker->second.get();
+ return stickers_.get_pointer(file_id);
}
-StickersManager::StickerSet *StickersManager::get_sticker_set(int64 sticker_set_id) {
- auto sticker_set = sticker_sets_.find(sticker_set_id);
- if (sticker_set == sticker_sets_.end()) {
- return nullptr;
- }
+StickersManager::StickerSet *StickersManager::get_sticker_set(StickerSetId sticker_set_id) {
+ return sticker_sets_.get_pointer(sticker_set_id);
+}
- return sticker_set->second.get();
+const StickersManager::StickerSet *StickersManager::get_sticker_set(StickerSetId sticker_set_id) const {
+ return sticker_sets_.get_pointer(sticker_set_id);
}
-const StickersManager::StickerSet *StickersManager::get_sticker_set(int64 sticker_set_id) const {
- auto sticker_set = sticker_sets_.find(sticker_set_id);
- if (sticker_set == sticker_sets_.end()) {
- return nullptr;
+StickerSetId StickersManager::get_sticker_set_id(const tl_object_ptr<telegram_api::InputStickerSet> &set_ptr) {
+ CHECK(set_ptr != nullptr);
+ switch (set_ptr->get_id()) {
+ case telegram_api::inputStickerSetEmpty::ID:
+ return StickerSetId();
+ case telegram_api::inputStickerSetID::ID:
+ return StickerSetId(static_cast<const telegram_api::inputStickerSetID *>(set_ptr.get())->id_);
+ case telegram_api::inputStickerSetShortName::ID:
+ LOG(ERROR) << "Receive sticker set by its short name";
+ return search_sticker_set(static_cast<const telegram_api::inputStickerSetShortName *>(set_ptr.get())->short_name_,
+ Auto());
+ case telegram_api::inputStickerSetAnimatedEmoji::ID:
+ case telegram_api::inputStickerSetAnimatedEmojiAnimations::ID:
+ case telegram_api::inputStickerSetPremiumGifts::ID:
+ case telegram_api::inputStickerSetEmojiGenericAnimations::ID:
+ case telegram_api::inputStickerSetEmojiDefaultStatuses::ID:
+ case telegram_api::inputStickerSetEmojiDefaultTopicIcons::ID:
+ LOG(ERROR) << "Receive special sticker set " << to_string(set_ptr);
+ return add_special_sticker_set(SpecialStickerSetType(set_ptr)).id_;
+ case telegram_api::inputStickerSetDice::ID:
+ LOG(ERROR) << "Receive special sticker set " << to_string(set_ptr);
+ return StickerSetId();
+ default:
+ UNREACHABLE();
+ return StickerSetId();
}
-
- return sticker_set->second.get();
}
-int64 StickersManager::add_sticker_set(tl_object_ptr<telegram_api::InputStickerSet> &&set_ptr) {
+StickerSetId StickersManager::add_sticker_set(tl_object_ptr<telegram_api::InputStickerSet> &&set_ptr) {
CHECK(set_ptr != nullptr);
switch (set_ptr->get_id()) {
case telegram_api::inputStickerSetEmpty::ID:
- return 0;
+ return StickerSetId();
case telegram_api::inputStickerSetID::ID: {
auto set = move_tl_object_as<telegram_api::inputStickerSetID>(set_ptr);
- int64 set_id = set->id_;
+ StickerSetId set_id{set->id_};
add_sticker_set(set_id, set->access_hash_);
return set_id;
}
@@ -1073,222 +2965,327 @@ int64 StickersManager::add_sticker_set(tl_object_ptr<telegram_api::InputStickerS
LOG(ERROR) << "Receive sticker set by its short name";
return search_sticker_set(set->short_name_, Auto());
}
+ case telegram_api::inputStickerSetAnimatedEmoji::ID:
+ case telegram_api::inputStickerSetAnimatedEmojiAnimations::ID:
+ case telegram_api::inputStickerSetPremiumGifts::ID:
+ case telegram_api::inputStickerSetEmojiGenericAnimations::ID:
+ case telegram_api::inputStickerSetEmojiDefaultStatuses::ID:
+ case telegram_api::inputStickerSetEmojiDefaultTopicIcons::ID:
+ LOG(ERROR) << "Receive special sticker set " << to_string(set_ptr);
+ return add_special_sticker_set(SpecialStickerSetType(set_ptr)).id_;
+ case telegram_api::inputStickerSetDice::ID:
+ LOG(ERROR) << "Receive special sticker set " << to_string(set_ptr);
+ return StickerSetId();
default:
UNREACHABLE();
- return 0;
+ return StickerSetId();
}
}
-StickersManager::StickerSet *StickersManager::add_sticker_set(int64 sticker_set_id, int64 access_hash) {
- auto &s = sticker_sets_[sticker_set_id];
+StickersManager::StickerSet *StickersManager::add_sticker_set(StickerSetId sticker_set_id, int64 access_hash) {
+ if (!sticker_set_id.is_valid()) {
+ return nullptr;
+ }
+ auto *s = get_sticker_set(sticker_set_id);
if (s == nullptr) {
- s = make_unique<StickerSet>();
+ auto sticker_set = make_unique<StickerSet>();
+ s = sticker_set.get();
- s->id = sticker_set_id;
- s->access_hash = access_hash;
- s->is_changed = false;
+ s->id_ = sticker_set_id;
+ s->access_hash_ = access_hash;
+ s->is_changed_ = false;
+ s->need_save_to_database_ = false;
+
+ sticker_sets_.set(sticker_set_id, std::move(sticker_set));
} else {
- CHECK(s->id == sticker_set_id);
- if (s->access_hash != access_hash) {
- s->access_hash = access_hash;
- s->is_changed = true;
+ CHECK(s->id_ == sticker_set_id);
+ if (s->access_hash_ != access_hash) {
+ LOG(INFO) << "Access hash of " << sticker_set_id << " changed";
+ s->access_hash_ = access_hash;
+ s->need_save_to_database_ = true;
}
}
- return s.get();
+ return s;
}
FileId StickersManager::get_sticker_thumbnail_file_id(FileId file_id) const {
- auto sticker = get_sticker(file_id);
+ auto *sticker = get_sticker(file_id);
CHECK(sticker != nullptr);
- return sticker->message_thumbnail.file_id;
+ return sticker->s_thumbnail_.file_id;
}
void StickersManager::delete_sticker_thumbnail(FileId file_id) {
- auto &sticker = stickers_[file_id];
+ auto *sticker = get_sticker(file_id);
CHECK(sticker != nullptr);
- sticker->message_thumbnail = PhotoSize();
+ sticker->s_thumbnail_ = PhotoSize();
+}
+
+vector<FileId> StickersManager::get_sticker_file_ids(FileId file_id) const {
+ vector<FileId> result;
+ auto sticker = get_sticker(file_id);
+ CHECK(sticker != nullptr);
+ result.push_back(file_id);
+ if (sticker->s_thumbnail_.file_id.is_valid()) {
+ result.push_back(sticker->s_thumbnail_.file_id);
+ }
+ if (sticker->m_thumbnail_.file_id.is_valid()) {
+ result.push_back(sticker->m_thumbnail_.file_id);
+ }
+ if (sticker->premium_animation_file_id_.is_valid()) {
+ result.push_back(sticker->premium_animation_file_id_);
+ }
+ return result;
}
FileId StickersManager::dup_sticker(FileId new_id, FileId old_id) {
const Sticker *old_sticker = get_sticker(old_id);
CHECK(old_sticker != nullptr);
- auto &new_sticker = stickers_[new_id];
- CHECK(!new_sticker);
- new_sticker = std::make_unique<Sticker>(*old_sticker);
- new_sticker->file_id = new_id;
- // there is no reason to dup sticker_thumb
- new_sticker->message_thumbnail.file_id = td_->file_manager_->dup_file_id(new_sticker->message_thumbnail.file_id);
+
+ CHECK(get_sticker(new_id) == nullptr);
+ auto new_sticker = make_unique<Sticker>(*old_sticker);
+ new_sticker->file_id_ = new_id;
+ // there is no reason to dup m_thumbnail and premium_animation_file_id
+ new_sticker->s_thumbnail_.file_id = td_->file_manager_->dup_file_id(new_sticker->s_thumbnail_.file_id, "dup_sticker");
+ stickers_.set(new_id, std::move(new_sticker));
return new_id;
}
-bool StickersManager::merge_stickers(FileId new_id, FileId old_id, bool can_delete_old) {
- if (!old_id.is_valid()) {
- LOG(ERROR) << "Old file id is invalid";
- return true;
- }
+void StickersManager::merge_stickers(FileId new_id, FileId old_id) {
+ CHECK(old_id.is_valid() && new_id.is_valid());
+ CHECK(new_id != old_id);
LOG(INFO) << "Merge stickers " << new_id << " and " << old_id;
const Sticker *old_ = get_sticker(old_id);
CHECK(old_ != nullptr);
- if (old_id == new_id) {
- return old_->is_changed;
- }
- auto new_it = stickers_.find(new_id);
- if (new_it == stickers_.end()) {
- auto &old = stickers_[old_id];
- old->is_changed = true;
- if (!can_delete_old) {
- dup_sticker(new_id, old_id);
- } else {
- old->file_id = new_id;
- stickers_.emplace(new_id, std::move(old));
- }
+ const auto *new_ = get_sticker(new_id);
+ if (new_ == nullptr) {
+ dup_sticker(new_id, old_id);
} else {
- Sticker *new_ = new_it->second.get();
- CHECK(new_ != nullptr);
-
- if (old_->alt != new_->alt || old_->set_id != new_->set_id ||
- (old_->dimensions.width != 0 && old_->dimensions.height != 0 && old_->dimensions != new_->dimensions)) {
- LOG(ERROR) << "Sticker has changed: alt = (" << old_->alt << ", " << new_->alt << "), set_id = (" << old_->set_id
- << ", " << new_->set_id << "), dimensions = (" << old_->dimensions << ", " << new_->dimensions << ")";
+ if (old_->set_id_ == new_->set_id_ &&
+ (old_->alt_ != new_->alt_ || old_->set_id_ != new_->set_id_ ||
+ (!is_sticker_format_vector(old_->format_) && !is_sticker_format_vector(new_->format_) &&
+ old_->dimensions_.width != 0 && old_->dimensions_.height != 0 && old_->dimensions_ != new_->dimensions_))) {
+ LOG(ERROR) << "Sticker has changed: alt = (" << old_->alt_ << ", " << new_->alt_ << "), set_id = ("
+ << old_->set_id_ << ", " << new_->set_id_ << "), dimensions = (" << old_->dimensions_ << ", "
+ << new_->dimensions_ << ")";
}
- new_->is_changed = true;
-
- if (old_->message_thumbnail != new_->message_thumbnail) {
- // LOG_STATUS(td_->file_manager_->merge(new_->message_thumbnail.file_id, old_->message_thumbnail.file_id));
+ if (old_->s_thumbnail_ != new_->s_thumbnail_) {
+ // LOG_STATUS(td_->file_manager_->merge(new_->s_thumbnail_.file_id, old_->s_thumbnail_.file_id));
}
- if (old_->sticker_thumbnail != new_->sticker_thumbnail) {
- // LOG_STATUS(td_->file_manager_->merge(new_->sticker_thumbnail.file_id, old_->sticker_thumbnail.file_id));
+ if (old_->m_thumbnail_ != new_->m_thumbnail_) {
+ // LOG_STATUS(td_->file_manager_->merge(new_->m_thumbnail_.file_id, old_->m_thumbnail_.file_id));
}
}
LOG_STATUS(td_->file_manager_->merge(new_id, old_id));
- if (can_delete_old) {
- stickers_.erase(old_id);
- }
- return true;
}
tl_object_ptr<telegram_api::InputStickerSet> StickersManager::get_input_sticker_set(const StickerSet *set) {
CHECK(set != nullptr);
- return make_tl_object<telegram_api::inputStickerSetID>(set->id, set->access_hash);
+ return make_tl_object<telegram_api::inputStickerSetID>(set->id_.get(), set->access_hash_);
}
-void StickersManager::reload_installed_sticker_sets(bool is_masks, bool force) {
- auto &next_load_time = next_installed_sticker_sets_load_time_[is_masks];
+void StickersManager::reload_installed_sticker_sets(StickerType sticker_type, bool force) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto type = static_cast<int32>(sticker_type);
+ auto &next_load_time = next_installed_sticker_sets_load_time_[type];
if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) {
LOG_IF(INFO, force) << "Reload sticker sets";
next_load_time = -1;
- td_->create_handler<GetAllStickersQuery>()->send(is_masks, installed_sticker_sets_hash_[is_masks]);
+ td_->create_handler<GetAllStickersQuery>()->send(sticker_type, installed_sticker_sets_hash_[type]);
+ }
+}
+
+void StickersManager::reload_featured_sticker_sets(StickerType sticker_type, bool force) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto type = static_cast<int32>(sticker_type);
+ auto &next_load_time = next_featured_sticker_sets_load_time_[type];
+ if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) {
+ LOG_IF(INFO, force) << "Reload trending sticker sets";
+ next_load_time = -1;
+ td_->create_handler<GetFeaturedStickerSetsQuery>()->send(sticker_type, featured_sticker_sets_hash_[type]);
}
}
-void StickersManager::reload_featured_sticker_sets(bool force) {
- if (!td_->auth_manager_->is_bot() && next_featured_sticker_sets_load_time_ >= 0 &&
- (next_featured_sticker_sets_load_time_ < Time::now() || force)) {
- LOG_IF(INFO, force) << "Reload featured sticker sets";
- next_featured_sticker_sets_load_time_ = -1;
- td_->create_handler<GetFeaturedStickerSetsQuery>()->send(featured_sticker_sets_hash_);
+void StickersManager::reload_old_featured_sticker_sets(StickerType sticker_type, uint32 generation) {
+ if (sticker_type != StickerType::Regular) {
+ return;
}
+ auto type = static_cast<int32>(sticker_type);
+ if (generation != 0 && generation != old_featured_sticker_set_generation_[type]) {
+ return;
+ }
+ td_->create_handler<GetOldFeaturedStickerSetsQuery>()->send(
+ sticker_type, static_cast<int32>(old_featured_sticker_set_ids_[type].size()), OLD_FEATURED_STICKER_SET_SLICE_SIZE,
+ old_featured_sticker_set_generation_[type]);
}
-int64 StickersManager::on_get_input_sticker_set(FileId sticker_file_id,
- tl_object_ptr<telegram_api::InputStickerSet> &&set_ptr,
- MultiPromiseActor *load_data_multipromise_ptr) {
+StickerSetId StickersManager::on_get_input_sticker_set(FileId sticker_file_id,
+ tl_object_ptr<telegram_api::InputStickerSet> &&set_ptr,
+ MultiPromiseActor *load_data_multipromise_ptr) {
if (set_ptr == nullptr) {
- return 0;
+ return StickerSetId();
}
switch (set_ptr->get_id()) {
case telegram_api::inputStickerSetEmpty::ID:
- return 0;
+ return StickerSetId();
case telegram_api::inputStickerSetID::ID: {
auto set = move_tl_object_as<telegram_api::inputStickerSetID>(set_ptr);
- int64 set_id = set->id_;
+ StickerSetId set_id{set->id_};
add_sticker_set(set_id, set->access_hash_);
return set_id;
}
case telegram_api::inputStickerSetShortName::ID: {
auto set = move_tl_object_as<telegram_api::inputStickerSetShortName>(set_ptr);
if (load_data_multipromise_ptr == nullptr) {
- LOG(ERROR) << "Receive sticker set by its short name";
+ LOG(ERROR) << "Receive sticker set " << set->short_name_ << " by its short name";
return search_sticker_set(set->short_name_, Auto());
}
auto set_id = search_sticker_set(set->short_name_, load_data_multipromise_ptr->get_promise());
- if (set_id == 0) {
- load_data_multipromise_ptr->add_promise(
- PromiseCreator::lambda([td = td_, sticker_file_id, short_name = set->short_name_](Result<Unit> result) {
+ if (!set_id.is_valid()) {
+ load_data_multipromise_ptr->add_promise(PromiseCreator::lambda(
+ [actor_id = actor_id(this), sticker_file_id, short_name = set->short_name_](Result<Unit> result) {
if (result.is_ok()) {
// just in case
- td->stickers_manager_->on_resolve_sticker_set_short_name(sticker_file_id, short_name);
+ send_closure(actor_id, &StickersManager::on_resolve_sticker_set_short_name, sticker_file_id,
+ short_name);
}
}));
}
- return set_id;
- }
+ // always return empty StickerSetId, because we can't trust the set_id provided by the peer in the secret chat
+ // the real sticker set id will be set in on_get_sticker if and only if the sticker is really from the set
+ return StickerSetId();
+ }
+ case telegram_api::inputStickerSetAnimatedEmoji::ID:
+ case telegram_api::inputStickerSetAnimatedEmojiAnimations::ID:
+ case telegram_api::inputStickerSetPremiumGifts::ID:
+ case telegram_api::inputStickerSetEmojiGenericAnimations::ID:
+ case telegram_api::inputStickerSetEmojiDefaultStatuses::ID:
+ case telegram_api::inputStickerSetEmojiDefaultTopicIcons::ID:
+ return add_special_sticker_set(SpecialStickerSetType(set_ptr)).id_;
+ case telegram_api::inputStickerSetDice::ID:
+ return StickerSetId();
default:
UNREACHABLE();
- return 0;
+ return StickerSetId();
}
}
void StickersManager::on_resolve_sticker_set_short_name(FileId sticker_file_id, const string &short_name) {
+ if (G()->close_flag()) {
+ return;
+ }
+
LOG(INFO) << "Resolve sticker " << sticker_file_id << " set to " << short_name;
- int64 set_id = search_sticker_set(short_name, Auto());
- if (set_id != 0) {
- auto &s = stickers_[sticker_file_id];
- if (s == nullptr) {
- LOG(ERROR) << "Can't find sticker " << sticker_file_id;
- }
- CHECK(s->file_id == sticker_file_id);
- if (s->set_id != set_id) {
- s->set_id = set_id;
- s->is_changed = true;
+ StickerSetId set_id = search_sticker_set(short_name, Auto());
+ if (set_id.is_valid()) {
+ auto *s = get_sticker(sticker_file_id);
+ CHECK(s != nullptr);
+ if (s->set_id_ != set_id) {
+ s->set_id_ = set_id;
}
}
}
-void StickersManager::create_sticker(FileId file_id, PhotoSize thumbnail, Dimensions dimensions, bool from_message,
+void StickersManager::add_sticker_thumbnail(Sticker *s, PhotoSize thumbnail) {
+ if (!thumbnail.file_id.is_valid()) {
+ return;
+ }
+ if (thumbnail.type == 'm') {
+ s->m_thumbnail_ = std::move(thumbnail);
+ return;
+ }
+ if (thumbnail.type == 's' || thumbnail.type == 't') {
+ s->s_thumbnail_ = std::move(thumbnail);
+ return;
+ }
+ LOG(ERROR) << "Receive sticker thumbnail of unsupported type " << thumbnail.type;
+}
+
+void StickersManager::create_sticker(FileId file_id, FileId premium_animation_file_id, string minithumbnail,
+ PhotoSize thumbnail, Dimensions dimensions,
tl_object_ptr<telegram_api::documentAttributeSticker> sticker,
- MultiPromiseActor *load_data_multipromise_ptr) {
+ tl_object_ptr<telegram_api::documentAttributeCustomEmoji> custom_emoji,
+ StickerFormat format, MultiPromiseActor *load_data_multipromise_ptr) {
+ if (format == StickerFormat::Unknown && sticker == nullptr) {
+ auto old_sticker = get_sticker(file_id);
+ if (old_sticker != nullptr) {
+ format = old_sticker->format_;
+ } else {
+ // guess format by file extension
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ auto suggested_path = file_view.suggested_path();
+ const PathView path_view(suggested_path);
+ format = get_sticker_format_by_extension(path_view.extension());
+ if (format == StickerFormat::Unknown) {
+ format = StickerFormat::Webp;
+ }
+ }
+ }
+ if (is_sticker_format_vector(format) && dimensions.width == 0) {
+ dimensions.width = custom_emoji != nullptr ? 100 : 512;
+ dimensions.height = custom_emoji != nullptr ? 100 : 512;
+ }
+
auto s = make_unique<Sticker>();
- s->file_id = file_id;
- s->dimensions = dimensions;
- if (from_message) {
- s->message_thumbnail = std::move(thumbnail);
- } else {
- s->sticker_thumbnail = std::move(thumbnail);
+ s->file_id_ = file_id;
+ s->dimensions_ = dimensions;
+ if (!td_->auth_manager_->is_bot()) {
+ s->minithumbnail_ = std::move(minithumbnail);
+ }
+ add_sticker_thumbnail(s.get(), std::move(thumbnail));
+ if (premium_animation_file_id.is_valid()) {
+ s->is_premium_ = true;
}
+ s->premium_animation_file_id_ = premium_animation_file_id;
if (sticker != nullptr) {
- s->set_id = on_get_input_sticker_set(file_id, std::move(sticker->stickerset_), load_data_multipromise_ptr);
- s->alt = std::move(sticker->alt_);
+ s->set_id_ = on_get_input_sticker_set(file_id, std::move(sticker->stickerset_), load_data_multipromise_ptr);
+ s->alt_ = std::move(sticker->alt_);
- s->is_mask = (sticker->flags_ & telegram_api::documentAttributeSticker::MASK_MASK) != 0;
+ if ((sticker->flags_ & telegram_api::documentAttributeSticker::MASK_MASK) != 0) {
+ s->type_ = StickerType::Mask;
+ }
if ((sticker->flags_ & telegram_api::documentAttributeSticker::MASK_COORDS_MASK) != 0) {
CHECK(sticker->mask_coords_ != nullptr);
int32 point = sticker->mask_coords_->n_;
if (0 <= point && point <= 3) {
- s->point = sticker->mask_coords_->n_;
- s->x_shift = sticker->mask_coords_->x_;
- s->y_shift = sticker->mask_coords_->y_;
- s->scale = sticker->mask_coords_->zoom_;
+ s->point_ = sticker->mask_coords_->n_;
+ s->x_shift_ = sticker->mask_coords_->x_;
+ s->y_shift_ = sticker->mask_coords_->y_;
+ s->scale_ = sticker->mask_coords_->zoom_;
}
}
+ } else if (custom_emoji != nullptr) {
+ s->set_id_ = on_get_input_sticker_set(file_id, std::move(custom_emoji->stickerset_), load_data_multipromise_ptr);
+ s->alt_ = std::move(custom_emoji->alt_);
+ s->type_ = StickerType::CustomEmoji;
+ s->is_premium_ = !custom_emoji->free_;
+ s->emoji_receive_date_ = G()->unix_time();
}
- on_get_sticker(std::move(s), sticker != nullptr);
+ s->format_ = format;
+ on_get_sticker(std::move(s),
+ (sticker != nullptr || custom_emoji != nullptr) && load_data_multipromise_ptr == nullptr);
}
bool StickersManager::has_input_media(FileId sticker_file_id, bool is_secret) const {
- const Sticker *sticker = get_sticker(sticker_file_id);
- CHECK(sticker != nullptr);
auto file_view = td_->file_manager_->get_file_view(sticker_file_id);
if (is_secret) {
- if (file_view.is_encrypted()) {
- if (file_view.has_remote_location() && !sticker->message_thumbnail.file_id.is_valid()) {
+ const Sticker *sticker = get_sticker(sticker_file_id);
+ CHECK(sticker != nullptr);
+ if (file_view.is_encrypted_secret()) {
+ if (!file_view.encryption_key().empty() && file_view.has_remote_location() &&
+ !sticker->s_thumbnail_.file_id.is_valid()) {
return true;
}
- } else {
- if (sticker->set_id != 0) {
+ } else if (!file_view.is_encrypted()) {
+ if (sticker->set_id_.is_valid()) {
// stickers within a set can be sent by id and access_hash
return true;
}
@@ -1297,7 +3294,19 @@ bool StickersManager::has_input_media(FileId sticker_file_id, bool is_secret) co
if (file_view.is_encrypted()) {
return false;
}
- if (file_view.has_remote_location() || file_view.has_url()) {
+ if (td_->auth_manager_->is_bot() && file_view.has_remote_location()) {
+ return true;
+ }
+ const Sticker *sticker = get_sticker(sticker_file_id);
+ CHECK(sticker != nullptr);
+ if (sticker->set_id_.is_valid()) {
+ // stickers within a set doesn't need to be duped
+ return true;
+ }
+ // having remote location is not enough to have InputMedia, because the file may not have valid file_reference
+ // also file_id needs to be duped, because upload can be called to repair the file_reference and every upload
+ // request must have unique file_id
+ if (/* file_view.has_remote_location() || */ file_view.has_url()) {
return true;
}
}
@@ -1307,310 +3316,475 @@ bool StickersManager::has_input_media(FileId sticker_file_id, bool is_secret) co
SecretInputMedia StickersManager::get_secret_input_media(FileId sticker_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- BufferSlice thumbnail) const {
+ BufferSlice thumbnail, int32 layer) const {
const Sticker *sticker = get_sticker(sticker_file_id);
CHECK(sticker != nullptr);
auto file_view = td_->file_manager_->get_file_view(sticker_file_id);
- if (file_view.is_encrypted()) {
+ if (file_view.is_encrypted_secret()) {
if (file_view.has_remote_location()) {
- input_file = file_view.remote_location().as_input_encrypted_file();
+ input_file = file_view.main_remote_location().as_input_encrypted_file();
}
if (!input_file) {
return {};
}
- if (sticker->message_thumbnail.file_id.is_valid() && thumbnail.empty()) {
+ if (sticker->s_thumbnail_.file_id.is_valid() && thumbnail.empty()) {
return {};
}
- } else {
- if (sticker->set_id == 0) {
+ } else if (!file_view.is_encrypted()) {
+ if (!sticker->set_id_.is_valid()) {
// stickers without set can't be sent by id and access_hash
return {};
}
+ } else {
+ return {};
}
- vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
tl_object_ptr<secret_api::InputStickerSet> input_sticker_set = make_tl_object<secret_api::inputStickerSetEmpty>();
- if (sticker->set_id) {
- const StickerSet *sticker_set = get_sticker_set(sticker->set_id);
+ if (sticker->set_id_.is_valid()) {
+ const StickerSet *sticker_set = get_sticker_set(sticker->set_id_);
CHECK(sticker_set != nullptr);
- if (sticker_set->is_inited) {
- input_sticker_set = make_tl_object<secret_api::inputStickerSetShortName>(sticker_set->short_name);
+ if (sticker_set->is_inited_) {
+ input_sticker_set = make_tl_object<secret_api::inputStickerSetShortName>(sticker_set->short_name_);
} else {
// TODO load sticker set
}
}
- attributes.push_back(
- make_tl_object<secret_api::documentAttributeSticker>(sticker->alt, std::move(input_sticker_set)));
-
- if (sticker->dimensions.width != 0 && sticker->dimensions.height != 0) {
- attributes.push_back(
- make_tl_object<secret_api::documentAttributeImageSize>(sticker->dimensions.width, sticker->dimensions.height));
- }
- if (file_view.is_encrypted()) {
- auto &encryption_key = file_view.encryption_key();
- return SecretInputMedia{std::move(input_file),
- make_tl_object<secret_api::decryptedMessageMediaDocument>(
- std::move(thumbnail), sticker->message_thumbnail.dimensions.width,
- sticker->message_thumbnail.dimensions.height, "image/webp",
- narrow_cast<int32>(file_view.size()), BufferSlice(encryption_key.key_slice()),
- BufferSlice(encryption_key.iv_slice()), std::move(attributes), "")};
+ vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
+ attributes.push_back(
+ secret_api::make_object<secret_api::documentAttributeSticker>(sticker->alt_, std::move(input_sticker_set)));
+ if (sticker->dimensions_.width != 0 && sticker->dimensions_.height != 0) {
+ attributes.push_back(secret_api::make_object<secret_api::documentAttributeImageSize>(sticker->dimensions_.width,
+ sticker->dimensions_.height));
+ }
+
+ if (file_view.is_encrypted_secret()) {
+ return {std::move(input_file),
+ std::move(thumbnail),
+ sticker->s_thumbnail_.dimensions,
+ get_sticker_format_mime_type(sticker->format_),
+ file_view,
+ std::move(attributes),
+ string(),
+ layer};
} else {
+ CHECK(!file_view.is_encrypted());
auto &remote_location = file_view.remote_location();
- CHECK(!remote_location.is_web()); // web stickers shouldn't have set_id
- return SecretInputMedia{nullptr,
- make_tl_object<secret_api::decryptedMessageMediaExternalDocument>(
- remote_location.get_id(), remote_location.get_access_hash(), 0 /*date*/, "image/webp",
- narrow_cast<int32>(file_view.size()), make_tl_object<secret_api::photoSizeEmpty>(),
- remote_location.get_dc_id().get_raw_id(), std::move(attributes))};
+ if (remote_location.is_web()) {
+ // web stickers shouldn't have set_id
+ LOG(ERROR) << "Have a web sticker in " << sticker->set_id_;
+ return {};
+ }
+ if (file_view.size() > 1000000000) {
+ LOG(ERROR) << "Have a sticker of size " << file_view.size() << " in " << sticker->set_id_;
+ return {};
+ }
+ return SecretInputMedia{
+ nullptr, make_tl_object<secret_api::decryptedMessageMediaExternalDocument>(
+ remote_location.get_id(), remote_location.get_access_hash(), 0 /*date*/,
+ get_sticker_format_mime_type(sticker->format_), narrow_cast<int32>(file_view.size()),
+ make_tl_object<secret_api::photoSizeEmpty>("t"), remote_location.get_dc_id().get_raw_id(),
+ std::move(attributes))};
}
}
tl_object_ptr<telegram_api::InputMedia> StickersManager::get_input_media(
FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file,
- tl_object_ptr<telegram_api::InputFile> input_thumbnail) const {
+ tl_object_ptr<telegram_api::InputFile> input_thumbnail, const string &emoji) const {
auto file_view = td_->file_manager_->get_file_view(file_id);
if (file_view.is_encrypted()) {
return nullptr;
}
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
- return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.remote_location().as_input_document(), 0);
+ if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
+ int32 flags = 0;
+ if (!emoji.empty()) {
+ flags |= telegram_api::inputMediaDocument::QUERY_MASK;
+ }
+ return make_tl_object<telegram_api::inputMediaDocument>(flags, file_view.main_remote_location().as_input_document(),
+ 0, emoji);
}
if (file_view.has_url()) {
return make_tl_object<telegram_api::inputMediaDocumentExternal>(0, file_view.url(), 0);
}
- CHECK(!file_view.has_remote_location());
if (input_file != nullptr) {
const Sticker *s = get_sticker(file_id);
CHECK(s != nullptr);
vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
- if (s->dimensions.width != 0 && s->dimensions.height != 0) {
+ if (s->dimensions_.width != 0 && s->dimensions_.height != 0) {
attributes.push_back(
- make_tl_object<telegram_api::documentAttributeImageSize>(s->dimensions.width, s->dimensions.height));
+ make_tl_object<telegram_api::documentAttributeImageSize>(s->dimensions_.width, s->dimensions_.height));
}
attributes.push_back(make_tl_object<telegram_api::documentAttributeSticker>(
- 0, false /*ignored*/, s->alt, make_tl_object<telegram_api::inputStickerSetEmpty>(), nullptr));
+ 0, false /*ignored*/, s->alt_, make_tl_object<telegram_api::inputStickerSetEmpty>(), nullptr));
int32 flags = 0;
if (input_thumbnail != nullptr) {
flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK;
}
+ auto mime_type = get_sticker_format_mime_type(s->format_);
return make_tl_object<telegram_api::inputMediaUploadedDocument>(
- flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), "image/webp",
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type,
std::move(attributes), vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ } else {
+ CHECK(!file_view.has_remote_location());
}
return nullptr;
}
-int64 StickersManager::on_get_sticker_set(tl_object_ptr<telegram_api::stickerSet> &&set, bool is_changed) {
- int64 set_id = set->id_;
+StickerSetId StickersManager::on_get_sticker_set(tl_object_ptr<telegram_api::stickerSet> &&set, bool is_changed,
+ const char *source) {
+ CHECK(set != nullptr);
+ StickerSetId set_id{set->id_};
StickerSet *s = add_sticker_set(set_id, set->access_hash_);
+ if (s == nullptr) {
+ return {};
+ }
bool is_installed = (set->flags_ & telegram_api::stickerSet::INSTALLED_DATE_MASK) != 0;
- bool is_archived = (set->flags_ & telegram_api::stickerSet::ARCHIVED_MASK) != 0;
- bool is_official = (set->flags_ & telegram_api::stickerSet::OFFICIAL_MASK) != 0;
- bool is_masks = (set->flags_ & telegram_api::stickerSet::MASKS_MASK) != 0;
-
- if (!s->is_inited) {
- s->is_inited = true;
- s->title = std::move(set->title_);
- s->short_name = std::move(set->short_name_);
- s->sticker_count = set->count_;
- s->hash = set->hash_;
- s->is_official = is_official;
- s->is_masks = is_masks;
- s->is_changed = true;
+ bool is_archived = set->archived_;
+ bool is_official = set->official_;
+ StickerFormat sticker_format =
+ set->videos_ ? StickerFormat::Webm : (set->animated_ ? StickerFormat::Tgs : StickerFormat::Webp);
+ StickerType sticker_type =
+ set->emojis_ ? StickerType::CustomEmoji : (set->masks_ ? StickerType::Mask : StickerType::Regular);
+
+ PhotoSize thumbnail;
+ string minithumbnail;
+ int64 thumbnail_document_id = 0;
+ for (auto &thumb : set->thumbs_) {
+ auto photo_size =
+ get_photo_size(td_->file_manager_.get(),
+ PhotoSizeSource::sticker_set_thumbnail(set_id.get(), s->access_hash_, set->thumb_version_), 0, 0,
+ "", DcId::create(set->thumb_dc_id_), DialogId(), std::move(thumb),
+ get_sticker_set_thumbnail_format(sticker_format));
+ if (photo_size.get_offset() == 0) {
+ if (!thumbnail.file_id.is_valid()) {
+ thumbnail = std::move(photo_size.get<0>());
+ }
+ } else {
+ minithumbnail = std::move(photo_size.get<1>());
+ }
+ }
+ if ((set->flags_ & telegram_api::stickerSet::THUMB_DOCUMENT_ID_MASK) != 0) {
+ thumbnail_document_id = set->thumb_document_id_;
+ }
+ if (!s->is_inited_) {
+ LOG(INFO) << "Init " << set_id;
+ s->is_inited_ = true;
+ s->title_ = std::move(set->title_);
+ s->short_name_ = std::move(set->short_name_);
+ if (!td_->auth_manager_->is_bot()) {
+ s->minithumbnail_ = std::move(minithumbnail);
+ }
+ s->thumbnail_ = std::move(thumbnail);
+ s->thumbnail_document_id_ = thumbnail_document_id;
+ s->is_thumbnail_reloaded_ = true;
+ s->are_legacy_sticker_thumbnails_reloaded_ = true;
+ s->sticker_count_ = set->count_;
+ s->hash_ = set->hash_;
+ s->is_official_ = is_official;
+ s->sticker_format_ = sticker_format;
+ s->sticker_type_ = sticker_type;
+ s->is_changed_ = true;
} else {
- CHECK(s->id == set_id);
- if (s->access_hash != set->access_hash_) {
- LOG(INFO) << "Sticker set " << set_id << " access hash has changed";
- s->access_hash = set->access_hash_;
- s->is_changed = true;
- }
- if (s->title != set->title_) {
- LOG(INFO) << "Sticker set " << set_id << " title has changed";
- s->title = std::move(set->title_);
- s->is_changed = true;
-
- if (installed_sticker_sets_hints_[s->is_masks].has_key(set_id)) {
- installed_sticker_sets_hints_[s->is_masks].add(set_id, s->title + " " + s->short_name);
+ CHECK(s->id_ == set_id);
+ auto type = static_cast<int32>(s->sticker_type_);
+ if (s->access_hash_ != set->access_hash_) {
+ LOG(INFO) << "Access hash of " << set_id << " has changed";
+ s->access_hash_ = set->access_hash_;
+ s->need_save_to_database_ = true;
+ }
+ if (s->title_ != set->title_) {
+ LOG(INFO) << "Title of " << set_id << " has changed";
+ s->title_ = std::move(set->title_);
+ s->is_changed_ = true;
+
+ if (installed_sticker_sets_hints_[type].has_key(set_id.get())) {
+ installed_sticker_sets_hints_[type].add(set_id.get(), PSLICE() << s->title_ << ' ' << s->short_name_);
}
}
- if (s->short_name != set->short_name_) {
- LOG(ERROR) << "Sticker set " << set_id << " short name has changed from \"" << s->short_name << "\" to \""
- << set->short_name_ << "\"";
- short_name_to_sticker_set_id_.erase(clean_username(s->short_name));
- s->short_name = std::move(set->short_name_);
- s->is_changed = true;
+ if (s->short_name_ != set->short_name_) {
+ LOG(ERROR) << "Short name of " << set_id << " has changed from \"" << s->short_name_ << "\" to \""
+ << set->short_name_ << "\" from " << source;
+ short_name_to_sticker_set_id_.erase(clean_username(s->short_name_));
+ s->short_name_ = std::move(set->short_name_);
+ s->is_changed_ = true;
- if (installed_sticker_sets_hints_[s->is_masks].has_key(set_id)) {
- installed_sticker_sets_hints_[s->is_masks].add(set_id, s->title + " " + s->short_name);
+ if (installed_sticker_sets_hints_[type].has_key(set_id.get())) {
+ installed_sticker_sets_hints_[type].add(set_id.get(), PSLICE() << s->title_ << ' ' << s->short_name_);
}
}
-
- if (s->sticker_count != set->count_ || s->hash != set->hash_) {
- s->is_loaded = false;
-
- s->sticker_count = set->count_;
- s->hash = set->hash_;
- s->is_changed = true;
+ if (s->minithumbnail_ != minithumbnail) {
+ LOG(INFO) << "Minithumbnail of " << set_id << " has changed";
+ s->minithumbnail_ = std::move(minithumbnail);
+ s->is_changed_ = true;
+ }
+ if (s->thumbnail_ != thumbnail) {
+ LOG(INFO) << "Thumbnail of " << set_id << " has changed from " << s->thumbnail_ << " to " << thumbnail;
+ s->thumbnail_ = std::move(thumbnail);
+ s->is_changed_ = true;
+ }
+ if (s->thumbnail_document_id_ != thumbnail_document_id) {
+ LOG(INFO) << "Thumbnail of " << set_id << " has changed from " << s->thumbnail_document_id_ << " to "
+ << thumbnail_document_id;
+ s->thumbnail_document_id_ = thumbnail_document_id;
+ s->is_changed_ = true;
+ }
+ if (!s->is_thumbnail_reloaded_ || !s->are_legacy_sticker_thumbnails_reloaded_) {
+ LOG(INFO) << "Sticker thumbnails and thumbnail of " << set_id << " was reloaded";
+ s->is_thumbnail_reloaded_ = true;
+ s->are_legacy_sticker_thumbnails_reloaded_ = true;
+ s->need_save_to_database_ = true;
+ }
+
+ if (s->sticker_count_ != set->count_ || s->hash_ != set->hash_) {
+ LOG(INFO) << "Number of stickers in " << set_id << " changed from " << s->sticker_count_ << " to " << set->count_;
+ s->is_loaded_ = false;
+
+ s->sticker_count_ = set->count_;
+ s->hash_ = set->hash_;
+ if (s->was_loaded_) {
+ s->need_save_to_database_ = true;
+ } else {
+ s->is_changed_ = true;
+ }
}
- if (s->is_official != is_official) {
- s->is_official = is_official;
- s->is_changed = true;
+ if (s->is_official_ != is_official) {
+ LOG(INFO) << "Official flag of " << set_id << " changed to " << is_official;
+ s->is_official_ = is_official;
+ s->is_changed_ = true;
+ }
+ if (s->sticker_format_ != sticker_format) {
+ LOG(ERROR) << "Format of stickers in " << set_id << "/" << s->short_name_ << " has changed from "
+ << s->sticker_format_ << " to " << sticker_format << " from " << source;
+ s->sticker_format_ = sticker_format;
+ s->is_changed_ = true;
}
- LOG_IF(ERROR, s->is_masks != is_masks) << "Type of the sticker set " << set_id << " has changed";
+ LOG_IF(ERROR, s->sticker_type_ != sticker_type)
+ << "Type of " << set_id << "/" << s->short_name_ << " has changed from " << s->sticker_type_ << " to "
+ << sticker_type << " from " << source;
+ }
+ auto cleaned_username = clean_username(s->short_name_);
+ if (!cleaned_username.empty()) {
+ short_name_to_sticker_set_id_.set(cleaned_username, set_id);
}
- short_name_to_sticker_set_id_.emplace(clean_username(s->short_name), set_id);
on_update_sticker_set(s, is_installed, is_archived, is_changed);
return set_id;
}
-int64 StickersManager::on_get_sticker_set_covered(tl_object_ptr<telegram_api::StickerSetCovered> &&set_ptr,
- bool is_changed) {
- int64 set_id = 0;
+StickerSetId StickersManager::on_get_sticker_set_covered(tl_object_ptr<telegram_api::StickerSetCovered> &&set_ptr,
+ bool is_changed, const char *source) {
+ StickerSetId set_id;
switch (set_ptr->get_id()) {
case telegram_api::stickerSetCovered::ID: {
auto covered_set = move_tl_object_as<telegram_api::stickerSetCovered>(set_ptr);
- set_id = on_get_sticker_set(std::move(covered_set->set_), is_changed);
- if (set_id == 0) {
+ set_id = on_get_sticker_set(std::move(covered_set->set_), is_changed, source);
+ if (!set_id.is_valid()) {
break;
}
auto sticker_set = get_sticker_set(set_id);
CHECK(sticker_set != nullptr);
- CHECK(sticker_set->is_inited);
- if (sticker_set->was_loaded) {
+ CHECK(sticker_set->is_inited_);
+ if (sticker_set->was_loaded_) {
break;
}
- if (sticker_set->sticker_count == 0) {
+ if (sticker_set->sticker_count_ == 0) {
break;
}
- auto &sticker_ids = sticker_set->sticker_ids;
+ auto &sticker_ids = sticker_set->sticker_ids_;
- auto sticker_id = on_get_sticker_document(std::move(covered_set->cover_), true).second;
- if (sticker_id.is_valid() && std::find(sticker_ids.begin(), sticker_ids.end(), sticker_id) == sticker_ids.end()) {
+ auto sticker_id = on_get_sticker_document(std::move(covered_set->cover_), sticker_set->sticker_format_).second;
+ if (sticker_id.is_valid() && !td::contains(sticker_ids, sticker_id)) {
sticker_ids.push_back(sticker_id);
- sticker_set->is_changed = true;
+ sticker_set->is_changed_ = true;
}
break;
}
case telegram_api::stickerSetMultiCovered::ID: {
auto multicovered_set = move_tl_object_as<telegram_api::stickerSetMultiCovered>(set_ptr);
- set_id = on_get_sticker_set(std::move(multicovered_set->set_), is_changed);
- if (set_id == 0) {
+ set_id = on_get_sticker_set(std::move(multicovered_set->set_), is_changed, source);
+ if (!set_id.is_valid()) {
break;
}
auto sticker_set = get_sticker_set(set_id);
CHECK(sticker_set != nullptr);
- CHECK(sticker_set->is_inited);
- if (sticker_set->was_loaded) {
+ CHECK(sticker_set->is_inited_);
+ if (sticker_set->was_loaded_) {
break;
}
- auto &sticker_ids = sticker_set->sticker_ids;
+ auto &sticker_ids = sticker_set->sticker_ids_;
for (auto &cover : multicovered_set->covers_) {
- auto sticker_id = on_get_sticker_document(std::move(cover), true).second;
- if (sticker_id.is_valid() &&
- std::find(sticker_ids.begin(), sticker_ids.end(), sticker_id) == sticker_ids.end()) {
+ auto sticker_id = on_get_sticker_document(std::move(cover), sticker_set->sticker_format_).second;
+ if (sticker_id.is_valid() && !td::contains(sticker_ids, sticker_id)) {
sticker_ids.push_back(sticker_id);
- sticker_set->is_changed = true;
+ sticker_set->is_changed_ = true;
}
}
break;
}
+ case telegram_api::stickerSetFullCovered::ID: {
+ auto set = move_tl_object_as<telegram_api::stickerSetFullCovered>(set_ptr);
+ auto sticker_set = telegram_api::make_object<telegram_api::messages_stickerSet>(
+ std::move(set->set_), std::move(set->packs_), std::move(set->keywords_), std::move(set->documents_));
+ return on_get_messages_sticker_set(StickerSetId(), std::move(sticker_set), is_changed, source);
+ }
default:
UNREACHABLE();
}
return set_id;
}
-void StickersManager::on_get_messages_sticker_set(int64 sticker_set_id,
- tl_object_ptr<telegram_api::messages_stickerSet> &&set,
- bool is_changed) {
- LOG(INFO) << "Receive sticker set " << to_string(set);
+StickerSetId StickersManager::on_get_messages_sticker_set(StickerSetId sticker_set_id,
+ tl_object_ptr<telegram_api::messages_StickerSet> &&set_ptr,
+ bool is_changed, const char *source) {
+ LOG(INFO) << "Receive sticker set " << to_string(set_ptr);
+ if (set_ptr->get_id() == telegram_api::messages_stickerSetNotModified::ID) {
+ if (!sticker_set_id.is_valid()) {
+ LOG(ERROR) << "Receive unexpected stickerSetNotModified from " << source;
+ } else {
+ auto s = get_sticker_set(sticker_set_id);
+ CHECK(s != nullptr);
+ CHECK(s->is_inited_);
+ CHECK(s->was_loaded_);
- auto set_id = on_get_sticker_set(std::move(set->set_), is_changed);
- if (set_id == 0) {
- return;
+ s->is_loaded_ = true;
+ s->expires_at_ = G()->unix_time() +
+ (td_->auth_manager_->is_bot() ? Random::fast(10 * 60, 15 * 60) : Random::fast(30 * 60, 50 * 60));
+ }
+ return sticker_set_id;
}
- if (sticker_set_id != 0 && sticker_set_id != set_id) {
- LOG(ERROR) << "Expected sticker set " << sticker_set_id << ", but receive sticker set " << set_id;
- on_load_sticker_set_fail(sticker_set_id, Status::Error(500, "Internal server error"));
- return;
+ auto set = move_tl_object_as<telegram_api::messages_stickerSet>(set_ptr);
+
+ auto set_id = on_get_sticker_set(std::move(set->set_), is_changed, source);
+ if (!set_id.is_valid()) {
+ return StickerSetId();
+ }
+ if (sticker_set_id.is_valid() && sticker_set_id != set_id) {
+ LOG(ERROR) << "Expected " << sticker_set_id << ", but receive " << set_id << " from " << source;
+ on_load_sticker_set_fail(sticker_set_id, Status::Error(500, "Internal Server Error: wrong sticker set received"));
+ return StickerSetId();
}
auto s = get_sticker_set(set_id);
CHECK(s != nullptr);
- CHECK(s->is_inited);
+ CHECK(s->is_inited_);
- s->expires_at = G()->unix_time() + (td_->auth_manager_->is_bot() ? Random::fast(10 * 60, 15 * 60)
- : Random::fast(20 * 60 * 60, 28 * 60 * 60));
+ s->expires_at_ = G()->unix_time() +
+ (td_->auth_manager_->is_bot() ? Random::fast(10 * 60, 15 * 60) : Random::fast(30 * 60, 50 * 60));
- if (s->is_loaded) {
- update_sticker_set(s);
+ if (s->is_loaded_) {
+ update_sticker_set(s, "on_get_messages_sticker_set");
send_update_installed_sticker_sets();
- return;
+ return set_id;
}
- s->was_loaded = true;
- s->is_loaded = true;
- s->is_changed = true;
-
- vector<tl_object_ptr<telegram_api::stickerPack>> packs = std::move(set->packs_);
- vector<tl_object_ptr<telegram_api::Document>> documents = std::move(set->documents_);
+ s->was_loaded_ = true;
+ s->is_loaded_ = true;
+ s->is_changed_ = true;
+ s->are_keywords_loaded_ = true;
- std::unordered_map<int64, FileId> document_id_to_sticker_id;
+ FlatHashMap<int64, FileId> document_id_to_sticker_id;
- s->sticker_ids.clear();
- for (auto &document_ptr : documents) {
- auto sticker_id = on_get_sticker_document(std::move(document_ptr), false);
- if (!sticker_id.second.is_valid()) {
+ s->sticker_ids_.clear();
+ s->premium_sticker_positions_.clear();
+ bool is_bot = td_->auth_manager_->is_bot();
+ for (auto &document_ptr : set->documents_) {
+ auto sticker_id = on_get_sticker_document(std::move(document_ptr), s->sticker_format_);
+ if (!sticker_id.second.is_valid() || sticker_id.first == 0) {
continue;
}
- s->sticker_ids.push_back(sticker_id.second);
- document_id_to_sticker_id.insert(sticker_id);
+ if (!is_bot && get_sticker(sticker_id.second)->is_premium_) {
+ s->premium_sticker_positions_.push_back(static_cast<int32>(s->sticker_ids_.size()));
+ }
+ s->sticker_ids_.push_back(sticker_id.second);
+ if (!is_bot) {
+ document_id_to_sticker_id.emplace(sticker_id.first, sticker_id.second);
+ }
}
- if (static_cast<int>(s->sticker_ids.size()) != s->sticker_count) {
- LOG(ERROR) << "Wrong sticker set size specified";
- s->sticker_count = static_cast<int>(s->sticker_ids.size());
+ if (static_cast<int32>(s->sticker_ids_.size()) != s->sticker_count_) {
+ LOG(ERROR) << "Wrong sticker set size " << s->sticker_count_ << " instead of " << s->sticker_ids_.size()
+ << " specified in " << set_id << "/" << s->short_name_ << " from " << source;
+ s->sticker_count_ = static_cast<int32>(s->sticker_ids_.size());
}
- s->emoji_stickers_map_.clear();
- s->sticker_emojis_map_.clear();
- for (auto &pack : packs) {
- vector<FileId> stickers;
- stickers.reserve(pack->documents_.size());
- for (int64 document_id : pack->documents_) {
+ if (!is_bot) {
+ s->emoji_stickers_map_.clear();
+ s->sticker_emojis_map_.clear();
+ s->keyword_stickers_map_.clear();
+ s->sticker_keywords_map_.clear();
+ for (auto &pack : set->packs_) {
+ auto cleaned_emoji = remove_emoji_modifiers(pack->emoticon_);
+ if (cleaned_emoji.empty()) {
+ LOG(ERROR) << "Receive empty emoji in " << set_id << "/" << s->short_name_ << " from " << source;
+ continue;
+ }
+
+ vector<FileId> stickers;
+ stickers.reserve(pack->documents_.size());
+ for (int64 document_id : pack->documents_) {
+ auto it = document_id_to_sticker_id.find(document_id);
+ if (it == document_id_to_sticker_id.end()) {
+ LOG(ERROR) << "Can't find document with ID " << document_id << " in " << set_id << "/" << s->short_name_
+ << " from " << source;
+ continue;
+ }
+
+ stickers.push_back(it->second);
+ s->sticker_emojis_map_[it->second].push_back(pack->emoticon_);
+ }
+
+ auto &sticker_ids = s->emoji_stickers_map_[cleaned_emoji];
+ for (auto sticker_id : stickers) {
+ if (!td::contains(sticker_ids, sticker_id)) {
+ sticker_ids.push_back(sticker_id);
+ }
+ }
+ }
+ for (auto &keywords : set->keywords_) {
+ auto document_id = keywords->document_id_;
auto it = document_id_to_sticker_id.find(document_id);
if (it == document_id_to_sticker_id.end()) {
- LOG(ERROR) << "Can't find document with id " << document_id;
+ LOG(ERROR) << "Can't find document with ID " << document_id << " in " << set_id << "/" << s->short_name_
+ << " from " << source;
continue;
}
- stickers.push_back(it->second);
- s->sticker_emojis_map_[it->second].push_back(pack->emoticon_);
+ bool is_inserted = s->sticker_keywords_map_.emplace(it->second, std::move(keywords->keyword_)).second;
+ if (!is_inserted) {
+ LOG(ERROR) << "Receive twice document with ID " << document_id << " in " << set_id << "/" << s->short_name_
+ << " from " << source;
+ }
}
- s->emoji_stickers_map_.emplace(remove_emoji_modifiers(pack->emoticon_), std::move(stickers));
}
- update_sticker_set(s);
+ update_sticker_set(s, "on_get_messages_sticker_set 2");
update_load_requests(s, true, Status::OK());
send_update_installed_sticker_sets();
+
+ if (set_id == add_special_sticker_set(SpecialStickerSetType::animated_emoji()).id_) {
+ try_update_animated_emoji_messages();
+ }
+ if (set_id == add_special_sticker_set(SpecialStickerSetType::premium_gifts()).id_) {
+ try_update_premium_gift_messages();
+ }
+
+ return set_id;
}
-void StickersManager::on_load_sticker_set_fail(int64 sticker_set_id, const Status &error) {
- if (sticker_set_id == 0) {
+void StickersManager::on_load_sticker_set_fail(StickerSetId sticker_set_id, const Status &error) {
+ if (!sticker_set_id.is_valid()) {
return;
}
update_load_requests(get_sticker_set(sticker_set_id), true, error);
@@ -1621,85 +3795,403 @@ void StickersManager::update_load_requests(StickerSet *sticker_set, bool with_st
return;
}
if (with_stickers) {
- for (auto load_request_id : sticker_set->load_requests) {
+ for (auto load_request_id : sticker_set->load_requests_) {
update_load_request(load_request_id, status);
}
- sticker_set->load_requests.clear();
+ sticker_set->load_requests_.clear();
}
- for (auto load_request_id : sticker_set->load_without_stickers_requests) {
+ for (auto load_request_id : sticker_set->load_without_stickers_requests_) {
update_load_request(load_request_id, status);
}
- sticker_set->load_without_stickers_requests.clear();
+ sticker_set->load_without_stickers_requests_.clear();
+
+ if (status.message() == "STICKERSET_INVALID") {
+ // the sticker set is likely to be deleted
+ // clear short_name_to_sticker_set_id_ to allow next searchStickerSet request to succeed
+ short_name_to_sticker_set_id_.erase(clean_username(sticker_set->short_name_));
+ }
}
void StickersManager::update_load_request(uint32 load_request_id, const Status &status) {
auto it = sticker_set_load_requests_.find(load_request_id);
CHECK(it != sticker_set_load_requests_.end());
- CHECK(it->second.left_queries > 0);
- if (status.is_error() && it->second.error.is_ok()) {
- it->second.error = status.clone();
+ CHECK(it->second.left_queries_ > 0);
+ if (status.is_error() && it->second.error_.is_ok()) {
+ it->second.error_ = status.clone();
}
- if (--it->second.left_queries == 0) {
- if (it->second.error.is_ok()) {
- it->second.promise.set_value(Unit());
+ if (--it->second.left_queries_ == 0) {
+ if (it->second.error_.is_ok()) {
+ it->second.promise_.set_value(Unit());
} else {
- it->second.promise.set_error(std::move(it->second.error));
+ it->second.promise_.set_error(std::move(it->second.error_));
}
sticker_set_load_requests_.erase(it);
}
}
-void StickersManager::on_get_installed_sticker_sets(bool is_masks,
+void StickersManager::on_get_special_sticker_set(const SpecialStickerSetType &type, StickerSetId sticker_set_id) {
+ auto s = get_sticker_set(sticker_set_id);
+ CHECK(s != nullptr);
+ CHECK(s->is_inited_);
+ CHECK(s->is_loaded_);
+
+ LOG(INFO) << "Receive special sticker set " << type.type_ << ": " << sticker_set_id << ' ' << s->access_hash_ << ' '
+ << s->short_name_;
+ auto &sticker_set = add_special_sticker_set(type);
+ if (sticker_set_id == sticker_set.id_ && s->access_hash_ == sticker_set.access_hash_ &&
+ s->short_name_ == sticker_set.short_name_ && !s->short_name_.empty()) {
+ on_load_special_sticker_set(type, Status::OK());
+ return;
+ }
+
+ sticker_set.id_ = sticker_set_id;
+ sticker_set.access_hash_ = s->access_hash_;
+ sticker_set.short_name_ = clean_username(s->short_name_);
+ sticker_set.type_ = type;
+
+ if (!td_->auth_manager_->is_bot()) {
+ G()->td_db()->get_binlog_pmc()->set(type.type_, PSTRING()
+ << sticker_set.id_.get() << ' ' << sticker_set.access_hash_
+ << ' ' << sticker_set.short_name_);
+ }
+
+ sticker_set.is_being_loaded_ = true;
+ on_load_special_sticker_set(type, Status::OK());
+}
+
+td_api::object_ptr<td_api::updateActiveEmojiReactions> StickersManager::get_update_active_emoji_reactions_object()
+ const {
+ return td_api::make_object<td_api::updateActiveEmojiReactions>(vector<string>(active_reactions_));
+}
+
+void StickersManager::save_active_reactions() {
+ LOG(INFO) << "Save " << active_reactions_.size() << " active reactions";
+ G()->td_db()->get_binlog_pmc()->set("active_reactions", log_event_store(active_reactions_).as_slice().str());
+}
+
+void StickersManager::save_reactions() {
+ LOG(INFO) << "Save " << reactions_.reactions_.size() << " available reactions";
+ are_reactions_loaded_from_database_ = true;
+ G()->td_db()->get_binlog_pmc()->set("reactions", log_event_store(reactions_).as_slice().str());
+}
+
+void StickersManager::save_recent_reactions() {
+ LOG(INFO) << "Save " << recent_reactions_.reactions_.size() << " recent reactions";
+ are_recent_reactions_loaded_from_database_ = true;
+ G()->td_db()->get_binlog_pmc()->set("recent_reactions", log_event_store(recent_reactions_).as_slice().str());
+}
+
+void StickersManager::save_top_reactions() {
+ LOG(INFO) << "Save " << top_reactions_.reactions_.size() << " top reactions";
+ are_top_reactions_loaded_from_database_ = true;
+ G()->td_db()->get_binlog_pmc()->set("top_reactions", log_event_store(top_reactions_).as_slice().str());
+}
+
+void StickersManager::load_active_reactions() {
+ LOG(INFO) << "Loading active reactions";
+ string active_reactions = G()->td_db()->get_binlog_pmc()->get("active_reactions");
+ if (active_reactions.empty()) {
+ return reload_reactions();
+ }
+
+ auto status = log_event_parse(active_reactions_, active_reactions);
+ if (status.is_error()) {
+ LOG(ERROR) << "Can't load active reactions: " << status;
+ active_reactions_ = {};
+ return reload_reactions();
+ }
+
+ LOG(INFO) << "Successfully loaded " << active_reactions_.size() << " active reactions";
+
+ td_->messages_manager_->set_active_reactions(vector<string>(active_reactions_));
+
+ send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object());
+}
+
+void StickersManager::load_reactions() {
+ if (are_reactions_loaded_from_database_) {
+ return;
+ }
+ are_reactions_loaded_from_database_ = true;
+
+ LOG(INFO) << "Loading available reactions";
+ string reactions = G()->td_db()->get_binlog_pmc()->get("reactions");
+ if (reactions.empty()) {
+ return reload_reactions();
+ }
+
+ auto new_reactions = reactions_;
+ auto status = log_event_parse(new_reactions, reactions);
+ if (status.is_error()) {
+ LOG(ERROR) << "Can't load available reactions: " << status;
+ return reload_reactions();
+ }
+ for (auto &reaction : new_reactions.reactions_) {
+ if (!reaction.is_valid()) {
+ LOG(ERROR) << "Loaded invalid reaction";
+ return reload_reactions();
+ }
+ }
+ reactions_ = std::move(new_reactions);
+
+ LOG(INFO) << "Successfully loaded " << reactions_.reactions_.size() << " available reactions";
+
+ update_active_reactions();
+}
+
+void StickersManager::load_recent_reactions() {
+ if (are_recent_reactions_loaded_from_database_) {
+ return;
+ }
+ are_recent_reactions_loaded_from_database_ = true;
+
+ LOG(INFO) << "Loading recent reactions";
+ string recent_reactions = G()->td_db()->get_binlog_pmc()->get("recent_reactions");
+ if (recent_reactions.empty()) {
+ return reload_recent_reactions();
+ }
+
+ auto status = log_event_parse(recent_reactions_, recent_reactions);
+ if (status.is_error()) {
+ LOG(ERROR) << "Can't load recent reactions: " << status;
+ recent_reactions_ = {};
+ return reload_recent_reactions();
+ }
+
+ LOG(INFO) << "Successfully loaded " << recent_reactions_.reactions_.size() << " recent reactions";
+}
+
+void StickersManager::load_top_reactions() {
+ if (are_top_reactions_loaded_from_database_) {
+ return;
+ }
+ are_top_reactions_loaded_from_database_ = true;
+
+ LOG(INFO) << "Loading top reactions";
+ string top_reactions = G()->td_db()->get_binlog_pmc()->get("top_reactions");
+ if (top_reactions.empty()) {
+ return reload_top_reactions();
+ }
+
+ auto status = log_event_parse(top_reactions_, top_reactions);
+ if (status.is_error()) {
+ LOG(ERROR) << "Can't load top reactions: " << status;
+ top_reactions_ = {};
+ return reload_top_reactions();
+ }
+
+ LOG(INFO) << "Successfully loaded " << top_reactions_.reactions_.size() << " top reactions";
+}
+
+void StickersManager::update_active_reactions() {
+ vector<string> active_reactions;
+ for (auto &reaction : reactions_.reactions_) {
+ if (reaction.is_active_) {
+ active_reactions.emplace_back(reaction.reaction_);
+ }
+ }
+ if (active_reactions == active_reactions_) {
+ return;
+ }
+ active_reactions_ = active_reactions;
+
+ save_active_reactions();
+
+ send_closure(G()->td(), &Td::send_update, get_update_active_emoji_reactions_object());
+
+ td_->messages_manager_->set_active_reactions(std::move(active_reactions));
+}
+
+void StickersManager::on_get_available_reactions(
+ tl_object_ptr<telegram_api::messages_AvailableReactions> &&available_reactions_ptr) {
+ CHECK(reactions_.are_being_reloaded_);
+ reactions_.are_being_reloaded_ = false;
+
+ auto get_emoji_reaction_queries = std::move(pending_get_emoji_reaction_queries_);
+ pending_get_emoji_reaction_queries_.clear();
+ SCOPE_EXIT {
+ for (auto &query : get_emoji_reaction_queries) {
+ query.second.set_value(get_emoji_reaction_object(query.first));
+ }
+ };
+
+ if (available_reactions_ptr == nullptr) {
+ // failed to get available reactions
+ return;
+ }
+
+ int32 constructor_id = available_reactions_ptr->get_id();
+ if (constructor_id == telegram_api::messages_availableReactionsNotModified::ID) {
+ LOG(INFO) << "Available reactions are not modified";
+ return;
+ }
+
+ CHECK(constructor_id == telegram_api::messages_availableReactions::ID);
+ auto available_reactions = move_tl_object_as<telegram_api::messages_availableReactions>(available_reactions_ptr);
+ vector<Reaction> new_reactions;
+ for (auto &available_reaction : available_reactions->reactions_) {
+ Reaction reaction;
+ reaction.is_active_ = !available_reaction->inactive_;
+ reaction.is_premium_ = available_reaction->premium_;
+ reaction.reaction_ = std::move(available_reaction->reaction_);
+ reaction.title_ = std::move(available_reaction->title_);
+ reaction.static_icon_ =
+ on_get_sticker_document(std::move(available_reaction->static_icon_), StickerFormat::Webp).second;
+ reaction.appear_animation_ =
+ on_get_sticker_document(std::move(available_reaction->appear_animation_), StickerFormat::Tgs).second;
+ reaction.select_animation_ =
+ on_get_sticker_document(std::move(available_reaction->select_animation_), StickerFormat::Tgs).second;
+ reaction.activate_animation_ =
+ on_get_sticker_document(std::move(available_reaction->activate_animation_), StickerFormat::Tgs).second;
+ reaction.effect_animation_ =
+ on_get_sticker_document(std::move(available_reaction->effect_animation_), StickerFormat::Tgs).second;
+ reaction.around_animation_ =
+ on_get_sticker_document(std::move(available_reaction->around_animation_), StickerFormat::Tgs).second;
+ reaction.center_animation_ =
+ on_get_sticker_document(std::move(available_reaction->center_icon_), StickerFormat::Tgs).second;
+
+ if (!reaction.is_valid()) {
+ LOG(ERROR) << "Receive invalid reaction " << reaction.reaction_;
+ continue;
+ }
+ if (reaction.is_premium_) {
+ LOG(ERROR) << "Receive premium reaction " << reaction.reaction_;
+ continue;
+ }
+
+ new_reactions.push_back(std::move(reaction));
+ }
+ reactions_.reactions_ = std::move(new_reactions);
+ reactions_.hash_ = available_reactions->hash_;
+
+ save_reactions();
+
+ update_active_reactions();
+}
+
+void StickersManager::on_get_recent_reactions(tl_object_ptr<telegram_api::messages_Reactions> &&reactions_ptr) {
+ CHECK(recent_reactions_.is_being_reloaded_);
+ recent_reactions_.is_being_reloaded_ = false;
+
+ if (reactions_ptr == nullptr) {
+ // failed to get recent reactions
+ return;
+ }
+
+ int32 constructor_id = reactions_ptr->get_id();
+ if (constructor_id == telegram_api::messages_reactionsNotModified::ID) {
+ LOG(INFO) << "Top reactions are not modified";
+ return;
+ }
+
+ CHECK(constructor_id == telegram_api::messages_reactions::ID);
+ auto reactions = move_tl_object_as<telegram_api::messages_reactions>(reactions_ptr);
+ auto new_reactions =
+ transform(reactions->reactions_, [](const telegram_api::object_ptr<telegram_api::Reaction> &reaction) {
+ return get_message_reaction_string(reaction);
+ });
+ if (new_reactions == recent_reactions_.reactions_ && recent_reactions_.hash_ == reactions->hash_) {
+ LOG(INFO) << "Top reactions are not modified";
+ return;
+ }
+ recent_reactions_.reactions_ = std::move(new_reactions);
+ recent_reactions_.hash_ = reactions->hash_;
+
+ auto expected_hash = get_reactions_hash(recent_reactions_.reactions_);
+ if (recent_reactions_.hash_ != expected_hash) {
+ LOG(ERROR) << "Receive hash " << recent_reactions_.hash_ << " instead of " << expected_hash << " for reactions "
+ << recent_reactions_.reactions_;
+ }
+
+ save_recent_reactions();
+}
+
+void StickersManager::on_get_top_reactions(tl_object_ptr<telegram_api::messages_Reactions> &&reactions_ptr) {
+ CHECK(top_reactions_.is_being_reloaded_);
+ top_reactions_.is_being_reloaded_ = false;
+
+ if (reactions_ptr == nullptr) {
+ // failed to get top reactions
+ return;
+ }
+
+ int32 constructor_id = reactions_ptr->get_id();
+ if (constructor_id == telegram_api::messages_reactionsNotModified::ID) {
+ LOG(INFO) << "Top reactions are not modified";
+ return;
+ }
+
+ CHECK(constructor_id == telegram_api::messages_reactions::ID);
+ auto reactions = move_tl_object_as<telegram_api::messages_reactions>(reactions_ptr);
+ auto new_reactions =
+ transform(reactions->reactions_, [](const telegram_api::object_ptr<telegram_api::Reaction> &reaction) {
+ return get_message_reaction_string(reaction);
+ });
+ if (new_reactions == top_reactions_.reactions_ && top_reactions_.hash_ == reactions->hash_) {
+ LOG(INFO) << "Top reactions are not modified";
+ return;
+ }
+ top_reactions_.reactions_ = std::move(new_reactions);
+ top_reactions_.hash_ = reactions->hash_;
+
+ save_top_reactions();
+}
+
+void StickersManager::on_get_installed_sticker_sets(StickerType sticker_type,
tl_object_ptr<telegram_api::messages_AllStickers> &&stickers_ptr) {
- next_installed_sticker_sets_load_time_[is_masks] = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
+ auto type = static_cast<int32>(sticker_type);
+ next_installed_sticker_sets_load_time_[type] = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
CHECK(stickers_ptr != nullptr);
int32 constructor_id = stickers_ptr->get_id();
if (constructor_id == telegram_api::messages_allStickersNotModified::ID) {
- LOG(INFO) << (is_masks ? "Masks" : "Stickers") << " are not modified";
+ LOG(INFO) << sticker_type << " stickers are not modified";
return;
}
CHECK(constructor_id == telegram_api::messages_allStickers::ID);
auto stickers = move_tl_object_as<telegram_api::messages_allStickers>(stickers_ptr);
- std::unordered_set<int64> uninstalled_sticker_sets(installed_sticker_set_ids_[is_masks].begin(),
- installed_sticker_set_ids_[is_masks].end());
+ FlatHashSet<StickerSetId, StickerSetIdHash> uninstalled_sticker_sets;
+ for (auto &sticker_set_id : installed_sticker_set_ids_[type]) {
+ uninstalled_sticker_sets.insert(sticker_set_id);
+ }
- vector<int64> sets_to_load;
- vector<int64> installed_sticker_set_ids;
- vector<int32> hashes;
- vector<int64> sticker_set_ids;
+ vector<StickerSetId> sets_to_load;
+ vector<StickerSetId> installed_sticker_set_ids;
+ vector<int32> debug_hashes;
+ vector<int64> debug_sticker_set_ids;
std::reverse(stickers->sets_.begin(), stickers->sets_.end()); // apply installed sticker sets in reverse order
for (auto &set : stickers->sets_) {
- hashes.push_back(set->hash_);
- sticker_set_ids.push_back(set->id_);
- int64 set_id = on_get_sticker_set(std::move(set), false);
- if (set_id == 0) {
+ debug_hashes.push_back(set->hash_);
+ debug_sticker_set_ids.push_back(set->id_);
+ StickerSetId set_id = on_get_sticker_set(std::move(set), false, "on_get_installed_sticker_sets");
+ if (!set_id.is_valid()) {
continue;
}
auto sticker_set = get_sticker_set(set_id);
CHECK(sticker_set != nullptr);
- LOG_IF(ERROR, !sticker_set->is_installed) << "Receive non-installed sticker set in getAllStickers";
- LOG_IF(ERROR, sticker_set->is_archived) << "Receive archived sticker set in getAllStickers";
- LOG_IF(ERROR, sticker_set->is_masks != is_masks) << "Receive sticker set of a wrong type in getAllStickers";
- CHECK(sticker_set->is_inited);
+ LOG_IF(ERROR, !sticker_set->is_installed_) << "Receive non-installed sticker set in getAllStickers";
+ LOG_IF(ERROR, sticker_set->is_archived_) << "Receive archived sticker set in getAllStickers";
+ LOG_IF(ERROR, sticker_set->sticker_type_ != sticker_type)
+ << "Receive sticker set of a wrong type in getAllStickers";
+ CHECK(sticker_set->is_inited_);
- if (sticker_set->is_installed && !sticker_set->is_archived && sticker_set->is_masks == is_masks) {
+ if (sticker_set->is_installed_ && !sticker_set->is_archived_ && sticker_set->sticker_type_ == sticker_type) {
installed_sticker_set_ids.push_back(set_id);
uninstalled_sticker_sets.erase(set_id);
}
- update_sticker_set(sticker_set);
+ update_sticker_set(sticker_set, "on_get_installed_sticker_sets");
- if (!sticker_set->is_archived && !sticker_set->is_loaded) {
+ if (!sticker_set->is_archived_ && !sticker_set->is_loaded_) {
sets_to_load.push_back(set_id);
}
}
- std::reverse(hashes.begin(), hashes.end());
+ std::reverse(debug_hashes.begin(), debug_hashes.end());
std::reverse(installed_sticker_set_ids.begin(), installed_sticker_set_ids.end());
- std::reverse(sticker_set_ids.begin(), sticker_set_ids.end());
+ std::reverse(debug_sticker_set_ids.begin(), debug_sticker_set_ids.end());
if (!sets_to_load.empty()) {
load_sticker_sets(std::move(sets_to_load), Auto());
@@ -1708,99 +4200,249 @@ void StickersManager::on_get_installed_sticker_sets(bool is_masks,
for (auto set_id : uninstalled_sticker_sets) {
auto sticker_set = get_sticker_set(set_id);
CHECK(sticker_set != nullptr);
- CHECK(sticker_set->is_installed && !sticker_set->is_archived);
+ CHECK(sticker_set->is_installed_ && !sticker_set->is_archived_);
on_update_sticker_set(sticker_set, false, false, true);
- update_sticker_set(sticker_set);
+ update_sticker_set(sticker_set, "on_get_installed_sticker_sets 2");
}
- on_load_installed_sticker_sets_finished(is_masks, std::move(installed_sticker_set_ids));
+ on_load_installed_sticker_sets_finished(sticker_type, std::move(installed_sticker_set_ids));
- if (installed_sticker_sets_hash_[is_masks] != stickers->hash_) {
- LOG(ERROR) << "Sticker sets hash mismatch: server hash list = " << format::as_array(hashes)
- << ", client hash list = "
- << format::as_array(
- transform(installed_sticker_set_ids_[is_masks],
- [this](int64 sticker_set_id) { return get_sticker_set(sticker_set_id)->hash; }))
- << ", server sticker set list = " << format::as_array(sticker_set_ids)
- << ", client sticker set list = " << format::as_array(installed_sticker_set_ids_[is_masks])
- << ", server hash = " << stickers->hash_ << ", client hash = " << installed_sticker_sets_hash_[is_masks];
+ if (installed_sticker_sets_hash_[type] != stickers->hash_) {
+ LOG(ERROR) << "Sticker sets hash mismatch: server hash list = " << debug_hashes << ", client hash list = "
+ << transform(installed_sticker_set_ids_[type],
+ [this](StickerSetId sticker_set_id) { return get_sticker_set(sticker_set_id)->hash_; })
+ << ", server sticker set list = " << debug_sticker_set_ids
+ << ", client sticker set list = " << installed_sticker_set_ids_[type]
+ << ", server hash = " << stickers->hash_ << ", client hash = " << installed_sticker_sets_hash_[type];
}
}
-void StickersManager::on_get_installed_sticker_sets_failed(bool is_masks, Status error) {
+void StickersManager::on_get_installed_sticker_sets_failed(StickerType sticker_type, Status error) {
CHECK(error.is_error());
- next_installed_sticker_sets_load_time_[is_masks] = Time::now_cached() + Random::fast(5, 10);
- auto promises = std::move(load_installed_sticker_sets_queries_[is_masks]);
- load_installed_sticker_sets_queries_[is_masks].clear();
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+ auto type = static_cast<int32>(sticker_type);
+ next_installed_sticker_sets_load_time_[type] = Time::now_cached() + Random::fast(5, 10);
+ fail_promises(load_installed_sticker_sets_queries_[type], std::move(error));
+}
+
+const std::map<string, vector<FileId>> &StickersManager::get_sticker_set_keywords(const StickerSet *sticker_set) {
+ if (sticker_set->keyword_stickers_map_.empty()) {
+ for (auto &sticker_id_keywords : sticker_set->sticker_keywords_map_) {
+ for (auto &keyword : Hints::fix_words(transform(sticker_id_keywords.second, utf8_prepare_search_string))) {
+ CHECK(!keyword.empty());
+ sticker_set->keyword_stickers_map_[keyword].push_back(sticker_id_keywords.first);
+ }
+ }
}
+ return sticker_set->keyword_stickers_map_;
}
-vector<FileId> StickersManager::get_stickers(string emoji, int32 limit, bool force, Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- promise.set_error(Status::Error(7, "Method is not available for bots"));
+void StickersManager::find_sticker_set_stickers(const StickerSet *sticker_set, const string &query,
+ const string &prepared_query, vector<FileId> &result) {
+ auto it = sticker_set->emoji_stickers_map_.find(query);
+ if (it != sticker_set->emoji_stickers_map_.end()) {
+ LOG(INFO) << "Add " << it->second << " stickers from " << sticker_set->id_;
+ append(result, it->second);
+ }
+
+ if (!prepared_query.empty()) {
+ const auto &keywords_map = get_sticker_set_keywords(sticker_set);
+ auto keywords_it = keywords_map.lower_bound(prepared_query);
+ if (keywords_it != keywords_map.end() && begins_with(keywords_it->first, prepared_query)) {
+ FlatHashSet<FileId, FileIdHash> found_sticker_ids;
+ if (it != sticker_set->emoji_stickers_map_.end()) {
+ for (auto file_id : it->second) {
+ found_sticker_ids.insert(file_id);
+ }
+ }
+ do {
+ for (auto file_id : keywords_it->second) {
+ if (found_sticker_ids.insert(file_id).second) {
+ result.push_back(file_id);
+ }
+ }
+ ++keywords_it;
+ } while (keywords_it != keywords_map.end() && begins_with(keywords_it->first, prepared_query));
+ }
+ }
+}
+
+bool StickersManager::can_found_sticker_by_query(FileId sticker_id, const string &query,
+ const string &prepared_query) const {
+ const Sticker *s = get_sticker(sticker_id);
+ CHECK(s != nullptr);
+ if (remove_emoji_modifiers(s->alt_) == query) {
+ // fast path
+ return true;
+ }
+ const StickerSet *sticker_set = get_sticker_set(s->set_id_);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ return false;
+ }
+ auto it = sticker_set->emoji_stickers_map_.find(query);
+ if (it != sticker_set->emoji_stickers_map_.end()) {
+ if (td::contains(it->second, sticker_id)) {
+ return true;
+ }
+ }
+
+ if (!prepared_query.empty()) {
+ const auto &keywords_map = get_sticker_set_keywords(sticker_set);
+ auto keywords_it = keywords_map.lower_bound(prepared_query);
+ if (keywords_it != keywords_map.end() && begins_with(keywords_it->first, prepared_query)) {
+ do {
+ if (td::contains(keywords_it->second, sticker_id)) {
+ return true;
+ }
+ ++keywords_it;
+ } while (keywords_it != keywords_map.end() && begins_with(keywords_it->first, prepared_query));
+ }
+ }
+
+ return false;
+}
+
+std::pair<vector<FileId>, vector<FileId>> StickersManager::split_stickers_by_premium(
+ const vector<FileId> &sticker_ids) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ vector<FileId> regular_sticker_ids;
+ vector<FileId> premium_sticker_ids;
+ for (const auto &sticker_id : sticker_ids) {
+ if (sticker_id.is_valid()) {
+ const Sticker *s = get_sticker(sticker_id);
+ CHECK(s != nullptr);
+ if (s->is_premium_) {
+ premium_sticker_ids.push_back(sticker_id);
+ } else {
+ regular_sticker_ids.push_back(sticker_id);
+ }
+ }
+ }
+ return {std::move(regular_sticker_ids), std::move(premium_sticker_ids)};
+}
+
+std::pair<vector<FileId>, vector<FileId>> StickersManager::split_stickers_by_premium(
+ const StickerSet *sticker_set) const {
+ CHECK(!td_->auth_manager_->is_bot());
+ if (!sticker_set->was_loaded_) {
+ return split_stickers_by_premium(sticker_set->sticker_ids_);
+ }
+ if (sticker_set->premium_sticker_positions_.empty()) {
+ return {sticker_set->sticker_ids_, {}};
+ }
+ vector<FileId> regular_sticker_ids;
+ vector<FileId> premium_sticker_ids;
+ size_t premium_pos = 0;
+ for (size_t i = 0; i < sticker_set->sticker_ids_.size(); i++) {
+ if (premium_pos < sticker_set->premium_sticker_positions_.size() &&
+ static_cast<size_t>(sticker_set->premium_sticker_positions_[premium_pos]) == i) {
+ premium_sticker_ids.push_back(sticker_set->sticker_ids_[i]);
+ premium_pos++;
+ } else {
+ regular_sticker_ids.push_back(sticker_set->sticker_ids_[i]);
+ }
+ }
+ CHECK(premium_pos == sticker_set->premium_sticker_positions_.size());
+ return {std::move(regular_sticker_ids), std::move(premium_sticker_ids)};
+}
+
+vector<FileId> StickersManager::get_stickers(StickerType sticker_type, string query, int32 limit, DialogId dialog_id,
+ bool force, Promise<Unit> &&promise) {
+ if (G()->close_flag()) {
+ promise.set_error(Global::request_aborted_error());
return {};
}
+
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return {};
}
- if (!are_installed_sticker_sets_loaded_[0]) {
- load_installed_sticker_sets(false, std::move(promise));
+
+ auto type = static_cast<int32>(sticker_type);
+ if (!are_installed_sticker_sets_loaded_[type]) {
+ CHECK(force == false);
+ load_installed_sticker_sets(
+ sticker_type,
+ PromiseCreator::lambda([actor_id = actor_id(this), sticker_type, query = std::move(query), limit, dialog_id,
+ force, promise = std::move(promise)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::get_stickers, sticker_type, std::move(query), limit, dialog_id,
+ force, std::move(promise));
+ }
+ }));
return {};
}
- emoji = remove_emoji_modifiers(emoji);
- if (!emoji.empty()) {
- if (!are_recent_stickers_loaded_[0]) {
- load_recent_stickers(false, std::move(promise));
- return {};
- }
- if (!are_favorite_stickers_loaded_) {
- load_favorite_stickers(std::move(promise));
- return {};
- }
- /*
- if (!are_featured_sticker_sets_loaded_) {
- load_featured_sticker_sets(std::move(promise));
- return {};
+ remove_emoji_modifiers_in_place(query);
+ if (!query.empty()) {
+ if (sticker_type == StickerType::Regular) {
+ if (!are_recent_stickers_loaded_[0 /*is_attached*/]) {
+ load_recent_stickers(false, std::move(promise));
+ return {};
+ }
+ if (!are_favorite_stickers_loaded_) {
+ load_favorite_stickers(std::move(promise));
+ return {};
+ }
+ } else if (sticker_type == StickerType::CustomEmoji) {
+ if (!are_featured_sticker_sets_loaded_[type]) {
+ load_featured_sticker_sets(sticker_type, std::move(promise));
+ return {};
+ }
}
- */
}
- vector<int64> sets_to_load;
+ vector<StickerSetId> examined_sticker_set_ids = installed_sticker_set_ids_[type];
+ if (!query.empty() && sticker_type == StickerType::CustomEmoji) {
+ append(examined_sticker_set_ids, featured_sticker_set_ids_[type]);
+ }
+
+ vector<StickerSetId> sets_to_load;
bool need_load = false;
- for (const auto &sticker_set_id : installed_sticker_set_ids_[0]) {
+ for (const auto &sticker_set_id : examined_sticker_set_ids) {
const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- CHECK(sticker_set->is_inited);
- CHECK(!sticker_set->is_archived);
- if (!sticker_set->is_loaded) {
+ CHECK(sticker_set->is_inited_);
+ if (!sticker_set->is_loaded_) {
sets_to_load.push_back(sticker_set_id);
- if (!sticker_set->was_loaded) {
+ if (!sticker_set->was_loaded_) {
need_load = true;
}
}
}
vector<FileId> prepend_sticker_ids;
- if (!emoji.empty()) {
+ if (!query.empty() && sticker_type == StickerType::Regular) {
prepend_sticker_ids.reserve(favorite_sticker_ids_.size() + recent_sticker_ids_[0].size());
append(prepend_sticker_ids, recent_sticker_ids_[0]);
for (auto sticker_id : favorite_sticker_ids_) {
- if (std::find(prepend_sticker_ids.begin(), prepend_sticker_ids.end(), sticker_id) == prepend_sticker_ids.end()) {
+ if (!td::contains(prepend_sticker_ids, sticker_id)) {
prepend_sticker_ids.push_back(sticker_id);
}
}
+ auto prefer_animated = [this](FileId lhs, FileId rhs) {
+ const Sticker *lhs_s = get_sticker(lhs);
+ const Sticker *rhs_s = get_sticker(rhs);
+ CHECK(lhs_s != nullptr && rhs_s != nullptr);
+ return is_sticker_format_animated(lhs_s->format_) && !is_sticker_format_animated(rhs_s->format_);
+ };
+ // std::stable_sort(prepend_sticker_ids.begin(), prepend_sticker_ids.begin() + recent_sticker_ids_[0].size(),
+ // prefer_animated);
+ std::stable_sort(prepend_sticker_ids.begin() + recent_sticker_ids_[0].size(), prepend_sticker_ids.end(),
+ prefer_animated);
+
+ LOG(INFO) << "Have " << recent_sticker_ids_[0] << " recent and " << favorite_sticker_ids_ << " favorite stickers";
for (const auto &sticker_id : prepend_sticker_ids) {
const Sticker *s = get_sticker(sticker_id);
- if (s->set_id != 0 && std::find(sets_to_load.begin(), sets_to_load.end(), s->set_id) == sets_to_load.end()) {
- const StickerSet *sticker_set = get_sticker_set(s->set_id);
- if (sticker_set == nullptr || !sticker_set->is_loaded) {
- sets_to_load.push_back(s->set_id);
- if (sticker_set == nullptr || !sticker_set->was_loaded) {
+ CHECK(s != nullptr);
+ LOG(INFO) << "Have prepend sticker " << sticker_id << " from " << s->set_id_;
+ if (s->set_id_.is_valid() && !td::contains(sets_to_load, s->set_id_)) {
+ const StickerSet *sticker_set = get_sticker_set(s->set_id_);
+ if (sticker_set == nullptr || !sticker_set->is_loaded_) {
+ sets_to_load.push_back(s->set_id_);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
need_load = true;
}
}
@@ -1823,40 +4465,68 @@ vector<FileId> StickersManager::get_stickers(string emoji, int32 limit, bool for
}
}
+ bool allow_premium = false;
+ if (sticker_type == StickerType::CustomEmoji) {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ if (dialog_id.get_user_id() == td_->contacts_manager_->get_my_id()) {
+ allow_premium = true;
+ }
+ break;
+ case DialogType::SecretChat:
+ if (td_->contacts_manager_->get_secret_chat_layer(dialog_id.get_secret_chat_id()) <
+ static_cast<int32>(SecretChatLayer::SpoilerAndCustomEmojiEntities)) {
+ promise.set_value(Unit());
+ return {};
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
vector<FileId> result;
auto limit_size_t = static_cast<size_t>(limit);
- if (emoji.empty()) {
- for (const auto &sticker_set_id : installed_sticker_set_ids_[0]) {
+ if (query.empty()) {
+ for (const auto &sticker_set_id : examined_sticker_set_ids) {
const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
- if (sticker_set == nullptr || !sticker_set->was_loaded) {
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
continue;
}
- append(result, sticker_set->sticker_ids);
+ append(result, sticker_set->sticker_ids_);
if (result.size() > limit_size_t) {
result.resize(limit_size_t);
break;
}
}
} else {
- vector<int64> examined_sticker_set_ids; // = featured_sticker_set_ids_;
- for (const auto &sticker_set_id : installed_sticker_set_ids_[0]) {
- if (std::find(examined_sticker_set_ids.begin(), examined_sticker_set_ids.end(), sticker_set_id) ==
- examined_sticker_set_ids.end()) {
- examined_sticker_set_ids.push_back(sticker_set_id);
- }
- }
+ auto prepared_query = utf8_prepare_search_string(query);
+ LOG(INFO) << "Search stickers by " << query << " and keyword " << prepared_query;
+ vector<const StickerSet *> examined_sticker_sets;
for (const auto &sticker_set_id : examined_sticker_set_ids) {
const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
- if (sticker_set == nullptr || !sticker_set->was_loaded) {
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
continue;
}
- auto it = sticker_set->emoji_stickers_map_.find(emoji);
- if (it != sticker_set->emoji_stickers_map_.end()) {
- append(result, it->second);
+ if (!td::contains(examined_sticker_sets, sticker_set)) {
+ examined_sticker_sets.push_back(sticker_set);
}
}
+ std::stable_sort(
+ examined_sticker_sets.begin(), examined_sticker_sets.end(), [](const StickerSet *lhs, const StickerSet *rhs) {
+ if (lhs->is_installed_ != rhs->is_installed_) {
+ return lhs->is_installed_;
+ }
+ if (lhs->is_archived_ != rhs->is_archived_) {
+ return lhs->is_archived_;
+ }
+ return is_sticker_format_animated(lhs->sticker_format_) && !is_sticker_format_animated(rhs->sticker_format_);
+ });
+ for (auto sticker_set : examined_sticker_sets) {
+ find_sticker_set_stickers(sticker_set, query, prepared_query, result);
+ }
vector<FileId> sorted;
sorted.reserve(min(limit_size_t, result.size()));
@@ -1864,6 +4534,7 @@ vector<FileId> StickersManager::get_stickers(string emoji, int32 limit, bool for
const size_t MAX_RECENT_STICKERS = 5;
for (size_t i = 0; i < prepend_sticker_ids.size(); i++) {
if (sorted.size() == MAX_RECENT_STICKERS && i < recent_stickers_size) {
+ LOG(INFO) << "Skip recent sticker " << prepend_sticker_ids[i];
continue;
}
@@ -1871,23 +4542,13 @@ vector<FileId> StickersManager::get_stickers(string emoji, int32 limit, bool for
bool is_good = false;
auto it = std::find(result.begin(), result.end(), sticker_id);
if (it != result.end()) {
+ LOG(INFO) << "Found prepend sticker " << sticker_id << " in installed packs at position "
+ << (it - result.begin());
*it = FileId();
is_good = true;
- } else {
- const Sticker *s = get_sticker(sticker_id);
- if (remove_emoji_modifiers(s->alt) == emoji) {
- is_good = true;
- } else if (s->set_id != 0) {
- const StickerSet *sticker_set = get_sticker_set(s->set_id);
- if (sticker_set != nullptr && sticker_set->was_loaded) {
- auto map_it = sticker_set->emoji_stickers_map_.find(emoji);
- if (map_it != sticker_set->emoji_stickers_map_.end()) {
- if (std::find(map_it->second.begin(), map_it->second.end(), sticker_id) != map_it->second.end()) {
- is_good = true;
- }
- }
- }
- }
+ } else if (can_found_sticker_by_query(sticker_id, query, prepared_query)) {
+ LOG(INFO) << "Found prepend sticker " << sticker_id;
+ is_good = true;
}
if (is_good) {
@@ -1898,13 +4559,56 @@ vector<FileId> StickersManager::get_stickers(string emoji, int32 limit, bool for
}
}
if (sorted.size() != limit_size_t) {
- for (const auto &sticker_id : result) {
- if (sticker_id.is_valid()) {
+ vector<FileId> regular_sticker_ids;
+ vector<FileId> premium_sticker_ids;
+ std::tie(regular_sticker_ids, premium_sticker_ids) = split_stickers_by_premium(result);
+ if (td_->option_manager_->get_option_boolean("is_premium") || allow_premium) {
+ auto normal_count = td_->option_manager_->get_option_integer("stickers_normal_by_emoji_per_premium_num", 2);
+ if (normal_count < 0) {
+ normal_count = 2;
+ }
+ if (normal_count > 10) {
+ normal_count = 10;
+ }
+ // premium users have normal_count normal stickers per each premium
+ size_t normal_pos = 0;
+ size_t premium_pos = 0;
+ normal_count++;
+ for (size_t pos = 1; normal_pos < regular_sticker_ids.size() || premium_pos < premium_sticker_ids.size();
+ pos++) {
+ if (pos % normal_count == 0 && premium_pos < premium_sticker_ids.size()) {
+ auto sticker_id = premium_sticker_ids[premium_pos++];
+ LOG(INFO) << "Add premium sticker " << sticker_id << " from installed sticker set";
+ sorted.push_back(sticker_id);
+ } else if (normal_pos < regular_sticker_ids.size()) {
+ auto sticker_id = regular_sticker_ids[normal_pos++];
+ LOG(INFO) << "Add normal sticker " << sticker_id << " from installed sticker set";
+ sorted.push_back(sticker_id);
+ }
+ if (sorted.size() == limit_size_t) {
+ break;
+ }
+ }
+ } else {
+ for (const auto &sticker_id : regular_sticker_ids) {
+ LOG(INFO) << "Add normal sticker " << sticker_id << " from installed sticker set";
sorted.push_back(sticker_id);
if (sorted.size() == limit_size_t) {
break;
}
}
+ if (sorted.size() < limit_size_t) {
+ auto premium_count = td_->option_manager_->get_option_integer("stickers_premium_by_emoji_num", 0);
+ if (premium_count > 0) {
+ for (const auto &sticker_id : premium_sticker_ids) {
+ LOG(INFO) << "Add premium sticker " << sticker_id << " from installed sticker set";
+ sorted.push_back(sticker_id);
+ if (sorted.size() == limit_size_t || --premium_count == 0) {
+ break;
+ }
+ }
+ }
+ }
}
}
@@ -1915,123 +4619,262 @@ vector<FileId> StickersManager::get_stickers(string emoji, int32 limit, bool for
return result;
}
-vector<FileId> StickersManager::search_stickers(string emoji, int32 limit, Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- promise.set_error(Status::Error(7, "Method is not available for bots"));
- return {};
+string StickersManager::get_found_stickers_database_key(const string &emoji) {
+ return PSTRING() << "found_stickers" << emoji;
+}
+
+void StickersManager::search_stickers(string emoji, int32 limit,
+ Promise<td_api::object_ptr<td_api::stickers>> &&promise) {
+ if (limit == 0) {
+ return promise.set_value(get_stickers_object({}));
}
- if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
- return {};
+ if (limit < 0) {
+ return promise.set_error(Status::Error(400, "Parameter limit must be positive"));
}
if (limit > MAX_FOUND_STICKERS) {
limit = MAX_FOUND_STICKERS;
}
if (emoji.empty()) {
- promise.set_error(Status::Error(3, "Emoji must be non-empty"));
- return {};
+ return promise.set_error(Status::Error(400, "Emoji must be non-empty"));
}
- emoji = remove_emoji_modifiers(emoji);
+ remove_emoji_modifiers_in_place(emoji);
if (emoji.empty()) {
- promise.set_value(Unit());
- return {};
+ return promise.set_value(get_stickers_object({}));
}
auto it = found_stickers_.find(emoji);
if (it != found_stickers_.end()) {
- promise.set_value(Unit());
- auto result_size = min(static_cast<size_t>(limit), it->second.size());
- return vector<FileId>(it->second.begin(), it->second.begin() + result_size);
+ const auto &sticker_ids = it->second.sticker_ids_;
+ auto result_size = min(static_cast<size_t>(limit), sticker_ids.size());
+ promise.set_value(get_stickers_object({sticker_ids.begin(), sticker_ids.begin() + result_size}));
+ if (Time::now() < it->second.next_reload_time_) {
+ return;
+ }
+
+ promise = Promise<td_api::object_ptr<td_api::stickers>>();
+ limit = 0;
}
auto &promises = search_stickers_queries_[emoji];
- promises.push_back(std::move(promise));
+ promises.emplace_back(limit, std::move(promise));
if (promises.size() == 1u) {
- td_->create_handler<SearchStickersQuery>()->send(std::move(emoji));
+ int64 hash = 0;
+ if (it != found_stickers_.end()) {
+ hash = get_recent_stickers_hash(it->second.sticker_ids_);
+ td_->create_handler<SearchStickersQuery>()->send(std::move(emoji), hash);
+ return;
+ }
+
+ if (G()->parameters().use_file_db) {
+ LOG(INFO) << "Trying to load stickers for " << emoji << " from database";
+ G()->td_db()->get_sqlite_pmc()->get(
+ get_found_stickers_database_key(emoji), PromiseCreator::lambda([emoji](string value) mutable {
+ send_closure(G()->stickers_manager(), &StickersManager::on_load_found_stickers_from_database,
+ std::move(emoji), std::move(value));
+ }));
+ } else {
+ td_->create_handler<SearchStickersQuery>()->send(std::move(emoji), 0);
+ }
+ }
+}
+
+void StickersManager::on_load_found_stickers_from_database(string emoji, string value) {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (value.empty()) {
+ LOG(INFO) << "Stickers for " << emoji << " aren't found in database";
+ td_->create_handler<SearchStickersQuery>()->send(std::move(emoji), 0);
+ return;
}
- return {};
+ LOG(INFO) << "Successfully loaded stickers for " << emoji << " from database";
+
+ auto &found_stickers = found_stickers_[emoji];
+ CHECK(found_stickers.next_reload_time_ == 0);
+ auto status = log_event_parse(found_stickers, value);
+ if (status.is_error()) {
+ LOG(ERROR) << "Can't load stickers for emoji: " << status << ' ' << format::as_hex_dump<4>(Slice(value));
+ found_stickers_.erase(emoji);
+ td_->create_handler<SearchStickersQuery>()->send(std::move(emoji), 0);
+ return;
+ }
+
+ on_search_stickers_finished(emoji, found_stickers);
+}
+
+void StickersManager::on_search_stickers_finished(const string &emoji, const FoundStickers &found_stickers) {
+ auto it = search_stickers_queries_.find(emoji);
+ CHECK(it != search_stickers_queries_.end());
+ CHECK(!it->second.empty());
+ auto queries = std::move(it->second);
+ search_stickers_queries_.erase(it);
+
+ const auto &sticker_ids = found_stickers.sticker_ids_;
+ for (auto &query : queries) {
+ auto result_size = min(static_cast<size_t>(query.first), sticker_ids.size());
+ query.second.set_value(get_stickers_object({sticker_ids.begin(), sticker_ids.begin() + result_size}));
+ }
}
void StickersManager::on_find_stickers_success(const string &emoji,
tl_object_ptr<telegram_api::messages_Stickers> &&stickers) {
CHECK(stickers != nullptr);
switch (stickers->get_id()) {
- case telegram_api::messages_stickersNotModified::ID:
- return on_find_stickers_fail(emoji, Status::Error(500, "Receive messages.stickerNotModified"));
+ case telegram_api::messages_stickersNotModified::ID: {
+ auto it = found_stickers_.find(emoji);
+ if (it == found_stickers_.end()) {
+ return on_find_stickers_fail(emoji, Status::Error(500, "Receive messages.stickerNotModified"));
+ }
+ auto &found_stickers = it->second;
+ found_stickers.next_reload_time_ = Time::now() + found_stickers.cache_time_;
+ return on_search_stickers_finished(emoji, found_stickers);
+ }
case telegram_api::messages_stickers::ID: {
- auto found_stickers = move_tl_object_as<telegram_api::messages_stickers>(stickers);
- vector<FileId> &sticker_ids = found_stickers_[emoji];
- CHECK(sticker_ids.empty());
+ auto received_stickers = move_tl_object_as<telegram_api::messages_stickers>(stickers);
- for (auto &sticker : found_stickers->stickers_) {
- FileId sticker_id = on_get_sticker_document(std::move(sticker), false).second;
+ auto &found_stickers = found_stickers_[emoji];
+ found_stickers.cache_time_ = 300;
+ found_stickers.next_reload_time_ = Time::now() + found_stickers.cache_time_;
+ found_stickers.sticker_ids_.clear();
+
+ for (auto &sticker : received_stickers->stickers_) {
+ FileId sticker_id = on_get_sticker_document(std::move(sticker), StickerFormat::Unknown).second;
if (sticker_id.is_valid()) {
- sticker_ids.push_back(sticker_id);
+ found_stickers.sticker_ids_.push_back(sticker_id);
}
}
- break;
+
+ if (G()->parameters().use_file_db && !G()->close_flag()) {
+ LOG(INFO) << "Save stickers for " << emoji << " to database";
+ G()->td_db()->get_sqlite_pmc()->set(get_found_stickers_database_key(emoji),
+ log_event_store(found_stickers).as_slice().str(), Auto());
+ }
+
+ return on_search_stickers_finished(emoji, found_stickers);
}
default:
UNREACHABLE();
}
+}
+
+void StickersManager::on_find_stickers_fail(const string &emoji, Status &&error) {
+ if (found_stickers_.count(emoji) != 0) {
+ found_stickers_[emoji].cache_time_ = Random::fast(40, 80);
+ return on_find_stickers_success(emoji, make_tl_object<telegram_api::messages_stickersNotModified>());
+ }
auto it = search_stickers_queries_.find(emoji);
CHECK(it != search_stickers_queries_.end());
CHECK(!it->second.empty());
- auto promises = std::move(it->second);
+ auto queries = std::move(it->second);
search_stickers_queries_.erase(it);
- for (auto &promise : promises) {
- promise.set_value(Unit());
+ for (auto &query : queries) {
+ query.second.set_error(error.clone());
}
}
-void StickersManager::on_find_stickers_fail(const string &emoji, Status &&error) {
- CHECK(found_stickers_.count(emoji) == 0);
+void StickersManager::get_premium_stickers(int32 limit, Promise<td_api::object_ptr<td_api::stickers>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
- auto it = search_stickers_queries_.find(emoji);
- CHECK(it != search_stickers_queries_.end());
- CHECK(!it->second.empty());
- auto promises = std::move(it->second);
- search_stickers_queries_.erase(it);
+ if (limit == 0) {
+ return promise.set_value(get_stickers_object({}));
+ }
+ if (limit > MAX_FOUND_STICKERS) {
+ limit = MAX_FOUND_STICKERS;
+ }
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+ MultiPromiseActorSafe mpas{"GetPremiumStickersMultiPromiseActor"};
+ mpas.add_promise(PromiseCreator::lambda(
+ [actor_id = actor_id(this), limit, promise = std::move(promise)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::do_get_premium_stickers, limit, std::move(promise));
+ }
+ }));
+
+ auto lock = mpas.get_promise();
+ search_stickers("📂⭐️", limit,
+ PromiseCreator::lambda(
+ [promise = mpas.get_promise()](Result<td_api::object_ptr<td_api::stickers>> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(Unit());
+ }
+ }));
+ get_stickers(StickerType::Regular, string(), 1, DialogId(), false, mpas.get_promise());
+ lock.set_value(Unit());
+}
+
+void StickersManager::do_get_premium_stickers(int32 limit, Promise<td_api::object_ptr<td_api::stickers>> &&promise) {
+ auto type = static_cast<int32>(StickerType::Regular);
+ CHECK(are_installed_sticker_sets_loaded_[type]);
+
+ vector<FileId> sticker_ids;
+ auto limit_size_t = static_cast<size_t>(limit);
+ for (const auto &sticker_set_id : installed_sticker_set_ids_[type]) {
+ const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ continue;
+ }
+
+ for (auto premium_sticker_position : sticker_set->premium_sticker_positions_) {
+ sticker_ids.push_back(sticker_set->sticker_ids_[premium_sticker_position]);
+ if (sticker_ids.size() == limit_size_t) {
+ return promise.set_value(get_stickers_object(sticker_ids));
+ }
+ }
+ }
+
+ auto it = found_stickers_.find(remove_emoji_modifiers("📂⭐️"));
+ CHECK(it != found_stickers_.end());
+ for (auto sticker_id : it->second.sticker_ids_) {
+ if (td::contains(sticker_ids, sticker_id)) {
+ continue;
+ }
+ sticker_ids.push_back(sticker_id);
+ if (sticker_ids.size() == limit_size_t) {
+ break;
+ }
}
+ promise.set_value(get_stickers_object(sticker_ids));
}
-vector<int64> StickersManager::get_installed_sticker_sets(bool is_masks, Promise<Unit> &&promise) {
- if (!are_installed_sticker_sets_loaded_[is_masks]) {
- load_installed_sticker_sets(is_masks, std::move(promise));
+vector<StickerSetId> StickersManager::get_installed_sticker_sets(StickerType sticker_type, Promise<Unit> &&promise) {
+ auto type = static_cast<int32>(sticker_type);
+ if (!are_installed_sticker_sets_loaded_[type]) {
+ load_installed_sticker_sets(sticker_type, std::move(promise));
return {};
}
- reload_installed_sticker_sets(is_masks, false);
+ reload_installed_sticker_sets(sticker_type, false);
promise.set_value(Unit());
- return installed_sticker_set_ids_[is_masks];
+ return installed_sticker_set_ids_[type];
}
bool StickersManager::update_sticker_set_cache(const StickerSet *sticker_set, Promise<Unit> &promise) {
CHECK(sticker_set != nullptr);
- auto set_id = sticker_set->id;
- if (!sticker_set->is_loaded) {
- if (!sticker_set->was_loaded || td_->auth_manager_->is_bot()) {
+ auto set_id = sticker_set->id_;
+ if (!sticker_set->is_loaded_) {
+ if (!sticker_set->was_loaded_ || td_->auth_manager_->is_bot()) {
load_sticker_sets({set_id}, std::move(promise));
return true;
} else {
load_sticker_sets({set_id}, Auto());
}
- } else if (sticker_set->is_installed) {
- reload_installed_sticker_sets(sticker_set->is_masks, false);
+ } else if (sticker_set->is_installed_) {
+ reload_installed_sticker_sets(sticker_set->sticker_type_, false);
} else {
- if (G()->unix_time() >= sticker_set->expires_at) {
+ if (G()->unix_time() >= sticker_set->expires_at_) {
if (td_->auth_manager_->is_bot()) {
- reload_sticker_set(set_id, get_input_sticker_set(sticker_set), std::move(promise));
+ do_reload_sticker_set(set_id, get_input_sticker_set(sticker_set), sticker_set->hash_, std::move(promise));
return true;
} else {
- reload_sticker_set(set_id, get_input_sticker_set(sticker_set), Auto());
+ do_reload_sticker_set(set_id, get_input_sticker_set(sticker_set), sticker_set->hash_, Auto());
}
}
}
@@ -2039,48 +4882,49 @@ bool StickersManager::update_sticker_set_cache(const StickerSet *sticker_set, Pr
return false;
}
-int64 StickersManager::get_sticker_set(int64 set_id, Promise<Unit> &&promise) {
+StickerSetId StickersManager::get_sticker_set(StickerSetId set_id, Promise<Unit> &&promise) {
const StickerSet *sticker_set = get_sticker_set(set_id);
if (sticker_set == nullptr) {
- if (set_id == GREAT_MINDS_SET_ID) {
- reload_sticker_set(set_id, make_tl_object<telegram_api::inputStickerSetID>(set_id, 0), std::move(promise));
- return 0;
+ if (set_id.get() == GREAT_MINDS_SET_ID) {
+ do_reload_sticker_set(set_id, make_tl_object<telegram_api::inputStickerSetID>(set_id.get(), 0), 0,
+ std::move(promise));
+ return StickerSetId();
}
promise.set_error(Status::Error(400, "Sticker set not found"));
- return 0;
+ return StickerSetId();
}
if (update_sticker_set_cache(sticker_set, promise)) {
- return 0;
+ return StickerSetId();
}
promise.set_value(Unit());
return set_id;
}
-int64 StickersManager::search_sticker_set(const string &short_name_to_search, Promise<Unit> &&promise) {
+StickerSetId StickersManager::search_sticker_set(const string &short_name_to_search, Promise<Unit> &&promise) {
string short_name = clean_username(short_name_to_search);
- auto it = short_name_to_sticker_set_id_.find(short_name);
- const StickerSet *sticker_set = it == short_name_to_sticker_set_id_.end() ? nullptr : get_sticker_set(it->second);
+ const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name));
if (sticker_set == nullptr) {
auto set_to_load = make_tl_object<telegram_api::inputStickerSetShortName>(short_name);
- reload_sticker_set(0, std::move(set_to_load), std::move(promise));
- return 0;
+ do_reload_sticker_set(StickerSetId(), std::move(set_to_load), 0, std::move(promise));
+ return StickerSetId();
}
if (update_sticker_set_cache(sticker_set, promise)) {
- return 0;
+ return StickerSetId();
}
promise.set_value(Unit());
- return sticker_set->id;
+ return sticker_set->id_;
}
-std::pair<int32, vector<int64>> StickersManager::search_installed_sticker_sets(bool is_masks, const string &query,
- int32 limit, Promise<Unit> &&promise) {
- LOG(INFO) << "Search installed " << (is_masks ? "masks " : "") << "sticker sets with query = \"" << query
+std::pair<int32, vector<StickerSetId>> StickersManager::search_installed_sticker_sets(StickerType sticker_type,
+ const string &query, int32 limit,
+ Promise<Unit> &&promise) {
+ LOG(INFO) << "Search installed " << sticker_type << " sticker sets with query = \"" << query
<< "\" and limit = " << limit;
if (limit < 0) {
@@ -2088,18 +4932,19 @@ std::pair<int32, vector<int64>> StickersManager::search_installed_sticker_sets(b
return {};
}
- if (!are_installed_sticker_sets_loaded_[is_masks]) {
- load_installed_sticker_sets(is_masks, std::move(promise));
+ auto type = static_cast<int32>(sticker_type);
+ if (!are_installed_sticker_sets_loaded_[type]) {
+ load_installed_sticker_sets(sticker_type, std::move(promise));
return {};
}
- reload_installed_sticker_sets(is_masks, false);
+ reload_installed_sticker_sets(sticker_type, false);
- std::pair<size_t, vector<int64>> result = installed_sticker_sets_hints_[is_masks].search(query, limit);
+ std::pair<size_t, vector<int64>> result = installed_sticker_sets_hints_[type].search(query, limit);
promise.set_value(Unit());
- return {narrow_cast<int32>(result.first), std::move(result.second)};
+ return {narrow_cast<int32>(result.first), convert_sticker_set_ids(result.second)};
}
-vector<int64> StickersManager::search_sticker_sets(const string &query, Promise<Unit> &&promise) {
+vector<StickerSetId> StickersManager::search_sticker_sets(const string &query, Promise<Unit> &&promise) {
auto q = clean_name(query, 1000);
auto it = found_sticker_sets_.find(q);
if (it != found_sticker_sets_.end()) {
@@ -2124,16 +4969,16 @@ void StickersManager::on_find_sticker_sets_success(
return on_find_sticker_sets_fail(query, Status::Error(500, "Receive messages.foundStickerSetsNotModified"));
case telegram_api::messages_foundStickerSets::ID: {
auto found_stickers_sets = move_tl_object_as<telegram_api::messages_foundStickerSets>(sticker_sets);
- vector<int64> &sticker_set_ids = found_sticker_sets_[query];
+ vector<StickerSetId> &sticker_set_ids = found_sticker_sets_[query];
CHECK(sticker_set_ids.empty());
for (auto &sticker_set : found_stickers_sets->sets_) {
- int64 set_id = on_get_sticker_set_covered(std::move(sticker_set), true);
- if (set_id == 0) {
+ StickerSetId set_id = on_get_sticker_set_covered(std::move(sticker_set), true, "on_find_sticker_sets_success");
+ if (!set_id.is_valid()) {
continue;
}
- update_sticker_set(get_sticker_set(set_id));
+ update_sticker_set(get_sticker_set(set_id), "on_find_sticker_sets_success");
sticker_set_ids.push_back(set_id);
}
@@ -2150,9 +4995,7 @@ void StickersManager::on_find_sticker_sets_success(
auto promises = std::move(it->second);
search_sticker_sets_queries_.erase(it);
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(promises);
}
void StickersManager::on_find_sticker_sets_fail(const string &query, Status &&error) {
@@ -2164,12 +5007,11 @@ void StickersManager::on_find_sticker_sets_fail(const string &query, Status &&er
auto promises = std::move(it->second);
search_sticker_sets_queries_.erase(it);
- for (auto &promise : promises) {
- promise.set_error(error.clone());
- }
+ fail_promises(promises, std::move(error));
}
-void StickersManager::change_sticker_set(int64 set_id, bool is_installed, bool is_archived, Promise<Unit> &&promise) {
+void StickersManager::change_sticker_set(StickerSetId set_id, bool is_installed, bool is_archived,
+ Promise<Unit> &&promise) {
if (is_installed && is_archived) {
return promise.set_error(Status::Error(400, "Sticker set can't be installed and archived simultaneously"));
}
@@ -2177,12 +5019,13 @@ void StickersManager::change_sticker_set(int64 set_id, bool is_installed, bool i
if (sticker_set == nullptr) {
return promise.set_error(Status::Error(400, "Sticker set not found"));
}
- if (!sticker_set->is_inited) {
+ if (!sticker_set->is_inited_) {
load_sticker_sets({set_id}, std::move(promise));
return;
}
- if (!are_installed_sticker_sets_loaded_[sticker_set->is_masks]) {
- load_installed_sticker_sets(sticker_set->is_masks, std::move(promise));
+ auto type = static_cast<int32>(sticker_set->sticker_type_);
+ if (!are_installed_sticker_sets_loaded_[type]) {
+ load_installed_sticker_sets(sticker_set->sticker_type_, std::move(promise));
return;
}
@@ -2190,7 +5033,7 @@ void StickersManager::change_sticker_set(int64 set_id, bool is_installed, bool i
is_installed = true;
}
if (is_installed) {
- if (sticker_set->is_installed && is_archived == sticker_set->is_archived) {
+ if (sticker_set->is_installed_ && is_archived == sticker_set->is_archived_) {
return promise.set_value(Unit());
}
@@ -2199,7 +5042,7 @@ void StickersManager::change_sticker_set(int64 set_id, bool is_installed, bool i
return;
}
- if (!sticker_set->is_installed) {
+ if (!sticker_set->is_installed_) {
return promise.set_value(Unit());
}
@@ -2208,99 +5051,114 @@ void StickersManager::change_sticker_set(int64 set_id, bool is_installed, bool i
void StickersManager::on_update_sticker_set(StickerSet *sticker_set, bool is_installed, bool is_archived,
bool is_changed, bool from_database) {
- LOG(INFO) << "Update sticker set " << sticker_set->id << ": installed = " << is_installed
- << ", archived = " << is_archived << ", changed = " << is_changed;
- CHECK(sticker_set->is_inited);
+ LOG(INFO) << "Update " << sticker_set->id_ << ": installed = " << is_installed << ", archived = " << is_archived
+ << ", changed = " << is_changed << ", from_database = " << from_database;
+ CHECK(sticker_set->is_inited_);
if (is_archived) {
is_installed = true;
}
- if (sticker_set->is_installed == is_installed && sticker_set->is_archived == is_archived) {
+ if (sticker_set->is_installed_ == is_installed && sticker_set->is_archived_ == is_archived) {
return;
}
- bool was_added = sticker_set->is_installed && !sticker_set->is_archived;
- bool was_archived = sticker_set->is_archived;
- sticker_set->is_installed = is_installed;
- sticker_set->is_archived = is_archived;
+ bool was_added = sticker_set->is_installed_ && !sticker_set->is_archived_;
+ bool was_archived = sticker_set->is_archived_;
+ sticker_set->is_installed_ = is_installed;
+ sticker_set->is_archived_ = is_archived;
if (!from_database) {
- sticker_set->is_changed = true;
+ sticker_set->is_changed_ = true;
}
- bool is_added = sticker_set->is_installed && !sticker_set->is_archived;
+ bool is_added = sticker_set->is_installed_ && !sticker_set->is_archived_;
+ auto type = static_cast<int32>(sticker_set->sticker_type_);
if (was_added != is_added) {
- vector<int64> &sticker_set_ids = installed_sticker_set_ids_[sticker_set->is_masks];
- need_update_installed_sticker_sets_[sticker_set->is_masks] = true;
+ vector<StickerSetId> &sticker_set_ids = installed_sticker_set_ids_[type];
+ need_update_installed_sticker_sets_[type] = true;
if (is_added) {
- installed_sticker_sets_hints_[sticker_set->is_masks].add(sticker_set->id,
- sticker_set->title + " " + sticker_set->short_name);
- sticker_set_ids.insert(sticker_set_ids.begin(), sticker_set->id);
+ installed_sticker_sets_hints_[type].add(sticker_set->id_.get(),
+ PSLICE() << sticker_set->title_ << ' ' << sticker_set->short_name_);
+ sticker_set_ids.insert(sticker_set_ids.begin(), sticker_set->id_);
} else {
- installed_sticker_sets_hints_[sticker_set->is_masks].remove(sticker_set->id);
- sticker_set_ids.erase(std::remove(sticker_set_ids.begin(), sticker_set_ids.end(), sticker_set->id),
- sticker_set_ids.end());
+ installed_sticker_sets_hints_[type].remove(sticker_set->id_.get());
+ td::remove(sticker_set_ids, sticker_set->id_);
}
}
if (was_archived != is_archived && is_changed) {
- int32 &total_count = total_archived_sticker_set_count_[sticker_set->is_masks];
- vector<int64> &sticker_set_ids = archived_sticker_set_ids_[sticker_set->is_masks];
+ int32 &total_count = total_archived_sticker_set_count_[type];
+ vector<StickerSetId> &sticker_set_ids = archived_sticker_set_ids_[type];
if (total_count < 0) {
return;
}
if (is_archived) {
- total_count++;
- sticker_set_ids.insert(sticker_set_ids.begin(), sticker_set->id);
+ if (!td::contains(sticker_set_ids, sticker_set->id_)) {
+ total_count++;
+ sticker_set_ids.insert(sticker_set_ids.begin(), sticker_set->id_);
+ }
} else {
total_count--;
- sticker_set_ids.erase(std::remove(sticker_set_ids.begin(), sticker_set_ids.end(), sticker_set->id),
- sticker_set_ids.end());
+ if (total_count < 0) {
+ LOG(ERROR) << "Total count of archived sticker sets became negative";
+ total_count = 0;
+ }
+ td::remove(sticker_set_ids, sticker_set->id_);
}
}
}
-void StickersManager::load_installed_sticker_sets(bool is_masks, Promise<Unit> &&promise) {
+void StickersManager::load_installed_sticker_sets(StickerType sticker_type, Promise<Unit> &&promise) {
+ auto type = static_cast<int32>(sticker_type);
if (td_->auth_manager_->is_bot()) {
- are_installed_sticker_sets_loaded_[is_masks] = true;
+ are_installed_sticker_sets_loaded_[type] = true;
}
- if (are_installed_sticker_sets_loaded_[is_masks]) {
+ if (are_installed_sticker_sets_loaded_[type]) {
promise.set_value(Unit());
return;
}
- load_installed_sticker_sets_queries_[is_masks].push_back(std::move(promise));
- if (load_installed_sticker_sets_queries_[is_masks].size() == 1u) {
+ load_installed_sticker_sets_queries_[type].push_back(std::move(promise));
+ if (load_installed_sticker_sets_queries_[type].size() == 1u) {
if (G()->parameters().use_file_db) {
- LOG(INFO) << "Trying to load installed " << (is_masks ? "masks " : "") << "sticker sets from database";
- G()->td_db()->get_sqlite_pmc()->get(is_masks ? "sss1" : "sss0", PromiseCreator::lambda([is_masks](string value) {
- send_closure(G()->stickers_manager(),
- &StickersManager::on_load_installed_sticker_sets_from_database,
- is_masks, std::move(value));
- }));
+ LOG(INFO) << "Trying to load installed " << sticker_type << " sticker sets from database";
+ G()->td_db()->get_sqlite_pmc()->get(
+ PSTRING() << "sss" << type, PromiseCreator::lambda([sticker_type](string value) {
+ send_closure(G()->stickers_manager(), &StickersManager::on_load_installed_sticker_sets_from_database,
+ sticker_type, std::move(value));
+ }));
} else {
- LOG(INFO) << "Trying to load installed " << (is_masks ? "masks " : "") << "sticker sets from server";
- reload_installed_sticker_sets(is_masks, true);
+ LOG(INFO) << "Trying to load installed " << sticker_type << " sticker sets from server";
+ reload_installed_sticker_sets(sticker_type, true);
}
}
}
-void StickersManager::on_load_installed_sticker_sets_from_database(bool is_masks, string value) {
+void StickersManager::on_load_installed_sticker_sets_from_database(StickerType sticker_type, string value) {
+ if (G()->close_flag()) {
+ return;
+ }
if (value.empty()) {
- LOG(INFO) << "Installed " << (is_masks ? "mask " : "") << "sticker sets aren't found in database";
- reload_installed_sticker_sets(is_masks, true);
+ LOG(INFO) << "Installed " << sticker_type << " sticker sets aren't found in database";
+ reload_installed_sticker_sets(sticker_type, true);
return;
}
- LOG(INFO) << "Successfully loaded installed " << (is_masks ? "mask " : "") << "sticker sets list of size "
- << value.size() << " from database";
+ LOG(INFO) << "Successfully loaded installed " << sticker_type << " sticker set list of size " << value.size()
+ << " from database";
StickerSetListLogEvent log_event;
- log_event_parse(log_event, value).ensure();
+ auto status = log_event_parse(log_event, value);
+ if (status.is_error()) {
+ // can't happen unless database is broken
+ LOG(ERROR) << "Can't load installed sticker set list: " << status << ' ' << format::as_hex_dump<4>(Slice(value));
+ return reload_installed_sticker_sets(sticker_type, true);
+ }
+ CHECK(!log_event.is_premium_);
- vector<int64> sets_to_load;
- for (auto sticker_set_id : log_event.sticker_set_ids) {
+ vector<StickerSetId> sets_to_load;
+ for (auto sticker_set_id : log_event.sticker_set_ids_) {
StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- if (!sticker_set->is_inited) {
+ if (!sticker_set->is_inited_) {
sets_to_load.push_back(sticker_set_id);
}
}
@@ -2308,230 +5166,262 @@ void StickersManager::on_load_installed_sticker_sets_from_database(bool is_masks
load_sticker_sets_without_stickers(
std::move(sets_to_load),
- PromiseCreator::lambda(
- [is_masks, sticker_set_ids = std::move(log_event.sticker_set_ids)](Result<> result) mutable {
- if (result.is_ok()) {
- send_closure(G()->stickers_manager(), &StickersManager::on_load_installed_sticker_sets_finished, is_masks,
- std::move(sticker_set_ids), true);
- }
- }));
+ PromiseCreator::lambda([sticker_type,
+ sticker_set_ids = std::move(log_event.sticker_set_ids_)](Result<Unit> result) mutable {
+ if (result.is_ok()) {
+ send_closure(G()->stickers_manager(), &StickersManager::on_load_installed_sticker_sets_finished, sticker_type,
+ std::move(sticker_set_ids), true);
+ } else {
+ send_closure(G()->stickers_manager(), &StickersManager::reload_installed_sticker_sets, sticker_type, true);
+ }
+ }));
}
-void StickersManager::on_load_installed_sticker_sets_finished(bool is_masks, vector<int64> &&installed_sticker_set_ids,
+void StickersManager::on_load_installed_sticker_sets_finished(StickerType sticker_type,
+ vector<StickerSetId> &&installed_sticker_set_ids,
bool from_database) {
bool need_reload = false;
- vector<int64> old_installed_sticker_set_ids;
- if (!are_installed_sticker_sets_loaded_[is_masks] && !installed_sticker_set_ids_[is_masks].empty()) {
- old_installed_sticker_set_ids = std::move(installed_sticker_set_ids_[is_masks]);
+ vector<StickerSetId> old_installed_sticker_set_ids;
+ auto type = static_cast<int32>(sticker_type);
+ if (!are_installed_sticker_sets_loaded_[type] && !installed_sticker_set_ids_[type].empty()) {
+ old_installed_sticker_set_ids = std::move(installed_sticker_set_ids_[type]);
}
- installed_sticker_set_ids_[is_masks].clear();
+ installed_sticker_set_ids_[type].clear();
for (auto set_id : installed_sticker_set_ids) {
- CHECK(set_id != 0);
+ CHECK(set_id.is_valid());
auto sticker_set = get_sticker_set(set_id);
CHECK(sticker_set != nullptr);
- CHECK(sticker_set->is_inited);
- CHECK(sticker_set->is_masks == is_masks);
- if (sticker_set->is_installed && !sticker_set->is_archived) {
- installed_sticker_set_ids_[is_masks].push_back(set_id);
+ CHECK(sticker_set->is_inited_);
+ if (sticker_set->is_installed_ && !sticker_set->is_archived_ && sticker_set->sticker_type_ == sticker_type) {
+ installed_sticker_set_ids_[type].push_back(set_id);
} else {
need_reload = true;
}
}
if (need_reload) {
- LOG(ERROR) << "Reload installed " << (is_masks ? "masks " : "") << "sticker sets, because only "
- << installed_sticker_set_ids_[is_masks].size() << " of " << installed_sticker_set_ids.size()
- << " are really installed";
- reload_installed_sticker_sets(is_masks, true);
+ LOG(ERROR) << "Reload installed " << sticker_type << " sticker sets, because only "
+ << installed_sticker_set_ids_[type].size() << " of " << installed_sticker_set_ids.size()
+ << " are really installed after loading from " << (from_database ? "database" : "server");
+ reload_installed_sticker_sets(sticker_type, true);
} else if (!old_installed_sticker_set_ids.empty() &&
- old_installed_sticker_set_ids != installed_sticker_set_ids_[is_masks]) {
- LOG(ERROR) << "Reload installed " << (is_masks ? "masks " : "") << "sticker sets, because they has changed from "
- << old_installed_sticker_set_ids << " to " << installed_sticker_set_ids_[is_masks];
- reload_installed_sticker_sets(is_masks, true);
+ old_installed_sticker_set_ids != installed_sticker_set_ids_[type]) {
+ LOG(ERROR) << "Reload installed " << sticker_type << " sticker sets, because they has changed from "
+ << old_installed_sticker_set_ids << " to " << installed_sticker_set_ids_[type] << " after loading from "
+ << (from_database ? "database" : "server");
+ reload_installed_sticker_sets(sticker_type, true);
}
- are_installed_sticker_sets_loaded_[is_masks] = true;
- need_update_installed_sticker_sets_[is_masks] = true;
+ are_installed_sticker_sets_loaded_[type] = true;
+ need_update_installed_sticker_sets_[type] = true;
send_update_installed_sticker_sets(from_database);
- auto promises = std::move(load_installed_sticker_sets_queries_[is_masks]);
- load_installed_sticker_sets_queries_[is_masks].clear();
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(load_installed_sticker_sets_queries_[type]);
}
-string StickersManager::get_sticker_set_database_key(int64 set_id) {
- return "ss" + to_string(set_id);
+string StickersManager::get_sticker_set_database_key(StickerSetId set_id) {
+ return PSTRING() << "ss" << set_id.get();
}
-string StickersManager::get_full_sticker_set_database_key(int64 set_id) {
- return "ssf" + to_string(set_id);
+string StickersManager::get_full_sticker_set_database_key(StickerSetId set_id) {
+ return PSTRING() << "ssf" << set_id.get();
}
-string StickersManager::get_sticker_set_database_value(const StickerSet *s, bool with_stickers) {
+string StickersManager::get_sticker_set_database_value(const StickerSet *s, bool with_stickers, const char *source) {
LogEventStorerCalcLength storer_calc_length;
- store_sticker_set(s, with_stickers, storer_calc_length);
+ store_sticker_set(s, with_stickers, storer_calc_length, source);
BufferSlice value_buffer{storer_calc_length.get_length()};
auto value = value_buffer.as_slice();
- LOG(DEBUG) << "Sticker set " << s->id << " serialized size is " << value.size();
+ LOG(DEBUG) << "Serialized size of " << s->id_ << " is " << value.size();
- LogEventStorerUnsafe storer_unsafe(value.begin());
- store_sticker_set(s, with_stickers, storer_unsafe);
+ LogEventStorerUnsafe storer_unsafe(value.ubegin());
+ store_sticker_set(s, with_stickers, storer_unsafe, source);
return value.str();
}
-void StickersManager::update_sticker_set(StickerSet *sticker_set) {
+void StickersManager::update_sticker_set(StickerSet *sticker_set, const char *source) {
CHECK(sticker_set != nullptr);
- if (sticker_set->is_changed) {
- sticker_set->is_changed = false;
- if (G()->parameters().use_file_db) {
- LOG(INFO) << "Save sticker set " << sticker_set->id << " to database";
- if (sticker_set->is_inited) {
- G()->td_db()->get_sqlite_pmc()->set(get_sticker_set_database_key(sticker_set->id),
- get_sticker_set_database_value(sticker_set, false), Auto());
+ if (sticker_set->is_changed_ || sticker_set->need_save_to_database_) {
+ if (G()->parameters().use_file_db && !G()->close_flag()) {
+ LOG(INFO) << "Save " << sticker_set->id_ << " to database from " << source;
+ if (sticker_set->is_inited_) {
+ G()->td_db()->get_sqlite_pmc()->set(get_sticker_set_database_key(sticker_set->id_),
+ get_sticker_set_database_value(sticker_set, false, source), Auto());
}
- if (sticker_set->was_loaded) {
- G()->td_db()->get_sqlite_pmc()->set(get_full_sticker_set_database_key(sticker_set->id),
- get_sticker_set_database_value(sticker_set, true), Auto());
+ if (sticker_set->was_loaded_) {
+ G()->td_db()->get_sqlite_pmc()->set(get_full_sticker_set_database_key(sticker_set->id_),
+ get_sticker_set_database_value(sticker_set, true, source), Auto());
}
}
- if (sticker_set->is_inited) {
+ if (sticker_set->is_changed_ && sticker_set->was_loaded_ && sticker_set->was_update_sent_) {
+ send_closure(G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateStickerSet>(get_sticker_set_object(sticker_set->id_)));
+ }
+ sticker_set->is_changed_ = false;
+ sticker_set->need_save_to_database_ = false;
+ if (sticker_set->is_inited_) {
update_load_requests(sticker_set, false, Status::OK());
}
}
}
-void StickersManager::load_sticker_sets(vector<int64> &&sticker_set_ids, Promise<Unit> &&promise) {
+void StickersManager::load_sticker_sets(vector<StickerSetId> &&sticker_set_ids, Promise<Unit> &&promise) {
if (sticker_set_ids.empty()) {
promise.set_value(Unit());
return;
}
- auto load_request_id = current_sticker_set_load_request_++;
+ CHECK(current_sticker_set_load_request_ < std::numeric_limits<uint32>::max());
+ auto load_request_id = ++current_sticker_set_load_request_;
StickerSetLoadRequest &load_request = sticker_set_load_requests_[load_request_id];
- load_request.promise = std::move(promise);
- load_request.left_queries = sticker_set_ids.size();
+ load_request.promise_ = std::move(promise);
+ load_request.left_queries_ = sticker_set_ids.size();
for (auto sticker_set_id : sticker_set_ids) {
StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- CHECK(!sticker_set->is_loaded);
+ CHECK(!sticker_set->is_loaded_);
- sticker_set->load_requests.push_back(load_request_id);
- if (sticker_set->load_requests.size() == 1u) {
- if (G()->parameters().use_file_db && !sticker_set->was_loaded) {
- LOG(INFO) << "Trying to load sticker set " << sticker_set_id << " with stickers from database";
+ sticker_set->load_requests_.push_back(load_request_id);
+ if (sticker_set->load_requests_.size() == 1u) {
+ if (G()->parameters().use_file_db && !sticker_set->was_loaded_) {
+ LOG(INFO) << "Trying to load " << sticker_set_id << " with stickers from database";
G()->td_db()->get_sqlite_pmc()->get(
get_full_sticker_set_database_key(sticker_set_id), PromiseCreator::lambda([sticker_set_id](string value) {
send_closure(G()->stickers_manager(), &StickersManager::on_load_sticker_set_from_database, sticker_set_id,
true, std::move(value));
}));
} else {
- LOG(INFO) << "Trying to load sticker set " << sticker_set_id << " with stickers from server";
- reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), Auto());
+ LOG(INFO) << "Trying to load " << sticker_set_id << " with stickers from server";
+ do_reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), 0, Auto());
}
}
}
}
-void StickersManager::load_sticker_sets_without_stickers(vector<int64> &&sticker_set_ids, Promise<Unit> &&promise) {
+void StickersManager::load_sticker_sets_without_stickers(vector<StickerSetId> &&sticker_set_ids,
+ Promise<Unit> &&promise) {
if (sticker_set_ids.empty()) {
promise.set_value(Unit());
return;
}
- auto load_request_id = current_sticker_set_load_request_++;
+ CHECK(current_sticker_set_load_request_ < std::numeric_limits<uint32>::max());
+ auto load_request_id = ++current_sticker_set_load_request_;
StickerSetLoadRequest &load_request = sticker_set_load_requests_[load_request_id];
- load_request.promise = std::move(promise);
- load_request.left_queries = sticker_set_ids.size();
+ load_request.promise_ = std::move(promise);
+ load_request.left_queries_ = sticker_set_ids.size();
for (auto sticker_set_id : sticker_set_ids) {
StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- CHECK(!sticker_set->is_inited);
+ CHECK(!sticker_set->is_inited_);
- if (!sticker_set->load_requests.empty()) {
- sticker_set->load_requests.push_back(load_request_id);
+ if (!sticker_set->load_requests_.empty()) {
+ sticker_set->load_requests_.push_back(load_request_id);
} else {
- sticker_set->load_without_stickers_requests.push_back(load_request_id);
- if (sticker_set->load_without_stickers_requests.size() == 1u) {
+ sticker_set->load_without_stickers_requests_.push_back(load_request_id);
+ if (sticker_set->load_without_stickers_requests_.size() == 1u) {
if (G()->parameters().use_file_db) {
- LOG(INFO) << "Trying to load sticker set " << sticker_set_id << " from database";
+ LOG(INFO) << "Trying to load " << sticker_set_id << " from database";
G()->td_db()->get_sqlite_pmc()->get(
get_sticker_set_database_key(sticker_set_id), PromiseCreator::lambda([sticker_set_id](string value) {
send_closure(G()->stickers_manager(), &StickersManager::on_load_sticker_set_from_database,
sticker_set_id, false, std::move(value));
}));
} else {
- LOG(INFO) << "Trying to load sticker set " << sticker_set_id << " from server";
- reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), Auto());
+ LOG(INFO) << "Trying to load " << sticker_set_id << " from server";
+ do_reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), 0, Auto());
}
}
}
}
}
-void StickersManager::on_load_sticker_set_from_database(int64 sticker_set_id, bool with_stickers, string value) {
+void StickersManager::on_load_sticker_set_from_database(StickerSetId sticker_set_id, bool with_stickers, string value) {
+ if (G()->close_flag()) {
+ return;
+ }
StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- if (sticker_set->was_loaded) {
- LOG(INFO) << "Sticker set " << sticker_set_id << " was loaded";
+ if (sticker_set->was_loaded_) {
+ LOG(INFO) << "Receive from database previously loaded " << sticker_set_id;
return;
}
- if (!with_stickers && sticker_set->is_inited) {
- LOG(INFO) << "Sticker set " << sticker_set_id << " was inited";
+ if (!with_stickers && sticker_set->is_inited_) {
+ LOG(INFO) << "Receive from database previously inited " << sticker_set_id;
return;
}
+ // it is possible that a server reload_sticker_set request has failed and cleared requests list with an error
if (with_stickers) {
- CHECK(!sticker_set->load_requests.empty());
+ // CHECK(!sticker_set->load_requests_.empty());
} else {
- CHECK(!sticker_set->load_without_stickers_requests.empty());
+ // CHECK(!sticker_set->load_without_stickers_requests_.empty());
}
+
if (value.empty()) {
- reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), Auto());
- return;
+ LOG(INFO) << "Failed to find in the database " << sticker_set_id;
+ return do_reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), 0, Auto());
}
- LOG(INFO) << "Successfully loaded sticker set " << sticker_set_id << " with" << (with_stickers ? "" : "out")
+ LOG(INFO) << "Successfully loaded " << sticker_set_id << " with" << (with_stickers ? "" : "out")
<< " stickers of size " << value.size() << " from database";
- auto old_sticker_count = sticker_set->sticker_ids.size();
+ auto old_sticker_count = sticker_set->sticker_ids_.size();
{
- LOG_IF(ERROR, sticker_set->is_changed) << "Sticker set with" << (with_stickers ? "" : "out") << " stickers "
- << sticker_set_id << " was changed before it is loaded from database";
+ LOG_IF(ERROR, sticker_set->is_changed_) << sticker_set_id << " with" << (with_stickers ? "" : "out")
+ << " stickers was changed before it is loaded from database";
LogEventParser parser(value);
parse_sticker_set(sticker_set, parser);
- LOG_IF(ERROR, sticker_set->is_changed)
- << "Sticker set with" << (with_stickers ? "" : "out") << " stickers " << sticker_set_id << " is changed";
+ LOG_IF(ERROR, sticker_set->is_changed_)
+ << sticker_set_id << " with" << (with_stickers ? "" : "out") << " stickers is changed";
parser.fetch_end();
- parser.get_status().ensure();
+ auto status = parser.get_status();
+ if (status.is_error()) {
+ G()->td_db()->get_sqlite_sync_pmc()->erase(with_stickers ? get_full_sticker_set_database_key(sticker_set_id)
+ : get_sticker_set_database_key(sticker_set_id));
+ // need to crash, because the current StickerSet state is spoiled by parse_sticker_set
+ LOG(FATAL) << "Failed to parse " << sticker_set_id << ": " << status << ' '
+ << format::as_hex_dump<4>(Slice(value));
+ }
+ }
+ if (!sticker_set->are_keywords_loaded_ || !sticker_set->is_thumbnail_reloaded_ ||
+ !sticker_set->are_legacy_sticker_thumbnails_reloaded_) {
+ do_reload_sticker_set(sticker_set_id, get_input_sticker_set(sticker_set), 0, Auto());
}
- if (with_stickers && old_sticker_count < 5 && old_sticker_count < sticker_set->sticker_ids.size()) {
- sticker_set->is_changed = true;
- update_sticker_set(sticker_set);
+ if (with_stickers && old_sticker_count < get_max_featured_sticker_count(sticker_set->sticker_type_) &&
+ old_sticker_count < sticker_set->sticker_ids_.size()) {
+ sticker_set->need_save_to_database_ = true;
+ update_sticker_set(sticker_set, "on_load_sticker_set_from_database");
}
update_load_requests(sticker_set, with_stickers, Status::OK());
}
-void StickersManager::reload_sticker_set(int64 sticker_set_id,
- tl_object_ptr<telegram_api::InputStickerSet> &&input_sticker_set,
- Promise<Unit> &&promise) const {
- td_->create_handler<GetStickerSetQuery>(std::move(promise))->send(sticker_set_id, std::move(input_sticker_set));
+void StickersManager::reload_sticker_set(StickerSetId sticker_set_id, int64 access_hash, Promise<Unit> &&promise) {
+ do_reload_sticker_set(sticker_set_id,
+ make_tl_object<telegram_api::inputStickerSetID>(sticker_set_id.get(), access_hash), 0,
+ std::move(promise));
}
-void StickersManager::on_install_sticker_set(int64 set_id, bool is_archived,
+void StickersManager::do_reload_sticker_set(StickerSetId sticker_set_id,
+ tl_object_ptr<telegram_api::InputStickerSet> &&input_sticker_set,
+ int32 hash, Promise<Unit> &&promise) const {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ td_->create_handler<GetStickerSetQuery>(std::move(promise))->send(sticker_set_id, std::move(input_sticker_set), hash);
+}
+
+void StickersManager::on_install_sticker_set(StickerSetId set_id, bool is_archived,
tl_object_ptr<telegram_api::messages_StickerSetInstallResult> &&result) {
StickerSet *sticker_set = get_sticker_set(set_id);
CHECK(sticker_set != nullptr);
on_update_sticker_set(sticker_set, true, is_archived, true);
- update_sticker_set(sticker_set);
+ update_sticker_set(sticker_set, "on_install_sticker_set");
switch (result->get_id()) {
case telegram_api::messages_stickerSetInstallResultSuccess::ID:
@@ -2539,11 +5429,12 @@ void StickersManager::on_install_sticker_set(int64 set_id, bool is_archived,
case telegram_api::messages_stickerSetInstallResultArchive::ID: {
auto archived_sets = move_tl_object_as<telegram_api::messages_stickerSetInstallResultArchive>(result);
for (auto &archived_set_ptr : archived_sets->sets_) {
- int64 archived_sticker_set_id = on_get_sticker_set_covered(std::move(archived_set_ptr), true);
- if (archived_sticker_set_id != 0) {
+ StickerSetId archived_sticker_set_id =
+ on_get_sticker_set_covered(std::move(archived_set_ptr), true, "on_install_sticker_set");
+ if (archived_sticker_set_id.is_valid()) {
auto archived_sticker_set = get_sticker_set(archived_sticker_set_id);
CHECK(archived_sticker_set != nullptr);
- update_sticker_set(archived_sticker_set);
+ update_sticker_set(archived_sticker_set, "on_install_sticker_set 2");
}
}
break;
@@ -2555,40 +5446,1163 @@ void StickersManager::on_install_sticker_set(int64 set_id, bool is_archived,
send_update_installed_sticker_sets();
}
-void StickersManager::on_uninstall_sticker_set(int64 set_id) {
+void StickersManager::on_uninstall_sticker_set(StickerSetId set_id) {
StickerSet *sticker_set = get_sticker_set(set_id);
CHECK(sticker_set != nullptr);
on_update_sticker_set(sticker_set, false, false, true);
- update_sticker_set(sticker_set);
+ update_sticker_set(sticker_set, "on_uninstall_sticker_set");
send_update_installed_sticker_sets();
}
-void StickersManager::on_update_sticker_sets() {
- // TODO better support
- archived_sticker_set_ids_[0].clear();
- total_archived_sticker_set_count_[0] = -1;
- reload_installed_sticker_sets(false, true);
+td_api::object_ptr<td_api::updateDiceEmojis> StickersManager::get_update_dice_emojis_object() const {
+ return td_api::make_object<td_api::updateDiceEmojis>(vector<string>(dice_emojis_));
+}
+
+void StickersManager::on_update_dice_emojis() {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ td_->option_manager_->set_option_empty("dice_emojis");
+ return;
+ }
+ if (!is_inited_) {
+ return;
+ }
+
+ auto dice_emojis_str =
+ td_->option_manager_->get_option_string("dice_emojis", "🎲\x01🎯\x01🏀\x01⚽\x01⚽️\x01🎰\x01🎳");
+ if (dice_emojis_str == dice_emojis_str_) {
+ return;
+ }
+ dice_emojis_str_ = std::move(dice_emojis_str);
+ auto new_dice_emojis = full_split(dice_emojis_str_, '\x01');
+ for (auto &emoji : new_dice_emojis) {
+ if (!td::contains(dice_emojis_, emoji)) {
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_dice(emoji));
+ if (special_sticker_set.id_.is_valid()) {
+ // drop information about the sticker set to reload it
+ special_sticker_set.id_ = StickerSetId();
+ special_sticker_set.access_hash_ = 0;
+ special_sticker_set.short_name_.clear();
+ }
+
+ if (G()->parameters().use_file_db) {
+ LOG(INFO) << "Load new dice sticker set for emoji " << emoji;
+ load_special_sticker_set(special_sticker_set);
+ }
+ }
+ }
+ dice_emojis_ = std::move(new_dice_emojis);
+
+ send_closure(G()->td(), &Td::send_update, get_update_dice_emojis_object());
+}
+
+void StickersManager::on_update_dice_success_values() {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (td_->auth_manager_->is_bot()) {
+ td_->option_manager_->set_option_empty("dice_success_values");
+ return;
+ }
+ if (!is_inited_) {
+ return;
+ }
+
+ auto dice_success_values_str =
+ td_->option_manager_->get_option_string("dice_success_values", "0,6:62,5:110,5:110,5:110,64:110,6:110");
+ if (dice_success_values_str == dice_success_values_str_) {
+ return;
+ }
+
+ LOG(INFO) << "Change dice success values to " << dice_success_values_str;
+ dice_success_values_str_ = std::move(dice_success_values_str);
+ dice_success_values_ = transform(full_split(dice_success_values_str_, ','), [](Slice value) {
+ auto result = split(value, ':');
+ return std::make_pair(to_integer<int32>(result.first), to_integer<int32>(result.second));
+ });
+}
+
+void StickersManager::on_update_emoji_sounds() {
+ if (G()->close_flag() || !is_inited_ || td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto emoji_sounds_str = td_->option_manager_->get_option_string("emoji_sounds");
+ if (emoji_sounds_str == emoji_sounds_str_) {
+ return;
+ }
+
+ LOG(INFO) << "Change emoji sounds to " << emoji_sounds_str;
+ emoji_sounds_str_ = std::move(emoji_sounds_str);
+
+ vector<FileId> old_file_ids;
+ for (auto &emoji_sound : emoji_sounds_) {
+ old_file_ids.push_back(emoji_sound.second);
+ }
+ emoji_sounds_.clear();
+
+ vector<FileId> new_file_ids;
+ auto sounds = full_split(Slice(emoji_sounds_str_), ',');
+ CHECK(sounds.size() % 2 == 0);
+ for (size_t i = 0; i < sounds.size(); i += 2) {
+ vector<Slice> parts = full_split(sounds[i + 1], ':');
+ CHECK(parts.size() == 3);
+ auto id = to_integer<int64>(parts[0]);
+ auto access_hash = to_integer<int64>(parts[1]);
+ auto dc_id = G()->net_query_dispatcher().get_main_dc_id();
+ auto file_reference = base64url_decode(parts[2]).move_as_ok();
+ int32 expected_size = 7000;
+ auto suggested_file_name = PSTRING() << static_cast<uint64>(id) << '.'
+ << MimeType::to_extension("audio/ogg", "oga");
+ auto file_id = td_->file_manager_->register_remote(
+ FullRemoteFileLocation(FileType::VoiceNote, id, access_hash, dc_id, std::move(file_reference)),
+ FileLocationSource::FromServer, DialogId(), 0, expected_size, std::move(suggested_file_name));
+ CHECK(file_id.is_valid());
+ auto cleaned_emoji = remove_fitzpatrick_modifier(sounds[i]).str();
+ if (!cleaned_emoji.empty()) {
+ emoji_sounds_.emplace(cleaned_emoji, file_id);
+ new_file_ids.push_back(file_id);
+ }
+ }
+ td_->file_manager_->change_files_source(get_app_config_file_source_id(), old_file_ids, new_file_ids);
+
+ try_update_animated_emoji_messages();
+}
+
+void StickersManager::on_update_disable_animated_emojis() {
+ if (G()->close_flag() || !is_inited_ || td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto disable_animated_emojis = td_->option_manager_->get_option_boolean("disable_animated_emoji");
+ if (disable_animated_emojis == disable_animated_emojis_) {
+ return;
+ }
+ disable_animated_emojis_ = disable_animated_emojis;
+ if (!disable_animated_emojis_) {
+ reload_special_sticker_set_by_type(SpecialStickerSetType::animated_emoji());
+ reload_special_sticker_set_by_type(SpecialStickerSetType::animated_emoji_click());
+ }
+ try_update_animated_emoji_messages();
+
+ vector<CustomEmojiId> custom_emoji_ids;
+ for (auto &it : custom_emoji_messages_) {
+ custom_emoji_ids.push_back(it.first);
+ }
+ for (auto custom_emoji_id : custom_emoji_ids) {
+ try_update_custom_emoji_messages(custom_emoji_id);
+ }
+
+ if (!disable_animated_emojis_) {
+ for (size_t i = 0; i < custom_emoji_ids.size(); i += MAX_GET_CUSTOM_EMOJI_STICKERS) {
+ auto end_i = td::min(i + MAX_GET_CUSTOM_EMOJI_STICKERS, custom_emoji_ids.size());
+ get_custom_emoji_stickers({custom_emoji_ids.begin() + i, custom_emoji_ids.begin() + end_i}, true, Auto());
+ }
+ }
+}
+
+void StickersManager::on_update_sticker_sets(StickerType sticker_type) {
+ auto type = static_cast<int32>(sticker_type);
+ archived_sticker_set_ids_[type].clear();
+ total_archived_sticker_set_count_[type] = -1;
+ reload_installed_sticker_sets(sticker_type, true);
+}
+
+void StickersManager::try_update_animated_emoji_messages() {
+ auto sticker_set = get_animated_emoji_sticker_set();
+ vector<FullMessageId> full_message_ids;
+ for (auto &it : emoji_messages_) {
+ auto new_animated_sticker = get_animated_emoji_sticker(sticker_set, it.first);
+ auto new_sound_file_id = get_animated_emoji_sound_file_id(it.first);
+ if (new_animated_sticker != it.second->animated_emoji_sticker_ ||
+ (new_animated_sticker.first.is_valid() && new_sound_file_id != it.second->sound_file_id_)) {
+ it.second->animated_emoji_sticker_ = new_animated_sticker;
+ it.second->sound_file_id_ = new_sound_file_id;
+ it.second->full_message_ids_.foreach(
+ [&](const FullMessageId &full_message_id) { full_message_ids.push_back(full_message_id); });
+ }
+ }
+ for (const auto &full_message_id : full_message_ids) {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ }
+}
+
+void StickersManager::try_update_custom_emoji_messages(CustomEmojiId custom_emoji_id) {
+ auto it = custom_emoji_messages_.find(custom_emoji_id);
+ if (it == custom_emoji_messages_.end()) {
+ return;
+ }
+
+ vector<FullMessageId> full_message_ids;
+ auto new_sticker_id = get_custom_animated_emoji_sticker_id(custom_emoji_id);
+ if (new_sticker_id != it->second->sticker_id_) {
+ it->second->sticker_id_ = new_sticker_id;
+ it->second->full_message_ids_.foreach(
+ [&](const FullMessageId &full_message_id) { full_message_ids.push_back(full_message_id); });
+ }
+ for (const auto &full_message_id : full_message_ids) {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ }
+}
+
+void StickersManager::try_update_premium_gift_messages() {
+ auto sticker_set = get_premium_gift_sticker_set();
+ vector<FullMessageId> full_message_ids;
+ for (auto &it : premium_gift_messages_) {
+ auto new_sticker_id = get_premium_gift_option_sticker_id(sticker_set, it.first);
+ if (new_sticker_id != it.second->sticker_id_) {
+ it.second->sticker_id_ = new_sticker_id;
+ for (const auto &full_message_id : it.second->full_message_ids_) {
+ full_message_ids.push_back(full_message_id);
+ }
+ }
+ }
+ for (const auto &full_message_id : full_message_ids) {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ }
+}
+
+void StickersManager::register_premium_gift(int32 months, FullMessageId full_message_id, const char *source) {
+ if (td_->auth_manager_->is_bot() || months == 0) {
+ return;
+ }
+
+ LOG(INFO) << "Register premium gift for " << months << " months from " << full_message_id << " from " << source;
+ auto &premium_gift_messages_ptr = premium_gift_messages_[months];
+ if (premium_gift_messages_ptr == nullptr) {
+ premium_gift_messages_ptr = make_unique<GiftPremiumMessages>();
+ }
+ auto &premium_gift_messages = *premium_gift_messages_ptr;
+
+ if (premium_gift_messages.full_message_ids_.empty()) {
+ premium_gift_messages.sticker_id_ = get_premium_gift_option_sticker_id(months);
+ }
+
+ bool is_inserted = premium_gift_messages.full_message_ids_.insert(full_message_id).second;
+ LOG_CHECK(is_inserted) << source << " " << months << " " << full_message_id;
+}
+
+void StickersManager::unregister_premium_gift(int32 months, FullMessageId full_message_id, const char *source) {
+ if (td_->auth_manager_->is_bot() || months == 0) {
+ return;
+ }
+
+ LOG(INFO) << "Unregister premium gift for " << months << " months from " << full_message_id << " from " << source;
+ auto it = premium_gift_messages_.find(months);
+ CHECK(it != premium_gift_messages_.end());
+ auto &message_ids = it->second->full_message_ids_;
+ auto is_deleted = message_ids.erase(full_message_id) > 0;
+ LOG_CHECK(is_deleted) << source << " " << months << " " << full_message_id;
+
+ if (message_ids.empty()) {
+ premium_gift_messages_.erase(it);
+ }
+}
+
+void StickersManager::register_dice(const string &emoji, int32 value, FullMessageId full_message_id,
+ const char *source) {
+ CHECK(!emoji.empty());
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ LOG(INFO) << "Register dice " << emoji << " with value " << value << " from " << full_message_id << " from "
+ << source;
+ dice_messages_[emoji].insert(full_message_id);
+
+ if (!td::contains(dice_emojis_, emoji)) {
+ if (full_message_id.get_message_id().is_any_server() &&
+ full_message_id.get_dialog_id().get_type() != DialogType::SecretChat) {
+ send_closure(G()->config_manager(), &ConfigManager::reget_app_config, Promise<Unit>());
+ }
+ return;
+ }
+
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_dice(emoji));
+ bool need_load = false;
+ StickerSet *sticker_set = nullptr;
+ if (!special_sticker_set.id_.is_valid()) {
+ need_load = true;
+ } else {
+ sticker_set = get_sticker_set(special_sticker_set.id_);
+ CHECK(sticker_set != nullptr);
+ need_load = !sticker_set->was_loaded_;
+ }
+
+ if (need_load) {
+ LOG(INFO) << "Waiting for a dice sticker set needed in " << full_message_id;
+ load_special_sticker_set(special_sticker_set);
+ } else {
+ // TODO reload once in a while
+ // reload_special_sticker_set(special_sticker_set, sticker_set->is_loaded_ ? sticker_set->hash_ : 0);
+ }
+}
+
+void StickersManager::unregister_dice(const string &emoji, int32 value, FullMessageId full_message_id,
+ const char *source) {
+ CHECK(!emoji.empty());
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ LOG(INFO) << "Unregister dice " << emoji << " with value " << value << " from " << full_message_id << " from "
+ << source;
+ auto &message_ids = dice_messages_[emoji];
+ auto is_deleted = message_ids.erase(full_message_id) > 0;
+ LOG_CHECK(is_deleted) << source << " " << emoji << " " << value << " " << full_message_id;
+
+ if (message_ids.empty()) {
+ dice_messages_.erase(emoji);
+ }
+}
+
+void StickersManager::register_emoji(const string &emoji, CustomEmojiId custom_emoji_id, FullMessageId full_message_id,
+ const char *source) {
+ CHECK(!emoji.empty());
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ LOG(INFO) << "Register emoji " << emoji << " with " << custom_emoji_id << " from " << full_message_id << " from "
+ << source;
+ if (custom_emoji_id.is_valid()) {
+ auto &emoji_messages_ptr = custom_emoji_messages_[custom_emoji_id];
+ if (emoji_messages_ptr == nullptr) {
+ emoji_messages_ptr = make_unique<CustomEmojiMessages>();
+ }
+ auto &emoji_messages = *emoji_messages_ptr;
+ if (emoji_messages.full_message_ids_.empty()) {
+ if (!disable_animated_emojis_ && custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) {
+ load_custom_emoji_sticker_from_database_force(custom_emoji_id);
+ if (custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) {
+ get_custom_emoji_stickers({custom_emoji_id}, false, Promise<td_api::object_ptr<td_api::stickers>>());
+ }
+ }
+ emoji_messages.sticker_id_ = get_custom_animated_emoji_sticker_id(custom_emoji_id);
+ }
+ emoji_messages.full_message_ids_.insert(full_message_id);
+ return;
+ }
+
+ auto &emoji_messages_ptr = emoji_messages_[emoji];
+ if (emoji_messages_ptr == nullptr) {
+ emoji_messages_ptr = make_unique<EmojiMessages>();
+ }
+ auto &emoji_messages = *emoji_messages_ptr;
+ if (emoji_messages.full_message_ids_.empty()) {
+ emoji_messages.animated_emoji_sticker_ = get_animated_emoji_sticker(emoji);
+ emoji_messages.sound_file_id_ = get_animated_emoji_sound_file_id(emoji);
+ }
+ emoji_messages.full_message_ids_.insert(full_message_id);
+}
+
+void StickersManager::unregister_emoji(const string &emoji, CustomEmojiId custom_emoji_id,
+ FullMessageId full_message_id, const char *source) {
+ CHECK(!emoji.empty());
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ LOG(INFO) << "Unregister emoji " << emoji << " with " << custom_emoji_id << " from " << full_message_id << " from "
+ << source;
+ if (custom_emoji_id.is_valid()) {
+ auto it = custom_emoji_messages_.find(custom_emoji_id);
+ CHECK(it != custom_emoji_messages_.end());
+ auto &full_message_ids = it->second->full_message_ids_;
+ auto is_deleted = full_message_ids.erase(full_message_id) > 0;
+ LOG_CHECK(is_deleted) << source << ' ' << custom_emoji_id << ' ' << full_message_id;
+
+ if (full_message_ids.empty()) {
+ custom_emoji_messages_.erase(it);
+ }
+ return;
+ }
+
+ auto it = emoji_messages_.find(emoji);
+ CHECK(it != emoji_messages_.end());
+ auto &full_message_ids = it->second->full_message_ids_;
+ auto is_deleted = full_message_ids.erase(full_message_id) > 0;
+ LOG_CHECK(is_deleted) << source << ' ' << emoji << ' ' << full_message_id;
+
+ if (full_message_ids.empty()) {
+ emoji_messages_.erase(it);
+ }
+}
+
+void StickersManager::get_animated_emoji(string emoji, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::animatedEmoji>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji());
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ if (is_recursive) {
+ return promise.set_value(nullptr);
+ }
+
+ pending_get_animated_emoji_queries_.push_back(
+ PromiseCreator::lambda([actor_id = actor_id(this), emoji = std::move(emoji),
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::get_animated_emoji, std::move(emoji), true, std::move(promise));
+ }
+ }));
+ load_special_sticker_set(special_sticker_set);
+ return;
+ }
+
+ promise.set_value(get_animated_emoji_object(get_animated_emoji_sticker(sticker_set, emoji),
+ get_animated_emoji_sound_file_id(emoji)));
+}
+
+void StickersManager::get_all_animated_emojis(bool is_recursive,
+ Promise<td_api::object_ptr<td_api::emojis>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji());
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ if (is_recursive) {
+ return promise.set_value(td_api::make_object<td_api::emojis>());
+ }
+
+ pending_get_animated_emoji_queries_.push_back(PromiseCreator::lambda(
+ [actor_id = actor_id(this), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::get_all_animated_emojis, true, std::move(promise));
+ }
+ }));
+ load_special_sticker_set(special_sticker_set);
+ return;
+ }
+
+ auto emojis = transform(sticker_set->sticker_ids_, [&](FileId sticker_id) {
+ auto s = get_sticker(sticker_id);
+ CHECK(s != nullptr);
+ return s->alt_;
+ });
+ promise.set_value(td_api::make_object<td_api::emojis>(std::move(emojis)));
+}
+
+void StickersManager::get_custom_emoji_reaction_generic_animations(
+ bool is_recursive, Promise<td_api::object_ptr<td_api::stickers>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::generic_animations());
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ if (is_recursive) {
+ return promise.set_value(td_api::make_object<td_api::stickers>());
+ }
+
+ pending_get_generic_animations_queries_.push_back(PromiseCreator::lambda(
+ [actor_id = actor_id(this), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::get_custom_emoji_reaction_generic_animations, true,
+ std::move(promise));
+ }
+ }));
+ load_special_sticker_set(special_sticker_set);
+ return;
+ }
+
+ promise.set_value(get_stickers_object(sticker_set->sticker_ids_));
+}
+
+void StickersManager::get_default_emoji_statuses(bool is_recursive,
+ Promise<td_api::object_ptr<td_api::emojiStatuses>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::default_statuses());
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ if (is_recursive) {
+ return promise.set_value(td_api::make_object<td_api::emojiStatuses>());
+ }
+
+ pending_get_default_statuses_queries_.push_back(PromiseCreator::lambda(
+ [actor_id = actor_id(this), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::get_default_emoji_statuses, true, std::move(promise));
+ }
+ }));
+ load_special_sticker_set(special_sticker_set);
+ return;
+ }
+
+ vector<td_api::object_ptr<td_api::emojiStatus>> statuses;
+ for (auto sticker_id : sticker_set->sticker_ids_) {
+ auto custom_emoji_id = get_custom_emoji_id(sticker_id);
+ if (!custom_emoji_id.is_valid()) {
+ LOG(ERROR) << "Ignore wrong sticker " << sticker_id;
+ continue;
+ }
+ statuses.push_back(td_api::make_object<td_api::emojiStatus>(custom_emoji_id.get()));
+ if (statuses.size() >= 8) {
+ break;
+ }
+ }
+ promise.set_value(td_api::make_object<td_api::emojiStatuses>(std::move(statuses)));
+}
+
+bool StickersManager::is_default_emoji_status(CustomEmojiId custom_emoji_id) {
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::default_statuses());
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ return false;
+ }
+ for (auto sticker_id : sticker_set->sticker_ids_) {
+ if (get_custom_emoji_id(sticker_id) == custom_emoji_id) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void StickersManager::get_default_topic_icons(bool is_recursive,
+ Promise<td_api::object_ptr<td_api::stickers>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::default_topic_icons());
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ if (is_recursive) {
+ return promise.set_value(td_api::make_object<td_api::stickers>());
+ }
+
+ pending_get_default_topic_icons_queries_.push_back(PromiseCreator::lambda(
+ [actor_id = actor_id(this), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::get_default_topic_icons, true, std::move(promise));
+ }
+ }));
+ load_special_sticker_set(special_sticker_set);
+ return;
+ }
+
+ if (!is_recursive && td_->auth_manager_->is_bot() && G()->unix_time() >= sticker_set->expires_at_) {
+ auto reload_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::get_default_topic_icons, true, std::move(promise));
+ }
+ });
+ do_reload_sticker_set(sticker_set->id_, get_input_sticker_set(sticker_set), sticker_set->hash_,
+ std::move(reload_promise));
+ return;
+ }
+
+ promise.set_value(get_stickers_object(sticker_set->sticker_ids_));
+}
+
+void StickersManager::load_custom_emoji_sticker_from_database_force(CustomEmojiId custom_emoji_id) {
+ if (!G()->parameters().use_file_db) {
+ return;
+ }
+
+ auto value = G()->td_db()->get_sqlite_sync_pmc()->get(get_custom_emoji_database_key(custom_emoji_id));
+ if (value.empty()) {
+ LOG(INFO) << "Failed to load " << custom_emoji_id << " from database";
+ return;
+ }
+
+ LOG(INFO) << "Synchronously loaded " << custom_emoji_id << " of size " << value.size() << " from database";
+ CustomEmojiLogEvent log_event;
+ if (log_event_parse(log_event, value).is_error()) {
+ LOG(ERROR) << "Delete invalid " << custom_emoji_id << " value from database";
+ G()->td_db()->get_sqlite_sync_pmc()->erase(get_custom_emoji_database_key(custom_emoji_id));
+ }
+}
+
+void StickersManager::load_custom_emoji_sticker_from_database(CustomEmojiId custom_emoji_id, Promise<Unit> &&promise) {
+ CHECK(custom_emoji_id.is_valid());
+ auto &queries = custom_emoji_load_queries_[custom_emoji_id];
+ queries.push_back(std::move(promise));
+ if (queries.size() == 1) {
+ LOG(INFO) << "Trying to load " << custom_emoji_id << " from database";
+ G()->td_db()->get_sqlite_pmc()->get(
+ get_custom_emoji_database_key(custom_emoji_id), PromiseCreator::lambda([custom_emoji_id](string value) {
+ send_closure(G()->stickers_manager(), &StickersManager::on_load_custom_emoji_from_database, custom_emoji_id,
+ std::move(value));
+ }));
+ }
+}
+
+void StickersManager::on_load_custom_emoji_from_database(CustomEmojiId custom_emoji_id, string value) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ if (!value.empty()) {
+ LOG(INFO) << "Successfully loaded " << custom_emoji_id << " of size " << value.size() << " from database";
+ CustomEmojiLogEvent log_event;
+ if (log_event_parse(log_event, value).is_error()) {
+ LOG(ERROR) << "Delete invalid " << custom_emoji_id << " value from database";
+ G()->td_db()->get_sqlite_pmc()->erase(get_custom_emoji_database_key(custom_emoji_id), Auto());
+ }
+ } else {
+ LOG(INFO) << "Failed to load " << custom_emoji_id << " from database";
+ }
+
+ auto it = custom_emoji_load_queries_.find(custom_emoji_id);
+ CHECK(it != custom_emoji_load_queries_.end());
+ CHECK(!it->second.empty());
+ auto promises = std::move(it->second);
+ custom_emoji_load_queries_.erase(it);
+
+ set_promises(promises);
+}
+
+td_api::object_ptr<td_api::stickers> StickersManager::get_custom_emoji_stickers_object(
+ const vector<CustomEmojiId> &custom_emoji_ids) {
+ vector<td_api::object_ptr<td_api::sticker>> stickers;
+ auto update_before_date = G()->unix_time() - 86400;
+ vector<CustomEmojiId> reload_custom_emoji_ids;
+ for (auto custom_emoji_id : custom_emoji_ids) {
+ auto file_id = custom_emoji_to_sticker_id_.get(custom_emoji_id);
+ if (file_id.is_valid()) {
+ auto s = get_sticker(file_id);
+ CHECK(s != nullptr);
+ CHECK(s->type_ == StickerType::CustomEmoji);
+ if (s->emoji_receive_date_ < update_before_date && !s->is_being_reloaded_) {
+ s->is_being_reloaded_ = true;
+ reload_custom_emoji_ids.push_back(custom_emoji_id);
+ }
- archived_sticker_set_ids_[1].clear();
- total_archived_sticker_set_count_[1] = -1;
- reload_installed_sticker_sets(true, true);
+ auto sticker = get_sticker_object(file_id);
+ CHECK(sticker != nullptr);
+ stickers.push_back(std::move(sticker));
+ }
+ }
+ if (!reload_custom_emoji_ids.empty()) {
+ LOG(INFO) << "Reload " << reload_custom_emoji_ids;
+ auto promise = PromiseCreator::lambda(
+ [actor_id =
+ actor_id(this)](Result<vector<telegram_api::object_ptr<telegram_api::Document>>> r_documents) mutable {
+ send_closure(actor_id, &StickersManager::on_get_custom_emoji_documents, std::move(r_documents),
+ vector<CustomEmojiId>(), Promise<td_api::object_ptr<td_api::stickers>>());
+ });
+ td_->create_handler<GetCustomEmojiDocumentsQuery>(std::move(promise))->send(std::move(reload_custom_emoji_ids));
+ }
+ return td_api::make_object<td_api::stickers>(std::move(stickers));
}
-void StickersManager::view_featured_sticker_sets(const vector<int64> &sticker_set_ids) {
+void StickersManager::get_custom_emoji_stickers(vector<CustomEmojiId> &&custom_emoji_ids, bool use_database,
+ Promise<td_api::object_ptr<td_api::stickers>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (custom_emoji_ids.size() > MAX_GET_CUSTOM_EMOJI_STICKERS) {
+ return promise.set_error(Status::Error(400, "Too many custom emoji identifiers specified"));
+ }
+
+ FlatHashSet<CustomEmojiId, CustomEmojiIdHash> unique_custom_emoji_ids;
+ size_t j = 0;
+ for (size_t i = 0; i < custom_emoji_ids.size(); i++) {
+ auto custom_emoji_id = custom_emoji_ids[i];
+ if (custom_emoji_id.is_valid() && unique_custom_emoji_ids.insert(custom_emoji_id).second) {
+ custom_emoji_ids[j++] = custom_emoji_id;
+ }
+ }
+ custom_emoji_ids.resize(j);
+
+ vector<CustomEmojiId> unknown_custom_emoji_ids;
+ for (auto custom_emoji_id : custom_emoji_ids) {
+ if (custom_emoji_to_sticker_id_.count(custom_emoji_id) == 0) {
+ unknown_custom_emoji_ids.push_back(custom_emoji_id);
+ }
+ }
+
+ if (unknown_custom_emoji_ids.empty()) {
+ return promise.set_value(get_custom_emoji_stickers_object(custom_emoji_ids));
+ }
+
+ if (use_database && G()->parameters().use_file_db) {
+ MultiPromiseActorSafe mpas{"LoadCustomEmojiMultiPromiseActor"};
+ mpas.add_promise(PromiseCreator::lambda(
+ [actor_id = actor_id(this), custom_emoji_ids, promise = std::move(promise)](Unit) mutable {
+ send_closure(actor_id, &StickersManager::get_custom_emoji_stickers, std::move(custom_emoji_ids), false,
+ std::move(promise));
+ }));
+
+ auto lock = mpas.get_promise();
+ for (auto custom_emoji_id : unknown_custom_emoji_ids) {
+ load_custom_emoji_sticker_from_database(custom_emoji_id, mpas.get_promise());
+ }
+
+ return lock.set_value(Unit());
+ }
+
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), custom_emoji_ids = std::move(custom_emoji_ids), promise = std::move(promise)](
+ Result<vector<telegram_api::object_ptr<telegram_api::Document>>> r_documents) mutable {
+ send_closure(actor_id, &StickersManager::on_get_custom_emoji_documents, std::move(r_documents),
+ std::move(custom_emoji_ids), std::move(promise));
+ });
+ td_->create_handler<GetCustomEmojiDocumentsQuery>(std::move(query_promise))
+ ->send(std::move(unknown_custom_emoji_ids));
+}
+
+void StickersManager::on_get_custom_emoji_documents(
+ Result<vector<telegram_api::object_ptr<telegram_api::Document>>> &&r_documents,
+ vector<CustomEmojiId> &&custom_emoji_ids, Promise<td_api::object_ptr<td_api::stickers>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ if (r_documents.is_error()) {
+ return promise.set_error(r_documents.move_as_error());
+ }
+ auto documents = r_documents.move_as_ok();
+
+ for (auto &document : documents) {
+ LOG(INFO) << "Receive " << to_string(document);
+ if (document->get_id() == telegram_api::documentEmpty::ID) {
+ continue;
+ }
+
+ on_get_sticker_document(std::move(document), StickerFormat::Unknown);
+ }
+
+ promise.set_value(get_custom_emoji_stickers_object(custom_emoji_ids));
+}
+
+void StickersManager::get_premium_gift_option_sticker(int32 month_count, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::sticker>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::premium_gifts());
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ if (is_recursive) {
+ return promise.set_value(nullptr);
+ }
+
+ pending_get_premium_gift_option_sticker_queries_.push_back(PromiseCreator::lambda(
+ [actor_id = actor_id(this), month_count, promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::get_premium_gift_option_sticker, month_count, true,
+ std::move(promise));
+ }
+ }));
+ load_special_sticker_set(special_sticker_set);
+ return;
+ }
+
+ promise.set_value(get_sticker_object(get_premium_gift_option_sticker_id(sticker_set, month_count)));
+}
+
+void StickersManager::get_animated_emoji_click_sticker(const string &message_text, FullMessageId full_message_id,
+ Promise<td_api::object_ptr<td_api::sticker>> &&promise) {
+ if (disable_animated_emojis_ || td_->auth_manager_->is_bot()) {
+ return promise.set_value(nullptr);
+ }
+
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji_click());
+ if (!special_sticker_set.id_.is_valid()) {
+ // don't wait for the first load of the sticker set from the server
+ load_special_sticker_set(special_sticker_set);
+ return promise.set_value(nullptr);
+ }
+
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ CHECK(sticker_set != nullptr);
+ if (sticker_set->was_loaded_) {
+ return choose_animated_emoji_click_sticker(sticker_set, message_text, full_message_id, Time::now(),
+ std::move(promise));
+ }
+
+ LOG(INFO) << "Waiting for an emoji click sticker set needed in " << full_message_id;
+ load_special_sticker_set(special_sticker_set);
+
+ PendingGetAnimatedEmojiClickSticker pending_request;
+ pending_request.message_text_ = message_text;
+ pending_request.full_message_id_ = full_message_id;
+ pending_request.start_time_ = Time::now();
+ pending_request.promise_ = std::move(promise);
+ pending_get_animated_emoji_click_stickers_.push_back(std::move(pending_request));
+}
+
+int StickersManager::get_emoji_number(Slice emoji) {
+ // '0'-'9' + U+20E3
+ auto data = emoji.ubegin();
+ if (emoji.size() != 4 || emoji[0] < '0' || emoji[0] > '9' || data[1] != 0xE2 || data[2] != 0x83 || data[3] != 0xA3) {
+ return -1;
+ }
+ return emoji[0] - '0';
+}
+
+vector<FileId> StickersManager::get_animated_emoji_click_stickers(const StickerSet *sticker_set, Slice emoji) const {
+ vector<FileId> result;
+ for (auto sticker_id : sticker_set->sticker_ids_) {
+ auto s = get_sticker(sticker_id);
+ CHECK(s != nullptr);
+ if (remove_emoji_modifiers(s->alt_) == emoji) {
+ result.push_back(sticker_id);
+ }
+ }
+ if (result.empty()) {
+ const static vector<string> heart_emojis{"💛", "💙", "💚", "💜", "🧡", "🖤", "🤎", "🤍"};
+ if (td::contains(heart_emojis, emoji)) {
+ return get_animated_emoji_click_stickers(sticker_set, Slice("❤"));
+ }
+ }
+ return result;
+}
+
+void StickersManager::choose_animated_emoji_click_sticker(const StickerSet *sticker_set, string message_text,
+ FullMessageId full_message_id, double start_time,
+ Promise<td_api::object_ptr<td_api::sticker>> &&promise) {
+ CHECK(sticker_set->was_loaded_);
+ remove_emoji_modifiers_in_place(message_text);
+ if (message_text.empty()) {
+ return promise.set_error(Status::Error(400, "Message is not an animated emoji message"));
+ }
+
+ if (disable_animated_emojis_ || td_->auth_manager_->is_bot()) {
+ return promise.set_value(nullptr);
+ }
+
+ auto now = Time::now();
+ if (last_clicked_animated_emoji_ == message_text && last_clicked_animated_emoji_full_message_id_ == full_message_id &&
+ next_click_animated_emoji_message_time_ >= now + 2 * MIN_ANIMATED_EMOJI_CLICK_DELAY) {
+ return promise.set_value(nullptr);
+ }
+
+ auto all_sticker_ids = get_animated_emoji_click_stickers(sticker_set, message_text);
+ vector<std::pair<int, FileId>> found_stickers;
+ for (auto sticker_id : all_sticker_ids) {
+ auto it = sticker_set->sticker_emojis_map_.find(sticker_id);
+ if (it != sticker_set->sticker_emojis_map_.end()) {
+ for (auto &emoji : it->second) {
+ auto number = get_emoji_number(emoji);
+ if (number > 0) {
+ found_stickers.emplace_back(number, sticker_id);
+ }
+ }
+ }
+ }
+ if (found_stickers.empty()) {
+ LOG(INFO) << "There is no click effect for " << message_text << " from " << full_message_id;
+ return promise.set_value(nullptr);
+ }
+
+ if (last_clicked_animated_emoji_full_message_id_ != full_message_id) {
+ flush_pending_animated_emoji_clicks();
+ last_clicked_animated_emoji_full_message_id_ = full_message_id;
+ }
+ if (last_clicked_animated_emoji_ != message_text) {
+ pending_animated_emoji_clicks_.clear();
+ last_clicked_animated_emoji_ = std::move(message_text);
+ }
+
+ if (!pending_animated_emoji_clicks_.empty() && found_stickers.size() >= 2) {
+ for (auto it = found_stickers.begin(); it != found_stickers.end(); ++it) {
+ if (it->first == pending_animated_emoji_clicks_.back().first) {
+ found_stickers.erase(it);
+ break;
+ }
+ }
+ }
+
+ CHECK(!found_stickers.empty());
+ auto result = found_stickers[Random::fast(0, narrow_cast<int>(found_stickers.size()) - 1)];
+
+ pending_animated_emoji_clicks_.emplace_back(result.first, start_time);
+ if (pending_animated_emoji_clicks_.size() == 5) {
+ flush_pending_animated_emoji_clicks();
+ } else {
+ set_timeout_in(0.5);
+ }
+ if (now >= next_click_animated_emoji_message_time_) {
+ next_click_animated_emoji_message_time_ = now + MIN_ANIMATED_EMOJI_CLICK_DELAY;
+ promise.set_value(get_sticker_object(result.second, false, true));
+ } else {
+ create_actor<SleepActor>("SendClickAnimatedEmojiMessageResponse", next_click_animated_emoji_message_time_ - now,
+ PromiseCreator::lambda([actor_id = actor_id(this), sticker_id = result.second,
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ send_closure(actor_id, &StickersManager::send_click_animated_emoji_message_response,
+ sticker_id, std::move(promise));
+ }))
+ .release();
+ next_click_animated_emoji_message_time_ += MIN_ANIMATED_EMOJI_CLICK_DELAY;
+ }
+}
+
+void StickersManager::send_click_animated_emoji_message_response(
+ FileId sticker_id, Promise<td_api::object_ptr<td_api::sticker>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ promise.set_value(get_sticker_object(sticker_id, false, true));
+}
+
+void StickersManager::timeout_expired() {
+ flush_pending_animated_emoji_clicks();
+}
+
+void StickersManager::flush_pending_animated_emoji_clicks() {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (pending_animated_emoji_clicks_.empty()) {
+ return;
+ }
+ auto clicks = std::move(pending_animated_emoji_clicks_);
+ pending_animated_emoji_clicks_.clear();
+ auto full_message_id = last_clicked_animated_emoji_full_message_id_;
+ last_clicked_animated_emoji_full_message_id_ = FullMessageId();
+ auto emoji = std::move(last_clicked_animated_emoji_);
+ last_clicked_animated_emoji_.clear();
+
+ if (td_->messages_manager_->is_message_edited_recently(full_message_id, 1)) {
+ // includes deleted full_message_id
+ return;
+ }
+ auto dialog_id = full_message_id.get_dialog_id();
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
+ if (input_peer == nullptr) {
+ return;
+ }
+
+ double start_time = clicks[0].second;
+ auto data = json_encode<string>(json_object([&clicks, start_time](auto &o) {
+ o("v", 1);
+ o("a", json_array(clicks, [start_time](auto &click) {
+ return json_object([&click, start_time](auto &o) {
+ o("i", click.first);
+ auto t = static_cast<int32>((click.second - start_time) * 100);
+ o("t", JsonRaw(PSLICE() << (t / 100) << '.' << (t < 10 ? "0" : "") << (t % 100)));
+ });
+ }));
+ }));
+
+ td_->create_handler<SendAnimatedEmojiClicksQuery>()->send(
+ dialog_id, std::move(input_peer),
+ make_tl_object<telegram_api::sendMessageEmojiInteraction>(
+ emoji, full_message_id.get_message_id().get_server_message_id().get(),
+ make_tl_object<telegram_api::dataJSON>(data)));
+
+ on_send_animated_emoji_clicks(dialog_id, emoji);
+}
+
+void StickersManager::on_send_animated_emoji_clicks(DialogId dialog_id, const string &emoji) {
+ flush_sent_animated_emoji_clicks();
+
+ if (!sent_animated_emoji_clicks_.empty() && sent_animated_emoji_clicks_.back().dialog_id_ == dialog_id &&
+ sent_animated_emoji_clicks_.back().emoji_ == emoji) {
+ sent_animated_emoji_clicks_.back().send_time_ = Time::now();
+ return;
+ }
+
+ SentAnimatedEmojiClicks clicks;
+ clicks.send_time_ = Time::now();
+ clicks.dialog_id_ = dialog_id;
+ clicks.emoji_ = emoji;
+ sent_animated_emoji_clicks_.push_back(std::move(clicks));
+}
+
+void StickersManager::flush_sent_animated_emoji_clicks() {
+ if (sent_animated_emoji_clicks_.empty()) {
+ return;
+ }
+ auto min_send_time = Time::now() - 30.0;
+ auto it = sent_animated_emoji_clicks_.begin();
+ while (it != sent_animated_emoji_clicks_.end() && it->send_time_ <= min_send_time) {
+ ++it;
+ }
+ sent_animated_emoji_clicks_.erase(sent_animated_emoji_clicks_.begin(), it);
+}
+
+bool StickersManager::is_sent_animated_emoji_click(DialogId dialog_id, const string &emoji) {
+ flush_sent_animated_emoji_clicks();
+ for (const auto &click : sent_animated_emoji_clicks_) {
+ if (click.dialog_id_ == dialog_id && click.emoji_ == emoji) {
+ return true;
+ }
+ }
+ return false;
+}
+
+Status StickersManager::on_animated_emoji_message_clicked(string &&emoji, FullMessageId full_message_id, string data) {
+ if (td_->auth_manager_->is_bot() || disable_animated_emojis_) {
+ return Status::OK();
+ }
+
+ TRY_RESULT(value, json_decode(data));
+ if (value.type() != JsonValue::Type::Object) {
+ return Status::Error("Expected an object");
+ }
+ auto &object = value.get_object();
+ TRY_RESULT(version, get_json_object_int_field(object, "v", false));
+ if (version != 1) {
+ return Status::OK();
+ }
+ TRY_RESULT(array_value, get_json_object_field(object, "a", JsonValue::Type::Array, false));
+ auto &array = array_value.get_array();
+ if (array.size() > 20) {
+ return Status::Error("Click array is too big");
+ }
+ vector<std::pair<int, double>> clicks;
+ double previous_start_time = 0.0;
+ double adjustment = 0.0;
+ for (auto &click : array) {
+ if (click.type() != JsonValue::Type::Object) {
+ return Status::Error("Expected clicks as JSON objects");
+ }
+ auto &click_object = click.get_object();
+ TRY_RESULT(index, get_json_object_int_field(click_object, "i", false));
+ if (index <= 0 || index > 9) {
+ return Status::Error("Wrong index");
+ }
+ TRY_RESULT(start_time, get_json_object_double_field(click_object, "t", false));
+ if (!std::isfinite(start_time)) {
+ return Status::Error("Receive invalid start time");
+ }
+ if (start_time < previous_start_time) {
+ return Status::Error("Non-monotonic start time");
+ }
+ if (start_time > previous_start_time + 3) {
+ return Status::Error("Too big delay between clicks");
+ }
+ previous_start_time = start_time;
+
+ auto adjusted_start_time =
+ clicks.empty() ? 0.0 : max(start_time + adjustment, clicks.back().second + MIN_ANIMATED_EMOJI_CLICK_DELAY);
+ adjustment = adjusted_start_time - start_time;
+ clicks.emplace_back(static_cast<int>(index), adjusted_start_time);
+ }
+
+ auto &special_sticker_set = add_special_sticker_set(SpecialStickerSetType::animated_emoji_click());
+ if (special_sticker_set.id_.is_valid()) {
+ auto sticker_set = get_sticker_set(special_sticker_set.id_);
+ CHECK(sticker_set != nullptr);
+ if (sticker_set->was_loaded_) {
+ schedule_update_animated_emoji_clicked(sticker_set, emoji, full_message_id, std::move(clicks));
+ return Status::OK();
+ }
+ }
+
+ LOG(INFO) << "Waiting for an emoji click sticker set needed in " << full_message_id;
+ load_special_sticker_set(special_sticker_set);
+
+ PendingOnAnimatedEmojiClicked pending_request;
+ pending_request.emoji_ = std::move(emoji);
+ pending_request.full_message_id_ = full_message_id;
+ pending_request.clicks_ = std::move(clicks);
+ pending_on_animated_emoji_message_clicked_.push_back(std::move(pending_request));
+ return Status::OK();
+}
+
+void StickersManager::schedule_update_animated_emoji_clicked(const StickerSet *sticker_set, Slice emoji,
+ FullMessageId full_message_id,
+ vector<std::pair<int, double>> clicks) {
+ if (clicks.empty()) {
+ return;
+ }
+ if (td_->messages_manager_->is_message_edited_recently(full_message_id, 2)) {
+ // includes deleted full_message_id
+ return;
+ }
+ auto dialog_id = full_message_id.get_dialog_id();
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Write)) {
+ return;
+ }
+
+ auto all_sticker_ids = get_animated_emoji_click_stickers(sticker_set, emoji);
+ FlatHashMap<int32, FileId> sticker_ids;
+ for (auto sticker_id : all_sticker_ids) {
+ auto it = sticker_set->sticker_emojis_map_.find(sticker_id);
+ if (it != sticker_set->sticker_emojis_map_.end()) {
+ for (auto &sticker_emoji : it->second) {
+ auto number = get_emoji_number(sticker_emoji);
+ if (number > 0) {
+ sticker_ids[number] = sticker_id;
+ }
+ }
+ }
+ }
+
+ auto now = Time::now();
+ auto start_time = max(now, next_update_animated_emoji_clicked_time_);
+ for (const auto &click : clicks) {
+ auto index = click.first;
+ if (index <= 0) {
+ return;
+ }
+ auto sticker_id = sticker_ids[index];
+ if (!sticker_id.is_valid()) {
+ LOG(INFO) << "Failed to find sticker for " << emoji << " with index " << index;
+ return;
+ }
+ create_actor<SleepActor>(
+ "SendUpdateAnimatedEmojiClicked", start_time + click.second - now,
+ PromiseCreator::lambda([actor_id = actor_id(this), full_message_id, sticker_id](Result<Unit> result) {
+ send_closure(actor_id, &StickersManager::send_update_animated_emoji_clicked, full_message_id, sticker_id);
+ }))
+ .release();
+ }
+ next_update_animated_emoji_clicked_time_ = start_time + clicks.back().second + MIN_ANIMATED_EMOJI_CLICK_DELAY;
+}
+
+void StickersManager::send_update_animated_emoji_clicked(FullMessageId full_message_id, FileId sticker_id) {
+ if (G()->close_flag() || disable_animated_emojis_ || td_->auth_manager_->is_bot()) {
+ return;
+ }
+ if (td_->messages_manager_->is_message_edited_recently(full_message_id, 2)) {
+ // includes deleted full_message_id
+ return;
+ }
+ auto dialog_id = full_message_id.get_dialog_id();
+ if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Write)) {
+ return;
+ }
+
+ send_closure(
+ G()->td(), &Td::send_update,
+ td_api::make_object<td_api::updateAnimatedEmojiMessageClicked>(
+ dialog_id.get(), full_message_id.get_message_id().get(), get_sticker_object(sticker_id, false, true)));
+}
+
+bool StickersManager::is_active_reaction(const string &reaction) const {
+ for (auto &supported_reaction : reactions_.reactions_) {
+ if (supported_reaction.reaction_ == reaction) {
+ return supported_reaction.is_active_;
+ }
+ }
+ return false;
+}
+
+void StickersManager::view_featured_sticker_sets(const vector<StickerSetId> &sticker_set_ids) {
for (auto sticker_set_id : sticker_set_ids) {
auto set = get_sticker_set(sticker_set_id);
- if (set != nullptr && !set->is_viewed) {
- need_update_featured_sticker_sets_ = true;
- set->is_viewed = true;
+ if (set != nullptr && !set->is_viewed_) {
+ auto type = static_cast<int32>(set->sticker_type_);
+ if (td::contains(featured_sticker_set_ids_[type], sticker_set_id)) {
+ need_update_featured_sticker_sets_[type] = true;
+ }
+ set->is_viewed_ = true;
pending_viewed_featured_sticker_set_ids_.insert(sticker_set_id);
- update_sticker_set(set);
+ update_sticker_set(set, "view_featured_sticker_sets");
}
}
- send_update_featured_sticker_sets();
+ for (int32 type = 0; type < MAX_STICKER_TYPE; type++) {
+ send_update_featured_sticker_sets(static_cast<StickerType>(type));
+ }
if (!pending_viewed_featured_sticker_set_ids_.empty() && !pending_featured_sticker_set_views_timeout_.has_timeout()) {
- LOG(INFO) << "Have pending viewed featured sticker sets";
+ LOG(INFO) << "Have pending viewed trending sticker sets";
pending_featured_sticker_set_views_timeout_.set_callback(read_featured_sticker_sets);
pending_featured_sticker_set_views_timeout_.set_callback_data(static_cast<void *>(td_));
pending_featured_sticker_set_views_timeout_.set_timeout_in(MAX_FEATURED_STICKER_SET_VIEW_DELAY);
@@ -2596,31 +6610,37 @@ void StickersManager::view_featured_sticker_sets(const vector<int64> &sticker_se
}
void StickersManager::read_featured_sticker_sets(void *td_void) {
+ if (G()->close_flag()) {
+ return;
+ }
+
CHECK(td_void != nullptr);
auto td = static_cast<Td *>(td_void);
auto &set_ids = td->stickers_manager_->pending_viewed_featured_sticker_set_ids_;
- td->create_handler<ReadFeaturedStickerSetsQuery>()->send(vector<int64>(set_ids.begin(), set_ids.end()));
+ vector<StickerSetId> sticker_set_ids;
+ for (auto sticker_set_id : set_ids) {
+ sticker_set_ids.push_back(sticker_set_id);
+ }
set_ids.clear();
+ td->create_handler<ReadFeaturedStickerSetsQuery>()->send(std::move(sticker_set_ids));
}
-std::pair<int32, vector<int64>> StickersManager::get_archived_sticker_sets(bool is_masks, int64 offset_sticker_set_id,
- int32 limit, bool force,
- Promise<Unit> &&promise) {
+std::pair<int32, vector<StickerSetId>> StickersManager::get_archived_sticker_sets(StickerType sticker_type,
+ StickerSetId offset_sticker_set_id,
+ int32 limit, bool force,
+ Promise<Unit> &&promise) {
if (limit <= 0) {
- promise.set_error(Status::Error(3, "Parameter limit must be positive"));
+ promise.set_error(Status::Error(400, "Parameter limit must be positive"));
return {};
}
- vector<int64> &sticker_set_ids = archived_sticker_set_ids_[is_masks];
- int32 total_count = total_archived_sticker_set_count_[is_masks];
- if (total_count < 0) {
- total_count = 0;
- }
-
- if (!sticker_set_ids.empty()) {
+ auto type = static_cast<int32>(sticker_type);
+ vector<StickerSetId> &sticker_set_ids = archived_sticker_set_ids_[type];
+ int32 total_count = total_archived_sticker_set_count_[type];
+ if (total_count >= 0) {
auto offset_it = sticker_set_ids.begin();
- if (offset_sticker_set_id != 0) {
+ if (offset_sticker_set_id.is_valid()) {
offset_it = std::find(sticker_set_ids.begin(), sticker_set_ids.end(), offset_sticker_set_id);
if (offset_it == sticker_set_ids.end()) {
offset_it = sticker_set_ids.begin();
@@ -2628,13 +6648,13 @@ std::pair<int32, vector<int64>> StickersManager::get_archived_sticker_sets(bool
++offset_it;
}
}
- vector<int64> result;
+ vector<StickerSetId> result;
while (result.size() < static_cast<size_t>(limit)) {
if (offset_it == sticker_set_ids.end()) {
break;
}
auto sticker_set_id = *offset_it++;
- if (sticker_set_id == 0) { // end of the list
+ if (!sticker_set_id.is_valid()) { // end of the list
promise.set_value(Unit());
return {total_count, std::move(result)};
}
@@ -2646,192 +6666,485 @@ std::pair<int32, vector<int64>> StickersManager::get_archived_sticker_sets(bool
}
}
- td_->create_handler<GetArchivedStickerSetsQuery>(std::move(promise))->send(is_masks, offset_sticker_set_id, limit);
+ td_->create_handler<GetArchivedStickerSetsQuery>(std::move(promise))
+ ->send(sticker_type, offset_sticker_set_id, limit);
return {};
}
void StickersManager::on_get_archived_sticker_sets(
- bool is_masks, vector<tl_object_ptr<telegram_api::StickerSetCovered>> &&sticker_sets, int32 total_count) {
- vector<int64> &sticker_set_ids = archived_sticker_set_ids_[is_masks];
- if (sticker_set_ids.size() > 0 && sticker_set_ids.back() == 0) {
+ StickerType sticker_type, StickerSetId offset_sticker_set_id,
+ vector<tl_object_ptr<telegram_api::StickerSetCovered>> &&sticker_sets, int32 total_count) {
+ auto type = static_cast<int32>(sticker_type);
+ vector<StickerSetId> &sticker_set_ids = archived_sticker_set_ids_[type];
+ if (!sticker_set_ids.empty() && sticker_set_ids.back() == StickerSetId()) {
return;
}
+ if (total_count < 0) {
+ LOG(ERROR) << "Receive " << total_count << " as total count of archived sticker sets";
+ }
+
+ // if 0 sticker sets are received, then set offset_sticker_set_id was found and there are no stickers after it
+ // or it wasn't found and there are no archived sets at all
+ bool is_last =
+ sticker_sets.empty() && (!offset_sticker_set_id.is_valid() ||
+ (!sticker_set_ids.empty() && offset_sticker_set_id == sticker_set_ids.back()));
- total_archived_sticker_set_count_[is_masks] = total_count;
+ total_archived_sticker_set_count_[type] = total_count;
for (auto &sticker_set_covered : sticker_sets) {
- auto sticker_set_id = on_get_sticker_set_covered(std::move(sticker_set_covered), false);
- if (sticker_set_id != 0) {
+ auto sticker_set_id =
+ on_get_sticker_set_covered(std::move(sticker_set_covered), false, "on_get_archived_sticker_sets");
+ if (sticker_set_id.is_valid()) {
auto sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- update_sticker_set(sticker_set);
+ update_sticker_set(sticker_set, "on_get_archived_sticker_sets");
- if (std::find(sticker_set_ids.begin(), sticker_set_ids.end(), sticker_set_id) == sticker_set_ids.end()) {
+ if (!td::contains(sticker_set_ids, sticker_set_id)) {
sticker_set_ids.push_back(sticker_set_id);
}
}
}
- if (sticker_set_ids.size() >= static_cast<size_t>(total_count)) {
- if (sticker_set_ids.size() > static_cast<size_t>(total_count)) {
- LOG(ERROR) << "Expected total of " << total_count << " archived sticker sets, but only " << sticker_set_ids.size()
+ if (sticker_set_ids.size() >= static_cast<size_t>(total_count) || is_last) {
+ if (sticker_set_ids.size() != static_cast<size_t>(total_count)) {
+ LOG(ERROR) << "Expected total of " << total_count << " archived sticker sets, but " << sticker_set_ids.size()
<< " found";
- total_archived_sticker_set_count_[is_masks] = static_cast<int32>(sticker_set_ids.size());
+ total_archived_sticker_set_count_[type] = static_cast<int32>(sticker_set_ids.size());
}
- sticker_set_ids.push_back(0);
+ sticker_set_ids.push_back(StickerSetId());
}
send_update_installed_sticker_sets();
}
-vector<int64> StickersManager::get_featured_sticker_sets(Promise<Unit> &&promise) {
- if (!are_featured_sticker_sets_loaded_) {
- load_featured_sticker_sets(std::move(promise));
+td_api::object_ptr<td_api::trendingStickerSets> StickersManager::get_featured_sticker_sets(StickerType sticker_type,
+ int32 offset, int32 limit,
+ Promise<Unit> &&promise) {
+ if (offset < 0) {
+ promise.set_error(Status::Error(400, "Parameter offset must be non-negative"));
+ return {};
+ }
+
+ if (limit < 0) {
+ promise.set_error(Status::Error(400, "Parameter limit must be non-negative"));
+ return {};
+ }
+ if (limit == 0) {
+ offset = 0;
+ }
+
+ if (sticker_type == StickerType::Mask) {
+ promise.set_value(Unit());
+ return get_trending_sticker_sets_object(sticker_type, {});
+ }
+ auto type = static_cast<int32>(sticker_type);
+
+ if (!are_featured_sticker_sets_loaded_[type]) {
+ load_featured_sticker_sets(sticker_type, std::move(promise));
+ return {};
+ }
+ reload_featured_sticker_sets(sticker_type, false);
+
+ auto set_count = static_cast<int32>(featured_sticker_set_ids_[type].size());
+ if (offset < set_count) {
+ if (limit > set_count - offset) {
+ limit = set_count - offset;
+ }
+ promise.set_value(Unit());
+ auto begin = featured_sticker_set_ids_[type].begin() + offset;
+ return get_trending_sticker_sets_object(sticker_type, {begin, begin + limit});
+ }
+
+ if (offset == set_count && are_old_featured_sticker_sets_invalidated_[type]) {
+ invalidate_old_featured_sticker_sets(sticker_type);
+ }
+
+ auto total_count =
+ set_count + (old_featured_sticker_set_count_[type] == -1 ? 1 : old_featured_sticker_set_count_[type]);
+ if (offset < total_count || old_featured_sticker_set_count_[type] == -1) {
+ offset -= set_count;
+ set_count = static_cast<int32>(old_featured_sticker_set_ids_[type].size());
+ if (offset < set_count) {
+ if (limit > set_count - offset) {
+ limit = set_count - offset;
+ }
+ promise.set_value(Unit());
+ auto begin = old_featured_sticker_set_ids_[type].begin() + offset;
+ return get_trending_sticker_sets_object(sticker_type, {begin, begin + limit});
+ }
+ if (offset > set_count) {
+ promise.set_error(
+ Status::Error(400, "Too big offset specified; trending sticker sets can be received only consequently"));
+ return {};
+ }
+
+ load_old_featured_sticker_sets(sticker_type, std::move(promise));
return {};
}
- reload_featured_sticker_sets(false);
promise.set_value(Unit());
- return featured_sticker_set_ids_;
+ return get_trending_sticker_sets_object(sticker_type, {});
+}
+
+void StickersManager::on_old_featured_sticker_sets_invalidated(StickerType sticker_type) {
+ if (sticker_type != StickerType::Regular) {
+ return;
+ }
+
+ auto type = static_cast<int32>(sticker_type);
+ LOG(INFO) << "Invalidate old trending sticker sets";
+ are_old_featured_sticker_sets_invalidated_[type] = true;
+
+ if (!G()->parameters().use_file_db) {
+ return;
+ }
+
+ G()->td_db()->get_binlog_pmc()->set("invalidate_old_featured_sticker_sets", "1");
+}
+
+void StickersManager::invalidate_old_featured_sticker_sets(StickerType sticker_type) {
+ if (G()->close_flag()) {
+ return;
+ }
+ if (sticker_type != StickerType::Regular) {
+ return;
+ }
+
+ auto type = static_cast<int32>(sticker_type);
+ LOG(INFO) << "Invalidate old featured sticker sets";
+ if (G()->parameters().use_file_db) {
+ G()->td_db()->get_binlog_pmc()->erase("invalidate_old_featured_sticker_sets");
+ G()->td_db()->get_sqlite_pmc()->erase_by_prefix("sssoldfeatured", Auto());
+ }
+ are_old_featured_sticker_sets_invalidated_[type] = false;
+ old_featured_sticker_set_ids_[type].clear();
+
+ old_featured_sticker_set_generation_[type]++;
+ fail_promises(load_old_featured_sticker_sets_queries_, Status::Error(400, "Trending sticker sets were updated"));
+}
+
+void StickersManager::set_old_featured_sticker_set_count(StickerType sticker_type, int32 count) {
+ auto type = static_cast<int32>(sticker_type);
+ if (old_featured_sticker_set_count_[type] == count) {
+ return;
+ }
+ if (sticker_type != StickerType::Regular) {
+ return;
+ }
+
+ on_old_featured_sticker_sets_invalidated(sticker_type);
+
+ old_featured_sticker_set_count_[type] = count;
+ need_update_featured_sticker_sets_[type] = true;
+
+ if (!G()->parameters().use_file_db) {
+ return;
+ }
+
+ LOG(INFO) << "Save old trending sticker set count " << count << " to binlog";
+ G()->td_db()->get_binlog_pmc()->set("old_featured_sticker_set_count", to_string(count));
+}
+
+void StickersManager::fix_old_featured_sticker_set_count(StickerType sticker_type) {
+ auto type = static_cast<int32>(sticker_type);
+ auto known_count = static_cast<int32>(old_featured_sticker_set_ids_[type].size());
+ if (old_featured_sticker_set_count_[type] < known_count) {
+ if (old_featured_sticker_set_count_[type] >= 0) {
+ LOG(ERROR) << "Have old trending sticker set count " << old_featured_sticker_set_count_[type] << ", but have "
+ << known_count << " old trending sticker sets";
+ }
+ set_old_featured_sticker_set_count(sticker_type, known_count);
+ }
+ if (old_featured_sticker_set_count_[type] > known_count && known_count % OLD_FEATURED_STICKER_SET_SLICE_SIZE != 0) {
+ LOG(ERROR) << "Have " << known_count << " old sticker sets out of " << old_featured_sticker_set_count_[type];
+ set_old_featured_sticker_set_count(sticker_type, known_count);
+ }
}
void StickersManager::on_get_featured_sticker_sets(
+ StickerType sticker_type, int32 offset, int32 limit, uint32 generation,
tl_object_ptr<telegram_api::messages_FeaturedStickers> &&sticker_sets_ptr) {
- next_featured_sticker_sets_load_time_ = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
+ auto type = static_cast<int32>(sticker_type);
+ if (offset < 0) {
+ next_featured_sticker_sets_load_time_[type] = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
+ }
int32 constructor_id = sticker_sets_ptr->get_id();
if (constructor_id == telegram_api::messages_featuredStickersNotModified::ID) {
- LOG(INFO) << "Featured stickers are not modified";
+ LOG(INFO) << "Trending sticker sets are not modified";
+ auto *stickers = static_cast<const telegram_api::messages_featuredStickersNotModified *>(sticker_sets_ptr.get());
+ if (offset >= 0 && generation == old_featured_sticker_set_generation_[type]) {
+ set_old_featured_sticker_set_count(sticker_type, stickers->count_);
+ fix_old_featured_sticker_set_count(sticker_type);
+ }
+ send_update_featured_sticker_sets(sticker_type);
return;
}
CHECK(constructor_id == telegram_api::messages_featuredStickers::ID);
auto featured_stickers = move_tl_object_as<telegram_api::messages_featuredStickers>(sticker_sets_ptr);
- vector<int64> featured_sticker_set_ids;
- std::unordered_set<int64> unread_sticker_set_ids(featured_stickers->unread_.begin(),
- featured_stickers->unread_.end());
+ if (featured_stickers->premium_ != are_featured_sticker_sets_premium_[type]) {
+ on_old_featured_sticker_sets_invalidated(sticker_type);
+ if (offset >= 0) {
+ featured_stickers->premium_ = are_featured_sticker_sets_premium_[type];
+ reload_featured_sticker_sets(sticker_type, true);
+ }
+ }
+
+ if (offset >= 0 && generation == old_featured_sticker_set_generation_[type]) {
+ set_old_featured_sticker_set_count(sticker_type, featured_stickers->count_);
+ // the count will be fixed in on_load_old_featured_sticker_sets_finished
+ }
+
+ FlatHashSet<StickerSetId, StickerSetIdHash> unread_sticker_set_ids;
+ for (auto &unread_sticker_set_id : featured_stickers->unread_) {
+ StickerSetId sticker_set_id(unread_sticker_set_id);
+ if (sticker_set_id.is_valid()) {
+ unread_sticker_set_ids.insert(sticker_set_id);
+ }
+ }
+
+ vector<StickerSetId> featured_sticker_set_ids;
for (auto &sticker_set : featured_stickers->sets_) {
- int64 set_id = on_get_sticker_set_covered(std::move(sticker_set), true);
- if (set_id == 0) {
+ StickerSetId set_id = on_get_sticker_set_covered(std::move(sticker_set), true, "on_get_featured_sticker_sets");
+ if (!set_id.is_valid()) {
continue;
}
auto set = get_sticker_set(set_id);
CHECK(set != nullptr);
bool is_viewed = unread_sticker_set_ids.count(set_id) == 0;
- if (is_viewed != set->is_viewed) {
- set->is_viewed = is_viewed;
- set->is_changed = true;
+ if (is_viewed != set->is_viewed_) {
+ set->is_viewed_ = is_viewed;
+ set->is_changed_ = true;
}
- update_sticker_set(set);
+ update_sticker_set(set, "on_get_featured_sticker_sets 2");
featured_sticker_set_ids.push_back(set_id);
}
send_update_installed_sticker_sets();
- on_load_featured_sticker_sets_finished(std::move(featured_sticker_set_ids));
-
- LOG_IF(ERROR, featured_sticker_sets_hash_ != featured_stickers->hash_) << "Featured sticker sets hash mismatch";
+ if (offset >= 0) {
+ if (generation == old_featured_sticker_set_generation_[type] && sticker_type == StickerType::Regular) {
+ if (G()->parameters().use_file_db && !G()->close_flag()) {
+ LOG(INFO) << "Save old trending sticker sets to database with offset "
+ << old_featured_sticker_set_ids_[type].size();
+ CHECK(old_featured_sticker_set_ids_[type].size() % OLD_FEATURED_STICKER_SET_SLICE_SIZE == 0);
+ StickerSetListLogEvent log_event(featured_sticker_set_ids, false);
+ G()->td_db()->get_sqlite_pmc()->set(PSTRING() << "sssoldfeatured" << old_featured_sticker_set_ids_[type].size(),
+ log_event_store(log_event).as_slice().str(), Auto());
+ }
+ on_load_old_featured_sticker_sets_finished(sticker_type, generation, std::move(featured_sticker_set_ids));
+ }
- if (!G()->parameters().use_file_db) {
+ send_update_featured_sticker_sets(sticker_type); // because of changed count
return;
}
- LOG(INFO) << "Save featured sticker sets to database";
- StickerSetListLogEvent log_event(featured_sticker_set_ids_);
- G()->td_db()->get_sqlite_pmc()->set("sssfeatured", log_event_store(log_event).as_slice().str(), Auto());
+ on_load_featured_sticker_sets_finished(sticker_type, std::move(featured_sticker_set_ids),
+ featured_stickers->premium_);
+
+ LOG_IF(ERROR, featured_sticker_sets_hash_[type] != featured_stickers->hash_) << "Trending sticker sets hash mismatch";
+
+ if (G()->parameters().use_file_db && !G()->close_flag()) {
+ LOG(INFO) << "Save trending sticker sets to database";
+ StickerSetListLogEvent log_event(featured_sticker_set_ids_[type], are_featured_sticker_sets_premium_[type]);
+ G()->td_db()->get_sqlite_pmc()->set(PSTRING() << "sssfeatured" << get_featured_sticker_suffix(sticker_type),
+ log_event_store(log_event).as_slice().str(), Auto());
+ }
}
-void StickersManager::on_get_featured_sticker_sets_failed(Status error) {
+void StickersManager::on_get_featured_sticker_sets_failed(StickerType sticker_type, int32 offset, int32 limit,
+ uint32 generation, Status error) {
+ auto type = static_cast<int32>(sticker_type);
CHECK(error.is_error());
- next_featured_sticker_sets_load_time_ = Time::now_cached() + Random::fast(5, 10);
- auto promises = std::move(load_featured_sticker_sets_queries_);
- load_featured_sticker_sets_queries_.clear();
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+ if (offset >= 0) {
+ if (generation != old_featured_sticker_set_generation_[type] || sticker_type != StickerType::Regular) {
+ return;
+ }
+ fail_promises(load_old_featured_sticker_sets_queries_, std::move(error));
+ } else {
+ next_featured_sticker_sets_load_time_[type] = Time::now_cached() + Random::fast(5, 10);
+ fail_promises(load_featured_sticker_sets_queries_[type], std::move(error));
}
}
-void StickersManager::load_featured_sticker_sets(Promise<Unit> &&promise) {
+void StickersManager::load_featured_sticker_sets(StickerType sticker_type, Promise<Unit> &&promise) {
+ CHECK(sticker_type != StickerType::Mask);
+ auto type = static_cast<int32>(sticker_type);
if (td_->auth_manager_->is_bot()) {
- are_featured_sticker_sets_loaded_ = true;
+ are_featured_sticker_sets_loaded_[type] = true;
+ old_featured_sticker_set_count_[type] = 0;
}
- if (are_featured_sticker_sets_loaded_) {
+ if (are_featured_sticker_sets_loaded_[type]) {
promise.set_value(Unit());
return;
}
- load_featured_sticker_sets_queries_.push_back(std::move(promise));
- if (load_featured_sticker_sets_queries_.size() == 1u) {
+ load_featured_sticker_sets_queries_[type].push_back(std::move(promise));
+ if (load_featured_sticker_sets_queries_[type].size() == 1u) {
if (G()->parameters().use_file_db) {
- LOG(INFO) << "Trying to load featured sticker sets from database";
- G()->td_db()->get_sqlite_pmc()->get("sssfeatured", PromiseCreator::lambda([](string value) {
+ LOG(INFO) << "Trying to load trending sticker sets from database";
+ G()->td_db()->get_sqlite_pmc()->get(PSTRING() << "sssfeatured" << get_featured_sticker_suffix(sticker_type),
+ PromiseCreator::lambda([sticker_type](string value) {
send_closure(G()->stickers_manager(),
&StickersManager::on_load_featured_sticker_sets_from_database,
- std::move(value));
+ sticker_type, std::move(value));
}));
} else {
- LOG(INFO) << "Trying to load featured sticker sets from server";
- reload_featured_sticker_sets(true);
+ LOG(INFO) << "Trying to load trending sticker sets from server";
+ reload_featured_sticker_sets(sticker_type, true);
}
}
}
-void StickersManager::on_load_featured_sticker_sets_from_database(string value) {
+void StickersManager::on_load_featured_sticker_sets_from_database(StickerType sticker_type, string value) {
+ if (G()->close_flag()) {
+ return;
+ }
if (value.empty()) {
- LOG(INFO) << "Featured sticker sets aren't found in database";
- reload_featured_sticker_sets(true);
+ LOG(INFO) << "Trending " << sticker_type << " sticker sets aren't found in database";
+ reload_featured_sticker_sets(sticker_type, true);
return;
}
- LOG(INFO) << "Successfully loaded featured sticker sets list of size " << value.size() << " from database";
+ LOG(INFO) << "Successfully loaded trending " << sticker_type << " sticker set list of size " << value.size()
+ << " from database";
StickerSetListLogEvent log_event;
- log_event_parse(log_event, value).ensure();
+ auto status = log_event_parse(log_event, value);
+ if (status.is_error()) {
+ // can't happen unless database is broken
+ LOG(ERROR) << "Can't load trending sticker set list: " << status << ' ' << format::as_hex_dump<4>(Slice(value));
+ return reload_featured_sticker_sets(sticker_type, true);
+ }
- vector<int64> sets_to_load;
- for (auto sticker_set_id : log_event.sticker_set_ids) {
+ vector<StickerSetId> sets_to_load;
+ for (auto sticker_set_id : log_event.sticker_set_ids_) {
StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- if (!sticker_set->is_inited) {
+ if (!sticker_set->is_inited_) {
sets_to_load.push_back(sticker_set_id);
}
}
load_sticker_sets_without_stickers(
std::move(sets_to_load),
- PromiseCreator::lambda([sticker_set_ids = std::move(log_event.sticker_set_ids)](Result<> result) mutable {
+ PromiseCreator::lambda([sticker_type, sticker_set_ids = std::move(log_event.sticker_set_ids_),
+ is_premium = log_event.is_premium_](Result<> result) mutable {
if (result.is_ok()) {
- send_closure(G()->stickers_manager(), &StickersManager::on_load_featured_sticker_sets_finished,
- std::move(sticker_set_ids));
+ send_closure(G()->stickers_manager(), &StickersManager::on_load_featured_sticker_sets_finished, sticker_type,
+ std::move(sticker_set_ids), is_premium);
+ } else {
+ send_closure(G()->stickers_manager(), &StickersManager::reload_featured_sticker_sets, sticker_type, true);
}
}));
}
-void StickersManager::on_load_featured_sticker_sets_finished(vector<int64> &&featured_sticker_set_ids) {
- featured_sticker_set_ids_ = std::move(featured_sticker_set_ids);
- are_featured_sticker_sets_loaded_ = true;
- need_update_featured_sticker_sets_ = true;
- send_update_featured_sticker_sets();
- auto promises = std::move(load_featured_sticker_sets_queries_);
- load_featured_sticker_sets_queries_.clear();
- for (auto &promise : promises) {
- promise.set_value(Unit());
+void StickersManager::on_load_featured_sticker_sets_finished(StickerType sticker_type,
+ vector<StickerSetId> &&featured_sticker_set_ids,
+ bool is_premium) {
+ auto type = static_cast<int32>(sticker_type);
+ if (!featured_sticker_set_ids_[type].empty() && featured_sticker_set_ids != featured_sticker_set_ids_[type]) {
+ // always invalidate old featured sticker sets when current featured sticker sets change
+ on_old_featured_sticker_sets_invalidated(sticker_type);
}
+ featured_sticker_set_ids_[type] = std::move(featured_sticker_set_ids);
+ are_featured_sticker_sets_premium_[type] = is_premium;
+ are_featured_sticker_sets_loaded_[type] = true;
+ need_update_featured_sticker_sets_[type] = true;
+ send_update_featured_sticker_sets(sticker_type);
+ set_promises(load_featured_sticker_sets_queries_[type]);
}
-vector<int64> StickersManager::get_attached_sticker_sets(FileId file_id, Promise<Unit> &&promise) {
- if (!file_id.is_valid()) {
- promise.set_error(Status::Error(5, "Wrong file_id specified"));
- return {};
+void StickersManager::load_old_featured_sticker_sets(StickerType sticker_type, Promise<Unit> &&promise) {
+ CHECK(sticker_type == StickerType::Regular);
+ CHECK(!td_->auth_manager_->is_bot());
+ auto type = static_cast<int32>(sticker_type);
+ CHECK(old_featured_sticker_set_ids_[type].size() % OLD_FEATURED_STICKER_SET_SLICE_SIZE == 0);
+ load_old_featured_sticker_sets_queries_.push_back(std::move(promise));
+ if (load_old_featured_sticker_sets_queries_.size() == 1u) {
+ if (G()->parameters().use_file_db) {
+ LOG(INFO) << "Trying to load old trending sticker sets from database with offset "
+ << old_featured_sticker_set_ids_[type].size();
+ G()->td_db()->get_sqlite_pmc()->get(
+ PSTRING() << "sssoldfeatured" << old_featured_sticker_set_ids_[type].size(),
+ PromiseCreator::lambda([sticker_type, generation = old_featured_sticker_set_generation_[type]](string value) {
+ send_closure(G()->stickers_manager(), &StickersManager::on_load_old_featured_sticker_sets_from_database,
+ sticker_type, generation, std::move(value));
+ }));
+ } else {
+ LOG(INFO) << "Trying to load old trending sticker sets from server with offset "
+ << old_featured_sticker_set_ids_[type].size();
+ reload_old_featured_sticker_sets(sticker_type);
+ }
}
+}
- auto file_view = td_->file_manager_->get_file_view(file_id);
- if (file_view.empty()) {
- promise.set_error(Status::Error(5, "File not found"));
- return {};
+void StickersManager::on_load_old_featured_sticker_sets_from_database(StickerType sticker_type, uint32 generation,
+ string value) {
+ if (G()->close_flag()) {
+ return;
}
- if (!file_view.has_remote_location() || file_view.remote_location().is_encrypted() ||
- file_view.remote_location().is_web()) {
- promise.set_value(Unit());
+ CHECK(sticker_type == StickerType::Regular);
+ auto type = static_cast<int32>(sticker_type);
+ if (generation != old_featured_sticker_set_generation_[type]) {
+ return;
+ }
+ if (value.empty()) {
+ LOG(INFO) << "Old trending sticker sets aren't found in database";
+ return reload_old_featured_sticker_sets(sticker_type);
+ }
+
+ LOG(INFO) << "Successfully loaded old trending sticker set list of size " << value.size()
+ << " from database with offset " << old_featured_sticker_set_ids_[type].size();
+
+ StickerSetListLogEvent log_event;
+ auto status = log_event_parse(log_event, value);
+ if (status.is_error()) {
+ // can't happen unless database is broken
+ LOG(ERROR) << "Can't load old trending sticker set list: " << status << ' ' << format::as_hex_dump<4>(Slice(value));
+ return reload_old_featured_sticker_sets(sticker_type);
+ }
+ CHECK(!log_event.is_premium_);
+
+ vector<StickerSetId> sets_to_load;
+ for (auto sticker_set_id : log_event.sticker_set_ids_) {
+ StickerSet *sticker_set = get_sticker_set(sticker_set_id);
+ CHECK(sticker_set != nullptr);
+ if (!sticker_set->is_inited_) {
+ sets_to_load.push_back(sticker_set_id);
+ }
+ }
+
+ load_sticker_sets_without_stickers(
+ std::move(sets_to_load),
+ PromiseCreator::lambda(
+ [sticker_type, generation, sticker_set_ids = std::move(log_event.sticker_set_ids_)](Result<> result) mutable {
+ if (result.is_ok()) {
+ send_closure(G()->stickers_manager(), &StickersManager::on_load_old_featured_sticker_sets_finished,
+ sticker_type, generation, std::move(sticker_set_ids));
+ } else {
+ send_closure(G()->stickers_manager(), &StickersManager::reload_old_featured_sticker_sets, sticker_type,
+ generation);
+ }
+ }));
+}
+
+void StickersManager::on_load_old_featured_sticker_sets_finished(StickerType sticker_type, uint32 generation,
+ vector<StickerSetId> &&featured_sticker_set_ids) {
+ auto type = static_cast<int32>(sticker_type);
+ if (generation != old_featured_sticker_set_generation_[type]) {
+ fix_old_featured_sticker_set_count(sticker_type); // must never be needed
+ return;
+ }
+ CHECK(sticker_type == StickerType::Regular);
+ append(old_featured_sticker_set_ids_[type], std::move(featured_sticker_set_ids));
+ fix_old_featured_sticker_set_count(sticker_type);
+ set_promises(load_old_featured_sticker_sets_queries_);
+}
+
+vector<StickerSetId> StickersManager::get_attached_sticker_sets(FileId file_id, Promise<Unit> &&promise) {
+ if (!file_id.is_valid()) {
+ promise.set_error(Status::Error(400, "Wrong file_id specified"));
return {};
}
@@ -2841,29 +7154,49 @@ vector<int64> StickersManager::get_attached_sticker_sets(FileId file_id, Promise
return it->second;
}
+ send_get_attached_stickers_query(file_id, std::move(promise));
+ return {};
+}
+
+void StickersManager::send_get_attached_stickers_query(FileId file_id, Promise<Unit> &&promise) {
+ auto file_view = td_->file_manager_->get_file_view(file_id);
+ if (file_view.empty()) {
+ return promise.set_error(Status::Error(400, "File not found"));
+ }
+ if (!file_view.has_remote_location() ||
+ (!file_view.remote_location().is_document() && !file_view.remote_location().is_photo()) ||
+ file_view.remote_location().is_web()) {
+ return promise.set_value(Unit());
+ }
+
tl_object_ptr<telegram_api::InputStickeredMedia> input_stickered_media;
- if (file_view.remote_location().is_photo()) {
- input_stickered_media =
- make_tl_object<telegram_api::inputStickeredMediaPhoto>(file_view.remote_location().as_input_photo());
+ string file_reference;
+ if (file_view.main_remote_location().is_photo()) {
+ auto input_photo = file_view.main_remote_location().as_input_photo();
+ file_reference = input_photo->file_reference_.as_slice().str();
+ input_stickered_media = make_tl_object<telegram_api::inputStickeredMediaPhoto>(std::move(input_photo));
} else {
- input_stickered_media =
- make_tl_object<telegram_api::inputStickeredMediaDocument>(file_view.remote_location().as_input_document());
+ auto input_document = file_view.main_remote_location().as_input_document();
+ file_reference = input_document->file_reference_.as_slice().str();
+ input_stickered_media = make_tl_object<telegram_api::inputStickeredMediaDocument>(std::move(input_document));
}
- td_->create_handler<GetAttachedStickerSetsQuery>(std::move(promise))->send(file_id, std::move(input_stickered_media));
- return {};
+ td_->create_handler<GetAttachedStickerSetsQuery>(std::move(promise))
+ ->send(file_id, std::move(file_reference), std::move(input_stickered_media));
}
void StickersManager::on_get_attached_sticker_sets(
FileId file_id, vector<tl_object_ptr<telegram_api::StickerSetCovered>> &&sticker_sets) {
- vector<int64> &sticker_set_ids = attached_sticker_sets_[file_id];
+ CHECK(file_id.is_valid());
+ vector<StickerSetId> &sticker_set_ids = attached_sticker_sets_[file_id];
sticker_set_ids.clear();
for (auto &sticker_set_covered : sticker_sets) {
- auto sticker_set_id = on_get_sticker_set_covered(std::move(sticker_set_covered), true);
- if (sticker_set_id != 0) {
+ auto sticker_set_id =
+ on_get_sticker_set_covered(std::move(sticker_set_covered), true, "on_get_attached_sticker_sets");
+ if (sticker_set_id.is_valid()) {
auto sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- update_sticker_set(sticker_set);
+ update_sticker_set(sticker_set, "on_get_attached_sticker_sets");
sticker_set_ids.push_back(sticker_set_id);
}
@@ -2873,18 +7206,24 @@ void StickersManager::on_get_attached_sticker_sets(
// -1 - order can't be applied, because some sticker sets aren't loaded or aren't installed,
// 0 - order wasn't changed, 1 - order was partly replaced by the new order, 2 - order was replaced by the new order
-int StickersManager::apply_installed_sticker_sets_order(bool is_masks, const vector<int64> &sticker_set_ids) {
- if (!are_installed_sticker_sets_loaded_[is_masks]) {
+int StickersManager::apply_installed_sticker_sets_order(StickerType sticker_type,
+ const vector<StickerSetId> &sticker_set_ids) {
+ auto type = static_cast<int32>(sticker_type);
+ if (!are_installed_sticker_sets_loaded_[type]) {
return -1;
}
- vector<int64> &current_sticker_set_ids = installed_sticker_set_ids_[is_masks];
+ vector<StickerSetId> &current_sticker_set_ids = installed_sticker_set_ids_[type];
if (sticker_set_ids == current_sticker_set_ids) {
return 0;
}
- std::unordered_set<int64> valid_set_ids(current_sticker_set_ids.begin(), current_sticker_set_ids.end());
- vector<int64> new_sticker_set_ids;
+ FlatHashSet<StickerSetId, StickerSetIdHash> valid_set_ids;
+ for (auto sticker_set_id : current_sticker_set_ids) {
+ valid_set_ids.insert(sticker_set_id);
+ }
+
+ vector<StickerSetId> new_sticker_set_ids;
for (auto sticker_set_id : sticker_set_ids) {
auto it = valid_set_ids.find(sticker_set_id);
if (it != valid_set_ids.end()) {
@@ -2898,7 +7237,7 @@ int StickersManager::apply_installed_sticker_sets_order(bool is_masks, const vec
return 0;
}
if (!valid_set_ids.empty()) {
- vector<int64> missed_sticker_set_ids;
+ vector<StickerSetId> missed_sticker_set_ids;
for (auto sticker_set_id : current_sticker_set_ids) {
auto it = valid_set_ids.find(sticker_set_id);
if (it != valid_set_ids.end()) {
@@ -2916,99 +7255,195 @@ int StickersManager::apply_installed_sticker_sets_order(bool is_masks, const vec
}
current_sticker_set_ids = std::move(new_sticker_set_ids);
- need_update_installed_sticker_sets_[is_masks] = true;
+ need_update_installed_sticker_sets_[type] = true;
if (sticker_set_ids != current_sticker_set_ids) {
return 1;
}
return 2;
}
-void StickersManager::on_update_sticker_sets_order(bool is_masks, const vector<int64> &sticker_set_ids) {
- int result = apply_installed_sticker_sets_order(is_masks, sticker_set_ids);
+void StickersManager::on_update_sticker_sets_order(StickerType sticker_type,
+ const vector<StickerSetId> &sticker_set_ids) {
+ int result = apply_installed_sticker_sets_order(sticker_type, sticker_set_ids);
+ if (result < 0) {
+ return reload_installed_sticker_sets(sticker_type, true);
+ }
+ if (result > 0) {
+ send_update_installed_sticker_sets();
+ }
+}
+
+// -1 - sticker set can't be moved to top, 0 - order wasn't changed, 1 - sticker set was moved to top
+int StickersManager::move_installed_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id) {
+ LOG(INFO) << "Move " << sticker_set_id << " to top of " << sticker_type;
+ auto type = static_cast<int32>(sticker_type);
+ if (!are_installed_sticker_sets_loaded_[type]) {
+ return -1;
+ }
+
+ vector<StickerSetId> &current_sticker_set_ids = installed_sticker_set_ids_[type];
+ auto it = std::find(current_sticker_set_ids.begin(), current_sticker_set_ids.end(), sticker_set_id);
+ if (it == current_sticker_set_ids.end()) {
+ return -1;
+ }
+ if (sticker_set_id == current_sticker_set_ids[0]) {
+ CHECK(it == current_sticker_set_ids.begin());
+ return 0;
+ }
+
+ std::rotate(current_sticker_set_ids.begin(), it, it + 1);
+
+ need_update_installed_sticker_sets_[type] = true;
+ return 1;
+}
+
+void StickersManager::on_update_move_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id) {
+ int result = move_installed_sticker_set_to_top(sticker_type, sticker_set_id);
if (result < 0) {
- return reload_installed_sticker_sets(is_masks, true);
+ return reload_installed_sticker_sets(sticker_type, true);
}
if (result > 0) {
send_update_installed_sticker_sets();
}
}
-void StickersManager::reorder_installed_sticker_sets(bool is_masks, const vector<int64> &sticker_set_ids,
+void StickersManager::reorder_installed_sticker_sets(StickerType sticker_type,
+ const vector<StickerSetId> &sticker_set_ids,
Promise<Unit> &&promise) {
- auto result = apply_installed_sticker_sets_order(is_masks, sticker_set_ids);
+ auto result = apply_installed_sticker_sets_order(sticker_type, sticker_set_ids);
if (result < 0) {
return promise.set_error(Status::Error(400, "Wrong sticker set list"));
}
if (result > 0) {
- td_->create_handler<ReorderStickerSetsQuery>()->send(is_masks, installed_sticker_set_ids_[is_masks]);
+ auto type = static_cast<int32>(sticker_type);
+ td_->create_handler<ReorderStickerSetsQuery>()->send(sticker_type, installed_sticker_set_ids_[type]);
send_update_installed_sticker_sets();
}
promise.set_value(Unit());
}
-Result<std::tuple<FileId, bool, bool>> StickersManager::prepare_input_sticker(td_api::inputSticker *sticker) {
+void StickersManager::move_sticker_set_to_top_by_sticker_id(FileId sticker_id) {
+ LOG(INFO) << "Move to top sticker set of " << sticker_id;
+ const auto *s = get_sticker(sticker_id);
+ if (s == nullptr || !s->set_id_.is_valid()) {
+ return;
+ }
+ if (s->type_ == StickerType::CustomEmoji) {
+ // just in case
+ return;
+ }
+ if (move_installed_sticker_set_to_top(s->type_, s->set_id_) > 0) {
+ send_update_installed_sticker_sets();
+ }
+}
+
+void StickersManager::move_sticker_set_to_top_by_custom_emoji_ids(const vector<CustomEmojiId> &custom_emoji_ids) {
+ LOG(INFO) << "Move to top sticker set of " << custom_emoji_ids;
+ StickerSetId sticker_set_id;
+ for (auto custom_emoji_id : custom_emoji_ids) {
+ auto sticker_id = custom_emoji_to_sticker_id_.get(custom_emoji_id);
+ if (!sticker_id.is_valid()) {
+ return;
+ }
+ const auto *s = get_sticker(sticker_id);
+ CHECK(s != nullptr);
+ CHECK(s->type_ == StickerType::CustomEmoji);
+ if (!s->set_id_.is_valid()) {
+ return;
+ }
+ if (s->set_id_ != sticker_set_id) {
+ if (sticker_set_id.is_valid()) {
+ return;
+ }
+ sticker_set_id = s->set_id_;
+ }
+ }
+ CHECK(sticker_set_id.is_valid());
+ if (move_installed_sticker_set_to_top(StickerType::CustomEmoji, sticker_set_id) > 0) {
+ send_update_installed_sticker_sets();
+ }
+}
+
+Result<std::tuple<FileId, bool, bool, StickerFormat>> StickersManager::prepare_input_sticker(
+ td_api::inputSticker *sticker, StickerType sticker_type) {
if (sticker == nullptr) {
- return Status::Error(3, "Input sticker shouldn't be empty");
+ return Status::Error(400, "Input sticker must be non-empty");
}
if (!clean_input_string(sticker->emojis_)) {
return Status::Error(400, "Emojis must be encoded in UTF-8");
}
- return prepare_input_file(sticker->png_sticker_);
+ if (sticker->format_ == nullptr) {
+ return Status::Error(400, "Sticker format must be non-empty");
+ }
+
+ return prepare_input_file(sticker->sticker_, get_sticker_format(sticker->format_), sticker_type, false);
}
-Result<std::tuple<FileId, bool, bool>> StickersManager::prepare_input_file(
- const tl_object_ptr<td_api::InputFile> &input_file) {
- auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Document, input_file, {}, false, false, false);
+Result<std::tuple<FileId, bool, bool, StickerFormat>> StickersManager::prepare_input_file(
+ const tl_object_ptr<td_api::InputFile> &input_file, StickerFormat format, StickerType type, bool for_thumbnail) {
+ auto file_type = format == StickerFormat::Tgs ? FileType::Sticker : FileType::Document;
+ auto r_file_id = td_->file_manager_->get_input_file_id(file_type, input_file, DialogId(), for_thumbnail, false);
if (r_file_id.is_error()) {
- return Status::Error(7, r_file_id.error().message());
+ return Status::Error(400, r_file_id.error().message());
}
auto file_id = r_file_id.move_as_ok();
+ if (file_id.empty()) {
+ return std::make_tuple(FileId(), false, false, StickerFormat::Unknown);
+ }
- td_->documents_manager_->create_document(file_id, PhotoSize(), "sticker.png", "image/png", false);
+ if (format == StickerFormat::Tgs) {
+ int32 width = for_thumbnail ? 100 : 512;
+ create_sticker(file_id, FileId(), string(), PhotoSize(), get_dimensions(width, width, "prepare_input_file"),
+ nullptr, nullptr, format, nullptr);
+ } else if (format == StickerFormat::Webm) {
+ td_->documents_manager_->create_document(file_id, string(), PhotoSize(), "sticker.webm", "video/webm", false);
+ } else {
+ td_->documents_manager_->create_document(file_id, string(), PhotoSize(), "sticker.png", "image/png", false);
+ }
FileView file_view = td_->file_manager_->get_file_view(file_id);
if (file_view.is_encrypted()) {
return Status::Error(400, "Can't use encrypted file");
}
- if (file_view.has_remote_location() && file_view.remote_location().is_web()) {
+ if (file_view.has_remote_location() && file_view.main_remote_location().is_web()) {
return Status::Error(400, "Can't use web file to create a sticker");
}
bool is_url = false;
bool is_local = false;
if (file_view.has_remote_location()) {
- CHECK(!file_view.remote_location().is_encrypted());
- CHECK(!file_view.remote_location().is_photo());
+ CHECK(file_view.main_remote_location().is_document());
} else {
if (file_view.has_url()) {
is_url = true;
} else {
- if (file_view.has_local_location() && file_view.local_size() > MAX_STICKER_FILE_SIZE) {
+ if (file_view.has_local_location() &&
+ file_view.expected_size() > get_max_sticker_file_size(format, type, for_thumbnail)) {
return Status::Error(400, "File is too big");
}
is_local = true;
}
}
- return std::make_tuple(file_id, is_url, is_local);
+ return std::make_tuple(file_id, is_url, is_local, format);
}
-FileId StickersManager::upload_sticker_file(UserId user_id, const tl_object_ptr<td_api::InputFile> &sticker,
+FileId StickersManager::upload_sticker_file(UserId user_id, tl_object_ptr<td_api::inputSticker> &&sticker,
Promise<Unit> &&promise) {
- auto input_user = td_->contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- promise.set_error(Status::Error(3, "User not found"));
- return FileId();
+ bool is_bot = td_->auth_manager_->is_bot();
+ if (!is_bot) {
+ user_id = td_->contacts_manager_->get_my_id();
}
- DialogId dialog_id(user_id);
- auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
- if (input_peer == nullptr) {
- promise.set_error(Status::Error(3, "Have no access to the user"));
+
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ promise.set_error(r_input_user.move_as_error());
return FileId();
}
- auto r_file_id = prepare_input_file(sticker);
+ // StickerType::Regular has less restrictions
+ auto r_file_id = prepare_input_sticker(sticker.get(), StickerType::Regular);
if (r_file_id.is_error()) {
promise.set_error(r_file_id.move_as_error());
return FileId();
@@ -3028,16 +7463,18 @@ FileId StickersManager::upload_sticker_file(UserId user_id, const tl_object_ptr<
return file_id;
}
-tl_object_ptr<telegram_api::inputStickerSetItem> StickersManager::get_input_sticker(td_api::inputSticker *sticker,
+tl_object_ptr<telegram_api::inputStickerSetItem> StickersManager::get_input_sticker(const td_api::inputSticker *sticker,
FileId file_id) const {
+ CHECK(sticker != nullptr);
FileView file_view = td_->file_manager_->get_file_view(file_id);
CHECK(file_view.has_remote_location());
- auto input_document = file_view.remote_location().as_input_document();
+ auto input_document = file_view.main_remote_location().as_input_document();
tl_object_ptr<telegram_api::maskCoords> mask_coords;
- if (sticker->mask_position_ != nullptr && sticker->mask_position_->point_ != nullptr) {
- auto point = [mask_point = std::move(sticker->mask_position_->point_)]() {
- switch (mask_point->get_id()) {
+ auto mask_position = sticker->mask_position_.get();
+ if (mask_position != nullptr && mask_position->point_ != nullptr) {
+ auto point = [mask_point_id = mask_position->point_->get_id()] {
+ switch (mask_point_id) {
case td_api::maskPointForehead::ID:
return 0;
case td_api::maskPointEyes::ID:
@@ -3051,8 +7488,8 @@ tl_object_ptr<telegram_api::inputStickerSetItem> StickersManager::get_input_stic
return -1;
}
}();
- mask_coords = make_tl_object<telegram_api::maskCoords>(
- point, sticker->mask_position_->x_shift_, sticker->mask_position_->y_shift_, sticker->mask_position_->scale_);
+ mask_coords = make_tl_object<telegram_api::maskCoords>(point, mask_position->x_shift_, mask_position->y_shift_,
+ mask_position->scale_);
}
int32 flags = 0;
@@ -3064,41 +7501,97 @@ tl_object_ptr<telegram_api::inputStickerSetItem> StickersManager::get_input_stic
std::move(mask_coords));
}
-void StickersManager::create_new_sticker_set(UserId user_id, string &title, string &short_name, bool is_masks,
- vector<tl_object_ptr<td_api::inputSticker>> &&stickers,
- Promise<Unit> &&promise) {
- auto input_user = td_->contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(3, "User not found"));
+void StickersManager::get_suggested_sticker_set_name(string title, Promise<string> &&promise) {
+ title = strip_empty_characters(title, MAX_STICKER_SET_TITLE_LENGTH);
+ if (title.empty()) {
+ return promise.set_error(Status::Error(400, "Sticker set title must be non-empty"));
}
- DialogId dialog_id(user_id);
- auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
- if (input_peer == nullptr) {
- return promise.set_error(Status::Error(3, "Have no access to the user"));
+
+ td_->create_handler<SuggestStickerSetShortNameQuery>(std::move(promise))->send(title);
+}
+
+void StickersManager::check_sticker_set_name(const string &name, Promise<CheckStickerSetNameResult> &&promise) {
+ if (name.empty()) {
+ return promise.set_value(CheckStickerSetNameResult::Invalid);
}
+ auto request_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<bool> result) mutable {
+ if (result.is_error()) {
+ auto error = result.move_as_error();
+ if (error.message() == "SHORT_NAME_INVALID") {
+ return promise.set_value(CheckStickerSetNameResult::Invalid);
+ }
+ if (error.message() == "SHORT_NAME_OCCUPIED") {
+ return promise.set_value(CheckStickerSetNameResult::Occupied);
+ }
+ return promise.set_error(std::move(error));
+ }
+
+ promise.set_value(CheckStickerSetNameResult::Ok);
+ });
+
+ return td_->create_handler<CheckStickerSetShortNameQuery>(std::move(request_promise))->send(name);
+}
+
+td_api::object_ptr<td_api::CheckStickerSetNameResult> StickersManager::get_check_sticker_set_name_result_object(
+ CheckStickerSetNameResult result) {
+ switch (result) {
+ case CheckStickerSetNameResult::Ok:
+ return td_api::make_object<td_api::checkStickerSetNameResultOk>();
+ case CheckStickerSetNameResult::Invalid:
+ return td_api::make_object<td_api::checkStickerSetNameResultNameInvalid>();
+ case CheckStickerSetNameResult::Occupied:
+ return td_api::make_object<td_api::checkStickerSetNameResultNameOccupied>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+void StickersManager::create_new_sticker_set(UserId user_id, string title, string short_name, StickerType sticker_type,
+ vector<td_api::object_ptr<td_api::inputSticker>> &&stickers,
+ string software,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise) {
+ bool is_bot = td_->auth_manager_->is_bot();
+ if (!is_bot) {
+ user_id = td_->contacts_manager_->get_my_id();
+ }
+
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id));
+
title = strip_empty_characters(title, MAX_STICKER_SET_TITLE_LENGTH);
if (title.empty()) {
- return promise.set_error(Status::Error(3, "Sticker set title can't be empty"));
+ return promise.set_error(Status::Error(400, "Sticker set title must be non-empty"));
}
short_name = strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH);
if (short_name.empty()) {
- return promise.set_error(Status::Error(3, "Sticker set name can't be empty"));
+ return promise.set_error(Status::Error(400, "Sticker set name must be non-empty"));
+ }
+
+ if (stickers.empty()) {
+ return promise.set_error(Status::Error(400, "At least 1 sticker must be specified"));
}
vector<FileId> file_ids;
file_ids.reserve(stickers.size());
vector<FileId> local_file_ids;
vector<FileId> url_file_ids;
+ FlatHashSet<int32> sticker_formats;
+ StickerFormat sticker_format = StickerFormat::Unknown;
for (auto &sticker : stickers) {
- auto r_file_id = prepare_input_sticker(sticker.get());
+ auto r_file_id = prepare_input_sticker(sticker.get(), sticker_type);
if (r_file_id.is_error()) {
return promise.set_error(r_file_id.move_as_error());
}
auto file_id = std::get<0>(r_file_id.ok());
auto is_url = std::get<1>(r_file_id.ok());
auto is_local = std::get<2>(r_file_id.ok());
+ sticker_format = std::get<3>(r_file_id.ok());
+ if (is_sticker_format_animated(sticker_format) && is_url) {
+ return promise.set_error(Status::Error(400, "Animated stickers can't be uploaded by URL"));
+ }
+ sticker_formats.insert(static_cast<int32>(get_sticker_format(sticker->format_)) + 1);
file_ids.push_back(file_id);
if (is_url) {
@@ -3107,27 +7600,31 @@ void StickersManager::create_new_sticker_set(UserId user_id, string &title, stri
local_file_ids.push_back(file_id);
}
}
+ if (sticker_formats.size() != 1) {
+ return promise.set_error(Status::Error(400, "All stickers must be of the same format"));
+ }
auto pending_new_sticker_set = make_unique<PendingNewStickerSet>();
- pending_new_sticker_set->user_id = user_id;
- pending_new_sticker_set->title = std::move(title);
- pending_new_sticker_set->short_name = short_name;
- pending_new_sticker_set->is_masks = is_masks;
- pending_new_sticker_set->file_ids = std::move(file_ids);
- pending_new_sticker_set->stickers = std::move(stickers);
- pending_new_sticker_set->promise = std::move(promise);
-
- auto &multipromise = pending_new_sticker_set->upload_files_multipromise;
+ pending_new_sticker_set->user_id_ = user_id;
+ pending_new_sticker_set->title_ = std::move(title);
+ pending_new_sticker_set->short_name_ = short_name;
+ pending_new_sticker_set->sticker_format_ = sticker_format;
+ pending_new_sticker_set->sticker_type_ = sticker_type;
+ pending_new_sticker_set->file_ids_ = std::move(file_ids);
+ pending_new_sticker_set->stickers_ = std::move(stickers);
+ pending_new_sticker_set->software_ = std::move(software);
+ pending_new_sticker_set->promise_ = std::move(promise);
+
+ auto &multipromise = pending_new_sticker_set->upload_files_multipromise_;
int64 random_id;
do {
random_id = Random::secure_int64();
- } while (random_id == 0 || pending_new_sticker_sets_.find(random_id) != pending_new_sticker_sets_.end());
+ } while (random_id == 0 || pending_new_sticker_sets_.count(random_id) > 0);
pending_new_sticker_sets_[random_id] = std::move(pending_new_sticker_set);
- multipromise.add_promise(PromiseCreator::lambda([random_id](Result<Unit> result) {
- send_closure_later(G()->stickers_manager(), &StickersManager::on_new_stickers_uploaded, random_id,
- std::move(result));
+ multipromise.add_promise(PromiseCreator::lambda([actor_id = actor_id(this), random_id](Result<Unit> result) {
+ send_closure_later(actor_id, &StickersManager::on_new_stickers_uploaded, random_id, std::move(result));
}));
auto lock_promise = multipromise.get_promise();
@@ -3143,10 +7640,17 @@ void StickersManager::create_new_sticker_set(UserId user_id, string &title, stri
}
void StickersManager::upload_sticker_file(UserId user_id, FileId file_id, Promise<Unit> &&promise) {
- CHECK(td_->documents_manager_->get_input_media(file_id, nullptr, nullptr) == nullptr);
-
- auto upload_file_id = td_->documents_manager_->dup_document(td_->file_manager_->dup_file_id(file_id), file_id);
+ FileId upload_file_id;
+ if (td_->file_manager_->get_file_view(file_id).get_type() == FileType::Sticker) {
+ CHECK(get_input_media(file_id, nullptr, nullptr, string()) == nullptr);
+ upload_file_id = dup_sticker(td_->file_manager_->dup_file_id(file_id, "upload_sticker_file"), file_id);
+ } else {
+ CHECK(td_->documents_manager_->get_input_media(file_id, nullptr, nullptr) == nullptr);
+ upload_file_id =
+ td_->documents_manager_->dup_document(td_->file_manager_->dup_file_id(file_id, "upload_sticker_file"), file_id);
+ }
+ CHECK(upload_file_id.is_valid());
being_uploaded_files_[upload_file_id] = {user_id, std::move(promise)};
LOG(INFO) << "Ask to upload sticker file " << upload_file_id;
td_->file_manager_->upload(upload_file_id, upload_sticker_file_callback_, 2, 0);
@@ -3191,14 +7695,30 @@ void StickersManager::on_upload_sticker_file_error(FileId file_id, Status status
void StickersManager::do_upload_sticker_file(UserId user_id, FileId file_id,
tl_object_ptr<telegram_api::InputFile> &&input_file,
Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
DialogId dialog_id(user_id);
auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
if (input_peer == nullptr) {
- return promise.set_error(Status::Error(3, "Have no access to the user"));
+ if (input_file != nullptr) {
+ td_->file_manager_->cancel_upload(file_id);
+ }
+ return promise.set_error(Status::Error(400, "Have no access to the user"));
}
- auto input_media = td_->documents_manager_->get_input_media(file_id, std::move(input_file), nullptr);
+ FileView file_view = td_->file_manager_->get_file_view(file_id);
+ FileType file_type = file_view.get_type();
+
+ bool had_input_file = input_file != nullptr;
+ auto input_media = file_type == FileType::Sticker
+ ? get_input_media(file_id, std::move(input_file), nullptr, string())
+ : td_->documents_manager_->get_input_media(file_id, std::move(input_file), nullptr);
CHECK(input_media != nullptr);
+ if (had_input_file && !FileManager::extract_was_uploaded(input_media)) {
+ // if we had InputFile, but has failed to use it for input_media, then we need to immediately cancel file upload
+ // so the next upload with the same file can succeed
+ td_->file_manager_->cancel_upload(file_id);
+ }
td_->create_handler<UploadStickerFileQuery>(std::move(promise))
->send(std::move(input_peer), file_id, std::move(input_media));
@@ -3207,6 +7727,7 @@ void StickersManager::do_upload_sticker_file(UserId user_id, FileId file_id,
void StickersManager::on_uploaded_sticker_file(FileId file_id, tl_object_ptr<telegram_api::MessageMedia> media,
Promise<Unit> &&promise) {
CHECK(media != nullptr);
+ LOG(INFO) << "Receive uploaded sticker file " << to_string(media);
if (media->get_id() != telegram_api::messageMediaDocument::ID) {
return promise.set_error(Status::Error(400, "Can't upload sticker file: wrong file type"));
}
@@ -3219,17 +7740,32 @@ void StickersManager::on_uploaded_sticker_file(FileId file_id, tl_object_ptr<tel
}
CHECK(document_id == telegram_api::document::ID);
+ FileView file_view = td_->file_manager_->get_file_view(file_id);
+ FileType file_type = file_view.get_type();
+ auto expected_document_type = file_type == FileType::Sticker ? Document::Type::Sticker : Document::Type::General;
+
auto parsed_document = td_->documents_manager_->on_get_document(
move_tl_object_as<telegram_api::document>(document_ptr), DialogId(), nullptr);
- if (parsed_document.first != DocumentsManager::DocumentType::General) {
+ if (parsed_document.type != expected_document_type) {
return promise.set_error(Status::Error(400, "Wrong file type"));
}
- td_->documents_manager_->merge_documents(parsed_document.second, file_id, true);
+ if (parsed_document.file_id != file_id) {
+ if (file_type == FileType::Sticker) {
+ merge_stickers(parsed_document.file_id, file_id);
+ } else {
+ // must not delete the old document, because the file_id could be used for simultaneous URL uploads
+ td_->documents_manager_->merge_documents(parsed_document.file_id, file_id);
+ }
+ }
promise.set_value(Unit());
}
void StickersManager::on_new_stickers_uploaded(int64 random_id, Result<Unit> result) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
+
auto it = pending_new_sticker_sets_.find(random_id);
CHECK(it != pending_new_sticker_sets_.end());
@@ -3239,50 +7775,70 @@ void StickersManager::on_new_stickers_uploaded(int64 random_id, Result<Unit> res
pending_new_sticker_sets_.erase(it);
if (result.is_error()) {
- pending_new_sticker_set->promise.set_error(result.move_as_error());
+ pending_new_sticker_set->promise_.set_error(result.move_as_error());
return;
}
- CHECK(pending_new_sticker_set->upload_files_multipromise.promise_count() == 0);
+ CHECK(pending_new_sticker_set->upload_files_multipromise_.promise_count() == 0);
- auto input_user = td_->contacts_manager_->get_input_user(pending_new_sticker_set->user_id);
- if (input_user == nullptr) {
- return pending_new_sticker_set->promise.set_error(Status::Error(3, "User not found"));
- }
+ auto &promise = pending_new_sticker_set->promise_;
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(pending_new_sticker_set->user_id_));
- bool is_masks = pending_new_sticker_set->is_masks;
+ StickerFormat sticker_format = pending_new_sticker_set->sticker_format_;
+ StickerType sticker_type = pending_new_sticker_set->sticker_type_;
- auto sticker_count = pending_new_sticker_set->stickers.size();
+ auto sticker_count = pending_new_sticker_set->stickers_.size();
vector<tl_object_ptr<telegram_api::inputStickerSetItem>> input_stickers;
input_stickers.reserve(sticker_count);
for (size_t i = 0; i < sticker_count; i++) {
input_stickers.push_back(
- get_input_sticker(pending_new_sticker_set->stickers[i].get(), pending_new_sticker_set->file_ids[i]));
+ get_input_sticker(pending_new_sticker_set->stickers_[i].get(), pending_new_sticker_set->file_ids_[i]));
}
- td_->create_handler<CreateNewStickerSetQuery>(std::move(pending_new_sticker_set->promise))
- ->send(std::move(input_user), pending_new_sticker_set->title, pending_new_sticker_set->short_name, is_masks,
- std::move(input_stickers));
+ td_->create_handler<CreateNewStickerSetQuery>(std::move(promise))
+ ->send(std::move(input_user), pending_new_sticker_set->title_, pending_new_sticker_set->short_name_, sticker_type,
+ sticker_format, std::move(input_stickers), pending_new_sticker_set->software_);
}
-void StickersManager::add_sticker_to_set(UserId user_id, string &short_name,
- tl_object_ptr<td_api::inputSticker> &&sticker, Promise<Unit> &&promise) {
- auto input_user = td_->contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- return promise.set_error(Status::Error(3, "User not found"));
+void StickersManager::add_sticker_to_set(UserId user_id, string short_name,
+ tl_object_ptr<td_api::inputSticker> &&sticker,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id));
+
+ short_name = clean_username(strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH));
+ if (short_name.empty()) {
+ return promise.set_error(Status::Error(400, "Sticker set name must be non-empty"));
}
- DialogId dialog_id(user_id);
- auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Write);
- if (input_peer == nullptr) {
- return promise.set_error(Status::Error(3, "Have no access to the user"));
+
+ const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name));
+ if (sticker_set != nullptr && sticker_set->was_loaded_) {
+ return do_add_sticker_to_set(user_id, short_name, std::move(sticker), std::move(promise));
}
- short_name = strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH);
- if (short_name.empty()) {
- return promise.set_error(Status::Error(3, "Sticker set name can't be empty"));
+ do_reload_sticker_set(
+ StickerSetId(), make_tl_object<telegram_api::inputStickerSetShortName>(short_name), 0,
+ PromiseCreator::lambda([actor_id = actor_id(this), user_id, short_name, sticker = std::move(sticker),
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::do_add_sticker_to_set, user_id, std::move(short_name),
+ std::move(sticker), std::move(promise));
+ }
+ }));
+}
+
+void StickersManager::do_add_sticker_to_set(UserId user_id, string short_name,
+ tl_object_ptr<td_api::inputSticker> &&sticker,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name));
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ return promise.set_error(Status::Error(400, "Sticker set not found"));
}
- auto r_file_id = prepare_input_sticker(sticker.get());
+ auto r_file_id = prepare_input_sticker(sticker.get(), sticker_set->sticker_type_);
if (r_file_id.is_error()) {
return promise.set_error(r_file_id.move_as_error());
}
@@ -3291,15 +7847,15 @@ void StickersManager::add_sticker_to_set(UserId user_id, string &short_name,
auto is_local = std::get<2>(r_file_id.ok());
auto pending_add_sticker_to_set = make_unique<PendingAddStickerToSet>();
- pending_add_sticker_to_set->short_name = short_name;
- pending_add_sticker_to_set->file_id = file_id;
- pending_add_sticker_to_set->sticker = std::move(sticker);
- pending_add_sticker_to_set->promise = std::move(promise);
+ pending_add_sticker_to_set->short_name_ = short_name;
+ pending_add_sticker_to_set->file_id_ = file_id;
+ pending_add_sticker_to_set->sticker_ = std::move(sticker);
+ pending_add_sticker_to_set->promise_ = std::move(promise);
int64 random_id;
do {
random_id = Random::secure_int64();
- } while (random_id == 0 || pending_add_sticker_to_sets_.find(random_id) != pending_add_sticker_to_sets_.end());
+ } while (random_id == 0 || pending_add_sticker_to_sets_.count(random_id) > 0);
pending_add_sticker_to_sets_[random_id] = std::move(pending_add_sticker_to_set);
auto on_upload_promise = PromiseCreator::lambda([random_id](Result<Unit> result) {
@@ -3316,6 +7872,10 @@ void StickersManager::add_sticker_to_set(UserId user_id, string &short_name,
}
void StickersManager::on_added_sticker_uploaded(int64 random_id, Result<Unit> result) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
+
auto it = pending_add_sticker_to_sets_.find(random_id);
CHECK(it != pending_add_sticker_to_sets_.end());
@@ -3325,53 +7885,169 @@ void StickersManager::on_added_sticker_uploaded(int64 random_id, Result<Unit> re
pending_add_sticker_to_sets_.erase(it);
if (result.is_error()) {
- pending_add_sticker_to_set->promise.set_error(result.move_as_error());
+ pending_add_sticker_to_set->promise_.set_error(result.move_as_error());
+ return;
+ }
+
+ td_->create_handler<AddStickerToSetQuery>(std::move(pending_add_sticker_to_set->promise_))
+ ->send(pending_add_sticker_to_set->short_name_,
+ get_input_sticker(pending_add_sticker_to_set->sticker_.get(), pending_add_sticker_to_set->file_id_));
+}
+
+void StickersManager::set_sticker_set_thumbnail(UserId user_id, string short_name,
+ tl_object_ptr<td_api::InputFile> &&thumbnail,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise) {
+ TRY_RESULT_PROMISE(promise, input_user, td_->contacts_manager_->get_input_user(user_id));
+
+ short_name = clean_username(strip_empty_characters(short_name, MAX_STICKER_SET_SHORT_NAME_LENGTH));
+ if (short_name.empty()) {
+ return promise.set_error(Status::Error(400, "Sticker set name must be non-empty"));
+ }
+
+ const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name));
+ if (sticker_set != nullptr && sticker_set->was_loaded_) {
+ return do_set_sticker_set_thumbnail(user_id, short_name, std::move(thumbnail), std::move(promise));
+ }
+
+ do_reload_sticker_set(
+ StickerSetId(), make_tl_object<telegram_api::inputStickerSetShortName>(short_name), 0,
+ PromiseCreator::lambda([actor_id = actor_id(this), user_id, short_name, thumbnail = std::move(thumbnail),
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &StickersManager::do_set_sticker_set_thumbnail, user_id, std::move(short_name),
+ std::move(thumbnail), std::move(promise));
+ }
+ }));
+}
+
+void StickersManager::do_set_sticker_set_thumbnail(UserId user_id, string short_name,
+ tl_object_ptr<td_api::InputFile> &&thumbnail,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ const StickerSet *sticker_set = get_sticker_set(short_name_to_sticker_set_id_.get(short_name));
+ if (sticker_set == nullptr || !sticker_set->was_loaded_) {
+ return promise.set_error(Status::Error(400, "Sticker set not found"));
+ }
+
+ auto r_file_id = prepare_input_file(thumbnail, sticker_set->sticker_format_, sticker_set->sticker_type_, true);
+ if (r_file_id.is_error()) {
+ return promise.set_error(r_file_id.move_as_error());
+ }
+ auto file_id = std::get<0>(r_file_id.ok());
+ auto is_url = std::get<1>(r_file_id.ok());
+ auto is_local = std::get<2>(r_file_id.ok());
+
+ if (!file_id.is_valid()) {
+ td_->create_handler<SetStickerSetThumbnailQuery>(std::move(promise))
+ ->send(short_name, telegram_api::make_object<telegram_api::inputDocumentEmpty>());
+ return;
+ }
+
+ auto pending_set_sticker_set_thumbnail = make_unique<PendingSetStickerSetThumbnail>();
+ pending_set_sticker_set_thumbnail->short_name_ = short_name;
+ pending_set_sticker_set_thumbnail->file_id_ = file_id;
+ pending_set_sticker_set_thumbnail->promise_ = std::move(promise);
+
+ int64 random_id;
+ do {
+ random_id = Random::secure_int64();
+ } while (random_id == 0 || pending_set_sticker_set_thumbnails_.count(random_id) > 0);
+ pending_set_sticker_set_thumbnails_[random_id] = std::move(pending_set_sticker_set_thumbnail);
+
+ auto on_upload_promise = PromiseCreator::lambda([random_id](Result<Unit> result) {
+ send_closure(G()->stickers_manager(), &StickersManager::on_sticker_set_thumbnail_uploaded, random_id,
+ std::move(result));
+ });
+
+ if (is_url) {
+ do_upload_sticker_file(user_id, file_id, nullptr, std::move(on_upload_promise));
+ } else if (is_local) {
+ upload_sticker_file(user_id, file_id, std::move(on_upload_promise));
+ } else {
+ on_upload_promise.set_value(Unit());
+ }
+}
+
+void StickersManager::on_sticker_set_thumbnail_uploaded(int64 random_id, Result<Unit> result) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
+
+ auto it = pending_set_sticker_set_thumbnails_.find(random_id);
+ CHECK(it != pending_set_sticker_set_thumbnails_.end());
+
+ auto pending_set_sticker_set_thumbnail = std::move(it->second);
+ CHECK(pending_set_sticker_set_thumbnail != nullptr);
+
+ pending_set_sticker_set_thumbnails_.erase(it);
+
+ if (result.is_error()) {
+ pending_set_sticker_set_thumbnail->promise_.set_error(result.move_as_error());
return;
}
- td_->create_handler<AddStickerToSetQuery>(std::move(pending_add_sticker_to_set->promise))
- ->send(pending_add_sticker_to_set->short_name,
- get_input_sticker(pending_add_sticker_to_set->sticker.get(), pending_add_sticker_to_set->file_id));
+ FileView file_view = td_->file_manager_->get_file_view(pending_set_sticker_set_thumbnail->file_id_);
+ CHECK(file_view.has_remote_location());
+
+ td_->create_handler<SetStickerSetThumbnailQuery>(std::move(pending_set_sticker_set_thumbnail->promise_))
+ ->send(pending_set_sticker_set_thumbnail->short_name_, file_view.main_remote_location().as_input_document());
+}
+
+string StickersManager::get_sticker_set_short_name(FileId sticker_id) const {
+ string sticker_set_short_name;
+ const Sticker *s = get_sticker(sticker_id);
+ if (s != nullptr && s->set_id_.is_valid()) {
+ const StickerSet *sticker_set = get_sticker_set(s->set_id_);
+ if (sticker_set != nullptr) {
+ return sticker_set->short_name_;
+ } else {
+ return to_string(s->set_id_.get());
+ }
+ }
+ return string();
}
void StickersManager::set_sticker_position_in_set(const tl_object_ptr<td_api::InputFile> &sticker, int32 position,
Promise<Unit> &&promise) {
if (position < 0) {
- return promise.set_error(Status::Error(7, "Wrong sticker position specified"));
+ return promise.set_error(Status::Error(400, "Wrong sticker position specified"));
}
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, sticker, DialogId(), false, false);
if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(7, r_file_id.error().message())); // TODO do not drop error code
+ return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code
}
auto file_id = r_file_id.move_as_ok();
auto file_view = td_->file_manager_->get_file_view(file_id);
- if (!file_view.has_remote_location() || file_view.remote_location().is_encrypted() ||
- file_view.remote_location().is_web()) {
- return promise.set_error(Status::Error(7, "Wrong sticker file specified"));
+ if (!file_view.has_remote_location() || !file_view.main_remote_location().is_document() ||
+ file_view.main_remote_location().is_web()) {
+ return promise.set_error(Status::Error(400, "Wrong sticker file specified"));
}
td_->create_handler<SetStickerPositionQuery>(std::move(promise))
- ->send(file_view.remote_location().as_input_document(), position);
+ ->send(get_sticker_set_short_name(file_id), file_view.main_remote_location().as_input_document(), position);
}
void StickersManager::remove_sticker_from_set(const tl_object_ptr<td_api::InputFile> &sticker,
Promise<Unit> &&promise) {
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, sticker, DialogId(), false, false);
if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(7, r_file_id.error().message())); // TODO do not drop error code
+ return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code
}
auto file_id = r_file_id.move_as_ok();
auto file_view = td_->file_manager_->get_file_view(file_id);
- if (!file_view.has_remote_location() || file_view.remote_location().is_encrypted() ||
- file_view.remote_location().is_web()) {
- return promise.set_error(Status::Error(7, "Wrong sticker file specified"));
+ if (!file_view.has_remote_location() || !file_view.main_remote_location().is_document() ||
+ file_view.main_remote_location().is_web()) {
+ return promise.set_error(Status::Error(400, "Wrong sticker file specified"));
}
td_->create_handler<DeleteStickerFromSetQuery>(std::move(promise))
- ->send(file_view.remote_location().as_input_document());
+ ->send(get_sticker_set_short_name(file_id), file_view.main_remote_location().as_input_document());
}
vector<FileId> StickersManager::get_attached_sticker_file_ids(const vector<int32> &int_file_ids) {
@@ -3380,22 +8056,32 @@ vector<FileId> StickersManager::get_attached_sticker_file_ids(const vector<int32
result.reserve(int_file_ids.size());
for (auto int_file_id : int_file_ids) {
FileId file_id(int_file_id, 0);
- if (get_sticker(file_id) == nullptr) {
+ const Sticker *s = get_sticker(file_id);
+ if (s == nullptr) {
LOG(WARNING) << "Can't find sticker " << file_id;
continue;
}
+ if (!s->set_id_.is_valid()) {
+ // only stickers from sticker sets can be attached to files
+ continue;
+ }
+ if (s->type_ == StickerType::CustomEmoji) {
+ // custom emoji stickers can't can be attached to files
+ continue;
+ }
+
auto file_view = td_->file_manager_->get_file_view(file_id);
CHECK(!file_view.empty());
if (!file_view.has_remote_location()) {
- LOG(WARNING) << "Sticker " << file_id << " has no remote location";
+ LOG(ERROR) << "Sticker " << file_id << " has no remote location";
continue;
}
- if (file_view.remote_location().is_encrypted()) {
- LOG(WARNING) << "Sticker " << file_id << " is encrypted";
+ if (file_view.remote_location().is_web()) {
+ LOG(ERROR) << "Sticker " << file_id << " is web";
continue;
}
- if (file_view.remote_location().is_web()) {
- LOG(WARNING) << "Sticker " << file_id << " is web";
+ if (!file_view.remote_location().is_document()) {
+ LOG(ERROR) << "Sticker " << file_id << " is encrypted";
continue;
}
result.push_back(file_id);
@@ -3408,51 +8094,64 @@ vector<FileId> StickersManager::get_attached_sticker_file_ids(const vector<int32
return result;
}
-int32 StickersManager::get_sticker_sets_hash(const vector<int64> &sticker_set_ids) const {
- vector<uint32> numbers;
+int64 StickersManager::get_sticker_sets_hash(const vector<StickerSetId> &sticker_set_ids) const {
+ vector<uint64> numbers;
numbers.reserve(sticker_set_ids.size());
for (auto sticker_set_id : sticker_set_ids) {
const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- CHECK(sticker_set->is_inited);
- numbers.push_back(static_cast<uint32>(sticker_set->hash));
+ CHECK(sticker_set->is_inited_);
+ numbers.push_back(sticker_set->hash_);
}
return get_vector_hash(numbers);
}
-int32 StickersManager::get_featured_sticker_sets_hash() const {
- vector<uint32> numbers;
- numbers.reserve(featured_sticker_set_ids_.size());
- for (auto sticker_set_id : featured_sticker_set_ids_) {
+int64 StickersManager::get_featured_sticker_sets_hash(StickerType sticker_type) const {
+ auto type = static_cast<int32>(sticker_type);
+ vector<uint64> numbers;
+ numbers.reserve(featured_sticker_set_ids_[type].size() * 2);
+ for (auto sticker_set_id : featured_sticker_set_ids_[type]) {
const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- CHECK(sticker_set->is_inited);
+ CHECK(sticker_set->is_inited_);
- uint64 pack_id = static_cast<uint64>(sticker_set_id);
- numbers.push_back(static_cast<uint32>(pack_id >> 32));
- numbers.push_back(static_cast<uint32>(pack_id & 0xFFFFFFFF));
+ numbers.push_back(sticker_set_id.get());
- if (!sticker_set->is_viewed) {
+ if (!sticker_set->is_viewed_) {
numbers.push_back(1);
}
}
return get_vector_hash(numbers);
}
+vector<int64> StickersManager::convert_sticker_set_ids(const vector<StickerSetId> &sticker_set_ids) {
+ return transform(sticker_set_ids, [](StickerSetId sticker_set_id) { return sticker_set_id.get(); });
+}
+
+vector<StickerSetId> StickersManager::convert_sticker_set_ids(const vector<int64> &sticker_set_ids) {
+ return transform(sticker_set_ids, [](int64 sticker_set_id) { return StickerSetId(sticker_set_id); });
+}
+
+td_api::object_ptr<td_api::updateInstalledStickerSets> StickersManager::get_update_installed_sticker_sets_object(
+ StickerType sticker_type) const {
+ auto type = static_cast<int32>(sticker_type);
+ return td_api::make_object<td_api::updateInstalledStickerSets>(
+ get_sticker_type_object(sticker_type), convert_sticker_set_ids(installed_sticker_set_ids_[type]));
+}
+
void StickersManager::send_update_installed_sticker_sets(bool from_database) {
- for (int is_masks = 0; is_masks < 2; is_masks++) {
- if (need_update_installed_sticker_sets_[is_masks]) {
- need_update_installed_sticker_sets_[is_masks] = false;
- if (are_installed_sticker_sets_loaded_[is_masks]) {
- installed_sticker_sets_hash_[is_masks] = get_sticker_sets_hash(installed_sticker_set_ids_[is_masks]);
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateInstalledStickerSets>(
- is_masks != 0, vector<int64>(installed_sticker_set_ids_[is_masks])));
-
- if (G()->parameters().use_file_db && !from_database) {
- LOG(INFO) << "Save installed " << (is_masks ? "mask " : "") << "sticker sets to database";
- StickerSetListLogEvent log_event(installed_sticker_set_ids_[is_masks]);
- G()->td_db()->get_sqlite_pmc()->set(is_masks ? "sss1" : "sss0", log_event_store(log_event).as_slice().str(),
+ for (int32 type = 0; type < MAX_STICKER_TYPE; type++) {
+ auto sticker_type = static_cast<StickerType>(type);
+ if (need_update_installed_sticker_sets_[type]) {
+ need_update_installed_sticker_sets_[type] = false;
+ if (are_installed_sticker_sets_loaded_[type]) {
+ installed_sticker_sets_hash_[type] = get_sticker_sets_hash(installed_sticker_set_ids_[type]);
+ send_closure(G()->td(), &Td::send_update, get_update_installed_sticker_sets_object(sticker_type));
+
+ if (G()->parameters().use_file_db && !from_database && !G()->close_flag()) {
+ LOG(INFO) << "Save installed " << sticker_type << " sticker sets to database";
+ StickerSetListLogEvent log_event(installed_sticker_set_ids_[type], false);
+ G()->td_db()->get_sqlite_pmc()->set(PSTRING() << "sss" << type, log_event_store(log_event).as_slice().str(),
Auto());
}
}
@@ -3460,23 +8159,95 @@ void StickersManager::send_update_installed_sticker_sets(bool from_database) {
}
}
-void StickersManager::send_update_featured_sticker_sets() {
- if (need_update_featured_sticker_sets_) {
- need_update_featured_sticker_sets_ = false;
- featured_sticker_sets_hash_ = get_featured_sticker_sets_hash();
+size_t StickersManager::get_max_featured_sticker_count(StickerType sticker_type) {
+ switch (sticker_type) {
+ case StickerType::Regular:
+ return 5;
+ case StickerType::Mask:
+ return 5;
+ case StickerType::CustomEmoji:
+ return 16;
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+}
+
+Slice StickersManager::get_featured_sticker_suffix(StickerType sticker_type) {
+ switch (sticker_type) {
+ case StickerType::Regular:
+ return Slice();
+ case StickerType::Mask:
+ return Slice("1");
+ case StickerType::CustomEmoji:
+ return Slice("2");
+ default:
+ UNREACHABLE();
+ return Slice();
+ }
+}
+
+td_api::object_ptr<td_api::trendingStickerSets> StickersManager::get_trending_sticker_sets_object(
+ StickerType sticker_type, const vector<StickerSetId> &sticker_set_ids) const {
+ auto type = static_cast<int32>(sticker_type);
+ auto total_count = static_cast<int32>(featured_sticker_set_ids_[type].size()) +
+ (old_featured_sticker_set_count_[type] == -1 ? 1 : old_featured_sticker_set_count_[type]);
+
+ vector<tl_object_ptr<td_api::stickerSetInfo>> result;
+ result.reserve(sticker_set_ids.size());
+ for (auto sticker_set_id : sticker_set_ids) {
+ auto sticker_set_info = get_sticker_set_info_object(sticker_set_id, get_max_featured_sticker_count(sticker_type),
+ are_featured_sticker_sets_premium_[type]);
+ if (sticker_set_info->size_ != 0) {
+ result.push_back(std::move(sticker_set_info));
+ }
+ }
+
+ auto result_size = narrow_cast<int32>(result.size());
+ CHECK(total_count >= result_size);
+ return td_api::make_object<td_api::trendingStickerSets>(total_count, std::move(result),
+ are_featured_sticker_sets_premium_[type]);
+}
+
+td_api::object_ptr<td_api::updateTrendingStickerSets> StickersManager::get_update_trending_sticker_sets_object(
+ StickerType sticker_type) const {
+ auto type = static_cast<int32>(sticker_type);
+ return td_api::make_object<td_api::updateTrendingStickerSets>(
+ get_sticker_type_object(sticker_type),
+ get_trending_sticker_sets_object(sticker_type, featured_sticker_set_ids_[type]));
+}
- send_closure(
- G()->td(), &Td::send_update,
- make_tl_object<td_api::updateTrendingStickerSets>(get_sticker_sets_object(-1, featured_sticker_set_ids_, 5)));
+void StickersManager::send_update_featured_sticker_sets(StickerType sticker_type) {
+ auto type = static_cast<int32>(sticker_type);
+ if (need_update_featured_sticker_sets_[type]) {
+ need_update_featured_sticker_sets_[type] = false;
+ featured_sticker_sets_hash_[type] = get_featured_sticker_sets_hash(sticker_type);
+
+ send_closure(G()->td(), &Td::send_update, get_update_trending_sticker_sets_object(sticker_type));
}
}
void StickersManager::reload_recent_stickers(bool is_attached, bool force) {
+ if (G()->close_flag()) {
+ return;
+ }
+
auto &next_load_time = next_recent_stickers_load_time_[is_attached];
if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) {
- LOG_IF(INFO, force) << "Reload recent stickers";
+ LOG_IF(INFO, force) << "Reload recent " << (is_attached ? "attached " : "") << "stickers";
next_load_time = -1;
- td_->create_handler<GetRecentStickersQuery>()->send(is_attached, recent_stickers_hash_[is_attached]);
+ td_->create_handler<GetRecentStickersQuery>()->send(false, is_attached, recent_stickers_hash_[is_attached]);
+ }
+}
+
+void StickersManager::repair_recent_stickers(bool is_attached, Promise<Unit> &&promise) {
+ if (td_->auth_manager_->is_bot()) {
+ return promise.set_error(Status::Error(400, "Bots have no recent stickers"));
+ }
+
+ repair_recent_stickers_queries_[is_attached].push_back(std::move(promise));
+ if (repair_recent_stickers_queries_[is_attached].size() == 1u) {
+ td_->create_handler<GetRecentStickersQuery>()->send(true, is_attached, 0);
}
}
@@ -3516,6 +8287,9 @@ void StickersManager::load_recent_stickers(bool is_attached, Promise<Unit> &&pro
}
void StickersManager::on_load_recent_stickers_from_database(bool is_attached, string value) {
+ if (G()->close_flag()) {
+ return;
+ }
if (value.empty()) {
LOG(INFO) << "Recent " << (is_attached ? "attached " : "") << "stickers aren't found in database";
reload_recent_stickers(is_attached, true);
@@ -3526,7 +8300,12 @@ void StickersManager::on_load_recent_stickers_from_database(bool is_attached, st
<< value.size() << " from database";
StickerListLogEvent log_event;
- log_event_parse(log_event, value).ensure();
+ auto status = log_event_parse(log_event, value);
+ if (status.is_error()) {
+ // can't happen unless database is broken, but has been seen in the wild
+ LOG(ERROR) << "Can't load recent stickers: " << status << ' ' << format::as_hex_dump<4>(Slice(value));
+ return reload_recent_stickers(is_attached, true);
+ }
on_load_recent_stickers_finished(is_attached, std::move(log_event.sticker_ids), true);
}
@@ -3538,24 +8317,24 @@ void StickersManager::on_load_recent_stickers_finished(bool is_attached, vector<
}
recent_sticker_ids_[is_attached] = std::move(recent_sticker_ids);
are_recent_stickers_loaded_[is_attached] = true;
- need_update_recent_stickers_[is_attached] = true;
- send_update_recent_stickers(from_database);
- auto promises = std::move(load_recent_stickers_queries_[is_attached]);
- load_recent_stickers_queries_[is_attached].clear();
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ send_update_recent_stickers(is_attached, from_database);
+ set_promises(load_recent_stickers_queries_[is_attached]);
}
-void StickersManager::on_get_recent_stickers(bool is_attached,
+void StickersManager::on_get_recent_stickers(bool is_repair, bool is_attached,
tl_object_ptr<telegram_api::messages_RecentStickers> &&stickers_ptr) {
CHECK(!td_->auth_manager_->is_bot());
- next_recent_stickers_load_time_[is_attached] = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
+ if (!is_repair) {
+ next_recent_stickers_load_time_[is_attached] = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
+ }
CHECK(stickers_ptr != nullptr);
int32 constructor_id = stickers_ptr->get_id();
if (constructor_id == telegram_api::messages_recentStickersNotModified::ID) {
- LOG(INFO) << (is_attached ? "Attached r" : "r") << "ecent stickers are not modified";
+ if (is_repair) {
+ return on_get_recent_stickers_failed(true, is_attached, Status::Error(500, "Failed to reload recent stickers"));
+ }
+ LOG(INFO) << (is_attached ? "Attached r" : "R") << "ecent stickers are not modified";
return;
}
CHECK(constructor_id == telegram_api::messages_recentStickers::ID);
@@ -3564,50 +8343,58 @@ void StickersManager::on_get_recent_stickers(bool is_attached,
vector<FileId> recent_sticker_ids;
recent_sticker_ids.reserve(stickers->stickers_.size());
for (auto &document_ptr : stickers->stickers_) {
- auto sticker_id = on_get_sticker_document(std::move(document_ptr), true).second;
+ auto sticker_id = on_get_sticker_document(std::move(document_ptr), StickerFormat::Unknown).second;
if (!sticker_id.is_valid()) {
continue;
}
recent_sticker_ids.push_back(sticker_id);
}
- on_load_recent_stickers_finished(is_attached, std::move(recent_sticker_ids));
+ if (is_repair) {
+ set_promises(repair_recent_stickers_queries_[is_attached]);
+ } else {
+ on_load_recent_stickers_finished(is_attached, std::move(recent_sticker_ids));
- LOG_IF(ERROR, recent_stickers_hash_[is_attached] != stickers->hash_) << "Stickers hash mismatch";
+ LOG_IF(ERROR, recent_stickers_hash_[is_attached] != stickers->hash_) << "Stickers hash mismatch";
+ }
}
-void StickersManager::on_get_recent_stickers_failed(bool is_attached, Status error) {
+void StickersManager::on_get_recent_stickers_failed(bool is_repair, bool is_attached, Status error) {
CHECK(error.is_error());
- next_recent_stickers_load_time_[is_attached] = Time::now_cached() + Random::fast(5, 10);
- auto promises = std::move(load_recent_stickers_queries_[is_attached]);
- load_recent_stickers_queries_[is_attached].clear();
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+ if (!is_repair) {
+ next_recent_stickers_load_time_[is_attached] = Time::now_cached() + Random::fast(5, 10);
}
+ fail_promises(is_repair ? repair_recent_stickers_queries_[is_attached] : load_recent_stickers_queries_[is_attached],
+ std::move(error));
}
-int32 StickersManager::get_recent_stickers_hash(const vector<FileId> &sticker_ids) const {
- vector<uint32> numbers;
- numbers.reserve(sticker_ids.size() * 2);
+int64 StickersManager::get_recent_stickers_hash(const vector<FileId> &sticker_ids) const {
+ vector<uint64> numbers;
+ numbers.reserve(sticker_ids.size());
for (auto sticker_id : sticker_ids) {
auto sticker = get_sticker(sticker_id);
CHECK(sticker != nullptr);
auto file_view = td_->file_manager_->get_file_view(sticker_id);
CHECK(file_view.has_remote_location());
- CHECK(!file_view.remote_location().is_encrypted());
- CHECK(!file_view.remote_location().is_web());
- auto id = static_cast<uint64>(file_view.remote_location().get_id());
- numbers.push_back(static_cast<uint32>(id >> 32));
- numbers.push_back(static_cast<uint32>(id & 0xFFFFFFFF));
+ if (!file_view.remote_location().is_document()) {
+ LOG(ERROR) << "Recent sticker remote location is not document: " << file_view.remote_location();
+ continue;
+ }
+ numbers.push_back(file_view.remote_location().get_id());
}
return get_vector_hash(numbers);
}
+FileSourceId StickersManager::get_recent_stickers_file_source_id(int is_attached) {
+ if (!recent_stickers_file_source_id_[is_attached].is_valid()) {
+ recent_stickers_file_source_id_[is_attached] =
+ td_->file_reference_manager_->create_recent_stickers_file_source(is_attached != 0);
+ }
+ return recent_stickers_file_source_id_[is_attached];
+}
+
void StickersManager::add_recent_sticker(bool is_attached, const tl_object_ptr<td_api::InputFile> &input_file,
Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(7, "Method is not available for bots"));
- }
if (!are_recent_stickers_loaded_[is_attached]) {
load_recent_stickers(is_attached, std::move(promise));
return;
@@ -3615,81 +8402,85 @@ void StickersManager::add_recent_sticker(bool is_attached, const tl_object_ptr<t
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false);
if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(7, r_file_id.error().message())); // TODO do not drop error code
+ return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code
}
- add_recent_sticker_inner(is_attached, r_file_id.ok(), std::move(promise));
+ add_recent_sticker_impl(is_attached, r_file_id.ok(), true, std::move(promise));
}
-void StickersManager::add_recent_sticker_inner(bool is_attached, FileId sticker_id, Promise<Unit> &&promise) {
- if (add_recent_sticker_impl(is_attached, sticker_id, promise)) {
- // TODO invokeAfter and log event
- auto file_view = td_->file_manager_->get_file_view(sticker_id);
- td_->create_handler<SaveRecentStickerQuery>(std::move(promise))
- ->send(is_attached, file_view.remote_location().as_input_document(), false);
- }
+void StickersManager::send_save_recent_sticker_query(bool is_attached, FileId sticker_id, bool unsave,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ // TODO invokeAfter and log event
+ auto file_view = td_->file_manager_->get_file_view(sticker_id);
+ CHECK(file_view.has_remote_location());
+ CHECK(file_view.remote_location().is_document());
+ CHECK(!file_view.remote_location().is_web());
+ td_->create_handler<SaveRecentStickerQuery>(std::move(promise))
+ ->send(is_attached, sticker_id, file_view.remote_location().as_input_document(), unsave);
}
void StickersManager::add_recent_sticker_by_id(bool is_attached, FileId sticker_id) {
// TODO log event
- Promise<Unit> promise;
- add_recent_sticker_impl(is_attached, sticker_id, promise);
+ add_recent_sticker_impl(is_attached, sticker_id, false, Auto());
}
-bool StickersManager::add_recent_sticker_impl(bool is_attached, FileId sticker_id, Promise<Unit> &promise) {
+void StickersManager::add_recent_sticker_impl(bool is_attached, FileId sticker_id, bool add_on_server,
+ Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
+ LOG(INFO) << "Add recent " << (is_attached ? "attached " : "") << "sticker " << sticker_id;
if (!are_recent_stickers_loaded_[is_attached]) {
- load_recent_stickers(is_attached, PromiseCreator::lambda([is_attached, sticker_id,
+ load_recent_stickers(is_attached, PromiseCreator::lambda([is_attached, sticker_id, add_on_server,
promise = std::move(promise)](Result<> result) mutable {
if (result.is_ok()) {
- send_closure(G()->stickers_manager(), &StickersManager::add_recent_sticker_inner,
- is_attached, sticker_id, std::move(promise));
+ send_closure(G()->stickers_manager(), &StickersManager::add_recent_sticker_impl,
+ is_attached, sticker_id, add_on_server, std::move(promise));
} else {
promise.set_error(result.move_as_error());
}
}));
- return false;
+ return;
}
+ auto is_equal = [sticker_id](FileId file_id) {
+ return file_id == sticker_id || (file_id.get_remote() == sticker_id.get_remote() && sticker_id.get_remote() != 0);
+ };
+
vector<FileId> &sticker_ids = recent_sticker_ids_[is_attached];
- if (!sticker_ids.empty() && sticker_ids[0] == sticker_id) {
+ if (!sticker_ids.empty() && is_equal(sticker_ids[0])) {
if (sticker_ids[0].get_remote() == 0 && sticker_id.get_remote() != 0) {
sticker_ids[0] = sticker_id;
save_recent_stickers_to_database(is_attached);
}
- promise.set_value(Unit());
- return false;
+ return promise.set_value(Unit());
}
auto sticker = get_sticker(sticker_id);
if (sticker == nullptr) {
- promise.set_error(Status::Error(7, "Sticker not found"));
- return false;
+ return promise.set_error(Status::Error(400, "Sticker not found"));
}
- if (sticker->set_id == 0) {
- promise.set_error(Status::Error(7, "Stickers without sticker set can't be added to recent"));
- return false;
+ if (!sticker->set_id_.is_valid()) {
+ return promise.set_error(Status::Error(400, "Stickers without sticker set can't be added to recent"));
+ }
+ if (sticker->type_ == StickerType::CustomEmoji) {
+ return promise.set_error(Status::Error(400, "Custom emoji stickers can't be added to recent"));
}
auto file_view = td_->file_manager_->get_file_view(sticker_id);
if (!file_view.has_remote_location()) {
- promise.set_error(Status::Error(7, "Can save only sent stickers"));
- return false;
- }
- if (file_view.remote_location().is_encrypted()) {
- promise.set_error(Status::Error(7, "Can't save encrypted stickers"));
- return false;
+ return promise.set_error(Status::Error(400, "Can save only sent stickers"));
}
if (file_view.remote_location().is_web()) {
- promise.set_error(Status::Error(7, "Can't save web stickers"));
- return false;
+ return promise.set_error(Status::Error(400, "Can't save web stickers"));
+ }
+ if (!file_view.remote_location().is_document()) {
+ return promise.set_error(Status::Error(400, "Can't save encrypted stickers"));
}
- need_update_recent_stickers_[is_attached] = true;
-
- auto it = std::find(sticker_ids.begin(), sticker_ids.end(), sticker_id);
+ auto it = std::find_if(sticker_ids.begin(), sticker_ids.end(), is_equal);
if (it == sticker_ids.end()) {
if (static_cast<int32>(sticker_ids.size()) == recent_stickers_limit_) {
sticker_ids.back() = sticker_id;
@@ -3703,15 +8494,14 @@ bool StickersManager::add_recent_sticker_impl(bool is_attached, FileId sticker_i
sticker_ids[0] = sticker_id;
}
- send_update_recent_stickers();
- return true;
+ send_update_recent_stickers(is_attached);
+ if (add_on_server) {
+ send_save_recent_sticker_query(is_attached, sticker_id, false, std::move(promise));
+ }
}
void StickersManager::remove_recent_sticker(bool is_attached, const tl_object_ptr<td_api::InputFile> &input_file,
Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(7, "Method is not available for bots"));
- }
if (!are_recent_stickers_loaded_[is_attached]) {
load_recent_stickers(is_attached, std::move(promise));
return;
@@ -3719,39 +8509,26 @@ void StickersManager::remove_recent_sticker(bool is_attached, const tl_object_pt
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false);
if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(7, r_file_id.error().message())); // TODO do not drop error code
+ return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code
}
vector<FileId> &sticker_ids = recent_sticker_ids_[is_attached];
FileId file_id = r_file_id.ok();
- auto it = std::find(sticker_ids.begin(), sticker_ids.end(), file_id);
- if (it == sticker_ids.end()) {
+ if (!td::remove(sticker_ids, file_id)) {
return promise.set_value(Unit());
}
auto sticker = get_sticker(file_id);
if (sticker == nullptr) {
- return promise.set_error(Status::Error(7, "Sticker not found"));
+ return promise.set_error(Status::Error(400, "Sticker not found"));
}
- // TODO invokeAfter
- auto file_view = td_->file_manager_->get_file_view(file_id);
- CHECK(file_view.has_remote_location());
- CHECK(!file_view.remote_location().is_encrypted());
- CHECK(!file_view.remote_location().is_web());
- td_->create_handler<SaveRecentStickerQuery>(std::move(promise))
- ->send(is_attached, file_view.remote_location().as_input_document(), true);
+ send_save_recent_sticker_query(is_attached, file_id, true, std::move(promise));
- sticker_ids.erase(it);
-
- need_update_recent_stickers_[is_attached] = true;
- send_update_recent_stickers();
+ send_update_recent_stickers(is_attached);
}
void StickersManager::clear_recent_stickers(bool is_attached, Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(7, "Method is not available for bots"));
- }
if (!are_recent_stickers_loaded_[is_attached]) {
load_recent_stickers(is_attached, std::move(promise));
return;
@@ -3767,34 +8544,41 @@ void StickersManager::clear_recent_stickers(bool is_attached, Promise<Unit> &&pr
sticker_ids.clear();
- need_update_recent_stickers_[is_attached] = true;
- send_update_recent_stickers();
+ send_update_recent_stickers(is_attached);
}
-void StickersManager::send_update_recent_stickers(bool from_database) {
- for (int is_attached = 0; is_attached < 2; is_attached++) {
- if (need_update_recent_stickers_[is_attached]) {
- need_update_recent_stickers_[is_attached] = false;
- if (are_recent_stickers_loaded_[is_attached]) {
- recent_stickers_hash_[is_attached] = get_recent_stickers_hash(recent_sticker_ids_[is_attached]);
- vector<int32> stickers;
- stickers.reserve(recent_sticker_ids_[is_attached].size());
- for (auto sticker_id : recent_sticker_ids_[is_attached]) {
- stickers.push_back(sticker_id.get());
- }
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateRecentStickers>(is_attached != 0, std::move(stickers)));
+td_api::object_ptr<td_api::updateRecentStickers> StickersManager::get_update_recent_stickers_object(
+ int is_attached) const {
+ return td_api::make_object<td_api::updateRecentStickers>(
+ is_attached != 0, td_->file_manager_->get_file_ids_object(recent_sticker_ids_[is_attached]));
+}
- if (!from_database) {
- save_recent_stickers_to_database(is_attached != 0);
- }
- }
- }
+void StickersManager::send_update_recent_stickers(bool is_attached, bool from_database) {
+ if (!are_recent_stickers_loaded_[is_attached]) {
+ return;
+ }
+
+ vector<FileId> new_recent_sticker_file_ids;
+ for (auto &sticker_id : recent_sticker_ids_[is_attached]) {
+ append(new_recent_sticker_file_ids, get_sticker_file_ids(sticker_id));
+ }
+ std::sort(new_recent_sticker_file_ids.begin(), new_recent_sticker_file_ids.end());
+ if (new_recent_sticker_file_ids != recent_sticker_file_ids_[is_attached]) {
+ td_->file_manager_->change_files_source(get_recent_stickers_file_source_id(is_attached),
+ recent_sticker_file_ids_[is_attached], new_recent_sticker_file_ids);
+ recent_sticker_file_ids_[is_attached] = std::move(new_recent_sticker_file_ids);
+ }
+
+ recent_stickers_hash_[is_attached] = get_recent_stickers_hash(recent_sticker_ids_[is_attached]);
+ send_closure(G()->td(), &Td::send_update, get_update_recent_stickers_object(is_attached));
+
+ if (!from_database) {
+ save_recent_stickers_to_database(is_attached != 0);
}
}
void StickersManager::save_recent_stickers_to_database(bool is_attached) {
- if (G()->parameters().use_file_db) {
+ if (G()->parameters().use_file_db && !G()->close_flag()) {
LOG(INFO) << "Save recent " << (is_attached ? "attached " : "") << "stickers to database";
StickerListLogEvent log_event(recent_sticker_ids_[is_attached]);
G()->td_db()->get_sqlite_pmc()->set(is_attached ? "ssr1" : "ssr0", log_event_store(log_event).as_slice().str(),
@@ -3802,7 +8586,14 @@ void StickersManager::save_recent_stickers_to_database(bool is_attached) {
}
}
-void StickersManager::on_update_recent_stickers_limit(int32 recent_stickers_limit) {
+void StickersManager::on_update_animated_emoji_zoom() {
+ animated_emoji_zoom_ =
+ static_cast<double>(td_->option_manager_->get_option_integer("animated_emoji_zoom", 625000000)) * 1e-9;
+}
+
+void StickersManager::on_update_recent_stickers_limit() {
+ auto recent_stickers_limit =
+ narrow_cast<int32>(td_->option_manager_->get_option_integer("recent_stickers_limit", 200));
if (recent_stickers_limit != recent_stickers_limit_) {
if (recent_stickers_limit > 0) {
LOG(INFO) << "Update recent stickers limit to " << recent_stickers_limit;
@@ -3810,7 +8601,7 @@ void StickersManager::on_update_recent_stickers_limit(int32 recent_stickers_limi
for (int is_attached = 0; is_attached < 2; is_attached++) {
if (static_cast<int32>(recent_sticker_ids_[is_attached].size()) > recent_stickers_limit) {
recent_sticker_ids_[is_attached].resize(recent_stickers_limit);
- send_update_recent_stickers();
+ send_update_recent_stickers(is_attached != 0);
}
}
} else {
@@ -3819,7 +8610,9 @@ void StickersManager::on_update_recent_stickers_limit(int32 recent_stickers_limi
}
}
-void StickersManager::on_update_favorite_stickers_limit(int32 favorite_stickers_limit) {
+void StickersManager::on_update_favorite_stickers_limit() {
+ auto favorite_stickers_limit =
+ narrow_cast<int32>(td_->option_manager_->get_option_integer("favorite_stickers_limit", 5));
if (favorite_stickers_limit != favorite_stickers_limit_) {
if (favorite_stickers_limit > 0) {
LOG(INFO) << "Update favorite stickers limit to " << favorite_stickers_limit;
@@ -3835,11 +8628,26 @@ void StickersManager::on_update_favorite_stickers_limit(int32 favorite_stickers_
}
void StickersManager::reload_favorite_stickers(bool force) {
- if (!td_->auth_manager_->is_bot() && next_favorite_stickers_load_time_ >= 0 &&
- (next_favorite_stickers_load_time_ < Time::now() || force)) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto &next_load_time = next_favorite_stickers_load_time_;
+ if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) {
LOG_IF(INFO, force) << "Reload favorite stickers";
- next_favorite_stickers_load_time_ = -1;
- td_->create_handler<GetFavedStickersQuery>()->send(get_favorite_stickers_hash());
+ next_load_time = -1;
+ td_->create_handler<GetFavedStickersQuery>()->send(false, get_favorite_stickers_hash());
+ }
+}
+
+void StickersManager::repair_favorite_stickers(Promise<Unit> &&promise) {
+ if (td_->auth_manager_->is_bot()) {
+ return promise.set_error(Status::Error(400, "Bots have no favorite stickers"));
+ }
+
+ repair_favorite_stickers_queries_.push_back(std::move(promise));
+ if (repair_favorite_stickers_queries_.size() == 1u) {
+ td_->create_handler<GetFavedStickersQuery>()->send(true, 0);
}
}
@@ -3879,6 +8687,9 @@ void StickersManager::load_favorite_stickers(Promise<Unit> &&promise) {
}
void StickersManager::on_load_favorite_stickers_from_database(const string &value) {
+ if (G()->close_flag()) {
+ return;
+ }
if (value.empty()) {
LOG(INFO) << "Favorite stickers aren't found in database";
reload_favorite_stickers(true);
@@ -3888,7 +8699,12 @@ void StickersManager::on_load_favorite_stickers_from_database(const string &valu
LOG(INFO) << "Successfully loaded favorite stickers list of size " << value.size() << " from database";
StickerListLogEvent log_event;
- log_event_parse(log_event, value).ensure();
+ auto status = log_event_parse(log_event, value);
+ if (status.is_error()) {
+ // can't happen unless database is broken, but has been seen in the wild
+ LOG(ERROR) << "Can't load favorite stickers: " << status << ' ' << format::as_hex_dump<4>(Slice(value));
+ return reload_favorite_stickers(true);
+ }
on_load_favorite_stickers_finished(std::move(log_event.sticker_ids), true);
}
@@ -3900,21 +8716,22 @@ void StickersManager::on_load_favorite_stickers_finished(vector<FileId> &&favori
favorite_sticker_ids_ = std::move(favorite_sticker_ids);
are_favorite_stickers_loaded_ = true;
send_update_favorite_stickers(from_database);
- auto promises = std::move(load_favorite_stickers_queries_);
- load_favorite_stickers_queries_.clear();
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(load_favorite_stickers_queries_);
}
void StickersManager::on_get_favorite_stickers(
- tl_object_ptr<telegram_api::messages_FavedStickers> &&favorite_stickers_ptr) {
+ bool is_repair, tl_object_ptr<telegram_api::messages_FavedStickers> &&favorite_stickers_ptr) {
CHECK(!td_->auth_manager_->is_bot());
- next_favorite_stickers_load_time_ = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
+ if (!is_repair) {
+ next_favorite_stickers_load_time_ = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
+ }
CHECK(favorite_stickers_ptr != nullptr);
int32 constructor_id = favorite_stickers_ptr->get_id();
if (constructor_id == telegram_api::messages_favedStickersNotModified::ID) {
+ if (is_repair) {
+ return on_get_favorite_stickers_failed(true, Status::Error(500, "Failed to reload favorite stickers"));
+ }
LOG(INFO) << "Favorite stickers are not modified";
return;
}
@@ -3926,7 +8743,7 @@ void StickersManager::on_get_favorite_stickers(
vector<FileId> favorite_sticker_ids;
favorite_sticker_ids.reserve(favorite_stickers->stickers_.size());
for (auto &document_ptr : favorite_stickers->stickers_) {
- auto sticker_id = on_get_sticker_document(std::move(document_ptr), true).second;
+ auto sticker_id = on_get_sticker_document(std::move(document_ptr), StickerFormat::Unknown).second;
if (!sticker_id.is_valid()) {
continue;
}
@@ -3934,30 +8751,43 @@ void StickersManager::on_get_favorite_stickers(
favorite_sticker_ids.push_back(sticker_id);
}
- on_load_favorite_stickers_finished(std::move(favorite_sticker_ids));
+ if (is_repair) {
+ set_promises(repair_favorite_stickers_queries_);
+ } else {
+ on_load_favorite_stickers_finished(std::move(favorite_sticker_ids));
- LOG_IF(ERROR, get_favorite_stickers_hash() != favorite_stickers->hash_) << "Favorite stickers hash mismatch";
+ LOG_IF(ERROR, get_favorite_stickers_hash() != favorite_stickers->hash_) << "Favorite stickers hash mismatch";
+ }
}
-void StickersManager::on_get_favorite_stickers_failed(Status error) {
+void StickersManager::on_get_favorite_stickers_failed(bool is_repair, Status error) {
CHECK(error.is_error());
- next_favorite_stickers_load_time_ = Time::now_cached() + Random::fast(5, 10);
- auto promises = std::move(load_favorite_stickers_queries_);
- load_favorite_stickers_queries_.clear();
- for (auto &promise : promises) {
- promise.set_error(error.clone());
+ if (!is_repair) {
+ next_favorite_stickers_load_time_ = Time::now_cached() + Random::fast(5, 10);
}
+ fail_promises(is_repair ? repair_favorite_stickers_queries_ : load_favorite_stickers_queries_, std::move(error));
}
-int32 StickersManager::get_favorite_stickers_hash() const {
+int64 StickersManager::get_favorite_stickers_hash() const {
return get_recent_stickers_hash(favorite_sticker_ids_);
}
+FileSourceId StickersManager::get_app_config_file_source_id() {
+ if (!app_config_file_source_id_.is_valid()) {
+ app_config_file_source_id_ = td_->file_reference_manager_->create_app_config_file_source();
+ }
+ return app_config_file_source_id_;
+}
+
+FileSourceId StickersManager::get_favorite_stickers_file_source_id() {
+ if (!favorite_stickers_file_source_id_.is_valid()) {
+ favorite_stickers_file_source_id_ = td_->file_reference_manager_->create_favorite_stickers_file_source();
+ }
+ return favorite_stickers_file_source_id_;
+}
+
void StickersManager::add_favorite_sticker(const tl_object_ptr<td_api::InputFile> &input_file,
Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(7, "Method is not available for bots"));
- }
if (!are_favorite_stickers_loaded_) {
load_favorite_stickers(std::move(promise));
return;
@@ -3965,77 +8795,81 @@ void StickersManager::add_favorite_sticker(const tl_object_ptr<td_api::InputFile
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false);
if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(7, r_file_id.error().message())); // TODO do not drop error code
+ return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code
}
- add_favorite_sticker_inner(r_file_id.ok(), std::move(promise));
+ add_favorite_sticker_impl(r_file_id.ok(), true, std::move(promise));
}
-void StickersManager::add_favorite_sticker_inner(FileId sticker_id, Promise<Unit> &&promise) {
- if (add_favorite_sticker_impl(sticker_id, promise)) {
- // TODO invokeAfter and log event
- auto file_view = td_->file_manager_->get_file_view(sticker_id);
- td_->create_handler<FaveStickerQuery>(std::move(promise))
- ->send(file_view.remote_location().as_input_document(), false);
- }
+void StickersManager::send_fave_sticker_query(FileId sticker_id, bool unsave, Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ // TODO invokeAfter and log event
+ auto file_view = td_->file_manager_->get_file_view(sticker_id);
+ CHECK(file_view.has_remote_location());
+ CHECK(file_view.remote_location().is_document());
+ CHECK(!file_view.remote_location().is_web());
+ td_->create_handler<FaveStickerQuery>(std::move(promise))
+ ->send(sticker_id, file_view.remote_location().as_input_document(), unsave);
}
void StickersManager::add_favorite_sticker_by_id(FileId sticker_id) {
// TODO log event
- Promise<Unit> promise;
- add_favorite_sticker_impl(sticker_id, promise);
+ add_favorite_sticker_impl(sticker_id, false, Auto());
}
-bool StickersManager::add_favorite_sticker_impl(FileId sticker_id, Promise<Unit> &promise) {
+void StickersManager::add_favorite_sticker_impl(FileId sticker_id, bool add_on_server, Promise<Unit> &&promise) {
CHECK(!td_->auth_manager_->is_bot());
if (!are_favorite_stickers_loaded_) {
- load_favorite_stickers(PromiseCreator::lambda([sticker_id, promise = std::move(promise)](Result<> result) mutable {
- if (result.is_ok()) {
- send_closure(G()->stickers_manager(), &StickersManager::add_favorite_sticker_inner, sticker_id,
- std::move(promise));
- } else {
- promise.set_error(result.move_as_error());
- }
- }));
- return false;
+ load_favorite_stickers(
+ PromiseCreator::lambda([sticker_id, add_on_server, promise = std::move(promise)](Result<> result) mutable {
+ if (result.is_ok()) {
+ send_closure(G()->stickers_manager(), &StickersManager::add_favorite_sticker_impl, sticker_id,
+ add_on_server, std::move(promise));
+ } else {
+ promise.set_error(result.move_as_error());
+ }
+ }));
+ return;
}
- if (!favorite_sticker_ids_.empty() && favorite_sticker_ids_[0] == sticker_id) {
+ auto is_equal = [sticker_id](FileId file_id) {
+ return file_id == sticker_id || (file_id.get_remote() == sticker_id.get_remote() && sticker_id.get_remote() != 0);
+ };
+
+ if (!favorite_sticker_ids_.empty() && is_equal(favorite_sticker_ids_[0])) {
if (favorite_sticker_ids_[0].get_remote() == 0 && sticker_id.get_remote() != 0) {
favorite_sticker_ids_[0] = sticker_id;
save_favorite_stickers_to_database();
}
- promise.set_value(Unit());
- return false;
+ return promise.set_value(Unit());
}
auto sticker = get_sticker(sticker_id);
if (sticker == nullptr) {
- promise.set_error(Status::Error(7, "Sticker not found"));
- return false;
+ return promise.set_error(Status::Error(400, "Sticker not found"));
}
- if (sticker->set_id == 0) {
- promise.set_error(Status::Error(7, "Stickers without sticker set can't be favorite"));
- return false;
+ if (!sticker->set_id_.is_valid()) {
+ return promise.set_error(Status::Error(400, "Stickers without sticker set can't be added to favorite"));
+ }
+ if (sticker->type_ == StickerType::CustomEmoji) {
+ return promise.set_error(Status::Error(400, "Custom emoji stickers can't be added to favorite"));
}
auto file_view = td_->file_manager_->get_file_view(sticker_id);
if (!file_view.has_remote_location()) {
- promise.set_error(Status::Error(7, "Can fave only sent stickers"));
- return false;
- }
- if (file_view.remote_location().is_encrypted()) {
- promise.set_error(Status::Error(7, "Can't fave encrypted stickers"));
- return false;
+ return promise.set_error(Status::Error(400, "Can add to favorites only sent stickers"));
}
if (file_view.remote_location().is_web()) {
- promise.set_error(Status::Error(7, "Can't fave web stickers"));
- return false;
+ return promise.set_error(Status::Error(400, "Can't add to favorites web stickers"));
+ }
+ if (!file_view.remote_location().is_document()) {
+ return promise.set_error(Status::Error(400, "Can't add to favorites encrypted stickers"));
}
- auto it = std::find(favorite_sticker_ids_.begin(), favorite_sticker_ids_.end(), sticker_id);
+ auto it = std::find_if(favorite_sticker_ids_.begin(), favorite_sticker_ids_.end(), is_equal);
if (it == favorite_sticker_ids_.end()) {
if (static_cast<int32>(favorite_sticker_ids_.size()) == favorite_stickers_limit_) {
favorite_sticker_ids_.back() = sticker_id;
@@ -4050,14 +8884,13 @@ bool StickersManager::add_favorite_sticker_impl(FileId sticker_id, Promise<Unit>
}
send_update_favorite_stickers();
- return true;
+ if (add_on_server) {
+ send_fave_sticker_query(sticker_id, false, std::move(promise));
+ }
}
void StickersManager::remove_favorite_sticker(const tl_object_ptr<td_api::InputFile> &input_file,
Promise<Unit> &&promise) {
- if (td_->auth_manager_->is_bot()) {
- return promise.set_error(Status::Error(7, "Method is not available for bots"));
- }
if (!are_favorite_stickers_loaded_) {
load_favorite_stickers(std::move(promise));
return;
@@ -4065,41 +8898,46 @@ void StickersManager::remove_favorite_sticker(const tl_object_ptr<td_api::InputF
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false);
if (r_file_id.is_error()) {
- return promise.set_error(Status::Error(7, r_file_id.error().message())); // TODO do not drop error code
+ return promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code
}
FileId file_id = r_file_id.ok();
- auto it = std::find(favorite_sticker_ids_.begin(), favorite_sticker_ids_.end(), file_id);
- if (it == favorite_sticker_ids_.end()) {
+ auto is_equal = [sticker_id = file_id](FileId file_id) {
+ return file_id == sticker_id || (file_id.get_remote() == sticker_id.get_remote() && sticker_id.get_remote() != 0);
+ };
+ if (!td::remove_if(favorite_sticker_ids_, is_equal)) {
return promise.set_value(Unit());
}
auto sticker = get_sticker(file_id);
if (sticker == nullptr) {
- return promise.set_error(Status::Error(7, "Sticker not found"));
+ return promise.set_error(Status::Error(400, "Sticker not found"));
}
- // TODO invokeAfter
- auto file_view = td_->file_manager_->get_file_view(file_id);
- CHECK(file_view.has_remote_location());
- CHECK(!file_view.remote_location().is_encrypted());
- CHECK(!file_view.remote_location().is_web());
- td_->create_handler<FaveStickerQuery>(std::move(promise))
- ->send(file_view.remote_location().as_input_document(), true);
-
- favorite_sticker_ids_.erase(it);
+ send_fave_sticker_query(file_id, true, std::move(promise));
send_update_favorite_stickers();
}
+td_api::object_ptr<td_api::updateFavoriteStickers> StickersManager::get_update_favorite_stickers_object() const {
+ return td_api::make_object<td_api::updateFavoriteStickers>(
+ td_->file_manager_->get_file_ids_object(favorite_sticker_ids_));
+}
+
void StickersManager::send_update_favorite_stickers(bool from_database) {
if (are_favorite_stickers_loaded_) {
- vector<int32> stickers;
- stickers.reserve(favorite_sticker_ids_.size());
- for (auto sticker_id : favorite_sticker_ids_) {
- stickers.push_back(sticker_id.get());
+ vector<FileId> new_favorite_sticker_file_ids;
+ for (auto &sticker_id : favorite_sticker_ids_) {
+ append(new_favorite_sticker_file_ids, get_sticker_file_ids(sticker_id));
+ }
+ std::sort(new_favorite_sticker_file_ids.begin(), new_favorite_sticker_file_ids.end());
+ if (new_favorite_sticker_file_ids != favorite_sticker_file_ids_) {
+ td_->file_manager_->change_files_source(get_favorite_stickers_file_source_id(), favorite_sticker_file_ids_,
+ new_favorite_sticker_file_ids);
+ favorite_sticker_file_ids_ = std::move(new_favorite_sticker_file_ids);
}
- send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateFavoriteStickers>(std::move(stickers)));
+
+ send_closure(G()->td(), &Td::send_update, get_update_favorite_stickers_object());
if (!from_database) {
save_favorite_stickers_to_database();
@@ -4108,7 +8946,7 @@ void StickersManager::send_update_favorite_stickers(bool from_database) {
}
void StickersManager::save_favorite_stickers_to_database() {
- if (G()->parameters().use_file_db) {
+ if (G()->parameters().use_file_db && !G()->close_flag()) {
LOG(INFO) << "Save favorite stickers to database";
StickerListLogEvent log_event(favorite_sticker_ids_);
G()->td_db()->get_sqlite_pmc()->set("ssfav", log_event_store(log_event).as_slice().str(), Auto());
@@ -4119,7 +8957,7 @@ vector<string> StickersManager::get_sticker_emojis(const tl_object_ptr<td_api::I
Promise<Unit> &&promise) {
auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Sticker, input_file, DialogId(), false, false);
if (r_file_id.is_error()) {
- promise.set_error(Status::Error(7, r_file_id.error().message())); // TODO do not drop error code
+ promise.set_error(Status::Error(400, r_file_id.error().message())); // TODO do not drop error code
return {};
}
@@ -4130,7 +8968,7 @@ vector<string> StickersManager::get_sticker_emojis(const tl_object_ptr<td_api::I
promise.set_value(Unit());
return {};
}
- if (sticker->set_id == 0) {
+ if (!sticker->set_id_.is_valid()) {
promise.set_value(Unit());
return {};
}
@@ -4140,7 +8978,7 @@ vector<string> StickersManager::get_sticker_emojis(const tl_object_ptr<td_api::I
promise.set_value(Unit());
return {};
}
- if (file_view.remote_location().is_encrypted()) {
+ if (!file_view.remote_location().is_document()) {
promise.set_value(Unit());
return {};
}
@@ -4149,7 +8987,7 @@ vector<string> StickersManager::get_sticker_emojis(const tl_object_ptr<td_api::I
return {};
}
- const StickerSet *sticker_set = get_sticker_set(sticker->set_id);
+ const StickerSet *sticker_set = get_sticker_set(sticker->set_id_);
if (update_sticker_set_cache(sticker_set, promise)) {
return {};
}
@@ -4163,27 +9001,528 @@ vector<string> StickersManager::get_sticker_emojis(const tl_object_ptr<td_api::I
return it->second;
}
-string StickersManager::remove_emoji_modifiers(string emoji) {
- static Slice modifiers[] = {u8"\uFE0E" /* variation selector-15 */,
- u8"\uFE0F" /* variation selector-16 */,
- u8"\u200D\u2640" /* zero width joiner + female sign */,
- u8"\u200D\u2642" /* zero width joiner + male sign */,
- u8"\U0001F3FB" /* emoji modifier fitzpatrick type-1-2 */,
- u8"\U0001F3FC" /* emoji modifier fitzpatrick type-3 */,
- u8"\U0001F3FD" /* emoji modifier fitzpatrick type-4 */,
- u8"\U0001F3FE" /* emoji modifier fitzpatrick type-5 */,
- u8"\U0001F3FF" /* emoji modifier fitzpatrick type-6 */};
- bool found = true;
- while (found) {
- found = false;
- for (auto &modifier : modifiers) {
- if (ends_with(emoji, modifier) && emoji.size() > modifier.size()) {
- emoji.resize(emoji.size() - modifier.size());
- found = true;
+string StickersManager::get_emoji_language_code_version_database_key(const string &language_code) {
+ return PSTRING() << "emojiv$" << language_code;
+}
+
+int32 StickersManager::get_emoji_language_code_version(const string &language_code) {
+ auto it = emoji_language_code_versions_.find(language_code);
+ if (it != emoji_language_code_versions_.end()) {
+ return it->second;
+ }
+ if (language_code.empty()) {
+ return 0;
+ }
+ auto &result = emoji_language_code_versions_[language_code];
+ result = to_integer<int32>(
+ G()->td_db()->get_sqlite_sync_pmc()->get(get_emoji_language_code_version_database_key(language_code)));
+ return result;
+}
+
+string StickersManager::get_emoji_language_code_last_difference_time_database_key(const string &language_code) {
+ return PSTRING() << "emojid$" << language_code;
+}
+
+double StickersManager::get_emoji_language_code_last_difference_time(const string &language_code) {
+ auto it = emoji_language_code_last_difference_times_.find(language_code);
+ if (it != emoji_language_code_last_difference_times_.end()) {
+ return it->second;
+ }
+ if (language_code.empty()) {
+ return Time::now_cached() - G()->unix_time();
+ }
+ auto &result = emoji_language_code_last_difference_times_[language_code];
+ auto old_unix_time = to_integer<int32>(G()->td_db()->get_sqlite_sync_pmc()->get(
+ get_emoji_language_code_last_difference_time_database_key(language_code)));
+ int32 passed_time = max(static_cast<int32>(0), G()->unix_time() - old_unix_time);
+ result = Time::now_cached() - passed_time;
+ return result;
+}
+
+string StickersManager::get_language_emojis_database_key(const string &language_code, const string &text) {
+ return PSTRING() << "emoji$" << language_code << '$' << text;
+}
+
+vector<string> StickersManager::search_language_emojis(const string &language_code, const string &text,
+ bool exact_match) {
+ LOG(INFO) << "Search for \"" << text << "\" in language " << language_code;
+ auto key = get_language_emojis_database_key(language_code, text);
+ if (exact_match) {
+ string emojis = G()->td_db()->get_sqlite_sync_pmc()->get(key);
+ return full_split(emojis, '$');
+ } else {
+ vector<string> result;
+ G()->td_db()->get_sqlite_sync_pmc()->get_by_prefix(key, [&result](Slice key, Slice value) {
+ for (auto &emoji : full_split(value, '$')) {
+ result.push_back(emoji.str());
+ }
+ return true;
+ });
+ return result;
+ }
+}
+
+string StickersManager::get_emoji_language_codes_database_key(const vector<string> &language_codes) {
+ return PSTRING() << "emojilc$" << implode(language_codes, '$');
+}
+
+void StickersManager::load_language_codes(vector<string> language_codes, string key, Promise<Unit> &&promise) {
+ auto &promises = load_language_codes_queries_[key];
+ promises.push_back(std::move(promise));
+ if (promises.size() != 1) {
+ // query has already been sent, just wait for the result
+ return;
+ }
+
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), key = std::move(key)](Result<vector<string>> &&result) {
+ send_closure(actor_id, &StickersManager::on_get_language_codes, key, std::move(result));
+ });
+ td_->create_handler<GetEmojiKeywordsLanguageQuery>(std::move(query_promise))->send(std::move(language_codes));
+}
+
+void StickersManager::on_get_language_codes(const string &key, Result<vector<string>> &&result) {
+ auto queries_it = load_language_codes_queries_.find(key);
+ CHECK(queries_it != load_language_codes_queries_.end());
+ CHECK(!queries_it->second.empty());
+ auto promises = std::move(queries_it->second);
+ load_language_codes_queries_.erase(queries_it);
+
+ if (result.is_error()) {
+ if (!G()->is_expected_error(result.error())) {
+ LOG(ERROR) << "Receive " << result.error() << " from GetEmojiKeywordsLanguageQuery";
+ }
+ fail_promises(promises, result.move_as_error());
+ return;
+ }
+
+ auto language_codes = result.move_as_ok();
+ LOG(INFO) << "Receive language codes " << language_codes << " for emojis search with key " << key;
+ td::remove_if(language_codes, [](const string &language_code) {
+ if (language_code.empty() || language_code.find('$') != string::npos) {
+ LOG(ERROR) << "Receive language_code \"" << language_code << '"';
+ return true;
+ }
+ return false;
+ });
+ if (language_codes.empty()) {
+ LOG(ERROR) << "Language codes list is empty";
+ language_codes.emplace_back("en");
+ }
+ td::unique(language_codes);
+
+ auto it = emoji_language_codes_.find(key);
+ CHECK(it != emoji_language_codes_.end());
+ if (it->second != language_codes) {
+ LOG(INFO) << "Update emoji language codes for " << key << " to " << language_codes;
+ if (!G()->close_flag()) {
+ CHECK(G()->parameters().use_file_db);
+ G()->td_db()->get_sqlite_pmc()->set(key, implode(language_codes, '$'), Auto());
+ }
+ it->second = std::move(language_codes);
+ }
+
+ set_promises(promises);
+}
+
+vector<string> StickersManager::get_emoji_language_codes(const vector<string> &input_language_codes, Slice text,
+ Promise<Unit> &promise) {
+ vector<string> language_codes = td_->language_pack_manager_.get_actor_unsafe()->get_used_language_codes();
+ auto system_language_code = G()->mtproto_header().get_system_language_code();
+ if (system_language_code.size() >= 2 && system_language_code.find('$') == string::npos &&
+ (system_language_code.size() == 2 || system_language_code[2] == '-')) {
+ language_codes.push_back(system_language_code.substr(0, 2));
+ }
+ for (auto &input_language_code : input_language_codes) {
+ if (input_language_code.size() >= 2 && input_language_code.find('$') == string::npos &&
+ (input_language_code.size() == 2 || input_language_code[2] == '-')) {
+ language_codes.push_back(input_language_code.substr(0, 2));
+ }
+ }
+ if (!text.empty()) {
+ uint32 code = 0;
+ next_utf8_unsafe(text.ubegin(), &code);
+ if ((0x410 <= code && code <= 0x44F) || code == 0x401 || code == 0x451) {
+ // the first letter is cyrillic
+ if (!td::contains(language_codes, "ru") && !td::contains(language_codes, "uk") &&
+ !td::contains(language_codes, "bg") && !td::contains(language_codes, "be") &&
+ !td::contains(language_codes, "mk") && !td::contains(language_codes, "sr") &&
+ !td::contains(language_codes, "mn") && !td::contains(language_codes, "ky") &&
+ !td::contains(language_codes, "kk") && !td::contains(language_codes, "uz") &&
+ !td::contains(language_codes, "tk")) {
+ language_codes.push_back("ru");
+ }
+ }
+ }
+
+ if (language_codes.empty()) {
+ LOG(INFO) << "List of language codes is empty";
+ language_codes.push_back("en");
+ }
+ td::unique(language_codes);
+
+ LOG(DEBUG) << "Have language codes " << language_codes;
+ auto key = get_emoji_language_codes_database_key(language_codes);
+ auto it = emoji_language_codes_.find(key);
+ if (it == emoji_language_codes_.end()) {
+ it = emoji_language_codes_.emplace(key, full_split(G()->td_db()->get_sqlite_sync_pmc()->get(key), '$')).first;
+ td::remove_if(it->second, [](const string &language_code) {
+ if (language_code.empty() || language_code.find('$') != string::npos) {
+ LOG(ERROR) << "Loaded language_code \"" << language_code << '"';
+ return true;
+ }
+ return false;
+ });
+ }
+ if (it->second.empty()) {
+ load_language_codes(std::move(language_codes), std::move(key), std::move(promise));
+ } else {
+ LOG(DEBUG) << "Have emoji language codes " << it->second;
+ double now = Time::now_cached();
+ for (auto &language_code : it->second) {
+ double last_difference_time = get_emoji_language_code_last_difference_time(language_code);
+ if (last_difference_time < now - EMOJI_KEYWORDS_UPDATE_DELAY &&
+ get_emoji_language_code_version(language_code) != 0) {
+ load_emoji_keywords_difference(language_code);
+ }
+ }
+ if (reloaded_emoji_keywords_.insert(key).second) {
+ load_language_codes(std::move(language_codes), std::move(key), Auto());
+ }
+ }
+ return it->second;
+}
+
+void StickersManager::load_emoji_keywords(const string &language_code, Promise<Unit> &&promise) {
+ auto &promises = load_emoji_keywords_queries_[language_code];
+ promises.push_back(std::move(promise));
+ if (promises.size() != 1) {
+ // query has already been sent, just wait for the result
+ return;
+ }
+
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this),
+ language_code](Result<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> &&result) mutable {
+ send_closure(actor_id, &StickersManager::on_get_emoji_keywords, language_code, std::move(result));
+ });
+ td_->create_handler<GetEmojiKeywordsQuery>(std::move(query_promise))->send(language_code);
+}
+
+void StickersManager::on_get_emoji_keywords(
+ const string &language_code, Result<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> &&result) {
+ auto it = load_emoji_keywords_queries_.find(language_code);
+ CHECK(it != load_emoji_keywords_queries_.end());
+ auto promises = std::move(it->second);
+ CHECK(!promises.empty());
+ load_emoji_keywords_queries_.erase(it);
+
+ if (result.is_error()) {
+ if (!G()->is_expected_error(result.error())) {
+ LOG(ERROR) << "Receive " << result.error() << " from GetEmojiKeywordsQuery";
+ }
+ fail_promises(promises, result.move_as_error());
+ return;
+ }
+
+ auto version = get_emoji_language_code_version(language_code);
+ CHECK(version == 0);
+
+ MultiPromiseActorSafe mpas{"SaveEmojiKeywordsMultiPromiseActor"};
+ for (auto &promise : promises) {
+ mpas.add_promise(std::move(promise));
+ }
+
+ auto lock = mpas.get_promise();
+
+ auto keywords = result.move_as_ok();
+ LOG(INFO) << "Receive " << keywords->keywords_.size() << " emoji keywords for language " << language_code;
+ LOG_IF(ERROR, language_code != keywords->lang_code_)
+ << "Receive keywords for " << keywords->lang_code_ << " instead of " << language_code;
+ LOG_IF(ERROR, keywords->from_version_ != 0) << "Receive keywords from version " << keywords->from_version_;
+ version = keywords->version_;
+ if (version <= 0) {
+ LOG(ERROR) << "Receive keywords of version " << version;
+ version = 1;
+ }
+ for (auto &keyword_ptr : keywords->keywords_) {
+ switch (keyword_ptr->get_id()) {
+ case telegram_api::emojiKeyword::ID: {
+ auto keyword = telegram_api::move_object_as<telegram_api::emojiKeyword>(keyword_ptr);
+ auto text = utf8_to_lower(keyword->keyword_);
+ bool is_good = true;
+ for (auto &emoji : keyword->emoticons_) {
+ if (emoji.find('$') != string::npos) {
+ LOG(ERROR) << "Receive emoji \"" << emoji << "\" from server for " << text;
+ is_good = false;
+ }
+ }
+ if (is_good && !G()->close_flag()) {
+ CHECK(G()->parameters().use_file_db);
+ G()->td_db()->get_sqlite_pmc()->set(get_language_emojis_database_key(language_code, text),
+ implode(keyword->emoticons_, '$'), mpas.get_promise());
+ }
+ break;
+ }
+ case telegram_api::emojiKeywordDeleted::ID:
+ LOG(ERROR) << "Receive emojiKeywordDeleted in keywords for " << language_code;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+ if (!G()->close_flag()) {
+ CHECK(G()->parameters().use_file_db);
+ G()->td_db()->get_sqlite_pmc()->set(get_emoji_language_code_version_database_key(language_code), to_string(version),
+ mpas.get_promise());
+ G()->td_db()->get_sqlite_pmc()->set(get_emoji_language_code_last_difference_time_database_key(language_code),
+ to_string(G()->unix_time()), mpas.get_promise());
+ }
+ emoji_language_code_versions_[language_code] = version;
+ emoji_language_code_last_difference_times_[language_code] = static_cast<int32>(Time::now_cached());
+
+ lock.set_value(Unit());
+}
+
+void StickersManager::load_emoji_keywords_difference(const string &language_code) {
+ LOG(INFO) << "Load emoji keywords difference for language " << language_code;
+ CHECK(!language_code.empty());
+ emoji_language_code_last_difference_times_[language_code] =
+ Time::now_cached() + 1e9; // prevent simultaneous requests
+ int32 from_version = get_emoji_language_code_version(language_code);
+ auto query_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), language_code,
+ from_version](Result<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> &&result) mutable {
+ send_closure(actor_id, &StickersManager::on_get_emoji_keywords_difference, language_code, from_version,
+ std::move(result));
+ });
+ td_->create_handler<GetEmojiKeywordsDifferenceQuery>(std::move(query_promise))->send(language_code, from_version);
+}
+
+void StickersManager::on_get_emoji_keywords_difference(
+ const string &language_code, int32 from_version,
+ Result<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> &&result) {
+ if (G()->close_flag()) {
+ result = Global::request_aborted_error();
+ }
+ if (result.is_error()) {
+ if (!G()->is_expected_error(result.error())) {
+ LOG(ERROR) << "Receive " << result.error() << " from GetEmojiKeywordsDifferenceQuery";
+ }
+ emoji_language_code_last_difference_times_[language_code] = Time::now_cached() - EMOJI_KEYWORDS_UPDATE_DELAY - 2;
+ return;
+ }
+
+ auto version = get_emoji_language_code_version(language_code);
+ CHECK(version == from_version);
+
+ auto keywords = result.move_as_ok();
+ LOG(INFO) << "Receive " << keywords->keywords_.size() << " emoji keywords difference for language " << language_code;
+ LOG_IF(ERROR, language_code != keywords->lang_code_)
+ << "Receive keywords for " << keywords->lang_code_ << " instead of " << language_code;
+ LOG_IF(ERROR, keywords->from_version_ != from_version)
+ << "Receive keywords from version " << keywords->from_version_ << " instead of " << from_version;
+ if (keywords->version_ < version) {
+ LOG(ERROR) << "Receive keywords of version " << keywords->version_ << ", but have of version " << version;
+ keywords->version_ = version;
+ }
+ version = keywords->version_;
+ FlatHashMap<string, string> key_values;
+ key_values.emplace(get_emoji_language_code_version_database_key(language_code), to_string(version));
+ key_values.emplace(get_emoji_language_code_last_difference_time_database_key(language_code),
+ to_string(G()->unix_time()));
+ for (auto &keyword_ptr : keywords->keywords_) {
+ switch (keyword_ptr->get_id()) {
+ case telegram_api::emojiKeyword::ID: {
+ auto keyword = telegram_api::move_object_as<telegram_api::emojiKeyword>(keyword_ptr);
+ auto text = utf8_to_lower(keyword->keyword_);
+ bool is_good = true;
+ for (auto &emoji : keyword->emoticons_) {
+ if (emoji.find('$') != string::npos) {
+ LOG(ERROR) << "Receive emoji \"" << emoji << "\" from server for " << text;
+ is_good = false;
+ }
+ }
+ if (is_good) {
+ vector<string> emojis = search_language_emojis(language_code, text, true);
+ bool is_changed = false;
+ for (auto &emoji : keyword->emoticons_) {
+ if (!td::contains(emojis, emoji)) {
+ emojis.push_back(emoji);
+ is_changed = true;
+ }
+ }
+ if (is_changed) {
+ key_values.emplace(get_language_emojis_database_key(language_code, text), implode(emojis, '$'));
+ } else {
+ LOG(INFO) << "Emoji keywords not changed for \"" << text << "\" from version " << from_version
+ << " to version " << version;
+ }
+ }
+ break;
+ }
+ case telegram_api::emojiKeywordDeleted::ID: {
+ auto keyword = telegram_api::move_object_as<telegram_api::emojiKeywordDeleted>(keyword_ptr);
+ auto text = utf8_to_lower(keyword->keyword_);
+ vector<string> emojis = search_language_emojis(language_code, text, true);
+ bool is_changed = false;
+ for (auto &emoji : keyword->emoticons_) {
+ if (td::remove(emojis, emoji)) {
+ is_changed = true;
+ }
+ }
+ if (is_changed) {
+ key_values.emplace(get_language_emojis_database_key(language_code, text), implode(emojis, '$'));
+ } else {
+ LOG(INFO) << "Emoji keywords not changed for \"" << text << "\" from version " << from_version
+ << " to version " << version;
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+ G()->td_db()->get_sqlite_pmc()->set_all(
+ std::move(key_values), PromiseCreator::lambda([actor_id = actor_id(this), language_code, version](Unit) mutable {
+ send_closure(actor_id, &StickersManager::finish_get_emoji_keywords_difference, std::move(language_code),
+ version);
+ }));
+}
+
+void StickersManager::finish_get_emoji_keywords_difference(string language_code, int32 version) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ LOG(INFO) << "Finished to get emoji keywords difference for language " << language_code;
+ emoji_language_code_versions_[language_code] = version;
+ emoji_language_code_last_difference_times_[language_code] = static_cast<int32>(Time::now_cached());
+}
+
+vector<string> StickersManager::search_emojis(const string &text, bool exact_match,
+ const vector<string> &input_language_codes, bool force,
+ Promise<Unit> &&promise) {
+ if (text.empty() || !G()->parameters().use_file_db /* have SQLite PMC */) {
+ promise.set_value(Unit());
+ return {};
+ }
+
+ auto language_codes = get_emoji_language_codes(input_language_codes, text, promise);
+ if (language_codes.empty()) {
+ // promise was consumed
+ return {};
+ }
+
+ vector<string> languages_to_load;
+ for (auto &language_code : language_codes) {
+ CHECK(!language_code.empty());
+ auto version = get_emoji_language_code_version(language_code);
+ if (version == 0) {
+ languages_to_load.push_back(language_code);
+ } else {
+ LOG(DEBUG) << "Found language " << language_code << " with version " << version;
+ }
+ }
+
+ if (!languages_to_load.empty()) {
+ if (!force) {
+ MultiPromiseActorSafe mpas{"LoadEmojiLanguagesMultiPromiseActor"};
+ mpas.add_promise(std::move(promise));
+
+ auto lock = mpas.get_promise();
+ for (auto &language_code : languages_to_load) {
+ load_emoji_keywords(language_code, mpas.get_promise());
}
+ lock.set_value(Unit());
+ return {};
+ } else {
+ LOG(ERROR) << "Have no " << languages_to_load << " emoji keywords";
+ }
+ }
+
+ auto text_lowered = utf8_to_lower(text);
+ vector<string> result;
+ for (auto &language_code : language_codes) {
+ combine(result, search_language_emojis(language_code, text_lowered, exact_match));
+ }
+
+ td::unique(result);
+
+ promise.set_value(Unit());
+ return result;
+}
+
+int64 StickersManager::get_emoji_suggestions_url(const string &language_code, Promise<Unit> &&promise) {
+ int64 random_id = 0;
+ do {
+ random_id = Random::secure_int64();
+ } while (random_id == 0 || emoji_suggestions_urls_.count(random_id) > 0);
+ emoji_suggestions_urls_[random_id]; // reserve place for result
+
+ auto query_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), random_id, promise = std::move(promise)](
+ Result<telegram_api::object_ptr<telegram_api::emojiURL>> &&result) mutable {
+ send_closure(actor_id, &StickersManager::on_get_emoji_suggestions_url, random_id, std::move(promise),
+ std::move(result));
+ });
+ td_->create_handler<GetEmojiUrlQuery>(std::move(query_promise))->send(language_code);
+ return random_id;
+}
+
+void StickersManager::on_get_emoji_suggestions_url(
+ int64 random_id, Promise<Unit> &&promise, Result<telegram_api::object_ptr<telegram_api::emojiURL>> &&r_emoji_url) {
+ auto it = emoji_suggestions_urls_.find(random_id);
+ CHECK(it != emoji_suggestions_urls_.end());
+ auto &result = it->second;
+ CHECK(result.empty());
+
+ if (r_emoji_url.is_error()) {
+ emoji_suggestions_urls_.erase(it);
+ return promise.set_error(r_emoji_url.move_as_error());
+ }
+
+ auto emoji_url = r_emoji_url.move_as_ok();
+ result = std::move(emoji_url->url_);
+ promise.set_value(Unit());
+}
+
+td_api::object_ptr<td_api::httpUrl> StickersManager::get_emoji_suggestions_url_result(int64 random_id) {
+ auto it = emoji_suggestions_urls_.find(random_id);
+ CHECK(it != emoji_suggestions_urls_.end());
+ auto result = td_api::make_object<td_api::httpUrl>(it->second);
+ emoji_suggestions_urls_.erase(it);
+ return result;
+}
+
+void StickersManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ if (td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (!active_reactions_.empty()) {
+ updates.push_back(get_update_active_emoji_reactions_object());
+ }
+ for (int32 type = 0; type < MAX_STICKER_TYPE; type++) {
+ if (are_installed_sticker_sets_loaded_[type]) {
+ updates.push_back(get_update_installed_sticker_sets_object(static_cast<StickerType>(type)));
+ }
+ if (are_featured_sticker_sets_loaded_[type]) {
+ updates.push_back(get_update_trending_sticker_sets_object(static_cast<StickerType>(type)));
}
}
- return emoji;
+
+ for (int is_attached = 0; is_attached < 2; is_attached++) {
+ if (are_recent_stickers_loaded_[is_attached]) {
+ updates.push_back(get_update_recent_stickers_object(is_attached));
+ }
+ }
+ if (are_favorite_stickers_loaded_) {
+ updates.push_back(get_update_favorite_stickers_object());
+ }
+ if (!dice_emojis_.empty()) {
+ updates.push_back(get_update_dice_emojis_object());
+ }
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StickersManager.h b/protocols/Telegram/tdlib/td/td/telegram/StickersManager.h
index 367c8c5960..4656b14a03 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/StickersManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/StickersManager.h
@@ -1,140 +1,298 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/CustomEmojiId.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/SpecialStickerSetType.h"
+#include "td/telegram/StickerFormat.h"
+#include "td/telegram/StickerSetId.h"
+#include "td/telegram/StickerType.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
#include "td/actor/actor.h"
#include "td/actor/MultiPromise.h"
-#include "td/actor/PromiseFuture.h"
#include "td/actor/Timeout.h"
-#include "td/telegram/files/FileId.h"
-#include "td/telegram/Photo.h"
-#include "td/telegram/SecretInputMedia.h"
-
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/Hints.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/WaitFreeHashMap.h"
+#include "td/utils/WaitFreeHashSet.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
+#include <map>
#include <memory>
#include <tuple>
#include <unordered_map>
-#include <unordered_set>
#include <utility>
namespace td {
-class MultiPromiseActor;
-class Td;
-} // namespace td
-namespace td {
+class Td;
-class StickersManager : public Actor {
+class StickersManager final : public Actor {
public:
+ static constexpr int64 GREAT_MINDS_SET_ID = 1842540969984001;
+
+ static vector<StickerSetId> convert_sticker_set_ids(const vector<int64> &sticker_set_ids);
+ static vector<int64> convert_sticker_set_ids(const vector<StickerSetId> &sticker_set_ids);
+
StickersManager(Td *td, ActorShared<> parent);
+ StickersManager(const StickersManager &) = delete;
+ StickersManager &operator=(const StickersManager &) = delete;
+ StickersManager(StickersManager &&) = delete;
+ StickersManager &operator=(StickersManager &&) = delete;
+ ~StickersManager() final;
+
+ void init();
+
+ StickerType get_sticker_type(FileId file_id) const;
+
+ bool is_premium_custom_emoji(CustomEmojiId custom_emoji_id, bool default_result) const;
+
+ tl_object_ptr<td_api::sticker> get_sticker_object(FileId file_id, bool for_animated_emoji = false,
+ bool for_clicked_animated_emoji = false) const;
+
+ tl_object_ptr<td_api::stickers> get_stickers_object(const vector<FileId> &sticker_ids) const;
+
+ tl_object_ptr<td_api::DiceStickers> get_dice_stickers_object(const string &emoji, int32 value) const;
+
+ int32 get_dice_success_animation_frame_number(const string &emoji, int32 value) const;
+
+ tl_object_ptr<td_api::stickerSet> get_sticker_set_object(StickerSetId sticker_set_id) const;
+
+ tl_object_ptr<td_api::stickerSets> get_sticker_sets_object(int32 total_count,
+ const vector<StickerSetId> &sticker_set_ids,
+ size_t covers_limit) const;
+
+ td_api::object_ptr<td_api::sticker> get_premium_gift_sticker_object(int32 month_count);
+
+ td_api::object_ptr<td_api::animatedEmoji> get_animated_emoji_object(const string &emoji,
+ CustomEmojiId custom_emoji_id);
+
+ tl_object_ptr<telegram_api::InputStickerSet> get_input_sticker_set(StickerSetId sticker_set_id) const;
+
+ void register_premium_gift(int32 months, FullMessageId full_message_id, const char *source);
- tl_object_ptr<td_api::sticker> get_sticker_object(FileId file_id);
+ void unregister_premium_gift(int32 months, FullMessageId full_message_id, const char *source);
- tl_object_ptr<td_api::stickers> get_stickers_object(const vector<FileId> &sticker_ids);
+ void register_dice(const string &emoji, int32 value, FullMessageId full_message_id, const char *source);
- tl_object_ptr<td_api::stickerSet> get_sticker_set_object(int64 sticker_set_id);
+ void unregister_dice(const string &emoji, int32 value, FullMessageId full_message_id, const char *source);
- tl_object_ptr<td_api::stickerSets> get_sticker_sets_object(int32 total_count, const vector<int64> &sticker_set_ids,
- size_t covers_limit);
+ void register_emoji(const string &emoji, CustomEmojiId custom_emoji_id, FullMessageId full_message_id,
+ const char *source);
- tl_object_ptr<telegram_api::InputStickerSet> get_input_sticker_set(int64 sticker_set_id) const;
+ void unregister_emoji(const string &emoji, CustomEmojiId custom_emoji_id, FullMessageId full_message_id,
+ const char *source);
- void create_sticker(FileId file_id, PhotoSize thumbnail, Dimensions dimensions, bool from_message,
- tl_object_ptr<telegram_api::documentAttributeSticker> sticker,
- MultiPromiseActor *load_data_multipromise_ptr);
+ void get_animated_emoji(string emoji, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::animatedEmoji>> &&promise);
+
+ void get_all_animated_emojis(bool is_recursive, Promise<td_api::object_ptr<td_api::emojis>> &&promise);
+
+ void get_custom_emoji_reaction_generic_animations(bool is_recursive,
+ Promise<td_api::object_ptr<td_api::stickers>> &&promise);
+
+ void get_default_emoji_statuses(bool is_recursive, Promise<td_api::object_ptr<td_api::emojiStatuses>> &&promise);
+
+ bool is_default_emoji_status(CustomEmojiId custom_emoji_id);
+
+ void get_default_topic_icons(bool is_recursive, Promise<td_api::object_ptr<td_api::stickers>> &&promise);
+
+ void get_custom_emoji_stickers(vector<CustomEmojiId> &&custom_emoji_ids, bool use_database,
+ Promise<td_api::object_ptr<td_api::stickers>> &&promise);
+
+ void get_premium_gift_option_sticker(int32 month_count, bool is_recursive,
+ Promise<td_api::object_ptr<td_api::sticker>> &&promise);
+
+ void get_animated_emoji_click_sticker(const string &message_text, FullMessageId full_message_id,
+ Promise<td_api::object_ptr<td_api::sticker>> &&promise);
+
+ void on_send_animated_emoji_clicks(DialogId dialog_id, const string &emoji);
+
+ bool is_sent_animated_emoji_click(DialogId dialog_id, const string &emoji);
+
+ Status on_animated_emoji_message_clicked(string &&emoji, FullMessageId full_message_id, string data);
+
+ bool is_active_reaction(const string &reaction) const;
+
+ void create_sticker(FileId file_id, FileId premium_animation_file_id, string minithumbnail, PhotoSize thumbnail,
+ Dimensions dimensions, tl_object_ptr<telegram_api::documentAttributeSticker> sticker,
+ tl_object_ptr<telegram_api::documentAttributeCustomEmoji> custom_emoji,
+ StickerFormat sticker_format, MultiPromiseActor *load_data_multipromise_ptr);
bool has_input_media(FileId sticker_file_id, bool is_secret) const;
tl_object_ptr<telegram_api::InputMedia> get_input_media(FileId file_id,
tl_object_ptr<telegram_api::InputFile> input_file,
- tl_object_ptr<telegram_api::InputFile> input_thumbnail) const;
+ tl_object_ptr<telegram_api::InputFile> input_thumbnail,
+ const string &emoji) const;
SecretInputMedia get_secret_input_media(FileId sticker_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- BufferSlice thumbnail) const;
+ BufferSlice thumbnail, int32 layer) const;
+
+ vector<FileId> get_stickers(StickerType sticker_type, string query, int32 limit, DialogId dialog_id, bool force,
+ Promise<Unit> &&promise);
+
+ void search_stickers(string emoji, int32 limit, Promise<td_api::object_ptr<td_api::stickers>> &&promise);
+
+ void get_premium_stickers(int32 limit, Promise<td_api::object_ptr<td_api::stickers>> &&promise);
+
+ vector<StickerSetId> get_installed_sticker_sets(StickerType sticker_type, Promise<Unit> &&promise);
+
+ static bool has_webp_thumbnail(const vector<tl_object_ptr<telegram_api::PhotoSize>> &thumbnails);
+
+ StickerSetId get_sticker_set_id(const tl_object_ptr<telegram_api::InputStickerSet> &set_ptr);
+
+ StickerSetId add_sticker_set(tl_object_ptr<telegram_api::InputStickerSet> &&set_ptr);
+
+ StickerSetId get_sticker_set(StickerSetId set_id, Promise<Unit> &&promise);
+
+ StickerSetId search_sticker_set(const string &short_name_to_search, Promise<Unit> &&promise);
+
+ std::pair<int32, vector<StickerSetId>> search_installed_sticker_sets(StickerType sticker_type, const string &query,
+ int32 limit, Promise<Unit> &&promise);
+
+ vector<StickerSetId> search_sticker_sets(const string &query, Promise<Unit> &&promise);
+
+ void change_sticker_set(StickerSetId set_id, bool is_installed, bool is_archived, Promise<Unit> &&promise);
+
+ void view_featured_sticker_sets(const vector<StickerSetId> &sticker_set_ids);
+
+ void get_emoji_reaction(const string &emoji, Promise<td_api::object_ptr<td_api::emojiReaction>> &&promise);
+
+ vector<string> get_recent_reactions();
+
+ vector<string> get_top_reactions();
- vector<FileId> get_stickers(string emoji, int32 limit, bool force, Promise<Unit> &&promise);
+ void add_recent_reaction(const string &reaction);
- vector<FileId> search_stickers(string emoji, int32 limit, Promise<Unit> &&promise);
+ void clear_recent_reactions(Promise<Unit> &&promise);
- vector<int64> get_installed_sticker_sets(bool is_masks, Promise<Unit> &&promise);
+ void reload_reactions();
- int64 add_sticker_set(tl_object_ptr<telegram_api::InputStickerSet> &&set_ptr);
+ void reload_recent_reactions();
- int64 get_sticker_set(int64 set_id, Promise<Unit> &&promise);
+ void reload_top_reactions();
- int64 search_sticker_set(const string &short_name_to_search, Promise<Unit> &&promise);
+ void reload_special_sticker_set_by_type(SpecialStickerSetType type, bool is_recursive = false);
- std::pair<int32, vector<int64>> search_installed_sticker_sets(bool is_masks, const string &query, int32 limit,
- Promise<Unit> &&promise);
+ void on_get_available_reactions(tl_object_ptr<telegram_api::messages_AvailableReactions> &&available_reactions_ptr);
- vector<int64> search_sticker_sets(const string &query, Promise<Unit> &&promise);
+ void on_get_recent_reactions(tl_object_ptr<telegram_api::messages_Reactions> &&reactions_ptr);
- void change_sticker_set(int64 set_id, bool is_installed, bool is_archived, Promise<Unit> &&promise);
+ void on_get_top_reactions(tl_object_ptr<telegram_api::messages_Reactions> &&reactions_ptr);
- void view_featured_sticker_sets(const vector<int64> &sticker_set_ids);
+ void on_get_installed_sticker_sets(StickerType sticker_type,
+ tl_object_ptr<telegram_api::messages_AllStickers> &&stickers_ptr);
- void on_get_installed_sticker_sets(bool is_masks, tl_object_ptr<telegram_api::messages_AllStickers> &&stickers_ptr);
+ void on_get_installed_sticker_sets_failed(StickerType sticker_type, Status error);
- void on_get_installed_sticker_sets_failed(bool is_masks, Status error);
+ StickerSetId on_get_messages_sticker_set(StickerSetId sticker_set_id,
+ tl_object_ptr<telegram_api::messages_StickerSet> &&set_ptr, bool is_changed,
+ const char *source);
- void on_get_messages_sticker_set(int64 sticker_set_id, tl_object_ptr<telegram_api::messages_stickerSet> &&set,
- bool is_changed);
+ StickerSetId on_get_sticker_set(tl_object_ptr<telegram_api::stickerSet> &&set, bool is_changed, const char *source);
- int64 on_get_sticker_set(tl_object_ptr<telegram_api::stickerSet> &&set, bool is_changed);
+ StickerSetId on_get_sticker_set_covered(tl_object_ptr<telegram_api::StickerSetCovered> &&set_ptr, bool is_changed,
+ const char *source);
- int64 on_get_sticker_set_covered(tl_object_ptr<telegram_api::StickerSetCovered> &&set_ptr, bool is_changed);
+ void on_get_special_sticker_set(const SpecialStickerSetType &type, StickerSetId sticker_set_id);
- void on_load_sticker_set_fail(int64 sticker_set_id, const Status &error);
+ void on_load_special_sticker_set(const SpecialStickerSetType &type, Status result);
- void on_install_sticker_set(int64 set_id, bool is_archived,
+ void on_load_sticker_set_fail(StickerSetId sticker_set_id, const Status &error);
+
+ void on_install_sticker_set(StickerSetId set_id, bool is_archived,
tl_object_ptr<telegram_api::messages_StickerSetInstallResult> &&result);
- void on_uninstall_sticker_set(int64 set_id);
+ void on_uninstall_sticker_set(StickerSetId set_id);
+
+ void on_update_animated_emoji_zoom();
+
+ void on_update_disable_animated_emojis();
+
+ void on_update_dice_emojis();
- void on_update_sticker_sets();
+ void on_update_dice_success_values();
- void on_update_sticker_sets_order(bool is_masks, const vector<int64> &sticker_set_ids);
+ void on_update_emoji_sounds();
- std::pair<int32, vector<int64>> get_archived_sticker_sets(bool is_masks, int64 offset_sticker_set_id, int32 limit,
- bool force, Promise<Unit> &&promise);
+ void on_update_sticker_sets(StickerType sticker_type);
- void on_get_archived_sticker_sets(bool is_masks,
+ void on_update_sticker_sets_order(StickerType sticker_type, const vector<StickerSetId> &sticker_set_ids);
+
+ void on_update_move_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id);
+
+ std::pair<int32, vector<StickerSetId>> get_archived_sticker_sets(StickerType sticker_type,
+ StickerSetId offset_sticker_set_id, int32 limit,
+ bool force, Promise<Unit> &&promise);
+
+ void on_get_archived_sticker_sets(StickerType sticker_type, StickerSetId offset_sticker_set_id,
vector<tl_object_ptr<telegram_api::StickerSetCovered>> &&sticker_sets,
int32 total_count);
- vector<int64> get_featured_sticker_sets(Promise<Unit> &&promise);
+ td_api::object_ptr<td_api::trendingStickerSets> get_featured_sticker_sets(StickerType sticker_type, int32 offset,
+ int32 limit, Promise<Unit> &&promise);
- void on_get_featured_sticker_sets(tl_object_ptr<telegram_api::messages_FeaturedStickers> &&sticker_sets_ptr);
+ void on_get_featured_sticker_sets(StickerType sticker_type, int32 offset, int32 limit, uint32 generation,
+ tl_object_ptr<telegram_api::messages_FeaturedStickers> &&sticker_sets_ptr);
- void on_get_featured_sticker_sets_failed(Status error);
+ void on_get_featured_sticker_sets_failed(StickerType sticker_type, int32 offset, int32 limit, uint32 generation,
+ Status error);
- vector<int64> get_attached_sticker_sets(FileId file_id, Promise<Unit> &&promise);
+ vector<StickerSetId> get_attached_sticker_sets(FileId file_id, Promise<Unit> &&promise);
void on_get_attached_sticker_sets(FileId file_id,
vector<tl_object_ptr<telegram_api::StickerSetCovered>> &&sticker_sets);
- void reorder_installed_sticker_sets(bool is_masks, const vector<int64> &sticker_set_ids, Promise<Unit> &&promise);
+ void reorder_installed_sticker_sets(StickerType sticker_type, const vector<StickerSetId> &sticker_set_ids,
+ Promise<Unit> &&promise);
- FileId upload_sticker_file(UserId user_id, const tl_object_ptr<td_api::InputFile> &sticker, Promise<Unit> &&promise);
+ void move_sticker_set_to_top_by_sticker_id(FileId sticker_id);
- void create_new_sticker_set(UserId user_id, string &title, string &short_name, bool is_masks,
- vector<tl_object_ptr<td_api::inputSticker>> &&stickers, Promise<Unit> &&promise);
+ void move_sticker_set_to_top_by_custom_emoji_ids(const vector<CustomEmojiId> &custom_emoji_ids);
- void add_sticker_to_set(UserId user_id, string &short_name, tl_object_ptr<td_api::inputSticker> &&sticker,
- Promise<Unit> &&promise);
+ FileId upload_sticker_file(UserId user_id, tl_object_ptr<td_api::inputSticker> &&sticker, Promise<Unit> &&promise);
+
+ void get_suggested_sticker_set_name(string title, Promise<string> &&promise);
+
+ enum class CheckStickerSetNameResult : uint8 { Ok, Invalid, Occupied };
+ void check_sticker_set_name(const string &name, Promise<CheckStickerSetNameResult> &&promise);
+
+ static td_api::object_ptr<td_api::CheckStickerSetNameResult> get_check_sticker_set_name_result_object(
+ CheckStickerSetNameResult result);
+
+ void create_new_sticker_set(UserId user_id, string title, string short_name, StickerType sticker_type,
+ vector<td_api::object_ptr<td_api::inputSticker>> &&stickers, string software,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise);
+
+ void add_sticker_to_set(UserId user_id, string short_name, tl_object_ptr<td_api::inputSticker> &&sticker,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise);
+
+ void set_sticker_set_thumbnail(UserId user_id, string short_name, tl_object_ptr<td_api::InputFile> &&thumbnail,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise);
void set_sticker_position_in_set(const tl_object_ptr<td_api::InputFile> &sticker, int32 position,
Promise<Unit> &&promise);
@@ -143,9 +301,12 @@ class StickersManager : public Actor {
vector<FileId> get_recent_stickers(bool is_attached, Promise<Unit> &&promise);
- void on_get_recent_stickers(bool is_attached, tl_object_ptr<telegram_api::messages_RecentStickers> &&stickers_ptr);
+ void on_get_recent_stickers(bool is_repair, bool is_attached,
+ tl_object_ptr<telegram_api::messages_RecentStickers> &&stickers_ptr);
+
+ void on_get_recent_stickers_failed(bool is_repair, bool is_attached, Status error);
- void on_get_recent_stickers_failed(bool is_attached, Status error);
+ FileSourceId get_recent_stickers_file_source_id(int is_attached);
void add_recent_sticker(bool is_attached, const tl_object_ptr<td_api::InputFile> &input_file,
Promise<Unit> &&promise);
@@ -155,17 +316,26 @@ class StickersManager : public Actor {
void remove_recent_sticker(bool is_attached, const tl_object_ptr<td_api::InputFile> &input_file,
Promise<Unit> &&promise);
+ void send_save_recent_sticker_query(bool is_attached, FileId sticker_id, bool unsave, Promise<Unit> &&promise);
+
void clear_recent_stickers(bool is_attached, Promise<Unit> &&promise);
- void on_update_recent_stickers_limit(int32 recent_stickers_limit);
+ void on_update_recent_stickers_limit();
- void on_update_favorite_stickers_limit(int32 favorite_stickers_limit);
+ void on_update_favorite_stickers_limit();
void reload_favorite_stickers(bool force);
- void on_get_favorite_stickers(tl_object_ptr<telegram_api::messages_FavedStickers> &&favorite_stickers_ptr);
+ void repair_favorite_stickers(Promise<Unit> &&promise);
+
+ void on_get_favorite_stickers(bool is_repair,
+ tl_object_ptr<telegram_api::messages_FavedStickers> &&favorite_stickers_ptr);
+
+ void on_get_favorite_stickers_failed(bool is_repair, Status error);
+
+ FileSourceId get_app_config_file_source_id();
- void on_get_favorite_stickers_failed(Status error);
+ FileSourceId get_favorite_stickers_file_source_id();
vector<FileId> get_favorite_stickers(Promise<Unit> &&promise);
@@ -175,34 +345,49 @@ class StickersManager : public Actor {
void remove_favorite_sticker(const tl_object_ptr<td_api::InputFile> &input_file, Promise<Unit> &&promise);
+ void send_fave_sticker_query(FileId sticker_id, bool unsave, Promise<Unit> &&promise);
+
vector<FileId> get_attached_sticker_file_ids(const vector<int32> &int_file_ids);
vector<string> get_sticker_emojis(const tl_object_ptr<td_api::InputFile> &input_file, Promise<Unit> &&promise);
- void reload_installed_sticker_sets(bool is_masks, bool force);
+ vector<string> search_emojis(const string &text, bool exact_match, const vector<string> &input_language_codes,
+ bool force, Promise<Unit> &&promise);
+
+ int64 get_emoji_suggestions_url(const string &language_code, Promise<Unit> &&promise);
+
+ td_api::object_ptr<td_api::httpUrl> get_emoji_suggestions_url_result(int64 random_id);
+
+ void reload_sticker_set(StickerSetId sticker_set_id, int64 access_hash, Promise<Unit> &&promise);
- void reload_featured_sticker_sets(bool force);
+ void reload_installed_sticker_sets(StickerType sticker_type, bool force);
+
+ void reload_featured_sticker_sets(StickerType sticker_type, bool force);
void reload_recent_stickers(bool is_attached, bool force);
+ void repair_recent_stickers(bool is_attached, Promise<Unit> &&promise);
+
FileId get_sticker_thumbnail_file_id(FileId file_id) const;
+ vector<FileId> get_sticker_file_ids(FileId file_id) const;
+
void delete_sticker_thumbnail(FileId file_id);
FileId dup_sticker(FileId new_id, FileId old_id);
- bool merge_stickers(FileId new_id, FileId old_id, bool can_delete_old);
+ void merge_stickers(FileId new_id, FileId old_id);
- template <class T>
- void store_sticker(FileId file_id, bool in_sticker_set, T &storer) const;
+ template <class StorerT>
+ void store_sticker(FileId file_id, bool in_sticker_set, StorerT &storer, const char *source) const;
- template <class T>
- FileId parse_sticker(bool in_sticker_set, T &parser);
+ template <class ParserT>
+ FileId parse_sticker(bool in_sticker_set, ParserT &parser);
void on_uploaded_sticker_file(FileId file_id, tl_object_ptr<telegram_api::MessageMedia> media,
Promise<Unit> &&promise);
- void on_find_stickers_success(const string &emoji, tl_object_ptr<telegram_api::messages_Stickers> &&sticker_sets);
+ void on_find_stickers_success(const string &emoji, tl_object_ptr<telegram_api::messages_Stickers> &&stickers);
void on_find_stickers_fail(const string &emoji, Status &&error);
@@ -211,180 +396,373 @@ class StickersManager : public Actor {
void on_find_sticker_sets_fail(const string &query, Status &&error);
+ void send_get_attached_stickers_query(FileId file_id, Promise<Unit> &&promise);
+
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
+ template <class StorerT>
+ void store_sticker_set_id(StickerSetId sticker_set_id, StorerT &storer) const;
+
+ template <class ParserT>
+ void parse_sticker_set_id(StickerSetId &sticker_set_id, ParserT &parser);
+
private:
static constexpr int32 MAX_FEATURED_STICKER_SET_VIEW_DELAY = 5;
+ static constexpr int32 OLD_FEATURED_STICKER_SET_SLICE_SIZE = 20;
static constexpr int32 MAX_FOUND_STICKERS = 100; // server side limit
- static constexpr int64 MAX_STICKER_FILE_SIZE = 1 << 19; // server side limit
static constexpr size_t MAX_STICKER_SET_TITLE_LENGTH = 64; // server side limit
static constexpr size_t MAX_STICKER_SET_SHORT_NAME_LENGTH = 64; // server side limit
+ static constexpr size_t MAX_GET_CUSTOM_EMOJI_STICKERS = 200; // server-side limit
- static constexpr int64 GREAT_MINDS_SET_ID = 1842540969984001;
+ static constexpr int32 EMOJI_KEYWORDS_UPDATE_DELAY = 3600;
+ static constexpr double MIN_ANIMATED_EMOJI_CLICK_DELAY = 0.2;
+
+ static constexpr int32 MAX_RECENT_REACTIONS = 100; // some reasonable value
class Sticker {
public:
- int64 set_id = 0;
- string alt;
- Dimensions dimensions;
- PhotoSize message_thumbnail;
- PhotoSize sticker_thumbnail;
- FileId file_id;
- bool is_mask = false;
- int32 point = -1;
- double x_shift = 0;
- double y_shift = 0;
- double scale = 0;
-
- bool is_changed = true;
+ StickerSetId set_id_;
+ string alt_;
+ Dimensions dimensions_;
+ string minithumbnail_;
+ PhotoSize s_thumbnail_;
+ PhotoSize m_thumbnail_;
+ FileId premium_animation_file_id_;
+ FileId file_id_;
+ StickerFormat format_ = StickerFormat::Unknown;
+ StickerType type_ = StickerType::Regular;
+ bool is_premium_ = false;
+ bool is_from_database_ = false;
+ bool is_being_reloaded_ = false;
+ int32 point_ = -1;
+ double x_shift_ = 0;
+ double y_shift_ = 0;
+ double scale_ = 0;
+ int32 emoji_receive_date_ = 0;
};
class StickerSet {
public:
- bool is_inited = false;
- bool was_loaded = false;
- bool is_loaded = false;
-
- int64 id = 0;
- int64 access_hash = 0;
- string title;
- string short_name;
- int32 sticker_count = 0;
- int32 hash = 0;
- int32 expires_at = 0;
-
- vector<FileId> sticker_ids;
- std::unordered_map<string, vector<FileId>> emoji_stickers_map_; // emoji -> stickers
- std::unordered_map<FileId, vector<string>, FileIdHash> sticker_emojis_map_; // sticker -> emojis
-
- bool is_installed = false;
- bool is_archived = false;
- bool is_official = false;
- bool is_masks = false;
- bool is_viewed = true;
- bool is_changed = true;
-
- vector<uint32> load_requests;
- vector<uint32> load_without_stickers_requests;
+ bool is_inited_ = false; // basic information about the set
+ bool was_loaded_ = false;
+ bool is_loaded_ = false;
+ bool are_keywords_loaded_ = false;
+
+ StickerSetId id_;
+ int64 access_hash_ = 0;
+ string title_;
+ string short_name_;
+ StickerFormat sticker_format_ = StickerFormat::Unknown;
+ StickerType sticker_type_ = StickerType::Regular;
+ int32 sticker_count_ = 0;
+ int32 hash_ = 0;
+ int32 expires_at_ = 0;
+
+ string minithumbnail_;
+ PhotoSize thumbnail_;
+ int64 thumbnail_document_id_ = 0;
+
+ vector<FileId> sticker_ids_;
+ vector<int32> premium_sticker_positions_;
+ FlatHashMap<string, vector<FileId>> emoji_stickers_map_; // emoji -> stickers
+ FlatHashMap<FileId, vector<string>, FileIdHash> sticker_emojis_map_; // sticker -> emojis
+ mutable std::map<string, vector<FileId>> keyword_stickers_map_; // keyword -> stickers
+ FlatHashMap<FileId, vector<string>, FileIdHash> sticker_keywords_map_; // sticker -> keywords
+
+ bool is_installed_ = false;
+ bool is_archived_ = false;
+ bool is_official_ = false;
+ bool is_viewed_ = true;
+ bool is_thumbnail_reloaded_ = false;
+ bool are_legacy_sticker_thumbnails_reloaded_ = false;
+ mutable bool was_update_sent_ = false; // does the sticker set is known to the client
+ bool is_changed_ = true; // have new changes that need to be sent to the client and database
+ bool need_save_to_database_ = true; // have new changes that need only to be saved to the database
+
+ vector<uint32> load_requests_;
+ vector<uint32> load_without_stickers_requests_;
};
struct PendingNewStickerSet {
- MultiPromiseActor upload_files_multipromise;
- UserId user_id;
- string title;
- string short_name;
- bool is_masks;
- vector<FileId> file_ids;
- vector<tl_object_ptr<td_api::inputSticker>> stickers;
- Promise<> promise;
+ MultiPromiseActor upload_files_multipromise_{"UploadNewStickerSetFilesMultiPromiseActor"};
+ UserId user_id_;
+ string title_;
+ string short_name_;
+ StickerType sticker_type_ = StickerType::Regular;
+ StickerFormat sticker_format_ = StickerFormat::Unknown;
+ vector<FileId> file_ids_;
+ vector<tl_object_ptr<td_api::inputSticker>> stickers_;
+ string software_;
+ Promise<td_api::object_ptr<td_api::stickerSet>> promise_;
};
struct PendingAddStickerToSet {
- string short_name;
- FileId file_id;
- tl_object_ptr<td_api::inputSticker> sticker;
- Promise<> promise;
+ string short_name_;
+ FileId file_id_;
+ tl_object_ptr<td_api::inputSticker> sticker_;
+ Promise<td_api::object_ptr<td_api::stickerSet>> promise_;
+ };
+
+ struct PendingSetStickerSetThumbnail {
+ string short_name_;
+ FileId file_id_;
+ Promise<td_api::object_ptr<td_api::stickerSet>> promise_;
+ };
+
+ struct PendingGetAnimatedEmojiClickSticker {
+ string message_text_;
+ FullMessageId full_message_id_;
+ double start_time_ = 0;
+ Promise<td_api::object_ptr<td_api::sticker>> promise_;
+ };
+
+ struct PendingOnAnimatedEmojiClicked {
+ string emoji_;
+ FullMessageId full_message_id_;
+ vector<std::pair<int, double>> clicks_;
+ };
+
+ struct SpecialStickerSet {
+ StickerSetId id_;
+ int64 access_hash_ = 0;
+ string short_name_;
+ SpecialStickerSetType type_;
+ bool is_being_loaded_ = false;
+ bool is_being_reloaded_ = false;
+ };
+
+ struct FoundStickers {
+ vector<FileId> sticker_ids_;
+ int32 cache_time_ = 300;
+ double next_reload_time_ = 0;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ struct Reaction {
+ string reaction_;
+ string title_;
+ bool is_active_ = false;
+ bool is_premium_ = false;
+ FileId static_icon_;
+ FileId appear_animation_;
+ FileId select_animation_;
+ FileId activate_animation_;
+ FileId effect_animation_;
+ FileId around_animation_;
+ FileId center_animation_;
+
+ bool is_valid() const {
+ return static_icon_.is_valid() && appear_animation_.is_valid() && select_animation_.is_valid() &&
+ activate_animation_.is_valid() && effect_animation_.is_valid() && !reaction_.empty();
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ struct Reactions {
+ int32 hash_ = 0;
+ bool are_being_reloaded_ = false;
+ vector<Reaction> reactions_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
};
+ struct ReactionList {
+ int64 hash_ = 0;
+ bool is_being_reloaded_ = false;
+ vector<string> reactions_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ class CustomEmojiLogEvent;
class StickerListLogEvent;
class StickerSetListLogEvent;
class UploadStickerFileCallback;
- tl_object_ptr<td_api::MaskPoint> get_mask_point_object(int32 point);
+ CustomEmojiId get_custom_emoji_id(FileId sticker_id) const;
+
+ static vector<td_api::object_ptr<td_api::closedVectorPath>> get_sticker_minithumbnail(CSlice path,
+ StickerSetId sticker_set_id,
+ int64 document_id, double zoom);
+
+ static double get_sticker_set_minithumbnail_zoom(const StickerSet *sticker_set);
+
+ static tl_object_ptr<td_api::MaskPoint> get_mask_point_object(int32 point);
- tl_object_ptr<td_api::stickerSetInfo> get_sticker_set_info_object(int64 sticker_set_id, size_t covers_limit);
+ td_api::object_ptr<td_api::thumbnail> get_sticker_set_thumbnail_object(const StickerSet *sticker_set) const;
+
+ tl_object_ptr<td_api::stickerSetInfo> get_sticker_set_info_object(StickerSetId sticker_set_id, size_t covers_limit,
+ bool prefer_premium) const;
+
+ td_api::object_ptr<td_api::emojiReaction> get_emoji_reaction_object(const string &emoji) const;
Sticker *get_sticker(FileId file_id);
const Sticker *get_sticker(FileId file_id) const;
- FileId on_get_sticker(std::unique_ptr<Sticker> new_sticker, bool replace);
+ static string get_found_stickers_database_key(const string &emoji);
+
+ void on_load_found_stickers_from_database(string emoji, string value);
- StickerSet *get_sticker_set(int64 sticker_set_id);
- const StickerSet *get_sticker_set(int64 sticker_set_id) const;
+ void on_search_stickers_finished(const string &emoji, const FoundStickers &found_stickers);
- StickerSet *add_sticker_set(int64 sticker_set_id, int64 access_hash);
+ static string get_custom_emoji_database_key(CustomEmojiId custom_emoji_id);
+
+ void load_custom_emoji_sticker_from_database_force(CustomEmojiId custom_emoji_id);
+
+ void load_custom_emoji_sticker_from_database(CustomEmojiId custom_emoji_id, Promise<Unit> &&promise);
+
+ void on_load_custom_emoji_from_database(CustomEmojiId custom_emoji_id, string value);
+
+ FileId on_get_sticker(unique_ptr<Sticker> new_sticker, bool replace);
+
+ StickerSet *get_sticker_set(StickerSetId sticker_set_id);
+
+ const StickerSet *get_sticker_set(StickerSetId sticker_set_id) const;
+
+ StickerSet *add_sticker_set(StickerSetId sticker_set_id, int64 access_hash);
+
+ static PhotoFormat get_sticker_set_thumbnail_format(StickerFormat sticker_format);
std::pair<int64, FileId> on_get_sticker_document(tl_object_ptr<telegram_api::Document> &&document_ptr,
- bool from_message);
+ StickerFormat expected_format);
static tl_object_ptr<telegram_api::InputStickerSet> get_input_sticker_set(const StickerSet *set);
- int64 on_get_input_sticker_set(FileId sticker_file_id, tl_object_ptr<telegram_api::InputStickerSet> &&set_ptr,
- MultiPromiseActor *load_data_multipromise_ptr = nullptr);
+ StickerSetId on_get_input_sticker_set(FileId sticker_file_id, tl_object_ptr<telegram_api::InputStickerSet> &&set_ptr,
+ MultiPromiseActor *load_data_multipromise_ptr = nullptr);
+
+ string get_sticker_set_short_name(FileId sticker_id) const;
void on_resolve_sticker_set_short_name(FileId sticker_file_id, const string &short_name);
- int apply_installed_sticker_sets_order(bool is_masks, const vector<int64> &sticker_set_ids);
+ int apply_installed_sticker_sets_order(StickerType sticker_type, const vector<StickerSetId> &sticker_set_ids);
+
+ int move_installed_sticker_set_to_top(StickerType sticker_type, StickerSetId sticker_set_id);
void on_update_sticker_set(StickerSet *sticker_set, bool is_installed, bool is_archived, bool is_changed,
bool from_database = false);
- static string get_sticker_set_database_key(int64 set_id);
+ static string get_sticker_set_database_key(StickerSetId set_id);
- static string get_full_sticker_set_database_key(int64 set_id);
+ static string get_full_sticker_set_database_key(StickerSetId set_id);
- string get_sticker_set_database_value(const StickerSet *s, bool with_stickers);
+ string get_sticker_set_database_value(const StickerSet *s, bool with_stickers, const char *source);
- void update_sticker_set(StickerSet *sticker_set);
+ void update_sticker_set(StickerSet *sticker_set, const char *source);
- void load_sticker_sets(vector<int64> &&sticker_set_ids, Promise<Unit> &&promise);
+ void load_sticker_sets(vector<StickerSetId> &&sticker_set_ids, Promise<Unit> &&promise);
- void load_sticker_sets_without_stickers(vector<int64> &&sticker_set_ids, Promise<Unit> &&promise);
+ void load_sticker_sets_without_stickers(vector<StickerSetId> &&sticker_set_ids, Promise<Unit> &&promise);
- void on_load_sticker_set_from_database(int64 sticker_set_id, bool with_stickers, string value);
+ void on_load_sticker_set_from_database(StickerSetId sticker_set_id, bool with_stickers, string value);
void update_load_requests(StickerSet *sticker_set, bool with_stickers, const Status &status);
void update_load_request(uint32 load_request_id, const Status &status);
- void reload_sticker_set(int64 sticker_set_id, tl_object_ptr<telegram_api::InputStickerSet> &&input_sticker_set,
- Promise<Unit> &&promise) const;
+ void do_reload_sticker_set(StickerSetId sticker_set_id,
+ tl_object_ptr<telegram_api::InputStickerSet> &&input_sticker_set, int32 hash,
+ Promise<Unit> &&promise) const;
+
+ void do_get_premium_stickers(int32 limit, Promise<td_api::object_ptr<td_api::stickers>> &&promise);
static void read_featured_sticker_sets(void *td_void);
- int32 get_sticker_sets_hash(const vector<int64> &sticker_set_ids) const;
+ int64 get_sticker_sets_hash(const vector<StickerSetId> &sticker_set_ids) const;
+
+ int64 get_featured_sticker_sets_hash(StickerType sticker_type) const;
- int32 get_featured_sticker_sets_hash() const;
+ int64 get_recent_stickers_hash(const vector<FileId> &sticker_ids) const;
- int32 get_recent_stickers_hash(const vector<FileId> &sticker_ids) const;
+ void load_installed_sticker_sets(StickerType sticker_type, Promise<Unit> &&promise);
- void load_installed_sticker_sets(bool is_masks, Promise<Unit> &&promise);
+ void load_featured_sticker_sets(StickerType sticker_type, Promise<Unit> &&promise);
- void load_featured_sticker_sets(Promise<Unit> &&promise);
+ void load_old_featured_sticker_sets(StickerType sticker_type, Promise<Unit> &&promise);
void load_recent_stickers(bool is_attached, Promise<Unit> &&promise);
- void on_load_installed_sticker_sets_from_database(bool is_masks, string value);
+ void on_load_installed_sticker_sets_from_database(StickerType sticker_type, string value);
- void on_load_installed_sticker_sets_finished(bool is_masks, vector<int64> &&installed_sticker_set_ids,
+ void on_load_installed_sticker_sets_finished(StickerType sticker_type,
+ vector<StickerSetId> &&installed_sticker_set_ids,
bool from_database = false);
- void on_load_featured_sticker_sets_from_database(string value);
+ void on_load_featured_sticker_sets_from_database(StickerType sticker_type, string value);
- void on_load_featured_sticker_sets_finished(vector<int64> &&featured_sticker_set_ids);
+ void on_load_featured_sticker_sets_finished(StickerType sticker_type, vector<StickerSetId> &&featured_sticker_set_ids,
+ bool is_premium);
+
+ void on_load_old_featured_sticker_sets_from_database(StickerType sticker_type, uint32 generation, string value);
+
+ void on_load_old_featured_sticker_sets_finished(StickerType sticker_type, uint32 generation,
+ vector<StickerSetId> &&old_featured_sticker_set_ids);
void on_load_recent_stickers_from_database(bool is_attached, string value);
void on_load_recent_stickers_finished(bool is_attached, vector<FileId> &&recent_sticker_ids,
bool from_database = false);
+ td_api::object_ptr<td_api::updateInstalledStickerSets> get_update_installed_sticker_sets_object(
+ StickerType sticker_type) const;
+
void send_update_installed_sticker_sets(bool from_database = false);
- void send_update_featured_sticker_sets();
+ void reload_old_featured_sticker_sets(StickerType sticker_type, uint32 generation = 0);
- void send_update_recent_stickers(bool from_database = false);
+ void on_old_featured_sticker_sets_invalidated(StickerType sticker_type);
- void save_recent_stickers_to_database(bool is_attached);
+ void invalidate_old_featured_sticker_sets(StickerType sticker_type);
- void add_recent_sticker_inner(bool is_attached, FileId sticker_id, Promise<Unit> &&promise);
+ void set_old_featured_sticker_set_count(StickerType sticker_type, int32 count);
- bool add_recent_sticker_impl(bool is_attached, FileId sticker_id, Promise<Unit> &promise);
+ // must be called after every call to set_old_featured_sticker_set_count or
+ // any change of old_featured_sticker_set_ids_ size
+ void fix_old_featured_sticker_set_count(StickerType sticker_type);
- int32 get_favorite_stickers_hash() const;
+ static size_t get_max_featured_sticker_count(StickerType sticker_type);
- void add_favorite_sticker_inner(FileId sticker_id, Promise<Unit> &&promise);
+ static Slice get_featured_sticker_suffix(StickerType sticker_type);
- bool add_favorite_sticker_impl(FileId sticker_id, Promise<Unit> &promise);
+ td_api::object_ptr<td_api::trendingStickerSets> get_trending_sticker_sets_object(
+ StickerType sticker_type, const vector<StickerSetId> &sticker_set_ids) const;
+
+ td_api::object_ptr<td_api::updateTrendingStickerSets> get_update_trending_sticker_sets_object(
+ StickerType sticker_type) const;
+
+ void send_update_featured_sticker_sets(StickerType sticker_type);
+
+ td_api::object_ptr<td_api::updateRecentStickers> get_update_recent_stickers_object(int is_attached) const;
+
+ void send_update_recent_stickers(bool is_attached, bool from_database = false);
+
+ void save_recent_stickers_to_database(bool is_attached);
+
+ void add_recent_sticker_impl(bool is_attached, FileId sticker_id, bool add_on_server, Promise<Unit> &&promise);
+
+ int64 get_favorite_stickers_hash() const;
+
+ void add_favorite_sticker_impl(FileId sticker_id, bool add_on_server, Promise<Unit> &&promise);
void load_favorite_stickers(Promise<Unit> &&promise);
@@ -392,27 +770,29 @@ class StickersManager : public Actor {
void on_load_favorite_stickers_finished(vector<FileId> &&favorite_sticker_ids, bool from_database = false);
+ td_api::object_ptr<td_api::updateFavoriteStickers> get_update_favorite_stickers_object() const;
+
void send_update_favorite_stickers(bool from_database = false);
void save_favorite_stickers_to_database();
- template <class T>
- void store_sticker_set(const StickerSet *sticker_set, bool with_stickers, T &storer) const;
+ template <class StorerT>
+ void store_sticker_set(const StickerSet *sticker_set, bool with_stickers, StorerT &storer, const char *source) const;
- template <class T>
- void parse_sticker_set(StickerSet *sticker_set, T &parser);
+ template <class ParserT>
+ void parse_sticker_set(StickerSet *sticker_set, ParserT &parser);
- template <class T>
- void store_sticker_set_id(int64 sticker_set_id, T &storer) const;
+ std::pair<vector<FileId>, vector<FileId>> split_stickers_by_premium(const vector<FileId> &sticker_ids) const;
- template <class T>
- void parse_sticker_set_id(int64 &sticker_set_id, T &parser);
+ std::pair<vector<FileId>, vector<FileId>> split_stickers_by_premium(const StickerSet *sticker_set) const;
- Result<std::tuple<FileId, bool, bool>> prepare_input_file(const tl_object_ptr<td_api::InputFile> &input_file);
+ Result<std::tuple<FileId, bool, bool, StickerFormat>> prepare_input_file(
+ const tl_object_ptr<td_api::InputFile> &input_file, StickerFormat format, StickerType type, bool for_thumbnail);
- Result<std::tuple<FileId, bool, bool>> prepare_input_sticker(td_api::inputSticker *sticker);
+ Result<std::tuple<FileId, bool, bool, StickerFormat>> prepare_input_sticker(td_api::inputSticker *sticker,
+ StickerType sticker_type);
- tl_object_ptr<telegram_api::inputStickerSetItem> get_input_sticker(td_api::inputSticker *sticker,
+ tl_object_ptr<telegram_api::inputStickerSetItem> get_input_sticker(const td_api::inputSticker *sticker,
FileId file_id) const;
void upload_sticker_file(UserId user_id, FileId file_id, Promise<Unit> &&promise);
@@ -428,81 +808,326 @@ class StickersManager : public Actor {
void on_added_sticker_uploaded(int64 random_id, Result<Unit> result);
+ void do_add_sticker_to_set(UserId user_id, string short_name, tl_object_ptr<td_api::inputSticker> &&sticker,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise);
+
+ void on_sticker_set_thumbnail_uploaded(int64 random_id, Result<Unit> result);
+
+ void do_set_sticker_set_thumbnail(UserId user_id, string short_name, tl_object_ptr<td_api::InputFile> &&thumbnail,
+ Promise<td_api::object_ptr<td_api::stickerSet>> &&promise);
+
bool update_sticker_set_cache(const StickerSet *sticker_set, Promise<Unit> &promise);
- void tear_down() override;
+ const StickerSet *get_premium_gift_sticker_set();
+
+ static FileId get_premium_gift_option_sticker_id(const StickerSet *sticker_set, int32 month_count);
+
+ FileId get_premium_gift_option_sticker_id(int32 month_count);
+
+ void try_update_premium_gift_messages();
+
+ const StickerSet *get_animated_emoji_sticker_set();
+
+ static std::pair<FileId, int> get_animated_emoji_sticker(const StickerSet *sticker_set, const string &emoji);
+
+ std::pair<FileId, int> get_animated_emoji_sticker(const string &emoji);
+
+ FileId get_animated_emoji_sound_file_id(const string &emoji) const;
+
+ FileId get_custom_animated_emoji_sticker_id(CustomEmojiId custom_emoji_id) const;
+
+ td_api::object_ptr<td_api::animatedEmoji> get_animated_emoji_object(std::pair<FileId, int> animated_sticker,
+ FileId sound_file_id) const;
+
+ void try_update_animated_emoji_messages();
+
+ void try_update_custom_emoji_messages(CustomEmojiId custom_emoji_id);
+
+ static int get_emoji_number(Slice emoji);
+
+ vector<FileId> get_animated_emoji_click_stickers(const StickerSet *sticker_set, Slice emoji) const;
+
+ void choose_animated_emoji_click_sticker(const StickerSet *sticker_set, string message_text,
+ FullMessageId full_message_id, double start_time,
+ Promise<td_api::object_ptr<td_api::sticker>> &&promise);
+
+ void send_click_animated_emoji_message_response(FileId sticker_id,
+ Promise<td_api::object_ptr<td_api::sticker>> &&promise);
+
+ void flush_sent_animated_emoji_clicks();
+
+ void flush_pending_animated_emoji_clicks();
+
+ void schedule_update_animated_emoji_clicked(const StickerSet *sticker_set, Slice emoji, FullMessageId full_message_id,
+ vector<std::pair<int, double>> clicks);
+
+ void send_update_animated_emoji_clicked(FullMessageId full_message_id, FileId sticker_id);
+
+ td_api::object_ptr<td_api::updateDiceEmojis> get_update_dice_emojis_object() const;
+
+ void start_up() final;
+
+ void timeout_expired() final;
+
+ void tear_down() final;
+
+ void save_active_reactions();
+
+ void save_reactions();
+
+ void save_recent_reactions();
+
+ void save_top_reactions();
+
+ void load_active_reactions();
+
+ void load_reactions();
+
+ void load_recent_reactions();
+
+ void load_top_reactions();
+
+ void update_active_reactions();
- static string remove_emoji_modifiers(string emoji);
+ td_api::object_ptr<td_api::updateActiveEmojiReactions> get_update_active_emoji_reactions_object() const;
+
+ SpecialStickerSet &add_special_sticker_set(const SpecialStickerSetType &type);
+
+ static void init_special_sticker_set(SpecialStickerSet &sticker_set, int64 sticker_set_id, int64 access_hash,
+ string name);
+
+ void load_special_sticker_set_info_from_binlog(SpecialStickerSet &sticker_set);
+
+ void load_special_sticker_set_by_type(SpecialStickerSetType type);
+
+ void load_special_sticker_set(SpecialStickerSet &sticker_set);
+
+ void reload_special_sticker_set(SpecialStickerSet &sticker_set, int32 hash);
+
+ static void add_sticker_thumbnail(Sticker *s, PhotoSize thumbnail);
+
+ td_api::object_ptr<td_api::stickers> get_custom_emoji_stickers_object(const vector<CustomEmojiId> &custom_emoji_ids);
+
+ void on_get_custom_emoji_documents(Result<vector<telegram_api::object_ptr<telegram_api::Document>>> &&r_documents,
+ vector<CustomEmojiId> &&custom_emoji_ids,
+ Promise<td_api::object_ptr<td_api::stickers>> &&promise);
+
+ static const std::map<string, vector<FileId>> &get_sticker_set_keywords(const StickerSet *sticker_set);
+
+ static void find_sticker_set_stickers(const StickerSet *sticker_set, const string &query,
+ const string &prepared_query, vector<FileId> &result);
+
+ bool can_found_sticker_by_query(FileId sticker_id, const string &query, const string &prepared_query) const;
+
+ static string get_emoji_language_code_version_database_key(const string &language_code);
+
+ static string get_emoji_language_code_last_difference_time_database_key(const string &language_code);
+
+ static string get_language_emojis_database_key(const string &language_code, const string &text);
+
+ static string get_emoji_language_codes_database_key(const vector<string> &language_codes);
+
+ int32 get_emoji_language_code_version(const string &language_code);
+
+ double get_emoji_language_code_last_difference_time(const string &language_code);
+
+ vector<string> get_emoji_language_codes(const vector<string> &input_language_codes, Slice text,
+ Promise<Unit> &promise);
+
+ void load_language_codes(vector<string> language_codes, string key, Promise<Unit> &&promise);
+
+ void on_get_language_codes(const string &key, Result<vector<string>> &&result);
+
+ static vector<string> search_language_emojis(const string &language_code, const string &text, bool exact_match);
+
+ void load_emoji_keywords(const string &language_code, Promise<Unit> &&promise);
+
+ void on_get_emoji_keywords(const string &language_code,
+ Result<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> &&result);
+
+ void load_emoji_keywords_difference(const string &language_code);
+
+ void on_get_emoji_keywords_difference(
+ const string &language_code, int32 from_version,
+ Result<telegram_api::object_ptr<telegram_api::emojiKeywordsDifference>> &&result);
+
+ void finish_get_emoji_keywords_difference(string language_code, int32 version);
+
+ void on_get_emoji_suggestions_url(int64 random_id, Promise<Unit> &&promise,
+ Result<telegram_api::object_ptr<telegram_api::emojiURL>> &&r_emoji_url);
Td *td_;
ActorShared<> parent_;
- std::unordered_map<FileId, unique_ptr<Sticker>, FileIdHash> stickers_; // file_id -> Sticker
- std::unordered_map<int64, unique_ptr<StickerSet>> sticker_sets_; // id -> StickerSet
- std::unordered_map<string, int64> short_name_to_sticker_set_id_;
- vector<int64> installed_sticker_set_ids_[2];
- vector<int64> featured_sticker_set_ids_;
+ bool is_inited_ = false;
+
+ WaitFreeHashMap<FileId, unique_ptr<Sticker>, FileIdHash> stickers_; // file_id -> Sticker
+ WaitFreeHashMap<StickerSetId, unique_ptr<StickerSet>, StickerSetIdHash> sticker_sets_; // id -> StickerSet
+ WaitFreeHashMap<string, StickerSetId> short_name_to_sticker_set_id_;
+
+ vector<StickerSetId> installed_sticker_set_ids_[MAX_STICKER_TYPE];
+ vector<StickerSetId> featured_sticker_set_ids_[MAX_STICKER_TYPE];
+ vector<StickerSetId> old_featured_sticker_set_ids_[MAX_STICKER_TYPE];
vector<FileId> recent_sticker_ids_[2];
vector<FileId> favorite_sticker_ids_;
- double next_installed_sticker_sets_load_time_[2] = {0, 0};
- double next_featured_sticker_sets_load_time_ = 0;
+ double next_installed_sticker_sets_load_time_[MAX_STICKER_TYPE] = {0, 0, 0};
+ double next_featured_sticker_sets_load_time_[MAX_STICKER_TYPE] = {0, 0, 0};
double next_recent_stickers_load_time_[2] = {0, 0};
double next_favorite_stickers_load_time_ = 0;
- int32 installed_sticker_sets_hash_[2] = {0, 0};
- int32 featured_sticker_sets_hash_ = 0;
- int32 recent_stickers_hash_[2] = {0, 0};
+ int64 installed_sticker_sets_hash_[MAX_STICKER_TYPE] = {0, 0, 0};
+ int64 featured_sticker_sets_hash_[MAX_STICKER_TYPE] = {0, 0, 0};
+ int64 recent_stickers_hash_[2] = {0, 0};
- bool need_update_installed_sticker_sets_[2] = {false, false};
- bool need_update_featured_sticker_sets_ = false;
- bool need_update_recent_stickers_[2] = {false, false};
+ int32 old_featured_sticker_set_count_[MAX_STICKER_TYPE] = {-1, 0, 0};
+ uint32 old_featured_sticker_set_generation_[MAX_STICKER_TYPE] = {1, 0, 0};
- bool are_installed_sticker_sets_loaded_[2] = {false, false};
- bool are_featured_sticker_sets_loaded_ = false;
+ bool need_update_installed_sticker_sets_[MAX_STICKER_TYPE] = {false, false, false};
+ bool need_update_featured_sticker_sets_[MAX_STICKER_TYPE] = {false, false, false};
+
+ bool are_installed_sticker_sets_loaded_[MAX_STICKER_TYPE] = {false, false, false};
+ bool are_featured_sticker_sets_loaded_[MAX_STICKER_TYPE] = {false, true, false};
bool are_recent_stickers_loaded_[2] = {false, false};
bool are_favorite_stickers_loaded_ = false;
- vector<Promise<Unit>> load_installed_sticker_sets_queries_[2];
- vector<Promise<Unit>> load_featured_sticker_sets_queries_;
+ bool are_featured_sticker_sets_premium_[MAX_STICKER_TYPE] = {false, false, false};
+ bool are_old_featured_sticker_sets_invalidated_[MAX_STICKER_TYPE] = {false, false, false};
+
+ vector<Promise<Unit>> load_installed_sticker_sets_queries_[MAX_STICKER_TYPE];
+ vector<Promise<Unit>> load_featured_sticker_sets_queries_[MAX_STICKER_TYPE];
+ vector<Promise<Unit>> load_old_featured_sticker_sets_queries_;
vector<Promise<Unit>> load_recent_stickers_queries_[2];
+ vector<Promise<Unit>> repair_recent_stickers_queries_[2];
vector<Promise<Unit>> load_favorite_stickers_queries_;
+ vector<Promise<Unit>> repair_favorite_stickers_queries_;
+
+ vector<FileId> recent_sticker_file_ids_[2];
+ FileSourceId recent_stickers_file_source_id_[2];
+ vector<FileId> favorite_sticker_file_ids_;
+ FileSourceId favorite_stickers_file_source_id_;
+
+ FileSourceId app_config_file_source_id_;
- vector<int64> archived_sticker_set_ids_[2];
- int32 total_archived_sticker_set_count_[2] = {-1, -1};
+ vector<StickerSetId> archived_sticker_set_ids_[MAX_STICKER_TYPE];
+ int32 total_archived_sticker_set_count_[MAX_STICKER_TYPE] = {-1, -1, -1};
- std::unordered_map<FileId, vector<int64>, FileIdHash> attached_sticker_sets_;
+ FlatHashMap<FileId, vector<StickerSetId>, FileIdHash> attached_sticker_sets_;
- Hints installed_sticker_sets_hints_[2]; // search installed sticker sets by their title and name
+ Hints installed_sticker_sets_hints_[MAX_STICKER_TYPE]; // search installed sticker sets by their title and name
- std::unordered_map<string, vector<FileId>> found_stickers_;
- std::unordered_map<string, vector<Promise<Unit>>> search_stickers_queries_;
+ FlatHashMap<string, FoundStickers> found_stickers_;
+ FlatHashMap<string, vector<std::pair<int32, Promise<td_api::object_ptr<td_api::stickers>>>>> search_stickers_queries_;
- std::unordered_map<string, vector<int64>> found_sticker_sets_;
- std::unordered_map<string, vector<Promise<Unit>>> search_sticker_sets_queries_;
+ std::unordered_map<string, vector<StickerSetId>, Hash<string>> found_sticker_sets_;
+ std::unordered_map<string, vector<Promise<Unit>>, Hash<string>> search_sticker_sets_queries_;
- std::unordered_set<int64> pending_viewed_featured_sticker_set_ids_;
+ FlatHashSet<StickerSetId, StickerSetIdHash> pending_viewed_featured_sticker_set_ids_;
Timeout pending_featured_sticker_set_views_timeout_;
int32 recent_stickers_limit_ = 200;
int32 favorite_stickers_limit_ = 5;
+ FlatHashMap<SpecialStickerSetType, unique_ptr<SpecialStickerSet>, SpecialStickerSetTypeHash> special_sticker_sets_;
+
struct StickerSetLoadRequest {
- Promise<Unit> promise;
- Status error;
- size_t left_queries;
+ Promise<Unit> promise_;
+ Status error_;
+ size_t left_queries_ = 0;
};
- std::unordered_map<uint32, StickerSetLoadRequest> sticker_set_load_requests_;
+ FlatHashMap<uint32, StickerSetLoadRequest> sticker_set_load_requests_;
uint32 current_sticker_set_load_request_ = 0;
- std::unordered_map<int64, unique_ptr<PendingNewStickerSet>> pending_new_sticker_sets_;
+ FlatHashMap<CustomEmojiId, vector<Promise<Unit>>, CustomEmojiIdHash> custom_emoji_load_queries_;
+
+ FlatHashMap<int64, unique_ptr<PendingNewStickerSet>> pending_new_sticker_sets_;
+
+ FlatHashMap<int64, unique_ptr<PendingAddStickerToSet>> pending_add_sticker_to_sets_;
+
+ FlatHashMap<int64, unique_ptr<PendingSetStickerSetThumbnail>> pending_set_sticker_set_thumbnails_;
- std::unordered_map<int64, unique_ptr<PendingAddStickerToSet>> pending_add_sticker_to_sets_;
+ vector<Promise<Unit>> pending_get_animated_emoji_queries_;
+ vector<Promise<Unit>> pending_get_premium_gift_option_sticker_queries_;
+ vector<Promise<Unit>> pending_get_generic_animations_queries_;
+ vector<Promise<Unit>> pending_get_default_statuses_queries_;
+ vector<Promise<Unit>> pending_get_default_topic_icons_queries_;
+
+ vector<std::pair<string, Promise<td_api::object_ptr<td_api::emojiReaction>>>> pending_get_emoji_reaction_queries_;
+
+ double next_click_animated_emoji_message_time_ = 0;
+ double next_update_animated_emoji_clicked_time_ = 0;
+ vector<PendingGetAnimatedEmojiClickSticker> pending_get_animated_emoji_click_stickers_;
+ vector<PendingOnAnimatedEmojiClicked> pending_on_animated_emoji_message_clicked_;
+
+ string last_clicked_animated_emoji_;
+ FullMessageId last_clicked_animated_emoji_full_message_id_;
+ std::vector<std::pair<int, double>> pending_animated_emoji_clicks_;
+
+ struct SentAnimatedEmojiClicks {
+ double send_time_ = 0.0;
+ DialogId dialog_id_;
+ string emoji_;
+ };
+ std::vector<SentAnimatedEmojiClicks> sent_animated_emoji_clicks_;
std::shared_ptr<UploadStickerFileCallback> upload_sticker_file_callback_;
- std::unordered_map<FileId, std::pair<UserId, Promise<Unit>>, FileIdHash> being_uploaded_files_;
+ FlatHashMap<FileId, std::pair<UserId, Promise<Unit>>, FileIdHash> being_uploaded_files_;
+
+ Reactions reactions_;
+ vector<string> active_reactions_;
+
+ ReactionList recent_reactions_;
+ ReactionList top_reactions_;
+
+ bool are_reactions_loaded_from_database_ = false;
+ bool are_recent_reactions_loaded_from_database_ = false;
+ bool are_top_reactions_loaded_from_database_ = false;
+
+ FlatHashMap<string, vector<string>> emoji_language_codes_;
+ FlatHashMap<string, int32> emoji_language_code_versions_;
+ FlatHashMap<string, double> emoji_language_code_last_difference_times_;
+ FlatHashSet<string> reloaded_emoji_keywords_;
+ FlatHashMap<string, vector<Promise<Unit>>> load_emoji_keywords_queries_;
+ FlatHashMap<string, vector<Promise<Unit>>> load_language_codes_queries_;
+ FlatHashMap<int64, string> emoji_suggestions_urls_;
+
+ struct GiftPremiumMessages {
+ FlatHashSet<FullMessageId, FullMessageIdHash> full_message_ids_;
+ FileId sticker_id_;
+ };
+ FlatHashMap<int32, unique_ptr<GiftPremiumMessages>> premium_gift_messages_;
+
+ FlatHashMap<string, WaitFreeHashSet<FullMessageId, FullMessageIdHash>> dice_messages_;
+
+ struct EmojiMessages {
+ WaitFreeHashSet<FullMessageId, FullMessageIdHash> full_message_ids_;
+ std::pair<FileId, int> animated_emoji_sticker_;
+ FileId sound_file_id_;
+ };
+ FlatHashMap<string, unique_ptr<EmojiMessages>> emoji_messages_;
+
+ struct CustomEmojiMessages {
+ WaitFreeHashSet<FullMessageId, FullMessageIdHash> full_message_ids_;
+ FileId sticker_id_;
+ };
+ FlatHashMap<CustomEmojiId, unique_ptr<CustomEmojiMessages>, CustomEmojiIdHash> custom_emoji_messages_;
+
+ string dice_emojis_str_;
+ vector<string> dice_emojis_;
+
+ string dice_success_values_str_;
+ vector<std::pair<int32, int32>> dice_success_values_;
+
+ string emoji_sounds_str_;
+ FlatHashMap<string, FileId> emoji_sounds_;
+
+ WaitFreeHashMap<CustomEmojiId, FileId, CustomEmojiIdHash> custom_emoji_to_sticker_id_;
+
+ double animated_emoji_zoom_ = 0.0;
+
+ bool disable_animated_emojis_ = false;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StickersManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/StickersManager.hpp
index 2bdb3593a7..b9d17b676f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/StickersManager.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/StickersManager.hpp
@@ -1,124 +1,236 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/telegram/StickersManager.h"
#include "td/telegram/files/FileId.hpp"
#include "td/telegram/misc.h"
-#include "td/telegram/Photo.hpp"
+#include "td/telegram/PhotoSize.hpp"
+#include "td/telegram/StickerFormat.h"
+#include "td/telegram/StickersManager.h"
+#include "td/telegram/Td.h"
+#include "td/utils/emoji.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
#include "td/utils/tl_helpers.h"
+#include "td/utils/utf8.h"
namespace td {
-template <class T>
-void StickersManager::store_sticker(FileId file_id, bool in_sticker_set, T &storer) const {
- auto it = stickers_.find(file_id);
- CHECK(it != stickers_.end());
- const Sticker *sticker = it->second.get();
- bool has_sticker_set_access_hash = sticker->set_id != 0 && !in_sticker_set;
+template <class StorerT>
+void StickersManager::store_sticker(FileId file_id, bool in_sticker_set, StorerT &storer, const char *source) const {
+ const Sticker *sticker = get_sticker(file_id);
+ LOG_CHECK(sticker != nullptr) << file_id << ' ' << in_sticker_set << ' ' << source;
+ bool has_sticker_set_access_hash = sticker->set_id_.is_valid() && !in_sticker_set;
+ bool has_minithumbnail = !sticker->minithumbnail_.empty();
+ bool is_tgs = sticker->format_ == StickerFormat::Tgs;
+ bool is_webm = sticker->format_ == StickerFormat::Webm;
+ bool has_premium_animation = sticker->premium_animation_file_id_.is_valid();
+ bool is_mask = sticker->type_ == StickerType::Mask;
+ bool is_emoji = sticker->type_ == StickerType::CustomEmoji;
+ bool has_emoji_receive_date = is_emoji && sticker->emoji_receive_date_ != 0;
BEGIN_STORE_FLAGS();
- STORE_FLAG(sticker->is_mask);
+ STORE_FLAG(is_mask);
STORE_FLAG(has_sticker_set_access_hash);
STORE_FLAG(in_sticker_set);
+ STORE_FLAG(is_tgs);
+ STORE_FLAG(has_minithumbnail);
+ STORE_FLAG(is_webm);
+ STORE_FLAG(has_premium_animation);
+ STORE_FLAG(is_emoji);
+ STORE_FLAG(sticker->is_premium_);
+ STORE_FLAG(has_emoji_receive_date);
END_STORE_FLAGS();
if (!in_sticker_set) {
- store(sticker->set_id, storer);
+ store(sticker->set_id_.get(), storer);
if (has_sticker_set_access_hash) {
- auto sticker_set = get_sticker_set(sticker->set_id);
+ auto sticker_set = get_sticker_set(sticker->set_id_);
CHECK(sticker_set != nullptr);
- store(sticker_set->access_hash, storer);
+ store(sticker_set->access_hash_, storer);
}
}
- store(sticker->alt, storer);
- store(sticker->dimensions, storer);
- store(sticker->message_thumbnail, storer);
- store(sticker->sticker_thumbnail, storer);
+ store(sticker->alt_, storer);
+ store(sticker->dimensions_, storer);
+ store(sticker->s_thumbnail_, storer);
+ store(sticker->m_thumbnail_, storer);
store(file_id, storer);
- if (sticker->is_mask) {
- store(sticker->point, storer);
- store(sticker->x_shift, storer);
- store(sticker->y_shift, storer);
- store(sticker->scale, storer);
+ if (is_mask) {
+ store(sticker->point_, storer);
+ store(sticker->x_shift_, storer);
+ store(sticker->y_shift_, storer);
+ store(sticker->scale_, storer);
+ }
+ if (has_minithumbnail) {
+ store(sticker->minithumbnail_, storer);
+ }
+ if (has_premium_animation) {
+ store(sticker->premium_animation_file_id_, storer);
+ }
+ if (has_emoji_receive_date) {
+ store(sticker->emoji_receive_date_, storer);
}
}
-template <class T>
-FileId StickersManager::parse_sticker(bool in_sticker_set, T &parser) {
+template <class ParserT>
+FileId StickersManager::parse_sticker(bool in_sticker_set, ParserT &parser) {
+ if (parser.get_error() != nullptr) {
+ return FileId();
+ }
+
auto sticker = make_unique<Sticker>();
bool has_sticker_set_access_hash;
bool in_sticker_set_stored;
+ bool has_minithumbnail;
+ bool is_tgs;
+ bool is_webm;
+ bool has_premium_animation;
+ bool is_mask;
+ bool is_emoji;
+ bool has_emoji_receive_date;
BEGIN_PARSE_FLAGS();
- PARSE_FLAG(sticker->is_mask);
+ PARSE_FLAG(is_mask);
PARSE_FLAG(has_sticker_set_access_hash);
PARSE_FLAG(in_sticker_set_stored);
+ PARSE_FLAG(is_tgs);
+ PARSE_FLAG(has_minithumbnail);
+ PARSE_FLAG(is_webm);
+ PARSE_FLAG(has_premium_animation);
+ PARSE_FLAG(is_emoji);
+ PARSE_FLAG(sticker->is_premium_);
+ PARSE_FLAG(has_emoji_receive_date);
END_PARSE_FLAGS();
- CHECK(in_sticker_set_stored == in_sticker_set) << in_sticker_set << " " << in_sticker_set_stored;
+ if (is_webm) {
+ sticker->format_ = StickerFormat::Webm;
+ } else if (is_tgs) {
+ sticker->format_ = StickerFormat::Tgs;
+ } else {
+ sticker->format_ = StickerFormat::Webp;
+ }
+ sticker->type_ = ::td::get_sticker_type(is_mask, is_emoji);
+ if (in_sticker_set_stored != in_sticker_set) {
+ Slice data = parser.template fetch_string_raw<Slice>(parser.get_left_len());
+ for (auto c : data) {
+ if (c != '\0') {
+ parser.set_error("Invalid sticker set is stored in the database");
+ break;
+ }
+ }
+ parser.set_error("Zero sticker set is stored in the database");
+ return FileId();
+ }
if (!in_sticker_set) {
- parse(sticker->set_id, parser);
+ int64 set_id;
+ parse(set_id, parser);
+ sticker->set_id_ = StickerSetId(set_id);
if (has_sticker_set_access_hash) {
int64 sticker_set_access_hash;
parse(sticker_set_access_hash, parser);
- add_sticker_set(sticker->set_id, sticker_set_access_hash);
+ add_sticker_set(sticker->set_id_, sticker_set_access_hash);
} else {
// backward compatibility
- sticker->set_id = 0;
+ sticker->set_id_ = StickerSetId();
}
}
- parse(sticker->alt, parser);
- parse(sticker->dimensions, parser);
- parse(sticker->message_thumbnail, parser);
- parse(sticker->sticker_thumbnail, parser);
- parse(sticker->file_id, parser);
- if (sticker->is_mask) {
- parse(sticker->point, parser);
- parse(sticker->x_shift, parser);
- parse(sticker->y_shift, parser);
- parse(sticker->scale, parser);
+ parse(sticker->alt_, parser);
+ parse(sticker->dimensions_, parser);
+ PhotoSize thumbnail;
+ parse(thumbnail, parser);
+ add_sticker_thumbnail(sticker.get(), thumbnail);
+ parse(thumbnail, parser);
+ add_sticker_thumbnail(sticker.get(), thumbnail);
+ parse(sticker->file_id_, parser);
+ if (is_mask) {
+ parse(sticker->point_, parser);
+ parse(sticker->x_shift_, parser);
+ parse(sticker->y_shift_, parser);
+ parse(sticker->scale_, parser);
+ }
+ if (has_minithumbnail) {
+ parse(sticker->minithumbnail_, parser);
+ }
+ if (has_premium_animation) {
+ sticker->is_premium_ = true;
+ parse(sticker->premium_animation_file_id_, parser);
}
- return on_get_sticker(std::move(sticker), true);
+ if (has_emoji_receive_date) {
+ parse(sticker->emoji_receive_date_, parser);
+ }
+
+ if (parser.get_error() != nullptr || !sticker->file_id_.is_valid()) {
+ return FileId();
+ }
+ sticker->is_from_database_ = true;
+ return on_get_sticker(std::move(sticker), false); // data in the database is always outdated
}
-template <class T>
-void StickersManager::store_sticker_set(const StickerSet *sticker_set, bool with_stickers, T &storer) const {
- size_t stickers_limit = with_stickers ? sticker_set->sticker_ids.size() : 5;
- bool is_full = sticker_set->sticker_ids.size() <= stickers_limit;
- bool was_loaded = sticker_set->was_loaded && is_full;
- bool is_loaded = sticker_set->is_loaded && is_full;
- bool has_expires_at = !sticker_set->is_installed && sticker_set->expires_at != 0;
+template <class StorerT>
+void StickersManager::store_sticker_set(const StickerSet *sticker_set, bool with_stickers, StorerT &storer,
+ const char *source) const {
+ size_t stickers_limit =
+ with_stickers ? sticker_set->sticker_ids_.size() : get_max_featured_sticker_count(sticker_set->sticker_type_);
+ bool is_full = sticker_set->sticker_ids_.size() <= stickers_limit;
+ bool was_loaded = sticker_set->was_loaded_ && is_full;
+ bool is_loaded = sticker_set->is_loaded_ && is_full;
+ bool has_expires_at = !sticker_set->is_installed_ && sticker_set->expires_at_ != 0;
+ bool has_thumbnail = sticker_set->thumbnail_.file_id.is_valid();
+ bool has_minithumbnail = !sticker_set->minithumbnail_.empty();
+ bool is_tgs = sticker_set->sticker_format_ == StickerFormat::Tgs;
+ bool is_webm = sticker_set->sticker_format_ == StickerFormat::Webm;
+ bool is_masks = sticker_set->sticker_type_ == StickerType::Mask;
+ bool is_emojis = sticker_set->sticker_type_ == StickerType::CustomEmoji;
+ bool has_thumbnail_document_id = sticker_set->thumbnail_document_id_ != 0;
BEGIN_STORE_FLAGS();
- STORE_FLAG(sticker_set->is_inited);
+ STORE_FLAG(sticker_set->is_inited_);
STORE_FLAG(was_loaded);
STORE_FLAG(is_loaded);
- STORE_FLAG(sticker_set->is_installed);
- STORE_FLAG(sticker_set->is_archived);
- STORE_FLAG(sticker_set->is_official);
- STORE_FLAG(sticker_set->is_masks);
- STORE_FLAG(sticker_set->is_viewed);
+ STORE_FLAG(sticker_set->is_installed_);
+ STORE_FLAG(sticker_set->is_archived_);
+ STORE_FLAG(sticker_set->is_official_);
+ STORE_FLAG(is_masks);
+ STORE_FLAG(sticker_set->is_viewed_);
STORE_FLAG(has_expires_at);
+ STORE_FLAG(has_thumbnail);
+ STORE_FLAG(sticker_set->is_thumbnail_reloaded_);
+ STORE_FLAG(is_tgs);
+ STORE_FLAG(sticker_set->are_legacy_sticker_thumbnails_reloaded_);
+ STORE_FLAG(has_minithumbnail);
+ STORE_FLAG(is_webm);
+ STORE_FLAG(is_emojis);
+ STORE_FLAG(has_thumbnail_document_id);
+ STORE_FLAG(sticker_set->are_keywords_loaded_);
END_STORE_FLAGS();
- store(sticker_set->id, storer);
- store(sticker_set->access_hash, storer);
- if (sticker_set->is_inited) {
- store(sticker_set->title, storer);
- store(sticker_set->short_name, storer);
- store(sticker_set->sticker_count, storer);
- store(sticker_set->hash, storer);
+ store(sticker_set->id_.get(), storer);
+ store(sticker_set->access_hash_, storer);
+ if (sticker_set->is_inited_) {
+ store(sticker_set->title_, storer);
+ store(sticker_set->short_name_, storer);
+ store(sticker_set->sticker_count_, storer);
+ store(sticker_set->hash_, storer);
if (has_expires_at) {
- store(sticker_set->expires_at, storer);
+ store(sticker_set->expires_at_, storer);
+ }
+ if (has_thumbnail) {
+ store(sticker_set->thumbnail_, storer);
+ }
+ if (has_minithumbnail) {
+ store(sticker_set->minithumbnail_, storer);
+ }
+ if (has_thumbnail_document_id) {
+ store(sticker_set->thumbnail_document_id_, storer);
}
- uint32 stored_sticker_count = narrow_cast<uint32>(is_full ? sticker_set->sticker_ids.size() : stickers_limit);
+ auto stored_sticker_count = narrow_cast<uint32>(is_full ? sticker_set->sticker_ids_.size() : stickers_limit);
store(stored_sticker_count, storer);
for (uint32 i = 0; i < stored_sticker_count; i++) {
- auto sticker_id = sticker_set->sticker_ids[i];
- store_sticker(sticker_id, true, storer);
+ auto sticker_id = sticker_set->sticker_ids_[i];
+ store_sticker(sticker_id, true, storer, source);
if (was_loaded) {
auto it = sticker_set->sticker_emojis_map_.find(sticker_id);
@@ -128,44 +240,77 @@ void StickersManager::store_sticker_set(const StickerSet *sticker_set, bool with
store(vector<string>(), storer);
}
}
+ if (sticker_set->are_keywords_loaded_) {
+ auto it = sticker_set->sticker_keywords_map_.find(sticker_id);
+ if (it != sticker_set->sticker_keywords_map_.end()) {
+ store(it->second, storer);
+ } else {
+ store(vector<string>(), storer);
+ }
+ }
}
}
}
-template <class T>
-void StickersManager::parse_sticker_set(StickerSet *sticker_set, T &parser) {
+template <class ParserT>
+void StickersManager::parse_sticker_set(StickerSet *sticker_set, ParserT &parser) {
CHECK(sticker_set != nullptr);
- CHECK(!sticker_set->was_loaded);
- bool was_inited = sticker_set->is_inited;
+ CHECK(!sticker_set->was_loaded_);
+ bool was_inited = sticker_set->is_inited_;
bool is_installed;
bool is_archived;
bool is_official;
bool is_masks;
bool has_expires_at;
+ bool has_thumbnail;
+ bool is_tgs;
+ bool has_minithumbnail;
+ bool is_webm;
+ bool is_emojis;
+ bool has_thumbnail_document_id;
BEGIN_PARSE_FLAGS();
- PARSE_FLAG(sticker_set->is_inited);
- PARSE_FLAG(sticker_set->was_loaded);
- PARSE_FLAG(sticker_set->is_loaded);
+ PARSE_FLAG(sticker_set->is_inited_);
+ PARSE_FLAG(sticker_set->was_loaded_);
+ PARSE_FLAG(sticker_set->is_loaded_);
PARSE_FLAG(is_installed);
PARSE_FLAG(is_archived);
PARSE_FLAG(is_official);
PARSE_FLAG(is_masks);
- PARSE_FLAG(sticker_set->is_viewed);
+ PARSE_FLAG(sticker_set->is_viewed_);
PARSE_FLAG(has_expires_at);
+ PARSE_FLAG(has_thumbnail);
+ PARSE_FLAG(sticker_set->is_thumbnail_reloaded_);
+ PARSE_FLAG(is_tgs);
+ PARSE_FLAG(sticker_set->are_legacy_sticker_thumbnails_reloaded_);
+ PARSE_FLAG(has_minithumbnail);
+ PARSE_FLAG(is_webm);
+ PARSE_FLAG(is_emojis);
+ PARSE_FLAG(has_thumbnail_document_id);
+ PARSE_FLAG(sticker_set->are_keywords_loaded_);
END_PARSE_FLAGS();
int64 sticker_set_id;
int64 access_hash;
parse(sticker_set_id, parser);
parse(access_hash, parser);
- CHECK(sticker_set->id == sticker_set_id);
- if (sticker_set->access_hash != access_hash) {
- LOG(ERROR) << "Sticker set " << sticker_set_id << " access hash has changed from " << access_hash << " to "
- << sticker_set->access_hash;
+ CHECK(sticker_set->id_.get() == sticker_set_id);
+ (void)access_hash; // unused, because only known sticker sets with access hash can be loaded from database
+
+ StickerFormat sticker_format = StickerFormat::Unknown;
+ if (is_webm) {
+ sticker_format = StickerFormat::Webm;
+ } else if (is_tgs) {
+ sticker_format = StickerFormat::Tgs;
+ } else {
+ sticker_format = StickerFormat::Webp;
}
+ auto sticker_type = ::td::get_sticker_type(is_masks, is_emojis);
- if (sticker_set->is_inited) {
+ if (sticker_set->is_inited_) {
string title;
string short_name;
+ string minithumbnail;
+ PhotoSize thumbnail;
+ int64 thumbnail_document_id = 0;
int32 sticker_count;
int32 hash;
int32 expires_at = 0;
@@ -176,79 +321,242 @@ void StickersManager::parse_sticker_set(StickerSet *sticker_set, T &parser) {
if (has_expires_at) {
parse(expires_at, parser);
}
+ if (has_thumbnail) {
+ parse(thumbnail, parser);
+ }
+ if (has_minithumbnail) {
+ parse(minithumbnail, parser);
+ }
+ if (has_thumbnail_document_id) {
+ parse(thumbnail_document_id, parser);
+ }
+
if (!was_inited) {
- sticker_set->title = std::move(title);
- sticker_set->short_name = std::move(short_name);
- sticker_set->sticker_count = sticker_count;
- sticker_set->hash = hash;
- sticker_set->expires_at = expires_at;
- sticker_set->is_official = is_official;
- sticker_set->is_masks = is_masks;
-
- short_name_to_sticker_set_id_.emplace(clean_username(sticker_set->short_name), sticker_set_id);
+ sticker_set->title_ = std::move(title);
+ sticker_set->short_name_ = std::move(short_name);
+ sticker_set->minithumbnail_ = std::move(minithumbnail);
+ sticker_set->thumbnail_ = std::move(thumbnail);
+ sticker_set->thumbnail_document_id_ = thumbnail_document_id;
+ sticker_set->sticker_count_ = sticker_count;
+ sticker_set->hash_ = hash;
+ sticker_set->expires_at_ = expires_at;
+ sticker_set->is_official_ = is_official;
+ sticker_set->sticker_type_ = sticker_type;
+ sticker_set->sticker_format_ = sticker_format;
+
+ auto cleaned_username = clean_username(sticker_set->short_name_);
+ if (!cleaned_username.empty()) {
+ short_name_to_sticker_set_id_.set(cleaned_username, sticker_set->id_);
+ }
on_update_sticker_set(sticker_set, is_installed, is_archived, false, true);
} else {
- if (sticker_set->title != title) {
- LOG(INFO) << "Sticker set " << sticker_set_id << " title has changed";
+ if (sticker_set->title_ != title) {
+ LOG(INFO) << "Title of " << sticker_set->id_ << " has changed";
+ }
+ if (sticker_set->short_name_ != short_name) {
+ LOG(ERROR) << "Short name of " << sticker_set->id_ << " has changed from \"" << short_name << "\" to \""
+ << sticker_set->short_name_ << "\"";
}
- if (sticker_set->short_name != short_name) {
- LOG(ERROR) << "Sticker set " << sticker_set_id << " short name has changed from \"" << short_name << "\" to \""
- << sticker_set->short_name << "\"";
+ if (sticker_set->sticker_count_ != sticker_count || sticker_set->hash_ != hash) {
+ sticker_set->is_loaded_ = false;
}
- if (sticker_set->sticker_count != sticker_count || sticker_set->hash != hash) {
- sticker_set->is_loaded = false;
+ if (sticker_set->sticker_format_ != sticker_format) {
+ LOG(ERROR) << "Sticker format of " << sticker_set->id_ << " has changed from \"" << sticker_format << "\" to \""
+ << sticker_set->sticker_format_ << "\"";
+ }
+ if (sticker_set->sticker_type_ != sticker_type) {
+ LOG(ERROR) << "Type of " << sticker_set->id_ << " has changed from \"" << sticker_type << "\" to \""
+ << sticker_set->sticker_type_ << "\"";
}
}
uint32 stored_sticker_count;
parse(stored_sticker_count, parser);
- sticker_set->sticker_ids.clear();
- if (sticker_set->was_loaded) {
+ sticker_set->sticker_ids_.clear();
+ sticker_set->premium_sticker_positions_.clear();
+ if (sticker_set->was_loaded_) {
sticker_set->emoji_stickers_map_.clear();
sticker_set->sticker_emojis_map_.clear();
+ sticker_set->keyword_stickers_map_.clear();
+ sticker_set->sticker_keywords_map_.clear();
}
for (uint32 i = 0; i < stored_sticker_count; i++) {
auto sticker_id = parse_sticker(true, parser);
- sticker_set->sticker_ids.push_back(sticker_id);
+ if (parser.get_error() != nullptr) {
+ return;
+ }
+ if (!sticker_id.is_valid()) {
+ return parser.set_error("Receive invalid sticker in a sticker set");
+ }
+ sticker_set->sticker_ids_.push_back(sticker_id);
Sticker *sticker = get_sticker(sticker_id);
CHECK(sticker != nullptr);
- if (sticker->set_id != sticker_set->id) {
- LOG_IF(ERROR, sticker->set_id != 0) << "Sticker " << sticker_id << " set_id has changed";
- sticker->set_id = sticker_set->id;
- sticker->is_changed = true;
+ if (sticker->set_id_ != sticker_set->id_) {
+ LOG_IF(ERROR, sticker->set_id_.is_valid()) << "Sticker " << sticker_id << " set_id has changed";
+ sticker->set_id_ = sticker_set->id_;
+ }
+ if (sticker->is_premium_) {
+ sticker_set->premium_sticker_positions_.push_back(static_cast<int32>(sticker_set->sticker_ids_.size() - 1));
}
- if (sticker_set->was_loaded) {
+ if (sticker_set->was_loaded_) {
vector<string> emojis;
parse(emojis, parser);
for (auto &emoji : emojis) {
- sticker_set->emoji_stickers_map_[remove_emoji_modifiers(emoji)].push_back(sticker_id);
+ auto cleaned_emoji = remove_emoji_modifiers(emoji);
+ if (!cleaned_emoji.empty()) {
+ auto &sticker_ids = sticker_set->emoji_stickers_map_[cleaned_emoji];
+ if (sticker_ids.empty() || sticker_ids.back() != sticker_id) {
+ sticker_ids.push_back(sticker_id);
+ }
+ } else {
+ LOG(INFO) << "Sticker " << sticker_id << " in " << sticker_set_id << '/' << sticker_set->short_name_
+ << " has an empty emoji";
+ }
}
sticker_set->sticker_emojis_map_[sticker_id] = std::move(emojis);
}
+ if (sticker_set->are_keywords_loaded_) {
+ vector<string> keywords;
+ parse(keywords, parser);
+ if (!keywords.empty()) {
+ sticker_set->sticker_keywords_map_.emplace(sticker_id, std::move(keywords));
+ }
+ }
}
- if (expires_at > sticker_set->expires_at) {
- sticker_set->expires_at = expires_at;
+ if (expires_at > sticker_set->expires_at_) {
+ sticker_set->expires_at_ = expires_at;
+ }
+
+ if (!check_utf8(sticker_set->title_)) {
+ return parser.set_error("Have invalid sticker set title");
+ }
+ if (!check_utf8(sticker_set->short_name_)) {
+ return parser.set_error("Have invalid sticker set name");
}
}
}
-template <class T>
-void StickersManager::store_sticker_set_id(int64 sticker_set_id, T &storer) const {
- CHECK(sticker_set_id != 0);
+template <class StorerT>
+void StickersManager::store_sticker_set_id(StickerSetId sticker_set_id, StorerT &storer) const {
+ CHECK(sticker_set_id.is_valid());
const StickerSet *sticker_set = get_sticker_set(sticker_set_id);
CHECK(sticker_set != nullptr);
- store(sticker_set_id, storer);
- store(sticker_set->access_hash, storer);
+ store(sticker_set_id.get(), storer);
+ store(sticker_set->access_hash_, storer);
}
-template <class T>
-void StickersManager::parse_sticker_set_id(int64 &sticker_set_id, T &parser) {
- parse(sticker_set_id, parser);
+template <class ParserT>
+void StickersManager::parse_sticker_set_id(StickerSetId &sticker_set_id, ParserT &parser) {
+ int64 set_id;
+ parse(set_id, parser);
+ sticker_set_id = StickerSetId(set_id);
int64 sticker_set_access_hash;
parse(sticker_set_access_hash, parser);
add_sticker_set(sticker_set_id, sticker_set_access_hash);
}
+template <class StorerT>
+void StickersManager::Reaction::store(StorerT &storer) const {
+ StickersManager *stickers_manager = storer.context()->td().get_actor_unsafe()->stickers_manager_.get();
+ bool has_around_animation = !around_animation_.empty();
+ bool has_center_animation = !center_animation_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_active_);
+ STORE_FLAG(has_around_animation);
+ STORE_FLAG(has_center_animation);
+ STORE_FLAG(is_premium_);
+ END_STORE_FLAGS();
+ td::store(reaction_, storer);
+ td::store(title_, storer);
+ stickers_manager->store_sticker(static_icon_, false, storer, "Reaction");
+ stickers_manager->store_sticker(appear_animation_, false, storer, "Reaction");
+ stickers_manager->store_sticker(select_animation_, false, storer, "Reaction");
+ stickers_manager->store_sticker(activate_animation_, false, storer, "Reaction");
+ stickers_manager->store_sticker(effect_animation_, false, storer, "Reaction");
+ if (has_around_animation) {
+ stickers_manager->store_sticker(around_animation_, false, storer, "Reaction");
+ }
+ if (has_center_animation) {
+ stickers_manager->store_sticker(center_animation_, false, storer, "Reaction");
+ }
+}
+
+template <class ParserT>
+void StickersManager::Reaction::parse(ParserT &parser) {
+ StickersManager *stickers_manager = parser.context()->td().get_actor_unsafe()->stickers_manager_.get();
+ bool has_around_animation;
+ bool has_center_animation;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_active_);
+ PARSE_FLAG(has_around_animation);
+ PARSE_FLAG(has_center_animation);
+ PARSE_FLAG(is_premium_);
+ END_PARSE_FLAGS();
+ td::parse(reaction_, parser);
+ td::parse(title_, parser);
+ static_icon_ = stickers_manager->parse_sticker(false, parser);
+ appear_animation_ = stickers_manager->parse_sticker(false, parser);
+ select_animation_ = stickers_manager->parse_sticker(false, parser);
+ activate_animation_ = stickers_manager->parse_sticker(false, parser);
+ effect_animation_ = stickers_manager->parse_sticker(false, parser);
+ if (has_around_animation) {
+ around_animation_ = stickers_manager->parse_sticker(false, parser);
+ }
+ if (has_center_animation) {
+ center_animation_ = stickers_manager->parse_sticker(false, parser);
+ }
+
+ is_premium_ = false;
+}
+
+template <class StorerT>
+void StickersManager::Reactions::store(StorerT &storer) const {
+ bool has_reactions = !reactions_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_reactions);
+ END_STORE_FLAGS();
+ if (has_reactions) {
+ td::store(reactions_, storer);
+ td::store(hash_, storer);
+ }
+}
+
+template <class ParserT>
+void StickersManager::Reactions::parse(ParserT &parser) {
+ bool has_reactions;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_reactions);
+ END_PARSE_FLAGS();
+ if (has_reactions) {
+ td::parse(reactions_, parser);
+ td::parse(hash_, parser);
+ }
+}
+
+template <class StorerT>
+void StickersManager::ReactionList::store(StorerT &storer) const {
+ bool has_reactions = !reactions_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_reactions);
+ END_STORE_FLAGS();
+ if (has_reactions) {
+ td::store(reactions_, storer);
+ td::store(hash_, storer);
+ }
+}
+
+template <class ParserT>
+void StickersManager::ReactionList::parse(ParserT &parser) {
+ bool has_reactions;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_reactions);
+ END_PARSE_FLAGS();
+ if (has_reactions) {
+ td::parse(reactions_, parser);
+ td::parse(hash_, parser);
+ }
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StorageManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/StorageManager.cpp
index de68743e6c..946e244359 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/StorageManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/StorageManager.cpp
@@ -1,19 +1,23 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/StorageManager.h"
-#include "td/telegram/ConfigShared.h"
#include "td/telegram/DialogId.h"
#include "td/telegram/files/FileGcWorker.h"
#include "td/telegram/files/FileStatsWorker.h"
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/MessagesManager.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/db/SqliteDb.h"
+
+#include "td/utils/algorithm.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/Clocks.h"
@@ -23,6 +27,11 @@
#include "td/utils/Time.h"
namespace td {
+
+tl_object_ptr<td_api::databaseStatistics> DatabaseStats::get_database_statistics_object() const {
+ return make_tl_object<td_api::databaseStatistics>(debug);
+}
+
StorageManager::StorageManager(ActorShared<> parent, int32 scheduler_id)
: parent_(std::move(parent)), scheduler_id_(scheduler_id) {
}
@@ -33,154 +42,225 @@ void StorageManager::start_up() {
load_fast_stat();
}
-void StorageManager::on_new_file(int64 size) {
- if (size > 0) {
- fast_stat_.cnt++;
- } else {
- fast_stat_.cnt--;
- }
- fast_stat_.size += size;
+
+void StorageManager::on_new_file(int64 size, int64 real_size, int32 cnt) {
+ LOG(INFO) << "Add " << cnt << " file of size " << size << " with real size " << real_size
+ << " to fast storage statistics";
+ fast_stat_.cnt += cnt;
+#if TD_WINDOWS
+ auto add_size = size;
+#else
+ auto add_size = real_size;
+#endif
+ fast_stat_.size += add_size;
if (fast_stat_.cnt < 0 || fast_stat_.size < 0) {
- LOG(ERROR) << "Wrong fast stat after adding size " << size;
+ LOG(ERROR) << "Wrong fast stat after adding size " << add_size << " and cnt " << cnt;
fast_stat_ = FileTypeStat();
}
save_fast_stat();
}
-void StorageManager::get_storage_stats(int32 dialog_limit, Promise<FileStats> promise) {
- if (pending_storage_stats_.size() != 0) {
- promise.set_error(Status::Error(400, "Another storage stats is active"));
- return;
+
+void StorageManager::get_storage_stats(bool need_all_files, int32 dialog_limit, Promise<FileStats> promise) {
+ if (is_closed_) {
+ return promise.set_error(Global::request_aborted_error());
+ }
+ if (!pending_storage_stats_.empty()) {
+ if (stats_dialog_limit_ == dialog_limit && need_all_files == stats_need_all_files_) {
+ pending_storage_stats_.emplace_back(std::move(promise));
+ return;
+ }
+ //TODO group same queries
+ close_stats_worker();
+ }
+ if (!pending_run_gc_[0].empty() || !pending_run_gc_[1].empty()) {
+ close_gc_worker();
}
stats_dialog_limit_ = dialog_limit;
+ stats_need_all_files_ = need_all_files;
pending_storage_stats_.emplace_back(std::move(promise));
create_stats_worker();
- send_closure(stats_worker_, &FileStatsWorker::get_stats, false /*need_all_files*/, stats_dialog_limit_ != 0,
- PromiseCreator::lambda([actor_id = actor_id(this)](Result<FileStats> file_stats) {
- send_closure(actor_id, &StorageManager::on_file_stats, std::move(file_stats), false);
- }));
+ send_closure(stats_worker_, &FileStatsWorker::get_stats, need_all_files, stats_dialog_limit_ != 0,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), stats_generation = stats_generation_](Result<FileStats> file_stats) {
+ send_closure(actor_id, &StorageManager::on_file_stats, std::move(file_stats), stats_generation);
+ }));
}
void StorageManager::get_storage_stats_fast(Promise<FileStatsFast> promise) {
- promise.set_value(FileStatsFast(fast_stat_.size, fast_stat_.cnt, get_db_size()));
+ promise.set_value(FileStatsFast(fast_stat_.size, fast_stat_.cnt, get_database_size(),
+ get_language_pack_database_size(), get_log_size()));
+}
+
+void StorageManager::get_database_stats(Promise<DatabaseStats> promise) {
+ //TODO: use another thread
+ auto r_stats = G()->td_db()->get_stats();
+ if (r_stats.is_error()) {
+ promise.set_error(r_stats.move_as_error());
+ } else {
+ promise.set_value(DatabaseStats(r_stats.move_as_ok()));
+ }
}
void StorageManager::update_use_storage_optimizer() {
schedule_next_gc();
}
-void StorageManager::run_gc(FileGcParameters parameters, Promise<FileStats> promise) {
- if (pending_run_gc_.size() != 0) {
- promise.set_error(Status::Error(400, "Another gc is active"));
- return;
+void StorageManager::run_gc(FileGcParameters parameters, bool return_deleted_file_statistics,
+ Promise<FileStats> promise) {
+ if (is_closed_) {
+ return promise.set_error(Global::request_aborted_error());
}
-
- pending_run_gc_.emplace_back(std::move(promise));
- if (pending_run_gc_.size() > 1) {
- return;
+ if (!pending_run_gc_[0].empty() || !pending_run_gc_[1].empty()) {
+ close_gc_worker();
}
- gc_parameters_ = std::move(parameters);
-
- create_stats_worker();
- send_closure(stats_worker_, &FileStatsWorker::get_stats, true /*need_all_file*/,
- !gc_parameters_.owner_dialog_ids.empty() || !gc_parameters_.exclude_owner_dialog_ids.empty() ||
- gc_parameters_.dialog_limit != 0 /*split_by_owner_dialog_id*/,
- PromiseCreator::lambda([actor_id = actor_id(this)](Result<FileStats> file_stats) {
- send_closure(actor_id, &StorageManager::on_all_files, std::move(file_stats), false);
- }));
+ bool split_by_owner_dialog_id = !parameters.owner_dialog_ids_.empty() ||
+ !parameters.exclude_owner_dialog_ids_.empty() || parameters.dialog_limit_ != 0;
+ get_storage_stats(
+ true /*need_all_files*/, split_by_owner_dialog_id,
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this), parameters = std::move(parameters)](Result<FileStats> file_stats) mutable {
+ send_closure(actor_id, &StorageManager::on_all_files, std::move(parameters), std::move(file_stats));
+ }));
+
+ //NB: get_storage_stats will cancel all garbage collection queries, so promise needs to be added after the call
+ pending_run_gc_[return_deleted_file_statistics].push_back(std::move(promise));
}
-void StorageManager::on_file_stats(Result<FileStats> r_file_stats, bool dummy) {
+void StorageManager::on_file_stats(Result<FileStats> r_file_stats, uint32 generation) {
+ if (generation != stats_generation_) {
+ return;
+ }
if (r_file_stats.is_error()) {
- auto promises = std::move(pending_storage_stats_);
- for (auto &promise : promises) {
- promise.set_error(r_file_stats.error().clone());
- }
+ fail_promises(pending_storage_stats_, r_file_stats.move_as_error());
return;
}
+ update_fast_stats(r_file_stats.ok());
send_stats(r_file_stats.move_as_ok(), stats_dialog_limit_, std::move(pending_storage_stats_));
}
void StorageManager::create_stats_worker() {
+ CHECK(!is_closed_);
if (stats_worker_.empty()) {
- stats_worker_ = create_actor_on_scheduler<FileStatsWorker>("FileStatsWorker", scheduler_id_, create_reference());
+ stats_worker_ =
+ create_actor_on_scheduler<FileStatsWorker>("FileStatsWorker", scheduler_id_, create_reference(),
+ stats_cancellation_token_source_.get_cancellation_token());
}
}
-void StorageManager::on_all_files(Result<FileStats> r_file_stats, bool dummy) {
+void StorageManager::on_all_files(FileGcParameters gc_parameters, Result<FileStats> r_file_stats) {
+ int32 dialog_limit = gc_parameters.dialog_limit_;
+ if (is_closed_ && r_file_stats.is_ok()) {
+ r_file_stats = Global::request_aborted_error();
+ }
if (r_file_stats.is_error()) {
- LOG(ERROR) << "Stats for GC failed: " << r_file_stats.error();
- auto promises = std::move(pending_run_gc_);
- for (auto &promise : promises) {
- promise.set_error(r_file_stats.error().clone());
- }
- return;
+ return on_gc_finished(dialog_limit, r_file_stats.move_as_error());
}
create_gc_worker();
- send_closure(gc_worker_, &FileGcWorker::run_gc, gc_parameters_, r_file_stats.move_as_ok().all_files,
- PromiseCreator::lambda([actor_id = actor_id(this)](Result<FileStats> r_file_stats) {
- send_closure(actor_id, &StorageManager::on_gc_finished, std::move(r_file_stats), false);
+ send_closure(gc_worker_, &FileGcWorker::run_gc, std::move(gc_parameters), r_file_stats.ok_ref().get_all_files(),
+ PromiseCreator::lambda([actor_id = actor_id(this), dialog_limit](Result<FileGcResult> r_file_gc_result) {
+ send_closure(actor_id, &StorageManager::on_gc_finished, dialog_limit, std::move(r_file_gc_result));
}));
}
-int64 StorageManager::get_db_size() {
+int64 StorageManager::get_file_size(CSlice path) {
+ auto r_info = stat(path);
+ if (r_info.is_error()) {
+ return 0;
+ }
+
+ auto size = r_info.ok().real_size_;
+ LOG(DEBUG) << "Add file \"" << path << "\" of size " << size << " to fast storage statistics";
+ return size;
+}
+
+int64 StorageManager::get_database_size() {
int64 size = 0;
- auto add_path = [&](CSlice path) {
- TRY_RESULT(info, stat(path));
- size += info.size_;
+ G()->td_db()->with_db_path([&size](CSlice path) { size += get_file_size(path); });
+ return size;
+}
- return Status::OK();
- };
+int64 StorageManager::get_language_pack_database_size() {
+ int64 size = 0;
+ auto path = G()->get_option_string("language_pack_database_path");
+ if (!path.empty()) {
+ SqliteDb::with_db_path(path, [&size](CSlice path) { size += get_file_size(path); });
+ }
+ return size;
+}
- G()->td_db()->with_db_path([&](CSlice path) { add_path(path).ignore(); });
- add_path(G()->parameters().database_directory + "log").ignore();
- add_path(G()->parameters().database_directory + "log.old").ignore();
+int64 StorageManager::get_log_size() {
+ int64 size = 0;
+ for (auto &log_path : log_interface->get_file_paths()) {
+ size += get_file_size(log_path);
+ }
return size;
}
void StorageManager::create_gc_worker() {
+ CHECK(!is_closed_);
if (gc_worker_.empty()) {
- gc_worker_ = create_actor_on_scheduler<FileGcWorker>("FileGcWorker", scheduler_id_, create_reference());
+ gc_worker_ = create_actor_on_scheduler<FileGcWorker>("FileGcWorker", scheduler_id_, create_reference(),
+ gc_cancellation_token_source_.get_cancellation_token());
}
}
-void StorageManager::on_gc_finished(Result<FileStats> r_file_stats, bool dummy) {
- if (r_file_stats.is_error()) {
- LOG(ERROR) << "GC failed: " << r_file_stats.error();
- auto promises = std::move(pending_run_gc_);
- for (auto &promise : promises) {
- promise.set_error(r_file_stats.error().clone());
+void StorageManager::on_gc_finished(int32 dialog_limit, Result<FileGcResult> r_file_gc_result) {
+ if (r_file_gc_result.is_error()) {
+ if (r_file_gc_result.error().code() != 500) {
+ LOG(ERROR) << "GC failed: " << r_file_gc_result.error();
}
+ auto promises = std::move(pending_run_gc_[0]);
+ append(promises, std::move(pending_run_gc_[1]));
+ pending_run_gc_[0].clear();
+ pending_run_gc_[1].clear();
+ fail_promises(promises, r_file_gc_result.move_as_error());
return;
}
- send_stats(r_file_stats.move_as_ok(), gc_parameters_.dialog_limit, std::move(pending_run_gc_));
+ update_fast_stats(r_file_gc_result.ok().kept_file_stats_);
+
+ auto kept_file_promises = std::move(pending_run_gc_[0]);
+ auto removed_file_promises = std::move(pending_run_gc_[1]);
+ send_stats(std::move(r_file_gc_result.ok_ref().kept_file_stats_), dialog_limit, std::move(kept_file_promises));
+ send_stats(std::move(r_file_gc_result.ok_ref().removed_file_stats_), dialog_limit, std::move(removed_file_promises));
}
void StorageManager::save_fast_stat() {
G()->td_db()->get_binlog_pmc()->set("fast_file_stat", log_event_store(fast_stat_).as_slice().str());
}
+
void StorageManager::load_fast_stat() {
auto status = log_event_parse(fast_stat_, G()->td_db()->get_binlog_pmc()->get("fast_file_stat"));
if (status.is_error()) {
fast_stat_ = FileTypeStat();
}
+ LOG(INFO) << "Loaded fast storage statistics with " << fast_stat_.cnt << " files of total size " << fast_stat_.size;
}
-void StorageManager::send_stats(FileStats &&stats, int32 dialog_limit, std::vector<Promise<FileStats>> promises) {
+void StorageManager::update_fast_stats(const FileStats &stats) {
fast_stat_ = stats.get_total_nontemp_stat();
+ LOG(INFO) << "Recalculate fast storage statistics to " << fast_stat_.cnt << " files of total size "
+ << fast_stat_.size;
save_fast_stat();
+}
+
+void StorageManager::send_stats(FileStats &&stats, int32 dialog_limit, std::vector<Promise<FileStats>> &&promises) {
+ if (promises.empty()) {
+ return;
+ }
stats.apply_dialog_limit(dialog_limit);
- std::vector<DialogId> dialog_ids = stats.get_dialog_ids();
+ auto dialog_ids = stats.get_dialog_ids();
- auto promise =
- PromiseCreator::lambda([promises = std::move(promises), stats = std::move(stats)](Result<Unit>) mutable {
+ auto promise = PromiseCreator::lambda(
+ [promises = std::move(promises), stats = std::move(stats)](vector<DialogId> dialog_ids) mutable {
+ stats.apply_dialog_ids(dialog_ids);
for (auto &promise : promises) {
promise.set_value(FileStats(stats));
}
@@ -190,6 +270,7 @@ void StorageManager::send_stats(FileStats &&stats, int32 dialog_limit, std::vect
}
ActorShared<> StorageManager::create_reference() {
+ ref_cnt_++;
return actor_shared(this, 1);
}
@@ -200,7 +281,27 @@ void StorageManager::hangup_shared() {
}
}
+void StorageManager::close_stats_worker() {
+ fail_promises(pending_storage_stats_, Global::request_aborted_error());
+ stats_generation_++;
+ stats_worker_.reset();
+ stats_cancellation_token_source_.cancel();
+}
+
+void StorageManager::close_gc_worker() {
+ auto promises = std::move(pending_run_gc_[0]);
+ append(promises, std::move(pending_run_gc_[1]));
+ pending_run_gc_[0].clear();
+ pending_run_gc_[1].clear();
+ fail_promises(promises, Global::request_aborted_error());
+ gc_worker_.reset();
+ gc_cancellation_token_source_.cancel();
+}
+
void StorageManager::hangup() {
+ is_closed_ = true;
+ close_stats_worker();
+ close_gc_worker();
hangup_shared();
}
@@ -208,16 +309,17 @@ uint32 StorageManager::load_last_gc_timestamp() {
last_gc_timestamp_ = to_integer<uint32>(G()->td_db()->get_binlog_pmc()->get("files_gc_ts"));
return last_gc_timestamp_;
}
+
void StorageManager::save_last_gc_timestamp() {
last_gc_timestamp_ = static_cast<uint32>(Clocks::system());
G()->td_db()->get_binlog_pmc()->set("files_gc_ts", to_string(last_gc_timestamp_));
}
+
void StorageManager::schedule_next_gc() {
- if (!G()->shared_config().get_option_boolean("use_storage_optimizer") &&
- !G()->parameters().enable_storage_optimizer) {
+ if (!G()->get_option_boolean("use_storage_optimizer") && !G()->parameters().enable_storage_optimizer) {
next_gc_at_ = 0;
cancel_timeout();
- LOG(INFO) << "No next file gc is scheduled";
+ LOG(INFO) << "No next file clean up is scheduled";
return;
}
auto sys_time = static_cast<uint32>(Clocks::system());
@@ -233,7 +335,7 @@ void StorageManager::schedule_next_gc() {
CHECK(next_gc_at >= sys_time);
auto next_gc_in = next_gc_at - sys_time;
- LOG(INFO) << "Schedule next file gc in " << next_gc_in;
+ LOG(INFO) << "Schedule next file clean up in " << next_gc_in;
next_gc_at_ = Time::now() + next_gc_in;
set_timeout_at(next_gc_at_);
}
@@ -242,9 +344,14 @@ void StorageManager::timeout_expired() {
if (next_gc_at_ == 0) {
return;
}
+ if (!pending_run_gc_[0].empty() || !pending_run_gc_[1].empty() || !pending_storage_stats_.empty()) {
+ set_timeout_in(60);
+ return;
+ }
next_gc_at_ = 0;
- run_gc({}, PromiseCreator::lambda([actor_id = actor_id(this)](Result<FileStats> r_stats) {
- if (!r_stats.is_error() || r_stats.error().code() != 1) {
+ run_gc({}, false, PromiseCreator::lambda([actor_id = actor_id(this)](Result<FileStats> r_stats) {
+ if (!r_stats.is_error() || r_stats.error().code() != 500) {
+ // do not save garbage collection timestamp if request was canceled
send_closure(actor_id, &StorageManager::save_last_gc_timestamp);
}
send_closure(actor_id, &StorageManager::schedule_next_gc);
diff --git a/protocols/Telegram/tdlib/td/td/telegram/StorageManager.h b/protocols/Telegram/tdlib/td/td/telegram/StorageManager.h
index 48185b9081..9c6c39bf8b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/StorageManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/StorageManager.h
@@ -1,39 +1,49 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/files/FileGcWorker.h"
#include "td/telegram/files/FileStats.h"
+#include "td/telegram/files/FileStatsWorker.h"
+#include "td/telegram/td_api.h"
+
+#include "td/actor/actor.h"
+#include "td/utils/CancellationToken.h"
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
#include "td/utils/Status.h"
namespace td {
-class FileStatsWorker;
-class FileGcWorker;
-} // namespace td
-namespace td {
-class StorageManager : public Actor {
+struct DatabaseStats {
+ string debug;
+ DatabaseStats() = default;
+ explicit DatabaseStats(string debug) : debug(std::move(debug)) {
+ }
+ tl_object_ptr<td_api::databaseStatistics> get_database_statistics_object() const;
+};
+
+class StorageManager final : public Actor {
public:
StorageManager(ActorShared<> parent, int32 scheduler_id);
- void get_storage_stats(int32 dialog_limit, Promise<FileStats> promise);
+ void get_storage_stats(bool need_all_files, int32 dialog_limit, Promise<FileStats> promise);
void get_storage_stats_fast(Promise<FileStatsFast> promise);
- void run_gc(FileGcParameters parameters, Promise<FileStats> promise);
+ void get_database_stats(Promise<DatabaseStats> promise);
+ void run_gc(FileGcParameters parameters, bool return_deleted_file_statistics, Promise<FileStats> promise);
void update_use_storage_optimizer();
- void on_new_file(int64 size);
+
+ void on_new_file(int64 size, int64 real_size, int32 cnt);
private:
- static constexpr uint32 GC_EACH = 60 * 60 * 24; // 1 day
- static constexpr uint32 GC_DELAY = 60;
- static constexpr uint32 GC_RAND_DELAY = 60 * 15;
+ static constexpr int GC_EACH = 60 * 60 * 24; // 1 day
+ static constexpr int GC_DELAY = 60;
+ static constexpr int GC_RAND_DELAY = 60 * 15;
ActorShared<> parent_;
@@ -42,41 +52,54 @@ class StorageManager : public Actor {
// get stats
ActorOwn<FileStatsWorker> stats_worker_;
std::vector<Promise<FileStats>> pending_storage_stats_;
- int32 stats_dialog_limit_ = 0;
+ uint32 stats_generation_{0};
+ int32 stats_dialog_limit_{0};
+ bool stats_need_all_files_{false};
FileTypeStat fast_stat_;
- void on_file_stats(Result<FileStats> r_file_stats, bool dummy);
+ CancellationTokenSource stats_cancellation_token_source_;
+ CancellationTokenSource gc_cancellation_token_source_;
+
+ void on_file_stats(Result<FileStats> r_file_stats, uint32 generation);
void create_stats_worker();
- void send_stats(FileStats &&stats, int32 dialog_limit, std::vector<Promise<FileStats>> promises);
+ void update_fast_stats(const FileStats &stats);
+ static void send_stats(FileStats &&stats, int32 dialog_limit, std::vector<Promise<FileStats>> &&promises);
void save_fast_stat();
void load_fast_stat();
- static int64 get_db_size();
+ static int64 get_database_size();
+ static int64 get_language_pack_database_size();
+ static int64 get_log_size();
+ static int64 get_file_size(CSlice path);
// RefCnt
int32 ref_cnt_{1};
+ bool is_closed_{false};
ActorShared<> create_reference();
- void start_up() override;
- void hangup_shared() override;
- void hangup() override;
+ void start_up() final;
+ void hangup_shared() final;
+ void hangup() final;
// Gc
ActorOwn<FileGcWorker> gc_worker_;
- std::vector<Promise<FileStats>> pending_run_gc_;
- FileGcParameters gc_parameters_;
+ std::vector<Promise<FileStats>> pending_run_gc_[2];
uint32 last_gc_timestamp_ = 0;
double next_gc_at_ = 0;
- void on_all_files(Result<FileStats> r_file_stats, bool dummy);
+ void on_all_files(FileGcParameters gc_parameters, Result<FileStats> r_file_stats);
void create_gc_worker();
- void on_gc_finished(Result<FileStats> r_file_stats, bool dummy);
+ void on_gc_finished(int32 dialog_limit, Result<FileGcResult> r_file_gc_result);
+
+ void close_stats_worker();
+ void close_gc_worker();
uint32 load_last_gc_timestamp();
void save_last_gc_timestamp();
void schedule_next_gc();
- void timeout_expired() override;
+ void timeout_expired() final;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SuggestedAction.cpp b/protocols/Telegram/tdlib/td/td/telegram/SuggestedAction.cpp
new file mode 100644
index 0000000000..fb4b4e3b0b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SuggestedAction.cpp
@@ -0,0 +1,197 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SuggestedAction.h"
+
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/Td.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/misc.h"
+#include "td/utils/Status.h"
+
+#include <algorithm>
+
+namespace td {
+
+void SuggestedAction::init(Type type) {
+ type_ = type;
+}
+
+SuggestedAction::SuggestedAction(Slice action_str) {
+ if (action_str == Slice("AUTOARCHIVE_POPULAR")) {
+ init(Type::EnableArchiveAndMuteNewChats);
+ } else if (action_str == Slice("VALIDATE_PASSWORD")) {
+ init(Type::CheckPassword);
+ } else if (action_str == Slice("VALIDATE_PHONE_NUMBER")) {
+ init(Type::CheckPhoneNumber);
+ } else if (action_str == Slice("NEWCOMER_TICKS")) {
+ init(Type::ViewChecksHint);
+ }
+}
+
+SuggestedAction::SuggestedAction(Slice action_str, DialogId dialog_id) {
+ CHECK(dialog_id.is_valid());
+ if (action_str == Slice("CONVERT_GIGAGROUP")) {
+ type_ = Type::ConvertToGigagroup;
+ dialog_id_ = dialog_id;
+ }
+}
+
+SuggestedAction::SuggestedAction(const td_api::object_ptr<td_api::SuggestedAction> &suggested_action) {
+ if (suggested_action == nullptr) {
+ return;
+ }
+ switch (suggested_action->get_id()) {
+ case td_api::suggestedActionEnableArchiveAndMuteNewChats::ID:
+ init(Type::EnableArchiveAndMuteNewChats);
+ break;
+ case td_api::suggestedActionCheckPassword::ID:
+ init(Type::CheckPassword);
+ break;
+ case td_api::suggestedActionCheckPhoneNumber::ID:
+ init(Type::CheckPhoneNumber);
+ break;
+ case td_api::suggestedActionViewChecksHint::ID:
+ init(Type::ViewChecksHint);
+ break;
+ case td_api::suggestedActionConvertToBroadcastGroup::ID: {
+ auto action = static_cast<const td_api::suggestedActionConvertToBroadcastGroup *>(suggested_action.get());
+ ChannelId channel_id(action->supergroup_id_);
+ if (channel_id.is_valid()) {
+ type_ = Type::ConvertToGigagroup;
+ dialog_id_ = DialogId(channel_id);
+ }
+ break;
+ }
+ case td_api::suggestedActionSetPassword::ID: {
+ auto action = static_cast<const td_api::suggestedActionSetPassword *>(suggested_action.get());
+ type_ = Type::SetPassword;
+ otherwise_relogin_days_ = action->authorization_delay_;
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+}
+
+string SuggestedAction::get_suggested_action_str() const {
+ switch (type_) {
+ case Type::EnableArchiveAndMuteNewChats:
+ return "AUTOARCHIVE_POPULAR";
+ case Type::CheckPassword:
+ return "VALIDATE_PASSWORD";
+ case Type::CheckPhoneNumber:
+ return "VALIDATE_PHONE_NUMBER";
+ case Type::ViewChecksHint:
+ return "NEWCOMER_TICKS";
+ case Type::ConvertToGigagroup:
+ return "CONVERT_GIGAGROUP";
+ default:
+ return string();
+ }
+}
+
+td_api::object_ptr<td_api::SuggestedAction> SuggestedAction::get_suggested_action_object() const {
+ switch (type_) {
+ case Type::Empty:
+ return nullptr;
+ case Type::EnableArchiveAndMuteNewChats:
+ return td_api::make_object<td_api::suggestedActionEnableArchiveAndMuteNewChats>();
+ case Type::CheckPassword:
+ return td_api::make_object<td_api::suggestedActionCheckPassword>();
+ case Type::CheckPhoneNumber:
+ return td_api::make_object<td_api::suggestedActionCheckPhoneNumber>();
+ case Type::ViewChecksHint:
+ return td_api::make_object<td_api::suggestedActionViewChecksHint>();
+ case Type::ConvertToGigagroup:
+ return td_api::make_object<td_api::suggestedActionConvertToBroadcastGroup>(dialog_id_.get_channel_id().get());
+ case Type::SetPassword:
+ return td_api::make_object<td_api::suggestedActionSetPassword>(otherwise_relogin_days_);
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+td_api::object_ptr<td_api::updateSuggestedActions> get_update_suggested_actions_object(
+ const vector<SuggestedAction> &added_actions, const vector<SuggestedAction> &removed_actions) {
+ auto get_object = [](const SuggestedAction &action) {
+ return action.get_suggested_action_object();
+ };
+ return td_api::make_object<td_api::updateSuggestedActions>(transform(added_actions, get_object),
+ transform(removed_actions, get_object));
+}
+
+void update_suggested_actions(vector<SuggestedAction> &suggested_actions,
+ vector<SuggestedAction> &&new_suggested_actions) {
+ td::unique(new_suggested_actions);
+ if (new_suggested_actions == suggested_actions) {
+ return;
+ }
+
+ vector<SuggestedAction> added_actions;
+ vector<SuggestedAction> removed_actions;
+ auto old_it = suggested_actions.begin();
+ auto new_it = new_suggested_actions.begin();
+ while (old_it != suggested_actions.end() || new_it != new_suggested_actions.end()) {
+ if (old_it != suggested_actions.end() && (new_it == new_suggested_actions.end() || *old_it < *new_it)) {
+ removed_actions.push_back(*old_it++);
+ } else if (old_it == suggested_actions.end() || *new_it < *old_it) {
+ added_actions.push_back(*new_it++);
+ } else {
+ ++old_it;
+ ++new_it;
+ }
+ }
+ CHECK(!added_actions.empty() || !removed_actions.empty());
+ suggested_actions = std::move(new_suggested_actions);
+ send_closure(G()->td(), &Td::send_update, get_update_suggested_actions_object(added_actions, removed_actions));
+}
+
+void remove_suggested_action(vector<SuggestedAction> &suggested_actions, SuggestedAction suggested_action) {
+ if (td::remove(suggested_actions, suggested_action)) {
+ send_closure(G()->td(), &Td::send_update, get_update_suggested_actions_object({}, {suggested_action}));
+ }
+}
+
+void dismiss_suggested_action(SuggestedAction action, Promise<Unit> &&promise) {
+ switch (action.type_) {
+ case SuggestedAction::Type::Empty:
+ return promise.set_error(Status::Error(400, "Action must be non-empty"));
+ case SuggestedAction::Type::EnableArchiveAndMuteNewChats:
+ case SuggestedAction::Type::CheckPassword:
+ case SuggestedAction::Type::CheckPhoneNumber:
+ case SuggestedAction::Type::ViewChecksHint:
+ return send_closure_later(G()->config_manager(), &ConfigManager::dismiss_suggested_action, std::move(action),
+ std::move(promise));
+ case SuggestedAction::Type::ConvertToGigagroup:
+ return send_closure_later(G()->contacts_manager(), &ContactsManager::dismiss_dialog_suggested_action,
+ std::move(action), std::move(promise));
+ case SuggestedAction::Type::SetPassword: {
+ if (action.otherwise_relogin_days_ <= 0) {
+ return promise.set_error(Status::Error(400, "Invalid authorization_delay specified"));
+ }
+ auto days = narrow_cast<int32>(G()->get_option_integer("otherwise_relogin_days"));
+ if (days == action.otherwise_relogin_days_) {
+ vector<SuggestedAction> removed_actions{SuggestedAction{SuggestedAction::Type::SetPassword, DialogId(), days}};
+ send_closure(G()->td(), &Td::send_update, get_update_suggested_actions_object({}, removed_actions));
+ G()->set_option_empty("otherwise_relogin_days");
+ }
+ return promise.set_value(Unit());
+ }
+ default:
+ UNREACHABLE();
+ return;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/SuggestedAction.h b/protocols/Telegram/tdlib/td/td/telegram/SuggestedAction.h
new file mode 100644
index 0000000000..af33f9202c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/SuggestedAction.h
@@ -0,0 +1,79 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+struct SuggestedAction {
+ enum class Type : int32 {
+ Empty,
+ EnableArchiveAndMuteNewChats,
+ CheckPhoneNumber,
+ ViewChecksHint,
+ ConvertToGigagroup,
+ CheckPassword,
+ SetPassword
+ };
+ Type type_ = Type::Empty;
+ DialogId dialog_id_;
+ int32 otherwise_relogin_days_ = 0;
+
+ void init(Type type);
+
+ SuggestedAction() = default;
+
+ explicit SuggestedAction(Type type, DialogId dialog_id = DialogId(), int32 otherwise_relogin_days = 0)
+ : type_(type), dialog_id_(dialog_id), otherwise_relogin_days_(otherwise_relogin_days) {
+ }
+
+ explicit SuggestedAction(Slice action_str);
+
+ SuggestedAction(Slice action_str, DialogId dialog_id);
+
+ explicit SuggestedAction(const td_api::object_ptr<td_api::SuggestedAction> &suggested_action);
+
+ bool is_empty() const {
+ return type_ == Type::Empty;
+ }
+
+ string get_suggested_action_str() const;
+
+ td_api::object_ptr<td_api::SuggestedAction> get_suggested_action_object() const;
+};
+
+inline bool operator==(const SuggestedAction &lhs, const SuggestedAction &rhs) {
+ CHECK(lhs.dialog_id_ == rhs.dialog_id_);
+ return lhs.type_ == rhs.type_;
+}
+
+inline bool operator!=(const SuggestedAction &lhs, const SuggestedAction &rhs) {
+ return !(lhs == rhs);
+}
+
+inline bool operator<(const SuggestedAction &lhs, const SuggestedAction &rhs) {
+ CHECK(lhs.dialog_id_ == rhs.dialog_id_);
+ return static_cast<int32>(lhs.type_) < static_cast<int32>(rhs.type_);
+}
+
+td_api::object_ptr<td_api::updateSuggestedActions> get_update_suggested_actions_object(
+ const vector<SuggestedAction> &added_actions, const vector<SuggestedAction> &removed_actions);
+
+void update_suggested_actions(vector<SuggestedAction> &suggested_actions,
+ vector<SuggestedAction> &&new_suggested_actions);
+
+void remove_suggested_action(vector<SuggestedAction> &suggested_actions, SuggestedAction suggested_action);
+
+void dismiss_suggested_action(SuggestedAction action, Promise<Unit> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Support.cpp b/protocols/Telegram/tdlib/td/td/telegram/Support.cpp
new file mode 100644
index 0000000000..31dea54d2a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Support.cpp
@@ -0,0 +1,113 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Support.h"
+
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+static td_api::object_ptr<td_api::userSupportInfo> get_user_support_info_object(
+ Td *td, telegram_api::object_ptr<telegram_api::help_UserInfo> user_info) {
+ CHECK(user_info != nullptr);
+ auto result = td_api::make_object<td_api::userSupportInfo>();
+ FormattedText message;
+ if (user_info->get_id() == telegram_api::help_userInfo::ID) {
+ auto info = telegram_api::move_object_as<telegram_api::help_userInfo>(user_info);
+ message = get_message_text(td->contacts_manager_.get(), std::move(info->message_), std::move(info->entities_), true,
+ true, info->date_, false, "get_user_support_info_object");
+ result->author_ = std::move(info->author_);
+ result->date_ = info->date_;
+ }
+ result->message_ = get_formatted_text_object(message, true, 0);
+ return result;
+}
+
+class GetUserInfoQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::userSupportInfo>> promise_;
+
+ public:
+ explicit GetUserInfoQuery(Promise<td_api::object_ptr<td_api::userSupportInfo>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(UserId user_id) {
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return on_error(r_input_user.move_as_error());
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::help_getUserInfo(r_input_user.move_as_ok())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_getUserInfo>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(get_user_support_info_object(td_, result_ptr.move_as_ok()));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class EditUserInfoQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::userSupportInfo>> promise_;
+
+ public:
+ explicit EditUserInfoQuery(Promise<td_api::object_ptr<td_api::userSupportInfo>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(UserId user_id, FormattedText &&formatted_text) {
+ auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return on_error(r_input_user.move_as_error());
+ }
+
+ send_query(G()->net_query_creator().create(telegram_api::help_editUserInfo(
+ r_input_user.move_as_ok(), formatted_text.text,
+ get_input_message_entities(td_->contacts_manager_.get(), &formatted_text, "EditUserInfoQuery"))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_editUserInfo>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(get_user_support_info_object(td_, result_ptr.move_as_ok()));
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+void get_user_info(Td *td, UserId user_id, Promise<td_api::object_ptr<td_api::userSupportInfo>> &&promise) {
+ td->create_handler<GetUserInfoQuery>(std::move(promise))->send(user_id);
+}
+
+void set_user_info(Td *td, UserId user_id, td_api::object_ptr<td_api::formattedText> &&message,
+ Promise<td_api::object_ptr<td_api::userSupportInfo>> &&promise) {
+ TRY_RESULT_PROMISE(promise, formatted_text,
+ get_formatted_text(td, DialogId(td->contacts_manager_->get_my_id()), std::move(message), false,
+ true, true, false));
+ td->create_handler<EditUserInfoQuery>(std::move(promise))->send(user_id, std::move(formatted_text));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Support.h b/protocols/Telegram/tdlib/td/td/telegram/Support.h
new file mode 100644
index 0000000000..3c0cb3d9eb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Support.h
@@ -0,0 +1,24 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+
+namespace td {
+
+class Td;
+
+void get_user_info(Td *td, UserId user_id, Promise<td_api::object_ptr<td_api::userSupportInfo>> &&promise);
+
+void set_user_info(Td *td, UserId user_id, td_api::object_ptr<td_api::formattedText> &&message,
+ Promise<td_api::object_ptr<td_api::userSupportInfo>> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Td.cpp b/protocols/Telegram/tdlib/td/td/telegram/Td.cpp
index a150e6cca7..9284608942 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Td.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/Td.cpp
@@ -1,80 +1,161 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Td.h"
-#include "td/db/binlog/BinlogEvent.h"
-
-#include "td/telegram/net/ConnectionCreator.h"
-#include "td/telegram/net/MtprotoHeader.h"
-#include "td/telegram/net/NetQuery.h"
-#include "td/telegram/net/NetQueryDelayer.h"
-#include "td/telegram/net/NetQueryDispatcher.h"
-#include "td/telegram/net/NetStatsManager.h"
-#include "td/telegram/net/TempAuthKeyWatchdog.h"
-
-#include "td/telegram/AccessRights.h"
+#include "td/telegram/Account.h"
#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/Application.h"
+#include "td/telegram/AttachMenuManager.h"
#include "td/telegram/AudiosManager.h"
#include "td/telegram/AuthManager.h"
+#include "td/telegram/AutoDownloadSettings.h"
+#include "td/telegram/BackgroundId.h"
+#include "td/telegram/BackgroundManager.h"
+#include "td/telegram/BackgroundType.h"
+#include "td/telegram/BotCommand.h"
+#include "td/telegram/BotMenuButton.h"
#include "td/telegram/CallbackQueriesManager.h"
#include "td/telegram/CallId.h"
#include "td/telegram/CallManager.h"
#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChannelType.h"
#include "td/telegram/ChatId.h"
#include "td/telegram/ConfigManager.h"
-#include "td/telegram/ConfigShared.h"
#include "td/telegram/ContactsManager.h"
+#include "td/telegram/CountryInfoManager.h"
+#include "td/telegram/CustomEmojiId.h"
#include "td/telegram/DeviceTokenManager.h"
+#include "td/telegram/DialogAction.h"
+#include "td/telegram/DialogEventLog.h"
+#include "td/telegram/DialogFilter.h"
+#include "td/telegram/DialogFilterId.h"
#include "td/telegram/DialogId.h"
+#include "td/telegram/DialogListId.h"
+#include "td/telegram/DialogLocation.h"
#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/DialogParticipantFilter.h"
+#include "td/telegram/DialogSource.h"
#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/DownloadManager.h"
+#include "td/telegram/DownloadManagerCallback.h"
+#include "td/telegram/EmailVerification.h"
+#include "td/telegram/EmojiStatus.h"
+#include "td/telegram/FileReferenceManager.h"
#include "td/telegram/files/FileGcParameters.h"
#include "td/telegram/files/FileId.h"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/files/FileStats.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/FolderId.h"
+#include "td/telegram/ForumTopicManager.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/GameManager.h"
#include "td/telegram/Global.h"
+#include "td/telegram/GroupCallId.h"
+#include "td/telegram/GroupCallManager.h"
#include "td/telegram/HashtagHints.h"
#include "td/telegram/InlineQueriesManager.h"
+#include "td/telegram/JsonValue.h"
+#include "td/telegram/LanguagePackManager.h"
+#include "td/telegram/LinkManager.h"
+#include "td/telegram/Location.h"
+#include "td/telegram/Logging.h"
+#include "td/telegram/MessageCopyOptions.h"
#include "td/telegram/MessageEntity.h"
#include "td/telegram/MessageId.h"
+#include "td/telegram/MessageLinkInfo.h"
+#include "td/telegram/MessageReaction.h"
+#include "td/telegram/MessageSearchFilter.h"
+#include "td/telegram/MessageSender.h"
#include "td/telegram/MessagesManager.h"
+#include "td/telegram/MessageThreadInfo.h"
#include "td/telegram/misc.h"
+#include "td/telegram/net/ConnectionCreator.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/net/MtprotoHeader.h"
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/net/NetQueryDelayer.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/net/NetStatsManager.h"
+#include "td/telegram/net/NetType.h"
+#include "td/telegram/net/Proxy.h"
+#include "td/telegram/net/PublicRsaKeyShared.h"
+#include "td/telegram/net/TempAuthKeyWatchdog.h"
+#include "td/telegram/NotificationGroupId.h"
+#include "td/telegram/NotificationId.h"
+#include "td/telegram/NotificationManager.h"
+#include "td/telegram/NotificationSettingsManager.h"
+#include "td/telegram/NotificationSettingsScope.h"
+#include "td/telegram/OptionManager.h"
#include "td/telegram/PasswordManager.h"
+#include "td/telegram/Payments.h"
+#include "td/telegram/PhoneNumberManager.h"
#include "td/telegram/Photo.h"
+#include "td/telegram/PhotoSizeSource.h"
+#include "td/telegram/PollManager.h"
+#include "td/telegram/Premium.h"
#include "td/telegram/PrivacyManager.h"
+#include "td/telegram/PublicDialogType.h"
+#include "td/telegram/ReportReason.h"
+#include "td/telegram/RequestActor.h"
+#include "td/telegram/ScopeNotificationSettings.h"
#include "td/telegram/SecretChatId.h"
#include "td/telegram/SecretChatsManager.h"
+#include "td/telegram/SecureManager.h"
+#include "td/telegram/SecureValue.h"
+#include "td/telegram/SentEmailCode.h"
+#include "td/telegram/SponsoredMessageManager.h"
#include "td/telegram/StateManager.h"
+#include "td/telegram/StickerSetId.h"
#include "td/telegram/StickersManager.h"
+#include "td/telegram/StickerType.h"
#include "td/telegram/StorageManager.h"
+#include "td/telegram/SuggestedAction.h"
+#include "td/telegram/Support.h"
+#include "td/telegram/td_api.hpp"
#include "td/telegram/TdDb.h"
+#include "td/telegram/telegram_api.hpp"
+#include "td/telegram/ThemeManager.h"
+#include "td/telegram/TopDialogCategory.h"
#include "td/telegram/TopDialogManager.h"
#include "td/telegram/UpdatesManager.h"
+#include "td/telegram/UserId.h"
+#include "td/telegram/Version.h"
#include "td/telegram/VideoNotesManager.h"
#include "td/telegram/VideosManager.h"
#include "td/telegram/VoiceNotesManager.h"
#include "td/telegram/WebPageId.h"
#include "td/telegram/WebPagesManager.h"
-#include "td/telegram/td_api.hpp"
-#include "td/telegram/telegram_api.h"
+#include "td/db/binlog/BinlogEvent.h"
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/mtproto/DhCallback.h"
+#include "td/mtproto/Handshake.h"
+#include "td/mtproto/HandshakeActor.h"
+#include "td/mtproto/RawConnection.h"
+#include "td/mtproto/RSA.h"
+#include "td/mtproto/TransportType.h"
-#include "td/mtproto/utils.h" // for create_storer, fetch_result, etc, TODO
+#include "td/actor/actor.h"
+#include "td/utils/algorithm.h"
#include "td/utils/buffer.h"
+#include "td/utils/filesystem.h"
#include "td/utils/format.h"
#include "td/utils/MimeType.h"
#include "td/utils/misc.h"
#include "td/utils/PathView.h"
-#include "td/utils/port/path.h"
+#include "td/utils/port/IPAddress.h"
+#include "td/utils/port/SocketFd.h"
+#include "td/utils/port/uname.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/Timer.h"
#include "td/utils/tl_parsers.h"
@@ -85,123 +166,51 @@
#include <type_traits>
namespace td {
-namespace {
-DbKey as_db_key(string key) {
- // Database will still be effectively not encrypted, but
- // 1. sqlite db will be protected from corruption, because that's how sqlcipher works
- // 2. security through obscurity
- // 3. no need for reencryption of sqlite db
- if (key.empty()) {
- return DbKey::raw_key("cucumber");
- }
- return DbKey::raw_key(std::move(key));
-}
-} // namespace
-void Td::ResultHandler::set_td(Td *new_td) {
- CHECK(td == nullptr);
- td = new_td;
-}
+int VERBOSITY_NAME(td_init) = VERBOSITY_NAME(DEBUG) + 3;
+int VERBOSITY_NAME(td_requests) = VERBOSITY_NAME(INFO);
-void Td::ResultHandler::on_result(NetQueryPtr query) {
- CHECK(query->is_ready());
- if (query->is_ok()) {
- on_result(query->id(), std::move(query->ok()));
- } else {
- on_error(query->id(), std::move(query->error()));
- }
- query->clear();
+void Td::ResultHandler::set_td(Td *td) {
+ CHECK(td_ == nullptr);
+ td_ = td;
}
void Td::ResultHandler::send_query(NetQueryPtr query) {
- td->add_handler(query->id(), shared_from_this());
- td->send(std::move(query));
+ CHECK(!is_query_sent_)
+ is_query_sent_ = true;
+ td_->add_handler(query->id(), shared_from_this());
+ query->debug("Send to NetQueryDispatcher");
+ G()->net_query_dispatcher().dispatch(std::move(query));
}
-class GetNearestDcQuery : public Td::ResultHandler {
- Promise<string> promise_;
-
- public:
- explicit GetNearestDcQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
- }
-
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::help_getNearestDc()), DcId::main(),
- NetQuery::Type::Common, NetQuery::AuthFlag::Off));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::help_getNearestDc>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
- }
-
- promise_.set_value(std::move(result_ptr.ok()->country_));
- }
-
- void on_error(uint64 id, Status status) override {
- LOG(ERROR) << "GetNearestDc returned " << status;
- promise_.set_error(std::move(status));
- }
-};
-
-class GetWallpapersQuery : public Td::ResultHandler {
- Promise<tl_object_ptr<td_api::wallpapers>> promise_;
+class GetPromoDataQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::help_PromoData>> promise_;
public:
- explicit GetWallpapersQuery(Promise<tl_object_ptr<td_api::wallpapers>> &&promise) : promise_(std::move(promise)) {
+ explicit GetPromoDataQuery(Promise<telegram_api::object_ptr<telegram_api::help_PromoData>> &&promise)
+ : promise_(std::move(promise)) {
}
void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::account_getWallPapers())));
+ // we don't poll promo data before authorization
+ send_query(G()->net_query_creator().create(telegram_api::help_getPromoData()));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::account_getWallPapers>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_getPromoData>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto wallpapers = result_ptr.move_as_ok();
-
- auto results = make_tl_object<td_api::wallpapers>();
- results->wallpapers_.reserve(wallpapers.size());
- for (auto &wallpaper_ptr : wallpapers) {
- CHECK(wallpaper_ptr != nullptr);
- switch (wallpaper_ptr->get_id()) {
- case telegram_api::wallPaper::ID: {
- auto wallpaper = move_tl_object_as<telegram_api::wallPaper>(wallpaper_ptr);
- vector<tl_object_ptr<td_api::photoSize>> sizes;
- sizes.reserve(wallpaper->sizes_.size());
- for (auto &size_ptr : wallpaper->sizes_) {
- auto photo_size =
- get_photo_size(td->file_manager_.get(), FileType::Wallpaper, 0, 0, DialogId(), std::move(size_ptr));
- sizes.push_back(get_photo_size_object(td->file_manager_.get(), &photo_size));
- }
- sort_photo_sizes(sizes);
- results->wallpapers_.push_back(
- make_tl_object<td_api::wallpaper>(wallpaper->id_, std::move(sizes), wallpaper->color_));
- break;
- }
- case telegram_api::wallPaperSolid::ID: {
- auto wallpaper = move_tl_object_as<telegram_api::wallPaperSolid>(wallpaper_ptr);
- results->wallpapers_.push_back(make_tl_object<td_api::wallpaper>(
- wallpaper->id_, vector<tl_object_ptr<td_api::photoSize>>(), wallpaper->bg_color_));
- break;
- }
- default:
- UNREACHABLE();
- }
- }
- promise_.set_value(std::move(results));
+ promise_.set_value(result_ptr.move_as_ok());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class GetRecentMeUrlsQuery : public Td::ResultHandler {
+class GetRecentMeUrlsQuery final : public Td::ResultHandler {
Promise<tl_object_ptr<td_api::tMeUrls>> promise_;
public:
@@ -209,18 +218,18 @@ class GetRecentMeUrlsQuery : public Td::ResultHandler {
}
void send(const string &referrer) {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::help_getRecentMeUrls(referrer))));
+ send_query(G()->net_query_creator().create(telegram_api::help_getRecentMeUrls(referrer)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::help_getRecentMeUrls>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto urls_full = result_ptr.move_as_ok();
- td->contacts_manager_->on_get_users(std::move(urls_full->users_));
- td->contacts_manager_->on_get_chats(std::move(urls_full->chats_));
+ td_->contacts_manager_->on_get_users(std::move(urls_full->users_), "GetRecentMeUrlsQuery");
+ td_->contacts_manager_->on_get_chats(std::move(urls_full->chats_), "GetRecentMeUrlsQuery");
auto urls = std::move(urls_full->urls_);
auto results = make_tl_object<td_api::tMeUrls>();
@@ -239,7 +248,7 @@ class GetRecentMeUrlsQuery : public Td::ResultHandler {
break;
}
result->type_ = make_tl_object<td_api::tMeUrlTypeUser>(
- td->contacts_manager_->get_user_id_object(user_id, "tMeUrlTypeUser"));
+ td_->contacts_manager_->get_user_id_object(user_id, "tMeUrlTypeUser"));
break;
}
case telegram_api::recentMeUrlChat::ID: {
@@ -252,27 +261,33 @@ class GetRecentMeUrlsQuery : public Td::ResultHandler {
break;
}
result->type_ = make_tl_object<td_api::tMeUrlTypeSupergroup>(
- td->contacts_manager_->get_supergroup_id_object(channel_id, "tMeUrlTypeSupergroup"));
+ td_->contacts_manager_->get_supergroup_id_object(channel_id, "tMeUrlTypeSupergroup"));
break;
}
case telegram_api::recentMeUrlChatInvite::ID: {
auto url = move_tl_object_as<telegram_api::recentMeUrlChatInvite>(url_ptr);
result->url_ = std::move(url->url_);
- td->contacts_manager_->on_get_dialog_invite_link_info(result->url_, std::move(url->chat_invite_));
- result->type_ = make_tl_object<td_api::tMeUrlTypeChatInvite>(
- td->contacts_manager_->get_chat_invite_link_info_object(result->url_));
+ td_->contacts_manager_->on_get_dialog_invite_link_info(result->url_, std::move(url->chat_invite_),
+ Promise<Unit>());
+ auto info_object = td_->contacts_manager_->get_chat_invite_link_info_object(result->url_);
+ if (info_object == nullptr) {
+ result = nullptr;
+ break;
+ }
+ result->type_ = make_tl_object<td_api::tMeUrlTypeChatInvite>(std::move(info_object));
break;
}
case telegram_api::recentMeUrlStickerSet::ID: {
auto url = move_tl_object_as<telegram_api::recentMeUrlStickerSet>(url_ptr);
result->url_ = std::move(url->url_);
- auto sticker_set_id = td->stickers_manager_->on_get_sticker_set_covered(std::move(url->set_), false);
- if (sticker_set_id == 0) {
+ auto sticker_set_id =
+ td_->stickers_manager_->on_get_sticker_set_covered(std::move(url->set_), false, "recentMeUrlStickerSet");
+ if (!sticker_set_id.is_valid()) {
LOG(ERROR) << "Receive invalid sticker set";
result = nullptr;
break;
}
- result->type_ = make_tl_object<td_api::tMeUrlTypeStickerSet>(sticker_set_id);
+ result->type_ = make_tl_object<td_api::tMeUrlTypeStickerSet>(sticker_set_id.get());
break;
}
case telegram_api::recentMeUrlUnknown::ID:
@@ -289,38 +304,40 @@ class GetRecentMeUrlsQuery : public Td::ResultHandler {
promise_.set_value(std::move(results));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class SendCustomRequestQuery : public Td::ResultHandler {
- Promise<string> promise_;
+class SendCustomRequestQuery final : public Td::ResultHandler {
+ Promise<td_api::object_ptr<td_api::customRequestResult>> promise_;
public:
- explicit SendCustomRequestQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ explicit SendCustomRequestQuery(Promise<td_api::object_ptr<td_api::customRequestResult>> &&promise)
+ : promise_(std::move(promise)) {
}
void send(const string &method, const string &parameters) {
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::bots_sendCustomRequest(method, make_tl_object<telegram_api::dataJSON>(parameters)))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::bots_sendCustomRequest(method, make_tl_object<telegram_api::dataJSON>(parameters))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::bots_sendCustomRequest>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- promise_.set_value(std::move(result_ptr.ok()->data_));
+ auto result = result_ptr.move_as_ok();
+ promise_.set_value(td_api::make_object<td_api::customRequestResult>(result->data_));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class AnswerCustomQueryQuery : public Td::ResultHandler {
+class AnswerCustomQueryQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
public:
@@ -328,14 +345,14 @@ class AnswerCustomQueryQuery : public Td::ResultHandler {
}
void send(int64 custom_query_id, const string &data) {
- send_query(G()->net_query_creator().create(create_storer(
- telegram_api::bots_answerWebhookJSONQuery(custom_query_id, make_tl_object<telegram_api::dataJSON>(data)))));
+ send_query(G()->net_query_creator().create(
+ telegram_api::bots_answerWebhookJSONQuery(custom_query_id, make_tl_object<telegram_api::dataJSON>(data))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::bots_answerWebhookJSONQuery>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
@@ -345,421 +362,211 @@ class AnswerCustomQueryQuery : public Td::ResultHandler {
promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
-class SetBotUpdatesStatusQuery : public Td::ResultHandler {
+class SetBotUpdatesStatusQuery final : public Td::ResultHandler {
public:
void send(int32 pending_update_count, const string &error_message) {
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::help_setBotUpdatesStatus(pending_update_count, error_message))));
+ send_query(
+ G()->net_query_creator().create(telegram_api::help_setBotUpdatesStatus(pending_update_count, error_message)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::help_setBotUpdatesStatus>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
LOG_IF(WARNING, !result) << "Set bot updates status has failed";
}
- void on_error(uint64 id, Status status) override {
- LOG(WARNING) << "Receive error for SetBotUpdatesStatus: " << status;
+ void on_error(Status status) final {
+ if (!G()->is_expected_error(status)) {
+ LOG(WARNING) << "Receive error for SetBotUpdatesStatusQuery: " << status;
+ }
status.ignore();
}
};
-class UpdateStatusQuery : public Td::ResultHandler {
+class UpdateStatusQuery final : public Td::ResultHandler {
bool is_offline_;
public:
NetQueryRef send(bool is_offline) {
is_offline_ = is_offline;
- auto net_query = G()->net_query_creator().create(create_storer(telegram_api::account_updateStatus(is_offline)));
+ auto net_query = G()->net_query_creator().create(telegram_api::account_updateStatus(is_offline));
auto result = net_query.get_weak();
send_query(std::move(net_query));
return result;
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::account_updateStatus>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
bool result = result_ptr.ok();
- LOG(INFO) << "UpdateStatus returned " << result;
- td->on_update_status_success(!is_offline_);
+ LOG(INFO) << "Receive result for UpdateStatusQuery: " << result;
+ td_->on_update_status_success(!is_offline_);
}
- void on_error(uint64 id, Status status) override {
- if (status.code() != NetQuery::Cancelled) {
+ void on_error(Status status) final {
+ if (status.code() != NetQuery::Canceled && !G()->is_expected_error(status)) {
LOG(ERROR) << "Receive error for UpdateStatusQuery: " << status;
}
status.ignore();
}
};
-class GetInviteTextQuery : public Td::ResultHandler {
- Promise<string> promise_;
+class TestNetworkQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
public:
- explicit GetInviteTextQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ explicit TestNetworkQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
}
void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::help_getInviteText())));
+ send_query(G()->net_query_creator().create_unauth(telegram_api::help_getConfig()));
}
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::help_getInviteText>(packet);
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_getConfig>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(Status::Error(500, "Fetch failed"));
}
- promise_.set_value(std::move(result_ptr.ok()->message_));
+ LOG(DEBUG) << "TestNetwork OK: " << to_string(result_ptr.ok());
+ promise_.set_value(Unit());
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
+ LOG(ERROR) << "Test query failed: " << status;
promise_.set_error(std::move(status));
}
};
-class GetTermsOfServiceQuery : public Td::ResultHandler {
- Promise<string> promise_;
+class TestProxyRequest final : public RequestOnceActor {
+ Proxy proxy_;
+ int16 dc_id_;
+ double timeout_;
+ ActorOwn<> child_;
+ Promise<> promise_;
- public:
- explicit GetTermsOfServiceQuery(Promise<string> &&promise) : promise_(std::move(promise)) {
+ auto get_transport() {
+ return mtproto::TransportType{mtproto::TransportType::ObfuscatedTcp, dc_id_, proxy_.secret()};
}
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::help_getTermsOfService()), DcId::main(),
- NetQuery::Type::Common, NetQuery::AuthFlag::Off));
- }
+ void do_run(Promise<Unit> &&promise) final {
+ set_timeout_in(timeout_);
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::help_getTermsOfService>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ promise_ = std::move(promise);
+ IPAddress ip_address;
+ auto status = ip_address.init_host_port(proxy_.server(), proxy_.port());
+ if (status.is_error()) {
+ return promise_.set_error(Status::Error(400, status.public_message()));
}
-
- promise_.set_value(std::move(result_ptr.ok()->text_));
- }
-
- void on_error(uint64 id, Status status) override {
- promise_.set_error(std::move(status));
- }
-};
-
-template <class T = Unit>
-class RequestActor : public Actor {
- public:
- RequestActor(ActorShared<Td> td_id, uint64 request_id)
- : td_id_(std::move(td_id)), td(td_id_.get().get_actor_unsafe()), request_id_(request_id) {
- }
-
- void loop() override {
- PromiseActor<T> promise;
- FutureActor<T> future;
- init_promise_future(&promise, &future);
-
- do_run(PromiseCreator::from_promise_actor(std::move(promise)));
-
- if (future.is_ready()) {
- if (future.is_error()) {
- do_send_error(future.move_as_error());
- } else {
- do_set_result(future.move_as_ok());
- do_send_result();
- }
- stop();
- } else {
- if (--tries_left_ == 0) {
- future.close();
- do_send_error(Status::Error(400, "Requested data is unaccessible"));
- return stop();
- }
-
- future.set_event(EventCreator::raw(actor_id(), nullptr));
- future_ = std::move(future);
+ auto r_socket_fd = SocketFd::open(ip_address);
+ if (r_socket_fd.is_error()) {
+ return promise_.set_error(Status::Error(400, r_socket_fd.error().public_message()));
}
- }
- void raw_event(const Event::Raw &event) override {
- if (future_.is_error()) {
- auto error = future_.move_as_error();
- if (error == Status::Hangup()) {
- // dropping query due to lost authorization or lost promise
- // td may be already closed, so we should check is auth_manager_ is empty
- bool is_authorized = td->auth_manager_ && td->auth_manager_->is_authorized();
- if (is_authorized) {
- LOG(ERROR) << "Promise was lost";
- do_send_error(Status::Error(500, "Query can't be answered due to bug in the TDLib"));
- } else {
- do_send_error(Status::Error(401, "Unauthorized"));
- }
- return stop();
+ auto dc_options = ConnectionCreator::get_default_dc_options(false);
+ IPAddress mtproto_ip_address;
+ for (auto &dc_option : dc_options.dc_options) {
+ if (dc_option.get_dc_id().get_raw_id() == dc_id_) {
+ mtproto_ip_address = dc_option.get_ip_address();
+ break;
}
-
- do_send_error(std::move(error));
- stop();
- } else {
- do_set_result(future_.move_as_ok());
- loop();
}
- }
-
- void on_start_migrate(int32 /*sched_id*/) override {
- UNREACHABLE();
- }
- void on_finish_migrate() override {
- UNREACHABLE();
- }
-
- int get_tries() const {
- return tries_left_;
- }
-
- void set_tries(int32 tries) {
- tries_left_ = tries;
- }
- protected:
- ActorShared<Td> td_id_;
- Td *td;
+ auto connection_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<ConnectionCreator::ConnectionData> r_data) mutable {
+ send_closure(actor_id, &TestProxyRequest::on_connection_data, std::move(r_data));
+ });
- void send_result(tl_object_ptr<td_api::Object> &&result) {
- send_closure(td_id_, &Td::send_result, request_id_, std::move(result));
+ child_ = ConnectionCreator::prepare_connection(ip_address, r_socket_fd.move_as_ok(), proxy_, mtproto_ip_address,
+ get_transport(), "Test", "TestPingDC2", nullptr, {}, false,
+ std::move(connection_promise));
}
- void send_error(Status &&status) {
- LOG(INFO) << "Receive error for query: " << status;
- send_closure(td_id_, &Td::send_error, request_id_, std::move(status));
- }
-
- private:
- virtual void do_run(Promise<T> &&promise) = 0;
-
- virtual void do_send_result() {
- send_result(make_tl_object<td_api::ok>());
- }
-
- virtual void do_send_error(Status &&status) {
- send_error(std::move(status));
- }
-
- virtual void do_set_result(T &&result) {
- CHECK((std::is_same<T, Unit>::value)); // all other results should be implicitly handled by overriding this method
- }
-
- void hangup() override {
- do_send_error(Status::Error(500, "Request aborted"));
- stop();
- }
-
- friend class RequestOnceActor;
-
- uint64 request_id_;
- int tries_left_ = 2;
- FutureActor<T> future_;
-};
-
-class RequestOnceActor : public RequestActor<> {
- public:
- RequestOnceActor(ActorShared<Td> td_id, uint64 request_id) : RequestActor(std::move(td_id), request_id) {
- }
-
- void loop() override {
- if (get_tries() < 2) {
- do_send_result();
- stop();
- return;
+ void on_connection_data(Result<ConnectionCreator::ConnectionData> r_data) {
+ if (r_data.is_error()) {
+ return promise_.set_error(r_data.move_as_error());
}
+ class HandshakeContext final : public mtproto::AuthKeyHandshakeContext {
+ public:
+ mtproto::DhCallback *get_dh_callback() final {
+ return nullptr;
+ }
+ mtproto::PublicRsaKeyInterface *get_public_rsa_key_interface() final {
+ return &public_rsa_key;
+ }
- RequestActor::loop();
- }
-};
-
-/*** Td ***/
-/** Td queries **/
-class TestQuery : public Td::ResultHandler {
- public:
- explicit TestQuery(uint64 request_id) : request_id_(request_id) {
- }
-
- void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::help_getConfig())));
- }
-
- void on_result(uint64 id, BufferSlice packet) override {
- auto result_ptr = fetch_result<telegram_api::help_getConfig>(packet);
- if (result_ptr.is_error()) {
- return on_error(id, Status::Error(500, "Fetch failed"));
+ private:
+ PublicRsaKeyShared public_rsa_key{DcId::empty(), false};
+ };
+ auto handshake = make_unique<mtproto::AuthKeyHandshake>(dc_id_, 3600);
+ auto data = r_data.move_as_ok();
+ auto raw_connection =
+ mtproto::RawConnection::create(data.ip_address, std::move(data.buffered_socket_fd), get_transport(), nullptr);
+ child_ = create_actor<mtproto::HandshakeActor>(
+ "HandshakeActor", std::move(handshake), std::move(raw_connection), make_unique<HandshakeContext>(), 10.0,
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<unique_ptr<mtproto::RawConnection>> raw_connection) {
+ send_closure(actor_id, &TestProxyRequest::on_handshake_connection, std::move(raw_connection));
+ }),
+ PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<unique_ptr<mtproto::AuthKeyHandshake>> handshake) mutable {
+ send_closure(actor_id, &TestProxyRequest::on_handshake, std::move(handshake));
+ }));
+ }
+ void on_handshake_connection(Result<unique_ptr<mtproto::RawConnection>> r_raw_connection) {
+ if (r_raw_connection.is_error()) {
+ return promise_.set_error(Status::Error(400, r_raw_connection.move_as_error().public_message()));
}
-
- LOG(DEBUG) << "TestOK: " << to_string(result_ptr.ok());
- send_closure(G()->td(), &Td::send_result, request_id_, make_tl_object<td_api::ok>());
}
-
- void on_error(uint64 id, Status status) override {
- status.ignore();
- LOG(ERROR) << "Test query failed: " << status;
- }
-
- private:
- uint64 request_id_;
-};
-
-class GetAccountTtlRequest : public RequestActor<int32> {
- int32 account_ttl_;
-
- void do_run(Promise<int32> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(account_ttl_));
+ void on_handshake(Result<unique_ptr<mtproto::AuthKeyHandshake>> r_handshake) {
+ if (!promise_) {
return;
}
-
- td->contacts_manager_->get_account_ttl(std::move(promise));
- }
-
- void do_set_result(int32 &&result) override {
- account_ttl_ = result;
- }
-
- void do_send_result() override {
- send_result(make_tl_object<td_api::accountTtl>(account_ttl_));
- }
-
- public:
- GetAccountTtlRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
- }
-};
-
-class SetAccountTtlRequest : public RequestOnceActor {
- int32 account_ttl_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->set_account_ttl(account_ttl_, std::move(promise));
- }
-
- public:
- SetAccountTtlRequest(ActorShared<Td> td, uint64 request_id, int32 account_ttl)
- : RequestOnceActor(std::move(td), request_id), account_ttl_(account_ttl) {
- }
-};
-
-class GetActiveSessionsRequest : public RequestActor<tl_object_ptr<td_api::sessions>> {
- tl_object_ptr<td_api::sessions> sessions_;
-
- void do_run(Promise<tl_object_ptr<td_api::sessions>> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(sessions_));
- return;
+ if (r_handshake.is_error()) {
+ return promise_.set_error(Status::Error(400, r_handshake.move_as_error().public_message()));
}
- td->contacts_manager_->get_active_sessions(std::move(promise));
- }
-
- void do_set_result(tl_object_ptr<td_api::sessions> &&result) override {
- sessions_ = std::move(result);
- }
-
- void do_send_result() override {
- CHECK(sessions_ != nullptr);
- send_result(std::move(sessions_));
- }
-
- public:
- GetActiveSessionsRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
- }
-};
-
-class TerminateSessionRequest : public RequestOnceActor {
- int64 session_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->terminate_session(session_id_, std::move(promise));
- }
-
- public:
- TerminateSessionRequest(ActorShared<Td> td, uint64 request_id, int64 session_id)
- : RequestOnceActor(std::move(td), request_id), session_id_(session_id) {
- }
-};
-
-class TerminateAllOtherSessionsRequest : public RequestOnceActor {
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->terminate_all_other_sessions(std::move(promise));
- }
-
- public:
- TerminateAllOtherSessionsRequest(ActorShared<Td> td, uint64 request_id)
- : RequestOnceActor(std::move(td), request_id) {
- }
-};
-
-class GetConnectedWebsitesRequest : public RequestActor<tl_object_ptr<td_api::connectedWebsites>> {
- tl_object_ptr<td_api::connectedWebsites> connected_websites_;
-
- void do_run(Promise<tl_object_ptr<td_api::connectedWebsites>> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(connected_websites_));
- return;
+ auto handshake = r_handshake.move_as_ok();
+ if (!handshake->is_ready_for_finish()) {
+ promise_.set_error(Status::Error(400, "Handshake is not ready"));
}
-
- td->contacts_manager_->get_connected_websites(std::move(promise));
- }
-
- void do_set_result(tl_object_ptr<td_api::connectedWebsites> &&result) override {
- connected_websites_ = std::move(result);
- }
-
- void do_send_result() override {
- CHECK(connected_websites_ != nullptr);
- send_result(std::move(connected_websites_));
- }
-
- public:
- GetConnectedWebsitesRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
- }
-};
-
-class DisconnectWebsiteRequest : public RequestOnceActor {
- int64 website_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->disconnect_website(website_id_, std::move(promise));
- }
-
- public:
- DisconnectWebsiteRequest(ActorShared<Td> td, uint64 request_id, int64 website_id)
- : RequestOnceActor(std::move(td), request_id), website_id_(website_id) {
+ promise_.set_value(Unit());
}
-};
-class DisconnectAllWebsitesRequest : public RequestOnceActor {
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->disconnect_all_websites(std::move(promise));
+ void timeout_expired() final {
+ send_error(Status::Error(400, "Timeout expired"));
+ stop();
}
public:
- DisconnectAllWebsitesRequest(ActorShared<Td> td, uint64 request_id) : RequestOnceActor(std::move(td), request_id) {
+ TestProxyRequest(ActorShared<Td> td, uint64 request_id, Proxy proxy, int32 dc_id, double timeout)
+ : RequestOnceActor(std::move(td), request_id)
+ , proxy_(std::move(proxy))
+ , dc_id_(static_cast<int16>(dc_id))
+ , timeout_(timeout) {
}
};
-class GetMeRequest : public RequestActor<> {
+class GetMeRequest final : public RequestActor<> {
UserId user_id_;
- void do_run(Promise<Unit> &&promise) override {
- user_id_ = td->contacts_manager_->get_me(std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ user_id_ = td_->contacts_manager_->get_me(std::move(promise));
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_user_object(user_id_));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_user_object(user_id_));
}
public:
@@ -767,120 +574,121 @@ class GetMeRequest : public RequestActor<> {
}
};
-class GetUserRequest : public RequestActor<> {
+class GetUserRequest final : public RequestActor<> {
UserId user_id_;
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->get_user(user_id_, get_tries(), std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->contacts_manager_->get_user(user_id_, get_tries(), std::move(promise));
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_user_object(user_id_));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_user_object(user_id_));
}
public:
- GetUserRequest(ActorShared<Td> td, uint64 request_id, int32 user_id)
+ GetUserRequest(ActorShared<Td> td, uint64 request_id, int64 user_id)
: RequestActor(std::move(td), request_id), user_id_(user_id) {
set_tries(3);
}
};
-class GetUserFullInfoRequest : public RequestActor<> {
+class GetUserFullInfoRequest final : public RequestActor<> {
UserId user_id_;
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->get_user_full(user_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->contacts_manager_->load_user_full(user_id_, get_tries() < 2, std::move(promise), "GetUserFullInfoRequest");
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_user_full_info_object(user_id_));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_user_full_info_object(user_id_));
}
public:
- GetUserFullInfoRequest(ActorShared<Td> td, uint64 request_id, int32 user_id)
+ GetUserFullInfoRequest(ActorShared<Td> td, uint64 request_id, int64 user_id)
: RequestActor(std::move(td), request_id), user_id_(user_id) {
}
};
-class GetGroupRequest : public RequestActor<> {
+class GetGroupRequest final : public RequestActor<> {
ChatId chat_id_;
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->get_chat(chat_id_, get_tries(), std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->contacts_manager_->get_chat(chat_id_, get_tries(), std::move(promise));
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_basic_group_object(chat_id_));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_basic_group_object(chat_id_));
}
public:
- GetGroupRequest(ActorShared<Td> td, uint64 request_id, int32 chat_id)
+ GetGroupRequest(ActorShared<Td> td, uint64 request_id, int64 chat_id)
: RequestActor(std::move(td), request_id), chat_id_(chat_id) {
set_tries(3);
}
};
-class GetGroupFullInfoRequest : public RequestActor<> {
+class GetGroupFullInfoRequest final : public RequestActor<> {
ChatId chat_id_;
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->get_chat_full(chat_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->contacts_manager_->load_chat_full(chat_id_, get_tries() < 2, std::move(promise), "getBasicGroupFullInfo");
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_basic_group_full_info_object(chat_id_));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_basic_group_full_info_object(chat_id_));
}
public:
- GetGroupFullInfoRequest(ActorShared<Td> td, uint64 request_id, int32 chat_id)
+ GetGroupFullInfoRequest(ActorShared<Td> td, uint64 request_id, int64 chat_id)
: RequestActor(std::move(td), request_id), chat_id_(chat_id) {
}
};
-class GetSupergroupRequest : public RequestActor<> {
+class GetSupergroupRequest final : public RequestActor<> {
ChannelId channel_id_;
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->get_channel(channel_id_, get_tries(), std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->contacts_manager_->get_channel(channel_id_, get_tries(), std::move(promise));
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_supergroup_object(channel_id_));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_supergroup_object(channel_id_));
}
public:
- GetSupergroupRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id)
+ GetSupergroupRequest(ActorShared<Td> td, uint64 request_id, int64 channel_id)
: RequestActor(std::move(td), request_id), channel_id_(channel_id) {
set_tries(3);
}
};
-class GetSupergroupFullInfoRequest : public RequestActor<> {
+class GetSupergroupFullInfoRequest final : public RequestActor<> {
ChannelId channel_id_;
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->get_channel_full(channel_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->contacts_manager_->load_channel_full(channel_id_, get_tries() < 2, std::move(promise),
+ "GetSupergroupFullInfoRequest");
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_channel_full_info_object(channel_id_));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_supergroup_full_info_object(channel_id_));
}
public:
- GetSupergroupFullInfoRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id)
+ GetSupergroupFullInfoRequest(ActorShared<Td> td, uint64 request_id, int64 channel_id)
: RequestActor(std::move(td), request_id), channel_id_(channel_id) {
}
};
-class GetSecretChatRequest : public RequestActor<> {
+class GetSecretChatRequest final : public RequestActor<> {
SecretChatId secret_chat_id_;
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->get_secret_chat(secret_chat_id_, get_tries() < 2, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->contacts_manager_->get_secret_chat(secret_chat_id_, get_tries() < 2, std::move(promise));
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_secret_chat_object(secret_chat_id_));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_secret_chat_object(secret_chat_id_));
}
public:
@@ -889,20 +697,20 @@ class GetSecretChatRequest : public RequestActor<> {
}
};
-class GetChatRequest : public RequestActor<> {
+class GetChatRequest final : public RequestActor<> {
DialogId dialog_id_;
bool dialog_found_ = false;
- void do_run(Promise<Unit> &&promise) override {
- dialog_found_ = td->messages_manager_->load_dialog(dialog_id_, get_tries(), std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_found_ = td_->messages_manager_->load_dialog(dialog_id_, get_tries(), std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
if (!dialog_found_) {
send_error(Status::Error(400, "Chat is not accessible"));
} else {
- send_result(td->messages_manager_->get_chat_object(dialog_id_));
+ send_result(td_->messages_manager_->get_chat_object(dialog_id_));
}
}
@@ -913,59 +721,95 @@ class GetChatRequest : public RequestActor<> {
}
};
-class GetChatsRequest : public RequestActor<> {
- DialogDate offset_;
- int32 limit_;
+class GetChatFilterRequest final : public RequestActor<> {
+ DialogFilterId dialog_filter_id_;
- vector<DialogId> dialog_ids_;
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->load_dialog_filter(dialog_filter_id_, get_tries() < 2, std::move(promise));
+ }
- void do_run(Promise<Unit> &&promise) override {
- dialog_ids_ = td->messages_manager_->get_dialogs(offset_, limit_, get_tries() < 2, std::move(promise));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_chat_filter_object(dialog_filter_id_));
}
- void do_send_result() override {
- send_result(MessagesManager::get_chats_object(dialog_ids_));
+ public:
+ GetChatFilterRequest(ActorShared<Td> td, uint64 request_id, int32 dialog_filter_id)
+ : RequestActor(std::move(td), request_id), dialog_filter_id_(dialog_filter_id) {
+ set_tries(3);
+ }
+};
+
+class SearchUserByPhoneNumberRequest final : public RequestActor<> {
+ string phone_number_;
+
+ UserId user_id_;
+
+ void do_run(Promise<Unit> &&promise) final {
+ user_id_ = td_->contacts_manager_->search_user_by_phone_number(phone_number_, std::move(promise));
+ }
+
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_user_object(user_id_));
}
public:
- GetChatsRequest(ActorShared<Td> td, uint64 request_id, int64 offset_order, int64 offset_dialog_id, int32 limit)
- : RequestActor(std::move(td), request_id), offset_(offset_order, DialogId(offset_dialog_id)), limit_(limit) {
+ SearchUserByPhoneNumberRequest(ActorShared<Td> td, uint64 request_id, string &&phone_number)
+ : RequestActor(std::move(td), request_id), phone_number_(std::move(phone_number)) {
+ }
+};
+
+class LoadChatsRequest final : public RequestActor<> {
+ DialogListId dialog_list_id_;
+ DialogDate offset_;
+ int32 limit_;
+
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->get_dialogs(dialog_list_id_, offset_, limit_, false, get_tries() < 2, std::move(promise));
+ }
+
+ public:
+ LoadChatsRequest(ActorShared<Td> td, uint64 request_id, DialogListId dialog_list_id, DialogDate offset, int32 limit)
+ : RequestActor(std::move(td), request_id), dialog_list_id_(dialog_list_id), offset_(offset), limit_(limit) {
// 1 for database + 1 for server request + 1 for server request at the end + 1 for return + 1 just in case
set_tries(5);
+
+ if (limit_ > 100) {
+ limit_ = 100;
+ }
}
};
-class SearchPublicChatRequest : public RequestActor<> {
+class SearchPublicChatRequest final : public RequestActor<> {
string username_;
DialogId dialog_id_;
- void do_run(Promise<Unit> &&promise) override {
- dialog_id_ = td->messages_manager_->search_public_dialog(username_, get_tries() < 3, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_id_ = td_->messages_manager_->search_public_dialog(username_, get_tries() < 3, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_chat_object(dialog_id_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_chat_object(dialog_id_));
}
public:
SearchPublicChatRequest(ActorShared<Td> td, uint64 request_id, string username)
: RequestActor(std::move(td), request_id), username_(std::move(username)) {
- set_tries(3);
+ set_tries(4); // 1 for server request + 1 for reload voice chat + 1 for reload dialog + 1 for result
}
};
-class SearchPublicChatsRequest : public RequestActor<> {
+class SearchPublicChatsRequest final : public RequestActor<> {
string query_;
vector<DialogId> dialog_ids_;
- void do_run(Promise<Unit> &&promise) override {
- dialog_ids_ = td->messages_manager_->search_public_dialogs(query_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_ids_ = td_->messages_manager_->search_public_dialogs(query_, std::move(promise));
}
- void do_send_result() override {
- send_result(MessagesManager::get_chats_object(dialog_ids_));
+ void do_send_result() final {
+ send_result(MessagesManager::get_chats_object(-1, dialog_ids_));
}
public:
@@ -974,17 +818,17 @@ class SearchPublicChatsRequest : public RequestActor<> {
}
};
-class SearchChatsRequest : public RequestActor<> {
+class SearchChatsRequest final : public RequestActor<> {
string query_;
int32 limit_;
- vector<DialogId> dialog_ids_;
+ std::pair<int32, vector<DialogId>> dialog_ids_;
- void do_run(Promise<Unit> &&promise) override {
- dialog_ids_ = td->messages_manager_->search_dialogs(query_, limit_, std::move(promise)).second;
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_ids_ = td_->messages_manager_->search_dialogs(query_, limit_, std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
send_result(MessagesManager::get_chats_object(dialog_ids_));
}
@@ -994,18 +838,18 @@ class SearchChatsRequest : public RequestActor<> {
}
};
-class SearchChatsOnServerRequest : public RequestActor<> {
+class SearchChatsOnServerRequest final : public RequestActor<> {
string query_;
int32 limit_;
vector<DialogId> dialog_ids_;
- void do_run(Promise<Unit> &&promise) override {
- dialog_ids_ = td->messages_manager_->search_dialogs_on_server(query_, limit_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_ids_ = td_->messages_manager_->search_dialogs_on_server(query_, limit_, std::move(promise));
}
- void do_send_result() override {
- send_result(MessagesManager::get_chats_object(dialog_ids_));
+ void do_send_result() final {
+ send_result(MessagesManager::get_chats_object(-1, dialog_ids_));
}
public:
@@ -1014,82 +858,88 @@ class SearchChatsOnServerRequest : public RequestActor<> {
}
};
-class GetGroupsInCommonRequest : public RequestActor<> {
+class GetGroupsInCommonRequest final : public RequestActor<> {
UserId user_id_;
DialogId offset_dialog_id_;
int32 limit_;
- vector<DialogId> dialog_ids_;
+ std::pair<int32, vector<DialogId>> dialog_ids_;
- void do_run(Promise<Unit> &&promise) override {
- dialog_ids_ = td->messages_manager_->get_common_dialogs(user_id_, offset_dialog_id_, limit_, get_tries() < 2,
- std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_ids_ = td_->messages_manager_->get_common_dialogs(user_id_, offset_dialog_id_, limit_, get_tries() < 2,
+ std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
send_result(MessagesManager::get_chats_object(dialog_ids_));
}
public:
- GetGroupsInCommonRequest(ActorShared<Td> td, uint64 request_id, int32 user_id, int64 offset_dialog_id, int32 limit)
+ GetGroupsInCommonRequest(ActorShared<Td> td, uint64 request_id, int64 user_id, int64 offset_dialog_id, int32 limit)
: RequestActor(std::move(td), request_id), user_id_(user_id), offset_dialog_id_(offset_dialog_id), limit_(limit) {
}
};
-class CheckChatUsernameRequest : public RequestActor<CheckDialogUsernameResult> {
- DialogId dialog_id_;
- string username_;
+class GetSuitableDiscussionChatsRequest final : public RequestActor<> {
+ vector<DialogId> dialog_ids_;
- CheckDialogUsernameResult result_ = CheckDialogUsernameResult::Ok;
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_ids_ = td_->contacts_manager_->get_dialogs_for_discussion(std::move(promise));
+ }
- void do_run(Promise<CheckDialogUsernameResult> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(result_));
- return;
- }
+ void do_send_result() final {
+ send_result(MessagesManager::get_chats_object(-1, dialog_ids_));
+ }
- td->contacts_manager_->check_dialog_username(dialog_id_, username_, std::move(promise));
+ public:
+ GetSuitableDiscussionChatsRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
}
+};
+
+class GetInactiveSupergroupChatsRequest final : public RequestActor<> {
+ vector<DialogId> dialog_ids_;
- void do_set_result(CheckDialogUsernameResult &&result) override {
- result_ = std::move(result);
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_ids_ = td_->contacts_manager_->get_inactive_channels(std::move(promise));
}
- void do_send_result() override {
- send_result(ContactsManager::get_check_chat_username_result_object(result_));
+ void do_send_result() final {
+ send_result(MessagesManager::get_chats_object(-1, dialog_ids_));
}
public:
- CheckChatUsernameRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, string username)
- : RequestActor(std::move(td), request_id), dialog_id_(dialog_id), username_(std::move(username)) {
+ GetInactiveSupergroupChatsRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
}
};
-class GetCreatedPublicChatsRequest : public RequestActor<> {
- vector<DialogId> dialog_ids_;
+class GetRecentlyOpenedChatsRequest final : public RequestActor<> {
+ int32 limit_;
+
+ std::pair<int32, vector<DialogId>> dialog_ids_;
- void do_run(Promise<Unit> &&promise) override {
- dialog_ids_ = td->contacts_manager_->get_created_public_dialogs(std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_ids_ = td_->messages_manager_->get_recently_opened_dialogs(limit_, std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
send_result(MessagesManager::get_chats_object(dialog_ids_));
}
public:
- GetCreatedPublicChatsRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
+ GetRecentlyOpenedChatsRequest(ActorShared<Td> td, uint64 request_id, int32 limit)
+ : RequestActor(std::move(td), request_id), limit_(limit) {
}
};
-class GetMessageRequest : public RequestOnceActor {
+class GetMessageRequest final : public RequestOnceActor {
FullMessageId full_message_id_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->get_message(full_message_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->get_message(full_message_id_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_message_object(full_message_id_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_message_object(full_message_id_, "GetMessageRequest"));
}
public:
@@ -1098,393 +948,297 @@ class GetMessageRequest : public RequestOnceActor {
}
};
-class GetRepliedMessageRequest : public RequestOnceActor {
+class GetRepliedMessageRequest final : public RequestOnceActor {
DialogId dialog_id_;
MessageId message_id_;
- MessageId replied_message_id_;
+ FullMessageId replied_message_id_;
- void do_run(Promise<Unit> &&promise) override {
+ void do_run(Promise<Unit> &&promise) final {
replied_message_id_ =
- td->messages_manager_->get_replied_message(dialog_id_, message_id_, get_tries() < 3, std::move(promise));
+ td_->messages_manager_->get_replied_message(dialog_id_, message_id_, get_tries() < 3, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_message_object({dialog_id_, replied_message_id_}));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_message_object(replied_message_id_, "GetRepliedMessageRequest"));
}
public:
GetRepliedMessageRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id)
: RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id), message_id_(message_id) {
- set_tries(3);
+ set_tries(3); // 1 to get initial message, 1 to get the reply and 1 for result
}
};
-class GetChatPinnedMessageRequest : public RequestActor<MessageId> {
+class GetMessageThreadRequest final : public RequestActor<MessageThreadInfo> {
DialogId dialog_id_;
+ MessageId message_id_;
- MessageId pinned_message_id_;
+ MessageThreadInfo message_thread_info_;
- void do_run(Promise<MessageId> &&promise) override {
+ void do_run(Promise<MessageThreadInfo> &&promise) final {
if (get_tries() < 2) {
- promise.set_value(std::move(pinned_message_id_));
+ promise.set_value(std::move(message_thread_info_));
return;
}
-
- td->messages_manager_->get_dialog_pinned_message(dialog_id_, std::move(promise));
+ td_->messages_manager_->get_message_thread(dialog_id_, message_id_, std::move(promise));
}
- void do_set_result(MessageId &&result) override {
- pinned_message_id_ = result;
+ void do_set_result(MessageThreadInfo &&result) final {
+ message_thread_info_ = std::move(result);
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_message_object({dialog_id_, pinned_message_id_}));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_message_thread_info_object(message_thread_info_));
}
public:
- GetChatPinnedMessageRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id)
- : RequestActor(std::move(td), request_id), dialog_id_(dialog_id) {
+ GetMessageThreadRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id)
+ : RequestActor(std::move(td), request_id), dialog_id_(dialog_id), message_id_(message_id) {
}
};
-class GetMessagesRequest : public RequestOnceActor {
+class GetChatPinnedMessageRequest final : public RequestOnceActor {
DialogId dialog_id_;
- vector<MessageId> message_ids_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->get_messages(dialog_id_, message_ids_, std::move(promise));
+ MessageId pinned_message_id_;
+
+ void do_run(Promise<Unit> &&promise) final {
+ pinned_message_id_ = td_->messages_manager_->get_dialog_pinned_message(dialog_id_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_messages_object(-1, dialog_id_, message_ids_));
+ void do_send_result() final {
+ send_result(
+ td_->messages_manager_->get_message_object({dialog_id_, pinned_message_id_}, "GetChatPinnedMessageRequest"));
}
public:
- GetMessagesRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, const vector<int64> &message_ids)
- : RequestOnceActor(std::move(td), request_id)
- , dialog_id_(dialog_id)
- , message_ids_(MessagesManager::get_message_ids(message_ids)) {
+ GetChatPinnedMessageRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id)
+ : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id) {
+ set_tries(3); // 1 to get pinned_message_id, 1 to get the message and 1 for result
}
};
-class GetPublicMessageLinkRequest : public RequestActor<> {
- FullMessageId full_message_id_;
- bool for_group_;
-
- string link_;
- string html_;
+class GetCallbackQueryMessageRequest final : public RequestOnceActor {
+ DialogId dialog_id_;
+ MessageId message_id_;
+ int64 callback_query_id_;
- void do_run(Promise<Unit> &&promise) override {
- std::tie(link_, html_) =
- td->messages_manager_->get_public_message_link(full_message_id_, for_group_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->get_callback_query_message(dialog_id_, message_id_, callback_query_id_, std::move(promise));
}
- void do_send_result() override {
- send_result(make_tl_object<td_api::publicMessageLink>(link_, html_));
+ void do_send_result() final {
+ send_result(
+ td_->messages_manager_->get_message_object({dialog_id_, message_id_}, "GetCallbackQueryMessageRequest"));
}
public:
- GetPublicMessageLinkRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id, bool for_group)
- : RequestActor(std::move(td), request_id)
- , full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , for_group_(for_group) {
+ GetCallbackQueryMessageRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
+ int64 callback_query_id)
+ : RequestOnceActor(std::move(td), request_id)
+ , dialog_id_(dialog_id)
+ , message_id_(message_id)
+ , callback_query_id_(callback_query_id) {
}
};
-class EditMessageTextRequest : public RequestOnceActor {
- FullMessageId full_message_id_;
- tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
- tl_object_ptr<td_api::InputMessageContent> input_message_content_;
+class GetMessagesRequest final : public RequestOnceActor {
+ DialogId dialog_id_;
+ vector<MessageId> message_ids_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->edit_message_text(full_message_id_, std::move(reply_markup_),
- std::move(input_message_content_), std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->get_messages(dialog_id_, message_ids_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_message_object(full_message_id_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_messages_object(-1, dialog_id_, message_ids_, false, "GetMessagesRequest"));
}
public:
- EditMessageTextRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
- tl_object_ptr<td_api::ReplyMarkup> reply_markup,
- tl_object_ptr<td_api::InputMessageContent> input_message_content)
+ GetMessagesRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, const vector<int64> &message_ids)
: RequestOnceActor(std::move(td), request_id)
- , full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , reply_markup_(std::move(reply_markup))
- , input_message_content_(std::move(input_message_content)) {
+ , dialog_id_(dialog_id)
+ , message_ids_(MessagesManager::get_message_ids(message_ids)) {
}
};
-class EditMessageLiveLocationRequest : public RequestOnceActor {
+class GetMessageEmbeddingCodeRequest final : public RequestActor<> {
FullMessageId full_message_id_;
- tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
- tl_object_ptr<td_api::location> location_;
+ bool for_group_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->edit_message_live_location(full_message_id_, std::move(reply_markup_), std::move(location_),
- std::move(promise));
+ string html_;
+
+ void do_run(Promise<Unit> &&promise) final {
+ html_ = td_->messages_manager_->get_message_embedding_code(full_message_id_, for_group_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_message_object(full_message_id_));
+ void do_send_result() final {
+ send_result(make_tl_object<td_api::text>(html_));
}
public:
- EditMessageLiveLocationRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
- tl_object_ptr<td_api::ReplyMarkup> reply_markup,
- tl_object_ptr<td_api::location> location)
- : RequestOnceActor(std::move(td), request_id)
+ GetMessageEmbeddingCodeRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
+ bool for_group)
+ : RequestActor(std::move(td), request_id)
, full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , reply_markup_(std::move(reply_markup))
- , location_(std::move(location)) {
+ , for_group_(for_group) {
}
};
-class EditMessageCaptionRequest : public RequestOnceActor {
- FullMessageId full_message_id_;
- tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
- tl_object_ptr<td_api::formattedText> caption_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->edit_message_caption(full_message_id_, std::move(reply_markup_), std::move(caption_),
- std::move(promise));
- }
+class GetMessageLinkInfoRequest final : public RequestActor<MessageLinkInfo> {
+ string url_;
- void do_send_result() override {
- send_result(td->messages_manager_->get_message_object(full_message_id_));
- }
+ MessageLinkInfo message_link_info_;
- public:
- EditMessageCaptionRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
- tl_object_ptr<td_api::ReplyMarkup> reply_markup,
- tl_object_ptr<td_api::formattedText> caption)
- : RequestOnceActor(std::move(td), request_id)
- , full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , reply_markup_(std::move(reply_markup))
- , caption_(std::move(caption)) {
+ void do_run(Promise<MessageLinkInfo> &&promise) final {
+ if (get_tries() < 2) {
+ promise.set_value(std::move(message_link_info_));
+ return;
+ }
+ td_->messages_manager_->get_message_link_info(url_, std::move(promise));
}
-};
-
-class EditMessageReplyMarkupRequest : public RequestOnceActor {
- FullMessageId full_message_id_;
- tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->edit_message_reply_markup(full_message_id_, std::move(reply_markup_), std::move(promise));
+ void do_set_result(MessageLinkInfo &&result) final {
+ message_link_info_ = std::move(result);
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_message_object(full_message_id_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_message_link_info_object(message_link_info_));
}
public:
- EditMessageReplyMarkupRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
- tl_object_ptr<td_api::ReplyMarkup> reply_markup)
- : RequestOnceActor(std::move(td), request_id)
- , full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , reply_markup_(std::move(reply_markup)) {
+ GetMessageLinkInfoRequest(ActorShared<Td> td, uint64 request_id, string url)
+ : RequestActor(std::move(td), request_id), url_(std::move(url)) {
}
};
-class EditInlineMessageTextRequest : public RequestOnceActor {
- string inline_message_id_;
+class EditMessageTextRequest final : public RequestOnceActor {
+ FullMessageId full_message_id_;
tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
tl_object_ptr<td_api::InputMessageContent> input_message_content_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->edit_inline_message_text(inline_message_id_, std::move(reply_markup_),
- std::move(input_message_content_), std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->edit_message_text(full_message_id_, std::move(reply_markup_),
+ std::move(input_message_content_), std::move(promise));
+ }
+
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_message_object(full_message_id_, "EditMessageTextRequest"));
}
public:
- EditInlineMessageTextRequest(ActorShared<Td> td, uint64 request_id, string inline_message_id,
- tl_object_ptr<td_api::ReplyMarkup> reply_markup,
- tl_object_ptr<td_api::InputMessageContent> input_message_content)
+ EditMessageTextRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
+ tl_object_ptr<td_api::ReplyMarkup> reply_markup,
+ tl_object_ptr<td_api::InputMessageContent> input_message_content)
: RequestOnceActor(std::move(td), request_id)
- , inline_message_id_(std::move(inline_message_id))
+ , full_message_id_(DialogId(dialog_id), MessageId(message_id))
, reply_markup_(std::move(reply_markup))
, input_message_content_(std::move(input_message_content)) {
}
};
-class EditInlineMessageLiveLocationRequest : public RequestOnceActor {
- string inline_message_id_;
+class EditMessageLiveLocationRequest final : public RequestOnceActor {
+ FullMessageId full_message_id_;
tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
tl_object_ptr<td_api::location> location_;
+ int32 heading_;
+ int32 proximity_alert_radius_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->edit_inline_message_live_location(inline_message_id_, std::move(reply_markup_),
- std::move(location_), std::move(promise));
- }
-
- public:
- EditInlineMessageLiveLocationRequest(ActorShared<Td> td, uint64 request_id, string inline_message_id,
- tl_object_ptr<td_api::ReplyMarkup> reply_markup,
- tl_object_ptr<td_api::location> location)
- : RequestOnceActor(std::move(td), request_id)
- , inline_message_id_(std::move(inline_message_id))
- , reply_markup_(std::move(reply_markup))
- , location_(std::move(location)) {
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->edit_message_live_location(full_message_id_, std::move(reply_markup_), std::move(location_),
+ heading_, proximity_alert_radius_, std::move(promise));
}
-};
-
-class EditInlineMessageCaptionRequest : public RequestOnceActor {
- string inline_message_id_;
- tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
- tl_object_ptr<td_api::formattedText> caption_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->edit_inline_message_caption(inline_message_id_, std::move(reply_markup_),
- std::move(caption_), std::move(promise));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_message_object(full_message_id_, "EditMessageLiveLocationRequest"));
}
public:
- EditInlineMessageCaptionRequest(ActorShared<Td> td, uint64 request_id, string inline_message_id,
- tl_object_ptr<td_api::ReplyMarkup> reply_markup,
- tl_object_ptr<td_api::formattedText> caption)
+ EditMessageLiveLocationRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
+ tl_object_ptr<td_api::ReplyMarkup> reply_markup,
+ tl_object_ptr<td_api::location> location, int32 heading, int32 proximity_alert_radius)
: RequestOnceActor(std::move(td), request_id)
- , inline_message_id_(std::move(inline_message_id))
+ , full_message_id_(DialogId(dialog_id), MessageId(message_id))
, reply_markup_(std::move(reply_markup))
- , caption_(std::move(caption)) {
- }
-};
-
-class EditInlineMessageReplyMarkupRequest : public RequestOnceActor {
- string inline_message_id_;
- tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->edit_inline_message_reply_markup(inline_message_id_, std::move(reply_markup_),
- std::move(promise));
- }
-
- public:
- EditInlineMessageReplyMarkupRequest(ActorShared<Td> td, uint64 request_id, string inline_message_id,
- tl_object_ptr<td_api::ReplyMarkup> reply_markup)
- : RequestOnceActor(std::move(td), request_id)
- , inline_message_id_(std::move(inline_message_id))
- , reply_markup_(std::move(reply_markup)) {
+ , location_(std::move(location))
+ , heading_(heading)
+ , proximity_alert_radius_(proximity_alert_radius) {
}
};
-class SetGameScoreRequest : public RequestOnceActor {
+class EditMessageMediaRequest final : public RequestOnceActor {
FullMessageId full_message_id_;
- bool edit_message_;
- UserId user_id_;
- int32 score_;
- bool force_;
+ tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
+ tl_object_ptr<td_api::InputMessageContent> input_message_content_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->set_game_score(full_message_id_, edit_message_, user_id_, score_, force_,
- std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->edit_message_media(full_message_id_, std::move(reply_markup_),
+ std::move(input_message_content_), std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_message_object(full_message_id_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_message_object(full_message_id_, "EditMessageMediaRequest"));
}
public:
- SetGameScoreRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id, bool edit_message,
- int32 user_id, int32 score, bool force)
+ EditMessageMediaRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
+ tl_object_ptr<td_api::ReplyMarkup> reply_markup,
+ tl_object_ptr<td_api::InputMessageContent> input_message_content)
: RequestOnceActor(std::move(td), request_id)
, full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , edit_message_(edit_message)
- , user_id_(user_id)
- , score_(score)
- , force_(force) {
- }
-};
-
-class SetInlineGameScoreRequest : public RequestOnceActor {
- string inline_message_id_;
- bool edit_message_;
- UserId user_id_;
- int32 score_;
- bool force_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->set_inline_game_score(inline_message_id_, edit_message_, user_id_, score_, force_,
- std::move(promise));
- }
-
- public:
- SetInlineGameScoreRequest(ActorShared<Td> td, uint64 request_id, string inline_message_id, bool edit_message,
- int32 user_id, int32 score, bool force)
- : RequestOnceActor(std::move(td), request_id)
- , inline_message_id_(std::move(inline_message_id))
- , edit_message_(edit_message)
- , user_id_(user_id)
- , score_(score)
- , force_(force) {
+ , reply_markup_(std::move(reply_markup))
+ , input_message_content_(std::move(input_message_content)) {
}
};
-class GetGameHighScoresRequest : public RequestOnceActor {
+class EditMessageCaptionRequest final : public RequestOnceActor {
FullMessageId full_message_id_;
- UserId user_id_;
-
- int64 random_id_;
+ tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
+ tl_object_ptr<td_api::formattedText> caption_;
- void do_run(Promise<Unit> &&promise) override {
- random_id_ = td->messages_manager_->get_game_high_scores(full_message_id_, user_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->edit_message_caption(full_message_id_, std::move(reply_markup_), std::move(caption_),
+ std::move(promise));
}
- void do_send_result() override {
- CHECK(random_id_ != 0);
- send_result(td->messages_manager_->get_game_high_scores_object(random_id_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_message_object(full_message_id_, "EditMessageCaptionRequest"));
}
public:
- GetGameHighScoresRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id, int32 user_id)
+ EditMessageCaptionRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
+ tl_object_ptr<td_api::ReplyMarkup> reply_markup,
+ tl_object_ptr<td_api::formattedText> caption)
: RequestOnceActor(std::move(td), request_id)
, full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , user_id_(user_id)
- , random_id_(0) {
+ , reply_markup_(std::move(reply_markup))
+ , caption_(std::move(caption)) {
}
};
-class GetInlineGameHighScoresRequest : public RequestOnceActor {
- string inline_message_id_;
- UserId user_id_;
-
- int64 random_id_;
+class EditMessageReplyMarkupRequest final : public RequestOnceActor {
+ FullMessageId full_message_id_;
+ tl_object_ptr<td_api::ReplyMarkup> reply_markup_;
- void do_run(Promise<Unit> &&promise) override {
- random_id_ = td->messages_manager_->get_inline_game_high_scores(inline_message_id_, user_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->edit_message_reply_markup(full_message_id_, std::move(reply_markup_), std::move(promise));
}
- void do_send_result() override {
- CHECK(random_id_ != 0);
- send_result(td->messages_manager_->get_game_high_scores_object(random_id_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_message_object(full_message_id_, "EditMessageReplyMarkupRequest"));
}
public:
- GetInlineGameHighScoresRequest(ActorShared<Td> td, uint64 request_id, string inline_message_id, int32 user_id)
+ EditMessageReplyMarkupRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
+ tl_object_ptr<td_api::ReplyMarkup> reply_markup)
: RequestOnceActor(std::move(td), request_id)
- , inline_message_id_(std::move(inline_message_id))
- , user_id_(user_id)
- , random_id_(0) {
- }
-};
-
-class SendChatActionRequest : public RequestOnceActor {
- DialogId dialog_id_;
- tl_object_ptr<td_api::ChatAction> action_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->send_dialog_action(dialog_id_, action_, std::move(promise));
- }
-
- public:
- SendChatActionRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id,
- tl_object_ptr<td_api::ChatAction> &&action)
- : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id), action_(std::move(action)) {
+ , full_message_id_(DialogId(dialog_id), MessageId(message_id))
+ , reply_markup_(std::move(reply_markup)) {
}
};
-class GetChatHistoryRequest : public RequestActor<> {
+class GetChatHistoryRequest final : public RequestActor<> {
DialogId dialog_id_;
MessageId from_message_id_;
int32 offset_;
@@ -1493,12 +1247,12 @@ class GetChatHistoryRequest : public RequestActor<> {
tl_object_ptr<td_api::messages> messages_;
- void do_run(Promise<Unit> &&promise) override {
- messages_ = td->messages_manager_->get_dialog_history(dialog_id_, from_message_id_, offset_, limit_,
- get_tries() - 1, only_local_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ messages_ = td_->messages_manager_->get_dialog_history(dialog_id_, from_message_id_, offset_, limit_,
+ get_tries() - 1, only_local_, std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
send_result(std::move(messages_));
}
@@ -1517,45 +1271,94 @@ class GetChatHistoryRequest : public RequestActor<> {
}
};
-class DeleteChatHistoryRequest : public RequestOnceActor {
+class GetMessageThreadHistoryRequest final : public RequestActor<> {
DialogId dialog_id_;
- bool remove_from_chat_list_;
+ MessageId message_id_;
+ MessageId from_message_id_;
+ int32 offset_;
+ int32 limit_;
+ int64 random_id_;
+
+ std::pair<DialogId, vector<MessageId>> messages_;
+
+ void do_run(Promise<Unit> &&promise) final {
+ messages_ = td_->messages_manager_->get_message_thread_history(dialog_id_, message_id_, from_message_id_, offset_,
+ limit_, random_id_, std::move(promise));
+ }
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->delete_dialog_history(dialog_id_, remove_from_chat_list_, std::move(promise));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_messages_object(-1, messages_.first, messages_.second, true,
+ "GetMessageThreadHistoryRequest"));
}
public:
- DeleteChatHistoryRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, bool remove_from_chat_list)
- : RequestOnceActor(std::move(td), request_id)
+ GetMessageThreadHistoryRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
+ int64 from_message_id, int32 offset, int32 limit)
+ : RequestActor(std::move(td), request_id)
, dialog_id_(dialog_id)
- , remove_from_chat_list_(remove_from_chat_list) {
+ , message_id_(message_id)
+ , from_message_id_(from_message_id)
+ , offset_(offset)
+ , limit_(limit)
+ , random_id_(0) {
+ set_tries(3);
}
};
-class SearchChatMessagesRequest : public RequestActor<> {
+class GetChatMessageCalendarRequest final : public RequestActor<> {
+ DialogId dialog_id_;
+ MessageId from_message_id_;
+ MessageSearchFilter filter_;
+ int64 random_id_;
+
+ td_api::object_ptr<td_api::messageCalendar> calendar_;
+
+ void do_run(Promise<Unit> &&promise) final {
+ calendar_ = td_->messages_manager_->get_dialog_message_calendar(dialog_id_, from_message_id_, filter_, random_id_,
+ get_tries() == 3, std::move(promise));
+ }
+
+ void do_send_result() final {
+ send_result(std::move(calendar_));
+ }
+
+ public:
+ GetChatMessageCalendarRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 from_message_id,
+ tl_object_ptr<td_api::SearchMessagesFilter> filter)
+ : RequestActor(std::move(td), request_id)
+ , dialog_id_(dialog_id)
+ , from_message_id_(from_message_id)
+ , filter_(get_message_search_filter(filter))
+ , random_id_(0) {
+ set_tries(3);
+ }
+};
+
+class SearchChatMessagesRequest final : public RequestActor<> {
DialogId dialog_id_;
string query_;
- UserId sender_user_id_;
+ td_api::object_ptr<td_api::MessageSender> sender_id_;
MessageId from_message_id_;
int32 offset_;
int32 limit_;
- tl_object_ptr<td_api::SearchMessagesFilter> filter_;
+ MessageSearchFilter filter_;
+ MessageId top_thread_message_id_;
int64 random_id_;
std::pair<int32, vector<MessageId>> messages_;
- void do_run(Promise<Unit> &&promise) override {
- messages_ = td->messages_manager_->search_dialog_messages(dialog_id_, query_, sender_user_id_, from_message_id_,
- offset_, limit_, filter_, random_id_, get_tries() == 3,
- std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ messages_ = td_->messages_manager_->search_dialog_messages(dialog_id_, query_, sender_id_, from_message_id_,
+ offset_, limit_, filter_, top_thread_message_id_,
+ random_id_, get_tries() == 3, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_messages_object(messages_.first, dialog_id_, messages_.second));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_messages_object(messages_.first, dialog_id_, messages_.second, true,
+ "SearchChatMessagesRequest"));
}
- void do_send_error(Status &&status) override {
+ void do_send_error(Status &&status) final {
if (status.message() == "SEARCH_QUERY_EMPTY") {
messages_.first = 0;
messages_.second.clear();
@@ -1565,80 +1368,82 @@ class SearchChatMessagesRequest : public RequestActor<> {
}
public:
- SearchChatMessagesRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, string query, int32 user_id,
- int64 from_message_id, int32 offset, int32 limit,
- tl_object_ptr<td_api::SearchMessagesFilter> filter)
+ SearchChatMessagesRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, string query,
+ td_api::object_ptr<td_api::MessageSender> sender_id, int64 from_message_id, int32 offset,
+ int32 limit, tl_object_ptr<td_api::SearchMessagesFilter> filter, int64 message_thread_id)
: RequestActor(std::move(td), request_id)
, dialog_id_(dialog_id)
, query_(std::move(query))
- , sender_user_id_(user_id)
+ , sender_id_(std::move(sender_id))
, from_message_id_(from_message_id)
, offset_(offset)
, limit_(limit)
- , filter_(std::move(filter))
+ , filter_(get_message_search_filter(filter))
+ , top_thread_message_id_(message_thread_id)
, random_id_(0) {
set_tries(3);
}
};
-class OfflineSearchMessagesRequest : public RequestActor<> {
+class SearchSecretMessagesRequest final : public RequestActor<> {
DialogId dialog_id_;
string query_;
- int64 from_search_id_;
+ string offset_;
int32 limit_;
- tl_object_ptr<td_api::SearchMessagesFilter> filter_;
+ MessageSearchFilter filter_;
int64 random_id_;
- std::pair<int64, vector<FullMessageId>> messages_;
+ MessagesManager::FoundMessages found_messages_;
- void do_run(Promise<Unit> &&promise) override {
- messages_ = td->messages_manager_->offline_search_messages(dialog_id_, query_, from_search_id_, limit_, filter_,
- random_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ found_messages_ = td_->messages_manager_->offline_search_messages(dialog_id_, query_, offset_, limit_, filter_,
+ random_id_, std::move(promise));
}
- void do_send_result() override {
- vector<tl_object_ptr<td_api::message>> result;
- result.reserve(messages_.second.size());
- for (auto full_message_id : messages_.second) {
- result.push_back(td->messages_manager_->get_message_object(full_message_id));
- }
-
- send_result(make_tl_object<td_api::foundMessages>(std::move(result), messages_.first));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_found_messages_object(found_messages_, "SearchSecretMessagesRequest"));
}
public:
- OfflineSearchMessagesRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, string query,
- int64 from_search_id, int32 limit, tl_object_ptr<td_api::SearchMessagesFilter> filter)
+ SearchSecretMessagesRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, string query, string offset,
+ int32 limit, tl_object_ptr<td_api::SearchMessagesFilter> filter)
: RequestActor(std::move(td), request_id)
, dialog_id_(dialog_id)
, query_(std::move(query))
- , from_search_id_(from_search_id)
+ , offset_(std::move(offset))
, limit_(limit)
- , filter_(std::move(filter))
+ , filter_(get_message_search_filter(filter))
, random_id_(0) {
}
};
-class SearchMessagesRequest : public RequestActor<> {
+class SearchMessagesRequest final : public RequestActor<> {
+ FolderId folder_id_;
+ bool ignore_folder_id_;
string query_;
int32 offset_date_;
DialogId offset_dialog_id_;
MessageId offset_message_id_;
int32 limit_;
+ MessageSearchFilter filter_;
+ int32 min_date_;
+ int32 max_date_;
int64 random_id_;
std::pair<int32, vector<FullMessageId>> messages_;
- void do_run(Promise<Unit> &&promise) override {
- messages_ = td->messages_manager_->search_messages(query_, offset_date_, offset_dialog_id_, offset_message_id_,
- limit_, random_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ messages_ = td_->messages_manager_->search_messages(folder_id_, ignore_folder_id_, query_, offset_date_,
+ offset_dialog_id_, offset_message_id_, limit_, filter_,
+ min_date_, max_date_, random_id_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_messages_object(messages_.first, messages_.second));
+ void do_send_result() final {
+ send_result(
+ td_->messages_manager_->get_messages_object(messages_.first, messages_.second, true, "SearchMessagesRequest"));
}
- void do_send_error(Status &&status) override {
+ void do_send_error(Status &&status) final {
if (status.message() == "SEARCH_QUERY_EMPTY") {
messages_.first = 0;
messages_.second.clear();
@@ -1648,19 +1453,25 @@ class SearchMessagesRequest : public RequestActor<> {
}
public:
- SearchMessagesRequest(ActorShared<Td> td, uint64 request_id, string query, int32 offset_date, int64 offset_dialog_id,
- int64 offset_message_id, int32 limit)
+ SearchMessagesRequest(ActorShared<Td> td, uint64 request_id, FolderId folder_id, bool ignore_folder_id, string query,
+ int32 offset_date, int64 offset_dialog_id, int64 offset_message_id, int32 limit,
+ tl_object_ptr<td_api::SearchMessagesFilter> &&filter, int32 min_date, int32 max_date)
: RequestActor(std::move(td), request_id)
+ , folder_id_(folder_id)
+ , ignore_folder_id_(ignore_folder_id)
, query_(std::move(query))
, offset_date_(offset_date)
, offset_dialog_id_(offset_dialog_id)
, offset_message_id_(offset_message_id)
, limit_(limit)
+ , filter_(get_message_search_filter(filter))
+ , min_date_(min_date)
+ , max_date_(max_date)
, random_id_(0) {
}
};
-class SearchCallMessagesRequest : public RequestActor<> {
+class SearchCallMessagesRequest final : public RequestActor<> {
MessageId from_message_id_;
int32 limit_;
bool only_missed_;
@@ -1668,13 +1479,14 @@ class SearchCallMessagesRequest : public RequestActor<> {
std::pair<int32, vector<FullMessageId>> messages_;
- void do_run(Promise<Unit> &&promise) override {
- messages_ = td->messages_manager_->search_call_messages(from_message_id_, limit_, only_missed_, random_id_,
- get_tries() == 3, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ messages_ = td_->messages_manager_->search_call_messages(from_message_id_, limit_, only_missed_, random_id_,
+ get_tries() == 3, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_messages_object(messages_.first, messages_.second));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_messages_object(messages_.first, messages_.second, true,
+ "SearchCallMessagesRequest"));
}
public:
@@ -1688,37 +1500,16 @@ class SearchCallMessagesRequest : public RequestActor<> {
}
};
-class SearchChatRecentLocationMessagesRequest : public RequestActor<> {
- DialogId dialog_id_;
- int32 limit_;
- int64 random_id_;
-
- std::pair<int32, vector<MessageId>> messages_;
-
- void do_run(Promise<Unit> &&promise) override {
- messages_ = td->messages_manager_->search_dialog_recent_location_messages(dialog_id_, limit_, random_id_,
- std::move(promise));
- }
-
- void do_send_result() override {
- send_result(td->messages_manager_->get_messages_object(messages_.first, dialog_id_, messages_.second));
- }
-
- public:
- SearchChatRecentLocationMessagesRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int32 limit)
- : RequestActor(std::move(td), request_id), dialog_id_(dialog_id), limit_(limit), random_id_(0) {
- }
-};
-
-class GetActiveLiveLocationMessagesRequest : public RequestActor<> {
+class GetActiveLiveLocationMessagesRequest final : public RequestActor<> {
vector<FullMessageId> full_message_ids_;
- void do_run(Promise<Unit> &&promise) override {
- full_message_ids_ = td->messages_manager_->get_active_live_location_messages(std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ full_message_ids_ = td_->messages_manager_->get_active_live_location_messages(std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_messages_object(-1, full_message_ids_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_messages_object(-1, full_message_ids_, true,
+ "GetActiveLiveLocationMessagesRequest"));
}
public:
@@ -1727,18 +1518,18 @@ class GetActiveLiveLocationMessagesRequest : public RequestActor<> {
}
};
-class GetChatMessageByDateRequest : public RequestOnceActor {
+class GetChatMessageByDateRequest final : public RequestOnceActor {
DialogId dialog_id_;
int32 date_;
int64 random_id_;
- void do_run(Promise<Unit> &&promise) override {
- random_id_ = td->messages_manager_->get_dialog_message_by_date(dialog_id_, date_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ random_id_ = td_->messages_manager_->get_dialog_message_by_date(dialog_id_, date_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_dialog_message_by_date_object(random_id_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_dialog_message_by_date_object(random_id_));
}
public:
@@ -1747,62 +1538,39 @@ class GetChatMessageByDateRequest : public RequestOnceActor {
}
};
-class DeleteMessagesRequest : public RequestOnceActor {
+class GetChatScheduledMessagesRequest final : public RequestActor<> {
DialogId dialog_id_;
- vector<MessageId> message_ids_;
- bool revoke_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->delete_messages(dialog_id_, message_ids_, revoke_, std::move(promise));
- }
+ vector<MessageId> message_ids_;
- public:
- DeleteMessagesRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, vector<int64> message_ids, bool revoke)
- : RequestOnceActor(std::move(td), request_id)
- , dialog_id_(dialog_id)
- , message_ids_(MessagesManager::get_message_ids(message_ids))
- , revoke_(revoke) {
+ void do_run(Promise<Unit> &&promise) final {
+ message_ids_ =
+ td_->messages_manager_->get_dialog_scheduled_messages(dialog_id_, get_tries() < 2, false, std::move(promise));
}
-};
-
-class DeleteChatMessagesFromUserRequest : public RequestOnceActor {
- DialogId dialog_id_;
- UserId user_id_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->delete_dialog_messages_from_user(dialog_id_, user_id_, std::move(promise));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_messages_object(-1, dialog_id_, message_ids_, true,
+ "GetChatScheduledMessagesRequest"));
}
public:
- DeleteChatMessagesFromUserRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int32 user_id)
- : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id), user_id_(user_id) {
- }
-};
-
-class ReadAllChatMentionsRequest : public RequestOnceActor {
- DialogId dialog_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->read_all_dialog_mentions(dialog_id_, std::move(promise));
- }
-
- public:
- ReadAllChatMentionsRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id)
- : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id) {
+ GetChatScheduledMessagesRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id)
+ : RequestActor(std::move(td), request_id), dialog_id_(dialog_id) {
+ set_tries(4);
}
};
-class GetWebPagePreviewRequest : public RequestOnceActor {
+class GetWebPagePreviewRequest final : public RequestOnceActor {
td_api::object_ptr<td_api::formattedText> text_;
int64 request_id_ = 0;
- void do_run(Promise<Unit> &&promise) override {
- request_id_ = td->web_pages_manager_->get_web_page_preview(std::move(text_), std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ request_id_ = td_->web_pages_manager_->get_web_page_preview(std::move(text_), std::move(promise));
}
- void do_send_result() override {
- send_result(td->web_pages_manager_->get_web_page_preview_result(request_id_));
+ void do_send_result() final {
+ send_result(td_->web_pages_manager_->get_web_page_preview_result(request_id_));
}
public:
@@ -1811,37 +1579,44 @@ class GetWebPagePreviewRequest : public RequestOnceActor {
}
};
-class GetWebPageInstantViewRequest : public RequestActor<> {
+class GetWebPageInstantViewRequest final : public RequestActor<WebPageId> {
string url_;
bool force_full_;
WebPageId web_page_id_;
- void do_run(Promise<Unit> &&promise) override {
- web_page_id_ = td->web_pages_manager_->get_web_page_instant_view(url_, force_full_, std::move(promise));
+ void do_run(Promise<WebPageId> &&promise) final {
+ if (get_tries() < 2) {
+ promise.set_value(std::move(web_page_id_));
+ return;
+ }
+ td_->web_pages_manager_->get_web_page_instant_view(url_, force_full_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->web_pages_manager_->get_web_page_instant_view_object(web_page_id_));
+ void do_set_result(WebPageId &&result) final {
+ web_page_id_ = result;
+ }
+
+ void do_send_result() final {
+ send_result(td_->web_pages_manager_->get_web_page_instant_view_object(web_page_id_));
}
public:
GetWebPageInstantViewRequest(ActorShared<Td> td, uint64 request_id, string url, bool force_full)
: RequestActor(std::move(td), request_id), url_(std::move(url)), force_full_(force_full) {
- set_tries(3);
}
};
-class CreateChatRequest : public RequestActor<> {
+class CreateChatRequest final : public RequestActor<> {
DialogId dialog_id_;
bool force_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->create_dialog(dialog_id_, force_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->messages_manager_->create_dialog(dialog_id_, force_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->messages_manager_->get_chat_object(dialog_id_));
+ void do_send_result() final {
+ send_result(td_->messages_manager_->get_chat_object(dialog_id_));
}
public:
@@ -1850,108 +1625,113 @@ class CreateChatRequest : public RequestActor<> {
}
};
-class CreateNewGroupChatRequest : public RequestActor<> {
+class CreateNewGroupChatRequest final : public RequestActor<> {
vector<UserId> user_ids_;
string title_;
int64 random_id_;
DialogId dialog_id_;
- void do_run(Promise<Unit> &&promise) override {
- dialog_id_ = td->messages_manager_->create_new_group_chat(user_ids_, title_, random_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_id_ = td_->messages_manager_->create_new_group_chat(user_ids_, title_, random_id_, std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
CHECK(dialog_id_.is_valid());
- send_result(td->messages_manager_->get_chat_object(dialog_id_));
+ send_result(td_->messages_manager_->get_chat_object(dialog_id_));
}
public:
- CreateNewGroupChatRequest(ActorShared<Td> td, uint64 request_id, vector<int32> user_ids, string title)
- : RequestActor(std::move(td), request_id), title_(std::move(title)), random_id_(0) {
- for (auto user_id : user_ids) {
- user_ids_.emplace_back(user_id);
- }
+ CreateNewGroupChatRequest(ActorShared<Td> td, uint64 request_id, vector<UserId> user_ids, string title)
+ : RequestActor(std::move(td), request_id)
+ , user_ids_(std::move(user_ids))
+ , title_(std::move(title))
+ , random_id_(0) {
}
};
-class CreateNewSecretChatRequest : public RequestActor<SecretChatId> {
+class CreateNewSecretChatRequest final : public RequestActor<SecretChatId> {
UserId user_id_;
SecretChatId secret_chat_id_;
- void do_run(Promise<SecretChatId> &&promise) override {
+ void do_run(Promise<SecretChatId> &&promise) final {
if (get_tries() < 2) {
promise.set_value(std::move(secret_chat_id_));
return;
}
- td->messages_manager_->create_new_secret_chat(user_id_, std::move(promise));
+ td_->messages_manager_->create_new_secret_chat(user_id_, std::move(promise));
}
- void do_set_result(SecretChatId &&result) override {
+ void do_set_result(SecretChatId &&result) final {
secret_chat_id_ = result;
LOG(INFO) << "New " << secret_chat_id_ << " created";
}
- void do_send_result() override {
+ void do_send_result() final {
CHECK(secret_chat_id_.is_valid());
- // SecretChatActor will send this update by himself.
+ // SecretChatActor will send this update by itself
// But since the update may still be on its way, we will update essential fields here.
- td->contacts_manager_->on_update_secret_chat(
+ td_->contacts_manager_->on_update_secret_chat(
secret_chat_id_, 0 /* no access_hash */, user_id_, SecretChatState::Unknown, true /* it is outbound chat */,
- -1 /* unknown ttl */, 0 /* unknown creation date */, "" /* no key_hash */, 0);
+ -1 /* unknown TTL */, 0 /* unknown creation date */, "" /* no key_hash */, 0, FolderId());
DialogId dialog_id(secret_chat_id_);
- td->messages_manager_->force_create_dialog(dialog_id, "create new secret chat");
- send_result(td->messages_manager_->get_chat_object(dialog_id));
+ td_->messages_manager_->force_create_dialog(dialog_id, "create new secret chat", true);
+ send_result(td_->messages_manager_->get_chat_object(dialog_id));
}
public:
- CreateNewSecretChatRequest(ActorShared<Td> td, uint64 request_id, int32 user_id)
+ CreateNewSecretChatRequest(ActorShared<Td> td, uint64 request_id, int64 user_id)
: RequestActor(std::move(td), request_id), user_id_(user_id) {
}
};
-class CreateNewSupergroupChatRequest : public RequestActor<> {
+class CreateNewSupergroupChatRequest final : public RequestActor<> {
string title_;
bool is_megagroup_;
string description_;
+ DialogLocation location_;
+ bool for_import_;
int64 random_id_;
DialogId dialog_id_;
- void do_run(Promise<Unit> &&promise) override {
- dialog_id_ = td->messages_manager_->create_new_channel_chat(title_, is_megagroup_, description_, random_id_,
- std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_id_ = td_->messages_manager_->create_new_channel_chat(title_, is_megagroup_, description_, location_,
+ for_import_, random_id_, std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
CHECK(dialog_id_.is_valid());
- send_result(td->messages_manager_->get_chat_object(dialog_id_));
+ send_result(td_->messages_manager_->get_chat_object(dialog_id_));
}
public:
CreateNewSupergroupChatRequest(ActorShared<Td> td, uint64 request_id, string title, bool is_megagroup,
- string description)
+ string description, td_api::object_ptr<td_api::chatLocation> &&location,
+ bool for_import)
: RequestActor(std::move(td), request_id)
, title_(std::move(title))
, is_megagroup_(is_megagroup)
, description_(std::move(description))
+ , location_(std::move(location))
+ , for_import_(for_import)
, random_id_(0) {
}
};
-class UpgradeGroupChatToSupergroupChatRequest : public RequestActor<> {
+class UpgradeGroupChatToSupergroupChatRequest final : public RequestActor<> {
string title_;
DialogId dialog_id_;
DialogId result_dialog_id_;
- void do_run(Promise<Unit> &&promise) override {
- result_dialog_id_ = td->messages_manager_->migrate_dialog_to_megagroup(dialog_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ result_dialog_id_ = td_->messages_manager_->migrate_dialog_to_megagroup(dialog_id_, std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
CHECK(result_dialog_id_.is_valid());
- send_result(td->messages_manager_->get_chat_object(result_dialog_id_));
+ send_result(td_->messages_manager_->get_chat_object(result_dialog_id_));
}
public:
@@ -1960,196 +1740,15 @@ class UpgradeGroupChatToSupergroupChatRequest : public RequestActor<> {
}
};
-class SetChatPhotoRequest : public RequestOnceActor {
- DialogId dialog_id_;
- tl_object_ptr<td_api::InputFile> input_file_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->set_dialog_photo(dialog_id_, input_file_, std::move(promise));
- }
-
- public:
- SetChatPhotoRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id,
- tl_object_ptr<td_api::InputFile> &&input_file)
- : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id), input_file_(std::move(input_file)) {
- }
-};
-
-class SetChatTitleRequest : public RequestOnceActor {
- DialogId dialog_id_;
- string title_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->set_dialog_title(dialog_id_, title_, std::move(promise));
- }
-
- public:
- SetChatTitleRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, string &&title)
- : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id), title_(std::move(title)) {
- }
-};
-
-class AddChatMemberRequest : public RequestOnceActor {
- DialogId dialog_id_;
- UserId user_id_;
- int32 forward_limit_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->add_dialog_participant(dialog_id_, user_id_, forward_limit_, std::move(promise));
- }
-
- public:
- AddChatMemberRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int32 user_id, int32 forward_limit)
- : RequestOnceActor(std::move(td), request_id)
- , dialog_id_(dialog_id)
- , user_id_(user_id)
- , forward_limit_(forward_limit) {
- }
-};
-
-class AddChatMembersRequest : public RequestOnceActor {
- DialogId dialog_id_;
- vector<UserId> user_ids_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->add_dialog_participants(dialog_id_, user_ids_, std::move(promise));
- }
-
- public:
- AddChatMembersRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, const vector<int32> &user_ids)
- : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id) {
- for (auto &user_id : user_ids) {
- user_ids_.emplace_back(user_id);
- }
- }
-};
-
-class SetChatMemberStatusRequest : public RequestOnceActor {
- DialogId dialog_id_;
- UserId user_id_;
- tl_object_ptr<td_api::ChatMemberStatus> status_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->set_dialog_participant_status(dialog_id_, user_id_, status_, std::move(promise));
- }
-
- public:
- SetChatMemberStatusRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int32 user_id,
- tl_object_ptr<td_api::ChatMemberStatus> &&status)
- : RequestOnceActor(std::move(td), request_id)
- , dialog_id_(dialog_id)
- , user_id_(user_id)
- , status_(std::move(status)) {
- }
-};
-
-class GetChatMemberRequest : public RequestActor<> {
- DialogId dialog_id_;
- UserId user_id_;
- int64 random_id_;
-
- DialogParticipant dialog_participant_;
-
- void do_run(Promise<Unit> &&promise) override {
- dialog_participant_ = td->messages_manager_->get_dialog_participant(dialog_id_, user_id_, random_id_,
- get_tries() < 3, std::move(promise));
- }
-
- void do_send_result() override {
- if (!td->contacts_manager_->have_user(user_id_)) {
- return send_error(Status::Error(3, "User not found"));
- }
- send_result(td->contacts_manager_->get_chat_member_object(dialog_participant_));
- }
-
- void do_send_error(Status &&status) override {
- send_error(std::move(status));
- }
-
- public:
- GetChatMemberRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int32 user_id)
- : RequestActor(std::move(td), request_id), dialog_id_(dialog_id), user_id_(user_id), random_id_(0) {
- set_tries(3);
- }
-};
-
-class SearchChatMembersRequest : public RequestActor<> {
- DialogId dialog_id_;
- string query_;
- int32 limit_;
- int64 random_id_ = 0;
-
- std::pair<int32, vector<DialogParticipant>> participants_;
-
- void do_run(Promise<Unit> &&promise) override {
- participants_ = td->messages_manager_->search_dialog_participants(dialog_id_, query_, limit_, random_id_,
- get_tries() < 3, std::move(promise));
- }
-
- void do_send_result() override {
- // TODO create function get_chat_members_object
- vector<tl_object_ptr<td_api::chatMember>> result;
- result.reserve(participants_.second.size());
- for (auto participant : participants_.second) {
- result.push_back(td->contacts_manager_->get_chat_member_object(participant));
- }
-
- send_result(make_tl_object<td_api::chatMembers>(participants_.first, std::move(result)));
- }
-
- public:
- SearchChatMembersRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, string &&query, int32 limit)
- : RequestActor(std::move(td), request_id), dialog_id_(dialog_id), query_(std::move(query)), limit_(limit) {
- set_tries(3);
- }
-};
-
-class GetChatAdministratorsRequest : public RequestActor<> {
- DialogId dialog_id_;
-
- vector<UserId> user_ids_;
-
- void do_run(Promise<Unit> &&promise) override {
- user_ids_ = td->messages_manager_->get_dialog_administrators(dialog_id_, get_tries(), std::move(promise));
- }
-
- void do_send_result() override {
- send_result(td->contacts_manager_->get_users_object(-1, user_ids_));
- }
-
- public:
- GetChatAdministratorsRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id)
- : RequestActor(std::move(td), request_id), dialog_id_(dialog_id) {
- set_tries(3);
- }
-};
-
-class GenerateChatInviteLinkRequest : public RequestOnceActor {
- DialogId dialog_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->export_dialog_invite_link(dialog_id_, std::move(promise));
- }
-
- void do_send_result() override {
- send_result(make_tl_object<td_api::chatInviteLink>(td->messages_manager_->get_dialog_invite_link(dialog_id_)));
- }
-
- public:
- GenerateChatInviteLinkRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id)
- : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id) {
- }
-};
-
-class CheckChatInviteLinkRequest : public RequestActor<> {
+class CheckChatInviteLinkRequest final : public RequestActor<> {
string invite_link_;
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->check_dialog_invite_link(invite_link_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->contacts_manager_->check_dialog_invite_link(invite_link_, get_tries() < 2, std::move(promise));
}
- void do_send_result() override {
- auto result = td->contacts_manager_->get_chat_invite_link_info_object(invite_link_);
+ void do_send_result() final {
+ auto result = td_->contacts_manager_->get_chat_invite_link_info_object(invite_link_);
CHECK(result != nullptr);
send_result(std::move(result));
}
@@ -2160,27 +1759,27 @@ class CheckChatInviteLinkRequest : public RequestActor<> {
}
};
-class JoinChatByInviteLinkRequest : public RequestActor<DialogId> {
+class JoinChatByInviteLinkRequest final : public RequestActor<DialogId> {
string invite_link_;
DialogId dialog_id_;
- void do_run(Promise<DialogId> &&promise) override {
+ void do_run(Promise<DialogId> &&promise) final {
if (get_tries() < 2) {
promise.set_value(std::move(dialog_id_));
return;
}
- td->contacts_manager_->import_dialog_invite_link(invite_link_, std::move(promise));
+ td_->contacts_manager_->import_dialog_invite_link(invite_link_, std::move(promise));
}
- void do_set_result(DialogId &&result) override {
+ void do_set_result(DialogId &&result) final {
dialog_id_ = result;
}
- void do_send_result() override {
+ void do_send_result() final {
CHECK(dialog_id_.is_valid());
- td->messages_manager_->force_create_dialog(dialog_id_, "join chat by invite link");
- send_result(td->messages_manager_->get_chat_object(dialog_id_));
+ td_->messages_manager_->force_create_dialog(dialog_id_, "join chat via an invite link");
+ send_result(td_->messages_manager_->get_chat_object(dialog_id_));
}
public:
@@ -2189,101 +1788,46 @@ class JoinChatByInviteLinkRequest : public RequestActor<DialogId> {
}
};
-class GetChatEventLogRequest : public RequestOnceActor {
- DialogId dialog_id_;
- string query_;
- int64 from_event_id_;
- int32 limit_;
- tl_object_ptr<td_api::chatEventLogFilters> filters_;
- vector<UserId> user_ids_;
-
- int64 random_id_ = 0;
-
- void do_run(Promise<Unit> &&promise) override {
- random_id_ = td->messages_manager_->get_dialog_event_log(dialog_id_, query_, from_event_id_, limit_, filters_,
- user_ids_, std::move(promise));
- }
-
- void do_send_result() override {
- CHECK(random_id_ != 0);
- send_result(td->messages_manager_->get_chat_events_object(random_id_));
- }
-
- public:
- GetChatEventLogRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, string &&query, int64 from_event_id,
- int32 limit, tl_object_ptr<td_api::chatEventLogFilters> &&filters, vector<int32> user_ids)
- : RequestOnceActor(std::move(td), request_id)
- , dialog_id_(dialog_id)
- , query_(std::move(query))
- , from_event_id_(from_event_id)
- , limit_(limit)
- , filters_(std::move(filters)) {
- for (auto user_id : user_ids) {
- user_ids_.emplace_back(user_id);
- }
- }
-};
-
-class GetBlockedUsersRequest : public RequestOnceActor {
- int32 offset_;
- int32 limit_;
-
- int64 random_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- random_id_ = td->contacts_manager_->get_blocked_users(offset_, limit_, std::move(promise));
- }
-
- void do_send_result() override {
- send_result(td->contacts_manager_->get_blocked_users_object(random_id_));
- }
-
- public:
- GetBlockedUsersRequest(ActorShared<Td> td, uint64 request_id, int32 offset, int32 limit)
- : RequestOnceActor(std::move(td), request_id), offset_(offset), limit_(limit), random_id_(0) {
- }
-};
-
-class ImportContactsRequest : public RequestActor<> {
- vector<tl_object_ptr<td_api::contact>> contacts_;
+class ImportContactsRequest final : public RequestActor<> {
+ vector<Contact> contacts_;
int64 random_id_;
std::pair<vector<UserId>, vector<int32>> imported_contacts_;
- void do_run(Promise<Unit> &&promise) override {
- imported_contacts_ = td->contacts_manager_->import_contacts(contacts_, random_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ imported_contacts_ = td_->contacts_manager_->import_contacts(contacts_, random_id_, std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
CHECK(imported_contacts_.first.size() == contacts_.size());
CHECK(imported_contacts_.second.size() == contacts_.size());
send_result(make_tl_object<td_api::importedContacts>(transform(imported_contacts_.first,
[this](UserId user_id) {
- return td->contacts_manager_->get_user_id_object(
+ return td_->contacts_manager_->get_user_id_object(
user_id, "ImportContactsRequest");
}),
std::move(imported_contacts_.second)));
}
public:
- ImportContactsRequest(ActorShared<Td> td, uint64 request_id, vector<tl_object_ptr<td_api::contact>> &&contacts)
+ ImportContactsRequest(ActorShared<Td> td, uint64 request_id, vector<Contact> &&contacts)
: RequestActor(std::move(td), request_id), contacts_(std::move(contacts)), random_id_(0) {
set_tries(3); // load_contacts + import_contacts
}
};
-class SearchContactsRequest : public RequestActor<> {
+class SearchContactsRequest final : public RequestActor<> {
string query_;
int32 limit_;
std::pair<int32, vector<UserId>> user_ids_;
- void do_run(Promise<Unit> &&promise) override {
- user_ids_ = td->contacts_manager_->search_contacts(query_, limit_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ user_ids_ = td_->contacts_manager_->search_contacts(query_, limit_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_users_object(user_ids_.first, user_ids_.second));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_users_object(user_ids_.first, user_ids_.second));
}
public:
@@ -2292,31 +1836,28 @@ class SearchContactsRequest : public RequestActor<> {
}
};
-class RemoveContactsRequest : public RequestActor<> {
+class RemoveContactsRequest final : public RequestActor<> {
vector<UserId> user_ids_;
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->remove_contacts(user_ids_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->contacts_manager_->remove_contacts(user_ids_, std::move(promise));
}
public:
- RemoveContactsRequest(ActorShared<Td> td, uint64 request_id, vector<int32> &&user_ids)
- : RequestActor(std::move(td), request_id) {
- for (auto user_id : user_ids) {
- user_ids_.emplace_back(user_id);
- }
+ RemoveContactsRequest(ActorShared<Td> td, uint64 request_id, vector<UserId> &&user_ids)
+ : RequestActor(std::move(td), request_id), user_ids_(std::move(user_ids)) {
set_tries(3); // load_contacts + delete_contacts
}
};
-class GetImportedContactCountRequest : public RequestActor<> {
+class GetImportedContactCountRequest final : public RequestActor<> {
int32 imported_contact_count_ = 0;
- void do_run(Promise<Unit> &&promise) override {
- imported_contact_count_ = td->contacts_manager_->get_imported_contact_count(std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ imported_contact_count_ = td_->contacts_manager_->get_imported_contact_count(std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
send_result(td_api::make_object<td_api::count>(imported_contact_count_));
}
@@ -2325,32 +1866,30 @@ class GetImportedContactCountRequest : public RequestActor<> {
}
};
-class ChangeImportedContactsRequest : public RequestActor<> {
- vector<tl_object_ptr<td_api::contact>> contacts_;
+class ChangeImportedContactsRequest final : public RequestActor<> {
+ vector<Contact> contacts_;
size_t contacts_size_;
int64 random_id_;
std::pair<vector<UserId>, vector<int32>> imported_contacts_;
- void do_run(Promise<Unit> &&promise) override {
- imported_contacts_ =
- td->contacts_manager_->change_imported_contacts(std::move(contacts_), random_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ imported_contacts_ = td_->contacts_manager_->change_imported_contacts(contacts_, random_id_, std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
CHECK(imported_contacts_.first.size() == contacts_size_);
CHECK(imported_contacts_.second.size() == contacts_size_);
send_result(make_tl_object<td_api::importedContacts>(transform(imported_contacts_.first,
[this](UserId user_id) {
- return td->contacts_manager_->get_user_id_object(
+ return td_->contacts_manager_->get_user_id_object(
user_id, "ChangeImportedContactsRequest");
}),
std::move(imported_contacts_.second)));
}
public:
- ChangeImportedContactsRequest(ActorShared<Td> td, uint64 request_id,
- vector<tl_object_ptr<td_api::contact>> &&contacts)
+ ChangeImportedContactsRequest(ActorShared<Td> td, uint64 request_id, vector<Contact> &&contacts)
: RequestActor(std::move(td), request_id)
, contacts_(std::move(contacts))
, contacts_size_(contacts_.size())
@@ -2359,26 +1898,15 @@ class ChangeImportedContactsRequest : public RequestActor<> {
}
};
-class ClearImportedContactsRequest : public RequestActor<> {
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->clear_imported_contacts(std::move(promise));
- }
-
- public:
- ClearImportedContactsRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
- set_tries(3); // load_contacts + clear
- }
-};
-
-class GetRecentInlineBotsRequest : public RequestActor<> {
+class GetRecentInlineBotsRequest final : public RequestActor<> {
vector<UserId> user_ids_;
- void do_run(Promise<Unit> &&promise) override {
- user_ids_ = td->inline_queries_manager_->get_recent_inline_bots(std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ user_ids_ = td_->inline_queries_manager_->get_recent_inline_bots(std::move(promise));
}
- void do_send_result() override {
- send_result(td->contacts_manager_->get_users_object(-1, user_ids_));
+ void do_send_result() final {
+ send_result(td_->contacts_manager_->get_users_object(-1, user_ids_));
}
public:
@@ -2386,492 +1914,189 @@ class GetRecentInlineBotsRequest : public RequestActor<> {
}
};
-class SetNameRequest : public RequestOnceActor {
- string first_name_;
- string last_name_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->set_name(first_name_, last_name_, std::move(promise));
- }
-
- public:
- SetNameRequest(ActorShared<Td> td, uint64 request_id, string first_name, string last_name)
- : RequestOnceActor(std::move(td), request_id)
- , first_name_(std::move(first_name))
- , last_name_(std::move(last_name)) {
- }
-};
-
-class SetBioRequest : public RequestOnceActor {
- string bio_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->set_bio(bio_, std::move(promise));
- }
-
- public:
- SetBioRequest(ActorShared<Td> td, uint64 request_id, string bio)
- : RequestOnceActor(std::move(td), request_id), bio_(std::move(bio)) {
- }
-};
-
-class SetUsernameRequest : public RequestOnceActor {
- string username_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->set_username(username_, std::move(promise));
- }
-
- public:
- SetUsernameRequest(ActorShared<Td> td, uint64 request_id, string username)
- : RequestOnceActor(std::move(td), request_id), username_(std::move(username)) {
- }
-};
-
-class SetProfilePhotoRequest : public RequestOnceActor {
- tl_object_ptr<td_api::InputFile> input_file_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->set_profile_photo(input_file_, std::move(promise));
- }
-
- public:
- SetProfilePhotoRequest(ActorShared<Td> td, uint64 request_id, tl_object_ptr<td_api::InputFile> &&input_file)
- : RequestOnceActor(std::move(td), request_id), input_file_(std::move(input_file)) {
- }
-};
-
-class DeleteProfilePhotoRequest : public RequestOnceActor {
- int64 profile_photo_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->delete_profile_photo(profile_photo_id_, std::move(promise));
- }
-
- public:
- DeleteProfilePhotoRequest(ActorShared<Td> td, uint64 request_id, int64 profile_photo_id)
- : RequestOnceActor(std::move(td), request_id), profile_photo_id_(profile_photo_id) {
- }
-};
-
-class ToggleGroupAdministratorsRequest : public RequestOnceActor {
- ChatId chat_id_;
- bool everyone_is_administrator_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->toggle_chat_administrators(chat_id_, everyone_is_administrator_, std::move(promise));
- }
-
- public:
- ToggleGroupAdministratorsRequest(ActorShared<Td> td, uint64 request_id, int32 chat_id, bool everyone_is_administrator)
- : RequestOnceActor(std::move(td), request_id)
- , chat_id_(chat_id)
- , everyone_is_administrator_(everyone_is_administrator) {
- }
-};
-
-class SetSupergroupUsernameRequest : public RequestOnceActor {
- ChannelId channel_id_;
- string username_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->set_channel_username(channel_id_, username_, std::move(promise));
- }
-
- public:
- SetSupergroupUsernameRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id, string username)
- : RequestOnceActor(std::move(td), request_id), channel_id_(channel_id), username_(std::move(username)) {
- }
-};
-
-class SetSupergroupStickerSetRequest : public RequestOnceActor {
- ChannelId channel_id_;
- int64 sticker_set_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->set_channel_sticker_set(channel_id_, sticker_set_id_, std::move(promise));
- }
-
- public:
- SetSupergroupStickerSetRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id, int64 sticker_set_id)
- : RequestOnceActor(std::move(td), request_id), channel_id_(channel_id), sticker_set_id_(sticker_set_id) {
- }
-};
-
-class ToggleSupergroupInvitesRequest : public RequestOnceActor {
- ChannelId channel_id_;
- bool anyone_can_invite_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->toggle_channel_invites(channel_id_, anyone_can_invite_, std::move(promise));
- }
-
- public:
- ToggleSupergroupInvitesRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id, bool anyone_can_invite)
- : RequestOnceActor(std::move(td), request_id), channel_id_(channel_id), anyone_can_invite_(anyone_can_invite) {
- }
-};
-
-class ToggleSupergroupSignMessagesRequest : public RequestOnceActor {
- ChannelId channel_id_;
- bool sign_messages_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->toggle_channel_sign_messages(channel_id_, sign_messages_, std::move(promise));
- }
-
- public:
- ToggleSupergroupSignMessagesRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id, bool sign_messages)
- : RequestOnceActor(std::move(td), request_id), channel_id_(channel_id), sign_messages_(sign_messages) {
- }
-};
-
-class ToggleSupergroupIsAllHistoryAvailableRequest : public RequestOnceActor {
- ChannelId channel_id_;
- bool is_all_history_available_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->toggle_channel_is_all_history_available(channel_id_, is_all_history_available_,
- std::move(promise));
- }
-
- public:
- ToggleSupergroupIsAllHistoryAvailableRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id,
- bool is_all_history_available)
- : RequestOnceActor(std::move(td), request_id)
- , channel_id_(channel_id)
- , is_all_history_available_(is_all_history_available) {
- }
-};
-
-class SetSupergroupDescriptionRequest : public RequestOnceActor {
- ChannelId channel_id_;
- string description_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->set_channel_description(channel_id_, description_, std::move(promise));
- }
-
- public:
- SetSupergroupDescriptionRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id, string description)
- : RequestOnceActor(std::move(td), request_id), channel_id_(channel_id), description_(std::move(description)) {
- }
-};
-
-class PinSupergroupMessageRequest : public RequestOnceActor {
- ChannelId channel_id_;
- MessageId message_id_;
- bool disable_notification_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->pin_channel_message(channel_id_, message_id_, disable_notification_, std::move(promise));
- }
-
- public:
- PinSupergroupMessageRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id, int64 message_id,
- bool disable_notification)
- : RequestOnceActor(std::move(td), request_id)
- , channel_id_(channel_id)
- , message_id_(message_id)
- , disable_notification_(disable_notification) {
- }
-};
-
-class UnpinSupergroupMessageRequest : public RequestOnceActor {
- ChannelId channel_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->unpin_channel_message(channel_id_, std::move(promise));
- }
-
- public:
- UnpinSupergroupMessageRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id)
- : RequestOnceActor(std::move(td), request_id), channel_id_(channel_id) {
- }
-};
-
-class ReportSupergroupSpamRequest : public RequestOnceActor {
- ChannelId channel_id_;
- UserId user_id_;
- vector<MessageId> message_ids_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->report_channel_spam(channel_id_, user_id_, message_ids_, std::move(promise));
- }
-
- public:
- ReportSupergroupSpamRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id, int32 user_id,
- const vector<int64> &message_ids)
- : RequestOnceActor(std::move(td), request_id)
- , channel_id_(channel_id)
- , user_id_(user_id)
- , message_ids_(MessagesManager::get_message_ids(message_ids)) {
- }
-};
-
-class GetSupergroupMembersRequest : public RequestActor<> {
- ChannelId channel_id_;
- tl_object_ptr<td_api::SupergroupMembersFilter> filter_;
- int32 offset_;
- int32 limit_;
- int64 random_id_ = 0;
-
- std::pair<int32, vector<DialogParticipant>> participants_;
-
- void do_run(Promise<Unit> &&promise) override {
- participants_ = td->contacts_manager_->get_channel_participants(channel_id_, filter_, offset_, limit_, random_id_,
- get_tries() < 3, std::move(promise));
- }
-
- void do_send_result() override {
- // TODO create function get_chat_members_object
- vector<tl_object_ptr<td_api::chatMember>> result;
- result.reserve(participants_.second.size());
- for (auto participant : participants_.second) {
- result.push_back(td->contacts_manager_->get_chat_member_object(participant));
- }
-
- send_result(make_tl_object<td_api::chatMembers>(participants_.first, std::move(result)));
- }
-
- public:
- GetSupergroupMembersRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id,
- tl_object_ptr<td_api::SupergroupMembersFilter> &&filter, int32 offset, int32 limit)
- : RequestActor(std::move(td), request_id)
- , channel_id_(channel_id)
- , filter_(std::move(filter))
- , offset_(offset)
- , limit_(limit) {
- set_tries(3);
- }
-};
-
-class DeleteSupergroupRequest : public RequestOnceActor {
- ChannelId channel_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->contacts_manager_->delete_channel(channel_id_, std::move(promise));
- }
-
- public:
- DeleteSupergroupRequest(ActorShared<Td> td, uint64 request_id, int32 channel_id)
- : RequestOnceActor(std::move(td), request_id), channel_id_(channel_id) {
- }
-};
-
-class GetUserProfilePhotosRequest : public RequestActor<> {
+class GetUserProfilePhotosRequest final : public RequestActor<> {
UserId user_id_;
int32 offset_;
int32 limit_;
std::pair<int32, vector<const Photo *>> photos;
- void do_run(Promise<Unit> &&promise) override {
- photos = td->contacts_manager_->get_user_profile_photos(user_id_, offset_, limit_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ photos = td_->contacts_manager_->get_user_profile_photos(user_id_, offset_, limit_, std::move(promise));
}
- void do_send_result() override {
+ void do_send_result() final {
// TODO create function get_user_profile_photos_object
- vector<tl_object_ptr<td_api::photo>> result;
- result.reserve(photos.second.size());
- for (auto photo : photos.second) {
- result.push_back(get_photo_object(td->file_manager_.get(), photo));
- }
+ auto result = transform(photos.second, [file_manager = td_->file_manager_.get()](const Photo *photo) {
+ CHECK(photo != nullptr);
+ CHECK(!photo->is_empty());
+ return get_chat_photo_object(file_manager, *photo);
+ });
- send_result(make_tl_object<td_api::userProfilePhotos>(photos.first, std::move(result)));
+ send_result(make_tl_object<td_api::chatPhotos>(photos.first, std::move(result)));
}
public:
- GetUserProfilePhotosRequest(ActorShared<Td> td, uint64 request_id, int32 user_id, int32 offset, int32 limit)
+ GetUserProfilePhotosRequest(ActorShared<Td> td, uint64 request_id, int64 user_id, int32 offset, int32 limit)
: RequestActor(std::move(td), request_id), user_id_(user_id), offset_(offset), limit_(limit) {
}
};
-class GetNotificationSettingsRequest : public RequestActor<> {
+class GetChatNotificationSettingsExceptionsRequest final : public RequestActor<> {
NotificationSettingsScope scope_;
+ bool filter_scope_;
+ bool compare_sound_;
- const NotificationSettings *notification_settings_ = nullptr;
-
- void do_run(Promise<Unit> &&promise) override {
- notification_settings_ = td->messages_manager_->get_notification_settings(scope_, std::move(promise));
- }
-
- void do_send_result() override {
- CHECK(notification_settings_ != nullptr);
- send_result(td->messages_manager_->get_notification_settings_object(notification_settings_));
- }
-
- public:
- GetNotificationSettingsRequest(ActorShared<Td> td, uint64 request_id, NotificationSettingsScope scope)
- : RequestActor(std::move(td), request_id), scope_(scope) {
- }
-};
-
-class GetChatReportSpamStateRequest : public RequestActor<> {
- DialogId dialog_id_;
-
- bool can_report_spam_ = false;
-
- void do_run(Promise<Unit> &&promise) override {
- can_report_spam_ = td->messages_manager_->get_dialog_report_spam_state(dialog_id_, std::move(promise));
- }
-
- void do_send_result() override {
- send_result(make_tl_object<td_api::chatReportSpamState>(can_report_spam_));
- }
-
- public:
- GetChatReportSpamStateRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id)
- : RequestActor(std::move(td), request_id), dialog_id_(dialog_id) {
- }
-};
-
-class ChangeChatReportSpamStateRequest : public RequestOnceActor {
- DialogId dialog_id_;
- bool is_spam_dialog_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->change_dialog_report_spam_state(dialog_id_, is_spam_dialog_, std::move(promise));
- }
+ vector<DialogId> dialog_ids_;
- public:
- ChangeChatReportSpamStateRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, bool is_spam_dialog)
- : RequestOnceActor(std::move(td), request_id), dialog_id_(dialog_id), is_spam_dialog_(is_spam_dialog) {
+ void do_run(Promise<Unit> &&promise) final {
+ dialog_ids_ = td_->messages_manager_->get_dialog_notification_settings_exceptions(
+ scope_, filter_scope_, compare_sound_, get_tries() < 3, std::move(promise));
}
-};
-
-class ReportChatRequest : public RequestOnceActor {
- DialogId dialog_id_;
- tl_object_ptr<td_api::ChatReportReason> reason_;
- vector<MessageId> message_ids_;
- void do_run(Promise<Unit> &&promise) override {
- td->messages_manager_->report_dialog(dialog_id_, reason_, message_ids_, std::move(promise));
+ void do_send_result() final {
+ send_result(MessagesManager::get_chats_object(-1, dialog_ids_));
}
public:
- ReportChatRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id,
- tl_object_ptr<td_api::ChatReportReason> reason, const vector<int64> &message_ids)
- : RequestOnceActor(std::move(td), request_id)
- , dialog_id_(dialog_id)
- , reason_(std::move(reason))
- , message_ids_(MessagesManager::get_message_ids(message_ids)) {
+ GetChatNotificationSettingsExceptionsRequest(ActorShared<Td> td, uint64 request_id, NotificationSettingsScope scope,
+ bool filter_scope, bool compare_sound)
+ : RequestActor(std::move(td), request_id)
+ , scope_(scope)
+ , filter_scope_(filter_scope)
+ , compare_sound_(compare_sound) {
+ set_tries(3);
}
};
-class GetStickersRequest : public RequestActor<> {
- string emoji_;
- int32 limit_;
+class GetScopeNotificationSettingsRequest final : public RequestActor<> {
+ NotificationSettingsScope scope_;
- vector<FileId> sticker_ids_;
+ const ScopeNotificationSettings *notification_settings_ = nullptr;
- void do_run(Promise<Unit> &&promise) override {
- sticker_ids_ = td->stickers_manager_->get_stickers(emoji_, limit_, get_tries() < 2, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ notification_settings_ =
+ td_->notification_settings_manager_->get_scope_notification_settings(scope_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_stickers_object(sticker_ids_));
+ void do_send_result() final {
+ CHECK(notification_settings_ != nullptr);
+ send_result(get_scope_notification_settings_object(notification_settings_));
}
public:
- GetStickersRequest(ActorShared<Td> td, uint64 request_id, string &&emoji, int32 limit)
- : RequestActor(std::move(td), request_id), emoji_(std::move(emoji)), limit_(limit) {
- set_tries(5);
+ GetScopeNotificationSettingsRequest(ActorShared<Td> td, uint64 request_id, NotificationSettingsScope scope)
+ : RequestActor(std::move(td), request_id), scope_(scope) {
}
};
-class SearchStickersRequest : public RequestActor<> {
- string emoji_;
+class GetStickersRequest final : public RequestActor<> {
+ StickerType sticker_type_;
+ string query_;
int32 limit_;
+ DialogId dialog_id_;
vector<FileId> sticker_ids_;
- void do_run(Promise<Unit> &&promise) override {
- sticker_ids_ = td->stickers_manager_->search_stickers(emoji_, limit_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ sticker_ids_ = td_->stickers_manager_->get_stickers(sticker_type_, query_, limit_, dialog_id_, get_tries() < 2,
+ std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_stickers_object(sticker_ids_));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_stickers_object(sticker_ids_));
}
public:
- SearchStickersRequest(ActorShared<Td> td, uint64 request_id, string &&emoji, int32 limit)
- : RequestActor(std::move(td), request_id), emoji_(std::move(emoji)), limit_(limit) {
+ GetStickersRequest(ActorShared<Td> td, uint64 request_id, StickerType sticker_type, string &&query, int32 limit,
+ int64 dialog_id)
+ : RequestActor(std::move(td), request_id)
+ , sticker_type_(sticker_type)
+ , query_(std::move(query))
+ , limit_(limit)
+ , dialog_id_(dialog_id) {
+ set_tries(4);
}
};
-class GetInstalledStickerSetsRequest : public RequestActor<> {
- bool is_masks_;
+class GetInstalledStickerSetsRequest final : public RequestActor<> {
+ StickerType sticker_type_;
- vector<int64> sticker_set_ids_;
+ vector<StickerSetId> sticker_set_ids_;
- void do_run(Promise<Unit> &&promise) override {
- sticker_set_ids_ = td->stickers_manager_->get_installed_sticker_sets(is_masks_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ sticker_set_ids_ = td_->stickers_manager_->get_installed_sticker_sets(sticker_type_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_sticker_sets_object(-1, sticker_set_ids_, 1));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_sticker_sets_object(-1, sticker_set_ids_, 1));
}
public:
- GetInstalledStickerSetsRequest(ActorShared<Td> td, uint64 request_id, bool is_masks)
- : RequestActor(std::move(td), request_id), is_masks_(is_masks) {
+ GetInstalledStickerSetsRequest(ActorShared<Td> td, uint64 request_id, StickerType sticker_type)
+ : RequestActor(std::move(td), request_id), sticker_type_(sticker_type) {
}
};
-class GetArchivedStickerSetsRequest : public RequestActor<> {
- bool is_masks_;
- int64 offset_sticker_set_id_;
+class GetArchivedStickerSetsRequest final : public RequestActor<> {
+ StickerType sticker_type_;
+ StickerSetId offset_sticker_set_id_;
int32 limit_;
- int32 total_count_;
- vector<int64> sticker_set_ids_;
+ int32 total_count_ = -1;
+ vector<StickerSetId> sticker_set_ids_;
- void do_run(Promise<Unit> &&promise) override {
- std::tie(total_count_, sticker_set_ids_) = td->stickers_manager_->get_archived_sticker_sets(
- is_masks_, offset_sticker_set_id_, limit_, get_tries() < 2, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ std::tie(total_count_, sticker_set_ids_) = td_->stickers_manager_->get_archived_sticker_sets(
+ sticker_type_, offset_sticker_set_id_, limit_, get_tries() < 2, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_sticker_sets_object(total_count_, sticker_set_ids_, 1));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_sticker_sets_object(total_count_, sticker_set_ids_, 1));
}
public:
- GetArchivedStickerSetsRequest(ActorShared<Td> td, uint64 request_id, bool is_masks, int64 offset_sticker_set_id,
- int32 limit)
+ GetArchivedStickerSetsRequest(ActorShared<Td> td, uint64 request_id, StickerType sticker_type,
+ int64 offset_sticker_set_id, int32 limit)
: RequestActor(std::move(td), request_id)
- , is_masks_(is_masks)
+ , sticker_type_(sticker_type)
, offset_sticker_set_id_(offset_sticker_set_id)
, limit_(limit) {
}
};
-class GetTrendingStickerSetsRequest : public RequestActor<> {
- vector<int64> sticker_set_ids_;
+class GetTrendingStickerSetsRequest final : public RequestActor<> {
+ td_api::object_ptr<td_api::trendingStickerSets> result_;
+ StickerType sticker_type_;
+ int32 offset_;
+ int32 limit_;
- void do_run(Promise<Unit> &&promise) override {
- sticker_set_ids_ = td->stickers_manager_->get_featured_sticker_sets(std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ result_ = td_->stickers_manager_->get_featured_sticker_sets(sticker_type_, offset_, limit_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_sticker_sets_object(-1, sticker_set_ids_, 5));
+ void do_send_result() final {
+ send_result(std::move(result_));
}
public:
- GetTrendingStickerSetsRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
+ GetTrendingStickerSetsRequest(ActorShared<Td> td, uint64 request_id, StickerType sticker_type, int32 offset,
+ int32 limit)
+ : RequestActor(std::move(td), request_id), sticker_type_(sticker_type), offset_(offset), limit_(limit) {
+ set_tries(3);
}
};
-class GetAttachedStickerSetsRequest : public RequestActor<> {
+class GetAttachedStickerSetsRequest final : public RequestActor<> {
FileId file_id_;
- vector<int64> sticker_set_ids_;
+ vector<StickerSetId> sticker_set_ids_;
- void do_run(Promise<Unit> &&promise) override {
- sticker_set_ids_ = td->stickers_manager_->get_attached_sticker_sets(file_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ sticker_set_ids_ = td_->stickers_manager_->get_attached_sticker_sets(file_id_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_sticker_sets_object(-1, sticker_set_ids_, 5));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_sticker_sets_object(-1, sticker_set_ids_, 5));
}
public:
@@ -2880,17 +2105,17 @@ class GetAttachedStickerSetsRequest : public RequestActor<> {
}
};
-class GetStickerSetRequest : public RequestActor<> {
- int64 set_id_;
+class GetStickerSetRequest final : public RequestActor<> {
+ StickerSetId set_id_;
- int64 sticker_set_id_;
+ StickerSetId sticker_set_id_;
- void do_run(Promise<Unit> &&promise) override {
- sticker_set_id_ = td->stickers_manager_->get_sticker_set(set_id_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ sticker_set_id_ = td_->stickers_manager_->get_sticker_set(set_id_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_sticker_set_object(sticker_set_id_));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_sticker_set_object(sticker_set_id_));
}
public:
@@ -2900,17 +2125,17 @@ class GetStickerSetRequest : public RequestActor<> {
}
};
-class SearchStickerSetRequest : public RequestActor<> {
+class SearchStickerSetRequest final : public RequestActor<> {
string name_;
- int64 sticker_set_id_;
+ StickerSetId sticker_set_id_;
- void do_run(Promise<Unit> &&promise) override {
- sticker_set_id_ = td->stickers_manager_->search_sticker_set(name_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ sticker_set_id_ = td_->stickers_manager_->search_sticker_set(name_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_sticker_set_object(sticker_set_id_));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_sticker_set_object(sticker_set_id_));
}
public:
@@ -2920,39 +2145,40 @@ class SearchStickerSetRequest : public RequestActor<> {
}
};
-class SearchInstalledStickerSetsRequest : public RequestActor<> {
- bool is_masks_;
+class SearchInstalledStickerSetsRequest final : public RequestActor<> {
+ StickerType sticker_type_;
string query_;
int32 limit_;
- std::pair<int32, vector<int64>> sticker_set_ids_;
+ std::pair<int32, vector<StickerSetId>> sticker_set_ids_;
- void do_run(Promise<Unit> &&promise) override {
+ void do_run(Promise<Unit> &&promise) final {
sticker_set_ids_ =
- td->stickers_manager_->search_installed_sticker_sets(is_masks_, query_, limit_, std::move(promise));
+ td_->stickers_manager_->search_installed_sticker_sets(sticker_type_, query_, limit_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_sticker_sets_object(sticker_set_ids_.first, sticker_set_ids_.second, 5));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_sticker_sets_object(sticker_set_ids_.first, sticker_set_ids_.second, 5));
}
public:
- SearchInstalledStickerSetsRequest(ActorShared<Td> td, uint64 request_id, bool is_masks, string &&query, int32 limit)
- : RequestActor(std::move(td), request_id), is_masks_(is_masks), query_(std::move(query)), limit_(limit) {
+ SearchInstalledStickerSetsRequest(ActorShared<Td> td, uint64 request_id, StickerType sticker_type, string &&query,
+ int32 limit)
+ : RequestActor(std::move(td), request_id), sticker_type_(sticker_type), query_(std::move(query)), limit_(limit) {
}
};
-class SearchStickerSetsRequest : public RequestActor<> {
+class SearchStickerSetsRequest final : public RequestActor<> {
string query_;
- vector<int64> sticker_set_ids_;
+ vector<StickerSetId> sticker_set_ids_;
- void do_run(Promise<Unit> &&promise) override {
- sticker_set_ids_ = td->stickers_manager_->search_sticker_sets(query_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ sticker_set_ids_ = td_->stickers_manager_->search_sticker_sets(query_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_sticker_sets_object(-1, sticker_set_ids_, 5));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_sticker_sets_object(-1, sticker_set_ids_, 5));
}
public:
@@ -2961,13 +2187,13 @@ class SearchStickerSetsRequest : public RequestActor<> {
}
};
-class ChangeStickerSetRequest : public RequestOnceActor {
- int64 set_id_;
+class ChangeStickerSetRequest final : public RequestOnceActor {
+ StickerSetId set_id_;
bool is_installed_;
bool is_archived_;
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->change_sticker_set(set_id_, is_installed_, is_archived_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->stickers_manager_->change_sticker_set(set_id_, is_installed_, is_archived_, std::move(promise));
}
public:
@@ -2980,140 +2206,38 @@ class ChangeStickerSetRequest : public RequestOnceActor {
}
};
-class ReorderInstalledStickerSetsRequest : public RequestOnceActor {
- bool is_masks_;
- vector<int64> sticker_set_ids_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->reorder_installed_sticker_sets(is_masks_, sticker_set_ids_, std::move(promise));
- }
-
- public:
- ReorderInstalledStickerSetsRequest(ActorShared<Td> td, uint64 request_id, bool is_masks,
- vector<int64> &&sticker_set_ids)
- : RequestOnceActor(std::move(td), request_id), is_masks_(is_masks), sticker_set_ids_(std::move(sticker_set_ids)) {
- }
-};
-
-class UploadStickerFileRequest : public RequestOnceActor {
+class UploadStickerFileRequest final : public RequestOnceActor {
UserId user_id_;
- tl_object_ptr<td_api::InputFile> sticker_;
+ tl_object_ptr<td_api::inputSticker> sticker_;
FileId file_id;
- void do_run(Promise<Unit> &&promise) override {
- file_id = td->stickers_manager_->upload_sticker_file(user_id_, sticker_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ file_id = td_->stickers_manager_->upload_sticker_file(user_id_, std::move(sticker_), std::move(promise));
}
- void do_send_result() override {
- send_result(td->file_manager_->get_file_object(file_id));
+ void do_send_result() final {
+ send_result(td_->file_manager_->get_file_object(file_id));
}
public:
- UploadStickerFileRequest(ActorShared<Td> td, uint64 request_id, int32 user_id,
- tl_object_ptr<td_api::InputFile> &&sticker)
+ UploadStickerFileRequest(ActorShared<Td> td, uint64 request_id, int64 user_id,
+ tl_object_ptr<td_api::inputSticker> &&sticker)
: RequestOnceActor(std::move(td), request_id), user_id_(user_id), sticker_(std::move(sticker)) {
}
};
-class CreateNewStickerSetRequest : public RequestOnceActor {
- UserId user_id_;
- string title_;
- string name_;
- bool is_masks_;
- vector<tl_object_ptr<td_api::inputSticker>> stickers_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->create_new_sticker_set(user_id_, title_, name_, is_masks_, std::move(stickers_),
- std::move(promise));
- }
-
- void do_send_result() override {
- auto set_id = td->stickers_manager_->search_sticker_set(name_, Auto());
- if (set_id == 0) {
- return send_error(Status::Error(500, "Created sticker set not found"));
- }
- send_result(td->stickers_manager_->get_sticker_set_object(set_id));
- }
-
- public:
- CreateNewStickerSetRequest(ActorShared<Td> td, uint64 request_id, int32 user_id, string &&title, string &&name,
- bool is_masks, vector<tl_object_ptr<td_api::inputSticker>> &&stickers)
- : RequestOnceActor(std::move(td), request_id)
- , user_id_(user_id)
- , title_(std::move(title))
- , name_(std::move(name))
- , is_masks_(is_masks)
- , stickers_(std::move(stickers)) {
- }
-};
-
-class AddStickerToSetRequest : public RequestOnceActor {
- UserId user_id_;
- string name_;
- tl_object_ptr<td_api::inputSticker> sticker_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->add_sticker_to_set(user_id_, name_, std::move(sticker_), std::move(promise));
- }
-
- void do_send_result() override {
- auto set_id = td->stickers_manager_->search_sticker_set(name_, Auto());
- if (set_id == 0) {
- return send_error(Status::Error(500, "Sticker set not found"));
- }
- send_result(td->stickers_manager_->get_sticker_set_object(set_id));
- }
-
- public:
- AddStickerToSetRequest(ActorShared<Td> td, uint64 request_id, int32 user_id, string &&name,
- tl_object_ptr<td_api::inputSticker> &&sticker)
- : RequestOnceActor(std::move(td), request_id)
- , user_id_(user_id)
- , name_(std::move(name))
- , sticker_(std::move(sticker)) {
- }
-};
-
-class SetStickerPositionInSetRequest : public RequestOnceActor {
- tl_object_ptr<td_api::InputFile> sticker_;
- int32 position_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->set_sticker_position_in_set(sticker_, position_, std::move(promise));
- }
-
- public:
- SetStickerPositionInSetRequest(ActorShared<Td> td, uint64 request_id, tl_object_ptr<td_api::InputFile> &&sticker,
- int32 position)
- : RequestOnceActor(std::move(td), request_id), sticker_(std::move(sticker)), position_(position) {
- }
-};
-
-class RemoveStickerFromSetRequest : public RequestOnceActor {
- tl_object_ptr<td_api::InputFile> sticker_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->remove_sticker_from_set(sticker_, std::move(promise));
- }
-
- public:
- RemoveStickerFromSetRequest(ActorShared<Td> td, uint64 request_id, tl_object_ptr<td_api::InputFile> &&sticker)
- : RequestOnceActor(std::move(td), request_id), sticker_(std::move(sticker)) {
- }
-};
-
-class GetRecentStickersRequest : public RequestActor<> {
+class GetRecentStickersRequest final : public RequestActor<> {
bool is_attached_;
vector<FileId> sticker_ids_;
- void do_run(Promise<Unit> &&promise) override {
- sticker_ids_ = td->stickers_manager_->get_recent_stickers(is_attached_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ sticker_ids_ = td_->stickers_manager_->get_recent_stickers(is_attached_, std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_stickers_object(sticker_ids_));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_stickers_object(sticker_ids_));
}
public:
@@ -3122,12 +2246,12 @@ class GetRecentStickersRequest : public RequestActor<> {
}
};
-class AddRecentStickerRequest : public RequestActor<> {
+class AddRecentStickerRequest final : public RequestActor<> {
bool is_attached_;
tl_object_ptr<td_api::InputFile> input_file_;
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->add_recent_sticker(is_attached_, input_file_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->stickers_manager_->add_recent_sticker(is_attached_, input_file_, std::move(promise));
}
public:
@@ -3138,12 +2262,12 @@ class AddRecentStickerRequest : public RequestActor<> {
}
};
-class RemoveRecentStickerRequest : public RequestActor<> {
+class RemoveRecentStickerRequest final : public RequestActor<> {
bool is_attached_;
tl_object_ptr<td_api::InputFile> input_file_;
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->remove_recent_sticker(is_attached_, input_file_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->stickers_manager_->remove_recent_sticker(is_attached_, input_file_, std::move(promise));
}
public:
@@ -3154,11 +2278,11 @@ class RemoveRecentStickerRequest : public RequestActor<> {
}
};
-class ClearRecentStickersRequest : public RequestActor<> {
+class ClearRecentStickersRequest final : public RequestActor<> {
bool is_attached_;
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->clear_recent_stickers(is_attached_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->stickers_manager_->clear_recent_stickers(is_attached_, std::move(promise));
}
public:
@@ -3168,15 +2292,15 @@ class ClearRecentStickersRequest : public RequestActor<> {
}
};
-class GetFavoriteStickersRequest : public RequestActor<> {
+class GetFavoriteStickersRequest final : public RequestActor<> {
vector<FileId> sticker_ids_;
- void do_run(Promise<Unit> &&promise) override {
- sticker_ids_ = td->stickers_manager_->get_favorite_stickers(std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ sticker_ids_ = td_->stickers_manager_->get_favorite_stickers(std::move(promise));
}
- void do_send_result() override {
- send_result(td->stickers_manager_->get_stickers_object(sticker_ids_));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_stickers_object(sticker_ids_));
}
public:
@@ -3184,11 +2308,11 @@ class GetFavoriteStickersRequest : public RequestActor<> {
}
};
-class AddFavoriteStickerRequest : public RequestOnceActor {
+class AddFavoriteStickerRequest final : public RequestOnceActor {
tl_object_ptr<td_api::InputFile> input_file_;
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->add_favorite_sticker(input_file_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->stickers_manager_->add_favorite_sticker(input_file_, std::move(promise));
}
public:
@@ -3198,11 +2322,11 @@ class AddFavoriteStickerRequest : public RequestOnceActor {
}
};
-class RemoveFavoriteStickerRequest : public RequestOnceActor {
+class RemoveFavoriteStickerRequest final : public RequestOnceActor {
tl_object_ptr<td_api::InputFile> input_file_;
- void do_run(Promise<Unit> &&promise) override {
- td->stickers_manager_->remove_favorite_sticker(input_file_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->stickers_manager_->remove_favorite_sticker(input_file_, std::move(promise));
}
public:
@@ -3212,17 +2336,17 @@ class RemoveFavoriteStickerRequest : public RequestOnceActor {
}
};
-class GetStickerEmojisRequest : public RequestActor<> {
+class GetStickerEmojisRequest final : public RequestActor<> {
tl_object_ptr<td_api::InputFile> input_file_;
vector<string> emojis_;
- void do_run(Promise<Unit> &&promise) override {
- emojis_ = td->stickers_manager_->get_sticker_emojis(input_file_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ emojis_ = td_->stickers_manager_->get_sticker_emojis(input_file_, std::move(promise));
}
- void do_send_result() override {
- send_result(make_tl_object<td_api::stickerEmojis>(std::move(emojis_)));
+ void do_send_result() final {
+ send_result(td_api::make_object<td_api::emojis>(std::move(emojis_)));
}
public:
@@ -3232,881 +2356,763 @@ class GetStickerEmojisRequest : public RequestActor<> {
}
};
-class GetSavedAnimationsRequest : public RequestActor<> {
- vector<FileId> animation_ids_;
-
- void do_run(Promise<Unit> &&promise) override {
- animation_ids_ = td->animations_manager_->get_saved_animations(std::move(promise));
- }
-
- void do_send_result() override {
- send_result(make_tl_object<td_api::animations>(transform(std::move(animation_ids_), [td = td](FileId animation_id) {
- return td->animations_manager_->get_animation_object(animation_id, "GetSavedAnimationsRequest");
- })));
- }
-
- public:
- GetSavedAnimationsRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
- }
-};
-
-class AddSavedAnimationRequest : public RequestOnceActor {
- tl_object_ptr<td_api::InputFile> input_file_;
+class SearchEmojisRequest final : public RequestActor<> {
+ string text_;
+ bool exact_match_;
+ vector<string> input_language_codes_;
- void do_run(Promise<Unit> &&promise) override {
- td->animations_manager_->add_saved_animation(input_file_, std::move(promise));
- }
+ vector<string> emojis_;
- public:
- AddSavedAnimationRequest(ActorShared<Td> td, uint64 request_id, tl_object_ptr<td_api::InputFile> &&input_file)
- : RequestOnceActor(std::move(td), request_id), input_file_(std::move(input_file)) {
- set_tries(3);
+ void do_run(Promise<Unit> &&promise) final {
+ emojis_ = td_->stickers_manager_->search_emojis(text_, exact_match_, input_language_codes_, get_tries() < 2,
+ std::move(promise));
}
-};
-class RemoveSavedAnimationRequest : public RequestOnceActor {
- tl_object_ptr<td_api::InputFile> input_file_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->animations_manager_->remove_saved_animation(input_file_, std::move(promise));
+ void do_send_result() final {
+ send_result(td_api::make_object<td_api::emojis>(std::move(emojis_)));
}
public:
- RemoveSavedAnimationRequest(ActorShared<Td> td, uint64 request_id, tl_object_ptr<td_api::InputFile> &&input_file)
- : RequestOnceActor(std::move(td), request_id), input_file_(std::move(input_file)) {
+ SearchEmojisRequest(ActorShared<Td> td, uint64 request_id, string &&text, bool exact_match,
+ vector<string> &&input_language_codes)
+ : RequestActor(std::move(td), request_id)
+ , text_(std::move(text))
+ , exact_match_(exact_match)
+ , input_language_codes_(std::move(input_language_codes)) {
set_tries(3);
}
};
-class GetInlineQueryResultsRequest : public RequestOnceActor {
- UserId bot_user_id_;
- DialogId dialog_id_;
- Location user_location_;
- string query_;
- string offset_;
-
- uint64 query_hash_;
-
- void do_run(Promise<Unit> &&promise) override {
- query_hash_ = td->inline_queries_manager_->send_inline_query(bot_user_id_, dialog_id_, user_location_, query_,
- offset_, std::move(promise));
- }
+class GetEmojiSuggestionsUrlRequest final : public RequestOnceActor {
+ string language_code_;
- void do_send_result() override {
- send_result(td->inline_queries_manager_->get_inline_query_results_object(query_hash_));
- }
+ int64 random_id_;
- public:
- GetInlineQueryResultsRequest(ActorShared<Td> td, uint64 request_id, int32 bot_user_id, int64 dialog_id,
- const tl_object_ptr<td_api::location> &user_location, string query, string offset)
- : RequestOnceActor(std::move(td), request_id)
- , bot_user_id_(bot_user_id)
- , dialog_id_(dialog_id)
- , user_location_(user_location)
- , query_(std::move(query))
- , offset_(std::move(offset))
- , query_hash_(0) {
+ void do_run(Promise<Unit> &&promise) final {
+ random_id_ = td_->stickers_manager_->get_emoji_suggestions_url(language_code_, std::move(promise));
}
-};
-class AnswerInlineQueryRequest : public RequestOnceActor {
- int64 inline_query_id_;
- bool is_personal_;
- vector<tl_object_ptr<td_api::InputInlineQueryResult>> results_;
- int32 cache_time_;
- string next_offset_;
- string switch_pm_text_;
- string switch_pm_parameter_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->inline_queries_manager_->answer_inline_query(inline_query_id_, is_personal_, std::move(results_), cache_time_,
- next_offset_, switch_pm_text_, switch_pm_parameter_,
- std::move(promise));
+ void do_send_result() final {
+ send_result(td_->stickers_manager_->get_emoji_suggestions_url_result(random_id_));
}
public:
- AnswerInlineQueryRequest(ActorShared<Td> td, uint64 request_id, int64 inline_query_id, bool is_personal,
- vector<tl_object_ptr<td_api::InputInlineQueryResult>> &&results, int32 cache_time,
- string next_offset, string switch_pm_text, string switch_pm_parameter)
- : RequestOnceActor(std::move(td), request_id)
- , inline_query_id_(inline_query_id)
- , is_personal_(is_personal)
- , results_(std::move(results))
- , cache_time_(cache_time)
- , next_offset_(std::move(next_offset))
- , switch_pm_text_(std::move(switch_pm_text))
- , switch_pm_parameter_(std::move(switch_pm_parameter)) {
+ GetEmojiSuggestionsUrlRequest(ActorShared<Td> td, uint64 request_id, string &&language_code)
+ : RequestOnceActor(std::move(td), request_id), language_code_(std::move(language_code)), random_id_(0) {
}
};
-class GetCallbackQueryAnswerRequest : public RequestOnceActor {
- FullMessageId full_message_id_;
- tl_object_ptr<td_api::CallbackQueryPayload> payload_;
-
- int64 result_id_;
-
- void do_run(Promise<Unit> &&promise) override {
- result_id_ = td->callback_queries_manager_->send_callback_query(full_message_id_, payload_, std::move(promise));
- }
-
- void do_send_result() override {
- send_result(td->callback_queries_manager_->get_callback_query_answer_object(result_id_));
- }
-
- void do_send_error(Status &&status) override {
- if (status.code() == 502 && td->messages_manager_->is_message_edited_recently(full_message_id_, 31)) {
- return send_result(make_tl_object<td_api::callbackQueryAnswer>());
- }
- send_error(std::move(status));
- }
+class GetSavedAnimationsRequest final : public RequestActor<> {
+ vector<FileId> animation_ids_;
- public:
- GetCallbackQueryAnswerRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
- tl_object_ptr<td_api::CallbackQueryPayload> payload)
- : RequestOnceActor(std::move(td), request_id)
- , full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , payload_(std::move(payload))
- , result_id_(0) {
+ void do_run(Promise<Unit> &&promise) final {
+ animation_ids_ = td_->animations_manager_->get_saved_animations(std::move(promise));
}
-};
-class AnswerCallbackQueryRequest : public RequestOnceActor {
- int64 callback_query_id_;
- string text_;
- bool show_alert_;
- string url_;
- int32 cache_time_;
-
- void do_run(Promise<Unit> &&promise) override {
- td->callback_queries_manager_->answer_callback_query(callback_query_id_, text_, show_alert_, url_, cache_time_,
- std::move(promise));
+ void do_send_result() final {
+ send_result(make_tl_object<td_api::animations>(transform(animation_ids_, [td = td_](FileId animation_id) {
+ return td->animations_manager_->get_animation_object(animation_id);
+ })));
}
public:
- AnswerCallbackQueryRequest(ActorShared<Td> td, uint64 request_id, int64 callback_query_id, string text,
- bool show_alert, string url, int32 cache_time)
- : RequestOnceActor(std::move(td), request_id)
- , callback_query_id_(callback_query_id)
- , text_(std::move(text))
- , show_alert_(show_alert)
- , url_(std::move(url))
- , cache_time_(cache_time) {
+ GetSavedAnimationsRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
}
};
-class AnswerShippingQueryRequest : public RequestOnceActor {
- int64 shipping_query_id_;
- vector<tl_object_ptr<td_api::shippingOption>> shipping_options_;
- string error_message_;
+class AddSavedAnimationRequest final : public RequestOnceActor {
+ tl_object_ptr<td_api::InputFile> input_file_;
- void do_run(Promise<Unit> &&promise) override {
- answer_shipping_query(shipping_query_id_, std::move(shipping_options_), error_message_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->animations_manager_->add_saved_animation(input_file_, std::move(promise));
}
public:
- AnswerShippingQueryRequest(ActorShared<Td> td, uint64 request_id, int64 shipping_query_id,
- vector<tl_object_ptr<td_api::shippingOption>> shipping_options, string error_message)
- : RequestOnceActor(std::move(td), request_id)
- , shipping_query_id_(shipping_query_id)
- , shipping_options_(std::move(shipping_options))
- , error_message_(std::move(error_message)) {
+ AddSavedAnimationRequest(ActorShared<Td> td, uint64 request_id, tl_object_ptr<td_api::InputFile> &&input_file)
+ : RequestOnceActor(std::move(td), request_id), input_file_(std::move(input_file)) {
+ set_tries(3);
}
};
-class AnswerPreCheckoutQueryRequest : public RequestOnceActor {
- int64 pre_checkout_query_id_;
- string error_message_;
+class RemoveSavedAnimationRequest final : public RequestOnceActor {
+ tl_object_ptr<td_api::InputFile> input_file_;
- void do_run(Promise<Unit> &&promise) override {
- answer_pre_checkout_query(pre_checkout_query_id_, error_message_, std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->animations_manager_->remove_saved_animation(input_file_, std::move(promise));
}
public:
- AnswerPreCheckoutQueryRequest(ActorShared<Td> td, uint64 request_id, int64 pre_checkout_query_id,
- string error_message)
- : RequestOnceActor(std::move(td), request_id)
- , pre_checkout_query_id_(pre_checkout_query_id)
- , error_message_(std::move(error_message)) {
+ RemoveSavedAnimationRequest(ActorShared<Td> td, uint64 request_id, tl_object_ptr<td_api::InputFile> &&input_file)
+ : RequestOnceActor(std::move(td), request_id), input_file_(std::move(input_file)) {
+ set_tries(3);
}
};
-class GetPaymentFormRequest : public RequestActor<tl_object_ptr<td_api::paymentForm>> {
- FullMessageId full_message_id_;
-
- tl_object_ptr<td_api::paymentForm> payment_form_;
-
- void do_run(Promise<tl_object_ptr<td_api::paymentForm>> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(payment_form_));
- return;
- }
-
- td->messages_manager_->get_payment_form(full_message_id_, std::move(promise));
- }
+class GetSavedNotificationSoundRequest final : public RequestActor<> {
+ int64 ringtone_id_;
+ FileId ringtone_file_id_;
- void do_set_result(tl_object_ptr<td_api::paymentForm> &&result) override {
- payment_form_ = std::move(result);
+ void do_run(Promise<Unit> &&promise) final {
+ ringtone_file_id_ = td_->notification_settings_manager_->get_saved_ringtone(ringtone_id_, std::move(promise));
}
- void do_send_result() override {
- CHECK(payment_form_ != nullptr);
- send_result(std::move(payment_form_));
+ void do_send_result() final {
+ send_result(td_->audios_manager_->get_notification_sound_object(ringtone_file_id_));
}
public:
- GetPaymentFormRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id)
- : RequestActor(std::move(td), request_id), full_message_id_(DialogId(dialog_id), MessageId(message_id)) {
+ GetSavedNotificationSoundRequest(ActorShared<Td> td, uint64 request_id, int64 ringtone_id)
+ : RequestActor(std::move(td), request_id), ringtone_id_(ringtone_id) {
}
};
-class ValidateOrderInfoRequest : public RequestActor<tl_object_ptr<td_api::validatedOrderInfo>> {
- FullMessageId full_message_id_;
- tl_object_ptr<td_api::orderInfo> order_info_;
- bool allow_save_;
-
- tl_object_ptr<td_api::validatedOrderInfo> validated_order_info_;
+class GetSavedNotificationSoundsRequest final : public RequestActor<> {
+ vector<FileId> ringtone_file_ids_;
- void do_run(Promise<tl_object_ptr<td_api::validatedOrderInfo>> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(validated_order_info_));
- return;
- }
-
- td->messages_manager_->validate_order_info(full_message_id_, std::move(order_info_), allow_save_,
- std::move(promise));
+ void do_run(Promise<Unit> &&promise) final {
+ ringtone_file_ids_ = td_->notification_settings_manager_->get_saved_ringtones(std::move(promise));
}
- void do_set_result(tl_object_ptr<td_api::validatedOrderInfo> &&result) override {
- validated_order_info_ = std::move(result);
- }
-
- void do_send_result() override {
- CHECK(validated_order_info_ != nullptr);
- send_result(std::move(validated_order_info_));
+ void do_send_result() final {
+ send_result(td_api::make_object<td_api::notificationSounds>(
+ transform(ringtone_file_ids_, [td = td_](FileId ringtone_file_id) {
+ return td->audios_manager_->get_notification_sound_object(ringtone_file_id);
+ })));
}
public:
- ValidateOrderInfoRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id,
- tl_object_ptr<td_api::orderInfo> order_info, bool allow_save)
- : RequestActor(std::move(td), request_id)
- , full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , order_info_(std::move(order_info))
- , allow_save_(allow_save) {
+ GetSavedNotificationSoundsRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
}
};
-class SendPaymentFormRequest : public RequestActor<tl_object_ptr<td_api::paymentResult>> {
- FullMessageId full_message_id_;
- string order_info_id_;
- string shipping_option_id_;
- tl_object_ptr<td_api::InputCredentials> credentials_;
-
- tl_object_ptr<td_api::paymentResult> payment_result_;
+class RemoveSavedNotificationSoundRequest final : public RequestOnceActor {
+ int64 ringtone_id_;
- void do_run(Promise<tl_object_ptr<td_api::paymentResult>> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(payment_result_));
- return;
- }
-
- td->messages_manager_->send_payment_form(full_message_id_, order_info_id_, shipping_option_id_, credentials_,
- std::move(promise));
- }
-
- void do_set_result(tl_object_ptr<td_api::paymentResult> &&result) override {
- payment_result_ = std::move(result);
- }
-
- void do_send_result() override {
- CHECK(payment_result_ != nullptr);
- send_result(std::move(payment_result_));
+ void do_run(Promise<Unit> &&promise) final {
+ td_->notification_settings_manager_->remove_saved_ringtone(ringtone_id_, std::move(promise));
}
public:
- SendPaymentFormRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id, string order_info_id,
- string shipping_option_id, tl_object_ptr<td_api::InputCredentials> credentials)
- : RequestActor(std::move(td), request_id)
- , full_message_id_(DialogId(dialog_id), MessageId(message_id))
- , order_info_id_(std::move(order_info_id))
- , shipping_option_id_(std::move(shipping_option_id))
- , credentials_(std::move(credentials)) {
+ RemoveSavedNotificationSoundRequest(ActorShared<Td> td, uint64 request_id, int64 ringtone_id)
+ : RequestOnceActor(std::move(td), request_id), ringtone_id_(ringtone_id) {
+ set_tries(3);
}
};
-class GetPaymentReceiptRequest : public RequestActor<tl_object_ptr<td_api::paymentReceipt>> {
- FullMessageId full_message_id_;
-
- tl_object_ptr<td_api::paymentReceipt> payment_receipt_;
-
- void do_run(Promise<tl_object_ptr<td_api::paymentReceipt>> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(payment_receipt_));
- return;
- }
+class GetInlineQueryResultsRequest final : public RequestOnceActor {
+ UserId bot_user_id_;
+ DialogId dialog_id_;
+ Location user_location_;
+ string query_;
+ string offset_;
- td->messages_manager_->get_payment_receipt(full_message_id_, std::move(promise));
- }
+ uint64 query_hash_;
- void do_set_result(tl_object_ptr<td_api::paymentReceipt> &&result) override {
- payment_receipt_ = std::move(result);
+ void do_run(Promise<Unit> &&promise) final {
+ query_hash_ = td_->inline_queries_manager_->send_inline_query(bot_user_id_, dialog_id_, user_location_, query_,
+ offset_, std::move(promise));
}
- void do_send_result() override {
- CHECK(payment_receipt_ != nullptr);
- send_result(std::move(payment_receipt_));
+ void do_send_result() final {
+ send_result(td_->inline_queries_manager_->get_inline_query_results_object(query_hash_));
}
public:
- GetPaymentReceiptRequest(ActorShared<Td> td, uint64 request_id, int64 dialog_id, int64 message_id)
- : RequestActor(std::move(td), request_id), full_message_id_(DialogId(dialog_id), MessageId(message_id)) {
+ GetInlineQueryResultsRequest(ActorShared<Td> td, uint64 request_id, int64 bot_user_id, int64 dialog_id,
+ const tl_object_ptr<td_api::location> &user_location, string query, string offset)
+ : RequestOnceActor(std::move(td), request_id)
+ , bot_user_id_(bot_user_id)
+ , dialog_id_(dialog_id)
+ , user_location_(user_location)
+ , query_(std::move(query))
+ , offset_(std::move(offset))
+ , query_hash_(0) {
}
};
-class GetSavedOrderInfoRequest : public RequestActor<tl_object_ptr<td_api::orderInfo>> {
- tl_object_ptr<td_api::orderInfo> order_info_;
-
- void do_run(Promise<tl_object_ptr<td_api::orderInfo>> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(order_info_));
- return;
- }
+class SearchBackgroundRequest final : public RequestActor<> {
+ string name_;
- get_saved_order_info(std::move(promise));
- }
+ std::pair<BackgroundId, BackgroundType> background_;
- void do_set_result(tl_object_ptr<td_api::orderInfo> &&result) override {
- order_info_ = std::move(result);
+ void do_run(Promise<Unit> &&promise) final {
+ background_ = td_->background_manager_->search_background(name_, std::move(promise));
}
- void do_send_result() override {
- send_result(std::move(order_info_));
+ void do_send_result() final {
+ send_result(td_->background_manager_->get_background_object(background_.first, false, &background_.second));
}
public:
- GetSavedOrderInfoRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
- }
-};
-
-class DeleteSavedOrderInfoRequest : public RequestOnceActor {
- void do_run(Promise<Unit> &&promise) override {
- delete_saved_order_info(std::move(promise));
- }
-
- public:
- DeleteSavedOrderInfoRequest(ActorShared<Td> td, uint64 request_id) : RequestOnceActor(std::move(td), request_id) {
+ SearchBackgroundRequest(ActorShared<Td> td, uint64 request_id, string &&name)
+ : RequestActor(std::move(td), request_id), name_(std::move(name)) {
+ set_tries(3);
}
};
-class DeleteSavedCredentialsRequest : public RequestOnceActor {
- void do_run(Promise<Unit> &&promise) override {
- delete_saved_credentials(std::move(promise));
- }
+Td::Td(unique_ptr<TdCallback> callback, Options options)
+ : callback_(std::move(callback)), td_options_(std::move(options)) {
+ CHECK(callback_ != nullptr);
+ LOG(INFO) << "Create Td with layer " << MTPROTO_LAYER << ", database version " << current_db_version()
+ << " and version " << static_cast<int32>(Version::Next) - 1 << " on "
+ << Scheduler::instance()->sched_count() << " threads";
+}
- public:
- DeleteSavedCredentialsRequest(ActorShared<Td> td, uint64 request_id) : RequestOnceActor(std::move(td), request_id) {
- }
-};
+Td::~Td() = default;
-class GetSupportUserRequest : public RequestActor<> {
- UserId user_id_;
+void Td::on_alarm_timeout_callback(void *td_ptr, int64 alarm_id) {
+ auto td = static_cast<Td *>(td_ptr);
+ auto td_id = td->actor_id(td);
+ send_closure_later(td_id, &Td::on_alarm_timeout, alarm_id);
+}
- void do_run(Promise<Unit> &&promise) override {
- user_id_ = td->contacts_manager_->get_support_user(std::move(promise));
+void Td::on_alarm_timeout(int64 alarm_id) {
+ if (alarm_id == ONLINE_ALARM_ID) {
+ on_online_updated(false, true);
+ return;
}
-
- void do_send_result() override {
- send_result(td->contacts_manager_->get_user_object(user_id_));
+ if (alarm_id == PING_SERVER_ALARM_ID) {
+ if (!close_flag_ && updates_manager_ != nullptr && auth_manager_->is_authorized()) {
+ updates_manager_->ping_server();
+ alarm_timeout_.set_timeout_in(PING_SERVER_ALARM_ID,
+ PING_SERVER_TIMEOUT + Random::fast(0, PING_SERVER_TIMEOUT / 5));
+ set_is_bot_online(false);
+ }
+ return;
}
-
- public:
- GetSupportUserRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
+ if (alarm_id == TERMS_OF_SERVICE_ALARM_ID) {
+ if (!close_flag_ && !auth_manager_->is_bot()) {
+ get_terms_of_service(
+ this, PromiseCreator::lambda([actor_id = actor_id(this)](Result<std::pair<int32, TermsOfService>> result) {
+ send_closure(actor_id, &Td::on_get_terms_of_service, std::move(result), false);
+ }));
+ }
+ return;
}
-};
-
-class GetWallpapersRequest : public RequestActor<tl_object_ptr<td_api::wallpapers>> {
- tl_object_ptr<td_api::wallpapers> wallpapers_;
-
- void do_run(Promise<tl_object_ptr<td_api::wallpapers>> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(wallpapers_));
- return;
+ if (alarm_id == PROMO_DATA_ALARM_ID) {
+ if (!close_flag_ && !auth_manager_->is_bot()) {
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<telegram_api::object_ptr<telegram_api::help_PromoData>> result) {
+ send_closure(actor_id, &Td::on_get_promo_data, std::move(result), false);
+ });
+ create_handler<GetPromoDataQuery>(std::move(promise))->send();
}
-
- td->create_handler<GetWallpapersQuery>(std::move(promise))->send();
+ return;
}
-
- void do_set_result(tl_object_ptr<td_api::wallpapers> &&result) override {
- wallpapers_ = std::move(result);
+ if (close_flag_ >= 2) {
+ // pending_alarms_ was already cleared
+ return;
}
- void do_send_result() override {
- CHECK(wallpapers_ != nullptr);
- send_result(std::move(wallpapers_));
- }
+ auto it = pending_alarms_.find(alarm_id);
+ CHECK(it != pending_alarms_.end());
+ uint64 request_id = it->second;
+ pending_alarms_.erase(alarm_id);
+ send_result(request_id, make_tl_object<td_api::ok>());
+}
- public:
- GetWallpapersRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
+void Td::on_online_updated(bool force, bool send_update) {
+ if (close_flag_ >= 2 || !auth_manager_->is_authorized() || auth_manager_->is_bot()) {
+ return;
}
-};
-
-class GetRecentlyVisitedTMeUrlsRequest : public RequestActor<tl_object_ptr<td_api::tMeUrls>> {
- string referrer_;
-
- tl_object_ptr<td_api::tMeUrls> urls_;
-
- void do_run(Promise<tl_object_ptr<td_api::tMeUrls>> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(urls_));
- return;
+ if (force || is_online_) {
+ contacts_manager_->set_my_online_status(is_online_, send_update, true);
+ if (!update_status_query_.empty()) {
+ LOG(INFO) << "Cancel previous update status query";
+ cancel_query(update_status_query_);
}
-
- td->create_handler<GetRecentMeUrlsQuery>(std::move(promise))->send(referrer_);
+ update_status_query_ = create_handler<UpdateStatusQuery>()->send(!is_online_);
}
-
- void do_set_result(tl_object_ptr<td_api::tMeUrls> &&result) override {
- urls_ = std::move(result);
+ if (is_online_) {
+ alarm_timeout_.set_timeout_in(
+ ONLINE_ALARM_ID, static_cast<double>(G()->get_option_integer("online_update_period_ms", 210000)) * 1e-3);
+ } else {
+ alarm_timeout_.cancel_timeout(ONLINE_ALARM_ID);
}
+}
- void do_send_result() override {
- CHECK(urls_ != nullptr);
- send_result(std::move(urls_));
+void Td::on_update_status_success(bool is_online) {
+ if (is_online == is_online_) {
+ if (!update_status_query_.empty()) {
+ update_status_query_ = NetQueryRef();
+ }
+ contacts_manager_->set_my_online_status(is_online_, true, false);
}
+}
- public:
- GetRecentlyVisitedTMeUrlsRequest(ActorShared<Td> td, uint64 request_id, string referrer)
- : RequestActor(std::move(td), request_id), referrer_(std::move(referrer)) {
+td_api::object_ptr<td_api::updateTermsOfService> Td::get_update_terms_of_service_object() const {
+ auto terms_of_service = pending_terms_of_service_.get_terms_of_service_object();
+ if (terms_of_service == nullptr) {
+ return nullptr;
}
-};
-
-class SendCustomRequestRequest : public RequestActor<string> {
- string method_;
- string parameters_;
-
- string request_result_;
+ return td_api::make_object<td_api::updateTermsOfService>(pending_terms_of_service_.get_id().str(),
+ std::move(terms_of_service));
+}
- void do_run(Promise<string> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(request_result_));
- return;
+void Td::on_get_terms_of_service(Result<std::pair<int32, TermsOfService>> result, bool dummy) {
+ int32 expires_in = 0;
+ if (result.is_error()) {
+ expires_in = Random::fast(10, 60);
+ } else {
+ auto terms = result.move_as_ok();
+ pending_terms_of_service_ = std::move(terms.second);
+ auto update = get_update_terms_of_service_object();
+ if (update == nullptr) {
+ expires_in = min(max(terms.first, G()->unix_time() + 3600) - G()->unix_time(), 86400);
+ } else {
+ send_update(std::move(update));
}
-
- td->create_handler<SendCustomRequestQuery>(std::move(promise))->send(method_, parameters_);
}
-
- void do_set_result(string &&result) override {
- request_result_ = std::move(result);
+ if (expires_in > 0) {
+ schedule_get_terms_of_service(expires_in);
}
+}
- void do_send_result() override {
- send_result(make_tl_object<td_api::customRequestResult>(request_result_));
+void Td::schedule_get_terms_of_service(int32 expires_in) {
+ if (expires_in == 0) {
+ // drop pending Terms of Service after successful accept
+ pending_terms_of_service_ = TermsOfService();
}
-
- public:
- SendCustomRequestRequest(ActorShared<Td> td, uint64 request_id, string &&method, string &&parameters)
- : RequestActor(std::move(td), request_id), method_(method), parameters_(parameters) {
+ if (!close_flag_ && !auth_manager_->is_bot()) {
+ alarm_timeout_.set_timeout_in(TERMS_OF_SERVICE_ALARM_ID, expires_in);
}
-};
-
-class AnswerCustomQueryRequest : public RequestOnceActor {
- int64 custom_query_id_;
- string data_;
+}
- void do_run(Promise<Unit> &&promise) override {
- td->create_handler<AnswerCustomQueryQuery>(std::move(promise))->send(custom_query_id_, data_);
+void Td::on_get_promo_data(Result<telegram_api::object_ptr<telegram_api::help_PromoData>> r_promo_data, bool dummy) {
+ if (G()->close_flag()) {
+ return;
}
- public:
- AnswerCustomQueryRequest(ActorShared<Td> td, uint64 request_id, int64 custom_query_id, string data)
- : RequestOnceActor(std::move(td), request_id), custom_query_id_(custom_query_id), data_(std::move(data)) {
+ if (r_promo_data.is_error()) {
+ LOG(ERROR) << "Receive error for GetPromoData: " << r_promo_data.error();
+ return schedule_get_promo_data(60);
}
-};
-class GetCountryCodeRequest : public RequestActor<string> {
- string country_code_;
-
- void do_run(Promise<string> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(country_code_));
- return;
+ auto promo_data_ptr = r_promo_data.move_as_ok();
+ CHECK(promo_data_ptr != nullptr);
+ LOG(DEBUG) << "Receive " << to_string(promo_data_ptr);
+ int32 expires_at = 0;
+ switch (promo_data_ptr->get_id()) {
+ case telegram_api::help_promoDataEmpty::ID: {
+ auto promo = telegram_api::move_object_as<telegram_api::help_promoDataEmpty>(promo_data_ptr);
+ expires_at = promo->expires_;
+ messages_manager_->remove_sponsored_dialog();
+ break;
}
-
- td->create_handler<GetNearestDcQuery>(std::move(promise))->send();
- }
-
- void do_set_result(string &&result) override {
- country_code_ = std::move(result);
- }
-
- void do_send_result() override {
- send_result(make_tl_object<td_api::text>(country_code_));
- }
-
- public:
- GetCountryCodeRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
- }
-};
-
-class GetInviteTextRequest : public RequestActor<string> {
- string text_;
-
- void do_run(Promise<string> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(text_));
- return;
+ case telegram_api::help_promoData::ID: {
+ auto promo = telegram_api::move_object_as<telegram_api::help_promoData>(promo_data_ptr);
+ expires_at = promo->expires_;
+ bool is_proxy = promo->proxy_;
+ messages_manager_->on_get_sponsored_dialog(
+ std::move(promo->peer_),
+ is_proxy ? DialogSource::mtproto_proxy()
+ : DialogSource::public_service_announcement(promo->psa_type_, promo->psa_message_),
+ std::move(promo->users_), std::move(promo->chats_));
+ break;
}
-
- td->create_handler<GetInviteTextQuery>(std::move(promise))->send();
+ default:
+ UNREACHABLE();
}
+ schedule_get_promo_data(expires_at == 0 ? 0 : expires_at - G()->unix_time());
+}
- void do_set_result(string &&result) override {
- text_ = std::move(result);
+void Td::schedule_get_promo_data(int32 expires_in) {
+ expires_in = expires_in <= 0 ? 0 : clamp(expires_in, 60, 86400);
+ if (!close_flag_ && auth_manager_->is_authorized() && !auth_manager_->is_bot()) {
+ LOG(INFO) << "Schedule getPromoData in " << expires_in;
+ alarm_timeout_.set_timeout_in(PROMO_DATA_ALARM_ID, expires_in);
}
+}
- void do_send_result() override {
- send_result(make_tl_object<td_api::text>(text_));
- }
+bool Td::is_online() const {
+ return is_online_;
+}
- public:
- GetInviteTextRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
+void Td::set_is_online(bool is_online) {
+ if (is_online == is_online_) {
+ return;
}
-};
-
-class GetTermsOfServiceRequest : public RequestActor<string> {
- string text_;
- void do_run(Promise<string> &&promise) override {
- if (get_tries() < 2) {
- promise.set_value(std::move(text_));
- return;
- }
-
- td->create_handler<GetTermsOfServiceQuery>(std::move(promise))->send();
+ is_online_ = is_online;
+ if (auth_manager_ != nullptr) { // postpone if there is no AuthManager yet
+ on_online_updated(true, true);
}
+}
- void do_set_result(string &&result) override {
- text_ = std::move(result);
+void Td::set_is_bot_online(bool is_bot_online) {
+ if (G()->get_option_integer("session_count") > 1) {
+ is_bot_online = false;
}
- void do_send_result() override {
- send_result(make_tl_object<td_api::text>(text_));
+ if (is_bot_online == is_bot_online_) {
+ return;
}
- public:
- GetTermsOfServiceRequest(ActorShared<Td> td, uint64 request_id) : RequestActor(std::move(td), request_id) {
+ is_bot_online_ = is_bot_online;
+ send_closure(G()->state_manager(), &StateManager::on_online, is_bot_online_);
+}
+
+bool Td::is_authentication_request(int32 id) {
+ switch (id) {
+ case td_api::setTdlibParameters::ID:
+ case td_api::getAuthorizationState::ID:
+ case td_api::setAuthenticationPhoneNumber::ID:
+ case td_api::setAuthenticationEmailAddress::ID:
+ case td_api::resendAuthenticationCode::ID:
+ case td_api::checkAuthenticationEmailCode::ID:
+ case td_api::checkAuthenticationCode::ID:
+ case td_api::registerUser::ID:
+ case td_api::requestQrCodeAuthentication::ID:
+ case td_api::checkAuthenticationPassword::ID:
+ case td_api::requestAuthenticationPasswordRecovery::ID:
+ case td_api::checkAuthenticationPasswordRecoveryCode::ID:
+ case td_api::recoverAuthenticationPassword::ID:
+ case td_api::deleteAccount::ID:
+ case td_api::logOut::ID:
+ case td_api::close::ID:
+ case td_api::destroy::ID:
+ case td_api::checkAuthenticationBotToken::ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Td::is_synchronous_request(const td_api::Function *function) {
+ switch (function->get_id()) {
+ case td_api::getTextEntities::ID:
+ case td_api::parseTextEntities::ID:
+ case td_api::parseMarkdown::ID:
+ case td_api::getMarkdownText::ID:
+ case td_api::getFileMimeType::ID:
+ case td_api::getFileExtension::ID:
+ case td_api::cleanFileName::ID:
+ case td_api::getLanguagePackString::ID:
+ case td_api::getPhoneNumberInfoSync::ID:
+ case td_api::getChatFilterDefaultIconName::ID:
+ case td_api::getJsonValue::ID:
+ case td_api::getJsonString::ID:
+ case td_api::getThemeParametersJsonString::ID:
+ case td_api::getPushReceiverId::ID:
+ case td_api::setLogStream::ID:
+ case td_api::getLogStream::ID:
+ case td_api::setLogVerbosityLevel::ID:
+ case td_api::getLogVerbosityLevel::ID:
+ case td_api::getLogTags::ID:
+ case td_api::setLogTagVerbosityLevel::ID:
+ case td_api::getLogTagVerbosityLevel::ID:
+ case td_api::addLogMessage::ID:
+ case td_api::testReturnError::ID:
+ return true;
+ case td_api::getOption::ID:
+ return OptionManager::is_synchronous_option(static_cast<const td_api::getOption *>(function)->name_);
+ default:
+ return false;
+ }
+}
+
+bool Td::is_preinitialization_request(int32 id) {
+ switch (id) {
+ case td_api::getCurrentState::ID:
+ case td_api::setAlarm::ID:
+ case td_api::testUseUpdate::ID:
+ case td_api::testCallEmpty::ID:
+ case td_api::testSquareInt::ID:
+ case td_api::testCallString::ID:
+ case td_api::testCallBytes::ID:
+ case td_api::testCallVectorInt::ID:
+ case td_api::testCallVectorIntObject::ID:
+ case td_api::testCallVectorString::ID:
+ case td_api::testCallVectorStringObject::ID:
+ case td_api::testProxy::ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool Td::is_preauthentication_request(int32 id) {
+ switch (id) {
+ case td_api::getInternalLinkType::ID:
+ case td_api::getLocalizationTargetInfo::ID:
+ case td_api::getLanguagePackInfo::ID:
+ case td_api::getLanguagePackStrings::ID:
+ case td_api::synchronizeLanguagePack::ID:
+ case td_api::addCustomServerLanguagePack::ID:
+ case td_api::setCustomLanguagePack::ID:
+ case td_api::editCustomLanguagePackInfo::ID:
+ case td_api::setCustomLanguagePackString::ID:
+ case td_api::deleteLanguagePack::ID:
+ case td_api::processPushNotification::ID:
+ case td_api::getOption::ID:
+ case td_api::setOption::ID:
+ case td_api::getStorageStatistics::ID:
+ case td_api::getStorageStatisticsFast::ID:
+ case td_api::getDatabaseStatistics::ID:
+ case td_api::setNetworkType::ID:
+ case td_api::getNetworkStatistics::ID:
+ case td_api::addNetworkStatistics::ID:
+ case td_api::resetNetworkStatistics::ID:
+ case td_api::getCountries::ID:
+ case td_api::getCountryCode::ID:
+ case td_api::getPhoneNumberInfo::ID:
+ case td_api::getDeepLinkInfo::ID:
+ case td_api::getApplicationConfig::ID:
+ case td_api::saveApplicationLogEvent::ID:
+ case td_api::addProxy::ID:
+ case td_api::editProxy::ID:
+ case td_api::enableProxy::ID:
+ case td_api::disableProxy::ID:
+ case td_api::removeProxy::ID:
+ case td_api::getProxies::ID:
+ case td_api::getProxyLink::ID:
+ case td_api::pingProxy::ID:
+ case td_api::testNetwork::ID:
+ return true;
+ default:
+ return false;
}
-};
+}
-/** Td **/
-Td::Td(std::unique_ptr<TdCallback> callback) : callback_(std::move(callback)) {
+td_api::object_ptr<td_api::AuthorizationState> Td::get_fake_authorization_state_object() const {
+ switch (state_) {
+ case State::WaitParameters:
+ return td_api::make_object<td_api::authorizationStateWaitTdlibParameters>();
+ case State::Run:
+ UNREACHABLE();
+ return nullptr;
+ case State::Close:
+ if (close_flag_ == 5) {
+ return td_api::make_object<td_api::authorizationStateClosed>();
+ } else {
+ return td_api::make_object<td_api::authorizationStateClosing>();
+ }
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
}
-void Td::on_alarm_timeout_callback(void *td_ptr, int64 alarm_id) {
- auto td = static_cast<Td *>(td_ptr);
- auto td_id = td->actor_id(td);
- send_closure_later(td_id, &Td::on_alarm_timeout, alarm_id);
+vector<td_api::object_ptr<td_api::Update>> Td::get_fake_current_state() const {
+ CHECK(state_ != State::Run);
+ vector<td_api::object_ptr<td_api::Update>> updates;
+ OptionManager::get_common_state(updates);
+ updates.push_back(td_api::make_object<td_api::updateAuthorizationState>(get_fake_authorization_state_object()));
+ return updates;
}
-void Td::on_alarm_timeout(int64 alarm_id) {
- if (alarm_id == ONLINE_ALARM_ID) {
- on_online_updated(false, true);
- return;
- }
- if (alarm_id == PING_SERVER_ALARM_ID && updates_manager_ != nullptr) {
- updates_manager_->ping_server();
- alarm_timeout_.set_timeout_in(PING_SERVER_ALARM_ID, PING_SERVER_TIMEOUT + Random::fast(0, PING_SERVER_TIMEOUT / 5));
- return;
+DbKey Td::as_db_key(string key) {
+ // Database will still be effectively not encrypted, but
+ // 1. SQLite database will be protected from corruption, because that's how sqlcipher works
+ // 2. security through obscurity
+ // 3. no need for reencryption of SQLite database
+ if (key.empty()) {
+ return DbKey::raw_key("cucumber");
}
- auto it = pending_alarms_.find(alarm_id);
- CHECK(it != pending_alarms_.end());
- uint64 request_id = it->second;
- pending_alarms_.erase(alarm_id);
- send_result(request_id, make_tl_object<td_api::ok>());
+ return DbKey::raw_key(std::move(key));
}
-void Td::on_online_updated(bool force, bool send_update) {
- if (close_flag_ >= 2 || auth_manager_->is_bot() || !auth_manager_->is_authorized()) {
+void Td::request(uint64 id, tl_object_ptr<td_api::Function> function) {
+ if (id == 0) {
+ LOG(ERROR) << "Ignore request with ID == 0: " << to_string(function);
return;
}
- if (force || is_online_) {
- contacts_manager_->set_my_online_status(is_online_, send_update, true);
- if (!update_status_query_.empty()) {
- LOG(INFO) << "Cancel previous update status query";
- cancel_query(update_status_query_);
- }
- update_status_query_ = create_handler<UpdateStatusQuery>()->send(!is_online_);
- }
- if (is_online_) {
- alarm_timeout_.set_timeout_in(ONLINE_ALARM_ID, ONLINE_TIMEOUT);
- } else {
- alarm_timeout_.cancel_timeout(ONLINE_ALARM_ID);
- }
-}
-void Td::on_update_status_success(bool is_online) {
- if (is_online == is_online_) {
- if (!update_status_query_.empty()) {
- update_status_query_ = NetQueryRef();
- }
- contacts_manager_->set_my_online_status(is_online_, true, false);
+ if (function == nullptr) {
+ return callback_->on_error(id, make_error(400, "Request is empty"));
}
-}
-void Td::on_channel_unban_timeout(int64 channel_id_long) {
- if (close_flag_ >= 2) {
- return;
+ VLOG(td_requests) << "Receive request " << id << ": " << to_string(function);
+ request_set_.emplace(id, function->get_id());
+ if (is_synchronous_request(function.get())) {
+ // send response synchronously
+ return send_result(id, static_request(std::move(function)));
}
- contacts_manager_->on_channel_unban_timeout(ChannelId(narrow_cast<int32>(channel_id_long)));
-}
-bool Td::is_online() const {
- return is_online_;
+ run_request(id, std::move(function));
}
-void Td::request(uint64 id, tl_object_ptr<td_api::Function> function) {
- request_set_.insert(id);
-
- if (id == 0) {
- LOG(ERROR) << "Receive request with id == 0";
- return send_error_raw(id, 400, "Wrong request id == 0");
- }
- if (function == nullptr) {
- LOG(ERROR) << "Receive empty request";
- return send_error_raw(id, 400, "Request is empty");
+void Td::run_request(uint64 id, tl_object_ptr<td_api::Function> function) {
+ if (set_parameters_request_id_ > 0) {
+ pending_set_parameters_requests_.emplace_back(id, std::move(function));
+ return;
}
+ int32 function_id = function->get_id();
+ if (state_ != State::Run) {
+ switch (function_id) {
+ case td_api::getAuthorizationState::ID:
+ // send response synchronously to prevent "Request aborted"
+ return send_result(id, get_fake_authorization_state_object());
+ case td_api::getCurrentState::ID:
+ // send response synchronously to prevent "Request aborted"
+ return send_result(id, td_api::make_object<td_api::updates>(get_fake_current_state()));
+ case td_api::close::ID:
+ // need to send response before actual closing
+ send_closure(actor_id(this), &Td::send_result, id, td_api::make_object<td_api::ok>());
+ send_closure(actor_id(this), &Td::close);
+ return;
+ default:
+ break;
+ }
+ }
switch (state_) {
case State::WaitParameters: {
- switch (function->get_id()) {
- case td_api::getAuthorizationState::ID:
- return send_closure(actor_id(this), &Td::send_result, id,
- td_api::make_object<td_api::authorizationStateWaitTdlibParameters>());
- case td_api::setTdlibParameters::ID:
- return answer_ok_query(
- id, set_parameters(std::move(move_tl_object_as<td_api::setTdlibParameters>(function)->parameters_)));
- default:
- return send_error_raw(id, 401, "Initialization parameters are needed");
- }
- break;
- }
- case State::Decrypt: {
- string encryption_key;
- switch (function->get_id()) {
- case td_api::getAuthorizationState::ID:
- return send_closure(
- actor_id(this), &Td::send_result, id,
- td_api::make_object<td_api::authorizationStateWaitEncryptionKey>(encryption_info_.is_encrypted));
- case td_api::checkDatabaseEncryptionKey::ID: {
- auto check_key = move_tl_object_as<td_api::checkDatabaseEncryptionKey>(function);
- encryption_key = std::move(check_key->encryption_key_);
- break;
- }
- case td_api::setDatabaseEncryptionKey::ID: {
- auto set_key = move_tl_object_as<td_api::setDatabaseEncryptionKey>(function);
- encryption_key = std::move(set_key->new_encryption_key_);
- break;
+ switch (function_id) {
+ case td_api::setTdlibParameters::ID: {
+ auto parameters = move_tl_object_as<td_api::setTdlibParameters>(function);
+ auto database_encryption_key = as_db_key(std::move(parameters->database_encryption_key_));
+ auto status = set_parameters(std::move(parameters));
+ if (status.is_error()) {
+ return send_closure(actor_id(this), &Td::send_error, id, std::move(status));
+ }
+
+ VLOG(td_init) << "Begin to open database";
+ set_parameters_request_id_ = id;
+
+ auto promise =
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<TdDb::OpenedDatabase> r_opened_database) {
+ send_closure(actor_id, &Td::init, std::move(r_opened_database));
+ });
+ return TdDb::open(get_database_scheduler_id(), parameters_, std::move(database_encryption_key),
+ std::move(promise));
}
- case td_api::close::ID:
- return close();
- case td_api::destroy::ID:
- return destroy();
default:
- return send_error_raw(id, 401, "Database encryption key is needed");
+ if (is_preinitialization_request(function_id)) {
+ break;
+ }
+ if (is_preauthentication_request(function_id)) {
+ pending_preauthentication_requests_.emplace_back(id, std::move(function));
+ return;
+ }
+ return send_error_impl(
+ id, make_error(400, "Initialization parameters are needed: call setTdlibParameters first"));
}
- return answer_ok_query(id, init(as_db_key(encryption_key)));
+ break;
}
- case State::Close: {
- if (function->get_id() == td_api::getAuthorizationState::ID) {
- if (close_flag_ == 5) {
- return send_closure(actor_id(this), &Td::send_result, id,
- td_api::make_object<td_api::authorizationStateClosed>());
- } else {
- return send_closure(actor_id(this), &Td::send_result, id,
- td_api::make_object<td_api::authorizationStateClosing>());
- }
+ case State::Close:
+ if (destroy_flag_) {
+ return send_error_impl(id, make_error(401, "Unauthorized"));
+ } else {
+ return send_error_impl(id, make_error(500, "Request aborted"));
}
- return send_error_raw(id, 401, "Unauthorized");
- }
case State::Run:
break;
}
- VLOG(td_requests) << "Receive request " << id << ": " << to_string(function);
+ if ((auth_manager_ == nullptr || !auth_manager_->is_authorized()) && !is_preauthentication_request(function_id) &&
+ !is_preinitialization_request(function_id) && !is_authentication_request(function_id)) {
+ return send_error_impl(id, make_error(401, "Unauthorized"));
+ }
downcast_call(*function, [this, id](auto &request) { this->on_request(id, request); });
}
td_api::object_ptr<td_api::Object> Td::static_request(td_api::object_ptr<td_api::Function> function) {
- VLOG(td_requests) << "Receive static request: " << to_string(function);
+ if (function == nullptr) {
+ return td_api::make_object<td_api::error>(400, "Request is empty");
+ }
+
+ auto function_id = function->get_id();
+ bool need_logging = [function_id] {
+ switch (function_id) {
+ case td_api::parseTextEntities::ID:
+ case td_api::parseMarkdown::ID:
+ case td_api::getMarkdownText::ID:
+ case td_api::getFileMimeType::ID:
+ case td_api::getFileExtension::ID:
+ case td_api::cleanFileName::ID:
+ case td_api::getChatFilterDefaultIconName::ID:
+ case td_api::getJsonValue::ID:
+ case td_api::getJsonString::ID:
+ case td_api::getThemeParametersJsonString::ID:
+ case td_api::testReturnError::ID:
+ return true;
+ default:
+ return false;
+ }
+ }();
+
+ if (need_logging) {
+ VLOG(td_requests) << "Receive static request: " << to_string(function);
+ }
td_api::object_ptr<td_api::Object> response;
downcast_call(*function, [&response](auto &request) { response = Td::do_static_request(request); });
+ LOG_CHECK(response != nullptr) << function_id;
- VLOG(td_requests) << "Sending result for static request: " << to_string(response);
+ if (need_logging) {
+ VLOG(td_requests) << "Sending result for static request: " << to_string(response);
+ }
return response;
}
void Td::add_handler(uint64 id, std::shared_ptr<ResultHandler> handler) {
- result_handlers_.emplace_back(id, handler);
+ result_handlers_[id] = std::move(handler);
}
std::shared_ptr<Td::ResultHandler> Td::extract_handler(uint64 id) {
- std::shared_ptr<Td::ResultHandler> result;
- for (size_t i = 0; i < result_handlers_.size(); i++) {
- if (result_handlers_[i].first == id) {
- result = std::move(result_handlers_[i].second);
- result_handlers_.erase(result_handlers_.begin() + i);
- break;
- }
+ auto it = result_handlers_.find(id);
+ if (it == result_handlers_.end()) {
+ return nullptr;
}
+ auto result = std::move(it->second);
+ result_handlers_.erase(it);
return result;
}
-void Td::invalidate_handler(ResultHandler *handler) {
- for (size_t i = 0; i < result_handlers_.size(); i++) {
- if (result_handlers_[i].second.get() == handler) {
- result_handlers_.erase(result_handlers_.begin() + i);
- i--;
- }
- }
-}
-
-void Td::send(NetQueryPtr &&query) {
- VLOG(net_query) << "Send " << query << " to dispatcher";
- query->debug("Td: send to NetQueryDispatcher");
- query->set_callback(actor_shared(this, 1));
- G()->net_query_dispatcher().dispatch(std::move(query));
-}
-
-void Td::update_qts(int32 qts) {
+void Td::on_update(BufferSlice &&update) {
if (close_flag_ > 1) {
return;
}
- updates_manager_->set_qts(qts);
-}
-
-void Td::force_get_difference() {
- if (close_flag_) {
- return;
+ TlBufferParser parser(&update);
+ auto ptr = telegram_api::Updates::fetch(parser);
+ parser.fetch_end();
+ if (parser.get_error()) {
+ LOG(ERROR) << "Failed to fetch update: " << parser.get_error() << format::as_hex_dump<4>(update.as_slice());
+ updates_manager_->schedule_get_difference("failed to fetch update");
+ } else {
+ updates_manager_->on_get_updates(std::move(ptr), Promise<Unit>());
+ if (auth_manager_->is_bot() && auth_manager_->is_authorized()) {
+ alarm_timeout_.set_timeout_in(PING_SERVER_ALARM_ID,
+ PING_SERVER_TIMEOUT + Random::fast(0, PING_SERVER_TIMEOUT / 5));
+ set_is_bot_online(true);
+ }
}
-
- updates_manager_->get_difference("force_get_difference");
}
void Td::on_result(NetQueryPtr query) {
query->debug("Td: received from DcManager");
- VLOG(net_query) << "on_result " << query;
+ VLOG(net_query) << "Receive result of " << query;
if (close_flag_ > 1) {
return;
}
- if (query->id() == 0) {
- if (query->is_error()) {
- query->clear();
- updates_manager_->schedule_get_difference("error in update");
- LOG(ERROR) << "Error in update";
- return;
- }
- auto ok = query->move_as_ok();
- TlBufferParser parser(&ok);
- auto ptr = telegram_api::Updates::fetch(parser);
- if (parser.get_error()) {
- LOG(ERROR) << "Failed to fetch update: " << parser.get_error() << format::as_hex_dump<4>(ok.as_slice());
- updates_manager_->schedule_get_difference("failed to fetch update");
+ auto handler = extract_handler(query->id());
+ if (handler != nullptr) {
+ CHECK(query->is_ready());
+ if (query->is_ok()) {
+ handler->on_result(std::move(query->ok()));
} else {
- updates_manager_->on_get_updates(std::move(ptr));
- if (auth_manager_->is_bot()) {
- alarm_timeout_.set_timeout_in(PING_SERVER_ALARM_ID,
- PING_SERVER_TIMEOUT + Random::fast(0, PING_SERVER_TIMEOUT / 5));
- }
+ handler->on_error(std::move(query->error()));
}
- return;
+ } else if (!query->is_ok() || query->ok_tl_constructor() != telegram_api::upload_file::ID) {
+ LOG(WARNING) << query << " is ignored: no handlers found";
}
- auto handler = extract_handler(query->id());
- if (handler == nullptr) {
- query->clear();
- LOG_IF(WARNING, !query->is_ok() || query->ok_tl_constructor() != telegram_api::upload_file::ID)
- << tag("NetQuery", query) << " is ignored: no handlers found";
- return;
- }
- handler->on_result(std::move(query));
-}
-
-bool Td::is_internal_config_option(Slice name) {
- return name == "call_ring_timeout_ms" || name == "call_receive_timeout_ms" || name == "channels_read_media_period" ||
- name == "edit_time_limit" || name == "revoke_pm_inbox" || name == "revoke_time_limit" ||
- name == "revoke_pm_time_limit" || name == "rating_e_decay" || name == "saved_animations_limit" ||
- name == "recent_stickers_limit" || name == "auth";
+ query->clear();
}
-void Td::on_config_option_updated(const string &name) {
- if (close_flag_) {
- return;
- }
- if (name == "auth") {
- on_authorization_lost();
- return;
- } else if (name == "saved_animations_limit") {
- return animations_manager_->on_update_saved_animations_limit(G()->shared_config().get_option_integer(name));
- } else if (name == "recent_stickers_limit") {
- return stickers_manager_->on_update_recent_stickers_limit(G()->shared_config().get_option_integer(name));
- } else if (name == "favorite_stickers_limit") {
- stickers_manager_->on_update_favorite_stickers_limit(G()->shared_config().get_option_integer(name));
- } else if (name == "my_id") {
- G()->set_my_id(G()->shared_config().get_option_integer(name));
- } else if (name == "session_count") {
- G()->net_query_dispatcher().update_session_count();
- } else if (name == "use_pfs") {
- G()->net_query_dispatcher().update_use_pfs();
- } else if (name == "use_storage_optimizer") {
- send_closure(storage_manager_, &StorageManager::update_use_storage_optimizer);
- } else if (name == "rating_e_decay") {
- return send_closure(top_dialog_manager_, &TopDialogManager::update_rating_e_decay);
- } else if (is_internal_config_option(name)) {
+void Td::on_connection_state_changed(ConnectionState new_state) {
+ if (G()->close_flag()) {
return;
}
- // send_closure was already used in the callback
- send_update(make_tl_object<td_api::updateOption>(name, G()->shared_config().get_option_value(name)));
-}
-
-tl_object_ptr<td_api::ConnectionState> Td::get_connection_state_object(StateManager::State state) {
- switch (state) {
- case StateManager::State::Empty:
- UNREACHABLE();
- return nullptr;
- case StateManager::State::WaitingForNetwork:
- return make_tl_object<td_api::connectionStateWaitingForNetwork>();
- case StateManager::State::ConnectingToProxy:
- return make_tl_object<td_api::connectionStateConnectingToProxy>();
- case StateManager::State::Connecting:
- return make_tl_object<td_api::connectionStateConnecting>();
- case StateManager::State::Updating:
- return make_tl_object<td_api::connectionStateUpdating>();
- case StateManager::State::Ready:
- return make_tl_object<td_api::connectionStateReady>();
- default:
- UNREACHABLE();
- return nullptr;
- }
-}
-
-void Td::on_connection_state_changed(StateManager::State new_state) {
if (new_state == connection_state_) {
LOG(ERROR) << "State manager sends update about unchanged state " << static_cast<int32>(new_state);
return;
}
connection_state_ = new_state;
- send_closure(actor_id(this), &Td::send_update,
- make_tl_object<td_api::updateConnectionState>(get_connection_state_object(connection_state_)));
-}
-
-void Td::on_authorization_lost() {
- LOG(WARNING) << "on_authorization_lost";
- destroy();
-}
-
-static td_api::object_ptr<td_api::error> make_error(int32 code, CSlice error) {
- return td_api::make_object<td_api::error>(code, error.str());
+ send_closure(actor_id(this), &Td::send_update, get_update_connection_state_object(connection_state_));
}
void Td::start_up() {
@@ -4119,13 +3125,23 @@ void Td::start_up() {
LOG_IF(FATAL, symbol != c) << "TDLib requires little-endian platform";
}
+ VLOG(td_init) << "Create Global";
+ old_context_ = set_context(std::make_shared<Global>());
+ G()->set_net_query_stats(td_options_.net_query_stats);
+ inc_request_actor_refcnt(); // guard
+ inc_actor_refcnt(); // guard
+
+ alarm_timeout_.set_callback(on_alarm_timeout_callback);
+ alarm_timeout_.set_callback_data(static_cast<void *>(this));
+
CHECK(state_ == State::WaitParameters);
- send_update(td_api::make_object<td_api::updateAuthorizationState>(
- td_api::make_object<td_api::authorizationStateWaitTdlibParameters>()));
+ for (auto &update : get_fake_current_state()) {
+ send_update(std::move(update));
+ }
}
void Td::tear_down() {
- CHECK(close_flag_ == 5);
+ LOG_CHECK(close_flag_ == 5) << close_flag_;
}
void Td::hangup_shared() {
@@ -4133,7 +3149,7 @@ void Td::hangup_shared() {
auto type = Container<int>::type_from_id(token);
if (type == RequestActorIdType) {
- request_actors_.erase(get_link_token());
+ request_actors_.erase(token);
dec_request_actor_refcnt();
} else if (type == ActorIdType) {
dec_actor_refcnt();
@@ -4143,6 +3159,7 @@ void Td::hangup_shared() {
}
void Td::hangup() {
+ LOG(INFO) << "Receive Td::hangup";
close();
dec_stop_cnt();
}
@@ -4151,51 +3168,89 @@ ActorShared<Td> Td::create_reference() {
inc_actor_refcnt();
return actor_shared(this, ActorIdType);
}
+
void Td::inc_actor_refcnt() {
actor_refcnt_++;
}
void Td::dec_actor_refcnt() {
actor_refcnt_--;
+ if (actor_refcnt_ < 3) {
+ LOG(DEBUG) << "Decrease reference count to " << actor_refcnt_;
+ }
if (actor_refcnt_ == 0) {
if (close_flag_ == 2) {
create_reference();
close_flag_ = 3;
} else if (close_flag_ == 3) {
- LOG(WARNING) << "ON_ACTORS_CLOSED";
+ LOG(INFO) << "All actors were closed";
Timer timer;
animations_manager_.reset();
- LOG(DEBUG) << "AnimationsManager was cleared " << timer;
+ LOG(DEBUG) << "AnimationsManager was cleared" << timer;
+ attach_menu_manager_.reset();
+ LOG(DEBUG) << "AttachMenuManager was cleared" << timer;
audios_manager_.reset();
- LOG(DEBUG) << "AudiosManager was cleared " << timer;
+ LOG(DEBUG) << "AudiosManager was cleared" << timer;
auth_manager_.reset();
- LOG(DEBUG) << "AuthManager was cleared " << timer;
- change_phone_number_manager_.reset();
- LOG(DEBUG) << "ChangePhoneNumberManager was cleared " << timer;
+ LOG(DEBUG) << "AuthManager was cleared" << timer;
+ background_manager_.reset();
+ LOG(DEBUG) << "BackgroundManager was cleared" << timer;
+ callback_queries_manager_.reset();
+ LOG(DEBUG) << "CallbackQueriesManager was cleared" << timer;
contacts_manager_.reset();
- LOG(DEBUG) << "ContactsManager was cleared " << timer;
+ LOG(DEBUG) << "ContactsManager was cleared" << timer;
+ country_info_manager_.reset();
+ LOG(DEBUG) << "CountryInfoManager was cleared" << timer;
documents_manager_.reset();
- LOG(DEBUG) << "DocumentsManager was cleared " << timer;
+ LOG(DEBUG) << "DocumentsManager was cleared" << timer;
+ download_manager_.reset();
+ LOG(DEBUG) << "DownloadManager was cleared" << timer;
file_manager_.reset();
- LOG(DEBUG) << "FileManager was cleared " << timer;
+ LOG(DEBUG) << "FileManager was cleared" << timer;
+ file_reference_manager_.reset();
+ LOG(DEBUG) << "FileReferenceManager was cleared" << timer;
+ forum_topic_manager_.reset();
+ LOG(DEBUG) << "ForumTopicManager was cleared" << timer;
+ game_manager_.reset();
+ LOG(DEBUG) << "GameManager was cleared" << timer;
+ group_call_manager_.reset();
+ LOG(DEBUG) << "GroupCallManager was cleared" << timer;
inline_queries_manager_.reset();
- LOG(DEBUG) << "InlineQueriesManager was cleared " << timer;
+ LOG(DEBUG) << "InlineQueriesManager was cleared" << timer;
+ link_manager_.reset();
+ LOG(DEBUG) << "LinkManager was cleared" << timer;
messages_manager_.reset();
- LOG(DEBUG) << "MessagesManager was cleared " << timer;
+ LOG(DEBUG) << "MessagesManager was cleared" << timer;
+ notification_manager_.reset();
+ LOG(DEBUG) << "NotificationManager was cleared" << timer;
+ notification_settings_manager_.reset();
+ LOG(DEBUG) << "NotificationSettingsManager was cleared" << timer;
+ poll_manager_.reset();
+ LOG(DEBUG) << "PollManager was cleared" << timer;
+ sponsored_message_manager_.reset();
+ LOG(DEBUG) << "SponsoredMessageManager was cleared" << timer;
stickers_manager_.reset();
- LOG(DEBUG) << "StickersManager was cleared " << timer;
+ LOG(DEBUG) << "StickersManager was cleared" << timer;
+ theme_manager_.reset();
+ LOG(DEBUG) << "ThemeManager was cleared" << timer;
+ top_dialog_manager_.reset();
+ LOG(DEBUG) << "TopDialogManager was cleared" << timer;
updates_manager_.reset();
- LOG(DEBUG) << "UpdatesManager was cleared " << timer;
+ LOG(DEBUG) << "UpdatesManager was cleared" << timer;
video_notes_manager_.reset();
- LOG(DEBUG) << "VideoNotesManager was cleared " << timer;
+ LOG(DEBUG) << "VideoNotesManager was cleared" << timer;
videos_manager_.reset();
- LOG(DEBUG) << "VideosManager was cleared " << timer;
+ LOG(DEBUG) << "VideosManager was cleared" << timer;
voice_notes_manager_.reset();
- LOG(DEBUG) << "VoiceNotesManager was cleared " << timer;
+ LOG(DEBUG) << "VoiceNotesManager was cleared" << timer;
web_pages_manager_.reset();
- LOG(DEBUG) << "WebPagesManager was cleared " << timer;
- Promise<> promise = PromiseCreator::lambda([actor_id = create_reference()](Unit) mutable { actor_id.reset(); });
+ LOG(DEBUG) << "WebPagesManager was cleared" << timer;
+ G()->set_option_manager(nullptr);
+ option_manager_.reset();
+ LOG(DEBUG) << "OptionManager was cleared" << timer;
+
+ Promise<> promise = PromiseCreator::lambda([actor_id = create_reference()](Unit) mutable { actor_id.reset(); });
if (destroy_flag_) {
G()->close_and_destroy_all(std::move(promise));
} else {
@@ -4212,17 +3267,17 @@ void Td::dec_actor_refcnt() {
}
void Td::on_closed() {
- LOG(WARNING) << "ON_CLOSED";
close_flag_ = 5;
send_update(
td_api::make_object<td_api::updateAuthorizationState>(td_api::make_object<td_api::authorizationStateClosed>()));
- callback_->on_closed();
dec_stop_cnt();
}
void Td::dec_stop_cnt() {
stop_cnt_--;
if (stop_cnt_ == 0) {
+ LOG(INFO) << "Stop Td";
+ set_context(std::move(old_context_));
stop();
}
}
@@ -4233,15 +3288,29 @@ void Td::inc_request_actor_refcnt() {
void Td::dec_request_actor_refcnt() {
request_actor_refcnt_--;
+ LOG(DEBUG) << "Decrease request actor count to " << request_actor_refcnt_;
if (request_actor_refcnt_ == 0) {
- LOG(WARNING) << "no request actors";
+ LOG(INFO) << "Have no request actors";
clear();
dec_actor_refcnt(); // remove guard
}
}
-void Td::clear_handlers() {
- result_handlers_.clear();
+void Td::clear_requests() {
+ while (!pending_alarms_.empty()) {
+ auto it = pending_alarms_.begin();
+ auto alarm_id = it->first;
+ pending_alarms_.erase(it);
+ alarm_timeout_.cancel_timeout(alarm_id);
+ }
+ while (!request_set_.empty()) {
+ uint64 id = request_set_.begin()->first;
+ if (destroy_flag_) {
+ send_error_impl(id, make_error(401, "Unauthorized"));
+ } else {
+ send_error_impl(id, make_error(500, "Request aborted"));
+ }
+ }
}
void Td::clear() {
@@ -4249,92 +3318,123 @@ void Td::clear() {
return;
}
+ LOG(INFO) << "Clear Td";
close_flag_ = 2;
Timer timer;
- if (destroy_flag_) {
- for (auto &option : G()->shared_config().get_options()) {
- if (!is_internal_config_option(option.first)) {
- send_update(make_tl_object<td_api::updateOption>(option.first, make_tl_object<td_api::optionValueEmpty>()));
- }
+ if (!auth_manager_->is_bot()) {
+ if (destroy_flag_) {
+ notification_manager_->destroy_all_notifications();
+ } else {
+ notification_manager_->flush_all_notifications();
}
}
- LOG(DEBUG) << "Options was cleared " << timer;
G()->net_query_creator().stop_check();
- clear_handlers();
- LOG(DEBUG) << "Handlers was cleared " << timer;
+ result_handlers_.clear();
+ LOG(DEBUG) << "Handlers were cleared" << timer;
G()->net_query_dispatcher().stop();
- LOG(DEBUG) << "NetQueryDispatcher was stopped " << timer;
+ LOG(DEBUG) << "NetQueryDispatcher was stopped" << timer;
state_manager_.reset();
- LOG(DEBUG) << "StateManager was cleared " << timer;
- while (!pending_alarms_.empty()) {
- auto it = pending_alarms_.begin();
- auto alarm_id = it->first;
- pending_alarms_.erase(it);
- alarm_timeout_.cancel_timeout(alarm_id);
- }
- while (!request_set_.empty()) {
- uint64 id = *request_set_.begin();
- if (destroy_flag_) {
- send_error_impl(id, make_error(401, "Unauthorized"));
- } else {
- send_error_impl(id, make_error(500, "Internal Server Error: closing"));
- }
- }
+ LOG(DEBUG) << "StateManager was cleared" << timer;
+ clear_requests();
if (is_online_) {
is_online_ = false;
alarm_timeout_.cancel_timeout(ONLINE_ALARM_ID);
}
alarm_timeout_.cancel_timeout(PING_SERVER_ALARM_ID);
- LOG(DEBUG) << "Requests was answered " << timer;
+ alarm_timeout_.cancel_timeout(TERMS_OF_SERVICE_ALARM_ID);
+ alarm_timeout_.cancel_timeout(PROMO_DATA_ALARM_ID);
+ LOG(DEBUG) << "Requests were answered" << timer;
// close all pure actors
call_manager_.reset();
- LOG(DEBUG) << "CallManager was cleared " << timer;
+ LOG(DEBUG) << "CallManager was cleared" << timer;
+ change_phone_number_manager_.reset();
+ LOG(DEBUG) << "ChangePhoneNumberManager was cleared" << timer;
config_manager_.reset();
- LOG(DEBUG) << "ConfigManager was cleared " << timer;
+ LOG(DEBUG) << "ConfigManager was cleared" << timer;
+ confirm_phone_number_manager_.reset();
+ LOG(DEBUG) << "ConfirmPhoneNumberManager was cleared" << timer;
device_token_manager_.reset();
- LOG(DEBUG) << "DeviceTokenManager was cleared " << timer;
+ LOG(DEBUG) << "DeviceTokenManager was cleared" << timer;
hashtag_hints_.reset();
- LOG(DEBUG) << "HashtagHints was cleared " << timer;
+ LOG(DEBUG) << "HashtagHints was cleared" << timer;
+ language_pack_manager_.reset();
+ LOG(DEBUG) << "LanguagePackManager was cleared" << timer;
net_stats_manager_.reset();
- LOG(DEBUG) << "NetStatsManager was cleared " << timer;
+ LOG(DEBUG) << "NetStatsManager was cleared" << timer;
password_manager_.reset();
- LOG(DEBUG) << "PasswordManager was cleared " << timer;
+ LOG(DEBUG) << "PasswordManager was cleared" << timer;
privacy_manager_.reset();
- LOG(DEBUG) << "PrivacyManager was cleared " << timer;
+ LOG(DEBUG) << "PrivacyManager was cleared" << timer;
+ secure_manager_.reset();
+ LOG(DEBUG) << "SecureManager was cleared" << timer;
secret_chats_manager_.reset();
- LOG(DEBUG) << "SecretChatsManager was cleared " << timer;
+ LOG(DEBUG) << "SecretChatsManager was cleared" << timer;
storage_manager_.reset();
- LOG(DEBUG) << "StorageManager was cleared " << timer;
- top_dialog_manager_.reset();
- LOG(DEBUG) << "TopDialogManager was cleared " << timer;
+ LOG(DEBUG) << "StorageManager was cleared" << timer;
+ verify_phone_number_manager_.reset();
+ LOG(DEBUG) << "VerifyPhoneNumberManager was cleared" << timer;
G()->set_connection_creator(ActorOwn<ConnectionCreator>());
- LOG(DEBUG) << "ConnectionCreator was cleared " << timer;
+ LOG(DEBUG) << "ConnectionCreator was cleared" << timer;
+ G()->set_temp_auth_key_watchdog(ActorOwn<TempAuthKeyWatchdog>());
+ LOG(DEBUG) << "TempAuthKeyWatchdog was cleared" << timer;
// clear actors which are unique pointers
animations_manager_actor_.reset();
- LOG(DEBUG) << "AnimationsManager actor was cleared " << timer;
+ LOG(DEBUG) << "AnimationsManager actor was cleared" << timer;
+ attach_menu_manager_actor_.reset();
+ LOG(DEBUG) << "AttachMenuManager actor was cleared" << timer;
auth_manager_actor_.reset();
- LOG(DEBUG) << "AuthManager actor was cleared " << timer;
- change_phone_number_manager_actor_.reset();
- LOG(DEBUG) << "ChangePhoneNumberManager actor was cleared " << timer;
+ LOG(DEBUG) << "AuthManager actor was cleared" << timer;
+ background_manager_actor_.reset();
+ LOG(DEBUG) << "BackgroundManager actor was cleared" << timer;
contacts_manager_actor_.reset();
- LOG(DEBUG) << "ContactsManager actor was cleared " << timer;
+ LOG(DEBUG) << "ContactsManager actor was cleared" << timer;
+ country_info_manager_actor_.reset();
+ LOG(DEBUG) << "CountryInfoManager actor was cleared" << timer;
+ download_manager_actor_.reset();
+ LOG(DEBUG) << "DownloadManager actor was cleared" << timer;
file_manager_actor_.reset();
- LOG(DEBUG) << "FileManager actor was cleared " << timer;
+ LOG(DEBUG) << "FileManager actor was cleared" << timer;
+ file_reference_manager_actor_.reset();
+ LOG(DEBUG) << "FileReferenceManager actor was cleared" << timer;
+ forum_topic_manager_actor_.reset();
+ LOG(DEBUG) << "ForumTopicManager actor was cleared" << timer;
+ game_manager_actor_.reset();
+ LOG(DEBUG) << "GameManager actor was cleared" << timer;
+ group_call_manager_actor_.reset();
+ LOG(DEBUG) << "GroupCallManager actor was cleared" << timer;
inline_queries_manager_actor_.reset();
- LOG(DEBUG) << "InlineQueriesManager actor was cleared " << timer;
+ LOG(DEBUG) << "InlineQueriesManager actor was cleared" << timer;
+ link_manager_actor_.reset();
+ LOG(DEBUG) << "LinkManager actor was cleared" << timer;
messages_manager_actor_.reset(); // TODO: Stop silent
- LOG(DEBUG) << "MessagesManager actor was cleared " << timer;
+ LOG(DEBUG) << "MessagesManager actor was cleared" << timer;
+ notification_manager_actor_.reset();
+ LOG(DEBUG) << "NotificationManager actor was cleared" << timer;
+ notification_settings_manager_actor_.reset();
+ LOG(DEBUG) << "NotificationSettingsManager actor was cleared" << timer;
+ poll_manager_actor_.reset();
+ LOG(DEBUG) << "PollManager actor was cleared" << timer;
+ sponsored_message_manager_actor_.reset();
+ LOG(DEBUG) << "SponsoredMessageManager actor was cleared" << timer;
stickers_manager_actor_.reset();
- LOG(DEBUG) << "StickersManager actor was cleared " << timer;
+ LOG(DEBUG) << "StickersManager actor was cleared" << timer;
+ theme_manager_actor_.reset();
+ LOG(DEBUG) << "ThemeManager actor was cleared" << timer;
+ top_dialog_manager_actor_.reset();
+ LOG(DEBUG) << "TopDialogManager actor was cleared" << timer;
updates_manager_actor_.reset();
- LOG(DEBUG) << "UpdatesManager actor was cleared " << timer;
+ LOG(DEBUG) << "UpdatesManager actor was cleared" << timer;
+ video_notes_manager_actor_.reset();
+ LOG(DEBUG) << "VideoNotesManager actor was cleared" << timer;
+ voice_notes_manager_actor_.reset();
+ LOG(DEBUG) << "VoiceNotesManager actor was cleared" << timer;
web_pages_manager_actor_.reset();
- LOG(DEBUG) << "WebPagesManager actor was cleared " << timer;
+ LOG(DEBUG) << "WebPagesManager actor was cleared" << timer;
}
void Td::close() {
@@ -4350,81 +3450,259 @@ void Td::close_impl(bool destroy_flag) {
if (close_flag_) {
return;
}
+
+ LOG(WARNING) << (destroy_flag ? "Destroy" : "Close") << " Td in state " << static_cast<int32>(state_);
if (state_ == State::WaitParameters) {
- return on_closed();
- }
- if (state_ == State::Decrypt) {
- if (destroy_flag) {
- TdDb::destroy(parameters_);
- }
+ clear_requests();
state_ = State::Close;
close_flag_ = 4;
- return dec_actor_refcnt();
+ G()->set_close_flag();
+ send_update(td_api::make_object<td_api::updateAuthorizationState>(
+ td_api::make_object<td_api::authorizationStateClosing>()));
+
+ request_actors_.clear();
+ return send_closure_later(actor_id(this), &Td::dec_request_actor_refcnt); // remove guard
}
+
state_ = State::Close;
close_flag_ = 1;
G()->set_close_flag();
- send_closure(auth_manager_actor_, &AuthManager::on_closing);
- LOG(WARNING) << "Close " << tag("destroy", destroy_flag);
+ send_closure(auth_manager_actor_, &AuthManager::on_closing, destroy_flag);
+ updates_manager_->timeout_expired(); // save pts and qts
- // wait till all request_actors will stop.
+ // wait till all request_actors will stop
request_actors_.clear();
G()->td_db()->flush_all();
send_closure_later(actor_id(this), &Td::dec_request_actor_refcnt); // remove guard
}
-class Td::DownloadFileCallback : public FileManager::DownloadCallback {
+class Td::DownloadFileCallback final : public FileManager::DownloadCallback {
public:
- void on_progress(FileId file_id) override {
- }
-
- void on_download_ok(FileId file_id) override {
+ void on_download_ok(FileId file_id) final {
+ send_closure(G()->td(), &Td::on_file_download_finished, file_id);
}
- void on_download_error(FileId file_id, Status error) override {
+ void on_download_error(FileId file_id, Status error) final {
+ send_closure(G()->td(), &Td::on_file_download_finished, file_id);
}
};
-class Td::UploadFileCallback : public FileManager::UploadCallback {
+class Td::UploadFileCallback final : public FileManager::UploadCallback {
public:
- void on_progress(FileId file_id) override {
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
+ // cancel file upload of the file to allow next upload with the same file to succeed
+ send_closure(G()->file_manager(), &FileManager::cancel_upload, file_id);
}
- void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) override {
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
// cancel file upload of the file to allow next upload with the same file to succeed
- send_closure(G()->file_manager(), &FileManager::upload, file_id, nullptr, 0, 0);
+ send_closure(G()->file_manager(), &FileManager::cancel_upload, file_id);
}
- void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) override {
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
// cancel file upload of the file to allow next upload with the same file to succeed
- send_closure(G()->file_manager(), &FileManager::upload, file_id, nullptr, 0, 0);
+ send_closure(G()->file_manager(), &FileManager::cancel_upload, file_id);
}
- void on_upload_error(FileId file_id, Status error) override {
+ void on_upload_error(FileId file_id, Status error) final {
}
};
-int VERBOSITY_NAME(td_init) = VERBOSITY_NAME(DEBUG) + 3;
+template <class T>
+void Td::complete_pending_preauthentication_requests(const T &func) {
+ for (auto &request : pending_preauthentication_requests_) {
+ if (request.second != nullptr && func(request.second->get_id())) {
+ downcast_call(*request.second, [this, id = request.first](auto &request) { this->on_request(id, request); });
+ request.second = nullptr;
+ }
+ }
+}
-Status Td::init(DbKey key) {
+int32 Td::get_database_scheduler_id() {
auto current_scheduler_id = Scheduler::instance()->sched_id();
auto scheduler_count = Scheduler::instance()->sched_count();
+ return min(current_scheduler_id + 1, scheduler_count - 1);
+}
+
+void Td::finish_set_parameters() {
+ CHECK(set_parameters_request_id_ != 0);
+ set_parameters_request_id_ = 0;
+
+ if (pending_set_parameters_requests_.empty()) {
+ return;
+ }
+
+ VLOG(td_init) << "Continue to execute " << pending_set_parameters_requests_.size() << " pending requests";
+ auto requests = std::move(pending_set_parameters_requests_);
+ for (auto &request : requests) {
+ run_request(request.first, std::move(request.second));
+ }
+ CHECK(pending_set_parameters_requests_.size() < requests.size());
+}
+
+void Td::init(Result<TdDb::OpenedDatabase> r_opened_database) {
+ CHECK(set_parameters_request_id_ != 0);
+ if (r_opened_database.is_error()) {
+ LOG(WARNING) << "Failed to open database: " << r_opened_database.error();
+ send_closure(actor_id(this), &Td::send_error, set_parameters_request_id_,
+ Status::Error(400, r_opened_database.error().message()));
+ return finish_set_parameters();
+ }
+ auto events = r_opened_database.move_as_ok();
+
+ parameters_.database_directory = std::move(events.database_directory);
+ parameters_.files_directory = std::move(events.files_directory);
- VLOG(td_init) << "Begin to init database";
- TdDb::Events events;
- TRY_RESULT(td_db,
- TdDb::open(min(current_scheduler_id + 1, scheduler_count - 1), parameters_, std::move(key), events));
LOG(INFO) << "Successfully inited database in " << tag("database_directory", parameters_.database_directory)
<< " and " << tag("files_directory", parameters_.files_directory);
- G()->init(parameters_, actor_id(this), std::move(td_db)).ensure();
+ VLOG(td_init) << "Successfully inited database";
+
+ G()->init(parameters_, actor_id(this), std::move(events.database)).ensure();
+
+ init_options_and_network();
+
+ // we need to process td_api::getOption along with td_api::setOption for consistency
+ // we need to process td_api::setOption before managers and MTProto header are created,
+ // because their initialiation may be affected by the options
+ complete_pending_preauthentication_requests([](int32 id) {
+ switch (id) {
+ case td_api::getOption::ID:
+ case td_api::setOption::ID:
+ return true;
+ default:
+ return false;
+ }
+ });
+
+ options_.language_pack = G()->get_option_string("localization_target");
+ options_.language_code = G()->get_option_string("language_pack_id");
+ options_.parameters = G()->get_option_string("connection_parameters");
+ options_.tz_offset = static_cast<int32>(G()->get_option_integer("utc_time_offset"));
+ options_.is_emulator = G()->get_option_boolean("is_emulator");
+ // options_.proxy = Proxy();
+ G()->set_mtproto_header(make_unique<MtprotoHeader>(options_));
+ G()->set_store_all_files_in_files_directory(G()->get_option_boolean("store_all_files_in_files_directory"));
+
+ VLOG(td_init) << "Create NetQueryDispatcher";
+ auto net_query_dispatcher = make_unique<NetQueryDispatcher>([&] { return create_reference(); });
+ G()->set_net_query_dispatcher(std::move(net_query_dispatcher));
+
+ complete_pending_preauthentication_requests([](int32 id) {
+ // pingProxy uses NetQueryDispatcher to get main_dc_id, so must be called after NetQueryDispatcher is created
+ return id == td_api::pingProxy::ID;
+ });
+
+ VLOG(td_init) << "Create AuthManager";
+ auth_manager_ = td::make_unique<AuthManager>(parameters_.api_id, parameters_.api_hash, create_reference());
+ auth_manager_actor_ = register_actor("AuthManager", auth_manager_.get());
+ G()->set_auth_manager(auth_manager_actor_.get());
+
+ init_file_manager();
+
+ init_managers();
+
+ storage_manager_ = create_actor<StorageManager>("StorageManager", create_reference(), G()->get_gc_scheduler_id());
+ G()->set_storage_manager(storage_manager_.get());
+
+ option_manager_->on_td_inited();
+
+ VLOG(td_init) << "Send binlog events";
+ for (auto &event : events.user_events) {
+ contacts_manager_->on_binlog_user_event(std::move(event));
+ }
+
+ for (auto &event : events.channel_events) {
+ contacts_manager_->on_binlog_channel_event(std::move(event));
+ }
+
+ // chats may contain links to channels, so should be inited after
+ for (auto &event : events.chat_events) {
+ contacts_manager_->on_binlog_chat_event(std::move(event));
+ }
+
+ for (auto &event : events.secret_chat_events) {
+ contacts_manager_->on_binlog_secret_chat_event(std::move(event));
+ }
+
+ for (auto &event : events.web_page_events) {
+ web_pages_manager_->on_binlog_web_page_event(std::move(event));
+ }
+
+ for (auto &event : events.save_app_log_events) {
+ on_save_app_log_binlog_event(this, std::move(event));
+ }
+
+ if (option_manager_->get_option_boolean("default_reaction_needs_sync")) {
+ send_set_default_reaction_query(this);
+ }
+
+ if (is_online_) {
+ on_online_updated(true, true);
+ }
+ if (auth_manager_->is_bot()) {
+ set_is_bot_online(true);
+ }
+
+ // Send binlog events to managers
+ //
+ // 1. Actors must receive all binlog events before other queries.
+ //
+ // -- All actors have one "entry point". So there is only one way to send query to them. So all queries are ordered
+ // for each Actor.
+ //
+ // 2. An actor must not make some decisions before all binlog events are processed.
+ // For example, SecretChatActor must not send RequestKey, before it receives log event with RequestKey and understands
+ // that RequestKey was already sent.
+ //
+ // 3. During replay of binlog some queries may be sent to other actors. They shouldn't process such events before all
+ // their binlog events are processed. So actor may receive some old queries. It must be in its actual state in
+ // orded to handle them properly.
+ //
+ // -- Use send_closure_later, so actors don't even start process binlog events, before all binlog events are sent
+
+ for (auto &event : events.to_secret_chats_manager) {
+ send_closure_later(secret_chats_manager_, &SecretChatsManager::replay_binlog_event, std::move(event));
+ }
+
+ send_closure_later(poll_manager_actor_, &PollManager::on_binlog_events, std::move(events.to_poll_manager));
+
+ send_closure_later(messages_manager_actor_, &MessagesManager::on_binlog_events,
+ std::move(events.to_messages_manager));
- // Init all managers and actors
+ send_closure_later(notification_manager_actor_, &NotificationManager::on_binlog_events,
+ std::move(events.to_notification_manager));
+
+ send_closure_later(notification_settings_manager_actor_, &NotificationSettingsManager::on_binlog_events,
+ std::move(events.to_notification_settings_manager));
+
+ send_closure(secret_chats_manager_, &SecretChatsManager::binlog_replay_finish);
+
+ VLOG(td_init) << "Ping datacenter";
+ if (!auth_manager_->is_authorized()) {
+ country_info_manager_->get_current_country_code(Promise<string>());
+ } else {
+ updates_manager_->get_difference("init");
+ schedule_get_terms_of_service(0);
+ schedule_get_promo_data(0);
+ }
+
+ complete_pending_preauthentication_requests([](int32 id) { return true; });
+
+ VLOG(td_init) << "Finish initialization";
+
+ state_ = State::Run;
+
+ send_closure(actor_id(this), &Td::send_result, set_parameters_request_id_, td_api::make_object<td_api::ok>());
+ return finish_set_parameters();
+}
+
+void Td::init_options_and_network() {
VLOG(td_init) << "Create StateManager";
- class StateManagerCallback : public StateManager::Callback {
+ class StateManagerCallback final : public StateManager::Callback {
public:
explicit StateManagerCallback(ActorShared<Td> td) : td_(std::move(td)) {
}
- bool on_state(StateManager::State state) override {
+ bool on_state(ConnectionState state) final {
send_closure(td_, &Td::on_connection_state_changed, state);
return td_.is_alive();
}
@@ -4432,70 +3710,108 @@ Status Td::init(DbKey key) {
private:
ActorShared<Td> td_;
};
- state_manager_ = create_actor<StateManager>("State manager");
+ state_manager_ = create_actor<StateManager>("State manager", create_reference());
send_closure(state_manager_, &StateManager::add_callback, make_unique<StateManagerCallback>(create_reference()));
G()->set_state_manager(state_manager_.get());
- connection_state_ = StateManager::State::Empty;
-
- VLOG(td_init) << "Create ConnectionCreator";
- {
- auto connection_creator = create_actor<ConnectionCreator>("ConnectionCreator", create_reference());
- auto net_stats_manager = create_actor<NetStatsManager>("NetStatsManager", create_reference());
- // How else could I let two actor know about each other, without quite complex async logic?
- auto net_stats_manager_ptr = net_stats_manager->get_actor_unsafe();
- net_stats_manager_ptr->init();
- connection_creator->get_actor_unsafe()->set_net_stats_callback(net_stats_manager_ptr->get_common_stats_callback(),
- net_stats_manager_ptr->get_media_stats_callback());
- G()->set_net_stats_file_callbacks(net_stats_manager_ptr->get_file_stats_callbacks());
+ VLOG(td_init) << "Create OptionManager";
+ option_manager_ = make_unique<OptionManager>(this);
+ G()->set_option_manager(option_manager_.get());
- G()->set_connection_creator(std::move(connection_creator));
- net_stats_manager_ = std::move(net_stats_manager);
- }
+ init_connection_creator();
VLOG(td_init) << "Create TempAuthKeyWatchdog";
- auto temp_auth_key_watchdog = create_actor<TempAuthKeyWatchdog>("TempAuthKeyWatchdog");
+ auto temp_auth_key_watchdog = create_actor<TempAuthKeyWatchdog>("TempAuthKeyWatchdog", create_reference());
G()->set_temp_auth_key_watchdog(std::move(temp_auth_key_watchdog));
- VLOG(td_init) << "Create ConfigManager and ConfigShared";
- class ConfigSharedCallback : public ConfigShared::Callback {
- public:
- void on_option_updated(const string &name) override {
- send_closure(G()->td(), &Td::on_config_option_updated, name);
- }
- };
- send_closure(
- actor_id(this), &Td::send_update,
- make_tl_object<td_api::updateOption>("version", make_tl_object<td_api::optionValueString>(TDLIB_VERSION)));
-
- G()->set_shared_config(
- std::make_unique<ConfigShared>(G()->td_db()->get_config_pmc(), std::make_unique<ConfigSharedCallback>()));
+ VLOG(td_init) << "Create ConfigManager";
config_manager_ = create_actor<ConfigManager>("ConfigManager", create_reference());
G()->set_config_manager(config_manager_.get());
+}
- VLOG(td_init) << "Create NetQueryDispatcher";
- auto net_query_dispatcher = std::make_unique<NetQueryDispatcher>([&] { return create_reference(); });
- G()->set_net_query_dispatcher(std::move(net_query_dispatcher));
-
- VLOG(td_init) << "Create AuthManager";
- auth_manager_ = std::make_unique<AuthManager>(parameters_.api_id, parameters_.api_hash, create_reference());
- auth_manager_actor_ = register_actor("AuthManager", auth_manager_.get());
+void Td::init_connection_creator() {
+ VLOG(td_init) << "Create ConnectionCreator";
+ auto connection_creator = create_actor<ConnectionCreator>("ConnectionCreator", create_reference());
+ auto net_stats_manager = create_actor<NetStatsManager>("NetStatsManager", create_reference());
+
+ // How else could I let two actor know about each other, without quite complex async logic?
+ auto net_stats_manager_ptr = net_stats_manager.get_actor_unsafe();
+ net_stats_manager_ptr->init();
+ connection_creator.get_actor_unsafe()->set_net_stats_callback(net_stats_manager_ptr->get_common_stats_callback(),
+ net_stats_manager_ptr->get_media_stats_callback());
+ G()->set_net_stats_file_callbacks(net_stats_manager_ptr->get_file_stats_callbacks());
+
+ G()->set_connection_creator(std::move(connection_creator));
+ net_stats_manager_ = std::move(net_stats_manager);
+
+ complete_pending_preauthentication_requests([](int32 id) {
+ switch (id) {
+ case td_api::setNetworkType::ID:
+ case td_api::getNetworkStatistics::ID:
+ case td_api::addNetworkStatistics::ID:
+ case td_api::resetNetworkStatistics::ID:
+ case td_api::addProxy::ID:
+ case td_api::editProxy::ID:
+ case td_api::enableProxy::ID:
+ case td_api::disableProxy::ID:
+ case td_api::removeProxy::ID:
+ case td_api::getProxies::ID:
+ case td_api::getProxyLink::ID:
+ return true;
+ default:
+ return false;
+ }
+ });
+}
+void Td::init_file_manager() {
VLOG(td_init) << "Create FileManager";
download_file_callback_ = std::make_shared<DownloadFileCallback>();
upload_file_callback_ = std::make_shared<UploadFileCallback>();
- class FileManagerContext : public FileManager::Context {
+ class FileManagerContext final : public FileManager::Context {
public:
explicit FileManagerContext(Td *td) : td_(td) {
}
- void on_new_file(int64 size) final {
- send_closure(G()->storage_manager(), &StorageManager::on_new_file, size);
+
+ bool need_notify_on_new_files() final {
+ return !td_->auth_manager_->is_bot();
}
+
+ void on_new_file(int64 size, int64 real_size, int32 cnt) final {
+ send_closure(G()->storage_manager(), &StorageManager::on_new_file, size, real_size, cnt);
+ }
+
void on_file_updated(FileId file_id) final {
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateFile>(td_->file_manager_->get_file_object(file_id)));
}
+
+ bool add_file_source(FileId file_id, FileSourceId file_source_id) final {
+ return td_->file_reference_manager_->add_file_source(file_id, file_source_id);
+ }
+
+ bool remove_file_source(FileId file_id, FileSourceId file_source_id) final {
+ return td_->file_reference_manager_->remove_file_source(file_id, file_source_id);
+ }
+
+ void on_merge_files(FileId to_file_id, FileId from_file_id) final {
+ td_->file_reference_manager_->merge(to_file_id, from_file_id);
+ }
+
+ vector<FileSourceId> get_some_file_sources(FileId file_id) final {
+ return td_->file_reference_manager_->get_some_file_sources(file_id);
+ }
+
+ void repair_file_reference(FileId file_id, Promise<Unit> promise) final {
+ send_closure(G()->file_reference_manager(), &FileReferenceManager::repair_file_reference, file_id,
+ std::move(promise));
+ }
+
+ void reload_photo(PhotoSizeSource source, Promise<Unit> promise) final {
+ FileReferenceManager::reload_photo(std::move(source), std::move(promise));
+ }
+
ActorShared<> create_reference() final {
return td_->create_reference();
}
@@ -4503,131 +3819,150 @@ Status Td::init(DbKey key) {
private:
Td *td_;
};
- file_manager_ = std::make_unique<FileManager>(std::make_unique<FileManagerContext>(this));
+
+ file_manager_ = make_unique<FileManager>(make_unique<FileManagerContext>(this));
file_manager_actor_ = register_actor("FileManager", file_manager_.get());
file_manager_->init_actor();
G()->set_file_manager(file_manager_actor_.get());
+ file_reference_manager_ = make_unique<FileReferenceManager>(create_reference());
+ file_reference_manager_actor_ = register_actor("FileReferenceManager", file_reference_manager_.get());
+ G()->set_file_reference_manager(file_reference_manager_actor_.get());
+}
+
+void Td::init_managers() {
VLOG(td_init) << "Create Managers";
audios_manager_ = make_unique<AudiosManager>(this);
callback_queries_manager_ = make_unique<CallbackQueriesManager>(this);
documents_manager_ = make_unique<DocumentsManager>(this);
- video_notes_manager_ = make_unique<VideoNotesManager>(this);
videos_manager_ = make_unique<VideosManager>(this);
- voice_notes_manager_ = make_unique<VoiceNotesManager>(this);
- animations_manager_ = std::make_unique<AnimationsManager>(this, create_reference());
+ animations_manager_ = make_unique<AnimationsManager>(this, create_reference());
animations_manager_actor_ = register_actor("AnimationsManager", animations_manager_.get());
G()->set_animations_manager(animations_manager_actor_.get());
- change_phone_number_manager_ = std::make_unique<ChangePhoneNumberManager>(create_reference());
- change_phone_number_manager_actor_ = register_actor("ChangePhoneNumberManager", change_phone_number_manager_.get());
- contacts_manager_ = std::make_unique<ContactsManager>(this, create_reference());
+ attach_menu_manager_ = make_unique<AttachMenuManager>(this, create_reference());
+ attach_menu_manager_actor_ = register_actor("AttachMenuManager", attach_menu_manager_.get());
+ G()->set_attach_menu_manager(attach_menu_manager_actor_.get());
+ background_manager_ = make_unique<BackgroundManager>(this, create_reference());
+ background_manager_actor_ = register_actor("BackgroundManager", background_manager_.get());
+ G()->set_background_manager(background_manager_actor_.get());
+ contacts_manager_ = make_unique<ContactsManager>(this, create_reference());
contacts_manager_actor_ = register_actor("ContactsManager", contacts_manager_.get());
G()->set_contacts_manager(contacts_manager_actor_.get());
- inline_queries_manager_ = std::make_unique<InlineQueriesManager>(this, create_reference());
+ country_info_manager_ = make_unique<CountryInfoManager>(this, create_reference());
+ country_info_manager_actor_ = register_actor("CountryInfoManager", country_info_manager_.get());
+ download_manager_ = DownloadManager::create(td::make_unique<DownloadManagerCallback>(this, create_reference()));
+ download_manager_actor_ = register_actor("DownloadManager", download_manager_.get());
+ G()->set_download_manager(download_manager_actor_.get());
+ forum_topic_manager_ = make_unique<ForumTopicManager>(this, create_reference());
+ forum_topic_manager_actor_ = register_actor("ForumTopicManager", forum_topic_manager_.get());
+ G()->set_forum_topic_manager(forum_topic_manager_actor_.get());
+ game_manager_ = make_unique<GameManager>(this, create_reference());
+ game_manager_actor_ = register_actor("GameManager", game_manager_.get());
+ G()->set_game_manager(game_manager_actor_.get());
+ group_call_manager_ = make_unique<GroupCallManager>(this, create_reference());
+ group_call_manager_actor_ = register_actor("GroupCallManager", group_call_manager_.get());
+ G()->set_group_call_manager(group_call_manager_actor_.get());
+ inline_queries_manager_ = make_unique<InlineQueriesManager>(this, create_reference());
inline_queries_manager_actor_ = register_actor("InlineQueriesManager", inline_queries_manager_.get());
- messages_manager_ = std::make_unique<MessagesManager>(this, create_reference());
+ link_manager_ = make_unique<LinkManager>(this, create_reference());
+ link_manager_actor_ = register_actor("LinkManager", link_manager_.get());
+ G()->set_link_manager(link_manager_actor_.get());
+ messages_manager_ = make_unique<MessagesManager>(this, create_reference());
messages_manager_actor_ = register_actor("MessagesManager", messages_manager_.get());
G()->set_messages_manager(messages_manager_actor_.get());
- stickers_manager_ = std::make_unique<StickersManager>(this, create_reference());
+ notification_manager_ = make_unique<NotificationManager>(this, create_reference());
+ notification_manager_actor_ = register_actor("NotificationManager", notification_manager_.get());
+ G()->set_notification_manager(notification_manager_actor_.get());
+ notification_settings_manager_ = make_unique<NotificationSettingsManager>(this, create_reference());
+ notification_settings_manager_actor_ =
+ register_actor("NotificationSettingsManager", notification_settings_manager_.get());
+ G()->set_notification_settings_manager(notification_settings_manager_actor_.get());
+ poll_manager_ = make_unique<PollManager>(this, create_reference());
+ poll_manager_actor_ = register_actor("PollManager", poll_manager_.get());
+ sponsored_message_manager_ = make_unique<SponsoredMessageManager>(this, create_reference());
+ sponsored_message_manager_actor_ = register_actor("SponsoredMessageManager", sponsored_message_manager_.get());
+ G()->set_sponsored_message_manager(sponsored_message_manager_actor_.get());
+ stickers_manager_ = make_unique<StickersManager>(this, create_reference());
stickers_manager_actor_ = register_actor("StickersManager", stickers_manager_.get());
G()->set_stickers_manager(stickers_manager_actor_.get());
- updates_manager_ = std::make_unique<UpdatesManager>(this, create_reference());
+ theme_manager_ = make_unique<ThemeManager>(this, create_reference());
+ theme_manager_actor_ = register_actor("ThemeManager", theme_manager_.get());
+ G()->set_theme_manager(theme_manager_actor_.get());
+ top_dialog_manager_ = make_unique<TopDialogManager>(this, create_reference());
+ top_dialog_manager_actor_ = register_actor("TopDialogManager", top_dialog_manager_.get());
+ G()->set_top_dialog_manager(top_dialog_manager_actor_.get());
+ updates_manager_ = make_unique<UpdatesManager>(this, create_reference());
updates_manager_actor_ = register_actor("UpdatesManager", updates_manager_.get());
G()->set_updates_manager(updates_manager_actor_.get());
- web_pages_manager_ = std::make_unique<WebPagesManager>(this, create_reference());
+ video_notes_manager_ = make_unique<VideoNotesManager>(this, create_reference());
+ video_notes_manager_actor_ = register_actor("VideoNotesManager", video_notes_manager_.get());
+ voice_notes_manager_ = make_unique<VoiceNotesManager>(this, create_reference());
+ voice_notes_manager_actor_ = register_actor("VoiceNotesManager", voice_notes_manager_.get());
+ web_pages_manager_ = make_unique<WebPagesManager>(this, create_reference());
web_pages_manager_actor_ = register_actor("WebPagesManager", web_pages_manager_.get());
G()->set_web_pages_manager(web_pages_manager_actor_.get());
call_manager_ = create_actor<CallManager>("CallManager", create_reference());
G()->set_call_manager(call_manager_.get());
+ change_phone_number_manager_ = create_actor<PhoneNumberManager>(
+ "ChangePhoneNumberManager", PhoneNumberManager::Type::ChangePhone, create_reference());
+ confirm_phone_number_manager_ = create_actor<PhoneNumberManager>(
+ "ConfirmPhoneNumberManager", PhoneNumberManager::Type::ConfirmPhone, create_reference());
device_token_manager_ = create_actor<DeviceTokenManager>("DeviceTokenManager", create_reference());
hashtag_hints_ = create_actor<HashtagHints>("HashtagHints", "text", create_reference());
+ language_pack_manager_ = create_actor<LanguagePackManager>("LanguagePackManager", create_reference());
+ G()->set_language_pack_manager(language_pack_manager_.get());
password_manager_ = create_actor<PasswordManager>("PasswordManager", create_reference());
+ G()->set_password_manager(password_manager_.get());
privacy_manager_ = create_actor<PrivacyManager>("PrivacyManager", create_reference());
secret_chats_manager_ = create_actor<SecretChatsManager>("SecretChatsManager", create_reference());
G()->set_secret_chats_manager(secret_chats_manager_.get());
- storage_manager_ = create_actor<StorageManager>("StorageManager", create_reference(),
- min(current_scheduler_id + 2, scheduler_count - 1));
- G()->set_storage_manager(storage_manager_.get());
- top_dialog_manager_ = create_actor<TopDialogManager>("TopDialogManager", create_reference());
- G()->set_top_dialog_manager(top_dialog_manager_.get());
-
- VLOG(td_init) << "Send binlog events";
- for (auto &event : events.user_events) {
- contacts_manager_->on_binlog_user_event(std::move(event));
- }
-
- for (auto &event : events.channel_events) {
- contacts_manager_->on_binlog_channel_event(std::move(event));
- }
-
- // chats may contain links to channels, so should be inited after
- for (auto &event : events.chat_events) {
- contacts_manager_->on_binlog_chat_event(std::move(event));
- }
-
- for (auto &event : events.secret_chat_events) {
- contacts_manager_->on_binlog_secret_chat_event(std::move(event));
- }
-
- for (auto &event : events.web_page_events) {
- web_pages_manager_->on_binlog_web_page_event(std::move(event));
- }
-
- // Send binlog events to managers
- //
- // 1. Actors must receive all binlog events before other queries.
- //
- // -- All actors have one "entry point". So there is only one way to send query to them. So all queries are ordered
- // for each Actor.
- //
- //
- // 2. An actor must not make some decisions before all binlog events are processed.
- // For example, SecretChatActor must not send RequestKey, before it receives logevent with RequestKey and understands
- // that RequestKey was already sent.
- //
- // -- G()->wait_binlog_replay_finish(Promise<>);
- //
- // 3. During replay of binlog some queries may be sent to other actors. They shouldn't process such events before all
- // their binlog events are processed. So actor may receive some old queries. It must be in it's actual state in
- // orded to handle them properly.
- //
- // -- Use send_closure_later, so actors don't even start process binlog events, before all binlog events are sent
-
- for (auto &event : events.to_secret_chats_manager) {
- send_closure_later(secret_chats_manager_, &SecretChatsManager::replay_binlog_event, std::move(event));
- }
-
- send_closure_later(messages_manager_actor_, &MessagesManager::on_binlog_events,
- std::move(events.to_messages_manager));
-
- // NB: be very careful. This notification may be received before all binlog events are.
- G()->on_binlog_replay_finish();
- send_closure(secret_chats_manager_, &SecretChatsManager::binlog_replay_finish);
-
- VLOG(td_init) << "Ping datacenter";
- if (!auth_manager_->is_authorized()) {
- create_handler<GetNearestDcQuery>(Promise<string>())->send();
- } else {
- updates_manager_->get_difference("init");
- }
-
- state_ = State::Run;
- return Status::OK();
+ secure_manager_ = create_actor<SecureManager>("SecureManager", create_reference());
+ verify_phone_number_manager_ = create_actor<PhoneNumberManager>(
+ "VerifyPhoneNumberManager", PhoneNumberManager::Type::VerifyPhone, create_reference());
}
void Td::send_update(tl_object_ptr<td_api::Update> &&object) {
- switch (object->get_id()) {
+ CHECK(object != nullptr);
+ auto object_id = object->get_id();
+ if (close_flag_ >= 5 && object_id != td_api::updateAuthorizationState::ID) {
+ // just in case
+ return;
+ }
+
+ switch (object_id) {
+ case td_api::updateChatThemes::ID:
case td_api::updateFavoriteStickers::ID:
case td_api::updateInstalledStickerSets::ID:
case td_api::updateRecentStickers::ID:
case td_api::updateSavedAnimations::ID:
+ case td_api::updateSavedNotificationSounds::ID:
case td_api::updateUserStatus::ID:
VLOG(td_requests) << "Sending update: " << oneline(to_string(object));
break;
- case td_api::updateTrendingStickerSets::ID:
- VLOG(td_requests) << "Sending update: updateTrendingStickerSets { ... }";
+ case td_api::updateTrendingStickerSets::ID: {
+ auto update = static_cast<const td_api::updateTrendingStickerSets *>(object.get());
+ auto sticker_sets = update->sticker_sets_.get();
+ VLOG(td_requests) << "Sending update: updateTrendingStickerSets { " << oneline(to_string(update->sticker_type_))
+ << ", total_count = " << sticker_sets->total_count_
+ << ", count = " << sticker_sets->sets_.size() << " }";
+ break;
+ }
+ case td_api::updateAnimatedEmojiMessageClicked::ID / 2:
+ case td_api::updateOption::ID / 2:
+ case td_api::updateChatReadInbox::ID / 2:
+ case td_api::updateUnreadMessageCount::ID / 2:
+ case td_api::updateUnreadChatCount::ID / 2:
+ case td_api::updateChatOnlineMemberCount::ID / 2:
+ case td_api::updateChatAction::ID / 2:
+ case td_api::updateChatFilters::ID / 2:
+ case td_api::updateChatPosition::ID / 2:
+ case td_api::updateFileAddedToDownloads::ID / 2:
+ case td_api::updateFileDownload::ID / 2:
+ case td_api::updateFileRemovedFromDownloads::ID / 2:
+ case td_api::updateDefaultReactionType::ID / 2:
+ LOG(ERROR) << "Sending update: " << oneline(to_string(object));
break;
default:
VLOG(td_requests) << "Sending update: " << to_string(object);
@@ -4644,23 +3979,25 @@ void Td::send_result(uint64 id, tl_object_ptr<td_api::Object> object) {
auto it = request_set_.find(id);
if (it != request_set_.end()) {
- request_set_.erase(it);
- VLOG(td_requests) << "Sending result for request " << id << ": " << to_string(object);
if (object == nullptr) {
object = make_tl_object<td_api::error>(404, "Not Found");
}
+ VLOG(td_requests) << "Sending result for request " << id << ": " << to_string(object);
+ request_set_.erase(it);
callback_->on_result(id, std::move(object));
}
}
void Td::send_error_impl(uint64 id, tl_object_ptr<td_api::error> error) {
CHECK(id != 0);
- CHECK(callback_ != nullptr);
CHECK(error != nullptr);
auto it = request_set_.find(id);
if (it != request_set_.end()) {
- request_set_.erase(it);
+ if (error->code_ == 0 && error->message_ == "Lost promise") {
+ LOG(FATAL) << "Lost promise for query " << id << " of type " << it->second << " in close state " << close_flag_;
+ }
VLOG(td_requests) << "Sending error for request " << id << ": " << oneline(to_string(error));
+ request_set_.erase(it);
callback_->on_error(id, std::move(error));
}
}
@@ -4682,14 +4019,20 @@ void Td::answer_ok_query(uint64 id, Status status) {
}
}
+Promise<Unit> Td::create_ok_request_promise(uint64 id) {
+ return PromiseCreator::lambda([id = id, actor_id = actor_id(this)](Result<Unit> result) {
+ if (result.is_error()) {
+ send_closure(actor_id, &Td::send_error, id, result.move_as_error());
+ } else {
+ send_closure(actor_id, &Td::send_result, id, td_api::make_object<td_api::ok>());
+ }
+ });
+}
+
#define CLEAN_INPUT_STRING(field_name) \
if (!clean_input_string(field_name)) { \
return send_error_raw(id, 400, "Strings must be encoded in UTF-8"); \
}
-#define CHECK_AUTH() \
- if (!auth_manager_->is_authorized()) { \
- return send_error_raw(id, 401, "Unauthorized"); \
- }
#define CHECK_IS_BOT() \
if (!auth_manager_->is_bot()) { \
return send_error_raw(id, 400, "Only bots can use the method"); \
@@ -4707,8 +4050,10 @@ void Td::answer_ok_query(uint64 id, Status status) {
auto slot_id = request_actors_.create(ActorOwn<>(), RequestActorIdType); \
inc_request_actor_refcnt(); \
*request_actors_.get(slot_id) = create_actor<name>(#name, actor_shared(this, slot_id), id, __VA_ARGS__);
-#define CREATE_REQUEST_PROMISE(name) \
- auto name = create_request_promise<std::decay_t<decltype(request)>::ReturnType>(id);
+#define CREATE_REQUEST_PROMISE() auto promise = create_request_promise<std::decay_t<decltype(request)>::ReturnType>(id)
+#define CREATE_OK_REQUEST_PROMISE() \
+ static_assert(std::is_same<std::decay_t<decltype(request)>::ReturnType, td_api::object_ptr<td_api::ok>>::value, ""); \
+ auto promise = create_ok_request_promise(id)
Status Td::fix_parameters(TdParameters &parameters) {
if (parameters.database_directory.empty()) {
@@ -4719,11 +4064,11 @@ Status Td::fix_parameters(TdParameters &parameters) {
VLOG(td_init) << "Fix files_directory";
parameters.files_directory = parameters.database_directory;
}
- if (parameters.use_message_db) {
+ if (parameters.use_message_db && !parameters.use_chat_info_db) {
VLOG(td_init) << "Fix use_chat_info_db";
parameters.use_chat_info_db = true;
}
- if (parameters.use_chat_info_db) {
+ if (parameters.use_chat_info_db && !parameters.use_file_db) {
VLOG(td_init) << "Fix use_file_db";
parameters.use_file_db = true;
}
@@ -4735,47 +4080,13 @@ Status Td::fix_parameters(TdParameters &parameters) {
VLOG(td_init) << "Invalid api_hash";
return Status::Error(400, "Valid api_hash must be provided. Can be obtained at https://my.telegram.org");
}
-
- auto prepare_dir = [](string dir) -> Result<string> {
- CHECK(!dir.empty());
- if (dir.back() != TD_DIR_SLASH) {
- dir += TD_DIR_SLASH;
- }
- TRY_STATUS(mkpath(dir, 0750));
- TRY_RESULT(real_dir, realpath(dir, true));
- if (dir.back() != TD_DIR_SLASH) {
- dir += TD_DIR_SLASH;
- }
- return real_dir;
- };
-
- auto r_database_directory = prepare_dir(parameters.database_directory);
- if (r_database_directory.is_error()) {
- VLOG(td_init) << "Invalid database_directory";
- return Status::Error(400, PSLICE() << "Can't init database in the directory \"" << parameters.database_directory
- << "\": " << r_database_directory.error());
- }
- parameters.database_directory = r_database_directory.move_as_ok();
- auto r_files_directory = prepare_dir(parameters.files_directory);
- if (r_files_directory.is_error()) {
- VLOG(td_init) << "Invalid files_directory";
- return Status::Error(400, PSLICE() << "Can't init files directory \"" << parameters.files_directory
- << "\": " << r_files_directory.error());
- }
- parameters.files_directory = r_files_directory.move_as_ok();
-
return Status::OK();
}
-Status Td::set_parameters(td_api::object_ptr<td_api::tdlibParameters> parameters) {
+Status Td::set_parameters(td_api::object_ptr<td_api::setTdlibParameters> parameters) {
VLOG(td_init) << "Begin to set TDLib parameters";
- if (parameters == nullptr) {
- VLOG(td_init) << "Empty parameters";
- return Status::Error(400, "Parameters aren't specified");
- }
-
- if (!clean_input_string(parameters->api_hash_) && !clean_input_string(parameters->system_language_code_) &&
- !clean_input_string(parameters->device_model_) && !clean_input_string(parameters->system_version_) &&
+ if (!clean_input_string(parameters->api_hash_) || !clean_input_string(parameters->system_language_code_) ||
+ !clean_input_string(parameters->device_model_) || !clean_input_string(parameters->system_version_) ||
!clean_input_string(parameters->application_version_)) {
VLOG(td_init) << "Wrong string encoding";
return Status::Error(400, "Strings must be encoded in UTF-8");
@@ -4793,52 +4104,39 @@ Status Td::set_parameters(td_api::object_ptr<td_api::tdlibParameters> parameters
parameters_.use_chat_info_db = parameters->use_chat_info_database_;
parameters_.use_message_db = parameters->use_message_database_;
- VLOG(td_init) << "Fix parameters...";
TRY_STATUS(fix_parameters(parameters_));
- VLOG(td_init) << "Check binlog encryption...";
- TRY_RESULT(encryption_info, TdDb::check_encryption(parameters_));
- encryption_info_ = std::move(encryption_info);
-
- VLOG(td_init) << "Init alarm multitimeout...";
- alarm_timeout_.set_callback(on_alarm_timeout_callback);
- alarm_timeout_.set_callback_data(static_cast<void *>(this));
-
- VLOG(td_init) << "Create Global";
- set_context(std::make_shared<Global>());
- inc_request_actor_refcnt(); // guard
- inc_actor_refcnt(); // guard
- VLOG(td_init) << "Create MtprotoHeader";
- MtprotoHeader::Options options;
- options.api_id = parameters->api_id_;
- options.system_language_code = trim(parameters->system_language_code_);
- options.device_model = trim(parameters->device_model_);
- options.system_version = trim(parameters->system_version_);
- options.application_version = trim(parameters->application_version_);
- if (options.system_language_code.empty()) {
+ VLOG(td_init) << "Create MtprotoHeader::Options";
+ options_.api_id = parameters->api_id_;
+ options_.system_language_code = trim(parameters->system_language_code_);
+ options_.device_model = trim(parameters->device_model_);
+ options_.system_version = trim(parameters->system_version_);
+ options_.application_version = trim(parameters->application_version_);
+ if (options_.system_language_code.empty()) {
return Status::Error(400, "System language code must be non-empty");
}
- if (options.device_model.empty()) {
+ if (options_.device_model.empty()) {
return Status::Error(400, "Device model must be non-empty");
}
- if (options.system_version.empty()) {
- return Status::Error(400, "System version must be non-empty");
+ if (options_.system_version.empty()) {
+ options_.system_version = get_operating_system_version().str();
+ VLOG(td_init) << "Set system version to " << options_.system_version;
}
- if (options.application_version.empty()) {
+ if (options_.application_version.empty()) {
return Status::Error(400, "Application version must be non-empty");
}
- if (options.api_id != 21724) {
- options.application_version += ", TDLib ";
- options.application_version += TDLIB_VERSION;
+ if (options_.api_id != 21724) {
+ options_.application_version += ", TDLib ";
+ auto version = OptionManager::get_option_synchronously("version");
+ CHECK(version->get_id() == td_api::optionValueString::ID);
+ options_.application_version += static_cast<const td_api::optionValueString *>(version.get())->value_;
}
- G()->set_mtproto_header(std::make_unique<MtprotoHeader>(options));
+ options_.language_pack = string();
+ options_.language_code = string();
+ options_.parameters = string();
+ options_.is_emulator = false;
+ options_.proxy = Proxy();
- state_ = State::Decrypt;
- VLOG(td_init) << "Send authorizationStateWaitEncryptionKey";
- send_closure(actor_id(this), &Td::send_update,
- td_api::make_object<td_api::updateAuthorizationState>(
- td_api::make_object<td_api::authorizationStateWaitEncryptionKey>(encryption_info_.is_encrypted)));
- VLOG(td_init) << "Finish set parameters";
return Status::OK();
}
@@ -4846,20 +4144,9 @@ void Td::on_request(uint64 id, const td_api::setTdlibParameters &request) {
send_error_raw(id, 400, "Unexpected setTdlibParameters");
}
-void Td::on_request(uint64 id, const td_api::checkDatabaseEncryptionKey &request) {
- send_error_raw(id, 400, "Unexpected checkDatabaseEncryptionKey");
-}
-
void Td::on_request(uint64 id, td_api::setDatabaseEncryptionKey &request) {
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
- if (result.is_error()) {
- promise.set_error(result.move_as_error());
- } else {
- promise.set_value(make_tl_object<td_api::ok>());
- }
- });
- G()->td_db()->get_binlog()->change_key(as_db_key(std::move(request.new_encryption_key_)), std::move(query_promise));
+ CREATE_OK_REQUEST_PROMISE();
+ G()->td_db()->get_binlog()->change_key(as_db_key(std::move(request.new_encryption_key_)), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getAuthorizationState &request) {
@@ -4869,19 +4156,37 @@ void Td::on_request(uint64 id, const td_api::getAuthorizationState &request) {
void Td::on_request(uint64 id, td_api::setAuthenticationPhoneNumber &request) {
CLEAN_INPUT_STRING(request.phone_number_);
send_closure(auth_manager_actor_, &AuthManager::set_phone_number, id, std::move(request.phone_number_),
- request.allow_flash_call_, request.is_current_phone_number_);
+ std::move(request.settings_));
+}
+
+void Td::on_request(uint64 id, td_api::setAuthenticationEmailAddress &request) {
+ CLEAN_INPUT_STRING(request.email_address_);
+ send_closure(auth_manager_actor_, &AuthManager::set_email_address, id, std::move(request.email_address_));
}
void Td::on_request(uint64 id, const td_api::resendAuthenticationCode &request) {
send_closure(auth_manager_actor_, &AuthManager::resend_authentication_code, id);
}
+void Td::on_request(uint64 id, td_api::checkAuthenticationEmailCode &request) {
+ send_closure(auth_manager_actor_, &AuthManager::check_email_code, id, EmailVerification(std::move(request.code_)));
+}
+
void Td::on_request(uint64 id, td_api::checkAuthenticationCode &request) {
CLEAN_INPUT_STRING(request.code_);
+ send_closure(auth_manager_actor_, &AuthManager::check_code, id, std::move(request.code_));
+}
+
+void Td::on_request(uint64 id, td_api::registerUser &request) {
CLEAN_INPUT_STRING(request.first_name_);
CLEAN_INPUT_STRING(request.last_name_);
- send_closure(auth_manager_actor_, &AuthManager::check_code, id, std::move(request.code_),
- std::move(request.first_name_), std::move(request.last_name_));
+ send_closure(auth_manager_actor_, &AuthManager::register_user, id, std::move(request.first_name_),
+ std::move(request.last_name_));
+}
+
+void Td::on_request(uint64 id, td_api::requestQrCodeAuthentication &request) {
+ send_closure(auth_manager_actor_, &AuthManager::request_qr_code_authentication, id,
+ UserId::get_user_ids(request.other_user_ids_));
}
void Td::on_request(uint64 id, td_api::checkAuthenticationPassword &request) {
@@ -4893,24 +4198,34 @@ void Td::on_request(uint64 id, const td_api::requestAuthenticationPasswordRecove
send_closure(auth_manager_actor_, &AuthManager::request_password_recovery, id);
}
+void Td::on_request(uint64 id, td_api::checkAuthenticationPasswordRecoveryCode &request) {
+ CLEAN_INPUT_STRING(request.recovery_code_);
+ send_closure(auth_manager_actor_, &AuthManager::check_password_recovery_code, id, std::move(request.recovery_code_));
+}
+
void Td::on_request(uint64 id, td_api::recoverAuthenticationPassword &request) {
CLEAN_INPUT_STRING(request.recovery_code_);
- send_closure(auth_manager_actor_, &AuthManager::recover_password, id, std::move(request.recovery_code_));
+ CLEAN_INPUT_STRING(request.new_password_);
+ CLEAN_INPUT_STRING(request.new_hint_);
+ send_closure(auth_manager_actor_, &AuthManager::recover_password, id, std::move(request.recovery_code_),
+ std::move(request.new_password_), std::move(request.new_hint_));
}
void Td::on_request(uint64 id, const td_api::logOut &request) {
// will call Td::destroy later
- send_closure(auth_manager_actor_, &AuthManager::logout, id);
+ send_closure(auth_manager_actor_, &AuthManager::log_out, id);
}
void Td::on_request(uint64 id, const td_api::close &request) {
- close();
+ // send response before actually closing
send_closure(actor_id(this), &Td::send_result, id, td_api::make_object<td_api::ok>());
+ send_closure(actor_id(this), &Td::close);
}
void Td::on_request(uint64 id, const td_api::destroy &request) {
- destroy();
+ // send response before actually destroying
send_closure(actor_id(this), &Td::send_result, id, td_api::make_object<td_api::ok>());
+ send_closure(actor_id(this), &Td::destroy);
}
void Td::on_request(uint64 id, td_api::checkAuthenticationBotToken &request) {
@@ -4918,332 +4233,528 @@ void Td::on_request(uint64 id, td_api::checkAuthenticationBotToken &request) {
send_closure(auth_manager_actor_, &AuthManager::check_bot_token, id, std::move(request.token_));
}
+void Td::on_request(uint64 id, td_api::confirmQrCodeAuthentication &request) {
+ CLEAN_INPUT_STRING(request.link_);
+ CREATE_REQUEST_PROMISE();
+ confirm_qr_code_authentication(this, request.link_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getCurrentState &request) {
+ vector<td_api::object_ptr<td_api::Update>> updates;
+
+ option_manager_->get_current_state(updates);
+
+ auto state = auth_manager_->get_current_authorization_state_object();
+ if (state != nullptr) {
+ updates.push_back(td_api::make_object<td_api::updateAuthorizationState>(std::move(state)));
+ }
+
+ updates.push_back(get_update_connection_state_object(connection_state_));
+
+ if (auth_manager_->is_authorized()) {
+ contacts_manager_->get_current_state(updates);
+
+ background_manager_->get_current_state(updates);
+
+ animations_manager_->get_current_state(updates);
+
+ attach_menu_manager_->get_current_state(updates);
+
+ stickers_manager_->get_current_state(updates);
+
+ notification_settings_manager_->get_current_state(updates);
+
+ messages_manager_->get_current_state(updates);
+
+ notification_manager_->get_current_state(updates);
+
+ config_manager_.get_actor_unsafe()->get_current_state(updates);
+
+ // TODO updateFileGenerationStart generation_id:int64 original_path:string destination_path:string conversion:string = Update;
+ // TODO updateCall call:call = Update;
+ // TODO updateGroupCall call:groupCall = Update;
+ }
+
+ auto update_terms_of_service = get_update_terms_of_service_object();
+ if (update_terms_of_service != nullptr) {
+ updates.push_back(std::move(update_terms_of_service));
+ }
+
+ // send response synchronously to prevent "Request aborted" or other changes of the current state
+ send_result(id, td_api::make_object<td_api::updates>(std::move(updates)));
+}
+
void Td::on_request(uint64 id, td_api::getPasswordState &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
send_closure(password_manager_, &PasswordManager::get_state, std::move(promise));
}
void Td::on_request(uint64 id, td_api::setPassword &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.old_password_);
CLEAN_INPUT_STRING(request.new_password_);
CLEAN_INPUT_STRING(request.new_hint_);
CLEAN_INPUT_STRING(request.new_recovery_email_address_);
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
send_closure(password_manager_, &PasswordManager::set_password, std::move(request.old_password_),
std::move(request.new_password_), std::move(request.new_hint_), request.set_recovery_email_address_,
std::move(request.new_recovery_email_address_), std::move(promise));
}
+void Td::on_request(uint64 id, td_api::setLoginEmailAddress &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.new_login_email_address_);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<SentEmailCode> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(result.ok().get_email_address_authentication_code_info_object());
+ }
+ });
+ send_closure(password_manager_, &PasswordManager::set_login_email_address,
+ std::move(request.new_login_email_address_), std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, const td_api::resendLoginEmailAddressCode &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<SentEmailCode> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(result.ok().get_email_address_authentication_code_info_object());
+ }
+ });
+ send_closure(password_manager_, &PasswordManager::resend_login_email_address_code, std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, td_api::checkLoginEmailAddressCode &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(password_manager_, &PasswordManager::check_login_email_address_code,
+ EmailVerification(std::move(request.code_)), std::move(promise));
+}
+
void Td::on_request(uint64 id, td_api::setRecoveryEmailAddress &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.password_);
CLEAN_INPUT_STRING(request.new_recovery_email_address_);
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
send_closure(password_manager_, &PasswordManager::set_recovery_email_address, std::move(request.password_),
std::move(request.new_recovery_email_address_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::getRecoveryEmailAddress &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.password_);
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
send_closure(password_manager_, &PasswordManager::get_recovery_email_address, std::move(request.password_),
std::move(promise));
}
+void Td::on_request(uint64 id, td_api::checkRecoveryEmailAddressCode &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.code_);
+ CREATE_REQUEST_PROMISE();
+ send_closure(password_manager_, &PasswordManager::check_recovery_email_address_code, std::move(request.code_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::resendRecoveryEmailAddressCode &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ send_closure(password_manager_, &PasswordManager::resend_recovery_email_address_code, std::move(promise));
+}
+
void Td::on_request(uint64 id, td_api::requestPasswordRecovery &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST_PROMISE(promise);
- send_closure(password_manager_, &PasswordManager::request_password_recovery, std::move(promise));
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<SentEmailCode> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(result.ok().get_email_address_authentication_code_info_object());
+ }
+ });
+ send_closure(password_manager_, &PasswordManager::request_password_recovery, std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, td_api::checkPasswordRecoveryCode &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.recovery_code_);
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(password_manager_, &PasswordManager::check_password_recovery_code, std::move(request.recovery_code_),
+ std::move(promise));
}
void Td::on_request(uint64 id, td_api::recoverPassword &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.recovery_code_);
- CREATE_REQUEST_PROMISE(promise);
+ CLEAN_INPUT_STRING(request.new_password_);
+ CLEAN_INPUT_STRING(request.new_hint_);
+ CREATE_REQUEST_PROMISE();
send_closure(password_manager_, &PasswordManager::recover_password, std::move(request.recovery_code_),
- std::move(promise));
+ std::move(request.new_password_), std::move(request.new_hint_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::resetPassword &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ send_closure(password_manager_, &PasswordManager::reset_password, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::cancelPasswordReset &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(password_manager_, &PasswordManager::cancel_password_reset, std::move(promise));
}
void Td::on_request(uint64 id, td_api::getTemporaryPasswordState &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
send_closure(password_manager_, &PasswordManager::get_temp_password_state, std::move(promise));
}
void Td::on_request(uint64 id, td_api::createTemporaryPassword &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.password_);
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
send_closure(password_manager_, &PasswordManager::create_temp_password, std::move(request.password_),
request.valid_for_, std::move(promise));
}
-void Td::on_request(uint64 id, td_api::processDcUpdate &request) {
- CREATE_REQUEST_PROMISE(promise);
- CLEAN_INPUT_STRING(request.dc_);
- CLEAN_INPUT_STRING(request.addr_);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
- if (result.is_error()) {
- promise.set_error(result.move_as_error());
- } else {
- promise.set_value(make_tl_object<td_api::ok>());
- }
- });
- auto dc_id_raw = to_integer<int32>(request.dc_);
- if (!DcId::is_valid(dc_id_raw)) {
- promise.set_error(Status::Error("Invalid dc id"));
- return;
- }
- send_closure(G()->connection_creator(), &ConnectionCreator::on_dc_update, DcId::internal(dc_id_raw), request.addr_,
- std::move(query_promise));
+void Td::on_request(uint64 id, td_api::processPushNotification &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.payload_);
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->notification_manager(), &NotificationManager::process_push_notification,
+ std::move(request.payload_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::registerDevice &request) {
- CHECK_AUTH();
CHECK_IS_USER();
if (request.device_token_ == nullptr) {
- return send_error_raw(id, 400, "Device token should not be empty");
+ return send_error_raw(id, 400, "Device token must be non-empty");
}
-
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
send_closure(device_token_manager_, &DeviceTokenManager::register_device, std::move(request.device_token_),
- std::move(request.other_user_ids_), std::move(promise));
+ UserId::get_user_ids(request.other_user_ids_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::getUserPrivacySettingRules &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
send_closure(privacy_manager_, &PrivacyManager::get_privacy, std::move(request.setting_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::setUserPrivacySettingRules &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_OK_REQUEST_PROMISE();
send_closure(privacy_manager_, &PrivacyManager::set_privacy, std::move(request.setting_), std::move(request.rules_),
std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getAccountTtl &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(GetAccountTtlRequest);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<int32> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(td_api::make_object<td_api::accountTtl>(result.ok()));
+ }
+ });
+ get_account_ttl(this, std::move(query_promise));
}
void Td::on_request(uint64 id, const td_api::setAccountTtl &request) {
- CHECK_AUTH();
CHECK_IS_USER();
if (request.ttl_ == nullptr) {
- return send_error_raw(id, 400, "New account TTL should not be empty");
+ return send_error_raw(id, 400, "New account TTL must be non-empty");
}
- CREATE_REQUEST(SetAccountTtlRequest, request.ttl_->days_);
+ CREATE_OK_REQUEST_PROMISE();
+ set_account_ttl(this, request.ttl_->days_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::deleteAccount &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.reason_);
- send_closure(auth_manager_actor_, &AuthManager::delete_account, id, request.reason_);
+ send_closure(auth_manager_actor_, &AuthManager::delete_account, id, request.reason_, request.password_);
}
void Td::on_request(uint64 id, td_api::changePhoneNumber &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.phone_number_);
- change_phone_number_manager_->change_phone_number(id, std::move(request.phone_number_), request.allow_flash_call_,
- request.is_current_phone_number_);
+ send_closure(change_phone_number_manager_, &PhoneNumberManager::set_phone_number, id,
+ std::move(request.phone_number_), std::move(request.settings_));
}
void Td::on_request(uint64 id, td_api::checkChangePhoneNumberCode &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.code_);
- change_phone_number_manager_->check_code(id, std::move(request.code_));
+ send_closure(change_phone_number_manager_, &PhoneNumberManager::check_code, id, std::move(request.code_));
}
void Td::on_request(uint64 id, td_api::resendChangePhoneNumberCode &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- change_phone_number_manager_->resend_authentication_code(id);
+ send_closure(change_phone_number_manager_, &PhoneNumberManager::resend_authentication_code, id);
}
void Td::on_request(uint64 id, const td_api::getActiveSessions &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(GetActiveSessionsRequest);
+ CREATE_REQUEST_PROMISE();
+ get_active_sessions(this, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::terminateSession &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(TerminateSessionRequest, request.session_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ terminate_session(this, request.session_id_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::terminateAllOtherSessions &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(TerminateAllOtherSessionsRequest);
+ CREATE_OK_REQUEST_PROMISE();
+ terminate_all_other_sessions(this, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleSessionCanAcceptCalls &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ toggle_session_can_accept_calls(this, request.session_id_, request.can_accept_calls_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleSessionCanAcceptSecretChats &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ toggle_session_can_accept_secret_chats(this, request.session_id_, request.can_accept_secret_chats_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setInactiveSessionTtl &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ set_inactive_session_ttl_days(this, request.inactive_session_ttl_days_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getConnectedWebsites &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(GetConnectedWebsitesRequest);
+ CREATE_REQUEST_PROMISE();
+ get_connected_websites(this, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::disconnectWebsite &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(DisconnectWebsiteRequest, request.website_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ disconnect_website(this, request.website_id_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::disconnectAllWebsites &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(DisconnectAllWebsitesRequest);
+ CREATE_OK_REQUEST_PROMISE();
+ disconnect_all_websites(this, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getMe &request) {
- CHECK_AUTH();
CREATE_NO_ARGS_REQUEST(GetMeRequest);
}
void Td::on_request(uint64 id, const td_api::getUser &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetUserRequest, request.user_id_);
}
void Td::on_request(uint64 id, const td_api::getUserFullInfo &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetUserFullInfoRequest, request.user_id_);
}
void Td::on_request(uint64 id, const td_api::getBasicGroup &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetGroupRequest, request.basic_group_id_);
}
void Td::on_request(uint64 id, const td_api::getBasicGroupFullInfo &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetGroupFullInfoRequest, request.basic_group_id_);
}
void Td::on_request(uint64 id, const td_api::getSupergroup &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetSupergroupRequest, request.supergroup_id_);
}
void Td::on_request(uint64 id, const td_api::getSupergroupFullInfo &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetSupergroupFullInfoRequest, request.supergroup_id_);
}
void Td::on_request(uint64 id, const td_api::getSecretChat &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetSecretChatRequest, request.secret_chat_id_);
}
void Td::on_request(uint64 id, const td_api::getChat &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetChatRequest, request.chat_id_);
}
void Td::on_request(uint64 id, const td_api::getMessage &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetMessageRequest, request.chat_id_, request.message_id_);
}
+void Td::on_request(uint64 id, const td_api::getMessageLocally &request) {
+ FullMessageId full_message_id(DialogId(request.chat_id_), MessageId(request.message_id_));
+ send_closure(actor_id(this), &Td::send_result, id,
+ messages_manager_->get_message_object(full_message_id, "getMessageLocally"));
+}
+
void Td::on_request(uint64 id, const td_api::getRepliedMessage &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetRepliedMessageRequest, request.chat_id_, request.message_id_);
}
void Td::on_request(uint64 id, const td_api::getChatPinnedMessage &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetChatPinnedMessageRequest, request.chat_id_);
}
+void Td::on_request(uint64 id, const td_api::getCallbackQueryMessage &request) {
+ CHECK_IS_BOT();
+ CREATE_REQUEST(GetCallbackQueryMessageRequest, request.chat_id_, request.message_id_, request.callback_query_id_);
+}
+
void Td::on_request(uint64 id, const td_api::getMessages &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetMessagesRequest, request.chat_id_, request.message_ids_);
}
-void Td::on_request(uint64 id, const td_api::getPublicMessageLink &request) {
- CHECK_AUTH();
- CREATE_REQUEST(GetPublicMessageLinkRequest, request.chat_id_, request.message_id_, request.for_album_);
+void Td::on_request(uint64 id, const td_api::getChatSponsoredMessages &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ sponsored_message_manager_->get_dialog_sponsored_messages(DialogId(request.chat_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getMessageThread &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST(GetMessageThreadRequest, request.chat_id_, request.message_id_);
+}
+
+void Td::on_request(uint64 id, const td_api::getMessageViewers &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->get_message_viewers({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getMessageLink &request) {
+ auto r_message_link =
+ messages_manager_->get_message_link({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ request.media_timestamp_, request.for_album_, request.in_message_thread_);
+ if (r_message_link.is_error()) {
+ send_closure(actor_id(this), &Td::send_error, id, r_message_link.move_as_error());
+ } else {
+ send_closure(actor_id(this), &Td::send_result, id,
+ td_api::make_object<td_api::messageLink>(r_message_link.ok().first, r_message_link.ok().second));
+ }
+}
+
+void Td::on_request(uint64 id, const td_api::getMessageEmbeddingCode &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST(GetMessageEmbeddingCodeRequest, request.chat_id_, request.message_id_, request.for_album_);
+}
+
+void Td::on_request(uint64 id, td_api::getMessageLinkInfo &request) {
+ CLEAN_INPUT_STRING(request.url_);
+ CREATE_REQUEST(GetMessageLinkInfoRequest, std::move(request.url_));
+}
+
+void Td::on_request(uint64 id, td_api::translateText &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.text_);
+ CLEAN_INPUT_STRING(request.from_language_code_);
+ CLEAN_INPUT_STRING(request.to_language_code_);
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->translate_text(request.text_, request.from_language_code_, request.to_language_code_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::recognizeSpeech &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->recognize_speech({DialogId(request.chat_id_), MessageId(request.message_id_)}, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::rateSpeechRecognition &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->rate_speech_recognition({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ request.is_good_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getFile &request) {
- CHECK_AUTH();
- send_closure(actor_id(this), &Td::send_result, id, file_manager_->get_file_object(FileId(request.file_id_, 0)));
+ auto file_object = file_manager_->get_file_object(FileId(request.file_id_, 0));
+ if (file_object->id_ == 0) {
+ file_object = nullptr;
+ } else {
+ file_object->id_ = request.file_id_;
+ }
+ send_closure(actor_id(this), &Td::send_result, id, std::move(file_object));
}
void Td::on_request(uint64 id, td_api::getRemoteFile &request) {
- CHECK_AUTH();
CLEAN_INPUT_STRING(request.remote_file_id_);
- auto r_file_id = file_manager_->from_persistent_id(
- request.remote_file_id_, request.file_type_ == nullptr ? FileType::Temp : from_td_api(*request.file_type_));
+ auto file_type = request.file_type_ == nullptr ? FileType::Temp : get_file_type(*request.file_type_);
+ auto r_file_id = file_manager_->from_persistent_id(request.remote_file_id_, file_type);
if (r_file_id.is_error()) {
- auto error = r_file_id.move_as_error();
- send_closure(actor_id(this), &Td::send_error, id, std::move(error));
+ send_closure(actor_id(this), &Td::send_error, id, r_file_id.move_as_error());
} else {
send_closure(actor_id(this), &Td::send_result, id, file_manager_->get_file_object(r_file_id.ok()));
}
}
void Td::on_request(uint64 id, td_api::getStorageStatistics &request) {
- CHECK_AUTH();
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<FileStats> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(result.ok().as_td_api());
+ promise.set_value(result.ok().get_storage_statistics_object());
}
});
- send_closure(storage_manager_, &StorageManager::get_storage_stats, request.chat_limit_, std::move(query_promise));
+ send_closure(storage_manager_, &StorageManager::get_storage_stats, false /*need_all_files*/, request.chat_limit_,
+ std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::getStorageStatisticsFast &request) {
- CHECK_AUTH();
- CREATE_REQUEST_PROMISE(promise);
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<FileStatsFast> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(result.ok().as_td_api());
+ promise.set_value(result.ok().get_storage_statistics_fast_object());
}
});
send_closure(storage_manager_, &StorageManager::get_storage_stats_fast, std::move(query_promise));
}
+void Td::on_request(uint64 id, td_api::getDatabaseStatistics &request) {
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<DatabaseStats> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(result.ok().get_database_statistics_object());
+ }
+ });
+ send_closure(storage_manager_, &StorageManager::get_database_stats, std::move(query_promise));
+}
void Td::on_request(uint64 id, td_api::optimizeStorage &request) {
- CHECK_AUTH();
std::vector<FileType> file_types;
for (auto &file_type : request.file_types_) {
if (file_type == nullptr) {
- return send_error_raw(id, 400, "File type should not be empty");
+ return send_error_raw(id, 400, "File type must be non-empty");
}
- file_types.push_back(from_td_api(*file_type));
+ file_types.push_back(get_file_type(*file_type));
}
std::vector<DialogId> owner_dialog_ids;
for (auto chat_id : request.chat_ids_) {
DialogId dialog_id(chat_id);
if (!dialog_id.is_valid() && dialog_id != DialogId()) {
- return send_error_raw(id, 400, "Wrong chat id");
+ return send_error_raw(id, 400, "Wrong chat identifier");
}
owner_dialog_ids.push_back(dialog_id);
}
@@ -5251,7 +4762,7 @@ void Td::on_request(uint64 id, td_api::optimizeStorage &request) {
for (auto chat_id : request.exclude_chat_ids_) {
DialogId dialog_id(chat_id);
if (!dialog_id.is_valid() && dialog_id != DialogId()) {
- return send_error_raw(id, 400, "Wrong chat id");
+ return send_error_raw(id, 400, "Wrong chat identifier");
}
exclude_owner_dialog_ids.push_back(dialog_id);
}
@@ -5259,24 +4770,28 @@ void Td::on_request(uint64 id, td_api::optimizeStorage &request) {
std::move(file_types), std::move(owner_dialog_ids), std::move(exclude_owner_dialog_ids),
request.chat_limit_);
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<FileStats> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(result.ok().as_td_api());
+ promise.set_value(result.ok().get_storage_statistics_object());
}
});
- send_closure(storage_manager_, &StorageManager::run_gc, std::move(parameters), std::move(query_promise));
+ send_closure(storage_manager_, &StorageManager::run_gc, std::move(parameters),
+ request.return_deleted_file_statistics_, std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::getNetworkStatistics &request) {
- CREATE_REQUEST_PROMISE(promise);
+ if (!request.only_current_ && G()->get_option_boolean("disable_persistent_network_statistics")) {
+ return send_error_raw(id, 400, "Persistent network statistics is disabled");
+ }
+ CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<NetworkStats> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(result.ok().as_td_api());
+ promise.set_value(result.ok().get_network_statistics_object());
}
});
send_closure(net_stats_manager_, &NetStatsManager::get_network_stats, request.only_current_,
@@ -5284,15 +4799,14 @@ void Td::on_request(uint64 id, td_api::getNetworkStatistics &request) {
}
void Td::on_request(uint64 id, td_api::resetNetworkStatistics &request) {
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_OK_REQUEST_PROMISE();
send_closure(net_stats_manager_, &NetStatsManager::reset_network_stats);
- promise.set_value(make_tl_object<td_api::ok>());
+ promise.set_value(Unit());
}
void Td::on_request(uint64 id, td_api::addNetworkStatistics &request) {
- CREATE_REQUEST_PROMISE(promise);
if (request.entry_ == nullptr) {
- return send_error_raw(id, 400, "Network statistics entry should not be empty");
+ return send_error_raw(id, 400, "Network statistics entry must be non-empty");
}
NetworkStatsEntry entry;
@@ -5301,9 +4815,9 @@ void Td::on_request(uint64 id, td_api::addNetworkStatistics &request) {
auto file_entry = move_tl_object_as<td_api::networkStatisticsEntryFile>(request.entry_);
entry.is_call = false;
if (file_entry->file_type_ != nullptr) {
- entry.file_type = from_td_api(*file_entry->file_type_);
+ entry.file_type = get_file_type(*file_entry->file_type_);
}
- entry.net_type = from_td_api(file_entry->network_type_);
+ entry.net_type = get_net_type(file_entry->network_type_);
entry.rx = file_entry->received_bytes_;
entry.tx = file_entry->sent_bytes_;
break;
@@ -5311,7 +4825,7 @@ void Td::on_request(uint64 id, td_api::addNetworkStatistics &request) {
case td_api::networkStatisticsEntryCall::ID: {
auto call_entry = move_tl_object_as<td_api::networkStatisticsEntryCall>(request.entry_);
entry.is_call = true;
- entry.net_type = from_td_api(call_entry->network_type_);
+ entry.net_type = get_net_type(call_entry->network_type_);
entry.rx = call_entry->received_bytes_;
entry.tx = call_entry->sent_bytes_;
entry.duration = call_entry->duration_;
@@ -5324,10 +4838,10 @@ void Td::on_request(uint64 id, td_api::addNetworkStatistics &request) {
if (entry.net_type == NetType::None) {
return send_error_raw(id, 400, "Network statistics entry can't be increased for NetworkTypeNone");
}
- if (entry.rx > (1ll << 40) || entry.rx < 0) {
+ if (entry.rx > (static_cast<int64>(1) << 40) || entry.rx < 0) {
return send_error_raw(id, 400, "Wrong received bytes value");
}
- if (entry.tx > (1ll << 40) || entry.tx < 0) {
+ if (entry.tx > (static_cast<int64>(1) << 40) || entry.tx < 0) {
return send_error_raw(id, 400, "Wrong sent bytes value");
}
if (entry.count > (1 << 30) || entry.count < 0) {
@@ -5341,256 +4855,528 @@ void Td::on_request(uint64 id, td_api::addNetworkStatistics &request) {
send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
}
-void Td::on_request(uint64 id, td_api::setNetworkType &request) {
- CREATE_REQUEST_PROMISE(promise);
- send_closure(state_manager_, &StateManager::on_network, from_td_api(request.type_));
- promise.set_value(make_tl_object<td_api::ok>());
+void Td::on_request(uint64 id, const td_api::setNetworkType &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(state_manager_, &StateManager::on_network, get_net_type(request.type_));
+ promise.set_value(Unit());
}
-void Td::on_request(uint64 id, td_api::getTopChats &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::getAutoDownloadSettingsPresets &request) {
CHECK_IS_USER();
- CREATE_REQUEST_PROMISE(promise);
- if (request.category_ == nullptr) {
- promise.set_error(Status::Error(400, "Top chat category should not be empty"));
- return;
- }
- if (request.limit_ <= 0) {
- promise.set_error(Status::Error(400, "Limit must be positive"));
- return;
+ CREATE_REQUEST_PROMISE();
+ get_auto_download_settings_presets(this, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setAutoDownloadSettings &request) {
+ CHECK_IS_USER();
+ if (request.settings_ == nullptr) {
+ return send_error_raw(id, 400, "New settings must be non-empty");
}
+ CREATE_OK_REQUEST_PROMISE();
+ set_auto_download_settings(this, get_net_type(request.type_), get_auto_download_settings(request.settings_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getTopChats &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<vector<DialogId>> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(MessagesManager::get_chats_object(result.ok()));
+ promise.set_value(MessagesManager::get_chats_object(-1, result.ok()));
}
});
- send_closure(top_dialog_manager_, &TopDialogManager::get_top_dialogs,
- top_dialog_category_from_td_api(*request.category_), request.limit_, std::move(query_promise));
+ send_closure(top_dialog_manager_actor_, &TopDialogManager::get_top_dialogs,
+ get_top_dialog_category(request.category_), request.limit_, std::move(query_promise));
}
void Td::on_request(uint64 id, const td_api::removeTopChat &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- if (request.category_ == nullptr) {
- return send_error_raw(id, 400, "Top chat category should not be empty");
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(top_dialog_manager_actor_, &TopDialogManager::remove_dialog, get_top_dialog_category(request.category_),
+ DialogId(request.chat_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::loadChats &request) {
+ CHECK_IS_USER();
+
+ DialogListId dialog_list_id(request.chat_list_);
+ auto r_offset = messages_manager_->get_dialog_list_last_date(dialog_list_id);
+ if (r_offset.is_error()) {
+ return send_error_raw(id, 400, r_offset.error().message());
+ }
+ auto offset = r_offset.move_as_ok();
+ if (offset == MAX_DIALOG_DATE) {
+ return send_closure(actor_id(this), &Td::send_result, id, nullptr);
}
- send_closure(top_dialog_manager_, &TopDialogManager::remove_dialog,
- top_dialog_category_from_td_api(*request.category_), DialogId(request.chat_id_),
- messages_manager_->get_input_peer(DialogId(request.chat_id_), AccessRights::Read));
- send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
+ CREATE_REQUEST(LoadChatsRequest, dialog_list_id, offset, request.limit_);
}
void Td::on_request(uint64 id, const td_api::getChats &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(GetChatsRequest, request.offset_order_, request.offset_chat_id_, request.limit_);
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->get_dialogs_from_list(DialogListId(request.chat_list_), request.limit_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::searchPublicChat &request) {
- CHECK_AUTH();
CLEAN_INPUT_STRING(request.username_);
CREATE_REQUEST(SearchPublicChatRequest, request.username_);
}
void Td::on_request(uint64 id, td_api::searchPublicChats &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.query_);
CREATE_REQUEST(SearchPublicChatsRequest, request.query_);
}
void Td::on_request(uint64 id, td_api::searchChats &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.query_);
CREATE_REQUEST(SearchChatsRequest, request.query_, request.limit_);
}
void Td::on_request(uint64 id, td_api::searchChatsOnServer &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.query_);
CREATE_REQUEST(SearchChatsOnServerRequest, request.query_, request.limit_);
}
+void Td::on_request(uint64 id, const td_api::searchChatsNearby &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->search_dialogs_nearby(Location(request.location_), std::move(promise));
+}
+
void Td::on_request(uint64 id, const td_api::getGroupsInCommon &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(GetGroupsInCommonRequest, request.user_id_, request.offset_chat_id_, request.limit_);
}
void Td::on_request(uint64 id, td_api::checkChatUsername &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.username_);
- CREATE_REQUEST(CheckChatUsernameRequest, request.chat_id_, std::move(request.username_));
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda(
+ [promise = std::move(promise)](Result<ContactsManager::CheckDialogUsernameResult> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(ContactsManager::get_check_chat_username_result_object(result.ok()));
+ }
+ });
+ contacts_manager_->check_dialog_username(DialogId(request.chat_id_), request.username_, std::move(query_promise));
}
void Td::on_request(uint64 id, const td_api::getCreatedPublicChats &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(GetCreatedPublicChatsRequest);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_created_public_dialogs(get_public_dialog_type(request.type_), std::move(promise), false);
+}
+
+void Td::on_request(uint64 id, const td_api::checkCreatedPublicChatsLimit &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->check_created_public_dialogs_limit(get_public_dialog_type(request.type_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getSuitableDiscussionChats &request) {
+ CHECK_IS_USER();
+ CREATE_NO_ARGS_REQUEST(GetSuitableDiscussionChatsRequest);
+}
+
+void Td::on_request(uint64 id, const td_api::getInactiveSupergroupChats &request) {
+ CHECK_IS_USER();
+ CREATE_NO_ARGS_REQUEST(GetInactiveSupergroupChatsRequest);
}
void Td::on_request(uint64 id, const td_api::addRecentlyFoundChat &request) {
- CHECK_AUTH();
CHECK_IS_USER();
answer_ok_query(id, messages_manager_->add_recently_found_dialog(DialogId(request.chat_id_)));
}
void Td::on_request(uint64 id, const td_api::removeRecentlyFoundChat &request) {
- CHECK_AUTH();
CHECK_IS_USER();
answer_ok_query(id, messages_manager_->remove_recently_found_dialog(DialogId(request.chat_id_)));
}
void Td::on_request(uint64 id, const td_api::clearRecentlyFoundChats &request) {
- CHECK_AUTH();
CHECK_IS_USER();
messages_manager_->clear_recently_found_dialogs();
send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
}
+void Td::on_request(uint64 id, const td_api::getRecentlyOpenedChats &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST(GetRecentlyOpenedChatsRequest, request.limit_);
+}
+
void Td::on_request(uint64 id, const td_api::openChat &request) {
- CHECK_AUTH();
CHECK_IS_USER();
answer_ok_query(id, messages_manager_->open_dialog(DialogId(request.chat_id_)));
}
void Td::on_request(uint64 id, const td_api::closeChat &request) {
- CHECK_AUTH();
CHECK_IS_USER();
answer_ok_query(id, messages_manager_->close_dialog(DialogId(request.chat_id_)));
}
void Td::on_request(uint64 id, const td_api::viewMessages &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- answer_ok_query(
- id, messages_manager_->view_messages(
- DialogId(request.chat_id_), MessagesManager::get_message_ids(request.message_ids_), request.force_read_));
+ answer_ok_query(id, messages_manager_->view_messages(
+ DialogId(request.chat_id_), MessageId(request.message_thread_id_),
+ MessagesManager::get_message_ids(request.message_ids_), request.force_read_));
}
void Td::on_request(uint64 id, const td_api::openMessageContent &request) {
- CHECK_AUTH();
CHECK_IS_USER();
answer_ok_query(
id, messages_manager_->open_message_content({DialogId(request.chat_id_), MessageId(request.message_id_)}));
}
+void Td::on_request(uint64 id, const td_api::clickAnimatedEmojiMessage &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->click_animated_emoji_message({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getInternalLinkType &request) {
+ auto type = LinkManager::parse_internal_link(request.link_);
+ send_closure(actor_id(this), &Td::send_result, id, type == nullptr ? nullptr : type->get_internal_link_type_object());
+}
+
+void Td::on_request(uint64 id, td_api::getExternalLinkInfo &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.link_);
+ CREATE_REQUEST_PROMISE();
+ link_manager_->get_external_link_info(std::move(request.link_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getExternalLink &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.link_);
+ CREATE_REQUEST_PROMISE();
+ link_manager_->get_link_login_url(request.link_, request.allow_write_access_, std::move(promise));
+}
+
void Td::on_request(uint64 id, const td_api::getChatHistory &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(GetChatHistoryRequest, request.chat_id_, request.from_message_id_, request.offset_, request.limit_,
request.only_local_);
}
void Td::on_request(uint64 id, const td_api::deleteChatHistory &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(DeleteChatHistoryRequest, request.chat_id_, request.remove_from_chat_list_);
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->delete_dialog_history(DialogId(request.chat_id_), request.remove_from_chat_list_, request.revoke_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::deleteChat &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ DialogId dialog_id(request.chat_id_);
+ auto query_promise = [actor_id = messages_manager_actor_.get(), dialog_id,
+ promise = std::move(promise)](Result<Unit> &&result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ send_closure(actor_id, &MessagesManager::on_dialog_deleted, dialog_id, std::move(promise));
+ }
+ };
+ contacts_manager_->delete_dialog(dialog_id, std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getMessageThreadHistory &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST(GetMessageThreadHistoryRequest, request.chat_id_, request.message_id_, request.from_message_id_,
+ request.offset_, request.limit_);
+}
+
+void Td::on_request(uint64 id, td_api::getChatMessageCalendar &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST(GetChatMessageCalendarRequest, request.chat_id_, request.from_message_id_, std::move(request.filter_));
}
void Td::on_request(uint64 id, td_api::searchChatMessages &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.query_);
- CREATE_REQUEST(SearchChatMessagesRequest, request.chat_id_, std::move(request.query_), request.sender_user_id_,
- request.from_message_id_, request.offset_, request.limit_, std::move(request.filter_));
+ CREATE_REQUEST(SearchChatMessagesRequest, request.chat_id_, std::move(request.query_), std::move(request.sender_id_),
+ request.from_message_id_, request.offset_, request.limit_, std::move(request.filter_),
+ request.message_thread_id_);
}
void Td::on_request(uint64 id, td_api::searchSecretMessages &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.query_);
- CREATE_REQUEST(OfflineSearchMessagesRequest, request.chat_id_, std::move(request.query_), request.from_search_id_,
+ CLEAN_INPUT_STRING(request.offset_);
+ CREATE_REQUEST(SearchSecretMessagesRequest, request.chat_id_, std::move(request.query_), std::move(request.offset_),
request.limit_, std::move(request.filter_));
}
void Td::on_request(uint64 id, td_api::searchMessages &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.query_);
- CREATE_REQUEST(SearchMessagesRequest, std::move(request.query_), request.offset_date_, request.offset_chat_id_,
- request.offset_message_id_, request.limit_);
+ DialogListId dialog_list_id(request.chat_list_);
+ if (!dialog_list_id.is_folder()) {
+ return send_error_raw(id, 400, "Wrong chat list specified");
+ }
+ CREATE_REQUEST(SearchMessagesRequest, dialog_list_id.get_folder_id(), request.chat_list_ == nullptr,
+ std::move(request.query_), request.offset_date_, request.offset_chat_id_, request.offset_message_id_,
+ request.limit_, std::move(request.filter_), request.min_date_, request.max_date_);
}
-void Td::on_request(uint64 id, td_api::searchCallMessages &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::searchCallMessages &request) {
CHECK_IS_USER();
CREATE_REQUEST(SearchCallMessagesRequest, request.from_message_id_, request.limit_, request.only_missed_);
}
+void Td::on_request(uint64 id, td_api::searchOutgoingDocumentMessages &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.query_);
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->search_outgoing_document_messages(request.query_, request.limit_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::deleteAllCallMessages &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->delete_all_call_messages(request.revoke_, std::move(promise));
+}
+
void Td::on_request(uint64 id, const td_api::searchChatRecentLocationMessages &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(SearchChatRecentLocationMessagesRequest, request.chat_id_, request.limit_);
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->search_dialog_recent_location_messages(DialogId(request.chat_id_), request.limit_,
+ std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getActiveLiveLocationMessages &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_NO_ARGS_REQUEST(GetActiveLiveLocationMessagesRequest);
}
void Td::on_request(uint64 id, const td_api::getChatMessageByDate &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetChatMessageByDateRequest, request.chat_id_, request.date_);
}
+void Td::on_request(uint64 id, const td_api::getChatSparseMessagePositions &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->get_dialog_sparse_message_positions(
+ DialogId(request.chat_id_), get_message_search_filter(request.filter_), MessageId(request.from_message_id_),
+ request.limit_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getChatMessageCount &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<int32> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(make_tl_object<td_api::count>(result.move_as_ok()));
+ }
+ });
+ messages_manager_->get_dialog_message_count(DialogId(request.chat_id_), get_message_search_filter(request.filter_),
+ request.return_local_, std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getChatMessagePosition &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<int32> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(make_tl_object<td_api::count>(result.move_as_ok()));
+ }
+ });
+ messages_manager_->get_dialog_message_position({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ get_message_search_filter(request.filter_),
+ MessageId(request.message_thread_id_), std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getChatScheduledMessages &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST(GetChatScheduledMessagesRequest, request.chat_id_);
+}
+
+void Td::on_request(uint64 id, const td_api::getEmojiReaction &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->get_emoji_reaction(request.emoji_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getCustomEmojiReactionAnimations &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->get_custom_emoji_reaction_generic_animations(false, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getMessageAvailableReactions &request) {
+ CHECK_IS_USER();
+ auto r_reactions = messages_manager_->get_message_available_reactions(
+ {DialogId(request.chat_id_), MessageId(request.message_id_)}, request.row_size_);
+ if (r_reactions.is_error()) {
+ send_closure(actor_id(this), &Td::send_error, id, r_reactions.move_as_error());
+ } else {
+ send_closure(actor_id(this), &Td::send_result, id, r_reactions.move_as_ok());
+ }
+}
+
+void Td::on_request(uint64 id, const td_api::clearRecentReactions &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ stickers_manager_->clear_recent_reactions(std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::addMessageReaction &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->add_message_reaction({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ get_message_reaction_string(request.reaction_type_), request.is_big_,
+ request.update_recent_reactions_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::removeMessageReaction &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->remove_message_reaction({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ get_message_reaction_string(request.reaction_type_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getMessageAddedReactions &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.offset_);
+ CREATE_REQUEST_PROMISE();
+ get_message_added_reactions(this, {DialogId(request.chat_id_), MessageId(request.message_id_)},
+ get_message_reaction_string(request.reaction_type_), std::move(request.offset_),
+ request.limit_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setDefaultReactionType &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ set_default_reaction(this, get_message_reaction_string(request.reaction_type_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getMessagePublicForwards &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.offset_);
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->get_message_public_forwards({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ std::move(request.offset_), request.limit_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::removeNotification &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(notification_manager_actor_, &NotificationManager::remove_notification,
+ NotificationGroupId(request.notification_group_id_), NotificationId(request.notification_id_), false,
+ true, std::move(promise), "td_api::removeNotification");
+}
+
+void Td::on_request(uint64 id, const td_api::removeNotificationGroup &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(notification_manager_actor_, &NotificationManager::remove_notification_group,
+ NotificationGroupId(request.notification_group_id_), NotificationId(request.max_notification_id_),
+ MessageId(), -1, true, std::move(promise));
+}
+
void Td::on_request(uint64 id, const td_api::deleteMessages &request) {
- CHECK_AUTH();
- CREATE_REQUEST(DeleteMessagesRequest, request.chat_id_, request.message_ids_, request.revoke_);
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->delete_messages(DialogId(request.chat_id_), MessagesManager::get_message_ids(request.message_ids_),
+ request.revoke_, std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::deleteChatMessagesFromUser &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::deleteChatMessagesBySender &request) {
CHECK_IS_USER();
- CREATE_REQUEST(DeleteChatMessagesFromUserRequest, request.chat_id_, request.user_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, sender_dialog_id, get_message_sender_dialog_id(this, request.sender_id_, false, false));
+ messages_manager_->delete_dialog_messages_by_sender(DialogId(request.chat_id_), sender_dialog_id, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::deleteChatMessagesByDate &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->delete_dialog_messages_by_date(DialogId(request.chat_id_), request.min_date_, request.max_date_,
+ request.revoke_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::readAllChatMentions &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(ReadAllChatMentionsRequest, request.chat_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->read_all_dialog_mentions(DialogId(request.chat_id_), MessageId(), std::move(promise));
}
-void Td::on_request(uint64 id, td_api::sendMessage &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::readAllMessageThreadMentions &request) {
+ CHECK_IS_USER();
+ if (request.message_thread_id_ == 0) {
+ return send_error_raw(id, 400, "Invalid message thread identifier specified");
+ }
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->read_all_dialog_mentions(DialogId(request.chat_id_), MessageId(request.message_thread_id_),
+ std::move(promise));
+}
- DialogId dialog_id(request.chat_id_);
- auto r_new_message_id = messages_manager_->send_message(
- dialog_id, MessageId(request.reply_to_message_id_), request.disable_notification_, request.from_background_,
- std::move(request.reply_markup_), std::move(request.input_message_content_));
- if (r_new_message_id.is_error()) {
- return send_closure(actor_id(this), &Td::send_error, id, r_new_message_id.move_as_error());
+void Td::on_request(uint64 id, const td_api::readAllChatReactions &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->read_all_dialog_reactions(DialogId(request.chat_id_), MessageId(), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::readAllMessageThreadReactions &request) {
+ CHECK_IS_USER();
+ if (request.message_thread_id_ == 0) {
+ return send_error_raw(id, 400, "Invalid message thread identifier specified");
}
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->read_all_dialog_reactions(DialogId(request.chat_id_), MessageId(request.message_thread_id_),
+ std::move(promise));
+}
- CHECK(r_new_message_id.ok().is_valid());
- send_closure(actor_id(this), &Td::send_result, id,
- messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()}));
+void Td::on_request(uint64 id, const td_api::getChatAvailableMessageSenders &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->get_dialog_send_message_as_dialog_ids(DialogId(request.chat_id_), std::move(promise));
}
-void Td::on_request(uint64 id, td_api::sendMessageAlbum &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::setChatMessageSender &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, message_sender_dialog_id,
+ get_message_sender_dialog_id(this, request.message_sender_id_, true, false));
+ messages_manager_->set_dialog_default_send_message_as_dialog_id(DialogId(request.chat_id_), message_sender_dialog_id,
+ std::move(promise));
+}
- DialogId dialog_id(request.chat_id_);
- auto r_message_ids = messages_manager_->send_message_group(dialog_id, MessageId(request.reply_to_message_id_),
- request.disable_notification_, request.from_background_,
- std::move(request.input_message_contents_));
- if (r_message_ids.is_error()) {
- return send_closure(actor_id(this), &Td::send_error, id, r_message_ids.move_as_error());
+void Td::on_request(uint64 id, td_api::sendMessage &request) {
+ auto r_sent_message = messages_manager_->send_message(
+ DialogId(request.chat_id_), MessageId(request.message_thread_id_), MessageId(request.reply_to_message_id_),
+ std::move(request.options_), std::move(request.reply_markup_), std::move(request.input_message_content_));
+ if (r_sent_message.is_error()) {
+ send_closure(actor_id(this), &Td::send_error, id, r_sent_message.move_as_error());
+ } else {
+ send_closure(actor_id(this), &Td::send_result, id, r_sent_message.move_as_ok());
}
+}
- send_closure(actor_id(this), &Td::send_result, id,
- messages_manager_->get_messages_object(-1, dialog_id, r_message_ids.ok()));
+void Td::on_request(uint64 id, td_api::sendMessageAlbum &request) {
+ auto r_messages = messages_manager_->send_message_group(
+ DialogId(request.chat_id_), MessageId(request.message_thread_id_), MessageId(request.reply_to_message_id_),
+ std::move(request.options_), std::move(request.input_message_contents_), request.only_preview_);
+ if (r_messages.is_error()) {
+ send_closure(actor_id(this), &Td::send_error, id, r_messages.move_as_error());
+ } else {
+ send_closure(actor_id(this), &Td::send_result, id, r_messages.move_as_ok());
+ }
}
void Td::on_request(uint64 id, td_api::sendBotStartMessage &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.parameter_);
@@ -5601,1092 +5387,2122 @@ void Td::on_request(uint64 id, td_api::sendBotStartMessage &request) {
return send_closure(actor_id(this), &Td::send_error, id, r_new_message_id.move_as_error());
}
- CHECK(r_new_message_id.ok().is_valid());
+ CHECK(r_new_message_id.ok().is_valid() || r_new_message_id.ok().is_valid_scheduled());
send_closure(actor_id(this), &Td::send_result, id,
- messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()}));
+ messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()}, "sendBotStartMessage"));
}
void Td::on_request(uint64 id, td_api::sendInlineQueryResultMessage &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.result_id_);
DialogId dialog_id(request.chat_id_);
auto r_new_message_id = messages_manager_->send_inline_query_result_message(
- dialog_id, MessageId(request.reply_to_message_id_), request.disable_notification_, request.from_background_,
- request.query_id_, request.result_id_);
+ dialog_id, MessageId(request.message_thread_id_), MessageId(request.reply_to_message_id_),
+ std::move(request.options_), request.query_id_, request.result_id_, request.hide_via_bot_);
if (r_new_message_id.is_error()) {
return send_closure(actor_id(this), &Td::send_error, id, r_new_message_id.move_as_error());
}
- CHECK(r_new_message_id.ok().is_valid());
- send_closure(actor_id(this), &Td::send_result, id,
- messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()}));
+ CHECK(r_new_message_id.ok().is_valid() || r_new_message_id.ok().is_valid_scheduled());
+ send_closure(
+ actor_id(this), &Td::send_result, id,
+ messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()}, "sendInlineQueryResultMessage"));
}
-void Td::on_request(uint64 id, const td_api::sendChatSetTtlMessage &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::addLocalMessage &request) {
+ CHECK_IS_USER();
DialogId dialog_id(request.chat_id_);
- auto r_new_message_id = messages_manager_->send_dialog_set_ttl_message(dialog_id, request.ttl_);
+ auto r_new_message_id = messages_manager_->add_local_message(
+ dialog_id, std::move(request.sender_id_), MessageId(request.reply_to_message_id_), request.disable_notification_,
+ std::move(request.input_message_content_));
if (r_new_message_id.is_error()) {
return send_closure(actor_id(this), &Td::send_error, id, r_new_message_id.move_as_error());
}
CHECK(r_new_message_id.ok().is_valid());
send_closure(actor_id(this), &Td::send_result, id,
- messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()}));
+ messages_manager_->get_message_object({dialog_id, r_new_message_id.ok()}, "addLocalMessage"));
}
void Td::on_request(uint64 id, td_api::editMessageText &request) {
- CHECK_AUTH();
CREATE_REQUEST(EditMessageTextRequest, request.chat_id_, request.message_id_, std::move(request.reply_markup_),
std::move(request.input_message_content_));
}
void Td::on_request(uint64 id, td_api::editMessageLiveLocation &request) {
- CHECK_AUTH();
CREATE_REQUEST(EditMessageLiveLocationRequest, request.chat_id_, request.message_id_,
- std::move(request.reply_markup_), std::move(request.location_));
+ std::move(request.reply_markup_), std::move(request.location_), request.heading_,
+ request.proximity_alert_radius_);
+}
+
+void Td::on_request(uint64 id, td_api::editMessageMedia &request) {
+ CREATE_REQUEST(EditMessageMediaRequest, request.chat_id_, request.message_id_, std::move(request.reply_markup_),
+ std::move(request.input_message_content_));
}
void Td::on_request(uint64 id, td_api::editMessageCaption &request) {
- CHECK_AUTH();
CREATE_REQUEST(EditMessageCaptionRequest, request.chat_id_, request.message_id_, std::move(request.reply_markup_),
std::move(request.caption_));
}
void Td::on_request(uint64 id, td_api::editMessageReplyMarkup &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CREATE_REQUEST(EditMessageReplyMarkupRequest, request.chat_id_, request.message_id_,
std::move(request.reply_markup_));
}
void Td::on_request(uint64 id, td_api::editInlineMessageText &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.inline_message_id_);
- CREATE_REQUEST(EditInlineMessageTextRequest, std::move(request.inline_message_id_), std::move(request.reply_markup_),
- std::move(request.input_message_content_));
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->edit_inline_message_text(request.inline_message_id_, std::move(request.reply_markup_),
+ std::move(request.input_message_content_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::editInlineMessageLiveLocation &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.inline_message_id_);
- CREATE_REQUEST(EditInlineMessageLiveLocationRequest, std::move(request.inline_message_id_),
- std::move(request.reply_markup_), std::move(request.location_));
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->edit_inline_message_live_location(request.inline_message_id_, std::move(request.reply_markup_),
+ std::move(request.location_), request.heading_,
+ request.proximity_alert_radius_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::editInlineMessageMedia &request) {
+ CHECK_IS_BOT();
+ CLEAN_INPUT_STRING(request.inline_message_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->edit_inline_message_media(request.inline_message_id_, std::move(request.reply_markup_),
+ std::move(request.input_message_content_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::editInlineMessageCaption &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.inline_message_id_);
- CREATE_REQUEST(EditInlineMessageCaptionRequest, std::move(request.inline_message_id_),
- std::move(request.reply_markup_), std::move(request.caption_));
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->edit_inline_message_caption(request.inline_message_id_, std::move(request.reply_markup_),
+ std::move(request.caption_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::editInlineMessageReplyMarkup &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.inline_message_id_);
- CREATE_REQUEST(EditInlineMessageReplyMarkupRequest, std::move(request.inline_message_id_),
- std::move(request.reply_markup_));
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->edit_inline_message_reply_markup(request.inline_message_id_, std::move(request.reply_markup_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::editMessageSchedulingState &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->edit_message_scheduling_state({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ std::move(request.scheduling_state_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getForumTopicDefaultIcons &request) {
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->get_default_topic_icons(false, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::createForumTopic &request) {
+ CLEAN_INPUT_STRING(request.name_);
+ CREATE_REQUEST_PROMISE();
+ forum_topic_manager_->create_forum_topic(DialogId(request.chat_id_), std::move(request.name_),
+ std::move(request.icon_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::editForumTopic &request) {
+ CLEAN_INPUT_STRING(request.name_);
+ CREATE_OK_REQUEST_PROMISE();
+ forum_topic_manager_->edit_forum_topic(DialogId(request.chat_id_), MessageId(request.message_thread_id_),
+ std::move(request.name_), CustomEmojiId(request.icon_custom_emoji_id_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleForumTopicIsClosed &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ forum_topic_manager_->toggle_forum_topic_is_closed(DialogId(request.chat_id_), MessageId(request.message_thread_id_),
+ request.is_closed_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::deleteForumTopic &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ forum_topic_manager_->delete_forum_topic(DialogId(request.chat_id_), MessageId(request.message_thread_id_),
+ std::move(promise));
}
void Td::on_request(uint64 id, td_api::setGameScore &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
- CREATE_REQUEST(SetGameScoreRequest, request.chat_id_, request.message_id_, request.edit_message_, request.user_id_,
- request.score_, request.force_);
+ CREATE_REQUEST_PROMISE();
+ game_manager_->set_game_score({DialogId(request.chat_id_), MessageId(request.message_id_)}, request.edit_message_,
+ UserId(request.user_id_), request.score_, request.force_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::setInlineGameScore &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.inline_message_id_);
- CREATE_REQUEST(SetInlineGameScoreRequest, std::move(request.inline_message_id_), request.edit_message_,
- request.user_id_, request.score_, request.force_);
+ CREATE_OK_REQUEST_PROMISE();
+ game_manager_->set_inline_game_score(request.inline_message_id_, request.edit_message_, UserId(request.user_id_),
+ request.score_, request.force_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::getGameHighScores &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
- CREATE_REQUEST(GetGameHighScoresRequest, request.chat_id_, request.message_id_, request.user_id_);
+ CREATE_REQUEST_PROMISE();
+ game_manager_->get_game_high_scores({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ UserId(request.user_id_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::getInlineGameHighScores &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.inline_message_id_);
- CREATE_REQUEST(GetInlineGameHighScoresRequest, std::move(request.inline_message_id_), request.user_id_);
+ CREATE_REQUEST_PROMISE();
+ game_manager_->get_inline_game_high_scores(request.inline_message_id_, UserId(request.user_id_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::deleteChatReplyMarkup &request) {
- CHECK_AUTH();
CHECK_IS_USER();
answer_ok_query(
id, messages_manager_->delete_dialog_reply_markup(DialogId(request.chat_id_), MessageId(request.message_id_)));
}
void Td::on_request(uint64 id, td_api::sendChatAction &request) {
- CHECK_AUTH();
- CREATE_REQUEST(SendChatActionRequest, request.chat_id_, std::move(request.action_));
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->send_dialog_action(DialogId(request.chat_id_), MessageId(request.message_thread_id_),
+ DialogAction(std::move(request.action_)), std::move(promise));
}
void Td::on_request(uint64 id, td_api::sendChatScreenshotTakenNotification &request) {
- CHECK_AUTH();
CHECK_IS_USER();
answer_ok_query(id, messages_manager_->send_screenshot_taken_notification_message(DialogId(request.chat_id_)));
}
-void Td::on_request(uint64 id, const td_api::forwardMessages &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::forwardMessages &request) {
+ auto input_message_ids = MessagesManager::get_message_ids(request.message_ids_);
+ auto message_copy_options =
+ transform(input_message_ids, [send_copy = request.send_copy_, remove_caption = request.remove_caption_](
+ MessageId) { return MessageCopyOptions(send_copy, remove_caption); });
+ auto r_messages = messages_manager_->forward_messages(
+ DialogId(request.chat_id_), MessageId(request.message_thread_id_), DialogId(request.from_chat_id_),
+ std::move(input_message_ids), std::move(request.options_), false, std::move(message_copy_options),
+ request.only_preview_);
+ if (r_messages.is_error()) {
+ send_closure(actor_id(this), &Td::send_error, id, r_messages.move_as_error());
+ } else {
+ send_closure(actor_id(this), &Td::send_result, id, r_messages.move_as_ok());
+ }
+}
+void Td::on_request(uint64 id, const td_api::resendMessages &request) {
DialogId dialog_id(request.chat_id_);
- auto r_message_ids = messages_manager_->forward_messages(
- dialog_id, DialogId(request.from_chat_id_), MessagesManager::get_message_ids(request.message_ids_),
- request.disable_notification_, request.from_background_, false, request.as_album_);
+ auto r_message_ids =
+ messages_manager_->resend_messages(dialog_id, MessagesManager::get_message_ids(request.message_ids_));
if (r_message_ids.is_error()) {
return send_closure(actor_id(this), &Td::send_error, id, r_message_ids.move_as_error());
}
send_closure(actor_id(this), &Td::send_result, id,
- messages_manager_->get_messages_object(-1, dialog_id, r_message_ids.ok()));
+ messages_manager_->get_messages_object(-1, dialog_id, r_message_ids.ok(), false, "resendMessages"));
}
void Td::on_request(uint64 id, td_api::getWebPagePreview &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(GetWebPagePreviewRequest, std::move(request.text_));
}
void Td::on_request(uint64 id, td_api::getWebPageInstantView &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.url_);
CREATE_REQUEST(GetWebPageInstantViewRequest, std::move(request.url_), request.force_full_);
}
void Td::on_request(uint64 id, const td_api::createPrivateChat &request) {
- CHECK_AUTH();
CREATE_REQUEST(CreateChatRequest, DialogId(UserId(request.user_id_)), request.force_);
}
void Td::on_request(uint64 id, const td_api::createBasicGroupChat &request) {
- CHECK_AUTH();
CREATE_REQUEST(CreateChatRequest, DialogId(ChatId(request.basic_group_id_)), request.force_);
}
void Td::on_request(uint64 id, const td_api::createSupergroupChat &request) {
- CHECK_AUTH();
CREATE_REQUEST(CreateChatRequest, DialogId(ChannelId(request.supergroup_id_)), request.force_);
}
void Td::on_request(uint64 id, td_api::createSecretChat &request) {
- CHECK_AUTH();
CREATE_REQUEST(CreateChatRequest, DialogId(SecretChatId(request.secret_chat_id_)), true);
}
void Td::on_request(uint64 id, td_api::createNewBasicGroupChat &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.title_);
- CREATE_REQUEST(CreateNewGroupChatRequest, request.user_ids_, std::move(request.title_));
+ CREATE_REQUEST(CreateNewGroupChatRequest, UserId::get_user_ids(request.user_ids_), std::move(request.title_));
}
void Td::on_request(uint64 id, td_api::createNewSupergroupChat &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.title_);
CLEAN_INPUT_STRING(request.description_);
CREATE_REQUEST(CreateNewSupergroupChatRequest, std::move(request.title_), !request.is_channel_,
- std::move(request.description_));
+ std::move(request.description_), std::move(request.location_), request.for_import_);
}
void Td::on_request(uint64 id, td_api::createNewSecretChat &request) {
- CHECK_AUTH();
CREATE_REQUEST(CreateNewSecretChatRequest, request.user_id_);
}
-void Td::on_request(uint64 id, td_api::createCall &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::createCall &request) {
CHECK_IS_USER();
- CREATE_REQUEST_PROMISE(promise);
+
+ if (request.protocol_ == nullptr) {
+ return send_error_raw(id, 400, "Call protocol must be non-empty");
+ }
+
+ UserId user_id(request.user_id_);
+ auto r_input_user = contacts_manager_->get_input_user(user_id);
+ if (r_input_user.is_error()) {
+ return send_error_raw(id, r_input_user.error().code(), r_input_user.error().message());
+ }
+
+ if (!G()->get_option_boolean("calls_enabled")) {
+ return send_error_raw(id, 400, "Calls are not enabled for the current user");
+ }
+
+ CREATE_REQUEST_PROMISE();
auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<CallId> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(result.ok().as_td_api());
+ promise.set_value(result.ok().get_call_id_object());
}
});
+ send_closure(G()->call_manager(), &CallManager::create_call, user_id, r_input_user.move_as_ok(),
+ CallProtocol(*request.protocol_), request.is_video_, std::move(query_promise));
+}
- if (!request.protocol_) {
- return query_promise.set_error(Status::Error(5, "CallProtocol must not be empty"));
+void Td::on_request(uint64 id, const td_api::acceptCall &request) {
+ CHECK_IS_USER();
+ if (request.protocol_ == nullptr) {
+ return send_error_raw(id, 400, "Call protocol must be non-empty");
}
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->call_manager(), &CallManager::accept_call, CallId(request.call_id_),
+ CallProtocol(*request.protocol_), std::move(promise));
+}
- UserId user_id(request.user_id_);
- auto input_user = contacts_manager_->get_input_user(user_id);
- if (input_user == nullptr) {
- return query_promise.set_error(Status::Error(6, "User not found"));
- }
+void Td::on_request(uint64 id, td_api::sendCallSignalingData &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->call_manager(), &CallManager::send_call_signaling_data, CallId(request.call_id_),
+ std::move(request.data_), std::move(promise));
+}
- if (!G()->shared_config().get_option_boolean("calls_enabled")) {
- return query_promise.set_error(Status::Error(7, "Calls are not enabled for the current user"));
- }
+void Td::on_request(uint64 id, const td_api::discardCall &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->call_manager(), &CallManager::discard_call, CallId(request.call_id_), request.is_disconnected_,
+ request.duration_, request.is_video_, request.connection_id_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::sendCallRating &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.comment_);
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->call_manager(), &CallManager::rate_call, CallId(request.call_id_), request.rating_,
+ std::move(request.comment_), std::move(request.problems_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::sendCallDebugInformation &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.debug_information_);
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->call_manager(), &CallManager::send_call_debug_information, CallId(request.call_id_),
+ std::move(request.debug_information_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::sendCallLog &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->call_manager(), &CallManager::send_call_log, CallId(request.call_id_), std::move(request.log_file_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getVideoChatAvailableParticipants &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ group_call_manager_->get_group_call_join_as(DialogId(request.chat_id_), std::move(promise));
+}
- send_closure(G()->call_manager(), &CallManager::create_call, user_id, std::move(input_user),
- CallProtocol::from_td_api(*request.protocol_), std::move(query_promise));
+void Td::on_request(uint64 id, const td_api::setVideoChatDefaultParticipant &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, default_join_as_dialog_id,
+ get_message_sender_dialog_id(this, request.default_participant_id_, true, false));
+ group_call_manager_->set_group_call_default_join_as(DialogId(request.chat_id_), default_join_as_dialog_id,
+ std::move(promise));
}
-void Td::on_request(uint64 id, td_api::discardCall &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::createVideoChat &request) {
CHECK_IS_USER();
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
+ CLEAN_INPUT_STRING(request.title_);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<GroupCallId> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(make_tl_object<td_api::ok>());
+ promise.set_value(td_api::make_object<td_api::groupCallId>(result.ok().get()));
}
});
- send_closure(G()->call_manager(), &CallManager::discard_call, CallId(request.call_id_), request.is_disconnected_,
- request.duration_, request.connection_id_, std::move(query_promise));
+ group_call_manager_->create_voice_chat(DialogId(request.chat_id_), std::move(request.title_), request.start_date_,
+ request.is_rtmp_stream_, std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getVideoChatRtmpUrl &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ group_call_manager_->get_voice_chat_rtmp_stream_url(DialogId(request.chat_id_), false, std::move(promise));
}
-void Td::on_request(uint64 id, td_api::acceptCall &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::replaceVideoChatRtmpUrl &request) {
CHECK_IS_USER();
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
+ CREATE_REQUEST_PROMISE();
+ group_call_manager_->get_voice_chat_rtmp_stream_url(DialogId(request.chat_id_), true, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getGroupCall &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ group_call_manager_->get_group_call(GroupCallId(request.group_call_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::startScheduledGroupCall &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->start_scheduled_group_call(GroupCallId(request.group_call_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleGroupCallEnabledStartNotification &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->toggle_group_call_start_subscribed(GroupCallId(request.group_call_id_),
+ request.enabled_start_notification_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::joinGroupCall &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.invite_hash_);
+ CLEAN_INPUT_STRING(request.payload_);
+ CREATE_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, join_as_dialog_id,
+ get_message_sender_dialog_id(this, request.participant_id_, true, true));
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(make_tl_object<td_api::ok>());
+ promise.set_value(make_tl_object<td_api::text>(result.move_as_ok()));
}
});
- if (!request.protocol_) {
- return query_promise.set_error(Status::Error(5, "Call protocol must not be empty"));
- }
- send_closure(G()->call_manager(), &CallManager::accept_call, CallId(request.call_id_),
- CallProtocol::from_td_api(*request.protocol_), std::move(query_promise));
+ group_call_manager_->join_group_call(GroupCallId(request.group_call_id_), join_as_dialog_id, request.audio_source_id_,
+ std::move(request.payload_), request.is_muted_, request.is_my_video_enabled_,
+ request.invite_hash_, std::move(query_promise));
}
-void Td::on_request(uint64 id, td_api::sendCallRating &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::startGroupCallScreenSharing &request) {
CHECK_IS_USER();
- CLEAN_INPUT_STRING(request.comment_);
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
+ CLEAN_INPUT_STRING(request.payload_);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(make_tl_object<td_api::ok>());
+ promise.set_value(make_tl_object<td_api::text>(result.move_as_ok()));
}
});
- send_closure(G()->call_manager(), &CallManager::rate_call, CallId(request.call_id_), request.rating_,
- std::move(request.comment_), std::move(query_promise));
+ group_call_manager_->start_group_call_screen_sharing(GroupCallId(request.group_call_id_), request.audio_source_id_,
+ std::move(request.payload_), std::move(query_promise));
}
-void Td::on_request(uint64 id, td_api::sendCallDebugInformation &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::toggleGroupCallScreenSharingIsPaused &request) {
CHECK_IS_USER();
- CLEAN_INPUT_STRING(request.debug_information_);
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->toggle_group_call_is_my_presentation_paused(GroupCallId(request.group_call_id_),
+ request.is_paused_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::endGroupCallScreenSharing &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->end_group_call_screen_sharing(GroupCallId(request.group_call_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setGroupCallTitle &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.title_);
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->set_group_call_title(GroupCallId(request.group_call_id_), std::move(request.title_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleGroupCallMuteNewParticipants &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->toggle_group_call_mute_new_participants(GroupCallId(request.group_call_id_),
+ request.mute_new_participants_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::revokeGroupCallInviteLink &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->revoke_group_call_invite_link(GroupCallId(request.group_call_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::inviteGroupCallParticipants &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->invite_group_call_participants(GroupCallId(request.group_call_id_),
+ UserId::get_user_ids(request.user_ids_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getGroupCallInviteLink &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(make_tl_object<td_api::ok>());
+ promise.set_value(td_api::make_object<td_api::httpUrl>(result.move_as_ok()));
}
});
- send_closure(G()->call_manager(), &CallManager::send_call_debug_information, CallId(request.call_id_),
- std::move(request.debug_information_), std::move(query_promise));
+ group_call_manager_->get_group_call_invite_link(GroupCallId(request.group_call_id_), request.can_self_unmute_,
+ std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, td_api::startGroupCallRecording &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.title_);
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->toggle_group_call_recording(GroupCallId(request.group_call_id_), true, std::move(request.title_),
+ request.record_video_, request.use_portrait_orientation_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::endGroupCallRecording &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->toggle_group_call_recording(GroupCallId(request.group_call_id_), false, string(), false, false,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleGroupCallIsMyVideoPaused &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->toggle_group_call_is_my_video_paused(GroupCallId(request.group_call_id_),
+ request.is_my_video_paused_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleGroupCallIsMyVideoEnabled &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->toggle_group_call_is_my_video_enabled(GroupCallId(request.group_call_id_),
+ request.is_my_video_enabled_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setGroupCallParticipantIsSpeaking &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->set_group_call_participant_is_speaking(
+ GroupCallId(request.group_call_id_), request.audio_source_, request.is_speaking_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleGroupCallParticipantIsMuted &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, participant_dialog_id,
+ get_message_sender_dialog_id(this, request.participant_id_, true, false));
+ group_call_manager_->toggle_group_call_participant_is_muted(
+ GroupCallId(request.group_call_id_), participant_dialog_id, request.is_muted_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setGroupCallParticipantVolumeLevel &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, participant_dialog_id,
+ get_message_sender_dialog_id(this, request.participant_id_, true, false));
+ group_call_manager_->set_group_call_participant_volume_level(
+ GroupCallId(request.group_call_id_), participant_dialog_id, request.volume_level_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleGroupCallParticipantIsHandRaised &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, participant_dialog_id,
+ get_message_sender_dialog_id(this, request.participant_id_, true, false));
+ group_call_manager_->toggle_group_call_participant_is_hand_raised(
+ GroupCallId(request.group_call_id_), participant_dialog_id, request.is_hand_raised_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::loadGroupCallParticipants &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->load_group_call_participants(GroupCallId(request.group_call_id_), request.limit_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::leaveGroupCall &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->leave_group_call(GroupCallId(request.group_call_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::endGroupCall &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ group_call_manager_->discard_group_call(GroupCallId(request.group_call_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getGroupCallStreams &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ group_call_manager_->get_group_call_streams(GroupCallId(request.group_call_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getGroupCallStreamSegment &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ auto file_part = td_api::make_object<td_api::filePart>();
+ file_part->data_ = result.move_as_ok();
+ promise.set_value(std::move(file_part));
+ }
+ });
+ group_call_manager_->get_group_call_stream_segment(GroupCallId(request.group_call_id_), request.time_offset_,
+ request.scale_, request.channel_id_,
+ std::move(request.video_quality_), std::move(query_promise));
}
void Td::on_request(uint64 id, const td_api::upgradeBasicGroupChatToSupergroupChat &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(UpgradeGroupChatToSupergroupChatRequest, request.chat_id_);
}
+void Td::on_request(uint64 id, const td_api::getChatListsToAddChat &request) {
+ CHECK_IS_USER();
+ auto dialog_lists = messages_manager_->get_dialog_lists_to_add_dialog(DialogId(request.chat_id_));
+ auto chat_lists =
+ transform(dialog_lists, [](DialogListId dialog_list_id) { return dialog_list_id.get_chat_list_object(); });
+ send_closure(actor_id(this), &Td::send_result, id, td_api::make_object<td_api::chatLists>(std::move(chat_lists)));
+}
+
+void Td::on_request(uint64 id, const td_api::addChatToList &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->add_dialog_to_list(DialogId(request.chat_id_), DialogListId(request.chat_list_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getChatFilter &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST(GetChatFilterRequest, request.chat_filter_id_);
+}
+
+void Td::on_request(uint64 id, const td_api::getRecommendedChatFilters &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->get_recommended_dialog_filters(std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::createChatFilter &request) {
+ CHECK_IS_USER();
+ if (request.filter_ == nullptr) {
+ return send_error_raw(id, 400, "Chat filter must be non-empty");
+ }
+ CLEAN_INPUT_STRING(request.filter_->title_);
+ CLEAN_INPUT_STRING(request.filter_->icon_name_);
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->create_dialog_filter(std::move(request.filter_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::editChatFilter &request) {
+ CHECK_IS_USER();
+ if (request.filter_ == nullptr) {
+ return send_error_raw(id, 400, "Chat filter must be non-empty");
+ }
+ CLEAN_INPUT_STRING(request.filter_->title_);
+ CLEAN_INPUT_STRING(request.filter_->icon_name_);
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->edit_dialog_filter(DialogFilterId(request.chat_filter_id_), std::move(request.filter_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::deleteChatFilter &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->delete_dialog_filter(DialogFilterId(request.chat_filter_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::reorderChatFilters &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->reorder_dialog_filters(
+ transform(request.chat_filter_ids_, [](int32 id) { return DialogFilterId(id); }),
+ request.main_chat_list_position_, std::move(promise));
+}
+
void Td::on_request(uint64 id, td_api::setChatTitle &request) {
- CHECK_AUTH();
CLEAN_INPUT_STRING(request.title_);
- CREATE_REQUEST(SetChatTitleRequest, request.chat_id_, std::move(request.title_));
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->set_dialog_title(DialogId(request.chat_id_), request.title_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setChatPhoto &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->set_dialog_photo(DialogId(request.chat_id_), request.photo_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setChatMessageTtl &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->set_dialog_message_ttl(DialogId(request.chat_id_), request.ttl_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setChatPermissions &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->set_dialog_permissions(DialogId(request.chat_id_), request.permissions_, std::move(promise));
}
-void Td::on_request(uint64 id, td_api::setChatPhoto &request) {
- CHECK_AUTH();
- CREATE_REQUEST(SetChatPhotoRequest, request.chat_id_, std::move(request.photo_));
+void Td::on_request(uint64 id, td_api::setChatTheme &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.theme_name_);
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->set_dialog_theme(DialogId(request.chat_id_), request.theme_name_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::setChatDraftMessage &request) {
- CHECK_AUTH();
CHECK_IS_USER();
answer_ok_query(
- id, messages_manager_->set_dialog_draft_message(DialogId(request.chat_id_), std::move(request.draft_message_)));
+ id, messages_manager_->set_dialog_draft_message(DialogId(request.chat_id_), MessageId(request.message_thread_id_),
+ std::move(request.draft_message_)));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleChatHasProtectedContent &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->toggle_dialog_has_protected_content(DialogId(request.chat_id_), request.has_protected_content_,
+ std::move(promise));
}
void Td::on_request(uint64 id, const td_api::toggleChatIsPinned &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- answer_ok_query(id, messages_manager_->toggle_dialog_is_pinned(DialogId(request.chat_id_), request.is_pinned_));
+ answer_ok_query(id, messages_manager_->toggle_dialog_is_pinned(DialogListId(request.chat_list_),
+ DialogId(request.chat_id_), request.is_pinned_));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleChatIsMarkedAsUnread &request) {
+ CHECK_IS_USER();
+ answer_ok_query(id, messages_manager_->toggle_dialog_is_marked_as_unread(DialogId(request.chat_id_),
+ request.is_marked_as_unread_));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleMessageSenderIsBlocked &request) {
+ CHECK_IS_USER();
+ answer_ok_query(id, messages_manager_->toggle_message_sender_is_blocked(request.sender_id_, request.is_blocked_));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleChatDefaultDisableNotification &request) {
+ CHECK_IS_USER();
+ answer_ok_query(id, messages_manager_->toggle_dialog_silent_send_message(DialogId(request.chat_id_),
+ request.default_disable_notification_));
}
void Td::on_request(uint64 id, const td_api::setPinnedChats &request) {
- CHECK_AUTH();
CHECK_IS_USER();
answer_ok_query(id, messages_manager_->set_pinned_dialogs(
+ DialogListId(request.chat_list_),
transform(request.chat_ids_, [](int64 chat_id) { return DialogId(chat_id); })));
}
+void Td::on_request(uint64 id, const td_api::getAttachmentMenuBot &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ attach_menu_manager_->get_attach_menu_bot(UserId(request.bot_user_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleBotIsAddedToAttachmentMenu &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ attach_menu_manager_->toggle_bot_is_added_to_attach_menu(UserId(request.bot_user_id_), request.is_added_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setChatAvailableReactions &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->set_dialog_available_reactions(DialogId(request.chat_id_), std::move(request.available_reactions_),
+ std::move(promise));
+}
+
void Td::on_request(uint64 id, td_api::setChatClientData &request) {
- CHECK_AUTH();
answer_ok_query(
id, messages_manager_->set_dialog_client_data(DialogId(request.chat_id_), std::move(request.client_data_)));
}
+void Td::on_request(uint64 id, td_api::setChatDescription &request) {
+ CLEAN_INPUT_STRING(request.description_);
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->set_dialog_description(DialogId(request.chat_id_), request.description_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setChatDiscussionGroup &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_channel_discussion_group(DialogId(request.chat_id_), DialogId(request.discussion_chat_id_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setChatLocation &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_channel_location(DialogId(request.chat_id_), DialogLocation(std::move(request.location_)),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setChatSlowModeDelay &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_channel_slow_mode_delay(DialogId(request.chat_id_), request.slow_mode_delay_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::pinChatMessage &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->pin_dialog_message(DialogId(request.chat_id_), MessageId(request.message_id_),
+ request.disable_notification_, request.only_for_self_, false,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::unpinChatMessage &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->pin_dialog_message(DialogId(request.chat_id_), MessageId(request.message_id_), false, false, true,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::unpinAllChatMessages &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->unpin_all_dialog_messages(DialogId(request.chat_id_), MessageId(), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::unpinAllMessageThreadMessages &request) {
+ if (request.message_thread_id_ == 0) {
+ return send_error_raw(id, 400, "Invalid message thread identifier specified");
+ }
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->unpin_all_dialog_messages(DialogId(request.chat_id_), MessageId(request.message_thread_id_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::joinChat &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->add_dialog_participant(DialogId(request.chat_id_), contacts_manager_->get_my_id(), 0,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::leaveChat &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ DialogId dialog_id(request.chat_id_);
+ td_api::object_ptr<td_api::ChatMemberStatus> new_status = td_api::make_object<td_api::chatMemberStatusLeft>();
+ if (dialog_id.get_type() == DialogType::Channel && messages_manager_->have_dialog_force(dialog_id, "leaveChat")) {
+ auto status = contacts_manager_->get_channel_status(dialog_id.get_channel_id());
+ if (status.is_creator()) {
+ if (!status.is_member()) {
+ return promise.set_value(Unit());
+ }
+
+ new_status =
+ td_api::make_object<td_api::chatMemberStatusCreator>(status.get_rank(), status.is_anonymous(), false);
+ }
+ }
+ contacts_manager_->set_dialog_participant_status(dialog_id, DialogId(contacts_manager_->get_my_id()),
+ std::move(new_status), std::move(promise));
+}
+
void Td::on_request(uint64 id, const td_api::addChatMember &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(AddChatMemberRequest, request.chat_id_, request.user_id_, request.forward_limit_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->add_dialog_participant(DialogId(request.chat_id_), UserId(request.user_id_),
+ request.forward_limit_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::addChatMembers &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(AddChatMembersRequest, request.chat_id_, request.user_ids_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->add_dialog_participants(DialogId(request.chat_id_), UserId::get_user_ids(request.user_ids_),
+ std::move(promise));
}
void Td::on_request(uint64 id, td_api::setChatMemberStatus &request) {
- CHECK_AUTH();
- CREATE_REQUEST(SetChatMemberStatusRequest, request.chat_id_, request.user_id_, std::move(request.status_));
+ CREATE_OK_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, participant_dialog_id,
+ get_message_sender_dialog_id(this, request.member_id_, false, false));
+ contacts_manager_->set_dialog_participant_status(DialogId(request.chat_id_), participant_dialog_id,
+ std::move(request.status_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::banChatMember &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, participant_dialog_id,
+ get_message_sender_dialog_id(this, request.member_id_, false, false));
+ contacts_manager_->ban_dialog_participant(DialogId(request.chat_id_), participant_dialog_id,
+ request.banned_until_date_, request.revoke_messages_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::canTransferOwnership &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda(
+ [promise = std::move(promise)](Result<ContactsManager::CanTransferOwnershipResult> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(ContactsManager::get_can_transfer_ownership_result_object(result.ok()));
+ }
+ });
+ contacts_manager_->can_transfer_ownership(std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, td_api::transferChatOwnership &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.password_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->transfer_dialog_ownership(DialogId(request.chat_id_), UserId(request.user_id_), request.password_,
+ std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getChatMember &request) {
- CHECK_AUTH();
- CREATE_REQUEST(GetChatMemberRequest, request.chat_id_, request.user_id_);
+ CREATE_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, participant_dialog_id,
+ get_message_sender_dialog_id(this, request.member_id_, false, false));
+ contacts_manager_->get_dialog_participant(DialogId(request.chat_id_), participant_dialog_id, std::move(promise));
}
void Td::on_request(uint64 id, td_api::searchChatMembers &request) {
- CHECK_AUTH();
CLEAN_INPUT_STRING(request.query_);
- CREATE_REQUEST(SearchChatMembersRequest, request.chat_id_, std::move(request.query_), request.limit_);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise =
+ PromiseCreator::lambda([promise = std::move(promise), td = this](Result<DialogParticipants> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(result.ok().get_chat_members_object(td));
+ }
+ });
+ contacts_manager_->search_dialog_participants(DialogId(request.chat_id_), request.query_, request.limit_,
+ DialogParticipantFilter(request.filter_), std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getChatAdministrators &request) {
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_dialog_administrators(DialogId(request.chat_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::replacePrimaryChatInviteLink &request) {
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->export_dialog_invite_link(DialogId(request.chat_id_), string(), 0, 0, false, true,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::createChatInviteLink &request) {
+ CLEAN_INPUT_STRING(request.name_);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->export_dialog_invite_link(DialogId(request.chat_id_), std::move(request.name_),
+ request.expiration_date_, request.member_limit_,
+ request.creates_join_request_, false, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::editChatInviteLink &request) {
+ CLEAN_INPUT_STRING(request.name_);
+ CLEAN_INPUT_STRING(request.invite_link_);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->edit_dialog_invite_link(DialogId(request.chat_id_), request.invite_link_, std::move(request.name_),
+ request.expiration_date_, request.member_limit_,
+ request.creates_join_request_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getChatInviteLink &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.invite_link_);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_dialog_invite_link(DialogId(request.chat_id_), request.invite_link_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getChatInviteLinkCounts &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_dialog_invite_link_counts(DialogId(request.chat_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getChatInviteLinks &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.offset_invite_link_);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_dialog_invite_links(DialogId(request.chat_id_), UserId(request.creator_user_id_),
+ request.is_revoked_, request.offset_date_, request.offset_invite_link_,
+ request.limit_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getChatInviteLinkMembers &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.invite_link_);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_dialog_invite_link_users(DialogId(request.chat_id_), request.invite_link_,
+ std::move(request.offset_member_), request.limit_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getChatJoinRequests &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.invite_link_);
+ CLEAN_INPUT_STRING(request.query_);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_dialog_join_requests(DialogId(request.chat_id_), request.invite_link_, request.query_,
+ std::move(request.offset_request_), request.limit_, std::move(promise));
}
-void Td::on_request(uint64 id, td_api::getChatAdministrators &request) {
- CHECK_AUTH();
- CREATE_REQUEST(GetChatAdministratorsRequest, request.chat_id_);
+void Td::on_request(uint64 id, const td_api::processChatJoinRequest &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->process_dialog_join_request(DialogId(request.chat_id_), UserId(request.user_id_), request.approve_,
+ std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::generateChatInviteLink &request) {
- CHECK_AUTH();
- CREATE_REQUEST(GenerateChatInviteLinkRequest, request.chat_id_);
+void Td::on_request(uint64 id, td_api::processChatJoinRequests &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.invite_link_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->process_dialog_join_requests(DialogId(request.chat_id_), request.invite_link_, request.approve_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::revokeChatInviteLink &request) {
+ CLEAN_INPUT_STRING(request.invite_link_);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->revoke_dialog_invite_link(DialogId(request.chat_id_), request.invite_link_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::deleteRevokedChatInviteLink &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.invite_link_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->delete_revoked_dialog_invite_link(DialogId(request.chat_id_), request.invite_link_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::deleteAllRevokedChatInviteLinks &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->delete_all_revoked_dialog_invite_links(DialogId(request.chat_id_),
+ UserId(request.creator_user_id_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::checkChatInviteLink &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.invite_link_);
CREATE_REQUEST(CheckChatInviteLinkRequest, request.invite_link_);
}
void Td::on_request(uint64 id, td_api::joinChatByInviteLink &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.invite_link_);
CREATE_REQUEST(JoinChatByInviteLinkRequest, request.invite_link_);
}
void Td::on_request(uint64 id, td_api::getChatEventLog &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.query_);
- CREATE_REQUEST(GetChatEventLogRequest, request.chat_id_, std::move(request.query_), request.from_event_id_,
- request.limit_, std::move(request.filters_), std::move(request.user_ids_));
+ CREATE_REQUEST_PROMISE();
+ get_dialog_event_log(this, DialogId(request.chat_id_), std::move(request.query_), request.from_event_id_,
+ request.limit_, std::move(request.filters_), UserId::get_user_ids(request.user_ids_),
+ std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::downloadFile &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::clearAllDraftMessages &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->clear_all_draft_messages(request.exclude_secret_chats_, std::move(promise));
+}
+void Td::on_request(uint64 id, const td_api::downloadFile &request) {
auto priority = request.priority_;
if (!(1 <= priority && priority <= 32)) {
- return send_error_raw(id, 5, "Download priority must be in [1;32] range");
+ return send_error_raw(id, 400, "Download priority must be between 1 and 32");
+ }
+ auto offset = request.offset_;
+ if (offset < 0) {
+ return send_error_raw(id, 400, "Download offset must be non-negative");
+ }
+ auto limit = request.limit_;
+ if (limit < 0) {
+ return send_error_raw(id, 400, "Download limit must be non-negative");
}
- file_manager_->download(FileId(request.file_id_, 0), download_file_callback_, priority);
- auto file = file_manager_->get_file_object(FileId(request.file_id_, 0), false);
- if (file->id_ == 0) {
- return send_error_raw(id, 400, "Invalid file id");
+ FileId file_id(request.file_id_, 0);
+ auto file_view = file_manager_->get_file_view(file_id);
+ if (file_view.empty()) {
+ return send_error_raw(id, 400, "Invalid file identifier");
}
- send_closure(actor_id(this), &Td::send_result, id, std::move(file));
+ auto info_it = pending_file_downloads_.find(file_id);
+ DownloadInfo *info = info_it == pending_file_downloads_.end() ? nullptr : &info_it->second;
+ if (info != nullptr && (offset != info->offset || limit != info->limit)) {
+ // we can't have two pending requests with different offset and limit, so cancel all previous requests
+ auto request_ids = std::move(info->request_ids);
+ info->request_ids.clear();
+ for (auto request_id : request_ids) {
+ send_closure(actor_id(this), &Td::send_error, request_id,
+ Status::Error(200, "Canceled by another downloadFile request"));
+ }
+ }
+ if (request.synchronous_) {
+ if (info == nullptr) {
+ info = &pending_file_downloads_[file_id];
+ }
+ info->offset = offset;
+ info->limit = limit;
+ info->request_ids.push_back(id);
+ }
+ Promise<td_api::object_ptr<td_api::file>> download_promise;
+ if (!request.synchronous_) {
+ CREATE_REQUEST_PROMISE();
+ download_promise = std::move(promise);
+ }
+ file_manager_->download(file_id, download_file_callback_, priority, offset, limit, std::move(download_promise));
}
-void Td::on_request(uint64 id, const td_api::cancelDownloadFile &request) {
- CHECK_AUTH();
+void Td::on_file_download_finished(FileId file_id) {
+ auto it = pending_file_downloads_.find(file_id);
+ if (it == pending_file_downloads_.end()) {
+ return;
+ }
+ for (auto id : it->second.request_ids) {
+ // there was send_closure to call this function
+ auto file_object = file_manager_->get_file_object(file_id, false);
+ CHECK(file_object != nullptr);
+ auto download_offset = file_object->local_->download_offset_;
+ auto downloaded_size = file_object->local_->downloaded_prefix_size_;
+ auto file_size = file_object->size_;
+ auto limit = it->second.limit;
+ if (limit == 0) {
+ limit = std::numeric_limits<int64>::max();
+ }
+ if (file_object->local_->is_downloading_completed_ ||
+ (download_offset <= it->second.offset && download_offset + downloaded_size >= it->second.offset &&
+ ((file_size != 0 && download_offset + downloaded_size == file_size) ||
+ download_offset + downloaded_size - it->second.offset >= limit))) {
+ send_result(id, std::move(file_object));
+ } else {
+ send_error_impl(id, td_api::make_object<td_api::error>(400, "File download has failed or was canceled"));
+ }
+ }
+ pending_file_downloads_.erase(it);
+}
- file_manager_->download(FileId(request.file_id_, 0), nullptr, request.only_if_pending_ ? -1 : 0);
+void Td::on_request(uint64 id, const td_api::getFileDownloadedPrefixSize &request) {
+ if (request.offset_ < 0) {
+ return send_error_raw(id, 400, "Parameter offset must be non-negative");
+ }
+ auto file_view = file_manager_->get_file_view(FileId(request.file_id_, 0));
+ if (file_view.empty()) {
+ return send_closure(actor_id(this), &Td::send_error, id, Status::Error(400, "Unknown file ID"));
+ }
+ send_closure(actor_id(this), &Td::send_result, id,
+ td_api::make_object<td_api::fileDownloadedPrefixSize>(file_view.downloaded_prefix(request.offset_)));
+}
+void Td::on_request(uint64 id, const td_api::cancelDownloadFile &request) {
+ file_manager_->download(FileId(request.file_id_, 0), nullptr, request.only_if_pending_ ? -1 : 0,
+ FileManager::KEEP_DOWNLOAD_OFFSET, FileManager::KEEP_DOWNLOAD_LIMIT,
+ Promise<td_api::object_ptr<td_api::file>>());
send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
}
-void Td::on_request(uint64 id, td_api::uploadFile &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::getSuggestedFileName &request) {
+ Result<string> r_file_name = file_manager_->get_suggested_file_name(FileId(request.file_id_, 0), request.directory_);
+ if (r_file_name.is_error()) {
+ return send_closure(actor_id(this), &Td::send_error, id, r_file_name.move_as_error());
+ }
+ send_closure(actor_id(this), &Td::send_result, id, td_api::make_object<td_api::text>(r_file_name.ok()));
+}
+void Td::on_request(uint64 id, td_api::preliminaryUploadFile &request) {
auto priority = request.priority_;
if (!(1 <= priority && priority <= 32)) {
- return send_error_raw(id, 5, "Upload priority must be in [1;32] range");
+ return send_error_raw(id, 400, "Upload priority must be between 1 and 32");
}
- auto file_type = request.file_type_ == nullptr ? FileType::Temp : from_td_api(*request.file_type_);
+ auto file_type = request.file_type_ == nullptr ? FileType::Temp : get_file_type(*request.file_type_);
bool is_secret = file_type == FileType::Encrypted || file_type == FileType::EncryptedThumbnail;
- auto r_file_id = file_manager_->get_input_file_id(file_type, request.file_, DialogId(), false, is_secret, true);
+ bool is_secure = file_type == FileType::SecureEncrypted;
+ auto r_file_id = file_manager_->get_input_file_id(file_type, request.file_, DialogId(), false, is_secret,
+ !is_secure && !is_secret, is_secure);
if (r_file_id.is_error()) {
return send_error_raw(id, 400, r_file_id.error().message());
}
auto file_id = r_file_id.ok();
- auto upload_file_id = file_manager_->dup_file_id(file_id);
+ auto upload_file_id = file_manager_->dup_file_id(file_id, "preliminaryUploadFile");
file_manager_->upload(upload_file_id, upload_file_callback_, priority, 0);
send_closure(actor_id(this), &Td::send_result, id, file_manager_->get_file_object(upload_file_id, false));
}
-void Td::on_request(uint64 id, const td_api::cancelUploadFile &request) {
- CHECK_AUTH();
-
- file_manager_->upload(FileId(request.file_id_, 0), nullptr, 0, 0);
+void Td::on_request(uint64 id, const td_api::cancelPreliminaryUploadFile &request) {
+ file_manager_->cancel_upload(FileId(request.file_id_, 0));
send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
}
+void Td::on_request(uint64 id, td_api::writeGeneratedFilePart &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(file_manager_actor_, &FileManager::external_file_generate_write_part, request.generation_id_,
+ request.offset_, std::move(request.data_), std::move(promise));
+}
+
void Td::on_request(uint64 id, const td_api::setFileGenerationProgress &request) {
- CHECK_AUTH();
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
- if (result.is_error()) {
- promise.set_error(result.move_as_error());
- } else {
- promise.set_value(make_tl_object<td_api::ok>());
- }
- });
+ CREATE_OK_REQUEST_PROMISE();
send_closure(file_manager_actor_, &FileManager::external_file_generate_progress, request.generation_id_,
- request.expected_size_, request.local_prefix_size_, std::move(query_promise));
+ request.expected_size_, request.local_prefix_size_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::finishFileGeneration &request) {
- CHECK_AUTH();
Status status;
if (request.error_ != nullptr) {
CLEAN_INPUT_STRING(request.error_->message_);
status = Status::Error(request.error_->code_, request.error_->message_);
}
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
- if (result.is_error()) {
- promise.set_error(result.move_as_error());
- } else {
- promise.set_value(make_tl_object<td_api::ok>());
- }
- });
+ CREATE_OK_REQUEST_PROMISE();
send_closure(file_manager_actor_, &FileManager::external_file_generate_finish, request.generation_id_,
- std::move(status), std::move(query_promise));
+ std::move(status), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::readFilePart &request) {
+ CREATE_REQUEST_PROMISE();
+ send_closure(file_manager_actor_, &FileManager::read_file_part, FileId(request.file_id_, 0), request.offset_,
+ request.count_, 2, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::deleteFile &request) {
- CHECK_AUTH();
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(file_manager_actor_, &FileManager::delete_file, FileId(request.file_id_, 0), std::move(promise),
+ "td_api::deleteFile");
+}
+
+void Td::on_request(uint64 id, const td_api::addFileToDownloads &request) {
+ if (!(1 <= request.priority_ && request.priority_ <= 32)) {
+ return send_error_raw(id, 400, "Download priority must be between 1 and 32");
+ }
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->add_message_file_to_downloads(
+ FullMessageId(DialogId(request.chat_id_), MessageId(request.message_id_)), FileId(request.file_id_, 0),
+ request.priority_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleDownloadIsPaused &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(download_manager_actor_, &DownloadManager::toggle_is_paused, FileId(request.file_id_, 0),
+ request.is_paused_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::toggleAllDownloadsArePaused &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(download_manager_actor_, &DownloadManager::toggle_all_is_paused, request.are_paused_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::removeFileFromDownloads &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(download_manager_actor_, &DownloadManager::remove_file, FileId(request.file_id_, 0), FileSourceId(),
+ request.delete_from_cache_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::removeAllFilesFromDownloads &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(download_manager_actor_, &DownloadManager::remove_all_files, request.only_active_,
+ request.only_completed_, request.delete_from_cache_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::searchFileDownloads &request) {
+ CLEAN_INPUT_STRING(request.query_);
+ CLEAN_INPUT_STRING(request.offset_);
+ CREATE_REQUEST_PROMISE();
+ send_closure(download_manager_actor_, &DownloadManager::search, std::move(request.query_), request.only_active_,
+ request.only_completed_, std::move(request.offset_), request.limit_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getMessageFileType &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.message_file_head_);
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->get_message_file_type(request.message_file_head_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getMessageImportConfirmationText &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(make_tl_object<td_api::ok>());
+ promise.set_value(make_tl_object<td_api::text>(result.move_as_ok()));
}
});
+ messages_manager_->get_message_import_confirmation_text(DialogId(request.chat_id_), std::move(query_promise));
+}
- send_closure(file_manager_actor_, &FileManager::delete_file, FileId(request.file_id_, 0), std::move(query_promise),
- "td_api::deleteFile");
+void Td::on_request(uint64 id, const td_api::importMessages &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->import_messages(DialogId(request.chat_id_), request.message_file_, request.attached_files_,
+ std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::blockUser &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::blockMessageSenderFromReplies &request) {
CHECK_IS_USER();
- answer_ok_query(id, contacts_manager_->block_user(UserId(request.user_id_)));
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->block_message_sender_from_replies(MessageId(request.message_id_), request.delete_message_,
+ request.delete_all_messages_, request.report_spam_,
+ std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::unblockUser &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::getBlockedMessageSenders &request) {
CHECK_IS_USER();
- answer_ok_query(id, contacts_manager_->unblock_user(UserId(request.user_id_)));
+ CREATE_REQUEST_PROMISE();
+ messages_manager_->get_blocked_dialogs(request.offset_, request.limit_, std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::getBlockedUsers &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::addContact &request) {
CHECK_IS_USER();
- CREATE_REQUEST(GetBlockedUsersRequest, request.offset_, request.limit_);
+ auto r_contact = get_contact(std::move(request.contact_));
+ if (r_contact.is_error()) {
+ return send_closure(actor_id(this), &Td::send_error, id, r_contact.move_as_error());
+ }
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->add_contact(r_contact.move_as_ok(), request.share_phone_number_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::importContacts &request) {
- CHECK_AUTH();
CHECK_IS_USER();
+ vector<Contact> contacts;
+ contacts.reserve(request.contacts_.size());
for (auto &contact : request.contacts_) {
- if (contact == nullptr) {
- return send_error_raw(id, 5, "Contact must not be empty");
+ auto r_contact = get_contact(std::move(contact));
+ if (r_contact.is_error()) {
+ return send_closure(actor_id(this), &Td::send_error, id, r_contact.move_as_error());
}
- CLEAN_INPUT_STRING(contact->phone_number_);
- CLEAN_INPUT_STRING(contact->first_name_);
- CLEAN_INPUT_STRING(contact->last_name_);
+ contacts.push_back(r_contact.move_as_ok());
}
- CREATE_REQUEST(ImportContactsRequest, std::move(request.contacts_));
+ CREATE_REQUEST(ImportContactsRequest, std::move(contacts));
+}
+
+void Td::on_request(uint64 id, const td_api::getContacts &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST(SearchContactsRequest, string(), 1000000);
}
void Td::on_request(uint64 id, td_api::searchContacts &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.query_);
CREATE_REQUEST(SearchContactsRequest, request.query_, request.limit_);
}
void Td::on_request(uint64 id, td_api::removeContacts &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(RemoveContactsRequest, std::move(request.user_ids_));
+ CREATE_REQUEST(RemoveContactsRequest, UserId::get_user_ids(request.user_ids_));
}
void Td::on_request(uint64 id, const td_api::getImportedContactCount &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_NO_ARGS_REQUEST(GetImportedContactCountRequest);
}
void Td::on_request(uint64 id, td_api::changeImportedContacts &request) {
- CHECK_AUTH();
CHECK_IS_USER();
+ vector<Contact> contacts;
+ contacts.reserve(request.contacts_.size());
for (auto &contact : request.contacts_) {
- if (contact == nullptr) {
- return send_error_raw(id, 5, "Contact must not be empty");
+ auto r_contact = get_contact(std::move(contact));
+ if (r_contact.is_error()) {
+ return send_closure(actor_id(this), &Td::send_error, id, r_contact.move_as_error());
}
- CLEAN_INPUT_STRING(contact->phone_number_);
- CLEAN_INPUT_STRING(contact->first_name_);
- CLEAN_INPUT_STRING(contact->last_name_);
+ contacts.push_back(r_contact.move_as_ok());
}
- CREATE_REQUEST(ChangeImportedContactsRequest, std::move(request.contacts_));
+ CREATE_REQUEST(ChangeImportedContactsRequest, std::move(contacts));
}
void Td::on_request(uint64 id, const td_api::clearImportedContacts &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(ClearImportedContactsRequest);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->clear_imported_contacts(std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::searchUserByPhoneNumber &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.phone_number_);
+ CREATE_REQUEST(SearchUserByPhoneNumberRequest, std::move(request.phone_number_));
+}
+
+void Td::on_request(uint64 id, const td_api::sharePhoneNumber &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->share_phone_number(UserId(request.user_id_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getRecentInlineBots &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_NO_ARGS_REQUEST(GetRecentInlineBotsRequest);
}
void Td::on_request(uint64 id, td_api::setName &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.first_name_);
CLEAN_INPUT_STRING(request.last_name_);
- CREATE_REQUEST(SetNameRequest, std::move(request.first_name_), std::move(request.last_name_));
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_name(request.first_name_, request.last_name_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::setBio &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.bio_);
- CREATE_REQUEST(SetBioRequest, std::move(request.bio_));
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_bio(request.bio_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::setUsername &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.username_);
- CREATE_REQUEST(SetUsernameRequest, std::move(request.username_));
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_username(request.username_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::toggleUsernameIsActive &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.username_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->toggle_username_is_active(std::move(request.username_), request.is_active_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::reorderActiveUsernames &request) {
+ CHECK_IS_USER();
+ for (auto &username : request.usernames_) {
+ CLEAN_INPUT_STRING(username);
+ }
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->reorder_usernames(std::move(request.usernames_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setEmojiStatus &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_emoji_status(EmojiStatus(request.emoji_status_, request.duration_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getThemedEmojiStatuses &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->get_default_emoji_statuses(false, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getDefaultEmojiStatuses &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ get_default_emoji_statuses(this, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getRecentEmojiStatuses &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ get_recent_emoji_statuses(this, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::clearRecentEmojiStatuses &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ clear_recent_emoji_statuses(this, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setCommands &request) {
+ CHECK_IS_BOT();
+ CREATE_OK_REQUEST_PROMISE();
+ set_commands(this, std::move(request.scope_), std::move(request.language_code_), std::move(request.commands_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::deleteCommands &request) {
+ CHECK_IS_BOT();
+ CREATE_OK_REQUEST_PROMISE();
+ delete_commands(this, std::move(request.scope_), std::move(request.language_code_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getCommands &request) {
+ CHECK_IS_BOT();
+ CREATE_REQUEST_PROMISE();
+ get_commands(this, std::move(request.scope_), std::move(request.language_code_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setMenuButton &request) {
+ CHECK_IS_BOT();
+ CREATE_OK_REQUEST_PROMISE();
+ set_menu_button(this, UserId(request.user_id_), std::move(request.menu_button_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getMenuButton &request) {
+ CHECK_IS_BOT();
+ CREATE_REQUEST_PROMISE();
+ get_menu_button(this, UserId(request.user_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setDefaultGroupAdministratorRights &request) {
+ CHECK_IS_BOT();
+ CREATE_OK_REQUEST_PROMISE();
+ set_default_group_administrator_rights(
+ this, AdministratorRights(request.default_group_administrator_rights_, ChannelType::Megagroup),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setDefaultChannelAdministratorRights &request) {
+ CHECK_IS_BOT();
+ CREATE_OK_REQUEST_PROMISE();
+ set_default_channel_administrator_rights(
+ this, AdministratorRights(request.default_channel_administrator_rights_, ChannelType::Broadcast),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setLocation &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_location(Location(request.location_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::setProfilePhoto &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(SetProfilePhotoRequest, std::move(request.photo_));
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_profile_photo(request.photo_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::deleteProfilePhoto &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(DeleteProfilePhotoRequest, request.profile_photo_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->delete_profile_photo(request.profile_photo_id_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getUserProfilePhotos &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetUserProfilePhotosRequest, request.user_id_, request.offset_, request.limit_);
}
-void Td::on_request(uint64 id, const td_api::toggleBasicGroupAdministrators &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::setSupergroupUsername &request) {
CHECK_IS_USER();
- CREATE_REQUEST(ToggleGroupAdministratorsRequest, request.basic_group_id_, request.everyone_is_administrator_);
+ CLEAN_INPUT_STRING(request.username_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_channel_username(ChannelId(request.supergroup_id_), request.username_, std::move(promise));
}
-void Td::on_request(uint64 id, td_api::setSupergroupUsername &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::toggleSupergroupUsernameIsActive &request) {
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.username_);
- CREATE_REQUEST(SetSupergroupUsernameRequest, request.supergroup_id_, std::move(request.username_));
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->toggle_channel_username_is_active(ChannelId(request.supergroup_id_), std::move(request.username_),
+ request.is_active_, std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::setSupergroupStickerSet &request) {
- CHECK_AUTH();
- CREATE_REQUEST(SetSupergroupStickerSetRequest, request.supergroup_id_, request.sticker_set_id_);
+void Td::on_request(uint64 id, const td_api::disableAllSupergroupUsernames &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->disable_all_channel_usernames(ChannelId(request.supergroup_id_), std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::toggleSupergroupInvites &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::reorderSupergroupActiveUsernames &request) {
CHECK_IS_USER();
- CREATE_REQUEST(ToggleSupergroupInvitesRequest, request.supergroup_id_, request.anyone_can_invite_);
+ for (auto &username : request.usernames_) {
+ CLEAN_INPUT_STRING(username);
+ }
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->reorder_channel_usernames(ChannelId(request.supergroup_id_), std::move(request.usernames_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::setSupergroupStickerSet &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->set_channel_sticker_set(ChannelId(request.supergroup_id_), StickerSetId(request.sticker_set_id_),
+ std::move(promise));
}
void Td::on_request(uint64 id, const td_api::toggleSupergroupSignMessages &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(ToggleSupergroupSignMessagesRequest, request.supergroup_id_, request.sign_messages_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->toggle_channel_sign_messages(ChannelId(request.supergroup_id_), request.sign_messages_,
+ std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailable &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::toggleSupergroupJoinToSendMessages &request) {
CHECK_IS_USER();
- CREATE_REQUEST(ToggleSupergroupIsAllHistoryAvailableRequest, request.supergroup_id_,
- request.is_all_history_available_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->toggle_channel_join_to_send(ChannelId(request.supergroup_id_), request.join_to_send_messages_,
+ std::move(promise));
}
-void Td::on_request(uint64 id, td_api::setSupergroupDescription &request) {
- CHECK_AUTH();
- CLEAN_INPUT_STRING(request.description_);
- CREATE_REQUEST(SetSupergroupDescriptionRequest, request.supergroup_id_, std::move(request.description_));
+void Td::on_request(uint64 id, const td_api::toggleSupergroupJoinByRequest &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->toggle_channel_join_request(ChannelId(request.supergroup_id_), request.join_by_request_,
+ std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::pinSupergroupMessage &request) {
- CHECK_AUTH();
- CREATE_REQUEST(PinSupergroupMessageRequest, request.supergroup_id_, request.message_id_,
- request.disable_notification_);
+void Td::on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailable &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->toggle_channel_is_all_history_available(ChannelId(request.supergroup_id_),
+ request.is_all_history_available_, std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::unpinSupergroupMessage &request) {
- CHECK_AUTH();
- CREATE_REQUEST(UnpinSupergroupMessageRequest, request.supergroup_id_);
+void Td::on_request(uint64 id, const td_api::toggleSupergroupIsForum &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->toggle_channel_is_forum(ChannelId(request.supergroup_id_), request.is_forum_, std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::reportSupergroupSpam &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::toggleSupergroupIsBroadcastGroup &request) {
CHECK_IS_USER();
- CREATE_REQUEST(ReportSupergroupSpamRequest, request.supergroup_id_, request.user_id_, request.message_ids_);
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->convert_channel_to_gigagroup(ChannelId(request.supergroup_id_), std::move(promise));
}
-void Td::on_request(uint64 id, td_api::getSupergroupMembers &request) {
- CHECK_AUTH();
- CREATE_REQUEST(GetSupergroupMembersRequest, request.supergroup_id_, std::move(request.filter_), request.offset_,
- request.limit_);
+void Td::on_request(uint64 id, const td_api::reportSupergroupSpam &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ contacts_manager_->report_channel_spam(ChannelId(request.supergroup_id_),
+ MessagesManager::get_message_ids(request.message_ids_), std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::deleteSupergroup &request) {
- CHECK_AUTH();
- CHECK_IS_USER();
- CREATE_REQUEST(DeleteSupergroupRequest, request.supergroup_id_);
+void Td::on_request(uint64 id, td_api::getSupergroupMembers &request) {
+ CREATE_REQUEST_PROMISE();
+ auto query_promise =
+ PromiseCreator::lambda([promise = std::move(promise), td = this](Result<DialogParticipants> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(result.ok().get_chat_members_object(td));
+ }
+ });
+ contacts_manager_->get_channel_participants(ChannelId(request.supergroup_id_), std::move(request.filter_), string(),
+ request.offset_, request.limit_, -1, std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::closeSecretChat &request) {
- CHECK_AUTH();
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
- if (result.is_error()) {
- promise.set_error(result.move_as_error());
- } else {
- promise.set_value(make_tl_object<td_api::ok>());
- }
- });
- send_closure(secret_chats_manager_, &SecretChatsManager::cancel_chat, SecretChatId(request.secret_chat_id_),
- std::move(query_promise));
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(secret_chats_manager_, &SecretChatsManager::cancel_chat, SecretChatId(request.secret_chat_id_), false,
+ std::move(promise));
}
void Td::on_request(uint64 id, td_api::getStickers &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CLEAN_INPUT_STRING(request.emoji_);
- CREATE_REQUEST(GetStickersRequest, std::move(request.emoji_), request.limit_);
+ CLEAN_INPUT_STRING(request.query_);
+ CREATE_REQUEST(GetStickersRequest, get_sticker_type(request.sticker_type_), std::move(request.query_), request.limit_,
+ request.chat_id_);
}
void Td::on_request(uint64 id, td_api::searchStickers &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.emoji_);
- CREATE_REQUEST(SearchStickersRequest, std::move(request.emoji_), request.limit_);
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->search_stickers(std::move(request.emoji_), request.limit_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getPremiumStickers &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->get_premium_stickers(request.limit_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getInstalledStickerSets &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(GetInstalledStickerSetsRequest, request.is_masks_);
+ CREATE_REQUEST(GetInstalledStickerSetsRequest, get_sticker_type(request.sticker_type_));
}
void Td::on_request(uint64 id, const td_api::getArchivedStickerSets &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(GetArchivedStickerSetsRequest, request.is_masks_, request.offset_sticker_set_id_, request.limit_);
+ CREATE_REQUEST(GetArchivedStickerSetsRequest, get_sticker_type(request.sticker_type_), request.offset_sticker_set_id_,
+ request.limit_);
}
void Td::on_request(uint64 id, const td_api::getTrendingStickerSets &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(GetTrendingStickerSetsRequest);
+ CREATE_REQUEST(GetTrendingStickerSetsRequest, get_sticker_type(request.sticker_type_), request.offset_,
+ request.limit_);
}
void Td::on_request(uint64 id, const td_api::getAttachedStickerSets &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(GetAttachedStickerSetsRequest, request.file_id_);
}
void Td::on_request(uint64 id, const td_api::getStickerSet &request) {
- CHECK_AUTH();
CREATE_REQUEST(GetStickerSetRequest, request.set_id_);
}
void Td::on_request(uint64 id, td_api::searchStickerSet &request) {
- CHECK_AUTH();
CLEAN_INPUT_STRING(request.name_);
CREATE_REQUEST(SearchStickerSetRequest, std::move(request.name_));
}
void Td::on_request(uint64 id, td_api::searchInstalledStickerSets &request) {
- CHECK_AUTH();
CLEAN_INPUT_STRING(request.query_);
- CREATE_REQUEST(SearchInstalledStickerSetsRequest, request.is_masks_, std::move(request.query_), request.limit_);
+ CREATE_REQUEST(SearchInstalledStickerSetsRequest, get_sticker_type(request.sticker_type_), std::move(request.query_),
+ request.limit_);
}
void Td::on_request(uint64 id, td_api::searchStickerSets &request) {
- CHECK_AUTH();
CLEAN_INPUT_STRING(request.query_);
CREATE_REQUEST(SearchStickerSetsRequest, std::move(request.query_));
}
void Td::on_request(uint64 id, const td_api::changeStickerSet &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(ChangeStickerSetRequest, request.set_id_, request.is_installed_, request.is_archived_);
}
void Td::on_request(uint64 id, const td_api::viewTrendingStickerSets &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- stickers_manager_->view_featured_sticker_sets(request.sticker_set_ids_);
+ stickers_manager_->view_featured_sticker_sets(StickersManager::convert_sticker_set_ids(request.sticker_set_ids_));
send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
}
void Td::on_request(uint64 id, td_api::reorderInstalledStickerSets &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(ReorderInstalledStickerSetsRequest, request.is_masks_, std::move(request.sticker_set_ids_));
+ CREATE_OK_REQUEST_PROMISE();
+ stickers_manager_->reorder_installed_sticker_sets(get_sticker_type(request.sticker_type_),
+ StickersManager::convert_sticker_set_ids(request.sticker_set_ids_),
+ std::move(promise));
}
void Td::on_request(uint64 id, td_api::uploadStickerFile &request) {
- CHECK_AUTH();
- CHECK_IS_BOT();
- CREATE_REQUEST(UploadStickerFileRequest, request.user_id_, std::move(request.png_sticker_));
+ CREATE_REQUEST(UploadStickerFileRequest, request.user_id_, std::move(request.sticker_));
+}
+
+void Td::on_request(uint64 id, td_api::getSuggestedStickerSetName &request) {
+ CLEAN_INPUT_STRING(request.title_);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(make_tl_object<td_api::text>(result.move_as_ok()));
+ }
+ });
+ stickers_manager_->get_suggested_sticker_set_name(std::move(request.title_), std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, td_api::checkStickerSetName &request) {
+ CLEAN_INPUT_STRING(request.name_);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda(
+ [promise = std::move(promise)](Result<StickersManager::CheckStickerSetNameResult> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(StickersManager::get_check_sticker_set_name_result_object(result.ok()));
+ }
+ });
+ stickers_manager_->check_sticker_set_name(request.name_, std::move(query_promise));
}
void Td::on_request(uint64 id, td_api::createNewStickerSet &request) {
- CHECK_AUTH();
- CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.title_);
CLEAN_INPUT_STRING(request.name_);
- CREATE_REQUEST(CreateNewStickerSetRequest, request.user_id_, std::move(request.title_), std::move(request.name_),
- request.is_masks_, std::move(request.stickers_));
+ CLEAN_INPUT_STRING(request.source_);
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->create_new_sticker_set(UserId(request.user_id_), std::move(request.title_),
+ std::move(request.name_), get_sticker_type(request.sticker_type_),
+ std::move(request.stickers_), std::move(request.source_),
+ std::move(promise));
}
void Td::on_request(uint64 id, td_api::addStickerToSet &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.name_);
- CREATE_REQUEST(AddStickerToSetRequest, request.user_id_, std::move(request.name_), std::move(request.sticker_));
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->add_sticker_to_set(UserId(request.user_id_), std::move(request.name_), std::move(request.sticker_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setStickerSetThumbnail &request) {
+ CHECK_IS_BOT();
+ CLEAN_INPUT_STRING(request.name_);
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->set_sticker_set_thumbnail(UserId(request.user_id_), std::move(request.name_),
+ std::move(request.thumbnail_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::setStickerPositionInSet &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
- CREATE_REQUEST(SetStickerPositionInSetRequest, std::move(request.sticker_), request.position_);
+ CREATE_OK_REQUEST_PROMISE();
+ stickers_manager_->set_sticker_position_in_set(request.sticker_, request.position_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::removeStickerFromSet &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
- CREATE_REQUEST(RemoveStickerFromSetRequest, std::move(request.sticker_));
+ CREATE_OK_REQUEST_PROMISE();
+ stickers_manager_->remove_sticker_from_set(request.sticker_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getRecentStickers &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(GetRecentStickersRequest, request.is_attached_);
}
void Td::on_request(uint64 id, td_api::addRecentSticker &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(AddRecentStickerRequest, request.is_attached_, std::move(request.sticker_));
}
void Td::on_request(uint64 id, td_api::removeRecentSticker &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(RemoveRecentStickerRequest, request.is_attached_, std::move(request.sticker_));
}
void Td::on_request(uint64 id, td_api::clearRecentStickers &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(ClearRecentStickersRequest, request.is_attached_);
}
void Td::on_request(uint64 id, const td_api::getFavoriteStickers &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_NO_ARGS_REQUEST(GetFavoriteStickersRequest);
}
void Td::on_request(uint64 id, td_api::addFavoriteSticker &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(AddFavoriteStickerRequest, std::move(request.sticker_));
}
void Td::on_request(uint64 id, td_api::removeFavoriteSticker &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(RemoveFavoriteStickerRequest, std::move(request.sticker_));
}
void Td::on_request(uint64 id, td_api::getStickerEmojis &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(GetStickerEmojisRequest, std::move(request.sticker_));
}
+void Td::on_request(uint64 id, td_api::searchEmojis &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.text_);
+ for (auto &input_language_code : request.input_language_codes_) {
+ CLEAN_INPUT_STRING(input_language_code);
+ }
+ CREATE_REQUEST(SearchEmojisRequest, std::move(request.text_), request.exact_match_,
+ std::move(request.input_language_codes_));
+}
+
+void Td::on_request(uint64 id, td_api::getAnimatedEmoji &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.emoji_);
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->get_animated_emoji(std::move(request.emoji_), false, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getEmojiSuggestionsUrl &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.language_code_);
+ CREATE_REQUEST(GetEmojiSuggestionsUrlRequest, std::move(request.language_code_));
+}
+
+void Td::on_request(uint64 id, const td_api::getCustomEmojiStickers &request) {
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->get_custom_emoji_stickers(
+ transform(request.custom_emoji_ids_, [](int64 custom_emoji_id) { return CustomEmojiId(custom_emoji_id); }), true,
+ std::move(promise));
+}
+
void Td::on_request(uint64 id, const td_api::getSavedAnimations &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_NO_ARGS_REQUEST(GetSavedAnimationsRequest);
}
void Td::on_request(uint64 id, td_api::addSavedAnimation &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(AddSavedAnimationRequest, std::move(request.animation_));
}
void Td::on_request(uint64 id, td_api::removeSavedAnimation &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CREATE_REQUEST(RemoveSavedAnimationRequest, std::move(request.animation_));
}
-void Td::on_request(uint64 id, const td_api::getNotificationSettings &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::getSavedNotificationSound &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST(GetSavedNotificationSoundRequest, request.notification_sound_id_);
+}
+
+void Td::on_request(uint64 id, const td_api::getSavedNotificationSounds &request) {
CHECK_IS_USER();
- CREATE_REQUEST(GetNotificationSettingsRequest, messages_manager_->get_notification_settings_scope(request.scope_));
+ CREATE_NO_ARGS_REQUEST(GetSavedNotificationSoundsRequest);
}
-void Td::on_request(uint64 id, const td_api::getChatReportSpamState &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::addSavedNotificationSound &request) {
CHECK_IS_USER();
- CREATE_REQUEST(GetChatReportSpamStateRequest, request.chat_id_);
+ CREATE_REQUEST_PROMISE();
+ notification_settings_manager_->add_saved_ringtone(std::move(request.sound_), std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::changeChatReportSpamState &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::removeSavedNotificationSound &request) {
CHECK_IS_USER();
- CREATE_REQUEST(ChangeChatReportSpamStateRequest, request.chat_id_, request.is_spam_chat_);
+ CREATE_REQUEST(RemoveSavedNotificationSoundRequest, request.notification_sound_id_);
+}
+
+void Td::on_request(uint64 id, const td_api::getChatNotificationSettingsExceptions &request) {
+ CHECK_IS_USER();
+ bool filter_scope = false;
+ NotificationSettingsScope scope = NotificationSettingsScope::Private;
+ if (request.scope_ != nullptr) {
+ filter_scope = true;
+ scope = get_notification_settings_scope(request.scope_);
+ }
+ CREATE_REQUEST(GetChatNotificationSettingsExceptionsRequest, scope, filter_scope, request.compare_sound_);
+}
+
+void Td::on_request(uint64 id, const td_api::getScopeNotificationSettings &request) {
+ CHECK_IS_USER();
+ if (request.scope_ == nullptr) {
+ return send_error_raw(id, 400, "Scope must be non-empty");
+ }
+ CREATE_REQUEST(GetScopeNotificationSettingsRequest, get_notification_settings_scope(request.scope_));
+}
+
+void Td::on_request(uint64 id, const td_api::removeChatActionBar &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->remove_dialog_action_bar(DialogId(request.chat_id_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::reportChat &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(ReportChatRequest, request.chat_id_, std::move(request.reason_), request.message_ids_);
+ auto r_report_reason = ReportReason::get_report_reason(std::move(request.reason_), std::move(request.text_));
+ if (r_report_reason.is_error()) {
+ return send_error_raw(id, r_report_reason.error().code(), r_report_reason.error().message());
+ }
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->report_dialog(DialogId(request.chat_id_), MessagesManager::get_message_ids(request.message_ids_),
+ r_report_reason.move_as_ok(), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::reportChatPhoto &request) {
+ CHECK_IS_USER();
+ auto r_report_reason = ReportReason::get_report_reason(std::move(request.reason_), std::move(request.text_));
+ if (r_report_reason.is_error()) {
+ return send_error_raw(id, r_report_reason.error().code(), r_report_reason.error().message());
+ }
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->report_dialog_photo(DialogId(request.chat_id_), FileId(request.file_id_, 0),
+ r_report_reason.move_as_ok(), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::reportMessageReactions &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ TRY_RESULT_PROMISE(promise, sender_dialog_id, get_message_sender_dialog_id(this, request.sender_id_, false, false));
+ report_message_reactions(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, sender_dialog_id,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getChatStatistics &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_channel_statistics(DialogId(request.chat_id_), request.is_dark_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getMessageStatistics &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_channel_message_statistics({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ request.is_dark_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getStatisticalGraph &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.token_);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->load_statistics_graph(DialogId(request.chat_id_), std::move(request.token_), request.x_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setChatNotificationSettings &request) {
+ CHECK_IS_USER();
+ answer_ok_query(id, messages_manager_->set_dialog_notification_settings(DialogId(request.chat_id_),
+ std::move(request.notification_settings_)));
}
-void Td::on_request(uint64 id, td_api::setNotificationSettings &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::setScopeNotificationSettings &request) {
CHECK_IS_USER();
- CLEAN_INPUT_STRING(request.notification_settings_->sound_);
- answer_ok_query(id, messages_manager_->set_notification_settings(
- messages_manager_->get_notification_settings_scope(request.scope_),
- std::move(request.notification_settings_)));
+ if (request.scope_ == nullptr) {
+ return send_error_raw(id, 400, "Scope must be non-empty");
+ }
+ answer_ok_query(id, notification_settings_manager_->set_scope_notification_settings(
+ get_notification_settings_scope(request.scope_), std::move(request.notification_settings_)));
}
void Td::on_request(uint64 id, const td_api::resetAllNotificationSettings &request) {
- CHECK_AUTH();
CHECK_IS_USER();
messages_manager_->reset_all_notification_settings();
send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
}
-void Td::on_request(uint64 id, td_api::getOption &request) {
- CLEAN_INPUT_STRING(request.name_);
+void Td::on_request(uint64 id, const td_api::getMapThumbnailFile &request) {
+ DialogId dialog_id(request.chat_id_);
+ if (!messages_manager_->have_dialog_force(dialog_id, "getMapThumbnailFile")) {
+ dialog_id = DialogId();
+ }
- tl_object_ptr<td_api::OptionValue> option_value;
- switch (request.name_[0]) {
- case 'o':
- if (request.name_ == "online") {
- option_value = make_tl_object<td_api::optionValueBoolean>(is_online_);
- }
- break;
- case 'v':
- if (request.name_ == "version") {
- option_value = make_tl_object<td_api::optionValueString>(TDLIB_VERSION);
- }
- break;
+ auto r_file_id = file_manager_->get_map_thumbnail_file_id(Location(request.location_), request.zoom_, request.width_,
+ request.height_, request.scale_, dialog_id);
+ if (r_file_id.is_error()) {
+ send_closure(actor_id(this), &Td::send_error, id, r_file_id.move_as_error());
+ } else {
+ send_closure(actor_id(this), &Td::send_result, id, file_manager_->get_file_object(r_file_id.ok()));
}
- if (option_value == nullptr) {
- option_value = G()->shared_config().get_option_value(request.name_);
+}
+
+void Td::on_request(uint64 id, const td_api::getLocalizationTargetInfo &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ send_closure(language_pack_manager_, &LanguagePackManager::get_languages, request.only_local_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getLanguagePackInfo &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.language_pack_id_);
+ CREATE_REQUEST_PROMISE();
+ send_closure(language_pack_manager_, &LanguagePackManager::search_language_info, request.language_pack_id_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getLanguagePackStrings &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.language_pack_id_);
+ for (auto &key : request.keys_) {
+ CLEAN_INPUT_STRING(key);
}
- send_closure(actor_id(this), &Td::send_result, id, std::move(option_value));
+ CREATE_REQUEST_PROMISE();
+ send_closure(language_pack_manager_, &LanguagePackManager::get_language_pack_strings,
+ std::move(request.language_pack_id_), std::move(request.keys_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::synchronizeLanguagePack &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.language_pack_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(language_pack_manager_, &LanguagePackManager::synchronize_language_pack,
+ std::move(request.language_pack_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::addCustomServerLanguagePack &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.language_pack_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(language_pack_manager_, &LanguagePackManager::add_custom_server_language,
+ std::move(request.language_pack_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setCustomLanguagePack &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(language_pack_manager_, &LanguagePackManager::set_custom_language, std::move(request.info_),
+ std::move(request.strings_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::editCustomLanguagePackInfo &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(language_pack_manager_, &LanguagePackManager::edit_custom_language_info, std::move(request.info_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setCustomLanguagePackString &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.language_pack_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(language_pack_manager_, &LanguagePackManager::set_custom_language_string,
+ std::move(request.language_pack_id_), std::move(request.new_string_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::deleteLanguagePack &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.language_pack_id_);
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(language_pack_manager_, &LanguagePackManager::delete_language, std::move(request.language_pack_id_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getOption &request) {
+ CLEAN_INPUT_STRING(request.name_);
+ CREATE_REQUEST_PROMISE();
+ option_manager_->get_option(request.name_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::setOption &request) {
CLEAN_INPUT_STRING(request.name_);
- int32 value_constructor_id = request.value_ == nullptr ? td_api::optionValueEmpty::ID : request.value_->get_id();
+ CREATE_OK_REQUEST_PROMISE();
+ option_manager_->set_option(request.name_, std::move(request.value_), std::move(promise));
+}
- auto set_integer_option = [&](Slice name, int32 min = 0, int32 max = std::numeric_limits<int32>::max()) {
- if (request.name_ == name) {
- if (value_constructor_id != td_api::optionValueInteger::ID &&
- value_constructor_id != td_api::optionValueEmpty::ID) {
- send_error_raw(id, 3, PSLICE() << "Option \"" << name << "\" must have integer value");
- return true;
- }
- if (value_constructor_id == td_api::optionValueEmpty::ID) {
- G()->shared_config().set_option_empty(name);
- } else {
- int32 value = static_cast<td_api::optionValueInteger *>(request.value_.get())->value_;
- if (value < min || value > max) {
- send_error_raw(id, 3,
- PSLICE() << "Option's \"" << name << "\" value " << value << " is outside of a valid range ["
- << min << ", " << max << "]");
- return true;
+void Td::on_request(uint64 id, td_api::setPollAnswer &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->set_poll_answer({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ std::move(request.option_ids_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getPollVoters &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda(
+ [promise = std::move(promise), td = this](Result<std::pair<int32, vector<UserId>>> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(td->contacts_manager_->get_users_object(result.ok().first, result.ok().second));
}
- G()->shared_config().set_option_integer(name, clamp(value, min, max));
- }
- send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
- return true;
- }
- return false;
- };
+ });
+ messages_manager_->get_poll_voters({DialogId(request.chat_id_), MessageId(request.message_id_)}, request.option_id_,
+ request.offset_, request.limit_, std::move(query_promise));
+}
- auto set_boolean_option = [&](Slice name) {
- if (request.name_ == name) {
- if (value_constructor_id != td_api::optionValueBoolean::ID &&
- value_constructor_id != td_api::optionValueEmpty::ID) {
- send_error_raw(id, 3, PSLICE() << "Option \"" << name << "\" must have boolean value");
- return true;
- }
- if (value_constructor_id == td_api::optionValueEmpty::ID) {
- G()->shared_config().set_option_empty(name);
- } else {
- bool value = static_cast<td_api::optionValueBoolean *>(request.value_.get())->value_;
- G()->shared_config().set_option_boolean(name, value);
- }
- send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
- return true;
- }
- return false;
- };
+void Td::on_request(uint64 id, td_api::stopPoll &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ messages_manager_->stop_poll({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ std::move(request.reply_markup_), std::move(promise));
+}
- switch (request.name_[0]) {
- case 'd':
- if (set_boolean_option("disable_contact_registered_notifications")) {
- return;
- }
- break;
- case 'o':
- if (request.name_ == "online") {
- if (value_constructor_id != td_api::optionValueBoolean::ID &&
- value_constructor_id != td_api::optionValueEmpty::ID) {
- return send_error_raw(id, 3, "Option \"online\" must have boolean value");
- }
- bool is_online = value_constructor_id == td_api::optionValueEmpty::ID ||
- static_cast<const td_api::optionValueBoolean *>(request.value_.get())->value_;
- if (!auth_manager_->is_bot()) {
- send_closure(G()->state_manager(), &StateManager::on_online, is_online);
- }
- if (is_online != is_online_) {
- is_online_ = is_online;
- on_online_updated(true, true);
- }
- return send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
- }
- break;
- case 's':
- if (set_integer_option("session_count", 0, 50)) {
- return;
- }
- if (set_integer_option("storage_max_files_size")) {
- return;
- }
- if (set_integer_option("storage_max_time_from_last_access")) {
- return;
- }
- if (set_integer_option("storage_max_file_count")) {
- return;
- }
- if (set_integer_option("storage_immunity_delay")) {
- return;
- }
- break;
- case 'X':
- case 'x': {
- if (request.name_.size() > 255) {
- return send_error_raw(id, 3, "Option name is too long");
- }
- switch (value_constructor_id) {
- case td_api::optionValueBoolean::ID:
- G()->shared_config().set_option_boolean(
- request.name_, static_cast<const td_api::optionValueBoolean *>(request.value_.get())->value_);
- break;
- case td_api::optionValueEmpty::ID:
- G()->shared_config().set_option_empty(request.name_);
- break;
- case td_api::optionValueInteger::ID:
- G()->shared_config().set_option_integer(
- request.name_, static_cast<const td_api::optionValueInteger *>(request.value_.get())->value_);
- break;
- case td_api::optionValueString::ID:
- G()->shared_config().set_option_string(
- request.name_, static_cast<const td_api::optionValueString *>(request.value_.get())->value_);
- break;
- default:
- UNREACHABLE();
- }
- return send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
- }
- case 'u':
- if (set_boolean_option("use_pfs")) {
- return;
- }
- if (set_boolean_option("use_quick_ack")) {
- return;
- }
- if (set_boolean_option("use_storage_optimizer")) {
- return;
- }
- break;
- }
+void Td::on_request(uint64 id, const td_api::hideSuggestedAction &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ dismiss_suggested_action(SuggestedAction(request.action_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getLoginUrlInfo &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ link_manager_->get_login_url_info({DialogId(request.chat_id_), MessageId(request.message_id_)}, request.button_id_,
+ std::move(promise));
+}
- return send_error_raw(id, 3, "Option can't be set");
+void Td::on_request(uint64 id, const td_api::getLoginUrl &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ link_manager_->get_login_url({DialogId(request.chat_id_), MessageId(request.message_id_)}, request.button_id_,
+ request.allow_write_access_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::getInlineQueryResults &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.query_);
CLEAN_INPUT_STRING(request.offset_);
@@ -6695,116 +7511,391 @@ void Td::on_request(uint64 id, td_api::getInlineQueryResults &request) {
}
void Td::on_request(uint64 id, td_api::answerInlineQuery &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.next_offset_);
CLEAN_INPUT_STRING(request.switch_pm_text_);
CLEAN_INPUT_STRING(request.switch_pm_parameter_);
- CREATE_REQUEST(AnswerInlineQueryRequest, request.inline_query_id_, request.is_personal_, std::move(request.results_),
- request.cache_time_, std::move(request.next_offset_), std::move(request.switch_pm_text_),
- std::move(request.switch_pm_parameter_));
+ CREATE_OK_REQUEST_PROMISE();
+ inline_queries_manager_->answer_inline_query(
+ request.inline_query_id_, request.is_personal_, std::move(request.results_), request.cache_time_,
+ request.next_offset_, request.switch_pm_text_, request.switch_pm_parameter_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getWebAppUrl &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.url_);
+ CLEAN_INPUT_STRING(request.application_name_);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(td_api::make_object<td_api::httpUrl>(result.move_as_ok()));
+ }
+ });
+ inline_queries_manager_->get_simple_web_view_url(UserId(request.bot_user_id_), std::move(request.url_),
+ std::move(request.theme_), std::move(request.application_name_),
+ std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, td_api::sendWebAppData &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.button_text_);
+ CLEAN_INPUT_STRING(request.data_);
+ CREATE_OK_REQUEST_PROMISE();
+ inline_queries_manager_->send_web_view_data(UserId(request.bot_user_id_), std::move(request.button_text_),
+ std::move(request.data_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::openWebApp &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.url_);
+ CLEAN_INPUT_STRING(request.application_name_);
+ CREATE_REQUEST_PROMISE();
+ attach_menu_manager_->request_web_view(DialogId(request.chat_id_), UserId(request.bot_user_id_),
+ MessageId(request.message_thread_id_), MessageId(request.reply_to_message_id_),
+ std::move(request.url_), std::move(request.theme_),
+ std::move(request.application_name_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::closeWebApp &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ attach_menu_manager_->close_web_view(request.web_app_launch_id_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::answerWebAppQuery &request) {
+ CHECK_IS_BOT();
+ CLEAN_INPUT_STRING(request.web_app_query_id_);
+ CREATE_REQUEST_PROMISE();
+ inline_queries_manager_->answer_web_view_query(request.web_app_query_id_, std::move(request.result_),
+ std::move(promise));
}
void Td::on_request(uint64 id, td_api::getCallbackQueryAnswer &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(GetCallbackQueryAnswerRequest, request.chat_id_, request.message_id_, std::move(request.payload_));
+ CREATE_REQUEST_PROMISE();
+ callback_queries_manager_->send_callback_query({DialogId(request.chat_id_), MessageId(request.message_id_)},
+ std::move(request.payload_), std::move(promise));
}
void Td::on_request(uint64 id, td_api::answerCallbackQuery &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.text_);
CLEAN_INPUT_STRING(request.url_);
- CREATE_REQUEST(AnswerCallbackQueryRequest, request.callback_query_id_, std::move(request.text_), request.show_alert_,
- std::move(request.url_), request.cache_time_);
+ CREATE_OK_REQUEST_PROMISE();
+ callback_queries_manager_->answer_callback_query(request.callback_query_id_, request.text_, request.show_alert_,
+ request.url_, request.cache_time_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::answerShippingQuery &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.error_message_);
- CREATE_REQUEST(AnswerShippingQueryRequest, request.shipping_query_id_, std::move(request.shipping_options_),
- std::move(request.error_message_));
+ CREATE_OK_REQUEST_PROMISE();
+ answer_shipping_query(this, request.shipping_query_id_, std::move(request.shipping_options_), request.error_message_,
+ std::move(promise));
}
void Td::on_request(uint64 id, td_api::answerPreCheckoutQuery &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.error_message_);
- CREATE_REQUEST(AnswerPreCheckoutQueryRequest, request.pre_checkout_query_id_, std::move(request.error_message_));
+ CREATE_OK_REQUEST_PROMISE();
+ answer_pre_checkout_query(this, request.pre_checkout_query_id_, request.error_message_, std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::getPaymentForm &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::getBankCardInfo &request) {
CHECK_IS_USER();
- CREATE_REQUEST(GetPaymentFormRequest, request.chat_id_, request.message_id_);
+ CLEAN_INPUT_STRING(request.bank_card_number_);
+ CREATE_REQUEST_PROMISE();
+ get_bank_card_info(this, request.bank_card_number_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getPaymentForm &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ get_payment_form(this, std::move(request.input_invoice_), request.theme_, std::move(promise));
}
void Td::on_request(uint64 id, td_api::validateOrderInfo &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(ValidateOrderInfoRequest, request.chat_id_, request.message_id_, std::move(request.order_info_),
- request.allow_save_);
+ CREATE_REQUEST_PROMISE();
+ validate_order_info(this, std::move(request.input_invoice_), std::move(request.order_info_), request.allow_save_,
+ std::move(promise));
}
void Td::on_request(uint64 id, td_api::sendPaymentForm &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.order_info_id_);
CLEAN_INPUT_STRING(request.shipping_option_id_);
- if (request.credentials_ == nullptr) {
- return send_error_raw(id, 400, "Input payments credentials must not be empty");
- }
- CREATE_REQUEST(SendPaymentFormRequest, request.chat_id_, request.message_id_, std::move(request.order_info_id_),
- std::move(request.shipping_option_id_), std::move(request.credentials_));
+ CREATE_REQUEST_PROMISE();
+ send_payment_form(this, std::move(request.input_invoice_), request.payment_form_id_, request.order_info_id_,
+ request.shipping_option_id_, request.credentials_, request.tip_amount_, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getPaymentReceipt &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_REQUEST(GetPaymentReceiptRequest, request.chat_id_, request.message_id_);
+ CREATE_REQUEST_PROMISE();
+ get_payment_receipt(this, {DialogId(request.chat_id_), MessageId(request.message_id_)}, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getSavedOrderInfo &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(GetSavedOrderInfoRequest);
+ CREATE_REQUEST_PROMISE();
+ get_saved_order_info(this, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::deleteSavedOrderInfo &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(DeleteSavedOrderInfoRequest);
+ CREATE_OK_REQUEST_PROMISE();
+ delete_saved_order_info(this, std::move(promise));
}
void Td::on_request(uint64 id, const td_api::deleteSavedCredentials &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(DeleteSavedCredentialsRequest);
+ CREATE_OK_REQUEST_PROMISE();
+ delete_saved_credentials(this, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::createInvoiceLink &request) {
+ CHECK_IS_BOT();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(td_api::make_object<td_api::httpUrl>(result.move_as_ok()));
+ }
+ });
+ export_invoice(this, std::move(request.invoice_), std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, td_api::getPassportElement &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.password_);
+ if (request.type_ == nullptr) {
+ return send_error_raw(id, 400, "Type must be non-empty");
+ }
+ CREATE_REQUEST_PROMISE();
+ send_closure(secure_manager_, &SecureManager::get_secure_value, std::move(request.password_),
+ get_secure_value_type_td_api(request.type_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getAllPassportElements &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.password_);
+ CREATE_REQUEST_PROMISE();
+ send_closure(secure_manager_, &SecureManager::get_all_secure_values, std::move(request.password_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setPassportElement &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.password_);
+ auto r_secure_value = get_secure_value(file_manager_.get(), std::move(request.element_));
+ if (r_secure_value.is_error()) {
+ return send_error_raw(id, 400, r_secure_value.error().message());
+ }
+ CREATE_REQUEST_PROMISE();
+ send_closure(secure_manager_, &SecureManager::set_secure_value, std::move(request.password_),
+ r_secure_value.move_as_ok(), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::deletePassportElement &request) {
+ CHECK_IS_USER();
+ if (request.type_ == nullptr) {
+ return send_error_raw(id, 400, "Type must be non-empty");
+ }
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(secure_manager_, &SecureManager::delete_secure_value, get_secure_value_type_td_api(request.type_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setPassportElementErrors &request) {
+ CHECK_IS_BOT();
+ auto r_input_user = contacts_manager_->get_input_user(UserId(request.user_id_));
+ if (r_input_user.is_error()) {
+ return send_error_raw(id, r_input_user.error().code(), r_input_user.error().message());
+ }
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(secure_manager_, &SecureManager::set_secure_value_errors, this, r_input_user.move_as_ok(),
+ std::move(request.errors_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getPreferredCountryLanguage &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.country_code_);
+ CREATE_REQUEST_PROMISE();
+ send_closure(secure_manager_, &SecureManager::get_preferred_country_language, std::move(request.country_code_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::sendPhoneNumberVerificationCode &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.phone_number_);
+ send_closure(verify_phone_number_manager_, &PhoneNumberManager::set_phone_number, id,
+ std::move(request.phone_number_), std::move(request.settings_));
+}
+
+void Td::on_request(uint64 id, const td_api::resendPhoneNumberVerificationCode &request) {
+ CHECK_IS_USER();
+ send_closure(verify_phone_number_manager_, &PhoneNumberManager::resend_authentication_code, id);
+}
+
+void Td::on_request(uint64 id, td_api::checkPhoneNumberVerificationCode &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.code_);
+ send_closure(verify_phone_number_manager_, &PhoneNumberManager::check_code, id, std::move(request.code_));
+}
+
+void Td::on_request(uint64 id, td_api::sendEmailAddressVerificationCode &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.email_address_);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<SentEmailCode> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(result.ok().get_email_address_authentication_code_info_object());
+ }
+ });
+ send_closure(password_manager_, &PasswordManager::send_email_address_verification_code,
+ std::move(request.email_address_), std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, const td_api::resendEmailAddressVerificationCode &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<SentEmailCode> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(result.ok().get_email_address_authentication_code_info_object());
+ }
+ });
+ send_closure(password_manager_, &PasswordManager::resend_email_address_verification_code, std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, td_api::checkEmailAddressVerificationCode &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.code_);
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(password_manager_, &PasswordManager::check_email_address_verification_code, std::move(request.code_),
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getPassportAuthorizationForm &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.public_key_);
+ CLEAN_INPUT_STRING(request.scope_);
+ CLEAN_INPUT_STRING(request.nonce_);
+ UserId bot_user_id(request.bot_user_id_);
+ if (!bot_user_id.is_valid()) {
+ return send_error_raw(id, 400, "Bot user identifier invalid");
+ }
+ if (request.nonce_.empty()) {
+ return send_error_raw(id, 400, "Nonce must be non-empty");
+ }
+ CREATE_REQUEST_PROMISE();
+ send_closure(secure_manager_, &SecureManager::get_passport_authorization_form, bot_user_id, std::move(request.scope_),
+ std::move(request.public_key_), std::move(request.nonce_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getPassportAuthorizationFormAvailableElements &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.password_);
+ CREATE_REQUEST_PROMISE();
+ send_closure(secure_manager_, &SecureManager::get_passport_authorization_form_available_elements,
+ request.autorization_form_id_, std::move(request.password_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::sendPassportAuthorizationForm &request) {
+ CHECK_IS_USER();
+ for (auto &type : request.types_) {
+ if (type == nullptr) {
+ return send_error_raw(id, 400, "Type must be non-empty");
+ }
+ }
+
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(secure_manager_, &SecureManager::send_passport_authorization_form, request.autorization_form_id_,
+ get_secure_value_types_td_api(request.types_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::sendPhoneNumberConfirmationCode &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.phone_number_);
+ CLEAN_INPUT_STRING(request.hash_);
+ send_closure(confirm_phone_number_manager_, &PhoneNumberManager::set_phone_number_and_hash, id,
+ std::move(request.hash_), std::move(request.phone_number_), std::move(request.settings_));
+}
+
+void Td::on_request(uint64 id, const td_api::resendPhoneNumberConfirmationCode &request) {
+ CHECK_IS_USER();
+ send_closure(confirm_phone_number_manager_, &PhoneNumberManager::resend_authentication_code, id);
+}
+
+void Td::on_request(uint64 id, td_api::checkPhoneNumberConfirmationCode &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.code_);
+ send_closure(confirm_phone_number_manager_, &PhoneNumberManager::check_code, id, std::move(request.code_));
}
void Td::on_request(uint64 id, const td_api::getSupportUser &request) {
- CHECK_AUTH();
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(GetSupportUserRequest);
+ CREATE_REQUEST_PROMISE();
+ contacts_manager_->get_support_user(std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getBackgrounds &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ background_manager_->get_backgrounds(request.for_dark_theme_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::getBackgroundUrl &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.name_);
+ Result<string> r_url = LinkManager::get_background_url(request.name_, std::move(request.type_));
+ if (r_url.is_error()) {
+ return send_closure(actor_id(this), &Td::send_error, id, r_url.move_as_error());
+ }
+
+ send_closure(actor_id(this), &Td::send_result, id, td_api::make_object<td_api::httpUrl>(r_url.ok()));
+}
+
+void Td::on_request(uint64 id, td_api::searchBackground &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.name_);
+ CREATE_REQUEST(SearchBackgroundRequest, std::move(request.name_));
+}
+
+void Td::on_request(uint64 id, td_api::setBackground &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ background_manager_->set_background(request.background_.get(), request.type_.get(), request.for_dark_theme_,
+ std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::removeBackground &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ background_manager_->remove_background(BackgroundId(request.background_id_), std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::getWallpapers &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::resetBackgrounds &request) {
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(GetWallpapersRequest);
+ CREATE_OK_REQUEST_PROMISE();
+ background_manager_->reset_backgrounds(std::move(promise));
}
void Td::on_request(uint64 id, td_api::getRecentlyVisitedTMeUrls &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.referrer_);
- CREATE_REQUEST(GetRecentlyVisitedTMeUrlsRequest, std::move(request.referrer_));
+ CREATE_REQUEST_PROMISE();
+ create_handler<GetRecentMeUrlsQuery>(std::move(promise))->send(request.referrer_);
}
void Td::on_request(uint64 id, td_api::setBotUpdatesStatus &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.error_message_);
create_handler<SetBotUpdatesStatusQuery>()->send(request.pending_update_count_, request.error_message_);
@@ -6812,18 +7903,18 @@ void Td::on_request(uint64 id, td_api::setBotUpdatesStatus &request) {
}
void Td::on_request(uint64 id, td_api::sendCustomRequest &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.method_);
CLEAN_INPUT_STRING(request.parameters_);
- CREATE_REQUEST(SendCustomRequestRequest, std::move(request.method_), std::move(request.parameters_));
+ CREATE_REQUEST_PROMISE();
+ create_handler<SendCustomRequestQuery>(std::move(promise))->send(request.method_, request.parameters_);
}
void Td::on_request(uint64 id, td_api::answerCustomQuery &request) {
- CHECK_AUTH();
CHECK_IS_BOT();
CLEAN_INPUT_STRING(request.data_);
- CREATE_REQUEST(AnswerCustomQueryRequest, request.custom_query_id_, std::move(request.data_));
+ CREATE_OK_REQUEST_PROMISE();
+ create_handler<AnswerCustomQueryQuery>(std::move(promise))->send(request.custom_query_id_, request.data_);
}
void Td::on_request(uint64 id, const td_api::setAlarm &request) {
@@ -6837,10 +7928,9 @@ void Td::on_request(uint64 id, const td_api::setAlarm &request) {
}
void Td::on_request(uint64 id, td_api::searchHashtags &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.prefix_);
- CREATE_REQUEST_PROMISE(promise);
+ CREATE_REQUEST_PROMISE();
auto query_promise =
PromiseCreator::lambda([promise = std::move(promise)](Result<std::vector<string>> result) mutable {
if (result.is_error()) {
@@ -6854,83 +7944,308 @@ void Td::on_request(uint64 id, td_api::searchHashtags &request) {
}
void Td::on_request(uint64 id, td_api::removeRecentHashtag &request) {
- CHECK_AUTH();
CHECK_IS_USER();
CLEAN_INPUT_STRING(request.hashtag_);
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<> result) mutable {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(hashtag_hints_, &HashtagHints::remove_hashtag, std::move(request.hashtag_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getPremiumLimit &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ get_premium_limit(request.limit_type_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getPremiumFeatures &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ get_premium_features(this, request.source_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getPremiumStickerExamples &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ stickers_manager_->search_stickers("⭐️⭐️", 100, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::viewPremiumFeature &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ view_premium_feature(this, request.feature_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::clickPremiumSubscriptionButton &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ click_premium_subscription_button(this, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getPremiumState &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ get_premium_state(this, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::canPurchasePremium &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ can_purchase_premium(this, std::move(request.purpose_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::assignAppStoreTransaction &request) {
+ CHECK_IS_USER();
+ CREATE_OK_REQUEST_PROMISE();
+ assign_app_store_transaction(this, request.receipt_, std::move(request.purpose_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::assignGooglePlayTransaction &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.package_name_);
+ CLEAN_INPUT_STRING(request.store_product_id_);
+ CLEAN_INPUT_STRING(request.purchase_token_);
+ CREATE_OK_REQUEST_PROMISE();
+ assign_play_market_transaction(this, request.package_name_, request.store_product_id_, request.purchase_token_,
+ std::move(request.purpose_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::acceptTermsOfService &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.terms_of_service_id_);
+ auto promise = PromiseCreator::lambda([id = id, actor_id = actor_id(this)](Result<> result) {
if (result.is_error()) {
- promise.set_error(result.move_as_error());
+ send_closure(actor_id, &Td::send_error, id, result.move_as_error());
} else {
- promise.set_value(make_tl_object<td_api::ok>());
+ send_closure(actor_id, &Td::send_result, id, td_api::make_object<td_api::ok>());
+ send_closure(actor_id, &Td::schedule_get_terms_of_service, 0);
}
});
- send_closure(hashtag_hints_, &HashtagHints::remove_hashtag, std::move(request.hashtag_), std::move(query_promise));
+ accept_terms_of_service(this, std::move(request.terms_of_service_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getCountries &request) {
+ CREATE_REQUEST_PROMISE();
+ country_info_manager_->get_countries(std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getCountryCode &request) {
- CREATE_NO_ARGS_REQUEST(GetCountryCodeRequest);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(make_tl_object<td_api::text>(result.move_as_ok()));
+ }
+ });
+ country_info_manager_->get_current_country_code(std::move(query_promise));
}
-void Td::on_request(uint64 id, const td_api::getInviteText &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, const td_api::getPhoneNumberInfo &request) {
+ CREATE_REQUEST_PROMISE();
+ country_info_manager_->get_phone_number_info(request.phone_number_prefix_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getApplicationDownloadLink &request) {
CHECK_IS_USER();
- CREATE_NO_ARGS_REQUEST(GetInviteTextRequest);
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(make_tl_object<td_api::httpUrl>(result.move_as_ok()));
+ }
+ });
+ get_invite_text(this, std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, td_api::getDeepLinkInfo &request) {
+ CLEAN_INPUT_STRING(request.link_);
+ CREATE_REQUEST_PROMISE();
+ link_manager_->get_deep_link_info(request.link_, std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::getTermsOfService &request) {
- CREATE_NO_ARGS_REQUEST(GetTermsOfServiceRequest);
+void Td::on_request(uint64 id, const td_api::getApplicationConfig &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ send_closure(G()->config_manager(), &ConfigManager::get_app_config, std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::saveApplicationLogEvent &request) {
+ CHECK_IS_USER();
+ CLEAN_INPUT_STRING(request.type_);
+ CREATE_OK_REQUEST_PROMISE();
+ save_app_log(this, request.type_, DialogId(request.chat_id_), convert_json_value(std::move(request.data_)),
+ std::move(promise));
}
-void Td::on_request(uint64 id, const td_api::getProxy &request) {
- CREATE_REQUEST_PROMISE(promise);
- auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<Proxy> result) mutable {
+void Td::on_request(uint64 id, td_api::addProxy &request) {
+ CLEAN_INPUT_STRING(request.server_);
+ CREATE_REQUEST_PROMISE();
+ send_closure(G()->connection_creator(), &ConnectionCreator::add_proxy, -1, std::move(request.server_), request.port_,
+ request.enable_, std::move(request.type_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::editProxy &request) {
+ if (request.proxy_id_ < 0) {
+ return send_error_raw(id, 400, "Proxy identifier invalid");
+ }
+ CLEAN_INPUT_STRING(request.server_);
+ CREATE_REQUEST_PROMISE();
+ send_closure(G()->connection_creator(), &ConnectionCreator::add_proxy, request.proxy_id_, std::move(request.server_),
+ request.port_, request.enable_, std::move(request.type_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::enableProxy &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->connection_creator(), &ConnectionCreator::enable_proxy, request.proxy_id_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::disableProxy &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->connection_creator(), &ConnectionCreator::disable_proxy, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::removeProxy &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ send_closure(G()->connection_creator(), &ConnectionCreator::remove_proxy, request.proxy_id_, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getProxies &request) {
+ CREATE_REQUEST_PROMISE();
+ send_closure(G()->connection_creator(), &ConnectionCreator::get_proxies, std::move(promise));
+}
+
+void Td::on_request(uint64 id, const td_api::getProxyLink &request) {
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<string> result) mutable {
+ if (result.is_error()) {
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(make_tl_object<td_api::httpUrl>(result.move_as_ok()));
+ }
+ });
+ send_closure(G()->connection_creator(), &ConnectionCreator::get_proxy_link, request.proxy_id_,
+ std::move(query_promise));
+}
+
+void Td::on_request(uint64 id, const td_api::pingProxy &request) {
+ CREATE_REQUEST_PROMISE();
+ auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result<double> result) mutable {
if (result.is_error()) {
promise.set_error(result.move_as_error());
} else {
- promise.set_value(result.move_as_ok().as_td_api());
+ promise.set_value(make_tl_object<td_api::seconds>(result.move_as_ok()));
}
});
- send_closure(G()->connection_creator(), &ConnectionCreator::get_proxy, std::move(query_promise));
+ send_closure(G()->connection_creator(), &ConnectionCreator::ping_proxy, request.proxy_id_, std::move(query_promise));
}
-void Td::on_request(uint64 id, const td_api::setProxy &request) {
- CREATE_REQUEST_PROMISE(promise);
- send_closure(G()->connection_creator(), &ConnectionCreator::set_proxy, Proxy::from_td_api(request.proxy_));
- promise.set_value(make_tl_object<td_api::ok>());
+void Td::on_request(uint64 id, const td_api::getUserSupportInfo &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ get_user_info(this, UserId(request.user_id_), std::move(promise));
+}
+
+void Td::on_request(uint64 id, td_api::setUserSupportInfo &request) {
+ CHECK_IS_USER();
+ CREATE_REQUEST_PROMISE();
+ set_user_info(this, UserId(request.user_id_), std::move(request.message_), std::move(promise));
}
void Td::on_request(uint64 id, const td_api::getTextEntities &request) {
- // don't check authorization state
- send_closure(actor_id(this), &Td::send_result, id, do_static_request(request));
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::parseTextEntities &request) {
+ UNREACHABLE();
}
-void Td::on_request(uint64 id, td_api::parseTextEntities &request) {
- // don't check authorization state
- send_closure(actor_id(this), &Td::send_result, id, do_static_request(request));
+void Td::on_request(uint64 id, const td_api::parseMarkdown &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getMarkdownText &request) {
+ UNREACHABLE();
}
void Td::on_request(uint64 id, const td_api::getFileMimeType &request) {
- // don't check authorization state
- send_closure(actor_id(this), &Td::send_result, id, do_static_request(request));
+ UNREACHABLE();
}
void Td::on_request(uint64 id, const td_api::getFileExtension &request) {
- // don't check authorization state
- send_closure(actor_id(this), &Td::send_result, id, do_static_request(request));
+ UNREACHABLE();
}
-template <class T>
-td_api::object_ptr<td_api::Object> Td::do_static_request(const T &request) {
- return make_error(400, "Function can't be executed synchronously");
+void Td::on_request(uint64 id, const td_api::cleanFileName &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getLanguagePackString &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getPhoneNumberInfoSync &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getPushReceiverId &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getChatFilterDefaultIconName &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getJsonValue &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getJsonString &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getThemeParametersJsonString &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::setLogStream &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getLogStream &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::setLogVerbosityLevel &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getLogVerbosityLevel &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getLogTags &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::setLogTagVerbosityLevel &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::getLogTagVerbosityLevel &request) {
+ UNREACHABLE();
+}
+
+void Td::on_request(uint64 id, const td_api::addLogMessage &request) {
+ UNREACHABLE();
}
td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getTextEntities &request) {
if (!check_utf8(request.text_)) {
return make_error(400, "Text must be encoded in UTF-8");
}
- auto text_entities = find_entities(request.text_, false);
- return make_tl_object<td_api::textEntities>(get_text_entities_object(text_entities));
+ auto text_entities = find_entities(request.text_, false, false);
+ return make_tl_object<td_api::textEntities>(
+ get_text_entities_object(text_entities, false, std::numeric_limits<int32>::max()));
}
td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseTextEntities &request) {
@@ -6941,23 +8256,80 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseTextEntiti
return make_error(400, "Parse mode must be non-empty");
}
- Result<vector<MessageEntity>> r_entities;
- switch (request.parse_mode_->get_id()) {
- case td_api::textParseModeHTML::ID:
- r_entities = parse_html(request.text_);
- break;
- case td_api::textParseModeMarkdown::ID:
- r_entities = parse_markdown(request.text_);
- break;
- default:
- UNREACHABLE();
- break;
- }
+ auto r_entities = [&]() -> Result<vector<MessageEntity>> {
+ if (utf8_length(request.text_) > 65536) {
+ return Status::Error("Text is too long");
+ }
+ switch (request.parse_mode_->get_id()) {
+ case td_api::textParseModeHTML::ID:
+ return parse_html(request.text_);
+ case td_api::textParseModeMarkdown::ID: {
+ auto version = static_cast<const td_api::textParseModeMarkdown *>(request.parse_mode_.get())->version_;
+ if (version == 0 || version == 1) {
+ return parse_markdown(request.text_);
+ }
+ if (version == 2) {
+ return parse_markdown_v2(request.text_);
+ }
+ return Status::Error("Wrong Markdown version specified");
+ }
+ default:
+ UNREACHABLE();
+ return Status::Error(500, "Unknown parse mode");
+ }
+ }();
if (r_entities.is_error()) {
return make_error(400, PSLICE() << "Can't parse entities: " << r_entities.error().message());
}
- return make_tl_object<td_api::formattedText>(std::move(request.text_), get_text_entities_object(r_entities.ok()));
+ return make_tl_object<td_api::formattedText>(std::move(request.text_),
+ get_text_entities_object(r_entities.ok(), false, -1));
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::parseMarkdown &request) {
+ if (request.text_ == nullptr) {
+ return make_error(400, "Text must be non-empty");
+ }
+
+ auto r_entities = get_message_entities(nullptr, std::move(request.text_->entities_), true);
+ if (r_entities.is_error()) {
+ return make_error(400, r_entities.error().message());
+ }
+ auto entities = r_entities.move_as_ok();
+ auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true);
+ if (status.is_error()) {
+ return make_error(400, status.message());
+ }
+
+ auto parsed_text = parse_markdown_v3({std::move(request.text_->text_), std::move(entities)});
+ fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).ensure();
+ return get_formatted_text_object(parsed_text, false, std::numeric_limits<int32>::max());
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getOption &request) {
+ if (!is_synchronous_request(&request)) {
+ return make_error(400, "The option can't be get synchronously");
+ }
+ return OptionManager::get_option_synchronously(request.name_);
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::getMarkdownText &request) {
+ if (request.text_ == nullptr) {
+ return make_error(400, "Text must be non-empty");
+ }
+
+ auto r_entities = get_message_entities(nullptr, std::move(request.text_->entities_));
+ if (r_entities.is_error()) {
+ return make_error(400, r_entities.error().message());
+ }
+ auto entities = r_entities.move_as_ok();
+ auto status = fix_formatted_text(request.text_->text_, entities, true, true, true, true, true);
+ if (status.is_error()) {
+ return make_error(400, status.message());
+ }
+
+ return get_formatted_text_object(get_markdown_v3({std::move(request.text_->text_), std::move(entities)}), false,
+ std::numeric_limits<int32>::max());
}
td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getFileMimeType &request) {
@@ -6970,30 +8342,164 @@ td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getFileEx
return make_tl_object<td_api::text>(MimeType::to_extension(request.mime_type_));
}
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::cleanFileName &request) {
+ // don't check file name UTF-8 correctness
+ return make_tl_object<td_api::text>(clean_filename(request.file_name_));
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getLanguagePackString &request) {
+ return LanguagePackManager::get_language_pack_string(
+ request.language_pack_database_path_, request.localization_target_, request.language_pack_id_, request.key_);
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::getPhoneNumberInfoSync &request) {
+ // don't check language_code/phone number UTF-8 correctness
+ return CountryInfoManager::get_phone_number_info_sync(request.language_code_,
+ std::move(request.phone_number_prefix_));
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getPushReceiverId &request) {
+ // don't check push payload UTF-8 correctness
+ auto r_push_receiver_id = NotificationManager::get_push_receiver_id(request.payload_);
+ if (r_push_receiver_id.is_error()) {
+ VLOG(notifications) << "Failed to get push notification receiver from \"" << format::escaped(request.payload_)
+ << '"';
+ return make_error(r_push_receiver_id.error().code(), r_push_receiver_id.error().message());
+ }
+ return td_api::make_object<td_api::pushReceiverId>(r_push_receiver_id.ok());
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getChatFilterDefaultIconName &request) {
+ if (request.filter_ == nullptr) {
+ return make_error(400, "Chat filter must be non-empty");
+ }
+ if (!check_utf8(request.filter_->title_)) {
+ return make_error(400, "Chat filter title must be encoded in UTF-8");
+ }
+ if (!check_utf8(request.filter_->icon_name_)) {
+ return make_error(400, "Chat filter icon name must be encoded in UTF-8");
+ }
+ return td_api::make_object<td_api::text>(DialogFilter::get_default_icon_name(request.filter_.get()));
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::getJsonValue &request) {
+ if (!check_utf8(request.json_)) {
+ return make_error(400, "JSON has invalid encoding");
+ }
+ auto result = get_json_value(request.json_);
+ if (result.is_error()) {
+ return make_error(400, result.error().message());
+ } else {
+ return result.move_as_ok();
+ }
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getJsonString &request) {
+ return td_api::make_object<td_api::text>(get_json_string(request.json_value_.get()));
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getThemeParametersJsonString &request) {
+ return td_api::make_object<td_api::text>(ThemeManager::get_theme_parameters_json_string(request.theme_, true));
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::setLogStream &request) {
+ auto result = Logging::set_current_stream(std::move(request.log_stream_));
+ if (result.is_ok()) {
+ return td_api::make_object<td_api::ok>();
+ } else {
+ return make_error(400, result.message());
+ }
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getLogStream &request) {
+ auto result = Logging::get_current_stream();
+ if (result.is_ok()) {
+ return result.move_as_ok();
+ } else {
+ return make_error(400, result.error().message());
+ }
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::setLogVerbosityLevel &request) {
+ auto result = Logging::set_verbosity_level(static_cast<int>(request.new_verbosity_level_));
+ if (result.is_ok()) {
+ return td_api::make_object<td_api::ok>();
+ } else {
+ return make_error(400, result.message());
+ }
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getLogVerbosityLevel &request) {
+ return td_api::make_object<td_api::logVerbosityLevel>(Logging::get_verbosity_level());
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getLogTags &request) {
+ return td_api::make_object<td_api::logTags>(Logging::get_tags());
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::setLogTagVerbosityLevel &request) {
+ auto result = Logging::set_tag_verbosity_level(request.tag_, static_cast<int>(request.new_verbosity_level_));
+ if (result.is_ok()) {
+ return td_api::make_object<td_api::ok>();
+ } else {
+ return make_error(400, result.message());
+ }
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::getLogTagVerbosityLevel &request) {
+ auto result = Logging::get_tag_verbosity_level(request.tag_);
+ if (result.is_ok()) {
+ return td_api::make_object<td_api::logVerbosityLevel>(result.ok());
+ } else {
+ return make_error(400, result.error().message());
+ }
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(const td_api::addLogMessage &request) {
+ Logging::add_message(request.verbosity_level_, request.text_);
+ return td_api::make_object<td_api::ok>();
+}
+
+td_api::object_ptr<td_api::Object> Td::do_static_request(td_api::testReturnError &request) {
+ if (request.error_ == nullptr) {
+ return td_api::make_object<td_api::error>(404, "Not Found");
+ }
+
+ return std::move(request.error_);
+}
+
// test
-void Td::on_request(uint64 id, td_api::testNetwork &request) {
- create_handler<TestQuery>(id)->send();
+void Td::on_request(uint64 id, const td_api::testNetwork &request) {
+ CREATE_OK_REQUEST_PROMISE();
+ create_handler<TestNetworkQuery>(std::move(promise))->send();
}
-void Td::on_request(uint64 id, td_api::testGetDifference &request) {
- CHECK_AUTH();
+void Td::on_request(uint64 id, td_api::testProxy &request) {
+ auto r_proxy = Proxy::create_proxy(std::move(request.server_), request.port_, request.type_.get());
+ if (r_proxy.is_error()) {
+ return send_closure(actor_id(this), &Td::send_error, id, r_proxy.move_as_error());
+ }
+ CREATE_REQUEST(TestProxyRequest, r_proxy.move_as_ok(), request.dc_id_, request.timeout_);
+}
+
+void Td::on_request(uint64 id, const td_api::testGetDifference &request) {
updates_manager_->get_difference("testGetDifference");
send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
}
-void Td::on_request(uint64 id, td_api::testUseUpdate &request) {
+void Td::on_request(uint64 id, const td_api::testUseUpdate &request) {
send_closure(actor_id(this), &Td::send_result, id, nullptr);
}
-void Td::on_request(uint64 id, td_api::testUseError &request) {
- send_closure(actor_id(this), &Td::send_result, id, nullptr);
+void Td::on_request(uint64 id, const td_api::testReturnError &request) {
+ UNREACHABLE();
}
-void Td::on_request(uint64 id, td_api::testCallEmpty &request) {
+void Td::on_request(uint64 id, const td_api::testCallEmpty &request) {
send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::ok>());
}
-void Td::on_request(uint64 id, td_api::testSquareInt &request) {
+void Td::on_request(uint64 id, const td_api::testSquareInt &request) {
send_closure(actor_id(this), &Td::send_result, id, make_tl_object<td_api::testInt>(request.x_ * request.x_));
}
@@ -7024,13 +8530,11 @@ void Td::on_request(uint64 id, td_api::testCallVectorStringObject &request) {
}
#undef CLEAN_INPUT_STRING
-#undef CHECK_AUTH
#undef CHECK_IS_BOT
#undef CHECK_IS_USER
#undef CREATE_NO_ARGS_REQUEST
#undef CREATE_REQUEST
#undef CREATE_REQUEST_PROMISE
-
-constexpr const char *Td::TDLIB_VERSION;
+#undef CREATE_OK_REQUEST_PROMISE
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Td.h b/protocols/Telegram/tdlib/td/td/telegram/Td.h
index 69595ef159..9c61731fb4 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Td.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Td.h
@@ -1,55 +1,81 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/ConnectionState.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/net/MtprotoHeader.h"
#include "td/telegram/net/NetQuery.h"
-#include "td/telegram/StateManager.h"
+#include "td/telegram/net/NetQueryStats.h"
+#include "td/telegram/td_api.h"
#include "td/telegram/TdCallback.h"
#include "td/telegram/TdDb.h"
#include "td/telegram/TdParameters.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/TermsOfService.h"
-#include "td/telegram/td_api.h"
+#include "td/db/DbKey.h"
#include "td/actor/actor.h"
-#include "td/actor/Timeout.h"
+#include "td/actor/MultiTimeout.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/Container.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include <memory>
#include <unordered_map>
-#include <unordered_set>
#include <utility>
namespace td {
+
class AnimationsManager;
+class AttachMenuManager;
class AudiosManager;
class AuthManager;
+class BackgroundManager;
class CallManager;
class CallbackQueriesManager;
-class ChangePhoneNumberManager;
class ConfigManager;
class ContactsManager;
+class CountryInfoManager;
class DeviceTokenManager;
class DocumentsManager;
+class DownloadManager;
class FileManager;
+class FileReferenceManager;
+class ForumTopicManager;
+class GameManager;
+class GroupCallManager;
class InlineQueriesManager;
class HashtagHints;
+class LanguagePackManager;
+class LinkManager;
class MessagesManager;
class NetStatsManager;
+class NotificationManager;
+class NotificationSettingsManager;
+class OptionManager;
class PasswordManager;
+class PhoneNumberManager;
+class PollManager;
class PrivacyManager;
+class SecureManager;
class SecretChatsManager;
+class SponsoredMessageManager;
+class StateManager;
class StickersManager;
class StorageManager;
+class ThemeManager;
class TopDialogManager;
class UpdatesManager;
class VideoNotesManager;
@@ -57,11 +83,8 @@ class VideosManager;
class VoiceNotesManager;
class WebPagesManager;
-template <class T>
-class Promise;
-} // namespace td
-
-namespace td {
+extern int VERBOSITY_NAME(td_init);
+extern int VERBOSITY_NAME(td_requests);
// Td may start closing after explicit "close" or "destroy" query.
// Or it may start closing by itself, because authorization is lost.
@@ -71,88 +94,117 @@ namespace td {
// It happens after "hangup".
//
// Parent needs a way to know that it will receive no more updates.
-// It happens after destruction of callback or after on_closed.
-class Td final : public NetQueryCallback {
+// It happens after destruction of callback
+class Td final : public Actor {
public:
Td(const Td &) = delete;
Td(Td &&) = delete;
Td &operator=(const Td &) = delete;
Td &operator=(Td &&) = delete;
+ ~Td() final;
- explicit Td(unique_ptr<TdCallback> callback);
+ struct Options {
+ std::shared_ptr<NetQueryStats> net_query_stats;
+ };
+
+ Td(unique_ptr<TdCallback> callback, Options options);
void request(uint64 id, tl_object_ptr<td_api::Function> function);
void destroy();
- void close();
- void update_qts(int32 qts);
+ void schedule_get_terms_of_service(int32 expires_in);
- void force_get_difference();
+ void schedule_get_promo_data(int32 expires_in);
- void on_result(NetQueryPtr query) override;
- void on_connection_state_changed(StateManager::State new_state);
- void on_authorization_lost();
+ void on_update(BufferSlice &&update);
+
+ void on_result(NetQueryPtr query);
void on_online_updated(bool force, bool send_update);
- void on_update_status_success(bool is_online);
- void on_channel_unban_timeout(int64 channel_id_long);
+ void on_update_status_success(bool is_online);
bool is_online() const;
- template <class ActorT, class... ArgsT>
- ActorId<ActorT> create_net_actor(ArgsT &&... args) {
- auto slot_id = request_actors_.create(ActorOwn<>(), RequestActorIdType);
- inc_request_actor_refcnt();
- auto actor = make_unique<ActorT>(std::forward<ArgsT>(args)...);
- actor->set_parent(actor_shared(this, slot_id));
-
- auto actor_own = register_actor("net_actor", std::move(actor));
- auto actor_id = actor_own.get();
- *request_actors_.get(slot_id) = std::move(actor_own);
- return actor_id;
- }
+ void set_is_online(bool is_online);
+
+ void set_is_bot_online(bool is_bot_online);
unique_ptr<AudiosManager> audios_manager_;
unique_ptr<CallbackQueriesManager> callback_queries_manager_;
unique_ptr<DocumentsManager> documents_manager_;
- unique_ptr<VideoNotesManager> video_notes_manager_;
+ unique_ptr<OptionManager> option_manager_;
unique_ptr<VideosManager> videos_manager_;
- unique_ptr<VoiceNotesManager> voice_notes_manager_;
- std::unique_ptr<AnimationsManager> animations_manager_;
+ unique_ptr<AnimationsManager> animations_manager_;
ActorOwn<AnimationsManager> animations_manager_actor_;
- std::unique_ptr<AuthManager> auth_manager_;
+ unique_ptr<AttachMenuManager> attach_menu_manager_;
+ ActorOwn<AttachMenuManager> attach_menu_manager_actor_;
+ unique_ptr<AuthManager> auth_manager_;
ActorOwn<AuthManager> auth_manager_actor_;
- std::unique_ptr<ChangePhoneNumberManager> change_phone_number_manager_;
- ActorOwn<ChangePhoneNumberManager> change_phone_number_manager_actor_;
- std::unique_ptr<ContactsManager> contacts_manager_;
+ unique_ptr<BackgroundManager> background_manager_;
+ ActorOwn<BackgroundManager> background_manager_actor_;
+ unique_ptr<ContactsManager> contacts_manager_;
ActorOwn<ContactsManager> contacts_manager_actor_;
- std::unique_ptr<FileManager> file_manager_;
+ unique_ptr<CountryInfoManager> country_info_manager_;
+ ActorOwn<CountryInfoManager> country_info_manager_actor_;
+ unique_ptr<DownloadManager> download_manager_;
+ ActorOwn<DownloadManager> download_manager_actor_;
+ unique_ptr<FileManager> file_manager_;
ActorOwn<FileManager> file_manager_actor_;
- std::unique_ptr<InlineQueriesManager> inline_queries_manager_;
+ unique_ptr<FileReferenceManager> file_reference_manager_;
+ ActorOwn<FileReferenceManager> file_reference_manager_actor_;
+ unique_ptr<ForumTopicManager> forum_topic_manager_;
+ ActorOwn<ForumTopicManager> forum_topic_manager_actor_;
+ unique_ptr<GameManager> game_manager_;
+ ActorOwn<GameManager> game_manager_actor_;
+ unique_ptr<GroupCallManager> group_call_manager_;
+ ActorOwn<GroupCallManager> group_call_manager_actor_;
+ unique_ptr<InlineQueriesManager> inline_queries_manager_;
ActorOwn<InlineQueriesManager> inline_queries_manager_actor_;
- std::unique_ptr<MessagesManager> messages_manager_;
+ unique_ptr<LinkManager> link_manager_;
+ ActorOwn<LinkManager> link_manager_actor_;
+ unique_ptr<MessagesManager> messages_manager_;
ActorOwn<MessagesManager> messages_manager_actor_;
- std::unique_ptr<StickersManager> stickers_manager_;
+ unique_ptr<NotificationManager> notification_manager_;
+ ActorOwn<NotificationManager> notification_manager_actor_;
+ unique_ptr<NotificationSettingsManager> notification_settings_manager_;
+ ActorOwn<NotificationSettingsManager> notification_settings_manager_actor_;
+ unique_ptr<PollManager> poll_manager_;
+ ActorOwn<PollManager> poll_manager_actor_;
+ unique_ptr<SponsoredMessageManager> sponsored_message_manager_;
+ ActorOwn<SponsoredMessageManager> sponsored_message_manager_actor_;
+ unique_ptr<StickersManager> stickers_manager_;
ActorOwn<StickersManager> stickers_manager_actor_;
- std::unique_ptr<UpdatesManager> updates_manager_;
+ unique_ptr<ThemeManager> theme_manager_;
+ ActorOwn<ThemeManager> theme_manager_actor_;
+ unique_ptr<TopDialogManager> top_dialog_manager_;
+ ActorOwn<TopDialogManager> top_dialog_manager_actor_;
+ unique_ptr<UpdatesManager> updates_manager_;
ActorOwn<UpdatesManager> updates_manager_actor_;
- std::unique_ptr<WebPagesManager> web_pages_manager_;
+ unique_ptr<VideoNotesManager> video_notes_manager_;
+ ActorOwn<VideoNotesManager> video_notes_manager_actor_;
+ unique_ptr<VoiceNotesManager> voice_notes_manager_;
+ ActorOwn<VoiceNotesManager> voice_notes_manager_actor_;
+ unique_ptr<WebPagesManager> web_pages_manager_;
ActorOwn<WebPagesManager> web_pages_manager_actor_;
ActorOwn<CallManager> call_manager_;
+ ActorOwn<PhoneNumberManager> change_phone_number_manager_;
ActorOwn<ConfigManager> config_manager_;
+ ActorOwn<PhoneNumberManager> confirm_phone_number_manager_;
ActorOwn<DeviceTokenManager> device_token_manager_;
ActorOwn<HashtagHints> hashtag_hints_;
+ ActorOwn<LanguagePackManager> language_pack_manager_;
ActorOwn<NetStatsManager> net_stats_manager_;
ActorOwn<PasswordManager> password_manager_;
ActorOwn<PrivacyManager> privacy_manager_;
ActorOwn<SecretChatsManager> secret_chats_manager_;
+ ActorOwn<SecureManager> secure_manager_;
ActorOwn<StateManager> state_manager_;
ActorOwn<StorageManager> storage_manager_;
- ActorOwn<TopDialogManager> top_dialog_manager_;
+ ActorOwn<PhoneNumberManager> verify_phone_number_manager_;
class ResultHandler : public std::enable_shared_from_this<ResultHandler> {
public:
@@ -160,27 +212,34 @@ class Td final : public NetQueryCallback {
ResultHandler(const ResultHandler &) = delete;
ResultHandler &operator=(const ResultHandler &) = delete;
virtual ~ResultHandler() = default;
- virtual void on_result(NetQueryPtr query);
- virtual void on_result(uint64 id, BufferSlice packet) {
+
+ virtual void on_result(BufferSlice packet) {
UNREACHABLE();
}
- virtual void on_error(uint64 id, Status status) {
+
+ virtual void on_error(Status status) {
UNREACHABLE();
}
friend class Td;
protected:
- void send_query(NetQueryPtr);
+ void send_query(NetQueryPtr query);
- Td *td = nullptr;
+ Td *td_ = nullptr;
+ bool is_query_sent_ = false;
private:
- void set_td(Td *new_td);
+ void set_td(Td *td);
};
template <class HandlerT, class... Args>
- std::shared_ptr<HandlerT> create_handler(Args &&... args) {
+ std::shared_ptr<HandlerT> create_handler(Args &&...args) {
+ LOG_CHECK(close_flag_ < 2) << close_flag_
+#if TD_CLANG || TD_GCC
+ << ' ' << __PRETTY_FUNCTION__
+#endif
+ ;
auto ptr = std::make_shared<HandlerT>(std::forward<Args>(args)...);
ptr->set_td(this);
return ptr;
@@ -188,16 +247,18 @@ class Td final : public NetQueryCallback {
void send_update(tl_object_ptr<td_api::Update> &&object);
- ActorShared<Td> create_reference();
-
static td_api::object_ptr<td_api::Object> static_request(td_api::object_ptr<td_api::Function> function);
private:
- static constexpr const char *TDLIB_VERSION = "1.2.0";
static constexpr int64 ONLINE_ALARM_ID = 0;
- static constexpr int32 ONLINE_TIMEOUT = 240;
static constexpr int64 PING_SERVER_ALARM_ID = -1;
static constexpr int32 PING_SERVER_TIMEOUT = 300;
+ static constexpr int64 TERMS_OF_SERVICE_ALARM_ID = -2;
+ static constexpr int64 PROMO_DATA_ALARM_ID = -3;
+
+ void on_connection_state_changed(ConnectionState new_state);
+
+ void run_request(uint64 id, tl_object_ptr<td_api::Function> function);
void send_result(uint64 id, tl_object_ptr<td_api::Object> object);
void send_error(uint64 id, Status error);
@@ -205,65 +266,91 @@ class Td final : public NetQueryCallback {
void send_error_raw(uint64 id, int32 code, CSlice error);
void answer_ok_query(uint64 id, Status status);
+ ActorShared<Td> create_reference();
+
void inc_actor_refcnt();
void dec_actor_refcnt();
void inc_request_actor_refcnt();
void dec_request_actor_refcnt();
+ void close();
void on_closed();
void dec_stop_cnt();
- TdParameters parameters_;
-
unique_ptr<TdCallback> callback_;
+ Options td_options_;
+
+ MtprotoHeader::Options options_;
- StateManager::State connection_state_;
+ TdParameters parameters_;
+
+ ConnectionState connection_state_ = ConnectionState::Empty;
- std::unordered_multiset<uint64> request_set_;
+ std::unordered_multimap<uint64, int32> request_set_;
int actor_refcnt_ = 0;
int request_actor_refcnt_ = 0;
int stop_cnt_ = 2;
bool destroy_flag_ = false;
int close_flag_ = 0;
- enum class State { WaitParameters, Decrypt, Run, Close } state_ = State::WaitParameters;
- EncryptionInfo encryption_info_;
+ enum class State : int32 { WaitParameters, Run, Close } state_ = State::WaitParameters;
+ uint64 set_parameters_request_id_ = 0;
- vector<std::pair<uint64, std::shared_ptr<ResultHandler>>> result_handlers_;
+ FlatHashMap<uint64, std::shared_ptr<ResultHandler>> result_handlers_;
enum : int8 { RequestActorIdType = 1, ActorIdType = 2 };
Container<ActorOwn<Actor>> request_actors_;
bool is_online_ = false;
+ bool is_bot_online_ = false;
NetQueryRef update_status_query_;
int64 alarm_id_ = 1;
- std::unordered_map<int64, uint64> pending_alarms_;
- MultiTimeout alarm_timeout_;
+ FlatHashMap<int64, uint64> pending_alarms_;
+ MultiTimeout alarm_timeout_{"AlarmTimeout"};
+
+ TermsOfService pending_terms_of_service_;
+
+ struct DownloadInfo {
+ int64 offset = -1;
+ int64 limit = -1;
+ vector<uint64> request_ids;
+ };
+ FlatHashMap<FileId, DownloadInfo, FileIdHash> pending_file_downloads_;
+
+ vector<std::pair<uint64, td_api::object_ptr<td_api::Function>>> pending_preauthentication_requests_;
+
+ vector<std::pair<uint64, td_api::object_ptr<td_api::Function>>> pending_set_parameters_requests_;
+ vector<std::pair<uint64, td_api::object_ptr<td_api::Function>>> pending_init_requests_;
+
+ template <class T>
+ void complete_pending_preauthentication_requests(const T &func);
+
+ td_api::object_ptr<td_api::AuthorizationState> get_fake_authorization_state_object() const;
+
+ vector<td_api::object_ptr<td_api::Update>> get_fake_current_state() const;
static void on_alarm_timeout_callback(void *td_ptr, int64 alarm_id);
void on_alarm_timeout(int64 alarm_id);
+ td_api::object_ptr<td_api::updateTermsOfService> get_update_terms_of_service_object() const;
+
+ void on_get_terms_of_service(Result<std::pair<int32, TermsOfService>> result, bool dummy);
+
+ void on_get_promo_data(Result<telegram_api::object_ptr<telegram_api::help_PromoData>> r_promo_data, bool dummy);
+
template <class T>
- friend class RequestActor; // uses send_result/send_error
- friend class TestQuery; // uses send_result/send_error
- friend class AuthManager; // uses send_result/send_error
- friend class ChangePhoneNumberManager; // uses send_result/send_error
+ friend class RequestActor; // uses send_result/send_error
+ friend class AuthManager; // uses send_result/send_error, TODO pass Promise<>
+ friend class PhoneNumberManager; // uses send_result/send_error, TODO pass Promise<>
void add_handler(uint64 id, std::shared_ptr<ResultHandler> handler);
std::shared_ptr<ResultHandler> extract_handler(uint64 id);
- void invalidate_handler(ResultHandler *handler);
- void clear_handlers();
- // void destroy_handler(ResultHandler *handler);
-
- static bool is_internal_config_option(Slice name);
- void on_config_option_updated(const string &name);
+ void clear_requests();
- static tl_object_ptr<td_api::ConnectionState> get_connection_state_object(StateManager::State state);
-
- void send(NetQueryPtr &&query);
+ void on_file_download_finished(FileId file_id);
class OnRequest;
@@ -275,8 +362,12 @@ class Td final : public NetQueryCallback {
std::shared_ptr<UploadFileCallback> upload_file_callback_;
+ std::shared_ptr<ActorContext> old_context_;
+
+ static int *get_log_verbosity_level(Slice name);
+
template <class T>
- auto create_request_promise(uint64 id) {
+ Promise<T> create_request_promise(uint64 id) {
return PromiseCreator::lambda([id = id, actor_id = actor_id(this)](Result<T> r_state) {
if (r_state.is_error()) {
send_closure(actor_id, &Td::send_error, id, r_state.move_as_error());
@@ -286,27 +377,43 @@ class Td final : public NetQueryCallback {
});
}
+ Promise<Unit> create_ok_request_promise(uint64 id);
+
+ static bool is_authentication_request(int32 id);
+
+ static bool is_synchronous_request(const td_api::Function *function);
+
+ static bool is_preinitialization_request(int32 id);
+
+ static bool is_preauthentication_request(int32 id);
+
template <class T>
void on_request(uint64 id, const T &request) = delete;
void on_request(uint64 id, const td_api::setTdlibParameters &request);
- void on_request(uint64 id, const td_api::checkDatabaseEncryptionKey &request);
-
- void on_request(uint64 id, td_api::setDatabaseEncryptionKey &request);
-
void on_request(uint64 id, const td_api::getAuthorizationState &request);
void on_request(uint64 id, td_api::setAuthenticationPhoneNumber &request);
+ void on_request(uint64 id, td_api::setAuthenticationEmailAddress &request);
+
void on_request(uint64 id, const td_api::resendAuthenticationCode &request);
+ void on_request(uint64 id, td_api::checkAuthenticationEmailCode &request);
+
void on_request(uint64 id, td_api::checkAuthenticationCode &request);
+ void on_request(uint64 id, td_api::registerUser &request);
+
+ void on_request(uint64 id, td_api::requestQrCodeAuthentication &request);
+
void on_request(uint64 id, td_api::checkAuthenticationPassword &request);
void on_request(uint64 id, const td_api::requestAuthenticationPasswordRecovery &request);
+ void on_request(uint64 id, td_api::checkAuthenticationPasswordRecoveryCode &request);
+
void on_request(uint64 id, td_api::recoverAuthenticationPassword &request);
void on_request(uint64 id, const td_api::logOut &request);
@@ -317,23 +424,45 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::checkAuthenticationBotToken &request);
+ void on_request(uint64 id, td_api::confirmQrCodeAuthentication &request);
+
+ void on_request(uint64 id, td_api::setDatabaseEncryptionKey &request);
+
+ void on_request(uint64 id, const td_api::getCurrentState &request);
+
void on_request(uint64 id, td_api::getPasswordState &request);
void on_request(uint64 id, td_api::setPassword &request);
+ void on_request(uint64 id, td_api::setLoginEmailAddress &request);
+
+ void on_request(uint64 id, const td_api::resendLoginEmailAddressCode &request);
+
+ void on_request(uint64 id, td_api::checkLoginEmailAddressCode &request);
+
void on_request(uint64 id, td_api::getRecoveryEmailAddress &request);
void on_request(uint64 id, td_api::setRecoveryEmailAddress &request);
+ void on_request(uint64 id, td_api::checkRecoveryEmailAddressCode &request);
+
+ void on_request(uint64 id, const td_api::resendRecoveryEmailAddressCode &request);
+
void on_request(uint64 id, td_api::requestPasswordRecovery &request);
+ void on_request(uint64 id, td_api::checkPasswordRecoveryCode &request);
+
void on_request(uint64 id, td_api::recoverPassword &request);
+ void on_request(uint64 id, const td_api::resetPassword &request);
+
+ void on_request(uint64 id, const td_api::cancelPasswordReset &request);
+
void on_request(uint64 id, td_api::getTemporaryPasswordState &request);
void on_request(uint64 id, td_api::createTemporaryPassword &request);
- void on_request(uint64 id, td_api::processDcUpdate &request);
+ void on_request(uint64 id, td_api::processPushNotification &request);
void on_request(uint64 id, td_api::registerDevice &request);
@@ -359,6 +488,12 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::terminateAllOtherSessions &request);
+ void on_request(uint64 id, const td_api::toggleSessionCanAcceptCalls &request);
+
+ void on_request(uint64 id, const td_api::toggleSessionCanAcceptSecretChats &request);
+
+ void on_request(uint64 id, const td_api::setInactiveSessionTtl &request);
+
void on_request(uint64 id, const td_api::getConnectedWebsites &request);
void on_request(uint64 id, const td_api::disconnectWebsite &request);
@@ -385,13 +520,33 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::getMessage &request);
+ void on_request(uint64 id, const td_api::getMessageLocally &request);
+
void on_request(uint64 id, const td_api::getRepliedMessage &request);
void on_request(uint64 id, const td_api::getChatPinnedMessage &request);
+ void on_request(uint64 id, const td_api::getCallbackQueryMessage &request);
+
+ void on_request(uint64 id, const td_api::getMessageThread &request);
+
+ void on_request(uint64 id, const td_api::getMessageViewers &request);
+
void on_request(uint64 id, const td_api::getMessages &request);
- void on_request(uint64 id, const td_api::getPublicMessageLink &request);
+ void on_request(uint64 id, const td_api::getChatSponsoredMessages &request);
+
+ void on_request(uint64 id, const td_api::getMessageLink &request);
+
+ void on_request(uint64 id, const td_api::getMessageEmbeddingCode &request);
+
+ void on_request(uint64 id, td_api::getMessageLinkInfo &request);
+
+ void on_request(uint64 id, td_api::translateText &request);
+
+ void on_request(uint64 id, const td_api::recognizeSpeech &request);
+
+ void on_request(uint64 id, const td_api::rateSpeechRecognition &request);
void on_request(uint64 id, const td_api::getFile &request);
@@ -401,6 +556,8 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::getStorageStatisticsFast &request);
+ void on_request(uint64 id, td_api::getDatabaseStatistics &request);
+
void on_request(uint64 id, td_api::optimizeStorage &request);
void on_request(uint64 id, td_api::getNetworkStatistics &request);
@@ -409,12 +566,18 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::addNetworkStatistics &request);
- void on_request(uint64 id, td_api::setNetworkType &request);
+ void on_request(uint64 id, const td_api::setNetworkType &request);
+
+ void on_request(uint64 id, const td_api::getAutoDownloadSettingsPresets &request);
- void on_request(uint64 id, td_api::getTopChats &request);
+ void on_request(uint64 id, const td_api::setAutoDownloadSettings &request);
+
+ void on_request(uint64 id, const td_api::getTopChats &request);
void on_request(uint64 id, const td_api::removeTopChat &request);
+ void on_request(uint64 id, const td_api::loadChats &request);
+
void on_request(uint64 id, const td_api::getChats &request);
void on_request(uint64 id, td_api::searchPublicChat &request);
@@ -425,18 +588,28 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::searchChatsOnServer &request);
+ void on_request(uint64 id, const td_api::searchChatsNearby &request);
+
void on_request(uint64 id, const td_api::addRecentlyFoundChat &request);
void on_request(uint64 id, const td_api::removeRecentlyFoundChat &request);
void on_request(uint64 id, const td_api::clearRecentlyFoundChats &request);
+ void on_request(uint64 id, const td_api::getRecentlyOpenedChats &request);
+
void on_request(uint64 id, const td_api::getGroupsInCommon &request);
void on_request(uint64 id, td_api::checkChatUsername &request);
void on_request(uint64 id, const td_api::getCreatedPublicChats &request);
+ void on_request(uint64 id, const td_api::checkCreatedPublicChatsLimit &request);
+
+ void on_request(uint64 id, const td_api::getSuitableDiscussionChats &request);
+
+ void on_request(uint64 id, const td_api::getInactiveSupergroupChats &request);
+
void on_request(uint64 id, const td_api::openChat &request);
void on_request(uint64 id, const td_api::closeChat &request);
@@ -445,17 +618,35 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::openMessageContent &request);
+ void on_request(uint64 id, const td_api::clickAnimatedEmojiMessage &request);
+
+ void on_request(uint64 id, const td_api::getInternalLinkType &request);
+
+ void on_request(uint64 id, td_api::getExternalLinkInfo &request);
+
+ void on_request(uint64 id, td_api::getExternalLink &request);
+
void on_request(uint64 id, const td_api::getChatHistory &request);
void on_request(uint64 id, const td_api::deleteChatHistory &request);
+ void on_request(uint64 id, const td_api::deleteChat &request);
+
+ void on_request(uint64 id, const td_api::getMessageThreadHistory &request);
+
+ void on_request(uint64 id, td_api::getChatMessageCalendar &request);
+
void on_request(uint64 id, td_api::searchChatMessages &request);
void on_request(uint64 id, td_api::searchSecretMessages &request);
void on_request(uint64 id, td_api::searchMessages &request);
- void on_request(uint64 id, td_api::searchCallMessages &request);
+ void on_request(uint64 id, const td_api::searchCallMessages &request);
+
+ void on_request(uint64 id, td_api::searchOutgoingDocumentMessages &request);
+
+ void on_request(uint64 id, const td_api::deleteAllCallMessages &request);
void on_request(uint64 id, const td_api::searchChatRecentLocationMessages &request);
@@ -463,12 +654,54 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::getChatMessageByDate &request);
+ void on_request(uint64 id, const td_api::getChatSparseMessagePositions &request);
+
+ void on_request(uint64 id, const td_api::getChatMessageCount &request);
+
+ void on_request(uint64 id, const td_api::getChatMessagePosition &request);
+
+ void on_request(uint64 id, const td_api::getChatScheduledMessages &request);
+
+ void on_request(uint64 id, const td_api::getEmojiReaction &request);
+
+ void on_request(uint64 id, const td_api::getCustomEmojiReactionAnimations &request);
+
+ void on_request(uint64 id, const td_api::getMessageAvailableReactions &request);
+
+ void on_request(uint64 id, const td_api::clearRecentReactions &request);
+
+ void on_request(uint64 id, td_api::addMessageReaction &request);
+
+ void on_request(uint64 id, td_api::removeMessageReaction &request);
+
+ void on_request(uint64 id, td_api::getMessageAddedReactions &request);
+
+ void on_request(uint64 id, td_api::setDefaultReactionType &request);
+
+ void on_request(uint64 id, td_api::getMessagePublicForwards &request);
+
+ void on_request(uint64 id, const td_api::removeNotification &request);
+
+ void on_request(uint64 id, const td_api::removeNotificationGroup &request);
+
void on_request(uint64 id, const td_api::deleteMessages &request);
- void on_request(uint64 id, const td_api::deleteChatMessagesFromUser &request);
+ void on_request(uint64 id, const td_api::deleteChatMessagesBySender &request);
+
+ void on_request(uint64 id, const td_api::deleteChatMessagesByDate &request);
void on_request(uint64 id, const td_api::readAllChatMentions &request);
+ void on_request(uint64 id, const td_api::readAllMessageThreadMentions &request);
+
+ void on_request(uint64 id, const td_api::readAllChatReactions &request);
+
+ void on_request(uint64 id, const td_api::readAllMessageThreadReactions &request);
+
+ void on_request(uint64 id, const td_api::getChatAvailableMessageSenders &request);
+
+ void on_request(uint64 id, const td_api::setChatMessageSender &request);
+
void on_request(uint64 id, td_api::sendMessage &request);
void on_request(uint64 id, td_api::sendMessageAlbum &request);
@@ -477,12 +710,14 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::sendInlineQueryResultMessage &request);
- void on_request(uint64 id, const td_api::sendChatSetTtlMessage &request);
+ void on_request(uint64 id, td_api::addLocalMessage &request);
void on_request(uint64 id, td_api::editMessageText &request);
void on_request(uint64 id, td_api::editMessageLiveLocation &request);
+ void on_request(uint64 id, td_api::editMessageMedia &request);
+
void on_request(uint64 id, td_api::editMessageCaption &request);
void on_request(uint64 id, td_api::editMessageReplyMarkup &request);
@@ -491,10 +726,24 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::editInlineMessageLiveLocation &request);
+ void on_request(uint64 id, td_api::editInlineMessageMedia &request);
+
void on_request(uint64 id, td_api::editInlineMessageCaption &request);
void on_request(uint64 id, td_api::editInlineMessageReplyMarkup &request);
+ void on_request(uint64 id, td_api::editMessageSchedulingState &request);
+
+ void on_request(uint64 id, const td_api::getForumTopicDefaultIcons &request);
+
+ void on_request(uint64 id, td_api::createForumTopic &request);
+
+ void on_request(uint64 id, td_api::editForumTopic &request);
+
+ void on_request(uint64 id, const td_api::toggleForumTopicIsClosed &request);
+
+ void on_request(uint64 id, const td_api::deleteForumTopic &request);
+
void on_request(uint64 id, td_api::setGameScore &request);
void on_request(uint64 id, td_api::setInlineGameScore &request);
@@ -509,7 +758,9 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::sendChatScreenshotTakenNotification &request);
- void on_request(uint64 id, const td_api::forwardMessages &request);
+ void on_request(uint64 id, td_api::forwardMessages &request);
+
+ void on_request(uint64 id, const td_api::resendMessages &request);
void on_request(uint64 id, td_api::getWebPagePreview &request);
@@ -529,43 +780,193 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::createNewSecretChat &request);
- void on_request(uint64 id, td_api::createCall &request);
+ void on_request(uint64 id, const td_api::createCall &request);
- void on_request(uint64 id, td_api::discardCall &request);
+ void on_request(uint64 id, const td_api::acceptCall &request);
- void on_request(uint64 id, td_api::acceptCall &request);
+ void on_request(uint64 id, td_api::sendCallSignalingData &request);
+
+ void on_request(uint64 id, const td_api::discardCall &request);
void on_request(uint64 id, td_api::sendCallRating &request);
void on_request(uint64 id, td_api::sendCallDebugInformation &request);
+ void on_request(uint64 id, td_api::sendCallLog &request);
+
+ void on_request(uint64 id, const td_api::getVideoChatAvailableParticipants &request);
+
+ void on_request(uint64 id, const td_api::setVideoChatDefaultParticipant &request);
+
+ void on_request(uint64 id, td_api::createVideoChat &request);
+
+ void on_request(uint64 id, const td_api::getVideoChatRtmpUrl &request);
+
+ void on_request(uint64 id, const td_api::replaceVideoChatRtmpUrl &request);
+
+ void on_request(uint64 id, const td_api::getGroupCall &request);
+
+ void on_request(uint64 id, const td_api::startScheduledGroupCall &request);
+
+ void on_request(uint64 id, const td_api::toggleGroupCallEnabledStartNotification &request);
+
+ void on_request(uint64 id, td_api::joinGroupCall &request);
+
+ void on_request(uint64 id, td_api::startGroupCallScreenSharing &request);
+
+ void on_request(uint64 id, const td_api::endGroupCallScreenSharing &request);
+
+ void on_request(uint64 id, td_api::setGroupCallTitle &request);
+
+ void on_request(uint64 id, const td_api::toggleGroupCallMuteNewParticipants &request);
+
+ void on_request(uint64 id, const td_api::revokeGroupCallInviteLink &request);
+
+ void on_request(uint64 id, const td_api::inviteGroupCallParticipants &request);
+
+ void on_request(uint64 id, const td_api::getGroupCallInviteLink &request);
+
+ void on_request(uint64 id, td_api::startGroupCallRecording &request);
+
+ void on_request(uint64 id, const td_api::toggleGroupCallScreenSharingIsPaused &request);
+
+ void on_request(uint64 id, const td_api::endGroupCallRecording &request);
+
+ void on_request(uint64 id, const td_api::toggleGroupCallIsMyVideoPaused &request);
+
+ void on_request(uint64 id, const td_api::toggleGroupCallIsMyVideoEnabled &request);
+
+ void on_request(uint64 id, const td_api::setGroupCallParticipantIsSpeaking &request);
+
+ void on_request(uint64 id, const td_api::toggleGroupCallParticipantIsMuted &request);
+
+ void on_request(uint64 id, const td_api::setGroupCallParticipantVolumeLevel &request);
+
+ void on_request(uint64 id, const td_api::toggleGroupCallParticipantIsHandRaised &request);
+
+ void on_request(uint64 id, const td_api::loadGroupCallParticipants &request);
+
+ void on_request(uint64 id, const td_api::leaveGroupCall &request);
+
+ void on_request(uint64 id, const td_api::endGroupCall &request);
+
+ void on_request(uint64 id, const td_api::getGroupCallStreams &request);
+
+ void on_request(uint64 id, td_api::getGroupCallStreamSegment &request);
+
void on_request(uint64 id, const td_api::upgradeBasicGroupChatToSupergroupChat &request);
+ void on_request(uint64 id, const td_api::getChatListsToAddChat &request);
+
+ void on_request(uint64 id, const td_api::addChatToList &request);
+
+ void on_request(uint64 id, const td_api::getChatFilter &request);
+
+ void on_request(uint64 id, const td_api::getRecommendedChatFilters &request);
+
+ void on_request(uint64 id, td_api::createChatFilter &request);
+
+ void on_request(uint64 id, td_api::editChatFilter &request);
+
+ void on_request(uint64 id, const td_api::deleteChatFilter &request);
+
+ void on_request(uint64 id, const td_api::reorderChatFilters &request);
+
void on_request(uint64 id, td_api::setChatTitle &request);
- void on_request(uint64 id, td_api::setChatPhoto &request);
+ void on_request(uint64 id, const td_api::setChatPhoto &request);
+
+ void on_request(uint64 id, const td_api::setChatMessageTtl &request);
+
+ void on_request(uint64 id, const td_api::setChatPermissions &request);
+
+ void on_request(uint64 id, td_api::setChatTheme &request);
void on_request(uint64 id, td_api::setChatDraftMessage &request);
+ void on_request(uint64 id, const td_api::toggleChatHasProtectedContent &request);
+
void on_request(uint64 id, const td_api::toggleChatIsPinned &request);
+ void on_request(uint64 id, const td_api::toggleChatIsMarkedAsUnread &request);
+
+ void on_request(uint64 id, const td_api::toggleMessageSenderIsBlocked &request);
+
+ void on_request(uint64 id, const td_api::toggleChatDefaultDisableNotification &request);
+
void on_request(uint64 id, const td_api::setPinnedChats &request);
+ void on_request(uint64 id, const td_api::getAttachmentMenuBot &request);
+
+ void on_request(uint64 id, const td_api::toggleBotIsAddedToAttachmentMenu &request);
+
+ void on_request(uint64 id, td_api::setChatAvailableReactions &request);
+
void on_request(uint64 id, td_api::setChatClientData &request);
+ void on_request(uint64 id, td_api::setChatDescription &request);
+
+ void on_request(uint64 id, const td_api::setChatDiscussionGroup &request);
+
+ void on_request(uint64 id, td_api::setChatLocation &request);
+
+ void on_request(uint64 id, const td_api::setChatSlowModeDelay &request);
+
+ void on_request(uint64 id, const td_api::pinChatMessage &request);
+
+ void on_request(uint64 id, const td_api::unpinChatMessage &request);
+
+ void on_request(uint64 id, const td_api::unpinAllChatMessages &request);
+
+ void on_request(uint64 id, const td_api::unpinAllMessageThreadMessages &request);
+
+ void on_request(uint64 id, const td_api::joinChat &request);
+
+ void on_request(uint64 id, const td_api::leaveChat &request);
+
void on_request(uint64 id, const td_api::addChatMember &request);
void on_request(uint64 id, const td_api::addChatMembers &request);
void on_request(uint64 id, td_api::setChatMemberStatus &request);
+ void on_request(uint64 id, const td_api::banChatMember &request);
+
+ void on_request(uint64 id, const td_api::canTransferOwnership &request);
+
+ void on_request(uint64 id, td_api::transferChatOwnership &request);
+
void on_request(uint64 id, const td_api::getChatMember &request);
void on_request(uint64 id, td_api::searchChatMembers &request);
- void on_request(uint64 id, td_api::getChatAdministrators &request);
+ void on_request(uint64 id, const td_api::getChatAdministrators &request);
+
+ void on_request(uint64 id, const td_api::replacePrimaryChatInviteLink &request);
+
+ void on_request(uint64 id, td_api::createChatInviteLink &request);
+
+ void on_request(uint64 id, td_api::editChatInviteLink &request);
+
+ void on_request(uint64 id, td_api::getChatInviteLink &request);
+
+ void on_request(uint64 id, const td_api::getChatInviteLinkCounts &request);
+
+ void on_request(uint64 id, td_api::getChatInviteLinks &request);
+
+ void on_request(uint64 id, td_api::getChatInviteLinkMembers &request);
+
+ void on_request(uint64 id, td_api::getChatJoinRequests &request);
+
+ void on_request(uint64 id, const td_api::processChatJoinRequest &request);
+
+ void on_request(uint64 id, td_api::processChatJoinRequests &request);
+
+ void on_request(uint64 id, td_api::revokeChatInviteLink &request);
+
+ void on_request(uint64 id, td_api::deleteRevokedChatInviteLink &request);
- void on_request(uint64 id, const td_api::generateChatInviteLink &request);
+ void on_request(uint64 id, const td_api::deleteAllRevokedChatInviteLinks &request);
void on_request(uint64 id, td_api::checkChatInviteLink &request);
@@ -573,28 +974,58 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::getChatEventLog &request);
+ void on_request(uint64 id, const td_api::clearAllDraftMessages &request);
+
void on_request(uint64 id, const td_api::downloadFile &request);
+ void on_request(uint64 id, const td_api::getFileDownloadedPrefixSize &request);
+
void on_request(uint64 id, const td_api::cancelDownloadFile &request);
- void on_request(uint64 id, td_api::uploadFile &request);
+ void on_request(uint64 id, const td_api::getSuggestedFileName &request);
+
+ void on_request(uint64 id, td_api::preliminaryUploadFile &request);
- void on_request(uint64 id, const td_api::cancelUploadFile &request);
+ void on_request(uint64 id, const td_api::cancelPreliminaryUploadFile &request);
+
+ void on_request(uint64 id, td_api::writeGeneratedFilePart &request);
void on_request(uint64 id, const td_api::setFileGenerationProgress &request);
void on_request(uint64 id, td_api::finishFileGeneration &request);
+ void on_request(uint64 id, const td_api::readFilePart &request);
+
void on_request(uint64 id, const td_api::deleteFile &request);
- void on_request(uint64 id, const td_api::blockUser &request);
+ void on_request(uint64 id, const td_api::addFileToDownloads &request);
+
+ void on_request(uint64 id, const td_api::toggleDownloadIsPaused &request);
+
+ void on_request(uint64 id, const td_api::toggleAllDownloadsArePaused &request);
+
+ void on_request(uint64 id, const td_api::removeFileFromDownloads &request);
+
+ void on_request(uint64 id, const td_api::removeAllFilesFromDownloads &request);
+
+ void on_request(uint64 id, td_api::searchFileDownloads &request);
+
+ void on_request(uint64 id, td_api::getMessageFileType &request);
+
+ void on_request(uint64 id, const td_api::getMessageImportConfirmationText &request);
- void on_request(uint64 id, const td_api::unblockUser &request);
+ void on_request(uint64 id, const td_api::importMessages &request);
- void on_request(uint64 id, const td_api::getBlockedUsers &request);
+ void on_request(uint64 id, const td_api::blockMessageSenderFromReplies &request);
+
+ void on_request(uint64 id, const td_api::getBlockedMessageSenders &request);
+
+ void on_request(uint64 id, td_api::addContact &request);
void on_request(uint64 id, td_api::importContacts &request);
+ void on_request(uint64 id, const td_api::getContacts &request);
+
void on_request(uint64 id, td_api::searchContacts &request);
void on_request(uint64 id, td_api::removeContacts &request);
@@ -605,6 +1036,10 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::clearImportedContacts &request);
+ void on_request(uint64 id, td_api::searchUserByPhoneNumber &request);
+
+ void on_request(uint64 id, const td_api::sharePhoneNumber &request);
+
void on_request(uint64 id, const td_api::getRecentInlineBots &request);
void on_request(uint64 id, td_api::setName &request);
@@ -613,42 +1048,76 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::setUsername &request);
+ void on_request(uint64 id, td_api::toggleUsernameIsActive &request);
+
+ void on_request(uint64 id, td_api::reorderActiveUsernames &request);
+
+ void on_request(uint64 id, const td_api::setEmojiStatus &request);
+
+ void on_request(uint64 id, const td_api::getThemedEmojiStatuses &request);
+
+ void on_request(uint64 id, const td_api::getDefaultEmojiStatuses &request);
+
+ void on_request(uint64 id, const td_api::getRecentEmojiStatuses &request);
+
+ void on_request(uint64 id, const td_api::clearRecentEmojiStatuses &request);
+
+ void on_request(uint64 id, td_api::setCommands &request);
+
+ void on_request(uint64 id, td_api::deleteCommands &request);
+
+ void on_request(uint64 id, td_api::getCommands &request);
+
+ void on_request(uint64 id, td_api::setMenuButton &request);
+
+ void on_request(uint64 id, const td_api::getMenuButton &request);
+
+ void on_request(uint64 id, const td_api::setDefaultGroupAdministratorRights &request);
+
+ void on_request(uint64 id, const td_api::setDefaultChannelAdministratorRights &request);
+
+ void on_request(uint64 id, const td_api::setLocation &request);
+
void on_request(uint64 id, td_api::setProfilePhoto &request);
void on_request(uint64 id, const td_api::deleteProfilePhoto &request);
void on_request(uint64 id, const td_api::getUserProfilePhotos &request);
- void on_request(uint64 id, const td_api::toggleBasicGroupAdministrators &request);
-
void on_request(uint64 id, td_api::setSupergroupUsername &request);
- void on_request(uint64 id, const td_api::setSupergroupStickerSet &request);
+ void on_request(uint64 id, td_api::toggleSupergroupUsernameIsActive &request);
+
+ void on_request(uint64 id, const td_api::disableAllSupergroupUsernames &request);
- void on_request(uint64 id, const td_api::toggleSupergroupInvites &request);
+ void on_request(uint64 id, td_api::reorderSupergroupActiveUsernames &request);
+
+ void on_request(uint64 id, const td_api::setSupergroupStickerSet &request);
void on_request(uint64 id, const td_api::toggleSupergroupSignMessages &request);
- void on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailable &request);
+ void on_request(uint64 id, const td_api::toggleSupergroupJoinToSendMessages &request);
- void on_request(uint64 id, td_api::setSupergroupDescription &request);
+ void on_request(uint64 id, const td_api::toggleSupergroupJoinByRequest &request);
- void on_request(uint64 id, const td_api::pinSupergroupMessage &request);
+ void on_request(uint64 id, const td_api::toggleSupergroupIsAllHistoryAvailable &request);
+
+ void on_request(uint64 id, const td_api::toggleSupergroupIsForum &request);
- void on_request(uint64 id, const td_api::unpinSupergroupMessage &request);
+ void on_request(uint64 id, const td_api::toggleSupergroupIsBroadcastGroup &request);
void on_request(uint64 id, const td_api::reportSupergroupSpam &request);
void on_request(uint64 id, td_api::getSupergroupMembers &request);
- void on_request(uint64 id, const td_api::deleteSupergroup &request);
-
void on_request(uint64 id, td_api::closeSecretChat &request);
void on_request(uint64 id, td_api::getStickers &request);
void on_request(uint64 id, td_api::searchStickers &request);
+ void on_request(uint64 id, const td_api::getPremiumStickers &request);
+
void on_request(uint64 id, const td_api::getInstalledStickerSets &request);
void on_request(uint64 id, const td_api::getArchivedStickerSets &request);
@@ -673,10 +1142,16 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::uploadStickerFile &request);
+ void on_request(uint64 id, td_api::getSuggestedStickerSetName &request);
+
+ void on_request(uint64 id, td_api::checkStickerSetName &request);
+
void on_request(uint64 id, td_api::createNewStickerSet &request);
void on_request(uint64 id, td_api::addStickerToSet &request);
+ void on_request(uint64 id, td_api::setStickerSetThumbnail &request);
+
void on_request(uint64 id, td_api::setStickerPositionInSet &request);
void on_request(uint64 id, td_api::removeStickerFromSet &request);
@@ -697,32 +1172,102 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::getStickerEmojis &request);
+ void on_request(uint64 id, td_api::searchEmojis &request);
+
+ void on_request(uint64 id, td_api::getAnimatedEmoji &request);
+
+ void on_request(uint64 id, td_api::getEmojiSuggestionsUrl &request);
+
+ void on_request(uint64 id, const td_api::getCustomEmojiStickers &request);
+
void on_request(uint64 id, const td_api::getFavoriteStickers &request);
void on_request(uint64 id, td_api::addFavoriteSticker &request);
void on_request(uint64 id, td_api::removeFavoriteSticker &request);
- void on_request(uint64 id, const td_api::getNotificationSettings &request);
+ void on_request(uint64 id, const td_api::getSavedNotificationSound &request);
- void on_request(uint64 id, td_api::setNotificationSettings &request);
+ void on_request(uint64 id, const td_api::getSavedNotificationSounds &request);
- void on_request(uint64 id, const td_api::resetAllNotificationSettings &request);
+ void on_request(uint64 id, td_api::addSavedNotificationSound &request);
+
+ void on_request(uint64 id, const td_api::removeSavedNotificationSound &request);
+
+ void on_request(uint64 id, const td_api::getChatNotificationSettingsExceptions &request);
- void on_request(uint64 id, const td_api::getChatReportSpamState &request);
+ void on_request(uint64 id, const td_api::getScopeNotificationSettings &request);
- void on_request(uint64 id, const td_api::changeChatReportSpamState &request);
+ void on_request(uint64 id, td_api::setChatNotificationSettings &request);
+
+ void on_request(uint64 id, td_api::setScopeNotificationSettings &request);
+
+ void on_request(uint64 id, const td_api::resetAllNotificationSettings &request);
+
+ void on_request(uint64 id, const td_api::removeChatActionBar &request);
void on_request(uint64 id, td_api::reportChat &request);
+ void on_request(uint64 id, td_api::reportChatPhoto &request);
+
+ void on_request(uint64 id, const td_api::reportMessageReactions &request);
+
+ void on_request(uint64 id, const td_api::getChatStatistics &request);
+
+ void on_request(uint64 id, const td_api::getMessageStatistics &request);
+
+ void on_request(uint64 id, td_api::getStatisticalGraph &request);
+
+ void on_request(uint64 id, const td_api::getMapThumbnailFile &request);
+
+ void on_request(uint64 id, const td_api::getLocalizationTargetInfo &request);
+
+ void on_request(uint64 id, td_api::getLanguagePackInfo &request);
+
+ void on_request(uint64 id, td_api::getLanguagePackStrings &request);
+
+ void on_request(uint64 id, td_api::synchronizeLanguagePack &request);
+
+ void on_request(uint64 id, td_api::addCustomServerLanguagePack &request);
+
+ void on_request(uint64 id, td_api::setCustomLanguagePack &request);
+
+ void on_request(uint64 id, td_api::editCustomLanguagePackInfo &request);
+
+ void on_request(uint64 id, td_api::setCustomLanguagePackString &request);
+
+ void on_request(uint64 id, td_api::deleteLanguagePack &request);
+
void on_request(uint64 id, td_api::getOption &request);
void on_request(uint64 id, td_api::setOption &request);
+ void on_request(uint64 id, td_api::setPollAnswer &request);
+
+ void on_request(uint64 id, td_api::getPollVoters &request);
+
+ void on_request(uint64 id, td_api::stopPoll &request);
+
+ void on_request(uint64 id, const td_api::hideSuggestedAction &request);
+
+ void on_request(uint64 id, const td_api::getLoginUrlInfo &request);
+
+ void on_request(uint64 id, const td_api::getLoginUrl &request);
+
void on_request(uint64 id, td_api::getInlineQueryResults &request);
void on_request(uint64 id, td_api::answerInlineQuery &request);
+ void on_request(uint64 id, td_api::getWebAppUrl &request);
+
+ void on_request(uint64 id, td_api::sendWebAppData &request);
+
+ void on_request(uint64 id, td_api::openWebApp &request);
+
+ void on_request(uint64 id, const td_api::closeWebApp &request);
+
+ void on_request(uint64 id, td_api::answerWebAppQuery &request);
+
void on_request(uint64 id, td_api::getCallbackQueryAnswer &request);
void on_request(uint64 id, td_api::answerCallbackQuery &request);
@@ -731,7 +1276,9 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::answerPreCheckoutQuery &request);
- void on_request(uint64 id, const td_api::getPaymentForm &request);
+ void on_request(uint64 id, td_api::getBankCardInfo &request);
+
+ void on_request(uint64 id, td_api::getPaymentForm &request);
void on_request(uint64 id, td_api::validateOrderInfo &request);
@@ -745,9 +1292,57 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::deleteSavedCredentials &request);
+ void on_request(uint64 id, td_api::createInvoiceLink &request);
+
+ void on_request(uint64 id, td_api::getPassportElement &request);
+
+ void on_request(uint64 id, td_api::getAllPassportElements &request);
+
+ void on_request(uint64 id, td_api::setPassportElement &request);
+
+ void on_request(uint64 id, const td_api::deletePassportElement &request);
+
+ void on_request(uint64 id, td_api::setPassportElementErrors &request);
+
+ void on_request(uint64 id, td_api::getPreferredCountryLanguage &request);
+
+ void on_request(uint64 id, td_api::sendPhoneNumberVerificationCode &request);
+
+ void on_request(uint64 id, const td_api::resendPhoneNumberVerificationCode &request);
+
+ void on_request(uint64 id, td_api::checkPhoneNumberVerificationCode &request);
+
+ void on_request(uint64 id, td_api::sendEmailAddressVerificationCode &request);
+
+ void on_request(uint64 id, const td_api::resendEmailAddressVerificationCode &request);
+
+ void on_request(uint64 id, td_api::checkEmailAddressVerificationCode &request);
+
+ void on_request(uint64 id, td_api::getPassportAuthorizationForm &request);
+
+ void on_request(uint64 id, td_api::getPassportAuthorizationFormAvailableElements &request);
+
+ void on_request(uint64 id, td_api::sendPassportAuthorizationForm &request);
+
+ void on_request(uint64 id, td_api::sendPhoneNumberConfirmationCode &request);
+
+ void on_request(uint64 id, const td_api::resendPhoneNumberConfirmationCode &request);
+
+ void on_request(uint64 id, td_api::checkPhoneNumberConfirmationCode &request);
+
void on_request(uint64 id, const td_api::getSupportUser &request);
- void on_request(uint64 id, const td_api::getWallpapers &request);
+ void on_request(uint64 id, const td_api::getBackgrounds &request);
+
+ void on_request(uint64 id, td_api::getBackgroundUrl &request);
+
+ void on_request(uint64 id, td_api::searchBackground &request);
+
+ void on_request(uint64 id, td_api::setBackground &request);
+
+ void on_request(uint64 id, const td_api::removeBackground &request);
+
+ void on_request(uint64 id, const td_api::resetBackgrounds &request);
void on_request(uint64 id, td_api::getRecentlyVisitedTMeUrls &request);
@@ -763,31 +1358,112 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::removeRecentHashtag &request);
+ void on_request(uint64 id, const td_api::getPremiumLimit &request);
+
+ void on_request(uint64 id, const td_api::getPremiumFeatures &request);
+
+ void on_request(uint64 id, const td_api::getPremiumStickerExamples &request);
+
+ void on_request(uint64 id, const td_api::viewPremiumFeature &request);
+
+ void on_request(uint64 id, const td_api::clickPremiumSubscriptionButton &request);
+
+ void on_request(uint64 id, const td_api::getPremiumState &request);
+
+ void on_request(uint64 id, td_api::canPurchasePremium &request);
+
+ void on_request(uint64 id, td_api::assignAppStoreTransaction &request);
+
+ void on_request(uint64 id, td_api::assignGooglePlayTransaction &request);
+
+ void on_request(uint64 id, td_api::acceptTermsOfService &request);
+
+ void on_request(uint64 id, const td_api::getCountries &request);
+
void on_request(uint64 id, const td_api::getCountryCode &request);
- void on_request(uint64 id, const td_api::getInviteText &request);
+ void on_request(uint64 id, const td_api::getPhoneNumberInfo &request);
+
+ void on_request(uint64 id, const td_api::getApplicationDownloadLink &request);
+
+ void on_request(uint64 id, td_api::getDeepLinkInfo &request);
+
+ void on_request(uint64 id, const td_api::getApplicationConfig &request);
+
+ void on_request(uint64 id, td_api::saveApplicationLogEvent &request);
+
+ void on_request(uint64 id, td_api::addProxy &request);
+
+ void on_request(uint64 id, td_api::editProxy &request);
+
+ void on_request(uint64 id, const td_api::enableProxy &request);
+
+ void on_request(uint64 id, const td_api::disableProxy &request);
+
+ void on_request(uint64 id, const td_api::removeProxy &request);
- void on_request(uint64 id, const td_api::getTermsOfService &request);
+ void on_request(uint64 id, const td_api::getProxies &request);
- void on_request(uint64 id, const td_api::getProxy &request);
+ void on_request(uint64 id, const td_api::getProxyLink &request);
- void on_request(uint64 id, const td_api::setProxy &request);
+ void on_request(uint64 id, const td_api::pingProxy &request);
+
+ void on_request(uint64 id, const td_api::getUserSupportInfo &request);
+
+ void on_request(uint64 id, td_api::setUserSupportInfo &request);
void on_request(uint64 id, const td_api::getTextEntities &request);
- void on_request(uint64 id, td_api::parseTextEntities &request);
+ void on_request(uint64 id, const td_api::parseTextEntities &request);
+
+ void on_request(uint64 id, const td_api::parseMarkdown &request);
+
+ void on_request(uint64 id, const td_api::getMarkdownText &request);
void on_request(uint64 id, const td_api::getFileMimeType &request);
void on_request(uint64 id, const td_api::getFileExtension &request);
+ void on_request(uint64 id, const td_api::cleanFileName &request);
+
+ void on_request(uint64 id, const td_api::getLanguagePackString &request);
+
+ void on_request(uint64 id, const td_api::getPhoneNumberInfoSync &request);
+
+ void on_request(uint64 id, const td_api::getPushReceiverId &request);
+
+ void on_request(uint64 id, const td_api::getChatFilterDefaultIconName &request);
+
+ void on_request(uint64 id, const td_api::getJsonValue &request);
+
+ void on_request(uint64 id, const td_api::getJsonString &request);
+
+ void on_request(uint64 id, const td_api::getThemeParametersJsonString &request);
+
+ void on_request(uint64 id, const td_api::setLogStream &request);
+
+ void on_request(uint64 id, const td_api::getLogStream &request);
+
+ void on_request(uint64 id, const td_api::setLogVerbosityLevel &request);
+
+ void on_request(uint64 id, const td_api::getLogVerbosityLevel &request);
+
+ void on_request(uint64 id, const td_api::getLogTags &request);
+
+ void on_request(uint64 id, const td_api::setLogTagVerbosityLevel &request);
+
+ void on_request(uint64 id, const td_api::getLogTagVerbosityLevel &request);
+
+ void on_request(uint64 id, const td_api::addLogMessage &request);
+
// test
- void on_request(uint64 id, td_api::testNetwork &request);
- void on_request(uint64 id, td_api::testGetDifference &request);
- void on_request(uint64 id, td_api::testUseUpdate &request);
- void on_request(uint64 id, td_api::testUseError &request);
- void on_request(uint64 id, td_api::testCallEmpty &request);
- void on_request(uint64 id, td_api::testSquareInt &request);
+ void on_request(uint64 id, const td_api::testNetwork &request);
+ void on_request(uint64 id, td_api::testProxy &request);
+ void on_request(uint64 id, const td_api::testGetDifference &request);
+ void on_request(uint64 id, const td_api::testUseUpdate &request);
+ void on_request(uint64 id, const td_api::testReturnError &request);
+ void on_request(uint64 id, const td_api::testCallEmpty &request);
+ void on_request(uint64 id, const td_api::testSquareInt &request);
void on_request(uint64 id, td_api::testCallString &request);
void on_request(uint64 id, td_api::testCallBytes &request);
void on_request(uint64 id, td_api::testCallVectorInt &request);
@@ -796,23 +1472,67 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, td_api::testCallVectorStringObject &request);
template <class T>
- static td_api::object_ptr<td_api::Object> do_static_request(const T &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const T &request) {
+ return td_api::make_object<td_api::error>(400, "The method can't be executed synchronously");
+ }
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getOption &request);
static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getTextEntities &request);
static td_api::object_ptr<td_api::Object> do_static_request(td_api::parseTextEntities &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(td_api::parseMarkdown &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(td_api::getMarkdownText &request);
static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getFileMimeType &request);
static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getFileExtension &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::cleanFileName &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getLanguagePackString &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(td_api::getPhoneNumberInfoSync &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getPushReceiverId &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getChatFilterDefaultIconName &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(td_api::getJsonValue &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getJsonString &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getThemeParametersJsonString &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(td_api::setLogStream &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getLogStream &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::setLogVerbosityLevel &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getLogVerbosityLevel &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getLogTags &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::setLogTagVerbosityLevel &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::getLogTagVerbosityLevel &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(const td_api::addLogMessage &request);
+ static td_api::object_ptr<td_api::Object> do_static_request(td_api::testReturnError &request);
+
+ static DbKey as_db_key(string key);
+
+ static int32 get_database_scheduler_id();
+
+ void finish_set_parameters();
+
+ void init(Result<TdDb::OpenedDatabase> r_opened_database);
+
+ void init_options_and_network();
+
+ void init_connection_creator();
+
+ void init_file_manager();
+
+ void init_managers();
- Status init(DbKey key) TD_WARN_UNUSED_RESULT;
void clear();
+
void close_impl(bool destroy_flag);
- Status fix_parameters(TdParameters &parameters) TD_WARN_UNUSED_RESULT;
- Status set_parameters(td_api::object_ptr<td_api::tdlibParameters> parameters) TD_WARN_UNUSED_RESULT;
+
+ static Status fix_parameters(TdParameters &parameters) TD_WARN_UNUSED_RESULT;
+
+ Status set_parameters(td_api::object_ptr<td_api::setTdlibParameters> parameters) TD_WARN_UNUSED_RESULT;
+
+ static td_api::object_ptr<td_api::error> make_error(int32 code, CSlice error) {
+ return td_api::make_object<td_api::error>(code, error.str());
+ }
// Actor
- void start_up() override;
- void tear_down() override;
- void hangup_shared() override;
- void hangup() override;
+ void start_up() final;
+ void tear_down() final;
+ void hangup_shared() final;
+ void hangup() final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TdCallback.h b/protocols/Telegram/tdlib/td/td/telegram/TdCallback.h
index eead22c6c9..a7ba30c836 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/TdCallback.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/TdCallback.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -34,12 +34,6 @@ class TdCallback {
virtual void on_error(std::uint64_t id, td_api::object_ptr<td_api::error> error) = 0;
/**
- * This function is called when TDLib has been fully closed. It's not possible to make any new requests after this function has been called
- * without creating a new instance of ClientActor. The callback will be destroyed as soon as a call to this function returns.
- */
- virtual void on_closed() = 0;
-
- /**
* Destroys the TdCallback.
*/
virtual ~TdCallback() = default;
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TdDb.cpp b/protocols/Telegram/tdlib/td/td/telegram/TdDb.cpp
index c642f2d50a..54467bc1c9 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/TdDb.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/TdDb.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,21 +8,41 @@
#include "td/telegram/DialogDb.h"
#include "td/telegram/files/FileDb.h"
+#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
-#include "td/telegram/MessagesDb.h"
+#include "td/telegram/MessageDb.h"
+#include "td/telegram/MessageThreadDb.h"
+#include "td/telegram/Td.h"
#include "td/telegram/TdParameters.h"
+#include "td/telegram/Version.h"
+
+#include "td/db/binlog/Binlog.h"
+#include "td/db/binlog/ConcurrentBinlog.h"
+#include "td/db/BinlogKeyValue.h"
+#include "td/db/SqliteConnectionSafe.h"
+#include "td/db/SqliteDb.h"
+#include "td/db/SqliteKeyValue.h"
+#include "td/db/SqliteKeyValueAsync.h"
+#include "td/db/SqliteKeyValueSafe.h"
#include "td/actor/actor.h"
#include "td/actor/MultiPromise.h"
-#include "td/db/BinlogKeyValue.h"
-
+#include "td/utils/common.h"
+#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/port/path.h"
#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StringBuilder.h"
+
+#include <algorithm>
namespace td {
+
namespace {
+
std::string get_binlog_path(const TdParameters &parameters) {
return PSTRING() << parameters.database_directory << "td" << (parameters.use_test_dc ? "_test" : "") << ".binlog";
}
@@ -32,20 +52,8 @@ std::string get_sqlite_path(const TdParameters &parameters) {
return parameters.database_directory + db_name + ".sqlite";
}
-Result<EncryptionInfo> check_encryption(string path) {
- Binlog binlog;
- auto status = binlog.init(path, Binlog::Callback());
- if (status.is_error() && status.code() != Binlog::Error::WrongPassword) {
- return Status::Error(400, status.message());
- }
- EncryptionInfo info;
- info.is_encrypted = binlog.get_info().wrong_password;
- binlog.close(false /*need_sync*/).ensure();
- return info;
-}
-
Status init_binlog(Binlog &binlog, string path, BinlogKeyValue<Binlog> &binlog_pmc, BinlogKeyValue<Binlog> &config_pmc,
- TdDb::Events &events, DbKey key) {
+ TdDb::OpenedDatabase &events, DbKey key) {
auto callback = [&](const BinlogEvent &event) {
switch (event.type_) {
case LogEvent::HandlerType::SecretChats:
@@ -66,24 +74,54 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue<Binlog> &binlog_p
case LogEvent::HandlerType::WebPages:
events.web_page_events.push_back(event.clone());
break;
+ case LogEvent::HandlerType::SetPollAnswer:
+ case LogEvent::HandlerType::StopPoll:
+ events.to_poll_manager.push_back(event.clone());
+ break;
case LogEvent::HandlerType::SendMessage:
case LogEvent::HandlerType::DeleteMessage:
- case LogEvent::HandlerType::DeleteMessagesFromServer:
+ case LogEvent::HandlerType::DeleteMessagesOnServer:
case LogEvent::HandlerType::ReadHistoryOnServer:
case LogEvent::HandlerType::ReadMessageContentsOnServer:
case LogEvent::HandlerType::ForwardMessages:
case LogEvent::HandlerType::SendBotStartMessage:
case LogEvent::HandlerType::SendScreenshotTakenNotificationMessage:
case LogEvent::HandlerType::SendInlineQueryResultMessage:
- case LogEvent::HandlerType::DeleteDialogHistoryFromServer:
+ case LogEvent::HandlerType::DeleteDialogHistoryOnServer:
case LogEvent::HandlerType::ReadAllDialogMentionsOnServer:
- case LogEvent::HandlerType::DeleteAllChannelMessagesFromUserOnServer:
+ case LogEvent::HandlerType::DeleteAllChannelMessagesFromSenderOnServer:
case LogEvent::HandlerType::ToggleDialogIsPinnedOnServer:
case LogEvent::HandlerType::ReorderPinnedDialogsOnServer:
case LogEvent::HandlerType::SaveDialogDraftMessageOnServer:
+ case LogEvent::HandlerType::UpdateDialogNotificationSettingsOnServer:
+ case LogEvent::HandlerType::ResetAllNotificationSettingsOnServer:
+ case LogEvent::HandlerType::ToggleDialogReportSpamStateOnServer:
+ case LogEvent::HandlerType::RegetDialog:
case LogEvent::HandlerType::GetChannelDifference:
+ case LogEvent::HandlerType::ReadHistoryInSecretChat:
+ case LogEvent::HandlerType::ToggleDialogIsMarkedAsUnreadOnServer:
+ case LogEvent::HandlerType::SetDialogFolderIdOnServer:
+ case LogEvent::HandlerType::DeleteScheduledMessagesOnServer:
+ case LogEvent::HandlerType::ToggleDialogIsBlockedOnServer:
+ case LogEvent::HandlerType::ReadMessageThreadHistoryOnServer:
+ case LogEvent::HandlerType::BlockMessageSenderFromRepliesOnServer:
+ case LogEvent::HandlerType::UnpinAllDialogMessagesOnServer:
+ case LogEvent::HandlerType::DeleteAllCallMessagesOnServer:
+ case LogEvent::HandlerType::DeleteDialogMessagesByDateOnServer:
+ case LogEvent::HandlerType::ReadAllDialogReactionsOnServer:
+ case LogEvent::HandlerType::DeleteTopicHistoryOnServer:
events.to_messages_manager.push_back(event.clone());
break;
+ case LogEvent::HandlerType::UpdateScopeNotificationSettingsOnServer:
+ events.to_notification_settings_manager.push_back(event.clone());
+ break;
+ case LogEvent::HandlerType::AddMessagePushNotification:
+ case LogEvent::HandlerType::EditMessagePushNotification:
+ events.to_notification_manager.push_back(event.clone());
+ break;
+ case LogEvent::HandlerType::SaveAppLog:
+ events.save_app_log_events.push_back(event.clone());
+ break;
case LogEvent::HandlerType::BinlogPmcMagic:
binlog_pmc.external_init_handle(event);
break;
@@ -91,7 +129,7 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue<Binlog> &binlog_p
config_pmc.external_init_handle(event);
break;
default:
- LOG(FATAL) << "Unsupported logevent type " << event.type_;
+ LOG(FATAL) << "Unsupported log event type " << event.type_;
}
};
@@ -102,42 +140,42 @@ Status init_binlog(Binlog &binlog, string path, BinlogKeyValue<Binlog> &binlog_p
return Status::OK();
}
-Status init_db(SqliteDb &db) {
- TRY_STATUS(db.exec("PRAGMA encoding=\"UTF-8\""));
- TRY_STATUS(db.exec("PRAGMA journal_mode=WAL"));
-
- TRY_STATUS(db.exec("PRAGMA synchronous=NORMAL"));
- TRY_STATUS(db.exec("PRAGMA temp_store=MEMORY"));
- TRY_STATUS(db.exec("PRAGMA secure_delete=1"));
-
- return Status::OK();
-}
-
} // namespace
std::shared_ptr<FileDbInterface> TdDb::get_file_db_shared() {
return file_db_;
}
+
std::shared_ptr<SqliteConnectionSafe> &TdDb::get_sqlite_connection_safe() {
return sql_connection_;
}
-ConcurrentBinlog *TdDb::get_binlog() {
- CHECK(binlog_);
+
+BinlogInterface *TdDb::get_binlog_impl(const char *file, int line) {
+ LOG_CHECK(binlog_) << G()->close_flag() << " " << file << " " << line;
return binlog_.get();
}
-BinlogPmc TdDb::get_binlog_pmc_shared() {
+
+std::shared_ptr<KeyValueSyncInterface> TdDb::get_binlog_pmc_shared() {
+ CHECK(binlog_pmc_);
return binlog_pmc_;
}
-BinlogPmcPtr TdDb::get_binlog_pmc() {
- CHECK(binlog_pmc_);
+
+std::shared_ptr<KeyValueSyncInterface> TdDb::get_config_pmc_shared() {
+ CHECK(config_pmc_);
+ return config_pmc_;
+}
+
+KeyValueSyncInterface *TdDb::get_binlog_pmc_impl(const char *file, int line) {
+ LOG_CHECK(binlog_pmc_) << G()->close_flag() << ' ' << file << ' ' << line;
return binlog_pmc_.get();
}
-BinlogPmcPtr TdDb::get_config_pmc() {
+
+KeyValueSyncInterface *TdDb::get_config_pmc() {
CHECK(config_pmc_);
return config_pmc_.get();
}
-BigPmcPtr TdDb::get_sqlite_sync_pmc() {
+SqliteKeyValue *TdDb::get_sqlite_sync_pmc() {
CHECK(common_kv_safe_);
return &common_kv_safe_->get();
}
@@ -147,15 +185,26 @@ SqliteKeyValueAsyncInterface *TdDb::get_sqlite_pmc() {
return common_kv_async_.get();
}
-MessagesDbSyncInterface *TdDb::get_messages_db_sync() {
- return &messages_db_sync_safe_->get();
+MessageDbSyncInterface *TdDb::get_message_db_sync() {
+ return &message_db_sync_safe_->get();
+}
+
+MessageDbAsyncInterface *TdDb::get_message_db_async() {
+ return message_db_async_.get();
}
-MessagesDbAsyncInterface *TdDb::get_messages_db_async() {
- return messages_db_async_.get();
+
+MessageThreadDbSyncInterface *TdDb::get_message_thread_db_sync() {
+ return &message_thread_db_sync_safe_->get();
+}
+
+MessageThreadDbAsyncInterface *TdDb::get_message_thread_db_async() {
+ return message_thread_db_async_.get();
}
+
DialogDbSyncInterface *TdDb::get_dialog_db_sync() {
return &dialog_db_sync_safe_->get();
}
+
DialogDbAsyncInterface *TdDb::get_dialog_db_async() {
return dialog_db_async_.get();
}
@@ -168,24 +217,35 @@ CSlice TdDb::sqlite_path() const {
}
void TdDb::flush_all() {
- if (messages_db_async_) {
- messages_db_async_->force_flush();
+ LOG(INFO) << "Flush all databases";
+ if (message_db_async_) {
+ message_db_async_->force_flush();
+ }
+ if (message_thread_db_async_) {
+ message_thread_db_async_->force_flush();
+ }
+ if (dialog_db_async_) {
+ dialog_db_async_->force_flush();
}
binlog_->force_flush();
}
+
void TdDb::close_all(Promise<> on_finished) {
+ LOG(INFO) << "Close all databases";
do_close(std::move(on_finished), false /*destroy_flag*/);
}
void TdDb::close_and_destroy_all(Promise<> on_finished) {
+ LOG(INFO) << "Destroy all databases";
do_close(std::move(on_finished), true /*destroy_flag*/);
}
+
void TdDb::do_close(Promise<> on_finished, bool destroy_flag) {
- MultiPromiseActorSafe mpas;
+ MultiPromiseActorSafe mpas{"TdDbCloseMultiPromiseActor"};
mpas.add_promise(PromiseCreator::lambda(
[promise = std::move(on_finished), sql_connection = std::move(sql_connection_), destroy_flag](Unit) mutable {
if (sql_connection) {
- CHECK(sql_connection.unique()) << sql_connection.use_count();
+ LOG_CHECK(sql_connection.unique()) << sql_connection.use_count();
if (destroy_flag) {
sql_connection->close_and_destroy();
} else {
@@ -207,9 +267,14 @@ void TdDb::do_close(Promise<> on_finished, bool destroy_flag) {
common_kv_async_->close(mpas.get_promise());
}
- messages_db_sync_safe_.reset();
- if (messages_db_async_) {
- messages_db_async_->close(mpas.get_promise());
+ message_db_sync_safe_.reset();
+ if (message_db_async_) {
+ message_db_async_->close(mpas.get_promise());
+ }
+
+ message_thread_db_sync_safe_.reset();
+ if (message_thread_db_async_) {
+ message_thread_db_async_->close(mpas.get_promise());
}
dialog_db_sync_safe_.reset();
@@ -231,30 +296,34 @@ void TdDb::do_close(Promise<> on_finished, bool destroy_flag) {
}
binlog_.reset();
}
+
+ lock.set_value(Unit());
}
-Status TdDb::init_sqlite(int32 scheduler_id, const TdParameters &parameters, DbKey key, DbKey old_key,
+Status TdDb::init_sqlite(const TdParameters &parameters, const DbKey &key, const DbKey &old_key,
BinlogKeyValue<Binlog> &binlog_pmc) {
CHECK(!parameters.use_message_db || parameters.use_chat_info_db);
CHECK(!parameters.use_chat_info_db || parameters.use_file_db);
- const string sql_db_name = get_sqlite_path(parameters);
+ const string sql_database_path = get_sqlite_path(parameters);
bool use_sqlite = parameters.use_file_db;
bool use_file_db = parameters.use_file_db;
bool use_dialog_db = parameters.use_message_db;
+ bool use_message_thread_db = parameters.use_message_db && false;
bool use_message_db = parameters.use_message_db;
if (!use_sqlite) {
- unlink(sql_db_name).ignore();
+ SqliteDb::destroy(sql_database_path).ignore();
return Status::OK();
}
- sqlite_path_ = sql_db_name;
- TRY_STATUS(SqliteDb::change_key(sqlite_path_, key, old_key));
- sql_connection_ = std::make_shared<SqliteConnectionSafe>(sql_db_name, key);
+ sqlite_path_ = sql_database_path;
+ TRY_RESULT(db_instance, SqliteDb::change_key(sqlite_path_, true, key, old_key));
+ sql_connection_ = std::make_shared<SqliteConnectionSafe>(sql_database_path, key, db_instance.get_cipher_version());
+ sql_connection_->set(std::move(db_instance));
auto &db = sql_connection_->get();
-
- TRY_STATUS(init_db(db));
+ TRY_STATUS(db.exec("PRAGMA journal_mode=WAL"));
+ TRY_STATUS(db.exec("PRAGMA secure_delete=1"));
// Init databases
// Do initialization once and before everything else to avoid "database is locked" error.
@@ -265,21 +334,28 @@ Status TdDb::init_sqlite(int32 scheduler_id, const TdParameters &parameters, DbK
// Get 'PRAGMA user_version'
TRY_RESULT(user_version, db.user_version());
- LOG(WARNING) << "got PRAGMA user_version = " << user_version;
+ LOG(INFO) << "Got PRAGMA user_version = " << user_version;
// init DialogDb
bool dialog_db_was_created = false;
if (use_dialog_db) {
- TRY_STATUS(init_dialog_db(db, user_version, dialog_db_was_created));
+ TRY_STATUS(init_dialog_db(db, user_version, binlog_pmc, dialog_db_was_created));
} else {
TRY_STATUS(drop_dialog_db(db, user_version));
}
- // init MessagesDb
+ // init MessageThreadDb
+ if (use_message_thread_db) {
+ TRY_STATUS(init_message_thread_db(db, user_version));
+ } else {
+ TRY_STATUS(drop_message_thread_db(db, user_version));
+ }
+
+ // init MessageDb
if (use_message_db) {
- TRY_STATUS(init_messages_db(db, user_version));
+ TRY_STATUS(init_message_db(db, user_version));
} else {
- TRY_STATUS(drop_messages_db(db, user_version));
+ TRY_STATUS(drop_message_db(db, user_version));
}
// init filesDb
@@ -292,55 +368,90 @@ Status TdDb::init_sqlite(int32 scheduler_id, const TdParameters &parameters, DbK
// Update 'PRAGMA user_version'
auto db_version = current_db_version();
if (db_version != user_version) {
- LOG(WARNING) << "set PRAGMA user_version = " << db_version;
+ LOG(WARNING) << "Set PRAGMA user_version = " << db_version;
TRY_STATUS(db.set_user_version(db_version));
}
if (dialog_db_was_created) {
- binlog_pmc.erase("unread_message_count");
- binlog_pmc.erase("last_server_dialog_date");
- }
- if (db_version == 0) {
+ binlog_pmc.erase_by_prefix("pinned_dialog_ids");
+ binlog_pmc.erase_by_prefix("last_server_dialog_date");
+ binlog_pmc.erase_by_prefix("unread_message_count");
+ binlog_pmc.erase_by_prefix("unread_dialog_count");
+ binlog_pmc.erase("sponsored_dialog_id");
binlog_pmc.erase_by_prefix("top_dialogs");
+ binlog_pmc.erase("dlds_counter");
+ binlog_pmc.erase_by_prefix("dlds#");
+ }
+ if (user_version == 0) {
binlog_pmc.erase("next_contacts_sync_date");
+ binlog_pmc.erase("saved_contact_count");
+ binlog_pmc.erase("old_featured_sticker_set_count");
+ binlog_pmc.erase("invalidate_old_featured_sticker_sets");
}
binlog_pmc.force_sync({});
TRY_STATUS(db.exec("COMMIT TRANSACTION"));
- file_db_ = create_file_db(sql_connection_, scheduler_id);
+ file_db_ = create_file_db(sql_connection_);
common_kv_safe_ = std::make_shared<SqliteKeyValueSafe>("common", sql_connection_);
- common_kv_async_ = create_sqlite_key_value_async(common_kv_safe_, scheduler_id);
+ common_kv_async_ = create_sqlite_key_value_async(common_kv_safe_);
if (use_dialog_db) {
dialog_db_sync_safe_ = create_dialog_db_sync(sql_connection_);
- dialog_db_async_ = create_dialog_db_async(dialog_db_sync_safe_, scheduler_id);
+ dialog_db_async_ = create_dialog_db_async(dialog_db_sync_safe_);
+ }
+
+ if (use_message_thread_db) {
+ message_thread_db_sync_safe_ = create_message_thread_db_sync(sql_connection_);
+ message_thread_db_async_ = create_message_thread_db_async(message_thread_db_sync_safe_);
}
if (use_message_db) {
- messages_db_sync_safe_ = create_messages_db_sync(sql_connection_);
- messages_db_async_ = create_messages_db_async(messages_db_sync_safe_, scheduler_id);
+ message_db_sync_safe_ = create_message_db_sync(sql_connection_);
+ message_db_async_ = create_message_db_async(message_db_sync_safe_);
}
return Status::OK();
}
-Status TdDb::init(int32 scheduler_id, const TdParameters &parameters, DbKey key, Events &events) {
+void TdDb::open(int32 scheduler_id, TdParameters parameters, DbKey key, Promise<OpenedDatabase> &&promise) {
+ Scheduler::instance()->run_on_scheduler(
+ scheduler_id, [parameters = std::move(parameters), key = std::move(key), promise = std::move(promise)](
+ Unit) mutable { TdDb::open_impl(std::move(parameters), std::move(key), std::move(promise)); });
+}
+
+void TdDb::open_impl(TdParameters parameters, DbKey key, Promise<OpenedDatabase> &&promise) {
+ TRY_STATUS_PROMISE(promise, check_parameters(parameters));
+
+ OpenedDatabase result;
+ result.database_directory = parameters.database_directory;
+ result.files_directory = parameters.files_directory;
+
// Init pmc
Binlog *binlog_ptr = nullptr;
auto binlog = std::shared_ptr<Binlog>(new Binlog, [&](Binlog *ptr) { binlog_ptr = ptr; });
- auto binlog_pmc = std::make_unique<BinlogKeyValue<Binlog>>();
- auto config_pmc = std::make_unique<BinlogKeyValue<Binlog>>();
+ auto binlog_pmc = make_unique<BinlogKeyValue<Binlog>>();
+ auto config_pmc = make_unique<BinlogKeyValue<Binlog>>();
binlog_pmc->external_init_begin(static_cast<int32>(LogEvent::HandlerType::BinlogPmcMagic));
config_pmc->external_init_begin(static_cast<int32>(LogEvent::HandlerType::ConfigPmcMagic));
bool encrypt_binlog = !key.is_empty();
- TRY_STATUS(init_binlog(*binlog, get_binlog_path(parameters), *binlog_pmc, *config_pmc, events, std::move(key)));
+ VLOG(td_init) << "Start binlog loading";
+ TRY_STATUS_PROMISE(
+ promise, init_binlog(*binlog, get_binlog_path(parameters), *binlog_pmc, *config_pmc, result, std::move(key)));
+ VLOG(td_init) << "Finish binlog loading";
binlog_pmc->external_init_finish(binlog);
+ VLOG(td_init) << "Finish initialization of binlog PMC";
config_pmc->external_init_finish(binlog);
+ VLOG(td_init) << "Finish initialization of config PMC";
+
+ if (parameters.use_file_db && binlog_pmc->get("auth").empty()) {
+ LOG(INFO) << "Destroy SQLite database, because wasn't authorized yet";
+ SqliteDb::destroy(get_sqlite_path(parameters)).ignore();
+ }
DbKey new_sqlite_key;
DbKey old_sqlite_key;
@@ -361,21 +472,29 @@ Status TdDb::init(int32 scheduler_id, const TdParameters &parameters, DbKey key,
drop_sqlite_key = true;
}
}
- auto init_sqlite_status = init_sqlite(scheduler_id, parameters, new_sqlite_key, old_sqlite_key, *binlog_pmc);
+ VLOG(td_init) << "Start to init database";
+ auto db = make_unique<TdDb>();
+ auto init_sqlite_status = db->init_sqlite(parameters, new_sqlite_key, old_sqlite_key, *binlog_pmc);
+ VLOG(td_init) << "Finish to init database";
if (init_sqlite_status.is_error()) {
- LOG(ERROR) << "Destroy bad sqlite db because of: " << init_sqlite_status;
+ LOG(ERROR) << "Destroy bad SQLite database because of " << init_sqlite_status;
+ if (db->sql_connection_ != nullptr) {
+ db->sql_connection_->get().close();
+ }
SqliteDb::destroy(get_sqlite_path(parameters)).ignore();
- TRY_STATUS(init_sqlite(scheduler_id, parameters, new_sqlite_key, old_sqlite_key, *binlog_pmc));
+ TRY_STATUS_PROMISE(promise, db->init_sqlite(parameters, new_sqlite_key, old_sqlite_key, *binlog_pmc));
}
if (drop_sqlite_key) {
binlog_pmc->erase("sqlite_key");
binlog_pmc->force_sync(Auto());
}
+ VLOG(td_init) << "Create concurrent_binlog_pmc";
auto concurrent_binlog_pmc = std::make_shared<BinlogKeyValue<ConcurrentBinlog>>();
concurrent_binlog_pmc->external_init_begin(binlog_pmc->get_magic());
concurrent_binlog_pmc->external_init_handle(std::move(*binlog_pmc));
+ VLOG(td_init) << "Create concurrent_config_pmc";
auto concurrent_config_pmc = std::make_shared<BinlogKeyValue<ConcurrentBinlog>>();
concurrent_config_pmc->external_init_begin(config_pmc->get_magic());
concurrent_config_pmc->external_init_handle(std::move(*config_pmc));
@@ -385,40 +504,152 @@ Status TdDb::init(int32 scheduler_id, const TdParameters &parameters, DbKey key,
config_pmc.reset();
CHECK(binlog_ptr != nullptr);
- auto concurrent_binlog = std::make_shared<ConcurrentBinlog>(std::unique_ptr<Binlog>(binlog_ptr), scheduler_id);
+ VLOG(td_init) << "Create concurrent_binlog";
+ auto concurrent_binlog = std::make_shared<ConcurrentBinlog>(unique_ptr<Binlog>(binlog_ptr));
+ VLOG(td_init) << "Init concurrent_binlog_pmc";
concurrent_binlog_pmc->external_init_finish(concurrent_binlog);
+ VLOG(td_init) << "Init concurrent_config_pmc";
concurrent_config_pmc->external_init_finish(concurrent_binlog);
- binlog_pmc_ = std::move(concurrent_binlog_pmc);
- config_pmc_ = std::move(concurrent_config_pmc);
- binlog_ = std::move(concurrent_binlog);
+ db->binlog_pmc_ = std::move(concurrent_binlog_pmc);
+ db->config_pmc_ = std::move(concurrent_config_pmc);
+ db->binlog_ = std::move(concurrent_binlog);
- return Status::OK();
+ result.database = std::move(db);
+
+ promise.set_value(std::move(result));
}
TdDb::TdDb() = default;
TdDb::~TdDb() = default;
-Result<std::unique_ptr<TdDb>> TdDb::open(int32 scheduler_id, const TdParameters &parameters, DbKey key,
- Events &events) {
- auto db = std::make_unique<TdDb>();
- TRY_STATUS(db->init(scheduler_id, parameters, std::move(key), events));
- return std::move(db);
-}
-Result<EncryptionInfo> TdDb::check_encryption(const TdParameters &parameters) {
- return ::td::check_encryption(get_binlog_path(parameters));
+Status TdDb::check_parameters(TdParameters &parameters) {
+ auto prepare_dir = [](string dir) -> Result<string> {
+ CHECK(!dir.empty());
+ if (dir.back() != TD_DIR_SLASH) {
+ dir += TD_DIR_SLASH;
+ }
+ TRY_STATUS(mkpath(dir, 0750));
+ TRY_RESULT(real_dir, realpath(dir, true));
+ if (real_dir.empty()) {
+ return Status::Error(PSTRING() << "Failed to get realpath for \"" << dir << '"');
+ }
+ if (real_dir.back() != TD_DIR_SLASH) {
+ real_dir += TD_DIR_SLASH;
+ }
+ return real_dir;
+ };
+
+ auto r_database_directory = prepare_dir(parameters.database_directory);
+ if (r_database_directory.is_error()) {
+ VLOG(td_init) << "Invalid database_directory";
+ return Status::Error(PSLICE() << "Can't init database in the directory \"" << parameters.database_directory
+ << "\": " << r_database_directory.error());
+ }
+ parameters.database_directory = r_database_directory.move_as_ok();
+
+ auto r_files_directory = prepare_dir(parameters.files_directory);
+ if (r_files_directory.is_error()) {
+ VLOG(td_init) << "Invalid files_directory";
+ return Status::Error(PSLICE() << "Can't init files directory \"" << parameters.files_directory
+ << "\": " << r_files_directory.error());
+ }
+ parameters.files_directory = r_files_directory.move_as_ok();
+
+ return Status::OK();
}
+
void TdDb::change_key(DbKey key, Promise<> promise) {
get_binlog()->change_key(std::move(key), std::move(promise));
}
+
Status TdDb::destroy(const TdParameters &parameters) {
SqliteDb::destroy(get_sqlite_path(parameters)).ignore();
Binlog::destroy(get_binlog_path(parameters)).ignore();
return Status::OK();
}
-void TdDb::with_db_path(std::function<void(CSlice)> callback) {
+
+void TdDb::with_db_path(const std::function<void(CSlice)> &callback) {
SqliteDb::with_db_path(sqlite_path(), callback);
callback(binlog_path());
}
+
+Result<string> TdDb::get_stats() {
+ auto sb = StringBuilder({}, true);
+ auto &sql = sql_connection_->get();
+ auto run_query = [&](CSlice query, Slice desc) -> Status {
+ TRY_RESULT(stmt, sql.get_statement(query));
+ TRY_STATUS(stmt.step());
+ CHECK(stmt.has_row());
+ auto key_size = stmt.view_int64(0);
+ auto value_size = stmt.view_int64(1);
+ auto count = stmt.view_int64(2);
+ sb << query << "\n";
+ sb << desc << ":\n";
+ sb << format::as_size(key_size + value_size) << "\t";
+ sb << format::as_size(key_size) << "\t";
+ sb << format::as_size(value_size) << "\t";
+ sb << format::as_size((key_size + value_size) / (count ? count : 1)) << "\t";
+ sb << "\n";
+ return Status::OK();
+ };
+ auto run_kv_query = [&](Slice mask, Slice table = Slice("common")) {
+ return run_query(PSLICE() << "SELECT SUM(length(k)), SUM(length(v)), COUNT(*) FROM " << table << " WHERE k like '"
+ << mask << "'",
+ PSLICE() << table << ":" << mask);
+ };
+ TRY_STATUS(run_query("SELECT 0, SUM(length(data)), COUNT(*) FROM messages WHERE 1", "messages"));
+ TRY_STATUS(run_query("SELECT 0, SUM(length(data)), COUNT(*) FROM dialogs WHERE 1", "dialogs"));
+ TRY_STATUS(run_kv_query("%", "common"));
+ TRY_STATUS(run_kv_query("%", "files"));
+ TRY_STATUS(run_kv_query("wp%"));
+ TRY_STATUS(run_kv_query("wpurl%"));
+ TRY_STATUS(run_kv_query("wpiv%"));
+ TRY_STATUS(run_kv_query("us%"));
+ TRY_STATUS(run_kv_query("ch%"));
+ TRY_STATUS(run_kv_query("ss%"));
+ TRY_STATUS(run_kv_query("gr%"));
+
+ vector<int32> prev(1);
+ size_t count = 0;
+ int32 max_bad_to = 0;
+ size_t bad_count = 0;
+ file_db_->pmc().get_by_range("file0", "file:", [&](Slice key, Slice value) {
+ if (value.substr(0, 2) != "@@") {
+ return true;
+ }
+ count++;
+ auto from = to_integer<int32>(key.substr(4));
+ auto to = to_integer<int32>(value.substr(2));
+ if (from <= to) {
+ LOG(DEBUG) << "Have forward reference from " << from << " to " << to;
+ if (to > max_bad_to) {
+ max_bad_to = to;
+ }
+ bad_count++;
+ return true;
+ }
+ if (static_cast<size_t>(from) >= prev.size()) {
+ prev.resize(from + 1);
+ }
+ if (static_cast<size_t>(to) >= prev.size()) {
+ prev.resize(to + 1);
+ }
+ prev[from] = to;
+ return true;
+ });
+ for (size_t i = 1; i < prev.size(); i++) {
+ if (!prev[i]) {
+ continue;
+ }
+ prev[i] = prev[prev[i]] + 1;
+ }
+ sb << "Max file database depth out of " << prev.size() << '/' << count
+ << " elements: " << *std::max_element(prev.begin(), prev.end()) << "\n";
+ sb << "Have " << bad_count << " forward references with maximum reference to " << max_bad_to;
+
+ return sb.as_cslice().str();
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TdDb.h b/protocols/Telegram/tdlib/td/td/telegram/TdDb.h
index dbb219ec01..6a3ead3e30 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/TdDb.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/TdDb.h
@@ -1,23 +1,19 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/db/binlog/Binlog.h"
+#include "td/telegram/TdParameters.h"
+
#include "td/db/binlog/BinlogEvent.h"
-#include "td/db/binlog/ConcurrentBinlog.h"
-#include "td/db/BinlogKeyValue.h"
+#include "td/db/binlog/BinlogInterface.h"
#include "td/db/DbKey.h"
-#include "td/db/Pmc.h"
-#include "td/db/SqliteKeyValue.h"
-#include "td/db/SqliteKeyValueAsync.h"
-#include "td/db/SqliteKeyValueSafe.h"
-
-#include "td/telegram/TdParameters.h"
+#include "td/db/KeyValueSyncInterface.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
@@ -26,21 +22,24 @@
namespace td {
-class SqliteConnectionSafe;
-class SqliteKeyValueSafe;
-class SqliteKeyValueAsyncInterface;
-class SqliteKeyValue;
-class MessagesDbSyncInterface;
-class MessagesDbSyncSafeInterface;
-class MessagesDbAsyncInterface;
+class Binlog;
+template <class BinlogT>
+class BinlogKeyValue;
+class ConcurrentBinlog;
class DialogDbSyncInterface;
class DialogDbSyncSafeInterface;
class DialogDbAsyncInterface;
class FileDbInterface;
-
-struct EncryptionInfo {
- bool is_encrypted{false};
-};
+class MessageDbSyncInterface;
+class MessageDbSyncSafeInterface;
+class MessageDbAsyncInterface;
+class MessageThreadDbSyncInterface;
+class MessageThreadDbSyncSafeInterface;
+class MessageThreadDbAsyncInterface;
+class SqliteConnectionSafe;
+class SqliteKeyValueSafe;
+class SqliteKeyValueAsyncInterface;
+class SqliteKeyValue;
class TdDb {
public:
@@ -51,47 +50,65 @@ class TdDb {
TdDb &operator=(TdDb &&) = delete;
~TdDb();
- struct Events;
- static Result<std::unique_ptr<TdDb>> open(int32 scheduler_id, const TdParameters &parameters, DbKey key,
- Events &events);
- static Result<EncryptionInfo> check_encryption(const TdParameters &parameters);
- static Status destroy(const TdParameters &parameters);
+ struct OpenedDatabase {
+ string database_directory;
+ string files_directory;
+
+ unique_ptr<TdDb> database;
- struct Events {
vector<BinlogEvent> to_secret_chats_manager;
vector<BinlogEvent> user_events;
vector<BinlogEvent> chat_events;
vector<BinlogEvent> channel_events;
vector<BinlogEvent> secret_chat_events;
vector<BinlogEvent> web_page_events;
+ vector<BinlogEvent> save_app_log_events;
+ vector<BinlogEvent> to_poll_manager;
vector<BinlogEvent> to_messages_manager;
+ vector<BinlogEvent> to_notification_manager;
+ vector<BinlogEvent> to_notification_settings_manager;
};
+ static void open(int32 scheduler_id, TdParameters parameters, DbKey key, Promise<OpenedDatabase> &&promise);
+
+ static Status destroy(const TdParameters &parameters);
std::shared_ptr<FileDbInterface> get_file_db_shared();
std::shared_ptr<SqliteConnectionSafe> &get_sqlite_connection_safe();
- ConcurrentBinlog *get_binlog();
+#define get_binlog() get_binlog_impl(__FILE__, __LINE__)
+ BinlogInterface *get_binlog_impl(const char *file, int line);
- BinlogPmc get_binlog_pmc_shared();
- BinlogPmcPtr get_binlog_pmc();
- BinlogPmcPtr get_config_pmc();
+ std::shared_ptr<KeyValueSyncInterface> get_binlog_pmc_shared();
+ std::shared_ptr<KeyValueSyncInterface> get_config_pmc_shared();
- BigPmcPtr get_sqlite_sync_pmc();
+#define get_binlog_pmc() get_binlog_pmc_impl(__FILE__, __LINE__)
+ KeyValueSyncInterface *get_binlog_pmc_impl(const char *file, int line);
+ KeyValueSyncInterface *get_config_pmc();
+
+ SqliteKeyValue *get_sqlite_sync_pmc();
SqliteKeyValueAsyncInterface *get_sqlite_pmc();
+
CSlice binlog_path() const;
CSlice sqlite_path() const;
+
void flush_all();
+
void close_all(Promise<> on_finished);
void close_and_destroy_all(Promise<> on_finished);
- MessagesDbSyncInterface *get_messages_db_sync();
- MessagesDbAsyncInterface *get_messages_db_async();
+ MessageDbSyncInterface *get_message_db_sync();
+ MessageDbAsyncInterface *get_message_db_async();
+
+ MessageThreadDbSyncInterface *get_message_thread_db_sync();
+ MessageThreadDbAsyncInterface *get_message_thread_db_async();
DialogDbSyncInterface *get_dialog_db_sync();
DialogDbAsyncInterface *get_dialog_db_async();
void change_key(DbKey key, Promise<> promise);
- void with_db_path(std::function<void(CSlice)> callback);
+ void with_db_path(const std::function<void(CSlice)> &callback);
+
+ Result<string> get_stats();
private:
string sqlite_path_;
@@ -100,10 +117,13 @@ class TdDb {
std::shared_ptr<FileDbInterface> file_db_;
std::shared_ptr<SqliteKeyValueSafe> common_kv_safe_;
- std::unique_ptr<SqliteKeyValueAsyncInterface> common_kv_async_;
+ unique_ptr<SqliteKeyValueAsyncInterface> common_kv_async_;
+
+ std::shared_ptr<MessageDbSyncSafeInterface> message_db_sync_safe_;
+ std::shared_ptr<MessageDbAsyncInterface> message_db_async_;
- std::shared_ptr<MessagesDbSyncSafeInterface> messages_db_sync_safe_;
- std::shared_ptr<MessagesDbAsyncInterface> messages_db_async_;
+ std::shared_ptr<MessageThreadDbSyncSafeInterface> message_thread_db_sync_safe_;
+ std::shared_ptr<MessageThreadDbAsyncInterface> message_thread_db_async_;
std::shared_ptr<DialogDbSyncSafeInterface> dialog_db_sync_safe_;
std::shared_ptr<DialogDbAsyncInterface> dialog_db_async_;
@@ -112,10 +132,14 @@ class TdDb {
std::shared_ptr<BinlogKeyValue<ConcurrentBinlog>> config_pmc_;
std::shared_ptr<ConcurrentBinlog> binlog_;
- Status init(int32 scheduler_id, const TdParameters &parameters, DbKey key, Events &events);
- Status init_sqlite(int32 scheduler_id, const TdParameters &parameters, DbKey key, DbKey old_key,
+ static void open_impl(TdParameters parameters, DbKey key, Promise<OpenedDatabase> &&promise);
+
+ static Status check_parameters(TdParameters &parameters);
+
+ Status init_sqlite(const TdParameters &parameters, const DbKey &key, const DbKey &old_key,
BinlogKeyValue<Binlog> &binlog_pmc);
void do_close(Promise<> on_finished, bool destroy_flag);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TdParameters.h b/protocols/Telegram/tdlib/td/td/telegram/TdParameters.h
index b89022910e..da6ee70f9c 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/TdParameters.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/TdParameters.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TermsOfService.cpp b/protocols/Telegram/tdlib/td/td/telegram/TermsOfService.cpp
new file mode 100644
index 0000000000..b86ffb1e78
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/TermsOfService.cpp
@@ -0,0 +1,121 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/TermsOfService.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/misc.h"
+#include "td/telegram/net/NetQueryCreator.h"
+#include "td/telegram/Td.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class GetTermsOfServiceUpdateQuery final : public Td::ResultHandler {
+ Promise<std::pair<int32, TermsOfService>> promise_;
+
+ public:
+ explicit GetTermsOfServiceUpdateQuery(Promise<std::pair<int32, TermsOfService>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send() {
+ // we don't poll terms of service before authorization
+ send_query(G()->net_query_creator().create(telegram_api::help_getTermsOfServiceUpdate()));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_getTermsOfServiceUpdate>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ switch (result->get_id()) {
+ case telegram_api::help_termsOfServiceUpdateEmpty::ID: {
+ auto update = move_tl_object_as<telegram_api::help_termsOfServiceUpdateEmpty>(result);
+ promise_.set_value(std::make_pair(update->expires_, TermsOfService()));
+ break;
+ }
+ case telegram_api::help_termsOfServiceUpdate::ID: {
+ auto update = move_tl_object_as<telegram_api::help_termsOfServiceUpdate>(result);
+ promise_.set_value(std::make_pair(update->expires_, TermsOfService(std::move(update->terms_of_service_))));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class AcceptTermsOfServiceQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit AcceptTermsOfServiceQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(const string &terms_of_service_id) {
+ send_query(G()->net_query_creator().create(telegram_api::help_acceptTermsOfService(
+ telegram_api::make_object<telegram_api::dataJSON>(terms_of_service_id))));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::help_acceptTermsOfService>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.ok();
+ if (!result) {
+ LOG(ERROR) << "Failed to accept terms of service";
+ }
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+TermsOfService::TermsOfService(telegram_api::object_ptr<telegram_api::help_termsOfService> terms) {
+ if (terms == nullptr) {
+ return;
+ }
+
+ id_ = std::move(terms->id_->data_);
+ auto entities = get_message_entities(nullptr, std::move(terms->entities_), "TermsOfService");
+ auto status = fix_formatted_text(terms->text_, entities, true, true, true, true, false);
+ if (status.is_error()) {
+ if (!clean_input_string(terms->text_)) {
+ terms->text_.clear();
+ }
+ entities = find_entities(terms->text_, true, true);
+ }
+ if (terms->text_.empty()) {
+ id_.clear();
+ }
+ text_ = FormattedText{std::move(terms->text_), std::move(entities)};
+ min_user_age_ = terms->min_age_confirm_;
+ show_popup_ = terms->popup_;
+}
+
+void get_terms_of_service(Td *td, Promise<std::pair<int32, TermsOfService>> promise) {
+ td->create_handler<GetTermsOfServiceUpdateQuery>(std::move(promise))->send();
+}
+
+void accept_terms_of_service(Td *td, string &&terms_of_service_id, Promise<Unit> &&promise) {
+ td->create_handler<AcceptTermsOfServiceQuery>(std::move(promise))->send(terms_of_service_id);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TermsOfService.h b/protocols/Telegram/tdlib/td/td/telegram/TermsOfService.h
new file mode 100644
index 0000000000..410526da84
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/TermsOfService.h
@@ -0,0 +1,74 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/MessageEntity.hpp"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+#include "td/utils/tl_helpers.h"
+
+#include <utility>
+
+namespace td {
+
+class Td;
+
+class TermsOfService {
+ string id_;
+ FormattedText text_;
+ int32 min_user_age_ = 0;
+ bool show_popup_ = true;
+
+ public:
+ explicit TermsOfService(telegram_api::object_ptr<telegram_api::help_termsOfService> terms = nullptr);
+
+ Slice get_id() const {
+ return id_;
+ }
+
+ td_api::object_ptr<td_api::termsOfService> get_terms_of_service_object() const {
+ if (id_.empty()) {
+ return nullptr;
+ }
+
+ return td_api::make_object<td_api::termsOfService>(get_formatted_text_object(text_, true, -1), min_user_age_,
+ show_popup_);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(show_popup_);
+ END_STORE_FLAGS();
+ store(id_, storer);
+ store(text_, storer);
+ store(min_user_age_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(show_popup_);
+ END_PARSE_FLAGS();
+ parse(id_, parser);
+ parse(text_, parser);
+ parse(min_user_age_, parser);
+ }
+};
+
+void get_terms_of_service(Td *td, Promise<std::pair<int32, TermsOfService>> promise);
+
+void accept_terms_of_service(Td *td, string &&terms_of_service_id, Promise<Unit> &&promise);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ThemeManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/ThemeManager.cpp
new file mode 100644
index 0000000000..89ceb3df18
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ThemeManager.cpp
@@ -0,0 +1,441 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ThemeManager.h"
+
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/BackgroundManager.h"
+#include "td/telegram/BackgroundType.hpp"
+#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/net/NetQueryCreator.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/emoji.h"
+#include "td/utils/JsonBuilder.h"
+#include "td/utils/logging.h"
+#include "td/utils/Random.h"
+#include "td/utils/Time.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class GetChatThemesQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::account_Themes>> promise_;
+
+ public:
+ explicit GetChatThemesQuery(Promise<telegram_api::object_ptr<telegram_api::account_Themes>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(int64 hash) {
+ send_query(G()->net_query_creator().create(telegram_api::account_getChatThemes(hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::account_getChatThemes>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+bool operator==(const ThemeManager::ThemeSettings &lhs, const ThemeManager::ThemeSettings &rhs) {
+ return lhs.accent_color == rhs.accent_color && lhs.message_accent_color == rhs.message_accent_color &&
+ lhs.background_id == rhs.background_id && lhs.background_type == rhs.background_type &&
+ lhs.base_theme == rhs.base_theme && lhs.message_colors == rhs.message_colors &&
+ lhs.animate_message_colors == rhs.animate_message_colors;
+}
+
+bool operator!=(const ThemeManager::ThemeSettings &lhs, const ThemeManager::ThemeSettings &rhs) {
+ return !(lhs == rhs);
+}
+
+template <class StorerT>
+void ThemeManager::ThemeSettings::store(StorerT &storer) const {
+ using td::store;
+ bool has_message_accent_color = message_accent_color != accent_color;
+ bool has_background = background_id.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(animate_message_colors);
+ STORE_FLAG(has_message_accent_color);
+ STORE_FLAG(has_background);
+ END_STORE_FLAGS();
+ store(accent_color, storer);
+ if (has_message_accent_color) {
+ store(message_accent_color, storer);
+ }
+ if (has_background) {
+ storer.context()->td().get_actor_unsafe()->background_manager_->store_background(background_id, storer);
+ store(background_type, storer);
+ }
+ store(base_theme, storer);
+ store(message_colors, storer);
+}
+
+template <class ParserT>
+void ThemeManager::ThemeSettings::parse(ParserT &parser) {
+ using td::parse;
+ bool has_message_accent_color;
+ bool has_background;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(animate_message_colors);
+ PARSE_FLAG(has_message_accent_color);
+ PARSE_FLAG(has_background);
+ END_PARSE_FLAGS();
+ parse(accent_color, parser);
+ if (has_message_accent_color) {
+ parse(message_accent_color, parser);
+ } else {
+ message_accent_color = accent_color;
+ }
+ if (has_background) {
+ parser.context()->td().get_actor_unsafe()->background_manager_->parse_background(background_id, parser);
+ parse(background_type, parser);
+ }
+ parse(base_theme, parser);
+ parse(message_colors, parser);
+}
+
+template <class StorerT>
+void ThemeManager::ChatTheme::store(StorerT &storer) const {
+ BEGIN_STORE_FLAGS();
+ END_STORE_FLAGS();
+ td::store(emoji, storer);
+ td::store(id, storer);
+ td::store(light_theme, storer);
+ td::store(dark_theme, storer);
+}
+
+template <class ParserT>
+void ThemeManager::ChatTheme::parse(ParserT &parser) {
+ BEGIN_PARSE_FLAGS();
+ END_PARSE_FLAGS();
+ td::parse(emoji, parser);
+ td::parse(id, parser);
+ td::parse(light_theme, parser);
+ td::parse(dark_theme, parser);
+}
+
+template <class StorerT>
+void ThemeManager::ChatThemes::store(StorerT &storer) const {
+ td::store(hash, storer);
+ td::store(themes, storer);
+}
+
+template <class ParserT>
+void ThemeManager::ChatThemes::parse(ParserT &parser) {
+ td::parse(hash, parser);
+ td::parse(themes, parser);
+}
+
+ThemeManager::ThemeManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
+}
+
+void ThemeManager::start_up() {
+ init();
+}
+
+void ThemeManager::init() {
+ if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ auto log_event_string = G()->td_db()->get_binlog_pmc()->get(get_chat_themes_database_key());
+ if (!log_event_string.empty()) {
+ auto status = log_event_parse(chat_themes_, log_event_string);
+ if (status.is_ok()) {
+ send_update_chat_themes();
+ } else {
+ LOG(ERROR) << "Failed to parse chat themes from binlog: " << status;
+ chat_themes_ = ChatThemes();
+ }
+ }
+ chat_themes_.next_reload_time = Time::now();
+ loop();
+}
+
+void ThemeManager::tear_down() {
+ parent_.reset();
+}
+
+void ThemeManager::loop() {
+ if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (Time::now() < chat_themes_.next_reload_time) {
+ return set_timeout_at(chat_themes_.next_reload_time);
+ }
+
+ auto request_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<telegram_api::object_ptr<telegram_api::account_Themes>> result) {
+ send_closure(actor_id, &ThemeManager::on_get_chat_themes, std::move(result));
+ });
+
+ td_->create_handler<GetChatThemesQuery>(std::move(request_promise))->send(chat_themes_.hash);
+}
+
+bool ThemeManager::is_dark_base_theme(BaseTheme base_theme) {
+ switch (base_theme) {
+ case BaseTheme::Classic:
+ case BaseTheme::Day:
+ case BaseTheme::Arctic:
+ return false;
+ case BaseTheme::Night:
+ case BaseTheme::Tinted:
+ return true;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+}
+
+void ThemeManager::on_update_theme(telegram_api::object_ptr<telegram_api::theme> &&theme, Promise<Unit> &&promise) {
+ CHECK(theme != nullptr);
+ bool is_changed = false;
+ bool was_light = false;
+ bool was_dark = false;
+ for (auto &chat_theme : chat_themes_.themes) {
+ if (chat_theme.id == theme->id_) {
+ for (auto &settings : theme->settings_) {
+ auto theme_settings = get_chat_theme_settings(std::move(settings));
+ if (theme_settings.message_colors.empty()) {
+ continue;
+ }
+ if (is_dark_base_theme(theme_settings.base_theme)) {
+ if (!was_dark) {
+ was_dark = true;
+ if (chat_theme.dark_theme != theme_settings) {
+ chat_theme.dark_theme = std::move(theme_settings);
+ is_changed = true;
+ }
+ }
+ } else {
+ if (!was_light) {
+ was_light = true;
+ if (chat_theme.light_theme != theme_settings) {
+ chat_theme.light_theme = std::move(theme_settings);
+ is_changed = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (is_changed) {
+ save_chat_themes();
+ send_update_chat_themes();
+ }
+ promise.set_value(Unit());
+}
+
+namespace {
+template <bool for_web_view>
+static auto get_color_json(int32 color);
+
+template <>
+auto get_color_json<false>(int32 color) {
+ return static_cast<int64>(static_cast<uint32>(color) | 0xFF000000);
+}
+
+template <>
+auto get_color_json<true>(int32 color) {
+ string res(7, '#');
+ const char *hex = "0123456789abcdef";
+ for (int i = 0; i < 3; i++) {
+ int32 num = (color >> (i * 8)) & 0xFF;
+ res[2 * i + 1] = hex[num >> 4];
+ res[2 * i + 2] = hex[num & 15];
+ }
+ return res;
+}
+
+template <bool for_web_view>
+string get_theme_parameters_json_string_impl(const td_api::object_ptr<td_api::themeParameters> &theme) {
+ if (for_web_view && theme == nullptr) {
+ return "null";
+ }
+ return json_encode<string>(json_object([&theme](auto &o) {
+ auto get_color = &get_color_json<for_web_view>;
+ o("bg_color", get_color(theme->background_color_));
+ o("secondary_bg_color", get_color(theme->secondary_background_color_));
+ o("text_color", get_color(theme->text_color_));
+ o("hint_color", get_color(theme->hint_color_));
+ o("link_color", get_color(theme->link_color_));
+ o("button_color", get_color(theme->button_color_));
+ o("button_text_color", get_color(theme->button_text_color_));
+ }));
+}
+} // namespace
+
+string ThemeManager::get_theme_parameters_json_string(const td_api::object_ptr<td_api::themeParameters> &theme,
+ bool for_web_view) {
+ if (for_web_view) {
+ return get_theme_parameters_json_string_impl<true>(theme);
+ } else {
+ return get_theme_parameters_json_string_impl<false>(theme);
+ }
+}
+
+td_api::object_ptr<td_api::themeSettings> ThemeManager::get_theme_settings_object(const ThemeSettings &settings) const {
+ auto fill = [colors = settings.message_colors]() mutable -> td_api::object_ptr<td_api::BackgroundFill> {
+ if (colors.size() >= 3) {
+ return td_api::make_object<td_api::backgroundFillFreeformGradient>(std::move(colors));
+ }
+ CHECK(!colors.empty());
+ if (colors.size() == 1 || colors[0] == colors[1]) {
+ return td_api::make_object<td_api::backgroundFillSolid>(colors[0]);
+ }
+ return td_api::make_object<td_api::backgroundFillGradient>(colors[1], colors[0], 0);
+ }();
+
+ // ignore settings.base_theme for now
+ return td_api::make_object<td_api::themeSettings>(
+ settings.accent_color,
+ td_->background_manager_->get_background_object(settings.background_id, false, &settings.background_type),
+ std::move(fill), settings.animate_message_colors, settings.message_accent_color);
+}
+
+td_api::object_ptr<td_api::chatTheme> ThemeManager::get_chat_theme_object(const ChatTheme &theme) const {
+ return td_api::make_object<td_api::chatTheme>(theme.emoji, get_theme_settings_object(theme.light_theme),
+ get_theme_settings_object(theme.dark_theme));
+}
+
+td_api::object_ptr<td_api::updateChatThemes> ThemeManager::get_update_chat_themes_object() const {
+ return td_api::make_object<td_api::updateChatThemes>(
+ transform(chat_themes_.themes, [this](const ChatTheme &theme) { return get_chat_theme_object(theme); }));
+}
+
+string ThemeManager::get_chat_themes_database_key() {
+ return "chat_themes";
+}
+
+void ThemeManager::save_chat_themes() {
+ G()->td_db()->get_binlog_pmc()->set(get_chat_themes_database_key(), log_event_store(chat_themes_).as_slice().str());
+}
+
+void ThemeManager::send_update_chat_themes() const {
+ send_closure(G()->td(), &Td::send_update, get_update_chat_themes_object());
+}
+
+void ThemeManager::on_get_chat_themes(Result<telegram_api::object_ptr<telegram_api::account_Themes>> result) {
+ if (result.is_error()) {
+ set_timeout_in(Random::fast(40, 60));
+ return;
+ }
+
+ chat_themes_.next_reload_time = Time::now() + THEME_CACHE_TIME;
+ set_timeout_at(chat_themes_.next_reload_time);
+
+ auto chat_themes_ptr = result.move_as_ok();
+ LOG(DEBUG) << "Receive " << to_string(chat_themes_ptr);
+ if (chat_themes_ptr->get_id() == telegram_api::account_themesNotModified::ID) {
+ return;
+ }
+ CHECK(chat_themes_ptr->get_id() == telegram_api::account_themes::ID);
+ auto chat_themes = telegram_api::move_object_as<telegram_api::account_themes>(chat_themes_ptr);
+ chat_themes_.hash = chat_themes->hash_;
+ chat_themes_.themes.clear();
+ for (auto &theme : chat_themes->themes_) {
+ if (!is_emoji(theme->emoticon_) || !theme->for_chat_) {
+ LOG(ERROR) << "Receive " << to_string(theme);
+ continue;
+ }
+
+ bool was_light = false;
+ bool was_dark = false;
+ ChatTheme chat_theme;
+ chat_theme.emoji = std::move(theme->emoticon_);
+ chat_theme.id = theme->id_;
+ for (auto &settings : theme->settings_) {
+ auto theme_settings = get_chat_theme_settings(std::move(settings));
+ if (theme_settings.message_colors.empty()) {
+ continue;
+ }
+ if (is_dark_base_theme(theme_settings.base_theme)) {
+ if (!was_dark) {
+ was_dark = true;
+ if (chat_theme.dark_theme != theme_settings) {
+ chat_theme.dark_theme = std::move(theme_settings);
+ }
+ }
+ } else {
+ if (!was_light) {
+ was_light = true;
+ if (chat_theme.light_theme != theme_settings) {
+ chat_theme.light_theme = std::move(theme_settings);
+ }
+ }
+ }
+ }
+ if (chat_theme.light_theme.message_colors.empty() || chat_theme.dark_theme.message_colors.empty()) {
+ continue;
+ }
+ chat_themes_.themes.push_back(std::move(chat_theme));
+ }
+
+ save_chat_themes();
+ send_update_chat_themes();
+}
+
+ThemeManager::BaseTheme ThemeManager::get_base_theme(
+ const telegram_api::object_ptr<telegram_api::BaseTheme> &base_theme) {
+ CHECK(base_theme != nullptr);
+ switch (base_theme->get_id()) {
+ case telegram_api::baseThemeClassic::ID:
+ return BaseTheme::Classic;
+ case telegram_api::baseThemeDay::ID:
+ return BaseTheme::Day;
+ case telegram_api::baseThemeNight::ID:
+ return BaseTheme::Night;
+ case telegram_api::baseThemeTinted::ID:
+ return BaseTheme::Tinted;
+ case telegram_api::baseThemeArctic::ID:
+ return BaseTheme::Arctic;
+ default:
+ UNREACHABLE();
+ return BaseTheme::Classic;
+ }
+}
+
+ThemeManager::ThemeSettings ThemeManager::get_chat_theme_settings(
+ telegram_api::object_ptr<telegram_api::themeSettings> settings) {
+ ThemeSettings result;
+ if (settings != nullptr && !settings->message_colors_.empty() && settings->message_colors_.size() <= 4) {
+ auto background =
+ td_->background_manager_->on_get_background(BackgroundId(), string(), std::move(settings->wallpaper_), false);
+
+ result.accent_color = settings->accent_color_;
+ bool has_outbox_accent_color = (settings->flags_ & telegram_api::themeSettings::OUTBOX_ACCENT_COLOR_MASK) != 0;
+ result.message_accent_color = (has_outbox_accent_color ? settings->outbox_accent_color_ : result.accent_color);
+ result.background_id = background.first;
+ result.background_type = std::move(background.second);
+ result.base_theme = get_base_theme(settings->base_theme_);
+ result.message_colors = std::move(settings->message_colors_);
+ result.animate_message_colors = settings->message_colors_animated_;
+ }
+ return result;
+}
+
+void ThemeManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
+ if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot() || chat_themes_.themes.empty()) {
+ return;
+ }
+
+ updates.push_back(get_update_chat_themes_object());
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/ThemeManager.h b/protocols/Telegram/tdlib/td/td/telegram/ThemeManager.h
new file mode 100644
index 0000000000..e11b9038e1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/ThemeManager.h
@@ -0,0 +1,120 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/BackgroundId.h"
+#include "td/telegram/BackgroundType.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class Td;
+
+class ThemeManager final : public Actor {
+ public:
+ ThemeManager(Td *td, ActorShared<> parent);
+
+ void init();
+
+ void on_update_theme(telegram_api::object_ptr<telegram_api::theme> &&theme, Promise<Unit> &&promise);
+
+ static string get_theme_parameters_json_string(const td_api::object_ptr<td_api::themeParameters> &theme,
+ bool for_web_view);
+
+ void get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const;
+
+ private:
+ // apeend-only
+ enum class BaseTheme : int32 { Classic, Day, Night, Tinted, Arctic };
+
+ static constexpr int32 THEME_CACHE_TIME = 3600;
+
+ struct ThemeSettings {
+ int32 accent_color = 0;
+ int32 message_accent_color = 0;
+ BackgroundId background_id;
+ BackgroundType background_type;
+ BaseTheme base_theme = BaseTheme::Classic;
+ vector<int32> message_colors;
+ bool animate_message_colors = false;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ friend bool operator==(const ThemeSettings &lhs, const ThemeSettings &rhs);
+
+ friend bool operator!=(const ThemeSettings &lhs, const ThemeSettings &rhs);
+
+ struct ChatTheme {
+ string emoji;
+ int64 id = 0;
+ ThemeSettings light_theme;
+ ThemeSettings dark_theme;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ struct ChatThemes {
+ int64 hash = 0;
+ double next_reload_time = 0;
+ vector<ChatTheme> themes;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+ };
+
+ void start_up() final;
+
+ void loop() final;
+
+ void tear_down() final;
+
+ static bool is_dark_base_theme(BaseTheme base_theme);
+
+ void on_get_chat_themes(Result<telegram_api::object_ptr<telegram_api::account_Themes>> result);
+
+ td_api::object_ptr<td_api::themeSettings> get_theme_settings_object(const ThemeSettings &settings) const;
+
+ td_api::object_ptr<td_api::chatTheme> get_chat_theme_object(const ChatTheme &theme) const;
+
+ td_api::object_ptr<td_api::updateChatThemes> get_update_chat_themes_object() const;
+
+ static string get_chat_themes_database_key();
+
+ void save_chat_themes();
+
+ void send_update_chat_themes() const;
+
+ static BaseTheme get_base_theme(const telegram_api::object_ptr<telegram_api::BaseTheme> &base_theme);
+
+ ThemeSettings get_chat_theme_settings(telegram_api::object_ptr<telegram_api::themeSettings> settings);
+
+ ChatThemes chat_themes_;
+
+ Td *td_;
+ ActorShared<> parent_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TopDialogCategory.cpp b/protocols/Telegram/tdlib/td/td/telegram/TopDialogCategory.cpp
new file mode 100644
index 0000000000..eea3dca08e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/TopDialogCategory.cpp
@@ -0,0 +1,109 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/TopDialogCategory.h"
+
+namespace td {
+
+CSlice get_top_dialog_category_name(TopDialogCategory category) {
+ switch (category) {
+ case TopDialogCategory::Correspondent:
+ return CSlice("correspondent");
+ case TopDialogCategory::BotPM:
+ return CSlice("bot_pm");
+ case TopDialogCategory::BotInline:
+ return CSlice("bot_inline");
+ case TopDialogCategory::Group:
+ return CSlice("group");
+ case TopDialogCategory::Channel:
+ return CSlice("channel");
+ case TopDialogCategory::Call:
+ return CSlice("call");
+ case TopDialogCategory::ForwardUsers:
+ return CSlice("forward_users");
+ case TopDialogCategory::ForwardChats:
+ return CSlice("forward_chats");
+ default:
+ UNREACHABLE();
+ return CSlice();
+ }
+}
+
+TopDialogCategory get_top_dialog_category(const td_api::object_ptr<td_api::TopChatCategory> &category) {
+ if (category == nullptr) {
+ return TopDialogCategory::Size;
+ }
+ switch (category->get_id()) {
+ case td_api::topChatCategoryUsers::ID:
+ return TopDialogCategory::Correspondent;
+ case td_api::topChatCategoryBots::ID:
+ return TopDialogCategory::BotPM;
+ case td_api::topChatCategoryInlineBots::ID:
+ return TopDialogCategory::BotInline;
+ case td_api::topChatCategoryGroups::ID:
+ return TopDialogCategory::Group;
+ case td_api::topChatCategoryChannels::ID:
+ return TopDialogCategory::Channel;
+ case td_api::topChatCategoryCalls::ID:
+ return TopDialogCategory::Call;
+ case td_api::topChatCategoryForwardChats::ID:
+ return TopDialogCategory::ForwardUsers;
+ default:
+ UNREACHABLE();
+ return TopDialogCategory::Size;
+ }
+}
+
+TopDialogCategory get_top_dialog_category(const telegram_api::object_ptr<telegram_api::TopPeerCategory> &category) {
+ CHECK(category != nullptr);
+ switch (category->get_id()) {
+ case telegram_api::topPeerCategoryCorrespondents::ID:
+ return TopDialogCategory::Correspondent;
+ case telegram_api::topPeerCategoryBotsPM::ID:
+ return TopDialogCategory::BotPM;
+ case telegram_api::topPeerCategoryBotsInline::ID:
+ return TopDialogCategory::BotInline;
+ case telegram_api::topPeerCategoryGroups::ID:
+ return TopDialogCategory::Group;
+ case telegram_api::topPeerCategoryChannels::ID:
+ return TopDialogCategory::Channel;
+ case telegram_api::topPeerCategoryPhoneCalls::ID:
+ return TopDialogCategory::Call;
+ case telegram_api::topPeerCategoryForwardUsers::ID:
+ return TopDialogCategory::ForwardUsers;
+ case telegram_api::topPeerCategoryForwardChats::ID:
+ return TopDialogCategory::ForwardChats;
+ default:
+ UNREACHABLE();
+ return TopDialogCategory::Size;
+ }
+}
+
+telegram_api::object_ptr<telegram_api::TopPeerCategory> get_input_top_peer_category(TopDialogCategory category) {
+ switch (category) {
+ case TopDialogCategory::Correspondent:
+ return make_tl_object<telegram_api::topPeerCategoryCorrespondents>();
+ case TopDialogCategory::BotPM:
+ return make_tl_object<telegram_api::topPeerCategoryBotsPM>();
+ case TopDialogCategory::BotInline:
+ return make_tl_object<telegram_api::topPeerCategoryBotsInline>();
+ case TopDialogCategory::Group:
+ return make_tl_object<telegram_api::topPeerCategoryGroups>();
+ case TopDialogCategory::Channel:
+ return make_tl_object<telegram_api::topPeerCategoryChannels>();
+ case TopDialogCategory::Call:
+ return make_tl_object<telegram_api::topPeerCategoryPhoneCalls>();
+ case TopDialogCategory::ForwardUsers:
+ return make_tl_object<telegram_api::topPeerCategoryForwardUsers>();
+ case TopDialogCategory::ForwardChats:
+ return make_tl_object<telegram_api::topPeerCategoryForwardChats>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TopDialogCategory.h b/protocols/Telegram/tdlib/td/td/telegram/TopDialogCategory.h
new file mode 100644
index 0000000000..8c62bd6330
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/TopDialogCategory.h
@@ -0,0 +1,37 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+enum class TopDialogCategory : int32 {
+ Correspondent,
+ BotPM,
+ BotInline,
+ Group,
+ Channel,
+ Call,
+ ForwardUsers,
+ ForwardChats,
+ Size
+};
+
+CSlice get_top_dialog_category_name(TopDialogCategory category);
+
+TopDialogCategory get_top_dialog_category(const td_api::object_ptr<td_api::TopChatCategory> &category);
+
+TopDialogCategory get_top_dialog_category(const telegram_api::object_ptr<telegram_api::TopPeerCategory> &category);
+
+telegram_api::object_ptr<telegram_api::TopPeerCategory> get_input_top_peer_category(TopDialogCategory category);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.cpp
index 02a9d7baac..48e0519ec0 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.cpp
@@ -1,12 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/TopDialogManager.h"
-#include "td/telegram/ConfigShared.h"
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/AuthManager.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/DialogId.h"
#include "td/telegram/Global.h"
@@ -17,81 +18,186 @@
#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/StateManager.h"
#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/Clocks.h"
-#include "td/utils/ScopeGuard.h"
-#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/tl_helpers.h"
-#include "td/telegram/telegram_api.h"
-
#include <algorithm>
#include <cmath>
#include <iterator>
namespace td {
-static CSlice top_dialog_category_name(TopDialogCategory category) {
- switch (category) {
- case TopDialogCategory::Correspondent:
- return CSlice("correspondent");
- case TopDialogCategory::BotPM:
- return CSlice("bot_pm");
- case TopDialogCategory::BotInline:
- return CSlice("bot_inline");
- case TopDialogCategory::Group:
- return CSlice("group");
- case TopDialogCategory::Channel:
- return CSlice("channel");
- case TopDialogCategory::Call:
- return CSlice("call");
- default:
- UNREACHABLE();
+class GetTopPeersQuery final : public Td::ResultHandler {
+ Promise<telegram_api::object_ptr<telegram_api::contacts_TopPeers>> promise_;
+
+ public:
+ explicit GetTopPeersQuery(Promise<telegram_api::object_ptr<telegram_api::contacts_TopPeers>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
+ void send(int64 hash) {
+ int32 flags =
+ telegram_api::contacts_getTopPeers::CORRESPONDENTS_MASK | telegram_api::contacts_getTopPeers::BOTS_PM_MASK |
+ telegram_api::contacts_getTopPeers::BOTS_INLINE_MASK | telegram_api::contacts_getTopPeers::GROUPS_MASK |
+ telegram_api::contacts_getTopPeers::CHANNELS_MASK | telegram_api::contacts_getTopPeers::PHONE_CALLS_MASK |
+ telegram_api::contacts_getTopPeers::FORWARD_USERS_MASK | telegram_api::contacts_getTopPeers::FORWARD_CHATS_MASK;
+ send_query(G()->net_query_creator().create(telegram_api::contacts_getTopPeers(
+ flags, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, 0 /*offset*/, 100 /*limit*/, hash)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_getTopPeers>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(result_ptr.move_as_ok());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ToggleTopPeersQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+
+ public:
+ explicit ToggleTopPeersQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(bool is_enabled) {
+ send_query(G()->net_query_creator().create(telegram_api::contacts_toggleTopPeers(is_enabled)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_toggleTopPeers>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
+ }
+};
+
+class ResetTopPeerRatingQuery final : public Td::ResultHandler {
+ DialogId dialog_id_;
+
+ public:
+ void send(TopDialogCategory category, DialogId dialog_id) {
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return;
+ }
+
+ dialog_id_ = dialog_id;
+ send_query(G()->net_query_creator().create(
+ telegram_api::contacts_resetTopPeerRating(get_input_top_peer_category(category), std::move(input_peer))));
}
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::contacts_resetTopPeerRating>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ // ignore the result
+ }
+
+ void on_error(Status status) final {
+ if (!td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "ResetTopPeerRatingQuery")) {
+ LOG(INFO) << "Receive error for ResetTopPeerRatingQuery: " << status;
+ }
+ }
+};
+
+TopDialogManager::TopDialogManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
}
-static TopDialogCategory top_dialog_category_from_telegram_api(const telegram_api::TopPeerCategory &category) {
- switch (category.get_id()) {
- case telegram_api::topPeerCategoryCorrespondents::ID:
- return TopDialogCategory::Correspondent;
- case telegram_api::topPeerCategoryBotsPM::ID:
- return TopDialogCategory::BotPM;
- case telegram_api::topPeerCategoryBotsInline::ID:
- return TopDialogCategory::BotInline;
- case telegram_api::topPeerCategoryGroups::ID:
- return TopDialogCategory::Group;
- case telegram_api::topPeerCategoryChannels::ID:
- return TopDialogCategory::Channel;
- case telegram_api::topPeerCategoryPhoneCalls::ID:
- return TopDialogCategory::Call;
- default:
- UNREACHABLE();
+void TopDialogManager::update_is_enabled(bool is_enabled) {
+ if (td_->auth_manager_ == nullptr || !td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) {
+ return;
+ }
+
+ if (set_is_enabled(is_enabled)) {
+ G()->td_db()->get_binlog_pmc()->set("top_peers_enabled", is_enabled ? "1" : "0");
+ send_toggle_top_peers(is_enabled);
+
+ loop();
}
}
-static tl_object_ptr<telegram_api::TopPeerCategory> top_dialog_category_as_telegram_api(TopDialogCategory category) {
- switch (category) {
- case TopDialogCategory::Correspondent:
- return make_tl_object<telegram_api::topPeerCategoryCorrespondents>();
- case TopDialogCategory::BotPM:
- return make_tl_object<telegram_api::topPeerCategoryBotsPM>();
- case TopDialogCategory::BotInline:
- return make_tl_object<telegram_api::topPeerCategoryBotsInline>();
- case TopDialogCategory::Group:
- return make_tl_object<telegram_api::topPeerCategoryGroups>();
- case TopDialogCategory::Channel:
- return make_tl_object<telegram_api::topPeerCategoryChannels>();
- case TopDialogCategory::Call:
- return make_tl_object<telegram_api::topPeerCategoryPhoneCalls>();
- default:
- UNREACHABLE();
+bool TopDialogManager::set_is_enabled(bool is_enabled) {
+ if (is_enabled_ == is_enabled) {
+ return false;
}
+
+ LOG(DEBUG) << "Change top chats is_enabled to " << is_enabled;
+ is_enabled_ = is_enabled;
+ try_start();
+ return true;
+}
+
+void TopDialogManager::send_toggle_top_peers(bool is_enabled) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ if (have_toggle_top_peers_query_) {
+ have_pending_toggle_top_peers_query_ = true;
+ pending_toggle_top_peers_query_ = is_enabled;
+ return;
+ }
+
+ LOG(DEBUG) << "Send toggle top peers query to " << is_enabled;
+ have_toggle_top_peers_query_ = true;
+
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), is_enabled](Result<Unit> result) {
+ send_closure(actor_id, &TopDialogManager::on_toggle_top_peers, is_enabled, std::move(result));
+ });
+ td_->create_handler<ToggleTopPeersQuery>(std::move(promise))->send(is_enabled);
+}
+
+void TopDialogManager::on_toggle_top_peers(bool is_enabled, Result<Unit> &&result) {
+ CHECK(have_toggle_top_peers_query_);
+ have_toggle_top_peers_query_ = false;
+
+ if (have_pending_toggle_top_peers_query_) {
+ have_pending_toggle_top_peers_query_ = false;
+ if (pending_toggle_top_peers_query_ != is_enabled) {
+ send_toggle_top_peers(pending_toggle_top_peers_query_);
+ return;
+ }
+ }
+
+ if (result.is_ok()) {
+ // everything is synchronized
+ G()->td_db()->get_binlog_pmc()->erase("top_peers_enabled");
+ } else {
+ // let's resend the query forever
+ send_toggle_top_peers(is_enabled);
+ }
+ loop();
}
void TopDialogManager::on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date) {
- if (!is_active_) {
+ if (!is_active_ || !is_enabled_) {
return;
}
auto pos = static_cast<size_t>(category);
@@ -119,7 +225,7 @@ void TopDialogManager::on_dialog_used(TopDialogCategory category, DialogId dialo
it = next;
}
- LOG(INFO) << "Update " << top_dialog_category_name(category) << " rating of " << dialog_id << " by " << delta;
+ LOG(INFO) << "Update " << get_top_dialog_category_name(category) << " rating of " << dialog_id << " by " << delta;
if (!first_unsync_change_) {
first_unsync_change_ = Timestamp::now_cached();
@@ -127,29 +233,31 @@ void TopDialogManager::on_dialog_used(TopDialogCategory category, DialogId dialo
loop();
}
-void TopDialogManager::remove_dialog(TopDialogCategory category, DialogId dialog_id,
- tl_object_ptr<telegram_api::InputPeer> input_peer) {
- if (!is_active_) {
- return;
+void TopDialogManager::remove_dialog(TopDialogCategory category, DialogId dialog_id, Promise<Unit> &&promise) {
+ if (category == TopDialogCategory::Size) {
+ return promise.set_error(Status::Error(400, "Top chat category must be non-empty"));
+ }
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "remove_dialog")) {
+ return promise.set_error(Status::Error(400, "Chat not found"));
+ }
+ if (!is_active_ || !is_enabled_) {
+ return promise.set_value(Unit());
+ }
+
+ if (category == TopDialogCategory::ForwardUsers && dialog_id.get_type() != DialogType::User) {
+ category = TopDialogCategory::ForwardChats;
}
auto pos = static_cast<size_t>(category);
CHECK(pos < by_category_.size());
auto &top_dialogs = by_category_[pos];
- LOG(INFO) << "Remove " << top_dialog_category_name(category) << " rating of " << dialog_id;
-
- if (input_peer != nullptr) {
- auto query =
- telegram_api::contacts_resetTopPeerRating(top_dialog_category_as_telegram_api(category), std::move(input_peer));
- auto net_query = G()->net_query_creator().create(create_storer(query));
- G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this, 1));
- }
+ td_->create_handler<ResetTopPeerRatingQuery>()->send(category, dialog_id);
auto it = std::find_if(top_dialogs.dialogs.begin(), top_dialogs.dialogs.end(),
[&](auto &top_dialog) { return top_dialog.dialog_id == dialog_id; });
if (it == top_dialogs.dialogs.end()) {
- return;
+ return promise.set_value(Unit());
}
top_dialogs.is_dirty = true;
@@ -158,16 +266,26 @@ void TopDialogManager::remove_dialog(TopDialogCategory category, DialogId dialog
first_unsync_change_ = Timestamp::now_cached();
}
loop();
+ promise.set_value(Unit());
}
-void TopDialogManager::get_top_dialogs(TopDialogCategory category, size_t limit, Promise<vector<DialogId>> promise) {
+void TopDialogManager::get_top_dialogs(TopDialogCategory category, int32 limit, Promise<vector<DialogId>> promise) {
+ if (category == TopDialogCategory::Size) {
+ return promise.set_error(Status::Error(400, "Top chat category must be non-empty"));
+ }
+ if (limit <= 0) {
+ return promise.set_error(Status::Error(400, "Limit must be positive"));
+ }
if (!is_active_) {
- promise.set_error(Status::Error(400, "Not supported without chat info database"));
- return;
+ return promise.set_error(Status::Error(400, "Not supported without chat info database"));
+ }
+ if (!is_enabled_) {
+ return promise.set_error(Status::Error(400, "Top chats computation is disabled"));
}
+
GetTopDialogsQuery query;
query.category = category;
- query.limit = limit;
+ query.limit = static_cast<size_t>(limit);
query.promise = std::move(promise);
pending_get_top_dialogs_.push_back(std::move(query));
loop();
@@ -177,37 +295,37 @@ void TopDialogManager::update_rating_e_decay() {
if (!is_active_) {
return;
}
- rating_e_decay_ = G()->shared_config().get_option_integer("rating_e_decay", rating_e_decay_);
-}
-
-template <class T>
-void parse(TopDialogManager::TopDialog &top_dialog, T &parser) {
- using ::td::parse;
- parse(top_dialog.dialog_id, parser);
- parse(top_dialog.rating, parser);
+ rating_e_decay_ = narrow_cast<int32>(G()->get_option_integer("rating_e_decay", rating_e_decay_));
}
-template <class T>
-void store(const TopDialogManager::TopDialog &top_dialog, T &storer) {
+template <class StorerT>
+void store(const TopDialogManager::TopDialog &top_dialog, StorerT &storer) {
using ::td::store;
store(top_dialog.dialog_id, storer);
store(top_dialog.rating, storer);
}
-template <class T>
-void parse(TopDialogManager::TopDialogs &top_dialogs, T &parser) {
+template <class ParserT>
+void parse(TopDialogManager::TopDialog &top_dialog, ParserT &parser) {
using ::td::parse;
- parse(top_dialogs.rating_timestamp, parser);
- parse(top_dialogs.dialogs, parser);
+ parse(top_dialog.dialog_id, parser);
+ parse(top_dialog.rating, parser);
}
-template <class T>
-void store(const TopDialogManager::TopDialogs &top_dialogs, T &storer) {
+template <class StorerT>
+void store(const TopDialogManager::TopDialogs &top_dialogs, StorerT &storer) {
using ::td::store;
store(top_dialogs.rating_timestamp, storer);
store(top_dialogs.dialogs, storer);
}
+template <class ParserT>
+void parse(TopDialogManager::TopDialogs &top_dialogs, ParserT &parser) {
+ using ::td::parse;
+ parse(top_dialogs.rating_timestamp, parser);
+ parse(top_dialogs.dialogs, parser);
+}
+
double TopDialogManager::rating_add(double now, double rating_timestamp) const {
return std::exp((now - rating_timestamp) / rating_e_decay_);
}
@@ -229,129 +347,162 @@ void TopDialogManager::normalize_rating() {
}
void TopDialogManager::do_get_top_dialogs(GetTopDialogsQuery &&query) {
- auto pos = static_cast<size_t>(query.category);
- CHECK(pos < by_category_.size());
- auto &top_dialogs = by_category_[pos];
-
- auto limit = std::min({query.limit, MAX_TOP_DIALOGS_LIMIT, top_dialogs.dialogs.size()});
+ vector<DialogId> dialog_ids;
+ if (query.category != TopDialogCategory::ForwardUsers) {
+ auto pos = static_cast<size_t>(query.category);
+ CHECK(pos < by_category_.size());
+ dialog_ids = transform(by_category_[pos].dialogs, [](const auto &x) { return x.dialog_id; });
+ } else {
+ // merge ForwardUsers and ForwardChats
+ auto &users = by_category_[static_cast<size_t>(TopDialogCategory::ForwardUsers)];
+ auto &chats = by_category_[static_cast<size_t>(TopDialogCategory::ForwardChats)];
+ size_t users_pos = 0;
+ size_t chats_pos = 0;
+ while (users_pos < users.dialogs.size() || chats_pos < chats.dialogs.size()) {
+ if (chats_pos == chats.dialogs.size() ||
+ (users_pos < users.dialogs.size() && users.dialogs[users_pos] < chats.dialogs[chats_pos])) {
+ dialog_ids.push_back(users.dialogs[users_pos++].dialog_id);
+ } else {
+ dialog_ids.push_back(chats.dialogs[chats_pos++].dialog_id);
+ }
+ }
+ }
- vector<DialogId> dialog_ids = transform(top_dialogs.dialogs, [](const auto &x) { return x.dialog_id; });
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), query = std::move(query)](Result<vector<DialogId>> r_dialog_ids) mutable {
+ if (r_dialog_ids.is_error()) {
+ return query.promise.set_error(r_dialog_ids.move_as_error());
+ }
+ send_closure(actor_id, &TopDialogManager::on_load_dialogs, std::move(query), r_dialog_ids.move_as_ok());
+ });
+ send_closure(td_->messages_manager_actor_, &MessagesManager::load_dialogs, std::move(dialog_ids), std::move(promise));
+}
- auto promise = PromiseCreator::lambda([query = std::move(query), dialog_ids, limit](Result<Unit>) mutable {
- vector<DialogId> result;
- result.reserve(limit);
- for (auto dialog_id : dialog_ids) {
- if (dialog_id.get_type() == DialogType::User) {
- auto user_id = dialog_id.get_user_id();
- if (G()->td().get_actor_unsafe()->contacts_manager_->is_user_deleted(user_id)) {
- LOG(INFO) << "Skip deleted " << user_id;
+void TopDialogManager::on_load_dialogs(GetTopDialogsQuery &&query, vector<DialogId> &&dialog_ids) {
+ auto limit = std::min({query.limit, MAX_TOP_DIALOGS_LIMIT, dialog_ids.size()});
+ vector<DialogId> result;
+ result.reserve(limit);
+ for (auto dialog_id : dialog_ids) {
+ if (dialog_id.get_type() == DialogType::User) {
+ auto user_id = dialog_id.get_user_id();
+ if (td_->contacts_manager_->is_user_deleted(user_id)) {
+ LOG(INFO) << "Skip deleted " << user_id;
+ continue;
+ }
+ if (td_->contacts_manager_->get_my_id() == user_id) {
+ LOG(INFO) << "Skip self " << user_id;
+ continue;
+ }
+ if (query.category == TopDialogCategory::BotInline || query.category == TopDialogCategory::BotPM) {
+ auto r_bot_info = td_->contacts_manager_->get_bot_data(user_id);
+ if (r_bot_info.is_error()) {
+ LOG(INFO) << "Skip not a bot " << user_id;
continue;
}
- if (G()->td().get_actor_unsafe()->contacts_manager_->get_my_id("do_get_top_dialogs") == user_id) {
- LOG(INFO) << "Skip self " << user_id;
+ if (query.category == TopDialogCategory::BotInline &&
+ (r_bot_info.ok().username.empty() || !r_bot_info.ok().is_inline)) {
+ LOG(INFO) << "Skip not inline bot " << user_id;
continue;
}
}
+ }
- result.push_back(dialog_id);
- if (result.size() == limit) {
- break;
- }
+ result.push_back(dialog_id);
+ if (result.size() == limit) {
+ break;
}
+ }
- query.promise.set_value(std::move(result));
- });
- send_closure(G()->messages_manager(), &MessagesManager::load_dialogs, std::move(dialog_ids), std::move(promise));
+ query.promise.set_value(std::move(result));
}
void TopDialogManager::do_get_top_peers() {
- LOG(INFO) << "Send get top peers request";
- using telegram_api::contacts_getTopPeers;
-
- std::vector<uint32> ids;
+ std::vector<uint64> ids;
for (auto &category : by_category_) {
for (auto &top_dialog : category.dialogs) {
auto dialog_id = top_dialog.dialog_id;
switch (dialog_id.get_type()) {
- case DialogType::Channel:
- ids.push_back(dialog_id.get_channel_id().get());
- break;
case DialogType::User:
ids.push_back(dialog_id.get_user_id().get());
break;
case DialogType::Chat:
ids.push_back(dialog_id.get_chat_id().get());
break;
+ case DialogType::Channel:
+ ids.push_back(dialog_id.get_channel_id().get());
+ break;
default:
break;
}
}
}
-
- int32 hash = get_vector_hash(ids);
-
- int32 flags = contacts_getTopPeers::CORRESPONDENTS_MASK | contacts_getTopPeers::BOTS_PM_MASK |
- contacts_getTopPeers::BOTS_INLINE_MASK | contacts_getTopPeers::GROUPS_MASK |
- contacts_getTopPeers::CHANNELS_MASK | contacts_getTopPeers::PHONE_CALLS_MASK;
-
- contacts_getTopPeers query{
- flags, true /*correspondents*/, true /*bot_pm*/, true /*bot_inline */, true /*phone_calls*/,
- true /*groups*/, true /*channels*/, 0 /*offset*/, 100 /*limit*/, hash};
- auto net_query = G()->net_query_creator().create(create_storer(query));
- G()->net_query_dispatcher().dispatch_with_callback(std::move(net_query), actor_shared(this));
+ auto promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this)](Result<telegram_api::object_ptr<telegram_api::contacts_TopPeers>> result) {
+ send_closure(actor_id, &TopDialogManager::on_get_top_peers, std::move(result));
+ });
+ td_->create_handler<GetTopPeersQuery>(std::move(promise))->send(get_vector_hash(ids));
}
-void TopDialogManager::on_result(NetQueryPtr net_query) {
- if (get_link_token() == 1) {
- return;
- }
- SCOPE_EXIT {
- loop();
- };
+void TopDialogManager::on_get_top_peers(Result<telegram_api::object_ptr<telegram_api::contacts_TopPeers>> result) {
normalize_rating(); // once a day too
- last_server_sync_ = Timestamp::now();
- server_sync_state_ = SyncState::Ok;
- G()->td_db()->get_binlog_pmc()->set("top_dialogs_ts", to_string(static_cast<uint32>(Clocks::system())));
- auto r_top_peers = fetch_result<telegram_api::contacts_getTopPeers>(std::move(net_query));
- if (r_top_peers.is_error()) {
- LOG(ERROR) << "contacts_getTopPeers failed: " << r_top_peers.error();
- return;
- }
- auto top_peers_parent = r_top_peers.move_as_ok();
- LOG(INFO) << "contacts_getTopPeers returned " << to_string(top_peers_parent);
- if (top_peers_parent->get_id() == telegram_api::contacts_topPeersNotModified::ID) {
+ if (result.is_error()) {
+ last_server_sync_ = Timestamp::in(SERVER_SYNC_RESEND_DELAY - SERVER_SYNC_DELAY);
+ loop();
return;
}
- CHECK(top_peers_parent->get_id() == telegram_api::contacts_topPeers::ID);
- auto top_peers = move_tl_object_as<telegram_api::contacts_topPeers>(std::move(top_peers_parent));
-
- send_closure(G()->contacts_manager(), &ContactsManager::on_get_users, std::move(top_peers->users_));
- send_closure(G()->contacts_manager(), &ContactsManager::on_get_chats, std::move(top_peers->chats_));
- for (auto &category : top_peers->categories_) {
- auto dialog_category = top_dialog_category_from_telegram_api(*category->category_);
- auto pos = static_cast<size_t>(dialog_category);
- CHECK(pos < by_category_.size());
- auto &top_dialogs = by_category_[pos];
+ last_server_sync_ = Timestamp::now();
+ server_sync_state_ = SyncState::Ok;
- top_dialogs.is_dirty = true;
- top_dialogs.dialogs.clear();
- for (auto &top_peer : category->peers_) {
- TopDialog top_dialog;
- top_dialog.dialog_id = DialogId(top_peer->peer_);
- top_dialog.rating = top_peer->rating_;
- top_dialogs.dialogs.push_back(std::move(top_dialog));
+ auto top_peers_parent = result.move_as_ok();
+ LOG(DEBUG) << "Receive contacts_getTopPeers result: " << to_string(top_peers_parent);
+ switch (top_peers_parent->get_id()) {
+ case telegram_api::contacts_topPeersNotModified::ID:
+ // nothing to do
+ break;
+ case telegram_api::contacts_topPeersDisabled::ID:
+ G()->set_option_boolean("disable_top_chats", true);
+ set_is_enabled(false); // apply immediately
+ break;
+ case telegram_api::contacts_topPeers::ID: {
+ G()->set_option_empty("disable_top_chats");
+ set_is_enabled(true); // apply immediately
+ auto top_peers = move_tl_object_as<telegram_api::contacts_topPeers>(std::move(top_peers_parent));
+
+ td_->contacts_manager_->on_get_users(std::move(top_peers->users_), "on get top chats");
+ td_->contacts_manager_->on_get_chats(std::move(top_peers->chats_), "on get top chats");
+ for (auto &category : top_peers->categories_) {
+ auto dialog_category = get_top_dialog_category(category->category_);
+ auto pos = static_cast<size_t>(dialog_category);
+ CHECK(pos < by_category_.size());
+ auto &top_dialogs = by_category_[pos];
+
+ top_dialogs.is_dirty = true;
+ top_dialogs.dialogs.clear();
+ for (auto &top_peer : category->peers_) {
+ TopDialog top_dialog;
+ top_dialog.dialog_id = DialogId(top_peer->peer_);
+ top_dialog.rating = top_peer->rating_;
+ top_dialogs.dialogs.push_back(std::move(top_dialog));
+ }
+ }
+ db_sync_state_ = SyncState::None;
+ break;
}
+ default:
+ UNREACHABLE();
}
- db_sync_state_ = SyncState::None;
+
+ G()->td_db()->get_binlog_pmc()->set("top_dialogs_ts", to_string(static_cast<uint32>(Clocks::system())));
+ loop();
}
void TopDialogManager::do_save_top_dialogs() {
LOG(INFO) << "Save top chats";
for (size_t top_dialog_category_i = 0; top_dialog_category_i < by_category_.size(); top_dialog_category_i++) {
auto top_dialog_category = TopDialogCategory(top_dialog_category_i);
- auto key = PSTRING() << "top_dialogs#" << top_dialog_category_name(top_dialog_category);
+ auto key = PSTRING() << "top_dialogs#" << get_top_dialog_category_name(top_dialog_category);
auto &top_dialogs = by_category_[top_dialog_category_i];
if (!top_dialogs.is_dirty) {
@@ -366,12 +517,43 @@ void TopDialogManager::do_save_top_dialogs() {
}
void TopDialogManager::start_up() {
- if (!G()->parameters().use_chat_info_db) {
+ init();
+}
+
+void TopDialogManager::tear_down() {
+ parent_.reset();
+}
+
+void TopDialogManager::init() {
+ if (td_->auth_manager_ == nullptr || !td_->auth_manager_->is_authorized()) {
+ return;
+ }
+
+ is_active_ = G()->parameters().use_chat_info_db && !td_->auth_manager_->is_bot();
+ is_enabled_ = !G()->get_option_boolean("disable_top_chats");
+ update_rating_e_decay();
+
+ string need_update_top_peers = G()->td_db()->get_binlog_pmc()->get("top_peers_enabled");
+ if (!need_update_top_peers.empty()) {
+ send_toggle_top_peers(need_update_top_peers[0] == '1');
+ }
+
+ try_start();
+ loop();
+}
+
+void TopDialogManager::try_start() {
+ was_first_sync_ = false;
+ first_unsync_change_ = Timestamp();
+ server_sync_state_ = SyncState::None;
+ last_server_sync_ = Timestamp();
+ CHECK(pending_get_top_dialogs_.empty());
+
+ LOG(DEBUG) << "Init is enabled: " << is_enabled_;
+ if (!is_active_) {
G()->td_db()->get_binlog_pmc()->erase_by_prefix("top_dialogs");
- is_active_ = false;
return;
}
- is_active_ = true;
auto di_top_dialogs_ts = G()->td_db()->get_binlog_pmc()->get("top_dialogs_ts");
if (!di_top_dialogs_ts.empty()) {
@@ -380,36 +562,46 @@ void TopDialogManager::start_up() {
server_sync_state_ = SyncState::Ok;
}
}
- update_rating_e_decay();
- for (size_t top_dialog_category_i = 0; top_dialog_category_i < by_category_.size(); top_dialog_category_i++) {
- auto top_dialog_category = TopDialogCategory(top_dialog_category_i);
- auto key = PSTRING() << "top_dialogs#" << top_dialog_category_name(top_dialog_category);
- auto value = G()->td_db()->get_binlog_pmc()->get(key);
+ if (is_enabled_) {
+ for (size_t top_dialog_category_i = 0; top_dialog_category_i < by_category_.size(); top_dialog_category_i++) {
+ auto top_dialog_category = TopDialogCategory(top_dialog_category_i);
+ auto key = PSTRING() << "top_dialogs#" << get_top_dialog_category_name(top_dialog_category);
+ auto value = G()->td_db()->get_binlog_pmc()->get(key);
- auto &top_dialogs = by_category_[top_dialog_category_i];
- top_dialogs.is_dirty = false;
- if (value.empty()) {
- continue;
+ auto &top_dialogs = by_category_[top_dialog_category_i];
+ top_dialogs.is_dirty = false;
+ if (value.empty()) {
+ continue;
+ }
+ log_event_parse(top_dialogs, value).ensure();
+ }
+ normalize_rating();
+ } else {
+ G()->td_db()->get_binlog_pmc()->erase_by_prefix("top_dialogs#");
+ for (auto &top_dialogs : by_category_) {
+ top_dialogs.is_dirty = false;
+ top_dialogs.rating_timestamp = 0;
+ top_dialogs.dialogs.clear();
}
- log_event_parse(top_dialogs, value).ensure();
}
- normalize_rating();
db_sync_state_ = SyncState::Ok;
send_closure(G()->state_manager(), &StateManager::wait_first_sync,
- PromiseCreator::event(self_closure(this, &TopDialogManager::on_first_sync)));
-
- loop();
+ create_event_promise(self_closure(this, &TopDialogManager::on_first_sync)));
}
void TopDialogManager::on_first_sync() {
was_first_sync_ = true;
+ if (!G()->close_flag() && td_->auth_manager_->is_bot()) {
+ is_active_ = false;
+ try_start();
+ }
loop();
}
void TopDialogManager::loop() {
- if (!is_active_) {
+ if (!is_active_ || G()->close_flag()) {
return;
}
@@ -437,22 +629,24 @@ void TopDialogManager::loop() {
do_get_top_peers();
}
- // db sync
- Timestamp db_sync_timeout;
- if (db_sync_state_ == SyncState::Ok) {
- if (first_unsync_change_) {
- db_sync_timeout = Timestamp::at(first_unsync_change_.at() + DB_SYNC_DELAY);
- if (db_sync_timeout.is_in_past()) {
- db_sync_state_ = SyncState::None;
+ if (is_enabled_) {
+ // database sync
+ Timestamp db_sync_timeout;
+ if (db_sync_state_ == SyncState::Ok) {
+ if (first_unsync_change_) {
+ db_sync_timeout = Timestamp::at(first_unsync_change_.at() + DB_SYNC_DELAY);
+ if (db_sync_timeout.is_in_past()) {
+ db_sync_state_ = SyncState::None;
+ }
}
}
- }
- if (db_sync_state_ == SyncState::Ok) {
- wakeup_timeout.relax(db_sync_timeout);
- } else if (db_sync_state_ == SyncState::None) {
- if (server_sync_state_ == SyncState::Ok) {
- do_save_top_dialogs();
+ if (db_sync_state_ == SyncState::Ok) {
+ wakeup_timeout.relax(db_sync_timeout);
+ } else if (db_sync_state_ == SyncState::None) {
+ if (server_sync_state_ == SyncState::Ok) {
+ do_save_top_dialogs();
+ }
}
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.h b/protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.h
index c301e4252e..fa5e47a254 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/TopDialogManager.h
@@ -1,71 +1,63 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
-#include "td/telegram/td_api.h"
+#include "td/telegram/DialogId.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/TopDialogCategory.h"
-#include "td/telegram/DialogId.h"
-#include "td/telegram/net/NetQuery.h"
+#include "td/actor/actor.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
#include "td/utils/Time.h"
#include <array>
#include <utility>
namespace td {
-enum class TopDialogCategory { Correspondent, BotPM, BotInline, Group, Channel, Call, Size };
-
-inline TopDialogCategory top_dialog_category_from_td_api(const td_api::TopChatCategory &category) {
- switch (category.get_id()) {
- case td_api::topChatCategoryUsers::ID:
- return TopDialogCategory::Correspondent;
- case td_api::topChatCategoryBots::ID:
- return TopDialogCategory::BotPM;
- case td_api::topChatCategoryInlineBots::ID:
- return TopDialogCategory::BotInline;
- case td_api::topChatCategoryGroups::ID:
- return TopDialogCategory::Group;
- case td_api::topChatCategoryChannels::ID:
- return TopDialogCategory::Channel;
- case td_api::topChatCategoryCalls::ID:
- return TopDialogCategory::Call;
- default:
- UNREACHABLE();
- }
-}
-
-class TopDialogManager : public NetQueryCallback {
+
+class Td;
+
+class TopDialogManager final : public Actor {
public:
- explicit TopDialogManager(ActorShared<> parent) : parent_(std::move(parent)) {
- }
+ TopDialogManager(Td *td, ActorShared<> parent);
+
+ void init();
void on_dialog_used(TopDialogCategory category, DialogId dialog_id, int32 date);
- void remove_dialog(TopDialogCategory category, DialogId dialog_id, tl_object_ptr<telegram_api::InputPeer> input_peer);
+ void remove_dialog(TopDialogCategory category, DialogId dialog_id, Promise<Unit> &&promise);
- void get_top_dialogs(TopDialogCategory category, size_t limit, Promise<vector<DialogId>> promise);
+ void get_top_dialogs(TopDialogCategory category, int32 limit, Promise<vector<DialogId>> promise);
void update_rating_e_decay();
+ void update_is_enabled(bool is_enabled);
+
private:
static constexpr size_t MAX_TOP_DIALOGS_LIMIT = 30;
- static constexpr int32 SERVER_SYNC_DELAY = 86400; // seconds
- static constexpr int32 DB_SYNC_DELAY = 5; // seconds
+ static constexpr int32 SERVER_SYNC_DELAY = 86400; // seconds
+ static constexpr int32 SERVER_SYNC_RESEND_DELAY = 60; // seconds
+ static constexpr int32 DB_SYNC_DELAY = 5; // seconds
+
+ Td *td_;
ActorShared<> parent_;
- bool is_active_{false};
+ bool is_active_ = false;
+ bool is_enabled_ = true;
+ int32 rating_e_decay_ = 241920;
+
+ bool have_toggle_top_peers_query_ = false;
+ bool have_pending_toggle_top_peers_query_ = false;
+ bool pending_toggle_top_peers_query_ = false;
bool was_first_sync_{false};
- enum class SyncState { None, Pending, Ok };
+ enum class SyncState : int32 { None, Pending, Ok };
SyncState db_sync_state_ = SyncState::None;
Timestamp first_unsync_change_;
SyncState server_sync_state_ = SyncState::None;
@@ -76,7 +68,7 @@ class TopDialogManager : public NetQueryCallback {
size_t limit;
Promise<vector<DialogId>> promise;
};
- std::vector<GetTopDialogsQuery> pending_get_top_dialogs_;
+ vector<GetTopDialogsQuery> pending_get_top_dialogs_;
struct TopDialog {
DialogId dialog_id;
@@ -89,16 +81,16 @@ class TopDialogManager : public NetQueryCallback {
struct TopDialogs {
bool is_dirty = false;
double rating_timestamp = 0;
- std::vector<TopDialog> dialogs;
+ vector<TopDialog> dialogs;
};
- template <class T>
- friend void parse(TopDialog &top_dialog, T &parser);
- template <class T>
- friend void store(const TopDialog &top_dialog, T &storer);
- template <class T>
- friend void parse(TopDialogs &top_dialogs, T &parser);
- template <class T>
- friend void store(const TopDialogs &top_dialogs, T &storer);
+ template <class StorerT>
+ friend void store(const TopDialog &top_dialog, StorerT &storer);
+ template <class ParserT>
+ friend void parse(TopDialog &top_dialog, ParserT &parser);
+ template <class StorerT>
+ friend void store(const TopDialogs &top_dialogs, StorerT &storer);
+ template <class ParserT>
+ friend void parse(TopDialogs &top_dialogs, ParserT &parser);
std::array<TopDialogs, static_cast<size_t>(TopDialogCategory::Size)> by_category_;
@@ -106,18 +98,31 @@ class TopDialogManager : public NetQueryCallback {
double current_rating_add(double rating_timestamp) const;
void normalize_rating();
- int32 rating_e_decay_ = 241920;
+ bool set_is_enabled(bool is_enabled);
+
+ void send_toggle_top_peers(bool is_enabled);
+
+ void on_toggle_top_peers(bool is_enabled, Result<Unit> &&result);
void do_get_top_dialogs(GetTopDialogsQuery &&query);
+ void on_load_dialogs(GetTopDialogsQuery &&query, vector<DialogId> &&dialog_ids);
+
void do_get_top_peers();
+
void do_save_top_dialogs();
void on_first_sync();
- void on_result(NetQueryPtr net_query) override;
+ void on_get_top_peers(Result<telegram_api::object_ptr<telegram_api::contacts_TopPeers>> result);
+
+ void try_start();
+
+ void start_up() final;
- void start_up() override;
- void loop() override;
+ void loop() final;
+
+ void tear_down() final;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.cpp b/protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.cpp
new file mode 100644
index 0000000000..85f506f8f1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.cpp
@@ -0,0 +1,207 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/TranscriptionInfo.h"
+
+#include "td/telegram/AccessRights.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
+
+namespace td {
+
+class TranscribeAudioQuery final : public Td::ResultHandler {
+ DialogId dialog_id_;
+ std::function<void(Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>>)> handler_;
+
+ public:
+ void send(FullMessageId full_message_id,
+ std::function<void(Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>>)> &&handler) {
+ dialog_id_ = full_message_id.get_dialog_id();
+ handler_ = std::move(handler);
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_transcribeAudio(
+ std::move(input_peer), full_message_id.get_message_id().get_server_message_id().get())));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_transcribeAudio>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ auto result = result_ptr.move_as_ok();
+ LOG(INFO) << "Receive result for TranscribeAudioQuery: " << to_string(result);
+ if (result->transcription_id_ == 0) {
+ return on_error(Status::Error(500, "Receive no recognition identifier"));
+ }
+ auto update = telegram_api::make_object<telegram_api::updateTranscribedAudio>();
+ update->text_ = std::move(result->text_);
+ update->transcription_id_ = result->transcription_id_;
+ update->pending_ = result->pending_;
+ handler_(std::move(update));
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "TranscribeAudioQuery");
+ handler_(std::move(status));
+ }
+};
+
+class RateTranscribedAudioQuery final : public Td::ResultHandler {
+ Promise<Unit> promise_;
+ DialogId dialog_id_;
+
+ public:
+ explicit RateTranscribedAudioQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ }
+
+ void send(FullMessageId full_message_id, int64 transcription_id, bool is_good) {
+ dialog_id_ = full_message_id.get_dialog_id();
+ auto input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
+ if (input_peer == nullptr) {
+ return on_error(Status::Error(400, "Can't access the chat"));
+ }
+ send_query(G()->net_query_creator().create(telegram_api::messages_rateTranscribedAudio(
+ std::move(input_peer), full_message_id.get_message_id().get_server_message_id().get(), transcription_id,
+ is_good)));
+ }
+
+ void on_result(BufferSlice packet) final {
+ auto result_ptr = fetch_result<telegram_api::messages_rateTranscribedAudio>(packet);
+ if (result_ptr.is_error()) {
+ return on_error(result_ptr.move_as_error());
+ }
+
+ bool result = result_ptr.ok();
+ LOG(INFO) << "Receive result for RateTranscribedAudioQuery: " << result;
+ promise_.set_value(Unit());
+ }
+
+ void on_error(Status status) final {
+ td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "RateTranscribedAudioQuery");
+ promise_.set_error(std::move(status));
+ }
+};
+
+bool TranscriptionInfo::recognize_speech(
+ Td *td, FullMessageId full_message_id, Promise<Unit> &&promise,
+ std::function<void(Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>>)> &&handler) {
+ if (is_transcribed_) {
+ promise.set_value(Unit());
+ return false;
+ }
+ speech_recognition_queries_.push_back(std::move(promise));
+ if (speech_recognition_queries_.size() == 1) {
+ last_transcription_error_ = Status::OK();
+ td->create_handler<TranscribeAudioQuery>()->send(full_message_id, std::move(handler));
+ return true;
+ }
+ return false;
+}
+
+vector<Promise<Unit>> TranscriptionInfo::on_final_transcription(string &&text, int64 transcription_id) {
+ CHECK(!is_transcribed_);
+ CHECK(transcription_id_ == 0 || transcription_id_ == transcription_id);
+ CHECK(transcription_id != 0);
+ transcription_id_ = transcription_id;
+ is_transcribed_ = true;
+ text_ = std::move(text);
+ last_transcription_error_ = Status::OK();
+
+ CHECK(!speech_recognition_queries_.empty());
+ auto promises = std::move(speech_recognition_queries_);
+ speech_recognition_queries_.clear();
+
+ return promises;
+}
+
+bool TranscriptionInfo::on_partial_transcription(string &&text, int64 transcription_id) {
+ CHECK(!is_transcribed_);
+ CHECK(transcription_id_ == 0 || transcription_id_ == transcription_id);
+ CHECK(transcription_id != 0);
+ bool is_changed = text_ != text;
+ transcription_id_ = transcription_id;
+ text_ = std::move(text);
+ last_transcription_error_ = Status::OK();
+
+ return is_changed;
+}
+
+vector<Promise<Unit>> TranscriptionInfo::on_failed_transcription(Status &&error) {
+ CHECK(!is_transcribed_);
+ transcription_id_ = 0;
+ text_.clear();
+ last_transcription_error_ = std::move(error);
+
+ CHECK(!speech_recognition_queries_.empty());
+ auto promises = std::move(speech_recognition_queries_);
+ speech_recognition_queries_.clear();
+ return promises;
+}
+
+void TranscriptionInfo::rate_speech_recognition(Td *td, FullMessageId full_message_id, bool is_good,
+ Promise<Unit> &&promise) const {
+ if (!is_transcribed_) {
+ return promise.set_value(Unit());
+ }
+ CHECK(transcription_id_ != 0);
+ td->create_handler<RateTranscribedAudioQuery>(std::move(promise))->send(full_message_id, transcription_id_, is_good);
+}
+
+unique_ptr<TranscriptionInfo> TranscriptionInfo::copy_if_transcribed(const unique_ptr<TranscriptionInfo> &info) {
+ if (info == nullptr || !info->is_transcribed_) {
+ return nullptr;
+ }
+ auto result = make_unique<TranscriptionInfo>();
+ result->is_transcribed_ = true;
+ result->transcription_id_ = info->transcription_id_;
+ result->text_ = info->text_;
+ return result;
+}
+
+bool TranscriptionInfo::update_from(unique_ptr<TranscriptionInfo> &old_info, unique_ptr<TranscriptionInfo> &&new_info) {
+ if (new_info == nullptr || !new_info->is_transcribed_) {
+ return false;
+ }
+ CHECK(new_info->transcription_id_ != 0);
+ CHECK(new_info->last_transcription_error_.is_ok());
+ CHECK(new_info->speech_recognition_queries_.empty());
+ if (old_info == nullptr) {
+ old_info = std::move(new_info);
+ return true;
+ }
+ if (old_info->transcription_id_ != 0 || !old_info->speech_recognition_queries_.empty()) {
+ return false;
+ }
+ CHECK(!old_info->is_transcribed_);
+ old_info = std::move(new_info);
+ return true;
+}
+
+td_api::object_ptr<td_api::SpeechRecognitionResult> TranscriptionInfo::get_speech_recognition_result_object() const {
+ if (is_transcribed_) {
+ return td_api::make_object<td_api::speechRecognitionResultText>(text_);
+ }
+ if (!speech_recognition_queries_.empty()) {
+ return td_api::make_object<td_api::speechRecognitionResultPending>(text_);
+ }
+ if (last_transcription_error_.is_error()) {
+ return td_api::make_object<td_api::speechRecognitionResultError>(td_api::make_object<td_api::error>(
+ last_transcription_error_.code(), last_transcription_error_.message().str()));
+ }
+ return nullptr;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.h b/protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.h
new file mode 100644
index 0000000000..cd11bfe8a4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.h
@@ -0,0 +1,62 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+
+#include <functional>
+
+namespace td {
+
+class Td;
+
+class TranscriptionInfo {
+ bool is_transcribed_ = false;
+ int64 transcription_id_ = 0;
+ string text_;
+
+ // temporary state
+ Status last_transcription_error_;
+ vector<Promise<Unit>> speech_recognition_queries_;
+
+ public:
+ bool is_transcribed() const {
+ return is_transcribed_;
+ }
+
+ bool recognize_speech(
+ Td *td, FullMessageId full_message_id, Promise<Unit> &&promise,
+ std::function<void(Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>>)> &&handler);
+
+ vector<Promise<Unit>> on_final_transcription(string &&text, int64 transcription_id);
+
+ bool on_partial_transcription(string &&text, int64 transcription_id);
+
+ vector<Promise<Unit>> on_failed_transcription(Status &&error);
+
+ void rate_speech_recognition(Td *td, FullMessageId full_message_id, bool is_good, Promise<Unit> &&promise) const;
+
+ static unique_ptr<TranscriptionInfo> copy_if_transcribed(const unique_ptr<TranscriptionInfo> &info);
+
+ static bool update_from(unique_ptr<TranscriptionInfo> &old_info, unique_ptr<TranscriptionInfo> &&new_info);
+
+ td_api::object_ptr<td_api::SpeechRecognitionResult> get_speech_recognition_result_object() const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.hpp b/protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.hpp
new file mode 100644
index 0000000000..224d637dce
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/TranscriptionInfo.hpp
@@ -0,0 +1,31 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/TranscriptionInfo.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void TranscriptionInfo::store(StorerT &storer) const {
+ CHECK(is_transcribed());
+ td::store(transcription_id_, storer);
+ td::store(text_, storer);
+}
+
+template <class ParserT>
+void TranscriptionInfo::parse(ParserT &parser) {
+ is_transcribed_ = true;
+ td::parse(transcription_id_, parser);
+ td::parse(text_, parser);
+ CHECK(transcription_id_ != 0);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/UniqueId.h b/protocols/Telegram/tdlib/td/td/telegram/UniqueId.h
index 746a6c3ba8..9f76a92aa9 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/UniqueId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/UniqueId.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.cpp
index 43153d96a3..2603773177 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.cpp
@@ -1,14 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/UpdatesManager.h"
-#include "td/telegram/telegram_api.hpp"
-
#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/AttachMenuManager.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/CallbackQueriesManager.h"
#include "td/telegram/CallManager.h"
@@ -16,252 +15,390 @@
#include "td/telegram/ChatId.h"
#include "td/telegram/ConfigManager.h"
#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogAction.h"
#include "td/telegram/DialogId.h"
+#include "td/telegram/DialogInviteLink.h"
+#include "td/telegram/DialogParticipant.h"
+#include "td/telegram/DownloadManager.h"
+#include "td/telegram/EmojiStatus.h"
+#include "td/telegram/FolderId.h"
#include "td/telegram/Global.h"
+#include "td/telegram/GroupCallManager.h"
#include "td/telegram/InlineQueriesManager.h"
+#include "td/telegram/LanguagePackManager.h"
#include "td/telegram/Location.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/MessagesManager.h"
+#include "td/telegram/MessageTtl.h"
+#include "td/telegram/misc.h"
#include "td/telegram/net/DcOptions.h"
#include "td/telegram/net/NetQuery.h"
-#include "td/telegram/Payments.h"
+#include "td/telegram/NotificationManager.h"
+#include "td/telegram/NotificationSettingsManager.h"
+#include "td/telegram/NotificationSettingsScope.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/OrderInfo.h"
+#include "td/telegram/PollId.h"
+#include "td/telegram/PollManager.h"
#include "td/telegram/PrivacyManager.h"
+#include "td/telegram/PublicDialogType.h"
+#include "td/telegram/ScheduledServerMessageId.h"
#include "td/telegram/SecretChatId.h"
#include "td/telegram/SecretChatsManager.h"
+#include "td/telegram/ServerMessageId.h"
+#include "td/telegram/SpecialStickerSetType.h"
#include "td/telegram/StateManager.h"
+#include "td/telegram/StickerSetId.h"
#include "td/telegram/StickersManager.h"
+#include "td/telegram/StickerType.h"
#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/telegram_api.hpp"
+#include "td/telegram/ThemeManager.h"
+#include "td/telegram/Usernames.h"
#include "td/telegram/WebPagesManager.h"
+#include "td/actor/MultiPromise.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/utils/algorithm.h"
#include "td/utils/buffer.h"
-#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Random.h"
+#include "td/utils/ScopeGuard.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
+#include "td/utils/Time.h"
+#include <iterator>
#include <limits>
namespace td {
+int VERBOSITY_NAME(get_difference) = VERBOSITY_NAME(INFO);
+
class OnUpdate {
- UpdatesManager *manager_;
+ UpdatesManager *updates_manager_;
tl_object_ptr<telegram_api::Update> &update_;
- bool force_apply_;
+ mutable Promise<Unit> promise_;
public:
- OnUpdate(UpdatesManager *manager, tl_object_ptr<telegram_api::Update> &update, bool force_apply)
- : manager_(manager), update_(update), force_apply_(force_apply) {
+ OnUpdate(UpdatesManager *updates_manager, tl_object_ptr<telegram_api::Update> &update, Promise<Unit> &&promise)
+ : updates_manager_(updates_manager), update_(update), promise_(std::move(promise)) {
}
template <class T>
void operator()(T &obj) const {
CHECK(&*update_ == &obj);
- manager_->on_update(move_tl_object_as<T>(update_), force_apply_);
+ updates_manager_->on_update(move_tl_object_as<T>(update_), std::move(promise_));
}
};
-class GetUpdatesStateQuery : public Td::ResultHandler {
+class GetUpdatesStateQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::updates_state>> promise_;
+
public:
+ explicit GetUpdatesStateQuery(Promise<tl_object_ptr<telegram_api::updates_state>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
void send() {
- // TODO this call must be first after client is logged in, there must be no API calls before
- // it succeeds
- send_query(G()->net_query_creator().create(create_storer(telegram_api::updates_getState())));
+ send_query(G()->net_query_creator().create(telegram_api::updates_getState()));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::updates_getState>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto state = result_ptr.move_as_ok();
- CHECK(state->get_id() == telegram_api::updates_state::ID);
-
- td->updates_manager_->on_get_updates_state(std::move(state), "GetUpdatesStateQuery");
+ promise_.set_value(result_ptr.move_as_ok());
}
- void on_error(uint64 id, Status status) override {
- if (status.message() != CSlice("SESSION_REVOKED") && status.message() != CSlice("USER_DEACTIVATED")) {
- LOG(ERROR) << "updates.getState error: " << status;
- }
- status.ignore();
- td->updates_manager_->on_get_updates_state(nullptr, "GetUpdatesStateQuery");
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
}
};
-class PingServerQuery : public Td::ResultHandler {
+class PingServerQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::updates_state>> promise_;
+
public:
+ explicit PingServerQuery(Promise<tl_object_ptr<telegram_api::updates_state>> &&promise)
+ : promise_(std::move(promise)) {
+ }
+
void send() {
- send_query(G()->net_query_creator().create(create_storer(telegram_api::updates_getState())));
+ send_query(G()->net_query_creator().create(telegram_api::updates_getState()));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::updates_getState>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- auto state = result_ptr.move_as_ok();
- CHECK(state->get_id() == telegram_api::updates_state::ID);
- td->updates_manager_->on_server_pong(std::move(state));
+ promise_.set_value(result_ptr.move_as_ok());
}
- void on_error(uint64 id, Status status) override {
- status.ignore();
- td->updates_manager_->on_server_pong(nullptr);
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
}
};
-class GetDifferenceQuery : public Td::ResultHandler {
- public:
- void send() {
- int32 pts = td->updates_manager_->get_pts();
- int32 date = td->updates_manager_->get_date();
- int32 qts = td->updates_manager_->get_qts();
- if (pts < 0) {
- pts = 0;
- }
+class GetDifferenceQuery final : public Td::ResultHandler {
+ Promise<tl_object_ptr<telegram_api::updates_Difference>> promise_;
- LOG(INFO) << tag("pts", pts) << tag("qts", qts) << tag("date", date);
+ public:
+ explicit GetDifferenceQuery(Promise<tl_object_ptr<telegram_api::updates_Difference>> &&promise)
+ : promise_(std::move(promise)) {
+ }
- send_query(
- G()->net_query_creator().create(create_storer(telegram_api::updates_getDifference(0, pts, 0, date, qts))));
+ void send(int32 pts, int32 date, int32 qts) {
+ send_query(G()->net_query_creator().create(telegram_api::updates_getDifference(0, pts, 0, date, qts)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
+ VLOG(get_difference) << "Receive getDifference result of size " << packet.size();
auto result_ptr = fetch_result<telegram_api::updates_getDifference>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
- td->updates_manager_->on_get_difference(result_ptr.move_as_ok());
+ promise_.set_value(result_ptr.move_as_ok());
}
- void on_error(uint64 id, Status status) override {
- if (status.message() != CSlice("SESSION_REVOKED") && status.message() != CSlice("USER_DEACTIVATED")) {
- LOG(ERROR) << "updates.getDifference error: " << status;
- }
- td->updates_manager_->on_get_difference(nullptr);
- if (status.message() == CSlice("PERSISTENT_TIMESTAMP_INVALID")) {
- td->updates_manager_->set_pts(std::numeric_limits<int32>::max(), "PERSISTENT_TIMESTAMP_INVALID")
- .set_value(Unit());
- }
- status.ignore();
+ void on_error(Status status) final {
+ promise_.set_error(std::move(status));
}
};
-const double UpdatesManager::MAX_UNFILLED_GAP_TIME = 1.0;
-
UpdatesManager::UpdatesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
- pts_manager_.init(-1);
+ last_pts_save_time_ = last_qts_save_time_ = Time::now() - 2 * MAX_PTS_SAVE_DELAY;
+
+ pending_audio_transcription_timeout_.set_callback(on_pending_audio_transcription_timeout_callback);
+ pending_audio_transcription_timeout_.set_callback_data(static_cast<void *>(td_));
}
void UpdatesManager::tear_down() {
parent_.reset();
+
+ LOG(DEBUG) << "Have " << being_processed_updates_ << " unprocessed updates to apply";
+}
+
+void UpdatesManager::start_up() {
+ class StateCallback final : public StateManager::Callback {
+ public:
+ explicit StateCallback(ActorId<UpdatesManager> parent) : parent_(std::move(parent)) {
+ }
+ bool on_online(bool is_online) final {
+ if (is_online) {
+ send_closure(parent_, &UpdatesManager::try_reload_data);
+ }
+ return parent_.is_alive();
+ }
+
+ private:
+ ActorId<UpdatesManager> parent_;
+ };
+ send_closure(G()->state_manager(), &StateManager::add_callback, make_unique<StateCallback>(actor_id(this)));
+
+ next_data_reload_time_ = Time::now() - 1;
+}
+
+void UpdatesManager::hangup_shared() {
+ ref_cnt_--;
+ if (ref_cnt_ == 0) {
+ stop();
+ }
+}
+
+void UpdatesManager::hangup() {
+ pending_pts_updates_.clear();
+ postponed_pts_updates_.clear();
+ postponed_updates_.clear();
+ pending_seq_updates_.clear();
+ pending_qts_updates_.clear();
+
+ hangup_shared();
+}
+
+ActorShared<UpdatesManager> UpdatesManager::create_reference() {
+ ref_cnt_++;
+ return actor_shared(this, 1);
}
void UpdatesManager::fill_pts_gap(void *td) {
- fill_gap(td, "pts");
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(td != nullptr);
+ auto updates_manager = static_cast<Td *>(td)->updates_manager_.get();
+ auto min_pts = std::numeric_limits<int32>::max();
+ auto max_pts = 0;
+ if (!updates_manager->pending_pts_updates_.empty()) {
+ min_pts = min(min_pts, updates_manager->pending_pts_updates_.begin()->first);
+ max_pts = max(max_pts, updates_manager->pending_pts_updates_.rbegin()->first);
+ }
+ if (!updates_manager->postponed_pts_updates_.empty()) {
+ min_pts = min(min_pts, updates_manager->postponed_pts_updates_.begin()->first);
+ max_pts = max(max_pts, updates_manager->postponed_pts_updates_.rbegin()->first);
+ }
+ string source = PSTRING() << "pts from " << updates_manager->get_pts() << " to " << min_pts << '-' << max_pts;
+ fill_gap(td, source.c_str());
}
void UpdatesManager::fill_seq_gap(void *td) {
- fill_gap(td, "seq");
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(td != nullptr);
+ auto updates_manager = static_cast<Td *>(td)->updates_manager_.get();
+ auto min_seq = std::numeric_limits<int32>::max();
+ auto max_seq = 0;
+ if (!updates_manager->pending_seq_updates_.empty()) {
+ min_seq = updates_manager->pending_seq_updates_.begin()->first;
+ max_seq = updates_manager->pending_seq_updates_.rbegin()->second.seq_end;
+ }
+ string source = PSTRING() << "seq from " << updates_manager->seq_ << " to " << min_seq << '-' << max_seq;
+ fill_gap(td, source.c_str());
+}
+
+void UpdatesManager::fill_qts_gap(void *td) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(td != nullptr);
+ auto updates_manager = static_cast<Td *>(td)->updates_manager_.get();
+ auto min_qts = std::numeric_limits<int32>::max();
+ auto max_qts = 0;
+ if (!updates_manager->pending_qts_updates_.empty()) {
+ min_qts = updates_manager->pending_qts_updates_.begin()->first;
+ max_qts = updates_manager->pending_qts_updates_.rbegin()->first;
+ }
+ string source = PSTRING() << "qts from " << updates_manager->get_qts() << " to " << min_qts << '-' << max_qts;
+ fill_gap(td, source.c_str());
}
void UpdatesManager::fill_get_difference_gap(void *td) {
- fill_gap(td, "getDifference");
+ fill_gap(td, nullptr);
}
void UpdatesManager::fill_gap(void *td, const char *source) {
+ if (G()->close_flag()) {
+ return;
+ }
CHECK(td != nullptr);
+ if (!static_cast<Td *>(td)->auth_manager_->is_authorized()) {
+ return;
+ }
auto updates_manager = static_cast<Td *>(td)->updates_manager_.get();
- LOG(WARNING) << "Filling gap in " << source << " by running getDifference. " << updates_manager->get_state();
+ if (source != nullptr && !updates_manager->running_get_difference_) {
+ LOG(WARNING) << "Filling gap in " << source << " by running getDifference";
+ }
updates_manager->get_difference("fill_gap");
}
-string UpdatesManager::get_state() const {
- char buff[1024];
- StringBuilder sb({buff, sizeof(buff)});
- sb << "UpdatesManager is in state ";
- switch (state_.type) {
- case State::Type::General:
- sb << "General";
- break;
- case State::Type::RunningGetUpdatesState:
- sb << "RunningGetUpdatesState";
- break;
- case State::Type::RunningGetDifference:
- sb << "RunningGetDifference";
- break;
- case State::Type::ApplyingDifference:
- sb << "ApplyingDifference";
- break;
- case State::Type::ApplyingDifferenceSlice:
- sb << "ApplyingDifferenceSlice";
- break;
- case State::Type::ApplyingUpdates:
- sb << "ApplyingUpdates";
- break;
- case State::Type::ApplyingSeqUpdates:
- sb << "ApplyingSeqUpdates";
- break;
- default:
- UNREACHABLE();
+void UpdatesManager::on_pending_audio_transcription_timeout_callback(void *td, int64 transcription_id) {
+ if (G()->close_flag()) {
+ return;
+ }
+ CHECK(td != nullptr);
+ if (!static_cast<Td *>(td)->auth_manager_->is_authorized()) {
+ return;
}
- sb << " with pts = " << state_.pts << ", qts = " << state_.qts << " and date = " << state_.date;
- CHECK(!sb.is_error());
- return sb.as_cslice().str();
-}
-void UpdatesManager::set_state(State::Type type) {
- state_.type = type;
- state_.pts = get_pts();
- state_.qts = qts_;
- state_.date = date_;
+ auto updates_manager = static_cast<Td *>(td)->updates_manager_.get();
+ send_closure_later(updates_manager->actor_id(updates_manager), &UpdatesManager::on_pending_audio_transcription_failed,
+ transcription_id, Status::Error(500, "Timeout expired"));
}
void UpdatesManager::get_difference(const char *source) {
- if (get_pts() == -1) {
- init_state();
+ if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
return;
}
-
- if (!td_->auth_manager_->is_authorized()) {
+ if (get_pts() == -1) {
+ init_state();
return;
}
if (running_get_difference_) {
- LOG(INFO) << "Skip running getDifference from " << source << " because it is already running";
+ VLOG(get_difference) << "Skip running getDifference from " << source << " because it is already running";
return;
}
+
+ run_get_difference(false, source);
+}
+
+void UpdatesManager::run_get_difference(bool is_recursive, const char *source) {
+ CHECK(get_pts() != -1);
+ CHECK(td_->auth_manager_->is_authorized());
+ CHECK(!running_get_difference_);
+
running_get_difference_ = true;
- LOG(INFO) << "-----BEGIN GET DIFFERENCE----- from " << source;
+ int32 pts = get_pts();
+ int32 date = get_date();
+ int32 qts = get_qts();
+ if (pts < 0) {
+ pts = 0;
+ }
- before_get_difference();
+ VLOG(get_difference) << "-----BEGIN GET DIFFERENCE----- from " << source << " with pts = " << pts << ", qts = " << qts
+ << ", date = " << date;
- td_->create_handler<GetDifferenceQuery>()->send();
- last_get_difference_pts_ = get_pts();
+ before_get_difference(false);
+
+ if (!is_recursive) {
+ min_postponed_update_pts_ = 0;
+ min_postponed_update_qts_ = 0;
+ }
- set_state(State::Type::RunningGetDifference);
+ auto promise = PromiseCreator::lambda([](Result<tl_object_ptr<telegram_api::updates_Difference>> result) {
+ if (result.is_ok()) {
+ send_closure(G()->updates_manager(), &UpdatesManager::on_get_difference, result.move_as_ok());
+ } else {
+ send_closure(G()->updates_manager(), &UpdatesManager::on_failed_get_difference, result.move_as_error());
+ }
+ });
+ td_->create_handler<GetDifferenceQuery>(std::move(promise))->send(pts, date, qts);
+ last_get_difference_pts_ = pts;
+ last_get_difference_qts_ = qts;
}
-void UpdatesManager::before_get_difference() {
+void UpdatesManager::before_get_difference(bool is_initial) {
// may be called many times before after_get_difference is called
send_closure(G()->state_manager(), &StateManager::on_synchronized, false);
- td_->messages_manager_->before_get_difference();
- send_closure(td_->secret_chats_manager_, &SecretChatsManager::before_get_difference, get_qts());
+ postponed_pts_updates_.insert(std::make_move_iterator(pending_pts_updates_.begin()),
+ std::make_move_iterator(pending_pts_updates_.end()));
+
+ drop_all_pending_pts_updates();
+
+ send_closure_later(td_->notification_manager_actor_, &NotificationManager::before_get_difference);
+
+ if (get_difference_start_time_ <= 0) {
+ get_difference_start_time_ = Time::now();
+ }
}
Promise<> UpdatesManager::add_pts(int32 pts) {
auto id = pts_manager_.add_pts(pts);
- return PromiseCreator::event(self_closure(this, &UpdatesManager::on_pts_ack, id));
+ return create_event_promise(self_closure(this, &UpdatesManager::on_pts_ack, id));
+}
+
+Promise<> UpdatesManager::add_qts(int32 qts) {
+ auto id = qts_manager_.add_pts(qts);
+ return create_event_promise(self_closure(this, &UpdatesManager::on_qts_ack, id));
}
void UpdatesManager::on_pts_ack(PtsManager::PtsId ack_token) {
@@ -272,53 +409,92 @@ void UpdatesManager::on_pts_ack(PtsManager::PtsId ack_token) {
}
}
+void UpdatesManager::on_qts_ack(PtsManager::PtsId ack_token) {
+ auto old_qts = qts_manager_.db_pts();
+ auto new_qts = qts_manager_.finish(ack_token);
+ if (old_qts != new_qts) {
+ save_qts(new_qts);
+ }
+}
+
void UpdatesManager::save_pts(int32 pts) {
if (pts == std::numeric_limits<int32>::max()) {
G()->td_db()->get_binlog_pmc()->erase("updates.pts");
- } else {
- G()->td_db()->get_binlog_pmc()->set("updates.pts", to_string(pts));
+ last_pts_save_time_ -= 2 * MAX_PTS_SAVE_DELAY;
+ pending_pts_ = 0;
+ } else if (!G()->ignore_background_updates()) {
+ auto now = Time::now();
+ auto delay = last_pts_save_time_ + MAX_PTS_SAVE_DELAY - now;
+ if (delay <= 0 || !td_->auth_manager_->is_bot()) {
+ last_pts_save_time_ = now;
+ pending_pts_ = 0;
+ G()->td_db()->get_binlog_pmc()->set("updates.pts", to_string(pts));
+ } else {
+ pending_pts_ = pts;
+ if (!has_timeout()) {
+ set_timeout_in(delay);
+ }
+ }
+ }
+}
+
+void UpdatesManager::save_qts(int32 qts) {
+ if (!G()->ignore_background_updates()) {
+ auto now = Time::now();
+ auto delay = last_qts_save_time_ + MAX_PTS_SAVE_DELAY - now;
+ if (delay <= 0 || !td_->auth_manager_->is_bot()) {
+ last_qts_save_time_ = now;
+ pending_qts_ = 0;
+ G()->td_db()->get_binlog_pmc()->set("updates.qts", to_string(qts));
+ } else {
+ pending_qts_ = qts;
+ if (!has_timeout()) {
+ set_timeout_in(delay);
+ }
+ }
+ }
+}
+
+void UpdatesManager::timeout_expired() {
+ if (pending_pts_ != 0) {
+ last_pts_save_time_ -= 2 * MAX_PTS_SAVE_DELAY;
+ save_pts(pending_pts_);
+ CHECK(pending_pts_ == 0);
+ }
+ if (pending_qts_ != 0) {
+ last_qts_save_time_ -= 2 * MAX_PTS_SAVE_DELAY;
+ save_qts(pending_qts_);
+ CHECK(pending_qts_ == 0);
}
}
Promise<> UpdatesManager::set_pts(int32 pts, const char *source) {
if (pts == std::numeric_limits<int32>::max()) {
LOG(WARNING) << "Update pts from " << get_pts() << " to -1 from " << source;
- G()->td_db()->get_binlog_pmc()->erase("updates.pts");
- auto result = add_pts(std::numeric_limits<int32>::max());
+ save_pts(pts);
+ auto result = add_pts(pts);
init_state();
return result;
}
Promise<> result;
- if (pts > get_pts() || (0 < pts && pts < get_pts() - 999999)) { // pts can only go up or drop cardinally
- if (pts < get_pts() - 999999) {
- LOG(WARNING) << "Pts decreases from " << get_pts() << " to " << pts << " from " << source << ". " << get_state();
+ if (pts > get_pts() || (0 < pts && pts < get_pts() - 399999)) { // pts can only go up or drop cardinally
+ if (pts < get_pts() - 399999) {
+ LOG(WARNING) << "PTS decreases from " << get_pts() << " to " << pts << " from " << source;
} else {
LOG(INFO) << "Update pts from " << get_pts() << " to " << pts << " from " << source;
}
result = add_pts(pts);
- if (last_get_difference_pts_ + FORCED_GET_DIFFERENCE_PTS_DIFF < get_pts()) {
+ if (last_get_difference_pts_ < get_pts() - FORCED_GET_DIFFERENCE_PTS_DIFF) {
last_get_difference_pts_ = get_pts();
- schedule_get_difference("set_pts");
+ schedule_get_difference("rare pts getDifference");
}
- } else if (pts < get_pts()) {
- LOG(ERROR) << "Receive wrong pts = " << pts << " from " << source << ". Current pts = " << get_pts() << ". "
- << get_state();
+ } else if (pts < get_pts() && (pts > 1 || td_->option_manager_->get_option_integer("session_count") <= 1)) {
+ LOG(ERROR) << "Receive wrong pts = " << pts << " from " << source << ". Current pts = " << get_pts();
}
return result;
}
-void UpdatesManager::set_qts(int32 qts) {
- if (qts > qts_) {
- LOG(INFO) << "Update qts to " << qts;
-
- qts_ = qts;
- G()->td_db()->get_binlog_pmc()->set("updates.qts", to_string(qts));
- } else if (qts < qts_) {
- LOG(ERROR) << "Receive wrong qts = " << qts << ". Current qts = " << qts_ << ". " << get_state();
- }
-}
-
void UpdatesManager::set_date(int32 date, bool from_update, string date_source) {
if (date > date_) {
LOG(INFO) << "Update date to " << date;
@@ -341,7 +517,9 @@ void UpdatesManager::set_date(int32 date, bool from_update, string date_source)
date_ = date;
date_source_ = std::move(date_source);
- G()->td_db()->get_binlog_pmc()->set("updates.date", to_string(date));
+ if (!G()->ignore_background_updates()) {
+ G()->td_db()->get_binlog_pmc()->set("updates.date", to_string(date));
+ }
} else if (date < date_) {
if (from_update) {
date++;
@@ -351,17 +529,61 @@ void UpdatesManager::set_date(int32 date, bool from_update, string date_source)
}
}
LOG(ERROR) << "Receive wrong by " << (date_ - date) << " date = " << date << " from " << date_source
- << ". Current date = " << date_ << " from " << date_source_ << ". " << get_state();
+ << ". Current date = " << date_ << " from " << date_source_;
}
}
+bool UpdatesManager::is_acceptable_user(UserId user_id) const {
+ return td_->contacts_manager_->have_user_force(user_id) && td_->contacts_manager_->have_user(user_id);
+}
+
+bool UpdatesManager::is_acceptable_chat(ChatId chat_id) const {
+ return td_->contacts_manager_->have_chat_force(chat_id);
+}
+
+bool UpdatesManager::is_acceptable_channel(ChannelId channel_id) const {
+ return td_->contacts_manager_->have_channel_force(channel_id);
+}
+
+bool UpdatesManager::is_acceptable_peer(const tl_object_ptr<telegram_api::Peer> &peer) const {
+ if (peer == nullptr) {
+ return true;
+ }
+
+ DialogId dialog_id(peer);
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ if (!is_acceptable_user(dialog_id.get_user_id())) {
+ return false;
+ }
+ break;
+ case DialogType::Chat:
+ if (!is_acceptable_chat(dialog_id.get_chat_id())) {
+ return false;
+ }
+ break;
+ case DialogType::Channel:
+ if (!is_acceptable_channel(dialog_id.get_channel_id())) {
+ return false;
+ }
+ break;
+ case DialogType::None:
+ return false;
+ case DialogType::SecretChat:
+ default:
+ UNREACHABLE();
+ return false;
+ }
+ return true;
+}
+
bool UpdatesManager::is_acceptable_message_entities(
const vector<tl_object_ptr<telegram_api::MessageEntity>> &message_entities) const {
for (auto &entity : message_entities) {
if (entity->get_id() == telegram_api::messageEntityMentionName::ID) {
auto entity_mention_name = static_cast<const telegram_api::messageEntityMentionName *>(entity.get());
UserId user_id(entity_mention_name->user_id_);
- if (!td_->contacts_manager_->have_user(user_id) || !td_->contacts_manager_->have_input_user(user_id)) {
+ if (!is_acceptable_user(user_id) || td_->contacts_manager_->get_input_user(user_id).is_error()) {
return false;
}
}
@@ -369,48 +591,77 @@ bool UpdatesManager::is_acceptable_message_entities(
return true;
}
+bool UpdatesManager::is_acceptable_reply_markup(const tl_object_ptr<telegram_api::ReplyMarkup> &reply_markup) const {
+ if (reply_markup == nullptr || reply_markup->get_id() != telegram_api::replyInlineMarkup::ID) {
+ return true;
+ }
+ for (const auto &row : static_cast<const telegram_api::replyInlineMarkup *>(reply_markup.get())->rows_) {
+ for (const auto &button : row->buttons_) {
+ if (button->get_id() == telegram_api::keyboardButtonUserProfile::ID) {
+ auto user_profile_button = static_cast<const telegram_api::keyboardButtonUserProfile *>(button.get());
+ UserId user_id(user_profile_button->user_id_);
+ if (!is_acceptable_user(user_id) || td_->contacts_manager_->get_input_user(user_id).is_error()) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool UpdatesManager::is_acceptable_message_reply_header(
+ const telegram_api::object_ptr<telegram_api::messageReplyHeader> &header) const {
+ if (header == nullptr) {
+ return true;
+ }
+
+ if (!is_acceptable_peer(header->reply_to_peer_id_)) {
+ return false;
+ }
+ return true;
+}
+
+bool UpdatesManager::is_acceptable_message_forward_header(
+ const telegram_api::object_ptr<telegram_api::messageFwdHeader> &header) const {
+ if (header == nullptr) {
+ return true;
+ }
+
+ if (!is_acceptable_peer(header->from_id_)) {
+ return false;
+ }
+ if (!is_acceptable_peer(header->saved_from_peer_)) {
+ return false;
+ }
+ return true;
+}
+
bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_ptr) const {
CHECK(message_ptr != nullptr);
int32 constructor_id = message_ptr->get_id();
- bool is_channel_post = false;
- DialogId dialog_id;
- UserId sender_user_id;
-
switch (constructor_id) {
case telegram_api::messageEmpty::ID:
return true;
case telegram_api::message::ID: {
auto message = static_cast<const telegram_api::message *>(message_ptr);
- is_channel_post = (message->flags_ & MessagesManager::MESSAGE_FLAG_IS_POST) != 0;
- dialog_id = DialogId(message->to_id_);
- if (message->flags_ & MessagesManager::MESSAGE_FLAG_HAS_FROM_ID) {
- sender_user_id = UserId(message->from_id_);
+ if (!is_acceptable_peer(message->peer_id_)) {
+ return false;
+ }
+ if (!is_acceptable_peer(message->from_id_)) {
+ return false;
}
- if (message->flags_ & MessagesManager::MESSAGE_FLAG_IS_FORWARDED) {
- CHECK(message->fwd_from_ != nullptr);
- auto flags = message->fwd_from_->flags_;
- bool from_post = (flags & MessagesManager::MESSAGE_FORWARD_HEADER_FLAG_HAS_CHANNEL_ID) != 0;
- if (from_post && !td_->contacts_manager_->have_channel(ChannelId(message->fwd_from_->channel_id_))) {
- return false;
- }
- if (flags & MessagesManager::MESSAGE_FORWARD_HEADER_FLAG_HAS_AUTHOR_ID) {
- UserId user_id(message->fwd_from_->from_id_);
- if (from_post && !td_->contacts_manager_->have_min_user(user_id)) {
- return false;
- }
- if (!from_post && !td_->contacts_manager_->have_user(user_id)) {
- return false;
- }
- }
- } else {
- CHECK(message->fwd_from_ == nullptr);
+ if (!is_acceptable_message_reply_header(message->reply_to_)) {
+ return false;
+ }
+ if (!is_acceptable_message_forward_header(message->fwd_from_)) {
+ return false;
}
if ((message->flags_ & MessagesManager::MESSAGE_FLAG_IS_SENT_VIA_BOT) &&
- !td_->contacts_manager_->have_user(UserId(message->via_bot_id_))) {
+ !is_acceptable_user(UserId(message->via_bot_id_))) {
return false;
}
@@ -424,11 +675,24 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_
if (media_id == telegram_api::messageMediaContact::ID) {
auto message_media_contact = static_cast<const telegram_api::messageMediaContact *>(message->media_.get());
UserId user_id(message_media_contact->user_id_);
- if (user_id != UserId() && !td_->contacts_manager_->have_user(user_id)) {
+ if (user_id != UserId() && !is_acceptable_user(user_id)) {
return false;
}
}
/*
+ // the users are always min, so no need to check
+ if (media_id == telegram_api::messageMediaPoll::ID) {
+ auto message_media_poll = static_cast<const telegram_api::messageMediaPoll *>(message->media_.get());
+ for (auto recent_voter_user_id : message_media_poll->results_->recent_voters_) {
+ UserId user_id(recent_voter_user_id);
+ if (!is_acceptable_user(user_id)) {
+ return false;
+ }
+ }
+ }
+ */
+ /*
+ // the channel is always min, so no need to check
if (media_id == telegram_api::messageMediaWebPage::ID) {
auto message_media_web_page = static_cast<const telegram_api::messageMediaWebPage *>(message->media_.get());
if (message_media_web_page->webpage_->get_id() == telegram_api::webPage::ID) {
@@ -442,7 +706,7 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_
auto page_block_channel = static_cast<const telegram_api::pageBlockChannel *>(page_block.get());
auto channel_id = ContactsManager::get_channel_id(page_block_channel->channel_);
if (channel_id.is_valid()) {
- if (!td_->contacts_manager_->have_channel(channel_id)) {
+ if (!is_acceptable_channel(channel_id)) {
return false;
}
} else {
@@ -458,15 +722,27 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_
CHECK(message->media_ == nullptr);
}
+ /*
+ // the dialogs are always min, so no need to check
+ if (message->replies_ != nullptr) {
+ for (auto &peer : message->replies_->recent_repliers_) {
+ if (!is_acceptable_peer(peer)) {
+ return false;
+ }
+ }
+ }
+ */
+
break;
}
case telegram_api::messageService::ID: {
auto message = static_cast<const telegram_api::messageService *>(message_ptr);
- is_channel_post = (message->flags_ & MessagesManager::MESSAGE_FLAG_IS_POST) != 0;
- dialog_id = DialogId(message->to_id_);
- if (message->flags_ & MessagesManager::MESSAGE_FLAG_HAS_FROM_ID) {
- sender_user_id = UserId(message->from_id_);
+ if (!is_acceptable_peer(message->peer_id_)) {
+ return false;
+ }
+ if (!is_acceptable_peer(message->from_id_)) {
+ return false;
}
const telegram_api::MessageAction *action = message->action_.get();
@@ -487,11 +763,24 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_
case telegram_api::messageActionPaymentSent::ID:
case telegram_api::messageActionPaymentSentMe::ID:
case telegram_api::messageActionScreenshotTaken::ID:
+ case telegram_api::messageActionSecureValuesSent::ID:
+ case telegram_api::messageActionSecureValuesSentMe::ID:
+ case telegram_api::messageActionContactSignUp::ID:
+ case telegram_api::messageActionGroupCall::ID:
+ case telegram_api::messageActionGroupCallScheduled::ID:
+ case telegram_api::messageActionSetMessagesTTL::ID:
+ case telegram_api::messageActionSetChatTheme::ID:
+ case telegram_api::messageActionChatJoinedByRequest::ID:
+ case telegram_api::messageActionWebViewDataSentMe::ID:
+ case telegram_api::messageActionWebViewDataSent::ID:
+ case telegram_api::messageActionGiftPremium::ID:
+ case telegram_api::messageActionTopicCreate::ID:
+ case telegram_api::messageActionTopicEdit::ID:
break;
case telegram_api::messageActionChatCreate::ID: {
auto chat_create = static_cast<const telegram_api::messageActionChatCreate *>(action);
for (auto &user : chat_create->users_) {
- if (!td_->contacts_manager_->have_user(UserId(user))) {
+ if (!is_acceptable_user(UserId(user))) {
return false;
}
}
@@ -500,40 +789,55 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_
case telegram_api::messageActionChatAddUser::ID: {
auto chat_add_user = static_cast<const telegram_api::messageActionChatAddUser *>(action);
for (auto &user : chat_add_user->users_) {
- if (!td_->contacts_manager_->have_user(UserId(user))) {
+ if (!is_acceptable_user(UserId(user))) {
return false;
}
}
break;
}
- case telegram_api::messageActionChatJoinedByLink::ID: {
- auto chat_joined_by_link = static_cast<const telegram_api::messageActionChatJoinedByLink *>(action);
- if (!td_->contacts_manager_->have_user(UserId(chat_joined_by_link->inviter_id_))) {
- return false;
- }
+ case telegram_api::messageActionChatJoinedByLink::ID:
+ // inviter_id_ isn't used
break;
- }
case telegram_api::messageActionChatDeleteUser::ID: {
auto chat_delete_user = static_cast<const telegram_api::messageActionChatDeleteUser *>(action);
- if (!td_->contacts_manager_->have_user(UserId(chat_delete_user->user_id_))) {
+ if (!is_acceptable_user(UserId(chat_delete_user->user_id_))) {
return false;
}
break;
}
case telegram_api::messageActionChatMigrateTo::ID: {
auto chat_migrate_to = static_cast<const telegram_api::messageActionChatMigrateTo *>(action);
- if (!td_->contacts_manager_->have_channel(ChannelId(chat_migrate_to->channel_id_))) {
+ if (!is_acceptable_channel(ChannelId(chat_migrate_to->channel_id_))) {
return false;
}
break;
}
case telegram_api::messageActionChannelMigrateFrom::ID: {
auto channel_migrate_from = static_cast<const telegram_api::messageActionChannelMigrateFrom *>(action);
- if (!td_->contacts_manager_->have_chat(ChatId(channel_migrate_from->chat_id_))) {
+ if (!is_acceptable_chat(ChatId(channel_migrate_from->chat_id_))) {
+ return false;
+ }
+ break;
+ }
+ case telegram_api::messageActionGeoProximityReached::ID: {
+ auto geo_proximity_reached = static_cast<const telegram_api::messageActionGeoProximityReached *>(action);
+ if (!is_acceptable_peer(geo_proximity_reached->from_id_)) {
+ return false;
+ }
+ if (!is_acceptable_peer(geo_proximity_reached->to_id_)) {
return false;
}
break;
}
+ case telegram_api::messageActionInviteToGroupCall::ID: {
+ auto invite_to_group_call = static_cast<const telegram_api::messageActionInviteToGroupCall *>(action);
+ for (auto &user : invite_to_group_call->users_) {
+ if (!is_acceptable_user(UserId(user))) {
+ return false;
+ }
+ }
+ break;
+ }
default:
UNREACHABLE();
return false;
@@ -545,43 +849,6 @@ bool UpdatesManager::is_acceptable_message(const telegram_api::Message *message_
return false;
}
- switch (dialog_id.get_type()) {
- case DialogType::None:
- LOG(ERROR) << "Receive message in the invalid " << dialog_id;
- return false;
- case DialogType::User: {
- if (!td_->contacts_manager_->have_user(dialog_id.get_user_id())) {
- return false;
- }
- break;
- }
- case DialogType::Chat: {
- if (!td_->contacts_manager_->have_chat(dialog_id.get_chat_id())) {
- return false;
- }
- break;
- }
- case DialogType::Channel: {
- if (!td_->contacts_manager_->have_channel(dialog_id.get_channel_id())) {
- return false;
- }
- break;
- }
- case DialogType::SecretChat:
- default:
- UNREACHABLE();
- return false;
- }
-
- if (sender_user_id != UserId()) {
- if (is_channel_post && !td_->contacts_manager_->have_min_user(sender_user_id)) {
- return false;
- }
- if (!is_channel_post && !td_->contacts_manager_->have_user(sender_user_id)) {
- return false;
- }
- }
-
return true;
}
@@ -597,6 +864,9 @@ bool UpdatesManager::is_acceptable_update(const telegram_api::Update *update) co
if (id == telegram_api::updateNewChannelMessage::ID) {
message = static_cast<const telegram_api::updateNewChannelMessage *>(update)->message_.get();
}
+ if (id == telegram_api::updateNewScheduledMessage::ID) {
+ message = static_cast<const telegram_api::updateNewScheduledMessage *>(update)->message_.get();
+ }
if (id == telegram_api::updateEditMessage::ID) {
message = static_cast<const telegram_api::updateEditMessage *>(update)->message_.get();
}
@@ -619,20 +889,50 @@ bool UpdatesManager::is_acceptable_update(const telegram_api::Update *update) co
return true;
}
-void UpdatesManager::on_get_updates(tl_object_ptr<telegram_api::Updates> &&updates_ptr) {
+void UpdatesManager::on_get_updates(tl_object_ptr<telegram_api::Updates> &&updates_ptr, Promise<Unit> &&promise) {
CHECK(updates_ptr != nullptr);
+ promise = PromiseCreator::lambda(
+ [promise = std::move(promise), update_ids = get_update_ids(updates_ptr.get())](Result<Unit> result) mutable {
+ if (!G()->close_flag() && result.is_error()) {
+ LOG(ERROR) << "Failed to process updates " << update_ids << ": " << result.error();
+ }
+ promise.set_value(Unit());
+ });
+
auto updates_type = updates_ptr->get_id();
if (updates_type != telegram_api::updateShort::ID) {
LOG(INFO) << "Receive " << to_string(updates_ptr);
}
if (!td_->auth_manager_->is_authorized()) {
- LOG(INFO) << "Ignore updates received before authorization or after logout";
- return;
+ if (updates_type == telegram_api::updateShort::ID && !G()->close_flag()) {
+ auto &update = static_cast<telegram_api::updateShort *>(updates_ptr.get())->update_;
+ auto update_id = update->get_id();
+ if (update_id == telegram_api::updateLoginToken::ID) {
+ td_->auth_manager_->on_update_login_token();
+ return promise.set_value(Unit());
+ }
+
+ switch (update_id) {
+ case telegram_api::updateServiceNotification::ID:
+ case telegram_api::updateDcOptions::ID:
+ case telegram_api::updateConfig::ID:
+ case telegram_api::updateLangPackTooLong::ID:
+ case telegram_api::updateLangPack::ID:
+ LOG(INFO) << "Apply without authorization " << to_string(updates_ptr);
+ downcast_call(*update, OnUpdate(this, update, std::move(promise)));
+ return;
+ default:
+ break;
+ }
+ }
+ LOG(INFO) << "Ignore received before authorization or after logout " << to_string(updates_ptr);
+ return promise.set_value(Unit());
}
- switch (updates_ptr->get_id()) {
+ switch (updates_type) {
case telegram_api::updatesTooLong::ID:
get_difference("updatesTooLong");
+ promise.set_value(Unit());
break;
case telegram_api::updateShortMessage::ID: {
auto update = move_tl_object_as<telegram_api::updateShortMessage>(updates_ptr);
@@ -645,20 +945,20 @@ void UpdatesManager::on_get_updates(tl_object_ptr<telegram_api::Updates> &&updat
update->flags_ ^= MessagesManager::MESSAGE_FLAG_HAS_MEDIA;
}
- auto from_id = update->flags_ & MessagesManager::MESSAGE_FLAG_IS_OUT
- ? td_->contacts_manager_->get_my_id("on_get_updates").get()
- : update->user_id_;
-
+ auto from_id = update->flags_ & MessagesManager::MESSAGE_FLAG_IS_OUT ? td_->contacts_manager_->get_my_id().get()
+ : update->user_id_;
update->flags_ |= MessagesManager::MESSAGE_FLAG_HAS_FROM_ID;
+
+ auto message = make_tl_object<telegram_api::message>(
+ update->flags_, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, update->id_,
+ make_tl_object<telegram_api::peerUser>(from_id), make_tl_object<telegram_api::peerUser>(update->user_id_),
+ std::move(update->fwd_from_), update->via_bot_id_, std::move(update->reply_to_), update->date_,
+ update->message_, nullptr, nullptr, std::move(update->entities_), 0, 0, nullptr, 0, string(), 0, nullptr,
+ Auto(), update->ttl_period_);
on_pending_update(
- make_tl_object<telegram_api::updateNewMessage>(
- make_tl_object<telegram_api::message>(
- update->flags_, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- false /*ignored*/, update->id_, from_id, make_tl_object<telegram_api::peerUser>(update->user_id_),
- std::move(update->fwd_from_), update->via_bot_id_, update->reply_to_msg_id_, update->date_,
- update->message_, nullptr, nullptr, std::move(update->entities_), 0, 0, "", 0),
- update->pts_, update->pts_count_),
- 0, "telegram_api::updatesShortMessage");
+ make_tl_object<telegram_api::updateNewMessage>(std::move(message), update->pts_, update->pts_count_), 0,
+ std::move(promise), "telegram_api::updatesShortMessage");
break;
}
case telegram_api::updateShortChatMessage::ID: {
@@ -673,62 +973,98 @@ void UpdatesManager::on_get_updates(tl_object_ptr<telegram_api::Updates> &&updat
}
update->flags_ |= MessagesManager::MESSAGE_FLAG_HAS_FROM_ID;
- on_pending_update(make_tl_object<telegram_api::updateNewMessage>(
- make_tl_object<telegram_api::message>(
- update->flags_, false /*ignored*/, false /*ignored*/, false /*ignored*/,
- false /*ignored*/, false /*ignored*/, update->id_, update->from_id_,
- make_tl_object<telegram_api::peerChat>(update->chat_id_), std::move(update->fwd_from_),
- update->via_bot_id_, update->reply_to_msg_id_, update->date_, update->message_, nullptr,
- nullptr, std::move(update->entities_), 0, 0, "", 0),
- update->pts_, update->pts_count_),
- 0, "telegram_api::updatesShortChatMessage");
+ auto message = make_tl_object<telegram_api::message>(
+ update->flags_, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/,
+ false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, false /*ignored*/, update->id_,
+ make_tl_object<telegram_api::peerUser>(update->from_id_),
+ make_tl_object<telegram_api::peerChat>(update->chat_id_), std::move(update->fwd_from_), update->via_bot_id_,
+ std::move(update->reply_to_), update->date_, update->message_, nullptr, nullptr, std::move(update->entities_),
+ 0, 0, nullptr, 0, string(), 0, nullptr, Auto(), update->ttl_period_);
+ on_pending_update(
+ make_tl_object<telegram_api::updateNewMessage>(std::move(message), update->pts_, update->pts_count_), 0,
+ std::move(promise), "telegram_api::updatesShortChatMessage");
break;
}
case telegram_api::updateShort::ID: {
auto update = move_tl_object_as<telegram_api::updateShort>(updates_ptr);
- LOG(DEBUG) << "Receive " << to_string(update);
+ LOG(DEBUG) << "Receive " << oneline(to_string(update));
if (!is_acceptable_update(update->update_.get())) {
- LOG(ERROR) << "Receive unacceptable short update: " << td::oneline(to_string(update));
+ LOG(ERROR) << "Receive unacceptable short update: " << oneline(to_string(update));
+ promise.set_value(Unit());
return get_difference("unacceptable short update");
}
-
- if (!downcast_call(*update->update_, OnUpdate(this, update->update_, false))) {
- LOG(ERROR) << "Can't call on some update";
- }
+ short_update_date_ = update->date_;
+ downcast_call(*update->update_, OnUpdate(this, update->update_, std::move(promise)));
+ short_update_date_ = 0;
break;
}
case telegram_api::updatesCombined::ID: {
auto updates = move_tl_object_as<telegram_api::updatesCombined>(updates_ptr);
- td_->contacts_manager_->on_get_users(std::move(updates->users_));
- td_->contacts_manager_->on_get_chats(std::move(updates->chats_));
- on_pending_updates(std::move(updates->updates_), updates->seq_start_, updates->seq_, updates->date_,
- "telegram_api::updatesCombined");
+ td_->contacts_manager_->on_get_users(std::move(updates->users_), "updatesCombined");
+ td_->contacts_manager_->on_get_chats(std::move(updates->chats_), "updatesCombined");
+ on_pending_updates(std::move(updates->updates_), updates->seq_start_, updates->seq_, updates->date_, Time::now(),
+ std::move(promise), "telegram_api::updatesCombined");
break;
}
case telegram_api::updates::ID: {
auto updates = move_tl_object_as<telegram_api::updates>(updates_ptr);
- td_->contacts_manager_->on_get_users(std::move(updates->users_));
- td_->contacts_manager_->on_get_chats(std::move(updates->chats_));
- on_pending_updates(std::move(updates->updates_), updates->seq_, updates->seq_, updates->date_,
- "telegram_api::updates");
+ string source_str;
+ const char *source = "updates";
+ if (updates->updates_.size() == 1 && updates->updates_[0] != nullptr) {
+ source_str = PSTRING() << "update " << updates->updates_[0]->get_id();
+ source = source_str.c_str();
+ }
+ td_->contacts_manager_->on_get_users(std::move(updates->users_), source);
+ td_->contacts_manager_->on_get_chats(std::move(updates->chats_), source);
+ on_pending_updates(std::move(updates->updates_), updates->seq_, updates->seq_, updates->date_, Time::now(),
+ std::move(promise), "telegram_api::updates");
break;
}
case telegram_api::updateShortSentMessage::ID:
LOG(ERROR) << "Receive " << oneline(to_string(updates_ptr));
get_difference("updateShortSentMessage");
+ promise.set_value(Unit());
break;
default:
UNREACHABLE();
}
}
-void UpdatesManager::on_failed_get_difference() {
+void UpdatesManager::on_failed_get_updates_state(Status &&error) {
+ if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
+ return;
+ }
+ if (error.code() != 401) {
+ LOG(ERROR) << "Receive updates.getState error: " << error;
+ }
+
+ running_get_difference_ = false;
+ schedule_get_difference("on_failed_get_updates_state");
+}
+
+void UpdatesManager::on_failed_get_difference(Status &&error) {
+ if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
+ return;
+ }
+ if (error.code() != 401) {
+ LOG(ERROR) << "Receive updates.getDifference error: " << error;
+ }
+
+ running_get_difference_ = false;
schedule_get_difference("on_failed_get_difference");
+
+ if (error.message() == Slice("PERSISTENT_TIMESTAMP_INVALID")) {
+ set_pts(std::numeric_limits<int32>::max(), "PERSISTENT_TIMESTAMP_INVALID").set_value(Unit());
+ }
}
void UpdatesManager::schedule_get_difference(const char *source) {
- LOG(INFO) << "Schedule getDifference from " << source;
+ if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
+ return;
+ }
if (!retry_timeout_.has_timeout()) {
+ LOG(WARNING) << "Schedule getDifference in " << retry_time_ << " seconds with pts = " << get_pts()
+ << ", qts = " << get_qts() << ", date = " << get_date() << " from " << source;
retry_timeout_.set_callback(std::move(fill_get_difference_gap));
retry_timeout_.set_callback_data(static_cast<void *>(td_));
retry_timeout_.set_timeout_in(retry_time_);
@@ -736,28 +1072,34 @@ void UpdatesManager::schedule_get_difference(const char *source) {
if (retry_time_ > 60) {
retry_time_ = Random::fast(60, 80);
}
+ } else {
+ VLOG(get_difference) << "Schedule getDifference from " << source;
}
}
void UpdatesManager::on_get_updates_state(tl_object_ptr<telegram_api::updates_state> &&state, const char *source) {
- if (state == nullptr) {
- running_get_difference_ = false;
- on_failed_get_difference();
- return;
- }
- LOG(INFO) << "Receive " << oneline(to_string(state)) << " from " << source;
+ CHECK(state != nullptr);
+
+ VLOG(get_difference) << "Receive " << oneline(to_string(state)) << " from " << source;
// TODO use state->unread_count;
if (get_pts() == std::numeric_limits<int32>::max()) {
LOG(WARNING) << "Restore pts to " << state->pts_;
// restoring right pts
+ CHECK(pending_pts_updates_.empty());
+ auto real_running_get_difference = running_get_difference_;
+ running_get_difference_ = false;
+ process_postponed_pts_updates(); // drop all updates with old pts
+ running_get_difference_ = real_running_get_difference;
pts_manager_.init(state->pts_);
last_get_difference_pts_ = get_pts();
+ last_pts_save_time_ = Time::now() - 2 * MAX_PTS_SAVE_DELAY;
+ save_pts(state->pts_);
} else {
string full_source = "on_get_updates_state " + oneline(to_string(state)) + " from " + source;
set_pts(state->pts_, full_source.c_str()).set_value(Unit());
set_date(state->date_, false, std::move(full_source));
- // set_qts(state->qts_);
+ add_qts(state->qts_).set_value(Unit());
seq_ = state->seq_;
}
@@ -768,9 +1110,8 @@ void UpdatesManager::on_get_updates_state(tl_object_ptr<telegram_api::updates_st
}
}
-std::unordered_set<int64> UpdatesManager::get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr) {
- std::unordered_set<int64> random_ids;
- const vector<tl_object_ptr<telegram_api::Update>> *updates;
+const vector<tl_object_ptr<telegram_api::Update>> *UpdatesManager::get_updates(
+ const telegram_api::Updates *updates_ptr) {
switch (updates_ptr->get_id()) {
case telegram_api::updatesTooLong::ID:
case telegram_api::updateShortMessage::ID:
@@ -778,66 +1119,185 @@ std::unordered_set<int64> UpdatesManager::get_sent_messages_random_ids(const tel
case telegram_api::updateShort::ID:
case telegram_api::updateShortSentMessage::ID:
LOG(ERROR) << "Receive " << oneline(to_string(*updates_ptr)) << " instead of updates";
- return random_ids;
- case telegram_api::updatesCombined::ID: {
- updates = &static_cast<const telegram_api::updatesCombined *>(updates_ptr)->updates_;
- break;
- }
- case telegram_api::updates::ID: {
- updates = &static_cast<const telegram_api::updates *>(updates_ptr)->updates_;
- break;
- }
+ return nullptr;
+ case telegram_api::updatesCombined::ID:
+ return &static_cast<const telegram_api::updatesCombined *>(updates_ptr)->updates_;
+ case telegram_api::updates::ID:
+ return &static_cast<const telegram_api::updates *>(updates_ptr)->updates_;
default:
UNREACHABLE();
- return random_ids;
+ return nullptr;
}
+}
+
+vector<tl_object_ptr<telegram_api::Update>> *UpdatesManager::get_updates(telegram_api::Updates *updates_ptr) {
+ return const_cast<vector<tl_object_ptr<telegram_api::Update>> *>(
+ get_updates(static_cast<const telegram_api::Updates *>(updates_ptr)));
+}
+
+FlatHashSet<int64> UpdatesManager::get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr) {
+ FlatHashSet<int64> random_ids;
+ auto updates = get_updates(updates_ptr);
+ if (updates != nullptr) {
+ for (auto &update : *updates) {
+ if (update->get_id() == telegram_api::updateMessageID::ID) {
+ int64 random_id = static_cast<const telegram_api::updateMessageID *>(update.get())->random_id_;
+ if (random_id != 0 && !random_ids.insert(random_id).second) {
+ LOG(ERROR) << "Receive twice updateMessageID for " << random_id;
+ }
+ }
+ }
+ }
+ return random_ids;
+}
+
+const telegram_api::Message *UpdatesManager::get_message_by_random_id(const telegram_api::Updates *updates_ptr,
+ DialogId dialog_id, int64 random_id) {
+ auto updates = get_updates(updates_ptr);
+ if (updates == nullptr) {
+ return nullptr;
+ }
+
+ int32 message_id = 0;
for (auto &update : *updates) {
if (update->get_id() == telegram_api::updateMessageID::ID) {
- int64 random_id = static_cast<const telegram_api::updateMessageID *>(update.get())->random_id_;
- if (!random_ids.insert(random_id).second) {
- LOG(ERROR) << "Receive twice updateMessageID for " << random_id;
+ auto update_message_id = static_cast<const telegram_api::updateMessageID *>(update.get());
+ if (update_message_id->random_id_ == random_id) {
+ if (message_id != 0 || update_message_id->id_ == 0) {
+ return nullptr;
+ }
+ message_id = update_message_id->id_;
}
}
}
- return random_ids;
+ if (message_id == 0) {
+ return nullptr;
+ }
+
+ const telegram_api::Message *result = nullptr;
+ FullMessageId full_message_id(dialog_id, MessageId(ServerMessageId(message_id)));
+ for (auto &update : *updates) {
+ auto constructor_id = update->get_id();
+ const tl_object_ptr<telegram_api::Message> *message = nullptr;
+ if (constructor_id == telegram_api::updateNewMessage::ID) {
+ message = &static_cast<const telegram_api::updateNewMessage *>(update.get())->message_;
+ } else if (constructor_id == telegram_api::updateNewChannelMessage::ID) {
+ message = &static_cast<const telegram_api::updateNewChannelMessage *>(update.get())->message_;
+ }
+ if (message != nullptr && MessagesManager::get_full_message_id(*message, false) == full_message_id) {
+ if (result != nullptr) {
+ return nullptr;
+ }
+ result = message->get();
+ }
+ }
+ return result;
}
vector<const tl_object_ptr<telegram_api::Message> *> UpdatesManager::get_new_messages(
const telegram_api::Updates *updates_ptr) {
vector<const tl_object_ptr<telegram_api::Message> *> messages;
- const vector<tl_object_ptr<telegram_api::Update>> *updates;
- switch (updates_ptr->get_id()) {
+ auto updates = get_updates(updates_ptr);
+ if (updates != nullptr) {
+ for (auto &update : *updates) {
+ auto constructor_id = update->get_id();
+ if (constructor_id == telegram_api::updateNewMessage::ID) {
+ messages.emplace_back(&static_cast<const telegram_api::updateNewMessage *>(update.get())->message_);
+ } else if (constructor_id == telegram_api::updateNewChannelMessage::ID) {
+ messages.emplace_back(&static_cast<const telegram_api::updateNewChannelMessage *>(update.get())->message_);
+ } else if (constructor_id == telegram_api::updateNewScheduledMessage::ID) {
+ messages.emplace_back(&static_cast<const telegram_api::updateNewScheduledMessage *>(update.get())->message_);
+ }
+ }
+ }
+ return messages;
+}
+
+vector<InputGroupCallId> UpdatesManager::get_update_new_group_call_ids(const telegram_api::Updates *updates_ptr) {
+ vector<InputGroupCallId> input_group_call_ids;
+ auto updates = get_updates(updates_ptr);
+ if (updates != nullptr) {
+ for (auto &update : *updates) {
+ InputGroupCallId input_group_call_id;
+ if (update->get_id() == telegram_api::updateGroupCall::ID) {
+ auto group_call_ptr = static_cast<const telegram_api::updateGroupCall *>(update.get())->call_.get();
+ if (group_call_ptr->get_id() == telegram_api::groupCall::ID) {
+ auto group_call = static_cast<const telegram_api::groupCall *>(group_call_ptr);
+ input_group_call_id = InputGroupCallId(group_call->id_, group_call->access_hash_);
+ }
+ }
+
+ if (input_group_call_id.is_valid()) {
+ input_group_call_ids.push_back(input_group_call_id);
+ }
+ }
+ }
+ return input_group_call_ids;
+}
+
+string UpdatesManager::extract_join_group_call_presentation_params(telegram_api::Updates *updates_ptr) {
+ auto updates = get_updates(updates_ptr);
+ for (auto it = updates->begin(); it != updates->end(); ++it) {
+ auto *update = it->get();
+ if (update->get_id() == telegram_api::updateGroupCallConnection::ID &&
+ static_cast<const telegram_api::updateGroupCallConnection *>(update)->presentation_) {
+ string result = std::move(static_cast<telegram_api::updateGroupCallConnection *>(update)->params_->data_);
+ updates->erase(it);
+ return result;
+ }
+ }
+ return string();
+}
+
+vector<DialogId> UpdatesManager::get_update_notify_settings_dialog_ids(const telegram_api::Updates *updates_ptr) {
+ vector<DialogId> dialog_ids;
+ auto updates = get_updates(updates_ptr);
+ if (updates != nullptr) {
+ dialog_ids.reserve(updates->size());
+ for (auto &update : *updates) {
+ DialogId dialog_id;
+ if (update->get_id() == telegram_api::updateNotifySettings::ID) {
+ auto notify_peer = static_cast<const telegram_api::updateNotifySettings *>(update.get())->peer_.get();
+ if (notify_peer->get_id() == telegram_api::notifyPeer::ID) {
+ dialog_id = DialogId(static_cast<const telegram_api::notifyPeer *>(notify_peer)->peer_);
+ }
+ }
+
+ if (dialog_id.is_valid()) {
+ dialog_ids.push_back(dialog_id);
+ } else {
+ LOG(ERROR) << "Receive unexpected " << to_string(update);
+ }
+ }
+ }
+ return dialog_ids;
+}
+
+vector<int32> UpdatesManager::get_update_ids(const telegram_api::Updates *updates_ptr) {
+ const vector<tl_object_ptr<telegram_api::Update>> *updates = nullptr;
+ auto updates_type = updates_ptr->get_id();
+ switch (updates_type) {
case telegram_api::updatesTooLong::ID:
case telegram_api::updateShortMessage::ID:
case telegram_api::updateShortChatMessage::ID:
- case telegram_api::updateShort::ID:
case telegram_api::updateShortSentMessage::ID:
- LOG(ERROR) << "Receive " << oneline(to_string(*updates_ptr)) << " instead of updates";
- return messages;
- case telegram_api::updatesCombined::ID: {
+ return {updates_type};
+ case telegram_api::updateShort::ID:
+ return {static_cast<const telegram_api::updateShort *>(updates_ptr)->update_->get_id()};
+ case telegram_api::updatesCombined::ID:
updates = &static_cast<const telegram_api::updatesCombined *>(updates_ptr)->updates_;
break;
- }
- case telegram_api::updates::ID: {
+ case telegram_api::updates::ID:
updates = &static_cast<const telegram_api::updates *>(updates_ptr)->updates_;
break;
- }
default:
UNREACHABLE();
- return messages;
}
- for (auto &update : *updates) {
- auto constructor_id = update->get_id();
- if (constructor_id == telegram_api::updateNewMessage::ID) {
- messages.emplace_back(&static_cast<const telegram_api::updateNewMessage *>(update.get())->message_);
- } else if (constructor_id == telegram_api::updateNewChannelMessage::ID) {
- messages.emplace_back(&static_cast<const telegram_api::updateNewChannelMessage *>(update.get())->message_);
- }
- }
- return messages;
+
+ return transform(*updates, [](const tl_object_ptr<telegram_api::Update> &update) { return update->get_id(); });
}
-vector<DialogId> UpdatesManager::get_chats(const telegram_api::Updates *updates_ptr) {
+vector<DialogId> UpdatesManager::get_chat_dialog_ids(const telegram_api::Updates *updates_ptr) {
const vector<tl_object_ptr<telegram_api::Chat>> *chats = nullptr;
switch (updates_ptr->get_id()) {
case telegram_api::updatesTooLong::ID:
@@ -847,14 +1307,12 @@ vector<DialogId> UpdatesManager::get_chats(const telegram_api::Updates *updates_
case telegram_api::updateShortSentMessage::ID:
LOG(ERROR) << "Receive " << oneline(to_string(*updates_ptr)) << " instead of updates";
break;
- case telegram_api::updatesCombined::ID: {
+ case telegram_api::updatesCombined::ID:
chats = &static_cast<const telegram_api::updatesCombined *>(updates_ptr)->chats_;
break;
- }
- case telegram_api::updates::ID: {
+ case telegram_api::updates::ID:
chats = &static_cast<const telegram_api::updates *>(updates_ptr)->chats_;
break;
- }
default:
UNREACHABLE();
}
@@ -866,53 +1324,123 @@ vector<DialogId> UpdatesManager::get_chats(const telegram_api::Updates *updates_
vector<DialogId> dialog_ids;
dialog_ids.reserve(chats->size());
for (const auto &chat : *chats) {
- auto chat_id = ContactsManager::get_chat_id(chat);
- if (chat_id.is_valid()) {
- dialog_ids.push_back(DialogId(chat_id));
- continue;
+ auto dialog_id = ContactsManager::get_dialog_id(chat);
+ if (dialog_id.is_valid()) {
+ dialog_ids.push_back(dialog_id);
+ } else {
+ LOG(ERROR) << "Can't find identifier of " << oneline(to_string(chat));
}
+ }
+ return dialog_ids;
+}
- auto channel_id = ContactsManager::get_channel_id(chat);
- if (channel_id.is_valid()) {
- dialog_ids.push_back(DialogId(channel_id));
- continue;
+int32 UpdatesManager::get_update_edit_message_pts(const telegram_api::Updates *updates_ptr,
+ FullMessageId full_message_id) {
+ int32 pts = 0;
+ auto updates = get_updates(updates_ptr);
+ if (updates != nullptr) {
+ for (auto &update_ptr : *updates) {
+ int32 update_pts = [&] {
+ switch (update_ptr->get_id()) {
+ case telegram_api::updateEditMessage::ID: {
+ auto update = static_cast<const telegram_api::updateEditMessage *>(update_ptr.get());
+ if (MessagesManager::get_full_message_id(update->message_, false) == full_message_id) {
+ return update->pts_;
+ }
+ return 0;
+ }
+ case telegram_api::updateEditChannelMessage::ID: {
+ auto update = static_cast<const telegram_api::updateEditChannelMessage *>(update_ptr.get());
+ if (MessagesManager::get_full_message_id(update->message_, false) == full_message_id) {
+ return update->pts_;
+ }
+ return 0;
+ }
+ case telegram_api::updateNewScheduledMessage::ID: {
+ auto update = static_cast<const telegram_api::updateNewScheduledMessage *>(update_ptr.get());
+ auto new_full_message_id = MessagesManager::get_full_message_id(update->message_, true);
+ if (new_full_message_id.get_dialog_id() == full_message_id.get_dialog_id()) {
+ auto new_message_id = new_full_message_id.get_message_id();
+ auto old_message_id = full_message_id.get_message_id();
+ if (new_message_id.is_valid_scheduled() && new_message_id.is_scheduled_server() &&
+ old_message_id.is_valid_scheduled() && old_message_id.is_scheduled_server() &&
+ old_message_id.get_scheduled_server_message_id() ==
+ new_message_id.get_scheduled_server_message_id()) {
+ return -2;
+ }
+ }
+ return 0;
+ }
+ default:
+ return 0;
+ }
+ }();
+ if (update_pts != 0) {
+ if (pts == 0) {
+ pts = update_pts;
+ } else {
+ pts = -1;
+ }
+ }
}
-
- LOG(ERROR) << "Can't find id of " << oneline(to_string(chat));
}
- return dialog_ids;
+ if (pts == -1) {
+ LOG(ERROR) << "Receive multiple edit message updates in " << to_string(*updates_ptr);
+ pts = 0;
+ } else if (pts == 0) {
+ LOG(ERROR) << "Receive no edit message updates for " << full_message_id << " in " << to_string(*updates_ptr);
+ }
+ return pts;
}
void UpdatesManager::init_state() {
- if (!td_->auth_manager_->is_authorized()) {
+ if (G()->close_flag() || !td_->auth_manager_->is_authorized()) {
return;
}
auto pmc = G()->td_db()->get_binlog_pmc();
+ if (G()->ignore_background_updates()) {
+ // just in case
+ pmc->erase("updates.pts");
+ pmc->erase("updates.qts");
+ pmc->erase("updates.date");
+ }
string pts_str = pmc->get("updates.pts");
if (pts_str.empty()) {
if (!running_get_difference_) {
running_get_difference_ = true;
- send_closure(G()->state_manager(), &StateManager::on_synchronized, false);
- td_->create_handler<GetUpdatesStateQuery>()->send();
- set_state(State::Type::RunningGetUpdatesState);
+ before_get_difference(true);
+
+ auto promise = PromiseCreator::lambda([](Result<tl_object_ptr<telegram_api::updates_state>> result) {
+ if (result.is_ok()) {
+ send_closure(G()->updates_manager(), &UpdatesManager::on_get_updates_state, result.move_as_ok(),
+ "GetUpdatesStateQuery");
+ } else {
+ send_closure(G()->updates_manager(), &UpdatesManager::on_failed_get_updates_state, result.move_as_error());
+ }
+ });
+ td_->create_handler<GetUpdatesStateQuery>(std::move(promise))->send();
}
return;
}
pts_manager_.init(to_integer<int32>(pts_str));
last_get_difference_pts_ = get_pts();
- qts_ = to_integer<int32>(pmc->get("updates.qts"));
+ qts_manager_.init(to_integer<int32>(pmc->get("updates.qts")));
+ last_get_difference_qts_ = get_qts();
date_ = to_integer<int32>(pmc->get("updates.date"));
date_source_ = "database";
- LOG(DEBUG) << "Init: " << get_pts() << " " << qts_ << " " << date_;
- send_closure(td_->secret_chats_manager_, &SecretChatsManager::init_qts, qts_);
+ LOG(DEBUG) << "Init: " << get_pts() << " " << get_qts() << " " << date_;
get_difference("init_state");
}
void UpdatesManager::ping_server() {
- td_->create_handler<PingServerQuery>()->send();
+ auto promise = PromiseCreator::lambda([](Result<tl_object_ptr<telegram_api::updates_state>> result) {
+ auto state = result.is_ok() ? result.move_as_ok() : nullptr;
+ send_closure(G()->updates_manager(), &UpdatesManager::on_server_pong, std::move(state));
+ });
+ td_->create_handler<PingServerQuery>(std::move(promise))->send();
}
void UpdatesManager::on_server_pong(tl_object_ptr<telegram_api::updates_state> &&state) {
@@ -924,27 +1452,52 @@ void UpdatesManager::on_server_pong(tl_object_ptr<telegram_api::updates_state> &
void UpdatesManager::process_get_difference_updates(
vector<tl_object_ptr<telegram_api::Message>> &&new_messages,
- vector<tl_object_ptr<telegram_api::EncryptedMessage>> &&new_encrypted_messages, int32 qts,
+ vector<tl_object_ptr<telegram_api::EncryptedMessage>> &&new_encrypted_messages,
vector<tl_object_ptr<telegram_api::Update>> &&other_updates) {
- LOG(INFO) << "In get difference receive " << new_messages.size() << " messages, " << new_encrypted_messages.size()
- << " encrypted messages and " << other_updates.size() << " other updates";
+ VLOG(get_difference) << "In get difference receive " << new_messages.size() << " messages, "
+ << new_encrypted_messages.size() << " encrypted messages and " << other_updates.size()
+ << " other updates";
for (auto &update : other_updates) {
auto constructor_id = update->get_id();
if (constructor_id == telegram_api::updateMessageID::ID) {
- on_update(move_tl_object_as<telegram_api::updateMessageID>(update), true);
+ // in getDifference updateMessageID can't be received for scheduled messages
+ LOG(INFO) << "Receive update about sent message " << to_string(update);
+ auto update_message_id = move_tl_object_as<telegram_api::updateMessageID>(update);
+ td_->messages_manager_->on_update_message_id(update_message_id->random_id_,
+ MessageId(ServerMessageId(update_message_id->id_)), "getDifference");
CHECK(!running_get_difference_);
}
if (constructor_id == telegram_api::updateEncryption::ID) {
- on_update(move_tl_object_as<telegram_api::updateEncryption>(update), true);
+ on_update(move_tl_object_as<telegram_api::updateEncryption>(update), Promise<Unit>());
+ CHECK(!running_get_difference_);
+ }
+
+ if (constructor_id == telegram_api::updateFolderPeers::ID) {
+ auto update_folder_peers = move_tl_object_as<telegram_api::updateFolderPeers>(update);
+ if (update_folder_peers->pts_count_ != 0) {
+ LOG(ERROR) << "Receive updateFolderPeers with pts_count = " << update_folder_peers->pts_count_;
+ update_folder_peers->pts_count_ = 0;
+ }
+ update_folder_peers->pts_ = 0;
+ on_update(std::move(update_folder_peers), Promise<Unit>());
CHECK(!running_get_difference_);
}
+ if (constructor_id == telegram_api::updateChat::ID) {
+ update = nullptr;
+ }
+
+ if (constructor_id == telegram_api::updateChannel::ID) {
+ update = nullptr;
+ }
+
/*
// TODO can't apply it here, because dialog may not be created yet
// process updateReadHistoryInbox before new messages
if (constructor_id == telegram_api::updateReadHistoryInbox::ID) {
- on_update(move_tl_object_as<telegram_api::updateReadHistoryInbox>(update), true);
+ static_cast<telegram_api::updateReadHistoryInbox *>(update.get())->still_unread_count_ = -1;
+ process_pts_update(std::move(update));
CHECK(!running_get_difference_);
}
*/
@@ -952,45 +1505,68 @@ void UpdatesManager::process_get_difference_updates(
for (auto &message : new_messages) {
// channel messages must not be received in this vector
- td_->messages_manager_->on_get_message(std::move(message), true, false, true, true, "get difference");
+ td_->messages_manager_->on_get_message(std::move(message), true, false, false, true, true, "get difference");
CHECK(!running_get_difference_);
}
for (auto &encrypted_message : new_encrypted_messages) {
- on_update(make_tl_object<telegram_api::updateNewEncryptedMessage>(std::move(encrypted_message), 0), true);
+ send_closure(td_->secret_chats_manager_, &SecretChatsManager::on_new_message, std::move(encrypted_message),
+ Promise<Unit>());
}
- send_closure(td_->secret_chats_manager_, &SecretChatsManager::update_qts, qts);
- process_updates(std::move(other_updates), true);
+ process_updates(std::move(other_updates), true, Promise<Unit>());
}
void UpdatesManager::on_get_difference(tl_object_ptr<telegram_api::updates_Difference> &&difference_ptr) {
- LOG(INFO) << "----- END GET DIFFERENCE-----";
+ VLOG(get_difference) << "----- END GET DIFFERENCE-----";
running_get_difference_ = false;
- if (difference_ptr == nullptr) {
- on_failed_get_difference();
+ if (!td_->auth_manager_->is_authorized()) {
+ // just in case
return;
}
LOG(DEBUG) << "Result of get difference: " << to_string(difference_ptr);
+ CHECK(difference_ptr != nullptr);
switch (difference_ptr->get_id()) {
case telegram_api::updates_differenceEmpty::ID: {
auto difference = move_tl_object_as<telegram_api::updates_differenceEmpty>(difference_ptr);
set_date(difference->date_, false, "on_get_difference_empty");
seq_ = difference->seq_;
+
+ process_pending_qts_updates();
+ if (!pending_qts_updates_.empty()) {
+ LOG(WARNING) << "Drop " << pending_qts_updates_.size() << " pending qts updates after receive empty difference";
+ auto pending_qts_updates = std::move(pending_qts_updates_);
+ pending_qts_updates_.clear();
+
+ for (auto &pending_update : pending_qts_updates) {
+ set_promises(pending_update.second.promises);
+ }
+ }
+
+ process_pending_seq_updates();
+ if (!pending_seq_updates_.empty()) {
+ LOG(WARNING) << "Drop " << pending_seq_updates_.size() << " pending seq updates after receive empty difference";
+ auto pending_seq_updates = std::move(pending_seq_updates_);
+ pending_seq_updates_.clear();
+
+ for (auto &pending_update : pending_seq_updates) {
+ pending_update.second.promise.set_value(Unit());
+ }
+ }
break;
}
case telegram_api::updates_difference::ID: {
auto difference = move_tl_object_as<telegram_api::updates_difference>(difference_ptr);
- td_->contacts_manager_->on_get_users(std::move(difference->users_));
- td_->contacts_manager_->on_get_chats(std::move(difference->chats_));
-
- set_state(State::Type::ApplyingDifference);
+ VLOG(get_difference) << "In get difference receive " << difference->users_.size() << " users and "
+ << difference->chats_.size() << " chats";
+ td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.difference");
+ td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.difference");
process_get_difference_updates(std::move(difference->new_messages_),
- std::move(difference->new_encrypted_messages_), difference->state_->qts_,
+ std::move(difference->new_encrypted_messages_),
std::move(difference->other_updates_));
if (running_get_difference_) {
LOG(ERROR) << "Get difference has run while processing get difference updates";
@@ -1002,25 +1578,56 @@ void UpdatesManager::on_get_difference(tl_object_ptr<telegram_api::updates_Diffe
}
case telegram_api::updates_differenceSlice::ID: {
auto difference = move_tl_object_as<telegram_api::updates_differenceSlice>(difference_ptr);
- td_->contacts_manager_->on_get_users(std::move(difference->users_));
- td_->contacts_manager_->on_get_chats(std::move(difference->chats_));
+ bool is_pts_changed = have_update_pts_changed(difference->other_updates_);
+ if (difference->intermediate_state_->pts_ >= get_pts() && get_pts() != std::numeric_limits<int32>::max() &&
+ difference->intermediate_state_->date_ >= date_ && difference->intermediate_state_->qts_ == get_qts() &&
+ !is_pts_changed) {
+ // TODO send new getDifference request and apply difference slice only after that
+ }
- set_state(State::Type::ApplyingDifferenceSlice);
+ VLOG(get_difference) << "In get difference receive " << difference->users_.size() << " users and "
+ << difference->chats_.size() << " chats";
+ td_->contacts_manager_->on_get_users(std::move(difference->users_), "updates.differenceSlice");
+ td_->contacts_manager_->on_get_chats(std::move(difference->chats_), "updates.differenceSlice");
process_get_difference_updates(std::move(difference->new_messages_),
std::move(difference->new_encrypted_messages_),
- difference->intermediate_state_->qts_, std::move(difference->other_updates_));
+ std::move(difference->other_updates_));
if (running_get_difference_) {
- LOG(ERROR) << "Get difference has run while processing get difference updates";
+ if (!is_pts_changed) {
+ LOG(ERROR) << "Get difference has run while processing get difference updates";
+ }
break;
}
+ CHECK(!is_pts_changed);
+ auto old_pts = get_pts();
+ auto old_date = get_date();
+ auto old_qts = get_qts();
on_get_updates_state(std::move(difference->intermediate_state_), "get difference slice");
- get_difference("on updates_differenceSlice");
+
+ process_postponed_pts_updates();
+ process_pending_qts_updates();
+
+ auto new_pts = get_pts();
+ auto new_date = get_date();
+ auto new_qts = get_qts();
+ if (old_pts != std::numeric_limits<int32>::max() && new_date == old_date &&
+ (new_pts == old_pts || (min_postponed_update_pts_ != 0 && new_pts >= min_postponed_update_pts_)) &&
+ (new_qts == old_qts || (min_postponed_update_qts_ != 0 && new_qts >= min_postponed_update_qts_))) {
+ VLOG(get_difference) << "Switch back from getDifference to update processing";
+ break;
+ }
+
+ if (new_pts != -1) { // just in case
+ run_get_difference(true, "on updates_differenceSlice");
+ }
break;
}
case telegram_api::updates_differenceTooLong::ID: {
- LOG(ERROR) << "Receive differenceTooLong";
+ if (td_->option_manager_->get_option_integer("session_count") <= 1) {
+ LOG(ERROR) << "Receive differenceTooLong";
+ }
// TODO
auto difference = move_tl_object_as<telegram_api::updates_differenceTooLong>(difference_ptr);
set_pts(difference->pts_, "differenceTooLong").set_value(Unit());
@@ -1038,54 +1645,192 @@ void UpdatesManager::on_get_difference(tl_object_ptr<telegram_api::updates_Diffe
void UpdatesManager::after_get_difference() {
CHECK(!running_get_difference_);
- send_closure(td_->secret_chats_manager_, &SecretChatsManager::after_get_difference);
- auto saved_state = state_;
retry_timeout_.cancel_timeout();
retry_time_ = 1;
- process_pending_seq_updates(); // cancels seq_gap_timeout_, may apply some updates coming before getDifference, but
- // not returned in getDifference
+ // cancels qts_gap_timeout_ if needed, can apply some updates received during getDifference,
+ // but missed in getDifference
+ process_pending_qts_updates();
+
+ // cancels seq_gap_timeout_ if needed, can apply some updates received during getDifference,
+ // but missed in getDifference
+ process_pending_seq_updates();
+
if (running_get_difference_) {
return;
}
- if (postponed_updates_.size()) {
- LOG(INFO) << "Begin to apply postponed updates";
+ if (!postponed_updates_.empty()) {
+ auto begin_time = Time::now();
+ auto chunk_count = postponed_updates_.size();
+ VLOG(get_difference) << "Begin to apply " << chunk_count << " postponed update chunks";
+ size_t total_update_count = 0;
while (!postponed_updates_.empty()) {
auto it = postponed_updates_.begin();
auto updates = std::move(it->second.updates);
auto updates_seq_begin = it->second.seq_begin;
auto updates_seq_end = it->second.seq_end;
+ auto receive_time = it->second.receive_time;
+ auto promise = std::move(it->second.promise);
// ignore it->second.date, because it may be too old
postponed_updates_.erase(it);
- on_pending_updates(std::move(updates), updates_seq_begin, updates_seq_end, 0, "postponed updates");
+ auto update_count = updates.size();
+ on_pending_updates(std::move(updates), updates_seq_begin, updates_seq_end, 0, receive_time, std::move(promise),
+ "postponed updates");
if (running_get_difference_) {
- LOG(INFO) << "Finish to apply postponed updates because forced to run getDifference";
+ VLOG(get_difference) << "Finish to apply postponed updates with " << postponed_updates_.size()
+ << " updates left after applied " << total_update_count
+ << " updates, because forced to run getDifference";
return;
}
+ total_update_count += update_count;
+ }
+ VLOG(get_difference) << "Finished to apply " << total_update_count << " postponed updates";
+ auto passed_time = Time::now() - begin_time;
+ if (passed_time >= UPDATE_APPLY_WARNING_TIME) {
+ LOG(WARNING) << "Applied " << total_update_count << " postponed for "
+ << (Time::now() - get_difference_start_time_) << " updates in " << chunk_count << " chunks in "
+ << passed_time;
}
- LOG(INFO) << "Finish to apply postponed updates";
}
- state_ = saved_state;
+ if (!postponed_pts_updates_.empty()) { // must be before td_->messages_manager_->after_get_difference()
+ auto postponed_updates = std::move(postponed_pts_updates_);
+ postponed_pts_updates_.clear();
+
+ auto begin_time = Time::now();
+ auto update_count = postponed_updates.size();
+ VLOG(get_difference) << "Begin to apply " << postponed_updates.size()
+ << " postponed pts updates with pts = " << get_pts();
+ for (auto &postponed_update : postponed_updates) {
+ auto &update = postponed_update.second;
+ add_pending_pts_update(std::move(update.update), update.pts, update.pts_count, update.receive_time,
+ std::move(update.promise), AFTER_GET_DIFFERENCE_SOURCE);
+ CHECK(!running_get_difference_);
+ }
+ VLOG(get_difference) << "After applying postponed pts updates have pts = " << get_pts()
+ << ", max_pts = " << accumulated_pts_ << " and " << pending_pts_updates_.size() << " + "
+ << postponed_pts_updates_.size() << " pending pts updates";
+ auto passed_time = Time::now() - begin_time;
+ if (passed_time >= UPDATE_APPLY_WARNING_TIME) {
+ LOG(WARNING) << "Applied " << update_count << " postponed for " << (Time::now() - get_difference_start_time_)
+ << " pts updates in " << passed_time;
+ }
+ }
+ send_closure(td_->download_manager_actor_, &DownloadManager::after_get_difference);
td_->inline_queries_manager_->after_get_difference();
td_->messages_manager_->after_get_difference();
+ send_closure_later(td_->notification_manager_actor_, &NotificationManager::after_get_difference);
send_closure(G()->state_manager(), &StateManager::on_synchronized, true);
+ get_difference_start_time_ = 0.0;
+
+ try_reload_data();
+}
- set_state(State::Type::General);
+void UpdatesManager::schedule_data_reload() {
+ if (data_reload_timeout_.has_timeout()) {
+ return;
+ }
+
+ auto timeout = next_data_reload_time_ - Time::now();
+ LOG(INFO) << "Schedule data reload in " << timeout;
+ data_reload_timeout_.set_callback(std::move(try_reload_data_static));
+ data_reload_timeout_.set_callback_data(static_cast<void *>(td_));
+ data_reload_timeout_.set_timeout_in(timeout);
+}
+
+void UpdatesManager::try_reload_data_static(void *td) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ CHECK(td != nullptr);
+ static_cast<Td *>(td)->updates_manager_->try_reload_data();
+}
+
+void UpdatesManager::try_reload_data() {
+ if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot() || running_get_difference_ ||
+ !td_->is_online()) {
+ return;
+ }
+
+ auto now = Time::now();
+ if (now < next_data_reload_time_) {
+ schedule_data_reload();
+ return;
+ }
+ next_data_reload_time_ = now + Random::fast(3000, 4200);
+
+ LOG(INFO) << "Reload data";
+ td_->animations_manager_->get_saved_animations(Auto());
+ td_->contacts_manager_->reload_created_public_dialogs(PublicDialogType::HasUsername, Auto());
+ td_->contacts_manager_->reload_created_public_dialogs(PublicDialogType::IsLocationBased, Auto());
+ get_default_emoji_statuses(td_, Auto());
+ td_->notification_settings_manager_->reload_saved_ringtones(Auto());
+ td_->notification_settings_manager_->send_get_scope_notification_settings_query(NotificationSettingsScope::Private,
+ Auto());
+ td_->notification_settings_manager_->send_get_scope_notification_settings_query(NotificationSettingsScope::Group,
+ Auto());
+ td_->notification_settings_manager_->send_get_scope_notification_settings_query(NotificationSettingsScope::Channel,
+ Auto());
+ td_->stickers_manager_->reload_reactions();
+ td_->stickers_manager_->reload_recent_reactions();
+ td_->stickers_manager_->reload_top_reactions();
+ for (int32 type = 0; type < MAX_STICKER_TYPE; type++) {
+ auto sticker_type = static_cast<StickerType>(type);
+ td_->stickers_manager_->get_installed_sticker_sets(sticker_type, Auto());
+ td_->stickers_manager_->get_featured_sticker_sets(sticker_type, 0, 1000, Auto());
+ }
+ td_->stickers_manager_->get_recent_stickers(false, Auto());
+ td_->stickers_manager_->get_recent_stickers(true, Auto());
+ td_->stickers_manager_->get_favorite_stickers(Auto());
+ td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::animated_emoji());
+ td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::animated_emoji_click());
+ td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::premium_gifts());
+ td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::generic_animations());
+ td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::default_statuses());
+ td_->stickers_manager_->reload_special_sticker_set_by_type(SpecialStickerSetType::default_topic_icons());
+
+ schedule_data_reload();
+}
+
+void UpdatesManager::subscribe_to_transcribed_audio_updates(int64 transcription_id, TranscribedAudioHandler on_update) {
+ if (pending_audio_transcriptions_.count(transcription_id) != 0) {
+ on_pending_audio_transcription_failed(transcription_id,
+ Status::Error(500, "Receive duplicate speech recognition identifier"));
+ }
+ bool is_inserted = pending_audio_transcriptions_.emplace(transcription_id, std::move(on_update)).second;
+ CHECK(is_inserted);
+ pending_audio_transcription_timeout_.set_timeout_in(transcription_id, AUDIO_TRANSCRIPTION_TIMEOUT);
+}
+
+void UpdatesManager::on_pending_audio_transcription_failed(int64 transcription_id, Status &&error) {
+ if (G()->close_flag()) {
+ return;
+ }
+ auto it = pending_audio_transcriptions_.find(transcription_id);
+ if (it == pending_audio_transcriptions_.end()) {
+ return;
+ }
+ auto on_update = std::move(it->second);
+ pending_audio_transcriptions_.erase(it);
+ pending_audio_transcription_timeout_.cancel_timeout(transcription_id);
+
+ on_update(std::move(error));
}
void UpdatesManager::on_pending_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, int32 seq_begin,
- int32 seq_end, int32 date, const char *source) {
+ int32 seq_end, int32 date, double receive_time, Promise<Unit> &&promise,
+ const char *source) {
if (get_pts() == -1) {
init_state();
}
if (!td_->auth_manager_->is_authorized()) {
LOG(INFO) << "Ignore updates received before authorization or after logout";
- return;
+ return promise.set_value(Unit());
}
// for (auto &update : updates) {
@@ -1096,53 +1841,152 @@ void UpdatesManager::on_pending_updates(vector<tl_object_ptr<telegram_api::Updat
LOG(ERROR) << "Wrong updates parameters seq_begin = " << seq_begin << ", seq_end = " << seq_end
<< ", date = " << date << " from " << source;
get_difference("on wrong updates in on_pending_updates");
- return;
+ return promise.set_value(Unit());
}
- if (running_get_difference_) {
- LOG(INFO) << "Postpone " << updates.size() << " updates [" << seq_begin << ", " << seq_end
- << "] with date = " << date << " from " << source;
- postponed_updates_.emplace(seq_begin, PendingUpdates(seq_begin, seq_end, date, std::move(updates)));
- return;
+ for (auto &update : updates) {
+ if (update != nullptr) {
+ switch (update->get_id()) {
+ case telegram_api::updateUserTyping::ID:
+ case telegram_api::updateChatUserTyping::ID:
+ case telegram_api::updateChannelUserTyping::ID:
+ case telegram_api::updateEncryptedChatTyping::ID:
+ case telegram_api::updateLoginToken::ID:
+ case telegram_api::updateDcOptions::ID:
+ case telegram_api::updateConfig::ID:
+ case telegram_api::updateServiceNotification::ID:
+ case telegram_api::updateLangPackTooLong::ID:
+ case telegram_api::updateLangPack::ID:
+ short_update_date_ = date == 0 ? G()->unix_time() : date;
+ LOG(INFO) << "Process short " << oneline(to_string(update));
+ // don't need promise for short update
+ downcast_call(*update, OnUpdate(this, update, Promise<Unit>()));
+ short_update_date_ = 0;
+ update = nullptr;
+ break;
+ default:
+ break;
+ }
+ }
}
- // TODO typings must be processed before NewMessage
+ bool need_postpone = running_get_difference_ /*|| string(source) != string("postponed updates")*/;
+ if (!need_postpone) {
+ for (auto &update : updates) {
+ if (!is_acceptable_update(update.get())) {
+ CHECK(update != nullptr);
+ int32 id = update->get_id();
+ const tl_object_ptr<telegram_api::Message> *message_ptr = nullptr;
+ int32 pts = 0;
+ if (id == telegram_api::updateNewChannelMessage::ID) {
+ auto update_new_channel_message = static_cast<const telegram_api::updateNewChannelMessage *>(update.get());
+ message_ptr = &update_new_channel_message->message_;
+ pts = update_new_channel_message->pts_;
+ }
+ if (id == telegram_api::updateEditChannelMessage::ID) {
+ auto update_edit_channel_message = static_cast<const telegram_api::updateEditChannelMessage *>(update.get());
+ message_ptr = &update_edit_channel_message->message_;
+ pts = update_edit_channel_message->pts_;
+ }
+
+ // for channels we can try to replace unacceptable update with updateChannelTooLong
+ if (message_ptr != nullptr) {
+ auto dialog_id = td_->messages_manager_->get_message_dialog_id(*message_ptr);
+ if (dialog_id.get_type() == DialogType::Channel) {
+ auto channel_id = dialog_id.get_channel_id();
+ if (td_->contacts_manager_->have_channel_force(channel_id)) {
+ if (td_->messages_manager_->is_old_channel_update(dialog_id, pts)) {
+ // the update will be ignored anyway, so there is no reason to replace it or force get_difference
+ LOG(INFO) << "Allow an outdated unacceptable update from " << source;
+ continue;
+ }
+ if ((*message_ptr)->get_id() != telegram_api::messageService::ID) {
+ // don't replace service messages, because they can be about bot's kicking
+ LOG(INFO) << "Replace update about new message with updateChannelTooLong in " << dialog_id;
+ update = telegram_api::make_object<telegram_api::updateChannelTooLong>(
+ telegram_api::updateChannelTooLong::PTS_MASK, channel_id.get(), pts);
+ continue;
+ }
+ }
+ } else {
+ LOG(ERROR) << "Update is not from a channel: " << to_string(update);
+ }
+ }
- size_t processed_updates = 0;
+ get_difference("on unacceptable updates in on_pending_updates");
+ return promise.set_value(Unit());
+ }
+ }
+ }
+ if (date > 0 && updates.size() == 1 && updates[0] != nullptr &&
+ updates[0]->get_id() == telegram_api::updateReadHistoryOutbox::ID) {
+ auto update = static_cast<const telegram_api::updateReadHistoryOutbox *>(updates[0].get());
+ DialogId dialog_id(update->peer_);
+ if (dialog_id.get_type() == DialogType::User) {
+ auto user_id = dialog_id.get_user_id();
+ if (user_id.is_valid()) {
+ td_->contacts_manager_->on_update_user_local_was_online(user_id, date);
+ }
+ }
+ }
+
+ size_t ordinary_new_message_count = 0;
+ size_t scheduled_new_message_count = 0;
+ size_t update_message_id_count = 0;
+ size_t update_count = 0;
for (auto &update : updates) {
- if (!is_acceptable_update(update.get())) {
- CHECK(update != nullptr);
- int32 id = update->get_id();
- const tl_object_ptr<telegram_api::Message> *message_ptr = nullptr;
- int32 pts = 0;
- if (id == telegram_api::updateNewChannelMessage::ID) {
- auto update_new_channel_message = static_cast<const telegram_api::updateNewChannelMessage *>(update.get());
- message_ptr = &update_new_channel_message->message_;
- pts = update_new_channel_message->pts_;
- }
- if (id == telegram_api::updateEditChannelMessage::ID) {
- auto update_edit_channel_message = static_cast<const telegram_api::updateEditChannelMessage *>(update.get());
- message_ptr = &update_edit_channel_message->message_;
- pts = update_edit_channel_message->pts_;
- }
- if (message_ptr != nullptr) {
- auto dialog_id = td_->messages_manager_->get_message_dialog_id(*message_ptr);
- if (dialog_id.get_type() == DialogType::Channel) {
- // for channels we can replace unacceptable update with updateChannelTooLong
- update = telegram_api::make_object<telegram_api::updateChannelTooLong>(
- telegram_api::updateChannelTooLong::PTS_MASK, dialog_id.get_channel_id().get(), pts);
- continue;
- } else {
- LOG(ERROR) << "Update is not from a channel: " << to_string(update);
+ if (update != nullptr) {
+ auto constructor_id = update->get_id();
+ if (constructor_id == telegram_api::updateMessageID::ID) {
+ update_message_id_count++;
+ } else {
+ update_count++;
+ if (constructor_id == telegram_api::updateNewMessage::ID ||
+ constructor_id == telegram_api::updateNewChannelMessage::ID) {
+ ordinary_new_message_count++;
+ } else if (constructor_id == telegram_api::updateNewScheduledMessage::ID) {
+ scheduled_new_message_count++;
}
}
+ }
+ }
- return get_difference("on unacceptable updates in on_pending_updates");
+ if (update_message_id_count != 0 && ordinary_new_message_count != 0 && scheduled_new_message_count != 0) {
+ LOG(ERROR) << "Receive mixed message types in updates:";
+ for (auto &update : updates) {
+ LOG(ERROR) << "Update: " << oneline(to_string(update));
+ }
+ if (!running_get_difference_) {
+ schedule_get_difference("on_get_wrong_updates");
}
+ return promise.set_value(Unit());
}
- set_state(State::Type::ApplyingUpdates);
+ MultiPromiseActorSafe mpas{"OnPendingUpdatesMultiPromiseActor"};
+ Promise<Unit> lock;
+ auto use_mpas = need_postpone || update_count != 1;
+ auto get_promise = [&] {
+ if (use_mpas) {
+ return mpas.get_promise();
+ } else {
+ CHECK(update_count != 0);
+ update_count--;
+ return std::move(promise);
+ }
+ };
+ if (use_mpas) {
+ being_processed_updates_++;
+ mpas.add_promise([actor_id = create_reference(), promise = std::move(promise)](Result<Unit> &&result) mutable {
+ send_closure(actor_id, &UpdatesManager::on_pending_updates_processed, std::move(result), std::move(promise));
+ });
+ lock = get_promise();
+ }
+ SCOPE_EXIT {
+ if (!use_mpas && update_count == 1) {
+ promise.set_value(Unit());
+ }
+ };
for (auto &update : updates) {
if (update != nullptr) {
@@ -1151,84 +1995,206 @@ void UpdatesManager::on_pending_updates(vector<tl_object_ptr<telegram_api::Updat
if (id == telegram_api::updateMessageID::ID) {
LOG(INFO) << "Receive from " << source << " " << to_string(update);
auto sent_message_update = move_tl_object_as<telegram_api::updateMessageID>(update);
- if (!td_->messages_manager_->on_update_message_id(
- sent_message_update->random_id_, MessageId(ServerMessageId(sent_message_update->id_)), source)) {
+ MessageId message_id;
+ if (ordinary_new_message_count != 0) {
+ message_id = MessageId(ServerMessageId(sent_message_update->id_));
+ } else if (scheduled_new_message_count != 0) {
+ message_id = MessageId(ScheduledServerMessageId(sent_message_update->id_), std::numeric_limits<int32>::max());
+ }
+ if (!td_->messages_manager_->on_update_message_id(sent_message_update->random_id_, message_id, source)) {
for (auto &debug_update : updates) {
LOG(ERROR) << "Update: " << oneline(to_string(debug_update));
}
}
- processed_updates++;
update = nullptr;
- CHECK(!running_get_difference_);
}
+ if (id == telegram_api::updateFolderPeers::ID) {
+ on_update(move_tl_object_as<telegram_api::updateFolderPeers>(update), get_promise());
+ update = nullptr;
+ }
+ if (id == telegram_api::updateEncryption::ID) {
+ on_update(move_tl_object_as<telegram_api::updateEncryption>(update), get_promise());
+ update = nullptr;
+ }
+ CHECK(need_postpone || !running_get_difference_);
}
}
for (auto &update : updates) {
if (update != nullptr) {
- int32 id = update->get_id();
- if (id == telegram_api::updateNewMessage::ID || id == telegram_api::updateReadMessagesContents::ID ||
- id == telegram_api::updateEditMessage::ID || id == telegram_api::updateDeleteMessages::ID ||
- id == telegram_api::updateReadHistoryInbox::ID || id == telegram_api::updateReadHistoryOutbox::ID ||
- id == telegram_api::updateWebPage::ID) {
- if (!downcast_call(*update, OnUpdate(this, update, false))) {
- LOG(ERROR) << "Can't call on some update received from " << source;
+ if (is_pts_update(update.get())) {
+ if (running_get_difference_) {
+ auto pts = get_update_pts(update.get());
+ if (pts != 0 && (min_postponed_update_pts_ == 0 || pts < min_postponed_update_pts_)) {
+ min_postponed_update_pts_ = pts;
+ }
+ }
+ downcast_call(*update, OnUpdate(this, update, get_promise()));
+ update = nullptr;
+ } else if (is_qts_update(update.get())) {
+ if (running_get_difference_) {
+ auto qts = get_update_qts(update.get());
+ if (qts != 0 && (min_postponed_update_qts_ == 0 || qts < min_postponed_update_qts_)) {
+ min_postponed_update_qts_ = qts;
+ }
}
- processed_updates++;
+ downcast_call(*update, OnUpdate(this, update, get_promise()));
+ update = nullptr;
+ } else if (is_channel_pts_update(update.get())) {
+ downcast_call(*update, OnUpdate(this, update, get_promise()));
update = nullptr;
}
}
}
- if (running_get_difference_) {
- LOG(INFO) << "Postpone " << updates.size() << " updates [" << seq_begin << ", " << seq_end
- << "] with date = " << date << " from " << source;
- postponed_updates_.emplace(seq_begin, PendingUpdates(seq_begin, seq_end, date, std::move(updates)));
- return;
+ if (seq_begin == 0 && seq_end == 0) {
+ bool have_updates = false;
+ for (auto &update : updates) {
+ if (update != nullptr) {
+ have_updates = true;
+ break;
+ }
+ }
+ if (!have_updates) {
+ LOG(INFO) << "All updates were processed";
+ return lock.set_value(Unit());
+ }
}
- set_state(State::Type::General);
-
- if (processed_updates == updates.size()) {
- if (seq_begin || seq_end) {
- LOG(ERROR) << "All updates from " << source << " was processed but seq = " << seq_
- << ", seq_begin = " << seq_begin << ", seq_end = " << seq_end;
- } else {
- LOG(INFO) << "All updates was processed";
+ if (!use_mpas && update_count == 1) {
+ // still need to process the only update
+ lock = std::move(promise); // now we can use lock as the last promise
+ update_count = 0;
+ }
+ if (need_postpone || running_get_difference_) {
+ LOG(INFO) << "Postpone " << updates.size() << " updates [" << seq_begin << ", " << seq_end
+ << "] with date = " << date << " from " << source;
+ if (!need_postpone) {
+ LOG(ERROR) << "Run get difference while applying updates from " << source;
}
+ postponed_updates_.emplace(
+ seq_begin, PendingSeqUpdates(seq_begin, seq_end, date, receive_time, std::move(updates), std::move(lock)));
return;
}
if (seq_begin == 0 || seq_begin == seq_ + 1) {
LOG(INFO) << "Process " << updates.size() << " updates [" << seq_begin << ", " << seq_end
<< "] with date = " << date << " from " << source;
- process_seq_updates(seq_end, date, std::move(updates));
+ process_seq_updates(seq_end, date, std::move(updates), std::move(lock));
process_pending_seq_updates();
return;
}
if (seq_begin <= seq_) {
+ if (seq_ >= (1 << 30) && seq_begin < seq_ - (1 << 30)) {
+ set_seq_gap_timeout(0.001);
+ }
if (seq_end > seq_) {
- LOG(ERROR) << "Strange updates from " << source << " coming with seq_begin = " << seq_begin
- << ", seq_end = " << seq_end << ", but seq = " << seq_;
+ LOG(ERROR) << "Receive updates with seq_begin = " << seq_begin << ", seq_end = " << seq_end
+ << ", but seq = " << seq_ << " from " << source;
} else {
- LOG(INFO) << "Old updates from " << source << " coming with seq_begin = " << seq_begin
- << ", seq_end = " << seq_end << ", but seq = " << seq_;
+ LOG(INFO) << "Receive old updates with seq_begin = " << seq_begin << ", seq_end = " << seq_end
+ << ", but seq = " << seq_ << " from " << source;
}
- return;
+ return lock.set_value(Unit());
}
LOG(INFO) << "Gap in seq has found. Receive " << updates.size() << " updates [" << seq_begin << ", " << seq_end
<< "] from " << source << ", but seq = " << seq_;
- LOG_IF(WARNING, pending_seq_updates_.find(seq_begin) != pending_seq_updates_.end())
+ LOG_IF(WARNING, pending_seq_updates_.count(seq_begin) > 0)
<< "Already have pending updates with seq = " << seq_begin << ", but receive it again from " << source;
- pending_seq_updates_.emplace(seq_begin, PendingUpdates(seq_begin, seq_end, date, std::move(updates)));
- set_seq_gap_timeout(MAX_UNFILLED_GAP_TIME);
+ pending_seq_updates_.emplace(
+ seq_begin, PendingSeqUpdates(seq_begin, seq_end, date, receive_time, std::move(updates), std::move(lock)));
+ set_seq_gap_timeout(receive_time + MAX_UNFILLED_GAP_TIME - Time::now());
}
-void UpdatesManager::process_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, bool force_apply) {
- tl_object_ptr<telegram_api::updatePtsChanged> update_pts_changed;
+void UpdatesManager::on_pending_updates_processed(Result<Unit> result, Promise<Unit> promise) {
+ being_processed_updates_--;
+ promise.set_result(std::move(result));
+}
+
+void UpdatesManager::add_pending_qts_update(tl_object_ptr<telegram_api::Update> &&update, int32 qts,
+ Promise<Unit> &&promise) {
+ CHECK(update != nullptr);
+ if (qts <= 1) {
+ LOG(ERROR) << "Receive wrong qts " << qts << " in " << oneline(to_string(update));
+ schedule_get_difference("wrong qts");
+ promise.set_value(Unit());
+ return;
+ }
+
+ int32 old_qts = get_qts();
+ LOG(INFO) << "Process update with qts = " << qts << ", current qts = " << old_qts;
+ if (qts < old_qts - 100001) {
+ LOG(WARNING) << "Restore qts after qts overflow from " << old_qts << " to " << qts << " by "
+ << oneline(to_string(update));
+ add_qts(qts - 1).set_value(Unit());
+ CHECK(get_qts() == qts - 1);
+ old_qts = qts - 1;
+ last_get_difference_qts_ = get_qts();
+ }
+
+ if (qts <= old_qts) {
+ LOG(INFO) << "Skip already applied update with qts = " << qts;
+ promise.set_value(Unit());
+ return;
+ }
+
+ if (running_get_difference_ || (qts - 1 > old_qts && old_qts > 0)) {
+ LOG(INFO) << "Postpone update with qts = " << qts;
+ if (!running_get_difference_ && pending_qts_updates_.empty()) {
+ set_qts_gap_timeout(MAX_UNFILLED_GAP_TIME);
+ }
+ auto &pending_update = pending_qts_updates_[qts];
+ if (pending_update.update != nullptr) {
+ LOG(WARNING) << "Receive duplicate update with qts = " << qts;
+ } else {
+ pending_update.receive_time = Time::now();
+ }
+ pending_update.update = std::move(update);
+ pending_update.promises.push_back(std::move(promise));
+ return;
+ }
+
+ process_qts_update(std::move(update), qts, std::move(promise));
+ process_pending_qts_updates();
+}
+
+void UpdatesManager::process_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, bool force_apply,
+ Promise<Unit> &&promise) {
+ int32 update_count = 0;
+ for (auto &update : updates) {
+ if (update != nullptr) {
+ update_count++;
+ }
+ }
+ if (update_count == 0) {
+ return promise.set_value(Unit());
+ }
+
+ MultiPromiseActorSafe mpas{"OnProcessUpdatesMultiPromiseActor"};
+ Promise<Unit> lock;
+ auto use_mpas = update_count != 1;
+ auto get_promise = [&] {
+ if (use_mpas) {
+ return mpas.get_promise();
+ } else {
+ CHECK(update_count != 0);
+ update_count--;
+ return std::move(promise);
+ }
+ };
+ if (use_mpas) {
+ mpas.add_promise(std::move(promise));
+ lock = get_promise();
+ }
+ SCOPE_EXIT {
+ if (!use_mpas && update_count == 1) {
+ promise.set_value(Unit());
+ }
+ };
+
/*
for (auto &update : updates) {
if (update != nullptr) {
@@ -1236,565 +2202,1563 @@ void UpdatesManager::process_updates(vector<tl_object_ptr<telegram_api::Update>>
// process updateReadChannelInbox before updateNewChannelMessage
auto constructor_id = update->get_id();
if (constructor_id == telegram_api::updateReadChannelInbox::ID) {
- on_update(move_tl_object_as<telegram_api::updateReadChannelInbox>(update), force_apply);
+ on_update(move_tl_object_as<telegram_api::updateReadChannelInbox>(update), get_promise());
}
}
}
*/
+
+ tl_object_ptr<telegram_api::updatePtsChanged> update_pts_changed;
for (auto &update : updates) {
if (update != nullptr) {
// process updateNewChannelMessage first
auto constructor_id = update->get_id();
if (constructor_id == telegram_api::updateNewChannelMessage::ID) {
- on_update(move_tl_object_as<telegram_api::updateNewChannelMessage>(update), force_apply);
+ on_update(move_tl_object_as<telegram_api::updateNewChannelMessage>(update), get_promise());
+ continue;
+ }
+
+ // process updateNewScheduledMessage first
+ if (constructor_id == telegram_api::updateNewScheduledMessage::ID) {
+ on_update(move_tl_object_as<telegram_api::updateNewScheduledMessage>(update), get_promise());
+ continue;
+ }
+
+ // updateGroupCallConnection must be processed before updateGroupCall
+ if (constructor_id == telegram_api::updateGroupCallConnection::ID) {
+ on_update(move_tl_object_as<telegram_api::updateGroupCallConnection>(update), get_promise());
+ continue;
}
// updatePtsChanged forces get difference, so process it last
if (constructor_id == telegram_api::updatePtsChanged::ID) {
update_pts_changed = move_tl_object_as<telegram_api::updatePtsChanged>(update);
+ continue;
+ }
+ }
+ }
+ if (force_apply) {
+ for (auto &update : updates) {
+ if (update != nullptr) {
+ if (is_pts_update(update.get())) {
+ auto constructor_id = update->get_id();
+ if (constructor_id == telegram_api::updateWebPage::ID) {
+ auto update_web_page = move_tl_object_as<telegram_api::updateWebPage>(update);
+ td_->web_pages_manager_->on_get_web_page(std::move(update_web_page->webpage_), DialogId());
+ continue;
+ }
+
+ CHECK(constructor_id != telegram_api::updateFolderPeers::ID);
+
+ if (constructor_id == telegram_api::updateReadHistoryInbox::ID) {
+ static_cast<telegram_api::updateReadHistoryInbox *>(update.get())->still_unread_count_ = -1;
+ }
+
+ process_pts_update(std::move(update));
+ } else if (is_qts_update(update.get())) {
+ process_qts_update(std::move(update), 0, get_promise());
+ } else if (update->get_id() == telegram_api::updateChannelTooLong::ID) {
+ td_->messages_manager_->on_update_channel_too_long(
+ move_tl_object_as<telegram_api::updateChannelTooLong>(update), true);
+ }
}
}
}
for (auto &update : updates) {
if (update != nullptr) {
LOG(INFO) << "Process update " << to_string(update);
- if (!downcast_call(*update, OnUpdate(this, update, force_apply))) {
- LOG(ERROR) << "Can't call on some update";
- }
+ downcast_call(*update, OnUpdate(this, update, get_promise()));
CHECK(!running_get_difference_);
}
}
if (update_pts_changed != nullptr) {
- on_update(std::move(update_pts_changed), force_apply);
+ on_update(std::move(update_pts_changed), get_promise());
}
+ lock.set_value(Unit());
}
-void UpdatesManager::process_seq_updates(int32 seq_end, int32 date,
- vector<tl_object_ptr<telegram_api::Update>> &&updates) {
- set_state(State::Type::ApplyingSeqUpdates);
+void UpdatesManager::process_pts_update(tl_object_ptr<telegram_api::Update> &&update) {
+ CHECK(update != nullptr);
- string serialized_updates = PSTRING() << "process_seq_updates [seq_ = " << seq_ << ", seq_end = " << seq_end << "]: ";
- // TODO remove after bugs will be fixed
- for (auto &update : updates) {
- if (update != nullptr) {
- serialized_updates += oneline(to_string(update));
+ // TODO need to save all updates that can change result of running queries not associated with pts (for example
+ // getHistory) and apply the updates to results of the queries
+
+ if (!check_pts_update(update)) {
+ LOG(ERROR) << "Receive wrong pts update: " << oneline(to_string(update));
+ return;
+ }
+
+ // must be called only during getDifference
+ CHECK(pending_pts_updates_.empty());
+ CHECK(accumulated_pts_ == -1);
+
+ td_->messages_manager_->process_pts_update(std::move(update));
+}
+
+void UpdatesManager::add_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts,
+ int32 pts_count, double receive_time, Promise<Unit> &&promise,
+ const char *source) {
+ // do not try to run getDifference from this function
+ CHECK(update != nullptr);
+ CHECK(source != nullptr);
+ LOG(INFO) << "Receive from " << source << " pending " << to_string(update);
+ if (pts_count < 0 || new_pts <= pts_count) {
+ LOG(ERROR) << "Receive update with wrong pts = " << new_pts << " or pts_count = " << pts_count << " from " << source
+ << ": " << oneline(to_string(update));
+ return promise.set_value(Unit());
+ }
+
+ // TODO need to save all updates that can change result of running queries not associated with pts (for example
+ // getHistory) and apply them to result of this queries
+
+ if (!check_pts_update(update)) {
+ LOG(ERROR) << "Receive wrong pts update from " << source << ": " << oneline(to_string(update));
+ return promise.set_value(Unit());
+ }
+
+ if (DROP_PTS_UPDATES) {
+ set_pts_gap_timeout(1.0);
+ return promise.set_value(Unit());
+ }
+
+ int32 old_pts = get_pts();
+ if (new_pts < old_pts - 99 && source != AFTER_GET_DIFFERENCE_SOURCE) {
+ bool need_restore_pts = new_pts < old_pts - 19999;
+ auto now = Time::now();
+ if (now > last_pts_jump_warning_time_ + 1 && (need_restore_pts || now < last_pts_jump_warning_time_ + 5) &&
+ !(old_pts == std::numeric_limits<int32>::max() && running_get_difference_)) {
+ LOG(ERROR) << "Restore pts after delete_first_messages from " << old_pts << " to " << new_pts
+ << " is disabled, pts_count = " << pts_count << ", update is from " << source << ": "
+ << oneline(to_string(update));
+ last_pts_jump_warning_time_ = now;
+ }
+ if (need_restore_pts) {
+ set_pts_gap_timeout(0.001);
+
+ /*
+ LOG(WARNING) << "Restore pts after delete_first_messages";
+ set_pts(new_pts - 1, "restore pts after delete_first_messages");
+ old_pts = get_pts();
+ CHECK(old_pts == new_pts - 1);
+ */
+ }
+ }
+
+ if (new_pts <= old_pts || (old_pts >= 1 && new_pts - (1 << 30) > old_pts)) {
+ td_->messages_manager_->skip_old_pending_pts_update(std::move(update), new_pts, old_pts, pts_count, source);
+ return promise.set_value(Unit());
+ }
+
+ if (running_get_difference_ || !postponed_pts_updates_.empty()) {
+ LOG(INFO) << "Save pending update got while running getDifference from " << source;
+ postpone_pts_update(std::move(update), new_pts, pts_count, receive_time, std::move(promise));
+ return;
+ }
+
+ // is_acceptable_update check was skipped for postponed pts updates
+ if (source == AFTER_GET_DIFFERENCE_SOURCE && !is_acceptable_update(update.get())) {
+ LOG(INFO) << "Postpone again unacceptable pending update";
+ postpone_pts_update(std::move(update), new_pts, pts_count, receive_time, std::move(promise));
+ set_pts_gap_timeout(0.001);
+ return;
+ }
+
+ if (old_pts > new_pts - pts_count) {
+ LOG(WARNING) << "Have old_pts (= " << old_pts << ") + pts_count (= " << pts_count << ") > new_pts (= " << new_pts
+ << "). Logged in " << td_->option_manager_->get_option_integer("authorization_date")
+ << ". Update from " << source << " = " << oneline(to_string(update));
+ postpone_pts_update(std::move(update), new_pts, pts_count, receive_time, std::move(promise));
+ set_pts_gap_timeout(0.001);
+ return;
+ }
+
+ accumulated_pts_count_ += pts_count;
+ if (new_pts > accumulated_pts_) {
+ accumulated_pts_ = new_pts;
+ }
+
+ if (old_pts > accumulated_pts_ - accumulated_pts_count_) {
+ LOG(WARNING) << "Have old_pts (= " << old_pts << ") + accumulated_pts_count (= " << accumulated_pts_count_
+ << ") > accumulated_pts (= " << accumulated_pts_ << "). new_pts = " << new_pts
+ << ", pts_count = " << pts_count << ". Logged in "
+ << td_->option_manager_->get_option_integer("authorization_date") << ". Update from " << source
+ << " = " << oneline(to_string(update));
+ postpone_pts_update(std::move(update), new_pts, pts_count, receive_time, std::move(promise));
+ set_pts_gap_timeout(0.001);
+ return;
+ }
+
+ LOG_IF(INFO, pts_count == 0 && update->get_id() != dummyUpdate::ID) << "Skip useless update " << to_string(update);
+
+ if (pending_pts_updates_.empty() && old_pts == accumulated_pts_ - accumulated_pts_count_ &&
+ !pts_gap_timeout_.has_timeout()) {
+ if (pts_count > 0) {
+ td_->messages_manager_->process_pts_update(std::move(update));
+
+ set_pts(accumulated_pts_, "process pending updates fast path")
+ .set_value(Unit()); // TODO can't set until data are really stored on persistent storage
+ accumulated_pts_count_ = 0;
+ accumulated_pts_ = -1;
+ }
+ promise.set_value(Unit());
+ return;
+ }
+
+ pending_pts_updates_.emplace(
+ new_pts, PendingPtsUpdate(std::move(update), new_pts, pts_count, receive_time, std::move(promise)));
+
+ if (old_pts < accumulated_pts_ - accumulated_pts_count_) {
+ if (old_pts == new_pts - pts_count) {
+ // can't apply all updates, but can apply this and probably some other updates
+ process_pending_pts_updates();
+ } else {
+ set_pts_gap_timeout(receive_time + MAX_UNFILLED_GAP_TIME - Time::now());
}
+ return;
}
- process_updates(std::move(updates), false);
+
+ CHECK(old_pts == accumulated_pts_ - accumulated_pts_count_);
+ process_all_pending_pts_updates();
+}
+
+void UpdatesManager::postpone_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count,
+ double receive_time, Promise<Unit> &&promise) {
+ postponed_pts_updates_.emplace(pts,
+ PendingPtsUpdate(std::move(update), pts, pts_count, receive_time, std::move(promise)));
+}
+
+void UpdatesManager::process_seq_updates(int32 seq_end, int32 date,
+ vector<tl_object_ptr<telegram_api::Update>> &&updates,
+ Promise<Unit> &&promise) {
+ string serialized_updates;
+ if (date && seq_end) {
+ serialized_updates = PSTRING() << "process_seq_updates [seq_ = " << seq_ << ", seq_end = " << seq_end << "]: ";
+ // TODO remove after bugs will be fixed
+ for (auto &update : updates) {
+ if (update != nullptr) {
+ serialized_updates += oneline(to_string(update));
+ }
+ }
+ }
+ process_updates(std::move(updates), false, std::move(promise));
if (seq_end) {
seq_ = seq_end;
}
if (date && seq_end) {
set_date(date, true, std::move(serialized_updates));
}
+}
- if (!running_get_difference_) {
- set_state(State::Type::General);
+void UpdatesManager::process_qts_update(tl_object_ptr<telegram_api::Update> &&update_ptr, int32 qts,
+ Promise<Unit> &&promise) {
+ LOG(DEBUG) << "Process " << to_string(update_ptr);
+ if (last_get_difference_qts_ < qts - FORCED_GET_DIFFERENCE_PTS_DIFF) {
+ if (last_get_difference_qts_ != 0) {
+ schedule_get_difference("rare qts getDifference");
+ }
+ last_get_difference_qts_ = qts;
+ }
+ switch (update_ptr->get_id()) {
+ case telegram_api::updateNewEncryptedMessage::ID: {
+ auto update = move_tl_object_as<telegram_api::updateNewEncryptedMessage>(update_ptr);
+ send_closure(td_->secret_chats_manager_, &SecretChatsManager::on_new_message, std::move(update->message_),
+ add_qts(qts));
+ break;
+ }
+ case telegram_api::updateMessagePollVote::ID: {
+ auto update = move_tl_object_as<telegram_api::updateMessagePollVote>(update_ptr);
+ td_->poll_manager_->on_get_poll_vote(PollId(update->poll_id_), UserId(update->user_id_),
+ std::move(update->options_));
+ add_qts(qts).set_value(Unit());
+ break;
+ }
+ case telegram_api::updateBotStopped::ID: {
+ auto update = move_tl_object_as<telegram_api::updateBotStopped>(update_ptr);
+ td_->contacts_manager_->on_update_bot_stopped(UserId(update->user_id_), update->date_, update->stopped_);
+ add_qts(qts).set_value(Unit());
+ break;
+ }
+ case telegram_api::updateChatParticipant::ID: {
+ auto update = move_tl_object_as<telegram_api::updateChatParticipant>(update_ptr);
+ td_->contacts_manager_->on_update_chat_participant(
+ ChatId(update->chat_id_), UserId(update->actor_id_), update->date_,
+ DialogInviteLink(std::move(update->invite_), "updateChatParticipant"), std::move(update->prev_participant_),
+ std::move(update->new_participant_));
+ add_qts(qts).set_value(Unit());
+ break;
+ }
+ case telegram_api::updateChannelParticipant::ID: {
+ auto update = move_tl_object_as<telegram_api::updateChannelParticipant>(update_ptr);
+ td_->contacts_manager_->on_update_channel_participant(
+ ChannelId(update->channel_id_), UserId(update->actor_id_), update->date_,
+ DialogInviteLink(std::move(update->invite_), "updateChannelParticipant"),
+ std::move(update->prev_participant_), std::move(update->new_participant_));
+ add_qts(qts).set_value(Unit());
+ break;
+ }
+ case telegram_api::updateBotChatInviteRequester::ID: {
+ auto update = move_tl_object_as<telegram_api::updateBotChatInviteRequester>(update_ptr);
+ td_->contacts_manager_->on_update_chat_invite_requester(
+ DialogId(update->peer_), UserId(update->user_id_), std::move(update->about_), update->date_,
+ DialogInviteLink(std::move(update->invite_), "updateBotChatInviteRequester"));
+ add_qts(qts).set_value(Unit());
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::process_all_pending_pts_updates() {
+ auto begin_time = Time::now();
+ for (auto &update : pending_pts_updates_) {
+ td_->messages_manager_->process_pts_update(std::move(update.second.update));
+ update.second.promise.set_value(Unit());
+ }
+
+ if (last_pts_gap_time_ != 0) {
+ auto begin_diff = begin_time - last_pts_gap_time_;
+ auto diff = Time::now() - last_pts_gap_time_;
+ last_pts_gap_time_ = 0;
+ if (diff > 0.1) {
+ VLOG(get_difference) << "Gap in pts from " << accumulated_pts_ - accumulated_pts_count_ << " to "
+ << accumulated_pts_ << " has been filled in " << begin_diff << '-' << diff << " seconds";
+ }
+ }
+
+ set_pts(accumulated_pts_, "process_all_pending_pts_updates")
+ .set_value(Unit()); // TODO can't set until updates are stored on persistent storage
+ drop_all_pending_pts_updates();
+}
+
+void UpdatesManager::drop_all_pending_pts_updates() {
+ accumulated_pts_count_ = 0;
+ accumulated_pts_ = -1;
+ pts_gap_timeout_.cancel_timeout();
+ pending_pts_updates_.clear();
+}
+
+void UpdatesManager::process_postponed_pts_updates() {
+ if (postponed_pts_updates_.empty()) {
+ return;
+ }
+
+ auto begin_time = Time::now();
+ auto initial_pts = get_pts();
+ auto old_pts = initial_pts;
+ int32 skipped_update_count = 0;
+ int32 applied_update_count = 0;
+ auto update_it = postponed_pts_updates_.begin();
+ while (update_it != postponed_pts_updates_.end()) {
+ auto new_pts = update_it->second.pts;
+ auto pts_count = update_it->second.pts_count;
+ if (new_pts <= old_pts || (old_pts >= 1 && new_pts - (1 << 30) > old_pts)) {
+ skipped_update_count++;
+ td_->messages_manager_->skip_old_pending_pts_update(std::move(update_it->second.update), new_pts, old_pts,
+ pts_count, "process_postponed_pts_updates");
+ update_it->second.promise.set_value(Unit());
+ update_it = postponed_pts_updates_.erase(update_it);
+ continue;
+ }
+
+ if (Time::now() - begin_time >= td::min(UPDATE_APPLY_WARNING_TIME / 2, 0.1)) {
+ // the updates will be applied or skipped later; reget the remaining updates through getDifference
+ break;
+ }
+
+ auto last_update_it = update_it;
+ for (int32 i = 1; true; i++) {
+ ++last_update_it;
+ if (old_pts == new_pts - pts_count) {
+ // the updates can be applied
+ break;
+ }
+ if (old_pts > new_pts - pts_count || last_update_it == postponed_pts_updates_.end() ||
+ i == GAP_TIMEOUT_UPDATE_COUNT) {
+ // the updates can't be applied
+ VLOG(get_difference) << "Can't apply " << i << " next postponed updates with pts " << update_it->second.pts
+ << '-' << new_pts << ", because their pts_count is " << pts_count
+ << " instead of expected " << new_pts - old_pts;
+ last_update_it = update_it;
+ break;
+ }
+
+ new_pts = last_update_it->second.pts;
+ pts_count += last_update_it->second.pts_count;
+ }
+
+ if (last_update_it == update_it) {
+ // the updates will be applied or skipped later
+ break;
+ }
+ CHECK(old_pts == new_pts - pts_count);
+
+ while (update_it != last_update_it) {
+ if (update_it->second.pts_count > 0) {
+ applied_update_count++;
+ td_->messages_manager_->process_pts_update(std::move(update_it->second.update));
+ }
+ update_it->second.promise.set_value(Unit());
+ update_it = postponed_pts_updates_.erase(update_it);
+ }
+ old_pts = new_pts;
+ }
+ if (old_pts != initial_pts) {
+ set_pts(old_pts, "process_postponed_pts_updates")
+ .set_value(Unit()); // TODO can't set until data are really stored on persistent storage
+ }
+ CHECK(!running_get_difference_);
+ if (skipped_update_count + applied_update_count > 0) {
+ VLOG(get_difference) << "PTS has changed from " << initial_pts << " to " << old_pts << " after skipping "
+ << skipped_update_count << ", applying " << applied_update_count << " and keeping "
+ << postponed_pts_updates_.size() << " postponed updates";
+ }
+
+ auto passed_time = Time::now() - begin_time;
+ if (passed_time >= UPDATE_APPLY_WARNING_TIME) {
+ LOG(WARNING) << "PTS has changed from " << initial_pts << " to " << old_pts << " after skipping "
+ << skipped_update_count << ", applying " << applied_update_count << " and keeping "
+ << postponed_pts_updates_.size() << " postponed for " << (Time::now() - get_difference_start_time_)
+ << " updates in " << passed_time;
+ }
+}
+
+void UpdatesManager::process_pending_pts_updates() {
+ if (pending_pts_updates_.empty()) {
+ return;
+ }
+
+ auto begin_time = Time::now();
+ auto initial_pts = get_pts();
+ int32 applied_update_count = 0;
+ while (!pending_pts_updates_.empty()) {
+ auto update_it = pending_pts_updates_.begin();
+ auto &update = update_it->second;
+ if (get_pts() != update.pts - update.pts_count) {
+ // the updates will be applied or skipped later
+ break;
+ }
+
+ applied_update_count++;
+ if (update.pts_count > 0) {
+ td_->messages_manager_->process_pts_update(std::move(update.update));
+ set_pts(update.pts, "process_pending_pts_updates")
+ .set_value(Unit()); // TODO can't set until data are really stored on persistent storage
+
+ if (accumulated_pts_ != -1) {
+ CHECK(update.pts <= accumulated_pts_);
+ CHECK(accumulated_pts_count_ >= update.pts_count);
+ accumulated_pts_count_ -= update.pts_count;
+ }
+ }
+ update.promise.set_value(Unit());
+ pending_pts_updates_.erase(update_it);
+ }
+ if (applied_update_count > 0) {
+ pts_gap_timeout_.cancel_timeout();
+ }
+ if (!pending_pts_updates_.empty()) {
+ // if still have a gap, reset timeout
+ auto update_it = pending_pts_updates_.begin();
+ double receive_time = update_it->second.receive_time;
+ for (size_t i = 0; i < GAP_TIMEOUT_UPDATE_COUNT; i++) {
+ if (++update_it == pending_pts_updates_.end()) {
+ break;
+ }
+ receive_time = min(receive_time, update_it->second.receive_time);
+ }
+ set_pts_gap_timeout(receive_time + MAX_UNFILLED_GAP_TIME - Time::now());
+ }
+
+ auto passed_time = Time::now() - begin_time;
+ if (passed_time >= UPDATE_APPLY_WARNING_TIME) {
+ LOG(WARNING) << "PTS has changed from " << initial_pts << " to " << get_pts() << " after applying "
+ << applied_update_count << " and keeping " << pending_pts_updates_.size() << " pending updates in "
+ << passed_time;
}
}
void UpdatesManager::process_pending_seq_updates() {
+ if (!pending_seq_updates_.empty()) {
+ LOG(DEBUG) << "Trying to process " << pending_seq_updates_.size() << " pending seq updates";
+ // must not return, because in case of seq overflow there are no pending seq updates
+ }
+
+ auto begin_time = Time::now();
+ int32 initial_seq = seq_;
+ int32 applied_update_count = 0;
while (!pending_seq_updates_.empty() && !running_get_difference_) {
auto update_it = pending_seq_updates_.begin();
- auto seq_begin = update_it->second.seq_begin;
- if (seq_begin > seq_ + 1) {
+ auto &update = update_it->second;
+ auto seq_begin = update.seq_begin;
+ if (seq_begin - 1 > seq_ && seq_begin - (1 << 30) <= seq_) {
+ // the updates will be applied later
break;
}
- if (seq_begin == seq_ + 1) {
- process_seq_updates(update_it->second.seq_end, update_it->second.date, std::move(update_it->second.updates));
+
+ applied_update_count++;
+ auto seq_end = update.seq_end;
+ if (seq_begin - 1 == seq_) {
+ process_seq_updates(seq_end, update.date, std::move(update.updates), std::move(update.promise));
} else {
// old update
CHECK(seq_begin != 0);
- LOG_IF(ERROR, update_it->second.seq_end > seq_)
- << "Strange updates coming with seq_begin = " << seq_begin << ", seq_end = " << update_it->second.seq_end
- << ", but seq = " << seq_;
+ if (seq_begin <= seq_ && seq_ < seq_end) {
+ LOG(ERROR) << "Receive updates with seq_begin = " << seq_begin << ", seq_end = " << seq_end
+ << ", but seq = " << seq_;
+ }
+ update.promise.set_value(Unit());
}
pending_seq_updates_.erase(update_it);
}
- if (pending_seq_updates_.empty()) {
+ if (pending_seq_updates_.empty() || applied_update_count > 0) {
seq_gap_timeout_.cancel_timeout();
}
+ if (!pending_seq_updates_.empty()) {
+ // if still have a gap, reset timeout
+ auto update_it = pending_seq_updates_.begin();
+ double receive_time = update_it->second.receive_time;
+ for (size_t i = 0; i < GAP_TIMEOUT_UPDATE_COUNT; i++) {
+ if (++update_it == pending_seq_updates_.end()) {
+ break;
+ }
+ receive_time = min(receive_time, update_it->second.receive_time);
+ }
+ set_seq_gap_timeout(receive_time + MAX_UNFILLED_GAP_TIME - Time::now());
+ }
+
+ auto passed_time = Time::now() - begin_time;
+ if (passed_time >= UPDATE_APPLY_WARNING_TIME) {
+ LOG(WARNING) << "Seq has changed from " << initial_seq << " to " << seq_ << " after applying "
+ << applied_update_count << " and keeping " << pending_seq_updates_.size() << " pending updates in "
+ << passed_time;
+ }
+}
+
+void UpdatesManager::process_pending_qts_updates() {
+ if (pending_qts_updates_.empty()) {
+ return;
+ }
+
+ LOG(DEBUG) << "Process " << pending_qts_updates_.size() << " pending qts updates";
+ auto begin_time = Time::now();
+ auto initial_qts = get_qts();
+ int32 applied_update_count = 0;
+ while (!pending_qts_updates_.empty()) {
+ CHECK(!running_get_difference_);
+ auto update_it = pending_qts_updates_.begin();
+ auto qts = update_it->first;
+ auto old_qts = get_qts();
+ if (qts - 1 > old_qts && qts - (1 << 30) <= old_qts) {
+ // the update will be applied later
+ break;
+ }
+ auto promise = PromiseCreator::lambda(
+ [promises = std::move(update_it->second.promises)](Unit) mutable { set_promises(promises); });
+ applied_update_count++;
+ if (qts == old_qts + 1) {
+ process_qts_update(std::move(update_it->second.update), qts, std::move(promise));
+ } else {
+ promise.set_value(Unit());
+ }
+ pending_qts_updates_.erase(update_it);
+ }
+
+ if (applied_update_count > 0) {
+ qts_gap_timeout_.cancel_timeout();
+ }
+ if (!pending_qts_updates_.empty()) {
+ // if still have a gap, reset timeout
+ auto update_it = pending_qts_updates_.begin();
+ double receive_time = update_it->second.receive_time;
+ for (size_t i = 0; i < GAP_TIMEOUT_UPDATE_COUNT; i++) {
+ if (++update_it == pending_qts_updates_.end()) {
+ break;
+ }
+ receive_time = min(receive_time, update_it->second.receive_time);
+ }
+ set_qts_gap_timeout(receive_time + MAX_UNFILLED_GAP_TIME - Time::now());
+ }
+ CHECK(!running_get_difference_);
+
+ auto passed_time = Time::now() - begin_time;
+ if (passed_time >= UPDATE_APPLY_WARNING_TIME) {
+ LOG(WARNING) << "QTS has changed from " << initial_qts << " to " << get_qts() << " after applying "
+ << applied_update_count << " and keeping " << pending_qts_updates_.size() << " pending updates in "
+ << passed_time;
+ }
+}
+
+void UpdatesManager::set_pts_gap_timeout(double timeout) {
+ if (!pts_gap_timeout_.has_timeout() || timeout < pts_gap_timeout_.get_timeout()) {
+ pts_gap_timeout_.set_callback(std::move(fill_pts_gap));
+ pts_gap_timeout_.set_callback_data(static_cast<void *>(td_));
+ pts_gap_timeout_.set_timeout_in(timeout);
+ last_pts_gap_time_ = Time::now();
+ }
}
void UpdatesManager::set_seq_gap_timeout(double timeout) {
- if (!seq_gap_timeout_.has_timeout()) {
+ if (!seq_gap_timeout_.has_timeout() || timeout < seq_gap_timeout_.get_timeout()) {
seq_gap_timeout_.set_callback(std::move(fill_seq_gap));
seq_gap_timeout_.set_callback_data(static_cast<void *>(td_));
seq_gap_timeout_.set_timeout_in(timeout);
}
}
-void UpdatesManager::on_pending_update(tl_object_ptr<telegram_api::Update> update, int32 seq, const char *source) {
- vector<tl_object_ptr<telegram_api::Update>> v;
- v.push_back(std::move(update));
+void UpdatesManager::set_qts_gap_timeout(double timeout) {
+ if (!qts_gap_timeout_.has_timeout() || timeout < qts_gap_timeout_.get_timeout()) {
+ qts_gap_timeout_.set_callback(std::move(fill_qts_gap));
+ qts_gap_timeout_.set_callback_data(static_cast<void *>(td_));
+ qts_gap_timeout_.set_timeout_in(timeout);
+ }
+}
- on_pending_updates(std::move(v), seq, seq, 0, source); // TODO can be optimized
+void UpdatesManager::on_pending_update(tl_object_ptr<telegram_api::Update> update, int32 seq, Promise<Unit> &&promise,
+ const char *source) {
+ vector<tl_object_ptr<telegram_api::Update>> updates;
+ updates.push_back(std::move(update));
+ on_pending_updates(std::move(updates), seq, seq, 0, Time::now(), std::move(promise), source);
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewMessage> update, bool force_apply) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewMessage> update, Promise<Unit> &&promise) {
int new_pts = update->pts_;
int pts_count = update->pts_count_;
- td_->messages_manager_->add_pending_update(std::move(update), new_pts, pts_count, force_apply, "on_updateNewMessage");
+ add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise), "updateNewMessage");
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewChannelMessage> update, bool /*force_apply*/) {
- CHECK(update != nullptr);
- td_->messages_manager_->on_update_new_channel_message(std::move(update));
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewChannelMessage> update, Promise<Unit> &&promise) {
+ DialogId dialog_id = MessagesManager::get_message_dialog_id(update->message_);
+ int new_pts = update->pts_;
+ int pts_count = update->pts_count_;
+ td_->messages_manager_->add_pending_channel_update(dialog_id, std::move(update), new_pts, pts_count,
+ std::move(promise), "updateNewChannelMessage");
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessageID> update, bool force_apply) {
- CHECK(update != nullptr);
- if (!force_apply) {
- LOG(ERROR) << "Receive updateMessageID not in getDifference";
- return;
- }
- LOG(INFO) << "Receive update about sent message " << to_string(update);
- td_->messages_manager_->on_update_message_id(update->random_id_, MessageId(ServerMessageId(update->id_)),
- "getDifference");
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessageID> update, Promise<Unit> &&promise) {
+ LOG(ERROR) << "Receive not in getDifference and not in on_pending_updates " << to_string(update);
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadMessagesContents> update, bool force_apply) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadMessagesContents> update,
+ Promise<Unit> &&promise) {
int new_pts = update->pts_;
int pts_count = update->pts_count_;
- td_->messages_manager_->add_pending_update(std::move(update), new_pts, pts_count, force_apply,
- "on_updateReadMessagesContents");
+ add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
+ "updateReadMessagesContents");
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEditMessage> update, bool force_apply) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEditMessage> update, Promise<Unit> &&promise) {
int new_pts = update->pts_;
int pts_count = update->pts_count_;
- td_->messages_manager_->add_pending_update(std::move(update), new_pts, pts_count, force_apply,
- "on_updateEditMessage");
+ add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise), "updateEditMessage");
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDeleteMessages> update, bool force_apply) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDeleteMessages> update, Promise<Unit> &&promise) {
int new_pts = update->pts_;
int pts_count = update->pts_count_;
if (update->messages_.empty()) {
- td_->messages_manager_->add_pending_update(make_tl_object<dummyUpdate>(), new_pts, pts_count, force_apply,
- "on_updateDeleteMessages");
+ add_pending_pts_update(make_tl_object<dummyUpdate>(), new_pts, pts_count, Time::now(), Promise<Unit>(),
+ "updateDeleteMessages");
+ promise.set_value(Unit());
} else {
- td_->messages_manager_->add_pending_update(std::move(update), new_pts, pts_count, force_apply,
- "on_updateDeleteMessages");
+ add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
+ "updateDeleteMessages");
}
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadHistoryInbox> update, bool force_apply) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadHistoryInbox> update, Promise<Unit> &&promise) {
int new_pts = update->pts_;
int pts_count = update->pts_count_;
- td_->messages_manager_->add_pending_update(std::move(update), new_pts, pts_count, force_apply,
- "on_updateReadHistoryInbox");
+ add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
+ "updateReadHistoryInbox");
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadHistoryOutbox> update, bool force_apply) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadHistoryOutbox> update, Promise<Unit> &&promise) {
int new_pts = update->pts_;
int pts_count = update->pts_count_;
- td_->messages_manager_->add_pending_update(std::move(update), new_pts, pts_count, force_apply,
- "on_updateReadHistoryOutbox");
+ add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
+ "updateReadHistoryOutbox");
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateServiceNotification> update, bool /*force_apply*/) {
- CHECK(update != nullptr);
- td_->messages_manager_->on_update_service_notification(std::move(update));
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateServiceNotification> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_service_notification(std::move(update), true, Promise<Unit>());
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateContactRegistered> update, bool /*force_apply*/) {
- CHECK(update != nullptr);
- td_->messages_manager_->on_update_contact_registered(std::move(update));
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChat> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_dialog_info_full_invalidated(DialogId(ChatId(update->chat_id_)));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadChannelInbox> update, bool /*force_apply*/) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadChannelInbox> update, Promise<Unit> &&promise) {
td_->messages_manager_->on_update_read_channel_inbox(std::move(update));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadChannelOutbox> update, bool /*force_apply*/) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadChannelOutbox> update, Promise<Unit> &&promise) {
td_->messages_manager_->on_update_read_channel_outbox(std::move(update));
+ promise.set_value(Unit());
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelReadMessagesContents> update,
- bool /*force_apply*/) {
+ Promise<Unit> &&promise) {
td_->messages_manager_->on_update_read_channel_messages_contents(std::move(update));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelTooLong> update, bool force_apply) {
- td_->messages_manager_->on_update_channel_too_long(std::move(update), force_apply);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelTooLong> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_channel_too_long(std::move(update), false);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannel> update, bool /*force_apply*/) {
- // nothing to do
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannel> update, Promise<Unit> &&promise) {
+ td_->contacts_manager_->invalidate_channel_full(ChannelId(update->channel_id_), false, "updateChannel");
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEditChannelMessage> update, bool /*force_apply*/) {
- td_->messages_manager_->on_update_edit_channel_message(std::move(update));
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEditChannelMessage> update, Promise<Unit> &&promise) {
+ DialogId dialog_id = MessagesManager::get_message_dialog_id(update->message_);
+ int new_pts = update->pts_;
+ int pts_count = update->pts_count_;
+ td_->messages_manager_->add_pending_channel_update(dialog_id, std::move(update), new_pts, pts_count,
+ std::move(promise), "updateEditChannelMessage");
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDeleteChannelMessages> update, bool /*force_apply*/) {
- ChannelId channel_id(update->channel_id_);
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
- return;
- }
- DialogId dialog_id(channel_id);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDeleteChannelMessages> update,
+ Promise<Unit> &&promise) {
+ DialogId dialog_id(ChannelId(update->channel_id_));
int new_pts = update->pts_;
int pts_count = update->pts_count_;
td_->messages_manager_->add_pending_channel_update(dialog_id, std::move(update), new_pts, pts_count,
- "on_updateDeleteChannelMessages");
+ std::move(promise), "updateDeleteChannelMessages");
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelMessageViews> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelMessageViews> update, Promise<Unit> &&promise) {
ChannelId channel_id(update->channel_id_);
if (!channel_id.is_valid()) {
LOG(ERROR) << "Receive invalid " << channel_id;
- return;
+ } else {
+ DialogId dialog_id(channel_id);
+ td_->messages_manager_->on_update_message_view_count({dialog_id, MessageId(ServerMessageId(update->id_))},
+ update->views_);
}
- DialogId dialog_id(channel_id);
- td_->messages_manager_->on_update_message_views({dialog_id, MessageId(ServerMessageId(update->id_))}, update->views_);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelPinnedMessage> update, bool /*force_apply*/) {
- td_->contacts_manager_->on_update_channel_pinned_message(ChannelId(update->channel_id_),
- MessageId(ServerMessageId(update->id_)));
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelMessageForwards> update,
+ Promise<Unit> &&promise) {
+ ChannelId channel_id(update->channel_id_);
+ if (!channel_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << channel_id;
+ } else {
+ DialogId dialog_id(channel_id);
+ td_->messages_manager_->on_update_message_forward_count({dialog_id, MessageId(ServerMessageId(update->id_))},
+ update->forwards_);
+ }
+ promise.set_value(Unit());
}
void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelAvailableMessages> update,
- bool /*force_apply*/) {
+ Promise<Unit> &&promise) {
td_->messages_manager_->on_update_channel_max_unavailable_message_id(
ChannelId(update->channel_id_), MessageId(ServerMessageId(update->available_min_id_)));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNotifySettings> update, bool /*force_apply*/) {
- CHECK(update != nullptr);
- td_->messages_manager_->on_update_notify_settings(
- td_->messages_manager_->get_notification_settings_scope(std::move(update->peer_)),
- std::move(update->notify_settings_));
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadChannelDiscussionInbox> update,
+ Promise<Unit> &&promise) {
+ auto last_read_inbox_message_id = MessageId(ServerMessageId(update->read_max_id_));
+ if (!last_read_inbox_message_id.is_valid()) {
+ LOG(ERROR) << "Receive " << to_string(update);
+ return;
+ }
+ td_->messages_manager_->on_update_read_message_comments(DialogId(ChannelId(update->channel_id_)),
+ MessageId(ServerMessageId(update->top_msg_id_)), MessageId(),
+ last_read_inbox_message_id, MessageId());
+ if ((update->flags_ & telegram_api::updateReadChannelDiscussionInbox::BROADCAST_ID_MASK) != 0) {
+ td_->messages_manager_->on_update_read_message_comments(DialogId(ChannelId(update->broadcast_id_)),
+ MessageId(ServerMessageId(update->broadcast_post_)),
+ MessageId(), last_read_inbox_message_id, MessageId());
+ }
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateWebPage> update, bool force_apply) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadChannelDiscussionOutbox> update,
+ Promise<Unit> &&promise) {
+ auto last_read_outbox_message_id = MessageId(ServerMessageId(update->read_max_id_));
+ if (!last_read_outbox_message_id.is_valid()) {
+ LOG(ERROR) << "Receive " << to_string(update);
+ return;
+ }
+ td_->messages_manager_->on_update_read_message_comments(DialogId(ChannelId(update->channel_id_)),
+ MessageId(ServerMessageId(update->top_msg_id_)), MessageId(),
+ MessageId(), last_read_outbox_message_id);
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePinnedMessages> update, Promise<Unit> &&promise) {
+ int new_pts = update->pts_;
+ int pts_count = update->pts_count_;
+ add_pending_pts_update(std::move(update), new_pts, pts_count, Time::now(), std::move(promise),
+ "updatePinnedMessages");
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePinnedChannelMessages> update,
+ Promise<Unit> &&promise) {
+ DialogId dialog_id(ChannelId(update->channel_id_));
+ int new_pts = update->pts_;
+ int pts_count = update->pts_count_;
+ td_->messages_manager_->add_pending_channel_update(dialog_id, std::move(update), new_pts, pts_count,
+ std::move(promise), "updatePinnedChannelMessages");
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNotifySettings> update, Promise<Unit> &&promise) {
+ switch (update->peer_->get_id()) {
+ case telegram_api::notifyPeer::ID: {
+ DialogId dialog_id(static_cast<const telegram_api::notifyPeer *>(update->peer_.get())->peer_);
+ if (dialog_id.is_valid()) {
+ td_->messages_manager_->on_update_dialog_notify_settings(dialog_id, std::move(update->notify_settings_),
+ "updateNotifySettings");
+ } else {
+ LOG(ERROR) << "Receive wrong " << to_string(update);
+ }
+ break;
+ }
+ case telegram_api::notifyUsers::ID:
+ td_->notification_settings_manager_->on_update_scope_notify_settings(NotificationSettingsScope::Private,
+ std::move(update->notify_settings_));
+ break;
+ case telegram_api::notifyChats::ID:
+ td_->notification_settings_manager_->on_update_scope_notify_settings(NotificationSettingsScope::Group,
+ std::move(update->notify_settings_));
+ break;
+ case telegram_api::notifyBroadcasts::ID:
+ td_->notification_settings_manager_->on_update_scope_notify_settings(NotificationSettingsScope::Channel,
+ std::move(update->notify_settings_));
+ break;
+ case telegram_api::notifyForumTopic::ID:
+ // TODO
+ break;
+ default:
+ UNREACHABLE();
+ }
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePeerSettings> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_get_peer_settings(DialogId(update->peer_), std::move(update->settings_));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePeerHistoryTTL> update, Promise<Unit> &&promise) {
+ MessageTtl message_ttl;
+ if ((update->flags_ & telegram_api::updatePeerHistoryTTL::TTL_PERIOD_MASK) != 0) {
+ message_ttl = MessageTtl(update->ttl_period_);
+ }
+ td_->messages_manager_->on_update_dialog_message_ttl(DialogId(update->peer_), message_ttl);
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePeerLocated> update, Promise<Unit> &&promise) {
+ td_->contacts_manager_->on_update_peer_located(std::move(update->peers_), true);
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateWebPage> update, Promise<Unit> &&promise) {
td_->web_pages_manager_->on_get_web_page(std::move(update->webpage_), DialogId());
- td_->messages_manager_->add_pending_update(make_tl_object<dummyUpdate>(), update->pts_, update->pts_count_,
- force_apply, "on_updateWebPage");
+ add_pending_pts_update(make_tl_object<dummyUpdate>(), update->pts_, update->pts_count_, Time::now(), Promise<Unit>(),
+ "updateWebPage");
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelWebPage> update, bool /*force_apply*/) {
- CHECK(update != nullptr);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelWebPage> update, Promise<Unit> &&promise) {
td_->web_pages_manager_->on_get_web_page(std::move(update->webpage_), DialogId());
- ChannelId channel_id(update->channel_id_);
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
- return;
- }
- DialogId dialog_id(channel_id);
+ DialogId dialog_id(ChannelId(update->channel_id_));
td_->messages_manager_->add_pending_channel_update(dialog_id, make_tl_object<dummyUpdate>(), update->pts_,
- update->pts_count_, "on_updateChannelWebPage");
-}
-
-tl_object_ptr<td_api::ChatAction> UpdatesManager::convert_send_message_action(
- tl_object_ptr<telegram_api::SendMessageAction> action) {
- auto fix_progress = [](int32 progress) { return progress <= 0 || progress > 100 ? 0 : progress; };
-
- switch (action->get_id()) {
- case telegram_api::sendMessageCancelAction::ID:
- return make_tl_object<td_api::chatActionCancel>();
- case telegram_api::sendMessageTypingAction::ID:
- return make_tl_object<td_api::chatActionTyping>();
- case telegram_api::sendMessageRecordVideoAction::ID:
- return make_tl_object<td_api::chatActionRecordingVideo>();
- case telegram_api::sendMessageUploadVideoAction::ID: {
- auto upload_video_action = move_tl_object_as<telegram_api::sendMessageUploadVideoAction>(action);
- return make_tl_object<td_api::chatActionUploadingVideo>(fix_progress(upload_video_action->progress_));
- }
- case telegram_api::sendMessageRecordAudioAction::ID:
- return make_tl_object<td_api::chatActionRecordingVoiceNote>();
- case telegram_api::sendMessageUploadAudioAction::ID: {
- auto upload_audio_action = move_tl_object_as<telegram_api::sendMessageUploadAudioAction>(action);
- return make_tl_object<td_api::chatActionUploadingVoiceNote>(fix_progress(upload_audio_action->progress_));
- }
- case telegram_api::sendMessageUploadPhotoAction::ID: {
- auto upload_photo_action = move_tl_object_as<telegram_api::sendMessageUploadPhotoAction>(action);
- return make_tl_object<td_api::chatActionUploadingPhoto>(fix_progress(upload_photo_action->progress_));
- }
- case telegram_api::sendMessageUploadDocumentAction::ID: {
- auto upload_document_action = move_tl_object_as<telegram_api::sendMessageUploadDocumentAction>(action);
- return make_tl_object<td_api::chatActionUploadingDocument>(fix_progress(upload_document_action->progress_));
- }
- case telegram_api::sendMessageGeoLocationAction::ID:
- return make_tl_object<td_api::chatActionChoosingLocation>();
- case telegram_api::sendMessageChooseContactAction::ID:
- return make_tl_object<td_api::chatActionChoosingContact>();
- case telegram_api::sendMessageGamePlayAction::ID:
- return make_tl_object<td_api::chatActionStartPlayingGame>();
- case telegram_api::sendMessageRecordRoundAction::ID:
- return make_tl_object<td_api::chatActionRecordingVideoNote>();
- case telegram_api::sendMessageUploadRoundAction::ID: {
- auto upload_round_action = move_tl_object_as<telegram_api::sendMessageUploadRoundAction>(action);
- return make_tl_object<td_api::chatActionUploadingVideoNote>(fix_progress(upload_round_action->progress_));
+ update->pts_count_, Promise<Unit>(), "updateChannelWebPage");
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessageReactions> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_message_reactions(
+ {DialogId(update->peer_), MessageId(ServerMessageId(update->msg_id_))}, std::move(update->reactions_),
+ std::move(promise));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateRecentReactions> update, Promise<Unit> &&promise) {
+ td_->stickers_manager_->reload_recent_reactions();
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateAttachMenuBots> update, Promise<Unit> &&promise) {
+ td_->attach_menu_manager_->reload_attach_menu_bots(std::move(promise));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateWebViewResultSent> update, Promise<Unit> &&promise) {
+ td_->attach_menu_manager_->close_web_view(update->query_id_, std::move(promise));
+ send_closure(G()->td(), &Td::send_update, td_api::make_object<td_api::updateWebAppMessageSent>(update->query_id_));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateFolderPeers> update, Promise<Unit> &&promise) {
+ for (auto &folder_peer : update->folder_peers_) {
+ DialogId dialog_id(folder_peer->peer_);
+ FolderId folder_id(folder_peer->folder_id_);
+ td_->messages_manager_->on_update_dialog_folder_id(dialog_id, folder_id);
+ }
+
+ if (update->pts_ > 0) {
+ add_pending_pts_update(make_tl_object<dummyUpdate>(), update->pts_, update->pts_count_, Time::now(),
+ Promise<Unit>(), "updateFolderPeers");
+ }
+ promise.set_value(Unit());
+}
+
+int32 UpdatesManager::get_short_update_date() const {
+ int32 now = G()->unix_time();
+ if (short_update_date_ > 0) {
+ return min(short_update_date_, now);
+ }
+ return now;
+}
+
+bool UpdatesManager::have_update_pts_changed(const vector<tl_object_ptr<telegram_api::Update>> &updates) {
+ for (auto &update : updates) {
+ CHECK(update != nullptr);
+ if (update->get_id() == telegram_api::updatePtsChanged::ID) {
+ return true;
}
+ }
+ return false;
+}
+
+bool UpdatesManager::check_pts_update_dialog_id(DialogId dialog_id) {
+ switch (dialog_id.get_type()) {
+ case DialogType::User:
+ case DialogType::Chat:
+ return true;
+ case DialogType::Channel:
+ case DialogType::SecretChat:
+ case DialogType::None:
+ return false;
default:
UNREACHABLE();
- return make_tl_object<td_api::chatActionTyping>();
+ return false;
}
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserTyping> update, bool /*force_apply*/) {
- UserId user_id(update->user_id_);
- if (!td_->contacts_manager_->have_min_user(user_id)) {
- LOG(DEBUG) << "Ignore user typing of unknown " << user_id;
- return;
+bool UpdatesManager::check_pts_update(const tl_object_ptr<telegram_api::Update> &update) {
+ CHECK(update != nullptr);
+ switch (update->get_id()) {
+ case dummyUpdate::ID:
+ case updateSentMessage::ID:
+ case telegram_api::updateReadMessagesContents::ID:
+ case telegram_api::updateDeleteMessages::ID:
+ return true;
+ case telegram_api::updateNewMessage::ID: {
+ auto update_new_message = static_cast<const telegram_api::updateNewMessage *>(update.get());
+ return check_pts_update_dialog_id(MessagesManager::get_message_dialog_id(update_new_message->message_));
+ }
+ case telegram_api::updateReadHistoryInbox::ID: {
+ auto update_read_history_inbox = static_cast<const telegram_api::updateReadHistoryInbox *>(update.get());
+ return check_pts_update_dialog_id(DialogId(update_read_history_inbox->peer_));
+ }
+ case telegram_api::updateReadHistoryOutbox::ID: {
+ auto update_read_history_outbox = static_cast<const telegram_api::updateReadHistoryOutbox *>(update.get());
+ return check_pts_update_dialog_id(DialogId(update_read_history_outbox->peer_));
+ }
+ case telegram_api::updateEditMessage::ID: {
+ auto update_edit_message = static_cast<const telegram_api::updateEditMessage *>(update.get());
+ return check_pts_update_dialog_id(MessagesManager::get_message_dialog_id(update_edit_message->message_));
+ }
+ case telegram_api::updatePinnedMessages::ID: {
+ auto update_pinned_messages = static_cast<const telegram_api::updatePinnedMessages *>(update.get());
+ return check_pts_update_dialog_id(DialogId(update_pinned_messages->peer_));
+ }
+ default:
+ return false;
}
- DialogId dialog_id(user_id);
- if (!td_->messages_manager_->have_dialog(dialog_id)) {
- LOG(DEBUG) << "Ignore user typing in unknown " << dialog_id;
- return;
+}
+
+bool UpdatesManager::is_pts_update(const telegram_api::Update *update) {
+ switch (update->get_id()) {
+ case telegram_api::updateNewMessage::ID:
+ case telegram_api::updateReadMessagesContents::ID:
+ case telegram_api::updateEditMessage::ID:
+ case telegram_api::updateDeleteMessages::ID:
+ case telegram_api::updateReadHistoryInbox::ID:
+ case telegram_api::updateReadHistoryOutbox::ID:
+ case telegram_api::updateWebPage::ID:
+ case telegram_api::updatePinnedMessages::ID:
+ case telegram_api::updateFolderPeers::ID:
+ return true;
+ default:
+ return false;
}
- td_->messages_manager_->on_user_dialog_action(dialog_id, user_id,
- convert_send_message_action(std::move(update->action_)));
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatUserTyping> update, bool /*force_apply*/) {
- UserId user_id(update->user_id_);
- if (!td_->contacts_manager_->have_min_user(user_id)) {
- LOG(DEBUG) << "Ignore user chat typing of unknown " << user_id;
- return;
+int32 UpdatesManager::get_update_pts(const telegram_api::Update *update) {
+ switch (update->get_id()) {
+ case telegram_api::updateNewMessage::ID:
+ return static_cast<const telegram_api::updateNewMessage *>(update)->pts_;
+ case telegram_api::updateReadMessagesContents::ID:
+ return static_cast<const telegram_api::updateReadMessagesContents *>(update)->pts_;
+ case telegram_api::updateEditMessage::ID:
+ return static_cast<const telegram_api::updateEditMessage *>(update)->pts_;
+ case telegram_api::updateDeleteMessages::ID:
+ return static_cast<const telegram_api::updateDeleteMessages *>(update)->pts_;
+ case telegram_api::updateReadHistoryInbox::ID:
+ return static_cast<const telegram_api::updateReadHistoryInbox *>(update)->pts_;
+ case telegram_api::updateReadHistoryOutbox::ID:
+ return static_cast<const telegram_api::updateReadHistoryOutbox *>(update)->pts_;
+ case telegram_api::updateWebPage::ID:
+ return static_cast<const telegram_api::updateWebPage *>(update)->pts_;
+ case telegram_api::updatePinnedMessages::ID:
+ return static_cast<const telegram_api::updatePinnedMessages *>(update)->pts_;
+ case telegram_api::updateFolderPeers::ID:
+ return static_cast<const telegram_api::updateFolderPeers *>(update)->pts_;
+ default:
+ return 0;
}
- ChatId chat_id(update->chat_id_);
- DialogId dialog_id(chat_id);
- if (!td_->messages_manager_->have_dialog(dialog_id)) {
- ChannelId channel_id(update->chat_id_);
- dialog_id = DialogId(channel_id);
- if (!td_->messages_manager_->have_dialog(dialog_id)) {
- LOG(DEBUG) << "Ignore user chat typing in unknown " << dialog_id;
- return;
- }
+}
+
+bool UpdatesManager::is_qts_update(const telegram_api::Update *update) {
+ switch (update->get_id()) {
+ case telegram_api::updateNewEncryptedMessage::ID:
+ case telegram_api::updateMessagePollVote::ID:
+ case telegram_api::updateBotStopped::ID:
+ case telegram_api::updateChatParticipant::ID:
+ case telegram_api::updateChannelParticipant::ID:
+ case telegram_api::updateBotChatInviteRequester::ID:
+ return true;
+ default:
+ return false;
}
- td_->messages_manager_->on_user_dialog_action(dialog_id, user_id,
- convert_send_message_action(std::move(update->action_)));
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEncryptedChatTyping> update, bool /*force_apply*/) {
- SecretChatId secret_chat_id(update->chat_id_);
- DialogId dialog_id(secret_chat_id);
+int32 UpdatesManager::get_update_qts(const telegram_api::Update *update) {
+ switch (update->get_id()) {
+ case telegram_api::updateNewEncryptedMessage::ID:
+ return static_cast<const telegram_api::updateNewEncryptedMessage *>(update)->qts_;
+ case telegram_api::updateMessagePollVote::ID:
+ return static_cast<const telegram_api::updateMessagePollVote *>(update)->qts_;
+ case telegram_api::updateBotStopped::ID:
+ return static_cast<const telegram_api::updateBotStopped *>(update)->qts_;
+ case telegram_api::updateChatParticipant::ID:
+ return static_cast<const telegram_api::updateChatParticipant *>(update)->qts_;
+ case telegram_api::updateChannelParticipant::ID:
+ return static_cast<const telegram_api::updateChannelParticipant *>(update)->qts_;
+ case telegram_api::updateBotChatInviteRequester::ID:
+ return static_cast<const telegram_api::updateBotChatInviteRequester *>(update)->qts_;
+ default:
+ return 0;
+ }
+}
- if (!td_->messages_manager_->have_dialog(dialog_id)) {
- LOG(DEBUG) << "Ignore secret chat typing in unknown " << dialog_id;
- return;
+bool UpdatesManager::is_channel_pts_update(const telegram_api::Update *update) {
+ switch (update->get_id()) {
+ case telegram_api::updateNewChannelMessage::ID:
+ case telegram_api::updateEditChannelMessage::ID:
+ case telegram_api::updateDeleteChannelMessages::ID:
+ case telegram_api::updatePinnedChannelMessages::ID:
+ return true;
+ default:
+ return false;
}
+}
- UserId user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id);
- if (!td_->contacts_manager_->have_user_force(user_id)) {
- LOG(DEBUG) << "Ignore secret chat typing of unknown " << user_id;
- return;
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserTyping> update, Promise<Unit> &&promise) {
+ DialogId dialog_id(UserId(update->user_id_));
+ td_->messages_manager_->on_dialog_action(dialog_id, MessageId(), dialog_id, DialogAction(std::move(update->action_)),
+ get_short_update_date());
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatUserTyping> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_dialog_action(DialogId(ChatId(update->chat_id_)), MessageId(), DialogId(update->from_id_),
+ DialogAction(std::move(update->action_)), get_short_update_date());
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelUserTyping> update, Promise<Unit> &&promise) {
+ MessageId top_thread_message_id;
+ if ((update->flags_ & telegram_api::updateChannelUserTyping::TOP_MSG_ID_MASK) != 0) {
+ top_thread_message_id = MessageId(ServerMessageId(update->top_msg_id_));
}
+ td_->messages_manager_->on_dialog_action(DialogId(ChannelId(update->channel_id_)), top_thread_message_id,
+ DialogId(update->from_id_), DialogAction(std::move(update->action_)),
+ get_short_update_date());
+ promise.set_value(Unit());
+}
- td_->messages_manager_->on_user_dialog_action(dialog_id, user_id, make_tl_object<td_api::chatActionTyping>());
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEncryptedChatTyping> update, Promise<Unit> &&promise) {
+ SecretChatId secret_chat_id(update->chat_id_);
+ UserId user_id = td_->contacts_manager_->get_secret_chat_user_id(secret_chat_id);
+ td_->messages_manager_->on_dialog_action(DialogId(secret_chat_id), MessageId(), DialogId(user_id),
+ DialogAction::get_typing_action(), get_short_update_date());
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserStatus> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserStatus> update, Promise<Unit> &&promise) {
td_->contacts_manager_->on_update_user_online(UserId(update->user_id_), std::move(update->status_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserName> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserName> update, Promise<Unit> &&promise) {
td_->contacts_manager_->on_update_user_name(UserId(update->user_id_), std::move(update->first_name_),
- std::move(update->last_name_), std::move(update->username_));
+ std::move(update->last_name_),
+ Usernames{string(), std::move(update->usernames_)});
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserPhone> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserPhone> update, Promise<Unit> &&promise) {
td_->contacts_manager_->on_update_user_phone_number(UserId(update->user_id_), std::move(update->phone_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserPhoto> update, bool /*force_apply*/) {
- // TODO update->previous_, update->date_
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserPhoto> update, Promise<Unit> &&promise) {
td_->contacts_manager_->on_update_user_photo(UserId(update->user_id_), std::move(update->photo_));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserEmojiStatus> update, Promise<Unit> &&promise) {
+ td_->contacts_manager_->on_update_user_emoji_status(UserId(update->user_id_), std::move(update->emoji_status_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateUserBlocked> update, bool /*force_apply*/) {
- td_->contacts_manager_->on_update_user_blocked(UserId(update->user_id_), update->blocked_);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateRecentEmojiStatuses> update, Promise<Unit> &&promise) {
+ get_recent_emoji_statuses(td_, Auto());
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateContactLink> update, bool /*force_apply*/) {
- td_->contacts_manager_->on_update_user_links(UserId(update->user_id_), std::move(update->my_link_),
- std::move(update->foreign_link_));
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePeerBlocked> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_dialog_is_blocked(DialogId(update->peer_id_), update->blocked_);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatParticipants> update, bool /*force_apply*/) {
- td_->contacts_manager_->on_get_chat_participants(std::move(update->participants_));
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotCommands> update, Promise<Unit> &&promise) {
+ td_->contacts_manager_->on_update_bot_commands(DialogId(update->peer_), UserId(update->bot_id_),
+ std::move(update->commands_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatParticipantAdd> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotMenuButton> update, Promise<Unit> &&promise) {
+ td_->contacts_manager_->on_update_bot_menu_button(UserId(update->bot_id_), std::move(update->button_));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatParticipants> update, Promise<Unit> &&promise) {
+ td_->contacts_manager_->on_get_chat_participants(std::move(update->participants_), true);
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatParticipantAdd> update, Promise<Unit> &&promise) {
td_->contacts_manager_->on_update_chat_add_user(ChatId(update->chat_id_), UserId(update->inviter_id_),
UserId(update->user_id_), update->date_, update->version_);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatParticipantAdmin> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatParticipantAdmin> update,
+ Promise<Unit> &&promise) {
td_->contacts_manager_->on_update_chat_edit_administrator(ChatId(update->chat_id_), UserId(update->user_id_),
update->is_admin_, update->version_);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatParticipantDelete> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatParticipantDelete> update,
+ Promise<Unit> &&promise) {
td_->contacts_manager_->on_update_chat_delete_user(ChatId(update->chat_id_), UserId(update->user_id_),
update->version_);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatAdmins> update, bool /*force_apply*/) {
- td_->contacts_manager_->on_update_chat_everyone_is_administrator(ChatId(update->chat_id_), !update->enabled_,
- update->version_);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatDefaultBannedRights> update,
+ Promise<Unit> &&promise) {
+ DialogId dialog_id(update->peer_);
+ RestrictedRights permissions(update->default_banned_rights_);
+ auto version = update->version_;
+ switch (dialog_id.get_type()) {
+ case DialogType::Chat:
+ td_->contacts_manager_->on_update_chat_default_permissions(dialog_id.get_chat_id(), permissions, version);
+ break;
+ case DialogType::Channel:
+ LOG_IF(ERROR, version != 0) << "Receive version " << version << " in " << dialog_id;
+ td_->contacts_manager_->on_update_channel_default_permissions(dialog_id.get_channel_id(), permissions);
+ break;
+ case DialogType::None:
+ case DialogType::User:
+ case DialogType::SecretChat:
+ default:
+ LOG(ERROR) << "Receive updateChatDefaultBannedRights in " << dialog_id;
+ break;
+ }
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDraftMessage> update, bool /*force_apply*/) {
- td_->messages_manager_->on_update_dialog_draft_message(DialogId(update->peer_), std::move(update->draft_));
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDraftMessage> update, Promise<Unit> &&promise) {
+ MessageId top_thread_message_id;
+ if ((update->flags_ & telegram_api::updateDraftMessage::TOP_MSG_ID_MASK) != 0) {
+ top_thread_message_id = MessageId(ServerMessageId(update->top_msg_id_));
+ }
+ td_->messages_manager_->on_update_dialog_draft_message(DialogId(update->peer_), top_thread_message_id,
+ std::move(update->draft_));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDialogPinned> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_dialog_is_pinned(FolderId(update->folder_id_), DialogId(update->peer_),
+ update->pinned_);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDialogPinned> update, bool /*force_apply*/) {
- td_->messages_manager_->on_update_dialog_pinned(
- DialogId(update->peer_), (update->flags_ & telegram_api::updateDialogPinned::PINNED_MASK) != 0);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePinnedDialogs> update, Promise<Unit> &&promise) {
+ FolderId folder_id(update->flags_ & telegram_api::updatePinnedDialogs::FOLDER_ID_MASK ? update->folder_id_ : 0);
+ td_->messages_manager_->on_update_pinned_dialogs(folder_id); // TODO use update->order_
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePinnedDialogs> update, bool /*force_apply*/) {
- td_->messages_manager_->on_update_pinned_dialogs(); // TODO use update->order_
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDialogUnreadMark> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_dialog_is_marked_as_unread(DialogId(update->peer_), update->unread_);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDcOptions> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDialogFilter> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_dialog_filters();
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDialogFilters> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_dialog_filters();
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDialogFilterOrder> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_dialog_filters();
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDcOptions> update, Promise<Unit> &&promise) {
send_closure(G()->config_manager(), &ConfigManager::on_dc_options_update, DcOptions(update->dc_options_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotInlineQuery> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotInlineQuery> update, Promise<Unit> &&promise) {
td_->inline_queries_manager_->on_new_query(update->query_id_, UserId(update->user_id_), Location(update->geo_),
- update->query_, update->offset_);
+ std::move(update->peer_type_), update->query_, update->offset_);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotInlineSend> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotInlineSend> update, Promise<Unit> &&promise) {
td_->inline_queries_manager_->on_chosen_result(UserId(update->user_id_), Location(update->geo_), update->query_,
update->id_, std::move(update->msg_id_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotCallbackQuery> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotCallbackQuery> update, Promise<Unit> &&promise) {
td_->callback_queries_manager_->on_new_query(update->flags_, update->query_id_, UserId(update->user_id_),
DialogId(update->peer_), MessageId(ServerMessageId(update->msg_id_)),
std::move(update->data_), update->chat_instance_,
std::move(update->game_short_name_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateInlineBotCallbackQuery> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateInlineBotCallbackQuery> update,
+ Promise<Unit> &&promise) {
td_->callback_queries_manager_->on_new_inline_query(update->flags_, update->query_id_, UserId(update->user_id_),
std::move(update->msg_id_), std::move(update->data_),
update->chat_instance_, std::move(update->game_short_name_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateFavedStickers> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateFavedStickers> update, Promise<Unit> &&promise) {
td_->stickers_manager_->reload_favorite_stickers(true);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateSavedGifs> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateSavedGifs> update, Promise<Unit> &&promise) {
td_->animations_manager_->reload_saved_animations(true);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateConfig> update, bool /*force_apply*/) {
- send_closure(td_->config_manager_, &ConfigManager::request_config);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateConfig> update, Promise<Unit> &&promise) {
+ send_closure(td_->config_manager_, &ConfigManager::request_config, false);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePtsChanged> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePtsChanged> update, Promise<Unit> &&promise) {
set_pts(std::numeric_limits<int32>::max(), "updatePtsChanged").set_value(Unit());
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEncryption> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEncryption> update, Promise<Unit> &&promise) {
send_closure(td_->secret_chats_manager_, &SecretChatsManager::on_update_chat, std::move(update));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewEncryptedMessage> update, bool force_apply) {
- send_closure(td_->secret_chats_manager_, &SecretChatsManager::on_update_message, std::move(update), force_apply);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewEncryptedMessage> update, Promise<Unit> &&promise) {
+ auto qts = update->qts_;
+ add_pending_qts_update(std::move(update), qts, std::move(promise));
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEncryptedMessagesRead> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateEncryptedMessagesRead> update,
+ Promise<Unit> &&promise) {
td_->messages_manager_->read_secret_chat_outbox(SecretChatId(update->chat_id_), update->max_date_, update->date_);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePrivacy> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePrivacy> update, Promise<Unit> &&promise) {
send_closure(td_->privacy_manager_, &PrivacyManager::update_privacy, std::move(update));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewStickerSet> update, Promise<Unit> &&promise) {
+ td_->stickers_manager_->on_get_messages_sticker_set(StickerSetId(), std::move(update->stickerset_), true,
+ "updateNewStickerSet");
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewStickerSet> update, bool /*force_apply*/) {
- td_->stickers_manager_->on_get_messages_sticker_set(0, std::move(update->stickerset_), true);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateStickerSets> update, Promise<Unit> &&promise) {
+ auto sticker_type = get_sticker_type(update->masks_, update->emojis_);
+ td_->stickers_manager_->on_update_sticker_sets(sticker_type);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateStickerSets> update, bool /*force_apply*/) {
- td_->stickers_manager_->on_update_sticker_sets();
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateStickerSetsOrder> update, Promise<Unit> &&promise) {
+ auto sticker_type = get_sticker_type(update->masks_, update->emojis_);
+ td_->stickers_manager_->on_update_sticker_sets_order(sticker_type,
+ StickersManager::convert_sticker_set_ids(update->order_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateStickerSetsOrder> update, bool /*force_apply*/) {
- bool is_masks = (update->flags_ & telegram_api::updateStickerSetsOrder::MASKS_MASK) != 0;
- td_->stickers_manager_->on_update_sticker_sets_order(is_masks, update->order_);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMoveStickerSetToTop> update, Promise<Unit> &&promise) {
+ auto sticker_type = get_sticker_type(update->masks_, update->emojis_);
+ td_->stickers_manager_->on_update_move_sticker_set_to_top(sticker_type, StickerSetId(update->stickerset_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadFeaturedStickers> update, bool /*force_apply*/) {
- td_->stickers_manager_->reload_featured_sticker_sets(true);
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadFeaturedStickers> update,
+ Promise<Unit> &&promise) {
+ td_->stickers_manager_->reload_featured_sticker_sets(StickerType::Regular, true);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateRecentStickers> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateReadFeaturedEmojiStickers> update,
+ Promise<Unit> &&promise) {
+ td_->stickers_manager_->reload_featured_sticker_sets(StickerType::CustomEmoji, true);
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateRecentStickers> update, Promise<Unit> &&promise) {
td_->stickers_manager_->reload_recent_stickers(false, true);
td_->stickers_manager_->reload_recent_stickers(true, true);
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotShippingQuery> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotShippingQuery> update, Promise<Unit> &&promise) {
UserId user_id(update->user_id_);
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive shipping query from invalid " << user_id;
- return;
- }
- CHECK(update->shipping_address_ != nullptr);
+ } else {
+ CHECK(update->shipping_address_ != nullptr);
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateNewShippingQuery>(
- update->query_id_, td_->contacts_manager_->get_user_id_object(user_id, "updateNewShippingQuery"),
- update->payload_.as_slice().str(),
- get_shipping_address_object(get_shipping_address(
- std::move(update->shipping_address_))))); // TODO use convert_shipping_address
+ send_closure(
+ G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateNewShippingQuery>(
+ update->query_id_, td_->contacts_manager_->get_user_id_object(user_id, "updateNewShippingQuery"),
+ update->payload_.as_slice().str(),
+ get_address_object(get_address(std::move(update->shipping_address_))))); // TODO use convert_address
+ }
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotPrecheckoutQuery> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotPrecheckoutQuery> update, Promise<Unit> &&promise) {
UserId user_id(update->user_id_);
if (!user_id.is_valid()) {
LOG(ERROR) << "Receive pre-checkout query from invalid " << user_id;
- return;
+ } else if (update->total_amount_ <= 0 || !check_currency_amount(update->total_amount_)) {
+ LOG(ERROR) << "Receive pre-checkout query with invalid total amount " << update->total_amount_;
+ } else {
+ send_closure(
+ G()->td(), &Td::send_update,
+ make_tl_object<td_api::updateNewPreCheckoutQuery>(
+ update->query_id_, td_->contacts_manager_->get_user_id_object(user_id, "updateNewPreCheckoutQuery"),
+ update->currency_, update->total_amount_, update->payload_.as_slice().str(), update->shipping_option_id_,
+ get_order_info_object(get_order_info(std::move(update->info_)))));
}
-
- send_closure(G()->td(), &Td::send_update,
- make_tl_object<td_api::updateNewPreCheckoutQuery>(
- update->query_id_, td_->contacts_manager_->get_user_id_object(user_id, "updateNewPreCheckoutQuery"),
- update->currency_, update->total_amount_, update->payload_.as_slice().str(),
- update->shipping_option_id_, get_order_info_object(get_order_info(std::move(update->info_)))));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotWebhookJSON> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotWebhookJSON> update, Promise<Unit> &&promise) {
send_closure(G()->td(), &Td::send_update, make_tl_object<td_api::updateNewCustomEvent>(update->data_->data_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotWebhookJSONQuery> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotWebhookJSONQuery> update, Promise<Unit> &&promise) {
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateNewCustomQuery>(update->query_id_, update->data_->data_, update->timeout_));
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePhoneCall> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePhoneCall> update, Promise<Unit> &&promise) {
send_closure(G()->call_manager(), &CallManager::update_call, std::move(update));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePhoneCallSignalingData> update,
+ Promise<Unit> &&promise) {
+ send_closure(G()->call_manager(), &CallManager::update_call_signaling_data, update->phone_call_id_,
+ update->data_.as_slice().str());
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateContactsReset> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateGroupCallConnection> update, Promise<Unit> &&promise) {
+ if (update->presentation_) {
+ LOG(ERROR) << "Receive unexpected updateGroupCallConnection";
+ } else {
+ send_closure(G()->group_call_manager(), &GroupCallManager::on_update_group_call_connection,
+ std::move(update->params_->data_));
+ }
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateGroupCall> update, Promise<Unit> &&promise) {
+ DialogId dialog_id(ChatId(update->chat_id_));
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "updateGroupCall")) {
+ dialog_id = DialogId(ChannelId(update->chat_id_));
+ if (!td_->messages_manager_->have_dialog_force(dialog_id, "updateGroupCall")) {
+ dialog_id = DialogId();
+ }
+ }
+ send_closure(G()->group_call_manager(), &GroupCallManager::on_update_group_call, std::move(update->call_), dialog_id);
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateGroupCallParticipants> update,
+ Promise<Unit> &&promise) {
+ send_closure(G()->group_call_manager(), &GroupCallManager::on_update_group_call_participants,
+ InputGroupCallId(update->call_), std::move(update->participants_), update->version_, false);
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateContactsReset> update, Promise<Unit> &&promise) {
td_->contacts_manager_->on_update_contacts_reset();
+ promise.set_value(Unit());
}
-// unsupported updates
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateLangPackTooLong> update, Promise<Unit> &&promise) {
+ send_closure(G()->language_pack_manager(), &LanguagePackManager::on_language_pack_too_long,
+ std::move(update->lang_code_));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateLangPack> update, Promise<Unit> &&promise) {
+ send_closure(G()->language_pack_manager(), &LanguagePackManager::on_update_language_pack,
+ std::move(update->difference_));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateGeoLiveViewed> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_live_location_viewed(
+ {DialogId(update->peer_), MessageId(ServerMessageId(update->msg_id_))});
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessageExtendedMedia> update,
+ Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_message_extended_media(
+ {DialogId(update->peer_), MessageId(ServerMessageId(update->msg_id_))}, std::move(update->extended_media_));
+ promise.set_value(Unit());
+}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateLangPackTooLong> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessagePoll> update, Promise<Unit> &&promise) {
+ td_->poll_manager_->on_get_poll(PollId(update->poll_id_), std::move(update->poll_), std::move(update->results_),
+ "updateMessagePoll");
+ promise.set_value(Unit());
}
-void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateLangPack> update, bool /*force_apply*/) {
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateMessagePollVote> update, Promise<Unit> &&promise) {
+ auto qts = update->qts_;
+ add_pending_qts_update(std::move(update), qts, std::move(promise));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateNewScheduledMessage> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_get_message(std::move(update->message_), true, false, true, true, true,
+ "updateNewScheduledMessage");
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateDeleteScheduledMessages> update,
+ Promise<Unit> &&promise) {
+ vector<ScheduledServerMessageId> message_ids = transform(update->messages_, [](int32 scheduled_server_message_id) {
+ return ScheduledServerMessageId(scheduled_server_message_id);
+ });
+
+ td_->messages_manager_->on_update_delete_scheduled_messages(DialogId(update->peer_), std::move(message_ids));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateLoginToken> update, Promise<Unit> &&promise) {
+ LOG(INFO) << "Ignore updateLoginToken after authorization";
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotStopped> update, Promise<Unit> &&promise) {
+ auto qts = update->qts_;
+ add_pending_qts_update(std::move(update), qts, std::move(promise));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChatParticipant> update, Promise<Unit> &&promise) {
+ auto qts = update->qts_;
+ add_pending_qts_update(std::move(update), qts, std::move(promise));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelParticipant> update, Promise<Unit> &&promise) {
+ auto qts = update->qts_;
+ add_pending_qts_update(std::move(update), qts, std::move(promise));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateBotChatInviteRequester> update,
+ Promise<Unit> &&promise) {
+ auto qts = update->qts_;
+ add_pending_qts_update(std::move(update), qts, std::move(promise));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateTheme> update, Promise<Unit> &&promise) {
+ td_->theme_manager_->on_update_theme(std::move(update->theme_), std::move(promise));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updatePendingJoinRequests> update, Promise<Unit> &&promise) {
+ td_->messages_manager_->on_update_dialog_pending_join_requests(DialogId(update->peer_), update->requests_pending_,
+ std::move(update->recent_requesters_));
+ promise.set_value(Unit());
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateSavedRingtones> update, Promise<Unit> &&promise) {
+ td_->notification_settings_manager_->reload_saved_ringtones(std::move(promise));
+}
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateTranscribedAudio> update, Promise<Unit> &&promise) {
+ auto it = pending_audio_transcriptions_.find(update->transcription_id_);
+ if (it == pending_audio_transcriptions_.end()) {
+ return promise.set_value(Unit());
+ }
+ // flags_, dialog_id_ and message_id_ must not be used
+ if (!update->pending_) {
+ auto on_update = std::move(it->second);
+ pending_audio_transcriptions_.erase(it);
+ pending_audio_transcription_timeout_.cancel_timeout(update->transcription_id_);
+ on_update(std::move(update));
+ } else {
+ it->second(std::move(update));
+ }
+ promise.set_value(Unit());
+}
+
+// unsupported updates
+
+void UpdatesManager::on_update(tl_object_ptr<telegram_api::updateChannelPinnedTopic> update, Promise<Unit> &&promise) {
+ promise.set_value(Unit());
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.h b/protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.h
index 9602fee7ef..83fa8ad4c1 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/UpdatesManager.h
@@ -1,273 +1,567 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ChatId.h"
#include "td/telegram/DialogId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/InputGroupCallId.h"
+#include "td/telegram/MessageId.h"
#include "td/telegram/PtsManager.h"
-
-#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/actor/actor.h"
+#include "td/actor/MultiTimeout.h"
#include "td/actor/Timeout.h"
-#include "td/utils/common.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+#include "td/utils/tl_storers.h"
+#include "td/utils/TlStorerToString.h"
+
+#include <functional>
#include <map>
-#include <unordered_set>
namespace td {
+extern int VERBOSITY_NAME(get_difference);
+
class Td;
-class UpdatesManager : public Actor {
+class dummyUpdate final : public telegram_api::Update {
public:
- UpdatesManager(Td *td, ActorShared<> parent);
+ static constexpr int32 ID = 1234567891;
+ int32 get_id() const final {
+ return ID;
+ }
- void on_get_updates(tl_object_ptr<telegram_api::Updates> &&updates_ptr);
+ void store(TlStorerUnsafe &s) const final {
+ UNREACHABLE();
+ }
- void on_get_updates_state(tl_object_ptr<telegram_api::updates_state> &&state, const char *source);
+ void store(TlStorerCalcLength &s) const final {
+ UNREACHABLE();
+ }
- void on_get_difference(tl_object_ptr<telegram_api::updates_Difference> &&difference_ptr);
+ void store(TlStorerToString &s, const char *field_name) const final {
+ s.store_class_begin(field_name, "dummyUpdate");
+ s.store_class_end();
+ }
+};
- std::unordered_set<int64> get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr);
+class updateSentMessage final : public telegram_api::Update {
+ public:
+ int64 random_id_;
+ MessageId message_id_;
+ int32 date_;
+ int32 ttl_period_;
- vector<const tl_object_ptr<telegram_api::Message> *> get_new_messages(const telegram_api::Updates *updates_ptr);
+ updateSentMessage(int64 random_id, MessageId message_id, int32 date, int32 ttl_period)
+ : random_id_(random_id), message_id_(message_id), date_(date), ttl_period_(ttl_period) {
+ }
- vector<DialogId> get_chats(const telegram_api::Updates *updates_ptr);
+ static constexpr int32 ID = 1234567890;
+ int32 get_id() const final {
+ return ID;
+ }
- void get_difference(const char *source);
+ void store(TlStorerUnsafe &s) const final {
+ UNREACHABLE();
+ }
- void schedule_get_difference(const char *source);
+ void store(TlStorerCalcLength &s) const final {
+ UNREACHABLE();
+ }
- void init_state();
+ void store(TlStorerToString &s, const char *field_name) const final {
+ s.store_class_begin(field_name, "updateSentMessage");
+ s.store_field("random_id", random_id_);
+ s.store_field("message_id", message_id_.get());
+ s.store_field("date", date_);
+ s.store_field("ttl_period", ttl_period_);
+ s.store_class_end();
+ }
+};
- void ping_server();
+class UpdatesManager final : public Actor {
+ public:
+ UpdatesManager(Td *td, ActorShared<> parent);
- void on_server_pong(tl_object_ptr<telegram_api::updates_state> &&state);
+ void on_get_updates(tl_object_ptr<telegram_api::Updates> &&updates_ptr, Promise<Unit> &&promise);
- int32 get_pts() const {
- return pts_manager_.mem_pts();
- }
- int32 get_qts() const {
- return qts_;
- }
- int32 get_date() const {
- return date_;
- }
+ void add_pending_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 new_pts, int32 pts_count,
+ double receive_time, Promise<Unit> &&promise, const char *source);
- string get_state() const; // for debug purposes only
+ static FlatHashSet<int64> get_sent_messages_random_ids(const telegram_api::Updates *updates_ptr);
- Promise<> set_pts(int32 pts, const char *source) TD_WARN_UNUSED_RESULT;
+ static const telegram_api::Message *get_message_by_random_id(const telegram_api::Updates *updates_ptr,
+ DialogId dialog_id, int64 random_id);
- void set_qts(int32 qts);
+ static vector<const tl_object_ptr<telegram_api::Message> *> get_new_messages(
+ const telegram_api::Updates *updates_ptr);
- static const double MAX_UNFILLED_GAP_TIME;
+ static vector<InputGroupCallId> get_update_new_group_call_ids(const telegram_api::Updates *updates_ptr);
- static void fill_pts_gap(void *td);
+ static string extract_join_group_call_presentation_params(telegram_api::Updates *updates_ptr);
+
+ static vector<DialogId> get_update_notify_settings_dialog_ids(const telegram_api::Updates *updates_ptr);
+
+ static vector<DialogId> get_chat_dialog_ids(const telegram_api::Updates *updates_ptr);
+
+ static int32 get_update_edit_message_pts(const telegram_api::Updates *updates_ptr, FullMessageId full_message_id);
+
+ using TranscribedAudioHandler =
+ std::function<void(Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>>)>;
+ void subscribe_to_transcribed_audio_updates(int64 transcription_id, TranscribedAudioHandler on_update);
+
+ void get_difference(const char *source);
+
+ void schedule_get_difference(const char *source);
+
+ void ping_server();
bool running_get_difference() const {
return running_get_difference_;
}
+ void timeout_expired() final;
+
private:
static constexpr int32 FORCED_GET_DIFFERENCE_PTS_DIFF = 100000;
+ static constexpr int32 GAP_TIMEOUT_UPDATE_COUNT = 20;
+ static constexpr double MAX_UNFILLED_GAP_TIME = 0.7;
+ static constexpr double MAX_PTS_SAVE_DELAY = 0.05;
+ static constexpr double UPDATE_APPLY_WARNING_TIME = 0.25;
+ static constexpr bool DROP_PTS_UPDATES = false;
+ static constexpr const char *AFTER_GET_DIFFERENCE_SOURCE = "after get difference";
+ static constexpr int32 AUDIO_TRANSCRIPTION_TIMEOUT = 60;
friend class OnUpdate;
- class PendingUpdates {
+ class PendingPtsUpdate {
+ public:
+ tl_object_ptr<telegram_api::Update> update;
+ int32 pts;
+ int32 pts_count;
+ double receive_time;
+ Promise<Unit> promise;
+
+ PendingPtsUpdate(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count, double receive_time,
+ Promise<Unit> &&promise)
+ : update(std::move(update))
+ , pts(pts)
+ , pts_count(pts_count)
+ , receive_time(receive_time)
+ , promise(std::move(promise)) {
+ }
+ };
+
+ class PendingSeqUpdates {
public:
int32 seq_begin;
int32 seq_end;
int32 date;
+ double receive_time;
vector<tl_object_ptr<telegram_api::Update>> updates;
-
- PendingUpdates(int32 seq_begin, int32 seq_end, int32 date, vector<tl_object_ptr<telegram_api::Update>> &&updates)
- : seq_begin(seq_begin), seq_end(seq_end), date(date), updates(std::move(updates)) {
+ Promise<Unit> promise;
+
+ PendingSeqUpdates(int32 seq_begin, int32 seq_end, int32 date, double receive_time,
+ vector<tl_object_ptr<telegram_api::Update>> &&updates, Promise<Unit> &&promise)
+ : seq_begin(seq_begin)
+ , seq_end(seq_end)
+ , date(date)
+ , receive_time(receive_time)
+ , updates(std::move(updates))
+ , promise(std::move(promise)) {
}
};
+ class PendingQtsUpdate {
+ public:
+ double receive_time = 0.0;
+ tl_object_ptr<telegram_api::Update> update;
+ vector<Promise<Unit>> promises;
+ };
+
Td *td_;
ActorShared<> parent_;
+ int32 ref_cnt_ = 1;
PtsManager pts_manager_;
- int32 qts_ = 0;
+ PtsManager qts_manager_;
int32 date_ = 0;
int32 seq_ = 0;
string date_source_ = "nowhere";
- std::multimap<int32, PendingUpdates> postponed_updates_; // updates received during getDifference
- std::multimap<int32, PendingUpdates> pending_seq_updates_; // updates with too big seq
+ double last_pts_save_time_ = 0;
+ double last_qts_save_time_ = 0;
+ int32 pending_pts_ = 0;
+ int32 pending_qts_ = 0;
+
+ int64 being_processed_updates_ = 0;
+
+ int32 short_update_date_ = 0;
+
+ int32 accumulated_pts_count_ = 0;
+ int32 accumulated_pts_ = -1;
+ double last_pts_jump_warning_time_ = 0;
+ double last_pts_gap_time_ = 0;
+
+ std::multimap<int32, PendingPtsUpdate> pending_pts_updates_;
+ std::multimap<int32, PendingPtsUpdate> postponed_pts_updates_;
+
+ std::multimap<int32, PendingSeqUpdates> postponed_updates_; // updates received during getDifference
+ std::multimap<int32, PendingSeqUpdates> pending_seq_updates_; // updates with too big seq
+
+ std::map<int32, PendingQtsUpdate> pending_qts_updates_; // updates with too big qts
+
+ Timeout pts_gap_timeout_;
Timeout seq_gap_timeout_;
+ Timeout qts_gap_timeout_;
+
int32 retry_time_ = 1;
Timeout retry_timeout_;
+ double next_data_reload_time_ = 0.0;
+ Timeout data_reload_timeout_;
+
bool running_get_difference_ = false;
int32 last_get_difference_pts_ = 0;
+ int32 last_get_difference_qts_ = 0;
+ int32 min_postponed_update_pts_ = 0;
+ int32 min_postponed_update_qts_ = 0;
+ double get_difference_start_time_ = 0; // time from which we started to get difference without success
- class State {
- public:
- enum class Type {
- General,
- RunningGetUpdatesState,
- RunningGetDifference,
- ApplyingDifference,
- ApplyingDifferenceSlice,
- ApplyingUpdates,
- ApplyingSeqUpdates
- };
- Type type = Type::General;
- int32 pts = -1;
- int32 qts = -1;
- int32 date = -1;
- };
+ FlatHashMap<int64, TranscribedAudioHandler> pending_audio_transcriptions_;
+ MultiTimeout pending_audio_transcription_timeout_{"PendingAudioTranscriptionTimeout"};
- State state_; // for debug purposes only
+ void start_up() final;
- void set_state(State::Type type); // for debug purposes only
+ void tear_down() final;
- void tear_down() override;
+ void hangup_shared() final;
+ void hangup() final;
+
+ ActorShared<UpdatesManager> create_reference();
+
+ int32 get_pts() const {
+ return pts_manager_.mem_pts();
+ }
+ int32 get_qts() const {
+ return qts_manager_.mem_pts();
+ }
+ int32 get_date() const {
+ return date_;
+ }
+
+ Promise<> set_pts(int32 pts, const char *source) TD_WARN_UNUSED_RESULT;
Promise<> add_pts(int32 pts);
void on_pts_ack(PtsManager::PtsId ack_token);
void save_pts(int32 pts);
+ Promise<> add_qts(int32 qts);
+ void on_qts_ack(PtsManager::PtsId ack_token);
+ void save_qts(int32 qts);
+
void set_date(int32 date, bool from_update, string date_source);
- static tl_object_ptr<td_api::ChatAction> convert_send_message_action(
- tl_object_ptr<telegram_api::SendMessageAction> action);
+ int32 get_short_update_date() const;
+
+ void init_state();
+
+ void on_get_updates_state(tl_object_ptr<telegram_api::updates_state> &&state, const char *source);
+
+ void on_server_pong(tl_object_ptr<telegram_api::updates_state> &&state);
+
+ void on_get_difference(tl_object_ptr<telegram_api::updates_Difference> &&difference_ptr);
void process_get_difference_updates(vector<tl_object_ptr<telegram_api::Message>> &&new_messages,
vector<tl_object_ptr<telegram_api::EncryptedMessage>> &&new_encrypted_messages,
- int32 qts, vector<tl_object_ptr<telegram_api::Update>> &&other_updates);
+ vector<tl_object_ptr<telegram_api::Update>> &&other_updates);
+
+ void on_pending_update(tl_object_ptr<telegram_api::Update> update, int32 seq, Promise<Unit> &&promise,
+ const char *source);
- void on_pending_update(tl_object_ptr<telegram_api::Update> update, int32 seq, const char *source);
+ void add_pending_qts_update(tl_object_ptr<telegram_api::Update> &&update, int32 qts, Promise<Unit> &&promise);
void on_pending_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, int32 seq_begin, int32 seq_end,
- int32 date, const char *source);
+ int32 date, double receive_time, Promise<Unit> &&promise, const char *source);
+
+ void on_pending_updates_processed(Result<Unit> result, Promise<Unit> promise);
+
+ void process_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, bool force_apply,
+ Promise<Unit> &&promise);
+
+ void postpone_pts_update(tl_object_ptr<telegram_api::Update> &&update, int32 pts, int32 pts_count,
+ double receive_time, Promise<Unit> &&promise);
+
+ void process_pts_update(tl_object_ptr<telegram_api::Update> &&update);
+
+ void process_seq_updates(int32 seq_end, int32 date, vector<tl_object_ptr<telegram_api::Update>> &&updates,
+ Promise<Unit> &&promise);
+
+ void process_qts_update(tl_object_ptr<telegram_api::Update> &&update_ptr, int32 qts, Promise<Unit> &&promise);
- void process_updates(vector<tl_object_ptr<telegram_api::Update>> &&updates, bool force_apply);
+ void process_all_pending_pts_updates();
- void process_seq_updates(int32 seq_end, int32 date, vector<tl_object_ptr<telegram_api::Update>> &&updates);
+ void drop_all_pending_pts_updates();
+
+ void process_postponed_pts_updates();
+
+ void process_pending_pts_updates();
void process_pending_seq_updates();
+ void process_pending_qts_updates();
+
+ static void fill_pts_gap(void *td);
+
static void fill_seq_gap(void *td);
+ static void fill_qts_gap(void *td);
+
static void fill_get_difference_gap(void *td);
static void fill_gap(void *td, const char *source);
+ static void on_pending_audio_transcription_timeout_callback(void *td, int64 transcription_id);
+
+ void set_pts_gap_timeout(double timeout);
+
void set_seq_gap_timeout(double timeout);
- void on_failed_get_difference();
+ void set_qts_gap_timeout(double timeout);
+
+ void run_get_difference(bool is_recursive, const char *source);
- void before_get_difference();
+ void on_failed_get_updates_state(Status &&error);
+
+ void on_failed_get_difference(Status &&error);
+
+ void before_get_difference(bool is_initial);
void after_get_difference();
+ void schedule_data_reload();
+
+ static void try_reload_data_static(void *td);
+
+ void try_reload_data();
+
+ static vector<int32> get_update_ids(const telegram_api::Updates *updates_ptr);
+
+ static bool have_update_pts_changed(const vector<tl_object_ptr<telegram_api::Update>> &updates);
+
+ static bool check_pts_update_dialog_id(DialogId dialog_id);
+
+ static bool check_pts_update(const tl_object_ptr<telegram_api::Update> &update);
+
+ static bool is_pts_update(const telegram_api::Update *update);
+
+ static int32 get_update_pts(const telegram_api::Update *update);
+
+ static bool is_qts_update(const telegram_api::Update *update);
+
+ static int32 get_update_qts(const telegram_api::Update *update);
+
+ static bool is_channel_pts_update(const telegram_api::Update *update);
+
+ static const vector<tl_object_ptr<telegram_api::Update>> *get_updates(const telegram_api::Updates *updates_ptr);
+
+ static vector<tl_object_ptr<telegram_api::Update>> *get_updates(telegram_api::Updates *updates_ptr);
+
+ void on_pending_audio_transcription_failed(int64 transcription_id, Status &&error);
+
+ bool is_acceptable_user(UserId user_id) const;
+
+ bool is_acceptable_chat(ChatId chat_id) const;
+
+ bool is_acceptable_channel(ChannelId channel_id) const;
+
+ bool is_acceptable_peer(const tl_object_ptr<telegram_api::Peer> &peer) const;
+
bool is_acceptable_message_entities(const vector<tl_object_ptr<telegram_api::MessageEntity>> &message_entities) const;
+ bool is_acceptable_reply_markup(const tl_object_ptr<telegram_api::ReplyMarkup> &reply_markup) const;
+
+ bool is_acceptable_message_reply_header(
+ const telegram_api::object_ptr<telegram_api::messageReplyHeader> &header) const;
+
+ bool is_acceptable_message_forward_header(
+ const telegram_api::object_ptr<telegram_api::messageFwdHeader> &header) const;
+
bool is_acceptable_message(const telegram_api::Message *message_ptr) const;
bool is_acceptable_update(const telegram_api::Update *update) const;
- void on_update(tl_object_ptr<telegram_api::updateNewMessage> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateMessageID> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateReadMessagesContents> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateEditMessage> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateDeleteMessages> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateReadHistoryInbox> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateReadHistoryOutbox> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateNotifySettings> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateNewMessage> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateMessageID> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateReadMessagesContents> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateEditMessage> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateDeleteMessages> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateReadHistoryInbox> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateReadHistoryOutbox> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateNotifySettings> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updatePeerSettings> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updatePeerHistoryTTL> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updatePeerLocated> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateWebPage> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChannelWebPage> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateMessageReactions> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateRecentReactions> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateAttachMenuBots> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateWebViewResultSent> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateFolderPeers> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateUserTyping> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChatUserTyping> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChannelUserTyping> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateEncryptedChatTyping> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateUserStatus> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateUserName> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateUserPhone> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateUserPhoto> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateUserEmojiStatus> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateRecentEmojiStatuses> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updatePeerBlocked> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateBotCommands> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateBotMenuButton> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateChatParticipants> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChatParticipantAdd> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChatParticipantAdmin> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChatParticipantDelete> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateChatDefaultBannedRights> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateServiceNotification> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateDcOptions> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateChat> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateNewChannelMessage> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateReadChannelInbox> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateReadChannelOutbox> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChannelReadMessagesContents> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChannelTooLong> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChannel> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateEditChannelMessage> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateDeleteChannelMessages> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChannelMessageViews> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChannelMessageForwards> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChannelAvailableMessages> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateReadChannelDiscussionInbox> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateReadChannelDiscussionOutbox> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updatePinnedMessages> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updatePinnedChannelMessages> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateDraftMessage> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateDialogPinned> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updatePinnedDialogs> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateDialogUnreadMark> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateDialogFilter> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateDialogFilters> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateDialogFilterOrder> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateBotInlineQuery> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateBotInlineSend> update, Promise<Unit> &&promise);
+
+ void on_update(tl_object_ptr<telegram_api::updateBotCallbackQuery> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateInlineBotCallbackQuery> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateWebPage> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateChannelWebPage> update, bool force_apply);
+ void on_update(tl_object_ptr<telegram_api::updateFavedStickers> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateUserTyping> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateChatUserTyping> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateEncryptedChatTyping> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateSavedGifs> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateUserStatus> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateUserName> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateUserPhone> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateUserPhoto> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateUserBlocked> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateContactLink> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateConfig> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateChatParticipants> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateChatParticipantAdd> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateChatParticipantAdmin> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateChatParticipantDelete> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateChatAdmins> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updatePtsChanged> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateServiceNotification> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateContactRegistered> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updatePrivacy> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateDcOptions> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateEncryption> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateNewEncryptedMessage> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateEncryptedMessagesRead> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateNewChannelMessage> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateReadChannelInbox> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateReadChannelOutbox> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateChannelReadMessagesContents> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateChannelTooLong> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateChannel> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateEditChannelMessage> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateDeleteChannelMessages> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateChannelMessageViews> update, bool force_apply);
- void on_update(tl_object_ptr<telegram_api::updateChannelPinnedMessage> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateChannelAvailableMessages> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateNewStickerSet> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateStickerSets> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateStickerSetsOrder> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateMoveStickerSetToTop> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateReadFeaturedStickers> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateReadFeaturedEmojiStickers> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateRecentStickers> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateDraftMessage> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateBotShippingQuery> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateBotPrecheckoutQuery> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateDialogPinned> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updatePinnedDialogs> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateBotWebhookJSON> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateBotWebhookJSONQuery> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateBotInlineQuery> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateBotInlineSend> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updatePhoneCall> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updatePhoneCallSignalingData> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateBotCallbackQuery> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateInlineBotCallbackQuery> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateGroupCallConnection> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateGroupCall> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateGroupCallParticipants> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateFavedStickers> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateContactsReset> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateSavedGifs> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateLangPackTooLong> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateLangPack> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateConfig> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateGeoLiveViewed> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateMessageExtendedMedia> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updatePtsChanged> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateMessagePoll> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateMessagePollVote> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updatePrivacy> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateNewScheduledMessage> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateDeleteScheduledMessages> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateEncryption> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateNewEncryptedMessage> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateEncryptedMessagesRead> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateLoginToken> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateNewStickerSet> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateStickerSets> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateStickerSetsOrder> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateReadFeaturedStickers> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateRecentStickers> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateBotStopped> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChatParticipant> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateChannelParticipant> update, Promise<Unit> &&promise);
+ void on_update(tl_object_ptr<telegram_api::updateBotChatInviteRequester> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateBotShippingQuery> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateBotPrecheckoutQuery> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateTheme> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateBotWebhookJSON> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateBotWebhookJSONQuery> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updatePendingJoinRequests> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updatePhoneCall> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateSavedRingtones> update, Promise<Unit> &&promise);
- void on_update(tl_object_ptr<telegram_api::updateContactsReset> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateTranscribedAudio> update, Promise<Unit> &&promise);
// unsupported updates
- void on_update(tl_object_ptr<telegram_api::updateLangPackTooLong> update, bool /*force_apply*/);
- void on_update(tl_object_ptr<telegram_api::updateLangPack> update, bool /*force_apply*/);
+ void on_update(tl_object_ptr<telegram_api::updateChannelPinnedTopic> update, Promise<Unit> &&promise);
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/UserId.h b/protocols/Telegram/tdlib/td/td/telegram/UserId.h
index d7088b8645..698b5b03e4 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/UserId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/UserId.h
@@ -1,35 +1,57 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/Version.h"
+
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/StringBuilder.h"
-#include <functional>
#include <type_traits>
namespace td {
class UserId {
- int32 id = 0;
+ int64 id = 0;
public:
+ static constexpr int64 MAX_USER_ID = (static_cast<int64>(1) << 40) - 1;
+
UserId() = default;
- explicit UserId(int32 user_id) : id(user_id) {
+ explicit UserId(int64 user_id) : id(user_id) {
}
- template <class T, typename = std::enable_if_t<std::is_convertible<T, int32>::value>>
+ template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
UserId(T user_id) = delete;
+ static vector<UserId> get_user_ids(const vector<int64> &input_user_ids) {
+ vector<UserId> user_ids;
+ user_ids.reserve(input_user_ids.size());
+ for (auto &input_user_id : input_user_ids) {
+ user_ids.emplace_back(input_user_id);
+ }
+ return user_ids;
+ }
+
+ static vector<int64> get_input_user_ids(const vector<UserId> &user_ids) {
+ vector<int64> input_user_ids;
+ input_user_ids.reserve(user_ids.size());
+ for (auto &user_id : user_ids) {
+ input_user_ids.emplace_back(user_id.get());
+ }
+ return input_user_ids;
+ }
+
bool is_valid() const {
- return id > 0;
+ return 0 < id && id <= MAX_USER_ID;
}
- int32 get() const {
+ int64 get() const {
return id;
}
@@ -43,18 +65,22 @@ class UserId {
template <class StorerT>
void store(StorerT &storer) const {
- storer.store_int(id);
+ storer.store_long(id);
}
template <class ParserT>
void parse(ParserT &parser) {
- id = parser.fetch_int();
+ if (parser.version() >= static_cast<int32>(Version::Support64BitIds)) {
+ id = parser.fetch_long();
+ } else {
+ id = parser.fetch_int();
+ }
}
};
struct UserIdHash {
- std::size_t operator()(UserId user_id) const {
- return std::hash<int32>()(user_id.get());
+ uint32 operator()(UserId user_id) const {
+ return Hash<int64>()(user_id.get());
}
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Usernames.cpp b/protocols/Telegram/tdlib/td/td/telegram/Usernames.cpp
new file mode 100644
index 0000000000..eb8cf45c0b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Usernames.cpp
@@ -0,0 +1,222 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Usernames.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/utf8.h"
+
+namespace td {
+
+Usernames::Usernames(string &&first_username, vector<telegram_api::object_ptr<telegram_api::username>> &&usernames) {
+ if (usernames.empty()) {
+ if (!first_username.empty()) {
+ active_usernames_.push_back(std::move(first_username));
+ editable_username_pos_ = 0;
+ }
+ return;
+ }
+
+ if (!first_username.empty()) {
+ LOG(ERROR) << "Receive first username " << first_username << " with " << to_string(usernames);
+ return;
+ }
+ bool was_editable = false;
+ for (auto &username : usernames) {
+ if (username->username_.empty()) {
+ LOG(ERROR) << "Receive empty username in " << to_string(usernames);
+ return;
+ }
+ if (username->editable_) {
+ if (was_editable) {
+ LOG(ERROR) << "Receive two editable usernames in " << to_string(usernames);
+ return;
+ }
+ if (!username->active_) {
+ LOG(ERROR) << "Receive disabled editable usernames in " << to_string(usernames);
+ return;
+ }
+ was_editable = true;
+ }
+ }
+
+ for (size_t i = 0; i < usernames.size(); i++) {
+ if (usernames[i]->active_) {
+ active_usernames_.push_back(std::move(usernames[i]->username_));
+ if (usernames[i]->editable_) {
+ editable_username_pos_ = narrow_cast<int32>(i);
+ }
+ } else {
+ disabled_usernames_.push_back(std::move(usernames[i]->username_));
+ }
+ }
+ CHECK(has_editable_username() == was_editable);
+}
+
+tl_object_ptr<td_api::usernames> Usernames::get_usernames_object() const {
+ if (is_empty()) {
+ return nullptr;
+ }
+ return make_tl_object<td_api::usernames>(
+ vector<string>(active_usernames_), vector<string>(disabled_usernames_),
+ has_editable_username() ? active_usernames_[editable_username_pos_] : string());
+}
+
+Usernames Usernames::change_editable_username(string &&new_username) const {
+ Usernames result = *this;
+ if (has_editable_username()) {
+ if (new_username.empty()) {
+ result.active_usernames_.erase(result.active_usernames_.begin() + editable_username_pos_);
+ result.editable_username_pos_ = -1;
+ } else {
+ // keep position
+ result.active_usernames_[editable_username_pos_] = std::move(new_username);
+ }
+ } else if (!new_username.empty()) {
+ // add to the beginning
+ result.active_usernames_.insert(result.active_usernames_.begin(), std::move(new_username));
+ result.editable_username_pos_ = 0;
+ }
+ return result;
+}
+
+bool Usernames::can_toggle(const string &username) const {
+ if (td::contains(active_usernames_, username)) {
+ return !has_editable_username() || active_usernames_[editable_username_pos_] != username;
+ }
+ if (td::contains(disabled_usernames_, username)) {
+ return true;
+ }
+ return false;
+}
+
+Usernames Usernames::toggle(const string &username, bool is_active) const {
+ Usernames result = *this;
+ for (size_t i = 0; i < disabled_usernames_.size(); i++) {
+ if (disabled_usernames_[i] == username) {
+ if (is_active) {
+ // activate the username
+ result.disabled_usernames_.erase(result.disabled_usernames_.begin() + i);
+ result.active_usernames_.push_back(username);
+ // editable username position wasn't changed
+ }
+ return result;
+ }
+ }
+ for (size_t i = 0; i < active_usernames_.size(); i++) {
+ if (active_usernames_[i] == username) {
+ if (!is_active) {
+ // disable the username
+ result.active_usernames_.erase(result.active_usernames_.begin() + i);
+ result.disabled_usernames_.insert(result.disabled_usernames_.begin(), username);
+ if (result.has_editable_username() && i <= static_cast<size_t>(result.editable_username_pos_)) {
+ CHECK(i != static_cast<size_t>(result.editable_username_pos_));
+ CHECK(result.editable_username_pos_ > 0);
+ result.editable_username_pos_--;
+ }
+ }
+ return result;
+ }
+ }
+ UNREACHABLE();
+ return result;
+}
+
+Usernames Usernames::deactivate_all() const {
+ Usernames result;
+ for (size_t i = 0; i < active_usernames_.size(); i++) {
+ if (i == static_cast<size_t>(editable_username_pos_)) {
+ result.active_usernames_.push_back(active_usernames_[i]);
+ result.editable_username_pos_ = 0;
+ } else {
+ result.disabled_usernames_.push_back(active_usernames_[i]);
+ }
+ }
+ append(result.disabled_usernames_, disabled_usernames_);
+ CHECK(result.has_editable_username() == has_editable_username());
+ return result;
+}
+
+bool Usernames::can_reorder_to(const vector<string> &new_username_order) const {
+ if (new_username_order.size() != active_usernames_.size()) {
+ return false;
+ }
+ FlatHashSet<string> active_usernames;
+ for (auto &username : active_usernames_) {
+ active_usernames.insert(username);
+ }
+ for (auto &username : new_username_order) {
+ auto it = active_usernames.find(username);
+ if (it == active_usernames.end()) {
+ return false;
+ }
+ active_usernames.erase(it);
+ }
+ CHECK(active_usernames.empty());
+ return true;
+}
+
+Usernames Usernames::reorder_to(vector<string> &&new_username_order) const {
+ Usernames result;
+ result.active_usernames_ = std::move(new_username_order);
+ result.disabled_usernames_ = disabled_usernames_;
+ if (has_editable_username()) {
+ const string &editable_username = active_usernames_[editable_username_pos_];
+ for (size_t i = 0; i < result.active_usernames_.size(); i++) {
+ if (result.active_usernames_[i] == editable_username) {
+ result.editable_username_pos_ = narrow_cast<int32>(i);
+ break;
+ }
+ }
+ CHECK(result.has_editable_username());
+ }
+ return result;
+}
+
+void Usernames::check_utf8_validness() {
+ for (auto &username : active_usernames_) {
+ if (!check_utf8(username)) {
+ LOG(ERROR) << "Have invalid active username \"" << username << '"';
+ *this = Usernames();
+ return;
+ }
+ }
+ for (auto &username : disabled_usernames_) {
+ if (!check_utf8(username)) {
+ LOG(ERROR) << "Have invalid disabled username \"" << username << '"';
+ *this = Usernames();
+ return;
+ }
+ }
+}
+
+bool operator==(const Usernames &lhs, const Usernames &rhs) {
+ return lhs.active_usernames_ == rhs.active_usernames_ && lhs.disabled_usernames_ == rhs.disabled_usernames_ &&
+ lhs.editable_username_pos_ == rhs.editable_username_pos_;
+}
+
+bool operator!=(const Usernames &lhs, const Usernames &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Usernames &usernames) {
+ string_builder << "Usernames[";
+ if (usernames.has_editable_username()) {
+ string_builder << usernames.active_usernames_[usernames.editable_username_pos_];
+ }
+ if (!usernames.active_usernames_.empty()) {
+ string_builder << ", active " << usernames.active_usernames_;
+ }
+ if (!usernames.disabled_usernames_.empty()) {
+ string_builder << ", disabled " << usernames.disabled_usernames_;
+ }
+ return string_builder << ']';
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Usernames.h b/protocols/Telegram/tdlib/td/td/telegram/Usernames.h
new file mode 100644
index 0000000000..95f5182eb7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Usernames.h
@@ -0,0 +1,142 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class Usernames {
+ vector<string> active_usernames_;
+ vector<string> disabled_usernames_;
+ int32 editable_username_pos_ = -1;
+
+ friend bool operator==(const Usernames &lhs, const Usernames &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const Usernames &usernames);
+
+ void check_utf8_validness();
+
+ public:
+ Usernames() = default;
+
+ Usernames(string &&first_username, vector<telegram_api::object_ptr<telegram_api::username>> &&usernames);
+
+ td_api::object_ptr<td_api::usernames> get_usernames_object() const;
+
+ bool is_empty() const {
+ return editable_username_pos_ == -1 && active_usernames_.empty() && disabled_usernames_.empty();
+ }
+
+ string get_first_username() const {
+ if (!has_first_username()) {
+ return string();
+ }
+ return active_usernames_[0];
+ }
+
+ bool has_first_username() const {
+ return !active_usernames_.empty();
+ }
+
+ string get_editable_username() const {
+ if (!has_editable_username()) {
+ return string();
+ }
+ return active_usernames_[editable_username_pos_];
+ }
+
+ bool has_editable_username() const {
+ return editable_username_pos_ != -1;
+ }
+
+ const vector<string> &get_active_usernames() const {
+ return active_usernames_;
+ }
+
+ Usernames change_editable_username(string &&new_username) const;
+
+ bool can_toggle(const string &username) const;
+
+ Usernames toggle(const string &username, bool is_active) const;
+
+ Usernames deactivate_all() const;
+
+ bool can_reorder_to(const vector<string> &new_username_order) const;
+
+ Usernames reorder_to(vector<string> &&new_username_order) const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ CHECK(!is_empty());
+ bool has_many_active_usernames = active_usernames_.size() > 1;
+ bool has_disabled_usernames = !disabled_usernames_.empty();
+ bool has_editable_username = editable_username_pos_ != -1;
+ bool has_active_usernames = !active_usernames_.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_many_active_usernames);
+ STORE_FLAG(has_disabled_usernames);
+ STORE_FLAG(has_editable_username);
+ STORE_FLAG(has_active_usernames);
+ END_STORE_FLAGS();
+ if (has_many_active_usernames) {
+ td::store(active_usernames_, storer);
+ if (has_editable_username) {
+ td::store(editable_username_pos_, storer);
+ }
+ } else if (has_active_usernames) {
+ td::store(active_usernames_[0], storer);
+ }
+ if (has_disabled_usernames) {
+ td::store(disabled_usernames_, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ bool has_many_active_usernames;
+ bool has_disabled_usernames;
+ bool has_editable_username;
+ bool has_active_usernames;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_many_active_usernames);
+ PARSE_FLAG(has_disabled_usernames);
+ PARSE_FLAG(has_editable_username);
+ PARSE_FLAG(has_active_usernames);
+ END_PARSE_FLAGS();
+ if (has_many_active_usernames) {
+ td::parse(active_usernames_, parser);
+ if (has_editable_username) {
+ td::parse(editable_username_pos_, parser);
+ CHECK(static_cast<size_t>(editable_username_pos_) < active_usernames_.size());
+ }
+ } else if (has_active_usernames) {
+ active_usernames_.resize(1);
+ td::parse(active_usernames_[0], parser);
+ if (has_editable_username) {
+ editable_username_pos_ = 0;
+ }
+ }
+ if (has_disabled_usernames) {
+ td::parse(disabled_usernames_, parser);
+ }
+ check_utf8_validness();
+ }
+};
+
+bool operator==(const Usernames &lhs, const Usernames &rhs);
+bool operator!=(const Usernames &lhs, const Usernames &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Usernames &usernames);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Venue.cpp b/protocols/Telegram/tdlib/td/td/telegram/Venue.cpp
new file mode 100644
index 0000000000..711d843674
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Venue.cpp
@@ -0,0 +1,126 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/Venue.h"
+
+#include "td/telegram/misc.h"
+#include "td/telegram/secret_api.h"
+
+namespace td {
+
+Venue::Venue(const tl_object_ptr<telegram_api::GeoPoint> &geo_point_ptr, string title, string address, string provider,
+ string id, string type)
+ : location_(geo_point_ptr)
+ , title_(std::move(title))
+ , address_(std::move(address))
+ , provider_(std::move(provider))
+ , id_(std::move(id))
+ , type_(std::move(type)) {
+}
+
+Venue::Venue(Location location, string title, string address, string provider, string id, string type)
+ : location_(location)
+ , title_(std::move(title))
+ , address_(std::move(address))
+ , provider_(std::move(provider))
+ , id_(std::move(id))
+ , type_(std::move(type)) {
+}
+
+Venue::Venue(const tl_object_ptr<td_api::venue> &venue)
+ : location_(venue->location_)
+ , title_(venue->title_)
+ , address_(venue->address_)
+ , provider_(venue->provider_)
+ , id_(venue->id_)
+ , type_(venue->type_) {
+}
+
+bool Venue::empty() const {
+ return location_.empty();
+}
+
+Location &Venue::location() {
+ return location_;
+}
+
+const Location &Venue::location() const {
+ return location_;
+}
+
+tl_object_ptr<td_api::venue> Venue::get_venue_object() const {
+ return make_tl_object<td_api::venue>(location_.get_location_object(), title_, address_, provider_, id_, type_);
+}
+
+tl_object_ptr<telegram_api::inputMediaVenue> Venue::get_input_media_venue() const {
+ return make_tl_object<telegram_api::inputMediaVenue>(location_.get_input_geo_point(), title_, address_, provider_,
+ id_, type_);
+}
+
+SecretInputMedia Venue::get_secret_input_media_venue() const {
+ return SecretInputMedia{nullptr,
+ make_tl_object<secret_api::decryptedMessageMediaVenue>(
+ location_.get_latitude(), location_.get_longitude(), title_, address_, provider_, id_)};
+}
+
+tl_object_ptr<telegram_api::inputBotInlineMessageMediaVenue> Venue::get_input_bot_inline_message_media_venue(
+ tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) const {
+ int32 flags = 0;
+ if (reply_markup != nullptr) {
+ flags |= telegram_api::inputBotInlineMessageMediaVenue::REPLY_MARKUP_MASK;
+ }
+ return make_tl_object<telegram_api::inputBotInlineMessageMediaVenue>(
+ flags, location_.get_input_geo_point(), title_, address_, provider_, id_, type_, std::move(reply_markup));
+}
+
+bool operator==(const Venue &lhs, const Venue &rhs) {
+ return lhs.location_ == rhs.location_ && lhs.title_ == rhs.title_ && lhs.address_ == rhs.address_ &&
+ lhs.provider_ == rhs.provider_ && lhs.id_ == rhs.id_ && lhs.type_ == rhs.type_;
+}
+
+bool operator!=(const Venue &lhs, const Venue &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Venue &venue) {
+ return string_builder << "Venue[location = " << venue.location_ << ", title = " << venue.title_
+ << ", address = " << venue.address_ << ", provider = " << venue.provider_
+ << ", ID = " << venue.id_ << ", type = " << venue.type_ << "]";
+}
+
+Result<Venue> process_input_message_venue(tl_object_ptr<td_api::InputMessageContent> &&input_message_content) {
+ CHECK(input_message_content != nullptr);
+ CHECK(input_message_content->get_id() == td_api::inputMessageVenue::ID);
+ auto venue = std::move(static_cast<td_api::inputMessageVenue *>(input_message_content.get())->venue_);
+ if (venue == nullptr) {
+ return Status::Error(400, "Venue must be non-empty");
+ }
+
+ if (!clean_input_string(venue->title_)) {
+ return Status::Error(400, "Venue title must be encoded in UTF-8");
+ }
+ if (!clean_input_string(venue->address_)) {
+ return Status::Error(400, "Venue address must be encoded in UTF-8");
+ }
+ if (!clean_input_string(venue->provider_)) {
+ return Status::Error(400, "Venue provider must be encoded in UTF-8");
+ }
+ if (!clean_input_string(venue->id_)) {
+ return Status::Error(400, "Venue identifier must be encoded in UTF-8");
+ }
+ if (!clean_input_string(venue->type_)) {
+ return Status::Error(400, "Venue type must be encoded in UTF-8");
+ }
+
+ Venue result(venue);
+ if (result.empty()) {
+ return Status::Error(400, "Wrong venue location specified");
+ }
+
+ return result;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Venue.h b/protocols/Telegram/tdlib/td/td/telegram/Venue.h
new file mode 100644
index 0000000000..c9cf6b7935
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/Venue.h
@@ -0,0 +1,92 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/Location.h"
+#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/Version.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+class Venue {
+ Location location_;
+ string title_;
+ string address_;
+ string provider_;
+ string id_;
+ string type_;
+
+ friend bool operator==(const Venue &lhs, const Venue &rhs);
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, const Venue &venue);
+
+ public:
+ Venue() = default;
+
+ Venue(const tl_object_ptr<telegram_api::GeoPoint> &geo_point_ptr, string title, string address, string provider,
+ string id, string type);
+
+ Venue(Location location, string title, string address, string provider, string id, string type);
+
+ explicit Venue(const tl_object_ptr<td_api::venue> &venue);
+
+ bool empty() const;
+
+ Location &location();
+
+ const Location &location() const;
+
+ tl_object_ptr<td_api::venue> get_venue_object() const;
+
+ tl_object_ptr<telegram_api::inputMediaVenue> get_input_media_venue() const;
+
+ SecretInputMedia get_secret_input_media_venue() const;
+
+ tl_object_ptr<telegram_api::inputBotInlineMessageMediaVenue> get_input_bot_inline_message_media_venue(
+ tl_object_ptr<telegram_api::ReplyMarkup> &&reply_markup) const;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(location_, storer);
+ store(title_, storer);
+ store(address_, storer);
+ store(provider_, storer);
+ store(id_, storer);
+ store(type_, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(location_, parser);
+ parse(title_, parser);
+ parse(address_, parser);
+ parse(provider_, parser);
+ parse(id_, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddVenueType)) {
+ parse(type_, parser);
+ }
+ }
+};
+
+bool operator==(const Venue &lhs, const Venue &rhs);
+bool operator!=(const Venue &lhs, const Venue &rhs);
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Venue &venue);
+
+Result<Venue> process_input_message_venue(td_api::object_ptr<td_api::InputMessageContent> &&input_message_content)
+ TD_WARN_UNUSED_RESULT;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/Version.h b/protocols/Telegram/tdlib/td/td/telegram/Version.h
index f27c3b77be..313e6d0fda 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/Version.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/Version.h
@@ -1,42 +1,82 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/utils/common.h"
+
namespace td {
+constexpr int32 MTPROTO_LAYER = 148;
+
enum class Version : int32 {
- Initial,
+ Initial, // 0
StoreFileId,
AddKeyHashToSecretChat,
AddDurationToAnimation,
FixStoreGameWithoutAnimation,
- AddAccessHashToSecretChat,
+ AddAccessHashToSecretChat, // 5
StoreFileOwnerId,
StoreFileEncryptionKey,
NetStatsCountDuration,
FixWebPageInstantViewDatabase,
- FixMinUsers,
+ FixMinUsers, // 10
FixPageBlockAudioEmptyFile,
AddMessageInvoiceProviderData,
AddCaptionEntities,
+ AddVenueType,
+ AddTermsOfService, // 15
+ AddContactVcard,
+ AddMessageUnsupportedVersion,
+ SupportInstantView2_0,
+ AddNotificationGroupInfoMaxRemovedMessageId,
+ SupportMinithumbnails, // 20
+ AddVideoCallsSupport,
+ AddPhotoSizeSource,
+ AddFolders,
+ SupportPolls2_0,
+ AddDiceEmoji, // 25
+ AddAnimationStickers,
+ AddDialogPhotoHasAnimation,
+ AddPhotoProgressiveSizes,
+ AddLiveLocationHeading,
+ AddLiveLocationProximityAlertDistance, // 30
+ SupportBannedChannels,
+ RemovePhotoVolumeAndLocalId,
+ Support64BitIds,
+ AddInviteLinksRequiringApproval,
+ AddKeyboardButtonFlags, // 35
+ AddAudioFlags,
+ UseServerForwardAsCopy,
+ AddMainDialogListPosition,
+ AddVoiceNoteFlags,
+ AddMessageStickerFlags, // 40
+ AddStickerSetListFlags,
+ AddInputInvoiceFlags,
+ AddVideoNoteFlags,
Next
};
enum class DbVersion : int32 {
DialogDbCreated = 3,
- MessagesDbMediaIndex,
- MessagesDb30MediaIndex,
- MessagesDbFts,
+ MessageDbMediaIndex,
+ MessageDb30MediaIndex,
+ MessageDbFts,
MessagesCallIndex,
FixFileRemoteLocationKeyBug,
+ AddNotificationsSupport,
+ AddFolders,
+ AddScheduledMessages,
+ StorePinnedDialogsInBinlog,
+ AddMessageThreadSupport,
+ AddMessageThreadDatabase,
Next
};
-inline int32 current_db_version() {
+inline constexpr int32 current_db_version() {
return static_cast<int32>(DbVersion::Next) - 1;
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.cpp
index e83fefa582..89526dc511 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.cpp
@@ -1,64 +1,83 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/VideoNotesManager.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/files/FileManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/OptionManager.h"
+#include "td/telegram/PhotoFormat.h"
#include "td/telegram/secret_api.h"
+#include "td/telegram/Td.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/UpdatesManager.h"
-#include "td/actor/PromiseFuture.h"
-
-#include "td/telegram/DocumentsManager.h"
-#include "td/telegram/files/FileManager.h"
-#include "td/telegram/SecretChatActor.h"
-#include "td/telegram/Td.h"
+#include "td/actor/actor.h"
+#include "td/utils/buffer.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Status.h"
namespace td {
-VideoNotesManager::VideoNotesManager(Td *td) : td_(td) {
+VideoNotesManager::VideoNotesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
}
-int32 VideoNotesManager::get_video_note_duration(FileId file_id) {
- auto &video_note = video_notes_[file_id];
+VideoNotesManager::~VideoNotesManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), video_notes_);
+}
+
+void VideoNotesManager::tear_down() {
+ parent_.reset();
+}
+
+int32 VideoNotesManager::get_video_note_duration(FileId file_id) const {
+ auto video_note = get_video_note(file_id);
CHECK(video_note != nullptr);
return video_note->duration;
}
-tl_object_ptr<td_api::videoNote> VideoNotesManager::get_video_note_object(FileId file_id) {
+tl_object_ptr<td_api::videoNote> VideoNotesManager::get_video_note_object(FileId file_id) const {
if (!file_id.is_valid()) {
return nullptr;
}
- auto &video_note = video_notes_[file_id];
- CHECK(video_note != nullptr);
- video_note->is_changed = false;
-
- return make_tl_object<td_api::videoNote>(video_note->duration, video_note->dimensions.width,
- get_photo_size_object(td_->file_manager_.get(), &video_note->thumbnail),
- td_->file_manager_->get_file_object(file_id));
+ auto video_note = get_video_note(file_id);
+ auto speech_recognition_result = video_note->transcription_info == nullptr
+ ? nullptr
+ : video_note->transcription_info->get_speech_recognition_result_object();
+ return make_tl_object<td_api::videoNote>(
+ video_note->duration, video_note->waveform, video_note->dimensions.width,
+ get_minithumbnail_object(video_note->minithumbnail),
+ get_thumbnail_object(td_->file_manager_.get(), video_note->thumbnail, PhotoFormat::Jpeg),
+ std::move(speech_recognition_result), td_->file_manager_->get_file_object(file_id));
}
-FileId VideoNotesManager::on_get_video_note(std::unique_ptr<VideoNote> new_video_note, bool replace) {
+FileId VideoNotesManager::on_get_video_note(unique_ptr<VideoNote> new_video_note, bool replace) {
auto file_id = new_video_note->file_id;
+ CHECK(file_id.is_valid());
LOG(INFO) << "Receive video note " << file_id;
auto &v = video_notes_[file_id];
if (v == nullptr) {
v = std::move(new_video_note);
} else if (replace) {
CHECK(v->file_id == new_video_note->file_id);
- if (v->duration != new_video_note->duration || v->dimensions != new_video_note->dimensions) {
+ if (v->duration != new_video_note->duration || v->dimensions != new_video_note->dimensions ||
+ v->waveform != new_video_note->waveform) {
LOG(DEBUG) << "Video note " << file_id << " info has changed";
v->duration = new_video_note->duration;
v->dimensions = new_video_note->dimensions;
- v->is_changed = true;
+ v->waveform = std::move(new_video_note->waveform);
+ }
+ if (v->minithumbnail != new_video_note->minithumbnail) {
+ v->minithumbnail = std::move(new_video_note->minithumbnail);
}
if (v->thumbnail != new_video_note->thumbnail) {
if (!v->thumbnail.file_id.is_valid()) {
@@ -67,21 +86,21 @@ FileId VideoNotesManager::on_get_video_note(std::unique_ptr<VideoNote> new_video
LOG(INFO) << "Video note " << file_id << " thumbnail has changed from " << v->thumbnail << " to "
<< new_video_note->thumbnail;
}
- v->thumbnail = new_video_note->thumbnail;
- v->is_changed = true;
+ v->thumbnail = std::move(new_video_note->thumbnail);
+ }
+ if (TranscriptionInfo::update_from(v->transcription_info, std::move(new_video_note->transcription_info))) {
+ on_video_note_transcription_completed(file_id);
}
}
return file_id;
}
-const VideoNotesManager::VideoNote *VideoNotesManager::get_video_note(FileId file_id) const {
- auto video_note = video_notes_.find(file_id);
- if (video_note == video_notes_.end()) {
- return nullptr;
- }
+VideoNotesManager::VideoNote *VideoNotesManager::get_video_note(FileId file_id) {
+ return video_notes_.get_pointer(file_id);
+}
- CHECK(video_note->second->file_id == file_id);
- return video_note->second.get();
+const VideoNotesManager::VideoNote *VideoNotesManager::get_video_note(FileId file_id) const {
+ return video_notes_.get_pointer(file_id);
}
FileId VideoNotesManager::get_video_note_thumbnail_file_id(FileId file_id) const {
@@ -100,53 +119,41 @@ FileId VideoNotesManager::dup_video_note(FileId new_id, FileId old_id) {
const VideoNote *old_video_note = get_video_note(old_id);
CHECK(old_video_note != nullptr);
auto &new_video_note = video_notes_[new_id];
- CHECK(!new_video_note);
- new_video_note = std::make_unique<VideoNote>(*old_video_note);
+ CHECK(new_video_note == nullptr);
+ new_video_note = make_unique<VideoNote>();
new_video_note->file_id = new_id;
- new_video_note->thumbnail.file_id = td_->file_manager_->dup_file_id(new_video_note->thumbnail.file_id);
+ new_video_note->duration = old_video_note->duration;
+ new_video_note->dimensions = old_video_note->dimensions;
+ new_video_note->waveform = old_video_note->waveform;
+ new_video_note->minithumbnail = old_video_note->minithumbnail;
+ new_video_note->thumbnail = old_video_note->thumbnail;
+ new_video_note->thumbnail.file_id =
+ td_->file_manager_->dup_file_id(new_video_note->thumbnail.file_id, "dup_video_note");
+ new_video_note->transcription_info = TranscriptionInfo::copy_if_transcribed(old_video_note->transcription_info);
return new_id;
}
-bool VideoNotesManager::merge_video_notes(FileId new_id, FileId old_id, bool can_delete_old) {
- if (!old_id.is_valid()) {
- LOG(ERROR) << "Old file id is invalid";
- return true;
- }
+void VideoNotesManager::merge_video_notes(FileId new_id, FileId old_id) {
+ CHECK(old_id.is_valid() && new_id.is_valid());
+ CHECK(new_id != old_id);
LOG(INFO) << "Merge video notes " << new_id << " and " << old_id;
const VideoNote *old_ = get_video_note(old_id);
CHECK(old_ != nullptr);
- if (old_id == new_id) {
- return old_->is_changed;
- }
-
- auto new_it = video_notes_.find(new_id);
- if (new_it == video_notes_.end()) {
- auto &old = video_notes_[old_id];
- old->is_changed = true;
- if (!can_delete_old) {
- dup_video_note(new_id, old_id);
- } else {
- old->file_id = new_id;
- video_notes_.emplace(new_id, std::move(old));
- }
+
+ const auto *new_ = get_video_note(new_id);
+ if (new_ == nullptr) {
+ dup_video_note(new_id, old_id);
} else {
- VideoNote *new_ = new_it->second.get();
- CHECK(new_ != nullptr);
- new_->is_changed = true;
if (old_->thumbnail != new_->thumbnail) {
// LOG_STATUS(td_->file_manager_->merge(new_->thumbnail.file_id, old_->thumbnail.file_id));
}
}
LOG_STATUS(td_->file_manager_->merge(new_id, old_id));
- if (can_delete_old) {
- video_notes_.erase(old_id);
- }
- return true;
}
-void VideoNotesManager::create_video_note(FileId file_id, PhotoSize thumbnail, int32 duration, Dimensions dimensions,
- bool replace) {
+void VideoNotesManager::create_video_note(FileId file_id, string minithumbnail, PhotoSize thumbnail, int32 duration,
+ Dimensions dimensions, string waveform, bool replace) {
auto v = make_unique<VideoNote>();
v->file_id = file_id;
v->duration = max(duration, 0);
@@ -155,22 +162,146 @@ void VideoNotesManager::create_video_note(FileId file_id, PhotoSize thumbnail, i
} else {
LOG(INFO) << "Receive wrong video note dimensions " << dimensions;
}
+ v->waveform = std::move(waveform);
+ if (!td_->auth_manager_->is_bot()) {
+ v->minithumbnail = std::move(minithumbnail);
+ }
v->thumbnail = std::move(thumbnail);
on_get_video_note(std::move(v), replace);
}
+void VideoNotesManager::register_video_note(FileId video_note_file_id, FullMessageId full_message_id,
+ const char *source) {
+ if (full_message_id.get_message_id().is_scheduled() || !full_message_id.get_message_id().is_server() ||
+ td_->auth_manager_->is_bot()) {
+ return;
+ }
+ LOG(INFO) << "Register video note " << video_note_file_id << " from " << full_message_id << " from " << source;
+ bool is_inserted = video_note_messages_[video_note_file_id].insert(full_message_id).second;
+ LOG_CHECK(is_inserted) << source << ' ' << video_note_file_id << ' ' << full_message_id;
+ is_inserted = message_video_notes_.emplace(full_message_id, video_note_file_id).second;
+ CHECK(is_inserted);
+}
+
+void VideoNotesManager::unregister_video_note(FileId video_note_file_id, FullMessageId full_message_id,
+ const char *source) {
+ if (full_message_id.get_message_id().is_scheduled() || !full_message_id.get_message_id().is_server() ||
+ td_->auth_manager_->is_bot()) {
+ return;
+ }
+ LOG(INFO) << "Unregister video note " << video_note_file_id << " from " << full_message_id << " from " << source;
+ auto &message_ids = video_note_messages_[video_note_file_id];
+ auto is_deleted = message_ids.erase(full_message_id) > 0;
+ LOG_CHECK(is_deleted) << source << ' ' << video_note_file_id << ' ' << full_message_id;
+ if (message_ids.empty()) {
+ video_note_messages_.erase(video_note_file_id);
+ }
+ is_deleted = message_video_notes_.erase(full_message_id) > 0;
+ CHECK(is_deleted);
+}
+
+void VideoNotesManager::recognize_speech(FullMessageId full_message_id, Promise<Unit> &&promise) {
+ auto it = message_video_notes_.find(full_message_id);
+ CHECK(it != message_video_notes_.end());
+
+ auto file_id = it->second;
+ auto video_note = get_video_note(file_id);
+ CHECK(video_note != nullptr);
+ if (video_note->transcription_info == nullptr) {
+ video_note->transcription_info = make_unique<TranscriptionInfo>();
+ }
+
+ auto handler = [actor_id = actor_id(this),
+ file_id](Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>> r_update) {
+ send_closure(actor_id, &VideoNotesManager::on_transcribed_audio_update, file_id, true, std::move(r_update));
+ };
+ if (video_note->transcription_info->recognize_speech(td_, full_message_id, std::move(promise), std::move(handler))) {
+ on_video_note_transcription_updated(file_id);
+ }
+}
+
+void VideoNotesManager::on_transcribed_audio_update(
+ FileId file_id, bool is_initial, Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>> r_update) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto video_note = get_video_note(file_id);
+ CHECK(video_note != nullptr);
+ CHECK(video_note->transcription_info != nullptr);
+
+ if (r_update.is_error()) {
+ auto promises = video_note->transcription_info->on_failed_transcription(r_update.error().clone());
+ on_video_note_transcription_updated(file_id);
+ fail_promises(promises, r_update.move_as_error());
+ return;
+ }
+ auto update = r_update.move_as_ok();
+ auto transcription_id = update->transcription_id_;
+ if (!update->pending_) {
+ auto promises = video_note->transcription_info->on_final_transcription(std::move(update->text_), transcription_id);
+ on_video_note_transcription_completed(file_id);
+ set_promises(promises);
+ } else {
+ auto is_changed =
+ video_note->transcription_info->on_partial_transcription(std::move(update->text_), transcription_id);
+ if (is_changed) {
+ on_video_note_transcription_updated(file_id);
+ }
+
+ if (is_initial) {
+ td_->updates_manager_->subscribe_to_transcribed_audio_updates(
+ transcription_id, [actor_id = actor_id(this),
+ file_id](Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>> r_update) {
+ send_closure(actor_id, &VideoNotesManager::on_transcribed_audio_update, file_id, false,
+ std::move(r_update));
+ });
+ }
+ }
+}
+
+void VideoNotesManager::on_video_note_transcription_updated(FileId file_id) {
+ auto it = video_note_messages_.find(file_id);
+ if (it != video_note_messages_.end()) {
+ for (const auto &full_message_id : it->second) {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ }
+ }
+}
+
+void VideoNotesManager::on_video_note_transcription_completed(FileId file_id) {
+ auto it = video_note_messages_.find(file_id);
+ if (it != video_note_messages_.end()) {
+ for (const auto &full_message_id : it->second) {
+ td_->messages_manager_->on_update_message_content(full_message_id);
+ }
+ }
+}
+
+void VideoNotesManager::rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise<Unit> &&promise) {
+ auto it = message_video_notes_.find(full_message_id);
+ CHECK(it != message_video_notes_.end());
+
+ auto file_id = it->second;
+ auto video_note = get_video_note(file_id);
+ CHECK(video_note != nullptr);
+ if (video_note->transcription_info == nullptr) {
+ return promise.set_value(Unit());
+ }
+ video_note->transcription_info->rate_speech_recognition(td_, full_message_id, is_good, std::move(promise));
+}
+
SecretInputMedia VideoNotesManager::get_secret_input_media(FileId video_note_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
BufferSlice thumbnail, int32 layer) const {
const VideoNote *video_note = get_video_note(video_note_file_id);
CHECK(video_note != nullptr);
auto file_view = td_->file_manager_->get_file_view(video_note_file_id);
- auto &encryption_key = file_view.encryption_key();
- if (encryption_key.empty()) {
+ if (!file_view.is_encrypted_secret() || file_view.encryption_key().empty()) {
return SecretInputMedia{};
}
if (file_view.has_remote_location()) {
- input_file = file_view.remote_location().as_input_encrypted_file();
+ input_file = file_view.main_remote_location().as_input_encrypted_file();
}
if (!input_file) {
return SecretInputMedia{};
@@ -178,17 +309,19 @@ SecretInputMedia VideoNotesManager::get_secret_input_media(FileId video_note_fil
if (video_note->thumbnail.file_id.is_valid() && thumbnail.empty()) {
return SecretInputMedia{};
}
- CHECK(layer >= SecretChatActor::VOICE_NOTES_LAYER);
vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
- attributes.push_back(make_tl_object<secret_api::documentAttributeVideo66>(
- secret_api::documentAttributeVideo66::Flags::ROUND_MESSAGE_MASK, true, video_note->duration,
- video_note->dimensions.width, video_note->dimensions.height));
- return SecretInputMedia{
- std::move(input_file),
- make_tl_object<secret_api::decryptedMessageMediaDocument>(
- std::move(thumbnail), video_note->thumbnail.dimensions.width, video_note->thumbnail.dimensions.height,
- "video/mp4", narrow_cast<int32>(file_view.size()), BufferSlice(encryption_key.key_slice()),
- BufferSlice(encryption_key.iv_slice()), std::move(attributes), "")};
+ attributes.push_back(make_tl_object<secret_api::documentAttributeVideo>(
+ secret_api::documentAttributeVideo::ROUND_MESSAGE_MASK, true, video_note->duration, video_note->dimensions.width,
+ video_note->dimensions.height));
+
+ return {std::move(input_file),
+ std::move(thumbnail),
+ video_note->thumbnail.dimensions,
+ "video/mp4",
+ file_view,
+ std::move(attributes),
+ string(),
+ layer};
}
tl_object_ptr<telegram_api::InputMedia> VideoNotesManager::get_input_media(
@@ -198,30 +331,34 @@ tl_object_ptr<telegram_api::InputMedia> VideoNotesManager::get_input_media(
if (file_view.is_encrypted()) {
return nullptr;
}
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
- return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.remote_location().as_input_document(), 0);
+ if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
+ return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.main_remote_location().as_input_document(), 0,
+ string());
}
if (file_view.has_url()) {
return make_tl_object<telegram_api::inputMediaDocumentExternal>(0, file_view.url(), 0);
}
- CHECK(!file_view.has_remote_location());
if (input_file != nullptr) {
const VideoNote *video_note = get_video_note(file_id);
CHECK(video_note != nullptr);
vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
+ auto suggested_video_note_length =
+ narrow_cast<int32>(td_->option_manager_->get_option_integer("suggested_video_note_length", 384));
attributes.push_back(make_tl_object<telegram_api::documentAttributeVideo>(
telegram_api::documentAttributeVideo::ROUND_MESSAGE_MASK, false /*ignored*/, false /*ignored*/,
- video_note->duration, video_note->dimensions.width ? video_note->dimensions.width : 240,
- video_note->dimensions.height ? video_note->dimensions.height : 240));
- int32 flags = 0;
+ video_note->duration, video_note->dimensions.width ? video_note->dimensions.width : suggested_video_note_length,
+ video_note->dimensions.height ? video_note->dimensions.height : suggested_video_note_length));
+ int32 flags = telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK;
if (input_thumbnail != nullptr) {
flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK;
}
return make_tl_object<telegram_api::inputMediaUploadedDocument>(
- flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), "video/mp4", std::move(attributes),
- vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), "video/mp4",
+ std::move(attributes), vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ } else {
+ CHECK(!file_view.has_remote_location());
}
return nullptr;
diff --git a/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.h b/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.h
index f5babe801c..c779a32869 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.h
@@ -1,38 +1,57 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/PhotoSize.h"
+#include "td/telegram/SecretInputMedia.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/TranscriptionInfo.h"
-#include "td/telegram/files/FileId.h"
-#include "td/telegram/Photo.h"
-#include "td/telegram/SecretInputMedia.h"
+#include "td/actor/actor.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
-
-#include <unordered_map>
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+#include "td/utils/WaitFreeHashMap.h"
namespace td {
-class Td;
-} // namespace td
-namespace td {
+class Td;
-class VideoNotesManager {
+class VideoNotesManager final : public Actor {
public:
- explicit VideoNotesManager(Td *td);
+ VideoNotesManager(Td *td, ActorShared<> parent);
+ VideoNotesManager(const VideoNotesManager &) = delete;
+ VideoNotesManager &operator=(const VideoNotesManager &) = delete;
+ VideoNotesManager(VideoNotesManager &&) = delete;
+ VideoNotesManager &operator=(VideoNotesManager &&) = delete;
+ ~VideoNotesManager() final;
+
+ int32 get_video_note_duration(FileId file_id) const;
+
+ tl_object_ptr<td_api::videoNote> get_video_note_object(FileId file_id) const;
- int32 get_video_note_duration(FileId file_id);
+ void create_video_note(FileId file_id, string minithumbnail, PhotoSize thumbnail, int32 duration,
+ Dimensions dimensions, string waveform, bool replace);
- tl_object_ptr<td_api::videoNote> get_video_note_object(FileId file_id);
+ void register_video_note(FileId video_note_file_id, FullMessageId full_message_id, const char *source);
- void create_video_note(FileId file_id, PhotoSize thumbnail, int32 duration, Dimensions dimensions, bool replace);
+ void unregister_video_note(FileId video_note_file_id, FullMessageId full_message_id, const char *source);
+
+ void recognize_speech(FullMessageId full_message_id, Promise<Unit> &&promise);
+
+ void rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise<Unit> &&promise);
tl_object_ptr<telegram_api::InputMedia> get_input_media(FileId file_id,
tl_object_ptr<telegram_api::InputFile> input_file,
@@ -48,32 +67,49 @@ class VideoNotesManager {
FileId dup_video_note(FileId new_id, FileId old_id);
- bool merge_video_notes(FileId new_id, FileId old_id, bool can_delete_old);
+ void merge_video_notes(FileId new_id, FileId old_id);
- template <class T>
- void store_video_note(FileId file_id, T &storer) const;
+ template <class StorerT>
+ void store_video_note(FileId file_id, StorerT &storer) const;
- template <class T>
- FileId parse_video_note(T &parser);
+ template <class ParserT>
+ FileId parse_video_note(ParserT &parser);
private:
class VideoNote {
public:
int32 duration = 0;
Dimensions dimensions;
+ string waveform;
+ string minithumbnail;
PhotoSize thumbnail;
+ unique_ptr<TranscriptionInfo> transcription_info;
FileId file_id;
-
- bool is_changed = true;
};
+ VideoNote *get_video_note(FileId file_id);
+
const VideoNote *get_video_note(FileId file_id) const;
- FileId on_get_video_note(std::unique_ptr<VideoNote> new_video_note, bool replace);
+ FileId on_get_video_note(unique_ptr<VideoNote> new_video_note, bool replace);
+
+ void on_video_note_transcription_updated(FileId file_id);
+
+ void on_video_note_transcription_completed(FileId file_id);
+
+ void on_transcribed_audio_update(FileId file_id, bool is_initial,
+ Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>> r_update);
+
+ void tear_down() final;
Td *td_;
- std::unordered_map<FileId, unique_ptr<VideoNote>, FileIdHash> video_notes_;
+ ActorShared<> parent_;
+
+ WaitFreeHashMap<FileId, unique_ptr<VideoNote>, FileIdHash> video_notes_;
+
+ FlatHashMap<FileId, FlatHashSet<FullMessageId, FullMessageIdHash>, FileIdHash> video_note_messages_;
+ FlatHashMap<FullMessageId, FileId, FullMessageIdHash> message_video_notes_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.hpp
index 5992848c97..272fbe681d 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/VideoNotesManager.hpp
@@ -1,39 +1,101 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/telegram/VideoNotesManager.h"
#include "td/telegram/files/FileId.hpp"
-#include "td/telegram/Photo.hpp"
+#include "td/telegram/PhotoSize.hpp"
+#include "td/telegram/Version.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/tl_helpers.h"
namespace td {
-template <class T>
-void VideoNotesManager::store_video_note(FileId file_id, T &storer) const {
- auto it = video_notes_.find(file_id);
- CHECK(it != video_notes_.end());
- const VideoNote *video_note = it->second.get();
- store(video_note->duration, storer);
+template <class StorerT>
+void VideoNotesManager::store_video_note(FileId file_id, StorerT &storer) const {
+ const VideoNote *video_note = get_video_note(file_id);
+ CHECK(video_note != nullptr);
+ bool has_duration = video_note->duration != 0;
+ bool has_minithumbnail = !video_note->minithumbnail.empty();
+ bool has_thumbnail = video_note->thumbnail.file_id.is_valid();
+ bool is_transcribed = video_note->transcription_info != nullptr && video_note->transcription_info->is_transcribed();
+ bool has_waveform = !video_note->waveform.empty();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_duration);
+ STORE_FLAG(has_minithumbnail);
+ STORE_FLAG(has_thumbnail);
+ STORE_FLAG(is_transcribed);
+ STORE_FLAG(has_waveform);
+ END_STORE_FLAGS();
+ if (has_duration) {
+ store(video_note->duration, storer);
+ }
store(video_note->dimensions, storer);
- store(video_note->thumbnail, storer);
+ if (has_minithumbnail) {
+ store(video_note->minithumbnail, storer);
+ }
+ if (has_thumbnail) {
+ store(video_note->thumbnail, storer);
+ }
+ if (is_transcribed) {
+ store(video_note->transcription_info, storer);
+ }
+ if (has_waveform) {
+ store(video_note->waveform, storer);
+ }
store(file_id, storer);
}
-template <class T>
-FileId VideoNotesManager::parse_video_note(T &parser) {
+template <class ParserT>
+FileId VideoNotesManager::parse_video_note(ParserT &parser) {
auto video_note = make_unique<VideoNote>();
- parse(video_note->duration, parser);
+ bool has_duration;
+ bool has_minithumbnail;
+ bool has_thumbnail;
+ bool is_transcribed;
+ bool has_waveform;
+ if (parser.version() >= static_cast<int32>(Version::AddVideoNoteFlags)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_duration);
+ PARSE_FLAG(has_minithumbnail);
+ PARSE_FLAG(has_thumbnail);
+ PARSE_FLAG(is_transcribed);
+ PARSE_FLAG(has_waveform);
+ END_PARSE_FLAGS();
+ } else {
+ has_duration = true;
+ has_minithumbnail = parser.version() >= static_cast<int32>(Version::SupportMinithumbnails);
+ has_thumbnail = true;
+ is_transcribed = false;
+ has_waveform = false;
+ }
+ if (has_duration) {
+ parse(video_note->duration, parser);
+ }
parse(video_note->dimensions, parser);
- parse(video_note->thumbnail, parser);
+ if (has_minithumbnail) {
+ parse(video_note->minithumbnail, parser);
+ }
+ if (has_thumbnail) {
+ parse(video_note->thumbnail, parser);
+ }
+ if (is_transcribed) {
+ parse(video_note->transcription_info, parser);
+ }
+ if (has_waveform) {
+ parse(video_note->waveform, parser);
+ }
parse(video_note->file_id, parser);
- return on_get_video_note(std::move(video_note), true);
+ if (parser.get_error() != nullptr || !video_note->file_id.is_valid()) {
+ return FileId();
+ }
+ return on_get_video_note(std::move(video_note), false);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/VideosManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/VideosManager.cpp
index 0d171cd871..7457646d4c 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/VideosManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/VideosManager.cpp
@@ -1,22 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/VideosManager.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/AuthManager.h"
-#include "td/telegram/DocumentsManager.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/Global.h"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/secret_api.h"
#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
@@ -27,29 +26,35 @@ namespace td {
VideosManager::VideosManager(Td *td) : td_(td) {
}
-int32 VideosManager::get_video_duration(FileId file_id) {
- auto &video = videos_[file_id];
+VideosManager::~VideosManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), videos_);
+}
+
+int32 VideosManager::get_video_duration(FileId file_id) const {
+ auto video = get_video(file_id);
CHECK(video != nullptr);
return video->duration;
}
-tl_object_ptr<td_api::video> VideosManager::get_video_object(FileId file_id) {
+tl_object_ptr<td_api::video> VideosManager::get_video_object(FileId file_id) const {
if (!file_id.is_valid()) {
return nullptr;
}
- auto &video = videos_[file_id];
+ auto video = get_video(file_id);
CHECK(video != nullptr);
- video->is_changed = false;
-
- return make_tl_object<td_api::video>(
- video->duration, video->dimensions.width, video->dimensions.height, video->file_name, video->mime_type,
- video->has_stickers, video->supports_streaming,
- get_photo_size_object(td_->file_manager_.get(), &video->thumbnail), td_->file_manager_->get_file_object(file_id));
+ auto thumbnail = video->animated_thumbnail.file_id.is_valid()
+ ? get_thumbnail_object(td_->file_manager_.get(), video->animated_thumbnail, PhotoFormat::Mpeg4)
+ : get_thumbnail_object(td_->file_manager_.get(), video->thumbnail, PhotoFormat::Jpeg);
+ return make_tl_object<td_api::video>(video->duration, video->dimensions.width, video->dimensions.height,
+ video->file_name, video->mime_type, video->has_stickers,
+ video->supports_streaming, get_minithumbnail_object(video->minithumbnail),
+ std::move(thumbnail), td_->file_manager_->get_file_object(file_id));
}
-FileId VideosManager::on_get_video(std::unique_ptr<Video> new_video, bool replace) {
+FileId VideosManager::on_get_video(unique_ptr<Video> new_video, bool replace) {
auto file_id = new_video->file_id;
+ CHECK(file_id.is_valid());
LOG(INFO) << "Receive video " << file_id;
auto &v = videos_[file_id];
if (v == nullptr) {
@@ -58,8 +63,7 @@ FileId VideosManager::on_get_video(std::unique_ptr<Video> new_video, bool replac
CHECK(v->file_id == new_video->file_id);
if (v->mime_type != new_video->mime_type) {
LOG(DEBUG) << "Video " << file_id << " MIME type has changed";
- v->mime_type = new_video->mime_type;
- v->is_changed = true;
+ v->mime_type = std::move(new_video->mime_type);
}
if (v->duration != new_video->duration || v->dimensions != new_video->dimensions ||
v->supports_streaming != new_video->supports_streaming) {
@@ -67,12 +71,13 @@ FileId VideosManager::on_get_video(std::unique_ptr<Video> new_video, bool replac
v->duration = new_video->duration;
v->dimensions = new_video->dimensions;
v->supports_streaming = new_video->supports_streaming;
- v->is_changed = true;
}
if (v->file_name != new_video->file_name) {
LOG(DEBUG) << "Video " << file_id << " file name has changed";
v->file_name = std::move(new_video->file_name);
- v->is_changed = true;
+ }
+ if (v->minithumbnail != new_video->minithumbnail) {
+ v->minithumbnail = std::move(new_video->minithumbnail);
}
if (v->thumbnail != new_video->thumbnail) {
if (!v->thumbnail.file_id.is_valid()) {
@@ -81,29 +86,29 @@ FileId VideosManager::on_get_video(std::unique_ptr<Video> new_video, bool replac
LOG(INFO) << "Video " << file_id << " thumbnail has changed from " << v->thumbnail << " to "
<< new_video->thumbnail;
}
- v->thumbnail = new_video->thumbnail;
- v->is_changed = true;
+ v->thumbnail = std::move(new_video->thumbnail);
+ }
+ if (v->animated_thumbnail != new_video->animated_thumbnail) {
+ if (!v->animated_thumbnail.file_id.is_valid()) {
+ LOG(DEBUG) << "Video " << file_id << " animated thumbnail has changed";
+ } else {
+ LOG(INFO) << "Video " << file_id << " animated thumbnail has changed from " << v->animated_thumbnail << " to "
+ << new_video->animated_thumbnail;
+ }
+ v->animated_thumbnail = std::move(new_video->animated_thumbnail);
}
if (v->has_stickers != new_video->has_stickers && new_video->has_stickers) {
v->has_stickers = new_video->has_stickers;
- v->is_changed = true;
}
if (v->sticker_file_ids != new_video->sticker_file_ids && !new_video->sticker_file_ids.empty()) {
- v->sticker_file_ids = new_video->sticker_file_ids;
- v->is_changed = true;
+ v->sticker_file_ids = std::move(new_video->sticker_file_ids);
}
}
return file_id;
}
const VideosManager::Video *VideosManager::get_video(FileId file_id) const {
- auto video = videos_.find(file_id);
- if (video == videos_.end()) {
- return nullptr;
- }
-
- CHECK(video->second->file_id == file_id);
- return video->second.get();
+ return videos_.get_pointer(file_id);
}
FileId VideosManager::get_video_thumbnail_file_id(FileId file_id) const {
@@ -112,76 +117,70 @@ FileId VideosManager::get_video_thumbnail_file_id(FileId file_id) const {
return video->thumbnail.file_id;
}
+FileId VideosManager::get_video_animated_thumbnail_file_id(FileId file_id) const {
+ auto video = get_video(file_id);
+ CHECK(video != nullptr);
+ return video->animated_thumbnail.file_id;
+}
+
void VideosManager::delete_video_thumbnail(FileId file_id) {
auto &video = videos_[file_id];
CHECK(video != nullptr);
video->thumbnail = PhotoSize();
+ video->animated_thumbnail = AnimationSize();
}
FileId VideosManager::dup_video(FileId new_id, FileId old_id) {
const Video *old_video = get_video(old_id);
CHECK(old_video != nullptr);
auto &new_video = videos_[new_id];
- CHECK(!new_video);
- new_video = std::make_unique<Video>(*old_video);
+ CHECK(new_video == nullptr);
+ new_video = make_unique<Video>(*old_video);
new_video->file_id = new_id;
- new_video->thumbnail.file_id = td_->file_manager_->dup_file_id(new_video->thumbnail.file_id);
+ new_video->thumbnail.file_id = td_->file_manager_->dup_file_id(new_video->thumbnail.file_id, "dup_video");
+ new_video->animated_thumbnail.file_id =
+ td_->file_manager_->dup_file_id(new_video->animated_thumbnail.file_id, "dup_video");
return new_id;
}
-bool VideosManager::merge_videos(FileId new_id, FileId old_id, bool can_delete_old) {
- if (!old_id.is_valid()) {
- LOG(ERROR) << "Old file id is invalid";
- return true;
- }
+void VideosManager::merge_videos(FileId new_id, FileId old_id) {
+ CHECK(old_id.is_valid() && new_id.is_valid());
+ CHECK(new_id != old_id);
LOG(INFO) << "Merge videos " << new_id << " and " << old_id;
const Video *old_ = get_video(old_id);
CHECK(old_ != nullptr);
- if (old_id == new_id) {
- return old_->is_changed;
- }
- auto new_it = videos_.find(new_id);
- if (new_it == videos_.end()) {
- auto &old = videos_[old_id];
- old->is_changed = true;
- if (!can_delete_old) {
- dup_video(new_id, old_id);
- } else {
- old->file_id = new_id;
- videos_.emplace(new_id, std::move(old));
- }
+ const auto *new_ = get_video(new_id);
+ if (new_ == nullptr) {
+ dup_video(new_id, old_id);
} else {
- Video *new_ = new_it->second.get();
- CHECK(new_ != nullptr);
-
if (!old_->mime_type.empty() && old_->mime_type != new_->mime_type) {
LOG(INFO) << "Video has changed: mime_type = (" << old_->mime_type << ", " << new_->mime_type << ")";
}
- new_->is_changed = true;
if (old_->thumbnail != new_->thumbnail) {
// LOG_STATUS(td_->file_manager_->merge(new_->thumbnail.file_id, old_->thumbnail.file_id));
}
}
LOG_STATUS(td_->file_manager_->merge(new_id, old_id));
- if (can_delete_old) {
- videos_.erase(old_id);
- }
- return true;
}
-void VideosManager::create_video(FileId file_id, PhotoSize thumbnail, bool has_stickers,
- vector<FileId> &&sticker_file_ids, string file_name, string mime_type, int32 duration,
- Dimensions dimensions, bool supports_streaming, bool replace) {
+void VideosManager::create_video(FileId file_id, string minithumbnail, PhotoSize thumbnail,
+ AnimationSize animated_thumbnail, bool has_stickers, vector<FileId> &&sticker_file_ids,
+ string file_name, string mime_type, int32 duration, Dimensions dimensions,
+ bool supports_streaming, bool replace) {
auto v = make_unique<Video>();
v->file_id = file_id;
v->file_name = std::move(file_name);
v->mime_type = std::move(mime_type);
v->duration = max(duration, 0);
v->dimensions = dimensions;
+ if (!td_->auth_manager_->is_bot()) {
+ v->minithumbnail = std::move(minithumbnail);
+ }
v->thumbnail = std::move(thumbnail);
+ v->animated_thumbnail = std::move(animated_thumbnail);
v->supports_streaming = supports_streaming;
v->has_stickers = has_stickers;
v->sticker_file_ids = std::move(sticker_file_ids);
@@ -190,29 +189,35 @@ void VideosManager::create_video(FileId file_id, PhotoSize thumbnail, bool has_s
SecretInputMedia VideosManager::get_secret_input_media(FileId video_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption, BufferSlice thumbnail) const {
+ const string &caption, BufferSlice thumbnail,
+ int32 layer) const {
const Video *video = get_video(video_file_id);
CHECK(video != nullptr);
auto file_view = td_->file_manager_->get_file_view(video_file_id);
- auto &encryption_key = file_view.encryption_key();
- if (encryption_key.empty()) {
+ if (!file_view.is_encrypted_secret() || file_view.encryption_key().empty()) {
return SecretInputMedia{};
}
if (file_view.has_remote_location()) {
- input_file = file_view.remote_location().as_input_encrypted_file();
+ input_file = file_view.main_remote_location().as_input_encrypted_file();
}
if (!input_file) {
- return SecretInputMedia{};
+ return {};
}
if (video->thumbnail.file_id.is_valid() && thumbnail.empty()) {
return {};
}
- return SecretInputMedia{
- std::move(input_file),
- make_tl_object<secret_api::decryptedMessageMediaVideo>(
- std::move(thumbnail), video->thumbnail.dimensions.width, video->thumbnail.dimensions.height, video->duration,
- video->mime_type, video->dimensions.width, video->dimensions.height, narrow_cast<int32>(file_view.size()),
- BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()), caption)};
+ vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
+ attributes.emplace_back(make_tl_object<secret_api::documentAttributeVideo>(
+ 0, false, video->duration, video->dimensions.width, video->dimensions.height));
+
+ return {std::move(input_file),
+ std::move(thumbnail),
+ video->thumbnail.dimensions,
+ video->mime_type,
+ file_view,
+ std::move(attributes),
+ caption,
+ layer};
}
tl_object_ptr<telegram_api::InputMedia> VideosManager::get_input_media(
@@ -226,13 +231,13 @@ tl_object_ptr<telegram_api::InputMedia> VideosManager::get_input_media(
if (file_view.is_encrypted()) {
return nullptr;
}
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
+ if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
int32 flags = 0;
if (ttl != 0) {
flags |= telegram_api::inputMediaDocument::TTL_SECONDS_MASK;
}
- return make_tl_object<telegram_api::inputMediaDocument>(flags, file_view.remote_location().as_input_document(),
- ttl);
+ return make_tl_object<telegram_api::inputMediaDocument>(flags, file_view.main_remote_location().as_input_document(),
+ ttl, string());
}
if (file_view.has_url()) {
int32 flags = 0;
@@ -241,7 +246,6 @@ tl_object_ptr<telegram_api::InputMedia> VideosManager::get_input_media(
}
return make_tl_object<telegram_api::inputMediaDocumentExternal>(flags, file_view.url(), ttl);
}
- CHECK(!file_view.has_remote_location());
if (input_file != nullptr) {
const Video *video = get_video(file_id);
@@ -259,11 +263,8 @@ tl_object_ptr<telegram_api::InputMedia> VideosManager::get_input_media(
if (!video->file_name.empty()) {
attributes.push_back(make_tl_object<telegram_api::documentAttributeFilename>(video->file_name));
}
- int32 flags = 0;
+ int32 flags = telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK;
vector<tl_object_ptr<telegram_api::InputDocument>> added_stickers;
- if (ttl != 0 || !td_->auth_manager_->is_bot()) {
- flags |= telegram_api::inputMediaUploadedDocument::NOSOUND_VIDEO_MASK;
- }
if (video->has_stickers) {
flags |= telegram_api::inputMediaUploadedDocument::STICKERS_MASK;
added_stickers = td_->file_manager_->get_input_documents(video->sticker_file_ids);
@@ -279,8 +280,10 @@ tl_object_ptr<telegram_api::InputMedia> VideosManager::get_input_media(
flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK;
}
return make_tl_object<telegram_api::inputMediaUploadedDocument>(
- flags, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type, std::move(attributes),
- std::move(added_stickers), ttl);
+ flags, false /*ignored*/, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type,
+ std::move(attributes), std::move(added_stickers), ttl);
+ } else {
+ CHECK(!file_view.has_remote_location());
}
return nullptr;
diff --git a/protocols/Telegram/tdlib/td/td/telegram/VideosManager.h b/protocols/Telegram/tdlib/td/td/telegram/VideosManager.h
index 777327138f..c80361f27a 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/VideosManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/VideosManager.h
@@ -1,40 +1,42 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
+#include "td/telegram/Dimensions.h"
#include "td/telegram/files/FileId.h"
-#include "td/telegram/Photo.h"
+#include "td/telegram/PhotoSize.h"
#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
-
-#include <unordered_map>
+#include "td/utils/WaitFreeHashMap.h"
namespace td {
-class Td;
-} // namespace td
-namespace td {
+class Td;
class VideosManager {
public:
explicit VideosManager(Td *td);
+ VideosManager(const VideosManager &) = delete;
+ VideosManager &operator=(const VideosManager &) = delete;
+ VideosManager(VideosManager &&) = delete;
+ VideosManager &operator=(VideosManager &&) = delete;
+ ~VideosManager();
- int32 get_video_duration(FileId file_id);
+ int32 get_video_duration(FileId file_id) const;
- tl_object_ptr<td_api::video> get_video_object(FileId file_id);
+ tl_object_ptr<td_api::video> get_video_object(FileId file_id) const;
- void create_video(FileId file_id, PhotoSize thumbnail, bool has_stickers, vector<FileId> &&sticker_file_ids,
- string file_name, string mime_type, int32 duration, Dimensions dimensions, bool supports_streaming,
- bool replace);
+ void create_video(FileId file_id, string minithumbnail, PhotoSize thumbnail, AnimationSize animated_thumbnail,
+ bool has_stickers, vector<FileId> &&sticker_file_ids, string file_name, string mime_type,
+ int32 duration, Dimensions dimensions, bool supports_streaming, bool replace);
tl_object_ptr<telegram_api::InputMedia> get_input_media(FileId file_id,
tl_object_ptr<telegram_api::InputFile> input_file,
@@ -43,21 +45,23 @@ class VideosManager {
SecretInputMedia get_secret_input_media(FileId video_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption, BufferSlice thumbnail) const;
+ const string &caption, BufferSlice thumbnail, int32 layer) const;
FileId get_video_thumbnail_file_id(FileId file_id) const;
+ FileId get_video_animated_thumbnail_file_id(FileId file_id) const;
+
void delete_video_thumbnail(FileId file_id);
FileId dup_video(FileId new_id, FileId old_id);
- bool merge_videos(FileId new_id, FileId old_id, bool can_delete_old);
+ void merge_videos(FileId new_id, FileId old_id);
- template <class T>
- void store_video(FileId file_id, T &storer) const;
+ template <class StorerT>
+ void store_video(FileId file_id, StorerT &storer) const;
- template <class T>
- FileId parse_video(T &parser);
+ template <class ParserT>
+ FileId parse_video(ParserT &parser);
string get_video_search_text(FileId file_id) const;
@@ -68,7 +72,9 @@ class VideosManager {
string mime_type;
int32 duration = 0;
Dimensions dimensions;
+ string minithumbnail;
PhotoSize thumbnail;
+ AnimationSize animated_thumbnail;
bool supports_streaming = false;
@@ -76,16 +82,14 @@ class VideosManager {
vector<FileId> sticker_file_ids;
FileId file_id;
-
- bool is_changed = true;
};
const Video *get_video(FileId file_id) const;
- FileId on_get_video(std::unique_ptr<Video> new_video, bool replace);
+ FileId on_get_video(unique_ptr<Video> new_video, bool replace);
Td *td_;
- std::unordered_map<FileId, unique_ptr<Video>, FileIdHash> videos_;
+ WaitFreeHashMap<FileId, unique_ptr<Video>, FileIdHash> videos_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/VideosManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/VideosManager.hpp
index b93ce4c02c..44719623a0 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/VideosManager.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/VideosManager.hpp
@@ -1,57 +1,75 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/telegram/VideosManager.h"
#include "td/telegram/files/FileId.hpp"
#include "td/telegram/Photo.hpp"
+#include "td/telegram/Version.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/tl_helpers.h"
namespace td {
-template <class T>
-void VideosManager::store_video(FileId file_id, T &storer) const {
- auto it = videos_.find(file_id);
- CHECK(it != videos_.end());
- const Video *video = it->second.get();
+template <class StorerT>
+void VideosManager::store_video(FileId file_id, StorerT &storer) const {
+ const Video *video = get_video(file_id);
+ CHECK(video != nullptr);
+ bool has_animated_thumbnail = video->animated_thumbnail.file_id.is_valid();
BEGIN_STORE_FLAGS();
STORE_FLAG(video->has_stickers);
STORE_FLAG(video->supports_streaming);
+ STORE_FLAG(has_animated_thumbnail);
END_STORE_FLAGS();
store(video->file_name, storer);
store(video->mime_type, storer);
store(video->duration, storer);
store(video->dimensions, storer);
+ store(video->minithumbnail, storer);
store(video->thumbnail, storer);
store(file_id, storer);
if (video->has_stickers) {
store(video->sticker_file_ids, storer);
}
+ if (has_animated_thumbnail) {
+ store(video->animated_thumbnail, storer);
+ }
}
-template <class T>
-FileId VideosManager::parse_video(T &parser) {
+template <class ParserT>
+FileId VideosManager::parse_video(ParserT &parser) {
auto video = make_unique<Video>();
+ bool has_animated_thumbnail;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(video->has_stickers);
PARSE_FLAG(video->supports_streaming);
+ PARSE_FLAG(has_animated_thumbnail);
END_PARSE_FLAGS();
parse(video->file_name, parser);
parse(video->mime_type, parser);
parse(video->duration, parser);
parse(video->dimensions, parser);
+ if (parser.version() >= static_cast<int32>(Version::SupportMinithumbnails)) {
+ parse(video->minithumbnail, parser);
+ }
parse(video->thumbnail, parser);
parse(video->file_id, parser);
if (video->has_stickers) {
parse(video->sticker_file_ids, parser);
}
- return on_get_video(std::move(video), true);
+ if (has_animated_thumbnail) {
+ parse(video->animated_thumbnail, parser);
+ }
+ if (parser.get_error() != nullptr || !video->file_id.is_valid()) {
+ return FileId();
+ }
+ return on_get_video(std::move(video), false);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.cpp
index 0ba7b92291..572e637c28 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.cpp
@@ -1,52 +1,65 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/VoiceNotesManager.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/PromiseFuture.h"
-
-#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/Dimensions.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/Global.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/secret_api.h"
#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UpdatesManager.h"
#include "td/utils/buffer.h"
#include "td/utils/logging.h"
-#include "td/utils/misc.h"
-#include "td/utils/Status.h"
namespace td {
-VoiceNotesManager::VoiceNotesManager(Td *td) : td_(td) {
+VoiceNotesManager::VoiceNotesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
}
-int32 VoiceNotesManager::get_voice_note_duration(FileId file_id) {
- auto &voice_note = voice_notes_[file_id];
- CHECK(voice_note != nullptr);
+VoiceNotesManager::~VoiceNotesManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), voice_notes_, voice_note_messages_,
+ message_voice_notes_);
+}
+
+void VoiceNotesManager::tear_down() {
+ parent_.reset();
+}
+
+int32 VoiceNotesManager::get_voice_note_duration(FileId file_id) const {
+ auto voice_note = get_voice_note(file_id);
+ if (voice_note == nullptr) {
+ return 0;
+ }
return voice_note->duration;
}
-tl_object_ptr<td_api::voiceNote> VoiceNotesManager::get_voice_note_object(FileId file_id) {
+tl_object_ptr<td_api::voiceNote> VoiceNotesManager::get_voice_note_object(FileId file_id) const {
if (!file_id.is_valid()) {
return nullptr;
}
- auto &voice_note = voice_notes_[file_id];
+ auto voice_note = get_voice_note(file_id);
CHECK(voice_note != nullptr);
- voice_note->is_changed = false;
+ auto speech_recognition_result = voice_note->transcription_info == nullptr
+ ? nullptr
+ : voice_note->transcription_info->get_speech_recognition_result_object();
return make_tl_object<td_api::voiceNote>(voice_note->duration, voice_note->waveform, voice_note->mime_type,
+ std::move(speech_recognition_result),
td_->file_manager_->get_file_object(file_id));
}
-FileId VoiceNotesManager::on_get_voice_note(std::unique_ptr<VoiceNote> new_voice_note, bool replace) {
+FileId VoiceNotesManager::on_get_voice_note(unique_ptr<VoiceNote> new_voice_note, bool replace) {
auto file_id = new_voice_note->file_id;
+ CHECK(file_id.is_valid());
LOG(INFO) << "Receive voice note " << file_id;
auto &v = voice_notes_[file_id];
if (v == nullptr) {
@@ -55,83 +68,65 @@ FileId VoiceNotesManager::on_get_voice_note(std::unique_ptr<VoiceNote> new_voice
CHECK(v->file_id == new_voice_note->file_id);
if (v->mime_type != new_voice_note->mime_type) {
LOG(DEBUG) << "Voice note " << file_id << " info has changed";
- v->mime_type = new_voice_note->mime_type;
- v->is_changed = true;
+ v->mime_type = std::move(new_voice_note->mime_type);
}
if (v->duration != new_voice_note->duration || v->waveform != new_voice_note->waveform) {
LOG(DEBUG) << "Voice note " << file_id << " info has changed";
v->duration = new_voice_note->duration;
- v->waveform = new_voice_note->waveform;
- v->is_changed = true;
+ v->waveform = std::move(new_voice_note->waveform);
+ }
+ if (TranscriptionInfo::update_from(v->transcription_info, std::move(new_voice_note->transcription_info))) {
+ on_voice_note_transcription_completed(file_id);
}
}
return file_id;
}
-const VoiceNotesManager::VoiceNote *VoiceNotesManager::get_voice_note(FileId file_id) const {
- auto voice_note = voice_notes_.find(file_id);
- if (voice_note == voice_notes_.end()) {
- return nullptr;
- }
+VoiceNotesManager::VoiceNote *VoiceNotesManager::get_voice_note(FileId file_id) {
+ return voice_notes_.get_pointer(file_id);
+}
- CHECK(voice_note->second->file_id == file_id);
- return voice_note->second.get();
+const VoiceNotesManager::VoiceNote *VoiceNotesManager::get_voice_note(FileId file_id) const {
+ return voice_notes_.get_pointer(file_id);
}
FileId VoiceNotesManager::dup_voice_note(FileId new_id, FileId old_id) {
const VoiceNote *old_voice_note = get_voice_note(old_id);
CHECK(old_voice_note != nullptr);
auto &new_voice_note = voice_notes_[new_id];
- CHECK(!new_voice_note);
- new_voice_note = std::make_unique<VoiceNote>(*old_voice_note);
+ CHECK(new_voice_note == nullptr);
+ new_voice_note = make_unique<VoiceNote>();
new_voice_note->file_id = new_id;
+ new_voice_note->mime_type = old_voice_note->mime_type;
+ new_voice_note->duration = old_voice_note->duration;
+ new_voice_note->waveform = old_voice_note->waveform;
+ new_voice_note->transcription_info = TranscriptionInfo::copy_if_transcribed(old_voice_note->transcription_info);
return new_id;
}
-bool VoiceNotesManager::merge_voice_notes(FileId new_id, FileId old_id, bool can_delete_old) {
- if (!old_id.is_valid()) {
- LOG(ERROR) << "Old file id is invalid";
- return true;
- }
+void VoiceNotesManager::merge_voice_notes(FileId new_id, FileId old_id) {
+ CHECK(old_id.is_valid() && new_id.is_valid());
+ CHECK(new_id != old_id);
LOG(INFO) << "Merge voice notes " << new_id << " and " << old_id;
const VoiceNote *old_ = get_voice_note(old_id);
CHECK(old_ != nullptr);
- if (old_id == new_id) {
- return old_->is_changed;
- }
-
- auto new_it = voice_notes_.find(new_id);
- if (new_it == voice_notes_.end()) {
- auto &old = voice_notes_[old_id];
- old->is_changed = true;
- if (!can_delete_old) {
- dup_voice_note(new_id, old_id);
- } else {
- old->file_id = new_id;
- voice_notes_.emplace(new_id, std::move(old));
- }
- } else {
- VoiceNote *new_ = new_it->second.get();
- CHECK(new_ != nullptr);
+ const auto *new_ = get_voice_note(new_id);
+ if (new_ == nullptr) {
+ dup_voice_note(new_id, old_id);
+ } else {
if (!old_->mime_type.empty() && old_->mime_type != new_->mime_type) {
LOG(INFO) << "Voice note has changed: mime_type = (" << old_->mime_type << ", " << new_->mime_type << ")";
}
-
- new_->is_changed = true;
}
LOG_STATUS(td_->file_manager_->merge(new_id, old_id));
- if (can_delete_old) {
- voice_notes_.erase(old_id);
- }
- return true;
}
void VoiceNotesManager::create_voice_note(FileId file_id, string mime_type, int32 duration, string waveform,
bool replace) {
- auto v = std::make_unique<VoiceNote>();
+ auto v = make_unique<VoiceNote>();
v->file_id = file_id;
v->mime_type = std::move(mime_type);
v->duration = max(duration, 0);
@@ -139,31 +134,150 @@ void VoiceNotesManager::create_voice_note(FileId file_id, string mime_type, int3
on_get_voice_note(std::move(v), replace);
}
-SecretInputMedia VoiceNotesManager::get_secret_input_media(FileId voice_file_id,
- tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption) const {
- auto *voice_note = get_voice_note(voice_file_id);
+void VoiceNotesManager::register_voice_note(FileId voice_note_file_id, FullMessageId full_message_id,
+ const char *source) {
+ if (full_message_id.get_message_id().is_scheduled() || !full_message_id.get_message_id().is_server() ||
+ td_->auth_manager_->is_bot()) {
+ return;
+ }
+ LOG(INFO) << "Register voice note " << voice_note_file_id << " from " << full_message_id << " from " << source;
+ bool is_inserted = voice_note_messages_[voice_note_file_id].insert(full_message_id).second;
+ LOG_CHECK(is_inserted) << source << ' ' << voice_note_file_id << ' ' << full_message_id;
+ is_inserted = message_voice_notes_.emplace(full_message_id, voice_note_file_id).second;
+ CHECK(is_inserted);
+}
+
+void VoiceNotesManager::unregister_voice_note(FileId voice_note_file_id, FullMessageId full_message_id,
+ const char *source) {
+ if (full_message_id.get_message_id().is_scheduled() || !full_message_id.get_message_id().is_server() ||
+ td_->auth_manager_->is_bot()) {
+ return;
+ }
+ LOG(INFO) << "Unregister voice note " << voice_note_file_id << " from " << full_message_id << " from " << source;
+ auto &message_ids = voice_note_messages_[voice_note_file_id];
+ auto is_deleted = message_ids.erase(full_message_id) > 0;
+ LOG_CHECK(is_deleted) << source << ' ' << voice_note_file_id << ' ' << full_message_id;
+ if (message_ids.empty()) {
+ voice_note_messages_.erase(voice_note_file_id);
+ }
+ is_deleted = message_voice_notes_.erase(full_message_id) > 0;
+ CHECK(is_deleted);
+}
+
+void VoiceNotesManager::recognize_speech(FullMessageId full_message_id, Promise<Unit> &&promise) {
+ auto it = message_voice_notes_.find(full_message_id);
+ CHECK(it != message_voice_notes_.end());
+
+ auto file_id = it->second;
+ auto voice_note = get_voice_note(file_id);
CHECK(voice_note != nullptr);
- auto file_view = td_->file_manager_->get_file_view(voice_file_id);
- auto &encryption_key = file_view.encryption_key();
- if (encryption_key.empty()) {
+ if (voice_note->transcription_info == nullptr) {
+ voice_note->transcription_info = make_unique<TranscriptionInfo>();
+ }
+
+ auto handler = [actor_id = actor_id(this),
+ file_id](Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>> r_update) {
+ send_closure(actor_id, &VoiceNotesManager::on_transcribed_audio_update, file_id, true, std::move(r_update));
+ };
+ if (voice_note->transcription_info->recognize_speech(td_, full_message_id, std::move(promise), std::move(handler))) {
+ on_voice_note_transcription_updated(file_id);
+ }
+}
+
+void VoiceNotesManager::on_transcribed_audio_update(
+ FileId file_id, bool is_initial, Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>> r_update) {
+ if (G()->close_flag()) {
+ return;
+ }
+
+ auto voice_note = get_voice_note(file_id);
+ CHECK(voice_note != nullptr);
+ CHECK(voice_note->transcription_info != nullptr);
+
+ if (r_update.is_error()) {
+ auto promises = voice_note->transcription_info->on_failed_transcription(r_update.error().clone());
+ on_voice_note_transcription_updated(file_id);
+ fail_promises(promises, r_update.move_as_error());
+ return;
+ }
+ auto update = r_update.move_as_ok();
+ auto transcription_id = update->transcription_id_;
+ if (!update->pending_) {
+ auto promises = voice_note->transcription_info->on_final_transcription(std::move(update->text_), transcription_id);
+ on_voice_note_transcription_completed(file_id);
+ set_promises(promises);
+ } else {
+ auto is_changed =
+ voice_note->transcription_info->on_partial_transcription(std::move(update->text_), transcription_id);
+ if (is_changed) {
+ on_voice_note_transcription_updated(file_id);
+ }
+
+ if (is_initial) {
+ td_->updates_manager_->subscribe_to_transcribed_audio_updates(
+ transcription_id, [actor_id = actor_id(this),
+ file_id](Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>> r_update) {
+ send_closure(actor_id, &VoiceNotesManager::on_transcribed_audio_update, file_id, false,
+ std::move(r_update));
+ });
+ }
+ }
+}
+
+void VoiceNotesManager::on_voice_note_transcription_updated(FileId file_id) {
+ auto it = voice_note_messages_.find(file_id);
+ if (it != voice_note_messages_.end()) {
+ for (const auto &full_message_id : it->second) {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ }
+ }
+}
+
+void VoiceNotesManager::on_voice_note_transcription_completed(FileId file_id) {
+ auto it = voice_note_messages_.find(file_id);
+ if (it != voice_note_messages_.end()) {
+ for (const auto &full_message_id : it->second) {
+ td_->messages_manager_->on_update_message_content(full_message_id);
+ }
+ }
+}
+
+void VoiceNotesManager::rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise<Unit> &&promise) {
+ auto it = message_voice_notes_.find(full_message_id);
+ CHECK(it != message_voice_notes_.end());
+
+ auto file_id = it->second;
+ auto voice_note = get_voice_note(file_id);
+ CHECK(voice_note != nullptr);
+ if (voice_note->transcription_info == nullptr) {
+ return promise.set_value(Unit());
+ }
+ voice_note->transcription_info->rate_speech_recognition(td_, full_message_id, is_good, std::move(promise));
+}
+
+SecretInputMedia VoiceNotesManager::get_secret_input_media(FileId voice_note_file_id,
+ tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
+ const string &caption, int32 layer) const {
+ auto file_view = td_->file_manager_->get_file_view(voice_note_file_id);
+ if (!file_view.is_encrypted_secret() || file_view.encryption_key().empty()) {
return SecretInputMedia{};
}
if (file_view.has_remote_location()) {
- input_file = file_view.remote_location().as_input_encrypted_file();
+ input_file = file_view.main_remote_location().as_input_encrypted_file();
}
if (!input_file) {
return SecretInputMedia{};
}
+
+ auto *voice_note = get_voice_note(voice_note_file_id);
+ CHECK(voice_note != nullptr);
vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
attributes.push_back(make_tl_object<secret_api::documentAttributeAudio>(
- secret_api::documentAttributeAudio::Flags::VOICE_MASK | secret_api::documentAttributeAudio::Flags::WAVEFORM_MASK,
+ secret_api::documentAttributeAudio::VOICE_MASK | secret_api::documentAttributeAudio::WAVEFORM_MASK,
false /*ignored*/, voice_note->duration, "", "", BufferSlice(voice_note->waveform)));
- return SecretInputMedia{std::move(input_file),
- make_tl_object<secret_api::decryptedMessageMediaDocument>(
- BufferSlice(), 0, 0, voice_note->mime_type, narrow_cast<int32>(file_view.size()),
- BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()),
- std::move(attributes), caption)};
+
+ return {std::move(input_file), BufferSlice(), Dimensions(), voice_note->mime_type, file_view,
+ std::move(attributes), caption, layer};
}
tl_object_ptr<telegram_api::InputMedia> VoiceNotesManager::get_input_media(
@@ -172,13 +286,13 @@ tl_object_ptr<telegram_api::InputMedia> VoiceNotesManager::get_input_media(
if (file_view.is_encrypted()) {
return nullptr;
}
- if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
- return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.remote_location().as_input_document(), 0);
+ if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
+ return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.main_remote_location().as_input_document(), 0,
+ string());
}
if (file_view.has_url()) {
return make_tl_object<telegram_api::inputMediaDocumentExternal>(0, file_view.url(), 0);
}
- CHECK(!file_view.has_remote_location());
if (input_file != nullptr) {
const VoiceNote *voice_note = get_voice_note(file_id);
@@ -196,8 +310,10 @@ tl_object_ptr<telegram_api::InputMedia> VoiceNotesManager::get_input_media(
mime_type = "audio/ogg";
}
return make_tl_object<telegram_api::inputMediaUploadedDocument>(
- 0, false /*ignored*/, std::move(input_file), nullptr, mime_type, std::move(attributes),
+ 0, false /*ignored*/, false /*ignored*/, std::move(input_file), nullptr, mime_type, std::move(attributes),
vector<tl_object_ptr<telegram_api::InputDocument>>(), 0);
+ } else {
+ CHECK(!file_view.has_remote_location());
}
return nullptr;
diff --git a/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.h b/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.h
index ba77f003ea..af74a850a3 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.h
@@ -1,53 +1,70 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/SecretInputMedia.h"
#include "td/telegram/td_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/TranscriptionInfo.h"
-#include "td/telegram/files/FileId.h"
-#include "td/telegram/SecretInputMedia.h"
+#include "td/actor/actor.h"
#include "td/utils/common.h"
-
-#include <unordered_map>
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Status.h"
+#include "td/utils/WaitFreeHashMap.h"
namespace td {
-class Td;
-} // namespace td
-namespace td {
+class Td;
-class VoiceNotesManager {
+class VoiceNotesManager final : public Actor {
public:
- explicit VoiceNotesManager(Td *td);
+ VoiceNotesManager(Td *td, ActorShared<> parent);
+ VoiceNotesManager(const VoiceNotesManager &) = delete;
+ VoiceNotesManager &operator=(const VoiceNotesManager &) = delete;
+ VoiceNotesManager(VoiceNotesManager &&) = delete;
+ VoiceNotesManager &operator=(VoiceNotesManager &&) = delete;
+ ~VoiceNotesManager() final;
- int32 get_voice_note_duration(FileId file_id);
+ int32 get_voice_note_duration(FileId file_id) const;
- tl_object_ptr<td_api::voiceNote> get_voice_note_object(FileId file_id);
+ tl_object_ptr<td_api::voiceNote> get_voice_note_object(FileId file_id) const;
void create_voice_note(FileId file_id, string mime_type, int32 duration, string waveform, bool replace);
+ void register_voice_note(FileId voice_note_file_id, FullMessageId full_message_id, const char *source);
+
+ void unregister_voice_note(FileId voice_note_file_id, FullMessageId full_message_id, const char *source);
+
+ void recognize_speech(FullMessageId full_message_id, Promise<Unit> &&promise);
+
+ void rate_speech_recognition(FullMessageId full_message_id, bool is_good, Promise<Unit> &&promise);
+
tl_object_ptr<telegram_api::InputMedia> get_input_media(FileId file_id,
tl_object_ptr<telegram_api::InputFile> input_file) const;
- SecretInputMedia get_secret_input_media(FileId voice_file_id,
+ SecretInputMedia get_secret_input_media(FileId voice_note_file_id,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
- const string &caption) const;
+ const string &caption, int32 layer) const;
FileId dup_voice_note(FileId new_id, FileId old_id);
- bool merge_voice_notes(FileId new_id, FileId old_id, bool can_delete_old);
+ void merge_voice_notes(FileId new_id, FileId old_id);
- template <class T>
- void store_voice_note(FileId file_id, T &storer) const;
+ template <class StorerT>
+ void store_voice_note(FileId file_id, StorerT &storer) const;
- template <class T>
- FileId parse_voice_note(T &parser);
+ template <class ParserT>
+ FileId parse_voice_note(ParserT &parser);
private:
class VoiceNote {
@@ -55,18 +72,33 @@ class VoiceNotesManager {
string mime_type;
int32 duration = 0;
string waveform;
+ unique_ptr<TranscriptionInfo> transcription_info;
FileId file_id;
-
- bool is_changed = true;
};
+ VoiceNote *get_voice_note(FileId file_id);
+
const VoiceNote *get_voice_note(FileId file_id) const;
- FileId on_get_voice_note(std::unique_ptr<VoiceNote> new_voice_note, bool replace);
+ FileId on_get_voice_note(unique_ptr<VoiceNote> new_voice_note, bool replace);
+
+ void on_voice_note_transcription_updated(FileId file_id);
+
+ void on_voice_note_transcription_completed(FileId file_id);
+
+ void on_transcribed_audio_update(FileId file_id, bool is_initial,
+ Result<telegram_api::object_ptr<telegram_api::updateTranscribedAudio>> r_update);
+
+ void tear_down() final;
Td *td_;
- std::unordered_map<FileId, unique_ptr<VoiceNote>, FileIdHash> voice_notes_;
+ ActorShared<> parent_;
+
+ WaitFreeHashMap<FileId, unique_ptr<VoiceNote>, FileIdHash> voice_notes_;
+
+ FlatHashMap<FileId, FlatHashSet<FullMessageId, FullMessageIdHash>, FileIdHash> voice_note_messages_;
+ FlatHashMap<FullMessageId, FileId, FullMessageIdHash> message_voice_notes_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.hpp
index 6165f42607..49cf69b522 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/VoiceNotesManager.hpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,31 +9,80 @@
#include "td/telegram/VoiceNotesManager.h"
#include "td/telegram/files/FileId.hpp"
+#include "td/telegram/TranscriptionInfo.hpp"
+#include "td/telegram/Version.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/tl_helpers.h"
namespace td {
-template <class T>
-void VoiceNotesManager::store_voice_note(FileId file_id, T &storer) const {
- auto it = voice_notes_.find(file_id);
- CHECK(it != voice_notes_.end());
- const VoiceNote *voice_note = it->second.get();
- store(voice_note->mime_type, storer);
- store(voice_note->duration, storer);
- store(voice_note->waveform, storer);
+template <class StorerT>
+void VoiceNotesManager::store_voice_note(FileId file_id, StorerT &storer) const {
+ const VoiceNote *voice_note = get_voice_note(file_id);
+ CHECK(voice_note != nullptr);
+ bool has_mime_type = !voice_note->mime_type.empty();
+ bool has_duration = voice_note->duration != 0;
+ bool has_waveform = !voice_note->waveform.empty();
+ bool is_transcribed = voice_note->transcription_info != nullptr && voice_note->transcription_info->is_transcribed();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_mime_type);
+ STORE_FLAG(has_duration);
+ STORE_FLAG(has_waveform);
+ STORE_FLAG(is_transcribed);
+ END_STORE_FLAGS();
+ if (has_mime_type) {
+ store(voice_note->mime_type, storer);
+ }
+ if (has_duration) {
+ store(voice_note->duration, storer);
+ }
+ if (has_waveform) {
+ store(voice_note->waveform, storer);
+ }
+ if (is_transcribed) {
+ store(voice_note->transcription_info, storer);
+ }
store(file_id, storer);
}
-template <class T>
-FileId VoiceNotesManager::parse_voice_note(T &parser) {
+template <class ParserT>
+FileId VoiceNotesManager::parse_voice_note(ParserT &parser) {
auto voice_note = make_unique<VoiceNote>();
- parse(voice_note->mime_type, parser);
- parse(voice_note->duration, parser);
- parse(voice_note->waveform, parser);
+ bool has_mime_type;
+ bool has_duration;
+ bool has_waveform;
+ bool is_transcribed;
+ if (parser.version() >= static_cast<int32>(Version::AddVoiceNoteFlags)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_mime_type);
+ PARSE_FLAG(has_duration);
+ PARSE_FLAG(has_waveform);
+ PARSE_FLAG(is_transcribed);
+ END_PARSE_FLAGS();
+ } else {
+ has_mime_type = true;
+ has_duration = true;
+ has_waveform = true;
+ is_transcribed = false;
+ }
+ if (has_mime_type) {
+ parse(voice_note->mime_type, parser);
+ }
+ if (has_duration) {
+ parse(voice_note->duration, parser);
+ }
+ if (has_waveform) {
+ parse(voice_note->waveform, parser);
+ }
+ if (is_transcribed) {
+ parse(voice_note->transcription_info, parser);
+ }
parse(voice_note->file_id, parser);
- return on_get_voice_note(std::move(voice_note), true);
+ if (parser.get_error() != nullptr || !voice_note->file_id.is_valid()) {
+ return FileId();
+ }
+ return on_get_voice_note(std::move(voice_note), false);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/WebPageBlock.cpp b/protocols/Telegram/tdlib/td/td/telegram/WebPageBlock.cpp
new file mode 100644
index 0000000000..50e0fac88f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/WebPageBlock.cpp
@@ -0,0 +1,2424 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/WebPageBlock.h"
+
+#include "td/telegram/AnimationsManager.h"
+#include "td/telegram/AnimationsManager.hpp"
+#include "td/telegram/AudiosManager.h"
+#include "td/telegram/AudiosManager.hpp"
+#include "td/telegram/ChannelId.h"
+#include "td/telegram/ContactsManager.h"
+#include "td/telegram/DialogId.h"
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/DocumentsManager.h"
+#include "td/telegram/DocumentsManager.hpp"
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/LinkManager.h"
+#include "td/telegram/Location.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/Photo.hpp"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/Version.h"
+#include "td/telegram/VideosManager.h"
+#include "td/telegram/VideosManager.hpp"
+#include "td/telegram/VoiceNotesManager.h"
+#include "td/telegram/VoiceNotesManager.hpp"
+#include "td/telegram/WebPageId.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/HttpUrl.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+#include <type_traits>
+#include <unordered_map>
+
+namespace td {
+
+class RichText;
+
+struct GetWebPageBlockObjectContext {
+ Td *td_ = nullptr;
+ Slice base_url_;
+ string real_url_host_;
+ string real_url_rhash_;
+
+ bool is_first_pass_ = true;
+ bool has_anchor_urls_ = false;
+ std::unordered_map<Slice, const RichText *, SliceHash> anchors_; // anchor -> text
+};
+
+static vector<td_api::object_ptr<td_api::PageBlock>> get_page_blocks_object(
+ const vector<unique_ptr<WebPageBlock>> &page_blocks, GetWebPageBlockObjectContext *context) {
+ return transform(page_blocks, [context](const unique_ptr<WebPageBlock> &page_block) {
+ return page_block->get_page_block_object(context);
+ });
+}
+
+class RichText {
+ static vector<td_api::object_ptr<td_api::RichText>> get_rich_texts_object(const vector<RichText> &rich_texts,
+ GetWebPageBlockObjectContext *context) {
+ return transform(rich_texts,
+ [context](const RichText &rich_text) { return rich_text.get_rich_text_object(context); });
+ }
+
+ public:
+ enum class Type : int32 {
+ Plain,
+ Bold,
+ Italic,
+ Underline,
+ Strikethrough,
+ Fixed,
+ Url,
+ EmailAddress,
+ Concatenation,
+ Subscript,
+ Superscript,
+ Marked,
+ PhoneNumber,
+ Icon,
+ Anchor
+ };
+ Type type = Type::Plain;
+ string content;
+ vector<RichText> texts;
+ FileId document_file_id;
+ WebPageId web_page_id;
+
+ bool empty() const {
+ return type == Type::Plain && content.empty();
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const {
+ if (type == RichText::Type::Icon) {
+ CHECK(document_file_id.is_valid());
+ Document(Document::Type::General, document_file_id).append_file_ids(td, file_ids);
+ } else {
+ for (auto &text : texts) {
+ text.append_file_ids(td, file_ids);
+ }
+ }
+ }
+
+ td_api::object_ptr<td_api::RichText> get_rich_text_object(GetWebPageBlockObjectContext *context) const {
+ switch (type) {
+ case RichText::Type::Plain:
+ return make_tl_object<td_api::richTextPlain>(content);
+ case RichText::Type::Bold:
+ return make_tl_object<td_api::richTextBold>(texts[0].get_rich_text_object(context));
+ case RichText::Type::Italic:
+ return make_tl_object<td_api::richTextItalic>(texts[0].get_rich_text_object(context));
+ case RichText::Type::Underline:
+ return make_tl_object<td_api::richTextUnderline>(texts[0].get_rich_text_object(context));
+ case RichText::Type::Strikethrough:
+ return make_tl_object<td_api::richTextStrikethrough>(texts[0].get_rich_text_object(context));
+ case RichText::Type::Fixed:
+ return make_tl_object<td_api::richTextFixed>(texts[0].get_rich_text_object(context));
+ case RichText::Type::Url:
+ if (!context->base_url_.empty() && begins_with(content, context->base_url_) &&
+ content[context->base_url_.size()] == '#') {
+ if (context->is_first_pass_) {
+ context->has_anchor_urls_ = true;
+ } else {
+ auto anchor = Slice(content).substr(context->base_url_.size() + 1);
+ // https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-indicated-part-of-the-document
+ for (int i = 0; i < 2; i++) {
+ string url_decoded_anchor;
+ if (i == 1) { // try to url_decode anchor
+ url_decoded_anchor = url_decode(anchor, false);
+ anchor = url_decoded_anchor;
+ }
+ auto it = context->anchors_.find(anchor);
+ if (it != context->anchors_.end()) {
+ if (it->second == nullptr) {
+ return make_tl_object<td_api::richTextAnchorLink>(texts[0].get_rich_text_object(context),
+ anchor.str(), content);
+ } else {
+ return make_tl_object<td_api::richTextReference>(texts[0].get_rich_text_object(context), anchor.str(),
+ content);
+ }
+ }
+ }
+ }
+ }
+ if (!context->real_url_rhash_.empty() && get_url_host(content) == context->real_url_host_) {
+ if (context->is_first_pass_) {
+ context->has_anchor_urls_ = true;
+ } else {
+ return make_tl_object<td_api::richTextUrl>(
+ texts[0].get_rich_text_object(context),
+ LinkManager::get_instant_view_link(content, context->real_url_rhash_), true);
+ }
+ }
+ return make_tl_object<td_api::richTextUrl>(texts[0].get_rich_text_object(context), content,
+ web_page_id.is_valid());
+ case RichText::Type::EmailAddress:
+ return make_tl_object<td_api::richTextEmailAddress>(texts[0].get_rich_text_object(context), content);
+ case RichText::Type::Concatenation:
+ return make_tl_object<td_api::richTexts>(get_rich_texts_object(texts, context));
+ case RichText::Type::Subscript:
+ return make_tl_object<td_api::richTextSubscript>(texts[0].get_rich_text_object(context));
+ case RichText::Type::Superscript:
+ return make_tl_object<td_api::richTextSuperscript>(texts[0].get_rich_text_object(context));
+ case RichText::Type::Marked:
+ return make_tl_object<td_api::richTextMarked>(texts[0].get_rich_text_object(context));
+ case RichText::Type::PhoneNumber:
+ return make_tl_object<td_api::richTextPhoneNumber>(texts[0].get_rich_text_object(context), content);
+ case RichText::Type::Icon: {
+ auto dimensions = to_integer<uint32>(content);
+ auto width = static_cast<int32>(dimensions / 65536);
+ auto height = static_cast<int32>(dimensions % 65536);
+ return make_tl_object<td_api::richTextIcon>(
+ context->td_->documents_manager_->get_document_object(document_file_id, PhotoFormat::Jpeg), width, height);
+ }
+ case RichText::Type::Anchor: {
+ if (context->is_first_pass_) {
+ context->anchors_.emplace(Slice(content), texts[0].empty() ? nullptr : &texts[0]);
+ }
+ if (texts[0].empty()) {
+ return make_tl_object<td_api::richTextAnchor>(content);
+ }
+ auto result = make_tl_object<td_api::richTexts>();
+ result->texts_.push_back(make_tl_object<td_api::richTextAnchor>(content));
+ result->texts_.push_back(texts[0].get_rich_text_object(context));
+ return std::move(result);
+ }
+ }
+ UNREACHABLE();
+ return nullptr;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(type, storer);
+ store(content, storer);
+ store(texts, storer);
+ if (type == Type::Icon) {
+ storer.context()->td().get_actor_unsafe()->documents_manager_->store_document(document_file_id, storer);
+ }
+ if (type == Type::Url) {
+ store(web_page_id, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(type, parser);
+ parse(content, parser);
+ parse(texts, parser);
+ if (type == Type::Icon) {
+ document_file_id = parser.context()->td().get_actor_unsafe()->documents_manager_->parse_document(parser);
+ if (!document_file_id.is_valid()) {
+ LOG(ERROR) << "Failed to load document from database";
+ *this = RichText();
+ }
+ } else {
+ document_file_id = FileId();
+ }
+ if (type == Type::Url && parser.version() >= static_cast<int32>(Version::SupportInstantView2_0)) {
+ parse(web_page_id, parser);
+ } else {
+ web_page_id = WebPageId();
+ }
+ }
+};
+
+namespace {
+
+class WebPageBlockCaption {
+ public:
+ RichText text;
+ RichText credit;
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const {
+ text.append_file_ids(td, file_ids);
+ credit.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::pageBlockCaption> get_page_block_caption_object(
+ GetWebPageBlockObjectContext *context) const {
+ return td_api::make_object<td_api::pageBlockCaption>(text.get_rich_text_object(context),
+ credit.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(text, storer);
+ store(credit, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(text, parser);
+ if (parser.version() >= static_cast<int32>(Version::SupportInstantView2_0)) {
+ parse(credit, parser);
+ } else {
+ credit = RichText();
+ }
+ }
+};
+
+class WebPageBlockTableCell {
+ public:
+ RichText text;
+ bool is_header = false;
+ bool align_left = false;
+ bool align_center = false;
+ bool align_right = false;
+ bool valign_top = false;
+ bool valign_middle = false;
+ bool valign_bottom = false;
+ int32 colspan = 1;
+ int32 rowspan = 1;
+
+ td_api::object_ptr<td_api::pageBlockTableCell> get_page_block_table_cell_object(
+ GetWebPageBlockObjectContext *context) const {
+ auto align = [&]() -> td_api::object_ptr<td_api::PageBlockHorizontalAlignment> {
+ if (align_left) {
+ return td_api::make_object<td_api::pageBlockHorizontalAlignmentLeft>();
+ }
+ if (align_center) {
+ return td_api::make_object<td_api::pageBlockHorizontalAlignmentCenter>();
+ }
+ if (align_right) {
+ return td_api::make_object<td_api::pageBlockHorizontalAlignmentRight>();
+ }
+ UNREACHABLE();
+ return nullptr;
+ }();
+ auto valign = [&]() -> td_api::object_ptr<td_api::PageBlockVerticalAlignment> {
+ if (valign_top) {
+ return td_api::make_object<td_api::pageBlockVerticalAlignmentTop>();
+ }
+ if (valign_middle) {
+ return td_api::make_object<td_api::pageBlockVerticalAlignmentMiddle>();
+ }
+ if (valign_bottom) {
+ return td_api::make_object<td_api::pageBlockVerticalAlignmentBottom>();
+ }
+ UNREACHABLE();
+ return nullptr;
+ }();
+ return td_api::make_object<td_api::pageBlockTableCell>(text.empty() ? nullptr : text.get_rich_text_object(context),
+ is_header, colspan, rowspan, std::move(align),
+ std::move(valign));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ bool has_text = !text.empty();
+ bool has_colspan = colspan != 1;
+ bool has_rowspan = rowspan != 1;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_header);
+ STORE_FLAG(align_left);
+ STORE_FLAG(align_center);
+ STORE_FLAG(align_right);
+ STORE_FLAG(valign_top);
+ STORE_FLAG(valign_middle);
+ STORE_FLAG(valign_bottom);
+ STORE_FLAG(has_text);
+ STORE_FLAG(has_colspan);
+ STORE_FLAG(has_rowspan);
+ END_STORE_FLAGS();
+ if (has_text) {
+ store(text, storer);
+ }
+ if (has_colspan) {
+ store(colspan, storer);
+ }
+ if (has_rowspan) {
+ store(rowspan, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ bool has_text;
+ bool has_colspan;
+ bool has_rowspan;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_header);
+ PARSE_FLAG(align_left);
+ PARSE_FLAG(align_center);
+ PARSE_FLAG(align_right);
+ PARSE_FLAG(valign_top);
+ PARSE_FLAG(valign_middle);
+ PARSE_FLAG(valign_bottom);
+ PARSE_FLAG(has_text);
+ PARSE_FLAG(has_colspan);
+ PARSE_FLAG(has_rowspan);
+ END_PARSE_FLAGS();
+ if (has_text) {
+ parse(text, parser);
+ }
+ if (has_colspan) {
+ parse(colspan, parser);
+ }
+ if (has_rowspan) {
+ parse(rowspan, parser);
+ }
+ }
+};
+
+class RelatedArticle {
+ public:
+ string url;
+ WebPageId web_page_id;
+ string title;
+ string description;
+ Photo photo;
+ string author;
+ int32 published_date = 0;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ bool has_title = !title.empty();
+ bool has_description = !description.empty();
+ bool has_photo = !photo.is_empty();
+ bool has_author = !author.empty();
+ bool has_date = published_date != 0;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_title);
+ STORE_FLAG(has_description);
+ STORE_FLAG(has_photo);
+ STORE_FLAG(has_author);
+ STORE_FLAG(has_date);
+ END_STORE_FLAGS();
+ store(url, storer);
+ store(web_page_id, storer);
+ if (has_title) {
+ store(title, storer);
+ }
+ if (has_description) {
+ store(description, storer);
+ }
+ if (has_photo) {
+ store(photo, storer);
+ }
+ if (has_author) {
+ store(author, storer);
+ }
+ if (has_date) {
+ store(published_date, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ bool has_title;
+ bool has_description;
+ bool has_photo;
+ bool has_author;
+ bool has_date;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_title);
+ PARSE_FLAG(has_description);
+ PARSE_FLAG(has_photo);
+ PARSE_FLAG(has_author);
+ PARSE_FLAG(has_date);
+ END_PARSE_FLAGS();
+ parse(url, parser);
+ parse(web_page_id, parser);
+ if (has_title) {
+ parse(title, parser);
+ }
+ if (has_description) {
+ parse(description, parser);
+ }
+ if (has_photo) {
+ parse(photo, parser);
+ }
+ if (has_author) {
+ parse(author, parser);
+ }
+ if (has_date) {
+ parse(published_date, parser);
+ }
+ }
+};
+
+class WebPageBlockTitle final : public WebPageBlock {
+ RichText title;
+
+ public:
+ WebPageBlockTitle() = default;
+
+ explicit WebPageBlockTitle(RichText &&title) : title(std::move(title)) {
+ }
+
+ Type get_type() const final {
+ return Type::Title;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ title.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockTitle>(title.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(title, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(title, parser);
+ }
+};
+
+class WebPageBlockSubtitle final : public WebPageBlock {
+ RichText subtitle;
+
+ public:
+ WebPageBlockSubtitle() = default;
+ explicit WebPageBlockSubtitle(RichText &&subtitle) : subtitle(std::move(subtitle)) {
+ }
+
+ Type get_type() const final {
+ return Type::Subtitle;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ subtitle.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockSubtitle>(subtitle.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(subtitle, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(subtitle, parser);
+ }
+};
+
+class WebPageBlockAuthorDate final : public WebPageBlock {
+ RichText author;
+ int32 date = 0;
+
+ public:
+ WebPageBlockAuthorDate() = default;
+ WebPageBlockAuthorDate(RichText &&author, int32 date) : author(std::move(author)), date(max(date, 0)) {
+ }
+
+ Type get_type() const final {
+ return Type::AuthorDate;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ author.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockAuthorDate>(author.get_rich_text_object(context), date);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(author, storer);
+ store(date, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(author, parser);
+ parse(date, parser);
+ }
+};
+
+class WebPageBlockHeader final : public WebPageBlock {
+ RichText header;
+
+ public:
+ WebPageBlockHeader() = default;
+ explicit WebPageBlockHeader(RichText &&header) : header(std::move(header)) {
+ }
+
+ Type get_type() const final {
+ return Type::Header;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ header.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockHeader>(header.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(header, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(header, parser);
+ }
+};
+
+class WebPageBlockSubheader final : public WebPageBlock {
+ RichText subheader;
+
+ public:
+ WebPageBlockSubheader() = default;
+ explicit WebPageBlockSubheader(RichText &&subheader) : subheader(std::move(subheader)) {
+ }
+
+ Type get_type() const final {
+ return Type::Subheader;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ subheader.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockSubheader>(subheader.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(subheader, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(subheader, parser);
+ }
+};
+
+class WebPageBlockKicker final : public WebPageBlock {
+ RichText kicker;
+
+ public:
+ WebPageBlockKicker() = default;
+ explicit WebPageBlockKicker(RichText &&kicker) : kicker(std::move(kicker)) {
+ }
+
+ Type get_type() const final {
+ return Type::Kicker;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ kicker.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockKicker>(kicker.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(kicker, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(kicker, parser);
+ }
+};
+
+class WebPageBlockParagraph final : public WebPageBlock {
+ RichText text;
+
+ public:
+ WebPageBlockParagraph() = default;
+ explicit WebPageBlockParagraph(RichText &&text) : text(std::move(text)) {
+ }
+
+ Type get_type() const final {
+ return Type::Paragraph;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ text.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockParagraph>(text.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(text, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(text, parser);
+ }
+};
+
+class WebPageBlockPreformatted final : public WebPageBlock {
+ RichText text;
+ string language;
+
+ public:
+ WebPageBlockPreformatted() = default;
+ WebPageBlockPreformatted(RichText &&text, string &&language) : text(std::move(text)), language(std::move(language)) {
+ }
+
+ Type get_type() const final {
+ return Type::Preformatted;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ text.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockPreformatted>(text.get_rich_text_object(context), language);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(text, storer);
+ store(language, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(text, parser);
+ parse(language, parser);
+ }
+};
+
+class WebPageBlockFooter final : public WebPageBlock {
+ RichText footer;
+
+ public:
+ WebPageBlockFooter() = default;
+ explicit WebPageBlockFooter(RichText &&footer) : footer(std::move(footer)) {
+ }
+
+ Type get_type() const final {
+ return Type::Footer;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ footer.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockFooter>(footer.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(footer, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(footer, parser);
+ }
+};
+
+class WebPageBlockDivider final : public WebPageBlock {
+ public:
+ Type get_type() const final {
+ return Type::Divider;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockDivider>();
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ }
+};
+
+class WebPageBlockAnchor final : public WebPageBlock {
+ string name;
+
+ public:
+ WebPageBlockAnchor() = default;
+ explicit WebPageBlockAnchor(string &&name) : name(std::move(name)) {
+ }
+
+ Type get_type() const final {
+ return Type::Anchor;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ if (context->is_first_pass_) {
+ context->anchors_.emplace(name, nullptr);
+ }
+ return make_tl_object<td_api::pageBlockAnchor>(name);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(name, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(name, parser);
+ }
+};
+
+class WebPageBlockList final : public WebPageBlock {
+ public:
+ struct Item {
+ string label;
+ vector<unique_ptr<WebPageBlock>> page_blocks;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(label, storer);
+ store(page_blocks, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(label, parser);
+ parse(page_blocks, parser);
+ }
+ };
+
+ private:
+ vector<Item> items;
+
+ static td_api::object_ptr<td_api::pageBlockListItem> get_page_block_list_item_object(const Item &item,
+ Context *context) {
+ // if label is empty, then Bullet U+2022 is used as a label
+ return td_api::make_object<td_api::pageBlockListItem>(item.label.empty() ? "\xE2\x80\xA2" : item.label,
+ get_page_blocks_object(item.page_blocks, context));
+ }
+
+ public:
+ WebPageBlockList() = default;
+ explicit WebPageBlockList(vector<Item> &&items) : items(std::move(items)) {
+ }
+
+ Type get_type() const final {
+ return Type::List;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ for (auto &item : items) {
+ for (auto &page_block : item.page_blocks) {
+ page_block->append_file_ids(td, file_ids);
+ }
+ }
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return td_api::make_object<td_api::pageBlockList>(
+ transform(items, [context](const Item &item) { return get_page_block_list_item_object(item, context); }));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(items, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+
+ if (parser.version() >= static_cast<int32>(Version::SupportInstantView2_0)) {
+ parse(items, parser);
+ } else {
+ vector<RichText> text_items;
+ bool is_ordered;
+
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_ordered);
+ END_PARSE_FLAGS();
+
+ parse(text_items, parser);
+
+ int pos = 0;
+ items.reserve(text_items.size());
+ for (auto &text_item : text_items) {
+ Item item;
+ if (is_ordered) {
+ pos++;
+ item.label = (PSTRING() << pos << '.');
+ }
+ item.page_blocks.push_back(make_unique<WebPageBlockParagraph>(std::move(text_item)));
+ items.push_back(std::move(item));
+ }
+ }
+ }
+};
+
+class WebPageBlockBlockQuote final : public WebPageBlock {
+ RichText text;
+ RichText credit;
+
+ public:
+ WebPageBlockBlockQuote() = default;
+ WebPageBlockBlockQuote(RichText &&text, RichText &&credit) : text(std::move(text)), credit(std::move(credit)) {
+ }
+
+ Type get_type() const final {
+ return Type::BlockQuote;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ text.append_file_ids(td, file_ids);
+ credit.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockBlockQuote>(text.get_rich_text_object(context),
+ credit.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(text, storer);
+ store(credit, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(text, parser);
+ parse(credit, parser);
+ }
+};
+
+class WebPageBlockPullQuote final : public WebPageBlock {
+ RichText text;
+ RichText credit;
+
+ public:
+ WebPageBlockPullQuote() = default;
+ WebPageBlockPullQuote(RichText &&text, RichText &&credit) : text(std::move(text)), credit(std::move(credit)) {
+ }
+
+ Type get_type() const final {
+ return Type::PullQuote;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ text.append_file_ids(td, file_ids);
+ credit.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockPullQuote>(text.get_rich_text_object(context),
+ credit.get_rich_text_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(text, storer);
+ store(credit, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(text, parser);
+ parse(credit, parser);
+ }
+};
+
+class WebPageBlockAnimation final : public WebPageBlock {
+ FileId animation_file_id;
+ WebPageBlockCaption caption;
+ bool need_autoplay = false;
+
+ public:
+ WebPageBlockAnimation() = default;
+ WebPageBlockAnimation(FileId animation_file_id, WebPageBlockCaption &&caption, bool need_autoplay)
+ : animation_file_id(animation_file_id), caption(std::move(caption)), need_autoplay(need_autoplay) {
+ }
+
+ Type get_type() const final {
+ return Type::Animation;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ caption.append_file_ids(td, file_ids);
+ Document(Document::Type::Animation, animation_file_id).append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockAnimation>(
+ context->td_->animations_manager_->get_animation_object(animation_file_id),
+ caption.get_page_block_caption_object(context), need_autoplay);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+
+ bool has_empty_animation = !animation_file_id.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(need_autoplay);
+ STORE_FLAG(has_empty_animation);
+ END_STORE_FLAGS();
+
+ if (!has_empty_animation) {
+ storer.context()->td().get_actor_unsafe()->animations_manager_->store_animation(animation_file_id, storer);
+ }
+ store(caption, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+
+ bool has_empty_animation;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(need_autoplay);
+ PARSE_FLAG(has_empty_animation);
+ END_PARSE_FLAGS();
+
+ if (parser.version() >= static_cast<int32>(Version::FixWebPageInstantViewDatabase)) {
+ if (!has_empty_animation) {
+ animation_file_id = parser.context()->td().get_actor_unsafe()->animations_manager_->parse_animation(parser);
+ } else {
+ animation_file_id = FileId();
+ }
+ } else {
+ animation_file_id = FileId();
+ parser.set_error("Wrong stored object");
+ }
+ parse(caption, parser);
+ }
+};
+
+class WebPageBlockPhoto final : public WebPageBlock {
+ Photo photo;
+ WebPageBlockCaption caption;
+ string url;
+ WebPageId web_page_id;
+
+ public:
+ WebPageBlockPhoto() = default;
+ WebPageBlockPhoto(Photo &&photo, WebPageBlockCaption &&caption, string &&url, WebPageId web_page_id)
+ : photo(std::move(photo)), caption(std::move(caption)), url(std::move(url)), web_page_id(web_page_id) {
+ }
+
+ Type get_type() const final {
+ return Type::Photo;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ append(file_ids, photo_get_file_ids(photo));
+ caption.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockPhoto>(get_photo_object(context->td_->file_manager_.get(), photo),
+ caption.get_page_block_caption_object(context), url);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(photo, storer);
+ store(caption, storer);
+ store(url, storer);
+ store(web_page_id, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(photo, parser);
+ parse(caption, parser);
+ if (parser.version() >= static_cast<int32>(Version::SupportInstantView2_0)) {
+ parse(url, parser);
+ parse(web_page_id, parser);
+ } else {
+ url.clear();
+ web_page_id = WebPageId();
+ }
+ }
+};
+
+class WebPageBlockVideo final : public WebPageBlock {
+ FileId video_file_id;
+ WebPageBlockCaption caption;
+ bool need_autoplay = false;
+ bool is_looped = false;
+
+ public:
+ WebPageBlockVideo() = default;
+ WebPageBlockVideo(FileId video_file_id, WebPageBlockCaption &&caption, bool need_autoplay, bool is_looped)
+ : video_file_id(video_file_id), caption(std::move(caption)), need_autoplay(need_autoplay), is_looped(is_looped) {
+ }
+
+ Type get_type() const final {
+ return Type::Video;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ caption.append_file_ids(td, file_ids);
+ Document(Document::Type::Video, video_file_id).append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockVideo>(context->td_->videos_manager_->get_video_object(video_file_id),
+ caption.get_page_block_caption_object(context), need_autoplay,
+ is_looped);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+
+ bool has_empty_video = !video_file_id.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(need_autoplay);
+ STORE_FLAG(is_looped);
+ STORE_FLAG(has_empty_video);
+ END_STORE_FLAGS();
+
+ if (!has_empty_video) {
+ storer.context()->td().get_actor_unsafe()->videos_manager_->store_video(video_file_id, storer);
+ }
+ store(caption, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+
+ bool has_empty_video;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(need_autoplay);
+ PARSE_FLAG(is_looped);
+ PARSE_FLAG(has_empty_video);
+ END_PARSE_FLAGS();
+
+ if (parser.version() >= static_cast<int32>(Version::FixWebPageInstantViewDatabase)) {
+ if (!has_empty_video) {
+ video_file_id = parser.context()->td().get_actor_unsafe()->videos_manager_->parse_video(parser);
+ } else {
+ video_file_id = FileId();
+ }
+ } else {
+ video_file_id = FileId();
+ parser.set_error("Wrong stored object");
+ }
+ parse(caption, parser);
+ }
+};
+
+class WebPageBlockCover final : public WebPageBlock {
+ unique_ptr<WebPageBlock> cover;
+
+ public:
+ WebPageBlockCover() = default;
+ explicit WebPageBlockCover(unique_ptr<WebPageBlock> &&cover) : cover(std::move(cover)) {
+ }
+
+ Type get_type() const final {
+ return Type::Cover;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ cover->append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockCover>(cover->get_page_block_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(cover, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(cover, parser);
+ }
+};
+
+class WebPageBlockEmbedded final : public WebPageBlock {
+ string url;
+ string html;
+ Photo poster_photo;
+ Dimensions dimensions;
+ WebPageBlockCaption caption;
+ bool is_full_width;
+ bool allow_scrolling;
+
+ public:
+ WebPageBlockEmbedded() = default;
+ WebPageBlockEmbedded(string &&url, string &&html, Photo &&poster_photo, Dimensions dimensions,
+ WebPageBlockCaption &&caption, bool is_full_width, bool allow_scrolling)
+ : url(std::move(url))
+ , html(std::move(html))
+ , poster_photo(std::move(poster_photo))
+ , dimensions(dimensions)
+ , caption(std::move(caption))
+ , is_full_width(is_full_width)
+ , allow_scrolling(allow_scrolling) {
+ }
+
+ Type get_type() const final {
+ return Type::Embedded;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ append(file_ids, photo_get_file_ids(poster_photo));
+ caption.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockEmbedded>(
+ url, html, get_photo_object(context->td_->file_manager_.get(), poster_photo), dimensions.width,
+ dimensions.height, caption.get_page_block_caption_object(context), is_full_width, allow_scrolling);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_full_width);
+ STORE_FLAG(allow_scrolling);
+ END_STORE_FLAGS();
+
+ store(url, storer);
+ store(html, storer);
+ store(poster_photo, storer);
+ store(dimensions, storer);
+ store(caption, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_full_width);
+ PARSE_FLAG(allow_scrolling);
+ END_PARSE_FLAGS();
+
+ parse(url, parser);
+ parse(html, parser);
+ parse(poster_photo, parser);
+ parse(dimensions, parser);
+ parse(caption, parser);
+ }
+};
+
+class WebPageBlockEmbeddedPost final : public WebPageBlock {
+ string url;
+ string author;
+ Photo author_photo;
+ int32 date;
+ vector<unique_ptr<WebPageBlock>> page_blocks;
+ WebPageBlockCaption caption;
+
+ public:
+ WebPageBlockEmbeddedPost() = default;
+ WebPageBlockEmbeddedPost(string &&url, string &&author, Photo &&author_photo, int32 date,
+ vector<unique_ptr<WebPageBlock>> &&page_blocks, WebPageBlockCaption &&caption)
+ : url(std::move(url))
+ , author(std::move(author))
+ , author_photo(std::move(author_photo))
+ , date(max(date, 0))
+ , page_blocks(std::move(page_blocks))
+ , caption(std::move(caption)) {
+ }
+
+ Type get_type() const final {
+ return Type::EmbeddedPost;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ append(file_ids, photo_get_file_ids(author_photo));
+ for (auto &page_block : page_blocks) {
+ page_block->append_file_ids(td, file_ids);
+ }
+ caption.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockEmbeddedPost>(
+ url, author, get_photo_object(context->td_->file_manager_.get(), author_photo), date,
+ get_page_blocks_object(page_blocks, context), caption.get_page_block_caption_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(url, storer);
+ store(author, storer);
+ store(author_photo, storer);
+ store(date, storer);
+ store(page_blocks, storer);
+ store(caption, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(url, parser);
+ parse(author, parser);
+ parse(author_photo, parser);
+ parse(date, parser);
+ parse(page_blocks, parser);
+ parse(caption, parser);
+ }
+};
+
+class WebPageBlockCollage final : public WebPageBlock {
+ vector<unique_ptr<WebPageBlock>> page_blocks;
+ WebPageBlockCaption caption;
+
+ public:
+ WebPageBlockCollage() = default;
+ WebPageBlockCollage(vector<unique_ptr<WebPageBlock>> &&page_blocks, WebPageBlockCaption &&caption)
+ : page_blocks(std::move(page_blocks)), caption(std::move(caption)) {
+ }
+
+ Type get_type() const final {
+ return Type::Collage;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ for (auto &page_block : page_blocks) {
+ page_block->append_file_ids(td, file_ids);
+ }
+ caption.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockCollage>(get_page_blocks_object(page_blocks, context),
+ caption.get_page_block_caption_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(page_blocks, storer);
+ store(caption, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(page_blocks, parser);
+ parse(caption, parser);
+ }
+};
+
+class WebPageBlockSlideshow final : public WebPageBlock {
+ vector<unique_ptr<WebPageBlock>> page_blocks;
+ WebPageBlockCaption caption;
+
+ public:
+ WebPageBlockSlideshow() = default;
+ WebPageBlockSlideshow(vector<unique_ptr<WebPageBlock>> &&page_blocks, WebPageBlockCaption &&caption)
+ : page_blocks(std::move(page_blocks)), caption(std::move(caption)) {
+ }
+
+ Type get_type() const final {
+ return Type::Slideshow;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ for (auto &page_block : page_blocks) {
+ page_block->append_file_ids(td, file_ids);
+ }
+ caption.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockSlideshow>(get_page_blocks_object(page_blocks, context),
+ caption.get_page_block_caption_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(page_blocks, storer);
+ store(caption, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(page_blocks, parser);
+ parse(caption, parser);
+ }
+};
+
+class WebPageBlockChatLink final : public WebPageBlock {
+ string title;
+ DialogPhoto photo;
+ string username;
+
+ public:
+ WebPageBlockChatLink() = default;
+ WebPageBlockChatLink(string &&title, DialogPhoto photo, string &&username)
+ : title(std::move(title)), photo(std::move(photo)), username(std::move(username)) {
+ }
+
+ Type get_type() const final {
+ return Type::ChatLink;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ append(file_ids, dialog_photo_get_file_ids(photo));
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockChatLink>(
+ title, get_chat_photo_info_object(context->td_->file_manager_.get(), &photo), username);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(title, storer);
+ store(photo, storer);
+ store(username, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(title, parser);
+ parse(photo, parser);
+ parse(username, parser);
+ }
+};
+
+class WebPageBlockAudio final : public WebPageBlock {
+ FileId audio_file_id;
+ WebPageBlockCaption caption;
+
+ public:
+ WebPageBlockAudio() = default;
+ WebPageBlockAudio(FileId audio_file_id, WebPageBlockCaption &&caption)
+ : audio_file_id(audio_file_id), caption(std::move(caption)) {
+ }
+
+ Type get_type() const final {
+ return Type::Audio;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ Document(Document::Type::Audio, audio_file_id).append_file_ids(td, file_ids);
+ caption.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockAudio>(context->td_->audios_manager_->get_audio_object(audio_file_id),
+ caption.get_page_block_caption_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+
+ bool has_empty_audio = !audio_file_id.is_valid();
+ bool is_voice_note_repaired = true;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_empty_audio);
+ STORE_FLAG(is_voice_note_repaired);
+ END_STORE_FLAGS();
+
+ if (!has_empty_audio) {
+ storer.context()->td().get_actor_unsafe()->audios_manager_->store_audio(audio_file_id, storer);
+ }
+ store(caption, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+
+ bool has_empty_audio;
+ bool is_voice_note_repaired;
+ if (parser.version() >= static_cast<int32>(Version::FixPageBlockAudioEmptyFile)) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_empty_audio);
+ PARSE_FLAG(is_voice_note_repaired);
+ END_PARSE_FLAGS();
+ } else {
+ has_empty_audio = false;
+ is_voice_note_repaired = false;
+ }
+
+ if (!has_empty_audio) {
+ audio_file_id = parser.context()->td().get_actor_unsafe()->audios_manager_->parse_audio(parser);
+ } else {
+ if (!is_voice_note_repaired) {
+ parser.set_error("Trying to repair WebPageBlockVoiceNote");
+ }
+ audio_file_id = FileId();
+ }
+ parse(caption, parser);
+ }
+};
+
+class WebPageBlockTable final : public WebPageBlock {
+ RichText title;
+ vector<vector<WebPageBlockTableCell>> cells;
+ bool is_bordered = false;
+ bool is_striped = false;
+
+ public:
+ WebPageBlockTable() = default;
+ WebPageBlockTable(RichText &&title, vector<vector<WebPageBlockTableCell>> &&cells, bool is_bordered, bool is_striped)
+ : title(std::move(title)), cells(std::move(cells)), is_bordered(is_bordered), is_striped(is_striped) {
+ }
+
+ Type get_type() const final {
+ return Type::Table;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ title.append_file_ids(td, file_ids);
+ for (auto &row : cells) {
+ for (auto &cell : row) {
+ cell.text.append_file_ids(td, file_ids);
+ }
+ }
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ auto cell_objects = transform(cells, [&](const vector<WebPageBlockTableCell> &row) {
+ return transform(
+ row, [&](const WebPageBlockTableCell &cell) { return cell.get_page_block_table_cell_object(context); });
+ });
+
+ return make_tl_object<td_api::pageBlockTable>(title.get_rich_text_object(context), std::move(cell_objects),
+ is_bordered, is_striped);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_bordered);
+ STORE_FLAG(is_striped);
+ END_STORE_FLAGS();
+ store(title, storer);
+ store(cells, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_bordered);
+ PARSE_FLAG(is_striped);
+ END_PARSE_FLAGS();
+ parse(title, parser);
+ parse(cells, parser);
+ }
+};
+
+class WebPageBlockDetails final : public WebPageBlock {
+ RichText header;
+ vector<unique_ptr<WebPageBlock>> page_blocks;
+ bool is_open;
+
+ public:
+ WebPageBlockDetails() = default;
+ WebPageBlockDetails(RichText &&header, vector<unique_ptr<WebPageBlock>> &&page_blocks, bool is_open)
+ : header(std::move(header)), page_blocks(std::move(page_blocks)), is_open(is_open) {
+ }
+
+ Type get_type() const final {
+ return Type::Details;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ header.append_file_ids(td, file_ids);
+ for (auto &page_block : page_blocks) {
+ page_block->append_file_ids(td, file_ids);
+ }
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockDetails>(header.get_rich_text_object(context),
+ get_page_blocks_object(page_blocks, context), is_open);
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(is_open);
+ END_STORE_FLAGS();
+ store(header, storer);
+ store(page_blocks, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(is_open);
+ END_PARSE_FLAGS();
+ parse(header, parser);
+ parse(page_blocks, parser);
+ }
+};
+
+class WebPageBlockRelatedArticles final : public WebPageBlock {
+ RichText header;
+ vector<RelatedArticle> related_articles;
+
+ public:
+ WebPageBlockRelatedArticles() = default;
+ WebPageBlockRelatedArticles(RichText &&header, vector<RelatedArticle> &&related_articles)
+ : header(std::move(header)), related_articles(std::move(related_articles)) {
+ }
+
+ Type get_type() const final {
+ return Type::RelatedArticles;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ header.append_file_ids(td, file_ids);
+ for (auto &article : related_articles) {
+ if (!article.photo.is_empty()) {
+ append(file_ids, photo_get_file_ids(article.photo));
+ }
+ }
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ auto related_article_objects = transform(related_articles, [context](const RelatedArticle &article) {
+ return td_api::make_object<td_api::pageBlockRelatedArticle>(
+ article.url, article.title, article.description,
+ get_photo_object(context->td_->file_manager_.get(), article.photo), article.author, article.published_date);
+ });
+ return make_tl_object<td_api::pageBlockRelatedArticles>(header.get_rich_text_object(context),
+ std::move(related_article_objects));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(header, storer);
+ store(related_articles, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(header, parser);
+ parse(related_articles, parser);
+ }
+};
+
+class WebPageBlockMap final : public WebPageBlock {
+ Location location;
+ int32 zoom = 0;
+ Dimensions dimensions;
+ WebPageBlockCaption caption;
+
+ public:
+ WebPageBlockMap() = default;
+ WebPageBlockMap(Location location, int32 zoom, Dimensions dimensions, WebPageBlockCaption &&caption)
+ : location(std::move(location)), zoom(zoom), dimensions(dimensions), caption(std::move(caption)) {
+ }
+
+ Type get_type() const final {
+ return Type::Map;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ caption.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockMap>(location.get_location_object(), zoom, dimensions.width,
+ dimensions.height, caption.get_page_block_caption_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+ store(location, storer);
+ store(zoom, storer);
+ store(dimensions, storer);
+ store(caption, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+ parse(location, parser);
+ parse(zoom, parser);
+ parse(dimensions, parser);
+ parse(caption, parser);
+ }
+};
+
+class WebPageBlockVoiceNote final : public WebPageBlock {
+ FileId voice_note_file_id;
+ WebPageBlockCaption caption;
+
+ public:
+ WebPageBlockVoiceNote() = default;
+ WebPageBlockVoiceNote(FileId voice_note_file_id, WebPageBlockCaption &&caption)
+ : voice_note_file_id(voice_note_file_id), caption(std::move(caption)) {
+ }
+
+ Type get_type() const final {
+ return Type::VoiceNote;
+ }
+
+ void append_file_ids(const Td *td, vector<FileId> &file_ids) const final {
+ Document(Document::Type::VoiceNote, voice_note_file_id).append_file_ids(td, file_ids);
+ caption.append_file_ids(td, file_ids);
+ }
+
+ td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const final {
+ return make_tl_object<td_api::pageBlockVoiceNote>(
+ context->td_->voice_notes_manager_->get_voice_note_object(voice_note_file_id),
+ caption.get_page_block_caption_object(context));
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using ::td::store;
+
+ bool has_empty_voice_note = !voice_note_file_id.is_valid();
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_empty_voice_note);
+ END_STORE_FLAGS();
+
+ if (!has_empty_voice_note) {
+ storer.context()->td().get_actor_unsafe()->voice_notes_manager_->store_voice_note(voice_note_file_id, storer);
+ }
+ store(caption, storer);
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using ::td::parse;
+
+ bool has_empty_voice_note;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_empty_voice_note);
+ END_PARSE_FLAGS();
+
+ if (!has_empty_voice_note) {
+ voice_note_file_id = parser.context()->td().get_actor_unsafe()->voice_notes_manager_->parse_voice_note(parser);
+ } else {
+ voice_note_file_id = FileId();
+ }
+ parse(caption, parser);
+ }
+};
+
+vector<RichText> get_rich_texts(vector<tl_object_ptr<telegram_api::RichText>> &&rich_text_ptrs,
+ const FlatHashMap<int64, FileId> &documents);
+
+RichText get_rich_text(tl_object_ptr<telegram_api::RichText> &&rich_text_ptr,
+ const FlatHashMap<int64, FileId> &documents) {
+ CHECK(rich_text_ptr != nullptr);
+
+ RichText result;
+ switch (rich_text_ptr->get_id()) {
+ case telegram_api::textEmpty::ID:
+ break;
+ case telegram_api::textPlain::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textPlain>(rich_text_ptr);
+ result.content = std::move(rich_text->text_);
+ break;
+ }
+ case telegram_api::textBold::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textBold>(rich_text_ptr);
+ result.type = RichText::Type::Bold;
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textItalic::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textItalic>(rich_text_ptr);
+ result.type = RichText::Type::Italic;
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textUnderline::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textUnderline>(rich_text_ptr);
+ result.type = RichText::Type::Underline;
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textStrike::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textStrike>(rich_text_ptr);
+ result.type = RichText::Type::Strikethrough;
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textFixed::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textFixed>(rich_text_ptr);
+ result.type = RichText::Type::Fixed;
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textUrl::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textUrl>(rich_text_ptr);
+ result.type = RichText::Type::Url;
+ result.content = std::move(rich_text->url_);
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ result.web_page_id = WebPageId(rich_text->webpage_id_);
+ break;
+ }
+ case telegram_api::textEmail::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textEmail>(rich_text_ptr);
+ result.type = RichText::Type::EmailAddress;
+ result.content = std::move(rich_text->email_);
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textConcat::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textConcat>(rich_text_ptr);
+ result.type = RichText::Type::Concatenation;
+ result.texts = get_rich_texts(std::move(rich_text->texts_), documents);
+ break;
+ }
+ case telegram_api::textSubscript::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textSubscript>(rich_text_ptr);
+ result.type = RichText::Type::Subscript;
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textSuperscript::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textSuperscript>(rich_text_ptr);
+ result.type = RichText::Type::Superscript;
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textMarked::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textMarked>(rich_text_ptr);
+ result.type = RichText::Type::Marked;
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textPhone::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textPhone>(rich_text_ptr);
+ result.type = RichText::Type::PhoneNumber;
+ result.content = std::move(rich_text->phone_);
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ case telegram_api::textImage::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textImage>(rich_text_ptr);
+ auto it = documents.find(rich_text->document_id_);
+ if (it != documents.end()) {
+ result.type = RichText::Type::Icon;
+ result.document_file_id = it->second;
+ Dimensions dimensions = get_dimensions(rich_text->w_, rich_text->h_, "textImage");
+ result.content = PSTRING() << (dimensions.width * static_cast<uint32>(65536) + dimensions.height);
+ } else {
+ LOG(ERROR) << "Can't find document " << rich_text->document_id_;
+ }
+ break;
+ }
+ case telegram_api::textAnchor::ID: {
+ auto rich_text = move_tl_object_as<telegram_api::textAnchor>(rich_text_ptr);
+ result.type = RichText::Type::Anchor;
+ result.content = std::move(rich_text->name_);
+ result.texts.push_back(get_rich_text(std::move(rich_text->text_), documents));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ return result;
+}
+
+vector<RichText> get_rich_texts(vector<tl_object_ptr<telegram_api::RichText>> &&rich_text_ptrs,
+ const FlatHashMap<int64, FileId> &documents) {
+ return transform(std::move(rich_text_ptrs), [&documents](tl_object_ptr<telegram_api::RichText> &&rich_text) {
+ return get_rich_text(std::move(rich_text), documents);
+ });
+}
+
+WebPageBlockCaption get_page_block_caption(tl_object_ptr<telegram_api::pageCaption> &&page_caption,
+ const FlatHashMap<int64, FileId> &documents) {
+ CHECK(page_caption != nullptr);
+ WebPageBlockCaption result;
+ result.text = get_rich_text(std::move(page_caption->text_), documents);
+ result.credit = get_rich_text(std::move(page_caption->credit_), documents);
+ return result;
+}
+
+unique_ptr<WebPageBlock> get_web_page_block(Td *td, tl_object_ptr<telegram_api::PageBlock> page_block_ptr,
+ const FlatHashMap<int64, FileId> &animations,
+ const FlatHashMap<int64, FileId> &audios,
+ const FlatHashMap<int64, FileId> &documents,
+ const FlatHashMap<int64, unique_ptr<Photo>> &photos,
+ const FlatHashMap<int64, FileId> &videos,
+ const FlatHashMap<int64, FileId> &voice_notes) {
+ CHECK(page_block_ptr != nullptr);
+ switch (page_block_ptr->get_id()) {
+ case telegram_api::pageBlockUnsupported::ID:
+ return nullptr;
+ case telegram_api::pageBlockTitle::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockTitle>(page_block_ptr);
+ return make_unique<WebPageBlockTitle>(get_rich_text(std::move(page_block->text_), documents));
+ }
+ case telegram_api::pageBlockSubtitle::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockSubtitle>(page_block_ptr);
+ return make_unique<WebPageBlockSubtitle>(get_rich_text(std::move(page_block->text_), documents));
+ }
+ case telegram_api::pageBlockAuthorDate::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockAuthorDate>(page_block_ptr);
+ return make_unique<WebPageBlockAuthorDate>(get_rich_text(std::move(page_block->author_), documents),
+ page_block->published_date_);
+ }
+ case telegram_api::pageBlockHeader::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockHeader>(page_block_ptr);
+ return make_unique<WebPageBlockHeader>(get_rich_text(std::move(page_block->text_), documents));
+ }
+ case telegram_api::pageBlockSubheader::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockSubheader>(page_block_ptr);
+ return make_unique<WebPageBlockSubheader>(get_rich_text(std::move(page_block->text_), documents));
+ }
+ case telegram_api::pageBlockKicker::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockKicker>(page_block_ptr);
+ return make_unique<WebPageBlockKicker>(get_rich_text(std::move(page_block->text_), documents));
+ }
+ case telegram_api::pageBlockParagraph::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockParagraph>(page_block_ptr);
+ return make_unique<WebPageBlockParagraph>(get_rich_text(std::move(page_block->text_), documents));
+ }
+ case telegram_api::pageBlockPreformatted::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockPreformatted>(page_block_ptr);
+ return td::make_unique<WebPageBlockPreformatted>(get_rich_text(std::move(page_block->text_), documents),
+ std::move(page_block->language_));
+ }
+ case telegram_api::pageBlockFooter::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockFooter>(page_block_ptr);
+ return make_unique<WebPageBlockFooter>(get_rich_text(std::move(page_block->text_), documents));
+ }
+ case telegram_api::pageBlockDivider::ID:
+ return make_unique<WebPageBlockDivider>();
+ case telegram_api::pageBlockAnchor::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockAnchor>(page_block_ptr);
+ return td::make_unique<WebPageBlockAnchor>(std::move(page_block->name_));
+ }
+ case telegram_api::pageBlockList::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockList>(page_block_ptr);
+ return td::make_unique<WebPageBlockList>(transform(std::move(page_block->items_), [&](auto &&list_item_ptr) {
+ WebPageBlockList::Item item;
+ CHECK(list_item_ptr != nullptr);
+ switch (list_item_ptr->get_id()) {
+ case telegram_api::pageListItemText::ID: {
+ auto list_item = telegram_api::move_object_as<telegram_api::pageListItemText>(list_item_ptr);
+ item.page_blocks.push_back(
+ make_unique<WebPageBlockParagraph>(get_rich_text(std::move(list_item->text_), documents)));
+ break;
+ }
+ case telegram_api::pageListItemBlocks::ID: {
+ auto list_item = telegram_api::move_object_as<telegram_api::pageListItemBlocks>(list_item_ptr);
+ item.page_blocks = get_web_page_blocks(td, std::move(list_item->blocks_), animations, audios, documents,
+ photos, videos, voice_notes);
+ break;
+ }
+ }
+ if (item.page_blocks.empty()) {
+ item.page_blocks.push_back(make_unique<WebPageBlockParagraph>(RichText()));
+ }
+ return item;
+ }));
+ }
+ case telegram_api::pageBlockOrderedList::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockOrderedList>(page_block_ptr);
+ int32 current_label = 0;
+ return td::make_unique<WebPageBlockList>(transform(std::move(page_block->items_), [&](auto &&list_item_ptr) {
+ WebPageBlockList::Item item;
+ CHECK(list_item_ptr != nullptr);
+ switch (list_item_ptr->get_id()) {
+ case telegram_api::pageListOrderedItemText::ID: {
+ auto list_item = telegram_api::move_object_as<telegram_api::pageListOrderedItemText>(list_item_ptr);
+ item.label = std::move(list_item->num_);
+ item.page_blocks.push_back(
+ make_unique<WebPageBlockParagraph>(get_rich_text(std::move(list_item->text_), documents)));
+ break;
+ }
+ case telegram_api::pageListOrderedItemBlocks::ID: {
+ auto list_item = telegram_api::move_object_as<telegram_api::pageListOrderedItemBlocks>(list_item_ptr);
+ item.label = std::move(list_item->num_);
+ item.page_blocks = get_web_page_blocks(td, std::move(list_item->blocks_), animations, audios, documents,
+ photos, videos, voice_notes);
+ break;
+ }
+ }
+ if (item.page_blocks.empty()) {
+ item.page_blocks.push_back(make_unique<WebPageBlockParagraph>(RichText()));
+ }
+ ++current_label;
+ if (item.label.empty()) {
+ item.label = PSTRING() << current_label << '.';
+ } else {
+ item.label += '.';
+ }
+ return item;
+ }));
+ }
+ case telegram_api::pageBlockBlockquote::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockBlockquote>(page_block_ptr);
+ return make_unique<WebPageBlockBlockQuote>(get_rich_text(std::move(page_block->text_), documents),
+ get_rich_text(std::move(page_block->caption_), documents));
+ }
+ case telegram_api::pageBlockPullquote::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockPullquote>(page_block_ptr);
+ return make_unique<WebPageBlockPullQuote>(get_rich_text(std::move(page_block->text_), documents),
+ get_rich_text(std::move(page_block->caption_), documents));
+ }
+ case telegram_api::pageBlockPhoto::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockPhoto>(page_block_ptr);
+ auto it = photos.find(page_block->photo_id_);
+ Photo photo;
+ if (it != photos.end()) {
+ photo = *it->second;
+ }
+ string url;
+ WebPageId web_page_id;
+ if ((page_block->flags_ & telegram_api::pageBlockPhoto::URL_MASK) != 0) {
+ url = std::move(page_block->url_);
+ web_page_id = WebPageId(page_block->webpage_id_);
+ }
+ return td::make_unique<WebPageBlockPhoto>(std::move(photo),
+ get_page_block_caption(std::move(page_block->caption_), documents),
+ std::move(url), web_page_id);
+ }
+ case telegram_api::pageBlockVideo::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockVideo>(page_block_ptr);
+ bool need_autoplay = page_block->autoplay_;
+ bool is_looped = page_block->loop_;
+ auto animations_it = animations.find(page_block->video_id_);
+ if (animations_it != animations.end()) {
+ LOG_IF(ERROR, !is_looped) << "Receive non-looped animation";
+ return make_unique<WebPageBlockAnimation>(
+ animations_it->second, get_page_block_caption(std::move(page_block->caption_), documents), need_autoplay);
+ }
+
+ auto it = videos.find(page_block->video_id_);
+ FileId video_file_id;
+ if (it != videos.end()) {
+ video_file_id = it->second;
+ }
+ return make_unique<WebPageBlockVideo>(
+ video_file_id, get_page_block_caption(std::move(page_block->caption_), documents), need_autoplay, is_looped);
+ }
+ case telegram_api::pageBlockCover::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockCover>(page_block_ptr);
+ auto cover = get_web_page_block(td, std::move(page_block->cover_), animations, audios, documents, photos, videos,
+ voice_notes);
+ if (cover == nullptr) {
+ return nullptr;
+ }
+ return make_unique<WebPageBlockCover>(std::move(cover));
+ }
+ case telegram_api::pageBlockEmbed::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockEmbed>(page_block_ptr);
+ bool is_full_width = page_block->full_width_;
+ bool allow_scrolling = page_block->allow_scrolling_;
+ bool has_dimensions = (page_block->flags_ & telegram_api::pageBlockEmbed::W_MASK) != 0;
+ Photo poster_photo;
+ if ((page_block->flags_ & telegram_api::pageBlockEmbed::POSTER_PHOTO_ID_MASK) != 0) {
+ auto it = photos.find(page_block->poster_photo_id_);
+ if (it != photos.end()) {
+ poster_photo = *it->second;
+ }
+ }
+ Dimensions dimensions;
+ if (has_dimensions) {
+ dimensions = get_dimensions(page_block->w_, page_block->h_, "pageBlockEmbed");
+ }
+ return td::make_unique<WebPageBlockEmbedded>(
+ std::move(page_block->url_), std::move(page_block->html_), std::move(poster_photo), dimensions,
+ get_page_block_caption(std::move(page_block->caption_), documents), is_full_width, allow_scrolling);
+ }
+ case telegram_api::pageBlockEmbedPost::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockEmbedPost>(page_block_ptr);
+ auto it = photos.find(page_block->author_photo_id_);
+ Photo author_photo;
+ if (it != photos.end()) {
+ author_photo = *it->second;
+ }
+ return td::make_unique<WebPageBlockEmbeddedPost>(
+ std::move(page_block->url_), std::move(page_block->author_), std::move(author_photo), page_block->date_,
+ get_web_page_blocks(td, std::move(page_block->blocks_), animations, audios, documents, photos, videos,
+ voice_notes),
+ get_page_block_caption(std::move(page_block->caption_), documents));
+ }
+ case telegram_api::pageBlockCollage::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockCollage>(page_block_ptr);
+ return td::make_unique<WebPageBlockCollage>(get_web_page_blocks(td, std::move(page_block->items_), animations,
+ audios, documents, photos, videos, voice_notes),
+ get_page_block_caption(std::move(page_block->caption_), documents));
+ }
+ case telegram_api::pageBlockSlideshow::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockSlideshow>(page_block_ptr);
+ return td::make_unique<WebPageBlockSlideshow>(get_web_page_blocks(td, std::move(page_block->items_), animations,
+ audios, documents, photos, videos, voice_notes),
+ get_page_block_caption(std::move(page_block->caption_), documents));
+ }
+ case telegram_api::pageBlockChannel::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockChannel>(page_block_ptr);
+ CHECK(page_block->channel_ != nullptr);
+ if (page_block->channel_->get_id() == telegram_api::channel::ID) {
+ auto channel = static_cast<telegram_api::channel *>(page_block->channel_.get());
+ ChannelId channel_id(channel->id_);
+ if (!channel_id.is_valid()) {
+ LOG(ERROR) << "Receive invalid " << channel_id;
+ return nullptr;
+ }
+
+ if (td->contacts_manager_->have_channel_force(channel_id)) {
+ td->contacts_manager_->on_get_chat(std::move(page_block->channel_), "pageBlockChannel");
+ LOG(INFO) << "Receive known min " << channel_id;
+ return td::make_unique<WebPageBlockChatLink>(td->contacts_manager_->get_channel_title(channel_id),
+ *td->contacts_manager_->get_channel_dialog_photo(channel_id),
+ td->contacts_manager_->get_channel_first_username(channel_id));
+ } else {
+ bool has_access_hash = (channel->flags_ & telegram_api::channel::ACCESS_HASH_MASK) != 0;
+ return td::make_unique<WebPageBlockChatLink>(
+ std::move(channel->title_),
+ get_dialog_photo(td->file_manager_.get(), DialogId(channel_id),
+ has_access_hash ? channel->access_hash_ : 0, std::move(channel->photo_)),
+ std::move(channel->username_));
+ }
+ } else {
+ LOG(ERROR) << "Receive wrong channel " << to_string(page_block->channel_);
+ return nullptr;
+ }
+ }
+ case telegram_api::pageBlockAudio::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockAudio>(page_block_ptr);
+ auto voice_note_it = voice_notes.find(page_block->audio_id_);
+ if (voice_note_it != voice_notes.end()) {
+ return make_unique<WebPageBlockVoiceNote>(voice_note_it->second,
+ get_page_block_caption(std::move(page_block->caption_), documents));
+ }
+
+ auto it = audios.find(page_block->audio_id_);
+ FileId audio_file_id;
+ if (it != audios.end()) {
+ audio_file_id = it->second;
+ }
+ return make_unique<WebPageBlockAudio>(audio_file_id,
+ get_page_block_caption(std::move(page_block->caption_), documents));
+ }
+ case telegram_api::pageBlockTable::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockTable>(page_block_ptr);
+ auto is_bordered = page_block->bordered_;
+ auto is_striped = page_block->striped_;
+ auto cells = transform(std::move(page_block->rows_), [&](tl_object_ptr<telegram_api::pageTableRow> &&row) {
+ return transform(std::move(row->cells_), [&](tl_object_ptr<telegram_api::pageTableCell> &&table_cell) {
+ WebPageBlockTableCell cell;
+ cell.is_header = table_cell->header_;
+ cell.align_center = table_cell->align_center_;
+ if (!cell.align_center) {
+ cell.align_right = table_cell->align_right_;
+ if (!cell.align_right) {
+ cell.align_left = true;
+ }
+ }
+ cell.valign_middle = table_cell->valign_middle_;
+ if (!cell.valign_middle) {
+ cell.valign_bottom = table_cell->valign_bottom_;
+ if (!cell.valign_bottom) {
+ cell.valign_top = true;
+ }
+ }
+ if (table_cell->text_ != nullptr) {
+ cell.text = get_rich_text(std::move(table_cell->text_), documents);
+ }
+ if ((table_cell->flags_ & telegram_api::pageTableCell::COLSPAN_MASK) != 0) {
+ cell.colspan = table_cell->colspan_;
+ }
+ if ((table_cell->flags_ & telegram_api::pageTableCell::ROWSPAN_MASK) != 0) {
+ cell.rowspan = table_cell->rowspan_;
+ }
+ return cell;
+ });
+ });
+ return td::make_unique<WebPageBlockTable>(get_rich_text(std::move(page_block->title_), documents),
+ std::move(cells), is_bordered, is_striped);
+ }
+ case telegram_api::pageBlockDetails::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockDetails>(page_block_ptr);
+ auto is_open = page_block->open_;
+ return td::make_unique<WebPageBlockDetails>(get_rich_text(std::move(page_block->title_), documents),
+ get_web_page_blocks(td, std::move(page_block->blocks_), animations,
+ audios, documents, photos, videos, voice_notes),
+ is_open);
+ }
+ case telegram_api::pageBlockRelatedArticles::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockRelatedArticles>(page_block_ptr);
+ auto articles = transform(
+ std::move(page_block->articles_), [&](tl_object_ptr<telegram_api::pageRelatedArticle> &&related_article) {
+ RelatedArticle article;
+ article.url = std::move(related_article->url_);
+ article.web_page_id = WebPageId(related_article->webpage_id_);
+ article.title = std::move(related_article->title_);
+ article.description = std::move(related_article->description_);
+ if ((related_article->flags_ & telegram_api::pageRelatedArticle::PHOTO_ID_MASK) != 0) {
+ auto it = photos.find(related_article->photo_id_);
+ if (it != photos.end()) {
+ article.photo = *it->second;
+ }
+ }
+ article.author = std::move(related_article->author_);
+ if ((related_article->flags_ & telegram_api::pageRelatedArticle::PUBLISHED_DATE_MASK) != 0) {
+ article.published_date = related_article->published_date_;
+ }
+ return article;
+ });
+ return td::make_unique<WebPageBlockRelatedArticles>(get_rich_text(std::move(page_block->title_), documents),
+ std::move(articles));
+ }
+ case telegram_api::pageBlockMap::ID: {
+ auto page_block = move_tl_object_as<telegram_api::pageBlockMap>(page_block_ptr);
+ Location location(page_block->geo_);
+ auto zoom = page_block->zoom_;
+ Dimensions dimensions = get_dimensions(page_block->w_, page_block->h_, "pageBlockMap");
+ if (location.empty()) {
+ LOG(ERROR) << "Receive invalid map location";
+ break;
+ }
+ if (zoom <= 0 || zoom > 30) {
+ LOG(ERROR) << "Receive invalid map zoom " << zoom;
+ break;
+ }
+ if (dimensions.width == 0) {
+ LOG(ERROR) << "Receive invalid map dimensions " << page_block->w_ << " " << page_block->h_;
+ break;
+ }
+ return make_unique<WebPageBlockMap>(std::move(location), zoom, dimensions,
+ get_page_block_caption(std::move(page_block->caption_), documents));
+ }
+ default:
+ UNREACHABLE();
+ }
+ return nullptr;
+}
+
+} // namespace
+
+template <class F>
+void WebPageBlock::call_impl(Type type, const WebPageBlock *ptr, F &&f) {
+ switch (type) {
+ case Type::Title:
+ return f(static_cast<const WebPageBlockTitle *>(ptr));
+ case Type::Subtitle:
+ return f(static_cast<const WebPageBlockSubtitle *>(ptr));
+ case Type::AuthorDate:
+ return f(static_cast<const WebPageBlockAuthorDate *>(ptr));
+ case Type::Header:
+ return f(static_cast<const WebPageBlockHeader *>(ptr));
+ case Type::Subheader:
+ return f(static_cast<const WebPageBlockSubheader *>(ptr));
+ case Type::Kicker:
+ return f(static_cast<const WebPageBlockKicker *>(ptr));
+ case Type::Paragraph:
+ return f(static_cast<const WebPageBlockParagraph *>(ptr));
+ case Type::Preformatted:
+ return f(static_cast<const WebPageBlockPreformatted *>(ptr));
+ case Type::Footer:
+ return f(static_cast<const WebPageBlockFooter *>(ptr));
+ case Type::Divider:
+ return f(static_cast<const WebPageBlockDivider *>(ptr));
+ case Type::Anchor:
+ return f(static_cast<const WebPageBlockAnchor *>(ptr));
+ case Type::List:
+ return f(static_cast<const WebPageBlockList *>(ptr));
+ case Type::BlockQuote:
+ return f(static_cast<const WebPageBlockBlockQuote *>(ptr));
+ case Type::PullQuote:
+ return f(static_cast<const WebPageBlockPullQuote *>(ptr));
+ case Type::Animation:
+ return f(static_cast<const WebPageBlockAnimation *>(ptr));
+ case Type::Photo:
+ return f(static_cast<const WebPageBlockPhoto *>(ptr));
+ case Type::Video:
+ return f(static_cast<const WebPageBlockVideo *>(ptr));
+ case Type::Cover:
+ return f(static_cast<const WebPageBlockCover *>(ptr));
+ case Type::Embedded:
+ return f(static_cast<const WebPageBlockEmbedded *>(ptr));
+ case Type::EmbeddedPost:
+ return f(static_cast<const WebPageBlockEmbeddedPost *>(ptr));
+ case Type::Collage:
+ return f(static_cast<const WebPageBlockCollage *>(ptr));
+ case Type::Slideshow:
+ return f(static_cast<const WebPageBlockSlideshow *>(ptr));
+ case Type::ChatLink:
+ return f(static_cast<const WebPageBlockChatLink *>(ptr));
+ case Type::Audio:
+ return f(static_cast<const WebPageBlockAudio *>(ptr));
+ case Type::Table:
+ return f(static_cast<const WebPageBlockTable *>(ptr));
+ case Type::Details:
+ return f(static_cast<const WebPageBlockDetails *>(ptr));
+ case Type::RelatedArticles:
+ return f(static_cast<const WebPageBlockRelatedArticles *>(ptr));
+ case Type::Map:
+ return f(static_cast<const WebPageBlockMap *>(ptr));
+ case Type::VoiceNote:
+ return f(static_cast<const WebPageBlockVoiceNote *>(ptr));
+ default:
+ UNREACHABLE();
+ }
+}
+
+template <class StorerT>
+void WebPageBlock::store(StorerT &storer) const {
+ Type type = get_type();
+ td::store(type, storer);
+ call_impl(type, this, [&](const auto *object) { td::store(*object, storer); });
+}
+
+template <class ParserT>
+unique_ptr<WebPageBlock> WebPageBlock::parse(ParserT &parser) {
+ Type type;
+ td::parse(type, parser);
+ if (static_cast<int32>(type) < 0 || static_cast<int32>(type) >= static_cast<int32>(Type::Size)) {
+ parser.set_error(PSTRING() << "Can't parse unknown BlockType " << static_cast<int32>(type));
+ return nullptr;
+ }
+ unique_ptr<WebPageBlock> res;
+ call_impl(type, nullptr, [&](const auto *ptr) {
+ using ObjT = std::decay_t<decltype(*ptr)>;
+ auto object = make_unique<ObjT>();
+ td::parse(*object, parser);
+ res = std::move(object);
+ });
+ return res;
+}
+
+template <class StorerT>
+void store_web_page_block(const unique_ptr<WebPageBlock> &block, StorerT &storer) {
+ block->store(storer);
+}
+
+template <class ParserT>
+void parse_web_page_block(unique_ptr<WebPageBlock> &block, ParserT &parser) {
+ block = WebPageBlock::parse(parser);
+}
+
+void store(const unique_ptr<WebPageBlock> &block, LogEventStorerCalcLength &storer) {
+ store_web_page_block(block, storer);
+}
+
+void store(const unique_ptr<WebPageBlock> &block, LogEventStorerUnsafe &storer) {
+ store_web_page_block(block, storer);
+}
+
+void parse(unique_ptr<WebPageBlock> &block, LogEventParser &parser) {
+ parse_web_page_block(block, parser);
+}
+
+vector<unique_ptr<WebPageBlock>> get_web_page_blocks(
+ Td *td, vector<tl_object_ptr<telegram_api::PageBlock>> page_block_ptrs,
+ const FlatHashMap<int64, FileId> &animations, const FlatHashMap<int64, FileId> &audios,
+ const FlatHashMap<int64, FileId> &documents, const FlatHashMap<int64, unique_ptr<Photo>> &photos,
+ const FlatHashMap<int64, FileId> &videos, const FlatHashMap<int64, FileId> &voice_notes) {
+ vector<unique_ptr<WebPageBlock>> result;
+ result.reserve(page_block_ptrs.size());
+ for (auto &page_block_ptr : page_block_ptrs) {
+ auto page_block =
+ get_web_page_block(td, std::move(page_block_ptr), animations, audios, documents, photos, videos, voice_notes);
+ if (page_block != nullptr) {
+ result.push_back(std::move(page_block));
+ }
+ }
+ return result;
+}
+
+vector<td_api::object_ptr<td_api::PageBlock>> get_page_blocks_object(
+ const vector<unique_ptr<WebPageBlock>> &page_blocks, Td *td, Slice base_url, Slice real_url) {
+ GetWebPageBlockObjectContext context;
+ context.td_ = td;
+ context.base_url_ = base_url;
+ context.real_url_rhash_ = LinkManager::get_instant_view_link_rhash(real_url);
+ if (!context.real_url_rhash_.empty()) {
+ context.real_url_host_ = get_url_host(LinkManager::get_instant_view_link_url(real_url));
+ if (context.real_url_host_.empty()) {
+ context.real_url_rhash_ = string();
+ }
+ }
+ auto blocks = get_page_blocks_object(page_blocks, &context);
+ if (!context.has_anchor_urls_) {
+ return blocks;
+ }
+
+ context.is_first_pass_ = false;
+ context.anchors_.emplace(Slice(), nullptr); // back to top
+ return get_page_blocks_object(page_blocks, &context);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/WebPageBlock.h b/protocols/Telegram/tdlib/td/td/telegram/WebPageBlock.h
new file mode 100644
index 0000000000..887a0f1b9e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/WebPageBlock.h
@@ -0,0 +1,107 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/FileId.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/Photo.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+struct GetWebPageBlockObjectContext;
+
+class Td;
+
+class WebPageBlock {
+ protected:
+ enum class Type : int32 {
+ Title,
+ Subtitle,
+ AuthorDate,
+ Header,
+ Subheader,
+ Paragraph,
+ Preformatted,
+ Footer,
+ Divider,
+ Anchor,
+ List,
+ BlockQuote,
+ PullQuote,
+ Animation,
+ Photo,
+ Video,
+ Cover,
+ Embedded,
+ EmbeddedPost,
+ Collage,
+ Slideshow,
+ ChatLink,
+ Audio,
+ Kicker,
+ Table,
+ Details,
+ RelatedArticles,
+ Map,
+ VoiceNote,
+ Size
+ };
+
+ virtual Type get_type() const = 0;
+
+ template <class F>
+ static void call_impl(Type type, const WebPageBlock *ptr, F &&f);
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ static unique_ptr<WebPageBlock> parse(ParserT &parser);
+
+ template <class StorerT>
+ friend void store_web_page_block(const unique_ptr<WebPageBlock> &block, StorerT &storer);
+
+ template <class ParserT>
+ friend void parse_web_page_block(unique_ptr<WebPageBlock> &block, ParserT &parser);
+
+ using Context = GetWebPageBlockObjectContext;
+
+ public:
+ WebPageBlock() = default;
+ WebPageBlock(const WebPageBlock &) = delete;
+ WebPageBlock &operator=(const WebPageBlock &) = delete;
+ WebPageBlock(WebPageBlock &&) = delete;
+ WebPageBlock &operator=(WebPageBlock &&) = delete;
+ virtual ~WebPageBlock() = default;
+
+ virtual void append_file_ids(const Td *td, vector<FileId> &file_ids) const = 0;
+
+ virtual td_api::object_ptr<td_api::PageBlock> get_page_block_object(Context *context) const = 0;
+};
+
+void store(const unique_ptr<WebPageBlock> &block, LogEventStorerCalcLength &storer);
+
+void store(const unique_ptr<WebPageBlock> &block, LogEventStorerUnsafe &storer);
+
+void parse(unique_ptr<WebPageBlock> &block, LogEventParser &parser);
+
+vector<unique_ptr<WebPageBlock>> get_web_page_blocks(
+ Td *td, vector<tl_object_ptr<telegram_api::PageBlock>> page_block_ptrs,
+ const FlatHashMap<int64, FileId> &animations, const FlatHashMap<int64, FileId> &audios,
+ const FlatHashMap<int64, FileId> &documents, const FlatHashMap<int64, unique_ptr<Photo>> &photos,
+ const FlatHashMap<int64, FileId> &videos, const FlatHashMap<int64, FileId> &voice_notes);
+
+vector<td_api::object_ptr<td_api::PageBlock>> get_page_blocks_object(
+ const vector<unique_ptr<WebPageBlock>> &page_blocks, Td *td, Slice base_url, Slice real_url);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/WebPageId.h b/protocols/Telegram/tdlib/td/td/telegram/WebPageId.h
index bb05f57931..7e98f9a9f0 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/WebPageId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/WebPageId.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,10 +7,10 @@
#pragma once
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_helpers.h"
-#include <functional>
#include <type_traits>
namespace td {
@@ -21,10 +21,10 @@ class WebPageId {
public:
WebPageId() = default;
- explicit WebPageId(int64 webpage_id) : id(webpage_id) {
+ explicit WebPageId(int64 web_page_id) : id(web_page_id) {
}
template <class T, typename = std::enable_if_t<std::is_convertible<T, int64>::value>>
- WebPageId(T webpage_id) = delete;
+ WebPageId(T web_page_id) = delete;
int64 get() const {
return id;
@@ -54,13 +54,13 @@ class WebPageId {
};
struct WebPageIdHash {
- std::size_t operator()(WebPageId webpage_id) const {
- return std::hash<int64>()(webpage_id.get());
+ uint32 operator()(WebPageId web_page_id) const {
+ return Hash<int64>()(web_page_id.get());
}
};
-inline StringBuilder &operator<<(StringBuilder &string_builder, WebPageId webpage_id) {
- return string_builder << "web page " << webpage_id.get();
+inline StringBuilder &operator<<(StringBuilder &string_builder, WebPageId web_page_id) {
+ return string_builder << "web page " << web_page_id.get();
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.cpp
index 4e0157e369..3b0c1fa19b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.cpp
@@ -1,55 +1,61 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/WebPagesManager.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/telegram_api.hpp"
-
-#include "td/actor/PromiseFuture.h"
-
-#include "td/db/binlog/BinlogHelper.h"
-
#include "td/telegram/AnimationsManager.h"
-#include "td/telegram/AnimationsManager.hpp"
#include "td/telegram/AudiosManager.h"
-#include "td/telegram/AudiosManager.hpp"
-#include "td/telegram/ChannelId.h"
-#include "td/telegram/ContactsManager.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/Dimensions.h"
+#include "td/telegram/Document.h"
+#include "td/telegram/Document.hpp"
#include "td/telegram/DocumentsManager.h"
-#include "td/telegram/DocumentsManager.hpp"
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileId.h"
#include "td/telegram/files/FileManager.h"
-#include "td/telegram/files/FileManager.hpp"
+#include "td/telegram/files/FileSourceId.h"
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/MessageEntity.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/Photo.h"
-#include "td/telegram/Photo.hpp"
+#include "td/telegram/PhotoFormat.h"
+#include "td/telegram/secret_api.h"
#include "td/telegram/StickersManager.h"
-#include "td/telegram/StickersManager.hpp"
#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
#include "td/telegram/VideoNotesManager.h"
-#include "td/telegram/VideoNotesManager.hpp"
#include "td/telegram/VideosManager.h"
-#include "td/telegram/VideosManager.hpp"
#include "td/telegram/VoiceNotesManager.h"
-#include "td/telegram/VoiceNotesManager.hpp"
+#include "td/telegram/WebPageBlock.h"
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/SqliteKeyValue.h"
+#include "td/db/SqliteKeyValueAsync.h"
+
+#include "td/utils/algorithm.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/HttpUrl.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_helpers.h"
+#include "td/utils/utf8.h"
-#include <type_traits>
+#include <limits>
namespace td {
-class GetWebPagePreviewQuery : public Td::ResultHandler {
+class GetWebPagePreviewQuery final : public Td::ResultHandler {
Promise<Unit> promise_;
int64 request_id_;
string url_;
@@ -68,100 +74,146 @@ class GetWebPagePreviewQuery : public Td::ResultHandler {
flags |= telegram_api::messages_getWebPagePreview::ENTITIES_MASK;
}
- send_query(G()->net_query_creator().create(
- create_storer(telegram_api::messages_getWebPagePreview(flags, text, std::move(entities)))));
+ send_query(
+ G()->net_query_creator().create(telegram_api::messages_getWebPagePreview(flags, text, std::move(entities))));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getWebPagePreview>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetWebPagePreviewQuery " << to_string(ptr);
- td->web_pages_manager_->on_get_web_page_preview_success(request_id_, url_, std::move(ptr), std::move(promise_));
+ LOG(INFO) << "Receive result for GetWebPagePreviewQuery: " << to_string(ptr);
+ td_->web_pages_manager_->on_get_web_page_preview_success(request_id_, url_, std::move(ptr), std::move(promise_));
}
- void on_error(uint64 id, Status status) override {
- td->web_pages_manager_->on_get_web_page_preview_fail(request_id_, url_, std::move(status), std::move(promise_));
+ void on_error(Status status) final {
+ td_->web_pages_manager_->on_get_web_page_preview_fail(request_id_, url_, std::move(status), std::move(promise_));
}
};
-class GetWebPageQuery : public Td::ResultHandler {
- Promise<Unit> promise_;
+class GetWebPageQuery final : public Td::ResultHandler {
+ Promise<WebPageId> promise_;
+ WebPageId web_page_id_;
string url_;
public:
- explicit GetWebPageQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
+ explicit GetWebPageQuery(Promise<WebPageId> &&promise) : promise_(std::move(promise)) {
}
- void send(const string &url, int32 hash) {
+ void send(WebPageId web_page_id, const string &url, int32 hash) {
+ if (url.empty()) {
+ return promise_.set_value(WebPageId());
+ }
+
+ web_page_id_ = web_page_id;
url_ = url;
- send_query(G()->net_query_creator().create(create_storer(telegram_api::messages_getWebPage(url, hash))));
+ send_query(G()->net_query_creator().create(telegram_api::messages_getWebPage(url, hash)));
}
- void on_result(uint64 id, BufferSlice packet) override {
+ void on_result(BufferSlice packet) final {
auto result_ptr = fetch_result<telegram_api::messages_getWebPage>(packet);
if (result_ptr.is_error()) {
- return on_error(id, result_ptr.move_as_error());
+ return on_error(result_ptr.move_as_error());
}
auto ptr = result_ptr.move_as_ok();
- LOG(INFO) << "Receive result for GetWebPageQuery " << to_string(ptr);
- if (ptr->get_id() != telegram_api::webPageNotModified::ID) {
- auto web_page_id = td->web_pages_manager_->on_get_web_page(std::move(ptr), DialogId());
- td->web_pages_manager_->on_get_web_page_by_url(url_, web_page_id, false);
+ LOG(INFO) << "Receive result for GetWebPageQuery: " << to_string(ptr);
+ if (ptr->get_id() == telegram_api::webPageNotModified::ID) {
+ if (web_page_id_.is_valid()) {
+ auto web_page = move_tl_object_as<telegram_api::webPageNotModified>(ptr);
+ int32 view_count = (web_page->flags_ & telegram_api::webPageNotModified::CACHED_PAGE_VIEWS_MASK) != 0
+ ? web_page->cached_page_views_
+ : 0;
+ td_->web_pages_manager_->on_get_web_page_instant_view_view_count(web_page_id_, view_count);
+ return promise_.set_value(std::move(web_page_id_));
+ } else {
+ LOG(ERROR) << "Receive webPageNotModified for " << url_;
+ return on_error(Status::Error(500, "Receive webPageNotModified"));
+ }
}
-
- promise_.set_value(Unit());
+ auto web_page_id = td_->web_pages_manager_->on_get_web_page(std::move(ptr), DialogId());
+ td_->web_pages_manager_->on_get_web_page_by_url(url_, web_page_id, false);
+ promise_.set_value(std::move(web_page_id));
}
- void on_error(uint64 id, Status status) override {
+ void on_error(Status status) final {
promise_.set_error(std::move(status));
}
};
class WebPagesManager::WebPageInstantView {
public:
- vector<unique_ptr<PageBlock>> page_blocks;
+ vector<unique_ptr<WebPageBlock>> page_blocks;
+ string url;
+ int32 view_count = 0;
int32 hash = 0;
+ bool is_v2 = false;
+ bool is_rtl = false;
bool is_empty = true;
bool is_full = false;
bool is_loaded = false;
bool was_loaded_from_database = false;
- template <class T>
- void store(T &storer) const {
+ template <class StorerT>
+ void store(StorerT &storer) const {
using ::td::store;
+ bool has_url = !url.empty();
+ bool has_view_count = view_count > 0;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_full);
STORE_FLAG(is_loaded);
+ STORE_FLAG(is_rtl);
+ STORE_FLAG(is_v2);
+ STORE_FLAG(has_url);
+ STORE_FLAG(has_view_count);
END_STORE_FLAGS();
store(page_blocks, storer);
store(hash, storer);
+ if (has_url) {
+ store(url, storer);
+ }
+ if (has_view_count) {
+ store(view_count, storer);
+ }
CHECK(!is_empty);
}
- template <class T>
- void parse(T &parser) {
+ template <class ParserT>
+ void parse(ParserT &parser) {
using ::td::parse;
+ bool has_url;
+ bool has_view_count;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_full);
PARSE_FLAG(is_loaded);
+ PARSE_FLAG(is_rtl);
+ PARSE_FLAG(is_v2);
+ PARSE_FLAG(has_url);
+ PARSE_FLAG(has_view_count);
END_PARSE_FLAGS();
parse(page_blocks, parser);
parse(hash, parser);
+ if (has_url) {
+ parse(url, parser);
+ }
+ if (has_view_count) {
+ parse(view_count, parser);
+ }
is_empty = false;
}
friend StringBuilder &operator<<(StringBuilder &string_builder,
const WebPagesManager::WebPageInstantView &instant_view) {
- return string_builder << "InstantView(size = " << instant_view.page_blocks.size()
- << ", hash = " << instant_view.hash << ", is_empty = " << instant_view.is_empty
- << ", is_full = " << instant_view.is_full << ", is_loaded = " << instant_view.is_loaded
+ return string_builder << "InstantView(url = " << instant_view.url << ", size = " << instant_view.page_blocks.size()
+ << ", view_count = " << instant_view.view_count << ", hash = " << instant_view.hash
+ << ", is_empty = " << instant_view.is_empty << ", is_v2 = " << instant_view.is_v2
+ << ", is_rtl = " << instant_view.is_rtl << ", is_full = " << instant_view.is_full
+ << ", is_loaded = " << instant_view.is_loaded
<< ", was_loaded_from_database = " << instant_view.was_loaded_from_database << ")";
}
};
@@ -180,27 +232,31 @@ class WebPagesManager::WebPage {
Dimensions embed_dimensions;
int32 duration = 0;
string author;
- DocumentsManager::DocumentType document_type = DocumentsManager::DocumentType::Unknown;
- FileId document_file_id;
+ Document document;
+ vector<Document> documents;
WebPageInstantView instant_view;
- uint64 logevent_id = 0;
+ FileSourceId file_source_id;
- template <class T>
- void store(T &storer) const {
+ mutable uint64 log_event_id = 0;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
using ::td::store;
bool has_type = !type.empty();
bool has_site_name = !site_name.empty();
bool has_title = !title.empty();
bool has_description = !description.empty();
- bool has_photo = photo.id != -2;
+ bool has_photo = !photo.is_empty();
bool has_embed = !embed_url.empty();
bool has_embed_dimensions = has_embed && embed_dimensions != Dimensions();
bool has_duration = duration > 0;
bool has_author = !author.empty();
- bool has_document = document_type != DocumentsManager::DocumentType::Unknown;
+ bool has_document = !document.empty();
bool has_instant_view = !instant_view.is_empty;
+ bool is_instant_view_v2 = instant_view.is_v2;
bool has_no_hash = true;
+ bool has_documents = !documents.empty();
BEGIN_STORE_FLAGS();
STORE_FLAG(has_type);
STORE_FLAG(has_site_name);
@@ -214,6 +270,8 @@ class WebPagesManager::WebPage {
STORE_FLAG(has_document);
STORE_FLAG(has_instant_view);
STORE_FLAG(has_no_hash);
+ STORE_FLAG(is_instant_view_v2);
+ STORE_FLAG(has_documents);
END_STORE_FLAGS();
store(url, storer);
@@ -247,41 +305,15 @@ class WebPagesManager::WebPage {
store(author, storer);
}
if (has_document) {
- Td *td = storer.context()->td().get_actor_unsafe();
- CHECK(td != nullptr);
-
- store(document_type, storer);
- switch (document_type) {
- case DocumentsManager::DocumentType::Animation:
- td->animations_manager_->store_animation(document_file_id, storer);
- break;
- case DocumentsManager::DocumentType::Audio:
- td->audios_manager_->store_audio(document_file_id, storer);
- break;
- case DocumentsManager::DocumentType::General:
- td->documents_manager_->store_document(document_file_id, storer);
- break;
- case DocumentsManager::DocumentType::Sticker:
- td->stickers_manager_->store_sticker(document_file_id, false, storer);
- break;
- case DocumentsManager::DocumentType::Video:
- td->videos_manager_->store_video(document_file_id, storer);
- break;
- case DocumentsManager::DocumentType::VideoNote:
- td->video_notes_manager_->store_video_note(document_file_id, storer);
- break;
- case DocumentsManager::DocumentType::VoiceNote:
- td->voice_notes_manager_->store_voice_note(document_file_id, storer);
- break;
- case DocumentsManager::DocumentType::Unknown:
- default:
- UNREACHABLE();
- }
+ store(document, storer);
+ }
+ if (has_documents) {
+ store(documents, storer);
}
}
- template <class T>
- void parse(T &parser) {
+ template <class ParserT>
+ void parse(ParserT &parser) {
using ::td::parse;
bool has_type;
bool has_site_name;
@@ -294,7 +326,9 @@ class WebPagesManager::WebPage {
bool has_author;
bool has_document;
bool has_instant_view;
+ bool is_instant_view_v2;
bool has_no_hash;
+ bool has_documents;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_type);
PARSE_FLAG(has_site_name);
@@ -308,6 +342,8 @@ class WebPagesManager::WebPage {
PARSE_FLAG(has_document);
PARSE_FLAG(has_instant_view);
PARSE_FLAG(has_no_hash);
+ PARSE_FLAG(is_instant_view_v2);
+ PARSE_FLAG(has_documents);
END_PARSE_FLAGS();
parse(url, parser);
@@ -330,8 +366,6 @@ class WebPagesManager::WebPage {
}
if (has_photo) {
parse(photo, parser);
- } else {
- photo.id = -2;
}
if (has_embed) {
parse(embed_url, parser);
@@ -347,1041 +381,31 @@ class WebPagesManager::WebPage {
parse(author, parser);
}
if (has_document) {
- Td *td = parser.context()->td().get_actor_unsafe();
- CHECK(td != nullptr);
-
- parse(document_type, parser);
- switch (document_type) {
- case DocumentsManager::DocumentType::Animation:
- document_file_id = td->animations_manager_->parse_animation(parser);
- break;
- case DocumentsManager::DocumentType::Audio:
- document_file_id = td->audios_manager_->parse_audio(parser);
- break;
- case DocumentsManager::DocumentType::General:
- document_file_id = td->documents_manager_->parse_document(parser);
- break;
- case DocumentsManager::DocumentType::Sticker:
- document_file_id = td->stickers_manager_->parse_sticker(false, parser);
- break;
- case DocumentsManager::DocumentType::Video:
- document_file_id = td->videos_manager_->parse_video(parser);
- break;
- case DocumentsManager::DocumentType::VideoNote:
- document_file_id = td->video_notes_manager_->parse_video_note(parser);
- break;
- case DocumentsManager::DocumentType::VoiceNote:
- document_file_id = td->voice_notes_manager_->parse_voice_note(parser);
- break;
- case DocumentsManager::DocumentType::Unknown:
- default:
- UNREACHABLE();
- }
- if (!document_file_id.is_valid()) {
- LOG(ERROR) << "Parse invalid document_file_id";
- document_type = DocumentsManager::DocumentType::Unknown;
- }
+ parse(document, parser);
+ }
+ if (has_documents) {
+ parse(documents, parser);
}
if (has_instant_view) {
instant_view.is_empty = false;
}
- }
-};
-
-class WebPagesManager::RichText {
- public:
- enum class Type : int32 { Plain, Bold, Italic, Underline, Strikethrough, Fixed, Url, EmailAddress, Concatenation };
- Type type = Type::Plain;
- string content;
- vector<RichText> texts;
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(type, storer);
- store(content, storer);
- store(texts, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(type, parser);
- parse(content, parser);
- parse(texts, parser);
- }
-};
-
-class WebPagesManager::PageBlock {
- public:
- enum class Type : int32 {
- Title,
- Subtitle,
- AuthorDate,
- Header,
- Subheader,
- Paragraph,
- Preformatted,
- Footer,
- Divider,
- Anchor,
- List,
- BlockQuote,
- PullQuote,
- Animation,
- Photo,
- Video,
- Cover,
- Embedded,
- EmbeddedPost,
- Collage,
- Slideshow,
- ChatLink,
- Audio
- };
-
- virtual Type get_type() const = 0;
-
- virtual tl_object_ptr<td_api::PageBlock> get_page_block_object() const = 0;
-
- PageBlock() = default;
- PageBlock(const PageBlock &) = delete;
- PageBlock &operator=(const PageBlock &) = delete;
- PageBlock(PageBlock &&) = delete;
- PageBlock &operator=(PageBlock &&) = delete;
- virtual ~PageBlock() = default;
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- Type type = get_type();
- store(type, storer);
- call_impl(type, this, [&](const auto *object) { store(*object, storer); });
- }
- template <class T>
- static std::unique_ptr<PageBlock> parse(T &parser) {
- using ::td::parse;
- Type type;
- parse(type, parser);
- std::unique_ptr<PageBlock> res;
- call_impl(type, nullptr, [&](const auto *ptr) {
- using ObjT = std::decay_t<decltype(*ptr)>;
- auto object = std::make_unique<ObjT>();
- parse(*object, parser);
- res = std::move(object);
- });
- return res;
- }
-
- private:
- template <class F>
- static void call_impl(Type type, const PageBlock *ptr, F &&f);
-};
-
-template <class T>
-void store(const unique_ptr<WebPagesManager::PageBlock> &block, T &storer) {
- block->store(storer);
-}
-
-template <class T>
-void parse(unique_ptr<WebPagesManager::PageBlock> &block, T &parser) {
- block = WebPagesManager::PageBlock::parse(parser);
-}
-
-class WebPagesManager::PageBlockTitle : public PageBlock {
- RichText title;
-
- public:
- PageBlockTitle() = default;
-
- explicit PageBlockTitle(RichText &&title) : title(std::move(title)) {
- }
-
- Type get_type() const override {
- return Type::Title;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockTitle>(get_rich_text_object(title));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(title, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(title, parser);
- }
-};
-
-class WebPagesManager::PageBlockSubtitle : public PageBlock {
- RichText subtitle;
-
- public:
- PageBlockSubtitle() = default;
- explicit PageBlockSubtitle(RichText &&subtitle) : subtitle(std::move(subtitle)) {
- }
-
- Type get_type() const override {
- return Type::Subtitle;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockSubtitle>(get_rich_text_object(subtitle));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(subtitle, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(subtitle, parser);
- }
-};
-
-class WebPagesManager::PageBlockAuthorDate : public PageBlock {
- RichText author;
- int32 date = 0;
-
- public:
- PageBlockAuthorDate() = default;
- PageBlockAuthorDate(RichText &&author, int32 date) : author(std::move(author)), date(max(date, 0)) {
- }
-
- Type get_type() const override {
- return Type::AuthorDate;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockAuthorDate>(get_rich_text_object(author), date);
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(author, storer);
- store(date, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(author, parser);
- parse(date, parser);
- }
-};
-
-class WebPagesManager::PageBlockHeader : public PageBlock {
- RichText header;
-
- public:
- PageBlockHeader() = default;
- explicit PageBlockHeader(RichText &&header) : header(std::move(header)) {
- }
-
- Type get_type() const override {
- return Type::Header;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockHeader>(get_rich_text_object(header));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(header, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(header, parser);
- }
-};
-
-class WebPagesManager::PageBlockSubheader : public PageBlock {
- RichText subheader;
-
- public:
- PageBlockSubheader() = default;
- explicit PageBlockSubheader(RichText &&subheader) : subheader(std::move(subheader)) {
- }
-
- Type get_type() const override {
- return Type::Subheader;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockSubheader>(get_rich_text_object(subheader));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(subheader, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(subheader, parser);
- }
-};
-
-class WebPagesManager::PageBlockParagraph : public PageBlock {
- RichText text;
-
- public:
- PageBlockParagraph() = default;
- explicit PageBlockParagraph(RichText &&text) : text(std::move(text)) {
- }
-
- Type get_type() const override {
- return Type::Paragraph;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockParagraph>(get_rich_text_object(text));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(text, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(text, parser);
- }
-};
-
-class WebPagesManager::PageBlockPreformatted : public PageBlock {
- RichText text;
- string language;
-
- public:
- PageBlockPreformatted() = default;
- PageBlockPreformatted(RichText &&text, string language) : text(std::move(text)), language(std::move(language)) {
- }
-
- Type get_type() const override {
- return Type::Preformatted;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockPreformatted>(get_rich_text_object(text), language);
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(text, storer);
- store(language, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(text, parser);
- parse(language, parser);
- }
-};
-
-class WebPagesManager::PageBlockFooter : public PageBlock {
- RichText footer;
-
- public:
- PageBlockFooter() = default;
- explicit PageBlockFooter(RichText &&footer) : footer(std::move(footer)) {
- }
-
- Type get_type() const override {
- return Type::Footer;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockFooter>(get_rich_text_object(footer));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(footer, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(footer, parser);
- }
-};
-
-class WebPagesManager::PageBlockDivider : public PageBlock {
- public:
- Type get_type() const override {
- return Type::Divider;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockDivider>();
- }
- template <class T>
- void store(T &storer) const {
- }
- template <class T>
- void parse(T &parser) {
- }
-};
-
-class WebPagesManager::PageBlockAnchor : public PageBlock {
- string name;
-
- public:
- PageBlockAnchor() = default;
- explicit PageBlockAnchor(string name) : name(std::move(name)) {
- }
-
- Type get_type() const override {
- return Type::Anchor;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockAnchor>(name);
- }
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(name, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(name, parser);
- }
-};
-
-class WebPagesManager::PageBlockList : public PageBlock {
- vector<RichText> items;
- bool is_ordered = false;
-
- public:
- PageBlockList() = default;
- PageBlockList(vector<RichText> &&items, bool is_ordered) : items(std::move(items)), is_ordered(is_ordered) {
- }
-
- Type get_type() const override {
- return Type::List;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockList>(get_rich_text_objects(items), is_ordered);
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
-
- BEGIN_STORE_FLAGS();
- STORE_FLAG(is_ordered);
- END_STORE_FLAGS();
-
- store(items, storer);
- }
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
-
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(is_ordered);
- END_PARSE_FLAGS();
-
- parse(items, parser);
- }
-};
-
-class WebPagesManager::PageBlockBlockQuote : public PageBlock {
- RichText text;
- RichText caption;
-
- public:
- PageBlockBlockQuote() = default;
- PageBlockBlockQuote(RichText &&text, RichText &&caption) : text(std::move(text)), caption(std::move(caption)) {
- }
-
- Type get_type() const override {
- return Type::BlockQuote;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockBlockQuote>(get_rich_text_object(text), get_rich_text_object(caption));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(text, storer);
- store(caption, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(text, parser);
- parse(caption, parser);
- }
-};
-
-class WebPagesManager::PageBlockPullQuote : public PageBlock {
- RichText text;
- RichText caption;
-
- public:
- PageBlockPullQuote() = default;
- PageBlockPullQuote(RichText &&text, RichText &&caption) : text(std::move(text)), caption(std::move(caption)) {
- }
-
- Type get_type() const override {
- return Type::PullQuote;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockPullQuote>(get_rich_text_object(text), get_rich_text_object(caption));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(text, storer);
- store(caption, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(text, parser);
- parse(caption, parser);
- }
-};
-
-class WebPagesManager::PageBlockAnimation : public PageBlock {
- FileId animation_file_id;
- RichText caption;
- bool need_autoplay = false;
-
- public:
- PageBlockAnimation() = default;
- PageBlockAnimation(FileId animation_file_id, RichText &&caption, bool need_autoplay)
- : animation_file_id(animation_file_id), caption(std::move(caption)), need_autoplay(need_autoplay) {
- }
-
- Type get_type() const override {
- return Type::Animation;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockAnimation>(
- G()->td().get_actor_unsafe()->animations_manager_->get_animation_object(animation_file_id,
- "get_page_block_object"),
- get_rich_text_object(caption), need_autoplay);
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
-
- bool has_empty_animation = !animation_file_id.is_valid();
- BEGIN_STORE_FLAGS();
- STORE_FLAG(need_autoplay);
- STORE_FLAG(has_empty_animation);
- END_STORE_FLAGS();
-
- if (!has_empty_animation) {
- storer.context()->td().get_actor_unsafe()->animations_manager_->store_animation(animation_file_id, storer);
- }
- store(caption, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
-
- bool has_empty_animation;
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(need_autoplay);
- PARSE_FLAG(has_empty_animation);
- END_PARSE_FLAGS();
-
- if (parser.version() >= static_cast<int32>(Version::FixWebPageInstantViewDatabase)) {
- if (!has_empty_animation) {
- animation_file_id = parser.context()->td().get_actor_unsafe()->animations_manager_->parse_animation(parser);
- } else {
- animation_file_id = FileId();
- }
- } else {
- animation_file_id = FileId();
- parser.set_error("Wrong stored object");
- }
- parse(caption, parser);
- }
-};
-
-class WebPagesManager::PageBlockPhoto : public PageBlock {
- Photo photo;
- RichText caption;
-
- public:
- PageBlockPhoto() = default;
- PageBlockPhoto(Photo photo, RichText &&caption) : photo(std::move(photo)), caption(std::move(caption)) {
- }
-
- Type get_type() const override {
- return Type::Photo;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockPhoto>(
- get_photo_object(G()->td().get_actor_unsafe()->file_manager_.get(), &photo), get_rich_text_object(caption));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(photo, storer);
- store(caption, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(photo, parser);
- parse(caption, parser);
- }
-};
-
-class WebPagesManager::PageBlockVideo : public PageBlock {
- FileId video_file_id;
- RichText caption;
- bool need_autoplay = false;
- bool is_looped = false;
-
- public:
- PageBlockVideo() = default;
- PageBlockVideo(FileId video_file_id, RichText &&caption, bool need_autoplay, bool is_looped)
- : video_file_id(video_file_id), caption(std::move(caption)), need_autoplay(need_autoplay), is_looped(is_looped) {
- }
-
- Type get_type() const override {
- return Type::Video;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockVideo>(
- G()->td().get_actor_unsafe()->videos_manager_->get_video_object(video_file_id), get_rich_text_object(caption),
- need_autoplay, is_looped);
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
-
- bool has_empty_video = !video_file_id.is_valid();
- BEGIN_STORE_FLAGS();
- STORE_FLAG(need_autoplay);
- STORE_FLAG(is_looped);
- STORE_FLAG(has_empty_video);
- END_STORE_FLAGS();
-
- if (!has_empty_video) {
- storer.context()->td().get_actor_unsafe()->videos_manager_->store_video(video_file_id, storer);
- }
- store(caption, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
-
- bool has_empty_video;
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(need_autoplay);
- PARSE_FLAG(is_looped);
- PARSE_FLAG(has_empty_video);
- END_PARSE_FLAGS();
-
- if (parser.version() >= static_cast<int32>(Version::FixWebPageInstantViewDatabase)) {
- if (!has_empty_video) {
- video_file_id = parser.context()->td().get_actor_unsafe()->videos_manager_->parse_video(parser);
- } else {
- video_file_id = FileId();
- }
- } else {
- video_file_id = FileId();
- parser.set_error("Wrong stored object");
- }
- parse(caption, parser);
- }
-};
-
-class WebPagesManager::PageBlockCover : public PageBlock {
- unique_ptr<PageBlock> cover;
-
- public:
- PageBlockCover() = default;
- explicit PageBlockCover(unique_ptr<PageBlock> &&cover) : cover(std::move(cover)) {
- }
-
- Type get_type() const override {
- return Type::Cover;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockCover>(cover->get_page_block_object());
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(cover, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(cover, parser);
- }
-};
-
-class WebPagesManager::PageBlockEmbedded : public PageBlock {
- string url;
- string html;
- Photo poster_photo;
- Dimensions dimensions;
- RichText caption;
- bool is_full_width;
- bool allow_scrolling;
-
- public:
- PageBlockEmbedded() = default;
- PageBlockEmbedded(string url, string html, Photo poster_photo, Dimensions dimensions, RichText &&caption,
- bool is_full_width, bool allow_scrolling)
- : url(std::move(url))
- , html(std::move(html))
- , poster_photo(std::move(poster_photo))
- , dimensions(dimensions)
- , caption(std::move(caption))
- , is_full_width(is_full_width)
- , allow_scrolling(allow_scrolling) {
- }
-
- Type get_type() const override {
- return Type::Embedded;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockEmbedded>(
- url, html, get_photo_object(G()->td().get_actor_unsafe()->file_manager_.get(), &poster_photo), dimensions.width,
- dimensions.height, get_rich_text_object(caption), is_full_width, allow_scrolling);
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- BEGIN_STORE_FLAGS();
- STORE_FLAG(is_full_width);
- STORE_FLAG(allow_scrolling);
- END_STORE_FLAGS();
-
- store(url, storer);
- store(html, storer);
- store(poster_photo, storer);
- store(dimensions, storer);
- store(caption, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(is_full_width);
- PARSE_FLAG(allow_scrolling);
- END_PARSE_FLAGS();
-
- parse(url, parser);
- parse(html, parser);
- parse(poster_photo, parser);
- parse(dimensions, parser);
- parse(caption, parser);
- }
-};
-
-class WebPagesManager::PageBlockEmbeddedPost : public PageBlock {
- string url;
- string author;
- Photo author_photo;
- int32 date;
- vector<unique_ptr<PageBlock>> page_blocks;
- RichText caption;
-
- public:
- PageBlockEmbeddedPost() = default;
- PageBlockEmbeddedPost(string url, string author, Photo author_photo, int32 date,
- vector<unique_ptr<PageBlock>> &&page_blocks, RichText &&caption)
- : url(std::move(url))
- , author(std::move(author))
- , author_photo(std::move(author_photo))
- , date(max(date, 0))
- , page_blocks(std::move(page_blocks))
- , caption(std::move(caption)) {
- }
-
- Type get_type() const override {
- return Type::EmbeddedPost;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockEmbeddedPost>(
- url, author, get_photo_object(G()->td().get_actor_unsafe()->file_manager_.get(), &author_photo), date,
- get_page_block_objects(page_blocks), get_rich_text_object(caption));
- }
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(url, storer);
- store(author, storer);
- store(author_photo, storer);
- store(date, storer);
- store(page_blocks, storer);
- store(caption, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(url, parser);
- parse(author, parser);
- parse(author_photo, parser);
- parse(date, parser);
- parse(page_blocks, parser);
- parse(caption, parser);
- }
-};
-
-class WebPagesManager::PageBlockCollage : public PageBlock {
- vector<unique_ptr<PageBlock>> page_blocks;
- RichText caption;
-
- public:
- PageBlockCollage() = default;
- PageBlockCollage(vector<unique_ptr<PageBlock>> &&page_blocks, RichText &&caption)
- : page_blocks(std::move(page_blocks)), caption(std::move(caption)) {
- }
-
- Type get_type() const override {
- return Type::Collage;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockCollage>(get_page_block_objects(page_blocks), get_rich_text_object(caption));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(page_blocks, storer);
- store(caption, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(page_blocks, parser);
- parse(caption, parser);
- }
-};
-
-class WebPagesManager::PageBlockSlideshow : public PageBlock {
- vector<unique_ptr<PageBlock>> page_blocks;
- RichText caption;
-
- public:
- PageBlockSlideshow() = default;
- PageBlockSlideshow(vector<unique_ptr<PageBlock>> &&page_blocks, RichText &&caption)
- : page_blocks(std::move(page_blocks)), caption(std::move(caption)) {
- }
-
- Type get_type() const override {
- return Type::Slideshow;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockSlideshow>(get_page_block_objects(page_blocks),
- get_rich_text_object(caption));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(page_blocks, storer);
- store(caption, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(page_blocks, parser);
- parse(caption, parser);
- }
-};
-
-class WebPagesManager::PageBlockChatLink : public PageBlock {
- string title;
- DialogPhoto photo;
- string username;
-
- public:
- PageBlockChatLink() = default;
- PageBlockChatLink(string title, DialogPhoto photo, string username)
- : title(std::move(title)), photo(std::move(photo)), username(std::move(username)) {
- }
-
- Type get_type() const override {
- return Type::ChatLink;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockChatLink>(
- title, get_chat_photo_object(G()->td().get_actor_unsafe()->file_manager_.get(), &photo), username);
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
- store(title, storer);
- store(photo, storer);
- store(username, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
- parse(title, parser);
- parse(photo, parser);
- parse(username, parser);
- }
-};
-
-class WebPagesManager::PageBlockAudio : public PageBlock {
- FileId audio_file_id;
- RichText caption;
-
- public:
- PageBlockAudio() = default;
- PageBlockAudio(FileId audio_file_id, RichText &&caption) : audio_file_id(audio_file_id), caption(std::move(caption)) {
- }
-
- Type get_type() const override {
- return Type::Audio;
- }
-
- tl_object_ptr<td_api::PageBlock> get_page_block_object() const override {
- return make_tl_object<td_api::pageBlockAudio>(
- G()->td().get_actor_unsafe()->audios_manager_->get_audio_object(audio_file_id), get_rich_text_object(caption));
- }
-
- template <class T>
- void store(T &storer) const {
- using ::td::store;
-
- bool has_empty_audio = !audio_file_id.is_valid();
- BEGIN_STORE_FLAGS();
- STORE_FLAG(has_empty_audio);
- END_STORE_FLAGS();
-
- if (!has_empty_audio) {
- storer.context()->td().get_actor_unsafe()->audios_manager_->store_audio(audio_file_id, storer);
+ if (is_instant_view_v2) {
+ instant_view.is_v2 = true;
}
- store(caption, storer);
}
- template <class T>
- void parse(T &parser) {
- using ::td::parse;
-
- bool has_empty_audio;
- if (parser.version() >= static_cast<int32>(Version::FixPageBlockAudioEmptyFile)) {
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(has_empty_audio);
- END_PARSE_FLAGS();
- } else {
- has_empty_audio = false;
- }
-
- if (!has_empty_audio) {
- audio_file_id = parser.context()->td().get_actor_unsafe()->audios_manager_->parse_audio(parser);
- } else {
- audio_file_id = FileId();
- }
- parse(caption, parser);
+ friend bool operator==(const WebPage &lhs, const WebPage &rhs) {
+ return lhs.url == rhs.url && lhs.display_url == rhs.display_url && lhs.type == rhs.type &&
+ lhs.site_name == rhs.site_name && lhs.title == rhs.title && lhs.description == rhs.description &&
+ lhs.photo == rhs.photo && lhs.type == rhs.type && lhs.embed_url == rhs.embed_url &&
+ lhs.embed_type == rhs.embed_type && lhs.embed_dimensions == rhs.embed_dimensions &&
+ lhs.duration == rhs.duration && lhs.author == rhs.author && lhs.document == rhs.document &&
+ lhs.documents == rhs.documents && lhs.instant_view.is_empty == rhs.instant_view.is_empty &&
+ lhs.instant_view.is_v2 == rhs.instant_view.is_v2;
}
};
-template <class F>
-void WebPagesManager::PageBlock::call_impl(Type type, const PageBlock *ptr, F &&f) {
- switch (type) {
- case Type::Title:
- return f(static_cast<const WebPagesManager::PageBlockTitle *>(ptr));
- case Type::Subtitle:
- return f(static_cast<const WebPagesManager::PageBlockSubtitle *>(ptr));
- case Type::AuthorDate:
- return f(static_cast<const WebPagesManager::PageBlockAuthorDate *>(ptr));
- case Type::Header:
- return f(static_cast<const WebPagesManager::PageBlockHeader *>(ptr));
- case Type::Subheader:
- return f(static_cast<const WebPagesManager::PageBlockSubheader *>(ptr));
- case Type::Paragraph:
- return f(static_cast<const WebPagesManager::PageBlockParagraph *>(ptr));
- case Type::Preformatted:
- return f(static_cast<const WebPagesManager::PageBlockPreformatted *>(ptr));
- case Type::Footer:
- return f(static_cast<const WebPagesManager::PageBlockFooter *>(ptr));
- case Type::Divider:
- return f(static_cast<const WebPagesManager::PageBlockDivider *>(ptr));
- case Type::Anchor:
- return f(static_cast<const WebPagesManager::PageBlockAnchor *>(ptr));
- case Type::List:
- return f(static_cast<const WebPagesManager::PageBlockList *>(ptr));
- case Type::BlockQuote:
- return f(static_cast<const WebPagesManager::PageBlockBlockQuote *>(ptr));
- case Type::PullQuote:
- return f(static_cast<const WebPagesManager::PageBlockPullQuote *>(ptr));
- case Type::Animation:
- return f(static_cast<const WebPagesManager::PageBlockAnimation *>(ptr));
- case Type::Photo:
- return f(static_cast<const WebPagesManager::PageBlockPhoto *>(ptr));
- case Type::Video:
- return f(static_cast<const WebPagesManager::PageBlockVideo *>(ptr));
- case Type::Cover:
- return f(static_cast<const WebPagesManager::PageBlockCover *>(ptr));
- case Type::Embedded:
- return f(static_cast<const WebPagesManager::PageBlockEmbedded *>(ptr));
- case Type::EmbeddedPost:
- return f(static_cast<const WebPagesManager::PageBlockEmbeddedPost *>(ptr));
- case Type::Collage:
- return f(static_cast<const WebPagesManager::PageBlockCollage *>(ptr));
- case Type::Slideshow:
- return f(static_cast<const WebPagesManager::PageBlockSlideshow *>(ptr));
- case Type::ChatLink:
- return f(static_cast<const WebPagesManager::PageBlockChatLink *>(ptr));
- case Type::Audio:
- return f(static_cast<const WebPagesManager::PageBlockAudio *>(ptr));
- }
- UNREACHABLE();
-}
-
WebPagesManager::WebPagesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
pending_web_pages_timeout_.set_callback(on_pending_web_page_timeout_callback);
pending_web_pages_timeout_.set_callback_data(static_cast<void *>(this));
@@ -1389,13 +413,21 @@ WebPagesManager::WebPagesManager(Td *td, ActorShared<> parent) : td_(td), parent
void WebPagesManager::tear_down() {
parent_.reset();
+
+ LOG(DEBUG) << "Have " << web_pages_.calc_size() << " web pages to free";
}
-WebPagesManager::~WebPagesManager() = default;
+WebPagesManager::~WebPagesManager() {
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), web_pages_, web_page_messages_,
+ got_web_page_previews_, url_to_web_page_id_, url_to_file_source_id_);
+}
WebPageId WebPagesManager::on_get_web_page(tl_object_ptr<telegram_api::WebPage> &&web_page_ptr,
DialogId owner_dialog_id) {
CHECK(web_page_ptr != nullptr);
+ if (td_->auth_manager_->is_bot()) {
+ return WebPageId();
+ }
LOG(DEBUG) << "Got " << to_string(web_page_ptr);
switch (web_page_ptr->get_id()) {
case telegram_api::webPageEmpty::ID: {
@@ -1407,30 +439,24 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr<telegram_api::WebPage>
}
LOG(INFO) << "Got empty " << web_page_id;
- auto web_page_to_delete = get_web_page(web_page_id);
+ const WebPage *web_page_to_delete = get_web_page(web_page_id);
if (web_page_to_delete != nullptr) {
- if (web_page_to_delete->logevent_id != 0) {
+ if (web_page_to_delete->log_event_id != 0) {
LOG(INFO) << "Erase " << web_page_id << " from binlog";
- BinlogHelper::erase(G()->td_db()->get_binlog(), web_page_to_delete->logevent_id);
- web_page_to_delete->logevent_id = 0;
+ binlog_erase(G()->td_db()->get_binlog(), web_page_to_delete->log_event_id);
+ web_page_to_delete->log_event_id = 0;
+ }
+ if (web_page_to_delete->file_source_id.is_valid()) {
+ td_->file_manager_->change_files_source(web_page_to_delete->file_source_id,
+ get_web_page_file_ids(web_page_to_delete), vector<FileId>());
}
web_pages_.erase(web_page_id);
}
- update_messages_content(web_page_id, false);
- if (!G()->parameters().use_message_db) {
- // update_messages_content(web_page_id, false);
- } else {
+ on_web_page_changed(web_page_id, false);
+ if (G()->parameters().use_message_db) {
LOG(INFO) << "Delete " << web_page_id << " from database";
- G()->td_db()->get_sqlite_pmc()->erase(get_web_page_database_key(web_page_id), Auto()
- /*
- PromiseCreator::lambda([web_page_id](Result<> result) {
- if (result.is_ok()) {
- send_closure(G()->web_pages_manager(), &WebPagesManager::update_messages_content, web_page_id, false);
- }
- })
- */
- );
+ G()->td_db()->get_sqlite_pmc()->erase(get_web_page_database_key(web_page_id), Auto());
G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto());
}
@@ -1445,7 +471,8 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr<telegram_api::WebPage>
}
auto web_page_date = web_page->date_;
- LOG(INFO) << "Got pending " << web_page_id << ", date = " << web_page_date << ", now = " << G()->server_time();
+ LOG(INFO) << "Got pending " << web_page_id << ", force_get_date = " << web_page_date
+ << ", now = " << G()->server_time();
pending_web_pages_timeout_.add_timeout_in(web_page_id.get(), max(web_page_date - G()->server_time(), 1.0));
return web_page_id;
@@ -1467,18 +494,13 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr<telegram_api::WebPage>
page->site_name = std::move(web_page->site_name_);
page->title = std::move(web_page->title_);
page->description = std::move(web_page->description_);
- if ((web_page->flags_ & WEBPAGE_FLAG_HAS_PHOTO) && web_page->photo_->get_id() == telegram_api::photo::ID) {
- page->photo = get_photo(td_->file_manager_.get(), move_tl_object_as<telegram_api::photo>(web_page->photo_),
- owner_dialog_id);
- } else {
- page->photo.id = -2;
- }
+ page->photo = get_photo(td_->file_manager_.get(), std::move(web_page->photo_), owner_dialog_id);
if (web_page->flags_ & WEBPAGE_FLAG_HAS_EMBEDDED_PREVIEW) {
page->embed_url = std::move(web_page->embed_url_);
page->embed_type = std::move(web_page->embed_type_);
}
if (web_page->flags_ & WEBPAGE_FLAG_HAS_EMBEDDED_PREVIEW_SIZE) {
- page->embed_dimensions = get_dimensions(web_page->embed_width_, web_page->embed_height_);
+ page->embed_dimensions = get_dimensions(web_page->embed_width_, web_page->embed_height_, "webPage");
}
if (web_page->flags_ & WEBPAGE_FLAG_HAS_DURATION) {
page->duration = web_page->duration_;
@@ -1495,10 +517,24 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr<telegram_api::WebPage>
if (document_id == telegram_api::document::ID) {
auto parsed_document = td_->documents_manager_->on_get_document(
move_tl_object_as<telegram_api::document>(web_page->document_), owner_dialog_id);
- page->document_type = parsed_document.first;
- page->document_file_id = parsed_document.second;
+ page->document = std::move(parsed_document);
}
}
+ for (auto &attribute : web_page->attributes_) {
+ CHECK(attribute != nullptr);
+ page->documents.clear();
+ for (auto &document : attribute->documents_) {
+ int32 document_id = document->get_id();
+ if (document_id == telegram_api::document::ID) {
+ auto parsed_document = td_->documents_manager_->on_get_document(
+ move_tl_object_as<telegram_api::document>(document), owner_dialog_id);
+ if (!parsed_document.empty()) {
+ page->documents.push_back(std::move(parsed_document));
+ }
+ }
+ }
+ // TODO attribute->settings_
+ }
if (web_page->flags_ & WEBPAGE_FLAG_HAS_INSTANT_VIEW) {
on_get_web_page_instant_view(page.get(), std::move(web_page->cached_page_), web_page->hash_, owner_dialog_id);
}
@@ -1518,32 +554,67 @@ WebPageId WebPagesManager::on_get_web_page(tl_object_ptr<telegram_api::WebPage>
void WebPagesManager::update_web_page(unique_ptr<WebPage> web_page, WebPageId web_page_id, bool from_binlog,
bool from_database) {
- LOG(INFO) << "Update " << web_page_id;
+ LOG(INFO) << "Update " << web_page_id << (from_database ? " from database" : (from_binlog ? " from binlog" : ""));
CHECK(web_page != nullptr);
auto &page = web_pages_[web_page_id];
+ auto old_file_ids = get_web_page_file_ids(page.get());
WebPageInstantView old_instant_view;
+ bool is_changed = true;
if (page != nullptr) {
+ if (*page == *web_page) {
+ is_changed = false;
+ }
+
old_instant_view = std::move(page->instant_view);
- web_page->logevent_id = page->logevent_id;
+ web_page->log_event_id = page->log_event_id;
+ } else {
+ auto it = url_to_file_source_id_.find(web_page->url);
+ if (it != url_to_file_source_id_.end()) {
+ VLOG(file_references) << "Move " << it->second << " inside of " << web_page_id;
+ web_page->file_source_id = it->second;
+ url_to_file_source_id_.erase(it);
+ }
}
page = std::move(web_page);
+ // must be called before any other action for correct behavior of get_url_file_source_id
+ if (!page->url.empty()) {
+ on_get_web_page_by_url(page->url, web_page_id, from_database);
+ }
+
update_web_page_instant_view(web_page_id, page->instant_view, std::move(old_instant_view));
- on_get_web_page_by_url(page->url, web_page_id, from_database);
+ auto new_file_ids = get_web_page_file_ids(page.get());
+ if (old_file_ids != new_file_ids) {
+ td_->file_manager_->change_files_source(get_web_page_file_source_id(page.get()), old_file_ids, new_file_ids);
+ }
- update_messages_content(web_page_id, true);
+ if (is_changed && !from_database) {
+ on_web_page_changed(web_page_id, true);
- if (!from_database) {
save_web_page(page.get(), web_page_id, from_binlog);
}
}
void WebPagesManager::update_web_page_instant_view(WebPageId web_page_id, WebPageInstantView &new_instant_view,
WebPageInstantView &&old_instant_view) {
+ LOG(INFO) << "Merge new " << new_instant_view << " and old " << old_instant_view;
+
bool new_from_database = new_instant_view.was_loaded_from_database;
bool old_from_database = old_instant_view.was_loaded_from_database;
+
+ if (new_instant_view.is_empty && !new_from_database) {
+ // new_instant_view is from server and is empty, need to delete the instant view
+ if (G()->parameters().use_message_db && (!old_instant_view.is_empty || !old_from_database)) {
+ // we have no instant view and probably want it to be deleted from database
+ LOG(INFO) << "Erase instant view of " << web_page_id << " from database";
+ new_instant_view.was_loaded_from_database = true;
+ G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto());
+ }
+ return;
+ }
+
if (need_use_old_instant_view(new_instant_view, old_instant_view)) {
new_instant_view = std::move(old_instant_view);
}
@@ -1556,7 +627,7 @@ void WebPagesManager::update_web_page_instant_view(WebPageId web_page_id, WebPag
auto previous_queries =
load_web_page_instant_view_queries.partial.size() + load_web_page_instant_view_queries.full.size();
if (previous_queries == 0) {
- // try to load it only if there is no pending load queries
+ // try to load it only if there are no pending load queries
load_web_page_instant_view(web_page_id, false, Auto());
return;
}
@@ -1585,7 +656,6 @@ void WebPagesManager::update_web_page_instant_view(WebPageId web_page_id, WebPag
bool WebPagesManager::need_use_old_instant_view(const WebPageInstantView &new_instant_view,
const WebPageInstantView &old_instant_view) {
- LOG(INFO) << "Merge " << new_instant_view << " and " << old_instant_view;
if (old_instant_view.is_empty || !old_instant_view.is_loaded) {
return false;
}
@@ -1605,16 +675,36 @@ bool WebPagesManager::need_use_old_instant_view(const WebPageInstantView &new_in
return new_instant_view.was_loaded_from_database;
}
+void WebPagesManager::on_get_web_page_instant_view_view_count(WebPageId web_page_id, int32 view_count) {
+ if (get_web_page_instant_view(web_page_id) == nullptr) {
+ return;
+ }
+
+ auto *instant_view = &web_pages_[web_page_id]->instant_view;
+ CHECK(!instant_view->is_empty);
+ if (instant_view->view_count >= view_count) {
+ return;
+ }
+ instant_view->view_count = view_count;
+ if (G()->parameters().use_message_db) {
+ LOG(INFO) << "Save instant view of " << web_page_id << " to database after updating view count to " << view_count;
+ G()->td_db()->get_sqlite_pmc()->set(get_web_page_instant_view_database_key(web_page_id),
+ log_event_store(*instant_view).as_slice().str(), Auto());
+ }
+}
+
void WebPagesManager::on_get_web_page_by_url(const string &url, WebPageId web_page_id, bool from_database) {
+ auto &cached_web_page_id = url_to_web_page_id_[url];
if (!from_database && G()->parameters().use_message_db) {
if (web_page_id.is_valid()) {
- G()->td_db()->get_sqlite_pmc()->set(get_web_page_url_database_key(url), to_string(web_page_id.get()), Auto());
+ if (cached_web_page_id != web_page_id) { // not already saved
+ G()->td_db()->get_sqlite_pmc()->set(get_web_page_url_database_key(url), to_string(web_page_id.get()), Auto());
+ }
} else {
G()->td_db()->get_sqlite_pmc()->erase(get_web_page_url_database_key(url), Auto());
}
}
- auto &cached_web_page_id = url_to_web_page_id_[url];
if (cached_web_page_id.is_valid() && web_page_id.is_valid() && web_page_id != cached_web_page_id) {
LOG(ERROR) << "Url \"" << url << "\" preview is changed from " << cached_web_page_id << " to " << web_page_id;
}
@@ -1622,10 +712,37 @@ void WebPagesManager::on_get_web_page_by_url(const string &url, WebPageId web_pa
cached_web_page_id = web_page_id;
}
-void WebPagesManager::wait_for_pending_web_page(DialogId dialog_id, MessageId message_id, WebPageId web_page_id) {
- LOG(INFO) << "Waiting for " << web_page_id << " needed in " << message_id << " in " << dialog_id;
- pending_web_pages_[web_page_id].emplace(dialog_id, message_id);
- pending_web_pages_timeout_.add_timeout_in(web_page_id.get(), 1.0);
+void WebPagesManager::register_web_page(WebPageId web_page_id, FullMessageId full_message_id, const char *source) {
+ if (!web_page_id.is_valid()) {
+ return;
+ }
+
+ LOG(INFO) << "Register " << web_page_id << " from " << full_message_id << " from " << source;
+ bool is_inserted = web_page_messages_[web_page_id].insert(full_message_id).second;
+ LOG_CHECK(is_inserted) << source << " " << web_page_id << " " << full_message_id;
+
+ if (!td_->auth_manager_->is_bot() && !have_web_page_force(web_page_id)) {
+ LOG(INFO) << "Waiting for " << web_page_id << " needed in " << full_message_id;
+ pending_web_pages_timeout_.add_timeout_in(web_page_id.get(), 1.0);
+ }
+}
+
+void WebPagesManager::unregister_web_page(WebPageId web_page_id, FullMessageId full_message_id, const char *source) {
+ if (!web_page_id.is_valid()) {
+ return;
+ }
+
+ LOG(INFO) << "Unregister " << web_page_id << " from " << full_message_id << " from " << source;
+ auto &message_ids = web_page_messages_[web_page_id];
+ auto is_deleted = message_ids.erase(full_message_id) > 0;
+ LOG_CHECK(is_deleted) << source << " " << web_page_id << " " << full_message_id;
+
+ if (message_ids.empty()) {
+ web_page_messages_.erase(web_page_id);
+ if (pending_get_web_pages_.count(web_page_id) == 0) {
+ pending_web_pages_timeout_.cancel_timeout(web_page_id.get());
+ }
+ }
}
void WebPagesManager::on_get_web_page_preview_success(int64 request_id, const string &url,
@@ -1652,7 +769,6 @@ void WebPagesManager::on_get_web_page_preview_success(int64 request_id, const st
if (web_page_id.is_valid() && !have_web_page(web_page_id)) {
pending_get_web_pages_[web_page_id].emplace(request_id,
std::make_pair(url, std::move(promise))); // TODO MultiPromise ?
- pending_web_pages_timeout_.add_timeout_in(web_page_id.get(), 1.0);
return;
}
@@ -1663,8 +779,8 @@ void WebPagesManager::on_get_web_page_preview_success(int64 request_id, const st
Promise<Unit> &&promise) {
CHECK(web_page_id == WebPageId() || have_web_page(web_page_id));
- CHECK(got_web_page_previews_.find(request_id) == got_web_page_previews_.end());
- got_web_page_previews_[request_id] = web_page_id;
+ bool is_inserted = got_web_page_previews_.emplace(request_id, web_page_id).second;
+ CHECK(is_inserted);
if (web_page_id.is_valid() && !url.empty()) {
on_get_web_page_by_url(url, web_page_id, true);
@@ -1681,31 +797,20 @@ void WebPagesManager::on_get_web_page_preview_fail(int64 request_id, const strin
}
int64 WebPagesManager::get_web_page_preview(td_api::object_ptr<td_api::formattedText> &&text, Promise<Unit> &&promise) {
- if (text == nullptr) {
- promise.set_value(Unit());
- return 0;
- }
-
- auto r_entities = get_message_entities(td_->contacts_manager_.get(), text->entities_);
- if (r_entities.is_error()) {
- promise.set_error(r_entities.move_as_error());
+ auto r_formatted_text = get_formatted_text(td_, DialogId(), std::move(text), false, true, true, true);
+ if (r_formatted_text.is_error()) {
+ promise.set_error(r_formatted_text.move_as_error());
return 0;
}
- auto entities = r_entities.move_as_ok();
+ auto formatted_text = r_formatted_text.move_as_ok();
- auto result = fix_formatted_text(text->text_, entities, true, false, true, false);
- if (text->text_.empty()) {
- promise.set_value(Unit());
- return 0;
- }
-
- auto url = get_first_url(text->text_, entities);
+ auto url = get_first_url(formatted_text);
if (url.empty()) {
promise.set_value(Unit());
return 0;
}
- LOG(INFO) << "Trying to get web page preview for message \"" << text->text_ << '"';
+ LOG(INFO) << "Trying to get web page preview for message \"" << formatted_text.text << '"';
int64 request_id = get_web_page_preview_request_id_++;
auto web_page_id = get_web_page_by_url(url);
@@ -1714,8 +819,10 @@ int64 WebPagesManager::get_web_page_preview(td_api::object_ptr<td_api::formatted
promise.set_value(Unit());
} else {
td_->create_handler<GetWebPagePreviewQuery>(std::move(promise))
- ->send(text->text_, get_input_message_entities(td_->contacts_manager_.get(), entities), request_id,
- std::move(url));
+ ->send(
+ formatted_text.text,
+ get_input_message_entities(td_->contacts_manager_.get(), formatted_text.entities, "get_web_page_preview"),
+ request_id, std::move(url));
}
return request_id;
}
@@ -1732,44 +839,59 @@ tl_object_ptr<td_api::webPage> WebPagesManager::get_web_page_preview_result(int6
return get_web_page_object(web_page_id);
}
-WebPageId WebPagesManager::get_web_page_instant_view(const string &url, bool force_full, Promise<Unit> &&promise) {
+void WebPagesManager::get_web_page_instant_view(const string &url, bool force_full, Promise<WebPageId> &&promise) {
LOG(INFO) << "Trying to get web page instant view for the url \"" << url << '"';
+ if (url.empty()) {
+ return promise.set_value(WebPageId());
+ }
auto it = url_to_web_page_id_.find(url);
if (it != url_to_web_page_id_.end()) {
- return get_web_page_instant_view(it->second, force_full, std::move(promise));
+ if (it->second == WebPageId()) {
+ // ignore negative caching
+ return reload_web_page_by_url(url, std::move(promise));
+ }
+ return get_web_page_instant_view_impl(it->second, force_full, std::move(promise));
}
- load_web_page_by_url(url, std::move(promise));
- return WebPageId();
+ auto new_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), force_full, promise = std::move(promise)](Result<WebPageId> r_web_page_id) mutable {
+ if (r_web_page_id.is_error()) {
+ promise.set_error(r_web_page_id.move_as_error());
+ } else {
+ send_closure(actor_id, &WebPagesManager::get_web_page_instant_view_impl, r_web_page_id.ok(), force_full,
+ std::move(promise));
+ }
+ });
+ load_web_page_by_url(url, std::move(new_promise));
}
-WebPageId WebPagesManager::get_web_page_instant_view(WebPageId web_page_id, bool force_full, Promise<Unit> &&promise) {
+void WebPagesManager::get_web_page_instant_view_impl(WebPageId web_page_id, bool force_full,
+ Promise<WebPageId> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
LOG(INFO) << "Trying to get web page instant view for " << web_page_id;
- auto web_page_instant_view = get_web_page_instant_view(web_page_id);
+ const WebPageInstantView *web_page_instant_view = get_web_page_instant_view(web_page_id);
if (web_page_instant_view == nullptr) {
- promise.set_value(Unit());
- return WebPageId();
+ return promise.set_value(WebPageId());
}
if (!web_page_instant_view->is_loaded || (force_full && !web_page_instant_view->is_full)) {
- load_web_page_instant_view(web_page_id, force_full, std::move(promise));
- return WebPageId();
+ return load_web_page_instant_view(web_page_id, force_full, std::move(promise));
}
if (force_full) {
reload_web_page_instant_view(web_page_id);
}
- promise.set_value(Unit());
- return web_page_id;
+ promise.set_value(std::move(web_page_id));
}
string WebPagesManager::get_web_page_instant_view_database_key(WebPageId web_page_id) {
- return "wpiv" + to_string(web_page_id.get());
+ return PSTRING() << "wpiv" << web_page_id.get();
}
-void WebPagesManager::load_web_page_instant_view(WebPageId web_page_id, bool force_full, Promise<Unit> &&promise) {
+void WebPagesManager::load_web_page_instant_view(WebPageId web_page_id, bool force_full, Promise<WebPageId> &&promise) {
auto &load_web_page_instant_view_queries = load_web_page_instant_view_queries_[web_page_id];
auto previous_queries =
load_web_page_instant_view_queries.partial.size() + load_web_page_instant_view_queries.full.size();
@@ -1786,9 +908,10 @@ void WebPagesManager::load_web_page_instant_view(WebPageId web_page_id, bool for
if (G()->parameters().use_message_db && !web_page_instant_view->was_loaded_from_database) {
LOG(INFO) << "Trying to load " << web_page_id << " instant view from database";
G()->td_db()->get_sqlite_pmc()->get(
- get_web_page_instant_view_database_key(web_page_id), PromiseCreator::lambda([web_page_id](string value) {
- send_closure(G()->web_pages_manager(), &WebPagesManager::on_load_web_page_instant_view_from_database,
- web_page_id, std::move(value));
+ get_web_page_instant_view_database_key(web_page_id),
+ PromiseCreator::lambda([actor_id = actor_id(this), web_page_id](string value) {
+ send_closure(actor_id, &WebPagesManager::on_load_web_page_instant_view_from_database, web_page_id,
+ std::move(value));
}));
} else {
reload_web_page_instant_view(web_page_id);
@@ -1798,114 +921,130 @@ void WebPagesManager::load_web_page_instant_view(WebPageId web_page_id, bool for
void WebPagesManager::reload_web_page_instant_view(WebPageId web_page_id) {
LOG(INFO) << "Reload " << web_page_id << " instant view";
- auto web_page = get_web_page(web_page_id);
+ const WebPage *web_page = get_web_page(web_page_id);
CHECK(web_page != nullptr && !web_page->instant_view.is_empty);
- auto promise = PromiseCreator::lambda([web_page_id](Result<> result) {
- send_closure(G()->web_pages_manager(), &WebPagesManager::update_web_page_instant_view_load_requests, web_page_id,
- true, std::move(result));
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), web_page_id](Result<WebPageId> result) {
+ send_closure(actor_id, &WebPagesManager::update_web_page_instant_view_load_requests, web_page_id, true,
+ std::move(result));
});
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
td_->create_handler<GetWebPageQuery>(std::move(promise))
- ->send(web_page->url, web_page->instant_view.is_full ? web_page->instant_view.hash : 0);
+ ->send(web_page_id, web_page->url, web_page->instant_view.is_full ? web_page->instant_view.hash : 0);
}
void WebPagesManager::on_load_web_page_instant_view_from_database(WebPageId web_page_id, string value) {
+ if (G()->close_flag()) {
+ return;
+ }
CHECK(G()->parameters().use_message_db);
LOG(INFO) << "Successfully loaded " << web_page_id << " instant view of size " << value.size() << " from database";
// G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto());
- // return;
+ // value.clear();
- auto web_page_instant_view = get_web_page_instant_view(web_page_id);
- if (web_page_instant_view == nullptr) {
+ WebPage *web_page = web_pages_.get_pointer(web_page_id);
+ if (web_page == nullptr || web_page->instant_view.is_empty) {
// possible if web page loses preview/instant view
LOG(WARNING) << "There is no instant view in " << web_page_id;
if (!value.empty()) {
G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto());
}
- update_web_page_instant_view_load_requests(web_page_id, true, Unit());
+ update_web_page_instant_view_load_requests(web_page_id, true, web_page_id);
return;
}
- if (web_page_instant_view->was_loaded_from_database) {
+ auto &web_page_instant_view = web_page->instant_view;
+ if (web_page_instant_view.was_loaded_from_database) {
return;
}
WebPageInstantView result;
if (!value.empty()) {
- if (log_event_parse(result, value).is_error()) {
+ auto status = log_event_parse(result, value);
+ if (status.is_error()) {
result = WebPageInstantView();
- LOG(INFO) << "Erase instant view in " << web_page_id << " from database";
+ LOG(ERROR) << "Erase instant view in " << web_page_id << " from database because of " << status.message();
G()->td_db()->get_sqlite_pmc()->erase(get_web_page_instant_view_database_key(web_page_id), Auto());
}
}
result.was_loaded_from_database = true;
- update_web_page_instant_view(web_page_id, *web_page_instant_view, std::move(result));
+ auto old_file_ids = get_web_page_file_ids(web_page);
+
+ update_web_page_instant_view(web_page_id, web_page_instant_view, std::move(result));
+
+ auto new_file_ids = get_web_page_file_ids(web_page);
+ if (old_file_ids != new_file_ids) {
+ td_->file_manager_->change_files_source(get_web_page_file_source_id(web_page), old_file_ids, new_file_ids);
+ }
- update_web_page_instant_view_load_requests(web_page_id, false, Unit());
+ update_web_page_instant_view_load_requests(web_page_id, false, web_page_id);
}
void WebPagesManager::update_web_page_instant_view_load_requests(WebPageId web_page_id, bool force_update,
- Result<> result) {
- // TODO [Error : 0 : Lost promise] on closing
+ Result<WebPageId> r_web_page_id) {
+ if (G()->close_flag()) {
+ r_web_page_id = Global::request_aborted_error();
+ }
LOG(INFO) << "Update load requests for " << web_page_id;
auto it = load_web_page_instant_view_queries_.find(web_page_id);
if (it == load_web_page_instant_view_queries_.end()) {
return;
}
- vector<Promise<Unit>> promises[2];
+ vector<Promise<WebPageId>> promises[2];
promises[0] = std::move(it->second.partial);
promises[1] = std::move(it->second.full);
reset_to_empty(it->second.partial);
reset_to_empty(it->second.full);
load_web_page_instant_view_queries_.erase(it);
- if (result.is_error()) {
- LOG(INFO) << "Receive error " << result.error() << " for load " << web_page_id;
- append(promises[0], std::move(promises[1]));
- for (auto &promise : promises[0]) {
- promise.set_error(result.error().clone());
- }
+ if (r_web_page_id.is_error()) {
+ LOG(INFO) << "Receive error " << r_web_page_id.error() << " for load " << web_page_id;
+ combine(promises[0], std::move(promises[1]));
+ fail_promises(promises[0], r_web_page_id.move_as_error());
return;
}
- LOG(INFO) << "Successfully loaded web page " << web_page_id;
- auto web_page_instant_view = get_web_page_instant_view(web_page_id);
+ auto new_web_page_id = r_web_page_id.move_as_ok();
+ LOG(INFO) << "Successfully loaded web page " << web_page_id << " as " << new_web_page_id;
+ const WebPageInstantView *web_page_instant_view = get_web_page_instant_view(new_web_page_id);
if (web_page_instant_view == nullptr) {
- append(promises[0], std::move(promises[1]));
+ combine(promises[0], std::move(promises[1]));
for (auto &promise : promises[0]) {
- promise.set_value(Unit());
+ promise.set_value(WebPageId());
}
return;
}
+ CHECK(new_web_page_id.is_valid());
if (web_page_instant_view->is_loaded) {
if (web_page_instant_view->is_full) {
- append(promises[0], std::move(promises[1]));
- promises[1].clear();
+ combine(promises[0], std::move(promises[1]));
}
for (auto &promise : promises[0]) {
- promise.set_value(Unit());
+ promise.set_value(WebPageId(new_web_page_id));
}
- promises[0].clear();
+ reset_to_empty(promises[0]);
}
if (!promises[0].empty() || !promises[1].empty()) {
if (force_update) {
// protection from cycles
- LOG(ERROR) << "Expected to receive " << web_page_id << " from the server, but didn't receive it";
- append(promises[0], std::move(promises[1]));
+ LOG(ERROR) << "Expected to receive " << web_page_id << '/' << new_web_page_id
+ << " from the server, but didn't receive it";
+ combine(promises[0], std::move(promises[1]));
for (auto &promise : promises[0]) {
- promise.set_value(Unit());
+ promise.set_value(WebPageId());
}
return;
}
- auto &load_queries = load_web_page_instant_view_queries_[web_page_id];
+ auto &load_queries = load_web_page_instant_view_queries_[new_web_page_id];
auto old_size = load_queries.partial.size() + load_queries.full.size();
- append(load_queries.partial, std::move(promises[0]));
- append(load_queries.full, std::move(promises[1]));
+ combine(load_queries.partial, std::move(promises[0]));
+ combine(load_queries.full, std::move(promises[1]));
if (old_size == 0) {
- reload_web_page_instant_view(web_page_id);
+ reload_web_page_instant_view(new_web_page_id);
}
}
}
@@ -1915,55 +1054,59 @@ WebPageId WebPagesManager::get_web_page_by_url(const string &url) const {
return WebPageId();
}
- LOG(INFO) << "Get web page id for the url \"" << url << '"';
-
auto it = url_to_web_page_id_.find(url);
if (it != url_to_web_page_id_.end()) {
+ LOG(INFO) << "Return " << it->second << " for the url \"" << url << '"';
return it->second;
}
+ LOG(INFO) << "Can't find web page identifier for the url \"" << url << '"';
return WebPageId();
}
-WebPageId WebPagesManager::get_web_page_by_url(const string &url, Promise<Unit> &&promise) {
- LOG(INFO) << "Trying to get web page id for the url \"" << url << '"';
+void WebPagesManager::get_web_page_by_url(const string &url, Promise<WebPageId> &&promise) {
+ LOG(INFO) << "Trying to get web page identifier for the url \"" << url << '"';
+ if (url.empty()) {
+ return promise.set_value(WebPageId());
+ }
auto it = url_to_web_page_id_.find(url);
if (it != url_to_web_page_id_.end()) {
- promise.set_value(Unit());
- return it->second;
+ return promise.set_value(WebPageId(it->second));
}
load_web_page_by_url(url, std::move(promise));
- return WebPageId();
}
-void WebPagesManager::load_web_page_by_url(const string &url, Promise<Unit> &&promise) {
+void WebPagesManager::load_web_page_by_url(string url, Promise<WebPageId> &&promise) {
+ if (url.empty()) {
+ return promise.set_value(WebPageId());
+ }
if (!G()->parameters().use_message_db) {
- reload_web_page_by_url(url, std::move(promise));
- return;
+ return reload_web_page_by_url(url, std::move(promise));
}
LOG(INFO) << "Load \"" << url << '"';
- G()->td_db()->get_sqlite_pmc()->get(get_web_page_url_database_key(url),
- PromiseCreator::lambda([url, promise = std::move(promise)](string value) mutable {
- send_closure(G()->web_pages_manager(),
- &WebPagesManager::on_load_web_page_id_by_url_from_database, url,
- value, std::move(promise));
+ auto key = get_web_page_url_database_key(url);
+ G()->td_db()->get_sqlite_pmc()->get(key, PromiseCreator::lambda([actor_id = actor_id(this), url = std::move(url),
+ promise = std::move(promise)](string value) mutable {
+ send_closure(actor_id,
+ &WebPagesManager::on_load_web_page_id_by_url_from_database,
+ std::move(url), std::move(value), std::move(promise));
}));
}
-void WebPagesManager::on_load_web_page_id_by_url_from_database(const string &url, string value,
- Promise<Unit> &&promise) {
+void WebPagesManager::on_load_web_page_id_by_url_from_database(string url, string value, Promise<WebPageId> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
LOG(INFO) << "Successfully loaded url \"" << url << "\" of size " << value.size() << " from database";
// G()->td_db()->get_sqlite_pmc()->erase(get_web_page_url_database_key(web_page_id), Auto());
- // return;
+ // value.clear();
auto it = url_to_web_page_id_.find(url);
if (it != url_to_web_page_id_.end()) {
// URL web page has already been loaded
- promise.set_value(Unit());
- return;
+ return promise.set_value(WebPageId(it->second));
}
if (!value.empty()) {
auto web_page_id = WebPageId(to_integer<int64>(value));
@@ -1971,16 +1114,16 @@ void WebPagesManager::on_load_web_page_id_by_url_from_database(const string &url
if (have_web_page(web_page_id)) {
// URL web page has already been loaded
on_get_web_page_by_url(url, web_page_id, true);
- promise.set_value(Unit());
+ promise.set_value(WebPageId(web_page_id));
return;
}
- load_web_page_from_database(
- web_page_id,
- PromiseCreator::lambda([web_page_id, url, promise = std::move(promise)](Result<> result) mutable {
- send_closure(G()->web_pages_manager(), &WebPagesManager::on_load_web_page_by_url_from_database, web_page_id,
- url, std::move(promise), std::move(result));
- }));
+ load_web_page_from_database(web_page_id,
+ PromiseCreator::lambda([actor_id = actor_id(this), web_page_id, url = std::move(url),
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ send_closure(actor_id, &WebPagesManager::on_load_web_page_by_url_from_database,
+ web_page_id, std::move(url), std::move(promise), std::move(result));
+ }));
return;
} else {
LOG(ERROR) << "Receive invalid " << web_page_id;
@@ -1990,30 +1133,28 @@ void WebPagesManager::on_load_web_page_id_by_url_from_database(const string &url
reload_web_page_by_url(url, std::move(promise));
}
-void WebPagesManager::on_load_web_page_by_url_from_database(WebPageId web_page_id, const string &url,
- Promise<Unit> &&promise, Result<> result) {
+void WebPagesManager::on_load_web_page_by_url_from_database(WebPageId web_page_id, string url,
+ Promise<WebPageId> &&promise, Result<Unit> &&result) {
if (result.is_error()) {
CHECK(G()->close_flag());
- promise.set_error(Status::Error(500, "Request aborted"));
- return;
+ return promise.set_error(Global::request_aborted_error());
}
- auto web_page = get_web_page(web_page_id);
+ const WebPage *web_page = get_web_page(web_page_id);
if (web_page == nullptr) {
- reload_web_page_by_url(url, std::move(promise));
- return;
+ return reload_web_page_by_url(url, std::move(promise));
}
if (web_page->url != url) {
on_get_web_page_by_url(url, web_page_id, true);
}
- promise.set_value(Unit());
+ promise.set_value(WebPageId(web_page_id));
}
-void WebPagesManager::reload_web_page_by_url(const string &url, Promise<Unit> &&promise) {
- LOG(INFO) << "Reload url \"" << url << '"';
- td_->create_handler<GetWebPageQuery>(std::move(promise))->send(url, 0);
+void WebPagesManager::reload_web_page_by_url(const string &url, Promise<WebPageId> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+ td_->create_handler<GetWebPageQuery>(std::move(promise))->send(WebPageId(), url, 0);
}
SecretInputMedia WebPagesManager::get_secret_input_media(WebPageId web_page_id) const {
@@ -2021,7 +1162,7 @@ SecretInputMedia WebPagesManager::get_secret_input_media(WebPageId web_page_id)
return SecretInputMedia{};
}
- auto web_page = get_web_page(web_page_id);
+ const WebPage *web_page = get_web_page(web_page_id);
if (web_page == nullptr) {
return SecretInputMedia{};
}
@@ -2039,45 +1180,135 @@ tl_object_ptr<td_api::webPage> WebPagesManager::get_web_page_object(WebPageId we
if (!web_page_id.is_valid()) {
return nullptr;
}
- auto web_page = get_web_page(web_page_id);
+ const WebPage *web_page = get_web_page(web_page_id);
if (web_page == nullptr) {
return nullptr;
}
+ int32 instant_view_version = [web_page] {
+ if (web_page->instant_view.is_empty) {
+ return 0;
+ }
+ if (web_page->instant_view.is_v2) {
+ return 2;
+ }
+ return 1;
+ }();
+
+ FormattedText description;
+ description.text = web_page->description;
+ description.entities = find_entities(web_page->description, true, false);
+
+ auto r_url = parse_url(web_page->display_url);
+ if (r_url.is_ok()) {
+ Slice host = r_url.ok().host_;
+ if (!host.empty() && host.back() == '.') {
+ host.truncate(host.size() - 1);
+ }
+
+ auto replace_entities = [](Slice text, vector<MessageEntity> &entities, auto replace_url) {
+ int32 current_offset = 0;
+ for (auto &entity : entities) {
+ CHECK(entity.offset >= current_offset);
+ text = utf8_utf16_substr(text, static_cast<size_t>(entity.offset - current_offset));
+ auto entity_text = utf8_utf16_substr(text, 0, static_cast<size_t>(entity.length));
+ text = text.substr(entity_text.size());
+ current_offset = entity.offset + entity.length;
+
+ auto replaced_url = replace_url(entity, entity_text);
+ if (!replaced_url.empty()) {
+ entity = MessageEntity(MessageEntity::Type::TextUrl, entity.offset, entity.length, std::move(replaced_url));
+ }
+ }
+ };
+
+ if (host == "instagram.com" || ends_with(host, ".instagram.com")) {
+ replace_entities(description.text, description.entities, [](const MessageEntity &entity, Slice text) {
+ if (entity.type == MessageEntity::Type::Mention) {
+ return PSTRING() << "https://www.instagram.com/" << text.substr(1) << '/';
+ }
+ if (entity.type == MessageEntity::Type::Hashtag) {
+ return PSTRING() << "https://www.instagram.com/explore/tags/" << url_encode(text.substr(1)) << '/';
+ }
+ return string();
+ });
+ } else if (host == "twitter.com" || ends_with(host, ".twitter.com")) {
+ replace_entities(description.text, description.entities, [](const MessageEntity &entity, Slice text) {
+ if (entity.type == MessageEntity::Type::Mention) {
+ return PSTRING() << "https://twitter.com/" << text.substr(1);
+ }
+ if (entity.type == MessageEntity::Type::Hashtag) {
+ return PSTRING() << "https://twitter.com/hashtag/" << url_encode(text.substr(1));
+ }
+ if (entity.type == MessageEntity::Type::Cashtag) {
+ return PSTRING() << "https://twitter.com/search?q=" << url_encode(text) << "&src=cashtag_click";
+ }
+ return string();
+ });
+ } else if (host == "t.me" || host == "telegram.me" || host == "telegram.dog" || host == "telesco.pe") {
+ // leave everything as is
+ } else {
+ td::remove_if(description.entities,
+ [](const MessageEntity &entity) { return entity.type == MessageEntity::Type::Mention; });
+
+ if (host == "youtube.com" || host == "www.youtube.com") {
+ replace_entities(description.text, description.entities, [](const MessageEntity &entity, Slice text) {
+ if (entity.type == MessageEntity::Type::Hashtag) {
+ return PSTRING() << "https://www.youtube.com/results?search_query=" << url_encode(text);
+ }
+ return string();
+ });
+ } else if (host == "music.youtube.com") {
+ replace_entities(description.text, description.entities, [](const MessageEntity &entity, Slice text) {
+ if (entity.type == MessageEntity::Type::Hashtag) {
+ return PSTRING() << "https://music.youtube.com/search?q=" << url_encode(text);
+ }
+ return string();
+ });
+ }
+ }
+ }
+
+ auto duration = get_web_page_media_duration(web_page);
return make_tl_object<td_api::webPage>(
- web_page->url, web_page->display_url, web_page->type, web_page->site_name, web_page->title, web_page->description,
- get_photo_object(td_->file_manager_.get(), &web_page->photo), web_page->embed_url, web_page->embed_type,
+ web_page->url, web_page->display_url, web_page->type, web_page->site_name, web_page->title,
+ get_formatted_text_object(description, true, duration == 0 ? std::numeric_limits<int32>::max() : duration),
+ get_photo_object(td_->file_manager_.get(), web_page->photo), web_page->embed_url, web_page->embed_type,
web_page->embed_dimensions.width, web_page->embed_dimensions.height, web_page->duration, web_page->author,
- web_page->document_type == DocumentsManager::DocumentType::Animation
- ? td_->animations_manager_->get_animation_object(web_page->document_file_id, "get_web_page_object")
+ web_page->document.type == Document::Type::Animation
+ ? td_->animations_manager_->get_animation_object(web_page->document.file_id)
: nullptr,
- web_page->document_type == DocumentsManager::DocumentType::Audio
- ? td_->audios_manager_->get_audio_object(web_page->document_file_id)
+ web_page->document.type == Document::Type::Audio
+ ? td_->audios_manager_->get_audio_object(web_page->document.file_id)
: nullptr,
- web_page->document_type == DocumentsManager::DocumentType::General
- ? td_->documents_manager_->get_document_object(web_page->document_file_id)
+ web_page->document.type == Document::Type::General
+ ? td_->documents_manager_->get_document_object(web_page->document.file_id, PhotoFormat::Jpeg)
: nullptr,
- web_page->document_type == DocumentsManager::DocumentType::Sticker
- ? td_->stickers_manager_->get_sticker_object(web_page->document_file_id)
+ web_page->document.type == Document::Type::Sticker
+ ? td_->stickers_manager_->get_sticker_object(web_page->document.file_id)
: nullptr,
- web_page->document_type == DocumentsManager::DocumentType::Video
- ? td_->videos_manager_->get_video_object(web_page->document_file_id)
+ web_page->document.type == Document::Type::Video
+ ? td_->videos_manager_->get_video_object(web_page->document.file_id)
: nullptr,
- web_page->document_type == DocumentsManager::DocumentType::VideoNote
- ? td_->video_notes_manager_->get_video_note_object(web_page->document_file_id)
+ web_page->document.type == Document::Type::VideoNote
+ ? td_->video_notes_manager_->get_video_note_object(web_page->document.file_id)
: nullptr,
- web_page->document_type == DocumentsManager::DocumentType::VoiceNote
- ? td_->voice_notes_manager_->get_voice_note_object(web_page->document_file_id)
+ web_page->document.type == Document::Type::VoiceNote
+ ? td_->voice_notes_manager_->get_voice_note_object(web_page->document.file_id)
: nullptr,
- !web_page->instant_view.is_empty);
+ instant_view_version);
}
tl_object_ptr<td_api::webPageInstantView> WebPagesManager::get_web_page_instant_view_object(
WebPageId web_page_id) const {
- return get_web_page_instant_view_object(get_web_page_instant_view(web_page_id));
+ const WebPage *web_page = get_web_page(web_page_id);
+ if (web_page == nullptr || web_page->instant_view.is_empty) {
+ return nullptr;
+ }
+ return get_web_page_instant_view_object(web_page_id, &web_page->instant_view, web_page->url);
}
tl_object_ptr<td_api::webPageInstantView> WebPagesManager::get_web_page_instant_view_object(
- const WebPageInstantView *web_page_instant_view) const {
+ WebPageId web_page_id, const WebPageInstantView *web_page_instant_view, Slice web_page_url) const {
if (web_page_instant_view == nullptr) {
return nullptr;
}
@@ -2085,21 +1316,39 @@ tl_object_ptr<td_api::webPageInstantView> WebPagesManager::get_web_page_instant_
LOG(ERROR) << "Trying to get not loaded web page instant view";
return nullptr;
}
- return make_tl_object<td_api::webPageInstantView>(
- transform(web_page_instant_view->page_blocks,
- [](const auto &page_block) { return page_block->get_page_block_object(); }),
- web_page_instant_view->is_full);
+ auto feedback_link = td_api::make_object<td_api::internalLinkTypeBotStart>(
+ "previews", PSTRING() << "webpage" << web_page_id.get(), true);
+ return td_api::make_object<td_api::webPageInstantView>(
+ get_page_blocks_object(web_page_instant_view->page_blocks, td_, web_page_instant_view->url, web_page_url),
+ web_page_instant_view->view_count, web_page_instant_view->is_v2 ? 2 : 1, web_page_instant_view->is_rtl,
+ web_page_instant_view->is_full, std::move(feedback_link));
}
-void WebPagesManager::update_messages_content(WebPageId web_page_id, bool have_web_page) {
- LOG(INFO) << "Update messages awaiting " << web_page_id;
- auto it = pending_web_pages_.find(web_page_id);
- if (it != pending_web_pages_.end()) {
- auto full_message_ids = std::move(it->second);
- pending_web_pages_.erase(it);
- for (auto full_message_id : full_message_ids) {
- send_closure_later(G()->messages_manager(), &MessagesManager::on_update_message_web_page, full_message_id,
- have_web_page);
+void WebPagesManager::on_web_page_changed(WebPageId web_page_id, bool have_web_page) {
+ LOG(INFO) << "Updated " << web_page_id;
+ auto it = web_page_messages_.find(web_page_id);
+ if (it != web_page_messages_.end()) {
+ vector<FullMessageId> full_message_ids;
+ for (const auto &full_message_id : it->second) {
+ full_message_ids.push_back(full_message_id);
+ }
+ CHECK(!full_message_ids.empty());
+ for (const auto &full_message_id : full_message_ids) {
+ if (!have_web_page) {
+ td_->messages_manager_->delete_pending_message_web_page(full_message_id);
+ } else {
+ td_->messages_manager_->on_external_update_message_content(full_message_id);
+ }
+ }
+
+ bool is_ok = (have_web_page ? web_page_messages_[web_page_id].size() == full_message_ids.size()
+ : web_page_messages_.count(web_page_id) == 0);
+ if (!is_ok) {
+ vector<FullMessageId> new_full_message_ids;
+ for (const auto &full_message_id : web_page_messages_[web_page_id]) {
+ new_full_message_ids.push_back(full_message_id);
+ }
+ LOG_CHECK(is_ok) << have_web_page << ' ' << full_message_ids << ' ' << new_full_message_ids;
}
}
auto get_it = pending_get_web_pages_.find(web_page_id);
@@ -2114,55 +1363,47 @@ void WebPagesManager::update_messages_content(WebPageId web_page_id, bool have_w
pending_web_pages_timeout_.cancel_timeout(web_page_id.get());
}
-WebPagesManager::WebPage *WebPagesManager::get_web_page(WebPageId web_page_id) {
- auto p = web_pages_.find(web_page_id);
- if (p == web_pages_.end()) {
- return nullptr;
- } else {
- return p->second.get();
- }
-}
-
const WebPagesManager::WebPage *WebPagesManager::get_web_page(WebPageId web_page_id) const {
- auto p = web_pages_.find(web_page_id);
- if (p == web_pages_.end()) {
- return nullptr;
- } else {
- return p->second.get();
- }
+ return web_pages_.get_pointer(web_page_id);
}
-WebPagesManager::WebPageInstantView *WebPagesManager::get_web_page_instant_view(WebPageId web_page_id) {
- auto web_page = get_web_page(web_page_id);
+const WebPagesManager::WebPageInstantView *WebPagesManager::get_web_page_instant_view(WebPageId web_page_id) const {
+ const WebPage *web_page = get_web_page(web_page_id);
if (web_page == nullptr || web_page->instant_view.is_empty) {
return nullptr;
}
return &web_page->instant_view;
}
-const WebPagesManager::WebPageInstantView *WebPagesManager::get_web_page_instant_view(WebPageId web_page_id) const {
- auto web_page = get_web_page(web_page_id);
- if (web_page == nullptr || web_page->instant_view.is_empty) {
- return nullptr;
+void WebPagesManager::on_pending_web_page_timeout_callback(void *web_pages_manager_ptr, int64 web_page_id_int) {
+ if (G()->close_flag()) {
+ return;
}
- return &web_page->instant_view;
-}
-void WebPagesManager::on_pending_web_page_timeout_callback(void *web_pages_manager_ptr, int64 web_page_id) {
- static_cast<WebPagesManager *>(web_pages_manager_ptr)->on_pending_web_page_timeout(WebPageId(web_page_id));
+ auto web_pages_manager = static_cast<WebPagesManager *>(web_pages_manager_ptr);
+ send_closure_later(web_pages_manager->actor_id(web_pages_manager), &WebPagesManager::on_pending_web_page_timeout,
+ WebPageId(web_page_id_int));
}
void WebPagesManager::on_pending_web_page_timeout(WebPageId web_page_id) {
+ if (G()->close_flag() || have_web_page(web_page_id)) {
+ return;
+ }
+
int32 count = 0;
- auto it = pending_web_pages_.find(web_page_id);
- if (it != pending_web_pages_.end()) {
+ auto it = web_page_messages_.find(web_page_id);
+ if (it != web_page_messages_.end()) {
vector<FullMessageId> full_message_ids;
- for (auto full_message_id : it->second) {
- full_message_ids.push_back(full_message_id);
+ for (const auto &full_message_id : it->second) {
+ if (full_message_id.get_dialog_id().get_type() != DialogType::SecretChat) {
+ full_message_ids.push_back(full_message_id);
+ }
count++;
}
- send_closure_later(G()->messages_manager(), &MessagesManager::get_messages_from_server, std::move(full_message_ids),
- Promise<Unit>(), nullptr);
+ if (!full_message_ids.empty()) {
+ send_closure_later(G()->messages_manager(), &MessagesManager::get_messages_from_server,
+ std::move(full_message_ids), Promise<Unit>(), "on_pending_web_page_timeout", nullptr);
+ }
}
auto get_it = pending_get_web_pages_.find(web_page_id);
if (get_it != pending_get_web_pages_.end()) {
@@ -2175,422 +1416,101 @@ void WebPagesManager::on_pending_web_page_timeout(WebPageId web_page_id) {
}
}
if (count == 0) {
- LOG(WARNING) << "Have no messages waiting for " << web_page_id;
- }
-}
-
-WebPagesManager::RichText WebPagesManager::get_rich_text(tl_object_ptr<telegram_api::RichText> &&rich_text_ptr) {
- CHECK(rich_text_ptr != nullptr);
-
- RichText result;
- switch (rich_text_ptr->get_id()) {
- case telegram_api::textEmpty::ID:
- break;
- case telegram_api::textPlain::ID: {
- auto rich_text = move_tl_object_as<telegram_api::textPlain>(rich_text_ptr);
- result.content = std::move(rich_text->text_);
- break;
- }
- case telegram_api::textBold::ID: {
- auto rich_text = move_tl_object_as<telegram_api::textBold>(rich_text_ptr);
- result.type = RichText::Type::Bold;
- result.texts.push_back(get_rich_text(std::move(rich_text->text_)));
- break;
- }
- case telegram_api::textItalic::ID: {
- auto rich_text = move_tl_object_as<telegram_api::textItalic>(rich_text_ptr);
- result.type = RichText::Type::Italic;
- result.texts.push_back(get_rich_text(std::move(rich_text->text_)));
- break;
- }
- case telegram_api::textUnderline::ID: {
- auto rich_text = move_tl_object_as<telegram_api::textUnderline>(rich_text_ptr);
- result.type = RichText::Type::Underline;
- result.texts.push_back(get_rich_text(std::move(rich_text->text_)));
- break;
- }
- case telegram_api::textStrike::ID: {
- auto rich_text = move_tl_object_as<telegram_api::textStrike>(rich_text_ptr);
- result.type = RichText::Type::Strikethrough;
- result.texts.push_back(get_rich_text(std::move(rich_text->text_)));
- break;
- }
- case telegram_api::textFixed::ID: {
- auto rich_text = move_tl_object_as<telegram_api::textFixed>(rich_text_ptr);
- result.type = RichText::Type::Fixed;
- result.texts.push_back(get_rich_text(std::move(rich_text->text_)));
- break;
- }
- case telegram_api::textUrl::ID: {
- auto rich_text = move_tl_object_as<telegram_api::textUrl>(rich_text_ptr);
- result.type = RichText::Type::Url;
- result.content = std::move(rich_text->url_);
- result.texts.push_back(get_rich_text(std::move(rich_text->text_)));
- break;
- }
- case telegram_api::textEmail::ID: {
- auto rich_text = move_tl_object_as<telegram_api::textEmail>(rich_text_ptr);
- result.type = RichText::Type::EmailAddress;
- result.content = std::move(rich_text->email_);
- result.texts.push_back(get_rich_text(std::move(rich_text->text_)));
- break;
- }
- case telegram_api::textConcat::ID: {
- auto rich_text = move_tl_object_as<telegram_api::textConcat>(rich_text_ptr);
- result.type = RichText::Type::Concatenation;
- result.texts.reserve(rich_text->texts_.size());
- for (auto &text : rich_text->texts_) {
- result.texts.push_back(get_rich_text(std::move(text)));
- }
- break;
- }
- default:
- UNREACHABLE();
- }
- return result;
-}
-
-vector<WebPagesManager::RichText> WebPagesManager::get_rich_texts(
- vector<tl_object_ptr<telegram_api::RichText>> &&rich_text_ptrs) {
- vector<RichText> result;
- result.reserve(rich_text_ptrs.size());
- for (auto &rich_text : rich_text_ptrs) {
- result.push_back(get_rich_text(std::move(rich_text)));
- }
- return result;
-}
-
-tl_object_ptr<td_api::RichText> WebPagesManager::get_rich_text_object(const RichText &rich_text) {
- switch (rich_text.type) {
- case RichText::Type::Plain:
- return make_tl_object<td_api::richTextPlain>(rich_text.content);
- case RichText::Type::Bold:
- return make_tl_object<td_api::richTextBold>(get_rich_text_object(rich_text.texts[0]));
- case RichText::Type::Italic:
- return make_tl_object<td_api::richTextItalic>(get_rich_text_object(rich_text.texts[0]));
- case RichText::Type::Underline:
- return make_tl_object<td_api::richTextUnderline>(get_rich_text_object(rich_text.texts[0]));
- case RichText::Type::Strikethrough:
- return make_tl_object<td_api::richTextStrikethrough>(get_rich_text_object(rich_text.texts[0]));
- case RichText::Type::Fixed:
- return make_tl_object<td_api::richTextFixed>(get_rich_text_object(rich_text.texts[0]));
- case RichText::Type::Url:
- return make_tl_object<td_api::richTextUrl>(get_rich_text_object(rich_text.texts[0]), rich_text.content);
- case RichText::Type::EmailAddress:
- return make_tl_object<td_api::richTextEmailAddress>(get_rich_text_object(rich_text.texts[0]), rich_text.content);
- case RichText::Type::Concatenation: {
- vector<tl_object_ptr<td_api::RichText>> texts;
- texts.reserve(rich_text.texts.size());
- for (auto &text : rich_text.texts) {
- texts.push_back(get_rich_text_object(text));
- }
- return make_tl_object<td_api::richTexts>(std::move(texts));
- }
- }
- UNREACHABLE();
- return nullptr;
-}
-
-vector<tl_object_ptr<td_api::RichText>> WebPagesManager::get_rich_text_objects(const vector<RichText> &rich_texts) {
- vector<tl_object_ptr<td_api::RichText>> result;
- result.reserve(rich_texts.size());
- for (auto &rich_text : rich_texts) {
- result.push_back(get_rich_text_object(rich_text));
- }
- return result;
-}
-
-vector<tl_object_ptr<td_api::PageBlock>> WebPagesManager::get_page_block_objects(
- const vector<unique_ptr<PageBlock>> &page_blocks) {
- vector<tl_object_ptr<td_api::PageBlock>> result;
- result.reserve(page_blocks.size());
- for (auto &page_block : page_blocks) {
- result.push_back(page_block->get_page_block_object());
- }
- return result;
-}
-
-unique_ptr<WebPagesManager::PageBlock> WebPagesManager::get_page_block(
- tl_object_ptr<telegram_api::PageBlock> page_block_ptr, const std::unordered_map<int64, FileId> &animations,
- const std::unordered_map<int64, FileId> &audios, const std::unordered_map<int64, Photo> &photos,
- const std::unordered_map<int64, FileId> &videos) const {
- CHECK(page_block_ptr != nullptr);
- switch (page_block_ptr->get_id()) {
- case telegram_api::pageBlockUnsupported::ID:
- return nullptr;
- case telegram_api::pageBlockTitle::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockTitle>(page_block_ptr);
- return make_unique<PageBlockTitle>(get_rich_text(std::move(page_block->text_)));
- }
- case telegram_api::pageBlockSubtitle::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockSubtitle>(page_block_ptr);
- return make_unique<PageBlockSubtitle>(get_rich_text(std::move(page_block->text_)));
- }
- case telegram_api::pageBlockAuthorDate::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockAuthorDate>(page_block_ptr);
- return make_unique<PageBlockAuthorDate>(get_rich_text(std::move(page_block->author_)),
- page_block->published_date_);
- }
- case telegram_api::pageBlockHeader::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockHeader>(page_block_ptr);
- return make_unique<PageBlockHeader>(get_rich_text(std::move(page_block->text_)));
- }
- case telegram_api::pageBlockSubheader::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockSubheader>(page_block_ptr);
- return make_unique<PageBlockSubheader>(get_rich_text(std::move(page_block->text_)));
- }
- case telegram_api::pageBlockParagraph::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockParagraph>(page_block_ptr);
- return make_unique<PageBlockParagraph>(get_rich_text(std::move(page_block->text_)));
- }
- case telegram_api::pageBlockPreformatted::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockPreformatted>(page_block_ptr);
- return make_unique<PageBlockPreformatted>(get_rich_text(std::move(page_block->text_)),
- std::move(page_block->language_));
- }
- case telegram_api::pageBlockFooter::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockFooter>(page_block_ptr);
- return make_unique<PageBlockFooter>(get_rich_text(std::move(page_block->text_)));
- }
- case telegram_api::pageBlockDivider::ID:
- return make_unique<PageBlockDivider>();
- case telegram_api::pageBlockAnchor::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockAnchor>(page_block_ptr);
- return make_unique<PageBlockAnchor>(std::move(page_block->name_));
- }
- case telegram_api::pageBlockList::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockList>(page_block_ptr);
- return make_unique<PageBlockList>(get_rich_texts(std::move(page_block->items_)), page_block->ordered_);
- }
- case telegram_api::pageBlockBlockquote::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockBlockquote>(page_block_ptr);
- return make_unique<PageBlockBlockQuote>(get_rich_text(std::move(page_block->text_)),
- get_rich_text(std::move(page_block->caption_)));
- }
- case telegram_api::pageBlockPullquote::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockPullquote>(page_block_ptr);
- return make_unique<PageBlockPullQuote>(get_rich_text(std::move(page_block->text_)),
- get_rich_text(std::move(page_block->caption_)));
- }
- case telegram_api::pageBlockPhoto::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockPhoto>(page_block_ptr);
- auto it = photos.find(page_block->photo_id_);
- Photo photo;
- if (it == photos.end()) {
- photo.id = -2;
- } else {
- photo = it->second;
- }
- return make_unique<PageBlockPhoto>(std::move(photo), get_rich_text(std::move(page_block->caption_)));
- }
- case telegram_api::pageBlockVideo::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockVideo>(page_block_ptr);
- bool need_autoplay = (page_block->flags_ & telegram_api::pageBlockVideo::AUTOPLAY_MASK) != 0;
- bool is_looped = (page_block->flags_ & telegram_api::pageBlockVideo::LOOP_MASK) != 0;
- auto animations_it = animations.find(page_block->video_id_);
- if (animations_it != animations.end()) {
- LOG_IF(ERROR, !is_looped) << "Receive non-looped animation";
- return make_unique<PageBlockAnimation>(animations_it->second, get_rich_text(std::move(page_block->caption_)),
- need_autoplay);
- }
-
- auto it = videos.find(page_block->video_id_);
- FileId video_file_id;
- if (it != videos.end()) {
- video_file_id = it->second;
- }
- return make_unique<PageBlockVideo>(video_file_id, get_rich_text(std::move(page_block->caption_)), need_autoplay,
- is_looped);
- }
- case telegram_api::pageBlockCover::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockCover>(page_block_ptr);
- auto cover = get_page_block(std::move(page_block->cover_), animations, audios, photos, videos);
- if (cover == nullptr) {
- return nullptr;
- }
- return make_unique<PageBlockCover>(std::move(cover));
- }
- case telegram_api::pageBlockEmbed::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockEmbed>(page_block_ptr);
- bool is_full_width = (page_block->flags_ & telegram_api::pageBlockEmbed::FULL_WIDTH_MASK) != 0;
- bool allow_scrolling = (page_block->flags_ & telegram_api::pageBlockEmbed::ALLOW_SCROLLING_MASK) != 0;
- auto it = (page_block->flags_ & telegram_api::pageBlockEmbed::POSTER_PHOTO_ID_MASK) != 0
- ? photos.find(page_block->poster_photo_id_)
- : photos.end();
- Photo poster_photo;
- if (it == photos.end()) {
- poster_photo.id = -2;
- } else {
- poster_photo = it->second;
- }
- return make_unique<PageBlockEmbedded>(std::move(page_block->url_), std::move(page_block->html_),
- std::move(poster_photo), get_dimensions(page_block->w_, page_block->h_),
- get_rich_text(std::move(page_block->caption_)), is_full_width,
- allow_scrolling);
- }
- case telegram_api::pageBlockEmbedPost::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockEmbedPost>(page_block_ptr);
- auto it = photos.find(page_block->author_photo_id_);
- Photo author_photo;
- if (it == photos.end()) {
- author_photo.id = -2;
- } else {
- author_photo = it->second;
- }
- return make_unique<PageBlockEmbeddedPost>(
- std::move(page_block->url_), std::move(page_block->author_), std::move(author_photo), page_block->date_,
- get_page_blocks(std::move(page_block->blocks_), animations, audios, photos, videos),
- get_rich_text(std::move(page_block->caption_)));
- }
- case telegram_api::pageBlockCollage::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockCollage>(page_block_ptr);
- return make_unique<PageBlockCollage>(
- get_page_blocks(std::move(page_block->items_), animations, audios, photos, videos),
- get_rich_text(std::move(page_block->caption_)));
- }
- case telegram_api::pageBlockSlideshow::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockSlideshow>(page_block_ptr);
- return make_unique<PageBlockSlideshow>(
- get_page_blocks(std::move(page_block->items_), animations, audios, photos, videos),
- get_rich_text(std::move(page_block->caption_)));
- }
- case telegram_api::pageBlockChannel::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockChannel>(page_block_ptr);
- CHECK(page_block->channel_ != nullptr);
- if (page_block->channel_->get_id() == telegram_api::channel::ID) {
- auto channel = static_cast<telegram_api::channel *>(page_block->channel_.get());
- ChannelId channel_id(channel->id_);
- if (!channel_id.is_valid()) {
- LOG(ERROR) << "Receive invalid " << channel_id;
- return nullptr;
- }
-
- if (td_->contacts_manager_->have_channel_force(channel_id)) {
- td_->contacts_manager_->on_get_chat(std::move(page_block->channel_));
- LOG(INFO) << "Receive known min " << channel_id;
- return make_unique<PageBlockChatLink>(td_->contacts_manager_->get_channel_title(channel_id),
- *td_->contacts_manager_->get_channel_dialog_photo(channel_id),
- td_->contacts_manager_->get_channel_username(channel_id));
- } else {
- return make_unique<PageBlockChatLink>(std::move(channel->title_),
- get_dialog_photo(td_->file_manager_.get(), std::move(channel->photo_)),
- std::move(channel->username_));
- }
- } else {
- LOG(ERROR) << "Receive wrong channel " << to_string(page_block->channel_);
- return nullptr;
- }
- }
- case telegram_api::pageBlockAudio::ID: {
- auto page_block = move_tl_object_as<telegram_api::pageBlockAudio>(page_block_ptr);
- auto it = audios.find(page_block->audio_id_);
- FileId audio_file_id;
- if (it != audios.end()) {
- audio_file_id = it->second;
- }
- return make_unique<PageBlockAudio>(audio_file_id, get_rich_text(std::move(page_block->caption_)));
- }
- default:
- UNREACHABLE();
+ LOG(WARNING) << "Have no messages and requests waiting for " << web_page_id;
}
- return nullptr;
}
-vector<unique_ptr<WebPagesManager::PageBlock>> WebPagesManager::get_page_blocks(
- vector<tl_object_ptr<telegram_api::PageBlock>> page_block_ptrs, const std::unordered_map<int64, FileId> &animations,
- const std::unordered_map<int64, FileId> &audios, const std::unordered_map<int64, Photo> &photos,
- const std::unordered_map<int64, FileId> &videos) const {
- vector<unique_ptr<PageBlock>> result;
- result.reserve(page_block_ptrs.size());
- for (auto &page_block_ptr : page_block_ptrs) {
- auto page_block = get_page_block(std::move(page_block_ptr), animations, audios, photos, videos);
- if (page_block != nullptr) {
- result.push_back(std::move(page_block));
- }
- }
- return result;
-}
-
-void WebPagesManager::on_get_web_page_instant_view(WebPage *web_page, tl_object_ptr<telegram_api::Page> &&page_ptr,
+void WebPagesManager::on_get_web_page_instant_view(WebPage *web_page, tl_object_ptr<telegram_api::page> &&page,
int32 hash, DialogId owner_dialog_id) {
- CHECK(page_ptr != nullptr);
- vector<tl_object_ptr<telegram_api::PageBlock>> page_block_ptrs;
- vector<tl_object_ptr<telegram_api::Photo>> photo_ptrs;
- vector<tl_object_ptr<telegram_api::Document>> document_ptrs;
- downcast_call(*page_ptr, [&](auto &page) {
- page_block_ptrs = std::move(page.blocks_);
- photo_ptrs = std::move(page.photos_);
- document_ptrs = std::move(page.documents_);
- });
-
- std::unordered_map<int64, Photo> photos;
- for (auto &photo_ptr : photo_ptrs) {
- if (photo_ptr->get_id() == telegram_api::photo::ID) {
- Photo photo =
- get_photo(td_->file_manager_.get(), move_tl_object_as<telegram_api::photo>(photo_ptr), owner_dialog_id);
- int64 photo_id = photo.id;
- photos.emplace(photo_id, std::move(photo));
+ CHECK(page != nullptr);
+ FlatHashMap<int64, unique_ptr<Photo>> photos;
+ for (auto &photo_ptr : page->photos_) {
+ Photo photo = get_photo(td_->file_manager_.get(), std::move(photo_ptr), owner_dialog_id);
+ if (photo.is_empty() || photo.id.get() == 0) {
+ LOG(ERROR) << "Receive empty photo in web page instant view for " << web_page->url;
+ } else {
+ auto photo_id = photo.id.get();
+ photos.emplace(photo_id, make_unique<Photo>(std::move(photo)));
+ }
+ }
+ if (!web_page->photo.is_empty() && web_page->photo.id.get() != 0) {
+ photos.emplace(web_page->photo.id.get(), make_unique<Photo>(web_page->photo));
+ }
+
+ FlatHashMap<int64, FileId> animations;
+ FlatHashMap<int64, FileId> audios;
+ FlatHashMap<int64, FileId> documents;
+ FlatHashMap<int64, FileId> videos;
+ FlatHashMap<int64, FileId> voice_notes;
+ FlatHashMap<int64, FileId> others;
+ auto get_map = [&](Document::Type document_type) {
+ switch (document_type) {
+ case Document::Type::Animation:
+ return &animations;
+ case Document::Type::Audio:
+ return &audios;
+ case Document::Type::General:
+ return &documents;
+ case Document::Type::Video:
+ return &videos;
+ case Document::Type::VoiceNote:
+ return &voice_notes;
+ default:
+ return &others;
}
- }
- if (web_page->photo.id != -2 && web_page->photo.id != 0) {
- photos.emplace(web_page->photo.id, web_page->photo);
- }
+ };
- std::unordered_map<int64, FileId> animations;
- std::unordered_map<int64, FileId> audios;
- std::unordered_map<int64, FileId> videos;
- for (auto &document_ptr : document_ptrs) {
+ for (auto &document_ptr : page->documents_) {
if (document_ptr->get_id() == telegram_api::document::ID) {
auto document = move_tl_object_as<telegram_api::document>(document_ptr);
auto document_id = document->id_;
auto parsed_document = td_->documents_manager_->on_get_document(std::move(document), owner_dialog_id);
- if (parsed_document.first == DocumentsManager::DocumentType::Animation) {
- animations.emplace(document_id, parsed_document.second);
- } else if (parsed_document.first == DocumentsManager::DocumentType::Audio) {
- audios.emplace(document_id, parsed_document.second);
- } else if (parsed_document.first == DocumentsManager::DocumentType::Video) {
- videos.emplace(document_id, parsed_document.second);
- } else {
- LOG(ERROR) << "Receive document of the wrong type " << static_cast<int32>(parsed_document.first);
+ if (!parsed_document.empty() && document_id != 0) {
+ get_map(parsed_document.type)->emplace(document_id, parsed_document.file_id);
}
}
}
- if (web_page->document_type == DocumentsManager::DocumentType::Animation) {
- auto file_view = td_->file_manager_->get_file_view(web_page->document_file_id);
- if (file_view.has_remote_location()) {
- animations.emplace(file_view.remote_location().get_id(), web_page->document_file_id);
- } else {
- LOG(ERROR) << "Animation has no remote location";
- }
+ if (!others.empty()) {
+ auto file_view = td_->file_manager_->get_file_view(others.begin()->second);
+ LOG(ERROR) << "Receive document of an unexpected type " << file_view.get_type();
}
- if (web_page->document_type == DocumentsManager::DocumentType::Audio) {
- auto file_view = td_->file_manager_->get_file_view(web_page->document_file_id);
+
+ auto add_document = [&](const Document &document) {
+ auto file_view = td_->file_manager_->get_file_view(document.file_id);
if (file_view.has_remote_location()) {
- audios.emplace(file_view.remote_location().get_id(), web_page->document_file_id);
+ auto document_id = file_view.remote_location().get_id();
+ if (document_id != 0) {
+ get_map(document.type)->emplace(document_id, document.file_id);
+ } else {
+ LOG(ERROR) << document.type << " has zero ID";
+ }
} else {
- LOG(ERROR) << "Audio has no remote location";
+ LOG(ERROR) << document.type << " has no remote location";
}
+ };
+ if (!web_page->document.empty()) {
+ add_document(web_page->document);
}
- if (web_page->document_type == DocumentsManager::DocumentType::Video) {
- auto file_view = td_->file_manager_->get_file_view(web_page->document_file_id);
- if (file_view.has_remote_location()) {
- videos.emplace(file_view.remote_location().get_id(), web_page->document_file_id);
- } else {
- LOG(ERROR) << "Video has no remote location";
- }
+ for (auto &document : web_page->documents) {
+ add_document(document);
}
- LOG(INFO) << "Receive a web page instant view with " << page_block_ptrs.size() << " blocks, " << animations.size()
- << " animations, " << audios.size() << " audios, " << photos.size() << " photos and " << videos.size()
- << " videos";
- web_page->instant_view.page_blocks = get_page_blocks(std::move(page_block_ptrs), animations, audios, photos, videos);
+ LOG(INFO) << "Receive a web page instant view with " << page->blocks_.size() << " blocks, " << animations.size()
+ << " animations, " << audios.size() << " audios, " << documents.size() << " documents, " << photos.size()
+ << " photos, " << videos.size() << " videos and " << voice_notes.size() << " voice notes";
+ web_page->instant_view.page_blocks =
+ get_web_page_blocks(td_, std::move(page->blocks_), animations, audios, documents, photos, videos, voice_notes);
+ web_page->instant_view.view_count = (page->flags_ & telegram_api::page::VIEWS_MASK) != 0 ? page->views_ : 0;
+ web_page->instant_view.is_v2 = page->v2_;
+ web_page->instant_view.is_rtl = page->rtl_;
web_page->instant_view.hash = hash;
+ web_page->instant_view.url = std::move(page->url_);
web_page->instant_view.is_empty = false;
- web_page->instant_view.is_full = page_ptr->get_id() == telegram_api::pageFull::ID;
+ web_page->instant_view.is_full = !page->part_;
web_page->instant_view.is_loaded = true;
LOG(DEBUG) << "Receive web page instant view: "
- << to_string(get_web_page_instant_view_object(&web_page->instant_view));
+ << to_string(get_web_page_instant_view_object(WebPageId(), &web_page->instant_view, web_page->url));
}
class WebPagesManager::WebPageLogEvent {
@@ -2613,34 +1533,31 @@ class WebPagesManager::WebPageLogEvent {
template <class ParserT>
void parse(ParserT &parser) {
td::parse(web_page_id, parser);
- CHECK(web_page_out == nullptr);
- web_page_out = make_unique<WebPage>();
- td::parse(*web_page_out, parser);
+ td::parse(web_page_out, parser);
}
};
-void WebPagesManager::save_web_page(WebPage *web_page, WebPageId web_page_id, bool from_binlog) {
+void WebPagesManager::save_web_page(const WebPage *web_page, WebPageId web_page_id, bool from_binlog) {
if (!G()->parameters().use_message_db) {
return;
}
CHECK(web_page != nullptr);
if (!from_binlog) {
- WebPageLogEvent logevent(web_page_id, web_page);
- LogEventStorerImpl<WebPageLogEvent> storer(logevent);
- if (web_page->logevent_id == 0) {
- web_page->logevent_id = BinlogHelper::add(G()->td_db()->get_binlog(), LogEvent::HandlerType::WebPages, storer);
+ WebPageLogEvent log_event(web_page_id, web_page);
+ auto storer = get_log_event_storer(log_event);
+ if (web_page->log_event_id == 0) {
+ web_page->log_event_id = binlog_add(G()->td_db()->get_binlog(), LogEvent::HandlerType::WebPages, storer);
} else {
- BinlogHelper::rewrite(G()->td_db()->get_binlog(), web_page->logevent_id, LogEvent::HandlerType::WebPages, storer);
+ binlog_rewrite(G()->td_db()->get_binlog(), web_page->log_event_id, LogEvent::HandlerType::WebPages, storer);
}
}
LOG(INFO) << "Save " << web_page_id << " to database";
G()->td_db()->get_sqlite_pmc()->set(
get_web_page_database_key(web_page_id), log_event_store(*web_page).as_slice().str(),
- PromiseCreator::lambda([web_page_id](Result<> result) {
- send_closure(G()->web_pages_manager(), &WebPagesManager::on_save_web_page_to_database, web_page_id,
- result.is_ok());
+ PromiseCreator::lambda([actor_id = actor_id(this), web_page_id](Result<> result) {
+ send_closure(actor_id, &WebPagesManager::on_save_web_page_to_database, web_page_id, result.is_ok());
}));
}
@@ -2650,7 +1567,7 @@ string WebPagesManager::get_web_page_url_database_key(const string &url) {
void WebPagesManager::on_binlog_web_page_event(BinlogEvent &&event) {
if (!G()->parameters().use_message_db) {
- BinlogHelper::erase(G()->td_db()->get_binlog(), event.id_);
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
return;
}
@@ -2658,21 +1575,28 @@ void WebPagesManager::on_binlog_web_page_event(BinlogEvent &&event) {
log_event_parse(log_event, event.data_).ensure();
auto web_page_id = log_event.web_page_id;
+ if (!web_page_id.is_valid()) {
+ binlog_erase(G()->td_db()->get_binlog(), event.id_);
+ return;
+ }
LOG(INFO) << "Add " << web_page_id << " from binlog";
auto web_page = std::move(log_event.web_page_out);
CHECK(web_page != nullptr);
- web_page->logevent_id = event.id_;
+ web_page->log_event_id = event.id_;
update_web_page(std::move(web_page), web_page_id, true, false);
}
string WebPagesManager::get_web_page_database_key(WebPageId web_page_id) {
- return "wp" + to_string(web_page_id.get());
+ return PSTRING() << "wp" << web_page_id.get();
}
void WebPagesManager::on_save_web_page_to_database(WebPageId web_page_id, bool success) {
- WebPage *web_page = get_web_page(web_page_id);
+ if (G()->close_flag()) {
+ return;
+ }
+ const WebPage *web_page = get_web_page(web_page_id);
if (web_page == nullptr) {
LOG(ERROR) << "Can't find " << (success ? "saved " : "failed to save ") << web_page_id;
return;
@@ -2680,19 +1604,20 @@ void WebPagesManager::on_save_web_page_to_database(WebPageId web_page_id, bool s
if (!success) {
LOG(ERROR) << "Failed to save " << web_page_id << " to database";
- save_web_page(web_page, web_page_id, web_page->logevent_id != 0);
+ save_web_page(web_page, web_page_id, web_page->log_event_id != 0);
} else {
LOG(INFO) << "Successfully saved " << web_page_id << " to database";
- if (web_page->logevent_id != 0) {
+ if (web_page->log_event_id != 0) {
LOG(INFO) << "Erase " << web_page_id << " from binlog";
- BinlogHelper::erase(G()->td_db()->get_binlog(), web_page->logevent_id);
- web_page->logevent_id = 0;
+ binlog_erase(G()->td_db()->get_binlog(), web_page->log_event_id);
+ web_page->log_event_id = 0;
}
}
}
void WebPagesManager::load_web_page_from_database(WebPageId web_page_id, Promise<Unit> promise) {
- if (!G()->parameters().use_message_db || loaded_from_database_web_pages_.count(web_page_id)) {
+ if (!G()->parameters().use_message_db || loaded_from_database_web_pages_.count(web_page_id) ||
+ !web_page_id.is_valid()) {
promise.set_value(Unit());
return;
}
@@ -2701,15 +1626,19 @@ void WebPagesManager::load_web_page_from_database(WebPageId web_page_id, Promise
auto &load_web_page_queries = load_web_page_from_database_queries_[web_page_id];
load_web_page_queries.push_back(std::move(promise));
if (load_web_page_queries.size() == 1u) {
- G()->td_db()->get_sqlite_pmc()->get(
- get_web_page_database_key(web_page_id), PromiseCreator::lambda([web_page_id](string value) {
- send_closure(G()->web_pages_manager(), &WebPagesManager::on_load_web_page_from_database, web_page_id,
- std::move(value));
- }));
+ G()->td_db()->get_sqlite_pmc()->get(get_web_page_database_key(web_page_id),
+ PromiseCreator::lambda([actor_id = actor_id(this), web_page_id](string value) {
+ send_closure(actor_id, &WebPagesManager::on_load_web_page_from_database,
+ web_page_id, std::move(value));
+ }));
}
}
void WebPagesManager::on_load_web_page_from_database(WebPageId web_page_id, string value) {
+ if (G()->close_flag()) {
+ return;
+ }
+ CHECK(web_page_id.is_valid());
if (!loaded_from_database_web_pages_.insert(web_page_id).second) {
return;
}
@@ -2724,52 +1653,131 @@ void WebPagesManager::on_load_web_page_from_database(WebPageId web_page_id, stri
LOG(INFO) << "Successfully loaded " << web_page_id << " of size " << value.size() << " from database";
// G()->td_db()->get_sqlite_pmc()->erase(get_web_page_database_key(web_page_id), Auto());
- // return;
+ // value.clear();
- WebPage *web_page = get_web_page(web_page_id);
- if (web_page == nullptr) {
+ if (!have_web_page(web_page_id)) {
if (!value.empty()) {
auto result = make_unique<WebPage>();
- log_event_parse(*result, value).ensure();
- update_web_page(std::move(result), web_page_id, true, true);
+ auto status = log_event_parse(*result, value);
+ if (status.is_error()) {
+ LOG(ERROR) << "Failed to parse web page loaded from database: " << status
+ << ", value = " << format::as_hex_dump<4>(Slice(value));
+ } else {
+ update_web_page(std::move(result), web_page_id, true, true);
+ }
}
} else {
// web page has already been loaded from the server
}
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(promises);
}
bool WebPagesManager::have_web_page_force(WebPageId web_page_id) {
return get_web_page_force(web_page_id) != nullptr;
}
-WebPagesManager::WebPage *WebPagesManager::get_web_page_force(WebPageId web_page_id) {
- WebPage *web_page = get_web_page(web_page_id);
+const WebPagesManager::WebPage *WebPagesManager::get_web_page_force(WebPageId web_page_id) {
+ const WebPage *web_page = get_web_page(web_page_id);
if (web_page != nullptr) {
return web_page;
}
if (!G()->parameters().use_message_db) {
return nullptr;
}
- if (loaded_from_database_web_pages_.count(web_page_id)) {
+ if (!web_page_id.is_valid() || loaded_from_database_web_pages_.count(web_page_id)) {
return nullptr;
}
- LOG(INFO) << "Try load " << web_page_id << " from database";
+ LOG(INFO) << "Trying to load " << web_page_id << " from database";
on_load_web_page_from_database(web_page_id,
G()->td_db()->get_sqlite_sync_pmc()->get(get_web_page_database_key(web_page_id)));
return get_web_page(web_page_id);
}
+FileSourceId WebPagesManager::get_web_page_file_source_id(WebPage *web_page) {
+ if (!web_page->file_source_id.is_valid()) {
+ web_page->file_source_id = td_->file_reference_manager_->create_web_page_file_source(web_page->url);
+ VLOG(file_references) << "Create " << web_page->file_source_id << " for URL " << web_page->url;
+ } else {
+ VLOG(file_references) << "Return " << web_page->file_source_id << " for URL " << web_page->url;
+ }
+ return web_page->file_source_id;
+}
+
+FileSourceId WebPagesManager::get_url_file_source_id(const string &url) {
+ if (url.empty()) {
+ return FileSourceId();
+ }
+
+ auto web_page_id = get_web_page_by_url(url);
+ if (web_page_id.is_valid()) {
+ const WebPage *web_page = get_web_page(web_page_id);
+ if (web_page != nullptr) {
+ if (!web_page->file_source_id.is_valid()) {
+ web_pages_[web_page_id]->file_source_id =
+ td_->file_reference_manager_->create_web_page_file_source(web_page->url);
+ VLOG(file_references) << "Create " << web_page->file_source_id << " for " << web_page_id << " with URL " << url;
+ } else {
+ VLOG(file_references) << "Return " << web_page->file_source_id << " for " << web_page_id << " with URL " << url;
+ }
+ return web_page->file_source_id;
+ }
+ }
+ auto &source_id = url_to_file_source_id_[url];
+ if (!source_id.is_valid()) {
+ source_id = td_->file_reference_manager_->create_web_page_file_source(url);
+ VLOG(file_references) << "Create " << source_id << " for URL " << url;
+ } else {
+ VLOG(file_references) << "Return " << source_id << " for URL " << url;
+ }
+ return source_id;
+}
+
string WebPagesManager::get_web_page_search_text(WebPageId web_page_id) const {
- auto *web_page = get_web_page(web_page_id);
+ const WebPage *web_page = get_web_page(web_page_id);
if (web_page == nullptr) {
return "";
}
return PSTRING() << web_page->title + " " + web_page->description;
}
+int32 WebPagesManager::get_web_page_media_duration(WebPageId web_page_id) const {
+ const WebPage *web_page = get_web_page(web_page_id);
+ if (web_page == nullptr) {
+ return -1;
+ }
+ return get_web_page_media_duration(web_page);
+}
+
+int32 WebPagesManager::get_web_page_media_duration(const WebPage *web_page) {
+ if (web_page->document.type == Document::Type::Audio || web_page->document.type == Document::Type::Video ||
+ web_page->document.type == Document::Type::VideoNote || web_page->document.type == Document::Type::VoiceNote ||
+ web_page->embed_type == "iframe") {
+ return web_page->duration;
+ }
+
+ return -1;
+}
+
+vector<FileId> WebPagesManager::get_web_page_file_ids(const WebPage *web_page) const {
+ if (web_page == nullptr) {
+ return vector<FileId>();
+ }
+
+ vector<FileId> result = photo_get_file_ids(web_page->photo);
+ if (!web_page->document.empty()) {
+ web_page->document.append_file_ids(td_, result);
+ }
+ for (auto &document : web_page->documents) {
+ document.append_file_ids(td_, result);
+ }
+ if (!web_page->instant_view.is_empty) {
+ for (auto &page_block : web_page->instant_view.page_blocks) {
+ page_block->append_file_ids(td_, result);
+ }
+ }
+ return result;
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.h b/protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.h
index 7c491a8f78..84282d5428 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/WebPagesManager.h
@@ -1,38 +1,40 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
#include "td/telegram/DialogId.h"
#include "td/telegram/files/FileId.h"
-#include "td/telegram/MessageId.h"
-#include "td/telegram/Photo.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/FullMessageId.h"
+#include "td/telegram/SecretInputMedia.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/WebPageId.h"
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-#include "td/actor/Timeout.h"
-
-#include "td/db/binlog/BinlogEvent.h"
+#include "td/actor/MultiTimeout.h"
#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/WaitFreeHashMap.h"
-#include <unordered_map>
-#include <unordered_set>
#include <utility>
namespace td {
+struct BinlogEvent;
+
class Td;
-class WebPagesManager : public Actor {
+class WebPagesManager final : public Actor {
public:
WebPagesManager(Td *td, ActorShared<> parent);
@@ -40,13 +42,17 @@ class WebPagesManager : public Actor {
WebPagesManager &operator=(const WebPagesManager &) = delete;
WebPagesManager(WebPagesManager &&) = delete;
WebPagesManager &operator=(WebPagesManager &&) = delete;
- ~WebPagesManager() override;
+ ~WebPagesManager() final;
WebPageId on_get_web_page(tl_object_ptr<telegram_api::WebPage> &&web_page_ptr, DialogId owner_dialog_id);
void on_get_web_page_by_url(const string &url, WebPageId web_page_id, bool from_database);
- void wait_for_pending_web_page(DialogId dialog_id, MessageId message_id, WebPageId web_page_id);
+ void on_get_web_page_instant_view_view_count(WebPageId web_page_id, int32 view_count);
+
+ void register_web_page(WebPageId web_page_id, FullMessageId full_message_id, const char *source);
+
+ void unregister_web_page(WebPageId web_page_id, FullMessageId full_message_id, const char *source);
bool have_web_page(WebPageId web_page_id) const;
@@ -60,11 +66,13 @@ class WebPagesManager : public Actor {
tl_object_ptr<td_api::webPage> get_web_page_preview_result(int64 request_id);
- WebPageId get_web_page_instant_view(const string &url, bool force_full, Promise<Unit> &&promise);
+ void get_web_page_instant_view(const string &url, bool force_full, Promise<WebPageId> &&promise);
WebPageId get_web_page_by_url(const string &url) const;
- WebPageId get_web_page_by_url(const string &url, Promise<Unit> &&promise);
+ void get_web_page_by_url(const string &url, Promise<WebPageId> &&promise);
+
+ void reload_web_page_by_url(const string &url, Promise<WebPageId> &&promise);
void on_get_web_page_preview_success(int64 request_id, const string &url,
tl_object_ptr<telegram_api::MessageMedia> &&message_media_ptr,
@@ -76,60 +84,32 @@ class WebPagesManager : public Actor {
void on_binlog_web_page_event(BinlogEvent &&event);
+ FileSourceId get_url_file_source_id(const string &url);
+
string get_web_page_search_text(WebPageId web_page_id) const;
+ int32 get_web_page_media_duration(WebPageId web_page_id) const;
+
private:
- static constexpr int32 WEBPAGE_FLAG_HAS_TYPE = 1;
- static constexpr int32 WEBPAGE_FLAG_HAS_SITE_NAME = 2;
- static constexpr int32 WEBPAGE_FLAG_HAS_TITLE = 4;
- static constexpr int32 WEBPAGE_FLAG_HAS_DESCRIPTION = 8;
- static constexpr int32 WEBPAGE_FLAG_HAS_PHOTO = 16;
- static constexpr int32 WEBPAGE_FLAG_HAS_EMBEDDED_PREVIEW = 32;
- static constexpr int32 WEBPAGE_FLAG_HAS_EMBEDDED_PREVIEW_SIZE = 64;
- static constexpr int32 WEBPAGE_FLAG_HAS_DURATION = 128;
- static constexpr int32 WEBPAGE_FLAG_HAS_AUTHOR = 256;
- static constexpr int32 WEBPAGE_FLAG_HAS_DOCUMENT = 512;
- static constexpr int32 WEBPAGE_FLAG_HAS_INSTANT_VIEW = 1024;
+ static constexpr int32 WEBPAGE_FLAG_HAS_TYPE = 1 << 0;
+ static constexpr int32 WEBPAGE_FLAG_HAS_SITE_NAME = 1 << 1;
+ static constexpr int32 WEBPAGE_FLAG_HAS_TITLE = 1 << 2;
+ static constexpr int32 WEBPAGE_FLAG_HAS_DESCRIPTION = 1 << 3;
+ static constexpr int32 WEBPAGE_FLAG_HAS_PHOTO = 1 << 4;
+ static constexpr int32 WEBPAGE_FLAG_HAS_EMBEDDED_PREVIEW = 1 << 5;
+ static constexpr int32 WEBPAGE_FLAG_HAS_EMBEDDED_PREVIEW_SIZE = 1 << 6;
+ static constexpr int32 WEBPAGE_FLAG_HAS_DURATION = 1 << 7;
+ static constexpr int32 WEBPAGE_FLAG_HAS_AUTHOR = 1 << 8;
+ static constexpr int32 WEBPAGE_FLAG_HAS_DOCUMENT = 1 << 9;
+ static constexpr int32 WEBPAGE_FLAG_HAS_INSTANT_VIEW = 1 << 10;
+ static constexpr int32 WEBPAGE_FLAG_HAS_DOCUMENTS = 1 << 11;
class WebPage;
- class RichText;
-
- class PageBlock;
- class PageBlockTitle;
- class PageBlockSubtitle;
- class PageBlockAuthorDate;
- class PageBlockHeader;
- class PageBlockSubheader;
- class PageBlockParagraph;
- class PageBlockPreformatted;
- class PageBlockFooter;
- class PageBlockDivider;
- class PageBlockAnchor;
- class PageBlockList;
- class PageBlockBlockQuote;
- class PageBlockPullQuote;
- class PageBlockAnimation;
- class PageBlockPhoto;
- class PageBlockVideo;
- class PageBlockCover;
- class PageBlockEmbedded;
- class PageBlockEmbeddedPost;
- class PageBlockCollage;
- class PageBlockSlideshow;
- class PageBlockChatLink;
- class PageBlockAudio;
-
class WebPageInstantView;
class WebPageLogEvent;
- template <class T>
- friend void store(const unique_ptr<PageBlock> &block, T &storer);
-
- template <class T>
- friend void parse(unique_ptr<PageBlock> &block, T &parser);
-
void update_web_page(unique_ptr<WebPage> web_page, WebPageId web_page_id, bool from_binlog, bool from_database);
void update_web_page_instant_view(WebPageId web_page_id, WebPageInstantView &new_instant_view,
@@ -138,53 +118,28 @@ class WebPagesManager : public Actor {
static bool need_use_old_instant_view(const WebPageInstantView &new_instant_view,
const WebPageInstantView &old_instant_view);
- void update_messages_content(WebPageId web_page_id, bool have_web_page);
+ void on_web_page_changed(WebPageId web_page_id, bool have_web_page);
- WebPage *get_web_page(WebPageId web_page_id);
const WebPage *get_web_page(WebPageId web_page_id) const;
- WebPageInstantView *get_web_page_instant_view(WebPageId web_page_id);
-
const WebPageInstantView *get_web_page_instant_view(WebPageId web_page_id) const;
- WebPageId get_web_page_instant_view(WebPageId web_page_id, bool force_full, Promise<Unit> &&promise);
+ void get_web_page_instant_view_impl(WebPageId web_page_id, bool force_full, Promise<WebPageId> &&promise);
tl_object_ptr<td_api::webPageInstantView> get_web_page_instant_view_object(
- const WebPageInstantView *web_page_instant_view) const;
+ WebPageId web_page_id, const WebPageInstantView *web_page_instant_view, Slice web_page_url) const;
+
+ static void on_pending_web_page_timeout_callback(void *web_pages_manager_ptr, int64 web_page_id_int);
- static void on_pending_web_page_timeout_callback(void *web_pages_manager_ptr, int64 web_page_id);
void on_pending_web_page_timeout(WebPageId web_page_id);
void on_get_web_page_preview_success(int64 request_id, const string &url, WebPageId web_page_id,
Promise<Unit> &&promise);
- static RichText get_rich_text(tl_object_ptr<telegram_api::RichText> &&rich_text_ptr);
-
- static vector<RichText> get_rich_texts(vector<tl_object_ptr<telegram_api::RichText>> &&rich_text_ptrs);
-
- static tl_object_ptr<td_api::RichText> get_rich_text_object(const RichText &rich_text);
-
- static vector<tl_object_ptr<td_api::RichText>> get_rich_text_objects(const vector<RichText> &rich_texts);
-
- static vector<tl_object_ptr<td_api::PageBlock>> get_page_block_objects(
- const vector<unique_ptr<PageBlock>> &page_blocks);
-
- unique_ptr<PageBlock> get_page_block(tl_object_ptr<telegram_api::PageBlock> page_block_ptr,
- const std::unordered_map<int64, FileId> &animations,
- const std::unordered_map<int64, FileId> &audios,
- const std::unordered_map<int64, Photo> &photos,
- const std::unordered_map<int64, FileId> &videos) const;
-
- vector<unique_ptr<PageBlock>> get_page_blocks(vector<tl_object_ptr<telegram_api::PageBlock>> page_block_ptrs,
- const std::unordered_map<int64, FileId> &animations,
- const std::unordered_map<int64, FileId> &audios,
- const std::unordered_map<int64, Photo> &photos,
- const std::unordered_map<int64, FileId> &videos) const;
-
- void on_get_web_page_instant_view(WebPage *web_page, tl_object_ptr<telegram_api::Page> &&page_ptr, int32 hash,
+ void on_get_web_page_instant_view(WebPage *web_page, tl_object_ptr<telegram_api::page> &&page, int32 hash,
DialogId owner_dialog_id);
- void save_web_page(WebPage *web_page, WebPageId web_page_id, bool from_binlog);
+ void save_web_page(const WebPage *web_page, WebPageId web_page_id, bool from_binlog);
static string get_web_page_database_key(WebPageId web_page_id);
@@ -194,54 +149,61 @@ class WebPagesManager : public Actor {
void on_load_web_page_from_database(WebPageId web_page_id, string value);
- WebPage *get_web_page_force(WebPageId web_page_id);
+ const WebPage *get_web_page_force(WebPageId web_page_id);
static string get_web_page_instant_view_database_key(WebPageId web_page_id);
- void load_web_page_instant_view(WebPageId web_page_id, bool force_full, Promise<Unit> &&promise);
+ void load_web_page_instant_view(WebPageId web_page_id, bool force_full, Promise<WebPageId> &&promise);
void on_load_web_page_instant_view_from_database(WebPageId web_page_id, string value);
void reload_web_page_instant_view(WebPageId web_page_id);
- void update_web_page_instant_view_load_requests(WebPageId web_page_id, bool force_update, Result<> result);
+ void update_web_page_instant_view_load_requests(WebPageId web_page_id, bool force_update,
+ Result<WebPageId> r_web_page_id);
static string get_web_page_url_database_key(const string &url);
- void load_web_page_by_url(const string &url, Promise<Unit> &&promise);
+ void load_web_page_by_url(string url, Promise<WebPageId> &&promise);
+
+ void on_load_web_page_id_by_url_from_database(string url, string value, Promise<WebPageId> &&promise);
- void reload_web_page_by_url(const string &url, Promise<Unit> &&promise);
+ void on_load_web_page_by_url_from_database(WebPageId web_page_id, string url, Promise<WebPageId> &&promise,
+ Result<Unit> &&result);
- void on_load_web_page_id_by_url_from_database(const string &url, string value, Promise<Unit> &&promise);
+ void tear_down() final;
- void on_load_web_page_by_url_from_database(WebPageId web_page_id, const string &url, Promise<Unit> &&promise,
- Result<> result);
+ static int32 get_web_page_media_duration(const WebPage *web_page);
- void tear_down() override;
+ FileSourceId get_web_page_file_source_id(WebPage *web_page);
+
+ vector<FileId> get_web_page_file_ids(const WebPage *web_page) const;
Td *td_;
ActorShared<> parent_;
- std::unordered_map<WebPageId, unique_ptr<WebPage>, WebPageIdHash> web_pages_;
+ WaitFreeHashMap<WebPageId, unique_ptr<WebPage>, WebPageIdHash> web_pages_;
- std::unordered_map<WebPageId, vector<Promise<Unit>>, WebPageIdHash> load_web_page_from_database_queries_;
- std::unordered_set<WebPageId, WebPageIdHash> loaded_from_database_web_pages_;
+ FlatHashMap<WebPageId, vector<Promise<Unit>>, WebPageIdHash> load_web_page_from_database_queries_;
+ FlatHashSet<WebPageId, WebPageIdHash> loaded_from_database_web_pages_;
struct PendingWebPageInstantViewQueries {
- vector<Promise<Unit>> partial;
- vector<Promise<Unit>> full;
+ vector<Promise<WebPageId>> partial;
+ vector<Promise<WebPageId>> full;
};
- std::unordered_map<WebPageId, PendingWebPageInstantViewQueries, WebPageIdHash> load_web_page_instant_view_queries_;
+ FlatHashMap<WebPageId, PendingWebPageInstantViewQueries, WebPageIdHash> load_web_page_instant_view_queries_;
+
+ FlatHashMap<WebPageId, FlatHashSet<FullMessageId, FullMessageIdHash>, WebPageIdHash> web_page_messages_;
- std::unordered_map<WebPageId, std::unordered_set<FullMessageId, FullMessageIdHash>, WebPageIdHash> pending_web_pages_;
- std::unordered_map<WebPageId, std::unordered_map<int64, std::pair<string, Promise<Unit>>>, WebPageIdHash>
- pending_get_web_pages_;
+ FlatHashMap<WebPageId, FlatHashMap<int64, std::pair<string, Promise<Unit>>>, WebPageIdHash> pending_get_web_pages_;
int64 get_web_page_preview_request_id_ = 1;
- std::unordered_map<int64, WebPageId> got_web_page_previews_;
+ FlatHashMap<int64, WebPageId> got_web_page_previews_;
+
+ FlatHashMap<string, WebPageId> url_to_web_page_id_;
- std::unordered_map<string, WebPageId> url_to_web_page_id_;
+ FlatHashMap<string, FileSourceId> url_to_file_source_id_;
- MultiTimeout pending_web_pages_timeout_;
+ MultiTimeout pending_web_pages_timeout_{"PendingWebPagesTimeout"};
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/cli.cpp b/protocols/Telegram/tdlib/td/td/telegram/cli.cpp
index ec69f8265e..c9cd04fcf3 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/cli.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/cli.cpp
@@ -1,48 +1,63 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#include "td/telegram/Client.h"
#include "td/telegram/ClientActor.h"
-#include "td/telegram/Log.h"
-
#include "td/telegram/td_api_json.h"
-#include "td/actor/actor.h"
+#include "td/net/HttpQuery.h"
+#include "td/net/HttpReader.h"
-#include "td/tl/tl_json.h" // should be included after td_api_json?
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
#include "memprof/memprof.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/base64.h"
#include "td/utils/buffer.h"
-#include "td/utils/BufferedFd.h"
+#include "td/utils/CombinedLog.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/ExitGuard.h"
#include "td/utils/FileLog.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
#include "td/utils/format.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/NullLog.h"
+#include "td/utils/OptionParser.h"
+#include "td/utils/port/detail/ThreadIdGuard.h"
#include "td/utils/port/FileFd.h"
+#include "td/utils/port/PollFlags.h"
#include "td/utils/port/signals.h"
#include "td/utils/port/Stat.h"
+#include "td/utils/port/StdStreams.h"
#include "td/utils/port/thread_local.h"
+#include "td/utils/Random.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Time.h"
+#include "td/utils/TsLog.h"
+#include "td/utils/utf8.h"
#ifndef USE_READLINE
#include "td/utils/find_boundary.h"
#endif
#include <algorithm>
-#include <array>
#include <atomic>
-#include <clocale>
+#include <cstdio>
#include <cstdlib>
-#include <cstring> // for strcmp
#include <ctime>
#include <iostream>
#include <limits>
@@ -50,7 +65,7 @@
#include <memory>
#include <queue>
#include <tuple>
-#include <unordered_map>
+#include <utility>
#ifdef USE_READLINE
/* Standard readline include files. */
@@ -62,24 +77,25 @@ namespace td {
static void dump_memory_usage() {
if (is_memprof_on()) {
- LOG(WARNING) << "memory_dump";
+ LOG(WARNING) << "Memory dump:";
clear_thread_locals();
- std::vector<AllocInfo> v;
- dump_alloc([&](const AllocInfo &info) { v.push_back(info); });
- std::sort(v.begin(), v.end(), [](const AllocInfo &a, const AllocInfo &b) { return a.size > b.size; });
+ std::vector<AllocInfo> alloc_info;
+ dump_alloc([&](const AllocInfo &info) { alloc_info.push_back(info); });
+ std::sort(alloc_info.begin(), alloc_info.end(),
+ [](const AllocInfo &lhs, const AllocInfo &rhs) { return lhs.size > rhs.size; });
size_t total_size = 0;
size_t other_size = 0;
int cnt = 0;
- for (auto &info : v) {
+ for (auto &info : alloc_info) {
if (cnt++ < 50) {
- LOG(WARNING) << td::format::as_size(info.size) << td::format::as_array(info.backtrace);
+ LOG(WARNING) << format::as_size(info.size) << format::as_array(info.backtrace);
} else {
other_size += info.size;
}
total_size += info.size;
}
- LOG(WARNING) << tag("other", td::format::as_size(other_size));
- LOG(WARNING) << tag("total", td::format::as_size(total_size));
+ LOG(WARNING) << tag("other", format::as_size(other_size));
+ LOG(WARNING) << tag("total", format::as_size(total_size));
LOG(WARNING) << tag("total traces", get_ht_size());
LOG(WARNING) << tag("fast_backtrace_success_rate", get_fast_backtrace_success_rate());
}
@@ -114,24 +130,11 @@ static void reactivate_readline() {
}
static char *command_generator(const char *text, int state) {
- static vector<CSlice> commands{"GetContacts",
- "GetChats",
- "GetHistory",
- "SetVerbosity",
- "SendVideo",
- "SearchDocument",
- "GetChatMember",
- "GetSupergroupAdministrators",
- "GetSupergroupBanned",
- "GetSupergroupMembers",
- "GetFile",
- "DownloadFile",
- "CancelDownloadFile",
- "ImportContacts",
- "RemoveContacts",
- "DumpNetQueries",
- "CreateSecretChat",
- "CreateNewSecretChat"};
+ static const vector<CSlice> commands{"GetHistory", "SetVerbosity", "SendVideo",
+ "SearchDocument", "GetChatMember", "GetSupergroupAdministrators",
+ "GetSupergroupBanned", "GetSupergroupMembers", "GetFile",
+ "DownloadFile", "CancelDownloadFile", "ImportContacts",
+ "RemoveContacts", "CreateSecretChat", "CreateNewSecretChat"};
static size_t cmd_i;
if (state == 0) {
cmd_i = 0;
@@ -175,29 +178,20 @@ static char **tg_cli_completion(const char *text, int start, int end) {
}
#endif
-class CliLog : public LogInterface {
- public:
- void append(CSlice slice, int log_level) override {
+class CliLog final : public LogInterface {
+ void do_append(int log_level, CSlice slice) final {
#ifdef USE_READLINE
deactivate_readline();
+ SCOPE_EXIT {
+ reactivate_readline();
+ };
#endif
- if (log_level == VERBOSITY_NAME(PLAIN)) {
-#if TD_WINDOWS
- TsCerr() << slice;
-#else
- TsCerr() << TC_GREEN << slice << TC_EMPTY;
-#endif
- } else {
- default_log_interface->append(slice, log_level);
- }
-#ifdef USE_READLINE
- reactivate_readline();
-#endif
- }
- void rotate() override {
+ default_log_interface->do_append(log_level, slice);
}
};
+static CombinedLog combined_log;
+
struct SendMessageInfo {
double start_time = 0;
double quick_ack_time = 0;
@@ -215,12 +209,14 @@ StringBuilder &operator<<(StringBuilder &sb, const SendMessageInfo &info) {
class CliClient final : public Actor {
public:
- CliClient(bool use_test_dc, bool get_chat_list, bool disable_network, int32 api_id, string api_hash)
- : use_test_dc_(use_test_dc)
+ CliClient(ConcurrentScheduler *scheduler, bool use_test_dc, bool get_chat_list, bool disable_network, int32 api_id,
+ string api_hash)
+ : scheduler_(scheduler)
+ , use_test_dc_(use_test_dc)
, get_chat_list_(get_chat_list)
, disable_network_(disable_network)
, api_id_(api_id)
- , api_hash_(api_hash) {
+ , api_hash_(std::move(api_hash)) {
}
static void quit_instance() {
@@ -228,12 +224,12 @@ class CliClient final : public Actor {
}
private:
- void start_up() override {
+ void start_up() final {
yield();
}
- std::unordered_map<uint64, SendMessageInfo> query_id_to_send_message_info_;
- std::unordered_map<uint64, SendMessageInfo> message_id_to_send_message_info_;
+ FlatHashMap<uint64, SendMessageInfo> query_id_to_send_message_info_;
+ FlatHashMap<uint64, SendMessageInfo> message_id_to_send_message_info_;
struct User {
string first_name;
@@ -241,27 +237,34 @@ class CliClient final : public Actor {
string username;
};
- std::unordered_map<int32, User> users_;
- std::unordered_map<string, int32> username_to_user_id_;
+ FlatHashMap<int64, unique_ptr<User>> users_;
+ FlatHashMap<string, int64> username_to_user_id_;
+
+ vector<string> authentication_tokens_;
void register_user(const td_api::user &user) {
- User &new_user = users_[user.id_];
+ auto &new_user_ptr = users_[user.id_];
+ if (new_user_ptr == nullptr) {
+ new_user_ptr = make_unique<User>();
+ }
+ User &new_user = *new_user_ptr;
new_user.first_name = user.first_name_;
new_user.last_name = user.last_name_;
- new_user.username = user.username_;
- username_to_user_id_[to_lower(new_user.username)] = user.id_;
+ if (user.usernames_ != nullptr) {
+ for (auto &username : user.usernames_->active_usernames_) {
+ username_to_user_id_[to_lower(username)] = user.id_;
+ }
+ }
}
- void print_user(Logger &log, int32 user_id, bool full = false) {
- const User *user = &users_[user_id];
+ void print_user(Logger &log, int64 user_id, bool full = false) {
+ const User *user = users_[user_id].get();
+ CHECK(user != nullptr);
log << user->first_name << " " << user->last_name << " #" << user_id;
- if (!user->username.empty()) {
- log << " @" << user->username;
- }
}
void update_users(const td_api::users &users) {
- Logger log{*log_interface, VERBOSITY_NAME(PLAIN)};
+ Logger log{*log_interface, LogOptions::plain(), VERBOSITY_NAME(PLAIN)};
for (auto &user_id : users.user_ids_) {
if (user_id == 0) {
continue;
@@ -271,50 +274,55 @@ class CliClient final : public Actor {
}
}
- std::unordered_map<string, int32> username_to_supergroup_id;
+ FlatHashMap<string, int64> username_to_supergroup_id_;
void register_supergroup(const td_api::supergroup &supergroup) {
- if (!supergroup.username_.empty()) {
- username_to_supergroup_id[to_lower(supergroup.username_)] = supergroup.id_;
+ if (supergroup.usernames_ != nullptr) {
+ for (auto &username : supergroup.usernames_->active_usernames_) {
+ username_to_supergroup_id_[to_lower(username)] = supergroup.id_;
+ }
}
}
void update_option(const td_api::updateOption &option) {
- if (option.name_ == "my_id") {
- if (option.value_->get_id() == td_api::optionValueInteger::ID) {
- my_id_ = static_cast<const td_api::optionValueInteger *>(option.value_.get())->value_;
- LOG(INFO) << "Set my id to " << my_id_;
- }
+ if (option.name_ == "my_id" && option.value_->get_id() == td_api::optionValueInteger::ID) {
+ my_id_ = static_cast<const td_api::optionValueInteger *>(option.value_.get())->value_;
+ LOG(INFO) << "Set my user identifier to " << my_id_;
+ }
+ if (option.name_ == "authentication_token" && option.value_->get_id() == td_api::optionValueString::ID) {
+ authentication_tokens_.insert(authentication_tokens_.begin(),
+ static_cast<const td_api::optionValueString *>(option.value_.get())->value_);
}
}
- int64 get_history_chat_id = 0;
- int64 search_chat_id = 0;
+ int64 get_history_chat_id_ = 0;
+ int64 search_chat_id_ = 0;
void on_get_messages(const td_api::messages &messages) {
- if (get_history_chat_id != 0) {
+ if (get_history_chat_id_ != 0) {
int64 last_message_id = 0;
+ int32 last_message_date = 0;
for (auto &m : messages.messages_) {
// LOG(PLAIN) << to_string(m);
if (m->content_->get_id() == td_api::messageText::ID) {
- LOG(PLAIN) << td::oneline(static_cast<const td_api::messageText *>(m->content_.get())->text_->text_) << "\n";
+ LOG(PLAIN) << oneline(static_cast<const td_api::messageText *>(m->content_.get())->text_->text_) << "\n";
}
last_message_id = m->id_;
+ last_message_date = m->date_;
}
- if (last_message_id > 0) {
- send_request(make_tl_object<td_api::getChatHistory>(get_history_chat_id, last_message_id, 0, 100, false));
+ if (last_message_id > 0 && last_message_date > 1660000000) {
+ send_request(td_api::make_object<td_api::getChatHistory>(get_history_chat_id_, last_message_id, 0, 100, false));
} else {
- get_history_chat_id = 0;
+ get_history_chat_id_ = 0;
}
}
- if (search_chat_id != 0) {
+ if (search_chat_id_ != 0) {
if (!messages.messages_.empty()) {
auto last_message_id = messages.messages_.back()->id_;
LOG(ERROR) << (last_message_id >> 20);
- send_request(
- make_tl_object<td_api::searchChatMessages>(search_chat_id, "", 0, last_message_id, 0, 100,
- make_tl_object<td_api::searchMessagesFilterPhotoAndVideo>()));
+ send_request(td_api::make_object<td_api::searchChatMessages>(search_chat_id_, "", nullptr, last_message_id, 0,
+ 100, as_search_messages_filter("pvi"), 0));
} else {
- search_chat_id = 0;
+ search_chat_id_ = 0;
}
}
}
@@ -322,7 +330,23 @@ class CliClient final : public Actor {
void on_get_message(const td_api::message &message) {
if (message.sending_state_ != nullptr &&
message.sending_state_->get_id() == td_api::messageSendingStatePending::ID) {
- // send_request(make_tl_object<td_api::deleteMessages>(message.chat_id_, vector<int64>{message.id_}, true));
+ // send_request(td_api::make_object<td_api::deleteMessages>(message.chat_id_, vector<int64>{message.id_}, true));
+ }
+ }
+
+ void on_get_file(const td_api::file &file) {
+ if (being_downloaded_files_.count(file.id_) == 0 && file.local_->is_downloading_active_) {
+ being_downloaded_files_[file.id_] = Time::now();
+ }
+
+ if (being_downloaded_files_.count(file.id_) != 0 && !file.local_->is_downloading_active_) {
+ double elapsed_time = Time::now() - being_downloaded_files_[file.id_];
+ being_downloaded_files_.erase(file.id_);
+ if (file.local_->is_downloading_completed_) {
+ LOG(ERROR) << "File " << file.id_ << " was downloaded in " << elapsed_time << " seconds";
+ } else {
+ LOG(ERROR) << "File " << file.id_ << " has failed to download in " << elapsed_time << " seconds";
+ }
}
}
@@ -330,18 +354,19 @@ class CliClient final : public Actor {
int64 id = 0;
string destination;
string source;
- int32 part_size = 0;
- int32 local_size = 0;
- int32 size = 0;
+ int64 part_size = 0;
+ int64 local_size = 0;
+ int64 size = 0;
+ bool test_local_size_decrease = false;
};
- vector<FileGeneration> pending_file_generations;
+ vector<FileGeneration> pending_file_generations_;
void on_file_generation_start(const td_api::updateFileGenerationStart &update) {
FileGeneration file_generation;
file_generation.id = update.generation_id_;
file_generation.destination = update.destination_path_;
- if (update.conversion_ == "#url#") {
+ if (update.conversion_ == "#url#" || update.conversion_ == "url") {
// TODO: actually download
file_generation.source = "test.jpg";
file_generation.part_size = 1000000;
@@ -349,13 +374,14 @@ class CliClient final : public Actor {
return;
} else {
file_generation.source = update.original_path_;
- file_generation.part_size = to_integer<int32>(update.conversion_);
+ file_generation.part_size = to_integer<int64>(update.conversion_);
+ file_generation.test_local_size_decrease = !update.conversion_.empty() && update.conversion_.back() == 't';
}
auto r_stat = stat(file_generation.source);
if (r_stat.is_ok()) {
auto size = r_stat.ok().size_;
- if (size <= 0 || size > 1500000000) {
+ if (size <= 0 || size > (static_cast<int64>(4000) << 20)) {
r_stat = Status::Error(400, size == 0 ? Slice("File is empty") : Slice("File is too big"));
}
}
@@ -364,121 +390,540 @@ class CliClient final : public Actor {
if (file_generation.part_size <= 0) {
file_generation.part_size = file_generation.size;
}
- pending_file_generations.push_back(std::move(file_generation));
+ pending_file_generations_.push_back(std::move(file_generation));
timeout_expired();
} else {
- send_request(make_tl_object<td_api::finishFileGeneration>(
+ send_request(td_api::make_object<td_api::finishFileGeneration>(
update.generation_id_, td_api::make_object<td_api::error>(400, r_stat.error().message().str())));
}
}
+ void on_update_autorization_state(td_api::object_ptr<td_api::AuthorizationState> &&state) {
+ authorization_state_ = std::move(state);
+ switch (authorization_state_->get_id()) {
+ case td_api::authorizationStateWaitTdlibParameters::ID: {
+ auto request = td_api::make_object<td_api::setTdlibParameters>();
+ request->use_test_dc_ = use_test_dc_;
+ request->use_message_database_ = true;
+ request->use_chat_info_database_ = true;
+ request->use_secret_chats_ = true;
+ request->api_id_ = api_id_;
+ request->api_hash_ = api_hash_;
+ request->system_language_code_ = "en";
+ request->device_model_ = "Desktop";
+ request->application_version_ = "1.0";
+ send_request(
+ td_api::make_object<td_api::setOption>("use_pfs", td_api::make_object<td_api::optionValueBoolean>(true)));
+ send_request(std::move(request));
+ break;
+ }
+ case td_api::authorizationStateReady::ID:
+ LOG(INFO) << "Logged in";
+ break;
+ case td_api::authorizationStateClosed::ID:
+ LOG(WARNING) << "Td closed";
+ td_client_.reset();
+ if (!close_flag_) {
+ create_td("ClientActor3");
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ static char get_delimiter(Slice str) {
+ FlatHashSet<char> chars;
+ for (auto c : trim(str)) {
+ if (!is_alnum(c) && c != '-' && c != '@' && c != '.' && c != '/' && c != '\0' && static_cast<uint8>(c) <= 127) {
+ chars.insert(c);
+ }
+ }
+ if (chars.empty()) {
+ return ' ';
+ }
+ if (chars.size() == 1) {
+ return *chars.begin();
+ }
+ LOG(ERROR) << "Failed to determine delimiter in \"" << str << '"';
+ return ' ';
+ }
+
+ static vector<Slice> autosplit(Slice str) {
+ return full_split(trim(str), get_delimiter(str));
+ }
+
+ static vector<string> autosplit_str(Slice str) {
+ return transform(autosplit(str), [](Slice slice) { return slice.str(); });
+ }
+
int64 as_chat_id(Slice str) const {
str = trim(str);
+ if (str == "me") {
+ return my_id_;
+ }
+ if (str == ".") {
+ return opened_chat_id_;
+ }
if (str[0] == '@') {
- auto it = username_to_user_id_.find(to_lower(str.substr(1)));
+ str.remove_prefix(1);
+ }
+ if (is_alpha(str[0])) {
+ auto it = username_to_user_id_.find(to_lower(str));
if (it != username_to_user_id_.end()) {
return it->second;
}
- auto it2 = username_to_supergroup_id.find(to_lower(str.substr(1)));
- if (it2 != username_to_supergroup_id.end()) {
+ auto it2 = username_to_supergroup_id_.find(to_lower(str));
+ if (it2 != username_to_supergroup_id_.end()) {
auto supergroup_id = it2->second;
return static_cast<int64>(-1000'000'000'000ll) - supergroup_id;
}
LOG(ERROR) << "Can't resolve " << str;
return 0;
}
- if (str == "me") {
- return my_id_;
- }
return to_integer<int64>(str);
}
- vector<int64> as_chat_ids(Slice chat_ids, char delimiter = ' ') const {
- return transform(full_split(chat_ids, delimiter), [this](Slice str) { return as_chat_id(str); });
+ static int32 as_chat_filter_id(Slice str) {
+ return to_integer<int32>(trim(str));
+ }
+
+ static vector<int32> as_chat_filter_ids(Slice chat_filter_ids) {
+ return transform(autosplit(chat_filter_ids), as_chat_filter_id);
+ }
+
+ static td_api::object_ptr<td_api::ChatList> as_chat_list(string chat_list) {
+ if (!chat_list.empty() && chat_list.back() == 'a') {
+ return td_api::make_object<td_api::chatListArchive>();
+ }
+ if (chat_list.find('-') != string::npos) {
+ return td_api::make_object<td_api::chatListFilter>(as_chat_filter_id(chat_list.substr(chat_list.find('-') + 1)));
+ }
+ return td_api::make_object<td_api::chatListMain>();
+ }
+
+ vector<int64> as_chat_ids(Slice chat_ids) const {
+ return transform(autosplit(chat_ids), [this](Slice str) { return as_chat_id(str); });
}
static int64 as_message_id(Slice str) {
str = trim(str);
if (!str.empty() && str.back() == 's') {
- return to_integer<int32>(str) << 20;
+ return to_integer<int64>(str) << 20;
}
return to_integer<int64>(str);
}
- static vector<int64> as_message_ids(Slice message_ids, char delimiter = ' ') {
- return transform(full_split(message_ids, delimiter), as_message_id);
+ static vector<int64> as_message_ids(Slice message_ids) {
+ return transform(autosplit(message_ids), as_message_id);
+ }
+
+ static int64 as_message_thread_id(Slice str) {
+ return as_message_id(str);
+ }
+
+ td_api::object_ptr<td_api::MessageSender> as_message_sender(Slice sender_id) const {
+ sender_id = trim(sender_id);
+ if (sender_id.empty() || sender_id[0] != '-') {
+ return td_api::make_object<td_api::messageSenderUser>(as_user_id(sender_id));
+ } else {
+ return td_api::make_object<td_api::messageSenderChat>(as_chat_id(sender_id));
+ }
+ }
+
+ static int32 as_button_id(Slice str) {
+ return to_integer<int32>(trim(str));
+ }
+
+ static td_api::object_ptr<td_api::StickerFormat> as_sticker_format(string sticker_format) {
+ if (!sticker_format.empty() && sticker_format.back() == 'a') {
+ return td_api::make_object<td_api::stickerFormatTgs>();
+ }
+ if (!sticker_format.empty() && sticker_format.back() == 'v') {
+ return td_api::make_object<td_api::stickerFormatWebm>();
+ }
+ return td_api::make_object<td_api::stickerFormatWebp>();
+ }
+
+ static td_api::object_ptr<td_api::StickerType> as_sticker_type(string sticker_type) {
+ if (!sticker_type.empty() && sticker_type.back() == 'e') {
+ return td_api::make_object<td_api::stickerTypeCustomEmoji>();
+ }
+ if (!sticker_type.empty() && sticker_type.back() == 'm') {
+ return td_api::make_object<td_api::stickerTypeMask>();
+ }
+ return Random::fast_bool() ? nullptr : td_api::make_object<td_api::stickerTypeRegular>();
+ }
+
+ static td_api::object_ptr<td_api::maskPosition> as_mask_position(string sticker_type) {
+ if (!sticker_type.empty() && sticker_type.back() == 'm') {
+ auto position = td_api::make_object<td_api::maskPosition>(td_api::make_object<td_api::maskPointEyes>(),
+ Random::fast(-5, 5), Random::fast(-5, 5), 1.0);
+ return Random::fast_bool() ? nullptr : std::move(position);
+ }
+ return nullptr;
+ }
+
+ static int32 as_limit(Slice str, int32 default_limit = 10) {
+ if (str.empty()) {
+ return default_limit;
+ }
+ return to_integer<int32>(trim(str));
}
- int32 as_user_id(Slice str) const {
+ int64 as_user_id(Slice str) const {
str = trim(str);
+ if (str == "me") {
+ return my_id_;
+ }
if (str[0] == '@') {
- auto it = username_to_user_id_.find(to_lower(str.substr(1)));
+ str.remove_prefix(1);
+ }
+ if (is_alpha(str[0])) {
+ auto it = username_to_user_id_.find(to_lower(str));
if (it != username_to_user_id_.end()) {
return it->second;
}
LOG(ERROR) << "Can't find user " << str;
return 0;
}
- if (str == "me") {
- return my_id_;
+ return to_integer<int64>(str);
+ }
+
+ vector<int64> as_user_ids(Slice user_ids) const {
+ return transform(autosplit(user_ids), [this](Slice str) { return as_user_id(str); });
+ }
+
+ static int64 as_basic_group_id(Slice str) {
+ str = trim(str);
+ auto result = to_integer<int64>(str);
+ if (result < 0) {
+ return -result;
}
- return to_integer<int32>(str);
+ return result;
}
- vector<int32> as_user_ids(Slice user_ids, char delimiter = ' ') const {
- return transform(full_split(user_ids, delimiter), [this](Slice str) { return as_user_id(str); });
+ int64 as_supergroup_id(Slice str) const {
+ str = trim(str);
+ if (str[0] == '@') {
+ str.remove_prefix(1);
+ }
+ if (is_alpha(str[0])) {
+ auto it = username_to_supergroup_id_.find(to_lower(str));
+ if (it == username_to_supergroup_id_.end()) {
+ return 0;
+ }
+ return it->second;
+ }
+ auto result = to_integer<int64>(str);
+ auto shift = static_cast<int64>(-1000000000000ll);
+ if (result <= shift) {
+ return shift - result;
+ }
+ return result;
}
- static int32 as_file_id(string str) {
- return to_integer<int32>(trim(std::move(str)));
+ static int32 as_secret_chat_id(Slice str) {
+ str = trim(str);
+ auto result = to_integer<int64>(str);
+ auto shift = static_cast<int64>(-2000000000000ll);
+ if (result <= shift + std::numeric_limits<int32>::max()) {
+ return static_cast<int32>(result - shift);
+ }
+ return static_cast<int32>(result);
}
- static int32 as_call_id(string str) {
- return to_integer<int32>(trim(std::move(str)));
+ static int32 as_file_id(Slice str) {
+ return to_integer<int32>(trim(str));
+ }
+
+ static td_api::object_ptr<td_api::InputFile> as_input_file_id(Slice str) {
+ return td_api::make_object<td_api::inputFileId>(as_file_id(str));
+ }
+
+ static td_api::object_ptr<td_api::InputFile> as_local_file(string path) {
+ return td_api::make_object<td_api::inputFileLocal>(trim(std::move(path)));
+ }
+
+ static td_api::object_ptr<td_api::InputFile> as_remote_file(string id) {
+ return td_api::make_object<td_api::inputFileRemote>(trim(std::move(id)));
+ }
+
+ static td_api::object_ptr<td_api::InputFile> as_generated_file(string original_path, string conversion,
+ int64 expected_size = 0) {
+ return td_api::make_object<td_api::inputFileGenerated>(trim(std::move(original_path)), trim(std::move(conversion)),
+ expected_size);
}
- static td_api::object_ptr<td_api::InputFile> as_input_file_id(string str) {
- return make_tl_object<td_api::inputFileId>(as_file_id(str));
+ static td_api::object_ptr<td_api::InputFile> as_input_file(Slice str) {
+ str = trim(str);
+ if ((str.size() >= 20 && is_base64url(str)) || begins_with(str, "http")) {
+ return as_remote_file(str.str());
+ }
+ auto r_file_id = to_integer_safe<int32>(str);
+ if (r_file_id.is_ok()) {
+ return as_input_file_id(str);
+ }
+ if (str.find(';') < str.size()) {
+ auto res = split(str, ';');
+ return as_generated_file(res.first.str(), res.second.str());
+ }
+ return as_local_file(str.str());
}
- static tl_object_ptr<td_api::InputFile> as_local_file(string path) {
- return make_tl_object<td_api::inputFileLocal>(trim(std::move(path)));
+ static td_api::object_ptr<td_api::inputThumbnail> as_input_thumbnail(td_api::object_ptr<td_api::InputFile> input_file,
+ int32 width = 0, int32 height = 0) {
+ return td_api::make_object<td_api::inputThumbnail>(std::move(input_file), width, height);
}
- static tl_object_ptr<td_api::InputFile> as_generated_file(string original_path, string conversion,
- int32 expected_size = 0) {
- return make_tl_object<td_api::inputFileGenerated>(trim(original_path), trim(conversion), expected_size);
+ static td_api::object_ptr<td_api::inputThumbnail> as_input_thumbnail(const string &thumbnail, int32 width = 0,
+ int32 height = 0) {
+ return as_input_thumbnail(as_input_file(thumbnail), width, height);
}
- static tl_object_ptr<td_api::location> as_location(string latitude, string longitude) {
- return make_tl_object<td_api::location>(to_double(latitude), to_double(longitude));
+ static td_api::object_ptr<td_api::inputThumbnail> as_input_thumbnail(const string &original_path,
+ const string &conversion, int32 width = 0,
+ int32 height = 0) {
+ return as_input_thumbnail(as_generated_file(original_path, conversion), width, height);
+ }
+
+ struct CallId {
+ int32 call_id = 0;
+
+ operator int32() const {
+ return call_id;
+ }
+ };
+
+ void get_args(string &args, CallId &arg) const {
+ arg.call_id = to_integer<int32>(trim(args));
+ }
+
+ struct GroupCallId {
+ int32 group_call_id = 0;
+
+ operator int32() const {
+ return group_call_id;
+ }
+ };
+
+ void get_args(string &args, GroupCallId &arg) const {
+ arg.group_call_id = to_integer<int32>(trim(args));
+ }
+
+ static int32 as_proxy_id(string str) {
+ return to_integer<int32>(trim(std::move(str)));
+ }
+
+ static td_api::object_ptr<td_api::location> as_location(const string &latitude, const string &longitude,
+ const string &accuracy) {
+ if (trim(latitude).empty() && trim(longitude).empty()) {
+ return nullptr;
+ }
+ return td_api::make_object<td_api::location>(to_double(latitude), to_double(longitude), to_double(accuracy));
+ }
+
+ static td_api::object_ptr<td_api::ReactionType> as_reaction_type(Slice type) {
+ type = trim(type);
+ if (type.empty()) {
+ return nullptr;
+ }
+ auto r_custom_emoji_id = to_integer_safe<int64>(type);
+ if (r_custom_emoji_id.is_ok()) {
+ return td_api::make_object<td_api::reactionTypeCustomEmoji>(r_custom_emoji_id.ok());
+ }
+ return td_api::make_object<td_api::reactionTypeEmoji>(type.str());
}
static bool as_bool(string str) {
- str = to_lower(str);
+ str = to_lower(trim(str));
return str == "true" || str == "1";
}
template <class T>
- static vector<T> to_integers(Slice ids_string, char delimiter = ' ') {
- return transform(full_split(ids_string, delimiter), to_integer<T>);
+ static vector<T> to_integers(Slice integers) {
+ return transform(transform(autosplit(integers), trim<Slice>), to_integer<T>);
+ }
+
+ static void get_args(string &args, string &arg) {
+ if (&args != &arg) {
+ arg = std::move(args);
+ }
+ }
+
+ static void get_args(string &args, bool &arg) {
+ arg = as_bool(args);
+ }
+
+ struct SearchQuery {
+ int32 limit = 0;
+ string query;
+ };
+
+ static void get_args(string &args, SearchQuery &arg) {
+ string limit;
+ std::tie(limit, arg.query) = split(trim(args));
+ auto r_limit = to_integer_safe<int32>(limit);
+ if (r_limit.is_ok() && r_limit.ok() > 0) {
+ arg.limit = r_limit.ok();
+ } else {
+ arg.limit = 10;
+ arg.query = std::move(args);
+ }
+ args.clear();
+ }
+
+ static void get_args(string &args, int32 &arg) {
+ arg = to_integer<int32>(args);
+ }
+
+ static void get_args(string &args, int64 &arg) {
+ arg = to_integer<int64>(args);
+ }
+
+ struct ChatId {
+ int64 chat_id = 0;
+
+ operator int64() const {
+ return chat_id;
+ }
+ };
+
+ void get_args(string &args, ChatId &arg) const {
+ arg.chat_id = as_chat_id(args);
+ }
+
+ struct MessageId {
+ int64 message_id = 0;
+
+ operator int64() const {
+ return message_id;
+ }
+ };
+
+ void get_args(string &args, MessageId &arg) const {
+ arg.message_id = as_message_id(args);
+ }
+
+ struct MessageThreadId {
+ int64 message_thread_id = 0;
+
+ operator int64() const {
+ return message_thread_id;
+ }
+ };
+
+ void get_args(string &args, MessageThreadId &arg) const {
+ arg.message_thread_id = as_message_thread_id(args);
+ }
+
+ struct UserId {
+ int64 user_id = 0;
+
+ operator int64() const {
+ return user_id;
+ }
+ };
+
+ void get_args(string &args, UserId &arg) const {
+ arg.user_id = as_user_id(args);
}
- void on_result(uint64 id, tl_object_ptr<td_api::Object> result) {
- if (id > 0 && GET_VERBOSITY_LEVEL() < VERBOSITY_NAME(td_requests)) {
- LOG(ERROR) << "on_result [id=" << id << "] " << to_string(result);
+ struct FileId {
+ int32 file_id = 0;
+
+ operator int32() const {
+ return file_id;
+ }
+ };
+
+ void get_args(string &args, FileId &arg) const {
+ arg.file_id = as_file_id(args);
+ }
+
+ struct InputInvoice {
+ int64 chat_id = 0;
+ int64 message_id = 0;
+ string invoice_name;
+
+ operator td_api::object_ptr<td_api::InputInvoice>() const {
+ if (invoice_name.empty()) {
+ return td_api::make_object<td_api::inputInvoiceMessage>(chat_id, message_id);
+ } else {
+ return td_api::make_object<td_api::inputInvoiceName>(invoice_name);
+ }
+ }
+ };
+
+ void get_args(string &args, InputInvoice &arg) const {
+ if (args.size() > 1 && args[0] == '#') {
+ arg.invoice_name = args;
+ } else {
+ string chat_id;
+ string message_id;
+ std::tie(chat_id, message_id) = split(args, get_delimiter(args));
+ arg.chat_id = as_chat_id(chat_id);
+ arg.message_id = as_message_id(message_id);
+ }
+ }
+
+ template <class FirstType, class SecondType, class... Types>
+ void get_args(string &args, FirstType &first_arg, SecondType &second_arg, Types &...other_args) const {
+ string arg;
+ std::tie(arg, args) = split(args);
+ get_args(arg, first_arg);
+ get_args(args, second_arg, other_args...);
+ }
+
+ void on_result(uint64 generation, uint64 id, td_api::object_ptr<td_api::Object> result) {
+ auto result_str = to_string(result);
+ if (result != nullptr) {
+ switch (result->get_id()) {
+ case td_api::stickerSets::ID: {
+ auto sticker_sets = static_cast<const td_api::stickerSets *>(result.get());
+ result_str = PSTRING() << "StickerSets { total_count = " << sticker_sets->total_count_
+ << ", count = " << sticker_sets->sets_.size();
+ for (auto &sticker_set : sticker_sets->sets_) {
+ result_str += PSTRING() << ", " << sticker_set->name_;
+ }
+ result_str += " }";
+ break;
+ }
+ case td_api::trendingStickerSets::ID: {
+ auto sticker_sets = static_cast<const td_api::trendingStickerSets *>(result.get());
+ result_str = PSTRING() << "TrendingStickerSets { is_premium = " << sticker_sets->is_premium_
+ << ", total_count = " << sticker_sets->total_count_
+ << ", count = " << sticker_sets->sets_.size();
+ for (auto &sticker_set : sticker_sets->sets_) {
+ result_str += PSTRING() << ", " << sticker_set->name_;
+ }
+ result_str += " }";
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (id > 0 && combined_log.get_first_verbosity_level() < get_log_tag_verbosity_level("td_requests")) {
+ LOG(ERROR) << "Receive result [" << generation << "][id=" << id << "] " << result_str;
}
auto as_json_str = json_encode<std::string>(ToJson(result));
- // LOG(INFO) << "on_result [id=" << id << "] " << as_json_str;
- auto copy_as_json_str = as_json_str;
- auto as_json_value = json_decode(copy_as_json_str).move_as_ok();
- td_api::object_ptr<td_api::Object> object;
- from_json(object, as_json_value).ensure();
- CHECK(object != nullptr);
- auto as_json_str2 = json_encode<std::string>(ToJson(object));
- CHECK(as_json_str == as_json_str2) << "\n" << tag("a", as_json_str) << "\n" << tag("b", as_json_str2);
- // LOG(INFO) << "on_result [id=" << id << "] " << as_json_str;
+ // LOG(INFO) << "Receive result [" << generation << "][id=" << id << "] " << as_json_str;
+ //auto copy_as_json_str = as_json_str;
+ //auto as_json_value = json_decode(copy_as_json_str).move_as_ok();
+ //td_api::object_ptr<td_api::Object> object;
+ //from_json(object, as_json_value).ensure();
+ //CHECK(object != nullptr);
+ //auto as_json_str2 = json_encode<std::string>(ToJson(object));
+ //LOG_CHECK(as_json_str == as_json_str2) << "\n" << tag("a", as_json_str) << "\n" << tag("b", as_json_str2);
+ // LOG(INFO) << "Receive result [" << generation << "][id=" << id << "] " << as_json_str;
+
+ if (generation != generation_) {
+ LOG(INFO) << "Drop received from previous Client " << result_str;
+ return;
+ }
int32 result_id = result == nullptr ? 0 : result->get_id();
@@ -544,6 +989,11 @@ class CliClient final : public Actor {
case td_api::updateFileGenerationStart::ID:
on_file_generation_start(*static_cast<const td_api::updateFileGenerationStart *>(result.get()));
break;
+ case td_api::updateAuthorizationState::ID:
+ LOG(WARNING) << result_str;
+ on_update_autorization_state(
+ std::move(static_cast<td_api::updateAuthorizationState *>(result.get())->authorization_state_));
+ break;
case td_api::updateChatLastMessage::ID: {
auto message = static_cast<const td_api::updateChatLastMessage *>(result.get())->last_message_.get();
if (message != nullptr && message->content_->get_id() == td_api::messageText::ID) {
@@ -551,22 +1001,46 @@ class CliClient final : public Actor {
}
break;
}
+ case td_api::updateNewMessage::ID: {
+ auto message = static_cast<const td_api::updateNewMessage *>(result.get())->message_.get();
+ if (message != nullptr && message->content_->get_id() == td_api::messageText::ID) {
+ auto chat_id = message->chat_id_;
+ auto text = static_cast<const td_api::messageText *>(message->content_.get())->text_->text_;
+ if (text == "/start" && !message->is_outgoing_ && use_test_dc_) {
+ on_cmd(PSTRING() << "sm " << chat_id << " Hi!");
+ }
+ }
+ break;
+ }
+ case td_api::file::ID:
+ on_get_file(*static_cast<const td_api::file *>(result.get()));
+ break;
+ case td_api::updateFile::ID:
+ on_get_file(*static_cast<const td_api::updateFile *>(result.get())->file_);
+ break;
+ case td_api::updateConnectionState::ID:
+ LOG(WARNING) << result_str;
+ break;
+ default:
+ break;
}
}
- void on_error(uint64 id, tl_object_ptr<td_api::error> error) {
- auto current_verbosity_level = GET_VERBOSITY_LEVEL();
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO));
- LOG(INFO) << "on_error [id=" << id << "] " << to_string(error);
- SET_VERBOSITY_LEVEL(current_verbosity_level);
+ void on_error(uint64 generation, uint64 id, td_api::object_ptr<td_api::error> error) {
+ if (id > 0 && combined_log.get_first_verbosity_level() < get_log_tag_verbosity_level("td_requests")) {
+ LOG(ERROR) << "Receive error [" << generation << "][id=" << id << "] " << to_string(error);
+ }
}
- void on_closed() {
- LOG(INFO) << "on_closed";
- ready_to_stop_ = true;
- if (close_flag_) {
- yield();
- return;
+ void on_closed(uint64 generation) {
+ LOG(WARNING) << "Td with generation " << generation << " is closed";
+ closed_td_++;
+ if (closed_td_ == generation_) {
+ LOG(WARNING) << "Ready to stop";
+ ready_to_stop_ = true;
+ if (close_flag_) {
+ yield();
+ }
}
}
@@ -578,31 +1052,23 @@ class CliClient final : public Actor {
LOG(WARNING) << "QUIT";
close_flag_ = true;
dump_memory_usage();
- td_.reset();
-#if TD_WINDOWS
- stdin_reader_.reset();
-#else
+ td_client_.reset();
+ Scheduler::unsubscribe(stdin_.get_poll_info().get_pollable_fd_ref());
is_stdin_reader_stopped_ = true;
-#endif
yield();
}
-#ifdef USE_READLINE
- Fd stdin_;
-#else
- using StreamConnection = BufferedFd<Fd>;
- StreamConnection stdin_;
-#endif
+ BufferedStdin stdin_;
static CliClient *instance_;
#ifdef USE_READLINE
/* Callback function called for each line when accept-line executed, EOF
* seen, or EOF character read. This sets a flag and returns; it could
* also call exit. */
- static void cb_linehandler(char *line) {
+ static void static_add_cmd(char *line) {
/* Can use ^D (stty eof) to exit. */
if (line == nullptr) {
- LOG(FATAL) << "closed";
+ LOG(FATAL) << "Closed";
return;
}
if (*line) {
@@ -611,59 +1077,110 @@ class CliClient final : public Actor {
instance_->add_cmd(line);
rl_free(line);
}
+ static int static_getc(FILE *) {
+ return instance_->stdin_getc();
+ }
#endif
- unique_ptr<TdCallback> make_td_callback() {
- class TdCallbackImpl : public TdCallback {
+ uint64 generation_ = 0;
+ uint64 closed_td_ = 0;
+ void create_td(Slice name) {
+ if (ready_to_stop_) {
+ return;
+ }
+
+ LOG(WARNING) << "Creating new Td " << name << " with generation " << generation_ + 1;
+ class TdCallbackImpl final : public TdCallback {
public:
- explicit TdCallbackImpl(CliClient *client) : client_(client) {
+ TdCallbackImpl(CliClient *client, uint64 generation) : client_(client), generation_(generation) {
}
- void on_result(uint64 id, tl_object_ptr<td_api::Object> result) override {
- client_->on_result(id, std::move(result));
+ void on_result(uint64 id, td_api::object_ptr<td_api::Object> result) final {
+ client_->on_result(generation_, id, std::move(result));
}
- void on_error(uint64 id, tl_object_ptr<td_api::error> error) override {
- client_->on_error(id, std::move(error));
+ void on_error(uint64 id, td_api::object_ptr<td_api::error> error) final {
+ client_->on_error(generation_, id, std::move(error));
}
- void on_closed() override {
- client_->on_closed();
+ TdCallbackImpl(const TdCallbackImpl &) = delete;
+ TdCallbackImpl &operator=(const TdCallbackImpl &) = delete;
+ TdCallbackImpl(TdCallbackImpl &&) = delete;
+ TdCallbackImpl &operator=(TdCallbackImpl &&) = delete;
+ ~TdCallbackImpl() final {
+ client_->on_closed(generation_);
}
private:
CliClient *client_;
+ uint64 generation_;
};
- return make_unique<TdCallbackImpl>(this);
+
+ ClientActor::Options options;
+ options.net_query_stats = net_query_stats_;
+
+ td_client_ = create_actor<ClientActor>(name, make_unique<TdCallbackImpl>(this, ++generation_), std::move(options));
+
+ if (get_chat_list_) {
+ send_request(td_api::make_object<td_api::getChats>(nullptr, 10000));
+ }
+ if (disable_network_) {
+ send_request(td_api::make_object<td_api::setNetworkType>(td_api::make_object<td_api::networkTypeNone>()));
+ }
}
void init_td() {
close_flag_ = false;
ready_to_stop_ = false;
+ generation_ = 0;
+ closed_td_ = 0;
- td_ = create_actor<ClientActor>("ClientActor1", make_td_callback());
- td_ = create_actor<ClientActor>("ClientActor2", make_td_callback());
- ready_to_stop_ = false;
+ create_td("ClientActor1");
+
+ bool test_init = false;
+ if (test_init) {
+ create_td("ClientActor2");
+
+ for (int i = 0; i < 4; i++) {
+ send_closure_later(td_client_, &ClientActor::request, std::numeric_limits<uint64>::max(),
+ td_api::make_object<td_api::setAlarm>(0.001 + 1000 * (i / 2)));
+ }
- auto bad_parameters = td_api::make_object<td_api::tdlibParameters>();
- bad_parameters->database_directory_ = "/..";
- bad_parameters->api_id_ = api_id_;
- bad_parameters->api_hash_ = api_hash_;
- send_request(td_api::make_object<td_api::setTdlibParameters>(std::move(bad_parameters)));
+ send_request(td_api::make_object<td_api::getStorageStatistics>(10));
+ send_request(td_api::make_object<td_api::getStorageStatisticsFast>());
- auto parameters = td_api::make_object<td_api::tdlibParameters>();
- parameters->use_test_dc_ = use_test_dc_;
- parameters->use_message_database_ = true;
- parameters->use_secret_chats_ = true;
- parameters->api_id_ = api_id_;
- parameters->api_hash_ = api_hash_;
- parameters->system_language_code_ = "en";
- parameters->device_model_ = "Desktop";
- parameters->system_version_ = "Unknown";
- parameters->application_version_ = "tg_cli";
- send_request(td_api::make_object<td_api::setTdlibParameters>(std::move(parameters)));
- send_request(td_api::make_object<td_api::checkDatabaseEncryptionKey>());
+ send_request(td_api::make_object<td_api::getTextEntities>(
+ "@telegram /test_command https://telegram.org telegram.me @gif @test"));
- for (int i = 0; i < 4; i++) {
- send_closure_later(td_, &ClientActor::request, std::numeric_limits<uint64>::max(),
- td_api::make_object<td_api::setAlarm>(0.001 + 1000 * (i / 2)));
+ send_request(
+ td_api::make_object<td_api::setOption>("xxx", td_api::make_object<td_api::optionValueBoolean>(true)));
+ send_request(td_api::make_object<td_api::setOption>("xxx", td_api::make_object<td_api::optionValueInteger>(1)));
+ send_request(td_api::make_object<td_api::setOption>("xxx", td_api::make_object<td_api::optionValueString>("2")));
+ send_request(td_api::make_object<td_api::setOption>("xxx", td_api::make_object<td_api::optionValueEmpty>()));
+
+ send_request(td_api::make_object<td_api::getOption>("use_pfs"));
+ send_request(td_api::make_object<td_api::setOption>(
+ "use_pfs", td_api::make_object<td_api::optionValueBoolean>(std::time(nullptr) / 86400 % 2 == 0)));
+ send_request(td_api::make_object<td_api::setOption>("notification_group_count_max",
+ td_api::make_object<td_api::optionValueInteger>(1)));
+ send_request(td_api::make_object<td_api::setOption>("use_storage_optimizer",
+ td_api::make_object<td_api::optionValueBoolean>(false)));
+ send_request(td_api::make_object<td_api::setOption>(
+ "use_pfs", td_api::make_object<td_api::optionValueBoolean>(std::time(nullptr) / 86400 % 2 == 0)));
+ send_request(td_api::make_object<td_api::setOption>("disable_contact_registered_notifications",
+ td_api::make_object<td_api::optionValueBoolean>(true)));
+
+ send_request(td_api::make_object<td_api::setNetworkType>(td_api::make_object<td_api::networkTypeWiFi>()));
+ send_request(td_api::make_object<td_api::getNetworkStatistics>());
+ send_request(td_api::make_object<td_api::getCountryCode>());
+ send_request(
+ td_api::make_object<td_api::addProxy>("1.1.1.1", 1111, true, td_api::make_object<td_api::proxyTypeSocks5>()));
+ send_request(td_api::make_object<td_api::addProxy>("1.1.1.1", 1112, false,
+ td_api::make_object<td_api::proxyTypeSocks5>()));
+ send_request(td_api::make_object<td_api::pingProxy>(0));
+
+ auto bad_request = td_api::make_object<td_api::setTdlibParameters>();
+ bad_request->database_directory_ = "/..";
+ bad_request->api_id_ = api_id_;
+ bad_request->api_hash_ = api_hash_;
+ send_request(std::move(bad_request));
}
}
@@ -672,71 +1189,14 @@ class CliClient final : public Actor {
init_td();
-#if TD_WINDOWS
- auto stdin_id = Scheduler::instance()->sched_count() - 1;
- class StdinReader : public Actor {
- public:
- explicit StdinReader(ActorShared<CliClient> parent) : parent_(std::move(parent)) {
- }
- void start_up() override {
- stdin_ = &Fd::Stdin();
- set_timeout_in(0.001);
- }
- void timeout_expired() override {
- std::array<char, 100> buf;
- auto t_res = stdin_->read(MutableSlice(buf.data(), buf.size()));
- if (t_res.is_error()) {
- LOG(FATAL) << "Can't read from stdin";
- }
- auto res = t_res.ok();
- VLOG(fd) << res << " " << string(buf.data(), res);
- data_.append(string(buf.data(), res));
- process();
- set_timeout_in(0.05);
- }
-
- private:
- Fd *stdin_ = nullptr;
- string data_;
- ActorShared<CliClient> parent_;
- void process() {
- while (true) {
- auto pos = data_.find('\n');
- if (pos == string::npos) {
- break;
- }
- auto cmd = string(data_.data(), pos);
- while (!cmd.empty() && cmd.back() == '\r') {
- cmd.pop_back();
- }
- send_closure(parent_, &CliClient::on_cmd, cmd);
- data_ = data_.substr(pos + 1);
- }
- }
- };
- stdin_reader_ = create_actor_on_scheduler<StdinReader>("stdin_reader", stdin_id, actor_shared(this, 1));
-#else
- Fd::Stdin().set_is_blocking(false).ensure();
#ifdef USE_READLINE
deactivate_readline();
- rl_callback_handler_install(prompt, cb_linehandler);
+ rl_getc_function = static_getc;
+ rl_callback_handler_install(prompt, static_add_cmd);
rl_attempted_completion_function = tg_cli_completion;
reactivate_readline();
-
- stdin_ = Fd::Stdin().clone();
-#else
- stdin_ = StreamConnection(Fd::Stdin().clone());
#endif
- stdin_.get_fd().set_observer(this);
- subscribe(stdin_, Fd::Read);
-#endif
-
- if (get_chat_list_) {
- send_request(make_tl_object<td_api::getChats>(std::numeric_limits<int64>::max(), 0, 100));
- }
- if (disable_network_) {
- send_request(make_tl_object<td_api::setNetworkType>(make_tl_object<td_api::networkTypeNone>()));
- }
+ Scheduler::subscribe(stdin_.get_poll_info().extract_pollable_fd(this), PollFlags::Read());
}
#ifndef USE_READLINE
size_t buffer_pos_ = 0;
@@ -751,98 +1211,143 @@ class CliClient final : public Actor {
if (!data.empty() && data[data.size() - 1] == '\r') {
data.truncate(data.size() - 1);
}
- buffer->cut_head(1);
+ buffer->advance(1);
buffer_pos_ = 0;
return std::move(data);
}
#endif
- static tl_object_ptr<td_api::formattedText> as_caption(string caption,
- vector<td_api::object_ptr<td_api::textEntity>> entities = {}) {
- if (entities.empty()) {
- auto parsed_caption =
- execute(make_tl_object<td_api::parseTextEntities>(caption, make_tl_object<td_api::textParseModeMarkdown>()));
- if (parsed_caption->get_id() == td_api::formattedText::ID) {
- return td_api::move_object_as<td_api::formattedText>(parsed_caption);
+ static td_api::object_ptr<td_api::formattedText> as_formatted_text(
+ const string &text, vector<td_api::object_ptr<td_api::textEntity>> entities = {}) {
+ if (entities.empty() && !text.empty()) {
+ auto parsed_text = execute(
+ td_api::make_object<td_api::parseTextEntities>(text, td_api::make_object<td_api::textParseModeMarkdown>(2)));
+ if (parsed_text->get_id() == td_api::formattedText::ID) {
+ return td_api::move_object_as<td_api::formattedText>(parsed_text);
}
}
- return make_tl_object<td_api::formattedText>(caption, std::move(entities));
+ return td_api::make_object<td_api::formattedText>(text, std::move(entities));
}
- tl_object_ptr<td_api::NotificationSettingsScope> get_notification_settings_scope(Slice scope) const {
- if (scope == "users" || scope == "privateChats") {
- return make_tl_object<td_api::notificationSettingsScopePrivateChats>();
- } else if (scope == "chats" || scope == "groups" || scope == "groupChats") {
- return make_tl_object<td_api::notificationSettingsScopeBasicGroupChats>();
- } else if (scope == "all" || scope == "dialogs") {
- return make_tl_object<td_api::notificationSettingsScopeAllChats>();
- } else {
- return make_tl_object<td_api::notificationSettingsScopeChat>(as_chat_id(scope));
+ static td_api::object_ptr<td_api::formattedText> as_caption(
+ const string &caption, vector<td_api::object_ptr<td_api::textEntity>> entities = {}) {
+ return as_formatted_text(caption, std::move(entities));
+ }
+
+ static td_api::object_ptr<td_api::NotificationSettingsScope> as_notification_settings_scope(Slice scope) {
+ if (scope.empty()) {
+ return nullptr;
+ }
+ if (scope == "channels" || scope == "ch") {
+ return td_api::make_object<td_api::notificationSettingsScopeChannelChats>();
}
+ if (scope == "chats" || scope == "groups" || as_bool(scope.str())) {
+ return td_api::make_object<td_api::notificationSettingsScopeGroupChats>();
+ }
+ return td_api::make_object<td_api::notificationSettingsScopePrivateChats>();
}
- static tl_object_ptr<td_api::UserPrivacySetting> get_user_privacy_setting(MutableSlice setting) {
+ static td_api::object_ptr<td_api::UserPrivacySetting> as_user_privacy_setting(MutableSlice setting) {
setting = trim(setting);
to_lower_inplace(setting);
if (setting == "invite") {
- return make_tl_object<td_api::userPrivacySettingAllowChatInvites>();
+ return td_api::make_object<td_api::userPrivacySettingAllowChatInvites>();
}
if (setting == "status") {
- return make_tl_object<td_api::userPrivacySettingShowStatus>();
+ return td_api::make_object<td_api::userPrivacySettingShowStatus>();
}
if (setting == "call") {
- return make_tl_object<td_api::userPrivacySettingAllowCalls>();
+ return td_api::make_object<td_api::userPrivacySettingAllowCalls>();
+ }
+ if (setting == "p2p") {
+ return td_api::make_object<td_api::userPrivacySettingAllowPeerToPeerCalls>();
+ }
+ if (setting == "forward") {
+ return td_api::make_object<td_api::userPrivacySettingShowLinkInForwardedMessages>();
+ }
+ if (setting == "photo") {
+ return td_api::make_object<td_api::userPrivacySettingShowProfilePhoto>();
+ }
+ if (setting == "phone_number") {
+ return td_api::make_object<td_api::userPrivacySettingShowPhoneNumber>();
+ }
+ if (setting == "find") {
+ return td_api::make_object<td_api::userPrivacySettingAllowFindingByPhoneNumber>();
}
return nullptr;
}
- static tl_object_ptr<td_api::SearchMessagesFilter> get_search_messages_filter(MutableSlice filter) {
+ td_api::object_ptr<td_api::userPrivacySettingRules> as_user_privacy_setting_rules(Slice allow, Slice ids) const {
+ vector<td_api::object_ptr<td_api::UserPrivacySettingRule>> rules;
+ if (allow == "c" || allow == "contacts") {
+ rules.push_back(td_api::make_object<td_api::userPrivacySettingRuleAllowContacts>());
+ } else if (allow == "users") {
+ rules.push_back(td_api::make_object<td_api::userPrivacySettingRuleAllowUsers>(as_user_ids(ids)));
+ } else if (allow == "chats") {
+ rules.push_back(td_api::make_object<td_api::userPrivacySettingRuleAllowChatMembers>(as_chat_ids(ids)));
+ } else if (as_bool(allow.str())) {
+ rules.push_back(td_api::make_object<td_api::userPrivacySettingRuleAllowAll>());
+ rules.push_back(td_api::make_object<td_api::userPrivacySettingRuleRestrictAll>());
+ } else {
+ rules.push_back(td_api::make_object<td_api::userPrivacySettingRuleRestrictAll>());
+ }
+ return td_api::make_object<td_api::userPrivacySettingRules>(std::move(rules));
+ }
+
+ static td_api::object_ptr<td_api::SearchMessagesFilter> as_search_messages_filter(Slice filter) {
filter = trim(filter);
- to_lower_inplace(filter);
+ string lowered_filter = to_lower(filter);
+ filter = lowered_filter;
+ if (begins_with(filter, "search")) {
+ filter.remove_prefix(6);
+ }
if (filter == "an" || filter == "animation") {
- return make_tl_object<td_api::searchMessagesFilterAnimation>();
+ return td_api::make_object<td_api::searchMessagesFilterAnimation>();
}
if (filter == "au" || filter == "audio") {
- return make_tl_object<td_api::searchMessagesFilterAudio>();
+ return td_api::make_object<td_api::searchMessagesFilterAudio>();
}
if (filter == "d" || filter == "document") {
- return make_tl_object<td_api::searchMessagesFilterDocument>();
+ return td_api::make_object<td_api::searchMessagesFilterDocument>();
}
if (filter == "p" || filter == "photo") {
- return make_tl_object<td_api::searchMessagesFilterPhoto>();
+ return td_api::make_object<td_api::searchMessagesFilterPhoto>();
}
if (filter == "vi" || filter == "video") {
- return make_tl_object<td_api::searchMessagesFilterVideo>();
+ return td_api::make_object<td_api::searchMessagesFilterVideo>();
}
if (filter == "vo" || filter == "voice") {
- return make_tl_object<td_api::searchMessagesFilterVoiceNote>();
+ return td_api::make_object<td_api::searchMessagesFilterVoiceNote>();
}
- if (filter == "pvo") {
- return make_tl_object<td_api::searchMessagesFilterPhotoAndVideo>();
+ if (filter == "pvi") {
+ return td_api::make_object<td_api::searchMessagesFilterPhotoAndVideo>();
}
if (filter == "u" || filter == "url") {
- return make_tl_object<td_api::searchMessagesFilterUrl>();
+ return td_api::make_object<td_api::searchMessagesFilterUrl>();
}
if (filter == "cp" || filter == "chatphoto") {
- return make_tl_object<td_api::searchMessagesFilterChatPhoto>();
- }
- if (filter == "vc" || filter == "call") {
- return make_tl_object<td_api::searchMessagesFilterCall>();
- }
- if (filter == "mvc" || filter == "missedcall") {
- return make_tl_object<td_api::searchMessagesFilterMissedCall>();
+ return td_api::make_object<td_api::searchMessagesFilterChatPhoto>();
}
if (filter == "vn" || filter == "videonote") {
- return make_tl_object<td_api::searchMessagesFilterVideoNote>();
+ return td_api::make_object<td_api::searchMessagesFilterVideoNote>();
}
if (filter == "vvn" || filter == "voicevideonote") {
- return make_tl_object<td_api::searchMessagesFilterVoiceAndVideoNote>();
+ return td_api::make_object<td_api::searchMessagesFilterVoiceAndVideoNote>();
}
if (filter == "m" || filter == "mention") {
- return make_tl_object<td_api::searchMessagesFilterMention>();
+ return td_api::make_object<td_api::searchMessagesFilterMention>();
}
if (filter == "um" || filter == "umention") {
- return make_tl_object<td_api::searchMessagesFilterUnreadMention>();
+ return td_api::make_object<td_api::searchMessagesFilterUnreadMention>();
+ }
+ if (filter == "ur" || filter == "ureaction") {
+ return td_api::make_object<td_api::searchMessagesFilterUnreadReaction>();
+ }
+ if (filter == "f" || filter == "failed") {
+ return td_api::make_object<td_api::searchMessagesFilterFailedToSend>();
+ }
+ if (filter == "pi" || filter == "pinned") {
+ return td_api::make_object<td_api::searchMessagesFilterPinned>();
}
if (!filter.empty()) {
LOG(ERROR) << "Unsupported message filter " << filter;
@@ -850,102 +1355,462 @@ class CliClient final : public Actor {
return nullptr;
}
- tl_object_ptr<td_api::TopChatCategory> get_top_chat_category(MutableSlice category) {
+ static td_api::object_ptr<td_api::ChatMembersFilter> as_chat_members_filter(MutableSlice filter) {
+ filter = trim(filter);
+ to_lower_inplace(filter);
+ if (filter == "a" || filter == "admin" || filter == "administrators") {
+ return td_api::make_object<td_api::chatMembersFilterAdministrators>();
+ }
+ if (filter == "b" || filter == "banned") {
+ return td_api::make_object<td_api::chatMembersFilterBanned>();
+ }
+ if (filter == "bot" || filter == "bots") {
+ return td_api::make_object<td_api::chatMembersFilterBots>();
+ }
+ if (filter == "c" || filter == "contacts") {
+ return td_api::make_object<td_api::chatMembersFilterContacts>();
+ }
+ if (filter == "m" || filter == "members") {
+ return td_api::make_object<td_api::chatMembersFilterMembers>();
+ }
+ if (begins_with(filter, "@")) {
+ return td_api::make_object<td_api::chatMembersFilterMention>(as_message_thread_id(filter.substr(1)));
+ }
+ if (filter == "r" || filter == "rest" || filter == "restricted") {
+ return td_api::make_object<td_api::chatMembersFilterRestricted>();
+ }
+ if (!filter.empty()) {
+ LOG(ERROR) << "Unsupported chat member filter " << filter;
+ }
+ return nullptr;
+ }
+
+ static td_api::object_ptr<td_api::SupergroupMembersFilter> as_supergroup_members_filter(MutableSlice filter,
+ const string &query,
+ Slice message_thread_id) {
+ filter = trim(filter);
+ to_lower_inplace(filter);
+ if (begins_with(filter, "get")) {
+ filter.remove_prefix(3);
+ }
+ if (begins_with(filter, "search")) {
+ filter.remove_prefix(6);
+ }
+ if (begins_with(filter, "supergroup")) {
+ filter.remove_prefix(10);
+ }
+ if (filter == "administrators") {
+ return td_api::make_object<td_api::supergroupMembersFilterAdministrators>();
+ }
+ if (filter == "banned") {
+ return td_api::make_object<td_api::supergroupMembersFilterBanned>(query);
+ }
+ if (filter == "bots") {
+ return td_api::make_object<td_api::supergroupMembersFilterBots>();
+ }
+ if (filter == "contacts") {
+ return td_api::make_object<td_api::supergroupMembersFilterContacts>(query);
+ }
+ if (filter == "members") {
+ if (query.empty()) {
+ return td_api::make_object<td_api::supergroupMembersFilterRecent>();
+ } else {
+ return td_api::make_object<td_api::supergroupMembersFilterSearch>(query);
+ }
+ }
+ if (filter == "restricted") {
+ return td_api::make_object<td_api::supergroupMembersFilterRestricted>(query);
+ }
+ if (filter == "mentions") {
+ return td_api::make_object<td_api::supergroupMembersFilterMention>(query,
+ as_message_thread_id(message_thread_id));
+ }
+ return nullptr;
+ }
+
+ static bool rand_bool() {
+ return Random::fast_bool();
+ }
+
+ td_api::object_ptr<td_api::chatFilter> as_chat_filter(string filter) const {
+ string title;
+ string icon_name;
+ string pinned_chat_ids;
+ string included_chat_ids;
+ string excluded_chat_ids;
+ get_args(filter, title, icon_name, pinned_chat_ids, included_chat_ids, excluded_chat_ids);
+ return td_api::make_object<td_api::chatFilter>(
+ title, icon_name, as_chat_ids(pinned_chat_ids), as_chat_ids(included_chat_ids), as_chat_ids(excluded_chat_ids),
+ rand_bool(), rand_bool(), rand_bool(), rand_bool(), rand_bool(), rand_bool(), rand_bool(), rand_bool());
+ }
+
+ static td_api::object_ptr<td_api::chatAdministratorRights> as_chat_administrator_rights(
+ bool can_manage_chat, bool can_change_info, bool can_post_messages, bool can_edit_messages,
+ bool can_delete_messages, bool can_invite_users, bool can_restrict_members, bool can_pin_messages,
+ bool can_manage_topics, bool can_promote_members, bool can_manage_video_chats, bool is_anonymous) {
+ return td_api::make_object<td_api::chatAdministratorRights>(
+ can_manage_chat, can_change_info, can_post_messages, can_edit_messages, can_delete_messages, can_invite_users,
+ can_restrict_members, can_pin_messages, can_manage_topics, can_promote_members, can_manage_video_chats,
+ is_anonymous);
+ }
+
+ static td_api::object_ptr<td_api::TopChatCategory> as_top_chat_category(MutableSlice category) {
category = trim(category);
to_lower_inplace(category);
if (!category.empty() && category.back() == 's') {
category.remove_suffix(1);
}
if (category == "bot") {
- return make_tl_object<td_api::topChatCategoryBots>();
+ return td_api::make_object<td_api::topChatCategoryBots>();
} else if (category == "group") {
- return make_tl_object<td_api::topChatCategoryGroups>();
+ return td_api::make_object<td_api::topChatCategoryGroups>();
} else if (category == "channel") {
- return make_tl_object<td_api::topChatCategoryChannels>();
+ return td_api::make_object<td_api::topChatCategoryChannels>();
} else if (category == "inline") {
- return make_tl_object<td_api::topChatCategoryInlineBots>();
+ return td_api::make_object<td_api::topChatCategoryInlineBots>();
} else if (category == "call") {
- return make_tl_object<td_api::topChatCategoryCalls>();
+ return td_api::make_object<td_api::topChatCategoryCalls>();
+ } else if (category == "forward") {
+ return td_api::make_object<td_api::topChatCategoryForwardChats>();
} else {
- return make_tl_object<td_api::topChatCategoryUsers>();
+ return td_api::make_object<td_api::topChatCategoryUsers>();
}
}
- static tl_object_ptr<td_api::ChatAction> get_chat_action(MutableSlice action) {
+ static td_api::object_ptr<td_api::ChatAction> as_chat_action(MutableSlice action) {
action = trim(action);
to_lower_inplace(action);
if (action == "c" || action == "cancel") {
- return make_tl_object<td_api::chatActionCancel>();
+ return td_api::make_object<td_api::chatActionCancel>();
}
if (action == "rvi" || action == "record_video") {
- return make_tl_object<td_api::chatActionRecordingVideo>();
+ return td_api::make_object<td_api::chatActionRecordingVideo>();
}
if (action == "uvi" || action == "upload_video") {
- return make_tl_object<td_api::chatActionUploadingVideo>(50);
+ return td_api::make_object<td_api::chatActionUploadingVideo>(50);
}
if (action == "rvo" || action == "record_voice") {
- return make_tl_object<td_api::chatActionRecordingVoiceNote>();
+ return td_api::make_object<td_api::chatActionRecordingVoiceNote>();
}
if (action == "uvo" || action == "upload_voice") {
- return make_tl_object<td_api::chatActionUploadingVoiceNote>(50);
+ return td_api::make_object<td_api::chatActionUploadingVoiceNote>(50);
}
if (action == "up" || action == "upload_photo") {
- return make_tl_object<td_api::chatActionUploadingPhoto>(50);
+ return td_api::make_object<td_api::chatActionUploadingPhoto>(50);
}
if (action == "ud" || action == "upload_document") {
- return make_tl_object<td_api::chatActionUploadingDocument>(50);
+ return td_api::make_object<td_api::chatActionUploadingDocument>(50);
}
if (action == "fl" || action == "find_location") {
- return make_tl_object<td_api::chatActionChoosingLocation>();
+ return td_api::make_object<td_api::chatActionChoosingLocation>();
}
if (action == "cc" || action == "choose_contact") {
- return make_tl_object<td_api::chatActionChoosingContact>();
+ return td_api::make_object<td_api::chatActionChoosingContact>();
}
if (action == "spg" || action == "start_play_game") {
- return make_tl_object<td_api::chatActionStartPlayingGame>();
+ return td_api::make_object<td_api::chatActionStartPlayingGame>();
}
if (action == "rvn" || action == "record_video_note") {
- return make_tl_object<td_api::chatActionRecordingVideoNote>();
+ return td_api::make_object<td_api::chatActionRecordingVideoNote>();
}
if (action == "uvn" || action == "upload_video_note") {
- return make_tl_object<td_api::chatActionUploadingVideoNote>(50);
+ return td_api::make_object<td_api::chatActionUploadingVideoNote>(50);
+ }
+ if (action == "cs" || action == "choose_sticker") {
+ return td_api::make_object<td_api::chatActionChoosingSticker>();
+ }
+ if (begins_with(action, "wa")) {
+ return td_api::make_object<td_api::chatActionWatchingAnimations>(action.substr(2).str());
+ }
+ return td_api::make_object<td_api::chatActionTyping>();
+ }
+
+ static td_api::object_ptr<td_api::ChatReportReason> as_chat_report_reason(MutableSlice reason) {
+ reason = trim(reason);
+ if (reason == "null") {
+ return nullptr;
+ }
+ if (reason == "spam") {
+ return td_api::make_object<td_api::chatReportReasonSpam>();
}
- return make_tl_object<td_api::chatActionTyping>();
+ if (reason == "violence") {
+ return td_api::make_object<td_api::chatReportReasonViolence>();
+ }
+ if (reason == "porno") {
+ return td_api::make_object<td_api::chatReportReasonPornography>();
+ }
+ if (reason == "ca") {
+ return td_api::make_object<td_api::chatReportReasonChildAbuse>();
+ }
+ if (reason == "copyright") {
+ return td_api::make_object<td_api::chatReportReasonCopyright>();
+ }
+ if (reason == "geo" || reason == "location") {
+ return td_api::make_object<td_api::chatReportReasonUnrelatedLocation>();
+ }
+ if (reason == "fake") {
+ return td_api::make_object<td_api::chatReportReasonFake>();
+ }
+ if (reason == "drugs") {
+ return td_api::make_object<td_api::chatReportReasonIllegalDrugs>();
+ }
+ if (reason == "pd") {
+ return td_api::make_object<td_api::chatReportReasonPersonalDetails>();
+ }
+ return td_api::make_object<td_api::chatReportReasonCustom>();
}
- static tl_object_ptr<td_api::NetworkType> get_network_type(MutableSlice type) {
+ static td_api::object_ptr<td_api::NetworkType> as_network_type(MutableSlice type) {
type = trim(type);
to_lower_inplace(type);
if (type == "none") {
- return make_tl_object<td_api::networkTypeNone>();
+ return td_api::make_object<td_api::networkTypeNone>();
}
if (type == "mobile") {
- return make_tl_object<td_api::networkTypeMobile>();
+ return td_api::make_object<td_api::networkTypeMobile>();
}
if (type == "roaming") {
- return make_tl_object<td_api::networkTypeMobileRoaming>();
+ return td_api::make_object<td_api::networkTypeMobileRoaming>();
}
if (type == "wifi") {
- return make_tl_object<td_api::networkTypeWiFi>();
+ return td_api::make_object<td_api::networkTypeWiFi>();
}
if (type == "other") {
- return make_tl_object<td_api::networkTypeOther>();
+ return td_api::make_object<td_api::networkTypeOther>();
+ }
+ return nullptr;
+ }
+
+ td_api::object_ptr<td_api::SuggestedAction> as_suggested_action(Slice action) const {
+ if (action == "unarchive") {
+ return td_api::make_object<td_api::suggestedActionEnableArchiveAndMuteNewChats>();
+ }
+ if (action == "pass") {
+ return td_api::make_object<td_api::suggestedActionCheckPassword>();
+ }
+ if (action == "number") {
+ return td_api::make_object<td_api::suggestedActionCheckPhoneNumber>();
+ }
+ if (action == "checks") {
+ return td_api::make_object<td_api::suggestedActionViewChecksHint>();
+ }
+ if (begins_with(action, "giga")) {
+ return td_api::make_object<td_api::suggestedActionConvertToBroadcastGroup>(as_supergroup_id(action.substr(4)));
}
+ if (begins_with(action, "spass")) {
+ return td_api::make_object<td_api::suggestedActionSetPassword>(to_integer<int32>(action.substr(5)));
+ }
+ return nullptr;
+ }
+
+ static td_api::object_ptr<td_api::EmailAddressAuthentication> as_email_address_authentication(Slice arg) {
+ if (begins_with(arg, "a ")) {
+ return td_api::make_object<td_api::emailAddressAuthenticationAppleId>(arg.substr(2).str());
+ } else if (begins_with(arg, "g ")) {
+ return td_api::make_object<td_api::emailAddressAuthenticationGoogleId>(arg.substr(2).str());
+ } else if (!arg.empty()) {
+ return td_api::make_object<td_api::emailAddressAuthenticationCode>(arg.str());
+ }
+ return nullptr;
+ }
+
+ static td_api::object_ptr<td_api::PassportElementType> as_passport_element_type(Slice passport_element_type) {
+ if (passport_element_type == "address" || passport_element_type == "a") {
+ return td_api::make_object<td_api::passportElementTypeAddress>();
+ }
+ if (passport_element_type == "email" || passport_element_type == "e") {
+ return td_api::make_object<td_api::passportElementTypeEmailAddress>();
+ }
+ if (passport_element_type == "phone" || passport_element_type == "p") {
+ return td_api::make_object<td_api::passportElementTypePhoneNumber>();
+ }
+ if (passport_element_type == "pd") {
+ return td_api::make_object<td_api::passportElementTypePersonalDetails>();
+ }
+ if (passport_element_type == "dl") {
+ return td_api::make_object<td_api::passportElementTypeDriverLicense>();
+ }
+ if (passport_element_type == "ip") {
+ return td_api::make_object<td_api::passportElementTypeInternalPassport>();
+ }
+ if (passport_element_type == "ic") {
+ return td_api::make_object<td_api::passportElementTypeIdentityCard>();
+ }
+ if (passport_element_type == "ra") {
+ return td_api::make_object<td_api::passportElementTypeRentalAgreement>();
+ }
+ if (passport_element_type == "pr") {
+ return td_api::make_object<td_api::passportElementTypePassportRegistration>();
+ }
+ if (passport_element_type == "tr") {
+ return td_api::make_object<td_api::passportElementTypeTemporaryRegistration>();
+ }
+ return td_api::make_object<td_api::passportElementTypePassport>();
+ }
+
+ static auto as_passport_element_types(Slice types) {
+ return transform(autosplit(types), as_passport_element_type);
+ }
+
+ static td_api::object_ptr<td_api::InputPassportElement> as_input_passport_element(const string &passport_element_type,
+ const string &arg,
+ bool with_selfie) {
+ vector<td_api::object_ptr<td_api::InputFile>> input_files;
+ td_api::object_ptr<td_api::InputFile> selfie;
+ if (!arg.empty()) {
+ auto files = autosplit(arg);
+ CHECK(!files.empty());
+ if (with_selfie) {
+ selfie = as_input_file(files.back());
+ files.pop_back();
+ }
+ for (const auto &file : files) {
+ input_files.push_back(as_input_file(file));
+ }
+ }
+ if (passport_element_type == "address" || passport_element_type == "a") {
+ return td_api::make_object<td_api::inputPassportElementAddress>(
+ td_api::make_object<td_api::address>("US", "CA", "Los Angeles", "Washington", "", "90001"));
+ } else if (passport_element_type == "email" || passport_element_type == "e") {
+ return td_api::make_object<td_api::inputPassportElementEmailAddress>(arg);
+ } else if (passport_element_type == "phone" || passport_element_type == "p") {
+ return td_api::make_object<td_api::inputPassportElementPhoneNumber>(arg);
+ } else if (passport_element_type == "pd") {
+ return td_api::make_object<td_api::inputPassportElementPersonalDetails>(
+ td_api::make_object<td_api::personalDetails>("Mike", "Jr", "Towers", u8"Mike\u2708", u8"Jr\u26fd",
+ u8"Towers\u2757", td_api::make_object<td_api::date>(29, 2, 2000),
+ "male", "US", "GB"));
+ } else if (passport_element_type == "driver_license" || passport_element_type == "dl") {
+ if (input_files.size() >= 2) {
+ auto front_side = std::move(input_files[0]);
+ input_files.erase(input_files.begin());
+ auto reverse_side = std::move(input_files[0]);
+ input_files.erase(input_files.begin());
+ return td_api::make_object<td_api::inputPassportElementDriverLicense>(
+ td_api::make_object<td_api::inputIdentityDocument>(
+ "1234567890", td_api::make_object<td_api::date>(1, 3, 2029), std::move(front_side),
+ std::move(reverse_side), std::move(selfie), std::move(input_files)));
+ }
+ } else if (passport_element_type == "identity_card" || passport_element_type == "ic") {
+ if (input_files.size() >= 2) {
+ auto front_side = std::move(input_files[0]);
+ input_files.erase(input_files.begin());
+ auto reverse_side = std::move(input_files[0]);
+ input_files.erase(input_files.begin());
+ return td_api::make_object<td_api::inputPassportElementIdentityCard>(
+ td_api::make_object<td_api::inputIdentityDocument>("1234567890", nullptr, std::move(front_side),
+ std::move(reverse_side), std::move(selfie),
+ std::move(input_files)));
+ }
+ } else if (passport_element_type == "internal_passport" || passport_element_type == "ip") {
+ if (!input_files.empty()) {
+ auto front_side = std::move(input_files[0]);
+ input_files.erase(input_files.begin());
+ return td_api::make_object<td_api::inputPassportElementInternalPassport>(
+ td_api::make_object<td_api::inputIdentityDocument>("1234567890", nullptr, std::move(front_side), nullptr,
+ std::move(selfie), std::move(input_files)));
+ }
+ } else if (passport_element_type == "rental_agreement" || passport_element_type == "ra") {
+ vector<td_api::object_ptr<td_api::InputFile>> translation;
+ if (selfie != nullptr) {
+ translation.push_back(std::move(selfie));
+ }
+ return td_api::make_object<td_api::inputPassportElementRentalAgreement>(
+ td_api::make_object<td_api::inputPersonalDocument>(std::move(input_files), std::move(translation)));
+ }
+
+ LOG(ERROR) << "Unsupported passport element type " << passport_element_type;
return nullptr;
}
- static td_api::object_ptr<td_api::Object> execute(tl_object_ptr<td_api::Function> f) {
- LOG(INFO) << "Execute request: " << to_string(f);
+ static td_api::object_ptr<td_api::languagePackInfo> as_language_pack_info(const string &language_code,
+ const string &name,
+ const string &native_name) {
+ return td_api::make_object<td_api::languagePackInfo>(language_code, "test", name, native_name, "en", true, true,
+ true, true, -1, 5, 3, "abacaba");
+ }
+
+ static td_api::object_ptr<td_api::MessageSchedulingState> as_message_scheduling_state(Slice date) {
+ date = trim(date);
+ if (date.empty()) {
+ return nullptr;
+ }
+ auto send_date = to_integer<int32>(date);
+ if (send_date == -1) {
+ return td_api::make_object<td_api::messageSchedulingStateSendWhenOnline>();
+ }
+ return td_api::make_object<td_api::messageSchedulingStateSendAtDate>(send_date);
+ }
+
+ static td_api::object_ptr<td_api::themeParameters> as_theme_parameters() {
+ return td_api::make_object<td_api::themeParameters>(0, 1, -1, 256, 65536, 123456789, 65535);
+ }
+
+ static td_api::object_ptr<td_api::BackgroundFill> as_background_fill(int32 color) {
+ return td_api::make_object<td_api::backgroundFillSolid>(color);
+ }
+
+ static td_api::object_ptr<td_api::BackgroundFill> as_background_fill(int32 top_color, int32 bottom_color) {
+ return td_api::make_object<td_api::backgroundFillGradient>(top_color, bottom_color, Random::fast(0, 7) * 45);
+ }
+
+ static td_api::object_ptr<td_api::BackgroundFill> as_background_fill(vector<int32> colors) {
+ return td_api::make_object<td_api::backgroundFillFreeformGradient>(std::move(colors));
+ }
+
+ static td_api::object_ptr<td_api::BackgroundType> as_solid_pattern_background(int32 color, int32 intensity,
+ bool is_moving) {
+ return as_gradient_pattern_background(color, color, intensity, false, is_moving);
+ }
+
+ static td_api::object_ptr<td_api::BackgroundType> as_gradient_pattern_background(int32 top_color, int32 bottom_color,
+ int32 intensity, bool is_inverted,
+ bool is_moving) {
+ return td_api::make_object<td_api::backgroundTypePattern>(as_background_fill(top_color, bottom_color), intensity,
+ is_inverted, is_moving);
+ }
+
+ static td_api::object_ptr<td_api::BackgroundType> as_freeform_gradient_pattern_background(vector<int32> colors,
+ int32 intensity,
+ bool is_inverted,
+ bool is_moving) {
+ return td_api::make_object<td_api::backgroundTypePattern>(as_background_fill(std::move(colors)), intensity,
+ is_inverted, is_moving);
+ }
+
+ static td_api::object_ptr<td_api::BackgroundType> as_solid_background(int32 color) {
+ return td_api::make_object<td_api::backgroundTypeFill>(as_background_fill(color));
+ }
+
+ static td_api::object_ptr<td_api::BackgroundType> as_gradient_background(int32 top_color, int32 bottom_color) {
+ return td_api::make_object<td_api::backgroundTypeFill>(as_background_fill(top_color, bottom_color));
+ }
+
+ static td_api::object_ptr<td_api::BackgroundType> as_freeform_gradient_background(vector<int32> colors) {
+ return td_api::make_object<td_api::backgroundTypeFill>(as_background_fill(std::move(colors)));
+ }
+
+ td_api::object_ptr<td_api::phoneNumberAuthenticationSettings> as_phone_number_authentication_settings() const {
+ return td_api::make_object<td_api::phoneNumberAuthenticationSettings>(false, true, false, false,
+ vector<string>(authentication_tokens_));
+ }
+
+ static td_api::object_ptr<td_api::Object> execute(td_api::object_ptr<td_api::Function> f) {
+ if (combined_log.get_first_verbosity_level() < get_log_tag_verbosity_level("td_requests")) {
+ LOG(ERROR) << "Execute request: " << to_string(f);
+ }
auto res = ClientActor::execute(std::move(f));
- LOG(INFO) << "Execute response: " << to_string(res);
+ if (combined_log.get_first_verbosity_level() < get_log_tag_verbosity_level("td_requests")) {
+ LOG(ERROR) << "Execute response: " << to_string(res);
+ }
return res;
}
- uint64 send_request(tl_object_ptr<td_api::Function> f) {
+ uint64 send_request(td_api::object_ptr<td_api::Function> f) {
static uint64 query_num = 1;
- if (!td_.empty()) {
+ if (!td_client_.empty()) {
auto id = query_num++;
- send_closure_later(td_, &ClientActor::request, id, std::move(f));
+ send_closure_later(td_client_, &ClientActor::request, id, std::move(f));
return id;
} else {
LOG(ERROR) << "Failed to send: " << to_string(f);
@@ -953,1879 +1818,3287 @@ class CliClient final : public Actor {
}
}
- void send_message(const string &chat_id, tl_object_ptr<td_api::InputMessageContent> &&input_message_content,
+ static int32 get_log_tag_verbosity_level(const string &name) {
+ auto level = ClientActor::execute(td_api::make_object<td_api::getLogTagVerbosityLevel>(name));
+ if (level->get_id() == td_api::error::ID) {
+ return -1;
+ }
+ CHECK(level->get_id() == td_api::logVerbosityLevel::ID);
+ return static_cast<const td_api::logVerbosityLevel *>(level.get())->verbosity_level_;
+ }
+
+ void send_message(int64 chat_id, td_api::object_ptr<td_api::InputMessageContent> &&input_message_content,
bool disable_notification = false, bool from_background = false, int64 reply_to_message_id = 0) {
- auto chat = as_chat_id(chat_id);
- auto id = send_request(make_tl_object<td_api::sendMessage>(
- chat, reply_to_message_id, disable_notification, from_background, nullptr, std::move(input_message_content)));
- query_id_to_send_message_info_[id].start_time = Time::now();
+ auto id = send_request(td_api::make_object<td_api::sendMessage>(
+ chat_id, message_thread_id_, reply_to_message_id,
+ td_api::make_object<td_api::messageSendOptions>(disable_notification, from_background, true, true,
+ as_message_scheduling_state(schedule_date_)),
+ nullptr, std::move(input_message_content)));
+ if (id != 0) {
+ query_id_to_send_message_info_[id].start_time = Time::now();
+ }
+ }
+
+ td_api::object_ptr<td_api::messageSendOptions> default_message_send_options() const {
+ return td_api::make_object<td_api::messageSendOptions>(false, false, false, true,
+ as_message_scheduling_state(schedule_date_));
+ }
+
+ void send_get_background_url(td_api::object_ptr<td_api::BackgroundType> &&background_type) {
+ send_request(td_api::make_object<td_api::getBackgroundUrl>("asd", std::move(background_type)));
}
void on_cmd(string cmd) {
// TODO: need to remove https://en.wikipedia.org/wiki/ANSI_escape_code from cmd
- cmd.erase(std::remove_if(cmd.begin(), cmd.end(), [](char c) { return 0 <= c && c < 32; }), cmd.end());
+ td::remove_if(cmd, [](unsigned char c) { return c < 32; });
LOG(INFO) << "CMD:[" << cmd << "]";
string op;
string args;
std::tie(op, args) = split(cmd);
- const int32 OP_BLOCK_COUNT = 5;
+ const int32 OP_BLOCK_COUNT = 10;
int32 op_not_found_count = 0;
if (op == "gas") {
- send_request(make_tl_object<td_api::getAuthorizationState>());
- } else if (op == "sap") {
- send_request(make_tl_object<td_api::setAuthenticationPhoneNumber>(args, false, false));
+ LOG(ERROR) << to_string(authorization_state_);
+ } else if (op == "sap" || op == "sapn") {
+ send_request(
+ td_api::make_object<td_api::setAuthenticationPhoneNumber>(args, as_phone_number_authentication_settings()));
+ } else if (op == "sae" || op == "saea") {
+ send_request(td_api::make_object<td_api::setAuthenticationEmailAddress>(args));
} else if (op == "rac") {
- send_request(make_tl_object<td_api::resendAuthenticationCode>());
- } else if (op == "cdek" || op == "CheckDatabaseEncryptionKey") {
- send_request(make_tl_object<td_api::checkDatabaseEncryptionKey>(args));
- } else if (op == "sdek" || op == "SetDatabaseEncryptionKey") {
- send_request(make_tl_object<td_api::setDatabaseEncryptionKey>(args));
+ send_request(td_api::make_object<td_api::resendAuthenticationCode>());
+ } else if (op == "sdek") {
+ send_request(td_api::make_object<td_api::setDatabaseEncryptionKey>(args));
+ } else if (op == "caec") {
+ send_request(td_api::make_object<td_api::checkAuthenticationEmailCode>(as_email_address_authentication(args)));
} else if (op == "cac") {
- string code;
+ send_request(td_api::make_object<td_api::checkAuthenticationCode>(args));
+ } else if (op == "ru") {
string first_name;
string last_name;
-
- std::tie(code, args) = split(args);
- std::tie(first_name, last_name) = split(args);
-
- send_request(make_tl_object<td_api::checkAuthenticationCode>(code, first_name, last_name));
+ get_args(args, first_name, last_name);
+ send_request(td_api::make_object<td_api::registerUser>(first_name, last_name));
} else if (op == "cap") {
- send_request(make_tl_object<td_api::checkAuthenticationPassword>(args));
- } else if (op == "cab") {
- send_request(make_tl_object<td_api::checkAuthenticationBotToken>(args));
+ send_request(td_api::make_object<td_api::checkAuthenticationPassword>(args));
+ } else if (op == "cabt") {
+ send_request(td_api::make_object<td_api::checkAuthenticationBotToken>(args));
+ } else if (op == "qr") {
+ send_request(td_api::make_object<td_api::requestQrCodeAuthentication>(as_user_ids(args)));
+ } else if (op == "cqr") {
+ send_request(td_api::make_object<td_api::confirmQrCodeAuthentication>(args));
+ } else if (op == "gcs") {
+ send_request(td_api::make_object<td_api::getCurrentState>());
} else if (op == "rapr") {
- send_request(make_tl_object<td_api::requestAuthenticationPasswordRecovery>());
+ send_request(td_api::make_object<td_api::requestAuthenticationPasswordRecovery>());
+ } else if (op == "caprc") {
+ string recovery_code = args;
+ send_request(td_api::make_object<td_api::checkAuthenticationPasswordRecoveryCode>(recovery_code));
} else if (op == "rap") {
- send_request(make_tl_object<td_api::recoverAuthenticationPassword>(args));
+ string recovery_code;
+ string new_password;
+ string new_hint;
+ get_args(args, recovery_code, new_password, new_hint);
+ send_request(td_api::make_object<td_api::recoverAuthenticationPassword>(recovery_code, new_password, new_hint));
} else if (op == "lo" || op == "LogOut" || op == "logout") {
- send_request(make_tl_object<td_api::logOut>());
- } else if (op == "ra" || op == "destroy") {
- send_request(make_tl_object<td_api::destroy>());
+ send_request(td_api::make_object<td_api::logOut>());
+ } else if (op == "destroy") {
+ send_request(td_api::make_object<td_api::destroy>());
} else if (op == "reset") {
- init_td();
+ td_client_.reset();
} else if (op == "close_td") {
- send_request(make_tl_object<td_api::close>());
+ // send_request(td_api::make_object<td_api::getCurrentState>());
+ send_request(td_api::make_object<td_api::close>());
+ // send_request(td_api::make_object<td_api::getCurrentState>());
+ // send_request(td_api::make_object<td_api::close>());
} else if (op == "DeleteAccountYesIReallyWantToDeleteMyAccount") {
- send_request(make_tl_object<td_api::deleteAccount>(args));
+ string password;
+ string reason;
+ get_args(args, password, reason);
+ send_request(td_api::make_object<td_api::deleteAccount>(reason, password));
} else if (op == "gps" || op == "GetPasswordState") {
- send_request(make_tl_object<td_api::getPasswordState>());
+ send_request(td_api::make_object<td_api::getPasswordState>());
} else if (op == "spass" || op == "SetPassword") {
string password;
string new_password;
string new_hint;
string recovery_email_address;
- std::tie(password, args) = split(args);
+ get_args(args, password, new_password, new_hint, recovery_email_address);
if (password == "#") {
password = "";
}
- std::tie(new_password, args) = split(args);
if (new_password == "#") {
new_password = "";
}
- std::tie(new_hint, args) = split(args);
if (new_hint == "#") {
new_hint = "";
}
- recovery_email_address = args;
if (recovery_email_address == "#") {
recovery_email_address = "";
}
- send_request(make_tl_object<td_api::setPassword>(password, new_password, new_hint, true, recovery_email_address));
+ send_request(
+ td_api::make_object<td_api::setPassword>(password, new_password, new_hint, true, recovery_email_address));
+ } else if (op == "gpafhttp") {
+ ChainBufferWriter writer;
+ writer.append(PSLICE() << "GET " << args << " HTTP/1.1\r\n\r\n\r\n");
+ ChainBufferReader reader = writer.extract_reader();
+ HttpReader http_reader;
+ http_reader.init(&reader);
+ HttpQuery query;
+ auto r_size = http_reader.read_next(&query);
+ if (r_size.is_error()) {
+ LOG(ERROR) << r_size.error();
+ return;
+ }
+ string bot_user_id = query.get_arg("bot_id").str();
+ string scope = query.get_arg("scope").str();
+ string public_key = query.get_arg("public_key").str();
+ string payload = query.get_arg("payload").str();
+ LOG(INFO) << "Callback URL:" << query.get_arg("callback_url");
+ send_request(td_api::make_object<td_api::getPassportAuthorizationForm>(as_user_id(bot_user_id), scope, public_key,
+ payload));
+ } else if (op == "gpaf") {
+ UserId bot_user_id;
+ string scope;
+ string public_key =
+ "-----BEGIN PUBLIC KEY-----\n"
+ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzmgKr0fPP4rB/TsNEweC\n"
+ "hoG3ntUxuBTmHsFBW6CpABGdaTmKZSjAI/cTofhBgtRQIOdX0YRGHHHhwyLf49Wv\n"
+ "9l+XexbJOa0lTsJSNMj8Y/9sZbqUl5ur8ZOTM0sxbXC0XKexu1tM9YavH+Lbrobk\n"
+ "jt0+cmo/zEYZWNtLVihnR2IDv+7tSgiDoFWi/koAUdfJ1VMw+hReUaLg3vE9CmPK\n"
+ "tQiTy+NvmrYaBPb75I0Jz3Lrz1+mZSjLKO25iT84RIsxarBDd8iYh2avWkCmvtiR\n"
+ "Lcif8wLxi2QWC1rZoCA3Ip+Hg9J9vxHlzl6xT01WjUStMhfwrUW6QBpur7FJ+aKM\n"
+ "oaMoHieFNCG4qIkWVEHHSsUpLum4SYuEnyNH3tkjbrdldZanCvanGq+TZyX0buRt\n"
+ "4zk7FGcu8iulUkAP/o/WZM0HKinFN/vuzNVA8iqcO/BBhewhzpqmmTMnWmAO8WPP\n"
+ "DJMABRtXJnVuPh1CI5pValzomLJM4/YvnJGppzI1QiHHNA9JtxVmj2xf8jaXa1LJ\n"
+ "WUNJK+RvUWkRUxpWiKQQO9FAyTPLRtDQGN9eUeDR1U0jqRk/gNT8smHGN6I4H+NR\n"
+ "3X3/1lMfcm1dvk654ql8mxjCA54IpTPr/icUMc7cSzyIiQ7Tp9PZTl1gHh281ZWf\n"
+ "P7d2+fuJMlkjtM7oAwf+tI8CAwEAAQ==\n"
+ "-----END PUBLIC KEY-----";
+ string payload;
+ get_args(args, bot_user_id, scope, payload);
+ send_request(td_api::make_object<td_api::getPassportAuthorizationForm>(bot_user_id, scope, public_key, payload));
+ } else if (op == "gpafae") {
+ int32 form_id;
+ string password;
+ get_args(args, form_id, password);
+ send_request(td_api::make_object<td_api::getPassportAuthorizationFormAvailableElements>(form_id, password));
+ } else if (op == "spaf") {
+ int32 form_id;
+ string types;
+ get_args(args, form_id, types);
+ send_request(
+ td_api::make_object<td_api::sendPassportAuthorizationForm>(form_id, as_passport_element_types(types)));
+ } else if (op == "gpcl") {
+ send_request(td_api::make_object<td_api::getPreferredCountryLanguage>(args));
+ } else if (op == "spnvc" || op == "SendPhoneNumberVerificationCode") {
+ send_request(td_api::make_object<td_api::sendPhoneNumberVerificationCode>(args, nullptr));
+ } else if (op == "cpnvc" || op == "CheckPhoneNumberVerificationCode") {
+ send_request(td_api::make_object<td_api::checkPhoneNumberVerificationCode>(args));
+ } else if (op == "rpnvc" || op == "ResendPhoneNumberVerificationCode") {
+ send_request(td_api::make_object<td_api::resendPhoneNumberVerificationCode>());
+ } else if (op == "seavc" || op == "SendEmailAddressVerificationCode") {
+ send_request(td_api::make_object<td_api::sendEmailAddressVerificationCode>(args));
+ } else if (op == "ceavc" || op == "CheckEmailAddressVerificationCode") {
+ send_request(td_api::make_object<td_api::checkEmailAddressVerificationCode>(args));
+ } else if (op == "reavc" || op == "ResendEmailAddressVerificationCode") {
+ send_request(td_api::make_object<td_api::resendEmailAddressVerificationCode>());
+ } else if (op == "slea") {
+ send_request(td_api::make_object<td_api::setLoginEmailAddress>(args));
+ } else if (op == "rleac") {
+ send_request(td_api::make_object<td_api::resendLoginEmailAddressCode>());
+ } else if (op == "cleac") {
+ send_request(td_api::make_object<td_api::checkLoginEmailAddressCode>(as_email_address_authentication(args)));
} else if (op == "srea" || op == "SetRecoveryEmailAddress") {
string password;
string recovery_email_address;
- std::tie(password, recovery_email_address) = split(args);
- send_request(make_tl_object<td_api::setRecoveryEmailAddress>(password, recovery_email_address));
- } else if (op == "rpr" || op == "RequestPasswordRecovery") {
- send_request(make_tl_object<td_api::requestPasswordRecovery>());
- } else if (op == "rp" || op == "RecoverPassword") {
- send_request(make_tl_object<td_api::recoverPassword>(args));
+ get_args(args, password, recovery_email_address);
+ send_request(td_api::make_object<td_api::setRecoveryEmailAddress>(password, recovery_email_address));
} else if (op == "grea" || op == "GetRecoveryEmailAddress") {
- send_request(make_tl_object<td_api::getRecoveryEmailAddress>(args));
+ send_request(td_api::make_object<td_api::getRecoveryEmailAddress>(args));
+ } else if (op == "creac") {
+ send_request(td_api::make_object<td_api::checkRecoveryEmailAddressCode>(args));
+ } else if (op == "rreac") {
+ send_request(td_api::make_object<td_api::resendRecoveryEmailAddressCode>());
+ } else if (op == "spncc") {
+ string hash;
+ string phone_number;
+ get_args(args, hash, phone_number);
+ send_request(td_api::make_object<td_api::sendPhoneNumberConfirmationCode>(hash, phone_number, nullptr));
+ } else if (op == "cpncc") {
+ send_request(td_api::make_object<td_api::checkPhoneNumberConfirmationCode>(args));
+ } else if (op == "rpncc") {
+ send_request(td_api::make_object<td_api::resendPhoneNumberConfirmationCode>());
+ } else if (op == "rpr") {
+ send_request(td_api::make_object<td_api::requestPasswordRecovery>());
+ } else if (op == "cprc") {
+ string recovery_code = args;
+ send_request(td_api::make_object<td_api::checkPasswordRecoveryCode>(recovery_code));
+ } else if (op == "rp") {
+ string recovery_code;
+ string new_password;
+ string new_hint;
+ get_args(args, recovery_code, new_password, new_hint);
+ send_request(td_api::make_object<td_api::recoverPassword>(recovery_code, new_password, new_hint));
+ } else if (op == "resetp") {
+ send_request(td_api::make_object<td_api::resetPassword>());
+ } else if (op == "cpr") {
+ send_request(td_api::make_object<td_api::cancelPasswordReset>());
} else if (op == "gtp" || op == "GetTemporaryPassword") {
- send_request(make_tl_object<td_api::getTemporaryPasswordState>());
+ send_request(td_api::make_object<td_api::getTemporaryPasswordState>());
} else if (op == "ctp" || op == "CreateTemporaryPassword") {
- send_request(make_tl_object<td_api::createTemporaryPassword>(args, 60 * 6));
- } else if (op == "pdu" || op == "processDcUpdate") {
- string dc_id;
- string ip_port;
- std::tie(dc_id, ip_port) = split(args);
- send_request(make_tl_object<td_api::processDcUpdate>(dc_id, ip_port));
+ send_request(td_api::make_object<td_api::createTemporaryPassword>(args, 60 * 6));
+ } else if (op == "gpe") {
+ string password;
+ string passport_element_type;
+ get_args(args, password, passport_element_type);
+ send_request(
+ td_api::make_object<td_api::getPassportElement>(as_passport_element_type(passport_element_type), password));
+ } else if (op == "gape") {
+ string password = args;
+ send_request(td_api::make_object<td_api::getAllPassportElements>(password));
+ } else if (op == "spe" || op == "spes") {
+ string password;
+ string passport_element_type;
+ string arg;
+ get_args(args, password, passport_element_type, arg);
+ send_request(td_api::make_object<td_api::setPassportElement>(
+ as_input_passport_element(passport_element_type, arg, op == "spes"), password));
+ } else if (op == "dpe") {
+ const string &passport_element_type = args;
+ send_request(td_api::make_object<td_api::deletePassportElement>(as_passport_element_type(passport_element_type)));
+ } else if (op == "ppn") {
+ send_request(td_api::make_object<td_api::processPushNotification>(args));
+ } else if (op == "gpri") {
+ send_request(td_api::make_object<td_api::getPushReceiverId>(args));
} else if (op == "rda") {
- send_request(make_tl_object<td_api::registerDevice>(make_tl_object<td_api::deviceTokenApplePush>(args, true),
- as_user_ids("")));
+ send_request(td_api::make_object<td_api::registerDevice>(
+ td_api::make_object<td_api::deviceTokenApplePush>(args, true), as_user_ids("")));
} else if (op == "rdb") {
- send_request(make_tl_object<td_api::registerDevice>(make_tl_object<td_api::deviceTokenBlackBerryPush>(args),
- as_user_ids("")));
+ send_request(td_api::make_object<td_api::registerDevice>(
+ td_api::make_object<td_api::deviceTokenBlackBerryPush>(args), as_user_ids("")));
+ } else if (op == "rdf") {
+ send_request(td_api::make_object<td_api::registerDevice>(
+ td_api::make_object<td_api::deviceTokenFirebaseCloudMessaging>(args, true), as_user_ids("")));
} else if (op == "rdt") {
string token;
string other_user_ids_str;
-
- std::tie(token, other_user_ids_str) = split(args);
- send_request(make_tl_object<td_api::registerDevice>(make_tl_object<td_api::deviceTokenTizenPush>(token),
- as_user_ids(other_user_ids_str)));
+ get_args(args, token, other_user_ids_str);
+ send_request(td_api::make_object<td_api::registerDevice>(td_api::make_object<td_api::deviceTokenTizenPush>(token),
+ as_user_ids(other_user_ids_str)));
} else if (op == "rdu") {
string token;
string other_user_ids_str;
-
- std::tie(token, other_user_ids_str) = split(args);
- send_request(make_tl_object<td_api::registerDevice>(make_tl_object<td_api::deviceTokenUbuntuPush>(token),
- as_user_ids(other_user_ids_str)));
+ get_args(args, token, other_user_ids_str);
+ send_request(td_api::make_object<td_api::registerDevice>(
+ td_api::make_object<td_api::deviceTokenUbuntuPush>(token), as_user_ids(other_user_ids_str)));
} else if (op == "rdw") {
string endpoint;
string key;
string secret;
string other_user_ids_str;
-
- std::tie(endpoint, args) = split(args);
- std::tie(key, args) = split(args);
- std::tie(secret, other_user_ids_str) = split(args);
- send_request(make_tl_object<td_api::registerDevice>(
- make_tl_object<td_api::deviceTokenWebPush>(endpoint, key, secret), as_user_ids(other_user_ids_str)));
+ get_args(args, endpoint, key, secret, other_user_ids_str);
+ send_request(td_api::make_object<td_api::registerDevice>(
+ td_api::make_object<td_api::deviceTokenWebPush>(endpoint, key, secret), as_user_ids(other_user_ids_str)));
+ } else if (op == "gbci") {
+ send_request(td_api::make_object<td_api::getBankCardInfo>(args));
} else if (op == "gpf") {
- string chat_id;
- string message_id;
-
- std::tie(chat_id, message_id) = split(args);
- send_request(make_tl_object<td_api::getPaymentForm>(as_chat_id(chat_id), as_message_id(message_id)));
+ InputInvoice input_invoice;
+ get_args(args, input_invoice);
+ send_request(td_api::make_object<td_api::getPaymentForm>(input_invoice, as_theme_parameters()));
} else if (op == "voi") {
- string chat_id;
- string message_id;
- string allow_save;
-
- std::tie(chat_id, args) = split(args);
- std::tie(message_id, allow_save) = split(args);
- send_request(make_tl_object<td_api::validateOrderInfo>(as_chat_id(chat_id), as_message_id(message_id), nullptr,
- as_bool(allow_save)));
+ InputInvoice input_invoice;
+ bool allow_save;
+ get_args(args, input_invoice, allow_save);
+ send_request(td_api::make_object<td_api::validateOrderInfo>(input_invoice, nullptr, allow_save));
} else if (op == "spfs") {
- string chat_id;
- string message_id;
+ InputInvoice input_invoice;
+ int64 tip_amount;
+ int64 payment_form_id;
string order_info_id;
string shipping_option_id;
string saved_credentials_id;
-
- std::tie(chat_id, args) = split(args);
- std::tie(message_id, args) = split(args);
- std::tie(order_info_id, args) = split(args);
- std::tie(shipping_option_id, saved_credentials_id) = split(args);
- send_request(make_tl_object<td_api::sendPaymentForm>(
- as_chat_id(chat_id), as_message_id(message_id), order_info_id, shipping_option_id,
- make_tl_object<td_api::inputCredentialsSaved>(saved_credentials_id)));
+ get_args(args, input_invoice, tip_amount, payment_form_id, order_info_id, shipping_option_id,
+ saved_credentials_id);
+ send_request(td_api::make_object<td_api::sendPaymentForm>(
+ input_invoice, payment_form_id, order_info_id, shipping_option_id,
+ td_api::make_object<td_api::inputCredentialsSaved>(saved_credentials_id), tip_amount));
} else if (op == "spfn") {
- string chat_id;
- string message_id;
+ InputInvoice input_invoice;
+ int64 tip_amount;
+ int64 payment_form_id;
string order_info_id;
string shipping_option_id;
string data;
-
- std::tie(chat_id, args) = split(args);
- std::tie(message_id, args) = split(args);
- std::tie(order_info_id, args) = split(args);
- std::tie(shipping_option_id, data) = split(args);
- send_request(make_tl_object<td_api::sendPaymentForm>(as_chat_id(chat_id), as_message_id(message_id),
- order_info_id, shipping_option_id,
- make_tl_object<td_api::inputCredentialsNew>(data, true)));
+ get_args(args, input_invoice, tip_amount, payment_form_id, order_info_id, shipping_option_id, data);
+ send_request(td_api::make_object<td_api::sendPaymentForm>(
+ input_invoice, payment_form_id, order_info_id, shipping_option_id,
+ td_api::make_object<td_api::inputCredentialsNew>(data, true), tip_amount));
} else if (op == "gpre") {
- string chat_id;
- string message_id;
-
- std::tie(chat_id, message_id) = split(args);
- send_request(make_tl_object<td_api::getPaymentReceipt>(as_chat_id(chat_id), as_message_id(message_id)));
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::getPaymentReceipt>(chat_id, message_id));
} else if (op == "gsoi") {
- send_request(make_tl_object<td_api::getSavedOrderInfo>());
+ send_request(td_api::make_object<td_api::getSavedOrderInfo>());
} else if (op == "dsoi") {
- send_request(make_tl_object<td_api::deleteSavedOrderInfo>());
+ send_request(td_api::make_object<td_api::deleteSavedOrderInfo>());
} else if (op == "dsc") {
- send_request(make_tl_object<td_api::deleteSavedCredentials>());
+ send_request(td_api::make_object<td_api::deleteSavedCredentials>());
+ // } else if (op == "stlsr") {
+ // send_request(td_api::make_object<td_api::sendTonLiteServerRequest>());
+ // } else if (op == "gtwps") {
+ // send_request(td_api::make_object<td_api::getTonWalletPasswordSalt>());
} else if (op == "gpr") {
- send_request(make_tl_object<td_api::getUserPrivacySettingRules>(get_user_privacy_setting(args)));
+ send_request(td_api::make_object<td_api::getUserPrivacySettingRules>(as_user_privacy_setting(args)));
} else if (op == "spr") {
string setting;
string allow;
- std::tie(setting, allow) = split(args);
-
- std::vector<tl_object_ptr<td_api::UserPrivacySettingRule>> rules;
- if (as_bool(allow)) {
- rules.push_back(make_tl_object<td_api::userPrivacySettingRuleAllowAll>());
- } else {
- rules.push_back(make_tl_object<td_api::userPrivacySettingRuleRestrictAll>());
- }
- send_request(make_tl_object<td_api::setUserPrivacySettingRules>(
- get_user_privacy_setting(setting), make_tl_object<td_api::userPrivacySettingRules>(std::move(rules))));
+ string ids;
+ get_args(args, setting, allow, ids);
+ send_request(td_api::make_object<td_api::setUserPrivacySettingRules>(as_user_privacy_setting(setting),
+ as_user_privacy_setting_rules(allow, ids)));
} else if (op == "cp" || op == "ChangePhone") {
- send_request(make_tl_object<td_api::changePhoneNumber>(args, false, false));
+ send_request(td_api::make_object<td_api::changePhoneNumber>(args, nullptr));
} else if (op == "ccpc" || op == "CheckChangePhoneCode") {
- send_request(make_tl_object<td_api::checkChangePhoneNumberCode>(args));
+ send_request(td_api::make_object<td_api::checkChangePhoneNumberCode>(args));
} else if (op == "rcpc" || op == "ResendChangePhoneCode") {
- send_request(make_tl_object<td_api::resendChangePhoneNumberCode>());
- } else if (op == "GetContacts") {
- auto limit = to_integer<int32>(args);
- if (limit == 0) {
- limit = 10000;
- }
- send_request(make_tl_object<td_api::searchContacts>("", limit));
- } else if (op == "ImportContacts") {
+ send_request(td_api::make_object<td_api::resendChangePhoneNumberCode>());
+ } else if (op == "gco") {
+ if (args.empty()) {
+ send_request(td_api::make_object<td_api::getContacts>());
+ } else {
+ send_request(td_api::make_object<td_api::searchContacts>("", as_limit(args)));
+ }
+ } else if (op == "AddContact") {
+ UserId user_id;
+ string first_name;
+ string last_name;
+ get_args(args, user_id, first_name, last_name);
+ send_request(td_api::make_object<td_api::addContact>(
+ td_api::make_object<td_api::contact>(string(), first_name, last_name, string(), user_id), false));
+ } else if (op == "subpn") {
+ string phone_number;
+ get_args(args, phone_number);
+ send_request(td_api::make_object<td_api::searchUserByPhoneNumber>(phone_number));
+ } else if (op == "spn") {
+ UserId user_id;
+ get_args(args, user_id);
+ send_request(td_api::make_object<td_api::sharePhoneNumber>(user_id));
+ } else if (op == "ImportContacts" || op == "cic") {
vector<string> contacts_str = full_split(args, ';');
- vector<tl_object_ptr<td_api::contact>> contacts;
+ vector<td_api::object_ptr<td_api::contact>> contacts;
for (auto c : contacts_str) {
string phone_number;
string first_name;
string last_name;
std::tie(phone_number, c) = split(c, ',');
std::tie(first_name, last_name) = split(c, ',');
- contacts.push_back(make_tl_object<td_api::contact>(phone_number, first_name, last_name, 0));
+ contacts.push_back(td_api::make_object<td_api::contact>(phone_number, first_name, last_name, string(), 0));
+ }
+ if (op == "cic") {
+ send_request(td_api::make_object<td_api::changeImportedContacts>(std::move(contacts)));
+ } else {
+ send_request(td_api::make_object<td_api::importContacts>(std::move(contacts)));
}
-
- send_request(make_tl_object<td_api::importContacts>(std::move(contacts)));
} else if (op == "RemoveContacts") {
- send_request(make_tl_object<td_api::removeContacts>(as_user_ids(args)));
+ send_request(td_api::make_object<td_api::removeContacts>(as_user_ids(args)));
} else if (op == "gicc") {
- send_request(make_tl_object<td_api::getImportedContactCount>());
- } else if (op == "cic") {
- vector<string> contacts_str = full_split(args, ';');
- vector<tl_object_ptr<td_api::contact>> contacts;
- for (auto c : contacts_str) {
- string phone_number;
- string first_name;
- string last_name;
- std::tie(phone_number, c) = split(c, ',');
- std::tie(first_name, last_name) = split(c, ',');
- contacts.push_back(make_tl_object<td_api::contact>(phone_number, first_name, last_name, 0));
- }
-
- send_request(make_tl_object<td_api::changeImportedContacts>(std::move(contacts)));
+ send_request(td_api::make_object<td_api::getImportedContactCount>());
} else if (op == "ClearImportedContacts") {
- send_request(make_tl_object<td_api::clearImportedContacts>());
+ send_request(td_api::make_object<td_api::clearImportedContacts>());
} else {
op_not_found_count++;
}
- if (op == "gc" || op == "GetChats") {
- string offset_order_string;
- string offset_chat_id;
+ if (op == "gc" || op == "gca" || begins_with(op, "gc-")) {
+ send_request(td_api::make_object<td_api::getChats>(as_chat_list(op), as_limit(args, 10000)));
+ } else if (op == "lc" || op == "lca" || begins_with(op, "lc-")) {
+ send_request(td_api::make_object<td_api::loadChats>(as_chat_list(op), as_limit(args, 10000)));
+ } else if (op == "gctest") {
+ send_request(td_api::make_object<td_api::getChats>(nullptr, 1));
+ send_request(td_api::make_object<td_api::getChats>(nullptr, 10));
+ send_request(td_api::make_object<td_api::getChats>(nullptr, 5));
+ } else if (op == "gcc" || op == "GetCommonChats") {
+ UserId user_id;
+ ChatId offset_chat_id;
string limit;
-
- std::tie(limit, args) = split(args);
- std::tie(offset_order_string, offset_chat_id) = split(args);
-
- if (limit.empty()) {
- limit = "10000";
+ get_args(args, user_id, offset_chat_id, limit);
+ send_request(td_api::make_object<td_api::getGroupsInCommon>(user_id, offset_chat_id, as_limit(limit, 100)));
+ } else if (op == "gh" || op == "GetHistory" || op == "ghl" || op == "gmth") {
+ ChatId chat_id;
+ MessageId thread_message_id;
+ MessageId from_message_id;
+ int32 offset;
+ string limit;
+ if (op == "gmth") {
+ get_args(args, thread_message_id, args);
}
- int64 offset_order;
- if (offset_order_string.empty()) {
- offset_order = std::numeric_limits<int64>::max();
+ get_args(args, chat_id, from_message_id, offset, limit);
+ if (op == "gmth") {
+ send_request(td_api::make_object<td_api::getMessageThreadHistory>(chat_id, thread_message_id, from_message_id,
+ offset, as_limit(limit)));
} else {
- offset_order = to_integer<int64>(offset_order_string);
+ send_request(td_api::make_object<td_api::getChatHistory>(chat_id, from_message_id, offset, as_limit(limit),
+ op == "ghl"));
}
- send_request(
- make_tl_object<td_api::getChats>(offset_order, as_chat_id(offset_chat_id), to_integer<int32>(limit)));
- } else if (op == "gcc" || op == "GetCommonChats") {
- string user_id;
- string offset_chat_id;
+ } else if (op == "gcsm") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getChatScheduledMessages>(chat_id));
+ } else if (op == "sdrt") {
+ string reaction;
+ get_args(args, reaction);
+ send_request(td_api::make_object<td_api::setDefaultReactionType>(as_reaction_type(reaction)));
+ } else if (op == "ger") {
+ string emoji;
+ get_args(args, emoji);
+ send_request(td_api::make_object<td_api::getEmojiReaction>(emoji));
+ } else if (op == "gcera") {
+ send_request(td_api::make_object<td_api::getCustomEmojiReactionAnimations>());
+ } else if (op == "gmar") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::getMessageAvailableReactions>(chat_id, message_id, 8));
+ } else if (op == "crr") {
+ send_request(td_api::make_object<td_api::clearRecentReactions>());
+ } else if (op == "amr" || op == "react") {
+ ChatId chat_id;
+ MessageId message_id;
+ string reaction;
+ bool is_big;
+ bool update_recent_reactions;
+ get_args(args, chat_id, message_id, reaction, is_big, update_recent_reactions);
+ send_request(td_api::make_object<td_api::addMessageReaction>(chat_id, message_id, as_reaction_type(reaction),
+ is_big, update_recent_reactions));
+ } else if (op == "rmr") {
+ ChatId chat_id;
+ MessageId message_id;
+ string reaction;
+ get_args(args, chat_id, message_id, reaction);
+ send_request(td_api::make_object<td_api::removeMessageReaction>(chat_id, message_id, as_reaction_type(reaction)));
+ } else if (op == "gmars") {
+ ChatId chat_id;
+ MessageId message_id;
+ string reaction;
+ string offset;
string limit;
-
- std::tie(user_id, args) = split(args);
- std::tie(offset_chat_id, limit) = split(args);
-
- if (limit.empty()) {
- limit = "100";
- }
- send_request(make_tl_object<td_api::getGroupsInCommon>(as_user_id(user_id), as_chat_id(offset_chat_id),
- to_integer<int32>(limit)));
- } else if (op == "gh" || op == "GetHistory" || op == "ghl") {
- string chat_id;
- string from_message_id;
+ get_args(args, chat_id, message_id, reaction, offset, limit);
+ send_request(td_api::make_object<td_api::getMessageAddedReactions>(
+ chat_id, message_id, as_reaction_type(reaction), offset, as_limit(limit)));
+ } else if (op == "gmpf") {
+ ChatId chat_id;
+ MessageId message_id;
string offset;
string limit;
-
- std::tie(chat_id, args) = split(args);
- std::tie(from_message_id, args) = split(args);
- if (from_message_id.empty()) {
- from_message_id = "0";
- }
- std::tie(offset, args) = split(args);
- if (offset.empty()) {
- offset = "0";
- }
- std::tie(limit, args) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
- if (!args.empty()) {
- LOG(ERROR) << "Wrong parameters to function getChatHistory specified";
- } else {
- send_request(make_tl_object<td_api::getChatHistory>(as_chat_id(chat_id), as_message_id(from_message_id),
- to_integer<int32>(offset), to_integer<int32>(limit),
- op == "ghl"));
- }
+ get_args(args, chat_id, message_id, offset, limit);
+ send_request(td_api::make_object<td_api::getMessagePublicForwards>(chat_id, message_id, offset, as_limit(limit)));
} else if (op == "ghf") {
- get_history_chat_id = as_chat_id(args);
-
- send_request(make_tl_object<td_api::getChatHistory>(get_history_chat_id, std::numeric_limits<int64>::max(), 0,
- 100, false));
+ get_history_chat_id_ = as_chat_id(args);
+ send_request(td_api::make_object<td_api::getChatHistory>(get_history_chat_id_, std::numeric_limits<int64>::max(),
+ 0, 100, false));
+ } else if (op == "replies") {
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
+ string filter;
+ get_args(args, chat_id, message_thread_id, filter);
+ send_request(td_api::make_object<td_api::searchChatMessages>(
+ chat_id, "", nullptr, 0, 0, 100, as_search_messages_filter(filter), message_thread_id));
} else if (op == "spvf") {
- search_chat_id = as_chat_id(args);
-
- send_request(make_tl_object<td_api::searchChatMessages>(
- search_chat_id, "", 0, 0, 0, 100, make_tl_object<td_api::searchMessagesFilterPhotoAndVideo>()));
- } else if (op == "Search") {
- string from_date;
- string limit;
+ search_chat_id_ = as_chat_id(args);
+ send_request(td_api::make_object<td_api::searchChatMessages>(search_chat_id_, "", nullptr, 0, 0, 100,
+ as_search_messages_filter("pvi"), 0));
+ } else if (op == "Search" || op == "SearchA" || op == "SearchM") {
string query;
-
- std::tie(query, args) = split(args);
- std::tie(limit, from_date) = split(args);
- if (from_date.empty()) {
- from_date = "0";
- }
- send_request(make_tl_object<td_api::searchMessages>(query, to_integer<int32>(from_date), 2147482647, 0,
- to_integer<int32>(limit)));
- } else if (op == "SCM") {
- string chat_id;
string limit;
- string query;
-
- std::tie(chat_id, args) = split(args);
- std::tie(limit, query) = split(args);
- if (limit.empty()) {
- limit = "10";
+ string filter;
+ int32 from_date;
+ get_args(args, query, limit, filter, from_date);
+ td_api::object_ptr<td_api::ChatList> chat_list;
+ if (op == "SearchA") {
+ chat_list = td_api::make_object<td_api::chatListArchive>();
}
-
- send_request(make_tl_object<td_api::searchChatMessages>(as_chat_id(chat_id), query, 0, 0, 0,
- to_integer<int32>(limit), nullptr));
+ if (op == "SearchM") {
+ chat_list = td_api::make_object<td_api::chatListMain>();
+ }
+ send_request(td_api::make_object<td_api::searchMessages>(std::move(chat_list), query, from_date, 2147483647, 0,
+ as_limit(limit), as_search_messages_filter(filter), 1,
+ 2147483647));
+ } else if (op == "SCM") {
+ ChatId chat_id;
+ SearchQuery query;
+ get_args(args, chat_id, query);
+ send_request(td_api::make_object<td_api::searchChatMessages>(chat_id, query.query, nullptr, 0, 0, query.limit,
+ nullptr, 0));
} else if (op == "SMME") {
- string chat_id;
+ ChatId chat_id;
string limit;
-
- std::tie(chat_id, limit) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
-
- send_request(make_tl_object<td_api::searchChatMessages>(as_chat_id(chat_id), "", my_id_, 0, 0,
- to_integer<int32>(limit), nullptr));
- } else if (op == "SM") {
- string chat_id;
- string offset_message_id;
- string offset;
+ get_args(args, chat_id, limit);
+ send_request(td_api::make_object<td_api::searchChatMessages>(
+ chat_id, "", td_api::make_object<td_api::messageSenderUser>(my_id_), 0, 0, as_limit(limit), nullptr, 0));
+ } else if (op == "SMU" || op == "SMC") {
+ ChatId chat_id;
+ string sender_id;
+ MessageId from_message_id;
string limit;
+ get_args(args, chat_id, sender_id, from_message_id, limit);
+ send_request(td_api::make_object<td_api::searchChatMessages>(chat_id, "", as_message_sender(sender_id),
+ from_message_id, 0, as_limit(limit), nullptr, 0));
+ } else if (op == "SM") {
+ ChatId chat_id;
string filter;
-
- std::tie(chat_id, args) = split(args);
- std::tie(filter, args) = split(args);
- std::tie(limit, args) = split(args);
- std::tie(offset_message_id, offset) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
- if (offset_message_id.empty()) {
- offset_message_id = "0";
- }
- if (offset.empty()) {
- offset = "0";
- }
-
- send_request(make_tl_object<td_api::searchChatMessages>(
- as_chat_id(chat_id), "", 0, as_message_id(offset_message_id), to_integer<int32>(offset),
- to_integer<int32>(limit), get_search_messages_filter(filter)));
+ string limit;
+ MessageId offset_message_id;
+ int32 offset;
+ get_args(args, chat_id, filter, limit, offset_message_id, offset);
+ send_request(td_api::make_object<td_api::searchChatMessages>(
+ chat_id, "", nullptr, offset_message_id, offset, as_limit(limit), as_search_messages_filter(filter), 0));
} else if (op == "SC") {
string limit;
- string offset_message_id;
- string only_missed;
-
- std::tie(limit, args) = split(args);
- std::tie(offset_message_id, only_missed) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
- if (offset_message_id.empty()) {
- offset_message_id = "0";
- }
-
- send_request(make_tl_object<td_api::searchCallMessages>(as_message_id(offset_message_id),
- to_integer<int32>(limit), as_bool(only_missed)));
+ MessageId offset_message_id;
+ bool only_missed;
+ get_args(args, limit, offset_message_id, only_missed);
+ send_request(td_api::make_object<td_api::searchCallMessages>(offset_message_id, as_limit(limit), only_missed));
+ } else if (op == "sodm") {
+ SearchQuery query;
+ get_args(args, query);
+ send_request(td_api::make_object<td_api::searchOutgoingDocumentMessages>(query.query, query.limit));
+ } else if (op == "DeleteAllCallMessages") {
+ bool revoke = as_bool(args);
+ send_request(td_api::make_object<td_api::deleteAllCallMessages>(revoke));
} else if (op == "SCRLM") {
- string chat_id;
- string limit;
-
- std::tie(chat_id, limit) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
-
- send_request(
- make_tl_object<td_api::searchChatRecentLocationMessages>(as_chat_id(chat_id), to_integer<int32>(limit)));
- } else if (op == "SearchAudio") {
- string chat_id;
- string offset_message_id;
- string limit;
- string query;
-
- std::tie(chat_id, args) = split(args);
- std::tie(offset_message_id, args) = split(args);
- if (offset_message_id.empty()) {
- offset_message_id = "0";
- }
- std::tie(limit, query) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::searchChatMessages>(
- as_chat_id(chat_id), query, 0, as_message_id(offset_message_id), 0, to_integer<int32>(limit),
- make_tl_object<td_api::searchMessagesFilterAudio>()));
- } else if (op == "SearchDocument") {
- string chat_id;
- string offset_message_id;
- string limit;
- string query;
-
- std::tie(chat_id, args) = split(args);
- std::tie(offset_message_id, args) = split(args);
- if (offset_message_id.empty()) {
- offset_message_id = "0";
- }
- std::tie(limit, query) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::searchChatMessages>(
- as_chat_id(chat_id), query, 0, to_integer<int64>(offset_message_id), 0, to_integer<int32>(limit),
- make_tl_object<td_api::searchMessagesFilterDocument>()));
- } else if (op == "SearchPhoto") {
- string chat_id;
- string offset_message_id;
+ ChatId chat_id;
string limit;
- string query;
-
- std::tie(chat_id, args) = split(args);
- std::tie(offset_message_id, args) = split(args);
- if (offset_message_id.empty()) {
- offset_message_id = "2000000000000000000";
- }
- std::tie(limit, query) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::searchChatMessages>(
- as_chat_id(chat_id), query, 0, as_message_id(offset_message_id), 0, to_integer<int32>(limit),
- make_tl_object<td_api::searchMessagesFilterPhoto>()));
- } else if (op == "SearchChatPhoto") {
- string chat_id;
- string offset_message_id;
+ get_args(args, chat_id, limit);
+ send_request(td_api::make_object<td_api::searchChatRecentLocationMessages>(chat_id, as_limit(limit)));
+ } else if (op == "gcmca") {
+ ChatId chat_id;
+ string filter;
+ MessageId from_message_id;
+ get_args(args, chat_id, filter, from_message_id);
+ send_request(td_api::make_object<td_api::getChatMessageCalendar>(chat_id, as_search_messages_filter(filter),
+ from_message_id));
+ } else if (op == "SearchAudio" || op == "SearchDocument" || op == "SearchPhoto" || op == "SearchChatPhoto") {
+ ChatId chat_id;
+ MessageId offset_message_id;
+ SearchQuery query;
+ get_args(args, chat_id, offset_message_id, query);
+ send_request(td_api::make_object<td_api::searchChatMessages>(chat_id, query.query, nullptr, offset_message_id, 0,
+ query.limit, as_search_messages_filter(op), 0));
+ } else if (op == "gcmbd") {
+ ChatId chat_id;
+ int32 date;
+ get_args(args, chat_id, date);
+ send_request(td_api::make_object<td_api::getChatMessageByDate>(chat_id, date));
+ } else if (op == "gcsmp") {
+ ChatId chat_id;
+ string filter;
+ MessageId from_message_id;
string limit;
- string query;
-
- std::tie(chat_id, args) = split(args);
- std::tie(offset_message_id, args) = split(args);
- if (offset_message_id.empty()) {
- offset_message_id = "2000000000000000000";
- }
- std::tie(limit, query) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::searchChatMessages>(
- as_chat_id(chat_id), query, 0, as_message_id(offset_message_id), 0, to_integer<int32>(limit),
- make_tl_object<td_api::searchMessagesFilterChatPhoto>()));
- } else if (op == "gup" || op == "GetUserPhotos") {
- string user_id;
- string offset;
+ get_args(args, chat_id, filter, from_message_id, limit);
+ send_request(td_api::make_object<td_api::getChatSparseMessagePositions>(
+ chat_id, as_search_messages_filter(filter), from_message_id, as_limit(limit)));
+ } else if (op == "gcmc") {
+ ChatId chat_id;
+ string filter;
+ bool return_local;
+ get_args(args, chat_id, filter, return_local);
+ send_request(
+ td_api::make_object<td_api::getChatMessageCount>(chat_id, as_search_messages_filter(filter), return_local));
+ } else if (op == "gcmp") {
+ ChatId chat_id;
+ MessageId message_id;
+ string filter;
+ MessageThreadId message_thread_id;
+ get_args(args, chat_id, message_id, filter, message_thread_id);
+ send_request(td_api::make_object<td_api::getChatMessagePosition>(
+ chat_id, message_id, as_search_messages_filter(filter), message_thread_id));
+ } else if (op == "gup" || op == "gupp") {
+ UserId user_id;
+ int32 offset;
string limit;
-
- std::tie(user_id, args) = split(args);
- std::tie(offset, args) = split(args);
- if (offset.empty()) {
- offset = "0";
- }
- std::tie(limit, args) = split(args);
- if (limit.empty()) {
- limit = "10";
- }
- if (!args.empty()) {
- LOG(ERROR) << "Wrong parameters to function getUserProfilePhotos specified";
+ get_args(args, user_id, offset, limit);
+ send_request(td_api::make_object<td_api::getUserProfilePhotos>(user_id, offset, as_limit(limit)));
+ } else if (op == "dcrm") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::deleteChatReplyMarkup>(chat_id, message_id));
+ } else if (op == "glti") {
+ send_request(td_api::make_object<td_api::getLocalizationTargetInfo>(as_bool(args)));
+ } else if (op == "glpi") {
+ send_request(td_api::make_object<td_api::getLanguagePackInfo>(args));
+ } else if (op == "glps") {
+ string language_code;
+ string keys;
+ get_args(args, language_code, keys);
+ send_request(td_api::make_object<td_api::getLanguagePackStrings>(language_code, autosplit_str(keys)));
+ } else if (op == "glpss") {
+ string language_database_path;
+ string language_pack;
+ string language_code;
+ string key;
+ get_args(args, language_database_path, language_pack, language_code, key);
+ send_request(td_api::make_object<td_api::getLanguagePackString>(language_database_path, language_pack,
+ language_code, key));
+ } else if (op == "synclp") {
+ const string &language_code = args;
+ send_request(td_api::make_object<td_api::synchronizeLanguagePack>(language_code));
+ } else if (op == "acslp") {
+ const string &language_code = args;
+ send_request(td_api::make_object<td_api::addCustomServerLanguagePack>(language_code));
+ } else if (op == "sclp") {
+ string language_code;
+ string name;
+ string native_name;
+ string key;
+ get_args(args, language_code, name, native_name, key);
+ vector<td_api::object_ptr<td_api::languagePackString>> strings;
+ strings.push_back(td_api::make_object<td_api::languagePackString>(
+ key, td_api::make_object<td_api::languagePackStringValueOrdinary>("Ordinary value")));
+ strings.push_back(td_api::make_object<td_api::languagePackString>(
+ "Plu", td_api::make_object<td_api::languagePackStringValuePluralized>("Zero", string("One\0One", 7), "Two",
+ "Few", "Many", "Other")));
+ strings.push_back(td_api::make_object<td_api::languagePackString>(
+ "DELETED", td_api::make_object<td_api::languagePackStringValueDeleted>()));
+ send_request(td_api::make_object<td_api::setCustomLanguagePack>(
+ as_language_pack_info(language_code, name, native_name), std::move(strings)));
+ } else if (op == "eclpi") {
+ string language_code;
+ string name;
+ string native_name;
+ get_args(args, language_code, name, native_name);
+ send_request(td_api::make_object<td_api::editCustomLanguagePackInfo>(
+ as_language_pack_info(language_code, name, native_name)));
+ } else if (op == "sclpsv" || op == "sclpsp" || op == "sclpsd") {
+ string language_code;
+ string key;
+ string value;
+ get_args(args, language_code, key, value);
+ td_api::object_ptr<td_api::languagePackString> str =
+ td_api::make_object<td_api::languagePackString>(key, nullptr);
+ if (op == "sclsv") {
+ str->value_ = td_api::make_object<td_api::languagePackStringValueOrdinary>(value);
+ } else if (op == "sclsp") {
+ str->value_ = td_api::make_object<td_api::languagePackStringValuePluralized>(value, string("One\0One", 7),
+ "Two", "Few", "Many", "Other");
} else {
- send_request(make_tl_object<td_api::getUserProfilePhotos>(as_user_id(user_id), to_integer<int32>(offset),
- to_integer<int32>(limit)));
+ str->value_ = td_api::make_object<td_api::languagePackStringValueDeleted>();
}
- } else if (op == "dcrm") {
- string chat_id;
- string message_id;
-
- std::tie(chat_id, message_id) = split(args);
- send_request(make_tl_object<td_api::deleteChatReplyMarkup>(as_chat_id(chat_id), as_message_id(message_id)));
+ send_request(td_api::make_object<td_api::setCustomLanguagePackString>(language_code, std::move(str)));
+ } else if (op == "dlp") {
+ send_request(td_api::make_object<td_api::deleteLanguagePack>(args));
+ } else if (op == "on" || op == "off") {
+ send_request(td_api::make_object<td_api::setOption>("online",
+ td_api::make_object<td_api::optionValueBoolean>(op == "on")));
} else if (op == "go") {
- send_request(make_tl_object<td_api::getOption>(args));
+ send_request(td_api::make_object<td_api::getOption>(args));
+ } else if (op == "gos") {
+ execute(td_api::make_object<td_api::getOption>(args));
} else if (op == "sob") {
string name;
- string value;
-
- std::tie(name, value) = split(args);
- send_request(make_tl_object<td_api::setOption>(name, make_tl_object<td_api::optionValueBoolean>(as_bool(value))));
+ bool value;
+ get_args(args, name, value);
+ send_request(
+ td_api::make_object<td_api::setOption>(name, td_api::make_object<td_api::optionValueBoolean>(value)));
} else if (op == "soe") {
- send_request(make_tl_object<td_api::setOption>(args, make_tl_object<td_api::optionValueEmpty>()));
+ send_request(td_api::make_object<td_api::setOption>(args, td_api::make_object<td_api::optionValueEmpty>()));
} else if (op == "soi") {
string name;
- string value;
-
- std::tie(name, value) = split(args);
- int32 value_int = to_integer<int32>(value);
- send_request(make_tl_object<td_api::setOption>(name, make_tl_object<td_api::optionValueInteger>(value_int)));
+ int64 value;
+ get_args(args, name, value);
+ send_request(
+ td_api::make_object<td_api::setOption>(name, td_api::make_object<td_api::optionValueInteger>(value)));
} else if (op == "sos") {
string name;
string value;
-
- std::tie(name, value) = split(args);
- send_request(make_tl_object<td_api::setOption>(name, make_tl_object<td_api::optionValueString>(value)));
+ get_args(args, name, value);
+ send_request(td_api::make_object<td_api::setOption>(name, td_api::make_object<td_api::optionValueString>(value)));
} else if (op == "me") {
- send_request(make_tl_object<td_api::getMe>());
+ send_request(td_api::make_object<td_api::getMe>());
} else if (op == "sattl") {
- send_request(make_tl_object<td_api::setAccountTtl>(make_tl_object<td_api::accountTtl>(to_integer<int32>(args))));
+ int32 days;
+ get_args(args, days);
+ send_request(td_api::make_object<td_api::setAccountTtl>(td_api::make_object<td_api::accountTtl>(days)));
} else if (op == "gattl") {
- send_request(make_tl_object<td_api::getAccountTtl>());
- } else if (op == "GetActiveSessions") {
- send_request(make_tl_object<td_api::getActiveSessions>());
+ send_request(td_api::make_object<td_api::getAccountTtl>());
+ } else if (op == "GetActiveSessions" || op == "devices" || op == "sessions") {
+ send_request(td_api::make_object<td_api::getActiveSessions>());
} else if (op == "TerminateSession") {
- send_request(make_tl_object<td_api::terminateSession>(to_integer<int64>(args)));
+ int64 session_id;
+ get_args(args, session_id);
+ send_request(td_api::make_object<td_api::terminateSession>(session_id));
} else if (op == "TerminateAllOtherSessions") {
- send_request(make_tl_object<td_api::terminateAllOtherSessions>());
+ send_request(td_api::make_object<td_api::terminateAllOtherSessions>());
+ } else if (op == "tscac") {
+ int64 session_id;
+ bool can_accept_calls;
+ get_args(args, session_id, can_accept_calls);
+ send_request(td_api::make_object<td_api::toggleSessionCanAcceptCalls>(session_id, can_accept_calls));
+ } else if (op == "tscasc") {
+ int64 session_id;
+ bool can_accept_secret_chats;
+ get_args(args, session_id, can_accept_secret_chats);
+ send_request(td_api::make_object<td_api::toggleSessionCanAcceptSecretChats>(session_id, can_accept_secret_chats));
+ } else if (op == "sist") {
+ int32 inactive_session_ttl_days;
+ get_args(args, inactive_session_ttl_days);
+ send_request(td_api::make_object<td_api::setInactiveSessionTtl>(inactive_session_ttl_days));
} else if (op == "gcw") {
- send_request(make_tl_object<td_api::getConnectedWebsites>());
+ send_request(td_api::make_object<td_api::getConnectedWebsites>());
} else if (op == "dw") {
- send_request(make_tl_object<td_api::disconnectWebsite>(to_integer<int64>(args)));
+ int64 website_id;
+ get_args(args, website_id);
+ send_request(td_api::make_object<td_api::disconnectWebsite>(website_id));
} else if (op == "daw") {
- send_request(make_tl_object<td_api::disconnectAllWebsites>());
- } else if (op == "gw") {
- send_request(make_tl_object<td_api::getWallpapers>());
- } else if (op == "gccode") {
- send_request(make_tl_object<td_api::getCountryCode>());
- } else if (op == "git") {
- send_request(make_tl_object<td_api::getInviteText>());
- } else if (op == "gtos") {
- send_request(make_tl_object<td_api::getTermsOfService>());
+ send_request(td_api::make_object<td_api::disconnectAllWebsites>());
+ } else if (op == "gbgs") {
+ send_request(td_api::make_object<td_api::getBackgrounds>(as_bool(args)));
+ } else if (op == "gbgu") {
+ send_get_background_url(td_api::make_object<td_api::backgroundTypeWallpaper>(false, false));
+ send_get_background_url(td_api::make_object<td_api::backgroundTypeWallpaper>(false, true));
+ send_get_background_url(td_api::make_object<td_api::backgroundTypeWallpaper>(true, false));
+ send_get_background_url(td_api::make_object<td_api::backgroundTypeWallpaper>(true, true));
+ send_get_background_url(as_solid_pattern_background(-1, 0, false));
+ send_get_background_url(as_solid_pattern_background(0x1000000, 0, true));
+ send_get_background_url(as_solid_pattern_background(0, -1, false));
+ send_get_background_url(as_solid_pattern_background(0, 101, false));
+ send_get_background_url(as_solid_pattern_background(0, 0, false));
+ send_get_background_url(as_solid_pattern_background(0xFFFFFF, 100, true));
+ send_get_background_url(as_solid_pattern_background(0xABCDEF, 49, true));
+ send_get_background_url(as_gradient_pattern_background(0, 0, 0, false, false));
+ send_get_background_url(as_gradient_pattern_background(0, 0, 0, true, false));
+ send_get_background_url(as_gradient_pattern_background(0xFFFFFF, 0, 100, false, true));
+ send_get_background_url(as_gradient_pattern_background(0xFFFFFF, 0, 100, true, true));
+ send_get_background_url(as_gradient_pattern_background(0xABCDEF, 0xFEDCBA, 49, false, true));
+ send_get_background_url(as_gradient_pattern_background(0, 0x1000000, 49, false, true));
+ send_get_background_url(as_freeform_gradient_pattern_background({0xABCDEF, 0xFEDCBA}, 49, false, true));
+ send_get_background_url(as_freeform_gradient_pattern_background({0xABCDEF, 0x111111, 0x222222}, 49, true, true));
+ send_get_background_url(
+ as_freeform_gradient_pattern_background({0xABCDEF, 0xFEDCBA, 0x111111, 0x222222}, 49, false, true));
+ send_get_background_url(as_solid_background(-1));
+ send_get_background_url(as_solid_background(0xABCDEF));
+ send_get_background_url(as_solid_background(0x1000000));
+ send_get_background_url(as_gradient_background(0xABCDEF, 0xFEDCBA));
+ send_get_background_url(as_gradient_background(0, 0));
+ send_get_background_url(as_gradient_background(-1, -1));
+ send_get_background_url(as_freeform_gradient_background({0xFEDCBA, 0x222222}));
+ send_get_background_url(as_freeform_gradient_background({0xFEDCBA, 0x111111, 0x222222}));
+ send_get_background_url(as_freeform_gradient_background({0xABCDEF, 0xFEDCBA, 0x111111, 0x222222}));
+ } else {
+ op_not_found_count++;
+ }
+
+ if (op == "sbg") {
+ send_request(td_api::make_object<td_api::searchBackground>(args));
+ } else if (op == "sbgd") {
+ send_request(td_api::make_object<td_api::setBackground>(nullptr, nullptr, as_bool(args)));
+ } else if (op == "sbgw" || op == "sbgwd") {
+ send_request(td_api::make_object<td_api::setBackground>(
+ td_api::make_object<td_api::inputBackgroundLocal>(as_input_file(args)),
+ td_api::make_object<td_api::backgroundTypeWallpaper>(true, true), op == "sbgwd"));
+ } else if (op == "sbgp" || op == "sbgpd") {
+ send_request(td_api::make_object<td_api::setBackground>(
+ td_api::make_object<td_api::inputBackgroundLocal>(as_input_file(args)),
+ as_solid_pattern_background(0xABCDEF, 49, true), op == "sbgpd"));
+ } else if (op == "sbggp" || op == "sbggpd") {
+ send_request(td_api::make_object<td_api::setBackground>(
+ td_api::make_object<td_api::inputBackgroundLocal>(as_input_file(args)),
+ as_gradient_pattern_background(0xABCDEF, 0xFE, 51, op == "sbggpd", false), op == "sbggpd"));
+ } else if (op == "sbgs" || op == "sbgsd") {
+ int32 color;
+ get_args(args, color);
+ send_request(td_api::make_object<td_api::setBackground>(nullptr, as_solid_background(color), op == "sbgsd"));
+ } else if (op == "sbgg" || op == "sbggd") {
+ int32 top_color;
+ int32 bottom_color;
+ get_args(args, top_color, bottom_color);
+ auto background_type = as_gradient_background(top_color, bottom_color);
+ send_request(td_api::make_object<td_api::setBackground>(nullptr, std::move(background_type), op == "sbggd"));
+ } else if (op == "sbgfg" || op == "sbgfgd") {
+ auto background_type = as_freeform_gradient_background(to_integers<int32>(args));
+ send_request(td_api::make_object<td_api::setBackground>(nullptr, std::move(background_type), op == "sbgfgd"));
+ } else if (op == "sbgfid" || op == "sbgfidd") {
+ int64 background_id;
+ get_args(args, background_id);
+ send_request(td_api::make_object<td_api::setBackground>(
+ td_api::make_object<td_api::inputBackgroundRemote>(background_id), nullptr, op == "sbgfidd"));
+ } else if (op == "sbgwid" || op == "sbgwidd") {
+ int64 background_id;
+ get_args(args, background_id);
+ send_request(td_api::make_object<td_api::setBackground>(
+ td_api::make_object<td_api::inputBackgroundRemote>(background_id),
+ td_api::make_object<td_api::backgroundTypeWallpaper>(true, true), op == "sbgwidd"));
+ } else if (op == "sbgpid" || op == "sbgpidd") {
+ int64 background_id;
+ get_args(args, background_id);
+ send_request(
+ td_api::make_object<td_api::setBackground>(td_api::make_object<td_api::inputBackgroundRemote>(background_id),
+ as_solid_pattern_background(0xabcdef, 49, true), op == "sbgpidd"));
+ } else if (op == "rbg") {
+ int64 background_id;
+ get_args(args, background_id);
+ send_request(td_api::make_object<td_api::removeBackground>(background_id));
+ } else if (op == "rbgs") {
+ send_request(td_api::make_object<td_api::resetBackgrounds>());
+ } else if (op == "gcos") {
+ send_request(td_api::make_object<td_api::getCountries>());
+ } else if (op == "gcoc") {
+ send_request(td_api::make_object<td_api::getCountryCode>());
+ } else if (op == "gpni") {
+ send_request(td_api::make_object<td_api::getPhoneNumberInfo>(args));
+ } else if (op == "gpnis") {
+ execute(td_api::make_object<td_api::getPhoneNumberInfoSync>(rand_bool() ? "en" : "", args));
+ } else if (op == "gadl") {
+ send_request(td_api::make_object<td_api::getApplicationDownloadLink>());
+ } else if (op == "gprl") {
+ auto limit_type = td_api::make_object<td_api::premiumLimitTypeChatFilterCount>();
+ send_request(td_api::make_object<td_api::getPremiumLimit>(std::move(limit_type)));
+ } else if (op == "gprf") {
+ auto source = td_api::make_object<td_api::premiumSourceLimitExceeded>(
+ td_api::make_object<td_api::premiumLimitTypeChatFilterCount>());
+ send_request(td_api::make_object<td_api::getPremiumFeatures>(std::move(source)));
+ } else if (op == "gprse") {
+ send_request(td_api::make_object<td_api::getPremiumStickerExamples>());
+ } else if (op == "vprf") {
+ auto feature = td_api::make_object<td_api::premiumFeatureProfileBadge>();
+ send_request(td_api::make_object<td_api::viewPremiumFeature>(std::move(feature)));
+ } else if (op == "cprsb") {
+ send_request(td_api::make_object<td_api::clickPremiumSubscriptionButton>());
+ } else if (op == "gprs") {
+ send_request(td_api::make_object<td_api::getPremiumState>());
+ } else if (op == "cppr") {
+ send_request(td_api::make_object<td_api::canPurchasePremium>());
+ } else if (op == "atos") {
+ send_request(td_api::make_object<td_api::acceptTermsOfService>(args));
+ } else if (op == "gdli") {
+ send_request(td_api::make_object<td_api::getDeepLinkInfo>(args));
} else if (op == "tme") {
- send_request(make_tl_object<td_api::getRecentlyVisitedTMeUrls>(args));
- } else if (op == "bu") {
- send_request(make_tl_object<td_api::blockUser>(as_user_id(args)));
- } else if (op == "ubu") {
- send_request(make_tl_object<td_api::unblockUser>(as_user_id(args)));
- } else if (op == "gbu") {
- string offset;
+ send_request(td_api::make_object<td_api::getRecentlyVisitedTMeUrls>(args));
+ } else if (op == "gbms") {
+ int32 offset;
string limit;
-
- std::tie(offset, limit) = split(args);
- if (offset.empty()) {
- offset = "0";
- }
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::getBlockedUsers>(to_integer<int32>(offset), to_integer<int32>(limit)));
+ get_args(args, offset, limit);
+ send_request(td_api::make_object<td_api::getBlockedMessageSenders>(offset, as_limit(limit)));
} else if (op == "gu") {
- send_request(make_tl_object<td_api::getUser>(as_user_id(args)));
+ UserId user_id;
+ get_args(args, user_id);
+ send_request(td_api::make_object<td_api::getUser>(user_id));
} else if (op == "gsu") {
- send_request(make_tl_object<td_api::getSupportUser>());
- } else if (op == "gs") {
- string limit;
- string emoji;
- std::tie(limit, emoji) = split(args);
- send_request(make_tl_object<td_api::getStickers>(emoji, to_integer<int32>(limit)));
+ send_request(td_api::make_object<td_api::getSupportUser>());
+ } else if (op == "gs" || op == "gsmm" || op == "gsee" || op == "gseeme") {
+ SearchQuery query;
+ get_args(args, query);
+ send_request(td_api::make_object<td_api::getStickers>(as_sticker_type(op), query.query, query.limit,
+ op == "gseeme" ? my_id_ : 0));
} else if (op == "sst") {
+ SearchQuery query;
+ get_args(args, query);
+ send_request(td_api::make_object<td_api::searchStickers>(query.query, query.limit));
+ } else if (op == "gprst") {
string limit;
- string emoji;
- std::tie(limit, emoji) = split(args);
- send_request(make_tl_object<td_api::searchStickers>(emoji, to_integer<int32>(limit)));
+ get_args(args, limit);
+ send_request(td_api::make_object<td_api::getPremiumStickers>(as_limit(limit)));
} else if (op == "gss") {
- send_request(make_tl_object<td_api::getStickerSet>(to_integer<int64>(args)));
- } else if (op == "giss") {
- send_request(make_tl_object<td_api::getInstalledStickerSets>(as_bool(args)));
- } else if (op == "gass") {
- string is_masks;
- string offset_sticker_set_id;
+ int64 sticker_set_id;
+ get_args(args, sticker_set_id);
+ send_request(td_api::make_object<td_api::getStickerSet>(sticker_set_id));
+ } else if (op == "giss" || op == "gissm" || op == "gisse") {
+ send_request(td_api::make_object<td_api::getInstalledStickerSets>(as_sticker_type(op)));
+ } else if (op == "gass" || op == "gassm" || op == "gasse") {
+ int64 offset_sticker_set_id;
string limit;
-
- std::tie(is_masks, args) = split(args);
- std::tie(offset_sticker_set_id, limit) = split(args);
-
- send_request(make_tl_object<td_api::getArchivedStickerSets>(
- as_bool(is_masks), to_integer<int64>(offset_sticker_set_id), to_integer<int32>(limit)));
- } else if (op == "gtss") {
- send_request(make_tl_object<td_api::getTrendingStickerSets>());
+ get_args(args, offset_sticker_set_id, limit);
+ send_request(td_api::make_object<td_api::getArchivedStickerSets>(as_sticker_type(op), offset_sticker_set_id,
+ as_limit(limit)));
+ } else if (op == "gtss" || op == "gtssm" || op == "gtsse") {
+ int32 offset;
+ string limit;
+ get_args(args, offset, limit);
+ send_request(
+ td_api::make_object<td_api::getTrendingStickerSets>(as_sticker_type(op), offset, as_limit(limit, 1000)));
} else if (op == "gatss") {
- send_request(make_tl_object<td_api::getAttachedStickerSets>(to_integer<int32>(args)));
+ FileId file_id;
+ get_args(args, file_id);
+ send_request(td_api::make_object<td_api::getAttachedStickerSets>(file_id));
} else if (op == "storage") {
- send_request(make_tl_object<td_api::getStorageStatistics>(to_integer<int32>(args)));
+ int32 chat_limit;
+ get_args(args, chat_limit);
+ send_request(td_api::make_object<td_api::getStorageStatistics>(chat_limit));
} else if (op == "storage_fast") {
- send_request(make_tl_object<td_api::getStorageStatisticsFast>());
- } else if (op == "optimize_storage") {
+ send_request(td_api::make_object<td_api::getStorageStatisticsFast>());
+ } else if (op == "database") {
+ send_request(td_api::make_object<td_api::getDatabaseStatistics>());
+ } else if (op == "optimize_storage" || op == "optimize_storage_all") {
string chat_ids;
string exclude_chat_ids;
- string chat_ids_limit;
- std::tie(chat_ids, args) = split(args);
- std::tie(exclude_chat_ids, chat_ids_limit) = split(args);
- send_request(make_tl_object<td_api::optimizeStorage>(
- 10000000, -1, -1, 0, std::vector<tl_object_ptr<td_api::FileType>>(), as_chat_ids(chat_ids, ','),
- as_chat_ids(exclude_chat_ids, ','), to_integer<int32>(chat_ids_limit)));
+ int32 chat_ids_limit;
+ get_args(args, chat_ids, exclude_chat_ids, chat_ids_limit);
+ send_request(td_api::make_object<td_api::optimizeStorage>(
+ 10000000, -1, -1, 0, std::vector<td_api::object_ptr<td_api::FileType>>(), as_chat_ids(chat_ids),
+ as_chat_ids(exclude_chat_ids), op == "optimize_storage", chat_ids_limit));
} else if (op == "clean_storage_default") {
- send_request(make_tl_object<td_api::optimizeStorage>());
+ send_request(td_api::make_object<td_api::optimizeStorage>());
+ } else if (op == "clean_photos") {
+ std::vector<td_api::object_ptr<td_api::FileType>> types;
+ types.emplace_back(td_api::make_object<td_api::fileTypePhoto>());
+ send_request(td_api::make_object<td_api::optimizeStorage>(0, 0, 0, 0, std::move(types), as_chat_ids(""),
+ as_chat_ids(""), true, 20));
} else if (op == "clean_storage") {
- std::vector<tl_object_ptr<td_api::FileType>> types;
- types.push_back(make_tl_object<td_api::fileTypeThumbnail>());
- types.push_back(make_tl_object<td_api::fileTypeProfilePhoto>());
- types.push_back(make_tl_object<td_api::fileTypePhoto>());
- types.push_back(make_tl_object<td_api::fileTypeVoiceNote>());
- types.push_back(make_tl_object<td_api::fileTypeVideo>());
- types.push_back(make_tl_object<td_api::fileTypeDocument>());
- types.push_back(make_tl_object<td_api::fileTypeSecret>());
- types.push_back(make_tl_object<td_api::fileTypeUnknown>());
- types.push_back(make_tl_object<td_api::fileTypeSticker>());
- types.push_back(make_tl_object<td_api::fileTypeAudio>());
- types.push_back(make_tl_object<td_api::fileTypeAnimation>());
- types.push_back(make_tl_object<td_api::fileTypeVideoNote>());
- send_request(make_tl_object<td_api::optimizeStorage>(0, -1, -1, 0, std::move(types), as_chat_ids(args, ','),
- as_chat_ids(""), 20));
+ std::vector<td_api::object_ptr<td_api::FileType>> types;
+ types.emplace_back(td_api::make_object<td_api::fileTypeThumbnail>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeProfilePhoto>());
+ types.emplace_back(td_api::make_object<td_api::fileTypePhoto>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeVoiceNote>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeVideo>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeDocument>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeSecret>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeUnknown>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeSticker>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeAudio>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeAnimation>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeVideoNote>());
+ types.emplace_back(td_api::make_object<td_api::fileTypeSecure>());
+ send_request(td_api::make_object<td_api::optimizeStorage>(0, -1, -1, 0, std::move(types), as_chat_ids(args),
+ as_chat_ids(""), true, 20));
} else if (op == "network") {
- send_request(make_tl_object<td_api::getNetworkStatistics>());
+ send_request(td_api::make_object<td_api::getNetworkStatistics>());
} else if (op == "current_network") {
- send_request(make_tl_object<td_api::getNetworkStatistics>(true));
+ send_request(td_api::make_object<td_api::getNetworkStatistics>(true));
} else if (op == "reset_network") {
- send_request(make_tl_object<td_api::resetNetworkStatistics>());
+ send_request(td_api::make_object<td_api::resetNetworkStatistics>());
} else if (op == "snt") {
- send_request(make_tl_object<td_api::setNetworkType>(get_network_type(args)));
+ send_request(td_api::make_object<td_api::setNetworkType>(as_network_type(args)));
+ } else if (op == "gadsp") {
+ send_request(td_api::make_object<td_api::getAutoDownloadSettingsPresets>());
+ } else if (op == "sads") {
+ send_request(td_api::make_object<td_api::setAutoDownloadSettings>(
+ td_api::make_object<td_api::autoDownloadSettings>(), as_network_type(args)));
} else if (op == "ansc") {
- string sent_bytes;
- string received_bytes;
+ int32 sent_bytes;
+ int32 received_bytes;
string duration;
string network_type;
- std::tie(sent_bytes, args) = split(args);
- std::tie(received_bytes, args) = split(args);
- std::tie(duration, network_type) = split(args);
- send_request(make_tl_object<td_api::addNetworkStatistics>(make_tl_object<td_api::networkStatisticsEntryCall>(
- get_network_type(network_type), to_integer<int32>(sent_bytes), to_integer<int32>(received_bytes),
- to_double(duration))));
+ get_args(args, sent_bytes, received_bytes, duration, network_type);
+ send_request(
+ td_api::make_object<td_api::addNetworkStatistics>(td_api::make_object<td_api::networkStatisticsEntryCall>(
+ as_network_type(network_type), sent_bytes, received_bytes, to_double(duration))));
} else if (op == "ans") {
- string sent_bytes;
- string received_bytes;
+ int32 sent_bytes;
+ int32 received_bytes;
string network_type;
- std::tie(sent_bytes, args) = split(args);
- std::tie(received_bytes, network_type) = split(args);
- send_request(make_tl_object<td_api::addNetworkStatistics>(make_tl_object<td_api::networkStatisticsEntryFile>(
- make_tl_object<td_api::fileTypeDocument>(), get_network_type(network_type), to_integer<int32>(sent_bytes),
- to_integer<int32>(received_bytes))));
- } else if (op == "top_chats") {
- send_request(make_tl_object<td_api::getTopChats>(get_top_chat_category(args), 50));
+ get_args(args, sent_bytes, received_bytes, network_type);
+ send_request(
+ td_api::make_object<td_api::addNetworkStatistics>(td_api::make_object<td_api::networkStatisticsEntryFile>(
+ td_api::make_object<td_api::fileTypeDocument>(), as_network_type(network_type), sent_bytes,
+ received_bytes)));
+ } else if (op == "gtc") {
+ send_request(td_api::make_object<td_api::getTopChats>(as_top_chat_category(args), 50));
} else if (op == "rtc") {
- string chat_id;
+ ChatId chat_id;
string category;
- std::tie(chat_id, category) = split(args);
-
- send_request(make_tl_object<td_api::removeTopChat>(get_top_chat_category(category), as_chat_id(chat_id)));
+ get_args(args, chat_id, category);
+ send_request(td_api::make_object<td_api::removeTopChat>(as_top_chat_category(category), chat_id));
+ } else if (op == "gsssn") {
+ const string &title = args;
+ send_request(td_api::make_object<td_api::getSuggestedStickerSetName>(title));
+ } else if (op == "cssn") {
+ const string &name = args;
+ send_request(td_api::make_object<td_api::checkStickerSetName>(name));
+ } else if (op == "usf" || op == "usfa" || op == "usfv") {
+ send_request(td_api::make_object<td_api::uploadStickerFile>(
+ -1, td_api::make_object<td_api::inputSticker>(as_input_file(args), "😀", as_sticker_format(op),
+ as_mask_position(op))));
+ } else if (op == "cnss" || op == "cnssa" || op == "cnssv" || op == "cnssm" || op == "cnsse") {
+ string title;
+ string name;
+ string stickers;
+ get_args(args, title, name, stickers);
+ auto input_stickers =
+ transform(autosplit(stickers), [op](Slice sticker) -> td_api::object_ptr<td_api::inputSticker> {
+ return td_api::make_object<td_api::inputSticker>(as_input_file(sticker), "😀", as_sticker_format(op),
+ as_mask_position(op));
+ });
+ send_request(td_api::make_object<td_api::createNewStickerSet>(my_id_, title, name, as_sticker_type(op),
+ std::move(input_stickers), "tg_cli"));
} else if (op == "sss") {
- send_request(make_tl_object<td_api::searchStickerSet>(args));
+ send_request(td_api::make_object<td_api::searchStickerSet>(args));
} else if (op == "siss") {
- send_request(make_tl_object<td_api::searchInstalledStickerSets>(false, args, 2));
+ send_request(td_api::make_object<td_api::searchInstalledStickerSets>(nullptr, args, 2));
} else if (op == "ssss") {
- send_request(make_tl_object<td_api::searchStickerSets>(args));
+ send_request(td_api::make_object<td_api::searchStickerSets>(args));
} else if (op == "css") {
- string set_id;
- string is_installed;
- string is_archived;
-
- std::tie(set_id, args) = split(args);
- std::tie(is_installed, is_archived) = split(args);
-
- send_request(make_tl_object<td_api::changeStickerSet>(to_integer<int64>(set_id), as_bool(is_installed),
- as_bool(is_archived)));
+ int64 set_id;
+ bool is_installed;
+ bool is_archived;
+ get_args(args, set_id, is_installed, is_archived);
+ send_request(td_api::make_object<td_api::changeStickerSet>(set_id, is_installed, is_archived));
} else if (op == "vtss") {
- send_request(make_tl_object<td_api::viewTrendingStickerSets>(to_integers<int64>(args)));
- } else if (op == "riss") {
- string is_masks;
+ send_request(td_api::make_object<td_api::viewTrendingStickerSets>(to_integers<int64>(args)));
+ } else if (op == "riss" || op == "rissm" || op == "risse") {
string new_order;
-
- std::tie(is_masks, new_order) = split(args);
-
+ get_args(args, new_order);
send_request(
- make_tl_object<td_api::reorderInstalledStickerSets>(as_bool(is_masks), to_integers<int64>(new_order)));
+ td_api::make_object<td_api::reorderInstalledStickerSets>(as_sticker_type(op), to_integers<int64>(new_order)));
} else if (op == "grs") {
- send_request(make_tl_object<td_api::getRecentStickers>(as_bool(args)));
+ send_request(td_api::make_object<td_api::getRecentStickers>(as_bool(args)));
} else if (op == "ars") {
- string is_attached;
+ bool is_attached;
string sticker_id;
-
- std::tie(is_attached, sticker_id) = split(args);
-
- send_request(make_tl_object<td_api::addRecentSticker>(as_bool(is_attached), as_input_file_id(sticker_id)));
+ get_args(args, is_attached, sticker_id);
+ send_request(td_api::make_object<td_api::addRecentSticker>(is_attached, as_input_file_id(sticker_id)));
} else if (op == "rrs") {
- string is_attached;
+ bool is_attached;
string sticker_id;
-
- std::tie(is_attached, sticker_id) = split(args);
-
- send_request(make_tl_object<td_api::removeRecentSticker>(as_bool(is_attached), as_input_file_id(sticker_id)));
+ get_args(args, is_attached, sticker_id);
+ send_request(td_api::make_object<td_api::removeRecentSticker>(is_attached, as_input_file_id(sticker_id)));
} else if (op == "gfs") {
- send_request(make_tl_object<td_api::getFavoriteStickers>());
+ send_request(td_api::make_object<td_api::getFavoriteStickers>());
} else if (op == "afs") {
- send_request(make_tl_object<td_api::addFavoriteSticker>(as_input_file_id(args)));
+ send_request(td_api::make_object<td_api::addFavoriteSticker>(as_input_file_id(args)));
} else if (op == "rfs") {
- send_request(make_tl_object<td_api::removeFavoriteSticker>(as_input_file_id(args)));
+ send_request(td_api::make_object<td_api::removeFavoriteSticker>(as_input_file_id(args)));
} else if (op == "crs") {
- send_request(make_tl_object<td_api::clearRecentStickers>(as_bool(args)));
+ send_request(td_api::make_object<td_api::clearRecentStickers>(as_bool(args)));
} else if (op == "gse") {
- send_request(make_tl_object<td_api::getStickerEmojis>(as_input_file_id(args)));
+ send_request(td_api::make_object<td_api::getStickerEmojis>(as_input_file_id(args)));
+ } else if (op == "se") {
+ send_request(td_api::make_object<td_api::searchEmojis>(args, false, vector<string>()));
+ } else if (op == "see") {
+ send_request(td_api::make_object<td_api::searchEmojis>(args, true, vector<string>()));
+ } else if (op == "seru") {
+ send_request(td_api::make_object<td_api::searchEmojis>(args, false, vector<string>{"ru_RU"}));
+ } else if (op == "gae") {
+ send_request(td_api::make_object<td_api::getAnimatedEmoji>(args));
+ } else if (op == "gesu") {
+ send_request(td_api::make_object<td_api::getEmojiSuggestionsUrl>(args));
+ } else if (op == "gces") {
+ send_request(td_api::make_object<td_api::getCustomEmojiStickers>(to_integers<int64>(args)));
+ } else if (op == "gsan") {
+ send_request(td_api::make_object<td_api::getSavedAnimations>());
+ } else if (op == "asan") {
+ send_request(td_api::make_object<td_api::addSavedAnimation>(as_input_file_id(args)));
+ } else if (op == "rsan") {
+ send_request(td_api::make_object<td_api::removeSavedAnimation>(as_input_file_id(args)));
} else {
op_not_found_count++;
}
- if (op == "gsan") {
- send_request(make_tl_object<td_api::getSavedAnimations>());
- } else if (op == "asan") {
- send_request(make_tl_object<td_api::addSavedAnimation>(as_input_file_id(args)));
- } else if (op == "rsan") {
- send_request(make_tl_object<td_api::removeSavedAnimation>(as_input_file_id(args)));
- } else if (op == "guf") {
- send_request(make_tl_object<td_api::getUserFullInfo>(as_user_id(args)));
+ if (op == "guf") {
+ UserId user_id;
+ get_args(args, user_id);
+ send_request(td_api::make_object<td_api::getUserFullInfo>(user_id));
} else if (op == "gbg") {
- send_request(make_tl_object<td_api::getBasicGroup>(to_integer<int32>(args)));
+ send_request(td_api::make_object<td_api::getBasicGroup>(as_basic_group_id(args)));
} else if (op == "gbgf") {
- send_request(make_tl_object<td_api::getBasicGroupFullInfo>(to_integer<int32>(args)));
+ send_request(td_api::make_object<td_api::getBasicGroupFullInfo>(as_basic_group_id(args)));
} else if (op == "gsg" || op == "gch") {
- send_request(make_tl_object<td_api::getSupergroup>(to_integer<int32>(args)));
+ send_request(td_api::make_object<td_api::getSupergroup>(as_supergroup_id(args)));
} else if (op == "gsgf" || op == "gchf") {
- send_request(make_tl_object<td_api::getSupergroupFullInfo>(to_integer<int32>(args)));
+ send_request(td_api::make_object<td_api::getSupergroupFullInfo>(as_supergroup_id(args)));
} else if (op == "gsc") {
- send_request(make_tl_object<td_api::getSecretChat>(to_integer<int32>(args)));
+ send_request(td_api::make_object<td_api::getSecretChat>(as_secret_chat_id(args)));
} else if (op == "scm") {
- string chat_id;
- string limit;
- string query;
-
- std::tie(chat_id, args) = split(args);
- std::tie(limit, query) = split(args);
- send_request(make_tl_object<td_api::searchChatMembers>(as_chat_id(chat_id), query, to_integer<int32>(limit)));
+ ChatId chat_id;
+ string filter;
+ SearchQuery query;
+ get_args(args, chat_id, filter, query);
+ send_request(td_api::make_object<td_api::searchChatMembers>(chat_id, query.query, query.limit,
+ as_chat_members_filter(filter)));
} else if (op == "gcm") {
- string chat_id;
- string user_id;
-
- std::tie(chat_id, user_id) = split(args);
- send_request(make_tl_object<td_api::getChatMember>(as_chat_id(chat_id), as_user_id(user_id)));
- } else if (op == "GetSupergroupAdministrators") {
- string supergroup_id;
- string offset;
- string limit;
-
- std::tie(supergroup_id, args) = split(args);
- std::tie(offset, limit) = split(args);
- if (offset.empty()) {
- offset = "0";
- }
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::getSupergroupMembers>(
- to_integer<int32>(supergroup_id), make_tl_object<td_api::supergroupMembersFilterAdministrators>(),
- to_integer<int32>(offset), to_integer<int32>(limit)));
+ ChatId chat_id;
+ string member_id;
+ get_args(args, chat_id, member_id);
+ send_request(td_api::make_object<td_api::getChatMember>(chat_id, as_message_sender(member_id)));
} else if (op == "GetChatAdministrators") {
- string chat_id = args;
- send_request(make_tl_object<td_api::getChatAdministrators>(as_chat_id(chat_id)));
- } else if (op == "GetSupergroupBanned") {
- string supergroup_id;
- string query;
- string offset;
- string limit;
-
- std::tie(supergroup_id, args) = split(args);
- std::tie(query, args) = split(args);
- std::tie(offset, limit) = split(args);
- if (offset.empty()) {
- offset = "0";
- }
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::getSupergroupMembers>(
- to_integer<int32>(supergroup_id), make_tl_object<td_api::supergroupMembersFilterBanned>(query),
- to_integer<int32>(offset), to_integer<int32>(limit)));
- } else if (op == "GetSupergroupBots") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getChatAdministrators>(chat_id));
+ } else if (op == "GetSupergroupAdministrators" || op == "GetSupergroupBanned" || op == "GetSupergroupBots" ||
+ op == "GetSupergroupContacts" || op == "GetSupergroupMembers" || op == "GetSupergroupRestricted" ||
+ op == "SearchSupergroupMembers" || op == "SearchSupergroupMentions") {
string supergroup_id;
- string offset;
- string limit;
-
- std::tie(supergroup_id, args) = split(args);
- std::tie(offset, limit) = split(args);
- if (offset.empty()) {
- offset = "0";
+ string message_thread_id;
+ int32 offset;
+ SearchQuery query;
+ if (op == "SearchSupergroupMentions") {
+ get_args(args, message_thread_id, args);
}
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::getSupergroupMembers>(to_integer<int32>(supergroup_id),
- make_tl_object<td_api::supergroupMembersFilterBots>(),
- to_integer<int32>(offset), to_integer<int32>(limit)));
- } else if (op == "GetSupergroupMembers") {
- string supergroup_id;
- string offset;
- string limit;
-
- std::tie(supergroup_id, args) = split(args);
- std::tie(offset, limit) = split(args);
- if (offset.empty()) {
- offset = "0";
- }
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::getSupergroupMembers>(to_integer<int32>(supergroup_id),
- make_tl_object<td_api::supergroupMembersFilterRecent>(),
- to_integer<int32>(offset), to_integer<int32>(limit)));
- } else if (op == "SearchSupergroupMembers") {
- string supergroup_id;
- string query;
- string offset;
- string limit;
-
- std::tie(supergroup_id, args) = split(args);
- std::tie(query, args) = split(args);
- std::tie(offset, limit) = split(args);
- if (offset.empty()) {
- offset = "0";
- }
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::getSupergroupMembers>(
- to_integer<int32>(supergroup_id), make_tl_object<td_api::supergroupMembersFilterSearch>(query),
- to_integer<int32>(offset), to_integer<int32>(limit)));
- } else if (op == "GetSupergroupRestricted") {
- string supergroup_id;
- string query;
- string offset;
- string limit;
-
- std::tie(supergroup_id, args) = split(args);
- std::tie(query, args) = split(args);
- std::tie(offset, limit) = split(args);
- if (offset.empty()) {
- offset = "0";
- }
- if (limit.empty()) {
- limit = "10";
- }
- send_request(make_tl_object<td_api::getSupergroupMembers>(
- to_integer<int32>(supergroup_id), make_tl_object<td_api::supergroupMembersFilterRestricted>(query),
- to_integer<int32>(offset), to_integer<int32>(limit)));
+ get_args(args, supergroup_id, offset, query);
+ send_request(td_api::make_object<td_api::getSupergroupMembers>(
+ as_supergroup_id(supergroup_id), as_supergroup_members_filter(op, query.query, message_thread_id), offset,
+ query.limit));
} else if (op == "gdialog" || op == "gd") {
- send_request(make_tl_object<td_api::getChat>(as_chat_id(args)));
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getChat>(chat_id));
} else if (op == "open") {
- send_request(make_tl_object<td_api::openChat>(as_chat_id(args)));
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::openChat>(chat_id));
+ opened_chat_id_ = chat_id;
} else if (op == "close") {
- send_request(make_tl_object<td_api::closeChat>(as_chat_id(args)));
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::closeChat>(chat_id));
} else if (op == "gm") {
- string chat_id;
- string message_id;
- std::tie(chat_id, message_id) = split(args);
- send_request(make_tl_object<td_api::getMessage>(as_chat_id(chat_id), as_message_id(message_id)));
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::getMessage>(chat_id, message_id));
+ } else if (op == "gmf") {
+ ChatId chat_id;
+ int64 from_message_id;
+ int64 to_message_id;
+ get_args(args, chat_id, from_message_id, to_message_id);
+ for (auto message_id = from_message_id; message_id <= to_message_id; message_id++) {
+ send_request(td_api::make_object<td_api::getMessage>(chat_id, message_id << 20));
+ }
+ } else if (op == "gml") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::getMessageLocally>(chat_id, message_id));
} else if (op == "grm") {
- string chat_id;
- string message_id;
- std::tie(chat_id, message_id) = split(args);
- send_request(make_tl_object<td_api::getRepliedMessage>(as_chat_id(chat_id), as_message_id(message_id)));
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::getRepliedMessage>(chat_id, message_id));
+ } else if (op == "gmt") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::getMessageThread>(chat_id, message_id));
+ } else if (op == "gmv") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::getMessageViewers>(chat_id, message_id));
} else if (op == "gcpm") {
- string chat_id = args;
- send_request(make_tl_object<td_api::getChatPinnedMessage>(as_chat_id(chat_id)));
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getChatPinnedMessage>(chat_id));
} else if (op == "gms") {
- string chat_id;
+ ChatId chat_id;
string message_ids;
- std::tie(chat_id, message_ids) = split(args);
- send_request(make_tl_object<td_api::getMessages>(as_chat_id(chat_id), as_message_ids(message_ids)));
- } else if (op == "gpml") {
- string chat_id;
- string message_id;
- string for_album;
- std::tie(chat_id, args) = split(args);
- std::tie(message_id, for_album) = split(args);
- send_request(make_tl_object<td_api::getPublicMessageLink>(as_chat_id(chat_id), as_message_id(message_id),
- as_bool(for_album)));
- } else if (op == "gcmbd") {
- string chat_id;
- string date;
- std::tie(chat_id, date) = split(args);
- send_request(make_tl_object<td_api::getChatMessageByDate>(as_chat_id(chat_id), to_integer<int32>(date)));
+ get_args(args, chat_id, message_ids);
+ send_request(td_api::make_object<td_api::getMessages>(chat_id, as_message_ids(message_ids)));
+ } else if (op == "gcspm") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getChatSponsoredMessages>(chat_id));
+ } else if (op == "gmlink") {
+ ChatId chat_id;
+ MessageId message_id;
+ int32 media_timestamp;
+ bool for_album;
+ bool for_comment;
+ get_args(args, chat_id, message_id, media_timestamp, for_album, for_comment);
+ send_request(
+ td_api::make_object<td_api::getMessageLink>(chat_id, message_id, media_timestamp, for_album, for_comment));
+ } else if (op == "gmec") {
+ ChatId chat_id;
+ MessageId message_id;
+ bool for_album;
+ get_args(args, chat_id, message_id, for_album);
+ send_request(td_api::make_object<td_api::getMessageEmbeddingCode>(chat_id, message_id, for_album));
+ } else if (op == "gmli") {
+ send_request(td_api::make_object<td_api::getMessageLinkInfo>(args));
+ } else if (op == "tt") {
+ string text;
+ string from_language_code;
+ string to_language_code;
+ get_args(args, text, from_language_code, to_language_code);
+ send_request(td_api::make_object<td_api::translateText>(text, from_language_code, to_language_code));
+ } else if (op == "rs") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::recognizeSpeech>(chat_id, message_id));
+ } else if (op == "rsr") {
+ ChatId chat_id;
+ MessageId message_id;
+ bool is_good;
+ get_args(args, chat_id, message_id, is_good);
+ send_request(td_api::make_object<td_api::rateSpeechRecognition>(chat_id, message_id, is_good));
} else if (op == "gf" || op == "GetFile") {
- send_request(make_tl_object<td_api::getFile>(as_file_id(args)));
+ FileId file_id;
+ get_args(args, file_id);
+ send_request(td_api::make_object<td_api::getFile>(file_id));
+ } else if (op == "gfdps") {
+ FileId file_id;
+ int64 offset;
+ get_args(args, file_id, offset);
+ send_request(td_api::make_object<td_api::getFileDownloadedPrefixSize>(file_id, offset));
+ } else if (op == "rfp") {
+ FileId file_id;
+ int64 offset;
+ int64 count;
+ get_args(args, file_id, offset, count);
+ send_request(td_api::make_object<td_api::readFilePart>(file_id, offset, count));
} else if (op == "grf") {
- send_request(make_tl_object<td_api::getRemoteFile>(args, nullptr));
- } else if (op == "df" || op == "DownloadFile") {
- string file_id;
- string priority;
- std::tie(file_id, priority) = split(args);
- if (priority.empty()) {
- priority = "1";
- }
-
- send_request(make_tl_object<td_api::downloadFile>(as_file_id(file_id), to_integer<int32>(priority)));
- } else if (op == "dff") {
- string file_id;
- string priority;
- std::tie(file_id, priority) = split(args);
- if (priority.empty()) {
- priority = "1";
+ send_request(td_api::make_object<td_api::getRemoteFile>(args, nullptr));
+ } else if (op == "gmtf") {
+ string latitude;
+ string longitude;
+ int32 zoom;
+ int32 width;
+ int32 height;
+ int32 scale;
+ ChatId chat_id;
+ get_args(args, latitude, longitude, zoom, width, height, scale, chat_id);
+ send_request(td_api::make_object<td_api::getMapThumbnailFile>(as_location(latitude, longitude, string()), zoom,
+ width, height, scale, chat_id));
+ } else if (op == "df" || op == "DownloadFile" || op == "dff" || op == "dfs") {
+ FileId file_id;
+ int64 offset;
+ int64 limit;
+ int32 priority;
+ get_args(args, file_id, offset, limit, priority);
+ if (priority <= 0) {
+ priority = 1;
}
-
- for (int i = 1; i <= as_file_id(file_id); i++) {
- send_request(make_tl_object<td_api::downloadFile>(i, to_integer<int32>(priority)));
+ int32 max_file_id = file_id.file_id;
+ int32 min_file_id = (op == "dff" ? 1 : max_file_id);
+ for (int32 i = min_file_id; i <= max_file_id; i++) {
+ send_request(td_api::make_object<td_api::downloadFile>(i, priority, offset, limit, op == "dfs"));
}
} else if (op == "cdf") {
- send_request(make_tl_object<td_api::cancelDownloadFile>(as_file_id(args), true));
- } else if (op == "uf") {
+ FileId file_id;
+ get_args(args, file_id);
+ send_request(td_api::make_object<td_api::cancelDownloadFile>(file_id, false));
+ } else if (op == "gsfn") {
+ FileId file_id;
+ string directory_name;
+ get_args(args, file_id, directory_name);
+ send_request(td_api::make_object<td_api::getSuggestedFileName>(file_id, directory_name));
+ } else if (op == "uf" || op == "ufs" || op == "ufse") {
string file_path;
- string priority;
- std::tie(file_path, priority) = split(args);
- if (priority.empty()) {
- priority = "1";
+ int32 priority;
+ get_args(args, file_path, priority);
+ if (priority <= 0) {
+ priority = 1;
}
-
- send_request(make_tl_object<td_api::uploadFile>(as_local_file(file_path), make_tl_object<td_api::fileTypePhoto>(),
- to_integer<int32>(priority)));
- } else if (op == "ufs") {
- string file_path;
- string priority;
- std::tie(file_path, priority) = split(args);
- if (priority.empty()) {
- priority = "1";
+ td_api::object_ptr<td_api::FileType> type = td_api::make_object<td_api::fileTypePhoto>();
+ if (op == "ufs") {
+ type = td_api::make_object<td_api::fileTypeSecret>();
}
-
- send_request(make_tl_object<td_api::uploadFile>(
- as_local_file(file_path), make_tl_object<td_api::fileTypeSecret>(), to_integer<int32>(priority)));
+ if (op == "ufse") {
+ type = td_api::make_object<td_api::fileTypeSecure>();
+ }
+ send_request(
+ td_api::make_object<td_api::preliminaryUploadFile>(as_input_file(file_path), std::move(type), priority));
} else if (op == "ufg") {
string file_path;
string conversion;
- std::tie(file_path, conversion) = split(args);
- send_request(make_tl_object<td_api::uploadFile>(as_generated_file(file_path, conversion),
- make_tl_object<td_api::fileTypePhoto>(), 1));
+ get_args(args, file_path, conversion);
+ send_request(td_api::make_object<td_api::preliminaryUploadFile>(as_generated_file(file_path, conversion),
+ td_api::make_object<td_api::fileTypePhoto>(), 1));
} else if (op == "cuf") {
- send_request(make_tl_object<td_api::cancelUploadFile>(as_file_id(args)));
+ FileId file_id;
+ get_args(args, file_id);
+ send_request(td_api::make_object<td_api::cancelPreliminaryUploadFile>(file_id));
} else if (op == "delf" || op == "DeleteFile") {
- string file_id = args;
- send_request(make_tl_object<td_api::deleteFile>(as_file_id(file_id)));
- } else if (op == "dm") {
- string chat_id;
+ FileId file_id;
+ get_args(args, file_id);
+ send_request(td_api::make_object<td_api::deleteFile>(file_id));
+ } else if (op == "aftd") {
+ FileId file_id;
+ ChatId chat_id;
+ MessageId message_id;
+ int32 priority;
+ get_args(args, file_id, chat_id, message_id, priority);
+ send_request(td_api::make_object<td_api::addFileToDownloads>(file_id, chat_id, message_id, max(priority, 1)));
+ } else if (op == "tdip") {
+ FileId file_id;
+ bool is_paused;
+ get_args(args, file_id, is_paused);
+ send_request(td_api::make_object<td_api::toggleDownloadIsPaused>(file_id, is_paused));
+ } else if (op == "tadap") {
+ bool are_paused;
+ get_args(args, are_paused);
+ send_request(td_api::make_object<td_api::toggleAllDownloadsArePaused>(are_paused));
+ } else if (op == "rffd") {
+ FileId file_id;
+ bool delete_from_cache;
+ get_args(args, file_id, delete_from_cache);
+ send_request(td_api::make_object<td_api::removeFileFromDownloads>(file_id, delete_from_cache));
+ } else if (op == "raffd" || op == "raffda" || op == "raffdc") {
+ bool delete_from_cache;
+ get_args(args, delete_from_cache);
+ send_request(td_api::make_object<td_api::removeAllFilesFromDownloads>(op.back() == 'a', op.back() == 'c',
+ delete_from_cache));
+ } else if (op == "sfd" || op == "sfda" || op == "sfdc") {
+ string offset;
+ SearchQuery query;
+ get_args(args, offset, query);
+ send_request(td_api::make_object<td_api::searchFileDownloads>(query.query, op.back() == 'a', op.back() == 'c',
+ offset, query.limit));
+ } else if (op == "dm" || op == "dmr") {
+ ChatId chat_id;
string message_ids;
- string revoke;
- std::tie(chat_id, args) = split(args);
- std::tie(message_ids, revoke) = split(args);
-
- send_request(make_tl_object<td_api::deleteMessages>(as_chat_id(chat_id), as_message_ids(message_ids, ','),
- as_bool(revoke)));
- } else if (op == "fm" || op == "fmg") {
- string chat_id;
- string from_chat_id;
+ get_args(args, chat_id, message_ids);
+ send_request(td_api::make_object<td_api::deleteMessages>(chat_id, as_message_ids(message_ids), op == "dmr"));
+ } else if (op == "fm" || op == "cm" || op == "fmp" || op == "cmp") {
+ ChatId chat_id;
+ ChatId from_chat_id;
string message_ids;
- std::tie(chat_id, args) = split(args);
- std::tie(from_chat_id, message_ids) = split(args);
-
- auto chat = as_chat_id(chat_id);
- send_request(make_tl_object<td_api::forwardMessages>(chat, as_chat_id(from_chat_id), as_message_ids(message_ids),
- false, false, op == "fmg"));
+ MessageThreadId message_thread_id;
+ get_args(args, chat_id, from_chat_id, message_ids, message_thread_id);
+ send_request(td_api::make_object<td_api::forwardMessages>(
+ chat_id, message_thread_id, from_chat_id, as_message_ids(message_ids), default_message_send_options(),
+ op[0] == 'c', rand_bool(), op.back() == 'p'));
+ } else if (op == "resend") {
+ ChatId chat_id;
+ string message_ids;
+ get_args(args, chat_id, message_ids);
+ send_request(td_api::make_object<td_api::resendMessages>(chat_id, as_message_ids(message_ids)));
} else if (op == "csc" || op == "CreateSecretChat") {
- send_request(make_tl_object<td_api::createSecretChat>(to_integer<int32>(args)));
+ send_request(td_api::make_object<td_api::createSecretChat>(as_secret_chat_id(args)));
} else if (op == "cnsc" || op == "CreateNewSecretChat") {
- send_request(make_tl_object<td_api::createNewSecretChat>(as_user_id(args)));
+ UserId user_id;
+ get_args(args, user_id);
+ send_request(td_api::make_object<td_api::createNewSecretChat>(user_id));
} else if (op == "scstn") {
- send_request(make_tl_object<td_api::sendChatScreenshotTakenNotification>(as_chat_id(args)));
- } else if (op == "sscttl" || op == "setSecretChatTtl") {
- string chat_id;
- string ttl;
- std::tie(chat_id, ttl) = split(args);
-
- send_request(make_tl_object<td_api::sendChatSetTtlMessage>(as_chat_id(chat_id), to_integer<int32>(ttl)));
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::sendChatScreenshotTakenNotification>(chat_id));
} else if (op == "closeSC" || op == "cancelSC") {
- send_request(make_tl_object<td_api::closeSecretChat>(to_integer<int32>(args)));
- } else if (op == "cc" || op == "CreateCall") {
- send_request(make_tl_object<td_api::createCall>(as_user_id(args),
- make_tl_object<td_api::callProtocol>(true, true, 65, 65)));
- } else if (op == "dc" || op == "DiscardCall") {
- string call_id;
- string is_disconnected;
- std::tie(call_id, is_disconnected) = split(args);
+ send_request(td_api::make_object<td_api::closeSecretChat>(as_secret_chat_id(args)));
+ } else {
+ op_not_found_count++;
+ }
- send_request(make_tl_object<td_api::discardCall>(as_call_id(call_id), as_bool(is_disconnected), 0, 0));
+ if (op == "cc" || op == "CreateCall") {
+ UserId user_id;
+ get_args(args, user_id);
+ send_request(td_api::make_object<td_api::createCall>(
+ user_id, td_api::make_object<td_api::callProtocol>(true, true, 65, 65, vector<string>{"2.6", "3.0"}),
+ rand_bool()));
} else if (op == "ac" || op == "AcceptCall") {
- send_request(make_tl_object<td_api::acceptCall>(as_call_id(args),
- make_tl_object<td_api::callProtocol>(true, true, 65, 65)));
+ CallId call_id;
+ get_args(args, call_id);
+ send_request(td_api::make_object<td_api::acceptCall>(
+ call_id, td_api::make_object<td_api::callProtocol>(true, true, 65, 65, vector<string>{"2.6", "3.0"})));
+ } else if (op == "scsd") {
+ CallId call_id;
+ get_args(args, call_id);
+ send_request(td_api::make_object<td_api::sendCallSignalingData>(call_id, "abacaba"));
+ } else if (op == "dc" || op == "DiscardCall") {
+ CallId call_id;
+ bool is_disconnected;
+ get_args(args, call_id, is_disconnected);
+ send_request(td_api::make_object<td_api::discardCall>(call_id, is_disconnected, 0, rand_bool(), 0));
} else if (op == "scr" || op == "SendCallRating") {
- send_request(make_tl_object<td_api::sendCallRating>(as_call_id(args), 5, "Wow, such good call! (TDLib test)"));
- } else if (op == "scdi" || op == "SendCallDebugInformation") {
- send_request(make_tl_object<td_api::sendCallDebugInformation>(as_call_id(args), "{}"));
+ CallId call_id;
+ int32 rating;
+ get_args(args, call_id, rating);
+ vector<td_api::object_ptr<td_api::CallProblem>> problems;
+ problems.emplace_back(td_api::make_object<td_api::callProblemNoise>());
+ problems.emplace_back(td_api::make_object<td_api::callProblemNoise>());
+ problems.emplace_back(td_api::make_object<td_api::callProblemDistortedVideo>());
+ problems.emplace_back(nullptr);
+ problems.emplace_back(td_api::make_object<td_api::callProblemNoise>());
+ problems.emplace_back(td_api::make_object<td_api::callProblemEcho>());
+ problems.emplace_back(td_api::make_object<td_api::callProblemPixelatedVideo>());
+ problems.emplace_back(td_api::make_object<td_api::callProblemDistortedSpeech>());
+ send_request(td_api::make_object<td_api::sendCallRating>(call_id, rating, "Wow, such good call! (TDLib test)",
+ std::move(problems)));
+ } else if (op == "scdi") {
+ CallId call_id;
+ get_args(args, call_id);
+ send_request(td_api::make_object<td_api::sendCallDebugInformation>(call_id, "{}"));
+ } else if (op == "sclog") {
+ CallId call_id;
+ string log_file;
+ get_args(args, call_id, log_file);
+ send_request(td_api::make_object<td_api::sendCallLog>(call_id, as_input_file(log_file)));
+ } else if (op == "gvcap") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getVideoChatAvailableParticipants>(chat_id));
+ } else if (op == "svcdp") {
+ ChatId chat_id;
+ string participant_id;
+ get_args(args, chat_id, participant_id);
+ send_request(
+ td_api::make_object<td_api::setVideoChatDefaultParticipant>(chat_id, as_message_sender(participant_id)));
+ } else if (op == "cvc") {
+ ChatId chat_id;
+ string title;
+ int32 start_date;
+ bool is_rtmp_stream;
+ get_args(args, chat_id, title, start_date, is_rtmp_stream);
+ send_request(td_api::make_object<td_api::createVideoChat>(chat_id, title, start_date, is_rtmp_stream));
+ } else if (op == "gvcru") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getVideoChatRtmpUrl>(chat_id));
+ } else if (op == "rvcru") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::replaceVideoChatRtmpUrl>(chat_id));
+ } else if (op == "ggc") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(td_api::make_object<td_api::getGroupCall>(group_call_id));
+ } else if (op == "ggcs") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(td_api::make_object<td_api::getGroupCallStreams>(group_call_id));
+ } else if (op == "ggcss") {
+ GroupCallId group_call_id;
+ int32 channel_id;
+ get_args(args, group_call_id, channel_id);
+ send_request(td_api::make_object<td_api::getGroupCallStreamSegment>(
+ group_call_id, (std::time(nullptr) - 5) * 1000, 0, channel_id, nullptr));
+ } else if (op == "ssgc") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(td_api::make_object<td_api::startScheduledGroupCall>(group_call_id));
+ } else if (op == "tgcesn" || op == "tgcesne") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(
+ td_api::make_object<td_api::toggleGroupCallEnabledStartNotification>(group_call_id, op == "tgcesne"));
+ } else if (op == "jgc" || op == "jgcv" || op == "sgcss") {
+ GroupCallId group_call_id;
+ string participant_id;
+ string invite_hash;
+ get_args(args, group_call_id, participant_id, invite_hash);
+
+ auto payload = PSTRING() << "{\"ufrag\":\"ufrag\",\"pwd\":\"pwd\",\"fingerprints\":[{\"hash\":\"hash\",\"setup\":"
+ "\"setup\",\"fingerprint\":\"fingerprint\"},{\"hash\":\"h2\",\"setup\":\"s2\","
+ "\"fingerprint\":\"fingerprint2\"}],\"ssrc\":"
+ << group_call_source_ << ',';
+ if (op == "jgc") {
+ payload.back() = '}';
+ } else {
+ string sim_sources = "[1,2]";
+ string fid_sources = "[3,4]";
+ if (op == "sgcss") {
+ sim_sources = "[5,6]";
+ fid_sources = "[7,8]";
+ }
+ payload +=
+ "\"payload-types\":[{\"id\":12345,\"name\":\"opus\",\"clockrate\":48000,\"channels\":2,\"rtcp-fbs\":[{"
+ "\"type\":\"transport-cc\",\"subtype\":\"subtype1\"},{\"type\":\"type2\",\"subtype\":\"subtype2\"}],"
+ "\"parameters\":{\"minptime\":\"10\",\"useinbandfec\":\"1\"}}],\"rtp-hdrexts\":[{\"id\":1,\"uri\":\"urn:"
+ "ietf:params:rtp-hdrext:ssrc-audio-level\"}],\"ssrc-groups\":[{\"sources\":" +
+ sim_sources + ",\"semantics\":\"SIM\"},{\"sources\":" + fid_sources + ",\"semantics\":\"FID\"}]}";
+ }
+ if (op == "sgcss") {
+ send_request(td_api::make_object<td_api::startGroupCallScreenSharing>(group_call_id, group_call_source_ + 1,
+ std::move(payload)));
+ } else {
+ send_request(td_api::make_object<td_api::joinGroupCall>(group_call_id, as_message_sender(participant_id),
+ group_call_source_, std::move(payload), true, true,
+ invite_hash));
+ }
+ } else if (op == "tgcssip") {
+ GroupCallId group_call_id;
+ bool is_paused;
+ get_args(args, group_call_id, is_paused);
+ send_request(td_api::make_object<td_api::toggleGroupCallScreenSharingIsPaused>(group_call_id, is_paused));
+ } else if (op == "egcss") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(td_api::make_object<td_api::endGroupCallScreenSharing>(group_call_id));
+ } else if (op == "sgct") {
+ GroupCallId group_call_id;
+ string title;
+ get_args(args, group_call_id, title);
+ send_request(td_api::make_object<td_api::setGroupCallTitle>(group_call_id, title));
+ } else if (op == "tgcmnp" || op == "tgcmnpe") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(td_api::make_object<td_api::toggleGroupCallMuteNewParticipants>(group_call_id, op == "tgcmnpe"));
+ } else if (op == "rgcil") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(td_api::make_object<td_api::revokeGroupCallInviteLink>(group_call_id));
+ } else if (op == "tgcimvp") {
+ GroupCallId group_call_id;
+ bool is_my_video_paused;
+ get_args(args, group_call_id, is_my_video_paused);
+ send_request(td_api::make_object<td_api::toggleGroupCallIsMyVideoPaused>(group_call_id, is_my_video_paused));
+ } else if (op == "tgcimve") {
+ GroupCallId group_call_id;
+ bool is_my_video_enabled;
+ get_args(args, group_call_id, is_my_video_enabled);
+ send_request(td_api::make_object<td_api::toggleGroupCallIsMyVideoEnabled>(group_call_id, is_my_video_enabled));
+ } else if (op == "sgcpis") {
+ GroupCallId group_call_id;
+ int32 source_id;
+ bool is_speaking;
+ get_args(args, group_call_id, source_id, is_speaking);
+ send_request(
+ td_api::make_object<td_api::setGroupCallParticipantIsSpeaking>(group_call_id, source_id, is_speaking));
+ } else if (op == "igcp") {
+ GroupCallId group_call_id;
+ string user_ids;
+ get_args(args, group_call_id, user_ids);
+ send_request(td_api::make_object<td_api::inviteGroupCallParticipants>(group_call_id, as_user_ids(user_ids)));
+ } else if (op == "ggcil") {
+ GroupCallId group_call_id;
+ bool can_self_unmute;
+ get_args(args, group_call_id, can_self_unmute);
+ send_request(td_api::make_object<td_api::getGroupCallInviteLink>(group_call_id, can_self_unmute));
+ } else if (op == "sgcr") {
+ GroupCallId group_call_id;
+ string title;
+ bool record_video;
+ bool use_portrait_orientation;
+ get_args(args, group_call_id, title, record_video, use_portrait_orientation);
+ send_request(td_api::make_object<td_api::startGroupCallRecording>(group_call_id, title, record_video,
+ use_portrait_orientation));
+ } else if (op == "egcr") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(td_api::make_object<td_api::endGroupCallRecording>(group_call_id));
+ } else if (op == "tgcpim") {
+ GroupCallId group_call_id;
+ string participant_id;
+ bool is_muted;
+ get_args(args, group_call_id, participant_id, is_muted);
+ send_request(td_api::make_object<td_api::toggleGroupCallParticipantIsMuted>(
+ group_call_id, as_message_sender(participant_id), is_muted));
+ } else if (op == "sgcpvl") {
+ GroupCallId group_call_id;
+ string participant_id;
+ int32 volume_level;
+ get_args(args, group_call_id, participant_id, volume_level);
+ send_request(td_api::make_object<td_api::setGroupCallParticipantVolumeLevel>(
+ group_call_id, as_message_sender(participant_id), volume_level));
+ } else if (op == "tgcpihr") {
+ GroupCallId group_call_id;
+ string participant_id;
+ bool is_hand_raised;
+ get_args(args, group_call_id, participant_id, is_hand_raised);
+ send_request(td_api::make_object<td_api::toggleGroupCallParticipantIsHandRaised>(
+ group_call_id, as_message_sender(participant_id), is_hand_raised));
+ } else if (op == "lgcp") {
+ GroupCallId group_call_id;
+ string limit;
+ get_args(args, group_call_id, limit);
+ send_request(td_api::make_object<td_api::loadGroupCallParticipants>(group_call_id, as_limit(limit)));
+ } else if (op == "lgc") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(td_api::make_object<td_api::leaveGroupCall>(group_call_id));
+ } else if (op == "egc") {
+ GroupCallId group_call_id;
+ get_args(args, group_call_id);
+ send_request(td_api::make_object<td_api::endGroupCall>(group_call_id));
+ } else if (op == "rpcil") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::replacePrimaryChatInviteLink>(chat_id));
+ } else if (op == "ccilt") {
+ ChatId chat_id;
+ string name;
+ int32 expiration_date;
+ int32 member_limit;
+ bool creates_join_request;
+ get_args(args, chat_id, name, expiration_date, member_limit, creates_join_request);
+ send_request(td_api::make_object<td_api::createChatInviteLink>(chat_id, name, expiration_date, member_limit,
+ creates_join_request));
+ } else if (op == "ecil") {
+ ChatId chat_id;
+ string invite_link;
+ string name;
+ int32 expiration_date;
+ int32 member_limit;
+ bool creates_join_request;
+ get_args(args, chat_id, invite_link, name, expiration_date, member_limit, creates_join_request);
+ send_request(td_api::make_object<td_api::editChatInviteLink>(chat_id, invite_link, name, expiration_date,
+ member_limit, creates_join_request));
+ } else if (op == "rcil") {
+ ChatId chat_id;
+ string invite_link;
+ get_args(args, chat_id, invite_link);
+ send_request(td_api::make_object<td_api::revokeChatInviteLink>(chat_id, invite_link));
+ } else if (op == "gcilc") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getChatInviteLinkCounts>(chat_id));
} else if (op == "gcil") {
- send_request(make_tl_object<td_api::generateChatInviteLink>(as_chat_id(args)));
+ ChatId chat_id;
+ string invite_link;
+ get_args(args, chat_id, invite_link);
+ send_request(td_api::make_object<td_api::getChatInviteLink>(chat_id, invite_link));
+ } else if (op == "gcils" || op == "gcilr") {
+ ChatId chat_id;
+ UserId creator_user_id;
+ int32 offset_date;
+ string offset_invite_link;
+ string limit;
+ get_args(args, chat_id, creator_user_id, offset_date, offset_invite_link, limit);
+ send_request(td_api::make_object<td_api::getChatInviteLinks>(chat_id, creator_user_id, op == "gcilr", offset_date,
+ offset_invite_link, as_limit(limit)));
+ } else if (op == "gcilm") {
+ ChatId chat_id;
+ string invite_link;
+ UserId offset_user_id;
+ int32 offset_date;
+ string limit;
+ get_args(args, chat_id, invite_link, offset_user_id, offset_date, limit);
+ send_request(td_api::make_object<td_api::getChatInviteLinkMembers>(
+ chat_id, invite_link, td_api::make_object<td_api::chatInviteLinkMember>(offset_user_id, offset_date, 0),
+ as_limit(limit)));
+ } else if (op == "gcjr") {
+ ChatId chat_id;
+ string invite_link;
+ string query;
+ UserId offset_user_id;
+ int32 offset_date;
+ string limit;
+ get_args(args, chat_id, invite_link, query, offset_user_id, offset_date, limit);
+ send_request(td_api::make_object<td_api::getChatJoinRequests>(
+ chat_id, invite_link, query,
+ td_api::make_object<td_api::chatJoinRequest>(offset_user_id, offset_date, string()), as_limit(limit)));
+ } else if (op == "pcjr") {
+ ChatId chat_id;
+ UserId user_id;
+ bool approve;
+ get_args(args, chat_id, user_id, approve);
+ send_request(td_api::make_object<td_api::processChatJoinRequest>(chat_id, user_id, approve));
+ } else if (op == "pcjrs") {
+ ChatId chat_id;
+ string invite_link;
+ bool approve;
+ get_args(args, chat_id, invite_link, approve);
+ send_request(td_api::make_object<td_api::processChatJoinRequests>(chat_id, invite_link, approve));
+ } else if (op == "drcil") {
+ ChatId chat_id;
+ string invite_link;
+ get_args(args, chat_id, invite_link);
+ send_request(td_api::make_object<td_api::deleteRevokedChatInviteLink>(chat_id, invite_link));
+ } else if (op == "darcil") {
+ ChatId chat_id;
+ UserId creator_user_id;
+ get_args(args, chat_id, creator_user_id);
+ send_request(td_api::make_object<td_api::deleteAllRevokedChatInviteLinks>(chat_id, creator_user_id));
} else if (op == "ccil") {
- send_request(make_tl_object<td_api::checkChatInviteLink>(args));
+ send_request(td_api::make_object<td_api::checkChatInviteLink>(args));
} else if (op == "jcbil") {
- send_request(make_tl_object<td_api::joinChatByInviteLink>(args));
+ send_request(td_api::make_object<td_api::joinChatByInviteLink>(args));
} else if (op == "gte") {
- send_request(make_tl_object<td_api::getTextEntities>(args));
- } else if (op == "gtes") {
- execute(make_tl_object<td_api::getTextEntities>(args));
+ send_request(td_api::make_object<td_api::getTextEntities>(args));
+ } else if (op == "gtee") {
+ execute(td_api::make_object<td_api::getTextEntities>(args));
+ } else if (op == "pm") {
+ send_request(
+ td_api::make_object<td_api::parseMarkdown>(td_api::make_object<td_api::formattedText>(args, Auto())));
} else if (op == "pte") {
- send_request(make_tl_object<td_api::parseTextEntities>(args, make_tl_object<td_api::textParseModeMarkdown>()));
+ send_request(
+ td_api::make_object<td_api::parseTextEntities>(args, td_api::make_object<td_api::textParseModeMarkdown>(2)));
+ } else if (op == "pteh") {
+ send_request(
+ td_api::make_object<td_api::parseTextEntities>(args, td_api::make_object<td_api::textParseModeHTML>()));
} else if (op == "ptes") {
- execute(make_tl_object<td_api::parseTextEntities>(args, make_tl_object<td_api::textParseModeMarkdown>()));
+ execute(
+ td_api::make_object<td_api::parseTextEntities>(args, td_api::make_object<td_api::textParseModeMarkdown>(2)));
+ } else if (op == "ptehs") {
+ execute(td_api::make_object<td_api::parseTextEntities>(args, td_api::make_object<td_api::textParseModeHTML>()));
} else if (op == "gfmt") {
- send_request(make_tl_object<td_api::getFileMimeType>(trim(args)));
+ execute(td_api::make_object<td_api::getFileMimeType>(trim(args)));
} else if (op == "gfe") {
- send_request(make_tl_object<td_api::getFileExtension>(trim(args)));
+ execute(td_api::make_object<td_api::getFileExtension>(trim(args)));
+ } else if (op == "cfn") {
+ execute(td_api::make_object<td_api::cleanFileName>(args));
+ } else if (op == "gjv") {
+ execute(td_api::make_object<td_api::getJsonValue>(args));
+ } else if (op == "gjvtest") {
+ execute(td_api::make_object<td_api::getJsonValue>("\"aba\200caba\""));
+ execute(td_api::make_object<td_api::getJsonValue>("\"\\u0080\""));
+ execute(td_api::make_object<td_api::getJsonValue>("\"\\uD800\""));
+ } else if (op == "gjs") {
+ auto test_get_json_string = [&](td_api::object_ptr<td_api::JsonValue> &&json_value) {
+ execute(td_api::make_object<td_api::getJsonString>(std::move(json_value)));
+ };
+
+ test_get_json_string(nullptr);
+ test_get_json_string(td_api::make_object<td_api::jsonValueNull>());
+ test_get_json_string(td_api::make_object<td_api::jsonValueBoolean>(true));
+ test_get_json_string(td_api::make_object<td_api::jsonValueNumber>(123456789123.0));
+ test_get_json_string(td_api::make_object<td_api::jsonValueString>(string("aba\0caba", 8)));
+ test_get_json_string(td_api::make_object<td_api::jsonValueString>("aba\200caba"));
+
+ auto inner_array = td_api::make_object<td_api::jsonValueArray>();
+ inner_array->values_.emplace_back(td_api::make_object<td_api::jsonValueBoolean>(false));
+ auto array = td_api::make_object<td_api::jsonValueArray>();
+ array->values_.emplace_back(nullptr);
+ array->values_.emplace_back(std::move(inner_array));
+ array->values_.emplace_back(td_api::make_object<td_api::jsonValueNull>());
+ array->values_.emplace_back(td_api::make_object<td_api::jsonValueNumber>(-1));
+ test_get_json_string(std::move(array));
+
+ auto object = td_api::make_object<td_api::jsonValueObject>();
+ object->members_.emplace_back(
+ td_api::make_object<td_api::jsonObjectMember>("", td_api::make_object<td_api::jsonValueString>("test")));
+ object->members_.emplace_back(td_api::make_object<td_api::jsonObjectMember>("a", nullptr));
+ object->members_.emplace_back(td_api::make_object<td_api::jsonObjectMember>("\x80", nullptr));
+ object->members_.emplace_back(nullptr);
+ object->members_.emplace_back(
+ td_api::make_object<td_api::jsonObjectMember>("a", td_api::make_object<td_api::jsonValueNull>()));
+ test_get_json_string(std::move(object));
+ } else if (op == "gtpjs") {
+ execute(td_api::make_object<td_api::getThemeParametersJsonString>(as_theme_parameters()));
+ } else if (op == "gac") {
+ send_request(td_api::make_object<td_api::getApplicationConfig>());
+ } else if (op == "sale") {
+ string type;
+ ChatId chat_id;
+ string json;
+ get_args(args, type, chat_id, json);
+ auto result = execute(td_api::make_object<td_api::getJsonValue>(json));
+ if (result->get_id() == td_api::error::ID) {
+ LOG(ERROR) << to_string(result);
+ } else {
+ send_request(td_api::make_object<td_api::saveApplicationLogEvent>(
+ type, chat_id, move_tl_object_as<td_api::JsonValue>(result)));
+ }
} else {
op_not_found_count++;
}
- if (op == "scdm") {
- string chat_id;
+ if (op == "scdm" || op == "scdmt") {
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
string reply_to_message_id;
string message;
- std::tie(chat_id, args) = split(args);
- std::tie(reply_to_message_id, message) = split(args);
- tl_object_ptr<td_api::draftMessage> draft_message;
+ if (op == "scdmt") {
+ get_args(args, message_thread_id, args);
+ }
+ get_args(args, chat_id, reply_to_message_id, message);
+ td_api::object_ptr<td_api::draftMessage> draft_message;
if (!reply_to_message_id.empty() || !message.empty()) {
- vector<tl_object_ptr<td_api::textEntity>> entities;
- entities.push_back(make_tl_object<td_api::textEntity>(0, 1, make_tl_object<td_api::textEntityTypePre>()));
-
- draft_message = make_tl_object<td_api::draftMessage>(
- as_message_id(reply_to_message_id),
- make_tl_object<td_api::inputMessageText>(
- make_tl_object<td_api::formattedText>(message, std::move(entities)), true, false));
+ vector<td_api::object_ptr<td_api::textEntity>> entities;
+ entities.push_back(
+ td_api::make_object<td_api::textEntity>(0, 1, td_api::make_object<td_api::textEntityTypePre>()));
+ draft_message = td_api::make_object<td_api::draftMessage>(
+ as_message_id(reply_to_message_id), 0,
+ td_api::make_object<td_api::inputMessageText>(as_formatted_text(message, std::move(entities)), true,
+ false));
}
- send_request(make_tl_object<td_api::setChatDraftMessage>(as_chat_id(chat_id), std::move(draft_message)));
- } else if (op == "tcip") {
- string chat_id;
- string is_pinned;
- std::tie(chat_id, is_pinned) = split(args);
- send_request(make_tl_object<td_api::toggleChatIsPinned>(as_chat_id(chat_id), as_bool(is_pinned)));
- } else if (op == "spchats") {
- vector<string> chat_ids_str = full_split(args, ' ');
- vector<int64> chat_ids;
- for (auto &chat_id_str : chat_ids_str) {
- chat_ids.push_back(as_chat_id(chat_id_str));
- }
- send_request(make_tl_object<td_api::setPinnedChats>(std::move(chat_ids)));
+ send_request(
+ td_api::make_object<td_api::setChatDraftMessage>(chat_id, message_thread_id, std::move(draft_message)));
+ } else if (op == "cadm") {
+ send_request(td_api::make_object<td_api::clearAllDraftMessages>());
+ } else if (op == "tchpc") {
+ ChatId chat_id;
+ bool has_protected_content;
+ get_args(args, chat_id, has_protected_content);
+ send_request(td_api::make_object<td_api::toggleChatHasProtectedContent>(chat_id, has_protected_content));
+ } else if (op == "tcip" || op == "tcipa" || begins_with(op, "tcip-")) {
+ ChatId chat_id;
+ bool is_pinned;
+ get_args(args, chat_id, is_pinned);
+ send_request(td_api::make_object<td_api::toggleChatIsPinned>(as_chat_list(op), chat_id, is_pinned));
+ } else if (op == "tcimau") {
+ ChatId chat_id;
+ bool is_marked_as_read;
+ get_args(args, chat_id, is_marked_as_read);
+ send_request(td_api::make_object<td_api::toggleChatIsMarkedAsUnread>(chat_id, is_marked_as_read));
+ } else if (op == "tmsib") {
+ string sender_id;
+ bool is_blocked;
+ get_args(args, sender_id, is_blocked);
+ send_request(td_api::make_object<td_api::toggleMessageSenderIsBlocked>(as_message_sender(sender_id), is_blocked));
+ } else if (op == "bmsfr") {
+ MessageId message_id;
+ bool delete_message;
+ bool delete_all_messages;
+ bool report_spam;
+ get_args(args, message_id, delete_message, delete_all_messages, report_spam);
+ send_request(td_api::make_object<td_api::blockMessageSenderFromReplies>(message_id, delete_message,
+ delete_all_messages, report_spam));
+ } else if (op == "tcddn") {
+ ChatId chat_id;
+ bool default_disable_notification;
+ get_args(args, chat_id, default_disable_notification);
+ send_request(
+ td_api::make_object<td_api::toggleChatDefaultDisableNotification>(chat_id, default_disable_notification));
+ } else if (op == "spchats" || op == "spchatsa" || begins_with(op, "spchats-")) {
+ send_request(td_api::make_object<td_api::setPinnedChats>(as_chat_list(op), as_chat_ids(args)));
+ } else if (op == "gamb") {
+ UserId user_id;
+ get_args(args, user_id);
+ send_request(td_api::make_object<td_api::getAttachmentMenuBot>(user_id));
+ } else if (op == "tbiatam") {
+ UserId user_id;
+ bool is_added;
+ get_args(args, user_id, is_added);
+ send_request(td_api::make_object<td_api::toggleBotIsAddedToAttachmentMenu>(user_id, is_added));
+ } else if (op == "gwau") {
+ UserId user_id;
+ string url;
+ get_args(args, user_id, url);
+ send_request(td_api::make_object<td_api::getWebAppUrl>(user_id, url, as_theme_parameters(), "android"));
+ } else if (op == "swad") {
+ UserId user_id;
+ string button_text;
+ string data;
+ get_args(args, user_id, button_text, data);
+ send_request(td_api::make_object<td_api::sendWebAppData>(user_id, button_text, data));
+ } else if (op == "owa") {
+ ChatId chat_id;
+ UserId bot_user_id;
+ string url;
+ MessageId reply_to_message_id;
+ MessageThreadId message_thread_id;
+ get_args(args, chat_id, bot_user_id, url, reply_to_message_id, message_thread_id);
+ send_request(td_api::make_object<td_api::openWebApp>(chat_id, bot_user_id, url, as_theme_parameters(), "android",
+ reply_to_message_id, message_thread_id));
+ } else if (op == "cwa") {
+ int64 launch_id;
+ get_args(args, launch_id);
+ send_request(td_api::make_object<td_api::closeWebApp>(launch_id));
} else if (op == "sca") {
- string chat_id;
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
string action;
- std::tie(chat_id, action) = split(args);
- send_request(make_tl_object<td_api::sendChatAction>(as_chat_id(chat_id), get_chat_action(action)));
+ get_args(args, chat_id, message_thread_id, action);
+ send_request(td_api::make_object<td_api::sendChatAction>(chat_id, message_thread_id, as_chat_action(action)));
} else if (op == "smt" || op == "smtp" || op == "smtf" || op == "smtpf") {
- const string &chat_id = args;
+ ChatId chat_id;
+ get_args(args, chat_id);
for (int i = 1; i <= 200; i++) {
- string message = PSTRING() << "#" << i;
+ string message = PSTRING() << (Random::fast(0, 3) == 0 && i > 90 ? "sleep " : "") << "#" << i;
if (i == 6 || (op.back() == 'f' && i % 2 == 0)) {
message = string(4097, 'a');
}
if (op[3] == 'p') {
- send_message(chat_id, make_tl_object<td_api::inputMessagePhoto>(as_local_file("rgb.jpg"), nullptr, Auto(), 0,
- 0, as_caption(message), 0));
+ send_message(chat_id, td_api::make_object<td_api::inputMessagePhoto>(as_local_file("rgb.jpg"), nullptr,
+ Auto(), 0, 0, as_caption(message), 0));
} else {
- send_message(chat_id,
- make_tl_object<td_api::inputMessageText>(
- make_tl_object<td_api::formattedText>(message, vector<tl_object_ptr<td_api::textEntity>>()),
- false, true));
+ send_message(chat_id, td_api::make_object<td_api::inputMessageText>(as_formatted_text(message), false, true));
}
}
} else if (op == "ssm") {
- string chat_id;
- string from_search_id;
- string limit;
+ ChatId chat_id;
string filter;
- string query;
-
- std::tie(chat_id, args) = split(args);
- std::tie(from_search_id, args) = split(args);
- std::tie(limit, args) = split(args);
- std::tie(filter, query) = split(args);
-
- send_request(
- make_tl_object<td_api::searchSecretMessages>(as_chat_id(chat_id), query, to_integer<int64>(from_search_id),
- to_integer<int32>(limit), get_search_messages_filter(filter)));
+ string offset;
+ SearchQuery query;
+ get_args(args, chat_id, filter, offset, query);
+ send_request(td_api::make_object<td_api::searchSecretMessages>(chat_id, query.query, offset, query.limit,
+ as_search_messages_filter(filter)));
+ } else if (op == "ssd") {
+ schedule_date_ = std::move(args);
+ } else if (op == "smti") {
+ get_args(args, message_thread_id_);
+ } else if (op == "gcams") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getChatAvailableMessageSenders>(chat_id));
+ } else if (op == "scmsr") {
+ ChatId chat_id;
+ string sender_id;
+ get_args(args, chat_id, sender_id);
+ send_request(td_api::make_object<td_api::setChatMessageSender>(chat_id, as_message_sender(sender_id)));
} else if (op == "sm" || op == "sms" || op == "smr" || op == "smf") {
- string chat_id;
- string reply_to_message_id;
+ ChatId chat_id;
+ MessageId reply_to_message_id;
string message;
-
- std::tie(chat_id, message) = split(args);
+ get_args(args, chat_id, message);
if (op == "smr") {
- std::tie(reply_to_message_id, message) = split(message);
+ get_args(message, reply_to_message_id, message);
}
if (op == "smf") {
- message = string(1000097, 'a');
+ message = string(5097, 'a');
}
-
- auto parsed_text =
- execute(make_tl_object<td_api::parseTextEntities>(message, make_tl_object<td_api::textParseModeMarkdown>()));
- if (parsed_text->get_id() == td_api::error::ID) {
- parsed_text = make_tl_object<td_api::formattedText>(message, vector<tl_object_ptr<td_api::textEntity>>());
+ send_message(chat_id, td_api::make_object<td_api::inputMessageText>(as_formatted_text(message), false, true),
+ op == "sms", false, reply_to_message_id);
+ } else if (op == "smce") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ vector<td_api::object_ptr<td_api::textEntity>> entities;
+ entities.push_back(td_api::make_object<td_api::textEntity>(
+ 0, 2, td_api::make_object<td_api::textEntityTypeCustomEmoji>(5368324170671202286)));
+ entities.push_back(td_api::make_object<td_api::textEntity>(
+ 3, 2, td_api::make_object<td_api::textEntityTypeCustomEmoji>(5377637695583426942)));
+ entities.push_back(td_api::make_object<td_api::textEntity>(
+ 6, 5, td_api::make_object<td_api::textEntityTypeCustomEmoji>(5368324170671202286)));
+ auto text = as_formatted_text("👍 😉 🧑‍🚒", std::move(entities));
+ send_message(chat_id, td_api::make_object<td_api::inputMessageText>(std::move(text), false, true), false, false,
+ 0);
+ } else if (op == "alm" || op == "almr") {
+ ChatId chat_id;
+ string sender_id;
+ MessageId reply_to_message_id;
+ string message;
+ get_args(args, chat_id, sender_id, message);
+ if (op == "almr") {
+ get_args(message, reply_to_message_id, message);
}
-
- send_message(
- chat_id,
- make_tl_object<td_api::inputMessageText>(move_tl_object_as<td_api::formattedText>(parsed_text), false, true),
- op == "sms", false, as_message_id(reply_to_message_id));
- } else if (op == "smap" || op == "smapr") {
- string chat_id;
- string reply_to_message_id;
- vector<string> photos;
-
- std::tie(chat_id, args) = split(args);
- if (op == "smapr") {
- std::tie(reply_to_message_id, args) = split(args);
+ send_request(td_api::make_object<td_api::addLocalMessage>(
+ chat_id, as_message_sender(sender_id), reply_to_message_id, false,
+ td_api::make_object<td_api::inputMessageText>(as_formatted_text(message), false, true)));
+ } else if (op == "smap" || op == "smapr" || op == "smapp" || op == "smaprp") {
+ ChatId chat_id;
+ MessageId reply_to_message_id;
+ get_args(args, chat_id, args);
+ if (op == "smapr" || op == "smaprp") {
+ get_args(args, reply_to_message_id, args);
}
- photos = full_split(args);
-
- send_request(make_tl_object<td_api::sendMessageAlbum>(
- as_chat_id(chat_id), as_message_id(reply_to_message_id), false, false,
- transform(photos, [](const string &photo_path) {
- tl_object_ptr<td_api::InputMessageContent> content = make_tl_object<td_api::inputMessagePhoto>(
- as_local_file(photo_path), nullptr, Auto(), 0, 0, as_caption(""), 0);
- return content;
- })));
+ auto input_message_contents = transform(full_split(args), [](const string &photo) {
+ td_api::object_ptr<td_api::InputMessageContent> content = td_api::make_object<td_api::inputMessagePhoto>(
+ as_input_file(photo), nullptr, Auto(), 0, 0, as_caption(""), 0);
+ return content;
+ });
+ send_request(td_api::make_object<td_api::sendMessageAlbum>(
+ chat_id, message_thread_id_, reply_to_message_id, default_message_send_options(),
+ std::move(input_message_contents), op == "smapp" || op == "smaprp"));
+ } else if (op == "smad" || op == "smadp") {
+ ChatId chat_id;
+ get_args(args, chat_id, args);
+ auto input_message_contents = transform(full_split(args), [](const string &document) {
+ td_api::object_ptr<td_api::InputMessageContent> content =
+ td_api::make_object<td_api::inputMessageDocument>(as_input_file(document), nullptr, true, as_caption(""));
+ return content;
+ });
+ send_request(td_api::make_object<td_api::sendMessageAlbum>(chat_id, message_thread_id_, 0,
+ default_message_send_options(),
+ std::move(input_message_contents), op.back() == 'p'));
+ } else if (op == "gmft") {
+ auto r_message_file_head = read_file_str(args, 2 << 10);
+ if (r_message_file_head.is_error()) {
+ LOG(ERROR) << r_message_file_head.error();
+ } else {
+ auto message_file_head = r_message_file_head.move_as_ok();
+ while (!check_utf8(message_file_head)) {
+ message_file_head.pop_back();
+ }
+ send_request(td_api::make_object<td_api::getMessageFileType>(message_file_head));
+ }
+ } else if (op == "gmict") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getMessageImportConfirmationText>(chat_id));
+ } else if (op == "im") {
+ ChatId chat_id;
+ string message_file;
+ vector<string> attached_files;
+ get_args(args, chat_id, message_file, args);
+ attached_files = full_split(args);
+ send_request(td_api::make_object<td_api::importMessages>(chat_id, as_input_file(message_file),
+ transform(attached_files, as_input_file)));
} else if (op == "em") {
- string chat_id;
- string message_id;
+ ChatId chat_id;
+ MessageId message_id;
string message;
- std::tie(chat_id, args) = split(args);
- std::tie(message_id, message) = split(args);
- send_request(make_tl_object<td_api::editMessageText>(
- as_chat_id(chat_id), as_message_id(message_id), nullptr,
- make_tl_object<td_api::inputMessageText>(
- make_tl_object<td_api::formattedText>(message, vector<tl_object_ptr<td_api::textEntity>>()), true,
- true)));
+ get_args(args, chat_id, message_id, message);
+ send_request(td_api::make_object<td_api::editMessageText>(
+ chat_id, message_id, nullptr,
+ td_api::make_object<td_api::inputMessageText>(as_formatted_text(message), true, true)));
+ } else if (op == "eman") {
+ ChatId chat_id;
+ MessageId message_id;
+ string animation;
+ get_args(args, chat_id, message_id, animation);
+ send_request(td_api::make_object<td_api::editMessageMedia>(
+ chat_id, message_id, nullptr,
+ td_api::make_object<td_api::inputMessageAnimation>(as_input_file(animation), nullptr, vector<int32>(), 0, 0,
+ 0, as_caption("animation"))));
+ } else if (op == "emc") {
+ ChatId chat_id;
+ MessageId message_id;
+ string caption;
+ get_args(args, chat_id, message_id, caption);
+ send_request(td_api::make_object<td_api::editMessageCaption>(chat_id, message_id, nullptr, as_caption(caption)));
+ } else if (op == "emd") {
+ ChatId chat_id;
+ MessageId message_id;
+ string document;
+ get_args(args, chat_id, message_id, document);
+ send_request(td_api::make_object<td_api::editMessageMedia>(
+ chat_id, message_id, nullptr,
+ td_api::make_object<td_api::inputMessageDocument>(as_input_file(document), nullptr, false, as_caption(""))));
+ } else if (op == "emp" || op == "empttl") {
+ ChatId chat_id;
+ MessageId message_id;
+ string photo;
+ get_args(args, chat_id, message_id, photo);
+ send_request(td_api::make_object<td_api::editMessageMedia>(
+ chat_id, message_id, nullptr,
+ td_api::make_object<td_api::inputMessagePhoto>(as_input_file(photo), as_input_thumbnail(photo), Auto(), 0, 0,
+ as_caption(""), op == "empttl" ? 10 : 0)));
+ } else if (op == "emvt") {
+ ChatId chat_id;
+ MessageId message_id;
+ string video;
+ string thumbnail;
+ get_args(args, chat_id, message_id, video, thumbnail);
+ send_request(td_api::make_object<td_api::editMessageMedia>(
+ chat_id, message_id, nullptr,
+ td_api::make_object<td_api::inputMessageVideo>(as_input_file(video), as_input_thumbnail(thumbnail), Auto(), 1,
+ 2, 3, true, as_caption(""), 0)));
} else if (op == "emll") {
- string chat_id;
- string message_id;
+ ChatId chat_id;
+ MessageId message_id;
string latitude;
string longitude;
- std::tie(chat_id, args) = split(args);
- std::tie(message_id, args) = split(args);
- std::tie(latitude, longitude) = split(args);
- send_request(make_tl_object<td_api::editMessageLiveLocation>(as_chat_id(chat_id), as_message_id(message_id),
- nullptr, as_location(latitude, longitude)));
+ string accuracy;
+ int32 heading;
+ int32 proximity_alert_radius;
+ get_args(args, chat_id, message_id, latitude, longitude, accuracy, heading, proximity_alert_radius);
+ send_request(td_api::make_object<td_api::editMessageLiveLocation>(
+ chat_id, message_id, nullptr, as_location(latitude, longitude, accuracy), heading, proximity_alert_radius));
+ } else if (op == "emss") {
+ ChatId chat_id;
+ MessageId message_id;
+ string date;
+ get_args(args, chat_id, message_id, date);
+ send_request(td_api::make_object<td_api::editMessageSchedulingState>(chat_id, message_id,
+ as_message_scheduling_state(date)));
+ } else if (op == "gftdi") {
+ send_request(td_api::make_object<td_api::getForumTopicDefaultIcons>());
+ } else if (op == "cft") {
+ ChatId chat_id;
+ string name;
+ int32 icon_color;
+ get_args(args, chat_id, name, icon_color);
+ send_request(td_api::make_object<td_api::createForumTopic>(
+ chat_id, name, td_api::make_object<td_api::forumTopicIcon>(icon_color, 0)));
+ } else if (op == "eft") {
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
+ string name;
+ int64 icon_custom_emoji_id;
+ get_args(args, chat_id, message_thread_id, name, icon_custom_emoji_id);
+ send_request(td_api::make_object<td_api::editForumTopic>(chat_id, message_thread_id, name, icon_custom_emoji_id));
+ } else if (op == "tftic") {
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
+ bool is_closed;
+ get_args(args, chat_id, message_thread_id, is_closed);
+ send_request(td_api::make_object<td_api::toggleForumTopicIsClosed>(chat_id, message_thread_id, is_closed));
+ } else if (op == "dft") {
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
+ get_args(args, chat_id, message_thread_id);
+ send_request(td_api::make_object<td_api::deleteForumTopic>(chat_id, message_thread_id));
} else if (op == "gallm") {
- send_request(make_tl_object<td_api::getActiveLiveLocationMessages>());
+ send_request(td_api::make_object<td_api::getActiveLiveLocationMessages>());
} else if (op == "sbsm") {
- string bot_id;
- string chat_id;
+ UserId bot_user_id;
+ ChatId chat_id;
string parameter;
- std::tie(bot_id, args) = split(args);
- std::tie(chat_id, parameter) = split(args);
- send_request(make_tl_object<td_api::sendBotStartMessage>(as_user_id(bot_id), as_chat_id(chat_id), parameter));
+ get_args(args, bot_user_id, chat_id, parameter);
+ send_request(td_api::make_object<td_api::sendBotStartMessage>(bot_user_id, chat_id, parameter));
} else if (op == "giqr") {
string bot_id;
string query;
- std::tie(bot_id, query) = split(args);
- send_request(make_tl_object<td_api::getInlineQueryResults>(as_user_id(bot_id), 0, nullptr, query, ""));
+ get_args(args, bot_id, query);
+ send_request(td_api::make_object<td_api::getInlineQueryResults>(as_user_id(bot_id), as_chat_id(bot_id), nullptr,
+ query, ""));
} else if (op == "giqro") {
- string bot_id;
+ UserId bot_user_id;
string offset;
string query;
- std::tie(bot_id, args) = split(args);
- std::tie(offset, query) = split(args);
- send_request(make_tl_object<td_api::getInlineQueryResults>(as_user_id(bot_id), 0, nullptr, query, offset));
+ get_args(args, bot_user_id, offset, query);
+ send_request(td_api::make_object<td_api::getInlineQueryResults>(bot_user_id, 0, nullptr, query, offset));
} else if (op == "giqrl") {
- string bot_id;
+ UserId bot_user_id;
string query;
- std::tie(bot_id, query) = split(args);
+ get_args(args, bot_user_id, query);
send_request(
- make_tl_object<td_api::getInlineQueryResults>(as_user_id(bot_id), 0, as_location("1.1", "2.2"), query, ""));
- } else if (op == "siqr") {
- string chat_id;
- string query_id;
+ td_api::make_object<td_api::getInlineQueryResults>(bot_user_id, 0, as_location("1.1", "2.2", ""), query, ""));
+ } else if (op == "siqr" || op == "siqrh") {
+ ChatId chat_id;
+ int64 query_id;
string result_id;
- std::tie(chat_id, args) = split(args);
- std::tie(query_id, result_id) = split(args);
-
- auto chat = as_chat_id(chat_id);
- send_request(make_tl_object<td_api::sendInlineQueryResultMessage>(chat, 0, false, false,
- to_integer<int64>(query_id), result_id));
- } else if (op == "gcqr") {
- string chat_id;
- string message_id;
+ get_args(args, chat_id, query_id, result_id);
+ send_request(td_api::make_object<td_api::sendInlineQueryResultMessage>(
+ chat_id, message_thread_id_, 0, default_message_send_options(), query_id, result_id, op == "siqrh"));
+ } else if (op == "gcqa") {
+ ChatId chat_id;
+ MessageId message_id;
string data;
- std::tie(chat_id, args) = split(args);
- std::tie(message_id, data) = split(args);
- send_request(make_tl_object<td_api::getCallbackQueryAnswer>(
- as_chat_id(chat_id), as_message_id(message_id), make_tl_object<td_api::callbackQueryPayloadData>(data)));
- } else if (op == "gcgqr") {
- string chat_id;
- string message_id;
- std::tie(chat_id, message_id) = split(args);
- send_request(make_tl_object<td_api::getCallbackQueryAnswer>(
- as_chat_id(chat_id), as_message_id(message_id), make_tl_object<td_api::callbackQueryPayloadGame>("")));
- } else if (op == "san") {
- string chat_id;
+ get_args(args, chat_id, message_id, data);
+ send_request(td_api::make_object<td_api::getCallbackQueryAnswer>(
+ chat_id, message_id, td_api::make_object<td_api::callbackQueryPayloadData>(data)));
+ } else if (op == "gcpqa") {
+ ChatId chat_id;
+ MessageId message_id;
+ string password;
+ string data;
+ get_args(args, chat_id, message_id, password, data);
+ send_request(td_api::make_object<td_api::getCallbackQueryAnswer>(
+ chat_id, message_id, td_api::make_object<td_api::callbackQueryPayloadDataWithPassword>(password, data)));
+ } else if (op == "gcgqa") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::getCallbackQueryAnswer>(
+ chat_id, message_id, td_api::make_object<td_api::callbackQueryPayloadGame>("")));
+ } else {
+ op_not_found_count++;
+ }
+
+ if (op == "san") {
+ ChatId chat_id;
string animation_path;
- string width;
- string height;
+ int32 width;
+ int32 height;
string caption;
- std::tie(chat_id, args) = split(args);
- std::tie(animation_path, args) = split(args);
- std::tie(width, args) = split(args);
- std::tie(height, caption) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageAnimation>(
- as_local_file(animation_path), nullptr, 60, to_integer<int32>(width),
- to_integer<int32>(height), as_caption(caption)));
+ get_args(args, chat_id, animation_path, width, height, caption);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageAnimation>(as_input_file(animation_path), nullptr,
+ vector<int32>(), 60, width, height,
+ as_caption(caption)));
} else if (op == "sang") {
- string chat_id;
+ ChatId chat_id;
string animation_path;
string animation_conversion;
- std::tie(chat_id, args) = split(args);
- std::tie(animation_path, animation_conversion) = split(args);
- send_message(chat_id,
- make_tl_object<td_api::inputMessageAnimation>(
- as_generated_file(animation_path, animation_conversion), nullptr, 60, 0, 0, as_caption("")));
+ get_args(args, chat_id, animation_path, animation_conversion);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageAnimation>(
+ as_generated_file(animation_path, animation_conversion), nullptr, vector<int32>(), 60,
+ 0, 0, as_caption("")));
} else if (op == "sanid") {
- string chat_id;
+ ChatId chat_id;
string file_id;
- std::tie(chat_id, file_id) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageAnimation>(as_input_file_id(file_id), nullptr, 0, 0, 0,
- as_caption("")));
+ get_args(args, chat_id, file_id);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageAnimation>(
+ as_input_file_id(file_id), nullptr, vector<int32>(), 0, 0, 0, as_caption("")));
} else if (op == "sanurl") {
- string chat_id;
+ ChatId chat_id;
string url;
- std::tie(chat_id, url) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageAnimation>(
- td_api::make_object<td_api::inputFileGenerated>(url, "#url#", 0), nullptr, 0, 0, 0,
- as_caption("")));
+ get_args(args, chat_id, url);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageAnimation>(
+ as_generated_file(url, "#url#"), nullptr, vector<int32>(), 0, 0, 0, as_caption("")));
} else if (op == "sanurl2") {
- string chat_id;
+ ChatId chat_id;
string url;
- std::tie(chat_id, url) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageAnimation>(
- td_api::make_object<td_api::inputFileRemote>(url), nullptr, 0, 0, 0, as_caption("")));
+ get_args(args, chat_id, url);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageAnimation>(
+ as_remote_file(url), nullptr, vector<int32>(), 0, 0, 0, as_caption("")));
} else if (op == "sau") {
- string chat_id;
+ ChatId chat_id;
string audio_path;
- string duration;
+ int32 duration;
string title;
string performer;
- std::tie(chat_id, args) = split(args);
- std::tie(audio_path, args) = split(args);
- std::tie(duration, args) = split(args);
- std::tie(title, performer) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageAudio>(as_local_file(audio_path), nullptr,
- to_integer<int32>(duration), title, performer,
- as_caption("audio caption")));
+ get_args(args, chat_id, audio_path, duration, title, performer);
+ send_message(chat_id,
+ td_api::make_object<td_api::inputMessageAudio>(as_input_file(audio_path), nullptr, duration, title,
+ performer, as_caption("audio caption")));
} else if (op == "svoice") {
- string chat_id;
+ ChatId chat_id;
string voice_path;
- std::tie(chat_id, voice_path) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageVoiceNote>(as_local_file(voice_path), 0, "abacaba",
- as_caption("voice caption")));
+ get_args(args, chat_id, voice_path);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageVoiceNote>(as_input_file(voice_path), 0, "abacaba",
+ as_caption("voice caption")));
} else if (op == "SendContact" || op == "scontact") {
- string chat_id;
+ ChatId chat_id;
string phone_number;
string first_name;
string last_name;
- string user_id;
- std::tie(chat_id, args) = split(args);
- std::tie(phone_number, args) = split(args);
- std::tie(first_name, args) = split(args);
- std::tie(last_name, user_id) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageContact>(make_tl_object<td_api::contact>(
- phone_number, first_name, last_name, as_user_id(user_id))));
- } else if (op == "sf") {
- string chat_id;
- string from_chat_id;
- string from_message_id;
- std::tie(chat_id, args) = split(args);
- std::tie(from_chat_id, from_message_id) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageForwarded>(as_chat_id(from_chat_id),
- as_message_id(from_message_id), true));
- } else if (op == "sd") {
- string chat_id;
+ UserId user_id;
+ get_args(args, chat_id, phone_number, first_name, last_name, user_id);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageContact>(td_api::make_object<td_api::contact>(
+ phone_number, first_name, last_name, string(), user_id)));
+ } else if (op == "sf" || op == "scopy") {
+ ChatId chat_id;
+ ChatId from_chat_id;
+ MessageId from_message_id;
+ get_args(args, chat_id, from_chat_id, from_message_id);
+ td_api::object_ptr<td_api::messageCopyOptions> copy_options;
+ if (op == "scopy") {
+ copy_options = td_api::make_object<td_api::messageCopyOptions>(true, rand_bool(), as_caption("_as_d"));
+ }
+ send_message(chat_id, td_api::make_object<td_api::inputMessageForwarded>(from_chat_id, from_message_id, true,
+ std::move(copy_options)));
+ } else if (op == "sdice" || op == "sdicecd") {
+ ChatId chat_id;
+ string emoji;
+ get_args(args, chat_id, emoji);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageDice>(emoji, op == "sdicecd"));
+ } else if (op == "sd" || op == "sdf") {
+ ChatId chat_id;
string document_path;
- std::tie(chat_id, document_path) = split(args);
- send_message(chat_id, make_tl_object<td_api::inputMessageDocument>(
- as_local_file(document_path), nullptr,
+ get_args(args, chat_id, document_path);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageDocument>(
+ as_input_file(document_path), nullptr, op == "sdf",
as_caption(u8"\u1680\u180Etest \u180E\n\u180E\n\u180E\n cap\ttion\u180E\u180E")));
- } else if (op == "sdt") {
- string chat_id;
+ } else if (op == "sdt" || op == "sdtf") {
+ ChatId chat_id;
string document_path;
string thumbnail_path;
- std::tie(chat_id, args) = split(args);
- std::tie(document_path, thumbnail_path) = split(args);
- send_message(chat_id, make_tl_object<td_api::inputMessageDocument>(
- as_local_file(document_path),
- make_tl_object<td_api::inputThumbnail>(as_local_file(thumbnail_path), 0, 0),
+ get_args(args, chat_id, document_path, thumbnail_path);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageDocument>(
+ as_input_file(document_path), as_input_thumbnail(thumbnail_path), op == "sdtf",
as_caption("test caption")));
- } else if (op == "sdg") {
- string chat_id;
+ } else if (op == "sdg" || op == "sdgu") {
+ ChatId chat_id;
string document_path;
string document_conversion;
- std::tie(chat_id, args) = split(args);
- std::tie(document_path, document_conversion) = split(args);
- send_message(chat_id,
- make_tl_object<td_api::inputMessageDocument>(as_generated_file(document_path, document_conversion),
- nullptr, as_caption("test caption")));
+ get_args(args, chat_id, document_path, document_conversion);
+ if (op == "sdgu") {
+ send_request(td_api::make_object<td_api::preliminaryUploadFile>(
+ as_generated_file(document_path, document_conversion), nullptr, 1));
+ }
+ send_message(chat_id, td_api::make_object<td_api::inputMessageDocument>(
+ as_generated_file(document_path, document_conversion), nullptr, false,
+ as_caption("test caption")));
} else if (op == "sdtg") {
- string chat_id;
+ ChatId chat_id;
string document_path;
string thumbnail_path;
string thumbnail_conversion;
- std::tie(chat_id, args) = split(args);
- std::tie(document_path, args) = split(args);
- std::tie(thumbnail_path, thumbnail_conversion) = split(args);
- send_message(chat_id, make_tl_object<td_api::inputMessageDocument>(
- as_local_file(document_path),
- make_tl_object<td_api::inputThumbnail>(
- as_generated_file(thumbnail_path, thumbnail_conversion), 0, 0),
- as_caption("test caption")));
+ get_args(args, chat_id, document_path, thumbnail_path, thumbnail_conversion);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageDocument>(
+ as_input_file(document_path), as_input_thumbnail(thumbnail_path, thumbnail_conversion),
+ false, as_caption("test caption")));
} else if (op == "sdgtg") {
- string chat_id;
+ ChatId chat_id;
string document_path;
string document_conversion;
string thumbnail_path;
string thumbnail_conversion;
- std::tie(chat_id, args) = split(args);
- std::tie(document_path, args) = split(args);
- std::tie(document_conversion, args) = split(args);
- std::tie(thumbnail_path, thumbnail_conversion) = split(args);
- send_message(chat_id, make_tl_object<td_api::inputMessageDocument>(
- as_generated_file(document_path, document_conversion),
- make_tl_object<td_api::inputThumbnail>(
- as_generated_file(thumbnail_path, thumbnail_conversion), 0, 0),
- as_caption("test caption")));
+ get_args(args, chat_id, document_path, document_conversion, thumbnail_path, thumbnail_conversion);
+ send_message(chat_id,
+ td_api::make_object<td_api::inputMessageDocument>(
+ as_generated_file(document_path, document_conversion),
+ as_input_thumbnail(thumbnail_path, thumbnail_conversion), false, as_caption("test caption")));
} else if (op == "sdid") {
- string chat_id;
+ ChatId chat_id;
string file_id;
- std::tie(chat_id, file_id) = split(args);
- send_message(chat_id,
- make_tl_object<td_api::inputMessageDocument>(as_input_file_id(file_id), nullptr, as_caption("")));
+ get_args(args, chat_id, file_id);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageDocument>(as_input_file_id(file_id), nullptr, false,
+ as_caption("")));
+ } else if (op == "sdurl") {
+ ChatId chat_id;
+ string url;
+ get_args(args, chat_id, url);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageDocument>(as_remote_file(url), nullptr, false,
+ as_caption("")));
} else if (op == "sg") {
- string chat_id;
- string bot_user_id;
+ ChatId chat_id;
+ UserId bot_user_id;
string game_short_name;
- std::tie(chat_id, args) = split(args);
- std::tie(bot_user_id, game_short_name) = split(args);
- send_message(chat_id, make_tl_object<td_api::inputMessageGame>(as_user_id(bot_user_id), game_short_name));
+ get_args(args, chat_id, bot_user_id, game_short_name);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageGame>(bot_user_id, game_short_name));
} else if (op == "sl") {
- string chat_id;
- std::tie(chat_id, args) = split(args);
-
+ ChatId chat_id;
string latitude;
string longitude;
- std::tie(latitude, longitude) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageLocation>(as_location(latitude, longitude), 0));
+ string accuracy;
+ get_args(args, chat_id, latitude, longitude, accuracy);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageLocation>(
+ as_location(latitude, longitude, accuracy), 0, 0, 0));
} else if (op == "sll") {
- string chat_id;
- string period;
+ ChatId chat_id;
+ int32 period;
string latitude;
string longitude;
- std::tie(chat_id, args) = split(args);
- std::tie(period, args) = split(args);
- std::tie(latitude, longitude) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageLocation>(as_location(latitude, longitude),
- to_integer<int32>(period)));
- } else if (op == "sp") {
- string chat_id;
- string photo_path;
- string sticker_file_ids_str;
- vector<int32> sticker_file_ids;
- std::tie(chat_id, args) = split(args);
- std::tie(sticker_file_ids_str, photo_path) = split(args);
- if (trim(photo_path).empty()) {
- photo_path = sticker_file_ids_str;
+ string accuracy;
+ int32 heading;
+ int32 proximity_alert_radius;
+ get_args(args, chat_id, period, latitude, longitude, accuracy, heading, proximity_alert_radius);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageLocation>(
+ as_location(latitude, longitude, accuracy), period, heading, proximity_alert_radius));
+ } else if (op == "spoll" || op == "spollm" || op == "spollp" || op == "squiz") {
+ ChatId chat_id;
+ string question;
+ get_args(args, chat_id, question, args);
+ auto options = autosplit_str(args);
+ td_api::object_ptr<td_api::PollType> poll_type;
+ if (op == "squiz") {
+ poll_type = td_api::make_object<td_api::pollTypeQuiz>(narrow_cast<int32>(options.size() - 1),
+ as_formatted_text("_te*st*_"));
} else {
- sticker_file_ids = to_integers<int32>(sticker_file_ids_str, ',');
+ poll_type = td_api::make_object<td_api::pollTypeRegular>(op == "spollm");
}
-
- send_message(chat_id,
- make_tl_object<td_api::inputMessagePhoto>(as_local_file(photo_path), nullptr,
- std::move(sticker_file_ids), 0, 0, as_caption(""), 0));
- } else if (op == "spttl") {
- string chat_id;
- string photo_path;
- std::tie(chat_id, photo_path) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessagePhoto>(as_local_file(photo_path), nullptr, Auto(), 0, 0,
- as_caption(""), 10));
- } else if (op == "spg") {
- string chat_id;
+ send_message(chat_id, td_api::make_object<td_api::inputMessagePoll>(question, std::move(options), op != "spollp",
+ std::move(poll_type), 0, 0, false));
+ } else if (op == "sp" || op == "spttl") {
+ ChatId chat_id;
+ string photo;
+ string caption;
+ string sticker_file_ids;
+ get_args(args, chat_id, photo, caption, sticker_file_ids);
+ send_message(chat_id, td_api::make_object<td_api::inputMessagePhoto>(
+ as_input_file(photo), nullptr, to_integers<int32>(sticker_file_ids), 0, 0,
+ as_caption(caption), op == "spttl" ? 10 : 0));
+ } else if (op == "spg" || op == "spgttl") {
+ ChatId chat_id;
string photo_path;
string conversion;
- std::tie(chat_id, args) = split(args);
- std::tie(photo_path, conversion) = split(args);
-
- send_message(chat_id,
- make_tl_object<td_api::inputMessagePhoto>(as_generated_file(photo_path, conversion), nullptr,
- vector<int32>(), 0, 0, as_caption(""), 0));
+ int64 expected_size;
+ get_args(args, chat_id, photo_path, conversion, expected_size);
+ send_message(chat_id, td_api::make_object<td_api::inputMessagePhoto>(
+ as_generated_file(photo_path, conversion, expected_size), nullptr, vector<int32>(), 0,
+ 0, as_caption(""), op == "spgttl" ? 10 : 0));
} else if (op == "spt") {
- string chat_id;
+ ChatId chat_id;
string photo_path;
string thumbnail_path;
- std::tie(chat_id, args) = split(args);
- std::tie(photo_path, thumbnail_path) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessagePhoto>(
- as_local_file(photo_path),
- make_tl_object<td_api::inputThumbnail>(as_local_file(thumbnail_path), 90, 89),
- vector<int32>(), 0, 0, as_caption(""), 0));
+ get_args(args, chat_id, photo_path, thumbnail_path);
+ send_message(chat_id, td_api::make_object<td_api::inputMessagePhoto>(as_input_file(photo_path),
+ as_input_thumbnail(thumbnail_path, 90, 89),
+ vector<int32>(), 0, 0, as_caption(""), 0));
} else if (op == "sptg") {
- string chat_id;
+ ChatId chat_id;
string photo_path;
string thumbnail_path;
string thumbnail_conversion;
- std::tie(chat_id, args) = split(args);
- std::tie(photo_path, args) = split(args);
- std::tie(thumbnail_path, thumbnail_conversion) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessagePhoto>(
- as_local_file(photo_path),
- make_tl_object<td_api::inputThumbnail>(
- as_generated_file(thumbnail_path, thumbnail_conversion), 90, 89),
- vector<int32>(), 0, 0, as_caption(""), 0));
+ get_args(args, chat_id, photo_path, thumbnail_path, thumbnail_conversion);
+ send_message(chat_id,
+ td_api::make_object<td_api::inputMessagePhoto>(
+ as_input_file(photo_path), as_input_thumbnail(thumbnail_path, thumbnail_conversion, 90, 89),
+ vector<int32>(), 0, 0, as_caption(""), 0));
} else if (op == "spgtg") {
- string chat_id;
+ ChatId chat_id;
string photo_path;
string conversion;
string thumbnail_path;
string thumbnail_conversion;
-
- std::tie(chat_id, args) = split(args);
- std::tie(photo_path, args) = split(args);
- std::tie(conversion, args) = split(args);
- std::tie(thumbnail_path, thumbnail_conversion) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessagePhoto>(
+ get_args(args, chat_id, photo_path, conversion, thumbnail_path, thumbnail_conversion);
+ send_message(chat_id, td_api::make_object<td_api::inputMessagePhoto>(
as_generated_file(photo_path, conversion),
- make_tl_object<td_api::inputThumbnail>(
- as_generated_file(thumbnail_path, thumbnail_conversion), 90, 89),
- vector<int32>(), 0, 0, as_caption(""), 0));
+ as_input_thumbnail(thumbnail_path, thumbnail_conversion, 90, 89), vector<int32>(), 0, 0,
+ as_caption(""), 0));
} else if (op == "spid") {
- string chat_id;
+ ChatId chat_id;
string file_id;
- std::tie(chat_id, file_id) = split(args);
- send_message(chat_id, make_tl_object<td_api::inputMessagePhoto>(as_input_file_id(file_id), nullptr,
- vector<int32>(), 0, 0, as_caption(""), 0));
+ get_args(args, chat_id, file_id);
+ send_message(chat_id, td_api::make_object<td_api::inputMessagePhoto>(as_input_file_id(file_id), nullptr,
+ vector<int32>(), 0, 0, as_caption(""), 0));
} else if (op == "ss") {
- string chat_id;
+ ChatId chat_id;
string sticker_path;
- std::tie(chat_id, sticker_path) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageSticker>(as_local_file(sticker_path), nullptr, 0, 0));
+ get_args(args, chat_id, sticker_path);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageSticker>(as_input_file(sticker_path), nullptr, 0, 0,
+ string()));
+ } else if (op == "sstt") {
+ ChatId chat_id;
+ string sticker_path;
+ string thumbnail_path;
+ get_args(args, chat_id, sticker_path, thumbnail_path);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageSticker>(
+ as_input_file(sticker_path), as_input_thumbnail(thumbnail_path), 0, 0, string()));
} else if (op == "ssid") {
- string chat_id;
+ ChatId chat_id;
string file_id;
- std::tie(chat_id, file_id) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageSticker>(as_input_file_id(file_id), nullptr, 0, 0));
- } else if (op == "sv") {
- string chat_id;
+ string emoji;
+ get_args(args, chat_id, file_id, emoji);
+ send_message(chat_id,
+ td_api::make_object<td_api::inputMessageSticker>(as_input_file_id(file_id), nullptr, 0, 0, emoji));
+ } else if (op == "sv" || op == "svttl") {
+ ChatId chat_id;
string video_path;
string sticker_file_ids_str;
vector<int32> sticker_file_ids;
- std::tie(chat_id, args) = split(args);
- std::tie(sticker_file_ids_str, video_path) = split(args);
+ get_args(args, chat_id, sticker_file_ids_str, video_path);
if (trim(video_path).empty()) {
video_path = sticker_file_ids_str;
} else {
- sticker_file_ids = to_integers<int32>(sticker_file_ids_str, ',');
+ sticker_file_ids = to_integers<int32>(sticker_file_ids_str);
}
-
- send_message(chat_id, make_tl_object<td_api::inputMessageVideo>(as_local_file(video_path), nullptr,
- std::move(sticker_file_ids), 1, 2, 3, true,
- as_caption(""), 0));
+ send_message(chat_id, td_api::make_object<td_api::inputMessageVideo>(as_input_file(video_path), nullptr,
+ std::move(sticker_file_ids), 1, 2, 3, true,
+ as_caption(""), op == "svttl" ? 10 : 0));
+ } else if (op == "svt" || op == "svtttl") {
+ ChatId chat_id;
+ string video;
+ string thumbnail;
+ get_args(args, chat_id, video, thumbnail);
+ send_message(chat_id, td_api::make_object<td_api::inputMessageVideo>(
+ as_input_file(video), as_input_thumbnail(thumbnail), vector<int32>(), 0, 0, 0, true,
+ as_caption(""), op == "svtttl" ? 10 : 0));
} else if (op == "svn") {
- string chat_id;
+ ChatId chat_id;
string video_path;
- std::tie(chat_id, video_path) = split(args);
- send_message(chat_id, make_tl_object<td_api::inputMessageVideoNote>(as_local_file(video_path), nullptr, 1, 5));
+ get_args(args, chat_id, video_path);
+ send_message(chat_id,
+ td_api::make_object<td_api::inputMessageVideoNote>(as_input_file(video_path), nullptr, 10, 5));
} else if (op == "svenue") {
- string chat_id;
-
+ ChatId chat_id;
string latitude;
string longitude;
+ string accuracy;
string title;
string address;
string provider;
string venue_id;
- std::tie(chat_id, args) = split(args);
- std::tie(latitude, args) = split(args);
- std::tie(longitude, args) = split(args);
- std::tie(title, args) = split(args);
- std::tie(address, args) = split(args);
- std::tie(provider, venue_id) = split(args);
-
- send_message(chat_id, make_tl_object<td_api::inputMessageVenue>(make_tl_object<td_api::venue>(
- as_location(latitude, longitude), title, address, provider, venue_id)));
- } else if (op == "test") {
- send_request(make_tl_object<td_api::testNetwork>());
+ string venue_type;
+ get_args(args, chat_id, latitude, longitude, accuracy, title, address, provider, venue_id, venue_type);
+ send_message(chat_id,
+ td_api::make_object<td_api::inputMessageVenue>(td_api::make_object<td_api::venue>(
+ as_location(latitude, longitude, accuracy), title, address, provider, venue_id, venue_type)));
+ } else {
+ op_not_found_count++;
+ }
+
+ if (op == "test") {
+ send_request(td_api::make_object<td_api::testNetwork>());
} else if (op == "alarm") {
- send_request(make_tl_object<td_api::setAlarm>(to_double(args)));
+ send_request(td_api::make_object<td_api::setAlarm>(to_double(args)));
} else if (op == "delete") {
- string chat_id;
- string remove_from_the_chat_list;
- std::tie(chat_id, remove_from_the_chat_list) = split(args);
- send_request(make_tl_object<td_api::deleteChatHistory>(as_chat_id(chat_id), as_bool(remove_from_the_chat_list)));
- } else if (op == "dmfu") {
- string chat_id;
- string user_id;
- std::tie(chat_id, user_id) = split(args);
- send_request(make_tl_object<td_api::deleteChatMessagesFromUser>(as_chat_id(chat_id), as_user_id(user_id)));
+ ChatId chat_id;
+ bool remove_from_the_chat_list;
+ bool revoke;
+ get_args(args, chat_id, remove_from_the_chat_list, revoke);
+ send_request(td_api::make_object<td_api::deleteChatHistory>(chat_id, remove_from_the_chat_list, revoke));
+ } else if (op == "dcmbd") {
+ ChatId chat_id;
+ int32 min_date;
+ int32 max_date;
+ bool revoke;
+ get_args(args, chat_id, min_date, max_date, revoke);
+ send_request(td_api::make_object<td_api::deleteChatMessagesByDate>(chat_id, min_date, max_date, revoke));
+ } else if (op == "dcmbs") {
+ ChatId chat_id;
+ string sender_id;
+ get_args(args, chat_id, sender_id);
+ send_request(td_api::make_object<td_api::deleteChatMessagesBySender>(chat_id, as_message_sender(sender_id)));
} else if (op == "cnbgc") {
string user_ids_string;
string title;
- std::tie(user_ids_string, title) = split(args);
-
- send_request(make_tl_object<td_api::createNewBasicGroupChat>(as_user_ids(user_ids_string, ','), title));
- } else if (op == "cnch") {
- send_request(make_tl_object<td_api::createNewSupergroupChat>(args, true, "Description"));
- } else if (op == "cnsg") {
- send_request(make_tl_object<td_api::createNewSupergroupChat>(args, false, "Description"));
+ get_args(args, user_ids_string, title);
+ send_request(td_api::make_object<td_api::createNewBasicGroupChat>(as_user_ids(user_ids_string), title));
+ } else if (op == "cnchc") {
+ send_request(td_api::make_object<td_api::createNewSupergroupChat>(args, true, "Description", nullptr, false));
+ } else if (op == "cnsgc") {
+ send_request(td_api::make_object<td_api::createNewSupergroupChat>(args, false, "Description", nullptr, false));
+ } else if (op == "cnsgcloc") {
+ send_request(td_api::make_object<td_api::createNewSupergroupChat>(
+ args, false, "Description",
+ td_api::make_object<td_api::chatLocation>(as_location("40.0", "60.0", ""), "address"), false));
+ } else if (op == "cnsgcimport") {
+ send_request(td_api::make_object<td_api::createNewSupergroupChat>(args, false, "Description", nullptr, true));
} else if (op == "UpgradeBasicGroupChatToSupergroupChat") {
- send_request(make_tl_object<td_api::upgradeBasicGroupChatToSupergroupChat>(as_chat_id(args)));
- } else if (op == "DeleteSupergroup") {
- send_request(make_tl_object<td_api::deleteSupergroup>(to_integer<int32>(args)));
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::upgradeBasicGroupChatToSupergroupChat>(chat_id));
+ } else if (op == "DeleteChat") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::deleteChat>(chat_id));
} else if (op == "gcpc") {
- send_request(make_tl_object<td_api::getCreatedPublicChats>());
+ send_request(td_api::make_object<td_api::getCreatedPublicChats>());
+ } else if (op == "gcpcl") {
+ send_request(td_api::make_object<td_api::getCreatedPublicChats>(
+ td_api::make_object<td_api::publicChatTypeIsLocationBased>()));
+ } else if (op == "ccpcl") {
+ send_request(td_api::make_object<td_api::checkCreatedPublicChatsLimit>());
+ } else if (op == "ccpcll") {
+ send_request(td_api::make_object<td_api::checkCreatedPublicChatsLimit>(
+ td_api::make_object<td_api::publicChatTypeIsLocationBased>()));
+ } else if (op == "gsdc") {
+ send_request(td_api::make_object<td_api::getSuitableDiscussionChats>());
+ } else if (op == "gisc") {
+ send_request(td_api::make_object<td_api::getInactiveSupergroupChats>());
} else if (op == "cpc") {
- string user_id;
- string force;
-
- std::tie(user_id, force) = split(args);
- send_request(make_tl_object<td_api::createPrivateChat>(as_user_id(user_id), as_bool(force)));
+ UserId user_id;
+ bool force;
+ get_args(args, user_id, force);
+ send_request(td_api::make_object<td_api::createPrivateChat>(user_id, force));
} else if (op == "cbgc") {
string basic_group_id;
- string force;
-
- std::tie(basic_group_id, force) = split(args);
- send_request(make_tl_object<td_api::createBasicGroupChat>(to_integer<int32>(basic_group_id), as_bool(force)));
+ bool force;
+ get_args(args, basic_group_id, force);
+ send_request(td_api::make_object<td_api::createBasicGroupChat>(as_basic_group_id(basic_group_id), force));
} else if (op == "csgc" || op == "cchc") {
string supergroup_id;
- string force;
-
- std::tie(supergroup_id, force) = split(args);
- send_request(make_tl_object<td_api::createSupergroupChat>(to_integer<int32>(supergroup_id), as_bool(force)));
+ bool force;
+ get_args(args, supergroup_id, force);
+ send_request(td_api::make_object<td_api::createSupergroupChat>(as_supergroup_id(supergroup_id), force));
+ } else if (op == "gcltac") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::getChatListsToAddChat>(chat_id));
+ } else if (op == "actl" || op == "actla" || begins_with(op, "actl-")) {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::addChatToList>(chat_id, as_chat_list(op)));
+ } else if (op == "gcf") {
+ send_request(td_api::make_object<td_api::getChatFilter>(as_chat_filter_id(args)));
+ } else if (op == "ccf") {
+ send_request(td_api::make_object<td_api::createChatFilter>(as_chat_filter(args)));
+ } else if (op == "ccfe") {
+ auto chat_filter = td_api::make_object<td_api::chatFilter>();
+ chat_filter->title_ = "empty";
+ chat_filter->included_chat_ids_ = as_chat_ids(args);
+ send_request(td_api::make_object<td_api::createChatFilter>(std::move(chat_filter)));
+ } else if (op == "ecf") {
+ string chat_filter_id;
+ string filter;
+ get_args(args, chat_filter_id, filter);
+ send_request(
+ td_api::make_object<td_api::editChatFilter>(as_chat_filter_id(chat_filter_id), as_chat_filter(filter)));
+ } else if (op == "dcf") {
+ send_request(td_api::make_object<td_api::deleteChatFilter>(as_chat_filter_id(args)));
+ } else if (op == "rcf") {
+ int32 main_chat_list_position;
+ string chat_filter_ids;
+ get_args(args, main_chat_list_position, chat_filter_ids);
+ send_request(td_api::make_object<td_api::reorderChatFilters>(as_chat_filter_ids(chat_filter_ids),
+ main_chat_list_position));
+ } else if (op == "grcf") {
+ send_request(td_api::make_object<td_api::getRecommendedChatFilters>());
+ } else if (op == "gcfdin") {
+ execute(td_api::make_object<td_api::getChatFilterDefaultIconName>(as_chat_filter(args)));
} else if (op == "sct") {
- string chat_id;
+ ChatId chat_id;
string title;
-
- std::tie(chat_id, title) = split(args);
- send_request(make_tl_object<td_api::setChatTitle>(as_chat_id(chat_id), title));
+ get_args(args, chat_id, title);
+ send_request(td_api::make_object<td_api::setChatTitle>(chat_id, title));
+ } else if (op == "scpe") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::setChatPhoto>(chat_id, nullptr));
+ } else if (op == "scpp") {
+ ChatId chat_id;
+ int64 photo_id;
+ get_args(args, chat_id, photo_id);
+ send_request(td_api::make_object<td_api::setChatPhoto>(
+ chat_id, td_api::make_object<td_api::inputChatPhotoPrevious>(photo_id)));
} else if (op == "scp") {
- string chat_id;
+ ChatId chat_id;
string photo_path;
-
- std::tie(chat_id, photo_path) = split(args);
- send_request(make_tl_object<td_api::setChatPhoto>(as_chat_id(chat_id), as_local_file(photo_path)));
- } else if (op == "scpid") {
- string chat_id;
- string file_id;
-
- std::tie(chat_id, file_id) = split(args);
- send_request(make_tl_object<td_api::setChatPhoto>(as_chat_id(chat_id), as_input_file_id(file_id)));
+ get_args(args, chat_id, photo_path);
+ send_request(td_api::make_object<td_api::setChatPhoto>(
+ chat_id, td_api::make_object<td_api::inputChatPhotoStatic>(as_input_file(photo_path))));
+ } else if (op == "scpa" || op == "scpv") {
+ ChatId chat_id;
+ string animation;
+ string main_frame_timestamp;
+ get_args(args, chat_id, animation, main_frame_timestamp);
+ send_request(td_api::make_object<td_api::setChatPhoto>(
+ chat_id, td_api::make_object<td_api::inputChatPhotoAnimation>(as_input_file(animation),
+ to_double(main_frame_timestamp))));
+ } else if (op == "scmt") {
+ ChatId chat_id;
+ int32 ttl;
+ get_args(args, chat_id, ttl);
+ send_request(td_api::make_object<td_api::setChatMessageTtl>(chat_id, ttl));
+ } else if (op == "scperm") {
+ ChatId chat_id;
+ string permissions;
+ get_args(args, chat_id, permissions);
+ if (permissions.size() == 9) {
+ auto &s = permissions;
+ send_request(td_api::make_object<td_api::setChatPermissions>(
+ chat_id, td_api::make_object<td_api::chatPermissions>(s[0] == '1', s[1] == '1', s[2] == '1', s[3] == '1',
+ s[4] == '1', s[5] == '1', s[6] == '1', s[7] == '1',
+ s[8] == '1')));
+ } else {
+ LOG(ERROR) << "Wrong permissions size, expected 8";
+ }
+ } else if (op == "sctn") {
+ ChatId chat_id;
+ string theme_name;
+ get_args(args, chat_id, theme_name);
+ send_request(td_api::make_object<td_api::setChatTheme>(chat_id, theme_name));
} else if (op == "sccd") {
- string chat_id;
+ ChatId chat_id;
string client_data;
-
- std::tie(chat_id, client_data) = split(args);
- send_request(make_tl_object<td_api::setChatClientData>(as_chat_id(chat_id), client_data));
+ get_args(args, chat_id, client_data);
+ send_request(td_api::make_object<td_api::setChatClientData>(chat_id, client_data));
} else if (op == "acm") {
- string chat_id;
- string user_id;
- string forward_limit;
-
- std::tie(chat_id, args) = split(args);
- std::tie(user_id, forward_limit) = split(args);
- send_request(make_tl_object<td_api::addChatMember>(as_chat_id(chat_id), as_user_id(user_id),
- to_integer<int32>(forward_limit)));
+ ChatId chat_id;
+ UserId user_id;
+ int32 forward_limit;
+ get_args(args, chat_id, user_id, forward_limit);
+ send_request(td_api::make_object<td_api::addChatMember>(chat_id, user_id, forward_limit));
} else if (op == "acms") {
- string chat_id;
+ ChatId chat_id;
string user_ids;
-
- std::tie(chat_id, user_ids) = split(args);
- send_request(make_tl_object<td_api::addChatMembers>(as_chat_id(chat_id), as_user_ids(user_ids, ',')));
+ get_args(args, chat_id, user_ids);
+ send_request(td_api::make_object<td_api::addChatMembers>(chat_id, as_user_ids(user_ids)));
+ } else if (op == "bcm") {
+ ChatId chat_id;
+ string member_id;
+ int32 banned_until_date;
+ bool revoke_messages;
+ get_args(args, chat_id, member_id, banned_until_date, revoke_messages);
+ send_request(td_api::make_object<td_api::banChatMember>(chat_id, as_message_sender(member_id), banned_until_date,
+ revoke_messages));
+ } else if (op == "spolla") {
+ ChatId chat_id;
+ MessageId message_id;
+ string option_ids;
+ get_args(args, chat_id, message_id, option_ids);
+ send_request(td_api::make_object<td_api::setPollAnswer>(chat_id, message_id, to_integers<int32>(option_ids)));
+ } else if (op == "gpollv") {
+ ChatId chat_id;
+ MessageId message_id;
+ int32 option_id;
+ int32 offset;
+ string limit;
+ get_args(args, chat_id, message_id, option_id, offset, limit);
+ send_request(td_api::make_object<td_api::getPollVoters>(chat_id, message_id, option_id, offset, as_limit(limit)));
+ } else if (op == "stoppoll") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::stopPoll>(chat_id, message_id, nullptr));
} else {
op_not_found_count++;
}
if (op == "scms") {
- string chat_id;
- string user_id;
+ ChatId chat_id;
+ string member_id;
string status_str;
- tl_object_ptr<td_api::ChatMemberStatus> status;
-
- std::tie(chat_id, args) = split(args);
- std::tie(user_id, status_str) = split(args);
+ td_api::object_ptr<td_api::ChatMemberStatus> status;
+ get_args(args, chat_id, member_id, status_str);
if (status_str == "member") {
- status = make_tl_object<td_api::chatMemberStatusMember>();
+ status = td_api::make_object<td_api::chatMemberStatusMember>();
} else if (status_str == "left") {
- status = make_tl_object<td_api::chatMemberStatusLeft>();
+ status = td_api::make_object<td_api::chatMemberStatusLeft>();
} else if (status_str == "banned") {
- status = make_tl_object<td_api::chatMemberStatusBanned>(std::numeric_limits<int32>::max());
+ status = td_api::make_object<td_api::chatMemberStatusBanned>(std::numeric_limits<int32>::max());
} else if (status_str == "creator") {
- status = make_tl_object<td_api::chatMemberStatusCreator>(true);
+ status = td_api::make_object<td_api::chatMemberStatusCreator>("", false, true);
+ } else if (status_str == "creatortitle") {
+ status = td_api::make_object<td_api::chatMemberStatusCreator>("owner", false, true);
+ } else if (status_str == "creatoranon") {
+ status = td_api::make_object<td_api::chatMemberStatusCreator>("", true, true);
} else if (status_str == "uncreator") {
- status = make_tl_object<td_api::chatMemberStatusCreator>(false);
+ status = td_api::make_object<td_api::chatMemberStatusCreator>("", false, false);
+ } else if (status_str == "anonadmin") {
+ status = td_api::make_object<td_api::chatMemberStatusAdministrator>(
+ "anon", true,
+ as_chat_administrator_rights(true, true, true, true, true, true, true, true, true, true, true, true));
+ } else if (status_str == "anon") {
+ status = td_api::make_object<td_api::chatMemberStatusAdministrator>(
+ "anon", false,
+ as_chat_administrator_rights(false, false, false, false, false, false, false, false, false, false, false,
+ true));
+ } else if (status_str == "addadmin") {
+ status = td_api::make_object<td_api::chatMemberStatusAdministrator>(
+ "anon", false,
+ as_chat_administrator_rights(false, false, false, false, false, false, false, false, false, true, false,
+ false));
+ } else if (status_str == "calladmin") {
+ status = td_api::make_object<td_api::chatMemberStatusAdministrator>(
+ "anon", false,
+ as_chat_administrator_rights(false, false, false, false, false, false, false, false, false, false, true,
+ false));
} else if (status_str == "admin") {
- status =
- make_tl_object<td_api::chatMemberStatusAdministrator>(true, true, true, true, true, true, true, true, true);
+ status = td_api::make_object<td_api::chatMemberStatusAdministrator>(
+ "", true,
+ as_chat_administrator_rights(false, true, true, true, true, true, true, true, true, true, true, false));
+ } else if (status_str == "adminq") {
+ status = td_api::make_object<td_api::chatMemberStatusAdministrator>(
+ "title", true,
+ as_chat_administrator_rights(false, true, true, true, true, true, true, true, true, true, true, false));
+ } else if (status_str == "minadmin") {
+ status = td_api::make_object<td_api::chatMemberStatusAdministrator>(
+ "", true,
+ as_chat_administrator_rights(true, false, false, false, false, false, false, false, false, false, false,
+ false));
} else if (status_str == "unadmin") {
- status = make_tl_object<td_api::chatMemberStatusAdministrator>(true, false, false, false, false, false, false,
- false, false);
+ status = td_api::make_object<td_api::chatMemberStatusAdministrator>("", true, nullptr);
} else if (status_str == "rest") {
- status = make_tl_object<td_api::chatMemberStatusRestricted>(true, static_cast<int32>(60 + std::time(nullptr)),
- false, false, false, false);
+ status = td_api::make_object<td_api::chatMemberStatusRestricted>(
+ true, static_cast<int32>(120 + std::time(nullptr)),
+ td_api::make_object<td_api::chatPermissions>(false, false, false, false, false, false, false, false,
+ false));
} else if (status_str == "restkick") {
- status = make_tl_object<td_api::chatMemberStatusRestricted>(false, static_cast<int32>(60 + std::time(nullptr)),
- true, false, false, false);
+ status = td_api::make_object<td_api::chatMemberStatusRestricted>(
+ false, static_cast<int32>(120 + std::time(nullptr)),
+ td_api::make_object<td_api::chatPermissions>(true, false, false, false, false, false, false, false, false));
+ } else if (status_str == "restunkick") {
+ status = td_api::make_object<td_api::chatMemberStatusRestricted>(
+ true, static_cast<int32>(120 + std::time(nullptr)),
+ td_api::make_object<td_api::chatPermissions>(true, false, false, false, false, false, false, false, false));
} else if (status_str == "unrest") {
- status = make_tl_object<td_api::chatMemberStatusRestricted>(true, 0, true, true, true, true);
+ status = td_api::make_object<td_api::chatMemberStatusRestricted>(
+ true, 0,
+ td_api::make_object<td_api::chatPermissions>(true, true, true, true, true, true, true, true, true));
}
if (status != nullptr) {
send_request(
- make_tl_object<td_api::setChatMemberStatus>(as_chat_id(chat_id), as_user_id(user_id), std::move(status)));
+ td_api::make_object<td_api::setChatMemberStatus>(chat_id, as_message_sender(member_id), std::move(status)));
} else {
LOG(ERROR) << "Unknown status \"" << status_str << "\"";
}
+ } else if (op == "cto") {
+ send_request(td_api::make_object<td_api::canTransferOwnership>());
+ } else if (op == "transferChatOwnership") {
+ ChatId chat_id;
+ UserId user_id;
+ string password;
+ get_args(args, chat_id, user_id, password);
+ send_request(td_api::make_object<td_api::transferChatOwnership>(chat_id, user_id, password));
} else if (op == "log") {
- string chat_id;
+ ChatId chat_id;
string limit;
-
- std::tie(chat_id, limit) = split(args);
- send_request(make_tl_object<td_api::getChatEventLog>(as_chat_id(chat_id), "", 0, to_integer<int32>(limit),
- nullptr, vector<int32>()));
+ string user_ids;
+ get_args(args, chat_id, limit, user_ids);
+ send_request(td_api::make_object<td_api::getChatEventLog>(chat_id, "", 0, as_limit(limit), nullptr,
+ as_user_ids(user_ids)));
+ } else if (op == "join") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::joinChat>(chat_id));
+ } else if (op == "leave") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::leaveChat>(chat_id));
} else if (op == "dcm") {
- string chat_id;
- string user_id;
-
- std::tie(chat_id, user_id) = split(args);
- send_request(make_tl_object<td_api::setChatMemberStatus>(as_chat_id(chat_id), as_user_id(user_id),
- make_tl_object<td_api::chatMemberStatusBanned>()));
+ ChatId chat_id;
+ string member_id;
+ get_args(args, chat_id, member_id);
+ td_api::object_ptr<td_api::ChatMemberStatus> status = td_api::make_object<td_api::chatMemberStatusBanned>();
+ if (as_user_id(member_id) == my_id_) {
+ status = td_api::make_object<td_api::chatMemberStatusLeft>();
+ }
+ send_request(
+ td_api::make_object<td_api::setChatMemberStatus>(chat_id, as_message_sender(member_id), std::move(status)));
} else if (op == "sn") {
string first_name;
string last_name;
- std::tie(first_name, last_name) = split(args);
- send_request(make_tl_object<td_api::setName>(first_name, last_name));
+ get_args(args, first_name, last_name);
+ send_request(td_api::make_object<td_api::setName>(first_name, last_name));
} else if (op == "sb") {
- send_request(make_tl_object<td_api::setBio>("\n" + args + "\n" + args + "\n"));
+ send_request(td_api::make_object<td_api::setBio>("\n" + args + "\n" + args + "\n"));
} else if (op == "sun") {
- send_request(make_tl_object<td_api::setUsername>(args));
- } else if (op == "tbga") {
- string group_id;
- string everyone_is_administrator;
-
- std::tie(group_id, everyone_is_administrator) = split(args);
- send_request(make_tl_object<td_api::toggleBasicGroupAdministrators>(to_integer<int32>(group_id),
- as_bool(everyone_is_administrator)));
+ send_request(td_api::make_object<td_api::setUsername>(args));
+ } else if (op == "tunia") {
+ string username;
+ bool is_active;
+ get_args(args, username, is_active);
+ send_request(td_api::make_object<td_api::toggleUsernameIsActive>(username, is_active));
+ } else if (op == "raun") {
+ send_request(td_api::make_object<td_api::reorderActiveUsernames>(autosplit_str(args)));
+ } else if (op == "sese") {
+ send_request(td_api::make_object<td_api::setEmojiStatus>(nullptr, 0));
+ } else if (op == "ses") {
+ int64 custom_emoji_id;
+ int32 until_date;
+ get_args(args, custom_emoji_id, until_date);
+ send_request(td_api::make_object<td_api::setEmojiStatus>(
+ td_api::make_object<td_api::emojiStatus>(custom_emoji_id), until_date));
+ } else if (op == "gtes") {
+ send_request(td_api::make_object<td_api::getThemedEmojiStatuses>());
+ } else if (op == "gdes") {
+ send_request(td_api::make_object<td_api::getDefaultEmojiStatuses>());
+ } else if (op == "gres") {
+ send_request(td_api::make_object<td_api::getRecentEmojiStatuses>());
+ } else if (op == "cres") {
+ send_request(td_api::make_object<td_api::clearRecentEmojiStatuses>());
} else if (op == "ccun") {
- string chat_id;
+ ChatId chat_id;
string username;
-
- std::tie(chat_id, username) = split(args);
- send_request(make_tl_object<td_api::checkChatUsername>(as_chat_id(chat_id), username));
+ get_args(args, chat_id, username);
+ send_request(td_api::make_object<td_api::checkChatUsername>(chat_id, username));
} else if (op == "ssgun" || op == "schun") {
string supergroup_id;
string username;
-
- std::tie(supergroup_id, username) = split(args);
- send_request(make_tl_object<td_api::setSupergroupUsername>(to_integer<int32>(supergroup_id), username));
- } else if (op == "ssgss") {
+ get_args(args, supergroup_id, username);
+ send_request(td_api::make_object<td_api::setSupergroupUsername>(as_supergroup_id(supergroup_id), username));
+ } else if (op == "tsgunia" || op == "tchunia") {
string supergroup_id;
- string sticker_set_id;
-
- std::tie(supergroup_id, sticker_set_id) = split(args);
- send_request(make_tl_object<td_api::setSupergroupStickerSet>(to_integer<int32>(supergroup_id),
- to_integer<int64>(sticker_set_id)));
- } else if (op == "tsgi") {
+ string username;
+ bool is_active;
+ get_args(args, supergroup_id, username, is_active);
+ send_request(td_api::make_object<td_api::toggleSupergroupUsernameIsActive>(as_supergroup_id(supergroup_id),
+ username, is_active));
+ } else if (op == "dasgun" || op == "dachun") {
string supergroup_id;
- string anyone_can_invite;
-
- std::tie(supergroup_id, anyone_can_invite) = split(args);
- send_request(make_tl_object<td_api::toggleSupergroupInvites>(to_integer<int32>(supergroup_id),
- as_bool(anyone_can_invite)));
+ get_args(args, supergroup_id);
+ send_request(td_api::make_object<td_api::disableAllSupergroupUsernames>(as_supergroup_id(supergroup_id)));
+ } else if (op == "rsgaun" || op == "rchaun") {
+ string supergroup_id;
+ get_args(args, supergroup_id, args);
+ send_request(td_api::make_object<td_api::reorderSupergroupActiveUsernames>(as_supergroup_id(supergroup_id),
+ autosplit_str(args)));
+ } else if (op == "ssgss") {
+ string supergroup_id;
+ int64 sticker_set_id;
+ get_args(args, supergroup_id, sticker_set_id);
+ send_request(
+ td_api::make_object<td_api::setSupergroupStickerSet>(as_supergroup_id(supergroup_id), sticker_set_id));
} else if (op == "tsgp") {
string supergroup_id;
- string is_all_history_available;
-
- std::tie(supergroup_id, is_all_history_available) = split(args);
- send_request(make_tl_object<td_api::toggleSupergroupIsAllHistoryAvailable>(to_integer<int32>(supergroup_id),
- as_bool(is_all_history_available)));
- } else if (op == "csgd" || op == "cchd") {
+ bool is_all_history_available;
+ get_args(args, supergroup_id, is_all_history_available);
+ send_request(td_api::make_object<td_api::toggleSupergroupIsAllHistoryAvailable>(as_supergroup_id(supergroup_id),
+ is_all_history_available));
+ } else if (op == "tsgif") {
string supergroup_id;
- string description;
-
- std::tie(supergroup_id, description) = split(args);
- send_request(make_tl_object<td_api::setSupergroupDescription>(to_integer<int32>(supergroup_id), description));
- } else if (op == "psgm" || op == "pchm") {
+ bool is_forum;
+ get_args(args, supergroup_id, is_forum);
+ send_request(td_api::make_object<td_api::toggleSupergroupIsForum>(as_supergroup_id(supergroup_id), is_forum));
+ } else if (op == "ToggleSupergroupIsBroadcastGroup") {
string supergroup_id;
- string message_id;
-
- std::tie(supergroup_id, message_id) = split(args);
- send_request(make_tl_object<td_api::pinSupergroupMessage>(to_integer<int32>(supergroup_id),
- as_message_id(message_id), false));
- } else if (op == "pchms") {
+ get_args(args, supergroup_id);
+ send_request(td_api::make_object<td_api::toggleSupergroupIsBroadcastGroup>(as_supergroup_id(supergroup_id)));
+ } else if (op == "tsgsm") {
string supergroup_id;
- string message_id;
-
- std::tie(supergroup_id, message_id) = split(args);
- send_request(make_tl_object<td_api::pinSupergroupMessage>(to_integer<int32>(supergroup_id),
- as_message_id(message_id), true));
- } else if (op == "upsgm" || op == "upchm") {
- send_request(make_tl_object<td_api::unpinSupergroupMessage>(to_integer<int32>(args)));
+ bool sign_messages;
+ get_args(args, supergroup_id, sign_messages);
+ send_request(
+ td_api::make_object<td_api::toggleSupergroupSignMessages>(as_supergroup_id(supergroup_id), sign_messages));
+ } else if (op == "tsgjtsm") {
+ string supergroup_id;
+ bool join_to_send_message;
+ get_args(args, supergroup_id, join_to_send_message);
+ send_request(td_api::make_object<td_api::toggleSupergroupJoinToSendMessages>(as_supergroup_id(supergroup_id),
+ join_to_send_message));
+ } else if (op == "tsgjbr") {
+ string supergroup_id;
+ bool join_by_request;
+ get_args(args, supergroup_id, join_by_request);
+ send_request(
+ td_api::make_object<td_api::toggleSupergroupJoinByRequest>(as_supergroup_id(supergroup_id), join_by_request));
+ } else if (op == "scar") {
+ ChatId chat_id;
+ string available_reactions;
+ get_args(args, chat_id, available_reactions);
+ td_api::object_ptr<td_api::ChatAvailableReactions> chat_available_reactions;
+ if (available_reactions == "all") {
+ chat_available_reactions = td_api::make_object<td_api::chatAvailableReactionsAll>();
+ } else if (!available_reactions.empty()) {
+ auto reactions = transform(autosplit_str(available_reactions), as_reaction_type);
+ chat_available_reactions = td_api::make_object<td_api::chatAvailableReactionsSome>(std::move(reactions));
+ }
+ send_request(
+ td_api::make_object<td_api::setChatAvailableReactions>(chat_id, std::move(chat_available_reactions)));
+ } else if (op == "scd") {
+ ChatId chat_id;
+ string description;
+ get_args(args, chat_id, description);
+ send_request(td_api::make_object<td_api::setChatDescription>(chat_id, description));
+ } else if (op == "scdg") {
+ ChatId chat_id;
+ ChatId group_chat_id;
+ get_args(args, chat_id, group_chat_id);
+ send_request(td_api::make_object<td_api::setChatDiscussionGroup>(chat_id, group_chat_id));
+ } else if (op == "scl") {
+ ChatId chat_id;
+ string latitude;
+ string longitude;
+ get_args(args, chat_id, latitude, longitude);
+ send_request(td_api::make_object<td_api::setChatLocation>(
+ chat_id, td_api::make_object<td_api::chatLocation>(as_location(latitude, longitude, string()), "address")));
+ } else if (op == "scsmd") {
+ ChatId chat_id;
+ int32 slow_mode_delay;
+ get_args(args, chat_id, slow_mode_delay);
+ send_request(td_api::make_object<td_api::setChatSlowModeDelay>(chat_id, slow_mode_delay));
+ } else if (op == "pcm" || op == "pcms" || op == "pcmo") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::pinChatMessage>(chat_id, message_id, op == "pcms", op == "pcmo"));
+ } else if (op == "upcm") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::unpinChatMessage>(chat_id, message_id));
+ } else if (op == "uacm") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::unpinAllChatMessages>(chat_id));
+ } else if (op == "uamtm") {
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
+ get_args(args, chat_id, message_thread_id);
+ send_request(td_api::make_object<td_api::unpinAllMessageThreadMessages>(chat_id, message_thread_id));
} else if (op == "grib") {
- send_request(make_tl_object<td_api::getRecentInlineBots>());
- } else if (op == "spc" || op == "su" || op == "sch") {
- send_request(make_tl_object<td_api::searchPublicChat>(args));
+ send_request(td_api::make_object<td_api::getRecentInlineBots>());
+ } else if (op == "spc" || op == "su") {
+ send_request(td_api::make_object<td_api::searchPublicChat>(args));
} else if (op == "spcs") {
- send_request(make_tl_object<td_api::searchPublicChats>(args));
+ send_request(td_api::make_object<td_api::searchPublicChats>(args));
} else if (op == "sc") {
- string limit;
- string query;
- std::tie(limit, query) = split(args);
- send_request(make_tl_object<td_api::searchChats>(query, to_integer<int32>(limit)));
+ SearchQuery query;
+ get_args(args, query);
+ send_request(td_api::make_object<td_api::searchChats>(query.query, query.limit));
} else if (op == "scos") {
- string limit;
- string query;
- std::tie(limit, query) = split(args);
- send_request(make_tl_object<td_api::searchChatsOnServer>(query, to_integer<int32>(limit)));
+ SearchQuery query;
+ get_args(args, query);
+ send_request(td_api::make_object<td_api::searchChatsOnServer>(query.query, query.limit));
+ } else if (op == "scn") {
+ string latitude;
+ string longitude;
+ get_args(args, latitude, longitude);
+ send_request(td_api::make_object<td_api::searchChatsNearby>(as_location(latitude, longitude, string())));
+ } else if (op == "sloc") {
+ string latitude;
+ string longitude;
+ get_args(args, latitude, longitude);
+ send_request(td_api::make_object<td_api::setLocation>(as_location(latitude, longitude, string())));
} else if (op == "sco") {
- string limit;
- string query;
- std::tie(limit, query) = split(args);
- send_request(make_tl_object<td_api::searchContacts>(query, to_integer<int32>(limit)));
+ SearchQuery query;
+ get_args(args, query);
+ send_request(td_api::make_object<td_api::searchContacts>(query.query, query.limit));
} else if (op == "arfc") {
- send_request(make_tl_object<td_api::addRecentlyFoundChat>(as_chat_id(args)));
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::addRecentlyFoundChat>(chat_id));
} else if (op == "rrfc") {
- send_request(make_tl_object<td_api::removeRecentlyFoundChat>(as_chat_id(args)));
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::removeRecentlyFoundChat>(chat_id));
} else if (op == "crfcs") {
- send_request(make_tl_object<td_api::clearRecentlyFoundChats>());
+ send_request(td_api::make_object<td_api::clearRecentlyFoundChats>());
+ } else if (op == "groc") {
+ send_request(td_api::make_object<td_api::getRecentlyOpenedChats>(as_limit(args)));
} else if (op == "gwpp") {
- send_request(make_tl_object<td_api::getWebPagePreview>(as_caption(args)));
+ send_request(td_api::make_object<td_api::getWebPagePreview>(as_caption(args)));
} else if (op == "gwpiv") {
string url;
- string force_full;
- std::tie(url, force_full) = split(args);
-
- send_request(make_tl_object<td_api::getWebPageInstantView>(url, as_bool(force_full)));
+ bool force_full;
+ get_args(args, url, force_full);
+ send_request(td_api::make_object<td_api::getWebPageInstantView>(url, force_full));
+ } else if (op == "sppp") {
+ int64 profile_photo_id;
+ get_args(args, profile_photo_id);
+ send_request(td_api::make_object<td_api::setProfilePhoto>(
+ td_api::make_object<td_api::inputChatPhotoPrevious>(profile_photo_id)));
} else if (op == "spp") {
- send_request(make_tl_object<td_api::setProfilePhoto>(as_local_file(args)));
- } else if (op == "sppg") {
- string path;
- string conversion;
- std::tie(path, conversion) = split(args);
- send_request(make_tl_object<td_api::setProfilePhoto>(as_generated_file(path, conversion)));
+ send_request(td_api::make_object<td_api::setProfilePhoto>(
+ td_api::make_object<td_api::inputChatPhotoStatic>(as_input_file(args))));
+ } else if (op == "sppa" || op == "sppv") {
+ string animation;
+ string main_frame_timestamp;
+ get_args(args, animation, main_frame_timestamp);
+ send_request(td_api::make_object<td_api::setProfilePhoto>(td_api::make_object<td_api::inputChatPhotoAnimation>(
+ as_input_file(animation), to_double(main_frame_timestamp))));
} else if (op == "sh") {
- auto prefix = std::move(args);
- send_request(make_tl_object<td_api::searchHashtags>(prefix, 10));
+ const string &prefix = args;
+ send_request(td_api::make_object<td_api::searchHashtags>(prefix, 10));
} else if (op == "rrh") {
- auto hashtag = std::move(args);
- send_request(make_tl_object<td_api::removeRecentHashtag>(hashtag));
- } else if (op == "view") {
- string chat_id;
+ const string &hashtag = args;
+ send_request(td_api::make_object<td_api::removeRecentHashtag>(hashtag));
+ } else if (op == "view" || op == "viewt") {
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
string message_ids;
- std::tie(chat_id, message_ids) = split(args);
-
- send_request(make_tl_object<td_api::viewMessages>(as_chat_id(chat_id), as_message_ids(message_ids), true));
+ get_args(args, chat_id, message_ids);
+ if (op == "viewt") {
+ get_args(message_ids, message_thread_id, message_ids);
+ }
+ send_request(
+ td_api::make_object<td_api::viewMessages>(chat_id, message_thread_id, as_message_ids(message_ids), true));
} else if (op == "omc") {
- string chat_id;
- string message_id;
- std::tie(chat_id, message_id) = split(args);
-
- send_request(make_tl_object<td_api::openMessageContent>(as_chat_id(chat_id), as_message_id(message_id)));
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::openMessageContent>(chat_id, message_id));
+ } else if (op == "caem") {
+ ChatId chat_id;
+ MessageId message_id;
+ get_args(args, chat_id, message_id);
+ send_request(td_api::make_object<td_api::clickAnimatedEmojiMessage>(chat_id, message_id));
+ } else if (op == "gilt") {
+ const string &link = args;
+ send_request(td_api::make_object<td_api::getInternalLinkType>(link));
+ } else if (op == "geli") {
+ const string &link = args;
+ send_request(td_api::make_object<td_api::getExternalLinkInfo>(link));
+ } else if (op == "gel" || op == "gelw") {
+ const string &link = args;
+ send_request(td_api::make_object<td_api::getExternalLink>(link, op == "gelw"));
} else if (op == "racm") {
- string chat_id = args;
- send_request(make_tl_object<td_api::readAllChatMentions>(as_chat_id(chat_id)));
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::readAllChatMentions>(chat_id));
+ } else if (op == "ramtm") {
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
+ get_args(args, chat_id, message_thread_id);
+ send_request(td_api::make_object<td_api::readAllMessageThreadMentions>(chat_id, message_thread_id));
+ } else if (op == "racr") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::readAllChatReactions>(chat_id));
+ } else if (op == "ramtr") {
+ ChatId chat_id;
+ MessageThreadId message_thread_id;
+ get_args(args, chat_id, message_thread_id);
+ send_request(td_api::make_object<td_api::readAllMessageThreadReactions>(chat_id, message_thread_id));
+ } else if (op == "tre") {
+ send_request(td_api::make_object<td_api::testReturnError>(
+ args.empty() ? nullptr : td_api::make_object<td_api::error>(-1, args)));
} else if (op == "dpp") {
- send_request(make_tl_object<td_api::deleteProfilePhoto>(to_integer<int64>(args)));
+ int64 profile_photo_id;
+ get_args(args, profile_photo_id);
+ send_request(td_api::make_object<td_api::deleteProfilePhoto>(profile_photo_id));
} else if (op == "gns") {
- send_request(make_tl_object<td_api::getNotificationSettings>(get_notification_settings_scope(args)));
- } else if (op == "sns") {
- string scope;
- string settings;
-
- std::tie(scope, settings) = split(args);
-
- string mute_for;
+ int64 notification_sound_id;
+ get_args(args, notification_sound_id);
+ send_request(td_api::make_object<td_api::getSavedNotificationSound>(notification_sound_id));
+ } else if (op == "gnss") {
+ send_request(td_api::make_object<td_api::getSavedNotificationSounds>());
+ } else if (op == "asns") {
string sound;
- string show_previews;
-
- std::tie(mute_for, settings) = split(settings, ',');
- std::tie(sound, show_previews) = split(settings, ',');
-
- send_request(make_tl_object<td_api::setNotificationSettings>(
- get_notification_settings_scope(scope),
- make_tl_object<td_api::notificationSettings>(to_integer<int32>(mute_for), sound, as_bool(show_previews))));
+ get_args(args, sound);
+ send_request(td_api::make_object<td_api::addSavedNotificationSound>(as_input_file(sound)));
+ } else if (op == "rns") {
+ int64 notification_sound_id;
+ get_args(args, notification_sound_id);
+ send_request(td_api::make_object<td_api::removeSavedNotificationSound>(notification_sound_id));
+ } else if (op == "gcnse" || op == "gcnses") {
+ send_request(td_api::make_object<td_api::getChatNotificationSettingsExceptions>(
+ as_notification_settings_scope(args), op == "gcnses"));
+ } else if (op == "gsns") {
+ send_request(td_api::make_object<td_api::getScopeNotificationSettings>(as_notification_settings_scope(args)));
+ } else if (op == "scns" || op == "ssns") {
+ string chat_id_or_scope;
+ string mute_for;
+ int64 sound_id;
+ string show_preview;
+ string disable_pinned_message_notifications;
+ string disable_mention_notifications;
+ get_args(args, chat_id_or_scope, mute_for, sound_id, show_preview, disable_pinned_message_notifications,
+ disable_mention_notifications);
+ if (op == "scns") {
+ send_request(td_api::make_object<td_api::setChatNotificationSettings>(
+ as_chat_id(chat_id_or_scope),
+ td_api::make_object<td_api::chatNotificationSettings>(
+ mute_for.empty(), to_integer<int32>(mute_for), sound_id == -1, sound_id, show_preview.empty(),
+ as_bool(show_preview), disable_pinned_message_notifications.empty(),
+ as_bool(disable_pinned_message_notifications), disable_mention_notifications.empty(),
+ as_bool(disable_mention_notifications))));
+ } else {
+ send_request(td_api::make_object<td_api::setScopeNotificationSettings>(
+ as_notification_settings_scope(chat_id_or_scope),
+ td_api::make_object<td_api::scopeNotificationSettings>(
+ to_integer<int32>(mute_for), sound_id, as_bool(show_preview),
+ as_bool(disable_pinned_message_notifications), as_bool(disable_mention_notifications))));
+ }
} else if (op == "rans") {
- send_request(make_tl_object<td_api::resetAllNotificationSettings>());
- } else if (op == "gcrss") {
- send_request(make_tl_object<td_api::getChatReportSpamState>(as_chat_id(args)));
- } else if (op == "ccrss") {
- string chat_id;
- string is_spam_chat;
- std::tie(chat_id, is_spam_chat) = split(args);
-
- send_request(make_tl_object<td_api::changeChatReportSpamState>(as_chat_id(chat_id), as_bool(is_spam_chat)));
+ send_request(td_api::make_object<td_api::resetAllNotificationSettings>());
+ } else if (op == "rn") {
+ int32 group_id;
+ string notification_ids;
+ get_args(args, group_id, notification_ids);
+ for (auto notification_id : to_integers<int32>(notification_ids)) {
+ send_request(td_api::make_object<td_api::removeNotification>(group_id, notification_id));
+ }
+ } else if (op == "rng") {
+ int32 group_id;
+ int32 max_notification_id;
+ get_args(args, group_id, max_notification_id);
+ send_request(td_api::make_object<td_api::removeNotificationGroup>(group_id, max_notification_id));
+ } else if (op == "rcab") {
+ ChatId chat_id;
+ get_args(args, chat_id);
+ send_request(td_api::make_object<td_api::removeChatActionBar>(chat_id));
} else if (op == "rc") {
- string chat_id;
- string reason_str;
+ ChatId chat_id;
string message_ids;
- std::tie(chat_id, args) = split(args);
- std::tie(reason_str, message_ids) = split(args);
-
- tl_object_ptr<td_api::ChatReportReason> reason;
- if (reason_str == "spam") {
- reason = make_tl_object<td_api::chatReportReasonSpam>();
- } else if (reason_str == "violence") {
- reason = make_tl_object<td_api::chatReportReasonViolence>();
- } else if (reason_str == "porno") {
- reason = make_tl_object<td_api::chatReportReasonPornography>();
+ string reason;
+ string text;
+ get_args(args, chat_id, message_ids, reason, text);
+ send_request(td_api::make_object<td_api::reportChat>(chat_id, as_message_ids(message_ids),
+ as_chat_report_reason(reason), text));
+ } else if (op == "rcp") {
+ ChatId chat_id;
+ FileId file_id;
+ string reason;
+ string text;
+ get_args(args, chat_id, file_id, reason, text);
+ send_request(td_api::make_object<td_api::reportChatPhoto>(chat_id, file_id, as_chat_report_reason(reason), text));
+ } else if (op == "reportmr") {
+ ChatId chat_id;
+ MessageId message_id;
+ string sender_id;
+ get_args(args, chat_id, message_id, sender_id);
+ send_request(
+ td_api::make_object<td_api::reportMessageReactions>(chat_id, message_id, as_message_sender(sender_id)));
+ } else if (op == "gcst") {
+ ChatId chat_id;
+ bool is_dark;
+ get_args(args, chat_id, is_dark);
+ send_request(td_api::make_object<td_api::getChatStatistics>(chat_id, is_dark));
+ } else {
+ op_not_found_count++;
+ }
+
+ if (op == "sgs") {
+ ChatId chat_id;
+ MessageId message_id;
+ UserId user_id;
+ int32 score;
+ get_args(args, chat_id, message_id, user_id, score);
+ send_request(td_api::make_object<td_api::setGameScore>(chat_id, message_id, true, user_id, score, true));
+ } else if (op == "gghs") {
+ ChatId chat_id;
+ MessageId message_id;
+ UserId user_id;
+ get_args(args, chat_id, message_id, user_id);
+ send_request(td_api::make_object<td_api::getGameHighScores>(chat_id, message_id, user_id));
+ } else if (op == "gmst") {
+ ChatId chat_id;
+ MessageId message_id;
+ bool is_dark;
+ get_args(args, chat_id, message_id, is_dark);
+ send_request(td_api::make_object<td_api::getMessageStatistics>(chat_id, message_id, is_dark));
+ } else if (op == "gstg") {
+ ChatId chat_id;
+ string token;
+ int64 x;
+ get_args(args, chat_id, token, x);
+ send_request(td_api::make_object<td_api::getStatisticalGraph>(chat_id, token, x));
+ } else if (op == "hsa") {
+ send_request(td_api::make_object<td_api::hideSuggestedAction>(as_suggested_action(args)));
+ } else if (op == "glui" || op == "glu" || op == "glua") {
+ ChatId chat_id;
+ MessageId message_id;
+ string button_id;
+ get_args(args, chat_id, message_id, button_id);
+ if (op == "glui") {
+ send_request(td_api::make_object<td_api::getLoginUrlInfo>(chat_id, message_id, as_button_id(button_id)));
} else {
- reason = make_tl_object<td_api::chatReportReasonCustom>(reason_str);
+ send_request(
+ td_api::make_object<td_api::getLoginUrl>(chat_id, message_id, as_button_id(button_id), op == "glua"));
}
-
- send_request(
- make_tl_object<td_api::reportChat>(as_chat_id(chat_id), std::move(reason), as_message_ids(message_ids)));
- } else if (op == "rsgs" || op == "rchs") {
+ } else if (op == "rsgs") {
string supergroup_id;
- string user_id;
string message_ids;
- std::tie(supergroup_id, args) = split(args);
- std::tie(user_id, message_ids) = split(args);
-
- send_request(make_tl_object<td_api::reportSupergroupSpam>(to_integer<int32>(supergroup_id), as_user_id(user_id),
- as_message_ids(message_ids)));
+ get_args(args, supergroup_id, message_ids);
+ send_request(td_api::make_object<td_api::reportSupergroupSpam>(as_supergroup_id(supergroup_id),
+ as_message_ids(message_ids)));
} else if (op == "gdiff") {
- send_request(make_tl_object<td_api::testGetDifference>());
- } else if (op == "cproxy") {
- send_request(make_tl_object<td_api::setProxy>(make_tl_object<td_api::proxyEmpty>()));
- } else if (op == "sproxy") {
+ send_request(td_api::make_object<td_api::testGetDifference>());
+ } else if (op == "dproxy") {
+ send_request(td_api::make_object<td_api::disableProxy>());
+ } else if (op == "eproxy") {
+ send_request(td_api::make_object<td_api::enableProxy>(as_proxy_id(args)));
+ } else if (op == "rproxy") {
+ send_request(td_api::make_object<td_api::removeProxy>(as_proxy_id(args)));
+ } else if (op == "aproxy" || op == "aeproxy" || op == "aeproxytcp" || op == "editproxy" || op == "editeproxy" ||
+ op == "editeproxytcp" || op == "tproxy") {
+ string proxy_id;
string server;
- string port;
+ int32 port;
string user;
string password;
- std::tie(server, args) = split(args);
- std::tie(port, args) = split(args);
- std::tie(user, password) = split(args);
-
- send_request(make_tl_object<td_api::setProxy>(
- make_tl_object<td_api::proxySocks5>(server, to_integer<int32>(port), user, password)));
- } else if (op == "gproxy") {
- send_request(make_tl_object<td_api::getProxy>());
- } else if (op == "SetVerbosity") {
- td::Log::set_verbosity_level(to_integer<int>(args));
+ if (op[0] == 'e') {
+ get_args(args, proxy_id, args);
+ }
+ get_args(args, server, port, user, password);
+ bool enable = op != "aproxy" && op != "editproxy";
+ td_api::object_ptr<td_api::ProxyType> type;
+ if (!user.empty() && password.empty()) {
+ type = td_api::make_object<td_api::proxyTypeMtproto>(user);
+ } else {
+ if (port == 80 || port == 8080) {
+ type = td_api::make_object<td_api::proxyTypeHttp>(user, password, op.back() != 'p');
+ } else {
+ type = td_api::make_object<td_api::proxyTypeSocks5>(user, password);
+ }
+ }
+ if (op[0] == 'e') {
+ send_request(
+ td_api::make_object<td_api::editProxy>(as_proxy_id(proxy_id), server, port, enable, std::move(type)));
+ } else if (op == "tproxy") {
+ send_request(td_api::make_object<td_api::testProxy>(server, port, std::move(type), 2, 10.0));
+ } else {
+ send_request(td_api::make_object<td_api::addProxy>(server, port, enable, std::move(type)));
+ }
+ } else if (op == "gproxy" || op == "gproxies") {
+ send_request(td_api::make_object<td_api::getProxies>());
+ } else if (op == "gproxyl" || op == "gpl") {
+ send_request(td_api::make_object<td_api::getProxyLink>(as_proxy_id(args)));
+ } else if (op == "pproxy") {
+ send_request(td_api::make_object<td_api::pingProxy>(as_proxy_id(args)));
+ } else if (op == "gusi") {
+ UserId user_id;
+ get_args(args, user_id);
+ send_request(td_api::make_object<td_api::getUserSupportInfo>(user_id));
+ } else if (op == "susi") {
+ UserId user_id;
+ string text;
+ get_args(args, user_id, text);
+ send_request(td_api::make_object<td_api::setUserSupportInfo>(user_id, as_formatted_text(text)));
+ } else if (op == "touch") {
+ auto r_fd = FileFd::open(args, FileFd::Read | FileFd::Write);
+ if (r_fd.is_error()) {
+ LOG(ERROR) << r_fd.error();
+ return;
+ }
+
+ auto fd = r_fd.move_as_ok();
+ auto size = fd.get_size().move_as_ok();
+ fd.seek(size).ignore();
+ fd.write("a").ignore();
+ fd.seek(size).ignore();
+ fd.truncate_to_current_position(size).ignore();
+ } else if (op == "mem") {
+ auto r_mem_stats = mem_stat();
+ if (r_mem_stats.is_error()) {
+ LOG(ERROR) << r_mem_stats.error();
+ } else {
+ auto stats = r_mem_stats.move_as_ok();
+ LOG(ERROR) << "RSS = " << stats.resident_size_ << ", peak RSS = " << stats.resident_size_peak_ << ", VSZ "
+ << stats.virtual_size_ << ", peak VSZ = " << stats.virtual_size_peak_;
+ }
+ } else if (op == "cpu") {
+ auto inc_count = to_integer<uint32>(args);
+ while (inc_count-- > 0) {
+ cpu_counter_++;
+ }
+ auto r_cpu_stats = cpu_stat();
+ if (r_cpu_stats.is_error()) {
+ LOG(ERROR) << r_cpu_stats.error();
+ } else {
+ auto stats = r_cpu_stats.move_as_ok();
+ LOG(ERROR) << cpu_counter_.load() << ", total ticks = " << stats.total_ticks_
+ << ", user ticks = " << stats.process_user_ticks_
+ << ", system ticks = " << stats.process_system_ticks_;
+ }
+ } else if (op[0] == 'v' && (op[1] == 'v' || is_digit(op[1]))) {
+ int new_verbosity_level = op[1] == 'v' ? static_cast<int>(op.size()) : to_integer<int>(op.substr(1));
+ SET_VERBOSITY_LEVEL(td::max(new_verbosity_level, VERBOSITY_NAME(DEBUG)));
+ combined_log.set_first_verbosity_level(new_verbosity_level);
+ } else if (op == "slse") {
+ execute(td_api::make_object<td_api::setLogStream>(td_api::make_object<td_api::logStreamEmpty>()));
+ } else if (op == "slsd") {
+ execute(td_api::make_object<td_api::setLogStream>(td_api::make_object<td_api::logStreamDefault>()));
+ } else if (op == "gls") {
+ execute(td_api::make_object<td_api::getLogStream>());
+ } else if (op == "slvl") {
+ int32 new_verbosity_level;
+ get_args(args, new_verbosity_level);
+ execute(td_api::make_object<td_api::setLogVerbosityLevel>(new_verbosity_level));
+ } else if (op == "glvl") {
+ execute(td_api::make_object<td_api::getLogVerbosityLevel>());
+ } else if (op == "gtags" || op == "glt") {
+ execute(td_api::make_object<td_api::getLogTags>());
+ } else if (op == "sltvl" || op == "sltvle" || op == "tag") {
+ string tag;
+ int32 level;
+ get_args(args, tag, level);
+ auto request = td_api::make_object<td_api::setLogTagVerbosityLevel>(tag, level);
+ if (op == "sltvl") {
+ send_request(std::move(request));
+ } else {
+ execute(std::move(request));
+ }
+ } else if (op == "gltvl" || op == "gltvle" || op == "gtag") {
+ const string &tag = args;
+ auto request = td_api::make_object<td_api::getLogTagVerbosityLevel>(tag);
+ if (op == "gltvl") {
+ send_request(std::move(request));
+ } else {
+ execute(std::move(request));
+ }
+ } else if (op == "alog" || op == "aloge") {
+ int32 level;
+ string text;
+ get_args(args, level, text);
+ auto request = td_api::make_object<td_api::addLogMessage>(level, text);
+ if (op == "alog") {
+ send_request(std::move(request));
+ } else {
+ execute(std::move(request));
+ }
} else if (op == "q" || op == "Quit") {
quit();
- } else if (op == "dnq" || op == "DumpNetQueries") {
- dump_pending_network_queries();
+ } else if (op == "dnq") {
+ dump_pending_network_queries(*net_query_stats_);
} else if (op == "fatal") {
LOG(FATAL) << "Fatal!";
} else if (op == "unreachable") {
@@ -2839,21 +5112,18 @@ class CliClient final : public Actor {
}
}
- bool inited_ = false;
- void loop() override {
- if (!inited_) {
- inited_ = true;
+ bool is_inited_ = false;
+ void loop() final {
+ if (!is_inited_) {
+ is_inited_ = true;
init();
}
-#if !TD_WINDOWS
+ stdin_.flush_read().ensure();
#ifdef USE_READLINE
- if (can_read(stdin_)) {
+ while (!stdin_.input_buffer().empty()) {
rl_callback_read_char();
- stdin_.get_fd().clear_flags(Fd::Read);
}
#else
- auto r = stdin_.flush_read();
- CHECK(r.is_ok());
while (true) {
auto cmd = process_stdin(&stdin_.input_buffer());
if (cmd.is_error()) {
@@ -2868,51 +5138,63 @@ class CliClient final : public Actor {
cmd_queue_.pop();
on_cmd(std::move(cmd));
}
-#endif
if (ready_to_stop_ && close_flag_ && is_stdin_reader_stopped_) {
#ifdef USE_READLINE
rl_callback_handler_remove();
#endif
Scheduler::instance()->finish();
- LOG(WARNING) << "STOP";
stop();
}
}
- void timeout_expired() override {
+ void timeout_expired() final {
if (close_flag_) {
return;
}
- for (auto it = pending_file_generations.begin(); it != pending_file_generations.end();) {
+ for (auto it = pending_file_generations_.begin(); it != pending_file_generations_.end();) {
auto left_size = it->size - it->local_size;
CHECK(left_size > 0);
if (it->part_size > left_size) {
it->part_size = left_size;
}
- BufferSlice block(it->part_size);
+ BufferSlice block(narrow_cast<size_t>(it->part_size));
FileFd::open(it->source, FileFd::Flags::Read).move_as_ok().pread(block.as_slice(), it->local_size).ensure();
- auto open_flags = FileFd::Flags::Write | (it->local_size ? 1 : FileFd::Flags::Truncate | FileFd::Flags::Create);
- FileFd::open(it->destination, open_flags).move_as_ok().pwrite(block.as_slice(), it->local_size).ensure();
+ if (rand_bool()) {
+ auto open_flags = FileFd::Flags::Write | (it->local_size ? 0 : FileFd::Flags::Truncate | FileFd::Flags::Create);
+ FileFd::open(it->destination, open_flags).move_as_ok().pwrite(block.as_slice(), it->local_size).ensure();
+ } else {
+ send_request(
+ td_api::make_object<td_api::writeGeneratedFilePart>(it->id, it->local_size, block.as_slice().str()));
+ }
it->local_size += it->part_size;
if (it->local_size == it->size) {
- send_request(make_tl_object<td_api::setFileGenerationProgress>(it->id, it->size, it->size));
- send_request(make_tl_object<td_api::finishFileGeneration>(it->id, nullptr));
- it = pending_file_generations.erase(it);
+ send_request(td_api::make_object<td_api::setFileGenerationProgress>(it->id, it->size, it->size));
+ send_request(td_api::make_object<td_api::finishFileGeneration>(it->id, nullptr));
+ it = pending_file_generations_.erase(it);
} else {
- send_request(
- make_tl_object<td_api::setFileGenerationProgress>(it->id, (it->size + it->local_size) / 2, it->local_size));
+ auto local_size = it->local_size;
+ if (it->test_local_size_decrease && local_size > it->size / 2) {
+ local_size = local_size * 2 - it->size;
+ }
+ send_request(td_api::make_object<td_api::setFileGenerationProgress>(it->id, (it->size + 3 * it->local_size) / 4,
+ local_size));
++it;
}
}
- if (!pending_file_generations.empty()) {
+ if (!pending_file_generations_.empty()) {
set_timeout_in(0.01);
}
}
- void hangup_shared() override {
+ void notify() final {
+ auto guard = scheduler_->get_send_guard();
+ send_event_later(actor_id(), Event::yield());
+ }
+
+ void hangup_shared() final {
CHECK(get_link_token() == 1);
LOG(INFO) << "StdinReader stopped";
is_stdin_reader_stopped_ = true;
@@ -2922,11 +5204,29 @@ class CliClient final : public Actor {
void add_cmd(string cmd) {
cmd_queue_.push(std::move(cmd));
}
+ int stdin_getc() {
+ auto slice = stdin_.input_buffer().prepare_read();
+ if (slice.empty()) {
+ return EOF;
+ }
+ int res = static_cast<unsigned char>(slice[0]);
+ stdin_.input_buffer().confirm_read(1);
+ return res;
+ }
+
+ FlatHashMap<int32, double> being_downloaded_files_;
+
+ int64 my_id_ = 0;
+ td_api::object_ptr<td_api::AuthorizationState> authorization_state_;
+ string schedule_date_;
+ MessageThreadId message_thread_id_;
+ int64 opened_chat_id_ = 0;
- int32 my_id_ = 0;
+ ConcurrentScheduler *scheduler_{nullptr};
bool use_test_dc_ = false;
- ActorOwn<ClientActor> td_;
+ std::shared_ptr<NetQueryStats> net_query_stats_ = create_net_query_stats();
+ ActorOwn<ClientActor> td_client_;
std::queue<string> cmd_queue_;
bool close_flag_ = false;
bool ready_to_stop_ = false;
@@ -2937,11 +5237,12 @@ class CliClient final : public Actor {
int api_id_ = 0;
std::string api_hash_;
-#if TD_WINDOWS
- ActorOwn<> stdin_reader_;
-#endif
+ int32 group_call_source_ = Random::fast(1, 1000000000);
+
+ static std::atomic<uint64> cpu_counter_;
};
CliClient *CliClient::instance_ = nullptr;
+std::atomic<uint64> CliClient::cpu_counter_;
void quit() {
CliClient::quit_instance();
@@ -2954,33 +5255,39 @@ static void fail_signal(int sig) {
}
}
-static void usage() {
- //TODO:
-}
-
-static void on_fatal_error(const char *error) {
- std::cerr << "Fatal error: " << error << std::endl;
+static void on_log_message(int verbosity_level, const char *message) {
+ if (verbosity_level == 0) {
+ std::cerr << "Fatal error: " << message;
+ }
+ // std::cerr << "Log message: " << message;
}
void main(int argc, char **argv) {
+ ExitGuard exit_guard;
+ detail::ThreadIdGuard thread_id_guard;
ignore_signal(SignalType::HangUp).ensure();
ignore_signal(SignalType::Pipe).ensure();
set_signal_handler(SignalType::Error, fail_signal).ensure();
set_signal_handler(SignalType::Abort, fail_signal).ensure();
- td::Log::set_fatal_error_callback(on_fatal_error);
+ ClientManager::set_log_message_callback(0, on_log_message);
+ init_openssl_threads();
- const char *locale_name = (std::setlocale(LC_ALL, "fr-FR") == nullptr ? "" : "fr-FR");
- std::locale new_locale(locale_name);
+ std::locale new_locale("C");
std::locale::global(new_locale);
SCOPE_EXIT {
std::locale::global(std::locale::classic());
+ static NullLog null_log;
+ log_interface = &null_log;
};
CliLog cli_log;
- log_interface = &cli_log;
- td::FileLog file_log;
- td::TsLog ts_log(&file_log);
+ FileLog file_log;
+ TsLog ts_log(&file_log);
+
+ combined_log.set_first(&cli_log);
+
+ log_interface = &combined_log;
int new_verbosity_level = VERBOSITY_NAME(INFO);
bool use_test_dc = false;
@@ -2988,7 +5295,7 @@ void main(int argc, char **argv) {
bool disable_network = false;
auto api_id = [](auto x) -> int32 {
if (x) {
- return td::to_integer<int32>(Slice(x));
+ return to_integer<int32>(Slice(x));
}
return 0;
}(std::getenv("TD_API_ID"));
@@ -2996,75 +5303,101 @@ void main(int argc, char **argv) {
if (x) {
return x;
}
- return "";
+ return std::string();
}(std::getenv("TD_API_HASH"));
- // TODO port OptionsParser to Windows
- for (int i = 1; i < argc; i++) {
- if (!std::strcmp(argv[i], "--test")) {
- use_test_dc = true;
- } else if (!std::strncmp(argv[i], "-v", 2)) {
- const char *arg = argv[i] + 2;
- if (*arg == '\0' && i + 1 < argc) {
- arg = argv[++i];
- }
- int new_verbosity = 0;
- if (arg[0] == 'v') {
- new_verbosity = 1;
- while (arg[0] == 'v') {
- new_verbosity++;
- arg++;
- }
- }
- new_verbosity += to_integer<int>(Slice(arg));
- new_verbosity_level = VERBOSITY_NAME(FATAL) + new_verbosity;
- } else if (!std::strncmp(argv[i], "-l", 2)) {
- const char *arg = argv[i] + 2;
- if (*arg == '\0' && i + 1 < argc) {
- arg = argv[++i];
- }
- if (file_log.init(arg) && file_log.init(arg) && file_log.init(arg, 1000 << 20)) {
- log_interface = &ts_log;
- }
- } else if (!std::strcmp(argv[i], "-W")) {
- get_chat_list = true;
- } else if (!std::strcmp(argv[i], "--disable-network") || !std::strcmp(argv[i], "-n")) {
- disable_network = true;
- } else if (!std::strcmp(argv[i], "--api_id")) {
- if (i + 1 >= argc) {
- return usage();
- }
- api_id = td::to_integer<int32>(Slice(argv[++i]));
- } else if (!std::strcmp(argv[i], "--api_hash")) {
- if (i + 1 >= argc) {
- return usage();
- }
- api_hash = argv[++i];
+
+ OptionParser options;
+ options.set_description("TDLib test client");
+ options.add_option('\0', "test", "Use test DC", [&] { use_test_dc = true; });
+ options.add_option('v', "verbosity", "Set verbosity level", [&](Slice level) {
+ int new_verbosity = 1;
+ while (begins_with(level, "v")) {
+ new_verbosity++;
+ level.remove_prefix(1);
+ }
+ if (!level.empty()) {
+ new_verbosity += to_integer<int>(level) - (new_verbosity == 1);
+ }
+ new_verbosity_level = VERBOSITY_NAME(FATAL) + new_verbosity;
+ });
+ options.add_option('l', "log", "Log to file", [&](Slice file_name) {
+ if (file_log.init(file_name.str()).is_ok() && file_log.init(file_name.str()).is_ok() &&
+ file_log.init(file_name.str(), 1000 << 20).is_ok()) {
+ combined_log.set_first(&ts_log);
}
+ });
+ options.add_option('W', "", "Preload chat list", [&] { get_chat_list = true; });
+ options.add_option('n', "disable-network", "Disable network", [&] { disable_network = true; });
+ options.add_checked_option('\0', "api-id", "Set Telegram API ID", OptionParser::parse_integer(api_id));
+ options.add_checked_option('\0', "api_id", "Set Telegram API ID", OptionParser::parse_integer(api_id));
+ options.add_option('\0', "api-hash", "Set Telegram API hash", OptionParser::parse_string(api_hash));
+ options.add_option('\0', "api_hash", "Set Telegram API hash", OptionParser::parse_string(api_hash));
+ options.add_check([&] {
+ if (api_id == 0 || api_hash.empty()) {
+ return Status::Error("You must provide valid api-id and api-hash obtained at https://my.telegram.org");
+ }
+ return Status::OK();
+ });
+ auto r_non_options = options.run(argc, argv, 0);
+ if (r_non_options.is_error()) {
+ LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();
+ LOG(PLAIN) << options;
+ return;
}
- if (api_id == 0 || api_hash == "") {
- LOG(ERROR) << "You should provide some valid api_id and api_hash";
- return usage();
- }
+ SET_VERBOSITY_LEVEL(td::max(new_verbosity_level, VERBOSITY_NAME(DEBUG)));
+ combined_log.set_first_verbosity_level(new_verbosity_level);
- SET_VERBOSITY_LEVEL(new_verbosity_level);
+ if (combined_log.get_first() == &cli_log) {
+ file_log.init("tg_cli.log", 1000 << 20, false).ensure();
+ file_log.lazy_rotate();
+ combined_log.set_second(&ts_log);
+ combined_log.set_second_verbosity_level(VERBOSITY_NAME(DEBUG));
+ }
{
- ConcurrentScheduler scheduler;
- scheduler.init(4);
+ ConcurrentScheduler scheduler(3, 0);
+ class CreateClient final : public Actor {
+ public:
+ CreateClient(ConcurrentScheduler *scheduler, bool use_test_dc, bool get_chat_list, bool disable_network,
+ int32 api_id, std::string api_hash)
+ : scheduler_(scheduler)
+ , use_test_dc_(use_test_dc)
+ , get_chat_list_(get_chat_list)
+ , disable_network_(disable_network)
+ , api_id_(api_id)
+ , api_hash_(std::move(api_hash)) {
+ }
+
+ private:
+ void start_up() final {
+ create_actor<CliClient>("CliClient", scheduler_, use_test_dc_, get_chat_list_, disable_network_, api_id_,
+ api_hash_)
+ .release();
+ }
+
+ ConcurrentScheduler *scheduler_;
+ bool use_test_dc_;
+ bool get_chat_list_;
+ bool disable_network_;
+ int32 api_id_;
+ std::string api_hash_;
+ };
scheduler
- .create_actor_unsafe<CliClient>(0, "CliClient", use_test_dc, get_chat_list, disable_network, api_id, api_hash)
+ .create_actor_unsafe<CreateClient>(0, "CreateClient", &scheduler, use_test_dc, get_chat_list, disable_network,
+ api_id, api_hash)
.release();
scheduler.start();
- while (scheduler.run_main(100)) {
+ while (scheduler.run_main(Timestamp::in(100))) {
}
scheduler.finish();
}
dump_memory_usage();
}
+
} // namespace td
int main(int argc, char **argv) {
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileBitmask.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileBitmask.cpp
new file mode 100644
index 0000000000..48f58a14f2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileBitmask.cpp
@@ -0,0 +1,172 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileBitmask.h"
+
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+#include "td/utils/ScopeGuard.h"
+
+namespace td {
+
+Bitmask::Bitmask(Decode, Slice data) : data_(zero_one_decode(data)) {
+}
+
+Bitmask::Bitmask(Ones, int64 count) : data_(narrow_cast<size_t>((count + 7) / 8), '\0') {
+ for (int64 i = 0; i < count; i++) {
+ set(i);
+ }
+}
+
+Bitmask Bitmask::compress(int k) const {
+ Bitmask res;
+ for (int64 i = 0; i * k < size(); i++) {
+ bool f = true;
+ for (int64 j = 0; j < k && f; j++) {
+ f &= get(i * k + j);
+ }
+ if (f) {
+ res.set(i);
+ }
+ }
+ return res;
+}
+
+std::string Bitmask::encode(int32 prefix_count) {
+ // remove zeroes in the end to make encoding deterministic
+ Slice data(data_);
+
+ int save_i = -1;
+ char save_c;
+ if (prefix_count != -1) {
+ auto truncated_size = (prefix_count + 7) / 8;
+ data.truncate(truncated_size);
+ if (prefix_count % 8 != 0) {
+ save_i = truncated_size - 1;
+ save_c = data_[save_i];
+ auto mask = 0xff >> (8 - prefix_count % 8);
+ data_[save_i] = static_cast<char>(data_[save_i] & mask);
+ }
+ }
+ SCOPE_EXIT {
+ if (save_i != -1) {
+ data_[save_i] = save_c;
+ }
+ };
+ while (!data.empty() && data.back() == '\0') {
+ data.remove_suffix(1);
+ }
+ return zero_one_encode(data);
+}
+
+int64 Bitmask::get_ready_prefix_size(int64 offset, int64 part_size, int64 file_size) const {
+ if (offset < 0) {
+ return 0;
+ }
+ if (part_size == 0) {
+ return 0;
+ }
+ CHECK(part_size > 0);
+ auto offset_part = offset / part_size;
+ auto ones = get_ready_parts(offset_part);
+ if (ones == 0) {
+ return 0;
+ }
+ auto ready_parts_end = (offset_part + ones) * part_size;
+ if (file_size != 0 && ready_parts_end > file_size) {
+ ready_parts_end = file_size;
+ if (offset > file_size) {
+ offset = file_size;
+ }
+ }
+ auto res = ready_parts_end - offset;
+ CHECK(res >= 0);
+ return res;
+}
+
+int64 Bitmask::get_total_size(int64 part_size, int64 file_size) const {
+ int64 res = 0;
+ for (int64 i = 0; i < size(); i++) {
+ if (get(i)) {
+ auto from = i * part_size;
+ auto to = from + part_size;
+ if (file_size != 0 && file_size < to) {
+ to = file_size;
+ }
+ if (from < to) {
+ res += to - from;
+ }
+ }
+ }
+ return res;
+}
+
+bool Bitmask::get(int64 offset_part) const {
+ if (offset_part < 0) {
+ return false;
+ }
+ auto index = narrow_cast<size_t>(offset_part / 8);
+ if (index >= data_.size()) {
+ return false;
+ }
+ return (static_cast<uint8>(data_[index]) & (1 << static_cast<int>(offset_part % 8))) != 0;
+}
+
+int64 Bitmask::get_ready_parts(int64 offset_part) const {
+ int64 res = 0;
+ while (get(offset_part + res)) {
+ res++;
+ }
+ return res;
+}
+
+std::vector<int32> Bitmask::as_vector() const {
+ std::vector<int32> res;
+ auto size = narrow_cast<int32>(data_.size() * 8);
+ for (int32 i = 0; i < size; i++) {
+ if (get(i)) {
+ res.push_back(i);
+ }
+ }
+ return res;
+}
+
+void Bitmask::set(int64 offset_part) {
+ CHECK(offset_part >= 0);
+ auto need_size = narrow_cast<size_t>(offset_part / 8 + 1);
+ if (need_size > data_.size()) {
+ data_.resize(need_size, '\0');
+ }
+ data_[need_size - 1] = static_cast<char>(data_[need_size - 1] | (1 << (offset_part % 8)));
+}
+
+int64 Bitmask::size() const {
+ return static_cast<int64>(data_.size()) * 8;
+}
+
+StringBuilder &operator<<(StringBuilder &sb, const Bitmask &mask) {
+ bool prev = false;
+ int32 cnt = 0;
+ for (int64 i = 0; i <= mask.size(); i++) {
+ bool cur = mask.get(i);
+ if (cur != prev) { // zeros at the end are intentionally skipped
+ if (cnt < 5) {
+ while (cnt > 0) {
+ sb << (prev ? '1' : '0');
+ cnt--;
+ }
+ } else {
+ sb << (prev ? '1' : '0') << "(x" << cnt << ')';
+ cnt = 0;
+ }
+ }
+ prev = cur;
+ cnt++;
+ }
+ return sb;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileBitmask.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileBitmask.h
new file mode 100644
index 0000000000..5310870657
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileBitmask.h
@@ -0,0 +1,41 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class Bitmask {
+ public:
+ struct Decode {};
+ struct Ones {};
+ Bitmask() = default;
+ Bitmask(Decode, Slice data);
+ Bitmask(Ones, int64 count);
+ std::string encode(int32 prefix_count = -1);
+ int64 get_ready_prefix_size(int64 offset, int64 part_size, int64 file_size) const;
+ int64 get_total_size(int64 part_size, int64 file_size) const;
+ bool get(int64 offset_part) const;
+
+ int64 get_ready_parts(int64 offset_part) const;
+
+ std::vector<int32> as_vector() const;
+ void set(int64 offset_part);
+ int64 size() const;
+
+ Bitmask compress(int k) const;
+
+ private:
+ std::string data_;
+};
+
+StringBuilder &operator<<(StringBuilder &sb, const Bitmask &mask);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileData.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileData.h
new file mode 100644
index 0000000000..2acd8083de
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileData.h
@@ -0,0 +1,60 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileEncryptionKey.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileSourceId.h"
+
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+class FileData {
+ public:
+ DialogId owner_dialog_id_;
+ uint64 pmc_id_ = 0;
+ RemoteFileLocation remote_;
+ LocalFileLocation local_;
+ unique_ptr<FullGenerateFileLocation> generate_;
+ int64 size_ = 0;
+ int64 expected_size_ = 0;
+ string remote_name_;
+ string url_;
+ FileEncryptionKey encryption_key_;
+ vector<FileSourceId> file_source_ids_;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+
+ template <class ParserT>
+ void parse(ParserT &parser, bool register_file_sources);
+};
+
+inline StringBuilder &operator<<(StringBuilder &sb, const FileData &file_data) {
+ sb << "[" << tag("remote_name", file_data.remote_name_) << " " << tag("size", file_data.size_)
+ << tag("expected_size", file_data.expected_size_) << " " << file_data.encryption_key_;
+ if (!file_data.url_.empty()) {
+ sb << tag("url", file_data.url_);
+ }
+ if (file_data.local_.type() == LocalFileLocation::Type::Full) {
+ sb << " local " << file_data.local_.full();
+ }
+ if (file_data.generate_ != nullptr) {
+ sb << " generate " << *file_data.generate_;
+ }
+ if (file_data.remote_.type() == RemoteFileLocation::Type::Full) {
+ sb << " remote " << file_data.remote_.full();
+ }
+ sb << ", sources = " << format::as_array(file_data.file_source_ids_);
+ return sb << "]";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileData.hpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileData.hpp
new file mode 100644
index 0000000000..5c64fbf907
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileData.hpp
@@ -0,0 +1,137 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/FileData.h"
+
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/FileReferenceManager.hpp"
+#include "td/telegram/files/FileEncryptionKey.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileLocation.hpp"
+#include "td/telegram/Global.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/Version.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+template <class StorerT>
+void FileData::store(StorerT &storer) const {
+ using ::td::store;
+ bool has_owner_dialog_id = owner_dialog_id_.is_valid();
+ bool has_expected_size = size_ == 0 && expected_size_ != 0;
+ bool encryption_key_is_secure = encryption_key_.is_secure();
+ bool has_sources = !file_source_ids_.empty();
+ bool has_version = true;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(has_owner_dialog_id);
+ STORE_FLAG(has_expected_size);
+ STORE_FLAG(encryption_key_is_secure);
+ STORE_FLAG(has_sources);
+ STORE_FLAG(has_version);
+ END_STORE_FLAGS();
+
+ if (has_version) {
+ store(static_cast<int32>(Version::Next) - 1, storer);
+ }
+ if (has_owner_dialog_id) {
+ store(owner_dialog_id_, storer);
+ }
+ store(pmc_id_, storer);
+ store(remote_, storer);
+ store(local_, storer);
+ auto generate = generate_ == nullptr ? GenerateFileLocation() : GenerateFileLocation(*generate_);
+ store(generate, storer);
+ if (has_expected_size) {
+ store(expected_size_, storer);
+ } else {
+ store(size_, storer);
+ }
+ store(remote_name_, storer);
+ store(url_, storer);
+ store(encryption_key_, storer);
+ if (has_sources) {
+ auto td = G()->td().get_actor_unsafe();
+ store(narrow_cast<int32>(file_source_ids_.size()), storer);
+ for (auto file_source_id : file_source_ids_) {
+ td->file_reference_manager_->store_file_source(file_source_id, storer);
+ }
+ }
+}
+
+template <class ParserT>
+void FileData::parse(ParserT &parser, bool register_file_sources) {
+ using ::td::parse;
+ bool has_owner_dialog_id;
+ bool has_expected_size;
+ bool encryption_key_is_secure;
+ bool has_sources;
+ bool has_version;
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(has_owner_dialog_id);
+ PARSE_FLAG(has_expected_size);
+ PARSE_FLAG(encryption_key_is_secure);
+ PARSE_FLAG(has_sources);
+ PARSE_FLAG(has_version);
+ END_PARSE_FLAGS();
+ if (parser.get_error()) {
+ return;
+ }
+
+ int32 version = 0;
+ if (has_version) {
+ parse(version, parser);
+ }
+ parser.set_version(version);
+ if (has_owner_dialog_id) {
+ parse(owner_dialog_id_, parser);
+ }
+ parse(pmc_id_, parser);
+ parse(remote_, parser);
+ parse(local_, parser);
+ GenerateFileLocation generate;
+ parse(generate, parser);
+ if (generate.type() == GenerateFileLocation::Type::Full) {
+ generate_ = make_unique<FullGenerateFileLocation>(generate.full());
+ } else {
+ generate_ = nullptr;
+ }
+ if (has_expected_size) {
+ parse(expected_size_, parser);
+ } else {
+ parse(size_, parser);
+ }
+ parse(remote_name_, parser);
+ parse(url_, parser);
+ encryption_key_.parse(encryption_key_is_secure ? FileEncryptionKey::Type::Secure : FileEncryptionKey::Type::Secret,
+ parser);
+ if (has_sources && register_file_sources) {
+ Td *td = G()->td().get_actor_unsafe();
+ int32 file_source_count;
+ parse(file_source_count, parser);
+ if (0 < file_source_count && file_source_count < 5) {
+ for (int i = 0; i < file_source_count; i++) {
+ if (parser.get_error()) {
+ return;
+ }
+ auto file_source_id = td->file_reference_manager_->parse_file_source(td, parser);
+ if (file_source_id.is_valid() && !td::contains(file_source_ids_, file_source_id)) {
+ file_source_ids_.push_back(file_source_id);
+ }
+ }
+ } else {
+ parser.set_error("Wrong number of file source ids");
+ }
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileDb.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileDb.cpp
index 96795dcea1..d763a97bff 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileDb.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileDb.cpp
@@ -1,28 +1,33 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileDb.h"
+#include "td/telegram/files/FileData.h"
+#include "td/telegram/files/FileData.hpp"
#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileLocation.hpp"
+#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/Version.h"
-#include "td/actor/actor.h"
-
+#include "td/db/SqliteConnectionSafe.h"
+#include "td/db/SqliteDb.h"
+#include "td/db/SqliteKeyValue.h"
#include "td/db/SqliteKeyValueSafe.h"
+#include "td/actor/actor.h"
+
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
-#include "td/utils/ScopeGuard.h"
#include "td/utils/Slice.h"
-#include "td/utils/StackAllocator.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/tl_parsers.h"
-#include "td/utils/tl_storers.h"
namespace td {
@@ -32,20 +37,17 @@ Status drop_file_db(SqliteDb &db, int32 version) {
return Status::OK();
}
-Status fix_file_remote_location_key_bug(SqliteDb &db);
Status init_file_db(SqliteDb &db, int32 version) {
- LOG(INFO) << "Init file db " << tag("version", version);
+ LOG(INFO) << "Init file database " << tag("version", version);
// Check if database exists
TRY_RESULT(has_table, db.has_table("files"));
if (!has_table) {
version = 0;
- } else if (version < static_cast<int32>(DbVersion::DialogDbCreated)) {
+ } else if (version < static_cast<int32>(DbVersion::FixFileRemoteLocationKeyBug)) {
TRY_STATUS(drop_file_db(db, version));
version = 0;
- } else if (version < static_cast<int>(DbVersion::FixFileRemoteLocationKeyBug)) {
- TRY_STATUS(fix_file_remote_location_key_bug(db));
}
if (version == 0) {
@@ -54,12 +56,11 @@ Status init_file_db(SqliteDb &db, int32 version) {
return Status::OK();
}
-class FileDb : public FileDbInterface {
+class FileDb final : public FileDbInterface {
public:
- class FileDbActor : public Actor {
+ class FileDbActor final : public Actor {
public:
- using Id = FileDbInterface::Id;
- FileDbActor(Id current_pmc_id, std::shared_ptr<SqliteKeyValueSafe> file_kv_safe)
+ FileDbActor(FileDbId current_pmc_id, std::shared_ptr<SqliteKeyValueSafe> file_kv_safe)
: current_pmc_id_(current_pmc_id), file_kv_safe_(std::move(file_kv_safe)) {
}
@@ -71,110 +72,124 @@ class FileDb : public FileDbInterface {
}
void load_file_data(const string &key, Promise<FileData> promise) {
- promise.set_result(load_file_data_impl(file_pmc(), key));
+ promise.set_result(load_file_data_impl(actor_id(this), file_pmc(), key, current_pmc_id_));
}
- void clear_file_data(Id id, const string &remote_key, const string &local_key, const string &generate_key) {
+ void clear_file_data(FileDbId id, const string &remote_key, const string &local_key, const string &generate_key) {
auto &pmc = file_pmc();
- pmc.begin_transaction().ensure();
- SCOPE_EXIT {
- pmc.commit_transaction().ensure();
- };
+ pmc.begin_write_transaction().ensure();
if (id > current_pmc_id_) {
- pmc.set("file_id", to_string(id));
+ pmc.set("file_id", to_string(id.get()));
current_pmc_id_ = id;
}
- pmc.erase("file" + to_string(id));
- LOG(DEBUG) << "ERASE " << format::as_hex_dump<4>(Slice(PSLICE() << "file" << to_string(id)));
+ pmc.erase(PSTRING() << "file" << id.get());
+ // LOG(DEBUG) << "ERASE " << format::as_hex_dump<4>(Slice(PSLICE() << "file" << id.get()));
if (!remote_key.empty()) {
pmc.erase(remote_key);
- LOG(DEBUG) << "ERASE remote " << format::as_hex_dump<4>(Slice(remote_key));
+ // LOG(DEBUG) << "ERASE remote " << format::as_hex_dump<4>(Slice(remote_key));
}
if (!local_key.empty()) {
pmc.erase(local_key);
- LOG(DEBUG) << "ERASE local " << format::as_hex_dump<4>(Slice(local_key));
+ // LOG(DEBUG) << "ERASE local " << format::as_hex_dump<4>(Slice(local_key));
}
if (!generate_key.empty()) {
pmc.erase(generate_key);
}
+
+ pmc.commit_transaction().ensure();
}
- void store_file_data(Id id, const string &file_data, const string &remote_key, const string &local_key,
+
+ void store_file_data(FileDbId id, const string &file_data, const string &remote_key, const string &local_key,
const string &generate_key) {
auto &pmc = file_pmc();
- pmc.begin_transaction().ensure();
- SCOPE_EXIT {
- pmc.commit_transaction().ensure();
- };
+ pmc.begin_write_transaction().ensure();
if (id > current_pmc_id_) {
- pmc.set("file_id", to_string(id));
+ pmc.set("file_id", to_string(id.get()));
current_pmc_id_ = id;
}
- pmc.set("file" + to_string(id), file_data);
+ pmc.set(PSTRING() << "file" << id.get(), file_data);
if (!remote_key.empty()) {
- pmc.set(remote_key, to_string(id));
+ pmc.set(remote_key, to_string(id.get()));
}
if (!local_key.empty()) {
- pmc.set(local_key, to_string(id));
+ pmc.set(local_key, to_string(id.get()));
}
if (!generate_key.empty()) {
- pmc.set(generate_key, to_string(id));
+ pmc.set(generate_key, to_string(id.get()));
}
+
+ pmc.commit_transaction().ensure();
}
- void store_file_data_ref(Id id, Id new_id) {
+
+ void store_file_data_ref(FileDbId id, FileDbId new_id) {
auto &pmc = file_pmc();
- pmc.begin_transaction().ensure();
- SCOPE_EXIT {
- pmc.commit_transaction().ensure();
- };
+ pmc.begin_write_transaction().ensure();
if (id > current_pmc_id_) {
- pmc.set("file_id", to_string(id));
+ pmc.set("file_id", to_string(id.get()));
current_pmc_id_ = id;
}
- pmc.set("file" + to_string(id), "@@" + to_string(new_id));
+ do_store_file_data_ref(id, new_id);
+
+ pmc.commit_transaction().ensure();
+ }
+
+ void optimize_refs(std::vector<FileDbId> ids, FileDbId main_id) {
+ LOG(INFO) << "Optimize " << ids.size() << " ids in file database to " << main_id.get();
+ auto &pmc = file_pmc();
+ pmc.begin_write_transaction().ensure();
+ for (size_t i = 0; i + 1 < ids.size(); i++) {
+ do_store_file_data_ref(ids[i], main_id);
+ }
+ pmc.commit_transaction().ensure();
}
private:
- Id current_pmc_id_;
+ FileDbId current_pmc_id_;
std::shared_ptr<SqliteKeyValueSafe> file_kv_safe_;
SqliteKeyValue &file_pmc() {
return file_kv_safe_->get();
}
+
+ void do_store_file_data_ref(FileDbId id, FileDbId new_id) {
+ file_pmc().set(PSTRING() << "file" << id.get(), PSTRING() << "@@" << new_id.get());
+ }
};
explicit FileDb(std::shared_ptr<SqliteKeyValueSafe> kv_safe, int scheduler_id = -1) {
file_kv_safe_ = std::move(kv_safe);
CHECK(file_kv_safe_);
- current_pmc_id_ = to_integer<int32>(file_kv_safe_->get().get("file_id"));
+ current_pmc_id_ = FileDbId(to_integer<uint64>(file_kv_safe_->get().get("file_id")));
file_db_actor_ =
create_actor_on_scheduler<FileDbActor>("FileDbActor", scheduler_id, current_pmc_id_, file_kv_safe_);
}
- Id create_pmc_id() override {
- return ++current_pmc_id_;
+ FileDbId create_pmc_id() final {
+ current_pmc_id_ = FileDbId(current_pmc_id_.get() + 1);
+ return current_pmc_id_;
}
- void close(Promise<> promise) override {
+ void close(Promise<> promise) final {
send_closure(std::move(file_db_actor_), &FileDbActor::close, std::move(promise));
}
- void get_file_data_impl(string key, Promise<FileData> promise) override {
+ void get_file_data_impl(string key, Promise<FileData> promise) final {
send_closure(file_db_actor_, &FileDbActor::load_file_data, std::move(key), std::move(promise));
}
- Result<FileData> get_file_data_sync_impl(string key) override {
- return load_file_data_impl(file_kv_safe_->get(), key);
+ Result<FileData> get_file_data_sync_impl(string key) final {
+ return load_file_data_impl(file_db_actor_.get(), file_kv_safe_->get(), key, current_pmc_id_);
}
- void clear_file_data(Id id, const FileData &file_data) override {
+ void clear_file_data(FileDbId id, const FileData &file_data) final {
string remote_key;
if (file_data.remote_.type() == RemoteFileLocation::Type::Full) {
remote_key = as_key(file_data.remote_.full());
@@ -189,7 +204,8 @@ class FileDb : public FileDbInterface {
}
send_closure(file_db_actor_, &FileDbActor::clear_file_data, id, remote_key, local_key, generate_key);
}
- void set_file_data(Id id, const FileData &file_data, bool new_remote, bool new_local, bool new_generate) override {
+
+ void set_file_data(FileDbId id, const FileData &file_data, bool new_remote, bool new_local, bool new_generate) final {
string remote_key;
if (file_data.remote_.type() == RemoteFileLocation::Type::Full && new_remote) {
remote_key = as_key(file_data.remote_.full());
@@ -202,63 +218,77 @@ class FileDb : public FileDbInterface {
if (file_data.generate_ != nullptr && new_generate) {
generate_key = as_key(*file_data.generate_);
}
- LOG(DEBUG) << "SAVE " << id << " -> " << file_data << " "
- << tag("remote", format::as_hex_dump<4>(Slice(remote_key)))
- << tag("local", format::as_hex_dump<4>(Slice(local_key)));
+ // LOG(DEBUG) << "SAVE " << id.get() << " -> " << file_data << " "
+ // << tag("remote_key", format::as_hex_dump<4>(Slice(remote_key)))
+ // << tag("local_key", format::as_hex_dump<4>(Slice(local_key)))
+ // << tag("generate_key", format::as_hex_dump<4>(Slice(generate_key)));
send_closure(file_db_actor_, &FileDbActor::store_file_data, id, serialize(file_data), remote_key, local_key,
generate_key);
}
- void set_file_data_ref(Id id, Id new_id) override {
+ void set_file_data_ref(FileDbId id, FileDbId new_id) final {
send_closure(file_db_actor_, &FileDbActor::store_file_data_ref, id, new_id);
}
- SqliteKeyValue &pmc() override {
+ SqliteKeyValue &pmc() final {
return file_kv_safe_->get();
}
private:
ActorOwn<FileDbActor> file_db_actor_;
- Id current_pmc_id_;
+ FileDbId current_pmc_id_;
std::shared_ptr<SqliteKeyValueSafe> file_kv_safe_;
- static Result<FileData> load_file_data_impl(SqliteKeyValue &pmc, const string &key) {
- //LOG(DEBUG) << "Load by key " << format::as_hex_dump<4>(Slice(key));
+ static Result<FileData> load_file_data_impl(ActorId<FileDbActor> file_db_actor_id, SqliteKeyValue &pmc,
+ const string &key, FileDbId current_pmc_id) {
+ // LOG(DEBUG) << "Load by key " << format::as_hex_dump<4>(Slice(key));
TRY_RESULT(id, get_id(pmc, key));
+ vector<FileDbId> ids;
string data_str;
int attempt_count = 0;
while (true) {
- if (attempt_count > 5) {
- LOG(FATAL) << "cycle in files db?";
+ if (attempt_count > 100) {
+ LOG(FATAL) << "Cycle in file database? current_pmc_id=" << current_pmc_id << " key=" << key
+ << " links=" << format::as_array(ids);
}
attempt_count++;
- data_str = pmc.get(PSTRING() << "file" << id);
+ data_str = pmc.get(PSTRING() << "file" << id.get());
auto data_slice = Slice(data_str);
if (data_slice.substr(0, 2) == "@@") {
- id = to_integer<Id>(data_slice.substr(2));
+ ids.push_back(id);
+
+ id = FileDbId(to_integer<uint64>(data_slice.substr(2)));
} else {
break;
}
}
- //LOG(DEBUG) << "By id " << id << " found data " << format::as_hex_dump<4>(Slice(data_str));
+ if (ids.size() > 1) {
+ send_closure(file_db_actor_id, &FileDbActor::optimize_refs, std::move(ids), id);
+ }
+ // LOG(DEBUG) << "By ID " << id.get() << " found data " << format::as_hex_dump<4>(Slice(data_str));
+ // LOG(INFO) << attempt_count;
+ log_event::WithVersion<TlParser> parser(data_str);
+ parser.set_version(static_cast<int32>(Version::Initial));
FileData data;
- auto status = unserialize(data, data_str);
+ data.parse(parser, true);
+ parser.fetch_end();
+ auto status = parser.get_status();
if (status.is_error()) {
return std::move(status);
}
return std::move(data);
}
- static Result<Id> get_id(SqliteKeyValue &pmc, const string &key) TD_WARN_UNUSED_RESULT {
+ static Result<FileDbId> get_id(SqliteKeyValue &pmc, const string &key) TD_WARN_UNUSED_RESULT {
auto id_str = pmc.get(key);
- //LOG(DEBUG) << "Found id " << id_str << " by key " << format::as_hex_dump<4>(Slice(key));
+ // LOG(DEBUG) << "Found ID " << id_str << " by key " << format::as_hex_dump<4>(Slice(key));
if (id_str.empty()) {
- return Status::Error("There is no such a key in db");
+ return Status::Error("There is no such a key in database");
}
- return to_integer<Id>(id_str);
+ return FileDbId(to_integer<uint64>(id_str));
}
};
@@ -267,24 +297,4 @@ std::shared_ptr<FileDbInterface> create_file_db(std::shared_ptr<SqliteConnection
return std::make_shared<FileDb>(std::move(kv), scheduler_id);
}
-Status fix_file_remote_location_key_bug(SqliteDb &db) {
- static const int32 OLD_KEY_MAGIC = 0x64378433;
- SqliteKeyValue kv;
- kv.init_with_connection(db.clone(), "files").ensure();
- auto ptr = StackAllocator::alloc(4);
- MutableSlice prefix = ptr.as_slice();
- TlStorerUnsafe(prefix.begin()).store_int(OLD_KEY_MAGIC);
- kv.get_by_prefix(prefix, [&](Slice key, Slice value) {
- CHECK(TlParser(key).fetch_int() == OLD_KEY_MAGIC);
- auto remote_str = PSTRING() << key.substr(4, 4) << Slice("\0\0\0\0") << key.substr(8);
- FullRemoteFileLocation remote;
- if (unserialize(remote, remote_str).is_ok()) {
- kv.set(as_key(remote), value);
- }
- LOG(DEBUG) << "ERASE " << format::as_hex_dump<4>(Slice(key));
- kv.erase(key);
- });
- return Status::OK();
-}
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileDb.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileDb.h
index d53fb32091..76813dabae 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileDb.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileDb.h
@@ -1,27 +1,29 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/files/FileLocation.h"
-
-#include "td/actor/PromiseFuture.h"
+#include "td/telegram/files/FileData.h"
+#include "td/telegram/files/FileDbId.h"
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
+#include "td/utils/tl_storers.h"
#include <memory>
namespace td {
+
class SqliteDb;
class SqliteConnectionSafe;
class SqliteKeyValue;
-} // namespace td
-namespace td {
Status drop_file_db(SqliteDb &db, int32 version) TD_WARN_UNUSED_RESULT;
Status init_file_db(SqliteDb &db, int32 version) TD_WARN_UNUSED_RESULT;
@@ -29,40 +31,54 @@ class FileDbInterface;
std::shared_ptr<FileDbInterface> create_file_db(std::shared_ptr<SqliteConnectionSafe> connection,
int32 scheduler_id = -1) TD_WARN_UNUSED_RESULT;
-using FileDbId = uint64;
-
class FileDbInterface {
public:
- using Id = FileDbId;
FileDbInterface() = default;
FileDbInterface(const FileDbInterface &) = delete;
FileDbInterface &operator=(const FileDbInterface &) = delete;
virtual ~FileDbInterface() = default;
- // non thread safe
- virtual Id create_pmc_id() = 0;
+ // non-thread-safe
+ virtual FileDbId create_pmc_id() = 0;
- // thread safe
+ // thread-safe
virtual void close(Promise<> promise) = 0;
+
+ template <class LocationT>
+ static string as_key(const LocationT &object) {
+ TlStorerCalcLength calc_length;
+ calc_length.store_int(0);
+ object.as_key().store(calc_length);
+
+ BufferSlice key_buffer{calc_length.get_length()};
+ auto key = key_buffer.as_slice();
+ TlStorerUnsafe storer(key.ubegin());
+ storer.store_int(LocationT::KEY_MAGIC);
+ object.as_key().store(storer);
+ CHECK(storer.get_buf() == key.uend());
+ return key.str();
+ }
+
template <class LocationT>
void get_file_data(const LocationT &location, Promise<FileData> promise) {
- get_file_data(as_key(location), std::move(promise));
+ get_file_data_impl(as_key(location), std::move(promise));
}
template <class LocationT>
Result<FileData> get_file_data_sync(const LocationT &location) {
auto res = get_file_data_sync_impl(as_key(location));
if (res.is_ok()) {
- LOG(DEBUG) << "GET " << location << " " << res.ok();
+ LOG(DEBUG) << "GET " << location << ": " << res.ok();
} else {
- LOG(DEBUG) << "GET " << location << " " << res.error();
+ LOG(DEBUG) << "GET " << location << ": " << res.error();
}
return res;
}
- virtual void clear_file_data(Id id, const FileData &file_data) = 0;
- virtual void set_file_data(Id id, const FileData &file_data, bool new_remote, bool new_local, bool new_generate) = 0;
- virtual void set_file_data_ref(Id id, Id new_id) = 0;
+ virtual void clear_file_data(FileDbId id, const FileData &file_data) = 0;
+ virtual void set_file_data(FileDbId id, const FileData &file_data, bool new_remote, bool new_local,
+ bool new_generate) = 0;
+ virtual void set_file_data_ref(FileDbId id, FileDbId new_id) = 0;
// For FileStatsWorker. TODO: remove it
virtual SqliteKeyValue &pmc() = 0;
@@ -71,5 +87,5 @@ class FileDbInterface {
virtual void get_file_data_impl(string key, Promise<FileData> promise) = 0;
virtual Result<FileData> get_file_data_sync_impl(string key) = 0;
};
-;
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileDbId.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileDbId.h
new file mode 100644
index 0000000000..d1292cb744
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileDbId.h
@@ -0,0 +1,58 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class FileDbId {
+ uint64 id = 0;
+
+ public:
+ FileDbId() = default;
+
+ explicit FileDbId(uint64 file_db_id) : id(file_db_id) {
+ }
+ template <class T1, typename = std::enable_if_t<std::is_convertible<T1, uint64>::value>>
+ FileDbId(T1 file_db_id) = delete;
+
+ bool empty() const {
+ return id == 0;
+ }
+ bool is_valid() const {
+ return id > 0;
+ }
+
+ uint64 get() const {
+ return id;
+ }
+
+ bool operator<(const FileDbId &other) const {
+ return id < other.id;
+ }
+ bool operator>(const FileDbId &other) const {
+ return id > other.id;
+ }
+
+ bool operator==(const FileDbId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const FileDbId &other) const {
+ return id != other.id;
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &sb, const FileDbId &id) {
+ return sb << "FileDbId{" << id.get() << "}";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.cpp
index 29180dd701..433c5717bd 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.cpp
@@ -1,33 +1,40 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileDownloader.h"
-#include "td/telegram/telegram_api.h"
-
+#include "td/telegram/FileReferenceManager.h"
#include "td/telegram/files/FileLoaderUtils.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/SecureStorage.h"
#include "td/telegram/UniqueId.h"
+#include "td/utils/as.h"
+#include "td/utils/base64.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/Stat.h"
#include "td/utils/ScopeGuard.h"
-#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/UInt.h"
#include <tuple>
namespace td {
FileDownloader::FileDownloader(const FullRemoteFileLocation &remote, const LocalFileLocation &local, int64 size,
- string name, const FileEncryptionKey &encryption_key, bool is_small, bool search_file,
- std::unique_ptr<Callback> callback)
+ string name, const FileEncryptionKey &encryption_key, bool is_small,
+ bool need_search_file, int64 offset, int64 limit, unique_ptr<Callback> callback)
: remote_(remote)
, local_(local)
, size_(size)
@@ -35,10 +42,15 @@ FileDownloader::FileDownloader(const FullRemoteFileLocation &remote, const Local
, encryption_key_(encryption_key)
, callback_(std::move(callback))
, is_small_(is_small)
- , search_file_(search_file) {
- if (!encryption_key.empty()) {
+ , need_search_file_(need_search_file)
+ , offset_(offset)
+ , limit_(limit) {
+ if (encryption_key.is_secret()) {
set_ordered_flag(true);
}
+ if (!encryption_key.empty()) {
+ CHECK(offset_ == 0);
+ }
}
Result<FileLoader::FileInfo> FileDownloader::init() {
@@ -48,72 +60,88 @@ Result<FileLoader::FileInfo> FileDownloader::init() {
if (local_.type() == LocalFileLocation::Type::Full) {
return Status::Error("File is already downloaded");
}
- int ready_part_count = 0;
+ if (encryption_key_.is_secure() && !encryption_key_.has_value_hash()) {
+ LOG(ERROR) << "Can't download Secure file with unknown value_hash";
+ }
+ if (remote_.file_type_ == FileType::SecureEncrypted) {
+ size_ = 0;
+ }
int32 part_size = 0;
+ Bitmask bitmask{Bitmask::Ones{}, 0};
if (local_.type() == LocalFileLocation::Type::Partial) {
const auto &partial = local_.partial();
path_ = partial.path_;
auto result_fd = FileFd::open(path_, FileFd::Write | FileFd::Read);
// TODO: check timestamps..
if (result_fd.is_ok()) {
- if (!encryption_key_.empty()) {
- CHECK(partial.iv_.size() == 32) << partial.iv_.size();
+ bitmask = Bitmask(Bitmask::Decode{}, partial.ready_bitmask_);
+ if (encryption_key_.is_secret()) {
+ LOG_CHECK(partial.iv_.size() == 32) << partial.iv_.size();
encryption_key_.mutable_iv() = as<UInt256>(partial.iv_.data());
- next_part_ = partial.ready_part_count_;
+ next_part_ = narrow_cast<int32>(bitmask.get_ready_parts(0));
}
fd_ = result_fd.move_as_ok();
- part_size = partial.part_size_;
- ready_part_count = partial.ready_part_count_;
+ CHECK(partial.part_size_ <= (1 << 20));
+ CHECK(0 <= partial.part_size_);
+ part_size = static_cast<int32>(partial.part_size_);
+ CHECK((part_size & (part_size - 1)) == 0);
}
}
- if (search_file_ && fd_.empty() && size_ > 0 && size_ < 1000 * (1 << 20) && encryption_key_.empty() &&
- !remote_.is_web()) {
+ if (need_search_file_ && fd_.empty() && size_ > 0 && encryption_key_.empty() && !remote_.is_web()) {
[&] {
- TRY_RESULT(path, search_file(get_files_dir(remote_.file_type_), name_, size_));
+ TRY_RESULT(path, search_file(remote_.file_type_, name_, size_));
TRY_RESULT(fd, FileFd::open(path, FileFd::Read));
LOG(INFO) << "Check hash of local file " << path;
path_ = std::move(path);
fd_ = std::move(fd);
need_check_ = true;
only_check_ = true;
- part_size = 32 * (1 << 10);
- ready_part_count = narrow_cast<int>((size_ + part_size - 1) / part_size);
+ part_size = 128 * (1 << 10);
+ bitmask = Bitmask{Bitmask::Ones{}, (size_ + part_size - 1) / part_size};
return Status::OK();
}();
}
- std::vector<int> parts(ready_part_count);
- for (int i = 0; i < ready_part_count; i++) {
- parts[i] = i;
- }
-
FileInfo res;
res.size = size_;
res.is_size_final = true;
res.part_size = part_size;
- res.ready_parts = std::move(parts);
+ res.ready_parts = bitmask.as_vector();
res.use_part_count_limit = false;
res.only_check = only_check_;
- res.need_delay = !is_small_ && (remote_.file_type_ == FileType::VideoNote ||
- remote_.file_type_ == FileType::VoiceNote || remote_.file_type_ == FileType::Audio ||
- remote_.file_type_ == FileType::Video || remote_.file_type_ == FileType::Animation ||
- (remote_.file_type_ == FileType::Encrypted && size_ > (1 << 20)));
+ auto file_type = remote_.file_type_;
+ res.need_delay =
+ !is_small_ &&
+ (file_type == FileType::VideoNote || file_type == FileType::Document || file_type == FileType::DocumentAsFile ||
+ file_type == FileType::VoiceNote || file_type == FileType::Audio || file_type == FileType::Video ||
+ file_type == FileType::Animation || (file_type == FileType::Encrypted && size_ > (1 << 20)));
+ res.offset = offset_;
+ res.limit = limit_;
return res;
}
-Status FileDownloader::on_ok(int64 size) {
- auto dir = get_files_dir(remote_.file_type_);
+Status FileDownloader::on_ok(int64 size) {
std::string path;
+ fd_.close();
+ if (encryption_key_.is_secure()) {
+ TRY_RESULT(file_path, open_temp_file(remote_.file_type_));
+ string tmp_path;
+ std::tie(std::ignore, tmp_path) = std::move(file_path);
+ TRY_STATUS(secure_storage::decrypt_file(encryption_key_.secret(), encryption_key_.value_hash(), path_, tmp_path));
+ unlink(path_).ignore();
+ path_ = std::move(tmp_path);
+ TRY_RESULT(path_stat, stat(path_));
+ size = path_stat.size_;
+ }
if (only_check_) {
path = path_;
} else {
- TRY_RESULT(perm_path, create_from_temp(path_, dir, name_));
- path = std::move(perm_path);
+ TRY_RESULT_ASSIGN(path, create_from_temp(remote_.file_type_, path_, name_));
}
- fd_.close();
- callback_->on_ok(FullLocalFileLocation(remote_.file_type_, std::move(path), 0), size);
+ callback_->on_ok(FullLocalFileLocation(remote_.file_type_, std::move(path), 0), size, !only_check_);
return Status::OK();
}
+
void FileDownloader::on_error(Status status) {
fd_.close();
callback_->on_error(std::move(status));
@@ -136,10 +164,10 @@ Result<bool> FileDownloader::should_restart_part(Part part, NetQueryPtr &net_que
switch (narrow_cast<QueryType>(UniqueId::extract_key(net_query->id()))) {
case QueryType::Default: {
if (net_query->ok_tl_constructor() == telegram_api::upload_fileCdnRedirect::ID) {
- LOG(DEBUG) << part.id << " got REDIRECT";
TRY_RESULT(file_base, fetch_result<telegram_api::upload_getFile>(net_query->ok()));
CHECK(file_base->get_id() == telegram_api::upload_fileCdnRedirect::ID);
auto file = move_tl_object_as<telegram_api::upload_fileCdnRedirect>(file_base);
+ LOG(DEBUG) << part.id << " got REDIRECT " << to_string(file);
auto new_cdn_file_token = file->file_token_.as_slice();
if (cdn_file_token_ == new_cdn_file_token) {
@@ -170,10 +198,10 @@ Result<bool> FileDownloader::should_restart_part(Part part, NetQueryPtr &net_que
}
case QueryType::CDN: {
if (net_query->ok_tl_constructor() == telegram_api::upload_cdnFileReuploadNeeded::ID) {
- LOG(DEBUG) << part.id << " got REUPLOAD";
TRY_RESULT(file_base, fetch_result<telegram_api::upload_getCdnFile>(net_query->ok()));
CHECK(file_base->get_id() == telegram_api::upload_cdnFileReuploadNeeded::ID);
auto file = move_tl_object_as<telegram_api::upload_cdnFileReuploadNeeded>(file_base);
+ LOG(DEBUG) << part.id << " got REUPLOAD " << to_string(file);
cdn_part_reupload_token_[part.id] = file->request_token_.as_slice().str();
return true;
}
@@ -191,8 +219,9 @@ Result<bool> FileDownloader::should_restart_part(Part part, NetQueryPtr &net_que
return false;
}
-Result<std::pair<NetQueryPtr, bool>> FileDownloader::start_part(Part part, int32 part_count) {
- if (!encryption_key_.empty()) {
+
+Result<std::pair<NetQueryPtr, bool>> FileDownloader::start_part(Part part, int32 part_count, int64 streaming_offset) {
+ if (encryption_key_.is_secret()) {
part.size = (part.size + 15) & ~15; // fix for last part
}
// auto size = part.size;
@@ -206,36 +235,48 @@ Result<std::pair<NetQueryPtr, bool>> FileDownloader::start_part(Part part, int32
callback_->on_start_download();
+ auto net_query_type = is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download;
NetQueryPtr net_query;
if (!use_cdn_) {
- net_query = G()->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::Default)),
+ int32 flags = 0;
+#if !TD_EMSCRIPTEN
+ // CDN is supported, unless we use domains instead of IPs from a browser
+ if (streaming_offset == 0) {
+ flags |= telegram_api::upload_getFile::CDN_SUPPORTED_MASK;
+ }
+#endif
+ DcId dc_id = remote_.is_web() ? G()->get_webfile_dc_id() : remote_.get_dc_id();
+ auto id = UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::Default));
+ net_query =
remote_.is_web()
- ? create_storer(telegram_api::upload_getWebFile(remote_.as_input_web_file_location(),
- static_cast<int32>(part.offset), static_cast<int32>(size)))
- : create_storer(telegram_api::upload_getFile(remote_.as_input_file_location(),
- static_cast<int32>(part.offset), static_cast<int32>(size))),
- remote_.get_dc_id(), is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download);
+ ? G()->net_query_creator().create(
+ id,
+ telegram_api::upload_getWebFile(remote_.as_input_web_file_location(), narrow_cast<int32>(part.offset),
+ narrow_cast<int32>(size)),
+ {}, dc_id, net_query_type, NetQuery::AuthFlag::On)
+ : G()->net_query_creator().create(
+ id,
+ telegram_api::upload_getFile(flags, false /*ignored*/, false /*ignored*/,
+ remote_.as_input_file_location(), part.offset, narrow_cast<int32>(size)),
+ {}, dc_id, net_query_type, NetQuery::AuthFlag::On);
} else {
if (remote_.is_web()) {
return Status::Error("Can't download web file from CDN");
}
auto it = cdn_part_reupload_token_.find(part.id);
if (it == cdn_part_reupload_token_.end()) {
- auto query = telegram_api::upload_getCdnFile(BufferSlice(cdn_file_token_), static_cast<int32>(part.offset),
- static_cast<int32>(size));
+ auto query = telegram_api::upload_getCdnFile(BufferSlice(cdn_file_token_), part.offset, narrow_cast<int32>(size));
cdn_part_file_token_generation_[part.id] = cdn_file_token_generation_;
LOG(DEBUG) << part.id << " " << to_string(query);
- net_query = G()->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::CDN)), create_storer(query), cdn_dc_id_,
- is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download, NetQuery::AuthFlag::Off);
+ net_query =
+ G()->net_query_creator().create(UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::CDN)),
+ query, {}, cdn_dc_id_, net_query_type, NetQuery::AuthFlag::Off);
} else {
auto query = telegram_api::upload_reuploadCdnFile(BufferSlice(cdn_file_token_), BufferSlice(it->second));
LOG(DEBUG) << part.id << " " << to_string(query);
net_query = G()->net_query_creator().create(
- UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::ReuploadCDN)), create_storer(query),
- remote_.get_dc_id(), is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download,
- NetQuery::AuthFlag::On);
+ UniqueId::next(UniqueId::Type::Default, static_cast<uint8>(QueryType::ReuploadCDN)), query, {},
+ remote_.get_dc_id(), net_query_type, NetQuery::AuthFlag::On);
cdn_part_reupload_token_.erase(it);
}
}
@@ -243,10 +284,21 @@ Result<std::pair<NetQueryPtr, bool>> FileDownloader::start_part(Part part, int32
return std::make_pair(std::move(net_query), false);
}
-Result<size_t> FileDownloader::process_part(Part part, NetQueryPtr net_query) {
+Status FileDownloader::check_net_query(NetQueryPtr &net_query) {
if (net_query->is_error()) {
- return std::move(net_query->error());
+ auto error = net_query->move_as_error();
+ if (FileReferenceManager::is_file_reference_error(error)) {
+ VLOG(file_references) << "Receive " << error << " for being downloaded file";
+ error = Status::Error(error.code(),
+ PSLICE() << error.message() << "#BASE64" << base64_encode(remote_.get_file_reference()));
+ }
+ return error;
}
+ return Status::OK();
+}
+
+Result<size_t> FileDownloader::process_part(Part part, NetQueryPtr net_query) {
+ TRY_STATUS(check_net_query(net_query));
BufferSlice bytes;
bool need_cdn_decrypt = false;
@@ -260,7 +312,7 @@ Result<size_t> FileDownloader::process_part(Part part, NetQueryPtr net_query) {
TRY_RESULT(file_base, fetch_result<telegram_api::upload_getFile>(net_query->ok()));
CHECK(file_base->get_id() == telegram_api::upload_file::ID);
auto file = move_tl_object_as<telegram_api::upload_file>(file_base);
- LOG(DEBUG) << part.id << " upload_getFile result";
+ LOG(DEBUG) << part.id << " upload.getFile result " << to_string(file);
bytes = std::move(file->bytes_);
}
break;
@@ -269,7 +321,7 @@ Result<size_t> FileDownloader::process_part(Part part, NetQueryPtr net_query) {
TRY_RESULT(file_base, fetch_result<telegram_api::upload_getCdnFile>(net_query->ok()));
CHECK(file_base->get_id() == telegram_api::upload_cdnFile::ID);
auto file = move_tl_object_as<telegram_api::upload_cdnFile>(file_base);
- LOG(DEBUG) << part.id << " upload_getCdnFile result";
+ LOG(DEBUG) << part.id << " upload.getCdnFile result " << to_string(file);
bytes = std::move(file->bytes_);
need_cdn_decrypt = true;
break;
@@ -279,10 +331,9 @@ Result<size_t> FileDownloader::process_part(Part part, NetQueryPtr net_query) {
}
auto padded_size = part.size;
- if (!encryption_key_.empty()) {
+ if (encryption_key_.is_secret()) {
padded_size = (part.size + 15) & ~15;
}
- LOG(INFO) << "Got " << bytes.size() << " padded_size=" << padded_size;
if (bytes.size() > padded_size) {
return Status::Error("Part size is more than requested");
}
@@ -292,59 +343,64 @@ Result<size_t> FileDownloader::process_part(Part part, NetQueryPtr net_query) {
// Encryption
if (need_cdn_decrypt) {
- auto iv = as<UInt128>(cdn_encryption_iv_.c_str());
CHECK(part.offset % 16 == 0);
auto offset = narrow_cast<uint32>(part.offset / 16);
offset =
((offset & 0xff) << 24) | ((offset & 0xff00) << 8) | ((offset & 0xff0000) >> 8) | ((offset & 0xff000000) >> 24);
- as<uint32>(iv.raw + 12) = offset;
- auto key = as<UInt256>(cdn_encryption_key_.c_str());
AesCtrState ctr_state;
- ctr_state.init(key, iv);
+ string iv = cdn_encryption_iv_;
+ as<uint32>(&iv[12]) = offset;
+ ctr_state.init(cdn_encryption_key_, iv);
ctr_state.decrypt(bytes.as_slice(), bytes.as_slice());
}
- if (!encryption_key_.empty()) {
- CHECK(next_part_ == part.id) << tag("expected part.id", next_part_) << "!=" << tag("part.id", part.id);
+ if (encryption_key_.is_secret()) {
+ LOG_CHECK(next_part_ == part.id) << tag("expected part.id", next_part_) << "!=" << tag("part.id", part.id);
CHECK(!next_part_stop_);
next_part_++;
if (part.size % 16 != 0) {
next_part_stop_ = true;
}
- aes_ige_decrypt(encryption_key_.key(), &encryption_key_.mutable_iv(), bytes.as_slice(), bytes.as_slice());
+ aes_ige_decrypt(as_slice(encryption_key_.key()), as_slice(encryption_key_.mutable_iv()), bytes.as_slice(),
+ bytes.as_slice());
}
- auto slice = bytes.as_slice().truncate(part.size);
+ auto slice = bytes.as_slice().substr(0, part.size);
TRY_STATUS(acquire_fd());
+ LOG(INFO) << "Got " << slice.size() << " bytes at offset " << part.offset << " for \"" << path_ << '"';
TRY_RESULT(written, fd_.pwrite(slice, part.offset));
+ LOG(INFO) << "Written " << written << " bytes";
// may write less than part.size, when size of downloadable file is unknown
if (written != slice.size()) {
return Status::Error("Failed to save file part to the file");
}
return written;
}
-void FileDownloader::on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready,
- int64 ready_size) {
- if (is_ready) {
+
+void FileDownloader::on_progress(Progress progress) {
+ if (progress.is_ready) {
// do not send partial location. will lead to wrong local_size
return;
}
- if (ready_size == 0 || path_.empty()) {
+ if (progress.ready_size == 0 || path_.empty()) {
return;
}
- if (encryption_key_.empty()) {
- callback_->on_partial_download(PartialLocalFileLocation{remote_.file_type_, path_, part_size, ready_part_count, ""},
- ready_size);
- } else {
+ if (encryption_key_.empty() || encryption_key_.is_secure()) {
+ callback_->on_partial_download(
+ PartialLocalFileLocation{remote_.file_type_, progress.part_size, path_, "", std::move(progress.ready_bitmask)},
+ progress.ready_size, progress.size);
+ } else if (encryption_key_.is_secret()) {
UInt256 iv;
- if (ready_part_count == next_part_) {
+ if (progress.ready_part_count == next_part_) {
iv = encryption_key_.mutable_iv();
} else {
- LOG(FATAL) << tag("ready_part_count", ready_part_count) << tag("next_part", next_part_);
+ LOG(FATAL) << tag("ready_part_count", progress.ready_part_count) << tag("next_part", next_part_);
}
- callback_->on_partial_download(PartialLocalFileLocation{remote_.file_type_, path_, part_size, ready_part_count,
- Slice(iv.raw, sizeof(iv)).str()},
- ready_size);
+ callback_->on_partial_download(PartialLocalFileLocation{remote_.file_type_, progress.part_size, path_,
+ as_slice(iv).str(), std::move(progress.ready_bitmask)},
+ progress.ready_size, progress.size);
+ } else {
+ UNREACHABLE();
}
}
@@ -354,10 +410,12 @@ FileLoader::Callback *FileDownloader::get_callback() {
Status FileDownloader::process_check_query(NetQueryPtr net_query) {
has_hash_query_ = false;
- TRY_RESULT(file_hashes, fetch_result<telegram_api::upload_getCdnFileHashes>(std::move(net_query)));
+ TRY_STATUS(check_net_query(net_query));
+ TRY_RESULT(file_hashes, fetch_result<telegram_api::upload_getFileHashes>(std::move(net_query)));
add_hash_info(file_hashes);
return Status::OK();
}
+
Result<FileLoader::CheckInfo> FileDownloader::check_loop(int64 checked_prefix_size, int64 ready_prefix_size,
bool is_ready) {
if (!need_check_) {
@@ -373,7 +431,7 @@ Result<FileLoader::CheckInfo> FileDownloader::check_loop(int64 checked_prefix_si
search_info.offset = checked_prefix_size;
auto it = hash_info_.upper_bound(search_info);
if (it != hash_info_.begin()) {
- it--;
+ --it;
}
if (it != hash_info_.end() && it->offset <= checked_prefix_size &&
it->offset + narrow_cast<int64>(it->size) > checked_prefix_size) {
@@ -385,7 +443,7 @@ Result<FileLoader::CheckInfo> FileDownloader::check_loop(int64 checked_prefix_si
}
end_offset = ready_prefix_size;
}
- size_t size = narrow_cast<size_t>(end_offset - begin_offset);
+ auto size = narrow_cast<size_t>(end_offset - begin_offset);
auto slice = BufferSlice(size);
TRY_STATUS(acquire_fd());
TRY_RESULT(read_size, fd_.pread(slice.as_slice(), begin_offset));
@@ -408,11 +466,9 @@ Result<FileLoader::CheckInfo> FileDownloader::check_loop(int64 checked_prefix_si
}
if (!has_hash_query_) {
has_hash_query_ = true;
- auto query =
- telegram_api::upload_getFileHashes(remote_.as_input_file_location(), narrow_cast<int32>(checked_prefix_size));
- auto net_query = G()->net_query_creator().create(
- create_storer(query), remote_.get_dc_id(),
- is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download, NetQuery::AuthFlag::On);
+ auto query = telegram_api::upload_getFileHashes(remote_.as_input_file_location(), checked_prefix_size);
+ auto net_query_type = is_small_ ? NetQuery::Type::DownloadSmall : NetQuery::Type::Download;
+ auto net_query = G()->net_query_creator().create(query, {}, remote_.get_dc_id(), net_query_type);
info.queries.push_back(std::move(net_query));
break;
}
@@ -423,6 +479,7 @@ Result<FileLoader::CheckInfo> FileDownloader::check_loop(int64 checked_prefix_si
info.checked_prefix_size = checked_prefix_size;
return std::move(info);
}
+
void FileDownloader::add_hash_info(const std::vector<telegram_api::object_ptr<telegram_api::fileHash>> &hashes) {
for (auto &hash : hashes) {
//LOG(ERROR) << "ADD HASH " << hash->offset_ << "->" << hash->limit_;
@@ -448,11 +505,9 @@ void FileDownloader::try_release_fd() {
Status FileDownloader::acquire_fd() {
if (fd_.empty()) {
if (path_.empty()) {
- TRY_RESULT(file_path, open_temp_file(remote_.file_type_));
- std::tie(fd_, path_) = std::move(file_path);
+ TRY_RESULT_ASSIGN(std::tie(fd_, path_), open_temp_file(remote_.file_type_));
} else {
- TRY_RESULT(fd, FileFd::open(path_, (only_check_ ? 0 : FileFd::Write) | FileFd::Read));
- fd_ = std::move(fd);
+ TRY_RESULT_ASSIGN(fd_, FileFd::open(path_, (only_check_ ? 0 : FileFd::Write) | FileFd::Read));
}
}
return Status::OK();
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.h
index 6a45ca567b..6d0ef2adac 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileDownloader.h
@@ -1,19 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/telegram_api.h"
-
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
+#include "td/telegram/files/FileEncryptionKey.h"
#include "td/telegram/files/FileLoader.h"
#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/telegram_api.h"
#include "td/utils/common.h"
#include "td/utils/port/FileFd.h"
@@ -24,19 +22,19 @@
#include <utility>
namespace td {
-class FileDownloader : public FileLoader {
+class FileDownloader final : public FileLoader {
public:
class Callback : public FileLoader::Callback {
public:
virtual void on_start_download() = 0;
- virtual void on_partial_download(const PartialLocalFileLocation &partial_local, int64 ready_size) = 0;
- virtual void on_ok(const FullLocalFileLocation &full_local, int64 size) = 0;
+ virtual void on_partial_download(PartialLocalFileLocation partial_local, int64 ready_size, int64 size) = 0;
+ virtual void on_ok(FullLocalFileLocation full_local, int64 size, bool is_new) = 0;
virtual void on_error(Status status) = 0;
};
FileDownloader(const FullRemoteFileLocation &remote, const LocalFileLocation &local, int64 size, string name,
- const FileEncryptionKey &encryption_key, bool is_small, bool search_file,
- std::unique_ptr<Callback> callback);
+ const FileEncryptionKey &encryption_key, bool is_small, bool need_search_file, int64 offset,
+ int64 limit, unique_ptr<Callback> callback);
// Should just implement all parent pure virtual methods.
// Must not call any of them...
@@ -48,7 +46,7 @@ class FileDownloader : public FileLoader {
int64 size_;
string name_;
FileEncryptionKey encryption_key_;
- std::unique_ptr<Callback> callback_;
+ unique_ptr<Callback> callback_;
bool only_check_{false};
string path_;
@@ -57,7 +55,9 @@ class FileDownloader : public FileLoader {
int32 next_part_ = 0;
bool next_part_stop_ = false;
bool is_small_;
- bool search_file_{false};
+ bool need_search_file_{false};
+ int64 offset_;
+ int64 limit_;
bool use_cdn_ = false;
DcId cdn_dc_id_;
@@ -80,21 +80,24 @@ class FileDownloader : public FileLoader {
std::set<HashInfo> hash_info_;
bool has_hash_query_ = false;
- Result<FileInfo> init() override TD_WARN_UNUSED_RESULT;
- Status on_ok(int64 size) override TD_WARN_UNUSED_RESULT;
- void on_error(Status status) override;
- Result<bool> should_restart_part(Part part, NetQueryPtr &net_query) override TD_WARN_UNUSED_RESULT;
- Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count) override TD_WARN_UNUSED_RESULT;
- Result<size_t> process_part(Part part, NetQueryPtr net_query) override TD_WARN_UNUSED_RESULT;
- void on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready, int64 ready_size) override;
- FileLoader::Callback *get_callback() override;
- Status process_check_query(NetQueryPtr net_query) override;
- Result<CheckInfo> check_loop(int64 checked_prefix_size, int64 ready_prefix_size, bool is_ready) override;
+ Result<FileInfo> init() final TD_WARN_UNUSED_RESULT;
+ Status on_ok(int64 size) final TD_WARN_UNUSED_RESULT;
+ void on_error(Status status) final;
+ Result<bool> should_restart_part(Part part, NetQueryPtr &net_query) final TD_WARN_UNUSED_RESULT;
+ Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count,
+ int64 streaming_offset) final TD_WARN_UNUSED_RESULT;
+ Result<size_t> process_part(Part part, NetQueryPtr net_query) final TD_WARN_UNUSED_RESULT;
+ void on_progress(Progress progress) final;
+ FileLoader::Callback *get_callback() final;
+ Status process_check_query(NetQueryPtr net_query) final;
+ Result<CheckInfo> check_loop(int64 checked_prefix_size, int64 ready_prefix_size, bool is_ready) final;
void add_hash_info(const std::vector<telegram_api::object_ptr<telegram_api::fileHash>> &hashes);
bool keep_fd_ = false;
- void keep_fd_flag(bool keep_fd) override;
+ void keep_fd_flag(bool keep_fd) final;
void try_release_fd();
Status acquire_fd() TD_WARN_UNUSED_RESULT;
+
+ Status check_net_query(NetQueryPtr &net_query);
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileEncryptionKey.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileEncryptionKey.cpp
new file mode 100644
index 0000000000..834706a75a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileEncryptionKey.cpp
@@ -0,0 +1,101 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileEncryptionKey.h"
+
+#include "td/telegram/SecureStorage.h"
+
+#include "td/utils/as.h"
+#include "td/utils/crypto.h"
+#include "td/utils/Random.h"
+
+namespace td {
+
+FileEncryptionKey::FileEncryptionKey(Slice key, Slice iv) : key_iv_(key.size() + iv.size(), '\0'), type_(Type::Secret) {
+ if (key.size() != 32 || iv.size() != 32) {
+ LOG(ERROR) << "Wrong key/iv sizes: " << key.size() << " " << iv.size();
+ type_ = Type::None;
+ return;
+ }
+ CHECK(key_iv_.size() == 64);
+ MutableSlice(key_iv_).copy_from(key);
+ MutableSlice(key_iv_).substr(key.size()).copy_from(iv);
+}
+
+FileEncryptionKey::FileEncryptionKey(const secure_storage::Secret &secret) : type_(Type::Secure) {
+ key_iv_ = secret.as_slice().str();
+}
+
+FileEncryptionKey FileEncryptionKey::create() {
+ FileEncryptionKey res;
+ res.key_iv_.resize(64);
+ Random::secure_bytes(res.key_iv_);
+ res.type_ = Type::Secret;
+ return res;
+}
+FileEncryptionKey FileEncryptionKey::create_secure_key() {
+ return FileEncryptionKey(secure_storage::Secret::create_new());
+}
+
+const UInt256 &FileEncryptionKey::key() const {
+ CHECK(is_secret());
+ CHECK(key_iv_.size() == 64);
+ return *reinterpret_cast<const UInt256 *>(key_iv_.data());
+}
+Slice FileEncryptionKey::key_slice() const {
+ CHECK(is_secret());
+ CHECK(key_iv_.size() == 64);
+ return Slice(key_iv_.data(), 32);
+}
+secure_storage::Secret FileEncryptionKey::secret() const {
+ CHECK(is_secure());
+ return secure_storage::Secret::create(Slice(key_iv_).truncate(32)).move_as_ok();
+}
+
+bool FileEncryptionKey::has_value_hash() const {
+ CHECK(is_secure());
+ return key_iv_.size() > secure_storage::Secret::size();
+}
+
+void FileEncryptionKey::set_value_hash(const secure_storage::ValueHash &value_hash) {
+ key_iv_.resize(secure_storage::Secret::size() + value_hash.as_slice().size());
+ MutableSlice(key_iv_).remove_prefix(secure_storage::Secret::size()).copy_from(value_hash.as_slice());
+}
+
+secure_storage::ValueHash FileEncryptionKey::value_hash() const {
+ CHECK(has_value_hash());
+ return secure_storage::ValueHash::create(Slice(key_iv_).remove_prefix(secure_storage::Secret::size())).move_as_ok();
+}
+
+UInt256 &FileEncryptionKey::mutable_iv() {
+ CHECK(is_secret());
+ CHECK(key_iv_.size() == 64);
+ return *reinterpret_cast<UInt256 *>(&key_iv_[0] + 32);
+}
+Slice FileEncryptionKey::iv_slice() const {
+ CHECK(is_secret());
+ CHECK(key_iv_.size() == 64);
+ return Slice(key_iv_.data() + 32, 32);
+}
+
+int32 FileEncryptionKey::calc_fingerprint() const {
+ CHECK(is_secret());
+ char buf[16];
+ md5(key_iv_, {buf, sizeof(buf)});
+ return as<int32>(buf) ^ as<int32>(buf + 4);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const FileEncryptionKey &key) {
+ if (key.is_secret()) {
+ return string_builder << "SecretKey{" << key.size() << "}";
+ }
+ if (key.is_secure()) {
+ return string_builder << "SecureKey{" << key.size() << "}";
+ }
+ return string_builder << "NoKey{}";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileEncryptionKey.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileEncryptionKey.h
new file mode 100644
index 0000000000..005c254b6e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileEncryptionKey.h
@@ -0,0 +1,104 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/UInt.h"
+
+namespace td {
+
+namespace secure_storage {
+class Secret;
+class ValueHash;
+} // namespace secure_storage
+
+struct FileEncryptionKey {
+ enum class Type : int32 { None, Secret, Secure };
+
+ FileEncryptionKey() = default;
+
+ FileEncryptionKey(Slice key, Slice iv);
+
+ explicit FileEncryptionKey(const secure_storage::Secret &secret);
+
+ bool is_secret() const {
+ return type_ == Type::Secret;
+ }
+
+ bool is_secure() const {
+ return type_ == Type::Secure;
+ }
+
+ static FileEncryptionKey create();
+
+ static FileEncryptionKey create_secure_key();
+
+ const UInt256 &key() const;
+
+ Slice key_slice() const;
+
+ secure_storage::Secret secret() const;
+
+ bool has_value_hash() const;
+
+ void set_value_hash(const secure_storage::ValueHash &value_hash);
+
+ secure_storage::ValueHash value_hash() const;
+
+ UInt256 &mutable_iv();
+
+ Slice iv_slice() const;
+
+ int32 calc_fingerprint() const;
+
+ bool empty() const {
+ return key_iv_.empty();
+ }
+
+ size_t size() const {
+ return key_iv_.size();
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ td::store(key_iv_, storer);
+ }
+ template <class ParserT>
+ void parse(Type type, ParserT &parser) {
+ td::parse(key_iv_, parser);
+ if (key_iv_.empty()) {
+ type_ = Type::None;
+ } else {
+ if (type_ == Type::Secure) {
+ if (key_iv_.size() != 64) {
+ LOG(ERROR) << "Have wrong key size " << key_iv_.size();
+ }
+ }
+ type_ = type;
+ }
+ }
+
+ friend bool operator==(const FileEncryptionKey &lhs, const FileEncryptionKey &rhs) {
+ return lhs.key_iv_ == rhs.key_iv_;
+ }
+
+ private:
+ string key_iv_; // TODO wrong alignment is possible
+ Type type_ = Type::None;
+};
+
+inline bool operator!=(const FileEncryptionKey &lhs, const FileEncryptionKey &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const FileEncryptionKey &key);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.cpp
index 939803d8a8..7d597851c4 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,44 +7,24 @@
#include "td/telegram/files/FileFromBytes.h"
#include "td/telegram/files/FileLoaderUtils.h"
-#include "td/telegram/Global.h"
#include "td/utils/common.h"
#include "td/utils/misc.h"
-#include <tuple>
-
namespace td {
-FileFromBytes::FileFromBytes(FileType type, BufferSlice bytes, string name, std::unique_ptr<Callback> callback)
+FileFromBytes::FileFromBytes(FileType type, BufferSlice bytes, string name, unique_ptr<Callback> callback)
: type_(type), bytes_(std::move(bytes)), name_(std::move(name)), callback_(std::move(callback)) {
}
void FileFromBytes::wakeup() {
- auto r_fd_path = open_temp_file(type_);
- if (r_fd_path.is_error()) {
- return callback_->on_error(r_fd_path.move_as_error());
- }
- FileFd fd;
- string path;
- std::tie(fd, path) = r_fd_path.move_as_ok();
-
- auto r_size = fd.write(bytes_.as_slice());
- if (r_size.is_error()) {
- return callback_->on_error(r_size.move_as_error());
- }
- fd.close();
- auto size = r_size.ok();
- if (size != bytes_.size()) {
- return callback_->on_error(Status::Error("Failed to write bytes to the file"));
- }
-
- auto dir = get_files_dir(type_);
- auto r_perm_path = create_from_temp(path, dir, name_);
- if (r_perm_path.is_error()) {
- return callback_->on_error(r_perm_path.move_as_error());
+ auto size = narrow_cast<int64>(bytes_.size());
+ auto r_result = save_file_bytes(type_, std::move(bytes_), name_);
+ if (r_result.is_error()) {
+ callback_->on_error(r_result.move_as_error());
+ } else {
+ callback_->on_ok(r_result.ok(), size);
}
- callback_->on_ok(FullLocalFileLocation(type_, r_perm_path.move_as_ok(), 0), narrow_cast<int64>(bytes_.size()));
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.h
index 23f4b2ff4f..a3f5c978c5 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileFromBytes.h
@@ -1,15 +1,16 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-
#include "td/telegram/files/FileLoader.h"
#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileType.h"
+
+#include "td/actor/actor.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
@@ -17,7 +18,7 @@
#include "td/utils/Status.h"
namespace td {
-class FileFromBytes : public FileLoaderActor {
+class FileFromBytes final : public FileLoaderActor {
public:
class Callback {
public:
@@ -29,7 +30,7 @@ class FileFromBytes : public FileLoaderActor {
virtual void on_error(Status status) = 0;
};
- FileFromBytes(FileType type, BufferSlice bytes, string name, std::unique_ptr<Callback> callback);
+ FileFromBytes(FileType type, BufferSlice bytes, string name, unique_ptr<Callback> callback);
// Should just implement all parent pure virtual methods.
// Must not call any of them...
@@ -38,17 +39,17 @@ class FileFromBytes : public FileLoaderActor {
BufferSlice bytes_;
string name_;
- std::unique_ptr<Callback> callback_;
+ unique_ptr<Callback> callback_;
FileFd fd_;
string path_;
- void wakeup() override;
- void set_resource_manager(ActorShared<ResourceManager>) override {
+ void wakeup() final;
+ void set_resource_manager(ActorShared<ResourceManager>) final {
}
- void update_priority(int8 priority) override {
+ void update_priority(int8 priority) final {
}
- void update_resources(const ResourceState &other) override {
+ void update_resources(const ResourceState &other) final {
}
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.cpp
index 7b949d73a2..b541fe59e4 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.cpp
@@ -1,32 +1,46 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileGcParameters.h"
-#include "td/telegram/ConfigShared.h"
#include "td/telegram/Global.h"
+#include "td/utils/format.h"
+#include "td/utils/misc.h"
+
namespace td {
+
FileGcParameters::FileGcParameters(int64 size, int32 ttl, int32 count, int32 immunity_delay,
vector<FileType> file_types, vector<DialogId> owner_dialog_ids,
vector<DialogId> exclude_owner_dialog_ids, int32 dialog_limit)
- : file_types(std::move(file_types))
- , owner_dialog_ids(std::move(owner_dialog_ids))
- , exclude_owner_dialog_ids(std::move(exclude_owner_dialog_ids))
- , dialog_limit(dialog_limit) {
- auto &config = G()->shared_config();
- this->max_files_size =
- size >= 0 ? size : static_cast<int64>(config.get_option_integer("storage_max_files_size", 100 << 10)) << 10;
+ : file_types_(std::move(file_types))
+ , owner_dialog_ids_(std::move(owner_dialog_ids))
+ , exclude_owner_dialog_ids_(std::move(exclude_owner_dialog_ids))
+ , dialog_limit_(dialog_limit) {
+ max_files_size_ = size >= 0 ? size : G()->get_option_integer("storage_max_files_size", 100 << 10) << 10;
- this->max_time_from_last_access =
- ttl >= 0 ? ttl : config.get_option_integer("storage_max_time_from_last_access", 60 * 60 * 23);
+ max_time_from_last_access_ =
+ ttl >= 0 ? ttl : narrow_cast<int32>(G()->get_option_integer("storage_max_time_from_last_access", 60 * 60 * 23));
- this->max_file_count = count >= 0 ? count : config.get_option_integer("storage_max_file_count", 40000);
+ max_file_count_ = count >= 0 ? count : narrow_cast<int32>(G()->get_option_integer("storage_max_file_count", 40000));
- this->immunity_delay =
- immunity_delay >= 0 ? immunity_delay : config.get_option_integer("storage_immunity_delay", 60 * 60);
+ immunity_delay_ = immunity_delay >= 0
+ ? immunity_delay
+ : narrow_cast<int32>(G()->get_option_integer("storage_immunity_delay", 60 * 60));
}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const FileGcParameters &parameters) {
+ return string_builder << "FileGcParameters[" << tag("max_files_size", parameters.max_files_size_)
+ << tag("max_time_from_last_access", parameters.max_time_from_last_access_)
+ << tag("max_file_count", parameters.max_file_count_)
+ << tag("immunity_delay", parameters.immunity_delay_)
+ << tag("file_types", parameters.file_types_)
+ << tag("owner_dialog_ids", parameters.owner_dialog_ids_)
+ << tag("exclude_owner_dialog_ids", parameters.exclude_owner_dialog_ids_)
+ << tag("dialog_limit", parameters.dialog_limit_) << ']';
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.h
index a0c3c35524..b246c25728 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileGcParameters.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,9 +7,10 @@
#pragma once
#include "td/telegram/DialogId.h"
-#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileType.h"
#include "td/utils/common.h"
+#include "td/utils/StringBuilder.h"
namespace td {
@@ -19,16 +20,18 @@ struct FileGcParameters {
FileGcParameters(int64 size, int32 ttl, int32 count, int32 immunity_delay, vector<FileType> file_types,
vector<DialogId> owner_dialog_ids, vector<DialogId> exclude_owner_dialog_ids, int32 dialog_limit);
- int64 max_files_size;
- uint32 max_time_from_last_access;
- uint32 max_file_count;
- uint32 immunity_delay;
+ int64 max_files_size_;
+ uint32 max_time_from_last_access_;
+ uint32 max_file_count_;
+ uint32 immunity_delay_;
- vector<FileType> file_types;
- vector<DialogId> owner_dialog_ids;
- vector<DialogId> exclude_owner_dialog_ids;
+ vector<FileType> file_types_;
+ vector<DialogId> owner_dialog_ids_;
+ vector<DialogId> exclude_owner_dialog_ids_;
- int32 dialog_limit;
+ int32 dialog_limit_;
};
+StringBuilder &operator<<(StringBuilder &string_builder, const FileGcParameters &parameters);
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.cpp
index 35dce38e9c..cefdeba4b2 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.cpp
@@ -1,14 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileGcWorker.h"
+#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
+#include "td/telegram/TdParameters.h"
+#include "td/utils/algorithm.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
@@ -20,26 +24,19 @@
#include <array>
namespace td {
-void FileGcWorker::do_remove_file(const FullFileInfo &info) {
- // LOG(WARNING) << "Gc remove file: " << tag("path", file) << tag("mtime", stat.mtime_nsec_ / 1000000000)
- // << tag("atime", stat.atime_nsec_ / 1000000000);
- // TODO: remove file from db too.
- auto status = unlink(info.path);
- LOG_IF(WARNING, status.is_error()) << "Failed to unlink file during files gc: " << status;
- send_closure(G()->file_manager(), &FileManager::on_file_unlink,
- FullLocalFileLocation(info.file_type, info.path, info.mtime_nsec));
-}
+
+int VERBOSITY_NAME(file_gc) = VERBOSITY_NAME(INFO);
void FileGcWorker::run_gc(const FileGcParameters &parameters, std::vector<FullFileInfo> files,
- Promise<FileStats> promise) {
+ Promise<FileGcResult> promise) {
auto begin_time = Time::now();
- LOG(INFO) << "Start files gc";
+ VLOG(file_gc) << "Start files GC with " << parameters;
// quite stupid implementations
// needs a lot of memory
// may write something more clever, but i will need at least 2 passes over the files
// TODO update atime for all files in android (?)
- std::array<bool, file_type_size> immune_types{{false}};
+ std::array<bool, MAX_FILE_TYPE> immune_types{{false}};
if (G()->parameters().use_file_db) {
// immune by default
@@ -47,13 +44,21 @@ void FileGcWorker::run_gc(const FileGcParameters &parameters, std::vector<FullFi
immune_types[narrow_cast<size_t>(FileType::ProfilePhoto)] = true;
immune_types[narrow_cast<size_t>(FileType::Thumbnail)] = true;
immune_types[narrow_cast<size_t>(FileType::Wallpaper)] = true;
+ immune_types[narrow_cast<size_t>(FileType::Background)] = true;
+ immune_types[narrow_cast<size_t>(FileType::Ringtone)] = true;
}
- if (!parameters.file_types.empty()) {
+ if (!parameters.file_types_.empty()) {
std::fill(immune_types.begin(), immune_types.end(), true);
- for (auto file_type : parameters.file_types) {
+ for (auto file_type : parameters.file_types_) {
immune_types[narrow_cast<size_t>(file_type)] = false;
}
+ for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
+ auto main_file_type = narrow_cast<size_t>(get_main_file_type(static_cast<FileType>(i)));
+ if (!immune_types[main_file_type]) {
+ immune_types[i] = false;
+ }
+ }
}
if (G()->parameters().use_file_db) {
@@ -77,66 +82,78 @@ void FileGcWorker::run_gc(const FileGcParameters &parameters, std::vector<FullFi
total_size += info.size;
}
- FileStats new_stats;
- new_stats.split_by_owner_dialog_id = parameters.dialog_limit != 0;
+ FileStats new_stats(false, parameters.dialog_limit_ != 0);
+ FileStats removed_stats(false, parameters.dialog_limit_ != 0);
+
+ auto do_remove_file = [&removed_stats](const FullFileInfo &info) {
+ removed_stats.add_copy(info);
+ auto status = unlink(info.path);
+ LOG_IF(WARNING, status.is_error()) << "Failed to unlink file \"" << info.path << "\" during files GC: " << status;
+ send_closure(G()->file_manager(), &FileManager::on_file_unlink,
+ FullLocalFileLocation(info.file_type, info.path, info.mtime_nsec));
+ };
- // Remove all files with atime > now - max_time_from_last_access
double now = Clocks::system();
- files.erase(
- std::remove_if(
- files.begin(), files.end(),
- [&](const FullFileInfo &info) {
- if (immune_types[narrow_cast<size_t>(info.file_type)]) {
- type_immunity_ignored_cnt++;
- new_stats.add(FullFileInfo(info));
- return true;
- }
- if (std::find(parameters.exclude_owner_dialog_ids.begin(), parameters.exclude_owner_dialog_ids.end(),
- info.owner_dialog_id) != parameters.exclude_owner_dialog_ids.end()) {
- exclude_owner_dialog_id_ignored_cnt++;
- new_stats.add(FullFileInfo(info));
- return true;
- }
- if (!parameters.owner_dialog_ids.empty() &&
- std::find(parameters.owner_dialog_ids.begin(), parameters.owner_dialog_ids.end(),
- info.owner_dialog_id) == parameters.owner_dialog_ids.end()) {
- owner_dialog_id_ignored_cnt++;
- new_stats.add(FullFileInfo(info));
- return true;
- }
- if (static_cast<double>(info.mtime_nsec / 1000000000) > now - parameters.immunity_delay) {
- // new files are immune to gc.
- time_immunity_ignored_cnt++;
- new_stats.add(FullFileInfo(info));
- return true;
- }
-
- if (static_cast<double>(info.atime_nsec / 1000000000) < now - parameters.max_time_from_last_access) {
- do_remove_file(info);
- total_removed_size += info.size;
- remove_by_atime_cnt++;
- return true;
- }
- return false;
- }),
- files.end());
+
+ // Keep all immune files
+ // Remove all files with (atime > now - max_time_from_last_access)
+ td::remove_if(files, [&](const FullFileInfo &info) {
+ if (token_) {
+ return false;
+ }
+ if (immune_types[narrow_cast<size_t>(info.file_type)]) {
+ type_immunity_ignored_cnt++;
+ new_stats.add_copy(info);
+ return true;
+ }
+ if (td::contains(parameters.exclude_owner_dialog_ids_, info.owner_dialog_id)) {
+ exclude_owner_dialog_id_ignored_cnt++;
+ new_stats.add_copy(info);
+ return true;
+ }
+ if (!parameters.owner_dialog_ids_.empty() && !td::contains(parameters.owner_dialog_ids_, info.owner_dialog_id)) {
+ owner_dialog_id_ignored_cnt++;
+ new_stats.add_copy(info);
+ return true;
+ }
+ if (static_cast<double>(info.mtime_nsec) * 1e-9 > now - parameters.immunity_delay_) {
+ // new files are immune to GC
+ time_immunity_ignored_cnt++;
+ new_stats.add_copy(info);
+ return true;
+ }
+
+ if (static_cast<double>(info.atime_nsec) * 1e-9 < now - parameters.max_time_from_last_access_) {
+ do_remove_file(info);
+ total_removed_size += info.size;
+ remove_by_atime_cnt++;
+ return true;
+ }
+ return false;
+ });
+ if (token_) {
+ return promise.set_error(Global::request_aborted_error());
+ }
// sort by max(atime, mtime)
std::sort(files.begin(), files.end(), [](const auto &a, const auto &b) { return a.atime_nsec < b.atime_nsec; });
- // 1. Total memory must be less than max_memory
- // 2. Total file count must be less than MAX_FILE_COUNT
+ // 1. Total size must be less than parameters.max_files_size_
+ // 2. Total file count must be less than parameters.max_file_count_
size_t remove_count = 0;
- if (files.size() > parameters.max_file_count) {
- remove_count = files.size() - parameters.max_file_count;
+ if (files.size() > parameters.max_file_count_) {
+ remove_count = files.size() - parameters.max_file_count_;
}
- int64 remove_size = -parameters.max_files_size;
+ int64 remove_size = -parameters.max_files_size_;
for (auto &file : files) {
remove_size += file.size;
}
size_t pos = 0;
while (pos < files.size() && (remove_count > 0 || remove_size > 0)) {
+ if (token_) {
+ return promise.set_error(Global::request_aborted_error());
+ }
if (remove_count > 0) {
remove_by_count_cnt++;
} else {
@@ -154,21 +171,29 @@ void FileGcWorker::run_gc(const FileGcParameters &parameters, std::vector<FullFi
}
while (pos < files.size()) {
- new_stats.add(std::move(files[pos]));
+ new_stats.add_copy(files[pos]);
pos++;
}
auto end_time = Time::now();
- LOG(INFO) << "Finish files gc: " << tag("time", end_time - begin_time) << tag("total", file_cnt)
- << tag("removed", remove_by_atime_cnt + remove_by_count_cnt + remove_by_size_cnt)
- << tag("total_size", format::as_size(total_size))
- << tag("total_removed_size", format::as_size(total_removed_size)) << tag("by_atime", remove_by_atime_cnt)
- << tag("by_count", remove_by_count_cnt) << tag("by_size", remove_by_size_cnt)
- << tag("type_immunity", type_immunity_ignored_cnt) << tag("time_immunity", time_immunity_ignored_cnt)
- << tag("owner_dialog_id_immunity", owner_dialog_id_ignored_cnt)
- << tag("exclude_owner_dialog_id_immunity", exclude_owner_dialog_id_ignored_cnt);
+ VLOG(file_gc) << "Finish files GC: " << tag("time", end_time - begin_time) << tag("total", file_cnt)
+ << tag("removed", remove_by_atime_cnt + remove_by_count_cnt + remove_by_size_cnt)
+ << tag("total_size", format::as_size(total_size))
+ << tag("total_removed_size", format::as_size(total_removed_size))
+ << tag("by_atime", remove_by_atime_cnt) << tag("by_count", remove_by_count_cnt)
+ << tag("by_size", remove_by_size_cnt) << tag("type_immunity", type_immunity_ignored_cnt)
+ << tag("time_immunity", time_immunity_ignored_cnt)
+ << tag("owner_dialog_id_immunity", owner_dialog_id_ignored_cnt)
+ << tag("exclude_owner_dialog_id_immunity", exclude_owner_dialog_id_ignored_cnt);
+ if (end_time - begin_time > 1.0) {
+ LOG(WARNING) << "Finish file GC: " << tag("time", end_time - begin_time) << tag("total", file_cnt)
+ << tag("removed", remove_by_atime_cnt + remove_by_count_cnt + remove_by_size_cnt)
+ << tag("total_size", format::as_size(total_size))
+ << tag("total_removed_size", format::as_size(total_removed_size));
+ }
- promise.set_value(std::move(new_stats));
+ promise.set_value({std::move(new_stats), std::move(removed_stats)});
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.h
index 7e83d9fa98..e7da77f18b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileGcWorker.h
@@ -1,28 +1,38 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/files/FileGcParameters.h"
-#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/FileStats.h"
+#include "td/actor/actor.h"
+
+#include "td/utils/CancellationToken.h"
+#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
+
namespace td {
-class FileGcWorker : public Actor {
+
+extern int VERBOSITY_NAME(file_gc);
+
+struct FileGcResult {
+ FileStats kept_file_stats_;
+ FileStats removed_file_stats_;
+};
+
+class FileGcWorker final : public Actor {
public:
- explicit FileGcWorker(ActorShared<> parent) : parent_(std::move(parent)) {
+ FileGcWorker(ActorShared<> parent, CancellationToken token) : parent_(std::move(parent)), token_(std::move(token)) {
}
- void run_gc(const FileGcParameters &parameters, std::vector<FullFileInfo> files, Promise<FileStats> promise);
+ void run_gc(const FileGcParameters &parameters, std::vector<FullFileInfo> files, Promise<FileGcResult> promise);
private:
ActorShared<> parent_;
- void do_remove_file(const FullFileInfo &info);
+ CancellationToken token_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.cpp
index 22472a248f..b56f4bc5df 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.cpp
@@ -1,73 +1,80 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileGenerateManager.h"
-#include "td/telegram/td_api.h"
-
#include "td/telegram/files/FileId.h"
#include "td/telegram/files/FileLoaderUtils.h"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/Td.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
#include "td/utils/common.h"
+#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/Parser.h"
+#include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h"
+#include "td/utils/port/Stat.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include <cmath>
+#include <memory>
#include <utility>
namespace td {
class FileGenerateActor : public Actor {
public:
- FileGenerateActor() = default;
- FileGenerateActor(const FileGenerateActor &) = delete;
- FileGenerateActor &operator=(const FileGenerateActor &) = delete;
- FileGenerateActor(FileGenerateActor &&) = delete;
- FileGenerateActor &operator=(FileGenerateActor &&) = delete;
- ~FileGenerateActor() override = default;
- virtual void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) = 0;
+ virtual void file_generate_write_part(int64 offset, string data, Promise<> promise) {
+ LOG(ERROR) << "Receive unexpected file_generate_write_part";
+ }
+ virtual void file_generate_progress(int64 expected_size, int64 local_prefix_size, Promise<> promise) = 0;
virtual void file_generate_finish(Status status, Promise<> promise) = 0;
};
-class FileDownloadGenerateActor : public FileGenerateActor {
+class FileDownloadGenerateActor final : public FileGenerateActor {
public:
- FileDownloadGenerateActor(FileType file_type, FileId file_id, std::unique_ptr<FileGenerateCallback> callback,
+ FileDownloadGenerateActor(FileType file_type, FileId file_id, unique_ptr<FileGenerateCallback> callback,
ActorShared<> parent)
: file_type_(file_type), file_id_(file_id), callback_(std::move(callback)), parent_(std::move(parent)) {
}
- void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) override {
+ void file_generate_progress(int64 expected_size, int64 local_prefix_size, Promise<> promise) final {
UNREACHABLE();
}
- void file_generate_finish(Status status, Promise<> promise) override {
+ void file_generate_finish(Status status, Promise<> promise) final {
UNREACHABLE();
}
private:
FileType file_type_;
FileId file_id_;
- std::unique_ptr<FileGenerateCallback> callback_;
+ unique_ptr<FileGenerateCallback> callback_;
ActorShared<> parent_;
- void start_up() override {
- LOG(INFO) << "DOWNLOAD " << file_id_;
- class Callback : public FileManager::DownloadCallback {
+ void start_up() final {
+ LOG(INFO) << "Generate by downloading " << file_id_;
+ class Callback final : public FileManager::DownloadCallback {
public:
explicit Callback(ActorId<FileDownloadGenerateActor> parent) : parent_(std::move(parent)) {
}
// TODO: upload during download
- void on_download_ok(FileId file_id) override {
+ void on_download_ok(FileId file_id) final {
send_closure(parent_, &FileDownloadGenerateActor::on_download_ok);
}
- void on_download_error(FileId file_id, Status error) override {
+ void on_download_error(FileId file_id, Status error) final {
send_closure(parent_, &FileDownloadGenerateActor::on_download_error, std::move(error));
}
@@ -75,25 +82,30 @@ class FileDownloadGenerateActor : public FileGenerateActor {
ActorId<FileDownloadGenerateActor> parent_;
};
- send_closure(G()->file_manager(), &FileManager::download, file_id_, std::make_unique<Callback>(actor_id(this)), 1);
+ send_closure(G()->file_manager(), &FileManager::download, file_id_, std::make_shared<Callback>(actor_id(this)), 1,
+ FileManager::KEEP_DOWNLOAD_OFFSET, FileManager::KEEP_DOWNLOAD_LIMIT,
+ Promise<td_api::object_ptr<td_api::file>>());
}
- void hangup() override {
- send_closure(G()->file_manager(), &FileManager::download, file_id_, nullptr, 0);
+ void hangup() final {
+ send_closure(G()->file_manager(), &FileManager::download, file_id_, nullptr, 0, FileManager::KEEP_DOWNLOAD_OFFSET,
+ FileManager::KEEP_DOWNLOAD_LIMIT, Promise<td_api::object_ptr<td_api::file>>());
stop();
}
void on_download_ok() {
- send_lambda(G()->file_manager(), [file_type = file_type_, file_id = file_id_, callback = std::move(callback_)] {
- auto file_view = G()->td().get_actor_unsafe()->file_manager_->get_file_view(file_id);
- if (file_view.has_local_location()) {
- auto location = file_view.local_location();
- location.file_type_ = file_type;
- callback->on_ok(location);
- } else {
- LOG(ERROR) << "Expected to have local location";
- callback->on_error(Status::Error("Unknown"));
- }
- });
+ send_lambda(G()->file_manager(),
+ [file_type = file_type_, file_id = file_id_, callback = std::move(callback_)]() mutable {
+ auto file_view = G()->td().get_actor_unsafe()->file_manager_->get_file_view(file_id);
+ CHECK(!file_view.empty());
+ if (file_view.has_local_location()) {
+ auto location = file_view.local_location();
+ location.file_type_ = file_type;
+ callback->on_ok(std::move(location));
+ } else {
+ LOG(ERROR) << "Expected to have local location";
+ callback->on_error(Status::Error(500, "Unknown"));
+ }
+ });
stop();
}
void on_download_error(Status error) {
@@ -102,11 +114,160 @@ class FileDownloadGenerateActor : public FileGenerateActor {
}
};
-class FileExternalGenerateActor : public FileGenerateActor {
+class WebFileDownloadGenerateActor final : public FileGenerateActor {
+ public:
+ WebFileDownloadGenerateActor(string conversion, unique_ptr<FileGenerateCallback> callback, ActorShared<> parent)
+ : conversion_(std::move(conversion)), callback_(std::move(callback)), parent_(std::move(parent)) {
+ }
+ void file_generate_progress(int64 expected_size, int64 local_prefix_size, Promise<> promise) final {
+ UNREACHABLE();
+ }
+ void file_generate_finish(Status status, Promise<> promise) final {
+ UNREACHABLE();
+ }
+
+ private:
+ string conversion_;
+ unique_ptr<FileGenerateCallback> callback_;
+ ActorShared<> parent_;
+ string file_name_;
+
+ class Callback final : public NetQueryCallback {
+ ActorId<WebFileDownloadGenerateActor> parent_;
+
+ public:
+ explicit Callback(ActorId<WebFileDownloadGenerateActor> parent) : parent_(parent) {
+ }
+
+ void on_result(NetQueryPtr query) final {
+ send_closure(parent_, &WebFileDownloadGenerateActor::on_result, std::move(query));
+ }
+
+ void hangup_shared() final {
+ send_closure(parent_, &WebFileDownloadGenerateActor::hangup_shared);
+ }
+ };
+ ActorOwn<NetQueryCallback> net_callback_;
+
+ Result<tl_object_ptr<telegram_api::InputWebFileLocation>> parse_conversion() {
+ auto parts = full_split(Slice(conversion_), '#');
+ if (parts.size() <= 2 || !parts[0].empty() || !parts.back().empty()) {
+ return Status::Error("Wrong conversion");
+ }
+
+ if (parts.size() == 6 && parts[1] == "audio_t") {
+ // music thumbnail
+ if (parts[2].empty() && parts[3].empty()) {
+ return Status::Error("Title or performer must be non-empty");
+ }
+ if (parts[4] != "0" && parts[4] != "1") {
+ return Status::Error("Invalid conversion");
+ }
+
+ bool is_small = parts[4][0] == '1';
+ file_name_ = PSTRING() << "Album cover " << (is_small ? "thumbnail " : "") << "for " << parts[3] << " - "
+ << parts[2] << ".jpg";
+
+ int32 flags = telegram_api::inputWebFileAudioAlbumThumbLocation::TITLE_MASK;
+ if (is_small) {
+ flags |= telegram_api::inputWebFileAudioAlbumThumbLocation::SMALL_MASK;
+ }
+ return make_tl_object<telegram_api::inputWebFileAudioAlbumThumbLocation>(flags, false /*ignored*/, nullptr,
+ parts[2].str(), parts[3].str());
+ }
+
+ if (parts.size() != 9 || parts[1] != "map") {
+ return Status::Error("Wrong conversion");
+ }
+
+ TRY_RESULT(zoom, to_integer_safe<int32>(parts[2]));
+ TRY_RESULT(x, to_integer_safe<int32>(parts[3]));
+ TRY_RESULT(y, to_integer_safe<int32>(parts[4]));
+ TRY_RESULT(width, to_integer_safe<int32>(parts[5]));
+ TRY_RESULT(height, to_integer_safe<int32>(parts[6]));
+ TRY_RESULT(scale, to_integer_safe<int32>(parts[7]));
+
+ if (zoom < 13 || zoom > 20) {
+ return Status::Error("Wrong zoom");
+ }
+ auto size = 256 * (1 << zoom);
+ if (x < 0 || x >= size) {
+ return Status::Error("Wrong x");
+ }
+ if (y < 0 || y >= size) {
+ return Status::Error("Wrong y");
+ }
+ if (width < 16 || height < 16 || width > 1024 || height > 1024) {
+ return Status::Error("Wrong dimensions");
+ }
+ if (scale < 1 || scale > 3) {
+ return Status::Error("Wrong scale");
+ }
+
+ file_name_ = PSTRING() << "map_" << zoom << "_" << x << "_" << y << ".png";
+
+ const double PI = 3.14159265358979323846;
+ double longitude = (x + 0.1) * 360.0 / size - 180;
+ double latitude = 90 - 360 * std::atan(std::exp(((y + 0.1) / size - 0.5) * 2 * PI)) / PI;
+
+ int64 access_hash = G()->get_location_access_hash(latitude, longitude);
+ return make_tl_object<telegram_api::inputWebFileGeoPointLocation>(
+ make_tl_object<telegram_api::inputGeoPoint>(0, latitude, longitude, 0), access_hash, width, height, zoom,
+ scale);
+ }
+
+ void start_up() final {
+ auto r_input_web_file = parse_conversion();
+ if (r_input_web_file.is_error()) {
+ LOG(ERROR) << "Can't parse " << conversion_ << ": " << r_input_web_file.error();
+ return on_error(r_input_web_file.move_as_error());
+ }
+
+ net_callback_ = create_actor<Callback>("WebFileDownloadGenerateCallback", actor_id(this));
+
+ LOG(INFO) << "Download " << conversion_;
+ auto query =
+ G()->net_query_creator().create(telegram_api::upload_getWebFile(r_input_web_file.move_as_ok(), 0, 1 << 20), {},
+ G()->get_webfile_dc_id(), NetQuery::Type::DownloadSmall);
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), {net_callback_.get(), 0});
+ }
+
+ void on_result(NetQueryPtr query) {
+ auto r_result = process_result(std::move(query));
+ if (r_result.is_error()) {
+ return on_error(r_result.move_as_error());
+ }
+
+ callback_->on_ok(r_result.ok());
+ stop();
+ }
+
+ Result<FullLocalFileLocation> process_result(NetQueryPtr query) {
+ TRY_RESULT(web_file, fetch_result<telegram_api::upload_getWebFile>(std::move(query)));
+
+ if (static_cast<size_t>(web_file->size_) != web_file->bytes_.size()) {
+ LOG(ERROR) << "Failed to download web file of size " << web_file->size_;
+ return Status::Error("File is too big");
+ }
+
+ return save_file_bytes(FileType::Thumbnail, std::move(web_file->bytes_), file_name_);
+ }
+
+ void on_error(Status error) {
+ callback_->on_error(std::move(error));
+ stop();
+ }
+
+ void hangup_shared() final {
+ on_error(Status::Error(-1, "Canceled"));
+ }
+};
+
+class FileExternalGenerateActor final : public FileGenerateActor {
public:
FileExternalGenerateActor(uint64 query_id, const FullGenerateFileLocation &generate_location,
const LocalFileLocation &local_location, string name,
- std::unique_ptr<FileGenerateCallback> callback, ActorShared<> parent)
+ unique_ptr<FileGenerateCallback> callback, ActorShared<> parent)
: query_id_(query_id)
, generate_location_(generate_location)
, local_(local_location)
@@ -115,11 +276,21 @@ class FileExternalGenerateActor : public FileGenerateActor {
, parent_(std::move(parent)) {
}
- void file_generate_progress(int32 expected_size, int32 local_prefix_size, Promise<> promise) override {
+ void file_generate_write_part(int64 offset, string data, Promise<> promise) final {
+ check_status(do_file_generate_write_part(offset, data), std::move(promise));
+ }
+
+ void file_generate_progress(int64 expected_size, int64 local_prefix_size, Promise<> promise) final {
check_status(do_file_generate_progress(expected_size, local_prefix_size), std::move(promise));
}
- void file_generate_finish(Status status, Promise<> promise) override {
- check_status(do_file_generate_finish(std::move(status)), std::move(promise));
+
+ void file_generate_finish(Status status, Promise<> promise) final {
+ if (status.is_error()) {
+ check_status(std::move(status));
+ return promise.set_value(Unit());
+ }
+
+ check_status(do_file_generate_finish(), std::move(promise));
}
private:
@@ -128,10 +299,10 @@ class FileExternalGenerateActor : public FileGenerateActor {
LocalFileLocation local_;
string name_;
string path_;
- std::unique_ptr<FileGenerateCallback> callback_;
+ unique_ptr<FileGenerateCallback> callback_;
ActorShared<> parent_;
- void start_up() override {
+ void start_up() final {
if (local_.type() == LocalFileLocation::Type::Full) {
callback_->on_ok(local_.full());
callback_.reset();
@@ -157,25 +328,36 @@ class FileExternalGenerateActor : public FileGenerateActor {
make_tl_object<td_api::updateFileGenerationStart>(
static_cast<int64>(query_id_), generate_location_.original_path_, path_, generate_location_.conversion_));
}
- void hangup() override {
- check_status(Status::Error(1, "Cancelled"));
+ void hangup() final {
+ check_status(Status::Error(-1, "Canceled"));
}
- Status do_file_generate_progress(int32 expected_size, int32 local_prefix_size) {
- if (local_prefix_size < 0) {
- return Status::Error(1, "Invalid local prefix size");
+ Status do_file_generate_write_part(int64 offset, const string &data) {
+ if (offset < 0) {
+ return Status::Error("Wrong offset specified");
+ }
+
+ auto size = data.size();
+ TRY_RESULT(fd, FileFd::open(path_, FileFd::Create | FileFd::Write));
+ TRY_RESULT(written, fd.pwrite(data, offset));
+ if (written != size) {
+ return Status::Error(PSLICE() << "Failed to write file: written " << written << " bytes instead of " << size);
}
- callback_->on_partial_generate(
- PartialLocalFileLocation{generate_location_.file_type_, path_, 1, local_prefix_size, ""}, expected_size);
return Status::OK();
}
- Status do_file_generate_finish(Status status) {
- TRY_STATUS(std::move(status));
-
- auto dir = get_files_dir(generate_location_.file_type_);
+ Status do_file_generate_progress(int64 expected_size, int64 local_prefix_size) {
+ if (local_prefix_size < 0) {
+ return Status::Error(400, "Invalid local prefix size");
+ }
+ callback_->on_partial_generate(PartialLocalFileLocation{generate_location_.file_type_, local_prefix_size, path_, "",
+ Bitmask(Bitmask::Ones{}, 1).encode()},
+ expected_size);
+ return Status::OK();
+ }
- TRY_RESULT(perm_path, create_from_temp(path_, dir, name_));
+ Status do_file_generate_finish() {
+ TRY_RESULT(perm_path, create_from_temp(generate_location_.file_type_, path_, name_));
callback_->on_ok(FullLocalFileLocation(generate_location_.file_type_, std::move(perm_path), 0));
callback_.reset();
stop();
@@ -184,7 +366,7 @@ class FileExternalGenerateActor : public FileGenerateActor {
void check_status(Status status, Promise<> promise = Promise<>()) {
if (promise) {
- if (status.is_ok() || status.code() == 1) {
+ if (status.is_ok() || status.code() == -1) {
promise.set_value(Unit());
} else {
promise.set_error(Status::Error(400, status.message()));
@@ -200,32 +382,72 @@ class FileExternalGenerateActor : public FileGenerateActor {
}
}
- void tear_down() override {
+ void tear_down() final {
send_closure(G()->td(), &Td::send_update,
make_tl_object<td_api::updateFileGenerationStop>(static_cast<int64>(query_id_)));
}
};
FileGenerateManager::Query::~Query() = default;
-FileGenerateManager::Query::Query(Query &&other) = default;
-FileGenerateManager::Query &FileGenerateManager::Query::operator=(Query &&other) = default;
+FileGenerateManager::Query::Query(Query &&other) noexcept = default;
+FileGenerateManager::Query &FileGenerateManager::Query::operator=(Query &&other) noexcept = default;
-void FileGenerateManager::generate_file(uint64 query_id, const FullGenerateFileLocation &generate_location,
+static Status check_mtime(std::string &conversion, CSlice original_path) {
+ if (original_path.empty()) {
+ return Status::OK();
+ }
+ ConstParser parser(conversion);
+ if (!parser.try_skip("#mtime#")) {
+ return Status::OK();
+ }
+ auto mtime_str = parser.read_till('#');
+ parser.skip('#');
+ while (mtime_str.size() >= 2 && mtime_str[0] == '0') {
+ mtime_str.remove_prefix(1);
+ }
+ auto r_mtime = to_integer_safe<uint64>(mtime_str);
+ if (parser.status().is_error() || r_mtime.is_error()) {
+ return Status::OK();
+ }
+ auto expected_mtime = r_mtime.move_as_ok();
+ conversion = parser.read_all().str();
+ auto r_stat = stat(original_path);
+ uint64 actual_mtime = r_stat.is_ok() ? r_stat.ok().mtime_nsec_ : 0;
+ if (are_modification_times_equal(expected_mtime, actual_mtime)) {
+ LOG(DEBUG) << "File \"" << original_path << "\" modification time " << actual_mtime << " matches";
+ return Status::OK();
+ }
+ return Status::Error(PSLICE() << "FILE_GENERATE_LOCATION_INVALID: File \"" << original_path
+ << "\" was modified: " << tag("expected modification time", expected_mtime)
+ << tag("actual modification time", actual_mtime));
+}
+
+void FileGenerateManager::generate_file(uint64 query_id, FullGenerateFileLocation generate_location,
const LocalFileLocation &local_location, string name,
- std::unique_ptr<FileGenerateCallback> callback) {
+ unique_ptr<FileGenerateCallback> callback) {
+ LOG(INFO) << "Begin to generate file with " << generate_location;
+ auto mtime_status = check_mtime(generate_location.conversion_, generate_location.original_path_);
+ if (mtime_status.is_error()) {
+ return callback->on_error(std::move(mtime_status));
+ }
+
CHECK(query_id != 0);
- auto it_flag = query_id_to_query_.insert(std::make_pair(query_id, Query{}));
- CHECK(it_flag.second) << "Query id must be unique";
+ auto it_flag = query_id_to_query_.emplace(query_id, Query{});
+ LOG_CHECK(it_flag.second) << "Query identifier must be unique";
auto parent = actor_shared(this, query_id);
Slice file_id_query = "#file_id#";
Slice conversion = generate_location.conversion_;
auto &query = it_flag.first->second;
- if (conversion.copy().truncate(file_id_query.size()) == file_id_query) {
+ if (begins_with(conversion, file_id_query)) {
auto file_id = FileId(to_integer<int32>(conversion.substr(file_id_query.size())), 0);
query.worker_ = create_actor<FileDownloadGenerateActor>("FileDownloadGenerateActor", generate_location.file_type_,
file_id, std::move(callback), std::move(parent));
+ } else if (FileManager::is_remotely_generated_file(conversion) && generate_location.original_path_.empty()) {
+ query.worker_ = create_actor<WebFileDownloadGenerateActor>("WebFileDownloadGenerateActor",
+ std::move(generate_location.conversion_),
+ std::move(callback), std::move(parent));
} else {
query.worker_ = create_actor<FileExternalGenerateActor>("FileExternalGenerationActor", query_id, generate_location,
local_location, std::move(name), std::move(callback),
@@ -241,7 +463,17 @@ void FileGenerateManager::cancel(uint64 query_id) {
it->second.worker_.reset();
}
-void FileGenerateManager::external_file_generate_progress(uint64 query_id, int32 expected_size, int32 local_prefix_size,
+void FileGenerateManager::external_file_generate_write_part(uint64 query_id, int64 offset, string data,
+ Promise<> promise) {
+ auto it = query_id_to_query_.find(query_id);
+ if (it == query_id_to_query_.end()) {
+ return promise.set_error(Status::Error(400, "Unknown generation_id"));
+ }
+ send_closure(it->second.worker_, &FileGenerateActor::file_generate_write_part, offset, std::move(data),
+ std::move(promise));
+}
+
+void FileGenerateManager::external_file_generate_progress(uint64 query_id, int64 expected_size, int64 local_prefix_size,
Promise<> promise) {
auto it = query_id_to_query_.find(query_id);
if (it == query_id_to_query_.end()) {
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.h
index 4acd495d72..3df9d5e898 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileGenerateManager.h
@@ -1,21 +1,22 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/files/FileLocation.h"
+#include "td/actor/actor.h"
+
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
#include <map>
namespace td {
+
class FileGenerateActor;
class FileGenerateCallback {
@@ -25,23 +26,23 @@ class FileGenerateCallback {
FileGenerateCallback &operator=(const FileGenerateCallback &) = delete;
virtual ~FileGenerateCallback() = default;
- virtual void on_partial_generate(const PartialLocalFileLocation &partial_local, int32 expected_size) = 0;
- virtual void on_ok(const FullLocalFileLocation &local) = 0;
+ virtual void on_partial_generate(PartialLocalFileLocation partial_local, int64 expected_size) = 0;
+ virtual void on_ok(FullLocalFileLocation local) = 0;
virtual void on_error(Status error) = 0;
};
-class FileGenerateManager : public Actor {
+class FileGenerateManager final : public Actor {
public:
explicit FileGenerateManager(ActorShared<> parent) : parent_(std::move(parent)) {
}
- void generate_file(uint64 query_id, const FullGenerateFileLocation &generate_location,
- const LocalFileLocation &local_location, string name,
- std::unique_ptr<FileGenerateCallback> callback);
+ void generate_file(uint64 query_id, FullGenerateFileLocation generate_location,
+ const LocalFileLocation &local_location, string name, unique_ptr<FileGenerateCallback> callback);
void cancel(uint64 query_id);
// external updates about file generation state
- void external_file_generate_progress(uint64 query_id, int32 expected_size, int32 local_prefix_size,
+ void external_file_generate_write_part(uint64 query_id, int64 offset, string data, Promise<> promise);
+ void external_file_generate_progress(uint64 query_id, int64 expected_size, int64 local_prefix_size,
Promise<> promise);
void external_file_generate_finish(uint64 query_id, Status status, Promise<> promise);
@@ -50,8 +51,8 @@ class FileGenerateManager : public Actor {
Query() = default;
Query(const Query &other) = delete;
Query &operator=(const Query &other) = delete;
- Query(Query &&other);
- Query &operator=(Query &&other);
+ Query(Query &&other) noexcept;
+ Query &operator=(Query &&other) noexcept;
~Query();
ActorOwn<FileGenerateActor> worker_;
@@ -61,9 +62,9 @@ class FileGenerateManager : public Actor {
std::map<uint64, Query> query_id_to_query_;
bool close_flag_ = false;
- void hangup() override;
- void hangup_shared() override;
- void loop() override;
+ void hangup() final;
+ void hangup_shared() final;
+ void loop() final;
void do_cancel(uint64 query_id);
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.cpp
index 2408e16df4..75427bbbb1 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.cpp
@@ -1,27 +1,30 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileHashUploader.h"
-#include "td/telegram/telegram_api.h"
-
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/telegram_api.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/logging.h"
#include "td/utils/MimeType.h"
#include "td/utils/misc.h"
#include "td/utils/PathView.h"
-#include "td/utils/port/Fd.h"
#include "td/utils/port/FileFd.h"
+#include "td/utils/port/PollFlags.h"
#include "td/utils/Status.h"
namespace td {
+
void FileHashUploader::start_up() {
auto status = init();
if (status.is_error()) {
@@ -30,18 +33,21 @@ void FileHashUploader::start_up() {
return;
}
}
+
Status FileHashUploader::init() {
TRY_RESULT(fd, FileFd::open(local_.path_, FileFd::Read));
- if (fd.get_size() != size_) {
- return Status::Error("size mismatch");
+ TRY_RESULT(file_size, fd.get_size());
+ if (file_size != size_) {
+ return Status::Error("Size mismatch");
}
fd_ = BufferedFd<FileFd>(std::move(fd));
- sha256_init(&sha256_state_);
+ sha256_state_.init();
resource_state_.set_unit_size(1024);
resource_state_.update_estimated_limit(size_);
return Status::OK();
}
+
void FileHashUploader::loop() {
if (stop_flag_) {
return;
@@ -56,20 +62,19 @@ void FileHashUploader::loop() {
}
Status FileHashUploader::loop_impl() {
- if (state_ == CalcSha) {
+ if (state_ == State::CalcSha) {
TRY_STATUS(loop_sha());
}
- if (state_ == NetRequest) {
- // messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document;
+ if (state_ == State::NetRequest) {
+ // messages.getDocumentByHash#338e2464 sha256:bytes size:long mime_type:string = Document;
auto hash = BufferSlice(32);
- sha256_final(&sha256_state_, hash.as_slice());
+ sha256_state_.extract(hash.as_slice(), true);
auto mime_type = MimeType::from_extension(PathView(local_.path_).extension(), "image/gif");
- auto query =
- telegram_api::messages_getDocumentByHash(std::move(hash), static_cast<int32>(size_), std::move(mime_type));
+ auto query = telegram_api::messages_getDocumentByHash(std::move(hash), size_, std::move(mime_type));
LOG(INFO) << "Send getDocumentByHash request: " << to_string(query);
- auto ptr = G()->net_query_creator().create(create_storer(query));
+ auto ptr = G()->net_query_creator().create(query);
G()->net_query_dispatcher().dispatch_with_callback(std::move(ptr), actor_shared(this));
- state_ = WaitNetResult;
+ state_ = State::WaitNetResult;
}
return Status::OK();
}
@@ -84,17 +89,17 @@ Status FileHashUploader::loop_sha() {
}
resource_state_.start_use(limit);
- fd_.update_flags(Fd::Flag::Read);
+ fd_.get_poll_info().add_flags(PollFlags::Read());
TRY_RESULT(read_size, fd_.flush_read(static_cast<size_t>(limit)));
if (read_size != static_cast<size_t>(limit)) {
- return Status::Error("unexpected end of file");
+ return Status::Error("Unexpected end of file");
}
while (true) {
auto ready = fd_.input_buffer().prepare_read();
if (ready.empty()) {
break;
}
- sha256_update(ready, &sha256_state_);
+ sha256_state_.feed(ready);
fd_.input_buffer().confirm_read(ready.size());
}
resource_state_.stop_use(limit);
@@ -102,7 +107,7 @@ Status FileHashUploader::loop_sha() {
size_left_ -= narrow_cast<int64>(read_size);
CHECK(size_left_ >= 0);
if (size_left_ == 0) {
- state_ = NetRequest;
+ state_ = State::NetRequest;
return Status::OK();
}
return Status::OK();
@@ -128,8 +133,12 @@ Status FileHashUploader::on_result_impl(NetQueryPtr net_query) {
return Status::Error("Document is not found by hash");
case telegram_api::document::ID: {
auto document = move_tl_object_as<telegram_api::document>(res);
+ if (!DcId::is_valid(document->dc_id_)) {
+ return Status::Error("Found document has invalid DcId");
+ }
callback_->on_ok(FullRemoteFileLocation(FileType::Document, document->id_, document->access_hash_,
- DcId::internal(document->dc_id_)));
+ DcId::internal(document->dc_id_),
+ document->file_reference_.as_slice().str()));
stop_flag_ = true;
return Status::OK();
@@ -139,4 +148,5 @@ Status FileHashUploader::on_result_impl(NetQueryPtr net_query) {
return Status::Error("UNREACHABLE");
}
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.h
index d836c521b7..533ab8eaa2 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileHashUploader.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,6 +10,8 @@
#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/ResourceManager.h"
+#include "td/actor/actor.h"
+
#include "td/utils/BufferedFd.h"
#include "td/utils/crypto.h"
#include "td/utils/port/FileFd.h"
@@ -17,7 +19,7 @@
namespace td {
-class FileHashUploader : public FileLoaderActor {
+class FileHashUploader final : public FileLoaderActor {
public:
class Callback {
public:
@@ -25,23 +27,24 @@ class FileHashUploader : public FileLoaderActor {
Callback(const Callback &) = delete;
Callback &operator=(const Callback &) = delete;
virtual ~Callback() = default;
- virtual void on_ok(const FullRemoteFileLocation &locatioin) = 0;
+
+ virtual void on_ok(FullRemoteFileLocation location) = 0;
virtual void on_error(Status status) = 0;
};
- FileHashUploader(const FullLocalFileLocation &local, int64 size, std::unique_ptr<Callback> callback)
+ FileHashUploader(const FullLocalFileLocation &local, int64 size, unique_ptr<Callback> callback)
: local_(local), size_(size), size_left_(size), callback_(std::move(callback)) {
}
- void set_resource_manager(ActorShared<ResourceManager> resource_manager) override {
+ void set_resource_manager(ActorShared<ResourceManager> resource_manager) final {
resource_manager_ = std::move(resource_manager);
send_closure(resource_manager_, &ResourceManager::update_resources, resource_state_);
}
- void update_priority(int8 priority) override {
+ void update_priority(int8 priority) final {
send_closure(resource_manager_, &ResourceManager::update_priority, priority);
}
- void update_resources(const ResourceState &other) override {
+ void update_resources(const ResourceState &other) final {
if (stop_flag_) {
return;
}
@@ -60,21 +63,22 @@ class FileHashUploader : public FileLoaderActor {
ActorShared<ResourceManager> resource_manager_;
- enum { CalcSha, NetRequest, WaitNetResult } state_ = CalcSha;
+ enum class State : int32 { CalcSha, NetRequest, WaitNetResult } state_ = State::CalcSha;
bool stop_flag_ = false;
Sha256State sha256_state_;
- void start_up() override;
+ void start_up() final;
Status init();
- void loop() override;
+ void loop() final;
Status loop_impl();
Status loop_sha();
- void on_result(NetQueryPtr net_query) override;
+ void on_result(NetQueryPtr net_query) final;
Status on_result_impl(NetQueryPtr net_query);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileId.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileId.h
index ef68681ba0..d2dec609d5 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileId.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,9 +7,9 @@
#pragma once
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/StringBuilder.h"
-#include <functional>
#include <type_traits>
namespace td {
@@ -56,12 +56,13 @@ class FileId {
};
struct FileIdHash {
- std::size_t operator()(FileId file_id) const {
- return std::hash<int32>()(file_id.get());
+ uint32 operator()(FileId file_id) const {
+ return Hash<int32>()(file_id.get());
}
};
inline StringBuilder &operator<<(StringBuilder &string_builder, FileId file_id) {
return string_builder << file_id.get() << "(" << file_id.get_remote() << ")";
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileId.hpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileId.hpp
index b4b5dbf429..65966b9a53 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileId.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileId.hpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,6 +8,7 @@
#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileManager.h"
#include "td/telegram/files/FileManager.hpp"
#include "td/telegram/Td.h"
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.cpp
index 85ef83595d..6cc51c8184 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,22 +7,30 @@
#include "td/telegram/files/FileLoadManager.h"
#include "td/telegram/Global.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/TdParameters.h"
#include "td/utils/common.h"
#include "td/utils/filesystem.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
+#include "td/utils/port/path.h"
+#include "td/utils/SliceBuilder.h"
namespace td {
+
FileLoadManager::FileLoadManager(ActorShared<Callback> callback, ActorShared<> parent)
: callback_(std::move(callback)), parent_(std::move(parent)) {
}
void FileLoadManager::start_up() {
- upload_resource_manager_ =
- create_actor<ResourceManager>("UploadResourceManager", !G()->parameters().use_file_db /*tdlib_engine*/
- ? ResourceManager::Mode::Greedy
- : ResourceManager::Mode::Baseline);
+ constexpr int64 MAX_UPLOAD_RESOURCE_LIMIT = 4 << 20;
+ upload_resource_manager_ = create_actor<ResourceManager>("UploadResourceManager", MAX_UPLOAD_RESOURCE_LIMIT,
+ !G()->parameters().use_file_db /*tdlib_engine*/
+ ? ResourceManager::Mode::Greedy
+ : ResourceManager::Mode::Baseline);
+ if (G()->get_option_boolean("is_premium")) {
+ max_download_resource_limit_ *= 8;
+ }
}
ActorOwn<ResourceManager> &FileLoadManager::get_download_resource_manager(bool is_small, DcId dc_id) {
@@ -30,49 +38,52 @@ ActorOwn<ResourceManager> &FileLoadManager::get_download_resource_manager(bool i
if (actor.empty()) {
actor = create_actor<ResourceManager>(
PSLICE() << "DownloadResourceManager " << tag("is_small", is_small) << tag("dc_id", dc_id),
- ResourceManager::Mode::Baseline);
+ max_download_resource_limit_, ResourceManager::Mode::Baseline);
}
return actor;
}
void FileLoadManager::download(QueryId id, const FullRemoteFileLocation &remote_location,
const LocalFileLocation &local, int64 size, string name,
- const FileEncryptionKey &encryption_key, bool search_file, int8 priority) {
+ const FileEncryptionKey &encryption_key, bool search_file, int64 offset, int64 limit,
+ int8 priority) {
if (stop_flag_) {
return;
}
- CHECK(query_id_to_node_id_.find(id) == query_id_to_node_id_.end());
NodeId node_id = nodes_container_.create(Node());
Node *node = nodes_container_.get(node_id);
CHECK(node);
node->query_id_ = id;
auto callback = make_unique<FileDownloaderCallback>(actor_shared(this, node_id));
bool is_small = size < 20 * 1024;
- node->loader_ = create_actor<FileDownloader>("Downloader", remote_location, local, size, std::move(name),
- encryption_key, is_small, search_file, std::move(callback));
- auto &resource_manager = get_download_resource_manager(is_small, remote_location.get_dc_id());
+ node->loader_ =
+ create_actor<FileDownloader>("Downloader", remote_location, local, size, std::move(name), encryption_key,
+ is_small, search_file, offset, limit, std::move(callback));
+ DcId dc_id = remote_location.is_web() ? G()->get_webfile_dc_id() : remote_location.get_dc_id();
+ auto &resource_manager = get_download_resource_manager(is_small, dc_id);
send_closure(resource_manager, &ResourceManager::register_worker,
ActorShared<FileLoaderActor>(node->loader_.get(), static_cast<uint64>(-1)), priority);
- query_id_to_node_id_[id] = node_id;
+ bool is_inserted = query_id_to_node_id_.emplace(id, node_id).second;
+ CHECK(is_inserted);
}
void FileLoadManager::upload(QueryId id, const LocalFileLocation &local_location,
- const RemoteFileLocation &remote_location, int64 size,
+ const RemoteFileLocation &remote_location, int64 expected_size,
const FileEncryptionKey &encryption_key, int8 priority, vector<int> bad_parts) {
if (stop_flag_) {
return;
}
- CHECK(query_id_to_node_id_.find(id) == query_id_to_node_id_.end());
NodeId node_id = nodes_container_.create(Node());
Node *node = nodes_container_.get(node_id);
CHECK(node);
node->query_id_ = id;
auto callback = make_unique<FileUploaderCallback>(actor_shared(this, node_id));
- node->loader_ = create_actor<FileUploader>("Uploader", local_location, remote_location, size, encryption_key,
+ node->loader_ = create_actor<FileUploader>("Uploader", local_location, remote_location, expected_size, encryption_key,
std::move(bad_parts), std::move(callback));
send_closure(upload_resource_manager_, &ResourceManager::register_worker,
ActorShared<FileLoaderActor>(node->loader_.get(), static_cast<uint64>(-1)), priority);
- query_id_to_node_id_[id] = node_id;
+ bool is_inserted = query_id_to_node_id_.emplace(id, node_id).second;
+ CHECK(is_inserted);
}
void FileLoadManager::upload_by_hash(QueryId id, const FullLocalFileLocation &local_location, int64 size,
@@ -80,7 +91,6 @@ void FileLoadManager::upload_by_hash(QueryId id, const FullLocalFileLocation &lo
if (stop_flag_) {
return;
}
- CHECK(query_id_to_node_id_.find(id) == query_id_to_node_id_.end());
NodeId node_id = nodes_container_.create(Node());
Node *node = nodes_container_.get(node_id);
CHECK(node);
@@ -89,7 +99,8 @@ void FileLoadManager::upload_by_hash(QueryId id, const FullLocalFileLocation &lo
node->loader_ = create_actor<FileHashUploader>("HashUploader", local_location, size, std::move(callback));
send_closure(upload_resource_manager_, &ResourceManager::register_worker,
ActorShared<FileLoaderActor>(node->loader_.get(), static_cast<uint64>(-1)), priority);
- query_id_to_node_id_[id] = node_id;
+ bool is_inserted = query_id_to_node_id_.emplace(id, node_id).second;
+ CHECK(is_inserted);
}
void FileLoadManager::update_priority(QueryId id, int8 priority) {
@@ -111,7 +122,6 @@ void FileLoadManager::from_bytes(QueryId id, FileType type, BufferSlice bytes, s
if (stop_flag_) {
return;
}
- CHECK(query_id_to_node_id_.find(id) == query_id_to_node_id_.end());
NodeId node_id = nodes_container_.create(Node());
Node *node = nodes_container_.get(node_id);
CHECK(node);
@@ -119,12 +129,35 @@ void FileLoadManager::from_bytes(QueryId id, FileType type, BufferSlice bytes, s
auto callback = make_unique<FileFromBytesCallback>(actor_shared(this, node_id));
node->loader_ =
create_actor<FileFromBytes>("FromBytes", type, std::move(bytes), std::move(name), std::move(callback));
- query_id_to_node_id_[id] = node_id;
+ bool is_inserted = query_id_to_node_id_.emplace(id, node_id).second;
+ CHECK(is_inserted);
+}
+
+void FileLoadManager::get_content(string file_path, Promise<BufferSlice> promise) {
+ promise.set_result(read_file(file_path));
}
-void FileLoadManager::get_content(const FullLocalFileLocation &local_location, Promise<BufferSlice> promise) {
- // TODO: send query to other thread
- promise.set_result(read_file(local_location.path_));
+void FileLoadManager::read_file_part(string file_path, int64 offset, int64 count, Promise<string> promise) {
+ promise.set_result(read_file_str(file_path, count, offset));
+}
+
+void FileLoadManager::unlink_file(string file_path, Promise<Unit> promise) {
+ unlink(file_path).ignore();
+ promise.set_value(Unit());
+}
+
+void FileLoadManager::check_full_local_location(FullLocalLocationInfo local_info, bool skip_file_size_checks,
+ Promise<FullLocalLocationInfo> promise) {
+ promise.set_result(::td::check_full_local_location(std::move(local_info), skip_file_size_checks));
+}
+
+void FileLoadManager::check_partial_local_location(PartialLocalFileLocation partial, Promise<Unit> promise) {
+ auto status = ::td::check_partial_local_location(partial);
+ if (status.is_error()) {
+ promise.set_error(std::move(status));
+ } else {
+ promise.set_value(Unit());
+ }
}
// void upload_reload_parts(QueryId id, vector<int32> parts);
@@ -137,7 +170,7 @@ void FileLoadManager::cancel(QueryId id) {
if (it == query_id_to_node_id_.end()) {
return;
}
- on_error_impl(it->second, Status::Error(1, "Cancelled"));
+ on_error_impl(it->second, Status::Error(-1, "Canceled"));
}
void FileLoadManager::update_local_file_location(QueryId id, const LocalFileLocation &local) {
if (stop_flag_) {
@@ -153,7 +186,23 @@ void FileLoadManager::update_local_file_location(QueryId id, const LocalFileLoca
}
send_closure(node->loader_, &FileLoaderActor::update_local_file_location, local);
}
-void FileLoadManager::close() {
+
+void FileLoadManager::update_downloaded_part(QueryId id, int64 offset, int64 limit) {
+ if (stop_flag_) {
+ return;
+ }
+ auto it = query_id_to_node_id_.find(id);
+ if (it == query_id_to_node_id_.end()) {
+ return;
+ }
+ auto node = nodes_container_.get(it->second);
+ if (node == nullptr) {
+ return;
+ }
+ send_closure(node->loader_, &FileLoaderActor::update_downloaded_part, offset, limit, max_download_resource_limit_);
+}
+
+void FileLoadManager::hangup() {
nodes_container_.for_each([](auto id, auto &node) { node.loader_.reset(); });
stop_flag_ = true;
loop();
@@ -170,62 +219,74 @@ void FileLoadManager::on_start_download() {
}
}
-void FileLoadManager::on_partial_download(const PartialLocalFileLocation &partial_local, int64 ready_size) {
+void FileLoadManager::on_partial_download(PartialLocalFileLocation partial_local, int64 ready_size, int64 size) {
+ auto node_id = get_link_token();
+ auto node = nodes_container_.get(node_id);
+ if (node == nullptr) {
+ return;
+ }
+ if (!stop_flag_) {
+ send_closure(callback_, &Callback::on_partial_download, node->query_id_, std::move(partial_local), ready_size,
+ size);
+ }
+}
+
+void FileLoadManager::on_hash(string hash) {
auto node_id = get_link_token();
auto node = nodes_container_.get(node_id);
if (node == nullptr) {
return;
}
if (!stop_flag_) {
- send_closure(callback_, &Callback::on_partial_download, node->query_id_, partial_local, ready_size);
+ send_closure(callback_, &Callback::on_hash, node->query_id_, std::move(hash));
}
}
-void FileLoadManager::on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size) {
+void FileLoadManager::on_partial_upload(PartialRemoteFileLocation partial_remote, int64 ready_size) {
auto node_id = get_link_token();
auto node = nodes_container_.get(node_id);
if (node == nullptr) {
return;
}
if (!stop_flag_) {
- send_closure(callback_, &Callback::on_partial_upload, node->query_id_, partial_remote, ready_size);
+ send_closure(callback_, &Callback::on_partial_upload, node->query_id_, std::move(partial_remote), ready_size);
}
}
-void FileLoadManager::on_ok_download(const FullLocalFileLocation &local, int64 size) {
+void FileLoadManager::on_ok_download(FullLocalFileLocation local, int64 size, bool is_new) {
auto node_id = get_link_token();
auto node = nodes_container_.get(node_id);
if (node == nullptr) {
return;
}
if (!stop_flag_) {
- send_closure(callback_, &Callback::on_download_ok, node->query_id_, local, size);
+ send_closure(callback_, &Callback::on_download_ok, node->query_id_, std::move(local), size, is_new);
}
close_node(node_id);
loop();
}
-void FileLoadManager::on_ok_upload(FileType file_type, const PartialRemoteFileLocation &remote, int64 size) {
+void FileLoadManager::on_ok_upload(FileType file_type, PartialRemoteFileLocation remote, int64 size) {
auto node_id = get_link_token();
auto node = nodes_container_.get(node_id);
if (node == nullptr) {
return;
}
if (!stop_flag_) {
- send_closure(callback_, &Callback::on_upload_ok, node->query_id_, file_type, remote, size);
+ send_closure(callback_, &Callback::on_upload_ok, node->query_id_, file_type, std::move(remote), size);
}
close_node(node_id);
loop();
}
-void FileLoadManager::on_ok_upload_full(const FullRemoteFileLocation &remote) {
+void FileLoadManager::on_ok_upload_full(FullRemoteFileLocation remote) {
auto node_id = get_link_token();
auto node = nodes_container_.get(node_id);
if (node == nullptr) {
return;
}
if (!stop_flag_) {
- send_closure(callback_, &Callback::on_upload_full_ok, node->query_id_, remote);
+ send_closure(callback_, &Callback::on_upload_full_ok, node->query_id_, std::move(remote));
}
close_node(node_id);
loop();
@@ -251,15 +312,12 @@ void FileLoadManager::on_error_impl(NodeId node_id, Status status) {
void FileLoadManager::hangup_shared() {
auto node_id = get_link_token();
- on_error_impl(node_id, Status::Error(1, "Cancelled"));
+ on_error_impl(node_id, Status::Error(-1, "Canceled"));
}
void FileLoadManager::loop() {
- if (stop_flag_) {
- if (nodes_container_.empty()) {
- stop();
- }
- return;
+ if (stop_flag_ && nodes_container_.empty()) {
+ stop();
}
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.h
index d61df5d08e..76b79b7a7f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoadManager.h
@@ -1,23 +1,28 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/files/FileDownloader.h"
+#include "td/telegram/files/FileEncryptionKey.h"
#include "td/telegram/files/FileFromBytes.h"
#include "td/telegram/files/FileHashUploader.h"
+#include "td/telegram/files/FileLoaderUtils.h"
#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/files/FileUploader.h"
#include "td/telegram/files/ResourceManager.h"
+#include "td/telegram/net/DcId.h"
+
+#include "td/actor/actor.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/Container.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
#include <map>
@@ -29,33 +34,41 @@ class FileLoadManager final : public Actor {
using QueryId = uint64;
class Callback : public Actor {
public:
- Callback() = default;
- Callback(const Callback &) = delete;
- Callback &operator=(const Callback &) = delete;
- ~Callback() override = default;
virtual void on_start_download(QueryId id) = 0;
- virtual void on_partial_download(QueryId id, const PartialLocalFileLocation &partial_local, int64 ready_size) = 0;
- virtual void on_partial_upload(QueryId id, const PartialRemoteFileLocation &partial_remote, int64 ready_size) = 0;
- virtual void on_upload_ok(QueryId id, FileType file_type, const PartialRemoteFileLocation &remtoe, int64 size) = 0;
- virtual void on_upload_full_ok(QueryId id, const FullRemoteFileLocation &remote) = 0;
- virtual void on_download_ok(QueryId id, const FullLocalFileLocation &local, int64 size) = 0;
+ virtual void on_partial_download(QueryId id, PartialLocalFileLocation partial_local, int64 ready_size,
+ int64 size) = 0;
+ virtual void on_partial_upload(QueryId id, PartialRemoteFileLocation partial_remote, int64 ready_size) = 0;
+ virtual void on_hash(QueryId id, string hash) = 0;
+ virtual void on_upload_ok(QueryId id, FileType file_type, PartialRemoteFileLocation remtoe, int64 size) = 0;
+ virtual void on_upload_full_ok(QueryId id, FullRemoteFileLocation remote) = 0;
+ virtual void on_download_ok(QueryId id, FullLocalFileLocation local, int64 size, bool is_new) = 0;
virtual void on_error(QueryId id, Status status) = 0;
};
explicit FileLoadManager(ActorShared<Callback> callback, ActorShared<> parent);
+
void download(QueryId id, const FullRemoteFileLocation &remote_location, const LocalFileLocation &local, int64 size,
- string name, const FileEncryptionKey &encryption_key, bool search_file, int8 priority);
+ string name, const FileEncryptionKey &encryption_key, bool search_file, int64 offset, int64 limit,
+ int8 priority);
void upload(QueryId id, const LocalFileLocation &local_location, const RemoteFileLocation &remote_location,
- int64 size, const FileEncryptionKey &encryption_key, int8 priority, vector<int> bad_parts);
+ int64 expected_size, const FileEncryptionKey &encryption_key, int8 priority, vector<int> bad_parts);
void upload_by_hash(QueryId id, const FullLocalFileLocation &local_location, int64 size, int8 priority);
void update_priority(QueryId id, int8 priority);
void from_bytes(QueryId id, FileType type, BufferSlice bytes, string name);
void cancel(QueryId id);
void update_local_file_location(QueryId id, const LocalFileLocation &local);
- void get_content(const FullLocalFileLocation &local_location, Promise<BufferSlice> promise);
+ void update_downloaded_part(QueryId id, int64 offset, int64 limit);
- // just stops actor and all queries. no callbacks will be called
- void close();
+ void get_content(string file_path, Promise<BufferSlice> promise);
+
+ void read_file_part(string file_path, int64 offset, int64 count, Promise<string> promise);
+
+ void unlink_file(string file_path, Promise<Unit> promise);
+
+ void check_full_local_location(FullLocalLocationInfo local_info, bool skip_file_size_checks,
+ Promise<FullLocalLocationInfo> promise);
+
+ void check_partial_local_location(PartialLocalFileLocation partial, Promise<Unit> promise);
private:
struct Node {
@@ -73,25 +86,28 @@ class FileLoadManager final : public Actor {
ActorShared<Callback> callback_;
ActorShared<> parent_;
std::map<QueryId, NodeId> query_id_to_node_id_;
+ int64 max_download_resource_limit_ = 1 << 21;
bool stop_flag_ = false;
- void start_up() override;
- void loop() override;
- void hangup_shared() override;
+ void start_up() final;
+ void loop() final;
+ void hangup() final;
+ void hangup_shared() final;
void close_node(NodeId node_id);
ActorOwn<ResourceManager> &get_download_resource_manager(bool is_small, DcId dc_id);
void on_start_download();
- void on_partial_download(const PartialLocalFileLocation &partial_local, int64 ready_size);
- void on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size);
- void on_ok_download(const FullLocalFileLocation &local, int64 size);
- void on_ok_upload(FileType file_type, const PartialRemoteFileLocation &remote, int64 size);
- void on_ok_upload_full(const FullRemoteFileLocation &remote);
+ void on_partial_download(PartialLocalFileLocation partial_local, int64 ready_size, int64 size);
+ void on_partial_upload(PartialRemoteFileLocation partial_remote, int64 ready_size);
+ void on_hash(string hash);
+ void on_ok_download(FullLocalFileLocation local, int64 size, bool is_new);
+ void on_ok_upload(FileType file_type, PartialRemoteFileLocation remote, int64 size);
+ void on_ok_upload_full(FullRemoteFileLocation remote);
void on_error(Status status);
void on_error_impl(NodeId node_id, Status status);
- class FileDownloaderCallback : public FileDownloader::Callback {
+ class FileDownloaderCallback final : public FileDownloader::Callback {
public:
explicit FileDownloaderCallback(ActorShared<FileLoadManager> actor_id) : actor_id_(std::move(actor_id)) {
}
@@ -99,21 +115,21 @@ class FileLoadManager final : public Actor {
private:
ActorShared<FileLoadManager> actor_id_;
- void on_start_download() override {
+ void on_start_download() final {
send_closure(actor_id_, &FileLoadManager::on_start_download);
}
- void on_partial_download(const PartialLocalFileLocation &partial_local, int64 ready_size) override {
- send_closure(actor_id_, &FileLoadManager::on_partial_download, partial_local, ready_size);
+ void on_partial_download(PartialLocalFileLocation partial_local, int64 ready_size, int64 size) final {
+ send_closure(actor_id_, &FileLoadManager::on_partial_download, std::move(partial_local), ready_size, size);
}
- void on_ok(const FullLocalFileLocation &full_local, int64 size) override {
- send_closure(std::move(actor_id_), &FileLoadManager::on_ok_download, full_local, size);
+ void on_ok(FullLocalFileLocation full_local, int64 size, bool is_new) final {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_ok_download, std::move(full_local), size, is_new);
}
- void on_error(Status status) override {
+ void on_error(Status status) final {
send_closure(std::move(actor_id_), &FileLoadManager::on_error, std::move(status));
}
};
- class FileUploaderCallback : public FileUploader::Callback {
+ class FileUploaderCallback final : public FileUploader::Callback {
public:
explicit FileUploaderCallback(ActorShared<FileLoadManager> actor_id) : actor_id_(std::move(actor_id)) {
}
@@ -121,17 +137,20 @@ class FileLoadManager final : public Actor {
private:
ActorShared<FileLoadManager> actor_id_;
- void on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size) override {
- send_closure(actor_id_, &FileLoadManager::on_partial_upload, partial_remote, ready_size);
+ void on_hash(string hash) final {
+ send_closure(actor_id_, &FileLoadManager::on_hash, std::move(hash));
+ }
+ void on_partial_upload(PartialRemoteFileLocation partial_remote, int64 ready_size) final {
+ send_closure(actor_id_, &FileLoadManager::on_partial_upload, std::move(partial_remote), ready_size);
}
- void on_ok(FileType file_type, const PartialRemoteFileLocation &partial_remote, int64 size) override {
- send_closure(std::move(actor_id_), &FileLoadManager::on_ok_upload, file_type, partial_remote, size);
+ void on_ok(FileType file_type, PartialRemoteFileLocation partial_remote, int64 size) final {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_ok_upload, file_type, std::move(partial_remote), size);
}
- void on_error(Status status) override {
+ void on_error(Status status) final {
send_closure(std::move(actor_id_), &FileLoadManager::on_error, std::move(status));
}
};
- class FileHashUploaderCallback : public FileHashUploader::Callback {
+ class FileHashUploaderCallback final : public FileHashUploader::Callback {
public:
explicit FileHashUploaderCallback(ActorShared<FileLoadManager> actor_id) : actor_id_(std::move(actor_id)) {
}
@@ -139,15 +158,15 @@ class FileLoadManager final : public Actor {
private:
ActorShared<FileLoadManager> actor_id_;
- void on_ok(const FullRemoteFileLocation &remote) override {
- send_closure(std::move(actor_id_), &FileLoadManager::on_ok_upload_full, remote);
+ void on_ok(FullRemoteFileLocation remote) final {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_ok_upload_full, std::move(remote));
}
- void on_error(Status status) override {
+ void on_error(Status status) final {
send_closure(std::move(actor_id_), &FileLoadManager::on_error, std::move(status));
}
};
- class FileFromBytesCallback : public FileFromBytes::Callback {
+ class FileFromBytesCallback final : public FileFromBytes::Callback {
public:
explicit FileFromBytesCallback(ActorShared<FileLoadManager> actor_id) : actor_id_(std::move(actor_id)) {
}
@@ -155,10 +174,10 @@ class FileLoadManager final : public Actor {
private:
ActorShared<FileLoadManager> actor_id_;
- void on_ok(const FullLocalFileLocation &full_local, int64 size) override {
- send_closure(std::move(actor_id_), &FileLoadManager::on_ok_download, full_local, size);
+ void on_ok(const FullLocalFileLocation &full_local, int64 size) final {
+ send_closure(std::move(actor_id_), &FileLoadManager::on_ok_download, full_local, size, true);
}
- void on_error(Status status) override {
+ void on_error(Status status) final {
send_closure(std::move(actor_id_), &FileLoadManager::on_error, std::move(status));
}
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.cpp
index 8431aa8553..1ac07c10bc 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.cpp
@@ -1,17 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileLoader.h"
+#include "td/telegram/files/FileLoaderUtils.h"
#include "td/telegram/files/ResourceManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/net/NetQueryDispatcher.h"
-#include "td/telegram/Td.h"
#include "td/telegram/UniqueId.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
@@ -20,6 +21,7 @@
#include <tuple>
namespace td {
+
void FileLoader::set_resource_manager(ActorShared<ResourceManager> resource_manager) {
resource_manager_ = std::move(resource_manager);
send_closure(resource_manager_, &ResourceManager::update_resources, resource_state_);
@@ -29,7 +31,7 @@ void FileLoader::update_priority(int8 priority) {
}
void FileLoader::update_resources(const ResourceState &other) {
resource_state_.update_slave(other);
- VLOG(files) << "update resources " << resource_state_;
+ VLOG(file_loader) << "Update resources " << resource_state_;
loop();
}
void FileLoader::set_ordered_flag(bool flag) {
@@ -38,16 +40,23 @@ void FileLoader::set_ordered_flag(bool flag) {
size_t FileLoader::get_part_size() const {
return parts_manager_.get_part_size();
}
+
void FileLoader::hangup() {
- // if (!stop_flag_) {
- // stop_flag_ = true;
- // on_error(Status::Error("Cancelled"));
- //}
- stop();
+ if (delay_dispatcher_.empty()) {
+ stop();
+ } else {
+ delay_dispatcher_.reset();
+ }
+}
+
+void FileLoader::hangup_shared() {
+ if (get_link_token() == 1) {
+ stop();
+ }
}
void FileLoader::update_local_file_location(const LocalFileLocation &local) {
- auto r_prefix_info = on_update_local_location(local);
+ auto r_prefix_info = on_update_local_location(local, parts_manager_.get_size_or_zero());
if (r_prefix_info.is_error()) {
on_error(r_prefix_info.move_as_error());
stop_flag_ = true;
@@ -63,6 +72,27 @@ void FileLoader::update_local_file_location(const LocalFileLocation &local) {
loop();
}
+void FileLoader::update_downloaded_part(int64 offset, int64 limit, int64 max_resource_limit) {
+ if (parts_manager_.get_streaming_offset() != offset) {
+ auto begin_part_id = parts_manager_.set_streaming_offset(offset, limit);
+ auto new_end_part_id = limit <= 0 ? parts_manager_.get_part_count()
+ : narrow_cast<int32>((offset + limit - 1) / parts_manager_.get_part_size()) + 1;
+ auto max_parts = narrow_cast<int32>(max_resource_limit / parts_manager_.get_part_size());
+ auto end_part_id = begin_part_id + td::min(max_parts, new_end_part_id - begin_part_id);
+ VLOG(file_loader) << "Protect parts " << begin_part_id << " ... " << end_part_id - 1;
+ for (auto &it : part_map_) {
+ if (!it.second.second.empty() && !(begin_part_id <= it.second.first.id && it.second.first.id < end_part_id)) {
+ VLOG(file_loader) << "Cancel part " << it.second.first.id;
+ it.second.second.reset(); // cancel_query(it.second.second);
+ }
+ }
+ } else {
+ parts_manager_.set_streaming_limit(limit);
+ }
+ update_estimated_limit();
+ loop();
+}
+
void FileLoader::start_up() {
auto r_file_info = init();
if (r_file_info.is_error()) {
@@ -77,25 +107,43 @@ void FileLoader::start_up() {
auto part_size = file_info.part_size;
auto &ready_parts = file_info.ready_parts;
auto use_part_count_limit = file_info.use_part_count_limit;
- auto status = parts_manager_.init(size, expected_size, is_size_final, part_size, ready_parts, use_part_count_limit);
- if (file_info.only_check) {
- parts_manager_.set_checked_prefix_size(0);
- }
+ bool is_upload = file_info.is_upload;
+
+ // Two cases when FILE_UPLOAD_RESTART will happen
+ // 1. File is ready, size is final. But there are more uploaded parts than size of the file
+ // pm.init(1, 100000, true, 10, {0, 1, 2}, false, true).ensure_error();
+ // This error is definitely ok, because we are using actual size of the file on disk (mtime is checked by
+ // somebody else). And actual size could change arbitrarily.
+ //
+ // 2. size is unknown/zero, size is not final, some parts of file are already uploaded
+ // pm.init(0, 100000, false, 10, {0, 1, 2}, false, true).ensure_error();
+ // This case is more complicated
+ // It means that at some point we got inconsistent state. Like deleted local location, but left partial remote
+ // location untouched. This is completely possible at this point, but probably should be fixed.
+ auto status =
+ parts_manager_.init(size, expected_size, is_size_final, part_size, ready_parts, use_part_count_limit, is_upload);
+ LOG(DEBUG) << "Start " << (is_upload ? "up" : "down") << "loading a file of size " << size << " with expected "
+ << (is_size_final ? "exact" : "approximate") << " size " << expected_size << ", part size " << part_size
+ << " and " << ready_parts.size() << " ready parts: " << status;
if (status.is_error()) {
on_error(std::move(status));
stop_flag_ = true;
return;
}
+ if (file_info.only_check) {
+ parts_manager_.set_checked_prefix_size(0);
+ }
+ parts_manager_.set_streaming_offset(file_info.offset, file_info.limit);
if (ordered_flag_) {
ordered_parts_ = OrderedEventsProcessor<std::pair<Part, NetQueryPtr>>(parts_manager_.get_ready_prefix_count());
}
if (file_info.need_delay) {
- delay_dispatcher_ = create_actor<DelayDispatcher>("DelayDispatcher", 0.003);
+ delay_dispatcher_ = create_actor<DelayDispatcher>("DelayDispatcher", 0.003, actor_shared(this, 1));
next_delay_ = 0.05;
}
resource_state_.set_unit_size(parts_manager_.get_part_size());
update_estimated_limit();
- on_progress_impl(narrow_cast<size_t>(parts_manager_.get_ready_size()));
+ on_progress_impl();
yield();
}
@@ -105,7 +153,7 @@ void FileLoader::loop() {
}
auto status = do_loop();
if (status.is_error()) {
- if (status.code() == 1) {
+ if (status.code() == -1) {
return;
}
on_error(std::move(status));
@@ -113,23 +161,24 @@ void FileLoader::loop() {
return;
}
}
+
Status FileLoader::do_loop() {
TRY_RESULT(check_info,
check_loop(parts_manager_.get_checked_prefix_size(), parts_manager_.get_unchecked_ready_prefix_size(),
parts_manager_.unchecked_ready()));
if (check_info.changed) {
- on_progress_impl(narrow_cast<size_t>(parts_manager_.get_ready_size()));
+ on_progress_impl();
}
for (auto &query : check_info.queries) {
G()->net_query_dispatcher().dispatch_with_callback(
- std::move(query), actor_shared(this, UniqueId::next(UniqueId::Type::Default, CommonQueryKey)));
+ std::move(query), actor_shared(this, UniqueId::next(UniqueId::Type::Default, COMMON_QUERY_KEY)));
}
if (check_info.need_check) {
parts_manager_.set_need_check();
parts_manager_.set_checked_prefix_size(check_info.checked_prefix_size);
}
- if (parts_manager_.ready()) {
+ if (parts_manager_.may_finish()) {
TRY_STATUS(parts_manager_.finish());
TRY_STATUS(on_ok(parts_manager_.get_size()));
LOG(INFO) << "Bad download order rate: "
@@ -147,18 +196,18 @@ Status FileLoader::do_loop() {
if (blocking_id_ != 0) {
break;
}
- if (resource_state_.unused() < static_cast<int64>(parts_manager_.get_part_size())) {
- VLOG(files) << "Got only " << resource_state_.unused() << " resource";
+ if (resource_state_.unused() < narrow_cast<int64>(parts_manager_.get_part_size())) {
+ VLOG(file_loader) << "Got only " << resource_state_.unused() << " resource";
break;
}
TRY_RESULT(part, parts_manager_.start_part());
if (part.size == 0) {
break;
}
- VLOG(files) << "Start part " << tag("id", part.id) << tag("size", part.size);
+ VLOG(file_loader) << "Start part " << tag("id", part.id) << tag("size", part.size);
resource_state_.start_use(static_cast<int64>(part.size));
- TRY_RESULT(query_flag, start_part(part, parts_manager_.get_part_count()));
+ TRY_RESULT(query_flag, start_part(part, parts_manager_.get_part_count(), parts_manager_.get_streaming_offset()));
NetQueryPtr query;
bool is_blocking;
std::tie(query, is_blocking) = std::move(query_flag);
@@ -174,9 +223,10 @@ Status FileLoader::do_loop() {
if (delay_dispatcher_.empty()) {
G()->net_query_dispatcher().dispatch_with_callback(std::move(query), std::move(callback));
} else {
+ query->debug("sent to DelayDispatcher");
send_closure(delay_dispatcher_, &DelayDispatcher::send_with_callback_and_delay, std::move(query),
std::move(callback), next_delay_);
- next_delay_ = std::max(next_delay_ * 0.8, 0.003);
+ next_delay_ = max(next_delay_ * 0.8, 0.003);
}
}
return Status::OK();
@@ -186,14 +236,19 @@ void FileLoader::tear_down() {
for (auto &it : part_map_) {
it.second.second.reset(); // cancel_query(it.second.second);
}
+ ordered_parts_.clear([](auto &&part) { part.second->clear(); });
+ if (!delay_dispatcher_.empty()) {
+ send_closure(std::move(delay_dispatcher_), &DelayDispatcher::close_silent);
+ }
}
+
void FileLoader::update_estimated_limit() {
if (stop_flag_) {
return;
}
- auto estimated_exta = parts_manager_.get_expected_size() - parts_manager_.get_ready_size();
- resource_state_.update_estimated_limit(estimated_exta);
- VLOG(files) << "update estimated limit " << estimated_exta;
+ auto estimated_extra = parts_manager_.get_estimated_extra();
+ resource_state_.update_estimated_limit(estimated_extra);
+ VLOG(file_loader) << "Update estimated limit " << estimated_extra;
if (!resource_manager_.empty()) {
keep_fd_flag(narrow_cast<uint64>(resource_state_.active_limit()) >= parts_manager_.get_part_size());
send_closure(resource_manager_, &ResourceManager::update_resources, resource_state_);
@@ -208,7 +263,7 @@ void FileLoader::on_result(NetQueryPtr query) {
if (id == blocking_id_) {
blocking_id_ = 0;
}
- if (UniqueId::extract_key(id) == CommonQueryKey) {
+ if (UniqueId::extract_key(id) == COMMON_QUERY_KEY) {
on_common_query(std::move(query));
return loop();
}
@@ -221,12 +276,16 @@ void FileLoader::on_result(NetQueryPtr query) {
Part part = it->second.first;
it->second.second.release();
CHECK(query->is_ready());
+ part_map_.erase(it);
bool next = false;
auto status = [&] {
TRY_RESULT(should_restart, should_restart_part(part, query));
+ if (query->is_error() && query->error().code() == NetQuery::Error::Canceled) {
+ should_restart = true;
+ }
if (should_restart) {
- VLOG(files) << "Restart part " << tag("id", part.id) << tag("size", part.size);
+ VLOG(file_loader) << "Restart part " << tag("id", part.id) << tag("size", part.size);
resource_state_.stop_use(static_cast<int64>(part.size));
parts_manager_.on_part_failed(part.id);
} else {
@@ -243,8 +302,9 @@ void FileLoader::on_result(NetQueryPtr query) {
if (next) {
if (ordered_flag_) {
auto seq_no = part.id;
- ordered_parts_.add(seq_no, std::make_pair(part, std::move(query)),
- [this](auto seq_no, auto &&p) { this->on_part_query(p.first, std::move(p.second)); });
+ ordered_parts_.add(
+ seq_no, std::make_pair(part, std::move(query)),
+ [this](uint64 seq_no, std::pair<Part, NetQueryPtr> &&p) { on_part_query(p.first, std::move(p.second)); });
} else {
on_part_query(part, std::move(query));
}
@@ -254,6 +314,10 @@ void FileLoader::on_result(NetQueryPtr query) {
}
void FileLoader::on_part_query(Part part, NetQueryPtr query) {
+ if (stop_flag_) {
+ // important for secret files
+ return;
+ }
auto status = try_on_part_query(part, std::move(query));
if (status.is_error()) {
on_error(std::move(status));
@@ -271,22 +335,30 @@ void FileLoader::on_common_query(NetQueryPtr query) {
Status FileLoader::try_on_part_query(Part part, NetQueryPtr query) {
TRY_RESULT(size, process_part(part, std::move(query)));
- VLOG(files) << "Ok part " << tag("id", part.id) << tag("size", part.size);
+ VLOG(file_loader) << "Ok part " << tag("id", part.id) << tag("size", part.size);
resource_state_.stop_use(static_cast<int64>(part.size));
- auto old_ready_prefix_count = parts_manager_.get_ready_prefix_count();
+ auto old_ready_prefix_count = parts_manager_.get_unchecked_ready_prefix_count();
TRY_STATUS(parts_manager_.on_part_ok(part.id, part.size, size));
- auto new_ready_prefix_count = parts_manager_.get_ready_prefix_count();
+ auto new_ready_prefix_count = parts_manager_.get_unchecked_ready_prefix_count();
debug_total_parts_++;
if (old_ready_prefix_count == new_ready_prefix_count) {
debug_bad_parts_.push_back(part.id);
debug_bad_part_order_++;
}
- on_progress_impl(size);
+ on_progress_impl();
return Status::OK();
}
-void FileLoader::on_progress_impl(size_t size) {
- on_progress(parts_manager_.get_part_count(), static_cast<int32>(parts_manager_.get_part_size()),
- parts_manager_.get_ready_prefix_count(), parts_manager_.ready(), parts_manager_.get_ready_size());
+void FileLoader::on_progress_impl() {
+ Progress progress;
+ progress.part_count = parts_manager_.get_part_count();
+ progress.part_size = static_cast<int32>(parts_manager_.get_part_size());
+ progress.ready_part_count = parts_manager_.get_ready_prefix_count();
+ progress.ready_bitmask = parts_manager_.get_bitmask();
+ progress.is_ready = parts_manager_.ready();
+ progress.ready_size = parts_manager_.get_ready_size();
+ progress.size = parts_manager_.get_size_or_zero();
+ on_progress(std::move(progress));
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.h
index a97a5199e9..941692965a 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoader.h
@@ -1,14 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
+#include "td/telegram/DelayDispatcher.h"
#include "td/telegram/files/FileLoaderActor.h"
#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/PartsManager.h"
@@ -16,7 +14,7 @@
#include "td/telegram/files/ResourceState.h"
#include "td/telegram/net/NetQuery.h"
-#include "td/telegram/DelayDispatcher.h"
+#include "td/actor/actor.h"
#include "td/utils/OrderedEventsProcessor.h"
#include "td/utils/Status.h"
@@ -25,6 +23,7 @@
#include <utility>
namespace td {
+
class FileLoader : public FileLoaderActor {
public:
class Callback {
@@ -34,11 +33,12 @@ class FileLoader : public FileLoaderActor {
Callback &operator=(const Callback &) = delete;
virtual ~Callback() = default;
};
- void set_resource_manager(ActorShared<ResourceManager> resource_manager) override;
- void update_priority(int8 priority) override;
- void update_resources(const ResourceState &other) override;
+ void set_resource_manager(ActorShared<ResourceManager> resource_manager) final;
+ void update_priority(int8 priority) final;
+ void update_resources(const ResourceState &other) final;
- void update_local_file_location(const LocalFileLocation &local) override;
+ void update_local_file_location(const LocalFileLocation &local) final;
+ void update_downloaded_part(int64 offset, int64 limit, int64 max_resource_limit) final;
protected:
void set_ordered_flag(bool flag);
@@ -49,14 +49,17 @@ class FileLoader : public FileLoaderActor {
bool is_ready = false;
};
struct FileInfo {
- int64 size;
- int64 expected_size = 0;
- bool is_size_final;
- int32 part_size;
+ int64 size{0};
+ int64 expected_size{0};
+ bool is_size_final{false};
+ int32 part_size{0};
std::vector<int> ready_parts;
- bool use_part_count_limit = true;
- bool only_check = false;
- bool need_delay = false;
+ bool use_part_count_limit{true};
+ bool only_check{false};
+ bool need_delay{false};
+ int64 offset{0};
+ int64 limit{0};
+ bool is_upload{false};
};
virtual Result<FileInfo> init() TD_WARN_UNUSED_RESULT = 0;
virtual Status on_ok(int64 size) TD_WARN_UNUSED_RESULT = 0;
@@ -64,22 +67,32 @@ class FileLoader : public FileLoaderActor {
virtual Status before_start_parts() {
return Status::OK();
}
- virtual Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int part_count) TD_WARN_UNUSED_RESULT = 0;
+ virtual Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int part_count,
+ int64 streaming_offset) TD_WARN_UNUSED_RESULT = 0;
virtual void after_start_parts() {
}
virtual Result<size_t> process_part(Part part, NetQueryPtr net_query) TD_WARN_UNUSED_RESULT = 0;
- virtual void on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready,
- int64 ready_size) = 0;
+ struct Progress {
+ int32 part_count{0};
+ int32 part_size{0};
+ int32 ready_part_count{0};
+ string ready_bitmask;
+ bool is_ready{false};
+ int64 ready_size{0};
+ int64 size{0};
+ };
+ virtual void on_progress(Progress progress) = 0;
virtual Callback *get_callback() = 0;
- virtual Result<PrefixInfo> on_update_local_location(const LocalFileLocation &location) TD_WARN_UNUSED_RESULT {
- return Status::Error("unsupported");
+ virtual Result<PrefixInfo> on_update_local_location(const LocalFileLocation &location,
+ int64 file_size) TD_WARN_UNUSED_RESULT {
+ return Status::Error("Unsupported");
}
virtual Result<bool> should_restart_part(Part part, NetQueryPtr &net_query) TD_WARN_UNUSED_RESULT {
return false;
}
virtual Status process_check_query(NetQueryPtr net_query) {
- return Status::Error("unsupported");
+ return Status::Error("Unsupported");
}
struct CheckInfo {
bool need_check{false};
@@ -95,14 +108,13 @@ class FileLoader : public FileLoaderActor {
}
private:
- enum { CommonQueryKey = 2 };
+ static constexpr uint8 COMMON_QUERY_KEY = 2;
bool stop_flag_ = false;
ActorShared<ResourceManager> resource_manager_;
ResourceState resource_state_;
PartsManager parts_manager_;
uint64 blocking_id_{0};
std::map<uint64, std::pair<Part, ActorShared<>>> part_map_;
- // std::map<uint64, std::pair<Part, NetQueryRef>> part_map_;
bool ordered_flag_ = false;
OrderedEventsProcessor<std::pair<Part, NetQueryPtr>> ordered_parts_;
ActorOwn<DelayDispatcher> delay_dispatcher_;
@@ -112,18 +124,20 @@ class FileLoader : public FileLoaderActor {
uint32 debug_bad_part_order_ = 0;
std::vector<int32> debug_bad_parts_;
- void start_up() override;
- void loop() override;
+ void start_up() final;
+ void loop() final;
Status do_loop();
- void hangup() override;
- void tear_down() override;
+ void hangup() final;
+ void hangup_shared() final;
+ void tear_down() final;
void update_estimated_limit();
- void on_progress_impl(size_t size);
+ void on_progress_impl();
- void on_result(NetQueryPtr query) override;
+ void on_result(NetQueryPtr query) final;
void on_part_query(Part part, NetQueryPtr query);
void on_common_query(NetQueryPtr query);
Status try_on_part_query(Part part, NetQueryPtr query);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderActor.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderActor.h
index d802f589ca..3bbb6e79dd 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderActor.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderActor.h
@@ -1,28 +1,32 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/ResourceState.h"
#include "td/telegram/net/NetQuery.h"
+#include "td/actor/actor.h"
+
namespace td {
-class LocalFileLocation;
class ResourceManager;
class FileLoaderActor : public NetQueryCallback {
public:
- virtual void set_resource_manager(ActorShared<ResourceManager>) = 0;
+ virtual void set_resource_manager(ActorShared<ResourceManager> resource_manager) = 0;
virtual void update_priority(int8 priority) = 0;
virtual void update_resources(const ResourceState &other) = 0;
- // TODO: existence of this function is a dirty hack. Refactoring is highly appreciated
+ // TODO: existence of these two functions is a dirty hack. Refactoring is highly appreciated
virtual void update_local_file_location(const LocalFileLocation &local) {
}
+ virtual void update_downloaded_part(int64 offset, int64 limit, int64 max_resource_limit) {
+ }
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.cpp
index 1e89cb72ee..5e2e12d4cc 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.cpp
@@ -1,37 +1,64 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileLoaderUtils.h"
-#include "td/telegram/files/FileLocation.h"
#include "td/telegram/Global.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
#include "td/utils/common.h"
#include "td/utils/filesystem.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/PathView.h"
-#include "td/utils/port/FileFd.h"
+#include "td/utils/port/Clocks.h"
#include "td/utils/port/path.h"
+#include "td/utils/port/Stat.h"
#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/StringBuilder.h"
+#include "td/utils/utf8.h"
#include <tuple>
namespace td {
+int VERBOSITY_NAME(file_loader) = VERBOSITY_NAME(DEBUG) + 2;
+
namespace {
-Result<std::pair<FileFd, string>> try_create_new_file(Result<CSlice> result_name) {
- TRY_RESULT(name, std::move(result_name));
- TRY_RESULT(fd, FileFd::open(name, FileFd::Read | FileFd::Write | FileFd::CreateNew, 0640));
- return std::make_pair(std::move(fd), name.str());
+
+Result<std::pair<FileFd, string>> try_create_new_file(CSlice path, CSlice file_name) {
+ LOG(DEBUG) << "Trying to create new file \"" << file_name << "\" in the directory \"" << path << '"';
+ auto name = PSTRING() << path << file_name;
+ auto r_fd = FileFd::open(name, FileFd::Read | FileFd::Write | FileFd::CreateNew, 0640);
+ if (r_fd.is_error()) {
+ auto status = mkdir(path, 0750);
+ if (status.is_error()) {
+ auto r_stat = stat(path);
+ if (r_stat.is_ok() && r_stat.ok().is_dir_) {
+ LOG(ERROR) << "Creation of directory \"" << path << "\" failed with " << status << ", but directory exists";
+ } else {
+ LOG(ERROR) << "Creation of directory \"" << path << "\" failed with " << status;
+ }
+ return r_fd.move_as_error();
+ }
+#if TD_ANDROID
+ FileFd::open(PSLICE() << path << ".nomedia", FileFd::Create | FileFd::Read).ignore();
+#endif
+ r_fd = FileFd::open(name, FileFd::Read | FileFd::Write | FileFd::CreateNew, 0640);
+ if (r_fd.is_error()) {
+ return r_fd.move_as_error();
+ }
+ }
+ return std::make_pair(r_fd.move_as_ok(), std::move(name));
}
-Result<std::pair<FileFd, string>> try_open_file(Result<CSlice> result_name) {
- TRY_RESULT(name, std::move(result_name));
+
+Result<std::pair<FileFd, string>> try_open_file(CSlice name) {
+ LOG(DEBUG) << "Trying to open file " << name;
TRY_RESULT(fd, FileFd::open(name, FileFd::Read, 0640));
return std::make_pair(std::move(fd), name.str());
}
@@ -45,6 +72,7 @@ StringBuilder &operator<<(StringBuilder &sb, const RandSuff &) {
}
return sb;
}
+
struct Ext {
Slice ext;
};
@@ -56,59 +84,54 @@ StringBuilder &operator<<(StringBuilder &sb, const Ext &ext) {
}
} // namespace
-Result<std::pair<FileFd, string>> open_temp_file(const FileType &file_type) {
+Result<std::pair<FileFd, string>> open_temp_file(FileType file_type) {
auto pmc = G()->td_db()->get_binlog_pmc();
// TODO: CAS?
- int32 file_id = to_integer<int32>(pmc->get("tmp_file_id"));
- pmc->set("tmp_file_id", to_string(file_id + 1));
+ auto file_id = pmc->get("tmp_file_id");
+ pmc->set("tmp_file_id", to_string(to_integer<int32>(file_id) + 1));
auto temp_dir = get_files_temp_dir(file_type);
- auto res = try_create_new_file(PSLICE_SAFE() << temp_dir << file_id);
+ auto res = try_create_new_file(temp_dir, file_id);
if (res.is_error()) {
- res = try_create_new_file(PSLICE_SAFE() << temp_dir << file_id << "_" << RandSuff{6});
+ res = try_create_new_file(temp_dir, PSLICE() << file_id << '_' << RandSuff{6});
}
return res;
}
template <class F>
bool for_suggested_file_name(CSlice name, bool use_pmc, bool use_random, F &&callback) {
- auto try_callback = [&](Result<CSlice> r_path) {
- if (r_path.is_error()) {
- return true;
- }
- return callback(r_path.move_as_ok());
- };
auto cleaned_name = clean_filename(name);
PathView path_view(cleaned_name);
auto stem = path_view.file_stem();
auto ext = path_view.extension();
bool active = true;
if (!stem.empty() && !G()->parameters().ignore_file_names) {
- active = try_callback(PSLICE_SAFE() << stem << Ext{ext});
+ active = callback(PSLICE() << stem << Ext{ext});
for (int i = 0; active && i < 10; i++) {
- active = try_callback(PSLICE_SAFE() << stem << "_(" << i << ")" << Ext{ext});
+ active = callback(PSLICE() << stem << "_(" << i << ")" << Ext{ext});
}
for (int i = 2; active && i < 12 && use_random; i++) {
- active = try_callback(PSLICE_SAFE() << stem << "_(" << RandSuff{i} << ")" << Ext{ext});
+ active = callback(PSLICE() << stem << "_(" << RandSuff{i} << ")" << Ext{ext});
}
} else if (use_pmc) {
auto pmc = G()->td_db()->get_binlog_pmc();
- int32 file_id = to_integer<int32>(pmc->get("perm_file_id"));
+ auto file_id = to_integer<int32>(pmc->get("perm_file_id"));
pmc->set("perm_file_id", to_string(file_id + 1));
- active = try_callback(PSLICE_SAFE() << "file_" << file_id << Ext{ext});
+ active = callback(PSLICE() << "file_" << file_id << Ext{ext});
if (active) {
- active = try_callback(PSLICE_SAFE() << "file_" << file_id << "_" << RandSuff{6} << Ext{ext});
+ active = callback(PSLICE() << "file_" << file_id << "_" << RandSuff{6} << Ext{ext});
}
}
return active;
}
-Result<string> create_from_temp(CSlice temp_path, CSlice dir, CSlice name) {
- LOG(INFO) << "Create file in directory " << dir << " with suggested name " << name << " from temporary file "
- << temp_path;
+Result<string> create_from_temp(FileType file_type, CSlice temp_path, CSlice name) {
+ auto dir = get_files_dir(file_type);
+ LOG(INFO) << "Create file of type " << file_type << " in directory " << dir << " with suggested name " << name
+ << " from temporary file " << temp_path;
Result<std::pair<FileFd, string>> res = Status::Error(500, "Can't find suitable file name");
for_suggested_file_name(name, true, true, [&](CSlice suggested_name) {
- res = try_create_new_file(PSLICE_SAFE() << dir << suggested_name);
+ res = try_create_new_file(dir, suggested_name);
return res.is_error();
});
TRY_RESULT(tmp, std::move(res));
@@ -118,17 +141,19 @@ Result<string> create_from_temp(CSlice temp_path, CSlice dir, CSlice name) {
return perm_path;
}
-Result<string> search_file(CSlice dir, CSlice name, int64 expected_size) {
- Result<std::string> res = Status::Error(500, "Can't find suitable file name");
+Result<string> search_file(FileType file_type, CSlice name, int64 expected_size) {
+ Result<string> res = Status::Error(500, "Can't find suitable file name");
+ auto dir = get_files_dir(file_type);
for_suggested_file_name(name, false, false, [&](CSlice suggested_name) {
- auto r_pair = try_open_file(PSLICE_SAFE() << dir << suggested_name);
+ auto r_pair = try_open_file(PSLICE() << dir << suggested_name);
if (r_pair.is_error()) {
return false;
}
FileFd fd;
- std::string path;
+ string path;
std::tie(fd, path) = r_pair.move_as_ok();
- if (fd.stat().size_ != expected_size) {
+ auto r_size = fd.get_size();
+ if (r_size.is_error() || r_size.ok() != expected_size) {
return true;
}
fd.close();
@@ -138,31 +163,195 @@ Result<string> search_file(CSlice dir, CSlice name, int64 expected_size) {
return res;
}
-const char *file_type_name[file_type_size] = {"thumbnails", "profile_photos", "photos", "voice",
- "videos", "documents", "secret", "temp",
- "stickers", "music", "animations", "secret_thumbnails",
- "wallpapers", "video_notes"};
+Result<string> get_suggested_file_name(CSlice directory, Slice file_name) {
+ string cleaned_name = clean_filename(file_name.str());
+ file_name = cleaned_name;
+
+ if (directory.empty()) {
+ directory = CSlice("./");
+ }
+
+ auto dir_stat = stat(directory);
+ if (dir_stat.is_error() || !dir_stat.ok().is_dir_) {
+ return cleaned_name;
+ }
-string get_file_base_dir(const FileDirType &file_dir_type) {
+ PathView path_view(file_name);
+ auto stem = path_view.file_stem();
+ auto ext = path_view.extension();
+
+ if (stem.empty()) {
+ return cleaned_name;
+ }
+
+ Slice directory_slice = directory;
+ while (directory_slice.size() > 1 && (directory_slice.back() == '/' || directory_slice.back() == '\\')) {
+ directory_slice.remove_suffix(1);
+ }
+
+ auto check_file_name = [directory_slice](Slice name) {
+ return stat(PSLICE() << directory_slice << TD_DIR_SLASH << name).is_error(); // in case of success, the name is bad
+ };
+
+ string checked_name = PSTRING() << stem << Ext{ext};
+ if (check_file_name(checked_name)) {
+ return checked_name;
+ }
+
+ for (int i = 1; i < 100; i++) {
+ checked_name = PSTRING() << stem << " (" << i << ")" << Ext{ext};
+ if (check_file_name(checked_name)) {
+ return checked_name;
+ }
+ }
+
+ return PSTRING() << stem << " - " << StringBuilder::FixedDouble(Clocks::system(), 3) << Ext{ext};
+}
+
+Result<FullLocalFileLocation> save_file_bytes(FileType file_type, BufferSlice bytes, CSlice file_name) {
+ auto r_old_path = search_file(file_type, file_name, bytes.size());
+ if (r_old_path.is_ok()) {
+ auto r_old_bytes = read_file(r_old_path.ok());
+ if (r_old_bytes.is_ok() && r_old_bytes.ok().as_slice() == bytes.as_slice()) {
+ LOG(INFO) << "Found previous file with the same name " << r_old_path.ok();
+ return FullLocalFileLocation(file_type, r_old_path.ok(), 0);
+ }
+ }
+
+ TRY_RESULT(fd_path, open_temp_file(file_type));
+ FileFd fd = std::move(fd_path.first);
+ string path = std::move(fd_path.second);
+
+ TRY_RESULT(size, fd.write(bytes.as_slice()));
+ fd.close();
+
+ if (size != bytes.size()) {
+ return Status::Error("Failed to write bytes to the file");
+ }
+
+ TRY_RESULT(perm_path, create_from_temp(file_type, path, file_name));
+
+ return FullLocalFileLocation(file_type, std::move(perm_path), 0);
+}
+
+static Slice get_file_base_dir(const FileDirType &file_dir_type) {
switch (file_dir_type) {
case FileDirType::Secure:
- return G()->get_dir().str();
+ return G()->get_secure_files_dir();
case FileDirType::Common:
- return G()->get_files_dir().str();
+ return G()->get_files_dir();
default:
UNREACHABLE();
- return "";
+ return Slice();
}
}
-string get_files_base_dir(const FileType &file_type) {
+Slice get_files_base_dir(FileType file_type) {
return get_file_base_dir(get_file_dir_type(file_type));
}
-string get_files_temp_dir(const FileType &file_type) {
- return get_files_base_dir(file_type) + "temp" + TD_DIR_SLASH;
+
+string get_files_temp_dir(FileType file_type) {
+ return PSTRING() << get_files_base_dir(file_type) << "temp" << TD_DIR_SLASH;
+}
+
+string get_files_dir(FileType file_type) {
+ return PSTRING() << get_files_base_dir(file_type) << get_file_type_name(file_type) << TD_DIR_SLASH;
+}
+
+bool are_modification_times_equal(int64 old_mtime, int64 new_mtime) {
+ if (old_mtime == new_mtime) {
+ return true;
+ }
+ if (old_mtime < new_mtime) {
+ return false;
+ }
+ if (old_mtime - new_mtime == 1000000000 && old_mtime % 1000000000 == 0 && new_mtime % 2000000000 == 0) {
+ // FAT32 has 2 seconds mtime resolution, but file system sometimes reports odd modification time
+ return true;
+ }
+ return false;
+}
+
+Result<FullLocalLocationInfo> check_full_local_location(FullLocalLocationInfo local_info, bool skip_file_size_checks) {
+ constexpr int64 MAX_FILE_SIZE = static_cast<int64>(4000) << 20 /* 4000 MB */;
+ constexpr int64 MAX_THUMBNAIL_SIZE = 200 * (1 << 10) - 1 /* 200 KB - 1 B */;
+ constexpr int64 MAX_PHOTO_SIZE = 10 * (1 << 20) /* 10 MB */;
+ constexpr int64 DEFAULT_VIDEO_NOTE_SIZE_MAX = 12 * (1 << 20) /* 12 MB */;
+
+ FullLocalFileLocation &location = local_info.location_;
+ int64 &size = local_info.size_;
+ if (location.path_.empty()) {
+ return Status::Error(400, "File must have non-empty path");
+ }
+ auto r_path = realpath(location.path_, true);
+ if (r_path.is_error()) {
+ return Status::Error(400, "Can't find real file path");
+ }
+ location.path_ = r_path.move_as_ok();
+
+ auto r_stat = stat(location.path_);
+ if (r_stat.is_error()) {
+ return Status::Error(400, "Can't get stat about the file");
+ }
+ auto stat = r_stat.move_as_ok();
+ if (!stat.is_reg_) {
+ return Status::Error(400, "File must be a regular file");
+ }
+ if (stat.size_ < 0) {
+ // TODO is it possible?
+ return Status::Error(400, "File is too big");
+ }
+ if (stat.size_ == 0) {
+ return Status::Error(400, "File must be non-empty");
+ }
+
+ if (size == 0) {
+ size = stat.size_;
+ }
+ if (location.mtime_nsec_ == 0) {
+ VLOG(file_loader) << "Set file \"" << location.path_ << "\" modification time to " << stat.mtime_nsec_;
+ location.mtime_nsec_ = stat.mtime_nsec_;
+ } else if (!are_modification_times_equal(location.mtime_nsec_, stat.mtime_nsec_)) {
+ VLOG(file_loader) << "File \"" << location.path_ << "\" was modified: old mtime = " << location.mtime_nsec_
+ << ", new mtime = " << stat.mtime_nsec_;
+ return Status::Error(400, PSLICE() << "File \"" << utf8_encode(location.path_) << "\" was modified");
+ }
+ if (skip_file_size_checks) {
+ return std::move(local_info);
+ }
+
+ auto get_file_size_error = [&](Slice reason) {
+ return Status::Error(400, PSLICE() << "File \"" << utf8_encode(location.path_) << "\" of size " << size
+ << " bytes is too big" << reason);
+ };
+ if ((location.file_type_ == FileType::Thumbnail || location.file_type_ == FileType::EncryptedThumbnail) &&
+ size > MAX_THUMBNAIL_SIZE && !begins_with(PathView(location.path_).file_name(), "map") &&
+ !begins_with(PathView(location.path_).file_name(), "Album cover for ")) {
+ return get_file_size_error(" for a thumbnail");
+ }
+ if (size > MAX_FILE_SIZE) {
+ return get_file_size_error("");
+ }
+ if (location.file_type_ == FileType::Photo && size > MAX_PHOTO_SIZE) {
+ return get_file_size_error(" for a photo");
+ }
+ if (location.file_type_ == FileType::VideoNote &&
+ size > G()->get_option_integer("video_note_size_max", DEFAULT_VIDEO_NOTE_SIZE_MAX)) {
+ return get_file_size_error(" for a video note");
+ }
+ return std::move(local_info);
}
-string get_files_dir(const FileType &file_type) {
- return get_files_base_dir(file_type) + file_type_name[static_cast<int32>(file_type)] + TD_DIR_SLASH;
+
+Status check_partial_local_location(const PartialLocalFileLocation &location) {
+ TRY_RESULT(stat, stat(location.path_));
+ if (!stat.is_reg_) {
+ if (stat.is_dir_) {
+ return Status::Error(PSLICE() << "Can't use directory \"" << location.path_ << "\" as a file path");
+ }
+ return Status::Error("File must be a regular file");
+ }
+ // can't check mtime. Hope nobody will mess with this files in our temporary dir.
+ return Status::OK();
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.h
index 7eaf1e12e8..7a641f21fa 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileLoaderUtils.h
@@ -1,12 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileType.h"
+
+#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/logging.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
@@ -14,12 +19,37 @@
#include <utility>
namespace td {
-enum class FileType : int8;
-
-Result<std::pair<FileFd, string>> open_temp_file(const FileType &file_type) TD_WARN_UNUSED_RESULT;
-Result<string> create_from_temp(CSlice temp_path, CSlice dir, CSlice name) TD_WARN_UNUSED_RESULT;
-Result<string> search_file(CSlice dir, CSlice name, int64 expected_size) TD_WARN_UNUSED_RESULT;
-string get_files_base_dir(const FileType &file_type);
-string get_files_temp_dir(const FileType &file_type);
-string get_files_dir(const FileType &file_type);
+
+extern int VERBOSITY_NAME(file_loader);
+
+Result<std::pair<FileFd, string>> open_temp_file(FileType file_type) TD_WARN_UNUSED_RESULT;
+
+Result<string> create_from_temp(FileType file_type, CSlice temp_path, CSlice name) TD_WARN_UNUSED_RESULT;
+
+Result<string> search_file(FileType type, CSlice name, int64 expected_size) TD_WARN_UNUSED_RESULT;
+
+Result<string> get_suggested_file_name(CSlice dir, Slice file_name) TD_WARN_UNUSED_RESULT;
+
+Result<FullLocalFileLocation> save_file_bytes(FileType file_type, BufferSlice bytes, CSlice file_name);
+
+Slice get_files_base_dir(FileType file_type);
+
+string get_files_temp_dir(FileType file_type);
+
+string get_files_dir(FileType file_type);
+
+bool are_modification_times_equal(int64 old_mtime, int64 new_mtime);
+
+struct FullLocalLocationInfo {
+ FullLocalFileLocation location_;
+ int64 size_ = 0;
+
+ FullLocalLocationInfo(const FullLocalFileLocation &location, int64 size) : location_(location), size_(size) {
+ }
+};
+
+Result<FullLocalLocationInfo> check_full_local_location(FullLocalLocationInfo local_info, bool skip_file_size_checks);
+
+Status check_partial_local_location(const PartialLocalFileLocation &location);
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileLocation.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileLocation.h
index 7733b65d2a..4a80c9251c 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileLocation.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileLocation.h
@@ -1,215 +1,38 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
-#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileBitmask.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/net/DcId.h"
+#include "td/telegram/PhotoSizeSource.h"
+#include "td/telegram/telegram_api.h"
+#include "td/utils/base64.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
-#include "td/utils/crypto.h"
#include "td/utils/format.h"
-#include "td/utils/int_types.h"
#include "td/utils/logging.h"
-#include "td/utils/Random.h"
#include "td/utils/Slice.h"
#include "td/utils/StringBuilder.h"
-#include "td/utils/tl_helpers.h"
-#include "td/utils/tl_storers.h"
#include "td/utils/Variant.h"
-#include <cstring>
#include <tuple>
+#include <utility>
namespace td {
-enum class FileType : int8 {
- Thumbnail,
- ProfilePhoto,
- Photo,
- VoiceNote,
- Video,
- Document,
- Encrypted,
- Temp,
- Sticker,
- Audio,
- Animation,
- EncryptedThumbnail,
- Wallpaper,
- VideoNote,
- Size,
- None
-};
-
-inline FileType from_td_api(const td_api::FileType &file_type) {
- switch (file_type.get_id()) {
- case td_api::fileTypeThumbnail::ID:
- return FileType::Thumbnail;
- case td_api::fileTypeProfilePhoto::ID:
- return FileType::ProfilePhoto;
- case td_api::fileTypePhoto::ID:
- return FileType::Photo;
- case td_api::fileTypeVoiceNote::ID:
- return FileType::VoiceNote;
- case td_api::fileTypeVideo::ID:
- return FileType::Video;
- case td_api::fileTypeDocument::ID:
- return FileType::Document;
- case td_api::fileTypeSecret::ID:
- return FileType::Encrypted;
- case td_api::fileTypeUnknown::ID:
- return FileType::Temp;
- case td_api::fileTypeSticker::ID:
- return FileType::Sticker;
- case td_api::fileTypeAudio::ID:
- return FileType::Audio;
- case td_api::fileTypeAnimation::ID:
- return FileType::Animation;
- case td_api::fileTypeSecretThumbnail::ID:
- return FileType::EncryptedThumbnail;
- case td_api::fileTypeWallpaper::ID:
- return FileType::Wallpaper;
- case td_api::fileTypeVideoNote::ID:
- return FileType::VideoNote;
- case td_api::fileTypeNone::ID:
- return FileType::None;
- default:
- UNREACHABLE();
- return FileType::None;
- }
-}
-
-inline tl_object_ptr<td_api::FileType> as_td_api(FileType file_type) {
- switch (file_type) {
- case FileType::Thumbnail:
- return make_tl_object<td_api::fileTypeThumbnail>();
- case FileType::ProfilePhoto:
- return make_tl_object<td_api::fileTypeProfilePhoto>();
- case FileType::Photo:
- return make_tl_object<td_api::fileTypePhoto>();
- case FileType::VoiceNote:
- return make_tl_object<td_api::fileTypeVoiceNote>();
- case FileType::Video:
- return make_tl_object<td_api::fileTypeVideo>();
- case FileType::Document:
- return make_tl_object<td_api::fileTypeDocument>();
- case FileType::Encrypted:
- return make_tl_object<td_api::fileTypeSecret>();
- case FileType::Temp:
- return make_tl_object<td_api::fileTypeUnknown>();
- case FileType::Sticker:
- return make_tl_object<td_api::fileTypeSticker>();
- case FileType::Audio:
- return make_tl_object<td_api::fileTypeAudio>();
- case FileType::Animation:
- return make_tl_object<td_api::fileTypeAnimation>();
- case FileType::EncryptedThumbnail:
- return make_tl_object<td_api::fileTypeSecretThumbnail>();
- case FileType::Wallpaper:
- return make_tl_object<td_api::fileTypeWallpaper>();
- case FileType::VideoNote:
- return make_tl_object<td_api::fileTypeVideoNote>();
- case FileType::None:
- return make_tl_object<td_api::fileTypeNone>();
- default:
- UNREACHABLE();
- return nullptr;
- }
-}
-
-enum class FileDirType : int8 { Secure, Common };
-inline FileDirType get_file_dir_type(FileType file_type) {
- switch (file_type) {
- case FileType::Thumbnail:
- case FileType::ProfilePhoto:
- case FileType::Encrypted:
- case FileType::Sticker:
- case FileType::Temp:
- case FileType::Wallpaper:
- case FileType::EncryptedThumbnail:
- return FileDirType::Secure;
- default:
- return FileDirType::Common;
- }
-}
-
-constexpr int32 file_type_size = static_cast<int32>(FileType::Size);
-extern const char *file_type_name[file_type_size];
-
-struct FileEncryptionKey {
- FileEncryptionKey() = default;
- FileEncryptionKey(Slice key, Slice iv) : key_iv_(key.size() + iv.size(), '\0') {
- if (key.size() != 32 || iv.size() != 32) {
- LOG(ERROR) << "Wrong key/iv sizes: " << key.size() << " " << iv.size();
- return;
- }
- CHECK(key_iv_.size() == 64);
- std::memcpy(&key_iv_[0], key.data(), key.size());
- std::memcpy(&key_iv_[key.size()], iv.data(), iv.size());
- }
- static FileEncryptionKey create() {
- FileEncryptionKey res;
- res.key_iv_.resize(64);
- Random::secure_bytes(res.key_iv_);
- return res;
- }
-
- const UInt256 &key() const {
- CHECK(key_iv_.size() == 64);
- return *reinterpret_cast<const UInt256 *>(key_iv_.data());
- }
- Slice key_slice() const {
- CHECK(key_iv_.size() == 64);
- return Slice(key_iv_.data(), 32);
- }
-
- UInt256 &mutable_iv() {
- CHECK(key_iv_.size() == 64);
- return *reinterpret_cast<UInt256 *>(&key_iv_[0] + 32);
- }
- Slice iv_slice() const {
- CHECK(key_iv_.size() == 64);
- return Slice(key_iv_.data() + 32, 32);
- }
-
- int32 calc_fingerprint() const {
- char buf[16];
- md5(key_iv_, {buf, sizeof(buf)});
- return as<int32>(buf) ^ as<int32>(buf + 4);
- }
-
- bool empty() const {
- return key_iv_.empty();
- }
-
- template <class StorerT>
- void store(StorerT &storer) const {
- td::store(key_iv_, storer);
- }
- template <class ParserT>
- void parse(ParserT &parser) {
- td::parse(key_iv_, parser);
+class FileReferenceView {
+ public:
+ static Slice invalid_file_reference() {
+ return Slice("#");
}
-
- string key_iv_; // TODO wrong alignment is possible
};
-inline bool operator==(const FileEncryptionKey &lhs, const FileEncryptionKey &rhs) {
- return lhs.key_iv_ == rhs.key_iv_;
-}
-
-inline bool operator!=(const FileEncryptionKey &lhs, const FileEncryptionKey &rhs) {
- return !(lhs == rhs);
-}
-
struct EmptyRemoteFileLocation {
template <class StorerT>
void store(StorerT &storer) const {
@@ -233,24 +56,11 @@ struct PartialRemoteFileLocation {
int32 part_size_;
int32 ready_part_count_;
int32 is_big_;
+
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(file_id_, storer);
- store(part_count_, storer);
- store(part_size_, storer);
- store(ready_part_count_, storer);
- store(is_big_, storer);
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- parse(file_id_, parser);
- parse(part_count_, parser);
- parse(part_size_, parser);
- parse(ready_part_count_, parser);
- parse(is_big_, parser);
- }
+ void parse(ParserT &parser);
};
inline bool operator==(const PartialRemoteFileLocation &lhs, const PartialRemoteFileLocation &rhs) {
@@ -262,57 +72,46 @@ inline bool operator!=(const PartialRemoteFileLocation &lhs, const PartialRemote
return !(lhs == rhs);
}
+inline StringBuilder &operator<<(StringBuilder &sb, const PartialRemoteFileLocation &location) {
+ return sb << '[' << (location.is_big_ ? "Big" : "Small") << " partial remote location with " << location.part_count_
+ << " parts of size " << location.part_size_ << " with " << location.ready_part_count_ << " ready parts]";
+}
+
struct PhotoRemoteFileLocation {
int64 id_;
int64 access_hash_;
- int64 volume_id_;
- int64 secret_;
- int32 local_id_;
+ PhotoSizeSource source_;
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(id_, storer);
- store(access_hash_, storer);
- store(volume_id_, storer);
- store(secret_, storer);
- store(local_id_, storer);
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- parse(id_, parser);
- parse(access_hash_, parser);
- parse(volume_id_, parser);
- parse(secret_, parser);
- parse(local_id_, parser);
- }
+ void parse(ParserT &parser);
+
struct AsKey {
const PhotoRemoteFileLocation &key;
+ bool is_unique;
+
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(key.id_, storer);
- store(key.volume_id_, storer);
- store(key.local_id_, storer);
- }
+ void store(StorerT &storer) const;
};
- AsKey as_key() const {
- return AsKey{*this};
+ AsKey as_key(bool is_unique) const {
+ return AsKey{*this, is_unique};
}
bool operator<(const PhotoRemoteFileLocation &other) const {
- return std::tie(id_, volume_id_, local_id_) < std::tie(other.id_, other.volume_id_, other.local_id_);
+ if (id_ != other.id_) {
+ return id_ < other.id_;
+ }
+ return source_.get_unique() < other.source_.get_unique();
}
bool operator==(const PhotoRemoteFileLocation &other) const {
- return std::tie(id_, volume_id_, local_id_) == std::tie(other.id_, other.volume_id_, other.local_id_);
+ return id_ == other.id_ && source_.get_unique() == other.source_.get_unique();
}
};
inline StringBuilder &operator<<(StringBuilder &string_builder, const PhotoRemoteFileLocation &location) {
- return string_builder << "[id = " << location.id_ << ", access_hash = " << location.access_hash_
- << ", volume_id = " << location.volume_id_ << ", secret = " << location.secret_
- << ", local_id = " << location.local_id_ << "]";
+ return string_builder << "[ID = " << location.id_ << ", access_hash = " << location.access_hash_ << ", "
+ << location.source_ << "]";
}
struct WebRemoteFileLocation {
@@ -320,28 +119,20 @@ struct WebRemoteFileLocation {
int64 access_hash_;
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(url_, storer);
- store(access_hash_, storer);
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- parse(url_, parser);
- parse(access_hash_, parser);
- }
+ void parse(ParserT &parser);
+
struct AsKey {
const WebRemoteFileLocation &key;
+
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(key.url_, storer);
- }
+ void store(StorerT &storer) const;
};
- AsKey as_key() const {
+ AsKey as_key(bool /*is_unique*/) const {
return AsKey{*this};
}
+
bool operator<(const WebRemoteFileLocation &other) const {
return url_ < other.url_;
}
@@ -359,37 +150,30 @@ struct CommonRemoteFileLocation {
int64 access_hash_;
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(id_, storer);
- store(access_hash_, storer);
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- parse(id_, parser);
- parse(access_hash_, parser);
- }
+ void parse(ParserT &parser);
+
struct AsKey {
const CommonRemoteFileLocation &key;
+
template <class StorerT>
- void store(StorerT &storer) const {
- td::store(key.id_, storer);
- }
+ void store(StorerT &storer) const;
};
- AsKey as_key() const {
+ AsKey as_key(bool /*is_unique*/) const {
return AsKey{*this};
}
+
bool operator<(const CommonRemoteFileLocation &other) const {
- return std::tie(id_) < std::tie(other.id_);
+ return id_ < other.id_;
}
bool operator==(const CommonRemoteFileLocation &other) const {
- return std::tie(id_) == std::tie(other.id_);
+ return id_ == other.id_;
}
};
inline StringBuilder &operator<<(StringBuilder &string_builder, const CommonRemoteFileLocation &location) {
- return string_builder << "[id = " << location.id_ << ", access_hash = " << location.access_hash_ << "]";
+ return string_builder << "[ID = " << location.id_ << ", access_hash = " << location.access_hash_ << "]";
}
class FullRemoteFileLocation {
@@ -398,36 +182,27 @@ class FullRemoteFileLocation {
private:
static constexpr int32 WEB_LOCATION_FLAG = 1 << 24;
- bool web_location_flag_{false};
+ static constexpr int32 FILE_REFERENCE_FLAG = 1 << 25;
DcId dc_id_;
- enum class LocationType { Web, Photo, Common, None };
+ string file_reference_;
+ enum class LocationType : int32 { Web, Photo, Common, None };
Variant<WebRemoteFileLocation, PhotoRemoteFileLocation, CommonRemoteFileLocation> variant_;
LocationType location_type() const {
if (is_web()) {
return LocationType::Web;
}
- switch (file_type_) {
- case FileType::Photo:
- case FileType::ProfilePhoto:
- case FileType::Thumbnail:
- case FileType::EncryptedThumbnail:
- case FileType::Wallpaper:
+ switch (get_file_type_class(file_type_)) {
+ case FileTypeClass::Photo:
return LocationType::Photo;
- case FileType::Video:
- case FileType::VoiceNote:
- case FileType::Document:
- case FileType::Sticker:
- case FileType::Audio:
- case FileType::Animation:
- case FileType::Encrypted:
- case FileType::VideoNote:
+ case FileTypeClass::Document:
+ case FileTypeClass::Secure:
+ case FileTypeClass::Encrypted:
return LocationType::Common;
- case FileType::None:
- case FileType::Size:
+ case FileTypeClass::Temp:
+ return LocationType::None;
default:
UNREACHABLE();
- case FileType::Temp:
return LocationType::None;
}
}
@@ -454,7 +229,7 @@ class FullRemoteFileLocation {
friend StringBuilder &operator<<(StringBuilder &string_builder,
const FullRemoteFileLocation &full_remote_file_location);
- int32 full_type() const {
+ int32 key_type() const {
auto type = static_cast<int32>(file_type_);
if (is_web()) {
type |= WEB_LOCATION_FLAG;
@@ -462,71 +237,44 @@ class FullRemoteFileLocation {
return type;
}
+ void check_file_reference() {
+ if (file_reference_ == FileReferenceView::invalid_file_reference()) {
+ LOG(ERROR) << "Tried to register file with invalid file reference";
+ file_reference_.clear();
+ }
+ }
+
public:
template <class StorerT>
- void store(StorerT &storer) const {
- using ::td::store;
- store(full_type(), storer);
- store(dc_id_.get_value(), storer);
- variant_.visit([&](auto &&value) {
- using td::store;
- store(value, storer);
- });
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using ::td::parse;
- int32 raw_type;
- parse(raw_type, parser);
- web_location_flag_ = (raw_type & WEB_LOCATION_FLAG) != 0;
- raw_type &= ~WEB_LOCATION_FLAG;
- if (raw_type < 0 || raw_type >= static_cast<int32>(FileType::Size)) {
- return parser.set_error("Invalid FileType in FullRemoteFileLocation");
- }
- file_type_ = static_cast<FileType>(raw_type);
- int32 dc_id_value;
- parse(dc_id_value, parser);
- dc_id_ = DcId::from_value(dc_id_value);
-
- switch (location_type()) {
- case LocationType::Web: {
- variant_ = WebRemoteFileLocation();
- return web().parse(parser);
- }
- case LocationType::Photo: {
- variant_ = PhotoRemoteFileLocation();
- return photo().parse(parser);
- }
- case LocationType::Common: {
- variant_ = CommonRemoteFileLocation();
- return common().parse(parser);
- }
- case LocationType::None: {
- break;
- }
- }
- parser.set_error("Invalid FileType in FullRemoteFileLocation");
- }
+ void parse(ParserT &parser);
struct AsKey {
const FullRemoteFileLocation &key;
+
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(key.full_type(), storer);
- key.variant_.visit([&](auto &&value) {
- using td::store;
- store(value.as_key(), storer);
- });
- }
+ void store(StorerT &storer) const;
};
AsKey as_key() const {
return AsKey{*this};
}
+ struct AsUnique {
+ const FullRemoteFileLocation &key;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+ };
+ AsUnique as_unique() const {
+ return AsUnique{*this};
+ }
+
DcId get_dc_id() const {
+ CHECK(!is_web());
return dc_id_;
}
+
int64 get_access_hash() const {
switch (location_type()) {
case LocationType::Photo:
@@ -541,6 +289,7 @@ class FullRemoteFileLocation {
return 0;
}
}
+
int64 get_id() const {
switch (location_type()) {
case LocationType::Photo:
@@ -554,6 +303,43 @@ class FullRemoteFileLocation {
return 0;
}
}
+
+ PhotoSizeSource get_source() const {
+ switch (location_type()) {
+ case LocationType::Photo:
+ return photo().source_;
+ case LocationType::Common:
+ case LocationType::Web:
+ return PhotoSizeSource::full_legacy(0, 0, 0);
+ case LocationType::None:
+ default:
+ UNREACHABLE();
+ return PhotoSizeSource::full_legacy(0, 0, 0);
+ }
+ }
+
+ void set_source(PhotoSizeSource source) {
+ CHECK(is_photo());
+ file_type_ = source.get_file_type("set_source");
+ photo().source_ = std::move(source);
+ }
+
+ bool delete_file_reference(Slice bad_file_reference) {
+ if (file_reference_ != FileReferenceView::invalid_file_reference() && file_reference_ == bad_file_reference) {
+ file_reference_ = FileReferenceView::invalid_file_reference().str();
+ return true;
+ }
+ return false;
+ }
+
+ bool has_file_reference() const {
+ return file_reference_ != FileReferenceView::invalid_file_reference();
+ }
+
+ Slice get_file_reference() const {
+ return file_reference_;
+ }
+
string get_url() const {
if (is_web()) {
return web().url_;
@@ -563,7 +349,7 @@ class FullRemoteFileLocation {
}
bool is_web() const {
- return web_location_flag_;
+ return variant_.get_offset() == 0;
}
bool is_photo() const {
return location_type() == LocationType::Photo;
@@ -571,23 +357,105 @@ class FullRemoteFileLocation {
bool is_common() const {
return location_type() == LocationType::Common;
}
- bool is_encrypted() const {
+ bool is_encrypted_secret() const {
return file_type_ == FileType::Encrypted;
}
+ bool is_encrypted_secure() const {
+ return file_type_ == FileType::SecureEncrypted;
+ }
+ bool is_encrypted_any() const {
+ return is_encrypted_secret() || is_encrypted_secure();
+ }
+ bool is_secure() const {
+ return file_type_ == FileType::SecureDecrypted || file_type_ == FileType::SecureEncrypted;
+ }
+ bool is_document() const {
+ return is_common() && !is_secure() && !is_encrypted_secret();
+ }
- tl_object_ptr<telegram_api::inputWebFileLocation> as_input_web_file_location() const {
- CHECK(is_web());
+#define as_input_web_file_location() as_input_web_file_location_impl(__FILE__, __LINE__)
+ tl_object_ptr<telegram_api::inputWebFileLocation> as_input_web_file_location_impl(const char *file, int line) const {
+ LOG_CHECK(is_web()) << file << ' ' << line;
return make_tl_object<telegram_api::inputWebFileLocation>(web().url_, web().access_hash_);
}
+
tl_object_ptr<telegram_api::InputFileLocation> as_input_file_location() const {
switch (location_type()) {
- case LocationType::Photo:
- return make_tl_object<telegram_api::inputFileLocation>(photo().volume_id_, photo().local_id_, photo().secret_);
+ case LocationType::Photo: {
+ const auto &id = photo().id_;
+ const auto &access_hash = photo().access_hash_;
+ const auto &source = photo().source_;
+ switch (source.get_type("as_input_file_location")) {
+ case PhotoSizeSource::Type::Legacy:
+ UNREACHABLE();
+ break;
+ case PhotoSizeSource::Type::Thumbnail: {
+ auto &thumbnail = source.thumbnail();
+ switch (thumbnail.file_type) {
+ case FileType::Photo:
+ return make_tl_object<telegram_api::inputPhotoFileLocation>(
+ id, access_hash, BufferSlice(file_reference_),
+ std::string(1, static_cast<char>(static_cast<uint8>(thumbnail.thumbnail_type))));
+ case FileType::Thumbnail:
+ return make_tl_object<telegram_api::inputDocumentFileLocation>(
+ id, access_hash, BufferSlice(file_reference_),
+ std::string(1, static_cast<char>(static_cast<uint8>(thumbnail.thumbnail_type))));
+ default:
+ UNREACHABLE();
+ break;
+ }
+ break;
+ }
+ case PhotoSizeSource::Type::DialogPhotoSmall:
+ case PhotoSizeSource::Type::DialogPhotoBig: {
+ auto &dialog_photo = source.dialog_photo();
+ bool is_big = source.get_type("as_input_file_location 2") == PhotoSizeSource::Type::DialogPhotoBig;
+ return make_tl_object<telegram_api::inputPeerPhotoFileLocation>(
+ is_big * telegram_api::inputPeerPhotoFileLocation::BIG_MASK, false /*ignored*/,
+ dialog_photo.get_input_peer(), id);
+ }
+ case PhotoSizeSource::Type::StickerSetThumbnail:
+ UNREACHABLE();
+ break;
+ case PhotoSizeSource::Type::FullLegacy: {
+ const auto &full_legacy = source.full_legacy();
+ return make_tl_object<telegram_api::inputPhotoLegacyFileLocation>(
+ id, access_hash, BufferSlice(file_reference_), full_legacy.volume_id, full_legacy.local_id,
+ full_legacy.secret);
+ }
+ case PhotoSizeSource::Type::DialogPhotoSmallLegacy:
+ case PhotoSizeSource::Type::DialogPhotoBigLegacy: {
+ auto &dialog_photo = source.dialog_photo_legacy();
+ bool is_big = source.get_type("as_input_file_location 3") == PhotoSizeSource::Type::DialogPhotoBigLegacy;
+ return make_tl_object<telegram_api::inputPeerPhotoFileLocationLegacy>(
+ is_big * telegram_api::inputPeerPhotoFileLocationLegacy::BIG_MASK, false /*ignored*/,
+ dialog_photo.get_input_peer(), dialog_photo.volume_id, dialog_photo.local_id);
+ }
+ case PhotoSizeSource::Type::StickerSetThumbnailLegacy: {
+ auto &sticker_set_thumbnail = source.sticker_set_thumbnail_legacy();
+ return make_tl_object<telegram_api::inputStickerSetThumbLegacy>(
+ sticker_set_thumbnail.get_input_sticker_set(), sticker_set_thumbnail.volume_id,
+ sticker_set_thumbnail.local_id);
+ }
+ case PhotoSizeSource::Type::StickerSetThumbnailVersion: {
+ auto &sticker_set_thumbnail = source.sticker_set_thumbnail_version();
+ return make_tl_object<telegram_api::inputStickerSetThumb>(sticker_set_thumbnail.get_input_sticker_set(),
+ sticker_set_thumbnail.version);
+ }
+ default:
+ break;
+ }
+ UNREACHABLE();
+ return nullptr;
+ }
case LocationType::Common:
- if (is_encrypted()) {
+ if (is_encrypted_secret()) {
return make_tl_object<telegram_api::inputEncryptedFileLocation>(common().id_, common().access_hash_);
+ } else if (is_secure()) {
+ return make_tl_object<telegram_api::inputSecureFileLocation>(common().id_, common().access_hash_);
} else {
- return make_tl_object<telegram_api::inputDocumentFileLocation>(common().id_, common().access_hash_, 0);
+ return make_tl_object<telegram_api::inputDocumentFileLocation>(common().id_, common().access_hash_,
+ BufferSlice(file_reference_), string());
}
case LocationType::Web:
case LocationType::None:
@@ -597,47 +465,65 @@ class FullRemoteFileLocation {
}
}
- tl_object_ptr<telegram_api::InputDocument> as_input_document() const {
- CHECK(is_common());
- LOG_IF(ERROR, is_encrypted()) << "Can't call as_input_document on an encrypted file";
- return make_tl_object<telegram_api::inputDocument>(common().id_, common().access_hash_);
+#define as_input_document() as_input_document_impl(__FILE__, __LINE__)
+ tl_object_ptr<telegram_api::inputDocument> as_input_document_impl(const char *file, int line) const {
+ LOG_CHECK(is_common()) << file << ' ' << line;
+ LOG_CHECK(is_document()) << file << ' ' << line;
+ return make_tl_object<telegram_api::inputDocument>(common().id_, common().access_hash_,
+ BufferSlice(file_reference_));
}
- tl_object_ptr<telegram_api::InputPhoto> as_input_photo() const {
- CHECK(is_photo());
- return make_tl_object<telegram_api::inputPhoto>(photo().id_, photo().access_hash_);
+#define as_input_photo() as_input_photo_impl(__FILE__, __LINE__)
+ tl_object_ptr<telegram_api::inputPhoto> as_input_photo_impl(const char *file, int line) const {
+ LOG_CHECK(is_photo()) << file << ' ' << line;
+ return make_tl_object<telegram_api::inputPhoto>(photo().id_, photo().access_hash_, BufferSlice(file_reference_));
}
- tl_object_ptr<telegram_api::InputEncryptedFile> as_input_encrypted_file() const {
- CHECK(is_encrypted()) << "Can't call as_input_encrypted_file on a non-encrypted file";
+ tl_object_ptr<telegram_api::inputEncryptedFile> as_input_encrypted_file() const {
+ CHECK(is_encrypted_secret());
return make_tl_object<telegram_api::inputEncryptedFile>(common().id_, common().access_hash_);
}
- // TODO: this constructor is just for immediate unserialize
+#define as_input_secure_file() as_input_secure_file_impl(__FILE__, __LINE__)
+ tl_object_ptr<telegram_api::inputSecureFile> as_input_secure_file_impl(const char *file, int line) const {
+ LOG_CHECK(is_secure()) << file << ' ' << line;
+ return make_tl_object<telegram_api::inputSecureFile>(common().id_, common().access_hash_);
+ }
+
+ // this constructor is just for immediate unserialize
FullRemoteFileLocation() = default;
- FullRemoteFileLocation(FileType file_type, int64 id, int64 access_hash, int32 local_id, int64 volume_id, int64 secret,
- DcId dc_id)
- : file_type_(file_type)
+
+ // photo
+ FullRemoteFileLocation(const PhotoSizeSource &source, int64 id, int64 access_hash, DcId dc_id,
+ std::string file_reference)
+ : file_type_(source.get_file_type("FullRemoteFileLocation"))
, dc_id_(dc_id)
- , variant_(PhotoRemoteFileLocation{id, access_hash, volume_id, secret, local_id}) {
+ , file_reference_(std::move(file_reference))
+ , variant_(PhotoRemoteFileLocation{id, access_hash, source}) {
CHECK(is_photo());
+ check_file_reference();
}
- FullRemoteFileLocation(FileType file_type, int64 id, int64 access_hash, DcId dc_id)
- : file_type_(file_type), dc_id_(dc_id), variant_(CommonRemoteFileLocation{id, access_hash}) {
- CHECK(is_common());
- }
- FullRemoteFileLocation(FileType file_type, string url, int64 access_hash, DcId dc_id)
+
+ // document
+ FullRemoteFileLocation(FileType file_type, int64 id, int64 access_hash, DcId dc_id, std::string file_reference)
: file_type_(file_type)
- , web_location_flag_{true}
, dc_id_(dc_id)
- , variant_(WebRemoteFileLocation{std::move(url), access_hash}) {
+ , file_reference_(std::move(file_reference))
+ , variant_(CommonRemoteFileLocation{id, access_hash}) {
+ CHECK(is_common());
+ check_file_reference();
+ }
+
+ // web document
+ FullRemoteFileLocation(FileType file_type, string url, int64 access_hash)
+ : file_type_(file_type), dc_id_(), variant_(WebRemoteFileLocation{std::move(url), access_hash}) {
CHECK(is_web());
CHECK(!web().url_.empty());
}
bool operator<(const FullRemoteFileLocation &other) const {
- if (full_type() != other.full_type()) {
- return full_type() < other.full_type();
+ if (key_type() != other.key_type()) {
+ return key_type() < other.key_type();
}
if (dc_id_ != other.dc_id_) {
return dc_id_ < other.dc_id_;
@@ -656,7 +542,7 @@ class FullRemoteFileLocation {
}
}
bool operator==(const FullRemoteFileLocation &other) const {
- if (full_type() != other.full_type()) {
+ if (key_type() != other.key_type()) {
return false;
}
if (dc_id_ != other.dc_id_) {
@@ -681,9 +567,15 @@ class FullRemoteFileLocation {
inline StringBuilder &operator<<(StringBuilder &string_builder,
const FullRemoteFileLocation &full_remote_file_location) {
- string_builder << "[" << file_type_name[static_cast<int32>(full_remote_file_location.file_type_)] << ", "
- << full_remote_file_location.get_dc_id() << ", location = ";
+ string_builder << "[" << full_remote_file_location.file_type_;
+ if (!full_remote_file_location.is_web()) {
+ string_builder << ", " << full_remote_file_location.get_dc_id();
+ }
+ if (!full_remote_file_location.file_reference_.empty()) {
+ string_builder << ", " << tag("file_reference", base64_encode(full_remote_file_location.file_reference_));
+ }
+ string_builder << ", location = ";
if (full_remote_file_location.is_web()) {
string_builder << full_remote_file_location.web();
} else if (full_remote_file_location.is_photo()) {
@@ -703,17 +595,6 @@ class RemoteFileLocation {
return static_cast<Type>(variant_.get_offset());
}
- template <class StorerT>
- void store(StorerT &storer) const {
- storer.store_int(variant_.get_offset());
- bool ok{false};
- variant_.visit([&](auto &&value) {
- using td::store;
- store(value, storer);
- ok = true;
- });
- CHECK(ok);
- }
PartialRemoteFileLocation &partial() {
return variant_.get<1>();
}
@@ -726,47 +607,43 @@ class RemoteFileLocation {
const FullRemoteFileLocation &full() const {
return variant_.get<2>();
}
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- auto type = static_cast<Type>(parser.fetch_int());
- switch (type) {
- case Type::Empty: {
- variant_ = EmptyRemoteFileLocation();
- return;
- }
- case Type::Partial: {
- variant_ = PartialRemoteFileLocation();
- return partial().parse(parser);
- }
- case Type::Full: {
- variant_ = FullRemoteFileLocation();
- return full().parse(parser);
- }
- }
- parser.set_error("Invalid type in RemoteFileLocation");
- }
+ void parse(ParserT &parser);
RemoteFileLocation() : variant_{EmptyRemoteFileLocation{}} {
}
- explicit RemoteFileLocation(const FullRemoteFileLocation &full) : variant_(full) {
- }
- explicit RemoteFileLocation(const PartialRemoteFileLocation &partial) : variant_(partial) {
- }
- RemoteFileLocation(FileType file_type, int64 id, int64 access_hash, int32 local_id, int64 volume_id, int64 secret,
- DcId dc_id)
- : variant_(FullRemoteFileLocation{file_type, id, access_hash, local_id, volume_id, secret, dc_id}) {
+ explicit RemoteFileLocation(FullRemoteFileLocation full) : variant_(std::move(full)) {
}
- RemoteFileLocation(FileType file_type, int64 id, int64 access_hash, DcId dc_id)
- : variant_(FullRemoteFileLocation{file_type, id, access_hash, dc_id}) {
+ explicit RemoteFileLocation(PartialRemoteFileLocation partial) : variant_(std::move(partial)) {
}
private:
Variant<EmptyRemoteFileLocation, PartialRemoteFileLocation, FullRemoteFileLocation> variant_;
friend bool operator==(const RemoteFileLocation &lhs, const RemoteFileLocation &rhs);
+
+ bool is_empty() const {
+ switch (type()) {
+ case Type::Empty:
+ return true;
+ case Type::Partial:
+ return partial().ready_part_count_ == 0;
+ case Type::Full:
+ return false;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+ }
};
inline bool operator==(const RemoteFileLocation &lhs, const RemoteFileLocation &rhs) {
+ if (lhs.is_empty() && rhs.is_empty()) {
+ return true;
+ }
return lhs.variant_ == rhs.variant_;
}
@@ -774,6 +651,20 @@ inline bool operator!=(const RemoteFileLocation &lhs, const RemoteFileLocation &
return !(lhs == rhs);
}
+inline StringBuilder &operator<<(StringBuilder &sb, const RemoteFileLocation &location) {
+ switch (location.type()) {
+ case RemoteFileLocation::Type::Empty:
+ return sb << "[empty remote location]";
+ case RemoteFileLocation::Type::Partial:
+ return sb << location.partial();
+ case RemoteFileLocation::Type::Full:
+ return sb << location.full();
+ default:
+ UNREACHABLE();
+ return sb;
+ }
+}
+
struct EmptyLocalFileLocation {
template <class StorerT>
void store(StorerT &storer) const {
@@ -793,65 +684,42 @@ inline bool operator!=(const EmptyLocalFileLocation &lhs, const EmptyLocalFileLo
struct PartialLocalFileLocation {
FileType file_type_;
+ int64 part_size_;
string path_;
- int32 part_size_;
- int32 ready_part_count_;
string iv_;
+ string ready_bitmask_;
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(file_type_, storer);
- store(path_, storer);
- store(part_size_, storer);
- store(ready_part_count_, storer);
- store(iv_, storer);
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- parse(file_type_, parser);
- if (file_type_ < FileType::Thumbnail || file_type_ >= FileType::Size) {
- return parser.set_error("Invalid type in PartialLocalFileLocation");
- }
- parse(path_, parser);
- parse(part_size_, parser);
- parse(ready_part_count_, parser);
- parse(iv_, parser);
- }
+ void parse(ParserT &parser);
};
inline bool operator==(const PartialLocalFileLocation &lhs, const PartialLocalFileLocation &rhs) {
return lhs.file_type_ == rhs.file_type_ && lhs.path_ == rhs.path_ && lhs.part_size_ == rhs.part_size_ &&
- lhs.ready_part_count_ == rhs.ready_part_count_ && lhs.iv_ == rhs.iv_;
+ lhs.iv_ == rhs.iv_ && lhs.ready_bitmask_ == rhs.ready_bitmask_;
}
inline bool operator!=(const PartialLocalFileLocation &lhs, const PartialLocalFileLocation &rhs) {
return !(lhs == rhs);
}
+inline StringBuilder &operator<<(StringBuilder &sb, const PartialLocalFileLocation &location) {
+ return sb << "[partial local location of " << location.file_type_ << " with part size " << location.part_size_
+ << " and ready parts " << Bitmask(Bitmask::Decode{}, location.ready_bitmask_) << "] at \"" << location.path_
+ << '"';
+}
+
struct FullLocalFileLocation {
FileType file_type_;
string path_;
uint64 mtime_nsec_;
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(file_type_, storer);
- store(mtime_nsec_, storer);
- store(path_, storer);
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- parse(file_type_, parser);
- if (file_type_ < FileType::Thumbnail || file_type_ >= FileType::Size) {
- return parser.set_error("Invalid type in FullLocalFileLocation");
- }
- parse(mtime_nsec_, parser);
- parse(path_, parser);
- }
+ void parse(ParserT &parser);
+
const FullLocalFileLocation &as_key() const {
return *this;
}
@@ -879,7 +747,43 @@ inline bool operator!=(const FullLocalFileLocation &lhs, const FullLocalFileLoca
}
inline StringBuilder &operator<<(StringBuilder &sb, const FullLocalFileLocation &location) {
- return sb << tag("path", location.path_);
+ return sb << "[full local location of " << location.file_type_ << "] at \"" << location.path_ << '"';
+}
+
+struct PartialLocalFileLocationPtr {
+ unique_ptr<PartialLocalFileLocation> location_; // must never be equal to nullptr
+
+ PartialLocalFileLocationPtr() : location_(make_unique<PartialLocalFileLocation>()) {
+ }
+ explicit PartialLocalFileLocationPtr(PartialLocalFileLocation location)
+ : location_(make_unique<PartialLocalFileLocation>(std::move(location))) {
+ }
+ PartialLocalFileLocationPtr(const PartialLocalFileLocationPtr &other)
+ : location_(make_unique<PartialLocalFileLocation>(*other.location_)) {
+ }
+ PartialLocalFileLocationPtr &operator=(const PartialLocalFileLocationPtr &other) {
+ if (this != &other) {
+ *location_ = *other.location_;
+ }
+ return *this;
+ }
+ PartialLocalFileLocationPtr(PartialLocalFileLocationPtr &&other) noexcept
+ : location_(make_unique<PartialLocalFileLocation>(std::move(*other.location_))) {
+ }
+ PartialLocalFileLocationPtr &operator=(PartialLocalFileLocationPtr &&other) noexcept {
+ *location_ = std::move(*other.location_);
+ return *this;
+ }
+ ~PartialLocalFileLocationPtr() = default;
+
+ template <class StorerT>
+ void store(StorerT &storer) const;
+ template <class ParserT>
+ void parse(ParserT &parser);
+};
+
+inline bool operator==(const PartialLocalFileLocationPtr &lhs, const PartialLocalFileLocationPtr &rhs) {
+ return *lhs.location_ == *rhs.location_;
}
class LocalFileLocation {
@@ -891,13 +795,13 @@ class LocalFileLocation {
}
PartialLocalFileLocation &partial() {
- return variant_.get<1>();
+ return *variant_.get<1>().location_;
}
FullLocalFileLocation &full() {
return variant_.get<2>();
}
const PartialLocalFileLocation &partial() const {
- return variant_.get<1>();
+ return *variant_.get<1>().location_;
}
const FullLocalFileLocation &full() const {
return variant_.get<2>();
@@ -916,44 +820,23 @@ class LocalFileLocation {
}
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(variant_.get_offset(), storer);
- variant_.visit([&](auto &&value) {
- using td::store;
- store(value, storer);
- });
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- auto type = static_cast<Type>(parser.fetch_int());
- switch (type) {
- case Type::Empty:
- variant_ = EmptyLocalFileLocation();
- return;
- case Type::Partial:
- variant_ = PartialLocalFileLocation();
- return parse(partial(), parser);
- case Type::Full:
- variant_ = FullLocalFileLocation();
- return parse(full(), parser);
- }
- return parser.set_error("Invalid type in LocalFileLocation");
- }
+ void parse(ParserT &parser);
LocalFileLocation() : variant_{EmptyLocalFileLocation()} {
}
- explicit LocalFileLocation(const PartialLocalFileLocation &partial) : variant_(partial) {
+ explicit LocalFileLocation(PartialLocalFileLocation partial)
+ : variant_(PartialLocalFileLocationPtr(std::move(partial))) {
}
- explicit LocalFileLocation(const FullLocalFileLocation &full) : variant_(full) {
+ explicit LocalFileLocation(FullLocalFileLocation full) : variant_(std::move(full)) {
}
LocalFileLocation(FileType file_type, string path, uint64 mtime_nsec)
: variant_(FullLocalFileLocation{file_type, std::move(path), mtime_nsec}) {
}
private:
- Variant<EmptyLocalFileLocation, PartialLocalFileLocation, FullLocalFileLocation> variant_;
+ Variant<EmptyLocalFileLocation, PartialLocalFileLocationPtr, FullLocalFileLocation> variant_;
friend bool operator==(const LocalFileLocation &lhs, const LocalFileLocation &rhs);
};
@@ -966,6 +849,20 @@ inline bool operator!=(const LocalFileLocation &lhs, const LocalFileLocation &rh
return !(lhs == rhs);
}
+inline StringBuilder &operator<<(StringBuilder &sb, const LocalFileLocation &location) {
+ switch (location.type()) {
+ case LocalFileLocation::Type::Empty:
+ return sb << "[empty local location]";
+ case LocalFileLocation::Type::Partial:
+ return sb << location.partial();
+ case LocalFileLocation::Type::Full:
+ return sb << location.full();
+ default:
+ UNREACHABLE();
+ return sb;
+ }
+}
+
struct FullGenerateFileLocation {
FileType file_type_{FileType::None};
string original_path_;
@@ -973,19 +870,9 @@ struct FullGenerateFileLocation {
static const int32 KEY_MAGIC = 0x8b60a1c8;
template <class StorerT>
- void store(StorerT &storer) const {
- using td::store;
- store(file_type_, storer);
- store(original_path_, storer);
- store(conversion_, storer);
- }
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- using td::parse;
- parse(file_type_, parser);
- parse(original_path_, parser);
- parse(conversion_, parser);
- }
+ void parse(ParserT &parser);
const FullGenerateFileLocation &as_key() const {
return *this;
@@ -1012,10 +899,9 @@ inline bool operator!=(const FullGenerateFileLocation &lhs, const FullGenerateFi
inline StringBuilder &operator<<(StringBuilder &string_builder,
const FullGenerateFileLocation &full_generated_file_location) {
- return string_builder << "["
- << tag("file_type", file_type_name[static_cast<int32>(full_generated_file_location.file_type_)])
+ return string_builder << '[' << tag("file_type", full_generated_file_location.file_type_)
<< tag("original_path", full_generated_file_location.original_path_)
- << tag("conversion", full_generated_file_location.conversion_) << "]";
+ << tag("conversion", full_generated_file_location.conversion_) << ']';
}
class GenerateFileLocation {
@@ -1036,27 +922,9 @@ class GenerateFileLocation {
}
template <class StorerT>
- void store(StorerT &storer) const {
- td::store(type_, storer);
- switch (type_) {
- case Type::Empty:
- return;
- case Type::Full:
- return td::store(full_, storer);
- }
- }
-
+ void store(StorerT &storer) const;
template <class ParserT>
- void parse(ParserT &parser) {
- td::parse(type_, parser);
- switch (type_) {
- case Type::Empty:
- return;
- case Type::Full:
- return td::parse(full_, parser);
- }
- return parser.set_error("Invalid type in GenerateFileLocation");
- }
+ void parse(ParserT &parser);
GenerateFileLocation() : type_(Type::Empty) {
}
@@ -1091,109 +959,4 @@ inline bool operator!=(const GenerateFileLocation &lhs, const GenerateFileLocati
return !(lhs == rhs);
}
-class FileData {
- public:
- DialogId owner_dialog_id_;
- uint64 pmc_id_ = 0;
- RemoteFileLocation remote_;
- LocalFileLocation local_;
- unique_ptr<FullGenerateFileLocation> generate_;
- int64 size_ = 0;
- int64 expected_size_ = 0;
- string remote_name_;
- string url_;
- FileEncryptionKey encryption_key_;
-
- template <class StorerT>
- void store(StorerT &storer) const {
- using ::td::store;
- bool has_owner_dialog_id = owner_dialog_id_.is_valid();
- bool has_expected_size = size_ == 0 && expected_size_ != 0;
- BEGIN_STORE_FLAGS();
- STORE_FLAG(has_owner_dialog_id);
- STORE_FLAG(has_expected_size);
- END_STORE_FLAGS();
-
- if (has_owner_dialog_id) {
- store(owner_dialog_id_, storer);
- }
- store(pmc_id_, storer);
- store(remote_, storer);
- store(local_, storer);
- auto generate = generate_ == nullptr ? GenerateFileLocation() : GenerateFileLocation(*generate_);
- store(generate, storer);
- if (has_expected_size) {
- store(expected_size_, storer);
- } else {
- store(size_, storer);
- }
- store(remote_name_, storer);
- store(url_, storer);
- store(encryption_key_, storer);
- }
- template <class ParserT>
- void parse(ParserT &parser) {
- using ::td::parse;
- bool has_owner_dialog_id;
- bool has_expected_size;
- BEGIN_PARSE_FLAGS();
- PARSE_FLAG(has_owner_dialog_id);
- PARSE_FLAG(has_expected_size);
- END_PARSE_FLAGS();
-
- if (has_owner_dialog_id) {
- parse(owner_dialog_id_, parser);
- }
- parse(pmc_id_, parser);
- parse(remote_, parser);
- parse(local_, parser);
- GenerateFileLocation generate;
- parse(generate, parser);
- if (generate.type() == GenerateFileLocation::Type::Full) {
- generate_ = std::make_unique<FullGenerateFileLocation>(generate.full());
- } else {
- generate_ = nullptr;
- }
- if (has_expected_size) {
- parse(expected_size_, parser);
- } else {
- parse(size_, parser);
- }
- parse(remote_name_, parser);
- parse(url_, parser);
- parse(encryption_key_, parser);
- }
-};
-inline StringBuilder &operator<<(StringBuilder &sb, const FileData &file_data) {
- sb << "[" << tag("remote_name", file_data.remote_name_) << " " << file_data.owner_dialog_id_ << " "
- << tag("size", file_data.size_) << tag("expected_size", file_data.expected_size_);
- if (!file_data.url_.empty()) {
- sb << tag("url", file_data.url_);
- }
- if (file_data.local_.type() == LocalFileLocation::Type::Full) {
- sb << " local " << file_data.local_.full();
- }
- if (file_data.generate_ != nullptr) {
- sb << " generate " << *file_data.generate_;
- }
- if (file_data.remote_.type() == RemoteFileLocation::Type::Full) {
- sb << " remote " << file_data.remote_.full();
- }
- return sb << "]";
-}
-
-template <class T>
-string as_key(const T &object) {
- TlStorerCalcLength calc_length;
- calc_length.store_int(0);
- object.as_key().store(calc_length);
-
- BufferSlice key_buffer{calc_length.get_length()};
- auto key = key_buffer.as_slice();
- TlStorerUnsafe storer(key.begin());
- storer.store_int(T::KEY_MAGIC);
- object.as_key().store(storer);
- return key.str();
-}
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileLocation.hpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileLocation.hpp
new file mode 100644
index 0000000000..19e16976d1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileLocation.hpp
@@ -0,0 +1,433 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/files/FileLocation.h"
+
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/PhotoSizeSource.hpp"
+#include "td/telegram/Version.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/Variant.h"
+
+namespace td {
+
+template <class StorerT>
+void PartialRemoteFileLocation::store(StorerT &storer) const {
+ using td::store;
+ store(file_id_, storer);
+ store(part_count_, storer);
+ store(part_size_, storer);
+ store(ready_part_count_, storer);
+ store(is_big_, storer);
+}
+
+template <class ParserT>
+void PartialRemoteFileLocation::parse(ParserT &parser) {
+ using td::parse;
+ parse(file_id_, parser);
+ parse(part_count_, parser);
+ parse(part_size_, parser);
+ parse(ready_part_count_, parser);
+ parse(is_big_, parser);
+}
+
+template <class StorerT>
+void PhotoRemoteFileLocation::store(StorerT &storer) const {
+ using td::store;
+ store(id_, storer);
+ store(access_hash_, storer);
+ store(source_, storer);
+}
+
+template <class ParserT>
+void PhotoRemoteFileLocation::parse(ParserT &parser) {
+ using td::parse;
+ parse(id_, parser);
+ parse(access_hash_, parser);
+ if (parser.version() >= static_cast<int32>(Version::RemovePhotoVolumeAndLocalId)) {
+ parse(source_, parser);
+ } else {
+ int64 volume_id;
+ PhotoSizeSource source;
+ int32 local_id;
+ parse(volume_id, parser);
+ if (parser.version() >= static_cast<int32>(Version::AddPhotoSizeSource)) {
+ parse(source, parser);
+ parse(local_id, parser);
+ } else {
+ int64 secret;
+ parse(secret, parser);
+ parse(local_id, parser);
+ source = PhotoSizeSource::full_legacy(volume_id, local_id, secret);
+ }
+
+ if (parser.get_error() != nullptr) {
+ return;
+ }
+
+ switch (source.get_type("PhotoRemoteFileLocation::parse")) {
+ case PhotoSizeSource::Type::Legacy:
+ source_ = PhotoSizeSource::full_legacy(volume_id, local_id, source.legacy().secret);
+ break;
+ case PhotoSizeSource::Type::FullLegacy:
+ case PhotoSizeSource::Type::Thumbnail:
+ source_ = source;
+ break;
+ case PhotoSizeSource::Type::DialogPhotoSmall:
+ case PhotoSizeSource::Type::DialogPhotoBig: {
+ auto &dialog_photo = source.dialog_photo();
+ bool is_big = source.get_type("PhotoRemoteFileLocation::parse") == PhotoSizeSource::Type::DialogPhotoBig;
+ source_ = PhotoSizeSource::dialog_photo_legacy(dialog_photo.dialog_id, dialog_photo.dialog_access_hash, is_big,
+ volume_id, local_id);
+ break;
+ }
+ case PhotoSizeSource::Type::StickerSetThumbnail: {
+ auto &sticker_set_thumbnail = source.sticker_set_thumbnail();
+ source_ = PhotoSizeSource::sticker_set_thumbnail_legacy(
+ sticker_set_thumbnail.sticker_set_id, sticker_set_thumbnail.sticker_set_access_hash, volume_id, local_id);
+ break;
+ }
+ default:
+ parser.set_error("Invalid PhotoSizeSource in legacy PhotoRemoteFileLocation");
+ break;
+ }
+ }
+}
+
+template <class StorerT>
+void PhotoRemoteFileLocation::AsKey::store(StorerT &storer) const {
+ using td::store;
+ auto unique = key.source_.get_unique();
+ switch (key.source_.get_type("PhotoRemoteFileLocation::AsKey::store")) {
+ case PhotoSizeSource::Type::Legacy:
+ case PhotoSizeSource::Type::StickerSetThumbnail:
+ UNREACHABLE();
+ break;
+ case PhotoSizeSource::Type::FullLegacy:
+ case PhotoSizeSource::Type::DialogPhotoSmallLegacy:
+ case PhotoSizeSource::Type::DialogPhotoBigLegacy:
+ case PhotoSizeSource::Type::StickerSetThumbnailLegacy: // 12/20 bytes
+ if (!is_unique) {
+ store(key.id_, storer);
+ }
+ storer.store_slice(unique); // volume_id + local_id
+ break;
+ case PhotoSizeSource::Type::DialogPhotoSmall:
+ case PhotoSizeSource::Type::DialogPhotoBig:
+ case PhotoSizeSource::Type::Thumbnail: // 8 + 1 bytes
+ store(key.id_, storer); // photo_id or document_id
+ storer.store_slice(unique);
+ break;
+ case PhotoSizeSource::Type::StickerSetThumbnailVersion: // 13 bytes
+ // sticker set thumbnails have no photo_id or document_id
+ storer.store_slice(unique);
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+template <class StorerT>
+void WebRemoteFileLocation::store(StorerT &storer) const {
+ using td::store;
+ store(url_, storer);
+ store(access_hash_, storer);
+}
+
+template <class ParserT>
+void WebRemoteFileLocation::parse(ParserT &parser) {
+ using td::parse;
+ parse(url_, parser);
+ parse(access_hash_, parser);
+}
+
+template <class StorerT>
+void WebRemoteFileLocation::AsKey::store(StorerT &storer) const {
+ td::store(key.url_, storer);
+}
+
+template <class StorerT>
+void CommonRemoteFileLocation::store(StorerT &storer) const {
+ using td::store;
+ store(id_, storer);
+ store(access_hash_, storer);
+}
+
+template <class ParserT>
+void CommonRemoteFileLocation::parse(ParserT &parser) {
+ using td::parse;
+ parse(id_, parser);
+ parse(access_hash_, parser);
+}
+
+template <class StorerT>
+void CommonRemoteFileLocation::AsKey::store(StorerT &storer) const {
+ td::store(key.id_, storer);
+}
+
+template <class StorerT>
+void FullRemoteFileLocation::store(StorerT &storer) const {
+ using ::td::store;
+ bool has_file_reference = !file_reference_.empty();
+ auto type = key_type();
+ if (has_file_reference) {
+ type |= FILE_REFERENCE_FLAG;
+ }
+ store(type, storer);
+ store(dc_id_.get_value(), storer);
+ if (has_file_reference) {
+ store(file_reference_, storer);
+ }
+ variant_.visit([&](auto &&value) {
+ using td::store;
+ store(value, storer);
+ });
+}
+
+template <class ParserT>
+void FullRemoteFileLocation::parse(ParserT &parser) {
+ using ::td::parse;
+ int32 raw_type;
+ parse(raw_type, parser);
+ bool is_web = (raw_type & WEB_LOCATION_FLAG) != 0;
+ raw_type &= ~WEB_LOCATION_FLAG;
+ bool has_file_reference = (raw_type & FILE_REFERENCE_FLAG) != 0;
+ raw_type &= ~FILE_REFERENCE_FLAG;
+ if (raw_type < 0 || raw_type >= static_cast<int32>(FileType::Size)) {
+ return parser.set_error("Invalid FileType in FullRemoteFileLocation");
+ }
+ file_type_ = static_cast<FileType>(raw_type);
+ int32 dc_id_value;
+ parse(dc_id_value, parser);
+ dc_id_ = DcId::from_value(dc_id_value);
+
+ if (has_file_reference) {
+ parse(file_reference_, parser);
+ if (file_reference_ == FileReferenceView::invalid_file_reference()) {
+ file_reference_.clear();
+ }
+ }
+ if (is_web) {
+ variant_ = WebRemoteFileLocation();
+ return web().parse(parser);
+ }
+
+ switch (location_type()) {
+ case LocationType::Web:
+ UNREACHABLE();
+ break;
+ case LocationType::Photo:
+ variant_ = PhotoRemoteFileLocation();
+ photo().parse(parser);
+ if (parser.get_error() != nullptr) {
+ return;
+ }
+ switch (photo().source_.get_type("FullRemoteFileLocation::parse")) {
+ case PhotoSizeSource::Type::Legacy:
+ case PhotoSizeSource::Type::FullLegacy:
+ break;
+ case PhotoSizeSource::Type::Thumbnail:
+ if (photo().source_.get_file_type("FullRemoteFileLocation::parse") != file_type_ ||
+ (file_type_ != FileType::Photo && file_type_ != FileType::Thumbnail &&
+ file_type_ != FileType::EncryptedThumbnail)) {
+ parser.set_error("Invalid FileType in PhotoRemoteFileLocation Thumbnail");
+ }
+ break;
+ case PhotoSizeSource::Type::DialogPhotoSmall:
+ case PhotoSizeSource::Type::DialogPhotoBig:
+ case PhotoSizeSource::Type::DialogPhotoSmallLegacy:
+ case PhotoSizeSource::Type::DialogPhotoBigLegacy:
+ if (file_type_ != FileType::ProfilePhoto) {
+ parser.set_error("Invalid FileType in PhotoRemoteFileLocation DialogPhoto");
+ }
+ break;
+ case PhotoSizeSource::Type::StickerSetThumbnail:
+ case PhotoSizeSource::Type::StickerSetThumbnailLegacy:
+ case PhotoSizeSource::Type::StickerSetThumbnailVersion:
+ if (file_type_ != FileType::Thumbnail) {
+ parser.set_error("Invalid FileType in PhotoRemoteFileLocation StickerSetThumbnail");
+ }
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ return;
+ case LocationType::Common:
+ variant_ = CommonRemoteFileLocation();
+ return common().parse(parser);
+ case LocationType::None:
+ break;
+ }
+ parser.set_error("Invalid FileType in FullRemoteFileLocation");
+}
+
+template <class StorerT>
+void FullRemoteFileLocation::AsKey::store(StorerT &storer) const {
+ using td::store;
+ store(key.key_type(), storer);
+ key.variant_.visit([&](auto &&value) {
+ using td::store;
+ store(value.as_key(false), storer);
+ });
+}
+
+template <class StorerT>
+void FullRemoteFileLocation::AsUnique::store(StorerT &storer) const {
+ using td::store;
+
+ int32 type = [key = &key] {
+ if (key->is_web()) {
+ return 0;
+ }
+ return static_cast<int32>(get_file_type_class(key->file_type_)) + 1;
+ }();
+ store(type, storer);
+ key.variant_.visit([&](auto &&value) {
+ using td::store;
+ store(value.as_key(true), storer);
+ });
+}
+
+template <class StorerT>
+void RemoteFileLocation::store(StorerT &storer) const {
+ td::store(variant_, storer);
+}
+
+template <class ParserT>
+void RemoteFileLocation::parse(ParserT &parser) {
+ td::parse(variant_, parser);
+}
+
+template <class StorerT>
+void PartialLocalFileLocation::store(StorerT &storer) const {
+ using td::store;
+ store(file_type_, storer);
+ store(path_, storer);
+ store(static_cast<int32>(part_size_ & 0x7FFFFFFF), storer);
+ int32 deprecated_ready_part_count = part_size_ > 0x7FFFFFFF ? -2 : -1;
+ store(deprecated_ready_part_count, storer);
+ store(iv_, storer);
+ store(ready_bitmask_, storer);
+ if (deprecated_ready_part_count == -2) {
+ CHECK(part_size_ < (static_cast<int64>(1) << 62));
+ store(static_cast<int32>(part_size_ >> 31), storer);
+ }
+}
+
+template <class ParserT>
+void PartialLocalFileLocation::parse(ParserT &parser) {
+ using td::parse;
+ parse(file_type_, parser);
+ if (file_type_ < FileType::Thumbnail || file_type_ >= FileType::Size) {
+ return parser.set_error("Invalid type in PartialLocalFileLocation");
+ }
+ parse(path_, parser);
+ int32 part_size_low;
+ parse(part_size_low, parser);
+ part_size_ = part_size_low;
+ int32 deprecated_ready_part_count;
+ parse(deprecated_ready_part_count, parser);
+ parse(iv_, parser);
+ if (deprecated_ready_part_count == -1 || deprecated_ready_part_count == -2) {
+ parse(ready_bitmask_, parser);
+ if (deprecated_ready_part_count == -2) {
+ int32 part_size_high;
+ parse(part_size_high, parser);
+ part_size_ += static_cast<int64>(part_size_high) << 31;
+ }
+ } else {
+ CHECK(0 <= deprecated_ready_part_count);
+ CHECK(deprecated_ready_part_count <= (1 << 22));
+ ready_bitmask_ = Bitmask(Bitmask::Ones{}, deprecated_ready_part_count).encode();
+ }
+}
+
+template <class StorerT>
+void FullLocalFileLocation::store(StorerT &storer) const {
+ using td::store;
+ store(file_type_, storer);
+ store(mtime_nsec_, storer);
+ store(path_, storer);
+}
+
+template <class ParserT>
+void FullLocalFileLocation::parse(ParserT &parser) {
+ using td::parse;
+ parse(file_type_, parser);
+ if (file_type_ < FileType::Thumbnail || file_type_ >= FileType::Size) {
+ return parser.set_error("Invalid type in FullLocalFileLocation");
+ }
+ parse(mtime_nsec_, parser);
+ parse(path_, parser);
+}
+
+template <class StorerT>
+void PartialLocalFileLocationPtr::store(StorerT &storer) const {
+ td::store(*location_, storer);
+}
+
+template <class ParserT>
+void PartialLocalFileLocationPtr::parse(ParserT &parser) {
+ td::parse(*location_, parser);
+}
+
+template <class StorerT>
+void LocalFileLocation::store(StorerT &storer) const {
+ td::store(variant_, storer);
+}
+
+template <class ParserT>
+void LocalFileLocation::parse(ParserT &parser) {
+ td::parse(variant_, parser);
+}
+
+template <class StorerT>
+void FullGenerateFileLocation::store(StorerT &storer) const {
+ using td::store;
+ store(file_type_, storer);
+ store(original_path_, storer);
+ store(conversion_, storer);
+}
+
+template <class ParserT>
+void FullGenerateFileLocation::parse(ParserT &parser) {
+ using td::parse;
+ parse(file_type_, parser);
+ parse(original_path_, parser);
+ parse(conversion_, parser);
+}
+
+template <class StorerT>
+void GenerateFileLocation::store(StorerT &storer) const {
+ td::store(type_, storer);
+ switch (type_) {
+ case Type::Empty:
+ return;
+ case Type::Full:
+ return td::store(full_, storer);
+ }
+}
+
+template <class ParserT>
+void GenerateFileLocation::parse(ParserT &parser) {
+ td::parse(type_, parser);
+ switch (type_) {
+ case Type::Empty:
+ return;
+ case Type::Full:
+ return td::parse(full_, parser);
+ }
+ return parser.set_error("Invalid type in GenerateFileLocation");
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp
index dc5e2d1caf..c288744372 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.cpp
@@ -1,40 +1,124 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileManager.h"
-#include "td/telegram/telegram_api.h"
-
+#include "td/telegram/DownloadManager.h"
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/files/FileData.h"
+#include "td/telegram/files/FileDb.h"
#include "td/telegram/files/FileLoaderUtils.h"
#include "td/telegram/files/FileLocation.h"
-#include "td/telegram/files/FileUploader.h"
+#include "td/telegram/files/FileLocation.hpp"
#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/misc.h"
-#include "td/telegram/Td.h"
+#include "td/telegram/SecureStorage.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+#include "td/telegram/Version.h"
+
+#include "td/actor/SleepActor.h"
+#include "td/utils/algorithm.h"
#include "td/utils/base64.h"
+#include "td/utils/crypto.h"
+#include "td/utils/filesystem.h"
#include "td/utils/format.h"
#include "td/utils/HttpUrl.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/PathView.h"
-#include "td/utils/port/FileFd.h"
-#include "td/utils/port/path.h"
#include "td/utils/port/Stat.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/Time.h"
#include "td/utils/tl_helpers.h"
+#include "td/utils/tl_parsers.h"
#include <algorithm>
+#include <cmath>
#include <limits>
+#include <numeric>
#include <tuple>
#include <utility>
namespace td {
+namespace {
+constexpr int64 MAX_FILE_SIZE = static_cast<int64>(4000) << 20; // 4000MB
+} // namespace
+
+int VERBOSITY_NAME(update_file) = VERBOSITY_NAME(INFO);
+
+StringBuilder &operator<<(StringBuilder &string_builder, FileLocationSource source) {
+ switch (source) {
+ case FileLocationSource::None:
+ return string_builder << "None";
+ case FileLocationSource::FromUser:
+ return string_builder << "User";
+ case FileLocationSource::FromBinlog:
+ return string_builder << "Binlog";
+ case FileLocationSource::FromDatabase:
+ return string_builder << "Database";
+ case FileLocationSource::FromServer:
+ return string_builder << "Server";
+ default:
+ UNREACHABLE();
+ return string_builder << "Unknown";
+ }
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, FileManager::Query::Type type) {
+ switch (type) {
+ case FileManager::Query::Type::UploadByHash:
+ return string_builder << "UploadByHash";
+ case FileManager::Query::Type::UploadWaitFileReference:
+ return string_builder << "UploadWaitFileReference";
+ case FileManager::Query::Type::Upload:
+ return string_builder << "Upload";
+ case FileManager::Query::Type::DownloadWaitFileReference:
+ return string_builder << "DownloadWaitFileReference";
+ case FileManager::Query::Type::DownloadReloadDialog:
+ return string_builder << "DownloadReloadDialog";
+ case FileManager::Query::Type::Download:
+ return string_builder << "Download";
+ case FileManager::Query::Type::SetContent:
+ return string_builder << "SetContent";
+ case FileManager::Query::Type::Generate:
+ return string_builder << "Generate";
+ default:
+ UNREACHABLE();
+ return string_builder << "Unknown";
+ }
+}
+
+NewRemoteFileLocation::NewRemoteFileLocation(RemoteFileLocation remote, FileLocationSource source) {
+ switch (remote.type()) {
+ case RemoteFileLocation::Type::Empty:
+ break;
+ case RemoteFileLocation::Type::Partial:
+ partial = make_unique<PartialRemoteFileLocation>(remote.partial());
+ break;
+ case RemoteFileLocation::Type::Full:
+ full = remote.full();
+ full_source = source;
+ is_full_alive = true;
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
-static int VERBOSITY_NAME(update_file) = VERBOSITY_NAME(INFO);
+RemoteFileLocation NewRemoteFileLocation::partial_or_empty() const {
+ if (partial) {
+ return RemoteFileLocation(*partial);
+ }
+ return {};
+}
FileNode *FileNodePtr::operator->() const {
return get();
@@ -59,45 +143,202 @@ FileNode *FileNodePtr::get_unsafe() const {
return file_manager_->get_file_node_raw(file_id_);
}
-FileNodePtr::operator bool() const {
+FileNodePtr::operator bool() const noexcept {
return file_manager_ != nullptr && get_unsafe() != nullptr;
}
-void FileNode::set_local_location(const LocalFileLocation &local, int64 ready_size) {
+void FileNode::recalc_ready_prefix_size(int64 prefix_offset, int64 ready_prefix_size) {
+ if (local_.type() != LocalFileLocation::Type::Partial) {
+ return;
+ }
+ int64 new_local_ready_prefix_size;
+ if (download_offset_ == prefix_offset) {
+ new_local_ready_prefix_size = ready_prefix_size;
+ } else {
+ new_local_ready_prefix_size = Bitmask(Bitmask::Decode{}, local_.partial().ready_bitmask_)
+ .get_ready_prefix_size(download_offset_, local_.partial().part_size_, size_);
+ }
+ if (new_local_ready_prefix_size != local_ready_prefix_size_) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed local_ready_prefix_size from "
+ << local_ready_prefix_size_ << " to " << new_local_ready_prefix_size;
+ local_ready_prefix_size_ = new_local_ready_prefix_size;
+ on_info_changed();
+ }
+}
+
+void FileNode::init_ready_size() {
+ if (local_.type() != LocalFileLocation::Type::Partial) {
+ return;
+ }
+ auto bitmask = Bitmask(Bitmask::Decode{}, local_.partial().ready_bitmask_);
+ local_ready_prefix_size_ = bitmask.get_ready_prefix_size(0, local_.partial().part_size_, size_);
+ local_ready_size_ = bitmask.get_total_size(local_.partial().part_size_, size_);
+}
+
+void FileNode::set_download_offset(int64 download_offset) {
+ if (download_offset < 0 || download_offset > MAX_FILE_SIZE) {
+ // KEEP_DOWNLOAD_OFFSET is handled here
+ return;
+ }
+ if (download_offset == download_offset_) {
+ return;
+ }
+
+ VLOG(update_file) << "File " << main_file_id_ << " has changed download_offset from " << download_offset_ << " to "
+ << download_offset;
+ download_offset_ = download_offset;
+ is_download_offset_dirty_ = true;
+ recalc_ready_prefix_size(-1, -1);
+ on_info_changed();
+}
+
+int64 FileNode::get_download_limit() const {
+ if (ignore_download_limit_) {
+ return 0;
+ }
+ return private_download_limit_;
+}
+
+void FileNode::update_effective_download_limit(int64 old_download_limit) {
+ if (get_download_limit() == old_download_limit) {
+ return;
+ }
+
+ // There should be no false positives here
+ // When we use IGNORE_DOWNLOAD_LIMIT, set_download_limit will be ignored
+ // And in case we turn off ignore_download_limit, set_download_limit will not change effective download limit
+ VLOG(update_file) << "File " << main_file_id_ << " has changed download_limit from " << old_download_limit << " to "
+ << get_download_limit() << " (limit=" << private_download_limit_
+ << ";ignore=" << ignore_download_limit_ << ")";
+ is_download_limit_dirty_ = true;
+}
+
+void FileNode::set_download_limit(int64 download_limit) {
+ if (download_limit < 0) {
+ // KEEP_DOWNLOAD_LIMIT is handled here
+ return;
+ }
+ if (download_limit > MAX_FILE_SIZE) {
+ download_limit = MAX_FILE_SIZE;
+ }
+ auto old_download_limit = get_download_limit();
+ private_download_limit_ = download_limit;
+ update_effective_download_limit(old_download_limit);
+}
+
+void FileNode::set_ignore_download_limit(bool ignore_download_limit) {
+ auto old_download_limit = get_download_limit();
+ ignore_download_limit_ = ignore_download_limit;
+ update_effective_download_limit(old_download_limit);
+}
+
+void FileNode::drop_local_location() {
+ set_local_location(LocalFileLocation(), 0, -1, -1);
+}
+
+void FileNode::set_local_location(const LocalFileLocation &local, int64 ready_size, int64 prefix_offset,
+ int64 ready_prefix_size) {
if (local_ready_size_ != ready_size) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed local ready size from " << local_ready_size_
+ << " to " << ready_size;
local_ready_size_ = ready_size;
- VLOG(update_file) << "File " << main_file_id_ << " has changed local ready size";
on_info_changed();
}
if (local_ != local) {
VLOG(update_file) << "File " << main_file_id_ << " has changed local location";
local_ = local;
+
+ recalc_ready_prefix_size(prefix_offset, ready_prefix_size);
+
on_changed();
}
}
-void FileNode::set_remote_location(const RemoteFileLocation &remote, FileLocationSource source, int64 ready_size) {
- if (remote_ready_size_ != ready_size) {
- remote_ready_size_ = ready_size;
- VLOG(update_file) << "File " << main_file_id_ << " has changed remote ready size";
- on_info_changed();
- }
- if (remote_ == remote) {
- if (remote_.type() == RemoteFileLocation::Type::Full) {
- if (remote_.full().get_access_hash() == remote.full().get_access_hash()) {
- return;
+void FileNode::set_new_remote_location(NewRemoteFileLocation new_remote) {
+ if (new_remote.full) {
+ if (remote_.full && remote_.full.value() == new_remote.full.value()) {
+ if (remote_.full.value().get_access_hash() != new_remote.full.value().get_access_hash() ||
+ remote_.full.value().get_file_reference() != new_remote.full.value().get_file_reference() ||
+ remote_.full.value().get_source() != new_remote.full.value().get_source()) {
+ on_pmc_changed();
}
} else {
- return;
+ VLOG(update_file) << "File " << main_file_id_ << " has changed remote location";
+ on_changed();
}
+ remote_.full = new_remote.full;
+ remote_.full_source = new_remote.full_source;
+ remote_.is_full_alive = new_remote.is_full_alive;
+ } else {
+ if (remote_.full) {
+ VLOG(update_file) << "File " << main_file_id_ << " has lost remote location";
+ remote_.full = {};
+ remote_.is_full_alive = false;
+ remote_.full_source = FileLocationSource::None;
+ on_changed();
+ }
+ }
+
+ if (new_remote.partial) {
+ set_partial_remote_location(*new_remote.partial, new_remote.ready_size);
+ } else {
+ delete_partial_remote_location();
+ }
+}
+void FileNode::delete_partial_remote_location() {
+ if (remote_.partial) {
+ VLOG(update_file) << "File " << main_file_id_ << " has lost partial remote location";
+ remote_.partial.reset();
+ on_changed();
+ }
+}
+
+void FileNode::set_partial_remote_location(PartialRemoteFileLocation remote, int64 ready_size) {
+ if (remote_.is_full_alive) {
+ VLOG(update_file) << "File " << main_file_id_ << " remote is still alive, so there is NO reason to update partial";
+ return;
+ }
+ if (remote_.ready_size != ready_size) {
+ VLOG(update_file) << "File " << main_file_id_ << " has changed remote ready size from " << remote_.ready_size
+ << " to " << ready_size;
+ remote_.ready_size = ready_size;
+ on_info_changed();
+ }
+ if (remote_.partial && *remote_.partial == remote) {
+ VLOG(update_file) << "Partial location of " << main_file_id_ << " is NOT changed";
+ return;
+ }
+ if (!remote_.partial && remote.ready_part_count_ == 0) {
+ // empty partial remote is equal to empty remote
+ VLOG(update_file) << "Partial location of " << main_file_id_
+ << " is still empty, so there is NO reason to update it";
+ return;
}
- VLOG(update_file) << "File " << main_file_id_ << " has changed remote location";
- remote_ = remote;
- remote_source_ = source;
+ VLOG(update_file) << "File " << main_file_id_ << " partial location has changed to " << remote;
+ remote_.partial = make_unique<PartialRemoteFileLocation>(std::move(remote));
on_changed();
}
+bool FileNode::delete_file_reference(Slice file_reference) {
+ if (!remote_.full) {
+ VLOG(file_references) << "Can't delete file reference, because there is no remote location";
+ return false;
+ }
+
+ if (!remote_.full.value().delete_file_reference(file_reference)) {
+ VLOG(file_references) << "Can't delete unmatching file reference " << format::escaped(file_reference) << ", have "
+ << format::escaped(remote_.full.value().get_file_reference());
+ return false;
+ }
+
+ VLOG(file_references) << "Do delete file reference of main file " << main_file_id_;
+ upload_was_update_file_reference_ = false;
+ download_was_update_file_reference_ = false;
+ on_pmc_changed();
+ return true;
+}
+
void FileNode::set_generate_location(unique_ptr<FullGenerateFileLocation> &&generate) {
bool is_changed = generate_ == nullptr ? generate != nullptr : generate == nullptr || *generate_ != *generate;
if (is_changed) {
@@ -151,6 +392,16 @@ void FileNode::set_encryption_key(FileEncryptionKey key) {
}
}
+void FileNode::set_upload_pause(FileId upload_pause) {
+ if (upload_pause_ != upload_pause) {
+ LOG(INFO) << "Change file " << main_file_id_ << " upload_pause from " << upload_pause_ << " to " << upload_pause;
+ if (upload_pause_.is_valid() != upload_pause.is_valid()) {
+ on_info_changed();
+ }
+ upload_pause_ = upload_pause;
+ }
+}
+
void FileNode::set_download_priority(int8 priority) {
if ((download_priority_ == 0) != (priority == 0)) {
VLOG(update_file) << "File " << main_file_id_ << " has changed download priority to " << priority;
@@ -160,7 +411,7 @@ void FileNode::set_download_priority(int8 priority) {
}
void FileNode::set_upload_priority(int8 priority) {
- if ((upload_priority_ == 0) != (priority == 0)) {
+ if (!remote_.is_full_alive && (upload_priority_ == 0) != (priority == 0)) {
VLOG(update_file) << "File " << main_file_id_ << " has changed upload priority to " << priority;
on_info_changed();
}
@@ -168,7 +419,8 @@ void FileNode::set_upload_priority(int8 priority) {
}
void FileNode::set_generate_priority(int8 download_priority, int8 upload_priority) {
- if ((download_priority_ == 0) != (download_priority == 0) || (upload_priority_ == 0) != (upload_priority == 0)) {
+ if ((generate_download_priority_ == 0) != (download_priority == 0) ||
+ (generate_upload_priority_ == 0) != (upload_priority == 0)) {
VLOG(update_file) << "File " << main_file_id_ << " has changed generate priority to " << download_priority << "/"
<< upload_priority;
on_info_changed();
@@ -182,9 +434,11 @@ void FileNode::on_changed() {
on_pmc_changed();
on_info_changed();
}
+
void FileNode::on_info_changed() {
info_changed_flag_ = true;
}
+
void FileNode::on_pmc_changed() {
pmc_changed_flag_ = true;
}
@@ -199,7 +453,7 @@ bool FileNode::need_pmc_flush() const {
}
// already in pmc
- if (pmc_id_ != 0) {
+ if (pmc_id_.is_valid()) {
return true;
}
@@ -215,12 +469,12 @@ bool FileNode::need_pmc_flush() const {
has_generate_location = false;
}
- if (remote_.type() == RemoteFileLocation::Type::Full &&
- (has_generate_location || local_.type() != LocalFileLocation::Type::Empty)) {
+ if (remote_.full/* &&
+ (has_generate_location || local_.type() != LocalFileLocation::Type::Empty)*/) {
+ // we need to always save file sources
return true;
}
- if (local_.type() == LocalFileLocation::Type::Full &&
- (has_generate_location || remote_.type() != RemoteFileLocation::Type::Empty)) {
+ if (local_.type() == LocalFileLocation::Type::Full && (has_generate_location || remote_.full || remote_.partial)) {
return true;
}
@@ -236,7 +490,7 @@ void FileNode::on_info_flushed() {
info_changed_flag_ = false;
}
-string FileNode::suggested_name() const {
+string FileNode::suggested_path() const {
if (!remote_name_.empty()) {
return remote_name_;
}
@@ -258,24 +512,61 @@ string FileNode::suggested_name() const {
bool FileView::has_local_location() const {
return node_->local_.type() == LocalFileLocation::Type::Full;
}
+
const FullLocalFileLocation &FileView::local_location() const {
CHECK(has_local_location());
return node_->local_.full();
}
+
bool FileView::has_remote_location() const {
- return node_->remote_.type() == RemoteFileLocation::Type::Full;
+ return static_cast<bool>(node_->remote_.full);
}
+
+bool FileView::has_alive_remote_location() const {
+ return node_->remote_.is_full_alive;
+}
+
+bool FileView::has_active_upload_remote_location() const {
+ if (!has_remote_location()) {
+ return false;
+ }
+ if (!has_alive_remote_location()) {
+ return false;
+ }
+ if (main_remote_location().is_encrypted_any()) {
+ return true;
+ }
+ return main_remote_location().has_file_reference();
+}
+
+bool FileView::has_active_download_remote_location() const {
+ if (!has_remote_location()) {
+ return false;
+ }
+ if (remote_location().is_encrypted_any()) {
+ return true;
+ }
+ return remote_location().has_file_reference();
+}
+
const FullRemoteFileLocation &FileView::remote_location() const {
CHECK(has_remote_location());
auto *remote = node_.get_remote();
if (remote) {
return *remote;
}
- return node_->remote_.full();
+ return node_->remote_.full.value();
}
+
+const FullRemoteFileLocation &FileView::main_remote_location() const {
+ CHECK(has_remote_location());
+ return node_->remote_.full.value();
+}
+
bool FileView::has_generate_location() const {
return node_->generate_ != nullptr;
}
+
const FullGenerateFileLocation &FileView::generate_location() const {
CHECK(has_generate_location());
return *node_->generate_;
@@ -285,23 +576,73 @@ int64 FileView::size() const {
return node_->size_;
}
-int64 FileView::expected_size() const {
+int64 FileView::get_allocated_local_size() const {
+ auto file_path = path();
+ if (file_path.empty()) {
+ return 0;
+ }
+ auto r_stat = stat(file_path);
+ if (r_stat.is_error()) {
+ return 0;
+ }
+ return r_stat.ok().real_size_;
+}
+
+int64 FileView::expected_size(bool may_guess) const {
if (node_->size_ != 0) {
return node_->size_;
}
- return node_->expected_size_;
+ int64 current_size = local_total_size(); // TODO: this is not the best approximation
+ if (node_->expected_size_ != 0) {
+ return max(current_size, node_->expected_size_);
+ }
+ if (may_guess && node_->local_.type() == LocalFileLocation::Type::Partial) {
+ current_size *= 3;
+ }
+ return current_size;
}
bool FileView::is_downloading() const {
return node_->download_priority_ != 0 || node_->generate_download_priority_ != 0;
}
-int64 FileView::local_size() const {
+int64 FileView::download_offset() const {
+ return node_->download_offset_;
+}
+
+int64 FileView::downloaded_prefix(int64 offset) const {
switch (node_->local_.type()) {
+ case LocalFileLocation::Type::Empty:
+ return 0;
case LocalFileLocation::Type::Full:
- return node_->size_;
+ if (offset < node_->size_) {
+ return node_->size_ - offset;
+ }
+ return 0;
case LocalFileLocation::Type::Partial:
- return node_->local_.partial().part_size_ * node_->local_.partial().ready_part_count_;
+ if (is_encrypted_secure()) {
+ // File is not decrypted and verified yet
+ return 0;
+ }
+ return Bitmask(Bitmask::Decode{}, node_->local_.partial().ready_bitmask_)
+ .get_ready_prefix_size(offset, node_->local_.partial().part_size_, node_->size_);
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+}
+
+int64 FileView::local_prefix_size() const {
+ switch (node_->local_.type()) {
+ case LocalFileLocation::Type::Full:
+ return node_->download_offset_ <= node_->size_ ? node_->size_ - node_->download_offset_ : 0;
+ case LocalFileLocation::Type::Partial: {
+ if (is_encrypted_secure()) {
+ // File is not decrypted and verified yet
+ return 0;
+ }
+ return node_->local_ready_prefix_size_;
+ }
default:
return 0;
}
@@ -313,8 +654,9 @@ int64 FileView::local_total_size() const {
case LocalFileLocation::Type::Full:
return node_->size_;
case LocalFileLocation::Type::Partial:
- return max(static_cast<int64>(node_->local_.partial().part_size_) * node_->local_.partial().ready_part_count_,
- node_->local_ready_size_);
+ VLOG(update_file) << "Have local_ready_prefix_size = " << node_->local_ready_prefix_size_
+ << " and local_ready_size = " << node_->local_ready_size_;
+ return max(node_->local_ready_prefix_size_, node_->local_ready_size_);
default:
UNREACHABLE();
return 0;
@@ -322,25 +664,26 @@ int64 FileView::local_total_size() const {
}
bool FileView::is_uploading() const {
- return node_->upload_priority_ != 0 || node_->generate_upload_priority_ != 0;
+ return node_->upload_priority_ != 0 || node_->generate_upload_priority_ != 0 || node_->upload_pause_.is_valid();
}
int64 FileView::remote_size() const {
- switch (node_->remote_.type()) {
- case RemoteFileLocation::Type::Full:
- return node_->size_;
- case RemoteFileLocation::Type::Partial: {
- auto res =
- max(static_cast<int64>(node_->remote_.partial().part_size_) * node_->remote_.partial().ready_part_count_,
- node_->remote_ready_size_);
- if (size() != 0 && size() < res) {
- res = size();
- }
- return res;
+ if (node_->remote_.is_full_alive) {
+ return node_->size_;
+ }
+ if (node_->remote_.partial) {
+ auto part_size = static_cast<int64>(node_->remote_.partial->part_size_);
+ auto ready_part_count = node_->remote_.partial->ready_part_count_;
+ auto remote_ready_size = node_->remote_.ready_size;
+ VLOG(update_file) << "Have part_size = " << part_size << ", remote_ready_part_count = " << ready_part_count
+ << ", remote_ready_size = " << remote_ready_size << ", size = " << size();
+ auto res = max(part_size * ready_part_count, remote_ready_size);
+ if (size() != 0 && size() < res) {
+ res = size();
}
- default:
- return 0;
+ return res;
}
+ return node_->remote_.ready_size; //???
}
string FileView::path() const {
@@ -350,7 +693,7 @@ string FileView::path() const {
case LocalFileLocation::Type::Partial:
return node_->local_.partial().path_;
default:
- return "";
+ return string();
}
}
@@ -366,8 +709,8 @@ const string &FileView::remote_name() const {
return node_->remote_name_;
}
-string FileView::suggested_name() const {
- return node_->suggested_name();
+string FileView::suggested_path() const {
+ return node_->suggested_path();
}
DialogId FileView::owner_dialog_id() const {
@@ -392,11 +735,19 @@ bool FileView::can_download_from_server() const {
if (remote_location().file_type_ == FileType::Encrypted && encryption_key().empty()) {
return false;
}
+ if (remote_location().is_web()) {
+ return true;
+ }
if (remote_location().get_dc_id().is_empty()) {
return false;
}
+ if (!remote_location().is_encrypted_any() && !remote_location().has_file_reference() &&
+ ((node_->download_id_ == 0 && node_->download_was_update_file_reference_) || !node_->remote_.is_full_alive)) {
+ return false;
+ }
return true;
}
+
bool FileView::can_generate() const {
return has_generate_location();
}
@@ -408,14 +759,68 @@ bool FileView::can_delete() const {
return node_->local_.type() == LocalFileLocation::Type::Partial;
}
+string FileView::get_unique_id(const FullGenerateFileLocation &location) {
+ return base64url_encode(zero_encode('\xff' + serialize(location)));
+}
+
+string FileView::get_unique_id(const FullRemoteFileLocation &location) {
+ return base64url_encode(zero_encode(serialize(location.as_unique())));
+}
+
+string FileView::get_persistent_id(const FullGenerateFileLocation &location) {
+ auto binary = serialize(location);
+
+ binary = zero_encode(binary);
+ binary.push_back(FileNode::PERSISTENT_ID_VERSION_GENERATED);
+ return base64url_encode(binary);
+}
+
+string FileView::get_persistent_id(const FullRemoteFileLocation &location) {
+ auto binary = serialize(location);
+
+ binary = zero_encode(binary);
+ binary.push_back(static_cast<char>(narrow_cast<uint8>(Version::Next) - 1));
+ binary.push_back(FileNode::PERSISTENT_ID_VERSION);
+ return base64url_encode(binary);
+}
+
+string FileView::get_persistent_file_id() const {
+ if (!empty()) {
+ if (has_alive_remote_location()) {
+ return get_persistent_id(remote_location());
+ } else if (has_url()) {
+ return url();
+ } else if (has_generate_location() && FileManager::is_remotely_generated_file(generate_location().conversion_)) {
+ return get_persistent_id(generate_location());
+ }
+ }
+ return string();
+}
+
+string FileView::get_unique_file_id() const {
+ if (!empty()) {
+ if (has_alive_remote_location()) {
+ if (!remote_location().is_web()) {
+ return get_unique_id(remote_location());
+ }
+ } else if (has_generate_location() && FileManager::is_remotely_generated_file(generate_location().conversion_)) {
+ return get_unique_id(generate_location());
+ }
+ }
+ return string();
+}
+
/*** FileManager ***/
+static int merge_choose_remote_location(const FullRemoteFileLocation &x, FileLocationSource x_source,
+ const FullRemoteFileLocation &y, FileLocationSource y_source);
+
namespace {
void prepare_path_for_pmc(FileType file_type, string &path) {
path = PathView::relative(path, get_files_base_dir(file_type)).str();
}
} // namespace
-FileManager::FileManager(std::unique_ptr<Context> context) : context_(std::move(context)) {
+FileManager::FileManager(unique_ptr<Context> context) : context_(std::move(context)) {
if (G()->parameters().use_file_db) {
file_db_ = G()->td_db()->get_file_db_shared();
}
@@ -424,32 +829,7 @@ FileManager::FileManager(std::unique_ptr<Context> context) : context_(std::move(
next_file_id();
next_file_node_id();
- std::vector<string> dirs;
- auto create_dir = [&](CSlice path) {
- dirs.push_back(path.str());
- auto status = mkdir(path, 0750);
- if (status.is_error()) {
- auto r_stat = stat(path);
- if (r_stat.is_ok() && r_stat.ok().is_dir_) {
- LOG(ERROR) << "mkdir " << tag("path", path) << " failed " << status << ", but directory exists";
- } else {
- LOG(ERROR) << "mkdir " << tag("path", path) << " failed " << status;
- }
- }
-#if TD_ANDROID
- FileFd::open(dirs.back() + ".nomedia", FileFd::Create | FileFd::Read).ignore();
-#endif
- };
- for (int i = 0; i < file_type_size; i++) {
- auto path = get_files_dir(FileType(i));
- create_dir(path);
- }
-
- // Create both temp dirs.
- create_dir(get_files_temp_dir(FileType::Encrypted));
- create_dir(get_files_temp_dir(FileType::Video));
-
- G()->td_db()->with_db_path([this](CSlice path) { this->bad_paths_.insert(path.str()); });
+ G()->td_db()->with_db_path([bad_paths = &bad_paths_](CSlice path) { bad_paths->insert(path.str()); });
}
void FileManager::init_actor() {
@@ -458,13 +838,15 @@ void FileManager::init_actor() {
file_generate_manager_ = create_actor_on_scheduler<FileGenerateManager>(
"FileGenerateManager", G()->get_slow_net_scheduler_id(), context_->create_reference());
}
+
FileManager::~FileManager() {
- // NB: As FileLoadManager callback is just "this" pointer, this event must be processed immediately.
- send_closure(std::move(file_load_manager_), &FileLoadManager::close);
+ Scheduler::instance()->destroy_on_scheduler(G()->get_gc_scheduler_id(), remote_location_info_, file_hash_to_file_id_,
+ local_location_to_file_id_, generate_location_to_file_id_,
+ pmc_id_to_file_node_id_, file_id_info_, empty_file_ids_, file_nodes_);
}
string FileManager::fix_file_extension(Slice file_name, Slice file_type, Slice file_extension) {
- return (file_name.empty() ? file_type : file_name).str() + "." + file_extension.str();
+ return PSTRING() << (file_name.empty() ? file_type : file_name) << '.' << file_extension;
}
string FileManager::get_file_name(FileType file_type, Slice path) {
@@ -486,13 +868,14 @@ string FileManager::get_file_name(FileType file_type, Slice path) {
break;
case FileType::VoiceNote:
if (extension != "ogg" && extension != "oga" && extension != "mp3" && extension != "mpeg3" &&
- extension != "m4a") {
+ extension != "m4a" && extension != "opus") {
return fix_file_extension(file_name, "voice", "oga");
}
break;
case FileType::Video:
case FileType::VideoNote:
- if (extension != "mov" && extension != "3gp" && extension != "mpeg4" && extension != "mp4") {
+ if (extension != "mov" && extension != "3gp" && extension != "mpeg4" && extension != "mp4" &&
+ extension != "mkv") {
return fix_file_extension(file_name, "video", "mp4");
}
break;
@@ -502,13 +885,31 @@ string FileManager::get_file_name(FileType file_type, Slice path) {
return fix_file_extension(file_name, "audio", "mp3");
}
break;
- case FileType::Document:
+ case FileType::Wallpaper:
+ case FileType::Background:
+ if (extension != "jpg" && extension != "jpeg" && extension != "png") {
+ return fix_file_extension(file_name, "wallpaper", "jpg");
+ }
+ break;
case FileType::Sticker:
+ if (extension != "webp" && extension != "tgs" && extension != "webm") {
+ return fix_file_extension(file_name, "sticker", "webp");
+ }
+ break;
+ case FileType::Ringtone:
+ if (extension != "ogg" && extension != "oga" && extension != "mp3" && extension != "mpeg3") {
+ return fix_file_extension(file_name, "notification_sound", "mp3");
+ }
+ break;
+ case FileType::Document:
case FileType::Animation:
case FileType::Encrypted:
case FileType::Temp:
case FileType::EncryptedThumbnail:
- case FileType::Wallpaper:
+ case FileType::SecureEncrypted:
+ case FileType::SecureDecrypted:
+ case FileType::DocumentAsFile:
+ case FileType::CallLog:
break;
default:
UNREACHABLE();
@@ -516,93 +917,186 @@ string FileManager::get_file_name(FileType file_type, Slice path) {
return file_name.str();
}
-Status FileManager::check_local_location(FullLocalFileLocation &location, int64 &size) {
- constexpr int64 MAX_THUMBNAIL_SIZE = 200 * (1 << 10) /* 200KB */;
- constexpr int64 MAX_FILE_SIZE = 1500 * (1 << 20) /* 1500MB */;
+bool FileManager::is_remotely_generated_file(Slice conversion) {
+ return begins_with(conversion, "#map#") || begins_with(conversion, "#audio_t#");
+}
- if (location.path_.empty()) {
- return Status::Error("File must have non-empty path");
+void FileManager::check_local_location(FileId file_id, bool skip_file_size_checks) {
+ auto node = get_sync_file_node(file_id);
+ if (node) {
+ check_local_location(node, skip_file_size_checks).ignore();
}
- TRY_RESULT(path, realpath(location.path_, true));
- if (bad_paths_.count(path) != 0) {
- return Status::Error("Sending of internal database files is forbidden");
+}
+
+Status FileManager::check_local_location(FileNodePtr node, bool skip_file_size_checks) {
+ Status status;
+ if (node->local_.type() == LocalFileLocation::Type::Full) {
+ auto r_info = check_full_local_location({node->local_.full(), node->size_}, skip_file_size_checks);
+ if (r_info.is_error()) {
+ status = r_info.move_as_error();
+ } else if (bad_paths_.count(r_info.ok().location_.path_) != 0) {
+ status = Status::Error(400, "Sending of internal database files is forbidden");
+ } else if (r_info.ok().location_ != node->local_.full() || r_info.ok().size_ != node->size_) {
+ LOG(ERROR) << "Local location changed from " << node->local_.full() << " with size " << node->size_ << " to "
+ << r_info.ok().location_ << " with size " << r_info.ok().size_;
+ }
+ } else if (node->local_.type() == LocalFileLocation::Type::Partial) {
+ status = check_partial_local_location(node->local_.partial());
}
- location.path_ = std::move(path);
- TRY_RESULT(stat, stat(location.path_));
- if (!stat.is_reg_) {
- return Status::Error("File must be a regular file");
+ if (status.is_error()) {
+ on_failed_check_local_location(node);
}
- if (stat.size_ < 0) {
- // TODO is it possible?
- return Status::Error("File is too big");
+ return status;
+}
+
+void FileManager::on_failed_check_local_location(FileNodePtr node) {
+ send_closure(G()->download_manager(), &DownloadManager::remove_file_if_finished, node->main_file_id_);
+ node->drop_local_location();
+ try_flush_node(node, "on_failed_check_local_location");
+}
+
+void FileManager::check_local_location_async(FileNodePtr node, bool skip_file_size_checks, Promise<Unit> promise) {
+ if (node->local_.type() == LocalFileLocation::Type::Empty) {
+ return promise.set_value(Unit());
}
- if (stat.size_ == 0) {
- return Status::Error("File must be non-empty");
+
+ if (node->local_.type() == LocalFileLocation::Type::Full) {
+ send_closure(file_load_manager_, &FileLoadManager::check_full_local_location,
+ FullLocalLocationInfo{node->local_.full(), node->size_}, skip_file_size_checks,
+ PromiseCreator::lambda([actor_id = actor_id(this), file_id = node->main_file_id_,
+ checked_location = node->local_,
+ promise = std::move(promise)](Result<FullLocalLocationInfo> result) mutable {
+ send_closure(actor_id, &FileManager::on_check_full_local_location, file_id,
+ std::move(checked_location), std::move(result), std::move(promise));
+ }));
+ } else {
+ CHECK(node->local_.type() == LocalFileLocation::Type::Partial);
+ send_closure(file_load_manager_, &FileLoadManager::check_partial_local_location, node->local_.partial(),
+ PromiseCreator::lambda([actor_id = actor_id(this), file_id = node->main_file_id_,
+ checked_location = node->local_,
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ send_closure(actor_id, &FileManager::on_check_partial_local_location, file_id,
+ std::move(checked_location), std::move(result), std::move(promise));
+ }));
}
+}
+
+void FileManager::on_check_full_local_location(FileId file_id, LocalFileLocation checked_location,
+ Result<FullLocalLocationInfo> r_info, Promise<Unit> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
- if (size == 0) {
- size = stat.size_;
+ auto node = get_file_node(file_id);
+ if (!node) {
+ return;
}
- if (location.mtime_nsec_ == 0) {
- LOG(INFO) << "Set file \"" << location.path_ << "\" modification time to " << stat.mtime_nsec_;
- location.mtime_nsec_ = stat.mtime_nsec_;
- } else if (location.mtime_nsec_ != stat.mtime_nsec_) {
- LOG(INFO) << "File \"" << location.path_ << "\" was nodified: old mtime = " << location.mtime_nsec_
- << ", new mtime = " << stat.mtime_nsec_;
- return Status::Error(PSLICE() << "File \"" << location.path_ << "\" was modified");
+ if (node->local_ != checked_location) {
+ LOG(INFO) << "Full location changed while being checked; ignore check result";
+ return promise.set_value(Unit());
}
- if ((location.file_type_ == FileType::Thumbnail || location.file_type_ == FileType::EncryptedThumbnail) &&
- size >= MAX_THUMBNAIL_SIZE) {
- return Status::Error(PSLICE() << "File \"" << location.path_ << "\" is too big for thumbnail "
- << tag("size", format::as_size(size)));
+ Status status;
+ if (r_info.is_error()) {
+ status = r_info.move_as_error();
+ } else if (bad_paths_.count(r_info.ok().location_.path_) != 0) {
+ status = Status::Error(400, "Sending of internal database files is forbidden");
+ } else if (r_info.ok().location_ != node->local_.full() || r_info.ok().size_ != node->size_) {
+ LOG(ERROR) << "Local location changed from " << node->local_.full() << " with size " << node->size_ << " to "
+ << r_info.ok().location_ << " with size " << r_info.ok().size_;
}
- if (size >= MAX_FILE_SIZE) {
- return Status::Error(PSLICE() << "File \"" << location.path_ << "\" is too big "
- << tag("size", format::as_size(size)));
+ if (status.is_error()) {
+ on_failed_check_local_location(node);
+ promise.set_error(std::move(status));
+ } else {
+ promise.set_value(Unit());
}
- return Status::OK();
}
-static Status check_partial_local_location(const PartialLocalFileLocation &location) {
- TRY_RESULT(stat, stat(location.path_));
- if (!stat.is_reg_) {
- if (stat.is_dir_) {
- return Status::Error(PSLICE() << "Can't use directory \"" << location.path_ << "\" as a file path");
- }
- return Status::Error("File must be a regular file");
+void FileManager::on_check_partial_local_location(FileId file_id, LocalFileLocation checked_location,
+ Result<Unit> result, Promise<Unit> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto node = get_file_node(file_id);
+ CHECK(node);
+ if (node->local_ != checked_location) {
+ LOG(INFO) << "Partial location changed while being checked; ignore check result";
+ return promise.set_value(Unit());
+ }
+ if (result.is_error()) {
+ on_failed_check_local_location(node);
+ promise.set_error(result.move_as_error());
+ } else {
+ promise.set_value(Unit());
}
- // can't check mtime. Hope nobody will mess with this file in our temporary dir.
- return Status::OK();
}
-Status FileManager::check_local_location(FileNodePtr node) {
- Status status;
- if (node->local_.type() == LocalFileLocation::Type::Full) {
- status = check_local_location(node->local_.full(), node->size_);
- } else if (node->local_.type() == LocalFileLocation::Type::Partial) {
- status = check_partial_local_location(node->local_.partial());
+void FileManager::recheck_full_local_location(FullLocalLocationInfo location_info, bool skip_file_size_checks) {
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), checked_location = location_info.location_](
+ Result<FullLocalLocationInfo> result) mutable {
+ send_closure(actor_id, &FileManager::on_recheck_full_local_location, std::move(checked_location),
+ std::move(result));
+ });
+ send_closure(file_load_manager_, &FileLoadManager::check_full_local_location, std::move(location_info),
+ skip_file_size_checks, std::move(promise));
+}
+
+void FileManager::on_recheck_full_local_location(FullLocalFileLocation checked_location,
+ Result<FullLocalLocationInfo> r_info) {
+ if (G()->close_flag()) {
+ return;
}
- if (status.is_error()) {
- node->set_local_location(LocalFileLocation(), 0);
- try_flush_node(node);
+
+ auto file_id_it = local_location_to_file_id_.find(checked_location);
+ if (file_id_it == local_location_to_file_id_.end()) {
+ return;
}
- return status;
+ auto file_id = file_id_it->second;
+
+ on_check_full_local_location(file_id, LocalFileLocation(checked_location), std::move(r_info), Promise<Unit>());
+}
+
+bool FileManager::try_fix_partial_local_location(FileNodePtr node) {
+ LOG(INFO) << "Trying to fix partial local location";
+ if (node->local_.type() != LocalFileLocation::Type::Partial) {
+ LOG(INFO) << " failed - not a partial location";
+ return false;
+ }
+ auto partial = node->local_.partial();
+ if (!partial.iv_.empty()) {
+ // can't recalc iv_
+ LOG(INFO) << " failed - partial location has nonempty iv";
+ return false;
+ }
+ if (partial.part_size_ >= 512 * (1 << 10) || (partial.part_size_ & (partial.part_size_ - 1)) != 0) {
+ LOG(INFO) << " failed - too big part_size already: " << partial.part_size_;
+ return false;
+ }
+ auto old_part_size = narrow_cast<int32>(partial.part_size_);
+ int new_part_size = 512 * (1 << 10);
+ auto k = new_part_size / old_part_size;
+ Bitmask mask(Bitmask::Decode(), partial.ready_bitmask_);
+ auto new_mask = mask.compress(k);
+
+ partial.part_size_ = new_part_size;
+ partial.ready_bitmask_ = new_mask.encode();
+
+ auto ready_size = new_mask.get_total_size(partial.part_size_, node->size_);
+ node->set_local_location(LocalFileLocation(std::move(partial)), ready_size, -1, -1);
+ LOG(INFO) << " ok: increase part_size " << old_part_size << "->" << new_part_size;
+ return true;
}
FileManager::FileIdInfo *FileManager::get_file_id_info(FileId file_id) {
- CHECK(0 <= file_id.get() && file_id.get() < static_cast<int32>(file_id_info_.size()))
- << file_id << " " << file_id_info_.size();
+ CHECK(static_cast<size_t>(file_id.get()) < file_id_info_.size());
return &file_id_info_[file_id.get()];
}
-FileId FileManager::dup_file_id(FileId file_id) {
+FileId FileManager::dup_file_id(FileId file_id, const char *source) {
int32 file_node_id;
auto *file_node = get_file_node_raw(file_id, &file_node_id);
if (!file_node) {
return FileId();
}
- auto result = create_file_id(file_node_id, file_node);
- LOG(INFO) << "Dup file " << file_id << " to " << result;
+ auto result = FileId(create_file_id(file_node_id, file_node).get(), file_id.get_remote());
+ LOG(INFO) << "Dup file " << file_id << " to " << result << " from " << source;
return result;
}
@@ -612,20 +1106,24 @@ FileId FileManager::create_file_id(int32 file_node_id, FileNode *file_node) {
file_node->file_ids_.push_back(file_id);
return file_id;
}
+
void FileManager::try_forget_file_id(FileId file_id) {
auto *info = get_file_id_info(file_id);
- if (info->send_updates_flag_ || info->pin_flag_) {
+ if (info->send_updates_flag_ || info->pin_flag_ || info->sent_file_id_flag_) {
+ LOG(DEBUG) << "Can't forget file " << file_id << ", because of"
+ << (info->send_updates_flag_ ? " (sent updates)" : "") << (info->pin_flag_ ? " (pin)" : "")
+ << (info->sent_file_id_flag_ ? " (sent file identifier)" : "");
return;
}
auto file_node = get_file_node(file_id);
if (file_node->main_file_id_ == file_id) {
+ LOG(DEBUG) << "Can't forget main file " << file_id;
return;
}
- LOG(INFO) << "Forget file " << file_id;
- auto it = std::find(file_node->file_ids_.begin(), file_node->file_ids_.end(), file_id);
- CHECK(it != file_node->file_ids_.end());
- file_node->file_ids_.erase(it);
+ LOG(DEBUG) << "Forget file " << file_id;
+ bool is_removed = td::remove(file_node->file_ids_, file_id);
+ CHECK(is_removed);
*info = FileIdInfo();
empty_file_ids_.push_back(file_id.get());
}
@@ -642,29 +1140,41 @@ void FileManager::on_file_unlink(const FullLocalFileLocation &location) {
auto file_id = it->second;
auto file_node = get_sync_file_node(file_id);
CHECK(file_node);
- file_node->set_local_location(LocalFileLocation(), 0);
- try_flush_node_info(file_node);
+ clear_from_pmc(file_node);
+ send_closure(G()->download_manager(), &DownloadManager::remove_file_if_finished, file_node->main_file_id_);
+ file_node->drop_local_location();
+ try_flush_node(file_node, "on_file_unlink");
}
Result<FileId> FileManager::register_local(FullLocalFileLocation location, DialogId owner_dialog_id, int64 size,
- bool get_by_hash, bool force) {
+ bool get_by_hash, bool force, bool skip_file_size_checks,
+ FileId merge_file_id) {
// TODO: use get_by_hash
FileData data;
data.local_ = LocalFileLocation(std::move(location));
data.owner_dialog_id_ = owner_dialog_id;
data.size_ = size;
- return register_file(std::move(data), FileLocationSource::None /*won't be used*/, "register_local", force);
+ return register_file(std::move(data), FileLocationSource::None /*won't be used*/, merge_file_id, "register_local",
+ force, skip_file_size_checks);
}
-FileId FileManager::register_remote(const FullRemoteFileLocation &location, FileLocationSource file_location_source,
- DialogId owner_dialog_id, int64 size, int64 expected_size, string name) {
+FileId FileManager::register_remote(FullRemoteFileLocation location, FileLocationSource file_location_source,
+ DialogId owner_dialog_id, int64 size, int64 expected_size, string remote_name) {
FileData data;
- data.remote_ = RemoteFileLocation(location);
+ auto url = location.get_url();
+ data.remote_ = RemoteFileLocation(std::move(location));
data.owner_dialog_id_ = owner_dialog_id;
data.size_ = size;
data.expected_size_ = expected_size;
- data.remote_name_ = std::move(name);
- return register_file(std::move(data), file_location_source, "register_remote", false).move_as_ok();
+ data.remote_name_ = std::move(remote_name);
+
+ auto file_id = register_file(std::move(data), file_location_source, FileId(), "register_remote", false).move_as_ok();
+ if (!url.empty()) {
+ auto file_node = get_file_node(file_id);
+ CHECK(file_node);
+ file_node->set_url(url);
+ }
+ return file_id;
}
FileId FileManager::register_url(string url, FileType file_type, FileLocationSource file_location_source,
@@ -679,42 +1189,80 @@ FileId FileManager::register_url(string url, FileType file_type, FileLocationSou
Result<FileId> FileManager::register_generate(FileType file_type, FileLocationSource file_location_source,
string original_path, string conversion, DialogId owner_dialog_id,
int64 expected_size) {
+ // add #mtime# into conversion
+ if (!original_path.empty() && conversion[0] != '#' && PathView(original_path).is_absolute()) {
+ auto file_paths = log_interface->get_file_paths();
+ if (!td::contains(file_paths, original_path)) {
+ auto r_stat = stat(original_path);
+ uint64 mtime = r_stat.is_ok() ? r_stat.ok().mtime_nsec_ : 0;
+ conversion = PSTRING() << "#mtime#" << lpad0(to_string(mtime), 20) << '#' << conversion;
+ }
+ }
+
FileData data;
- data.generate_ = make_unique<FullGenerateFileLocation>(file_type, std::move(original_path), std::move(conversion));
+ data.generate_ =
+ td::make_unique<FullGenerateFileLocation>(file_type, std::move(original_path), std::move(conversion));
data.owner_dialog_id_ = owner_dialog_id;
data.expected_size_ = expected_size;
- return register_file(std::move(data), file_location_source, "register_generate", false);
+ return register_file(std::move(data), file_location_source, FileId(), "register_generate", false);
}
-Result<FileId> FileManager::register_file(FileData data, FileLocationSource file_location_source, const char *source,
- bool force) {
+Result<FileId> FileManager::register_file(FileData &&data, FileLocationSource file_location_source,
+ FileId merge_file_id, const char *source, bool force,
+ bool skip_file_size_checks) {
bool has_remote = data.remote_.type() == RemoteFileLocation::Type::Full;
bool has_generate = data.generate_ != nullptr;
if (data.local_.type() == LocalFileLocation::Type::Full && !force) {
- if (file_location_source == FileLocationSource::FromDb) {
+ bool is_from_database = file_location_source == FileLocationSource::FromBinlog ||
+ file_location_source == FileLocationSource::FromDatabase;
+ if (is_from_database) {
PathView path_view(data.local_.full().path_);
if (path_view.is_relative()) {
- data.local_.full().path_ = get_files_base_dir(data.local_.full().file_type_) + data.local_.full().path_;
+ data.local_.full().path_ = PSTRING()
+ << get_files_base_dir(data.local_.full().file_type_) << data.local_.full().path_;
}
}
- auto status = check_local_location(data.local_.full(), data.size_);
- if (status.is_error()) {
- LOG(WARNING) << "Invalid local location: " << status << " from " << source;
- data.local_ = LocalFileLocation();
- if (data.remote_.type() == RemoteFileLocation::Type::Partial) {
- data.remote_ = {};
+ if (file_location_source != FileLocationSource::FromDatabase) {
+ Status status;
+ auto r_info = check_full_local_location({data.local_.full(), data.size_}, skip_file_size_checks);
+ if (r_info.is_error()) {
+ status = r_info.move_as_error();
+ } else if (bad_paths_.count(r_info.ok().location_.path_) != 0) {
+ status = Status::Error(400, "Sending of internal database files is forbidden");
}
+ if (status.is_error()) {
+ LOG(INFO) << "Invalid " << data.local_.full() << ": " << status << " from " << source;
+ data.local_ = LocalFileLocation();
+ if (data.remote_.type() == RemoteFileLocation::Type::Partial) {
+ data.remote_ = {};
+ }
- if (!has_remote && !has_generate) {
- return std::move(status);
+ if (!has_remote && !has_generate) {
+ return std::move(status);
+ }
+ } else {
+ data.local_ = LocalFileLocation(std::move(r_info.ok().location_));
+ data.size_ = r_info.ok().size_;
}
+ } else {
+ // the location has been checked previously, but recheck it just in case
+ recheck_full_local_location({data.local_.full(), data.size_}, skip_file_size_checks);
}
}
bool has_local = data.local_.type() == LocalFileLocation::Type::Full;
bool has_location = has_local || has_remote || has_generate;
if (!has_location) {
- return Status::Error("No location");
+ return Status::Error(400, "No location");
+ }
+
+ if (data.size_ < 0) {
+ LOG(ERROR) << "Receive file of size " << data.size_;
+ data.size_ = 0;
+ }
+ if (data.expected_size_ < 0) {
+ LOG(ERROR) << "Receive file of expected size " << data.expected_size_;
+ data.expected_size_ = 0;
}
FileId file_id = next_file_id();
@@ -723,33 +1271,31 @@ Result<FileId> FileManager::register_file(FileData data, FileLocationSource file
// create FileNode
auto file_node_id = next_file_node_id();
auto &node = file_nodes_[file_node_id];
- node = std::make_unique<FileNode>(std::move(data.local_), std::move(data.remote_), std::move(data.generate_),
- data.size_, data.expected_size_, std::move(data.remote_name_), std::move(data.url_),
- data.owner_dialog_id_, std::move(data.encryption_key_), file_id,
- static_cast<int8>(has_remote));
- node->remote_source_ = file_location_source;
- node->pmc_id_ = data.pmc_id_;
+ node = td::make_unique<FileNode>(std::move(data.local_), NewRemoteFileLocation(data.remote_, file_location_source),
+ std::move(data.generate_), data.size_, data.expected_size_,
+ std::move(data.remote_name_), std::move(data.url_), data.owner_dialog_id_,
+ std::move(data.encryption_key_), file_id, static_cast<int8>(has_remote));
+ node->pmc_id_ = FileDbId(data.pmc_id_);
get_file_id_info(file_id)->node_id_ = file_node_id;
node->file_ids_.push_back(file_id);
FileView file_view(get_file_node(file_id));
- std::vector<FileId> to_merge;
- auto register_location = [&](const auto &location, auto &mp) {
+ vector<FileId> to_merge;
+ auto register_location = [&](const auto &location, auto &mp) -> FileId * {
auto &other_id = mp[location];
if (other_id.empty()) {
other_id = file_id;
- get_file_id_info(file_id)->pin_flag_ = true;
- return true;
+ return &other_id;
} else {
to_merge.push_back(other_id);
- return false;
+ return nullptr;
}
};
bool new_remote = false;
int32 remote_key = 0;
if (file_view.has_remote_location()) {
- RemoteInfo info{file_view.remote_location(), file_id};
+ RemoteInfo info{file_view.remote_location(), file_location_source, file_id};
remote_key = remote_location_info_.add(info);
auto &stored_info = remote_location_info_.get(remote_key);
if (stored_info.file_id_ == file_id) {
@@ -757,25 +1303,24 @@ Result<FileId> FileManager::register_file(FileData data, FileLocationSource file
new_remote = true;
} else {
to_merge.push_back(stored_info.file_id_);
- if (stored_info.remote_ == file_view.remote_location() &&
- stored_info.remote_.get_access_hash() != file_view.remote_location().get_access_hash() &&
- file_location_source == FileLocationSource::FromServer) {
+ if (merge_choose_remote_location(file_view.remote_location(), file_location_source, stored_info.remote_,
+ stored_info.file_location_source_) == 0) {
stored_info.remote_ = file_view.remote_location();
+ stored_info.file_location_source_ = file_location_source;
}
}
}
- bool new_local = false;
+ FileId *new_local_file_id = nullptr;
if (file_view.has_local_location()) {
- new_local = register_location(file_view.local_location(), local_location_to_file_id_);
+ new_local_file_id = register_location(file_view.local_location(), local_location_to_file_id_);
}
- bool new_generate = false;
+ FileId *new_generate_file_id = nullptr;
if (file_view.has_generate_location()) {
- new_generate = register_location(file_view.generate_location(), generate_location_to_file_id_);
+ new_generate_file_id = register_location(file_view.generate_location(), generate_location_to_file_id_);
}
- std::sort(to_merge.begin(), to_merge.end());
- to_merge.erase(std::unique(to_merge.begin(), to_merge.end()), to_merge.end());
+ td::unique(to_merge);
- int new_cnt = new_remote + new_local + new_generate;
+ int new_cnt = new_remote + (new_local_file_id != nullptr) + (new_generate_file_id != nullptr);
if (data.pmc_id_ == 0 && file_db_ && new_cnt > 0) {
node->need_load_from_pmc_ = true;
}
@@ -784,44 +1329,107 @@ Result<FileId> FileManager::register_file(FileData data, FileLocationSource file
// may invalidate node
merge(file_id, id, no_sync_merge).ignore();
}
+ Status status;
+ if (merge_file_id.is_valid()) {
+ status = merge(file_id, merge_file_id);
+ }
- try_flush_node(get_file_node(file_id));
+ try_flush_node(get_file_node(file_id), "register_file");
auto main_file_id = get_file_node(file_id)->main_file_id_;
- try_forget_file_id(file_id);
+ if (main_file_id != file_id) {
+ if (new_local_file_id != nullptr) {
+ *new_local_file_id = main_file_id;
+ }
+ if (new_generate_file_id != nullptr) {
+ *new_generate_file_id = main_file_id;
+ }
+ try_forget_file_id(file_id);
+ }
+ if ((new_local_file_id != nullptr) || (new_generate_file_id != nullptr)) {
+ get_file_id_info(main_file_id)->pin_flag_ = true;
+ }
+
+ if (!data.file_source_ids_.empty()) {
+ VLOG(file_references) << "Loaded " << data.file_source_ids_ << " for file " << main_file_id << " from " << source;
+ for (auto file_source_id : data.file_source_ids_) {
+ CHECK(file_source_id.is_valid());
+ context_->add_file_source(main_file_id, file_source_id);
+ }
+ }
+ if (status.is_error()) {
+ return std::move(status);
+ }
return FileId(main_file_id.get(), remote_key);
}
// 0 -- choose x
// 1 -- choose y
// 2 -- choose any
-static int merge_choose(const LocalFileLocation &x, const LocalFileLocation &y) {
- int32 x_type = static_cast<int32>(x.type());
- int32 y_type = static_cast<int32>(y.type());
+static int merge_choose_local_location(const LocalFileLocation &x, const LocalFileLocation &y) {
+ auto x_type = static_cast<int32>(x.type());
+ auto y_type = static_cast<int32>(y.type());
if (x_type != y_type) {
return x_type < y_type;
}
return 2;
}
-static int merge_choose(const RemoteFileLocation &x, int8 x_source, const RemoteFileLocation &y, int8 y_source) {
- int32 x_type = static_cast<int32>(x.type());
- int32 y_type = static_cast<int32>(y.type());
- if (x_type != y_type) {
- return x_type < y_type;
+static int merge_choose_file_source_location(FileLocationSource x, FileLocationSource y) {
+ return static_cast<int>(x) < static_cast<int>(y);
+}
+
+static int merge_choose_remote_location(const FullRemoteFileLocation &x, FileLocationSource x_source,
+ const FullRemoteFileLocation &y, FileLocationSource y_source) {
+ LOG(INFO) << "Choose between " << x << " from " << x_source << " and " << y << " from " << y_source;
+ if (x.is_web() != y.is_web()) {
+ return x.is_web(); // prefer non-web
}
- // If access_hash changed use a newer one
- if (x.type() == RemoteFileLocation::Type::Full) {
- if (x.full().get_access_hash() != y.full().get_access_hash()) {
- return x_source < y_source;
+ auto x_ref = x.has_file_reference();
+ auto y_ref = y.has_file_reference();
+ if (x_ref || y_ref) {
+ if (x_ref != y_ref) {
+ return !x_ref;
+ }
+ if (x.get_file_reference() != y.get_file_reference()) {
+ return merge_choose_file_source_location(x_source, y_source);
}
}
+ if ((x.get_access_hash() != y.get_access_hash() || x.get_source() != y.get_source()) &&
+ (x_source != y_source || x.is_web() || x.get_id() == y.get_id())) {
+ return merge_choose_file_source_location(x_source, y_source);
+ }
return 2;
}
-static int merge_choose(const unique_ptr<FullGenerateFileLocation> &x, const unique_ptr<FullGenerateFileLocation> &y) {
- int x_type = static_cast<int>(x != nullptr);
- int y_type = static_cast<int>(y != nullptr);
- if (x_type != y_type) {
- return x_type < y_type;
+
+static int merge_choose_remote_location(const NewRemoteFileLocation &x, const NewRemoteFileLocation &y) {
+ if (x.is_full_alive != y.is_full_alive) {
+ return !x.is_full_alive;
+ }
+ if (x.is_full_alive) {
+ return merge_choose_remote_location(x.full.value(), x.full_source, y.full.value(), y.full_source);
+ }
+ if (!x.partial != !y.partial) {
+ return !x.partial;
+ }
+ return 2;
+}
+
+static int merge_choose_generate_location(const unique_ptr<FullGenerateFileLocation> &x,
+ const unique_ptr<FullGenerateFileLocation> &y) {
+ int x_empty = (x == nullptr);
+ int y_empty = (y == nullptr);
+ if (x_empty != y_empty) {
+ return x_empty ? 1 : 0;
+ }
+ if (!x_empty && *x != *y) {
+ bool x_has_mtime = begins_with(x->conversion_, "#mtime#");
+ bool y_has_mtime = begins_with(y->conversion_, "#mtime#");
+ if (x_has_mtime != y_has_mtime) {
+ return x_has_mtime ? 0 : 1;
+ }
+ return x->conversion_ >= y->conversion_
+ ? 0
+ : 1; // the bigger conversion, the bigger mtime or at least more stable choise
}
return 2;
}
@@ -839,6 +1447,7 @@ static int merge_choose_size(int64 x, int64 y) {
}
return 2;
}
+
static int merge_choose_expected_size(int64 x, int64 y) {
if (x == 0) {
return 1;
@@ -874,32 +1483,34 @@ static int merge_choose_encryption_key(const FileEncryptionKey &a, const FileEnc
if (a.empty() != b.empty()) {
return a.empty() > b.empty();
}
- if (a.key_iv_ != b.key_iv_) {
+ if (a != b) {
return -1;
}
return 2;
}
-void FileManager::cancel_download(FileNodePtr node) {
+void FileManager::do_cancel_download(FileNodePtr node) {
if (node->download_id_ == 0) {
return;
}
send_closure(file_load_manager_, &FileLoadManager::cancel, node->download_id_);
node->download_id_ = 0;
node->is_download_started_ = false;
+ node->download_was_update_file_reference_ = false;
node->set_download_priority(0);
}
-void FileManager::cancel_upload(FileNodePtr node) {
+void FileManager::do_cancel_upload(FileNodePtr node) {
if (node->upload_id_ == 0) {
return;
}
send_closure(file_load_manager_, &FileLoadManager::cancel, node->upload_id_);
node->upload_id_ = 0;
+ node->upload_was_update_file_reference_ = false;
node->set_upload_priority(0);
}
-void FileManager::cancel_generate(FileNodePtr node) {
+void FileManager::do_cancel_generate(FileNodePtr node) {
if (node->generate_id_ == 0) {
return;
}
@@ -909,49 +1520,75 @@ void FileManager::cancel_generate(FileNodePtr node) {
node->set_generate_priority(0, 0);
}
-Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sync) {
- LOG(INFO) << x_file_id << " VS " << y_file_id;
-
+Status FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sync) {
if (!x_file_id.is_valid()) {
return Status::Error("First file_id is invalid");
}
FileNodePtr x_node = no_sync ? get_file_node(x_file_id) : get_sync_file_node(x_file_id);
if (!x_node) {
- return Status::Error(PSLICE() << "Can't merge files. First id is invalid: " << x_file_id << " and " << y_file_id);
+ return Status::Error(PSLICE() << "Can't merge files. First identifier is invalid: " << x_file_id << " and "
+ << y_file_id);
}
if (!y_file_id.is_valid()) {
- return x_node->main_file_id_;
+ LOG(DEBUG) << "Old file is invalid";
+ return Status::OK();
}
FileNodePtr y_node = get_file_node(y_file_id);
if (!y_node) {
- return Status::Error(PSLICE() << "Can't merge files. Second id is invalid: " << x_file_id << " and " << y_file_id);
+ return Status::Error(PSLICE() << "Can't merge files. Second identifier is invalid: " << x_file_id << " and "
+ << y_file_id);
}
if (x_file_id == x_node->upload_pause_) {
- x_node->upload_pause_ = FileId();
+ x_node->set_upload_pause(FileId());
}
if (x_node.get() == y_node.get()) {
- return x_node->main_file_id_;
+ if (x_file_id != y_file_id) {
+ LOG(DEBUG) << "New file " << x_file_id << " and old file " << y_file_id << " are already merged";
+ }
+ try_flush_node_info(x_node, "merge 1");
+ return Status::OK();
}
if (y_file_id == y_node->upload_pause_) {
- y_node->upload_pause_ = FileId();
+ y_node->set_upload_pause(FileId());
}
- if (x_node->remote_.type() == RemoteFileLocation::Type::Full &&
- y_node->remote_.type() == RemoteFileLocation::Type::Full &&
- x_node->remote_.full().get_dc_id() != y_node->remote_.full().get_dc_id()) {
- LOG(ERROR) << "File remote location was changed from " << y_node->remote_.full() << " to "
- << x_node->remote_.full();
+ LOG(INFO) << "Merge new file " << x_file_id << " and old file " << y_file_id;
+ if (x_node->remote_.full && y_node->remote_.full && !x_node->remote_.full.value().is_web() &&
+ !y_node->remote_.full.value().is_web() && y_node->remote_.is_full_alive &&
+ x_node->remote_.full_source == FileLocationSource::FromServer &&
+ y_node->remote_.full_source == FileLocationSource::FromServer &&
+ x_node->remote_.full.value().get_dc_id() != y_node->remote_.full.value().get_dc_id()) {
+ LOG(ERROR) << "File remote location was changed from " << y_node->remote_.full.value() << " to "
+ << x_node->remote_.full.value();
+ }
+
+ bool drop_last_successful_force_reupload_time = x_node->last_successful_force_reupload_time_ <= 0 &&
+ x_node->remote_.full &&
+ x_node->remote_.full_source == FileLocationSource::FromServer;
+
+ auto count_local = [](auto &node) {
+ return std::accumulate(node->file_ids_.begin(), node->file_ids_.end(), 0,
+ [](const auto &x, const auto &y) { return x + (y.get_remote() != 0); });
+ };
+ auto x_local_file_ids = count_local(x_node);
+ auto y_local_file_ids = count_local(y_node);
+ if (x_local_file_ids + y_local_file_ids > 100) {
+ }
+
+ if (y_node->file_ids_.size() >= 100 || x_node->file_ids_.size() >= 100) {
+ LOG(INFO) << "Merge files with " << x_local_file_ids << '/' << x_node->file_ids_.size() << " and "
+ << y_local_file_ids << '/' << y_node->file_ids_.size() << " file IDs";
}
FileNodePtr nodes[] = {x_node, y_node, x_node};
FileNodeId node_ids[] = {get_file_id_info(x_file_id)->node_id_, get_file_id_info(y_file_id)->node_id_};
+ int trusted_by_source = merge_choose_file_source_location(x_node->remote_.full_source, y_node->remote_.full_source);
- int local_i = merge_choose(x_node->local_, y_node->local_);
- int remote_i = merge_choose(x_node->remote_, static_cast<int8>(x_node->remote_source_), y_node->remote_,
- static_cast<int8>(y_node->remote_source_));
- int generate_i = merge_choose(x_node->generate_, y_node->generate_);
+ int local_i = merge_choose_local_location(x_node->local_, y_node->local_);
+ int remote_i = merge_choose_remote_location(x_node->remote_, y_node->remote_);
+ int generate_i = merge_choose_generate_location(x_node->generate_, y_node->generate_);
int size_i = merge_choose_size(x_node->size_, y_node->size_);
int expected_size_i = merge_choose_expected_size(x_node->expected_size_, y_node->expected_size_);
int remote_name_i = merge_choose_name(x_node->remote_name_, y_node->remote_name_);
@@ -962,45 +1599,66 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy
y_node->main_file_id_, y_node->main_file_id_priority_);
if (size_i == -1) {
+ try_flush_node_info(x_node, "merge 2");
+ try_flush_node_info(y_node, "merge 3");
return Status::Error(PSLICE() << "Can't merge files. Different size: " << x_node->size_ << " and "
<< y_node->size_);
}
if (encryption_key_i == -1) {
- if (nodes[remote_i]->remote_.type() == RemoteFileLocation::Type::Full &&
- nodes[local_i]->local_.type() != LocalFileLocation::Type::Partial) {
- //???
- LOG(ERROR) << "Different encryption key in files, but go Choose same key as remote location";
+ if (nodes[remote_i]->remote_.full && nodes[local_i]->local_.type() != LocalFileLocation::Type::Partial) {
+ LOG(ERROR) << "Different encryption key in files, but lets choose same key as remote location";
encryption_key_i = remote_i;
} else {
+ try_flush_node_info(x_node, "merge 4");
+ try_flush_node_info(y_node, "merge 5");
return Status::Error("Can't merge files. Different encryption keys");
}
}
- int node_i = std::make_tuple(y_node->pmc_id_, y_node->file_ids_.size(), main_file_id_i == 1) >
- std::make_tuple(x_node->pmc_id_, x_node->file_ids_.size(), main_file_id_i == 0);
+ // prefer more trusted source
+ if (remote_name_i == 2) {
+ remote_name_i = trusted_by_source;
+ }
+ if (url_i == 2) {
+ url_i = trusted_by_source;
+ }
+ if (expected_size_i == 2) {
+ expected_size_i = trusted_by_source;
+ }
+
+ int node_i =
+ std::make_tuple(y_node->pmc_id_.is_valid(), x_node->pmc_id_, y_node->file_ids_.size(), main_file_id_i == 1) >
+ std::make_tuple(x_node->pmc_id_.is_valid(), y_node->pmc_id_, x_node->file_ids_.size(), main_file_id_i == 0);
auto other_node_i = 1 - node_i;
FileNodePtr node = nodes[node_i];
FileNodePtr other_node = nodes[other_node_i];
auto file_view = FileView(node);
- LOG(INFO) << "x_node->pmc_id_ = " << x_node->pmc_id_ << ", y_node->pmc_id_ = " << y_node->pmc_id_
- << ", x_node_size = " << x_node->file_ids_.size() << ", y_node_size = " << y_node->file_ids_.size()
- << ", node_i = " << node_i << ", local_i = " << local_i << ", remote_i = " << remote_i
- << ", generate_i = " << generate_i << ", size_i = " << size_i << ", remote_name_i = " << remote_name_i
- << ", url_i = " << url_i << ", owner_i = " << owner_i << ", encryption_key_i = " << encryption_key_i
- << ", main_file_id_i = " << main_file_id_i;
+ LOG(DEBUG) << "Have x_node->pmc_id_ = " << x_node->pmc_id_.get() << ", y_node->pmc_id_ = " << y_node->pmc_id_.get()
+ << ", x_node_size = " << x_node->file_ids_.size() << ", y_node_size = " << y_node->file_ids_.size()
+ << ", node_i = " << node_i << ", local_i = " << local_i << ", remote_i = " << remote_i
+ << ", generate_i = " << generate_i << ", size_i = " << size_i << ", remote_name_i = " << remote_name_i
+ << ", url_i = " << url_i << ", owner_i = " << owner_i << ", encryption_key_i = " << encryption_key_i
+ << ", main_file_id_i = " << main_file_id_i << ", trusted_by_source = " << trusted_by_source
+ << ", x_source = " << x_node->remote_.full_source << ", y_source = " << y_node->remote_.full_source;
if (local_i == other_node_i) {
- cancel_download(node);
- node->set_local_location(other_node->local_, other_node->local_ready_size_);
+ do_cancel_download(node);
+ node->set_download_offset(other_node->download_offset_);
+ node->set_local_location(other_node->local_, other_node->local_ready_size_, other_node->download_offset_,
+ other_node->local_ready_prefix_size_);
node->download_id_ = other_node->download_id_;
+ node->download_was_update_file_reference_ = other_node->download_was_update_file_reference_;
node->is_download_started_ |= other_node->is_download_started_;
node->set_download_priority(other_node->download_priority_);
other_node->download_id_ = 0;
+ other_node->download_was_update_file_reference_ = false;
other_node->is_download_started_ = false;
other_node->download_priority_ = 0;
+ other_node->download_offset_ = 0;
+ other_node->local_ready_prefix_size_ = 0;
- //cancel_generate(node);
+ //do_cancel_generate(node);
//node->set_generate_location(std::move(other_node->generate_));
//node->generate_id_ = other_node->generate_id_;
//node->set_generate_priority(other_node->generate_download_priority_, other_node->generate_upload_priority_);
@@ -1010,25 +1668,27 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy
//other_node->generate_download_priority_ = 0;
//other_node->generate_upload_priority_ = 0;
} else {
- cancel_download(other_node);
- //cancel_generate(other_node);
+ do_cancel_download(other_node);
+ //do_cancel_generate(other_node);
}
if (remote_i == other_node_i) {
- cancel_upload(node);
- node->set_remote_location(other_node->remote_, other_node->remote_source_, other_node->remote_ready_size_);
+ do_cancel_upload(node);
+ node->set_new_remote_location(std::move(other_node->remote_));
node->upload_id_ = other_node->upload_id_;
+ node->upload_was_update_file_reference_ = other_node->upload_was_update_file_reference_;
node->set_upload_priority(other_node->upload_priority_);
- node->upload_pause_ = other_node->upload_pause_;
+ node->set_upload_pause(other_node->upload_pause_);
other_node->upload_id_ = 0;
+ other_node->upload_was_update_file_reference_ = false;
other_node->upload_priority_ = 0;
other_node->upload_pause_ = FileId();
} else {
- cancel_upload(other_node);
+ do_cancel_upload(other_node);
}
if (generate_i == other_node_i) {
- cancel_generate(node);
+ do_cancel_generate(node);
node->set_generate_location(std::move(other_node->generate_));
node->generate_id_ = other_node->generate_id_;
node->set_generate_priority(other_node->generate_download_priority_, other_node->generate_upload_priority_);
@@ -1037,7 +1697,7 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy
other_node->generate_download_priority_ = 0;
other_node->generate_upload_priority_ = 0;
} else {
- cancel_generate(other_node);
+ do_cancel_generate(other_node);
}
if (size_i == other_node_i) {
@@ -1066,10 +1726,20 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy
}
node->need_load_from_pmc_ |= other_node->need_load_from_pmc_;
node->can_search_locally_ &= other_node->can_search_locally_;
+ node->upload_prefer_small_ |= other_node->upload_prefer_small_;
+
+ if (drop_last_successful_force_reupload_time) {
+ node->last_successful_force_reupload_time_ = -1e10;
+ } else if (other_node->last_successful_force_reupload_time_ > node->last_successful_force_reupload_time_) {
+ node->last_successful_force_reupload_time_ = other_node->last_successful_force_reupload_time_;
+ }
if (main_file_id_i == other_node_i) {
+ context_->on_merge_files(other_node->main_file_id_, node->main_file_id_);
node->main_file_id_ = other_node->main_file_id_;
node->main_file_id_priority_ = other_node->main_file_id_priority_;
+ } else {
+ context_->on_merge_files(node->main_file_id_, other_node->main_file_id_);
}
bool send_updates_flag = false;
@@ -1078,8 +1748,7 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy
for (auto file_id : other_node->file_ids_) {
auto file_id_info = get_file_id_info(file_id);
- CHECK(file_id_info->node_id_ == node_ids[other_node_i])
- << node_ids[node_i] << " " << node_ids[other_node_i] << " " << file_id << " " << file_id_info->node_id_;
+ CHECK(file_id_info->node_id_ == node_ids[other_node_i]);
file_id_info->node_id_ = node_ids[node_i];
send_updates_flag |= file_id_info->send_updates_flag_;
}
@@ -1091,7 +1760,12 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy
node->on_info_changed();
}
- // Check is some download/upload queries are ready
+ if (node->file_ids_.size() > (static_cast<size_t>(1) << file_node_size_warning_exp_)) {
+ LOG(WARNING) << "File of type " << file_view.get_type() << " has " << node->file_ids_.size() << " file identifiers";
+ file_node_size_warning_exp_++;
+ }
+
+ // Check if some download/upload queries are ready
for (auto file_id : vector<FileId>(node->file_ids_)) {
auto *info = get_file_id_info(file_id);
if (info->download_priority_ != 0 && file_view.has_local_location()) {
@@ -1101,7 +1775,7 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy
info->download_callback_.reset();
}
}
- if (info->upload_priority_ != 0 && file_view.has_remote_location()) {
+ if (info->upload_priority_ != 0 && file_view.has_active_upload_remote_location()) {
info->upload_priority_ = 0;
if (info->upload_callback_) {
info->upload_callback_->on_upload_ok(file_id, nullptr);
@@ -1113,42 +1787,137 @@ Result<FileId> FileManager::merge(FileId x_file_id, FileId y_file_id, bool no_sy
file_nodes_[node_ids[other_node_i]] = nullptr;
run_generate(node);
- run_download(node);
+ run_download(node, false);
run_upload(node, {});
- if (other_pmc_id != 0) {
+ if (other_pmc_id.is_valid()) {
// node might not changed, but we need to merge nodes in pmc anyway
node->on_pmc_changed();
}
- try_flush_node(node, node_i != remote_i, node_i != local_i, node_i != generate_i, other_pmc_id);
+ try_flush_node_full(node, node_i != remote_i, node_i != local_i, node_i != generate_i, other_pmc_id);
+
+ return Status::OK();
+}
+
+void FileManager::add_file_source(FileId file_id, FileSourceId file_source_id) {
+ auto node = get_sync_file_node(file_id); // synchronously load the file to preload known file sources
+ if (!node) {
+ return;
+ }
+
+ CHECK(file_source_id.is_valid());
+ if (context_->add_file_source(node->main_file_id_, file_source_id)) {
+ node->on_pmc_changed();
+ try_flush_node_pmc(node, "add_file_source");
+ }
+}
+
+void FileManager::remove_file_source(FileId file_id, FileSourceId file_source_id) {
+ auto node = get_sync_file_node(file_id); // synchronously load the file to preload known file sources
+ if (!node) {
+ return;
+ }
+
+ CHECK(file_source_id.is_valid());
+ if (context_->remove_file_source(node->main_file_id_, file_source_id)) {
+ node->on_pmc_changed();
+ try_flush_node_pmc(node, "remove_file_source");
+ }
+}
+
+void FileManager::change_files_source(FileSourceId file_source_id, const vector<FileId> &old_file_ids,
+ const vector<FileId> &new_file_ids) {
+ if (old_file_ids == new_file_ids) {
+ return;
+ }
+ CHECK(file_source_id.is_valid());
- return node->main_file_id_;
+ auto old_main_file_ids = get_main_file_ids(old_file_ids);
+ auto new_main_file_ids = get_main_file_ids(new_file_ids);
+ for (auto file_id : old_main_file_ids) {
+ auto it = new_main_file_ids.find(file_id);
+ if (it == new_main_file_ids.end()) {
+ remove_file_source(file_id, file_source_id);
+ } else {
+ new_main_file_ids.erase(it);
+ }
+ }
+ for (auto file_id : new_main_file_ids) {
+ add_file_source(file_id, file_source_id);
+ }
+}
+
+void FileManager::on_file_reference_repaired(FileId file_id, FileSourceId file_source_id, Result<Unit> &&result,
+ Promise<Unit> &&promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ auto file_view = get_file_view(file_id);
+ CHECK(!file_view.empty());
+ if (result.is_ok() &&
+ (!file_view.has_active_upload_remote_location() || !file_view.has_active_download_remote_location())) {
+ result = Status::Error("No active remote location");
+ }
+ if (result.is_error() && result.error().code() != 429 && result.error().code() < 500) {
+ VLOG(file_references) << "Invalid " << file_source_id << " " << result.error();
+ remove_file_source(file_id, file_source_id);
+ }
+ promise.set_result(std::move(result));
}
-void FileManager::try_flush_node(FileNodePtr node, bool new_remote, bool new_local, bool new_generate,
- FileDbId other_pmc_id) {
+FlatHashSet<FileId, FileIdHash> FileManager::get_main_file_ids(const vector<FileId> &file_ids) {
+ FlatHashSet<FileId, FileIdHash> result;
+ for (auto file_id : file_ids) {
+ auto node = get_file_node(file_id);
+ if (node) {
+ result.insert(node->main_file_id_);
+ }
+ }
+ return result;
+}
+
+void FileManager::try_flush_node_full(FileNodePtr node, bool new_remote, bool new_local, bool new_generate,
+ FileDbId other_pmc_id) {
if (node->need_pmc_flush()) {
if (file_db_) {
load_from_pmc(node, true, true, true);
- flush_to_pmc(node, new_remote, new_local, new_generate);
- if (other_pmc_id != 0 && node->pmc_id_ != other_pmc_id) {
+ flush_to_pmc(node, new_remote, new_local, new_generate, "try_flush_node_full");
+ if (other_pmc_id.is_valid() && node->pmc_id_ != other_pmc_id) {
file_db_->set_file_data_ref(other_pmc_id, node->pmc_id_);
}
}
node->on_pmc_flushed();
}
- try_flush_node_info(node);
+ try_flush_node_info(node, "try_flush_node_full");
+}
+
+void FileManager::try_flush_node(FileNodePtr node, const char *source) {
+ try_flush_node_pmc(node, source);
+ try_flush_node_info(node, source);
}
-void FileManager::try_flush_node_info(FileNodePtr node) {
+void FileManager::try_flush_node_pmc(FileNodePtr node, const char *source) {
+ if (node->need_pmc_flush()) {
+ if (file_db_) {
+ load_from_pmc(node, true, true, true);
+ flush_to_pmc(node, false, false, false, source);
+ }
+ node->on_pmc_flushed();
+ }
+}
+
+void FileManager::try_flush_node_info(FileNodePtr node, const char *source) {
if (node->need_info_flush()) {
for (auto file_id : vector<FileId>(node->file_ids_)) {
auto *info = get_file_id_info(file_id);
if (info->send_updates_flag_) {
- VLOG(update_file) << "Send UpdateFile about file " << file_id;
+ VLOG(update_file) << "Send UpdateFile about file " << file_id << " from " << source;
context_->on_file_updated(file_id);
}
+ if (info->download_callback_) {
+ // For DownloadManager. For everybody else it is just an empty function call (I hope).
+ info->download_callback_->on_progress(file_id);
+ }
}
node->on_info_flushed();
}
@@ -1158,7 +1927,7 @@ void FileManager::clear_from_pmc(FileNodePtr node) {
if (!file_db_) {
return;
}
- if (node->pmc_id_ == 0) {
+ if (node->pmc_id_.empty()) {
return;
}
@@ -1167,44 +1936,53 @@ void FileManager::clear_from_pmc(FileNodePtr node) {
auto file_view = FileView(node);
if (file_view.has_local_location()) {
data.local_ = node->local_;
+ prepare_path_for_pmc(data.local_.full().file_type_, data.local_.full().path_);
}
if (file_view.has_remote_location()) {
- data.remote_ = node->remote_;
+ data.remote_ = RemoteFileLocation(*node->remote_.full);
}
if (file_view.has_generate_location()) {
- data.generate_ = std::make_unique<FullGenerateFileLocation>(*node->generate_);
+ data.generate_ = make_unique<FullGenerateFileLocation>(*node->generate_);
}
file_db_->clear_file_data(node->pmc_id_, data);
- node->pmc_id_ = 0;
+ node->pmc_id_ = FileDbId();
}
-void FileManager::flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate) {
+void FileManager::flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate,
+ const char *source) {
if (!file_db_) {
return;
}
FileView view(node);
bool create_flag = false;
- if (node->pmc_id_ == 0) {
+ if (node->pmc_id_.empty()) {
create_flag = true;
node->pmc_id_ = file_db_->create_pmc_id();
}
FileData data;
- data.pmc_id_ = node->pmc_id_;
+ data.pmc_id_ = node->pmc_id_.get();
data.local_ = node->local_;
if (data.local_.type() == LocalFileLocation::Type::Full) {
prepare_path_for_pmc(data.local_.full().file_type_, data.local_.full().path_);
}
- data.remote_ = node->remote_;
+ if (node->remote_.full) {
+ data.remote_ = RemoteFileLocation(node->remote_.full.value());
+ } else if (node->remote_.partial) {
+ data.remote_ = RemoteFileLocation(*node->remote_.partial);
+ }
if (node->generate_ != nullptr && !begins_with(node->generate_->conversion_, "#file_id#")) {
- data.generate_ = std::make_unique<FullGenerateFileLocation>(*node->generate_);
+ data.generate_ = make_unique<FullGenerateFileLocation>(*node->generate_);
}
- // TODO: not needed when GenerateLocation has constant convertion
+ // TODO: not needed when GenerateLocation has constant conversion
if (data.remote_.type() != RemoteFileLocation::Type::Full && data.local_.type() != LocalFileLocation::Type::Full) {
data.local_ = LocalFileLocation();
data.remote_ = RemoteFileLocation();
}
+ if (data.remote_.type() != RemoteFileLocation::Type::Full && node->encryption_key_.is_secure()) {
+ data.remote_ = RemoteFileLocation();
+ }
data.size_ = node->size_;
data.expected_size_ = node->expected_size_;
@@ -1212,6 +1990,9 @@ void FileManager::flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local
data.encryption_key_ = node->encryption_key_;
data.url_ = node->url_;
data.owner_dialog_id_ = node->owner_dialog_id_;
+ data.file_source_ids_ = context_->get_some_file_sources(view.get_main_file_id());
+ VLOG(file_references) << "Save file " << view.get_main_file_id() << " to database with " << data.file_source_ids_
+ << " from " << source;
file_db_->set_file_data(node->pmc_id_, data, (create_flag || new_remote), (create_flag || new_local),
(create_flag || new_generate));
@@ -1250,6 +2031,7 @@ void FileManager::load_from_pmc(FileNodePtr node, bool new_remote, bool new_loca
return;
}
auto file_view = get_file_view(file_id);
+ CHECK(!file_view.empty());
FullRemoteFileLocation remote;
FullLocalFileLocation local;
@@ -1260,7 +2042,7 @@ void FileManager::load_from_pmc(FileNodePtr node, bool new_remote, bool new_loca
}
new_local &= file_view.has_local_location();
if (new_local) {
- local = get_file_view(file_id).local_location();
+ local = file_view.local_location();
prepare_path_for_pmc(local.file_type_, local.path_);
}
new_generate &= file_view.has_generate_location();
@@ -1268,25 +2050,24 @@ void FileManager::load_from_pmc(FileNodePtr node, bool new_remote, bool new_loca
generate = file_view.generate_location();
}
- LOG(INFO) << "Load from pmc " << file_id << "/" << file_view.file_id() << ", new_remote = " << new_remote
- << ", new_local = " << new_local << ", new_generate = " << new_generate;
- auto load = [&](auto location) {
+ LOG(DEBUG) << "Load from pmc file " << file_id << '/' << file_view.get_main_file_id()
+ << ", new_remote = " << new_remote << ", new_local = " << new_local << ", new_generate = " << new_generate;
+ auto load = [&](auto location, const char *source) {
TRY_RESULT(file_data, file_db_->get_file_data_sync(location));
- TRY_RESULT(new_file_id, register_file(std::move(file_data), FileLocationSource::FromDb, "load_from_pmc", false));
- TRY_RESULT(main_file_id, merge(file_id, new_file_id));
- file_id = main_file_id;
+ TRY_RESULT(new_file_id,
+ register_file(std::move(file_data), FileLocationSource::FromDatabase, FileId(), source, false));
+ TRY_STATUS(merge(file_id, new_file_id)); // merge manually to keep merge parameters order
return Status::OK();
};
if (new_remote) {
- load(remote);
+ load(remote, "load remote from database").ignore();
}
if (new_local) {
- load(local);
+ load(local, "load local from database").ignore();
}
if (new_generate) {
- load(generate);
+ load(generate, "load generate from database").ignore();
}
- return;
}
bool FileManager::set_encryption_key(FileId file_id, FileEncryptionKey key) {
@@ -1302,11 +2083,15 @@ bool FileManager::set_encryption_key(FileId file_id, FileEncryptionKey key) {
return false;
}
node->set_encryption_key(std::move(key));
- try_flush_node(node);
+ try_flush_node_pmc(node, "set_encryption_key");
return true;
}
bool FileManager::set_content(FileId file_id, BufferSlice bytes) {
+ if (G()->get_option_boolean("ignore_inline_thumbnails")) {
+ return false;
+ }
+
auto node = get_sync_file_node(file_id);
if (!node) {
return false;
@@ -1321,18 +2106,18 @@ bool FileManager::set_content(FileId file_id, BufferSlice bytes) {
return true;
}
- cancel_download(node);
+ do_cancel_download(node);
auto *file_info = get_file_id_info(file_id);
file_info->download_priority_ = FROM_BYTES_PRIORITY;
node->set_download_priority(FROM_BYTES_PRIORITY);
- QueryId id = queries_container_.create(Query{file_id, Query::SetContent});
+ QueryId id = queries_container_.create(Query{file_id, Query::Type::SetContent});
node->download_id_ = id;
node->is_download_started_ = true;
- send_closure(file_load_manager_, &FileLoadManager::from_bytes, id, node->remote_.full().file_type_, std::move(bytes),
- node->suggested_name());
+ send_closure(file_load_manager_, &FileLoadManager::from_bytes, id, node->remote_.full.value().file_type_,
+ std::move(bytes), node->suggested_path());
return true;
}
@@ -1341,15 +2126,88 @@ void FileManager::get_content(FileId file_id, Promise<BufferSlice> promise) {
if (!node) {
return promise.set_error(Status::Error("Unknown file_id"));
}
- auto status = check_local_location(node);
- status.ignore();
+ check_local_location(node, true).ignore();
auto file_view = FileView(node);
if (!file_view.has_local_location()) {
return promise.set_error(Status::Error("No local location"));
}
- send_closure(file_load_manager_, &FileLoadManager::get_content, node->local_.full(), std::move(promise));
+ send_closure(file_load_manager_, &FileLoadManager::get_content, node->local_.full().path_, std::move(promise));
+}
+
+void FileManager::read_file_part(FileId file_id, int64 offset, int64 count, int left_tries,
+ Promise<td_api::object_ptr<td_api::filePart>> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ if (!file_id.is_valid()) {
+ return promise.set_error(Status::Error(400, "File identifier is invalid"));
+ }
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ return promise.set_error(Status::Error(400, "File not found"));
+ }
+ if (offset < 0) {
+ return promise.set_error(Status::Error(400, "Parameter offset must be non-negative"));
+ }
+ if (count < 0) {
+ return promise.set_error(Status::Error(400, "Parameter count must be non-negative"));
+ }
+
+ auto file_view = FileView(node);
+
+ if (count == 0) {
+ count = file_view.downloaded_prefix(offset);
+ if (count == 0) {
+ return promise.set_value(td_api::make_object<td_api::filePart>());
+ }
+ } else if (file_view.downloaded_prefix(offset) < count) {
+ // TODO this check is safer to do in another thread
+ return promise.set_error(Status::Error(400, "There is not enough downloaded bytes in the file to read"));
+ }
+ if (count >= static_cast<int64>(std::numeric_limits<size_t>::max() / 2 - 1)) {
+ return promise.set_error(Status::Error(400, "Part length is too big"));
+ }
+
+ const string *path = nullptr;
+ bool is_partial = false;
+ if (file_view.has_local_location()) {
+ path = &file_view.local_location().path_;
+ if (!begins_with(*path, get_files_dir(file_view.get_type()))) {
+ return promise.set_error(Status::Error(400, "File is not inside the cache"));
+ }
+ } else {
+ CHECK(node->local_.type() == LocalFileLocation::Type::Partial);
+ path = &node->local_.partial().path_;
+ is_partial = true;
+ }
+
+ auto read_file_part_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), file_id, offset, count, left_tries, is_partial,
+ promise = std::move(promise)](Result<string> r_bytes) mutable {
+ if (r_bytes.is_error()) {
+ LOG(INFO) << "Failed to read file bytes: " << r_bytes.error();
+ if (left_tries == 1 || !is_partial) {
+ return promise.set_error(Status::Error(400, "Failed to read the file"));
+ }
+
+ // the temporary file could be moved from temp to persistent directory
+ // we need to wait for the corresponding update and repeat the reading
+ create_actor<SleepActor>("RepeatReadFilePartActor", 0.01,
+ PromiseCreator::lambda([actor_id, file_id, offset, count, left_tries,
+ promise = std::move(promise)](Result<Unit> result) mutable {
+ send_closure(actor_id, &FileManager::read_file_part, file_id, offset, count,
+ left_tries - 1, std::move(promise));
+ }))
+ .release();
+ } else {
+ auto result = td_api::make_object<td_api::filePart>();
+ result->data_ = r_bytes.move_as_ok();
+ promise.set_value(std::move(result));
+ }
+ });
+ send_closure(file_load_manager_, &FileLoadManager::read_file_part, *path, offset, count,
+ std::move(read_file_part_promise));
}
void FileManager::delete_file(FileId file_id, Promise<Unit> promise, const char *source) {
@@ -1361,169 +2219,496 @@ void FileManager::delete_file(FileId file_id, Promise<Unit> promise, const char
auto file_view = FileView(node);
- // TODO: review delete condition
+ send_closure(G()->download_manager(), &DownloadManager::remove_file_if_finished, file_view.get_main_file_id());
+ string path;
if (file_view.has_local_location()) {
if (begins_with(file_view.local_location().path_, get_files_dir(file_view.get_type()))) {
- LOG(INFO) << "Unlink file " << file_id << " at " << file_view.local_location().path_;
clear_from_pmc(node);
-
- unlink(file_view.local_location().path_).ignore();
- context_->on_new_file(-file_view.size());
- node->set_local_location(LocalFileLocation(), 0);
- try_flush_node(node);
+ if (context_->need_notify_on_new_files()) {
+ context_->on_new_file(-file_view.size(), -file_view.get_allocated_local_size(), -1);
+ }
+ path = std::move(node->local_.full().path_);
}
} else {
if (file_view.get_type() == FileType::Encrypted) {
clear_from_pmc(node);
}
if (node->local_.type() == LocalFileLocation::Type::Partial) {
- LOG(INFO) << "Unlink partial file " << file_id << " at " << node->local_.partial().path_;
- unlink(node->local_.partial().path_).ignore();
- node->set_local_location(LocalFileLocation(), 0);
- try_flush_node(node);
+ path = std::move(node->local_.partial().path_);
}
}
- promise.set_value(Unit());
+ if (path.empty()) {
+ return promise.set_value(Unit());
+ }
+
+ LOG(INFO) << "Unlink file " << file_id << " at " << path;
+ node->drop_local_location();
+ try_flush_node(node, "delete_file");
+ send_closure(file_load_manager_, &FileLoadManager::unlink_file, path, std::move(promise));
}
-void FileManager::download(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority) {
+void FileManager::download(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority, int64 offset,
+ int64 limit, Promise<td_api::object_ptr<td_api::file>> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
auto node = get_sync_file_node(file_id);
if (!node) {
+ LOG(INFO) << "File " << file_id << " not found";
+ auto error = Status::Error(400, "File not found");
if (callback) {
- callback->on_download_error(file_id, Status::Error("File not found"));
+ callback->on_download_error(file_id, error.clone());
}
- return;
+ return promise.set_error(std::move(error));
+ }
+
+ if ((callback == nullptr && new_priority <= 0) || node->local_.type() == LocalFileLocation::Type::Empty) {
+ // skip local location check if download is canceled or there is no local location
+ return download_impl(file_id, std::move(callback), new_priority, offset, limit, Status::OK(), std::move(promise));
}
+ LOG(INFO) << "Asynchronously check location of file " << file_id << " before downloading";
+ auto check_promise =
+ PromiseCreator::lambda([actor_id = actor_id(this), file_id, callback = std::move(callback), new_priority, offset,
+ limit, promise = std::move(promise)](Result<Unit> result) mutable {
+ Status check_status;
+ if (result.is_error()) {
+ check_status = result.move_as_error();
+ }
+ send_closure(actor_id, &FileManager::download_impl, file_id, std::move(callback), new_priority, offset, limit,
+ std::move(check_status), std::move(promise));
+ });
+ check_local_location_async(node, true, std::move(check_promise));
+}
+
+void FileManager::download_impl(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority,
+ int64 offset, int64 limit, Status check_status,
+ Promise<td_api::object_ptr<td_api::file>> promise) {
+ TRY_STATUS_PROMISE(promise, G()->close_status());
+
+ LOG(INFO) << "Download file " << file_id << " with priority " << new_priority;
+ auto node = get_file_node(file_id);
+ CHECK(node);
+
+ if (check_status.is_error()) {
+ LOG(WARNING) << "Need to redownload file " << file_id << ": " << check_status;
+ }
if (node->local_.type() == LocalFileLocation::Type::Full) {
- auto status = check_local_location(node);
- if (status.is_error()) {
- LOG(WARNING) << "Need to redownload file " << file_id << ": " << status.error();
- } else {
- if (callback) {
- callback->on_download_ok(file_id);
- }
- return;
- }
- } else if (node->local_.type() == LocalFileLocation::Type::Partial) {
- auto status = check_local_location(node);
- if (status.is_error()) {
- LOG(WARNING) << "Need to download file " << file_id << " from beginning: " << status.error();
+ LOG(INFO) << "File " << file_id << " is already downloaded";
+ if (callback) {
+ callback->on_download_ok(file_id);
}
+ return promise.set_value(get_file_object(file_id, false));
}
FileView file_view(node);
if (!file_view.can_download_from_server() && !file_view.can_generate()) {
+ LOG(INFO) << "File " << file_id << " can't be downloaded";
+ auto error = Status::Error(400, "Can't download or generate the file");
if (callback) {
- callback->on_download_error(file_id, Status::Error("Can't download or generate file"));
+ callback->on_download_error(file_id, error.clone());
}
- return;
+ return promise.set_error(std::move(error));
}
if (new_priority == -1) {
if (node->is_download_started_) {
- return;
+ LOG(INFO) << "File " << file_id << " is being downloaded";
+ return promise.set_value(get_file_object(file_id, false));
}
new_priority = 0;
}
+ LOG(INFO) << "Change download priority of file " << file_id << " to " << new_priority << " with callback "
+ << callback.get();
+ node->set_download_offset(offset);
+ node->set_download_limit(limit);
auto *file_info = get_file_id_info(file_id);
CHECK(new_priority == 0 || callback);
+ if (file_info->download_callback_ != nullptr && file_info->download_callback_.get() != callback.get()) {
+ // the old callback will be destroyed soon and lost forever
+ // this is a bug and must never happen, unless we cancel previous download query
+ // but still there is no way to prevent this with the current FileManager implementation
+ if (new_priority == 0) {
+ file_info->download_callback_->on_download_error(file_id, Status::Error(200, "Canceled"));
+ } else {
+ LOG(ERROR) << "File " << file_id << " is used with different download callbacks";
+ file_info->download_callback_->on_download_error(file_id, Status::Error(500, "Internal Server Error"));
+ }
+ }
+ file_info->ignore_download_limit = limit == IGNORE_DOWNLOAD_LIMIT;
file_info->download_priority_ = narrow_cast<int8>(new_priority);
file_info->download_callback_ = std::move(callback);
+
+ if (file_info->download_callback_) {
+ file_info->download_callback_->on_progress(file_id);
+ }
// TODO: send current progress?
run_generate(node);
- run_download(node);
+ run_download(node, true);
- try_flush_node(node);
+ try_flush_node(node, "download");
+ promise.set_value(get_file_object(file_id, false));
}
-void FileManager::run_download(FileNodePtr node) {
- if (node->need_load_from_pmc_) {
- return;
- }
- if (node->generate_id_) {
- return;
- }
- auto file_view = FileView(node);
- if (!file_view.can_download_from_server()) {
- return;
- }
+void FileManager::run_download(FileNodePtr node, bool force_update_priority) {
int8 priority = 0;
+ bool ignore_download_limit = false;
for (auto id : node->file_ids_) {
auto *info = get_file_id_info(id);
if (info->download_priority_ > priority) {
priority = info->download_priority_;
}
+ ignore_download_limit |= info->ignore_download_limit;
}
auto old_priority = node->download_priority_;
- node->set_download_priority(priority);
if (priority == 0) {
+ node->set_download_priority(priority);
if (old_priority != 0) {
- cancel_download(node);
+ LOG(INFO) << "Cancel downloading of file " << node->main_file_id_;
+ do_cancel_download(node);
}
return;
}
+ if (node->need_load_from_pmc_) {
+ LOG(INFO) << "Skip run_download, because file " << node->main_file_id_ << " needs to be loaded from PMC";
+ return;
+ }
+ if (node->generate_id_) {
+ LOG(INFO) << "Skip run_download, because file " << node->main_file_id_ << " is being generated";
+ return;
+ }
+ auto file_view = FileView(node);
+ if (!file_view.can_download_from_server()) {
+ LOG(INFO) << "Skip run_download, because file " << node->main_file_id_ << " can't be downloaded from server";
+ return;
+ }
+ node->set_download_priority(priority);
+ node->set_ignore_download_limit(ignore_download_limit);
+ bool need_update_offset = node->is_download_offset_dirty_;
+ node->is_download_offset_dirty_ = false;
+
+ bool need_update_limit = node->is_download_limit_dirty_;
+ node->is_download_limit_dirty_ = false;
+
if (old_priority != 0) {
+ LOG(INFO) << "Update download offset and limits of file " << node->main_file_id_;
CHECK(node->download_id_ != 0);
- send_closure(file_load_manager_, &FileLoadManager::update_priority, node->download_id_, priority);
+ if (force_update_priority || priority != old_priority) {
+ send_closure(file_load_manager_, &FileLoadManager::update_priority, node->download_id_, priority);
+ }
+ if (need_update_limit || need_update_offset) {
+ auto download_offset = node->download_offset_;
+ auto download_limit = node->get_download_limit();
+ if (file_view.is_encrypted_any()) {
+ CHECK(download_offset <= MAX_FILE_SIZE);
+ CHECK(download_limit <= MAX_FILE_SIZE);
+ download_limit += download_offset;
+ download_offset = 0;
+ }
+ send_closure(file_load_manager_, &FileLoadManager::update_downloaded_part, node->download_id_, download_offset,
+ download_limit);
+ }
return;
}
CHECK(node->download_id_ == 0);
CHECK(!node->file_ids_.empty());
- auto file_id = node->file_ids_.back();
- QueryId id = queries_container_.create(Query{file_id, Query::Download});
+ auto file_id = node->main_file_id_;
+
+ if (node->need_reload_photo_ && file_view.may_reload_photo()) {
+ LOG(INFO) << "Reload photo from file " << node->main_file_id_;
+ QueryId id = queries_container_.create(Query{file_id, Query::Type::DownloadReloadDialog});
+ node->download_id_ = id;
+ context_->reload_photo(file_view.remote_location().get_source(),
+ PromiseCreator::lambda([id, actor_id = actor_id(this), file_id](Result<Unit> res) {
+ Status error;
+ if (res.is_ok()) {
+ error = Status::Error("FILE_DOWNLOAD_ID_INVALID");
+ } else {
+ error = res.move_as_error();
+ }
+ VLOG(file_references)
+ << "Got result from reload photo for file " << file_id << ": " << error;
+ send_closure(actor_id, &FileManager::on_error, id, std::move(error));
+ }));
+ node->need_reload_photo_ = false;
+ return;
+ }
+
+ // If file reference is needed
+ if (!file_view.has_active_download_remote_location()) {
+ VLOG(file_references) << "Do not have valid file_reference for file " << file_id;
+ QueryId id = queries_container_.create(Query{file_id, Query::Type::DownloadWaitFileReference});
+ node->download_id_ = id;
+ if (node->download_was_update_file_reference_) {
+ on_error(id, Status::Error("Can't download file: have no valid file reference"));
+ return;
+ }
+ node->download_was_update_file_reference_ = true;
+
+ context_->repair_file_reference(
+ file_id, PromiseCreator::lambda([id, actor_id = actor_id(this), file_id](Result<Unit> res) {
+ Status error;
+ if (res.is_ok()) {
+ error = Status::Error("FILE_DOWNLOAD_RESTART_WITH_FILE_REFERENCE");
+ } else {
+ error = res.move_as_error();
+ }
+ VLOG(file_references) << "Got result from FileSourceManager for file " << file_id << ": " << error;
+ send_closure(actor_id, &FileManager::on_error, id, std::move(error));
+ }));
+ return;
+ }
+
+ QueryId id = queries_container_.create(Query{file_id, Query::Type::Download});
node->download_id_ = id;
node->is_download_started_ = false;
- send_closure(file_load_manager_, &FileLoadManager::download, id, node->remote_.full(), node->local_, node->size_,
- node->suggested_name(), node->encryption_key_, node->can_search_locally_, priority);
-}
+ LOG(INFO) << "Run download of file " << file_id << " of size " << node->size_ << " from "
+ << node->remote_.full.value() << " with suggested name " << node->suggested_path() << " and encyption key "
+ << node->encryption_key_;
+ auto download_offset = node->download_offset_;
+ auto download_limit = node->get_download_limit();
+ if (file_view.is_encrypted_any()) {
+ CHECK(download_offset <= MAX_FILE_SIZE);
+ CHECK(download_limit <= MAX_FILE_SIZE);
+ download_limit += download_offset;
+ download_offset = 0;
+ }
+ send_closure(file_load_manager_, &FileLoadManager::download, id, node->remote_.full.value(), node->local_,
+ node->size_, node->suggested_path(), node->encryption_key_, node->can_search_locally_, download_offset,
+ download_limit, priority);
+}
+
+class FileManager::ForceUploadActor final : public Actor {
+ public:
+ ForceUploadActor(FileManager *file_manager, FileId file_id, std::shared_ptr<FileManager::UploadCallback> callback,
+ int32 new_priority, uint64 upload_order, bool prefer_small, ActorShared<> parent)
+ : file_manager_(file_manager)
+ , file_id_(file_id)
+ , callback_(std::move(callback))
+ , new_priority_(new_priority)
+ , upload_order_(upload_order)
+ , prefer_small_(prefer_small)
+ , parent_(std::move(parent)) {
+ }
+
+ private:
+ FileManager *file_manager_;
+ FileId file_id_;
+ std::shared_ptr<FileManager::UploadCallback> callback_;
+ int32 new_priority_;
+ uint64 upload_order_;
+ bool prefer_small_;
+ ActorShared<> parent_;
+ bool is_active_{false};
+ int attempt_{0};
+
+ class UploadCallback final : public FileManager::UploadCallback {
+ public:
+ explicit UploadCallback(ActorId<ForceUploadActor> callback) : callback_(std::move(callback)) {
+ }
+ void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) final {
+ send_closure(std::move(callback_), &ForceUploadActor::on_upload_ok, std::move(input_file));
+ }
-void FileManager::resume_upload(FileId file_id, std::vector<int> bad_parts, std::shared_ptr<UploadCallback> callback,
- int32 new_priority, uint64 upload_order) {
- LOG(INFO) << "Resume upload of file " << file_id << " with priority " << new_priority;
+ void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) final {
+ send_closure(std::move(callback_), &ForceUploadActor::on_upload_encrypted_ok, std::move(input_file));
+ }
+
+ void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) final {
+ send_closure(std::move(callback_), &ForceUploadActor::on_upload_secure_ok, std::move(input_file));
+ }
+ void on_upload_error(FileId file_id, Status error) final {
+ send_closure(std::move(callback_), &ForceUploadActor::on_upload_error, std::move(error));
+ }
+ ~UploadCallback() final {
+ if (callback_.empty()) {
+ return;
+ }
+ send_closure(std::move(callback_), &ForceUploadActor::on_upload_error, Status::Error(200, "Canceled"));
+ }
+
+ private:
+ ActorId<ForceUploadActor> callback_;
+ };
+
+ void on_upload_ok(tl_object_ptr<telegram_api::InputFile> input_file) {
+ is_active_ = false;
+ if (input_file || is_ready()) {
+ callback_->on_upload_ok(file_id_, std::move(input_file));
+ on_ok();
+ } else {
+ loop();
+ }
+ }
+
+ void on_upload_encrypted_ok(tl_object_ptr<telegram_api::InputEncryptedFile> input_file) {
+ is_active_ = false;
+ if (input_file || is_ready()) {
+ callback_->on_upload_encrypted_ok(file_id_, std::move(input_file));
+ on_ok();
+ } else {
+ loop();
+ }
+ }
+
+ void on_upload_secure_ok(tl_object_ptr<telegram_api::InputSecureFile> input_file) {
+ is_active_ = false;
+ if (input_file || is_ready()) {
+ callback_->on_upload_secure_ok(file_id_, std::move(input_file));
+ on_ok();
+ } else {
+ loop();
+ }
+ }
+
+ bool is_ready() const {
+ return !G()->close_flag() && file_manager_->get_file_view(file_id_).has_active_upload_remote_location();
+ }
+
+ void on_ok() {
+ callback_.reset();
+ send_closure(G()->file_manager(), &FileManager::on_force_reupload_success, file_id_);
+ stop();
+ }
+
+ void on_upload_error(Status error) {
+ if (attempt_ == 2) {
+ callback_->on_upload_error(file_id_, std::move(error));
+ callback_.reset();
+ stop();
+ } else {
+ is_active_ = false;
+ loop();
+ }
+ }
+
+ auto create_callback() {
+ return std::make_shared<UploadCallback>(actor_id(this));
+ }
+
+ void loop() final {
+ if (is_active_) {
+ return;
+ }
+ if (G()->close_flag()) {
+ return stop();
+ }
+
+ is_active_ = true;
+ attempt_++;
+ send_closure(G()->file_manager(), &FileManager::resume_upload, file_id_, vector<int>(), create_callback(),
+ new_priority_, upload_order_, attempt_ == 2, prefer_small_);
+ }
+
+ void tear_down() final {
+ if (callback_) {
+ callback_->on_upload_error(file_id_, Status::Error(200, "Canceled"));
+ }
+ }
+};
+
+void FileManager::on_force_reupload_success(FileId file_id) {
+ auto node = get_sync_file_node(file_id);
+ CHECK(node);
+ if (!node->remote_.is_full_alive) { // do not update for multiple simultaneous uploads
+ node->last_successful_force_reupload_time_ = Time::now();
+ }
+}
+
+void FileManager::resume_upload(FileId file_id, vector<int> bad_parts, std::shared_ptr<UploadCallback> callback,
+ int32 new_priority, uint64 upload_order, bool force, bool prefer_small) {
auto node = get_sync_file_node(file_id);
if (!node) {
+ LOG(INFO) << "File " << file_id << " not found";
if (callback) {
- callback->on_upload_error(file_id, Status::Error("Wrong file id to upload"));
+ callback->on_upload_error(file_id, Status::Error(400, "File not found"));
}
return;
}
+
+ if (bad_parts.size() == 1 && bad_parts[0] == -1) {
+ if (node->last_successful_force_reupload_time_ >= Time::now() - 60) {
+ LOG(INFO) << "Recently reuploaded file " << file_id << ", do not try again";
+ if (callback) {
+ callback->on_upload_error(file_id, Status::Error(400, "Failed to reupload file"));
+ }
+ return;
+ }
+
+ create_actor<ForceUploadActor>("ForceUploadActor", this, file_id, std::move(callback), new_priority, upload_order,
+ prefer_small, context_->create_reference())
+ .release();
+ return;
+ }
+ LOG(INFO) << "Resume upload of file " << file_id << " with priority " << new_priority << " and force = " << force;
+
+ if (force) {
+ node->remote_.is_full_alive = false;
+ }
+ if (prefer_small) {
+ node->upload_prefer_small_ = true;
+ }
if (node->upload_pause_ == file_id) {
- node->upload_pause_ = FileId();
+ node->set_upload_pause(FileId());
}
+ SCOPE_EXIT {
+ try_flush_node(node, "resume_upload");
+ };
FileView file_view(node);
- if (file_view.has_remote_location() && file_view.get_type() != FileType::Thumbnail &&
- file_view.get_type() != FileType::EncryptedThumbnail) {
+ if (file_view.has_active_upload_remote_location() && can_reuse_remote_file(file_view.get_type())) {
+ LOG(INFO) << "File " << file_id << " is already uploaded";
if (callback) {
callback->on_upload_ok(file_id, nullptr);
}
return;
}
- if (file_view.has_local_location()) {
- auto status = check_local_location(node);
+ if (file_view.has_local_location() && new_priority != 0) {
+ auto status = check_local_location(node, false);
if (status.is_error()) {
LOG(INFO) << "Full local location of file " << file_id << " for upload is invalid: " << status;
}
}
- if (!file_view.has_local_location() && !file_view.has_generate_location()) {
+ if (!file_view.has_local_location() && !file_view.has_generate_location() && !file_view.has_alive_remote_location()) {
+ LOG(INFO) << "File " << file_id << " can't be uploaded";
if (callback) {
- callback->on_upload_error(file_id, Status::Error("Need full local (or generate) location for upload"));
+ callback->on_upload_error(
+ file_id, Status::Error(400, "Need full local (or generate, or inactive remote) location for upload"));
+ }
+ return;
+ }
+ if (file_view.get_type() == FileType::Thumbnail &&
+ (!file_view.has_local_location() && file_view.can_download_from_server())) {
+ // TODO
+ if (callback) {
+ callback->on_upload_error(file_id, Status::Error(400, "Failed to upload thumbnail without local location"));
}
return;
}
+ LOG(INFO) << "Change upload priority of file " << file_id << " to " << new_priority << " with callback "
+ << callback.get();
auto *file_info = get_file_id_info(file_id);
CHECK(new_priority == 0 || callback);
+ if (file_info->upload_callback_ != nullptr && file_info->upload_callback_.get() != callback.get()) {
+ // the old callback will be destroyed soon and lost forever
+ // this is a bug and must never happen, unless we cancel previous upload query
+ // but still there is no way to prevent this with the current FileManager implementation
+ if (new_priority == 0) {
+ file_info->upload_callback_->on_upload_error(file_id, Status::Error(200, "Canceled"));
+ } else {
+ LOG(ERROR) << "File " << file_id << " is used with different upload callbacks";
+ file_info->upload_callback_->on_upload_error(file_id, Status::Error(500, "Internal Server Error"));
+ }
+ }
file_info->upload_order_ = upload_order;
file_info->upload_priority_ = narrow_cast<int8>(new_priority);
file_info->upload_callback_ = std::move(callback);
@@ -1531,24 +2716,26 @@ void FileManager::resume_upload(FileId file_id, std::vector<int> bad_parts, std:
run_generate(node);
run_upload(node, std::move(bad_parts));
- try_flush_node(node);
}
bool FileManager::delete_partial_remote_location(FileId file_id) {
auto node = get_sync_file_node(file_id);
if (!node) {
- LOG(INFO) << "Wrong file id " << file_id;
+ LOG(INFO) << "Wrong file identifier " << file_id;
return false;
}
if (node->upload_pause_ == file_id) {
- node->upload_pause_ = FileId();
+ node->set_upload_pause(FileId());
}
- if (node->remote_.type() == RemoteFileLocation::Type::Full) {
+ SCOPE_EXIT {
+ try_flush_node(node, "delete_partial_remote_location");
+ };
+ if (node->remote_.is_full_alive) {
LOG(INFO) << "File " << file_id << " is already uploaded";
return true;
}
- node->set_remote_location(RemoteFileLocation(), FileLocationSource::None, 0);
+ node->delete_partial_remote_location();
auto *file_info = get_file_id_info(file_id);
file_info->upload_priority_ = 0;
@@ -1557,22 +2744,49 @@ bool FileManager::delete_partial_remote_location(FileId file_id) {
return false;
}
- auto status = check_local_location(node);
+ auto status = check_local_location(node, false);
if (status.is_error()) {
LOG(INFO) << "Need full local location to upload file " << file_id << ": " << status;
return false;
}
- run_upload(node, std::vector<int>());
- try_flush_node(node);
+ run_upload(node, vector<int>());
return true;
}
-void FileManager::external_file_generate_progress(int64 id, int32 expected_size, int32 local_prefix_size,
+void FileManager::delete_file_reference(FileId file_id, Slice file_reference) {
+ VLOG(file_references) << "Delete file reference of file " << file_id << " "
+ << tag("reference_base64", base64_encode(file_reference));
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ LOG(ERROR) << "Wrong file identifier " << file_id;
+ return;
+ }
+ node->delete_file_reference(file_reference);
+ auto remote = get_remote(file_id.get_remote());
+ if (remote != nullptr) {
+ VLOG(file_references) << "Do delete file reference of remote file " << file_id;
+ if (remote->delete_file_reference(file_reference)) {
+ VLOG(file_references) << "Successfully deleted file reference of remote file " << file_id;
+ node->upload_was_update_file_reference_ = false;
+ node->download_was_update_file_reference_ = false;
+ node->on_pmc_changed();
+ }
+ }
+ try_flush_node_pmc(node, "delete_file_reference");
+}
+
+void FileManager::external_file_generate_write_part(int64 id, int64 offset, string data, Promise<> promise) {
+ send_closure(file_generate_manager_, &FileGenerateManager::external_file_generate_write_part, id, offset,
+ std::move(data), std::move(promise));
+}
+
+void FileManager::external_file_generate_progress(int64 id, int64 expected_size, int64 local_prefix_size,
Promise<> promise) {
send_closure(file_generate_manager_, &FileGenerateManager::external_file_generate_progress, id, expected_size,
local_prefix_size, std::move(promise));
}
+
void FileManager::external_file_generate_finish(int64 id, Status status, Promise<> promise) {
send_closure(file_generate_manager_, &FileGenerateManager::external_file_generate_finish, id, std::move(status),
std::move(promise));
@@ -1580,10 +2794,20 @@ void FileManager::external_file_generate_finish(int64 id, Status status, Promise
void FileManager::run_generate(FileNodePtr node) {
if (node->need_load_from_pmc_) {
+ LOG(INFO) << "Skip run_generate, because file " << node->main_file_id_ << " needs to be loaded from PMC";
return;
}
FileView file_view(node);
- if (file_view.has_local_location() || file_view.can_download_from_server() || !file_view.can_generate()) {
+ if (!file_view.can_generate()) {
+ // LOG(INFO) << "Skip run_generate, because file " << node->main_file_id_ << " can't be generated";
+ return;
+ }
+ if (file_view.has_local_location()) {
+ LOG(INFO) << "Skip run_generate, because file " << node->main_file_id_ << " has local location";
+ return;
+ }
+ if (file_view.can_download_from_server()) {
+ LOG(INFO) << "Skip run_generate, because file " << node->main_file_id_ << " can be downloaded from server";
return;
}
@@ -1612,7 +2836,7 @@ void FileManager::run_generate(FileNodePtr node) {
if (node->generate_priority_ == 0) {
if (old_priority != 0) {
LOG(INFO) << "Cancel file " << file_id << " generation";
- cancel_generate(node);
+ do_cancel_generate(node);
}
return;
}
@@ -1622,47 +2846,35 @@ void FileManager::run_generate(FileNodePtr node) {
return;
}
- QueryId id = queries_container_.create(Query{file_id, Query::Generate});
+ QueryId id = queries_container_.create(Query{file_id, Query::Type::Generate});
node->generate_id_ = id;
send_closure(file_generate_manager_, &FileGenerateManager::generate_file, id, *node->generate_, node->local_,
- node->suggested_name(), [file_manager = this, id] {
- class Callback : public FileGenerateCallback {
+ node->suggested_path(), [file_manager = this, id] {
+ class Callback final : public FileGenerateCallback {
ActorId<FileManager> actor_;
uint64 query_id_;
public:
Callback(ActorId<FileManager> actor, QueryId id) : actor_(std::move(actor)), query_id_(id) {
}
- void on_partial_generate(const PartialLocalFileLocation &partial_local,
- int32 expected_size) override {
- send_closure(actor_, &FileManager::on_partial_generate, query_id_, partial_local, expected_size);
+ void on_partial_generate(PartialLocalFileLocation partial_local, int64 expected_size) final {
+ send_closure(actor_, &FileManager::on_partial_generate, query_id_, std::move(partial_local),
+ expected_size);
}
- void on_ok(const FullLocalFileLocation &local) override {
- send_closure(actor_, &FileManager::on_generate_ok, query_id_, local);
+ void on_ok(FullLocalFileLocation local) final {
+ send_closure(actor_, &FileManager::on_generate_ok, query_id_, std::move(local));
}
- void on_error(Status error) override {
+ void on_error(Status error) final {
send_closure(actor_, &FileManager::on_error, query_id_, std::move(error));
}
};
- return std::make_unique<Callback>(file_manager->actor_id(file_manager), id);
+ return make_unique<Callback>(file_manager->actor_id(file_manager), id);
}());
LOG(INFO) << "File " << file_id << " generate request has sent to FileGenerateManager";
}
-void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) {
- if (node->need_load_from_pmc_) {
- return;
- }
- if (node->upload_pause_.is_valid()) {
- return;
- }
- FileView file_view(node);
- if (!file_view.has_local_location()) {
- if (node->get_by_hash_ || node->generate_id_ == 0 || !node->generate_was_update_) {
- return;
- }
- }
+void FileManager::run_upload(FileNodePtr node, vector<int> bad_parts) {
int8 priority = 0;
FileId file_id = node->main_file_id_;
for (auto id : node->file_ids_) {
@@ -1674,16 +2886,41 @@ void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) {
}
auto old_priority = node->upload_priority_;
- node->set_upload_priority(priority);
if (priority == 0) {
+ node->set_upload_priority(priority);
if (old_priority != 0) {
LOG(INFO) << "Cancel file " << file_id << " uploading";
- cancel_upload(node);
+ do_cancel_upload(node);
}
return;
}
+ if (node->need_load_from_pmc_) {
+ LOG(INFO) << "File " << node->main_file_id_ << " needs to be loaded from database before upload";
+ return;
+ }
+ if (node->upload_pause_.is_valid()) {
+ LOG(INFO) << "File " << node->main_file_id_ << " upload is paused: " << node->upload_pause_;
+ return;
+ }
+
+ FileView file_view(node);
+ if (!file_view.has_local_location() && !file_view.has_remote_location()) {
+ if (node->get_by_hash_ || node->generate_id_ == 0 || !node->generate_was_update_) {
+ LOG(INFO) << "Have no local location for file: get_by_hash = " << node->get_by_hash_
+ << ", generate_id = " << node->generate_id_ << ", generate_was_update = " << node->generate_was_update_;
+ return;
+ }
+ if (file_view.has_generate_location() && file_view.generate_location().file_type_ == FileType::SecureEncrypted) {
+ // Can't upload secure file before its size is known
+ LOG(INFO) << "Can't upload secure file " << node->main_file_id_ << " before it's size is known";
+ return;
+ }
+ }
+
+ node->set_upload_priority(priority);
+
// create encryption key if necessary
if (((file_view.has_generate_location() && file_view.generate_location().file_type_ == FileType::Encrypted) ||
(file_view.has_local_location() && file_view.local_location().file_type_ == FileType::Encrypted)) &&
@@ -1693,6 +2930,14 @@ void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) {
LOG_IF(FATAL, !success) << "Failed to set encryption key for file " << file_id;
}
+ // create encryption key if necessary
+ if (file_view.has_local_location() && file_view.local_location().file_type_ == FileType::SecureEncrypted &&
+ file_view.encryption_key().empty()) {
+ CHECK(!node->file_ids_.empty());
+ bool success = set_encryption_key(node->file_ids_[0], FileEncryptionKey::create_secure_key());
+ LOG_IF(FATAL, !success) << "Failed to set encryption key for file " << file_id;
+ }
+
if (old_priority != 0) {
LOG(INFO) << "File " << file_id << " is already uploading";
CHECK(node->upload_id_ != 0);
@@ -1701,8 +2946,26 @@ void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) {
}
CHECK(node->upload_id_ == 0);
- if (node->remote_.type() != RemoteFileLocation::Type::Partial && node->get_by_hash_) {
- QueryId id = queries_container_.create(Query{file_id, Query::UploadByHash});
+ if (file_view.has_alive_remote_location() && !file_view.has_active_upload_remote_location() &&
+ can_reuse_remote_file(file_view.get_type())) {
+ QueryId id = queries_container_.create(Query{file_id, Query::Type::UploadWaitFileReference});
+ node->upload_id_ = id;
+ if (node->upload_was_update_file_reference_) {
+ on_error(id, Status::Error("Can't upload file: have no valid file reference"));
+ return;
+ }
+ node->upload_was_update_file_reference_ = true;
+
+ context_->repair_file_reference(
+ node->main_file_id_, PromiseCreator::lambda([id, actor_id = actor_id(this)](Result<Unit> res) {
+ send_closure(actor_id, &FileManager::on_error, id, Status::Error("FILE_UPLOAD_RESTART_WITH_FILE_REFERENCE"));
+ }));
+ return;
+ }
+
+ if (!node->remote_.partial && node->get_by_hash_) {
+ LOG(INFO) << "Get file " << node->main_file_id_ << " by hash";
+ QueryId id = queries_container_.create(Query{file_id, Query::Type::UploadByHash});
node->upload_id_ = id;
send_closure(file_load_manager_, &FileLoadManager::upload_by_hash, id, node->local_.full(), node->size_,
@@ -1710,80 +2973,42 @@ void FileManager::run_upload(FileNodePtr node, std::vector<int> bad_parts) {
return;
}
- QueryId id = queries_container_.create(Query{file_id, Query::Upload});
+ auto new_priority = narrow_cast<int8>(bad_parts.empty() ? -priority : priority);
+ td::remove_if(bad_parts, [](auto part_id) { return part_id < 0; });
+
+ auto expected_size = file_view.expected_size(true);
+ if (node->upload_prefer_small_ && (10 << 20) < expected_size && expected_size < (30 << 20)) {
+ expected_size = 10 << 20;
+ }
+
+ QueryId id = queries_container_.create(Query{file_id, Query::Type::Upload});
node->upload_id_ = id;
- send_closure(file_load_manager_, &FileLoadManager::upload, id, node->local_, node->remote_, node->size_,
- node->encryption_key_, narrow_cast<int8>(bad_parts.empty() ? -priority : priority),
- std::move(bad_parts));
+ send_closure(file_load_manager_, &FileLoadManager::upload, id, node->local_, node->remote_.partial_or_empty(),
+ expected_size, node->encryption_key_, new_priority, std::move(bad_parts));
LOG(INFO) << "File " << file_id << " upload request has sent to FileLoadManager";
}
void FileManager::upload(FileId file_id, std::shared_ptr<UploadCallback> callback, int32 new_priority,
uint64 upload_order) {
- return resume_upload(file_id, std::vector<int>(), std::move(callback), new_priority, upload_order);
-}
-
-// is't quite stupid, yep
-// 0x00 <count of zeroes>
-static string zero_decode(Slice s) {
- string res;
- for (size_t n = s.size(), i = 0; i < n; i++) {
- if (i + 1 < n && s[i] == 0) {
- res.append(static_cast<unsigned char>(s[i + 1]), 0);
- i++;
- continue;
- }
- res.push_back(s[i]);
- }
- return res;
-}
-
-static string zero_encode(Slice s) {
- string res;
- for (size_t n = s.size(), i = 0; i < n; i++) {
- res.push_back(s[i]);
- if (s[i] == 0) {
- unsigned char cnt = 1;
- while (cnt < 250 && i + cnt < n && s[i + cnt] == 0) {
- cnt++;
- }
- res.push_back(cnt);
- i += cnt - 1;
- }
- }
- return res;
-}
-
-static bool is_document_type(FileType type) {
- return type == FileType::Document || type == FileType::Sticker || type == FileType::Audio ||
- type == FileType::Animation;
+ return resume_upload(file_id, vector<int>(), std::move(callback), new_priority, upload_order);
}
-string FileManager::get_persistent_id(const FullRemoteFileLocation &location) {
- auto binary = serialize(location);
-
- binary = zero_encode(binary);
- binary.push_back(PERSISTENT_ID_VERSION);
- return base64url_encode(binary);
+void FileManager::cancel_upload(FileId file_id) {
+ return resume_upload(file_id, vector<int>(), nullptr, 0, 0);
}
-Result<string> FileManager::to_persistent_id(FileId file_id) {
- auto view = get_file_view(file_id);
- if (view.empty()) {
- return Status::Error(10, "Unknown file id");
- }
- if (!view.has_remote_location()) {
- return Status::Error(10, "File has no persistent id");
- }
- return get_persistent_id(view.remote_location());
+static bool is_background_type(FileType type) {
+ return type == FileType::Wallpaper || type == FileType::Background;
}
Result<FileId> FileManager::from_persistent_id(CSlice persistent_id, FileType file_type) {
if (persistent_id.find('.') != string::npos) {
- string input_url = persistent_id.str(); // TODO do not copy persistent_id
- TRY_RESULT(http_url, parse_url(input_url));
- auto url = http_url.get_url();
+ auto r_http_url = parse_url(persistent_id);
+ if (r_http_url.is_error()) {
+ return Status::Error(400, PSLICE() << "Invalid file HTTP URL specified: " << r_http_url.error().message());
+ }
+ auto url = r_http_url.ok().get_url();
if (!clean_input_string(url)) {
return Status::Error(400, "URL must be in UTF-8");
}
@@ -1792,31 +3017,88 @@ Result<FileId> FileManager::from_persistent_id(CSlice persistent_id, FileType fi
auto r_binary = base64url_decode(persistent_id);
if (r_binary.is_error()) {
- return Status::Error(10, "Wrong remote file id specified: " + r_binary.error().message().str());
+ return Status::Error(400, PSLICE() << "Wrong remote file identifier specified: " << r_binary.error().message());
}
auto binary = r_binary.move_as_ok();
if (binary.empty()) {
- return Status::Error(10, "Remote file id can't be empty");
+ return Status::Error(400, "Remote file identifier must be non-empty");
+ }
+ if (binary.back() == FileNode::PERSISTENT_ID_VERSION_OLD) {
+ return from_persistent_id_v2(binary, file_type);
+ }
+ if (binary.back() == FileNode::PERSISTENT_ID_VERSION) {
+ return from_persistent_id_v3(binary, file_type);
+ }
+ if (binary.back() == FileNode::PERSISTENT_ID_VERSION_GENERATED) {
+ return from_persistent_id_generated(binary, file_type);
+ }
+ return Status::Error(400, "Wrong remote file identifier specified: can't unserialize it. Wrong last symbol");
+}
+
+Result<FileId> FileManager::from_persistent_id_generated(Slice binary, FileType file_type) {
+ binary.remove_suffix(1);
+ auto decoded_binary = zero_decode(binary);
+ FullGenerateFileLocation generate_location;
+ auto status = unserialize(generate_location, decoded_binary);
+ if (status.is_error()) {
+ return Status::Error(400, "Wrong remote file identifier specified: can't unserialize it");
}
- if (binary.back() != PERSISTENT_ID_VERSION) {
- return Status::Error(10, "Wrong remote file id specified: can't unserialize it. Wrong last symbol");
+ auto real_file_type = generate_location.file_type_;
+ if ((real_file_type != file_type && file_type != FileType::Temp) ||
+ (real_file_type != FileType::Thumbnail && real_file_type != FileType::EncryptedThumbnail)) {
+ return Status::Error(400, PSLICE() << "Can't use file of type " << real_file_type << " as " << file_type);
}
- binary.pop_back();
- binary = zero_decode(binary);
+ if (!is_remotely_generated_file(generate_location.conversion_)) {
+ return Status::Error(400, "Unexpected conversion type");
+ }
+ FileData data;
+ data.generate_ = make_unique<FullGenerateFileLocation>(std::move(generate_location));
+ return register_file(std::move(data), FileLocationSource::FromUser, FileId(), "from_persistent_id_generated", false)
+ .move_as_ok();
+}
+
+Result<FileId> FileManager::from_persistent_id_v23(Slice binary, FileType file_type, int32 version) {
+ if (version < 0 || version >= static_cast<int32>(Version::Next)) {
+ return Status::Error(400, "Invalid remote file identifier");
+ }
+ auto decoded_binary = zero_decode(binary);
FullRemoteFileLocation remote_location;
- auto status = unserialize(remote_location, binary);
+ log_event::WithVersion<TlParser> parser(decoded_binary);
+ parser.set_version(version);
+ parse(remote_location, parser);
+ parser.fetch_end();
+ auto status = parser.get_status();
if (status.is_error()) {
- return Status::Error(10, "Wrong remote file id specified: can't unserialize it");
+ return Status::Error(400, "Wrong remote file identifier specified: can't unserialize it");
}
auto &real_file_type = remote_location.file_type_;
- if (is_document_type(real_file_type) && is_document_type(file_type)) {
+ if (is_document_file_type(real_file_type) && is_document_file_type(file_type)) {
real_file_type = file_type;
+ } else if (is_background_type(real_file_type) && is_background_type(file_type)) {
+ // type of file matches, but real type is in the stored remote location
} else if (real_file_type != file_type && file_type != FileType::Temp) {
- return Status::Error(10, "Type of file mismatch");
+ return Status::Error(400, PSLICE() << "Can't use file of type " << real_file_type << " as " << file_type);
}
FileData data;
data.remote_ = RemoteFileLocation(std::move(remote_location));
- return register_file(std::move(data), FileLocationSource::FromUser, "from_persistent_id", false).move_as_ok();
+ auto file_id = register_file(std::move(data), FileLocationSource::FromUser, FileId(), "from_persistent_id_v23", false)
+ .move_as_ok();
+ return file_id;
+}
+
+Result<FileId> FileManager::from_persistent_id_v2(Slice binary, FileType file_type) {
+ binary.remove_suffix(1);
+ return from_persistent_id_v23(binary, file_type, 0);
+}
+
+Result<FileId> FileManager::from_persistent_id_v3(Slice binary, FileType file_type) {
+ binary.remove_suffix(1);
+ if (binary.empty()) {
+ return Status::Error(400, "Invalid remote file identifier");
+ }
+ int32 version = static_cast<uint8>(binary.back());
+ binary.remove_suffix(1);
+ return from_persistent_id_v23(binary, file_type, version);
}
FileView FileManager::get_file_view(FileId file_id) const {
@@ -1826,6 +3108,7 @@ FileView FileManager::get_file_view(FileId file_id) const {
}
return FileView(file_node);
}
+
FileView FileManager::get_sync_file_view(FileId file_id) {
auto file_node = get_sync_file_node(file_id);
if (!file_node) {
@@ -1834,7 +3117,7 @@ FileView FileManager::get_sync_file_view(FileId file_id) {
return FileView(file_node);
}
-tl_object_ptr<td_api::file> FileManager::get_file_object(FileId file_id, bool with_main_file_id) {
+td_api::object_ptr<td_api::file> FileManager::get_file_object(FileId file_id, bool with_main_file_id) {
auto file_view = get_sync_file_view(file_id);
if (file_view.empty()) {
@@ -1842,18 +3125,15 @@ tl_object_ptr<td_api::file> FileManager::get_file_object(FileId file_id, bool wi
td_api::make_object<td_api::remoteFile>());
}
- string persistent_file_id;
- if (file_view.has_remote_location()) {
- persistent_file_id = get_persistent_id(file_view.remote_location());
- } else if (file_view.has_url()) {
- persistent_file_id = file_view.url();
- }
-
- int32 size = narrow_cast<int32>(file_view.size());
- int32 expected_size = narrow_cast<int32>(file_view.expected_size());
- int32 local_size = narrow_cast<int32>(file_view.local_size());
- int32 local_total_size = narrow_cast<int32>(file_view.local_total_size());
- int32 remote_size = narrow_cast<int32>(file_view.remote_size());
+ string persistent_file_id = file_view.get_persistent_file_id();
+ string unique_file_id = file_view.get_unique_file_id();
+ bool is_uploading_completed = !persistent_file_id.empty();
+ auto size = file_view.size();
+ auto expected_size = file_view.expected_size();
+ auto download_offset = file_view.download_offset();
+ auto local_prefix_size = file_view.local_prefix_size();
+ auto local_total_size = file_view.local_total_size();
+ auto remote_size = file_view.remote_size();
string path = file_view.path();
bool can_be_downloaded = file_view.can_download_from_server() || file_view.can_generate();
bool can_be_deleted = file_view.can_delete();
@@ -1862,56 +3142,93 @@ tl_object_ptr<td_api::file> FileManager::get_file_object(FileId file_id, bool wi
auto *file_info = get_file_id_info(result_file_id);
if (with_main_file_id) {
if (!file_info->send_updates_flag_) {
- result_file_id = file_view.file_id();
+ result_file_id = file_view.get_main_file_id();
}
- file_info = get_file_id_info(file_view.file_id());
+ file_info = get_file_id_info(file_view.get_main_file_id());
}
file_info->send_updates_flag_ = true;
VLOG(update_file) << "Send file " << file_id << " as " << result_file_id << " and update send_updates_flag_ for file "
- << (with_main_file_id ? file_view.file_id() : result_file_id);
+ << (with_main_file_id ? file_view.get_main_file_id() : result_file_id);
return td_api::make_object<td_api::file>(
result_file_id.get(), size, expected_size,
td_api::make_object<td_api::localFile>(std::move(path), can_be_downloaded, can_be_deleted,
- file_view.is_downloading(), file_view.has_local_location(), local_size,
- local_total_size),
- td_api::make_object<td_api::remoteFile>(std::move(persistent_file_id), file_view.is_uploading(),
- file_view.has_remote_location(), remote_size));
+ file_view.is_downloading(), file_view.has_local_location(),
+ download_offset, local_prefix_size, local_total_size),
+ td_api::make_object<td_api::remoteFile>(std::move(persistent_file_id), std::move(unique_file_id),
+ file_view.is_uploading(), is_uploading_completed, remote_size));
+}
+
+vector<int32> FileManager::get_file_ids_object(const vector<FileId> &file_ids, bool with_main_file_id) {
+ return transform(file_ids, [this, with_main_file_id](FileId file_id) {
+ auto file_view = get_sync_file_view(file_id);
+ auto result_file_id = file_id;
+ auto *file_info = get_file_id_info(result_file_id);
+ if (with_main_file_id) {
+ if (!file_info->sent_file_id_flag_ && !file_info->send_updates_flag_) {
+ result_file_id = file_view.get_main_file_id();
+ }
+ file_info = get_file_id_info(file_view.get_main_file_id());
+ }
+ file_info->sent_file_id_flag_ = true;
+
+ return result_file_id.get();
+ });
}
Result<FileId> FileManager::check_input_file_id(FileType type, Result<FileId> result, bool is_encrypted,
- bool allow_zero) {
+ bool allow_zero, bool is_secure) {
TRY_RESULT(file_id, std::move(result));
if (allow_zero && !file_id.is_valid()) {
return FileId();
}
- auto file_node = get_file_node(file_id);
+ auto file_node = get_sync_file_node(file_id); // we need full data about sent files
if (!file_node) {
- return Status::Error(6, "File not found");
+ return Status::Error(400, "File not found");
}
auto file_view = FileView(file_node);
FileType real_type = file_view.get_type();
- if (!is_encrypted) {
+ LOG(INFO) << "Checking file " << file_id << " of type " << type << "/" << real_type;
+ if (!is_encrypted && !is_secure) {
if (real_type != type && !(real_type == FileType::Temp && file_view.has_url()) &&
- !(is_document_type(real_type) && is_document_type(type))) {
+ !(is_document_file_type(real_type) && is_document_file_type(type)) &&
+ !(is_background_type(real_type) && is_background_type(type)) &&
+ !(file_view.is_encrypted() && type == FileType::Ringtone)) {
// TODO: send encrypted file to unencrypted chat
- return Status::Error(6, "Type of file mismatch");
+ return Status::Error(400, PSLICE() << "Can't use file of type " << real_type << " as " << type);
}
}
if (!file_view.has_remote_location()) {
- // TODO why not return file_id here? We will dup it anyway
- // But it will not be duped if has_input_media(), so for now we can't return main_file_id
- return dup_file_id(file_id);
+ // There are no reasons to dup file_id, because it will be duped anyway before upload/reupload
+ // It will not be duped in dup_message_content only if has_input_media(),
+ // but currently in this case the file never needs to be reuploaded
+
+ if (!is_encrypted) {
+ // URLs in non-secret chats never needs to be reuploaded, so they don't need to be duped
+ // non-URLs without remote location will be duped at dup_message_content, because they have no input media
+ return file_node->main_file_id_;
+ }
+
+ return dup_file_id(file_id, "check_input_file_id");
}
- return file_node->main_file_id_;
+
+ int32 remote_id = file_id.get_remote();
+ if (remote_id == 0) {
+ RemoteInfo info{file_view.remote_location(), FileLocationSource::FromUser, file_id};
+ remote_id = remote_location_info_.add(info);
+ if (remote_location_info_.get(remote_id).file_id_ == file_id) {
+ get_file_id_info(file_id)->pin_flag_ = true;
+ }
+ }
+ return FileId(file_node->main_file_id_.get(), remote_id);
}
Result<FileId> FileManager::get_input_thumbnail_file_id(const tl_object_ptr<td_api::InputFile> &thumbnail_input_file,
DialogId owner_dialog_id, bool is_encrypted) {
if (thumbnail_input_file == nullptr) {
- return Status::Error(6, "inputThumbnail not specified");
+ return Status::Error(400, "inputThumbnail not specified");
}
switch (thumbnail_input_file->get_id()) {
@@ -1922,9 +3239,9 @@ Result<FileId> FileManager::get_input_thumbnail_file_id(const tl_object_ptr<td_a
owner_dialog_id, 0, false);
}
case td_api::inputFileId::ID:
- return Status::Error(6, "InputFileId is not supported for thumbnails");
+ return Status::Error(400, "InputFileId is not supported for thumbnails");
case td_api::inputFileRemote::ID:
- return Status::Error(6, "InputFileRemote is not supported for thumbnails");
+ return Status::Error(400, "InputFileRemote is not supported for thumbnails");
case td_api::inputFileGenerated::ID: {
auto *generated_thumbnail = static_cast<const td_api::inputFileGenerated *>(thumbnail_input_file.get());
return register_generate(is_encrypted ? FileType::EncryptedThumbnail : FileType::Thumbnail,
@@ -1939,17 +3256,20 @@ Result<FileId> FileManager::get_input_thumbnail_file_id(const tl_object_ptr<td_a
Result<FileId> FileManager::get_input_file_id(FileType type, const tl_object_ptr<td_api::InputFile> &file,
DialogId owner_dialog_id, bool allow_zero, bool is_encrypted,
- bool get_by_hash) {
- if (is_encrypted) {
- get_by_hash = false;
- }
- if (!file) {
+ bool get_by_hash, bool is_secure, bool force_reuse) {
+ if (file == nullptr) {
if (allow_zero) {
return FileId();
}
- return Status::Error(6, "InputFile not specified");
+ return Status::Error(400, "InputFile is not specified");
+ }
+
+ if (is_encrypted || is_secure) {
+ get_by_hash = false;
}
+ auto new_type = is_encrypted ? FileType::Encrypted : (is_secure ? FileType::SecureEncrypted : type);
+
auto r_file_id = [&]() -> Result<FileId> {
switch (file->get_id()) {
case td_api::inputFileLocal::ID: {
@@ -1957,8 +3277,37 @@ Result<FileId> FileManager::get_input_file_id(FileType type, const tl_object_ptr
if (allow_zero && path.empty()) {
return FileId();
}
- return register_local(FullLocalFileLocation(is_encrypted ? FileType::Encrypted : type, path, 0),
- owner_dialog_id, 0, get_by_hash);
+ string hash;
+ if (G()->get_option_boolean("reuse_uploaded_photos_by_hash") && new_type == FileType::Photo) {
+ auto r_stat = stat(path);
+ if (r_stat.is_ok() && r_stat.ok().size_ > 0 && r_stat.ok().size_ < 11000000) {
+ auto r_file_content = read_file_str(path, r_stat.ok().size_);
+ if (r_file_content.is_ok()) {
+ hash = sha256(r_file_content.ok());
+ auto file_id = file_hash_to_file_id_.get(hash);
+ LOG(INFO) << "Found file " << file_id << " by hash " << hex_encode(hash);
+ if (file_id.is_valid()) {
+ auto file_view = get_file_view(file_id);
+ if (!file_view.empty()) {
+ if (force_reuse) {
+ return file_id;
+ }
+ if (file_view.has_remote_location() && !file_view.remote_location().is_web()) {
+ return file_id;
+ }
+ if (file_view.is_uploading()) {
+ hash.clear();
+ }
+ }
+ }
+ }
+ }
+ }
+ TRY_RESULT(file_id, register_local(FullLocalFileLocation(new_type, path, 0), owner_dialog_id, 0, get_by_hash));
+ if (!hash.empty()) {
+ file_hash_to_file_id_.set(hash, file_id);
+ }
+ return file_id;
}
case td_api::inputFileId::ID: {
FileId file_id(static_cast<const td_api::inputFileId *>(file.get())->id_, 0);
@@ -1976,9 +3325,8 @@ Result<FileId> FileManager::get_input_file_id(FileType type, const tl_object_ptr
}
case td_api::inputFileGenerated::ID: {
auto *generated_file = static_cast<const td_api::inputFileGenerated *>(file.get());
- return register_generate(is_encrypted ? FileType::Encrypted : type, FileLocationSource::FromUser,
- generated_file->original_path_, generated_file->conversion_, owner_dialog_id,
- generated_file->expected_size_);
+ return register_generate(new_type, FileLocationSource::FromUser, generated_file->original_path_,
+ generated_file->conversion_, owner_dialog_id, generated_file->expected_size_);
}
default:
UNREACHABLE();
@@ -1986,7 +3334,134 @@ Result<FileId> FileManager::get_input_file_id(FileType type, const tl_object_ptr
}
}();
- return check_input_file_id(type, std::move(r_file_id), is_encrypted, allow_zero);
+ return check_input_file_id(type, std::move(r_file_id), is_encrypted, allow_zero, is_secure);
+}
+
+Result<FileId> FileManager::get_map_thumbnail_file_id(Location location, int32 zoom, int32 width, int32 height,
+ int32 scale, DialogId owner_dialog_id) {
+ if (!location.is_valid_map_point()) {
+ return Status::Error(400, "Invalid location specified");
+ }
+ if (zoom < 13 || zoom > 20) {
+ return Status::Error(400, "Wrong zoom");
+ }
+ if (width < 16 || width > 1024) {
+ return Status::Error(400, "Wrong width");
+ }
+ if (height < 16 || height > 1024) {
+ return Status::Error(400, "Wrong height");
+ }
+ if (scale < 1 || scale > 3) {
+ return Status::Error(400, "Wrong scale");
+ }
+
+ const double PI = 3.14159265358979323846;
+ double sin_latitude = std::sin(location.get_latitude() * PI / 180);
+ int32 size = 256 * (1 << zoom);
+ auto x = static_cast<int32>((location.get_longitude() + 180) / 360 * size);
+ auto y = static_cast<int32>((0.5 - std::log((1 + sin_latitude) / (1 - sin_latitude)) / (4 * PI)) * size);
+ x = clamp(x, 0, size - 1); // just in case
+ y = clamp(y, 0, size - 1); // just in case
+
+ string conversion = PSTRING() << "#map#" << zoom << '#' << x << '#' << y << '#' << width << '#' << height << '#'
+ << scale << '#';
+ return register_generate(
+ owner_dialog_id.get_type() == DialogType::SecretChat ? FileType::EncryptedThumbnail : FileType::Thumbnail,
+ FileLocationSource::FromUser, string(), std::move(conversion), owner_dialog_id, 0);
+}
+
+Result<FileId> FileManager::get_audio_thumbnail_file_id(string title, string performer, bool is_small,
+ DialogId owner_dialog_id) {
+ if (!clean_input_string(title)) {
+ return Status::Error(400, "Title must be encoded in UTF-8");
+ }
+ if (!clean_input_string(performer)) {
+ return Status::Error(400, "Performer must be encoded in UTF-8");
+ }
+ for (auto &c : title) {
+ if (c == '\n' || c == '#') {
+ c = ' ';
+ }
+ }
+ for (auto &c : performer) {
+ if (c == '\n' || c == '#') {
+ c = ' ';
+ }
+ }
+ title = trim(title);
+ performer = trim(performer);
+ if (title.empty() && performer.empty()) {
+ return Status::Error(400, "Title or performer must be non-empty");
+ }
+
+ string conversion = PSTRING() << "#audio_t#" << title << '#' << performer << '#' << (is_small ? '1' : '0') << '#';
+ return register_generate(
+ owner_dialog_id.get_type() == DialogType::SecretChat ? FileType::EncryptedThumbnail : FileType::Thumbnail,
+ FileLocationSource::FromUser, string(), std::move(conversion), owner_dialog_id, 0);
+}
+
+FileType FileManager::guess_file_type(const tl_object_ptr<td_api::InputFile> &file) {
+ if (file == nullptr) {
+ return FileType::Temp;
+ }
+
+ auto guess_file_type_by_path = [](const string &file_path) {
+ PathView path_view(file_path);
+ auto file_name = path_view.file_name();
+ auto extension = path_view.extension();
+ if (extension == "jpg" || extension == "jpeg") {
+ return FileType::Photo;
+ }
+ if (extension == "ogg" || extension == "oga" || extension == "opus") {
+ return FileType::VoiceNote;
+ }
+ if (extension == "3gp" || extension == "mov") {
+ return FileType::Video;
+ }
+ if (extension == "mp3" || extension == "mpeg3" || extension == "m4a") {
+ return FileType::Audio;
+ }
+ if (extension == "webp" || extension == "tgs" || extension == "webm") {
+ return FileType::Sticker;
+ }
+ if (extension == "gif") {
+ return FileType::Animation;
+ }
+ if (extension == "mp4" || extension == "mpeg4") {
+ return to_lower(file_name).find("-gif-") != string::npos ? FileType::Animation : FileType::Video;
+ }
+ return FileType::Document;
+ };
+
+ switch (file->get_id()) {
+ case td_api::inputFileLocal::ID:
+ return guess_file_type_by_path(static_cast<const td_api::inputFileLocal *>(file.get())->path_);
+ case td_api::inputFileId::ID: {
+ FileId file_id(static_cast<const td_api::inputFileId *>(file.get())->id_, 0);
+ auto file_view = get_file_view(file_id);
+ if (file_view.empty()) {
+ return FileType::Temp;
+ }
+ return file_view.get_type();
+ }
+ case td_api::inputFileRemote::ID: {
+ const string &file_persistent_id = static_cast<const td_api::inputFileRemote *>(file.get())->id_;
+ Result<FileId> r_file_id = from_persistent_id(file_persistent_id, FileType::Temp);
+ if (r_file_id.is_error()) {
+ return FileType::Temp;
+ }
+ auto file_view = get_file_view(r_file_id.ok());
+ if (file_view.empty()) {
+ return FileType::Temp;
+ }
+ return file_view.get_type();
+ }
+ case td_api::inputFileGenerated::ID:
+ return guess_file_type_by_path(static_cast<const td_api::inputFileGenerated *>(file.get())->original_path_);
+ default:
+ UNREACHABLE();
+ return FileType::Temp;
+ }
}
vector<tl_object_ptr<telegram_api::InputDocument>> FileManager::get_input_documents(const vector<FileId> &file_ids) {
@@ -2002,30 +3477,97 @@ vector<tl_object_ptr<telegram_api::InputDocument>> FileManager::get_input_docume
return result;
}
+bool FileManager::extract_was_uploaded(const tl_object_ptr<telegram_api::InputMedia> &input_media) {
+ if (input_media == nullptr) {
+ return false;
+ }
+
+ auto input_media_id = input_media->get_id();
+ return input_media_id == telegram_api::inputMediaUploadedPhoto::ID ||
+ input_media_id == telegram_api::inputMediaUploadedDocument::ID;
+}
+
+bool FileManager::extract_was_thumbnail_uploaded(const tl_object_ptr<telegram_api::InputMedia> &input_media) {
+ if (input_media == nullptr || input_media->get_id() != telegram_api::inputMediaUploadedDocument::ID) {
+ return false;
+ }
+
+ return static_cast<const telegram_api::inputMediaUploadedDocument *>(input_media.get())->thumb_ != nullptr;
+}
+
+string FileManager::extract_file_reference(const tl_object_ptr<telegram_api::InputMedia> &input_media) {
+ if (input_media == nullptr) {
+ return string();
+ }
+
+ switch (input_media->get_id()) {
+ case telegram_api::inputMediaDocument::ID:
+ return extract_file_reference(static_cast<const telegram_api::inputMediaDocument *>(input_media.get())->id_);
+ case telegram_api::inputMediaPhoto::ID:
+ return extract_file_reference(static_cast<const telegram_api::inputMediaPhoto *>(input_media.get())->id_);
+ default:
+ return string();
+ }
+}
+
+string FileManager::extract_file_reference(const tl_object_ptr<telegram_api::InputDocument> &input_document) {
+ if (input_document == nullptr || input_document->get_id() != telegram_api::inputDocument::ID) {
+ return string();
+ }
+
+ return static_cast<const telegram_api::inputDocument *>(input_document.get())->file_reference_.as_slice().str();
+}
+
+string FileManager::extract_file_reference(const tl_object_ptr<telegram_api::InputPhoto> &input_photo) {
+ if (input_photo == nullptr || input_photo->get_id() != telegram_api::inputPhoto::ID) {
+ return string();
+ }
+
+ return static_cast<const telegram_api::inputPhoto *>(input_photo.get())->file_reference_.as_slice().str();
+}
+
+bool FileManager::extract_was_uploaded(const tl_object_ptr<telegram_api::InputChatPhoto> &input_chat_photo) {
+ return input_chat_photo != nullptr && input_chat_photo->get_id() == telegram_api::inputChatUploadedPhoto::ID;
+}
+
+string FileManager::extract_file_reference(const tl_object_ptr<telegram_api::InputChatPhoto> &input_chat_photo) {
+ if (input_chat_photo == nullptr || input_chat_photo->get_id() != telegram_api::inputChatPhoto::ID) {
+ return string();
+ }
+
+ return extract_file_reference(static_cast<const telegram_api::inputChatPhoto *>(input_chat_photo.get())->id_);
+}
+
FileId FileManager::next_file_id() {
if (!empty_file_ids_.empty()) {
auto res = empty_file_ids_.back();
empty_file_ids_.pop_back();
return FileId{res, 0};
}
+ CHECK(file_id_info_.size() <= static_cast<size_t>(std::numeric_limits<int32>::max()));
FileId res(static_cast<int32>(file_id_info_.size()), 0);
- // LOG(ERROR) << "NEXT file_id " << res;
file_id_info_.push_back({});
return res;
}
FileManager::FileNodeId FileManager::next_file_node_id() {
- FileNodeId res = static_cast<FileNodeId>(file_nodes_.size());
+ CHECK(file_nodes_.size() <= static_cast<size_t>(std::numeric_limits<FileNodeId>::max()));
+ auto res = static_cast<FileNodeId>(file_nodes_.size());
file_nodes_.emplace_back(nullptr);
return res;
}
void FileManager::on_start_download(QueryId query_id) {
+ if (is_closed_) {
+ return;
+ }
+
auto query = queries_container_.get(query_id);
CHECK(query != nullptr);
auto file_id = query->file_id_;
auto file_node = get_file_node(file_id);
+ LOG(DEBUG) << "Receive on_start_download for file " << file_id;
if (!file_node) {
return;
}
@@ -2037,13 +3579,19 @@ void FileManager::on_start_download(QueryId query_id) {
file_node->is_download_started_ = true;
}
-void FileManager::on_partial_download(QueryId query_id, const PartialLocalFileLocation &partial_local,
- int64 ready_size) {
+void FileManager::on_partial_download(QueryId query_id, PartialLocalFileLocation partial_local, int64 ready_size,
+ int64 size) {
+ if (is_closed_) {
+ return;
+ }
+
auto query = queries_container_.get(query_id);
CHECK(query != nullptr);
auto file_id = query->file_id_;
auto file_node = get_file_node(file_id);
+ LOG(DEBUG) << "Receive on_partial_download for file " << file_id << " with " << partial_local
+ << ", ready_size = " << ready_size << " and size = " << size;
if (!file_node) {
return;
}
@@ -2051,17 +3599,28 @@ void FileManager::on_partial_download(QueryId query_id, const PartialLocalFileLo
return;
}
- file_node->set_local_location(LocalFileLocation(partial_local), ready_size);
- try_flush_node(file_node);
+ if (size != 0) {
+ FileView file_view(file_node);
+ if (!file_view.is_encrypted_secure()) {
+ file_node->set_size(size);
+ }
+ }
+ file_node->set_local_location(LocalFileLocation(std::move(partial_local)), ready_size, -1, -1 /* TODO */);
+ try_flush_node(file_node, "on_partial_download");
}
-void FileManager::on_partial_upload(QueryId query_id, const PartialRemoteFileLocation &partial_remote,
- int64 ready_size) {
+void FileManager::on_hash(QueryId query_id, string hash) {
+ if (is_closed_) {
+ return;
+ }
+
auto query = queries_container_.get(query_id);
CHECK(query != nullptr);
auto file_id = query->file_id_;
+
auto file_node = get_file_node(file_id);
+ LOG(DEBUG) << "Receive on_hash for file " << file_id;
if (!file_node) {
return;
}
@@ -2069,22 +3628,66 @@ void FileManager::on_partial_upload(QueryId query_id, const PartialRemoteFileLoc
return;
}
- file_node->set_remote_location(RemoteFileLocation(partial_remote), FileLocationSource::None, ready_size);
- try_flush_node(file_node);
+ file_node->encryption_key_.set_value_hash(secure_storage::ValueHash::create(hash).move_as_ok());
}
-void FileManager::on_download_ok(QueryId query_id, const FullLocalFileLocation &local, int64 size) {
- auto file_id = finish_query(query_id).first.file_id_;
- LOG(INFO) << "ON DOWNLOAD OK file " << file_id << " of size " << size;
- auto r_new_file_id = register_local(local, DialogId(), size);
+
+void FileManager::on_partial_upload(QueryId query_id, PartialRemoteFileLocation partial_remote, int64 ready_size) {
+ if (is_closed_) {
+ return;
+ }
+
+ auto query = queries_container_.get(query_id);
+ CHECK(query != nullptr);
+
+ auto file_id = query->file_id_;
+ auto file_node = get_file_node(file_id);
+ LOG(DEBUG) << "Receive on_partial_upload for file " << file_id << " with " << partial_remote << " and ready size "
+ << ready_size;
+ if (!file_node) {
+ LOG(ERROR) << "Can't find being uploaded file " << file_id;
+ return;
+ }
+ if (file_node->upload_id_ != query_id) {
+ LOG(DEBUG) << "Upload identifier of file " << file_id << " is " << file_node->upload_id_ << " instead of "
+ << query_id;
+ return;
+ }
+
+ file_node->set_partial_remote_location(std::move(partial_remote), ready_size);
+ try_flush_node(file_node, "on_partial_upload");
+}
+
+void FileManager::on_download_ok(QueryId query_id, FullLocalFileLocation local, int64 size, bool is_new) {
+ if (is_closed_) {
+ return;
+ }
+
+ Query query;
+ bool was_active;
+ std::tie(query, was_active) = finish_query(query_id);
+ auto file_id = query.file_id_;
+ LOG(INFO) << "ON DOWNLOAD OK of " << (is_new ? "new" : "checked") << " file " << file_id << " of size " << size;
+ auto r_new_file_id = register_local(std::move(local), DialogId(), size, false, false, true, file_id);
+ Status status = Status::OK();
if (r_new_file_id.is_error()) {
- LOG(ERROR) << "Can't register local file after download: " << r_new_file_id.error();
+ status = Status::Error(PSLICE() << "Can't register local file after download: " << r_new_file_id.error().message());
} else {
- context_->on_new_file(get_file_view(r_new_file_id.ok()).size());
- LOG_STATUS(merge(r_new_file_id.ok(), file_id));
+ if (is_new && context_->need_notify_on_new_files()) {
+ context_->on_new_file(size, get_file_view(r_new_file_id.ok()).get_allocated_local_size(), 1);
+ }
+ }
+ if (status.is_error()) {
+ LOG(ERROR) << status.message();
+ return on_error_impl(get_file_node(file_id), query.type_, was_active, std::move(status));
}
}
-void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const PartialRemoteFileLocation &partial_remote,
+
+void FileManager::on_upload_ok(QueryId query_id, FileType file_type, PartialRemoteFileLocation partial_remote,
int64 size) {
+ if (is_closed_) {
+ return;
+ }
+
CHECK(partial_remote.ready_part_count_ == partial_remote.part_count_);
auto some_file_id = finish_query(query_id).first.file_id_;
LOG(INFO) << "ON UPLOAD OK file " << some_file_id << " of size " << size;
@@ -2108,13 +3711,14 @@ void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const Parti
}
auto *file_info = get_file_id_info(file_id);
+ LOG(INFO) << "Found being uploaded file " << file_id << " with priority " << file_info->upload_priority_;
file_info->upload_priority_ = 0;
file_info->download_priority_ = 0;
FileView file_view(file_node);
- string file_name = get_file_name(file_type, file_view.suggested_name());
+ string file_name = get_file_name(file_type, file_view.suggested_path());
- if (file_view.is_encrypted()) {
+ if (file_view.is_encrypted_secret()) {
tl_object_ptr<telegram_api::InputEncryptedFile> input_file;
if (partial_remote.is_big_) {
input_file = make_tl_object<telegram_api::inputEncryptedFileBigUploaded>(
@@ -2124,8 +3728,18 @@ void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const Parti
partial_remote.file_id_, partial_remote.part_count_, "", file_view.encryption_key().calc_fingerprint());
}
if (file_info->upload_callback_) {
+ file_node->set_upload_pause(file_id);
file_info->upload_callback_->on_upload_encrypted_ok(file_id, std::move(input_file));
- file_node->upload_pause_ = file_id;
+ file_info->upload_callback_.reset();
+ }
+ } else if (file_view.is_secure()) {
+ tl_object_ptr<telegram_api::InputSecureFile> input_file;
+ input_file = make_tl_object<telegram_api::inputSecureFileUploaded>(
+ partial_remote.file_id_, partial_remote.part_count_, "" /*md5*/, BufferSlice() /*file_hash*/,
+ BufferSlice() /*encrypted_secret*/);
+ if (file_info->upload_callback_) {
+ file_node->set_upload_pause(file_id);
+ file_info->upload_callback_->on_upload_secure_ok(file_id, std::move(input_file));
file_info->upload_callback_.reset();
}
} else {
@@ -2138,37 +3752,48 @@ void FileManager::on_upload_ok(QueryId query_id, FileType file_type, const Parti
std::move(file_name), "");
}
if (file_info->upload_callback_) {
+ file_node->set_upload_pause(file_id);
file_info->upload_callback_->on_upload_ok(file_id, std::move(input_file));
- file_node->upload_pause_ = file_id;
file_info->upload_callback_.reset();
}
}
+ // don't flush node info, because nothing actually changed
}
-void FileManager::on_upload_full_ok(QueryId query_id, const FullRemoteFileLocation &remote) {
- LOG(INFO) << "ON UPLOAD OK";
+// for upload by hash
+void FileManager::on_upload_full_ok(QueryId query_id, FullRemoteFileLocation remote) {
+ if (is_closed_) {
+ return;
+ }
+
auto file_id = finish_query(query_id).first.file_id_;
- auto new_file_id = register_remote(remote, FileLocationSource::FromServer, DialogId(), 0, 0, "");
+ LOG(INFO) << "ON UPLOAD FULL OK for file " << file_id;
+ auto new_file_id = register_remote(std::move(remote), FileLocationSource::FromServer, DialogId(), 0, 0, "");
LOG_STATUS(merge(new_file_id, file_id));
}
-void FileManager::on_partial_generate(QueryId query_id, const PartialLocalFileLocation &partial_local,
- int32 expected_size) {
- LOG(INFO) << "on_parital_generate: " << partial_local.path_ << " " << partial_local.ready_part_count_;
+void FileManager::on_partial_generate(QueryId query_id, PartialLocalFileLocation partial_local, int64 expected_size) {
+ if (is_closed_) {
+ return;
+ }
+
auto query = queries_container_.get(query_id);
CHECK(query != nullptr);
auto file_id = query->file_id_;
auto file_node = get_file_node(file_id);
+ auto bitmask = Bitmask(Bitmask::Decode{}, partial_local.ready_bitmask_);
+ LOG(DEBUG) << "Receive on_partial_generate for file " << file_id << ": " << partial_local.path_ << " " << bitmask;
if (!file_node) {
return;
}
if (file_node->generate_id_ != query_id) {
return;
}
- file_node->set_local_location(LocalFileLocation(partial_local), 0);
+ auto ready_size = bitmask.get_total_size(partial_local.part_size_, file_node->size_);
+ file_node->set_local_location(LocalFileLocation(partial_local), ready_size, -1, -1 /* TODO */);
// TODO check for size and local_size, abort generation if needed
- if (expected_size != 0) {
+ if (expected_size > 0) {
file_node->set_expected_size(expected_size);
}
if (!file_node->generate_was_update_) {
@@ -2177,18 +3802,23 @@ void FileManager::on_partial_generate(QueryId query_id, const PartialLocalFileLo
}
if (file_node->upload_id_ != 0) {
send_closure(file_load_manager_, &FileLoadManager::update_local_file_location, file_node->upload_id_,
- LocalFileLocation(partial_local));
+ LocalFileLocation(std::move(partial_local)));
}
- try_flush_node(file_node);
+ try_flush_node(file_node, "on_partial_generate");
}
-void FileManager::on_generate_ok(QueryId query_id, const FullLocalFileLocation &local) {
- LOG(INFO) << "on_ok_generate: " << local;
+
+void FileManager::on_generate_ok(QueryId query_id, FullLocalFileLocation local) {
+ if (is_closed_) {
+ return;
+ }
+
Query query;
bool was_active;
std::tie(query, was_active) = finish_query(query_id);
auto generate_file_id = query.file_id_;
+ LOG(INFO) << "Receive on_generate_ok for file " << generate_file_id << ": " << local;
auto file_node = get_file_node(generate_file_id);
if (!file_node) {
return;
@@ -2196,35 +3826,37 @@ void FileManager::on_generate_ok(QueryId query_id, const FullLocalFileLocation &
auto old_upload_id = file_node->upload_id_;
- auto r_new_file_id = register_local(local, DialogId(), 0);
- Status status;
- if (r_new_file_id.is_error()) {
- status = Status::Error(PSLICE() << "Can't register local file after generate: " << r_new_file_id.error());
- } else {
- auto result = merge(r_new_file_id.ok(), generate_file_id);
- if (result.is_error()) {
- status = result.move_as_error();
- }
- }
+ auto r_new_file_id = register_local(local, DialogId(), 0, false, false, false, generate_file_id);
file_node = get_file_node(generate_file_id);
- if (status.is_error()) {
- return on_error_impl(file_node, query.type_, was_active, std::move(status));
+ if (r_new_file_id.is_error()) {
+ return on_error_impl(
+ file_node, query.type_, was_active,
+ Status::Error(PSLICE() << "Can't register local file after generate: " << r_new_file_id.error()));
}
-
CHECK(file_node);
- context_->on_new_file(FileView(file_node).size());
+
+ FileView file_view(file_node);
+ if (context_->need_notify_on_new_files()) {
+ if (!file_view.has_generate_location() || !begins_with(file_view.generate_location().conversion_, "#file_id#")) {
+ context_->on_new_file(file_view.size(), file_view.get_allocated_local_size(), 1);
+ }
+ }
run_upload(file_node, {});
if (was_active) {
if (old_upload_id != 0 && old_upload_id == file_node->upload_id_) {
send_closure(file_load_manager_, &FileLoadManager::update_local_file_location, file_node->upload_id_,
- LocalFileLocation(local));
+ LocalFileLocation(std::move(local)));
}
}
}
void FileManager::on_error(QueryId query_id, Status status) {
+ if (is_closed_) {
+ return;
+ }
+
Query query;
bool was_active;
std::tie(query, was_active) = finish_query(query_id);
@@ -2234,7 +3866,7 @@ void FileManager::on_error(QueryId query_id, Status status) {
return;
}
- if (query.type_ == Query::UploadByHash) {
+ if (query.type_ == Query::Type::UploadByHash && !G()->close_flag()) {
LOG(INFO) << "Upload By Hash failed: " << status << ", restart upload";
node->get_by_hash_ = false;
run_upload(node, {});
@@ -2243,34 +3875,87 @@ void FileManager::on_error(QueryId query_id, Status status) {
on_error_impl(node, query.type_, was_active, std::move(status));
}
-void FileManager::on_error_impl(FileNodePtr node, FileManager::Query::Type type, bool was_active, Status status) {
+void FileManager::on_error_impl(FileNodePtr node, Query::Type type, bool was_active, Status status) {
SCOPE_EXIT {
- try_flush_node(node);
+ try_flush_node(node, "on_error_impl");
};
- if (status.code() != 1) {
- LOG(WARNING) << "Failed to upload/download/generate file: " << status << ". Query type = " << type
- << ". File type is " << file_type_name[static_cast<int32>(FileView(node).get_type())];
- if (status.code() == 0) {
- // Remove partial locations
- if (node->local_.type() == LocalFileLocation::Type::Partial && status.message() != "FILE_UPLOAD_RESTART") {
- LOG(INFO) << "Unlink file " << node->local_.partial().path_;
- unlink(node->local_.partial().path_).ignore();
- node->set_local_location(LocalFileLocation(), 0);
- }
- if (node->remote_.type() == RemoteFileLocation::Type::Partial) {
- node->set_remote_location(RemoteFileLocation(), FileLocationSource::None, 0);
+
+ if (status.message() == "FILE_PART_INVALID") {
+ bool has_partial_small_location = node->remote_.partial && !node->remote_.partial->is_big_;
+ FileView file_view(node);
+ auto expected_size = file_view.expected_size(true);
+ bool should_be_big_location = is_file_big(file_view.get_type(), expected_size);
+
+ node->delete_partial_remote_location();
+ if (has_partial_small_location && should_be_big_location) {
+ run_upload(node, {});
+ return;
+ }
+
+ LOG(ERROR) << "Failed to upload file " << node->main_file_id_ << ": unexpected " << status
+ << ", is_small = " << has_partial_small_location << ", should_be_big = " << should_be_big_location
+ << ", expected size = " << expected_size;
+ }
+
+ if (begins_with(status.message(), "FILE_GENERATE_LOCATION_INVALID")) {
+ node->set_generate_location(nullptr);
+ }
+
+ if ((status.message() == "FILE_ID_INVALID" || status.message() == "LOCATION_INVALID") &&
+ FileView(node).may_reload_photo()) {
+ node->need_reload_photo_ = true;
+ run_download(node, true);
+ return;
+ }
+
+ if (FileReferenceManager::is_file_reference_error(status)) {
+ string file_reference;
+ Slice prefix = "#BASE64";
+ Slice error_message = status.message();
+ auto pos = error_message.rfind('#');
+ if (pos < error_message.size() && begins_with(error_message.substr(pos), prefix)) {
+ auto r_file_reference = base64_decode(error_message.substr(pos + prefix.size()));
+ if (r_file_reference.is_ok()) {
+ file_reference = r_file_reference.move_as_ok();
+ } else {
+ LOG(ERROR) << "Can't decode file reference from error " << status << ": " << r_file_reference.error();
}
- status = Status::Error(400, status.message());
+ } else {
+ LOG(ERROR) << "Unexpected error, file_reference will be deleted just in case " << status;
}
+ CHECK(!node->file_ids_.empty());
+ delete_file_reference(node->file_ids_.back(), file_reference);
+ run_download(node, true);
+ return;
}
- if (status.message() == "FILE_UPLOAD_RESTART") {
+ if (begins_with(status.message(), "FILE_UPLOAD_RESTART")) {
+ if (ends_with(status.message(), "WITH_FILE_REFERENCE")) {
+ node->upload_was_update_file_reference_ = true;
+ }
run_upload(node, {});
return;
}
- if (status.message() == "FILE_DOWNLOAD_RESTART") {
- node->can_search_locally_ = false;
- run_download(node);
+
+ if (begins_with(status.message(), "FILE_DOWNLOAD_RESTART")) {
+ if (ends_with(status.message(), "WITH_FILE_REFERENCE")) {
+ node->download_was_update_file_reference_ = true;
+ run_download(node, true);
+ return;
+ } else if (ends_with(status.message(), "INCREASE_PART_SIZE")) {
+ if (try_fix_partial_local_location(node)) {
+ run_download(node, true);
+ return;
+ }
+ } else {
+ node->can_search_locally_ = false;
+ run_download(node, true);
+ return;
+ }
+ }
+
+ if (status.message() == "MTPROTO_CLUSTER_INVALID") {
+ run_download(node, true);
return;
}
@@ -2278,10 +3963,41 @@ void FileManager::on_error_impl(FileNodePtr node, FileManager::Query::Type type,
return;
}
+ if (G()->close_flag() && status.code() < 400) {
+ status = Global::request_aborted_error();
+ } else {
+ if (status.code() != -1) {
+ if (type == Query::Type::Generate && node->generate_ != nullptr) {
+ LOG(WARNING) << "Failed to generate file " << node->main_file_id_ << " with " << *node->generate_ << ": "
+ << status;
+ } else {
+ LOG(WARNING) << "Failed to " << type << " file " << node->main_file_id_ << " of type "
+ << FileView(node).get_type() << ": " << status;
+ }
+ }
+ if (status.code() == 0) {
+ // Remove partial locations
+ if (node->local_.type() == LocalFileLocation::Type::Partial &&
+ !begins_with(status.message(), "FILE_DOWNLOAD_ID_INVALID") &&
+ !begins_with(status.message(), "FILE_DOWNLOAD_LIMIT")) {
+ CSlice path = node->local_.partial().path_;
+ if (begins_with(path, get_files_temp_dir(FileType::SecureDecrypted)) ||
+ begins_with(path, get_files_temp_dir(FileType::Video))) {
+ LOG(INFO) << "Unlink file " << path;
+ send_closure(file_load_manager_, &FileLoadManager::unlink_file, std::move(node->local_.partial().path_),
+ Promise<Unit>());
+ node->drop_local_location();
+ }
+ }
+ node->delete_partial_remote_location();
+ }
+ status = Status::Error(400, status.message());
+ }
+
// Stop everything on error
- cancel_generate(node);
- cancel_download(node);
- cancel_upload(node);
+ do_cancel_generate(node);
+ do_cancel_download(node);
+ do_cancel_upload(node);
for (auto file_id : vector<FileId>(node->file_ids_)) {
auto *info = get_file_id_info(file_id);
@@ -2323,12 +4039,14 @@ std::pair<FileManager::Query, bool> FileManager::finish_query(QueryId query_id)
}
if (node->download_id_ == query_id) {
node->download_id_ = 0;
+ node->download_was_update_file_reference_ = false;
node->is_download_started_ = false;
node->set_download_priority(0);
was_active = true;
}
if (node->upload_id_ == query_id) {
node->upload_id_ = 0;
+ node->upload_was_update_file_reference_ = false;
node->set_upload_priority(0);
was_active = true;
}
@@ -2342,15 +4060,42 @@ FullRemoteFileLocation *FileManager::get_remote(int32 key) {
return &remote_location_info_.get(key).remote_;
}
+Result<string> FileManager::get_suggested_file_name(FileId file_id, const string &directory) {
+ if (!file_id.is_valid()) {
+ return Status::Error(400, "Invalid file identifier");
+ }
+ auto node = get_sync_file_node(file_id);
+ if (!node) {
+ return Status::Error(400, "Wrong file identifier");
+ }
+
+ return ::td::get_suggested_file_name(directory, PathView(node->suggested_path()).file_name());
+}
+
void FileManager::hangup() {
file_db_.reset();
file_generate_manager_.reset();
file_load_manager_.reset();
+ while (!queries_container_.empty()) {
+ auto ids = queries_container_.ids();
+ for (auto id : ids) {
+ on_error(id, Global::request_aborted_error());
+ }
+ }
+ is_closed_ = true;
stop();
}
void FileManager::tear_down() {
parent_.reset();
+
+ LOG(DEBUG) << "Have " << file_id_info_.size() << " files with " << file_nodes_.size() << " file nodes, "
+ << local_location_to_file_id_.size() << " local locations and " << remote_location_info_.size()
+ << " remote locations to free";
}
+constexpr int64 FileManager::KEEP_DOWNLOAD_LIMIT;
+constexpr int64 FileManager::KEEP_DOWNLOAD_OFFSET;
+constexpr int64 FileManager::IGNORE_DOWNLOAD_LIMIT;
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.h
index 10f961c213..b1b90cdc14 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.h
@@ -1,28 +1,41 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-#include "td/telegram/telegram_api.h"
-
#include "td/telegram/DialogId.h"
-#include "td/telegram/files/FileDb.h"
+#include "td/telegram/files/FileDbId.h"
+#include "td/telegram/files/FileEncryptionKey.h"
#include "td/telegram/files/FileGenerateManager.h"
#include "td/telegram/files/FileId.h"
+#include "td/telegram/files/FileLoaderUtils.h"
#include "td/telegram/files/FileLoadManager.h"
#include "td/telegram/files/FileLocation.h"
-#include "td/telegram/files/FileStats.h"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/files/FileType.h"
+#include "td/telegram/Location.h"
+#include "td/telegram/PhotoSizeSource.h"
+#include "td/telegram/td_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/actor/actor.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/Container.h"
#include "td/utils/Enumerator.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/logging.h"
+#include "td/utils/optional.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/WaitFreeHashMap.h"
+#include "td/utils/WaitFreeVector.h"
#include <map>
#include <memory>
@@ -31,11 +44,31 @@
namespace td {
-enum class FileLocationSource : int8 { None, FromUser, FromDb, FromServer };
+extern int VERBOSITY_NAME(update_file);
+
+class FileData;
+class FileDbInterface;
+
+enum class FileLocationSource : int8 { None, FromUser, FromBinlog, FromDatabase, FromServer };
+
+struct NewRemoteFileLocation {
+ NewRemoteFileLocation() = default;
+ NewRemoteFileLocation(RemoteFileLocation remote, FileLocationSource source);
+ RemoteFileLocation partial_or_empty() const;
+ unique_ptr<PartialRemoteFileLocation> partial;
+
+ //TODO: use RemoteId
+ // hardest part is to determine whether we should flush this location to db.
+ // probably, will need some generation in RemoteInfo
+ optional<FullRemoteFileLocation> full;
+ bool is_full_alive{false}; // if false, then we may try to upload this file
+ FileLocationSource full_source{FileLocationSource::None};
+ int64 ready_size = 0;
+};
class FileNode {
public:
- FileNode(LocalFileLocation local, RemoteFileLocation remote, unique_ptr<FullGenerateFileLocation> generate,
+ FileNode(LocalFileLocation local, NewRemoteFileLocation remote, unique_ptr<FullGenerateFileLocation> generate,
int64 size, int64 expected_size, string remote_name, string url, DialogId owner_dialog_id,
FileEncryptionKey key, FileId main_file_id, int8 main_file_id_priority)
: local_(std::move(local))
@@ -49,9 +82,16 @@ class FileNode {
, encryption_key_(std::move(key))
, main_file_id_(main_file_id)
, main_file_id_priority_(main_file_id_priority) {
+ init_ready_size();
}
- void set_local_location(const LocalFileLocation &local, int64 ready_size);
- void set_remote_location(const RemoteFileLocation &remote, FileLocationSource source, int64 ready_size);
+ void drop_local_location();
+ void set_local_location(const LocalFileLocation &local, int64 ready_size, int64 prefix_offset,
+ int64 ready_prefix_size);
+ void set_new_remote_location(NewRemoteFileLocation remote);
+ void delete_partial_remote_location();
+ void set_partial_remote_location(PartialRemoteFileLocation remote, int64 ready_size);
+
+ bool delete_file_reference(Slice file_reference);
void set_generate_location(unique_ptr<FullGenerateFileLocation> &&generate);
void set_size(int64 size);
void set_expected_size(int64 expected_size);
@@ -59,11 +99,16 @@ class FileNode {
void set_url(string url);
void set_owner_dialog_id(DialogId owner_id);
void set_encryption_key(FileEncryptionKey key);
+ void set_upload_pause(FileId upload_pause);
void set_download_priority(int8 priority);
void set_upload_priority(int8 priority);
void set_generate_priority(int8 download_priority, int8 upload_priority);
+ void set_download_offset(int64 download_offset);
+ void set_download_limit(int64 download_limit);
+ void set_ignore_download_limit(bool ignore_download_limit);
+
void on_changed();
void on_info_changed();
void on_pmc_changed();
@@ -74,19 +119,28 @@ class FileNode {
void on_pmc_flushed();
void on_info_flushed();
- string suggested_name() const;
+ int64 get_download_limit() const;
+
+ string suggested_path() const;
private:
friend class FileView;
friend class FileManager;
+ static constexpr char PERSISTENT_ID_VERSION_OLD = 2;
+ static constexpr char PERSISTENT_ID_VERSION_GENERATED = 3;
+ static constexpr char PERSISTENT_ID_VERSION = 4;
+
LocalFileLocation local_;
FileLoadManager::QueryId upload_id_ = 0;
- int64 local_ready_size_ = 0;
+ int64 download_offset_ = 0;
+ int64 private_download_limit_ = 0;
+ int64 local_ready_size_ = 0; // PartialLocal only
+ int64 local_ready_prefix_size_ = 0; // PartialLocal only
+
+ NewRemoteFileLocation remote_;
- RemoteFileLocation remote_;
FileLoadManager::QueryId download_id_ = 0;
- int64 remote_ready_size_ = 0;
unique_ptr<FullGenerateFileLocation> generate_;
FileLoadManager::QueryId generate_id_ = 0;
@@ -97,12 +151,15 @@ class FileNode {
string url_;
DialogId owner_dialog_id_;
FileEncryptionKey encryption_key_;
- FileDbId pmc_id_ = 0;
- std::vector<FileId> file_ids_;
+ FileDbId pmc_id_;
+ vector<FileId> file_ids_;
FileId main_file_id_;
+ double last_successful_force_reupload_time_ = -1e10;
+
FileId upload_pause_;
+
int8 upload_priority_ = 0;
int8 download_priority_ = 0;
int8 generate_priority_ = 0;
@@ -112,10 +169,12 @@ class FileNode {
int8 main_file_id_priority_ = 0;
- FileLocationSource remote_source_ = FileLocationSource::FromUser;
+ bool is_download_offset_dirty_ = false;
+ bool is_download_limit_dirty_ = false;
- bool get_by_hash_ = false;
+ bool get_by_hash_{false};
bool can_search_locally_{true};
+ bool need_reload_photo_{false};
bool is_download_started_ = false;
bool generate_was_update_ = false;
@@ -124,6 +183,18 @@ class FileNode {
bool pmc_changed_flag_{false};
bool info_changed_flag_{false};
+
+ bool upload_was_update_file_reference_{false};
+ bool download_was_update_file_reference_{false};
+
+ bool upload_prefer_small_{false};
+
+ bool ignore_download_limit_{false};
+
+ void init_ready_size();
+
+ void recalc_ready_prefix_size(int64 prefix_offset, int64 ready_prefix_size);
+ void update_effective_download_limit(int64 old_download_limit);
};
class FileManager;
@@ -138,7 +209,7 @@ class FileNodePtr {
FileNode &operator*() const;
FileNode *get() const;
FullRemoteFileLocation *get_remote() const;
- explicit operator bool() const;
+ explicit operator bool() const noexcept;
private:
FileId file_id_;
@@ -159,8 +230,8 @@ class ConstFileNodePtr {
return file_node_ptr_.operator*();
}
- explicit operator bool() const {
- return bool(file_node_ptr_);
+ explicit operator bool() const noexcept {
+ return static_cast<bool>(file_node_ptr_);
}
const FullRemoteFileLocation *get_remote() const {
return file_node_ptr_.get_remote();
@@ -180,7 +251,11 @@ class FileView {
bool has_local_location() const;
const FullLocalFileLocation &local_location() const;
bool has_remote_location() const;
+ bool has_alive_remote_location() const;
+ bool has_active_upload_remote_location() const;
+ bool has_active_download_remote_location() const;
const FullRemoteFileLocation &remote_location() const;
+ const FullRemoteFileLocation &main_remote_location() const;
bool has_generate_location() const;
const FullGenerateFileLocation &generate_location() const;
@@ -189,25 +264,29 @@ class FileView {
const string &remote_name() const;
- string suggested_name() const;
+ string suggested_path() const;
DialogId owner_dialog_id() const;
bool get_by_hash() const;
- FileId file_id() const {
+ FileId get_main_file_id() const {
return node_->main_file_id_;
}
int64 size() const;
- int64 expected_size() const;
+ int64 expected_size(bool may_guess = false) const;
bool is_downloading() const;
- int64 local_size() const;
+ int64 download_offset() const;
+ int64 downloaded_prefix(int64 offset) const;
+ int64 local_prefix_size() const;
int64 local_total_size() const;
bool is_uploading() const;
int64 remote_size() const;
string path() const;
+ int64 get_allocated_local_size() const;
+
bool can_download_from_server() const;
bool can_generate() const;
bool can_delete() const;
@@ -224,20 +303,56 @@ class FileView {
}
return FileType::Temp;
}
- bool is_encrypted() const {
+ bool is_encrypted_secret() const {
return get_type() == FileType::Encrypted;
}
+ bool is_encrypted_secure() const {
+ return get_type() == FileType::SecureEncrypted;
+ }
+ bool is_secure() const {
+ return get_type() == FileType::SecureEncrypted || get_type() == FileType::SecureDecrypted;
+ }
+ bool is_encrypted_any() const {
+ return is_encrypted_secret() || is_encrypted_secure();
+ }
+ bool is_encrypted() const {
+ return is_encrypted_secret() || is_secure();
+ }
const FileEncryptionKey &encryption_key() const {
return node_->encryption_key_;
}
+ bool may_reload_photo() const {
+ if (!has_remote_location()) {
+ return false;
+ }
+ if (!remote_location().is_photo()) {
+ return false;
+ }
+ auto type = remote_location().get_source().get_type("may_reload_photo");
+ return type != PhotoSizeSource::Type::Legacy && type != PhotoSizeSource::Type::FullLegacy &&
+ type != PhotoSizeSource::Type::Thumbnail;
+ }
+
+ string get_persistent_file_id() const;
+
+ string get_unique_file_id() const;
+
private:
ConstFileNodePtr node_{};
+
+ static string get_unique_id(const FullGenerateFileLocation &location);
+ static string get_unique_id(const FullRemoteFileLocation &location);
+
+ static string get_persistent_id(const FullGenerateFileLocation &location);
+ static string get_persistent_id(const FullRemoteFileLocation &location);
};
-class Td;
-class FileManager : public FileLoadManager::Callback {
+class FileManager final : public FileLoadManager::Callback {
public:
+ static constexpr int64 KEEP_DOWNLOAD_LIMIT = -1;
+ static constexpr int64 KEEP_DOWNLOAD_OFFSET = -1;
+ static constexpr int64 IGNORE_DOWNLOAD_LIMIT = -2;
class DownloadCallback {
public:
DownloadCallback() = default;
@@ -258,151 +373,236 @@ class FileManager : public FileLoadManager::Callback {
UploadCallback &operator=(const UploadCallback &) = delete;
virtual ~UploadCallback() = default;
- virtual void on_progress(FileId file_id) {
- }
-
// After on_upload_ok all uploads of this file will be paused till merge, delete_partial_remote_location or
// explicit upload request with the same file_id.
// Also upload may be resumed after some other merges.
virtual void on_upload_ok(FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file) = 0;
virtual void on_upload_encrypted_ok(FileId file_id, tl_object_ptr<telegram_api::InputEncryptedFile> input_file) = 0;
+ virtual void on_upload_secure_ok(FileId file_id, tl_object_ptr<telegram_api::InputSecureFile> input_file) = 0;
virtual void on_upload_error(FileId file_id, Status error) = 0;
};
class Context {
public:
- virtual void on_new_file(int64 size) = 0;
+ virtual bool need_notify_on_new_files() = 0;
+
+ virtual void on_new_file(int64 size, int64 real_size, int32 cnt) = 0;
+
virtual void on_file_updated(FileId size) = 0;
+
+ virtual bool add_file_source(FileId file_id, FileSourceId file_source_id) = 0;
+
+ virtual bool remove_file_source(FileId file_id, FileSourceId file_source_id) = 0;
+
+ virtual void on_merge_files(FileId to_file_id, FileId from_file_id) = 0;
+
+ virtual vector<FileSourceId> get_some_file_sources(FileId file_id) = 0;
+
+ virtual void repair_file_reference(FileId file_id, Promise<Unit> promise) = 0;
+
+ virtual void reload_photo(PhotoSizeSource source, Promise<Unit> promise) = 0;
+
virtual ActorShared<> create_reference() = 0;
+
Context() = default;
Context(const Context &) = delete;
Context &operator=(const Context &) = delete;
virtual ~Context() = default;
};
- explicit FileManager(std::unique_ptr<Context> context);
+ explicit FileManager(unique_ptr<Context> context);
FileManager(const FileManager &other) = delete;
FileManager &operator=(const FileManager &other) = delete;
FileManager(FileManager &&other) = delete;
FileManager &operator=(FileManager &&other) = delete;
- ~FileManager() override;
+ ~FileManager() final;
+
+ static bool is_remotely_generated_file(Slice conversion);
void init_actor();
- FileId dup_file_id(FileId file_id);
+ FileId dup_file_id(FileId file_id, const char *source);
void on_file_unlink(const FullLocalFileLocation &location);
FileId register_empty(FileType type);
Result<FileId> register_local(FullLocalFileLocation location, DialogId owner_dialog_id, int64 size,
- bool get_by_hash = false, bool force = false) TD_WARN_UNUSED_RESULT;
- FileId register_remote(const FullRemoteFileLocation &location, FileLocationSource file_location_source,
- DialogId owner_dialog_id, int64 size, int64 expected_size, string name) TD_WARN_UNUSED_RESULT;
+ bool get_by_hash = false, bool force = false, bool skip_file_size_checks = false,
+ FileId merge_file_id = FileId()) TD_WARN_UNUSED_RESULT;
+ FileId register_remote(FullRemoteFileLocation location, FileLocationSource file_location_source,
+ DialogId owner_dialog_id, int64 size, int64 expected_size,
+ string remote_name) TD_WARN_UNUSED_RESULT;
Result<FileId> register_generate(FileType file_type, FileLocationSource file_location_source, string original_path,
string conversion, DialogId owner_dialog_id,
int64 expected_size) TD_WARN_UNUSED_RESULT;
- Result<FileId> register_file(FileData data, FileLocationSource file_location_source, const char *source, bool force);
- Result<FileId> merge(FileId x_file_id, FileId y_file_id, bool no_sync = false) TD_WARN_UNUSED_RESULT;
+ Status merge(FileId x_file_id, FileId y_file_id, bool no_sync = false);
+
+ void add_file_source(FileId file_id, FileSourceId file_source_id);
+
+ void remove_file_source(FileId file_id, FileSourceId file_source_id);
+
+ void change_files_source(FileSourceId file_source_id, const vector<FileId> &old_file_ids,
+ const vector<FileId> &new_file_ids);
+
+ void on_file_reference_repaired(FileId file_id, FileSourceId file_source_id, Result<Unit> &&result,
+ Promise<Unit> &&promise);
bool set_encryption_key(FileId file_id, FileEncryptionKey key);
bool set_content(FileId file_id, BufferSlice bytes);
- void download(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority);
+ void check_local_location(FileId file_id, bool skip_file_size_checks);
+
+ void download(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority, int64 offset,
+ int64 limit, Promise<td_api::object_ptr<td_api::file>> promise);
void upload(FileId file_id, std::shared_ptr<UploadCallback> callback, int32 new_priority, uint64 upload_order);
void resume_upload(FileId file_id, std::vector<int> bad_parts, std::shared_ptr<UploadCallback> callback,
- int32 new_priority, uint64 upload_order);
+ int32 new_priority, uint64 upload_order, bool force = false, bool prefer_small = false);
+ void cancel_upload(FileId file_id);
bool delete_partial_remote_location(FileId file_id);
+ void delete_file_reference(FileId file_id, Slice file_reference);
void get_content(FileId file_id, Promise<BufferSlice> promise);
+ Result<string> get_suggested_file_name(FileId file_id, const string &directory);
+
+ void read_file_part(FileId file_id, int64 offset, int64 count, int left_tries,
+ Promise<td_api::object_ptr<td_api::filePart>> promise);
+
void delete_file(FileId file_id, Promise<Unit> promise, const char *source);
- void external_file_generate_progress(int64 id, int32 expected_size, int32 local_prefix_size, Promise<> promise);
+ void external_file_generate_write_part(int64 id, int64 offset, string data, Promise<> promise);
+ void external_file_generate_progress(int64 id, int64 expected_size, int64 local_prefix_size, Promise<> promise);
void external_file_generate_finish(int64 id, Status status, Promise<> promise);
- static constexpr char PERSISTENT_ID_VERSION = 2;
- Result<string> to_persistent_id(FileId file_id) TD_WARN_UNUSED_RESULT;
Result<FileId> from_persistent_id(CSlice persistent_id, FileType file_type) TD_WARN_UNUSED_RESULT;
FileView get_file_view(FileId file_id) const;
FileView get_sync_file_view(FileId file_id);
- tl_object_ptr<td_api::file> get_file_object(FileId file_id, bool with_main_file_id = true);
+ td_api::object_ptr<td_api::file> get_file_object(FileId file_id, bool with_main_file_id = true);
+ vector<int32> get_file_ids_object(const vector<FileId> &file_ids, bool with_main_file_id = true);
- Result<FileId> get_input_thumbnail_file_id(const tl_object_ptr<td_api::InputFile> &thumb_input_file,
+ Result<FileId> get_input_thumbnail_file_id(const tl_object_ptr<td_api::InputFile> &thumbnail_input_file,
DialogId owner_dialog_id, bool is_encrypted) TD_WARN_UNUSED_RESULT;
Result<FileId> get_input_file_id(FileType type, const tl_object_ptr<td_api::InputFile> &file,
DialogId owner_dialog_id, bool allow_zero, bool is_encrypted,
- bool get_by_hash = false) TD_WARN_UNUSED_RESULT;
+ bool get_by_hash = false, bool is_secure = false,
+ bool force_reuse = false) TD_WARN_UNUSED_RESULT;
+
+ Result<FileId> get_map_thumbnail_file_id(Location location, int32 zoom, int32 width, int32 height, int32 scale,
+ DialogId owner_dialog_id) TD_WARN_UNUSED_RESULT;
+
+ Result<FileId> get_audio_thumbnail_file_id(string title, string performer, bool is_small,
+ DialogId owner_dialog_id) TD_WARN_UNUSED_RESULT;
+
+ FileType guess_file_type(const tl_object_ptr<td_api::InputFile> &file);
vector<tl_object_ptr<telegram_api::InputDocument>> get_input_documents(const vector<FileId> &file_ids);
- template <class T>
- void store_file(FileId file_id, T &storer, int32 ttl = 5) const;
+ static bool extract_was_uploaded(const tl_object_ptr<telegram_api::InputMedia> &input_media);
+ static bool extract_was_thumbnail_uploaded(const tl_object_ptr<telegram_api::InputMedia> &input_media);
+ static string extract_file_reference(const tl_object_ptr<telegram_api::InputMedia> &input_media);
+
+ static string extract_file_reference(const tl_object_ptr<telegram_api::InputDocument> &input_document);
- template <class T>
- FileId parse_file(T &parser);
+ static string extract_file_reference(const tl_object_ptr<telegram_api::InputPhoto> &input_photo);
+
+ static bool extract_was_uploaded(const tl_object_ptr<telegram_api::InputChatPhoto> &input_chat_photo);
+ static string extract_file_reference(const tl_object_ptr<telegram_api::InputChatPhoto> &input_chat_photo);
+
+ template <class StorerT>
+ void store_file(FileId file_id, StorerT &storer, int32 ttl = 5) const;
+
+ template <class ParserT>
+ FileId parse_file(ParserT &parser);
private:
- Result<FileId> check_input_file_id(FileType type, Result<FileId> result, bool is_encrypted,
- bool allow_zero) TD_WARN_UNUSED_RESULT;
+ Result<FileId> check_input_file_id(FileType type, Result<FileId> result, bool is_encrypted, bool allow_zero,
+ bool is_secure) TD_WARN_UNUSED_RESULT;
FileId register_url(string url, FileType file_type, FileLocationSource file_location_source,
DialogId owner_dialog_id);
+ Result<FileId> register_file(FileData &&data, FileLocationSource file_location_source, FileId merge_file_id,
+ const char *source, bool force, bool skip_file_size_checks = false);
static constexpr int8 FROM_BYTES_PRIORITY = 10;
+
using FileNodeId = int32;
+
using QueryId = FileLoadManager::QueryId;
class Query {
public:
FileId file_id_;
- enum Type { UploadByHash, Upload, Download, SetContent, Generate } type_;
+ enum class Type : int32 {
+ UploadByHash,
+ UploadWaitFileReference,
+ Upload,
+ DownloadWaitFileReference,
+ DownloadReloadDialog,
+ Download,
+ SetContent,
+ Generate
+ } type_;
};
+
+ friend StringBuilder &operator<<(StringBuilder &string_builder, Query::Type type);
+
struct FileIdInfo {
FileNodeId node_id_{0};
bool send_updates_flag_{false};
bool pin_flag_{false};
+ bool sent_file_id_flag_{false};
+ bool ignore_download_limit{false};
int8 download_priority_{0};
int8 upload_priority_{0};
- uint64 upload_order_;
+ uint64 upload_order_{0};
std::shared_ptr<DownloadCallback> download_callback_;
std::shared_ptr<UploadCallback> upload_callback_;
};
+ class ForceUploadActor;
+
ActorShared<> parent_;
- std::unique_ptr<Context> context_;
+ unique_ptr<Context> context_;
std::shared_ptr<FileDbInterface> file_db_;
FileIdInfo *get_file_id_info(FileId file_id);
struct RemoteInfo {
- // mutible is set to to enable changing access hash
+ // mutable is set to to enable changing of access hash
mutable FullRemoteFileLocation remote_;
+ mutable FileLocationSource file_location_source_;
FileId file_id_;
bool operator==(const RemoteInfo &other) const {
- return this->remote_ == other.remote_;
+ return remote_ == other.remote_;
}
bool operator<(const RemoteInfo &other) const {
- return this->remote_ < other.remote_;
+ return remote_ < other.remote_;
}
};
Enumerator<RemoteInfo> remote_location_info_;
+ WaitFreeHashMap<string, FileId> file_hash_to_file_id_;
+
std::map<FullLocalFileLocation, FileId> local_location_to_file_id_;
std::map<FullGenerateFileLocation, FileId> generate_location_to_file_id_;
std::map<FileDbId, int32> pmc_id_to_file_node_id_;
- vector<FileIdInfo> file_id_info_;
- vector<int32> empty_file_ids_;
- vector<std::unique_ptr<FileNode>> file_nodes_;
+ WaitFreeVector<FileIdInfo> file_id_info_;
+ WaitFreeVector<int32> empty_file_ids_;
+ WaitFreeVector<unique_ptr<FileNode>> file_nodes_;
ActorOwn<FileLoadManager> file_load_manager_;
ActorOwn<FileGenerateManager> file_generate_manager_;
Container<Query> queries_container_;
+ bool is_closed_ = false;
+
std::set<std::string> bad_paths_;
+ int file_node_size_warning_exp_ = 10;
+
FileId next_file_id();
FileNodeId next_file_node_id();
int32 next_pmc_file_id();
@@ -417,18 +617,34 @@ class FileManager : public FileLoadManager::Callback {
void load_from_pmc_result(FileId file_id, Result<FileData> &&result);
FileId register_pmc_file_data(FileData &&data);
- Status check_local_location(FileNodePtr node);
- Status check_local_location(FullLocalFileLocation &location, int64 &size);
- void try_flush_node(FileNodePtr node, bool new_remote = false, bool new_local = false, bool new_generate = false,
- FileDbId other_pmc_id = Auto());
- void try_flush_node_info(FileNodePtr node);
+ void download_impl(FileId file_id, std::shared_ptr<DownloadCallback> callback, int32 new_priority, int64 offset,
+ int64 limit, Status check_status, Promise<td_api::object_ptr<td_api::file>> promise);
+
+ Status check_local_location(FileNodePtr node, bool skip_file_size_checks);
+ void on_failed_check_local_location(FileNodePtr node);
+ void check_local_location_async(FileNodePtr node, bool skip_file_size_checks, Promise<Unit> promise);
+ void on_check_full_local_location(FileId file_id, LocalFileLocation checked_location,
+ Result<FullLocalLocationInfo> r_info, Promise<Unit> promise);
+ void on_check_partial_local_location(FileId file_id, LocalFileLocation checked_location, Result<Unit> result,
+ Promise<Unit> promise);
+ void recheck_full_local_location(FullLocalLocationInfo location_info, bool skip_file_size_checks);
+ void on_recheck_full_local_location(FullLocalFileLocation checked_location, Result<FullLocalLocationInfo> r_info);
+
+ static bool try_fix_partial_local_location(FileNodePtr node);
+ void try_flush_node_full(FileNodePtr node, bool new_remote, bool new_local, bool new_generate, FileDbId other_pmc_id);
+ void try_flush_node(FileNodePtr node, const char *source);
+ void try_flush_node_info(FileNodePtr node, const char *source);
+ void try_flush_node_pmc(FileNodePtr node, const char *source);
void clear_from_pmc(FileNodePtr node);
- void flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate);
+ void flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate, const char *source);
void load_from_pmc(FileNodePtr node, bool new_remote, bool new_local, bool new_generate);
- string get_persistent_id(const FullRemoteFileLocation &location);
+ Result<FileId> from_persistent_id_generated(Slice binary, FileType file_type);
+ Result<FileId> from_persistent_id_v2(Slice binary, FileType file_type);
+ Result<FileId> from_persistent_id_v3(Slice binary, FileType file_type);
+ Result<FileId> from_persistent_id_v23(Slice binary, FileType file_type, int32 version);
- string fix_file_extension(Slice file_name, Slice file_type, Slice file_extension);
+ static string fix_file_extension(Slice file_name, Slice file_type, Slice file_extension);
string get_file_name(FileType file_type, Slice path);
ConstFileNodePtr get_file_node(FileId file_id) const {
@@ -441,35 +657,41 @@ class FileManager : public FileLoadManager::Callback {
FileNodePtr get_sync_file_node(FileId file_id);
+ void on_force_reupload_success(FileId file_id);
+
// void release_file_node(FileNodeId id);
- void cancel_download(FileNodePtr node);
- void cancel_upload(FileNodePtr node);
- void cancel_generate(FileNodePtr node);
- void run_upload(FileNodePtr node, std::vector<int> bad_parts);
- void run_download(FileNodePtr node);
+ void do_cancel_download(FileNodePtr node);
+ void do_cancel_upload(FileNodePtr node);
+ void do_cancel_generate(FileNodePtr node);
+ void run_upload(FileNodePtr node, vector<int> bad_parts);
+ void run_download(FileNodePtr node, bool force_update_priority);
void run_generate(FileNodePtr node);
- void on_start_download(QueryId query_id) override;
- void on_partial_download(QueryId query_id, const PartialLocalFileLocation &partial_local, int64 ready_size) override;
- void on_partial_upload(QueryId query_id, const PartialRemoteFileLocation &partial_remote, int64 ready_size) override;
- void on_download_ok(QueryId query_id, const FullLocalFileLocation &local, int64 size) override;
- void on_upload_ok(QueryId query_id, FileType file_type, const PartialRemoteFileLocation &partial_remote,
- int64 size) override;
- void on_upload_full_ok(QueryId query_id, const FullRemoteFileLocation &remote) override;
- void on_error(QueryId query_id, Status status) override;
+ void on_start_download(QueryId query_id) final;
+ void on_partial_download(QueryId query_id, PartialLocalFileLocation partial_local, int64 ready_size,
+ int64 size) final;
+ void on_hash(QueryId query_id, string hash) final;
+ void on_partial_upload(QueryId query_id, PartialRemoteFileLocation partial_remote, int64 ready_size) final;
+ void on_download_ok(QueryId query_id, FullLocalFileLocation local, int64 size, bool is_new) final;
+ void on_upload_ok(QueryId query_id, FileType file_type, PartialRemoteFileLocation partial_remote, int64 size) final;
+ void on_upload_full_ok(QueryId query_id, FullRemoteFileLocation remote) final;
+ void on_error(QueryId query_id, Status status) final;
void on_error_impl(FileNodePtr node, Query::Type type, bool was_active, Status status);
- void on_partial_generate(QueryId, const PartialLocalFileLocation &partial_local, int32 expected_size);
- void on_generate_ok(QueryId, const FullLocalFileLocation &local);
+ void on_partial_generate(QueryId, PartialLocalFileLocation partial_local, int64 expected_size);
+ void on_generate_ok(QueryId, FullLocalFileLocation local);
std::pair<Query, bool> finish_query(QueryId query_id);
FullRemoteFileLocation *get_remote(int32 key);
- void hangup() override;
- void tear_down() override;
+ FlatHashSet<FileId, FileIdHash> get_main_file_ids(const vector<FileId> &file_ids);
+
+ void hangup() final;
+ void tear_down() final;
friend class FileNodePtr;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.hpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.hpp
index 28f481308e..a41e78d68d 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.hpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileManager.hpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,14 +7,21 @@
#pragma once
#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileEncryptionKey.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileLocation.hpp"
#include "td/telegram/files/FileManager.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Version.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/tl_helpers.h"
+#include <limits>
+
namespace td {
enum class FileStoreType : int32 { Empty, Url, Generate, Local, Remote };
@@ -26,12 +33,12 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const {
if (file_view.empty() || ttl <= 0) {
} else if (file_view.has_remote_location()) {
file_store_type = FileStoreType::Remote;
- } else if (file_view.has_local_location()) {
- file_store_type = FileStoreType::Local;
} else if (file_view.has_url()) {
file_store_type = FileStoreType::Url;
} else if (file_view.has_generate_location()) {
file_store_type = FileStoreType::Generate;
+ } else if (file_view.has_local_location()) {
+ file_store_type = FileStoreType::Local;
}
store(file_store_type, storer);
@@ -39,11 +46,22 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const {
bool has_encryption_key = false;
bool has_expected_size =
file_store_type == FileStoreType::Remote && file_view.size() == 0 && file_view.expected_size() != 0;
+ bool has_secure_key = false;
+ int64 size = 0;
+ bool has_64bit_size = false;
if (file_store_type != FileStoreType::Empty) {
- has_encryption_key = !file_view.empty() && file_view.is_encrypted();
+ has_encryption_key = !file_view.empty() && file_view.is_encrypted_secret();
+ has_secure_key = !file_view.empty() && file_view.is_encrypted_secure();
+ if (file_store_type != FileStoreType::Url) {
+ size = has_expected_size || file_store_type == FileStoreType::Generate ? file_view.expected_size()
+ : file_view.size();
+ has_64bit_size = (size > std::numeric_limits<int32>::max());
+ }
BEGIN_STORE_FLAGS();
STORE_FLAG(has_encryption_key);
STORE_FLAG(has_expected_size);
+ STORE_FLAG(has_secure_key);
+ STORE_FLAG(has_64bit_size);
END_STORE_FLAGS();
}
@@ -57,10 +75,10 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const {
break;
case FileStoreType::Remote: {
store(file_view.remote_location(), storer);
- if (has_expected_size) {
- store(narrow_cast<int32>(file_view.expected_size()), storer);
+ if (has_64bit_size) {
+ store(size, storer);
} else {
- store(narrow_cast<int32>(file_view.size()), storer);
+ store(narrow_cast<int32>(size), storer);
}
store(file_view.remote_name(), storer);
store(file_view.owner_dialog_id(), storer);
@@ -68,7 +86,11 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const {
}
case FileStoreType::Local: {
store(file_view.local_location(), storer);
- store(narrow_cast<int32>(file_view.size()), storer);
+ if (has_64bit_size) {
+ store(size, storer);
+ } else {
+ store(narrow_cast<int32>(size), storer);
+ }
store(static_cast<int32>(file_view.get_by_hash()), storer);
store(file_view.owner_dialog_id(), storer);
break;
@@ -87,8 +109,12 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const {
have_file_id = true;
}
store(generate_location, storer);
- store(static_cast<int32>(0), storer); // expected_size
- store(static_cast<int32>(0), storer);
+ if (has_64bit_size) {
+ store(size, storer);
+ } else {
+ store(narrow_cast<int32>(size), storer);
+ store(static_cast<int32>(0), storer); // legacy
+ }
store(file_view.owner_dialog_id(), storer);
if (have_file_id) {
@@ -96,8 +122,10 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const {
}
break;
}
+ default:
+ UNREACHABLE();
}
- if (has_encryption_key) {
+ if (has_encryption_key || has_secure_key) {
store(file_view.encryption_key(), storer);
}
}
@@ -113,11 +141,15 @@ FileId FileManager::parse_file(ParserT &parser) {
bool has_encryption_key = false;
bool has_expected_size = false;
+ bool has_secure_key = false;
+ bool has_64bit_size = false;
if (file_store_type != FileStoreType::Empty) {
if (parser.version() >= static_cast<int32>(Version::StoreFileEncryptionKey)) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_encryption_key);
PARSE_FLAG(has_expected_size);
+ PARSE_FLAG(has_secure_key);
+ PARSE_FLAG(has_64bit_size);
END_PARSE_FLAGS();
}
}
@@ -129,27 +161,42 @@ FileId FileManager::parse_file(ParserT &parser) {
case FileStoreType::Remote: {
FullRemoteFileLocation full_remote_location;
parse(full_remote_location, parser);
- int32 size = 0;
- int32 expected_size = 0;
- if (has_expected_size) {
- parse(expected_size, parser);
+ int64 stored_size;
+ if (has_64bit_size) {
+ parse(stored_size, parser);
} else {
- parse(size, parser);
+ int32 int_size;
+ parse(int_size, parser);
+ stored_size = int_size;
+ if (stored_size < 0) {
+ stored_size += static_cast<int64>(1) << 32;
+ }
}
+ int64 size = has_expected_size ? 0 : stored_size;
+ int64 expected_size = has_expected_size ? stored_size : 0;
string name;
parse(name, parser);
DialogId owner_dialog_id;
if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
parse(owner_dialog_id, parser);
}
- return register_remote(full_remote_location, FileLocationSource::FromDb, owner_dialog_id, size, expected_size,
- name);
+ return register_remote(full_remote_location, FileLocationSource::FromBinlog, owner_dialog_id, size,
+ expected_size, name);
}
case FileStoreType::Local: {
FullLocalFileLocation full_local_location;
parse(full_local_location, parser);
- int32 size;
- parse(size, parser);
+ int64 size;
+ if (has_64bit_size) {
+ parse(size, parser);
+ } else {
+ int32 int_size;
+ parse(int_size, parser);
+ size = int_size;
+ if (size < 0) {
+ size += static_cast<int64>(1) << 32;
+ }
+ }
int32 get_by_hash;
parse(get_by_hash, parser);
DialogId owner_dialog_id;
@@ -160,16 +207,26 @@ FileId FileManager::parse_file(ParserT &parser) {
if (r_file_id.is_ok()) {
return r_file_id.move_as_ok();
}
- LOG(ERROR) << "Can't resend local file " << full_local_location.path_;
+ LOG(ERROR) << "Can't resend local file " << full_local_location << " of size " << size << " owned by "
+ << owner_dialog_id;
return register_empty(full_local_location.file_type_);
}
case FileStoreType::Generate: {
FullGenerateFileLocation full_generated_location;
parse(full_generated_location, parser);
- int32 expected_size;
- parse(expected_size, parser); // expected_size
- int32 zero;
- parse(zero, parser);
+ int64 expected_size;
+ if (has_64bit_size) {
+ parse(expected_size, parser);
+ } else {
+ int32 int_size;
+ parse(int_size, parser);
+ expected_size = int_size;
+ if (expected_size < 0) {
+ expected_size += static_cast<int64>(1) << 32;
+ }
+ int32 zero;
+ parse(zero, parser);
+ }
DialogId owner_dialog_id;
if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
parse(owner_dialog_id, parser);
@@ -183,11 +240,11 @@ FileId FileManager::parse_file(ParserT &parser) {
if (file_id.empty()) {
return register_empty(full_generated_location.file_type_);
}
- auto download_file_id = dup_file_id(file_id);
+ auto download_file_id = dup_file_id(file_id, "parse_download_file_id");
full_generated_location.conversion_ = PSTRING() << "#file_id#" << download_file_id.get();
}
- auto r_file_id = register_generate(full_generated_location.file_type_, FileLocationSource::FromDb,
+ auto r_file_id = register_generate(full_generated_location.file_type_, FileLocationSource::FromBinlog,
full_generated_location.original_path_, full_generated_location.conversion_,
owner_dialog_id, expected_size);
if (r_file_id.is_ok()) {
@@ -204,15 +261,16 @@ FileId FileManager::parse_file(ParserT &parser) {
if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
parse(owner_dialog_id, parser);
}
- return register_url(url, type, FileLocationSource::FromDb, owner_dialog_id);
+ return register_url(url, type, FileLocationSource::FromBinlog, owner_dialog_id);
}
}
return FileId();
}();
- if (has_encryption_key) {
+ if (has_encryption_key || has_secure_key) {
+ auto key_type = has_encryption_key ? FileEncryptionKey::Type::Secret : FileEncryptionKey::Type::Secure;
FileEncryptionKey encryption_key;
- parse(encryption_key, parser);
+ encryption_key.parse(key_type, parser);
set_encryption_key(file_id, std::move(encryption_key));
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileSourceId.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileSourceId.h
new file mode 100644
index 0000000000..036e61fe0a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileSourceId.h
@@ -0,0 +1,59 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/StringBuilder.h"
+
+#include <type_traits>
+
+namespace td {
+
+class FileSourceId {
+ int32 id = 0;
+
+ public:
+ FileSourceId() = default;
+
+ explicit FileSourceId(int32 file_source_id) : id(file_source_id) {
+ }
+ template <class T1, typename = std::enable_if_t<std::is_convertible<T1, int32>::value>>
+ FileSourceId(T1 file_source_id) = delete;
+
+ bool is_valid() const {
+ return id > 0;
+ }
+
+ int32 get() const {
+ return id;
+ }
+
+ bool operator<(const FileSourceId &other) const {
+ return id < other.id;
+ }
+
+ bool operator==(const FileSourceId &other) const {
+ return id == other.id;
+ }
+
+ bool operator!=(const FileSourceId &other) const {
+ return id != other.id;
+ }
+};
+
+struct FileSourceIdHash {
+ uint32 operator()(FileSourceId file_source_id) const {
+ return Hash<int32>()(file_source_id.get());
+ }
+};
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, FileSourceId file_source_id) {
+ return string_builder << "FileSourceId(" << file_source_id.get() << ")";
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileSourceId.hpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileSourceId.hpp
new file mode 100644
index 0000000000..116a0beecc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileSourceId.hpp
@@ -0,0 +1,28 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/FileReferenceManager.h"
+#include "td/telegram/FileReferenceManager.hpp"
+#include "td/telegram/files/FileSourceId.h"
+#include "td/telegram/Td.h"
+
+namespace td {
+
+template <class StorerT>
+void store(FileSourceId file_source_id, StorerT &storer) {
+ Td *td = storer.context()->td().get_actor_unsafe();
+ td->file_reference_manager_->store_file_source(file_source_id, storer);
+}
+
+template <class ParserT>
+void parse(FileSourceId &file_source_id, ParserT &parser) {
+ Td *td = parser.context()->td().get_actor_unsafe();
+ file_source_id = td->file_reference_manager_->parse_file_source(td, parser);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileStats.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileStats.cpp
index 8c93f01ce3..c231783f7a 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileStats.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileStats.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,44 +8,54 @@
#include "td/telegram/td_api.h"
-#include "td/telegram/files/FileLoaderUtils.h"
-#include "td/telegram/Global.h"
-
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashSet.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
-#include "td/utils/Slice.h"
+#include "td/utils/misc.h"
#include <algorithm>
-#include <unordered_set>
#include <utility>
namespace td {
-tl_object_ptr<td_api::storageStatisticsFast> FileStatsFast::as_td_api() const {
- return make_tl_object<td_api::storageStatisticsFast>(size, count, db_size);
+tl_object_ptr<td_api::storageStatisticsFast> FileStatsFast::get_storage_statistics_fast_object() const {
+ return make_tl_object<td_api::storageStatisticsFast>(size, count, database_size, language_pack_database_size,
+ log_size);
}
void FileStats::add(StatByType &by_type, FileType file_type, int64 size) {
auto pos = static_cast<size_t>(file_type);
- CHECK(pos < stat_by_type.size());
+ CHECK(pos < stat_by_type_.size());
by_type[pos].size += size;
by_type[pos].cnt++;
}
-void FileStats::add(FullFileInfo &&info) {
- if (split_by_owner_dialog_id) {
- add(stat_by_owner_dialog_id[info.owner_dialog_id], info.file_type, info.size);
+void FileStats::add_impl(const FullFileInfo &info) {
+ if (split_by_owner_dialog_id_) {
+ add(stat_by_owner_dialog_id_[info.owner_dialog_id], info.file_type, info.size);
} else {
- add(stat_by_type, info.file_type, info.size);
+ add(stat_by_type_, info.file_type, info.size);
+ }
+}
+
+void FileStats::add_copy(const FullFileInfo &info) {
+ add_impl(info);
+ if (need_all_files_) {
+ all_files_.push_back(info);
}
- if (need_all_files) {
- all_files.emplace_back(std::move(info));
+}
+
+void FileStats::add(FullFileInfo &&info) {
+ add_impl(info);
+ if (need_all_files_) {
+ all_files_.push_back(std::move(info));
}
}
-FileTypeStat get_nontemp_stat(const FileStats::StatByType &by_type) {
+FileTypeStat FileStats::get_nontemp_stat(const FileStats::StatByType &by_type) {
FileTypeStat stat;
- for (size_t i = 0; i < file_type_size; i++) {
+ for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
if (FileType(i) != FileType::Temp) {
stat.size += by_type[i].size;
stat.cnt += by_type[i].cnt;
@@ -53,28 +63,30 @@ FileTypeStat get_nontemp_stat(const FileStats::StatByType &by_type) {
}
return stat;
}
+
FileTypeStat FileStats::get_total_nontemp_stat() const {
- if (!split_by_owner_dialog_id) {
- return get_nontemp_stat(stat_by_type);
+ if (!split_by_owner_dialog_id_) {
+ return get_nontemp_stat(stat_by_type_);
}
FileTypeStat stat;
- for (auto &dialog : stat_by_owner_dialog_id) {
+ for (auto &dialog : stat_by_owner_dialog_id_) {
auto tmp = get_nontemp_stat(dialog.second);
stat.size += tmp.size;
stat.cnt += tmp.cnt;
}
return stat;
}
+
void FileStats::apply_dialog_limit(int32 limit) {
if (limit == -1) {
return;
}
- if (!split_by_owner_dialog_id) {
+ if (!split_by_owner_dialog_id_) {
return;
}
std::vector<std::pair<int64, DialogId>> dialogs;
- for (auto &dialog : stat_by_owner_dialog_id) {
+ for (auto &dialog : stat_by_owner_dialog_id_) {
if (!dialog.first.is_valid()) {
continue;
}
@@ -82,7 +94,7 @@ void FileStats::apply_dialog_limit(int32 limit) {
for (auto &it : dialog.second) {
size += it.size;
}
- dialogs.push_back(std::make_pair(size, dialog.first));
+ dialogs.emplace_back(size, dialog.first);
}
size_t prefix = dialogs.size();
if (prefix > static_cast<size_t>(limit)) {
@@ -92,57 +104,71 @@ void FileStats::apply_dialog_limit(int32 limit) {
[](const auto &x, const auto &y) { return x.first > y.first; });
dialogs.resize(prefix);
- std::unordered_set<DialogId, DialogIdHash> all_dialogs;
+ apply_dialog_ids(transform(dialogs, [](const auto &dialog) { return dialog.second; }));
+}
- for (auto &dialog : dialogs) {
- all_dialogs.insert(dialog.second);
+void FileStats::apply_dialog_ids(const vector<DialogId> &dialog_ids) {
+ FlatHashSet<DialogId, DialogIdHash> all_dialog_ids;
+ for (auto &dialog_id : dialog_ids) {
+ CHECK(dialog_id.is_valid());
+ all_dialog_ids.insert(dialog_id);
}
-
StatByType other_stats;
bool other_flag = false;
- for (auto it = stat_by_owner_dialog_id.begin(); it != stat_by_owner_dialog_id.end();) {
- if (all_dialogs.count(it->first)) {
- it++;
- } else {
- for (size_t i = 0; i < file_type_size; i++) {
- other_stats[i].size += it->second[i].size;
- other_stats[i].cnt += it->second[i].cnt;
+ table_remove_if(stat_by_owner_dialog_id_, [&](const auto &it) {
+ if (!all_dialog_ids.count(it.first)) {
+ for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
+ other_stats[i].size += it.second[i].size;
+ other_stats[i].cnt += it.second[i].cnt;
}
other_flag = true;
- it = stat_by_owner_dialog_id.erase(it);
+ return true;
}
- }
+ return false;
+ });
if (other_flag) {
- DialogId other_dialog_id;
- stat_by_owner_dialog_id[other_dialog_id] = other_stats;
+ DialogId other_dialog_id; // prevents MSVC warning C4709: comma operator within array index expression
+ stat_by_owner_dialog_id_[other_dialog_id] = other_stats;
}
}
-tl_object_ptr<td_api::storageStatisticsByChat> as_td_api(DialogId dialog_id,
- const FileStats::StatByType &stat_by_type) {
+td_api::object_ptr<td_api::storageStatisticsByChat> FileStats::get_storage_statistics_by_chat_object(
+ DialogId dialog_id, const FileStats::StatByType &stat_by_type_) {
auto stats = make_tl_object<td_api::storageStatisticsByChat>(dialog_id.get(), 0, 0, Auto());
- for (size_t i = 0; i < file_type_size; i++) {
- if (stat_by_type[i].size == 0) {
+ FileStats::StatByType aggregated_stats;
+ for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
+ auto file_type = narrow_cast<size_t>(get_main_file_type(static_cast<FileType>(i)));
+ aggregated_stats[file_type].size += stat_by_type_[i].size;
+ aggregated_stats[file_type].cnt += stat_by_type_[i].cnt;
+ }
+
+ for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
+ auto size = aggregated_stats[i].size;
+ auto cnt = aggregated_stats[i].cnt;
+
+ if (size == 0) {
continue;
}
- stats->size_ += stat_by_type[i].size;
- stats->count_ += stat_by_type[i].cnt;
- stats->by_file_type_.push_back(make_tl_object<td_api::storageStatisticsByFileType>(
- as_td_api(FileType(i)), stat_by_type[i].size, stat_by_type[i].cnt));
+
+ auto file_type = static_cast<FileType>(i);
+ stats->size_ += size;
+ stats->count_ += cnt;
+ stats->by_file_type_.push_back(
+ make_tl_object<td_api::storageStatisticsByFileType>(get_file_type_object(file_type), size, cnt));
}
return stats;
}
-tl_object_ptr<td_api::storageStatistics> FileStats::as_td_api() const {
+tl_object_ptr<td_api::storageStatistics> FileStats::get_storage_statistics_object() const {
auto stats = make_tl_object<td_api::storageStatistics>(0, 0, Auto());
- if (!split_by_owner_dialog_id) {
+ if (!split_by_owner_dialog_id_) {
stats->by_chat_.reserve(1);
- stats->by_chat_.push_back(::td::as_td_api(DialogId(), stat_by_type));
+ stats->by_chat_.push_back(get_storage_statistics_by_chat_object(DialogId(), stat_by_type_));
} else {
- stats->by_chat_.reserve(stat_by_owner_dialog_id.size());
- for (auto &by_dialog : stat_by_owner_dialog_id) {
- stats->by_chat_.push_back(::td::as_td_api(by_dialog.first, by_dialog.second));
+ stats->by_chat_.reserve(stat_by_owner_dialog_id_.size());
+ for (auto &by_dialog : stat_by_owner_dialog_id_) {
+ stats->by_chat_.push_back(get_storage_statistics_by_chat_object(by_dialog.first, by_dialog.second));
}
std::sort(stats->by_chat_.begin(), stats->by_chat_.end(), [](const auto &x, const auto &y) {
if (x->chat_id_ == 0 || y->chat_id_ == 0) {
@@ -158,13 +184,13 @@ tl_object_ptr<td_api::storageStatistics> FileStats::as_td_api() const {
return stats;
}
-std::vector<DialogId> FileStats::get_dialog_ids() const {
- std::vector<DialogId> res;
- if (!split_by_owner_dialog_id) {
+vector<DialogId> FileStats::get_dialog_ids() const {
+ vector<DialogId> res;
+ if (!split_by_owner_dialog_id_) {
return res;
}
- res.reserve(stat_by_owner_dialog_id.size());
- for (auto &by_dialog : stat_by_owner_dialog_id) {
+ res.reserve(stat_by_owner_dialog_id_.size());
+ for (auto &by_dialog : stat_by_owner_dialog_id_) {
if (by_dialog.first.is_valid()) {
res.push_back(by_dialog.first);
}
@@ -172,27 +198,31 @@ std::vector<DialogId> FileStats::get_dialog_ids() const {
return res;
}
-StringBuilder &operator<<(StringBuilder &sb, const FileTypeStat &stat) {
+vector<FullFileInfo> FileStats::get_all_files() {
+ return std::move(all_files_);
+}
+
+static StringBuilder &operator<<(StringBuilder &sb, const FileTypeStat &stat) {
return sb << tag("size", format::as_size(stat.size)) << tag("count", stat.cnt);
}
StringBuilder &operator<<(StringBuilder &sb, const FileStats &file_stats) {
- if (!file_stats.split_by_owner_dialog_id) {
+ if (!file_stats.split_by_owner_dialog_id_) {
FileTypeStat total_stat;
- for (auto &type_stat : file_stats.stat_by_type) {
+ for (auto &type_stat : file_stats.stat_by_type_) {
total_stat.size += type_stat.size;
total_stat.cnt += type_stat.cnt;
}
sb << "[FileStat " << tag("total", total_stat);
- for (int i = 0; i < file_type_size; i++) {
- sb << tag(Slice(file_type_name[i]), file_stats.stat_by_type[i]);
+ for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
+ sb << tag(get_file_type_name(FileType(i)), file_stats.stat_by_type_[i]);
}
sb << "]";
} else {
{
FileTypeStat total_stat;
- for (auto &by_type : file_stats.stat_by_owner_dialog_id) {
+ for (auto &by_type : file_stats.stat_by_owner_dialog_id_) {
for (auto &type_stat : by_type.second) {
total_stat.size += type_stat.size;
total_stat.cnt += type_stat.cnt;
@@ -200,7 +230,7 @@ StringBuilder &operator<<(StringBuilder &sb, const FileStats &file_stats) {
}
sb << "[FileStat " << tag("total", total_stat);
}
- for (auto &by_type : file_stats.stat_by_owner_dialog_id) {
+ for (auto &by_type : file_stats.stat_by_owner_dialog_id_) {
FileTypeStat dialog_stat;
for (auto &type_stat : by_type.second) {
dialog_stat.size += type_stat.size;
@@ -208,8 +238,8 @@ StringBuilder &operator<<(StringBuilder &sb, const FileStats &file_stats) {
}
sb << "[FileStat " << tag("owner_dialog_id", by_type.first) << tag("total", dialog_stat);
- for (int i = 0; i < file_type_size; i++) {
- sb << tag(Slice(file_type_name[i]), by_type.second[i]);
+ for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
+ sb << tag(get_file_type_name(FileType(i)), by_type.second[i]);
}
sb << "]";
}
@@ -218,4 +248,5 @@ StringBuilder &operator<<(StringBuilder &sb, const FileStats &file_stats) {
return sb;
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileStats.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileStats.h
index 5e02fd249e..5e713aa146 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileStats.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileStats.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,7 @@
#pragma once
#include "td/telegram/DialogId.h"
-#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileType.h"
#include "td/utils/common.h"
#include "td/utils/StringBuilder.h"
@@ -17,27 +17,25 @@
#include <unordered_map>
namespace td {
+
namespace td_api {
class storageStatistics;
class storageStatisticsFast;
} // namespace td_api
-} // namespace td
-
-namespace td {
struct FileTypeStat {
int64 size{0};
int32 cnt{0};
};
-template <class T>
-void store(const FileTypeStat &stat, T &storer) {
+template <class StorerT>
+void store(const FileTypeStat &stat, StorerT &storer) {
using ::td::store;
store(stat.size, storer);
store(stat.cnt, storer);
}
-template <class T>
-void parse(FileTypeStat &stat, T &parser) {
+template <class ParserT>
+void parse(FileTypeStat &stat, ParserT &parser) {
using ::td::parse;
parse(stat.size, parser);
parse(stat.cnt, parser);
@@ -55,32 +53,60 @@ struct FullFileInfo {
struct FileStatsFast {
int64 size{0};
int32 count{0};
- int64 db_size{0};
- FileStatsFast(int64 size, int32 count, int64 db_size) : size(size), count(count), db_size(db_size) {
+ int64 database_size{0};
+ int64 language_pack_database_size{0};
+ int64 log_size{0};
+ FileStatsFast(int64 size, int32 count, int64 database_size, int64 language_pack_database_size, int64 log_size)
+ : size(size)
+ , count(count)
+ , database_size(database_size)
+ , language_pack_database_size(language_pack_database_size)
+ , log_size(log_size) {
}
- tl_object_ptr<td_api::storageStatisticsFast> as_td_api() const;
+ tl_object_ptr<td_api::storageStatisticsFast> get_storage_statistics_fast_object() const;
};
-struct FileStats {
- bool need_all_files{false};
- bool split_by_owner_dialog_id{false};
+class FileStats {
+ bool need_all_files_{false};
+ bool split_by_owner_dialog_id_{false};
+
+ using StatByType = std::array<FileTypeStat, MAX_FILE_TYPE>;
+
+ StatByType stat_by_type_;
+ std::unordered_map<DialogId, StatByType, DialogIdHash> stat_by_owner_dialog_id_;
+ vector<FullFileInfo> all_files_;
+
+ void add_impl(const FullFileInfo &info);
+
+ void add(StatByType &by_type, FileType file_type, int64 size);
- using StatByType = std::array<FileTypeStat, file_type_size>;
+ static FileTypeStat get_nontemp_stat(const StatByType &by_type);
- StatByType stat_by_type;
- std::unordered_map<DialogId, StatByType, DialogIdHash> stat_by_owner_dialog_id;
+ static td_api::object_ptr<td_api::storageStatisticsByChat> get_storage_statistics_by_chat_object(
+ DialogId dialog_id, const StatByType &stat_by_type_);
- std::vector<FullFileInfo> all_files;
+ friend StringBuilder &operator<<(StringBuilder &sb, const FileStats &file_stats);
+
+ public:
+ FileStats(bool need_all_files, bool split_by_owner_dialog_id)
+ : need_all_files_(need_all_files), split_by_owner_dialog_id_(split_by_owner_dialog_id) {
+ }
+
+ void add_copy(const FullFileInfo &info);
void add(FullFileInfo &&info);
+
void apply_dialog_limit(int32 limit);
- tl_object_ptr<td_api::storageStatistics> as_td_api() const;
- std::vector<DialogId> get_dialog_ids() const;
+ void apply_dialog_ids(const vector<DialogId> &dialog_ids);
+
+ tl_object_ptr<td_api::storageStatistics> get_storage_statistics_object() const;
+
+ vector<DialogId> get_dialog_ids() const;
+
FileTypeStat get_total_nontemp_stat() const;
- private:
- void add(StatByType &by_type, FileType file_type, int64 size);
+ vector<FullFileInfo> get_all_files();
};
StringBuilder &operator<<(StringBuilder &sb, const FileStats &file_stats);
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.cpp
index 999402ae2b..9ca33d0c5f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,22 +7,32 @@
#include "td/telegram/files/FileStatsWorker.h"
#include "td/telegram/DialogId.h"
+#include "td/telegram/files/FileData.h"
#include "td/telegram/files/FileDb.h"
#include "td/telegram/files/FileLoaderUtils.h"
+#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/TdParameters.h"
+
+#include "td/db/SqliteKeyValue.h"
#include "td/utils/format.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/PathView.h"
#include "td/utils/port/path.h"
#include "td/utils/port/Stat.h"
#include "td/utils/Slice.h"
-#include "td/utils/Status.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
-#include "td/utils/tl_helpers.h"
+#include "td/utils/tl_parsers.h"
-#include <functional>
#include <unordered_map>
+#include <unordered_set>
namespace td {
namespace {
@@ -39,19 +49,22 @@ struct DbFileInfo {
int64 size;
};
-// long and blocking
template <class CallbackT>
-Status scan_db(CallbackT &&callback) {
+void scan_db(CancellationToken &token, CallbackT &&callback) {
G()->td_db()->get_file_db_shared()->pmc().get_by_range("file0", "file:", [&](Slice key, Slice value) {
+ if (token) {
+ return false;
+ }
// skip reference to other data
if (value.substr(0, 2) == "@@") {
- return;
+ return true;
}
+ log_event::WithVersion<TlParser> parser(value);
FileData data;
- auto status = unserialize(data, value);
- if (status.is_error()) {
+ data.parse(parser, false);
+ if (parser.get_status().is_error()) {
LOG(ERROR) << "Invalid FileData in the database " << tag("value", format::escaped(value));
- return;
+ return true;
}
DbFileInfo info;
if (data.local_.type() == LocalFileLocation::Type::Full) {
@@ -61,22 +74,22 @@ Status scan_db(CallbackT &&callback) {
info.file_type = data.local_.partial().file_type_;
info.path = data.local_.partial().path_;
} else {
- return;
+ return true;
}
PathView path_view(info.path);
if (path_view.is_relative()) {
- info.path = get_files_base_dir(info.file_type) + info.path;
+ info.path = PSTRING() << get_files_base_dir(info.file_type) << info.path;
}
// LOG(INFO) << "Found file in the database: " << data << " " << info.path;
info.owner_dialog_id = data.owner_dialog_id_;
info.size = data.size_;
if (info.size == 0 && data.local_.type() == LocalFileLocation::Type::Full) {
LOG(ERROR) << "Unknown size in the database";
- return;
+ return true;
}
callback(info);
+ return true;
});
- return Status::OK();
}
struct FsFileInfo {
@@ -87,33 +100,48 @@ struct FsFileInfo {
uint64 mtime_nsec;
};
-// long and blocking
template <class CallbackT>
-Status scan_fs(CallbackT &&callback) {
- for (int i = 0; i < file_type_size; i++) {
- auto file_type = static_cast<FileType>(i);
- auto files_dir = get_files_dir(file_type);
- td::walk_path(files_dir, [&](CSlice path, bool is_dir) {
- if (is_dir) {
- // TODO: skip subdirs
- return;
+void scan_fs(CancellationToken &token, CallbackT &&callback) {
+ std::unordered_set<string, Hash<string>> scanned_file_dirs;
+ auto scan_dir = [&](FileType file_type, const string &file_dir) {
+ LOG(INFO) << "Trying to scan directory " << file_dir;
+ if (!scanned_file_dirs.insert(file_dir).second) {
+ return;
+ }
+ walk_path(file_dir, [&](CSlice path, WalkPath::Type type) {
+ if (token) {
+ return WalkPath::Action::Abort;
+ }
+ if (type != WalkPath::Type::NotDir) {
+ return WalkPath::Action::Continue;
}
auto r_stat = stat(path);
if (r_stat.is_error()) {
LOG(WARNING) << "Stat in files gc failed: " << r_stat.error();
- return;
+ return WalkPath::Action::Continue;
}
auto stat = r_stat.move_as_ok();
+ if (stat.size_ == 0 && ends_with(path, "/.nomedia")) {
+ // skip .nomedia file
+ return WalkPath::Action::Continue;
+ }
+
FsFileInfo info;
info.path = path.str();
- info.size = stat.size_;
+ info.size = stat.real_size_;
info.file_type = file_type;
info.atime_nsec = stat.atime_nsec_;
info.mtime_nsec = stat.mtime_nsec_;
callback(info);
- });
+ return WalkPath::Action::Continue;
+ }).ignore();
+ };
+ for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
+ auto file_type = static_cast<FileType>(i);
+ scan_dir(get_main_file_type(file_type), get_files_dir(file_type));
}
- return Status::OK();
+ scan_dir(get_main_file_type(FileType::Temp), get_files_temp_dir(FileType::SecureDecrypted));
+ scan_dir(get_main_file_type(FileType::Temp), get_files_temp_dir(FileType::Video));
}
} // namespace
@@ -122,10 +150,9 @@ void FileStatsWorker::get_stats(bool need_all_files, bool split_by_owner_dialog_
split_by_owner_dialog_id = false;
}
if (!split_by_owner_dialog_id) {
- FileStats file_stats;
- file_stats.need_all_files = need_all_files;
+ FileStats file_stats(need_all_files, false);
auto start = Time::now();
- scan_fs([&](FsFileInfo &fs_info) {
+ scan_fs(token_, [&](FsFileInfo &fs_info) {
FullFileInfo info;
info.file_type = fs_info.file_type;
info.path = std::move(fs_info.path);
@@ -136,12 +163,15 @@ void FileStatsWorker::get_stats(bool need_all_files, bool split_by_owner_dialog_
});
auto passed = Time::now() - start;
LOG_IF(INFO, passed > 0.5) << "Get file stats took: " << format::as_time(passed);
+ if (token_) {
+ return promise.set_error(Global::request_aborted_error());
+ }
promise.set_value(std::move(file_stats));
} else {
auto start = Time::now();
std::vector<FullFileInfo> full_infos;
- scan_fs([&](FsFileInfo &fs_info) {
+ scan_fs(token_, [&](FsFileInfo &fs_info) {
FullFileInfo info;
info.file_type = fs_info.file_type;
info.path = std::move(fs_info.path);
@@ -154,26 +184,37 @@ void FileStatsWorker::get_stats(bool need_all_files, bool split_by_owner_dialog_
full_infos.push_back(std::move(info));
});
- std::unordered_map<size_t, size_t> hash_to_pos;
+ if (token_) {
+ return promise.set_error(Global::request_aborted_error());
+ }
+
+ std::unordered_map<int64, size_t, Hash<int64>> hash_to_pos;
size_t pos = 0;
for (auto &full_info : full_infos) {
- hash_to_pos[std::hash<std::string>()(full_info.path)] = pos;
+ hash_to_pos[Hash<string>()(full_info.path)] = pos;
pos++;
+ if (token_) {
+ return promise.set_error(Global::request_aborted_error());
+ }
}
- scan_db([&](DbFileInfo &db_info) {
- auto it = hash_to_pos.find(std::hash<std::string>()(db_info.path));
+ scan_db(token_, [&](DbFileInfo &db_info) {
+ auto it = hash_to_pos.find(Hash<string>()(db_info.path));
if (it == hash_to_pos.end()) {
return;
}
// LOG(INFO) << "Match! " << db_info.path << " from " << db_info.owner_dialog_id;
full_infos[it->second].owner_dialog_id = db_info.owner_dialog_id;
});
+ if (token_) {
+ return promise.set_error(Global::request_aborted_error());
+ }
- FileStats file_stats;
- file_stats.need_all_files = need_all_files;
- file_stats.split_by_owner_dialog_id = split_by_owner_dialog_id;
+ FileStats file_stats(need_all_files, split_by_owner_dialog_id);
for (auto &full_info : full_infos) {
file_stats.add(std::move(full_info));
+ if (token_) {
+ return promise.set_error(Global::request_aborted_error());
+ }
}
auto passed = Time::now() - start;
LOG_IF(INFO, passed > 0.5) << "Get file stats took: " << format::as_time(passed);
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.h
index f70fcac1e0..71c4a7003f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileStatsWorker.h
@@ -1,26 +1,30 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/files/FileStats.h"
+
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-#include "td/telegram/files/FileStats.h"
+#include "td/utils/CancellationToken.h"
+#include "td/utils/Promise.h"
namespace td {
-class FileStatsWorker : public Actor {
+class FileStatsWorker final : public Actor {
public:
- explicit FileStatsWorker(ActorShared<> parent) : parent_(std::move(parent)) {
+ FileStatsWorker(ActorShared<> parent, CancellationToken token)
+ : parent_(std::move(parent)), token_(std::move(token)) {
}
void get_stats(bool need_all_files, bool split_by_owner_dialog_id, Promise<FileStats> promise);
private:
ActorShared<> parent_;
+ CancellationToken token_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileType.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileType.cpp
new file mode 100644
index 0000000000..a02224df74
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileType.cpp
@@ -0,0 +1,306 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/files/FileType.h"
+
+namespace td {
+
+FileType get_file_type(const td_api::FileType &file_type) {
+ switch (file_type.get_id()) {
+ case td_api::fileTypeThumbnail::ID:
+ return FileType::Thumbnail;
+ case td_api::fileTypeProfilePhoto::ID:
+ return FileType::ProfilePhoto;
+ case td_api::fileTypePhoto::ID:
+ return FileType::Photo;
+ case td_api::fileTypeVoiceNote::ID:
+ return FileType::VoiceNote;
+ case td_api::fileTypeVideo::ID:
+ return FileType::Video;
+ case td_api::fileTypeDocument::ID:
+ return FileType::Document;
+ case td_api::fileTypeSecret::ID:
+ return FileType::Encrypted;
+ case td_api::fileTypeUnknown::ID:
+ return FileType::Temp;
+ case td_api::fileTypeSticker::ID:
+ return FileType::Sticker;
+ case td_api::fileTypeAudio::ID:
+ return FileType::Audio;
+ case td_api::fileTypeAnimation::ID:
+ return FileType::Animation;
+ case td_api::fileTypeSecretThumbnail::ID:
+ return FileType::EncryptedThumbnail;
+ case td_api::fileTypeWallpaper::ID:
+ return FileType::Background;
+ case td_api::fileTypeVideoNote::ID:
+ return FileType::VideoNote;
+ case td_api::fileTypeSecure::ID:
+ return FileType::SecureEncrypted;
+ case td_api::fileTypeNotificationSound::ID:
+ return FileType::Ringtone;
+ case td_api::fileTypeNone::ID:
+ return FileType::None;
+ default:
+ UNREACHABLE();
+ return FileType::None;
+ }
+}
+
+tl_object_ptr<td_api::FileType> get_file_type_object(FileType file_type) {
+ switch (file_type) {
+ case FileType::Thumbnail:
+ return make_tl_object<td_api::fileTypeThumbnail>();
+ case FileType::ProfilePhoto:
+ return make_tl_object<td_api::fileTypeProfilePhoto>();
+ case FileType::Photo:
+ return make_tl_object<td_api::fileTypePhoto>();
+ case FileType::VoiceNote:
+ return make_tl_object<td_api::fileTypeVoiceNote>();
+ case FileType::Video:
+ return make_tl_object<td_api::fileTypeVideo>();
+ case FileType::Document:
+ return make_tl_object<td_api::fileTypeDocument>();
+ case FileType::Encrypted:
+ return make_tl_object<td_api::fileTypeSecret>();
+ case FileType::Temp:
+ return make_tl_object<td_api::fileTypeUnknown>();
+ case FileType::Sticker:
+ return make_tl_object<td_api::fileTypeSticker>();
+ case FileType::Audio:
+ return make_tl_object<td_api::fileTypeAudio>();
+ case FileType::Animation:
+ return make_tl_object<td_api::fileTypeAnimation>();
+ case FileType::EncryptedThumbnail:
+ return make_tl_object<td_api::fileTypeSecretThumbnail>();
+ case FileType::Wallpaper:
+ return make_tl_object<td_api::fileTypeWallpaper>();
+ case FileType::VideoNote:
+ return make_tl_object<td_api::fileTypeVideoNote>();
+ case FileType::SecureEncrypted:
+ return make_tl_object<td_api::fileTypeSecure>();
+ case FileType::SecureDecrypted:
+ UNREACHABLE();
+ return make_tl_object<td_api::fileTypeSecure>();
+ case FileType::Background:
+ return make_tl_object<td_api::fileTypeWallpaper>();
+ case FileType::DocumentAsFile:
+ return make_tl_object<td_api::fileTypeDocument>();
+ case FileType::Ringtone:
+ return make_tl_object<td_api::fileTypeNotificationSound>();
+ case FileType::CallLog:
+ return make_tl_object<td_api::fileTypeDocument>();
+ case FileType::None:
+ return make_tl_object<td_api::fileTypeNone>();
+ default:
+ UNREACHABLE();
+ return nullptr;
+ }
+}
+
+FileType get_main_file_type(FileType file_type) {
+ switch (file_type) {
+ case FileType::Wallpaper:
+ return FileType::Background;
+ case FileType::SecureDecrypted:
+ return FileType::SecureEncrypted;
+ case FileType::DocumentAsFile:
+ return FileType::Document;
+ case FileType::CallLog:
+ return FileType::Document;
+ default:
+ return file_type;
+ }
+}
+
+CSlice get_file_type_name(FileType file_type) {
+ switch (file_type) {
+ case FileType::Thumbnail:
+ return CSlice("thumbnails");
+ case FileType::ProfilePhoto:
+ return CSlice("profile_photos");
+ case FileType::Photo:
+ return CSlice("photos");
+ case FileType::VoiceNote:
+ return CSlice("voice");
+ case FileType::Video:
+ return CSlice("videos");
+ case FileType::Document:
+ return CSlice("documents");
+ case FileType::Encrypted:
+ return CSlice("secret");
+ case FileType::Temp:
+ return CSlice("temp");
+ case FileType::Sticker:
+ return CSlice("stickers");
+ case FileType::Audio:
+ return CSlice("music");
+ case FileType::Animation:
+ return CSlice("animations");
+ case FileType::EncryptedThumbnail:
+ return CSlice("secret_thumbnails");
+ case FileType::Wallpaper:
+ return CSlice("wallpapers");
+ case FileType::VideoNote:
+ return CSlice("video_notes");
+ case FileType::SecureDecrypted:
+ return CSlice("passport");
+ case FileType::SecureEncrypted:
+ return CSlice("passport");
+ case FileType::Background:
+ return CSlice("wallpapers");
+ case FileType::DocumentAsFile:
+ return CSlice("documents");
+ case FileType::Ringtone:
+ return CSlice("notification_sounds");
+ case FileType::CallLog:
+ return CSlice("documents");
+ case FileType::Size:
+ case FileType::None:
+ default:
+ UNREACHABLE();
+ return CSlice("none");
+ }
+}
+
+FileTypeClass get_file_type_class(FileType file_type) {
+ switch (file_type) {
+ case FileType::Photo:
+ case FileType::ProfilePhoto:
+ case FileType::Thumbnail:
+ case FileType::EncryptedThumbnail:
+ case FileType::Wallpaper:
+ return FileTypeClass::Photo;
+ case FileType::Video:
+ case FileType::VoiceNote:
+ case FileType::Document:
+ case FileType::Sticker:
+ case FileType::Audio:
+ case FileType::Animation:
+ case FileType::VideoNote:
+ case FileType::Background:
+ case FileType::DocumentAsFile:
+ case FileType::Ringtone:
+ case FileType::CallLog:
+ return FileTypeClass::Document;
+ case FileType::SecureDecrypted:
+ case FileType::SecureEncrypted:
+ return FileTypeClass::Secure;
+ case FileType::Encrypted:
+ return FileTypeClass::Encrypted;
+ case FileType::Temp:
+ return FileTypeClass::Temp;
+ case FileType::None:
+ case FileType::Size:
+ default:
+ UNREACHABLE();
+ return FileTypeClass::Temp;
+ }
+}
+
+bool is_document_file_type(FileType file_type) {
+ return get_file_type_class(file_type) == FileTypeClass::Document;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, FileType file_type) {
+ switch (file_type) {
+ case FileType::Thumbnail:
+ return string_builder << "Thumbnail";
+ case FileType::ProfilePhoto:
+ return string_builder << "ChatPhoto";
+ case FileType::Photo:
+ return string_builder << "Photo";
+ case FileType::VoiceNote:
+ return string_builder << "VoiceNote";
+ case FileType::Video:
+ return string_builder << "Video";
+ case FileType::Document:
+ return string_builder << "Document";
+ case FileType::Encrypted:
+ return string_builder << "Secret";
+ case FileType::Temp:
+ return string_builder << "Temp";
+ case FileType::Sticker:
+ return string_builder << "Sticker";
+ case FileType::Audio:
+ return string_builder << "Audio";
+ case FileType::Animation:
+ return string_builder << "Animation";
+ case FileType::EncryptedThumbnail:
+ return string_builder << "SecretThumbnail";
+ case FileType::Wallpaper:
+ return string_builder << "Wallpaper";
+ case FileType::VideoNote:
+ return string_builder << "VideoNote";
+ case FileType::SecureDecrypted:
+ return string_builder << "Passport";
+ case FileType::SecureEncrypted:
+ return string_builder << "Passport";
+ case FileType::Background:
+ return string_builder << "Background";
+ case FileType::DocumentAsFile:
+ return string_builder << "DocumentAsFile";
+ case FileType::Ringtone:
+ return string_builder << "NotificationSound";
+ case FileType::CallLog:
+ return string_builder << "CallLog";
+ case FileType::Size:
+ case FileType::None:
+ default:
+ return string_builder << "<invalid>";
+ }
+}
+
+FileDirType get_file_dir_type(FileType file_type) {
+ switch (file_type) {
+ case FileType::Thumbnail:
+ case FileType::ProfilePhoto:
+ case FileType::Encrypted:
+ case FileType::Sticker:
+ case FileType::Temp:
+ case FileType::Wallpaper:
+ case FileType::EncryptedThumbnail:
+ case FileType::SecureEncrypted:
+ case FileType::SecureDecrypted:
+ case FileType::Background:
+ case FileType::Ringtone:
+ return FileDirType::Secure;
+ default:
+ return FileDirType::Common;
+ }
+}
+
+bool is_file_big(FileType file_type, int64 expected_size) {
+ switch (file_type) {
+ case FileType::Thumbnail:
+ case FileType::ProfilePhoto:
+ case FileType::Photo:
+ case FileType::EncryptedThumbnail:
+ case FileType::VideoNote:
+ case FileType::Ringtone:
+ case FileType::CallLog:
+ return false;
+ default:
+ break;
+ }
+
+ constexpr int64 SMALL_FILE_MAX_SIZE = 10 << 20;
+ return expected_size > SMALL_FILE_MAX_SIZE;
+}
+
+bool can_reuse_remote_file(FileType file_type) {
+ switch (file_type) {
+ case FileType::Thumbnail:
+ case FileType::EncryptedThumbnail:
+ case FileType::Background:
+ case FileType::CallLog:
+ return false;
+ default:
+ return true;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileType.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileType.h
new file mode 100644
index 0000000000..5281c79011
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileType.h
@@ -0,0 +1,68 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+enum class FileType : int32 {
+ Thumbnail,
+ ProfilePhoto,
+ Photo,
+ VoiceNote,
+ Video,
+ Document,
+ Encrypted,
+ Temp,
+ Sticker,
+ Audio,
+ Animation,
+ EncryptedThumbnail,
+ Wallpaper,
+ VideoNote,
+ SecureDecrypted,
+ SecureEncrypted,
+ Background,
+ DocumentAsFile,
+ Ringtone,
+ CallLog,
+ Size,
+ None
+};
+
+enum class FileDirType : int8 { Secure, Common };
+
+constexpr int32 MAX_FILE_TYPE = static_cast<int32>(FileType::Size);
+
+FileType get_file_type(const td_api::FileType &file_type);
+
+tl_object_ptr<td_api::FileType> get_file_type_object(FileType file_type);
+
+FileType get_main_file_type(FileType file_type);
+
+CSlice get_file_type_name(FileType file_type);
+
+enum class FileTypeClass : int32 { Photo, Document, Secure, Encrypted, Temp };
+
+FileTypeClass get_file_type_class(FileType file_type);
+
+bool is_document_file_type(FileType file_type);
+
+StringBuilder &operator<<(StringBuilder &string_builder, FileType file_type);
+
+FileDirType get_file_dir_type(FileType file_type);
+
+bool is_file_big(FileType file_type, int64 expected_size);
+
+bool can_reuse_remote_file(FileType file_type);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.cpp
index 635e5c8933..fbda0d6bd6 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.cpp
@@ -1,38 +1,47 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/FileUploader.h"
-#include "td/telegram/telegram_api.h"
-
+#include "td/telegram/files/FileLoaderUtils.h"
#include "td/telegram/Global.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/SecureStorage.h"
+#include "td/telegram/telegram_api.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/port/path.h"
#include "td/utils/Random.h"
#include "td/utils/ScopeGuard.h"
namespace td {
+
FileUploader::FileUploader(const LocalFileLocation &local, const RemoteFileLocation &remote, int64 expected_size,
const FileEncryptionKey &encryption_key, std::vector<int> bad_parts,
- std::unique_ptr<Callback> callback)
+ unique_ptr<Callback> callback)
: local_(local)
, remote_(remote)
, expected_size_(expected_size)
, encryption_key_(encryption_key)
, bad_parts_(std::move(bad_parts))
, callback_(std::move(callback)) {
- if (!encryption_key_.empty()) {
+ if (encryption_key_.is_secret()) {
iv_ = encryption_key_.mutable_iv();
generate_iv_ = encryption_key_.iv_slice().str();
}
+ if (remote_.type() == RemoteFileLocation::Type::Partial && encryption_key_.is_secure() &&
+ remote_.partial().part_count_ != remote_.partial().ready_part_count_) {
+ remote_ = RemoteFileLocation{};
+ }
}
Result<FileLoader::FileInfo> FileUploader::init() {
@@ -40,7 +49,9 @@ Result<FileLoader::FileInfo> FileUploader::init() {
return Status::Error("File is already uploaded");
}
- TRY_RESULT(prefix_info, on_update_local_location(local_));
+ // file_size is needed only for partial local locations, but for uploaded partial files
+ // size is yet unknown or local location is full, so we can always pass 0 here
+ TRY_RESULT(prefix_info, on_update_local_location(local_, 0));
(void)prefix_info;
int offset = 0;
@@ -53,7 +64,7 @@ Result<FileLoader::FileInfo> FileUploader::init() {
offset = partial.ready_part_count_;
} else {
file_id_ = Random::secure_int64();
- big_flag_ = expected_size_ > 10 * (1 << 20);
+ big_flag_ = is_file_big(file_type_, expected_size_);
}
std::vector<bool> ok(offset, true);
@@ -68,6 +79,7 @@ Result<FileLoader::FileInfo> FileUploader::init() {
parts.push_back(i);
}
}
+ LOG(DEBUG) << "Init file uploader for " << remote_ << " with offset = " << offset << " and part size = " << part_size;
if (!ok.empty() && !ok[0]) {
parts.clear();
}
@@ -76,39 +88,70 @@ Result<FileLoader::FileInfo> FileUploader::init() {
res.is_size_final = local_is_ready_;
res.part_size = part_size;
res.ready_parts = std::move(parts);
+ res.is_upload = true;
return res;
}
-Result<FileLoader::PrefixInfo> FileUploader::on_update_local_location(const LocalFileLocation &location) {
+Result<FileLoader::PrefixInfo> FileUploader::on_update_local_location(const LocalFileLocation &location,
+ int64 file_size) {
SCOPE_EXIT {
try_release_fd();
};
+
+ if (encryption_key_.is_secure() && !fd_path_.empty()) {
+ return Status::Error("Can't change local location for Secure file");
+ }
+
string path;
- int64 local_size = 0;
+ int64 local_size = -1;
bool local_is_ready{false};
FileType file_type{FileType::Temp};
- if (location.type() == LocalFileLocation::Type::Empty) {
+ if (location.type() == LocalFileLocation::Type::Empty ||
+ (location.type() == LocalFileLocation::Type::Partial && encryption_key_.is_secure())) {
path = "";
local_size = 0;
local_is_ready = false;
file_type = FileType::Temp;
} else if (location.type() == LocalFileLocation::Type::Partial) {
path = location.partial().path_;
- local_size = static_cast<int64>(location.partial().part_size_) * location.partial().ready_part_count_;
+ local_size = Bitmask(Bitmask::Decode{}, location.partial().ready_bitmask_)
+ .get_ready_prefix_size(0, location.partial().part_size_, file_size);
local_is_ready = false;
file_type = location.partial().file_type_;
} else {
path = location.full().path_;
+ if (path.empty()) {
+ return Status::Error("FullLocalFileLocation with empty path");
+ }
local_is_ready = true;
file_type = location.full().file_type_;
}
- if (!path.empty() && path != fd_path_) {
+ LOG(INFO) << "In FileUploader::on_update_local_location with " << location << ". Have path = \"" << path
+ << "\", local_size = " << local_size << ", local_is_ready = " << local_is_ready
+ << " and file type = " << file_type;
+
+ file_type_ = file_type;
+
+ bool is_temp = false;
+ if (encryption_key_.is_secure() && local_is_ready && remote_.type() == RemoteFileLocation::Type::Empty) {
+ TRY_RESULT(file_fd_path, open_temp_file(FileType::Temp));
+ file_fd_path.first.close();
+ auto new_path = std::move(file_fd_path.second);
+ TRY_RESULT(hash, secure_storage::encrypt_file(encryption_key_.secret(), path, new_path));
+ LOG(INFO) << "ENCRYPT " << path << " " << new_path;
+ callback_->on_hash(hash.as_slice().str());
+ path = new_path;
+ is_temp = true;
+ }
+
+ if (!path.empty() && (path != fd_path_ || fd_.empty())) {
auto res_fd = FileFd::open(path, FileFd::Read);
// Race: partial location could be already deleted. Just ignore such locations
if (res_fd.is_error()) {
if (location.type() == LocalFileLocation::Type::Partial) {
+ LOG(INFO) << "Ignore partial local location: " << res_fd.error();
PrefixInfo info;
info.size = local_size_;
info.is_ready = local_is_ready_;
@@ -120,16 +163,17 @@ Result<FileLoader::PrefixInfo> FileUploader::on_update_local_location(const Loca
fd_.close();
fd_ = res_fd.move_as_ok();
fd_path_ = path;
+ is_temp_ = is_temp;
}
-
if (local_is_ready) {
CHECK(!fd_.empty());
- local_size = fd_.get_size();
+ TRY_RESULT_ASSIGN(local_size, fd_.get_size());
+ LOG(INFO) << "Set file local_size to " << local_size;
if (local_size == 0) {
return Status::Error("Can't upload empty file");
}
} else if (!fd_.empty()) {
- auto real_local_size = fd_.get_size();
+ TRY_RESULT(real_local_size, fd_.get_size());
if (real_local_size < local_size) {
LOG(ERROR) << tag("real_local_size", real_local_size) << " < " << tag("local_size", local_size);
PrefixInfo info;
@@ -140,11 +184,10 @@ Result<FileLoader::PrefixInfo> FileUploader::on_update_local_location(const Loca
}
local_size_ = local_size;
- if (expected_size_ < local_size_) {
+ if (expected_size_ < local_size_ && (expected_size_ != (10 << 20) || local_size_ >= (30 << 20))) {
expected_size_ = local_size_;
}
local_is_ready_ = local_is_ready;
- file_type_ = file_type;
PrefixInfo info;
info.size = local_size_;
@@ -154,15 +197,24 @@ Result<FileLoader::PrefixInfo> FileUploader::on_update_local_location(const Loca
Status FileUploader::on_ok(int64 size) {
fd_.close();
+ if (is_temp_) {
+ LOG(INFO) << "UNLINK " << fd_path_;
+ unlink(fd_path_).ignore();
+ }
return Status::OK();
}
+
void FileUploader::on_error(Status status) {
fd_.close();
+ if (is_temp_) {
+ LOG(INFO) << "UNLINK " << fd_path_;
+ unlink(fd_path_).ignore();
+ }
callback_->on_error(std::move(status));
}
Status FileUploader::generate_iv_map() {
- LOG(INFO) << "generate iv_map " << generate_offset_ << " " << local_size_;
+ LOG(INFO) << "Generate iv_map " << generate_offset_ << " " << local_size_;
auto part_size = get_part_size();
auto encryption_key = FileEncryptionKey(encryption_key_.key_slice(), generate_iv_);
BufferSlice bytes(part_size);
@@ -176,7 +228,8 @@ Status FileUploader::generate_iv_map() {
if (read_size != part_size) {
return Status::Error("Failed to read file part (for iv_map)");
}
- aes_ige_encrypt(encryption_key.key(), &encryption_key.mutable_iv(), bytes.as_slice(), bytes.as_slice());
+ aes_ige_encrypt(as_slice(encryption_key.key()), as_slice(encryption_key.mutable_iv()), bytes.as_slice(),
+ bytes.as_slice());
iv_map_.push_back(encryption_key.mutable_iv());
}
generate_iv_ = encryption_key.iv_slice().str();
@@ -186,25 +239,26 @@ Status FileUploader::generate_iv_map() {
Status FileUploader::before_start_parts() {
auto status = acquire_fd();
if (status.is_error() && !local_is_ready_) {
- return Status::Error(1, "Can't open temporary file");
+ return Status::Error(-1, "Can't open temporary file");
}
return status;
}
+
void FileUploader::after_start_parts() {
try_release_fd();
}
-Result<std::pair<NetQueryPtr, bool>> FileUploader::start_part(Part part, int32 part_count) {
+Result<std::pair<NetQueryPtr, bool>> FileUploader::start_part(Part part, int32 part_count, int64 streaming_offset) {
auto padded_size = part.size;
- if (!encryption_key_.empty()) {
+ if (encryption_key_.is_secret()) {
padded_size = (padded_size + 15) & ~15;
}
BufferSlice bytes(padded_size);
TRY_RESULT(size, fd_.pread(bytes.as_slice().truncate(part.size), part.offset));
- if (!encryption_key_.empty()) {
+ if (encryption_key_.is_secret()) {
Random::secure_bytes(bytes.as_slice().substr(part.size));
if (next_offset_ == part.offset) {
- aes_ige_encrypt(encryption_key_.key(), &iv_, bytes.as_slice(), bytes.as_slice());
+ aes_ige_encrypt(as_slice(encryption_key_.key()), as_slice(iv_), bytes.as_slice(), bytes.as_slice());
next_offset_ += static_cast<int64>(bytes.size());
} else {
if (part.id >= static_cast<int32>(iv_map_.size())) {
@@ -212,12 +266,11 @@ Result<std::pair<NetQueryPtr, bool>> FileUploader::start_part(Part part, int32 p
}
CHECK(part.id < static_cast<int32>(iv_map_.size()) && part.id >= 0);
auto iv = iv_map_[part.id];
- aes_ige_encrypt(encryption_key_.key(), &iv, bytes.as_slice(), bytes.as_slice());
+ aes_ige_encrypt(as_slice(encryption_key_.key()), as_slice(iv), bytes.as_slice(), bytes.as_slice());
}
}
if (size != part.size) {
- LOG(ERROR) << "Need to read " << part.size << " bytes, but read " << size << " bytes instead";
return Status::Error("Failed to read file part");
}
@@ -225,12 +278,10 @@ Result<std::pair<NetQueryPtr, bool>> FileUploader::start_part(Part part, int32 p
if (big_flag_) {
auto query =
telegram_api::upload_saveBigFilePart(file_id_, part.id, local_is_ready_ ? part_count : -1, std::move(bytes));
- net_query = G()->net_query_creator().create(create_storer(query), DcId::main(), NetQuery::Type::Upload,
- NetQuery::AuthFlag::On, NetQuery::GzipFlag::Off);
+ net_query = G()->net_query_creator().create(query, {}, DcId::main(), NetQuery::Type::Upload);
} else {
auto query = telegram_api::upload_saveFilePart(file_id_, part.id, std::move(bytes));
- net_query = G()->net_query_creator().create(create_storer(query), DcId::main(), NetQuery::Type::Upload,
- NetQuery::AuthFlag::On, NetQuery::GzipFlag::Off);
+ net_query = G()->net_query_creator().create(query, {}, DcId::main(), NetQuery::Type::Upload);
}
net_query->file_type_ = narrow_cast<int32>(file_type_);
return std::make_pair(std::move(net_query), false);
@@ -240,32 +291,35 @@ Result<size_t> FileUploader::process_part(Part part, NetQueryPtr net_query) {
if (net_query->is_error()) {
return std::move(net_query->error());
}
- Result<bool> result;
- if (big_flag_) {
- result = fetch_result<telegram_api::upload_saveBigFilePart>(net_query->ok());
- } else {
- result = fetch_result<telegram_api::upload_saveFilePart>(net_query->ok());
- }
+ Result<bool> result = [&] {
+ if (big_flag_) {
+ return fetch_result<telegram_api::upload_saveBigFilePart>(net_query->ok());
+ } else {
+ return fetch_result<telegram_api::upload_saveFilePart>(net_query->ok());
+ }
+ }();
if (result.is_error()) {
return result.move_as_error();
}
if (!result.ok()) {
// TODO: it is possible
- return Status::Error(500, "Internal Server Error");
+ return Status::Error(500, "Internal Server Error during file upload");
}
return part.size;
}
-void FileUploader::on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready,
- int64 ready_size) {
- callback_->on_partial_upload(PartialRemoteFileLocation{file_id_, part_count, part_size, ready_part_count, big_flag_},
- ready_size);
- if (is_ready) {
+void FileUploader::on_progress(Progress progress) {
+ callback_->on_partial_upload(PartialRemoteFileLocation{file_id_, progress.part_count, progress.part_size,
+ progress.ready_part_count, big_flag_},
+ progress.ready_size);
+ if (progress.is_ready) {
callback_->on_ok(file_type_,
- PartialRemoteFileLocation{file_id_, part_count, part_size, ready_part_count, big_flag_},
+ PartialRemoteFileLocation{file_id_, progress.part_count, progress.part_size,
+ progress.ready_part_count, big_flag_},
local_size_);
}
}
+
FileLoader::Callback *FileUploader::get_callback() {
return static_cast<FileLoader::Callback *>(callback_.get());
}
@@ -283,8 +337,7 @@ void FileUploader::try_release_fd() {
Status FileUploader::acquire_fd() {
if (fd_.empty()) {
- TRY_RESULT(fd, FileFd::open(fd_path_, FileFd::Read));
- fd_ = std::move(fd);
+ TRY_RESULT_ASSIGN(fd_, FileFd::open(fd_path_, FileFd::Read));
}
return Status::OK();
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.h b/protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.h
index 7f2ccfe8e8..12594c1122 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/FileUploader.h
@@ -1,34 +1,36 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
+#include "td/telegram/files/FileEncryptionKey.h"
#include "td/telegram/files/FileLoader.h"
#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileType.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/Status.h"
+#include "td/utils/UInt.h"
#include <utility>
namespace td {
-class FileUploader : public FileLoader {
+
+class FileUploader final : public FileLoader {
public:
class Callback : public FileLoader::Callback {
public:
- virtual void on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size) = 0;
- virtual void on_ok(FileType file_type, const PartialRemoteFileLocation &partial_remote, int64 size) = 0;
+ virtual void on_hash(string hash) = 0;
+ virtual void on_partial_upload(PartialRemoteFileLocation partial_remote, int64 ready_size) = 0;
+ virtual void on_ok(FileType file_type, PartialRemoteFileLocation partial_remote, int64 size) = 0;
virtual void on_error(Status status) = 0;
};
FileUploader(const LocalFileLocation &local, const RemoteFileLocation &remote, int64 expected_size,
- const FileEncryptionKey &encryption_key, std::vector<int> bad_parts, std::unique_ptr<Callback> callback);
+ const FileEncryptionKey &encryption_key, std::vector<int> bad_parts, unique_ptr<Callback> callback);
// Should just implement all parent pure virtual methods.
// Must not call any of them...
@@ -39,7 +41,7 @@ class FileUploader : public FileLoader {
int64 expected_size_;
FileEncryptionKey encryption_key_;
std::vector<int> bad_parts_;
- std::unique_ptr<Callback> callback_;
+ unique_ptr<Callback> callback_;
int64 local_size_ = 0;
bool local_is_ready_ = false;
FileType file_type_ = FileType::Temp;
@@ -52,25 +54,29 @@ class FileUploader : public FileLoader {
FileFd fd_;
string fd_path_;
- int64 file_id_;
- bool big_flag_;
+ bool is_temp_ = false;
+ int64 file_id_ = 0;
+ bool big_flag_ = false;
- Result<FileInfo> init() override TD_WARN_UNUSED_RESULT;
- Status on_ok(int64 size) override TD_WARN_UNUSED_RESULT;
- void on_error(Status status) override;
- Status before_start_parts() override;
- void after_start_parts() override;
- Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count) override TD_WARN_UNUSED_RESULT;
- Result<size_t> process_part(Part part, NetQueryPtr net_query) override TD_WARN_UNUSED_RESULT;
- void on_progress(int32 part_count, int32 part_size, int32 ready_part_count, bool is_ready, int64 ready_size) override;
- FileLoader::Callback *get_callback() override;
- Result<PrefixInfo> on_update_local_location(const LocalFileLocation &location) override TD_WARN_UNUSED_RESULT;
+ Result<FileInfo> init() final TD_WARN_UNUSED_RESULT;
+ Status on_ok(int64 size) final TD_WARN_UNUSED_RESULT;
+ void on_error(Status status) final;
+ Status before_start_parts() final;
+ void after_start_parts() final;
+ Result<std::pair<NetQueryPtr, bool>> start_part(Part part, int32 part_count,
+ int64 streaming_offset) final TD_WARN_UNUSED_RESULT;
+ Result<size_t> process_part(Part part, NetQueryPtr net_query) final TD_WARN_UNUSED_RESULT;
+ void on_progress(Progress progress) final;
+ FileLoader::Callback *get_callback() final;
+ Result<PrefixInfo> on_update_local_location(const LocalFileLocation &location,
+ int64 file_size) final TD_WARN_UNUSED_RESULT;
Status generate_iv_map();
bool keep_fd_ = false;
- void keep_fd_flag(bool keep_fd) override;
+ void keep_fd_flag(bool keep_fd) final;
void try_release_fd();
Status acquire_fd() TD_WARN_UNUSED_RESULT;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.cpp
index 75debb7af1..ffe9890daf 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.cpp
@@ -1,23 +1,25 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/PartsManager.h"
+#include "td/telegram/files/FileLoaderUtils.h"
+
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
#include <limits>
#include <numeric>
namespace td {
-/*** PartsManager ***/
namespace {
-int64 calc_parts_count(int64 size, int64 part_size) {
+int64 calc_part_count(int64 size, int64 part_size) {
CHECK(part_size != 0);
return (size + part_size - 1) / part_size;
}
@@ -29,6 +31,57 @@ Status PartsManager::init_known_prefix(int64 known_prefix, size_t part_size, con
return init_no_size(part_size, ready_parts);
}
+int32 PartsManager::set_streaming_offset(int64 offset, int64 limit) {
+ auto finish = [&] {
+ set_streaming_limit(limit);
+ update_first_not_ready_part();
+ return first_streaming_not_ready_part_;
+ };
+
+ if (offset < 0 || need_check_ || (!unknown_size_flag_ && get_size() < offset)) {
+ streaming_offset_ = 0;
+ LOG_IF(ERROR, offset != 0) << "Ignore streaming_offset " << offset << ", need_check_ = " << need_check_
+ << ", unknown_size_flag_ = " << unknown_size_flag_ << ", size = " << get_size();
+
+ return finish();
+ }
+
+ auto part_i = offset / part_size_;
+ if (use_part_count_limit_ && part_i >= MAX_PART_COUNT_PREMIUM) {
+ streaming_offset_ = 0;
+ LOG(ERROR) << "Ignore streaming_offset " << offset << " in part " << part_i;
+
+ return finish();
+ }
+
+ streaming_offset_ = offset;
+ first_streaming_empty_part_ = narrow_cast<int>(part_i);
+ first_streaming_not_ready_part_ = narrow_cast<int>(part_i);
+ if (part_count_ < first_streaming_empty_part_) {
+ part_count_ = first_streaming_empty_part_;
+ part_status_.resize(part_count_, PartStatus::Empty);
+ }
+
+ return finish();
+}
+
+int32 PartsManager::get_pending_count() const {
+ return pending_count_;
+}
+
+void PartsManager::set_streaming_limit(int64 limit) {
+ streaming_limit_ = limit;
+ streaming_ready_size_ = 0;
+ if (streaming_limit_ == 0) {
+ return;
+ }
+ for (int part_i = 0; part_i < part_count_; part_i++) {
+ if (is_part_in_streaming_limit(part_i) && part_status_[part_i] == PartStatus::Ready) {
+ streaming_ready_size_ += get_part(part_i).size;
+ }
+ }
+}
+
Status PartsManager::init_no_size(size_t part_size, const std::vector<int> &ready_parts) {
unknown_size_flag_ = true;
size_ = 0;
@@ -38,30 +91,25 @@ Status PartsManager::init_no_size(size_t part_size, const std::vector<int> &read
if (part_size != 0) {
part_size_ = part_size;
} else {
- part_size_ = 32 * (1 << 10);
- while (use_part_count_limit_ && calc_parts_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ part_size_ = 32 << 10;
+ while (part_size_ < MAX_PART_SIZE && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) {
part_size_ *= 2;
- CHECK(part_size_ <= MAX_PART_SIZE);
}
// just in case if expected_size_ is wrong
if (part_size_ < MAX_PART_SIZE) {
part_size_ *= 2;
}
}
- part_count_ = 0;
- if (known_prefix_flag_) {
- part_count_ = static_cast<int>(known_prefix_size_ / part_size_);
- }
- part_count_ = max(part_count_, std::accumulate(ready_parts.begin(), ready_parts.end(), 0,
- [](auto a, auto b) { return max(a, b + 1); }));
+ part_count_ =
+ std::accumulate(ready_parts.begin(), ready_parts.end(), 0, [](auto a, auto b) { return max(a, b + 1); });
- init_common(ready_parts);
- return Status::OK();
+ return init_common(ready_parts);
}
Status PartsManager::init(int64 size, int64 expected_size, bool is_size_final, size_t part_size,
- const std::vector<int> &ready_parts, bool use_part_count_limit) {
+ const std::vector<int> &ready_parts, bool use_part_count_limit, bool is_upload) {
CHECK(expected_size >= size);
+ is_upload_ = is_upload;
use_part_count_limit_ = use_part_count_limit;
expected_size_ = expected_size;
if (expected_size_ > MAX_FILE_SIZE) {
@@ -73,116 +121,221 @@ Status PartsManager::init(int64 size, int64 expected_size, bool is_size_final, s
if (size == 0) {
return init_no_size(part_size, ready_parts);
}
- CHECK(size > 0) << tag("size", size);
+ LOG_CHECK(size > 0) << tag("size", size);
unknown_size_flag_ = false;
size_ = size;
if (part_size != 0) {
part_size_ = part_size;
- if (use_part_count_limit_ && calc_parts_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ if (use_part_count_limit_ && part_size_ < MAX_PART_SIZE &&
+ calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ CHECK(is_upload_);
return Status::Error("FILE_UPLOAD_RESTART");
}
} else {
- // TODO choose part_size_ depending on size
- part_size_ = 64 * (1 << 10);
- while (use_part_count_limit && calc_parts_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ part_size_ = 64 << 10;
+ while (part_size_ < MAX_PART_SIZE && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) {
part_size_ *= 2;
- CHECK(part_size_ <= MAX_PART_SIZE);
}
}
- CHECK(1 <= size_) << tag("size_", size_);
- CHECK(!use_part_count_limit || calc_parts_count(expected_size_, part_size_) <= MAX_PART_COUNT)
+ LOG_CHECK(1 <= size_) << tag("size_", size_);
+ LOG_CHECK(!use_part_count_limit || calc_part_count(expected_size_, part_size_) <= MAX_PART_COUNT_PREMIUM)
<< tag("size_", size_) << tag("expected_size", size_) << tag("is_size_final", is_size_final)
<< tag("part_size_", part_size_) << tag("ready_parts", ready_parts.size());
- part_count_ = static_cast<int>(calc_parts_count(size_, part_size_));
+ part_count_ = static_cast<int>(calc_part_count(size_, part_size_));
- init_common(ready_parts);
- return Status::OK();
+ return init_common(ready_parts);
}
bool PartsManager::unchecked_ready() {
- VLOG(files) << "Check readiness. Ready size is " << ready_size_ << ", total size is " << size_
- << ", unknown_size_flag = " << unknown_size_flag_ << ", need_check = " << need_check_
- << ", checked_prefix_size = " << checked_prefix_size_;
+ VLOG(file_loader) << "Check readiness. Ready size is " << ready_size_ << ", total size is " << size_
+ << ", unknown_size_flag = " << unknown_size_flag_ << ", need_check = " << need_check_
+ << ", checked_prefix_size = " << checked_prefix_size_;
return !unknown_size_flag_ && ready_size_ == size_;
}
+
+bool PartsManager::may_finish() {
+ if (is_streaming_limit_reached()) {
+ return true;
+ }
+ return ready();
+}
+
bool PartsManager::ready() {
return unchecked_ready() && (!need_check_ || checked_prefix_size_ == size_);
}
Status PartsManager::finish() {
- if (!ready()) {
- return Status::Error("File transferring not finished");
+ if (ready()) {
+ return Status::OK();
}
- return Status::OK();
+ if (is_streaming_limit_reached()) {
+ return Status::Error("FILE_DOWNLOAD_LIMIT");
+ }
+ return Status::Error("File transferring not finished");
}
void PartsManager::update_first_empty_part() {
while (first_empty_part_ < part_count_ && part_status_[first_empty_part_] != PartStatus::Empty) {
first_empty_part_++;
}
+
+ if (streaming_offset_ == 0) {
+ first_streaming_empty_part_ = first_empty_part_;
+ return;
+ }
+ while (first_streaming_empty_part_ < part_count_ && part_status_[first_streaming_empty_part_] != PartStatus::Empty) {
+ first_streaming_empty_part_++;
+ }
}
void PartsManager::update_first_not_ready_part() {
while (first_not_ready_part_ < part_count_ && part_status_[first_not_ready_part_] == PartStatus::Ready) {
first_not_ready_part_++;
}
+ if (streaming_offset_ == 0) {
+ first_streaming_not_ready_part_ = first_not_ready_part_;
+ return;
+ }
+ while (first_streaming_not_ready_part_ < part_count_ &&
+ part_status_[first_streaming_not_ready_part_] == PartStatus::Ready) {
+ first_streaming_not_ready_part_++;
+ }
}
-int32 PartsManager::get_ready_prefix_count() {
+int32 PartsManager::get_unchecked_ready_prefix_count() {
update_first_not_ready_part();
- auto res = first_not_ready_part_;
+ return first_not_ready_part_;
+}
+
+int32 PartsManager::get_ready_prefix_count() {
+ auto res = get_unchecked_ready_prefix_count();
if (need_check_) {
auto checked_parts = narrow_cast<int32>(checked_prefix_size_ / part_size_);
if (checked_parts < res) {
- res = checked_parts;
+ return checked_parts;
}
}
return res;
}
+int64 PartsManager::get_streaming_offset() const {
+ return streaming_offset_;
+}
+
+string PartsManager::get_bitmask() {
+ int32 prefix_count = -1;
+ if (need_check_) {
+ prefix_count = narrow_cast<int32>(checked_prefix_size_ / part_size_);
+ }
+ return bitmask_.encode(prefix_count);
+}
+
+bool PartsManager::is_part_in_streaming_limit(int part_i) const {
+ CHECK(part_i < part_count_);
+ auto offset_begin = static_cast<int64>(part_i) * static_cast<int64>(get_part_size());
+ auto offset_end = offset_begin + static_cast<int64>(get_part(part_i).size);
+
+ if (offset_begin >= get_expected_size()) {
+ return false;
+ }
+
+ if (streaming_limit_ == 0) {
+ return true;
+ }
+
+ auto is_intersect_with = [&](int64 begin, int64 end) {
+ return max(begin, offset_begin) < min(end, offset_end);
+ };
+
+ auto streaming_begin = streaming_offset_;
+ auto streaming_end = streaming_offset_ + streaming_limit_;
+ if (is_intersect_with(streaming_begin, streaming_end)) {
+ return true;
+ }
+ // wrap limit
+ if (!unknown_size_flag_ && streaming_end > get_size() && is_intersect_with(0, streaming_end - get_size())) {
+ return true;
+ }
+ return false;
+}
+
+bool PartsManager::is_streaming_limit_reached() {
+ if (streaming_limit_ == 0) {
+ return false;
+ }
+ update_first_not_ready_part();
+ auto part_i = first_streaming_not_ready_part_;
+
+ // wrap
+ if (!unknown_size_flag_ && part_i == part_count_) {
+ part_i = first_not_ready_part_;
+ }
+ if (part_i == part_count_) {
+ return false;
+ }
+ return !is_part_in_streaming_limit(part_i);
+}
+
Result<Part> PartsManager::start_part() {
update_first_empty_part();
- if (first_empty_part_ == part_count_) {
+ auto part_i = first_streaming_empty_part_;
+ if (known_prefix_flag_ && part_i >= static_cast<int>(known_prefix_size_ / part_size_)) {
+ return Status::Error(-1, "Wait for prefix to be known");
+ }
+ if (part_i == part_count_) {
if (unknown_size_flag_) {
- if (known_prefix_flag_ == false) {
- part_count_++;
- if (part_count_ > MAX_PART_COUNT) {
- return Status::Error("Too big file with unknown size");
+ part_count_++;
+ if (part_count_ > MAX_PART_COUNT_PREMIUM + (use_part_count_limit_ ? 0 : 64)) {
+ if (!is_upload_) {
+ // Caller will try to increase part size if it is possible
+ return Status::Error("FILE_DOWNLOAD_RESTART_INCREASE_PART_SIZE");
}
- part_status_.push_back(PartStatus::Empty);
- } else {
- return Status::Error(1, "Wait for prefix to be known");
+ return Status::Error("Too big file with unknown size");
}
+ part_status_.push_back(PartStatus::Empty);
} else {
- return get_empty_part();
+ if (first_empty_part_ < part_count_) {
+ part_i = first_empty_part_;
+ } else {
+ return get_empty_part();
+ }
}
}
- CHECK(part_status_[first_empty_part_] == PartStatus::Empty);
- int id = first_empty_part_;
- on_part_start(id);
- return get_part(id);
+
+ if (!is_part_in_streaming_limit(part_i)) {
+ return get_empty_part();
+ }
+ CHECK(part_status_[part_i] == PartStatus::Empty);
+ on_part_start(part_i);
+ return get_part(part_i);
}
Status PartsManager::set_known_prefix(size_t size, bool is_ready) {
- CHECK(known_prefix_flag_);
- CHECK(size >= static_cast<size_t>(known_prefix_size_));
+ if (!known_prefix_flag_ || size < static_cast<size_t>(known_prefix_size_)) {
+ CHECK(is_upload_);
+ return Status::Error("FILE_UPLOAD_RESTART");
+ }
known_prefix_size_ = narrow_cast<int64>(size);
expected_size_ = max(known_prefix_size_, expected_size_);
CHECK(static_cast<size_t>(part_count_) == part_status_.size());
if (is_ready) {
- part_count_ = static_cast<int>(calc_parts_count(size, part_size_));
+ part_count_ = static_cast<int>(calc_part_count(size, part_size_));
size_ = narrow_cast<int64>(size);
unknown_size_flag_ = false;
+ known_prefix_flag_ = false;
} else {
part_count_ = static_cast<int>(size / part_size_);
}
- CHECK(static_cast<size_t>(part_count_) >= part_status_.size())
+
+ LOG_CHECK(static_cast<size_t>(part_count_) >= part_status_.size())
<< size << " " << is_ready << " " << part_count_ << " " << part_size_ << " " << part_status_.size();
part_status_.resize(part_count_);
- if (use_part_count_limit_ && calc_parts_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ if (use_part_count_limit_ && part_size_ < MAX_PART_SIZE &&
+ calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) {
+ CHECK(is_upload_);
return Status::Error("FILE_UPLOAD_RESTART");
}
return Status::OK();
@@ -193,9 +346,15 @@ Status PartsManager::on_part_ok(int32 id, size_t part_size, size_t actual_size)
pending_count_--;
part_status_[id] = PartStatus::Ready;
+ if (actual_size != 0) {
+ bitmask_.set(id);
+ }
ready_size_ += narrow_cast<int64>(actual_size);
+ if (streaming_limit_ > 0 && is_part_in_streaming_limit(id)) {
+ streaming_ready_size_ += narrow_cast<int64>(actual_size);
+ }
- VLOG(files) << "Transferred part " << id << " of size " << part_size << ", total ready size = " << ready_size_;
+ VLOG(file_loader) << "Transferred part " << id << " of size " << part_size << ", total ready size = " << ready_size_;
int64 offset = narrow_cast<int64>(part_size_) * id;
int64 end_offset = offset + narrow_cast<int64>(actual_size);
@@ -234,16 +393,69 @@ void PartsManager::on_part_failed(int32 id) {
if (id < first_empty_part_) {
first_empty_part_ = id;
}
+ if (streaming_offset_ == 0) {
+ first_streaming_empty_part_ = id;
+ return;
+ }
+ auto part_i = narrow_cast<int>(streaming_offset_ / part_size_);
+ if (id >= part_i && id < first_streaming_empty_part_) {
+ first_streaming_empty_part_ = id;
+ }
}
int64 PartsManager::get_size() const {
CHECK(!unknown_size_flag_);
return size_;
}
+
int64 PartsManager::get_size_or_zero() const {
return size_;
}
+int64 PartsManager::get_estimated_extra() const {
+ auto total_estimated_extra = get_expected_size() - get_ready_size();
+ if (streaming_limit_ != 0) {
+ int64 expected_size = get_expected_size();
+ int64 streaming_begin = streaming_offset_ / get_part_size() * get_part_size();
+ int64 streaming_end =
+ (streaming_offset_ + streaming_limit_ + get_part_size() - 1) / get_part_size() * get_part_size();
+ int64 streaming_size = streaming_end - streaming_begin;
+ if (unknown_size_flag_) {
+ if (streaming_begin < expected_size) {
+ streaming_size = min(expected_size - streaming_begin, streaming_size);
+ } else {
+ streaming_size = 0;
+ }
+ } else {
+ if (streaming_end > expected_size) {
+ int64 total = streaming_limit_;
+ int64 suffix = 0;
+ if (streaming_offset_ < expected_size_) {
+ suffix = expected_size_ - streaming_begin;
+ total -= expected_size_ - streaming_offset_;
+ }
+ int64 prefix = (total + get_part_size() - 1) / get_part_size() * get_part_size();
+ streaming_size = min(expected_size, prefix + suffix);
+ }
+ }
+ int64 res = streaming_size;
+
+ //TODO: delete this block if CHECK won't fail
+ int64 sub = 0;
+ for (int part_i = 0; part_i < part_count_; part_i++) {
+ if (is_part_in_streaming_limit(part_i) && part_status_[part_i] == PartStatus::Ready) {
+ sub += get_part(part_i).size;
+ }
+ }
+ CHECK(sub == streaming_ready_size_);
+
+ res -= streaming_ready_size_;
+ CHECK(res >= 0);
+ return res;
+ }
+ return total_estimated_extra;
+}
+
int64 PartsManager::get_ready_size() const {
return ready_size_;
}
@@ -263,25 +475,42 @@ int32 PartsManager::get_part_count() const {
return part_count_;
}
-void PartsManager::init_common(const std::vector<int> &ready_parts) {
+Status PartsManager::init_common(const std::vector<int> &ready_parts) {
ready_size_ = 0;
+ streaming_ready_size_ = 0;
pending_count_ = 0;
first_empty_part_ = 0;
first_not_ready_part_ = 0;
part_status_ = vector<PartStatus>(part_count_);
for (auto i : ready_parts) {
- CHECK(0 <= i && i < part_count_) << tag("i", i) << tag("part_count", part_count_);
+ if (known_prefix_flag_ && i >= static_cast<int>(known_prefix_size_ / part_size_)) {
+ CHECK(is_upload_);
+ return Status::Error("FILE_UPLOAD_RESTART");
+ }
+ if (is_upload_ && i >= part_count_) {
+ return Status::Error("FILE_UPLOAD_RESTART");
+ }
+ LOG_CHECK(0 <= i && i < part_count_) << tag("i", i) << tag("part_count", part_count_) << tag("size", size_)
+ << tag("part_size", part_size_) << tag("known_prefix_flag", known_prefix_flag_)
+ << tag("known_prefix_size", known_prefix_size_)
+ << tag("real part_count",
+ std::accumulate(ready_parts.begin(), ready_parts.end(), 0,
+ [](auto a, auto b) { return max(a, b + 1); }));
part_status_[i] = PartStatus::Ready;
+ bitmask_.set(i);
auto part = get_part(i);
ready_size_ += narrow_cast<int64>(part.size);
}
checked_prefix_size_ = get_ready_prefix_count() * narrow_cast<int64>(part_size_);
+
+ return Status::OK();
}
void PartsManager::set_need_check() {
need_check_ = true;
+ set_streaming_offset(0, 0);
}
void PartsManager::set_checked_prefix_size(int64 size) {
@@ -291,6 +520,7 @@ void PartsManager::set_checked_prefix_size(int64 size) {
int64 PartsManager::get_checked_prefix_size() const {
return checked_prefix_size_;
}
+
int64 PartsManager::get_unchecked_ready_prefix_size() {
update_first_not_ready_part();
auto count = first_not_ready_part_;
@@ -306,16 +536,14 @@ int64 PartsManager::get_unchecked_ready_prefix_size() {
return res;
}
-Part PartsManager::get_part(int id) {
- int64 offset = narrow_cast<int64>(part_size_) * id;
- int64 size = narrow_cast<int64>(part_size_);
- if (!unknown_size_flag_) {
- auto total_size = get_size();
- if (total_size < offset) {
- size = 0;
- } else {
- size = min(size, total_size - offset);
- }
+Part PartsManager::get_part(int id) const {
+ auto size = narrow_cast<int64>(part_size_);
+ auto offset = size * id;
+ auto total_size = unknown_size_flag_ ? max_size_ : get_size();
+ if (total_size < offset) {
+ size = 0;
+ } else {
+ size = min(size, total_size - offset);
}
return Part{id, offset, static_cast<size_t>(size)};
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.h b/protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.h
index 26c31d5174..fc8c708d77 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/PartsManager.h
@@ -1,17 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/files/FileBitmask.h"
+
#include "td/utils/common.h"
#include "td/utils/Status.h"
namespace td {
-/*** PartsManager***/
struct Part {
int id;
int64 offset;
@@ -21,7 +22,8 @@ struct Part {
class PartsManager {
public:
Status init(int64 size, int64 expected_size, bool is_size_final, size_t part_size,
- const std::vector<int> &ready_parts, bool use_part_count_limit) TD_WARN_UNUSED_RESULT;
+ const std::vector<int> &ready_parts, bool use_part_count_limit, bool is_upload) TD_WARN_UNUSED_RESULT;
+ bool may_finish();
bool ready();
bool unchecked_ready();
Status finish() TD_WARN_UNUSED_RESULT;
@@ -33,55 +35,74 @@ class PartsManager {
Status set_known_prefix(size_t size, bool is_ready);
void set_need_check();
void set_checked_prefix_size(int64 size);
+ int32 set_streaming_offset(int64 offset, int64 limit);
+ void set_streaming_limit(int64 limit);
int64 get_checked_prefix_size() const;
int64 get_unchecked_ready_prefix_size();
int64 get_size() const;
int64 get_size_or_zero() const;
int64 get_expected_size() const;
+ int64 get_estimated_extra() const;
int64 get_ready_size() const;
size_t get_part_size() const;
int32 get_part_count() const;
+ int32 get_unchecked_ready_prefix_count();
int32 get_ready_prefix_count();
+ int64 get_streaming_offset() const;
+ string get_bitmask();
+ int32 get_pending_count() const;
private:
- static constexpr int MAX_PART_COUNT = 3000;
- static constexpr int MAX_PART_SIZE = 512 * (1 << 10);
- static constexpr int64 MAX_FILE_SIZE = MAX_PART_SIZE * MAX_PART_COUNT;
+ static constexpr int MAX_PART_COUNT = 4000;
+ static constexpr int MAX_PART_COUNT_PREMIUM = 8000;
+ static constexpr size_t MAX_PART_SIZE = 512 << 10;
+ static constexpr int64 MAX_FILE_SIZE = static_cast<int64>(MAX_PART_SIZE) * MAX_PART_COUNT_PREMIUM;
- enum class PartStatus { Empty, Pending, Ready };
+ enum class PartStatus : int32 { Empty, Pending, Ready };
+ bool is_upload_{false};
bool need_check_{false};
int64 checked_prefix_size_{0};
bool known_prefix_flag_{false};
- int64 known_prefix_size_;
-
- int64 size_;
- int64 expected_size_;
- int64 min_size_;
- int64 max_size_;
- bool unknown_size_flag_;
- int64 ready_size_;
-
- size_t part_size_;
- int part_count_;
- int pending_count_;
- int first_empty_part_;
- int first_not_ready_part_;
+ int64 known_prefix_size_{0};
+
+ int64 size_{0};
+ int64 expected_size_{0};
+ int64 min_size_{0};
+ int64 max_size_{0};
+ bool unknown_size_flag_{false};
+ int64 ready_size_{0};
+ int64 streaming_ready_size_{0};
+
+ size_t part_size_{0};
+ int part_count_{0};
+ int pending_count_{0};
+ int first_empty_part_{0};
+ int first_not_ready_part_{0};
+ int64 streaming_offset_{0};
+ int64 streaming_limit_{0};
+ int first_streaming_empty_part_{0};
+ int first_streaming_not_ready_part_{0};
vector<PartStatus> part_status_;
- bool use_part_count_limit_;
+ Bitmask bitmask_;
+ bool use_part_count_limit_{false};
- void init_common(const vector<int> &ready_parts);
+ Status init_common(const vector<int> &ready_parts);
Status init_known_prefix(int64 known_prefix, size_t part_size,
const std::vector<int> &ready_parts) TD_WARN_UNUSED_RESULT;
Status init_no_size(size_t part_size, const std::vector<int> &ready_parts) TD_WARN_UNUSED_RESULT;
- Part get_part(int id);
- Part get_empty_part();
+ static Part get_empty_part();
+
+ Part get_part(int id) const;
void on_part_start(int32 id);
void update_first_empty_part();
void update_first_not_ready_part();
+
+ bool is_streaming_limit_reached();
+ bool is_part_in_streaming_limit(int part_i) const;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.cpp
index 00677ddd76..2f9b7bbc8e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.cpp
@@ -1,11 +1,14 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/files/ResourceManager.h"
+#include "td/telegram/files/FileLoaderUtils.h"
+
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
@@ -18,7 +21,7 @@ namespace td {
void ResourceManager::register_worker(ActorShared<FileLoaderActor> callback, int8 priority) {
auto node_id = nodes_container_.create();
auto *node_ptr = nodes_container_.get(node_id);
- *node_ptr = std::make_unique<Node>();
+ *node_ptr = make_unique<Node>();
auto *node = (*node_ptr).get();
CHECK(node);
node->node_id = node_id;
@@ -50,13 +53,11 @@ void ResourceManager::update_resources(const ResourceState &resource_state) {
}
auto node = (*node_ptr).get();
CHECK(node);
- VLOG(files) << "before total: " << resource_state_;
- VLOG(files) << "before " << tag("node_id", node_id) << ": " << node->resource_state_;
+ VLOG(file_loader) << "Before total: " << resource_state_ << "; node " << node_id << ": " << node->resource_state_;
resource_state_ -= node->resource_state_;
node->resource_state_.update_master(resource_state);
resource_state_ += node->resource_state_;
- VLOG(files) << "after total: " << resource_state_;
- VLOG(files) << "after " << tag("node_id", node_id) << ": " << node->resource_state_;
+ VLOG(file_loader) << "After total: " << resource_state_ << "; node " << node_id << ": " << node->resource_state_;
if (mode_ == Mode::Greedy) {
add_to_heap(node);
@@ -104,16 +105,16 @@ bool ResourceManager::satisfy_node(NodeId file_node_id) {
CHECK(file_node);
auto part_size = narrow_cast<int64>(file_node->resource_state_.unit_size());
auto need = file_node->resource_state_.estimated_extra();
- VLOG(files) << tag("need", need) << tag("part_size", part_size);
+ VLOG(file_loader) << tag("need", need) << tag("part_size", part_size);
need = (need + part_size - 1) / part_size * part_size;
- VLOG(files) << tag("need", need);
+ VLOG(file_loader) << tag("need", need);
if (need == 0) {
return true;
}
auto give = resource_state_.unused();
give = min(need, give);
give -= give % part_size;
- VLOG(files) << tag("give", give);
+ VLOG(file_loader) << tag("give", give);
if (give == 0) {
return false;
}
@@ -131,7 +132,7 @@ void ResourceManager::loop() {
return;
}
auto active_limit = resource_state_.active_limit();
- resource_state_.update_limit(2 * 1024 * (1 << 10) - active_limit);
+ resource_state_.update_limit(max_resource_limit_ - active_limit);
LOG(INFO) << tag("unused", resource_state_.unused());
if (mode_ == Mode::Greedy) {
@@ -158,15 +159,17 @@ void ResourceManager::loop() {
}
}
}
+
void ResourceManager::add_node(NodeId node_id, int8 priority) {
if (priority >= 0) {
auto it = std::find_if(to_xload_.begin(), to_xload_.end(), [&](auto &x) { return x.first <= priority; });
to_xload_.insert(it, std::make_pair(priority, node_id));
} else {
auto it = std::find_if(to_xload_.begin(), to_xload_.end(), [&](auto &x) { return x.first < -priority; });
- to_xload_.insert(it, std::make_pair(-priority, node_id));
+ to_xload_.insert(it, std::make_pair(narrow_cast<int8>(-priority), node_id));
}
}
+
bool ResourceManager::remove_node(NodeId node_id) {
auto it = std::find_if(to_xload_.begin(), to_xload_.end(), [&](auto &x) { return x.second == node_id; });
if (it != to_xload_.end()) {
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.h b/protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.h
index 48a32f18f6..688a004c5a 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/ResourceManager.h
@@ -1,27 +1,27 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/files/FileLoaderActor.h"
#include "td/telegram/files/ResourceState.h"
+#include "td/actor/actor.h"
+
#include "td/utils/Container.h"
#include "td/utils/Heap.h"
#include <utility>
namespace td {
-class ResourceManager : public Actor {
+
+class ResourceManager final : public Actor {
public:
- enum class Mode { Baseline, Greedy };
- explicit ResourceManager(Mode mode) : mode_(mode) {
+ enum class Mode : int32 { Baseline, Greedy };
+ ResourceManager(int64 max_resource_limit, Mode mode) : max_resource_limit_(max_resource_limit), mode_(mode) {
}
// use through ActorShared
void update_priority(int8 priority);
@@ -30,10 +30,12 @@ class ResourceManager : public Actor {
void register_worker(ActorShared<FileLoaderActor> callback, int8 priority);
private:
+ int64 max_resource_limit_ = 0;
Mode mode_;
+
using NodeId = uint64;
- struct Node : public HeapNode {
- NodeId node_id;
+ struct Node final : public HeapNode {
+ NodeId node_id = 0;
ResourceState resource_state_;
ActorShared<FileLoaderActor> callback_;
@@ -46,7 +48,7 @@ class ResourceManager : public Actor {
}
};
- Container<std::unique_ptr<Node>> nodes_container_;
+ Container<unique_ptr<Node>> nodes_container_;
vector<std::pair<int8, NodeId>> to_xload_;
KHeap<int64> by_estimated_extra_;
ResourceState resource_state_;
@@ -54,13 +56,14 @@ class ResourceManager : public Actor {
ActorShared<> parent_;
bool stop_flag_ = false;
- void hangup_shared() override;
+ void hangup_shared() final;
- void loop() override;
+ void loop() final;
void add_to_heap(Node *node);
bool satisfy_node(NodeId file_node_id);
void add_node(NodeId node_id, int8 priority);
bool remove_node(NodeId node_id);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/files/ResourceState.h b/protocols/Telegram/tdlib/td/td/telegram/files/ResourceState.h
index e6a251ce95..73c9eaee98 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/files/ResourceState.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/files/ResourceState.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,7 +8,6 @@
#include "td/utils/common.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
#include "td/utils/StringBuilder.h"
namespace td {
@@ -31,7 +30,18 @@ class ResourceState {
}
bool update_estimated_limit(int64 extra) {
- auto new_estimated_limit = used_ + extra;
+ // unused() must be positive, i.e. used_ + using_ must be less than limit_
+ // TODO: use exact intersection between using_ and extra.
+ auto using_and_extra_intersection = min(using_, extra); // between 0 and min(using_, extra)
+ auto new_estimated_limit = used_ + using_ + extra - using_and_extra_intersection;
+
+ // Use extra extra limit
+ if (new_estimated_limit < limit_) {
+ auto extra_limit = limit_ - new_estimated_limit;
+ used_ += extra_limit;
+ new_estimated_limit += extra_limit;
+ }
+
if (new_estimated_limit == estimated_limit_) {
return false;
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEvent.h b/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEvent.h
index 9275c6faeb..349ea8608b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEvent.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEvent.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,20 +10,20 @@
#include "td/telegram/Version.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
-#include "td/utils/Storer.h"
+#include "td/utils/StorerBase.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/tl_parsers.h"
#include "td/utils/tl_storers.h"
-#include <type_traits>
-
namespace td {
-namespace logevent {
+namespace log_event {
template <class ParentT>
class WithVersion : public ParentT {
@@ -37,7 +37,7 @@ class WithVersion : public ParentT {
}
private:
- int32 version_;
+ int32 version_{};
};
template <class ParentT, class ContextT>
@@ -52,7 +52,7 @@ class WithContext : public ParentT {
}
private:
- ContextT context_;
+ ContextT context_{};
};
class LogEvent {
@@ -68,150 +68,79 @@ class LogEvent {
Channels = 4,
SecretChatInfos = 5,
WebPages = 0x10,
+ SetPollAnswer = 0x20,
+ StopPoll = 0x21,
SendMessage = 0x100,
DeleteMessage = 0x101,
- DeleteMessagesFromServer = 0x102,
+ DeleteMessagesOnServer = 0x102,
ReadHistoryOnServer = 0x103,
ForwardMessages = 0x104,
ReadMessageContentsOnServer = 0x105,
SendBotStartMessage = 0x106,
SendScreenshotTakenNotificationMessage = 0x107,
SendInlineQueryResultMessage = 0x108,
- DeleteDialogHistoryFromServer = 0x109,
+ DeleteDialogHistoryOnServer = 0x109,
ReadAllDialogMentionsOnServer = 0x10a,
- DeleteAllChannelMessagesFromUserOnServer = 0x10b,
+ DeleteAllChannelMessagesFromSenderOnServer = 0x10b,
ToggleDialogIsPinnedOnServer = 0x10c,
ReorderPinnedDialogsOnServer = 0x10d,
SaveDialogDraftMessageOnServer = 0x10e,
+ UpdateDialogNotificationSettingsOnServer = 0x10f,
+ UpdateScopeNotificationSettingsOnServer = 0x110,
+ ResetAllNotificationSettingsOnServer = 0x111,
+ ToggleDialogReportSpamStateOnServer = 0x112,
+ RegetDialog = 0x113,
+ ReadHistoryInSecretChat = 0x114,
+ ToggleDialogIsMarkedAsUnreadOnServer = 0x115,
+ SetDialogFolderIdOnServer = 0x116,
+ DeleteScheduledMessagesOnServer = 0x117,
+ ToggleDialogIsBlockedOnServer = 0x118,
+ ReadMessageThreadHistoryOnServer = 0x119,
+ BlockMessageSenderFromRepliesOnServer = 0x120,
+ UnpinAllDialogMessagesOnServer = 0x121,
+ DeleteAllCallMessagesOnServer = 0x122,
+ DeleteDialogMessagesByDateOnServer = 0x123,
+ ReadAllDialogReactionsOnServer = 0x124,
+ DeleteTopicHistoryOnServer = 0x125,
GetChannelDifference = 0x140,
+ AddMessagePushNotification = 0x200,
+ EditMessagePushNotification = 0x201,
+ SaveAppLog = 0x300,
ConfigPmcMagic = 0x1f18,
BinlogPmcMagic = 0x4327
};
using Id = uint64;
- Id logevent_id() const {
- return logevent_id_;
+ Id log_event_id() const {
+ return log_event_id_;
}
- void set_logevent_id(Id logevent_id) {
- logevent_id_ = logevent_id;
+ void set_log_event_id(Id log_event_id) {
+ log_event_id_ = log_event_id;
}
virtual StringBuilder &print(StringBuilder &sb) const {
- return sb << "[Logevent " << tag("id", logevent_id()) << "]";
+ return sb << "[Logevent " << tag("id", log_event_id()) << "]";
}
private:
- Id logevent_id_;
+ Id log_event_id_{};
};
+
inline StringBuilder &operator<<(StringBuilder &sb, const LogEvent &log_event) {
return log_event.print(sb);
}
-namespace detail {
-
-template <class EventT>
-int32 magic(EventT &event) {
- return static_cast<int32>(event.get_type());
-}
-
-template <class EventT, class StorerT>
-void store(const EventT &event, StorerT &storer) {
- EventT::downcast_call(event.get_type(),
- [&](auto *ptr) { static_cast<const std::decay_t<decltype(*ptr)> &>(event).store(storer); });
-}
-
-template <class DestT, class T>
-Result<std::unique_ptr<DestT>> from_parser(T &&parser) {
- auto version = parser.fetch_int();
- parser.set_version(version);
- parser.set_context(G());
- auto magic = static_cast<typename DestT::Type>(parser.fetch_int());
-
- std::unique_ptr<DestT> event;
- DestT::downcast_call(magic, [&](auto *ptr) {
- auto tmp = make_unique<std::decay_t<decltype(*ptr)>>();
- tmp->parse(parser);
- event = std::move(tmp);
- });
- parser.fetch_end();
- TRY_STATUS(parser.get_status());
- if (event) {
- return std::move(event);
- }
- return Status::Error(PSLICE() << "Unknown SecretChatEvent type: " << format::as_hex(magic));
-}
-
-template <class DestT>
-Result<std::unique_ptr<DestT>> from_buffer_slice(BufferSlice slice) {
- return from_parser<DestT>(WithVersion<WithContext<TlBufferParser, Global *>>{&slice});
-}
-
-template <class T>
-class StorerImpl : public Storer {
- public:
- explicit StorerImpl(const T &event) : event_(event) {
- }
-
- size_t size() const override {
- WithContext<TlStorerCalcLength, Global *> storer;
- storer.set_context(G());
-
- storer.store_int(T::version());
- td::store(magic(event_), storer);
- td::store(event_, storer);
- return storer.get_length();
- }
- size_t store(uint8 *ptr_x) const override {
- char *ptr = reinterpret_cast<char *>(ptr_x);
- WithContext<TlStorerUnsafe, Global *> storer(ptr);
- storer.set_context(G());
-
- storer.store_int(T::version());
- td::store(magic(event_), storer);
- td::store(event_, storer);
- return storer.get_buf() - ptr;
- }
-
- private:
- const T &event_;
-};
-} // namespace detail
-
-template <class ChildT>
-class LogEventBase : public LogEvent {
- public:
- template <class StorerT>
- void store(StorerT &storer) const {
- detail::store(static_cast<const ChildT &>(*this), storer);
- }
- static Result<std::unique_ptr<ChildT>> from_buffer_slice(BufferSlice slice) {
- return detail::from_buffer_slice<ChildT>(std::move(slice));
- }
-};
-
-template <class ChildT, class ParentT>
-class LogEventHelper : public ParentT {
- public:
- typename ParentT::Type get_type() const override {
- return ChildT::type;
- }
-
- constexpr int32 magic() const {
- return static_cast<int32>(get_type());
- }
-};
-
-class LogEventParser : public WithVersion<WithContext<TlParser, Global *>> {
+class LogEventParser final : public WithVersion<WithContext<TlParser, Global *>> {
public:
explicit LogEventParser(Slice data) : WithVersion<WithContext<TlParser, Global *>>(data) {
set_version(fetch_int());
- CHECK(version() < static_cast<int32>(Version::Next)) << "Wrong version " << version();
+ LOG_CHECK(version() < static_cast<int32>(Version::Next)) << "Wrong version " << version();
set_context(G());
}
};
-class LogEventStorerCalcLength : public WithContext<TlStorerCalcLength, Global *> {
+class LogEventStorerCalcLength final : public WithContext<TlStorerCalcLength, Global *> {
public:
LogEventStorerCalcLength() : WithContext<TlStorerCalcLength, Global *>() {
store_int(static_cast<int32>(Version::Next) - 1);
@@ -219,73 +148,84 @@ class LogEventStorerCalcLength : public WithContext<TlStorerCalcLength, Global *
}
};
-class LogEventStorerUnsafe : public WithContext<TlStorerUnsafe, Global *> {
+class LogEventStorerUnsafe final : public WithContext<TlStorerUnsafe, Global *> {
public:
- explicit LogEventStorerUnsafe(char *buf) : WithContext<TlStorerUnsafe, Global *>(buf) {
+ explicit LogEventStorerUnsafe(unsigned char *buf) : WithContext<TlStorerUnsafe, Global *>(buf) {
store_int(static_cast<int32>(Version::Next) - 1);
set_context(G());
}
};
-} // namespace logevent
-
-using LogEvent = logevent::LogEvent;
-using LogEventParser = logevent::LogEventParser;
-using LogEventStorerCalcLength = logevent::LogEventStorerCalcLength;
-using LogEventStorerUnsafe = logevent::LogEventStorerUnsafe;
-
-template <class T>
-Status log_event_parse(T &data, Slice slice) TD_WARN_UNUSED_RESULT;
-
-template <class T>
-Status log_event_parse(T &data, Slice slice) {
- LogEventParser parser(slice);
- parse(data, parser);
- parser.fetch_end();
- return parser.get_status();
-}
-
template <class T>
-class LogEventStorerImpl : public Storer {
+class LogEventStorerImpl final : public Storer {
public:
explicit LogEventStorerImpl(const T &event) : event_(event) {
}
- size_t size() const override {
+ size_t size() const final {
LogEventStorerCalcLength storer;
td::store(event_, storer);
return storer.get_length();
}
- size_t store(uint8 *ptr_x) const override {
- char *ptr = reinterpret_cast<char *>(ptr_x);
+ size_t store(uint8 *ptr) const final {
LogEventStorerUnsafe storer(ptr);
td::store(event_, storer);
#ifdef TD_DEBUG
T check_result;
log_event_parse(check_result, Slice(ptr, storer.get_buf())).ensure();
#endif
- return storer.get_buf() - ptr;
+ return static_cast<size_t>(storer.get_buf() - ptr);
}
private:
const T &event_;
};
+} // namespace log_event
+
+using LogEvent = log_event::LogEvent;
+using LogEventParser = log_event::LogEventParser;
+using LogEventStorerCalcLength = log_event::LogEventStorerCalcLength;
+using LogEventStorerUnsafe = log_event::LogEventStorerUnsafe;
+
+template <class T>
+Status log_event_parse(T &data, Slice slice) TD_WARN_UNUSED_RESULT;
+
+template <class T>
+Status log_event_parse(T &data, Slice slice) {
+ LogEventParser parser(slice);
+ parse(data, parser);
+ parser.fetch_end();
+ return parser.get_status();
+}
+
template <class T>
-BufferSlice log_event_store(const T &data) {
+BufferSlice log_event_store_impl(const T &data, const char *file, int line) {
LogEventStorerCalcLength storer_calc_length;
store(data, storer_calc_length);
BufferSlice value_buffer{storer_calc_length.get_length()};
+ auto ptr = value_buffer.as_slice().ubegin();
+ LOG_CHECK(is_aligned_pointer<4>(ptr)) << ptr;
- LogEventStorerUnsafe storer_unsafe(value_buffer.as_slice().begin());
+ LogEventStorerUnsafe storer_unsafe(ptr);
store(data, storer_unsafe);
#ifdef TD_DEBUG
T check_result;
- log_event_parse(check_result, value_buffer.as_slice()).ensure();
+ auto status = log_event_parse(check_result, value_buffer.as_slice());
+ if (status.is_error()) {
+ LOG(FATAL) << status << ' ' << file << ' ' << line;
+ }
#endif
return value_buffer;
}
+#define log_event_store(data) log_event_store_impl((data), __FILE__, __LINE__)
+
+template <class T>
+log_event::LogEventStorerImpl<T> get_log_event_storer(const T &event) {
+ return log_event::LogEventStorerImpl<T>(event);
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEventHelper.cpp b/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEventHelper.cpp
new file mode 100644
index 0000000000..0810717f45
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEventHelper.cpp
@@ -0,0 +1,56 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/logevent/LogEventHelper.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/TdDb.h"
+
+#include "td/db/binlog/BinlogHelper.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+void add_log_event(LogEventIdWithGeneration &log_event_id, const Storer &storer, uint32 type, Slice name) {
+ LOG(INFO) << "Save " << name << " to binlog";
+ if (log_event_id.log_event_id == 0) {
+ log_event_id.log_event_id = binlog_add(G()->td_db()->get_binlog(), type, storer);
+ LOG(INFO) << "Add " << name << " log event " << log_event_id.log_event_id;
+ } else {
+ auto new_log_event_id = binlog_rewrite(G()->td_db()->get_binlog(), log_event_id.log_event_id, type, storer);
+ LOG(INFO) << "Rewrite " << name << " log event " << log_event_id.log_event_id << " with " << new_log_event_id;
+ }
+ log_event_id.generation++;
+}
+
+void delete_log_event(LogEventIdWithGeneration &log_event_id, uint64 generation, Slice name) {
+ LOG(INFO) << "Finish to process " << name << " log event " << log_event_id.log_event_id << " with generation "
+ << generation;
+ if (log_event_id.generation == generation) {
+ CHECK(log_event_id.log_event_id != 0);
+ LOG(INFO) << "Delete " << name << " log event " << log_event_id.log_event_id;
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id.log_event_id);
+ log_event_id.log_event_id = 0;
+ }
+}
+
+Promise<Unit> get_erase_log_event_promise(uint64 log_event_id, Promise<Unit> promise) {
+ if (log_event_id == 0) {
+ return promise;
+ }
+
+ return PromiseCreator::lambda([log_event_id, promise = std::move(promise)](Result<Unit> result) mutable {
+ if (!G()->close_flag()) {
+ binlog_erase(G()->td_db()->get_binlog(), log_event_id);
+ }
+
+ promise.set_result(std::move(result));
+ });
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEventHelper.h b/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEventHelper.h
new file mode 100644
index 0000000000..b1beb6756e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/logevent/LogEventHelper.h
@@ -0,0 +1,57 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/Global.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StorerBase.h"
+#include "td/utils/Time.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+struct LogEventIdWithGeneration {
+ uint64 log_event_id = 0;
+ uint64 generation = 0;
+};
+
+void add_log_event(LogEventIdWithGeneration &log_event_id, const Storer &storer, uint32 type, Slice name);
+
+void delete_log_event(LogEventIdWithGeneration &log_event_id, uint64 generation, Slice name);
+
+Promise<Unit> get_erase_log_event_promise(uint64 log_event_id, Promise<Unit> promise = Promise<Unit>());
+
+template <class StorerT>
+void store_time(double time_at, StorerT &storer) {
+ if (time_at == 0) {
+ store(-1.0, storer);
+ } else {
+ double time_left = max(time_at - Time::now(), 0.0);
+ store(time_left, storer);
+ store(get_global_server_time(), storer);
+ }
+}
+
+template <class ParserT>
+void parse_time(double &time_at, ParserT &parser) {
+ double time_left;
+ parse(time_left, parser);
+ if (time_left < -0.1) {
+ time_at = 0;
+ } else {
+ double old_server_time;
+ parse(old_server_time, parser);
+ double passed_server_time = max(parser.context()->server_time() - old_server_time, 0.0);
+ time_left = max(time_left - passed_server_time, 0.0);
+ time_at = Time::now_cached() + time_left;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/logevent/SecretChatEvent.h b/protocols/Telegram/tdlib/td/td/telegram/logevent/SecretChatEvent.h
index 5b12f7b014..4072eaf6ea 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/logevent/SecretChatEvent.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/logevent/SecretChatEvent.h
@@ -1,27 +1,69 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/EncryptedFile.h"
+#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/secret_api.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
-#include "td/utils/Storer.h"
+#include "td/utils/Promise.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
+#include "td/utils/StorerBase.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_helpers.h"
+#include "td/utils/tl_parsers.h"
+#include "td/utils/tl_storers.h"
-#include "td/telegram/secret_api.h"
-#include "td/telegram/telegram_api.h"
+#include <type_traits>
namespace td {
-namespace logevent {
+namespace log_event {
+
+namespace detail {
+
+template <class T>
+class StorerImpl final : public Storer {
+ public:
+ explicit StorerImpl(const T &event) : event_(event) {
+ }
-class SecretChatEvent : public LogEventBase<SecretChatEvent> {
+ size_t size() const final {
+ WithContext<TlStorerCalcLength, Global *> storer;
+ storer.set_context(G());
+
+ storer.store_int(T::version());
+ td::store(static_cast<int32>(event_.get_type()), storer);
+ td::store(event_, storer);
+ return storer.get_length();
+ }
+ size_t store(uint8 *ptr) const final {
+ WithContext<TlStorerUnsafe, Global *> storer(ptr);
+ storer.set_context(G());
+
+ storer.store_int(T::version());
+ td::store(static_cast<int32>(event_.get_type()), storer);
+ td::store(event_, storer);
+ return static_cast<size_t>(storer.get_buf() - ptr);
+ }
+
+ private:
+ const T &event_;
+};
+
+} // namespace detail
+
+class SecretChatEvent : public LogEvent {
public:
// append only enum
enum class Type : int32 {
@@ -33,16 +75,51 @@ class SecretChatEvent : public LogEventBase<SecretChatEvent> {
virtual Type get_type() const = 0;
- static constexpr LogEvent::HandlerType get_handler_type() {
- return LogEvent::HandlerType::SecretChats;
- }
-
static constexpr int32 version() {
- return 2;
+ return 4;
}
template <class F>
static void downcast_call(Type type, F &&f);
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ downcast_call(get_type(),
+ [&](auto *ptr) { static_cast<const std::decay_t<decltype(*ptr)> *>(this)->store(storer); });
+ }
+
+ static Result<unique_ptr<SecretChatEvent>> from_buffer_slice(BufferSlice slice) {
+ WithVersion<WithContext<TlBufferParser, Global *>> parser{&slice};
+ auto version = parser.fetch_int();
+ parser.set_version(version);
+ parser.set_context(G());
+ auto magic = static_cast<Type>(parser.fetch_int());
+
+ unique_ptr<SecretChatEvent> event;
+ downcast_call(magic, [&](auto *ptr) {
+ auto tmp = make_unique<std::decay_t<decltype(*ptr)>>();
+ tmp->parse(parser);
+ event = std::move(tmp);
+ });
+ parser.fetch_end();
+ TRY_STATUS(parser.get_status());
+ if (event != nullptr) {
+ return std::move(event);
+ }
+ return Status::Error(PSLICE() << "Unknown SecretChatEvent type: " << format::as_hex(magic));
+ }
+};
+
+template <class ChildT>
+class SecretChatLogEventBase : public SecretChatEvent {
+ public:
+ typename SecretChatEvent::Type get_type() const final {
+ return ChildT::type;
+ }
+
+ constexpr int32 magic() const {
+ return static_cast<int32>(get_type());
+ }
};
// Internal structure
@@ -52,16 +129,17 @@ class SecretChatEvent : public LogEventBase<SecretChatEvent> {
// inputEncryptedFile#5a17b5e5 id:long access_hash:long = InputEncryptedFile;
// inputEncryptedFileBigUploaded#2dc173c8 id:long parts:int key_fingerprint:int = InputEncryptedFile;
struct EncryptedInputFile {
- static constexpr int32 magic = 0x4328d38a;
- enum Type : int32 { Empty = 0, Uploaded = 1, BigUploaded = 2, Location = 3 } type;
- int64 id;
- int64 access_hash;
- int32 parts;
- int32 key_fingerprint;
- template <class T>
- void store(T &storer) const {
+ static constexpr int32 MAGIC = 0x4328d38a;
+ enum Type : int32 { Empty = 0, Uploaded = 1, BigUploaded = 2, Location = 3 } type = Type::Empty;
+ int64 id = 0;
+ int64 access_hash = 0;
+ int32 parts = 0;
+ int32 key_fingerprint = 0;
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
using td::store;
- store(magic, storer);
+ store(MAGIC, storer);
store(type, storer);
store(id, storer);
store(access_hash, storer);
@@ -69,12 +147,17 @@ struct EncryptedInputFile {
store(key_fingerprint, storer);
}
+ EncryptedInputFile() = default;
+ EncryptedInputFile(Type type, int64 id, int64 access_hash, int32 parts, int32 key_fingerprint)
+ : type(type), id(id), access_hash(access_hash), parts(parts), key_fingerprint(key_fingerprint) {
+ }
+
bool empty() const {
return type == Empty;
}
- template <class T>
- void parse(T &parser) {
+ template <class ParserT>
+ void parse(ParserT &parser) {
using td::parse;
int32 got_magic;
@@ -85,37 +168,36 @@ struct EncryptedInputFile {
parse(parts, parser);
parse(key_fingerprint, parser);
- if (got_magic != magic) {
+ if (got_magic != MAGIC) {
parser.set_error("EncryptedInputFile magic mismatch");
return;
}
}
static EncryptedInputFile from_input_encrypted_file(const tl_object_ptr<telegram_api::InputEncryptedFile> &from) {
- if (!from) {
- return EncryptedInputFile{Empty, 0, 0, 0, 0};
+ if (from == nullptr) {
+ return EncryptedInputFile();
}
- return from_input_encrypted_file(*from);
- }
- static EncryptedInputFile from_input_encrypted_file(const telegram_api::InputEncryptedFile &from) {
- switch (from.get_id()) {
+ switch (from->get_id()) {
case telegram_api::inputEncryptedFileEmpty::ID:
return EncryptedInputFile{Empty, 0, 0, 0, 0};
case telegram_api::inputEncryptedFileUploaded::ID: {
- auto &uploaded = static_cast<const telegram_api::inputEncryptedFileUploaded &>(from);
+ auto &uploaded = static_cast<const telegram_api::inputEncryptedFileUploaded &>(*from);
return EncryptedInputFile{Uploaded, uploaded.id_, 0, uploaded.parts_, uploaded.key_fingerprint_};
}
case telegram_api::inputEncryptedFileBigUploaded::ID: {
- auto &uploaded = static_cast<const telegram_api::inputEncryptedFileBigUploaded &>(from);
+ auto &uploaded = static_cast<const telegram_api::inputEncryptedFileBigUploaded &>(*from);
return EncryptedInputFile{BigUploaded, uploaded.id_, 0, uploaded.parts_, uploaded.key_fingerprint_};
}
case telegram_api::inputEncryptedFile::ID: {
- auto &uploaded = static_cast<const telegram_api::inputEncryptedFile &>(from);
+ auto &uploaded = static_cast<const telegram_api::inputEncryptedFile &>(*from);
return EncryptedInputFile{Location, uploaded.id_, uploaded.access_hash_, 0, 0};
}
default:
UNREACHABLE();
+ return EncryptedInputFile();
}
}
+
tl_object_ptr<telegram_api::InputEncryptedFile> as_input_encrypted_file() const {
switch (type) {
case Empty:
@@ -128,6 +210,7 @@ struct EncryptedInputFile {
return make_tl_object<telegram_api::inputEncryptedFile>(id, access_hash);
}
UNREACHABLE();
+ return nullptr;
}
};
@@ -135,65 +218,17 @@ inline StringBuilder &operator<<(StringBuilder &sb, const EncryptedInputFile &fi
return sb << to_string(file.as_input_encrypted_file());
}
-// encryptedFile#4a70994c id:long access_hash:long size:int dc_id:int key_fingerprint:int = EncryptedFile;
-struct EncryptedFileLocation {
- static constexpr int32 magic = 0x473d738a;
- int64 id;
- int64 access_hash;
- int32 size;
- int32 dc_id;
- int32 key_fingerprint;
-
- tl_object_ptr<telegram_api::encryptedFile> as_encrypted_file() {
- return make_tl_object<telegram_api::encryptedFile>(id, access_hash, size, dc_id, key_fingerprint);
- }
- template <class T>
- void store(T &storer) const {
- using td::store;
- store(magic, storer);
- store(id, storer);
- store(access_hash, storer);
- store(size, storer);
- store(dc_id, storer);
- store(key_fingerprint, storer);
- }
-
- template <class T>
- void parse(T &parser) {
- using td::parse;
- int32 got_magic;
-
- parse(got_magic, parser);
- parse(id, parser);
- parse(access_hash, parser);
- parse(size, parser);
- parse(dc_id, parser);
- parse(key_fingerprint, parser);
-
- if (got_magic != magic) {
- parser.set_error("EncryptedFileLocation magic mismatch");
- return;
- }
- }
-};
-
-inline StringBuilder &operator<<(StringBuilder &sb, const EncryptedFileLocation &file) {
- return sb << "[" << tag("id", file.id) << tag("access_hash", file.access_hash) << tag("size", file.size)
- << tag("dc_id", file.dc_id) << tag("key_fingerprint", file.key_fingerprint) << "]";
-}
-
// LogEvents
// TODO: Qts and SeqNoState could be just Logevents that are updated during regenerate
-class InboundSecretMessage : public LogEventHelper<InboundSecretMessage, SecretChatEvent> {
+class InboundSecretMessage final : public SecretChatLogEventBase<InboundSecretMessage> {
public:
static constexpr Type type = SecretChatEvent::Type::InboundSecretMessage;
- int32 qts = 0;
int32 chat_id = 0;
int32 date = 0;
BufferSlice encrypted_message; // empty when we store event to binlog
- Promise<Unit> qts_ack;
+ Promise<Unit> promise;
bool is_checked = false;
// after decrypted and checked
@@ -205,25 +240,29 @@ class InboundSecretMessage : public LogEventHelper<InboundSecretMessage, SecretC
int32 my_out_seq_no = -1;
int32 his_in_seq_no = -1;
- EncryptedFileLocation file;
+ int32 his_layer() const {
+ return decrypted_message_layer->layer_;
+ }
+
+ unique_ptr<EncryptedFile> file;
- bool has_encrypted_file;
bool is_pending = false;
- template <class T>
- void store(T &storer) const {
+ template <class StorerT>
+ void store(StorerT &storer) const {
using td::store;
+ bool has_encrypted_file = file != nullptr;
BEGIN_STORE_FLAGS();
STORE_FLAG(has_encrypted_file);
STORE_FLAG(is_pending);
+ STORE_FLAG(true);
END_STORE_FLAGS();
- store(qts, storer);
store(chat_id, storer);
store(date, storer);
// skip encrypted_message
- // skip qts_ack
+ // skip promise
// TODO
decrypted_message_layer->store(storer);
@@ -238,20 +277,26 @@ class InboundSecretMessage : public LogEventHelper<InboundSecretMessage, SecretC
}
}
- template <class T>
- void parse(T &parser) {
+ template <class ParserT>
+ void parse(ParserT &parser) {
using td::parse;
+ bool has_encrypted_file;
+ bool no_qts;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_encrypted_file);
PARSE_FLAG(is_pending);
+ PARSE_FLAG(no_qts);
END_PARSE_FLAGS();
- parse(qts, parser);
+ if (!no_qts) {
+ int32 legacy_qts;
+ parse(legacy_qts, parser);
+ }
parse(chat_id, parser);
parse(date, parser);
// skip encrypted_message
- // skip qts_ack
+ // skip promise
// TODO
decrypted_message_layer = secret_api::decryptedMessageLayer::fetch(parser);
@@ -268,16 +313,16 @@ class InboundSecretMessage : public LogEventHelper<InboundSecretMessage, SecretC
is_checked = true;
}
- StringBuilder &print(StringBuilder &sb) const override {
- return sb << "[Logevent InboundSecretMessage " << tag("id", logevent_id())
- << tag("auth_key_id", format::as_hex(auth_key_id)) << tag("message_id", message_id)
+ StringBuilder &print(StringBuilder &sb) const final {
+ return sb << "[Logevent InboundSecretMessage " << tag("id", log_event_id()) << tag("chat_id", chat_id)
+ << tag("date", date) << tag("auth_key_id", format::as_hex(auth_key_id)) << tag("message_id", message_id)
<< tag("my_in_seq_no", my_in_seq_no) << tag("my_out_seq_no", my_out_seq_no)
<< tag("his_in_seq_no", his_in_seq_no) << tag("message", to_string(decrypted_message_layer))
- << tag("is_pending", is_pending) << format::cond(has_encrypted_file, tag("file", file)) << "]";
+ << tag("is_pending", is_pending) << format::cond(file != nullptr, tag("file", *file)) << "]";
}
};
-class OutboundSecretMessage : public LogEventHelper<OutboundSecretMessage, SecretChatEvent> {
+class OutboundSecretMessage final : public SecretChatLogEventBase<OutboundSecretMessage> {
public:
static constexpr Type type = SecretChatEvent::Type::OutboundSecretMessage;
@@ -292,10 +337,18 @@ class OutboundSecretMessage : public LogEventHelper<OutboundSecretMessage, Secre
int32 my_out_seq_no = -1;
int32 his_in_seq_no = -1;
+ int32 his_layer() const {
+ return -1;
+ }
+
bool is_sent = false;
- bool is_service = false;
+ // need send push notification to the receiver
+ // should send such messages with messages_sendEncryptedService
+ bool need_notify_user = false;
bool is_rewritable = false;
+ // should notify our parent about state of this message (using context and random_id)
bool is_external = false;
+ bool is_silent = false;
tl_object_ptr<secret_api::DecryptedMessageAction> action;
uint64 crc = 0; // DEBUG;
@@ -303,12 +356,11 @@ class OutboundSecretMessage : public LogEventHelper<OutboundSecretMessage, Secre
// Flags:
// 2. can_fail = !file.empty() // send of other messages can't fail if chat is ok. It is usless to rewrite them with
// empty
- // 1. is_service // use messages_sendEncryptedsService
// 3. can_rewrite_with_empty // false for almost all service messages
- // TODO: combine this two functions into one macros hell. Or lambda hell.
- template <class T>
- void store(T &storer) const {
+ // TODO: combine these two functions into one macros hell. Or a lambda hell.
+ template <class StorerT>
+ void store(StorerT &storer) const {
using td::store;
store(chat_id, storer);
@@ -320,13 +372,14 @@ class OutboundSecretMessage : public LogEventHelper<OutboundSecretMessage, Secre
store(my_out_seq_no, storer);
store(his_in_seq_no, storer);
- bool has_action = static_cast<bool>(action);
+ bool has_action = action != nullptr;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_sent);
- STORE_FLAG(is_service);
+ STORE_FLAG(need_notify_user);
STORE_FLAG(has_action);
STORE_FLAG(is_rewritable);
STORE_FLAG(is_external);
+ STORE_FLAG(is_silent);
END_STORE_FLAGS();
if (has_action) {
@@ -337,8 +390,8 @@ class OutboundSecretMessage : public LogEventHelper<OutboundSecretMessage, Secre
}
}
- template <class T>
- void parse(T &parser) {
+ template <class ParserT>
+ void parse(ParserT &parser) {
using td::parse;
parse(chat_id, parser);
@@ -353,10 +406,11 @@ class OutboundSecretMessage : public LogEventHelper<OutboundSecretMessage, Secre
bool has_action;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_sent);
- PARSE_FLAG(is_service);
+ PARSE_FLAG(need_notify_user);
PARSE_FLAG(has_action);
PARSE_FLAG(is_rewritable);
PARSE_FLAG(is_external);
+ PARSE_FLAG(is_silent);
END_PARSE_FLAGS();
if (has_action) {
@@ -365,62 +419,76 @@ class OutboundSecretMessage : public LogEventHelper<OutboundSecretMessage, Secre
}
}
- StringBuilder &print(StringBuilder &sb) const override {
- return sb << "[Logevent OutboundSecretMessage " << tag("id", logevent_id()) << tag("is_sent", is_sent)
- << tag("is_service", is_service) << tag("is_rewritable", is_rewritable) << tag("is_external", is_external)
- << tag("message_id", message_id) << tag("random_id", random_id) << tag("my_in_seq_no", my_in_seq_no)
- << tag("my_out_seq_no", my_out_seq_no) << tag("his_in_seq_no", his_in_seq_no) << tag("file", file)
- << tag("action", to_string(action)) << "]";
+ StringBuilder &print(StringBuilder &sb) const final {
+ return sb << "[Logevent OutboundSecretMessage " << tag("id", log_event_id()) << tag("chat_id", chat_id)
+ << tag("is_sent", is_sent) << tag("need_notify_user", need_notify_user)
+ << tag("is_rewritable", is_rewritable) << tag("is_external", is_external) << tag("message_id", message_id)
+ << tag("random_id", random_id) << tag("my_in_seq_no", my_in_seq_no) << tag("my_out_seq_no", my_out_seq_no)
+ << tag("his_in_seq_no", his_in_seq_no) << tag("file", file) << tag("action", to_string(action)) << "]";
}
};
-class CloseSecretChat : public LogEventHelper<CloseSecretChat, SecretChatEvent> {
+class CloseSecretChat final : public SecretChatLogEventBase<CloseSecretChat> {
public:
static constexpr Type type = SecretChatEvent::Type::CloseSecretChat;
int32 chat_id = 0;
+ bool delete_history = false;
+ bool is_already_discarded = false;
- template <class T>
- void store(T &storer) const {
+ template <class StorerT>
+ void store(StorerT &storer) const {
using td::store;
+ BEGIN_STORE_FLAGS();
+ STORE_FLAG(delete_history);
+ STORE_FLAG(is_already_discarded);
+ END_STORE_FLAGS();
store(chat_id, storer);
}
- template <class T>
- void parse(T &parser) {
+ template <class ParserT>
+ void parse(ParserT &parser) {
using td::parse;
+ if (parser.version() >= 3) {
+ BEGIN_PARSE_FLAGS();
+ PARSE_FLAG(delete_history);
+ PARSE_FLAG(is_already_discarded);
+ END_PARSE_FLAGS();
+ }
parse(chat_id, parser);
}
- StringBuilder &print(StringBuilder &sb) const override {
- return sb << "[Logevent CloseSecretChat " << tag("id", logevent_id()) << tag("chat_id", chat_id) << "]";
+ StringBuilder &print(StringBuilder &sb) const final {
+ return sb << "[Logevent CloseSecretChat " << tag("id", log_event_id()) << tag("chat_id", chat_id)
+ << tag("delete_history", delete_history) << tag("is_already_discarded", is_already_discarded) << "]";
}
};
-class CreateSecretChat : public LogEventHelper<CreateSecretChat, SecretChatEvent> {
+class CreateSecretChat final : public SecretChatLogEventBase<CreateSecretChat> {
public:
static constexpr Type type = SecretChatEvent::Type::CreateSecretChat;
int32 random_id = 0;
- int32 user_id = 0;
+ UserId user_id;
int64 user_access_hash = 0;
- template <class T>
- void store(T &storer) const {
+ template <class StorerT>
+ void store(StorerT &storer) const {
using td::store;
store(random_id, storer);
store(user_id, storer);
store(user_access_hash, storer);
}
- template <class T>
- void parse(T &parser) {
+ template <class ParserT>
+ void parse(ParserT &parser) {
using td::parse;
parse(random_id, parser);
- parse(user_id, parser);
+ user_id = UserId(parser.version() >= 4 ? parser.fetch_long() : static_cast<int64>(parser.fetch_int()));
parse(user_access_hash, parser);
}
- StringBuilder &print(StringBuilder &sb) const override {
- return sb << "[Logevent CreateSecretChat " << tag("id", logevent_id()) << tag("chat_id", random_id) << "]";
+ StringBuilder &print(StringBuilder &sb) const final {
+ return sb << "[Logevent CreateSecretChat " << tag("id", log_event_id()) << tag("chat_id", random_id) << user_id
+ << "]";
}
};
@@ -443,10 +511,10 @@ void SecretChatEvent::downcast_call(Type type, F &&f) {
break;
}
}
-} // namespace logevent
+} // namespace log_event
-inline auto create_storer(logevent::SecretChatEvent &event) {
- return logevent::detail::StorerImpl<logevent::SecretChatEvent>(event);
+inline auto create_storer(log_event::SecretChatEvent &event) {
+ return log_event::detail::StorerImpl<log_event::SecretChatEvent>(event);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/misc.cpp b/protocols/Telegram/tdlib/td/td/telegram/misc.cpp
index 282906279d..7983ed64a7 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/misc.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/misc.cpp
@@ -1,18 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/misc.h"
+#include "td/utils/algorithm.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
#include "td/utils/utf8.h"
-#include <algorithm>
#include <cstring>
#include <limits>
@@ -47,8 +46,27 @@ string clean_name(string str, size_t max_length) {
}
string clean_username(string str) {
- str.resize(std::remove(str.begin(), str.end(), '.') - str.begin());
- return trim(to_lower(str));
+ td::remove(str, '.');
+ to_lower_inplace(str);
+ return trim(str);
+}
+
+void clean_phone_number(string &phone_number) {
+ td::remove_if(phone_number, [](char c) { return !is_digit(c); });
+}
+
+void replace_offending_characters(string &str) {
+ // "(\xe2\x80\x8f|\xe2\x80\x8e){N}(\xe2\x80\x8f|\xe2\x80\x8e)" -> "(\xe2\x80\x8c){N}$2"
+ auto s = MutableSlice(str).ubegin();
+ for (size_t pos = 0; pos < str.size(); pos++) {
+ if (s[pos] == 0xe2 && s[pos + 1] == 0x80 && (s[pos + 2] == 0x8e || s[pos + 2] == 0x8f)) {
+ while (s[pos + 3] == 0xe2 && s[pos + 4] == 0x80 && (s[pos + 5] == 0x8e || s[pos + 5] == 0x8f)) {
+ s[pos + 2] = static_cast<unsigned char>(0x8c);
+ pos += 3;
+ }
+ pos += 2;
+ }
+ }
}
bool clean_input_string(string &str) {
@@ -60,7 +78,7 @@ bool clean_input_string(string &str) {
size_t str_size = str.size();
size_t new_size = 0;
for (size_t pos = 0; pos < str_size; pos++) {
- unsigned char c = static_cast<unsigned char>(str[pos]);
+ auto c = static_cast<unsigned char>(str[pos]);
switch (c) {
// remove control characters
case 0:
@@ -104,7 +122,7 @@ bool clean_input_string(string &str) {
default:
// remove \xe2\x80[\xa8-\xae]
if (c == 0xe2 && pos + 2 < str_size) {
- unsigned char next = static_cast<unsigned char>(str[pos + 1]);
+ auto next = static_cast<unsigned char>(str[pos + 1]);
if (next == 0x80) {
next = static_cast<unsigned char>(str[pos + 2]);
if (0xa8 <= next && next <= 0xae) {
@@ -115,7 +133,7 @@ bool clean_input_string(string &str) {
}
// remove vertical lines \xcc[\xb3\xbf\x8a]
if (c == 0xcc && pos + 1 < str_size) {
- unsigned char next = static_cast<unsigned char>(str[pos + 1]);
+ auto next = static_cast<unsigned char>(str[pos + 1]);
if (next == 0xb3 || next == 0xbf || next == 0x8a) {
pos++;
break;
@@ -132,14 +150,17 @@ bool clean_input_string(string &str) {
}
str.resize(new_size);
+
+ replace_offending_characters(str);
+
return true;
}
-string strip_empty_characters(string str, size_t max_length) {
+string strip_empty_characters(string str, size_t max_length, bool strip_rtlo) {
static const char *space_characters[] = {u8"\u1680", u8"\u180E", u8"\u2000", u8"\u2001", u8"\u2002",
u8"\u2003", u8"\u2004", u8"\u2005", u8"\u2006", u8"\u2007",
- u8"\u2008", u8"\u2009", u8"\u200A", u8"\u200B", u8"\u202F",
- u8"\u205F", u8"\u3000", u8"\uFEFF", u8"\uFFFC"};
+ u8"\u2008", u8"\u2009", u8"\u200A", u8"\u202E", u8"\u202F",
+ u8"\u205F", u8"\u2800", u8"\u3000", u8"\uFFFC"};
static bool can_be_first[std::numeric_limits<unsigned char>::max() + 1];
static bool can_be_first_inited = [&] {
for (auto space_ch : space_characters) {
@@ -161,7 +182,10 @@ string strip_empty_characters(string str, size_t max_length) {
bool found = false;
for (auto space_ch : space_characters) {
if (space_ch[0] == str[i] && space_ch[1] == str[i + 1] && space_ch[2] == str[i + 2]) {
- found = true;
+ if (static_cast<unsigned char>(str[i + 2]) != 0xAE || static_cast<unsigned char>(str[i + 1]) != 0x80 ||
+ static_cast<unsigned char>(str[i]) != 0xE2 || strip_rtlo) {
+ found = true;
+ }
break;
}
}
@@ -176,9 +200,13 @@ string strip_empty_characters(string str, size_t max_length) {
Slice trimmed = trim(utf8_truncate(trim(Slice(str.c_str(), new_len)), max_length));
// check if there is some non-empty character, empty characters:
+ // "\xE2\x80\x8B", ZERO WIDTH SPACE
// "\xE2\x80\x8C", ZERO WIDTH NON-JOINER
// "\xE2\x80\x8D", ZERO WIDTH JOINER
+ // "\xE2\x80\x8E", LEFT-TO-RIGHT MARK
+ // "\xE2\x80\x8F", RIGHT-TO-LEFT MARK
// "\xE2\x80\xAE", RIGHT-TO-LEFT OVERRIDE
+ // "\xEF\xBB\xBF", ZERO WIDTH NO-BREAK SPACE aka BYTE ORDER MARK
// "\xC2\xA0", NO-BREAK SPACE
for (i = 0;;) {
if (i == trimmed.size()) {
@@ -186,9 +214,19 @@ string strip_empty_characters(string str, size_t max_length) {
return string();
}
- if (static_cast<unsigned char>(trimmed[i]) == 0xE2 && static_cast<unsigned char>(trimmed[i + 1]) == 0x80 &&
- (static_cast<unsigned char>(trimmed[i + 2]) == 0x8C || static_cast<unsigned char>(trimmed[i + 2]) == 0x8D ||
- static_cast<unsigned char>(trimmed[i + 2]) == 0xAE)) {
+ if (trimmed[i] == ' ' || trimmed[i] == '\n') {
+ i++;
+ continue;
+ }
+ if (static_cast<unsigned char>(trimmed[i]) == 0xE2 && static_cast<unsigned char>(trimmed[i + 1]) == 0x80) {
+ auto next = static_cast<unsigned char>(trimmed[i + 2]);
+ if ((0x8B <= next && next <= 0x8F) || next == 0xAE) {
+ i += 3;
+ continue;
+ }
+ }
+ if (static_cast<unsigned char>(trimmed[i]) == 0xEF && static_cast<unsigned char>(trimmed[i + 1]) == 0xBB &&
+ static_cast<unsigned char>(trimmed[i + 2]) == 0xBF) {
i += 3;
continue;
}
@@ -205,12 +243,39 @@ bool is_empty_string(const string &str) {
return strip_empty_characters(str, str.size()).empty();
}
-int32 get_vector_hash(const vector<uint32> &numbers) {
- uint32 acc = 0;
+bool is_valid_username(Slice username) {
+ if (username.empty() || username.size() > 32) {
+ return false;
+ }
+ if (!is_alpha(username[0])) {
+ return false;
+ }
+ for (auto c : username) {
+ if (!is_alpha(c) && !is_digit(c) && c != '_') {
+ return false;
+ }
+ }
+ if (username.back() == '_') {
+ return false;
+ }
+ for (size_t i = 1; i < username.size(); i++) {
+ if (username[i - 1] == '_' && username[i] == '_') {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int64 get_vector_hash(const vector<uint64> &numbers) {
+ uint64 acc = 0;
for (auto number : numbers) {
- acc = acc * 20261 + number;
+ acc ^= acc >> 21;
+ acc ^= acc << 35;
+ acc ^= acc >> 4;
+ acc += number;
}
- return static_cast<int32>(acc & 0x7FFFFFFF);
+ return static_cast<int64>(acc);
}
string get_emoji_fingerprint(uint64 num) {
@@ -267,7 +332,12 @@ string get_emoji_fingerprint(uint64 num) {
// comment for clang-format
u8"\U0001f537"};
- return emojis[(num & 0x7FFFFFFFFFFFFFFF) % emojis.size()].str();
+ return emojis[static_cast<size_t>((num & 0x7FFFFFFFFFFFFFFF) % emojis.size())].str();
+}
+
+bool check_currency_amount(int64 amount) {
+ constexpr int64 MAX_AMOUNT = 9999'9999'9999;
+ return -MAX_AMOUNT <= amount && amount <= MAX_AMOUNT;
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/misc.h b/protocols/Telegram/tdlib/td/td/telegram/misc.h
index 8efbb23c2f..934338f6d2 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/misc.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/misc.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,6 +7,7 @@
#pragma once
#include "td/utils/common.h"
+#include "td/utils/Slice.h"
namespace td {
@@ -16,19 +17,31 @@ string clean_name(string str, size_t max_length) TD_WARN_UNUSED_RESULT;
// prepares username/stickername for search
string clean_username(string str) TD_WARN_UNUSED_RESULT;
+// prepares phone number for search
+void clean_phone_number(string &phone_number);
+
+// replaces some offending characters without changing string length
+void replace_offending_characters(string &str);
+
// removes control characters from the string, will fail if input string is not in UTF-8
bool clean_input_string(string &str) TD_WARN_UNUSED_RESULT;
// strips empty characters and ensures that string length is no more than max_length
-string strip_empty_characters(string str, size_t max_length) TD_WARN_UNUSED_RESULT;
+string strip_empty_characters(string str, size_t max_length, bool strip_rtlo = false) TD_WARN_UNUSED_RESULT;
// checks if string is empty after strip_empty_characters
bool is_empty_string(const string &str) TD_WARN_UNUSED_RESULT;
-// calculates hash of list of uint32
-int32 get_vector_hash(const vector<uint32> &numbers) TD_WARN_UNUSED_RESULT;
+// checks whether a string could be a valid username
+bool is_valid_username(Slice username);
+
+// calculates hash of list of uint64
+int64 get_vector_hash(const vector<uint64> &numbers) TD_WARN_UNUSED_RESULT;
// returns emoji corresponding to the specified number
string get_emoji_fingerprint(uint64 num);
+// checks whether currency amount is valid
+bool check_currency_amount(int64 amount);
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.cpp
index 318d472067..811e7ff8ac 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,32 +7,33 @@
#include "td/telegram/net/AuthDataShared.h"
#include "td/telegram/Global.h"
+#include "td/telegram/TdDb.h"
+#include "td/utils/algorithm.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/port/RwMutex.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/tl_helpers.h"
-#include <algorithm>
-
namespace td {
-class AuthDataSharedImpl : public AuthDataShared {
+class AuthDataSharedImpl final : public AuthDataShared {
public:
AuthDataSharedImpl(DcId dc_id, std::shared_ptr<PublicRsaKeyShared> public_rsa_key, std::shared_ptr<Guard> guard)
: dc_id_(dc_id), public_rsa_key_(std::move(public_rsa_key)), guard_(std::move(guard)) {
log_auth_key(get_auth_key());
}
- DcId dc_id() const override {
+ DcId dc_id() const final {
return dc_id_;
}
- const std::shared_ptr<PublicRsaKeyShared> &public_rsa_key() override {
+ const std::shared_ptr<PublicRsaKeyShared> &public_rsa_key() final {
return public_rsa_key_;
}
- mtproto::AuthKey get_auth_key() override {
+ mtproto::AuthKey get_auth_key() final {
string dc_key = G()->td_db()->get_binlog_pmc()->get(auth_key_key());
mtproto::AuthKey res;
@@ -41,15 +42,12 @@ class AuthDataSharedImpl : public AuthDataShared {
}
return res;
}
- using AuthDataShared::get_auth_state;
- std::pair<AuthState, bool> get_auth_state() override {
- // TODO (perf):
- auto auth_key = get_auth_key();
- AuthState state = get_auth_state(auth_key);
- return std::make_pair(state, auth_key.was_auth_flag());
+
+ AuthKeyState get_auth_key_state() final {
+ return AuthDataShared::get_auth_key_state(get_auth_key());
}
- void set_auth_key(const mtproto::AuthKey &auth_key) override {
+ void set_auth_key(const mtproto::AuthKey &auth_key) final {
G()->td_db()->get_binlog_pmc()->set(auth_key_key(), serialize(auth_key));
log_auth_key(auth_key);
@@ -57,26 +55,26 @@ class AuthDataSharedImpl : public AuthDataShared {
}
// TODO: extract it from G()
- void update_server_time_difference(double diff) override {
+ void update_server_time_difference(double diff) final {
G()->update_server_time_difference(diff);
}
- double get_server_time_difference() override {
+ double get_server_time_difference() final {
return G()->get_server_time_difference();
}
- void add_auth_key_listener(unique_ptr<Listener> listener) override {
+ void add_auth_key_listener(unique_ptr<Listener> listener) final {
if (listener->notify()) {
auto lock = rw_mutex_.lock_write();
auth_key_listeners_.push_back(std::move(listener));
}
}
- void set_future_salts(const std::vector<mtproto::ServerSalt> &future_salts) override {
+ void set_future_salts(const std::vector<mtproto::ServerSalt> &future_salts) final {
G()->td_db()->get_binlog_pmc()->set(future_salts_key(), serialize(future_salts));
}
- std::vector<mtproto::ServerSalt> get_future_salts() override {
+ std::vector<mtproto::ServerSalt> get_future_salts() final {
string future_salts = G()->td_db()->get_binlog_pmc()->get(future_salts_key());
std::vector<mtproto::ServerSalt> res;
if (!future_salts.empty()) {
@@ -92,23 +90,23 @@ class AuthDataSharedImpl : public AuthDataShared {
std::shared_ptr<Guard> guard_;
RwMutex rw_mutex_;
- string auth_key_key() {
+ string auth_key_key() const {
return PSTRING() << "auth" << dc_id_.get_raw_id();
}
- string future_salts_key() {
+ string future_salts_key() const {
return PSTRING() << "salt" << dc_id_.get_raw_id();
}
void notify() {
auto lock = rw_mutex_.lock_read();
- auto it = std::remove_if(auth_key_listeners_.begin(), auth_key_listeners_.end(),
- [&](auto &listener) { return !listener->notify(); });
- auth_key_listeners_.erase(it, auth_key_listeners_.end());
+ td::remove_if(auth_key_listeners_, [&](auto &listener) { return !listener->notify(); });
}
void log_auth_key(const mtproto::AuthKey &auth_key) {
- LOG(WARNING) << dc_id_ << " " << tag("auth_key_id", auth_key.id()) << tag("state", get_auth_state(auth_key));
+ LOG(WARNING) << dc_id_ << " " << tag("auth_key_id", auth_key.id())
+ << tag("state", AuthDataShared::get_auth_key_state(auth_key))
+ << tag("created_at", auth_key.created_at());
}
};
@@ -116,4 +114,5 @@ std::shared_ptr<AuthDataShared> AuthDataShared::create(DcId dc_id, std::shared_p
std::shared_ptr<Guard> guard) {
return std::make_shared<AuthDataSharedImpl>(dc_id, std::move(public_rsa_key), std::move(guard));
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.h b/protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.h
index 027aa80eda..958013b047 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/AuthDataShared.h
@@ -1,17 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/mtproto/AuthData.h"
-#include "td/mtproto/AuthKey.h"
-
#include "td/telegram/net/DcId.h"
#include "td/telegram/net/PublicRsaKeyShared.h"
+#include "td/mtproto/AuthData.h"
+#include "td/mtproto/AuthKey.h"
+
#include "td/utils/common.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/StringBuilder.h"
@@ -20,18 +20,19 @@
#include <utility>
namespace td {
-enum class AuthState : int32 { Empty, KeyNoAuth, OK };
-inline StringBuilder &operator<<(StringBuilder &sb, AuthState state) {
+enum class AuthKeyState : int32 { Empty, NoAuth, OK };
+
+inline StringBuilder &operator<<(StringBuilder &sb, AuthKeyState state) {
switch (state) {
- case AuthState::Empty:
+ case AuthKeyState::Empty:
return sb << "Empty";
- case AuthState::KeyNoAuth:
- return sb << "KeyNoAuth";
- case AuthState::OK:
+ case AuthKeyState::NoAuth:
+ return sb << "NoAuth";
+ case AuthKeyState::OK:
return sb << "OK";
default:
- return sb << "Unknown AuthState";
+ return sb << "Unknown AuthKeyState";
}
}
@@ -50,7 +51,7 @@ class AuthDataShared {
virtual DcId dc_id() const = 0;
virtual const std::shared_ptr<PublicRsaKeyShared> &public_rsa_key() = 0;
virtual mtproto::AuthKey get_auth_key() = 0;
- virtual std::pair<AuthState, bool> get_auth_state() = 0;
+ virtual AuthKeyState get_auth_key_state() = 0;
virtual void set_auth_key(const mtproto::AuthKey &auth_key) = 0;
virtual void update_server_time_difference(double diff) = 0;
virtual double get_server_time_difference() = 0;
@@ -59,20 +60,18 @@ class AuthDataShared {
virtual void set_future_salts(const std::vector<mtproto::ServerSalt> &future_salts) = 0;
virtual std::vector<mtproto::ServerSalt> get_future_salts() = 0;
- static AuthState get_auth_state(const mtproto::AuthKey &auth_key) {
- AuthState state;
+ static AuthKeyState get_auth_key_state(const mtproto::AuthKey &auth_key) {
if (auth_key.empty()) {
- state = AuthState::Empty;
+ return AuthKeyState::Empty;
} else if (auth_key.auth_flag()) {
- state = AuthState::OK;
+ return AuthKeyState::OK;
} else {
- state = AuthState::KeyNoAuth;
+ return AuthKeyState::NoAuth;
}
- return state;
}
static std::shared_ptr<AuthDataShared> create(DcId dc_id, std::shared_ptr<PublicRsaKeyShared> public_rsa_key,
std::shared_ptr<Guard> guard);
};
-}; // namespace td
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.cpp
index 3805835ef3..6cfa4600f6 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,32 +9,48 @@
#include "td/telegram/ConfigManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
+#include "td/telegram/MessagesManager.h"
+#include "td/telegram/net/MtprotoHeader.h"
+#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/net/NetType.h"
#include "td/telegram/StateManager.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/TdDb.h"
-#include "td/mtproto/IStreamTransport.h"
-#include "td/mtproto/PingConnection.h"
+#include "td/mtproto/Ping.h"
+#include "td/mtproto/ProxySecret.h"
#include "td/mtproto/RawConnection.h"
+#include "td/mtproto/TlsInit.h"
#include "td/net/GetHostByNameActor.h"
+#include "td/net/HttpProxy.h"
#include "td/net/Socks5.h"
+#include "td/net/TransparentProxy.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/base64.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/port/IPAddress.h"
+#include "td/utils/Random.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
#include "td/utils/tl_helpers.h"
-#include <algorithm>
+#include <utility>
namespace td {
+int VERBOSITY_NAME(connections) = VERBOSITY_NAME(INFO);
+
namespace detail {
class StatsCallback final : public mtproto::RawConnection::StatsCallback {
public:
StatsCallback(std::shared_ptr<NetStatsCallback> net_stats_callback, ActorId<ConnectionCreator> connection_creator,
- size_t hash, DcOptionsSet::Stat *option_stat)
+ uint32 hash, DcOptionsSet::Stat *option_stat)
: net_stats_callback_(std::move(net_stats_callback))
, connection_creator_(std::move(connection_creator))
, hash_(hash)
@@ -49,10 +65,16 @@ class StatsCallback final : public mtproto::RawConnection::StatsCallback {
}
void on_pong() final {
- send_lambda(connection_creator_, [stat = option_stat_] { stat->on_ok(); });
+ if (option_stat_) {
+ send_lambda(connection_creator_, [stat = option_stat_] { stat->on_ok(); });
+ }
+ send_closure(connection_creator_, &ConnectionCreator::on_pong, hash_);
}
+
void on_error() final {
- send_lambda(connection_creator_, [stat = option_stat_] { stat->on_error(); });
+ if (option_stat_) {
+ send_lambda(connection_creator_, [stat = option_stat_] { stat->on_error(); });
+ }
}
void on_mtproto_error() final {
@@ -62,179 +84,478 @@ class StatsCallback final : public mtproto::RawConnection::StatsCallback {
private:
std::shared_ptr<NetStatsCallback> net_stats_callback_;
ActorId<ConnectionCreator> connection_creator_;
- size_t hash_;
+ uint32 hash_;
DcOptionsSet::Stat *option_stat_;
};
-class PingActor : public Actor {
- public:
- PingActor(std::unique_ptr<mtproto::RawConnection> raw_connection,
- Promise<std::unique_ptr<mtproto::RawConnection>> promise, ActorShared<> parent)
- : promise_(std::move(promise)), parent_(std::move(parent)) {
- ping_connection_ = std::make_unique<mtproto::PingConnection>(std::move(raw_connection));
- }
+} // namespace detail
- private:
- std::unique_ptr<mtproto::PingConnection> ping_connection_;
- Promise<std::unique_ptr<mtproto::RawConnection>> promise_;
- ActorShared<> parent_;
- double start_at_;
+ConnectionCreator::ClientInfo::ClientInfo() {
+ sanity_flood_control.add_limit(5, 10);
- void start_up() override {
- ping_connection_->get_pollable().set_observer(this);
- subscribe(ping_connection_->get_pollable());
- start_at_ = Time::now();
- set_timeout_in(10);
- yield();
+ flood_control.add_limit(1, 1);
+ flood_control.add_limit(4, 2);
+ flood_control.add_limit(8, 3);
+
+ flood_control_online.add_limit(1, 4);
+ flood_control_online.add_limit(5, 5);
+
+ mtproto_error_flood_control.add_limit(1, 1);
+ mtproto_error_flood_control.add_limit(4, 2);
+ mtproto_error_flood_control.add_limit(8, 3);
+}
+
+int64 ConnectionCreator::ClientInfo::extract_session_id() {
+ if (!session_ids_.empty()) {
+ auto res = *session_ids_.begin();
+ session_ids_.erase(session_ids_.begin());
+ return res;
}
- void tear_down() override {
- finish(Status::OK());
+ int64 res = 0;
+ while (res == 0) {
+ res = Random::secure_int64();
}
+ return res;
+}
- void loop() override {
- auto status = ping_connection_->flush();
- if (status.is_error()) {
- finish(std::move(status));
- return stop();
- }
- if (ping_connection_->was_pong()) {
- finish(Status::OK());
- return stop();
- }
+void ConnectionCreator::ClientInfo::add_session_id(int64 session_id) {
+ if (session_id != 0) {
+ session_ids_.insert(session_id);
}
+}
- void timeout_expired() override {
- finish(Status::Error("Pong timeout expired"));
- stop();
- }
+ConnectionCreator::ConnectionCreator(ActorShared<> parent) : parent_(std::move(parent)) {
+}
- void finish(Status status) {
- auto raw_connection = ping_connection_->move_as_raw_connection();
- if (!raw_connection) {
- CHECK(!promise_);
- return;
+ConnectionCreator::ConnectionCreator(ConnectionCreator &&other) = default;
+
+ConnectionCreator &ConnectionCreator::operator=(ConnectionCreator &&other) = default;
+
+ConnectionCreator::~ConnectionCreator() = default;
+
+void ConnectionCreator::set_net_stats_callback(std::shared_ptr<NetStatsCallback> common_callback,
+ std::shared_ptr<NetStatsCallback> media_callback) {
+ common_net_stats_callback_ = std::move(common_callback);
+ media_net_stats_callback_ = std::move(media_callback);
+}
+
+void ConnectionCreator::add_proxy(int32 old_proxy_id, string server, int32 port, bool enable,
+ td_api::object_ptr<td_api::ProxyType> proxy_type,
+ Promise<td_api::object_ptr<td_api::proxy>> promise) {
+ TRY_RESULT_PROMISE(promise, new_proxy, Proxy::create_proxy(std::move(server), port, proxy_type.get()));
+ if (old_proxy_id >= 0) {
+ if (proxies_.count(old_proxy_id) == 0) {
+ return promise.set_error(Status::Error(400, "Proxy not found"));
}
- unsubscribe(raw_connection->get_pollable());
- raw_connection->get_pollable().set_observer(nullptr);
- if (promise_) {
- if (status.is_error()) {
- if (raw_connection->stats_callback()) {
- raw_connection->stats_callback()->on_error();
- }
- raw_connection->close();
- promise_.set_error(std::move(status));
- } else {
- raw_connection->rtt_ = Time::now() - start_at_;
- if (raw_connection->stats_callback()) {
- raw_connection->stats_callback()->on_pong();
- }
- promise_.set_value(std::move(raw_connection));
+ auto &old_proxy = proxies_[old_proxy_id];
+ if (old_proxy == new_proxy) {
+ if (enable) {
+ enable_proxy_impl(old_proxy_id);
}
- } else {
- if (raw_connection->stats_callback()) {
- raw_connection->stats_callback()->on_error();
+ return promise.set_value(get_proxy_object(old_proxy_id));
+ }
+ if (old_proxy_id == active_proxy_id_) {
+ enable = true;
+ disable_proxy_impl();
+ }
+
+ proxies_.erase(old_proxy_id);
+ G()->td_db()->get_binlog_pmc()->erase(get_proxy_used_database_key(old_proxy_id));
+ proxy_last_used_date_.erase(old_proxy_id);
+ proxy_last_used_saved_date_.erase(old_proxy_id);
+ }
+
+ auto proxy_id = [&] {
+ for (auto &proxy : proxies_) {
+ if (proxy.second == new_proxy) {
+ return proxy.first;
}
- raw_connection->close();
}
+
+ int32 proxy_id = old_proxy_id;
+ if (proxy_id < 0) {
+ CHECK(max_proxy_id_ >= 2);
+ proxy_id = max_proxy_id_++;
+ G()->td_db()->get_binlog_pmc()->set("proxy_max_id", to_string(max_proxy_id_));
+ }
+ bool is_inserted = proxies_.emplace(proxy_id, std::move(new_proxy)).second;
+ CHECK(is_inserted);
+ G()->td_db()->get_binlog_pmc()->set(get_proxy_database_key(proxy_id),
+ log_event_store(proxies_[proxy_id]).as_slice().str());
+ return proxy_id;
+ }();
+ if (enable) {
+ enable_proxy_impl(proxy_id);
}
-};
+ promise.set_value(get_proxy_object(proxy_id));
+}
-} // namespace detail
+void ConnectionCreator::enable_proxy(int32 proxy_id, Promise<Unit> promise) {
+ if (proxies_.count(proxy_id) == 0) {
+ return promise.set_error(Status::Error(400, "Unknown proxy identifier"));
+ }
+
+ enable_proxy_impl(proxy_id);
+ promise.set_value(Unit());
+}
+
+void ConnectionCreator::disable_proxy(Promise<Unit> promise) {
+ save_proxy_last_used_date(0);
+ disable_proxy_impl();
+ promise.set_value(Unit());
+}
-template <class T>
-void Proxy::parse(T &parser) {
- using td::parse;
- parse(type_, parser);
- if (type_ == Proxy::Type::Socks5) {
- parse(server_, parser);
- parse(port_, parser);
- parse(user_, parser);
- parse(password_, parser);
+void ConnectionCreator::remove_proxy(int32 proxy_id, Promise<Unit> promise) {
+ if (proxies_.count(proxy_id) == 0) {
+ return promise.set_error(Status::Error(400, "Unknown proxy identifier"));
+ }
+
+ if (proxy_id == active_proxy_id_) {
+ disable_proxy_impl();
+ }
+
+ proxies_.erase(proxy_id);
+
+ G()->td_db()->get_binlog_pmc()->erase(get_proxy_database_key(proxy_id));
+ G()->td_db()->get_binlog_pmc()->erase(get_proxy_used_database_key(proxy_id));
+ promise.set_value(Unit());
+}
+
+void ConnectionCreator::get_proxies(Promise<td_api::object_ptr<td_api::proxies>> promise) {
+ promise.set_value(td_api::make_object<td_api::proxies>(
+ transform(proxies_, [this](const std::pair<int32, Proxy> &proxy) { return get_proxy_object(proxy.first); })));
+}
+
+void ConnectionCreator::get_proxy_link(int32 proxy_id, Promise<string> promise) {
+ auto it = proxies_.find(proxy_id);
+ if (it == proxies_.end()) {
+ return promise.set_error(Status::Error(400, "Unknown proxy identifier"));
+ }
+
+ auto &proxy = it->second;
+ string url = G()->get_option_string("t_me_url", "https://t.me/");
+ bool is_socks = false;
+ switch (proxy.type()) {
+ case Proxy::Type::Socks5:
+ url += "socks";
+ is_socks = true;
+ break;
+ case Proxy::Type::HttpTcp:
+ case Proxy::Type::HttpCaching:
+ return promise.set_error(Status::Error(400, "HTTP proxy can't have public link"));
+ case Proxy::Type::Mtproto:
+ url += "proxy";
+ break;
+ default:
+ UNREACHABLE();
+ }
+ url += "?server=";
+ url += url_encode(proxy.server());
+ url += "&port=";
+ url += to_string(proxy.port());
+ if (is_socks) {
+ if (!proxy.user().empty() || !proxy.password().empty()) {
+ url += "&user=";
+ url += url_encode(proxy.user());
+ url += "&pass=";
+ url += url_encode(proxy.password());
+ }
} else {
- CHECK(type_ == Proxy::Type::None);
+ url += "&secret=";
+ url += proxy.secret().get_encoded_secret();
}
+ promise.set_value(std::move(url));
}
-template <class T>
-void Proxy::store(T &storer) const {
- using td::store;
- store(type_, storer);
- if (type_ == Proxy::Type::Socks5) {
- store(server_, storer);
- store(port_, storer);
- store(user_, storer);
- store(password_, storer);
+ActorId<GetHostByNameActor> ConnectionCreator::get_dns_resolver() {
+ if (G()->get_option_boolean("expect_blocking", true)) {
+ if (block_get_host_by_name_actor_.empty()) {
+ VLOG(connections) << "Init block bypass DNS resolver";
+ GetHostByNameActor::Options options;
+ options.scheduler_id = G()->get_gc_scheduler_id();
+ options.resolver_types = {GetHostByNameActor::ResolverType::Google, GetHostByNameActor::ResolverType::Native};
+ options.ok_timeout = 60;
+ options.error_timeout = 0;
+ block_get_host_by_name_actor_ = create_actor<GetHostByNameActor>("BlockDnsResolverActor", std::move(options));
+ }
+ return block_get_host_by_name_actor_.get();
} else {
- CHECK(type_ == Proxy::Type::None);
+ if (get_host_by_name_actor_.empty()) {
+ VLOG(connections) << "Init DNS resolver";
+ GetHostByNameActor::Options options;
+ options.scheduler_id = G()->get_gc_scheduler_id();
+ options.ok_timeout = 5 * 60 - 1;
+ options.error_timeout = 0;
+ get_host_by_name_actor_ = create_actor<GetHostByNameActor>("DnsResolverActor", std::move(options));
+ }
+ return get_host_by_name_actor_.get();
}
}
-ConnectionCreator::ClientInfo::ClientInfo() {
- flood_control.add_limit(1, 1);
- flood_control.add_limit(4, 2);
- flood_control.add_limit(8, 3);
+void ConnectionCreator::ping_proxy(int32 proxy_id, Promise<double> promise) {
+ CHECK(!close_flag_);
+ if (proxy_id == 0) {
+ auto main_dc_id = G()->net_query_dispatcher().get_main_dc_id();
+ bool prefer_ipv6 = G()->get_option_boolean("prefer_ipv6");
+ auto infos = dc_options_set_.find_all_connections(main_dc_id, false, false, prefer_ipv6, false);
+ if (infos.empty()) {
+ return promise.set_error(Status::Error(400, "Can't find valid DC address"));
+ }
+ const size_t MAX_CONNECTIONS = 10;
+ if (infos.size() > MAX_CONNECTIONS) {
+ infos.resize(MAX_CONNECTIONS);
+ }
- flood_control_online.add_limit(1, 4);
- flood_control_online.add_limit(5, 5);
+ auto token = next_token();
+ auto &request = ping_main_dc_requests_[token];
+ request.promise = std::move(promise);
+ request.left_queries = infos.size();
+ request.result = Status::Error(400, "Failed to ping");
+
+ for (auto &info : infos) {
+ auto r_transport_type = get_transport_type(Proxy(), info);
+ if (r_transport_type.is_error()) {
+ LOG(ERROR) << r_transport_type.error();
+ on_ping_main_dc_result(token, r_transport_type.move_as_error());
+ continue;
+ }
- mtproto_error_flood_control.add_limit(1, 1);
- mtproto_error_flood_control.add_limit(4, 2);
- mtproto_error_flood_control.add_limit(8, 3);
-}
+ auto ip_address = info.option->get_ip_address();
+ auto r_socket_fd = SocketFd::open(ip_address);
+ if (r_socket_fd.is_error()) {
+ LOG(DEBUG) << "Failed to open socket: " << r_socket_fd.error();
+ on_ping_main_dc_result(token, r_socket_fd.move_as_error());
+ continue;
+ }
-ConnectionCreator::ConnectionCreator(ActorShared<> parent) : parent_(std::move(parent)) {
+ ping_proxy_buffered_socket_fd(std::move(ip_address), BufferedFd<SocketFd>(r_socket_fd.move_as_ok()),
+ r_transport_type.move_as_ok(), PSTRING() << info.option->get_ip_address(),
+ PromiseCreator::lambda([actor_id = actor_id(this), token](Result<double> result) {
+ send_closure(actor_id, &ConnectionCreator::on_ping_main_dc_result, token,
+ std::move(result));
+ }));
+ }
+ return;
+ }
+
+ auto it = proxies_.find(proxy_id);
+ if (it == proxies_.end()) {
+ return promise.set_error(Status::Error(400, "Unknown proxy identifier"));
+ }
+ const Proxy &proxy = it->second;
+ bool prefer_ipv6 = G()->get_option_boolean("prefer_ipv6");
+ send_closure(get_dns_resolver(), &GetHostByNameActor::run, proxy.server().str(), proxy.port(), prefer_ipv6,
+ PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise),
+ proxy_id](Result<IPAddress> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(Status::Error(400, result.error().public_message()));
+ }
+ send_closure(actor_id, &ConnectionCreator::ping_proxy_resolved, proxy_id, result.move_as_ok(),
+ std::move(promise));
+ }));
}
-ConnectionCreator::ConnectionCreator(ConnectionCreator &&other) = default;
+void ConnectionCreator::ping_proxy_resolved(int32 proxy_id, IPAddress ip_address, Promise<double> promise) {
+ auto it = proxies_.find(proxy_id);
+ if (it == proxies_.end()) {
+ return promise.set_error(Status::Error(400, "Unknown proxy identifier"));
+ }
+ const Proxy &proxy = it->second;
+ auto main_dc_id = G()->net_query_dispatcher().get_main_dc_id();
+ FindConnectionExtra extra;
+ auto r_socket_fd = find_connection(proxy, ip_address, main_dc_id, false, extra);
+ if (r_socket_fd.is_error()) {
+ return promise.set_error(Status::Error(400, r_socket_fd.error().public_message()));
+ }
+ auto socket_fd = r_socket_fd.move_as_ok();
-ConnectionCreator &ConnectionCreator::operator=(ConnectionCreator &&other) = default;
+ auto connection_promise = PromiseCreator::lambda(
+ [ip_address, promise = std::move(promise), actor_id = actor_id(this), transport_type = extra.transport_type,
+ debug_str = extra.debug_str](Result<ConnectionData> r_connection_data) mutable {
+ if (r_connection_data.is_error()) {
+ return promise.set_error(Status::Error(400, r_connection_data.error().public_message()));
+ }
+ auto connection_data = r_connection_data.move_as_ok();
+ send_closure(actor_id, &ConnectionCreator::ping_proxy_buffered_socket_fd, ip_address,
+ std::move(connection_data.buffered_socket_fd), std::move(transport_type), std::move(debug_str),
+ std::move(promise));
+ });
+ CHECK(proxy.use_proxy());
+ auto token = next_token();
+ auto ref = prepare_connection(extra.ip_address, std::move(socket_fd), proxy, extra.mtproto_ip_address,
+ extra.transport_type, "Ping", extra.debug_str, nullptr, create_reference(token), false,
+ std::move(connection_promise));
+ if (!ref.empty()) {
+ children_[token] = {false, std::move(ref)};
+ }
+}
-ConnectionCreator::~ConnectionCreator() = default;
+void ConnectionCreator::ping_proxy_buffered_socket_fd(IPAddress ip_address, BufferedFd<SocketFd> buffered_socket_fd,
+ mtproto::TransportType transport_type, string debug_str,
+ Promise<double> promise) {
+ auto token = next_token();
+ auto raw_connection =
+ mtproto::RawConnection::create(ip_address, std::move(buffered_socket_fd), std::move(transport_type), nullptr);
+ children_[token] = {
+ false, create_ping_actor(debug_str, std::move(raw_connection), nullptr,
+ PromiseCreator::lambda([promise = std::move(promise)](
+ Result<unique_ptr<mtproto::RawConnection>> result) mutable {
+ if (result.is_error()) {
+ return promise.set_error(Status::Error(400, result.error().public_message()));
+ }
+ auto ping_time = result.ok()->extra().rtt;
+ promise.set_value(std::move(ping_time));
+ }),
+ create_reference(token))};
+}
-void ConnectionCreator::set_net_stats_callback(std::shared_ptr<NetStatsCallback> common_callback,
- std::shared_ptr<NetStatsCallback> media_callback) {
- common_net_stats_callback_ = std::move(common_callback);
- media_net_stats_callback_ = std::move(media_callback);
+void ConnectionCreator::set_active_proxy_id(int32 proxy_id, bool from_binlog) {
+ active_proxy_id_ = proxy_id;
+ if (proxy_id == 0) {
+ G()->set_option_empty("enabled_proxy_id");
+ } else {
+ G()->set_option_integer("enabled_proxy_id", proxy_id);
+ }
+ if (!from_binlog) {
+ if (proxy_id == 0) {
+ G()->td_db()->get_binlog_pmc()->erase("proxy_active_id");
+ send_closure(G()->config_manager(), &ConfigManager::request_config, false);
+ } else {
+ G()->td_db()->get_binlog_pmc()->set("proxy_active_id", to_string(proxy_id));
+ }
+ }
}
-void ConnectionCreator::set_proxy(Proxy proxy) {
- set_proxy_impl(std::move(proxy), false);
- loop();
+void ConnectionCreator::enable_proxy_impl(int32 proxy_id) {
+ CHECK(proxies_.count(proxy_id) == 1);
+ if (proxy_id == active_proxy_id_) {
+ return;
+ }
+
+ if ((active_proxy_id_ != 0 && proxies_[active_proxy_id_].type() == Proxy::Type::Mtproto) ||
+ proxies_[proxy_id].type() == Proxy::Type::Mtproto) {
+ update_mtproto_header(proxies_[proxy_id]);
+ }
+ save_proxy_last_used_date(0);
+
+ set_active_proxy_id(proxy_id);
+
+ on_proxy_changed(false);
}
-void ConnectionCreator::set_proxy_impl(Proxy proxy, bool from_db) {
- if (proxy_ == proxy) {
+void ConnectionCreator::disable_proxy_impl() {
+ if (active_proxy_id_ == 0) {
+ send_closure(G()->messages_manager(), &MessagesManager::remove_sponsored_dialog);
+ send_closure(G()->td(), &Td::schedule_get_promo_data, 0);
return;
}
+ CHECK(proxies_.count(active_proxy_id_) == 1);
+
+ if (proxies_[active_proxy_id_].type() == Proxy::Type::Mtproto) {
+ update_mtproto_header(Proxy());
+ }
- proxy_ = std::move(proxy);
- send_closure(G()->state_manager(), &StateManager::on_proxy, proxy_.type() != Proxy::Type::None);
+ set_active_proxy_id(0);
+
+ on_proxy_changed(false);
+}
+
+void ConnectionCreator::on_proxy_changed(bool from_db) {
+ send_closure(G()->state_manager(), &StateManager::on_proxy,
+ active_proxy_id_ != 0 && proxies_[active_proxy_id_].type() != Proxy::Type::Mtproto &&
+ proxies_[active_proxy_id_].type() != Proxy::Type::HttpCaching);
if (!from_db) {
- G()->td_db()->get_binlog_pmc()->set("proxy", log_event_store(proxy_).as_slice().str());
for (auto &child : children_) {
- child.second.reset();
+ if (child.second.first) {
+ child.second.second.reset();
+ }
}
}
+ VLOG(connections) << "Drop proxy IP address " << proxy_ip_address_;
resolve_proxy_query_token_ = 0;
resolve_proxy_timestamp_ = Timestamp();
proxy_ip_address_ = IPAddress();
+
+ if (active_proxy_id_ == 0 || !from_db) {
+ send_closure(G()->messages_manager(), &MessagesManager::remove_sponsored_dialog);
+ }
+ send_closure(G()->td(), &Td::schedule_get_promo_data, 0);
+
+ loop();
+}
+
+string ConnectionCreator::get_proxy_database_key(int32 proxy_id) {
+ CHECK(proxy_id > 0);
+ if (proxy_id == 1) {
+ return "proxy";
+ }
+ return PSTRING() << "proxy" << proxy_id;
+}
+
+string ConnectionCreator::get_proxy_used_database_key(int32 proxy_id) {
+ CHECK(proxy_id > 0);
+ return PSTRING() << "proxy_used" << proxy_id;
+}
+
+void ConnectionCreator::save_proxy_last_used_date(int32 delay) {
+ if (active_proxy_id_ == 0) {
+ return;
+ }
+
+ CHECK(delay >= 0);
+ int32 date = proxy_last_used_date_[active_proxy_id_];
+ int32 &saved_date = proxy_last_used_saved_date_[active_proxy_id_];
+ if (date <= saved_date + delay) {
+ return;
+ }
+ LOG(DEBUG) << "Save proxy last used date " << date;
+
+ saved_date = date;
+ G()->td_db()->get_binlog_pmc()->set(get_proxy_used_database_key(active_proxy_id_), to_string(date));
}
-void ConnectionCreator::get_proxy(Promise<Proxy> promise) {
- promise.set_value(Proxy(proxy_));
+td_api::object_ptr<td_api::proxy> ConnectionCreator::get_proxy_object(int32 proxy_id) const {
+ auto it = proxies_.find(proxy_id);
+ CHECK(it != proxies_.end());
+ const Proxy &proxy = it->second;
+ td_api::object_ptr<td_api::ProxyType> type;
+ switch (proxy.type()) {
+ case Proxy::Type::Socks5:
+ type = make_tl_object<td_api::proxyTypeSocks5>(proxy.user().str(), proxy.password().str());
+ break;
+ case Proxy::Type::HttpTcp:
+ type = make_tl_object<td_api::proxyTypeHttp>(proxy.user().str(), proxy.password().str(), false);
+ break;
+ case Proxy::Type::HttpCaching:
+ type = make_tl_object<td_api::proxyTypeHttp>(proxy.user().str(), proxy.password().str(), true);
+ break;
+ case Proxy::Type::Mtproto:
+ type = make_tl_object<td_api::proxyTypeMtproto>(proxy.secret().get_encoded_secret());
+ break;
+ default:
+ UNREACHABLE();
+ }
+ auto last_used_date_it = proxy_last_used_date_.find(proxy_id);
+ auto last_used_date = last_used_date_it == proxy_last_used_date_.end() ? 0 : last_used_date_it->second;
+ return make_tl_object<td_api::proxy>(proxy_id, proxy.server().str(), proxy.port(), last_used_date,
+ proxy_id == active_proxy_id_, std::move(type));
}
void ConnectionCreator::on_network(bool network_flag, uint32 network_generation) {
+ VLOG(connections) << "Receive network flag " << network_flag << " with generation " << network_generation;
network_flag_ = network_flag;
auto old_generation = network_generation_;
network_generation_ = network_generation;
if (network_flag_) {
+ VLOG(connections) << "Set proxy query token to 0: " << old_generation << " " << network_generation_;
resolve_proxy_query_token_ = 0;
resolve_proxy_timestamp_ = Timestamp();
+
for (auto &client : clients_) {
client.second.backoff.clear();
+ client.second.sanity_flood_control.clear_events();
client.second.flood_control.clear_events();
client.second.flood_control_online.clear_events();
client_loop(client.second);
@@ -245,25 +566,56 @@ void ConnectionCreator::on_network(bool network_flag, uint32 network_generation)
}
}
}
+
void ConnectionCreator::on_online(bool online_flag) {
+ VLOG(connections) << "Receive online flag " << online_flag;
+ bool need_drop_flood_control = online_flag || !online_flag_;
online_flag_ = online_flag;
- if (online_flag_) {
+ if (need_drop_flood_control) {
for (auto &client : clients_) {
client.second.backoff.clear();
+ client.second.sanity_flood_control.clear_events();
client.second.flood_control_online.clear_events();
client_loop(client.second);
}
}
}
+void ConnectionCreator::on_logging_out(bool is_logging_out) {
+ if (is_logging_out_ == is_logging_out) {
+ return;
+ }
-void ConnectionCreator::on_mtproto_error(size_t hash) {
+ VLOG(connections) << "Receive logging out flag " << is_logging_out;
+ is_logging_out_ = is_logging_out;
+ for (auto &client : clients_) {
+ client.second.backoff.clear();
+ client.second.sanity_flood_control.clear_events();
+ client.second.flood_control_online.clear_events();
+ client_loop(client.second);
+ }
+}
+
+void ConnectionCreator::on_pong(uint32 hash) {
+ G()->save_server_time();
+ if (active_proxy_id_ != 0) {
+ auto now = G()->unix_time();
+ int32 &last_used = proxy_last_used_date_[active_proxy_id_];
+ if (now > last_used) {
+ last_used = now;
+ save_proxy_last_used_date(MAX_PROXY_LAST_USED_SAVE_DELAY);
+ }
+ }
+}
+
+void ConnectionCreator::on_mtproto_error(uint32 hash) {
auto &client = clients_[hash];
client.hash = hash;
- client.mtproto_error_flood_control.add_event(static_cast<int32>(Time::now_cached()));
+ client.mtproto_error_flood_control.add_event(Time::now_cached());
}
void ConnectionCreator::request_raw_connection(DcId dc_id, bool allow_media_only, bool is_media,
- Promise<std::unique_ptr<mtproto::RawConnection>> promise, size_t hash) {
+ Promise<unique_ptr<mtproto::RawConnection>> promise, uint32 hash,
+ unique_ptr<mtproto::AuthData> auth_data) {
auto &client = clients_[hash];
if (!client.inited) {
client.inited = true;
@@ -277,65 +629,244 @@ void ConnectionCreator::request_raw_connection(DcId dc_id, bool allow_media_only
CHECK(client.allow_media_only == allow_media_only);
CHECK(client.is_media == is_media);
}
- VLOG(connections) << tag("client", format::as_hex(client.hash)) << " " << dc_id << " "
+ client.auth_data = std::move(auth_data);
+ client.auth_data_generation++;
+ VLOG(connections) << "Request connection for " << tag("client", format::as_hex(client.hash)) << " to " << dc_id << " "
<< tag("allow_media_only", allow_media_only);
client.queries.push_back(std::move(promise));
client_loop(client);
}
-void ConnectionCreator::request_raw_connection_by_ip(IPAddress ip_address,
- Promise<std::unique_ptr<mtproto::RawConnection>> promise) {
+void ConnectionCreator::request_raw_connection_by_ip(IPAddress ip_address, mtproto::TransportType transport_type,
+ Promise<unique_ptr<mtproto::RawConnection>> promise) {
auto r_socket_fd = SocketFd::open(ip_address);
if (r_socket_fd.is_error()) {
return promise.set_error(r_socket_fd.move_as_error());
}
- auto raw_connection = std::make_unique<mtproto::RawConnection>(r_socket_fd.move_as_ok(),
- mtproto::TransportType::ObfuscatedTcp, nullptr);
- raw_connection->extra_ = network_generation_;
- promise.set_value(std::move(raw_connection));
+ auto socket_fd = r_socket_fd.move_as_ok();
+
+ auto connection_promise = PromiseCreator::lambda([promise = std::move(promise), actor_id = actor_id(this),
+ transport_type, network_generation = network_generation_,
+ ip_address](Result<ConnectionData> r_connection_data) mutable {
+ if (r_connection_data.is_error()) {
+ return promise.set_error(Status::Error(400, r_connection_data.error().public_message()));
+ }
+ auto connection_data = r_connection_data.move_as_ok();
+ auto raw_connection = mtproto::RawConnection::create(ip_address, std::move(connection_data.buffered_socket_fd),
+ transport_type, nullptr);
+ raw_connection->extra().extra = network_generation;
+ promise.set_value(std::move(raw_connection));
+ });
+
+ auto token = next_token();
+ auto ref = prepare_connection(ip_address, std::move(socket_fd), Proxy(), IPAddress(), transport_type, "Raw",
+ PSTRING() << "to IP address " << ip_address, nullptr, create_reference(token), false,
+ std::move(connection_promise));
+ if (!ref.empty()) {
+ children_[token] = {false, std::move(ref)};
+ }
+}
+
+Result<mtproto::TransportType> ConnectionCreator::get_transport_type(const Proxy &proxy,
+ const DcOptionsSet::ConnectionInfo &info) {
+ int32 int_dc_id = info.option->get_dc_id().get_raw_id();
+ if (G()->is_test_dc()) {
+ int_dc_id += 10000;
+ }
+ auto raw_dc_id = narrow_cast<int16>(info.option->is_media_only() ? -int_dc_id : int_dc_id);
+
+ if (proxy.use_mtproto_proxy()) {
+ return mtproto::TransportType{mtproto::TransportType::ObfuscatedTcp, raw_dc_id, proxy.secret()};
+ }
+ if (proxy.use_http_caching_proxy()) {
+ CHECK(info.option != nullptr);
+ string proxy_authorization;
+ if (!proxy.user().empty() || !proxy.password().empty()) {
+ proxy_authorization = "|basic " + base64_encode(PSLICE() << proxy.user() << ':' << proxy.password());
+ }
+ return mtproto::TransportType{mtproto::TransportType::Http, 0,
+ mtproto::ProxySecret::from_raw(
+ PSTRING() << info.option->get_ip_address().get_ip_host() << proxy_authorization)};
+ }
+
+ if (info.use_http) {
+ return mtproto::TransportType{mtproto::TransportType::Http, 0, mtproto::ProxySecret()};
+ } else {
+ return mtproto::TransportType{mtproto::TransportType::ObfuscatedTcp, raw_dc_id, info.option->get_secret()};
+ }
+}
+
+Result<SocketFd> ConnectionCreator::find_connection(const Proxy &proxy, const IPAddress &proxy_ip_address, DcId dc_id,
+ bool allow_media_only, FindConnectionExtra &extra) {
+ extra.debug_str = PSTRING() << "Failed to find valid IP address for " << dc_id;
+ bool prefer_ipv6 = G()->get_option_boolean("prefer_ipv6") || (proxy.use_proxy() && proxy_ip_address.is_ipv6());
+ bool only_http = proxy.use_http_caching_proxy();
+#if TD_DARWIN_WATCH_OS
+ only_http = true;
+#endif
+ TRY_RESULT(info, dc_options_set_.find_connection(
+ dc_id, allow_media_only, proxy.use_proxy() && proxy.use_socks5_proxy(), prefer_ipv6, only_http));
+ extra.stat = info.stat;
+ TRY_RESULT_ASSIGN(extra.transport_type, get_transport_type(proxy, info));
+
+ extra.debug_str = PSTRING() << " to " << (info.option->is_media_only() ? "MEDIA " : "") << dc_id
+ << (info.use_http ? " over HTTP" : "");
+
+ if (proxy.use_mtproto_proxy()) {
+ extra.debug_str = PSTRING() << "MTProto " << proxy_ip_address << extra.debug_str;
+
+ VLOG(connections) << "Create: " << extra.debug_str;
+ return SocketFd::open(proxy_ip_address);
+ }
+
+ extra.check_mode |= info.should_check;
+
+ if (proxy.use_proxy()) {
+ extra.mtproto_ip_address = info.option->get_ip_address();
+ extra.ip_address = proxy_ip_address;
+ extra.debug_str = PSTRING() << (proxy.use_socks5_proxy() ? "Socks5" : (only_http ? "HTTP_ONLY" : "HTTP_TCP")) << ' '
+ << proxy_ip_address << " --> " << extra.mtproto_ip_address << extra.debug_str;
+ } else {
+ extra.ip_address = info.option->get_ip_address();
+ extra.debug_str = PSTRING() << info.option->get_ip_address() << extra.debug_str;
+ }
+ VLOG(connections) << "Create: " << extra.debug_str;
+ return SocketFd::open(extra.ip_address);
+}
+
+ActorOwn<> ConnectionCreator::prepare_connection(IPAddress ip_address, SocketFd socket_fd, const Proxy &proxy,
+ const IPAddress &mtproto_ip_address,
+ const mtproto::TransportType &transport_type, Slice actor_name_prefix,
+ Slice debug_str,
+ unique_ptr<mtproto::RawConnection::StatsCallback> stats_callback,
+ ActorShared<> parent, bool use_connection_token,
+ Promise<ConnectionData> promise) {
+ if (proxy.use_socks5_proxy() || proxy.use_http_tcp_proxy() || transport_type.secret.emulate_tls()) {
+ VLOG(connections) << "Create new transparent proxy connection " << debug_str;
+ class Callback final : public TransparentProxy::Callback {
+ public:
+ explicit Callback(Promise<ConnectionData> promise, IPAddress ip_address,
+ unique_ptr<mtproto::RawConnection::StatsCallback> stats_callback, bool use_connection_token,
+ bool was_connected)
+ : promise_(std::move(promise))
+ , ip_address_(std::move(ip_address))
+ , stats_callback_(std::move(stats_callback))
+ , use_connection_token_(use_connection_token)
+ , was_connected_(was_connected) {
+ }
+ void set_result(Result<BufferedFd<SocketFd>> r_buffered_socket_fd) final {
+ if (r_buffered_socket_fd.is_error()) {
+ if (use_connection_token_) {
+ connection_token_ = mtproto::ConnectionManager::ConnectionToken();
+ }
+ if (was_connected_ && stats_callback_) {
+ stats_callback_->on_error();
+ }
+ promise_.set_error(Status::Error(400, r_buffered_socket_fd.error().public_message()));
+ } else {
+ ConnectionData data;
+ data.ip_address = ip_address_;
+ data.buffered_socket_fd = r_buffered_socket_fd.move_as_ok();
+ data.connection_token = std::move(connection_token_);
+ data.stats_callback = std::move(stats_callback_);
+ promise_.set_value(std::move(data));
+ }
+ }
+ void on_connected() final {
+ if (use_connection_token_) {
+ connection_token_ = mtproto::ConnectionManager::connection_proxy(
+ static_cast<ActorId<mtproto::ConnectionManager>>(G()->state_manager()));
+ }
+ was_connected_ = true;
+ }
+
+ private:
+ Promise<ConnectionData> promise_;
+ mtproto::ConnectionManager::ConnectionToken connection_token_;
+ IPAddress ip_address_;
+ unique_ptr<mtproto::RawConnection::StatsCallback> stats_callback_;
+ bool use_connection_token_{false};
+ bool was_connected_{false};
+ };
+ VLOG(connections) << "Start "
+ << (proxy.use_socks5_proxy() ? "Socks5" : (proxy.use_http_tcp_proxy() ? "HTTP" : "TLS")) << ": "
+ << debug_str;
+ auto callback = make_unique<Callback>(std::move(promise), ip_address, std::move(stats_callback),
+ use_connection_token, !proxy.use_socks5_proxy());
+ if (proxy.use_socks5_proxy()) {
+ return ActorOwn<>(create_actor<Socks5>(PSLICE() << actor_name_prefix << "Socks5", std::move(socket_fd),
+ mtproto_ip_address, proxy.user().str(), proxy.password().str(),
+ std::move(callback), std::move(parent)));
+ } else if (proxy.use_http_tcp_proxy()) {
+ return ActorOwn<>(create_actor<HttpProxy>(PSLICE() << actor_name_prefix << "HttpProxy", std::move(socket_fd),
+ mtproto_ip_address, proxy.user().str(), proxy.password().str(),
+ std::move(callback), std::move(parent)));
+ } else if (transport_type.secret.emulate_tls()) {
+ return ActorOwn<>(create_actor<mtproto::TlsInit>(
+ PSLICE() << actor_name_prefix << "TlsInit", std::move(socket_fd), transport_type.secret.get_domain(),
+ transport_type.secret.get_proxy_secret().str(), std::move(callback), std::move(parent),
+ G()->get_dns_time_difference()));
+ } else {
+ UNREACHABLE();
+ }
+ } else {
+ VLOG(connections) << "Create new direct connection " << debug_str;
+
+ ConnectionData data;
+ data.ip_address = ip_address;
+ data.buffered_socket_fd = BufferedFd<SocketFd>(std::move(socket_fd));
+ data.stats_callback = std::move(stats_callback);
+ promise.set_result(std::move(data));
+ return {};
+ }
}
void ConnectionCreator::client_loop(ClientInfo &client) {
CHECK(client.hash != 0);
if (!network_flag_) {
+ VLOG(connections) << "Exit client_loop, because there is no network";
return;
}
if (close_flag_) {
+ VLOG(connections) << "Exit client_loop, because of closing";
return;
}
- bool use_socks5 = proxy_.type() == Proxy::Type::Socks5;
- if (use_socks5 && !proxy_ip_address_.is_valid()) {
+
+ Proxy proxy = active_proxy_id_ == 0 ? Proxy() : proxies_[active_proxy_id_];
+
+ if (proxy.use_proxy() && !proxy_ip_address_.is_valid()) {
+ VLOG(connections) << "Exit client_loop, because there is no valid IP address for proxy: " << proxy_ip_address_;
return;
}
- VLOG(connections) << "client_loop: " << tag("client", format::as_hex(client.hash));
+ VLOG(connections) << "In client_loop: " << tag("client", format::as_hex(client.hash));
// Remove expired ready connections
- client.ready_connections.erase(
- std::remove_if(client.ready_connections.begin(), client.ready_connections.end(),
- [&, expire_at = Time::now_cached() - ClientInfo::READY_CONNECTIONS_TIMEOUT](auto &v) {
- bool drop = v.second < expire_at;
- VLOG_IF(connections, drop) << "Drop expired " << tag("connection", v.first.get());
- return drop;
- }),
- client.ready_connections.end());
+ td::remove_if(client.ready_connections,
+ [&, expires_at = Time::now_cached() - ClientInfo::READY_CONNECTIONS_TIMEOUT](auto &v) {
+ bool drop = v.second < expires_at;
+ VLOG_IF(connections, drop) << "Drop expired " << tag("connection", v.first.get());
+ return drop;
+ });
// Send ready connections into promises
{
auto begin = client.queries.begin();
auto it = begin;
while (it != client.queries.end() && !client.ready_connections.empty()) {
- VLOG(connections) << "Send to promise " << tag("connection", client.ready_connections.back().first.get());
- it->set_value(std::move(client.ready_connections.back().first));
- client.ready_connections.pop_back();
- it++;
+ if (!it->is_canceled()) {
+ VLOG(connections) << "Send to promise " << tag("connection", client.ready_connections.back().first.get());
+ it->set_value(std::move(client.ready_connections.back().first));
+ client.ready_connections.pop_back();
+ }
+ ++it;
}
client.queries.erase(begin, it);
}
// Main loop. Create new connections till needed
- bool check_mode = client.checking_connections != 0;
+ bool check_mode = client.checking_connections != 0 && !proxy.use_proxy();
while (true) {
// Check if we need new connections
if (client.queries.empty()) {
@@ -354,134 +885,105 @@ void ConnectionCreator::client_loop(ClientInfo &client) {
}
}
+ bool act_as_if_online = online_flag_ || is_logging_out_;
// Check flood
- auto &flood_control = online_flag_ ? client.flood_control_online : client.flood_control;
+ auto &flood_control = act_as_if_online ? client.flood_control_online : client.flood_control;
auto wakeup_at = max(flood_control.get_wakeup_at(), client.mtproto_error_flood_control.get_wakeup_at());
- if (!online_flag_) {
- wakeup_at = max(wakeup_at, client.backoff.get_wakeup_at());
+ wakeup_at = max(client.sanity_flood_control.get_wakeup_at(), wakeup_at);
+
+ if (!act_as_if_online) {
+ wakeup_at = max(wakeup_at, static_cast<double>(client.backoff.get_wakeup_at()));
}
if (wakeup_at > Time::now()) {
return client_set_timeout_at(client, wakeup_at);
}
- flood_control.add_event(static_cast<int32>(Time::now()));
- if (!online_flag_) {
+ client.sanity_flood_control.add_event(Time::now());
+ if (!act_as_if_online) {
client.backoff.add_event(static_cast<int32>(Time::now()));
}
// Create new RawConnection
- DcOptionsSet::Stat *stat{nullptr};
- bool use_http{false};
- string debug_str;
-
- IPAddress mtproto_ip;
-
// sync part
- auto r_socket_fd = [&, dc_id = client.dc_id, allow_media_only = client.allow_media_only]() -> Result<SocketFd> {
- TRY_RESULT(info, dc_options_set_.find_connection(dc_id, allow_media_only, use_socks5));
- stat = info.stat;
- use_http = info.use_http;
- check_mode |= info.should_check;
-
- if (use_socks5) {
- mtproto_ip = info.option->get_ip_address();
- IPAddress socks5_ip;
- TRY_STATUS(socks5_ip.init_host_port(proxy_.server(), proxy_.port()));
- debug_str = PSTRING() << "Sock5 " << socks5_ip << " --> " << info.option->get_ip_address() << " " << dc_id
- << (info.use_http ? " HTTP" : "");
- LOG(INFO) << "Create: " << debug_str;
- return SocketFd::open(socks5_ip);
- } else {
- debug_str = PSTRING() << info.option->get_ip_address() << " " << dc_id << (info.use_http ? " HTTP" : "");
- LOG(INFO) << "Create: " << debug_str;
- return SocketFd::open(info.option->get_ip_address());
- }
- }();
+ FindConnectionExtra extra;
+ auto r_socket_fd = find_connection(proxy, proxy_ip_address_, client.dc_id, client.allow_media_only, extra);
+ check_mode |= extra.check_mode;
if (r_socket_fd.is_error()) {
- LOG(WARNING) << r_socket_fd.error();
- if (stat) {
- stat->on_error(); // TODO: different kind of error
+ LOG(WARNING) << extra.debug_str << ": " << r_socket_fd.error();
+ if (extra.stat) {
+ extra.stat->on_error(); // TODO: different kind of error
}
return client_set_timeout_at(client, Time::now() + 0.1);
}
+ // Events with failed socket creation are ignored
+ flood_control.add_event(Time::now());
+
auto socket_fd = r_socket_fd.move_as_ok();
IPAddress debug_ip;
auto debug_ip_status = debug_ip.init_socket_address(socket_fd);
if (debug_ip_status.is_ok()) {
- debug_str = PSTRING() << debug_str << debug_ip;
+ extra.debug_str = PSTRING() << extra.debug_str << " from " << debug_ip;
} else {
LOG(ERROR) << debug_ip_status;
}
client.pending_connections++;
if (check_mode) {
- stat->on_check();
+ if (extra.stat) {
+ extra.stat->on_check();
+ }
client.checking_connections++;
}
auto promise = PromiseCreator::lambda(
- [actor_id = actor_id(this), check_mode, use_http, hash = client.hash, debug_str,
+ [actor_id = actor_id(this), check_mode, transport_type = extra.transport_type, hash = client.hash,
+ debug_str = extra.debug_str,
network_generation = network_generation_](Result<ConnectionData> r_connection_data) mutable {
- send_closure(std::move(actor_id), &ConnectionCreator::client_create_raw_connection,
- std::move(r_connection_data), check_mode, use_http, hash, debug_str, network_generation);
+ send_closure(actor_id, &ConnectionCreator::client_create_raw_connection, std::move(r_connection_data),
+ check_mode, std::move(transport_type), hash, std::move(debug_str), network_generation);
});
- auto stats_callback = std::make_unique<detail::StatsCallback>(
- client.is_media ? media_net_stats_callback_ : common_net_stats_callback_, actor_id(this), client.hash, stat);
-
- if (use_socks5) {
- class Callback : public Socks5::Callback {
- public:
- explicit Callback(Promise<ConnectionData> promise, std::unique_ptr<detail::StatsCallback> stats_callback)
- : promise_(std::move(promise)), stats_callback_(std::move(stats_callback)) {
- }
- void set_result(Result<SocketFd> result) override {
- if (result.is_error()) {
- connection_token_ = StateManager::ConnectionToken();
- if (was_connected_) {
- stats_callback_->on_error();
- }
- promise_.set_error(result.move_as_error());
- } else {
- ConnectionData data;
- data.socket_fd = result.move_as_ok();
- data.connection_token = std::move(connection_token_);
- data.stats_callback = std::move(stats_callback_);
- promise_.set_value(std::move(data));
- }
- }
- void on_connected() override {
- connection_token_ = StateManager::connection_proxy(G()->state_manager());
- was_connected_ = true;
- }
-
- private:
- Promise<ConnectionData> promise_;
- StateManager::ConnectionToken connection_token_;
- bool was_connected_{false};
- std::unique_ptr<detail::StatsCallback> stats_callback_;
- };
- LOG(INFO) << "Start socks5: " << debug_str;
- auto token = next_token();
- children_[token] = create_actor<Socks5>(
- "Socks5", std::move(socket_fd), mtproto_ip, proxy_.user().str(), proxy_.password().str(),
- std::make_unique<Callback>(std::move(promise), std::move(stats_callback)), create_reference(token));
- } else {
- ConnectionData data;
- data.socket_fd = std::move(socket_fd);
- data.stats_callback = std::move(stats_callback);
- promise.set_result(std::move(data));
+ auto stats_callback =
+ td::make_unique<detail::StatsCallback>(client.is_media ? media_net_stats_callback_ : common_net_stats_callback_,
+ actor_id(this), client.hash, extra.stat);
+ auto token = next_token();
+ auto ref = prepare_connection(extra.ip_address, std::move(socket_fd), proxy, extra.mtproto_ip_address,
+ extra.transport_type, Slice(), extra.debug_str, std::move(stats_callback),
+ create_reference(token), true, std::move(promise));
+ if (!ref.empty()) {
+ children_[token] = {true, std::move(ref)};
}
}
}
void ConnectionCreator::client_create_raw_connection(Result<ConnectionData> r_connection_data, bool check_mode,
- bool use_http, size_t hash, string debug_str,
- uint32 network_generation) {
- auto promise = PromiseCreator::lambda([actor_id = actor_id(this), hash, check_mode,
- debug_str](Result<std::unique_ptr<mtproto::RawConnection>> result) mutable {
- VLOG(connections) << "Ready " << debug_str << " " << tag("checked", check_mode) << tag("ok", result.is_ok());
- send_closure(std::move(actor_id), &ConnectionCreator::client_add_connection, hash, std::move(result), check_mode);
+ mtproto::TransportType transport_type, uint32 hash,
+ string debug_str, uint32 network_generation) {
+ unique_ptr<mtproto::AuthData> auth_data;
+ uint64 auth_data_generation{0};
+ int64 session_id{0};
+ if (check_mode) {
+ auto it = clients_.find(hash);
+ CHECK(it != clients_.end());
+ const auto &auth_data_ptr = it->second.auth_data;
+ if (auth_data_ptr && auth_data_ptr->use_pfs() && auth_data_ptr->has_auth_key(Time::now_cached())) {
+ auth_data = make_unique<mtproto::AuthData>(*auth_data_ptr);
+ auth_data_generation = it->second.auth_data_generation;
+ session_id = it->second.extract_session_id();
+ auth_data->set_session_id(session_id);
+ }
+ }
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), hash, check_mode, auth_data_generation, session_id,
+ debug_str](Result<unique_ptr<mtproto::RawConnection>> result) mutable {
+ if (result.is_ok()) {
+ VLOG(connections) << "Ready connection (" << (check_mode ? "" : "un") << "checked) " << result.ok().get() << ' '
+ << tag("rtt", format::as_time(result.ok()->extra().rtt)) << ' ' << debug_str;
+ } else {
+ VLOG(connections) << "Failed connection (" << (check_mode ? "" : "un") << "checked) " << result.error() << ' '
+ << debug_str;
+ }
+ send_closure(actor_id, &ConnectionCreator::client_add_connection, hash, std::move(result), check_mode,
+ auth_data_generation, session_id);
});
if (r_connection_data.is_error()) {
@@ -489,20 +991,19 @@ void ConnectionCreator::client_create_raw_connection(Result<ConnectionData> r_co
}
auto connection_data = r_connection_data.move_as_ok();
- auto raw_connection = std::make_unique<mtproto::RawConnection>(
- std::move(connection_data.socket_fd),
- use_http ? mtproto::TransportType::Http : mtproto::TransportType::ObfuscatedTcp,
- std::move(connection_data.stats_callback));
+ auto raw_connection =
+ mtproto::RawConnection::create(connection_data.ip_address, std::move(connection_data.buffered_socket_fd),
+ std::move(transport_type), std::move(connection_data.stats_callback));
raw_connection->set_connection_token(std::move(connection_data.connection_token));
- raw_connection->extra_ = network_generation;
- raw_connection->debug_str_ = debug_str;
+ raw_connection->extra().extra = network_generation;
+ raw_connection->extra().debug_str = debug_str;
if (check_mode) {
- VLOG(connections) << "Start check: " << debug_str;
+ VLOG(connections) << "Start check: " << debug_str << " " << (auth_data ? "with" : "without") << " auth data";
auto token = next_token();
- children_[token] = create_actor<detail::PingActor>("PingActor", std::move(raw_connection), std::move(promise),
- create_reference(token));
+ children_[token] = {true, create_ping_actor(debug_str, std::move(raw_connection), std::move(auth_data),
+ std::move(promise), create_reference(token))};
} else {
promise.set_value(std::move(raw_connection));
}
@@ -517,10 +1018,10 @@ void ConnectionCreator::client_set_timeout_at(ClientInfo &client, double wakeup_
<< wakeup_at - Time::now_cached();
}
-void ConnectionCreator::client_add_connection(size_t hash,
- Result<std::unique_ptr<mtproto::RawConnection>> r_raw_connection,
- bool check_flag) {
+void ConnectionCreator::client_add_connection(uint32 hash, Result<unique_ptr<mtproto::RawConnection>> r_raw_connection,
+ bool check_flag, uint64 auth_data_generation, int64 session_id) {
auto &client = clients_[hash];
+ client.add_session_id(session_id);
CHECK(client.pending_connections > 0);
client.pending_connections--;
if (check_flag) {
@@ -528,19 +1029,29 @@ void ConnectionCreator::client_add_connection(size_t hash,
client.checking_connections--;
}
if (r_raw_connection.is_ok()) {
+ VLOG(connections) << "Add ready connection " << r_raw_connection.ok().get() << " for "
+ << tag("client", format::as_hex(hash));
client.backoff.clear();
- client.ready_connections.push_back(std::make_pair(r_raw_connection.move_as_ok(), Time::now_cached()));
+ client.ready_connections.emplace_back(r_raw_connection.move_as_ok(), Time::now_cached());
+ } else {
+ if (r_raw_connection.error().code() == -404 && client.auth_data &&
+ client.auth_data_generation == auth_data_generation) {
+ VLOG(connections) << "Drop auth data from " << tag("client", format::as_hex(hash));
+ client.auth_data = nullptr;
+ client.auth_data_generation++;
+ }
}
client_loop(client);
}
-void ConnectionCreator::client_wakeup(size_t hash) {
- LOG(INFO) << tag("hash", format::as_hex(hash)) << " wakeup";
+void ConnectionCreator::client_wakeup(uint32 hash) {
+ VLOG(connections) << tag("hash", format::as_hex(hash)) << " wakeup";
+ G()->save_server_time();
client_loop(clients_[hash]);
}
void ConnectionCreator::on_dc_options(DcOptions new_dc_options) {
- LOG(INFO) << "SAVE " << new_dc_options;
+ VLOG(connections) << "SAVE " << new_dc_options;
G()->td_db()->get_binlog_pmc()->set("dc_options", serialize(new_dc_options));
dc_options_set_.reset();
dc_options_set_.add_dc_options(get_default_dc_options(G()->is_test_dc()));
@@ -550,35 +1061,48 @@ void ConnectionCreator::on_dc_options(DcOptions new_dc_options) {
}
void ConnectionCreator::on_dc_update(DcId dc_id, string ip_port, Promise<> promise) {
- promise.set_result([&]() -> Result<> {
- if (!dc_id.is_exact()) {
- return Status::Error("Invalid dc_id");
- }
- IPAddress ip_address;
- TRY_STATUS(ip_address.init_host_port(ip_port));
- DcOptions options;
- options.dc_options.emplace_back(dc_id, ip_address);
- send_closure(G()->config_manager(), &ConfigManager::on_dc_options_update, std::move(options));
- return Unit();
- }());
+ if (!dc_id.is_exact()) {
+ return promise.set_error(Status::Error("Invalid dc_id"));
+ }
+
+ IPAddress ip_address;
+ TRY_STATUS_PROMISE(promise, ip_address.init_host_port(ip_port));
+ DcOptions options;
+ options.dc_options.emplace_back(dc_id, ip_address);
+ send_closure(G()->config_manager(), &ConfigManager::on_dc_options_update, std::move(options));
+ promise.set_value(Unit());
+}
+
+void ConnectionCreator::update_mtproto_header(const Proxy &proxy) {
+ if (G()->have_mtproto_header()) {
+ G()->mtproto_header().set_proxy(proxy);
+ }
+ if (G()->have_net_query_dispatcher()) {
+ G()->net_query_dispatcher().update_mtproto_header();
+ }
}
void ConnectionCreator::start_up() {
- class StateCallback : public StateManager::Callback {
+ class StateCallback final : public StateManager::Callback {
public:
- explicit StateCallback(ActorId<ConnectionCreator> session) : session_(std::move(session)) {
+ explicit StateCallback(ActorId<ConnectionCreator> connection_creator)
+ : connection_creator_(std::move(connection_creator)) {
}
- bool on_network(NetType network_type, uint32 generation) override {
- send_closure(session_, &ConnectionCreator::on_network, network_type != NetType::None, generation);
- return session_.is_alive();
+ bool on_network(NetType network_type, uint32 generation) final {
+ send_closure(connection_creator_, &ConnectionCreator::on_network, network_type != NetType::None, generation);
+ return connection_creator_.is_alive();
}
- bool on_online(bool online_flag) override {
- send_closure(session_, &ConnectionCreator::on_online, online_flag);
- return session_.is_alive();
+ bool on_online(bool online_flag) final {
+ send_closure(connection_creator_, &ConnectionCreator::on_online, online_flag);
+ return connection_creator_.is_alive();
+ }
+ bool on_logging_out(bool is_logging_out) final {
+ send_closure(connection_creator_, &ConnectionCreator::on_logging_out, is_logging_out);
+ return connection_creator_.is_alive();
}
private:
- ActorId<ConnectionCreator> session_;
+ ActorId<ConnectionCreator> connection_creator_;
};
send_closure(G()->state_manager(), &StateManager::add_callback, make_unique<StateCallback>(actor_id(this)));
@@ -591,18 +1115,65 @@ void ConnectionCreator::start_up() {
on_dc_options(std::move(dc_options));
}
- auto log_event_proxy = G()->td_db()->get_binlog_pmc()->get("proxy");
- if (!log_event_proxy.empty()) {
- Proxy proxy;
- log_event_parse(proxy, log_event_proxy).ensure();
- set_proxy_impl(std::move(proxy), true);
+ auto proxy_info = G()->td_db()->get_binlog_pmc()->prefix_get("proxy");
+ auto it = proxy_info.find("_max_id");
+ if (it != proxy_info.end()) {
+ max_proxy_id_ = to_integer<int32>(it->second);
+ proxy_info.erase(it);
+ }
+ it = proxy_info.find("_active_id");
+ if (it != proxy_info.end()) {
+ set_active_proxy_id(to_integer<int32>(it->second), true);
+ proxy_info.erase(it);
+ }
+
+ for (auto &info : proxy_info) {
+ if (begins_with(info.first, "_used")) {
+ auto proxy_id = to_integer_safe<int32>(Slice(info.first).substr(5)).move_as_ok();
+ auto last_used = to_integer_safe<int32>(info.second).move_as_ok();
+ CHECK(proxy_id > 0);
+ proxy_last_used_date_[proxy_id] = last_used;
+ proxy_last_used_saved_date_[proxy_id] = last_used;
+ } else {
+ LOG_CHECK(!ends_with(info.first, "_max_id")) << info.first;
+ auto proxy_id = info.first.empty() ? static_cast<int32>(1) : to_integer_safe<int32>(info.first).move_as_ok();
+ CHECK(proxy_id > 0);
+ CHECK(proxies_.count(proxy_id) == 0);
+ log_event_parse(proxies_[proxy_id], info.second).ensure();
+ if (proxies_[proxy_id].type() == Proxy::Type::None) {
+ LOG_IF(ERROR, proxy_id != 1) << "Have empty proxy " << proxy_id;
+ proxies_.erase(proxy_id);
+ if (active_proxy_id_ == proxy_id) {
+ set_active_proxy_id(0);
+ }
+ }
+ }
+ }
+
+ if (max_proxy_id_ == 0) {
+ // legacy one-proxy version
+ max_proxy_id_ = 2;
+ if (!proxies_.empty()) {
+ CHECK(proxies_.begin()->first == 1);
+ set_active_proxy_id(1);
+ }
+ G()->td_db()->get_binlog_pmc()->set("proxy_max_id", "2");
+ } else if (max_proxy_id_ < 2) {
+ LOG(ERROR) << "Found wrong max_proxy_id = " << max_proxy_id_;
+ max_proxy_id_ = 2;
}
- get_host_by_name_actor_ =
- create_actor_on_scheduler<GetHostByNameActor>("GetHostByNameActor", G()->get_gc_scheduler_id(), 29 * 60, 0);
+ if (active_proxy_id_ != 0) {
+ if (proxies_[active_proxy_id_].type() == Proxy::Type::Mtproto) {
+ update_mtproto_header(proxies_[active_proxy_id_]);
+ }
+
+ on_proxy_changed(true);
+ }
ref_cnt_guard_ = create_reference(-1);
+ is_inited_ = true;
loop();
}
@@ -622,91 +1193,159 @@ ActorShared<ConnectionCreator> ConnectionCreator::create_reference(int64 token)
void ConnectionCreator::hangup() {
close_flag_ = true;
+ save_proxy_last_used_date(0);
ref_cnt_guard_.reset();
for (auto &child : children_) {
- child.second.reset();
+ child.second.second.reset();
}
}
DcOptions ConnectionCreator::get_default_dc_options(bool is_test) {
DcOptions res;
- auto add_ip_ports = [&res](int32 dc_id, const vector<string> &ips, const vector<int> &ports, bool is_ipv6 = false) {
+ enum class HostType : int32 { IPv4, IPv6, Url };
+ auto add_ip_ports = [&res](int32 dc_id, const vector<string> &ips, const vector<int> &ports,
+ HostType type = HostType::IPv4) {
IPAddress ip_address;
- for (auto &ip : ips) {
- for (auto port : ports) {
- if (is_ipv6) {
- ip_address.init_ipv6_port(ip, port).ensure();
- } else {
- ip_address.init_ipv4_port(ip, port).ensure();
+ for (auto port : ports) {
+ for (auto &ip : ips) {
+ switch (type) {
+ case HostType::IPv4:
+ ip_address.init_ipv4_port(ip, port).ensure();
+ break;
+ case HostType::IPv6:
+ ip_address.init_ipv6_port(ip, port).ensure();
+ break;
+ case HostType::Url:
+ ip_address.init_host_port(ip, port).ensure();
+ break;
}
res.dc_options.emplace_back(DcId::internal(dc_id), ip_address);
}
}
};
vector<int> ports = {443, 80, 5222};
+#if TD_EMSCRIPTEN
+ if (is_test) {
+ add_ip_ports(1, {"pluto.web.telegram.org/apiws_test"}, {443}, HostType::Url);
+ add_ip_ports(2, {"venus.web.telegram.org/apiws_test"}, {443}, HostType::Url);
+ add_ip_ports(3, {"aurora.web.telegram.org/apiws_test"}, {443}, HostType::Url);
+ } else {
+ add_ip_ports(1, {"pluto.web.telegram.org/apiws"}, {443}, HostType::Url);
+ add_ip_ports(2, {"venus.web.telegram.org/apiws"}, {443}, HostType::Url);
+ add_ip_ports(3, {"aurora.web.telegram.org/apiws"}, {443}, HostType::Url);
+ add_ip_ports(4, {"vesta.web.telegram.org/apiws"}, {443}, HostType::Url);
+ add_ip_ports(5, {"flora.web.telegram.org/apiws"}, {443}, HostType::Url);
+ }
+#else
if (is_test) {
add_ip_ports(1, {"149.154.175.10"}, ports);
add_ip_ports(2, {"149.154.167.40"}, ports);
add_ip_ports(3, {"149.154.175.117"}, ports);
- add_ip_ports(1, {"2001:b28:f23d:f001::e"}, ports, true);
- add_ip_ports(2, {"2001:67c:4e8:f002::e"}, ports, true);
- add_ip_ports(3, {"2001:b28:f23d:f003::e"}, ports, true);
+ add_ip_ports(1, {"2001:b28:f23d:f001::e"}, ports, HostType::IPv6);
+ add_ip_ports(2, {"2001:67c:4e8:f002::e"}, ports, HostType::IPv6);
+ add_ip_ports(3, {"2001:b28:f23d:f003::e"}, ports, HostType::IPv6);
} else {
add_ip_ports(1, {"149.154.175.50"}, ports);
- add_ip_ports(2, {"149.154.167.51"}, ports);
+ add_ip_ports(2, {"149.154.167.51", "95.161.76.100"}, ports);
add_ip_ports(3, {"149.154.175.100"}, ports);
add_ip_ports(4, {"149.154.167.91"}, ports);
add_ip_ports(5, {"149.154.171.5"}, ports);
- add_ip_ports(1, {"2001:b28:f23d:f001::a"}, ports, true);
- add_ip_ports(2, {"2001:67c:4e8:f002::a"}, ports, true);
- add_ip_ports(3, {"2001:b28:f23d:f003::a"}, ports, true);
- add_ip_ports(4, {"2001:67c:4e8:f004::a"}, ports, true);
- add_ip_ports(5, {"2001:b28:f23f:f005::a"}, ports, true);
+ add_ip_ports(1, {"2001:b28:f23d:f001::a"}, ports, HostType::IPv6);
+ add_ip_ports(2, {"2001:67c:4e8:f002::a"}, ports, HostType::IPv6);
+ add_ip_ports(3, {"2001:b28:f23d:f003::a"}, ports, HostType::IPv6);
+ add_ip_ports(4, {"2001:67c:4e8:f004::a"}, ports, HostType::IPv6);
+ add_ip_ports(5, {"2001:b28:f23f:f005::a"}, ports, HostType::IPv6);
}
+#endif
return res;
}
void ConnectionCreator::loop() {
- if (!network_flag_) {
+ if (G()->close_flag()) {
return;
}
- if (proxy_.type() != Proxy::Type::Socks5) {
+ if (!is_inited_) {
return;
}
- if (resolve_proxy_query_token_ != 0) {
+ if (!network_flag_) {
return;
}
- if (resolve_proxy_timestamp_ && !resolve_proxy_timestamp_.is_in_past()) {
- set_timeout_at(resolve_proxy_timestamp_.at());
- return;
+
+ Timestamp timeout;
+ if (active_proxy_id_ != 0) {
+ if (resolve_proxy_timestamp_.is_in_past()) {
+ if (resolve_proxy_query_token_ == 0) {
+ resolve_proxy_query_token_ = next_token();
+ const Proxy &proxy = proxies_[active_proxy_id_];
+ bool prefer_ipv6 = G()->get_option_boolean("prefer_ipv6");
+ VLOG(connections) << "Resolve IP address " << resolve_proxy_query_token_ << " of " << proxy.server();
+ send_closure(
+ get_dns_resolver(), &GetHostByNameActor::run, proxy.server().str(), proxy.port(), prefer_ipv6,
+ PromiseCreator::lambda([actor_id = create_reference(resolve_proxy_query_token_)](Result<IPAddress> result) {
+ send_closure(actor_id, &ConnectionCreator::on_proxy_resolved, std::move(result), false);
+ }));
+ }
+ } else {
+ CHECK(resolve_proxy_query_token_ == 0);
+ timeout.relax(resolve_proxy_timestamp_);
+ }
+ }
+
+ if (timeout) {
+ set_timeout_at(timeout.at());
}
- resolve_proxy_query_token_ = next_token();
- send_closure(
- get_host_by_name_actor_, &GetHostByNameActor::run, proxy_.server().str(), proxy_.port(),
- PromiseCreator::lambda([actor_id = create_reference(resolve_proxy_query_token_)](Result<IPAddress> result) {
- send_closure(std::move(actor_id), &ConnectionCreator::on_proxy_resolved, std::move(result), false);
- }));
}
void ConnectionCreator::on_proxy_resolved(Result<IPAddress> r_ip_address, bool dummy) {
SCOPE_EXIT {
loop();
};
+
if (get_link_token() != resolve_proxy_query_token_) {
+ VLOG(connections) << "Ignore unneeded proxy IP address " << get_link_token() << ", expected "
+ << resolve_proxy_query_token_;
return;
}
+
resolve_proxy_query_token_ = 0;
if (r_ip_address.is_error()) {
- resolve_proxy_timestamp_ = Timestamp::in(5 * 60);
+ VLOG(connections) << "Receive error for resolving proxy IP address: " << r_ip_address.error();
+ resolve_proxy_timestamp_ = Timestamp::in(1 * 60);
return;
}
proxy_ip_address_ = r_ip_address.move_as_ok();
- resolve_proxy_timestamp_ = Timestamp::in(29 * 60);
+ VLOG(connections) << "Set proxy IP address to " << proxy_ip_address_;
+ resolve_proxy_timestamp_ = Timestamp::in(5 * 60);
for (auto &client : clients_) {
client_loop(client.second);
}
}
+void ConnectionCreator::on_ping_main_dc_result(uint64 token, Result<double> result) {
+ auto &request = ping_main_dc_requests_[token];
+ CHECK(request.left_queries > 0);
+ if (result.is_error()) {
+ LOG(DEBUG) << "Receive ping error " << result.error();
+ if (request.result.is_error()) {
+ request.result = std::move(result);
+ }
+ } else {
+ LOG(DEBUG) << "Receive ping result " << result.ok();
+ if (request.result.is_error() || request.result.ok() > result.ok()) {
+ request.result = result.ok();
+ }
+ }
+
+ if (--request.left_queries == 0) {
+ if (request.result.is_error()) {
+ request.promise.set_error(Status::Error(400, request.result.error().public_message()));
+ } else {
+ request.promise.set_value(request.result.move_as_ok());
+ }
+ ping_main_dc_requests_.erase(token);
+ }
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.h b/protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.h
index 1c5dbc8d9f..ee8aeafe2b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/ConnectionCreator.h
@@ -1,149 +1,100 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/td_api.h"
-
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/DcOptions.h"
#include "td/telegram/net/DcOptionsSet.h"
-#include "td/telegram/StateManager.h"
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/net/Proxy.h"
+#include "td/telegram/td_api.h"
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-#include "td/actor/SignalSlot.h"
+#include "td/mtproto/AuthData.h"
+#include "td/mtproto/ConnectionManager.h"
+#include "td/mtproto/RawConnection.h"
+#include "td/mtproto/TransportType.h"
#include "td/net/NetStats.h"
+#include "td/actor/actor.h"
+#include "td/actor/SignalSlot.h"
+
+#include "td/utils/BufferedFd.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/FloodControlStrict.h"
#include "td/utils/logging.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/SocketFd.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/Time.h"
#include <map>
#include <memory>
+#include <set>
#include <utility>
namespace td {
-namespace mtproto {
-class RawConnection;
-} // namespace mtproto
+
namespace detail {
class StatsCallback;
-}
-class GetHostByNameActor;
-} // namespace td
-
-namespace td {
-
-class Proxy {
- public:
- tl_object_ptr<td_api::Proxy> as_td_api() const {
- switch (type_) {
- case Type::None:
- return make_tl_object<td_api::proxyEmpty>();
- case Type::Socks5:
- return make_tl_object<td_api::proxySocks5>(server_, port_, user_, password_);
- }
- UNREACHABLE();
- return nullptr;
- }
-
- static Proxy from_td_api(const tl_object_ptr<td_api::Proxy> &proxy) {
- if (proxy == nullptr) {
- return Proxy();
- }
-
- switch (proxy->get_id()) {
- case td_api::proxyEmpty::ID:
- return Proxy();
- case td_api::proxySocks5::ID: {
- auto &socks5_proxy = static_cast<const td_api::proxySocks5 &>(*proxy);
- return Proxy::socks5(socks5_proxy.server_, socks5_proxy.port_, socks5_proxy.username_, socks5_proxy.password_);
- }
- }
- UNREACHABLE();
- return Proxy();
- }
-
- static Proxy socks5(string server, int32 port, string user, string password) {
- Proxy proxy;
- proxy.type_ = Type::Socks5;
- proxy.server_ = std::move(server);
- proxy.port_ = std::move(port);
- proxy.user_ = std::move(user);
- proxy.password_ = std::move(password);
- return proxy;
- }
-
- CSlice server() const {
- return server_;
- }
-
- int32 port() const {
- return port_;
- }
-
- CSlice user() const {
- return user_;
- }
-
- CSlice password() const {
- return password_;
- }
-
- enum class Type { None, Socks5 };
- Type type() const {
- return type_;
- }
+} // namespace detail
- template <class T>
- void parse(T &parser);
-
- template <class T>
- void store(T &storer) const;
-
- private:
- Type type_{Type::None};
- string server_;
- int32 port_ = 0;
- string user_;
- string password_;
-};
-
-inline bool operator==(const Proxy &lhs, const Proxy &rhs) {
- return lhs.type() == rhs.type() && lhs.server() == rhs.server() && lhs.port() == rhs.port() &&
- lhs.user() == rhs.user() && lhs.password() == rhs.password();
-}
+class GetHostByNameActor;
-inline bool operator!=(const Proxy &lhs, const Proxy &rhs) {
- return !(lhs == rhs);
-}
+extern int VERBOSITY_NAME(connections);
-class ConnectionCreator : public Actor {
+class ConnectionCreator final : public NetQueryCallback {
public:
explicit ConnectionCreator(ActorShared<> parent);
ConnectionCreator(ConnectionCreator &&other);
ConnectionCreator &operator=(ConnectionCreator &&other);
- ~ConnectionCreator() override;
+ ~ConnectionCreator() final;
+
void on_dc_options(DcOptions new_dc_options);
void on_dc_update(DcId dc_id, string ip_port, Promise<> promise);
- void on_mtproto_error(size_t hash);
+ void on_pong(uint32 hash);
+ void on_mtproto_error(uint32 hash);
void request_raw_connection(DcId dc_id, bool allow_media_only, bool is_media,
- Promise<std::unique_ptr<mtproto::RawConnection>> promise, size_t hash = 0);
- void request_raw_connection_by_ip(IPAddress ip_address, Promise<std::unique_ptr<mtproto::RawConnection>> promise);
+ Promise<unique_ptr<mtproto::RawConnection>> promise, uint32 hash = 0,
+ unique_ptr<mtproto::AuthData> auth_data = {});
+ void request_raw_connection_by_ip(IPAddress ip_address, mtproto::TransportType transport_type,
+ Promise<unique_ptr<mtproto::RawConnection>> promise);
void set_net_stats_callback(std::shared_ptr<NetStatsCallback> common_callback,
std::shared_ptr<NetStatsCallback> media_callback);
- void set_proxy(Proxy proxy);
- void get_proxy(Promise<Proxy> promise);
+ void add_proxy(int32 old_proxy_id, string server, int32 port, bool enable,
+ td_api::object_ptr<td_api::ProxyType> proxy_type, Promise<td_api::object_ptr<td_api::proxy>> promise);
+ void enable_proxy(int32 proxy_id, Promise<Unit> promise);
+ void disable_proxy(Promise<Unit> promise);
+ void remove_proxy(int32 proxy_id, Promise<Unit> promise);
+ void get_proxies(Promise<td_api::object_ptr<td_api::proxies>> promise);
+ void get_proxy_link(int32 proxy_id, Promise<string> promise);
+ void ping_proxy(int32 proxy_id, Promise<double> promise);
+
+ struct ConnectionData {
+ IPAddress ip_address;
+ BufferedFd<SocketFd> buffered_socket_fd;
+ mtproto::ConnectionManager::ConnectionToken connection_token;
+ unique_ptr<mtproto::RawConnection::StatsCallback> stats_callback;
+ };
+
+ static DcOptions get_default_dc_options(bool is_test);
+
+ static ActorOwn<> prepare_connection(IPAddress ip_address, SocketFd socket_fd, const Proxy &proxy,
+ const IPAddress &mtproto_ip_address,
+ const mtproto::TransportType &transport_type, Slice actor_name_prefix,
+ Slice debug_str,
+ unique_ptr<mtproto::RawConnection::StatsCallback> stats_callback,
+ ActorShared<> parent, bool use_connection_token,
+ Promise<ConnectionData> promise);
private:
ActorShared<> parent_;
@@ -151,9 +102,17 @@ class ConnectionCreator : public Actor {
bool network_flag_ = false;
uint32 network_generation_ = 0;
bool online_flag_ = false;
-
- Proxy proxy_;
+ bool is_logging_out_ = false;
+ bool is_inited_ = false;
+
+ static constexpr int32 MAX_PROXY_LAST_USED_SAVE_DELAY = 60;
+ std::map<int32, Proxy> proxies_;
+ FlatHashMap<int32, int32> proxy_last_used_date_;
+ FlatHashMap<int32, int32> proxy_last_used_saved_date_;
+ int32 max_proxy_id_ = 0;
+ int32 active_proxy_id_ = 0;
ActorOwn<GetHostByNameActor> get_host_by_name_actor_;
+ ActorOwn<GetHostByNameActor> block_get_host_by_name_actor_;
IPAddress proxy_ip_address_;
Timestamp resolve_proxy_timestamp_;
uint64 resolve_proxy_query_token_{0};
@@ -183,73 +142,115 @@ class ConnectionCreator : public Actor {
int32 next_delay_ = 1;
};
ClientInfo();
+ int64 extract_session_id();
+ void add_session_id(int64 session_id);
Backoff backoff;
+ FloodControlStrict sanity_flood_control;
FloodControlStrict flood_control;
FloodControlStrict flood_control_online;
FloodControlStrict mtproto_error_flood_control;
Slot slot;
size_t pending_connections{0};
size_t checking_connections{0};
- std::vector<std::pair<std::unique_ptr<mtproto::RawConnection>, double>> ready_connections;
- std::vector<Promise<std::unique_ptr<mtproto::RawConnection>>> queries;
+ std::vector<std::pair<unique_ptr<mtproto::RawConnection>, double>> ready_connections;
+ std::vector<Promise<unique_ptr<mtproto::RawConnection>>> queries;
static constexpr double READY_CONNECTIONS_TIMEOUT = 10;
bool inited{false};
- size_t hash{0};
+ uint32 hash{0};
DcId dc_id;
- bool allow_media_only;
- bool is_media;
+ bool allow_media_only{false};
+ bool is_media{false};
+ std::set<int64> session_ids_;
+ unique_ptr<mtproto::AuthData> auth_data;
+ uint64 auth_data_generation{0};
};
- std::map<size_t, ClientInfo> clients_;
+ std::map<uint32, ClientInfo> clients_;
std::shared_ptr<NetStatsCallback> media_net_stats_callback_;
std::shared_ptr<NetStatsCallback> common_net_stats_callback_;
- ActorShared<> ref_cnt_guard_;
+ ActorShared<ConnectionCreator> ref_cnt_guard_;
int ref_cnt_{0};
- ActorShared<ConnectionCreator> create_reference(int64 token);
bool close_flag_{false};
- int64 current_token_ = 0;
- std::map<int64, ActorShared<>> children_;
+ uint64 current_token_ = 0;
+ std::map<uint64, std::pair<bool, ActorShared<>>> children_;
- int64 next_token() {
+ struct PingMainDcRequest {
+ Promise<double> promise;
+ size_t left_queries = 0;
+ Result<double> result;
+ };
+ std::map<uint64, PingMainDcRequest> ping_main_dc_requests_;
+
+ uint64 next_token() {
return ++current_token_;
}
- void set_proxy_impl(Proxy proxy, bool from_db);
- void start_up() override;
- void hangup_shared() override;
- void hangup() override;
- void loop() override;
+ ActorShared<ConnectionCreator> create_reference(int64 token);
+
+ void set_active_proxy_id(int32 proxy_id, bool from_binlog = false);
+ void enable_proxy_impl(int32 proxy_id);
+ void disable_proxy_impl();
+ void on_proxy_changed(bool from_db);
+ static string get_proxy_database_key(int32 proxy_id);
+ static string get_proxy_used_database_key(int32 proxy_id);
+ void save_proxy_last_used_date(int32 delay);
+ td_api::object_ptr<td_api::proxy> get_proxy_object(int32 proxy_id) const;
+
+ void start_up() final;
+ void hangup_shared() final;
+ void hangup() final;
+ void loop() final;
void save_dc_options();
Result<SocketFd> do_request_connection(DcId dc_id, bool allow_media_only);
- Result<std::pair<std::unique_ptr<mtproto::RawConnection>, bool>> do_request_raw_connection(DcId dc_id,
- bool allow_media_only,
- bool is_media,
- size_t hash);
+ Result<std::pair<unique_ptr<mtproto::RawConnection>, bool>> do_request_raw_connection(DcId dc_id,
+ bool allow_media_only,
+ bool is_media, uint32 hash);
void on_network(bool network_flag, uint32 network_generation);
void on_online(bool online_flag);
+ void on_logging_out(bool is_logging_out);
- void client_wakeup(size_t hash);
+ static void update_mtproto_header(const Proxy &proxy);
+
+ void client_wakeup(uint32 hash);
void client_loop(ClientInfo &client);
- struct ConnectionData {
- SocketFd socket_fd;
- StateManager::ConnectionToken connection_token;
- std::unique_ptr<detail::StatsCallback> stats_callback;
- };
- void client_create_raw_connection(Result<ConnectionData> r_connection_data, bool check_mode, bool use_http,
- size_t hash, string debug_str, uint32 network_generation);
- void client_add_connection(size_t hash, Result<std::unique_ptr<mtproto::RawConnection>> r_raw_connection,
- bool check_flag);
+ void client_create_raw_connection(Result<ConnectionData> r_connection_data, bool check_mode,
+ mtproto::TransportType transport_type, uint32 hash, string debug_str,
+ uint32 network_generation);
+ void client_add_connection(uint32 hash, Result<unique_ptr<mtproto::RawConnection>> r_raw_connection, bool check_flag,
+ uint64 auth_data_generation, int64 session_id);
void client_set_timeout_at(ClientInfo &client, double wakeup_at);
void on_proxy_resolved(Result<IPAddress> ip_address, bool dummy);
- static DcOptions get_default_dc_options(bool is_test);
+ struct FindConnectionExtra {
+ DcOptionsSet::Stat *stat{nullptr};
+ mtproto::TransportType transport_type;
+ string debug_str;
+ IPAddress ip_address;
+ IPAddress mtproto_ip_address;
+ bool check_mode{false};
+ };
+
+ static Result<mtproto::TransportType> get_transport_type(const Proxy &proxy,
+ const DcOptionsSet::ConnectionInfo &info);
+
+ Result<SocketFd> find_connection(const Proxy &proxy, const IPAddress &proxy_ip_address, DcId dc_id,
+ bool allow_media_only, FindConnectionExtra &extra);
+
+ ActorId<GetHostByNameActor> get_dns_resolver();
+
+ void ping_proxy_resolved(int32 proxy_id, IPAddress ip_address, Promise<double> promise);
+
+ void ping_proxy_buffered_socket_fd(IPAddress ip_address, BufferedFd<SocketFd> buffered_socket_fd,
+ mtproto::TransportType transport_type, string debug_str, Promise<double> promise);
+
+ void on_ping_main_dc_result(uint64 token, Result<double> result);
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.cpp
index dd3776a449..23db9130e7 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.cpp
@@ -1,23 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/net/DcAuthManager.h"
-#include "td/actor/actor.h"
-
-#include "td/telegram/ConfigShared.h"
#include "td/telegram/Global.h"
#include "td/telegram/net/AuthDataShared.h"
#include "td/telegram/net/NetQuery.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UniqueId.h"
-#include "td/telegram/telegram_api.h"
+#include "td/actor/actor.h"
-#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
@@ -26,25 +24,33 @@
namespace td {
+int VERBOSITY_NAME(dc) = VERBOSITY_NAME(DEBUG) + 2;
+
DcAuthManager::DcAuthManager(ActorShared<> parent) {
parent_ = std::move(parent);
auto s_main_dc_id = G()->td_db()->get_binlog_pmc()->get("main_dc_id");
if (!s_main_dc_id.empty()) {
- main_dc_id_ = DcId::internal(to_integer<int32>(s_main_dc_id));
+ auto main_dc_id = to_integer<int32>(s_main_dc_id);
+ if (DcId::is_valid(main_dc_id)) {
+ main_dc_id_ = DcId::internal(main_dc_id);
+ VLOG(dc) << "Init main DcId to " << main_dc_id_;
+ } else {
+ LOG(ERROR) << "Receive invalid main DcId " << main_dc_id;
+ }
}
}
void DcAuthManager::add_dc(std::shared_ptr<AuthDataShared> auth_data) {
VLOG(dc) << "Register " << auth_data->dc_id();
- class Listener : public AuthDataShared::Listener {
+ class Listener final : public AuthDataShared::Listener {
public:
explicit Listener(ActorShared<DcAuthManager> dc_manager) : dc_manager_(std::move(dc_manager)) {
}
- bool notify() override {
+ bool notify() final {
if (!dc_manager_.is_alive()) {
return false;
}
- send_closure(dc_manager_, &DcAuthManager::update_auth_state);
+ send_closure(dc_manager_, &DcAuthManager::update_auth_key_state);
return true;
}
@@ -56,19 +62,20 @@ void DcAuthManager::add_dc(std::shared_ptr<AuthDataShared> auth_data) {
info.dc_id = auth_data->dc_id();
CHECK(info.dc_id.is_exact());
info.shared_auth_data = std::move(auth_data);
- auto state_was_auth = info.shared_auth_data->get_auth_state();
- info.auth_state = state_was_auth.first;
- was_auth_ |= state_was_auth.second;
+ info.auth_key_state = info.shared_auth_data->get_auth_key_state();
+ VLOG(dc) << "Add " << info.dc_id << " with auth key state " << info.auth_key_state;
if (!main_dc_id_.is_exact()) {
main_dc_id_ = info.dc_id;
+ VLOG(dc) << "Set main DcId to " << main_dc_id_;
}
- info.shared_auth_data->add_auth_key_listener(std::make_unique<Listener>(actor_shared(this, info.dc_id.get_raw_id())));
+ info.shared_auth_data->add_auth_key_listener(make_unique<Listener>(actor_shared(this, info.dc_id.get_raw_id())));
dcs_.emplace_back(std::move(info));
loop();
}
void DcAuthManager::update_main_dc(DcId new_main_dc_id) {
main_dc_id_ = new_main_dc_id;
+ VLOG(dc) << "Update main DcId to " << main_dc_id_;
loop();
}
@@ -85,20 +92,17 @@ DcAuthManager::DcInfo *DcAuthManager::find_dc(int32 dc_id) {
return &*it;
}
-void DcAuthManager::update_auth_state() {
- int32 dc_id = narrow_cast<int32>(get_link_token());
+void DcAuthManager::update_auth_key_state() {
+ auto dc_id = narrow_cast<int32>(get_link_token());
auto &dc = get_dc(dc_id);
- auto state_was_auth = dc.shared_auth_data->get_auth_state();
- VLOG(dc) << "Update dc auth state " << tag("dc_id", dc_id) << tag("old_auth_state", dc.auth_state)
- << tag("new_auth_state", state_was_auth.first);
- dc.auth_state = state_was_auth.first;
- was_auth_ |= state_was_auth.second;
+ dc.auth_key_state = dc.shared_auth_data->get_auth_key_state();
+ VLOG(dc) << "Update " << dc_id << " auth key state from " << dc.auth_key_state << " to " << dc.auth_key_state;
loop();
}
void DcAuthManager::on_result(NetQueryPtr result) {
- int32 dc_id = narrow_cast<int32>(get_link_token());
+ auto dc_id = narrow_cast<int32>(get_link_token());
auto &dc = get_dc(dc_id);
CHECK(dc.wait_id == result->id());
dc.wait_id = std::numeric_limits<decltype(dc.wait_id)>::max();
@@ -109,14 +113,15 @@ void DcAuthManager::on_result(NetQueryPtr result) {
dc.state = DcInfo::State::Export;
break;
}
- auto result_auth_exported = fetch_result<telegram_api::auth_exportAuthorization>(result->ok());
- if (result_auth_exported.is_error()) {
- LOG(WARNING) << "Failed to parse result to auth_exportAuthorization: " << result_auth_exported.error();
+ auto r_result_auth_exported = fetch_result<telegram_api::auth_exportAuthorization>(result->ok());
+ if (r_result_auth_exported.is_error()) {
+ LOG(WARNING) << "Failed to parse result to auth_exportAuthorization: " << r_result_auth_exported.error();
dc.state = DcInfo::State::Export;
break;
}
- dc.export_id = result_auth_exported.ok()->id_;
- dc.export_bytes = std::move(result_auth_exported.ok()->bytes_);
+ auto result_auth_exported = r_result_auth_exported.move_as_ok();
+ dc.export_id = result_auth_exported->id_;
+ dc.export_bytes = std::move(result_auth_exported->bytes_);
break;
}
case DcInfo::State::BeforeOk: {
@@ -142,25 +147,28 @@ void DcAuthManager::on_result(NetQueryPtr result) {
}
void DcAuthManager::dc_loop(DcInfo &dc) {
- VLOG(dc) << "dc_loop " << dc.dc_id << " " << dc.auth_state;
- if (dc.auth_state == AuthState::OK) {
+ VLOG(dc) << "In dc_loop: " << dc.dc_id << " " << dc.auth_key_state;
+ if (dc.auth_key_state == AuthKeyState::OK) {
return;
}
+ if (dc.state == DcInfo::State::Ok) {
+ LOG(WARNING) << "Lost key in " << dc.dc_id << ", restart dc_loop";
+ dc.state = DcInfo::State::Waiting;
+ }
CHECK(dc.shared_auth_data);
switch (dc.state) {
case DcInfo::State::Waiting: {
// wait for timeout
- // break;
+ // break;
}
case DcInfo::State::Export: {
// send auth.exportAuthorization to auth_dc
VLOG(dc) << "Send exportAuthorization to " << dc.dc_id;
auto id = UniqueId::next();
- G()->net_query_dispatcher().dispatch_with_callback(
- G()->net_query_creator().create(
- id, create_storer(telegram_api::auth_exportAuthorization(dc.dc_id.get_raw_id())), DcId::main(),
- NetQuery::Type::Common, NetQuery::AuthFlag::On, NetQuery::GzipFlag::On, 60 * 60 * 24),
- actor_shared(this, dc.dc_id.get_raw_id()));
+ auto query = G()->net_query_creator().create(id, telegram_api::auth_exportAuthorization(dc.dc_id.get_raw_id()),
+ {}, DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::On);
+ query->total_timeout_limit_ = 60 * 60 * 24;
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, dc.dc_id.get_raw_id()));
dc.wait_id = id;
dc.export_id = -1;
dc.state = DcInfo::State::Import;
@@ -173,21 +181,41 @@ void DcAuthManager::dc_loop(DcInfo &dc) {
}
uint64 id = UniqueId::next();
VLOG(dc) << "Send importAuthorization to " << dc.dc_id;
- G()->net_query_dispatcher().dispatch_with_callback(
- G()->net_query_creator().create(
- id, create_storer(telegram_api::auth_importAuthorization(dc.export_id, std::move(dc.export_bytes))),
- dc.dc_id, NetQuery::Type::Common, NetQuery::AuthFlag::Off, NetQuery::GzipFlag::On, 60 * 60 * 24),
- actor_shared(this, dc.dc_id.get_raw_id()));
+ auto query = G()->net_query_creator().create(
+ id, telegram_api::auth_importAuthorization(dc.export_id, std::move(dc.export_bytes)), {}, dc.dc_id,
+ NetQuery::Type::Common, NetQuery::AuthFlag::Off);
+ query->total_timeout_limit_ = 60 * 60 * 24;
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, dc.dc_id.get_raw_id()));
dc.wait_id = id;
dc.state = DcInfo::State::BeforeOk;
break;
}
- case DcInfo::State::BeforeOk: {
+ case DcInfo::State::BeforeOk:
break;
- }
- case DcInfo::State::Ok: {
+ case DcInfo::State::Ok:
break;
- }
+ }
+}
+
+void DcAuthManager::destroy(Promise<> promise) {
+ destroy_promise_ = std::move(promise);
+ loop();
+}
+
+void DcAuthManager::destroy_loop() {
+ if (!destroy_promise_) {
+ return;
+ }
+ bool is_ready{true};
+ for (auto &dc : dcs_) {
+ is_ready &= dc.auth_key_state == AuthKeyState::Empty;
+ }
+
+ if (is_ready) {
+ VLOG(dc) << "Destroy auth keys loop is ready, all keys are destroyed";
+ destroy_promise_.set_value(Unit());
+ } else {
+ VLOG(dc) << "DC is not ready for destroying auth key";
}
}
@@ -196,22 +224,28 @@ void DcAuthManager::loop() {
VLOG(dc) << "Skip loop because close_flag";
return;
}
+ destroy_loop();
if (!main_dc_id_.is_exact()) {
VLOG(dc) << "Skip loop because main_dc_id is unknown";
return;
}
auto main_dc = find_dc(main_dc_id_.get_raw_id());
- if (!main_dc || main_dc->auth_state != AuthState::OK) {
- if (was_auth_) {
- G()->shared_config().set_option_boolean("auth", false);
+ if (!main_dc || main_dc->auth_key_state != AuthKeyState::OK) {
+ if (main_dc && need_check_authorization_is_ok_) {
+ G()->log_out("Authorization check failed in DcAuthManager");
}
- VLOG(dc) << "Skip loop because auth state of main dc " << main_dc_id_.get_raw_id() << " is "
- << (main_dc != nullptr ? (PSTRING() << main_dc->auth_state) : "unknown");
-
+ VLOG(dc) << "Skip loop, because main DC is " << main_dc_id_ << ", main auth key state is "
+ << (main_dc != nullptr ? main_dc->auth_key_state : AuthKeyState::Empty);
return;
}
+ need_check_authorization_is_ok_ = false;
for (auto &dc : dcs_) {
dc_loop(dc);
}
}
+
+void DcAuthManager::check_authorization_is_ok() {
+ need_check_authorization_is_ok_ = true;
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.h b/protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.h
index a8710f6102..2626db9c3b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/DcAuthManager.h
@@ -1,58 +1,69 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/Global.h"
#include "td/telegram/net/AuthDataShared.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQuery.h"
#include "td/actor/actor.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
#include <memory>
namespace td {
-class DcAuthManager : public NetQueryCallback {
+
+extern int VERBOSITY_NAME(dc);
+
+class DcAuthManager final : public NetQueryCallback {
public:
explicit DcAuthManager(ActorShared<> parent);
void add_dc(std::shared_ptr<AuthDataShared> auth_data);
void update_main_dc(DcId new_main_dc_id);
+ void destroy(Promise<> promise);
+
+ void check_authorization_is_ok();
private:
struct DcInfo {
DcId dc_id;
std::shared_ptr<AuthDataShared> shared_auth_data;
- AuthState auth_state;
+ AuthKeyState auth_key_state;
- enum class State { Waiting, Export, Import, BeforeOk, Ok };
+ enum class State : int32 { Waiting, Export, Import, BeforeOk, Ok };
State state = State::Waiting;
- uint64 wait_id;
- int32 export_id;
+ uint64 wait_id = 0;
+ int64 export_id = 0;
BufferSlice export_bytes;
};
ActorShared<> parent_;
std::vector<DcInfo> dcs_;
- bool was_auth_ = false;
DcId main_dc_id_;
- bool close_flag_ = false;
+ bool need_check_authorization_is_ok_{false};
+ bool close_flag_{false};
+ Promise<> destroy_promise_;
DcInfo &get_dc(int32 dc_id);
DcInfo *find_dc(int32 dc_id);
- void update_auth_state();
+ void update_auth_key_state();
- void on_result(NetQueryPtr result) override;
+ void on_result(NetQueryPtr result) final;
void dc_loop(DcInfo &dc);
- void loop() override;
+ void destroy_loop();
+ void loop() final;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/DcId.h b/protocols/Telegram/tdlib/td/td/telegram/net/DcId.h
index f258d34014..3d6e134633 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/DcId.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/DcId.h
@@ -1,18 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/logging.h"
+
+#include "td/utils/common.h"
#include "td/utils/StringBuilder.h"
+#include <tuple>
+
namespace td {
+
class DcId {
public:
DcId() = default;
- DcId(const DcId &other) = default;
static bool is_valid(int32 dc_id) {
return 1 <= dc_id && dc_id <= 1000;
@@ -37,6 +40,13 @@ class DcId {
static DcId from_value(int32 value) {
return DcId{value, false};
}
+ static DcId create(int32 dc_id_value) {
+ if (DcId::is_valid(dc_id_value)) {
+ return DcId(dc_id_value, false);
+ } else {
+ return DcId::invalid();
+ }
+ }
bool is_empty() const {
return !is_valid();
@@ -64,14 +74,14 @@ class DcId {
return dc_id_ == other.dc_id_ && is_external_ == other.is_external_;
}
bool operator<(DcId other) const {
- return dc_id_ < other.dc_id_;
+ return std::tie(dc_id_, is_external_) < std::tie(other.dc_id_, other.is_external_);
}
bool operator!=(DcId other) const {
return !(*this == other);
}
private:
- enum { Empty = 0, MainDc = -1, Invalid = -2 };
+ enum : int32 { Empty = 0, MainDc = -1, Invalid = -2 };
int32 dc_id_{Empty};
bool is_external_{false};
@@ -85,8 +95,12 @@ class DcId {
inline StringBuilder &operator<<(StringBuilder &sb, const DcId &dc_id) {
sb << "DcId{";
- if (dc_id.is_empty()) {
+ if (dc_id == DcId::invalid()) {
+ sb << "invalid";
+ } else if (dc_id == DcId()) {
sb << "empty";
+ } else if (dc_id.is_empty()) {
+ sb << "is_empty";
} else if (dc_id.is_main()) {
sb << "main";
} else {
@@ -99,4 +113,4 @@ inline StringBuilder &operator<<(StringBuilder &sb, const DcId &dc_id) {
return sb;
}
-}; // namespace td
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/DcOptions.h b/protocols/Telegram/tdlib/td/td/telegram/net/DcOptions.h
index 9ce5aeb40f..66be1e9c4a 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/DcOptions.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/DcOptions.h
@@ -1,14 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/net/DcId.h"
#include "td/telegram/telegram_api.h"
-#include "td/telegram/net/DcId.h"
+#include "td/mtproto/ProxySecret.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
@@ -19,129 +20,175 @@
#include "td/utils/tl_helpers.h"
namespace td {
+
class DcOption {
// do not forget to update PrintFlags
- enum Flags { IPv6 = 1, MediaOnly = 2, ObfuscatedTcpOnly = 4, Cdn = 8, Static = 16 };
+ enum Flags : int32 { IPv6 = 1, MediaOnly = 2, ObfuscatedTcpOnly = 4, Cdn = 8, Static = 16, HasSecret = 32 };
- int32 flags = 0;
- DcId dc_id;
- IPAddress ip_address;
+ int32 flags_ = 0;
+ DcId dc_id_;
+ IPAddress ip_address_;
+ mtproto::ProxySecret secret_;
struct PrintFlags {
int32 flags;
};
- bool is_ipv6() const {
- return (flags & Flags::IPv6) != 0;
- }
-
public:
DcOption() = default;
DcOption(DcId dc_id, const IPAddress &ip_address)
- : flags(ip_address.is_ipv4() ? 0 : IPv6), dc_id(dc_id), ip_address(ip_address) {
+ : flags_(ip_address.is_ipv4() ? 0 : IPv6), dc_id_(dc_id), ip_address_(ip_address) {
}
explicit DcOption(const telegram_api::dcOption &option) {
auto ip = option.ip_address_;
auto port = option.port_;
- flags = 0;
+ flags_ = 0;
if (!DcId::is_valid(option.id_)) {
- dc_id = DcId::invalid();
+ dc_id_ = DcId::invalid();
return;
}
if (option.cdn_) {
- dc_id = DcId::external(option.id_);
- flags |= Flags::Cdn;
+ dc_id_ = DcId::external(option.id_);
+ flags_ |= Flags::Cdn;
} else {
- dc_id = DcId::internal(option.id_);
+ dc_id_ = DcId::internal(option.id_);
}
if (option.ipv6_) {
- flags |= Flags::IPv6;
+ flags_ |= Flags::IPv6;
}
if (option.media_only_) {
- flags |= Flags::MediaOnly;
+ flags_ |= Flags::MediaOnly;
}
if (option.tcpo_only_) {
- flags |= Flags::ObfuscatedTcpOnly;
+ flags_ |= Flags::ObfuscatedTcpOnly;
}
if (option.static_) {
- flags |= Flags::Static;
+ flags_ |= Flags::Static;
+ }
+ if (!option.secret_.empty()) {
+ flags_ |= Flags::HasSecret;
+ auto r_secret = mtproto::ProxySecret::from_binary(option.secret_.as_slice());
+ if (r_secret.is_error()) {
+ return;
+ }
+ secret_ = r_secret.move_as_ok();
}
init_ip_address(ip, port);
}
- DcOption(DcId new_dc_id, const telegram_api::ipPort &ip_port) {
- dc_id = new_dc_id;
- init_ip_address(IPAddress::ipv4_to_str(ip_port.ipv4_), ip_port.port_);
+ DcOption(DcId new_dc_id, const telegram_api::IpPort &ip_port_ref) {
+ switch (ip_port_ref.get_id()) {
+ case telegram_api::ipPort::ID: {
+ auto &ip_port = static_cast<const telegram_api::ipPort &>(ip_port_ref);
+ init_ip_address(IPAddress::ipv4_to_str(static_cast<uint32>(ip_port.ipv4_)), ip_port.port_);
+ break;
+ }
+ case telegram_api::ipPortSecret::ID: {
+ auto &ip_port = static_cast<const telegram_api::ipPortSecret &>(ip_port_ref);
+ auto r_secret = mtproto::ProxySecret::from_binary(ip_port.secret_.as_slice());
+ if (r_secret.is_error()) {
+ return;
+ }
+ flags_ |= Flags::HasSecret;
+ secret_ = r_secret.move_as_ok();
+ init_ip_address(IPAddress::ipv4_to_str(static_cast<uint32>(ip_port.ipv4_)), ip_port.port_);
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ flags_ |= Flags::ObfuscatedTcpOnly;
+ dc_id_ = new_dc_id;
}
DcId get_dc_id() const {
- return dc_id;
+ return dc_id_;
}
const IPAddress &get_ip_address() const {
- return ip_address;
+ return ip_address_;
+ }
+
+ bool is_ipv6() const {
+ return (flags_ & Flags::IPv6) != 0;
}
bool is_media_only() const {
- return (flags & Flags::MediaOnly) != 0;
+ return (flags_ & Flags::MediaOnly) != 0;
}
bool is_obfuscated_tcp_only() const {
- return (flags & Flags::ObfuscatedTcpOnly) != 0;
+ return (flags_ & Flags::ObfuscatedTcpOnly) != 0;
}
bool is_static() const {
- return (flags & Flags::Static) != 0;
+ return (flags_ & Flags::Static) != 0;
}
bool is_valid() const {
- return ip_address.is_valid() && dc_id.is_exact();
+ return ip_address_.is_valid() && dc_id_.is_exact();
+ }
+
+ const mtproto::ProxySecret &get_secret() const {
+ return secret_;
}
template <class StorerT>
void store(StorerT &storer) const {
- storer.store_int(flags);
- storer.store_int(dc_id.get_raw_id());
- CHECK(ip_address.is_valid());
- storer.store_string(ip_address.get_ip_str());
- storer.store_int(ip_address.get_port());
+ storer.store_int(flags_);
+ storer.store_int(dc_id_.get_raw_id());
+ CHECK(ip_address_.is_valid());
+ storer.store_string(ip_address_.get_ip_str());
+ storer.store_int(ip_address_.get_port());
+ if ((flags_ & Flags::HasSecret) != 0) {
+ td::store(secret_.get_raw_secret(), storer);
+ }
}
template <class ParserT>
void parse(ParserT &parser) {
- flags = parser.fetch_int();
+ flags_ = parser.fetch_int();
auto raw_dc_id = parser.fetch_int();
- if (flags & Flags::Cdn) {
- dc_id = DcId::external(raw_dc_id);
+ if (!DcId::is_valid(raw_dc_id)) {
+ LOG(ERROR) << "Have invalid DC ID " << raw_dc_id;
+ dc_id_ = DcId::invalid();
} else {
- dc_id = DcId::internal(raw_dc_id);
+ if ((flags_ & Flags::Cdn) != 0) {
+ dc_id_ = DcId::external(raw_dc_id);
+ } else {
+ dc_id_ = DcId::internal(raw_dc_id);
+ }
}
auto ip = parser.template fetch_string<std::string>();
auto port = parser.fetch_int();
init_ip_address(ip, port);
+ if ((flags_ & Flags::HasSecret) != 0) {
+ secret_ = mtproto::ProxySecret::from_raw(parser.template fetch_string<Slice>());
+ }
}
friend bool operator==(const DcOption &lhs, const DcOption &rhs);
- friend StringBuilder &operator<<(StringBuilder &sb, const DcOption::PrintFlags &flags);
+ friend StringBuilder &operator<<(StringBuilder &sb, const PrintFlags &flags);
friend StringBuilder &operator<<(StringBuilder &sb, const DcOption &dc_option);
private:
void init_ip_address(CSlice ip, int32 port) {
if (is_ipv6()) {
- ip_address.init_ipv6_port(ip, port).ignore();
+ ip_address_.init_ipv6_port(ip, port).ignore();
} else {
- ip_address.init_ipv4_port(ip, port).ignore();
+ ip_address_.init_ipv4_port(ip, port).ignore();
}
}
};
inline bool operator==(const DcOption &lhs, const DcOption &rhs) {
- return lhs.dc_id == rhs.dc_id && lhs.ip_address == rhs.ip_address && lhs.flags == rhs.flags;
+ return lhs.dc_id_ == rhs.dc_id_ && lhs.ip_address_ == rhs.ip_address_ && lhs.flags_ == rhs.flags_ &&
+ lhs.secret_ == rhs.secret_;
}
inline StringBuilder &operator<<(StringBuilder &sb, const DcOption::PrintFlags &flags) {
@@ -160,13 +207,17 @@ inline StringBuilder &operator<<(StringBuilder &sb, const DcOption::PrintFlags &
if ((flags.flags & DcOption::Flags::Static) != 0) {
sb << "(Static)";
}
+ if ((flags.flags & DcOption::Flags::HasSecret) != 0) {
+ sb << "(HasSecret)";
+ }
return sb;
}
inline StringBuilder &operator<<(StringBuilder &sb, const DcOption &dc_option) {
- return sb << tag("DcOption", format::concat(dc_option.dc_id, tag("ip", dc_option.ip_address.get_ip_str()),
- tag("port", dc_option.ip_address.get_port()),
- tag("flags", DcOption::PrintFlags{dc_option.flags})));
+ return sb << tag("DcOption", format::concat(dc_option.dc_id_, tag("ip", dc_option.ip_address_.get_ip_str()),
+ tag("port", dc_option.ip_address_.get_port()),
+ tag("secret_len", dc_option.get_secret().get_raw_secret().size()),
+ tag("flags", DcOption::PrintFlags{dc_option.flags_})));
}
class DcOptions {
@@ -180,15 +231,7 @@ class DcOptions {
}
}
}
- explicit DcOptions(const telegram_api::help_configSimple &config_simple) {
- auto dc_id = DcId::is_valid(config_simple.dc_id_) ? DcId::internal(config_simple.dc_id_) : DcId();
- for (auto &ip_port : config_simple.ip_port_list_) {
- DcOption option(dc_id, *ip_port);
- if (option.is_valid()) {
- dc_options.push_back(std::move(option));
- }
- }
- }
+
template <class StorerT>
void store(StorerT &storer) const {
::td::store(dc_options, storer);
@@ -201,7 +244,9 @@ class DcOptions {
std::vector<DcOption> dc_options;
};
+
inline StringBuilder &operator<<(StringBuilder &sb, const DcOptions &dc_options) {
return sb << "DcOptions" << format::as_array(dc_options.dc_options);
}
-}; // namespace td
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.cpp
index 4aefd3b7f7..939643ad95 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.cpp
@@ -1,13 +1,20 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/net/DcOptionsSet.h"
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/Global.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/algorithm.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
#include <algorithm>
#include <set>
@@ -43,10 +50,18 @@ DcOptions DcOptionsSet::get_dc_options() const {
return result;
}
-Result<DcOptionsSet::ConnectionInfo> DcOptionsSet::find_connection(DcId dc_id, bool allow_media_only, bool use_static) {
+vector<DcOptionsSet::ConnectionInfo> DcOptionsSet::find_all_connections(DcId dc_id, bool allow_media_only,
+ bool use_static, bool prefer_ipv6,
+ bool only_http) {
+ LOG(DEBUG) << "Find all " << (allow_media_only ? "media " : "") << "connections in " << dc_id
+ << ". use_static = " << use_static << ", prefer_ipv6 = " << prefer_ipv6 << ", only_http = " << only_http;
std::vector<ConnectionInfo> options;
std::vector<ConnectionInfo> static_options;
+ if (prefer_ipv6) {
+ use_static = false;
+ }
+
for (auto &option_info : options_) {
auto &option = option_info->option;
if (option.get_dc_id() != dc_id) {
@@ -67,46 +82,69 @@ Result<DcOptionsSet::ConnectionInfo> DcOptionsSet::find_connection(DcId dc_id, b
OptionStat *option_stat = get_option_stat(option_info.get());
- info.use_http = false;
- info.stat = &option_stat->tcp_stat;
- if (option.is_static()) {
- static_options.push_back(info);
- } else {
- options.push_back(info);
+ if (!only_http) {
+ info.use_http = false;
+ info.stat = &option_stat->tcp_stat;
+ if (option.is_static()) {
+ static_options.push_back(info);
+ } else {
+ options.push_back(info);
+ }
}
- if (!option.is_obfuscated_tcp_only() && !option.is_static() && false) { // TODO fix HTTP-mode and enable it
- info.use_http = true;
- info.stat = &option_stat->http_stat;
- options.push_back(info);
+ if (only_http) {
+ if (!option.is_obfuscated_tcp_only() && !option.is_static() && (prefer_ipv6 || !option.is_ipv6())) {
+ info.use_http = true;
+ info.stat = &option_stat->http_stat;
+ options.push_back(info);
+ }
}
}
if (use_static) {
if (!static_options.empty()) {
options = std::move(static_options);
+ } else {
+ bool have_ipv4 = std::any_of(options.begin(), options.end(), [](auto &v) { return !v.option->is_ipv6(); });
+ if (have_ipv4) {
+ td::remove_if(options, [](auto &v) { return v.option->is_ipv6(); });
+ }
}
} else {
if (options.empty()) {
options = std::move(static_options);
}
}
+
+ if (prefer_ipv6) {
+ bool have_ipv6 = std::any_of(options.begin(), options.end(), [](auto &v) { return v.option->is_ipv6(); });
+ if (have_ipv6) {
+ td::remove_if(options, [](auto &v) { return !v.option->is_ipv6(); });
+ }
+ }
+
bool have_media_only = std::any_of(options.begin(), options.end(), [](auto &v) { return v.option->is_media_only(); });
if (have_media_only) {
- options.erase(std::remove_if(options.begin(), options.end(), [](auto &v) { return !v.option->is_media_only(); }),
- options.end());
+ td::remove_if(options, [](auto &v) { return !v.option->is_media_only(); });
}
+ return options;
+}
+
+Result<DcOptionsSet::ConnectionInfo> DcOptionsSet::find_connection(DcId dc_id, bool allow_media_only, bool use_static,
+ bool prefer_ipv6, bool only_http) {
+ auto options = find_all_connections(dc_id, allow_media_only, use_static, prefer_ipv6, only_http);
+
if (options.empty()) {
+ send_closure(G()->config_manager(), &ConfigManager::lazy_request_config);
return Status::Error(PSLICE() << "No such connection: " << tag("dc_id", dc_id)
- << tag("allow_media_only", allow_media_only) << tag("use_static", use_static));
+ << tag("allow_media_only", allow_media_only) << tag("use_static", use_static)
+ << tag("prefer_ipv6", prefer_ipv6));
}
- auto last_error_at = std::min_element(options.begin(), options.end(),
- [](const auto &a_option, const auto &b_option) {
- return a_option.stat->error_at > b_option.stat->error_at;
- })
- ->stat->error_at;
+ auto last_error_at = std::min_element(options.begin(), options.end(), [](const auto &a_option, const auto &b_option) {
+ return a_option.stat->error_at > b_option.stat->error_at;
+ })->stat->error_at;
auto result = *std::min_element(options.begin(), options.end(), [](const auto &a_option, const auto &b_option) {
auto &a = *a_option.stat;
@@ -116,12 +154,12 @@ Result<DcOptionsSet::ConnectionInfo> DcOptionsSet::find_connection(DcId dc_id, b
if (a_state != b_state) {
return a_state < b_state;
}
- if (a_state == Stat::Ok) {
+ if (a_state == Stat::State::Ok) {
if (a_option.order == b_option.order) {
return a_option.use_http < b_option.use_http;
}
return a_option.order < b_option.order;
- } else if (a_state == Stat::Error) {
+ } else if (a_state == Stat::State::Error) {
return a.error_at < b.error_at;
}
return a_option.order < b_option.order;
@@ -131,11 +169,12 @@ Result<DcOptionsSet::ConnectionInfo> DcOptionsSet::find_connection(DcId dc_id, b
}
void DcOptionsSet::reset() {
+ options_.clear();
ordered_options_.clear();
}
DcOptionsSet::DcOptionInfo *DcOptionsSet::register_dc_option(DcOption &&option) {
- auto info = std::make_unique<DcOptionInfo>(std::move(option), options_.size());
+ auto info = make_unique<DcOptionInfo>(std::move(option), options_.size());
init_option_stat(info.get());
auto result = info.get();
options_.push_back(std::move(info));
@@ -144,9 +183,9 @@ DcOptionsSet::DcOptionInfo *DcOptionsSet::register_dc_option(DcOption &&option)
void DcOptionsSet::init_option_stat(DcOptionInfo *option_info) {
const auto &ip_address = option_info->option.get_ip_address();
- auto it_ok = option_to_stat_id_.insert(std::make_pair(ip_address, 0));
+ auto it_ok = option_to_stat_id_.emplace(ip_address, 0);
if (it_ok.second) {
- it_ok.first->second = option_stats_.create(std::make_unique<OptionStat>());
+ it_ok.first->second = option_stats_.create(make_unique<OptionStat>());
}
option_info->stat_id = it_ok.first->second;
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.h b/protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.h
index e57e90036e..66a2dd4779 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/DcOptionsSet.h
@@ -1,11 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/DcOptions.h"
#include "td/utils/Container.h"
@@ -16,6 +17,7 @@
#include <map>
namespace td {
+
class DcOptionsSet {
public:
void add_dc_options(DcOptions dc_options);
@@ -26,7 +28,7 @@ class DcOptionsSet {
double ok_at{-1000};
double error_at{-1001};
double check_at{-1002};
- enum State { Ok, Error, Checking };
+ enum class State : int32 { Ok, Error, Checking };
void on_ok() {
ok_at = Time::now_cached();
@@ -38,16 +40,16 @@ class DcOptionsSet {
check_at = Time::now_cached();
}
bool is_ok() const {
- return state() == Ok;
+ return state() == State::Ok;
}
State state() const {
if (ok_at > error_at && ok_at > check_at) {
- return Ok;
+ return State::Ok;
}
if (check_at > ok_at && check_at > error_at) {
- return Checking;
+ return State::Checking;
}
- return Error;
+ return State::Error;
}
};
@@ -59,11 +61,15 @@ class DcOptionsSet {
Stat *stat{nullptr};
};
- Result<ConnectionInfo> find_connection(DcId dc_id, bool allow_media_only, bool use_static);
+ vector<ConnectionInfo> find_all_connections(DcId dc_id, bool allow_media_only, bool use_static, bool prefer_ipv6,
+ bool only_http);
+
+ Result<ConnectionInfo> find_connection(DcId dc_id, bool allow_media_only, bool use_static, bool prefer_ipv6,
+ bool only_http);
void reset();
private:
- enum class State { Error, Ok, Checking };
+ enum class State : int32 { Error, Ok, Checking };
struct OptionStat {
Stat tcp_stat;
@@ -72,7 +78,7 @@ class DcOptionsSet {
struct DcOptionInfo {
DcOption option;
- int64 stat_id;
+ int64 stat_id = -1;
size_t pos;
size_t order = 0;
@@ -93,13 +99,14 @@ class DcOptionsSet {
}
};
- std::vector<std::unique_ptr<DcOptionInfo>> options_;
+ std::vector<unique_ptr<DcOptionInfo>> options_;
std::vector<DcOptionId> ordered_options_;
std::map<IPAddress, int64> option_to_stat_id_;
- Container<std::unique_ptr<OptionStat>> option_stats_;
+ Container<unique_ptr<OptionStat>> option_stats_;
DcOptionInfo *register_dc_option(DcOption &&option);
void init_option_stat(DcOptionInfo *option_info);
OptionStat *get_option_stat(const DcOptionInfo *option_info);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.cpp
index 3c41e2d1d9..30128f4ee8 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.cpp
@@ -1,16 +1,24 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/net/MtprotoHeader.h"
+#include "td/telegram/JsonValue.h"
+#include "td/telegram/LanguagePackManager.h"
+#include "td/telegram/telegram_api.h"
+#include "td/telegram/Version.h"
+
+#include "td/tl/tl_object_store.h"
+
#include "td/utils/tl_helpers.h"
namespace td {
namespace {
+
class HeaderStorer {
public:
HeaderStorer(const MtprotoHeader::Options &options, bool is_anonymous)
@@ -18,15 +26,25 @@ class HeaderStorer {
}
template <class StorerT>
void store(StorerT &storer) const {
- constexpr int32 LAYER = 76;
-
using td::store;
// invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
store(static_cast<int32>(0xda9b0d0d), storer);
- store(LAYER, storer);
- // initConnection#c7481da6 {X:Type} api_id:int device_model:string system_version:string app_version:string
- // system_lang_code:string lang_pack:string lang_code:string query:!X = X;
- store(static_cast<int32>(0xc7481da6), storer);
+ store(MTPROTO_LAYER, storer);
+ // initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string
+ // system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
+ store(static_cast<int32>(0x785188b8), storer);
+ int32 flags = 0;
+ bool have_proxy = !is_anonymous && options.proxy.type() == Proxy::Type::Mtproto;
+ if (have_proxy) {
+ flags |= 1 << 0;
+ }
+ if (!is_anonymous) {
+ flags |= 1 << 1;
+ }
+ if (options.is_emulator) {
+ flags |= 1 << 10;
+ }
+ store(flags, storer);
store(options.api_id, storer);
if (is_anonymous) {
store(Slice("n/a"), storer);
@@ -37,14 +55,56 @@ class HeaderStorer {
}
store(options.application_version, storer);
store(options.system_language_code, storer);
- store(string(), storer);
- store(string(), storer);
+ if (is_anonymous || options.language_pack.empty() ||
+ LanguagePackManager::is_custom_language_code(options.language_code)) {
+ store(Slice(), storer);
+ store(Slice(), storer);
+ } else {
+ store(options.language_pack, storer);
+ if (options.language_code.empty()) {
+ store(Slice("en"), storer);
+ } else {
+ store(options.language_code, storer);
+ }
+ }
+ if (have_proxy) {
+ // inputClientProxy#75588b3f address:string port:int = InputClientProxy;
+ store(static_cast<int32>(0x75588b3f), storer);
+ store(Slice(options.proxy.server()), storer);
+ store(options.proxy.port(), storer);
+ }
+ if (!is_anonymous) {
+ telegram_api::object_ptr<telegram_api::JSONValue> json_value;
+ if (options.parameters.empty()) {
+ json_value = make_tl_object<telegram_api::jsonObject>(vector<tl_object_ptr<telegram_api::jsonObjectValue>>());
+ } else {
+ auto parameters_copy = options.parameters;
+ json_value = get_input_json_value(parameters_copy).move_as_ok();
+ }
+ CHECK(json_value != nullptr);
+ if (json_value->get_id() == telegram_api::jsonObject::ID) {
+ auto &values = static_cast<telegram_api::jsonObject *>(json_value.get())->value_;
+ bool has_tz_offset = false;
+ for (auto &value : values) {
+ if (value->key_ == "tz_offset") {
+ has_tz_offset = true;
+ value->value_ = make_tl_object<telegram_api::jsonNumber>(options.tz_offset);
+ }
+ }
+ if (!has_tz_offset) {
+ values.push_back(make_tl_object<telegram_api::jsonObjectValue>(
+ "tz_offset", make_tl_object<telegram_api::jsonNumber>(options.tz_offset)));
+ }
+ }
+ TlStoreBoxedUnknown<TlStoreObject>::store(json_value, storer);
+ }
}
private:
const MtprotoHeader::Options &options;
bool is_anonymous;
};
+
} // namespace
string MtprotoHeader::gen_header(const MtprotoHeader::Options &options, bool is_anonymous) {
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.h b/protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.h
index e7f4a22bdb..1e5a85c103 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/MtprotoHeader.h
@@ -1,27 +1,91 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/net/Proxy.h"
+
#include "td/utils/common.h"
#include "td/utils/Slice.h"
namespace td {
+
class MtprotoHeader {
public:
struct Options {
- int32 api_id;
+ int32 api_id = -1;
string system_language_code;
string device_model;
string system_version;
string application_version;
+ string language_pack;
+ string language_code;
+ string parameters;
+ int32 tz_offset = 0;
+ bool is_emulator = false;
+ Proxy proxy;
};
- explicit MtprotoHeader(const Options &options)
- : default_header_(gen_header(options, false)), anonymous_header_(gen_header(options, true)) {
+ explicit MtprotoHeader(const Options &options) : options_(options) {
+ gen_headers();
+ }
+
+ void set_proxy(Proxy proxy) {
+ options_.proxy = std::move(proxy);
+ default_header_ = gen_header(options_, false);
+ }
+
+ bool set_parameters(string parameters) {
+ if (options_.parameters == parameters) {
+ return false;
+ }
+
+ options_.parameters = std::move(parameters);
+ default_header_ = gen_header(options_, false);
+ return true;
+ }
+
+ bool set_is_emulator(bool is_emulator) {
+ if (options_.is_emulator == is_emulator) {
+ return false;
+ }
+
+ options_.is_emulator = is_emulator;
+ default_header_ = gen_header(options_, false);
+ return true;
+ }
+
+ bool set_language_pack(string language_pack) {
+ if (options_.language_pack == language_pack) {
+ return false;
+ }
+
+ options_.language_pack = std::move(language_pack);
+ default_header_ = gen_header(options_, false);
+ return true;
+ }
+
+ bool set_language_code(string language_code) {
+ if (options_.language_code == language_code) {
+ return false;
+ }
+
+ options_.language_code = std::move(language_code);
+ default_header_ = gen_header(options_, false);
+ return true;
+ }
+
+ bool set_tz_offset(int32 tz_offset) {
+ if (options_.tz_offset == tz_offset) {
+ return false;
+ }
+
+ options_.tz_offset = tz_offset;
+ default_header_ = gen_header(options_, false);
+ return true;
}
Slice get_default_header() const {
@@ -31,10 +95,20 @@ class MtprotoHeader {
return anonymous_header_;
}
+ string get_system_language_code() const {
+ return options_.system_language_code;
+ }
+
private:
+ Options options_;
string default_header_;
string anonymous_header_;
+ void gen_headers() {
+ default_header_ = gen_header(options_, false);
+ anonymous_header_ = gen_header(options_, true);
+ }
+
static string gen_header(const Options &options, bool is_anonymous);
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetActor.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/NetActor.cpp
index 68c14a1a26..ce04d8160b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetActor.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetActor.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -12,7 +12,7 @@
namespace td {
-NetActor::NetActor() : td(static_cast<Td *>(G()->td().get_actor_unsafe())) {
+NetActor::NetActor() : td_(static_cast<Td *>(G()->td().get_actor_unsafe())) {
}
void NetActor::set_parent(ActorShared<> parent) {
@@ -22,9 +22,9 @@ void NetActor::set_parent(ActorShared<> parent) {
void NetActor::on_result(NetQueryPtr query) {
CHECK(query->is_ready());
if (query->is_ok()) {
- on_result(query->id(), query->move_as_ok());
+ on_result(query->move_as_ok());
} else {
- on_error(query->id(), query->move_as_error());
+ on_error(query->move_as_error());
}
on_result_finish();
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetActor.h b/protocols/Telegram/tdlib/td/td/telegram/net/NetActor.h
index 71972f6070..4208f16157 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetActor.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetActor.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -12,7 +12,6 @@
#include "td/utils/buffer.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include "td/utils/Status.h"
namespace td {
@@ -22,21 +21,27 @@ class Td;
class NetActor : public NetQueryCallback {
public:
NetActor();
+
void set_parent(ActorShared<> parent);
+
void on_result(NetQueryPtr query) override;
- virtual void on_result(uint64 id, BufferSlice packet) {
+
+ virtual void on_result(BufferSlice packet) {
UNREACHABLE();
}
- virtual void on_error(uint64 id, Status status) {
+
+ virtual void on_error(Status status) {
UNREACHABLE();
}
+
virtual void on_result_finish() {
}
protected:
+ Td *td_;
ActorShared<> parent_;
+
void send_query(NetQueryPtr query);
- Td *td;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.cpp
index 8cda0bccab..0161162878 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.cpp
@@ -1,18 +1,64 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/ChainId.h"
#include "td/telegram/Global.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/as.h"
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Time.h"
+
+#include <algorithm>
namespace td {
-ListNode net_query_list_;
-int32 NetQuery::get_my_id() {
- return G()->get_my_id();
+int VERBOSITY_NAME(net_query) = VERBOSITY_NAME(INFO);
+
+void NetQuery::debug(string state, bool may_be_lost) {
+ may_be_lost_ = may_be_lost;
+ VLOG(net_query) << *this << " " << tag("state", state);
+ {
+ auto guard = lock();
+ auto &data = get_data_unsafe();
+ data.state_ = std::move(state);
+ data.state_timestamp_ = Time::now();
+ data.state_change_count_++;
+ }
+}
+
+NetQuery::NetQuery(State state, uint64 id, BufferSlice &&query, BufferSlice &&answer, DcId dc_id, Type type,
+ AuthFlag auth_flag, GzipFlag gzip_flag, int32 tl_constructor, int32 total_timeout_limit,
+ NetQueryStats *stats, vector<ChainId> chain_ids)
+ : state_(state)
+ , type_(type)
+ , auth_flag_(auth_flag)
+ , gzip_flag_(gzip_flag)
+ , dc_id_(dc_id)
+ , status_()
+ , id_(id)
+ , query_(std::move(query))
+ , answer_(std::move(answer))
+ , tl_constructor_(tl_constructor)
+ , total_timeout_limit_(total_timeout_limit) {
+ CHECK(id_ != 0);
+ chain_ids_ = transform(chain_ids, [](ChainId chain_id) { return chain_id.get() == 0 ? 1 : chain_id.get(); });
+ td::unique(chain_ids_);
+
+ auto &data = get_data_unsafe();
+ data.my_id_ = G()->get_option_integer("my_id");
+ data.start_timestamp_ = data.state_timestamp_ = Time::now();
+ LOG(INFO) << *this;
+ if (stats) {
+ nq_counter_ = stats->register_query(this);
+ }
}
void NetQuery::on_net_write(size_t size) {
@@ -21,12 +67,14 @@ void NetQuery::on_net_write(size_t size) {
}
G()->get_net_stats_file_callbacks().at(file_type_)->on_write(size);
}
+
void NetQuery::on_net_read(size_t size) {
if (file_type_ == -1) {
return;
}
G()->get_net_stats_file_callbacks().at(file_type_)->on_read(size);
}
+
int32 NetQuery::tl_magic(const BufferSlice &buffer_slice) {
auto slice = buffer_slice.as_slice();
if (slice.size() < 4) {
@@ -35,29 +83,24 @@ int32 NetQuery::tl_magic(const BufferSlice &buffer_slice) {
return as<int32>(slice.begin());
}
-void dump_pending_network_queries() {
- auto n = NetQueryCounter::get_count();
- LOG(WARNING) << tag("pending net queries", n);
-
- decltype(n) i = 0;
- bool was_gap = false;
- for (auto end = &net_query_list_, cur = end->prev; cur != end; cur = cur->prev, i++) {
- if (i < 20 || i + 20 > n || i % (n / 20 + 1) == 0) {
- if (was_gap) {
- LOG(WARNING) << "...";
- was_gap = false;
- }
- auto nq = &static_cast<NetQuery &>(*cur);
- LOG(WARNING) << tag("id", nq->my_id_) << *nq << tag("total_flood", td::format::as_time(nq->total_timeout)) << " "
- << tag("since start", td::format::as_time(td::Time::now_cached() - nq->start_timestamp_))
- << tag("state", nq->debug_str_)
- << tag("since state", td::format::as_time(td::Time::now_cached() - nq->debug_timestamp_))
- << tag("resend_cnt", nq->debug_resend_cnt_) << tag("fail_cnt", nq->debug_send_failed_cnt_)
- << tag("ack", nq->debug_ack) << tag("unknown", nq->debug_unknown);
- } else {
- was_gap = true;
+void NetQuery::set_error(Status status, string source) {
+ if (status.code() == Error::Resend || status.code() == Error::Canceled || status.code() == Error::ResendInvokeAfter) {
+ return set_error_impl(Status::Error(200, PSLICE() << status), std::move(source));
+ }
+
+ if (begins_with(status.message(), "INPUT_METHOD_INVALID")) {
+ LOG(ERROR) << "Receive INPUT_METHOD_INVALID for query " << format::as_hex_dump<4>(Slice(query_.as_slice()));
+ }
+ if (status.message() == "BOT_METHOD_INVALID") {
+ auto id = tl_constructor();
+ if (id != telegram_api::help_getNearestDc::ID && id != telegram_api::help_getAppConfig::ID) {
+ LOG(ERROR) << "Receive BOT_METHOD_INVALID for query " << format::as_hex(id);
}
}
+ if (status.message() == "MSG_WAIT_FAILED" && status.code() != 400) {
+ status = Status::Error(400, "MSG_WAIT_FAILED");
+ }
+ set_error_impl(std::move(status), std::move(source));
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.h b/protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.h
index ccf1034a65..ee79be83b7 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQuery.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,28 +8,33 @@
#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQueryCounter.h"
+#include "td/telegram/net/NetQueryStats.h"
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
#include "td/actor/SignalSlot.h"
-#include "td/mtproto/utils.h" // for create_storer, fetch_result TODO
-
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
-#include "td/utils/List.h"
#include "td/utils/logging.h"
#include "td/utils/ObjectPool.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Span.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
-#include "td/utils/Time.h"
+#include "td/utils/tl_parsers.h"
+#include "td/utils/TsList.h"
#include <atomic>
#include <utility>
namespace td {
+extern int VERBOSITY_NAME(net_query);
+
+class ChainId;
+
class NetQuery;
using NetQueryPtr = ObjectPool<NetQuery>::OwnerPtr;
using NetQueryRef = ObjectPool<NetQuery>::WeakPtr;
@@ -40,17 +45,15 @@ class NetQueryCallback : public Actor {
virtual void on_result_resendable(NetQueryPtr query, Promise<NetQueryPtr> promise);
};
-extern ListNode net_query_list_;
-
-class NetQuery : public ListNode {
+class NetQuery final : public TsListNode<NetQueryDebug> {
public:
NetQuery() = default;
enum class State : int8 { Empty, Query, OK, Error };
- enum class Type { Common, Upload, Download, DownloadSmall };
+ enum class Type : int8 { Common, Upload, Download, DownloadSmall };
enum class AuthFlag : int8 { Off, On };
enum class GzipFlag : int8 { Off, On };
- enum Error : int32 { Resend = 202, Cancelled = 203, ResendInvokeAfter = 204 };
+ enum Error : int32 { Resend = 202, Canceled = 203, ResendInvokeAfter = 204 };
uint64 id() const {
return id_;
@@ -78,7 +81,10 @@ class NetQuery : public ListNode {
void resend(DcId new_dc_id) {
VLOG(net_query) << "Resend" << *this;
- debug_resend_cnt_++;
+ {
+ auto guard = lock();
+ get_data_unsafe().resend_count_++;
+ }
dc_id_ = new_dc_id;
status_ = Status::OK();
state_ = State::Query;
@@ -133,20 +139,14 @@ class NetQuery : public ListNode {
void on_net_write(size_t size);
void on_net_read(size_t size);
- void set_error(Status status, string source = "") {
- if (status.code() == Error::Resend || status.code() == Error::Cancelled ||
- status.code() == Error::ResendInvokeAfter) {
- return set_error_impl(Status::Error(200, PSLICE() << status), std::move(source));
- }
- set_error_impl(std::move(status), source);
- }
+ void set_error(Status status, string source = string());
void set_error_resend() {
set_error_impl(Status::Error<Error::Resend>());
}
- void set_error_cancelled() {
- set_error_impl(Status::Error<Error::Cancelled>());
+ void set_error_canceled() {
+ set_error_impl(Status::Error<Error::Canceled>());
}
void set_error_resend_invoke_after() {
@@ -156,7 +156,7 @@ class NetQuery : public ListNode {
bool update_is_ready() {
if (state_ == State::Query) {
if (cancellation_token_.load(std::memory_order_relaxed) == 0 || cancel_slot_.was_signal()) {
- set_error_cancelled();
+ set_error_canceled();
return true;
}
return false;
@@ -198,17 +198,17 @@ class NetQuery : public ListNode {
message_id_ = message_id;
}
- NetQueryRef invoke_after() const {
+ Span<NetQueryRef> invoke_after() const {
return invoke_after_;
}
- void set_invoke_after(NetQueryRef ref) {
- invoke_after_ = ref;
- }
- void set_session_rand(uint32 session_rand) {
- session_rand_ = session_rand;
+ void set_invoke_after(std::vector<NetQueryRef> refs) {
+ invoke_after_ = std::move(refs);
}
uint32 session_rand() const {
- return session_rand_;
+ if (in_sequence_dispacher_ && !chain_ids_.empty()) {
+ return static_cast<uint32>(chain_ids_[0] >> 10);
+ }
+ return 0;
}
void cancel(int32 cancellation_token) {
@@ -219,13 +219,16 @@ class NetQuery : public ListNode {
}
void clear() {
- LOG_IF(ERROR, !is_ready()) << "Destroy not ready query " << *this << " " << tag("debug", debug_str_);
+ if (!is_ready()) {
+ auto guard = lock();
+ LOG(ERROR) << "Destroy not ready query " << *this << " " << tag("state", get_data_unsafe().state_);
+ }
// TODO: CHECK if net_query is lost here
cancel_slot_.close();
*this = NetQuery();
}
bool empty() const {
- return state_ == State::Empty || nq_counter_.empty() || may_be_lost_;
+ return state_ == State::Empty || !nq_counter_ || may_be_lost_;
}
void stop_track() {
@@ -234,19 +237,16 @@ class NetQuery : public ListNode {
}
void debug_send_failed() {
- debug_send_failed_cnt_++;
+ auto guard = lock();
+ get_data_unsafe().send_failed_count_++;
}
- void debug(string str, bool may_be_lost = false) {
- may_be_lost_ = may_be_lost;
- debug_str_ = std::move(str);
- debug_timestamp_ = Time::now();
- debug_cnt_++;
- VLOG(net_query) << *this << " " << tag("debug", debug_str_);
- }
+ void debug(string state, bool may_be_lost = false);
+
void set_callback(ActorShared<NetQueryCallback> callback) {
callback_ = std::move(callback);
}
+
ActorShared<NetQueryCallback> move_callback() {
return std::move(callback_);
}
@@ -260,32 +260,54 @@ class NetQuery : public ListNode {
finish_migrate(cancel_slot_);
}
- static int32 tl_magic(const BufferSlice &buffer_slice);
+ int8 priority() const {
+ return priority_;
+ }
+ void set_priority(int8 priority) {
+ priority_ = priority;
+ }
+
+ Span<uint64> get_chain_ids() const {
+ return chain_ids_;
+ }
+
+ void set_in_sequence_dispatcher(bool in_sequence_dispacher) {
+ in_sequence_dispacher_ = in_sequence_dispacher;
+ }
+ bool in_sequence_dispatcher() const {
+ return in_sequence_dispacher_;
+ }
private:
State state_ = State::Empty;
- Type type_;
- AuthFlag auth_flag_;
- GzipFlag gzip_flag_;
+ Type type_ = Type::Common;
+ AuthFlag auth_flag_ = AuthFlag::Off;
+ GzipFlag gzip_flag_ = GzipFlag::Off;
DcId dc_id_;
+ NetQueryCounter nq_counter_;
Status status_;
- uint64 id_;
+ uint64 id_ = 0;
BufferSlice query_;
BufferSlice answer_;
- int32 tl_constructor_;
+ int32 tl_constructor_ = 0;
+
+ vector<NetQueryRef> invoke_after_;
+ vector<uint64> chain_ids_;
+
+ bool in_sequence_dispacher_ = false;
+ bool may_be_lost_ = false;
+ int8 priority_{0};
- NetQueryRef invoke_after_;
- uint32 session_rand_ = 0;
template <class T>
- struct movable_atomic : public std::atomic<T> {
+ struct movable_atomic final : public std::atomic<T> {
movable_atomic() = default;
movable_atomic(T &&x) : std::atomic<T>(std::forward<T>(x)) {
}
- movable_atomic(movable_atomic &&other) {
+ movable_atomic(movable_atomic &&other) noexcept {
this->store(other.load(std::memory_order_relaxed), std::memory_order_relaxed);
}
- movable_atomic &operator=(movable_atomic &&other) {
+ movable_atomic &operator=(movable_atomic &&other) noexcept {
this->store(other.load(std::memory_order_relaxed), std::memory_order_relaxed);
return *this;
}
@@ -294,63 +316,36 @@ class NetQuery : public ListNode {
~movable_atomic() = default;
};
- static int32 get_my_id();
-
movable_atomic<uint64> session_id_{0};
- uint64 message_id_;
+ uint64 message_id_{0};
movable_atomic<int32> cancellation_token_{-1}; // == 0 if query is canceled
ActorShared<NetQueryCallback> callback_;
- void set_error_impl(Status status, string source = "") {
+ void set_error_impl(Status status, string source = string()) {
VLOG(net_query) << "Got error " << *this << " " << status;
status_ = std::move(status);
state_ = State::Error;
source_ = std::move(source);
}
+ static int32 tl_magic(const BufferSlice &buffer_slice);
+
public:
- double next_timeout = 1;
- double total_timeout = 0;
- double total_timeout_limit = 60;
- double last_timeout = 0;
- bool need_resend_on_503 = true;
- bool may_be_lost_ = false;
- string debug_str_ = "empty";
- string source_ = "";
- double debug_timestamp_;
- int32 debug_cnt_ = 0;
- int32 debug_send_failed_cnt_ = 0;
- int32 debug_resend_cnt_ = 0;
- int debug_ack = 0;
- bool debug_unknown = false;
- int32 dispatch_ttl = -1;
- Slot cancel_slot_;
- Promise<> quick_ack_promise_;
- int32 file_type_ = -1;
-
- double start_timestamp_;
- int32 my_id_ = 0;
- NetQueryCounter nq_counter_;
+ int32 next_timeout_ = 1; // for NetQueryDelayer
+ int32 total_timeout_ = 0; // for NetQueryDelayer/SequenceDispatcher
+ int32 total_timeout_limit_ = 60; // for NetQueryDelayer/SequenceDispatcher and to be set by caller
+ int32 last_timeout_ = 0; // for NetQueryDelayer/SequenceDispatcher
+ string source_; // for NetQueryDelayer/SequenceDispatcher
+ int32 dispatch_ttl_ = -1; // for NetQueryDispatcher and to be set by caller
+ int32 file_type_ = -1; // to be set by caller
+ Slot cancel_slot_; // for Session and to be set by caller
+ Promise<> quick_ack_promise_; // for Session and to be set by caller
+ bool need_resend_on_503_ = true; // for NetQueryDispatcher and to be set by caller
NetQuery(State state, uint64 id, BufferSlice &&query, BufferSlice &&answer, DcId dc_id, Type type, AuthFlag auth_flag,
- GzipFlag gzip_flag, int32 tl_constructor)
- : state_(state)
- , type_(type)
- , auth_flag_(auth_flag)
- , gzip_flag_(gzip_flag)
- , dc_id_(dc_id)
- , status_()
- , id_(id)
- , query_(std::move(query))
- , answer_(std::move(answer))
- , tl_constructor_(tl_constructor)
- , nq_counter_(true) {
- my_id_ = get_my_id();
- start_timestamp_ = Time::now();
- LOG(INFO) << *this;
- // net_query_list_.put(this);
- }
+ GzipFlag gzip_flag, int32 tl_constructor, int32 total_timeout_limit, NetQueryStats *stats,
+ vector<ChainId> chain_ids);
};
inline StringBuilder &operator<<(StringBuilder &stream, const NetQuery &net_query) {
@@ -369,12 +364,14 @@ inline StringBuilder &operator<<(StringBuilder &stream, const NetQuery &net_quer
stream << "]";
return stream;
}
+
inline StringBuilder &operator<<(StringBuilder &stream, const NetQueryPtr &net_query_ptr) {
+ if (net_query_ptr.empty()) {
+ return stream << "[Query: null]";
+ }
return stream << *net_query_ptr;
}
-void dump_pending_network_queries();
-
inline void cancel_query(NetQueryRef &ref) {
if (ref.empty()) {
return;
@@ -383,6 +380,21 @@ inline void cancel_query(NetQueryRef &ref) {
}
template <class T>
+Result<typename T::ReturnType> fetch_result(const BufferSlice &message) {
+ TlBufferParser parser(&message);
+ auto result = T::fetch_result(parser);
+ parser.fetch_end();
+
+ const char *error = parser.get_error();
+ if (error != nullptr) {
+ LOG(ERROR) << "Can't parse: " << format::as_hex_dump<4>(message.as_slice());
+ return Status::Error(500, Slice(error));
+ }
+
+ return std::move(result);
+}
+
+template <class T>
Result<typename T::ReturnType> fetch_result(NetQueryPtr query) {
CHECK(!query.empty());
if (query->is_error()) {
@@ -392,9 +404,16 @@ Result<typename T::ReturnType> fetch_result(NetQueryPtr query) {
return fetch_result<T>(buffer);
}
+template <class T>
+Result<typename T::ReturnType> fetch_result(Result<NetQueryPtr> r_query) {
+ TRY_RESULT(query, std::move(r_query));
+ return fetch_result<T>(std::move(query));
+}
+
inline void NetQueryCallback::on_result(NetQueryPtr query) {
on_result_resendable(std::move(query), Auto());
}
+
inline void NetQueryCallback::on_result_resendable(NetQueryPtr query, Promise<NetQueryPtr> promise) {
on_result(std::move(query));
}
@@ -402,6 +421,7 @@ inline void NetQueryCallback::on_result_resendable(NetQueryPtr query, Promise<Ne
inline void start_migrate(NetQueryPtr &net_query, int32 sched_id) {
net_query->start_migrate(sched_id);
}
+
inline void finish_migrate(NetQueryPtr &net_query) {
net_query->finish_migrate();
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCounter.h b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCounter.h
index 0599d8cf36..744245b0ed 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCounter.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCounter.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,47 +9,32 @@
#include "td/utils/common.h"
#include <atomic>
+#include <memory>
namespace td {
class NetQueryCounter {
- static std::atomic<uint64> net_query_cnt_;
-
public:
- static uint64 get_count() {
- return net_query_cnt_.load();
- }
+ using Counter = std::atomic<uint64>;
- bool empty() const {
- return !is_alive_;
- }
+ NetQueryCounter() = default;
- explicit NetQueryCounter(bool is_alive = false) : is_alive_(is_alive) {
- if (is_alive) {
- net_query_cnt_++;
- }
+ explicit NetQueryCounter(Counter *counter) : ptr_(counter) {
+ CHECK(counter != nullptr);
+ counter->fetch_add(1, std::memory_order_relaxed);
}
- NetQueryCounter(const NetQueryCounter &other) = delete;
- NetQueryCounter &operator=(const NetQueryCounter &other) = delete;
- NetQueryCounter(NetQueryCounter &&other) : is_alive_(other.is_alive_) {
- other.is_alive_ = false;
- }
- NetQueryCounter &operator=(NetQueryCounter &&other) {
- if (is_alive_) {
- net_query_cnt_--;
- }
- is_alive_ = other.is_alive_;
- other.is_alive_ = false;
- return *this;
- }
- ~NetQueryCounter() {
- if (is_alive_) {
- net_query_cnt_--;
- }
+ explicit operator bool() const noexcept {
+ return static_cast<bool>(ptr_);
}
private:
- bool is_alive_;
+ struct Deleter {
+ void operator()(Counter *ptr) {
+ ptr->fetch_sub(1, std::memory_order_relaxed);
+ }
+ };
+ std::unique_ptr<Counter, Deleter> ptr_;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.cpp
index 20d71d3b98..62d2808797 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.cpp
@@ -1,29 +1,59 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/net/NetQueryCreator.h"
+#include "td/telegram/AuthManager.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/format.h"
#include "td/utils/Gzip.h"
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Storer.h"
namespace td {
-NetQueryCreator::Ptr NetQueryCreator::create(uint64 id, const Storer &storer, DcId dc_id, NetQuery::Type type,
- NetQuery::AuthFlag auth_flag, NetQuery::GzipFlag gzip_flag,
- double total_timeout_limit) {
+
+NetQueryCreator::NetQueryCreator(std::shared_ptr<NetQueryStats> net_query_stats)
+ : net_query_stats_(std::move(net_query_stats)) {
+ object_pool_.set_check_empty(true);
+}
+
+NetQueryPtr NetQueryCreator::create(const telegram_api::Function &function, vector<ChainId> chain_ids, DcId dc_id,
+ NetQuery::Type type) {
+ return create(UniqueId::next(), function, std::move(chain_ids), dc_id, type, NetQuery::AuthFlag::On);
+}
+
+NetQueryPtr NetQueryCreator::create(uint64 id, const telegram_api::Function &function, vector<ChainId> &&chain_ids,
+ DcId dc_id, NetQuery::Type type, NetQuery::AuthFlag auth_flag) {
+ LOG(INFO) << "Create query " << to_string(function);
+ auto storer = DefaultStorer<telegram_api::Function>(function);
BufferSlice slice(storer.size());
- storer.store(slice.as_slice().ubegin());
+ auto real_size = storer.store(slice.as_slice().ubegin());
+ LOG_CHECK(real_size == slice.size()) << real_size << " " << slice.size() << " "
+ << format::as_hex_dump<4>(Slice(slice.as_slice()));
- // TODO: magic constant
- if (slice.size() < (1 << 8)) {
- gzip_flag = NetQuery::GzipFlag::Off;
+ int32 tl_constructor = function.get_id();
+
+ size_t MIN_GZIPPED_SIZE = 128;
+ auto gzip_flag = slice.size() < MIN_GZIPPED_SIZE ? NetQuery::GzipFlag::Off : NetQuery::GzipFlag::On;
+ if (slice.size() >= 16384) {
+ // test compression ratio for the middle part
+ // if it is less than 0.9, then try to compress the whole request
+ size_t TESTED_SIZE = 1024;
+ BufferSlice compressed_part = gzencode(slice.as_slice().substr((slice.size() - TESTED_SIZE) / 2, TESTED_SIZE), 0.9);
+ if (compressed_part.empty()) {
+ gzip_flag = NetQuery::GzipFlag::Off;
+ }
}
- int32 tl_constructor = NetQuery::tl_magic(slice);
if (gzip_flag == NetQuery::GzipFlag::On) {
- // TODO: try to compress files?
- BufferSlice compressed;
- compressed = gzencode(slice.as_slice());
+ BufferSlice compressed = gzencode(slice.as_slice(), 0.9);
if (compressed.empty()) {
gzip_flag = NetQuery::GzipFlag::Off;
} else {
@@ -31,10 +61,26 @@ NetQueryCreator::Ptr NetQueryCreator::create(uint64 id, const Storer &storer, Dc
}
}
- auto query = object_pool_.create(NetQuery::State::Query, id, std::move(slice), BufferSlice(), dc_id, type, auth_flag,
- gzip_flag, tl_constructor);
+ int32 total_timeout_limit = 60;
+ if (!G()->close_flag()) {
+ auto td = G()->td();
+ if (!td.empty()) {
+ auto auth_manager = td.get_actor_unsafe()->auth_manager_.get();
+ if (auth_manager != nullptr && auth_manager->is_bot()) {
+ total_timeout_limit = 8;
+ }
+ if ((auth_manager == nullptr || !auth_manager->was_authorized()) && auth_flag == NetQuery::AuthFlag::On &&
+ tl_constructor != telegram_api::auth_exportAuthorization::ID &&
+ tl_constructor != telegram_api::auth_bindTempAuthKey::ID) {
+ LOG(ERROR) << "Send query before authorization: " << to_string(function);
+ }
+ }
+ }
+ auto query =
+ object_pool_.create(NetQuery::State::Query, id, std::move(slice), BufferSlice(), dc_id, type, auth_flag,
+ gzip_flag, tl_constructor, total_timeout_limit, net_query_stats_.get(), std::move(chain_ids));
query->set_cancellation_token(query.generation());
- query->total_timeout_limit = total_timeout_limit;
return query;
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.h b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.h
index 8973dc46bf..56c678ad6e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCreator.h
@@ -1,54 +1,48 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/ChainId.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/net/NetQueryStats.h"
#include "td/telegram/UniqueId.h"
-#include "td/utils/buffer.h"
#include "td/utils/ObjectPool.h"
-#include "td/utils/Storer.h"
+
+#include <memory>
namespace td {
+
+namespace telegram_api {
+class Function;
+} // namespace telegram_api
+
class NetQueryCreator {
public:
- using Ptr = NetQueryPtr;
- using Ref = NetQueryRef;
-
- NetQueryCreator() {
- object_pool_.set_check_empty(true);
- }
+ explicit NetQueryCreator(std::shared_ptr<NetQueryStats> net_query_stats);
void stop_check() {
object_pool_.set_check_empty(false);
}
- Ptr create_result(BufferSlice &&buffer, DcId dc_id = DcId::main(),
- NetQuery::AuthFlag auth_flag = NetQuery::AuthFlag::On,
- NetQuery::GzipFlag gzip_flag = NetQuery::GzipFlag::Off) {
- return create_result(0, std::move(buffer), dc_id, auth_flag, gzip_flag);
- }
- Ptr create_result(uint64 id, BufferSlice &&buffer, DcId dc_id = DcId::main(),
- NetQuery::AuthFlag auth_flag = NetQuery::AuthFlag::On,
- NetQuery::GzipFlag gzip_flag = NetQuery::GzipFlag::Off) {
- return object_pool_.create(NetQuery::State::OK, id, BufferSlice(), std::move(buffer), dc_id, NetQuery::Type::Common,
- auth_flag, gzip_flag, 0);
- }
+ NetQueryPtr create(const telegram_api::Function &function, vector<ChainId> chain_ids = {}, DcId dc_id = DcId::main(),
+ NetQuery::Type type = NetQuery::Type::Common);
- Ptr create(const Storer &storer, DcId dc_id = DcId::main(), NetQuery::Type type = NetQuery::Type::Common,
- NetQuery::AuthFlag auth_flag = NetQuery::AuthFlag::On,
- NetQuery::GzipFlag gzip_flag = NetQuery::GzipFlag::On, double total_timeout_limit = 60) {
- return create(UniqueId::next(), storer, dc_id, type, auth_flag, gzip_flag, total_timeout_limit);
+ NetQueryPtr create_unauth(const telegram_api::Function &function, DcId dc_id = DcId::main()) {
+ return create(UniqueId::next(), function, {}, dc_id, NetQuery::Type::Common, NetQuery::AuthFlag::Off);
}
- Ptr create(uint64 id, const Storer &storer, DcId dc_id = DcId::main(), NetQuery::Type type = NetQuery::Type::Common,
- NetQuery::AuthFlag auth_flag = NetQuery::AuthFlag::On,
- NetQuery::GzipFlag gzip_flag = NetQuery::GzipFlag::On, double total_timeout_limit = 60);
+
+ NetQueryPtr create(uint64 id, const telegram_api::Function &function, vector<ChainId> &&chain_ids, DcId dc_id,
+ NetQuery::Type type, NetQuery::AuthFlag auth_flag);
private:
+ std::shared_ptr<NetQueryStats> net_query_stats_;
ObjectPool<NetQuery> object_pool_;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.cpp
index 0950b19f87..6fed2ce1c7 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,31 +9,36 @@
#include "td/telegram/Global.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
namespace td {
+
void NetQueryDelayer::delay(NetQueryPtr query) {
- query->debug("try delay");
+ query->debug("trying to delay");
query->is_ready();
CHECK(query->is_error());
auto code = query->error().code();
- double timeout = 0;
- if (code < 0 || code == 500) {
+ int32 timeout = 0;
+ if (code < 0) {
// skip
+ } else if (code == 500) {
+ auto error_message = query->error().message();
+ if (error_message == "WORKER_BUSY_TOO_LONG_RETRY") {
+ timeout = 1; // it is dangerous to resend query without timeout, so use 1
+ }
} else if (code == 420) {
- auto msg = query->error().message();
- auto prefix = Slice("FLOOD_WAIT_");
- if (msg.substr(0, prefix.size()) == prefix) {
- timeout = to_integer<int>(msg.substr(prefix.size()));
- if (timeout < 0) {
- timeout = 0;
- }
- if (timeout > 24 * 60 * 60) {
- timeout = 24 * 60 * 60;
+ auto error_message = query->error().message();
+ for (auto prefix :
+ {Slice("FLOOD_WAIT_"), Slice("SLOWMODE_WAIT_"), Slice("2FA_CONFIRM_WAIT_"), Slice("TAKEOUT_INIT_DELAY_")}) {
+ if (begins_with(error_message, prefix)) {
+ timeout = clamp(to_integer<int>(error_message.substr(prefix.size())), 1, 14 * 24 * 60 * 60);
+ break;
}
}
} else {
@@ -42,40 +47,40 @@ void NetQueryDelayer::delay(NetQueryPtr query) {
}
if (timeout == 0) {
- timeout = query->next_timeout;
+ timeout = query->next_timeout_;
if (timeout < 60) {
- query->next_timeout *= 2;
+ query->next_timeout_ *= 2;
}
} else {
- query->next_timeout = 1;
+ query->next_timeout_ = 1;
}
- query->total_timeout += timeout;
- query->last_timeout = timeout;
+ query->total_timeout_ += timeout;
+ query->last_timeout_ = timeout;
+ LOG(INFO) << "Set total_timeout to " << query->total_timeout_ << " for " << query->id();
auto error = query->error().move_as_error();
query->resend();
// Fix for infinity flood control
- if (!query->need_resend_on_503 && code == -503) {
+ if (!query->need_resend_on_503_ && code == -503) {
query->set_error(Status::Error(502, "Bad Gateway"));
query->debug("DcManager: send to DcManager");
G()->net_query_dispatcher().dispatch(std::move(query));
return;
}
- if (query->total_timeout > query->total_timeout_limit) {
+ if (query->total_timeout_ > query->total_timeout_limit_) {
// TODO: support timeouts in DcAuth and GetConfig
- LOG(WARNING) << "Failed: " << query << " " << tag("timeout", timeout) << tag("total_timeout", query->total_timeout)
+ LOG(WARNING) << "Failed: " << query << " " << tag("timeout", timeout) << tag("total_timeout", query->total_timeout_)
<< " because of " << error << " from " << query->source_;
// NB: code must differ from tdapi FLOOD_WAIT code
- query->set_error(
- Status::Error(429, PSLICE() << "Too Many Requests: retry after " << static_cast<int32>(timeout + 0.999)));
+ query->set_error(Status::Error(429, PSLICE() << "Too Many Requests: retry after " << timeout));
query->debug("DcManager: send to DcManager");
G()->net_query_dispatcher().dispatch(std::move(query));
return;
}
- LOG(WARNING) << "Delay: " << query << " " << tag("timeout", timeout) << tag("total_timeout", query->total_timeout)
+ LOG(WARNING) << "Delay: " << query << " " << tag("timeout", timeout) << tag("total_timeout", query->total_timeout_)
<< " because of " << error << " from " << query->source_;
query->debug(PSTRING() << "delay for " << format::as_time(timeout));
auto id = container_.create(QuerySlot());
@@ -88,7 +93,6 @@ void NetQueryDelayer::delay(NetQueryPtr query) {
void NetQueryDelayer::wakeup() {
auto link_token = get_link_token();
if (link_token) {
- LOG(INFO) << "raw_event";
on_slot_event(link_token);
}
loop();
@@ -112,8 +116,9 @@ void NetQueryDelayer::on_slot_event(uint64 id) {
void NetQueryDelayer::tear_down() {
container_.for_each([](auto id, auto &query_slot) {
- query_slot.query_->set_error(Status::Error(500, "Internal Server Error: closing"));
+ query_slot.query_->set_error(Global::request_aborted_error());
G()->net_query_dispatcher().dispatch(std::move(query_slot.query_));
});
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.h b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.h
index 5083434004..7ec91aed69 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDelayer.h
@@ -1,10 +1,11 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/telegram/net/NetQuery.h"
#include "td/actor/actor.h"
@@ -13,7 +14,8 @@
#include "td/utils/Container.h"
namespace td {
-class NetQueryDelayer : public Actor {
+
+class NetQueryDelayer final : public Actor {
public:
explicit NetQueryDelayer(ActorShared<> parent) : parent_(std::move(parent)) {
}
@@ -26,10 +28,11 @@ class NetQueryDelayer : public Actor {
};
Container<QuerySlot> container_;
ActorShared<> parent_;
- void wakeup() override;
+ void wakeup() final;
void on_slot_event(uint64 id);
- void tear_down() override;
+ void tear_down() final;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.cpp
index 9eb0571ace..bdd6cf3ca5 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.cpp
@@ -1,40 +1,66 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/Global.h"
+#include "td/telegram/net/AuthDataShared.h"
#include "td/telegram/net/DcAuthManager.h"
#include "td/telegram/net/NetQuery.h"
#include "td/telegram/net/NetQueryDelayer.h"
+#include "td/telegram/net/PublicRsaKeyShared.h"
#include "td/telegram/net/PublicRsaKeyWatchdog.h"
#include "td/telegram/net/SessionMultiProxy.h"
-
-#include "td/telegram/ConfigShared.h"
-#include "td/telegram/Global.h"
+#include "td/telegram/SequenceDispatcher.h"
#include "td/telegram/Td.h"
-
-#include "td/db/Pmc.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/telegram_api.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
-#include "td/utils/port/thread.h"
+#include "td/utils/port/sleep.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
namespace td {
+
+void NetQueryDispatcher::complete_net_query(NetQueryPtr net_query) {
+ auto callback = net_query->move_callback();
+ if (callback.empty()) {
+ net_query->debug("sent to td (no callback)");
+ send_closure_later(G()->td(), &Td::on_result, std::move(net_query));
+ } else {
+ net_query->debug("sent to callback", true);
+ send_closure_later(std::move(callback), &NetQueryCallback::on_result, std::move(net_query));
+ }
+}
+
void NetQueryDispatcher::dispatch(NetQueryPtr net_query) {
- net_query->debug("dispatch");
+ // net_query->debug("dispatch");
if (stop_flag_.load(std::memory_order_relaxed)) {
- // Set error to avoid warning
- // No need to send result somewhere, it probably will be ignored anyway
- net_query->set_error(Status::Error(500, "Internal Server Error: closing"));
- net_query->clear();
- net_query.reset();
- // G()->net_query_creator().release(std::move(net_query));
+ net_query->set_error(Global::request_aborted_error());
+ return complete_net_query(std::move(net_query));
+ }
+ if (G()->get_option_boolean("test_flood_wait")) {
+ net_query->set_error(Status::Error(429, "Too Many Requests: retry after 10"));
+ return complete_net_query(std::move(net_query));
+ // if (net_query->is_ok() && net_query->tl_constructor() == telegram_api::messages_sendMessage::ID) {
+ // net_query->set_error(Status::Error(420, "FLOOD_WAIT_10"));
+ // }
+ }
+ if (net_query->tl_constructor() == telegram_api::account_getPassword::ID && false) {
+ net_query->set_error(Status::Error(429, "Too Many Requests: retry after 10"));
+ return complete_net_query(std::move(net_query));
+ }
+
+ if (!net_query->in_sequence_dispatcher() && !net_query->get_chain_ids().empty()) {
+ net_query->debug("sent to main sequence dispatcher");
+ send_closure_later(sequence_dispatcher_, &MultiSequenceDispatcher::send, std::move(net_query));
return;
}
@@ -47,13 +73,13 @@ void NetQueryDispatcher::dispatch(NetQueryPtr net_query) {
net_query->resend();
} else if (code < 0 || code == 500 || code == 420) {
net_query->debug("sent to NetQueryDelayer");
- return send_closure(delayer_, &NetQueryDelayer::delay, std::move(net_query));
+ return send_closure_later(delayer_, &NetQueryDelayer::delay, std::move(net_query));
}
}
}
if (!net_query->is_ready()) {
- if (net_query->dispatch_ttl == 0) {
+ if (net_query->dispatch_ttl_ == 0) {
net_query->set_error(Status::Error("DispatchTtlError"));
}
}
@@ -67,22 +93,14 @@ void NetQueryDispatcher::dispatch(NetQueryPtr net_query) {
}
if (net_query->is_ready()) {
- auto callback = net_query->move_callback();
- if (callback.empty()) {
- net_query->debug("sent to td (no callback)");
- send_closure(G()->td(), &NetQueryCallback::on_result, std::move(net_query));
- } else {
- net_query->debug("sent to callback", true);
- send_closure(std::move(callback), &NetQueryCallback::on_result, std::move(net_query));
- }
- return;
+ return complete_net_query(std::move(net_query));
}
- if (net_query->dispatch_ttl > 0) {
- net_query->dispatch_ttl--;
+ if (net_query->dispatch_ttl_ > 0) {
+ net_query->dispatch_ttl_--;
}
- size_t dc_pos = static_cast<size_t>(dest_dc_id.get_raw_id() - 1);
+ auto dc_pos = static_cast<size_t>(dest_dc_id.get_raw_id() - 1);
CHECK(dc_pos < dcs_.size());
switch (net_query->type()) {
case NetQuery::Type::Common:
@@ -109,9 +127,9 @@ Status NetQueryDispatcher::wait_dc_init(DcId dc_id, bool force) {
if (!dc_id.is_exact()) {
return Status::Error("Not exact DC");
}
- size_t pos = static_cast<size_t>(dc_id.get_raw_id() - 1);
+ auto pos = static_cast<size_t>(dc_id.get_raw_id() - 1);
if (pos >= dcs_.size()) {
- return Status::Error("Too big DC id");
+ return Status::Error("Too big DC ID");
}
auto &dc = dcs_[pos];
@@ -127,16 +145,18 @@ Status NetQueryDispatcher::wait_dc_init(DcId dc_id, bool force) {
if (should_init) {
std::lock_guard<std::mutex> guard(main_dc_id_mutex_);
- if (stop_flag_.load(std::memory_order_relaxed)) {
+ if (stop_flag_.load(std::memory_order_relaxed) || need_destroy_auth_key_) {
return Status::Error("Closing");
}
// init dc
+ dc.id_ = dc_id;
decltype(common_public_rsa_key_) public_rsa_key;
bool is_cdn = false;
+ bool need_destroy_key = false;
if (dc_id.is_internal()) {
public_rsa_key = common_public_rsa_key_;
} else {
- public_rsa_key = std::make_shared<PublicRsaKeyShared>(dc_id);
+ public_rsa_key = std::make_shared<PublicRsaKeyShared>(dc_id, G()->is_test_dc());
send_closure_later(public_rsa_key_watchdog_, &PublicRsaKeyWatchdog::add_public_rsa_key, public_rsa_key);
is_cdn = true;
}
@@ -147,19 +167,22 @@ Status NetQueryDispatcher::wait_dc_init(DcId dc_id, bool force) {
int32 slow_net_scheduler_id = G()->get_slow_net_scheduler_id();
auto raw_dc_id = dc_id.get_raw_id();
+ bool is_premium = G()->get_option_boolean("is_premium");
+ int32 upload_session_count = (raw_dc_id != 2 && raw_dc_id != 4) || is_premium ? 8 : 4;
+ int32 download_session_count = is_premium ? 8 : 2;
+ int32 download_small_session_count = is_premium ? 8 : 2;
dc.main_session_ = create_actor<SessionMultiProxy>(PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":main",
- session_count, auth_data, raw_dc_id == main_dc_id_,
- use_pfs || (session_count > 1), false, false, is_cdn);
+ session_count, auth_data, raw_dc_id == main_dc_id_, use_pfs,
+ false, false, is_cdn, need_destroy_key);
dc.upload_session_ = create_actor_on_scheduler<SessionMultiProxy>(
- PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":upload", slow_net_scheduler_id,
- raw_dc_id != 2 && raw_dc_id != 4 ? 8 : 4, auth_data, false, use_pfs || (session_count > 1), false, true,
- is_cdn);
+ PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":upload", slow_net_scheduler_id, upload_session_count,
+ auth_data, false, use_pfs, false, true, is_cdn, need_destroy_key);
dc.download_session_ = create_actor_on_scheduler<SessionMultiProxy>(
- PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":download", slow_net_scheduler_id, 1, auth_data, false,
- use_pfs, true, true, is_cdn);
+ PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":download", slow_net_scheduler_id, download_session_count,
+ auth_data, false, use_pfs, true, true, is_cdn, need_destroy_key);
dc.download_small_session_ = create_actor_on_scheduler<SessionMultiProxy>(
- PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":download_small", slow_net_scheduler_id, 1, auth_data, false,
- use_pfs, true, true, is_cdn);
+ PSLICE() << "SessionMultiProxy:" << raw_dc_id << ":download_small", slow_net_scheduler_id,
+ download_small_session_count, auth_data, false, use_pfs, true, true, is_cdn, need_destroy_key);
dc.is_inited_ = true;
if (dc_id.is_internal()) {
send_closure_later(dc_auth_manager_, &DcAuthManager::add_dc, std::move(auth_data));
@@ -170,7 +193,7 @@ Status NetQueryDispatcher::wait_dc_init(DcId dc_id, bool force) {
return Status::Error("Closing");
}
#if !TD_THREAD_UNSUPPORTED
- td::this_thread::yield();
+ usleep_for(1);
#endif
}
}
@@ -186,15 +209,16 @@ void NetQueryDispatcher::stop() {
std::lock_guard<std::mutex> guard(main_dc_id_mutex_);
td_guard_.reset();
stop_flag_ = true;
- delayer_.hangup();
- for (const auto &dc : dcs_) {
- dc.main_session_.hangup();
- dc.upload_session_.hangup();
- dc.download_session_.hangup();
- dc.download_small_session_.hangup();
+ delayer_.reset();
+ for (auto &dc : dcs_) {
+ dc.main_session_.reset();
+ dc.upload_session_.reset();
+ dc.download_session_.reset();
+ dc.download_small_session_.reset();
}
public_rsa_key_watchdog_.reset();
dc_auth_manager_.reset();
+ sequence_dispatcher_.reset();
}
void NetQueryDispatcher::update_session_count() {
@@ -203,41 +227,64 @@ void NetQueryDispatcher::update_session_count() {
bool use_pfs = get_use_pfs();
for (size_t i = 1; i < MAX_DC_COUNT; i++) {
if (is_dc_inited(narrow_cast<int32>(i))) {
- send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_options, session_count,
- use_pfs || (session_count > 1));
+ send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_options, session_count, use_pfs);
+ send_closure_later(dcs_[i - 1].upload_session_, &SessionMultiProxy::update_use_pfs, use_pfs);
+ send_closure_later(dcs_[i - 1].download_session_, &SessionMultiProxy::update_use_pfs, use_pfs);
+ send_closure_later(dcs_[i - 1].download_small_session_, &SessionMultiProxy::update_use_pfs, use_pfs);
}
}
}
+void NetQueryDispatcher::destroy_auth_keys(Promise<> promise) {
+ std::lock_guard<std::mutex> guard(main_dc_id_mutex_);
+ LOG(INFO) << "Destroy auth keys";
+ need_destroy_auth_key_ = true;
+ for (size_t i = 1; i < MAX_DC_COUNT; i++) {
+ if (is_dc_inited(narrow_cast<int32>(i)) && dcs_[i - 1].id_.is_internal()) {
+ send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_destroy_auth_key,
+ need_destroy_auth_key_);
+ }
+ }
+ send_closure_later(dc_auth_manager_, &DcAuthManager::destroy, std::move(promise));
+}
void NetQueryDispatcher::update_use_pfs() {
std::lock_guard<std::mutex> guard(main_dc_id_mutex_);
- int32 session_count = get_session_count();
bool use_pfs = get_use_pfs();
for (size_t i = 1; i < MAX_DC_COUNT; i++) {
if (is_dc_inited(narrow_cast<int32>(i))) {
- send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_use_pfs, use_pfs || (session_count > 1));
+ send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_use_pfs, use_pfs);
send_closure_later(dcs_[i - 1].upload_session_, &SessionMultiProxy::update_use_pfs, use_pfs);
send_closure_later(dcs_[i - 1].download_session_, &SessionMultiProxy::update_use_pfs, use_pfs);
send_closure_later(dcs_[i - 1].download_small_session_, &SessionMultiProxy::update_use_pfs, use_pfs);
}
}
}
-void NetQueryDispatcher::update_valid_dc(DcId dc_id) {
- wait_dc_init(dc_id, true);
+
+void NetQueryDispatcher::update_mtproto_header() {
+ std::lock_guard<std::mutex> guard(main_dc_id_mutex_);
+ for (size_t i = 1; i < MAX_DC_COUNT; i++) {
+ if (is_dc_inited(narrow_cast<int32>(i))) {
+ send_closure_later(dcs_[i - 1].main_session_, &SessionMultiProxy::update_mtproto_header);
+ send_closure_later(dcs_[i - 1].upload_session_, &SessionMultiProxy::update_mtproto_header);
+ send_closure_later(dcs_[i - 1].download_session_, &SessionMultiProxy::update_mtproto_header);
+ send_closure_later(dcs_[i - 1].download_small_session_, &SessionMultiProxy::update_mtproto_header);
+ }
+ }
}
bool NetQueryDispatcher::is_dc_inited(int32 raw_dc_id) {
return dcs_[raw_dc_id - 1].is_valid_.load(std::memory_order_relaxed);
}
+
int32 NetQueryDispatcher::get_session_count() {
- return max(G()->shared_config().get_option_integer("session_count"), 1);
+ return max(narrow_cast<int32>(G()->get_option_integer("session_count")), 1);
}
bool NetQueryDispatcher::get_use_pfs() {
- return G()->shared_config().get_option_boolean("use_pfs");
+ return G()->get_option_boolean("use_pfs") || get_session_count() > 1;
}
-NetQueryDispatcher::NetQueryDispatcher(std::function<ActorShared<>()> create_reference) {
+NetQueryDispatcher::NetQueryDispatcher(const std::function<ActorShared<>()> &create_reference) {
auto s_main_dc_id = G()->td_db()->get_binlog_pmc()->get("main_dc_id");
if (!s_main_dc_id.empty()) {
main_dc_id_ = to_integer<int32>(s_main_dc_id);
@@ -245,44 +292,26 @@ NetQueryDispatcher::NetQueryDispatcher(std::function<ActorShared<>()> create_ref
LOG(INFO) << tag("main_dc_id", main_dc_id_.load(std::memory_order_relaxed));
delayer_ = create_actor<NetQueryDelayer>("NetQueryDelayer", create_reference());
dc_auth_manager_ = create_actor<DcAuthManager>("DcAuthManager", create_reference());
- common_public_rsa_key_ = std::make_shared<PublicRsaKeyShared>(DcId::empty());
+ common_public_rsa_key_ = std::make_shared<PublicRsaKeyShared>(DcId::empty(), G()->is_test_dc());
public_rsa_key_watchdog_ = create_actor<PublicRsaKeyWatchdog>("PublicRsaKeyWatchdog", create_reference());
+ sequence_dispatcher_ = MultiSequenceDispatcher::create("MultiSequenceDispatcher");
- td_guard_ = create_shared_lambda_guard([actor = create_reference] {});
+ td_guard_ = create_shared_lambda_guard([actor = create_reference()] {});
}
NetQueryDispatcher::NetQueryDispatcher() = default;
NetQueryDispatcher::~NetQueryDispatcher() = default;
void NetQueryDispatcher::try_fix_migrate(NetQueryPtr &net_query) {
- auto msg = net_query->error().message();
+ auto error_message = net_query->error().message();
static constexpr CSlice prefixes[] = {"PHONE_MIGRATE_", "NETWORK_MIGRATE_", "USER_MIGRATE_"};
for (auto &prefix : prefixes) {
- if (msg.substr(0, prefix.size()) == prefix) {
- int32 new_main_dc_id = to_integer<int32>(msg.substr(prefix.size()));
- if (!DcId::is_valid(new_main_dc_id)) {
- LOG(FATAL) << "Receive " << prefix << " to wrong dc " << new_main_dc_id;
- }
- if (new_main_dc_id != main_dc_id_.load(std::memory_order_relaxed)) {
- // Very rare event. Mutex is ok.
- std::lock_guard<std::mutex> guard(main_dc_id_mutex_);
- if (new_main_dc_id != main_dc_id_) {
- LOG(INFO) << "Update: " << tag("main_dc_id", main_dc_id_.load(std::memory_order_relaxed));
- if (is_dc_inited(main_dc_id_.load(std::memory_order_relaxed))) {
- send_closure_later(dcs_[main_dc_id_ - 1].main_session_, &SessionMultiProxy::update_main_flag, false);
- }
- main_dc_id_ = new_main_dc_id;
- if (is_dc_inited(main_dc_id_.load(std::memory_order_relaxed))) {
- send_closure_later(dcs_[main_dc_id_ - 1].main_session_, &SessionMultiProxy::update_main_flag, true);
- }
- send_closure_later(dc_auth_manager_, &DcAuthManager::update_main_dc,
- DcId::internal(main_dc_id_.load(std::memory_order_relaxed)));
- G()->td_db()->get_binlog_pmc()->set("main_dc_id", to_string(main_dc_id_.load(std::memory_order_relaxed)));
- }
- }
+ if (error_message.substr(0, prefix.size()) == prefix) {
+ auto new_main_dc_id = to_integer<int32>(error_message.substr(prefix.size()));
+ set_main_dc_id(new_main_dc_id);
if (!net_query->dc_id().is_main()) {
- LOG(ERROR) << msg << " from query to non-main dc " << net_query->dc_id();
+ LOG(ERROR) << "Receive " << error_message << " for query to non-main DC" << net_query->dc_id();
net_query->resend(DcId::internal(new_main_dc_id));
} else {
net_query->resend();
@@ -292,4 +321,36 @@ void NetQueryDispatcher::try_fix_migrate(NetQueryPtr &net_query) {
}
}
+void NetQueryDispatcher::set_main_dc_id(int32 new_main_dc_id) {
+ if (!DcId::is_valid(new_main_dc_id)) {
+ LOG(ERROR) << "Receive wrong DC " << new_main_dc_id;
+ return;
+ }
+ if (new_main_dc_id == main_dc_id_.load(std::memory_order_relaxed)) {
+ return;
+ }
+
+ // Very rare event; mutex is ok.
+ std::lock_guard<std::mutex> guard(main_dc_id_mutex_);
+ if (new_main_dc_id == main_dc_id_) {
+ return;
+ }
+
+ LOG(INFO) << "Update main DcId from " << main_dc_id_.load(std::memory_order_relaxed) << " to " << new_main_dc_id;
+ if (is_dc_inited(main_dc_id_.load(std::memory_order_relaxed))) {
+ send_closure_later(dcs_[main_dc_id_ - 1].main_session_, &SessionMultiProxy::update_main_flag, false);
+ }
+ main_dc_id_ = new_main_dc_id;
+ if (is_dc_inited(main_dc_id_.load(std::memory_order_relaxed))) {
+ send_closure_later(dcs_[main_dc_id_ - 1].main_session_, &SessionMultiProxy::update_main_flag, true);
+ }
+ send_closure_later(dc_auth_manager_, &DcAuthManager::update_main_dc,
+ DcId::internal(main_dc_id_.load(std::memory_order_relaxed)));
+ G()->td_db()->get_binlog_pmc()->set("main_dc_id", to_string(main_dc_id_.load(std::memory_order_relaxed)));
+}
+
+void NetQueryDispatcher::check_authorization_is_ok() {
+ send_closure(dc_auth_manager_, &DcAuthManager::check_authorization_is_ok);
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.h b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.h
index 461c8b5ba8..737d7a7484 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryDispatcher.h
@@ -1,16 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/net/AuthDataShared.h"
+
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQuery.h"
#include "td/actor/actor.h"
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/Status.h"
@@ -21,19 +23,18 @@
#include <mutex>
namespace td {
-class NetQueryDelayer;
-class DataCenter;
+
class DcAuthManager;
-class SessionMultiProxy;
+class MultiSequenceDispatcher;
+class NetQueryDelayer;
class PublicRsaKeyShared;
class PublicRsaKeyWatchdog;
-} // namespace td
+class SessionMultiProxy;
-namespace td {
// Not just dispatcher.
class NetQueryDispatcher {
public:
- explicit NetQueryDispatcher(std::function<ActorShared<>()> create_reference);
+ explicit NetQueryDispatcher(const std::function<ActorShared<>()> &create_reference);
NetQueryDispatcher();
NetQueryDispatcher(const NetQueryDispatcher &) = delete;
NetQueryDispatcher &operator=(const NetQueryDispatcher &) = delete;
@@ -46,17 +47,25 @@ class NetQueryDispatcher {
void stop();
void update_session_count();
+ void destroy_auth_keys(Promise<> promise);
void update_use_pfs();
- void update_valid_dc(DcId dc_id);
- DcId main_dc_id() {
- return DcId::internal(main_dc_id_.load());
+ void update_mtproto_header();
+
+ DcId get_main_dc_id() const {
+ return DcId::internal(main_dc_id_.load(std::memory_order_relaxed));
}
+ void set_main_dc_id(int32 new_main_dc_id);
+ void check_authorization_is_ok();
+
private:
std::atomic<bool> stop_flag_{false};
+ bool need_destroy_auth_key_{false};
ActorOwn<NetQueryDelayer> delayer_;
ActorOwn<DcAuthManager> dc_auth_manager_;
+ ActorOwn<MultiSequenceDispatcher> sequence_dispatcher_;
struct Dc {
+ DcId id_;
std::atomic<bool> is_valid_{false};
std::atomic<bool> is_inited_{false}; // TODO: cache in scheduler local storage :D
@@ -83,6 +92,9 @@ class NetQueryDispatcher {
static int32 get_session_count();
static bool get_use_pfs();
+ static void complete_net_query(NetQueryPtr net_query);
+
void try_fix_migrate(NetQueryPtr &net_query);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryStats.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryStats.cpp
new file mode 100644
index 0000000000..357d46503b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryStats.cpp
@@ -0,0 +1,56 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/net/NetQueryStats.h"
+
+#include "td/telegram/net/NetQuery.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Time.h"
+
+namespace td {
+
+uint64 NetQueryStats::get_count() const {
+ return count_.load(std::memory_order_relaxed);
+}
+
+void NetQueryStats::dump_pending_network_queries() {
+ auto n = get_count();
+ LOG(WARNING) << tag("pending net queries", n);
+
+ if (!use_list_) {
+ return;
+ }
+ decltype(n) i = 0;
+ bool was_gap = false;
+ auto &net_query_list = list_;
+ auto guard = net_query_list.lock();
+ for (auto begin = net_query_list.begin(), cur = net_query_list.end(); cur != begin; i++) {
+ cur = cur->get_prev();
+ if (i < 20 || i + 20 > n || i % (n / 20 + 1) == 0) {
+ if (was_gap) {
+ LOG(WARNING) << "...";
+ was_gap = false;
+ }
+ const NetQueryDebug &debug = cur->get_data_unsafe();
+ const NetQuery &nq = *static_cast<const NetQuery *>(cur);
+ LOG(WARNING) << tag("user", lpad(PSTRING() << debug.my_id_, 10, ' ')) << nq
+ << tag("total flood", format::as_time(nq.total_timeout_))
+ << tag("since start", format::as_time(Time::now_cached() - debug.start_timestamp_))
+ << tag("state", debug.state_)
+ << tag("in this state", format::as_time(Time::now_cached() - debug.state_timestamp_))
+ << tag("state changed", debug.state_change_count_) << tag("resend count", debug.resend_count_)
+ << tag("fail count", debug.send_failed_count_) << tag("ack state", debug.ack_state_)
+ << tag("unknown", debug.unknown_state_);
+ } else {
+ was_gap = true;
+ }
+ }
+}
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryStats.h b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryStats.h
new file mode 100644
index 0000000000..cfee970fb4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryStats.h
@@ -0,0 +1,49 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/telegram/net/NetQueryCounter.h"
+
+#include "td/utils/common.h"
+#include "td/utils/TsList.h"
+
+#include <atomic>
+
+namespace td {
+
+struct NetQueryDebug {
+ double start_timestamp_ = 0;
+ int64 my_id_ = 0;
+ int32 resend_count_ = 0;
+ string state_ = "empty";
+ double state_timestamp_ = 0;
+ int32 state_change_count_ = 0;
+ int32 send_failed_count_ = 0;
+ int ack_state_ = 0;
+ bool unknown_state_ = false;
+};
+
+class NetQueryStats {
+ public:
+ NetQueryCounter register_query(TsListNode<NetQueryDebug> *query) {
+ if (use_list_.load(std::memory_order_relaxed)) {
+ list_.put(query);
+ }
+ return NetQueryCounter(&count_);
+ }
+
+ uint64 get_count() const;
+
+ void dump_pending_network_queries();
+
+ private:
+ NetQueryCounter::Counter count_{0};
+ std::atomic<bool> use_list_{true};
+ TsList<NetQueryDebug> list_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.cpp
index ff080db611..7fbb93149b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.cpp
@@ -1,26 +1,28 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/net/NetStatsManager.h"
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/StateManager.h"
+#include "td/telegram/TdDb.h"
+#include "td/telegram/Version.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/tl_helpers.h"
namespace td {
-template <class T>
-static void store(const NetStatsData &net_stats, T &storer) {
+template <class StorerT>
+static void store(const NetStatsData &net_stats, StorerT &storer) {
using ::td::store;
store(net_stats.read_size, storer);
store(net_stats.write_size, storer);
@@ -28,8 +30,8 @@ static void store(const NetStatsData &net_stats, T &storer) {
store(net_stats.duration, storer);
}
-template <class T>
-static void parse(NetStatsData &net_stats, T &parser) {
+template <class ParserT>
+static void parse(NetStatsData &net_stats, ParserT &parser) {
using ::td::parse;
parse(net_stats.read_size, parser);
parse(net_stats.write_size, parser);
@@ -41,8 +43,8 @@ static void parse(NetStatsData &net_stats, T &parser) {
}
void NetStatsManager::init() {
- CHECK(!empty());
- class NetStatsInternalCallback : public NetStats::Callback {
+ LOG_CHECK(!empty()) << G()->close_flag();
+ class NetStatsInternalCallback final : public NetStats::Callback {
public:
NetStatsInternalCallback(ActorId<NetStatsManager> parent, size_t id) : parent_(std::move(parent)), id_(id) {
}
@@ -50,14 +52,17 @@ void NetStatsManager::init() {
private:
ActorId<NetStatsManager> parent_;
size_t id_;
- void on_stats_updated() override {
+ void on_stats_updated() final {
send_closure(parent_, &NetStatsManager::on_stats_updated, id_);
}
};
- for_each_stat([&](NetStatsInfo &stat, size_t id, CSlice name, FileType) {
+ for_each_stat([&](NetStatsInfo &stat, size_t id, CSlice name, FileType file_type) {
+ auto main_file_type = get_main_file_type(file_type);
+ id += static_cast<size_t>(main_file_type) - static_cast<size_t>(file_type);
+
stat.key = "net_stats_" + name.str();
- stat.stats.set_callback(std::make_unique<NetStatsInternalCallback>(actor_id(this), id));
+ stat.stats.set_callback(make_unique<NetStatsInternalCallback>(actor_id(this), id));
});
}
@@ -74,12 +79,12 @@ void NetStatsManager::get_network_stats(bool current, Promise<NetworkStats> prom
NetStatsData total_files;
for_each_stat([&](NetStatsInfo &info, size_t id, CSlice name, FileType file_type) {
- auto type_stats = info.stats_by_type[net_type_i];
+ const auto &type_stats = info.stats_by_type[net_type_i];
auto stats = current ? type_stats.mem_stats : type_stats.mem_stats + type_stats.db_stats;
if (id == 0) {
} else if (id == 1) {
total = stats;
- } else if (id == call_net_stats_id_) {
+ } else if (id == CALL_NET_STATS_ID) {
} else if (file_type != FileType::None) {
total_files = total_files + stats;
}
@@ -90,7 +95,7 @@ void NetStatsManager::get_network_stats(bool current, Promise<NetworkStats> prom
if (id == 1) {
return;
}
- auto type_stats = info.stats_by_type[net_type_i];
+ const auto &type_stats = info.stats_by_type[net_type_i];
auto stats = current ? type_stats.mem_stats : type_stats.mem_stats + type_stats.db_stats;
NetworkStatsEntry entry;
@@ -102,22 +107,26 @@ void NetStatsManager::get_network_stats(bool current, Promise<NetworkStats> prom
entry.duration = stats.duration;
if (id == 0) {
result.entries.push_back(std::move(entry));
- } else if (id == call_net_stats_id_) {
+ } else if (id == CALL_NET_STATS_ID) {
entry.is_call = true;
result.entries.push_back(std::move(entry));
} else if (file_type != FileType::None) {
+ if (get_main_file_type(file_type) != file_type) {
+ return;
+ }
+
if (total_files.read_size != 0) {
entry.rx = static_cast<int64>(static_cast<double>(total.read_size) *
(static_cast<double>(entry.rx) / static_cast<double>(total_files.read_size)));
} else {
- // entry.rx += total.read_size / file_type_size;
+ // entry.rx += total.read_size / MAX_FILE_TYPE;
}
if (total_files.write_size != 0) {
entry.tx = static_cast<int64>(static_cast<double>(total.write_size) *
(static_cast<double>(entry.tx) / static_cast<double>(total_files.write_size)));
} else {
- // entry.tx += total.write_size / file_type_size;
+ // entry.tx += total.write_size / MAX_FILE_TYPE;
}
check.read_size += entry.rx;
check.write_size += entry.tx;
@@ -158,8 +167,8 @@ void NetStatsManager::add_network_stats(const NetworkStatsEntry &entry) {
return add_network_stats_impl(common_net_stats_, entry);
}
add_network_stats_impl(media_net_stats_, entry);
- size_t file_type_n = static_cast<size_t>(entry.file_type);
- CHECK(file_type_n < file_type_size);
+ auto file_type_n = static_cast<size_t>(entry.file_type);
+ CHECK(file_type_n < static_cast<size_t>(MAX_FILE_TYPE));
add_network_stats_impl(files_stats_[file_type_n], entry);
}
@@ -181,7 +190,11 @@ void NetStatsManager::add_network_stats_impl(NetStatsInfo &info, const NetworkSt
}
void NetStatsManager::start_up() {
- for_each_stat([&](NetStatsInfo &info, size_t id, CSlice name, FileType) {
+ for_each_stat([&](NetStatsInfo &info, size_t id, CSlice name, FileType file_type) {
+ if (get_main_file_type(file_type) != file_type) {
+ return;
+ }
+
for (size_t net_type_i = 0; net_type_i < net_type_size(); net_type_i++) {
auto net_type = NetType(net_type_i);
auto key = PSTRING() << info.key << "#" << net_type_string(net_type);
@@ -199,9 +212,13 @@ void NetStatsManager::start_up() {
auto since_str = G()->td_db()->get_binlog_pmc()->get("net_stats_since");
if (!since_str.empty()) {
auto since = to_integer<int32>(since_str);
+ auto authorization_date = G()->get_option_integer("authorization_date");
if (unix_time < since) {
since_total_ = unix_time;
G()->td_db()->get_binlog_pmc()->set("net_stats_since", to_string(since_total_));
+ } else if (since < authorization_date - 3600) {
+ since_total_ = narrow_cast<int32>(authorization_date);
+ G()->td_db()->get_binlog_pmc()->set("net_stats_since", to_string(since_total_));
} else {
since_total_ = since;
}
@@ -211,12 +228,12 @@ void NetStatsManager::start_up() {
G()->td_db()->get_binlog_pmc()->set("net_stats_since", to_string(since_total_));
}
- class NetCallback : public StateManager::Callback {
+ class NetCallback final : public StateManager::Callback {
public:
explicit NetCallback(ActorId<NetStatsManager> net_stats_manager)
: net_stats_manager_(std::move(net_stats_manager)) {
}
- bool on_network(NetType network_type, uint32 network_generation) override {
+ bool on_network(NetType network_type, uint32 network_generation) final {
send_closure(net_stats_manager_, &NetStatsManager::on_net_type_updated, network_type);
return net_stats_manager_.is_alive();
}
@@ -236,7 +253,14 @@ std::shared_ptr<NetStatsCallback> NetStatsManager::get_media_stats_callback() co
}
std::vector<std::shared_ptr<NetStatsCallback>> NetStatsManager::get_file_stats_callbacks() const {
- return transform(files_stats_, [](auto &stat) { return stat.stats.get_callback(); });
+ auto result = transform(files_stats_, [](auto &stat) { return stat.stats.get_callback(); });
+ for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
+ auto main_file_type = static_cast<int32>(get_main_file_type(static_cast<FileType>(i)));
+ if (main_file_type != i) {
+ result[i] = result[main_file_type];
+ }
+ }
+ return result;
}
void NetStatsManager::update(NetStatsInfo &info, bool force_save) {
@@ -265,6 +289,10 @@ void NetStatsManager::update(NetStatsInfo &info, bool force_save) {
}
void NetStatsManager::save_stats(NetStatsInfo &info, NetType net_type) {
+ if (G()->get_option_boolean("disable_persistent_network_statistics")) {
+ return;
+ }
+
auto net_type_i = static_cast<size_t>(net_type);
auto &type_stats = info.stats_by_type[net_type_i];
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.h b/protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.h
index e363f181cd..a794addb15 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetStatsManager.h
@@ -1,21 +1,20 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
-#include "td/telegram/td_api.h"
-
-#include "td/telegram/files/FileLocation.h"
+#include "td/telegram/files/FileType.h"
#include "td/telegram/net/NetType.h"
+#include "td/telegram/td_api.h"
#include "td/net/NetStats.h"
+#include "td/actor/actor.h"
+
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include <array>
@@ -34,12 +33,12 @@ struct NetworkStatsEntry {
int64 count{0};
double duration{0};
- tl_object_ptr<td_api::NetworkStatisticsEntry> as_td_api() const {
+ tl_object_ptr<td_api::NetworkStatisticsEntry> get_network_statistics_entry_object() const {
if (is_call) {
- return make_tl_object<td_api::networkStatisticsEntryCall>(::td::as_td_api(net_type), tx, rx, duration);
+ return make_tl_object<td_api::networkStatisticsEntryCall>(get_network_type_object(net_type), tx, rx, duration);
} else {
- return make_tl_object<td_api::networkStatisticsEntryFile>(::td::as_td_api(file_type), ::td::as_td_api(net_type),
- tx, rx);
+ return make_tl_object<td_api::networkStatisticsEntryFile>(get_file_type_object(file_type),
+ get_network_type_object(net_type), tx, rx);
}
}
};
@@ -48,20 +47,20 @@ struct NetworkStats {
int32 since = 0;
std::vector<NetworkStatsEntry> entries;
- auto as_td_api() const {
+ auto get_network_statistics_object() const {
auto result = make_tl_object<td_api::networkStatistics>();
result->since_date_ = since;
result->entries_.reserve(entries.size());
for (const auto &entry : entries) {
- if (entry.rx != 0 || entry.tx != 0) {
- result->entries_.push_back(entry.as_td_api());
+ if ((entry.rx != 0 || entry.tx != 0) && entry.file_type != FileType::SecureDecrypted) {
+ result->entries_.push_back(entry.get_network_statistics_entry_object());
}
}
return result;
}
};
-class NetStatsManager : public Actor {
+class NetStatsManager final : public Actor {
public:
explicit NetStatsManager(ActorShared<> parent) : parent_(std::move(parent)) {
}
@@ -83,12 +82,12 @@ class NetStatsManager : public Actor {
static constexpr size_t net_type_size() {
return static_cast<size_t>(NetType::Size);
}
- // TODO constexpr
+
static CSlice net_type_string(NetType type) {
switch (type) {
case NetType::Other:
return CSlice("other");
- case NetType::Wifi:
+ case NetType::WiFi:
return CSlice("wifi");
case NetType::Mobile:
return CSlice("mobile");
@@ -117,31 +116,34 @@ class NetStatsManager : public Actor {
int32 since_current_{0};
NetStatsInfo common_net_stats_;
NetStatsInfo media_net_stats_;
- std::array<NetStatsInfo, file_type_size> files_stats_;
+ std::array<NetStatsInfo, MAX_FILE_TYPE> files_stats_;
NetStatsInfo call_net_stats_;
- static constexpr int32 call_net_stats_id_{file_type_size + 2};
+ static constexpr int32 CALL_NET_STATS_ID{MAX_FILE_TYPE + 2};
template <class F>
void for_each_stat(F &&f) {
f(common_net_stats_, 0, CSlice("common"), FileType::None);
f(media_net_stats_, 1, CSlice("media"), FileType::None);
- for (size_t file_type_i = 0; file_type_i < file_type_size; file_type_i++) {
+ for (int32 file_type_i = 0; file_type_i < MAX_FILE_TYPE; file_type_i++) {
auto &stat = files_stats_[file_type_i];
- f(stat, file_type_i + 2, CSlice(file_type_name[file_type_i]), FileType(file_type_i));
+ auto file_type = static_cast<FileType>(file_type_i);
+ f(stat, file_type_i + 2, get_file_type_name(file_type), file_type);
}
- f(call_net_stats_, call_net_stats_id_, CSlice("calls"), FileType::None);
+ f(call_net_stats_, CALL_NET_STATS_ID, CSlice("calls"), FileType::None);
}
- void add_network_stats_impl(NetStatsInfo &info, const NetworkStatsEntry &entry);
+ static void add_network_stats_impl(NetStatsInfo &info, const NetworkStatsEntry &entry);
- void start_up() override;
- void update(NetStatsInfo &info, bool force_save);
- void save_stats(NetStatsInfo &info, NetType net_type);
- void info_loop(NetStatsInfo &info);
+ void start_up() final;
+
+ static void update(NetStatsInfo &info, bool force_save);
+ static void save_stats(NetStatsInfo &info, NetType net_type);
+ static void info_loop(NetStatsInfo &info);
void on_stats_updated(size_t id);
void on_net_type_updated(NetType net_type);
};
+
} // namespace td
/*
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetType.h b/protocols/Telegram/tdlib/td/td/telegram/net/NetType.h
index e04eb1a98c..939a09d0fa 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetType.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/NetType.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,12 +8,13 @@
#include "td/telegram/td_api.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
namespace td {
-enum class NetType : int8 { Other, Wifi, Mobile, MobileRoaming, Size, None, Unknown };
-inline NetType from_td_api(tl_object_ptr<td_api::NetworkType> &net_type) {
+enum class NetType : int8 { Other, WiFi, Mobile, MobileRoaming, Size, None, Unknown };
+
+inline NetType get_net_type(const tl_object_ptr<td_api::NetworkType> &net_type) {
if (net_type == nullptr) {
return NetType::Other;
}
@@ -22,7 +23,7 @@ inline NetType from_td_api(tl_object_ptr<td_api::NetworkType> &net_type) {
case td_api::networkTypeOther::ID:
return NetType::Other;
case td_api::networkTypeWiFi::ID:
- return NetType::Wifi;
+ return NetType::WiFi;
case td_api::networkTypeMobile::ID:
return NetType::Mobile;
case td_api::networkTypeMobileRoaming::ID:
@@ -34,11 +35,11 @@ inline NetType from_td_api(tl_object_ptr<td_api::NetworkType> &net_type) {
}
}
-inline tl_object_ptr<td_api::NetworkType> as_td_api(NetType net_type) {
+inline tl_object_ptr<td_api::NetworkType> get_network_type_object(NetType net_type) {
switch (net_type) {
case NetType::Other:
return make_tl_object<td_api::networkTypeOther>();
- case NetType::Wifi:
+ case NetType::WiFi:
return make_tl_object<td_api::networkTypeWiFi>();
case NetType::Mobile:
return make_tl_object<td_api::networkTypeMobile>();
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/Proxy.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/Proxy.cpp
new file mode 100644
index 0000000000..f9da6e1f55
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/Proxy.cpp
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/net/Proxy.h"
+
+#include "td/telegram/td_api.h"
+
+namespace td {
+
+Result<Proxy> Proxy::create_proxy(string server, int port, td_api::ProxyType *proxy_type) {
+ if (proxy_type == nullptr) {
+ return Status::Error(400, "Proxy type must be non-empty");
+ }
+ if (server.empty()) {
+ return Status::Error(400, "Server name must be non-empty");
+ }
+ if (server.size() > 255) {
+ return Status::Error(400, "Server name is too long");
+ }
+ if (port <= 0 || port > 65535) {
+ return Status::Error(400, "Wrong port number");
+ }
+
+ switch (proxy_type->get_id()) {
+ case td_api::proxyTypeSocks5::ID: {
+ auto type = static_cast<td_api::proxyTypeSocks5 *>(proxy_type);
+ return Proxy::socks5(std::move(server), port, std::move(type->username_), std::move(type->password_));
+ }
+ case td_api::proxyTypeHttp::ID: {
+ auto type = static_cast<td_api::proxyTypeHttp *>(proxy_type);
+ if (type->http_only_) {
+ return Proxy::http_caching(std::move(server), port, std::move(type->username_), std::move(type->password_));
+ } else {
+ return Proxy::http_tcp(std::move(server), port, std::move(type->username_), std::move(type->password_));
+ }
+ }
+ case td_api::proxyTypeMtproto::ID: {
+ auto type = static_cast<td_api::proxyTypeMtproto *>(proxy_type);
+ TRY_RESULT(secret, mtproto::ProxySecret::from_link(type->secret_));
+ return Proxy::mtproto(std::move(server), port, std::move(secret));
+ }
+ default:
+ UNREACHABLE();
+ return Status::Error(400, "Wrong proxy type");
+ }
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Proxy &proxy) {
+ switch (proxy.type()) {
+ case Proxy::Type::Socks5:
+ return string_builder << "ProxySocks5 " << proxy.server() << ":" << proxy.port();
+ case Proxy::Type::HttpTcp:
+ return string_builder << "ProxyHttpTcp " << proxy.server() << ":" << proxy.port();
+ case Proxy::Type::HttpCaching:
+ return string_builder << "ProxyHttpCaching " << proxy.server() << ":" << proxy.port();
+ case Proxy::Type::Mtproto:
+ return string_builder << "ProxyMtproto " << proxy.server() << ":" << proxy.port() << "/"
+ << proxy.secret().get_encoded_secret();
+ case Proxy::Type::None:
+ return string_builder << "ProxyEmpty";
+ default:
+ UNREACHABLE();
+ return string_builder;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/Proxy.h b/protocols/Telegram/tdlib/td/td/telegram/net/Proxy.h
new file mode 100644
index 0000000000..d7790da100
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/Proxy.h
@@ -0,0 +1,163 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/mtproto/ProxySecret.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_helpers.h"
+
+namespace td {
+
+namespace td_api {
+class ProxyType;
+} // namespace td_api
+
+class Proxy {
+ public:
+ static Result<Proxy> create_proxy(string server, int port, td_api::ProxyType *proxy_type);
+
+ static Proxy socks5(string server, int32 port, string user, string password) {
+ Proxy proxy;
+ proxy.type_ = Type::Socks5;
+ proxy.server_ = std::move(server);
+ proxy.port_ = port;
+ proxy.user_ = std::move(user);
+ proxy.password_ = std::move(password);
+ return proxy;
+ }
+
+ static Proxy http_tcp(string server, int32 port, string user, string password) {
+ Proxy proxy;
+ proxy.type_ = Type::HttpTcp;
+ proxy.server_ = std::move(server);
+ proxy.port_ = port;
+ proxy.user_ = std::move(user);
+ proxy.password_ = std::move(password);
+ return proxy;
+ }
+
+ static Proxy http_caching(string server, int32 port, string user, string password) {
+ Proxy proxy;
+ proxy.type_ = Type::HttpCaching;
+ proxy.server_ = std::move(server);
+ proxy.port_ = port;
+ proxy.user_ = std::move(user);
+ proxy.password_ = std::move(password);
+ return proxy;
+ }
+
+ static Proxy mtproto(string server, int32 port, mtproto::ProxySecret secret) {
+ Proxy proxy;
+ proxy.type_ = Type::Mtproto;
+ proxy.server_ = std::move(server);
+ proxy.port_ = port;
+ proxy.secret_ = std::move(secret);
+ return proxy;
+ }
+
+ CSlice server() const {
+ return server_;
+ }
+
+ int32 port() const {
+ return port_;
+ }
+
+ CSlice user() const {
+ return user_;
+ }
+
+ CSlice password() const {
+ return password_;
+ }
+
+ const mtproto::ProxySecret &secret() const {
+ return secret_;
+ }
+
+ enum class Type : int32 { None, Socks5, Mtproto, HttpTcp, HttpCaching };
+ Type type() const {
+ return type_;
+ }
+
+ bool use_proxy() const {
+ return type() != Proxy::Type::None;
+ }
+ bool use_socks5_proxy() const {
+ return type() == Proxy::Type::Socks5;
+ }
+ bool use_mtproto_proxy() const {
+ return type() == Proxy::Type::Mtproto;
+ }
+ bool use_http_tcp_proxy() const {
+ return type() == Proxy::Type::HttpTcp;
+ }
+ bool use_http_caching_proxy() const {
+ return type() == Proxy::Type::HttpCaching;
+ }
+
+ template <class StorerT>
+ void store(StorerT &storer) const {
+ using td::store;
+ store(type_, storer);
+ if (type_ == Proxy::Type::Socks5 || type_ == Proxy::Type::HttpTcp || type_ == Proxy::Type::HttpCaching) {
+ store(server_, storer);
+ store(port_, storer);
+ store(user_, storer);
+ store(password_, storer);
+ } else if (type_ == Proxy::Type::Mtproto) {
+ store(server_, storer);
+ store(port_, storer);
+ store(secret_.get_encoded_secret(), storer);
+ } else {
+ CHECK(type_ == Proxy::Type::None);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &parser) {
+ using td::parse;
+ parse(type_, parser);
+ if (type_ == Proxy::Type::Socks5 || type_ == Proxy::Type::HttpTcp || type_ == Proxy::Type::HttpCaching) {
+ parse(server_, parser);
+ parse(port_, parser);
+ parse(user_, parser);
+ parse(password_, parser);
+ } else if (type_ == Proxy::Type::Mtproto) {
+ parse(server_, parser);
+ parse(port_, parser);
+ secret_ = mtproto::ProxySecret::from_link(parser.template fetch_string<Slice>(), true).move_as_ok();
+ } else {
+ CHECK(type_ == Proxy::Type::None);
+ }
+ }
+
+ private:
+ Type type_{Type::None};
+ string server_;
+ int32 port_ = 0;
+ string user_;
+ string password_;
+ mtproto::ProxySecret secret_;
+};
+
+inline bool operator==(const Proxy &lhs, const Proxy &rhs) {
+ return lhs.type() == rhs.type() && lhs.server() == rhs.server() && lhs.port() == rhs.port() &&
+ lhs.user() == rhs.user() && lhs.password() == rhs.password() && lhs.secret() == rhs.secret();
+}
+
+inline bool operator!=(const Proxy &lhs, const Proxy &rhs) {
+ return !(lhs == rhs);
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, const Proxy &proxy);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.cpp
index 44169c66dc..1d361b421e 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.cpp
@@ -1,145 +1,113 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/net/PublicRsaKeyShared.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include <algorithm>
namespace td {
-PublicRsaKeyShared::PublicRsaKeyShared(DcId dc_id) : dc_id_(dc_id) {
+PublicRsaKeyShared::PublicRsaKeyShared(DcId dc_id, bool is_test) : dc_id_(dc_id) {
if (!dc_id_.is_empty()) {
return;
}
auto add_pem = [this](CSlice pem) {
- auto r_rsa = RSA::from_pem(pem);
- CHECK(r_rsa.is_ok()) << r_rsa.error();
+ auto r_rsa = mtproto::RSA::from_pem_public_key(pem);
+ LOG_CHECK(r_rsa.is_ok()) << r_rsa.error() << " " << pem;
if (r_rsa.is_ok()) {
- this->add_rsa(r_rsa.move_as_ok());
+ add_rsa(r_rsa.move_as_ok());
}
};
- //old_key
- add_pem(
- "-----BEGIN RSA PUBLIC KEY-----\n"
- "MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\n"
- "lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS\n"
- "an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw\n"
- "Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n"
- "8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\n"
- "Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n"
- "-----END RSA PUBLIC KEY-----");
-
- // a35e0b92d00f9b61c351ce30526cb855649b12a35e01fe39b5b315e81b515779 key1.pub
- add_pem(
- "-----BEGIN RSA PUBLIC KEY-----\n"
- "MIIBCgKCAQEAruw2yP/BCcsJliRoW5eB\n"
- "VBVle9dtjJw+OYED160Wybum9SXtBBLXriwt4rROd9csv0t0OHCaTmRqBcQ0J8fx\n"
- "hN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvd\n"
- "l84Kd9ORYjDEAyFnEA7dD556OptgLQQ2e2iVNq8NZLYTzLp5YpOdO1doK+ttrltg\n"
- "gTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnSLj16yE5HvJQn0CNpRdENvRUXe6tBP78O\n"
- "39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wFXGF710w9lwCGNbmNxNYhtIkdqfsEcwR5\n"
- "JwIDAQAB\n"
- "-----END RSA PUBLIC KEY-----\n");
- // f1c346bd6de0c3365658e0740de42372e51262099d47ee097c3ff1e238ebf985 key2.pub
- add_pem(
- "-----BEGIN RSA PUBLIC KEY-----\n"
- "MIIBCgKCAQEAvfLHfYH2r9R70w8prHbl\n"
- "Wt/nDkh+XkgpflqQVcnAfSuTtO05lNPspQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOO\n"
- "KPi0OfJXoRVylFzAQG/j83u5K3kRLbae7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ\n"
- "3TDS2pQOCtovG4eDl9wacrXOJTG2990VjgnIKNA0UMoP+KF03qzryqIt3oTvZq03\n"
- "DyWdGK+AZjgBLaDKSnC6qD2cFY81UryRWOab8zKkWAnhw2kFpcqhI0jdV5QaSCEx\n"
- "vnsjVaX0Y1N0870931/5Jb9ICe4nweZ9kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV\n"
- "/wIDAQAB\n"
- "-----END RSA PUBLIC KEY-----\n");
-
- // 129e129a464a2b515f579fd568f5579e8a6ea2832a362b07f282a7c271acfead key3.pub
- add_pem(
- "-----BEGIN RSA PUBLIC KEY-----\n"
- "MIIBCgKCAQEAs/ditzm+mPND6xkhzwFI\n"
- "z6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGrzqTDHkO30R8VeRM/Kz2f4nR05GIFiITl\n"
- "4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+th6knSU0yLtNKuQVP6voMrnt9MV1X92L\n"
- "GZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvSUwwc+yi1/gGaybwlzZwqXYoPOhwMebzK\n"
- "Uk0xW14htcJrRrq+PXXQbRzTMynseCoPIoke0dtCodbA3qQxQovE16q9zz4Otv2k\n"
- "4j63cz53J+mhkVWAeWxVGI0lltJmWtEYK6er8VqqWot3nqmWMXogrgRLggv/Nbbo\n"
- "oQIDAQAB\n"
- "-----END RSA PUBLIC KEY-----\n");
+ if (is_test) {
+ add_pem(
+ "-----BEGIN RSA PUBLIC KEY-----\n"
+ "MIIBCgKCAQEAyMEdY1aR+sCR3ZSJrtztKTKqigvO/vBfqACJLZtS7QMgCGXJ6XIR\n"
+ "yy7mx66W0/sOFa7/1mAZtEoIokDP3ShoqF4fVNb6XeqgQfaUHd8wJpDWHcR2OFwv\n"
+ "plUUI1PLTktZ9uW2WE23b+ixNwJjJGwBDJPQEQFBE+vfmH0JP503wr5INS1poWg/\n"
+ "j25sIWeYPHYeOrFp/eXaqhISP6G+q2IeTaWTXpwZj4LzXq5YOpk4bYEQ6mvRq7D1\n"
+ "aHWfYmlEGepfaYR8Q0YqvvhYtMte3ITnuSJs171+GDqpdKcSwHnd6FudwGO4pcCO\n"
+ "j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB\n"
+ "-----END RSA PUBLIC KEY-----");
+ return;
+ }
- // f9e47d59fbe0fa338ac8c5085201a0dd58dfd88f44abb16756ee5e9d50d52949 key4.pub
add_pem(
"-----BEGIN RSA PUBLIC KEY-----\n"
- "MIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q0\n"
- "5shjg8/4p6047bn6/m8yPy1RBsvIyvuDuGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xb\n"
- "nfxL5BXHplJhMtADXKM9bWB11PU1Eioc3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA\n"
- "9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvifRLJbY08/Gp66KpQvy7g8w7VB8wlgePe\n"
- "xW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqePji9NP3tJUFQjcECqcm0yV7/2d0t/pbC\n"
- "m+ZH1sadZspQCEPPrtbkQBlvHb4OLiIWPGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6M\n"
- "AQIDAQAB\n"
- "-----END RSA PUBLIC KEY-----\n");
+ "MIIBCgKCAQEA6LszBcC1LGzyr992NzE0ieY+BSaOW622Aa9Bd4ZHLl+TuFQ4lo4g\n"
+ "5nKaMBwK/BIb9xUfg0Q29/2mgIR6Zr9krM7HjuIcCzFvDtr+L0GQjae9H0pRB2OO\n"
+ "62cECs5HKhT5DZ98K33vmWiLowc621dQuwKWSQKjWf50XYFw42h21P2KXUGyp2y/\n"
+ "+aEyZ+uVgLLQbRA1dEjSDZ2iGRy12Mk5gpYc397aYp438fsJoHIgJ2lgMv5h7WY9\n"
+ "t6N/byY9Nw9p21Og3AoXSL2q/2IJ1WRUhebgAdGVMlV1fkuOQoEzR7EdpqtQD9Cs\n"
+ "5+bfo3Nhmcyvk5ftB0WkJ9z6bNZ7yxrP8wIDAQAB\n"
+ "-----END RSA PUBLIC KEY-----");
}
-void PublicRsaKeyShared::add_rsa(RSA rsa) {
+void PublicRsaKeyShared::add_rsa(mtproto::RSA rsa) {
auto lock = rw_mutex_.lock_write();
auto fingerprint = rsa.get_fingerprint();
- auto *has_rsa = get_rsa_locked(fingerprint);
- if (has_rsa) {
+ if (get_rsa_key_unsafe(fingerprint) != nullptr) {
return;
}
- options_.push_back(RsaOption{fingerprint, std::move(rsa)});
+ keys_.push_back(RsaKey{std::move(rsa), fingerprint});
}
-Result<std::pair<RSA, int64>> PublicRsaKeyShared::get_rsa(const vector<int64> &fingerprints) {
+Result<mtproto::PublicRsaKeyInterface::RsaKey> PublicRsaKeyShared::get_rsa_key(const vector<int64> &fingerprints) {
auto lock = rw_mutex_.lock_read();
for (auto fingerprint : fingerprints) {
- auto *rsa = get_rsa_locked(fingerprint);
- if (rsa) {
- return std::make_pair(rsa->clone(), fingerprint);
+ auto *rsa_key = get_rsa_key_unsafe(fingerprint);
+ if (rsa_key != nullptr) {
+ return RsaKey{rsa_key->rsa.clone(), fingerprint};
}
}
- return Status::Error("Unknown fingerprints");
+ return Status::Error(PSLICE() << "Unknown fingerprints " << format::as_array(fingerprints));
}
void PublicRsaKeyShared::drop_keys() {
if (dc_id_.is_empty()) {
+ // not CDN
return;
}
auto lock = rw_mutex_.lock_write();
- options_.clear();
+ LOG(INFO) << "Drop " << keys_.size() << " keys for " << dc_id_;
+ keys_.clear();
+ notify();
}
bool PublicRsaKeyShared::has_keys() {
auto lock = rw_mutex_.lock_read();
- return !options_.empty();
+ return !keys_.empty();
}
-void PublicRsaKeyShared::add_listener(std::unique_ptr<Listener> listener) {
+void PublicRsaKeyShared::add_listener(unique_ptr<Listener> listener) {
if (listener->notify()) {
auto lock = rw_mutex_.lock_write();
listeners_.push_back(std::move(listener));
}
}
-RSA *PublicRsaKeyShared::get_rsa_locked(int64 fingerprint) {
- auto it = std::find_if(options_.begin(), options_.end(),
- [&](const auto &value) { return value.fingerprint == fingerprint; });
- if (it == options_.end()) {
+mtproto::PublicRsaKeyInterface::RsaKey *PublicRsaKeyShared::get_rsa_key_unsafe(int64 fingerprint) {
+ auto it = std::find_if(keys_.begin(), keys_.end(),
+ [fingerprint](const auto &value) { return value.fingerprint == fingerprint; });
+ if (it == keys_.end()) {
return nullptr;
}
- return &it->rsa;
+ return &*it;
}
void PublicRsaKeyShared::notify() {
- auto lock = rw_mutex_.lock_read();
- auto it = std::remove_if(listeners_.begin(), listeners_.end(), [&](auto &listener) { return !listener->notify(); });
- listeners_.erase(it, listeners_.end());
+ td::remove_if(listeners_, [&](auto &listener) { return !listener->notify(); });
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.h b/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.h
index 453bb7bda1..5533dcde62 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyShared.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,19 +8,17 @@
#include "td/telegram/net/DcId.h"
-#include "td/mtproto/crypto.h"
+#include "td/mtproto/RSA.h"
#include "td/utils/common.h"
#include "td/utils/port/RwMutex.h"
#include "td/utils/Status.h"
-#include <utility>
-
namespace td {
-class PublicRsaKeyShared : public PublicRsaKeyInterface {
+class PublicRsaKeyShared final : public mtproto::PublicRsaKeyInterface {
public:
- explicit PublicRsaKeyShared(DcId dc_id);
+ PublicRsaKeyShared(DcId dc_id, bool is_test);
class Listener {
public:
@@ -33,12 +31,12 @@ class PublicRsaKeyShared : public PublicRsaKeyInterface {
virtual bool notify() = 0;
};
- void add_rsa(RSA rsa);
- Result<std::pair<RSA, int64>> get_rsa(const vector<int64> &fingerprints) override;
- void drop_keys() override;
+ void add_rsa(mtproto::RSA rsa);
+ Result<RsaKey> get_rsa_key(const vector<int64> &fingerprints) final;
+ void drop_keys() final;
bool has_keys();
- void add_listener(std::unique_ptr<Listener> listener);
+ void add_listener(unique_ptr<Listener> listener);
DcId dc_id() const {
return dc_id_;
@@ -46,15 +44,11 @@ class PublicRsaKeyShared : public PublicRsaKeyInterface {
private:
DcId dc_id_;
- struct RsaOption {
- int64 fingerprint;
- RSA rsa;
- };
- std::vector<RsaOption> options_;
- std::vector<std::unique_ptr<Listener>> listeners_;
+ std::vector<RsaKey> keys_;
+ std::vector<unique_ptr<Listener>> listeners_;
RwMutex rw_mutex_;
- RSA *get_rsa_locked(int64 fingerprint);
+ RsaKey *get_rsa_key_unsafe(int64 fingerprint);
void notify();
};
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.cpp
index 5946fca4ca..a0991bfdf3 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,8 +7,12 @@
#include "td/telegram/net/PublicRsaKeyWatchdog.h"
#include "td/telegram/Global.h"
-
+#include "td/telegram/net/NetQueryCreator.h"
+#include "td/telegram/TdDb.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/Version.h"
+
+#include "td/mtproto/RSA.h"
#include "td/utils/logging.h"
#include "td/utils/Time.h"
@@ -19,11 +23,11 @@ PublicRsaKeyWatchdog::PublicRsaKeyWatchdog(ActorShared<> parent) : parent_(std::
}
void PublicRsaKeyWatchdog::add_public_rsa_key(std::shared_ptr<PublicRsaKeyShared> key) {
- class Listener : public PublicRsaKeyShared::Listener {
+ class Listener final : public PublicRsaKeyShared::Listener {
public:
explicit Listener(ActorId<PublicRsaKeyWatchdog> parent) : parent_(std::move(parent)) {
}
- bool notify() override {
+ bool notify() final {
send_event(parent_, Event::yield());
return parent_.is_alive();
}
@@ -32,7 +36,7 @@ void PublicRsaKeyWatchdog::add_public_rsa_key(std::shared_ptr<PublicRsaKeyShared
ActorId<PublicRsaKeyWatchdog> parent_;
};
- key->add_listener(std::make_unique<Listener>(actor_id(this)));
+ key->add_listener(make_unique<Listener>(actor_id(this)));
sync_key(key);
keys_.push_back(std::move(key));
loop();
@@ -43,14 +47,23 @@ void PublicRsaKeyWatchdog::start_up() {
flood_control_.add_limit(2, 60);
flood_control_.add_limit(3, 2 * 60);
- sync(BufferSlice(G()->td_db()->get_binlog_pmc()->get("cdn_config")));
+ string version = G()->td_db()->get_binlog_pmc()->get("cdn_config_version");
+ current_version_ = to_string(MTPROTO_LAYER);
+ if (version != current_version_) {
+ G()->td_db()->get_binlog_pmc()->erase("cdn_config" + version);
+ } else {
+ sync(BufferSlice(G()->td_db()->get_binlog_pmc()->get("cdn_config" + version)));
+ }
+ CHECK(keys_.empty());
}
void PublicRsaKeyWatchdog::loop() {
if (has_query_) {
return;
}
- if (Time::now_cached() < flood_control_.get_wakeup_at()) {
+ auto now = Time::now();
+ if (now < flood_control_.get_wakeup_at()) {
+ set_timeout_at(flood_control_.get_wakeup_at() + 0.01);
return;
}
bool ok = true;
@@ -62,40 +75,47 @@ void PublicRsaKeyWatchdog::loop() {
if (ok) {
return;
}
- flood_control_.add_event(static_cast<int32>(Time::now_cached()));
+ flood_control_.add_event(now);
has_query_ = true;
- G()->net_query_dispatcher().dispatch_with_callback(
- G()->net_query_creator().create(create_storer(telegram_api::help_getCdnConfig()), DcId::main(),
- NetQuery::Type::Common, NetQuery::AuthFlag::Off, NetQuery::GzipFlag::On,
- 60 * 60 * 24),
- actor_shared(this));
+ auto query = G()->net_query_creator().create(telegram_api::help_getCdnConfig());
+ query->total_timeout_limit_ = 60 * 60 * 24;
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this));
}
void PublicRsaKeyWatchdog::on_result(NetQueryPtr net_query) {
has_query_ = false;
yield();
if (net_query->is_error()) {
- LOG(ERROR) << "getCdnConfig error " << net_query->move_as_error();
+ LOG(ERROR) << "Receive error for GetCdnConfig: " << net_query->move_as_error();
+ loop();
return;
}
auto buf = net_query->move_as_ok();
- G()->td_db()->get_binlog_pmc()->set("cdn_config", buf.as_slice().str());
+ G()->td_db()->get_binlog_pmc()->set("cdn_config_version", current_version_);
+ G()->td_db()->get_binlog_pmc()->set("cdn_config" + current_version_, buf.as_slice().str());
sync(std::move(buf));
}
void PublicRsaKeyWatchdog::sync(BufferSlice cdn_config_serialized) {
if (cdn_config_serialized.empty()) {
+ loop();
return;
}
auto r_keys = fetch_result<telegram_api::help_getCdnConfig>(cdn_config_serialized);
if (r_keys.is_error()) {
LOG(WARNING) << "Failed to deserialize help_getCdnConfig (probably not a problem) " << r_keys.error();
+ loop();
return;
}
cdn_config_ = r_keys.move_as_ok();
- for (auto &key : keys_) {
- sync_key(key);
+ if (keys_.empty()) {
+ LOG(INFO) << "Load " << to_string(cdn_config_);
+ } else {
+ LOG(INFO) << "Receive " << to_string(cdn_config_);
+ for (auto &key : keys_) {
+ sync_key(key);
+ }
}
}
@@ -105,13 +125,15 @@ void PublicRsaKeyWatchdog::sync_key(std::shared_ptr<PublicRsaKeyShared> &key) {
}
for (auto &config_key : cdn_config_->public_keys_) {
if (key->dc_id().get_raw_id() == config_key->dc_id_) {
- auto r_rsa = RSA::from_pem(config_key->public_key_);
+ auto r_rsa = mtproto::RSA::from_pem_public_key(config_key->public_key_);
if (r_rsa.is_error()) {
LOG(ERROR) << r_rsa.error();
continue;
}
+ LOG(INFO) << "Add CDN " << key->dc_id() << " key with fingerprint " << r_rsa.ok().get_fingerprint();
key->add_rsa(r_rsa.move_as_ok());
}
}
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.h b/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.h
index ff6fa39424..3141c59ece 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/PublicRsaKeyWatchdog.h
@@ -1,17 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/telegram/net/PublicRsaKeyShared.h"
-
#include "td/telegram/net/NetActor.h"
#include "td/telegram/net/NetQueryCreator.h"
#include "td/telegram/net/NetQueryDispatcher.h"
-
+#include "td/telegram/net/PublicRsaKeyShared.h"
#include "td/telegram/telegram_api.h"
#include "td/actor/actor.h"
@@ -23,7 +21,8 @@
#include <memory>
namespace td {
-class PublicRsaKeyWatchdog : public NetActor {
+
+class PublicRsaKeyWatchdog final : public NetActor {
public:
explicit PublicRsaKeyWatchdog(ActorShared<> parent);
@@ -35,12 +34,14 @@ class PublicRsaKeyWatchdog : public NetActor {
tl_object_ptr<telegram_api::cdnConfig> cdn_config_;
FloodControlStrict flood_control_;
bool has_query_{false};
+ string current_version_;
- void start_up() override;
- void loop() override;
+ void start_up() final;
+ void loop() final;
- void on_result(NetQueryPtr net_query) override;
+ void on_result(NetQueryPtr net_query) final;
void sync(BufferSlice cdn_config_serialized);
void sync_key(std::shared_ptr<PublicRsaKeyShared> &key);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/Session.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/Session.cpp
index 25759a87d7..47e1887870 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/Session.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/Session.cpp
@@ -1,54 +1,131 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/net/Session.h"
-#include "td/telegram/telegram_api.h"
-
#include "td/telegram/DhCache.h"
#include "td/telegram/Global.h"
+#include "td/telegram/net/DcAuthManager.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/MtprotoHeader.h"
#include "td/telegram/net/NetQuery.h"
#include "td/telegram/net/NetQueryDispatcher.h"
+#include "td/telegram/net/NetType.h"
#include "td/telegram/StateManager.h"
+#include "td/telegram/telegram_api.h"
#include "td/telegram/UniqueId.h"
+#include "td/mtproto/DhCallback.h"
#include "td/mtproto/Handshake.h"
#include "td/mtproto/HandshakeActor.h"
#include "td/mtproto/RawConnection.h"
+#include "td/mtproto/RSA.h"
#include "td/mtproto/SessionConnection.h"
+#include "td/mtproto/TransportType.h"
+
+#include "td/actor/PromiseFuture.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/as.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/port/thread_local.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Span.h"
#include "td/utils/Time.h"
#include "td/utils/Timer.h"
#include "td/utils/tl_parsers.h"
+#include "td/utils/utf8.h"
+#include "td/utils/VectorQueue.h"
-#include <algorithm>
+#include <atomic>
+#include <memory>
#include <tuple>
#include <utility>
namespace td {
namespace detail {
-class GenAuthKeyActor : public Actor {
+
+class SemaphoreActor final : public Actor {
+ public:
+ explicit SemaphoreActor(size_t capacity) : capacity_(capacity) {
+ }
+
+ void execute(Promise<Promise<Unit>> promise) {
+ if (capacity_ == 0) {
+ pending_.push(std::move(promise));
+ } else {
+ start(std::move(promise));
+ }
+ }
+
+ private:
+ size_t capacity_;
+ VectorQueue<Promise<Promise<Unit>>> pending_;
+
+ void finish(Result<Unit>) {
+ capacity_++;
+ if (!pending_.empty()) {
+ start(pending_.pop());
+ }
+ }
+
+ void start(Promise<Promise<Unit>> promise) {
+ CHECK(capacity_ > 0);
+ capacity_--;
+ promise.set_value(promise_send_closure(actor_id(this), &SemaphoreActor::finish));
+ }
+};
+
+struct Semaphore {
+ explicit Semaphore(size_t capacity) {
+ semaphore_ = create_actor<SemaphoreActor>("Semaphore", capacity).release();
+ }
+
+ void execute(Promise<Promise<Unit>> promise) {
+ send_closure(semaphore_, &SemaphoreActor::execute, std::move(promise));
+ }
+
+ private:
+ ActorId<SemaphoreActor> semaphore_;
+};
+
+class GenAuthKeyActor final : public Actor {
public:
- GenAuthKeyActor(std::unique_ptr<mtproto::AuthKeyHandshake> handshake,
- std::unique_ptr<mtproto::AuthKeyHandshakeContext> context,
- Promise<std::unique_ptr<mtproto::RawConnection>> connection_promise,
- Promise<std::unique_ptr<mtproto::AuthKeyHandshake>> handshake_promise,
+ GenAuthKeyActor(Slice name, unique_ptr<mtproto::AuthKeyHandshake> handshake,
+ unique_ptr<mtproto::AuthKeyHandshakeContext> context,
+ Promise<unique_ptr<mtproto::RawConnection>> connection_promise,
+ Promise<unique_ptr<mtproto::AuthKeyHandshake>> handshake_promise,
std::shared_ptr<Session::Callback> callback)
- : handshake_(std::move(handshake))
+ : name_(name.str())
+ , handshake_(std::move(handshake))
, context_(std::move(context))
, connection_promise_(std::move(connection_promise))
, handshake_promise_(std::move(handshake_promise))
, callback_(std::move(callback)) {
+ if (actor_count_.fetch_add(1, std::memory_order_relaxed) == MIN_HIGH_LOAD_ACTOR_COUNT - 1) {
+ LOG(WARNING) << "Number of GenAuthKeyActor exceeded high-load threshold";
+ }
+ }
+ GenAuthKeyActor(const GenAuthKeyActor &) = delete;
+ GenAuthKeyActor &operator=(const GenAuthKeyActor &) = delete;
+ GenAuthKeyActor(GenAuthKeyActor &&) = delete;
+ GenAuthKeyActor &operator=(GenAuthKeyActor &&) = delete;
+ ~GenAuthKeyActor() final {
+ if (actor_count_.fetch_sub(1, std::memory_order_relaxed) == MIN_HIGH_LOAD_ACTOR_COUNT) {
+ LOG(WARNING) << "Number of GenAuthKeyActor became lower than high-load threshold";
+ }
+ }
+
+ static bool is_high_loaded() {
+ return actor_count_.load(std::memory_order_relaxed) >= MIN_HIGH_LOAD_ACTOR_COUNT;
}
void on_network(uint32 network_generation) {
@@ -58,85 +135,175 @@ class GenAuthKeyActor : public Actor {
}
private:
+ string name_;
uint32 network_generation_ = 0;
- std::unique_ptr<mtproto::AuthKeyHandshake> handshake_;
- std::unique_ptr<mtproto::AuthKeyHandshakeContext> context_;
- Promise<std::unique_ptr<mtproto::RawConnection>> connection_promise_;
- Promise<std::unique_ptr<mtproto::AuthKeyHandshake>> handshake_promise_;
+ unique_ptr<mtproto::AuthKeyHandshake> handshake_;
+ unique_ptr<mtproto::AuthKeyHandshakeContext> context_;
+ Promise<unique_ptr<mtproto::RawConnection>> connection_promise_;
+ Promise<unique_ptr<mtproto::AuthKeyHandshake>> handshake_promise_;
std::shared_ptr<Session::Callback> callback_;
+ CancellationTokenSource cancellation_token_source_;
ActorOwn<mtproto::HandshakeActor> child_;
+ Promise<Unit> finish_promise_;
+
+ static constexpr size_t MIN_HIGH_LOAD_ACTOR_COUNT = 100;
+ static std::atomic<size_t> actor_count_;
+
+ static TD_THREAD_LOCAL Semaphore *semaphore_;
+ Semaphore &get_handshake_semaphore() {
+ auto old_context = set_context(std::make_shared<ActorContext>());
+ auto old_tag = set_tag(string());
+ init_thread_local<Semaphore>(semaphore_, 50);
+ set_context(std::move(old_context));
+ set_tag(std::move(old_tag));
+ return *semaphore_;
+ }
- void start_up() override {
+ void start_up() final {
// Bug in Android clang and MSVC
// std::tuple<Result<int>> b(std::forward_as_tuple(Result<int>()));
+ get_handshake_semaphore().execute(promise_send_closure(actor_id(this), &GenAuthKeyActor::do_start_up));
+ }
- callback_->request_raw_connection(PromiseCreator::lambda(
- [actor_id = actor_id(this)](Result<std::unique_ptr<mtproto::RawConnection>> r_raw_connection) {
- send_closure(actor_id, &GenAuthKeyActor::on_connection, std::move(r_raw_connection), false);
- }));
+ void do_start_up(Result<Promise<Unit>> r_finish_promise) {
+ if (r_finish_promise.is_error()) {
+ LOG(ERROR) << "Unexpected error: " << r_finish_promise.error();
+ } else {
+ finish_promise_ = r_finish_promise.move_as_ok();
+ }
+ callback_->request_raw_connection(
+ nullptr, PromiseCreator::cancellable_lambda(
+ cancellation_token_source_.get_cancellation_token(),
+ [actor_id = actor_id(this)](Result<unique_ptr<mtproto::RawConnection>> r_raw_connection) {
+ send_closure(actor_id, &GenAuthKeyActor::on_connection, std::move(r_raw_connection), false);
+ }));
+ }
+
+ void hangup() final {
+ if (connection_promise_) {
+ connection_promise_.set_error(Status::Error(1, "Canceled"));
+ }
+ if (handshake_promise_) {
+ handshake_promise_.set_error(Status::Error(1, "Canceled"));
+ }
+ stop();
}
- void on_connection(Result<std::unique_ptr<mtproto::RawConnection>> r_raw_connection, bool dummy) {
+ void on_connection(Result<unique_ptr<mtproto::RawConnection>> r_raw_connection, bool dummy) {
if (r_raw_connection.is_error()) {
connection_promise_.set_error(r_raw_connection.move_as_error());
handshake_promise_.set_value(std::move(handshake_));
return;
}
+
auto raw_connection = r_raw_connection.move_as_ok();
- network_generation_ = raw_connection->extra_;
+ VLOG(dc) << "Receive raw connection " << raw_connection.get();
+ network_generation_ = raw_connection->extra().extra;
child_ = create_actor_on_scheduler<mtproto::HandshakeActor>(
- "HandshakeActor", G()->get_slow_net_scheduler_id(), std::move(handshake_), std::move(raw_connection),
- std::move(context_), 10, std::move(connection_promise_), std::move(handshake_promise_));
+ PSLICE() << name_ + "::HandshakeActor", G()->get_slow_net_scheduler_id(), std::move(handshake_),
+ std::move(raw_connection), std::move(context_), 10, std::move(connection_promise_),
+ std::move(handshake_promise_));
}
};
+
+std::atomic<size_t> GenAuthKeyActor::actor_count_;
+TD_THREAD_LOCAL Semaphore *GenAuthKeyActor::semaphore_{};
+
} // namespace detail
-Session::Session(unique_ptr<Callback> callback, std::shared_ptr<AuthDataShared> shared_auth_data, bool is_main,
- bool use_pfs, bool is_cdn, const mtproto::AuthKey &tmp_auth_key) {
- VLOG(dc) << "Start connection";
+void Session::PriorityQueue::push(NetQueryPtr query) {
+ auto priority = query->priority();
+ queries_[priority].push(std::move(query));
+}
+
+NetQueryPtr Session::PriorityQueue::pop() {
+ CHECK(!empty());
+ auto it = queries_.begin();
+ auto res = it->second.pop();
+ if (it->second.empty()) {
+ queries_.erase(it);
+ }
+ return res;
+}
+
+bool Session::PriorityQueue::empty() const {
+ return queries_.empty();
+}
+
+Session::Session(unique_ptr<Callback> callback, std::shared_ptr<AuthDataShared> shared_auth_data, int32 raw_dc_id,
+ int32 dc_id, bool is_main, bool use_pfs, bool is_cdn, bool need_destroy,
+ const mtproto::AuthKey &tmp_auth_key, const vector<mtproto::ServerSalt> &server_salts)
+ : raw_dc_id_(raw_dc_id), dc_id_(dc_id), is_main_(is_main), is_cdn_(is_cdn), need_destroy_(need_destroy) {
+ VLOG(dc) << "Start connection " << tag("need_destroy", need_destroy_);
+ if (need_destroy_) {
+ use_pfs = false;
+ CHECK(!is_cdn);
+ }
shared_auth_data_ = std::move(shared_auth_data);
auth_data_.set_use_pfs(use_pfs);
auth_data_.set_main_auth_key(shared_auth_data_->get_auth_key());
+ // auth_data_.break_main_auth_key();
auth_data_.set_server_time_difference(shared_auth_data_->get_server_time_difference());
auth_data_.set_future_salts(shared_auth_data_->get_future_salts(), Time::now());
if (use_pfs && !tmp_auth_key.empty()) {
auth_data_.set_tmp_auth_key(tmp_auth_key);
+ if (is_main_) {
+ registered_temp_auth_key_ = TempAuthKeyWatchdog::register_auth_key_id(auth_data_.get_tmp_auth_key().id());
+ }
+ auth_data_.set_future_salts(server_salts, Time::now());
}
uint64 session_id = 0;
- Random::secure_bytes(reinterpret_cast<uint8 *>(&session_id), sizeof(session_id));
- auth_data_.session_id_ = session_id;
- LOG(WARNING) << "Generate new session_id " << session_id << " for auth key " << auth_data_.get_auth_key().id();
+ do {
+ Random::secure_bytes(reinterpret_cast<uint8 *>(&session_id), sizeof(session_id));
+ } while (session_id == 0);
+ auth_data_.set_session_id(session_id);
+ use_pfs_ = use_pfs;
+ LOG(WARNING) << "Generate new session_id " << session_id << " for " << (use_pfs ? "temp " : "")
+ << (is_cdn ? "CDN " : "") << "auth key " << auth_data_.get_auth_key().id() << " for "
+ << (is_main_ ? "main " : "") << "DC" << dc_id;
callback_ = std::shared_ptr<Callback>(callback.release());
- main_connection_.connection_id = 0;
- long_poll_connection_.connection_id = 1;
+ main_connection_.connection_id_ = 0;
+ long_poll_connection_.connection_id_ = 1;
- is_main_ = is_main;
- is_cdn_ = is_cdn;
if (is_cdn) {
auth_data_.set_header(G()->mtproto_header().get_anonymous_header().str());
} else {
auth_data_.set_header(G()->mtproto_header().get_default_header().str());
}
last_activity_timestamp_ = Time::now();
+ last_success_timestamp_ = Time::now() - 366 * 86400;
+ last_bind_success_timestamp_ = Time::now() - 366 * 86400;
+}
+
+bool Session::is_high_loaded() {
+ return detail::GenAuthKeyActor::is_high_loaded();
+}
+
+bool Session::can_destroy_auth_key() const {
+ return need_destroy_;
}
void Session::start_up() {
- class StateCallback : public StateManager::Callback {
+ class StateCallback final : public StateManager::Callback {
public:
explicit StateCallback(ActorId<Session> session) : session_(std::move(session)) {
}
- bool on_network(NetType network_type, uint32 network_generation) override {
+ bool on_network(NetType network_type, uint32 network_generation) final {
send_closure(session_, &Session::on_network, network_type != NetType::None, network_generation);
return session_.is_alive();
}
- bool on_online(bool online_flag) override {
+ bool on_online(bool online_flag) final {
send_closure(session_, &Session::on_online, online_flag);
return session_.is_alive();
}
+ bool on_logging_out(bool logging_out_flag) final {
+ send_closure(session_, &Session::on_logging_out, logging_out_flag);
+ return session_.is_alive();
+ }
private:
ActorId<Session> session_;
@@ -171,37 +338,34 @@ void Session::on_online(bool online_flag) {
loop();
}
+void Session::on_logging_out(bool logging_out_flag) {
+ logging_out_flag_ = logging_out_flag;
+ connection_online_update(true);
+ loop();
+}
+
void Session::connection_online_update(bool force) {
- bool new_connection_online_flag =
- online_flag_ && (has_queries() || last_activity_timestamp_ + 10 > Time::now_cached() || is_main_);
+ bool new_connection_online_flag = (online_flag_ || logging_out_flag_) &&
+ (has_queries() || last_activity_timestamp_ + 10 > Time::now_cached() || is_main_);
if (connection_online_flag_ == new_connection_online_flag && !force) {
return;
}
connection_online_flag_ = new_connection_online_flag;
- LOG(INFO) << "Set connection_online " << connection_online_flag_;
- if (is_main_) {
- if (main_connection_.connection) {
- main_connection_.connection->set_online(connection_online_flag_);
- }
- if (long_poll_connection_.connection) {
- long_poll_connection_.connection->set_online(connection_online_flag_);
- }
- } else {
- // TODO: support online state in media connections.
- if (connection_online_flag_) {
- connection_close(&main_connection_);
- connection_close(&long_poll_connection_);
- }
- return;
+ VLOG(dc) << "Set connection_online " << connection_online_flag_;
+ if (main_connection_.connection_) {
+ main_connection_.connection_->set_online(connection_online_flag_, is_main_);
+ }
+ if (long_poll_connection_.connection_) {
+ long_poll_connection_.connection_->set_online(connection_online_flag_, is_main_);
}
}
void Session::send(NetQueryPtr &&query) {
last_activity_timestamp_ = Time::now();
- query->debug("Session: received from DataCenter");
- query->set_session_id(auth_data_.session_id_);
- VLOG(net_query) << "got query " << query;
+ // query->debug("Session: received from SessionProxy");
+ query->set_session_id(auth_data_.get_session_id());
+ VLOG(net_query) << "Got query " << query;
if (query->update_is_ready()) {
return_query(std::move(query));
return;
@@ -210,57 +374,126 @@ void Session::send(NetQueryPtr &&query) {
loop();
}
-void Session::on_result(NetQueryPtr query) {
- CHECK(UniqueId::extract_type(query->id()) == UniqueId::BindKey);
- if (last_bind_id_ != query->id()) {
- query->clear();
- return;
- }
+void Session::on_bind_result(NetQueryPtr query) {
+ LOG(INFO) << "Receive answer to BindKey: " << query;
+ being_binded_tmp_auth_key_id_ = 0;
+ last_bind_query_id_ = 0;
- LOG(INFO) << "ANSWER TO BindKey" << query;
Status status;
- tmp_auth_key_id_ = 0;
- last_bind_id_ = 0;
if (query->is_error()) {
status = std::move(query->error());
+ if (status.code() == 400 && status.message() == "ENCRYPTED_MESSAGE_INVALID") {
+ auto server_time = G()->server_time();
+ auto auth_key_creation_date = auth_data_.get_main_auth_key().created_at();
+ auto auth_key_age = server_time - auth_key_creation_date;
+ auto is_server_time_reliable = G()->is_server_time_reliable();
+ auto last_success_time = use_pfs_ ? last_bind_success_timestamp_ : last_success_timestamp_;
+ auto now = Time::now();
+ bool has_immunity =
+ !is_server_time_reliable || auth_key_age < 60 || (auth_key_age > 86400 && last_success_time > now - 86400);
+ auto debug = PSTRING() << ". Server time is " << server_time << ", auth key created at " << auth_key_creation_date
+ << ", is_server_time_reliable = " << is_server_time_reliable
+ << ", last_success_time = " << last_success_time << ", now = " << now;
+ if (!use_pfs_) {
+ if (has_immunity) {
+ LOG(WARNING) << "Do not drop main key, because it was created too recently" << debug;
+ } else {
+ LOG(WARNING) << "Drop main key because check with temporary key failed" << debug;
+ auth_data_.drop_main_auth_key();
+ on_auth_key_updated();
+ G()->log_out("Main authorization key is invalid");
+ }
+ } else {
+ if (has_immunity) {
+ LOG(WARNING) << "Do not validate main key, because it was created too recently" << debug;
+ } else {
+ need_check_main_key_ = true;
+ auth_data_.set_use_pfs(false);
+ LOG(WARNING) << "Got ENCRYPTED_MESSAGE_INVALID error, validate main key" << debug;
+ }
+ }
+ }
} else {
auto r_flag = fetch_result<telegram_api::auth_bindTempAuthKey>(query->ok());
if (r_flag.is_error()) {
status = r_flag.move_as_error();
- } else {
- auto flag = r_flag.move_as_ok();
- if (!flag) {
- status = Status::Error("Returned false");
- }
+ } else if (!r_flag.ok()) {
+ status = Status::Error("Returned false");
}
}
if (status.is_ok()) {
- LOG(INFO) << "BOUND!" << tag("tmp_id", auth_data_.get_tmp_auth_key().id());
+ LOG(INFO) << "Bound temp auth key " << auth_data_.get_tmp_auth_key().id();
auth_data_.on_bind();
+ last_bind_success_timestamp_ = Time::now();
on_tmp_auth_key_updated();
+ } else if (status.message() == "DispatchTtlError") {
+ LOG(INFO) << "Resend bind auth key " << auth_data_.get_tmp_auth_key().id() << " request after DispatchTtlError";
} else {
LOG(ERROR) << "BindKey failed: " << status;
+ connection_close(&main_connection_);
+ connection_close(&long_poll_connection_);
}
+
query->clear();
yield();
}
+void Session::on_check_key_result(NetQueryPtr query) {
+ LOG(INFO) << "Receive answer to GetNearestDc: " << query;
+ being_checked_main_auth_key_id_ = 0;
+ last_check_query_id_ = 0;
+
+ Status status;
+ if (query->is_error()) {
+ status = std::move(query->error());
+ } else {
+ auto r_flag = fetch_result<telegram_api::help_getNearestDc>(query->ok());
+ if (r_flag.is_error()) {
+ status = r_flag.move_as_error();
+ }
+ }
+ if (status.is_ok() || status.code() != -404) {
+ LOG(INFO) << "Check main key ok";
+ need_check_main_key_ = false;
+ auth_data_.set_use_pfs(true);
+ } else {
+ LOG(ERROR) << "Check main key failed: " << status;
+ connection_close(&main_connection_);
+ connection_close(&long_poll_connection_);
+ }
+
+ query->clear();
+ yield();
+}
+
+void Session::on_result(NetQueryPtr query) {
+ CHECK(UniqueId::extract_type(query->id()) == UniqueId::BindKey);
+ if (last_bind_query_id_ == query->id()) {
+ return on_bind_result(std::move(query));
+ }
+ if (last_check_query_id_ == query->id()) {
+ return on_check_key_result(std::move(query));
+ }
+ query->clear();
+}
+
void Session::return_query(NetQueryPtr &&query) {
last_activity_timestamp_ = Time::now();
query->set_session_id(0);
- G()->net_query_dispatcher().dispatch(std::move(query));
+ callback_->on_result(std::move(query));
}
void Session::flush_pending_invoke_after_queries() {
while (!pending_invoke_after_queries_.empty()) {
auto &query = pending_invoke_after_queries_.front();
- pending_queries_.push_back(std::move(query));
+ pending_queries_.push(std::move(query));
pending_invoke_after_queries_.pop_front();
}
}
void Session::close() {
+ LOG(INFO) << "Close session (external)";
close_flag_ = true;
connection_close(&main_connection_);
connection_close(&long_poll_connection_);
@@ -269,7 +502,7 @@ void Session::close() {
auto &query = it.second.query;
query->set_message_id(0);
query->cancel_slot_.clear_event();
- pending_queries_.push_back(std::move(query));
+ pending_queries_.push(std::move(query));
}
sent_queries_.clear();
sent_containers_.clear();
@@ -277,10 +510,9 @@ void Session::close() {
flush_pending_invoke_after_queries();
CHECK(sent_queries_.empty());
while (!pending_queries_.empty()) {
- auto &query = pending_queries_.front();
+ auto query = pending_queries_.pop();
query->set_error_resend();
return_query(std::move(query));
- pending_queries_.pop_front();
}
callback_->on_closed();
@@ -309,8 +541,8 @@ void Session::raw_event(const Event::Raw &event) {
return_query(std::move(query));
LOG(DEBUG) << "Drop answer " << tag("message_id", format::as_hex(message_id));
- if (main_connection_.state == ConnectionInfo::State::Ready) {
- main_connection_.connection->cancel_answer(message_id);
+ if (main_connection_.state_ == ConnectionInfo::State::Ready) {
+ main_connection_.connection_->cancel_answer(message_id);
} else {
to_cancel_.push_back(message_id);
}
@@ -320,9 +552,11 @@ void Session::raw_event(const Event::Raw &event) {
/** Connection::Callback **/
void Session::on_connected() {
if (is_main_) {
- connection_token_ = StateManager::connection(G()->state_manager());
+ connection_token_ =
+ mtproto::ConnectionManager::connection(static_cast<ActorId<mtproto::ConnectionManager>>(G()->state_manager()));
}
}
+
Status Session::on_pong() {
constexpr int MAX_QUERY_TIMEOUT = 60;
constexpr int MIN_CONNECTION_ACTIVE = 60;
@@ -353,15 +587,18 @@ Status Session::on_pong() {
}
return Status::OK();
}
+
void Session::on_auth_key_updated() {
shared_auth_data_->set_auth_key(auth_data_.get_main_auth_key());
}
+
void Session::on_tmp_auth_key_updated() {
callback_->on_tmp_auth_key_updated(auth_data_.get_tmp_auth_key());
}
void Session::on_server_salt_updated() {
if (auth_data_.use_pfs()) {
+ callback_->on_server_salt_updated(auth_data_.get_future_salts());
return;
}
shared_auth_data_->set_future_salts(auth_data_.get_future_salts());
@@ -371,19 +608,20 @@ void Session::on_server_time_difference_updated() {
shared_auth_data_->update_server_time_difference(auth_data_.get_server_time_difference());
}
-void Session::on_before_close() {
- unsubscribe_before_close(current_info_->connection->get_pollable());
-}
-
void Session::on_closed(Status status) {
if (!close_flag_ && is_main_) {
connection_token_.reset();
}
+ auto raw_connection = current_info_->connection_->move_as_raw_connection();
+ Scheduler::unsubscribe_before_close(raw_connection->get_poll_info().get_pollable_fd_ref());
+ raw_connection->close();
if (status.is_error()) {
- LOG(WARNING) << "on_closed: " << status << " " << current_info_->connection->get_name();
+ LOG(WARNING) << "Session connection with " << sent_queries_.size() << " pending requests was closed: " << status
+ << ' ' << current_info_->connection_->get_name();
} else {
- LOG(INFO) << "on_closed: " << status << " " << current_info_->connection->get_name();
+ LOG(INFO) << "Session connection with " << sent_queries_.size() << " pending requests was closed: " << status << ' '
+ << current_info_->connection_->get_name();
}
if (status.is_error() && status.code() == -404) {
@@ -393,16 +631,31 @@ void Session::on_closed(Status status) {
on_tmp_auth_key_updated();
yield();
} else if (is_cdn_) {
- LOG(WARNING) << "Invalidate cdn tmp_key";
+ LOG(WARNING) << "Invalidate CDN tmp_key";
auth_data_.drop_main_auth_key();
on_auth_key_updated();
- on_session_failed(std::move(status));
+ on_session_failed(status.clone());
+ } else if (need_destroy_) {
+ auth_data_.drop_main_auth_key();
+ on_auth_key_updated();
+ } else {
+ // log out if has error and or 1 minute is passed from start, or 1 minute has passed since auth_key creation
+ if (!use_pfs_) {
+ LOG(WARNING) << "Use PFS to check main key";
+ auth_data_.set_use_pfs(true);
+ } else if (need_check_main_key_) {
+ LOG(WARNING) << "Invalidate main key";
+ auth_data_.drop_main_auth_key();
+ on_auth_key_updated();
+ G()->log_out("Main PFS authorization key is invalid");
+ }
+ yield();
}
}
- // resend all queries without ack.
+ // resend all queries without ack
for (auto it = sent_queries_.begin(); it != sent_queries_.end();) {
- if (!it->second.ack && it->second.connection_id == current_info_->connection_id) {
+ if (!it->second.ack && it->second.connection_id == current_info_->connection_id_) {
// container vector leak otherwise
cleanup_container(it->first, &it->second);
@@ -412,37 +665,38 @@ void Session::on_closed(Status status) {
mark_as_known(it->first, &it->second);
auto &query = it->second.query;
- VLOG(net_query) << "resend query (on_disconnected, no ack) " << query;
+ VLOG(net_query) << "Resend query (on_disconnected, no ack) " << query;
query->set_message_id(0);
query->cancel_slot_.clear_event();
- query->set_error(Status::Error(500, "Session failed: " + status.message().str()),
- current_info_->connection->get_name().str());
+ query->set_error(Status::Error(500, PSLICE() << "Session failed: " << status.message()),
+ current_info_->connection_->get_name().str());
return_query(std::move(query));
it = sent_queries_.erase(it);
} else {
mark_as_unknown(it->first, &it->second);
- it++;
+ ++it;
}
} else {
++it;
}
}
- current_info_->connection.reset();
- current_info_->state = ConnectionInfo::State::Empty;
+ current_info_->connection_.reset();
+ current_info_->state_ = ConnectionInfo::State::Empty;
}
void Session::on_session_created(uint64 unique_id, uint64 first_id) {
// TODO: use unique_id
- // send updatesTooLong to force getDifference
- LOG(INFO) << "new_session_created " << unique_id << " " << first_id;
+ LOG(INFO) << "New session " << unique_id << " created with first message_id " << first_id;
+ if (!use_pfs_) {
+ last_success_timestamp_ = Time::now();
+ }
if (is_main_) {
- LOG(INFO) << "Sending updatesTooLong to force getDifference";
- telegram_api::updatesTooLong too_long_;
- auto storer = create_storer(too_long_);
- BufferSlice packet(storer.size());
- storer.store(packet.as_slice().ubegin());
- return_query(G()->net_query_creator().create_result(0, std::move(packet)));
+ LOG(DEBUG) << "Sending updatesTooLong to force getDifference";
+ BufferSlice packet(4);
+ as<int32>(packet.as_slice().begin()) = telegram_api::updatesTooLong::ID;
+ last_activity_timestamp_ = Time::now();
+ callback_->on_update(std::move(packet));
}
for (auto it = sent_queries_.begin(); it != sent_queries_.end();) {
@@ -453,7 +707,7 @@ void Session::on_session_created(uint64 unique_id, uint64 first_id) {
mark_as_known(it->first, &it->second);
auto &query = it->second.query;
- VLOG(net_query) << "resend query (on_session_created) " << query;
+ VLOG(net_query) << "Resend query (on_session_created) " << query;
query->set_message_id(0);
query->cancel_slot_.clear_event();
resend_query(std::move(query));
@@ -476,7 +730,9 @@ void Session::on_session_failed(Status status) {
}
void Session::on_container_sent(uint64 container_id, vector<uint64> msg_ids) {
- auto erase_from = std::remove_if(msg_ids.begin(), msg_ids.end(), [&](uint64 msg_id) {
+ CHECK(container_id != 0);
+
+ td::remove_if(msg_ids, [&](uint64 msg_id) {
auto it = sent_queries_.find(msg_id);
if (it == sent_queries_.end()) {
return true; // remove
@@ -484,7 +740,6 @@ void Session::on_container_sent(uint64 container_id, vector<uint64> msg_ids) {
it->second.container_id = container_id;
return false;
});
- msg_ids.erase(erase_from, msg_ids.end());
if (msg_ids.empty()) {
return;
}
@@ -495,14 +750,16 @@ void Session::on_container_sent(uint64 container_id, vector<uint64> msg_ids) {
void Session::on_message_ack(uint64 id) {
on_message_ack_impl(id, 1);
}
+
void Session::on_message_ack_impl(uint64 id, int32 type) {
auto cit = sent_containers_.find(id);
if (cit != sent_containers_.end()) {
auto container_info = std::move(cit->second);
+ sent_containers_.erase(cit);
+
for (auto message_id : container_info.message_ids) {
on_message_ack_impl_inner(message_id, type, true);
}
- sent_containers_.erase(cit);
return;
}
@@ -516,7 +773,10 @@ void Session::on_message_ack_impl_inner(uint64 id, int32 type, bool in_container
}
VLOG(net_query) << "Ack " << tag("msg_id", id) << it->second.query;
it->second.ack = true;
- it->second.query->debug_ack |= type;
+ {
+ auto lock = it->second.query->lock();
+ it->second.query->get_data_unsafe().ack_state_ |= type;
+ }
it->second.query->quick_ack_promise_.set_value(Unit());
if (!in_container) {
cleanup_container(id, &it->second);
@@ -539,6 +799,7 @@ void Session::dec_container(uint64 message_id, Query *query) {
sent_containers_.erase(it);
}
}
+
void Session::cleanup_container(uint64 message_id, Query *query) {
if (query->container_id == message_id) {
// message was sent without any container
@@ -551,7 +812,10 @@ void Session::cleanup_container(uint64 message_id, Query *query) {
}
void Session::mark_as_known(uint64 id, Query *query) {
- query->query->debug_unknown = false;
+ {
+ auto lock = query->query->lock();
+ query->query->get_data_unsafe().unknown_state_ = false;
+ }
if (!query->unknown) {
return;
}
@@ -564,53 +828,70 @@ void Session::mark_as_known(uint64 id, Query *query) {
}
void Session::mark_as_unknown(uint64 id, Query *query) {
- query->query->debug_unknown = true;
+ {
+ auto lock = query->query->lock();
+ query->query->get_data_unsafe().unknown_state_ = true;
+ }
if (query->unknown) {
return;
}
VLOG(net_query) << "Mark as unknown " << tag("msg_id", id) << query->query;
query->unknown = true;
+ CHECK(id != 0);
unknown_queries_.insert(id);
}
+Status Session::on_update(BufferSlice packet) {
+ if (is_cdn_) {
+ return Status::Error("Receive at update from CDN connection");
+ }
+
+ if (!use_pfs_) {
+ last_success_timestamp_ = Time::now();
+ }
+ last_activity_timestamp_ = Time::now();
+ callback_->on_update(std::move(packet));
+ return Status::OK();
+}
+
Status Session::on_message_result_ok(uint64 id, BufferSlice packet, size_t original_size) {
- // Steal authorization information.
- // It is a dirty hack, yep.
+ last_success_timestamp_ = Time::now();
+
TlParser parser(packet.as_slice());
int32 ID = parser.fetch_int();
- if (!parser.get_error()) {
- if (ID == telegram_api::auth_authorization::ID) {
- LOG(INFO) << "GOT AUTHORIZATION!";
- auth_data_.set_auth_flag(true);
- shared_auth_data_->set_auth_key(auth_data_.get_main_auth_key());
- }
- }
- if (id == 0) {
- if (is_cdn_) {
- return Status::Error("Got update from CDN connection");
- }
- return_query(G()->net_query_creator().create_result(0, std::move(packet)));
- return Status::OK();
- }
auto it = sent_queries_.find(id);
if (it == sent_queries_.end()) {
- LOG(DEBUG) << "DROP result to " << tag("request_id", format::as_hex(id)) << tag("tl", format::as_hex(ID));
+ LOG(DEBUG) << "Drop result to " << tag("request_id", format::as_hex(id)) << tag("original_size", original_size)
+ << tag("tl", format::as_hex(ID));
- if (packet.size() > 16 * 1024) {
- dropped_size_ += packet.size();
+ if (original_size > 16 * 1024) {
+ dropped_size_ += original_size;
if (dropped_size_ > (256 * 1024)) {
auto dropped_size = dropped_size_;
dropped_size_ = 0;
return Status::Error(
- 2, PSLICE() << "Too much dropped packets " << tag("total_size", format::as_size(dropped_size)));
+ 2, PSLICE() << "Too many dropped packets " << tag("total_size", format::as_size(dropped_size)));
}
}
return Status::OK();
}
+
auth_data_.on_api_response();
Query *query_ptr = &it->second;
- VLOG(net_query) << "return query result " << query_ptr->query;
+ VLOG(net_query) << "Return query result " << query_ptr->query;
+
+ if (!parser.get_error()) {
+ // Steal authorization information.
+ // It is a dirty hack, yep.
+ if (ID == telegram_api::auth_authorization::ID || ID == telegram_api::auth_loginTokenSuccess::ID) {
+ if (query_ptr->query->tl_constructor() != telegram_api::auth_importAuthorization::ID) {
+ G()->net_query_dispatcher().set_main_dc_id(raw_dc_id_);
+ }
+ auth_data_.set_auth_flag(true);
+ shared_auth_data_->set_auth_key(auth_data_.get_main_auth_key());
+ }
+ }
cleanup_container(id, query_ptr);
mark_as_known(id, query_ptr);
@@ -624,43 +905,87 @@ Status Session::on_message_result_ok(uint64 id, BufferSlice packet, size_t origi
return Status::OK();
}
-void Session::on_message_result_error(uint64 id, int error_code, BufferSlice message) {
+void Session::on_message_result_error(uint64 id, int error_code, string message) {
+ if (!check_utf8(message)) {
+ LOG(ERROR) << "Receive invalid error message \"" << message << '"';
+ message = "INVALID_UTF8_ERROR_MESSAGE";
+ }
+ if (error_code <= -10000 || error_code >= 10000 || error_code == 0) {
+ LOG(ERROR) << "Receive invalid error code " << error_code << " with message \"" << message << '"';
+ error_code = 500;
+ }
+
// UNAUTHORIZED
- // TODO: some errors shouldn't cause loss of authorizations. Especially when PFS will be used
- if (error_code == 401 && message.as_slice() != CSlice("SESSION_PASSWORD_NEEDED")) {
- if (auth_data_.use_pfs() && message.as_slice() == CSlice("AUTH_KEY_PERM_EMPTY")) {
- LOG(ERROR) << "Receive AUTH_KEY_PERM_EMPTY in session " << auth_data_.session_id_ << " for auth key "
- << auth_data_.get_tmp_auth_key().id();
+ if (error_code == 401 && message != "SESSION_PASSWORD_NEEDED") {
+ if (auth_data_.use_pfs() && message == CSlice("AUTH_KEY_PERM_EMPTY")) {
+ LOG(INFO) << "Receive AUTH_KEY_PERM_EMPTY in session " << auth_data_.get_session_id() << " for auth key "
+ << auth_data_.get_tmp_auth_key().id();
+ // temporary key can be dropped any time
auth_data_.drop_tmp_auth_key();
on_tmp_auth_key_updated();
error_code = 500;
} else {
- LOG(WARNING) << "Lost authorization due to " << tag("msg", message.as_slice());
- auth_data_.set_auth_flag(false);
- shared_auth_data_->set_auth_key(auth_data_.get_main_auth_key());
- auth_lost_flag_ = true;
+ if (auth_data_.use_pfs() && !is_main_) {
+ // temporary key can be dropped any time
+ auth_data_.drop_tmp_auth_key();
+ on_tmp_auth_key_updated();
+ error_code = 500;
+ }
+
+ bool can_drop_main_auth_key_without_logging_out = is_cdn_;
+ if (!is_main_ && G()->net_query_dispatcher().get_main_dc_id().get_raw_id() != raw_dc_id_) {
+ can_drop_main_auth_key_without_logging_out = true;
+ }
+ LOG(INFO) << "Receive 401, " << message << " in session " << auth_data_.get_session_id() << " for auth key "
+ << auth_data_.get_auth_key().id() << ", PFS = " << auth_data_.use_pfs() << ", is_main = " << is_main_
+ << ", can_drop_main_auth_key_without_logging_out = " << can_drop_main_auth_key_without_logging_out;
+ if (can_drop_main_auth_key_without_logging_out) {
+ auth_data_.drop_main_auth_key();
+ on_auth_key_updated();
+ error_code = 500;
+ } else {
+ if (message == "USER_DEACTIVATED_BAN") {
+ LOG(PLAIN)
+ << "Your account was suspended for suspicious activity. If you think that this is a mistake, please "
+ "write to recover@telegram.org your phone number and other details to recover the account.";
+ }
+ auth_data_.set_auth_flag(false);
+ G()->log_out(message);
+ shared_auth_data_->set_auth_key(auth_data_.get_main_auth_key());
+ on_session_failed(Status::OK());
+ }
}
}
+ if (error_code == 400 && (message == "CONNECTION_NOT_INITED" || message == "CONNECTION_LAYER_INVALID")) {
+ LOG(WARNING) << "Receive " << message;
+ auth_data_.on_connection_not_inited();
+ error_code = 500;
+ }
if (id == 0) {
- LOG(WARNING) << "Session got error update";
+ LOG(ERROR) << "Received an error update";
return;
}
- LOG(DEBUG) << "Session::on_error " << tag("id", id) << tag("error_code", error_code)
- << tag("msg", message.as_slice());
+ if (error_code < 0) {
+ LOG(WARNING) << "Receive MTProto error " << error_code << " : " << message << " in session "
+ << auth_data_.get_session_id() << " for auth key " << auth_data_.get_auth_key().id() << " with "
+ << sent_queries_.size() << " pending requests";
+ } else {
+ LOG(DEBUG) << "Receive error " << error_code << " : " << message;
+ }
auto it = sent_queries_.find(id);
if (it == sent_queries_.end()) {
+ current_info_->connection_->force_ack();
return;
}
Query *query_ptr = &it->second;
- VLOG(net_query) << "return query error " << query_ptr->query;
+ VLOG(net_query) << "Return query error " << query_ptr->query;
cleanup_container(id, query_ptr);
mark_as_known(id, query_ptr);
- query_ptr->query->set_error(Status::Error(error_code, message.as_slice()),
- current_info_->connection->get_name().str());
+ query_ptr->query->set_error(Status::Error(error_code, message), current_info_->connection_->get_name().str());
query_ptr->query->set_message_id(0);
query_ptr->query->cancel_slot_.clear_event();
return_query(std::move(query_ptr->query));
@@ -669,7 +994,7 @@ void Session::on_message_result_error(uint64 id, int error_code, BufferSlice mes
}
void Session::on_message_failed_inner(uint64 id, bool in_container) {
- LOG(INFO) << "message inner failed " << id;
+ LOG(INFO) << "Message inner failed " << id;
auto it = sent_queries_.find(id);
if (it == sent_queries_.end()) {
return;
@@ -689,16 +1014,17 @@ void Session::on_message_failed_inner(uint64 id, bool in_container) {
}
void Session::on_message_failed(uint64 id, Status status) {
- LOG(INFO) << "on_message_failed " << tag("id", id) << tag("status", status);
+ LOG(INFO) << "Message failed: " << tag("id", id) << tag("status", status);
status.ignore();
auto cit = sent_containers_.find(id);
if (cit != sent_containers_.end()) {
auto container_info = std::move(cit->second);
+ sent_containers_.erase(cit);
+
for (auto message_id : container_info.message_ids) {
on_message_failed_inner(message_id, true);
}
- sent_containers_.erase(cit);
return;
}
@@ -729,7 +1055,7 @@ void Session::on_message_info(uint64 id, int32 state, uint64 answer_id, int32 an
case 2:
case 3:
// message not received by server
- return on_message_failed(id, Status::Error("Unknown message id"));
+ return on_message_failed(id, Status::Error("Unknown message identifier"));
case 0:
if (answer_id == 0) {
LOG(ERROR) << "Unexpected message_info.state == 0 " << tag("id", id) << tag("state", state)
@@ -752,10 +1078,16 @@ void Session::on_message_info(uint64 id, int32 state, uint64 answer_id, int32 an
<< tag("answer_size", answer_size) << it->second.query;
it->second.query->debug("Session: resend answer");
}
- current_info_->connection->resend_answer(answer_id);
+ current_info_->connection_->resend_answer(answer_id);
}
}
+Status Session::on_destroy_auth_key() {
+ auth_data_.drop_main_auth_key();
+ on_auth_key_updated();
+ return Status::Error("Close because of on_destroy_auth_key");
+}
+
bool Session::has_queries() const {
return !pending_invoke_after_queries_.empty() || !pending_queries_.empty() || !sent_queries_.empty();
}
@@ -773,178 +1105,204 @@ void Session::add_query(NetQueryPtr &&net_query) {
net_query->debug("Session: pending");
LOG_IF(FATAL, UniqueId::extract_type(net_query->id()) == UniqueId::BindKey)
<< "Add BindKey query inpo pending_queries_";
- pending_queries_.emplace_back(std::move(net_query));
+ pending_queries_.push(std::move(net_query));
}
void Session::connection_send_query(ConnectionInfo *info, NetQueryPtr &&net_query, uint64 message_id) {
- net_query->debug("Session: try send to mtproto::connection");
- CHECK(info->state == ConnectionInfo::State::Ready);
+ CHECK(info->state_ == ConnectionInfo::State::Ready);
current_info_ = info;
if (net_query->update_is_ready()) {
return return_query(std::move(net_query));
}
- uint64 invoke_after_id = 0;
- NetQueryRef invoke_after = net_query->invoke_after();
- if (!invoke_after.empty()) {
- invoke_after_id = invoke_after->message_id();
- if (invoke_after->session_id() != auth_data_.session_id_ || invoke_after_id == 0) {
+ Span<NetQueryRef> invoke_after = net_query->invoke_after();
+ std::vector<uint64> invoke_after_ids;
+ for (auto &ref : invoke_after) {
+ auto invoke_after_id = ref->message_id();
+ if (ref->session_id() != auth_data_.get_session_id() || invoke_after_id == 0) {
net_query->set_error_resend_invoke_after();
return return_query(std::move(net_query));
}
+ invoke_after_ids.push_back(invoke_after_id);
+ }
+ if (!invoke_after.empty()) {
if (!unknown_queries_.empty()) {
+ net_query->debug("Session: wait unknown query to invoke after it");
pending_invoke_after_queries_.push_back(std::move(net_query));
return;
}
}
- net_query->debug("Session: send to mtproto::connection");
- auto r_message_id =
- info->connection->send_query(net_query->query().clone(), net_query->gzip_flag() == NetQuery::GzipFlag::On,
- message_id, invoke_after_id, static_cast<bool>(net_query->quick_ack_promise_));
+ bool immediately_fail_query = false;
+ if (!immediately_fail_query) {
+ net_query->debug("Session: send to mtproto::connection");
+ auto r_message_id =
+ info->connection_->send_query(net_query->query().clone(), net_query->gzip_flag() == NetQuery::GzipFlag::On,
+ message_id, invoke_after_ids, static_cast<bool>(net_query->quick_ack_promise_));
- net_query->on_net_write(net_query->query().size());
+ net_query->on_net_write(net_query->query().size());
- if (r_message_id.is_error()) {
- LOG(FATAL) << "Failed to send query: " << r_message_id.error();
+ if (r_message_id.is_error()) {
+ LOG(FATAL) << "Failed to send query: " << r_message_id.error();
+ }
+ message_id = r_message_id.ok();
+ } else {
+ if (message_id == 0) {
+ message_id = auth_data_.next_message_id(Time::now_cached());
+ }
}
- message_id = r_message_id.ok();
- VLOG(net_query) << "send query to connection " << net_query << " [msg_id:" << format::as_hex(message_id) << "]"
- << tag("invoke_after", format::as_hex(invoke_after_id));
+ VLOG(net_query) << "Send query to connection " << net_query << " [msg_id:" << format::as_hex(message_id) << "]"
+ << tag("invoke_after",
+ transform(invoke_after_ids, [](auto id) { return PSTRING() << format::as_hex(id); }));
net_query->set_message_id(message_id);
net_query->cancel_slot_.clear_event();
- CHECK(sent_queries_.find(message_id) == sent_queries_.end()) << message_id;
- net_query->debug_unknown = false;
- net_query->debug_ack = 0;
+ {
+ auto lock = net_query->lock();
+ net_query->get_data_unsafe().unknown_state_ = false;
+ net_query->get_data_unsafe().ack_state_ = 0;
+ }
if (!net_query->cancel_slot_.empty()) {
- LOG(DEBUG) << "set event for net_query cancellation " << tag("message_id", format::as_hex(message_id));
+ LOG(DEBUG) << "Set event for net_query cancellation " << tag("message_id", format::as_hex(message_id));
net_query->cancel_slot_.set_event(EventCreator::raw(actor_id(), message_id));
}
auto status = sent_queries_.emplace(
- message_id, Query{message_id, std::move(net_query), main_connection_.connection_id, Time::now_cached()});
+ message_id, Query{message_id, std::move(net_query), main_connection_.connection_id_, Time::now_cached()});
+ LOG_CHECK(status.second) << message_id;
sent_queries_list_.put(status.first->second.get_list_node());
if (!status.second) {
- LOG(FATAL) << "Duplicate message_id oO [message_id=" << message_id << "]";
+ LOG(FATAL) << "Duplicate message_id [message_id = " << message_id << "]";
+ }
+ if (immediately_fail_query) {
+ on_message_result_error(message_id, 401, "TEST_ERROR");
}
}
void Session::connection_open(ConnectionInfo *info, bool ask_info) {
- CHECK(info->state == ConnectionInfo::State::Empty);
+ CHECK(info->state_ == ConnectionInfo::State::Empty);
if (!network_flag_) {
return;
}
if (!auth_data_.has_auth_key(Time::now_cached())) {
return;
}
- info->ask_info = ask_info;
+ info->ask_info_ = ask_info;
- info->state = ConnectionInfo::State::Connecting;
+ info->state_ = ConnectionInfo::State::Connecting;
+ info->cancellation_token_source_ = CancellationTokenSource{};
// NB: rely on constant location of info
- auto promise = PromiseCreator::lambda(
- [actor_id = actor_id(this), info = info](Result<std::unique_ptr<mtproto::RawConnection>> res) {
+ auto promise = PromiseCreator::cancellable_lambda(
+ info->cancellation_token_source_.get_cancellation_token(),
+ [actor_id = actor_id(this), info = info](Result<unique_ptr<mtproto::RawConnection>> res) {
send_closure(actor_id, &Session::connection_open_finish, info, std::move(res));
});
if (cached_connection_) {
- LOG(INFO) << "Reuse cached connection";
+ VLOG(dc) << "Reuse cached connection";
promise.set_value(std::move(cached_connection_));
} else {
- callback_->request_raw_connection(std::move(promise));
+ VLOG(dc) << "Request new connection";
+ unique_ptr<mtproto::AuthData> auth_data;
+ if (auth_data_.use_pfs() && auth_data_.has_auth_key(Time::now())) {
+ // auth_data = make_unique<mtproto::AuthData>(auth_data_);
+ }
+ callback_->request_raw_connection(std::move(auth_data), std::move(promise));
}
- info->wakeup_at = Time::now_cached() + 1000;
+ info->wakeup_at_ = Time::now_cached() + 1000;
}
-void Session::connection_add(std::unique_ptr<mtproto::RawConnection> raw_connection) {
- LOG(INFO) << "Cache connection";
+void Session::connection_add(unique_ptr<mtproto::RawConnection> raw_connection) {
+ VLOG(dc) << "Cache connection " << raw_connection.get();
cached_connection_ = std::move(raw_connection);
cached_connection_timestamp_ = Time::now();
}
void Session::connection_check_mode(ConnectionInfo *info) {
- if (close_flag_ || info->state != ConnectionInfo::State::Ready) {
+ if (close_flag_ || info->state_ != ConnectionInfo::State::Ready) {
return;
}
- if (info->mode != mode_) {
+ if (info->mode_ != mode_) {
LOG(WARNING) << "Close connection because of outdated mode_";
connection_close(info);
}
}
void Session::connection_open_finish(ConnectionInfo *info,
- Result<std::unique_ptr<mtproto::RawConnection>> r_raw_connection) {
- if (close_flag_ || info->state != ConnectionInfo::State::Connecting) {
+ Result<unique_ptr<mtproto::RawConnection>> r_raw_connection) {
+ if (close_flag_ || info->state_ != ConnectionInfo::State::Connecting) {
+ VLOG(dc) << "Ignore raw connection while closing";
return;
}
current_info_ = info;
- // Create new connection
if (r_raw_connection.is_error()) {
LOG(WARNING) << "Failed to open socket: " << r_raw_connection.error();
- info->state = ConnectionInfo::State::Empty;
+ info->state_ = ConnectionInfo::State::Empty;
yield();
return;
}
auto raw_connection = r_raw_connection.move_as_ok();
- if (raw_connection->extra_ != network_generation_) {
+ VLOG(dc) << "Receive raw connection " << raw_connection.get();
+ if (raw_connection->extra().extra != network_generation_) {
LOG(WARNING) << "Got RawConnection with old network_generation";
- info->state = ConnectionInfo::State::Empty;
+ info->state_ = ConnectionInfo::State::Empty;
yield();
return;
}
- Mode expected_mode = raw_connection->get_transport_type() == mtproto::TransportType::Http ? Mode::Http : Mode::Tcp;
+ Mode expected_mode =
+ raw_connection->get_transport_type().type == mtproto::TransportType::Http ? Mode::Http : Mode::Tcp;
if (mode_ != expected_mode) {
- LOG(INFO) << "Change mode " << mode_ << "--->" << expected_mode;
+ VLOG(dc) << "Change mode " << mode_ << "--->" << expected_mode;
mode_ = expected_mode;
- if (info->connection_id == 1 && mode_ != Mode::Http) {
- LOG(WARNING) << "Got tcp connection, for long poll connection";
+ if (info->connection_id_ == 1 && mode_ != Mode::Http) {
+ LOG(WARNING) << "Got tcp connection for long poll connection";
connection_add(std::move(raw_connection));
- info->state = ConnectionInfo::State::Empty;
+ info->state_ = ConnectionInfo::State::Empty;
yield();
return;
}
}
- // mtproto::TransportType transport_type = raw_connection->get_transport_type();
mtproto::SessionConnection::Mode mode;
Slice mode_name;
if (mode_ == Mode::Tcp) {
mode = mtproto::SessionConnection::Mode::Tcp;
- mode_name = "Tcp";
+ mode_name = Slice("Tcp");
} else {
- if (info->connection_id == 0) {
+ if (info->connection_id_ == 0) {
mode = mtproto::SessionConnection::Mode::Http;
- mode_name = "Http";
+ mode_name = Slice("Http");
} else {
mode = mtproto::SessionConnection::Mode::HttpLongPoll;
- mode_name = "HttpLongPoll";
+ mode_name = Slice("HttpLongPoll");
}
}
- auto name = PSTRING() << get_name() << "::Connect::" << mode_name << "::" << raw_connection->debug_str_;
- info->connection =
- make_unique<mtproto::SessionConnection>(mode, std::move(raw_connection), &auth_data_, DhCache::instance());
- if (is_main_) {
- info->connection->set_online(connection_online_flag_);
- }
- info->connection->set_name(name);
- info->connection->get_pollable().set_observer(this);
- subscribe(info->connection->get_pollable());
- info->mode = mode_;
- info->state = ConnectionInfo::State::Ready;
+ auto name = PSTRING() << get_name() << "::Connect::" << mode_name << "::" << raw_connection->extra().debug_str;
+ LOG(INFO) << "Finished to open connection " << name;
+ info->connection_ = make_unique<mtproto::SessionConnection>(mode, std::move(raw_connection), &auth_data_);
+ if (can_destroy_auth_key()) {
+ info->connection_->destroy_key();
+ }
+ info->connection_->set_online(connection_online_flag_, is_main_);
+ info->connection_->set_name(name);
+ Scheduler::subscribe(info->connection_->get_poll_info().extract_pollable_fd(this));
+ info->mode_ = mode_;
+ info->state_ = ConnectionInfo::State::Ready;
info->created_at_ = Time::now_cached();
- info->wakeup_at = Time::now_cached() + 10;
- if (unknown_queries_.size() > 1024) {
- on_session_failed(Status::Error("Too much queries with unknown state"));
+ info->wakeup_at_ = Time::now_cached() + 10;
+ if (unknown_queries_.size() > MAX_INFLIGHT_QUERIES) {
+ LOG(ERROR) << "With current limits `Too many queries with unknown state` error must be impossible";
+ on_session_failed(Status::Error("Too many queries with unknown state"));
return;
}
- if (info->ask_info) {
+ if (info->ask_info_) {
for (auto &id : unknown_queries_) {
- info->connection->get_state_info(id);
+ info->connection_->get_state_info(id);
}
for (auto &id : to_cancel_) {
- info->connection->cancel_answer(id);
+ info->connection_->cancel_answer(id);
}
to_cancel_.clear();
}
@@ -952,54 +1310,84 @@ void Session::connection_open_finish(ConnectionInfo *info,
}
void Session::connection_flush(ConnectionInfo *info) {
- CHECK(info->state == ConnectionInfo::State::Ready);
+ CHECK(info->state_ == ConnectionInfo::State::Ready);
current_info_ = info;
- info->wakeup_at = info->connection->flush(static_cast<mtproto::SessionConnection::Callback *>(this));
+ info->wakeup_at_ = info->connection_->flush(static_cast<mtproto::SessionConnection::Callback *>(this));
}
void Session::connection_close(ConnectionInfo *info) {
current_info_ = info;
- if (info->state != ConnectionInfo::State::Ready) {
+ if (info->state_ != ConnectionInfo::State::Ready) {
return;
}
- info->connection->force_close(static_cast<mtproto::SessionConnection::Callback *>(this));
- CHECK(info->state == ConnectionInfo::State::Empty);
+ info->connection_->force_close(static_cast<mtproto::SessionConnection::Callback *>(this));
+ CHECK(info->state_ == ConnectionInfo::State::Empty);
+}
+
+bool Session::need_send_check_main_key() const {
+ return need_check_main_key_ && auth_data_.get_main_auth_key().id() != being_checked_main_auth_key_id_;
+}
+
+bool Session::connection_send_check_main_key(ConnectionInfo *info) {
+ if (!need_check_main_key_) {
+ return false;
+ }
+ uint64 key_id = auth_data_.get_main_auth_key().id();
+ if (key_id == being_checked_main_auth_key_id_) {
+ return false;
+ }
+ CHECK(info->state_ != ConnectionInfo::State::Empty);
+ LOG(INFO) << "Check main key";
+ being_checked_main_auth_key_id_ = key_id;
+ last_check_query_id_ = UniqueId::next(UniqueId::BindKey);
+ NetQueryPtr query = G()->net_query_creator().create(last_check_query_id_, telegram_api::help_getNearestDc(), {},
+ DcId::main(), NetQuery::Type::Common, NetQuery::AuthFlag::On);
+ query->dispatch_ttl_ = 0;
+ query->set_callback(actor_shared(this));
+ connection_send_query(info, std::move(query));
+
+ return true;
}
-bool Session::need_send_bind_key() {
- return auth_data_.use_pfs() && !auth_data_.get_bind_flag() && auth_data_.get_tmp_auth_key().id() != tmp_auth_key_id_;
+bool Session::need_send_bind_key() const {
+ return auth_data_.use_pfs() && !auth_data_.get_bind_flag() &&
+ auth_data_.get_tmp_auth_key().id() != being_binded_tmp_auth_key_id_;
}
-bool Session::need_send_query() {
- return !close_flag_ && (!auth_data_.use_pfs() || auth_data_.get_bind_flag()) && !pending_queries_.empty();
+
+bool Session::need_send_query() const {
+ return !close_flag_ && !need_check_main_key_ && (!auth_data_.use_pfs() || auth_data_.get_bind_flag()) &&
+ !pending_queries_.empty() && !can_destroy_auth_key();
}
+
bool Session::connection_send_bind_key(ConnectionInfo *info) {
- CHECK(info->state != ConnectionInfo::State::Empty);
+ CHECK(info->state_ != ConnectionInfo::State::Empty);
uint64 key_id = auth_data_.get_tmp_auth_key().id();
- if (key_id == tmp_auth_key_id_) {
+ if (key_id == being_binded_tmp_auth_key_id_) {
return false;
}
- tmp_auth_key_id_ = key_id;
- last_bind_id_ = UniqueId::next(UniqueId::BindKey);
+ being_binded_tmp_auth_key_id_ = key_id;
+ last_bind_query_id_ = UniqueId::next(UniqueId::BindKey);
int64 perm_auth_key_id = auth_data_.get_main_auth_key().id();
int64 nonce = Random::secure_int64();
- int32 expire_at = static_cast<int32>(auth_data_.get_server_time(auth_data_.get_tmp_auth_key().expire_at()));
+ auto expires_at = static_cast<int32>(auth_data_.get_server_time(auth_data_.get_tmp_auth_key().expires_at()));
int64 message_id;
BufferSlice encrypted;
- std::tie(message_id, encrypted) = info->connection->encrypted_bind(perm_auth_key_id, nonce, expire_at);
+ std::tie(message_id, encrypted) = info->connection_->encrypted_bind(perm_auth_key_id, nonce, expires_at);
LOG(INFO) << "Bind key: " << tag("tmp", key_id) << tag("perm", static_cast<uint64>(perm_auth_key_id));
NetQueryPtr query = G()->net_query_creator().create(
- last_bind_id_,
- create_storer(telegram_api::auth_bindTempAuthKey(perm_auth_key_id, nonce, expire_at, std::move(encrypted))));
- query->dispatch_ttl = 0;
+ last_bind_query_id_,
+ telegram_api::auth_bindTempAuthKey(perm_auth_key_id, nonce, expires_at, std::move(encrypted)), {}, DcId::main(),
+ NetQuery::Type::Common, NetQuery::AuthFlag::On);
+ query->dispatch_ttl_ = 0;
query->set_callback(actor_shared(this));
connection_send_query(info, std::move(query), message_id);
return true;
}
-void Session::on_handshake_ready(Result<std::unique_ptr<mtproto::AuthKeyHandshake>> r_handshake) {
+void Session::on_handshake_ready(Result<unique_ptr<mtproto::AuthKeyHandshake>> r_handshake) {
auto handshake_id = narrow_cast<HandshakeId>(get_link_token() - 1);
bool is_main = handshake_id == MainAuthKeyHandshake;
auto &info = handshake_info_[handshake_id];
@@ -1011,30 +1399,30 @@ void Session::on_handshake_ready(Result<std::unique_ptr<mtproto::AuthKeyHandshak
} else {
auto handshake = r_handshake.move_as_ok();
if (!handshake->is_ready_for_finish()) {
- LOG(WARNING) << "Handshake is not yet ready";
+ LOG(INFO) << "Handshake is not yet ready";
info.handshake_ = std::move(handshake);
} else {
if (is_main) {
- auth_data_.set_main_auth_key(std::move(handshake->auth_key));
+ auth_data_.set_main_auth_key(handshake->release_auth_key());
on_auth_key_updated();
} else {
+ auth_data_.set_tmp_auth_key(handshake->release_auth_key());
if (is_main_) {
- registered_temp_auth_key_ = TempAuthKeyWatchdog::register_auth_key_id(handshake->auth_key.id());
+ registered_temp_auth_key_ = TempAuthKeyWatchdog::register_auth_key_id(auth_data_.get_tmp_auth_key().id());
}
- auth_data_.set_tmp_auth_key(std::move(handshake->auth_key));
on_tmp_auth_key_updated();
}
- LOG(WARNING) << "Update auth key in session_id " << auth_data_.session_id_ << " to "
+ LOG(WARNING) << "Update auth key in session_id " << auth_data_.get_session_id() << " to "
<< auth_data_.get_auth_key().id();
connection_close(&main_connection_);
connection_close(&long_poll_connection_);
// Salt of temporary key is different salt. Do not rewrite it
if (auth_data_.use_pfs() ^ is_main) {
- auth_data_.set_server_salt(handshake->server_salt, Time::now_cached());
+ auth_data_.set_server_salt(handshake->get_server_salt(), Time::now_cached());
on_server_salt_updated();
}
- if (auth_data_.update_server_time_difference(handshake->server_time_diff)) {
+ if (auth_data_.update_server_time_difference(handshake->get_server_time_diff())) {
on_server_time_difference_updated();
}
LOG(INFO) << "Got " << (is_main ? "main" : "tmp") << " auth key";
@@ -1053,44 +1441,52 @@ void Session::create_gen_auth_key_actor(HandshakeId handshake_id) {
info.flag_ = true;
bool is_main = handshake_id == MainAuthKeyHandshake;
if (!info.handshake_) {
- info.handshake_ = std::make_unique<mtproto::AuthKeyHandshake>(is_main && !is_cdn_ ? 0 : 24 * 60 * 60);
+ info.handshake_ = make_unique<mtproto::AuthKeyHandshake>(dc_id_, is_main && !is_cdn_ ? 0 : 24 * 60 * 60);
}
- class AuthKeyHandshakeContext : public mtproto::AuthKeyHandshakeContext {
+ class AuthKeyHandshakeContext final : public mtproto::AuthKeyHandshakeContext {
public:
- AuthKeyHandshakeContext(DhCallback *dh_callback, std::shared_ptr<PublicRsaKeyInterface> public_rsa_key)
+ AuthKeyHandshakeContext(mtproto::DhCallback *dh_callback,
+ std::shared_ptr<mtproto::PublicRsaKeyInterface> public_rsa_key)
: dh_callback_(dh_callback), public_rsa_key_(std::move(public_rsa_key)) {
}
- DhCallback *get_dh_callback() override {
+ mtproto::DhCallback *get_dh_callback() final {
return dh_callback_;
}
- PublicRsaKeyInterface *get_public_rsa_key_interface() override {
+ mtproto::PublicRsaKeyInterface *get_public_rsa_key_interface() final {
return public_rsa_key_.get();
}
private:
- DhCallback *dh_callback_;
- std::shared_ptr<PublicRsaKeyInterface> public_rsa_key_;
+ mtproto::DhCallback *dh_callback_;
+ std::shared_ptr<mtproto::PublicRsaKeyInterface> public_rsa_key_;
};
+
info.actor_ = create_actor<detail::GenAuthKeyActor>(
- "GenAuthKey", std::move(info.handshake_),
- std::make_unique<AuthKeyHandshakeContext>(DhCache::instance(), shared_auth_data_->public_rsa_key()),
- PromiseCreator::lambda([self = actor_id(this)](Result<std::unique_ptr<mtproto::RawConnection>> r_connection) {
- if (r_connection.is_error() && r_connection.error().code() != 1) {
- LOG(WARNING) << r_connection.error();
- return;
- }
- send_closure(self, &Session::connection_add, r_connection.move_as_ok());
- }),
+ PSLICE() << get_name() << "::GenAuthKey", get_name(), std::move(info.handshake_),
+ td::make_unique<AuthKeyHandshakeContext>(DhCache::instance(), shared_auth_data_->public_rsa_key()),
PromiseCreator::lambda(
- [self = actor_shared(this, handshake_id + 1), handshake_perf = PerfWarningTimer("handshake", 1000.1)](
- Result<std::unique_ptr<mtproto::AuthKeyHandshake>> handshake) mutable {
- // later is just to avoid lost hangup
- send_closure_later(std::move(self), &Session::on_handshake_ready, std::move(handshake));
+ [actor_id = actor_id(this), guard = callback_](Result<unique_ptr<mtproto::RawConnection>> r_connection) {
+ if (r_connection.is_error()) {
+ if (r_connection.error().code() != 1) {
+ LOG(WARNING) << "Failed to open connection: " << r_connection.error();
+ }
+ return;
+ }
+ send_closure(actor_id, &Session::connection_add, r_connection.move_as_ok());
}),
+ PromiseCreator::lambda([self = actor_shared(this, handshake_id + 1),
+ handshake_perf = PerfWarningTimer("handshake", 1000.1),
+ guard = callback_](Result<unique_ptr<mtproto::AuthKeyHandshake>> handshake) mutable {
+ // later is just to avoid lost hangup
+ send_closure_later(std::move(self), &Session::on_handshake_ready, std::move(handshake));
+ }),
callback_);
}
void Session::auth_loop() {
+ if (can_destroy_auth_key()) {
+ return;
+ }
if (auth_data_.need_main_auth_key()) {
create_gen_auth_key_actor(MainAuthKeyHandshake);
}
@@ -1108,7 +1504,8 @@ void Session::loop() {
if (cached_connection_timestamp_ < Time::now_cached() - 10) {
cached_connection_.reset();
}
- if (!is_main_ && !has_queries() && last_activity_timestamp_ < Time::now_cached() - ACTIVITY_TIMEOUT) {
+ if (!is_main_ && !has_queries() && !need_destroy_ &&
+ last_activity_timestamp_ < Time::now_cached() - ACTIVITY_TIMEOUT) {
on_session_failed(Status::OK());
}
@@ -1116,44 +1513,46 @@ void Session::loop() {
connection_online_update();
double wakeup_at = 0;
- main_connection_.wakeup_at = 0;
- long_poll_connection_.wakeup_at = 0;
+ main_connection_.wakeup_at_ = 0;
+ long_poll_connection_.wakeup_at_ = 0;
- auth_lost_flag_ = false;
// NB: order is crucial. First long_poll_connection, then main_connection
// Otherwise queries could be sent with big delay
connection_check_mode(&main_connection_);
connection_check_mode(&long_poll_connection_);
if (mode_ == Mode::Http) {
- if (long_poll_connection_.state == ConnectionInfo::State::Ready) {
+ if (long_poll_connection_.state_ == ConnectionInfo::State::Ready) {
connection_flush(&long_poll_connection_);
}
- if (!close_flag_ && long_poll_connection_.state == ConnectionInfo::State::Empty) {
+ if (!close_flag_ && long_poll_connection_.state_ == ConnectionInfo::State::Empty) {
connection_open(&long_poll_connection_);
}
- relax_timeout_at(&wakeup_at, long_poll_connection_.wakeup_at);
+ relax_timeout_at(&wakeup_at, long_poll_connection_.wakeup_at_);
}
- if (main_connection_.state == ConnectionInfo::State::Ready) {
+ if (main_connection_.state_ == ConnectionInfo::State::Ready) {
// do not send queries before we have key and e.t.c
// do not send queries before tmp_key is bound
bool need_flush = true;
- while (main_connection_.state == ConnectionInfo::State::Ready) {
+ while (main_connection_.state_ == ConnectionInfo::State::Ready) {
if (auth_data_.is_ready(Time::now_cached())) {
if (need_send_query()) {
- while (!pending_queries_.empty()) {
- auto &query = pending_queries_.front();
+ while (!pending_queries_.empty() && sent_queries_.size() < MAX_INFLIGHT_QUERIES) {
+ auto query = pending_queries_.pop();
connection_send_query(&main_connection_, std::move(query));
- pending_queries_.pop_front();
+ need_flush = true;
}
- need_flush = true;
}
if (need_send_bind_key()) {
// send auth.bindTempAuthKey
connection_send_bind_key(&main_connection_);
need_flush = true;
}
+ if (need_send_check_main_key()) {
+ connection_send_check_main_key(&main_connection_);
+ need_flush = true;
+ }
}
if (need_flush) {
connection_flush(&main_connection_);
@@ -1163,23 +1562,16 @@ void Session::loop() {
}
}
}
- if (!close_flag_ && main_connection_.state == ConnectionInfo::State::Empty) {
+ if (!close_flag_ && main_connection_.state_ == ConnectionInfo::State::Empty) {
connection_open(&main_connection_, true /*send ask_info*/);
}
- if (auth_lost_flag_) {
- connection_close(&main_connection_);
- connection_close(&long_poll_connection_);
- auth_lost_flag_ = false;
- relax_timeout_at(&wakeup_at, Time::now_cached() + 0.1);
- }
-
- relax_timeout_at(&wakeup_at, main_connection_.wakeup_at);
+ relax_timeout_at(&wakeup_at, main_connection_.wakeup_at_);
double wakeup_in = 0;
if (wakeup_at != 0) {
wakeup_in = wakeup_at - Time::now_cached();
- LOG(INFO) << "Wakeup After " << wakeup_in;
+ LOG(DEBUG) << "Wakeup after " << wakeup_in;
set_timeout_at(wakeup_at);
}
// TODO: write proper condition..
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/Session.h b/protocols/Telegram/tdlib/td/td/telegram/net/Session.h
index 93f69a27b4..cd4aae77ae 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/Session.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/Session.h
@@ -1,35 +1,39 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/telegram/net/AuthDataShared.h"
+#include "td/telegram/net/NetQuery.h"
+#include "td/telegram/net/TempAuthKeyWatchdog.h"
+
#include "td/mtproto/AuthData.h"
+#include "td/mtproto/AuthKey.h"
+#include "td/mtproto/ConnectionManager.h"
#include "td/mtproto/Handshake.h"
#include "td/mtproto/SessionConnection.h"
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
-#include "td/telegram/net/AuthDataShared.h"
-#include "td/telegram/net/NetQuery.h"
-#include "td/telegram/net/TempAuthKeyWatchdog.h"
-#include "td/telegram/StateManager.h"
#include "td/utils/buffer.h"
+#include "td/utils/CancellationToken.h"
#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashSet.h"
#include "td/utils/List.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
+#include "td/utils/VectorQueue.h"
#include <array>
#include <deque>
+#include <functional>
#include <map>
#include <memory>
-#include <unordered_map>
-#include <unordered_set>
#include <utility>
namespace td {
@@ -54,20 +58,26 @@ class Session final
virtual ~Callback() = default;
virtual void on_failed() = 0;
virtual void on_closed() = 0;
- virtual void request_raw_connection(Promise<std::unique_ptr<mtproto::RawConnection>>) = 0;
+ virtual void request_raw_connection(unique_ptr<mtproto::AuthData> auth_data,
+ Promise<unique_ptr<mtproto::RawConnection>>) = 0;
virtual void on_tmp_auth_key_updated(mtproto::AuthKey auth_key) = 0;
- // one still have to call close after on_closed
+ virtual void on_server_salt_updated(vector<mtproto::ServerSalt> server_salts) = 0;
+ virtual void on_update(BufferSlice &&update) = 0;
+ virtual void on_result(NetQueryPtr net_query) = 0;
};
- Session(unique_ptr<Callback> callback, std::shared_ptr<AuthDataShared> shared_auth_data, bool is_main, bool use_pfs,
- bool is_cdn, const mtproto::AuthKey &tmp_auth_key);
+ Session(unique_ptr<Callback> callback, std::shared_ptr<AuthDataShared> shared_auth_data, int32 raw_dc_id, int32 dc_id,
+ bool is_main, bool use_pfs, bool is_cdn, bool need_destroy, const mtproto::AuthKey &tmp_auth_key,
+ const vector<mtproto::ServerSalt> &server_salts);
+
void send(NetQueryPtr &&query);
- void on_network(bool network_flag, uint32 network_generation);
- void on_online(bool online_flag);
+
void close();
+ static bool is_high_loaded();
+
private:
- struct Query : private ListNode {
+ struct Query final : private ListNode {
uint64 container_id;
NetQueryPtr query;
@@ -88,107 +98,131 @@ class Session final
}
};
- // When connection is closed, mark all queries without ack as unknown
+ // When connection is closed, mark all queries without ack as unknown.
// Ask state of all unknown queries when new connection is created.
//
// Just re-ask answer_id each time we get information about it.
- // Thought mtproto::Connection must ensure delivery of such query
+ // Though mtproto::Connection must ensure delivery of such query.
- enum class Mode { Tcp, Http } mode_ = Mode::Tcp;
- bool is_main_;
+ int32 raw_dc_id_; // numerical datacenter ID, i.e. 2
+ int32 dc_id_; // unique datacenter ID, i.e. -10002
+ enum class Mode : int8 { Tcp, Http } mode_ = Mode::Tcp;
+ bool is_main_; // true only for the primary Session(s) to the main DC
bool is_cdn_;
+ bool need_destroy_;
bool was_on_network_ = false;
bool network_flag_ = false;
- uint32 network_generation_ = 0;
bool online_flag_ = false;
+ bool logging_out_flag_ = false;
bool connection_online_flag_ = false;
- uint64 tmp_auth_key_id_ = 0;
- uint64 last_bind_id_ = 0;
+ uint32 network_generation_ = 0;
+ uint64 being_binded_tmp_auth_key_id_ = 0;
+ uint64 being_checked_main_auth_key_id_ = 0;
+ uint64 last_bind_query_id_ = 0;
+ uint64 last_check_query_id_ = 0;
double last_activity_timestamp_ = 0;
+ double last_success_timestamp_ = 0; // time when auth_key and Session definitely was valid
+ double last_bind_success_timestamp_ = 0; // time when auth_key and Session definitely was valid and authorized
size_t dropped_size_ = 0;
- std::unordered_set<uint64> unknown_queries_;
- std::vector<int64> to_cancel_;
+ FlatHashSet<uint64> unknown_queries_;
+ vector<int64> to_cancel_;
- // Do not invalidate iterators of this two containers!
+ // Do not invalidate iterators of these two containers!
// TODO: better data structures
- std::deque<NetQueryPtr> pending_queries_;
+ struct PriorityQueue {
+ void push(NetQueryPtr query);
+ NetQueryPtr pop();
+ bool empty() const;
+
+ private:
+ std::map<int8, VectorQueue<NetQueryPtr>, std::greater<>> queries_;
+ };
+ PriorityQueue pending_queries_;
std::map<uint64, Query> sent_queries_;
std::deque<NetQueryPtr> pending_invoke_after_queries_;
ListNode sent_queries_list_;
struct ConnectionInfo {
- int8 connection_id;
- Mode mode;
- enum class State { Empty, Connecting, Ready } state = State::Empty;
- mtproto::AuthKeyHandshake handshake;
- mtproto::AuthKeyHandshake tmp_handshake;
- unique_ptr<mtproto::SessionConnection> connection;
- bool ask_info;
- double wakeup_at = 0;
+ int8 connection_id_ = 0;
+ Mode mode_ = Mode::Tcp;
+ enum class State : int8 { Empty, Connecting, Ready } state_ = State::Empty;
+ CancellationTokenSource cancellation_token_source_;
+ unique_ptr<mtproto::SessionConnection> connection_;
+ bool ask_info_ = false;
+ double wakeup_at_ = 0;
double created_at_ = 0;
};
ConnectionInfo *current_info_;
ConnectionInfo main_connection_;
ConnectionInfo long_poll_connection_;
- bool auth_lost_flag_ = false;
- StateManager::ConnectionToken connection_token_;
+ mtproto::ConnectionManager::ConnectionToken connection_token_;
double cached_connection_timestamp_ = 0;
- std::unique_ptr<mtproto::RawConnection> cached_connection_;
+ unique_ptr<mtproto::RawConnection> cached_connection_;
std::shared_ptr<Callback> callback_;
mtproto::AuthData auth_data_;
+ bool use_pfs_{false};
+ bool need_check_main_key_{false};
TempAuthKeyWatchdog::RegisteredAuthKey registered_temp_auth_key_;
std::shared_ptr<AuthDataShared> shared_auth_data_;
bool close_flag_ = false;
static constexpr double ACTIVITY_TIMEOUT = 60 * 5;
+ static constexpr size_t MAX_INFLIGHT_QUERIES = 1024;
struct ContainerInfo {
size_t ref_cnt;
std::vector<uint64> message_ids;
};
- std::unordered_map<uint64, ContainerInfo> sent_containers_;
+ FlatHashMap<uint64, ContainerInfo> sent_containers_;
friend class GenAuthKeyActor;
struct HandshakeInfo {
bool flag_ = false;
ActorOwn<detail::GenAuthKeyActor> actor_;
- std::unique_ptr<mtproto::AuthKeyHandshake> handshake_;
+ unique_ptr<mtproto::AuthKeyHandshake> handshake_;
};
enum HandshakeId : int32 { MainAuthKeyHandshake = 0, TmpAuthKeyHandshake = 1 };
std::array<HandshakeInfo, 2> handshake_info_;
double wakeup_at_;
- void on_handshake_ready(Result<std::unique_ptr<mtproto::AuthKeyHandshake>> r_handshake);
+ void on_handshake_ready(Result<unique_ptr<mtproto::AuthKeyHandshake>> r_handshake);
void create_gen_auth_key_actor(HandshakeId handshake_id);
void auth_loop();
// mtproto::Connection::Callback
- void on_connected() override;
- void on_before_close() override;
- void on_closed(Status status) override;
+ void on_connected() final;
+ void on_closed(Status status) final;
- Status on_pong() override;
+ Status on_pong() final;
- void on_auth_key_updated() override;
- void on_tmp_auth_key_updated() override;
- void on_server_salt_updated() override;
- void on_server_time_difference_updated() override;
+ void on_network(bool network_flag, uint32 network_generation);
+ void on_online(bool online_flag);
+ void on_logging_out(bool logging_out_flag);
+
+ void on_auth_key_updated() final;
+ void on_tmp_auth_key_updated() final;
+ void on_server_salt_updated() final;
+ void on_server_time_difference_updated() final;
- void on_session_created(uint64 unique_id, uint64 first_id) override;
- void on_session_failed(Status status) override;
+ void on_session_created(uint64 unique_id, uint64 first_id) final;
+ void on_session_failed(Status status) final;
- void on_container_sent(uint64 container_id, vector<uint64> msg_ids) override;
+ void on_container_sent(uint64 container_id, vector<uint64> msg_ids) final;
- void on_message_ack(uint64 id) override;
- Status on_message_result_ok(uint64 id, BufferSlice packet, size_t original_size) override;
- void on_message_result_error(uint64 id, int error_code, BufferSlice message) override;
- void on_message_failed(uint64 id, Status status) override;
+ Status on_update(BufferSlice packet) final;
- void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size) override;
+ void on_message_ack(uint64 id) final;
+ Status on_message_result_ok(uint64 id, BufferSlice packet, size_t original_size) final;
+ void on_message_result_error(uint64 id, int error_code, string message) final;
+ void on_message_failed(uint64 id, Status status) final;
+
+ void on_message_info(uint64 id, int32 state, uint64 answer_id, int32 answer_size) final;
+
+ Status on_destroy_auth_key() final;
void flush_pending_invoke_after_queries();
bool has_queries() const;
@@ -208,24 +242,30 @@ class Session final
void resend_query(NetQueryPtr query);
void connection_open(ConnectionInfo *info, bool ask_info = false);
- void connection_add(std::unique_ptr<mtproto::RawConnection> raw_connection);
+ void connection_add(unique_ptr<mtproto::RawConnection> raw_connection);
void connection_check_mode(ConnectionInfo *info);
- void connection_open_finish(ConnectionInfo *info, Result<std::unique_ptr<mtproto::RawConnection>> r_raw_connection);
+ void connection_open_finish(ConnectionInfo *info, Result<unique_ptr<mtproto::RawConnection>> r_raw_connection);
void connection_online_update(bool force = false);
void connection_close(ConnectionInfo *info);
void connection_flush(ConnectionInfo *info);
void connection_send_query(ConnectionInfo *info, NetQueryPtr &&net_query, uint64 message_id = 0);
- bool need_send_bind_key();
- bool need_send_query();
+ bool need_send_bind_key() const;
+ bool need_send_query() const;
+ bool can_destroy_auth_key() const;
bool connection_send_bind_key(ConnectionInfo *info);
+ bool need_send_check_main_key() const;
+ bool connection_send_check_main_key(ConnectionInfo *info);
+
+ void on_result(NetQueryPtr query) final;
- void on_result(NetQueryPtr query) override;
+ void on_bind_result(NetQueryPtr query);
+ void on_check_key_result(NetQueryPtr query);
- void start_up() override;
- void loop() override;
- void hangup() override;
- void raw_event(const Event::Raw &event) override;
+ void start_up() final;
+ void loop() final;
+ void hangup() final;
+ void raw_event(const Event::Raw &event) final;
friend StringBuilder &operator<<(StringBuilder &sb, Mode mode) {
return sb << (mode == Mode::Http ? "Http" : "Tcp");
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.cpp
index 7fe27dd81e..3051cccf09 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,9 +8,13 @@
#include "td/telegram/net/SessionProxy.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+
+#include <algorithm>
namespace td {
@@ -18,14 +22,16 @@ SessionMultiProxy::SessionMultiProxy() = default;
SessionMultiProxy::~SessionMultiProxy() = default;
SessionMultiProxy::SessionMultiProxy(int32 session_count, std::shared_ptr<AuthDataShared> shared_auth_data,
- bool is_main, bool use_pfs, bool allow_media_only, bool is_media, bool is_cdn)
+ bool is_main, bool use_pfs, bool allow_media_only, bool is_media, bool is_cdn,
+ bool need_destroy_auth_key)
: session_count_(session_count)
, auth_data_(std::move(shared_auth_data))
, is_main_(is_main)
, use_pfs_(use_pfs)
, allow_media_only_(allow_media_only)
, is_media_(is_media)
- , is_cdn_(is_cdn) {
+ , is_cdn_(is_cdn)
+ , need_destroy_auth_key_(need_destroy_auth_key) {
if (allow_media_only_) {
CHECK(is_media_);
}
@@ -33,28 +39,37 @@ SessionMultiProxy::SessionMultiProxy(int32 session_count, std::shared_ptr<AuthDa
void SessionMultiProxy::send(NetQueryPtr query) {
size_t pos = 0;
- // TODO temporary hack with total_timeout_limit
- if (query->auth_flag() == NetQuery::AuthFlag::On && query->total_timeout_limit > 50) {
+ if (query->auth_flag() == NetQuery::AuthFlag::On) {
if (query->session_rand()) {
pos = query->session_rand() % sessions_.size();
} else {
- pos = pos_++ % sessions_.size();
+ pos = std::min_element(sessions_.begin(), sessions_.end(),
+ [](const auto &a, const auto &b) { return a.queries_count < b.queries_count; }) -
+ sessions_.begin();
}
}
- query->debug(PSTRING() << get_name() << ": send to proxy #" << pos);
- send_closure(sessions_[pos], &SessionProxy::send, std::move(query));
+ // query->debug(PSTRING() << get_name() << ": send to proxy #" << pos);
+ sessions_[pos].queries_count++;
+ send_closure(sessions_[pos].proxy, &SessionProxy::send, std::move(query));
}
void SessionMultiProxy::update_main_flag(bool is_main) {
LOG(INFO) << "Update " << get_name() << " is_main to " << is_main;
is_main_ = is_main;
for (auto &session : sessions_) {
- send_closure(session, &SessionProxy::update_main_flag, is_main);
+ send_closure(session.proxy, &SessionProxy::update_main_flag, is_main);
}
}
+
+void SessionMultiProxy::update_destroy_auth_key(bool need_destroy_auth_key) {
+ need_destroy_auth_key_ = need_destroy_auth_key;
+ send_closure(sessions_[0].proxy, &SessionProxy::update_destroy, need_destroy_auth_key_);
+}
+
void SessionMultiProxy::update_session_count(int32 session_count) {
update_options(session_count, use_pfs_);
}
+
void SessionMultiProxy::update_use_pfs(bool use_pfs) {
update_options(session_count_, use_pfs);
}
@@ -87,25 +102,58 @@ void SessionMultiProxy::update_options(int32 session_count, bool use_pfs) {
}
}
+void SessionMultiProxy::update_mtproto_header() {
+ for (auto &session : sessions_) {
+ send_closure_later(session.proxy, &SessionProxy::update_mtproto_header);
+ }
+}
+
void SessionMultiProxy::start_up() {
init();
}
bool SessionMultiProxy::get_pfs_flag() const {
- return (!allow_media_only_ && use_pfs_); // pfs is not supported in media only DCs
+ return use_pfs_ && !is_cdn_;
}
void SessionMultiProxy::init() {
+ sessions_generation_++;
sessions_.clear();
- if (is_main_) {
+ if (is_main_ && session_count_ > 1) {
LOG(WARNING) << tag("session_count", session_count_);
}
for (int32 i = 0; i < session_count_; i++) {
string name = PSTRING() << "Session" << get_name().substr(Slice("SessionMulti").size())
<< format::cond(session_count_ > 1, format::concat("#", i));
- sessions_.push_back(create_actor<SessionProxy>(name, auth_data_, is_main_, allow_media_only_, is_media_,
- get_pfs_flag(), is_main_ && i != 0, is_cdn_));
+
+ SessionInfo info;
+ class Callback final : public SessionProxy::Callback {
+ public:
+ Callback(ActorId<SessionMultiProxy> parent, uint32 generation, int32 session_id)
+ : parent_(parent), generation_(generation), session_id_(session_id) {
+ }
+ void on_query_finished() final {
+ send_closure(parent_, &SessionMultiProxy::on_query_finished, generation_, session_id_);
+ }
+
+ private:
+ ActorId<SessionMultiProxy> parent_;
+ uint32 generation_;
+ int32 session_id_;
+ };
+ info.proxy = create_actor<SessionProxy>(name, make_unique<Callback>(actor_id(this), sessions_generation_, i),
+ auth_data_, is_main_, allow_media_only_, is_media_, get_pfs_flag(), is_cdn_,
+ need_destroy_auth_key_ && i == 0);
+ sessions_.push_back(std::move(info));
+ }
+}
+
+void SessionMultiProxy::on_query_finished(uint32 generation, int session_id) {
+ if (generation != sessions_generation_) {
+ return;
}
+ sessions_.at(session_id).queries_count--;
+ CHECK(sessions_.at(session_id).queries_count >= 0);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.h b/protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.h
index 7a31656cf6..f68e32c85b 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/SessionMultiProxy.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,16 +14,17 @@
#include <memory>
namespace td {
+
class SessionProxy;
-class SessionMultiProxy : public Actor {
+class SessionMultiProxy final : public Actor {
public:
SessionMultiProxy();
SessionMultiProxy(const SessionMultiProxy &other) = delete;
SessionMultiProxy &operator=(const SessionMultiProxy &other) = delete;
- ~SessionMultiProxy() override;
+ ~SessionMultiProxy() final;
SessionMultiProxy(int32 session_count, std::shared_ptr<AuthDataShared> shared_auth_data, bool is_main, bool use_pfs,
- bool allow_media_only, bool is_media, bool is_cdn);
+ bool allow_media_only, bool is_media, bool is_cdn, bool need_destroy_auth_key);
void send(NetQueryPtr query);
void update_main_flag(bool is_main);
@@ -31,9 +32,11 @@ class SessionMultiProxy : public Actor {
void update_session_count(int32 session_count);
void update_use_pfs(bool use_pfs);
void update_options(int32 session_count, bool use_pfs);
+ void update_mtproto_header();
+
+ void update_destroy_auth_key(bool need_destroy_auth_key);
private:
- size_t pos_ = 0;
int32 session_count_ = 0;
std::shared_ptr<AuthDataShared> auth_data_;
bool is_main_ = false;
@@ -41,13 +44,20 @@ class SessionMultiProxy : public Actor {
bool allow_media_only_ = false;
bool is_media_ = false;
bool is_cdn_ = false;
- std::vector<ActorOwn<SessionProxy>> sessions_;
-
- void start_up() override;
+ bool need_destroy_auth_key_ = false;
+ struct SessionInfo {
+ ActorOwn<SessionProxy> proxy;
+ int queries_count{0};
+ };
+ uint32 sessions_generation_{0};
+ std::vector<SessionInfo> sessions_;
+
+ void start_up() final;
void init();
bool get_pfs_flag() const;
- void update_auth_state();
+ void on_query_finished(uint32 generation, int session_id);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.cpp b/protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.cpp
index df1e1d84f8..0af94063f0 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,22 +8,29 @@
#include "td/telegram/Global.h"
#include "td/telegram/net/ConnectionCreator.h"
+#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/net/Session.h"
+#include "td/telegram/Td.h"
+#include "td/telegram/UniqueId.h"
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
-
-#include <functional>
+#include "td/utils/SliceBuilder.h"
namespace td {
+
namespace mtproto {
class RawConnection;
} // namespace mtproto
-class SessionCallback : public Session::Callback {
+class SessionCallback final : public Session::Callback {
public:
- SessionCallback(ActorShared<SessionProxy> parent, DcId dc_id, bool allow_media_only, bool is_media, size_t hash)
+ SessionCallback(ActorShared<SessionProxy> parent, DcId dc_id, bool allow_media_only, bool is_media, uint32 hash)
: parent_(std::move(parent))
, dc_id_(dc_id)
, allow_media_only_(allow_media_only)
@@ -31,80 +38,95 @@ class SessionCallback : public Session::Callback {
, hash_(hash) {
}
- void on_failed() override {
+ void on_failed() final {
send_closure(parent_, &SessionProxy::on_failed);
}
- void on_closed() override {
+ void on_closed() final {
send_closure(parent_, &SessionProxy::on_closed);
}
- void request_raw_connection(Promise<std::unique_ptr<mtproto::RawConnection>> promise) override {
+ void request_raw_connection(unique_ptr<mtproto::AuthData> auth_data,
+ Promise<unique_ptr<mtproto::RawConnection>> promise) final {
send_closure(G()->connection_creator(), &ConnectionCreator::request_raw_connection, dc_id_, allow_media_only_,
- is_media_, std::move(promise), hash_);
+ is_media_, std::move(promise), hash_, std::move(auth_data));
}
- void on_tmp_auth_key_updated(mtproto::AuthKey auth_key) override {
+ void on_tmp_auth_key_updated(mtproto::AuthKey auth_key) final {
send_closure(parent_, &SessionProxy::on_tmp_auth_key_updated, std::move(auth_key));
}
+ void on_server_salt_updated(std::vector<mtproto::ServerSalt> server_salts) final {
+ send_closure(parent_, &SessionProxy::on_server_salt_updated, std::move(server_salts));
+ }
+
+ void on_update(BufferSlice &&update) final {
+ send_closure_later(G()->td(), &Td::on_update, std::move(update));
+ }
+
+ void on_result(NetQueryPtr query) final {
+ if (UniqueId::extract_type(query->id()) != UniqueId::BindKey) {
+ send_closure(parent_, &SessionProxy::on_query_finished);
+ }
+ G()->net_query_dispatcher().dispatch(std::move(query));
+ }
+
private:
ActorShared<SessionProxy> parent_;
DcId dc_id_;
bool allow_media_only_ = false;
bool is_media_ = false;
- size_t hash_ = 0;
+ uint32 hash_ = 0;
};
-SessionProxy::SessionProxy(std::shared_ptr<AuthDataShared> shared_auth_data, bool is_main, bool allow_media_only,
- bool is_media, bool use_pfs, bool need_wait_for_key, bool is_cdn)
- : auth_data_(std::move(shared_auth_data))
+SessionProxy::SessionProxy(unique_ptr<Callback> callback, std::shared_ptr<AuthDataShared> shared_auth_data,
+ bool is_main, bool allow_media_only, bool is_media, bool use_pfs, bool is_cdn,
+ bool need_destroy)
+ : callback_(std::move(callback))
+ , auth_data_(std::move(shared_auth_data))
, is_main_(is_main)
, allow_media_only_(allow_media_only)
, is_media_(is_media)
, use_pfs_(use_pfs)
- , need_wait_for_key_(need_wait_for_key)
- , is_cdn_(is_cdn) {
+ , is_cdn_(is_cdn)
+ , need_destroy_(need_destroy) {
}
void SessionProxy::start_up() {
- class Listener : public AuthDataShared::Listener {
+ class Listener final : public AuthDataShared::Listener {
public:
explicit Listener(ActorShared<SessionProxy> session_proxy) : session_proxy_(std::move(session_proxy)) {
}
- bool notify() override {
+ bool notify() final {
if (!session_proxy_.is_alive()) {
return false;
}
- send_closure(session_proxy_, &SessionProxy::update_auth_state);
+ send_closure(session_proxy_, &SessionProxy::update_auth_key_state);
return true;
}
private:
ActorShared<SessionProxy> session_proxy_;
};
- auth_state_ = auth_data_->get_auth_state().first;
- auth_data_->add_auth_key_listener(std::make_unique<Listener>(actor_shared(this)));
- if (is_main_ && !need_wait_for_key_) {
- open_session();
- }
+ auth_key_state_ = auth_data_->get_auth_key_state();
+ auth_data_->add_auth_key_listener(make_unique<Listener>(actor_shared(this)));
+ open_session();
}
void SessionProxy::tear_down() {
for (auto &query : pending_queries_) {
query->resend();
+ callback_->on_query_finished();
G()->net_query_dispatcher().dispatch(std::move(query));
}
pending_queries_.clear();
}
void SessionProxy::send(NetQueryPtr query) {
- if (query->auth_flag() == NetQuery::AuthFlag::On && auth_state_ != AuthState::OK) {
+ if (query->auth_flag() == NetQuery::AuthFlag::On && auth_key_state_ != AuthKeyState::OK) {
query->debug(PSTRING() << get_name() << ": wait for auth");
pending_queries_.emplace_back(std::move(query));
return;
}
- if (session_.empty()) {
- open_session(true);
- }
+ open_session(true);
query->debug(PSTRING() << get_name() << ": sent to session");
send_closure(session_, &Session::send, std::move(query));
}
@@ -119,6 +141,16 @@ void SessionProxy::update_main_flag(bool is_main) {
open_session();
}
+void SessionProxy::update_destroy(bool need_destroy) {
+ if (need_destroy_ == need_destroy) {
+ LOG(INFO) << "Ignore reduntant update_destroy(" << need_destroy << ")";
+ return;
+ }
+ need_destroy_ = need_destroy;
+ close_session();
+ open_session();
+}
+
void SessionProxy::on_failed() {
if (session_generation_ != get_link_token()) {
return;
@@ -127,6 +159,11 @@ void SessionProxy::on_failed() {
open_session();
}
+void SessionProxy::update_mtproto_header() {
+ close_session();
+ open_session();
+}
+
void SessionProxy::on_closed() {
}
@@ -134,32 +171,60 @@ void SessionProxy::close_session() {
send_closure(std::move(session_), &Session::close);
session_generation_++;
}
+
void SessionProxy::open_session(bool force) {
- if (!force && !is_main_) {
+ if (!session_.empty()) {
+ return;
+ }
+ // There are several assumption that make this code OK
+ // 1. All unauthorized query will be sent into the same SessionProxy
+ // 2. All authorized query are delayed before we have authorization
+ // So only one SessionProxy will be active before we have authorization key
+ auto should_open = [&] {
+ if (force) {
+ return true;
+ }
+ if (need_destroy_) {
+ return auth_key_state_ != AuthKeyState::Empty;
+ }
+ if (auth_key_state_ != AuthKeyState::OK) {
+ return false;
+ }
+ return is_main_ || !pending_queries_.empty();
+ }();
+ if (!should_open) {
return;
}
+
CHECK(session_.empty());
+ auto dc_id = auth_data_->dc_id();
string name = PSTRING() << "Session" << get_name().substr(Slice("SessionProxy").size());
- string hash_string = PSTRING() << name << " " << auth_data_->dc_id().get_raw_id() << " " << allow_media_only_;
- auto hash = std::hash<std::string>()(hash_string);
- session_ =
- create_actor<Session>(name,
- make_unique<SessionCallback>(actor_shared(this, session_generation_), auth_data_->dc_id(),
- allow_media_only_, is_media_, hash),
- auth_data_, is_main_, use_pfs_, is_cdn_, tmp_auth_key_);
+ string hash_string = PSTRING() << name << " " << dc_id.get_raw_id() << " " << allow_media_only_;
+ auto hash = Hash<string>()(hash_string);
+ int32 raw_dc_id = dc_id.get_raw_id();
+ int32 int_dc_id = raw_dc_id;
+ if (G()->is_test_dc()) {
+ int_dc_id += 10000;
+ }
+ if (allow_media_only_ && !is_cdn_) {
+ int_dc_id = -int_dc_id;
+ }
+ session_ = create_actor<Session>(
+ name,
+ make_unique<SessionCallback>(actor_shared(this, session_generation_), dc_id, allow_media_only_, is_media_, hash),
+ auth_data_, raw_dc_id, int_dc_id, is_main_, use_pfs_, is_cdn_, need_destroy_, tmp_auth_key_, server_salts_);
}
-void SessionProxy::update_auth_state() {
- auth_state_ = auth_data_->get_auth_state().first;
- if (pending_queries_.empty() && !need_wait_for_key_) {
- return;
+void SessionProxy::update_auth_key_state() {
+ auto old_auth_key_state = auth_key_state_;
+ auth_key_state_ = auth_data_->get_auth_key_state();
+ if (auth_key_state_ != old_auth_key_state && old_auth_key_state == AuthKeyState::OK) {
+ close_session();
}
- if (auth_state_ != AuthState::OK) {
+ open_session();
+ if (session_.empty() || auth_key_state_ != AuthKeyState::OK) {
return;
}
- if (session_.empty()) {
- open_session(true);
- }
for (auto &query : pending_queries_) {
query->debug(PSTRING() << get_name() << ": sent to session");
send_closure(session_, &Session::send, std::move(query));
@@ -168,15 +233,24 @@ void SessionProxy::update_auth_state() {
}
void SessionProxy::on_tmp_auth_key_updated(mtproto::AuthKey auth_key) {
- string state;
+ Slice state;
if (auth_key.empty()) {
- state = "Empty";
+ state = Slice("Empty");
} else if (auth_key.auth_flag()) {
- state = "OK";
+ state = Slice("OK");
} else {
- state = "NoAuth";
+ state = Slice("NoAuth");
}
- LOG(WARNING) << "tmp_auth_key " << auth_key.id() << ": " << state;
+ LOG(WARNING) << "Have tmp_auth_key " << auth_key.id() << ": " << state;
tmp_auth_key_ = std::move(auth_key);
}
+
+void SessionProxy::on_server_salt_updated(std::vector<mtproto::ServerSalt> server_salts) {
+ server_salts_ = std::move(server_salts);
+}
+
+void SessionProxy::on_query_finished() {
+ callback_->on_query_finished();
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.h b/protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.h
index 40f881612d..966031306d 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/SessionProxy.h
@@ -1,39 +1,54 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/telegram/net/AuthDataShared.h"
#include "td/telegram/net/NetQuery.h"
+#include "td/mtproto/AuthData.h"
+#include "td/mtproto/AuthKey.h"
+
#include "td/actor/actor.h"
#include <memory>
namespace td {
+
class Session;
-class SessionProxy : public Actor {
+
+class SessionProxy final : public Actor {
public:
friend class SessionCallback;
+ class Callback {
+ public:
+ virtual ~Callback() = default;
+ virtual void on_query_finished() = 0;
+ };
- SessionProxy(std::shared_ptr<AuthDataShared> shared_auth_data, bool is_main, bool allow_media_only, bool is_media,
- bool use_pfs, bool need_wait_for_key, bool is_cdn);
+ SessionProxy(unique_ptr<Callback> callback, std::shared_ptr<AuthDataShared> shared_auth_data, bool is_main,
+ bool allow_media_only, bool is_media, bool use_pfs, bool is_cdn, bool need_destroy);
void send(NetQueryPtr query);
void update_main_flag(bool is_main);
+ void update_mtproto_header();
+ void update_destroy(bool need_destroy);
private:
+ unique_ptr<Callback> callback_;
std::shared_ptr<AuthDataShared> auth_data_;
- AuthState auth_state_;
+ AuthKeyState auth_key_state_ = AuthKeyState::Empty;
bool is_main_;
bool allow_media_only_;
bool is_media_;
bool use_pfs_;
mtproto::AuthKey tmp_auth_key_;
- bool need_wait_for_key_;
+ std::vector<mtproto::ServerSalt> server_salts_;
bool is_cdn_;
+ bool need_destroy_;
ActorOwn<Session> session_;
std::vector<NetQueryPtr> pending_queries_;
uint64 session_generation_ = 1;
@@ -43,10 +58,14 @@ class SessionProxy : public Actor {
void close_session();
void open_session(bool force = false);
- void update_auth_state();
+ void update_auth_key_state();
void on_tmp_auth_key_updated(mtproto::AuthKey auth_key);
+ void on_server_salt_updated(std::vector<mtproto::ServerSalt> server_salts);
+
+ void on_query_finished();
- void start_up() override;
- void tear_down() override;
+ void start_up() final;
+ void tear_down() final;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/TempAuthKeyWatchdog.h b/protocols/Telegram/tdlib/td/td/telegram/net/TempAuthKeyWatchdog.h
index eff190bf61..b15e9c9f2f 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/TempAuthKeyWatchdog.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/net/TempAuthKeyWatchdog.h
@@ -1,19 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-
#include "td/telegram/Global.h"
#include "td/telegram/net/NetQueryCreator.h"
#include "td/telegram/net/NetQueryDispatcher.h"
-
#include "td/telegram/telegram_api.h"
+#include "td/actor/actor.h"
+
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
@@ -22,7 +21,8 @@
#include <map>
namespace td {
-class TempAuthKeyWatchdog : public NetQueryCallback {
+
+class TempAuthKeyWatchdog final : public NetQueryCallback {
class RegisteredAuthKeyImpl {
public:
explicit RegisteredAuthKeyImpl(int64 auth_key_id)
@@ -42,23 +42,28 @@ class TempAuthKeyWatchdog : public NetQueryCallback {
};
public:
- using RegisteredAuthKey = std::unique_ptr<RegisteredAuthKeyImpl>;
+ explicit TempAuthKeyWatchdog(ActorShared<> parent) : parent_(std::move(parent)) {
+ }
+
+ using RegisteredAuthKey = unique_ptr<RegisteredAuthKeyImpl>;
static RegisteredAuthKey register_auth_key_id(int64 id) {
send_closure(G()->temp_auth_key_watchdog(), &TempAuthKeyWatchdog::register_auth_key_id_impl, id);
- return std::make_unique<RegisteredAuthKeyImpl>(id);
+ return make_unique<RegisteredAuthKeyImpl>(id);
}
private:
static constexpr double SYNC_WAIT = 0.1;
static constexpr double SYNC_WAIT_MAX = 1.0;
+ ActorShared<> parent_;
std::map<uint64, uint32> id_count_;
double sync_at_ = 0;
bool need_sync_ = false;
bool run_sync_ = false;
void register_auth_key_id_impl(int64 id) {
+ LOG(INFO) << "Register key " << id;
if (!++id_count_[id]) {
id_count_.erase(id);
}
@@ -66,6 +71,7 @@ class TempAuthKeyWatchdog : public NetQueryCallback {
}
void unregister_auth_key_id_impl(int64 id) {
+ LOG(INFO) << "Unregister key " << id;
if (!--id_count_[id]) {
id_count_.erase(id);
}
@@ -75,7 +81,7 @@ class TempAuthKeyWatchdog : public NetQueryCallback {
void need_sync() {
need_sync_ = true;
try_sync();
- LOG(DEBUG) << "need sync";
+ LOG(DEBUG) << "Need sync temp auth keys";
}
void try_sync() {
@@ -87,12 +93,12 @@ class TempAuthKeyWatchdog : public NetQueryCallback {
if (sync_at_ == 0) {
sync_at_ = now + SYNC_WAIT_MAX;
}
- LOG(DEBUG) << "set timeout";
+ LOG(DEBUG) << "Set sync timeout";
set_timeout_at(min(sync_at_, now + SYNC_WAIT));
}
- void timeout_expired() override {
- LOG(DEBUG) << "timeout expired";
+ void timeout_expired() final {
+ LOG(DEBUG) << "Sync timeout expired";
CHECK(!run_sync_);
if (!need_sync_) {
LOG(ERROR) << "Do not need sync..";
@@ -105,23 +111,26 @@ class TempAuthKeyWatchdog : public NetQueryCallback {
for (auto &id_count : id_count_) {
ids.push_back(id_count.first);
}
- if (G()->close_flag()) {
- return;
+ if (!G()->close_flag()) {
+ LOG(WARNING) << "Start auth_dropTempAuthKeys except keys " << format::as_array(ids);
+ auto query = G()->net_query_creator().create_unauth(telegram_api::auth_dropTempAuthKeys(std::move(ids)));
+ G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this));
}
- LOG(WARNING) << "Start auth_dropTempAuthKeys except keys " << format::as_array(ids);
- auto query = G()->net_query_creator().create(create_storer(telegram_api::auth_dropTempAuthKeys(std::move(ids))));
- G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this));
}
void on_result(NetQueryPtr query) final {
run_sync_ = false;
if (query->is_error()) {
- LOG(ERROR) << "auth_dropTempAuthKeys failed " << query->error();
+ if (G()->close_flag()) {
+ return;
+ }
+ LOG(ERROR) << "Receive error for auth_dropTempAuthKeys: " << query->error();
need_sync_ = true;
} else {
- LOG(INFO) << "auth_dropTempAuthKeys OK";
+ LOG(INFO) << "Receive OK for auth_dropTempAuthKeys";
}
try_sync();
}
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/td/telegram/td_c_client.cpp b/protocols/Telegram/tdlib/td/td/telegram/td_c_client.cpp
index da3061199d..c3c9285fbf 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/td_c_client.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/td_c_client.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,43 +8,36 @@
#include "td/telegram/Client.h"
#include "td/telegram/Log.h"
-
-#include "td/telegram/td_api.h"
#include "td/telegram/td_tdc_api_inner.h"
#include <cstring>
-void *TdCClientCreate() {
- return new td::Client();
+static td::ClientManager *GetClientManager() {
+ return td::ClientManager::get_manager_singleton();
+}
+
+int TdCClientCreateId() {
+ return GetClientManager()->create_client_id();
}
-void TdCClientSend(void *instance, struct TdRequest request) {
- auto client = static_cast<td::Client *>(instance);
- td::Client::Request client_request;
- client_request.id = request.id;
- client_request.function = TdConvertToInternal(request.function);
+void TdCClientSend(int client_id, struct TdRequest request) {
+ GetClientManager()->send(client_id, request.request_id, TdConvertToInternal(request.function));
TdDestroyObjectFunction(request.function);
- client->send(std::move(client_request));
}
-TdResponse TdCClientReceive(void *instance, double timeout) {
- auto client = static_cast<td::Client *>(instance);
- auto response = client->receive(timeout);
+TdResponse TdCClientReceive(double timeout) {
+ auto response = GetClientManager()->receive(timeout);
TdResponse c_response;
- c_response.id = response.id;
- c_response.object = response.object == nullptr
- ? nullptr
- : TdConvertFromInternal(static_cast<const td::td_api::Object &>(*response.object));
+ c_response.client_id = response.client_id;
+ c_response.request_id = response.request_id;
+ c_response.object = response.object == nullptr ? nullptr : TdConvertFromInternal(*response.object);
return c_response;
}
-void TdCClientDestroy(void *instance) {
- auto client = static_cast<td::Client *>(instance);
- delete client;
-}
-
-void TdCClientSetVerbosity(int new_verbosity_level) {
- td::Log::set_verbosity_level(new_verbosity_level);
+TdObject *TdCClientExecute(TdFunction *function) {
+ auto result = td::ClientManager::execute(TdConvertToInternal(function));
+ TdDestroyObjectFunction(function);
+ return TdConvertFromInternal(*result);
}
TdVectorInt *TdCreateObjectVectorInt(int size, int *data) {
diff --git a/protocols/Telegram/tdlib/td/td/telegram/td_c_client.h b/protocols/Telegram/tdlib/td/td/telegram/td_c_client.h
index fc472f9fdf..a0d6421a56 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/td_c_client.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/td_c_client.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -18,21 +18,23 @@ struct TdVectorObject *TdCreateObjectVectorObject(int size, struct TdObject **da
struct TdBytes TdCreateObjectBytes(unsigned char *data, int len);
struct TdRequest {
- long long id;
- TdFunction *function;
+ long long request_id;
+ struct TdFunction *function;
};
struct TdResponse {
- long long id;
- TdObject *object;
+ long long request_id;
+ int client_id;
+ struct TdObject *object;
};
-void *TdCClientCreate();
-void TdCClientSend(void *instance, struct TdRequest cmd);
-struct TdResponse TdCClientSendCommandSync(void *instance, double timeout);
-void TdCClientDestroy(void *instance);
+int TdCClientCreateId();
-void TdCClientSetVerbosity(int new_verbosity_level);
+void TdCClientSend(int client_id, struct TdRequest request);
+
+struct TdResponse TdCClientReceive(double timeout);
+
+struct TdObject *TdCClientExecute(struct TdFunction *function);
#ifdef __cplusplus
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/td_emscripten.cpp b/protocols/Telegram/tdlib/td/td/telegram/td_emscripten.cpp
index ba9e31f7e9..c55b2b7980 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/td_emscripten.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/td_emscripten.cpp
@@ -1,40 +1,39 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-// Just for testing.
-// Will be completly rewritten
-
#include "td/telegram/td_json_client.h"
#include "td/telegram/td_log.h"
+#include "td/actor/ConcurrentScheduler.h"
+
#include <emscripten.h>
extern "C" {
-EMSCRIPTEN_KEEPALIVE void *td_create() {
- return td_json_client_create();
+EMSCRIPTEN_KEEPALIVE double td_emscripten_create_client_id() {
+ return td_create_client_id();
}
-EMSCRIPTEN_KEEPALIVE void td_send(void *client, const char *query) {
- td_json_client_send(client, query);
-}
-EMSCRIPTEN_KEEPALIVE const char *td_receive(void *client) {
- return td_json_client_receive(client, 0);
+
+EMSCRIPTEN_KEEPALIVE void td_emscripten_send(double client_id, const char *query) {
+ td_send(static_cast<int>(client_id), query);
}
-EMSCRIPTEN_KEEPALIVE const char *td_execute(void *client, const char *query) {
- return td_json_client_execute(client, query);
+
+EMSCRIPTEN_KEEPALIVE const char *td_emscripten_receive() {
+ return td_receive(0);
}
-EMSCRIPTEN_KEEPALIVE void td_destroy(void *client) {
- td_json_client_destroy(client);
+
+EMSCRIPTEN_KEEPALIVE const char *td_emscripten_execute(const char *query) {
+ return td_execute(query);
}
-EMSCRIPTEN_KEEPALIVE void td_set_verbosity(int verbosity) {
- td_set_log_verbosity_level(verbosity);
+
+EMSCRIPTEN_KEEPALIVE double td_emscripten_get_timeout() {
+ return td::ConcurrentScheduler::emscripten_get_main_timeout();
}
}
-int main(void) {
+int main() {
emscripten_exit_with_live_runtime();
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/td_json_client.cpp b/protocols/Telegram/tdlib/td/td/telegram/td_json_client.cpp
index 94f327d19e..d1375d8cb7 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/td_json_client.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/td_json_client.cpp
@@ -1,19 +1,16 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/td_json_client.h"
+#include "td/telegram/Client.h"
#include "td/telegram/ClientJson.h"
#include "td/utils/Slice.h"
-extern "C" int td_json_client_square(int x, const char *str) {
- return x * x;
-}
-
void *td_json_client_create() {
return new td::ClientJson();
}
@@ -23,23 +20,33 @@ void td_json_client_destroy(void *client) {
}
void td_json_client_send(void *client, const char *request) {
- static_cast<td::ClientJson *>(client)->send(td::Slice(request));
+ static_cast<td::ClientJson *>(client)->send(td::Slice(request == nullptr ? "" : request));
}
const char *td_json_client_receive(void *client, double timeout) {
- auto slice = static_cast<td::ClientJson *>(client)->receive(timeout);
- if (slice.empty()) {
- return nullptr;
- } else {
- return slice.c_str();
- }
+ return static_cast<td::ClientJson *>(client)->receive(timeout);
}
const char *td_json_client_execute(void *client, const char *request) {
- auto slice = static_cast<td::ClientJson *>(client)->execute(td::Slice(request));
- if (slice.empty()) {
- return nullptr;
- } else {
- return slice.c_str();
- }
+ return td::ClientJson::execute(td::Slice(request == nullptr ? "" : request));
+}
+
+int td_create_client_id() {
+ return td::json_create_client_id();
+}
+
+void td_send(int client_id, const char *request) {
+ td::json_send(client_id, td::Slice(request == nullptr ? "" : request));
+}
+
+const char *td_receive(double timeout) {
+ return td::json_receive(timeout);
+}
+
+const char *td_execute(const char *request) {
+ return td::json_execute(td::Slice(request == nullptr ? "" : request));
+}
+
+void td_set_log_message_callback(int max_verbosity_level, td_log_message_callback_ptr callback) {
+ td::ClientManager::set_log_message_callback(max_verbosity_level, callback);
}
diff --git a/protocols/Telegram/tdlib/td/td/telegram/td_json_client.h b/protocols/Telegram/tdlib/td/td/telegram/td_json_client.h
index 55a0f00837..a12fa01b65 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/td_json_client.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/td_json_client.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -13,19 +13,113 @@
* and is able to work with JSON.
*
* The JSON serialization of TDLib API objects is straightforward: all API objects are represented as JSON objects with
- * the same keys as the API object field names. The object type name is stored in the special field "@type", which is
- * optional in places where a type is uniquely determined by the context.
- * Bool object fields are stored as Booleans in JSON. int32, int53 and double fields are stored as Numbers.
- * int64 and string fields are stored as Strings. bytes fields are base64 encoded and then stored as String.
- * vectors are stored as Arrays.
+ * the same keys as the API object field names. The object type name is stored in the special field "@type" which is
+ * optional in places where type is uniquely determined by the context.
+ * Fields of Bool type are stored as Boolean, fields of int32, int53, and double types are stored as Number, fields of
+ * int64 and string types are stored as String, fields of bytes type are base64 encoded and then stored as String,
+ * fields of array type are stored as Array.
+ *
+ * The main TDLib interface is asynchronous. To match requests with a corresponding response, the field "@extra" can
+ * be added to the request object. The corresponding response will have an "@extra" field with exactly the same value.
+ * Each returned object will have an "@client_id" field, containing the identifier of the client for which
+ * a response or an update was received.
+ *
+ * A TDLib client instance can be created through td_create_client_id.
+ * Requests can be sent using td_send and the received client identifier.
+ * New updates and responses to requests can be received through td_receive from any thread after the first request
+ * has been sent to the client instance. This function must not be called simultaneously from two different threads.
+ * Also note that all updates and responses to requests must be applied in the order they were received for consistency.
+ * Some TDLib requests can be executed synchronously from any thread using td_execute.
+ * TDLib client instances are destroyed automatically after they are closed.
+ * All TDLib client instances must be closed before application termination to ensure data consistency.
+ *
+ * General pattern of usage:
+ * \code
+ * int client_id = td_create_client_id();
+ * // share the client_id with other threads, which will be able to send requests via td_send
+ *
+ * const double WAIT_TIMEOUT = 10.0; // seconds
+ * while (true) {
+ * const char *result = td_receive(WAIT_TIMEOUT);
+ * if (result) {
+ * // parse the result as a JSON object and process it as an incoming update or the answer to a previously sent request
+ * }
+ * }
+ * \endcode
+ */
+
+#include "td/telegram/tdjson_export.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Returns an opaque identifier of a new TDLib instance.
+ * The TDLib instance will not send updates until the first request is sent to it.
+ * \return Opaque identifier of a new TDLib instance.
+ */
+TDJSON_EXPORT int td_create_client_id();
+
+/**
+ * Sends request to the TDLib client. May be called from any thread.
+ * \param[in] client_id TDLib client identifier.
+ * \param[in] request JSON-serialized null-terminated request to TDLib.
+ */
+TDJSON_EXPORT void td_send(int client_id, const char *request);
+
+/**
+ * Receives incoming updates and request responses. Must not be called simultaneously from two different threads.
+ * The returned pointer can be used until the next call to td_receive or td_execute, after which it will be deallocated by TDLib.
+ * \param[in] timeout The maximum number of seconds allowed for this function to wait for new data.
+ * \return JSON-serialized null-terminated incoming update or request response. May be NULL if the timeout expires.
+ */
+TDJSON_EXPORT const char *td_receive(double timeout);
+
+/**
+ * Synchronously executes a TDLib request.
+ * A request can be executed synchronously, only if it is documented with "Can be called synchronously".
+ * The returned pointer can be used until the next call to td_receive or td_execute, after which it will be deallocated by TDLib.
+ * \param[in] request JSON-serialized null-terminated request to TDLib.
+ * \return JSON-serialized null-terminated request response.
+ */
+TDJSON_EXPORT const char *td_execute(const char *request);
+
+/**
+ * A type of callback function that will be called when a message is added to the internal TDLib log.
+ *
+ * \param verbosity_level Log verbosity level with which the message was added from -1 up to 1024.
+ * If 0, then TDLib will crash as soon as the callback returns.
+ * None of the TDLib methods can be called from the callback.
+ * \param message Null-terminated UTF-8-encoded string with the message added to the log.
+ */
+typedef void (*td_log_message_callback_ptr)(int verbosity_level, const char *message);
+
+/**
+ * Sets the callback that will be called when a message is added to the internal TDLib log.
+ * None of the TDLib methods can be called from the callback.
+ * By default the callback is not set.
+ *
+ * \param[in] max_verbosity_level The maximum verbosity level of messages for which the callback will be called.
+ * \param[in] callback Callback that will be called when a message is added to the internal TDLib log.
+ * Pass nullptr to remove the callback.
+ */
+TDJSON_EXPORT void td_set_log_message_callback(int max_verbosity_level, td_log_message_callback_ptr callback);
+
+/**
+ * \file
+ * Alternatively, you can use old TDLib JSON interface, which will be removed in TDLib 2.0.0.
+ *
+ * Objects and functions serialization to JSON is the same for both JSON interfaces.
+ *
* The main TDLib interface is asynchronous. To match requests with a corresponding response a field "@extra" can
* be added to the request object. The corresponding response will have an "@extra" field with exactly the same value.
*
- * A TDLib client instance should be created through td_json_client_create.
+ * A TDLib client instance can be created through td_json_client_create.
* Requests then can be sent using td_json_client_send from any thread.
* New updates and request responses can be received through td_json_client_receive from any thread. This function
- * shouldn't be called simultaneously from two different threads. Also note that all updates and request responses
- * should be applied in the order they were received to ensure consistency.
+ * must not be called simultaneously from two different threads. Also note that all updates and request responses
+ * must be applied in the order they were received to ensure consistency.
* Given this information, it's advisable to call this function from a dedicated thread.
* Some service TDLib requests can be executed synchronously from any thread by using td_json_client_execute.
* The TDLib client instance can be destroyed via td_json_client_destroy.
@@ -47,12 +141,6 @@
* \endcode
*/
-#include "td/telegram/tdjson_export.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
/**
* Creates a new instance of TDLib.
* \return Pointer to the created instance of TDLib.
@@ -68,11 +156,11 @@ TDJSON_EXPORT void td_json_client_send(void *client, const char *request);
/**
* Receives incoming updates and request responses from the TDLib client. May be called from any thread, but
- * shouldn't be called simultaneously from two different threads.
+ * must not be called simultaneously from two different threads.
* Returned pointer will be deallocated by TDLib during next call to td_json_client_receive or td_json_client_execute
* in the same thread, so it can't be used after that.
* \param[in] client The client.
- * \param[in] timeout Maximum number of seconds allowed for this function to wait for new data.
+ * \param[in] timeout The maximum number of seconds allowed for this function to wait for new data.
* \return JSON-serialized null-terminated incoming update or request response. May be NULL if the timeout expires.
*/
TDJSON_EXPORT const char *td_json_client_receive(void *client, double timeout);
@@ -82,14 +170,14 @@ TDJSON_EXPORT const char *td_json_client_receive(void *client, double timeout);
* Only a few requests can be executed synchronously.
* Returned pointer will be deallocated by TDLib during next call to td_json_client_receive or td_json_client_execute
* in the same thread, so it can't be used after that.
- * \param[in] client The client.
+ * \param[in] client The client. Currently ignored for all requests, so NULL can be passed.
* \param[in] request JSON-serialized null-terminated request to TDLib.
- * \return JSON-serialized null-terminated request response. May be NULL if the request can't be parsed.
+ * \return JSON-serialized null-terminated request response.
*/
TDJSON_EXPORT const char *td_json_client_execute(void *client, const char *request);
/**
- * Destroys the TDLib client instance. After this is called the client instance shouldn't be used anymore.
+ * Destroys the TDLib client instance. After this is called the client instance must not be used anymore.
* \param[in] client The client.
*/
TDJSON_EXPORT void td_json_client_destroy(void *client);
diff --git a/protocols/Telegram/tdlib/td/td/telegram/td_log.cpp b/protocols/Telegram/tdlib/td/td/telegram/td_log.cpp
index 2eb3605787..cdf8d611bd 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/td_log.cpp
+++ b/protocols/Telegram/tdlib/td/td/telegram/td_log.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/td/telegram/td_log.h b/protocols/Telegram/tdlib/td/td/telegram/td_log.h
index 51ab6393a2..5891636094 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/td_log.h
+++ b/protocols/Telegram/tdlib/td/td/telegram/td_log.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,6 +10,8 @@
* \file
* C interface for managing the internal logging of TDLib.
* By default TDLib writes logs to stderr or an OS specific log and uses a verbosity level of 5.
+ * These functions are deprecated since TDLib 1.4.0 in favor of the setLogVerbosityLevel, setLogStream and
+ * other synchronous requests for managing the internal TDLib logging.
*/
#include "td/telegram/tdjson_export.h"
@@ -23,25 +25,28 @@ extern "C" {
* By default TDLib writes logs to stderr or an OS specific log.
* Use this method to write the log to a file instead.
*
+ * \deprecated Use synchronous setLogStream request instead.
* \param[in] file_path Null-terminated path to a file where the internal TDLib log will be written.
* Use an empty path to switch back to the default logging behaviour.
* \return True 1 on success, or 0 otherwise, i.e. if the file can't be opened for writing.
*/
-TDJSON_EXPORT int td_set_log_file_path(const char *file_path);
+TDJSON_DEPRECATED_EXPORT int td_set_log_file_path(const char *file_path);
/**
- * Sets maximum size of the file to where the internal TDLib log is written before the file will be auto-rotated.
+ * Sets the maximum size of the file to where the internal TDLib log is written before the file will be auto-rotated.
* Unused if log is not written to a file. Defaults to 10 MB.
*
- * \param[in] max_file_size Maximum size of the file to where the internal TDLib log is written before the file
+ * \deprecated Use synchronous setLogStream request instead.
+ * \param[in] max_file_size The maximum size of the file to where the internal TDLib log is written before the file
* will be auto-rotated. Should be positive.
*/
-TDJSON_EXPORT void td_set_log_max_file_size(long long max_file_size);
+TDJSON_DEPRECATED_EXPORT void td_set_log_max_file_size(long long max_file_size);
/**
* Sets the verbosity level of the internal logging of TDLib.
* By default the TDLib uses a log verbosity level of 5.
*
+ * \deprecated Use synchronous setLogVerbosityLevel request instead.
* \param[in] new_verbosity_level New value of logging verbosity level.
* Value 0 corresponds to fatal errors,
* value 1 corresponds to errors,
@@ -51,7 +56,7 @@ TDJSON_EXPORT void td_set_log_max_file_size(long long max_file_size);
* value 5 corresponds to verbose debug,
* value greater than 5 and up to 1024 can be used to enable even more logging.
*/
-TDJSON_EXPORT void td_set_log_verbosity_level(int new_verbosity_level);
+TDJSON_DEPRECATED_EXPORT void td_set_log_verbosity_level(int new_verbosity_level);
/**
* A type of callback function that will be called when a fatal error happens.
@@ -66,10 +71,11 @@ typedef void (*td_log_fatal_error_callback_ptr)(const char *error_message);
* The TDLib will crash as soon as callback returns.
* By default the callback is not set.
*
+ * \deprecated Use td_set_log_message_callback instead.
* \param[in] callback Callback that will be called when a fatal error happens.
* Pass NULL to remove the callback.
*/
-TDJSON_EXPORT void td_set_log_fatal_error_callback(td_log_fatal_error_callback_ptr callback);
+TDJSON_DEPRECATED_EXPORT void td_set_log_fatal_error_callback(td_log_fatal_error_callback_ptr callback);
#ifdef __cplusplus
} // extern "C"
diff --git a/protocols/Telegram/tdlib/td/td/tl/TlObject.h b/protocols/Telegram/tdlib/td/td/tl/TlObject.h
index 4fcbf6bc46..60671ec10b 100644
--- a/protocols/Telegram/tdlib/td/td/tl/TlObject.h
+++ b/protocols/Telegram/tdlib/td/td/tl/TlObject.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,9 +11,10 @@
* Contains the declarations of a base class for all TL-objects and some helper methods
*/
+#include <cstddef>
#include <cstdint>
-#include <memory>
#include <string>
+#include <type_traits>
#include <utility>
namespace td {
@@ -86,11 +87,101 @@ class TlObject {
virtual ~TlObject() = default;
};
+/// @cond UNDOCUMENTED
+namespace tl {
+
+template <class T>
+class unique_ptr {
+ public:
+ using pointer = T *;
+ using element_type = T;
+
+ unique_ptr() noexcept = default;
+ unique_ptr(const unique_ptr &other) = delete;
+ unique_ptr &operator=(const unique_ptr &other) = delete;
+ unique_ptr(unique_ptr &&other) noexcept : ptr_(other.release()) {
+ }
+ unique_ptr &operator=(unique_ptr &&other) noexcept {
+ reset(other.release());
+ return *this;
+ }
+ ~unique_ptr() {
+ reset();
+ }
+
+ unique_ptr(std::nullptr_t) noexcept {
+ }
+ explicit unique_ptr(T *ptr) noexcept : ptr_(ptr) {
+ }
+ template <class S, class = typename std::enable_if<std::is_base_of<T, S>::value>::type>
+ unique_ptr(unique_ptr<S> &&other) noexcept : ptr_(static_cast<S *>(other.release())) {
+ }
+ template <class S, class = typename std::enable_if<std::is_base_of<T, S>::value>::type>
+ unique_ptr &operator=(unique_ptr<S> &&other) noexcept {
+ reset(static_cast<T *>(other.release()));
+ return *this;
+ }
+ void reset(T *new_ptr = nullptr) noexcept {
+ static_assert(sizeof(T) > 0, "Can't destroy unique_ptr with incomplete type");
+ delete ptr_;
+ ptr_ = new_ptr;
+ }
+ T *release() noexcept {
+ auto res = ptr_;
+ ptr_ = nullptr;
+ return res;
+ }
+ T *get() noexcept {
+ return ptr_;
+ }
+ const T *get() const noexcept {
+ return ptr_;
+ }
+ T *operator->() noexcept {
+ return ptr_;
+ }
+ const T *operator->() const noexcept {
+ return ptr_;
+ }
+ T &operator*() noexcept {
+ return *ptr_;
+ }
+ const T &operator*() const noexcept {
+ return *ptr_;
+ }
+ explicit operator bool() const noexcept {
+ return ptr_ != nullptr;
+ }
+
+ private:
+ T *ptr_{nullptr};
+};
+
+template <class T>
+bool operator==(std::nullptr_t, const unique_ptr<T> &p) {
+ return !p;
+}
+template <class T>
+bool operator==(const unique_ptr<T> &p, std::nullptr_t) {
+ return !p;
+}
+template <class T>
+bool operator!=(std::nullptr_t, const unique_ptr<T> &p) {
+ return static_cast<bool>(p);
+}
+template <class T>
+bool operator!=(const unique_ptr<T> &p, std::nullptr_t) {
+ return static_cast<bool>(p);
+}
+
+} // namespace tl
+/// @endcond
+
/**
* A smart wrapper to store a pointer to a TL-object.
*/
template <class Type>
-using tl_object_ptr = std::unique_ptr<Type>;
+using tl_object_ptr = tl::unique_ptr<Type>;
/**
* A function to create a dynamically allocated TL-object. Can be treated as an analogue of std::make_unique.
@@ -98,8 +189,8 @@ using tl_object_ptr = std::unique_ptr<Type>;
* \code
* auto get_authorization_state_request = td::make_tl_object<td::td_api::getAuthorizationState>();
* auto message_text = td::make_tl_object<td::td_api::formattedText>("Hello, world!!!",
- * std::vector<td::tl_object_ptr<td::td_api::textEntities>>());
- * auto send_message_request = td::make_tl_object<td::td_api::sendMessage>(chat_id, 0, false, false, nullptr,
+ * td::td_api::array<td::tl_object_ptr<td::td_api::textEntity>>());
+ * auto send_message_request = td::make_tl_object<td::td_api::sendMessage>(chat_id, 0, 0, nullptr, nullptr,
* td::make_tl_object<td::td_api::inputMessageText>(std::move(message_text), false, true));
* \endcode
*
@@ -108,7 +199,7 @@ using tl_object_ptr = std::unique_ptr<Type>;
* \return Wrapped pointer to the created TL-object.
*/
template <class Type, class... Args>
-tl_object_ptr<Type> make_tl_object(Args &&... args) {
+tl_object_ptr<Type> make_tl_object(Args &&...args) {
return tl_object_ptr<Type>(new Type(std::forward<Args>(args)...));
}
diff --git a/protocols/Telegram/tdlib/td/td/tl/tl_dotnet_object.h b/protocols/Telegram/tdlib/td/td/tl/tl_dotnet_object.h
index 57b18b22c1..d8a3abd43d 100644
--- a/protocols/Telegram/tdlib/td/td/tl/tl_dotnet_object.h
+++ b/protocols/Telegram/tdlib/td/td/tl/tl_dotnet_object.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,8 +8,10 @@
#include "td/utils/port/CxCli.h"
+#pragma managed(push, off)
#include "td/telegram/td_api.h"
#include "td/telegram/td_api.hpp"
+#pragma managed(pop)
namespace Telegram {
namespace Td {
@@ -63,7 +65,7 @@ inline String^ FromUnmanaged(const std::string &from) {
}
inline auto CLRCALL BytesFromUnmanaged(const std::string &from) {
- Array<byte>^ res = REF_NEW Vector<byte>(static_cast<ArrayIndexType>(from.size()));
+ Array<BYTE>^ res = REF_NEW Vector<BYTE>(static_cast<ArrayIndexType>(from.size()));
ArrayIndexType i = 0;
for (auto b : from) {
ArraySet(res, i++, b);
@@ -82,6 +84,16 @@ auto CLRCALL FromUnmanaged(std::vector<FromT> &vec) {
return res;
}
+inline auto CLRCALL BytesFromUnmanaged(const std::vector<std::string> &vec) {
+ using ToT = decltype(BytesFromUnmanaged(vec[0]));
+ Array<ToT>^ res = REF_NEW Vector<ToT>(static_cast<ArrayIndexType>(vec.size()));
+ ArrayIndexType i = 0;
+ for (auto &from : vec) {
+ ArraySet(res, i++, BytesFromUnmanaged(from));
+ }
+ return res;
+}
+
template <class T>
auto CLRCALL FromUnmanaged(td::td_api::object_ptr<T> &from) -> decltype(FromUnmanaged(*from.get())) {
if (!from) {
@@ -90,9 +102,11 @@ auto CLRCALL FromUnmanaged(td::td_api::object_ptr<T> &from) -> decltype(FromUnma
return FromUnmanaged(*from.get());
}
+#if TD_CLI
template <class ResT>
ref class CallFromUnmanagedRes {
public:
+ [System::ThreadStaticAttribute]
static property ResT res;
};
@@ -103,17 +117,29 @@ struct CallFromUnmanaged {
CallFromUnmanagedRes<ResT>::res = FromUnmanaged(val);
}
};
+#endif
+
+template <class ResT, class T>
+ResT DoFromUnmanaged(T &from) {
+#if TD_WINRT
+ ResT res;
+ downcast_call(from, [&](auto &from_downcasted) {
+ res = FromUnmanaged(from_downcasted);
+ });
+ return res;
+#elif TD_CLI
+ CallFromUnmanaged<ResT> res;
+ downcast_call(from, res);
+ return CallFromUnmanagedRes<ResT>::res;
+#endif
+}
inline BaseObject^ FromUnmanaged(td::td_api::Function &from) {
- CallFromUnmanaged<BaseObject^> res;
- downcast_call(from, res);
- return CallFromUnmanagedRes<BaseObject^>::res;
+ return DoFromUnmanaged<BaseObject^>(from);
}
inline BaseObject^ FromUnmanaged(td::td_api::Object &from) {
- CallFromUnmanaged<BaseObject^> res;
- downcast_call(from, res);
- return CallFromUnmanagedRes<BaseObject^>::res;
+ return DoFromUnmanaged<BaseObject^>(from);
}
// to unmanaged
@@ -136,7 +162,7 @@ inline std::string ToUnmanaged(String ^from) {
return string_to_unmanaged(from);
}
-inline std::string ToUnmanaged(Array<byte>^ from) {
+inline std::string ToUnmanaged(Array<BYTE>^ from) {
if (!from) {
return std::string();
}
diff --git a/protocols/Telegram/tdlib/td/td/tl/tl_jni_object.cpp b/protocols/Telegram/tdlib/td/td/tl/tl_jni_object.cpp
index 5074017b90..8ad678019c 100644
--- a/protocols/Telegram/tdlib/td/td/tl/tl_jni_object.cpp
+++ b/protocols/Telegram/tdlib/td/td/tl/tl_jni_object.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,9 +7,11 @@
#include "td/tl/tl_jni_object.h"
#include "td/utils/common.h"
+#include "td/utils/ExitGuard.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include <memory>
@@ -26,6 +28,7 @@ static jclass StringClass;
static jclass ObjectClass;
jclass ArrayKeyboardButtonClass;
jclass ArrayInlineKeyboardButtonClass;
+jclass ArrayPageBlockTableCellClass;
jmethodID GetConstructorID;
jmethodID BooleanGetValueMethodID;
jmethodID IntegerGetValueMethodID;
@@ -78,13 +81,16 @@ void register_native_method(JNIEnv *env, jclass clazz, std::string name, std::st
std::unique_ptr<JNIEnv, JvmThreadDetacher> get_jni_env(JavaVM *java_vm, jint jni_version) {
JNIEnv *env = nullptr;
- if (java_vm->GetEnv(reinterpret_cast<void **>(&env), jni_version) == JNI_EDETACHED) {
+ if (!ExitGuard::is_exited() && java_vm->GetEnv(reinterpret_cast<void **>(&env), jni_version) == JNI_EDETACHED) {
#ifdef JDK1_2 // if not Android JNI
auto p_env = reinterpret_cast<void **>(&env);
#else
auto p_env = &env;
#endif
- java_vm->AttachCurrentThread(p_env, nullptr);
+ if (java_vm->AttachCurrentThread(p_env, nullptr) != JNI_OK) {
+ java_vm = nullptr;
+ env = nullptr;
+ }
} else {
java_vm = nullptr;
}
@@ -103,6 +109,8 @@ void init_vars(JNIEnv *env, const char *td_api_java_package) {
get_jclass(env, (PSLICE() << "[L" << td_api_java_package << "/TdApi$KeyboardButton;").c_str());
ArrayInlineKeyboardButtonClass =
get_jclass(env, (PSLICE() << "[L" << td_api_java_package << "/TdApi$InlineKeyboardButton;").c_str());
+ ArrayPageBlockTableCellClass =
+ get_jclass(env, (PSLICE() << "[L" << td_api_java_package << "/TdApi$PageBlockTableCell;").c_str());
GetConstructorID = get_method_id(env, ObjectClass, "getConstructor", "()I");
BooleanGetValueMethodID = get_method_id(env, BooleanClass, "booleanValue", "()Z");
IntegerGetValueMethodID = get_method_id(env, IntegerClass, "intValue", "()I");
@@ -113,10 +121,10 @@ void init_vars(JNIEnv *env, const char *td_api_java_package) {
static size_t get_utf8_from_utf16_length(const jchar *p, jsize len) {
size_t result = 0;
for (jsize i = 0; i < len; i++) {
- unsigned int cur = p[i];
+ uint32 cur = p[i];
if ((cur & 0xF800) == 0xD800) {
if (i < len) {
- unsigned int next = p[++i];
+ uint32 next = p[++i];
if ((next & 0xFC00) == 0xDC00 && (cur & 0x400) == 0) {
result += 4;
continue;
@@ -133,8 +141,8 @@ static size_t get_utf8_from_utf16_length(const jchar *p, jsize len) {
static void utf16_to_utf8(const jchar *p, jsize len, char *res) {
for (jsize i = 0; i < len; i++) {
- unsigned int cur = p[i];
- // TODO conversion unsigned int -> signed char is implementation defined
+ uint32 cur = p[i];
+ // TODO conversion uint32 -> signed char is implementation defined
if (cur <= 0x7f) {
*res++ = static_cast<char>(cur);
} else if (cur <= 0x7ff) {
@@ -146,8 +154,8 @@ static void utf16_to_utf8(const jchar *p, jsize len, char *res) {
*res++ = static_cast<char>(0x80 | (cur & 0x3f));
} else {
// correctness is already checked
- unsigned int next = p[++i];
- unsigned int val = ((cur - 0xD800) << 10) + next - 0xDC00 + 0x10000;
+ uint32 next = p[++i];
+ uint32 val = ((cur - 0xD800) << 10) + next - 0xDC00 + 0x10000;
*res++ = static_cast<char>(0xf0 | (val >> 18));
*res++ = static_cast<char>(0x80 | ((val >> 12) & 0x3f));
@@ -170,14 +178,14 @@ static jsize get_utf16_from_utf8_length(const char *p, size_t len, jsize *surrog
static void utf8_to_utf16(const char *p, size_t len, jchar *res) {
// UTF-8 correctness is supposed
for (size_t i = 0; i < len;) {
- unsigned int a = static_cast<unsigned char>(p[i++]);
+ uint32 a = static_cast<unsigned char>(p[i++]);
if (a >= 0x80) {
- unsigned int b = static_cast<unsigned char>(p[i++]);
+ uint32 b = static_cast<unsigned char>(p[i++]);
if (a >= 0xe0) {
- unsigned int c = static_cast<unsigned char>(p[i++]);
+ uint32 c = static_cast<unsigned char>(p[i++]);
if (a >= 0xf0) {
- unsigned int d = static_cast<unsigned char>(p[i++]);
- unsigned int val = ((a & 0x07) << 18) + ((b & 0x3f) << 12) + ((c & 0x3f) << 6) + (d & 0x3f) - 0x10000;
+ uint32 d = static_cast<unsigned char>(p[i++]);
+ uint32 val = ((a & 0x07) << 18) + ((b & 0x3f) << 12) + ((c & 0x3f) << 6) + (d & 0x3f) - 0x10000;
*res++ = static_cast<jchar>(0xD800 + (val >> 10));
*res++ = static_cast<jchar>(0xDC00 + (val & 0x3ff));
} else {
@@ -205,7 +213,7 @@ std::string fetch_string(JNIEnv *env, jobject o, jfieldID id) {
std::string from_jstring(JNIEnv *env, jstring s) {
if (!s) {
- return "";
+ return std::string();
}
jsize s_len = env->GetStringLength(s);
const jchar *p = env->GetStringChars(s, nullptr);
@@ -256,7 +264,7 @@ std::string from_bytes(JNIEnv *env, jbyteArray arr) {
jbyteArray to_bytes(JNIEnv *env, const std::string &b) {
static_assert(sizeof(char) == sizeof(jbyte), "Mismatched jbyte size");
- jsize length = narrow_cast<jsize>(b.size());
+ auto length = narrow_cast<jsize>(b.size());
jbyteArray arr = env->NewByteArray(length);
if (arr != nullptr && length != 0) {
env->SetByteArrayRegion(arr, 0, length, reinterpret_cast<const jbyte *>(b.data()));
@@ -266,7 +274,7 @@ jbyteArray to_bytes(JNIEnv *env, const std::string &b) {
jintArray store_vector(JNIEnv *env, const std::vector<std::int32_t> &v) {
static_assert(sizeof(std::int32_t) == sizeof(jint), "Mismatched jint size");
- jsize length = narrow_cast<jsize>(v.size());
+ auto length = narrow_cast<jsize>(v.size());
jintArray arr = env->NewIntArray(length);
if (arr != nullptr && length != 0) {
env->SetIntArrayRegion(arr, 0, length, reinterpret_cast<const jint *>(&v[0]));
@@ -276,7 +284,7 @@ jintArray store_vector(JNIEnv *env, const std::vector<std::int32_t> &v) {
jlongArray store_vector(JNIEnv *env, const std::vector<std::int64_t> &v) {
static_assert(sizeof(std::int64_t) == sizeof(jlong), "Mismatched jlong size");
- jsize length = narrow_cast<jsize>(v.size());
+ auto length = narrow_cast<jsize>(v.size());
jlongArray arr = env->NewLongArray(length);
if (arr != nullptr && length != 0) {
env->SetLongArrayRegion(arr, 0, length, reinterpret_cast<const jlong *>(&v[0]));
@@ -286,7 +294,7 @@ jlongArray store_vector(JNIEnv *env, const std::vector<std::int64_t> &v) {
jdoubleArray store_vector(JNIEnv *env, const std::vector<double> &v) {
static_assert(sizeof(double) == sizeof(jdouble), "Mismatched jdouble size");
- jsize length = narrow_cast<jsize>(v.size());
+ auto length = narrow_cast<jsize>(v.size());
jdoubleArray arr = env->NewDoubleArray(length);
if (arr != nullptr && length != 0) {
env->SetDoubleArrayRegion(arr, 0, length, reinterpret_cast<const jdouble *>(&v[0]));
@@ -295,7 +303,7 @@ jdoubleArray store_vector(JNIEnv *env, const std::vector<double> &v) {
}
jobjectArray store_vector(JNIEnv *env, const std::vector<std::string> &v) {
- jsize length = narrow_cast<jsize>(v.size());
+ auto length = narrow_cast<jsize>(v.size());
jobjectArray arr = env->NewObjectArray(length, StringClass, 0);
if (arr != nullptr) {
for (jsize i = 0; i < length; i++) {
diff --git a/protocols/Telegram/tdlib/td/td/tl/tl_jni_object.h b/protocols/Telegram/tdlib/td/td/tl/tl_jni_object.h
index 418d47df9e..85387aa6b8 100644
--- a/protocols/Telegram/tdlib/td/td/tl/tl_jni_object.h
+++ b/protocols/Telegram/tdlib/td/td/tl/tl_jni_object.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -17,6 +17,7 @@ namespace td {
namespace td_api {
class keyboardButton;
class inlineKeyboardButton;
+class pageBlockTableCell;
} // namespace td_api
namespace jni {
@@ -25,6 +26,7 @@ extern thread_local bool parse_error;
extern jclass ArrayKeyboardButtonClass;
extern jclass ArrayInlineKeyboardButtonClass;
+extern jclass ArrayPageBlockTableCellClass;
extern jmethodID GetConstructorID;
extern jmethodID BooleanGetValueMethodID;
extern jmethodID IntegerGetValueMethodID;
@@ -105,7 +107,7 @@ jobjectArray store_vector(JNIEnv *env, const std::vector<std::string> &v);
template <class T>
jobjectArray store_vector(JNIEnv *env, const std::vector<T> &v) {
- jint length = static_cast<jint>(v.size());
+ auto length = static_cast<jint>(v.size());
jobjectArray arr = env->NewObjectArray(length, T::element_type::Class, jobject());
if (arr != nullptr) {
for (jint i = 0; i < length; i++) {
@@ -143,9 +145,17 @@ class get_array_class<td_api::inlineKeyboardButton> {
}
};
+template <>
+class get_array_class<td_api::pageBlockTableCell> {
+ public:
+ static jclass get() {
+ return ArrayPageBlockTableCellClass;
+ }
+};
+
template <class T>
jobjectArray store_vector(JNIEnv *env, const std::vector<std::vector<T>> &v) {
- jint length = static_cast<jint>(v.size());
+ auto length = static_cast<jint>(v.size());
jobjectArray arr = env->NewObjectArray(length, get_array_class<typename T::element_type>::get(), 0);
if (arr != nullptr) {
for (jint i = 0; i < length; i++) {
@@ -200,7 +210,7 @@ struct FetchVector<std::string> {
result.reserve(length);
for (jsize i = 0; i < length; i++) {
jstring str = (jstring)env->GetObjectArrayElement(arr, i);
- result.push_back(jni::from_jstring(env, str));
+ result.push_back(from_jstring(env, str));
if (str) {
env->DeleteLocalRef(str);
}
@@ -211,6 +221,26 @@ struct FetchVector<std::string> {
}
};
+template <>
+struct FetchVector<jbyteArray> {
+ static std::vector<std::string> fetch(JNIEnv *env, jobjectArray arr) {
+ std::vector<std::string> result;
+ if (arr != nullptr) {
+ jsize length = env->GetArrayLength(arr);
+ result.reserve(length);
+ for (jsize i = 0; i < length; i++) {
+ jbyteArray bytes = (jbyteArray)env->GetObjectArrayElement(arr, i);
+ result.push_back(from_bytes(env, bytes));
+ if (bytes) {
+ env->DeleteLocalRef(bytes);
+ }
+ }
+ env->DeleteLocalRef(arr);
+ }
+ return result;
+ }
+};
+
template <class T>
struct FetchVector<std::vector<T>> {
static auto fetch(JNIEnv *env, jobjectArray arr) {
diff --git a/protocols/Telegram/tdlib/td/td/tl/tl_json.h b/protocols/Telegram/tdlib/td/td/tl/tl_json.h
index f8f21203da..fb7a4a1e63 100644
--- a/protocols/Telegram/tdlib/td/td/tl/tl_json.h
+++ b/protocols/Telegram/tdlib/td/td/tl/tl_json.h
@@ -1,47 +1,26 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/tl/TlObject.h"
+
#include "td/utils/base64.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
-#include "td/utils/tl_storers.h"
-
-#include "td/telegram/td_api.h"
-#include "td/telegram/td_api.hpp"
+#include "td/utils/TlDowncastHelper.h"
#include <type_traits>
namespace td {
-template <class T>
-class ToJsonImpl : public Jsonable {
- public:
- explicit ToJsonImpl(const T &value) : value_(value) {
- }
- void store(JsonValueScope *scope) const {
- to_json(*scope, value_);
- }
-
- private:
- const T &value_;
-};
-
-template <class T>
-auto ToJson(const T &value) {
- return ToJsonImpl<T>(value);
-}
-
-template <class T>
-void to_json(JsonValueScope &jv, const T &value) {
- jv << value;
-}
struct JsonInt64 {
int64 value;
@@ -50,6 +29,7 @@ struct JsonInt64 {
inline void to_json(JsonValueScope &jv, const JsonInt64 json_int64) {
jv << JsonString(PSLICE() << json_int64.value);
}
+
struct JsonVectorInt64 {
const std::vector<int64> &value;
};
@@ -61,13 +41,6 @@ inline void to_json(JsonValueScope &jv, const JsonVectorInt64 &vec) {
}
}
-inline void to_json(JsonValueScope &jv, const td_api::Object &object) {
- td_api::downcast_call(const_cast<td_api::Object &>(object), [&jv](const auto &object) { to_json(jv, object); });
-}
-inline void to_json(JsonValueScope &jv, const td_api::Function &object) {
- td_api::downcast_call(const_cast<td_api::Function &>(object), [&jv](const auto &object) { to_json(jv, object); });
-}
-
template <class T>
void to_json(JsonValueScope &jv, const tl_object_ptr<T> &value) {
if (value) {
@@ -85,101 +58,106 @@ void to_json(JsonValueScope &jv, const std::vector<T> &v) {
}
}
-inline Status from_json(int32 &to, JsonValue &from) {
+inline Status from_json(int32 &to, JsonValue from) {
if (from.type() != JsonValue::Type::Number && from.type() != JsonValue::Type::String) {
- return Status::Error(PSLICE() << "Expected number, got " << from.type());
+ if (from.type() == JsonValue::Type::Null) {
+ return Status::OK();
+ }
+ return Status::Error(PSLICE() << "Expected Number, got " << from.type());
}
Slice number = from.type() == JsonValue::Type::String ? from.get_string() : from.get_number();
- TRY_RESULT(res, to_integer_safe<int32>(number));
- to = res;
+ TRY_RESULT_ASSIGN(to, to_integer_safe<int32>(number));
return Status::OK();
}
-inline Status from_json(bool &to, JsonValue &from) {
- if (from.type() != JsonValue::Type::Boolean) {
- int32 x;
- auto status = from_json(x, from);
+inline Status from_json(bool &to, JsonValue from) {
+ auto from_type = from.type();
+ if (from_type != JsonValue::Type::Boolean) {
+ if (from_type == JsonValue::Type::Null) {
+ return Status::OK();
+ }
+ int32 x = 0;
+ auto status = from_json(x, std::move(from));
if (status.is_ok()) {
to = x != 0;
return Status::OK();
}
- return Status::Error(PSLICE() << "Expected bool, got " << from.type());
+ return Status::Error(PSLICE() << "Expected Boolean, got " << from_type);
}
to = from.get_boolean();
return Status::OK();
}
-inline Status from_json(int64 &to, JsonValue &from) {
+inline Status from_json(int64 &to, JsonValue from) {
if (from.type() != JsonValue::Type::Number && from.type() != JsonValue::Type::String) {
- return Status::Error(PSLICE() << "Expected number, got " << from.type());
+ if (from.type() == JsonValue::Type::Null) {
+ return Status::OK();
+ }
+ return Status::Error(PSLICE() << "Expected String or Number, got " << from.type());
}
Slice number = from.type() == JsonValue::Type::String ? from.get_string() : from.get_number();
- TRY_RESULT(res, to_integer_safe<int64>(number));
- to = res;
+ TRY_RESULT_ASSIGN(to, to_integer_safe<int64>(number));
return Status::OK();
}
-inline Status from_json(double &to, JsonValue &from) {
+
+inline Status from_json(double &to, JsonValue from) {
if (from.type() != JsonValue::Type::Number) {
- return Status::Error(PSLICE() << "Expected number, got " << from.type());
+ if (from.type() == JsonValue::Type::Null) {
+ return Status::OK();
+ }
+ return Status::Error(PSLICE() << "Expected Number, got " << from.type());
}
to = to_double(from.get_number());
return Status::OK();
}
-inline Status from_json(string &to, JsonValue &from) {
+inline Status from_json(string &to, JsonValue from) {
if (from.type() != JsonValue::Type::String) {
- return Status::Error(PSLICE() << "Expected string, got " << from.type());
+ if (from.type() == JsonValue::Type::Null) {
+ return Status::OK();
+ }
+ return Status::Error(PSLICE() << "Expected String, got " << from.type());
}
to = from.get_string().str();
return Status::OK();
}
-inline Status from_json_bytes(string &to, JsonValue &from) {
+inline Status from_json_bytes(string &to, JsonValue from) {
if (from.type() != JsonValue::Type::String) {
- return Status::Error(PSLICE() << "Expected string, got " << from.type());
+ if (from.type() == JsonValue::Type::Null) {
+ return Status::OK();
+ }
+ return Status::Error(PSLICE() << "Expected String, got " << from.type());
}
- TRY_RESULT(decoded, base64_decode(from.get_string()));
- to = std::move(decoded);
+ TRY_RESULT_ASSIGN(to, base64_decode(from.get_string()));
return Status::OK();
}
template <class T>
-Status from_json(std::vector<T> &to, JsonValue &from) {
+Status from_json(std::vector<T> &to, JsonValue from) {
if (from.type() != JsonValue::Type::Array) {
- return Status::Error(PSLICE() << "Expected array, got " << from.type());
+ if (from.type() == JsonValue::Type::Null) {
+ return Status::OK();
+ }
+ return Status::Error(PSLICE() << "Expected Array, got " << from.type());
}
to = std::vector<T>(from.get_array().size());
size_t i = 0;
for (auto &value : from.get_array()) {
- TRY_STATUS(from_json(to[i], value));
+ TRY_STATUS(from_json(to[i], std::move(value)));
i++;
}
return Status::OK();
}
template <class T>
-class DowncastHelper : public T {
- public:
- explicit DowncastHelper(int32 constructor) : constructor_(constructor) {
- }
- int32 get_id() const override {
- return constructor_;
- }
- void store(TlStorerToString &s, const char *field_name) const override {
- }
-
- private:
- int32 constructor_{0};
-};
-
-template <class T>
-std::enable_if_t<!std::is_constructible<T>::value, Status> from_json(tl_object_ptr<T> &to, JsonValue &from) {
+std::enable_if_t<!std::is_constructible<T>::value, Status> from_json(tl_object_ptr<T> &to, JsonValue from) {
if (from.type() != JsonValue::Type::Object) {
if (from.type() == JsonValue::Type::Null) {
to = nullptr;
return Status::OK();
}
- return Status::Error(PSLICE() << "Expected object, got " << from.type());
+ return Status::Error(PSLICE() << "Expected Object, got " << from.type());
}
auto &object = from.get_object();
@@ -188,13 +166,12 @@ std::enable_if_t<!std::is_constructible<T>::value, Status> from_json(tl_object_p
if (constructor_value.type() == JsonValue::Type::Number) {
constructor = to_integer<int32>(constructor_value.get_number());
} else if (constructor_value.type() == JsonValue::Type::String) {
- TRY_RESULT(t_constructor, tl_constructor_from_string(to.get(), constructor_value.get_string().str()));
- constructor = t_constructor;
+ TRY_RESULT_ASSIGN(constructor, tl_constructor_from_string(to.get(), constructor_value.get_string().str()));
} else {
- return Status::Error(PSLICE() << "Expected string or int, got " << constructor_value.type());
+ return Status::Error(PSLICE() << "Expected String or Integer, got " << constructor_value.type());
}
- DowncastHelper<T> helper(constructor);
+ TlDowncastHelper<T> helper(constructor);
Status status;
bool ok = downcast_call(static_cast<T &>(helper), [&](auto &dummy) {
auto result = make_tl_object<std::decay_t<decltype(dummy)>>();
@@ -210,13 +187,13 @@ std::enable_if_t<!std::is_constructible<T>::value, Status> from_json(tl_object_p
}
template <class T>
-std::enable_if_t<std::is_constructible<T>::value, Status> from_json(tl_object_ptr<T> &to, JsonValue &from) {
+std::enable_if_t<std::is_constructible<T>::value, Status> from_json(tl_object_ptr<T> &to, JsonValue from) {
if (from.type() != JsonValue::Type::Object) {
if (from.type() == JsonValue::Type::Null) {
to = nullptr;
return Status::OK();
}
- return Status::Error(PSLICE() << "Expected object, got " << from.type());
+ return Status::Error(PSLICE() << "Expected Object, got " << from.type());
}
to = make_tl_object<T>();
return from_json(*to, from.get_object());
diff --git a/protocols/Telegram/tdlib/td/td/tl/tl_object_parse.h b/protocols/Telegram/tdlib/td/td/tl/tl_object_parse.h
index 953e0940b3..c34c2af5a1 100644
--- a/protocols/Telegram/tdlib/td/td/tl/tl_object_parse.h
+++ b/protocols/Telegram/tdlib/td/td/tl/tl_object_parse.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,10 +8,10 @@
#include "td/tl/TlObject.h"
-#include "td/utils/int_types.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/UInt.h"
#include <cstdint>
-#include <memory>
#include <string>
#include <vector>
@@ -21,19 +21,21 @@ template <class Func, std::int32_t constructor_id>
class TlFetchBoxed {
public:
template <class ParserT>
- static auto parse(ParserT &p) -> decltype(Func::parse(p)) {
- if (p.fetch_int() != constructor_id) {
- p.set_error("Wrong constructor found");
- return decltype(Func::parse(p))();
+ static auto parse(ParserT &parser) -> decltype(Func::parse(parser)) {
+ auto parsed_constructor_id = parser.fetch_int();
+ if (parsed_constructor_id != constructor_id) {
+ parser.set_error(PSTRING() << "Wrong constructor " << parsed_constructor_id << " found instead of "
+ << constructor_id);
+ return decltype(Func::parse(parser))();
}
- return Func::parse(p);
+ return Func::parse(parser);
}
};
class TlFetchTrue {
public:
template <class ParserT>
- static bool parse(ParserT &p) {
+ static bool parse(ParserT &parser) {
return true;
}
};
@@ -41,16 +43,16 @@ class TlFetchTrue {
class TlFetchBool {
public:
template <class ParserT>
- static bool parse(ParserT &p) {
+ static bool parse(ParserT &parser) {
constexpr std::int32_t ID_BOOL_FALSE = 0xbc799737;
constexpr std::int32_t ID_BOOL_TRUE = 0x997275b5;
- std::int32_t c = p.fetch_int();
+ std::int32_t c = parser.fetch_int();
if (c == ID_BOOL_TRUE) {
return true;
}
if (c != ID_BOOL_FALSE) {
- p.set_error("Bool expected");
+ parser.set_error("Bool expected");
}
return false;
}
@@ -59,40 +61,40 @@ class TlFetchBool {
class TlFetchInt {
public:
template <class ParserT>
- static std::int32_t parse(ParserT &p) {
- return p.fetch_int();
+ static std::int32_t parse(ParserT &parser) {
+ return parser.fetch_int();
}
};
class TlFetchLong {
public:
template <class ParserT>
- static std::int64_t parse(ParserT &p) {
- return p.fetch_long();
+ static std::int64_t parse(ParserT &parser) {
+ return parser.fetch_long();
}
};
class TlFetchDouble {
public:
template <class ParserT>
- static double parse(ParserT &p) {
- return p.fetch_double();
+ static double parse(ParserT &parser) {
+ return parser.fetch_double();
}
};
class TlFetchInt128 {
public:
template <class ParserT>
- static UInt128 parse(ParserT &p) {
- return p.template fetch_binary<UInt128>();
+ static UInt128 parse(ParserT &parser) {
+ return parser.template fetch_binary<UInt128>();
}
};
class TlFetchInt256 {
public:
template <class ParserT>
- static UInt256 parse(ParserT &p) {
- return p.template fetch_binary<UInt256>();
+ static UInt256 parse(ParserT &parser) {
+ return parser.template fetch_binary<UInt256>();
}
};
@@ -100,8 +102,8 @@ template <class T>
class TlFetchString {
public:
template <class ParserT>
- static T parse(ParserT &p) {
- return p.template fetch_string<T>();
+ static T parse(ParserT &parser) {
+ return parser.template fetch_string<T>();
}
};
@@ -109,8 +111,8 @@ template <class T>
class TlFetchBytes {
public:
template <class ParserT>
- static T parse(ParserT &p) {
- return p.template fetch_string<T>();
+ static T parse(ParserT &parser) {
+ return parser.template fetch_string<T>();
}
};
@@ -118,15 +120,15 @@ template <class Func>
class TlFetchVector {
public:
template <class ParserT>
- static auto parse(ParserT &p) -> std::vector<decltype(Func::parse(p))> {
- const std::uint32_t multiplicity = p.fetch_int();
- std::vector<decltype(Func::parse(p))> v;
- if (p.get_left_len() < multiplicity) {
- p.set_error("Wrong vector length");
+ static auto parse(ParserT &parser) -> std::vector<decltype(Func::parse(parser))> {
+ const std::uint32_t multiplicity = parser.fetch_int();
+ std::vector<decltype(Func::parse(parser))> v;
+ if (parser.get_left_len() < multiplicity) {
+ parser.set_error("Wrong vector length");
} else {
v.reserve(multiplicity);
for (std::uint32_t i = 0; i < multiplicity; i++) {
- v.push_back(Func::parse(p));
+ v.push_back(Func::parse(parser));
}
}
return v;
@@ -137,8 +139,8 @@ template <class T>
class TlFetchObject {
public:
template <class ParserT>
- static tl_object_ptr<T> parse(ParserT &p) {
- return T::fetch(p);
+ static tl_object_ptr<T> parse(ParserT &parser) {
+ return T::fetch(parser);
}
};
diff --git a/protocols/Telegram/tdlib/td/td/tl/tl_object_store.h b/protocols/Telegram/tdlib/td/td/tl/tl_object_store.h
index f6cd376692..0fdd1dcda6 100644
--- a/protocols/Telegram/tdlib/td/td/tl/tl_object_store.h
+++ b/protocols/Telegram/tdlib/td/td/tl/tl_object_store.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,7 +11,6 @@
#include "td/utils/misc.h"
#include <cstdint>
-#include <memory>
#include <string>
#include <vector>
@@ -21,9 +20,9 @@ template <class Func, std::int32_t constructor_id>
class TlStoreBoxed {
public:
template <class T, class StorerT>
- static void store(const T &x, StorerT &s) {
- s.store_binary(constructor_id);
- Func::store(x, s);
+ static void store(const T &x, StorerT &storer) {
+ storer.store_binary(constructor_id);
+ Func::store(x, storer);
}
};
@@ -31,44 +30,36 @@ template <class Func>
class TlStoreBoxedUnknown {
public:
template <class T, class StorerT>
- static void store(const T &x, StorerT &s) {
- s.store_binary(x->get_id());
- Func::store(x, s);
+ static void store(const T &x, StorerT &storer) {
+ storer.store_binary(x->get_id());
+ Func::store(x, storer);
}
};
class TlStoreBool {
public:
template <class StorerT>
- static void store(const bool &x, StorerT &s) {
+ static void store(const bool &x, StorerT &storer) {
constexpr std::int32_t ID_BOOL_FALSE = 0xbc799737;
constexpr std::int32_t ID_BOOL_TRUE = 0x997275b5;
- s.store_binary(x ? ID_BOOL_TRUE : ID_BOOL_FALSE);
- }
-};
-
-class TlStoreTrue {
- public:
- template <class StorerT>
- static void store(const bool &x, StorerT &s) {
- // currently nothing to do
+ storer.store_binary(x ? ID_BOOL_TRUE : ID_BOOL_FALSE);
}
};
class TlStoreBinary {
public:
template <class T, class StorerT>
- static void store(const T &x, StorerT &s) {
- s.store_binary(x);
+ static void store(const T &x, StorerT &storer) {
+ storer.store_binary(x);
}
};
class TlStoreString {
public:
template <class T, class StorerT>
- static void store(const T &x, StorerT &s) {
- s.store_string(x);
+ static void store(const T &x, StorerT &storer) {
+ storer.store_string(x);
}
};
@@ -76,10 +67,10 @@ template <class Func>
class TlStoreVector {
public:
template <class T, class StorerT>
- static void store(const T &vec, StorerT &s) {
- s.store_binary(narrow_cast<int32>(vec.size()));
+ static void store(const T &vec, StorerT &storer) {
+ storer.store_binary(narrow_cast<int32>(vec.size()));
for (auto &val : vec) {
- Func::store(val, s);
+ Func::store(val, storer);
}
}
};
@@ -87,8 +78,8 @@ class TlStoreVector {
class TlStoreObject {
public:
template <class T, class StorerT>
- static void store(const tl_object_ptr<T> &obj, StorerT &s) {
- return obj->store(s);
+ static void store(const tl_object_ptr<T> &obj, StorerT &storer) {
+ return obj->store(storer);
}
};
diff --git a/protocols/Telegram/tdlib/td/tdactor/CMakeLists.txt b/protocols/Telegram/tdlib/td/tdactor/CMakeLists.txt
index c0c83025e5..a156384ce9 100644
--- a/protocols/Telegram/tdlib/td/tdactor/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/tdactor/CMakeLists.txt
@@ -1,14 +1,20 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
+
+if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
+ set(CMAKE_INSTALL_LIBDIR "lib")
+endif()
#SOURCE SETS
set(TDACTOR_SOURCE
- td/actor/impl/ConcurrentScheduler.cpp
+ td/actor/ConcurrentScheduler.cpp
td/actor/impl/Scheduler.cpp
td/actor/MultiPromise.cpp
- td/actor/Timeout.cpp
-
- td/actor/impl2/Scheduler.cpp
+ td/actor/MultiTimeout.cpp
+ td/actor/actor.h
+ td/actor/ConcurrentScheduler.h
td/actor/impl/Actor-decl.h
td/actor/impl/Actor.h
td/actor/impl/ActorId-decl.h
@@ -17,28 +23,19 @@ set(TDACTOR_SOURCE
td/actor/impl/ActorInfo.h
td/actor/impl/EventFull-decl.h
td/actor/impl/EventFull.h
- td/actor/impl/ConcurrentScheduler.h
td/actor/impl/Event.h
td/actor/impl/Scheduler-decl.h
td/actor/impl/Scheduler.h
- td/actor/Condition.h
td/actor/MultiPromise.h
+ td/actor/MultiTimeout.h
td/actor/PromiseFuture.h
td/actor/SchedulerLocalStorage.h
td/actor/SignalSlot.h
td/actor/SleepActor.h
td/actor/Timeout.h
- td/actor/actor.h
-
- td/actor/impl2/ActorLocker.h
- td/actor/impl2/ActorSignals.h
- td/actor/impl2/ActorState.h
- td/actor/impl2/Scheduler.h
- td/actor/impl2/SchedulerId.h
)
set(TDACTOR_TEST_SOURCE
- ${CMAKE_CURRENT_SOURCE_DIR}/test/actors_impl2.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/actors_main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/actors_simple.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/actors_workers.cpp
@@ -54,12 +51,12 @@ add_library(tdactor STATIC ${TDACTOR_SOURCE})
target_include_directories(tdactor PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_link_libraries(tdactor PUBLIC tdutils)
-add_executable(example example/example.cpp)
-target_link_libraries(example PRIVATE tdactor)
+if (NOT CMAKE_CROSSCOMPILING)
+ add_executable(example example/example.cpp)
+ target_link_libraries(example PRIVATE tdactor)
+endif()
install(TARGETS tdactor EXPORT TdTargets
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- RUNTIME DESTINATION bin
- INCLUDES DESTINATION include
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
diff --git a/protocols/Telegram/tdlib/td/tdactor/example/example.cpp b/protocols/Telegram/tdlib/td/tdactor/example/example.cpp
index 4c2415c5e2..8f182d8350 100644
--- a/protocols/Telegram/tdlib/td/tdactor/example/example.cpp
+++ b/protocols/Telegram/tdlib/td/tdactor/example/example.cpp
@@ -1,31 +1,33 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
#include "td/utils/logging.h"
+#include "td/utils/Time.h"
-class Worker : public td::Actor {
+class Worker final : public td::Actor {
public:
void ping(int x) {
- LOG(ERROR) << "got ping " << x;
+ LOG(ERROR) << "Got ping " << x;
}
};
-class MainActor : public td::Actor {
+class MainActor final : public td::Actor {
public:
- void start_up() override {
- LOG(ERROR) << "start up";
+ void start_up() final {
+ LOG(ERROR) << "Start up";
set_timeout_in(10);
worker_ = td::create_actor_on_scheduler<Worker>("Worker", 1);
send_closure(worker_, &Worker::ping, 123);
}
- void timeout_expired() override {
- LOG(ERROR) << "timeout expired";
+ void timeout_expired() final {
+ LOG(ERROR) << "Timeout expired";
td::Scheduler::instance()->finish();
}
@@ -33,17 +35,15 @@ class MainActor : public td::Actor {
td::ActorOwn<Worker> worker_;
};
-int main(void) {
- td::ConcurrentScheduler scheduler;
- scheduler.init(4 /*threads_count*/);
+int main() {
+ td::ConcurrentScheduler scheduler(4 /*thread_count*/, 0);
scheduler.start();
{
- auto guard = scheduler.get_current_guard();
+ auto guard = scheduler.get_main_guard();
td::create_actor_on_scheduler<MainActor>("Main actor", 0).release();
}
while (!scheduler.is_finished()) {
- scheduler.run_main(10);
+ scheduler.run_main(td::Timestamp::in(10));
}
scheduler.finish();
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/ConcurrentScheduler.cpp b/protocols/Telegram/tdlib/td/tdactor/td/actor/ConcurrentScheduler.cpp
new file mode 100644
index 0000000000..9a3cf21338
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/ConcurrentScheduler.cpp
@@ -0,0 +1,205 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/actor/ConcurrentScheduler.h"
+
+#include "td/utils/ExitGuard.h"
+#include "td/utils/MpscPollableQueue.h"
+#include "td/utils/port/thread_local.h"
+#include "td/utils/ScopeGuard.h"
+
+#include <memory>
+
+namespace td {
+
+ConcurrentScheduler::ConcurrentScheduler(int32 additional_thread_count, uint64 thread_affinity_mask) {
+#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
+ additional_thread_count = 0;
+#endif
+ additional_thread_count++;
+ std::vector<std::shared_ptr<MpscPollableQueue<EventFull>>> outbound(additional_thread_count);
+#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
+ for (int32 i = 0; i < additional_thread_count; i++) {
+ auto queue = std::make_shared<MpscPollableQueue<EventFull>>();
+ queue->init();
+ outbound[i] = queue;
+ }
+ thread_affinity_mask_ = thread_affinity_mask;
+#endif
+
+ // +1 for extra scheduler for IOCP and send_closure from unrelated threads
+ // It will know about other schedulers
+ // Other schedulers will have no idea about its existence
+ extra_scheduler_ = 1;
+#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
+ extra_scheduler_ = 0;
+#endif
+
+ schedulers_.resize(additional_thread_count + extra_scheduler_);
+ for (int32 i = 0; i < additional_thread_count + extra_scheduler_; i++) {
+ auto &sched = schedulers_[i];
+ sched = make_unique<Scheduler>();
+
+#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
+ if (i >= additional_thread_count) {
+ auto queue = std::make_shared<MpscPollableQueue<EventFull>>();
+ queue->init();
+ outbound.push_back(std::move(queue));
+ }
+#endif
+
+ sched->init(i, outbound, static_cast<Scheduler::Callback *>(this));
+ }
+
+#if TD_PORT_WINDOWS
+ iocp_ = make_unique<detail::Iocp>();
+ iocp_->init();
+#endif
+
+ state_ = State::Start;
+}
+
+void ConcurrentScheduler::test_one_thread_run() {
+ do {
+ for (auto &sched : schedulers_) {
+ sched->run(Timestamp::now_cached());
+ }
+ } while (!is_finished_.load(std::memory_order_relaxed));
+}
+
+#if !TD_THREAD_UNSUPPORTED
+thread::id ConcurrentScheduler::get_scheduler_thread_id(int32 sched_id) {
+ auto thread_pos = static_cast<size_t>(sched_id - 1);
+ CHECK(thread_pos < threads_.size());
+ return threads_[thread_pos].get_id();
+}
+#endif
+
+void ConcurrentScheduler::start() {
+ CHECK(state_ == State::Start);
+ is_finished_.store(false, std::memory_order_relaxed);
+#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
+ for (size_t i = 1; i + extra_scheduler_ < schedulers_.size(); i++) {
+ auto &sched = schedulers_[i];
+ threads_.push_back(td::thread([&, thread_affinity_mask = thread_affinity_mask_] {
+#if TD_PORT_WINDOWS
+ detail::Iocp::Guard iocp_guard(iocp_.get());
+#endif
+#if TD_HAVE_THREAD_AFFINITY
+ if (thread_affinity_mask != 0) {
+ thread::set_affinity_mask(this_thread::get_id(), thread_affinity_mask).ignore();
+ }
+#else
+ (void)thread_affinity_mask;
+#endif
+ while (!is_finished()) {
+ sched->run(Timestamp::in(10));
+ }
+ }));
+ }
+#if TD_PORT_WINDOWS
+ iocp_thread_ = td::thread([this] {
+ auto guard = this->get_send_guard();
+ this->iocp_->loop();
+ });
+#endif
+#endif
+
+ state_ = State::Run;
+}
+
+static TD_THREAD_LOCAL double emscripten_timeout;
+
+bool ConcurrentScheduler::run_main(Timestamp timeout) {
+ CHECK(state_ == State::Run);
+ // run main scheduler in same thread
+ auto &main_sched = schedulers_[0];
+ if (!is_finished()) {
+#if TD_PORT_WINDOWS
+ detail::Iocp::Guard iocp_guard(iocp_.get());
+#endif
+ main_sched->run(timeout);
+ }
+
+ // hack for emscripten
+ emscripten_timeout = get_main_timeout().at();
+
+ return !is_finished();
+}
+
+Timestamp ConcurrentScheduler::get_main_timeout() {
+ CHECK(state_ == State::Run);
+ return schedulers_[0]->get_timeout();
+}
+
+double ConcurrentScheduler::emscripten_get_main_timeout() {
+ return Timestamp::at(emscripten_timeout).in();
+}
+void ConcurrentScheduler::emscripten_clear_main_timeout() {
+ emscripten_timeout = 0;
+}
+
+void ConcurrentScheduler::finish() {
+ CHECK(state_ == State::Run);
+ if (!is_finished()) {
+ on_finish();
+ }
+#if TD_PORT_WINDOWS
+ SCOPE_EXIT {
+ iocp_->clear();
+ };
+ detail::Iocp::Guard iocp_guard(iocp_.get());
+#endif
+
+ if (ExitGuard::is_exited()) {
+#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
+ // prevent closing of schedulers from already killed by OS threads
+ for (auto &thread : threads_) {
+ thread.detach();
+ }
+#endif
+
+#if TD_PORT_WINDOWS
+ iocp_->interrupt_loop();
+ iocp_thread_.detach();
+#endif
+ return;
+ }
+
+#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
+ for (auto &thread : threads_) {
+ thread.join();
+ }
+ threads_.clear();
+#endif
+
+#if TD_PORT_WINDOWS
+ iocp_->interrupt_loop();
+ iocp_thread_.join();
+#endif
+
+ schedulers_.clear();
+ for (auto &f : at_finish_) {
+ f();
+ }
+ at_finish_.clear();
+
+ state_ = State::Start;
+}
+
+void ConcurrentScheduler::on_finish() {
+ is_finished_.store(true, std::memory_order_relaxed);
+ for (auto &it : schedulers_) {
+ it->wakeup();
+ }
+}
+
+void ConcurrentScheduler::register_at_finish(std::function<void()> f) {
+ std::lock_guard<std::mutex> lock(at_finish_mutex_);
+ at_finish_.push_back(std::move(f));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ConcurrentScheduler.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/ConcurrentScheduler.h
index 1e9793eab4..3f574de5ef 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ConcurrentScheduler.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/ConcurrentScheduler.h
@@ -1,17 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/impl/Scheduler-decl.h"
+#include "td/actor/actor.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include "td/utils/port/thread.h"
#include "td/utils/Slice.h"
+#include "td/utils/Time.h"
+
+#if TD_PORT_WINDOWS
+#include "td/utils/port/detail/Iocp.h"
+#endif
#include <atomic>
#include <functional>
@@ -20,34 +24,55 @@
namespace td {
-class ConcurrentScheduler : private Scheduler::Callback {
+class ConcurrentScheduler final : private Scheduler::Callback {
public:
- void init(int32 threads_n);
+ explicit ConcurrentScheduler(int32 additional_thread_count, uint64 thread_affinity_mask = 0);
void finish_async() {
schedulers_[0]->finish();
}
+
void wakeup() {
schedulers_[0]->wakeup();
}
- SchedulerGuard get_current_guard() {
+
+ SchedulerGuard get_main_guard() {
return schedulers_[0]->get_guard();
}
+ SchedulerGuard get_send_guard() {
+ return schedulers_.back()->get_const_guard();
+ }
+
void test_one_thread_run();
- bool is_finished() {
+ bool is_finished() const {
return is_finished_.load(std::memory_order_relaxed);
}
+#if TD_THREAD_UNSUPPORTED
+ int get_scheduler_thread_id(int32 sched_id) {
+ return 1;
+ }
+#else
+ thread::id get_scheduler_thread_id(int32 sched_id);
+#endif
+
void start();
- bool run_main(double timeout);
+ bool run_main(double timeout) {
+ return run_main(Timestamp::in(timeout));
+ }
+ bool run_main(Timestamp timeout);
+
+ Timestamp get_main_timeout();
+ static double emscripten_get_main_timeout();
+ static void emscripten_clear_main_timeout();
void finish();
template <class ActorT, class... Args>
- ActorOwn<ActorT> create_actor_unsafe(int32 sched_id, Slice name, Args &&... args) {
+ ActorOwn<ActorT> create_actor_unsafe(int32 sched_id, Slice name, Args &&...args) {
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
sched_id = 0;
#endif
@@ -68,26 +93,24 @@ class ConcurrentScheduler : private Scheduler::Callback {
private:
enum class State { Start, Run };
- State state_;
- std::vector<unique_ptr<Scheduler>> schedulers_;
- std::atomic<bool> is_finished_;
+ State state_ = State::Start;
std::mutex at_finish_mutex_;
- std::vector<std::function<void()>> at_finish_;
+ vector<std::function<void()>> at_finish_; // can be used during destruction by Scheduler destructors
+ vector<unique_ptr<Scheduler>> schedulers_;
+ std::atomic<bool> is_finished_{false};
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
- std::vector<thread> threads_;
+ vector<td::thread> threads_;
+ uint64 thread_affinity_mask_ = 0;
#endif
+#if TD_PORT_WINDOWS
+ unique_ptr<detail::Iocp> iocp_;
+ td::thread iocp_thread_;
+#endif
+ int32 extra_scheduler_ = 0;
- void on_finish() override {
- is_finished_.store(true, std::memory_order_relaxed);
- for (auto &it : schedulers_) {
- it->wakeup();
- }
- }
+ void on_finish() final;
- void register_at_finish(std::function<void()> f) override {
- std::lock_guard<std::mutex> lock(at_finish_mutex_);
- at_finish_.push_back(std::move(f));
- }
+ void register_at_finish(std::function<void()> f) final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/Condition.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/Condition.h
deleted file mode 100644
index c3799df487..0000000000
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/Condition.h
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/actor/actor.h"
-
-#include "td/utils/logging.h"
-
-namespace td {
-class Condition {
- class Helper : public Actor {
- public:
- void wait(Promise<> promise) {
- pending_promises_.push_back(std::move(promise));
- }
-
- private:
- std::vector<Promise<>> pending_promises_;
- void tear_down() override {
- for (auto &promise : pending_promises_) {
- promise.set_value(Unit());
- }
- }
- };
-
- public:
- Condition() {
- own_actor_ = create_actor<Helper>("helper");
- actor_ = own_actor_.get();
- }
- void wait(Promise<> promise) {
- send_closure(actor_, &Helper::wait, std::move(promise));
- }
- void set_true() {
- CHECK(!own_actor_.empty());
- own_actor_.reset();
- }
-
- private:
- ActorId<Helper> actor_;
- ActorOwn<Helper> own_actor_;
-};
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.cpp b/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.cpp
index 0d98f5cfb4..f78e0f0161 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.cpp
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.cpp
@@ -1,19 +1,23 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/actor/MultiPromise.h"
+#include "td/utils/logging.h"
+
namespace td {
+
void MultiPromiseActor::add_promise(Promise<Unit> &&promise) {
promises_.emplace_back(std::move(promise));
+ LOG(DEBUG) << "Add promise #" << promises_.size() << " to " << name_;
}
Promise<Unit> MultiPromiseActor::get_promise() {
if (empty()) {
- register_actor("MultiPromise", this).release();
+ register_actor(name_, this).release();
}
CHECK(!promises_.empty());
@@ -23,11 +27,13 @@ Promise<Unit> MultiPromiseActor::get_promise() {
future.set_event(EventCreator::raw(actor_id(), nullptr));
futures_.emplace_back(std::move(future));
- return PromiseCreator::from_promise_actor(std::move(promise));
+ LOG(DEBUG) << "Get promise #" << futures_.size() << " for " << name_;
+ return create_promise_from_promise_actor(std::move(promise));
}
void MultiPromiseActor::raw_event(const Event::Raw &event) {
received_results_++;
+ LOG(DEBUG) << "Receive result #" << received_results_ << " out of " << futures_.size() << " for " << name_;
if (received_results_ == futures_.size()) {
if (!ignore_errors_) {
for (auto &future : futures_) {
@@ -46,13 +52,21 @@ void MultiPromiseActor::set_ignore_errors(bool ignore_errors) {
}
void MultiPromiseActor::set_result(Result<Unit> &&result) {
- // MultiPromiseActor should be cleared before he begins to send out result
+ result_ = std::move(result);
+ stop();
+}
+
+void MultiPromiseActor::tear_down() {
+ LOG(DEBUG) << "Set result for " << promises_.size() << " promises in " << name_;
+
+ // MultiPromiseActor should be cleared before it begins to send out result
auto promises_copy = std::move(promises_);
promises_.clear();
auto futures_copy = std::move(futures_);
futures_.clear();
received_results_ = 0;
- stop();
+ auto result = std::move(result_);
+ result_ = Unit();
if (!promises_copy.empty()) {
for (size_t i = 0; i + 1 < promises_copy.size(); i++) {
@@ -87,4 +101,5 @@ MultiPromiseActorSafe::~MultiPromiseActorSafe() {
register_existing_actor(std::move(multi_promise_)).release();
}
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.h
index aa28947464..73b24d5d1c 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiPromise.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,7 +10,7 @@
#include "td/actor/PromiseFuture.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
namespace td {
@@ -20,7 +20,6 @@ class MultiPromiseInterface {
virtual void add_promise(Promise<> &&promise) = 0;
virtual Promise<> get_promise() = 0;
- // deprecated?
virtual size_t promise_count() const = 0;
virtual void set_ignore_errors(bool ignore_errors) = 0;
@@ -32,85 +31,90 @@ class MultiPromiseInterface {
virtual ~MultiPromiseInterface() = default;
};
-class MultiPromise : public MultiPromiseInterface {
+class MultiPromise final : public MultiPromiseInterface {
public:
- void add_promise(Promise<> &&promise) override {
+ void add_promise(Promise<> &&promise) final {
impl_->add_promise(std::move(promise));
}
- Promise<> get_promise() override {
+ Promise<> get_promise() final {
return impl_->get_promise();
}
- // deprecated?
- size_t promise_count() const override {
+ size_t promise_count() const final {
return impl_->promise_count();
}
- void set_ignore_errors(bool ignore_errors) override {
+ void set_ignore_errors(bool ignore_errors) final {
impl_->set_ignore_errors(ignore_errors);
}
MultiPromise() = default;
- explicit MultiPromise(std::unique_ptr<MultiPromiseInterface> impl) : impl_(std::move(impl)) {
+ explicit MultiPromise(unique_ptr<MultiPromiseInterface> impl) : impl_(std::move(impl)) {
}
private:
- std::unique_ptr<MultiPromiseInterface> impl_;
+ unique_ptr<MultiPromiseInterface> impl_;
};
class MultiPromiseActor final
: public Actor
, public MultiPromiseInterface {
public:
- MultiPromiseActor() = default;
+ explicit MultiPromiseActor(string name) : name_(std::move(name)) {
+ }
- void add_promise(Promise<Unit> &&promise) override;
+ void add_promise(Promise<Unit> &&promise) final;
- Promise<Unit> get_promise() override;
+ Promise<Unit> get_promise() final;
- void set_ignore_errors(bool ignore_errors) override;
+ void set_ignore_errors(bool ignore_errors) final;
- size_t promise_count() const override;
+ size_t promise_count() const final;
private:
void set_result(Result<Unit> &&result);
+ string name_;
vector<Promise<Unit>> promises_; // promises waiting for result
vector<FutureActor<Unit>> futures_; // futures waiting for result of the queries
size_t received_results_ = 0;
bool ignore_errors_ = false;
+ Result<Unit> result_;
+
+ void raw_event(const Event::Raw &event) final;
- void raw_event(const Event::Raw &event) override;
+ void tear_down() final;
- void on_start_migrate(int32) override {
+ void on_start_migrate(int32) final {
UNREACHABLE();
}
- void on_finish_migrate() override {
+ void on_finish_migrate() final {
UNREACHABLE();
}
};
-class MultiPromiseActorSafe : public MultiPromiseInterface {
+template <>
+class ActorTraits<MultiPromiseActor> {
+ public:
+ static constexpr bool need_context = false;
+ static constexpr bool need_start_up = true;
+};
+
+class MultiPromiseActorSafe final : public MultiPromiseInterface {
public:
- void add_promise(Promise<Unit> &&promise) override;
- Promise<Unit> get_promise() override;
- void set_ignore_errors(bool ignore_errors) override;
- size_t promise_count() const override;
- MultiPromiseActorSafe() = default;
+ void add_promise(Promise<Unit> &&promise) final;
+ Promise<Unit> get_promise() final;
+ void set_ignore_errors(bool ignore_errors) final;
+ size_t promise_count() const final;
+ explicit MultiPromiseActorSafe(string name) : multi_promise_(td::make_unique<MultiPromiseActor>(std::move(name))) {
+ }
MultiPromiseActorSafe(const MultiPromiseActorSafe &other) = delete;
MultiPromiseActorSafe &operator=(const MultiPromiseActorSafe &other) = delete;
MultiPromiseActorSafe(MultiPromiseActorSafe &&other) = delete;
MultiPromiseActorSafe &operator=(MultiPromiseActorSafe &&other) = delete;
- ~MultiPromiseActorSafe() override;
+ ~MultiPromiseActorSafe() final;
private:
- std::unique_ptr<MultiPromiseActor> multi_promise_ = std::make_unique<MultiPromiseActor>();
-};
-
-class MultiPromiseCreator {
- public:
- static MultiPromise create() {
- return MultiPromise(std::make_unique<MultiPromiseActor>());
- }
+ unique_ptr<MultiPromiseActor> multi_promise_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/Timeout.cpp b/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiTimeout.cpp
index fa2e5ffff3..8c3dcb942f 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/Timeout.cpp
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiTimeout.cpp
@@ -1,21 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/actor/Timeout.h"
+#include "td/actor/MultiTimeout.h"
-#include "td/utils/Time.h"
+#include "td/utils/logging.h"
namespace td {
bool MultiTimeout::has_timeout(int64 key) const {
- return items_.find(Item(key)) != items_.end();
+ return items_.count(Item(key)) > 0;
}
void MultiTimeout::set_timeout_at(int64 key, double timeout) {
- LOG(DEBUG) << "Set timeout for " << key << " in " << timeout - Time::now();
+ LOG(DEBUG) << "Set " << get_name() << " for " << key << " in " << timeout - Time::now();
auto item = items_.emplace(key);
auto heap_node = static_cast<HeapNode *>(const_cast<Item *>(&*item.first));
if (heap_node->in_heap()) {
@@ -35,7 +35,7 @@ void MultiTimeout::set_timeout_at(int64 key, double timeout) {
}
void MultiTimeout::add_timeout_at(int64 key, double timeout) {
- LOG(DEBUG) << "Add timeout for " << key << " in " << timeout - Time::now();
+ LOG(DEBUG) << "Add " << get_name() << " for " << key << " in " << timeout - Time::now();
auto item = items_.emplace(key);
auto heap_node = static_cast<HeapNode *>(const_cast<Item *>(&*item.first));
if (heap_node->in_heap()) {
@@ -50,7 +50,7 @@ void MultiTimeout::add_timeout_at(int64 key, double timeout) {
}
void MultiTimeout::cancel_timeout(int64 key) {
- LOG(DEBUG) << "Cancel timeout for " << key;
+ LOG(DEBUG) << "Cancel " << get_name() << " for " << key;
auto item = items_.find(Item(key));
if (item != items_.end()) {
auto heap_node = static_cast<HeapNode *>(const_cast<Item *>(&*item));
@@ -67,30 +67,44 @@ void MultiTimeout::cancel_timeout(int64 key) {
void MultiTimeout::update_timeout() {
if (items_.empty()) {
- LOG(DEBUG) << "Cancel timeout";
+ LOG(DEBUG) << "Cancel timeout of " << get_name();
CHECK(timeout_queue_.empty());
CHECK(Actor::has_timeout());
Actor::cancel_timeout();
} else {
- LOG(DEBUG) << "Set timeout in " << timeout_queue_.top_key() - Time::now_cached();
+ LOG(DEBUG) << "Set timeout of " << get_name() << " in " << timeout_queue_.top_key() - Time::now_cached();
Actor::set_timeout_at(timeout_queue_.top_key());
}
}
-void MultiTimeout::timeout_expired() {
- double now = Time::now_cached();
+vector<int64> MultiTimeout::get_expired_keys(double now) {
+ vector<int64> expired_keys;
while (!timeout_queue_.empty() && timeout_queue_.top_key() < now) {
int64 key = static_cast<Item *>(timeout_queue_.pop())->key;
items_.erase(Item(key));
- expired_.push_back(key);
+ expired_keys.push_back(key);
}
+ return expired_keys;
+}
+
+void MultiTimeout::timeout_expired() {
+ vector<int64> expired_keys = get_expired_keys(Time::now_cached());
if (!items_.empty()) {
update_timeout();
}
- for (auto key : expired_) {
+ for (auto key : expired_keys) {
+ callback_(data_, key);
+ }
+}
+
+void MultiTimeout::run_all() {
+ vector<int64> expired_keys = get_expired_keys(Time::now_cached() + 1e10);
+ if (!expired_keys.empty()) {
+ update_timeout();
+ }
+ for (auto key : expired_keys) {
callback_(data_, key);
}
- expired_.clear();
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiTimeout.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiTimeout.h
new file mode 100644
index 0000000000..64803d346d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/MultiTimeout.h
@@ -0,0 +1,81 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
+#include "td/utils/Heap.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Time.h"
+
+#include <set>
+
+namespace td {
+
+// TODO optimize
+class MultiTimeout final : public Actor {
+ struct Item final : public HeapNode {
+ int64 key;
+
+ explicit Item(int64 key) : key(key) {
+ }
+
+ bool operator<(const Item &other) const {
+ return key < other.key;
+ }
+ };
+
+ public:
+ using Data = void *;
+ using Callback = void (*)(Data, int64);
+ explicit MultiTimeout(Slice name) {
+ register_actor(name, this).release();
+ }
+
+ void set_callback(Callback callback) {
+ callback_ = callback;
+ }
+ void set_callback_data(Data data) {
+ data_ = data;
+ }
+
+ bool has_timeout(int64 key) const;
+
+ void set_timeout_in(int64 key, double timeout) {
+ set_timeout_at(key, Time::now() + timeout);
+ }
+
+ void add_timeout_in(int64 key, double timeout) {
+ add_timeout_at(key, Time::now() + timeout);
+ }
+
+ void set_timeout_at(int64 key, double timeout);
+
+ void add_timeout_at(int64 key, double timeout); // memcache semantics, doesn't replace old timeout
+
+ void cancel_timeout(int64 key);
+
+ void run_all();
+
+ private:
+ friend class Scheduler;
+
+ Callback callback_;
+ Data data_;
+
+ KHeap<double> timeout_queue_;
+ std::set<Item> items_;
+
+ void update_timeout();
+
+ void timeout_expired() final;
+
+ vector<int64> get_expired_keys(double now);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/PromiseFuture.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/PromiseFuture.h
index 63156c3838..5ddc6e9848 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/PromiseFuture.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/PromiseFuture.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,174 +10,23 @@
#include "td/utils/Closure.h"
#include "td/utils/common.h"
-#include "td/utils/invoke.h" // for tuple_for_each
-#include "td/utils/logging.h"
+#include "td/utils/invoke.h"
+#include "td/utils/Promise.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/Status.h"
#include <tuple>
-#include <type_traits>
#include <utility>
namespace td {
-template <class T = Unit>
-class PromiseInterface {
- public:
- PromiseInterface() = default;
- PromiseInterface(const PromiseInterface &) = delete;
- PromiseInterface &operator=(const PromiseInterface &) = delete;
- PromiseInterface(PromiseInterface &&) = default;
- PromiseInterface &operator=(PromiseInterface &&) = default;
- virtual ~PromiseInterface() = default;
- virtual void set_value(T &&value) {
- set_result(std::move(value));
- }
- virtual void set_error(Status &&error) {
- set_result(std::move(error));
- }
- virtual void set_result(Result<T> &&result) {
- if (result.is_ok()) {
- set_value(result.move_as_ok());
- } else {
- set_error(result.move_as_error());
- }
- }
- virtual void start_migrate(int32 sched_id) {
- }
- virtual void finish_migrate() {
- }
-};
-
-template <class T = Unit>
-class FutureInterface {
- public:
- FutureInterface() = default;
- FutureInterface(const FutureInterface &) = delete;
- FutureInterface &operator=(const FutureInterface &) = delete;
- FutureInterface(FutureInterface &&) = default;
- FutureInterface &operator=(FutureInterface &&) = default;
- virtual ~FutureInterface() = default;
- virtual bool is_ready() = 0;
- virtual bool is_ok() = 0;
- virtual bool is_error() = 0;
- virtual const T &ok() = 0;
- virtual T move_as_ok() = 0;
- virtual const Status &error() = 0;
- virtual Status move_as_error() TD_WARN_UNUSED_RESULT = 0;
- virtual const Result<T> &result() = 0;
- virtual Result<T> move_as_result() TD_WARN_UNUSED_RESULT = 0;
-};
-
-template <class T>
-class SafePromise;
-
-template <class T = Unit>
-class Promise {
- public:
- void set_value(T &&value) {
- if (!promise_) {
- return;
- }
- promise_->set_value(std::move(value));
- promise_.reset();
- }
- void set_error(Status &&error) {
- if (!promise_) {
- return;
- }
- promise_->set_error(std::move(error));
- promise_.reset();
- }
- void set_result(Result<T> &&result) {
- if (!promise_) {
- return;
- }
- promise_->set_result(std::move(result));
- promise_.reset();
- }
- void reset() {
- promise_.reset();
- }
- void start_migrate(int32 sched_id) {
- if (!promise_) {
- return;
- }
- promise_->start_migrate(sched_id);
- }
- void finish_migrate() {
- if (!promise_) {
- return;
- }
- promise_->finish_migrate();
- }
- std::unique_ptr<PromiseInterface<T>> release() {
- return std::move(promise_);
- }
-
- Promise() = default;
- explicit Promise(std::unique_ptr<PromiseInterface<T>> promise) : promise_(std::move(promise)) {
- }
- Promise(SafePromise<T> &&other);
- Promise &operator=(SafePromise<T> &&other);
-
- explicit operator bool() {
- return static_cast<bool>(promise_);
- }
-
- private:
- std::unique_ptr<PromiseInterface<T>> promise_;
-};
-
-template <class T>
-void start_migrate(Promise<T> &promise, int32 sched_id) {
- // promise.start_migrate(sched_id);
-}
-template <class T>
-void finish_migrate(Promise<T> &promise) {
- // promise.finish_migrate();
-}
-
-template <class T = Unit>
-class SafePromise {
- public:
- SafePromise(Promise<T> promise, Result<T> result) : promise_(std::move(promise)), result_(std::move(result)) {
- }
- SafePromise(const SafePromise &other) = delete;
- SafePromise &operator=(const SafePromise &other) = delete;
- SafePromise(SafePromise &&other) = default;
- SafePromise &operator=(SafePromise &&other) = default;
- ~SafePromise() {
- if (promise_) {
- promise_.set_result(std::move(result_));
- }
- }
- Promise<T> release() {
- return std::move(promise_);
- }
-
- private:
- Promise<T> promise_;
- Result<T> result_;
-};
-
-template <class T>
-Promise<T>::Promise(SafePromise<T> &&other) : Promise(other.release()) {
-}
-template <class T>
-Promise<T> &Promise<T>::operator=(SafePromise<T> &&other) {
- *this = other.release();
- return *this;
-}
-
namespace detail {
-
-class EventPromise : public PromiseInterface<Unit> {
+class EventPromise final : public PromiseInterface<Unit> {
public:
- void set_value(Unit &&) override {
+ void set_value(Unit &&) final {
ok_.try_emit();
fail_.clear();
}
- void set_error(Status &&) override {
+ void set_error(Status &&) final {
do_set_error();
}
@@ -185,7 +34,7 @@ class EventPromise : public PromiseInterface<Unit> {
EventPromise &operator=(const EventPromise &other) = delete;
EventPromise(EventPromise &&other) = delete;
EventPromise &operator=(EventPromise &&other) = delete;
- ~EventPromise() override {
+ ~EventPromise() final {
do_set_error();
}
@@ -209,109 +58,23 @@ class EventPromise : public PromiseInterface<Unit> {
}
};
-template <typename T>
-struct GetArg : public GetArg<decltype(&T::operator())> {};
-
-template <class C, class R, class Arg>
-class GetArg<R (C::*)(Arg)> {
- public:
- using type = Arg;
-};
-template <class C, class R, class Arg>
-class GetArg<R (C::*)(Arg) const> {
+class SendClosure {
public:
- using type = Arg;
-};
-
-template <class T>
-using get_arg_t = std::decay_t<typename GetArg<T>::type>;
-
-template <class T>
-struct DropResult {
- using type = T;
-};
-
-template <class T>
-struct DropResult<Result<T>> {
- using type = T;
-};
-
-template <class T>
-using drop_result_t = typename DropResult<T>::type;
-
-template <class ValueT, class FunctionOkT, class FunctionFailT>
-class LambdaPromise : public PromiseInterface<ValueT> {
- enum OnFail { None, Ok, Fail };
-
- public:
- void set_value(ValueT &&value) override {
- ok_(std::move(value));
- on_fail_ = None;
- }
- void set_error(Status &&error) override {
- do_error(std::move(error));
- }
- LambdaPromise(const LambdaPromise &other) = delete;
- LambdaPromise &operator=(const LambdaPromise &other) = delete;
- LambdaPromise(LambdaPromise &&other) = delete;
- LambdaPromise &operator=(LambdaPromise &&other) = delete;
- ~LambdaPromise() override {
- do_error(Status::Error("Lost promise"));
- }
-
- template <class FromOkT, class FromFailT>
- LambdaPromise(FromOkT &&ok, FromFailT &&fail, bool use_ok_as_fail)
- : ok_(std::forward<FromOkT>(ok)), fail_(std::forward<FromFailT>(fail)), on_fail_(use_ok_as_fail ? Ok : Fail) {
- }
-
- private:
- FunctionOkT ok_;
- FunctionFailT fail_;
- OnFail on_fail_ = None;
-
- template <class FuncT, class ArgT = detail::get_arg_t<FuncT>>
- std::enable_if_t<std::is_assignable<ArgT, Status>::value> do_error_impl(FuncT &func, Status &&status) {
- func(std::move(status));
- }
-
- template <class FuncT, class ArgT = detail::get_arg_t<FuncT>>
- std::enable_if_t<!std::is_assignable<ArgT, Status>::value> do_error_impl(FuncT &func, Status &&status) {
- func(Auto());
- }
-
- void do_error(Status &&error) {
- switch (on_fail_) {
- case None:
- break;
- case Ok:
- do_error_impl(ok_, std::move(error));
- break;
- case Fail:
- fail_(std::move(error));
- break;
- }
- on_fail_ = None;
+ template <class... ArgsT>
+ void operator()(ArgsT &&...args) const {
+ send_closure(std::forward<ArgsT>(args)...);
}
};
+} // namespace detail
-template <class... ArgsT>
-class JoinPromise : public PromiseInterface<Unit> {
- public:
- explicit JoinPromise(ArgsT &&... arg) : promises_(std::forward<ArgsT>(arg)...) {
- }
- void set_value(Unit &&) override {
- tuple_for_each(promises_, [](auto &promise) { promise.set_value(Unit()); });
- }
- void set_error(Status &&error) override {
- tuple_for_each(promises_, [&error](auto &promise) { promise.set_error(error.clone()); });
- }
+inline Promise<Unit> create_event_promise(EventFull &&ok) {
+ return Promise<Unit>(td::make_unique<detail::EventPromise>(std::move(ok)));
+}
- private:
- std::tuple<std::decay_t<ArgsT>...> promises_;
-};
-} // namespace detail
+inline Promise<Unit> create_event_promise(EventFull ok, EventFull fail) {
+ return Promise<Unit>(td::make_unique<detail::EventPromise>(std::move(ok), std::move(fail)));
+}
-/*** FutureActor and PromiseActor ***/
template <class T>
class FutureActor;
@@ -321,7 +84,8 @@ class PromiseActor;
template <class T>
class ActorTraits<FutureActor<T>> {
public:
- static constexpr bool is_lite = true;
+ static constexpr bool need_context = false;
+ static constexpr bool need_start_up = false;
};
template <class T>
@@ -335,12 +99,12 @@ class PromiseActor final : public PromiseInterface<T> {
PromiseActor &operator=(const PromiseActor &other) = delete;
PromiseActor(PromiseActor &&) = default;
PromiseActor &operator=(PromiseActor &&) = default;
- ~PromiseActor() override {
+ ~PromiseActor() final {
close();
}
- void set_value(T &&value) override;
- void set_error(Status &&error) override;
+ void set_value(T &&value) final;
+ void set_error(Status &&error) final;
void close() {
future_id_.reset();
@@ -373,7 +137,7 @@ class PromiseActor final : public PromiseInterface<T> {
private:
ActorOwn<FutureActor<T>> future_id_;
EventFull event_;
- State state_;
+ State state_ = State::Hangup;
void init() {
state_ = State::Waiting;
@@ -384,9 +148,12 @@ class PromiseActor final : public PromiseInterface<T> {
template <class T>
class FutureActor final : public Actor {
friend class PromiseActor<T>;
- enum State { Waiting, Ready };
public:
+ enum State { Waiting, Ready };
+
+ static constexpr int HANGUP_ERROR_CODE = 426487;
+
FutureActor() = default;
FutureActor(const FutureActor &other) = delete;
@@ -395,7 +162,7 @@ class FutureActor final : public Actor {
FutureActor(FutureActor &&other) = default;
FutureActor &operator=(FutureActor &&other) = default;
- ~FutureActor() override = default;
+ ~FutureActor() final = default;
bool is_ok() const {
return is_ready() && result_.is_ok();
@@ -435,13 +202,17 @@ class FutureActor final : public Actor {
}
}
+ State get_state() const {
+ return state_;
+ }
+
template <class S>
friend void init_promise_future(PromiseActor<S> *promise, FutureActor<S> *future);
private:
EventFull event_;
- Result<T> result_;
- State state_;
+ Result<T> result_ = Status::Error(500, "Empty FutureActor");
+ State state_ = State::Waiting;
void set_value(T &&value) {
set_result(std::move(value));
@@ -459,11 +230,11 @@ class FutureActor final : public Actor {
event_.try_emit_later();
}
- void hangup() override {
- set_error(Status::Hangup());
+ void hangup() final {
+ set_error(Status::Error<HANGUP_ERROR_CODE>());
}
- void start_up() override {
+ void start_up() final {
// empty
}
@@ -520,51 +291,26 @@ class PromiseFuture {
FutureActor<T> future_;
};
-template <class T, class ActorAT, class ActorBT, class ResultT, class... DestArgsT, class... ArgsT>
-FutureActor<T> send_promise(ActorId<ActorAT> actor_id, Send::Flags flags,
- ResultT (ActorBT::*func)(PromiseActor<T> &&, DestArgsT...), ArgsT &&... args) {
+template <ActorSendType send_type, class T, class ActorAT, class ActorBT, class ResultT, class... DestArgsT,
+ class... ArgsT>
+FutureActor<T> send_promise(ActorId<ActorAT> actor_id, ResultT (ActorBT::*func)(PromiseActor<T> &&, DestArgsT...),
+ ArgsT &&...args) {
PromiseFuture<T> pf;
- ::td::Scheduler::instance()->send_closure(
- std::move(actor_id), create_immediate_closure(func, pf.move_promise(), std::forward<ArgsT>(args)...), flags);
+ Scheduler::instance()->send_closure<send_type>(
+ std::move(actor_id), create_immediate_closure(func, pf.move_promise(), std::forward<ArgsT>(args)...));
return pf.move_future();
}
-class PromiseCreator {
- public:
- struct Ignore {
- void operator()(Status &&error) {
- error.ignore();
- }
+template <class... ArgsT>
+auto promise_send_closure(ArgsT &&...args) {
+ return [t = std::make_tuple(std::forward<ArgsT>(args)...)](auto &&res) mutable {
+ call_tuple(detail::SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::forward<decltype(res)>(res))));
};
+}
- template <class OkT, class ArgT = detail::drop_result_t<detail::get_arg_t<OkT>>>
- static Promise<ArgT> lambda(OkT &&ok) {
- return Promise<ArgT>(std::make_unique<detail::LambdaPromise<ArgT, std::decay_t<OkT>, Ignore>>(std::forward<OkT>(ok),
- Ignore(), true));
- }
-
- template <class OkT, class FailT, class ArgT = detail::get_arg_t<OkT>>
- static Promise<ArgT> lambda(OkT &&ok, FailT &&fail) {
- return Promise<ArgT>(std::make_unique<detail::LambdaPromise<ArgT, std::decay_t<OkT>, std::decay_t<FailT>>>(
- std::forward<OkT>(ok), std::forward<FailT>(fail), false));
- }
-
- static Promise<> event(EventFull &&ok) {
- return Promise<>(std::make_unique<detail::EventPromise>(std::move(ok)));
- }
-
- static Promise<> event(EventFull ok, EventFull fail) {
- return Promise<>(std::make_unique<detail::EventPromise>(std::move(ok), std::move(fail)));
- }
-
- template <class... ArgsT>
- static Promise<> join(ArgsT &&... args) {
- return Promise<>(std::make_unique<detail::JoinPromise<ArgsT...>>(std::forward<ArgsT>(args)...));
- }
+template <class T>
+Promise<T> create_promise_from_promise_actor(PromiseActor<T> &&from) {
+ return Promise<T>(td::make_unique<PromiseActor<T>>(std::move(from)));
+}
- template <class T>
- static Promise<T> from_promise_actor(PromiseActor<T> &&from) {
- return Promise<T>(std::make_unique<PromiseActor<T>>(std::move(from)));
- }
-};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/SchedulerLocalStorage.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/SchedulerLocalStorage.h
index f505836a16..b89a283f74 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/SchedulerLocalStorage.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/SchedulerLocalStorage.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,12 +8,13 @@
#include "td/actor/actor.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/optional.h"
#include <functional>
namespace td {
+
template <class T>
class SchedulerLocalStorage {
public:
@@ -45,6 +46,16 @@ class LazySchedulerLocalStorage {
LazySchedulerLocalStorage() = default;
explicit LazySchedulerLocalStorage(std::function<T()> create_func) : create_func_(std::move(create_func)) {
}
+ void set_create_func(std::function<T()> create_func) {
+ CHECK(!create_func_);
+ create_func_ = create_func;
+ }
+
+ void set(T &&t) {
+ auto &optional_value_ = sls_optional_value_.get();
+ CHECK(!optional_value_);
+ optional_value_ = std::move(t);
+ }
T &get() {
auto &optional_value_ = sls_optional_value_.get();
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/SignalSlot.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/SignalSlot.h
index 73b48f58ed..e1fd36323a 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/SignalSlot.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/SignalSlot.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,17 +9,20 @@
#include "td/actor/actor.h"
namespace td {
+
class Slot;
+
class Signal {
public:
void emit();
- explicit Signal(ActorId<Slot> slot_id) : slot_id_(slot_id) {
+ explicit Signal(ActorId<Slot> slot_id) : slot_id_(std::move(slot_id)) {
}
private:
ActorId<Slot> slot_id_;
};
+
class Slot final : public Actor {
public:
Slot() = default;
@@ -27,7 +30,7 @@ class Slot final : public Actor {
Slot &operator=(const Slot &other) = delete;
Slot(Slot &&) = default;
Slot &operator=(Slot &&) = default;
- ~Slot() override {
+ ~Slot() final {
close();
}
void set_event(EventFull &&event) {
@@ -69,18 +72,18 @@ class Slot final : public Actor {
}
ActorShared<> get_signal_new() {
register_if_empty();
- return actor_shared();
+ return actor_shared(this);
}
private:
bool was_signal_ = false;
EventFull event_;
- void timeout_expired() override {
+ void timeout_expired() final {
signal();
}
- void start_up() override {
+ void start_up() final {
empty();
}
@@ -97,10 +100,11 @@ class Slot final : public Actor {
event_.try_emit_later();
}
}
- void hangup_shared() override {
+ void hangup_shared() final {
signal();
}
};
+
inline void Signal::emit() {
send_closure(slot_id_, &Slot::signal);
}
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/SleepActor.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/SleepActor.h
index 9b9981ec38..8682ab0df5 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/SleepActor.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/SleepActor.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,11 +8,12 @@
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
namespace td {
-class SleepActor : public Actor {
+class SleepActor final : public Actor {
public:
SleepActor(double timeout, Promise<> promise) : timeout_(timeout), promise_(std::move(promise)) {
}
@@ -21,13 +22,20 @@ class SleepActor : public Actor {
double timeout_;
Promise<> promise_;
- void start_up() override {
+ void start_up() final {
set_timeout_in(timeout_);
}
- void timeout_expired() override {
+ void timeout_expired() final {
promise_.set_value(Unit());
stop();
}
};
+template <>
+class ActorTraits<SleepActor> {
+ public:
+ static constexpr bool need_context = false;
+ static constexpr bool need_start_up = true;
+};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/Timeout.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/Timeout.h
index a3a9ba1913..cde72657b8 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/Timeout.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/Timeout.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,13 +8,10 @@
#include "td/actor/actor.h"
-#include "td/utils/Heap.h"
-#include "td/utils/logging.h"
-#include "td/utils/Time.h"
-
-#include <set>
+#include "td/utils/common.h"
namespace td {
+
class Timeout final : public Actor {
public:
using Data = void *;
@@ -33,9 +30,15 @@ class Timeout final : public Actor {
bool has_timeout() const {
return Actor::has_timeout();
}
+ double get_timeout() const {
+ return Actor::get_timeout();
+ }
void set_timeout_in(double timeout) {
Actor::set_timeout_in(timeout);
}
+ void set_timeout_at(double timeout) {
+ Actor::set_timeout_at(timeout);
+ }
void cancel_timeout() {
if (has_timeout()) {
Actor::cancel_timeout();
@@ -47,14 +50,10 @@ class Timeout final : public Actor {
private:
friend class Scheduler;
- Callback callback_;
- Data data_;
+ Callback callback_{};
+ Data data_{};
- void set_timeout_at(double timeout) {
- Actor::set_timeout_at(timeout);
- }
-
- void timeout_expired() override {
+ void timeout_expired() final {
CHECK(!has_timeout());
CHECK(callback_ != Callback());
Callback callback = callback_;
@@ -66,62 +65,4 @@ class Timeout final : public Actor {
}
};
-// TODO optimize
-class MultiTimeout final : public Actor {
- struct Item : public HeapNode {
- int64 key;
-
- explicit Item(int64 key) : key(key) {
- }
-
- bool operator<(const Item &other) const {
- return key < other.key;
- }
- };
-
- public:
- using Data = void *;
- using Callback = void (*)(Data, int64);
- MultiTimeout() {
- register_actor("MultiTimeout", this).release();
- }
-
- void set_callback(Callback callback) {
- callback_ = callback;
- }
- void set_callback_data(Data data) {
- data_ = data;
- }
-
- bool has_timeout(int64 key) const;
-
- void set_timeout_in(int64 key, double timeout) {
- set_timeout_at(key, Time::now() + timeout);
- }
-
- void add_timeout_in(int64 key, double timeout) {
- add_timeout_at(key, Time::now() + timeout);
- }
-
- void set_timeout_at(int64 key, double timeout);
-
- void add_timeout_at(int64 key, double timeout); // memcache semantics, doesn't replace old timeout
-
- void cancel_timeout(int64 key);
-
- private:
- friend class Scheduler;
-
- Callback callback_;
- Data data_;
-
- KHeap<double> timeout_queue_;
- std::set<Item> items_;
- std::vector<int64> expired_;
-
- void update_timeout();
-
- void timeout_expired() override;
-};
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/actor.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/actor.h
index dadfadc055..0aed51710c 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/actor.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/actor.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,6 +9,5 @@
#include "td/actor/impl/Actor.h"
#include "td/actor/impl/ActorId.h"
#include "td/actor/impl/ActorInfo.h"
-#include "td/actor/impl/ConcurrentScheduler.h"
#include "td/actor/impl/EventFull.h"
#include "td/actor/impl/Scheduler.h"
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor-decl.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor-decl.h
index 4342214800..b0e75bd21c 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor-decl.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor-decl.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -24,8 +24,8 @@ class Actor : public ObserverBase {
Actor() = default;
Actor(const Actor &) = delete;
Actor &operator=(const Actor &) = delete;
- Actor(Actor &&other);
- Actor &operator=(Actor &&other);
+ Actor(Actor &&other) noexcept;
+ Actor &operator=(Actor &&other) noexcept;
~Actor() override {
if (!empty()) {
do_stop();
@@ -67,6 +67,7 @@ class Actor : public ObserverBase {
void stop();
void do_stop();
bool has_timeout() const;
+ double get_timeout() const;
void set_timeout_in(double timeout_in);
void set_timeout_at(double timeout_at);
void cancel_timeout();
@@ -74,8 +75,9 @@ class Actor : public ObserverBase {
void do_migrate(int32 sched_id);
uint64 get_link_token();
- void set_context(std::shared_ptr<ActorContext> context);
- void set_tag(CSlice tag);
+ std::weak_ptr<ActorContext> get_context_weak_ptr() const;
+ std::shared_ptr<ActorContext> set_context(std::shared_ptr<ActorContext> context);
+ string set_tag(string tag);
void always_wait_for_mailbox();
@@ -88,10 +90,10 @@ class Actor : public ObserverBase {
bool empty() const;
template <class FuncT, class... ArgsT>
- auto self_closure(FuncT &&func, ArgsT &&... args);
+ auto self_closure(FuncT &&func, ArgsT &&...args);
template <class SelfT, class FuncT, class... ArgsT>
- auto self_closure(SelfT *self, FuncT &&func, ArgsT &&... args);
+ auto self_closure(SelfT *self, FuncT &&func, ArgsT &&...args);
template <class LambdaT>
auto self_lambda(LambdaT &&lambda);
@@ -101,7 +103,6 @@ class Actor : public ObserverBase {
template <class SelfT>
ActorId<SelfT> actor_id(SelfT *self);
- ActorShared<> actor_shared();
template <class SelfT>
ActorShared<SelfT> actor_shared(SelfT *self, uint64 id = static_cast<uint64>(-1));
@@ -114,7 +115,8 @@ class Actor : public ObserverBase {
template <class ActorT>
class ActorTraits {
public:
- static constexpr bool is_lite = false;
+ static constexpr bool need_context = true;
+ static constexpr bool need_start_up = true;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor.h
index 3fe5e20abf..d190a2158e 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Actor.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,7 +10,7 @@
#include "td/actor/impl/EventFull-decl.h"
#include "td/actor/impl/Scheduler-decl.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/ObjectPool.h"
#include "td/utils/Slice.h"
@@ -19,14 +19,15 @@
#include <utility>
namespace td {
-inline Actor::Actor(Actor &&other) {
+
+inline Actor::Actor(Actor &&other) noexcept {
CHECK(info_.empty());
info_ = std::move(other.info_);
if (!empty()) {
info_->on_actor_moved(this);
}
}
-inline Actor &Actor::operator=(Actor &&other) {
+inline Actor &Actor::operator=(Actor &&other) noexcept {
CHECK(info_.empty());
info_ = std::move(other.info_);
if (!empty()) {
@@ -51,7 +52,10 @@ inline void Actor::do_stop() {
CHECK(empty());
}
inline bool Actor::has_timeout() const {
- return Scheduler::instance()->has_actor_timeout(this);
+ return get_info()->get_heap_node()->in_heap();
+}
+inline double Actor::get_timeout() const {
+ return Scheduler::instance()->get_actor_timeout(this);
}
inline void Actor::set_timeout_in(double timeout_in) {
Scheduler::instance()->set_actor_timeout_in(this, timeout_in);
@@ -75,32 +79,48 @@ std::enable_if_t<std::is_base_of<Actor, ActorType>::value> start_migrate(ActorTy
Scheduler::instance()->start_migrate_actor(&obj, sched_id);
}
}
+
template <class ActorType>
std::enable_if_t<std::is_base_of<Actor, ActorType>::value> finish_migrate(ActorType &obj) {
if (!obj.empty()) {
Scheduler::instance()->finish_migrate_actor(&obj);
}
}
+
inline uint64 Actor::get_link_token() {
return Scheduler::instance()->get_link_token(this);
}
-inline void Actor::set_context(std::shared_ptr<ActorContext> context) {
- info_->set_context(std::move(context));
+
+inline std::weak_ptr<ActorContext> Actor::get_context_weak_ptr() const {
+ return info_->get_context_weak_ptr();
+}
+
+inline std::shared_ptr<ActorContext> Actor::set_context(std::shared_ptr<ActorContext> context) {
+ return info_->set_context(std::move(context));
}
-inline void Actor::set_tag(CSlice tag) {
- info_->get_context()->tag_ = tag.c_str();
+
+inline string Actor::set_tag(string tag) {
+ auto *ctx = info_->get_context();
+ string old_tag;
+ if (ctx->tag_) {
+ old_tag = ctx->tag_;
+ }
+ ctx->set_tag(std::move(tag));
Scheduler::on_context_updated();
+ return old_tag;
}
inline void Actor::init(ObjectPool<ActorInfo>::OwnerPtr &&info) {
info_ = std::move(info);
}
+
inline ActorInfo *Actor::get_info() {
return &*info_;
}
inline const ActorInfo *Actor::get_info() const {
return &*info_;
}
+
inline ObjectPool<ActorInfo>::OwnerPtr Actor::clear() {
return std::move(info_);
}
@@ -118,22 +138,20 @@ ActorId<SelfT> Actor::actor_id(SelfT *self) {
return ActorId<SelfT>(info_.get_weak());
}
-inline ActorShared<> Actor::actor_shared() {
- return actor_shared(this);
-}
template <class SelfT>
ActorShared<SelfT> Actor::actor_shared(SelfT *self, uint64 id) {
CHECK(static_cast<Actor *>(self) == this);
+ CHECK(id != 0);
return ActorShared<SelfT>(actor_id(self), id);
}
template <class FuncT, class... ArgsT>
-auto Actor::self_closure(FuncT &&func, ArgsT &&... args) {
+auto Actor::self_closure(FuncT &&func, ArgsT &&...args) {
return self_closure(this, std::forward<FuncT>(func), std::forward<ArgsT>(args)...);
}
template <class SelfT, class FuncT, class... ArgsT>
-auto Actor::self_closure(SelfT *self, FuncT &&func, ArgsT &&... args) {
+auto Actor::self_closure(SelfT *self, FuncT &&func, ArgsT &&...args) {
return EventCreator::closure(actor_id(self), std::forward<FuncT>(func), std::forward<ArgsT>(args)...);
}
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId-decl.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId-decl.h
index 5e82ed6a05..2b60c72472 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId-decl.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId-decl.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -12,8 +12,10 @@
#include <type_traits>
namespace td {
-class ActorInfo;
+
class Actor;
+class ActorInfo;
+
template <class ActorType = Actor>
class ActorId {
public:
@@ -21,12 +23,12 @@ class ActorId {
explicit ActorId(ObjectPool<ActorInfo>::WeakPtr ptr) : ptr_(ptr) {
}
ActorId() = default;
- ActorId(const ActorId &) = default;
- ActorId &operator=(const ActorId &) = default;
- ActorId(ActorId &&other) : ptr_(other.ptr_) {
- other.ptr_.clear();
+ ActorId(const ActorId &other) = default;
+ ActorId &operator=(const ActorId &other) = default;
+ ActorId(ActorId &&other) noexcept : ptr_(other.ptr_) {
+ other.clear();
}
- ActorId &operator=(ActorId &&other) {
+ ActorId &operator=(ActorId &&other) noexcept {
if (&other == this) {
return *this;
}
@@ -48,10 +50,8 @@ class ActorId {
}
ActorInfo *get_actor_info() const;
- ActorType *get_actor_unsafe() const;
- // returns pointer to actor if it is on current thread. nullptr otherwise
- ActorType *try_get_actor() const;
+ ActorType *get_actor_unsafe() const;
Slice get_name() const;
@@ -60,33 +60,27 @@ class ActorId {
return ActorId<ToActorType>(ptr_);
}
- template <class AsActorType>
- ActorId<AsActorType> as() const {
- return ActorId<AsActorType>(ptr_);
- }
-
private:
ObjectPool<ActorInfo>::WeakPtr ptr_;
};
-// threat ActorId as pointer and ActorOwn as
-// unique_ptr<ActorId>
+// treat ActorId as pointer and ActorOwn as unique_ptr<ActorId>
template <class ActorType = Actor>
class ActorOwn {
public:
using ActorT = ActorType;
ActorOwn() = default;
- explicit ActorOwn(ActorId<ActorType>);
+ explicit ActorOwn(ActorId<ActorType> id);
template <class OtherActorType>
explicit ActorOwn(ActorId<OtherActorType> id);
template <class OtherActorType>
- explicit ActorOwn(ActorOwn<OtherActorType> &&);
+ explicit ActorOwn(ActorOwn<OtherActorType> &&other);
template <class OtherActorType>
- ActorOwn &operator=(ActorOwn<OtherActorType> &&);
- ActorOwn(ActorOwn &&);
- ActorOwn &operator=(ActorOwn &&);
- ActorOwn(const ActorOwn &) = delete;
- ActorOwn &operator=(const ActorOwn &) = delete;
+ ActorOwn &operator=(ActorOwn<OtherActorType> &&other);
+ ActorOwn(ActorOwn &&other) noexcept;
+ ActorOwn &operator=(ActorOwn &&other) noexcept;
+ ActorOwn(const ActorOwn &other) = delete;
+ ActorOwn &operator=(const ActorOwn &other) = delete;
~ActorOwn();
bool empty() const;
@@ -96,11 +90,7 @@ class ActorOwn {
ActorId<ActorType> get() const;
ActorId<ActorType> release();
void reset(ActorId<ActorType> other = ActorId<ActorType>());
- void hangup() const;
- const ActorId<ActorType> *operator->() const;
-
- using ActorIdConstRef = const ActorId<ActorType> &;
- // operator ActorIdConstRef();
+ ActorType *get_actor_unsafe() const;
private:
ActorId<ActorType> id_;
@@ -112,17 +102,17 @@ class ActorShared {
using ActorT = ActorType;
ActorShared() = default;
template <class OtherActorType>
- ActorShared(ActorId<OtherActorType>, uint64 token);
+ ActorShared(ActorId<OtherActorType> id, uint64 token);
template <class OtherActorType>
- ActorShared(ActorShared<OtherActorType> &&);
+ ActorShared(ActorShared<OtherActorType> &&other);
template <class OtherActorType>
- ActorShared(ActorOwn<OtherActorType> &&);
+ ActorShared(ActorOwn<OtherActorType> &&other);
template <class OtherActorType>
- ActorShared &operator=(ActorShared<OtherActorType> &&);
- ActorShared(ActorShared &&);
- ActorShared &operator=(ActorShared &&);
- ActorShared(const ActorShared &) = delete;
- ActorShared &operator=(const ActorShared &) = delete;
+ ActorShared &operator=(ActorShared<OtherActorType> &&other);
+ ActorShared(ActorShared &&other) noexcept;
+ ActorShared &operator=(ActorShared &&other) noexcept;
+ ActorShared(const ActorShared &other) = delete;
+ ActorShared &operator=(const ActorShared &other) = delete;
~ActorShared();
uint64 token() const;
@@ -133,13 +123,10 @@ class ActorShared {
ActorId<ActorType> get() const;
ActorId<ActorType> release();
void reset(ActorId<ActorType> other = ActorId<ActorType>());
- template <class OtherActorType>
- void reset(ActorId<OtherActorType> other);
- const ActorId<ActorType> *operator->() const;
private:
ActorId<ActorType> id_;
- uint64 token_;
+ uint64 token_ = 0;
};
class ActorRef {
@@ -148,6 +135,8 @@ class ActorRef {
template <class T>
ActorRef(const ActorId<T> &actor_id);
template <class T>
+ ActorRef(ActorId<T> &&actor_id);
+ template <class T>
ActorRef(const ActorShared<T> &actor_id);
template <class T>
ActorRef(ActorShared<T> &&actor_id);
@@ -166,4 +155,5 @@ class ActorRef {
ActorId<> actor_id_;
uint64 token_ = 0;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId.h
index 34d7970633..76ef6d9a8b 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorId.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -13,7 +13,6 @@
#include "td/utils/Slice.h"
namespace td {
-/*** ActorId ***/
// If actor is on our scheduler(thread) result will be valid
// If actor is on another scheduler we will see it in migrate_dest_flags
@@ -31,31 +30,24 @@ ActorType *ActorId<ActorType>::get_actor_unsafe() const {
}
template <class ActorType>
-ActorType *ActorId<ActorType>::try_get_actor() const {
- auto info = get_actor_info();
- if (info && !info->is_migrating() && Scheduler::instance()->sched_id() == info->migrate_dest()) {
- return static_cast<ActorType *>(info->get_actor_unsafe());
- }
- return nullptr;
-}
-
-template <class ActorType>
Slice ActorId<ActorType>::get_name() const {
return ptr_->get_name();
}
-// ActorOwn
template <class ActorType>
ActorOwn<ActorType>::ActorOwn(ActorId<ActorType> id) : id_(std::move(id)) {
}
+
template <class ActorType>
template <class OtherActorType>
ActorOwn<ActorType>::ActorOwn(ActorId<OtherActorType> id) : id_(std::move(id)) {
}
+
template <class ActorType>
template <class OtherActorType>
ActorOwn<ActorType>::ActorOwn(ActorOwn<OtherActorType> &&other) : id_(other.release()) {
}
+
template <class ActorType>
template <class OtherActorType>
ActorOwn<ActorType> &ActorOwn<ActorType>::operator=(ActorOwn<OtherActorType> &&other) {
@@ -64,10 +56,11 @@ ActorOwn<ActorType> &ActorOwn<ActorType>::operator=(ActorOwn<OtherActorType> &&o
}
template <class ActorType>
-ActorOwn<ActorType>::ActorOwn(ActorOwn &&other) : id_(other.release()) {
+ActorOwn<ActorType>::ActorOwn(ActorOwn &&other) noexcept : id_(other.release()) {
}
+
template <class ActorType>
-ActorOwn<ActorType> &ActorOwn<ActorType>::operator=(ActorOwn &&other) {
+ActorOwn<ActorType> &ActorOwn<ActorType>::operator=(ActorOwn &&other) noexcept {
reset(other.release());
return *this;
}
@@ -81,6 +74,7 @@ template <class ActorType>
bool ActorOwn<ActorType>::empty() const {
return id_.empty();
}
+
template <class ActorType>
ActorId<ActorType> ActorOwn<ActorType>::get() const {
return id_;
@@ -90,37 +84,36 @@ template <class ActorType>
ActorId<ActorType> ActorOwn<ActorType>::release() {
return std::move(id_);
}
+
template <class ActorType>
void ActorOwn<ActorType>::reset(ActorId<ActorType> other) {
static_assert(sizeof(ActorType) > 0, "Can't use ActorOwn with incomplete type");
- hangup();
- id_ = std::move(other);
-}
-
-template <class ActorType>
-void ActorOwn<ActorType>::hangup() const {
if (!id_.empty()) {
send_event(id_, Event::hangup());
}
+ id_ = std::move(other);
}
+
template <class ActorType>
-const ActorId<ActorType> *ActorOwn<ActorType>::operator->() const {
- return &id_;
+ActorType *ActorOwn<ActorType>::get_actor_unsafe() const {
+ return id_.get_actor_unsafe();
}
-// ActorShared
template <class ActorType>
template <class OtherActorType>
ActorShared<ActorType>::ActorShared(ActorId<OtherActorType> id, uint64 token) : id_(std::move(id)), token_(token) {
}
+
template <class ActorType>
template <class OtherActorType>
ActorShared<ActorType>::ActorShared(ActorShared<OtherActorType> &&other) : id_(other.release()), token_(other.token()) {
}
+
template <class ActorType>
template <class OtherActorType>
ActorShared<ActorType>::ActorShared(ActorOwn<OtherActorType> &&other) : id_(other.release()), token_(0) {
}
+
template <class ActorType>
template <class OtherActorType>
ActorShared<ActorType> &ActorShared<ActorType>::operator=(ActorShared<OtherActorType> &&other) {
@@ -130,10 +123,11 @@ ActorShared<ActorType> &ActorShared<ActorType>::operator=(ActorShared<OtherActor
}
template <class ActorType>
-ActorShared<ActorType>::ActorShared(ActorShared &&other) : id_(other.release()), token_(other.token_) {
+ActorShared<ActorType>::ActorShared(ActorShared &&other) noexcept : id_(other.release()), token_(other.token_) {
}
+
template <class ActorType>
-ActorShared<ActorType> &ActorShared<ActorType>::operator=(ActorShared &&other) {
+ActorShared<ActorType> &ActorShared<ActorType>::operator=(ActorShared &&other) noexcept {
reset(other.release());
token_ = other.token_;
return *this;
@@ -148,10 +142,12 @@ template <class ActorType>
uint64 ActorShared<ActorType>::token() const {
return token_;
}
+
template <class ActorType>
bool ActorShared<ActorType>::empty() const {
return id_.empty();
}
+
template <class ActorType>
ActorId<ActorType> ActorShared<ActorType>::get() const {
return id_;
@@ -161,38 +157,37 @@ template <class ActorType>
ActorId<ActorType> ActorShared<ActorType>::release() {
return std::move(id_);
}
-template <class ActorType>
-void ActorShared<ActorType>::reset(ActorId<ActorType> other) {
- reset<ActorType>(std::move(other));
-}
template <class ActorType>
-template <class OtherActorType>
-void ActorShared<ActorType>::reset(ActorId<OtherActorType> other) {
+void ActorShared<ActorType>::reset(ActorId<ActorType> other) {
static_assert(sizeof(ActorType) > 0, "Can't use ActorShared with incomplete type");
if (!id_.empty()) {
send_event(*this, Event::hangup());
}
- id_ = static_cast<ActorId<ActorType>>(other);
-}
-template <class ActorType>
-const ActorId<ActorType> *ActorShared<ActorType>::operator->() const {
- return &id_;
+ id_ = std::move(other);
}
-/*** ActorRef ***/
template <class T>
ActorRef::ActorRef(const ActorId<T> &actor_id) : actor_id_(actor_id) {
}
+
+template <class T>
+ActorRef::ActorRef(ActorId<T> &&actor_id) : actor_id_(actor_id) {
+ actor_id.clear();
+}
+
template <class T>
ActorRef::ActorRef(const ActorShared<T> &actor_id) : actor_id_(actor_id.get()), token_(actor_id.token()) {
}
+
template <class T>
ActorRef::ActorRef(ActorShared<T> &&actor_id) : actor_id_(actor_id.release()), token_(actor_id.token()) {
}
+
template <class T>
ActorRef::ActorRef(const ActorOwn<T> &actor_id) : actor_id_(actor_id.get()) {
}
+
template <class T>
ActorRef::ActorRef(ActorOwn<T> &&actor_id) : actor_id_(actor_id.release()) {
}
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo-decl.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo-decl.h
index de9fba794e..3b9d3c2f2a 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo-decl.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo-decl.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -32,13 +32,24 @@ class ActorContext {
ActorContext(ActorContext &&) = delete;
ActorContext &operator=(ActorContext &&) = delete;
virtual ~ActorContext() = default;
+
+ virtual int32 get_id() const {
+ return 0;
+ }
+
+ void set_tag(string tag) {
+ tag_storage_ = std::move(tag);
+ tag_ = tag_storage_.c_str();
+ }
+
const char *tag_ = nullptr;
+ string tag_storage_; // sometimes tag_ == tag_storage_.c_str()
std::weak_ptr<ActorContext> this_ptr_;
};
-class ActorInfo
+class ActorInfo final
: private ListNode
- , HeapNode {
+ , private HeapNode {
public:
enum class Deleter : uint8 { Destroy, None };
@@ -52,11 +63,11 @@ class ActorInfo
ActorInfo &operator=(const ActorInfo &) = delete;
void init(int32 sched_id, Slice name, ObjectPool<ActorInfo>::OwnerPtr &&this_ptr, Actor *actor_ptr, Deleter deleter,
- bool is_lite);
+ bool need_context, bool need_start_up);
void on_actor_moved(Actor *actor_new_ptr);
template <class ActorT>
- ActorOwn<ActorT> transfer_ownership_to_scheduler(std::unique_ptr<ActorT> actor);
+ ActorOwn<ActorT> transfer_ownership_to_scheduler(unique_ptr<ActorT> actor);
void clear();
void destroy_actor();
@@ -74,7 +85,8 @@ class ActorInfo
Actor *get_actor_unsafe();
const Actor *get_actor_unsafe() const;
- void set_context(std::shared_ptr<ActorContext> context);
+ std::shared_ptr<ActorContext> set_context(std::shared_ptr<ActorContext> context);
+ std::weak_ptr<ActorContext> get_context_weak_ptr() const;
ActorContext *get_context();
const ActorContext *get_context() const;
CSlice get_name() const;
@@ -93,16 +105,18 @@ class ActorInfo
vector<Event> mailbox_;
- bool is_lite() const;
+ bool need_context() const;
+ bool need_start_up() const;
void set_wait_generation(uint32 wait_generation);
bool must_wait(uint32 wait_generation) const;
void always_wait_for_mailbox();
private:
- Deleter deleter_;
- bool is_lite_;
- bool is_running_;
+ Deleter deleter_ = Deleter::None;
+ bool need_context_ = true;
+ bool need_start_up_ = true;
+ bool is_running_ = false;
bool always_wait_for_mailbox_{false};
uint32 wait_generation_{0};
@@ -116,4 +130,5 @@ class ActorInfo
};
StringBuilder &operator<<(StringBuilder &sb, const ActorInfo &info);
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo.h
index df0b0dfd81..35ec31b168 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ActorInfo.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,7 +11,6 @@
#include "td/actor/impl/Scheduler-decl.h"
#include "td/utils/common.h"
-#include "td/utils/format.h"
#include "td/utils/Heap.h"
#include "td/utils/List.h"
#include "td/utils/logging.h"
@@ -24,59 +23,69 @@
#include <utility>
namespace td {
-/*** ActorInfo ***/
+
inline StringBuilder &operator<<(StringBuilder &sb, const ActorInfo &info) {
sb << info.get_name() << ":" << const_cast<void *>(static_cast<const void *>(&info)) << ":"
<< const_cast<void *>(static_cast<const void *>(info.get_context()));
return sb;
}
+
inline void ActorInfo::init(int32 sched_id, Slice name, ObjectPool<ActorInfo>::OwnerPtr &&this_ptr, Actor *actor_ptr,
- Deleter deleter, bool is_lite) {
+ Deleter deleter, bool need_context, bool need_start_up) {
CHECK(!is_running());
CHECK(!is_migrating());
sched_id_.store(sched_id, std::memory_order_relaxed);
actor_ = actor_ptr;
- if (!is_lite) {
+ if (need_context) {
context_ = Scheduler::context()->this_ptr_.lock();
+ VLOG(actor) << "Set context " << context_.get() << " for " << name;
+ }
#ifdef TD_DEBUG
- name_ = name.str();
+ name_.assign(name.data(), name.size());
#endif
- }
actor_->init(std::move(this_ptr));
deleter_ = deleter;
- is_lite_ = is_lite;
+ need_context_ = need_context;
+ need_start_up_ = need_start_up;
is_running_ = false;
wait_generation_ = 0;
}
-inline bool ActorInfo::is_lite() const {
- return is_lite_;
+
+inline bool ActorInfo::need_context() const {
+ return need_context_;
+}
+
+inline bool ActorInfo::need_start_up() const {
+ return need_start_up_;
}
+
inline void ActorInfo::set_wait_generation(uint32 wait_generation) {
wait_generation_ = wait_generation;
}
+
inline bool ActorInfo::must_wait(uint32 wait_generation) const {
return wait_generation_ == wait_generation || (always_wait_for_mailbox_ && !mailbox_.empty());
}
+
inline void ActorInfo::always_wait_for_mailbox() {
always_wait_for_mailbox_ = true;
}
+
inline void ActorInfo::on_actor_moved(Actor *actor_new_ptr) {
actor_ = actor_new_ptr;
}
inline void ActorInfo::clear() {
- // LOG_IF(WARNING, !mailbox_.empty()) << "Destroy actor with non-empty mailbox: " << get_name()
- // << format::as_array(mailbox_);
- mailbox_.clear();
+ CHECK(mailbox_.empty());
+ CHECK(!actor_);
CHECK(!is_running());
CHECK(!is_migrating());
// NB: must be in non migrating state
// store invalid scheduler id.
sched_id_.store((1 << 30) - 1, std::memory_order_relaxed);
- destroy_actor();
- // Destroy context only after destructor.
+ VLOG(actor) << "Clear context " << context_.get() << " for " << get_name();
context_.reset();
}
@@ -92,10 +101,11 @@ inline void ActorInfo::destroy_actor() {
break;
}
actor_ = nullptr;
+ mailbox_.clear();
}
template <class ActorT>
-ActorOwn<ActorT> ActorInfo::transfer_ownership_to_scheduler(std::unique_ptr<ActorT> actor) {
+ActorOwn<ActorT> ActorInfo::transfer_ownership_to_scheduler(unique_ptr<ActorT> actor) {
CHECK(!empty());
CHECK(deleter_ == Deleter::None);
ActorT *actor_ptr = actor.release();
@@ -142,14 +152,22 @@ inline const Actor *ActorInfo::get_actor_unsafe() const {
return actor_;
}
-inline void ActorInfo::set_context(std::shared_ptr<ActorContext> context) {
+inline std::shared_ptr<ActorContext> ActorInfo::set_context(std::shared_ptr<ActorContext> context) {
CHECK(is_running());
context->this_ptr_ = context;
- context->tag_ = Scheduler::context()->tag_;
- context_ = std::move(context);
+ if (Scheduler::context()->tag_) {
+ context->set_tag(Scheduler::context()->tag_);
+ }
+ std::swap(context_, context);
Scheduler::context() = context_.get();
Scheduler::on_context_updated();
+ return context;
}
+
+inline std::weak_ptr<ActorContext> ActorInfo::get_context_weak_ptr() const {
+ return context_;
+}
+
inline const ActorContext *ActorInfo::get_context() const {
return context_.get();
}
@@ -167,13 +185,15 @@ inline CSlice ActorInfo::get_name() const {
}
inline void ActorInfo::start_run() {
- VLOG(actor) << "start_run: " << *this;
- CHECK(!is_running_) << "Recursive call of actor " << tag("name", get_name());
+ VLOG(actor) << "Start run actor: " << *this;
+ LOG_CHECK(!is_running_) << "Recursive call of actor " << get_name();
is_running_ = true;
}
inline void ActorInfo::finish_run() {
is_running_ = false;
- VLOG(actor) << "stop_run: " << *this;
+ if (!empty()) {
+ VLOG(actor) << "Stop run actor: " << *this;
+ }
}
inline bool ActorInfo::is_running() const {
@@ -198,4 +218,5 @@ inline const ListNode *ActorInfo::get_list_node() const {
inline ActorInfo *ActorInfo::from_list_node(ListNode *node) {
return static_cast<ActorInfo *>(node);
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ConcurrentScheduler.cpp b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ConcurrentScheduler.cpp
deleted file mode 100644
index 47593db90b..0000000000
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/ConcurrentScheduler.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#include "td/actor/impl/ConcurrentScheduler.h"
-
-#include "td/actor/impl/Actor.h"
-#include "td/actor/impl/ActorId.h"
-#include "td/actor/impl/ActorInfo.h"
-#include "td/actor/impl/Scheduler.h"
-
-#include "td/utils/MpscPollableQueue.h"
-#include "td/utils/port/thread_local.h"
-
-#include <memory>
-
-namespace td {
-
-void ConcurrentScheduler::init(int32 threads_n) {
-#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
- threads_n = 0;
-#endif
- threads_n++;
- std::vector<std::shared_ptr<MpscPollableQueue<EventFull>>> outbound(threads_n);
- for (int32 i = 0; i < threads_n; i++) {
-#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
-#else
- auto queue = std::make_shared<MpscPollableQueue<EventFull>>();
- queue->init();
- outbound[i] = queue;
-#endif
- }
-
- schedulers_.resize(threads_n);
- for (int32 i = 0; i < threads_n; i++) {
- auto &sched = schedulers_[i];
- sched = make_unique<Scheduler>();
- sched->init(i, outbound, static_cast<Scheduler::Callback *>(this));
- }
-
- state_ = State::Start;
-}
-
-void ConcurrentScheduler::test_one_thread_run() {
- do {
- for (auto &sched : schedulers_) {
- sched->run(0);
- }
- } while (!is_finished_.load(std::memory_order_relaxed));
-}
-
-void ConcurrentScheduler::start() {
- CHECK(state_ == State::Start);
- is_finished_.store(false, std::memory_order_relaxed);
- set_thread_id(0);
-#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
- for (size_t i = 1; i < schedulers_.size(); i++) {
- auto &sched = schedulers_[i];
- threads_.push_back(td::thread([&, tid = i]() {
- set_thread_id(static_cast<int32>(tid));
- while (!is_finished()) {
- sched->run(10);
- }
- }));
- }
-#endif
- state_ = State::Run;
-}
-
-bool ConcurrentScheduler::run_main(double timeout) {
- CHECK(state_ == State::Run);
- // run main scheduler in same thread
- auto &main_sched = schedulers_[0];
- if (!is_finished()) {
- main_sched->run(timeout);
- }
- return !is_finished();
-}
-
-void ConcurrentScheduler::finish() {
- CHECK(state_ == State::Run);
- if (!is_finished()) {
- on_finish();
- }
-#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
- for (auto &thread : threads_) {
- thread.join();
- }
- threads_.clear();
-#endif
- schedulers_.clear();
- for (auto &f : at_finish_) {
- f();
- }
- at_finish_.clear();
-
- state_ = State::Start;
-}
-
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Event.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Event.h
index fac66dd120..2796c701e5 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Event.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Event.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,8 +8,6 @@
#include "td/utils/Closure.h"
#include "td/utils/common.h"
-#include "td/utils/format.h"
-#include "td/utils/logging.h"
#include "td/utils/StringBuilder.h"
#include <type_traits>
@@ -50,7 +48,6 @@ class CustomEvent {
virtual ~CustomEvent() = default;
virtual void run(Actor *actor) = 0;
- virtual CustomEvent *clone() const = 0;
virtual void start_migrate(int32 sched_id) {
}
virtual void finish_migrate() {
@@ -58,26 +55,23 @@ class CustomEvent {
};
template <class ClosureT>
-class ClosureEvent : public CustomEvent {
+class ClosureEvent final : public CustomEvent {
public:
- void run(Actor *actor) override {
+ void run(Actor *actor) final {
closure_.run(static_cast<typename ClosureT::ActorType *>(actor));
}
- CustomEvent *clone() const override {
- return new ClosureEvent<ClosureT>(closure_.clone());
- }
template <class... ArgsT>
- explicit ClosureEvent(ArgsT &&... args) : closure_(std::forward<ArgsT>(args)...) {
+ explicit ClosureEvent(ArgsT &&...args) : closure_(std::forward<ArgsT>(args)...) {
}
- void start_migrate(int32 sched_id) override {
+ void start_migrate(int32 sched_id) final {
closure_.for_each([sched_id](auto &obj) {
using ::td::start_migrate;
start_migrate(obj, sched_id);
});
}
- void finish_migrate() override {
+ void finish_migrate() final {
closure_.for_each([](auto &obj) {
using ::td::finish_migrate;
finish_migrate(obj);
@@ -89,16 +83,12 @@ class ClosureEvent : public CustomEvent {
};
template <class LambdaT>
-class LambdaEvent : public CustomEvent {
+class LambdaEvent final : public CustomEvent {
public:
- void run(Actor *actor) override {
+ void run(Actor *actor) final {
f_();
}
- CustomEvent *clone() const override {
- LOG(FATAL) << "Not supported";
- return nullptr;
- }
- template <class FromLambdaT>
+ template <class FromLambdaT, std::enable_if_t<!std::is_same<std::decay_t<FromLambdaT>, LambdaEvent>::value, int> = 0>
explicit LambdaEvent(FromLambdaT &&lambda) : f_(std::forward<FromLambdaT>(lambda)) {
}
@@ -153,7 +143,7 @@ class Event {
new ClosureEvent<typename FromImmediateClosureT::Delayed>(std::forward<FromImmediateClosureT>(closure)));
}
template <class... ArgsT>
- static Event delayed_closure(ArgsT &&... args) {
+ static Event delayed_closure(ArgsT &&...args) {
using DelayedClosureT = decltype(create_delayed_closure(std::forward<ArgsT>(args)...));
return custom(new ClosureEvent<DelayedClosureT>(std::forward<ArgsT>(args)...));
}
@@ -167,10 +157,10 @@ class Event {
}
Event(const Event &other) = delete;
Event &operator=(const Event &) = delete;
- Event(Event &&other) : type(other.type), link_token(other.link_token), data(other.data) {
+ Event(Event &&other) noexcept : type(other.type), link_token(other.link_token), data(other.data) {
other.type = Type::NoType;
}
- Event &operator=(Event &&other) {
+ Event &operator=(Event &&other) noexcept {
destroy();
type = other.type;
link_token = other.link_token;
@@ -182,17 +172,6 @@ class Event {
destroy();
}
- Event clone() const {
- Event res;
- res.type = type;
- if (type == Type::Custom) {
- res.data.custom_event = data.custom_event->clone();
- } else {
- res.data = data;
- }
- return res;
- }
-
bool empty() const {
return type == Type::NoType;
}
@@ -241,7 +220,28 @@ class Event {
}
}
};
-inline StringBuilder &operator<<(StringBuilder &sb, const Event &e) {
- return sb << tag("Event", static_cast<int32>(e.type));
+
+inline StringBuilder &operator<<(StringBuilder &string_builder, const Event &e) {
+ string_builder << "Event::";
+ switch (e.type) {
+ case Event::Type::Start:
+ return string_builder << "Start";
+ case Event::Type::Stop:
+ return string_builder << "Stop";
+ case Event::Type::Yield:
+ return string_builder << "Yield";
+ case Event::Type::Hangup:
+ return string_builder << "Hangup";
+ case Event::Type::Timeout:
+ return string_builder << "Timeout";
+ case Event::Type::Raw:
+ return string_builder << "Raw";
+ case Event::Type::Custom:
+ return string_builder << "Custom";
+ case Event::Type::NoType:
+ default:
+ return string_builder << "NoType";
+ }
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull-decl.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull-decl.h
index ef2f1c2dcb..4137765111 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull-decl.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull-decl.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -45,7 +45,7 @@ class EventFull {
data_.link_token = actor_ref.token();
}
template <class T>
- EventFull(ActorId<T> actor_id, Event &&data) : actor_id_(actor_id), data_(std::move(data)) {
+ EventFull(ActorId<T> actor_id, Event &&data) : actor_id_(std::move(actor_id)), data_(std::move(data)) {
}
ActorId<> actor_id_;
@@ -56,7 +56,7 @@ class EventFull {
class EventCreator {
public:
template <class ActorIdT, class FunctionT, class... ArgsT>
- static EventFull closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
+ static EventFull closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&...args) {
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
using FunctionClassT = member_function_class_t<FunctionT>;
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull.h
index 1e997ee4b3..89eabef768 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/EventFull.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,7 +9,7 @@
#include "td/actor/impl/EventFull-decl.h"
#include "td/actor/impl/Scheduler-decl.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include <utility>
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler-decl.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler-decl.h
index 4b51c102a5..8ed9feb10a 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler-decl.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler-decl.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,37 +11,40 @@
#include "td/actor/impl/EventFull-decl.h"
#include "td/utils/Closure.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/Heap.h"
#include "td/utils/List.h"
+#include "td/utils/logging.h"
#include "td/utils/MovableValue.h"
#include "td/utils/MpscPollableQueue.h"
#include "td/utils/ObjectPool.h"
-#include "td/utils/port/EventFd.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/Poll.h"
+#include "td/utils/port/PollFlags.h"
#include "td/utils/port/thread_local.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
+#include "td/utils/Time.h"
#include "td/utils/type_traits.h"
#include <functional>
-#include <map>
#include <memory>
#include <type_traits>
#include <utility>
namespace td {
+
+extern int VERBOSITY_NAME(actor);
+
class ActorInfo;
-struct Send {
- using Flags = uint32;
- static const Flags immediate = 0x001;
- static const Flags later = 0x002;
- static const Flags later_weak = 0x004;
-};
+
+enum class ActorSendType { Immediate, Later, LaterWeak };
class Scheduler;
class SchedulerGuard {
public:
- explicit SchedulerGuard(Scheduler *scheduler);
+ explicit SchedulerGuard(Scheduler *scheduler, bool lock = true);
~SchedulerGuard();
SchedulerGuard(const SchedulerGuard &other) = delete;
SchedulerGuard &operator=(const SchedulerGuard &other) = delete;
@@ -50,6 +53,7 @@ class SchedulerGuard {
private:
MovableValue<bool> is_valid_ = true;
+ bool is_locked_;
Scheduler *scheduler_;
ActorContext *save_context_;
Scheduler *save_scheduler_;
@@ -82,9 +86,9 @@ class Scheduler {
int32 sched_count() const;
template <class ActorT, class... Args>
- TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor(Slice name, Args &&... args);
+ TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor(Slice name, Args &&...args);
template <class ActorT, class... Args>
- TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor_on_scheduler(Slice name, int32 sched_id, Args &&... args);
+ TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor_on_scheduler(Slice name, int32 sched_id, Args &&...args);
template <class ActorT>
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> register_actor(Slice name, ActorT *actor_ptr, int32 sched_id = -1);
template <class ActorT>
@@ -96,22 +100,28 @@ class Scheduler {
void send_to_scheduler(int32 sched_id, const ActorId<> &actor_id, Event &&event);
void send_to_other_scheduler(int32 sched_id, const ActorId<> &actor_id, Event &&event);
- template <class EventT>
- void send_lambda(ActorRef actor_ref, EventT &&lambda, Send::Flags flags = 0);
+ void run_on_scheduler(int32 sched_id, Promise<Unit> action); // TODO Action
+
+ template <class T>
+ void destroy_on_scheduler(int32 sched_id, T &value);
+
+ template <class... ArgsT>
+ void destroy_on_scheduler(int32 sched_id, ArgsT &...values);
+
+ template <ActorSendType send_type, class EventT>
+ void send_lambda(ActorRef actor_ref, EventT &&lambda);
- template <class EventT>
- void send_closure(ActorRef actor_ref, EventT &&closure, Send::Flags flags = 0);
+ template <ActorSendType send_type, class EventT>
+ void send_closure(ActorRef actor_ref, EventT &&closure);
- void send(ActorRef actor_ref, Event &&event, Send::Flags flags = 0);
+ template <ActorSendType send_type>
+ void send(ActorRef actor_ref, Event &&event);
- void hack(const ActorId<> &actor_id, Event &&event) {
- actor_id.get_actor_unsafe()->raw_event(event.data);
- }
void before_tail_send(const ActorId<> &actor_id);
- void subscribe(const Fd &fd, Fd::Flags flags = Fd::Write | Fd::Read);
- void unsubscribe(const Fd &fd);
- void unsubscribe_before_close(const Fd &fd);
+ static void subscribe(PollableFd fd, PollFlags flags = PollFlags::ReadWrite());
+ static void unsubscribe(PollableFdRef fd);
+ static void unsubscribe_before_close(PollableFdRef fd);
void yield_actor(Actor *actor);
void stop_actor(Actor *actor);
@@ -122,15 +132,15 @@ class Scheduler {
void start_migrate_actor(Actor *actor, int32 dest_sched_id);
void finish_migrate_actor(Actor *actor);
- bool has_actor_timeout(const Actor *actor) const;
+ double get_actor_timeout(const Actor *actor) const;
void set_actor_timeout_in(Actor *actor, double timeout);
void set_actor_timeout_at(Actor *actor, double timeout_at);
void cancel_actor_timeout(Actor *actor);
void finish();
void yield();
- void run(double timeout);
- void run_no_guard(double timeout);
+ void run(Timestamp timeout);
+ void run_no_guard(Timestamp timeout);
void wakeup();
@@ -139,22 +149,29 @@ class Scheduler {
static void on_context_updated();
SchedulerGuard get_guard();
+ SchedulerGuard get_const_guard();
+
+ Timestamp get_timeout();
private:
static void set_scheduler(Scheduler *scheduler);
- /*** ServiceActor ***/
+
+ void destroy_on_scheduler_impl(int32 sched_id, Promise<Unit> action);
+
class ServiceActor final : public Actor {
public:
void set_queue(std::shared_ptr<MpscPollableQueue<EventFull>> queues);
- void start_up() override;
private:
std::shared_ptr<MpscPollableQueue<EventFull>> inbound_;
- void loop() override;
+ bool subscribed_{false};
+
+ void start_up() final;
+ void loop() final;
+ void tear_down() final;
};
friend class ServiceActor;
- void do_custom_event(ActorInfo *actor, CustomEvent &event);
void do_event(ActorInfo *actor, Event &&event);
void enter_actor(ActorInfo *actor_info);
@@ -168,7 +185,7 @@ class Scheduler {
void do_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id);
void start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id);
- bool has_actor_timeout(const ActorInfo *actor_info) const;
+ double get_actor_timeout(const ActorInfo *actor_info) const;
void set_actor_timeout_in(ActorInfo *actor_info, double timeout);
void set_actor_timeout_at(ActorInfo *actor_info, double timeout_at);
void cancel_actor_timeout(ActorInfo *actor_info);
@@ -180,15 +197,15 @@ class Scheduler {
template <class RunFuncT, class EventFuncT>
void flush_mailbox(ActorInfo *actor_info, const RunFuncT &run_func, const EventFuncT &event_func);
- template <class RunFuncT, class EventFuncT>
- void send_impl(const ActorId<> &actor_id, Send::Flags flags, const RunFuncT &run_func, const EventFuncT &event_func);
+ template <ActorSendType send_type, class RunFuncT, class EventFuncT>
+ void send_impl(const ActorId<> &actor_id, const RunFuncT &run_func, const EventFuncT &event_func);
void inc_wait_generation();
- double run_timeout();
+ Timestamp run_timeout();
void run_mailbox();
- double run_events();
- void run_poll(double timeout);
+ Timestamp run_events(Timestamp timeout);
+ void run_poll(Timestamp timeout);
template <class ActorT>
ActorOwn<ActorT> register_actor_impl(Slice name, ActorT *actor_ptr, Actor::Deleter deleter, int32 sched_id);
@@ -198,42 +215,39 @@ class Scheduler {
static TD_THREAD_LOCAL ActorContext *context_;
Callback *callback_ = nullptr;
- std::unique_ptr<ObjectPool<ActorInfo>> actor_info_pool_;
+ unique_ptr<ObjectPool<ActorInfo>> actor_info_pool_;
- int32 actor_count_;
+ int32 actor_count_ = 0;
ListNode pending_actors_list_;
ListNode ready_actors_list_;
KHeap<double> timeout_queue_;
- std::map<ActorInfo *, std::vector<Event>> pending_events_;
+ FlatHashMap<ActorInfo *, std::vector<Event>> pending_events_;
-#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
- EventFd event_fd_;
-#endif
ServiceActor service_actor_;
Poll poll_;
- bool yield_flag_;
+ bool yield_flag_ = false;
bool has_guard_ = false;
bool close_flag_ = false;
- uint32 wait_generation_ = 0;
- int32 sched_id_;
- int32 sched_n_;
+ uint32 wait_generation_ = 1;
+ int32 sched_id_ = 0;
+ int32 sched_n_ = 0;
std::shared_ptr<MpscPollableQueue<EventFull>> inbound_queue_;
std::vector<std::shared_ptr<MpscPollableQueue<EventFull>>> outbound_queues_;
std::shared_ptr<ActorContext> save_context_;
struct EventContext {
- int32 dest_sched_id;
+ int32 dest_sched_id{0};
enum Flags { Stop = 1, Migrate = 2 };
int32 flags{0};
- uint64 link_token;
+ uint64 link_token{0};
- ActorInfo *actor_info;
+ ActorInfo *actor_info{nullptr};
};
- EventContext *event_context_ptr_;
+ EventContext *event_context_ptr_{nullptr};
friend class GlobalScheduler;
friend class SchedulerGuard;
@@ -241,14 +255,10 @@ class Scheduler {
};
/*** Interface to current scheduler ***/
-void subscribe(const Fd &fd, Fd::Flags flags = Fd::Write | Fd::Read);
-void unsubscribe(const Fd &fd);
-void unsubscribe_before_close(const Fd &fd);
-
template <class ActorT, class... Args>
-TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor(Slice name, Args &&... args);
+TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor(Slice name, Args &&...args);
template <class ActorT, class... Args>
-TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor_on_scheduler(Slice name, int32 sched_id, Args &&... args);
+TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor_on_scheduler(Slice name, int32 sched_id, Args &&...args);
template <class ActorT>
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> register_actor(Slice name, ActorT *actor_ptr, int32 sched_id = -1);
template <class ActorT>
@@ -258,39 +268,38 @@ template <class ActorT>
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> register_existing_actor(unique_ptr<ActorT> actor_ptr);
template <class ActorIdT, class FunctionT, class... ArgsT>
-void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
+void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&...args) {
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
using FunctionClassT = member_function_class_t<FunctionT>;
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
- Scheduler::instance()->send_closure(std::forward<ActorIdT>(actor_id),
- create_immediate_closure(function, std::forward<ArgsT>(args)...));
+ Scheduler::instance()->send_closure<ActorSendType::Immediate>(
+ std::forward<ActorIdT>(actor_id), create_immediate_closure(function, std::forward<ArgsT>(args)...));
}
template <class ActorIdT, class FunctionT, class... ArgsT>
-void send_closure_later(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
+void send_closure_later(ActorIdT &&actor_id, FunctionT function, ArgsT &&...args) {
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
using FunctionClassT = member_function_class_t<FunctionT>;
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
- Scheduler::instance()->send(std::forward<ActorIdT>(actor_id),
- Event::delayed_closure(function, std::forward<ArgsT>(args)...), Send::later);
+ Scheduler::instance()->send<ActorSendType::Later>(std::forward<ActorIdT>(actor_id),
+ Event::delayed_closure(function, std::forward<ArgsT>(args)...));
}
template <class... ArgsT>
-void send_lambda(ActorRef actor_ref, ArgsT &&... args) {
- Scheduler::instance()->send_lambda(actor_ref, std::forward<ArgsT>(args)...);
+void send_lambda(ActorRef actor_ref, ArgsT &&...args) {
+ Scheduler::instance()->send_lambda<ActorSendType::Immediate>(actor_ref, std::forward<ArgsT>(args)...);
}
template <class... ArgsT>
-void send_event(ActorRef actor_ref, ArgsT &&... args) {
- Scheduler::instance()->send(actor_ref, std::forward<ArgsT>(args)...);
+void send_event(ActorRef actor_ref, ArgsT &&...args) {
+ Scheduler::instance()->send<ActorSendType::Immediate>(actor_ref, std::forward<ArgsT>(args)...);
}
template <class... ArgsT>
-void send_event_later(ActorRef actor_ref, ArgsT &&... args) {
- Scheduler::instance()->send(actor_ref, std::forward<ArgsT>(args)..., Send::later);
+void send_event_later(ActorRef actor_ref, ArgsT &&...args) {
+ Scheduler::instance()->send<ActorSendType::Later>(actor_ref, std::forward<ArgsT>(args)...);
}
-void yield_scheduler();
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.cpp b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.cpp
index 479e419d62..38f2fc2e6f 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.cpp
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -13,19 +13,27 @@
#include "td/actor/impl/EventFull.h"
#include "td/utils/common.h"
+#include "td/utils/ExitGuard.h"
#include "td/utils/format.h"
#include "td/utils/List.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/MpscPollableQueue.h"
#include "td/utils/ObjectPool.h"
#include "td/utils/port/thread_local.h"
+#include "td/utils/Promise.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/Time.h"
#include <functional>
+#include <iterator>
+#include <memory>
#include <utility>
namespace td {
+int VERBOSITY_NAME(actor) = VERBOSITY_NAME(DEBUG) + 10;
+
TD_THREAD_LOCAL Scheduler *Scheduler::scheduler_; // static zero-initialized
TD_THREAD_LOCAL ActorContext *Scheduler::context_; // static zero-initialized
@@ -49,6 +57,10 @@ void Scheduler::set_scheduler(Scheduler *scheduler) {
scheduler_ = scheduler;
}
+void Scheduler::ServiceActor::set_queue(std::shared_ptr<MpscPollableQueue<EventFull>> queues) {
+ inbound_ = std::move(queues);
+}
+
void Scheduler::ServiceActor::start_up() {
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
CHECK(!inbound_);
@@ -56,10 +68,11 @@ void Scheduler::ServiceActor::start_up() {
if (!inbound_) {
return;
}
+#if !TD_PORT_WINDOWS
auto &fd = inbound_->reader_get_event_fd();
-
- fd.get_fd().set_observer(this);
- ::td::subscribe(fd.get_fd(), Fd::Read);
+ Scheduler::subscribe(fd.get_poll_info().extract_pollable_fd(this), PollFlags::Read());
+ subscribed_ = true;
+#endif
yield();
#endif
}
@@ -73,7 +86,11 @@ void Scheduler::ServiceActor::loop() {
while (ready_n-- > 0) {
EventFull event = queue->reader_get_unsafe();
if (event.actor_id().empty()) {
- Scheduler::instance()->register_migrated_actor(static_cast<ActorInfo *>(event.data().data.ptr));
+ if (event.data().empty()) {
+ Scheduler::instance()->yield();
+ } else {
+ Scheduler::instance()->register_migrated_actor(static_cast<ActorInfo *>(event.data().data.ptr));
+ }
} else {
VLOG(actor) << "Receive " << event.data();
finish_migrate(event.data());
@@ -84,10 +101,30 @@ void Scheduler::ServiceActor::loop() {
yield();
}
+void Scheduler::ServiceActor::tear_down() {
+ if (!subscribed_) {
+ return;
+ }
+#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
+ CHECK(!inbound_);
+#else
+ if (!inbound_) {
+ return;
+ }
+ auto &fd = inbound_->reader_get_event_fd();
+ Scheduler::unsubscribe(fd.get_poll_info().get_pollable_fd_ref());
+ subscribed_ = false;
+#endif
+}
+
/*** SchedlerGuard ***/
-SchedulerGuard::SchedulerGuard(Scheduler *scheduler) : scheduler_(scheduler) {
- CHECK(!scheduler_->has_guard_);
- scheduler_->has_guard_ = true;
+SchedulerGuard::SchedulerGuard(Scheduler *scheduler, bool lock) : scheduler_(scheduler) {
+ if (lock) {
+ // the next check can fail if OS killed the scheduler's thread without releasing the guard
+ CHECK(!scheduler_->has_guard_);
+ scheduler_->has_guard_ = true;
+ }
+ is_locked_ = lock;
save_scheduler_ = Scheduler::instance();
Scheduler::set_scheduler(scheduler_);
@@ -102,8 +139,10 @@ SchedulerGuard::~SchedulerGuard() {
if (is_valid_.get()) {
std::swap(save_context_, scheduler_->context());
Scheduler::set_scheduler(save_scheduler_);
- CHECK(scheduler_->has_guard_);
- scheduler_->has_guard_ = false;
+ if (is_locked_) {
+ CHECK(scheduler_->has_guard_);
+ scheduler_->has_guard_ = false;
+ }
LOG_TAG = save_tag_;
}
}
@@ -132,9 +171,11 @@ EventGuard::~EventGuard() {
}
info->finish_run();
swap_context(info);
- CHECK(info->is_lite() || save_context_ == info->get_context());
+ CHECK(!info->need_context() || save_context_ == info->get_context());
#ifdef TD_DEBUG
- CHECK(info->is_lite() || save_log_tag2_ == info->get_name().c_str());
+ LOG_CHECK(!info->need_context() || save_log_tag2_ == info->get_name().c_str())
+ << info->need_context() << " " << info->empty() << " " << info->is_migrating() << " " << save_log_tag2_ << " "
+ << info->get_name() << " " << scheduler_->close_flag_;
#endif
if (event_context_.flags & Scheduler::EventContext::Stop) {
scheduler_->do_stop_actor(info);
@@ -148,7 +189,7 @@ EventGuard::~EventGuard() {
void EventGuard::swap_context(ActorInfo *info) {
std::swap(scheduler_->event_context_ptr_, event_context_ptr_);
- if (info->is_lite()) {
+ if (!info->need_context()) {
return;
}
@@ -180,11 +221,6 @@ void Scheduler::init(int32 id, std::vector<std::shared_ptr<MpscPollableQueue<Eve
poll_.init();
-#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
- event_fd_.init();
- subscribe(event_fd_.get_fd(), Fd::Read);
-#endif
-
if (!outbound.empty()) {
inbound_queue_ = std::move(outbound[id]);
}
@@ -214,20 +250,12 @@ void Scheduler::clear() {
auto actor_info = ActorInfo::from_list_node(ready_actors_list_.get());
do_stop_actor(actor_info);
}
- LOG_IF(FATAL, !ready_actors_list_.empty()) << ActorInfo::from_list_node(ready_actors_list_.next)->get_name();
- CHECK(ready_actors_list_.empty());
poll_.clear();
-#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
- if (!event_fd_.empty()) {
- event_fd_.close();
- }
-#endif
-
- if (callback_) {
+ if (callback_ && !ExitGuard::is_exited()) {
// can't move lambda with unique_ptr inside into std::function
auto ptr = actor_info_pool_.release();
- callback_->register_at_finish([=]() { delete ptr; });
+ callback_->register_at_finish([ptr] { delete ptr; });
} else {
actor_info_pool_.reset();
}
@@ -236,59 +264,48 @@ void Scheduler::clear() {
void Scheduler::do_event(ActorInfo *actor_info, Event &&event) {
event_context_ptr_->link_token = event.link_token;
auto actor = actor_info->get_actor_unsafe();
+ VLOG(actor) << *actor_info << ' ' << event;
switch (event.type) {
- case Event::Type::Start: {
- VLOG(actor) << *actor_info << " Event::Start";
+ case Event::Type::Start:
actor->start_up();
break;
- }
- case Event::Type::Stop: {
- VLOG(actor) << *actor_info << " Event::Stop";
+ case Event::Type::Stop:
actor->tear_down();
break;
- }
- case Event::Type::Yield: {
- VLOG(actor) << *actor_info << " Event::Yield";
+ case Event::Type::Yield:
actor->wakeup();
break;
- }
- case Event::Type::Hangup: {
- auto token = get_link_token(actor);
- VLOG(actor) << *actor_info << " Event::Hangup " << tag("token", format::as_hex(token));
- if (token != 0) {
+ case Event::Type::Hangup:
+ if (get_link_token(actor) != 0) {
actor->hangup_shared();
} else {
actor->hangup();
}
break;
- }
- case Event::Type::Timeout: {
- VLOG(actor) << *actor_info << " Event::Timeout";
+ case Event::Type::Timeout:
actor->timeout_expired();
break;
- }
- case Event::Type::Raw: {
- VLOG(actor) << *actor_info << " Event::Raw";
+ case Event::Type::Raw:
actor->raw_event(event.data);
break;
- }
- case Event::Type::Custom: {
- do_custom_event(actor_info, *event.data.custom_event);
+ case Event::Type::Custom:
+ event.data.custom_event->run(actor);
break;
- }
- case Event::Type::NoType: {
+ case Event::Type::NoType:
+ default:
UNREACHABLE();
break;
- }
}
- // can't clear event here. It may be already destroyed during destory_actor
+ // can't clear event here. It may be already destroyed during destroy_actor
}
void Scheduler::register_migrated_actor(ActorInfo *actor_info) {
VLOG(actor) << "Register migrated actor: " << tag("name", *actor_info) << tag("ptr", actor_info)
<< tag("actor_count", actor_count_);
actor_count_++;
- CHECK(actor_info->is_migrating());
+ LOG_CHECK(actor_info->is_migrating()) << *actor_info << ' ' << actor_count_ << ' ' << sched_id_ << ' '
+ << actor_info->migrate_dest() << ' ' << actor_info->is_running() << ' '
+ << close_flag_;
CHECK(sched_id_ == actor_info->migrate_dest());
// CHECK(!actor_info->is_running());
actor_info->finish_migrate();
@@ -297,8 +314,8 @@ void Scheduler::register_migrated_actor(ActorInfo *actor_info) {
}
auto it = pending_events_.find(actor_info);
if (it != pending_events_.end()) {
- actor_info->mailbox_.insert(actor_info->mailbox_.end(), make_move_iterator(begin(it->second)),
- make_move_iterator(end(it->second)));
+ actor_info->mailbox_.insert(actor_info->mailbox_.end(), std::make_move_iterator(it->second.begin()),
+ std::make_move_iterator(it->second.end()));
pending_events_.erase(it);
}
if (actor_info->mailbox_.empty()) {
@@ -323,6 +340,43 @@ void Scheduler::send_to_other_scheduler(int32 sched_id, const ActorId<> &actor_i
}
}
+void Scheduler::run_on_scheduler(int32 sched_id, Promise<Unit> action) {
+ if (sched_id >= 0 && sched_id_ != sched_id) {
+ class Worker final : public Actor {
+ public:
+ explicit Worker(Promise<Unit> action) : action_(std::move(action)) {
+ }
+
+ private:
+ Promise<Unit> action_;
+
+ void start_up() final {
+ action_.set_value(Unit());
+ stop();
+ }
+ };
+ create_actor_on_scheduler<Worker>("RunOnSchedulerWorker", sched_id, std::move(action)).release();
+ return;
+ }
+
+ action.set_value(Unit());
+}
+
+void Scheduler::destroy_on_scheduler_impl(int32 sched_id, Promise<Unit> action) {
+ auto empty_context = std::make_shared<ActorContext>();
+ empty_context->this_ptr_ = empty_context;
+ ActorContext *current_context = context_;
+ context_ = empty_context.get();
+
+ const char *current_tag = LOG_TAG;
+ LOG_TAG = nullptr;
+
+ run_on_scheduler(sched_id, std::move(action));
+
+ context_ = current_context;
+ LOG_TAG = current_tag;
+}
+
void Scheduler::add_to_mailbox(ActorInfo *actor_info, Event &&event) {
if (!actor_info->is_running()) {
auto node = actor_info->get_list_node();
@@ -338,9 +392,9 @@ void Scheduler::do_stop_actor(Actor *actor) {
}
void Scheduler::do_stop_actor(ActorInfo *actor_info) {
CHECK(!actor_info->is_migrating());
- CHECK(actor_info->migrate_dest() == sched_id_) << actor_info->migrate_dest() << " " << sched_id_;
+ LOG_CHECK(actor_info->migrate_dest() == sched_id_) << actor_info->migrate_dest() << " " << sched_id_;
ObjectPool<ActorInfo>::OwnerPtr owner_ptr;
- if (!actor_info->is_lite()) {
+ if (actor_info->need_start_up()) {
EventGuard guard(this, actor_info);
do_event(actor_info, Event::stop());
owner_ptr = actor_info->get_actor_unsafe()->clear();
@@ -349,6 +403,7 @@ void Scheduler::do_stop_actor(ActorInfo *actor_info) {
event_context_ptr_->flags = 0;
} else {
owner_ptr = actor_info->get_actor_unsafe()->clear();
+ actor_info->destroy_actor();
}
destroy_actor(actor_info);
}
@@ -382,6 +437,7 @@ void Scheduler::do_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) {
void Scheduler::start_migrate_actor(Actor *actor, int32 dest_sched_id) {
start_migrate_actor(actor->get_info(), dest_sched_id);
}
+
void Scheduler::start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) {
VLOG(actor) << "Start migrate actor: " << tag("name", actor_info) << tag("ptr", actor_info)
<< tag("actor_count", actor_count_);
@@ -396,6 +452,11 @@ void Scheduler::start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id)
cancel_actor_timeout(actor_info);
}
+double Scheduler::get_actor_timeout(const ActorInfo *actor_info) const {
+ const HeapNode *heap_node = actor_info->get_heap_node();
+ return heap_node->in_heap() ? timeout_queue_.get_key(heap_node) - Time::now() : 0.0;
+}
+
void Scheduler::set_actor_timeout_in(ActorInfo *actor_info, double timeout) {
if (timeout > 1e10) {
timeout = 1e10;
@@ -403,13 +464,13 @@ void Scheduler::set_actor_timeout_in(ActorInfo *actor_info, double timeout) {
if (timeout < 0) {
timeout = 0;
}
- double expire_at = Time::now() + timeout;
- set_actor_timeout_at(actor_info, expire_at);
+ double expires_at = Time::now() + timeout;
+ set_actor_timeout_at(actor_info, expires_at);
}
void Scheduler::set_actor_timeout_at(ActorInfo *actor_info, double timeout_at) {
HeapNode *heap_node = actor_info->get_heap_node();
- VLOG(actor) << "set actor " << *actor_info << " " << tag("timeout", timeout_at) << timeout_at - Time::now_cached();
+ VLOG(actor) << "Set actor " << *actor_info << " timeout in " << timeout_at - Time::now_cached();
if (heap_node->in_heap()) {
timeout_queue_.fix(timeout_at, heap_node);
} else {
@@ -417,21 +478,20 @@ void Scheduler::set_actor_timeout_at(ActorInfo *actor_info, double timeout_at) {
}
}
-void Scheduler::run_poll(double timeout) {
- // LOG(DEBUG) << "run poll [timeout:" << format::as_time(timeout) << "]";
+void Scheduler::run_poll(Timestamp timeout) {
// we can't wait for less than 1ms
- poll_.run(static_cast<int32>(timeout * 1000 + 1));
-
-#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
- if (can_read(event_fd_.get_fd())) {
- std::atomic_thread_fence(std::memory_order_acquire);
- event_fd_.acquire();
- }
+ auto timeout_ms = static_cast<int>(clamp(timeout.in(), 0.0, 1000000.0) * 1000 + 1);
+#if TD_PORT_WINDOWS
+ CHECK(inbound_queue_);
+ inbound_queue_->reader_get_event_fd().wait(timeout_ms);
+ service_actor_.notify();
+#elif TD_PORT_POSIX
+ poll_.run(timeout_ms);
#endif
}
void Scheduler::run_mailbox() {
- VLOG(actor) << "run mailbox : begin";
+ VLOG(actor) << "Run mailbox : begin";
ListNode actors_list = std::move(ready_actors_list_);
while (!actors_list.empty()) {
ListNode *node = actors_list.get();
@@ -440,7 +500,7 @@ void Scheduler::run_mailbox() {
inc_wait_generation();
flush_mailbox(actor_info, static_cast<void (*)(ActorInfo *)>(nullptr), static_cast<Event (*)()>(nullptr));
}
- VLOG(actor) << "run mailbox : finish " << actor_count_;
+ VLOG(actor) << "Run mailbox : finish " << actor_count_;
//Useful for debug, but O(ActorsCount) check
@@ -457,40 +517,54 @@ void Scheduler::run_mailbox() {
//LOG(ERROR) << *actor_info;
//cnt++;
//}
- //CHECK(cnt == actor_count_) << cnt << " vs " << actor_count_;
+ //LOG_CHECK(cnt == actor_count_) << cnt << " vs " << actor_count_;
}
-double Scheduler::run_timeout() {
+Timestamp Scheduler::run_timeout() {
double now = Time::now();
+ //TODO: use Timestamp().is_in_past()
while (!timeout_queue_.empty() && timeout_queue_.top_key() < now) {
HeapNode *node = timeout_queue_.pop();
ActorInfo *actor_info = ActorInfo::from_heap_node(node);
inc_wait_generation();
- send(actor_info->actor_id(), Event::timeout(), Send::immediate);
- }
- if (timeout_queue_.empty()) {
- return 10000;
+ send<ActorSendType::Immediate>(actor_info->actor_id(), Event::timeout());
}
- double timeout = timeout_queue_.top_key() - now;
- // LOG(DEBUG) << "Timeout [cnt:" << timeout_queue_.size() << "] in " << format::as_time(timeout);
- return timeout;
+ return get_timeout();
}
-void Scheduler::run_no_guard(double timeout) {
+Timestamp Scheduler::run_events(Timestamp timeout) {
+ Timestamp res;
+ VLOG(actor) << "Run events " << sched_id_ << " " << tag("pending", pending_events_.size())
+ << tag("actors", actor_count_);
+ do {
+ run_mailbox();
+ res = run_timeout();
+ } while (!ready_actors_list_.empty() && !timeout.is_in_past());
+ return res;
+}
+
+void Scheduler::run_no_guard(Timestamp timeout) {
CHECK(has_guard_);
SCOPE_EXIT {
yield_flag_ = false;
};
- double next_timeout = run_events();
- if (next_timeout < timeout) {
- timeout = next_timeout;
- }
+ timeout.relax(run_events(timeout));
if (yield_flag_) {
return;
}
run_poll(timeout);
- run_events();
+ run_events(timeout);
+}
+
+Timestamp Scheduler::get_timeout() {
+ if (!ready_actors_list_.empty()) {
+ return Timestamp::in(0);
+ }
+ if (timeout_queue_.empty()) {
+ return Timestamp::in(10000);
+ }
+ return Timestamp::at(timeout_queue_.top_key());
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.h
index 7edf3f1d2d..d4d075785f 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.h
+++ b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl/Scheduler.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,26 +9,22 @@
#include "td/actor/impl/ActorInfo-decl.h"
#include "td/actor/impl/Scheduler-decl.h"
-#include "td/utils/format.h"
+#include "td/utils/common.h"
#include "td/utils/Heap.h"
#include "td/utils/logging.h"
-#include "td/utils/MpscPollableQueue.h"
#include "td/utils/ObjectPool.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/PollFlags.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
+#include "td/utils/Time.h"
#include <atomic>
-#include <memory>
#include <tuple>
#include <utility>
namespace td {
-/*** ServiceActor ***/
-inline void Scheduler::ServiceActor::set_queue(std::shared_ptr<MpscPollableQueue<EventFull>> queues) {
- inbound_ = std::move(queues);
-}
-
/*** EventGuard ***/
class EventGuard {
public:
@@ -58,6 +54,9 @@ class EventGuard {
inline SchedulerGuard Scheduler::get_guard() {
return SchedulerGuard(this);
}
+inline SchedulerGuard Scheduler::get_const_guard() {
+ return SchedulerGuard(this, false);
+}
inline void Scheduler::init() {
init(0, {}, nullptr);
@@ -71,12 +70,12 @@ inline int32 Scheduler::sched_count() const {
}
template <class ActorT, class... Args>
-ActorOwn<ActorT> Scheduler::create_actor(Slice name, Args &&... args) {
+ActorOwn<ActorT> Scheduler::create_actor(Slice name, Args &&...args) {
return register_actor_impl(name, new ActorT(std::forward<Args>(args)...), Actor::Deleter::Destroy, sched_id_);
}
template <class ActorT, class... Args>
-ActorOwn<ActorT> Scheduler::create_actor_on_scheduler(Slice name, int32 sched_id, Args &&... args) {
+ActorOwn<ActorT> Scheduler::create_actor_on_scheduler(Slice name, int32 sched_id, Args &&...args) {
return register_actor_impl(name, new ActorT(std::forward<Args>(args)...), Actor::Deleter::Destroy, sched_id);
}
@@ -92,29 +91,31 @@ ActorOwn<ActorT> Scheduler::register_actor(Slice name, unique_ptr<ActorT> actor_
template <class ActorT>
ActorOwn<ActorT> Scheduler::register_actor_impl(Slice name, ActorT *actor_ptr, Actor::Deleter deleter, int32 sched_id) {
+ CHECK(has_guard_);
if (sched_id == -1) {
sched_id = sched_id_;
}
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
sched_id = 0;
#endif
- CHECK(sched_id == sched_id_ || (0 <= sched_id && sched_id < static_cast<int32>(outbound_queues_.size()))) << sched_id;
+ LOG_CHECK(sched_id == sched_id_ || (0 <= sched_id && sched_id < static_cast<int32>(outbound_queues_.size())))
+ << sched_id;
auto info = actor_info_pool_->create_empty();
- VLOG(actor) << "Create actor: " << tag("name", name) << tag("ptr", *info) << tag("context", context())
- << tag("this", this) << tag("actor_count", actor_count_);
actor_count_++;
auto weak_info = info.get_weak();
auto actor_info = info.get();
- info->init(sched_id_, name, std::move(info), static_cast<Actor *>(actor_ptr), deleter, ActorTraits<ActorT>::is_lite);
+ actor_info->init(sched_id_, name, std::move(info), static_cast<Actor *>(actor_ptr), deleter,
+ ActorTraits<ActorT>::need_context, ActorTraits<ActorT>::need_start_up);
+ VLOG(actor) << "Create actor " << *actor_info << " (actor_count = " << actor_count_ << ')';
ActorId<ActorT> actor_id = weak_info->actor_id(actor_ptr);
if (sched_id != sched_id_) {
- send(actor_id, Event::start(), Send::later_weak);
+ send<ActorSendType::LaterWeak>(actor_id, Event::start());
do_migrate_actor(actor_info, sched_id);
} else {
pending_actors_list_.put(weak_info->get_list_node());
- if (!ActorTraits<ActorT>::is_lite) {
- send(actor_id, Event::start(), Send::later_weak);
+ if (ActorTraits<ActorT>::need_start_up) {
+ send<ActorSendType::LaterWeak>(actor_id, Event::start());
}
}
@@ -122,7 +123,7 @@ ActorOwn<ActorT> Scheduler::register_actor_impl(Slice name, ActorT *actor_ptr, A
}
template <class ActorT>
-ActorOwn<ActorT> Scheduler::register_existing_actor(std::unique_ptr<ActorT> actor_ptr) {
+ActorOwn<ActorT> Scheduler::register_existing_actor(unique_ptr<ActorT> actor_ptr) {
CHECK(!actor_ptr->empty());
auto actor_info = actor_ptr->get_info();
CHECK(actor_info->migrate_dest_flag_atomic().first == sched_id_);
@@ -130,10 +131,9 @@ ActorOwn<ActorT> Scheduler::register_existing_actor(std::unique_ptr<ActorT> acto
}
inline void Scheduler::destroy_actor(ActorInfo *actor_info) {
- VLOG(actor) << "Destroy actor: " << tag("name", *actor_info) << tag("ptr", actor_info)
- << tag("actor_count", actor_count_);
+ VLOG(actor) << "Destroy actor " << *actor_info << " (actor_count = " << actor_count_ << ')';
- CHECK(actor_info->migrate_dest() == sched_id_) << actor_info->migrate_dest() << " " << sched_id_;
+ LOG_CHECK(actor_info->migrate_dest() == sched_id_) << actor_info->migrate_dest() << " " << sched_id_;
cancel_actor_timeout(actor_info);
actor_info->get_list_node()->remove();
// called by ObjectPool
@@ -142,11 +142,6 @@ inline void Scheduler::destroy_actor(ActorInfo *actor_info) {
CHECK(actor_count_ >= 0);
}
-inline void Scheduler::do_custom_event(ActorInfo *actor_info, CustomEvent &event) {
- VLOG(actor) << *actor_info << " Event::Custom";
- event.run(actor_info->get_actor_unsafe());
-}
-
template <class RunFuncT, class EventFuncT>
void Scheduler::flush_mailbox(ActorInfo *actor_info, const RunFuncT &run_func, const EventFuncT &event_func) {
auto &mailbox = actor_info->mailbox_;
@@ -161,13 +156,13 @@ void Scheduler::flush_mailbox(ActorInfo *actor_info, const RunFuncT &run_func, c
if (guard.can_run()) {
(*run_func)(actor_info);
} else {
- mailbox.insert(begin(mailbox) + i, (*event_func)());
+ mailbox.insert(mailbox.begin() + i, (*event_func)());
}
}
- mailbox.erase(begin(mailbox), begin(mailbox) + i);
+ mailbox.erase(mailbox.begin(), mailbox.begin() + i);
}
-inline void Scheduler::send_to_scheduler(int32 sched_id, const ActorId<> &actor_id, Event &&event) {
+inline void Scheduler::send_to_scheduler(int32 sched_id, const ActorId<Actor> &actor_id, Event &&event) {
if (sched_id == sched_id_) {
ActorInfo *actor_info = actor_id.get_actor_info();
pending_events_[actor_info].push_back(std::move(event));
@@ -176,21 +171,34 @@ inline void Scheduler::send_to_scheduler(int32 sched_id, const ActorId<> &actor_
}
}
+template <class T>
+void Scheduler::destroy_on_scheduler(int32 sched_id, T &value) {
+ if (!value.empty()) {
+ destroy_on_scheduler_impl(sched_id, PromiseCreator::lambda([value = std::move(value)](Unit) {
+ // destroy value
+ }));
+ }
+}
+
+template <class... ArgsT>
+void Scheduler::destroy_on_scheduler(int32 sched_id, ArgsT &...values) {
+ destroy_on_scheduler_impl(sched_id, PromiseCreator::lambda([values = std::make_tuple(std::move(values)...)](Unit) {
+ // destroy values
+ }));
+}
+
inline void Scheduler::before_tail_send(const ActorId<> &actor_id) {
// TODO
}
inline void Scheduler::inc_wait_generation() {
- wait_generation_++;
+ wait_generation_ += 2;
}
-template <class RunFuncT, class EventFuncT>
-void Scheduler::send_impl(const ActorId<> &actor_id, Send::Flags flags, const RunFuncT &run_func,
- const EventFuncT &event_func) {
- CHECK(has_guard_);
+template <ActorSendType send_type, class RunFuncT, class EventFuncT>
+void Scheduler::send_impl(const ActorId<> &actor_id, const RunFuncT &run_func, const EventFuncT &event_func) {
ActorInfo *actor_info = actor_id.get_actor_info();
if (unlikely(actor_info == nullptr || close_flag_)) {
- // LOG(ERROR) << "Invalid actor id";
return;
}
@@ -199,8 +207,9 @@ void Scheduler::send_impl(const ActorId<> &actor_id, Send::Flags flags, const Ru
bool is_migrating;
std::tie(actor_sched_id, is_migrating) = actor_info->migrate_dest_flag_atomic();
bool on_current_sched = !is_migrating && sched_id_ == actor_sched_id;
+ CHECK(has_guard_ || !on_current_sched);
- if (likely(!(flags & Send::later) && !(flags & Send::later_weak) && on_current_sched && !actor_info->is_running() &&
+ if (likely(send_type == ActorSendType::Immediate && on_current_sched && !actor_info->is_running() &&
!actor_info->must_wait(wait_generation_))) { // run immediately
if (likely(actor_info->mailbox_.empty())) {
EventGuard guard(this, actor_info);
@@ -211,7 +220,7 @@ void Scheduler::send_impl(const ActorId<> &actor_id, Send::Flags flags, const Ru
} else {
if (on_current_sched) {
add_to_mailbox(actor_info, event_func());
- if (flags & Send::later) {
+ if (send_type == ActorSendType::Later) {
actor_info->set_wait_generation(wait_generation_);
}
} else {
@@ -220,57 +229,61 @@ void Scheduler::send_impl(const ActorId<> &actor_id, Send::Flags flags, const Ru
}
}
-template <class EventT>
-void Scheduler::send_lambda(ActorRef actor_ref, EventT &&lambda, Send::Flags flags) {
- return send_impl(actor_ref.get(), flags,
- [&](ActorInfo *actor_info) {
- event_context_ptr_->link_token = actor_ref.token();
- lambda();
- },
- [&]() {
- auto event = Event::lambda(std::forward<EventT>(lambda));
- event.set_link_token(actor_ref.token());
- return std::move(event);
- });
-}
-
-template <class EventT>
-void Scheduler::send_closure(ActorRef actor_ref, EventT &&closure, Send::Flags flags) {
- return send_impl(actor_ref.get(), flags,
- [&](ActorInfo *actor_info) {
- event_context_ptr_->link_token = actor_ref.token();
- closure.run(static_cast<typename EventT::ActorType *>(actor_info->get_actor_unsafe()));
- },
- [&]() {
- auto event = Event::immediate_closure(std::forward<EventT>(closure));
- event.set_link_token(actor_ref.token());
- return std::move(event);
- });
-}
-
-inline void Scheduler::send(ActorRef actor_ref, Event &&event, Send::Flags flags) {
+template <ActorSendType send_type, class EventT>
+void Scheduler::send_lambda(ActorRef actor_ref, EventT &&lambda) {
+ return send_impl<send_type>(
+ actor_ref.get(),
+ [&](ActorInfo *actor_info) {
+ event_context_ptr_->link_token = actor_ref.token();
+ lambda();
+ },
+ [&] {
+ auto event = Event::lambda(std::forward<EventT>(lambda));
+ event.set_link_token(actor_ref.token());
+ return event;
+ });
+}
+
+template <ActorSendType send_type, class EventT>
+void Scheduler::send_closure(ActorRef actor_ref, EventT &&closure) {
+ return send_impl<send_type>(
+ actor_ref.get(),
+ [&](ActorInfo *actor_info) {
+ event_context_ptr_->link_token = actor_ref.token();
+ closure.run(static_cast<typename EventT::ActorType *>(actor_info->get_actor_unsafe()));
+ },
+ [&] {
+ auto event = Event::immediate_closure(std::forward<EventT>(closure));
+ event.set_link_token(actor_ref.token());
+ return event;
+ });
+}
+
+template <ActorSendType send_type>
+void Scheduler::send(ActorRef actor_ref, Event &&event) {
event.set_link_token(actor_ref.token());
- return send_impl(actor_ref.get(), flags, [&](ActorInfo *actor_info) { do_event(actor_info, std::move(event)); },
- [&]() { return std::move(event); });
+ return send_impl<send_type>(
+ actor_ref.get(), [&](ActorInfo *actor_info) { do_event(actor_info, std::move(event)); },
+ [&] { return std::move(event); });
}
-inline void Scheduler::subscribe(const Fd &fd, Fd::Flags flags) {
- poll_.subscribe(fd, flags);
+inline void Scheduler::subscribe(PollableFd fd, PollFlags flags) {
+ instance()->poll_.subscribe(std::move(fd), flags);
}
-inline void Scheduler::unsubscribe(const Fd &fd) {
- poll_.unsubscribe(fd);
+inline void Scheduler::unsubscribe(PollableFdRef fd) {
+ instance()->poll_.unsubscribe(std::move(fd));
}
-inline void Scheduler::unsubscribe_before_close(const Fd &fd) {
- poll_.unsubscribe_before_close(fd);
+inline void Scheduler::unsubscribe_before_close(PollableFdRef fd) {
+ instance()->poll_.unsubscribe_before_close(std::move(fd));
}
inline void Scheduler::yield_actor(Actor *actor) {
yield_actor(actor->get_info());
}
inline void Scheduler::yield_actor(ActorInfo *actor_info) {
- send(actor_info->actor_id(), Event::yield(), Send::later_weak);
+ send<ActorSendType::LaterWeak>(actor_info->actor_id(), Event::yield());
}
inline void Scheduler::stop_actor(Actor *actor) {
@@ -285,7 +298,7 @@ inline uint64 Scheduler::get_link_token(Actor *actor) {
return get_link_token(actor->get_info());
}
inline uint64 Scheduler::get_link_token(ActorInfo *actor_info) {
- CHECK(event_context_ptr_->actor_info == actor_info);
+ LOG_CHECK(event_context_ptr_->actor_info == actor_info) << actor_info->get_name();
return event_context_ptr_->link_token;
}
@@ -293,8 +306,8 @@ inline void Scheduler::finish_migrate_actor(Actor *actor) {
register_migrated_actor(actor->get_info());
}
-inline bool Scheduler::has_actor_timeout(const Actor *actor) const {
- return has_actor_timeout(actor->get_info());
+inline double Scheduler::get_actor_timeout(const Actor *actor) const {
+ return get_actor_timeout(actor->get_info());
}
inline void Scheduler::set_actor_timeout_in(Actor *actor, double timeout) {
set_actor_timeout_in(actor->get_info(), timeout);
@@ -306,11 +319,6 @@ inline void Scheduler::cancel_actor_timeout(Actor *actor) {
cancel_actor_timeout(actor->get_info());
}
-inline bool Scheduler::has_actor_timeout(const ActorInfo *actor_info) const {
- const HeapNode *heap_node = actor_info->get_heap_node();
- return heap_node->in_heap();
-}
-
inline void Scheduler::cancel_actor_timeout(ActorInfo *actor_info) {
HeapNode *heap_node = actor_info->get_heap_node();
if (heap_node->in_heap()) {
@@ -332,46 +340,23 @@ inline void Scheduler::yield() {
inline void Scheduler::wakeup() {
std::atomic_thread_fence(std::memory_order_release);
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
- event_fd_.release();
+ inbound_queue_->writer_put({});
#endif
}
-inline double Scheduler::run_events() {
- double res;
- VLOG(actor) << "run events " << sched_id_ << " " << tag("pending", pending_events_.size())
- << tag("actors", actor_count_);
- do {
- run_mailbox();
- res = run_timeout();
- } while (!ready_actors_list_.empty());
- return res;
-}
-
-inline void Scheduler::run(double timeout) {
+inline void Scheduler::run(Timestamp timeout) {
auto guard = get_guard();
run_no_guard(timeout);
}
/*** Interface to current scheduler ***/
-inline void subscribe(const Fd &fd, Fd::Flags flags) {
- Scheduler::instance()->subscribe(fd, flags);
-}
-
-inline void unsubscribe(const Fd &fd) {
- Scheduler::instance()->unsubscribe(fd);
-}
-
-inline void unsubscribe_before_close(const Fd &fd) {
- Scheduler::instance()->unsubscribe_before_close(fd);
-}
-
template <class ActorT, class... Args>
-ActorOwn<ActorT> create_actor(Slice name, Args &&... args) {
+ActorOwn<ActorT> create_actor(Slice name, Args &&...args) {
return Scheduler::instance()->create_actor<ActorT>(name, std::forward<Args>(args)...);
}
template <class ActorT, class... Args>
-ActorOwn<ActorT> create_actor_on_scheduler(Slice name, int32 sched_id, Args &&... args) {
+ActorOwn<ActorT> create_actor_on_scheduler(Slice name, int32 sched_id, Args &&...args) {
return Scheduler::instance()->create_actor_on_scheduler<ActorT>(name, sched_id, std::forward<Args>(args)...);
}
@@ -390,8 +375,4 @@ ActorOwn<ActorT> register_existing_actor(unique_ptr<ActorT> actor_ptr) {
return Scheduler::instance()->register_existing_actor(std::move(actor_ptr));
}
-inline void yield_scheduler() {
- Scheduler::instance()->yield();
-}
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorLocker.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorLocker.h
deleted file mode 100644
index 2cb5cb2127..0000000000
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorLocker.h
+++ /dev/null
@@ -1,117 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/actor/impl2/ActorSignals.h"
-#include "td/actor/impl2/ActorState.h"
-
-#include "td/utils/logging.h"
-
-#include <atomic>
-
-namespace td {
-namespace actor2 {
-class ActorLocker {
- public:
- struct Options {
- Options() {
- }
- bool can_execute_paused = false;
- bool is_shared = true;
- Options &with_can_execute_paused(bool new_can_execute_paused) {
- can_execute_paused = new_can_execute_paused;
- return *this;
- }
- Options &with_is_shared(bool new_is_shared) {
- is_shared = new_is_shared;
- return *this;
- }
- };
- explicit ActorLocker(ActorState *state, Options options = {})
- : state_(state), flags_(state->get_flags_unsafe()), new_flags_{}, options_{options} {
- }
- bool try_lock() {
- CHECK(!own_lock());
- while (!can_try_add_signals()) {
- new_flags_ = flags_;
- new_flags_.set_locked(true);
- new_flags_.clear_signals();
- if (state_->state_.compare_exchange_strong(flags_.raw_ref(), new_flags_.raw(), std::memory_order_acq_rel)) {
- own_lock_ = true;
- return true;
- }
- }
- return false;
- }
- bool try_unlock(ActorState::Flags flags) {
- CHECK(!flags.is_locked());
- CHECK(own_lock());
- // can't unlock with signals set
- //CHECK(!flags.has_signals());
-
- flags_ = flags;
- //try unlock
- if (state_->state_.compare_exchange_strong(new_flags_.raw_ref(), flags.raw(), std::memory_order_acq_rel)) {
- own_lock_ = false;
- return true;
- }
-
- // read all signals
- flags.set_locked(true);
- flags.clear_signals();
- do {
- flags_.add_signals(new_flags_.get_signals());
- } while (!state_->state_.compare_exchange_strong(new_flags_.raw_ref(), flags.raw(), std::memory_order_acq_rel));
- new_flags_ = flags;
- return false;
- }
-
- bool try_add_signals(ActorSignals signals) {
- CHECK(!own_lock());
- CHECK(can_try_add_signals());
- new_flags_ = flags_;
- new_flags_.add_signals(signals);
- return state_->state_.compare_exchange_strong(flags_.raw_ref(), new_flags_.raw(), std::memory_order_acq_rel);
- }
- bool add_signals(ActorSignals signals) {
- CHECK(!own_lock());
- while (true) {
- if (can_try_add_signals()) {
- if (try_add_signals(signals)) {
- return false;
- }
- } else {
- if (try_lock()) {
- flags_.add_signals(signals);
- return true;
- }
- }
- }
- }
- bool own_lock() const {
- return own_lock_;
- }
- ActorState::Flags flags() const {
- return flags_;
- }
- bool can_execute() const {
- return flags_.is_shared() == options_.is_shared && (options_.can_execute_paused || !flags_.is_pause());
- }
-
- private:
- ActorState *state_{nullptr};
- ActorState::Flags flags_;
- ActorState::Flags new_flags_;
- bool own_lock_{false};
- Options options_;
-
- bool can_try_add_signals() const {
- return flags_.is_locked() || (flags_.is_in_queue() && !can_execute());
- }
-};
-} // namespace actor2
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorSignals.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorSignals.h
deleted file mode 100644
index b7a7483022..0000000000
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorSignals.h
+++ /dev/null
@@ -1,84 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/utils/common.h"
-
-namespace td {
-namespace actor2 {
-class ActorSignals {
- public:
- ActorSignals() = default;
- uint32 raw() const {
- return raw_;
- }
- bool empty() const {
- return raw_ == 0;
- }
- bool has_signal(uint32 signal) const {
- return (raw_ & (1u << signal)) != 0;
- }
- void add_signal(uint32 signal) {
- raw_ |= (1u << signal);
- }
- void add_signals(ActorSignals signals) {
- raw_ |= signals.raw();
- }
- void clear_signal(uint32 signal) {
- raw_ &= ~(1u << signal);
- }
- uint32 first_signal() {
- if (!raw_) {
- return 0;
- }
-#if TD_MSVC
- int res = 0;
- int bit = 1;
- while ((raw_ & bit) == 0) {
- res++;
- bit <<= 1;
- }
- return res;
-#else
- return __builtin_ctz(raw_);
-#endif
- }
- enum Signal : uint32 {
- // Signals in order of priority
- Wakeup = 1,
- Alarm = 2,
- Kill = 3, // immediate kill
- Io = 4, // move to io thread
- Cpu = 5, // move to cpu thread
- StartUp = 6,
- TearDown = 7,
- // Two signals for mpmc queue logic
- //
- // PopSignal is set after actor is popped from queue
- // When processed it should set InQueue and Pause flags to false.
- //
- // MessagesSignal is set after new messages was added to actor
- // If owner of actor wish to delay message handling, she should set InQueue flag to true and
- // add actor into mpmc queue.
- Pop = 8, // got popped from queue
- Message = 9, // got new message
- };
-
- static ActorSignals one(uint32 signal) {
- ActorSignals res;
- res.add_signal(signal);
- return res;
- }
-
- private:
- uint32 raw_{0};
- friend class ActorState;
- explicit ActorSignals(uint32 raw) : raw_(raw) {
- }
-};
-} // namespace actor2
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorState.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorState.h
deleted file mode 100644
index 02ead6bcf6..0000000000
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/ActorState.h
+++ /dev/null
@@ -1,166 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/actor/impl2/ActorSignals.h"
-#include "td/actor/impl2/SchedulerId.h"
-
-#include "td/utils/common.h"
-
-#include <atomic>
-
-namespace td {
-namespace actor2 {
-class ActorState {
- public:
- class Flags {
- public:
- Flags() = default;
- uint32 raw() const {
- return raw_;
- }
- uint32 &raw_ref() {
- return raw_;
- }
- SchedulerId get_scheduler_id() const {
- return SchedulerId{static_cast<uint8>(raw_ & SchedulerMask)};
- }
- void set_scheduler_id(SchedulerId id) {
- raw_ = (raw_ & ~SchedulerMask) | id.value();
- }
-
- bool is_shared() const {
- return check_flag(SharedFlag);
- }
- void set_shared(bool shared) {
- set_flag(SharedFlag, shared);
- }
-
- bool is_locked() const {
- return check_flag(LockFlag);
- }
- void set_locked(bool locked) {
- set_flag(LockFlag, locked);
- }
-
- bool is_migrate() const {
- return check_flag(MigrateFlag);
- }
- void set_migrate(bool migrate) {
- set_flag(MigrateFlag, migrate);
- }
-
- bool is_pause() const {
- return check_flag(PauseFlag);
- }
- void set_pause(bool pause) {
- set_flag(PauseFlag, pause);
- }
-
- bool is_closed() const {
- return check_flag(ClosedFlag);
- }
- void set_closed(bool closed) {
- set_flag(ClosedFlag, closed);
- }
-
- bool is_in_queue() const {
- return check_flag(InQueueFlag);
- }
- void set_in_queue(bool in_queue) {
- set_flag(InQueueFlag, in_queue);
- }
-
- bool has_signals() const {
- return check_flag(SignalMask);
- }
- void clear_signals() {
- set_flag(SignalMask, false);
- }
- void set_signals(ActorSignals signals) {
- raw_ = (raw_ & ~SignalMask) | (signals.raw() << SignalOffset);
- }
- void add_signals(ActorSignals signals) {
- raw_ = raw_ | (signals.raw() << SignalOffset);
- }
- ActorSignals get_signals() const {
- return ActorSignals{(raw_ & SignalMask) >> SignalOffset};
- }
-
- private:
- uint32 raw_{0};
-
- friend class ActorState;
- Flags(uint32 raw) : raw_(raw) {
- }
-
- bool check_flag(uint32 mask) const {
- return (raw_ & mask) != 0;
- }
- void set_flag(uint32 mask, bool flag) {
- raw_ = (raw_ & ~mask) | (flag * mask);
- }
- };
-
- Flags get_flags_unsafe() {
- return Flags(state_.load(std::memory_order_relaxed));
- }
- void set_flags_unsafe(Flags flags) {
- state_.store(flags.raw(), std::memory_order_relaxed);
- }
-
- private:
- friend class ActorLocker;
- std::atomic<uint32> state_{0};
- enum : uint32 {
- SchedulerMask = 255,
-
- // Actors can be shared or not.
- // If actor is shared, than any thread may try to lock it
- // If actor is not shared, than it is owned by its scheduler, and only
- // its scheduler is allowed to access it
- // This flag may NOT change during the lifetime of an actor
- SharedFlag = 1 << 9,
-
- // Only shared actors need lock
- // Lock if somebody is going to unlock it eventually.
- // For example actor is locked, when some scheduler is executing its mailbox
- // Or it is locked when it is in Mpmc queue, so someone will pop it eventually.
- LockFlag = 1 << 10,
-
- // While actor is migrating from one scheduler to another no one is allowed to change it
- // Could not be set for shared actors.
- MigrateFlag = 1 << 11,
-
- // While set all messages are delayed
- // Dropped from flush_maibox
- // PauseFlag => InQueueFlag
- PauseFlag = 1 << 12,
-
- ClosedFlag = 1 << 13,
-
- InQueueFlag = 1 << 14,
-
- // Signals
- SignalOffset = 15,
- Signal = 1 << SignalOffset,
- WakeupSignalFlag = Signal << ActorSignals::Wakeup,
- AlarmSignalFlag = Signal << ActorSignals::Alarm,
- KillSignalFlag = Signal << ActorSignals::Kill, // immediate kill
- IoSignalFlag = Signal << ActorSignals::Io, // move to io thread
- CpuSignalFlag = Signal << ActorSignals::Cpu, // move to cpu thread
- StartUpSignalFlag = Signal << ActorSignals::StartUp,
- TearDownSignalFlag = Signal << ActorSignals::TearDown,
- MessageSignalFlag = Signal << ActorSignals::Message,
- PopSignalFlag = Signal << ActorSignals::Pop,
-
- SignalMask = WakeupSignalFlag | AlarmSignalFlag | KillSignalFlag | IoSignalFlag | CpuSignalFlag |
- StartUpSignalFlag | TearDownSignalFlag | MessageSignalFlag | PopSignalFlag
- };
-};
-} // namespace actor2
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/Scheduler.h b/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/Scheduler.h
deleted file mode 100644
index 9d5783b165..0000000000
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/Scheduler.h
+++ /dev/null
@@ -1,1508 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/actor/impl2/ActorLocker.h"
-#include "td/actor/impl2/SchedulerId.h"
-
-#include "td/utils/Closure.h"
-#include "td/utils/common.h"
-#include "td/utils/format.h"
-#include "td/utils/Heap.h"
-#include "td/utils/List.h"
-#include "td/utils/logging.h"
-#include "td/utils/MpmcQueue.h"
-#include "td/utils/MpmcWaiter.h"
-#include "td/utils/MpscLinkQueue.h"
-#include "td/utils/MpscPollableQueue.h"
-#include "td/utils/port/Fd.h"
-#include "td/utils/port/Poll.h"
-#include "td/utils/port/thread.h"
-#include "td/utils/port/thread_local.h"
-#include "td/utils/ScopeGuard.h"
-#include "td/utils/SharedObjectPool.h"
-#include "td/utils/Slice.h"
-#include "td/utils/Time.h"
-#include "td/utils/type_traits.h"
-
-#include <atomic>
-#include <condition_variable>
-#include <limits>
-#include <memory>
-#include <mutex>
-#include <type_traits>
-#include <utility>
-
-namespace td {
-namespace actor2 {
-class Actor;
-
-template <class Impl>
-class Context {
- public:
- static Impl *get() {
- return context_;
- }
- class Guard {
- public:
- explicit Guard(Impl *new_context) {
- old_context_ = context_;
- context_ = new_context;
- }
- ~Guard() {
- context_ = old_context_;
- }
- Guard(const Guard &) = delete;
- Guard &operator=(const Guard &) = delete;
- Guard(Guard &&) = delete;
- Guard &operator=(Guard &&) = delete;
-
- private:
- Impl *old_context_;
- };
-
- private:
- static TD_THREAD_LOCAL Impl *context_;
-};
-
-template <class Impl>
-TD_THREAD_LOCAL Impl *Context<Impl>::context_;
-
-enum : uint64 { EmptyLinkToken = std::numeric_limits<uint64>::max() };
-
-class ActorExecuteContext : public Context<ActorExecuteContext> {
- public:
- explicit ActorExecuteContext(Actor *actor, Timestamp alarm_timestamp = Timestamp::never())
- : actor_(actor), alarm_timestamp_(alarm_timestamp) {
- }
- Actor &actor() const {
- CHECK(actor_);
- return *actor_;
- }
- bool has_flags() const {
- return flags_ != 0;
- }
- void set_stop() {
- flags_ |= 1 << Stop;
- }
- bool get_stop() const {
- return (flags_ & (1 << Stop)) != 0;
- }
- void set_pause() {
- flags_ |= 1 << Pause;
- }
- bool get_pause() const {
- return (flags_ & (1 << Pause)) != 0;
- }
- void clear_actor() {
- actor_ = nullptr;
- }
- void set_link_token(uint64 link_token) {
- link_token_ = link_token;
- }
- uint64 get_link_token() const {
- return link_token_;
- }
- Timestamp &alarm_timestamp() {
- flags_ |= 1 << Alarm;
- return alarm_timestamp_;
- }
- bool get_alarm_flag() const {
- return (flags_ & (1 << Alarm)) != 0;
- }
- Timestamp get_alarm_timestamp() const {
- return alarm_timestamp_;
- }
-
- private:
- Actor *actor_;
- uint32 flags_{0};
- uint64 link_token_{EmptyLinkToken};
- Timestamp alarm_timestamp_;
- enum { Stop, Pause, Alarm };
-};
-
-class ActorMessageImpl : private MpscLinkQueueImpl::Node {
- public:
- ActorMessageImpl() = default;
- ActorMessageImpl(const ActorMessageImpl &) = delete;
- ActorMessageImpl &operator=(const ActorMessageImpl &) = delete;
- ActorMessageImpl(ActorMessageImpl &&other) = delete;
- ActorMessageImpl &operator=(ActorMessageImpl &&other) = delete;
- virtual ~ActorMessageImpl() = default;
- virtual void run() = 0;
- //virtual void run_anonymous() = 0;
-
- // ActorMessage <--> MpscLintQueue::Node
- // Each actor's mailbox will be a queue
- static ActorMessageImpl *from_mpsc_link_queue_node(MpscLinkQueueImpl::Node *node) {
- return static_cast<ActorMessageImpl *>(node);
- }
- MpscLinkQueueImpl::Node *to_mpsc_link_queue_node() {
- return static_cast<MpscLinkQueueImpl::Node *>(this);
- }
-
- uint64 link_token_{EmptyLinkToken};
- bool is_big_{false};
-};
-
-class ActorMessage {
- public:
- ActorMessage() = default;
- explicit ActorMessage(std::unique_ptr<ActorMessageImpl> impl) : impl_(std::move(impl)) {
- }
- void run() {
- CHECK(impl_);
- impl_->run();
- }
- explicit operator bool() {
- return bool(impl_);
- }
- friend class ActorMailbox;
-
- void set_link_token(uint64 link_token) {
- impl_->link_token_ = link_token;
- }
- uint64 get_link_token() const {
- return impl_->link_token_;
- }
- bool is_big() const {
- return impl_->is_big_;
- }
- void set_big() {
- impl_->is_big_ = true;
- }
-
- private:
- std::unique_ptr<ActorMessageImpl> impl_;
-
- template <class T>
- friend class td::MpscLinkQueue;
-
- static ActorMessage from_mpsc_link_queue_node(MpscLinkQueueImpl::Node *node) {
- return ActorMessage(std::unique_ptr<ActorMessageImpl>(ActorMessageImpl::from_mpsc_link_queue_node(node)));
- }
- MpscLinkQueueImpl::Node *to_mpsc_link_queue_node() {
- return impl_.release()->to_mpsc_link_queue_node();
- }
-};
-
-class ActorMailbox {
- public:
- ActorMailbox() = default;
- ActorMailbox(const ActorMailbox &) = delete;
- ActorMailbox &operator=(const ActorMailbox &) = delete;
- ActorMailbox(ActorMailbox &&other) = delete;
- ActorMailbox &operator=(ActorMailbox &&other) = delete;
- ~ActorMailbox() {
- pop_all();
- while (reader_.read()) {
- // skip
- }
- }
- class Reader;
- void push(ActorMessage message) {
- queue_.push(std::move(message));
- }
- void push_unsafe(ActorMessage message) {
- queue_.push_unsafe(std::move(message));
- }
-
- td::MpscLinkQueue<ActorMessage>::Reader &reader() {
- return reader_;
- }
-
- void pop_all() {
- queue_.pop_all(reader_);
- }
- void pop_all_unsafe() {
- queue_.pop_all_unsafe(reader_);
- }
-
- private:
- td::MpscLinkQueue<ActorMessage> queue_;
- td::MpscLinkQueue<ActorMessage>::Reader reader_;
-};
-
-class ActorInfo
- : private HeapNode
- , private ListNode {
- public:
- ActorInfo(std::unique_ptr<Actor> actor, ActorState::Flags state_flags, Slice name)
- : actor_(std::move(actor)), name_(name.begin(), name.size()) {
- state_.set_flags_unsafe(state_flags);
- }
-
- bool has_actor() const {
- return bool(actor_);
- }
- Actor &actor() {
- CHECK(has_actor());
- return *actor_;
- }
- Actor *actor_ptr() const {
- return actor_.get();
- }
- void destroy_actor() {
- actor_.reset();
- }
- ActorState &state() {
- return state_;
- }
- ActorMailbox &mailbox() {
- return mailbox_;
- }
- CSlice get_name() const {
- return name_;
- }
-
- HeapNode *as_heap_node() {
- return this;
- }
- static ActorInfo *from_heap_node(HeapNode *node) {
- return static_cast<ActorInfo *>(node);
- }
-
- Timestamp &alarm_timestamp() {
- return alarm_timestamp_;
- }
-
- private:
- std::unique_ptr<Actor> actor_;
- ActorState state_;
- ActorMailbox mailbox_;
- std::string name_;
- Timestamp alarm_timestamp_;
-};
-
-using ActorInfoPtr = SharedObjectPool<ActorInfo>::Ptr;
-
-class Actor {
- public:
- Actor() = default;
- Actor(const Actor &) = delete;
- Actor &operator=(const Actor &) = delete;
- Actor(Actor &&other) = delete;
- Actor &operator=(Actor &&other) = delete;
- virtual ~Actor() = default;
-
- void set_actor_info_ptr(ActorInfoPtr actor_info_ptr) {
- actor_info_ptr_ = std::move(actor_info_ptr);
- }
- ActorInfoPtr get_actor_info_ptr() {
- return actor_info_ptr_;
- }
-
- protected:
- // Signal handlers
- virtual void start_up(); // StartUp signal handler
- virtual void tear_down(); // TearDown signal handler (or Kill)
- virtual void hang_up(); // HangUp signal handler
- virtual void wake_up(); // WakeUp signal handler
- virtual void alarm(); // Alarm signal handler
-
- friend class ActorMessageHangup;
-
- // Event handlers
- //virtual void hangup_shared();
- // TODO: raw event?
-
- virtual void loop(); // default handler
-
- // Useful functions
- void yield(); // send wakeup signal to itself
- void stop(); // send Kill signal to itself
- Timestamp &alarm_timestamp() {
- return ActorExecuteContext::get()->alarm_timestamp();
- }
- Timestamp get_alarm_timestamp() {
- return ActorExecuteContext::get()->get_alarm_timestamp();
- }
-
- CSlice get_name() {
- return actor_info_ptr_->get_name();
- }
-
- // Inteface to scheduler
- // Query will be just passed to current scheduler
- // Timeout functions
- //bool has_timeout() const;
- //void set_timeout_in(double timeout_in);
- //void set_timeout_at(double timeout_at);
- //void cancel_timeout();
- //uint64 get_link_token(); // get current request's link_token
- //set context that will be inherited by all childrens
- //void set_context(std::shared_ptr<ActorContext> context);
-
- //ActorShared<> actor_shared(); // ActorShared to itself
- //template <class SelfT>
- //ActorShared<SelfT> actor_shared(SelfT *self, uint64 id = static_cast<uint64>(-1)); // ActorShared with type
-
- // Create EventFull to itself
- //template <class FuncT, class... ArgsT>
- //auto self_closure(FuncT &&func, ArgsT &&... args);
- //template <class SelfT, class FuncT, class... ArgsT>
- //auto self_closure(SelfT *self, FuncT &&func, ArgsT &&... args);
- //template <class LambdaT>
- //auto self_lambda(LambdaT &&lambda);
-
- //void do_stop(); // process Kill signal immediately
-
- private:
- friend class ActorExecutor;
- ActorInfoPtr actor_info_ptr_;
-};
-// Signal handlers
-inline void Actor::start_up() {
- yield();
-}
-inline void Actor::tear_down() {
- // noop
-}
-inline void Actor::hang_up() {
- stop();
-}
-inline void Actor::wake_up() {
- loop();
-}
-inline void Actor::alarm() {
- loop();
-}
-
-inline void Actor::loop() {
- // noop
-}
-
-// Useful functions
-inline void Actor::yield() {
- // TODO
-}
-inline void Actor::stop() {
- ActorExecuteContext::get()->set_stop();
-}
-
-class ActorInfoCreator {
- public:
- class Options {
- public:
- Options() = default;
-
- Options &with_name(Slice new_name) {
- name = new_name;
- return *this;
- }
-
- Options &on_scheduler(SchedulerId new_scheduler_id) {
- scheduler_id = new_scheduler_id;
- return *this;
- }
- bool has_scheduler() const {
- return scheduler_id.is_valid();
- }
- Options &with_poll() {
- is_shared = false;
- return *this;
- }
-
- private:
- friend class ActorInfoCreator;
- Slice name;
- SchedulerId scheduler_id;
- bool is_shared{true};
- bool in_queue{true};
- //TODO: rename
- };
-
- //Create unlocked actor. One must send StartUp signal immediately.
- ActorInfoPtr create(std::unique_ptr<Actor> actor, const Options &args) {
- ActorState::Flags flags;
- flags.set_scheduler_id(args.scheduler_id);
- flags.set_shared(args.is_shared);
- flags.set_in_queue(args.in_queue);
- flags.set_signals(ActorSignals::one(ActorSignals::StartUp));
-
- auto actor_info_ptr = pool_.alloc(std::move(actor), flags, args.name);
- actor_info_ptr->actor().set_actor_info_ptr(actor_info_ptr);
- return actor_info_ptr;
- }
-
- ActorInfoCreator() = default;
- ActorInfoCreator(const ActorInfoCreator &) = delete;
- ActorInfoCreator &operator=(const ActorInfoCreator &) = delete;
- ActorInfoCreator(ActorInfoCreator &&other) = delete;
- ActorInfoCreator &operator=(ActorInfoCreator &&other) = delete;
- ~ActorInfoCreator() {
- pool_.for_each([](auto &actor_info) { actor_info.destroy_actor(); });
- }
-
- private:
- SharedObjectPool<ActorInfo> pool_;
-};
-
-using ActorOptions = ActorInfoCreator::Options;
-
-class SchedulerDispatcher {
- public:
- virtual SchedulerId get_scheduler_id() const = 0;
- virtual void add_to_queue(ActorInfoPtr actor_info_ptr, SchedulerId scheduler_id, bool need_poll) = 0;
- virtual void set_alarm_timestamp(const ActorInfoPtr &actor_info_ptr, Timestamp timestamp) = 0;
-
- SchedulerDispatcher() = default;
- SchedulerDispatcher(const SchedulerDispatcher &) = delete;
- SchedulerDispatcher &operator=(const SchedulerDispatcher &) = delete;
- SchedulerDispatcher(SchedulerDispatcher &&other) = delete;
- SchedulerDispatcher &operator=(SchedulerDispatcher &&other) = delete;
- virtual ~SchedulerDispatcher() = default;
-};
-
-class ActorExecutor {
- public:
- struct Options {
- Options &with_from_queue() {
- from_queue = true;
- return *this;
- }
- Options &with_has_poll(bool new_has_poll) {
- this->has_poll = new_has_poll;
- return *this;
- }
- bool from_queue{false};
- bool has_poll{false};
- };
- ActorExecutor(ActorInfo &actor_info, SchedulerDispatcher &dispatcher, Options options)
- : actor_info_(actor_info), dispatcher_(dispatcher), options_(options) {
- //LOG(ERROR) << "START " << actor_info_.get_name() << " " << tag("from_queue", from_queue);
- start();
- }
- ActorExecutor(const ActorExecutor &) = delete;
- ActorExecutor &operator=(const ActorExecutor &) = delete;
- ActorExecutor(ActorExecutor &&other) = delete;
- ActorExecutor &operator=(ActorExecutor &&other) = delete;
- ~ActorExecutor() {
- //LOG(ERROR) << "FINISH " << actor_info_.get_name() << " " << tag("own_lock", actor_locker_.own_lock());
- finish();
- }
-
- // our best guess if actor is closed or not
- bool can_send() {
- return !flags().is_closed();
- }
-
- bool can_send_immediate() {
- return actor_locker_.own_lock() && !actor_execute_context_.has_flags() && actor_locker_.can_execute();
- }
-
- template <class F>
- void send_immediate(F &&f, uint64 link_token) {
- CHECK(can_send_immediate());
- if (!can_send()) {
- return;
- }
- actor_execute_context_.set_link_token(link_token);
- f();
- }
- void send_immediate(ActorMessage message) {
- CHECK(can_send_immediate());
- if (message.is_big()) {
- actor_info_.mailbox().reader().delay(std::move(message));
- pending_signals_.add_signal(ActorSignals::Message);
- actor_execute_context_.set_pause();
- return;
- }
- actor_execute_context_.set_link_token(message.get_link_token());
- message.run();
- }
- void send_immediate(ActorSignals signals) {
- CHECK(can_send_immediate());
- while (flush_one_signal(signals) && can_send_immediate()) {
- }
- pending_signals_.add_signals(signals);
- }
-
- void send(ActorMessage message) {
- if (!can_send()) {
- return;
- }
- if (can_send_immediate()) {
- return send_immediate(std::move(message));
- }
- actor_info_.mailbox().push(std::move(message));
- pending_signals_.add_signal(ActorSignals::Message);
- }
-
- void send(ActorSignals signals) {
- if (!can_send()) {
- return;
- }
-
- pending_signals_.add_signals(signals);
- }
-
- private:
- ActorInfo &actor_info_;
- SchedulerDispatcher &dispatcher_;
- Options options_;
- ActorLocker actor_locker_{
- &actor_info_.state(),
- ActorLocker::Options().with_can_execute_paused(options_.from_queue).with_is_shared(!options_.has_poll)};
-
- ActorExecuteContext actor_execute_context_{actor_info_.actor_ptr(), actor_info_.alarm_timestamp()};
- ActorExecuteContext::Guard guard{&actor_execute_context_};
-
- ActorState::Flags flags_;
- ActorSignals pending_signals_;
-
- ActorState::Flags &flags() {
- return flags_;
- }
-
- void start() {
- if (!can_send()) {
- return;
- }
-
- ActorSignals signals;
- SCOPE_EXIT {
- pending_signals_.add_signals(signals);
- };
-
- if (options_.from_queue) {
- signals.add_signal(ActorSignals::Pop);
- }
-
- actor_locker_.try_lock();
- flags_ = actor_locker_.flags();
-
- if (!actor_locker_.own_lock()) {
- return;
- }
-
- if (options_.from_queue) {
- flags().set_pause(false);
- }
- if (!actor_locker_.can_execute()) {
- CHECK(!options_.from_queue);
- return;
- }
-
- signals.add_signals(flags().get_signals());
- actor_info_.mailbox().pop_all();
-
- while (!actor_execute_context_.has_flags() && flush_one(signals)) {
- }
- }
-
- void finish() {
- if (!actor_locker_.own_lock()) {
- if (!pending_signals_.empty() && actor_locker_.add_signals(pending_signals_)) {
- flags_ = actor_locker_.flags();
- } else {
- return;
- }
- }
- CHECK(actor_locker_.own_lock());
-
- if (actor_execute_context_.has_flags()) {
- if (actor_execute_context_.get_stop()) {
- if (actor_info_.alarm_timestamp()) {
- dispatcher_.set_alarm_timestamp(actor_info_.actor().get_actor_info_ptr(), Timestamp::never());
- }
- flags_.set_closed(true);
- actor_info_.actor().tear_down();
- actor_info_.destroy_actor();
- return;
- }
- if (actor_execute_context_.get_pause()) {
- flags_.set_pause(true);
- }
- if (actor_execute_context_.get_alarm_flag()) {
- auto old_timestamp = actor_info_.alarm_timestamp();
- auto new_timestamp = actor_execute_context_.get_alarm_timestamp();
- if (!(old_timestamp == new_timestamp)) {
- actor_info_.alarm_timestamp() = new_timestamp;
- dispatcher_.set_alarm_timestamp(actor_info_.actor().get_actor_info_ptr(), new_timestamp);
- }
- }
- }
- flags_.set_signals(pending_signals_);
-
- bool add_to_queue = false;
- while (true) {
- // Drop InQueue flag if has pop signal
- // Can't delay this signal
- auto signals = flags().get_signals();
- if (signals.has_signal(ActorSignals::Pop)) {
- signals.clear_signal(ActorSignals::Pop);
- flags().set_signals(signals);
- flags().set_in_queue(false);
- }
-
- if (flags().has_signals() && !flags().is_in_queue()) {
- add_to_queue = true;
- flags().set_in_queue(true);
- }
- if (actor_locker_.try_unlock(flags())) {
- if (add_to_queue) {
- dispatcher_.add_to_queue(actor_info_.actor().get_actor_info_ptr(), flags().get_scheduler_id(),
- !flags().is_shared());
- }
- break;
- }
- flags_ = actor_locker_.flags();
- }
- }
-
- bool flush_one(ActorSignals &signals) {
- return flush_one_signal(signals) || flush_one_message();
- }
-
- bool flush_one_signal(ActorSignals &signals) {
- auto signal = signals.first_signal();
- if (!signal) {
- return false;
- }
- switch (signal) {
- case ActorSignals::Wakeup:
- actor_info_.actor().wake_up();
- break;
- case ActorSignals::Alarm:
- if (actor_execute_context_.get_alarm_timestamp().is_in_past()) {
- actor_execute_context_.alarm_timestamp() = Timestamp::never();
- actor_info_.actor().alarm();
- }
- break;
- case ActorSignals::Kill:
- actor_execute_context_.set_stop();
- break;
- case ActorSignals::StartUp:
- actor_info_.actor().start_up();
- break;
- case ActorSignals::TearDown:
- actor_info_.actor().tear_down();
- break;
- case ActorSignals::Pop:
- flags().set_in_queue(false);
- break;
-
- case ActorSignals::Message:
- break;
- case ActorSignals::Io:
- case ActorSignals::Cpu:
- LOG(FATAL) << "TODO";
- default:
- UNREACHABLE();
- }
- signals.clear_signal(signal);
- return true;
- }
-
- bool flush_one_message() {
- auto message = actor_info_.mailbox().reader().read();
- if (!message) {
- return false;
- }
- if (message.is_big() && !options_.from_queue) {
- actor_info_.mailbox().reader().delay(std::move(message));
- pending_signals_.add_signal(ActorSignals::Message);
- actor_execute_context_.set_pause();
- return false;
- }
-
- actor_execute_context_.set_link_token(message.get_link_token());
- message.run();
- return true;
- }
-};
-
-using SchedulerMessage = ActorInfoPtr;
-
-struct WorkerInfo {
- enum class Type { Io, Cpu } type{Type::Io};
- WorkerInfo() = default;
- explicit WorkerInfo(Type type) : type(type) {
- }
- ActorInfoCreator actor_info_creator;
-};
-
-struct SchedulerInfo {
- SchedulerId id;
- // will be read by all workers is any thread
- std::unique_ptr<MpmcQueue<SchedulerMessage>> cpu_queue;
- std::unique_ptr<MpmcWaiter> cpu_queue_waiter;
- // only scheduler itself may read from io_queue_
- std::unique_ptr<MpscPollableQueue<SchedulerMessage>> io_queue;
- size_t cpu_threads_count{0};
-
- std::unique_ptr<WorkerInfo> io_worker;
- std::vector<std::unique_ptr<WorkerInfo>> cpu_workers;
-};
-
-struct SchedulerGroupInfo {
- explicit SchedulerGroupInfo(size_t n) : schedulers(n) {
- }
- std::atomic<bool> is_stop_requested{false};
-
- int active_scheduler_count{0};
- std::mutex active_scheduler_count_mutex;
- std::condition_variable active_scheduler_count_condition_variable;
-
- std::vector<SchedulerInfo> schedulers;
-};
-
-class SchedulerContext
- : public Context<SchedulerContext>
- , public SchedulerDispatcher {
- public:
- // DispatcherInterface
- SchedulerDispatcher &dispatcher() {
- return *this;
- }
-
- // ActorCreator Interface
- virtual ActorInfoCreator &get_actor_info_creator() = 0;
-
- // Poll interface
- virtual bool has_poll() = 0;
- virtual Poll &get_poll() = 0;
-
- // Timeout interface
- virtual bool has_heap() = 0;
- virtual KHeap<double> &get_heap() = 0;
-
- // Stop all schedulers
- virtual bool is_stop_requested() = 0;
- virtual void stop() = 0;
-};
-
-#if !TD_THREAD_UNSUPPORTED
-class Scheduler {
- public:
- Scheduler(std::shared_ptr<SchedulerGroupInfo> scheduler_group_info, SchedulerId id, size_t cpu_threads_count)
- : scheduler_group_info_(std::move(scheduler_group_info)), cpu_threads_(cpu_threads_count) {
- scheduler_group_info_->active_scheduler_count++;
- info_ = &scheduler_group_info_->schedulers.at(id.value());
- info_->id = id;
- if (cpu_threads_count != 0) {
- info_->cpu_threads_count = cpu_threads_count;
- info_->cpu_queue = std::make_unique<MpmcQueue<SchedulerMessage>>(1024, max_thread_count());
- info_->cpu_queue_waiter = std::make_unique<MpmcWaiter>();
- }
- info_->io_queue = std::make_unique<MpscPollableQueue<SchedulerMessage>>();
- info_->io_queue->init();
-
- info_->cpu_workers.resize(cpu_threads_count);
- for (auto &worker : info_->cpu_workers) {
- worker = std::make_unique<WorkerInfo>(WorkerInfo::Type::Cpu);
- }
- info_->io_worker = std::make_unique<WorkerInfo>(WorkerInfo::Type::Io);
-
- poll_.init();
- io_worker_ = std::make_unique<IoWorker>(*info_->io_queue);
- }
-
- Scheduler(const Scheduler &) = delete;
- Scheduler &operator=(const Scheduler &) = delete;
- Scheduler(Scheduler &&other) = delete;
- Scheduler &operator=(Scheduler &&other) = delete;
- ~Scheduler() {
- // should stop
- stop();
- do_stop();
- }
-
- void start() {
- for (size_t i = 0; i < cpu_threads_.size(); i++) {
- cpu_threads_[i] = td::thread([this, i] {
- this->run_in_context_impl(*this->info_->cpu_workers[i],
- [this] { CpuWorker(*info_->cpu_queue, *info_->cpu_queue_waiter).run(); });
- });
- }
- this->run_in_context([this] { this->io_worker_->start_up(); });
- }
-
- template <class F>
- void run_in_context(F &&f) {
- run_in_context_impl(*info_->io_worker, std::forward<F>(f));
- }
-
- bool run(double timeout) {
- bool res;
- run_in_context_impl(*info_->io_worker, [this, timeout, &res] {
- if (SchedulerContext::get()->is_stop_requested()) {
- res = false;
- } else {
- res = io_worker_->run_once(timeout);
- }
- if (!res) {
- io_worker_->tear_down();
- }
- });
- if (!res) {
- do_stop();
- }
- return res;
- }
-
- // Just syntactic sugar
- void stop() {
- run_in_context([] { SchedulerContext::get()->stop(); });
- }
-
- SchedulerId get_scheduler_id() const {
- return info_->id;
- }
-
- private:
- std::shared_ptr<SchedulerGroupInfo> scheduler_group_info_;
- SchedulerInfo *info_;
- std::vector<td::thread> cpu_threads_;
- bool is_stopped_{false};
- Poll poll_;
- KHeap<double> heap_;
- class IoWorker;
- std::unique_ptr<IoWorker> io_worker_;
-
- class SchedulerContextImpl : public SchedulerContext {
- public:
- SchedulerContextImpl(WorkerInfo *worker, SchedulerInfo *scheduler, SchedulerGroupInfo *scheduler_group, Poll *poll,
- KHeap<double> *heap)
- : worker_(worker), scheduler_(scheduler), scheduler_group_(scheduler_group), poll_(poll), heap_(heap) {
- }
-
- SchedulerId get_scheduler_id() const override {
- return scheduler()->id;
- }
- void add_to_queue(ActorInfoPtr actor_info_ptr, SchedulerId scheduler_id, bool need_poll) override {
- if (!scheduler_id.is_valid()) {
- scheduler_id = scheduler()->id;
- }
- auto &info = scheduler_group()->schedulers.at(scheduler_id.value());
- if (need_poll) {
- info.io_queue->writer_put(std::move(actor_info_ptr));
- } else {
- info.cpu_queue->push(std::move(actor_info_ptr), get_thread_id());
- info.cpu_queue_waiter->notify();
- }
- }
-
- ActorInfoCreator &get_actor_info_creator() override {
- return worker()->actor_info_creator;
- }
-
- bool has_poll() override {
- return poll_ != nullptr;
- }
- Poll &get_poll() override {
- CHECK(has_poll());
- return *poll_;
- }
-
- bool has_heap() override {
- return heap_ != nullptr;
- }
- KHeap<double> &get_heap() override {
- CHECK(has_heap());
- return *heap_;
- }
-
- void set_alarm_timestamp(const ActorInfoPtr &actor_info_ptr, Timestamp timestamp) override {
- // we are in PollWorker
- CHECK(has_heap());
- auto &heap = get_heap();
- auto *heap_node = actor_info_ptr->as_heap_node();
- if (timestamp) {
- if (heap_node->in_heap()) {
- heap.fix(timestamp.at(), heap_node);
- } else {
- heap.insert(timestamp.at(), heap_node);
- }
- } else {
- if (heap_node->in_heap()) {
- heap.erase(heap_node);
- }
- }
-
- // TODO: do something in plain worker
- }
-
- bool is_stop_requested() override {
- return scheduler_group()->is_stop_requested;
- }
-
- void stop() override {
- bool expect_false = false;
- // Trying to set close_flag_ to true with CAS
- auto &group = *scheduler_group();
- if (!group.is_stop_requested.compare_exchange_strong(expect_false, true)) {
- return;
- }
-
- // Notify all workers of all schedulers
- for (auto &scheduler_info : group.schedulers) {
- scheduler_info.io_queue->writer_put({});
- for (size_t i = 0; i < scheduler_info.cpu_threads_count; i++) {
- scheduler_info.cpu_queue->push({}, get_thread_id());
- scheduler_info.cpu_queue_waiter->notify();
- }
- }
- }
-
- private:
- WorkerInfo *worker() const {
- return worker_;
- }
- SchedulerInfo *scheduler() const {
- return scheduler_;
- }
- SchedulerGroupInfo *scheduler_group() const {
- return scheduler_group_;
- }
-
- WorkerInfo *worker_;
- SchedulerInfo *scheduler_;
- SchedulerGroupInfo *scheduler_group_;
- Poll *poll_;
-
- KHeap<double> *heap_;
- };
-
- template <class F>
- void run_in_context_impl(WorkerInfo &worker_info, F &&f) {
- bool is_io_worker = worker_info.type == WorkerInfo::Type::Io;
- SchedulerContextImpl context(&worker_info, info_, scheduler_group_info_.get(), is_io_worker ? &poll_ : nullptr,
- is_io_worker ? &heap_ : nullptr);
- SchedulerContext::Guard guard(&context);
- f();
- }
-
- class CpuWorker {
- public:
- CpuWorker(MpmcQueue<SchedulerMessage> &queue, MpmcWaiter &waiter) : queue_(queue), waiter_(waiter) {
- }
- void run() {
- auto thread_id = get_thread_id();
- auto &dispatcher = SchedulerContext::get()->dispatcher();
-
- int yields = 0;
- while (true) {
- SchedulerMessage message;
- if (queue_.try_pop(message, thread_id)) {
- if (!message) {
- return;
- }
- ActorExecutor executor(*message, dispatcher, ActorExecutor::Options().with_from_queue());
- yields = waiter_.stop_wait(yields, thread_id);
- } else {
- yields = waiter_.wait(yields, thread_id);
- }
- }
- }
-
- private:
- MpmcQueue<SchedulerMessage> &queue_;
- MpmcWaiter &waiter_;
- };
-
- class IoWorker {
- public:
- explicit IoWorker(MpscPollableQueue<SchedulerMessage> &queue) : queue_(queue) {
- }
-
- void start_up() {
- auto &poll = SchedulerContext::get()->get_poll();
- poll.subscribe(queue_.reader_get_event_fd().get_fd(), Fd::Flag::Read);
- }
- void tear_down() {
- auto &poll = SchedulerContext::get()->get_poll();
- poll.unsubscribe(queue_.reader_get_event_fd().get_fd());
- }
-
- bool run_once(double timeout) {
- auto &dispatcher = SchedulerContext::get()->dispatcher();
- auto &poll = SchedulerContext::get()->get_poll();
- auto &heap = SchedulerContext::get()->get_heap();
-
- auto now = Time::now(); // update Time::now_cached()
- while (!heap.empty() && heap.top_key() <= now) {
- auto *heap_node = heap.pop();
- auto *actor_info = ActorInfo::from_heap_node(heap_node);
-
- ActorExecutor executor(*actor_info, dispatcher, ActorExecutor::Options().with_has_poll(true));
- if (executor.can_send_immediate()) {
- executor.send_immediate(ActorSignals::one(ActorSignals::Alarm));
- } else {
- executor.send(ActorSignals::one(ActorSignals::Alarm));
- }
- }
-
- const int size = queue_.reader_wait_nonblock();
- for (int i = 0; i < size; i++) {
- auto message = queue_.reader_get_unsafe();
- if (!message) {
- return false;
- }
- ActorExecutor executor(*message, dispatcher, ActorExecutor::Options().with_from_queue().with_has_poll(true));
- }
- queue_.reader_flush();
-
- bool can_sleep = size == 0 && timeout != 0;
- int32 timeout_ms = 0;
- if (can_sleep) {
- auto wakeup_timestamp = Timestamp::in(timeout);
- if (!heap.empty()) {
- wakeup_timestamp.relax(Timestamp::at(heap.top_key()));
- }
- timeout_ms = static_cast<int>(wakeup_timestamp.in() * 1000) + 1;
- if (timeout_ms < 0) {
- timeout_ms = 0;
- }
- //const int thirty_seconds = 30 * 1000;
- //if (timeout_ms > thirty_seconds) {
- //timeout_ms = thirty_seconds;
- //}
- }
- poll.run(timeout_ms);
- return true;
- }
-
- private:
- MpscPollableQueue<SchedulerMessage> &queue_;
- };
-
- void do_stop() {
- if (is_stopped_) {
- return;
- }
- // wait other threads to finish
- for (auto &thread : cpu_threads_) {
- thread.join();
- }
- // Can't do anything else, other schedulers may send queries to this one.
- // Must wait till every scheduler is stopped first..
- is_stopped_ = true;
-
- io_worker_.reset();
- poll_.clear();
-
- std::unique_lock<std::mutex> lock(scheduler_group_info_->active_scheduler_count_mutex);
- scheduler_group_info_->active_scheduler_count--;
- scheduler_group_info_->active_scheduler_count_condition_variable.notify_all();
- }
-
- public:
- static void close_scheduler_group(SchedulerGroupInfo &group_info) {
- LOG(ERROR) << "close scheduler group";
- // Cannot close scheduler group before somebody asked to stop them
- CHECK(group_info.is_stop_requested);
- {
- std::unique_lock<std::mutex> lock(group_info.active_scheduler_count_mutex);
- group_info.active_scheduler_count_condition_variable.wait(lock,
- [&] { return group_info.active_scheduler_count == 0; });
- }
-
- // Drain all queues
- // Just to destroy all elements should be ok.
- for (auto &scheduler_info : group_info.schedulers) {
- // Drain io queue
- auto &io_queue = *scheduler_info.io_queue;
- while (true) {
- int n = io_queue.reader_wait_nonblock();
- if (n == 0) {
- break;
- }
- while (n-- > 0) {
- auto message = io_queue.reader_get_unsafe();
- // message's destructor is called
- }
- }
- scheduler_info.io_queue.reset();
-
- // Drain cpu queue
- auto &cpu_queue = *scheduler_info.cpu_queue;
- while (true) {
- SchedulerMessage message;
- if (!cpu_queue.try_pop(message, get_thread_id())) {
- break;
- }
- // message's destructor is called
- }
- scheduler_info.cpu_queue.reset();
-
- // Do not destroy worker infos. run_in_context will crash if they are empty
- }
- }
-};
-
-// Actor messages
-template <class LambdaT>
-class ActorMessageLambda : public ActorMessageImpl {
- public:
- template <class FromLambdaT>
- explicit ActorMessageLambda(FromLambdaT &&lambda) : lambda_(std::forward<FromLambdaT>(lambda)) {
- }
- void run() override {
- lambda_();
- }
-
- private:
- LambdaT lambda_;
-};
-
-class ActorMessageHangup : public ActorMessageImpl {
- public:
- void run() override {
- ActorExecuteContext::get()->actor().hang_up();
- }
-};
-
-class ActorMessageCreator {
- public:
- template <class F>
- static ActorMessage lambda(F &&f) {
- return ActorMessage(std::make_unique<ActorMessageLambda<F>>(std::forward<F>(f)));
- }
-
- static ActorMessage hangup() {
- return ActorMessage(std::make_unique<ActorMessageHangup>());
- }
-
- // Use faster allocation?
-};
-
-// SYNTAX SHUGAR
-namespace detail {
-struct ActorRef {
- ActorRef(ActorInfo &actor_info, uint64 link_token = EmptyLinkToken) : actor_info(actor_info), link_token(link_token) {
- }
-
- ActorInfo &actor_info;
- uint64 link_token;
-};
-
-template <class T>
-T &current_actor() {
- return static_cast<T &>(ActorExecuteContext::get()->actor());
-}
-
-void send_message(ActorInfo &actor_info, ActorMessage message) {
- ActorExecutor executor(actor_info, SchedulerContext::get()->dispatcher(), ActorExecutor::Options());
- executor.send(std::move(message));
-}
-
-void send_message(ActorRef actor_ref, ActorMessage message) {
- message.set_link_token(actor_ref.link_token);
- send_message(actor_ref.actor_info, std::move(message));
-}
-void send_message_later(ActorInfo &actor_info, ActorMessage message) {
- ActorExecutor executor(actor_info, SchedulerContext::get()->dispatcher(), ActorExecutor::Options());
- executor.send(std::move(message));
-}
-
-void send_message_later(ActorRef actor_ref, ActorMessage message) {
- message.set_link_token(actor_ref.link_token);
- send_message_later(actor_ref.actor_info, std::move(message));
-}
-
-template <class ExecuteF, class ToMessageF>
-void send_immediate(ActorRef actor_ref, ExecuteF &&execute, ToMessageF &&to_message) {
- auto &scheduler_context = *SchedulerContext::get();
- ActorExecutor executor(actor_ref.actor_info, scheduler_context.dispatcher(),
- ActorExecutor::Options().with_has_poll(scheduler_context.has_poll()));
- if (executor.can_send_immediate()) {
- return executor.send_immediate(execute, actor_ref.link_token);
- }
- auto message = to_message();
- message.set_link_token(actor_ref.link_token);
- executor.send(std::move(message));
-}
-
-template <class F>
-void send_lambda(ActorRef actor_ref, F &&lambda) {
- send_immediate(actor_ref, lambda, [&lambda]() mutable { return ActorMessageCreator::lambda(std::move(lambda)); });
-}
-template <class F>
-void send_lambda_later(ActorRef actor_ref, F &&lambda) {
- send_message_later(actor_ref, ActorMessageCreator::lambda(std::move(lambda)));
-}
-
-template <class ClosureT>
-void send_closure_impl(ActorRef actor_ref, ClosureT &&closure) {
- using ActorType = typename ClosureT::ActorType;
- send_immediate(actor_ref, [&closure]() mutable { closure.run(&current_actor<ActorType>()); },
- [&closure]() mutable {
- return ActorMessageCreator::lambda([closure = to_delayed_closure(std::move(closure))]() mutable {
- closure.run(&current_actor<ActorType>());
- });
- });
-}
-
-template <class... ArgsT>
-void send_closure(ActorRef actor_ref, ArgsT &&... args) {
- send_closure_impl(actor_ref, create_immediate_closure(std::forward<ArgsT>(args)...));
-}
-
-template <class ClosureT>
-void send_closure_later_impl(ActorRef actor_ref, ClosureT &&closure) {
- using ActorType = typename ClosureT::ActorType;
- send_message_later(actor_ref, ActorMessageCreator::lambda([closure = std::move(closure)]() mutable {
- closure.run(&current_actor<ActorType>());
- }));
-}
-
-template <class... ArgsT>
-void send_closure_later(ActorRef actor_ref, ArgsT &&... args) {
- send_closure_later_impl(actor_ref, create_delayed_closure(std::forward<ArgsT>(args)...));
-}
-
-void register_actor_info_ptr(ActorInfoPtr actor_info_ptr) {
- auto state = actor_info_ptr->state().get_flags_unsafe();
- SchedulerContext::get()->add_to_queue(std::move(actor_info_ptr), state.get_scheduler_id(), !state.is_shared());
-}
-
-template <class T, class... ArgsT>
-ActorInfoPtr create_actor(ActorOptions &options, ArgsT &&... args) {
- auto *scheduler_context = SchedulerContext::get();
- if (!options.has_scheduler()) {
- options.on_scheduler(scheduler_context->get_scheduler_id());
- }
- auto res =
- scheduler_context->get_actor_info_creator().create(std::make_unique<T>(std::forward<ArgsT>(args)...), options);
- register_actor_info_ptr(res);
- return res;
-}
-} // namespace detail
-
-// Essentially ActorInfoWeakPtr with Type
-template <class ActorType = Actor>
-class ActorId {
- public:
- using ActorT = ActorType;
- ActorId() = default;
- ActorId(const ActorId &) = default;
- ActorId &operator=(const ActorId &) = default;
- ActorId(ActorId &&other) = default;
- ActorId &operator=(ActorId &&other) = default;
-
- // allow only conversion from child to parent
- template <class ToActorType, class = std::enable_if_t<std::is_base_of<ToActorType, ActorType>::value>>
- explicit operator ActorId<ToActorType>() const {
- return ActorId<ToActorType>(ptr_);
- }
-
- const ActorInfoPtr &actor_info_ptr() const {
- return ptr_;
- }
-
- ActorInfo &actor_info() const {
- CHECK(ptr_);
- return *ptr_;
- }
- bool empty() const {
- return !ptr_;
- }
-
- template <class... ArgsT>
- static ActorId<ActorType> create(ActorOptions &options, ArgsT &&... args) {
- return ActorId<ActorType>(detail::create_actor<ActorType>(options, std::forward<ArgsT>(args)...));
- }
-
- detail::ActorRef as_actor_ref() const {
- CHECK(!empty());
- return detail::ActorRef(*actor_info_ptr());
- }
-
- private:
- ActorInfoPtr ptr_;
-
- explicit ActorId(ActorInfoPtr ptr) : ptr_(std::move(ptr)) {
- }
-
- template <class SelfT>
- friend ActorId<SelfT> actor_id(SelfT *self);
-};
-
-template <class ActorType = Actor>
-class ActorOwn {
- public:
- using ActorT = ActorType;
- ActorOwn() = default;
- explicit ActorOwn(ActorId<ActorType> id) : id_(std::move(id)) {
- }
- template <class OtherActorType>
- explicit ActorOwn(ActorId<OtherActorType> id) : id_(std::move(id)) {
- }
- template <class OtherActorType>
- explicit ActorOwn(ActorOwn<OtherActorType> &&other) : id_(other.release()) {
- }
- template <class OtherActorType>
- ActorOwn &operator=(ActorOwn<OtherActorType> &&other) {
- reset(other.release());
- }
- ActorOwn(ActorOwn &&other) : id_(other.release()) {
- }
- ActorOwn &operator=(ActorOwn &&other) {
- reset(other.release());
- }
- ActorOwn(const ActorOwn &) = delete;
- ActorOwn &operator=(const ActorOwn &) = delete;
- ~ActorOwn() {
- reset();
- }
-
- bool empty() const {
- return id_.empty();
- }
- bool is_alive() const {
- return id_.is_alive();
- }
- ActorId<ActorType> get() const {
- return id_;
- }
- ActorId<ActorType> release() {
- return std::move(id_);
- }
- void reset(ActorId<ActorType> other = ActorId<ActorType>()) {
- static_assert(sizeof(ActorType) > 0, "Can't use ActorOwn with incomplete type");
- hangup();
- id_ = std::move(other);
- }
- const ActorId<ActorType> *operator->() const {
- return &id_;
- }
-
- detail::ActorRef as_actor_ref() const {
- CHECK(!empty());
- return detail::ActorRef(*id_.actor_info_ptr(), 0);
- }
-
- private:
- ActorId<ActorType> id_;
- void hangup() const {
- if (empty()) {
- return;
- }
- detail::send_message(as_actor_ref(), ActorMessageCreator::hangup());
- }
-};
-
-template <class ActorType = Actor>
-class ActorShared {
- public:
- using ActorT = ActorType;
- ActorShared() = default;
- template <class OtherActorType>
- ActorShared(ActorId<OtherActorType> id, uint64 token) : id_(std::move(id)), token_(token) {
- CHECK(token_ != 0);
- }
- template <class OtherActorType>
- ActorShared(ActorShared<OtherActorType> &&other) : id_(other.release()), token_(other.token()) {
- }
- template <class OtherActorType>
- ActorShared(ActorOwn<OtherActorType> &&other) : id_(other.release()), token_(other.token()) {
- }
- template <class OtherActorType>
- ActorShared &operator=(ActorShared<OtherActorType> &&other) {
- reset(other.release(), other.token());
- }
- ActorShared(ActorShared &&other) : id_(other.release()), token_(other.token()) {
- }
- ActorShared &operator=(ActorShared &&other) {
- reset(other.release(), other.token());
- }
- ActorShared(const ActorShared &) = delete;
- ActorShared &operator=(const ActorShared &) = delete;
- ~ActorShared() {
- reset();
- }
-
- uint64 token() const {
- return token_;
- }
- bool empty() const {
- return id_.empty();
- }
- bool is_alive() const {
- return id_.is_alive();
- }
- ActorId<ActorType> get() const {
- return id_;
- }
- ActorId<ActorType> release();
- void reset(ActorId<ActorType> other = ActorId<ActorType>(), uint64 link_token = EmptyLinkToken) {
- static_assert(sizeof(ActorType) > 0, "Can't use ActorShared with incomplete type");
- hangup();
- id_ = other;
- token_ = link_token;
- }
- const ActorId<ActorType> *operator->() const {
- return &id_;
- }
-
- detail::ActorRef as_actor_ref() const {
- CHECK(!empty());
- return detail::ActorRef(*id_.actor_info_ptr(), token_);
- }
-
- private:
- ActorId<ActorType> id_;
- uint64 token_;
-
- void hangup() const {
- }
-};
-
-// common interface
-template <class SelfT>
-ActorId<SelfT> actor_id(SelfT *self) {
- CHECK(self);
- CHECK(static_cast<Actor *>(self) == &ActorExecuteContext::get()->actor());
- return ActorId<SelfT>(ActorExecuteContext::get()->actor().get_actor_info_ptr());
-}
-
-inline ActorId<> actor_id() {
- return actor_id(&ActorExecuteContext::get()->actor());
-}
-
-template <class T, class... ArgsT>
-ActorOwn<T> create_actor(ActorOptions options, ArgsT &&... args) {
- return ActorOwn<T>(ActorId<T>::create(options, std::forward<ArgsT>(args)...));
-}
-
-template <class T, class... ArgsT>
-ActorOwn<T> create_actor(Slice name, ArgsT &&... args) {
- return ActorOwn<T>(ActorId<T>::create(ActorOptions().with_name(name), std::forward<ArgsT>(args)...));
-}
-
-template <class ActorIdT, class FunctionT, class... ArgsT>
-void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
- using ActorT = typename std::decay_t<ActorIdT>::ActorT;
- using FunctionClassT = member_function_class_t<FunctionT>;
- static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
-
- ActorIdT id = std::forward<ActorIdT>(actor_id);
- detail::send_closure(id.as_actor_ref(), function, std::forward<ArgsT>(args)...);
-}
-
-template <class ActorIdT, class FunctionT, class... ArgsT>
-void send_closure_later(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
- using ActorT = typename std::decay_t<ActorIdT>::ActorT;
- using FunctionClassT = member_function_class_t<FunctionT>;
- static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
-
- ActorIdT id = std::forward<ActorIdT>(actor_id);
- detail::send_closure_later(id.as_actor_ref(), function, std::forward<ArgsT>(args)...);
-}
-
-template <class ActorIdT, class... ArgsT>
-void send_lambda(ActorIdT &&actor_id, ArgsT &&... args) {
- ActorIdT id = std::forward<ActorIdT>(actor_id);
- detail::send_lambda(id.as_actor_ref(), std::forward<ArgsT>(args)...);
-}
-
-#endif //!TD_THREAD_UNSUPPORTED
-} // namespace actor2
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/test/actors_bugs.cpp b/protocols/Telegram/tdlib/td/tdactor/test/actors_bugs.cpp
index f4267f2818..0720f0ed6f 100644
--- a/protocols/Telegram/tdlib/td/tdactor/test/actors_bugs.cpp
+++ b/protocols/Telegram/tdlib/td/tdactor/test/actors_bugs.cpp
@@ -1,38 +1,39 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/tests.h"
-
-#include "td/actor/Timeout.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+#include "td/actor/MultiTimeout.h"
-using namespace td;
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Random.h"
+#include "td/utils/tests.h"
TEST(MultiTimeout, bug) {
- ConcurrentScheduler sched;
- int threads_n = 0;
- sched.init(threads_n);
+ td::ConcurrentScheduler sched(0, 0);
sched.start();
- std::unique_ptr<MultiTimeout> multi_timeout;
+ td::unique_ptr<td::MultiTimeout> multi_timeout;
struct Data {
- MultiTimeout *multi_timeout;
+ td::MultiTimeout *multi_timeout;
};
Data data;
{
- auto guard = sched.get_current_guard();
- multi_timeout = std::make_unique<MultiTimeout>();
+ auto guard = sched.get_main_guard();
+ multi_timeout = td::make_unique<td::MultiTimeout>("MultiTimeout");
data.multi_timeout = multi_timeout.get();
- multi_timeout->set_callback([](void *void_data, int64 key) {
+ multi_timeout->set_callback([](void *void_data, td::int64 key) {
auto &data = *static_cast<Data *>(void_data);
if (key == 1) {
data.multi_timeout->cancel_timeout(key + 1);
data.multi_timeout->set_timeout_in(key + 2, 1);
} else {
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
});
multi_timeout->set_callback_data(&data);
@@ -45,3 +46,67 @@ TEST(MultiTimeout, bug) {
}
sched.finish();
}
+
+class TimeoutManager final : public td::Actor {
+ static td::int32 count;
+
+ public:
+ TimeoutManager() {
+ count++;
+
+ test_timeout_.set_callback(on_test_timeout_callback);
+ test_timeout_.set_callback_data(static_cast<void *>(this));
+ }
+ TimeoutManager(const TimeoutManager &) = delete;
+ TimeoutManager &operator=(const TimeoutManager &) = delete;
+ TimeoutManager(TimeoutManager &&) = delete;
+ TimeoutManager &operator=(TimeoutManager &&) = delete;
+ ~TimeoutManager() final {
+ count--;
+ LOG(INFO) << "Destroy TimeoutManager";
+ }
+
+ static void on_test_timeout_callback(void *timeout_manager_ptr, td::int64 id) {
+ CHECK(count >= 0);
+ if (count == 0) {
+ LOG(ERROR) << "Receive timeout after manager was closed";
+ return;
+ }
+
+ auto manager = static_cast<TimeoutManager *>(timeout_manager_ptr);
+ send_closure_later(manager->actor_id(manager), &TimeoutManager::test_timeout);
+ }
+
+ void test_timeout() {
+ CHECK(count > 0);
+ // we must yield scheduler, so run_main breaks immediately, if timeouts are handled immediately
+ td::Scheduler::instance()->yield();
+ }
+
+ td::MultiTimeout test_timeout_{"TestTimeout"};
+};
+
+td::int32 TimeoutManager::count;
+
+TEST(MultiTimeout, Destroy) {
+ td::ConcurrentScheduler sched(0, 0);
+
+ auto timeout_manager = sched.create_actor_unsafe<TimeoutManager>(0, "TimeoutManager");
+ TimeoutManager *manager = timeout_manager.get().get_actor_unsafe();
+ sched.start();
+ int cnt = 100;
+ while (sched.run_main(cnt == 100 || cnt <= 0 ? 0.001 : 10)) {
+ auto guard = sched.get_main_guard();
+ cnt--;
+ if (cnt > 0) {
+ for (int i = 0; i < 2; i++) {
+ manager->test_timeout_.set_timeout_in(td::Random::fast(0, 1000000000), td::Random::fast(2, 5) / 1000.0);
+ }
+ } else if (cnt == 0) {
+ timeout_manager.reset();
+ } else if (cnt == -10) {
+ td::Scheduler::instance()->finish();
+ }
+ }
+ sched.finish();
+}
diff --git a/protocols/Telegram/tdlib/td/tdactor/test/actors_impl2.cpp b/protocols/Telegram/tdlib/td/tdactor/test/actors_impl2.cpp
deleted file mode 100644
index 9185fe8858..0000000000
--- a/protocols/Telegram/tdlib/td/tdactor/test/actors_impl2.cpp
+++ /dev/null
@@ -1,535 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#include "td/actor/impl2/ActorLocker.h"
-#include "td/actor/impl2/Scheduler.h"
-
-#include "td/utils/format.h"
-#include "td/utils/logging.h"
-#include "td/utils/port/thread.h"
-#include "td/utils/Slice.h"
-#include "td/utils/StringBuilder.h"
-#include "td/utils/tests.h"
-#include "td/utils/Time.h"
-
-#include <array>
-#include <atomic>
-#include <deque>
-#include <memory>
-
-using td::actor2::ActorLocker;
-using td::actor2::ActorSignals;
-using td::actor2::ActorState;
-using td::actor2::SchedulerId;
-
-TEST(Actor2, signals) {
- ActorSignals signals;
- signals.add_signal(ActorSignals::Wakeup);
- signals.add_signal(ActorSignals::Cpu);
- signals.add_signal(ActorSignals::Kill);
- signals.clear_signal(ActorSignals::Cpu);
-
- bool was_kill = false;
- bool was_wakeup = false;
- while (!signals.empty()) {
- auto s = signals.first_signal();
- if (s == ActorSignals::Kill) {
- was_kill = true;
- } else if (s == ActorSignals::Wakeup) {
- was_wakeup = true;
- } else {
- UNREACHABLE();
- }
- signals.clear_signal(s);
- }
- CHECK(was_kill && was_wakeup);
-}
-
-TEST(Actors2, flags) {
- ActorState::Flags flags;
- CHECK(!flags.is_locked());
- flags.set_locked(true);
- CHECK(flags.is_locked());
- flags.set_locked(false);
- CHECK(!flags.is_locked());
- flags.set_pause(true);
-
- flags.set_scheduler_id(SchedulerId{123});
-
- auto signals = flags.get_signals();
- CHECK(signals.empty());
- signals.add_signal(ActorSignals::Cpu);
- signals.add_signal(ActorSignals::Kill);
- CHECK(signals.has_signal(ActorSignals::Cpu));
- CHECK(signals.has_signal(ActorSignals::Kill));
- flags.set_signals(signals);
- CHECK(flags.get_signals().raw() == signals.raw()) << flags.get_signals().raw() << " " << signals.raw();
-
- auto wakeup = ActorSignals{};
- wakeup.add_signal(ActorSignals::Wakeup);
-
- flags.add_signals(wakeup);
- signals.add_signal(ActorSignals::Wakeup);
- CHECK(flags.get_signals().raw() == signals.raw());
-
- flags.clear_signals();
- CHECK(flags.get_signals().empty());
-
- CHECK(flags.get_scheduler_id().value() == 123);
- CHECK(flags.is_pause());
-}
-
-TEST(Actor2, locker) {
- ActorState state;
-
- ActorSignals kill_signal;
- kill_signal.add_signal(ActorSignals::Kill);
-
- ActorSignals wakeup_signal;
- kill_signal.add_signal(ActorSignals::Wakeup);
-
- ActorSignals cpu_signal;
- kill_signal.add_signal(ActorSignals::Cpu);
-
- {
- ActorLocker lockerA(&state);
- ActorLocker lockerB(&state);
- ActorLocker lockerC(&state);
-
- CHECK(lockerA.try_lock());
- CHECK(lockerA.own_lock());
- auto flagsA = lockerA.flags();
- CHECK(lockerA.try_unlock(flagsA));
- CHECK(!lockerA.own_lock());
-
- CHECK(lockerA.try_lock());
- CHECK(!lockerB.try_lock());
- CHECK(!lockerC.try_lock());
-
- CHECK(lockerB.try_add_signals(kill_signal));
- CHECK(!lockerC.try_add_signals(wakeup_signal));
- CHECK(lockerC.try_add_signals(wakeup_signal));
- CHECK(!lockerC.add_signals(cpu_signal));
- CHECK(!lockerA.flags().has_signals());
- CHECK(!lockerA.try_unlock(lockerA.flags()));
- {
- auto flags = lockerA.flags();
- auto signals = flags.get_signals();
- bool was_kill = false;
- bool was_wakeup = false;
- bool was_cpu = false;
- while (!signals.empty()) {
- auto s = signals.first_signal();
- if (s == ActorSignals::Kill) {
- was_kill = true;
- } else if (s == ActorSignals::Wakeup) {
- was_wakeup = true;
- } else if (s == ActorSignals::Cpu) {
- was_cpu = true;
- } else {
- UNREACHABLE();
- }
- signals.clear_signal(s);
- }
- CHECK(was_kill && was_wakeup && was_cpu);
- flags.clear_signals();
- CHECK(lockerA.try_unlock(flags));
- }
- }
-
- {
- ActorLocker lockerB(&state);
- CHECK(lockerB.try_lock());
- CHECK(lockerB.try_unlock(lockerB.flags()));
- CHECK(lockerB.add_signals(kill_signal));
- CHECK(lockerB.flags().get_signals().has_signal(ActorSignals::Kill));
- auto flags = lockerB.flags();
- flags.clear_signals();
- ActorLocker lockerA(&state);
- CHECK(!lockerA.add_signals(kill_signal));
- CHECK(!lockerB.try_unlock(flags));
- CHECK(!lockerA.add_signals(kill_signal)); // do not loose this signal!
- CHECK(!lockerB.try_unlock(flags));
- CHECK(lockerB.flags().get_signals().has_signal(ActorSignals::Kill));
- CHECK(lockerB.try_unlock(flags));
- }
-
- {
- ActorLocker lockerA(&state);
- CHECK(lockerA.try_lock());
- auto flags = lockerA.flags();
- flags.set_pause(true);
- CHECK(lockerA.try_unlock(flags));
- //We have to lock, though we can't execute.
- CHECK(lockerA.add_signals(wakeup_signal));
- }
-}
-
-#if !TD_THREAD_UNSUPPORTED
-TEST(Actor2, locker_stress) {
- ActorState state;
-
- constexpr size_t threads_n = 5;
- auto stage = [&](std::atomic<int> &value, int need) {
- value.fetch_add(1, std::memory_order_release);
- while (value.load(std::memory_order_acquire) < need) {
- td::this_thread::yield();
- }
- };
-
- struct Node {
- std::atomic<td::uint32> request{0};
- td::uint32 response = 0;
- char pad[64];
- };
- std::array<Node, threads_n> nodes;
- auto do_work = [&]() {
- for (auto &node : nodes) {
- auto query = node.request.load(std::memory_order_acquire);
- if (query) {
- node.response = query * query;
- node.request.store(0, std::memory_order_relaxed);
- }
- }
- };
-
- std::atomic<int> begin{0};
- std::atomic<int> ready{0};
- std::atomic<int> check{0};
- std::atomic<int> finish{0};
- std::vector<td::thread> threads;
- for (size_t i = 0; i < threads_n; i++) {
- threads.push_back(td::thread([&, id = i] {
- for (size_t i = 1; i < 1000000; i++) {
- ActorLocker locker(&state);
- auto need = static_cast<int>(threads_n * i);
- auto query = static_cast<td::uint32>(id + need);
- stage(begin, need);
- nodes[id].request = 0;
- nodes[id].response = 0;
- stage(ready, need);
- if (locker.try_lock()) {
- nodes[id].response = query * query;
- } else {
- auto cpu = ActorSignals::one(ActorSignals::Cpu);
- nodes[id].request.store(query, std::memory_order_release);
- locker.add_signals(cpu);
- }
- while (locker.own_lock()) {
- auto flags = locker.flags();
- auto signals = flags.get_signals();
- if (!signals.empty()) {
- do_work();
- }
- flags.clear_signals();
- locker.try_unlock(flags);
- }
-
- stage(check, need);
- if (id == 0) {
- CHECK(locker.add_signals(ActorSignals{}));
- CHECK(!locker.flags().has_signals());
- CHECK(locker.try_unlock(locker.flags()));
- for (size_t thread_id = 0; thread_id < threads_n; thread_id++) {
- CHECK(nodes[thread_id].response ==
- static_cast<td::uint32>(thread_id + need) * static_cast<td::uint32>(thread_id + need))
- << td::tag("thread", thread_id) << " " << nodes[thread_id].response << " "
- << nodes[thread_id].request.load();
- }
- }
- }
- }));
- }
- for (auto &thread : threads) {
- thread.join();
- }
-}
-
-namespace {
-const size_t BUF_SIZE = 1024 * 1024;
-char buf[BUF_SIZE];
-td::StringBuilder sb(td::MutableSlice(buf, BUF_SIZE - 1));
-} // namespace
-
-TEST(Actor2, executor_simple) {
- using namespace td;
- using namespace td::actor2;
- struct Dispatcher : public SchedulerDispatcher {
- void add_to_queue(ActorInfoPtr ptr, SchedulerId scheduler_id, bool need_poll) override {
- queue.push_back(std::move(ptr));
- }
- void set_alarm_timestamp(const ActorInfoPtr &actor_info_ptr, Timestamp timestamp) override {
- UNREACHABLE();
- }
- SchedulerId get_scheduler_id() const override {
- return SchedulerId{0};
- }
- std::deque<ActorInfoPtr> queue;
- };
- Dispatcher dispatcher;
-
- class TestActor : public Actor {
- public:
- void close() {
- stop();
- }
-
- private:
- void start_up() override {
- sb << "StartUp";
- }
- void tear_down() override {
- sb << "TearDown";
- }
- };
- ActorInfoCreator actor_info_creator;
- auto actor = actor_info_creator.create(
- std::make_unique<TestActor>(), ActorInfoCreator::Options().on_scheduler(SchedulerId{0}).with_name("TestActor"));
- dispatcher.add_to_queue(actor, SchedulerId{0}, false);
-
- {
- ActorExecutor executor(*actor, dispatcher, ActorExecutor::Options());
- CHECK(executor.can_send());
- CHECK(executor.can_send_immediate());
- CHECK(sb.as_cslice() == "StartUp") << sb.as_cslice();
- sb.clear();
- executor.send(ActorMessageCreator::lambda([&] { sb << "A"; }));
- CHECK(sb.as_cslice() == "A") << sb.as_cslice();
- sb.clear();
- auto big_message = ActorMessageCreator::lambda([&] { sb << "big"; });
- big_message.set_big();
- executor.send(std::move(big_message));
- CHECK(sb.as_cslice() == "") << sb.as_cslice();
- executor.send(ActorMessageCreator::lambda([&] { sb << "A"; }));
- CHECK(sb.as_cslice() == "") << sb.as_cslice();
- }
- CHECK(dispatcher.queue.size() == 1);
- { ActorExecutor executor(*actor, dispatcher, ActorExecutor::Options().with_from_queue()); }
- CHECK(dispatcher.queue.size() == 1);
- dispatcher.queue.clear();
- CHECK(sb.as_cslice() == "bigA") << sb.as_cslice();
- sb.clear();
- {
- ActorExecutor executor(*actor, dispatcher, ActorExecutor::Options());
- executor.send(
- ActorMessageCreator::lambda([&] { static_cast<TestActor &>(ActorExecuteContext::get()->actor()).close(); }));
- }
- CHECK(sb.as_cslice() == "TearDown") << sb.as_cslice();
- sb.clear();
- CHECK(!actor->has_actor());
- {
- ActorExecutor executor(*actor, dispatcher, ActorExecutor::Options());
- executor.send(
- ActorMessageCreator::lambda([&] { static_cast<TestActor &>(ActorExecuteContext::get()->actor()).close(); }));
- }
- CHECK(dispatcher.queue.empty());
- CHECK(sb.as_cslice() == "");
-}
-
-using namespace td::actor2;
-using td::uint32;
-static std::atomic<int> cnt;
-class Worker : public Actor {
- public:
- void query(uint32 x, ActorInfoPtr master);
- void close() {
- stop();
- }
-};
-class Master : public Actor {
- public:
- void on_result(uint32 x, uint32 y) {
- loop();
- }
-
- private:
- uint32 l = 0;
- uint32 r = 100000;
- ActorInfoPtr worker;
- void start_up() override {
- worker = detail::create_actor<Worker>(ActorOptions().with_name("Master"));
- loop();
- }
- void loop() override {
- l++;
- if (l == r) {
- if (!--cnt) {
- SchedulerContext::get()->stop();
- }
- detail::send_closure(*worker, &Worker::close);
- stop();
- return;
- }
- detail::send_lambda(*worker,
- [x = l, self = get_actor_info_ptr()] { detail::current_actor<Worker>().query(x, self); });
- }
-};
-
-void Worker::query(uint32 x, ActorInfoPtr master) {
- auto y = x;
- for (int i = 0; i < 100; i++) {
- y = y * y;
- }
- detail::send_lambda(*master, [result = y, x] { detail::current_actor<Master>().on_result(x, result); });
-}
-
-TEST(Actor2, scheduler_simple) {
- auto group_info = std::make_shared<SchedulerGroupInfo>(1);
- Scheduler scheduler{group_info, SchedulerId{0}, 2};
- scheduler.start();
- scheduler.run_in_context([] {
- cnt = 10;
- for (int i = 0; i < 10; i++) {
- detail::create_actor<Master>(ActorOptions().with_name("Master"));
- }
- });
- while (scheduler.run(1000)) {
- }
- Scheduler::close_scheduler_group(*group_info);
-}
-
-TEST(Actor2, actor_id_simple) {
- auto group_info = std::make_shared<SchedulerGroupInfo>(1);
- Scheduler scheduler{group_info, SchedulerId{0}, 2};
- sb.clear();
- scheduler.start();
-
- scheduler.run_in_context([] {
- class A : public Actor {
- public:
- A(int value) : value_(value) {
- sb << "A" << value_;
- }
- void hello() {
- sb << "hello";
- }
- ~A() {
- sb << "~A";
- if (--cnt <= 0) {
- SchedulerContext::get()->stop();
- }
- }
-
- private:
- int value_;
- };
- cnt = 1;
- auto id = create_actor<A>("A", 123);
- CHECK(sb.as_cslice() == "A123");
- sb.clear();
- send_closure(id, &A::hello);
- });
- while (scheduler.run(1000)) {
- }
- CHECK(sb.as_cslice() == "hello~A");
- Scheduler::close_scheduler_group(*group_info);
- sb.clear();
-}
-
-TEST(Actor2, actor_creation) {
- auto group_info = std::make_shared<SchedulerGroupInfo>(1);
- Scheduler scheduler{group_info, SchedulerId{0}, 1};
- scheduler.start();
-
- scheduler.run_in_context([]() mutable {
- class B;
- class A : public Actor {
- public:
- void f() {
- check();
- stop();
- }
-
- private:
- void start_up() override {
- check();
- create_actor<B>("Simple", actor_id(this)).release();
- }
-
- void check() {
- auto &context = *SchedulerContext::get();
- CHECK(context.has_poll());
- context.get_poll();
- }
-
- void tear_down() override {
- if (--cnt <= 0) {
- SchedulerContext::get()->stop();
- }
- }
- };
-
- class B : public Actor {
- public:
- B(ActorId<A> a) : a_(a) {
- }
-
- private:
- void start_up() override {
- auto &context = *SchedulerContext::get();
- CHECK(!context.has_poll());
- send_closure(a_, &A::f);
- stop();
- }
- void tear_down() override {
- if (--cnt <= 0) {
- SchedulerContext::get()->stop();
- }
- }
- ActorId<A> a_;
- };
- cnt = 2;
- create_actor<A>(ActorOptions().with_name("Poll").with_poll()).release();
- });
- while (scheduler.run(1000)) {
- }
- scheduler.stop();
- Scheduler::close_scheduler_group(*group_info);
-}
-
-TEST(Actor2, actor_timeout_simple) {
- auto group_info = std::make_shared<SchedulerGroupInfo>(1);
- Scheduler scheduler{group_info, SchedulerId{0}, 2};
- sb.clear();
- scheduler.start();
-
- scheduler.run_in_context([] {
- class A : public Actor {
- public:
- void start_up() override {
- set_timeout();
- }
- void alarm() override {
- double diff = td::Time::now() - expected_timeout_;
- CHECK(-0.001 < diff && diff < 0.1) << diff;
- if (cnt_-- > 0) {
- set_timeout();
- } else {
- stop();
- }
- }
-
- void tear_down() override {
- SchedulerContext::get()->stop();
- }
-
- private:
- double expected_timeout_;
- int cnt_ = 5;
- void set_timeout() {
- auto wakeup_timestamp = td::Timestamp::in(0.1);
- expected_timeout_ = wakeup_timestamp.at();
- alarm_timestamp() = wakeup_timestamp;
- }
- };
- create_actor<A>(ActorInfoCreator::Options().with_name("A").with_poll()).release();
- });
- while (scheduler.run(1000)) {
- }
- Scheduler::close_scheduler_group(*group_info);
- sb.clear();
-}
-#endif //!TD_THREAD_UNSUPPORTED
diff --git a/protocols/Telegram/tdlib/td/tdactor/test/actors_main.cpp b/protocols/Telegram/tdlib/td/tdactor/test/actors_main.cpp
index ffceacc595..628b74a94c 100644
--- a/protocols/Telegram/tdlib/td/tdactor/test/actors_main.cpp
+++ b/protocols/Telegram/tdlib/td/tdactor/test/actors_main.cpp
@@ -1,35 +1,32 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/tests.h"
-
#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
#include "td/actor/PromiseFuture.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/Random.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/tests.h"
#include <limits>
#include <map>
+#include <memory>
#include <utility>
-using namespace td;
-
-REGISTER_TESTS(actors_main);
-
-namespace {
-
template <class ContainerT>
static typename ContainerT::value_type &rand_elem(ContainerT &cont) {
CHECK(0 < cont.size() && cont.size() <= static_cast<size_t>(std::numeric_limits<int>::max()));
- return cont[Random::fast(0, static_cast<int>(cont.size()) - 1)];
+ return cont[td::Random::fast(0, static_cast<int>(cont.size()) - 1)];
}
-static uint32 fast_pow_mod_uint32(uint32 x, uint32 p) {
- uint32 res = 1;
+static td::uint32 fast_pow_mod_uint32(td::uint32 x, td::uint32 p) {
+ td::uint32 res = 1;
while (p) {
if (p & 1) {
res *= x;
@@ -40,25 +37,25 @@ static uint32 fast_pow_mod_uint32(uint32 x, uint32 p) {
return res;
}
-static uint32 slow_pow_mod_uint32(uint32 x, uint32 p) {
- uint32 res = 1;
- for (uint32 i = 0; i < p; i++) {
+static td::uint32 slow_pow_mod_uint32(td::uint32 x, td::uint32 p) {
+ td::uint32 res = 1;
+ for (td::uint32 i = 0; i < p; i++) {
res *= x;
}
return res;
}
-struct Query {
- uint32 query_id;
- uint32 result;
- std::vector<int> todo;
- Query() = default;
- Query(const Query &) = delete;
- Query &operator=(const Query &) = delete;
- Query(Query &&) = default;
- Query &operator=(Query &&) = default;
- ~Query() {
- CHECK(todo.empty()) << "Query lost";
+struct ActorQuery {
+ td::uint32 query_id{};
+ td::uint32 result{};
+ td::vector<int> todo;
+ ActorQuery() = default;
+ ActorQuery(const ActorQuery &) = delete;
+ ActorQuery &operator=(const ActorQuery &) = delete;
+ ActorQuery(ActorQuery &&) = default;
+ ActorQuery &operator=(ActorQuery &&) = default;
+ ~ActorQuery() {
+ LOG_CHECK(todo.empty()) << "ActorQuery lost";
}
int next_pow() {
CHECK(!todo.empty());
@@ -71,25 +68,25 @@ struct Query {
}
};
-static uint32 fast_calc(Query &q) {
- uint32 result = q.result;
+static td::uint32 fast_calc(ActorQuery &q) {
+ td::uint32 result = q.result;
for (auto x : q.todo) {
result = fast_pow_mod_uint32(result, x);
}
return result;
}
-class Worker final : public Actor {
+class Worker final : public td::Actor {
public:
explicit Worker(int threads_n) : threads_n_(threads_n) {
}
- void query(PromiseActor<uint32> &&promise, uint32 x, uint32 p) {
- uint32 result = slow_pow_mod_uint32(x, p);
+ void query(td::PromiseActor<td::uint32> &&promise, td::uint32 x, td::uint32 p) {
+ td::uint32 result = slow_pow_mod_uint32(x, p);
promise.set_value(std::move(result));
(void)threads_n_;
- // if (threads_n_ > 1 && Random::fast(0, 9) == 0) {
- // migrate(Random::fast(2, threads_n));
+ // if (threads_n_ > 1 && td::Random::fast(0, 9) == 0) {
+ // migrate(td::Random::fast(2, threads_n));
//}
}
@@ -97,7 +94,7 @@ class Worker final : public Actor {
int threads_n_;
};
-class QueryActor final : public Actor {
+class QueryActor final : public td::Actor {
public:
class Callback {
public:
@@ -107,44 +104,46 @@ class QueryActor final : public Actor {
Callback(Callback &&) = delete;
Callback &operator=(Callback &&) = delete;
virtual ~Callback() = default;
- virtual void on_result(Query &&query) = 0;
+ virtual void on_result(ActorQuery &&query) = 0;
virtual void on_closed() = 0;
};
explicit QueryActor(int threads_n) : threads_n_(threads_n) {
}
- void set_callback(std::unique_ptr<Callback> callback) {
+ void set_callback(td::unique_ptr<Callback> callback) {
callback_ = std::move(callback);
}
- void set_workers(std::vector<ActorId<Worker>> workers) {
+ void set_workers(td::vector<td::ActorId<Worker>> workers) {
workers_ = std::move(workers);
}
- void query(Query &&query) {
- uint32 x = query.result;
- uint32 p = query.next_pow();
- if (Random::fast(0, 3) && (p <= 1000 || workers_.empty())) {
+ void query(ActorQuery &&query) {
+ td::uint32 x = query.result;
+ td::uint32 p = query.next_pow();
+ if (td::Random::fast(0, 3) && (p <= 1000 || workers_.empty())) {
query.result = slow_pow_mod_uint32(x, p);
callback_->on_result(std::move(query));
} else {
- auto future = send_promise(rand_elem(workers_), Random::fast(0, 3) == 0 ? 0 : Send::later, &Worker::query, x, p);
+ auto future = td::Random::fast(0, 3) == 0
+ ? td::send_promise<td::ActorSendType::Immediate>(rand_elem(workers_), &Worker::query, x, p)
+ : td::send_promise<td::ActorSendType::Later>(rand_elem(workers_), &Worker::query, x, p);
if (future.is_ready()) {
query.result = future.move_as_ok();
callback_->on_result(std::move(query));
} else {
- future.set_event(EventCreator::raw(actor_id(), query.query_id));
+ future.set_event(td::EventCreator::raw(actor_id(), query.query_id));
auto query_id = query.query_id;
- pending_.insert(std::make_pair(query_id, std::make_pair(std::move(future), std::move(query))));
+ pending_.emplace(query_id, std::make_pair(std::move(future), std::move(query)));
}
}
- if (threads_n_ > 1 && Random::fast(0, 9) == 0) {
- migrate(Random::fast(2, threads_n_));
+ if (threads_n_ > 1 && td::Random::fast(0, 9) == 0) {
+ migrate(td::Random::fast(2, threads_n_));
}
}
- void raw_event(const Event::Raw &event) override {
- uint32 id = event.u32;
+ void raw_event(const td::Event::Raw &event) final {
+ td::uint32 id = event.u32;
auto it = pending_.find(id);
auto future = std::move(it->second.first);
auto query = std::move(it->second.second);
@@ -159,44 +158,44 @@ class QueryActor final : public Actor {
stop();
}
- void on_start_migrate(int32 sched_id) override {
+ void on_start_migrate(td::int32 sched_id) final {
for (auto &it : pending_) {
start_migrate(it.second.first, sched_id);
}
}
- void on_finish_migrate() override {
+ void on_finish_migrate() final {
for (auto &it : pending_) {
finish_migrate(it.second.first);
}
}
private:
- unique_ptr<Callback> callback_;
- std::map<uint32, std::pair<FutureActor<uint32>, Query>> pending_;
- std::vector<ActorId<Worker>> workers_;
+ td::unique_ptr<Callback> callback_;
+ std::map<td::uint32, std::pair<td::FutureActor<td::uint32>, ActorQuery>> pending_;
+ td::vector<td::ActorId<Worker>> workers_;
int threads_n_;
};
-class MainQueryActor final : public Actor {
- class QueryActorCallback : public QueryActor::Callback {
+class MainQueryActor final : public td::Actor {
+ class QueryActorCallback final : public QueryActor::Callback {
public:
- void on_result(Query &&query) override {
+ void on_result(ActorQuery &&query) final {
if (query.ready()) {
send_closure(parent_id_, &MainQueryActor::on_result, std::move(query));
} else {
send_closure(next_solver_, &QueryActor::query, std::move(query));
}
}
- void on_closed() override {
+ void on_closed() final {
send_closure(parent_id_, &MainQueryActor::on_closed);
}
- QueryActorCallback(ActorId<MainQueryActor> parent_id, ActorId<QueryActor> next_solver)
+ QueryActorCallback(td::ActorId<MainQueryActor> parent_id, td::ActorId<QueryActor> next_solver)
: parent_id_(parent_id), next_solver_(next_solver) {
}
private:
- ActorId<MainQueryActor> parent_id_;
- ActorId<QueryActor> next_solver_;
+ td::ActorId<MainQueryActor> parent_id_;
+ td::ActorId<QueryActor> next_solver_;
};
const int ACTORS_CNT = 10;
@@ -206,39 +205,39 @@ class MainQueryActor final : public Actor {
explicit MainQueryActor(int threads_n) : threads_n_(threads_n) {
}
- void start_up() override {
+ void start_up() final {
actors_.resize(ACTORS_CNT);
for (auto &actor : actors_) {
- auto actor_ptr = make_unique<QueryActor>(threads_n_);
- actor = register_actor("QueryActor", std::move(actor_ptr), threads_n_ > 1 ? Random::fast(2, threads_n_) : 0)
+ auto actor_ptr = td::make_unique<QueryActor>(threads_n_);
+ actor = register_actor("QueryActor", std::move(actor_ptr), threads_n_ > 1 ? td::Random::fast(2, threads_n_) : 0)
.release();
}
workers_.resize(WORKERS_CNT);
for (auto &worker : workers_) {
- auto actor_ptr = make_unique<Worker>(threads_n_);
- worker =
- register_actor("Worker", std::move(actor_ptr), threads_n_ > 1 ? Random::fast(2, threads_n_) : 0).release();
+ auto actor_ptr = td::make_unique<Worker>(threads_n_);
+ worker = register_actor("Worker", std::move(actor_ptr), threads_n_ > 1 ? td::Random::fast(2, threads_n_) : 0)
+ .release();
}
for (int i = 0; i < ACTORS_CNT; i++) {
ref_cnt_++;
send_closure(actors_[i], &QueryActor::set_callback,
- make_unique<QueryActorCallback>(actor_id(this), actors_[(i + 1) % ACTORS_CNT]));
+ td::make_unique<QueryActorCallback>(actor_id(this), actors_[(i + 1) % ACTORS_CNT]));
send_closure(actors_[i], &QueryActor::set_workers, workers_);
}
yield();
}
- void on_result(Query &&query) {
+ void on_result(ActorQuery &&query) {
CHECK(query.ready());
CHECK(query.result == expected_[query.query_id]);
in_cnt_++;
wakeup();
}
- Query create_query() {
- Query q;
+ ActorQuery create_query() {
+ ActorQuery q;
q.query_id = (query_id_ += 2);
q.result = q.query_id;
q.todo = {1, 1, 1, 1, 1, 1, 1, 1, 10000};
@@ -249,14 +248,14 @@ class MainQueryActor final : public Actor {
void on_closed() {
ref_cnt_--;
if (ref_cnt_ == 0) {
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
}
- void wakeup() override {
- int cnt = 100000;
+ void wakeup() final {
+ int cnt = 10000;
while (out_cnt_ < in_cnt_ + 100 && out_cnt_ < cnt) {
- if (Random::fast(0, 1)) {
+ if (td::Random::fast_bool()) {
send_closure(rand_elem(actors_), &QueryActor::query, create_query());
} else {
send_closure_later(rand_elem(actors_), &QueryActor::query, create_query());
@@ -273,9 +272,9 @@ class MainQueryActor final : public Actor {
}
private:
- std::map<uint32, uint32> expected_;
- std::vector<ActorId<QueryActor>> actors_;
- std::vector<ActorId<Worker>> workers_;
+ std::map<td::uint32, td::uint32> expected_;
+ td::vector<td::ActorId<QueryActor>> actors_;
+ td::vector<td::ActorId<Worker>> workers_;
int out_cnt_ = 0;
int in_cnt_ = 0;
int query_id_ = 1;
@@ -283,101 +282,104 @@ class MainQueryActor final : public Actor {
int threads_n_;
};
-class SimpleActor final : public Actor {
+class SimpleActor final : public td::Actor {
public:
- explicit SimpleActor(int32 threads_n) : threads_n_(threads_n) {
+ explicit SimpleActor(td::int32 threads_n) : threads_n_(threads_n) {
}
- void start_up() override {
- auto actor_ptr = make_unique<Worker>(threads_n_);
+ void start_up() final {
+ auto actor_ptr = td::make_unique<Worker>(threads_n_);
worker_ =
- register_actor("Worker", std::move(actor_ptr), threads_n_ > 1 ? Random::fast(2, threads_n_) : 0).release();
+ register_actor("Worker", std::move(actor_ptr), threads_n_ > 1 ? td::Random::fast(2, threads_n_) : 0).release();
yield();
}
- void wakeup() override {
- if (q_ == 100000) {
- Scheduler::instance()->finish();
+ void wakeup() final {
+ if (q_ == 10000) {
+ td::Scheduler::instance()->finish();
stop();
return;
}
q_++;
- p_ = Random::fast(0, 1) ? 1 : 10000;
- auto future = send_promise(worker_, Random::fast(0, 3) == 0 ? 0 : Send::later, &Worker::query, q_, p_);
+ p_ = td::Random::fast_bool() ? 1 : 10000;
+ auto future = td::Random::fast(0, 3) == 0
+ ? td::send_promise<td::ActorSendType::Immediate>(worker_, &Worker::query, q_, p_)
+ : td::send_promise<td::ActorSendType::Later>(worker_, &Worker::query, q_, p_);
if (future.is_ready()) {
auto result = future.move_as_ok();
CHECK(result == fast_pow_mod_uint32(q_, p_));
yield();
} else {
- future.set_event(EventCreator::raw(actor_id(), nullptr));
+ future.set_event(td::EventCreator::raw(actor_id(), nullptr));
future_ = std::move(future);
}
- // if (threads_n_ > 1 && Random::fast(0, 2) == 0) {
- // migrate(Random::fast(1, threads_n));
+ // if (threads_n_ > 1 && td::Random::fast(0, 2) == 0) {
+ // migrate(td::Random::fast(1, threads_n));
//}
}
- void raw_event(const Event::Raw &event) override {
+ void raw_event(const td::Event::Raw &event) final {
auto result = future_.move_as_ok();
CHECK(result == fast_pow_mod_uint32(q_, p_));
yield();
}
- void on_start_migrate(int32 sched_id) override {
+ void on_start_migrate(td::int32 sched_id) final {
start_migrate(future_, sched_id);
}
- void on_finish_migrate() override {
+ void on_finish_migrate() final {
finish_migrate(future_);
}
private:
- int32 threads_n_;
- ActorId<Worker> worker_;
- FutureActor<uint32> future_;
- uint32 q_ = 1;
- uint32 p_;
+ td::int32 threads_n_;
+ td::ActorId<Worker> worker_;
+ td::FutureActor<td::uint32> future_;
+ td::uint32 q_ = 1;
+ td::uint32 p_ = 0;
};
-} // namespace
-class SendToDead : public Actor {
+class SendToDead final : public td::Actor {
public:
- class Parent : public Actor {
+ class Parent final : public td::Actor {
public:
- explicit Parent(ActorShared<> parent, int ttl = 3) : parent_(std::move(parent)), ttl_(ttl) {
+ explicit Parent(td::ActorShared<> parent, int ttl = 3) : parent_(std::move(parent)), ttl_(ttl) {
}
- void start_up() override {
- set_timeout_in(Random::fast_uint32() % 3 * 0.001);
+ void start_up() final {
+ set_timeout_in(td::Random::fast_uint32() % 3 * 0.001);
if (ttl_ != 0) {
- child_ = create_actor_on_scheduler<Parent>(
- "Child", Random::fast_uint32() % Scheduler::instance()->sched_count(), actor_shared(), ttl_ - 1);
+ child_ = td::create_actor_on_scheduler<Parent>(
+ "Child", td::Random::fast_uint32() % td::Scheduler::instance()->sched_count(), actor_shared(this),
+ ttl_ - 1);
}
}
- void timeout_expired() override {
+ void timeout_expired() final {
stop();
}
private:
- ActorOwn<Parent> child_;
- ActorShared<> parent_;
+ td::ActorOwn<Parent> child_;
+ td::ActorShared<> parent_;
int ttl_;
};
- void start_up() override {
+ void start_up() final {
for (int i = 0; i < 2000; i++) {
- create_actor_on_scheduler<Parent>("Parent", Random::fast_uint32() % Scheduler::instance()->sched_count(),
- create_reference(), 4)
+ td::create_actor_on_scheduler<Parent>(
+ "Parent", td::Random::fast_uint32() % td::Scheduler::instance()->sched_count(), create_reference(), 4)
.release();
}
}
- ActorShared<> create_reference() {
+ td::ActorShared<> create_reference() {
ref_cnt_++;
- return actor_shared();
+ return actor_shared(this);
}
- void hangup_shared() override {
+
+ void hangup_shared() final {
ref_cnt_--;
if (ref_cnt_ == 0) {
ttl_--;
if (ttl_ <= 0) {
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
stop();
} else {
start_up();
@@ -385,18 +387,17 @@ class SendToDead : public Actor {
}
}
- uint32 ttl_{50};
- uint32 ref_cnt_{0};
+ td::uint32 ttl_{50};
+ td::uint32 ref_cnt_{0};
};
TEST(Actors, send_to_dead) {
//TODO: fix CHECK(storage_count_.load() == 0)
return;
- ConcurrentScheduler sched;
int threads_n = 5;
- sched.init(threads_n);
+ td::ConcurrentScheduler sched(threads_n, 0);
- sched.create_actor_unsafe<SendToDead>(0, "manager").release();
+ sched.create_actor_unsafe<SendToDead>(0, "SendToDead").release();
sched.start();
while (sched.run_main(10)) {
// empty
@@ -405,11 +406,8 @@ TEST(Actors, send_to_dead) {
}
TEST(Actors, main_simple) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
-
- ConcurrentScheduler sched;
int threads_n = 3;
- sched.init(threads_n);
+ td::ConcurrentScheduler sched(threads_n, 0);
sched.create_actor_unsafe<SimpleActor>(threads_n > 1 ? 1 : 0, "simple", threads_n).release();
sched.start();
@@ -420,13 +418,10 @@ TEST(Actors, main_simple) {
}
TEST(Actors, main) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
-
- ConcurrentScheduler sched;
int threads_n = 9;
- sched.init(threads_n);
+ td::ConcurrentScheduler sched(threads_n, 0);
- sched.create_actor_unsafe<MainQueryActor>(threads_n > 1 ? 1 : 0, "manager", threads_n).release();
+ sched.create_actor_unsafe<MainQueryActor>(threads_n > 1 ? 1 : 0, "MainQuery", threads_n).release();
sched.start();
while (sched.run_main(10)) {
// empty
@@ -434,27 +429,76 @@ TEST(Actors, main) {
sched.finish();
}
-class DoAfterStop : public Actor {
+class DoAfterStop final : public td::Actor {
public:
- void loop() override {
- ptr = std::make_unique<int>(10);
+ void loop() final {
+ ptr = td::make_unique<int>(10);
stop();
CHECK(*ptr == 10);
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
private:
- std::unique_ptr<int> ptr;
+ td::unique_ptr<int> ptr;
};
TEST(Actors, do_after_stop) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
+ int threads_n = 0;
+ td::ConcurrentScheduler sched(threads_n, 0);
- ConcurrentScheduler sched;
+ sched.create_actor_unsafe<DoAfterStop>(0, "DoAfterStop").release();
+ sched.start();
+ while (sched.run_main(10)) {
+ // empty
+ }
+ sched.finish();
+}
+
+class XContext final : public td::ActorContext {
+ public:
+ td::int32 get_id() const final {
+ return 123456789;
+ }
+
+ void validate() {
+ CHECK(x == 1234);
+ }
+ ~XContext() final {
+ x = 0;
+ }
+ int x = 1234;
+};
+
+class WithXContext final : public td::Actor {
+ public:
+ void start_up() final {
+ auto old_context = set_context(std::make_shared<XContext>());
+ }
+ void f(td::unique_ptr<td::Guard> guard) {
+ }
+ void close() {
+ stop();
+ }
+};
+
+static void check_context() {
+ auto ptr = static_cast<XContext *>(td::Scheduler::context());
+ CHECK(ptr != nullptr);
+ ptr->validate();
+}
+
+TEST(Actors, context_during_destruction) {
int threads_n = 0;
- sched.init(threads_n);
+ td::ConcurrentScheduler sched(threads_n, 0);
- sched.create_actor_unsafe<DoAfterStop>(0, "manager").release();
+ {
+ auto guard = sched.get_main_guard();
+ auto with_context = td::create_actor<WithXContext>("WithXContext").release();
+ send_closure(with_context, &WithXContext::f, td::create_lambda_guard([] { check_context(); }));
+ send_closure_later(with_context, &WithXContext::close);
+ send_closure(with_context, &WithXContext::f, td::create_lambda_guard([] { check_context(); }));
+ send_closure(with_context, &WithXContext::f, td::create_lambda_guard([] { td::Scheduler::instance()->finish(); }));
+ }
sched.start();
while (sched.run_main(10)) {
// empty
diff --git a/protocols/Telegram/tdlib/td/tdactor/test/actors_simple.cpp b/protocols/Telegram/tdlib/td/tdactor/test/actors_simple.cpp
index c0a6c32b61..78d32d5437 100644
--- a/protocols/Telegram/tdlib/td/tdactor/test/actors_simple.cpp
+++ b/protocols/Telegram/tdlib/td/tdactor/test/actors_simple.cpp
@@ -1,57 +1,66 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/tests.h"
-
#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
#include "td/actor/MultiPromise.h"
#include "td/actor/PromiseFuture.h"
#include "td/actor/SleepActor.h"
-#include "td/actor/Timeout.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
+#include "td/utils/MpscPollableQueue.h"
#include "td/utils/Observer.h"
#include "td/utils/port/FileFd.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
+#include "td/utils/tests.h"
+#include "td/utils/Time.h"
+#include <memory>
#include <tuple>
-REGISTER_TESTS(actors_simple)
-
-namespace {
-using namespace td;
-
static const size_t BUF_SIZE = 1024 * 1024;
static char buf[BUF_SIZE];
static char buf2[BUF_SIZE];
-static StringBuilder sb(MutableSlice(buf, BUF_SIZE - 1));
-static StringBuilder sb2(MutableSlice(buf2, BUF_SIZE - 1));
+static td::StringBuilder sb(td::MutableSlice(buf, BUF_SIZE - 1));
+static td::StringBuilder sb2(td::MutableSlice(buf2, BUF_SIZE - 1));
+
+static td::vector<std::shared_ptr<td::MpscPollableQueue<td::EventFull>>> create_queues() {
+#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
+ return {};
+#else
+ auto res = std::make_shared<td::MpscPollableQueue<td::EventFull>>();
+ res->init();
+ return {res};
+#endif
+}
TEST(Actors, SendLater) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
sb.clear();
- Scheduler scheduler;
- scheduler.init();
+ td::Scheduler scheduler;
+ scheduler.init(0, create_queues(), nullptr);
auto guard = scheduler.get_guard();
- class Worker : public Actor {
+ class Worker final : public td::Actor {
public:
void f() {
sb << "A";
}
};
- auto id = create_actor<Worker>("Worker");
- scheduler.run_no_guard(0);
- send_closure(id, &Worker::f);
- send_closure_later(id, &Worker::f);
- send_closure(id, &Worker::f);
+ auto id = td::create_actor<Worker>("Worker");
+ scheduler.run_no_guard(td::Timestamp::in(1));
+ td::send_closure(id, &Worker::f);
+ td::send_closure_later(id, &Worker::f);
+ td::send_closure(id, &Worker::f);
ASSERT_STREQ("A", sb.as_cslice().c_str());
- scheduler.run_no_guard(0);
+ scheduler.run_no_guard(td::Timestamp::in(1));
ASSERT_STREQ("AAA", sb.as_cslice().c_str());
}
@@ -63,21 +72,21 @@ class X {
X(const X &) {
sb << "[cnstr_copy]";
}
- X(X &&) {
+ X(X &&) noexcept {
sb << "[cnstr_move]";
}
X &operator=(const X &) {
sb << "[set_copy]";
return *this;
}
- X &operator=(X &&) {
+ X &operator=(X &&) noexcept {
sb << "[set_move]";
return *this;
}
~X() = default;
};
-class XReceiver final : public Actor {
+class XReceiver final : public td::Actor {
public:
void by_const_ref(const X &) {
sb << "[by_const_ref]";
@@ -91,13 +100,12 @@ class XReceiver final : public Actor {
};
TEST(Actors, simple_pass_event_arguments) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
- Scheduler scheduler;
- scheduler.init();
+ td::Scheduler scheduler;
+ scheduler.init(0, create_queues(), nullptr);
auto guard = scheduler.get_guard();
- auto id = create_actor<XReceiver>("XR").release();
- scheduler.run_no_guard(0);
+ auto id = td::create_actor<XReceiver>("XR").release();
+ scheduler.run_no_guard(td::Timestamp::in(1));
X x;
@@ -112,47 +120,47 @@ TEST(Actors, simple_pass_event_arguments) {
// Tmp-->ConstRef
sb.clear();
- send_closure(id, &XReceiver::by_const_ref, X());
+ td::send_closure(id, &XReceiver::by_const_ref, X());
ASSERT_STREQ("[cnstr_default][by_const_ref]", sb.as_cslice().c_str());
// Tmp-->ConstRef (Delayed)
sb.clear();
- send_closure_later(id, &XReceiver::by_const_ref, X());
- scheduler.run_no_guard(0);
+ td::send_closure_later(id, &XReceiver::by_const_ref, X());
+ scheduler.run_no_guard(td::Timestamp::in(1));
// LOG(ERROR) << sb.as_cslice();
ASSERT_STREQ("[cnstr_default][cnstr_move][by_const_ref]", sb.as_cslice().c_str());
// Tmp-->LvalueRef
sb.clear();
- send_closure(id, &XReceiver::by_lvalue_ref, X());
+ td::send_closure(id, &XReceiver::by_lvalue_ref, X());
ASSERT_STREQ("[cnstr_default][by_lvalue_ref]", sb.as_cslice().c_str());
// Tmp-->LvalueRef (Delayed)
sb.clear();
- send_closure_later(id, &XReceiver::by_lvalue_ref, X());
- scheduler.run_no_guard(0);
+ td::send_closure_later(id, &XReceiver::by_lvalue_ref, X());
+ scheduler.run_no_guard(td::Timestamp::in(1));
ASSERT_STREQ("[cnstr_default][cnstr_move][by_lvalue_ref]", sb.as_cslice().c_str());
// Tmp-->Value
sb.clear();
- send_closure(id, &XReceiver::by_value, X());
+ td::send_closure(id, &XReceiver::by_value, X());
ASSERT_STREQ("[cnstr_default][cnstr_move][by_value]", sb.as_cslice().c_str());
// Tmp-->Value (Delayed)
sb.clear();
- send_closure_later(id, &XReceiver::by_value, X());
- scheduler.run_no_guard(0);
+ td::send_closure_later(id, &XReceiver::by_value, X());
+ scheduler.run_no_guard(td::Timestamp::in(1));
ASSERT_STREQ("[cnstr_default][cnstr_move][cnstr_move][by_value]", sb.as_cslice().c_str());
// Var-->ConstRef
sb.clear();
- send_closure(id, &XReceiver::by_const_ref, x);
+ td::send_closure(id, &XReceiver::by_const_ref, x);
ASSERT_STREQ("[by_const_ref]", sb.as_cslice().c_str());
// Var-->ConstRef (Delayed)
sb.clear();
- send_closure_later(id, &XReceiver::by_const_ref, x);
- scheduler.run_no_guard(0);
+ td::send_closure_later(id, &XReceiver::by_const_ref, x);
+ scheduler.run_no_guard(td::Timestamp::in(1));
ASSERT_STREQ("[cnstr_copy][by_const_ref]", sb.as_cslice().c_str());
// Var-->LvalueRef
@@ -161,24 +169,24 @@ TEST(Actors, simple_pass_event_arguments) {
// Var-->Value
sb.clear();
- send_closure(id, &XReceiver::by_value, x);
+ td::send_closure(id, &XReceiver::by_value, x);
ASSERT_STREQ("[cnstr_copy][by_value]", sb.as_cslice().c_str());
// Var-->Value (Delayed)
sb.clear();
- send_closure_later(id, &XReceiver::by_value, x);
- scheduler.run_no_guard(0);
+ td::send_closure_later(id, &XReceiver::by_value, x);
+ scheduler.run_no_guard(td::Timestamp::in(1));
ASSERT_STREQ("[cnstr_copy][cnstr_move][by_value]", sb.as_cslice().c_str());
}
-class PrintChar final : public Actor {
+class PrintChar final : public td::Actor {
public:
PrintChar(char c, int cnt) : char_(c), cnt_(cnt) {
}
- void start_up() override {
+ void start_up() final {
yield();
}
- void wakeup() override {
+ void wakeup() final {
if (cnt_ == 0) {
stop();
} else {
@@ -192,25 +200,23 @@ class PrintChar final : public Actor {
char char_;
int cnt_;
};
-} // namespace
//
// Yield must add actor to the end of queue
//
TEST(Actors, simple_hand_yield) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
- Scheduler scheduler;
- scheduler.init();
+ td::Scheduler scheduler;
+ scheduler.init(0, create_queues(), nullptr);
sb.clear();
int cnt = 1000;
{
auto guard = scheduler.get_guard();
- create_actor<PrintChar>("PrintA", 'A', cnt).release();
- create_actor<PrintChar>("PrintB", 'B', cnt).release();
- create_actor<PrintChar>("PrintC", 'C', cnt).release();
+ td::create_actor<PrintChar>("PrintA", 'A', cnt).release();
+ td::create_actor<PrintChar>("PrintB", 'B', cnt).release();
+ td::create_actor<PrintChar>("PrintC", 'C', cnt).release();
}
- scheduler.run(0);
- std::string expected;
+ scheduler.run(td::Timestamp::in(1));
+ td::string expected;
for (int i = 0; i < cnt; i++) {
expected += "ABC";
}
@@ -219,7 +225,7 @@ TEST(Actors, simple_hand_yield) {
class Ball {
public:
- friend void start_migrate(Ball &ball, int32 sched_id) {
+ friend void start_migrate(Ball &ball, td::int32 sched_id) {
sb << "start";
}
friend void finish_migrate(Ball &ball) {
@@ -227,31 +233,30 @@ class Ball {
}
};
-class Pong final : public Actor {
+class Pong final : public td::Actor {
public:
void pong(Ball ball) {
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
};
-class Ping final : public Actor {
+class Ping final : public td::Actor {
public:
- explicit Ping(ActorId<Pong> pong) : pong_(pong) {
+ explicit Ping(td::ActorId<Pong> pong) : pong_(pong) {
}
- void start_up() override {
- send_closure(pong_, &Pong::pong, Ball());
+ void start_up() final {
+ td::send_closure(pong_, &Pong::pong, Ball());
}
private:
- ActorId<Pong> pong_;
+ td::ActorId<Pong> pong_;
};
TEST(Actors, simple_migrate) {
sb.clear();
sb2.clear();
- ConcurrentScheduler scheduler;
- scheduler.init(2);
+ td::ConcurrentScheduler scheduler(2, 0);
auto pong = scheduler.create_actor_unsafe<Pong>(2, "Pong").release();
scheduler.create_actor_unsafe<Ping>(1, "Ping", pong).release();
scheduler.start();
@@ -267,26 +272,25 @@ TEST(Actors, simple_migrate) {
#endif
}
-class OpenClose final : public Actor {
+class OpenClose final : public td::Actor {
public:
explicit OpenClose(int cnt) : cnt_(cnt) {
}
- void start_up() override {
+ void start_up() final {
yield();
}
- void wakeup() override {
- ObserverBase *observer = reinterpret_cast<ObserverBase *>(123);
+ void wakeup() final {
+ auto observer = reinterpret_cast<td::ObserverBase *>(123);
if (cnt_ > 0) {
- auto r_file_fd = FileFd::open("server", FileFd::Read | FileFd::Create);
- CHECK(r_file_fd.is_ok()) << r_file_fd.error();
+ auto r_file_fd = td::FileFd::open("server", td::FileFd::Read | td::FileFd::Create);
+ LOG_CHECK(r_file_fd.is_ok()) << r_file_fd.error();
auto file_fd = r_file_fd.move_as_ok();
- // LOG(ERROR) << file_fd.get_native_fd();
- file_fd.get_fd().set_observer(observer);
+ { auto pollable_fd = file_fd.get_poll_info().extract_pollable_fd(observer); }
file_fd.close();
cnt_--;
yield();
} else {
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
}
@@ -295,14 +299,8 @@ class OpenClose final : public Actor {
};
TEST(Actors, open_close) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- ConcurrentScheduler scheduler;
- scheduler.init(2);
- int cnt = 1000000;
-#if TD_WINDOWS || TD_ANDROID
- // TODO(perf) optimize
- cnt = 100;
-#endif
+ td::ConcurrentScheduler scheduler(2, 0);
+ int cnt = 10000; // TODO(perf) optimize
scheduler.create_actor_unsafe<OpenClose>(1, "A", cnt).release();
scheduler.create_actor_unsafe<OpenClose>(2, "B", cnt).release();
scheduler.start();
@@ -311,62 +309,59 @@ TEST(Actors, open_close) {
scheduler.finish();
}
-namespace {
-class MsgActor : public Actor {
+class MsgActor : public td::Actor {
public:
virtual void msg() = 0;
};
-class Slave : public Actor {
+class Slave final : public td::Actor {
public:
- ActorId<MsgActor> msg;
- explicit Slave(ActorId<MsgActor> msg) : msg(msg) {
+ td::ActorId<MsgActor> msg;
+ explicit Slave(td::ActorId<MsgActor> msg) : msg(msg) {
}
- void hangup() override {
- send_closure(msg, &MsgActor::msg);
+ void hangup() final {
+ td::send_closure(msg, &MsgActor::msg);
}
};
-class MasterActor : public MsgActor {
+class MasterActor final : public MsgActor {
public:
- void loop() override {
+ void loop() final {
alive_ = true;
- slave = create_actor<Slave>("slave", static_cast<ActorId<MsgActor>>(actor_id(this)));
+ slave = td::create_actor<Slave>("Slave", static_cast<td::ActorId<MsgActor>>(actor_id(this)));
stop();
}
- ActorOwn<Slave> slave;
+ td::ActorOwn<Slave> slave;
MasterActor() = default;
MasterActor(const MasterActor &) = delete;
MasterActor &operator=(const MasterActor &) = delete;
MasterActor(MasterActor &&) = delete;
MasterActor &operator=(MasterActor &&) = delete;
- ~MasterActor() override {
+ ~MasterActor() final {
alive_ = 987654321;
}
- void msg() override {
+ void msg() final {
CHECK(alive_ == 123456789);
}
- uint64 alive_ = 123456789;
+ td::uint64 alive_ = 123456789;
};
-} // namespace
TEST(Actors, call_after_destruct) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
- Scheduler scheduler;
- scheduler.init();
+ td::Scheduler scheduler;
+ scheduler.init(0, create_queues(), nullptr);
{
auto guard = scheduler.get_guard();
- create_actor<MasterActor>("Master").release();
+ td::create_actor<MasterActor>("Master").release();
}
- scheduler.run(0);
+ scheduler.run(td::Timestamp::in(1));
}
-class LinkTokenSlave : public Actor {
+class LinkTokenSlave final : public td::Actor {
public:
- explicit LinkTokenSlave(ActorShared<> parent) : parent_(std::move(parent)) {
+ explicit LinkTokenSlave(td::ActorShared<> parent) : parent_(std::move(parent)) {
}
- void add(uint64 link_token) {
+ void add(td::uint64 link_token) {
CHECK(link_token == get_link_token());
}
void close() {
@@ -374,62 +369,61 @@ class LinkTokenSlave : public Actor {
}
private:
- ActorShared<> parent_;
+ td::ActorShared<> parent_;
};
-class LinkTokenMasterActor : public Actor {
+class LinkTokenMasterActor final : public td::Actor {
public:
explicit LinkTokenMasterActor(int cnt) : cnt_(cnt) {
}
- void start_up() override {
- child_ = create_actor<LinkTokenSlave>("Slave", actor_shared(this, 123)).release();
+ void start_up() final {
+ child_ = td::create_actor<LinkTokenSlave>("Slave", actor_shared(this, 123)).release();
yield();
}
- void loop() override {
+ void loop() final {
for (int i = 0; i < 100 && cnt_ > 0; cnt_--, i++) {
+ auto token = static_cast<td::uint64>(cnt_) + 1;
switch (i % 4) {
case 0: {
- send_closure(ActorShared<LinkTokenSlave>(child_, cnt_ + 1), &LinkTokenSlave::add, cnt_ + 1);
+ td::send_closure(td::ActorShared<LinkTokenSlave>(child_, token), &LinkTokenSlave::add, token);
break;
}
case 1: {
- send_closure_later(ActorShared<LinkTokenSlave>(child_, cnt_ + 1), &LinkTokenSlave::add, cnt_ + 1);
+ td::send_closure_later(td::ActorShared<LinkTokenSlave>(child_, token), &LinkTokenSlave::add, token);
break;
}
case 2: {
- EventCreator::closure(ActorShared<LinkTokenSlave>(child_, cnt_ + 1), &LinkTokenSlave::add, cnt_ + 1)
+ td::EventCreator::closure(td::ActorShared<LinkTokenSlave>(child_, token), &LinkTokenSlave::add, token)
.try_emit();
break;
}
case 3: {
- EventCreator::closure(ActorShared<LinkTokenSlave>(child_, cnt_ + 1), &LinkTokenSlave::add, cnt_ + 1)
+ td::EventCreator::closure(td::ActorShared<LinkTokenSlave>(child_, token), &LinkTokenSlave::add, token)
.try_emit_later();
break;
}
}
}
if (cnt_ == 0) {
- send_closure(child_, &LinkTokenSlave::close);
+ td::send_closure(child_, &LinkTokenSlave::close);
} else {
yield();
}
}
- void hangup_shared() override {
+ void hangup_shared() final {
CHECK(get_link_token() == 123);
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
stop();
}
private:
int cnt_;
- ActorId<LinkTokenSlave> child_;
+ td::ActorId<LinkTokenSlave> child_;
};
TEST(Actors, link_token) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- ConcurrentScheduler scheduler;
- scheduler.init(0);
+ td::ConcurrentScheduler scheduler(0, 0);
auto cnt = 100000;
scheduler.create_actor_unsafe<LinkTokenMasterActor>(0, "A", cnt).release();
scheduler.start();
@@ -440,25 +434,25 @@ TEST(Actors, link_token) {
TEST(Actors, promise) {
int value = -1;
- Promise<int> p1 = PromiseCreator::lambda([&](int x) { value = x; });
- p1.set_error(Status::Error("Test error"));
+ td::Promise<int> p1 = td::PromiseCreator::lambda([&](int x) { value = x; });
+ p1.set_error(td::Status::Error("Test error"));
ASSERT_EQ(0, value);
- Promise<int32> p2 = PromiseCreator::lambda([&](Result<int32> x) { value = 1; });
- p2.set_error(Status::Error("Test error"));
+ td::Promise<td::int32> p2 = td::PromiseCreator::lambda([&](td::Result<td::int32> x) { value = 1; });
+ p2.set_error(td::Status::Error("Test error"));
ASSERT_EQ(1, value);
}
-class LaterSlave : public Actor {
+class LaterSlave final : public td::Actor {
public:
- explicit LaterSlave(ActorShared<> parent) : parent_(std::move(parent)) {
+ explicit LaterSlave(td::ActorShared<> parent) : parent_(std::move(parent)) {
}
private:
- ActorShared<> parent_;
+ td::ActorShared<> parent_;
- void hangup() override {
+ void hangup() final {
sb << "A";
- send_closure(actor_id(this), &LaterSlave::finish);
+ td::send_closure(actor_id(this), &LaterSlave::finish);
}
void finish() {
sb << "B";
@@ -466,31 +460,29 @@ class LaterSlave : public Actor {
}
};
-class LaterMasterActor : public Actor {
+class LaterMasterActor final : public td::Actor {
int cnt_ = 3;
- std::vector<ActorOwn<LaterSlave>> children_;
- void start_up() override {
+ td::vector<td::ActorOwn<LaterSlave>> children_;
+ void start_up() final {
for (int i = 0; i < cnt_; i++) {
- children_.push_back(create_actor<LaterSlave>("B", actor_shared()));
+ children_.push_back(td::create_actor<LaterSlave>("B", actor_shared(this)));
}
yield();
}
- void loop() override {
+ void loop() final {
children_.clear();
}
- void hangup_shared() override {
+ void hangup_shared() final {
if (!--cnt_) {
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
stop();
}
}
};
TEST(Actors, later) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
sb.clear();
- ConcurrentScheduler scheduler;
- scheduler.init(0);
+ td::ConcurrentScheduler scheduler(0, 0);
scheduler.create_actor_unsafe<LaterMasterActor>(0, "A").release();
scheduler.start();
while (scheduler.run_main(10)) {
@@ -499,39 +491,36 @@ TEST(Actors, later) {
ASSERT_STREQ(sb.as_cslice().c_str(), "AAABBB");
}
-class MultiPromise2 : public Actor {
+class MultiPromise2 final : public td::Actor {
public:
- void start_up() override {
- auto promise = PromiseCreator::lambda([](Result<Unit> result) {
+ void start_up() final {
+ auto promise = td::PromiseCreator::lambda([](td::Result<td::Unit> result) {
result.ensure();
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
});
- MultiPromiseActorSafe multi_promise;
+ td::MultiPromiseActorSafe multi_promise{"MultiPromiseActor2"};
multi_promise.add_promise(std::move(promise));
for (int i = 0; i < 10; i++) {
- create_actor<SleepActor>("Sleep", 0.1, multi_promise.get_promise()).release();
+ td::create_actor<td::SleepActor>("Sleep", 0.1, multi_promise.get_promise()).release();
}
}
};
-class MultiPromise1 : public Actor {
+class MultiPromise1 final : public td::Actor {
public:
- void start_up() override {
- auto promise = PromiseCreator::lambda([](Result<Unit> result) {
+ void start_up() final {
+ auto promise = td::PromiseCreator::lambda([](td::Result<td::Unit> result) {
CHECK(result.is_error());
- create_actor<MultiPromise2>("B").release();
+ td::create_actor<MultiPromise2>("B").release();
});
- MultiPromiseActorSafe multi_promise;
+ td::MultiPromiseActorSafe multi_promise{"MultiPromiseActor1"};
multi_promise.add_promise(std::move(promise));
}
};
TEST(Actors, MultiPromise) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- sb.clear();
- ConcurrentScheduler scheduler;
- scheduler.init(0);
+ td::ConcurrentScheduler scheduler(0, 0);
scheduler.create_actor_unsafe<MultiPromise1>(0, "A").release();
scheduler.start();
while (scheduler.run_main(10)) {
@@ -539,23 +528,20 @@ TEST(Actors, MultiPromise) {
scheduler.finish();
}
-class FastPromise : public Actor {
+class FastPromise final : public td::Actor {
public:
- void start_up() override {
- PromiseFuture<int> pf;
+ void start_up() final {
+ td::PromiseFuture<int> pf;
auto promise = pf.move_promise();
auto future = pf.move_future();
promise.set_value(123);
CHECK(future.move_as_ok() == 123);
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
};
TEST(Actors, FastPromise) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- sb.clear();
- ConcurrentScheduler scheduler;
- scheduler.init(0);
+ td::ConcurrentScheduler scheduler(0, 0);
scheduler.create_actor_unsafe<FastPromise>(0, "A").release();
scheduler.start();
while (scheduler.run_main(10)) {
@@ -563,21 +549,18 @@ TEST(Actors, FastPromise) {
scheduler.finish();
}
-class StopInTeardown : public Actor {
- void loop() override {
+class StopInTeardown final : public td::Actor {
+ void loop() final {
stop();
}
- void tear_down() override {
+ void tear_down() final {
stop();
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
};
TEST(Actors, stop_in_teardown) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- sb.clear();
- ConcurrentScheduler scheduler;
- scheduler.init(0);
+ td::ConcurrentScheduler scheduler(0, 0);
scheduler.create_actor_unsafe<StopInTeardown>(0, "A").release();
scheduler.start();
while (scheduler.run_main(10)) {
@@ -585,38 +568,113 @@ TEST(Actors, stop_in_teardown) {
scheduler.finish();
}
-class AlwaysWaitForMailbox : public Actor {
+class AlwaysWaitForMailbox final : public td::Actor {
public:
- void start_up() override {
+ void start_up() final {
always_wait_for_mailbox();
- create_actor<SleepActor>("Sleep", 0.1, PromiseCreator::lambda([actor_id = actor_id(this), ptr = this](Unit) {
- send_closure(actor_id, &AlwaysWaitForMailbox::g);
- send_closure(actor_id, &AlwaysWaitForMailbox::g);
- CHECK(!ptr->was_f_);
- }))
+ td::create_actor<td::SleepActor>("Sleep", 0.1,
+ td::PromiseCreator::lambda([actor_id = actor_id(this), ptr = this](td::Unit) {
+ td::send_closure(actor_id, &AlwaysWaitForMailbox::g);
+ td::send_closure(actor_id, &AlwaysWaitForMailbox::g);
+ CHECK(!ptr->was_f_);
+ }))
.release();
}
void f() {
was_f_ = true;
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
void g() {
- send_closure(actor_id(this), &AlwaysWaitForMailbox::f);
+ td::send_closure(actor_id(this), &AlwaysWaitForMailbox::f);
}
private:
- Timeout timeout_;
bool was_f_{false};
};
TEST(Actors, always_wait_for_mailbox) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- ConcurrentScheduler scheduler;
- scheduler.init(0);
+ td::ConcurrentScheduler scheduler(0, 0);
scheduler.create_actor_unsafe<AlwaysWaitForMailbox>(0, "A").release();
scheduler.start();
while (scheduler.run_main(10)) {
}
scheduler.finish();
}
+
+#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
+TEST(Actors, send_from_other_threads) {
+ td::ConcurrentScheduler scheduler(1, 0);
+ int thread_n = 10;
+ class Listener final : public td::Actor {
+ public:
+ explicit Listener(int cnt) : cnt_(cnt) {
+ }
+ void dec() {
+ if (--cnt_ == 0) {
+ td::Scheduler::instance()->finish();
+ }
+ }
+
+ private:
+ int cnt_;
+ };
+
+ auto A = scheduler.create_actor_unsafe<Listener>(1, "A", thread_n).release();
+ scheduler.start();
+ td::vector<td::thread> threads(thread_n);
+ for (auto &thread : threads) {
+ thread = td::thread([&A, &scheduler] {
+ auto guard = scheduler.get_send_guard();
+ td::send_closure(A, &Listener::dec);
+ });
+ }
+ while (scheduler.run_main(10)) {
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+ scheduler.finish();
+}
+#endif
+
+class DelayedCall final : public td::Actor {
+ public:
+ void on_called(int *step) {
+ CHECK(*step == 0);
+ *step = 1;
+ }
+};
+
+class MultiPromiseSendClosureLaterTest final : public td::Actor {
+ public:
+ void start_up() final {
+ delayed_call_ = td::create_actor<DelayedCall>("DelayedCall").release();
+ mpa_.add_promise(td::PromiseCreator::lambda([this](td::Unit) {
+ CHECK(step_ == 1);
+ step_++;
+ td::Scheduler::instance()->finish();
+ }));
+ auto lock = mpa_.get_promise();
+ td::send_closure_later(delayed_call_, &DelayedCall::on_called, &step_);
+ lock.set_value(td::Unit());
+ }
+
+ void tear_down() final {
+ CHECK(step_ == 2);
+ }
+
+ private:
+ int step_ = 0;
+ td::MultiPromiseActor mpa_{"MultiPromiseActor"};
+ td::ActorId<DelayedCall> delayed_call_;
+};
+
+TEST(Actors, MultiPromiseSendClosureLater) {
+ td::ConcurrentScheduler scheduler(0, 0);
+ scheduler.create_actor_unsafe<MultiPromiseSendClosureLaterTest>(0, "MultiPromiseSendClosureLaterTest").release();
+ scheduler.start();
+ while (scheduler.run_main(1)) {
+ }
+ scheduler.finish();
+}
diff --git a/protocols/Telegram/tdlib/td/tdactor/test/actors_workers.cpp b/protocols/Telegram/tdlib/td/tdactor/test/actors_workers.cpp
index b97a258a44..bac42e3fd5 100644
--- a/protocols/Telegram/tdlib/td/tdactor/test/actors_workers.cpp
+++ b/protocols/Telegram/tdlib/td/tdactor/test/actors_workers.cpp
@@ -1,22 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/tests.h"
-
#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
-#include "td/utils/logging.h"
-
-REGISTER_TESTS(actors_workers);
-
-namespace {
-
-using namespace td;
+#include "td/utils/common.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tests.h"
-class PowerWorker final : public Actor {
+class PowerWorker final : public td::Actor {
public:
class Callback {
public:
@@ -29,12 +24,12 @@ class PowerWorker final : public Actor {
virtual void on_ready(int query, int res) = 0;
virtual void on_closed() = 0;
};
- void set_callback(unique_ptr<Callback> callback) {
+ void set_callback(td::unique_ptr<Callback> callback) {
callback_ = std::move(callback);
}
- void task(uint32 x, uint32 p) {
- uint32 res = 1;
- for (uint32 i = 0; i < p; i++) {
+ void task(td::uint32 x, td::uint32 p) {
+ td::uint32 res = 1;
+ for (td::uint32 i = 0; i < p; i++) {
res *= x;
}
callback_->on_ready(x, res);
@@ -45,39 +40,41 @@ class PowerWorker final : public Actor {
}
private:
- std::unique_ptr<Callback> callback_;
+ td::unique_ptr<Callback> callback_;
};
-class Manager final : public Actor {
+class Manager final : public td::Actor {
public:
- Manager(int queries_n, int query_size, std::vector<ActorId<PowerWorker>> workers)
- : workers_(std::move(workers)), left_query_(queries_n), query_size_(query_size) {
+ Manager(int queries_n, int query_size, td::vector<td::ActorId<PowerWorker>> workers)
+ : workers_(std::move(workers))
+ , ref_cnt_(static_cast<int>(workers_.size()))
+ , left_query_(queries_n)
+ , query_size_(query_size) {
}
- class Callback : public PowerWorker::Callback {
+ class Callback final : public PowerWorker::Callback {
public:
- Callback(ActorId<Manager> actor_id, int worker_id) : actor_id_(actor_id), worker_id_(worker_id) {
+ Callback(td::ActorId<Manager> actor_id, int worker_id) : actor_id_(actor_id), worker_id_(worker_id) {
}
- void on_ready(int query, int result) override {
- send_closure(actor_id_, &Manager::on_ready, worker_id_, query, result);
+ void on_ready(int query, int result) final {
+ td::send_closure(actor_id_, &Manager::on_ready, worker_id_, query, result);
}
- void on_closed() override {
- send_closure_later(actor_id_, &Manager::on_closed, worker_id_);
+ void on_closed() final {
+ td::send_closure_later(actor_id_, &Manager::on_closed, worker_id_);
}
private:
- ActorId<Manager> actor_id_;
+ td::ActorId<Manager> actor_id_;
int worker_id_;
};
- void start_up() override {
- ref_cnt_ = static_cast<int>(workers_.size());
+ void start_up() final {
int i = 0;
for (auto &worker : workers_) {
ref_cnt_++;
- send_closure_later(worker, &PowerWorker::set_callback, make_unique<Callback>(actor_id(this), i));
+ td::send_closure_later(worker, &PowerWorker::set_callback, td::make_unique<Callback>(actor_id(this), i));
i++;
- send_closure_later(worker, &PowerWorker::task, 3, query_size_);
+ td::send_closure_later(worker, &PowerWorker::task, 3, query_size_);
left_query_--;
}
}
@@ -85,10 +82,10 @@ class Manager final : public Actor {
void on_ready(int worker_id, int query, int res) {
ref_cnt_--;
if (left_query_ == 0) {
- send_closure(workers_[worker_id], &PowerWorker::close);
+ td::send_closure(workers_[worker_id], &PowerWorker::close);
} else {
ref_cnt_++;
- send_closure(workers_[worker_id], &PowerWorker::task, 3, query_size_);
+ td::send_closure(workers_[worker_id], &PowerWorker::task, 3, query_size_);
left_query_--;
}
}
@@ -96,30 +93,27 @@ class Manager final : public Actor {
void on_closed(int worker_id) {
ref_cnt_--;
if (ref_cnt_ == 0) {
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
stop();
}
}
private:
- std::vector<ActorId<PowerWorker>> workers_;
- int left_query_;
+ td::vector<td::ActorId<PowerWorker>> workers_;
int ref_cnt_;
+ int left_query_;
int query_size_;
};
-void test_workers(int threads_n, int workers_n, int queries_n, int query_size) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG));
-
- ConcurrentScheduler sched;
- sched.init(threads_n);
+static void test_workers(int threads_n, int workers_n, int queries_n, int query_size) {
+ td::ConcurrentScheduler sched(threads_n, 0);
- std::vector<ActorId<PowerWorker>> workers;
+ td::vector<td::ActorId<PowerWorker>> workers;
for (int i = 0; i < workers_n; i++) {
int thread_id = threads_n ? i % (threads_n - 1) + 2 : 0;
- workers.push_back(sched.create_actor_unsafe<PowerWorker>(thread_id, "worker" + to_string(i)).release());
+ workers.push_back(sched.create_actor_unsafe<PowerWorker>(thread_id, PSLICE() << "worker" << i).release());
}
- sched.create_actor_unsafe<Manager>(threads_n ? 1 : 0, "manager", queries_n, query_size, std::move(workers)).release();
+ sched.create_actor_unsafe<Manager>(threads_n ? 1 : 0, "Manager", queries_n, query_size, std::move(workers)).release();
sched.start();
while (sched.run_main(10)) {
@@ -129,7 +123,6 @@ void test_workers(int threads_n, int workers_n, int queries_n, int query_size) {
// sched.test_one_thread_run();
}
-} // namespace
TEST(Actors, workers_big_query_one_thread) {
test_workers(0, 10, 1000, 300000);
@@ -144,13 +137,13 @@ TEST(Actors, workers_big_query_nine_threads) {
}
TEST(Actors, workers_small_query_one_thread) {
- test_workers(0, 10, 1000000, 1);
+ test_workers(0, 10, 100000, 1);
}
TEST(Actors, workers_small_query_two_threads) {
- test_workers(2, 10, 1000000, 1);
+ test_workers(2, 10, 100000, 1);
}
TEST(Actors, workers_small_query_nine_threads) {
- test_workers(9, 10, 1000000, 1);
+ test_workers(9, 10, 10000, 1);
}
diff --git a/protocols/Telegram/tdlib/td/tdclientjson_export_list b/protocols/Telegram/tdlib/td/tdclientjson_export_list
index f1db3603c4..51f370e6f5 100644
--- a/protocols/Telegram/tdlib/td/tdclientjson_export_list
+++ b/protocols/Telegram/tdlib/td/tdclientjson_export_list
@@ -7,3 +7,8 @@ _td_set_log_file_path
_td_set_log_max_file_size
_td_set_log_verbosity_level
_td_set_log_fatal_error_callback
+_td_create_client_id
+_td_send
+_td_receive
+_td_execute
+_td_set_log_message_callback
diff --git a/protocols/Telegram/tdlib/td/tddb/CMakeLists.txt b/protocols/Telegram/tdlib/td/tddb/CMakeLists.txt
index 531dcc5c02..036f0ca61c 100644
--- a/protocols/Telegram/tdlib/td/tddb/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/tddb/CMakeLists.txt
@@ -1,4 +1,10 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
+
+if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
+ set(CMAKE_INSTALL_LIBDIR "lib")
+endif()
#SOURCE SETS
set(TDDB_SOURCE
@@ -8,16 +14,19 @@ set(TDDB_SOURCE
td/db/binlog/detail/BinlogEventsBuffer.cpp
td/db/binlog/detail/BinlogEventsProcessor.cpp
+ td/db/SqliteConnectionSafe.cpp
td/db/SqliteDb.cpp
- td/db/SqliteStatement.cpp
+ td/db/SqliteKeyValue.cpp
td/db/SqliteKeyValueAsync.cpp
+ td/db/SqliteStatement.cpp
+ td/db/TQueue.cpp
td/db/detail/RawSqliteDb.cpp
td/db/binlog/Binlog.h
- td/db/binlog/BinlogInterface.h
td/db/binlog/BinlogEvent.h
td/db/binlog/BinlogHelper.h
+ td/db/binlog/BinlogInterface.h
td/db/binlog/ConcurrentBinlog.h
td/db/binlog/detail/BinlogEventsBuffer.h
td/db/binlog/detail/BinlogEventsProcessor.h
@@ -25,7 +34,6 @@ set(TDDB_SOURCE
td/db/BinlogKeyValue.h
td/db/DbKey.h
td/db/KeyValueSyncInterface.h
- td/db/Pmc.h
td/db/SeqKeyValue.h
td/db/SqliteConnectionSafe.h
td/db/SqliteDb.h
@@ -33,6 +41,7 @@ set(TDDB_SOURCE
td/db/SqliteKeyValueAsync.h
td/db/SqliteKeyValueSafe.h
td/db/SqliteStatement.h
+ td/db/TQueue.h
td/db/TsSeqKeyValue.h
td/db/detail/RawSqliteDb.h
@@ -48,8 +57,6 @@ if (NOT CMAKE_CROSSCOMPILING)
endif()
install(TARGETS tddb EXPORT TdTargets
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- RUNTIME DESTINATION bin
- INCLUDES DESTINATION include
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/BinlogKeyValue.h b/protocols/Telegram/tdlib/td/tddb/td/db/BinlogKeyValue.h
index 04df413d53..9ed4d92240 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/BinlogKeyValue.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/BinlogKeyValue.h
@@ -1,40 +1,42 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/PromiseFuture.h"
-
#include "td/db/binlog/Binlog.h"
#include "td/db/binlog/BinlogEvent.h"
-#include "td/db/binlog/ConcurrentBinlog.h"
+#include "td/db/DbKey.h"
#include "td/db/KeyValueSyncInterface.h"
+#include "td/utils/algorithm.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/RwMutex.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/StorerBase.h"
#include "td/utils/tl_parsers.h"
#include "td/utils/tl_storers.h"
-#include <map>
#include <memory>
#include <unordered_map>
#include <utility>
namespace td {
+
template <class BinlogT>
-class BinlogKeyValue : public KeyValueSyncInterface {
+class BinlogKeyValue final : public KeyValueSyncInterface {
public:
- static constexpr int32 magic = 0x2a280000;
+ static constexpr int32 MAGIC = 0x2a280000;
- struct Event : public Storer {
+ struct Event final : public Storer {
Event() = default;
Event(Slice key, Slice value) : key(key), value(value) {
}
@@ -52,16 +54,15 @@ class BinlogKeyValue : public KeyValueSyncInterface {
value = parser.template fetch_string<Slice>();
}
- size_t size() const override {
+ size_t size() const final {
TlStorerCalcLength storer;
store(storer);
return storer.get_length();
}
- size_t store(uint8 *ptr_x) const override {
- auto ptr = reinterpret_cast<char *>(ptr_x);
+ size_t store(uint8 *ptr) const final {
TlStorerUnsafe storer(ptr);
store(storer);
- return storer.get_buf() - ptr;
+ return static_cast<size_t>(storer.get_buf() - ptr);
}
};
@@ -76,15 +77,15 @@ class BinlogKeyValue : public KeyValueSyncInterface {
magic_ = override_magic;
}
- binlog_ = std::make_unique<BinlogT>();
- TRY_STATUS(binlog_->init(name,
- [&](const BinlogEvent &binlog_event) {
- Event event;
- event.parse(TlParser(binlog_event.data_));
- map_.emplace(std::make_pair(event.key.str(),
- std::make_pair(event.value.str(), binlog_event.id_)));
- },
- std::move(db_key), DbKey::empty(), scheduler_id));
+ binlog_ = std::make_shared<BinlogT>();
+ TRY_STATUS(binlog_->init(
+ name,
+ [&](const BinlogEvent &binlog_event) {
+ Event event;
+ event.parse(TlParser(binlog_event.data_));
+ map_.emplace(event.key.str(), std::make_pair(event.value.str(), binlog_event.id_));
+ },
+ std::move(db_key), DbKey::empty(), scheduler_id));
return Status::OK();
}
@@ -103,7 +104,7 @@ class BinlogKeyValue : public KeyValueSyncInterface {
void external_init_handle(const BinlogEvent &binlog_event) {
Event event;
event.parse(TlParser(binlog_event.data_));
- map_.emplace(std::make_pair(event.key.str(), std::make_pair(event.value.str(), binlog_event.id_)));
+ map_.emplace(event.key.str(), std::make_pair(event.value.str(), binlog_event.id_));
}
void external_init_finish(std::shared_ptr<BinlogT> binlog) {
@@ -113,17 +114,24 @@ class BinlogKeyValue : public KeyValueSyncInterface {
void close() {
*this = BinlogKeyValue();
}
+ void close(Promise<> promise) final {
+ binlog_->close(std::move(promise));
+ }
- SeqNo set(string key, string value) override {
+ SeqNo set(string key, string value) final {
auto lock = rw_mutex_.lock_write().move_as_ok();
uint64 old_id = 0;
- auto it_ok = map_.insert({key, {value, 0}});
+ auto it_ok = map_.emplace(key, std::make_pair(value, 0));
if (!it_ok.second) {
if (it_ok.first->second.first == value) {
return 0;
}
+ VLOG(binlog) << "Change value of key " << key << " from " << hex_encode(it_ok.first->second.first) << " to "
+ << hex_encode(value);
old_id = it_ok.first->second.second;
it_ok.first->second.first = value;
+ } else {
+ VLOG(binlog) << "Set value of key " << key << " to " << hex_encode(value);
}
bool rewrite = false;
uint64 id;
@@ -142,15 +150,15 @@ class BinlogKeyValue : public KeyValueSyncInterface {
return seq_no;
}
- SeqNo erase(const string &key) override {
+ SeqNo erase(const string &key) final {
auto lock = rw_mutex_.lock_write().move_as_ok();
auto it = map_.find(key);
if (it == map_.end()) {
return 0;
}
+ VLOG(binlog) << "Remove value of key " << key << ", which is " << hex_encode(it->second.first);
uint64 id = it->second.second;
map_.erase(it);
- // LOG(ERROR) << "ADD EVENT";
auto seq_no = binlog_->next_id();
lock.reset();
add_event(seq_no, BinlogEvent::create_raw(id, BinlogEvent::ServiceTypes::Empty, BinlogEvent::Flags::Rewrite,
@@ -159,62 +167,62 @@ class BinlogKeyValue : public KeyValueSyncInterface {
}
void add_event(uint64 seq_no, BufferSlice &&event) {
- binlog_->add_raw_event(seq_no, std::move(event));
+ binlog_->add_raw_event(BinlogDebugInfo{__FILE__, __LINE__}, seq_no, std::move(event));
}
- bool isset(const string &key) override {
+ bool isset(const string &key) final {
auto lock = rw_mutex_.lock_read().move_as_ok();
return map_.count(key) > 0;
}
- string get(const string &key) override {
+ string get(const string &key) final {
auto lock = rw_mutex_.lock_read().move_as_ok();
auto it = map_.find(key);
if (it == map_.end()) {
return string();
}
+ VLOG(binlog) << "Get value of key " << key << ", which is " << hex_encode(it->second.first);
return it->second.first;
}
- void force_sync(Promise<> &&promise) {
+ void force_sync(Promise<> &&promise) final {
binlog_->force_sync(std::move(promise));
}
+
void lazy_sync(Promise<> &&promise) {
binlog_->lazy_sync(std::move(promise));
}
- std::unordered_map<string, string> prefix_get(Slice prefix) {
- // TODO: optimize with std::map?
+ std::unordered_map<string, string, Hash<string>> prefix_get(Slice prefix) final {
auto lock = rw_mutex_.lock_write().move_as_ok();
- std::unordered_map<string, string> res;
+ std::unordered_map<string, string, Hash<string>> res;
for (const auto &kv : map_) {
if (begins_with(kv.first, prefix)) {
- res[kv.first] = kv.second.first;
+ res.emplace(kv.first.substr(prefix.size()), kv.second.first);
}
}
return res;
}
- std::unordered_map<string, string> get_all() {
+ std::unordered_map<string, string, Hash<string>> get_all() final {
auto lock = rw_mutex_.lock_write().move_as_ok();
- std::unordered_map<string, string> res;
+ std::unordered_map<string, string, Hash<string>> res;
for (const auto &kv : map_) {
- res[kv.first] = kv.second.first;
+ res.emplace(kv.first, kv.second.first);
}
return res;
}
- void erase_by_prefix(Slice prefix) {
+ void erase_by_prefix(Slice prefix) final {
auto lock = rw_mutex_.lock_write().move_as_ok();
- std::vector<uint64> ids;
- for (auto it = map_.begin(); it != map_.end();) {
- if (begins_with(it->first, prefix)) {
- ids.push_back(it->second.second);
- it = map_.erase(it);
- } else {
- ++it;
+ vector<uint64> ids;
+ table_remove_if(map_, [&](const auto &it) {
+ if (begins_with(it.first, prefix)) {
+ ids.push_back(it.second.second);
+ return true;
}
- }
+ return false;
+ });
auto seq_no = binlog_->next_id(narrow_cast<int32>(ids.size()));
lock.reset();
for (auto id : ids) {
@@ -231,22 +239,26 @@ class BinlogKeyValue : public KeyValueSyncInterface {
}
private:
- std::unordered_map<string, std::pair<string, uint64>> map_;
+ std::unordered_map<string, std::pair<string, uint64>, Hash<string>> map_;
std::shared_ptr<BinlogT> binlog_;
RwMutex rw_mutex_;
- int32 magic_ = magic;
+ int32 magic_ = MAGIC;
};
+
template <>
inline void BinlogKeyValue<Binlog>::add_event(uint64 seq_no, BufferSlice &&event) {
- binlog_->add_raw_event(std::move(event));
+ binlog_->add_raw_event(std::move(event), BinlogDebugInfo{__FILE__, __LINE__});
}
+
template <>
inline void BinlogKeyValue<Binlog>::force_sync(Promise<> &&promise) {
binlog_->sync();
promise.set_value(Unit());
}
+
template <>
inline void BinlogKeyValue<Binlog>::lazy_sync(Promise<> &&promise) {
force_sync(std::move(promise));
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/DbKey.h b/protocols/Telegram/tdlib/td/tddb/td/db/DbKey.h
index b0edde2ae1..084f6283de 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/DbKey.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/DbKey.h
@@ -1,51 +1,61 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/utils/common.h"
#include "td/utils/Slice.h"
namespace td {
+
class DbKey {
- public:
- enum Type { Empty, RawKey, Password };
+ enum class Type { Empty, RawKey, Password };
Type type() const {
return type_;
}
+
+ public:
bool is_empty() const {
- return type_ == Empty;
+ return type_ == Type::Empty;
}
+
bool is_raw_key() const {
- return type_ == RawKey;
+ return type_ == Type::RawKey;
}
+
bool is_password() const {
- return type_ == Password;
+ return type_ == Type::Password;
}
+
CSlice data() const {
return data_;
}
+
static DbKey raw_key(string raw_key) {
DbKey res;
- res.type_ = RawKey;
+ res.type_ = Type::RawKey;
res.data_ = std::move(raw_key);
return res;
}
+
static DbKey password(string password) {
DbKey res;
- res.type_ = Password;
+ res.type_ = Type::Password;
res.data_ = std::move(password);
return res;
}
+
static DbKey empty() {
return DbKey();
}
private:
- Type type_{Empty};
+ Type type_{Type::Empty};
string data_;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/KeyValueSyncInterface.h b/protocols/Telegram/tdlib/td/tddb/td/db/KeyValueSyncInterface.h
index 8d19d0e75c..4007d0bfd6 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/KeyValueSyncInterface.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/KeyValueSyncInterface.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,6 +7,11 @@
#pragma once
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+
+#include <unordered_map>
namespace td {
@@ -29,7 +34,17 @@ class KeyValueSyncInterface {
virtual string get(const string &key) = 0;
+ virtual std::unordered_map<string, string, Hash<string>> prefix_get(Slice prefix) = 0;
+
+ virtual std::unordered_map<string, string, Hash<string>> get_all() = 0;
+
virtual SeqNo erase(const string &key) = 0;
+
+ virtual void erase_by_prefix(Slice prefix) = 0;
+
+ virtual void force_sync(Promise<> &&promise) = 0;
+
+ virtual void close(Promise<> promise) = 0;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/Pmc.h b/protocols/Telegram/tdlib/td/tddb/td/db/Pmc.h
deleted file mode 100644
index dcf0e0c351..0000000000
--- a/protocols/Telegram/tdlib/td/tddb/td/db/Pmc.h
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/db/binlog/ConcurrentBinlog.h"
-#include "td/db/BinlogKeyValue.h"
-#include "td/db/SqliteKeyValue.h"
-
-#include "td/utils/common.h"
-
-#include <memory>
-
-namespace td {
-
-using BinlogPmcBase = BinlogKeyValue<ConcurrentBinlog>;
-using BinlogPmc = std::shared_ptr<BinlogPmcBase>;
-using BinlogPmcPtr = BinlogPmcBase *;
-
-using BigPmcBase = SqliteKeyValue;
-using BigPmc = std::unique_ptr<BigPmcBase>;
-using BigPmcPtr = BigPmcBase *;
-
-}; // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SeqKeyValue.h b/protocols/Telegram/tdlib/td/tddb/td/db/SeqKeyValue.h
index ec6f2b99b6..0ec10b682a 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SeqKeyValue.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SeqKeyValue.h
@@ -1,16 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/utils/HashTableUtils.h"
#include "td/utils/Slice.h"
#include <unordered_map>
namespace td {
+
class SeqKeyValue {
public:
using SeqNo = uint64;
@@ -22,7 +24,7 @@ class SeqKeyValue {
~SeqKeyValue() = default;
SeqNo set(Slice key, Slice value) {
- auto it_ok = map_.insert({key.str(), value.str()});
+ auto it_ok = map_.emplace(key.str(), value.str());
if (!it_ok.second) {
if (it_ok.first->second == value) {
return 0;
@@ -31,6 +33,7 @@ class SeqKeyValue {
}
return next_seq_no();
}
+
SeqNo erase(const string &key) {
auto it = map_.find(key);
if (it == map_.end()) {
@@ -39,9 +42,11 @@ class SeqKeyValue {
map_.erase(it);
return next_seq_no();
}
+
SeqNo seq_no() const {
return current_id_ + 1;
}
+
string get(const string &key) const {
auto it = map_.find(key);
if (it == map_.end()) {
@@ -50,29 +55,28 @@ class SeqKeyValue {
return it->second;
}
- template <class F>
- void foreach (const F &f) {
- for (auto &it : map_) {
- f(it.first, it.second);
+ bool isset(const string &key) const {
+ auto it = map_.find(key);
+ if (it == map_.end()) {
+ return false;
}
+ return true;
}
size_t size() const {
return map_.size();
}
- void reset_seq_no() {
- current_id_ = 0;
- }
- std::unordered_map<string, string> get_all() const {
+ std::unordered_map<string, string, Hash<string>> get_all() const {
return map_;
}
private:
- std::unordered_map<string, string> map_;
+ std::unordered_map<string, string, Hash<string>> map_;
SeqNo current_id_ = 0;
SeqNo next_seq_no() {
return ++current_id_;
}
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteConnectionSafe.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteConnectionSafe.cpp
new file mode 100644
index 0000000000..a3c13d7120
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteConnectionSafe.cpp
@@ -0,0 +1,51 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/db/SqliteConnectionSafe.h"
+
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+
+namespace td {
+
+SqliteConnectionSafe::SqliteConnectionSafe(string path, DbKey key, optional<int32> cipher_version)
+ : path_(std::move(path))
+ , lsls_connection_([path = path_, close_state_ptr = &close_state_, key = std::move(key),
+ cipher_version = std::move(cipher_version)] {
+ auto r_db = SqliteDb::open_with_key(path, false, key, cipher_version.copy());
+ if (r_db.is_error()) {
+ LOG(FATAL) << "Can't open database in state " << close_state_ptr->load() << ": " << r_db.error().message();
+ }
+ auto db = r_db.move_as_ok();
+ db.exec("PRAGMA journal_mode=WAL").ensure();
+ db.exec("PRAGMA secure_delete=1").ensure();
+ return db;
+ }) {
+}
+
+void SqliteConnectionSafe::set(SqliteDb &&db) {
+ lsls_connection_.set(std::move(db));
+}
+
+SqliteDb &SqliteConnectionSafe::get() {
+ return lsls_connection_.get();
+}
+
+void SqliteConnectionSafe::close() {
+ LOG(INFO) << "Close SQLite database " << tag("path", path_);
+ close_state_++;
+ lsls_connection_.clear_values();
+}
+
+void SqliteConnectionSafe::close_and_destroy() {
+ close();
+ LOG(INFO) << "Destroy SQLite database " << tag("path", path_);
+ close_state_ += 65536;
+ SqliteDb::destroy(path_).ignore();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteConnectionSafe.h b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteConnectionSafe.h
index 6e45e79e63..a8bae2b3c2 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteConnectionSafe.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteConnectionSafe.h
@@ -1,53 +1,39 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/SchedulerLocalStorage.h"
-
+#include "td/db/DbKey.h"
#include "td/db/SqliteDb.h"
+#include "td/actor/SchedulerLocalStorage.h"
+
#include "td/utils/common.h"
-#include "td/utils/format.h"
-#include "td/utils/logging.h"
+#include "td/utils/optional.h"
+
+#include <atomic>
namespace td {
class SqliteConnectionSafe {
public:
SqliteConnectionSafe() = default;
- explicit SqliteConnectionSafe(string name, DbKey key = DbKey::empty())
- : lsls_connection_([name = name, key = std::move(key)] {
- auto db = SqliteDb::open_with_key(name, key).move_as_ok();
- db.exec("PRAGMA synchronous=NORMAL").ensure();
- db.exec("PRAGMA temp_store=MEMORY").ensure();
- db.exec("PRAGMA secure_delete=1").ensure();
- db.exec("PRAGMA recursive_triggers=1").ensure();
- return db;
- })
- , name_(std::move(name)) {
- }
-
- SqliteDb &get() {
- return lsls_connection_.get();
- }
-
- void close() {
- LOG(INFO) << "Close sqlite db " << tag("path", name_);
- lsls_connection_.clear_values();
- }
- void close_and_destroy() {
- close();
- LOG(INFO) << "Destroy sqlite db " << tag("path", name_);
- SqliteDb::destroy(name_).ignore();
- }
+ SqliteConnectionSafe(string path, DbKey key, optional<int32> cipher_version = {});
+
+ SqliteDb &get();
+ void set(SqliteDb &&db);
+
+ void close();
+
+ void close_and_destroy();
private:
+ string path_;
+ std::atomic<uint32> close_state_{0};
LazySchedulerLocalStorage<SqliteDb> lsls_connection_;
- string name_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.cpp
index 819818197d..2ae37b7f90 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.cpp
@@ -1,14 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/db/SqliteDb.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
+#include "td/utils/logging.h"
#include "td/utils/port/path.h"
#include "td/utils/port/Stat.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Timer.h"
@@ -18,12 +21,34 @@
namespace td {
namespace {
+string quote_string(Slice str) {
+ size_t cnt = 0;
+ for (auto &c : str) {
+ if (c == '\'') {
+ cnt++;
+ }
+ }
+ if (cnt == 0) {
+ return str.str();
+ }
+
+ string result;
+ result.reserve(str.size() + cnt);
+ for (auto &c : str) {
+ if (c == '\'') {
+ result += '\'';
+ }
+ result += c;
+ }
+ return result;
+}
+
string db_key_to_sqlcipher_key(const DbKey &db_key) {
if (db_key.is_empty()) {
return "''";
}
if (db_key.is_password()) {
- return PSTRING() << "'" << db_key.data().str() << "'";
+ return PSTRING() << "'" << quote_string(db_key.data()) << "'";
}
CHECK(db_key.is_raw_key());
Slice raw_key = db_key.data();
@@ -46,27 +71,29 @@ string db_key_to_sqlcipher_key(const DbKey &db_key) {
SqliteDb::~SqliteDb() = default;
-Status SqliteDb::init(CSlice path, bool *was_created) {
- // If database does not exist, delete all other files which may left
- // from older database
- bool is_db_exists = stat(path).is_ok();
- if (!is_db_exists) {
- destroy(path).ignore();
+Status SqliteDb::init(CSlice path, bool allow_creation) {
+ // if database does not exist, delete all other files which could have been left from the old database
+ auto database_stat = stat(path);
+ if (database_stat.is_error()) {
+ if (!allow_creation) {
+ bool was_destroyed = detail::RawSqliteDb::was_any_database_destroyed();
+ auto reason = was_destroyed ? Slice("was corrupted and deleted") : Slice("disappeared");
+ return Status::Error(PSLICE() << "Database " << reason
+ << " during execution and can't be recreated: " << database_stat.error());
+ }
+ TRY_STATUS(destroy(path));
}
- if (was_created != nullptr) {
- *was_created = !is_db_exists;
- }
- sqlite3 *db;
- CHECK(sqlite3_threadsafe() != 0);
- int rc = sqlite3_open_v2(path.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE /*| SQLITE_OPEN_SHAREDCACHE*/,
- nullptr);
+ tdsqlite3 *db;
+ CHECK(tdsqlite3_threadsafe() != 0);
+ int rc =
+ tdsqlite3_open_v2(path.c_str(), &db, SQLITE_OPEN_READWRITE | (allow_creation ? SQLITE_OPEN_CREATE : 0), nullptr);
if (rc != SQLITE_OK) {
- auto res = Status::Error(PSLICE() << "Failed to open db: " << detail::RawSqliteDb::last_error(db));
- sqlite3_close(db);
+ auto res = detail::RawSqliteDb::last_error(db, path);
+ tdsqlite3_close(db);
return res;
}
- sqlite3_busy_timeout(db, 1000 * 5 /* 5 seconds */);
+ tdsqlite3_busy_timeout(db, 1000 * 5 /* 5 seconds */);
raw_ = std::make_shared<detail::RawSqliteDb>(db, path.str());
return Status::OK();
}
@@ -74,32 +101,41 @@ Status SqliteDb::init(CSlice path, bool *was_created) {
static void trace_callback(void *ptr, const char *query) {
LOG(ERROR) << query;
}
+
static int trace_v2_callback(unsigned code, void *ctx, void *p_raw, void *x_raw) {
CHECK(code == SQLITE_TRACE_STMT);
auto x = static_cast<const char *>(x_raw);
if (x[0] == '-' && x[1] == '-') {
trace_callback(ctx, x);
} else {
- trace_callback(ctx, sqlite3_expanded_sql(static_cast<sqlite3_stmt *>(p_raw)));
+ trace_callback(ctx, tdsqlite3_expanded_sql(static_cast<tdsqlite3_stmt *>(p_raw)));
}
return 0;
}
+
void SqliteDb::trace(bool flag) {
- sqlite3_trace_v2(raw_->db(), SQLITE_TRACE_STMT, flag ? trace_v2_callback : nullptr, nullptr);
+ tdsqlite3_trace_v2(raw_->db(), SQLITE_TRACE_STMT, flag ? trace_v2_callback : nullptr, nullptr);
}
Status SqliteDb::exec(CSlice cmd) {
CHECK(!empty());
char *msg;
- VLOG(sqlite) << "Start exec " << tag("cmd", cmd) << tag("db", raw_->db());
- auto rc = sqlite3_exec(raw_->db(), cmd.c_str(), nullptr, nullptr, &msg);
- VLOG(sqlite) << "Finish exec " << tag("cmd", cmd) << tag("db", raw_->db());
+ if (enable_logging_) {
+ VLOG(sqlite) << "Start exec " << tag("query", cmd) << tag("database", raw_->db());
+ }
+ auto rc = tdsqlite3_exec(raw_->db(), cmd.c_str(), nullptr, nullptr, &msg);
if (rc != SQLITE_OK) {
CHECK(msg != nullptr);
- return Status::Error(PSLICE() << tag("query", cmd) << " failed: " << msg);
+ if (enable_logging_) {
+ VLOG(sqlite) << "Finish exec with error " << msg;
+ }
+ return Status::Error(PSLICE() << tag("query", cmd) << " to database \"" << raw_->path() << "\" failed: " << msg);
}
CHECK(msg == nullptr);
+ if (enable_logging_) {
+ VLOG(sqlite) << "Finish exec";
+ }
return Status::OK();
}
@@ -111,6 +147,7 @@ Result<bool> SqliteDb::has_table(Slice table) {
auto cnt = stmt.view_int32(0);
return cnt == 1;
}
+
Result<string> SqliteDb::get_pragma(Slice name) {
TRY_RESULT(stmt, get_statement(PSLICE() << "PRAGMA " << name));
TRY_STATUS(stmt.step());
@@ -121,11 +158,21 @@ Result<string> SqliteDb::get_pragma(Slice name) {
return std::move(res);
}
+Result<string> SqliteDb::get_pragma_string(Slice name) {
+ TRY_RESULT(stmt, get_statement(PSLICE() << "PRAGMA " << name));
+ TRY_STATUS(stmt.step());
+ CHECK(stmt.has_row());
+ auto res = stmt.view_string(0).str();
+ TRY_STATUS(stmt.step());
+ CHECK(!stmt.can_step());
+ return std::move(res);
+}
+
Result<int32> SqliteDb::user_version() {
TRY_RESULT(get_version_stmt, get_statement("PRAGMA user_version"));
TRY_STATUS(get_version_stmt.step());
if (!get_version_stmt.has_row()) {
- return Status::Error("PRAGMA user_version failed");
+ return Status::Error(PSLICE() << "PRAGMA user_version failed for database \"" << raw_->path() << '"');
}
return get_version_stmt.view_int32(0);
}
@@ -134,56 +181,103 @@ Status SqliteDb::set_user_version(int32 version) {
return exec(PSLICE() << "PRAGMA user_version = " << version);
}
-Status SqliteDb::begin_transaction() {
- return exec("BEGIN");
+Status SqliteDb::begin_read_transaction() {
+ if (raw_->on_begin()) {
+ return exec("BEGIN");
+ }
+ return Status::OK();
+}
+
+Status SqliteDb::begin_write_transaction() {
+ if (raw_->on_begin()) {
+ return exec("BEGIN IMMEDIATE");
+ }
+ return Status::OK();
}
+
Status SqliteDb::commit_transaction() {
- return exec("COMMIT");
+ TRY_RESULT(need_commit, raw_->on_commit());
+ if (need_commit) {
+ return exec("COMMIT");
+ }
+ return Status::OK();
}
-bool SqliteDb::is_encrypted() {
- return exec("SELECT count(*) FROM sqlite_master").is_error();
+Status SqliteDb::check_encryption() {
+ auto status = exec("SELECT count(*) FROM sqlite_master");
+ if (status.is_ok()) {
+ enable_logging_ = true;
+ }
+ return status;
}
-Result<SqliteDb> SqliteDb::open_with_key(CSlice path, const DbKey &db_key) {
+Result<SqliteDb> SqliteDb::open_with_key(CSlice path, bool allow_creation, const DbKey &db_key,
+ optional<int32> cipher_version) {
+ auto res = do_open_with_key(path, allow_creation, db_key, cipher_version ? cipher_version.value() : 0);
+ if (res.is_error() && !cipher_version && !db_key.is_empty()) {
+ return do_open_with_key(path, false, db_key, 3);
+ }
+ return res;
+}
+
+Result<SqliteDb> SqliteDb::do_open_with_key(CSlice path, bool allow_creation, const DbKey &db_key,
+ int32 cipher_version) {
SqliteDb db;
- TRY_STATUS(db.init(path));
+ TRY_STATUS(db.init(path, allow_creation));
if (!db_key.is_empty()) {
- if (!db.is_encrypted()) {
- return Status::Error("No key is needed");
+ if (db.check_encryption().is_ok()) {
+ return Status::Error(PSLICE() << "No key is needed for database \"" << path << '"');
}
auto key = db_key_to_sqlcipher_key(db_key);
TRY_STATUS(db.exec(PSLICE() << "PRAGMA key = " << key));
+ if (cipher_version != 0) {
+ LOG(INFO) << "Trying SQLCipher compatibility mode with version = " << cipher_version;
+ TRY_STATUS(db.exec(PSLICE() << "PRAGMA cipher_compatibility = " << cipher_version));
+ }
+ db.set_cipher_version(cipher_version);
}
- if (db.is_encrypted()) {
- return Status::Error("Wrong key");
- }
+ TRY_STATUS_PREFIX(db.check_encryption(), "Can't check database: ");
return std::move(db);
}
-Status SqliteDb::change_key(CSlice path, const DbKey &new_db_key, const DbKey &old_db_key) {
+void SqliteDb::set_cipher_version(int32 cipher_version) {
+ raw_->set_cipher_version(cipher_version);
+}
+
+optional<int32> SqliteDb::get_cipher_version() const {
+ return raw_->get_cipher_version();
+}
+
+Result<SqliteDb> SqliteDb::change_key(CSlice path, bool allow_creation, const DbKey &new_db_key,
+ const DbKey &old_db_key) {
// fast path
{
- auto r_db = open_with_key(path, new_db_key);
+ PerfWarningTimer perf("open database", 0.05);
+ auto r_db = open_with_key(path, allow_creation, new_db_key);
if (r_db.is_ok()) {
- return Status::OK();
+ return r_db;
}
}
- TRY_RESULT(db, open_with_key(path, old_db_key));
+ PerfWarningTimer perf("change database key", 0.5);
+ auto create_database = [](CSlice tmp_path) -> Status {
+ TRY_STATUS(destroy(tmp_path));
+ SqliteDb db;
+ return db.init(tmp_path, true);
+ };
+
+ TRY_RESULT(db, open_with_key(path, false, old_db_key));
TRY_RESULT(user_version, db.user_version());
auto new_key = db_key_to_sqlcipher_key(new_db_key);
if (old_db_key.is_empty() && !new_db_key.is_empty()) {
LOG(DEBUG) << "ENCRYPT";
- // Encrypt
- PerfWarningTimer timer("Encrypt sqlite database", 0.1);
- auto tmp_path = path.str() + ".ecnrypted";
- unlink(tmp_path).ignore();
+ PerfWarningTimer timer("Encrypt SQLite database", 0.1);
+ auto tmp_path = path.str() + ".encrypted";
+ TRY_STATUS(create_database(tmp_path));
- // make shure that database is not empty
+ // make sure that database is not empty
TRY_STATUS(db.exec("CREATE TABLE IF NOT EXISTS encryption_dummy_table(id INT PRIMARY KEY)"));
- //NB: not really safe
- TRY_STATUS(db.exec(PSLICE() << "ATTACH DATABASE '" << tmp_path << "' AS encrypted KEY " << new_key));
+ TRY_STATUS(db.exec(PSLICE() << "ATTACH DATABASE '" << quote_string(tmp_path) << "' AS encrypted KEY " << new_key));
TRY_STATUS(db.exec("SELECT sqlcipher_export('encrypted')"));
TRY_STATUS(db.exec(PSLICE() << "PRAGMA encrypted.user_version = " << user_version));
TRY_STATUS(db.exec("DETACH DATABASE encrypted"));
@@ -191,13 +285,11 @@ Status SqliteDb::change_key(CSlice path, const DbKey &new_db_key, const DbKey &o
TRY_STATUS(rename(tmp_path, path));
} else if (!old_db_key.is_empty() && new_db_key.is_empty()) {
LOG(DEBUG) << "DECRYPT";
- // Dectypt
- PerfWarningTimer timer("Decrypt sqlite database", 0.1);
- auto tmp_path = path.str() + ".ecnrypted";
- unlink(tmp_path).ignore();
+ PerfWarningTimer timer("Decrypt SQLite database", 0.1);
+ auto tmp_path = path.str() + ".encrypted";
+ TRY_STATUS(create_database(tmp_path));
- //NB: not really safe
- TRY_STATUS(db.exec(PSLICE() << "ATTACH DATABASE '" << tmp_path << "' AS decrypted KEY ''"));
+ TRY_STATUS(db.exec(PSLICE() << "ATTACH DATABASE '" << quote_string(tmp_path) << "' AS decrypted KEY ''"));
TRY_STATUS(db.exec("SELECT sqlcipher_export('decrypted')"));
TRY_STATUS(db.exec(PSLICE() << "PRAGMA decrypted.user_version = " << user_version));
TRY_STATUS(db.exec("DETACH DATABASE decrypted"));
@@ -205,24 +297,27 @@ Status SqliteDb::change_key(CSlice path, const DbKey &new_db_key, const DbKey &o
TRY_STATUS(rename(tmp_path, path));
} else {
LOG(DEBUG) << "REKEY";
- PerfWarningTimer timer("Rekey sqlite database", 0.1);
+ PerfWarningTimer timer("Rekey SQLite database", 0.1);
TRY_STATUS(db.exec(PSLICE() << "PRAGMA rekey = " << new_key));
}
- TRY_RESULT(new_db, open_with_key(path, new_db_key));
+ TRY_RESULT(new_db, open_with_key(path, false, new_db_key));
CHECK(new_db.user_version().ok() == user_version);
- return Status::OK();
+ return std::move(new_db);
}
Status SqliteDb::destroy(Slice path) {
return detail::RawSqliteDb::destroy(path);
}
Result<SqliteStatement> SqliteDb::get_statement(CSlice statement) {
- sqlite3_stmt *stmt = nullptr;
- auto rc = sqlite3_prepare_v2(get_native(), statement.c_str(), static_cast<int>(statement.size()) + 1, &stmt, nullptr);
+ tdsqlite3_stmt *stmt = nullptr;
+ auto rc =
+ tdsqlite3_prepare_v2(get_native(), statement.c_str(), static_cast<int>(statement.size()) + 1, &stmt, nullptr);
if (rc != SQLITE_OK) {
- return Status::Error(PSLICE() << "Failed to prepare sqlite " << tag("stmt", statement) << raw_->last_error());
+ return Status::Error(PSLICE() << "Failed to prepare SQLite " << tag("statement", statement) << raw_->last_error());
}
+ LOG_CHECK(stmt != nullptr) << statement;
return SqliteStatement(stmt, raw_);
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.h b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.h
index 40137464ce..899b02e4a5 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteDb.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,23 +11,19 @@
#include "td/db/detail/RawSqliteDb.h"
-#include "td/utils/logging.h"
+#include "td/utils/optional.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include <memory>
-struct sqlite3;
+struct tdsqlite3;
namespace td {
class SqliteDb {
public:
SqliteDb() = default;
- explicit SqliteDb(CSlice path) {
- auto status = init(path);
- LOG_IF(FATAL, status.is_error()) << status;
- }
SqliteDb(SqliteDb &&) = default;
SqliteDb &operator=(SqliteDb &&) = default;
SqliteDb(const SqliteDb &) = delete;
@@ -36,7 +32,7 @@ class SqliteDb {
// dangerous
SqliteDb clone() const {
- return SqliteDb(raw_);
+ return SqliteDb(raw_, enable_logging_);
}
bool empty() const {
@@ -46,26 +42,28 @@ class SqliteDb {
*this = SqliteDb();
}
- Status init(CSlice path, bool *was_created = nullptr) TD_WARN_UNUSED_RESULT;
Status exec(CSlice cmd) TD_WARN_UNUSED_RESULT;
Result<bool> has_table(Slice table);
Result<string> get_pragma(Slice name);
- Status begin_transaction();
- Status commit_transaction();
+ Result<string> get_pragma_string(Slice name);
+
+ Status begin_read_transaction() TD_WARN_UNUSED_RESULT;
+ Status begin_write_transaction() TD_WARN_UNUSED_RESULT;
+ Status commit_transaction() TD_WARN_UNUSED_RESULT;
Result<int32> user_version();
- Status set_user_version(int32 version);
+ Status set_user_version(int32 version) TD_WARN_UNUSED_RESULT;
void trace(bool flag);
static Status destroy(Slice path) TD_WARN_UNUSED_RESULT;
- // Anyway we can't change the key on the fly, so static functions is more than enough
- static Result<SqliteDb> open_with_key(CSlice path, const DbKey &db_key);
- static Status change_key(CSlice path, const DbKey &new_db_key, const DbKey &old_db_key);
-
- Status last_error();
+ // we can't change the key on the fly, so static functions are more than enough
+ static Result<SqliteDb> open_with_key(CSlice path, bool allow_creation, const DbKey &db_key,
+ optional<int32> cipher_version = {});
+ static Result<SqliteDb> change_key(CSlice path, bool allow_creation, const DbKey &new_db_key,
+ const DbKey &old_db_key);
- sqlite3 *get_native() const {
+ tdsqlite3 *get_native() const {
return raw_->db();
}
@@ -76,11 +74,20 @@ class SqliteDb {
detail::RawSqliteDb::with_db_path(main_path, f);
}
+ optional<int32> get_cipher_version() const;
+
private:
- explicit SqliteDb(std::shared_ptr<detail::RawSqliteDb> raw) : raw_(std::move(raw)) {
+ SqliteDb(std::shared_ptr<detail::RawSqliteDb> raw, bool enable_logging)
+ : raw_(std::move(raw)), enable_logging_(enable_logging) {
}
std::shared_ptr<detail::RawSqliteDb> raw_;
+ bool enable_logging_ = false;
+
+ Status init(CSlice path, bool allow_creation) TD_WARN_UNUSED_RESULT;
- bool is_encrypted();
+ Status check_encryption();
+ static Result<SqliteDb> do_open_with_key(CSlice path, bool allow_creation, const DbKey &db_key, int32 cipher_version);
+ void set_cipher_version(int32 cipher_version);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValue.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValue.cpp
new file mode 100644
index 0000000000..97e2e6fe94
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValue.cpp
@@ -0,0 +1,124 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/db/SqliteKeyValue.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/logging.h"
+#include "td/utils/ScopeGuard.h"
+
+namespace td {
+
+Status SqliteKeyValue::init_with_connection(SqliteDb connection, string table_name) {
+ auto init_guard = ScopeExit() + [&] {
+ close();
+ };
+ db_ = std::move(connection);
+ table_name_ = std::move(table_name);
+ TRY_STATUS(init(db_, table_name_));
+
+ TRY_RESULT_ASSIGN(set_stmt_,
+ db_.get_statement(PSLICE() << "REPLACE INTO " << table_name_ << " (k, v) VALUES (?1, ?2)"));
+ TRY_RESULT_ASSIGN(get_stmt_, db_.get_statement(PSLICE() << "SELECT v FROM " << table_name_ << " WHERE k = ?1"));
+ TRY_RESULT_ASSIGN(erase_stmt_, db_.get_statement(PSLICE() << "DELETE FROM " << table_name_ << " WHERE k = ?1"));
+ TRY_RESULT_ASSIGN(get_all_stmt_, db_.get_statement(PSLICE() << "SELECT k, v FROM " << table_name_));
+
+ TRY_RESULT_ASSIGN(erase_by_prefix_stmt_,
+ db_.get_statement(PSLICE() << "DELETE FROM " << table_name_ << " WHERE ?1 <= k AND k < ?2"));
+ TRY_RESULT_ASSIGN(erase_by_prefix_rare_stmt_,
+ db_.get_statement(PSLICE() << "DELETE FROM " << table_name_ << " WHERE ?1 <= k"));
+
+ TRY_RESULT_ASSIGN(get_by_prefix_stmt_,
+ db_.get_statement(PSLICE() << "SELECT k, v FROM " << table_name_ << " WHERE ?1 <= k AND k < ?2"));
+ TRY_RESULT_ASSIGN(get_by_prefix_rare_stmt_,
+ db_.get_statement(PSLICE() << "SELECT k, v FROM " << table_name_ << " WHERE ?1 <= k"));
+
+ init_guard.dismiss();
+ return Status::OK();
+}
+
+Status SqliteKeyValue::drop() {
+ if (empty()) {
+ return Status::OK();
+ }
+
+ auto result = drop(db_, table_name_);
+ close();
+ return result;
+}
+
+void SqliteKeyValue::set(Slice key, Slice value) {
+ set_stmt_.bind_blob(1, key).ensure();
+ set_stmt_.bind_blob(2, value).ensure();
+ auto status = set_stmt_.step();
+ if (status.is_error()) {
+ LOG(FATAL) << "Failed to set \"" << base64_encode(key) << "\": " << status;
+ }
+ set_stmt_.reset();
+}
+
+void SqliteKeyValue::set_all(const FlatHashMap<string, string> &key_values) {
+ begin_write_transaction().ensure();
+ for (auto &key_value : key_values) {
+ set(key_value.first, key_value.second);
+ }
+ commit_transaction().ensure();
+}
+
+string SqliteKeyValue::get(Slice key) {
+ SCOPE_EXIT {
+ get_stmt_.reset();
+ };
+ get_stmt_.bind_blob(1, key).ensure();
+ get_stmt_.step().ensure();
+ if (!get_stmt_.has_row()) {
+ return string();
+ }
+ auto data = get_stmt_.view_blob(0).str();
+ get_stmt_.step().ignore();
+ return data;
+}
+
+void SqliteKeyValue::erase(Slice key) {
+ erase_stmt_.bind_blob(1, key).ensure();
+ erase_stmt_.step().ensure();
+ erase_stmt_.reset();
+}
+
+void SqliteKeyValue::erase_by_prefix(Slice prefix) {
+ auto next = next_prefix(prefix);
+ if (next.empty()) {
+ SCOPE_EXIT {
+ erase_by_prefix_rare_stmt_.reset();
+ };
+ erase_by_prefix_rare_stmt_.bind_blob(1, prefix).ensure();
+ erase_by_prefix_rare_stmt_.step().ensure();
+ } else {
+ SCOPE_EXIT {
+ erase_by_prefix_stmt_.reset();
+ };
+ erase_by_prefix_stmt_.bind_blob(1, prefix).ensure();
+ erase_by_prefix_stmt_.bind_blob(2, next).ensure();
+ erase_by_prefix_stmt_.step().ensure();
+ }
+}
+
+string SqliteKeyValue::next_prefix(Slice prefix) {
+ string next = prefix.str();
+ size_t pos = next.size();
+ while (pos) {
+ pos--;
+ auto value = static_cast<uint8>(next[pos]);
+ value++;
+ next[pos] = static_cast<char>(value);
+ if (value != 0) {
+ return next;
+ }
+ }
+ return string{};
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValue.h b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValue.h
index 6fe050e7f2..bc617ff05f 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValue.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValue.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,149 +9,65 @@
#include "td/db/SqliteDb.h"
#include "td/db/SqliteStatement.h"
-#include "td/utils/logging.h"
-#include "td/utils/ScopeGuard.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
-#include <unordered_map>
-
namespace td {
+
class SqliteKeyValue {
public:
- static Status drop(SqliteDb &connection, Slice kv_name) TD_WARN_UNUSED_RESULT {
- return connection.exec(PSLICE() << "DROP TABLE IF EXISTS " << kv_name);
+ static Status drop(SqliteDb &connection, Slice table_name) TD_WARN_UNUSED_RESULT {
+ return connection.exec(PSLICE() << "DROP TABLE IF EXISTS " << table_name);
}
- static Status init(SqliteDb &connection, Slice kv_name) TD_WARN_UNUSED_RESULT {
- return connection.exec(PSLICE() << "CREATE TABLE IF NOT EXISTS " << kv_name << " (k BLOB PRIMARY KEY, v BLOB)");
+ static Status init(SqliteDb &connection, Slice table_name) TD_WARN_UNUSED_RESULT {
+ return connection.exec(PSLICE() << "CREATE TABLE IF NOT EXISTS " << table_name << " (k BLOB PRIMARY KEY, v BLOB)");
}
- using SeqNo = uint64;
- Result<bool> init(string name) TD_WARN_UNUSED_RESULT {
- name_ = std::move(name);
- bool is_created = false;
- SqliteDb db;
- TRY_STATUS(db.init(name, &is_created));
- TRY_STATUS(db.exec("PRAGMA encoding=\"UTF-8\""));
- TRY_STATUS(db.exec("PRAGMA synchronous=NORMAL"));
- TRY_STATUS(db.exec("PRAGMA journal_mode=WAL"));
- TRY_STATUS(db.exec("PRAGMA temp_store=MEMORY"));
- TRY_STATUS(init_with_connection(std::move(db), "KV"));
- return is_created;
+ bool empty() const {
+ return db_.empty();
}
- Status init_with_connection(SqliteDb connection, string kv_name) {
- db_ = std::move(connection);
- kv_name_ = std::move(kv_name);
- TRY_STATUS(init(db_, kv_name_));
- TRY_STATUS(db_.exec(PSLICE() << "CREATE TABLE IF NOT EXISTS " << kv_name_ << " (k BLOB PRIMARY KEY, v BLOB)"));
-
- TRY_RESULT(set_stmt, db_.get_statement(PSLICE() << "REPLACE INTO " << kv_name_ << " (k, v) VALUES (?1, ?2)"));
- set_stmt_ = std::move(set_stmt);
- TRY_RESULT(get_stmt, db_.get_statement(PSLICE() << "SELECT v FROM " << kv_name_ << " WHERE k = ?1"));
- get_stmt_ = std::move(get_stmt);
- TRY_RESULT(erase_stmt, db_.get_statement(PSLICE() << "DELETE FROM " << kv_name_ << " WHERE k = ?1"));
- erase_stmt_ = std::move(erase_stmt);
- TRY_RESULT(get_all_stmt, db_.get_statement(PSLICE() << "SELECT k, v FROM " << kv_name_ << ""));
-
- TRY_RESULT(erase_by_prefix_stmt,
- db_.get_statement(PSLICE() << "DELETE FROM " << kv_name_ << " WHERE ?1 <= k AND k < ?2"));
- erase_by_prefix_stmt_ = std::move(erase_by_prefix_stmt);
-
- TRY_RESULT(erase_by_prefix_rare_stmt,
- db_.get_statement(PSLICE() << "DELETE FROM " << kv_name_ << " WHERE ?1 <= k"));
- erase_by_prefix_rare_stmt_ = std::move(erase_by_prefix_rare_stmt);
-
- TRY_RESULT(get_by_prefix_stmt,
- db_.get_statement(PSLICE() << "SELECT k, v FROM " << kv_name_ << " WHERE ?1 <= k AND k < ?2"));
- get_by_prefix_stmt_ = std::move(get_by_prefix_stmt);
-
- TRY_RESULT(get_by_prefix_rare_stmt,
- db_.get_statement(PSLICE() << "SELECT k, v FROM " << kv_name_ << " WHERE ?1 <= k"));
- get_by_prefix_rare_stmt_ = std::move(get_by_prefix_rare_stmt);
-
- get_all_stmt_ = std::move(get_all_stmt);
- return Status::OK();
- }
+ Status init_with_connection(SqliteDb connection, string table_name) TD_WARN_UNUSED_RESULT;
- Result<bool> try_regenerate_index() TD_WARN_UNUSED_RESULT {
- return false;
- }
void close() {
- clear();
- }
- void close_silent() {
- clear();
- }
- static Status destroy(Slice name) {
- return SqliteDb::destroy(name);
- }
- void close_and_destroy() {
- db_.exec(PSLICE() << "DROP TABLE IF EXISTS " << kv_name_).ensure();
- auto name = std::move(name_);
- clear();
- if (!name.empty()) {
- SqliteDb::destroy(name).ignore();
- }
+ *this = SqliteKeyValue();
}
- SeqNo set(Slice key, Slice value) {
- set_stmt_.bind_blob(1, key).ensure();
- set_stmt_.bind_blob(2, value).ensure();
- set_stmt_.step().ensure();
- set_stmt_.reset();
- return 0;
- }
+ Status drop();
- SeqNo erase(Slice key) {
- erase_stmt_.bind_blob(1, key).ensure();
- erase_stmt_.step().ensure();
- erase_stmt_.reset();
- return 0;
- }
- string get(Slice key) {
- SCOPE_EXIT {
- get_stmt_.reset();
- };
- get_stmt_.bind_blob(1, key).ensure();
- get_stmt_.step().ensure();
- if (!get_stmt_.has_row()) {
- return "";
- }
- auto data = get_stmt_.view_blob(0).str();
- get_stmt_.step().ignore();
- return data;
+ void set(Slice key, Slice value);
+
+ void set_all(const FlatHashMap<string, string> &key_values);
+
+ string get(Slice key);
+
+ void erase(Slice key);
+
+ Status begin_read_transaction() TD_WARN_UNUSED_RESULT {
+ return db_.begin_read_transaction();
}
- Status begin_transaction() {
- return db_.begin_transaction();
+ Status begin_write_transaction() TD_WARN_UNUSED_RESULT {
+ return db_.begin_write_transaction();
}
- Status commit_transaction() {
+
+ Status commit_transaction() TD_WARN_UNUSED_RESULT {
return db_.commit_transaction();
}
- void erase_by_prefix(Slice prefix) {
- auto next = next_prefix(prefix);
- if (next.empty()) {
- SCOPE_EXIT {
- erase_by_prefix_rare_stmt_.reset();
- };
- erase_by_prefix_rare_stmt_.bind_blob(1, prefix).ensure();
- erase_by_prefix_rare_stmt_.step().ensure();
- } else {
- SCOPE_EXIT {
- erase_by_prefix_stmt_.reset();
- };
- erase_by_prefix_stmt_.bind_blob(1, prefix).ensure();
- erase_by_prefix_stmt_.bind_blob(2, next).ensure();
- erase_by_prefix_stmt_.step().ensure();
- }
- };
+ void erase_by_prefix(Slice prefix);
- std::unordered_map<string, string> get_all() {
- std::unordered_map<string, string> res;
- get_by_prefix("", [&](Slice key, Slice value) { res.emplace(key.str(), value.str()); });
+ FlatHashMap<string, string> get_all() {
+ FlatHashMap<string, string> res;
+ get_by_prefix("", [&](Slice key, Slice value) {
+ CHECK(!key.empty());
+ res.emplace(key.str(), value.str());
+ return true;
+ });
return res;
}
@@ -163,6 +79,7 @@ class SqliteKeyValue {
}
get_by_range(prefix, next, callback);
}
+
template <class CallbackT>
void get_by_range(Slice from, Slice till, CallbackT &&callback) {
SqliteStatement *stmt = nullptr;
@@ -181,18 +98,15 @@ class SqliteKeyValue {
auto guard = stmt->guard();
stmt->step().ensure();
while (stmt->has_row()) {
- callback(stmt->view_blob(0), stmt->view_blob(1));
+ if (!callback(stmt->view_blob(0), stmt->view_blob(1))) {
+ return;
+ }
stmt->step().ensure();
}
}
- void clear() {
- *this = SqliteKeyValue();
- }
-
private:
- string name_; // deprecated
- string kv_name_;
+ string table_name_;
SqliteDb db_;
SqliteStatement get_stmt_;
SqliteStatement set_stmt_;
@@ -203,19 +117,7 @@ class SqliteKeyValue {
SqliteStatement get_by_prefix_stmt_;
SqliteStatement get_by_prefix_rare_stmt_;
- string next_prefix(Slice prefix) {
- string next = prefix.str();
- size_t pos = next.size();
- while (pos) {
- pos--;
- auto value = static_cast<uint8>(next[pos]);
- value++;
- next[pos] = static_cast<char>(value);
- if (value != 0) {
- return next;
- }
- }
- return string{};
- }
+ static string next_prefix(Slice prefix);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.cpp
index e8575b62f1..a0a35a1e54 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.cpp
@@ -1,45 +1,57 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/db/SqliteKeyValueAsync.h"
+#include "td/db/SqliteKeyValue.h"
+
+#include "td/actor/actor.h"
+
+#include "td/utils/common.h"
#include "td/utils/optional.h"
#include "td/utils/Time.h"
-#include <unordered_map>
-
namespace td {
-class SqliteKeyValueAsync : public SqliteKeyValueAsyncInterface {
+
+class SqliteKeyValueAsync final : public SqliteKeyValueAsyncInterface {
public:
explicit SqliteKeyValueAsync(std::shared_ptr<SqliteKeyValueSafe> kv_safe, int32 scheduler_id = -1) {
impl_ = create_actor_on_scheduler<Impl>("KV", scheduler_id, std::move(kv_safe));
}
- void set(string key, string value, Promise<> promise) override {
+ void set(string key, string value, Promise<Unit> promise) final {
send_closure_later(impl_, &Impl::set, std::move(key), std::move(value), std::move(promise));
}
- void erase(string key, Promise<> promise) override {
+ void set_all(FlatHashMap<string, string> key_values, Promise<Unit> promise) final {
+ send_closure_later(impl_, &Impl::set_all, std::move(key_values), std::move(promise));
+ }
+ void erase(string key, Promise<Unit> promise) final {
send_closure_later(impl_, &Impl::erase, std::move(key), std::move(promise));
}
- void get(string key, Promise<string> promise) override {
+ void erase_by_prefix(string key_prefix, Promise<Unit> promise) final {
+ send_closure_later(impl_, &Impl::erase_by_prefix, std::move(key_prefix), std::move(promise));
+ }
+ void get(string key, Promise<string> promise) final {
send_closure_later(impl_, &Impl::get, std::move(key), std::move(promise));
}
- void close(Promise<> promise) override {
+ void close(Promise<Unit> promise) final {
send_closure_later(impl_, &Impl::close, std::move(promise));
}
private:
- class Impl : public Actor {
+ class Impl final : public Actor {
public:
explicit Impl(std::shared_ptr<SqliteKeyValueSafe> kv_safe) : kv_safe_(std::move(kv_safe)) {
}
- void set(string key, string value, Promise<> promise) {
+
+ void set(string key, string value, Promise<Unit> promise) {
auto it = buffer_.find(key);
if (it != buffer_.end()) {
it->second = std::move(value);
} else {
+ CHECK(!key.empty());
buffer_.emplace(std::move(key), std::move(value));
}
if (promise) {
@@ -48,11 +60,19 @@ class SqliteKeyValueAsync : public SqliteKeyValueAsyncInterface {
cnt_++;
do_flush(false /*force*/);
}
- void erase(string key, Promise<> promise) {
+
+ void set_all(FlatHashMap<string, string> key_values, Promise<Unit> promise) {
+ do_flush(true /*force*/);
+ kv_->set_all(key_values);
+ promise.set_value(Unit());
+ }
+
+ void erase(string key, Promise<Unit> promise) {
auto it = buffer_.find(key);
if (it != buffer_.end()) {
it->second = optional<string>();
} else {
+ CHECK(!key.empty());
buffer_.emplace(std::move(key), optional<string>());
}
if (promise) {
@@ -62,6 +82,12 @@ class SqliteKeyValueAsync : public SqliteKeyValueAsyncInterface {
do_flush(false /*force*/);
}
+ void erase_by_prefix(string key_prefix, Promise<Unit> promise) {
+ do_flush(true /*force*/);
+ kv_->erase_by_prefix(key_prefix);
+ promise.set_value(Unit());
+ }
+
void get(const string &key, Promise<string> promise) {
auto it = buffer_.find(key);
if (it != buffer_.end()) {
@@ -69,7 +95,8 @@ class SqliteKeyValueAsync : public SqliteKeyValueAsyncInterface {
}
promise.set_value(kv_->get(key));
}
- void close(Promise<> promise) {
+
+ void close(Promise<Unit> promise) {
do_flush(true /*force*/);
kv_safe_.reset();
kv_ = nullptr;
@@ -81,10 +108,10 @@ class SqliteKeyValueAsync : public SqliteKeyValueAsyncInterface {
std::shared_ptr<SqliteKeyValueSafe> kv_safe_;
SqliteKeyValue *kv_ = nullptr;
- static constexpr double MAX_PENDING_QUERIES_DELAY = 1;
+ static constexpr double MAX_PENDING_QUERIES_DELAY = 0.01;
static constexpr size_t MAX_PENDING_QUERIES_COUNT = 100;
- std::unordered_map<string, optional<string>> buffer_;
- std::vector<Promise<>> buffer_promises_;
+ FlatHashMap<string, optional<string>> buffer_;
+ vector<Promise<Unit>> buffer_promises_;
size_t cnt_ = 0;
double wakeup_at_ = 0;
@@ -107,7 +134,7 @@ class SqliteKeyValueAsync : public SqliteKeyValueAsyncInterface {
wakeup_at_ = 0;
cnt_ = 0;
- kv_->begin_transaction().ensure();
+ kv_->begin_write_transaction().ensure();
for (auto &it : buffer_) {
if (it.second) {
kv_->set(it.first, it.second.value());
@@ -117,25 +144,23 @@ class SqliteKeyValueAsync : public SqliteKeyValueAsyncInterface {
}
kv_->commit_transaction().ensure();
buffer_.clear();
- for (auto &promise : buffer_promises_) {
- promise.set_value(Unit());
- }
- buffer_promises_.clear();
+ set_promises(buffer_promises_);
}
- void timeout_expired() override {
+ void timeout_expired() final {
do_flush(false /*force*/);
}
- void start_up() override {
+ void start_up() final {
kv_ = &kv_safe_->get();
}
};
ActorOwn<Impl> impl_;
};
-std::unique_ptr<SqliteKeyValueAsyncInterface> create_sqlite_key_value_async(std::shared_ptr<SqliteKeyValueSafe> kv,
- int32 scheduler_id) {
- return std::make_unique<SqliteKeyValueAsync>(std::move(kv), scheduler_id);
+
+unique_ptr<SqliteKeyValueAsyncInterface> create_sqlite_key_value_async(std::shared_ptr<SqliteKeyValueSafe> kv,
+ int32 scheduler_id) {
+ return td::make_unique<SqliteKeyValueAsync>(std::move(kv), scheduler_id);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.h b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.h
index 6015d26fb2..e5bc29b1e8 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueAsync.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,7 +8,9 @@
#include "td/db/SqliteKeyValueSafe.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Promise.h"
#include <memory>
@@ -18,13 +20,19 @@ class SqliteKeyValueAsyncInterface {
public:
virtual ~SqliteKeyValueAsyncInterface() = default;
- virtual void set(string key, string value, Promise<> promise) = 0;
- virtual void erase(string key, Promise<> promise) = 0;
+ virtual void set(string key, string value, Promise<Unit> promise) = 0;
+
+ virtual void set_all(FlatHashMap<string, string> key_values, Promise<Unit> promise) = 0;
+
+ virtual void erase(string key, Promise<Unit> promise) = 0;
+
+ virtual void erase_by_prefix(string key_prefix, Promise<Unit> promise) = 0;
virtual void get(string key, Promise<string> promise) = 0;
- virtual void close(Promise<> promise) = 0;
+
+ virtual void close(Promise<Unit> promise) = 0;
};
-std::unique_ptr<SqliteKeyValueAsyncInterface> create_sqlite_key_value_async(std::shared_ptr<SqliteKeyValueSafe> kv,
- int32 scheduler_id = 1);
+unique_ptr<SqliteKeyValueAsyncInterface> create_sqlite_key_value_async(std::shared_ptr<SqliteKeyValueSafe> kv,
+ int32 scheduler_id = 1);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueSafe.h b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueSafe.h
index d63af3cfb2..b61a96e193 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueSafe.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteKeyValueSafe.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,6 +9,8 @@
#include "td/db/SqliteConnectionSafe.h"
#include "td/db/SqliteKeyValue.h"
+#include "td/actor/SchedulerLocalStorage.h"
+
#include <memory>
namespace td {
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.cpp
index c9ce8c3e8e..a2ae3833e4 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,40 +15,42 @@
namespace td {
+int VERBOSITY_NAME(sqlite) = VERBOSITY_NAME(DEBUG) + 10;
+
namespace {
-int printExplainQueryPlan(StringBuilder &sb, sqlite3_stmt *pStmt) {
- const char *zSql = sqlite3_sql(pStmt);
+int printExplainQueryPlan(StringBuilder &sb, tdsqlite3_stmt *pStmt) {
+ const char *zSql = tdsqlite3_sql(pStmt);
if (zSql == nullptr) {
return SQLITE_ERROR;
}
- sb << "Explain " << tag("cmd", zSql);
- char *zExplain = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zSql);
+ sb << "Explain query " << zSql;
+ char *zExplain = tdsqlite3_mprintf("EXPLAIN QUERY PLAN %s", zSql);
if (zExplain == nullptr) {
return SQLITE_NOMEM;
}
- sqlite3_stmt *pExplain; /* Compiled EXPLAIN QUERY PLAN command */
- int rc = sqlite3_prepare_v2(sqlite3_db_handle(pStmt), zExplain, -1, &pExplain, nullptr);
- sqlite3_free(zExplain);
+ tdsqlite3_stmt *pExplain; /* Compiled EXPLAIN QUERY PLAN command */
+ int rc = tdsqlite3_prepare_v2(tdsqlite3_db_handle(pStmt), zExplain, -1, &pExplain, nullptr);
+ tdsqlite3_free(zExplain);
if (rc != SQLITE_OK) {
return rc;
}
- while (SQLITE_ROW == sqlite3_step(pExplain)) {
- int iSelectid = sqlite3_column_int(pExplain, 0);
- int iOrder = sqlite3_column_int(pExplain, 1);
- int iFrom = sqlite3_column_int(pExplain, 2);
- const char *zDetail = reinterpret_cast<const char *>(sqlite3_column_text(pExplain, 3));
+ while (SQLITE_ROW == tdsqlite3_step(pExplain)) {
+ int iSelectid = tdsqlite3_column_int(pExplain, 0);
+ int iOrder = tdsqlite3_column_int(pExplain, 1);
+ int iFrom = tdsqlite3_column_int(pExplain, 2);
+ const char *zDetail = reinterpret_cast<const char *>(tdsqlite3_column_text(pExplain, 3));
- sb << "\n" << iSelectid << " " << iOrder << " " << iFrom << " " << zDetail;
+ sb << '\n' << iSelectid << ' ' << iOrder << ' ' << iFrom << ' ' << zDetail;
}
- return sqlite3_finalize(pExplain);
+ return tdsqlite3_finalize(pExplain);
}
} // namespace
-SqliteStatement::SqliteStatement(sqlite3_stmt *stmt, std::shared_ptr<detail::RawSqliteDb> db)
+SqliteStatement::SqliteStatement(tdsqlite3_stmt *stmt, std::shared_ptr<detail::RawSqliteDb> db)
: stmt_(stmt), db_(std::move(db)) {
CHECK(stmt != nullptr);
}
@@ -70,14 +72,14 @@ Result<string> SqliteStatement::explain() {
return sb.as_cslice().str();
}
Status SqliteStatement::bind_blob(int id, Slice blob) {
- auto rc = sqlite3_bind_blob(stmt_.get(), id, blob.data(), static_cast<int>(blob.size()), nullptr);
+ auto rc = tdsqlite3_bind_blob(stmt_.get(), id, blob.data(), static_cast<int>(blob.size()), nullptr);
if (rc != SQLITE_OK) {
return last_error();
}
return Status::OK();
}
Status SqliteStatement::bind_string(int id, Slice str) {
- auto rc = sqlite3_bind_text(stmt_.get(), id, str.data(), static_cast<int>(str.size()), nullptr);
+ auto rc = tdsqlite3_bind_text(stmt_.get(), id, str.data(), static_cast<int>(str.size()), nullptr);
if (rc != SQLITE_OK) {
return last_error();
}
@@ -85,21 +87,21 @@ Status SqliteStatement::bind_string(int id, Slice str) {
}
Status SqliteStatement::bind_int32(int id, int32 value) {
- auto rc = sqlite3_bind_int(stmt_.get(), id, value);
+ auto rc = tdsqlite3_bind_int(stmt_.get(), id, value);
if (rc != SQLITE_OK) {
return last_error();
}
return Status::OK();
}
Status SqliteStatement::bind_int64(int id, int64 value) {
- auto rc = sqlite3_bind_int64(stmt_.get(), id, value);
+ auto rc = tdsqlite3_bind_int64(stmt_.get(), id, value);
if (rc != SQLITE_OK) {
return last_error();
}
return Status::OK();
}
Status SqliteStatement::bind_null(int id) {
- auto rc = sqlite3_bind_null(stmt_.get(), id);
+ auto rc = tdsqlite3_bind_null(stmt_.get(), id);
if (rc != SQLITE_OK) {
return last_error();
}
@@ -125,8 +127,8 @@ StringBuilder &operator<<(StringBuilder &sb, SqliteStatement::Datatype type) {
}
Slice SqliteStatement::view_blob(int id) {
LOG_IF(ERROR, view_datatype(id) != Datatype::Blob) << view_datatype(id);
- auto *data = sqlite3_column_blob(stmt_.get(), id);
- auto size = sqlite3_column_bytes(stmt_.get(), id);
+ auto *data = tdsqlite3_column_blob(stmt_.get(), id);
+ auto size = tdsqlite3_column_bytes(stmt_.get(), id);
if (data == nullptr) {
return Slice();
}
@@ -134,8 +136,8 @@ Slice SqliteStatement::view_blob(int id) {
}
Slice SqliteStatement::view_string(int id) {
LOG_IF(ERROR, view_datatype(id) != Datatype::Text) << view_datatype(id);
- auto *data = sqlite3_column_text(stmt_.get(), id);
- auto size = sqlite3_column_bytes(stmt_.get(), id);
+ auto *data = tdsqlite3_column_text(stmt_.get(), id);
+ auto size = tdsqlite3_column_bytes(stmt_.get(), id);
if (data == nullptr) {
return Slice();
}
@@ -143,14 +145,14 @@ Slice SqliteStatement::view_string(int id) {
}
int32 SqliteStatement::view_int32(int id) {
LOG_IF(ERROR, view_datatype(id) != Datatype::Integer) << view_datatype(id);
- return sqlite3_column_int(stmt_.get(), id);
+ return tdsqlite3_column_int(stmt_.get(), id);
}
int64 SqliteStatement::view_int64(int id) {
LOG_IF(ERROR, view_datatype(id) != Datatype::Integer) << view_datatype(id);
- return sqlite3_column_int64(stmt_.get(), id);
+ return tdsqlite3_column_int64(stmt_.get(), id);
}
SqliteStatement::Datatype SqliteStatement::view_datatype(int id) {
- auto type = sqlite3_column_type(stmt_.get(), id);
+ auto type = tdsqlite3_column_type(stmt_.get(), id);
switch (type) {
case SQLITE_INTEGER:
return Datatype::Integer;
@@ -168,36 +170,36 @@ SqliteStatement::Datatype SqliteStatement::view_datatype(int id) {
}
void SqliteStatement::reset() {
- sqlite3_reset(stmt_.get());
- state_ = Start;
+ tdsqlite3_reset(stmt_.get());
+ state_ = State::Start;
}
Status SqliteStatement::step() {
- if (state_ == Finish) {
+ if (state_ == State::Finish) {
return Status::Error("One has to reset statement");
}
- VLOG(sqlite) << "Start step " << tag("cmd", sqlite3_sql(stmt_.get())) << tag("stmt", stmt_.get())
- << tag("db", db_.get());
- auto rc = sqlite3_step(stmt_.get());
- VLOG(sqlite) << "Finish step " << tag("cmd", sqlite3_sql(stmt_.get())) << tag("stmt", stmt_.get())
- << tag("db", db_.get());
+ VLOG(sqlite) << "Start step " << tag("query", tdsqlite3_sql(stmt_.get())) << tag("statement", stmt_.get())
+ << tag("database", db_.get());
+ auto rc = tdsqlite3_step(stmt_.get());
+ VLOG(sqlite) << "Finish step with response " << (rc == SQLITE_ROW ? "ROW" : (rc == SQLITE_DONE ? "DONE" : "ERROR"));
if (rc == SQLITE_ROW) {
- state_ = GotRow;
+ state_ = State::GotRow;
return Status::OK();
}
+
+ state_ = State::Finish;
if (rc == SQLITE_DONE) {
- state_ = Finish;
return Status::OK();
}
- state_ = Finish;
return last_error();
}
-void SqliteStatement::StmtDeleter::operator()(sqlite3_stmt *stmt) {
- sqlite3_finalize(stmt);
+void SqliteStatement::StmtDeleter::operator()(tdsqlite3_stmt *stmt) {
+ tdsqlite3_finalize(stmt);
}
Status SqliteStatement::last_error() {
return db_->last_error();
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.h b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.h
index 2e2182ff7e..35148508ce 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/SqliteStatement.h
@@ -1,24 +1,28 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/db/detail/RawSqliteDb.h"
+
#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/ScopeGuard.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
-#include "td/db/detail/RawSqliteDb.h"
-
#include <memory>
-struct sqlite3;
-struct sqlite3_stmt;
+struct tdsqlite3;
+struct tdsqlite3_stmt;
namespace td {
+extern int VERBOSITY_NAME(sqlite);
+
class SqliteStatement {
public:
SqliteStatement() = default;
@@ -44,10 +48,10 @@ class SqliteStatement {
Result<string> explain();
bool can_step() const {
- return state_ != Finish;
+ return state_ != State::Finish;
}
bool has_row() const {
- return state_ == GotRow;
+ return state_ == State::GotRow;
}
bool empty() const {
return !stmt_;
@@ -56,25 +60,29 @@ class SqliteStatement {
void reset();
auto guard() {
- return ScopeExit{} + [this] { this->reset(); };
+ return ScopeExit{} + [this] {
+ this->reset();
+ };
}
// TODO get row
private:
friend class SqliteDb;
- SqliteStatement(sqlite3_stmt *stmt, std::shared_ptr<detail::RawSqliteDb> db);
+ SqliteStatement(tdsqlite3_stmt *stmt, std::shared_ptr<detail::RawSqliteDb> db);
class StmtDeleter {
public:
- void operator()(sqlite3_stmt *stmt);
+ void operator()(tdsqlite3_stmt *stmt);
};
- enum { Start, GotRow, Finish } state_ = Start;
+ enum class State { Start, GotRow, Finish };
+ State state_ = State::Start;
- std::unique_ptr<sqlite3_stmt, StmtDeleter> stmt_;
+ std::unique_ptr<tdsqlite3_stmt, StmtDeleter> stmt_;
std::shared_ptr<detail::RawSqliteDb> db_;
Status last_error();
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/TQueue.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/TQueue.cpp
new file mode 100644
index 0000000000..c94d7defaa
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/TQueue.cpp
@@ -0,0 +1,558 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/db/TQueue.h"
+
+#include "td/db/binlog/Binlog.h"
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/binlog/BinlogInterface.h"
+
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/StorerBase.h"
+#include "td/utils/Time.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/tl_parsers.h"
+#include "td/utils/tl_storers.h"
+
+#include <set>
+
+namespace td {
+
+using EventId = TQueue::EventId;
+
+EventId::EventId() {
+}
+
+Result<EventId> EventId::from_int32(int32 id) {
+ if (!is_valid_id(id)) {
+ return Status::Error("Invalid ID");
+ }
+ return EventId(id);
+}
+
+bool EventId::is_valid() const {
+ return !empty() && is_valid_id(id_);
+}
+
+int32 EventId::value() const {
+ return id_;
+}
+
+Result<EventId> EventId::next() const {
+ return from_int32(id_ + 1);
+}
+
+Result<EventId> EventId::advance(size_t offset) const {
+ TRY_RESULT(new_id, narrow_cast_safe<int32>(id_ + offset));
+ return from_int32(new_id);
+}
+
+bool EventId::empty() const {
+ return id_ == 0;
+}
+
+bool EventId::operator==(const EventId &other) const {
+ return id_ == other.id_;
+}
+
+bool EventId::operator!=(const EventId &other) const {
+ return !(*this == other);
+}
+
+bool EventId::operator<(const EventId &other) const {
+ return id_ < other.id_;
+}
+
+StringBuilder &operator<<(StringBuilder &string_builder, EventId id) {
+ return string_builder << "EventId{" << id.value() << "}";
+}
+
+EventId::EventId(int32 id) : id_(id) {
+ CHECK(is_valid_id(id));
+}
+
+bool EventId::is_valid_id(int32 id) {
+ return 0 <= id && id < MAX_ID;
+}
+
+class TQueueImpl final : public TQueue {
+ static constexpr size_t MAX_EVENT_LENGTH = 65536 * 8;
+ static constexpr size_t MAX_QUEUE_EVENTS = 100000;
+ static constexpr size_t MAX_TOTAL_EVENT_LENGTH = 1 << 27;
+
+ public:
+ void set_callback(unique_ptr<StorageCallback> callback) final {
+ callback_ = std::move(callback);
+ }
+ unique_ptr<StorageCallback> extract_callback() final {
+ return std::move(callback_);
+ }
+
+ bool do_push(QueueId queue_id, RawEvent &&raw_event) final {
+ CHECK(raw_event.event_id.is_valid());
+ // raw_event.data can be empty when replaying binlog
+ if (raw_event.data.size() > MAX_EVENT_LENGTH || queue_id == 0) {
+ return false;
+ }
+ auto &q = queues_[queue_id];
+ if (q.events.size() >= MAX_QUEUE_EVENTS || q.total_event_length > MAX_TOTAL_EVENT_LENGTH - raw_event.data.size() ||
+ raw_event.expires_at <= 0) {
+ return false;
+ }
+ auto event_id = raw_event.event_id;
+ if (event_id < q.tail_id) {
+ return false;
+ }
+
+ if (!q.events.empty()) {
+ auto it = q.events.end();
+ --it;
+ if (it->second.data.empty()) {
+ if (callback_ != nullptr && it->second.log_event_id != 0) {
+ callback_->pop(it->second.log_event_id);
+ }
+ q.events.erase(it);
+ }
+ }
+ if (q.events.empty() && !raw_event.data.empty()) {
+ schedule_queue_gc(queue_id, q, raw_event.expires_at);
+ }
+
+ if (raw_event.log_event_id == 0 && callback_ != nullptr) {
+ raw_event.log_event_id = callback_->push(queue_id, raw_event);
+ }
+ q.tail_id = event_id.next().move_as_ok();
+ q.total_event_length += raw_event.data.size();
+ q.events.emplace(event_id, std::move(raw_event));
+ return true;
+ }
+
+ Result<EventId> push(QueueId queue_id, string data, int32 expires_at, int64 extra, EventId hint_new_id) final {
+ if (data.empty()) {
+ return Status::Error("Data is empty");
+ }
+ if (data.size() > MAX_EVENT_LENGTH) {
+ return Status::Error("Data is too big");
+ }
+ if (queue_id == 0) {
+ return Status::Error("Queue identifier is invalid");
+ }
+
+ auto &q = queues_[queue_id];
+ if (q.events.size() >= MAX_QUEUE_EVENTS) {
+ return Status::Error("Queue is full");
+ }
+ if (q.total_event_length > MAX_TOTAL_EVENT_LENGTH - data.size()) {
+ return Status::Error("Queue size is too big");
+ }
+ if (expires_at <= 0) {
+ return Status::Error("Failed to add already expired event");
+ }
+ EventId event_id;
+ while (true) {
+ if (q.tail_id.empty()) {
+ if (hint_new_id.empty()) {
+ q.tail_id = EventId::from_int32(
+ Random::fast(2 * max(static_cast<int>(MAX_QUEUE_EVENTS), 1000000) + 1, EventId::MAX_ID / 2))
+ .move_as_ok();
+ } else {
+ q.tail_id = hint_new_id;
+ }
+ }
+ event_id = q.tail_id;
+ CHECK(event_id.is_valid());
+ if (event_id.next().is_ok()) {
+ break;
+ }
+ for (auto it = q.events.begin(); it != q.events.end();) {
+ pop(q, queue_id, it, {});
+ }
+ q.tail_id = EventId();
+ CHECK(hint_new_id.next().is_ok());
+ }
+
+ RawEvent raw_event;
+ raw_event.event_id = event_id;
+ raw_event.data = std::move(data);
+ raw_event.expires_at = expires_at;
+ raw_event.extra = extra;
+ bool is_added = do_push(queue_id, std::move(raw_event));
+ CHECK(is_added);
+ return event_id;
+ }
+
+ EventId get_head(QueueId queue_id) const final {
+ auto it = queues_.find(queue_id);
+ if (it == queues_.end()) {
+ return EventId();
+ }
+ return get_queue_head(it->second);
+ }
+
+ EventId get_tail(QueueId queue_id) const final {
+ auto it = queues_.find(queue_id);
+ if (it == queues_.end()) {
+ return EventId();
+ }
+ auto &q = it->second;
+ return q.tail_id;
+ }
+
+ void forget(QueueId queue_id, EventId event_id) final {
+ auto q_it = queues_.find(queue_id);
+ if (q_it == queues_.end()) {
+ return;
+ }
+ auto &q = q_it->second;
+ auto it = q.events.find(event_id);
+ if (it == q.events.end()) {
+ return;
+ }
+ pop(q, queue_id, it, q.tail_id);
+ }
+
+ void clear(QueueId queue_id, size_t keep_count) final {
+ auto queue_it = queues_.find(queue_id);
+ if (queue_it == queues_.end()) {
+ return;
+ }
+ auto &q = queue_it->second;
+ auto size = get_size(q);
+ if (size <= keep_count) {
+ return;
+ }
+
+ auto start_time = Time::now();
+ auto total_event_length = q.total_event_length;
+
+ auto end_it = q.events.end();
+ for (size_t i = 0; i < keep_count; i++) {
+ --end_it;
+ }
+ for (auto it = q.events.begin(); it != end_it;) {
+ pop(q, queue_id, it, q.tail_id);
+ }
+
+ auto clear_time = Time::now() - start_time;
+ if (clear_time > 0.1) {
+ LOG(WARNING) << "Cleared " << (size - keep_count) << " TQueue events with total size "
+ << (total_event_length - q.total_event_length) << " in " << clear_time << " seconds";
+ }
+ }
+
+ Result<size_t> get(QueueId queue_id, EventId from_id, bool forget_previous, int32 unix_time_now,
+ MutableSpan<Event> &result_events) final {
+ auto it = queues_.find(queue_id);
+ if (it == queues_.end()) {
+ result_events.truncate(0);
+ return 0;
+ }
+ auto &q = it->second;
+ // Some sanity checks
+ if (from_id.value() > q.tail_id.value() + 10) {
+ return Status::Error("Specified from_id is in the future");
+ }
+ if (from_id.value() < get_queue_head(q).value() - static_cast<int32>(MAX_QUEUE_EVENTS)) {
+ return Status::Error("Specified from_id is in the past");
+ }
+
+ do_get(queue_id, q, from_id, forget_previous, unix_time_now, result_events);
+ return get_size(q);
+ }
+
+ std::pair<int64, bool> run_gc(int32 unix_time_now) final {
+ int64 deleted_events = 0;
+ auto max_finish_time = Time::now() + 0.05;
+ int64 counter = 0;
+ while (!queue_gc_at_.empty()) {
+ auto it = queue_gc_at_.begin();
+ if (it->first >= unix_time_now) {
+ break;
+ }
+ auto queue_id = it->second;
+ auto &q = queues_[queue_id];
+ CHECK(q.gc_at == it->first);
+ int32 new_gc_at = 0;
+
+ if (!q.events.empty()) {
+ size_t size_before = get_size(q);
+ for (auto event_it = q.events.begin(); event_it != q.events.end();) {
+ auto &event = event_it->second;
+ if ((++counter & 128) == 0 && Time::now() >= max_finish_time) {
+ if (new_gc_at == 0) {
+ new_gc_at = event.expires_at;
+ }
+ break;
+ }
+ if (event.expires_at < unix_time_now || event.data.empty()) {
+ pop(q, queue_id, event_it, q.tail_id);
+ } else {
+ if (new_gc_at != 0) {
+ break;
+ }
+ new_gc_at = event.expires_at;
+ ++event_it;
+ }
+ }
+ size_t size_after = get_size(q);
+ CHECK(size_after <= size_before);
+ deleted_events += size_before - size_after;
+ }
+ schedule_queue_gc(queue_id, q, new_gc_at);
+ if (Time::now() >= max_finish_time) {
+ return {deleted_events, false};
+ }
+ }
+ return {deleted_events, true};
+ }
+
+ size_t get_size(QueueId queue_id) const final {
+ auto it = queues_.find(queue_id);
+ if (it == queues_.end()) {
+ return 0;
+ }
+ return get_size(it->second);
+ }
+
+ void close(Promise<> promise) final {
+ if (callback_ != nullptr) {
+ callback_->close(std::move(promise));
+ callback_ = nullptr;
+ }
+ }
+
+ private:
+ struct Queue {
+ EventId tail_id;
+ std::map<EventId, RawEvent> events;
+ size_t total_event_length = 0;
+ int32 gc_at = 0;
+ };
+
+ FlatHashMap<QueueId, Queue> queues_;
+ std::set<std::pair<int32, QueueId>> queue_gc_at_;
+ unique_ptr<StorageCallback> callback_;
+
+ static EventId get_queue_head(const Queue &q) {
+ if (q.events.empty()) {
+ return q.tail_id;
+ }
+ return q.events.begin()->first;
+ }
+
+ static size_t get_size(const Queue &q) {
+ if (q.events.empty()) {
+ return 0;
+ }
+
+ return q.events.size() - (q.events.rbegin()->second.data.empty() ? 1 : 0);
+ }
+
+ void pop(Queue &q, QueueId queue_id, std::map<EventId, RawEvent>::iterator &it, EventId tail_id) {
+ auto &event = it->second;
+ if (callback_ == nullptr || event.log_event_id == 0) {
+ remove_event(q, it);
+ return;
+ }
+
+ if (event.event_id.next().ok() == tail_id) {
+ if (!event.data.empty()) {
+ clear_event_data(q, event);
+ callback_->push(queue_id, event);
+ }
+ ++it;
+ } else {
+ callback_->pop(event.log_event_id);
+ remove_event(q, it);
+ }
+ }
+
+ static void remove_event(Queue &q, std::map<EventId, RawEvent>::iterator &it) {
+ q.total_event_length -= it->second.data.size();
+ it = q.events.erase(it);
+ }
+
+ static void clear_event_data(Queue &q, RawEvent &event) {
+ q.total_event_length -= event.data.size();
+ event.data = {};
+ }
+
+ void do_get(QueueId queue_id, Queue &q, EventId from_id, bool forget_previous, int32 unix_time_now,
+ MutableSpan<Event> &result_events) {
+ if (forget_previous) {
+ for (auto it = q.events.begin(); it != q.events.end() && it->first < from_id;) {
+ pop(q, queue_id, it, q.tail_id);
+ }
+ }
+
+ size_t ready_n = 0;
+ for (auto it = q.events.lower_bound(from_id); it != q.events.end();) {
+ auto &event = it->second;
+ if (event.expires_at < unix_time_now || event.data.empty()) {
+ pop(q, queue_id, it, q.tail_id);
+ } else {
+ CHECK(!(event.event_id < from_id));
+ if (ready_n == result_events.size()) {
+ break;
+ }
+
+ auto &to = result_events[ready_n];
+ to.data = event.data;
+ to.id = event.event_id;
+ to.expires_at = event.expires_at;
+ to.extra = event.extra;
+ ready_n++;
+ ++it;
+ }
+ }
+
+ result_events.truncate(ready_n);
+ }
+
+ void schedule_queue_gc(QueueId queue_id, Queue &q, int32 gc_at) {
+ if (q.gc_at != 0) {
+ bool is_deleted = queue_gc_at_.erase({q.gc_at, queue_id}) > 0;
+ CHECK(is_deleted);
+ }
+ q.gc_at = gc_at;
+ if (q.gc_at != 0) {
+ bool is_inserted = queue_gc_at_.emplace(gc_at, queue_id).second;
+ CHECK(is_inserted);
+ }
+ }
+};
+
+unique_ptr<TQueue> TQueue::create() {
+ return make_unique<TQueueImpl>();
+}
+
+struct TQueueLogEvent final : public Storer {
+ int64 queue_id;
+ int32 event_id;
+ int32 expires_at;
+ Slice data;
+ int64 extra;
+
+ template <class StorerT>
+ void store(StorerT &&storer) const {
+ using td::store;
+ store(queue_id, storer);
+ store(event_id, storer);
+ store(expires_at, storer);
+ store(data, storer);
+ if (extra != 0) {
+ store(extra, storer);
+ }
+ }
+
+ template <class ParserT>
+ void parse(ParserT &&parser, int32 has_extra) {
+ using td::parse;
+ parse(queue_id, parser);
+ parse(event_id, parser);
+ parse(expires_at, parser);
+ data = parser.template fetch_string<Slice>();
+ if (has_extra == 0) {
+ extra = 0;
+ } else {
+ parse(extra, parser);
+ }
+ }
+
+ size_t size() const final {
+ TlStorerCalcLength storer;
+ store(storer);
+ return storer.get_length();
+ }
+
+ size_t store(uint8 *ptr) const final {
+ TlStorerUnsafe storer(ptr);
+ store(storer);
+ return static_cast<size_t>(storer.get_buf() - ptr);
+ }
+};
+
+template <class BinlogT>
+uint64 TQueueBinlog<BinlogT>::push(QueueId queue_id, const RawEvent &event) {
+ TQueueLogEvent log_event;
+ log_event.queue_id = queue_id;
+ log_event.event_id = event.event_id.value();
+ log_event.expires_at = event.expires_at;
+ log_event.data = event.data;
+ log_event.extra = event.extra;
+ auto magic = BINLOG_EVENT_TYPE + (log_event.extra != 0);
+ if (event.log_event_id == 0) {
+ return binlog_->add(magic, log_event);
+ }
+ binlog_->rewrite(event.log_event_id, magic, log_event);
+ return event.log_event_id;
+}
+
+template <class BinlogT>
+void TQueueBinlog<BinlogT>::pop(uint64 log_event_id) {
+ binlog_->erase(log_event_id);
+}
+
+template <class BinlogT>
+Status TQueueBinlog<BinlogT>::replay(const BinlogEvent &binlog_event, TQueue &q) const {
+ TQueueLogEvent event;
+ TlParser parser(binlog_event.data_);
+ int32 has_extra = binlog_event.type_ - BINLOG_EVENT_TYPE;
+ if (has_extra != 0 && has_extra != 1) {
+ return Status::Error("Wrong magic");
+ }
+ event.parse(parser, has_extra);
+ parser.fetch_end();
+ TRY_STATUS(parser.get_status());
+ TRY_RESULT(event_id, EventId::from_int32(event.event_id));
+ RawEvent raw_event;
+ raw_event.log_event_id = binlog_event.id_;
+ raw_event.event_id = event_id;
+ raw_event.expires_at = event.expires_at;
+ raw_event.data = event.data.str();
+ raw_event.extra = event.extra;
+ if (!q.do_push(event.queue_id, std::move(raw_event))) {
+ return Status::Error("Failed to add event");
+ }
+ return Status::OK();
+}
+
+template <class BinlogT>
+void TQueueBinlog<BinlogT>::close(Promise<> promise) {
+ binlog_->close(std::move(promise));
+}
+
+template class TQueueBinlog<BinlogInterface>;
+template class TQueueBinlog<Binlog>;
+
+uint64 TQueueMemoryStorage::push(QueueId queue_id, const RawEvent &event) {
+ auto log_event_id = event.log_event_id == 0 ? next_log_event_id_++ : event.log_event_id;
+ events_[log_event_id] = std::make_pair(queue_id, event);
+ return log_event_id;
+}
+
+void TQueueMemoryStorage::pop(uint64 log_event_id) {
+ events_.erase(log_event_id);
+}
+
+void TQueueMemoryStorage::replay(TQueue &q) const {
+ for (auto &e : events_) {
+ auto x = e.second;
+ x.second.log_event_id = e.first;
+ bool is_added = q.do_push(x.first, std::move(x.second));
+ CHECK(is_added);
+ }
+}
+void TQueueMemoryStorage::close(Promise<> promise) {
+ events_.clear();
+ promise.set_value({});
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/TQueue.h b/protocols/Telegram/tdlib/td/tddb/td/db/TQueue.h
new file mode 100644
index 0000000000..117726851b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/TQueue.h
@@ -0,0 +1,155 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Span.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+#include <map>
+#include <memory>
+#include <utility>
+
+namespace td {
+
+class TQueue {
+ public:
+ class EventId {
+ public:
+ static constexpr int32 MAX_ID = 2000000000;
+
+ EventId();
+
+ static Result<EventId> from_int32(int32 id);
+
+ bool is_valid() const;
+
+ int32 value() const;
+
+ Result<EventId> next() const;
+
+ Result<EventId> advance(size_t offset) const;
+
+ bool empty() const;
+
+ bool operator==(const EventId &other) const;
+ bool operator!=(const EventId &other) const;
+ bool operator<(const EventId &other) const;
+
+ private:
+ int32 id_{0};
+
+ explicit EventId(int32 id);
+
+ static bool is_valid_id(int32 id);
+ };
+
+ struct Event {
+ EventId id;
+ int32 expires_at{0};
+ Slice data;
+ int64 extra{0};
+ };
+
+ struct RawEvent {
+ uint64 log_event_id{0};
+ EventId event_id;
+ string data;
+ int64 extra{0};
+ int32 expires_at{0};
+ };
+
+ using QueueId = int64;
+
+ class StorageCallback {
+ public:
+ using QueueId = TQueue::QueueId;
+ using RawEvent = TQueue::RawEvent;
+
+ StorageCallback() = default;
+ StorageCallback(const StorageCallback &) = delete;
+ StorageCallback &operator=(const StorageCallback &) = delete;
+ StorageCallback(StorageCallback &&) = delete;
+ StorageCallback &operator=(StorageCallback &&) = delete;
+ virtual ~StorageCallback() = default;
+
+ virtual uint64 push(QueueId queue_id, const RawEvent &event) = 0;
+ virtual void pop(uint64 log_event_id) = 0;
+ virtual void close(Promise<> promise) = 0;
+ };
+
+ static unique_ptr<TQueue> create();
+
+ TQueue() = default;
+ TQueue(const TQueue &) = delete;
+ TQueue &operator=(const TQueue &) = delete;
+ TQueue(TQueue &&) = delete;
+ TQueue &operator=(TQueue &&) = delete;
+
+ virtual ~TQueue() = default;
+
+ virtual void set_callback(unique_ptr<StorageCallback> callback) = 0;
+ virtual unique_ptr<StorageCallback> extract_callback() = 0;
+
+ virtual bool do_push(QueueId queue_id, RawEvent &&raw_event) = 0;
+
+ virtual Result<EventId> push(QueueId queue_id, string data, int32 expires_at, int64 extra, EventId hint_new_id) = 0;
+
+ virtual void forget(QueueId queue_id, EventId event_id) = 0;
+
+ virtual void clear(QueueId queue_id, size_t keep_count) = 0;
+
+ virtual EventId get_head(QueueId queue_id) const = 0;
+ virtual EventId get_tail(QueueId queue_id) const = 0;
+
+ virtual Result<size_t> get(QueueId queue_id, EventId from_id, bool forget_previous, int32 unix_time_now,
+ MutableSpan<Event> &result_events) = 0;
+
+ virtual size_t get_size(QueueId queue_id) const = 0;
+
+ // returns number of deleted events and whether garbage collection was completed
+ virtual std::pair<int64, bool> run_gc(int32 unix_time_now) = 0;
+ virtual void close(Promise<> promise) = 0;
+};
+
+StringBuilder &operator<<(StringBuilder &string_builder, TQueue::EventId id);
+
+struct BinlogEvent;
+
+template <class BinlogT>
+class TQueueBinlog final : public TQueue::StorageCallback {
+ public:
+ uint64 push(QueueId queue_id, const RawEvent &event) final;
+ void pop(uint64 log_event_id) final;
+ Status replay(const BinlogEvent &binlog_event, TQueue &q) const TD_WARN_UNUSED_RESULT;
+
+ void set_binlog(std::shared_ptr<BinlogT> binlog) {
+ binlog_ = std::move(binlog);
+ }
+ void close(Promise<> promise) final;
+
+ private:
+ std::shared_ptr<BinlogT> binlog_;
+ static constexpr int32 BINLOG_EVENT_TYPE = 2314;
+};
+
+class TQueueMemoryStorage final : public TQueue::StorageCallback {
+ public:
+ uint64 push(QueueId queue_id, const RawEvent &event) final;
+ void pop(uint64 log_event_id) final;
+ void replay(TQueue &q) const;
+ void close(Promise<> promise) final;
+
+ private:
+ uint64 next_log_event_id_{1};
+ std::map<uint64, std::pair<QueueId, RawEvent>> events_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/TsSeqKeyValue.h b/protocols/Telegram/tdlib/td/tddb/td/db/TsSeqKeyValue.h
index 8d94d79673..5663bcdfff 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/TsSeqKeyValue.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/TsSeqKeyValue.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,6 +8,7 @@
#include "td/db/SeqKeyValue.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/port/RwMutex.h"
#include "td/utils/Slice.h"
@@ -15,6 +16,7 @@
#include <utility>
namespace td {
+
class TsSeqKeyValue {
public:
using SeqNo = SeqKeyValue::SeqNo;
@@ -32,30 +34,42 @@ class TsSeqKeyValue {
auto lock = rw_mutex_.lock_write().move_as_ok();
return kv_.set(key, value);
}
+
std::pair<SeqNo, RwMutex::WriteLock> set_and_lock(Slice key, Slice value) {
auto lock = rw_mutex_.lock_write().move_as_ok();
return std::make_pair(kv_.set(key, value), std::move(lock));
}
+
SeqNo erase(const string &key) {
auto lock = rw_mutex_.lock_write().move_as_ok();
return kv_.erase(key);
}
+
std::pair<SeqNo, RwMutex::WriteLock> erase_and_lock(const string &key) {
auto lock = rw_mutex_.lock_write().move_as_ok();
return std::make_pair(kv_.erase(key), std::move(lock));
}
- string get(const string &key) {
+
+ string get(const string &key) const {
auto lock = rw_mutex_.lock_read().move_as_ok();
return kv_.get(key);
}
+
+ bool isset(const string &key) const {
+ auto lock = rw_mutex_.lock_read().move_as_ok();
+ return kv_.isset(key);
+ }
+
size_t size() const {
return kv_.size();
}
- std::unordered_map<string, string> get_all() {
+
+ std::unordered_map<string, string, Hash<string>> get_all() const {
auto lock = rw_mutex_.lock_write().move_as_ok();
return kv_.get_all();
}
- // not thread safe method
+
+ // non-thread-safe method
SeqKeyValue &inner() {
return kv_;
}
@@ -65,7 +79,8 @@ class TsSeqKeyValue {
}
private:
- RwMutex rw_mutex_;
+ mutable RwMutex rw_mutex_;
SeqKeyValue kv_;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.cpp
index 5d76028dba..5b14eed69d 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,14 +11,16 @@
#include "td/utils/buffer.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/Clocks.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h"
+#include "td/utils/port/PollFlags.h"
+#include "td/utils/port/sleep.h"
#include "td/utils/port/Stat.h"
#include "td/utils/Random.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/Time.h"
#include "td/utils/tl_helpers.h"
@@ -53,7 +55,7 @@ struct AesCtrEncryptionEvent {
BufferSlice iv_;
BufferSlice key_hash_;
- BufferSlice generate_key(const DbKey &db_key) {
+ BufferSlice generate_key(const DbKey &db_key) const {
CHECK(!db_key.is_empty());
BufferSlice key(key_size());
size_t iteration_count = kdf_iteration_count();
@@ -63,7 +65,8 @@ struct AesCtrEncryptionEvent {
pbkdf2_sha256(db_key.data(), key_salt_.as_slice(), narrow_cast<int>(iteration_count), key.as_slice());
return key;
}
- BufferSlice generate_hash(Slice key) {
+
+ static BufferSlice generate_hash(Slice key) {
BufferSlice hash(hash_size());
hmac_sha256(key, "cucumbers everywhere", hash.as_slice());
return hash;
@@ -91,18 +94,23 @@ struct AesCtrEncryptionEvent {
class BinlogReader {
public:
- BinlogReader() = default;
explicit BinlogReader(ChainBufferReader *input) : input_(input) {
}
- void set_input(ChainBufferReader *input) {
+ void set_input(ChainBufferReader *input, bool is_encrypted, int64 expected_size) {
input_ = input;
+ is_encrypted_ = is_encrypted;
+ expected_size_ = expected_size;
+ }
+
+ ChainBufferReader *input() {
+ return input_;
}
- int64 offset() {
+ int64 offset() const {
return offset_;
}
Result<size_t> read_next(BinlogEvent *event) {
- if (state_ == ReadLength) {
+ if (state_ == State::ReadLength) {
if (input_->size() < 4) {
return 4;
}
@@ -112,35 +120,52 @@ class BinlogReader {
it.advance(4, MutableSlice(buf, 4));
size_ = static_cast<size_t>(TlParser(Slice(buf, 4)).fetch_int());
- if (size_ > MAX_EVENT_SIZE) {
+ if (size_ > BinlogEvent::MAX_SIZE) {
return Status::Error(PSLICE() << "Too big event " << tag("size", size_));
}
- if (size_ < MIN_EVENT_SIZE) {
+ if (size_ < BinlogEvent::MIN_SIZE) {
return Status::Error(PSLICE() << "Too small event " << tag("size", size_));
}
- state_ = ReadEvent;
+ if (size_ % 4 != 0) {
+ return Status::Error(-2, PSLICE() << "Event of size " << size_ << " at offset " << offset() << " out of "
+ << expected_size_ << ' ' << tag("is_encrypted", is_encrypted_)
+ << format::as_hex_dump<4>(Slice(input_->prepare_read().truncate(28))));
+ }
+ state_ = State::ReadEvent;
}
if (input_->size() < size_) {
return size_;
}
+ event->debug_info_ = BinlogDebugInfo{__FILE__, __LINE__};
TRY_STATUS(event->init(input_->cut_head(size_).move_as_buffer_slice()));
offset_ += size_;
event->offset_ = offset_;
- state_ = ReadLength;
+ state_ = State::ReadLength;
return 0;
}
private:
ChainBufferReader *input_;
- enum { ReadLength, ReadEvent } state_ = ReadLength;
+ enum class State { ReadLength, ReadEvent };
+ State state_ = State::ReadLength;
size_t size_{0};
int64 offset_{0};
+ int64 expected_size_{0};
+ bool is_encrypted_{false};
};
+
+static int64 file_size(CSlice path) {
+ auto r_stat = stat(path);
+ if (r_stat.is_error()) {
+ return 0;
+ }
+ return r_stat.ok().size_;
+}
} // namespace detail
-bool Binlog::IGNORE_ERASE_HACK = false;
+int32 VERBOSITY_NAME(binlog) = VERBOSITY_NAME(DEBUG) + 8;
Binlog::Binlog() = default;
@@ -148,9 +173,9 @@ Binlog::~Binlog() {
close().ignore();
}
-Result<FileFd> Binlog::open_binlog(CSlice path, int32 flags) {
+Result<FileFd> Binlog::open_binlog(const string &path, int32 flags) {
TRY_RESULT(fd, FileFd::open(path, flags));
- TRY_STATUS(fd.lock(FileFd::LockFlags::Write, 100));
+ TRY_STATUS(fd.lock(FileFd::LockFlags::Write, path, 100));
return std::move(fd);
}
@@ -161,9 +186,9 @@ Status Binlog::init(string path, const Callback &callback, DbKey db_key, DbKey o
db_key_ = std::move(db_key);
old_db_key_ = std::move(old_db_key);
- processor_ = std::make_unique<detail::BinlogEventsProcessor>();
+ processor_ = make_unique<detail::BinlogEventsProcessor>();
// Turn off BinlogEventsBuffer
- // events_buffer_ = std::make_unique<detail::BinlogEventsBuffer>();
+ // events_buffer_ = make_unique<detail::BinlogEventsBuffer>();
// try to restore binlog from regenerated version
if (stat(path).is_error()) {
@@ -200,6 +225,10 @@ Status Binlog::init(string path, const Callback &callback, DbKey db_key, DbKey o
}
void Binlog::add_event(BinlogEvent &&event) {
+ if (event.size_ % 4 != 0) {
+ LOG(FATAL) << "Trying to add event with bad size " << event.public_to_string();
+ }
+
if (!events_buffer_) {
do_add_event(std::move(event));
} else {
@@ -215,7 +244,7 @@ void Binlog::add_event(BinlogEvent &&event) {
auto need_reindex = [&](int64 min_size, int rate) {
return fd_size > min_size && fd_size / rate > processor_->total_raw_events_size();
};
- if (need_reindex(100000, 5) || need_reindex(500000, 2)) {
+ if (need_reindex(50000, 5) || need_reindex(100000, 4) || need_reindex(300000, 3) || need_reindex(500000, 2)) {
LOG(INFO) << tag("fd_size", format::as_size(fd_size))
<< tag("total events size", format::as_size(processor_->total_raw_events_size()));
do_reindex();
@@ -254,20 +283,25 @@ Status Binlog::close(bool need_sync) {
if (fd_.empty()) {
return Status::OK();
}
- SCOPE_EXIT {
- path_ = "";
- info_.is_opened = false;
- fd_.close();
- need_sync_ = false;
- };
if (need_sync) {
sync();
} else {
flush();
}
+
+ fd_.lock(FileFd::LockFlags::Unlock, path_, 1).ensure();
+ fd_.close();
+ path_.clear();
+ info_.is_opened = false;
+ need_sync_ = false;
return Status::OK();
}
+void Binlog::close(Promise<> promise) {
+ TRY_STATUS_PROMISE(promise, close());
+ promise.set_value({});
+}
+
void Binlog::change_key(DbKey new_db_key) {
db_key_ = std::move(new_db_key);
aes_ctr_key_salt_ = BufferSlice();
@@ -280,18 +314,24 @@ Status Binlog::close_and_destroy() {
destroy(path).ignore();
return close_status;
}
+
Status Binlog::destroy(Slice path) {
+ unlink(PSLICE() << path << ".new").ignore(); // delete regenerated version first to avoid it becoming main version
unlink(PSLICE() << path).ignore();
- unlink(PSLICE() << path << ".new").ignore();
return Status::OK();
}
void Binlog::do_event(BinlogEvent &&event) {
- fd_events_++;
- fd_size_ += event.raw_event_.size();
+ auto event_size = event.raw_event_.size();
if (state_ == State::Run || state_ == State::Reindex) {
- VLOG(binlog) << "Write binlog event: " << format::cond(state_ == State::Reindex, "[reindex] ") << event;
+ auto validate_status = event.validate();
+ if (validate_status.is_error()) {
+ LOG(FATAL) << "Failed to validate binlog event " << validate_status << " "
+ << format::as_hex_dump<4>(Slice(event.raw_event_.as_slice().truncate(28)));
+ }
+ VLOG(binlog) << "Write binlog event: " << format::cond(state_ == State::Reindex, "[reindex] ")
+ << event.public_to_string();
switch (encryption_type_) {
case EncryptionType::None: {
buffer_writer_.append(event.raw_event_.clone());
@@ -311,16 +351,18 @@ void Binlog::do_event(BinlogEvent &&event) {
BufferSlice key;
if (aes_ctr_key_salt_.as_slice() == encryption_event.key_salt_.as_slice()) {
- key = BufferSlice(Slice(aes_ctr_key_.raw, sizeof(aes_ctr_key_.raw)));
+ key = BufferSlice(as_slice(aes_ctr_key_));
} else if (!db_key_.is_empty()) {
key = encryption_event.generate_key(db_key_);
}
- if (encryption_event.generate_hash(key.as_slice()).as_slice() != encryption_event.key_hash_.as_slice()) {
+ if (detail::AesCtrEncryptionEvent::generate_hash(key.as_slice()).as_slice() !=
+ encryption_event.key_hash_.as_slice()) {
CHECK(state_ == State::Load);
if (!old_db_key_.is_empty()) {
key = encryption_event.generate_key(old_db_key_);
- if (encryption_event.generate_hash(key.as_slice()).as_slice() != encryption_event.key_hash_.as_slice()) {
+ if (detail::AesCtrEncryptionEvent::generate_hash(key.as_slice()).as_slice() !=
+ encryption_event.key_hash_.as_slice()) {
info_.wrong_password = true;
}
} else {
@@ -344,13 +386,31 @@ void Binlog::do_event(BinlogEvent &&event) {
update_write_encryption();
//LOG(INFO) << format::cond(state_ == State::Run, "Run", "Reindex") << ": init encryption";
}
- return;
}
}
if (state_ != State::Reindex) {
- processor_->add_event(std::move(event));
+ auto status = processor_->add_event(std::move(event));
+ if (status.is_error()) {
+ auto old_size = detail::file_size(path_);
+ auto data = debug_get_binlog_data(fd_size_, old_size);
+ if (state_ == State::Load) {
+ fd_.seek(fd_size_).ensure();
+ fd_.truncate_to_current_position(fd_size_).ensure();
+
+ if (data.empty()) {
+ return;
+ }
+ }
+
+ LOG(FATAL) << "Truncate binlog \"" << path_ << "\" from size " << old_size << " to size " << fd_size_
+ << " in state " << static_cast<int32>(state_) << " due to error: " << status << " after reading "
+ << data;
+ }
}
+
+ fd_events_++;
+ fd_size_ += event_size;
}
void Binlog::sync() {
@@ -396,7 +456,9 @@ void Binlog::update_read_encryption() {
CHECK(binlog_reader_ptr_);
switch (encryption_type_) {
case EncryptionType::None: {
- binlog_reader_ptr_->set_input(&buffer_reader_);
+ auto r_file_size = fd_.get_size();
+ r_file_size.ensure();
+ binlog_reader_ptr_->set_input(&buffer_reader_, false, r_file_size.ok());
byte_flow_flag_ = false;
break;
}
@@ -407,7 +469,9 @@ void Binlog::update_read_encryption() {
byte_flow_sink_ = ByteFlowSink();
byte_flow_source_ >> aes_xcode_byte_flow_ >> byte_flow_sink_;
byte_flow_flag_ = true;
- binlog_reader_ptr_->set_input(byte_flow_sink_.get_output());
+ auto r_file_size = fd_.get_size();
+ r_file_size.ensure();
+ binlog_reader_ptr_->set_input(byte_flow_sink_.get_output(), true, r_file_size.ok());
break;
}
}
@@ -439,67 +503,70 @@ Status Binlog::load_binlog(const Callback &callback, const Callback &debug_callb
buffer_writer_ = ChainBufferWriter();
buffer_reader_ = buffer_writer_.extract_reader();
fd_.set_input_writer(&buffer_writer_);
- detail::BinlogReader reader;
+ detail::BinlogReader reader{nullptr};
binlog_reader_ptr_ = &reader;
update_read_encryption();
- bool ready_flag = false;
- fd_.update_flags(Fd::Flag::Read);
+ fd_.get_poll_info().add_flags(PollFlags::Read());
info_.wrong_password = false;
while (true) {
BinlogEvent event;
auto r_need_size = reader.read_next(&event);
if (r_need_size.is_error()) {
+ if (r_need_size.error().code() == -2) {
+ auto old_size = detail::file_size(path_);
+ auto offset = reader.offset();
+ auto data = debug_get_binlog_data(offset, old_size);
+ fd_.seek(offset).ensure();
+ fd_.truncate_to_current_position(offset).ensure();
+ if (data.empty()) {
+ break;
+ }
+ LOG(FATAL) << "Truncate binlog \"" << path_ << "\" from size " << old_size << " to size " << offset
+ << " due to error: " << r_need_size.error() << " after reading " << data;
+ }
LOG(ERROR) << r_need_size.error();
break;
}
auto need_size = r_need_size.move_as_ok();
- // LOG(ERROR) << "need size = " << need_size;
+ // LOG(ERROR) << "Need size = " << need_size;
if (need_size == 0) {
- if (IGNORE_ERASE_HACK && event.type_ == BinlogEvent::ServiceTypes::Empty &&
- (event.flags_ & BinlogEvent::Flags::Rewrite) != 0) {
- // skip erase
- } else {
- if (debug_callback) {
- debug_callback(event);
- }
- do_add_event(std::move(event));
- if (info_.wrong_password) {
- return Status::OK();
- }
+ if (debug_callback) {
+ debug_callback(event);
}
- ready_flag = false;
- } else {
- // TODO(now): fix bug
- if (ready_flag) {
- break;
+ do_add_event(std::move(event));
+ if (info_.wrong_password) {
+ return Status::OK();
}
+ } else {
TRY_STATUS(fd_.flush_read(max(need_size, static_cast<size_t>(4096))));
buffer_reader_.sync_with_writer();
if (byte_flow_flag_) {
byte_flow_source_.wakeup();
}
- ready_flag = true;
+ if (reader.input()->size() < need_size) {
+ break;
+ }
}
}
auto offset = processor_->offset();
processor_->for_each([&](BinlogEvent &event) {
- VLOG(binlog) << "Replay binlog event: " << event;
+ VLOG(binlog) << "Replay binlog event: " << event.public_to_string();
if (callback) {
callback(event);
}
});
- auto fd_size = fd_.get_size();
+ TRY_RESULT(fd_size, fd_.get_size());
if (offset != fd_size) {
LOG(ERROR) << "Truncate " << tag("path", path_) << tag("old_size", fd_size) << tag("new_size", offset);
fd_.seek(offset).ensure();
fd_.truncate_to_current_position(offset).ensure();
db_key_used_ = false; // force reindex
}
- CHECK(IGNORE_ERASE_HACK || fd_size_ == offset) << fd_size << " " << fd_size_ << " " << offset;
+ LOG_CHECK(fd_size_ == offset) << fd_size << " " << fd_size_ << " " << offset;
binlog_reader_ptr_ = nullptr;
state_ = State::Run;
@@ -515,19 +582,11 @@ Status Binlog::load_binlog(const Callback &callback, const Callback &debug_callb
return Status::OK();
}
-static int64 file_size(CSlice path) {
- auto r_stat = stat(path);
- if (r_stat.is_error()) {
- return 0;
- }
- return r_stat.ok().size_;
-}
-
void Binlog::update_encryption(Slice key, Slice iv) {
- MutableSlice(aes_ctr_key_.raw, sizeof(aes_ctr_key_.raw)).copy_from(key);
+ as_slice(aes_ctr_key_).copy_from(key);
UInt128 aes_ctr_iv;
- MutableSlice(aes_ctr_iv.raw, sizeof(aes_ctr_iv.raw)).copy_from(iv);
- aes_ctr_state_.init(aes_ctr_key_, aes_ctr_iv);
+ as_slice(aes_ctr_iv).copy_from(iv);
+ aes_ctr_state_.init(as_slice(aes_ctr_key_), as_slice(aes_ctr_iv));
}
void Binlog::reset_encryption() {
@@ -550,15 +609,16 @@ void Binlog::reset_encryption() {
BufferSlice key;
if (aes_ctr_key_salt_.as_slice() == event.key_salt_.as_slice()) {
- key = BufferSlice(Slice(aes_ctr_key_.raw, sizeof(aes_ctr_key_.raw)));
+ key = BufferSlice(as_slice(aes_ctr_key_));
} else {
key = event.generate_key(db_key_);
}
- event.key_hash_ = event.generate_hash(key.as_slice());
+ event.key_hash_ = EncryptionEvent::generate_hash(key.as_slice());
do_event(BinlogEvent(
- BinlogEvent::create_raw(0, BinlogEvent::ServiceTypes::AesCtrEncryption, 0, create_default_storer(event))));
+ BinlogEvent::create_raw(0, BinlogEvent::ServiceTypes::AesCtrEncryption, 0, create_default_storer(event)),
+ BinlogDebugInfo{__FILE__, __LINE__}));
}
void Binlog::do_reindex() {
@@ -571,7 +631,7 @@ void Binlog::do_reindex() {
};
auto start_time = Clocks::monotonic();
- auto start_size = file_size(path_);
+ auto start_size = detail::file_size(path_);
auto start_events = fd_events_;
string new_path = path_ + ".new";
@@ -581,7 +641,7 @@ void Binlog::do_reindex() {
LOG(ERROR) << "Can't open new binlog for regenerate: " << r_opened_file.error();
return;
}
- fd_.close();
+ auto old_fd = std::move(fd_); // can't close fd_ now, because it will release file lock
fd_ = BufferedFdBase<FileFd>(r_opened_file.move_as_ok());
buffer_writer_ = ChainBufferWriter();
@@ -594,27 +654,49 @@ void Binlog::do_reindex() {
fd_events_ = 0;
reset_encryption();
processor_->for_each([&](BinlogEvent &event) {
+ event.realloc();
do_event(std::move(event)); // NB: no move is actually happens
});
- need_sync_ = true; // must sync creation of the file
+ need_sync_ = start_size != 0; // must sync creation of the file if it is non-empty
sync();
// finish_reindex
auto status = unlink(path_);
LOG_IF(FATAL, status.is_error()) << "Failed to unlink old binlog: " << status;
+ old_fd.close(); // now we can close old file and release the system lock
status = rename(new_path, path_);
+ FileFd::remove_local_lock(new_path); // now we can release local lock for temporary file
LOG_IF(FATAL, status.is_error()) << "Failed to rename binlog: " << status;
auto finish_time = Clocks::monotonic();
auto finish_size = fd_size_;
auto finish_events = fd_events_;
- CHECK(fd_size_ == file_size(path_));
+ for (int left_tries = 10; left_tries > 0; left_tries--) {
+ auto r_stat = stat(path_);
+ if (r_stat.is_error()) {
+ if (left_tries != 1) {
+ usleep_for(200000 / left_tries);
+ continue;
+ }
+ LOG(FATAL) << "Failed to rename binlog of size " << fd_size_ << " to " << path_ << ": " << r_stat.error()
+ << ". Temp file size is " << detail::file_size(new_path) << ", new size " << detail::file_size(path_);
+ }
+ LOG_CHECK(fd_size_ == r_stat.ok().size_) << fd_size_ << ' ' << r_stat.ok().size_ << ' '
+ << detail::file_size(new_path) << ' ' << fd_events_ << ' ' << path_;
+ break;
+ }
- // TODO: print warning only if time or ratio is suspicious
- double ratio = static_cast<double>(start_size) / static_cast<double>(finish_size + 1);
- LOG(INFO) << "regenerate index " << tag("name", path_) << tag("time", format::as_time(finish_time - start_time))
- << tag("before_size", format::as_size(start_size)) << tag("after_size", format::as_size(finish_size))
- << tag("ratio", ratio) << tag("before_events", start_events) << tag("after_events", finish_events);
+ auto ratio = static_cast<double>(start_size) / static_cast<double>(finish_size + 1);
+
+ [&](Slice msg) {
+ if (start_size > (10 << 20) || finish_time - start_time > 1) {
+ LOG(WARNING) << "Slow " << msg;
+ } else {
+ LOG(INFO) << msg;
+ }
+ }(PSLICE() << "Regenerate index " << tag("name", path_) << tag("time", format::as_time(finish_time - start_time))
+ << tag("before_size", format::as_size(start_size)) << tag("after_size", format::as_size(finish_size))
+ << tag("ratio", ratio) << tag("before_events", start_events) << tag("after_events", finish_events));
buffer_writer_ = ChainBufferWriter();
buffer_reader_ = buffer_writer_.extract_reader();
@@ -626,4 +708,58 @@ void Binlog::do_reindex() {
update_write_encryption();
}
+string Binlog::debug_get_binlog_data(int64 begin_offset, int64 end_offset) {
+ if (begin_offset > end_offset) {
+ return "Begin offset is bigger than end_offset";
+ }
+ if (begin_offset == end_offset) {
+ return string();
+ }
+
+ static int64 MAX_DATA_LENGTH = 512;
+ if (end_offset - begin_offset > MAX_DATA_LENGTH) {
+ end_offset = begin_offset + MAX_DATA_LENGTH;
+ }
+
+ auto r_fd = FileFd::open(path_, FileFd::Flags::Read);
+ if (r_fd.is_error()) {
+ return PSTRING() << "Failed to open binlog: " << r_fd.error();
+ }
+ auto fd = r_fd.move_as_ok();
+
+ fd_.lock(FileFd::LockFlags::Unlock, path_, 1).ignore();
+ SCOPE_EXIT {
+ fd_.lock(FileFd::LockFlags::Write, path_, 1).ensure();
+ };
+ auto expected_data_length = narrow_cast<size_t>(end_offset - begin_offset);
+ string data(expected_data_length, '\0');
+ auto r_data_size = fd.pread(data, begin_offset);
+ if (r_data_size.is_error()) {
+ return PSTRING() << "Failed to read binlog: " << r_data_size.error();
+ }
+
+ if (r_data_size.ok() < expected_data_length) {
+ data.resize(r_data_size.ok());
+ data = PSTRING() << format::as_hex_dump<4>(Slice(data)) << " | with " << expected_data_length - r_data_size.ok()
+ << " missed bytes";
+ } else {
+ if (encryption_type_ == EncryptionType::AesCtr) {
+ bool is_zero = true;
+ for (auto &c : data) {
+ if (c != '\0') {
+ is_zero = false;
+ }
+ }
+ // very often we have '\0' bytes written to disk instead of a real log event
+ // this is clearly impossible content for a real encrypted log event, so just ignore it
+ if (is_zero) {
+ return string();
+ }
+ }
+
+ data = PSTRING() << format::as_hex_dump<4>(Slice(data));
+ }
+ return data;
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.h b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.h
index cdda868b4e..88dbacf58f 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/Binlog.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,16 +15,23 @@
#include "td/utils/ByteFlow.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
+#include "td/utils/logging.h"
#include "td/utils/port/FileFd.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/StorerBase.h"
+#include "td/utils/UInt.h"
#include <functional>
namespace td {
+
+extern int32 VERBOSITY_NAME(binlog);
+
struct BinlogInfo {
- bool was_created;
- uint64 last_id;
+ bool was_created{false};
+ uint64 last_id{0};
bool is_encrypted{false};
bool wrong_password{false};
bool is_opened{false};
@@ -34,12 +41,11 @@ namespace detail {
class BinlogReader;
class BinlogEventsProcessor;
class BinlogEventsBuffer;
-}; // namespace detail
+} // namespace detail
class Binlog {
public:
enum Error : int { WrongPassword = -1 };
- static bool IGNORE_ERASE_HACK;
Binlog();
Binlog(const Binlog &other) = delete;
Binlog &operator=(const Binlog &other) = delete;
@@ -67,8 +73,28 @@ class Binlog {
return fd_.empty();
}
- void add_raw_event(BufferSlice &&raw_event) {
- add_event(BinlogEvent(std::move(raw_event)));
+ uint64 add(int32 type, const Storer &storer) {
+ auto log_event_id = next_id();
+ add_raw_event(BinlogEvent::create_raw(log_event_id, type, 0, storer), {});
+ return log_event_id;
+ }
+
+ uint64 rewrite(uint64 log_event_id, int32 type, const Storer &storer) {
+ auto seq_no = next_id();
+ add_raw_event(BinlogEvent::create_raw(log_event_id, type, BinlogEvent::Flags::Rewrite, storer), {});
+ return seq_no;
+ }
+
+ uint64 erase(uint64 log_event_id) {
+ auto seq_no = next_id();
+ add_raw_event(BinlogEvent::create_raw(log_event_id, BinlogEvent::ServiceTypes::Empty, BinlogEvent::Flags::Rewrite,
+ EmptyStorer()),
+ {});
+ return seq_no;
+ }
+
+ void add_raw_event(BufferSlice &&raw_event, BinlogDebugInfo info) {
+ add_event(BinlogEvent(std::move(raw_event), info));
}
void add_event(BinlogEvent &&event);
@@ -81,6 +107,7 @@ class Binlog {
void change_key(DbKey new_db_key);
Status close(bool need_sync = true) TD_WARN_UNUSED_RESULT;
+ void close(Promise<> promise);
Status close_and_destroy() TD_WARN_UNUSED_RESULT;
static Status destroy(Slice path) TD_WARN_UNUSED_RESULT;
@@ -96,7 +123,7 @@ class Binlog {
BufferedFdBase<FileFd> fd_;
ChainBufferWriter buffer_writer_;
ChainBufferReader buffer_reader_;
- detail::BinlogReader *binlog_reader_ptr_;
+ detail::BinlogReader *binlog_reader_ptr_ = nullptr;
BinlogInfo info_;
DbKey db_key_;
@@ -118,17 +145,15 @@ class Binlog {
uint64 fd_events_{0};
string path_;
std::vector<BinlogEvent> pending_events_;
- std::unique_ptr<detail::BinlogEventsProcessor> processor_;
- std::unique_ptr<detail::BinlogEventsBuffer> events_buffer_;
+ unique_ptr<detail::BinlogEventsProcessor> processor_;
+ unique_ptr<detail::BinlogEventsBuffer> events_buffer_;
bool in_flush_events_buffer_{false};
uint64 last_id_{0};
double need_flush_since_ = 0;
bool need_sync_{false};
enum class State { Empty, Load, Reindex, Run } state_{State::Empty};
- static constexpr uint32 MAX_EVENT_SIZE = 65536;
-
- Result<FileFd> open_binlog(CSlice path, int32 flags);
+ static Result<FileFd> open_binlog(const string &path, int32 flags);
size_t flush_events_buffer(bool force);
void do_add_event(BinlogEvent &&event);
void do_event(BinlogEvent &&event);
@@ -139,5 +164,8 @@ class Binlog {
void reset_encryption();
void update_read_encryption();
void update_write_encryption();
+
+ string debug_get_binlog_data(int64 begin_offset, int64 end_offset);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.cpp
index e4584e920e..09f9fc40e3 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.cpp
@@ -1,38 +1,78 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/db/binlog/BinlogEvent.h"
+#include "td/utils/crypto.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/tl_parsers.h"
+#include "td/utils/tl_storers.h"
namespace td {
-int32 VERBOSITY_NAME(binlog) = VERBOSITY_NAME(DEBUG) + 8;
Status BinlogEvent::init(BufferSlice &&raw_event, bool check_crc) {
TlParser parser(raw_event.as_slice());
size_ = parser.fetch_int();
- CHECK(size_ == raw_event.size());
+ LOG_CHECK(size_ == raw_event.size()) << size_ << " " << raw_event.size() << debug_info_;
id_ = parser.fetch_long();
type_ = parser.fetch_int();
flags_ = parser.fetch_int();
extra_ = parser.fetch_long();
- CHECK(size_ >= MIN_EVENT_SIZE);
- auto slice_data = parser.fetch_string_raw<Slice>(size_ - MIN_EVENT_SIZE);
+ CHECK(size_ >= MIN_SIZE);
+ auto slice_data = parser.fetch_string_raw<Slice>(size_ - MIN_SIZE);
data_ = MutableSlice(const_cast<char *>(slice_data.begin()), slice_data.size());
crc32_ = static_cast<uint32>(parser.fetch_int());
if (check_crc) {
- CHECK(size_ >= EVENT_TAIL_SIZE);
- auto calculated_crc = crc32(raw_event.as_slice().truncate(size_ - EVENT_TAIL_SIZE));
+ auto calculated_crc = crc32(raw_event.as_slice().substr(0, size_ - TAIL_SIZE));
if (calculated_crc != crc32_) {
return Status::Error(PSLICE() << "crc mismatch " << tag("actual", format::as_hex(calculated_crc))
- << tag("expected", format::as_hex(crc32_)));
+ << tag("expected", format::as_hex(crc32_)) << public_to_string());
}
}
raw_event_ = std::move(raw_event);
return Status::OK();
}
+Status BinlogEvent::validate() const {
+ BinlogEvent event;
+ if (raw_event_.size() < 4) {
+ return Status::Error("Too small event");
+ }
+ uint32 size = TlParser(raw_event_.as_slice().substr(0, 4)).fetch_int();
+ if (size_ != size) {
+ return Status::Error(PSLICE() << "Size of event changed: " << tag("was", size_) << tag("now", size));
+ }
+ return event.init(raw_event_.clone(), true);
+}
+
+BufferSlice BinlogEvent::create_raw(uint64 id, int32 type, int32 flags, const Storer &storer) {
+ auto raw_event = BufferSlice{storer.size() + MIN_SIZE};
+
+ TlStorerUnsafe tl_storer(raw_event.as_slice().ubegin());
+ tl_storer.store_int(narrow_cast<int32>(raw_event.size()));
+ tl_storer.store_long(id);
+ tl_storer.store_int(type);
+ tl_storer.store_int(flags);
+ tl_storer.store_long(0);
+
+ CHECK(tl_storer.get_buf() == raw_event.as_slice().ubegin() + HEADER_SIZE);
+ tl_storer.store_storer(storer);
+
+ CHECK(tl_storer.get_buf() == raw_event.as_slice().uend() - TAIL_SIZE);
+ tl_storer.store_int(crc32(raw_event.as_slice().truncate(raw_event.size() - TAIL_SIZE)));
+
+ return raw_event;
+}
+
+void BinlogEvent::realloc() {
+ auto data_offset = data_.begin() - raw_event_.as_slice().begin();
+ auto data_size = data_.size();
+ raw_event_ = raw_event_.copy();
+ data_ = raw_event_.as_slice().substr(data_offset, data_size);
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.h b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.h
index 1874543dff..8fab08da98 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogEvent.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,17 +8,16 @@
#include "td/utils/buffer.h"
#include "td/utils/common.h"
-#include "td/utils/crypto.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
-#include "td/utils/misc.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/Storer.h"
+#include "td/utils/StorerBase.h"
#include "td/utils/StringBuilder.h"
-#include "td/utils/tl_storers.h"
namespace td {
+
struct EmptyStorerImpl {
EmptyStorerImpl() {
}
@@ -33,15 +32,27 @@ inline auto EmptyStorer() {
return create_default_storer(impl);
}
-static constexpr size_t MAX_EVENT_SIZE = 1 << 24;
-static constexpr size_t EVENT_HEADER_SIZE = 4 + 8 + 4 + 4 + 8;
-static constexpr size_t EVENT_TAIL_SIZE = 4;
-static constexpr size_t MIN_EVENT_SIZE = EVENT_HEADER_SIZE + EVENT_TAIL_SIZE;
+struct BinlogDebugInfo {
+ BinlogDebugInfo() = default;
+ BinlogDebugInfo(const char *file, int line) : file(file), line(line) {
+ }
+ const char *file{""};
+ int line{0};
+};
-extern int32 VERBOSITY_NAME(binlog);
+inline StringBuilder &operator<<(StringBuilder &sb, const BinlogDebugInfo &info) {
+ if (info.line == 0) {
+ return sb;
+ }
+ return sb << "[" << info.file << ":" << info.line << "]";
+}
-// TODO: smaller BinlogEvent
struct BinlogEvent {
+ static constexpr size_t MAX_SIZE = 1 << 24;
+ static constexpr size_t HEADER_SIZE = 4 + 8 + 4 + 4 + 8;
+ static constexpr size_t TAIL_SIZE = 4;
+ static constexpr size_t MIN_SIZE = HEADER_SIZE + TAIL_SIZE;
+
int64 offset_;
uint32 size_;
@@ -54,6 +65,8 @@ struct BinlogEvent {
BufferSlice raw_event_;
+ BinlogDebugInfo debug_info_;
+
enum ServiceTypes { Header = -1, Empty = -2, AesCtrEncryption = -3, NoEncryption = -4 };
enum Flags { Rewrite = 1, Partial = 2 };
@@ -65,6 +78,7 @@ struct BinlogEvent {
}
BinlogEvent clone() const {
BinlogEvent result;
+ result.debug_info_ = BinlogDebugInfo{__FILE__, __LINE__};
result.init(raw_event_.clone()).ensure();
return result;
}
@@ -74,36 +88,32 @@ struct BinlogEvent {
}
BinlogEvent() = default;
- explicit BinlogEvent(BufferSlice &&raw_event) {
+ //explicit BinlogEvent(BufferSlice &&raw_event) {
+ //init(std::move(raw_event), false).ensure();
+ //}
+ BinlogEvent(BufferSlice &&raw_event, BinlogDebugInfo info) {
+ debug_info_ = info;
init(std::move(raw_event), false).ensure();
}
+
Status init(BufferSlice &&raw_event, bool check_crc = true) TD_WARN_UNUSED_RESULT;
static BufferSlice create_raw(uint64 id, int32 type, int32 flags, const Storer &storer);
+
+ std::string public_to_string() const {
+ return PSTRING() << "LogEvent[" << tag("id", format::as_hex(id_)) << tag("type", type_) << tag("flags", flags_)
+ << tag("data", data_.size()) << "]" << debug_info_;
+ }
+
+ Status validate() const;
+
+ void realloc();
};
inline StringBuilder &operator<<(StringBuilder &sb, const BinlogEvent &event) {
return sb << "LogEvent[" << tag("id", format::as_hex(event.id_)) << tag("type", event.type_)
- << tag("flags", event.flags_) << tag("data", format::as_hex_dump<4>(event.data_)) << "]";
+ << tag("flags", event.flags_) << tag("data", format::as_hex_dump<4>(event.data_)) << "]"
+ << event.debug_info_;
}
-// Implementation
-inline BufferSlice BinlogEvent::create_raw(uint64 id, int32 type, int32 flags, const Storer &storer) {
- auto raw_event = BufferSlice{storer.size() + MIN_EVENT_SIZE};
-
- TlStorerUnsafe tl_storer(raw_event.as_slice().begin());
- tl_storer.store_int(narrow_cast<int32>(raw_event.size()));
- tl_storer.store_long(id);
- tl_storer.store_int(type);
- tl_storer.store_int(flags);
- tl_storer.store_long(0);
-
- CHECK(tl_storer.get_buf() == raw_event.as_slice().begin() + EVENT_HEADER_SIZE);
- tl_storer.store_storer(storer);
-
- CHECK(tl_storer.get_buf() == raw_event.as_slice().end() - EVENT_TAIL_SIZE);
- tl_storer.store_int(::td::crc32(raw_event.as_slice().truncate(raw_event.size() - EVENT_TAIL_SIZE)));
-
- return raw_event;
-}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogHelper.h b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogHelper.h
index 1224ac4ac2..66a65e060d 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogHelper.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogHelper.h
@@ -1,45 +1,32 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/PromiseFuture.h"
-
#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogInterface.h"
#include "td/utils/common.h"
-#include "td/utils/Storer.h"
+#include "td/utils/Promise.h"
+#include "td/utils/StorerBase.h"
namespace td {
-class BinlogHelper {
- public:
- template <class BinlogT, class StorerT>
- static uint64 add(const BinlogT &binlog_ptr, int32 type, const StorerT &storer, Promise<> promise = Promise<>()) {
- auto logevent_id = binlog_ptr->next_id();
- binlog_ptr->add_raw_event(logevent_id, BinlogEvent::create_raw(logevent_id, type, 0, storer), std::move(promise));
- return logevent_id;
- }
- template <class BinlogT, class StorerT>
- static uint64 rewrite(const BinlogT &binlog_ptr, uint64 logevent_id, int32 type, const StorerT &storer,
- Promise<> promise = Promise<>()) {
- auto seq_no = binlog_ptr->next_id();
- binlog_ptr->add_raw_event(seq_no, BinlogEvent::create_raw(logevent_id, type, BinlogEvent::Flags::Rewrite, storer),
- std::move(promise));
- return seq_no;
- }
+inline uint64 binlog_add(BinlogInterface *binlog_ptr, int32 type, const Storer &storer,
+ Promise<> promise = Promise<>()) {
+ return binlog_ptr->add(type, storer, std::move(promise));
+}
+
+inline uint64 binlog_rewrite(BinlogInterface *binlog_ptr, uint64 log_event_id, int32 type, const Storer &storer,
+ Promise<> promise = Promise<>()) {
+ return binlog_ptr->rewrite(log_event_id, type, storer, std::move(promise));
+}
+
+inline uint64 binlog_erase(BinlogInterface *binlog_ptr, uint64 log_event_id, Promise<> promise = Promise<>()) {
+ return binlog_ptr->erase(log_event_id, std::move(promise));
+}
- template <class BinlogT>
- static uint64 erase(const BinlogT &binlog_ptr, uint64 logevent_id, Promise<> promise = Promise<>()) {
- auto seq_no = binlog_ptr->next_id();
- binlog_ptr->add_raw_event(seq_no,
- BinlogEvent::create_raw(logevent_id, BinlogEvent::ServiceTypes::Empty,
- BinlogEvent::Flags::Rewrite, EmptyStorer()),
- std::move(promise));
- return seq_no;
- }
-};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogInterface.h b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogInterface.h
index 2360f3c480..b3ec7b119f 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogInterface.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/BinlogInterface.h
@@ -1,20 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/PromiseFuture.h"
-
#include "td/db/binlog/BinlogEvent.h"
#include "td/db/DbKey.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
+#include "td/utils/StorerBase.h"
namespace td {
+
class BinlogInterface {
public:
BinlogInterface() = default;
@@ -30,15 +31,41 @@ class BinlogInterface {
void close_and_destroy(Promise<> promise = {}) {
close_and_destroy_impl(std::move(promise));
}
+ void add_raw_event(BinlogDebugInfo info, uint64 id, BufferSlice &&raw_event, Promise<> promise = Promise<>()) {
+ add_raw_event_impl(id, std::move(raw_event), std::move(promise), info);
+ }
void add_raw_event(uint64 id, BufferSlice &&raw_event, Promise<> promise = Promise<>()) {
- add_raw_event_impl(id, std::move(raw_event), std::move(promise));
+ add_raw_event_impl(id, std::move(raw_event), std::move(promise), {});
}
void lazy_sync(Promise<> promise = Promise<>()) {
- add_raw_event_impl(next_id(), BufferSlice(), std::move(promise));
+ add_raw_event_impl(next_id(), BufferSlice(), std::move(promise), {});
}
+
+ uint64 add(int32 type, const Storer &storer, Promise<> promise = Promise<>()) {
+ auto log_event_id = next_id();
+ add_raw_event_impl(log_event_id, BinlogEvent::create_raw(log_event_id, type, 0, storer), std::move(promise), {});
+ return log_event_id;
+ }
+
+ uint64 rewrite(uint64 log_event_id, int32 type, const Storer &storer, Promise<> promise = Promise<>()) {
+ auto seq_no = next_id();
+ add_raw_event_impl(seq_no, BinlogEvent::create_raw(log_event_id, type, BinlogEvent::Flags::Rewrite, storer),
+ std::move(promise), {});
+ return seq_no;
+ }
+
+ uint64 erase(uint64 log_event_id, Promise<> promise = Promise<>()) {
+ auto seq_no = next_id();
+ add_raw_event_impl(seq_no,
+ BinlogEvent::create_raw(log_event_id, BinlogEvent::ServiceTypes::Empty,
+ BinlogEvent::Flags::Rewrite, EmptyStorer()),
+ std::move(promise), {});
+ return seq_no;
+ }
+
virtual void force_sync(Promise<> promise) = 0;
virtual void force_flush() = 0;
- virtual void change_key(DbKey db_key, Promise<> promise = Promise<>()) = 0;
+ virtual void change_key(DbKey db_key, Promise<> promise) = 0;
virtual uint64 next_id() = 0;
virtual uint64 next_id(int32 shift) = 0;
@@ -46,6 +73,7 @@ class BinlogInterface {
protected:
virtual void close_impl(Promise<> promise) = 0;
virtual void close_and_destroy_impl(Promise<> promise) = 0;
- virtual void add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise) = 0;
+ virtual void add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise, BinlogDebugInfo info) = 0;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.cpp
index aaf07f2967..16a4e2c010 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,37 +8,41 @@
#include "td/utils/logging.h"
#include "td/utils/OrderedEventsProcessor.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
#include <map>
namespace td {
namespace detail {
-class BinlogActor : public Actor {
+class BinlogActor final : public Actor {
public:
- BinlogActor(std::unique_ptr<Binlog> binlog, uint64 seq_no) : binlog_(std::move(binlog)), processor_(seq_no) {
+ BinlogActor(unique_ptr<Binlog> binlog, uint64 seq_no) : binlog_(std::move(binlog)), processor_(seq_no) {
}
void close(Promise<> promise) {
binlog_->close().ensure();
- promise.set_value(Unit());
- LOG(INFO) << "close: done";
+ LOG(INFO) << "Finished to close binlog";
stop();
+
+ promise.set_value(Unit()); // setting promise can complete closing and destroy the current actor context
}
void close_and_destroy(Promise<> promise) {
binlog_->close_and_destroy().ensure();
- promise.set_value(Unit());
- LOG(INFO) << "close_and_destroy: done";
+ LOG(INFO) << "Finished to destroy binlog";
stop();
+
+ promise.set_value(Unit()); // setting promise can complete closing and destroy the current actor context
}
struct Event {
BufferSlice raw_event;
Promise<> sync_promise;
+ BinlogDebugInfo debug_info;
};
- void add_raw_event(uint64 seq_no, BufferSlice &&raw_event, Promise<> &&promise) {
- processor_.add(seq_no, Event{std::move(raw_event), std::move(promise)}, [&](uint64 id, Event &&event) {
+ void add_raw_event(uint64 seq_no, BufferSlice &&raw_event, Promise<> &&promise, BinlogDebugInfo info) {
+ processor_.add(seq_no, Event{std::move(raw_event), std::move(promise), info}, [&](uint64 id, Event &&event) {
if (!event.raw_event.empty()) {
- do_add_raw_event(std::move(event.raw_event));
+ do_add_raw_event(std::move(event.raw_event), event.debug_info);
}
do_lazy_sync(std::move(event.sync_promise));
});
@@ -67,7 +71,7 @@ class BinlogActor : public Actor {
}
private:
- std::unique_ptr<Binlog> binlog_;
+ unique_ptr<Binlog> binlog_;
OrderedEventsProcessor<Event> processor_;
@@ -78,7 +82,7 @@ class BinlogActor : public Actor {
bool flush_flag_ = false;
double wakeup_at_ = 0;
- static constexpr int32 FLUSH_TIMEOUT = 1; // 1s
+ static constexpr double FLUSH_TIMEOUT = 0.001; // 1ms
void wakeup_after(double after) {
auto now = Time::now_cached();
@@ -92,8 +96,8 @@ class BinlogActor : public Actor {
}
}
- void do_add_raw_event(BufferSlice &&raw_event) {
- binlog_->add_raw_event(std::move(raw_event));
+ void do_add_raw_event(BufferSlice &&raw_event, BinlogDebugInfo info) {
+ binlog_->add_raw_event(std::move(raw_event), info);
}
void try_flush() {
@@ -138,7 +142,7 @@ class BinlogActor : public Actor {
}
}
- void timeout_expired() override {
+ void timeout_expired() final {
bool need_sync = lazy_sync_flag_ || force_sync_flag_;
lazy_sync_flag_ = false;
force_sync_flag_ = false;
@@ -148,10 +152,7 @@ class BinlogActor : public Actor {
if (need_sync) {
binlog_->sync();
// LOG(ERROR) << "BINLOG SYNC";
- for (auto &promise : sync_promises_) {
- promise.set_value(Unit());
- }
- sync_promises_.clear();
+ set_promises(sync_promises_);
} else if (need_flush) {
try_flush();
// LOG(ERROR) << "BINLOG FLUSH";
@@ -162,24 +163,24 @@ class BinlogActor : public Actor {
ConcurrentBinlog::ConcurrentBinlog() = default;
ConcurrentBinlog::~ConcurrentBinlog() = default;
-ConcurrentBinlog::ConcurrentBinlog(std::unique_ptr<Binlog> binlog, int scheduler_id) {
+ConcurrentBinlog::ConcurrentBinlog(unique_ptr<Binlog> binlog, int scheduler_id) {
init_impl(std::move(binlog), scheduler_id);
}
Result<BinlogInfo> ConcurrentBinlog::init(string path, const Callback &callback, DbKey db_key, DbKey old_db_key,
int scheduler_id) {
- auto binlog = std::make_unique<Binlog>();
+ auto binlog = make_unique<Binlog>();
TRY_STATUS(binlog->init(std::move(path), callback, std::move(db_key), std::move(old_db_key)));
auto info = binlog->get_info();
init_impl(std::move(binlog), scheduler_id);
return info;
}
-void ConcurrentBinlog::init_impl(std::unique_ptr<Binlog> binlog, int32 scheduler_id) {
+void ConcurrentBinlog::init_impl(unique_ptr<Binlog> binlog, int32 scheduler_id) {
path_ = binlog->get_path().str();
last_id_ = binlog->peek_next_id();
- binlog_actor_ =
- create_actor_on_scheduler<detail::BinlogActor>("Binlog " + path_, scheduler_id, std::move(binlog), last_id_);
+ binlog_actor_ = create_actor_on_scheduler<detail::BinlogActor>(PSLICE() << "Binlog " << path_, scheduler_id,
+ std::move(binlog), last_id_);
}
void ConcurrentBinlog::close_impl(Promise<> promise) {
@@ -188,8 +189,8 @@ void ConcurrentBinlog::close_impl(Promise<> promise) {
void ConcurrentBinlog::close_and_destroy_impl(Promise<> promise) {
send_closure(std::move(binlog_actor_), &detail::BinlogActor::close_and_destroy, std::move(promise));
}
-void ConcurrentBinlog::add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise) {
- send_closure(binlog_actor_, &detail::BinlogActor::add_raw_event, id, std::move(raw_event), std::move(promise));
+void ConcurrentBinlog::add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise, BinlogDebugInfo info) {
+ send_closure(binlog_actor_, &detail::BinlogActor::add_raw_event, id, std::move(raw_event), std::move(promise), info);
}
void ConcurrentBinlog::force_sync(Promise<> promise) {
send_closure(binlog_actor_, &detail::BinlogActor::force_sync, std::move(promise));
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.h b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.h
index ce77a78a84..ad6c9bc5c3 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/ConcurrentBinlog.h
@@ -1,19 +1,20 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
-
#include "td/db/binlog/Binlog.h"
#include "td/db/binlog/BinlogInterface.h"
+#include "td/db/DbKey.h"
+
+#include "td/actor/actor.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
@@ -26,28 +27,28 @@ namespace detail {
class BinlogActor;
} // namespace detail
-class ConcurrentBinlog : public BinlogInterface {
+class ConcurrentBinlog final : public BinlogInterface {
public:
using Callback = std::function<void(const BinlogEvent &)>;
Result<BinlogInfo> init(string path, const Callback &callback, DbKey db_key = DbKey::empty(),
DbKey old_db_key = DbKey::empty(), int scheduler_id = -1) TD_WARN_UNUSED_RESULT;
ConcurrentBinlog();
- explicit ConcurrentBinlog(std::unique_ptr<Binlog> binlog, int scheduler_id = -1);
+ explicit ConcurrentBinlog(unique_ptr<Binlog> binlog, int scheduler_id = -1);
ConcurrentBinlog(const ConcurrentBinlog &other) = delete;
ConcurrentBinlog &operator=(const ConcurrentBinlog &other) = delete;
ConcurrentBinlog(ConcurrentBinlog &&other) = delete;
ConcurrentBinlog &operator=(ConcurrentBinlog &&other) = delete;
- ~ConcurrentBinlog() override;
+ ~ConcurrentBinlog() final;
- void force_sync(Promise<> promise) override;
- void force_flush() override;
- void change_key(DbKey db_key, Promise<> promise) override;
+ void force_sync(Promise<> promise) final;
+ void force_flush() final;
+ void change_key(DbKey db_key, Promise<> promise) final;
- uint64 next_id() override {
+ uint64 next_id() final {
return last_id_.fetch_add(1, std::memory_order_relaxed);
}
- uint64 next_id(int32 shift) override {
+ uint64 next_id(int32 shift) final {
return last_id_.fetch_add(shift, std::memory_order_relaxed);
}
@@ -56,13 +57,14 @@ class ConcurrentBinlog : public BinlogInterface {
}
private:
- void init_impl(std::unique_ptr<Binlog> binlog, int scheduler_id);
- void close_impl(Promise<> promise) override;
- void close_and_destroy_impl(Promise<> promise) override;
- void add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise) override;
+ void init_impl(unique_ptr<Binlog> binlog, int scheduler_id);
+ void close_impl(Promise<> promise) final;
+ void close_and_destroy_impl(Promise<> promise) final;
+ void add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise, BinlogDebugInfo info) final;
ActorOwn<detail::BinlogActor> binlog_actor_;
string path_;
- std::atomic<uint64> last_id_;
+ std::atomic<uint64> last_id_{0};
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/binlog_dump.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/binlog_dump.cpp
index a8b8bf9e1b..f3984062fc 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/binlog_dump.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/binlog_dump.cpp
@@ -1,52 +1,156 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/db/binlog/Binlog.h"
+#include "td/db/DbKey.h"
+
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/Stat.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tl_parsers.h"
-#include <cstdio>
#include <map>
+struct Trie {
+ Trie() {
+ nodes_.resize(1);
+ }
+
+ void add(td::Slice value) {
+ do_add(0, PSLICE() << value << '\0');
+ }
+
+ void dump() {
+ if (nodes_[0].sum == 0) { // division by zero
+ return;
+ }
+ LOG(PLAIN) << "TOTAL: " << nodes_[0].sum;
+ do_dump("", 0);
+ }
+
+ private:
+ struct FullNode {
+ int next[256] = {};
+ int sum = 0;
+ };
+ td::vector<FullNode> nodes_;
+
+ void do_add(int id, td::Slice value) {
+ nodes_[id].sum++;
+ if (value.empty()) {
+ return;
+ }
+
+ auto c = static_cast<td::uint8>(value[0]);
+ auto next_id = nodes_[id].next[c];
+ if (next_id == 0) {
+ next_id = static_cast<int>(nodes_.size());
+ nodes_.emplace_back();
+ nodes_[id].next[c] = next_id;
+ }
+ do_add(next_id, value.substr(1));
+ }
+
+ void do_dump(td::string path, int v) {
+ bool is_word_end = !path.empty() && path.back() == '\0';
+
+ bool need_stop = false;
+ int next_count = 0;
+ for (int c = 0; c < 256; c++) {
+ if (nodes_[v].next[c] != 0) {
+ need_stop |= c >= 128 || !(td::is_alpha(static_cast<char>(c)) || c == '.' || c == '_');
+ next_count++;
+ }
+ }
+ need_stop |= next_count == 0 || (next_count >= 2 && nodes_[v].sum <= nodes_[0].sum / 100);
+
+ if (is_word_end || need_stop) {
+ if (is_word_end) {
+ path.pop_back();
+ } else if (next_count != 1 || nodes_[v].next[0] == 0) {
+ path.push_back('*');
+ }
+ LOG(PLAIN) << nodes_[v].sum << " " << td::StringBuilder::FixedDouble(nodes_[v].sum * 100.0 / nodes_[0].sum, 2)
+ << "% [" << td::format::escaped(path) << "]";
+ return;
+ }
+ for (int c = 0; c < 256; c++) {
+ auto next_id = nodes_[v].next[c];
+ if (next_id == 0) {
+ continue;
+ }
+ do_dump(path + static_cast<char>(c), next_id);
+ }
+ }
+};
+
+enum Magic { ConfigPmcMagic = 0x1f18, BinlogPmcMagic = 0x4327 };
+
int main(int argc, char *argv[]) {
if (argc < 2) {
- std::fprintf(stderr, "Usage: binlog_dump <binlog_file_name>\n");
+ LOG(PLAIN) << "Usage: binlog_dump <binlog_file_name>";
+ return 1;
+ }
+ td::string binlog_file_name = argv[1];
+ auto r_stat = td::stat(binlog_file_name);
+ if (r_stat.is_error() || r_stat.ok().size_ == 0 || !r_stat.ok().is_reg_) {
+ LOG(PLAIN) << "Wrong binlog file name specified";
+ LOG(PLAIN) << "Usage: binlog_dump <binlog_file_name>";
return 1;
}
struct Info {
std::size_t full_size = 0;
std::size_t compressed_size = 0;
+ Trie trie;
+ Trie compressed_trie;
};
std::map<td::uint64, Info> info;
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
td::Binlog binlog;
binlog
- .init(argv[1],
- [&](auto &event) {
- info[0].compressed_size += event.raw_event_.size();
- info[event.type_].compressed_size += event.raw_event_.size();
- },
- td::DbKey::empty(), td::DbKey::empty(), -1,
- [&](auto &event) mutable {
- info[0].full_size += event.raw_event_.size();
- info[event.type_].full_size += event.raw_event_.size();
- LOG(PLAIN) << "LogEvent[" << td::tag("id", td::format::as_hex(event.id_)) << td::tag("type", event.type_)
- << td::tag("flags", event.flags_) << td::tag("data", td::format::escaped(event.data_))
- << "]\n";
- })
+ .init(
+ binlog_file_name,
+ [&](auto &event) {
+ info[0].compressed_size += event.raw_event_.size();
+ info[event.type_].compressed_size += event.raw_event_.size();
+ if (event.type_ == ConfigPmcMagic || event.type_ == BinlogPmcMagic) {
+ auto key = td::TlParser(event.data_).fetch_string<td::Slice>();
+ info[event.type_].compressed_trie.add(key);
+ }
+ },
+ td::DbKey::raw_key("cucumber"), td::DbKey::empty(), -1,
+ [&](auto &event) mutable {
+ info[0].full_size += event.raw_event_.size();
+ info[event.type_].full_size += event.raw_event_.size();
+ if (event.type_ == ConfigPmcMagic || event.type_ == BinlogPmcMagic) {
+ auto key = td::TlParser(event.data_).fetch_string<td::Slice>();
+ info[event.type_].trie.add(key);
+ }
+ LOG(PLAIN) << "LogEvent[" << td::tag("id", td::format::as_hex(event.id_)) << td::tag("type", event.type_)
+ << td::tag("flags", event.flags_) << td::tag("size", event.data_.size())
+ << td::tag("data", td::format::escaped(event.data_)) << "]\n";
+ })
.ensure();
for (auto &it : info) {
- LOG(ERROR) << td::tag("handler", td::format::as_hex(it.first))
+ LOG(PLAIN) << td::tag("handler", td::format::as_hex(it.first))
<< td::tag("full_size", td::format::as_size(it.second.full_size))
<< td::tag("compressed_size", td::format::as_size(it.second.compressed_size));
+ it.second.trie.dump();
+ if (it.second.full_size != it.second.compressed_size) {
+ it.second.compressed_trie.dump();
+ }
}
return 0;
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.cpp
index b7ddc98ff1..cb4e4817c5 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,6 +10,7 @@
namespace td {
namespace detail {
+
void BinlogEventsBuffer::add_event(BinlogEvent &&event) {
total_events_++;
if ((event.flags_ & BinlogEvent::Flags::Partial) == 0) {
@@ -26,14 +27,17 @@ void BinlogEventsBuffer::add_event(BinlogEvent &&event) {
size_ += event.size_;
events_.push_back(std::move(event));
}
+
bool BinlogEventsBuffer::need_flush() const {
return total_events_ > 5000 || ids_.size() > 100;
}
+
void BinlogEventsBuffer::clear() {
ids_.clear();
events_.clear();
total_events_ = 0;
size_ = 0;
}
+
} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.h b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.h
index dcd6d7c1b3..5b7cab8212 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsBuffer.h
@@ -1,16 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/db/binlog/BinlogEvent.h"
#include "td/utils/common.h"
namespace td {
namespace detail {
+
class BinlogEventsBuffer {
public:
void add_event(BinlogEvent &&event);
@@ -23,7 +25,8 @@ class BinlogEventsBuffer {
auto &event = events_[i];
if (i + 1 != ids_.size() && (event.flags_ & BinlogEvent::Flags::Partial) == 0) {
callback(BinlogEvent(BinlogEvent::create_raw(event.id_, event.type_, event.flags_ | BinlogEvent::Flags::Partial,
- create_storer(event.data_))));
+ create_storer(event.data_)),
+ BinlogDebugInfo{__FILE__, __LINE__}));
} else {
callback(std::move(event));
}
@@ -43,5 +46,6 @@ class BinlogEventsBuffer {
void do_event(BinlogEvent &&event);
void clear();
};
+
} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.cpp
index 50ad91bff8..7b2832b39a 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.cpp
@@ -1,25 +1,26 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/db/binlog/detail/BinlogEventsProcessor.h"
-#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
#include <algorithm>
namespace td {
namespace detail {
-void BinlogEventsProcessor::do_event(BinlogEvent &&event) {
+
+Status BinlogEventsProcessor::do_event(BinlogEvent &&event) {
offset_ = event.offset_;
auto fixed_id = event.id_ * 2;
if ((event.flags_ & BinlogEvent::Flags::Rewrite) && !ids_.empty() && ids_.back() >= fixed_id) {
auto it = std::lower_bound(ids_.begin(), ids_.end(), fixed_id);
if (it == ids_.end() || *it != fixed_id) {
- LOG(FATAL) << "Ignore rewrite logevent";
- return;
+ return Status::Error(PSLICE() << "Ignore rewrite log event " << event.public_to_string());
}
auto pos = it - ids_.begin();
total_raw_events_size_ -= static_cast<int64>(events_[pos].raw_event_.size());
@@ -32,10 +33,14 @@ void BinlogEventsProcessor::do_event(BinlogEvent &&event) {
total_raw_events_size_ += static_cast<int64>(event.raw_event_.size());
events_[pos] = std::move(event);
}
- } else if (event.type_ == BinlogEvent::ServiceTypes::Empty) {
- // just skip this event
+ } else if (event.type_ < 0) {
+ // just skip service events
} else {
- CHECK(ids_.empty() || ids_.back() < fixed_id);
+ if (!(ids_.empty() || ids_.back() < fixed_id)) {
+ return Status::Error(PSLICE() << offset_ << " " << ids_.size() << " " << ids_.back() << " " << fixed_id << " "
+ << event.public_to_string() << " " << total_events_ << " "
+ << total_raw_events_size_);
+ }
last_id_ = event.id_;
total_raw_events_size_ += static_cast<int64>(event.raw_event_.size());
total_events_++;
@@ -46,6 +51,7 @@ void BinlogEventsProcessor::do_event(BinlogEvent &&event) {
if (total_events_ > 10 && empty_events_ * 4 > total_events_ * 3) {
compactify();
}
+ return Status::OK();
}
void BinlogEventsProcessor::compactify() {
@@ -66,5 +72,6 @@ void BinlogEventsProcessor::compactify() {
empty_events_ = 0;
CHECK(ids_.size() == events_.size());
}
+
} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.h b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.h
index 645f8c50ab..8b276aef03 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/binlog/detail/BinlogEventsProcessor.h
@@ -1,25 +1,31 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/db/binlog/BinlogEvent.h"
#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Status.h"
namespace td {
namespace detail {
+
class BinlogEventsProcessor {
public:
- void add_event(BinlogEvent &&event) {
- do_event(std::move(event));
+ Status add_event(BinlogEvent &&event) TD_WARN_UNUSED_RESULT {
+ return do_event(std::move(event));
}
template <class CallbackT>
void for_each(CallbackT &&callback) {
for (size_t i = 0; i < ids_.size(); i++) {
+ LOG_CHECK(i == 0 || ids_[i - 1] < ids_[i]) << ids_[i - 1] << " " << events_[i - 1].public_to_string() << " "
+ << ids_[i] << " " << events_[i].public_to_string();
if ((ids_[i] & 1) == 0) {
callback(events_[i]);
}
@@ -46,8 +52,9 @@ class BinlogEventsProcessor {
int64 offset_{0};
int64 total_raw_events_size_{0};
- void do_event(BinlogEvent &&event);
+ Status do_event(BinlogEvent &&event);
void compactify();
};
+
} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.cpp b/protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.cpp
index 6ee1b45af2..83147562e8 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.cpp
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,29 +10,50 @@
#include "td/utils/common.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/port/path.h"
+#include "td/utils/port/Stat.h"
+
+#include <atomic>
namespace td {
namespace detail {
-Status RawSqliteDb::last_error(sqlite3 *db) {
- return Status::Error(Slice(sqlite3_errmsg(db)));
+
+static std::atomic<bool> was_database_destroyed{false};
+
+Status RawSqliteDb::last_error(tdsqlite3 *db, CSlice path) {
+ return Status::Error(PSLICE() << Slice(tdsqlite3_errmsg(db)) << " for database \"" << path << '"');
}
+
Status RawSqliteDb::destroy(Slice path) {
- with_db_path(path, [](auto path) { unlink(path).ignore(); });
- return Status::OK();
+ Status error;
+ with_db_path(path, [&](auto path) {
+ unlink(path).ignore();
+ if (!ends_with(path, "-shm") && !stat(path).is_error()) {
+ error = Status::Error(PSLICE() << "Failed to delete file \"" << path << '"');
+ }
+ });
+ return error;
}
+
Status RawSqliteDb::last_error() {
//If database was corrupted, try to delete it.
- auto code = sqlite3_errcode(db_);
+ auto code = tdsqlite3_errcode(db_);
if (code == SQLITE_CORRUPT) {
+ was_database_destroyed.store(true, std::memory_order_relaxed);
destroy(path_).ignore();
}
- return last_error(db_);
+ return last_error(db_, path());
+}
+
+bool RawSqliteDb::was_any_database_destroyed() {
+ return was_database_destroyed.load(std::memory_order_relaxed);
}
+
RawSqliteDb::~RawSqliteDb() {
- auto rc = sqlite3_close(db_);
- LOG_IF(FATAL, rc != SQLITE_OK) << last_error(db_);
+ auto rc = tdsqlite3_close(db_);
+ LOG_IF(FATAL, rc != SQLITE_OK) << last_error(db_, path());
}
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.h b/protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.h
index 801f3b192a..c2227d1fa3 100644
--- a/protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.h
+++ b/protocols/Telegram/tdlib/td/tddb/td/db/detail/RawSqliteDb.h
@@ -1,22 +1,24 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/logging.h"
+#include "td/utils/optional.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
-struct sqlite3;
+struct tdsqlite3;
namespace td {
namespace detail {
+
class RawSqliteDb {
public:
- RawSqliteDb(sqlite3 *db, std::string path) : db_(db), path_(std::move(path)) {
+ RawSqliteDb(tdsqlite3 *db, std::string path) : db_(db), path_(std::move(path)) {
}
RawSqliteDb(const RawSqliteDb &) = delete;
RawSqliteDb(RawSqliteDb &&) = delete;
@@ -28,12 +30,12 @@ class RawSqliteDb {
static void with_db_path(Slice main_path, F &&f) {
f(PSLICE() << main_path);
f(PSLICE() << main_path << "-journal");
- f(PSLICE() << main_path << "-shm");
f(PSLICE() << main_path << "-wal");
+ f(PSLICE() << main_path << "-shm");
}
static Status destroy(Slice path) TD_WARN_UNUSED_RESULT;
- sqlite3 *db() {
+ tdsqlite3 *db() {
return db_;
}
CSlice path() const {
@@ -41,11 +43,36 @@ class RawSqliteDb {
}
Status last_error();
- static Status last_error(sqlite3 *db);
+ static Status last_error(tdsqlite3 *db, CSlice path);
+
+ static bool was_any_database_destroyed();
+
+ bool on_begin() {
+ begin_cnt_++;
+ return begin_cnt_ == 1;
+ }
+ Result<bool> on_commit() {
+ if (begin_cnt_ == 0) {
+ return Status::Error("No matching begin for commit");
+ }
+ begin_cnt_--;
+ return begin_cnt_ == 0;
+ }
+
+ void set_cipher_version(int32 cipher_version) {
+ cipher_version_ = cipher_version;
+ }
+
+ optional<int32> get_cipher_version() const {
+ return cipher_version_.copy();
+ }
private:
- sqlite3 *db_;
+ tdsqlite3 *db_;
std::string path_;
+ size_t begin_cnt_{0};
+ optional<int32> cipher_version_;
};
-}; // namespace detail
+
+} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/CMakeLists.txt b/protocols/Telegram/tdlib/td/tdnet/CMakeLists.txt
index 823ed027d6..e14f3500f9 100644
--- a/protocols/Telegram/tdlib/td/tdnet/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/tdnet/CMakeLists.txt
@@ -1,4 +1,10 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
+
+if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
+ set(CMAKE_INSTALL_LIBDIR "lib")
+endif()
if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
@@ -14,11 +20,14 @@ set(TDNET_SOURCE
td/net/HttpFile.cpp
td/net/HttpInboundConnection.cpp
td/net/HttpOutboundConnection.cpp
+ td/net/HttpProxy.cpp
td/net/HttpQuery.cpp
td/net/HttpReader.cpp
td/net/Socks5.cpp
- td/net/SslFd.cpp
+ td/net/SslCtx.cpp
+ td/net/SslStream.cpp
td/net/TcpListener.cpp
+ td/net/TransparentProxy.cpp
td/net/Wget.cpp
td/net/GetHostByNameActor.h
@@ -29,26 +38,53 @@ set(TDNET_SOURCE
td/net/HttpHeaderCreator.h
td/net/HttpInboundConnection.h
td/net/HttpOutboundConnection.h
+ td/net/HttpProxy.h
td/net/HttpQuery.h
td/net/HttpReader.h
td/net/NetStats.h
td/net/Socks5.h
- td/net/SslFd.h
+ td/net/SslCtx.h
+ td/net/SslStream.h
td/net/TcpListener.h
+ td/net/TransparentProxy.h
td/net/Wget.h
)
+if (APPLE_WATCH)
+ set(TDNET_SOURCE
+ ${TDNET_SOURCE}
+ td/net/DarwinHttp.mm
+ td/net/DarwinHttp.h
+ )
+ set_source_files_properties(td/net/DarwinHttp.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
+endif()
+
#RULES
#LIBRARIES
add_library(tdnet STATIC ${TDNET_SOURCE})
target_include_directories(tdnet PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
-target_include_directories(tdnet SYSTEM PUBLIC $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>)
-target_link_libraries(tdnet PUBLIC tdutils tdactor ${OPENSSL_LIBRARIES} PRIVATE ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
+target_include_directories(tdnet SYSTEM PRIVATE $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>)
+target_link_libraries(tdnet PUBLIC tdutils tdactor)
+if (NOT EMSCRIPTEN)
+ target_link_libraries(tdnet PRIVATE ${OPENSSL_SSL_LIBRARY})
+endif()
+target_link_libraries(tdnet PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
+
+if (WIN32)
+ if (MINGW)
+ target_link_libraries(tdnet PRIVATE ws2_32 mswsock crypt32)
+ else()
+ target_link_libraries(tdnet PRIVATE ws2_32 Mswsock Crypt32)
+ endif()
+endif()
+
+if (APPLE_WATCH)
+ find_library(FOUNDATION_LIBRARY Foundation REQUIRED)
+ target_link_libraries(tdnet PRIVATE ${FOUNDATION_LIBRARY})
+endif()
install(TARGETS tdnet EXPORT TdTargets
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- RUNTIME DESTINATION bin
- INCLUDES DESTINATION include
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/DarwinHttp.h b/protocols/Telegram/tdlib/td/tdnet/td/net/DarwinHttp.h
new file mode 100644
index 0000000000..42c0a5b000
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/DarwinHttp.h
@@ -0,0 +1,21 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/buffer.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+class DarwinHttp {
+ public:
+ static void get(CSlice url, Promise<BufferSlice> promise);
+ static void post(CSlice url, Slice data, Promise<BufferSlice> promise);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/DarwinHttp.mm b/protocols/Telegram/tdlib/td/tdnet/td/net/DarwinHttp.mm
new file mode 100644
index 0000000000..f8e285433f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/DarwinHttp.mm
@@ -0,0 +1,68 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/net/DarwinHttp.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
+
+#import <Foundation/Foundation.h>
+
+namespace td {
+
+namespace {
+NSString *to_ns_string(CSlice slice) {
+ return [NSString stringWithUTF8String:slice.c_str()];
+}
+
+NSData *to_ns_data(Slice data) {
+ return [NSData dataWithBytes:static_cast<const void *>(data.data()) length:data.size()];
+}
+
+auto http_get(CSlice url) {
+ auto nsurl = [NSURL URLWithString:to_ns_string(url)];
+ auto request = [NSURLRequest requestWithURL:nsurl];
+ return request;
+}
+
+auto http_post(CSlice url, Slice data) {
+ auto nsurl = [NSURL URLWithString:to_ns_string(url)];
+ auto request = [NSMutableURLRequest requestWithURL:nsurl];
+ [request setHTTPMethod:@"POST"];
+ [request setHTTPBody:to_ns_data(data)];
+ [request setValue:@"keep-alive" forHTTPHeaderField:@"Connection"];
+ [request setValue:@"" forHTTPHeaderField:@"Host"];
+ [request setValue:to_ns_string(PSLICE() << data.size()) forHTTPHeaderField:@"Content-Length"];
+ [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
+ return request;
+}
+
+void http_send(NSURLRequest *request, Promise<BufferSlice> promise) {
+ __block auto callback = std::move(promise);
+ NSURLSessionDataTask *dataTask =
+ [NSURLSession.sharedSession
+ dataTaskWithRequest:request
+ completionHandler:
+ ^(NSData *data, NSURLResponse *response, NSError *error) {
+ if (error == nil) {
+ callback.set_value(BufferSlice(Slice((const char *)([data bytes]), [data length])));
+ } else {
+ callback.set_error(Status::Error(static_cast<int32>([error code]), "HTTP request failed"));
+ }
+ }];
+ [dataTask resume];
+}
+} // namespace
+
+void DarwinHttp::get(CSlice url, Promise<BufferSlice> promise) {
+ return http_send(http_get(url), std::move(promise));
+}
+
+void DarwinHttp::post(CSlice url, Slice data, Promise<BufferSlice> promise) {
+ return http_send(http_post(url, data), std::move(promise));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp
index b6cdcca0f0..8a720083c0 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp
@@ -1,48 +1,216 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/GetHostByNameActor.h"
+#include "td/net/HttpQuery.h"
+#include "td/net/SslCtx.h"
+#include "td/net/Wget.h"
+
+#include "td/utils/common.h"
+#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
namespace td {
-GetHostByNameActor::GetHostByNameActor(int32 ok_timeout, int32 error_timeout)
- : ok_timeout_(ok_timeout), error_timeout_(error_timeout) {
+namespace detail {
+
+class GoogleDnsResolver final : public Actor {
+ public:
+ GoogleDnsResolver(std::string host, bool prefer_ipv6, Promise<IPAddress> promise)
+ : host_(std::move(host)), prefer_ipv6_(prefer_ipv6), promise_(std::move(promise)) {
+ }
+
+ private:
+ std::string host_;
+ bool prefer_ipv6_;
+ Promise<IPAddress> promise_;
+ ActorOwn<Wget> wget_;
+ double begin_time_ = 0;
+
+ void start_up() final {
+ auto r_address = IPAddress::get_ip_address(host_);
+ if (r_address.is_ok()) {
+ promise_.set_value(r_address.move_as_ok());
+ return stop();
+ }
+
+ const int timeout = 10;
+ const int ttl = 3;
+ begin_time_ = Time::now();
+ auto wget_promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result<unique_ptr<HttpQuery>> r_http_query) {
+ send_closure(actor_id, &GoogleDnsResolver::on_result, std::move(r_http_query));
+ });
+ wget_ = create_actor<Wget>(
+ "GoogleDnsResolver", std::move(wget_promise),
+ PSTRING() << "https://dns.google/resolve?name=" << url_encode(host_) << "&type=" << (prefer_ipv6_ ? 28 : 1),
+ std::vector<std::pair<string, string>>({{"Host", "dns.google"}}), timeout, ttl, prefer_ipv6_,
+ SslCtx::VerifyPeer::Off);
+ }
+
+ static Result<IPAddress> get_ip_address(Result<unique_ptr<HttpQuery>> r_http_query) {
+ TRY_RESULT(http_query, std::move(r_http_query));
+
+ auto get_ip_address = [](JsonValue &answer) -> Result<IPAddress> {
+ auto &array = answer.get_array();
+ if (array.empty()) {
+ return Status::Error("Failed to parse DNS result: Answer is an empty array");
+ }
+ if (array[0].type() != JsonValue::Type::Object) {
+ return Status::Error("Failed to parse DNS result: Answer[0] is not an object");
+ }
+ auto &answer_0 = array[0].get_object();
+ TRY_RESULT(ip_str, get_json_object_string_field(answer_0, "data", false));
+ IPAddress ip;
+ TRY_STATUS(ip.init_host_port(ip_str, 0));
+ return ip;
+ };
+ if (!http_query->get_arg("Answer").empty()) {
+ TRY_RESULT(answer, json_decode(http_query->get_arg("Answer")));
+ if (answer.type() != JsonValue::Type::Array) {
+ return Status::Error("Expected JSON array");
+ }
+ return get_ip_address(answer);
+ } else {
+ TRY_RESULT(json_value, json_decode(http_query->content_));
+ if (json_value.type() != JsonValue::Type::Object) {
+ return Status::Error("Failed to parse DNS result: not an object");
+ }
+ TRY_RESULT(answer, get_json_object_field(json_value.get_object(), "Answer", JsonValue::Type::Array, false));
+ return get_ip_address(answer);
+ }
+ }
+
+ void on_result(Result<unique_ptr<HttpQuery>> r_http_query) {
+ auto end_time = Time::now();
+ auto result = get_ip_address(std::move(r_http_query));
+ VLOG(dns_resolver) << "Init IPv" << (prefer_ipv6_ ? "6" : "4") << " host = " << host_ << " in "
+ << end_time - begin_time_ << " seconds to "
+ << (result.is_ok() ? (PSLICE() << result.ok()) : CSlice("[invalid]"));
+ promise_.set_result(std::move(result));
+ stop();
+ }
+};
+
+class NativeDnsResolver final : public Actor {
+ public:
+ NativeDnsResolver(std::string host, bool prefer_ipv6, Promise<IPAddress> promise)
+ : host_(std::move(host)), prefer_ipv6_(prefer_ipv6), promise_(std::move(promise)) {
+ }
+
+ private:
+ std::string host_;
+ bool prefer_ipv6_;
+ Promise<IPAddress> promise_;
+
+ void start_up() final {
+ IPAddress ip;
+ auto begin_time = Time::now();
+ auto status = ip.init_host_port(host_, 0, prefer_ipv6_);
+ auto end_time = Time::now();
+ VLOG(dns_resolver) << "Init host = " << host_ << " in " << end_time - begin_time << " seconds to " << ip;
+ if (status.is_error()) {
+ promise_.set_error(std::move(status));
+ } else {
+ promise_.set_value(std::move(ip));
+ }
+ stop();
+ }
+};
+
+} // namespace detail
+
+int VERBOSITY_NAME(dns_resolver) = VERBOSITY_NAME(DEBUG);
+
+GetHostByNameActor::GetHostByNameActor(Options options) : options_(std::move(options)) {
+ CHECK(!options_.resolver_types.empty());
}
-void GetHostByNameActor::run(std::string host, int port, td::Promise<td::IPAddress> promise) {
- auto r_ip = load_ip(std::move(host), port);
- promise.set_result(std::move(r_ip));
+void GetHostByNameActor::run(string host, int port, bool prefer_ipv6, Promise<IPAddress> promise) {
+ auto r_ascii_host = idn_to_ascii(host);
+ if (r_ascii_host.is_error()) {
+ return promise.set_error(r_ascii_host.move_as_error());
+ }
+ auto ascii_host = r_ascii_host.move_as_ok();
+ if (ascii_host.empty()) {
+ return promise.set_error(Status::Error("Host is empty"));
+ }
+
+ auto begin_time = Time::now();
+ auto &value = cache_[prefer_ipv6].emplace(ascii_host, Value{{}, begin_time - 1.0}).first->second;
+ if (value.expires_at > begin_time) {
+ return promise.set_result(value.get_ip_port(port));
+ }
+
+ auto &query_ptr = active_queries_[prefer_ipv6][ascii_host];
+ if (query_ptr == nullptr) {
+ query_ptr = make_unique<Query>();
+ }
+ auto &query = *query_ptr;
+ query.promises.emplace_back(port, std::move(promise));
+ if (query.query.empty()) {
+ CHECK(query.promises.size() == 1);
+ query.real_host = std::move(host);
+ query.begin_time = Time::now();
+ run_query(std::move(ascii_host), prefer_ipv6, query);
+ }
}
-Result<td::IPAddress> GetHostByNameActor::load_ip(string host, int port) {
- auto &value = cache_.emplace(host, Value{{}, 0}).first->second;
- auto begin_time = td::Time::now();
- if (value.expire_at > begin_time) {
- auto ip = value.ip.clone();
- if (ip.is_ok()) {
- ip.ok_ref().set_port(port);
- CHECK(ip.ok().get_port() == port);
+void GetHostByNameActor::run_query(std::string host, bool prefer_ipv6, Query &query) {
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this), host, prefer_ipv6](Result<IPAddress> res) mutable {
+ send_closure(actor_id, &GetHostByNameActor::on_query_result, std::move(host), prefer_ipv6, std::move(res));
+ });
+
+ CHECK(query.query.empty());
+ CHECK(query.pos < options_.resolver_types.size());
+ auto resolver_type = options_.resolver_types[query.pos++];
+ query.query = [&] {
+ switch (resolver_type) {
+ case ResolverType::Native:
+ return ActorOwn<>(create_actor_on_scheduler<detail::NativeDnsResolver>(
+ "NativeDnsResolver", options_.scheduler_id, std::move(host), prefer_ipv6, std::move(promise)));
+ case ResolverType::Google:
+ return ActorOwn<>(create_actor_on_scheduler<detail::GoogleDnsResolver>(
+ "GoogleDnsResolver", options_.scheduler_id, std::move(host), prefer_ipv6, std::move(promise)));
+ default:
+ UNREACHABLE();
+ return ActorOwn<>();
}
- return ip;
+ }();
+}
+
+void GetHostByNameActor::on_query_result(std::string host, bool prefer_ipv6, Result<IPAddress> result) {
+ auto query_it = active_queries_[prefer_ipv6].find(host);
+ CHECK(query_it != active_queries_[prefer_ipv6].end());
+ auto &query = *query_it->second;
+ CHECK(!query.promises.empty());
+ CHECK(!query.query.empty());
+
+ if (result.is_error() && query.pos < options_.resolver_types.size()) {
+ query.query.reset();
+ return run_query(std::move(host), prefer_ipv6, query);
}
- td::IPAddress ip;
- auto status = ip.init_host_port(host, port);
- auto end_time = td::Time::now();
- LOG(WARNING) << "Init host = " << host << ", port = " << port << " in " << end_time - begin_time << " seconds to "
- << ip;
+ auto end_time = Time::now();
+ VLOG(dns_resolver) << "Init host = " << query.real_host << " in total of " << end_time - query.begin_time
+ << " seconds to " << (result.is_ok() ? (PSLICE() << result.ok()) : CSlice("[invalid]"));
- if (status.is_ok()) {
- value = Value{ip, end_time + ok_timeout_};
- return ip;
- } else {
- value = Value{status.clone(), end_time + error_timeout_};
- return std::move(status);
+ auto promises = std::move(query.promises);
+ auto value_it = cache_[prefer_ipv6].find(host);
+ CHECK(value_it != cache_[prefer_ipv6].end());
+ auto cache_timeout = result.is_ok() ? options_.ok_timeout : options_.error_timeout;
+ value_it->second = Value{std::move(result), end_time + cache_timeout};
+ active_queries_[prefer_ipv6].erase(query_it);
+
+ for (auto &promise : promises) {
+ promise.second.set_result(value_it->second.get_ip_port(promise.first));
}
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.h b/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.h
index b352a05d18..f864b13e3c 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.h
@@ -1,35 +1,75 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/logging.h"
#include "td/utils/port/IPAddress.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
-#include <unordered_map>
+#include <utility>
namespace td {
-class GetHostByNameActor final : public td::Actor {
+
+extern int VERBOSITY_NAME(dns_resolver);
+
+class GetHostByNameActor final : public Actor {
public:
- explicit GetHostByNameActor(int32 ok_timeout = CACHE_TIME, int32 error_timeout = ERROR_CACHE_TIME);
- void run(std::string host, int port, td::Promise<td::IPAddress> promise);
+ enum class ResolverType { Native, Google };
+
+ struct Options {
+ static constexpr int32 DEFAULT_CACHE_TIME = 60 * 29; // 29 minutes
+ static constexpr int32 DEFAULT_ERROR_CACHE_TIME = 60 * 5; // 5 minutes
+
+ vector<ResolverType> resolver_types{ResolverType::Native};
+ int32 scheduler_id{-1};
+ int32 ok_timeout{DEFAULT_CACHE_TIME};
+ int32 error_timeout{DEFAULT_ERROR_CACHE_TIME};
+ };
+
+ explicit GetHostByNameActor(Options options);
+
+ void run(std::string host, int port, bool prefer_ipv6, Promise<IPAddress> promise);
private:
+ void on_query_result(std::string host, bool prefer_ipv6, Result<IPAddress> result);
+
struct Value {
- Result<td::IPAddress> ip;
- double expire_at;
+ Result<IPAddress> ip;
+ double expires_at;
+
+ Value(Result<IPAddress> ip, double expires_at) : ip(std::move(ip)), expires_at(expires_at) {
+ }
+
+ Result<IPAddress> get_ip_port(int port) const {
+ auto result = ip.clone();
+ if (result.is_ok()) {
+ result.ok_ref().set_port(port);
+ }
+ return result;
+ }
};
- std::unordered_map<string, Value> cache_;
- static constexpr int32 CACHE_TIME = 60 * 29; // 29 minutes
- static constexpr int32 ERROR_CACHE_TIME = 60 * 5; // 5 minutes
+ FlatHashMap<string, Value> cache_[2];
- int32 ok_timeout_;
- int32 error_timeout_;
- Result<td::IPAddress> load_ip(string host, int port) TD_WARN_UNUSED_RESULT;
+ struct Query {
+ ActorOwn<> query;
+ size_t pos = 0;
+ string real_host;
+ double begin_time = 0.0;
+ std::vector<std::pair<int, Promise<IPAddress>>> promises;
+ };
+ FlatHashMap<string, unique_ptr<Query>> active_queries_[2];
+
+ Options options_;
+
+ void run_query(std::string host, bool prefer_ipv6, Query &query);
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.cpp
index 2edd225bfa..259e1fafbd 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,76 +8,77 @@
#include "td/utils/find_boundary.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
namespace td {
-void HttpChunkedByteFlow::loop() {
- bool was_updated = false;
- size_t need_size;
- while (true) {
- if (state_ == ReadChunkLength) {
+bool HttpChunkedByteFlow::loop() {
+ bool result = false;
+ do {
+ if (state_ == State::ReadChunkLength) {
bool ok = find_boundary(input_->clone(), "\r\n", len_);
if (len_ > 10) {
- return finish(Status::Error(PSLICE() << "Too long length in chunked "
- << input_->cut_head(len_).move_as_buffer_slice().as_slice()));
+ finish(Status::Error(PSLICE() << "Too long length in chunked "
+ << input_->cut_head(len_).move_as_buffer_slice().as_slice()));
+ return false;
}
if (!ok) {
- need_size = input_->size() + 1;
+ set_need_size(input_->size() + 1);
break;
}
auto s_len = input_->cut_head(len_).move_as_buffer_slice();
input_->advance(2);
len_ = hex_to_integer<size_t>(s_len.as_slice());
if (len_ > MAX_CHUNK_SIZE) {
- return finish(Status::Error(PSLICE() << "Invalid chunk size " << tag("size", len_)));
+ finish(Status::Error(PSLICE() << "Invalid chunk size " << tag("size", len_)));
+ return false;
}
save_len_ = len_;
- state_ = ReadChunkContent;
+ state_ = State::ReadChunkContent;
}
auto size = input_->size();
auto ready = min(len_, size);
- need_size = min(MIN_UPDATE_SIZE, len_ + 2);
+ auto need_size = min(MIN_UPDATE_SIZE, len_ + 2);
if (size < need_size) {
+ set_need_size(need_size);
break;
}
- total_size_ += ready;
- uncommited_size_ += ready;
- if (total_size_ > MAX_SIZE) {
- return finish(Status::Error(PSLICE() << "Too big query " << tag("size", input_->size())));
+ if (total_size_ > MAX_SIZE - ready) {
+ finish(Status::Error(PSLICE() << "Too big query " << tag("size", input_->size())));
+ return false;
}
+ total_size_ += ready;
+ uncommitted_size_ += ready;
output_.append(input_->cut_head(ready));
+ result = true;
len_ -= ready;
- if (uncommited_size_ >= MIN_UPDATE_SIZE) {
- uncommited_size_ = 0;
- was_updated = true;
+ if (uncommitted_size_ >= MIN_UPDATE_SIZE) {
+ uncommitted_size_ = 0;
}
if (len_ == 0) {
if (input_->size() < 2) {
- need_size = 2;
+ set_need_size(2);
break;
}
- input_->cut_head(2);
+ input_->advance(2);
total_size_ += 2;
if (save_len_ == 0) {
- return finish(Status::OK());
+ finish(Status::OK());
+ return false;
}
- state_ = ReadChunkLength;
+ state_ = State::ReadChunkLength;
len_ = 0;
}
+ } while (false);
+ if (!is_input_active_ && !result) {
+ finish(Status::Error("Unexpected end of stream"));
}
- if (was_updated) {
- on_output_updated();
- }
- if (!is_input_active_) {
- return finish(Status::Error("Unexpected end of stream"));
- }
- set_need_size(need_size);
+ return result;
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.h
index 9c62c3368e..8e248ebb73 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpChunkedByteFlow.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,21 +8,24 @@
#include "td/utils/ByteFlow.h"
+#include <limits>
+
namespace td {
class HttpChunkedByteFlow final : public ByteFlowBase {
public:
- void loop() override;
+ bool loop() final;
private:
- static constexpr int MAX_CHUNK_SIZE = 15 << 20; // some reasonable limit
- static constexpr int MAX_SIZE = 150 << 20; // some reasonable limit
+ static constexpr size_t MAX_CHUNK_SIZE = 15 << 20; // some reasonable limit
+ static constexpr size_t MAX_SIZE = std::numeric_limits<uint32>::max(); // some reasonable limit
static constexpr size_t MIN_UPDATE_SIZE = 1 << 14;
- enum { ReadChunkLength, ReadChunkContent, OK } state_ = ReadChunkLength;
+ enum class State { ReadChunkLength, ReadChunkContent, OK };
+ State state_ = State::ReadChunkLength;
size_t len_ = 0;
- size_t save_len_;
+ size_t save_len_ = 0;
size_t total_size_ = 0;
- size_t uncommited_size_ = 0;
+ size_t uncommitted_size_ = 0;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.cpp
index 087ee5b790..c1f630ea1a 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.cpp
@@ -1,29 +1,40 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpConnectionBase.h"
-#include "td/actor/actor.h"
-
#include "td/net/HttpHeaderCreator.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/port/detail/PollableFd.h"
namespace td {
namespace detail {
-HttpConnectionBase::HttpConnectionBase(State state, FdProxy fd, size_t max_post_size, size_t max_files,
- int32 idle_timeout)
+HttpConnectionBase::HttpConnectionBase(State state, BufferedFd<SocketFd> fd, SslStream ssl_stream, size_t max_post_size,
+ size_t max_files, int32 idle_timeout, int32 slow_scheduler_id)
: state_(state)
- , stream_connection_(std::move(fd))
+ , fd_(std::move(fd))
+ , ssl_stream_(std::move(ssl_stream))
, max_post_size_(max_post_size)
, max_files_(max_files)
- , idle_timeout_(idle_timeout) {
+ , idle_timeout_(idle_timeout)
+ , slow_scheduler_id_(slow_scheduler_id) {
CHECK(state_ != State::Close);
+
+ if (ssl_stream_) {
+ read_source_ >> ssl_stream_.read_byte_flow() >> read_sink_;
+ write_source_ >> ssl_stream_.write_byte_flow() >> write_sink_;
+ } else {
+ read_source_ >> read_sink_;
+ write_source_ >> write_sink_;
+ }
+ peer_address_.init_peer_address(fd_).ignore();
}
void HttpConnectionBase::live_event() {
@@ -33,9 +44,8 @@ void HttpConnectionBase::live_event() {
}
void HttpConnectionBase::start_up() {
- stream_connection_.get_fd().set_observer(this);
- subscribe(stream_connection_.get_fd());
- reader_.init(&stream_connection_.input_buffer(), max_post_size_, max_files_);
+ Scheduler::subscribe(fd_.get_poll_info().extract_pollable_fd(this));
+ reader_.init(read_sink_.get_output(), max_post_size_, max_files_);
if (state_ == State::Read) {
current_query_ = make_unique<HttpQuery>();
}
@@ -43,13 +53,16 @@ void HttpConnectionBase::start_up() {
yield();
}
void HttpConnectionBase::tear_down() {
- unsubscribe_before_close(stream_connection_.get_fd());
- stream_connection_.close();
+ Scheduler::unsubscribe_before_close(fd_.get_poll_info().get_pollable_fd_ref());
+ fd_.close();
}
-void HttpConnectionBase::write_next(BufferSlice buffer) {
+void HttpConnectionBase::write_next_noflush(BufferSlice buffer) {
CHECK(state_ == State::Write);
- stream_connection_.output_buffer().append(std::move(buffer));
+ write_buffer_.append(std::move(buffer));
+}
+void HttpConnectionBase::write_next(BufferSlice buffer) {
+ write_next_noflush(std::move(buffer));
loop();
}
@@ -63,7 +76,7 @@ void HttpConnectionBase::write_ok() {
void HttpConnectionBase::write_error(Status error) {
CHECK(state_ == State::Write);
- LOG(WARNING) << "Close http connection: " << error;
+ LOG(WARNING) << "Close HTTP connection: " << error;
state_ = State::Close;
loop();
}
@@ -71,7 +84,7 @@ void HttpConnectionBase::write_error(Status error) {
void HttpConnectionBase::timeout_expired() {
LOG(INFO) << "Idle timeout expired";
- if (stream_connection_.need_flush_write()) {
+ if (fd_.need_flush_write()) {
on_error(Status::Error("Write timeout expired"));
} else if (state_ == State::Read) {
on_error(Status::Error("Read timeout expired"));
@@ -80,74 +93,118 @@ void HttpConnectionBase::timeout_expired() {
stop();
}
void HttpConnectionBase::loop() {
- if (can_read(stream_connection_)) {
+ if (ssl_stream_) {
+ //ssl_stream_.read_byte_flow().set_need_size(0);
+ ssl_stream_.write_byte_flow().reset_need_size();
+ }
+ sync_with_poll(fd_);
+ if (can_read_local(fd_)) {
LOG(DEBUG) << "Can read from the connection";
- auto r = stream_connection_.flush_read();
+ auto r = fd_.flush_read();
if (r.is_error()) {
if (!begins_with(r.error().message(), "SSL error {336134278")) { // if error is not yet outputed
- LOG(INFO) << "flush_read error: " << r.error();
+ LOG(INFO) << "Receive flush_read error: " << r.error();
}
on_error(Status::Error(r.error().public_message()));
return stop();
}
}
+ read_source_.wakeup();
// TODO: read_next even when state_ == State::Write
bool want_read = false;
+ bool can_be_slow = slow_scheduler_id_ == -1;
if (state_ == State::Read) {
- auto res = reader_.read_next(current_query_.get());
+ auto res = reader_.read_next(current_query_.get(), can_be_slow);
if (res.is_error()) {
+ if (res.error().message() == "SLOW") {
+ LOG(INFO) << "Slow HTTP connection: migrate to " << slow_scheduler_id_;
+ CHECK(!can_be_slow);
+ yield();
+ migrate(slow_scheduler_id_);
+ slow_scheduler_id_ = -1;
+ return;
+ }
live_event();
state_ = State::Write;
- LOG(INFO) << res.error();
+ if (res.error().code() == 500) {
+ LOG(WARNING) << "Failed to process an HTTP query: " << res.error();
+ } else {
+ LOG(INFO) << res.error();
+ }
HttpHeaderCreator hc;
hc.init_status_line(res.error().code());
hc.set_content_size(0);
- stream_connection_.output_buffer().append(hc.finish().ok());
+ write_buffer_.append(hc.finish().ok());
close_after_write_ = true;
on_error(Status::Error(res.error().public_message()));
} else if (res.ok() == 0) {
state_ = State::Write;
- LOG(INFO) << "Send query to handler";
+ LOG(DEBUG) << "Send query to handler";
live_event();
+ current_query_->peer_address_ = peer_address_;
on_query(std::move(current_query_));
} else {
want_read = true;
}
}
- if (can_write(stream_connection_)) {
+ write_source_.wakeup();
+
+ if (can_write_local(fd_)) {
LOG(DEBUG) << "Can write to the connection";
- auto r = stream_connection_.flush_write();
+ auto r = fd_.flush_write();
if (r.is_error()) {
- LOG(INFO) << "flush_write error: " << r.error();
+ LOG(INFO) << "Receive flush_write error: " << r.error();
on_error(Status::Error(r.error().public_message()));
}
- if (close_after_write_ && !stream_connection_.need_flush_write()) {
+ if (close_after_write_ && !fd_.need_flush_write()) {
return stop();
}
}
- if (stream_connection_.get_fd().has_pending_error()) {
- auto pending_error = stream_connection_.get_pending_error();
+ Status pending_error;
+ if (fd_.get_poll_info().get_flags_local().has_pending_error()) {
+ pending_error = fd_.get_pending_error();
+ }
+ if (pending_error.is_ok() && write_sink_.status().is_error()) {
+ pending_error = std::move(write_sink_.status());
+ }
+ if (pending_error.is_ok() && read_sink_.status().is_error()) {
+ pending_error = std::move(read_sink_.status());
+ }
+ if (pending_error.is_error()) {
LOG(INFO) << pending_error;
if (!close_after_write_) {
on_error(Status::Error(pending_error.public_message()));
}
state_ = State::Close;
}
- if (can_close(stream_connection_)) {
- LOG(INFO) << "Can close the connection";
+
+ if (can_close_local(fd_)) {
+ LOG(DEBUG) << "Can close the connection";
state_ = State::Close;
}
if (state_ == State::Close) {
- LOG_IF(INFO, stream_connection_.need_flush_write()) << "Close nonempty connection";
- LOG_IF(INFO, want_read &&
- (stream_connection_.input_buffer().size() > 0 || current_query_->type_ != HttpQuery::Type::EMPTY))
- << "Close connection while reading request/response";
+ if (fd_.need_flush_write()) {
+ LOG(INFO) << "Close nonempty connection";
+ }
+ if (want_read && (!fd_.input_buffer().empty() || current_query_->type_ != HttpQuery::Type::Empty)) {
+ LOG(INFO) << "Close connection while reading request/response";
+ }
return stop();
}
}
+
+void HttpConnectionBase::on_start_migrate(int32 sched_id) {
+ Scheduler::unsubscribe(fd_.get_poll_info().get_pollable_fd_ref());
+}
+
+void HttpConnectionBase::on_finish_migrate() {
+ Scheduler::subscribe(fd_.get_poll_info().extract_pollable_fd(this));
+ live_event();
+}
+
} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.h
index 1d420a3175..95373dc326 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpConnectionBase.h
@@ -1,164 +1,77 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-
#include "td/net/HttpQuery.h"
#include "td/net/HttpReader.h"
+#include "td/net/SslStream.h"
+
+#include "td/actor/actor.h"
#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
-#include "td/utils/port/Fd.h"
-#include "td/utils/Slice.h"
+#include "td/utils/ByteFlow.h"
+#include "td/utils/port/IPAddress.h"
+#include "td/utils/port/SocketFd.h"
#include "td/utils/Status.h"
namespace td {
-class FdInterface {
- public:
- FdInterface() = default;
- FdInterface(const FdInterface &) = delete;
- FdInterface &operator=(const FdInterface &) = delete;
- FdInterface(FdInterface &&) = default;
- FdInterface &operator=(FdInterface &&) = default;
- virtual ~FdInterface() = default;
- virtual const Fd &get_fd() const = 0;
- virtual Fd &get_fd() = 0;
- virtual int32 get_flags() const = 0;
- virtual Status get_pending_error() TD_WARN_UNUSED_RESULT = 0;
-
- virtual Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT = 0;
- virtual Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT = 0;
-
- virtual void close() = 0;
- virtual bool empty() const = 0;
-};
-
-template <class FdT>
-class FdToInterface : public FdInterface {
- public:
- FdToInterface() = default;
- explicit FdToInterface(FdT fd) : fd_(std::move(fd)) {
- }
-
- const Fd &get_fd() const final {
- return fd_.get_fd();
- }
- Fd &get_fd() final {
- return fd_.get_fd();
- }
- int32 get_flags() const final {
- return fd_.get_flags();
- }
- Status get_pending_error() final TD_WARN_UNUSED_RESULT {
- return fd_.get_pending_error();
- }
-
- Result<size_t> write(Slice slice) final TD_WARN_UNUSED_RESULT {
- return fd_.write(slice);
- }
- Result<size_t> read(MutableSlice slice) final TD_WARN_UNUSED_RESULT {
- return fd_.read(slice);
- }
-
- void close() final {
- fd_.close();
- }
- bool empty() const final {
- return fd_.empty();
- }
-
- private:
- FdT fd_;
-};
-
-template <class FdT>
-std::unique_ptr<FdInterface> make_fd_interface(FdT fd) {
- return make_unique<FdToInterface<FdT>>(std::move(fd));
-}
-
-class FdProxy {
- public:
- FdProxy() = default;
- explicit FdProxy(std::unique_ptr<FdInterface> fd) : fd_(std::move(fd)) {
- }
-
- const Fd &get_fd() const {
- return fd_->get_fd();
- }
- Fd &get_fd() {
- return fd_->get_fd();
- }
- int32 get_flags() const {
- return fd_->get_flags();
- }
- Status get_pending_error() TD_WARN_UNUSED_RESULT {
- return fd_->get_pending_error();
- }
-
- Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT {
- return fd_->write(slice);
- }
- Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT {
- return fd_->read(slice);
- }
-
- void close() {
- fd_->close();
- }
- bool empty() const {
- return fd_->empty();
- }
-
- private:
- std::unique_ptr<FdInterface> fd_;
-};
-
-template <class FdT>
-FdProxy make_fd_proxy(FdT fd) {
- return FdProxy(make_fd_interface(std::move(fd)));
-}
-
namespace detail {
+
class HttpConnectionBase : public Actor {
public:
+ void write_next_noflush(BufferSlice buffer);
void write_next(BufferSlice buffer);
void write_ok();
void write_error(Status error);
protected:
enum class State { Read, Write, Close };
- template <class FdT>
- HttpConnectionBase(State state, FdT fd, size_t max_post_size, size_t max_files, int32 idle_timeout)
- : HttpConnectionBase(state, make_fd_proxy(std::move(fd)), max_post_size, max_files, idle_timeout) {
- }
- HttpConnectionBase(State state, FdProxy fd, size_t max_post_size, size_t max_files, int32 idle_timeout);
+ HttpConnectionBase(State state, BufferedFd<SocketFd> fd, SslStream ssl_stream, size_t max_post_size, size_t max_files,
+ int32 idle_timeout, int32 slow_scheduler_id);
private:
- using StreamConnection = BufferedFd<FdProxy>;
State state_;
- StreamConnection stream_connection_;
+
+ BufferedFd<SocketFd> fd_;
+ IPAddress peer_address_;
+ SslStream ssl_stream_;
+
+ ByteFlowSource read_source_{&fd_.input_buffer()};
+ ByteFlowSink read_sink_;
+
+ ChainBufferWriter write_buffer_;
+ ChainBufferReader write_buffer_reader_ = write_buffer_.extract_reader();
+ ByteFlowSource write_source_{&write_buffer_reader_};
+ ByteFlowMoveSink write_sink_{&fd_.output_buffer()};
+
size_t max_post_size_;
size_t max_files_;
int32 idle_timeout_;
HttpReader reader_;
- HttpQueryPtr current_query_;
+ unique_ptr<HttpQuery> current_query_;
bool close_after_write_ = false;
+ int32 slow_scheduler_id_{-1};
+
void live_event();
- void start_up() override;
- void tear_down() override;
- void timeout_expired() override;
- void loop() override;
+ void start_up() final;
+ void tear_down() final;
+ void timeout_expired() final;
+ void loop() final;
+
+ void on_start_migrate(int32 sched_id) final;
+ void on_finish_migrate() final;
- virtual void on_query(HttpQueryPtr) = 0;
+ virtual void on_query(unique_ptr<HttpQuery> query) = 0;
virtual void on_error(Status error) = 0;
};
+
} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.cpp
index ea299b3993..8493bd0997 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,7 +10,7 @@
namespace td {
-void HttpContentLengthByteFlow::loop() {
+bool HttpContentLengthByteFlow::loop() {
auto ready_size = input_->size();
if (ready_size > len_) {
ready_size = len_;
@@ -18,17 +18,19 @@ void HttpContentLengthByteFlow::loop() {
auto need_size = min(MIN_UPDATE_SIZE, len_);
if (ready_size < need_size) {
set_need_size(need_size);
- return;
+ return false;
}
output_.append(input_->cut_head(ready_size));
len_ -= ready_size;
if (len_ == 0) {
- return finish(Status::OK());
+ finish(Status::OK());
+ return false;
}
if (!is_input_active_) {
- return finish(Status::Error("Unexpected end of stream"));
+ finish(Status::Error("Unexpected end of stream"));
+ return false;
}
- on_output_updated();
+ return true;
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.h
index 18f86abdb0..6ebb7d339f 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpContentLengthByteFlow.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,7 +15,7 @@ class HttpContentLengthByteFlow final : public ByteFlowBase {
HttpContentLengthByteFlow() = default;
explicit HttpContentLengthByteFlow(size_t len) : len_(len) {
}
- void loop() override;
+ bool loop() final;
private:
static constexpr size_t MIN_UPDATE_SIZE = 1 << 14;
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.cpp
index b4f6e6d16b..6ca6975ac8 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.h
index 6f35843060..7ba795eae2 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpFile.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -30,7 +30,7 @@ class HttpFile {
HttpFile(const HttpFile &) = delete;
HttpFile &operator=(const HttpFile &) = delete;
- HttpFile(HttpFile &&other)
+ HttpFile(HttpFile &&other) noexcept
: field_name(std::move(other.field_name))
, name(std::move(other.name))
, content_type(std::move(other.content_type))
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpHeaderCreator.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpHeaderCreator.h
index d3e84e5dbf..72cebaf2be 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpHeaderCreator.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpHeaderCreator.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,6 +8,7 @@
#include "td/utils/logging.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
@@ -16,27 +17,26 @@ namespace td {
class HttpHeaderCreator {
public:
static constexpr size_t MAX_HEADER = 4096;
- HttpHeaderCreator() : sb_({header_, MAX_HEADER}) {
+ HttpHeaderCreator() : sb_(MutableSlice{header_, MAX_HEADER}) {
}
void init_ok() {
- sb_ = StringBuilder({header_, MAX_HEADER});
+ sb_ = StringBuilder(MutableSlice{header_, MAX_HEADER});
sb_ << "HTTP/1.1 200 OK\r\n";
}
void init_get(Slice url) {
- sb_ = StringBuilder({header_, MAX_HEADER});
+ sb_ = StringBuilder(MutableSlice{header_, MAX_HEADER});
sb_ << "GET " << url << " HTTP/1.1\r\n";
}
void init_post(Slice url) {
- sb_ = StringBuilder({header_, MAX_HEADER});
+ sb_ = StringBuilder(MutableSlice{header_, MAX_HEADER});
sb_ << "POST " << url << " HTTP/1.1\r\n";
}
void init_error(int code, Slice reason) {
- sb_ = StringBuilder({header_, MAX_HEADER});
+ sb_ = StringBuilder(MutableSlice{header_, MAX_HEADER});
sb_ << "HTTP/1.1 " << code << " " << reason << "\r\n";
}
void init_status_line(int http_status_code) {
- sb_ = StringBuilder({header_, MAX_HEADER});
- sb_ << "HTTP/1.1 " << http_status_code << " " << get_status_line(http_status_code) << "\r\n";
+ init_error(http_status_code, get_status_line(http_status_code));
}
void add_header(Slice key, Slice value) {
sb_ << key << ": " << value << "\r\n";
@@ -45,7 +45,7 @@ class HttpHeaderCreator {
add_header("Content-Type", type);
}
void set_content_size(size_t size) {
- add_header("Content-Length", to_string(size));
+ add_header("Content-Length", PSLICE() << size);
}
void set_keep_alive() {
add_header("Connection", "keep-alive");
@@ -57,7 +57,7 @@ class HttpHeaderCreator {
sb_ << content;
}
if (sb_.is_error()) {
- return Status::Error("Too much headers");
+ return Status::Error("Too many headers");
}
return sb_.as_cslice();
}
@@ -86,6 +86,8 @@ class HttpHeaderCreator {
return CSlice("Not Modified");
case 307:
return CSlice("Temporary Redirect");
+ case 308:
+ return CSlice("Permanent Redirect");
case 400:
return CSlice("Bad Request");
case 401:
@@ -102,16 +104,28 @@ class HttpHeaderCreator {
return CSlice("Request Timeout");
case 409:
return CSlice("Conflict");
+ case 410:
+ return CSlice("Gone");
case 411:
return CSlice("Length Required");
+ case 412:
+ return CSlice("Precondition Failed");
case 413:
return CSlice("Request Entity Too Large");
case 414:
return CSlice("Request-URI Too Long");
case 415:
return CSlice("Unsupported Media Type");
+ case 416:
+ return CSlice("Range Not Satisfiable");
+ case 417:
+ return CSlice("Expectation Failed");
case 418:
return CSlice("I'm a teapot");
+ case 421:
+ return CSlice("Misdirected Request");
+ case 426:
+ return CSlice("Upgrade Required");
case 429:
return CSlice("Too Many Requests");
case 431:
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.cpp
index 533cdd5407..88a5ca9350 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.cpp
@@ -1,22 +1,26 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpInboundConnection.h"
-#include "td/utils/logging.h"
+#include "td/net/SslStream.h"
+
+#include "td/utils/common.h"
namespace td {
-// HttpInboundConnection implementation
-HttpInboundConnection::HttpInboundConnection(SocketFd fd, size_t max_post_size, size_t max_files, int32 idle_timeout,
- ActorShared<Callback> callback)
- : HttpConnectionBase(State::Read, std::move(fd), max_post_size, max_files, idle_timeout)
+
+HttpInboundConnection::HttpInboundConnection(BufferedFd<SocketFd> fd, size_t max_post_size, size_t max_files,
+ int32 idle_timeout, ActorShared<Callback> callback,
+ int32 slow_scheduler_id)
+ : HttpConnectionBase(State::Read, std::move(fd), SslStream(), max_post_size, max_files, idle_timeout,
+ slow_scheduler_id)
, callback_(std::move(callback)) {
}
-void HttpInboundConnection::on_query(HttpQueryPtr query) {
+void HttpInboundConnection::on_query(unique_ptr<HttpQuery> query) {
CHECK(!callback_.empty());
send_closure(callback_, &Callback::handle, std::move(query), ActorOwn<HttpInboundConnection>(actor_id(this)));
}
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.h
index 013b024592..b92e92c9b1 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpInboundConnection.h
@@ -1,16 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-
#include "td/net/HttpConnectionBase.h"
#include "td/net/HttpQuery.h"
+#include "td/actor/actor.h"
+
+#include "td/utils/BufferedFd.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Status.h"
@@ -20,20 +21,20 @@ class HttpInboundConnection final : public detail::HttpConnectionBase {
public:
class Callback : public Actor {
public:
- virtual void handle(HttpQueryPtr query, ActorOwn<HttpInboundConnection> connection) = 0;
+ virtual void handle(unique_ptr<HttpQuery> query, ActorOwn<HttpInboundConnection> connection) = 0;
};
// Inherited interface
// void write_next(BufferSlice buffer);
// void write_ok();
// void write_error(Status error);
- HttpInboundConnection(SocketFd fd, size_t max_post_size, size_t max_files, int32 idle_timeout,
- ActorShared<Callback> callback);
+ HttpInboundConnection(BufferedFd<SocketFd> fd, size_t max_post_size, size_t max_files, int32 idle_timeout,
+ ActorShared<Callback> callback, int32 slow_scheduler_id = -1);
private:
- void on_query(HttpQueryPtr query) override;
- void on_error(Status error) override;
- void hangup() override {
+ void on_query(unique_ptr<HttpQuery> query) final;
+ void on_error(Status error) final;
+ void hangup() final {
callback_.release();
stop();
}
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.cpp
index f6efe7e07a..2bf8073809 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.cpp
@@ -1,16 +1,16 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpOutboundConnection.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
namespace td {
-// HttpOutboundConnection implementation
-void HttpOutboundConnection::on_query(HttpQueryPtr query) {
+
+void HttpOutboundConnection::on_query(unique_ptr<HttpQuery> query) {
CHECK(!callback_.empty());
send_closure(callback_, &Callback::handle, std::move(query));
}
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.h
index d7496c59c4..ca1f49f7d9 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpOutboundConnection.h
@@ -1,16 +1,19 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-
#include "td/net/HttpConnectionBase.h"
#include "td/net/HttpQuery.h"
+#include "td/net/SslStream.h"
+
+#include "td/actor/actor.h"
+#include "td/utils/BufferedFd.h"
+#include "td/utils/port/SocketFd.h"
#include "td/utils/Status.h"
namespace td {
@@ -19,13 +22,13 @@ class HttpOutboundConnection final : public detail::HttpConnectionBase {
public:
class Callback : public Actor {
public:
- virtual void handle(HttpQueryPtr query) = 0;
+ virtual void handle(unique_ptr<HttpQuery> query) = 0;
virtual void on_connection_error(Status error) = 0; // TODO rename to on_error
};
- template <class FdT>
- HttpOutboundConnection(FdT fd, size_t max_post_size, size_t max_files, int32 idle_timeout,
- ActorShared<Callback> callback)
- : HttpConnectionBase(HttpConnectionBase::State::Write, std::move(fd), max_post_size, max_files, idle_timeout)
+ HttpOutboundConnection(BufferedFd<SocketFd> fd, SslStream ssl_stream, size_t max_post_size, size_t max_files,
+ int32 idle_timeout, ActorShared<Callback> callback, int32 slow_scheduler_id = -1)
+ : HttpConnectionBase(HttpConnectionBase::State::Write, std::move(fd), std::move(ssl_stream), max_post_size,
+ max_files, idle_timeout, slow_scheduler_id)
, callback_(std::move(callback)) {
}
// Inherited interface
@@ -34,9 +37,9 @@ class HttpOutboundConnection final : public detail::HttpConnectionBase {
// void write_error(Status error);
private:
- void on_query(HttpQueryPtr query) override;
- void on_error(Status error) override;
- void hangup() override {
+ void on_query(unique_ptr<HttpQuery> query) final;
+ void on_error(Status error) final;
+ void hangup() final {
callback_.release();
HttpConnectionBase::hangup();
}
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpProxy.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpProxy.cpp
new file mode 100644
index 0000000000..a676e691f6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpProxy.cpp
@@ -0,0 +1,110 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/net/HttpProxy.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+
+namespace td {
+
+void HttpProxy::send_connect() {
+ VLOG(proxy) << "Send CONNECT to proxy";
+ CHECK(state_ == State::SendConnect);
+ state_ = State::WaitConnectResponse;
+
+ string host = PSTRING() << ip_address_.get_ip_host() << ':' << ip_address_.get_port();
+ string proxy_authorization;
+ if (!username_.empty() || !password_.empty()) {
+ auto userinfo = PSTRING() << username_ << ':' << password_;
+ proxy_authorization = PSTRING() << "Proxy-Authorization: basic " << base64_encode(userinfo) << "\r\n";
+ }
+ fd_.output_buffer().append(PSLICE() << "CONNECT " << host << " HTTP/1.1\r\n"
+ << "Host: " << host << "\r\n"
+ << proxy_authorization << "\r\n");
+}
+
+Status HttpProxy::wait_connect_response() {
+ CHECK(state_ == State::WaitConnectResponse);
+ auto it = fd_.input_buffer().clone();
+ VLOG(proxy) << "Receive CONNECT response of size " << it.size();
+ if (it.size() < 12 + 1 + 1) {
+ return Status::OK();
+ }
+ char begin_buf[12];
+ MutableSlice begin(begin_buf, 12);
+ it.advance(12, begin);
+ if ((begin.substr(0, 10) != "HTTP/1.1 2" && begin.substr(0, 10) != "HTTP/1.0 2") || !is_digit(begin[10]) ||
+ !is_digit(begin[11])) {
+ char buf[1024];
+ size_t len = min(sizeof(buf), it.size());
+ it.advance(len, MutableSlice{buf, sizeof(buf)});
+ VLOG(proxy) << "Failed to connect: " << format::escaped(Slice(buf, len));
+ return Status::Error(PSLICE() << "Failed to connect to " << ip_address_.get_ip_host() << ':'
+ << ip_address_.get_port());
+ }
+
+ size_t total_size = 12;
+ char c;
+ MutableSlice c_slice(&c, 1);
+ while (!it.empty()) {
+ it.advance(1, c_slice);
+ total_size++;
+ if (c == '\n') {
+ break;
+ }
+ }
+ if (it.empty()) {
+ return Status::OK();
+ }
+
+ char prev = '\n';
+ size_t pos = 0;
+ bool found = false;
+ while (!it.empty()) {
+ it.advance(1, c_slice);
+ total_size++;
+ if (c == '\n') {
+ if (pos == 0 || (pos == 1 && prev == '\r')) {
+ found = true;
+ break;
+ }
+ pos = 0;
+ } else {
+ pos++;
+ }
+ prev = c;
+ }
+ if (!found) {
+ CHECK(it.empty());
+ return Status::OK();
+ }
+
+ fd_.input_buffer().advance(total_size);
+ stop();
+ return Status::OK();
+}
+
+Status HttpProxy::loop_impl() {
+ switch (state_) {
+ case State::SendConnect:
+ send_connect();
+ break;
+ case State::WaitConnectResponse:
+ TRY_STATUS(wait_connect_response());
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return Status::OK();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpProxy.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpProxy.h
new file mode 100644
index 0000000000..fd9f6233ec
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpProxy.h
@@ -0,0 +1,28 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/net/TransparentProxy.h"
+
+#include "td/utils/Status.h"
+
+namespace td {
+
+class HttpProxy final : public TransparentProxy {
+ public:
+ using TransparentProxy::TransparentProxy;
+
+ private:
+ enum class State { SendConnect, WaitConnectResponse } state_ = State::SendConnect;
+
+ void send_connect();
+ Status wait_connect_response();
+
+ Status loop_impl() final;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.cpp
index b4af0eef3f..6cf0028f4d 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.cpp
@@ -1,51 +1,67 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/HttpQuery.h"
+#include "td/utils/misc.h"
+
#include <algorithm>
namespace td {
-Slice HttpQuery::header(Slice key) const {
+Slice HttpQuery::get_header(Slice key) const {
auto it = std::find_if(headers_.begin(), headers_.end(),
[&key](const std::pair<MutableSlice, MutableSlice> &s) { return s.first == key; });
return it == headers_.end() ? Slice() : it->second;
}
-MutableSlice HttpQuery::arg(Slice key) const {
+MutableSlice HttpQuery::get_arg(Slice key) const {
auto it = std::find_if(args_.begin(), args_.end(),
[&key](const std::pair<MutableSlice, MutableSlice> &s) { return s.first == key; });
return it == args_.end() ? MutableSlice() : it->second;
}
-std::vector<std::pair<string, string>> HttpQuery::string_args() const {
- std::vector<std::pair<string, string>> res;
+vector<std::pair<string, string>> HttpQuery::get_args() const {
+ vector<std::pair<string, string>> res;
+ res.reserve(args_.size());
for (auto &it : args_) {
- res.push_back(std::make_pair(it.first.str(), it.second.str()));
+ res.emplace_back(it.first.str(), it.second.str());
}
return res;
}
+int HttpQuery::get_retry_after() const {
+ auto value = get_header("retry-after");
+ if (value.empty()) {
+ return 0;
+ }
+ auto r_retry_after = to_integer_safe<int>(value);
+ if (r_retry_after.is_error()) {
+ return 0;
+ }
+
+ return td::max(0, r_retry_after.ok());
+}
+
StringBuilder &operator<<(StringBuilder &sb, const HttpQuery &q) {
switch (q.type_) {
- case HttpQuery::Type::EMPTY:
+ case HttpQuery::Type::Empty:
sb << "EMPTY";
return sb;
- case HttpQuery::Type::GET:
+ case HttpQuery::Type::Get:
sb << "GET";
break;
- case HttpQuery::Type::POST:
+ case HttpQuery::Type::Post:
sb << "POST";
break;
- case HttpQuery::Type::RESPONSE:
+ case HttpQuery::Type::Response:
sb << "RESPONSE";
break;
}
- if (q.type_ == HttpQuery::Type::RESPONSE) {
+ if (q.type_ == HttpQuery::Type::Response) {
sb << ":" << q.code_ << ":" << q.reason_;
} else {
sb << ":" << q.url_path_;
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.h
index acab74ac66..5abd4ae517 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpQuery.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,6 +10,7 @@
#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/port/IPAddress.h"
#include "td/utils/Slice.h"
#include "td/utils/StringBuilder.h"
@@ -19,28 +20,30 @@ namespace td {
class HttpQuery {
public:
- enum class Type : int8 { EMPTY, GET, POST, RESPONSE };
+ enum class Type : int8 { Empty, Get, Post, Response };
- std::vector<BufferSlice> container_;
- Type type_;
+ vector<BufferSlice> container_;
+ Type type_ = Type::Empty;
+ int32 code_ = 0;
MutableSlice url_path_;
- std::vector<std::pair<MutableSlice, MutableSlice>> args_;
- int code_;
+ vector<std::pair<MutableSlice, MutableSlice>> args_;
MutableSlice reason_;
- bool keep_alive_;
- std::vector<std::pair<MutableSlice, MutableSlice>> headers_;
- std::vector<HttpFile> files_;
+ bool keep_alive_ = true;
+ vector<std::pair<MutableSlice, MutableSlice>> headers_;
+ vector<HttpFile> files_;
MutableSlice content_;
- Slice header(Slice key) const;
+ IPAddress peer_address_;
- MutableSlice arg(Slice key) const;
+ Slice get_header(Slice key) const;
- std::vector<std::pair<string, string>> string_args() const;
-};
+ MutableSlice get_arg(Slice key) const;
+
+ vector<std::pair<string, string>> get_args() const;
-using HttpQueryPtr = std::unique_ptr<HttpQuery>;
+ int get_retry_after() const;
+};
StringBuilder &operator<<(StringBuilder &sb, const HttpQuery &q);
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.cpp
index 1cfa7666a7..b86039052c 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -17,40 +17,18 @@
#include "td/utils/Parser.h"
#include "td/utils/PathView.h"
#include "td/utils/port/path.h"
+#include "td/utils/SliceBuilder.h"
+#include <cstddef>
#include <cstring>
namespace td {
constexpr const char HttpReader::TEMP_DIRECTORY_PREFIX[];
-static size_t urldecode(Slice from, MutableSlice to, bool decode_plus_sign_as_space) {
- size_t to_i = 0;
- CHECK(to.size() >= from.size());
- for (size_t from_i = 0, n = from.size(); from_i < n; from_i++) {
- if (from[from_i] == '%' && from_i + 2 < n) {
- int high = hex_to_int(from[from_i + 1]);
- int low = hex_to_int(from[from_i + 2]);
- if (high < 16 && low < 16) {
- to[to_i++] = static_cast<char>(high * 16 + low);
- from_i += 2;
- continue;
- }
- }
- to[to_i++] = decode_plus_sign_as_space && from[from_i] == '+' ? ' ' : from[from_i];
- }
- return to_i;
-}
-
-static MutableSlice urldecode_inplace(MutableSlice str, bool decode_plus_sign_as_space) {
- size_t result_size = urldecode(str, str, decode_plus_sign_as_space);
- str.truncate(result_size);
- return str;
-}
-
void HttpReader::init(ChainBufferReader *input, size_t max_post_size, size_t max_files) {
input_ = input;
- state_ = ReadHeaders;
+ state_ = State::ReadHeaders;
headers_read_length_ = 0;
content_length_ = 0;
query_ = nullptr;
@@ -60,17 +38,21 @@ void HttpReader::init(ChainBufferReader *input, size_t max_post_size, size_t max
total_headers_length_ = 0;
}
-Result<size_t> HttpReader::read_next(HttpQuery *query) {
+Result<size_t> HttpReader::read_next(HttpQuery *query, bool can_be_slow) {
if (query_ != query) {
CHECK(query_ == nullptr);
query_ = query;
}
size_t need_size = input_->size() + 1;
while (true) {
- if (state_ != ReadHeaders) {
+ if (state_ != State::ReadHeaders) {
+ gzip_flow_.wakeup();
flow_source_.wakeup();
if (flow_sink_.is_ready() && flow_sink_.status().is_error()) {
- return Status::Error(400, "Bad Request: " + flow_sink_.status().message().str());
+ if (!temp_file_.empty()) {
+ clean_temporary_file();
+ }
+ return Status::Error(400, PSLICE() << "Bad Request: " << flow_sink_.status().message());
}
need_size = flow_source_.get_need_size();
if (need_size == 0) {
@@ -78,7 +60,7 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
}
}
switch (state_) {
- case ReadHeaders: {
+ case State::ReadHeaders: {
auto result = split_header();
if (result.is_error() || result.ok() != 0) {
return result;
@@ -104,12 +86,16 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
if (content_encoding_.empty()) {
} else if (content_encoding_ == "gzip" || content_encoding_ == "deflate") {
- gzip_flow_ = GzipByteFlow(Gzip::Decode);
- gzip_flow_.set_max_output_size(MAX_FILE_SIZE);
+ gzip_flow_ = GzipByteFlow(Gzip::Mode::Decode);
+ GzipByteFlow::Options options;
+ options.write_watermark.low = 0;
+ options.write_watermark.high = max(max_post_size_, MAX_TOTAL_PARAMETERS_LENGTH + 1);
+ gzip_flow_.set_options(options);
+ gzip_flow_.set_max_output_size(MAX_CONTENT_SIZE);
*source >> gzip_flow_;
source = &gzip_flow_;
} else {
- LOG(ERROR) << "Unsupported " << tag("content-encoding", content_encoding_);
+ LOG(WARNING) << "Unsupported " << tag("content-encoding", content_encoding_);
return Status::Error(415, "Unsupported Media Type: unsupported content-encoding");
}
@@ -117,26 +103,26 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
*source >> flow_sink_;
content_ = flow_sink_.get_output();
- if (content_length_ > MAX_CONTENT_SIZE) {
+ if (content_length_ >= MAX_CONTENT_SIZE) {
return Status::Error(413, PSLICE() << "Request Entity Too Large: content length is " << content_length_);
}
- if (std::strstr(content_type_lowercased_.c_str(), "multipart/form-data")) {
- state_ = ReadMultipartFormData;
+ if (content_type_lowercased_.find("multipart/form-data") != string::npos) {
+ state_ = State::ReadMultipartFormData;
const char *p = std::strstr(content_type_lowercased_.c_str(), "boundary");
if (p == nullptr) {
return Status::Error(400, "Bad Request: boundary not found");
}
p += 8;
- ptrdiff_t offset = p - content_type_lowercased_.c_str();
+ std::ptrdiff_t offset = p - content_type_lowercased_.c_str();
p = static_cast<const char *>(
std::memchr(content_type_.begin() + offset, '=', content_type_.size() - offset));
if (p == nullptr) {
return Status::Error(400, "Bad Request: boundary value not found");
}
p++;
- const char *end_p = static_cast<const char *>(std::memchr(p, ';', content_type_.end() - p));
+ auto end_p = static_cast<const char *>(std::memchr(p, ';', content_type_.end() - p));
if (end_p == nullptr) {
end_p = content_type_.end();
}
@@ -145,27 +131,32 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
end_p--;
}
+ CHECK(p != nullptr);
Slice boundary(p, static_cast<size_t>(end_p - p));
if (boundary.empty() || boundary.size() > MAX_BOUNDARY_LENGTH) {
return Status::Error(400, "Bad Request: boundary too big or empty");
}
boundary_ = "\r\n--" + boundary.str();
- form_data_parse_state_ = SkipPrologue;
+ form_data_parse_state_ = FormDataParseState::SkipPrologue;
form_data_read_length_ = 0;
form_data_skipped_length_ = 0;
- } else if (std::strstr(content_type_lowercased_.c_str(), "application/x-www-form-urlencoded") ||
- std::strstr(content_type_lowercased_.c_str(), "application/json")) {
- state_ = ReadArgs;
+ } else if (content_type_lowercased_.find("application/x-www-form-urlencoded") != string::npos ||
+ content_type_lowercased_.find("application/json") != string::npos) {
+ state_ = State::ReadArgs;
} else {
form_data_skipped_length_ = 0;
- state_ = ReadContent;
+ state_ = State::ReadContent;
}
continue;
}
- case ReadContent: {
+ case State::ReadContent: {
if (content_->size() > max_post_size_) {
- state_ = ReadContentToFile;
+ state_ = State::ReadContentToFile;
+ GzipByteFlow::Options options;
+ options.write_watermark.low = 4 << 20;
+ options.write_watermark.high = 8 << 20;
+ gzip_flow_.set_options(options);
continue;
}
if (flow_sink_.is_ready()) {
@@ -177,7 +168,10 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
return need_size;
}
- case ReadContentToFile: {
+ case State::ReadContentToFile: {
+ if (!can_be_slow) {
+ return Status::Error("SLOW");
+ }
// save content to a file
if (temp_file_.empty()) {
auto file = open_temp_file("file");
@@ -187,27 +181,32 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
}
auto size = content_->size();
- if (size) {
+ bool restart = false;
+ if (size > (1 << 20) || flow_sink_.is_ready()) {
TRY_STATUS(save_file_part(content_->cut_head(size).move_as_buffer_slice()));
+ restart = true;
}
if (flow_sink_.is_ready()) {
query_->files_.emplace_back("file", "", content_type_.str(), file_size_, temp_file_name_);
close_temp_file();
break;
}
+ if (restart) {
+ continue;
+ }
return need_size;
}
- case ReadArgs: {
+ case State::ReadArgs: {
auto size = content_->size();
if (size > MAX_TOTAL_PARAMETERS_LENGTH - total_parameters_length_) {
- return Status::Error(413, "Request Entity Too Large: too much parameters");
+ return Status::Error(413, "Request Entity Too Large: too many parameters");
}
if (flow_sink_.is_ready()) {
query_->container_.emplace_back(content_->cut_head(size).move_as_buffer_slice());
Status result;
- if (std::strstr(content_type_lowercased_.c_str(), "application/x-www-form-urlencoded")) {
+ if (content_type_lowercased_.find("application/x-www-form-urlencoded") != string::npos) {
result = parse_parameters(query_->container_.back().as_slice());
} else {
result = parse_json_parameters(query_->container_.back().as_slice());
@@ -224,10 +223,12 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
return need_size;
}
- case ReadMultipartFormData: {
- TRY_RESULT(result, parse_multipart_form_data());
- if (result) {
- break;
+ case State::ReadMultipartFormData: {
+ if (!content_->empty() || flow_sink_.is_ready()) {
+ TRY_RESULT(result, parse_multipart_form_data(can_be_slow));
+ if (result) {
+ break;
+ }
}
return need_size;
}
@@ -244,18 +245,19 @@ Result<size_t> HttpReader::read_next(HttpQuery *query) {
// returns Status on wrong request
// returns true if parsing has finished
// returns false if need more data
-Result<bool> HttpReader::parse_multipart_form_data() {
+Result<bool> HttpReader::parse_multipart_form_data(bool can_be_slow) {
while (true) {
- LOG(DEBUG) << "Parsing multipart form data in state " << form_data_parse_state_;
+ LOG(DEBUG) << "Parsing multipart form data in state " << static_cast<int32>(form_data_parse_state_)
+ << " with already read length " << form_data_read_length_;
switch (form_data_parse_state_) {
- case SkipPrologue:
+ case FormDataParseState::SkipPrologue:
if (find_boundary(content_->clone(), {boundary_.c_str() + 2, boundary_.size() - 2}, form_data_read_length_)) {
size_t to_skip = form_data_read_length_ + (boundary_.size() - 2);
content_->advance(to_skip);
form_data_skipped_length_ += to_skip;
form_data_read_length_ = 0;
- form_data_parse_state_ = ReadPartHeaders;
+ form_data_parse_state_ = FormDataParseState::ReadPartHeaders;
continue;
}
@@ -263,14 +265,14 @@ Result<bool> HttpReader::parse_multipart_form_data() {
form_data_skipped_length_ += form_data_read_length_;
form_data_read_length_ = 0;
return false;
- case ReadPartHeaders:
+ case FormDataParseState::ReadPartHeaders:
if (find_boundary(content_->clone(), "\r\n\r\n", form_data_read_length_)) {
total_headers_length_ += form_data_read_length_;
if (total_headers_length_ > MAX_TOTAL_HEADERS_LENGTH) {
return Status::Error(431, "Request Header Fields Too Large: total headers size exceeded");
}
if (form_data_read_length_ == 0) {
- // there is no headers at all
+ // there are no headers at all
return Status::Error(400, "Bad Request: headers in multipart/form-data are empty");
}
@@ -287,6 +289,7 @@ Result<bool> HttpReader::parse_multipart_form_data() {
file_field_name_.clear();
field_content_type_ = "application/octet-stream";
file_name_.clear();
+ has_file_name_ = false;
CHECK(temp_file_.empty());
temp_file_name_.clear();
@@ -317,35 +320,70 @@ Result<bool> HttpReader::parse_multipart_form_data() {
header_value.remove_prefix(10);
while (true) {
header_value = trim(header_value);
- const char *key_end =
+ const auto *key_end =
static_cast<const char *>(std::memchr(header_value.data(), '=', header_value.size()));
if (key_end == nullptr) {
break;
}
size_t key_size = key_end - header_value.data();
- auto key = header_value.substr(0, key_size);
- key = trim(key);
+ auto key = trim(header_value.substr(0, key_size));
header_value.remove_prefix(key_size + 1);
- const char *value_end =
- static_cast<const char *>(std::memchr(header_value.data(), ';', header_value.size()));
- size_t value_size;
- if (value_end == nullptr) {
- value_size = header_value.size();
- } else {
- value_size = value_end - header_value.data();
+
+ while (!header_value.empty() && is_space(header_value[0])) {
+ header_value.remove_prefix(1);
}
- auto value = header_value.substr(0, value_size);
- value = trim(value);
- if (value.size() > 1u && value[0] == '"' && value.back() == '"') {
- value = {value.data() + 1, value.size() - 2};
+
+ MutableSlice value;
+ if (!header_value.empty() && header_value[0] == '"') { // quoted-string
+ char *value_end = header_value.data() + 1;
+ const char *pos = value_end;
+ while (true) {
+ if (pos == header_value.data() + header_value.size()) {
+ return Status::Error(400, "Bad Request: unclosed quoted string in Content-Disposition header");
+ }
+ char c = *pos++;
+ if (c == '"') {
+ break;
+ }
+ if (c == '\\') {
+ if (pos == header_value.data() + header_value.size()) {
+ return Status::Error(400, "Bad Request: wrong escape sequence in Content-Disposition header");
+ }
+ c = *pos++;
+ }
+ *value_end++ = c;
+ }
+ value = header_value.substr(1, value_end - header_value.data() - 1);
+ header_value.remove_prefix(pos - header_value.data());
+
+ while (!header_value.empty() && is_space(header_value[0])) {
+ header_value.remove_prefix(1);
+ }
+ if (!header_value.empty()) {
+ if (header_value[0] != ';') {
+ return Status::Error(400, "Bad Request: expected ';' in Content-Disposition header");
+ }
+ header_value.remove_prefix(1);
+ }
+ } else { // token
+ auto value_end =
+ static_cast<const char *>(std::memchr(header_value.data(), ';', header_value.size()));
+ if (value_end != nullptr) {
+ auto value_size = static_cast<size_t>(value_end - header_value.data());
+ value = trim(header_value.substr(0, value_size));
+ header_value.remove_prefix(value_size + 1);
+ } else {
+ value = trim(header_value);
+ header_value = MutableSlice();
+ }
}
- header_value.remove_prefix(value_size + (header_value.size() > value_size));
if (key == "name") {
field_name_ = value;
} else if (key == "filename") {
file_name_ = value.str();
+ has_file_name_ = true;
} else {
// ignore unknown parts of header
}
@@ -365,10 +403,10 @@ Result<bool> HttpReader::parse_multipart_form_data() {
return Status::Error(400, "Bad Request: field name in multipart/form-data not found");
}
- if (!file_name_.empty()) {
+ if (has_file_name_) {
// file
if (query_->files_.size() == max_files_) {
- return Status::Error(413, "Request Entity Too Large: too much files attached");
+ return Status::Error(413, "Request Entity Too Large: too many files attached");
}
auto file = open_temp_file(file_name_);
if (file.is_error()) {
@@ -377,11 +415,11 @@ Result<bool> HttpReader::parse_multipart_form_data() {
// don't need to save headers for files
file_field_name_ = field_name_.str();
- form_data_parse_state_ = ReadFile;
+ form_data_parse_state_ = FormDataParseState::ReadFile;
} else {
// save headers for query parameters. They contain header names
query_->container_.push_back(std::move(headers));
- form_data_parse_state_ = ReadPartValue;
+ form_data_parse_state_ = FormDataParseState::ReadPartValue;
}
continue;
@@ -391,10 +429,10 @@ Result<bool> HttpReader::parse_multipart_form_data() {
return Status::Error(431, "Request Header Fields Too Large: total headers size exceeded");
}
return false;
- case ReadPartValue:
+ case FormDataParseState::ReadPartValue:
if (find_boundary(content_->clone(), boundary_, form_data_read_length_)) {
if (total_parameters_length_ + form_data_read_length_ > MAX_TOTAL_PARAMETERS_LENGTH) {
- return Status::Error(413, "Request Entity Too Large: too much parameters in form data");
+ return Status::Error(413, "Request Entity Too Large: too many parameters in form data");
}
query_->container_.emplace_back(content_->cut_head(form_data_read_length_).move_as_buffer_slice());
@@ -416,16 +454,19 @@ Result<bool> HttpReader::parse_multipart_form_data() {
query_->args_.emplace_back(field_name_, value);
}
- form_data_parse_state_ = CheckForLastBoundary;
+ form_data_parse_state_ = FormDataParseState::CheckForLastBoundary;
continue;
}
CHECK(content_->size() < form_data_read_length_ + boundary_.size());
if (total_parameters_length_ + form_data_read_length_ > MAX_TOTAL_PARAMETERS_LENGTH) {
- return Status::Error(413, "Request Entity Too Large: too much parameters in form data");
+ return Status::Error(413, "Request Entity Too Large: too many parameters in form data");
}
return false;
- case ReadFile: {
+ case FormDataParseState::ReadFile: {
+ if (!can_be_slow) {
+ return Status::Error("SLOW");
+ }
if (find_boundary(content_->clone(), boundary_, form_data_read_length_)) {
auto file_part = content_->cut_head(form_data_read_length_).move_as_buffer_slice();
content_->advance(boundary_.size());
@@ -437,7 +478,7 @@ Result<bool> HttpReader::parse_multipart_form_data() {
query_->files_.emplace_back(file_field_name_, file_name_, field_content_type_, file_size_, temp_file_name_);
close_temp_file();
- form_data_parse_state_ = CheckForLastBoundary;
+ form_data_parse_state_ = FormDataParseState::CheckForLastBoundary;
continue;
}
@@ -450,7 +491,7 @@ Result<bool> HttpReader::parse_multipart_form_data() {
TRY_STATUS(save_file_part(std::move(file_part)));
return false;
}
- case CheckForLastBoundary: {
+ case FormDataParseState::CheckForLastBoundary: {
if (content_->size() < 2) {
// need more data
return false;
@@ -462,13 +503,13 @@ Result<bool> HttpReader::parse_multipart_form_data() {
if (x[0] == '-' && x[1] == '-') {
content_->advance(2);
form_data_skipped_length_ += 2;
- form_data_parse_state_ = SkipEpilogue;
+ form_data_parse_state_ = FormDataParseState::SkipEpilogue;
} else {
- form_data_parse_state_ = ReadPartHeaders;
+ form_data_parse_state_ = FormDataParseState::ReadPartHeaders;
}
continue;
}
- case SkipEpilogue: {
+ case FormDataParseState::SkipEpilogue: {
size_t size = content_->size();
LOG(DEBUG) << "Skipping epilogue. Have " << size << " bytes";
content_->advance(size);
@@ -512,16 +553,20 @@ void HttpReader::process_header(MutableSlice header_name, MutableSlice header_va
header_name = trim(header_name);
header_value = trim(header_value); // TODO need to remove "\r\n" from value
to_lower_inplace(header_name);
- LOG(DEBUG) << "process_header [" << header_name << "=>" << header_value << "]";
+ LOG(DEBUG) << "Process header [" << header_name << "=>" << header_value << "]";
query_->headers_.emplace_back(header_name, header_value);
- // TODO: check if protocol is HTTP/1.1
- query_->keep_alive_ = true;
if (header_name == "content-length") {
- content_length_ = to_integer<size_t>(header_value);
+ auto content_length = to_integer<uint64>(header_value);
+ if (content_length > MAX_CONTENT_SIZE) {
+ content_length = MAX_CONTENT_SIZE;
+ }
+ content_length_ = static_cast<size_t>(content_length);
} else if (header_name == "connection") {
to_lower_inplace(header_value);
if (header_value == "close") {
query_->keep_alive_ = false;
+ } else {
+ query_->keep_alive_ = true;
}
} else if (header_name == "content-type") {
content_type_ = header_value;
@@ -542,7 +587,7 @@ Status HttpReader::parse_url(MutableSlice url) {
url_path_size++;
}
- query_->url_path_ = urldecode_inplace({url.data(), url_path_size}, false);
+ query_->url_path_ = url_decode_inplace({url.data(), url_path_size}, false);
if (url_path_size == url.size() || url[url_path_size] != '?') {
return Status::OK();
@@ -553,7 +598,7 @@ Status HttpReader::parse_url(MutableSlice url) {
Status HttpReader::parse_parameters(MutableSlice parameters) {
total_parameters_length_ += parameters.size();
if (total_parameters_length_ > MAX_TOTAL_PARAMETERS_LENGTH) {
- return Status::Error(413, "Request Entity Too Large: too much parameters");
+ return Status::Error(413, "Request Entity Too Large: too many parameters");
}
LOG(DEBUG) << "Parse parameters: \"" << parameters << "\"";
@@ -562,9 +607,9 @@ Status HttpReader::parse_parameters(MutableSlice parameters) {
auto key_value = parser.read_till_nofail('&');
parser.skip_nofail('&');
Parser kv_parser(key_value);
- auto key = urldecode_inplace(kv_parser.read_till_nofail('='), true);
+ auto key = url_decode_inplace(kv_parser.read_till_nofail('='), true);
kv_parser.skip_nofail('=');
- auto value = urldecode_inplace(kv_parser.data(), true);
+ auto value = url_decode_inplace(kv_parser.data(), true);
query_->args_.emplace_back(key, value);
}
@@ -579,15 +624,27 @@ Status HttpReader::parse_json_parameters(MutableSlice parameters) {
total_parameters_length_ += parameters.size();
if (total_parameters_length_ > MAX_TOTAL_PARAMETERS_LENGTH) {
- return Status::Error(413, "Request Entity Too Large: too much parameters");
+ return Status::Error(413, "Request Entity Too Large: too many parameters");
}
- LOG(DEBUG) << "Parse json parameters: \"" << parameters << "\"";
+ LOG(DEBUG) << "Parse JSON parameters: \"" << parameters << "\"";
Parser parser(parameters);
parser.skip_whitespaces();
+ if (parser.peek_char() == '"') {
+ auto r_value = json_string_decode(parser);
+ if (r_value.is_error()) {
+ return Status::Error(400, PSLICE() << "Bad Request: can't parse string content: " << r_value.error().message());
+ }
+ if (!parser.empty()) {
+ return Status::Error(400, "Bad Request: extra data after string");
+ }
+ query_->container_.emplace_back("content");
+ query_->args_.emplace_back(query_->container_.back().as_slice(), r_value.move_as_ok());
+ return Status::OK();
+ }
parser.skip('{');
if (parser.status().is_error()) {
- return Status::Error(400, "Bad Request: json object expected");
+ return Status::Error(400, "Bad Request: JSON object expected");
}
while (true) {
parser.skip_whitespaces();
@@ -603,29 +660,29 @@ Status HttpReader::parse_json_parameters(MutableSlice parameters) {
}
auto r_key = json_string_decode(parser);
if (r_key.is_error()) {
- return Status::Error(400, string("Bad Request: can't parse parameter name: ") + r_key.error().message().c_str());
+ return Status::Error(400, PSLICE() << "Bad Request: can't parse parameter name: " << r_key.error().message());
}
parser.skip_whitespaces();
if (!parser.try_skip(':')) {
return Status::Error(400, "Bad Request: can't parse object, ':' expected");
}
parser.skip_whitespaces();
- Result<MutableSlice> r_value;
- if (parser.peek_char() == '"') {
- r_value = json_string_decode(parser);
- } else {
- const int32 DEFAULT_MAX_DEPTH = 100;
- auto begin = parser.ptr();
- auto result = do_json_skip(parser, DEFAULT_MAX_DEPTH);
- if (result.is_ok()) {
- r_value = MutableSlice(begin, parser.ptr());
+ auto r_value = [&]() -> Result<MutableSlice> {
+ if (parser.peek_char() == '"') {
+ return json_string_decode(parser);
} else {
- r_value = result.move_as_error();
+ const int32 DEFAULT_MAX_DEPTH = 100;
+ auto begin = parser.ptr();
+ auto result = do_json_skip(parser, DEFAULT_MAX_DEPTH);
+ if (result.is_ok()) {
+ return MutableSlice(begin, parser.ptr());
+ } else {
+ return result.move_as_error();
+ }
}
- }
+ }();
if (r_value.is_error()) {
- return Status::Error(400,
- string("Bad Request: can't parse parameter value: ") + r_value.error().message().c_str());
+ return Status::Error(400, PSLICE() << "Bad Request: can't parse parameter value: " << r_value.error().message());
}
query_->args_.emplace_back(r_key.move_as_ok(), r_value.move_as_ok());
@@ -645,12 +702,16 @@ Status HttpReader::parse_head(MutableSlice head) {
parser.skip(' ');
// GET POST HTTP/1.1
if (type == "GET") {
- query_->type_ = HttpQuery::Type::GET;
+ query_->type_ = HttpQuery::Type::Get;
} else if (type == "POST") {
- query_->type_ = HttpQuery::Type::POST;
+ query_->type_ = HttpQuery::Type::Post;
} else if (type.size() >= 4 && type.substr(0, 4) == "HTTP") {
- if (type == "HTTP/1.1" || type == "HTTP/1.0") {
- query_->type_ = HttpQuery::Type::RESPONSE;
+ if (type == "HTTP/1.1") {
+ query_->type_ = HttpQuery::Type::Response;
+ query_->keep_alive_ = true;
+ } else if (type == "HTTP/1.0") {
+ query_->type_ = HttpQuery::Type::Response;
+ query_->keep_alive_ = false;
} else {
LOG(INFO) << "Unsupported HTTP version: " << type;
return Status::Error(505, "HTTP Version Not Supported");
@@ -662,10 +723,11 @@ Status HttpReader::parse_head(MutableSlice head) {
query_->args_.clear();
- if (query_->type_ == HttpQuery::Type::RESPONSE) {
+ if (query_->type_ == HttpQuery::Type::Response) {
query_->code_ = to_integer<int32>(parser.read_till(' '));
parser.skip(' ');
query_->reason_ = parser.read_till('\r');
+ LOG(DEBUG) << "Receive HTTP response " << query_->code_ << " " << query_->reason_;
} else {
auto url_version = parser.read_till('\r');
auto space_pos = url_version.rfind(' ');
@@ -685,12 +747,11 @@ Status HttpReader::parse_head(MutableSlice head) {
parser.skip('\n');
content_length_ = 0;
- content_type_ = "application/octet-stream";
+ content_type_ = Slice("application/octet-stream");
content_type_lowercased_ = content_type_.str();
- transfer_encoding_ = "";
- content_encoding_ = "";
+ transfer_encoding_ = Slice();
+ content_encoding_ = Slice();
- query_->keep_alive_ = false;
query_->headers_.clear();
query_->files_.clear();
query_->content_ = MutableSlice();
@@ -771,24 +832,26 @@ Status HttpReader::try_open_temp_file(Slice directory_name, CSlice desired_file_
Status HttpReader::save_file_part(BufferSlice &&file_part) {
file_size_ += narrow_cast<int64>(file_part.size());
if (file_size_ > MAX_FILE_SIZE) {
- string file_name = temp_file_name_;
- close_temp_file();
- delete_temp_file(file_name);
+ clean_temporary_file();
return Status::Error(
- 413, PSLICE() << "Request Entity Too Large: file is too big to be uploaded " << tag("size", file_size_));
+ 413, PSLICE() << "Request Entity Too Large: file of size " << file_size_ << " is too big to be uploaded");
}
LOG(DEBUG) << "Save file part of size " << file_part.size() << " to file " << temp_file_name_;
auto result_written = temp_file_.write(file_part.as_slice());
if (result_written.is_error() || result_written.ok() != file_part.size()) {
- string file_name = temp_file_name_;
- close_temp_file();
- delete_temp_file(file_name);
- return Status::Error(500, "Internal server error: can't upload the file");
+ clean_temporary_file();
+ return Status::Error(500, "Internal Server Error: can't upload the file");
}
return Status::OK();
}
+void HttpReader::clean_temporary_file() {
+ string file_name = temp_file_name_;
+ close_temp_file();
+ delete_temp_file(file_name);
+}
+
void HttpReader::close_temp_file() {
LOG(DEBUG) << "Close temporary file " << temp_file_name_;
CHECK(!temp_file_.empty());
@@ -807,7 +870,7 @@ void HttpReader::delete_temp_file(CSlice file_name) {
if (parent.size() >= prefix_length + 7 &&
parent.substr(parent.size() - prefix_length - 7, prefix_length) == TEMP_DIRECTORY_PREFIX) {
LOG(DEBUG) << "Unlink temporary directory " << parent;
- rmdir(Slice(parent.data(), parent.size() - 1).str()).ignore();
+ rmdir(PSLICE() << Slice(parent.data(), parent.size() - 1)).ignore();
}
}
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.h b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.h
index 74067d1291..3851e74671 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/HttpReader.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -27,7 +27,7 @@ class HttpReader {
public:
void init(ChainBufferReader *input, size_t max_post_size = std::numeric_limits<size_t>::max(),
size_t max_files = 100);
- Result<size_t> read_next(HttpQuery *query) TD_WARN_UNUSED_RESULT; // TODO move query to init
+ Result<size_t> read_next(HttpQuery *query, bool can_be_slow = true) TD_WARN_UNUSED_RESULT; // TODO move query to init
HttpReader() = default;
HttpReader(const HttpReader &other) = delete;
@@ -36,57 +36,60 @@ class HttpReader {
HttpReader &operator=(HttpReader &&other) = delete;
~HttpReader() {
if (!temp_file_.empty()) {
- temp_file_.close();
+ clean_temporary_file();
}
}
static void delete_temp_file(CSlice file_name);
private:
- size_t max_post_size_;
- size_t max_files_;
+ size_t max_post_size_ = 0;
+ size_t max_files_ = 0;
- enum { ReadHeaders, ReadContent, ReadContentToFile, ReadArgs, ReadMultipartFormData } state_;
- size_t headers_read_length_;
- size_t content_length_;
- ChainBufferReader *input_;
+ enum class State { ReadHeaders, ReadContent, ReadContentToFile, ReadArgs, ReadMultipartFormData };
+ State state_ = State::ReadHeaders;
+ size_t headers_read_length_ = 0;
+ size_t content_length_ = 0;
+ ChainBufferReader *input_ = nullptr;
ByteFlowSource flow_source_;
HttpChunkedByteFlow chunked_flow_;
GzipByteFlow gzip_flow_;
HttpContentLengthByteFlow content_length_flow_;
ByteFlowSink flow_sink_;
- ChainBufferReader *content_;
+ ChainBufferReader *content_ = nullptr;
- HttpQuery *query_;
+ HttpQuery *query_ = nullptr;
Slice transfer_encoding_;
Slice content_encoding_;
Slice content_type_;
string content_type_lowercased_;
- size_t total_parameters_length_;
- size_t total_headers_length_;
+ size_t total_parameters_length_ = 0;
+ size_t total_headers_length_ = 0;
string boundary_;
- size_t form_data_read_length_;
- size_t form_data_skipped_length_;
- enum {
+ size_t form_data_read_length_ = 0;
+ size_t form_data_skipped_length_ = 0;
+ enum class FormDataParseState : int32 {
SkipPrologue,
ReadPartHeaders,
ReadPartValue,
ReadFile,
CheckForLastBoundary,
SkipEpilogue
- } form_data_parse_state_;
+ };
+ FormDataParseState form_data_parse_state_ = FormDataParseState::SkipPrologue;
MutableSlice field_name_;
string file_field_name_;
string field_content_type_;
string file_name_;
+ bool has_file_name_ = false;
FileFd temp_file_;
string temp_file_name_;
- int64 file_size_;
+ int64 file_size_ = 0;
Result<size_t> split_header() TD_WARN_UNUSED_RESULT;
void process_header(MutableSlice header_name, MutableSlice header_value);
- Result<bool> parse_multipart_form_data() TD_WARN_UNUSED_RESULT;
+ Result<bool> parse_multipart_form_data(bool can_be_slow) TD_WARN_UNUSED_RESULT;
Status parse_url(MutableSlice url) TD_WARN_UNUSED_RESULT;
Status parse_parameters(MutableSlice parameters) TD_WARN_UNUSED_RESULT;
Status parse_json_parameters(MutableSlice parameters) TD_WARN_UNUSED_RESULT;
@@ -96,12 +99,13 @@ class HttpReader {
Status try_open_temp_file(Slice directory_name, CSlice desired_file_name) TD_WARN_UNUSED_RESULT;
Status save_file_part(BufferSlice &&file_part) TD_WARN_UNUSED_RESULT;
void close_temp_file();
+ void clean_temporary_file();
- static constexpr size_t MAX_CONTENT_SIZE = 150 << 20; // Some reasonable limit
- static constexpr size_t MAX_TOTAL_PARAMETERS_LENGTH = 1 << 16; // Some reasonable limit
- static constexpr size_t MAX_TOTAL_HEADERS_LENGTH = 1 << 18; // Some reasonable limit
- static constexpr size_t MAX_BOUNDARY_LENGTH = 70; // As defined by RFC1341
- static constexpr int64 MAX_FILE_SIZE = 1500 << 20; // Telegram server file size limit
+ static constexpr size_t MAX_CONTENT_SIZE = std::numeric_limits<uint32>::max(); // Some reasonable limit
+ static constexpr size_t MAX_TOTAL_PARAMETERS_LENGTH = 1 << 20; // Some reasonable limit
+ static constexpr size_t MAX_TOTAL_HEADERS_LENGTH = 1 << 18; // Some reasonable limit
+ static constexpr size_t MAX_BOUNDARY_LENGTH = 70; // As defined by RFC1341
+ static constexpr int64 MAX_FILE_SIZE = static_cast<int64>(4000) << 20; // Telegram server file size limit
static constexpr const char TEMP_DIRECTORY_PREFIX[] = "tdlib-server-tmp";
};
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/NetStats.h b/protocols/Telegram/tdlib/td/tdnet/td/net/NetStats.h
index e67f9fbc93..325196b34c 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/NetStats.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/NetStats.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,7 +10,6 @@
#include "td/utils/common.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Time.h"
@@ -87,12 +86,12 @@ class NetStats {
}
// do it before get_callback
- void set_callback(std::unique_ptr<Callback> callback) {
+ void set_callback(unique_ptr<Callback> callback) {
impl_->set_callback(std::move(callback));
}
private:
- class Impl : public NetStatsCallback {
+ class Impl final : public NetStatsCallback {
public:
NetStatsData get_stats() const {
NetStatsData res;
@@ -102,7 +101,7 @@ class NetStats {
});
return res;
}
- void set_callback(std::unique_ptr<Callback> callback) {
+ void set_callback(unique_ptr<Callback> callback) {
callback_ = std::move(callback);
}
@@ -114,7 +113,7 @@ class NetStats {
std::atomic<uint64> write_size{0};
};
SchedulerLocalStorage<LocalNetStats> local_net_stats_;
- std::unique_ptr<Callback> callback_;
+ unique_ptr<Callback> callback_;
void on_read(uint64 size) final {
auto &stats = local_net_stats_.get();
@@ -131,8 +130,8 @@ class NetStats {
void on_change(LocalNetStats &stats, uint64 size) {
stats.unsync_size += size;
- auto now = Time::now_cached();
- if (stats.unsync_size > 10000 || now - stats.last_update > 5 * 60) {
+ auto now = Time::now();
+ if (stats.unsync_size > 10000 || now - stats.last_update > 300) {
stats.unsync_size = 0;
stats.last_update = now;
callback_->on_stats_updated();
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.cpp
index 02e1e067ea..04c52278e8 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.cpp
@@ -1,67 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/net/Socks5.h"
-#include "td/utils/format.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
-#include "td/utils/port/Fd.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
namespace td {
-static int VERBOSITY_NAME(socks5) = VERBOSITY_NAME(DEBUG);
-
-Socks5::Socks5(SocketFd socket_fd, IPAddress ip_address, string username, string password,
- std::unique_ptr<Callback> callback, ActorShared<> parent)
- : fd_(std::move(socket_fd))
- , ip_address_(std::move(ip_address))
- , username_(std::move(username))
- , password_(std::move(password))
- , callback_(std::move(callback))
- , parent_(std::move(parent)) {
-}
-
-void Socks5::on_error(Status status) {
- CHECK(status.is_error());
- VLOG(socks5) << "Receive " << status;
- if (callback_) {
- callback_->set_result(std::move(status));
- callback_.reset();
- }
- stop();
-}
-
-void Socks5::tear_down() {
- VLOG(socks5) << "Finish to connect to proxy";
- unsubscribe(fd_.get_fd());
- fd_.get_fd().set_observer(nullptr);
- if (callback_) {
- callback_->set_result(std::move(fd_));
- callback_.reset();
- }
-}
-
-void Socks5::hangup() {
- on_error(Status::Error("Cancelled"));
-}
-
-void Socks5::start_up() {
- VLOG(socks5) << "Begin to connect to proxy";
- fd_.get_fd().set_observer(this);
- subscribe(fd_.get_fd());
- set_timeout_in(10);
- if (can_write(fd_)) {
- loop();
- }
-}
-
void Socks5::send_greeting() {
- VLOG(socks5) << "Send greeting to proxy";
+ VLOG(proxy) << "Send greeting to proxy";
CHECK(state_ == State::SendGreeting);
state_ = State::WaitGreetingResponse;
@@ -80,18 +34,17 @@ void Socks5::send_greeting() {
Status Socks5::wait_greeting_response() {
auto &buf = fd_.input_buffer();
- VLOG(socks5) << "Receive greeting response of size " << buf.size();
+ VLOG(proxy) << "Receive greeting response of size " << buf.size();
if (buf.size() < 2) {
return Status::OK();
}
auto buffer_slice = buf.read_as_buffer_slice(2);
auto slice = buffer_slice.as_slice();
if (slice[0] != '\x05') {
- return Status::Error(PSLICE() << "Unsupported socks protocol version " << int(slice[0]));
+ return Status::Error(PSLICE() << "Unsupported socks protocol version " << static_cast<int>(slice[0]));
}
auto authentication_method = slice[1];
if (authentication_method == '\0') {
- state_ = State::SendIpAddress;
send_ip_address();
return Status::OK();
}
@@ -102,7 +55,7 @@ Status Socks5::wait_greeting_response() {
}
Status Socks5::send_username_password() {
- VLOG(socks5) << "Send username and password";
+ VLOG(proxy) << "Send username and password";
if (username_.size() >= 128) {
return Status::Error("Username is too long");
}
@@ -124,27 +77,26 @@ Status Socks5::send_username_password() {
Status Socks5::wait_password_response() {
auto &buf = fd_.input_buffer();
- VLOG(socks5) << "Receive password response of size " << buf.size();
+ VLOG(proxy) << "Receive password response of size " << buf.size();
if (buf.size() < 2) {
return Status::OK();
}
auto buffer_slice = buf.read_as_buffer_slice(2);
auto slice = buffer_slice.as_slice();
if (slice[0] != '\x01') {
- return Status::Error(PSLICE() << "Unsupported socks subnegotiation protocol version " << int(slice[0]));
+ return Status::Error(PSLICE() << "Unsupported socks subnegotiation protocol version "
+ << static_cast<int>(slice[0]));
}
if (slice[1] != '\x00') {
return Status::Error("Wrong username or password");
}
- state_ = State::SendIpAddress;
send_ip_address();
return Status::OK();
}
void Socks5::send_ip_address() {
- VLOG(socks5) << "Send IP address";
- CHECK(state_ == State::SendIpAddress);
+ VLOG(proxy) << "Send IP address";
callback_->on_connected();
string request;
request += '\x05';
@@ -152,14 +104,14 @@ void Socks5::send_ip_address() {
request += '\x00';
if (ip_address_.is_ipv4()) {
request += '\x01';
- auto ipv4 = ip_address_.get_ipv4();
+ auto ipv4 = ntohl(ip_address_.get_ipv4());
request += static_cast<char>(ipv4 & 255);
request += static_cast<char>((ipv4 >> 8) & 255);
request += static_cast<char>((ipv4 >> 16) & 255);
request += static_cast<char>((ipv4 >> 24) & 255);
} else {
request += '\x04';
- request += ip_address_.get_ipv6().str();
+ request += ip_address_.get_ipv6();
}
auto port = ip_address_.get_port();
request += static_cast<char>((port >> 8) & 255);
@@ -171,7 +123,7 @@ void Socks5::send_ip_address() {
Status Socks5::wait_ip_address_response() {
CHECK(state_ == State::WaitIpAddressResponse);
auto it = fd_.input_buffer().clone();
- VLOG(socks5) << "Receive IP address response of size " << it.size();
+ VLOG(proxy) << "Receive IP address response of size " << it.size();
if (it.size() < 4) {
return Status::OK();
}
@@ -183,23 +135,26 @@ Status Socks5::wait_ip_address_response() {
}
it.advance(1, c_slice);
if (c != '\0') {
- return Status::Error(PSLICE() << tag("code", c));
+ return Status::Error(PSLICE() << "Receive error code " << static_cast<int32>(c) << " from server");
}
it.advance(1, c_slice);
if (c != '\0') {
- return Status::Error("byte must be zero");
+ return Status::Error("Byte must be zero");
}
it.advance(1, c_slice);
+ size_t total_size = 6;
if (c == '\x01') {
if (it.size() < 4) {
return Status::OK();
}
it.advance(4);
+ total_size += 4;
} else if (c == '\x04') {
if (it.size() < 16) {
return Status::OK();
}
it.advance(16);
+ total_size += 16;
} else {
return Status::Error("Invalid response");
}
@@ -207,43 +162,29 @@ Status Socks5::wait_ip_address_response() {
return Status::OK();
}
it.advance(2);
+ fd_.input_buffer().advance(total_size);
stop();
return Status::OK();
}
-void Socks5::loop() {
- auto status = [&] {
- TRY_STATUS(fd_.flush_read());
- switch (state_) {
- case State::SendGreeting:
- send_greeting();
- break;
- case State::WaitGreetingResponse:
- TRY_STATUS(wait_greeting_response());
- break;
- case State::WaitPasswordResponse:
- TRY_STATUS(wait_password_response());
- break;
- case State::WaitIpAddressResponse:
- TRY_STATUS(wait_ip_address_response());
- break;
- case State::SendIpAddress:
- case State::Stop:
- UNREACHABLE();
- }
- TRY_STATUS(fd_.flush_write());
- return Status::OK();
- }();
- if (status.is_error()) {
- on_error(std::move(status));
- }
- if (can_close(fd_)) {
- on_error(Status::Error("Connection closed"));
+Status Socks5::loop_impl() {
+ switch (state_) {
+ case State::SendGreeting:
+ send_greeting();
+ break;
+ case State::WaitGreetingResponse:
+ TRY_STATUS(wait_greeting_response());
+ break;
+ case State::WaitPasswordResponse:
+ TRY_STATUS(wait_password_response());
+ break;
+ case State::WaitIpAddressResponse:
+ TRY_STATUS(wait_ip_address_response());
+ break;
+ default:
+ UNREACHABLE();
}
-}
-
-void Socks5::timeout_expired() {
- on_error(Status::Error("Timeout expired"));
+ return Status::OK();
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.h b/protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.h
index b67a33c282..e438fb8d90 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/Socks5.h
@@ -1,58 +1,27 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/net/TransparentProxy.h"
-#include "td/utils/BufferedFd.h"
-#include "td/utils/common.h"
-#include "td/utils/port/IPAddress.h"
-#include "td/utils/port/SocketFd.h"
#include "td/utils/Status.h"
namespace td {
-class Socks5 : public Actor {
+class Socks5 final : public TransparentProxy {
public:
- class Callback {
- public:
- Callback() = default;
- Callback(const Callback &) = delete;
- Callback &operator=(const Callback &) = delete;
- virtual ~Callback() = default;
-
- virtual void set_result(Result<SocketFd>) = 0;
- virtual void on_connected() = 0;
- };
-
- Socks5(SocketFd socket_fd, IPAddress ip_address, string username, string password, std::unique_ptr<Callback> callback,
- ActorShared<> parent);
+ using TransparentProxy::TransparentProxy;
private:
- BufferedFd<SocketFd> fd_;
- IPAddress ip_address_;
- string username_;
- string password_;
- std::unique_ptr<Callback> callback_;
- ActorShared<> parent_;
-
- void on_error(Status status);
- void tear_down() override;
- void start_up() override;
- void hangup() override;
-
enum class State {
SendGreeting,
WaitGreetingResponse,
WaitPasswordResponse,
- SendIpAddress,
- WaitIpAddressResponse,
- Stop
+ WaitIpAddressResponse
} state_ = State::SendGreeting;
void send_greeting();
@@ -64,8 +33,7 @@ class Socks5 : public Actor {
void send_ip_address();
Status wait_ip_address_response();
- void loop() override;
- void timeout_expired() override;
+ Status loop_impl() final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/SslCtx.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/SslCtx.cpp
new file mode 100644
index 0000000000..4ec0412e66
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/SslCtx.cpp
@@ -0,0 +1,312 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/net/SslCtx.h"
+
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/wstring_convert.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Time.h"
+
+#if !TD_EMSCRIPTEN
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+
+#include <cstring>
+#include <memory>
+#include <mutex>
+
+#if TD_PORT_WINDOWS
+#include <wincrypt.h>
+#endif
+
+namespace td {
+
+namespace detail {
+namespace {
+int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
+ if (!preverify_ok) {
+ char buf[256];
+ X509_NAME_oneline(X509_get_subject_name(X509_STORE_CTX_get_current_cert(ctx)), buf, 256);
+
+ int err = X509_STORE_CTX_get_error(ctx);
+ auto warning = PSTRING() << "verify error:num=" << err << ":" << X509_verify_cert_error_string(err)
+ << ":depth=" << X509_STORE_CTX_get_error_depth(ctx) << ":" << Slice(buf, std::strlen(buf));
+ double now = Time::now();
+
+ static std::mutex warning_mutex;
+ {
+ std::lock_guard<std::mutex> lock(warning_mutex);
+ static FlatHashMap<string, double> next_warning_time;
+ double &next = next_warning_time[warning];
+ if (next <= now) {
+ next = now + 300; // one warning per 5 minutes
+ LOG(WARNING) << warning;
+ }
+ }
+ }
+
+ return preverify_ok;
+}
+
+using SslCtxPtr = std::shared_ptr<SSL_CTX>;
+
+Result<SslCtxPtr> do_create_ssl_ctx(CSlice cert_file, SslCtx::VerifyPeer verify_peer) {
+ auto ssl_method =
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ TLS_client_method();
+#else
+ SSLv23_client_method();
+#endif
+ if (ssl_method == nullptr) {
+ return create_openssl_error(-6, "Failed to create an SSL client method");
+ }
+ auto ssl_ctx = SSL_CTX_new(ssl_method);
+ if (!ssl_ctx) {
+ return create_openssl_error(-7, "Failed to create an SSL context");
+ }
+ auto ssl_ctx_ptr = SslCtxPtr(ssl_ctx, SSL_CTX_free);
+ long options = 0;
+#ifdef SSL_OP_NO_SSLv2
+ options |= SSL_OP_NO_SSLv2;
+#endif
+#ifdef SSL_OP_NO_SSLv3
+ options |= SSL_OP_NO_SSLv3;
+#endif
+ SSL_CTX_set_options(ssl_ctx, options);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_VERSION);
+#endif
+ SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+ if (cert_file.empty()) {
+#if TD_PORT_WINDOWS
+ LOG(DEBUG) << "Begin to load system store";
+ auto flags = CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER;
+ HCERTSTORE system_store =
+ CertOpenStore(CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, HCRYPTPROV_LEGACY(), flags,
+ static_cast<const void *>(to_wstring("ROOT").ok().c_str()));
+
+ if (system_store) {
+ X509_STORE *store = X509_STORE_new();
+
+ for (PCCERT_CONTEXT cert_context = CertEnumCertificatesInStore(system_store, nullptr); cert_context != nullptr;
+ cert_context = CertEnumCertificatesInStore(system_store, cert_context)) {
+ const unsigned char *in = cert_context->pbCertEncoded;
+ X509 *x509 = d2i_X509(nullptr, &in, static_cast<long>(cert_context->cbCertEncoded));
+ if (x509 != nullptr) {
+ if (X509_STORE_add_cert(store, x509) != 1) {
+ auto error_code = ERR_peek_error();
+ auto error = create_openssl_error(-20, "Failed to add certificate");
+ if (ERR_GET_REASON(error_code) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
+ LOG(ERROR) << error;
+ } else {
+ LOG(INFO) << error;
+ }
+ }
+
+ X509_free(x509);
+ } else {
+ LOG(ERROR) << create_openssl_error(-21, "Failed to load X509 certificate");
+ }
+ }
+
+ CertCloseStore(system_store, 0);
+
+ SSL_CTX_set_cert_store(ssl_ctx, store);
+ LOG(DEBUG) << "End to load system store";
+ } else {
+ LOG(ERROR) << create_openssl_error(-22, "Failed to open system certificate store");
+ }
+#else
+ if (SSL_CTX_set_default_verify_paths(ssl_ctx) == 0) {
+ auto error = create_openssl_error(-8, "Failed to load default verify paths");
+ if (verify_peer == SslCtx::VerifyPeer::On) {
+ return std::move(error);
+ } else {
+ LOG(ERROR) << error;
+ }
+ }
+#endif
+ } else {
+ if (SSL_CTX_load_verify_locations(ssl_ctx, cert_file.c_str(), nullptr) == 0) {
+ return create_openssl_error(-8, "Failed to set custom certificate file");
+ }
+ }
+
+ if (verify_peer == SslCtx::VerifyPeer::On) {
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);
+
+ constexpr int DEFAULT_VERIFY_DEPTH = 10;
+ SSL_CTX_set_verify_depth(ssl_ctx, DEFAULT_VERIFY_DEPTH);
+ } else {
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, nullptr);
+ }
+
+ string cipher_list;
+ if (SSL_CTX_set_cipher_list(ssl_ctx, cipher_list.empty() ? "DEFAULT" : cipher_list.c_str()) == 0) {
+ return create_openssl_error(-9, PSLICE() << "Failed to set cipher list \"" << cipher_list << '"');
+ }
+
+ return std::move(ssl_ctx_ptr);
+}
+
+Result<SslCtxPtr> get_default_ssl_ctx() {
+ static auto ctx = do_create_ssl_ctx(CSlice(), SslCtx::VerifyPeer::On);
+ if (ctx.is_error()) {
+ return ctx.error().clone();
+ }
+
+ return ctx.ok();
+}
+
+Result<SslCtxPtr> get_default_unverified_ssl_ctx() {
+ static auto ctx = do_create_ssl_ctx(CSlice(), SslCtx::VerifyPeer::Off);
+ if (ctx.is_error()) {
+ return ctx.error().clone();
+ }
+
+ return ctx.ok();
+}
+
+} // namespace
+
+class SslCtxImpl {
+ public:
+ Status init(CSlice cert_file, SslCtx::VerifyPeer verify_peer) {
+ SslCtx::init_openssl();
+
+ clear_openssl_errors("Before SslCtx::init");
+
+ if (cert_file.empty()) {
+ if (verify_peer == SslCtx::VerifyPeer::On) {
+ TRY_RESULT_ASSIGN(ssl_ctx_ptr_, get_default_ssl_ctx());
+ } else {
+ TRY_RESULT_ASSIGN(ssl_ctx_ptr_, get_default_unverified_ssl_ctx());
+ }
+ return Status::OK();
+ }
+
+ auto start_time = Time::now();
+ auto r_ssl_ctx_ptr = do_create_ssl_ctx(cert_file, verify_peer);
+ auto elapsed_time = Time::now() - start_time;
+ if (elapsed_time >= 0.1) {
+ LOG(ERROR) << "SSL context creation took " << elapsed_time << " seconds";
+ }
+ if (r_ssl_ctx_ptr.is_error()) {
+ return r_ssl_ctx_ptr.move_as_error();
+ }
+ ssl_ctx_ptr_ = r_ssl_ctx_ptr.move_as_ok();
+ return Status::OK();
+ }
+
+ void *get_openssl_ctx() const {
+ return static_cast<void *>(ssl_ctx_ptr_.get());
+ }
+
+ private:
+ SslCtxPtr ssl_ctx_ptr_;
+};
+
+} // namespace detail
+
+SslCtx::SslCtx() = default;
+
+SslCtx::SslCtx(const SslCtx &other) {
+ if (other.impl_) {
+ impl_ = make_unique<detail::SslCtxImpl>(*other.impl_);
+ }
+}
+
+SslCtx &SslCtx::operator=(const SslCtx &other) {
+ if (other.impl_) {
+ impl_ = make_unique<detail::SslCtxImpl>(*other.impl_);
+ } else {
+ impl_ = nullptr;
+ }
+ return *this;
+}
+
+SslCtx::SslCtx(SslCtx &&) noexcept = default;
+
+SslCtx &SslCtx::operator=(SslCtx &&) noexcept = default;
+
+SslCtx::~SslCtx() = default;
+
+void SslCtx::init_openssl() {
+ static bool is_inited = [] {
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ return OPENSSL_init_ssl(0, nullptr) != 0;
+#else
+ OpenSSL_add_all_algorithms();
+ SSL_load_error_strings();
+ return OpenSSL_add_ssl_algorithms() != 0;
+#endif
+ }();
+ CHECK(is_inited);
+}
+
+Result<SslCtx> SslCtx::create(CSlice cert_file, VerifyPeer verify_peer) {
+ auto impl = make_unique<detail::SslCtxImpl>();
+ TRY_STATUS(impl->init(cert_file, verify_peer));
+ return SslCtx(std::move(impl));
+}
+
+void *SslCtx::get_openssl_ctx() const {
+ return impl_ == nullptr ? nullptr : impl_->get_openssl_ctx();
+}
+
+SslCtx::SslCtx(unique_ptr<detail::SslCtxImpl> impl) : impl_(std::move(impl)) {
+}
+
+} // namespace td
+
+#else
+
+namespace td {
+
+namespace detail {
+class SslCtxImpl {};
+} // namespace detail
+
+SslCtx::SslCtx() = default;
+
+SslCtx::SslCtx(const SslCtx &other) {
+ UNREACHABLE();
+}
+
+SslCtx &SslCtx::operator=(const SslCtx &other) {
+ UNREACHABLE();
+ return *this;
+}
+
+SslCtx::SslCtx(SslCtx &&) noexcept = default;
+
+SslCtx &SslCtx::operator=(SslCtx &&) noexcept = default;
+
+SslCtx::~SslCtx() = default;
+
+void SslCtx::init_openssl() {
+}
+
+Result<SslCtx> SslCtx::create(CSlice cert_file, VerifyPeer verify_peer) {
+ return Status::Error("Not supported in Emscripten");
+}
+
+void *SslCtx::get_openssl_ctx() const {
+ return nullptr;
+}
+
+SslCtx::SslCtx(unique_ptr<detail::SslCtxImpl> impl) : impl_(std::move(impl)) {
+}
+
+} // namespace td
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/SslCtx.h b/protocols/Telegram/tdlib/td/tdnet/td/net/SslCtx.h
new file mode 100644
index 0000000000..5cd55925e5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/SslCtx.h
@@ -0,0 +1,45 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+namespace detail {
+class SslCtxImpl;
+} // namespace detail
+
+class SslCtx {
+ public:
+ SslCtx();
+ SslCtx(const SslCtx &other);
+ SslCtx &operator=(const SslCtx &other);
+ SslCtx(SslCtx &&) noexcept;
+ SslCtx &operator=(SslCtx &&) noexcept;
+ ~SslCtx();
+
+ static void init_openssl();
+
+ enum class VerifyPeer { On, Off };
+
+ static Result<SslCtx> create(CSlice cert_file, VerifyPeer verify_peer);
+
+ void *get_openssl_ctx() const;
+
+ explicit operator bool() const noexcept {
+ return static_cast<bool>(impl_);
+ }
+
+ private:
+ unique_ptr<detail::SslCtxImpl> impl_;
+
+ explicit SslCtx(unique_ptr<detail::SslCtxImpl> impl);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/SslFd.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/SslFd.cpp
deleted file mode 100644
index f6f7557235..0000000000
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/SslFd.cpp
+++ /dev/null
@@ -1,280 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#include "td/net/SslFd.h"
-
-#include "td/utils/logging.h"
-#include "td/utils/StackAllocator.h"
-#include "td/utils/StringBuilder.h"
-#include "td/utils/Time.h"
-
-#include <openssl/err.h>
-#include <openssl/evp.h>
-#include <openssl/ssl.h>
-#include <openssl/x509v3.h>
-
-#include <map>
-#include <mutex>
-
-namespace td {
-
-#if !TD_WINDOWS
-static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
- if (!preverify_ok) {
- char buf[256];
- X509_NAME_oneline(X509_get_subject_name(X509_STORE_CTX_get_current_cert(ctx)), buf, 256);
-
- int err = X509_STORE_CTX_get_error(ctx);
- auto warning = PSTRING() << "verify error:num=" << err << ":" << X509_verify_cert_error_string(err)
- << ":depth=" << X509_STORE_CTX_get_error_depth(ctx) << ":" << buf;
- double now = Time::now();
-
- static std::mutex warning_mutex;
- {
- std::lock_guard<std::mutex> lock(warning_mutex);
- static std::map<std::string, double> next_warning_time;
- double &next = next_warning_time[warning];
- if (next <= now) {
- next = now + 300; // one warning per 5 minutes
- LOG(WARNING) << warning;
- }
- }
- }
-
- return preverify_ok;
-}
-#endif
-
-namespace {
-
-Status create_openssl_error(int code, Slice message) {
- const int buf_size = 1 << 12;
- auto buf = StackAllocator::alloc(buf_size);
- StringBuilder sb(buf.as_slice());
-
- sb << message;
- while (unsigned long error_code = ERR_get_error()) {
- sb << "{" << error_code << ", " << ERR_error_string(error_code, nullptr) << "}";
- }
- LOG_IF(ERROR, sb.is_error()) << "OPENSSL error buffer overflow";
- return Status::Error(code, sb.as_cslice());
-}
-
-void openssl_clear_errors(Slice from) {
- if (ERR_peek_error() != 0) {
- LOG(ERROR) << from << ": " << create_openssl_error(0, "Unprocessed OPENSSL_ERROR");
- }
- errno = 0;
-}
-
-void do_ssl_shutdown(SSL *ssl_handle) {
- if (!SSL_is_init_finished(ssl_handle)) {
- return;
- }
- openssl_clear_errors("Before SSL_shutdown");
- SSL_set_quiet_shutdown(ssl_handle, 1);
- SSL_shutdown(ssl_handle);
- openssl_clear_errors("After SSL_shutdown");
-}
-
-} // namespace
-
-SslFd::SslFd(SocketFd &&fd, SSL *ssl_handle_, SSL_CTX *ssl_ctx_)
- : fd_(std::move(fd)), ssl_handle_(ssl_handle_), ssl_ctx_(ssl_ctx_) {
-}
-
-Result<SslFd> SslFd::init(SocketFd fd, CSlice host, CSlice cert_file, VerifyPeer verify_peer) {
-#if TD_WINDOWS
- return Status::Error("TODO");
-#else
- static bool init_openssl = [] {
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
- return OPENSSL_init_ssl(0, nullptr) != 0;
-#else
- OpenSSL_add_all_algorithms();
- SSL_load_error_strings();
- return OpenSSL_add_ssl_algorithms() != 0;
-#endif
- }();
- CHECK(init_openssl);
-
- openssl_clear_errors("Before SslFd::init");
- CHECK(!fd.empty());
-
- auto ssl_method =
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
- TLS_client_method();
-#else
- SSLv23_client_method();
-#endif
- if (ssl_method == nullptr) {
- return create_openssl_error(-6, "Failed to create an SSL client method");
- }
-
- auto ssl_ctx = SSL_CTX_new(ssl_method);
- if (ssl_ctx == nullptr) {
- return create_openssl_error(-7, "Failed to create an SSL context");
- }
- auto ssl_ctx_guard = ScopeExit() + [&]() { SSL_CTX_free(ssl_ctx); };
- long options = 0;
-#ifdef SSL_OP_NO_SSLv2
- options |= SSL_OP_NO_SSLv2;
-#endif
-#ifdef SSL_OP_NO_SSLv3
- options |= SSL_OP_NO_SSLv3;
-#endif
- SSL_CTX_set_options(ssl_ctx, options);
- SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE);
-
- if (cert_file.empty()) {
- SSL_CTX_set_default_verify_paths(ssl_ctx);
- } else {
- if (SSL_CTX_load_verify_locations(ssl_ctx, cert_file.c_str(), nullptr) == 0) {
- return create_openssl_error(-8, "Failed to set custom cert file");
- }
- }
- if (VERIFY_PEER && verify_peer == VerifyPeer::On) {
- SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);
-
- if (VERIFY_DEPTH != -1) {
- SSL_CTX_set_verify_depth(ssl_ctx, VERIFY_DEPTH);
- }
- } else {
- SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, nullptr);
- }
-
- // TODO(now): cipher list
- string cipher_list;
- if (SSL_CTX_set_cipher_list(ssl_ctx, cipher_list.empty() ? "DEFAULT" : cipher_list.c_str()) == 0) {
- return create_openssl_error(-9, PSLICE() << "Failed to set cipher list \"" << cipher_list << '"');
- }
-
- auto ssl_handle = SSL_new(ssl_ctx);
- if (ssl_handle == nullptr) {
- return create_openssl_error(-13, "Failed to create an SSL handle");
- }
- auto ssl_handle_guard = ScopeExit() + [&]() {
- do_ssl_shutdown(ssl_handle);
- SSL_free(ssl_handle);
- };
-
-#if OPENSSL_VERSION_NUMBER >= 0x10002000L
- X509_VERIFY_PARAM *param = SSL_get0_param(ssl_handle);
- /* Enable automatic hostname checks */
- // TODO: X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
- X509_VERIFY_PARAM_set_hostflags(param, 0);
- X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
-#else
-#warning DANGEROUS! HTTPS HOST WILL NOT BE CHECKED. INSTALL OPENSSL >= 1.0.2 OR IMPLEMENT HTTPS HOST CHECK MANUALLY
-#endif
-
- if (!SSL_set_fd(ssl_handle, fd.get_fd().get_native_fd())) {
- return create_openssl_error(-14, "Failed to set fd");
- }
-
-#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
- auto host_str = host.str();
- SSL_set_tlsext_host_name(ssl_handle, MutableCSlice(host_str).begin());
-#endif
- SSL_set_connect_state(ssl_handle);
-
- ssl_ctx_guard.dismiss();
- ssl_handle_guard.dismiss();
- return SslFd(std::move(fd), ssl_handle, ssl_ctx);
-#endif
-}
-
-Result<size_t> SslFd::process_ssl_error(int ret, int *mask) {
-#if TD_WINDOWS
- return Status::Error("TODO");
-#else
- auto openssl_errno = errno;
- int error = SSL_get_error(ssl_handle_, ret);
- LOG(INFO) << "SSL ERROR: " << ret << " " << error;
- switch (error) {
- case SSL_ERROR_NONE:
- LOG(ERROR) << "SSL_get_error returned no error";
- return 0;
- case SSL_ERROR_ZERO_RETURN:
- LOG(DEBUG) << "SSL_ERROR_ZERO_RETURN";
- fd_.get_fd().update_flags(Fd::Close);
- write_mask_ |= Fd::Error;
- *mask |= Fd::Error;
- return 0;
- case SSL_ERROR_WANT_READ:
- LOG(DEBUG) << "SSL_ERROR_WANT_READ";
- fd_.get_fd().clear_flags(Fd::Read);
- *mask |= Fd::Read;
- return 0;
- case SSL_ERROR_WANT_WRITE:
- LOG(DEBUG) << "SSL_ERROR_WANT_WRITE";
- fd_.get_fd().clear_flags(Fd::Write);
- *mask |= Fd::Write;
- return 0;
- case SSL_ERROR_WANT_CONNECT:
- case SSL_ERROR_WANT_ACCEPT:
- case SSL_ERROR_WANT_X509_LOOKUP:
- LOG(DEBUG) << "SSL_ERROR: CONNECT ACCEPT LOOKUP";
- fd_.get_fd().clear_flags(Fd::Write);
- *mask |= Fd::Write;
- return 0;
- case SSL_ERROR_SYSCALL:
- LOG(DEBUG) << "SSL_ERROR_SYSCALL";
- if (ERR_peek_error() == 0) {
- if (openssl_errno != 0) {
- CHECK(openssl_errno != EAGAIN);
- return Status::PosixError(openssl_errno, "SSL_ERROR_SYSCALL");
- } else {
- // Socket was closed from the other side, probably. Not an error
- fd_.get_fd().update_flags(Fd::Close);
- write_mask_ |= Fd::Error;
- *mask |= Fd::Error;
- return 0;
- }
- }
- /* fall through */
- default:
- LOG(DEBUG) << "SSL_ERROR Default";
- fd_.get_fd().update_flags(Fd::Close);
- write_mask_ |= Fd::Error;
- read_mask_ |= Fd::Error;
- return create_openssl_error(1, "SSL error ");
- }
-#endif
-}
-
-Result<size_t> SslFd::write(Slice slice) {
- openssl_clear_errors("Before SslFd::write");
- auto size = SSL_write(ssl_handle_, slice.data(), static_cast<int>(slice.size()));
- if (size <= 0) {
- return process_ssl_error(size, &write_mask_);
- }
- return size;
-}
-Result<size_t> SslFd::read(MutableSlice slice) {
- openssl_clear_errors("Before SslFd::read");
- auto size = SSL_read(ssl_handle_, slice.data(), static_cast<int>(slice.size()));
- if (size <= 0) {
- return process_ssl_error(size, &read_mask_);
- }
- return size;
-}
-
-void SslFd::close() {
- if (fd_.empty()) {
- CHECK(!ssl_handle_ && !ssl_ctx_);
- return;
- }
- CHECK(ssl_handle_ && ssl_ctx_);
- do_ssl_shutdown(ssl_handle_);
- SSL_free(ssl_handle_);
- ssl_handle_ = nullptr;
- SSL_CTX_free(ssl_ctx_);
- ssl_ctx_ = nullptr;
- fd_.close();
-}
-
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/SslFd.h b/protocols/Telegram/tdlib/td/tdnet/td/net/SslFd.h
deleted file mode 100644
index c197b9c318..0000000000
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/SslFd.h
+++ /dev/null
@@ -1,109 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/utils/port/Fd.h"
-#include "td/utils/port/SocketFd.h"
-#include "td/utils/Slice.h"
-#include "td/utils/Status.h"
-
-#include <openssl/ssl.h> // TODO can we remove it from header and make target_link_libraries dependence PRIVATE?
-
-namespace td {
-
-class SslFd {
- public:
- enum class VerifyPeer { On, Off };
- static Result<SslFd> init(SocketFd fd, CSlice host, CSlice cert_file = CSlice(),
- VerifyPeer verify_peer = VerifyPeer::On) TD_WARN_UNUSED_RESULT;
-
- SslFd(const SslFd &other) = delete;
- SslFd &operator=(const SslFd &other) = delete;
- SslFd(SslFd &&other)
- : fd_(std::move(other.fd_))
- , write_mask_(other.write_mask_)
- , read_mask_(other.read_mask_)
- , ssl_handle_(other.ssl_handle_)
- , ssl_ctx_(other.ssl_ctx_) {
- other.ssl_handle_ = nullptr;
- other.ssl_ctx_ = nullptr;
- }
- SslFd &operator=(SslFd &&other) {
- close();
-
- fd_ = std::move(other.fd_);
- write_mask_ = other.write_mask_;
- read_mask_ = other.read_mask_;
- ssl_handle_ = other.ssl_handle_;
- ssl_ctx_ = other.ssl_ctx_;
-
- other.ssl_handle_ = nullptr;
- other.ssl_ctx_ = nullptr;
- return *this;
- }
-
- const Fd &get_fd() const {
- return fd_.get_fd();
- }
-
- Fd &get_fd() {
- return fd_.get_fd();
- }
-
- Status get_pending_error() TD_WARN_UNUSED_RESULT {
- return fd_.get_pending_error();
- }
-
- Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT;
- Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT;
-
- void close();
-
- int32 get_flags() const {
- int32 res = 0;
- int32 fd_flags = fd_.get_flags();
- fd_flags &= ~Fd::Error;
- if (fd_flags & Fd::Close) {
- res |= Fd::Close;
- }
- write_mask_ &= ~fd_flags;
- read_mask_ &= ~fd_flags;
- if (write_mask_ == 0) {
- res |= Fd::Write;
- }
- if (read_mask_ == 0) {
- res |= Fd::Read;
- }
- return res;
- }
-
- bool empty() const {
- return fd_.empty();
- }
-
- ~SslFd() {
- close();
- }
-
- private:
- static constexpr bool VERIFY_PEER = true;
- static constexpr int VERIFY_DEPTH = 10;
-
- SocketFd fd_;
- mutable int write_mask_ = 0;
- mutable int read_mask_ = 0;
-
- // TODO unique_ptr
- SSL *ssl_handle_ = nullptr;
- SSL_CTX *ssl_ctx_ = nullptr;
-
- SslFd(SocketFd &&fd, SSL *ssl_handle_, SSL_CTX *ssl_ctx_);
-
- Result<size_t> process_ssl_error(int ret, int *mask) TD_WARN_UNUSED_RESULT;
-};
-
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/SslStream.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/SslStream.cpp
new file mode 100644
index 0000000000..bede94b0f1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/SslStream.cpp
@@ -0,0 +1,420 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/net/SslStream.h"
+
+#if !TD_EMSCRIPTEN
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/IPAddress.h"
+#include "td/utils/Status.h"
+#include "td/utils/Time.h"
+
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+
+#include <cstring>
+#include <memory>
+
+namespace td {
+
+namespace detail {
+namespace {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+void *BIO_get_data(BIO *b) {
+ return b->ptr;
+}
+void BIO_set_data(BIO *b, void *ptr) {
+ b->ptr = ptr;
+}
+void BIO_set_init(BIO *b, int init) {
+ b->init = init;
+}
+
+int BIO_get_new_index() {
+ return 0;
+}
+BIO_METHOD *BIO_meth_new(int type, const char *name) {
+ auto res = new BIO_METHOD();
+ std::memset(res, 0, sizeof(*res));
+ return res;
+}
+
+int BIO_meth_set_write(BIO_METHOD *biom, int (*bwrite)(BIO *, const char *, int)) {
+ biom->bwrite = bwrite;
+ return 1;
+}
+int BIO_meth_set_read(BIO_METHOD *biom, int (*bread)(BIO *, char *, int)) {
+ biom->bread = bread;
+ return 1;
+}
+int BIO_meth_set_ctrl(BIO_METHOD *biom, long (*ctrl)(BIO *, int, long, void *)) {
+ biom->ctrl = ctrl;
+ return 1;
+}
+int BIO_meth_set_create(BIO_METHOD *biom, int (*create)(BIO *)) {
+ biom->create = create;
+ return 1;
+}
+int BIO_meth_set_destroy(BIO_METHOD *biom, int (*destroy)(BIO *)) {
+ biom->destroy = destroy;
+ return 1;
+}
+#endif
+
+int strm_create(BIO *b) {
+ BIO_set_init(b, 1);
+ return 1;
+}
+
+int strm_destroy(BIO *b) {
+ return 1;
+}
+
+int strm_read(BIO *b, char *buf, int len);
+
+int strm_write(BIO *b, const char *buf, int len);
+
+long strm_ctrl(BIO *b, int cmd, long num, void *ptr) {
+ switch (cmd) {
+ case BIO_CTRL_FLUSH:
+ return 1;
+ case BIO_CTRL_PUSH:
+ case BIO_CTRL_POP:
+ return 0;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ case BIO_CTRL_GET_KTLS_SEND:
+ case BIO_CTRL_GET_KTLS_RECV:
+ return 0;
+#endif
+ default:
+ LOG(FATAL) << b << " " << cmd << " " << num << " " << ptr;
+ }
+ return 1;
+}
+
+BIO_METHOD *BIO_s_sslstream() {
+ static BIO_METHOD *result = [] {
+ BIO_METHOD *res = BIO_meth_new(BIO_get_new_index(), "td::SslStream helper bio");
+ BIO_meth_set_write(res, strm_write);
+ BIO_meth_set_read(res, strm_read);
+ BIO_meth_set_create(res, strm_create);
+ BIO_meth_set_destroy(res, strm_destroy);
+ BIO_meth_set_ctrl(res, strm_ctrl);
+ return res;
+ }();
+ return result;
+}
+
+struct SslHandleDeleter {
+ void operator()(SSL *ssl_handle) {
+ auto start_time = Time::now();
+ if (SSL_is_init_finished(ssl_handle)) {
+ clear_openssl_errors("Before SSL_shutdown");
+ SSL_set_quiet_shutdown(ssl_handle, 1);
+ SSL_shutdown(ssl_handle);
+ clear_openssl_errors("After SSL_shutdown");
+ }
+ SSL_free(ssl_handle);
+ auto elapsed_time = Time::now() - start_time;
+ if (elapsed_time >= 0.1) {
+ LOG(ERROR) << "SSL_free took " << elapsed_time << " seconds";
+ }
+ }
+};
+
+using SslHandle = std::unique_ptr<SSL, SslHandleDeleter>;
+
+} // namespace
+
+class SslStreamImpl {
+ public:
+ Status init(CSlice host, SslCtx ssl_ctx, bool check_ip_address_as_host) {
+ if (!ssl_ctx) {
+ return Status::Error("Invalid SSL context provided");
+ }
+
+ clear_openssl_errors("Before SslFd::init");
+
+ auto ssl_handle = SslHandle(SSL_new(static_cast<SSL_CTX *>(ssl_ctx.get_openssl_ctx())));
+ if (!ssl_handle) {
+ return create_openssl_error(-13, "Failed to create an SSL handle");
+ }
+
+ auto r_ip_address = IPAddress::get_ip_address(host);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ X509_VERIFY_PARAM *param = SSL_get0_param(ssl_handle.get());
+ X509_VERIFY_PARAM_set_hostflags(param, 0);
+ if (r_ip_address.is_ok() && !check_ip_address_as_host) {
+ LOG(DEBUG) << "Set verification IP address to " << r_ip_address.ok().get_ip_str();
+ X509_VERIFY_PARAM_set1_ip_asc(param, r_ip_address.ok().get_ip_str().c_str());
+ } else {
+ LOG(DEBUG) << "Set verification host to " << host;
+ X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
+ }
+#else
+#warning DANGEROUS! HTTPS HOST WILL NOT BE CHECKED. INSTALL OPENSSL >= 1.0.2 OR IMPLEMENT HTTPS HOST CHECK MANUALLY
+#endif
+
+ auto *bio = BIO_new(BIO_s_sslstream());
+ BIO_set_data(bio, static_cast<void *>(this));
+ SSL_set_bio(ssl_handle.get(), bio, bio);
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
+ if (r_ip_address.is_error()) { // IP address must not be send as SNI
+ LOG(DEBUG) << "Set SNI host name to " << host;
+ auto host_str = host.str();
+ SSL_set_tlsext_host_name(ssl_handle.get(), MutableCSlice(host_str).begin());
+ }
+#endif
+ SSL_set_connect_state(ssl_handle.get());
+
+ ssl_handle_ = std::move(ssl_handle);
+
+ return Status::OK();
+ }
+
+ ByteFlowInterface &read_byte_flow() {
+ return read_flow_;
+ }
+ ByteFlowInterface &write_byte_flow() {
+ return write_flow_;
+ }
+ size_t flow_read(MutableSlice slice) {
+ return read_flow_.read(slice);
+ }
+ size_t flow_write(Slice slice) {
+ return write_flow_.write(slice);
+ }
+
+ private:
+ SslHandle ssl_handle_;
+
+ friend class SslReadByteFlow;
+ friend class SslWriteByteFlow;
+
+ Result<size_t> write(Slice slice) {
+ clear_openssl_errors("Before SslFd::write");
+ auto start_time = Time::now();
+ auto size = SSL_write(ssl_handle_.get(), slice.data(), static_cast<int>(slice.size()));
+ auto elapsed_time = Time::now() - start_time;
+ if (elapsed_time >= 0.1) {
+ LOG(ERROR) << "SSL_write of size " << slice.size() << " took " << elapsed_time << " seconds and returned " << size
+ << ' ' << SSL_get_error(ssl_handle_.get(), size);
+ }
+ if (size <= 0) {
+ return process_ssl_error(size);
+ }
+ return size;
+ }
+
+ Result<size_t> read(MutableSlice slice) {
+ clear_openssl_errors("Before SslFd::read");
+ auto start_time = Time::now();
+ auto size = SSL_read(ssl_handle_.get(), slice.data(), static_cast<int>(slice.size()));
+ auto elapsed_time = Time::now() - start_time;
+ if (elapsed_time >= 0.1) {
+ LOG(ERROR) << "SSL_read took " << elapsed_time << " seconds and returned " << size << ' '
+ << SSL_get_error(ssl_handle_.get(), size);
+ }
+ if (size <= 0) {
+ return process_ssl_error(size);
+ }
+ return size;
+ }
+
+ class SslReadByteFlow final : public ByteFlowBase {
+ public:
+ explicit SslReadByteFlow(SslStreamImpl *stream) : stream_(stream) {
+ }
+ bool loop() final {
+ auto to_read = output_.prepare_append();
+ auto r_size = stream_->read(to_read);
+ if (r_size.is_error()) {
+ finish(r_size.move_as_error());
+ return false;
+ }
+ auto size = r_size.move_as_ok();
+ if (size == 0) {
+ return false;
+ }
+ output_.confirm_append(size);
+ return true;
+ }
+
+ size_t read(MutableSlice data) {
+ return input_->advance(min(data.size(), input_->size()), data);
+ }
+
+ private:
+ SslStreamImpl *stream_;
+ };
+
+ class SslWriteByteFlow final : public ByteFlowBase {
+ public:
+ explicit SslWriteByteFlow(SslStreamImpl *stream) : stream_(stream) {
+ }
+ bool loop() final {
+ auto to_write = input_->prepare_read();
+ auto r_size = stream_->write(to_write);
+ if (r_size.is_error()) {
+ finish(r_size.move_as_error());
+ return false;
+ }
+ auto size = r_size.move_as_ok();
+ if (size == 0) {
+ return false;
+ }
+ input_->confirm_read(size);
+ return true;
+ }
+
+ size_t write(Slice data) {
+ output_.append(data);
+ return data.size();
+ }
+
+ private:
+ SslStreamImpl *stream_;
+ };
+
+ SslReadByteFlow read_flow_{this};
+ SslWriteByteFlow write_flow_{this};
+
+ Result<size_t> process_ssl_error(int ret) {
+ auto os_error = OS_ERROR("SSL_ERROR_SYSCALL");
+ int error = SSL_get_error(ssl_handle_.get(), ret);
+ switch (error) {
+ case SSL_ERROR_NONE:
+ LOG(ERROR) << "SSL_get_error returned no error";
+ return 0;
+ case SSL_ERROR_ZERO_RETURN:
+ LOG(DEBUG) << "SSL_ZERO_RETURN";
+ return 0;
+ case SSL_ERROR_WANT_READ:
+ LOG(DEBUG) << "SSL_WANT_READ";
+ return 0;
+ case SSL_ERROR_WANT_WRITE:
+ LOG(DEBUG) << "SSL_WANT_WRITE";
+ return 0;
+ case SSL_ERROR_WANT_CONNECT:
+ case SSL_ERROR_WANT_ACCEPT:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ LOG(DEBUG) << "SSL: CONNECT ACCEPT LOOKUP";
+ return 0;
+ case SSL_ERROR_SYSCALL:
+ if (ERR_peek_error() == 0) {
+ if (os_error.code() != 0) {
+ LOG(DEBUG) << "SSL_ERROR_SYSCALL";
+ return std::move(os_error);
+ } else {
+ LOG(DEBUG) << "SSL_SYSCALL";
+ return 0;
+ }
+ }
+ /* fallthrough */
+ default:
+ LOG(DEBUG) << "SSL_ERROR Default";
+ return create_openssl_error(1, "SSL error ");
+ }
+ }
+};
+
+namespace {
+int strm_read(BIO *b, char *buf, int len) {
+ auto *stream = static_cast<SslStreamImpl *>(BIO_get_data(b));
+ CHECK(stream != nullptr);
+ BIO_clear_retry_flags(b);
+ CHECK(buf != nullptr);
+ auto res = narrow_cast<int>(stream->flow_read(MutableSlice(buf, len)));
+ if (res == 0) {
+ BIO_set_retry_read(b);
+ return -1;
+ }
+ return res;
+}
+int strm_write(BIO *b, const char *buf, int len) {
+ auto *stream = static_cast<SslStreamImpl *>(BIO_get_data(b));
+ CHECK(stream != nullptr);
+ BIO_clear_retry_flags(b);
+ CHECK(buf != nullptr);
+ return narrow_cast<int>(stream->flow_write(Slice(buf, len)));
+}
+} // namespace
+
+} // namespace detail
+
+SslStream::SslStream() = default;
+SslStream::SslStream(SslStream &&) noexcept = default;
+SslStream &SslStream::operator=(SslStream &&) noexcept = default;
+SslStream::~SslStream() = default;
+
+Result<SslStream> SslStream::create(CSlice host, SslCtx ssl_ctx, bool use_ip_address_as_host) {
+ auto impl = make_unique<detail::SslStreamImpl>();
+ TRY_STATUS(impl->init(host, ssl_ctx, use_ip_address_as_host));
+ return SslStream(std::move(impl));
+}
+SslStream::SslStream(unique_ptr<detail::SslStreamImpl> impl) : impl_(std::move(impl)) {
+}
+ByteFlowInterface &SslStream::read_byte_flow() {
+ return impl_->read_byte_flow();
+}
+ByteFlowInterface &SslStream::write_byte_flow() {
+ return impl_->write_byte_flow();
+}
+size_t SslStream::flow_read(MutableSlice slice) {
+ return impl_->flow_read(slice);
+}
+size_t SslStream::flow_write(Slice slice) {
+ return impl_->flow_write(slice);
+}
+
+} // namespace td
+
+#else
+
+namespace td {
+
+namespace detail {
+class SslStreamImpl {};
+} // namespace detail
+
+SslStream::SslStream() = default;
+SslStream::SslStream(SslStream &&) noexcept = default;
+SslStream &SslStream::operator=(SslStream &&) noexcept = default;
+SslStream::~SslStream() = default;
+
+Result<SslStream> SslStream::create(CSlice host, SslCtx ssl_ctx, bool check_ip_address_as_host) {
+ return Status::Error("Not supported in Emscripten");
+}
+
+SslStream::SslStream(unique_ptr<detail::SslStreamImpl> impl) : impl_(std::move(impl)) {
+}
+
+ByteFlowInterface &SslStream::read_byte_flow() {
+ UNREACHABLE();
+}
+
+ByteFlowInterface &SslStream::write_byte_flow() {
+ UNREACHABLE();
+}
+
+size_t SslStream::flow_read(MutableSlice slice) {
+ UNREACHABLE();
+}
+
+size_t SslStream::flow_write(Slice slice) {
+ UNREACHABLE();
+}
+
+} // namespace td
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/SslStream.h b/protocols/Telegram/tdlib/td/tdnet/td/net/SslStream.h
new file mode 100644
index 0000000000..286eb80be3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/SslStream.h
@@ -0,0 +1,46 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/net/SslCtx.h"
+
+#include "td/utils/ByteFlow.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+namespace detail {
+class SslStreamImpl;
+} // namespace detail
+
+class SslStream {
+ public:
+ SslStream();
+ SslStream(SslStream &&) noexcept;
+ SslStream &operator=(SslStream &&) noexcept;
+ ~SslStream();
+
+ static Result<SslStream> create(CSlice host, SslCtx ssl_ctx, bool use_ip_address_as_host = false);
+
+ ByteFlowInterface &read_byte_flow();
+ ByteFlowInterface &write_byte_flow();
+
+ size_t flow_read(MutableSlice slice);
+ size_t flow_write(Slice slice);
+
+ explicit operator bool() const noexcept {
+ return static_cast<bool>(impl_);
+ }
+
+ private:
+ unique_ptr<detail::SslStreamImpl> impl_;
+
+ explicit SslStream(unique_ptr<detail::SslStreamImpl> impl);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.cpp
index 54531f9b60..7a8d280624 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,11 +7,12 @@
#include "td/net/TcpListener.h"
#include "td/utils/logging.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
namespace td {
-// TcpListener implementation
-TcpListener::TcpListener(int port, ActorShared<Callback> callback) : port_(port), callback_(std::move(callback)) {
+
+TcpListener::TcpListener(int port, ActorShared<Callback> callback, Slice server_address)
+ : port_(port), callback_(std::move(callback)), server_address_(server_address.str()) {
}
void TcpListener::hangup() {
@@ -19,21 +20,19 @@ void TcpListener::hangup() {
}
void TcpListener::start_up() {
- auto r_socket = ServerSocketFd::open(port_);
+ auto r_socket = ServerSocketFd::open(port_, server_address_);
if (r_socket.is_error()) {
LOG(ERROR) << "Can't open server socket: " << r_socket.error();
set_timeout_in(5);
return;
}
server_fd_ = r_socket.move_as_ok();
- server_fd_.get_fd().set_observer(this);
- subscribe(server_fd_.get_fd());
+ Scheduler::subscribe(server_fd_.get_poll_info().extract_pollable_fd(this));
}
void TcpListener::tear_down() {
- LOG(ERROR) << "TcpListener closed";
if (!server_fd_.empty()) {
- unsubscribe_before_close(server_fd_.get_fd());
+ Scheduler::unsubscribe_before_close(server_fd_.get_poll_info().get_pollable_fd_ref());
server_fd_.close();
}
}
@@ -41,8 +40,12 @@ void TcpListener::tear_down() {
void TcpListener::loop() {
if (server_fd_.empty()) {
start_up();
+ if (server_fd_.empty()) {
+ return;
+ }
}
- while (can_read(server_fd_)) {
+ sync_with_poll(server_fd_);
+ while (can_read_local(server_fd_)) {
auto r_socket_fd = server_fd_.accept();
if (r_socket_fd.is_error()) {
if (r_socket_fd.error().code() != -1) {
@@ -53,8 +56,7 @@ void TcpListener::loop() {
send_closure(callback_, &Callback::accept, r_socket_fd.move_as_ok());
}
- if (can_close(server_fd_)) {
- LOG(ERROR) << "HELLO!";
+ if (can_close_local(server_fd_)) {
stop();
}
}
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.h b/protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.h
index f2e61a2387..84cf3c6874 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/TcpListener.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,6 +10,7 @@
#include "td/utils/port/ServerSocketFd.h"
#include "td/utils/port/SocketFd.h"
+#include "td/utils/Slice.h"
namespace td {
@@ -20,16 +21,17 @@ class TcpListener final : public Actor {
virtual void accept(SocketFd fd) = 0;
};
- TcpListener(int port, ActorShared<Callback> callback);
- void hangup() override;
+ TcpListener(int port, ActorShared<Callback> callback, Slice server_address = Slice("0.0.0.0"));
+ void hangup() final;
private:
int port_;
ServerSocketFd server_fd_;
ActorShared<Callback> callback_;
- void start_up() override;
- void tear_down() override;
- void loop() override;
+ const string server_address_;
+ void start_up() final;
+ void tear_down() final;
+ void loop() final;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/TransparentProxy.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/TransparentProxy.cpp
new file mode 100644
index 0000000000..b5102a37b4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/TransparentProxy.cpp
@@ -0,0 +1,84 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/net/TransparentProxy.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/port/detail/PollableFd.h"
+
+namespace td {
+
+int VERBOSITY_NAME(proxy) = VERBOSITY_NAME(DEBUG);
+
+TransparentProxy::TransparentProxy(SocketFd socket_fd, IPAddress ip_address, string username, string password,
+ unique_ptr<Callback> callback, ActorShared<> parent)
+ : fd_(std::move(socket_fd))
+ , ip_address_(std::move(ip_address))
+ , username_(std::move(username))
+ , password_(std::move(password))
+ , callback_(std::move(callback))
+ , parent_(std::move(parent)) {
+}
+
+void TransparentProxy::on_error(Status status) {
+ CHECK(status.is_error());
+ VLOG(proxy) << "Receive " << status;
+ if (callback_) {
+ callback_->set_result(std::move(status));
+ callback_.reset();
+ }
+ stop();
+}
+
+void TransparentProxy::tear_down() {
+ VLOG(proxy) << "Finish to connect to proxy";
+ Scheduler::unsubscribe(fd_.get_poll_info().get_pollable_fd_ref());
+ if (callback_) {
+ if (!fd_.input_buffer().empty()) {
+ LOG(ERROR) << "Have " << fd_.input_buffer().size() << " unread bytes";
+ callback_->set_result(Status::Error("Proxy has sent too many data"));
+ } else {
+ callback_->set_result(std::move(fd_));
+ }
+ callback_.reset();
+ }
+}
+
+void TransparentProxy::hangup() {
+ on_error(Status::Error("Canceled"));
+}
+
+void TransparentProxy::start_up() {
+ VLOG(proxy) << "Begin to connect to proxy";
+ Scheduler::subscribe(fd_.get_poll_info().extract_pollable_fd(this));
+ set_timeout_in(10);
+ sync_with_poll(fd_);
+ if (can_write_local(fd_)) {
+ loop();
+ }
+}
+
+void TransparentProxy::loop() {
+ sync_with_poll(fd_);
+ auto status = [&] {
+ TRY_STATUS(fd_.flush_read());
+ TRY_STATUS(loop_impl());
+ TRY_STATUS(fd_.flush_write());
+ return Status::OK();
+ }();
+ if (status.is_error()) {
+ on_error(std::move(status));
+ }
+ if (can_close_local(fd_)) {
+ on_error(Status::Error("Connection closed"));
+ }
+}
+
+void TransparentProxy::timeout_expired() {
+ on_error(Status::Error("Connection timeout expired"));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/TransparentProxy.h b/protocols/Telegram/tdlib/td/tdnet/td/net/TransparentProxy.h
new file mode 100644
index 0000000000..66a3830589
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/TransparentProxy.h
@@ -0,0 +1,57 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/actor/actor.h"
+
+#include "td/utils/BufferedFd.h"
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/IPAddress.h"
+#include "td/utils/port/SocketFd.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+extern int VERBOSITY_NAME(proxy);
+
+class TransparentProxy : public Actor {
+ public:
+ class Callback {
+ public:
+ Callback() = default;
+ Callback(const Callback &) = delete;
+ Callback &operator=(const Callback &) = delete;
+ virtual ~Callback() = default;
+
+ virtual void set_result(Result<BufferedFd<SocketFd>> r_buffered_socket_fd) = 0;
+ virtual void on_connected() = 0;
+ };
+
+ TransparentProxy(SocketFd socket_fd, IPAddress ip_address, string username, string password,
+ unique_ptr<Callback> callback, ActorShared<> parent);
+
+ protected:
+ BufferedFd<SocketFd> fd_;
+ IPAddress ip_address_;
+ string username_;
+ string password_;
+ unique_ptr<Callback> callback_;
+ ActorShared<> parent_;
+
+ void on_error(Status status);
+ void tear_down() override;
+ void start_up() override;
+ void hangup() override;
+
+ void loop() override;
+ void timeout_expired() override;
+
+ virtual Status loop_impl() = 0;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/Wget.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/Wget.cpp
index b30128be32..f6a6c72eb8 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/Wget.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/Wget.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,62 +8,90 @@
#include "td/net/HttpHeaderCreator.h"
#include "td/net/HttpOutboundConnection.h"
-#include "td/net/SslFd.h"
+#include "td/net/SslStream.h"
#include "td/utils/buffer.h"
+#include "td/utils/BufferedFd.h"
#include "td/utils/HttpUrl.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include <limits>
namespace td {
-Wget::Wget(Promise<HttpQueryPtr> promise, string url, std::vector<std::pair<string, string>> headers, int32 timeout_in,
- int32 ttl, SslFd::VerifyPeer verify_peer)
+
+Wget::Wget(Promise<unique_ptr<HttpQuery>> promise, string url, std::vector<std::pair<string, string>> headers,
+ int32 timeout_in, int32 ttl, bool prefer_ipv6, SslCtx::VerifyPeer verify_peer, string content,
+ string content_type)
: promise_(std::move(promise))
, input_url_(std::move(url))
, headers_(std::move(headers))
, timeout_in_(timeout_in)
, ttl_(ttl)
- , verify_peer_(verify_peer) {
+ , prefer_ipv6_(prefer_ipv6)
+ , verify_peer_(verify_peer)
+ , content_(std::move(content))
+ , content_type_(std::move(content_type)) {
}
Status Wget::try_init() {
- string input_url = input_url_;
- TRY_RESULT(url, parse_url(MutableSlice(input_url)));
-
- IPAddress addr;
- TRY_STATUS(addr.init_host_port(url.host_, url.port_));
+ TRY_RESULT(url, parse_url(input_url_));
+ TRY_RESULT_ASSIGN(url.host_, idn_to_ascii(url.host_));
- TRY_RESULT(fd, SocketFd::open(addr));
- if (url.protocol_ == HttpUrl::Protocol::HTTP) {
- connection_ =
- create_actor<HttpOutboundConnection>("Connect", std::move(fd), std::numeric_limits<std::size_t>::max(), 0, 0,
- ActorOwn<HttpOutboundConnection::Callback>(actor_id(this)));
+ HttpHeaderCreator hc;
+ if (content_.empty()) {
+ hc.init_get(url.query_);
} else {
- TRY_RESULT(ssl_fd, SslFd::init(std::move(fd), url.host_, CSlice() /* certificate */, verify_peer_));
- connection_ =
- create_actor<HttpOutboundConnection>("Connect", std::move(ssl_fd), std::numeric_limits<std::size_t>::max(), 0,
- 0, ActorOwn<HttpOutboundConnection::Callback>(actor_id(this)));
+ hc.init_post(url.query_);
+ hc.set_content_size(content_.size());
+ if (!content_type_.empty()) {
+ hc.set_content_type(content_type_);
+ }
}
-
- HttpHeaderCreator hc;
- hc.init_get(url.query_);
bool was_host = false;
+ bool was_accept_encoding = false;
for (auto &header : headers_) {
- if (header.first == "Host") { // TODO: lowercase
+ auto header_lower = to_lower(header.first);
+ if (header_lower == "host") {
was_host = true;
}
+ if (header_lower == "accept-encoding") {
+ was_accept_encoding = true;
+ }
hc.add_header(header.first, header.second);
}
if (!was_host) {
hc.add_header("Host", url.host_);
}
- hc.add_header("Accept-Encoding", "gzip, deflate");
+ if (!was_accept_encoding) {
+ hc.add_header("Accept-Encoding", "gzip, deflate");
+ }
+ TRY_RESULT(header, hc.finish(content_));
+
+ IPAddress addr;
+ TRY_STATUS(addr.init_host_port(url.host_, url.port_, prefer_ipv6_));
- send_closure(connection_, &HttpOutboundConnection::write_next, BufferSlice(hc.finish().ok()));
+ TRY_RESULT(fd, SocketFd::open(addr));
+ if (fd.empty()) {
+ return Status::Error("Sockets are not supported");
+ }
+ if (url.protocol_ == HttpUrl::Protocol::Http) {
+ connection_ = create_actor<HttpOutboundConnection>("Connect", BufferedFd<SocketFd>(std::move(fd)), SslStream{},
+ std::numeric_limits<std::size_t>::max(), 0, 0,
+ ActorOwn<HttpOutboundConnection::Callback>(actor_id(this)));
+ } else {
+ TRY_RESULT(ssl_ctx, SslCtx::create(CSlice() /* certificate */, verify_peer_));
+ TRY_RESULT(ssl_stream, SslStream::create(url.host_, std::move(ssl_ctx)));
+ connection_ = create_actor<HttpOutboundConnection>(
+ "Connect", BufferedFd<SocketFd>(std::move(fd)), std::move(ssl_stream), std::numeric_limits<std::size_t>::max(),
+ 0, 0, ActorOwn<HttpOutboundConnection::Callback>(actor_id(this)));
+ }
+
+ send_closure(connection_, &HttpOutboundConnection::write_next, BufferSlice(header));
send_closure(connection_, &HttpOutboundConnection::write_ok);
return Status::OK();
}
@@ -77,7 +105,7 @@ void Wget::loop() {
}
}
-void Wget::handle(HttpQueryPtr result) {
+void Wget::handle(unique_ptr<HttpQuery> result) {
on_ok(std::move(result));
}
@@ -85,11 +113,14 @@ void Wget::on_connection_error(Status error) {
on_error(std::move(error));
}
-void Wget::on_ok(HttpQueryPtr http_query_ptr) {
+void Wget::on_ok(unique_ptr<HttpQuery> http_query_ptr) {
CHECK(promise_);
- if (http_query_ptr->code_ == 302 && ttl_ > 0) {
+ CHECK(http_query_ptr);
+ if ((http_query_ptr->code_ == 301 || http_query_ptr->code_ == 302 || http_query_ptr->code_ == 307 ||
+ http_query_ptr->code_ == 308) &&
+ ttl_ > 0) {
LOG(DEBUG) << *http_query_ptr;
- input_url_ = http_query_ptr->header("location").str();
+ input_url_ = http_query_ptr->get_header("location").str();
LOG(DEBUG) << input_url_;
ttl_--;
connection_.reset();
@@ -98,7 +129,7 @@ void Wget::on_ok(HttpQueryPtr http_query_ptr) {
promise_.set_value(std::move(http_query_ptr));
stop();
} else {
- on_error(Status::Error(PSLICE() << "http error: " << http_query_ptr->code_));
+ on_error(Status::Error(PSLICE() << "HTTP error: " << http_query_ptr->code_));
}
}
@@ -115,12 +146,13 @@ void Wget::start_up() {
}
void Wget::timeout_expired() {
- on_error(Status::Error("Timeout expired"));
+ on_error(Status::Error("Response timeout expired"));
}
void Wget::tear_down() {
if (promise_) {
- on_error(Status::Error("Cancelled"));
+ on_error(Status::Error("Canceled"));
}
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/Wget.h b/protocols/Telegram/tdlib/td/tdnet/td/net/Wget.h
index cecb113c94..f3d225982b 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/Wget.h
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/Wget.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,41 +8,46 @@
#include "td/net/HttpOutboundConnection.h"
#include "td/net/HttpQuery.h"
-#include "td/net/SslFd.h"
+#include "td/net/SslCtx.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/actor/actor.h"
#include "td/utils/common.h"
+#include "td/utils/Promise.h"
#include "td/utils/Status.h"
#include <utility>
namespace td {
-class Wget : public HttpOutboundConnection::Callback {
+class Wget final : public HttpOutboundConnection::Callback {
public:
- explicit Wget(Promise<HttpQueryPtr> promise, string url, std::vector<std::pair<string, string>> headers = {},
- int32 timeout_in = 10, int32 ttl = 3, SslFd::VerifyPeer verify_peer = SslFd::VerifyPeer::On);
+ explicit Wget(Promise<unique_ptr<HttpQuery>> promise, string url, std::vector<std::pair<string, string>> headers = {},
+ int32 timeout_in = 10, int32 ttl = 3, bool prefer_ipv6 = false,
+ SslCtx::VerifyPeer verify_peer = SslCtx::VerifyPeer::On, string content = {}, string content_type = {});
private:
Status try_init();
- void loop() override;
- void handle(HttpQueryPtr result) override;
- void on_connection_error(Status error) override;
- void on_ok(HttpQueryPtr http_query_ptr);
+ void loop() final;
+ void handle(unique_ptr<HttpQuery> result) final;
+ void on_connection_error(Status error) final;
+ void on_ok(unique_ptr<HttpQuery> http_query_ptr);
void on_error(Status error);
- void tear_down() override;
- void start_up() override;
- void timeout_expired() override;
+ void tear_down() final;
+ void start_up() final;
+ void timeout_expired() final;
- Promise<HttpQueryPtr> promise_;
+ Promise<unique_ptr<HttpQuery>> promise_;
ActorOwn<HttpOutboundConnection> connection_;
string input_url_;
std::vector<std::pair<string, string>> headers_;
int32 timeout_in_;
int32 ttl_;
- SslFd::VerifyPeer verify_peer_;
+ bool prefer_ipv6_ = false;
+ SslCtx::VerifyPeer verify_peer_;
+ string content_;
+ string content_type_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdtl/CMakeLists.txt b/protocols/Telegram/tdlib/td/tdtl/CMakeLists.txt
index b0f83cd98d..8b139ddc68 100644
--- a/protocols/Telegram/tdlib/td/tdtl/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/tdtl/CMakeLists.txt
@@ -1,4 +1,6 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
#SOURCE SETS
set(TDTL_SOURCE
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.cpp b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.cpp
index e796596760..871b7e22b3 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.cpp
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.cpp
@@ -1,10 +1,10 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "tl_config.h"
+#include "td/tl/tl_config.h"
#include <cassert>
#include <cstdio>
@@ -40,7 +40,7 @@ void tl_config::add_type(tl_type *type) {
}
tl_type *tl_config::get_type(std::int32_t type_id) const {
- auto it = id_to_type.find(type_id);
+ std::map<std::int32_t, tl_type *>::const_iterator it = id_to_type.find(type_id);
assert(it != id_to_type.end());
return it->second;
}
@@ -327,7 +327,7 @@ tl_type *tl_config_parser::read_type() {
tl_config tl_config_parser::parse_config() {
schema_version = get_schema_version(try_parse_int());
if (schema_version < 2) {
- std::fprintf(stderr, "Unsupported tl-schema verdion %d\n", static_cast<int>(schema_version));
+ std::fprintf(stderr, "Unsupported tl-schema version %d\n", static_cast<int>(schema_version));
std::abort();
}
@@ -344,6 +344,7 @@ tl_config tl_config_parser::parse_config() {
std::int32_t constructors_n = try_parse_int();
assert(static_cast<std::size_t>(constructors_n) == constructors_total);
+ (void)constructors_total;
for (std::int32_t i = 0; i < constructors_n; i++) {
tl_combinator *constructor = read_combinator();
config.get_type(constructor->type_id)->add_constructor(constructor);
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.h
index 6a25a9cd09..9735a83626 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_config.h
@@ -1,13 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "tl_core.h"
-#include "tl_simple_parser.h"
+#include "td/tl/tl_core.h"
+#include "td/tl/tl_simple_parser.h"
#include <cstddef>
#include <cstdint>
@@ -77,7 +77,7 @@ class tl_config_parser {
std::string try_parse_string();
public:
- tl_config_parser(const char *s, std::size_t len) : p(s, len) {
+ tl_config_parser(const char *s, std::size_t len) : p(s, len), schema_version(-1) {
}
tl_config parse_config();
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.cpp b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.cpp
index f8aa6eb10d..a77e7092f3 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.cpp
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.cpp
@@ -1,10 +1,10 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "tl_core.h"
+#include "td/tl/tl_core.h"
#include <cassert>
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.h
index 04fabe1168..edf7580a80 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_core.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.cpp b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.cpp
index 046a1ce84e..6d52a27079 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.cpp
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.cpp
@@ -1,10 +1,10 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "tl_file_outputer.h"
+#include "td/tl/tl_file_outputer.h"
#include <cassert>
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.h
index 3b9c66caaf..7be40d5635 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_outputer.h
@@ -1,12 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "tl_outputer.h"
+#include "td/tl/tl_outputer.h"
#include <cstdio>
#include <string>
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.cpp b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.cpp
index bc79d7254e..5aa5d31942 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.cpp
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.cpp
@@ -1,14 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "tl_file_utils.h"
+#include "td/tl/tl_file_utils.h"
#include <cstdio>
#include <cstdlib>
-#include <cstring>
namespace td {
namespace tl {
@@ -32,11 +31,13 @@ std::string get_file_contents(const std::string &file_name, const std::string &m
std::size_t size = static_cast<std::size_t>(size_long);
std::string result(size, ' ');
- std::rewind(f);
- std::size_t fread_res = std::fread(&result[0], size, 1, f);
- if (size != 0 && fread_res != 1) {
- std::fprintf(stderr, "Can't read file \"%s\"", file_name.c_str());
- std::abort();
+ if (size != 0) {
+ std::rewind(f);
+ std::size_t fread_res = std::fread(&result[0], size, 1, f);
+ if (fread_res != 1) {
+ std::fprintf(stderr, "Can't read file \"%s\"", file_name.c_str());
+ std::abort();
+ }
}
std::fclose(f);
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.h
index 50e40b4418..2854cdc9ea 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_file_utils.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.cpp b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.cpp
index 401d451cd6..d2d7528ea3 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.cpp
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.cpp
@@ -1,17 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "tl_generate.h"
+#include "td/tl/tl_generate.h"
-#include "tl_config.h"
-#include "tl_core.h"
-#include "tl_file_utils.h"
-#include "tl_outputer.h"
-#include "tl_string_outputer.h"
-#include "tl_writer.h"
+#include "td/tl/tl_config.h"
+#include "td/tl/tl_core.h"
+#include "td/tl/tl_file_utils.h"
+#include "td/tl/tl_outputer.h"
+#include "td/tl/tl_string_outputer.h"
+#include "td/tl/tl_writer.h"
#include <cassert>
#include <cstdint>
@@ -53,26 +53,26 @@ static bool is_reachable_for_storer(int storer_type, const std::string &name,
static void write_class_constructor(tl_outputer &out, const tl_combinator *t, const std::string &class_name,
bool is_default, const TL_writer &w) {
// std::fprintf(stderr, "Gen constructor %s\n", class_name.c_str());
- int fields_num = 0;
+ int field_count = 0;
for (std::size_t i = 0; i < t->args.size(); i++) {
- fields_num += !w.gen_constructor_parameter(0, class_name, t->args[i], is_default).empty();
+ field_count += !w.gen_constructor_parameter(field_count, class_name, t->args[i], is_default).empty();
}
- out.append(w.gen_constructor_begin(fields_num, class_name, is_default));
+ out.append(w.gen_constructor_begin(field_count, class_name, is_default));
int field_num = 0;
for (std::size_t i = 0; i < t->args.size(); i++) {
std::string parameter_init = w.gen_constructor_parameter(field_num, class_name, t->args[i], is_default);
- if (parameter_init.size()) {
+ if (!parameter_init.empty()) {
out.append(parameter_init);
field_num++;
}
}
- assert(field_num == fields_num);
+ assert(field_num == field_count);
field_num = 0;
for (std::size_t i = 0; i < t->args.size(); i++) {
std::string field_init = w.gen_constructor_field_init(field_num, class_name, t->args[i], is_default);
- if (field_init.size()) {
+ if (!field_init.empty()) {
out.append(field_init);
field_num++;
}
@@ -85,38 +85,37 @@ static void write_function_fetch(tl_outputer &out, const std::string &parser_nam
const std::string &class_name, const std::set<std::string> &request_types,
const std::set<std::string> &result_types, const TL_writer &w) {
// std::fprintf(stderr, "Write function fetch %s\n", class_name.c_str());
- std::vector<var_description> vars(t->var_count);
int parser_type = w.get_parser_type(t, parser_name);
if (!is_reachable_for_parser(parser_type, t->name, request_types, result_types, w)) {
return;
}
- out.append(w.gen_fetch_function_begin(parser_name, class_name, 0, vars, parser_type));
+ std::vector<var_description> vars(t->var_count);
+ out.append(w.gen_fetch_function_begin(parser_name, class_name, class_name, 0, static_cast<int>(t->args.size()), vars,
+ parser_type));
out.append(w.gen_vars(t, NULL, vars));
int field_num = 0;
for (std::size_t i = 0; i < t->args.size(); i++) {
std::string field_fetch = w.gen_field_fetch(field_num, t->args[i], vars, false, parser_type);
- if (field_fetch.size()) {
+ if (!field_fetch.empty()) {
out.append(field_fetch);
field_num++;
}
}
- out.append(w.gen_fetch_function_end(field_num, vars, parser_type));
+ out.append(w.gen_fetch_function_end(false, field_num, vars, parser_type));
}
-static std::vector<var_description> write_function_store(tl_outputer &out, const std::string &storer_name,
- const tl_combinator *t, const std::string &class_name,
- std::vector<var_description> &vars,
- const std::set<std::string> &request_types,
- const std::set<std::string> &result_types,
- const TL_writer &w) {
+static void write_function_store(tl_outputer &out, const std::string &storer_name, const tl_combinator *t,
+ const std::string &class_name, std::vector<var_description> &vars,
+ const std::set<std::string> &request_types, const std::set<std::string> &result_types,
+ const TL_writer &w) {
// std::fprintf(stderr, "Write function store %s\n", class_name.c_str());
int storer_type = w.get_storer_type(t, storer_name);
if (!is_reachable_for_storer(storer_type, t->name, request_types, result_types, w)) {
- return vars;
+ return;
}
out.append(w.gen_store_function_begin(storer_name, class_name, 0, vars, storer_type));
@@ -126,8 +125,6 @@ static std::vector<var_description> write_function_store(tl_outputer &out, const
}
out.append(w.gen_store_function_end(vars, storer_type));
-
- return vars;
}
static void write_function_result_fetch(tl_outputer &out, const std::string &parser_name, const tl_combinator *t,
@@ -167,31 +164,32 @@ static void write_function_result_fetch(tl_outputer &out, const std::string &par
}
static void write_constructor_fetch(tl_outputer &out, const std::string &parser_name, const tl_combinator *t,
- const std::string &class_name, const tl_tree_type *result_type, bool is_flat,
+ const std::string &class_name, const std::string &parent_class_name,
+ const tl_tree_type *result_type, bool is_flat,
const std::set<std::string> &request_types,
const std::set<std::string> &result_types, const TL_writer &w) {
- std::vector<var_description> vars(t->var_count);
-
int parser_type = w.get_parser_type(t, parser_name);
if (!is_reachable_for_parser(parser_type, t->name, request_types, result_types, w)) {
return;
}
- out.append(w.gen_fetch_function_begin(parser_name, class_name, static_cast<int>(result_type->children.size()), vars,
- parser_type));
+ std::vector<var_description> vars(t->var_count);
+ out.append(w.gen_fetch_function_begin(parser_name, class_name, parent_class_name,
+ static_cast<int>(result_type->children.size()),
+ static_cast<int>(t->args.size()), vars, parser_type));
out.append(w.gen_vars(t, result_type, vars));
out.append(w.gen_uni(result_type, vars, true));
int field_num = 0;
for (std::size_t i = 0; i < t->args.size(); i++) {
std::string field_fetch = w.gen_field_fetch(field_num, t->args[i], vars, is_flat, parser_type);
- if (field_fetch.size()) {
+ if (!field_fetch.empty()) {
out.append(field_fetch);
field_num++;
}
}
- out.append(w.gen_fetch_function_end(field_num, vars, parser_type));
+ out.append(w.gen_fetch_function_end(class_name != parent_class_name, field_num, vars, parser_type));
}
static void write_constructor_store(tl_outputer &out, const std::string &storer_name, const tl_combinator *t,
@@ -233,7 +231,7 @@ static int gen_field_definitions(tl_outputer &out, const tl_combinator *t, const
}
std::string type_name = w.gen_field_type(a);
- if (type_name.size()) {
+ if (!type_name.empty()) {
out.append(w.gen_field_definition(class_name, type_name, w.gen_field_name(a.name)));
}
}
@@ -247,16 +245,18 @@ static void write_function(tl_outputer &out, const tl_combinator *t, const std::
std::string class_name = w.gen_class_name(t->name);
- out.append(w.gen_class_begin(class_name, w.gen_base_function_class_name(), false));
+ out.append(w.gen_class_begin(class_name, w.gen_base_function_class_name(), false, t->result));
int required_args = gen_field_definitions(out, t, class_name, w);
- out.append(w.gen_flags_definitions(t));
+ out.append(w.gen_flags_definitions(t, true));
std::vector<var_description> vars(t->var_count);
out.append(w.gen_function_vars(t, vars));
- write_class_constructor(out, t, class_name, true, w);
- if (required_args) {
+ if (w.is_default_constructor_generated(t, false, true)) {
+ write_class_constructor(out, t, class_name, true, w);
+ }
+ if (required_args && w.is_full_constructor_generated(t, false, true)) {
write_class_constructor(out, t, class_name, false, w);
}
@@ -294,19 +294,50 @@ static void write_function(tl_outputer &out, const tl_combinator *t, const std::
out.append(w.gen_class_end());
}
-static void write_constructor(tl_outputer &out, const tl_combinator *t, const std::string &base_class, bool is_proxy,
+static void write_constructor(tl_outputer &out, const tl_combinator *t, const std::string &base_class,
+ const std::string &parent_class, bool is_proxy,
const std::set<std::string> &request_types, const std::set<std::string> &result_types,
const TL_writer &w) {
assert(w.is_combinator_supported(t));
std::string class_name = w.gen_class_name(t->name);
- out.append(w.gen_class_begin(class_name, base_class, is_proxy));
+ out.append(w.gen_class_begin(class_name, base_class, is_proxy, t->result));
int required_args = gen_field_definitions(out, t, class_name, w);
- out.append(w.gen_flags_definitions(t));
- write_class_constructor(out, t, class_name, true, w);
- if (required_args) {
+ bool can_be_parsed = false;
+ bool is_can_be_parsed_inited = false;
+ std::vector<std::string> parsers = w.get_parsers();
+ for (std::size_t i = 0; i < parsers.size(); i++) {
+ int parser_type = w.get_parser_type(t, parsers[i]);
+ if (w.get_parser_mode(parser_type) != TL_writer::All) {
+ can_be_parsed |= is_reachable_for_parser(parser_type, t->name, request_types, result_types, w);
+ is_can_be_parsed_inited = true;
+ }
+ }
+ if (!is_can_be_parsed_inited) {
+ can_be_parsed = true;
+ }
+
+ bool can_be_stored = false;
+ bool is_can_be_stored_inited = false;
+ std::vector<std::string> storers = w.get_storers();
+ for (std::size_t i = 0; i < storers.size(); i++) {
+ int storer_type = w.get_storer_type(t, storers[i]);
+ if (w.get_storer_mode(storer_type) != TL_writer::All) {
+ can_be_stored |= is_reachable_for_storer(storer_type, t->name, request_types, result_types, w);
+ is_can_be_stored_inited = true;
+ }
+ }
+ if (!is_can_be_stored_inited) {
+ can_be_stored = true;
+ }
+
+ out.append(w.gen_flags_definitions(t, can_be_stored));
+ if (w.is_default_constructor_generated(t, can_be_parsed, can_be_stored)) {
+ write_class_constructor(out, t, class_name, true, w);
+ }
+ if (required_args && w.is_full_constructor_generated(t, can_be_parsed, can_be_stored)) {
write_class_constructor(out, t, class_name, false, w);
}
@@ -316,15 +347,13 @@ static void write_constructor(tl_outputer &out, const tl_combinator *t, const st
assert(t->result->get_type() == NODE_TYPE_TYPE);
const tl_tree_type *result_type = static_cast<const tl_tree_type *>(t->result);
- std::vector<std::string> parsers = w.get_parsers();
for (std::size_t i = 0; i < parsers.size(); i++) {
- write_constructor_fetch(out, parsers[i], t, class_name, result_type,
+ write_constructor_fetch(out, parsers[i], t, class_name, parent_class, result_type,
required_args == 1 && result_type->type->simple_constructors == 1, request_types,
result_types, w);
}
// STORER
- std::vector<std::string> storers = w.get_storers();
for (std::size_t i = 0; i < storers.size(); i++) {
write_constructor_store(out, storers[i], t, class_name, result_type,
required_args == 1 && result_type->type->simple_constructors == 1, request_types,
@@ -357,7 +386,7 @@ void write_class(tl_outputer &out, const tl_type *t, const std::set<std::string>
std::vector<var_description> empty_vars;
bool optimize_one_constructor = (t->simple_constructors == 1);
if (!optimize_one_constructor) {
- out.append(w.gen_class_begin(class_name, base_class, true));
+ out.append(w.gen_class_begin(class_name, base_class, true, nullptr));
out.append(w.gen_get_id(class_name, 0, true));
@@ -367,7 +396,7 @@ void write_class(tl_outputer &out, const tl_type *t, const std::set<std::string>
continue;
}
- out.append(w.gen_fetch_function_begin(parsers[i], class_name, t->arity, empty_vars, -1));
+ out.append(w.gen_fetch_function_begin(parsers[i], class_name, class_name, t->arity, -1, empty_vars, -1));
out.append(w.gen_fetch_switch_begin());
for (std::size_t j = 0; j < t->constructors_num; j++) {
if (w.is_combinator_supported(t->constructors[j])) {
@@ -376,7 +405,7 @@ void write_class(tl_outputer &out, const tl_type *t, const std::set<std::string>
}
out.append(w.gen_fetch_switch_end());
- out.append(w.gen_fetch_function_end(-1, empty_vars, -1));
+ out.append(w.gen_fetch_function_end(false, -1, empty_vars, -1));
}
std::vector<std::string> storers = w.get_storers();
@@ -409,10 +438,11 @@ void write_class(tl_outputer &out, const tl_type *t, const std::set<std::string>
for (std::size_t i = 0; i < t->constructors_num; i++) {
if (w.is_combinator_supported(t->constructors[i])) {
if (optimize_one_constructor) {
- write_constructor(out, t->constructors[i], base_class, false, request_types, result_types, w);
+ write_constructor(out, t->constructors[i], base_class, w.gen_class_name(t->constructors[i]->name), false,
+ request_types, result_types, w);
out.append(w.gen_class_alias(w.gen_class_name(t->constructors[i]->name), class_name));
} else {
- write_constructor(out, t->constructors[i], class_name, false, request_types, result_types, w);
+ write_constructor(out, t->constructors[i], class_name, class_name, false, request_types, result_types, w);
}
written_constructors++;
} else {
@@ -479,7 +509,6 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
std::size_t types_n = config.get_type_count();
std::size_t functions_n = config.get_function_count();
- bool found_complex = false;
for (std::size_t type = 0; type < types_n; type++) {
tl_type *t = config.get_type_by_num(type);
assert(t->constructors_num == t->constructors.size());
@@ -487,7 +516,6 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
if (t->name == "Type") {
assert(t->id == ID_VAR_TYPE);
t->flags |= FLAG_COMPLEX;
- found_complex = true;
}
continue;
}
@@ -525,7 +553,6 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
b.exist_var_num != -1) {
if (!w.is_built_in_complex_type(t->name)) {
t->flags |= FLAG_COMPLEX;
- found_complex = true;
}
} else {
assert(b_arg_type == NODE_TYPE_TYPE);
@@ -545,14 +572,13 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
if (main_type == NODE_TYPE_VAR_TYPE) {
if (!w.is_built_in_complex_type(t->name)) {
t->flags |= FLAG_COMPLEX;
- found_complex = true;
}
}
}
}
- while (found_complex) {
- found_complex = false;
+ while (true) {
+ bool found_complex = false;
for (std::size_t type = 0; type < types_n; type++) {
tl_type *t = config.get_type_by_num(type);
if (t->constructors_num == 0 || w.is_built_in_complex_type(t->name)) { // built-in dummy or complex types
@@ -572,6 +598,9 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
// std::fprintf(stderr, "Found complex %s\n", t->name.c_str());
}
}
+ if (!found_complex) {
+ break;
+ }
}
std::set<std::string> request_types;
@@ -619,7 +648,7 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
// write base classes
std::vector<var_description> empty_vars;
for (int i = 0; i <= w.get_max_arity(); i++) {
- out.append(w.gen_class_begin(w.gen_base_type_class_name(i), w.gen_base_tl_class_name(), true));
+ out.append(w.gen_class_begin(w.gen_base_type_class_name(i), w.gen_base_tl_class_name(), true, nullptr));
out.append(w.gen_get_id(w.gen_base_type_class_name(i), 0, true));
@@ -648,7 +677,8 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
continue;
}
- out.append(w.gen_fetch_function_begin(parsers[j], w.gen_base_type_class_name(i), i, empty_vars, -1));
+ out.append(w.gen_fetch_function_begin(parsers[j], w.gen_base_type_class_name(i), w.gen_base_type_class_name(i), i,
+ -1, empty_vars, -1));
out.append(w.gen_fetch_switch_begin());
for (std::size_t type = 0; type < types_n; type++) {
tl_type *t = config.get_type_by_num(type);
@@ -668,7 +698,7 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
}
}
out.append(w.gen_fetch_switch_end());
- out.append(w.gen_fetch_function_end(-1, empty_vars, -1));
+ out.append(w.gen_fetch_function_end(false, -1, empty_vars, -1));
}
std::vector<std::string> additional_functions = w.get_additional_functions();
@@ -712,7 +742,7 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
}
{
- out.append(w.gen_class_begin(w.gen_base_function_class_name(), w.gen_base_tl_class_name(), true));
+ out.append(w.gen_class_begin(w.gen_base_function_class_name(), w.gen_base_tl_class_name(), true, nullptr));
out.append(w.gen_get_id(w.gen_base_function_class_name(), 0, true));
@@ -722,7 +752,8 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
continue;
}
- out.append(w.gen_fetch_function_begin(parsers[j], w.gen_base_function_class_name(), 0, empty_vars, -1));
+ out.append(w.gen_fetch_function_begin(parsers[j], w.gen_base_function_class_name(),
+ w.gen_base_function_class_name(), 0, -1, empty_vars, -1));
out.append(w.gen_fetch_switch_begin());
for (std::size_t function = 0; function < functions_n; function++) {
tl_combinator *t = config.get_function_by_num(function);
@@ -732,7 +763,7 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
}
}
out.append(w.gen_fetch_switch_end());
- out.append(w.gen_fetch_function_end(-1, empty_vars, -1));
+ out.append(w.gen_fetch_function_end(false, -1, empty_vars, -1));
}
std::vector<std::string> storers = w.get_storers();
@@ -790,7 +821,7 @@ void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w) {
for (std::size_t function = 0; function < functions_n; function++) {
tl_combinator *t = config.get_function_by_num(function);
if (!w.is_combinator_supported(t)) {
- std::fprintf(stderr, "Function %s is too hard to store\n", t->name.c_str());
+ // std::fprintf(stderr, "Function %s is too hard to store\n", t->name.c_str());
continue;
}
@@ -826,7 +857,7 @@ bool write_tl_to_file(const tl_config &config, const std::string &file_name, con
tl_string_outputer out;
write_tl(config, out, w);
- auto old_file_contents = get_file_contents(file_name, "rb");
+ std::string old_file_contents = get_file_contents(file_name, "rb");
if (!w.is_documentation_generated()) {
old_file_contents = remove_documentation(old_file_contents);
}
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.h
index 3523d1c828..d8588023a1 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_generate.h
@@ -1,14 +1,14 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "tl_config.h"
-#include "tl_outputer.h"
-#include "tl_writer.h"
+#include "td/tl/tl_config.h"
+#include "td/tl/tl_outputer.h"
+#include "td/tl/tl_writer.h"
#include <string>
@@ -18,6 +18,7 @@ namespace tl {
void write_tl(const tl_config &config, tl_outputer &out, const TL_writer &w);
tl_config read_tl_config_from_file(const std::string &file_name);
+
bool write_tl_to_file(const tl_config &config, const std::string &file_name, const TL_writer &w);
} // namespace tl
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.cpp b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.cpp
index fed4ceb818..f26a49abd0 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.cpp
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.cpp
@@ -1,10 +1,10 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "tl_outputer.h"
+#include "td/tl/tl_outputer.h"
namespace td {
namespace tl {
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.h
index 47c0ab4fed..6ddd156b56 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_outputer.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple.h
index bf4801f1e2..1762087a12 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -16,24 +16,25 @@
#include <string>
#include <vector>
+#include <iostream>
+
namespace td {
namespace tl {
namespace simple {
-// TL type is
-std::string gen_cpp_name(std::string name) {
- for (std::size_t i = 0; i < name.size(); i++) {
- if ((name[i] < '0' || '9' < name[i]) && (name[i] < 'a' || 'z' < name[i]) && (name[i] < 'A' || 'Z' < name[i])) {
- name[i] = '_';
+inline std::string gen_cpp_name(std::string name) {
+ for (auto &c : name) {
+ if ((c < '0' || '9' < c) && (c < 'a' || 'z' < c) && (c < 'A' || 'Z' < c)) {
+ c = '_';
}
}
- assert(name.size() > 0);
+ assert(!name.empty());
assert(name[name.size() - 1] != '_');
return name;
}
-std::string gen_cpp_field_name(std::string name) {
- return gen_cpp_name(name) + "_";
+inline std::string gen_cpp_field_name(std::string name) {
+ return gen_cpp_name(name) + '_';
}
struct CustomType;
@@ -46,7 +47,7 @@ struct Type {
// type == Vector
const Type *vector_value_type{nullptr};
-}; // namespace simple
+};
struct Arg {
const Type *type;
@@ -63,6 +64,9 @@ struct Constructor {
struct CustomType {
std::string name;
std::vector<const Constructor *> constructors;
+
+ mutable bool is_result_{false};
+ mutable bool is_query_{false};
};
struct Function {
@@ -91,6 +95,23 @@ class Schema {
auto *from_function = config.get_function_by_num(function_num);
functions.push_back(get_function(from_function));
}
+ for (auto &function : functions_) {
+ mark_result(function->type);
+ for (auto &arg : function->args) {
+ mark_query(arg.type);
+ }
+ }
+
+ //for (auto custom_type : custom_types) {
+ //std::cerr << custom_type->name;
+ //if (custom_type->is_result_) {
+ //std::cerr << " result";
+ //}
+ //if (custom_type->is_query_) {
+ //std::cerr << " query";
+ //}
+ //std::cerr << std::endl;
+ //}
}
std::vector<const CustomType *> custom_types;
@@ -107,6 +128,34 @@ class Schema {
std::map<std::int32_t, Constructor *> constructor_by_id;
std::map<std::int32_t, Function *> function_by_id;
+ void mark_result(const Type *type) {
+ do_mark(type, true);
+ }
+
+ void mark_query(const Type *type) {
+ do_mark(type, false);
+ }
+
+ void do_mark(const Type *type, bool is_result) {
+ if (type->type == Type::Vector) {
+ return do_mark(type->vector_value_type, is_result);
+ }
+ if (type->type != Type::Custom) {
+ return;
+ }
+ auto *custom = type->custom;
+ auto &was = is_result ? custom->is_result_ : custom->is_query_;
+ if (was) {
+ return;
+ }
+ was = true;
+ for (auto constructor : custom->constructors) {
+ for (auto &arg : constructor->args) {
+ do_mark(arg.type, is_result);
+ }
+ }
+ }
+
const Type *get_type(const tl_type *from_type) {
auto &type = type_by_id[from_type->id];
if (!type) {
@@ -142,6 +191,7 @@ class Schema {
}
return type;
}
+
const CustomType *get_custom_type(const tl_type *from_type) {
auto *type = get_type(from_type);
assert(type->type == Type::Custom);
@@ -165,6 +215,7 @@ class Schema {
}
return constructor;
}
+
const Function *get_function(const tl_combinator *from) {
auto &function = function_by_id[from->id];
if (!function) {
@@ -182,6 +233,7 @@ class Schema {
}
return function;
}
+
const Type *get_type(const tl_tree *tree) {
assert(tree->get_type() == NODE_TYPE_TYPE);
auto *type_tree = static_cast<const tl_tree_type *>(tree);
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple_parser.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple_parser.h
index 9e612bf799..e5605cefe0 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple_parser.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_simple_parser.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -68,7 +68,7 @@ class tl_simple_parser {
std::int64_t fetch_long() {
check_len(sizeof(std::int64_t));
std::int64_t result;
- std::memcpy(reinterpret_cast<char *>(&result), data, sizeof(std::int64_t));
+ std::memcpy(&result, data, sizeof(std::int64_t));
data += sizeof(std::int64_t);
return result;
}
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.cpp b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.cpp
index 4a72ecdccd..2981dc04c1 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.cpp
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.cpp
@@ -1,10 +1,10 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "tl_string_outputer.h"
+#include "td/tl/tl_string_outputer.h"
namespace td {
namespace tl {
@@ -13,8 +13,19 @@ void tl_string_outputer::append(const std::string &str) {
result += str;
}
-const std::string &tl_string_outputer::get_result() const {
+std::string tl_string_outputer::get_result() const {
+#if defined(_WIN32)
+ std::string fixed_result;
+ for (std::size_t i = 0; i < result.size(); i++) {
+ if (result[i] == '\n') {
+ fixed_result += '\r';
+ }
+ fixed_result += result[i];
+ }
+ return fixed_result;
+#else
return result;
+#endif
}
} // namespace tl
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.h
index f0936340da..b78617be09 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_string_outputer.h
@@ -1,12 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "tl_outputer.h"
+#include "td/tl/tl_outputer.h"
#include <string>
@@ -19,7 +19,7 @@ class tl_string_outputer : public tl_outputer {
public:
virtual void append(const std::string &str);
- const std::string &get_result() const;
+ std::string get_result() const;
};
} // namespace tl
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.cpp b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.cpp
index f1d8b2e18a..8800246ebe 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.cpp
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.cpp
@@ -1,12 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "tl_writer.h"
+#include "td/tl/tl_writer.h"
-#include "tl_core.h"
+#include "td/tl/tl_core.h"
#include <cassert>
#include <cstdio>
@@ -16,7 +16,7 @@ namespace tl {
std::string TL_writer::int_to_string(int x) {
char buf[15];
- std::sprintf(buf, "%d", x);
+ std::snprintf(buf, sizeof(buf), "%d", x);
return buf;
}
@@ -135,6 +135,14 @@ bool TL_writer::is_documentation_generated() const {
return false;
}
+bool TL_writer::is_default_constructor_generated(const tl_combinator *t, bool can_be_parsed, bool can_be_stored) const {
+ return true;
+}
+
+bool TL_writer::is_full_constructor_generated(const tl_combinator *t, bool can_be_parsed, bool can_be_stored) const {
+ return true;
+}
+
std::string TL_writer::gen_main_class_name(const tl_type *t) const {
if (t->simple_constructors == 1) {
for (std::size_t i = 0; i < t->constructors_num; i++) {
diff --git a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.h b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.h
index 3c3847516b..dff8958aeb 100644
--- a/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.h
+++ b/protocols/Telegram/tdlib/td/tdtl/td/tl/tl_writer.h
@@ -1,12 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "tl_core.h"
+#include "td/tl/tl_core.h"
#include <cstdint>
#include <string>
@@ -56,6 +56,8 @@ class TL_writer {
virtual bool is_type_bare(const tl_type *t) const = 0;
virtual bool is_combinator_supported(const tl_combinator *constructor) const;
virtual bool is_documentation_generated() const;
+ virtual bool is_default_constructor_generated(const tl_combinator *t, bool can_be_parsed, bool can_be_stored) const;
+ virtual bool is_full_constructor_generated(const tl_combinator *t, bool can_be_parsed, bool can_be_stored) const;
virtual int get_parser_type(const tl_combinator *t, const std::string &parser_name) const;
virtual int get_storer_type(const tl_combinator *t, const std::string &storer_name) const;
@@ -86,15 +88,15 @@ class TL_writer {
virtual std::string gen_forward_class_declaration(const std::string &class_name, bool is_proxy) const = 0;
- virtual std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name,
- bool is_proxy) const = 0;
+ virtual std::string gen_class_begin(const std::string &class_name, const std::string &base_class_name, bool is_proxy,
+ const tl_tree *result) const = 0;
virtual std::string gen_class_end() const = 0;
virtual std::string gen_class_alias(const std::string &class_name, const std::string &alias_name) const = 0;
virtual std::string gen_field_definition(const std::string &class_name, const std::string &type_name,
const std::string &field_name) const = 0;
- virtual std::string gen_flags_definitions(const tl_combinator *t) const {
+ virtual std::string gen_flags_definitions(const tl_combinator *t, bool can_be_stored) const {
return "";
}
@@ -118,9 +120,10 @@ class TL_writer {
virtual std::string gen_function_result_type(const tl_tree *result) const = 0;
- virtual std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name, int arity,
+ virtual std::string gen_fetch_function_begin(const std::string &parser_name, const std::string &class_name,
+ const std::string &parent_class_name, int arity, int field_count,
std::vector<var_description> &vars, int parser_type) const = 0;
- virtual std::string gen_fetch_function_end(int field_num, const std::vector<var_description> &vars,
+ virtual std::string gen_fetch_function_end(bool has_parent, int field_count, const std::vector<var_description> &vars,
int parser_type) const = 0;
virtual std::string gen_fetch_function_result_begin(const std::string &parser_name, const std::string &class_name,
@@ -138,12 +141,12 @@ class TL_writer {
virtual std::string gen_fetch_switch_case(const tl_combinator *t, int arity) const = 0;
virtual std::string gen_fetch_switch_end() const = 0;
- virtual std::string gen_constructor_begin(int fields_num, const std::string &class_name, bool is_default) const = 0;
+ virtual std::string gen_constructor_begin(int field_count, const std::string &class_name, bool is_default) const = 0;
virtual std::string gen_constructor_parameter(int field_num, const std::string &class_name, const arg &a,
bool is_default) const = 0;
virtual std::string gen_constructor_field_init(int field_num, const std::string &class_name, const arg &a,
bool is_default) const = 0;
- virtual std::string gen_constructor_end(const tl_combinator *t, int fields_num, bool is_default) const = 0;
+ virtual std::string gen_constructor_end(const tl_combinator *t, int field_count, bool is_default) const = 0;
virtual std::string gen_additional_function(const std::string &function_name, const tl_combinator *t,
bool is_function) const;
diff --git a/protocols/Telegram/tdlib/td/tdutils/CMakeLists.txt b/protocols/Telegram/tdlib/td/tdutils/CMakeLists.txt
index 1fbc34df32..7c568fdeba 100644
--- a/protocols/Telegram/tdlib/td/tdutils/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/tdutils/CMakeLists.txt
@@ -1,4 +1,12 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
+
+option(TDUTILS_MIME_TYPE "Generate mime types conversion; requires gperf" ON)
+
+if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
+ set(CMAKE_INSTALL_LIBDIR "lib")
+endif()
if (NOT ZLIB_FOUND)
find_package(ZLIB)
@@ -16,6 +24,17 @@ if (ZLIB_FOUND)
endif()
endif()
+if (CRC32C_FOUND)
+ set(TD_HAVE_CRC32C 1)
+endif()
+
+if (TD_WITH_ABSEIL)
+ find_package(ABSL QUIET)
+ if (ABSL_FOUND)
+ set(TD_HAVE_ABSL 1)
+ endif()
+endif()
+
configure_file(td/utils/config.h.in td/utils/config.h @ONLY)
add_subdirectory(generate)
@@ -33,37 +52,55 @@ endif()
set(TDUTILS_SOURCE
td/utils/port/Clocks.cpp
- td/utils/port/Fd.cpp
td/utils/port/FileFd.cpp
td/utils/port/IPAddress.cpp
+ td/utils/port/MemoryMapping.cpp
td/utils/port/path.cpp
+ td/utils/port/platform.cpp
+ td/utils/port/PollFlags.cpp
+ td/utils/port/rlimit.cpp
td/utils/port/ServerSocketFd.cpp
td/utils/port/signals.cpp
td/utils/port/sleep.cpp
td/utils/port/SocketFd.cpp
+ td/utils/port/stacktrace.cpp
td/utils/port/Stat.cpp
+ td/utils/port/StdStreams.cpp
td/utils/port/thread_local.cpp
+ td/utils/port/UdpSocketFd.cpp
+ td/utils/port/uname.cpp
+ td/utils/port/user.cpp
td/utils/port/wstring_convert.cpp
td/utils/port/detail/Epoll.cpp
td/utils/port/detail/EventFdBsd.cpp
td/utils/port/detail/EventFdLinux.cpp
td/utils/port/detail/EventFdWindows.cpp
+ td/utils/port/detail/Iocp.cpp
td/utils/port/detail/KQueue.cpp
+ td/utils/port/detail/NativeFd.cpp
td/utils/port/detail/Poll.cpp
td/utils/port/detail/Select.cpp
td/utils/port/detail/ThreadIdGuard.cpp
+ td/utils/port/detail/ThreadPthread.cpp
td/utils/port/detail/WineventPoll.cpp
${TDMIME_AUTO}
+ td/utils/AsyncFileLog.cpp
td/utils/base64.cpp
td/utils/BigNum.cpp
td/utils/buffer.cpp
+ td/utils/BufferedUdp.cpp
+ td/utils/check.cpp
td/utils/crypto.cpp
+ td/utils/emoji.cpp
+ td/utils/ExitGuard.cpp
td/utils/FileLog.cpp
td/utils/filesystem.cpp
td/utils/find_boundary.cpp
+ td/utils/FlatHashTable.cpp
+ td/utils/FloodControlGlobal.cpp
td/utils/Gzip.cpp
td/utils/GzipByteFlow.cpp
td/utils/Hints.cpp
@@ -71,14 +108,24 @@ set(TDUTILS_SOURCE
td/utils/JsonBuilder.cpp
td/utils/logging.cpp
td/utils/misc.cpp
- td/utils/MimeType.cpp
+ td/utils/MpmcQueue.cpp
+ td/utils/OptionParser.cpp
+ td/utils/PathView.cpp
td/utils/Random.cpp
+ td/utils/SharedSlice.cpp
+ td/utils/Slice.cpp
td/utils/StackAllocator.cpp
td/utils/Status.cpp
td/utils/StringBuilder.cpp
+ td/utils/tests.cpp
td/utils/Time.cpp
td/utils/Timer.cpp
+ td/utils/TsFileLog.cpp
td/utils/tl_parsers.cpp
+ td/utils/translit.cpp
+ td/utils/TsCerr.cpp
+ td/utils/TsFileLog.cpp
+ td/utils/TsLog.cpp
td/utils/unicode.cpp
td/utils/utf8.cpp
@@ -87,57 +134,98 @@ set(TDUTILS_SOURCE
td/utils/port/CxCli.h
td/utils/port/EventFd.h
td/utils/port/EventFdBase.h
- td/utils/port/Fd.h
td/utils/port/FileFd.h
+ td/utils/port/FromApp.h
td/utils/port/IPAddress.h
+ td/utils/port/IoSlice.h
+ td/utils/port/MemoryMapping.h
+ td/utils/port/Mutex.h
td/utils/port/path.h
td/utils/port/platform.h
td/utils/port/Poll.h
td/utils/port/PollBase.h
+ td/utils/port/PollFlags.h
+ td/utils/port/rlimit.h
td/utils/port/RwMutex.h
td/utils/port/ServerSocketFd.h
td/utils/port/signals.h
td/utils/port/sleep.h
td/utils/port/SocketFd.h
+ td/utils/port/stacktrace.h
td/utils/port/Stat.h
+ td/utils/port/StdStreams.h
td/utils/port/thread.h
td/utils/port/thread_local.h
+ td/utils/port/UdpSocketFd.h
+ td/utils/port/uname.h
+ td/utils/port/user.h
td/utils/port/wstring_convert.h
td/utils/port/detail/Epoll.h
td/utils/port/detail/EventFdBsd.h
td/utils/port/detail/EventFdLinux.h
td/utils/port/detail/EventFdWindows.h
+ td/utils/port/detail/Iocp.h
td/utils/port/detail/KQueue.h
+ td/utils/port/detail/NativeFd.h
td/utils/port/detail/Poll.h
+ td/utils/port/detail/PollableFd.h
td/utils/port/detail/Select.h
+ td/utils/port/detail/skip_eintr.h
td/utils/port/detail/ThreadIdGuard.h
td/utils/port/detail/ThreadPthread.h
td/utils/port/detail/ThreadStl.h
td/utils/port/detail/WineventPoll.h
td/utils/AesCtrByteFlow.h
+ td/utils/algorithm.h
+ td/utils/as.h
+ td/utils/AsyncFileLog.h
+ td/utils/AtomicRead.h
td/utils/base64.h
td/utils/benchmark.h
td/utils/BigNum.h
+ td/utils/bits.h
td/utils/buffer.h
td/utils/BufferedFd.h
td/utils/BufferedReader.h
+ td/utils/BufferedUdp.h
td/utils/ByteFlow.h
+ td/utils/CancellationToken.h
+ td/utils/ChainScheduler.h
td/utils/ChangesProcessor.h
+ td/utils/check.h
td/utils/Closure.h
+ td/utils/CombinedLog.h
td/utils/common.h
+ td/utils/ConcurrentHashTable.h
td/utils/Container.h
+ td/utils/Context.h
td/utils/crypto.h
+ td/utils/DecTree.h
+ td/utils/Destructor.h
+ td/utils/emoji.h
td/utils/Enumerator.h
+ td/utils/EpochBasedMemoryReclamation.h
+ td/utils/ExitGuard.h
td/utils/FileLog.h
td/utils/filesystem.h
+ td/utils/fixed_vector.h
td/utils/find_boundary.h
+ td/utils/FlatHashMap.h
+ td/utils/FlatHashMapChunks.h
+ td/utils/FlatHashSet.h
+ td/utils/FlatHashTable.h
td/utils/FloodControlFast.h
+ td/utils/FloodControlGlobal.h
td/utils/FloodControlStrict.h
td/utils/format.h
td/utils/Gzip.h
td/utils/GzipByteFlow.h
+ td/utils/Hash.h
+ td/utils/HashMap.h
+ td/utils/HashSet.h
+ td/utils/HashTableUtils.h
td/utils/HazardPointers.h
td/utils/Heap.h
td/utils/Hints.h
@@ -147,8 +235,8 @@ set(TDUTILS_SOURCE
td/utils/JsonBuilder.h
td/utils/List.h
td/utils/logging.h
+ td/utils/MapNode.h
td/utils/MemoryLog.h
- td/utils/MimeType.h
td/utils/misc.h
td/utils/MovableValue.h
td/utils/MpmcQueue.h
@@ -156,68 +244,110 @@ set(TDUTILS_SOURCE
td/utils/MpscPollableQueue.h
td/utils/MpscLinkQueue.h
td/utils/Named.h
+ td/utils/NullLog.h
td/utils/ObjectPool.h
td/utils/Observer.h
td/utils/optional.h
- td/utils/OptionsParser.h
+ td/utils/OptionParser.h
td/utils/OrderedEventsProcessor.h
td/utils/overloaded.h
td/utils/Parser.h
td/utils/PathView.h
+ td/utils/Promise.h
td/utils/queue.h
td/utils/Random.h
td/utils/ScopeGuard.h
+ td/utils/SetNode.h
td/utils/SharedObjectPool.h
+ td/utils/SharedSlice.h
td/utils/Slice-decl.h
td/utils/Slice.h
+ td/utils/SliceBuilder.h
+ td/utils/Span.h
td/utils/SpinLock.h
td/utils/StackAllocator.h
td/utils/Status.h
+ td/utils/StealingQueue.h
td/utils/Storer.h
td/utils/StorerBase.h
td/utils/StringBuilder.h
td/utils/tests.h
+ td/utils/ThreadLocalStorage.h
+ td/utils/ThreadSafeCounter.h
td/utils/Time.h
td/utils/TimedStat.h
td/utils/Timer.h
td/utils/tl_helpers.h
td/utils/tl_parsers.h
td/utils/tl_storers.h
+ td/utils/TlDowncastHelper.h
+ td/utils/TlStorerToString.h
+ td/utils/translit.h
+ td/utils/TsCerr.h
+ td/utils/TsFileLog.h
+ td/utils/TsList.h
+ td/utils/TsLog.h
td/utils/type_traits.h
+ td/utils/UInt.h
+ td/utils/uint128.h
td/utils/unicode.h
+ td/utils/unique_ptr.h
td/utils/utf8.h
td/utils/Variant.h
+ td/utils/VectorQueue.h
+ td/utils/WaitFreeHashMap.h
+ td/utils/WaitFreeHashSet.h
+ td/utils/WaitFreeVector.h
)
+if (TDUTILS_MIME_TYPE)
+ set(TDUTILS_SOURCE
+ ${TDUTILS_SOURCE}
+ td/utils/MimeType.cpp
+ td/utils/MimeType.h
+ )
+endif()
+
set(TDUTILS_TEST_SOURCE
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/bitmask.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/buffer.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/ChainScheduler.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/ConcurrentHashMap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/crypto.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/emoji.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/Enumerator.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/EpochBasedMemoryReclamation.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/filesystem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/gzip.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/HazardPointers.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/HashSet.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/heap.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/HttpUrl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/json.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/List.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/log.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/misc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/MpmcQueue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/MpmcWaiter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/MpscLinkQueue.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/OptionParser.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/OrderedEventsProcessor.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/port.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/pq.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/SharedObjectPool.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/SharedSlice.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/StealingQueue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/variant.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/WaitFreeHashMap.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/WaitFreeHashSet.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test/WaitFreeVector.cpp
PARENT_SCOPE
)
-#RULES
#LIBRARIES
add_library(tdutils STATIC ${TDUTILS_SOURCE})
-if (WIN32)
- # find_library(WS2_32_LIBRARY ws2_32)
- # find_library(MSWSOCK_LIBRARY Mswsock)
- # target_link_libraries(tdutils PRIVATE ${WS2_32_LIBRARY} ${MSWSOCK_LIBRARY})
- target_link_libraries(tdutils PRIVATE ws2_32 Mswsock)
-endif()
-if (NOT CMAKE_CROSSCOMPILING)
+
+if (NOT CMAKE_CROSSCOMPILING AND TDUTILS_MIME_TYPE)
add_dependencies(tdutils tdmime_auto)
endif()
@@ -229,6 +359,14 @@ target_include_directories(tdutils PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOUR
if (OPENSSL_FOUND)
target_link_libraries(tdutils PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES})
target_include_directories(tdutils SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
+
+ if (WIN32)
+ if (MINGW)
+ target_link_libraries(tdutils PRIVATE ws2_32 mswsock crypt32)
+ else()
+ target_link_libraries(tdutils PRIVATE ws2_32 Mswsock Crypt32)
+ endif()
+ endif()
endif()
if (ZLIB_FOUND)
@@ -236,9 +374,49 @@ if (ZLIB_FOUND)
target_include_directories(tdutils SYSTEM PRIVATE ${ZLIB_INCLUDE_DIR})
endif()
+if (CRC32C_FOUND)
+ target_link_libraries(tdutils PRIVATE crc32c)
+endif()
+if (ABSL_FOUND)
+ target_link_libraries(tdutils PUBLIC absl::flat_hash_map absl::flat_hash_set absl::hash)
+endif()
+
+if (WIN32)
+ # find_library for system libraries doesn't work for UWP builds
+ # find_library(WS2_32_LIBRARY ws2_32)
+ # find_library(MSWSOCK_LIBRARY Mswsock)
+ # target_link_libraries(tdutils PRIVATE ${WS2_32_LIBRARY} ${MSWSOCK_LIBRARY})
+ if (MINGW)
+ target_link_libraries(tdutils PRIVATE ws2_32 mswsock normaliz psapi)
+ else()
+ target_link_libraries(tdutils PRIVATE ws2_32 Mswsock Normaliz psapi)
+ endif()
+ if (NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
+ target_link_libraries(tdutils PRIVATE shell32)
+ endif()
+endif()
+
+if (ANDROID)
+ target_link_libraries(tdutils PRIVATE log)
+endif()
+
+if (CMAKE_SYSTEM_NAME MATCHES "NetBSD")
+ target_link_directories(tdutils PUBLIC /usr/pkg/gcc12/x86_64--netbsd/lib /usr/pkg/gcc12/i486--netbsdelf/lib)
+ target_link_libraries(tdutils PUBLIC atomic)
+endif()
+
install(TARGETS tdutils EXPORT TdTargets
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- RUNTIME DESTINATION bin
- INCLUDES DESTINATION include
+ LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
+
+if (TD_TEST_FOLLY AND ABSL_FOUND)
+ find_package(benchmark QUIET)
+ find_package(folly QUIET)
+ find_package(gflags QUIET)
+
+ if (benchmark_FOUND AND folly_FOUND)
+ add_executable(benchmark-hashset test/hashset_benchmark.cpp)
+ target_link_libraries(benchmark-hashset PRIVATE tdutils benchmark::benchmark Folly::folly absl::flat_hash_map absl::hash)
+ endif()
+endif()
diff --git a/protocols/Telegram/tdlib/td/tdutils/generate/CMakeLists.txt b/protocols/Telegram/tdlib/td/tdutils/generate/CMakeLists.txt
index 69697b04b8..69b42d9144 100644
--- a/protocols/Telegram/tdlib/td/tdutils/generate/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/tdutils/generate/CMakeLists.txt
@@ -1,7 +1,13 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
# Generates files for MIME type <-> extension conversions
-# DEPENDS ON: gperf grep bash/powershell
+# DEPENDS ON: gperf grep
+
+if (NOT TDUTILS_MIME_TYPE)
+ return()
+endif()
file(MAKE_DIRECTORY auto)
@@ -19,7 +25,7 @@ add_custom_target(tdmime_auto DEPENDS ${TDMIME_SOURCE})
if (NOT CMAKE_CROSSCOMPILING)
find_program(GPERF_EXECUTABLE gperf)
if (NOT GPERF_EXECUTABLE)
- message(FATAL_ERROR "Could NOT find gperf. Path to gperf needs to be specified manually, i.e. 'cmake -DGPERF_EXECUTABLE:FILEPATH=\"<path to gperf executable>\" .')")
+ message(FATAL_ERROR "Could NOT find gperf. Add path to gperf executable to PATH environment variable or specify it manually using GPERF_EXECUTABLE option, i.e. 'cmake -DGPERF_EXECUTABLE:FILEPATH=\"<path to gperf executable>\"'.")
endif()
set(GPERF_FILES
@@ -38,7 +44,7 @@ if (NOT CMAKE_CROSSCOMPILING)
DEPENDS generate_mime_types_gperf mime_types.txt
)
- if (WIN32)
+ if (CMAKE_HOST_WIN32)
set(MIME_TYPE_TO_EXTENSION_CMD ${GPERF_EXECUTABLE} -m100 --output-file=auto/mime_type_to_extension.cpp auto/mime_type_to_extension.gperf)
else()
set(MIME_TYPE_TO_EXTENSION_CMD ${GPERF_EXECUTABLE} -m100 auto/mime_type_to_extension.gperf | grep -v __gnu_inline__ > auto/mime_type_to_extension.cpp)
@@ -47,10 +53,10 @@ if (NOT CMAKE_CROSSCOMPILING)
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/auto/mime_type_to_extension.cpp
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${MIME_TYPE_TO_EXTENSION_CMD}
- DEPENDS auto/mime_type_to_extension.gperf
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/auto/mime_type_to_extension.gperf
)
- if (WIN32)
+ if (CMAKE_HOST_WIN32)
set(EXTENSION_TO_MIME_TYPE_CMD ${GPERF_EXECUTABLE} -m100 --output-file=auto/extension_to_mime_type.cpp auto/extension_to_mime_type.gperf)
else()
set(EXTENSION_TO_MIME_TYPE_CMD ${GPERF_EXECUTABLE} -m100 auto/extension_to_mime_type.gperf | grep -v __gnu_inline__ > auto/extension_to_mime_type.cpp)
@@ -59,6 +65,6 @@ if (NOT CMAKE_CROSSCOMPILING)
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/auto/extension_to_mime_type.cpp
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${EXTENSION_TO_MIME_TYPE_CMD}
- DEPENDS auto/extension_to_mime_type.gperf
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/auto/extension_to_mime_type.gperf
)
endif()
diff --git a/protocols/Telegram/tdlib/td/tdutils/generate/generate_mime_types_gperf.cpp b/protocols/Telegram/tdlib/td/tdutils/generate/generate_mime_types_gperf.cpp
index ac7ff68605..44ab2bd1e9 100644
--- a/protocols/Telegram/tdlib/td/tdutils/generate/generate_mime_types_gperf.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/generate/generate_mime_types_gperf.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,7 +15,7 @@
#include <utility>
#include <vector>
-std::pair<std::string, std::string> split(std::string s, char delimiter = ' ') {
+static std::pair<std::string, std::string> split(std::string s, char delimiter = ' ') {
auto delimiter_pos = s.find(delimiter);
if (delimiter_pos == std::string::npos) {
return {std::move(s), ""};
@@ -26,8 +26,8 @@ std::pair<std::string, std::string> split(std::string s, char delimiter = ' ') {
}
}
-bool generate(const char *file_name, const char *from_name, const char *to_name,
- const std::map<std::string, std::string> &map) {
+static bool generate(const char *file_name, const char *from_name, const char *to_name,
+ const std::map<std::string, std::string> &map) {
// binary mode is needed for MSYS2 gperf
std::ofstream out(file_name, std::ios_base::trunc | std::ios_base::binary);
if (!out) {
@@ -71,6 +71,10 @@ bool generate(const char *file_name, const char *from_name, const char *to_name,
return true;
}
+static bool is_private_mime_type(const std::string &mime_type) {
+ return mime_type.find("/x-") != std::string::npos;
+}
+
int main(int argc, char *argv[]) {
if (argc != 4) {
std::cerr << "Wrong number of arguments supplied. Expected 'generate_mime_types_gperf <mime_types.txt> "
@@ -112,7 +116,7 @@ int main(int argc, char *argv[]) {
std::vector<std::string> extensions;
while (!extensions_string.empty()) {
- extensions.push_back("");
+ extensions.emplace_back();
std::tie(extensions.back(), extensions_string) = split(extensions_string);
}
assert(!extensions.empty());
@@ -132,7 +136,13 @@ int main(int argc, char *argv[]) {
for (auto &extension : extensions) {
if (!extension_to_mime_type.emplace(extension, mime_type).second) {
- std::cerr << "Extension \"" << extension << "\" matches more than one type" << std::endl;
+ if (is_private_mime_type(extension_to_mime_type[extension]) == is_private_mime_type(mime_type)) {
+ std::cerr << "Extension \"" << extension << "\" matches more than one type" << std::endl;
+ } else {
+ if (!is_private_mime_type(mime_type)) {
+ extension_to_mime_type[extension] = mime_type;
+ }
+ }
}
}
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/generate/mime_types.txt b/protocols/Telegram/tdlib/td/tdutils/generate/mime_types.txt
index a2d4abbf29..18dcfb775b 100644
--- a/protocols/Telegram/tdlib/td/tdutils/generate/mime_types.txt
+++ b/protocols/Telegram/tdlib/td/tdutils/generate/mime_types.txt
@@ -14,7 +14,7 @@ application/davmount+xml davmount
application/docbook+xml dbk
application/dssc+der dssc
application/dssc+xml xdssc
-application/ecmascript ecma
+application/ecmascript es
application/emma+xml emma
application/epub+zip epub
application/exi exi
@@ -498,6 +498,7 @@ application/x-dtbresource+xml res
application/x-dvi dvi
application/x-envoy evy
application/x-eva eva
+application/x-fictionbook+xml fb2
application/x-font-bdf bdf
application/x-font-ghostscript gsf
application/x-font-linux-psf psf
@@ -506,7 +507,7 @@ application/x-font-pcf pcf
application/x-font-snf snf
application/x-font-ttf ttf ttc
application/x-font-type1 pfa pfb pfm afm
-application/font-woff woff
+application/x-font-woff woff
application/x-freearc arc
application/x-futuresplash spl
application/x-gca-compressed gca
@@ -564,6 +565,8 @@ application/x-tex tex
application/x-tex-tfm tfm
application/x-texinfo texinfo texi
application/x-tgif obj
+application/x-tgsticker tgs
+application/x-tgwallpattern tgv
application/x-ustar ustar
application/x-wais-source src
application/x-x509-ca-cert der crt
@@ -589,9 +592,9 @@ application/zip zip
audio/adpcm adp
audio/basic au snd
audio/midi mid midi kar rmi
-audio/mp4 mp4a
+audio/mp4 m4a mp4a
audio/mpeg mpga mp2 mp2a mp3 m2a m3a
-audio/ogg oga ogg spx
+audio/ogg oga ogg opus spx
audio/s3m s3m
audio/silk sil
audio/vnd.dece.audio uva uvva
@@ -624,10 +627,19 @@ chemical/x-cmdf cmdf
chemical/x-cml cml
chemical/x-csml csml
chemical/x-xyz xyz
+font/collection ttc
+font/otf otf
+font/ttf ttf
+font/woff woff
+font/woff2 woff2
image/bmp bmp
image/cgm cgm
image/g3fax g3
image/gif gif
+image/heic heic
+image/heic-sequence heics
+image/heif heif
+image/heif-sequence heifs
image/ief ief
image/jpeg jpeg jpg jpe
image/ktx ktx
@@ -638,8 +650,8 @@ image/svg+xml svg svgz
image/tiff tiff tif
image/vnd.adobe.photoshop psd
image/vnd.dece.graphic uvi uvvi uvg uvvg
-image/vnd.dvb.subtitle sub
image/vnd.djvu djvu djv
+image/vnd.dvb.subtitle sub
image/vnd.dwg dwg
image/vnd.dxf dxf
image/vnd.fastbidsheet fbs
@@ -700,8 +712,8 @@ text/uri-list uri uris urls
text/vcard vcard
text/vnd.curl curl
text/vnd.curl.dcurl dcurl
-text/vnd.curl.scurl scurl
text/vnd.curl.mcurl mcurl
+text/vnd.curl.scurl scurl
text/vnd.dvb.subtitle sub
text/vnd.fly fly
text/vnd.fmi.flexstor flx
@@ -715,9 +727,10 @@ text/x-asm s asm
text/x-c c cc cxx cpp h hh dic
text/x-fortran f for f77 f90
text/x-java-source java
+text/x-nfo nfo
text/x-opml opml
text/x-pascal p pas
-text/x-nfo nfo
+text/x-php php
text/x-setext etx
text/x-sfv sfv
text/x-uuencode uu
@@ -728,6 +741,7 @@ video/3gpp2 3g2
video/h261 h261
video/h263 h263
video/h264 h264
+video/h265 h265
video/jpeg jpgv
video/jpm jpm jpgm
video/mj2 mj2 mjp2
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/AesCtrByteFlow.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/AesCtrByteFlow.h
index 820aa02fbe..5d9057f215 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/AesCtrByteFlow.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/AesCtrByteFlow.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,14 +11,15 @@
#include "td/utils/crypto.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include "td/utils/UInt.h"
namespace td {
#if TD_HAVE_OPENSSL
-class AesCtrByteFlow : public ByteFlowInplaceBase {
+class AesCtrByteFlow final : public ByteFlowInplaceBase {
public:
void init(const UInt256 &key, const UInt128 &iv) {
- state_.init(key, iv);
+ state_.init(as_slice(key), as_slice(iv));
}
void init(AesCtrState &&state) {
state_ = std::move(state);
@@ -26,25 +27,20 @@ class AesCtrByteFlow : public ByteFlowInplaceBase {
AesCtrState move_aes_ctr_state() {
return std::move(state_);
}
- void loop() override {
- bool was_updated = false;
- while (true) {
- auto ready = input_->prepare_read();
- if (ready.empty()) {
- break;
- }
+ bool loop() final {
+ bool result = false;
+ auto ready = input_->prepare_read();
+ if (!ready.empty()) {
state_.encrypt(ready, MutableSlice(const_cast<char *>(ready.data()), ready.size()));
input_->confirm_read(ready.size());
output_.advance_end(ready.size());
- was_updated = true;
- }
- if (was_updated) {
- on_output_updated();
+ result = true;
}
+
if (!is_input_active_) {
finish(Status::OK()); // End of input stream.
}
- set_need_size(1);
+ return result;
}
private:
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.cpp
new file mode 100644
index 0000000000..575333745d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.cpp
@@ -0,0 +1,160 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/AsyncFileLog.h"
+
+#include "td/utils/port/FileFd.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/sleep.h"
+#include "td/utils/port/StdStreams.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Time.h"
+
+namespace td {
+
+#if !TD_THREAD_UNSUPPORTED
+
+Status AsyncFileLog::init(string path, int64 rotate_threshold, bool redirect_stderr) {
+ CHECK(path_.empty());
+ CHECK(!path.empty());
+
+ TRY_RESULT(fd, FileFd::open(path, FileFd::Create | FileFd::Write | FileFd::Append));
+ if (!Stderr().empty() && redirect_stderr) {
+ fd.get_native_fd().duplicate(Stderr().get_native_fd()).ignore();
+ }
+
+ auto r_path = realpath(path, true);
+ if (r_path.is_error()) {
+ path_ = std::move(path);
+ } else {
+ path_ = r_path.move_as_ok();
+ }
+ TRY_RESULT(size, fd.get_size());
+
+ queue_ = td::make_unique<MpscPollableQueue<Query>>();
+ queue_->init();
+
+ logging_thread_ = td::thread(
+ [queue = queue_.get(), fd = std::move(fd), path = path_, size, rotate_threshold, redirect_stderr]() mutable {
+ auto after_rotation = [&] {
+ fd.close();
+ auto r_fd = FileFd::open(path, FileFd::Create | FileFd::Write | FileFd::Append);
+ if (r_fd.is_error()) {
+ process_fatal_error(PSLICE() << r_fd.error() << " in " << __FILE__ << " at " << __LINE__ << '\n');
+ }
+ fd = r_fd.move_as_ok();
+ if (!Stderr().empty() && redirect_stderr) {
+ fd.get_native_fd().duplicate(Stderr().get_native_fd()).ignore();
+ }
+ size = 0;
+ };
+ auto append = [&](CSlice slice) {
+ if (size > rotate_threshold) {
+ auto status = rename(path, PSLICE() << path << ".old");
+ if (status.is_error()) {
+ process_fatal_error(PSLICE() << status << " in " << __FILE__ << " at " << __LINE__ << '\n');
+ }
+ after_rotation();
+ }
+ while (!slice.empty()) {
+ if (redirect_stderr) {
+ while (has_log_guard()) {
+ // spin
+ }
+ }
+ auto r_size = fd.write(slice);
+ if (r_size.is_error()) {
+ process_fatal_error(PSLICE() << r_size.error() << " in " << __FILE__ << " at " << __LINE__ << '\n');
+ }
+ auto written = r_size.ok();
+ size += static_cast<int64>(written);
+ slice.remove_prefix(written);
+ }
+ };
+
+ while (true) {
+ int ready_count = queue->reader_wait_nonblock();
+ if (ready_count == 0) {
+ queue->reader_get_event_fd().wait(1000);
+ continue;
+ }
+ bool need_close = false;
+ while (ready_count-- > 0) {
+ Query query = queue->reader_get_unsafe();
+ switch (query.type_) {
+ case Query::Type::Log:
+ append(query.data_);
+ break;
+ case Query::Type::AfterRotation:
+ after_rotation();
+ break;
+ case Query::Type::Close:
+ need_close = true;
+ break;
+ default:
+ process_fatal_error("Invalid query type in AsyncFileLog");
+ }
+ }
+ queue->reader_flush();
+
+ if (need_close) {
+ fd.close();
+ break;
+ }
+ }
+ });
+
+ return Status::OK();
+}
+
+AsyncFileLog::~AsyncFileLog() {
+ if (queue_ == nullptr) {
+ return;
+ }
+ Query query;
+ query.type_ = Query::Type::Close;
+ queue_->writer_put(std::move(query));
+ logging_thread_.join();
+}
+
+vector<string> AsyncFileLog::get_file_paths() {
+ vector<string> result;
+ if (!path_.empty()) {
+ result.push_back(path_);
+ result.push_back(PSTRING() << path_ << ".old");
+ }
+ return result;
+}
+
+void AsyncFileLog::after_rotation() {
+ Query query;
+ query.type_ = Query::Type::AfterRotation;
+ if (queue_ == nullptr) {
+ process_fatal_error("AsyncFileLog is not inited");
+ }
+ queue_->writer_put(std::move(query));
+}
+
+void AsyncFileLog::do_append(int log_level, CSlice slice) {
+ Query query;
+ query.data_ = slice.str();
+ if (queue_ == nullptr) {
+ process_fatal_error("AsyncFileLog is not inited");
+ }
+ queue_->writer_put(std::move(query));
+ if (log_level == VERBOSITY_NAME(FATAL)) {
+ // it is not thread-safe to join logging_thread_ there, so just wait for the log line to be printed
+ auto end_time = Time::now() + 1.0;
+ while (!queue_->is_empty() && Time::now() < end_time) {
+ usleep_for(1000);
+ }
+ usleep_for(5000); // allow some time for the log line to be actually printed
+ }
+}
+
+#endif
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.h
new file mode 100644
index 0000000000..1b0ab73897
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.h
@@ -0,0 +1,51 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/MpscPollableQueue.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+#if !TD_THREAD_UNSUPPORTED
+
+class AsyncFileLog final : public LogInterface {
+ public:
+ AsyncFileLog() = default;
+ AsyncFileLog(const AsyncFileLog &) = delete;
+ AsyncFileLog &operator=(const AsyncFileLog &) = delete;
+ AsyncFileLog(AsyncFileLog &&) = delete;
+ AsyncFileLog &operator=(AsyncFileLog &&) = delete;
+ ~AsyncFileLog();
+
+ Status init(string path, int64 rotate_threshold, bool redirect_stderr = true);
+
+ private:
+ struct Query {
+ enum class Type : int32 { Log, AfterRotation, Close };
+ Type type_ = Type::Log;
+ string data_;
+ };
+
+ string path_;
+ unique_ptr<MpscPollableQueue<Query>> queue_;
+ thread logging_thread_;
+
+ vector<string> get_file_paths() final;
+
+ void after_rotation() final;
+
+ void do_append(int log_level, CSlice slice) final;
+};
+
+#endif
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/AtomicRead.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/AtomicRead.h
new file mode 100644
index 0000000000..d30e960a8c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/AtomicRead.h
@@ -0,0 +1,89 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/port/sleep.h"
+#include "td/utils/type_traits.h"
+
+#include <atomic>
+#include <cstring>
+#include <memory>
+
+namespace td {
+
+template <class T>
+class AtomicRead {
+ public:
+ void read(T &dest) const {
+ uint32 counter = 0;
+ auto wait = [&] {
+ counter++;
+ const int wait_each_count = 4;
+ if (counter % wait_each_count == 0) {
+ usleep_for(1);
+ }
+ };
+
+ while (true) {
+ static_assert(TD_IS_TRIVIALLY_COPYABLE(T), "T must be trivially copyable");
+ auto version_before = version.load();
+ if (version_before % 2 == 0) {
+ std::memcpy(&dest, &value, sizeof(dest));
+ auto version_after = version.load();
+ if (version_before == version_after) {
+ break;
+ }
+ }
+ wait();
+ }
+ }
+
+ struct Write {
+ explicit Write(AtomicRead *read) {
+ read->do_lock();
+ ptr.reset(read);
+ }
+ struct Destructor {
+ void operator()(AtomicRead *read) const {
+ read->do_unlock();
+ }
+ };
+ T &operator*() {
+ return value();
+ }
+ T *operator->() {
+ return &value();
+ }
+ T &value() {
+ CHECK(ptr);
+ return ptr->value;
+ }
+
+ private:
+ std::unique_ptr<AtomicRead, Destructor> ptr;
+ };
+
+ Write lock() {
+ return Write(this);
+ }
+
+ private:
+ std::atomic<uint64> version{0};
+ T value;
+
+ void do_lock() {
+ bool is_locked = ++version % 2 == 1;
+ CHECK(is_locked);
+ }
+ void do_unlock() {
+ bool is_unlocked = ++version % 2 == 0;
+ CHECK(is_unlocked);
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.cpp
index f553661d49..e7a93d4399 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -12,10 +12,13 @@ char disable_linker_warning_about_empty_file_bignum_cpp TD_UNUSED;
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
#include <openssl/bn.h>
#include <openssl/crypto.h>
+#include <algorithm>
+
namespace td {
class BigNumContext::Impl {
@@ -37,9 +40,8 @@ class BigNumContext::Impl {
BigNumContext::BigNumContext() : impl_(make_unique<Impl>()) {
}
-BigNumContext::BigNumContext(BigNumContext &&other) = default;
-BigNumContext &BigNumContext::operator=(BigNumContext &&other) = default;
-
+BigNumContext::BigNumContext(BigNumContext &&other) noexcept = default;
+BigNumContext &BigNumContext::operator=(BigNumContext &&other) noexcept = default;
BigNumContext::~BigNumContext() = default;
class BigNum::Impl {
@@ -68,6 +70,9 @@ BigNum::BigNum(const BigNum &other) : BigNum() {
}
BigNum &BigNum::operator=(const BigNum &other) {
+ if (this == &other) {
+ return *this;
+ }
CHECK(impl_ != nullptr);
CHECK(other.impl_ != nullptr);
BIGNUM *result = BN_copy(impl_->big_num, other.impl_->big_num);
@@ -75,20 +80,41 @@ BigNum &BigNum::operator=(const BigNum &other) {
return *this;
}
-BigNum::BigNum(BigNum &&other) = default;
-
-BigNum &BigNum::operator=(BigNum &&other) = default;
-
+BigNum::BigNum(BigNum &&other) noexcept = default;
+BigNum &BigNum::operator=(BigNum &&other) noexcept = default;
BigNum::~BigNum() = default;
BigNum BigNum::from_binary(Slice str) {
return BigNum(make_unique<Impl>(BN_bin2bn(str.ubegin(), narrow_cast<int>(str.size()), nullptr)));
}
-BigNum BigNum::from_decimal(CSlice str) {
+BigNum BigNum::from_le_binary(Slice str) {
+#if defined(OPENSSL_IS_BORINGSSL)
+ return BigNum(make_unique<Impl>(BN_le2bn(str.ubegin(), narrow_cast<int>(str.size()), nullptr)));
+#elif OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+ return BigNum(make_unique<Impl>(BN_lebin2bn(str.ubegin(), narrow_cast<int>(str.size()), nullptr)));
+#else
+ string str_copy = str.str();
+ std::reverse(str_copy.begin(), str_copy.end());
+ return from_binary(str_copy);
+#endif
+}
+
+Result<BigNum> BigNum::from_decimal(CSlice str) {
BigNum result;
- int err = BN_dec2bn(&result.impl_->big_num, str.c_str());
- LOG_IF(FATAL, err == 0);
+ int res = BN_dec2bn(&result.impl_->big_num, str.c_str());
+ if (res == 0 || static_cast<size_t>(res) != str.size()) {
+ return Status::Error(PSLICE() << "Failed to parse \"" << str << "\" as BigNum");
+ }
+ return result;
+}
+
+Result<BigNum> BigNum::from_hex(CSlice str) {
+ BigNum result;
+ int res = BN_hex2bn(&result.impl_->big_num, str.c_str());
+ if (res == 0 || static_cast<size_t>(res) != str.size()) {
+ return Status::Error(PSLICE() << "Failed to parse \"" << str << "\" as hexadecimal BigNum");
+ }
return result;
}
@@ -99,10 +125,6 @@ BigNum BigNum::from_raw(void *openssl_big_num) {
BigNum::BigNum(unique_ptr<Impl> &&impl) : impl_(std::move(impl)) {
}
-void BigNum::ensure_const_time() {
- BN_set_flags(impl_->big_num, BN_FLG_CONSTTIME);
-}
-
int BigNum::get_num_bits() const {
return BN_num_bits(impl_->big_num);
}
@@ -126,7 +148,12 @@ bool BigNum::is_bit_set(int num) const {
}
bool BigNum::is_prime(BigNumContext &context) const {
- int result = BN_is_prime_ex(impl_->big_num, BN_prime_checks, context.impl_->big_num_context, nullptr);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ int result = BN_check_prime(impl_->big_num, context.impl_->big_num_context, nullptr);
+#else
+ int result =
+ BN_is_prime_ex(impl_->big_num, get_num_bits() > 2048 ? 128 : 64, context.impl_->big_num_context, nullptr);
+#endif
LOG_IF(FATAL, result == -1);
return result == 1;
}
@@ -180,10 +207,32 @@ string BigNum::to_binary(int exact_size) const {
CHECK(exact_size >= num_size);
}
string res(exact_size, '\0');
- BN_bn2bin(impl_->big_num, reinterpret_cast<unsigned char *>(&res[exact_size - num_size]));
+ BN_bn2bin(impl_->big_num, MutableSlice(res).ubegin() + (exact_size - num_size));
return res;
}
+string BigNum::to_le_binary(int exact_size) const {
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) || defined(OPENSSL_IS_BORINGSSL)
+ int num_size = get_num_bytes();
+ if (exact_size == -1) {
+ exact_size = num_size;
+ } else {
+ CHECK(exact_size >= num_size);
+ }
+ string result(exact_size, '\0');
+#if defined(OPENSSL_IS_BORINGSSL)
+ BN_bn2le_padded(MutableSlice(result).ubegin(), exact_size, impl_->big_num);
+#else
+ BN_bn2lebinpad(impl_->big_num, MutableSlice(result).ubegin(), exact_size);
+#endif
+ return result;
+#else
+ string result = to_binary(exact_size);
+ std::reverse(result.begin(), result.end());
+ return result;
+#endif
+}
+
string BigNum::to_decimal() const {
char *result = BN_bn2dec(impl_->big_num);
CHECK(result != nullptr);
@@ -214,12 +263,29 @@ void BigNum::mul(BigNum &r, BigNum &a, BigNum &b, BigNumContext &context) {
LOG_IF(FATAL, result != 1);
}
+void BigNum::mod_add(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context) {
+ int result = BN_mod_add(r.impl_->big_num, a.impl_->big_num, b.impl_->big_num, m.impl_->big_num,
+ context.impl_->big_num_context);
+ LOG_IF(FATAL, result != 1);
+}
+
+void BigNum::mod_sub(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context) {
+ int result = BN_mod_sub(r.impl_->big_num, a.impl_->big_num, b.impl_->big_num, m.impl_->big_num,
+ context.impl_->big_num_context);
+ LOG_IF(FATAL, result != 1);
+}
+
void BigNum::mod_mul(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context) {
int result = BN_mod_mul(r.impl_->big_num, a.impl_->big_num, b.impl_->big_num, m.impl_->big_num,
context.impl_->big_num_context);
LOG_IF(FATAL, result != 1);
}
+void BigNum::mod_inverse(BigNum &r, BigNum &a, const BigNum &m, BigNumContext &context) {
+ auto result = BN_mod_inverse(r.impl_->big_num, a.impl_->big_num, m.impl_->big_num, context.impl_->big_num_context);
+ LOG_IF(FATAL, result != r.impl_->big_num);
+}
+
void BigNum::div(BigNum *quotient, BigNum *remainder, const BigNum &dividend, const BigNum &divisor,
BigNumContext &context) {
auto q = quotient == nullptr ? nullptr : quotient->impl_->big_num;
@@ -247,5 +313,9 @@ int BigNum::compare(const BigNum &a, const BigNum &b) {
return BN_cmp(a.impl_->big_num, b.impl_->big_num);
}
+StringBuilder &operator<<(StringBuilder &sb, const BigNum &bn) {
+ return sb << bn.to_decimal();
+}
+
} // namespace td
#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.h
index 6eecdeab03..9b666f4ce0 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,6 +11,8 @@
#if TD_HAVE_OPENSSL
#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
namespace td {
@@ -19,8 +21,8 @@ class BigNumContext {
BigNumContext();
BigNumContext(const BigNumContext &other) = delete;
BigNumContext &operator=(const BigNumContext &other) = delete;
- BigNumContext(BigNumContext &&other);
- BigNumContext &operator=(BigNumContext &&other);
+ BigNumContext(BigNumContext &&other) noexcept;
+ BigNumContext &operator=(BigNumContext &&other) noexcept;
~BigNumContext();
private:
@@ -35,20 +37,22 @@ class BigNum {
BigNum();
BigNum(const BigNum &other);
BigNum &operator=(const BigNum &other);
- BigNum(BigNum &&other);
- BigNum &operator=(BigNum &&other);
+ BigNum(BigNum &&other) noexcept;
+ BigNum &operator=(BigNum &&other) noexcept;
~BigNum();
static BigNum from_binary(Slice str);
- static BigNum from_decimal(CSlice str);
+ static BigNum from_le_binary(Slice str);
+
+ static Result<BigNum> from_decimal(CSlice str);
+
+ static Result<BigNum> from_hex(CSlice str);
static BigNum from_raw(void *openssl_big_num);
void set_value(uint32 new_value);
- void ensure_const_time();
-
int get_num_bits() const;
int get_num_bytes() const;
@@ -65,6 +69,8 @@ class BigNum {
string to_binary(int exact_size = -1) const;
+ string to_le_binary(int exact_size = -1) const;
+
string to_decimal() const;
void operator+=(uint32 value);
@@ -85,8 +91,14 @@ class BigNum {
static void mul(BigNum &r, BigNum &a, BigNum &b, BigNumContext &context);
+ static void mod_add(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context);
+
+ static void mod_sub(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context);
+
static void mod_mul(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context);
+ static void mod_inverse(BigNum &r, BigNum &a, const BigNum &m, BigNumContext &context);
+
static void div(BigNum *quotient, BigNum *remainder, const BigNum &dividend, const BigNum &divisor,
BigNumContext &context);
@@ -103,6 +115,8 @@ class BigNum {
explicit BigNum(unique_ptr<Impl> &&impl);
};
+StringBuilder &operator<<(StringBuilder &sb, const BigNum &bn);
+
} // namespace td
#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedFd.h
index 0c8f65408d..6bb9b77098 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedFd.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedFd.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,10 +7,13 @@
#pragma once
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/IoSlice.h"
#include "td/utils/Slice.h"
+#include "td/utils/Span.h"
#include "td/utils/Status.h"
#include <limits>
@@ -28,15 +31,16 @@ class BufferedFdBase : public FdT {
Result<size_t> flush_write() TD_WARN_UNUSED_RESULT;
bool need_flush_write(size_t at_least = 0) {
- CHECK(write_);
- write_->sync_with_writer();
- return write_->size() > at_least;
+ return ready_for_flush_write() > at_least;
}
size_t ready_for_flush_write() {
CHECK(write_);
write_->sync_with_writer();
return write_->size();
}
+ void sync_with_poll() {
+ ::td::sync_with_poll(*this);
+ }
void set_input_writer(ChainBufferWriter *read) {
read_ = read;
}
@@ -50,7 +54,7 @@ class BufferedFdBase : public FdT {
};
template <class FdT>
-class BufferedFd : public BufferedFdBase<FdT> {
+class BufferedFd final : public BufferedFdBase<FdT> {
using Parent = BufferedFdBase<FdT>;
ChainBufferWriter input_writer_;
ChainBufferReader input_reader_;
@@ -62,14 +66,21 @@ class BufferedFd : public BufferedFdBase<FdT> {
public:
BufferedFd();
explicit BufferedFd(FdT &&fd_);
- BufferedFd(BufferedFd &&);
- BufferedFd &operator=(BufferedFd &&);
+ BufferedFd(BufferedFd &&) noexcept;
+ BufferedFd &operator=(BufferedFd &&) noexcept;
BufferedFd(const BufferedFd &) = delete;
BufferedFd &operator=(const BufferedFd &) = delete;
~BufferedFd();
void close();
+ size_t left_unread() const {
+ return input_reader_.size();
+ }
+ size_t left_unwritten() const {
+ return output_reader_.size();
+ }
+
Result<size_t> flush_read(size_t max_read = std::numeric_limits<size_t>::max()) TD_WARN_UNUSED_RESULT;
Result<size_t> flush_write() TD_WARN_UNUSED_RESULT;
@@ -89,8 +100,9 @@ template <class FdT>
Result<size_t> BufferedFdBase<FdT>::flush_read(size_t max_read) {
CHECK(read_);
size_t result = 0;
- while (::td::can_read(*this) && max_read) {
- MutableSlice slice = read_->prepare_append().truncate(max_read);
+ while (::td::can_read_local(*this) && max_read) {
+ MutableSlice slice = read_->prepare_append();
+ slice.truncate(max_read);
TRY_RESULT(x, FdT::read(slice));
slice.truncate(x);
read_->confirm_append(x);
@@ -102,13 +114,25 @@ Result<size_t> BufferedFdBase<FdT>::flush_read(size_t max_read) {
template <class FdT>
Result<size_t> BufferedFdBase<FdT>::flush_write() {
- size_t result = 0;
// TODO: sync on demand
write_->sync_with_writer();
- while (!write_->empty() && ::td::can_write(*this)) {
- Slice slice = write_->prepare_read();
- TRY_RESULT(x, FdT::write(slice));
- write_->confirm_read(x);
+ size_t result = 0;
+ while (!write_->empty() && ::td::can_write_local(*this)) {
+ constexpr size_t BUF_SIZE = 20;
+ IoSlice buf[BUF_SIZE];
+
+ auto it = write_->clone();
+ size_t buf_i;
+ for (buf_i = 0; buf_i < BUF_SIZE; buf_i++) {
+ Slice slice = it.prepare_read();
+ if (slice.empty()) {
+ break;
+ }
+ buf[buf_i] = as_io_slice(slice);
+ it.confirm_read(slice.size());
+ }
+ TRY_RESULT(x, FdT::writev(Span<IoSlice>(buf, buf_i)));
+ write_->advance(x);
result += x;
}
return result;
@@ -139,12 +163,12 @@ BufferedFd<FdT>::BufferedFd(FdT &&fd_) : Parent(std::move(fd_)) {
}
template <class FdT>
-BufferedFd<FdT>::BufferedFd(BufferedFd &&from) {
+BufferedFd<FdT>::BufferedFd(BufferedFd &&from) noexcept {
*this = std::move(from);
}
template <class FdT>
-BufferedFd<FdT> &BufferedFd<FdT>::operator=(BufferedFd &&from) {
+BufferedFd<FdT> &BufferedFd<FdT>::operator=(BufferedFd &&from) noexcept {
FdT::operator=(std::move(static_cast<FdT &>(from)));
input_reader_ = std::move(from.input_reader_);
input_writer_ = std::move(from.input_writer_);
@@ -171,7 +195,7 @@ Result<size_t> BufferedFd<FdT>::flush_read(size_t max_read) {
if (result) {
// TODO: faster sync is possible if you owns writer.
input_reader_.sync_with_writer();
- LOG(DEBUG) << "flush_read: +" << format::as_size(result) << tag("total", format::as_size(input_reader_.size()));
+ LOG(DEBUG) << "Flush read: +" << format::as_size(result) << tag("total", format::as_size(input_reader_.size()));
}
return result;
}
@@ -180,7 +204,7 @@ template <class FdT>
Result<size_t> BufferedFd<FdT>::flush_write() {
TRY_RESULT(result, Parent::flush_write());
if (result) {
- LOG(DEBUG) << "flush_write: +" << format::as_size(result) << tag("left", format::as_size(output_reader_.size()));
+ LOG(DEBUG) << "Flush write: +" << format::as_size(result) << tag("left", format::as_size(output_reader_.size()));
}
return result;
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedReader.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedReader.h
index 9006d78132..5fd8ac44fe 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedReader.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedReader.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,12 +11,11 @@
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
-#include <cstring>
-
namespace td {
+
class BufferedReader {
public:
- explciit BufferedReader(FileFd &file, size_t buff_size = 8152)
+ explicit BufferedReader(FileFd &file, size_t buff_size = 8152)
: file_(file), buff_(buff_size), begin_pos_(0), end_pos_(0) {
}
@@ -33,13 +32,13 @@ inline Result<size_t> BufferedReader::read(MutableSlice slice) {
size_t available = end_pos_ - begin_pos_;
if (available >= slice.size()) {
// have enough data in buffer
- std::memcpy(slice.begin(), &buff_[begin_pos_], slice.size());
+ slice.copy_from({&buff_[begin_pos_], slice.size()});
begin_pos_ += slice.size();
return slice.size();
}
if (available) {
- std::memcpy(slice.begin(), &buff_[begin_pos_], available);
+ slice.copy_from({&buff_[begin_pos_], available});
begin_pos_ += available;
slice.remove_prefix(available);
}
@@ -54,8 +53,9 @@ inline Result<size_t> BufferedReader::read(MutableSlice slice) {
end_pos_ = result;
size_t left = min(end_pos_, slice.size());
- std::memcpy(slice.begin(), &buff_[begin_pos_], left);
- begin_pos_ += left;
+ slice.copy_from({&buff_[0], left});
+ begin_pos_ = left;
return left + available;
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.cpp
new file mode 100644
index 0000000000..e28bb97069
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.cpp
@@ -0,0 +1,17 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/BufferedUdp.h"
+
+char disable_linker_warning_about_empty_file_buffered_udp_cpp TD_UNUSED;
+
+namespace td {
+
+#if TD_PORT_POSIX
+TD_THREAD_LOCAL detail::UdpReader *BufferedUdp::udp_reader_;
+#endif
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.h
new file mode 100644
index 0000000000..5907e17557
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.h
@@ -0,0 +1,177 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/optional.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/thread_local.h"
+#include "td/utils/port/UdpSocketFd.h"
+#include "td/utils/Span.h"
+#include "td/utils/Status.h"
+#include "td/utils/VectorQueue.h"
+
+#include <array>
+
+namespace td {
+
+#if TD_PORT_POSIX
+namespace detail {
+class UdpWriter {
+ public:
+ static Status write_once(UdpSocketFd &fd, VectorQueue<UdpMessage> &queue) TD_WARN_UNUSED_RESULT {
+ std::array<UdpSocketFd::OutboundMessage, 16> messages;
+ auto to_send = queue.as_span();
+ size_t to_send_n = td::min(messages.size(), to_send.size());
+ to_send.truncate(to_send_n);
+ for (size_t i = 0; i < to_send_n; i++) {
+ messages[i].to = &to_send[i].address;
+ messages[i].data = to_send[i].data.as_slice();
+ }
+
+ size_t cnt;
+ auto status = fd.send_messages(Span<UdpSocketFd::OutboundMessage>(messages).truncate(to_send_n), cnt);
+ queue.pop_n(cnt);
+ return status;
+ }
+};
+
+class UdpReaderHelper {
+ public:
+ void init_inbound_message(UdpSocketFd::InboundMessage &message) {
+ message.from = &message_.address;
+ message.error = &message_.error;
+ if (buffer_.size() < MAX_PACKET_SIZE) {
+ buffer_ = BufferSlice(RESERVED_SIZE);
+ }
+ CHECK(buffer_.size() >= MAX_PACKET_SIZE);
+ message.data = buffer_.as_slice().substr(0, MAX_PACKET_SIZE);
+ }
+
+ UdpMessage extract_udp_message(UdpSocketFd::InboundMessage &message) {
+ message_.data = buffer_.from_slice(message.data);
+ auto size = message_.data.size();
+ size = (size + 7) & ~7;
+ CHECK(size <= MAX_PACKET_SIZE);
+ buffer_.confirm_read(size);
+ return std::move(message_);
+ }
+
+ private:
+ static constexpr size_t MAX_PACKET_SIZE = 2048;
+ static constexpr size_t RESERVED_SIZE = MAX_PACKET_SIZE * 8;
+ UdpMessage message_;
+ BufferSlice buffer_;
+};
+
+// One for thread is enough
+class UdpReader {
+ public:
+ UdpReader() {
+ for (size_t i = 0; i < messages_.size(); i++) {
+ helpers_[i].init_inbound_message(messages_[i]);
+ }
+ }
+ Status read_once(UdpSocketFd &fd, VectorQueue<UdpMessage> &queue) TD_WARN_UNUSED_RESULT {
+ for (auto &message : messages_) {
+ CHECK(message.data.size() == 2048);
+ }
+ size_t cnt = 0;
+ auto status = fd.receive_messages(messages_, cnt);
+ for (size_t i = 0; i < cnt; i++) {
+ queue.push(helpers_[i].extract_udp_message(messages_[i]));
+ helpers_[i].init_inbound_message(messages_[i]);
+ }
+ for (size_t i = cnt; i < messages_.size(); i++) {
+ LOG_CHECK(messages_[i].data.size() == 2048)
+ << " cnt = " << cnt << " i = " << i << " size = " << messages_[i].data.size() << " status = " << status;
+ }
+ if (status.is_error() && !UdpSocketFd::is_critical_read_error(status)) {
+ queue.push(UdpMessage{{}, {}, std::move(status)});
+ status = Status::OK();
+ }
+ return status;
+ }
+
+ private:
+ static constexpr size_t BUFFER_SIZE = 16;
+ std::array<UdpSocketFd::InboundMessage, BUFFER_SIZE> messages_;
+ std::array<UdpReaderHelper, BUFFER_SIZE> helpers_;
+};
+
+} // namespace detail
+
+#endif
+
+class BufferedUdp final : public UdpSocketFd {
+ public:
+ explicit BufferedUdp(UdpSocketFd fd) : UdpSocketFd(std::move(fd)) {
+ }
+
+#if TD_PORT_POSIX
+ void sync_with_poll() {
+ ::td::sync_with_poll(*this);
+ }
+ Result<optional<UdpMessage>> receive() {
+ if (input_.empty() && can_read_local(*this)) {
+ TRY_STATUS(flush_read_once());
+ }
+ if (input_.empty()) {
+ return optional<UdpMessage>();
+ }
+ return input_.pop();
+ }
+
+ void send(UdpMessage message) {
+ output_.push(std::move(message));
+ }
+
+ Status flush_send() {
+ Status status;
+ while (status.is_ok() && can_write_local(*this) && !output_.empty()) {
+ status = flush_send_once();
+ }
+ return status;
+ }
+#endif
+
+ UdpSocketFd move_as_udp_socket_fd() {
+ return std::move(as_fd());
+ }
+
+ UdpSocketFd &as_fd() {
+ return *static_cast<UdpSocketFd *>(this);
+ }
+
+ private:
+#if TD_PORT_POSIX
+ VectorQueue<UdpMessage> input_;
+ VectorQueue<UdpMessage> output_;
+
+ VectorQueue<UdpMessage> &input() {
+ return input_;
+ }
+ VectorQueue<UdpMessage> &output() {
+ return output_;
+ }
+
+ Status flush_send_once() TD_WARN_UNUSED_RESULT {
+ return detail::UdpWriter::write_once(as_fd(), output_);
+ }
+
+ Status flush_read_once() TD_WARN_UNUSED_RESULT {
+ init_thread_local<detail::UdpReader>(udp_reader_);
+ return udp_reader_->read_once(as_fd(), input_);
+ }
+
+ static TD_THREAD_LOCAL detail::UdpReader *udp_reader_;
+#endif
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ByteFlow.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ByteFlow.h
index fb0c4489eb..2043864656 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/ByteFlow.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ByteFlow.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,9 +8,10 @@
#include "td/utils/buffer.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include "td/utils/Status.h"
+#include <limits>
+
namespace td {
class ByteFlowInterface {
@@ -20,6 +21,10 @@ class ByteFlowInterface {
virtual void set_parent(ByteFlowInterface &other) = 0;
virtual void set_input(ChainBufferReader *input) = 0;
virtual size_t get_need_size() = 0;
+ virtual size_t get_read_size() = 0;
+ virtual size_t get_write_size() = 0;
+ virtual void reset_need_size() {
+ }
ByteFlowInterface() = default;
ByteFlowInterface(const ByteFlowInterface &) = delete;
ByteFlowInterface &operator=(const ByteFlowInterface &) = delete;
@@ -42,36 +47,98 @@ class ByteFlowBaseCommon : public ByteFlowInterface {
}
void wakeup() final {
- if (stop_flag_) {
+ if (stop_flag_ || !input_) {
return;
}
input_->sync_with_writer();
+
if (waiting_flag_) {
if (!is_input_active_) {
finish(Status::OK());
}
return;
}
- if (is_input_active_) {
- if (need_size_ != 0 && input_->size() < need_size_) {
- return;
+ while (true) {
+ if (stop_flag_) {
+ break;
+ }
+
+ // update can_read
+ if (is_input_active_) {
+ auto read_size = get_read_size();
+ if (read_size < min(need_size_, options_.read_watermark.low)) {
+ can_read = false;
+ }
+ if (read_size >= max(need_size_, options_.read_watermark.high)) {
+ can_read = true;
+ }
+ } else {
+ // always can read when input is closed
+ can_read = true;
+ }
+
+ // update can_write
+ {
+ auto write_size = get_write_size();
+ if (write_size > options_.write_watermark.high) {
+ can_write = false;
+ }
+ if (write_size <= options_.write_watermark.low) {
+ can_write = true;
+ }
+ }
+
+ if (!can_read || !can_write) {
+ break;
+ }
+ need_size_ = 0;
+
+ if (!loop()) {
+ if (need_size_ <= get_read_size()) {
+ need_size_ = get_read_size() + 1;
+ }
}
}
- need_size_ = 0;
- loop();
+ on_output_updated();
}
size_t get_need_size() final {
return need_size_;
}
+ void reset_need_size() override {
+ need_size_ = 0;
+ }
+ size_t get_read_size() override {
+ input_->sync_with_writer();
+ return input_->size();
+ }
+ size_t get_write_size() override {
+ CHECK(parent_);
+ return parent_->get_read_size();
+ }
+
+ struct Watermark {
+ size_t low{std::numeric_limits<size_t>::max()};
+ size_t high{0};
+ };
+ struct Options {
+ Watermark write_watermark;
+ Watermark read_watermark;
+ };
+ void set_options(Options options) {
+ options_ = options;
+ }
- virtual void loop() = 0;
+ virtual bool loop() = 0;
protected:
bool waiting_flag_ = false;
- ChainBufferReader *input_;
+ ChainBufferReader *input_ = nullptr;
bool is_input_active_ = true;
size_t need_size_ = 0;
+ bool can_read{true};
+ bool can_write{true};
+ Options options_;
void finish(Status status) {
stop_flag_ = true;
need_size_ = 0;
@@ -115,7 +182,7 @@ class ByteFlowBase : public ByteFlowBaseCommon {
parent_ = &other;
parent_->set_input(&output_reader_);
}
- void loop() override = 0;
+ bool loop() override = 0;
// ChainBufferWriter &get_output() {
// return output_;
@@ -138,7 +205,7 @@ class ByteFlowInplaceBase : public ByteFlowBaseCommon {
parent_ = &other;
parent_->set_input(&output_);
}
- void loop() override = 0;
+ bool loop() override = 0;
ChainBufferReader &get_output() {
return output_;
@@ -153,16 +220,16 @@ inline ByteFlowInterface &operator>>(ByteFlowInterface &from, ByteFlowInterface
return to;
}
-class ByteFlowSource : public ByteFlowInterface {
+class ByteFlowSource final : public ByteFlowInterface {
public:
ByteFlowSource() = default;
explicit ByteFlowSource(ChainBufferReader *buffer) : buffer_(buffer) {
}
- ByteFlowSource(ByteFlowSource &&other) : buffer_(other.buffer_), parent_(other.parent_) {
+ ByteFlowSource(ByteFlowSource &&other) noexcept : buffer_(other.buffer_), parent_(other.parent_) {
other.buffer_ = nullptr;
other.parent_ = nullptr;
}
- ByteFlowSource &operator=(ByteFlowSource &&other) {
+ ByteFlowSource &operator=(ByteFlowSource &&other) noexcept {
buffer_ = other.buffer_;
parent_ = other.parent_;
other.buffer_ = nullptr;
@@ -187,7 +254,9 @@ class ByteFlowSource : public ByteFlowInterface {
parent_ = nullptr;
}
void wakeup() final {
- CHECK(parent_);
+ if (!parent_) {
+ return;
+ }
parent_->wakeup();
}
size_t get_need_size() final {
@@ -196,19 +265,27 @@ class ByteFlowSource : public ByteFlowInterface {
}
return parent_->get_need_size();
}
+ size_t get_read_size() final {
+ UNREACHABLE();
+ return 0;
+ }
+ size_t get_write_size() final {
+ CHECK(parent_);
+ return parent_->get_read_size();
+ }
private:
ChainBufferReader *buffer_ = nullptr;
ByteFlowInterface *parent_ = nullptr;
};
-class ByteFlowSink : public ByteFlowInterface {
+class ByteFlowSink final : public ByteFlowInterface {
public:
void set_input(ChainBufferReader *input) final {
CHECK(buffer_ == nullptr);
buffer_ = input;
}
- void set_parent(ByteFlowInterface &parent) final {
+ void set_parent(ByteFlowInterface & /*parent*/) final {
UNREACHABLE();
}
void close_input(Status status) final {
@@ -224,6 +301,14 @@ class ByteFlowSink : public ByteFlowInterface {
UNREACHABLE();
return 0;
}
+ size_t get_read_size() final {
+ buffer_->sync_with_writer();
+ return buffer_->size();
+ }
+ size_t get_write_size() final {
+ UNREACHABLE();
+ return 0;
+ }
bool is_ready() {
return !active_;
}
@@ -244,13 +329,17 @@ class ByteFlowSink : public ByteFlowInterface {
ChainBufferReader *buffer_ = nullptr;
};
-class ByteFlowMoveSink : public ByteFlowInterface {
+class ByteFlowMoveSink final : public ByteFlowInterface {
public:
+ ByteFlowMoveSink() = default;
+ explicit ByteFlowMoveSink(ChainBufferWriter *output) {
+ set_output(output);
+ }
void set_input(ChainBufferReader *input) final {
CHECK(!input_);
input_ = input;
}
- void set_parent(ByteFlowInterface &parent) final {
+ void set_parent(ByteFlowInterface & /*parent*/) final {
UNREACHABLE();
}
void close_input(Status status) final {
@@ -267,6 +356,15 @@ class ByteFlowMoveSink : public ByteFlowInterface {
UNREACHABLE();
return 0;
}
+ size_t get_read_size() final {
+ input_->sync_with_writer();
+ //TODO: must be input_->size() + output_->size()
+ return input_->size();
+ }
+ size_t get_write_size() final {
+ UNREACHABLE();
+ return 0;
+ }
void set_output(ChainBufferWriter *output) {
CHECK(!output_);
output_ = output;
@@ -285,4 +383,5 @@ class ByteFlowMoveSink : public ByteFlowInterface {
ChainBufferReader *input_ = nullptr;
ChainBufferWriter *output_ = nullptr;
};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/CancellationToken.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/CancellationToken.h
new file mode 100644
index 0000000000..19f280655c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/CancellationToken.h
@@ -0,0 +1,71 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include <atomic>
+#include <memory>
+
+namespace td {
+
+namespace detail {
+struct RawCancellationToken {
+ std::atomic<bool> is_canceled_{false};
+};
+} // namespace detail
+
+class CancellationToken {
+ public:
+ explicit operator bool() const noexcept {
+ // empty CancellationToken is never canceled
+ if (!token_) {
+ return false;
+ }
+ return token_->is_canceled_.load(std::memory_order_acquire);
+ }
+ CancellationToken() = default;
+ explicit CancellationToken(std::shared_ptr<detail::RawCancellationToken> token) : token_(std::move(token)) {
+ }
+
+ private:
+ std::shared_ptr<detail::RawCancellationToken> token_;
+};
+
+class CancellationTokenSource {
+ public:
+ CancellationTokenSource() = default;
+ CancellationTokenSource(CancellationTokenSource &&other) noexcept : token_(std::move(other.token_)) {
+ }
+ CancellationTokenSource &operator=(CancellationTokenSource &&other) noexcept {
+ cancel();
+ token_ = std::move(other.token_);
+ return *this;
+ }
+ CancellationTokenSource(const CancellationTokenSource &other) = delete;
+ CancellationTokenSource &operator=(const CancellationTokenSource &other) = delete;
+ ~CancellationTokenSource() {
+ cancel();
+ }
+
+ CancellationToken get_cancellation_token() {
+ if (!token_) {
+ token_ = std::make_shared<detail::RawCancellationToken>();
+ }
+ return CancellationToken(token_);
+ }
+ void cancel() {
+ if (!token_) {
+ return;
+ }
+ token_->is_canceled_.store(true, std::memory_order_release);
+ token_.reset();
+ }
+
+ private:
+ std::shared_ptr<detail::RawCancellationToken> token_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ChainScheduler.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ChainScheduler.h
new file mode 100644
index 0000000000..721a22e137
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ChainScheduler.h
@@ -0,0 +1,376 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/Container.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/List.h"
+#include "td/utils/logging.h"
+#include "td/utils/optional.h"
+#include "td/utils/Span.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/VectorQueue.h"
+
+#include <functional>
+#include <unordered_map>
+
+namespace td {
+
+struct ChainSchedulerBase {
+ struct TaskWithParents {
+ uint64 task_id{};
+ vector<uint64> parents;
+ };
+};
+
+template <class ExtraT = Unit>
+class ChainScheduler final : public ChainSchedulerBase {
+ public:
+ using TaskId = uint64;
+ using ChainId = uint64;
+
+ TaskId create_task(Span<ChainId> chains, ExtraT extra = {});
+
+ ExtraT *get_task_extra(TaskId task_id);
+
+ optional<TaskWithParents> start_next_task();
+
+ void pause_task(TaskId task_id);
+
+ void finish_task(TaskId task_id);
+
+ void reset_task(TaskId task_id);
+
+ template <class F>
+ void for_each(F &&f) {
+ tasks_.for_each([&f](auto, Task &task) { f(task.extra); });
+ }
+
+ template <class F>
+ void for_each_dependent(TaskId task_id, F &&f) {
+ auto *task = tasks_.get(task_id);
+ CHECK(task != nullptr);
+ FlatHashSet<TaskId> visited;
+ bool check_for_collisions = task->chains.size() > 1;
+ for (TaskChainInfo &task_chain_info : task->chains) {
+ ChainInfo &chain_info = *task_chain_info.chain_info;
+ chain_info.chain.foreach_child(&task_chain_info.chain_node, [&](TaskId task_id, uint64) {
+ if (check_for_collisions && !visited.insert(task_id).second) {
+ return;
+ }
+ f(task_id);
+ });
+ }
+ }
+
+ private:
+ struct ChainNode : ListNode {
+ TaskId task_id{};
+ uint64 generation{};
+ };
+
+ class Chain {
+ public:
+ void add_task(ChainNode *node) {
+ head_.put_back(node);
+ }
+
+ optional<TaskId> get_first() {
+ if (head_.empty()) {
+ return {};
+ }
+ return static_cast<ChainNode &>(*head_.get_next()).task_id;
+ }
+
+ optional<TaskId> get_child(ChainNode *chain_node) {
+ if (chain_node->get_next() == head_.end()) {
+ return {};
+ }
+ return static_cast<ChainNode &>(*chain_node->get_next()).task_id;
+ }
+ optional<ChainNode *> get_parent(ChainNode *chain_node) {
+ if (chain_node->get_prev() == head_.end()) {
+ return {};
+ }
+ return static_cast<ChainNode *>(chain_node->get_prev());
+ }
+
+ void finish_task(ChainNode *node) {
+ node->remove();
+ }
+
+ bool empty() const {
+ return head_.empty();
+ }
+
+ void foreach(std::function<void(TaskId, uint64)> f) const {
+ for (auto it = head_.begin(); it != head_.end(); it = it->get_next()) {
+ auto &node = static_cast<const ChainNode &>(*it);
+ f(node.task_id, node.generation);
+ }
+ }
+ void foreach_child(ListNode *start_node, std::function<void(TaskId, uint64)> f) const {
+ for (auto it = start_node; it != head_.end(); it = it->get_next()) {
+ auto &node = static_cast<const ChainNode &>(*it);
+ f(node.task_id, node.generation);
+ }
+ }
+
+ private:
+ ListNode head_;
+ };
+ struct ChainInfo {
+ Chain chain;
+ uint32 active_tasks{};
+ uint64 generation{1};
+ };
+ struct TaskChainInfo {
+ ChainNode chain_node;
+ ChainId chain_id{};
+ ChainInfo *chain_info{};
+ };
+ struct Task {
+ enum class State { Pending, Active, Paused } state{State::Pending};
+ vector<TaskChainInfo> chains;
+ ExtraT extra;
+ };
+ std::unordered_map<ChainId, ChainInfo, Hash<ChainId>> chains_;
+ std::unordered_map<ChainId, TaskId, Hash<ChainId>> limited_tasks_;
+ Container<Task> tasks_;
+ VectorQueue<TaskId> pending_tasks_;
+
+ void try_start_task(TaskId task_id) {
+ auto *task = tasks_.get(task_id);
+ CHECK(task != nullptr);
+ if (task->state != Task::State::Pending) {
+ return;
+ }
+ for (TaskChainInfo &task_chain_info : task->chains) {
+ auto o_parent = task_chain_info.chain_info->chain.get_parent(&task_chain_info.chain_node);
+
+ if (o_parent) {
+ if (o_parent.value()->generation != task_chain_info.chain_info->generation) {
+ return;
+ }
+ }
+
+ if (task_chain_info.chain_info->active_tasks >= 10) {
+ limited_tasks_[task_chain_info.chain_id] = task_id;
+ return;
+ }
+ }
+
+ do_start_task(task_id, task);
+ }
+
+ void do_start_task(TaskId task_id, Task *task) {
+ for (TaskChainInfo &task_chain_info : task->chains) {
+ ChainInfo &chain_info = chains_[task_chain_info.chain_id];
+ chain_info.active_tasks++;
+ task_chain_info.chain_node.generation = chain_info.generation;
+ }
+ task->state = Task::State::Active;
+
+ pending_tasks_.push(task_id);
+ for_each_child(task, [&](TaskId task_id) { try_start_task(task_id); });
+ }
+
+ template <class F>
+ void for_each_child(Task *task, F &&f) {
+ for (TaskChainInfo &task_chain_info : task->chains) {
+ ChainInfo &chain_info = *task_chain_info.chain_info;
+ auto o_child = chain_info.chain.get_child(&task_chain_info.chain_node);
+ if (o_child) {
+ f(o_child.value());
+ }
+ }
+ }
+
+ void inactivate_task(TaskId task_id, bool failed) {
+ LOG(DEBUG) << "Inactivate " << task_id << " " << (failed ? "failed" : "finished");
+ auto *task = tasks_.get(task_id);
+ CHECK(task != nullptr);
+ bool was_active = task->state == Task::State::Active;
+ task->state = Task::State::Pending;
+ for (TaskChainInfo &task_chain_info : task->chains) {
+ ChainInfo &chain_info = *task_chain_info.chain_info;
+ if (was_active) {
+ chain_info.active_tasks--;
+ }
+ if (was_active && failed) {
+ chain_info.generation = td::max(chain_info.generation, task_chain_info.chain_node.generation + 1);
+ }
+
+ auto it = limited_tasks_.find(task_chain_info.chain_id);
+ if (it != limited_tasks_.end()) {
+ auto limited_task_id = it->second;
+ limited_tasks_.erase(it);
+ if (limited_task_id != task_id) {
+ try_start_task_later(limited_task_id);
+ }
+ }
+
+ auto o_first = chain_info.chain.get_first();
+ if (o_first) {
+ auto first_task_id = o_first.unwrap();
+ if (first_task_id != task_id) {
+ try_start_task_later(first_task_id);
+ }
+ }
+ }
+ }
+
+ void finish_chain_task(TaskChainInfo &task_chain_info) {
+ auto &chain = task_chain_info.chain_info->chain;
+ chain.finish_task(&task_chain_info.chain_node);
+ if (chain.empty()) {
+ chains_.erase(task_chain_info.chain_id);
+ }
+ }
+
+ vector<TaskId> to_start_;
+
+ void try_start_task_later(TaskId task_id) {
+ LOG(DEBUG) << "Start later " << task_id;
+ to_start_.push_back(task_id);
+ }
+
+ void flush_try_start_task() {
+ auto moved_to_start = std::move(to_start_);
+ for (auto task_id : moved_to_start) {
+ try_start_task(task_id);
+ }
+ CHECK(to_start_.empty());
+ }
+
+ template <class ExtraTT>
+ friend StringBuilder &operator<<(StringBuilder &sb, ChainScheduler<ExtraTT> &scheduler);
+};
+
+template <class ExtraT>
+typename ChainScheduler<ExtraT>::TaskId ChainScheduler<ExtraT>::create_task(Span<ChainScheduler::ChainId> chains,
+ ExtraT extra) {
+ auto task_id = tasks_.create();
+ Task &task = *tasks_.get(task_id);
+ task.extra = std::move(extra);
+ task.chains = transform(chains, [&](auto chain_id) {
+ TaskChainInfo task_chain_info;
+ ChainInfo &chain_info = chains_[chain_id];
+ task_chain_info.chain_id = chain_id;
+ task_chain_info.chain_info = &chain_info;
+ task_chain_info.chain_node.task_id = task_id;
+ task_chain_info.chain_node.generation = 0;
+ return task_chain_info;
+ });
+
+ for (TaskChainInfo &task_chain_info : task.chains) {
+ ChainInfo &chain_info = *task_chain_info.chain_info;
+ chain_info.chain.add_task(&task_chain_info.chain_node);
+ }
+
+ try_start_task(task_id);
+ return task_id;
+}
+
+// TODO: return reference
+template <class ExtraT>
+ExtraT *ChainScheduler<ExtraT>::get_task_extra(ChainScheduler::TaskId task_id) { // may return nullptr
+ auto *task = tasks_.get(task_id);
+ if (task == nullptr) {
+ return nullptr;
+ }
+ return &task->extra;
+}
+
+template <class ExtraT>
+optional<ChainSchedulerBase::TaskWithParents> ChainScheduler<ExtraT>::start_next_task() {
+ if (pending_tasks_.empty()) {
+ return {};
+ }
+ auto task_id = pending_tasks_.pop();
+ TaskWithParents res;
+ res.task_id = task_id;
+ auto *task = tasks_.get(task_id);
+ CHECK(task != nullptr);
+ for (TaskChainInfo &task_chain_info : task->chains) {
+ Chain &chain = task_chain_info.chain_info->chain;
+ auto o_parent = chain.get_parent(&task_chain_info.chain_node);
+ if (o_parent) {
+ res.parents.push_back(o_parent.value()->task_id);
+ }
+ }
+ return res;
+}
+
+template <class ExtraT>
+void ChainScheduler<ExtraT>::finish_task(ChainScheduler::TaskId task_id) {
+ auto *task = tasks_.get(task_id);
+ CHECK(task != nullptr);
+ CHECK(to_start_.empty());
+
+ inactivate_task(task_id, false);
+
+ for_each_child(task, [&](TaskId task_id) { try_start_task_later(task_id); });
+
+ for (TaskChainInfo &task_chain_info : task->chains) {
+ finish_chain_task(task_chain_info);
+ }
+
+ tasks_.erase(task_id);
+ flush_try_start_task();
+}
+
+template <class ExtraT>
+void ChainScheduler<ExtraT>::reset_task(ChainScheduler::TaskId task_id) {
+ CHECK(to_start_.empty());
+ auto *task = tasks_.get(task_id);
+ CHECK(task != nullptr);
+ inactivate_task(task_id, true);
+ try_start_task_later(task_id);
+ flush_try_start_task();
+}
+
+template <class ExtraT>
+void ChainScheduler<ExtraT>::pause_task(TaskId task_id) {
+ auto *task = tasks_.get(task_id);
+ CHECK(task != nullptr);
+ inactivate_task(task_id, true);
+ task->state = Task::State::Paused;
+ flush_try_start_task();
+}
+
+template <class ExtraT>
+StringBuilder &operator<<(StringBuilder &sb, ChainScheduler<ExtraT> &scheduler) {
+ // 1 print chains
+ sb << '\n';
+ for (auto &it : scheduler.chains_) {
+ sb << "ChainId{" << it.first << "}";
+ sb << " active_cnt = " << it.second.active_tasks;
+ sb << " g = " << it.second.generation;
+ sb << ':';
+ it.second.chain.foreach(
+ [&](auto task_id, auto generation) { sb << ' ' << *scheduler.get_task_extra(task_id) << ':' << generation; });
+ sb << '\n';
+ }
+ scheduler.tasks_.for_each([&](auto id, auto &task) {
+ sb << "Task: " << task.extra;
+ sb << " state = " << static_cast<int>(task.state);
+ for (auto &task_chain_info : task.chains) {
+ sb << " g = " << task_chain_info.chain_node.generation;
+ if (task_chain_info.chain_info->generation != task_chain_info.chain_node.generation) {
+ sb << " chain_g = " << task_chain_info.chain_info->generation;
+ }
+ }
+ sb << '\n';
+ });
+ return sb;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ChangesProcessor.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ChangesProcessor.h
index 9342f45a8b..669111a2ff 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/ChangesProcessor.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ChangesProcessor.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Closure.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Closure.h
index 718f930b8a..345f1b1f89 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Closure.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Closure.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,9 +8,7 @@
#include "td/utils/common.h"
#include "td/utils/invoke.h"
-#include "td/utils/logging.h"
-#include <cstdlib>
#include <tuple>
#include <type_traits>
#include <utility>
@@ -54,8 +52,7 @@
// If delay is needed, just std::forward data to temporary storage, and std::move them when call is executed.
//
//
-// create_immediate_closure(&Actor::func, arg1, arg2, ..., argn).run(actor)
-// to_delayed_closure(std::move(immediate)).run(actor)
+// create_immediate_closure(&ActorT::func, arg1, arg2, ..., argn).run(actor)
namespace td {
template <class ActorT, class FunctionT, class... ArgsT>
@@ -68,22 +65,22 @@ class ImmediateClosure {
friend Delayed;
using ActorType = ActorT;
- void run(ActorT *actor) {
- mem_call_tuple(actor, func, std::move(args));
- }
-
// no &&. just save references as references.
- explicit ImmediateClosure(FunctionT func, ArgsT... args) : func(func), args(std::forward<ArgsT>(args)...) {
+ explicit ImmediateClosure(FunctionT func, ArgsT... args) : args(func, std::forward<ArgsT>(args)...) {
}
private:
- FunctionT func;
- std::tuple<ArgsT...> args;
+ std::tuple<FunctionT, ArgsT...> args;
+
+ public:
+ auto run(ActorT *actor) -> decltype(mem_call_tuple(actor, std::move(args))) {
+ return mem_call_tuple(actor, std::move(args));
+ }
};
template <class ActorT, class ResultT, class... DestArgsT, class... SrcArgsT>
ImmediateClosure<ActorT, ResultT (ActorT::*)(DestArgsT...), SrcArgsT &&...> create_immediate_closure(
- ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&... args) {
+ ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&...args) {
return ImmediateClosure<ActorT, ResultT (ActorT::*)(DestArgsT...), SrcArgsT &&...>(func,
std::forward<SrcArgsT>(args)...);
}
@@ -92,21 +89,11 @@ template <class ActorT, class FunctionT, class... ArgsT>
class DelayedClosure {
public:
using ActorType = ActorT;
- using Delayed = DelayedClosure<ActorT, FunctionT, ArgsT...>;
- void run(ActorT *actor) {
- mem_call_tuple(actor, func, std::move(args));
+ explicit DelayedClosure(ImmediateClosure<ActorT, FunctionT, ArgsT...> &&other) : args(std::move(other.args)) {
}
- DelayedClosure clone() const {
- return do_clone(*this);
- }
-
- explicit DelayedClosure(ImmediateClosure<ActorT, FunctionT, ArgsT...> &&other)
- : func(std::move(other.func)), args(std::move(other.args)) {
- }
-
- explicit DelayedClosure(FunctionT func, ArgsT... args) : func(func), args(std::forward<ArgsT>(args)...) {
+ explicit DelayedClosure(FunctionT func, ArgsT... args) : args(func, std::forward<ArgsT>(args)...) {
}
template <class F>
@@ -115,53 +102,16 @@ class DelayedClosure {
}
private:
- using ArgsStorageT = std::tuple<typename std::decay<ArgsT>::type...>;
-
- FunctionT func;
- ArgsStorageT args;
+ std::tuple<FunctionT, typename std::decay<ArgsT>::type...> args;
- template <class FromActorT, class FromFunctionT, class... FromArgsT>
- explicit DelayedClosure(const DelayedClosure<FromActorT, FromFunctionT, FromArgsT...> &other,
- std::enable_if_t<LogicAnd<std::is_copy_constructible<FromArgsT>::value...>::value, int> = 0)
- : func(other.func), args(other.args) {
- }
-
- template <class FromActorT, class FromFunctionT, class... FromArgsT>
- explicit DelayedClosure(
- const DelayedClosure<FromActorT, FromFunctionT, FromArgsT...> &other,
- std::enable_if_t<!LogicAnd<std::is_copy_constructible<FromArgsT>::value...>::value, int> = 0) {
- LOG(FATAL) << "Deleted constructor";
- std::abort();
- }
-
- template <class FromActorT, class FromFunctionT, class... FromArgsT>
- std::enable_if_t<!LogicAnd<std::is_copy_constructible<FromArgsT>::value...>::value,
- DelayedClosure<FromActorT, FromFunctionT, FromArgsT...>>
- do_clone(const DelayedClosure<FromActorT, FromFunctionT, FromArgsT...> &value) const {
- LOG(FATAL) << "Trying to clone DelayedClosure that contains noncopyable elements";
- std::abort();
- }
-
- template <class FromActorT, class FromFunctionT, class... FromArgsT>
- std::enable_if_t<LogicAnd<std::is_copy_constructible<FromArgsT>::value...>::value,
- DelayedClosure<FromActorT, FromFunctionT, FromArgsT...>>
- do_clone(const DelayedClosure<FromActorT, FromFunctionT, FromArgsT...> &value) const {
- return DelayedClosure<FromActorT, FromFunctionT, FromArgsT...>(value);
+ public:
+ auto run(ActorT *actor) -> decltype(mem_call_tuple(actor, std::move(args))) {
+ return mem_call_tuple(actor, std::move(args));
}
};
-template <class... ArgsT>
-typename ImmediateClosure<ArgsT...>::Delayed to_delayed_closure(ImmediateClosure<ArgsT...> &&other) {
- return typename ImmediateClosure<ArgsT...>::Delayed(std::move(other));
-}
-
-template <class... ArgsT>
-DelayedClosure<ArgsT...> to_delayed_closure(DelayedClosure<ArgsT...> &&other) {
- return std::move(other);
-}
-
template <class ActorT, class ResultT, class... DestArgsT, class... SrcArgsT>
-auto create_delayed_closure(ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&... args) {
+auto create_delayed_closure(ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&...args) {
return DelayedClosure<ActorT, ResultT (ActorT::*)(DestArgsT...), SrcArgsT &&...>(func,
std::forward<SrcArgsT>(args)...);
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/CombinedLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/CombinedLog.h
new file mode 100644
index 0000000000..f2fe36069e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/CombinedLog.h
@@ -0,0 +1,86 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+class CombinedLog final : public LogInterface {
+ public:
+ void set_first(LogInterface *first) {
+ first_ = first;
+ }
+
+ void set_second(LogInterface *second) {
+ second_ = second;
+ }
+
+ void set_first_verbosity_level(int new_verbosity_level) {
+ first_verbosity_level_ = new_verbosity_level;
+ }
+
+ void set_second_verbosity_level(int new_verbosity_level) {
+ second_verbosity_level_ = new_verbosity_level;
+ }
+
+ const LogInterface *get_first() const {
+ return first_;
+ }
+
+ const LogInterface *get_second() const {
+ return second_;
+ }
+
+ int get_first_verbosity_level() const {
+ return first_verbosity_level_;
+ }
+
+ int get_second_verbosity_level() const {
+ return second_verbosity_level_;
+ }
+
+ private:
+ LogInterface *first_ = nullptr;
+ int first_verbosity_level_ = VERBOSITY_NAME(FATAL);
+ LogInterface *second_ = nullptr;
+ int second_verbosity_level_ = VERBOSITY_NAME(FATAL);
+
+ void do_append(int log_level, CSlice slice) final {
+ if (first_ && log_level <= first_verbosity_level_) {
+ first_->do_append(log_level, slice);
+ }
+ if (second_ && log_level <= second_verbosity_level_) {
+ second_->do_append(log_level, slice);
+ }
+ }
+
+ void after_rotation() final {
+ if (first_) {
+ first_->after_rotation();
+ }
+ if (second_) {
+ second_->after_rotation();
+ }
+ }
+
+ vector<string> get_file_paths() final {
+ vector<string> result;
+ if (first_) {
+ ::td::append(result, first_->get_file_paths());
+ }
+ if (second_) {
+ ::td::append(result, second_->get_file_paths());
+ }
+ return result;
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ConcurrentHashTable.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ConcurrentHashTable.h
new file mode 100644
index 0000000000..f3f0bcfa92
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ConcurrentHashTable.h
@@ -0,0 +1,322 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HazardPointers.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/thread_local.h"
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+
+namespace td {
+
+// AtomicHashArray<KeyT, ValueT>
+// Building block for other concurrent hash maps
+//
+// Support one operation:
+// template <class F>
+// bool with_value(KeyT key, bool should_create, F &&func);
+//
+// Finds slot for key, and call func(value)
+// Creates slot if should_create is true.
+// Returns true if func was called.
+//
+// Concurrent calls with the same key may result in concurrent calls to func(value)
+// It is responsibility of the caller to handle such races.
+//
+// Key should already be random
+// It is responsibility of the caller to provide unique random key.
+// One may use injective hash function, or handle collisions in some other way.
+
+template <class KeyT, class ValueT>
+class AtomicHashArray {
+ public:
+ explicit AtomicHashArray(size_t n) : nodes_(n) {
+ }
+ struct Node {
+ std::atomic<KeyT> key{KeyT{}};
+ ValueT value{};
+ };
+ size_t size() const {
+ return nodes_.size();
+ }
+ Node &node_at(size_t i) {
+ return nodes_[i];
+ }
+ static KeyT empty_key() {
+ return KeyT{};
+ }
+
+ template <class F>
+ bool with_value(KeyT key, bool should_create, F &&f) {
+ DCHECK(key != empty_key());
+ auto pos = static_cast<size_t>(key) % nodes_.size();
+ auto n = td::min(td::max(static_cast<size_t>(300), nodes_.size() / 16 + 2), nodes_.size());
+
+ for (size_t i = 0; i < n; i++) {
+ pos++;
+ if (pos >= nodes_.size()) {
+ pos = 0;
+ }
+ auto &node = nodes_[pos];
+ while (true) {
+ auto node_key = node.key.load(std::memory_order_acquire);
+ if (node_key == empty_key()) {
+ if (!should_create) {
+ return false;
+ }
+ KeyT expected_key = empty_key();
+ if (node.key.compare_exchange_strong(expected_key, key, std::memory_order_relaxed,
+ std::memory_order_relaxed)) {
+ f(node.value);
+ return true;
+ }
+ } else if (node_key == key) {
+ f(node.value);
+ return true;
+ } else {
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
+ private:
+ std::vector<Node> nodes_;
+};
+
+// Simple concurrent hash map with multiple limitations
+template <class KeyT, class ValueT>
+class ConcurrentHashMap {
+ using HashMap = AtomicHashArray<KeyT, std::atomic<ValueT>>;
+ static HazardPointers<HashMap> hp_;
+
+ public:
+ explicit ConcurrentHashMap(size_t n = 32) {
+ n = 1;
+ hash_map_.store(make_unique<HashMap>(n).release());
+ }
+ ConcurrentHashMap(const ConcurrentHashMap &) = delete;
+ ConcurrentHashMap &operator=(const ConcurrentHashMap &) = delete;
+ ConcurrentHashMap(ConcurrentHashMap &&) = delete;
+ ConcurrentHashMap &operator=(ConcurrentHashMap &&) = delete;
+ ~ConcurrentHashMap() {
+ unique_ptr<HashMap>(hash_map_.load()).reset();
+ }
+
+ static std::string get_name() {
+ return "ConcurrrentHashMap";
+ }
+
+ static KeyT empty_key() {
+ return KeyT{};
+ }
+ static ValueT empty_value() {
+ return ValueT{};
+ }
+ static ValueT migrate_value() {
+ return (ValueT)(1); // c-style conversion because reinterpret_cast<int>(1) is CE in MSVC
+ }
+
+ ValueT insert(KeyT key, ValueT value) {
+ CHECK(key != empty_key());
+ CHECK(value != migrate_value());
+ typename HazardPointers<HashMap>::Holder holder(hp_, get_thread_id(), 0);
+ while (true) {
+ auto hash_map = holder.protect(hash_map_);
+ if (!hash_map) {
+ do_migrate(nullptr);
+ continue;
+ }
+
+ bool ok = false;
+ ValueT inserted_value;
+ hash_map->with_value(key, true, [&](auto &node_value) {
+ ValueT expected_value = this->empty_value();
+ if (node_value.compare_exchange_strong(expected_value, value, std::memory_order_release,
+ std::memory_order_acquire)) {
+ ok = true;
+ inserted_value = value;
+ } else {
+ if (expected_value == this->migrate_value()) {
+ ok = false;
+ } else {
+ ok = true;
+ inserted_value = expected_value;
+ }
+ }
+ });
+ if (ok) {
+ return inserted_value;
+ }
+ do_migrate(hash_map);
+ }
+ }
+
+ ValueT find(KeyT key, ValueT value) {
+ typename HazardPointers<HashMap>::Holder holder(hp_, get_thread_id(), 0);
+ while (true) {
+ auto hash_map = holder.protect(hash_map_);
+ if (!hash_map) {
+ do_migrate(nullptr);
+ continue;
+ }
+
+ bool has_value = hash_map->with_value(
+ key, false, [&](auto &node_value) { value = node_value.load(std::memory_order_acquire); });
+ if (!has_value || value != migrate_value()) {
+ return value;
+ }
+ do_migrate(hash_map);
+ }
+ }
+
+ template <class F>
+ void for_each(F &&f) {
+ auto hash_map = hash_map_.load();
+ CHECK(hash_map);
+ auto size = hash_map->size();
+ for (size_t i = 0; i < size; i++) {
+ auto &node = hash_map->node_at(i);
+ auto key = node.key.load(std::memory_order_relaxed);
+ auto value = node.value.load(std::memory_order_relaxed);
+
+ if (key != empty_key()) {
+ CHECK(value != migrate_value());
+ if (value != empty_value()) {
+ f(key, value);
+ }
+ }
+ }
+ }
+
+ private:
+ // use no padding intentionally
+ std::atomic<HashMap *> hash_map_{nullptr};
+
+ std::mutex migrate_mutex_;
+ std::condition_variable migrate_cv_;
+
+ int migrate_cnt_{0};
+ int migrate_generation_{0};
+ HashMap *migrate_from_hash_map_{nullptr};
+ HashMap *migrate_to_hash_map_{nullptr};
+ struct Task {
+ size_t begin;
+ size_t end;
+ bool empty() const {
+ return begin >= end;
+ }
+ size_t size() const {
+ if (empty()) {
+ return 0;
+ }
+ return end - begin;
+ }
+ };
+
+ struct TaskCreator {
+ size_t chunk_size;
+ size_t size;
+ std::atomic<size_t> pos{0};
+ Task create() {
+ auto i = pos++;
+ auto begin = i * chunk_size;
+ auto end = begin + chunk_size;
+ if (end > size) {
+ end = size;
+ }
+ return {begin, end};
+ }
+ };
+ TaskCreator task_creator;
+
+ void do_migrate(HashMap *ptr) {
+ //LOG(ERROR) << "In do_migrate: " << ptr;
+ std::unique_lock<std::mutex> lock(migrate_mutex_);
+ if (hash_map_.load() != ptr) {
+ return;
+ }
+ init_migrate();
+ CHECK(!ptr || migrate_from_hash_map_ == ptr);
+ migrate_cnt_++;
+ auto migrate_generation = migrate_generation_;
+ lock.unlock();
+
+ run_migrate();
+
+ lock.lock();
+ migrate_cnt_--;
+ if (migrate_cnt_ == 0) {
+ finish_migrate();
+ }
+ migrate_cv_.wait(lock, [&] { return migrate_generation_ != migrate_generation; });
+ }
+
+ void finish_migrate() {
+ //LOG(ERROR) << "In finish_migrate";
+ hash_map_.store(migrate_to_hash_map_);
+ hp_.retire(get_thread_id(), migrate_from_hash_map_);
+ migrate_from_hash_map_ = nullptr;
+ migrate_to_hash_map_ = nullptr;
+ migrate_generation_++;
+ migrate_cv_.notify_all();
+ }
+
+ void init_migrate() {
+ if (migrate_from_hash_map_ != nullptr) {
+ return;
+ }
+ //LOG(ERROR) << "In init_migrate";
+ CHECK(migrate_cnt_ == 0);
+ migrate_generation_++;
+ migrate_from_hash_map_ = hash_map_.exchange(nullptr);
+ auto new_size = migrate_from_hash_map_->size() * 2;
+ migrate_to_hash_map_ = make_unique<HashMap>(new_size).release();
+ task_creator.chunk_size = 100;
+ task_creator.size = migrate_from_hash_map_->size();
+ task_creator.pos = 0;
+ }
+
+ void run_migrate() {
+ //LOG(ERROR) << "In run_migrate";
+ size_t cnt = 0;
+ while (true) {
+ auto task = task_creator.create();
+ cnt += task.size();
+ if (task.empty()) {
+ break;
+ }
+ run_task(task);
+ }
+ //LOG(ERROR) << "In run_migrate " << cnt;
+ }
+
+ void run_task(Task task) {
+ for (auto i = task.begin; i < task.end; i++) {
+ auto &node = migrate_from_hash_map_->node_at(i);
+ auto old_value = node.value.exchange(migrate_value(), std::memory_order_acq_rel);
+ if (old_value == 0) {
+ continue;
+ }
+ auto node_key = node.key.load(std::memory_order_relaxed);
+ //LOG(ERROR) << node_key << " " << node_key;
+ auto ok = migrate_to_hash_map_->with_value(
+ node_key, true, [&](auto &node_value) { node_value.store(old_value, std::memory_order_relaxed); });
+ LOG_CHECK(ok) << "Migration overflow";
+ }
+ }
+};
+
+template <class KeyT, class ValueT>
+HazardPointers<typename ConcurrentHashMap<KeyT, ValueT>::HashMap> ConcurrentHashMap<KeyT, ValueT>::hp_(64);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Container.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Container.h
index 57b4bb4d16..418edfd56a 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Container.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Container.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,6 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include <limits>
@@ -107,8 +106,8 @@ class Container {
}
int32 decode_id(Id id) const {
- int32 slot_id = static_cast<int32>(id >> 32);
- uint32 generation = static_cast<uint32>(id);
+ auto slot_id = static_cast<int32>(id >> 32);
+ auto generation = static_cast<uint32>(id);
if (slot_id < 0 || slot_id >= static_cast<int32>(slots_.size())) {
return -1;
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Context.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Context.h
new file mode 100644
index 0000000000..e9cac2ed09
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Context.h
@@ -0,0 +1,44 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/port/thread_local.h"
+
+namespace td {
+
+template <class Impl>
+class Context {
+ public:
+ static Impl *get() {
+ return context_;
+ }
+ class Guard {
+ public:
+ explicit Guard(Impl *new_context) {
+ old_context_ = context_;
+ context_ = new_context;
+ }
+ ~Guard() {
+ context_ = old_context_;
+ }
+ Guard(const Guard &) = delete;
+ Guard &operator=(const Guard &) = delete;
+ Guard(Guard &&) = delete;
+ Guard &operator=(Guard &&) = delete;
+
+ private:
+ Impl *old_context_;
+ };
+
+ private:
+ static TD_THREAD_LOCAL Impl *context_;
+};
+
+template <class Impl>
+TD_THREAD_LOCAL Impl *Context<Impl>::context_;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/DecTree.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/DecTree.h
new file mode 100644
index 0000000000..6044842f69
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/DecTree.h
@@ -0,0 +1,216 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Random.h"
+
+#include <functional>
+#include <utility>
+
+namespace td {
+
+template <typename KeyType, typename ValueType, typename Compare = std::less<KeyType>>
+class DecTree {
+ struct Node {
+ unique_ptr<Node> left_;
+ unique_ptr<Node> right_;
+ size_t size_;
+ KeyType key_;
+ ValueType value_;
+ uint32 y_;
+
+ void relax() {
+ size_ = 1;
+ if (left_ != nullptr) {
+ size_ += left_->size_;
+ }
+ if (right_ != nullptr) {
+ size_ += right_->size_;
+ }
+ }
+
+ Node(KeyType key, ValueType value, uint32 y) : size_(1), key_(std::move(key)), value_(std::move(value)), y_(y) {
+ }
+ };
+ unique_ptr<Node> root_;
+
+ static unique_ptr<Node> create_node(KeyType key, ValueType value, uint32 y) {
+ return make_unique<Node>(std::move(key), std::move(value), y);
+ }
+
+ static unique_ptr<Node> insert_node(unique_ptr<Node> Tree, KeyType key, ValueType value, uint32 y) {
+ if (Tree == nullptr) {
+ return create_node(std::move(key), std::move(value), y);
+ }
+ if (Tree->y_ < y) {
+ auto P = split_node(std::move(Tree), key);
+ auto T = create_node(std::move(key), std::move(value), y);
+ T->left_ = std::move(P.first);
+ T->right_ = std::move(P.second);
+ T->relax();
+ return T;
+ }
+ if (Compare()(key, Tree->key_)) {
+ Tree->left_ = insert_node(std::move(Tree->left_), std::move(key), std::move(value), y);
+ } else if (Compare()(Tree->key_, key)) {
+ Tree->right_ = insert_node(std::move(Tree->right_), std::move(key), std::move(value), y);
+ } else {
+ // ?? assert
+ }
+ Tree->relax();
+ return Tree;
+ }
+
+ static unique_ptr<Node> remove_node(unique_ptr<Node> Tree, const KeyType &key) {
+ if (Tree == nullptr) {
+ // ?? assert
+ return nullptr;
+ }
+ if (Compare()(key, Tree->key_)) {
+ Tree->left_ = remove_node(std::move(Tree->left_), key);
+ } else if (Compare()(Tree->key_, key)) {
+ Tree->right_ = remove_node(std::move(Tree->right_), key);
+ } else {
+ Tree = merge_node(std::move(Tree->left_), std::move(Tree->right_));
+ }
+ if (Tree != nullptr) {
+ Tree->relax();
+ }
+ return Tree;
+ }
+
+ static ValueType *get_node(unique_ptr<Node> &Tree, const KeyType &key) {
+ if (Tree == nullptr) {
+ return nullptr;
+ }
+ if (Compare()(key, Tree->key_)) {
+ return get_node(Tree->left_, key);
+ } else if (Compare()(Tree->key_, key)) {
+ return get_node(Tree->right_, key);
+ } else {
+ return &Tree->value_;
+ }
+ }
+
+ static ValueType *get_node_by_idx(unique_ptr<Node> &Tree, size_t idx) {
+ CHECK(Tree != nullptr);
+ auto s = (Tree->left_ != nullptr) ? Tree->left_->size_ : 0;
+ if (idx < s) {
+ return get_node_by_idx(Tree->left_, idx);
+ } else if (idx == s) {
+ return &Tree->value_;
+ } else {
+ return get_node_by_idx(Tree->right_, idx - s - 1);
+ }
+ }
+
+ static const ValueType *get_node(const unique_ptr<Node> &Tree, const KeyType &key) {
+ if (Tree == nullptr) {
+ return nullptr;
+ }
+ if (Compare()(key, Tree->key_)) {
+ return get_node(Tree->left_, key);
+ } else if (Compare()(Tree->key_, key)) {
+ return get_node(Tree->right_, key);
+ } else {
+ return &Tree->value_;
+ }
+ }
+
+ static const ValueType *get_node_by_idx(const unique_ptr<Node> &Tree, size_t idx) {
+ CHECK(Tree != nullptr);
+ auto s = (Tree->left_ != nullptr) ? Tree->left_->size_ : 0;
+ if (idx < s) {
+ return get_node_by_idx(Tree->left_, idx);
+ } else if (idx == s) {
+ return &Tree->value_;
+ } else {
+ return get_node_by_idx(Tree->right_, idx - s - 1);
+ }
+ }
+
+ static std::pair<unique_ptr<Node>, unique_ptr<Node>> split_node(unique_ptr<Node> Tree, const KeyType &key) {
+ if (Tree == nullptr) {
+ return {nullptr, nullptr};
+ }
+ if (Compare()(key, Tree->key_)) {
+ auto P = split_node(std::move(Tree->left_), key);
+ Tree->left_ = std::move(P.second);
+ Tree->relax();
+ P.second = std::move(Tree);
+ return P;
+ } else {
+ auto P = split_node(std::move(Tree->right_), key);
+ Tree->right_ = std::move(P.first);
+ Tree->relax();
+ P.first = std::move(Tree);
+ return P;
+ }
+ }
+
+ static unique_ptr<Node> merge_node(unique_ptr<Node> left, unique_ptr<Node> right) {
+ if (left == nullptr) {
+ return right;
+ }
+ if (right == nullptr) {
+ return left;
+ }
+ if (left->y_ < right->y_) {
+ right->left_ = merge_node(std::move(left), std::move(right->left_));
+ right->relax();
+ return right;
+ } else {
+ left->right_ = merge_node(std::move(left->right_), std::move(right));
+ left->relax();
+ return left;
+ }
+ }
+
+ public:
+ size_t size() const {
+ if (root_ == nullptr) {
+ return 0;
+ } else {
+ return root_->size_;
+ }
+ }
+ void insert(KeyType key, ValueType value) {
+ root_ = insert_node(std::move(root_), std::move(key), std::move(value), Random::fast_uint32());
+ }
+ void remove(const KeyType &key) {
+ root_ = remove_node(std::move(root_), key);
+ }
+ void reset() {
+ root_ = nullptr;
+ }
+ ValueType *get(const KeyType &key) {
+ return get_node(root_, key);
+ }
+ ValueType *get_random() {
+ if (size() == 0) {
+ return nullptr;
+ } else {
+ return get_node_by_idx(root_, Random::fast_uint32() % size());
+ }
+ }
+ const ValueType *get(const KeyType &key) const {
+ return get_node(root_, key);
+ }
+ const ValueType *get_random() const {
+ if (size() == 0) {
+ return nullptr;
+ } else {
+ return get_node_by_idx(root_, Random::fast_uint32() % size());
+ }
+ }
+ bool exists(const KeyType &key) const {
+ return get_node(root_, key) != nullptr;
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Destructor.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Destructor.h
new file mode 100644
index 0000000000..ced4a8eca8
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Destructor.h
@@ -0,0 +1,52 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#include <memory>
+#include <utility>
+
+namespace td {
+
+class Destructor {
+ public:
+ Destructor() = default;
+ Destructor(const Destructor &other) = delete;
+ Destructor &operator=(const Destructor &other) = delete;
+ Destructor(Destructor &&other) = default;
+ Destructor &operator=(Destructor &&other) = default;
+ virtual ~Destructor() = default;
+};
+
+template <class F>
+class LambdaDestructor final : public Destructor {
+ public:
+ explicit LambdaDestructor(F &&f) : f_(std::move(f)) {
+ }
+ LambdaDestructor(const LambdaDestructor &other) = delete;
+ LambdaDestructor &operator=(const LambdaDestructor &other) = delete;
+ LambdaDestructor(LambdaDestructor &&other) = default;
+ LambdaDestructor &operator=(LambdaDestructor &&other) = default;
+ ~LambdaDestructor() final {
+ f_();
+ }
+
+ private:
+ F f_;
+};
+
+template <class F>
+auto create_destructor(F &&f) {
+ return make_unique<LambdaDestructor<F>>(std::forward<F>(f));
+}
+template <class F>
+auto create_shared_destructor(F &&f) {
+ return std::make_shared<LambdaDestructor<F>>(std::forward<F>(f));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Enumerator.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Enumerator.h
index ca7c0493ff..367bc9fa8c 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Enumerator.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Enumerator.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,9 +7,9 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/logging.h"
-#include "td/utils/misc.h"
+#include "td/utils/WaitFreeVector.h"
+#include <limits>
#include <map>
#include <tuple>
@@ -21,7 +21,8 @@ class Enumerator {
using Key = int32;
Key add(ValueT v) {
- int32 next_id = narrow_cast<int32>(arr_.size() + 1);
+ CHECK(arr_.size() < static_cast<size_t>(std::numeric_limits<int32>::max() - 1));
+ auto next_id = static_cast<int32>(arr_.size() + 1);
bool was_inserted;
decltype(map_.begin()) it;
std::tie(it, was_inserted) = map_.emplace(std::move(v), next_id);
@@ -37,9 +38,18 @@ class Enumerator {
return *arr_[pos];
}
+ size_t size() const {
+ CHECK(map_.size() == arr_.size());
+ return arr_.size();
+ }
+
+ bool empty() const {
+ return size() == 0;
+ }
+
private:
std::map<ValueT, int32> map_;
- std::vector<const ValueT *> arr_;
+ WaitFreeVector<const ValueT *> arr_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/EpochBasedMemoryReclamation.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/EpochBasedMemoryReclamation.h
new file mode 100644
index 0000000000..a11d307672
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/EpochBasedMemoryReclamation.h
@@ -0,0 +1,201 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/sleep.h"
+
+#include <atomic>
+#include <memory>
+
+namespace td {
+
+template <class T>
+class EpochBasedMemoryReclamation {
+ public:
+ EpochBasedMemoryReclamation(const EpochBasedMemoryReclamation &other) = delete;
+ EpochBasedMemoryReclamation &operator=(const EpochBasedMemoryReclamation &other) = delete;
+ EpochBasedMemoryReclamation(EpochBasedMemoryReclamation &&other) = delete;
+ EpochBasedMemoryReclamation &operator=(EpochBasedMemoryReclamation &&other) = delete;
+ ~EpochBasedMemoryReclamation() = default;
+
+ class Locker {
+ public:
+ Locker(size_t thread_id, EpochBasedMemoryReclamation *ebmr) : thread_id_(thread_id), ebmr_(ebmr) {
+ }
+ Locker(const Locker &other) = delete;
+ Locker &operator=(const Locker &other) = delete;
+ Locker(Locker &&other) = default;
+ Locker &operator=(Locker &&other) = delete;
+
+ ~Locker() {
+ if (ebmr_) {
+ retire_sync();
+ unlock();
+ (void)ebmr_.release();
+ }
+ }
+ void lock() {
+ DCHECK(ebmr_);
+ ebmr_->lock(thread_id_);
+ }
+ void unlock() {
+ DCHECK(ebmr_);
+ ebmr_->unlock(thread_id_);
+ }
+
+ void retire_sync() {
+ ebmr_->retire_sync(thread_id_);
+ }
+
+ void retire() {
+ ebmr_->retire(thread_id_);
+ }
+
+ void retire(T *ptr) {
+ ebmr_->retire(thread_id_, ptr);
+ }
+
+ private:
+ size_t thread_id_;
+ struct Never {
+ template <class S>
+ void operator()(S *) const {
+ UNREACHABLE();
+ }
+ };
+ std::unique_ptr<EpochBasedMemoryReclamation, Never> ebmr_;
+ };
+
+ explicit EpochBasedMemoryReclamation(size_t threads_n) : threads_(threads_n) {
+ }
+
+ Locker get_locker(size_t thread_id) {
+ return Locker{thread_id, this};
+ }
+
+ size_t to_delete_size_unsafe() const {
+ size_t res = 0;
+ for (auto &thread_data : threads_) {
+ // LOG(ERROR) << "---" << thread_data.epoch.load() / 2;
+ for (size_t i = 0; i < MAX_BAGS; i++) {
+ res += thread_data.to_delete[i].size();
+ // LOG(ERROR) << thread_data.to_delete[i].size();
+ }
+ }
+ return res;
+ }
+
+ private:
+ static constexpr size_t MAX_BAGS = 3;
+ struct ThreadData {
+ std::atomic<int64> epoch{1};
+ char pad[TD_CONCURRENCY_PAD - sizeof(std::atomic<int64>)];
+
+ size_t to_skip{0};
+ size_t checked_thread_i{0};
+ size_t bag_i{0};
+ std::vector<unique_ptr<T>> to_delete[MAX_BAGS];
+ char pad2[TD_CONCURRENCY_PAD - sizeof(std::vector<unique_ptr<T>>) * MAX_BAGS];
+
+ void rotate_bags() {
+ bag_i = (bag_i + 1) % MAX_BAGS;
+ to_delete[bag_i].clear();
+ }
+
+ void set_epoch(int64 new_epoch) {
+ //LOG(ERROR) << new_epoch;
+ if (epoch.load(std::memory_order_relaxed) / 2 != new_epoch) {
+ checked_thread_i = 0;
+ to_skip = 0;
+ rotate_bags();
+ }
+ epoch = new_epoch * 2;
+ }
+
+ void idle() {
+ epoch.store(epoch.load(std::memory_order_relaxed) | 1);
+ }
+
+ size_t undeleted() const {
+ size_t res = 0;
+ for (size_t i = 0; i < MAX_BAGS; i++) {
+ res += to_delete[i].size();
+ }
+ return res;
+ }
+ };
+ std::vector<ThreadData> threads_;
+ char pad[TD_CONCURRENCY_PAD - sizeof(std::vector<ThreadData>)];
+
+ std::atomic<int64> epoch_{1};
+ char pad2[TD_CONCURRENCY_PAD - sizeof(std::atomic<int64>)];
+
+ void lock(size_t thread_id) {
+ auto &data = threads_[thread_id];
+ auto epoch = epoch_.load();
+ data.set_epoch(epoch);
+
+ if (data.to_skip == 0) {
+ data.to_skip = 30;
+ step_check(data);
+ } else {
+ data.to_skip--;
+ }
+ }
+
+ void unlock(size_t thread_id) {
+ //LOG(ERROR) << "UNLOCK";
+ auto &data = threads_[thread_id];
+ data.idle();
+ }
+
+ bool step_check(ThreadData &data) {
+ auto epoch = data.epoch.load(std::memory_order_relaxed) / 2;
+ auto checked_thread_epoch = threads_[data.checked_thread_i].epoch.load();
+ if (checked_thread_epoch % 2 == 1 || checked_thread_epoch / 2 == epoch) {
+ data.checked_thread_i++;
+ if (data.checked_thread_i == threads_.size()) {
+ if (epoch_.compare_exchange_strong(epoch, epoch + 1)) {
+ data.set_epoch(epoch + 1);
+ } else {
+ data.set_epoch(epoch);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ void retire_sync(size_t thread_id) {
+ auto &data = threads_[thread_id];
+
+ while (true) {
+ retire(thread_id);
+ data.idle();
+ if (data.undeleted() == 0) {
+ break;
+ }
+ usleep_for(1000);
+ }
+ }
+
+ void retire(size_t thread_id) {
+ auto &data = threads_[thread_id];
+ data.set_epoch(epoch_.load());
+ while (step_check(data) && data.undeleted() != 0) {
+ }
+ }
+
+ void retire(size_t thread_id, T *ptr) {
+ auto &data = threads_[thread_id];
+ data.to_delete[data.bag_i].push_back(unique_ptr<T>{ptr});
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.cpp
new file mode 100644
index 0000000000..3a410bd0d5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.cpp
@@ -0,0 +1,20 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/ExitGuard.h"
+
+#include "td/utils/logging.h"
+
+namespace td {
+
+std::atomic<bool> ExitGuard::is_exited_{false};
+
+ExitGuard::~ExitGuard() {
+ is_exited_.store(true, std::memory_order_relaxed);
+ set_verbosity_level(VERBOSITY_NAME(FATAL));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.h
new file mode 100644
index 0000000000..dd721fb5b9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.h
@@ -0,0 +1,30 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include <atomic>
+
+namespace td {
+
+class ExitGuard {
+ public:
+ ExitGuard() = default;
+ ExitGuard(ExitGuard &&) = delete;
+ ExitGuard &operator=(ExitGuard &&) = delete;
+ ExitGuard(const ExitGuard &) = delete;
+ ExitGuard &operator=(const ExitGuard &) = delete;
+ ~ExitGuard();
+
+ static bool is_exited() {
+ return is_exited_.load(std::memory_order_relaxed);
+ }
+
+ private:
+ static std::atomic<bool> is_exited_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.cpp
index e3c84f1713..a16731442a 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,85 +8,133 @@
#include "td/utils/common.h"
#include "td/utils/logging.h"
-#include "td/utils/port/Fd.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h"
+#include "td/utils/port/StdStreams.h"
+#include "td/utils/port/thread_local.h"
#include "td/utils/Slice.h"
-
-#include <limits>
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Time.h"
namespace td {
-bool FileLog::init(string path, int64 rotate_threshold) {
+Status FileLog::init(string path, int64 rotate_threshold, bool redirect_stderr) {
+ if (path.empty()) {
+ return Status::Error("Log file path must be non-empty");
+ }
if (path == path_) {
set_rotate_threshold(rotate_threshold);
- return true;
+ return Status::OK();
}
- auto r_fd = FileFd::open(path, FileFd::Create | FileFd::Write | FileFd::Append);
- if (r_fd.is_error()) {
- LOG(ERROR) << "Can't open log: " << r_fd.error();
- return false;
- }
+ TRY_RESULT(fd, FileFd::open(path, FileFd::Create | FileFd::Write | FileFd::Append));
fd_.close();
- fd_ = r_fd.move_as_ok();
- Fd::duplicate(fd_.get_fd(), Fd::Stderr()).ignore();
+ fd_ = std::move(fd);
+ if (!Stderr().empty() && redirect_stderr) {
+ fd_.get_native_fd().duplicate(Stderr().get_native_fd()).ignore();
+ }
- path_ = std::move(path);
- size_ = fd_.get_size();
+ auto r_path = realpath(path, true);
+ if (r_path.is_error()) {
+ path_ = std::move(path);
+ } else {
+ path_ = r_path.move_as_ok();
+ }
+ TRY_RESULT_ASSIGN(size_, fd_.get_size());
rotate_threshold_ = rotate_threshold;
- return true;
+ redirect_stderr_ = redirect_stderr;
+ return Status::OK();
+}
+
+Slice FileLog::get_path() const {
+ return path_;
+}
+
+vector<string> FileLog::get_file_paths() {
+ vector<string> result;
+ if (!path_.empty()) {
+ result.push_back(path_);
+ result.push_back(PSTRING() << path_ << ".old");
+ }
+ return result;
}
void FileLog::set_rotate_threshold(int64 rotate_threshold) {
rotate_threshold_ = rotate_threshold;
}
-void FileLog::append(CSlice cslice, int log_level) {
- Slice slice = cslice;
+int64 FileLog::get_rotate_threshold() const {
+ return rotate_threshold_;
+}
+
+bool FileLog::get_redirect_stderr() const {
+ return redirect_stderr_;
+}
+
+void FileLog::do_append(int log_level, CSlice slice) {
+ auto start_time = Time::now();
+ if (size_ > rotate_threshold_ || want_rotate_.load(std::memory_order_relaxed)) {
+ auto status = rename(path_, PSLICE() << path_ << ".old");
+ if (status.is_error()) {
+ process_fatal_error(PSLICE() << status << " in " << __FILE__ << " at " << __LINE__ << '\n');
+ }
+ do_after_rotation();
+ }
while (!slice.empty()) {
+ if (redirect_stderr_) {
+ while (has_log_guard()) {
+ // spin
+ }
+ }
auto r_size = fd_.write(slice);
if (r_size.is_error()) {
- process_fatal_error(r_size.error().message());
+ process_fatal_error(PSLICE() << r_size.error() << " in " << __FILE__ << " at " << __LINE__ << '\n');
}
auto written = r_size.ok();
size_ += static_cast<int64>(written);
slice.remove_prefix(written);
}
- if (log_level == VERBOSITY_NAME(FATAL)) {
- process_fatal_error(cslice);
- }
-
- if (size_ > rotate_threshold_) {
- auto status = rename(path_, path_ + ".old");
- if (status.is_error()) {
- process_fatal_error(status.message());
- }
- do_rotate();
+ auto total_time = Time::now() - start_time;
+ if (total_time >= 0.1 && log_level >= 1) {
+ auto thread_id = get_thread_id();
+ auto r_size = fd_.write(PSLICE() << "[ 1][t" << (0 <= thread_id && thread_id < 10 ? " " : "") << thread_id
+ << "] !!! Previous logging took " << total_time << " seconds !!!\n");
+ r_size.ignore();
}
}
-void FileLog::rotate() {
+void FileLog::after_rotation() {
if (path_.empty()) {
return;
}
- do_rotate();
+ do_after_rotation();
+}
+
+void FileLog::lazy_rotate() {
+ want_rotate_ = true;
}
-void FileLog::do_rotate() {
- auto current_verbosity_level = GET_VERBOSITY_LEVEL();
- SET_VERBOSITY_LEVEL(std::numeric_limits<int>::min()); // to ensure that nothing will be printed to the closed log
+void FileLog::do_after_rotation() {
+ want_rotate_ = false;
+ ScopedDisableLog disable_log; // to ensure that nothing will be printed to the closed log
CHECK(!path_.empty());
fd_.close();
- auto r_fd = FileFd::open(path_, FileFd::Create | FileFd::Truncate | FileFd::Write);
+ auto r_fd = FileFd::open(path_, FileFd::Create | FileFd::Write | FileFd::Append);
if (r_fd.is_error()) {
- process_fatal_error(r_fd.error().message());
+ process_fatal_error(PSLICE() << r_fd.error() << " in " << __FILE__ << " at " << __LINE__ << '\n');
}
fd_ = r_fd.move_as_ok();
- Fd::duplicate(fd_.get_fd(), Fd::Stderr()).ignore();
+ if (!Stderr().empty() && redirect_stderr_) {
+ fd_.get_native_fd().duplicate(Stderr().get_native_fd()).ignore();
+ }
size_ = 0;
- SET_VERBOSITY_LEVEL(current_verbosity_level);
+}
+
+Result<unique_ptr<LogInterface>> FileLog::create(string path, int64 rotate_threshold, bool redirect_stderr) {
+ auto l = make_unique<FileLog>();
+ TRY_STATUS(l->init(std::move(path), rotate_threshold, redirect_stderr));
+ return std::move(l);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.h
index 12e9d1479a..ad4ec5eb02 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,28 +10,45 @@
#include "td/utils/logging.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+#include <atomic>
namespace td {
-class FileLog : public LogInterface {
+class FileLog final : public LogInterface {
static constexpr int64 DEFAULT_ROTATE_THRESHOLD = 10 * (1 << 20);
public:
- bool init(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD);
+ static Result<unique_ptr<LogInterface>> create(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD,
+ bool redirect_stderr = true);
+ Status init(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD, bool redirect_stderr = true);
+
+ Slice get_path() const;
+
+ vector<string> get_file_paths() final;
void set_rotate_threshold(int64 rotate_threshold);
- void append(CSlice cslice, int log_level) override;
+ int64 get_rotate_threshold() const;
- void rotate() override;
+ bool get_redirect_stderr() const;
+
+ void after_rotation() final;
+
+ void lazy_rotate();
private:
FileFd fd_;
string path_;
- int64 size_;
- int64 rotate_threshold_;
+ int64 size_ = 0;
+ int64 rotate_threshold_ = 0;
+ bool redirect_stderr_ = false;
+ std::atomic<bool> want_rotate_{false};
+
+ void do_append(int log_level, CSlice slice) final;
- void do_rotate();
+ void do_after_rotation();
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMap.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMap.h
new file mode 100644
index 0000000000..51aa6d3e4c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMap.h
@@ -0,0 +1,24 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+//#include "td/utils/FlatHashMapChunks.h"
+#include "td/utils/FlatHashTable.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/MapNode.h"
+
+#include <functional>
+//#include <unordered_map>
+
+namespace td {
+
+template <class KeyT, class ValueT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>>
+using FlatHashMap = FlatHashTable<MapNode<KeyT, ValueT>, HashT, EqT>;
+//using FlatHashMap = FlatHashMapChunks<KeyT, ValueT, HashT, EqT>;
+//using FlatHashMap = std::unordered_map<KeyT, ValueT, HashT, EqT>;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMapChunks.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMapChunks.h
new file mode 100644
index 0000000000..6df4842bc1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMapChunks.h
@@ -0,0 +1,575 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/bits.h"
+#include "td/utils/common.h"
+#include "td/utils/fixed_vector.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/MapNode.h"
+#include "td/utils/SetNode.h"
+
+#include <cstddef>
+#include <functional>
+#include <initializer_list>
+#include <iterator>
+#include <limits>
+#include <utility>
+
+#if defined(__SSE2__) || (TD_MSVC && (defined(_M_X64) || (defined(_M_IX86) && _M_IX86_FP >= 2)))
+#define TD_SSE2 1
+#endif
+
+#ifdef __aarch64__
+#include <arm_neon.h>
+#endif
+
+#if TD_SSE2
+#include <emmintrin.h>
+#endif
+
+namespace td {
+template <int shift>
+struct MaskIterator {
+ uint64 mask;
+ explicit operator bool() const noexcept {
+ return mask != 0;
+ }
+ int pos() const {
+ return count_trailing_zeroes64(mask) / shift;
+ }
+ void next() {
+ mask &= mask - 1;
+ }
+
+ // For foreach
+ bool operator!=(MaskIterator &other) const {
+ return mask != other.mask;
+ }
+ auto operator*() const {
+ return pos();
+ }
+ void operator++() {
+ next();
+ }
+ auto begin() {
+ return *this;
+ }
+ auto end() {
+ return MaskIterator{0u};
+ }
+};
+
+struct MaskPortable {
+ static MaskIterator<1> equal_mask(uint8 *bytes, uint8 needle) {
+ uint64 res = 0;
+ for (int i = 0; i < 16; i++) {
+ res |= (bytes[i] == needle) << i;
+ }
+ return {res & ((1u << 14) - 1)};
+ }
+};
+
+#ifdef __aarch64__
+struct MaskNeonFolly {
+ static MaskIterator<4> equal_mask(uint8 *bytes, uint8 needle) {
+ uint8x16_t input_mask = vld1q_u8(bytes);
+ auto needle_mask = vdupq_n_u8(needle);
+ auto eq_mask = vceqq_u8(input_mask, needle_mask);
+ // get info from every byte into the bottom half of every uint16
+ // by shifting right 4, then round to get it into a 64-bit vector
+ uint8x8_t shifted_eq_mask = vshrn_n_u16(vreinterpretq_u16_u8(eq_mask), 4);
+ uint64 mask = vget_lane_u64(vreinterpret_u64_u8(shifted_eq_mask), 0);
+ return {mask & 0x11111111111111};
+ }
+};
+
+struct MaskNeon {
+ static MaskIterator<1> equal_mask(uint8 *bytes, uint8 needle) {
+ uint8x16_t input_mask = vld1q_u8(bytes);
+ auto needle_mask = vdupq_n_u8(needle);
+ auto eq_mask = vceqq_u8(input_mask, needle_mask);
+ uint16x8_t MASK = vdupq_n_u16(0x180);
+ uint16x8_t a_masked = vandq_u16(vreinterpretq_u16_u8(eq_mask), MASK);
+ const int16 __attribute__((aligned(16))) SHIFT_ARR[8] = {-7, -5, -3, -1, 1, 3, 5, 7};
+ int16x8_t SHIFT = vld1q_s16(SHIFT_ARR);
+ uint16x8_t a_shifted = vshlq_u16(a_masked, SHIFT);
+ return {vaddvq_u16(a_shifted) & ((1u << 14) - 1)};
+ }
+};
+#elif TD_SSE2
+struct MaskSse2 {
+ static MaskIterator<1> equal_mask(uint8 *bytes, uint8 needle) {
+ auto input_mask = _mm_loadu_si128(reinterpret_cast<const __m128i *>(bytes));
+ auto needle_mask = _mm_set1_epi8(needle);
+ auto match_mask = _mm_cmpeq_epi8(needle_mask, input_mask);
+ return {static_cast<uint32>(_mm_movemask_epi8(match_mask)) & ((1u << 14) - 1)};
+ }
+};
+#endif
+
+#ifdef __aarch64__
+using MaskHelper = MaskNeonFolly;
+#elif TD_SSE2
+using MaskHelper = MaskSse2;
+#else
+using MaskHelper = MaskPortable;
+#endif
+
+template <class NodeT, class HashT, class EqT>
+class FlatHashTableChunks {
+ public:
+ using Self = FlatHashTableChunks<NodeT, HashT, EqT>;
+ using Node = NodeT;
+ using NodeIterator = typename fixed_vector<Node>::iterator;
+ using ConstNodeIterator = typename fixed_vector<Node>::const_iterator;
+
+ using KeyT = typename Node::public_key_type;
+ using key_type = typename Node::public_key_type;
+ using value_type = typename Node::public_type;
+
+ struct Iterator {
+ using iterator_category = std::bidirectional_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = FlatHashTableChunks::value_type;
+ using pointer = value_type *;
+ using reference = value_type &;
+
+ friend class FlatHashTableChunks;
+ Iterator &operator++() {
+ do {
+ ++it_;
+ } while (it_ != map_->nodes_.end() && it_->empty());
+ return *this;
+ }
+ Iterator &operator--() {
+ do {
+ --it_;
+ } while (it_->empty());
+ return *this;
+ }
+ reference operator*() {
+ return it_->get_public();
+ }
+ pointer operator->() {
+ return &it_->get_public();
+ }
+ bool operator==(const Iterator &other) const {
+ DCHECK(map_ == other.map_);
+ return it_ == other.it_;
+ }
+ bool operator!=(const Iterator &other) const {
+ DCHECK(map_ == other.map_);
+ return it_ != other.it_;
+ }
+
+ Iterator() = default;
+ Iterator(NodeIterator it, Self *map) : it_(std::move(it)), map_(map) {
+ }
+
+ private:
+ NodeIterator it_;
+ Self *map_;
+ };
+
+ struct ConstIterator {
+ using iterator_category = std::bidirectional_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = FlatHashTableChunks::value_type;
+ using pointer = const value_type *;
+ using reference = const value_type &;
+
+ friend class FlatHashTableChunks;
+ ConstIterator &operator++() {
+ ++it_;
+ return *this;
+ }
+ ConstIterator &operator--() {
+ --it_;
+ return *this;
+ }
+ reference operator*() {
+ return *it_;
+ }
+ pointer operator->() {
+ return &*it_;
+ }
+ bool operator==(const ConstIterator &other) const {
+ return it_ == other.it_;
+ }
+ bool operator!=(const ConstIterator &other) const {
+ return it_ != other.it_;
+ }
+
+ ConstIterator() = default;
+ ConstIterator(Iterator it) : it_(std::move(it)) {
+ }
+
+ private:
+ Iterator it_;
+ };
+ using iterator = Iterator;
+ using const_iterator = ConstIterator;
+
+ FlatHashTableChunks() = default;
+ FlatHashTableChunks(const FlatHashTableChunks &other) {
+ assign(other);
+ }
+ FlatHashTableChunks &operator=(const FlatHashTableChunks &other) {
+ clear();
+ assign(other);
+ return *this;
+ }
+
+ FlatHashTableChunks(std::initializer_list<Node> nodes) {
+ reserve(nodes.size());
+ for (auto &new_node : nodes) {
+ CHECK(!new_node.empty());
+ if (count(new_node.key()) > 0) {
+ continue;
+ }
+ Node node;
+ node.copy_from(new_node);
+ emplace_node(std::move(node));
+ }
+ }
+
+ FlatHashTableChunks(FlatHashTableChunks &&other) noexcept {
+ swap(other);
+ }
+ FlatHashTableChunks &operator=(FlatHashTableChunks &&other) noexcept {
+ swap(other);
+ return *this;
+ }
+ void swap(FlatHashTableChunks &other) noexcept {
+ nodes_.swap(other.nodes_);
+ chunks_.swap(other.chunks_);
+ std::swap(used_nodes_, other.used_nodes_);
+ }
+ ~FlatHashTableChunks() = default;
+
+ size_t bucket_count() const {
+ return nodes_.size();
+ }
+
+ Iterator find(const KeyT &key) {
+ if (empty() || is_hash_table_key_empty(key)) {
+ return end();
+ }
+ const auto hash = calc_hash(key);
+ auto chunk_it = get_chunk_it(hash.chunk_i);
+ while (true) {
+ auto chunk_i = chunk_it.pos();
+ auto chunk_begin = nodes_.begin() + chunk_i * Chunk::CHUNK_SIZE;
+ //__builtin_prefetch(chunk_begin);
+ auto &chunk = chunks_[chunk_i];
+ auto mask_it = MaskHelper::equal_mask(chunk.ctrl, hash.small_hash);
+ for (auto pos : mask_it) {
+ auto it = chunk_begin + pos;
+ if (likely(EqT()(it->key(), key))) {
+ return Iterator{it, this};
+ }
+ }
+ if (chunk.skipped_cnt == 0) {
+ break;
+ }
+ chunk_it.next();
+ }
+ return end();
+ }
+
+ ConstIterator find(const KeyT &key) const {
+ return ConstIterator(const_cast<Self *>(this)->find(key));
+ }
+
+ size_t size() const {
+ return used_nodes_;
+ }
+
+ bool empty() const {
+ return size() == 0;
+ }
+
+ Iterator begin() {
+ if (empty()) {
+ return end();
+ }
+ auto it = nodes_.begin();
+ while (it->empty()) {
+ ++it;
+ }
+ return Iterator(it, this);
+ }
+ Iterator end() {
+ return Iterator(nodes_.end(), this);
+ }
+
+ ConstIterator begin() const {
+ return ConstIterator(const_cast<Self *>(this)->begin());
+ }
+ ConstIterator end() const {
+ return ConstIterator(const_cast<Self *>(this)->end());
+ }
+
+ void reserve(size_t size) {
+ //size_t want_size = normalize(size * 5 / 3 + 1);
+ size_t want_size = normalize(size * 14 / 12 + 1);
+ // size_t want_size = size * 2;
+ if (want_size > nodes_.size()) {
+ resize(want_size);
+ }
+ }
+
+ template <class... ArgsT>
+ std::pair<Iterator, bool> emplace(KeyT key, ArgsT &&...args) {
+ CHECK(!is_hash_table_key_empty(key));
+ auto it = find(key);
+ if (it != end()) {
+ return {it, false};
+ }
+ try_grow();
+
+ auto hash = calc_hash(key);
+ auto chunk_it = get_chunk_it(hash.chunk_i);
+ while (true) {
+ auto chunk_i = chunk_it.pos();
+ auto &chunk = chunks_[chunk_i];
+ auto mask_it = MaskHelper::equal_mask(chunk.ctrl, 0);
+ if (mask_it) {
+ auto shift = mask_it.pos();
+ DCHECK(chunk.ctrl[shift] == 0);
+ auto node_it = nodes_.begin() + shift + chunk_i * Chunk::CHUNK_SIZE;
+ DCHECK(node_it->empty());
+ node_it->emplace(std::move(key), std::forward<ArgsT>(args)...);
+ DCHECK(!node_it->empty());
+ chunk.ctrl[shift] = hash.small_hash;
+ used_nodes_++;
+ return {{node_it, this}, true};
+ }
+ CHECK(chunk.skipped_cnt != std::numeric_limits<uint16>::max());
+ chunk.skipped_cnt++;
+ chunk_it.next();
+ }
+ }
+
+ std::pair<Iterator, bool> insert(KeyT key) {
+ return emplace(std::move(key));
+ }
+
+ template <class ItT>
+ void insert(ItT begin, ItT end) {
+ for (; begin != end; ++begin) {
+ emplace(*begin);
+ }
+ }
+
+ template <class T = typename Node::second_type>
+ T &operator[](const KeyT &key) {
+ return emplace(key).first->second;
+ }
+
+ size_t erase(const KeyT &key) {
+ auto it = find(key);
+ if (it == end()) {
+ return 0;
+ }
+ erase(it);
+ try_shrink();
+ return 1;
+ }
+
+ size_t count(const KeyT &key) const {
+ return find(key) != end();
+ }
+
+ void clear() {
+ used_nodes_ = 0;
+ nodes_ = {};
+ chunks_ = {};
+ }
+
+ void erase(Iterator it) {
+ DCHECK(it != end());
+ DCHECK(!it.it_->empty());
+ erase_node(it.it_);
+ }
+
+ template <class F>
+ void remove_if(F &&f) {
+ for (auto it = nodes_.begin(), end = nodes_.end(); it != end; ++it) {
+ if (!it->empty() && f(it->get_public())) {
+ erase_node(it);
+ }
+ }
+ try_shrink();
+ }
+
+ private:
+ struct Chunk {
+ static constexpr int CHUNK_SIZE = 14;
+ static constexpr int MASK = (1 << CHUNK_SIZE) - 1;
+ // 0x0 - empty
+ uint8 ctrl[CHUNK_SIZE] = {};
+ uint16 skipped_cnt{0};
+ };
+ fixed_vector<Node> nodes_;
+ fixed_vector<Chunk> chunks_;
+ size_t used_nodes_{};
+
+ void assign(const FlatHashTableChunks &other) {
+ reserve(other.size());
+ for (const auto &new_node : other) {
+ Node node;
+ node.copy_from(new_node);
+ emplace_node(std::move(node));
+ }
+ }
+
+ void try_grow() {
+ if (should_grow(used_nodes_ + 1, nodes_.size())) {
+ grow();
+ }
+ }
+ static bool should_grow(size_t used_count, size_t bucket_count) {
+ return used_count * 14 > bucket_count * 12;
+ }
+ void try_shrink() {
+ if (should_shrink(used_nodes_, nodes_.size())) {
+ shrink();
+ }
+ }
+ static bool should_shrink(size_t used_count, size_t bucket_count) {
+ return used_count * 10 < bucket_count;
+ }
+
+ static size_t normalize(size_t size) {
+ auto x = (size / Chunk::CHUNK_SIZE) | 1;
+ auto y = static_cast<size_t>(1) << (64 - count_leading_zeroes64(x));
+ return y * Chunk::CHUNK_SIZE;
+ }
+
+ void shrink() {
+ size_t want_size = normalize((used_nodes_ + 1) * 5 / 3 + 1);
+ resize(want_size);
+ }
+
+ void grow() {
+ size_t want_size = normalize(2 * nodes_.size() - !nodes_.empty());
+ resize(want_size);
+ }
+
+ struct HashInfo {
+ size_t chunk_i;
+ uint8 small_hash;
+ };
+ struct ChunkIt {
+ size_t chunk_i;
+ size_t chunk_mask;
+ size_t shift;
+
+ size_t pos() const {
+ return chunk_i;
+ }
+ void next() {
+ DCHECK((chunk_mask & (chunk_mask + 1)) == 0);
+ shift++;
+ chunk_i += shift;
+ chunk_i &= chunk_mask;
+ }
+ };
+
+ ChunkIt get_chunk_it(size_t chunk_i) {
+ return ChunkIt{chunk_i, chunks_.size() - 1, 0};
+ }
+
+ HashInfo calc_hash(const KeyT &key) {
+ auto h = HashT()(key);
+ return {(h >> 8) % chunks_.size(), static_cast<uint8>(0x80 | h)};
+ }
+
+ void resize(size_t new_size) {
+ CHECK(new_size >= Chunk::CHUNK_SIZE);
+ fixed_vector<Node> old_nodes(new_size);
+ fixed_vector<Chunk> chunks(new_size / Chunk::CHUNK_SIZE);
+ old_nodes.swap(nodes_);
+ chunks_ = std::move(chunks);
+ used_nodes_ = 0;
+
+ for (auto &node : old_nodes) {
+ if (node.empty()) {
+ continue;
+ }
+ emplace_node(std::move(node));
+ }
+ }
+
+ void emplace_node(Node &&node) {
+ DCHECK(!node.empty());
+ auto hash = calc_hash(node.key());
+ auto chunk_it = get_chunk_it(hash.chunk_i);
+ while (true) {
+ auto chunk_i = chunk_it.pos();
+ auto &chunk = chunks_[chunk_i];
+ auto mask_it = MaskHelper::equal_mask(chunk.ctrl, 0);
+ if (mask_it) {
+ auto shift = mask_it.pos();
+ auto node_it = nodes_.begin() + shift + chunk_i * Chunk::CHUNK_SIZE;
+ DCHECK(node_it->empty());
+ *node_it = std::move(node);
+ DCHECK(chunk.ctrl[shift] == 0);
+ chunk.ctrl[shift] = hash.small_hash;
+ DCHECK(chunk.ctrl[shift] != 0);
+ used_nodes_++;
+ break;
+ }
+ CHECK(chunk.skipped_cnt != std::numeric_limits<uint16>::max());
+ chunk.skipped_cnt++;
+ chunk_it.next();
+ }
+ }
+
+ void next_bucket(size_t &bucket) const {
+ bucket++;
+ if (unlikely(bucket == nodes_.size())) {
+ bucket = 0;
+ }
+ }
+
+ void erase_node(NodeIterator it) {
+ DCHECK(!it->empty());
+ size_t empty_i = it - nodes_.begin();
+ DCHECK(0 <= empty_i && empty_i < nodes_.size());
+ auto empty_chunk_i = empty_i / Chunk::CHUNK_SIZE;
+ auto hash = calc_hash(it->key());
+ auto chunk_it = get_chunk_it(hash.chunk_i);
+ while (true) {
+ auto chunk_i = chunk_it.pos();
+ auto &chunk = chunks_[chunk_i];
+ if (chunk_i == empty_chunk_i) {
+ chunk.ctrl[empty_i - empty_chunk_i * Chunk::CHUNK_SIZE] = 0;
+ break;
+ }
+ chunk.skipped_cnt--;
+ chunk_it.next();
+ }
+ it->clear();
+ used_nodes_--;
+ }
+};
+
+template <class KeyT, class ValueT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>>
+using FlatHashMapChunks = FlatHashTableChunks<MapNode<KeyT, ValueT>, HashT, EqT>;
+
+template <class KeyT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>>
+using FlatHashSetChunks = FlatHashTableChunks<SetNode<KeyT>, HashT, EqT>;
+
+template <class NodeT, class HashT, class EqT, class FuncT>
+void table_remove_if(FlatHashTableChunks<NodeT, HashT, EqT> &table, FuncT &&func) {
+ table.remove_if(func);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashSet.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashSet.h
new file mode 100644
index 0000000000..385485979a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashSet.h
@@ -0,0 +1,24 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+//#include "td/utils/FlatHashMapChunks.h"
+#include "td/utils/FlatHashTable.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/SetNode.h"
+
+#include <functional>
+//#include <unordered_set>
+
+namespace td {
+
+template <class KeyT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>>
+using FlatHashSet = FlatHashTable<SetNode<KeyT>, HashT, EqT>;
+//using FlatHashSet = FlatHashSetChunks<KeyT, HashT, EqT>;
+//using FlatHashSet = std::unordered_set<KeyT, HashT, EqT>;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.cpp
new file mode 100644
index 0000000000..abe7afa764
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.cpp
@@ -0,0 +1,24 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/FlatHashTable.h"
+
+#include "td/utils/bits.h"
+#include "td/utils/Random.h"
+
+namespace td {
+namespace detail {
+
+uint32 normalize_flat_hash_table_size(uint32 size) {
+ return td::max(static_cast<uint32>(1) << (32 - count_leading_zeroes32(size)), static_cast<uint32>(8));
+}
+
+uint32 get_random_flat_hash_table_bucket(uint32 bucket_count_mask) {
+ return Random::fast_uint32() & bucket_count_mask;
+}
+
+} // namespace detail
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.h
new file mode 100644
index 0000000000..a312ad9533
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.h
@@ -0,0 +1,551 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+
+#include <cstddef>
+#include <initializer_list>
+#include <iterator>
+#include <utility>
+
+namespace td {
+
+namespace detail {
+uint32 normalize_flat_hash_table_size(uint32 size);
+uint32 get_random_flat_hash_table_bucket(uint32 bucket_count_mask);
+} // namespace detail
+
+template <class NodeT, class HashT, class EqT>
+class FlatHashTable {
+ static constexpr uint32 INVALID_BUCKET = 0xFFFFFFFF;
+
+ void allocate_nodes(uint32 size) {
+ DCHECK(size >= 8);
+ DCHECK((size & (size - 1)) == 0);
+ CHECK(size <= min(static_cast<uint32>(1) << 29, static_cast<uint32>(0x7FFFFFFF / sizeof(NodeT))));
+ nodes_ = new NodeT[size];
+ // used_node_count_ = 0;
+ bucket_count_mask_ = size - 1;
+ bucket_count_ = size;
+ begin_bucket_ = INVALID_BUCKET;
+ }
+
+ static void clear_nodes(NodeT *nodes) {
+ delete[] nodes;
+ }
+
+ public:
+ using KeyT = typename NodeT::public_key_type;
+ using key_type = typename NodeT::public_key_type;
+ using value_type = typename NodeT::public_type;
+
+ // TODO use EndSentinel for end() after switching to C++17
+ // struct EndSentinel {};
+
+ struct Iterator {
+ using iterator_category = std::forward_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = FlatHashTable::value_type;
+ using pointer = value_type *;
+ using reference = value_type &;
+
+ Iterator &operator++() {
+ DCHECK(it_ != nullptr);
+ do {
+ if (unlikely(++it_ == end_)) {
+ it_ = begin_;
+ }
+ if (unlikely(it_ == start_)) {
+ it_ = nullptr;
+ break;
+ }
+ } while (it_->empty());
+ return *this;
+ }
+ reference operator*() {
+ return it_->get_public();
+ }
+ const value_type &operator*() const {
+ return it_->get_public();
+ }
+ pointer operator->() {
+ return &it_->get_public();
+ }
+ const value_type *operator->() const {
+ return &it_->get_public();
+ }
+
+ NodeT *get() {
+ return it_;
+ }
+
+ bool operator==(const Iterator &other) const {
+ DCHECK(other.it_ == nullptr);
+ return it_ == nullptr;
+ }
+ bool operator!=(const Iterator &other) const {
+ DCHECK(other.it_ == nullptr);
+ return it_ != nullptr;
+ }
+
+ Iterator() = default;
+ Iterator(NodeT *it, NodeT *begin, NodeT *end) : it_(it), begin_(begin), start_(it), end_(end) {
+ }
+
+ private:
+ NodeT *it_ = nullptr;
+ NodeT *begin_ = nullptr;
+ NodeT *start_ = nullptr;
+ NodeT *end_ = nullptr;
+ };
+
+ struct ConstIterator {
+ using iterator_category = std::forward_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = FlatHashTable::value_type;
+ using pointer = const value_type *;
+ using reference = const value_type &;
+
+ ConstIterator &operator++() {
+ ++it_;
+ return *this;
+ }
+ reference operator*() const {
+ return *it_;
+ }
+ pointer operator->() const {
+ return &*it_;
+ }
+ bool operator==(const ConstIterator &other) const {
+ return it_ == other.it_;
+ }
+ bool operator!=(const ConstIterator &other) const {
+ return it_ != other.it_;
+ }
+
+ ConstIterator() = default;
+ ConstIterator(Iterator it) : it_(std::move(it)) {
+ }
+
+ private:
+ Iterator it_;
+ };
+ using iterator = Iterator;
+ using const_iterator = ConstIterator;
+
+ struct NodePointer {
+ value_type &operator*() {
+ return it_->get_public();
+ }
+ const value_type &operator*() const {
+ return it_->get_public();
+ }
+ value_type *operator->() {
+ return &it_->get_public();
+ }
+ const value_type *operator->() const {
+ return &it_->get_public();
+ }
+
+ NodeT *get() {
+ return it_;
+ }
+
+ bool operator==(const Iterator &) const {
+ return it_ == nullptr;
+ }
+ bool operator!=(const Iterator &) const {
+ return it_ != nullptr;
+ }
+
+ explicit NodePointer(NodeT *it) : it_(it) {
+ }
+
+ private:
+ NodeT *it_ = nullptr;
+ };
+
+ struct ConstNodePointer {
+ const value_type &operator*() const {
+ return it_->get_public();
+ }
+ const value_type *operator->() const {
+ return &it_->get_public();
+ }
+
+ bool operator==(const ConstIterator &) const {
+ return it_ == nullptr;
+ }
+ bool operator!=(const ConstIterator &) const {
+ return it_ != nullptr;
+ }
+
+ const NodeT *get() const {
+ return it_;
+ }
+
+ explicit ConstNodePointer(const NodeT *it) : it_(it) {
+ }
+
+ private:
+ const NodeT *it_ = nullptr;
+ };
+
+ FlatHashTable() = default;
+ FlatHashTable(const FlatHashTable &other) = delete;
+ FlatHashTable &operator=(const FlatHashTable &other) = delete;
+
+ FlatHashTable(std::initializer_list<NodeT> nodes) {
+ if (nodes.size() == 0) {
+ return;
+ }
+ reserve(nodes.size());
+ uint32 used_nodes = 0;
+ for (auto &new_node : nodes) {
+ CHECK(!new_node.empty());
+ auto bucket = calc_bucket(new_node.key());
+ while (true) {
+ auto &node = nodes_[bucket];
+ if (node.empty()) {
+ node.copy_from(new_node);
+ used_nodes++;
+ break;
+ }
+ if (EqT()(node.key(), new_node.key())) {
+ break;
+ }
+ next_bucket(bucket);
+ }
+ }
+ used_node_count_ = used_nodes;
+ }
+
+ FlatHashTable(FlatHashTable &&other) noexcept
+ : nodes_(other.nodes_)
+ , used_node_count_(other.used_node_count_)
+ , bucket_count_mask_(other.bucket_count_mask_)
+ , bucket_count_(other.bucket_count_)
+ , begin_bucket_(other.begin_bucket_) {
+ other.drop();
+ }
+ void operator=(FlatHashTable &&other) noexcept {
+ clear();
+ nodes_ = other.nodes_;
+ used_node_count_ = other.used_node_count_;
+ bucket_count_mask_ = other.bucket_count_mask_;
+ bucket_count_ = other.bucket_count_;
+ begin_bucket_ = other.begin_bucket_;
+ other.drop();
+ }
+ ~FlatHashTable() {
+ clear_nodes(nodes_);
+ }
+
+ void swap(FlatHashTable &other) noexcept {
+ std::swap(nodes_, other.nodes_);
+ std::swap(used_node_count_, other.used_node_count_);
+ std::swap(bucket_count_mask_, other.bucket_count_mask_);
+ std::swap(bucket_count_, other.bucket_count_);
+ std::swap(begin_bucket_, other.begin_bucket_);
+ }
+
+ uint32 bucket_count() const {
+ return bucket_count_;
+ }
+
+ NodePointer find(const KeyT &key) {
+ return NodePointer(find_impl(key));
+ }
+
+ ConstNodePointer find(const KeyT &key) const {
+ return ConstNodePointer(const_cast<FlatHashTable *>(this)->find_impl(key));
+ }
+
+ size_t size() const {
+ return used_node_count_;
+ }
+
+ bool empty() const {
+ return used_node_count_ == 0;
+ }
+
+ Iterator begin() {
+ return create_iterator(begin_impl());
+ }
+ Iterator end() {
+ return Iterator();
+ }
+ ConstIterator begin() const {
+ return ConstIterator(const_cast<FlatHashTable *>(this)->begin());
+ }
+ ConstIterator end() const {
+ return ConstIterator();
+ }
+
+ void reserve(size_t size) {
+ if (size == 0) {
+ return;
+ }
+ CHECK(size <= (1u << 29));
+ uint32 want_size = detail::normalize_flat_hash_table_size(static_cast<uint32>(size) * 5 / 3 + 1);
+ if (want_size > bucket_count()) {
+ resize(want_size);
+ }
+ }
+
+ template <class... ArgsT>
+ std::pair<NodePointer, bool> emplace(KeyT key, ArgsT &&...args) {
+ CHECK(!is_hash_table_key_empty(key));
+ if (unlikely(bucket_count_mask_ == 0)) {
+ CHECK(used_node_count_ == 0);
+ resize(8);
+ }
+ auto bucket = calc_bucket(key);
+ while (true) {
+ auto &node = nodes_[bucket];
+ if (node.empty()) {
+ if (unlikely(used_node_count_ * 5 >= bucket_count_mask_ * 3)) {
+ resize(2 * bucket_count_);
+ CHECK(used_node_count_ * 5 < bucket_count_mask_ * 3);
+ return emplace(std::move(key), std::forward<ArgsT>(args)...);
+ }
+ invalidate_iterators();
+
+ node.emplace(std::move(key), std::forward<ArgsT>(args)...);
+ used_node_count_++;
+ return {NodePointer(&node), true};
+ }
+ if (EqT()(node.key(), key)) {
+ return {NodePointer(&node), false};
+ }
+ next_bucket(bucket);
+ }
+ }
+
+ std::pair<NodePointer, bool> insert(KeyT key) {
+ return emplace(std::move(key));
+ }
+
+ template <class ItT>
+ void insert(ItT begin, ItT end) {
+ for (; begin != end; ++begin) {
+ emplace(*begin);
+ }
+ }
+
+ template <class T = typename NodeT::second_type>
+ T &operator[](const KeyT &key) {
+ return emplace(key).first->second;
+ }
+
+ size_t erase(const KeyT &key) {
+ auto *node = find_impl(key);
+ if (node == nullptr) {
+ return 0;
+ }
+ erase_node(node);
+ try_shrink();
+ return 1;
+ }
+
+ size_t count(const KeyT &key) const {
+ return const_cast<FlatHashTable *>(this)->find_impl(key) != nullptr;
+ }
+
+ void clear() {
+ if (nodes_ != nullptr) {
+ clear_nodes(nodes_);
+ drop();
+ }
+ }
+
+ void erase(Iterator it) {
+ DCHECK(it != end());
+ erase_node(it.get());
+ try_shrink();
+ }
+
+ void erase(NodePointer it) {
+ DCHECK(it != end());
+ erase_node(it.get());
+ try_shrink();
+ }
+
+ template <class F>
+ void remove_if(F &&f) {
+ if (empty()) {
+ return;
+ }
+
+ auto it = begin_impl();
+ auto end = nodes_ + bucket_count();
+ while (it != end && !it->empty()) {
+ ++it;
+ }
+ if (it == end) {
+ do {
+ --it;
+ } while (!it->empty());
+ }
+ auto first_empty = it;
+ while (it != end) {
+ if (!it->empty() && f(it->get_public())) {
+ erase_node(it);
+ } else {
+ ++it;
+ }
+ }
+ for (it = nodes_; it != first_empty;) {
+ if (!it->empty() && f(it->get_public())) {
+ erase_node(it);
+ } else {
+ ++it;
+ }
+ }
+ try_shrink();
+ }
+
+ private:
+ NodeT *nodes_ = nullptr;
+ uint32 used_node_count_ = 0;
+ uint32 bucket_count_mask_ = 0;
+ uint32 bucket_count_ = 0;
+ uint32 begin_bucket_ = 0;
+
+ void drop() {
+ nodes_ = nullptr;
+ used_node_count_ = 0;
+ bucket_count_mask_ = 0;
+ bucket_count_ = 0;
+ begin_bucket_ = 0;
+ }
+
+ NodeT *begin_impl() {
+ if (empty()) {
+ return nullptr;
+ }
+ if (begin_bucket_ == INVALID_BUCKET) {
+ begin_bucket_ = detail::get_random_flat_hash_table_bucket(bucket_count_mask_);
+ while (nodes_[begin_bucket_].empty()) {
+ next_bucket(begin_bucket_);
+ }
+ }
+ return nodes_ + begin_bucket_;
+ }
+
+ NodeT *find_impl(const KeyT &key) {
+ if (unlikely(nodes_ == nullptr) || is_hash_table_key_empty(key)) {
+ return nullptr;
+ }
+ auto bucket = calc_bucket(key);
+ while (true) {
+ auto &node = nodes_[bucket];
+ if (node.empty()) {
+ return nullptr;
+ }
+ if (EqT()(node.key(), key)) {
+ return &node;
+ }
+ next_bucket(bucket);
+ }
+ }
+
+ void try_shrink() {
+ DCHECK(nodes_ != nullptr);
+ if (unlikely(used_node_count_ * 10 < bucket_count_mask_ && bucket_count_mask_ > 7)) {
+ resize(detail::normalize_flat_hash_table_size((used_node_count_ + 1) * 5 / 3 + 1));
+ }
+ invalidate_iterators();
+ }
+
+ uint32 calc_bucket(const KeyT &key) const {
+ return HashT()(key) & bucket_count_mask_;
+ }
+
+ inline void next_bucket(uint32 &bucket) const {
+ bucket = (bucket + 1) & bucket_count_mask_;
+ }
+
+ void resize(uint32 new_size) {
+ if (unlikely(nodes_ == nullptr)) {
+ allocate_nodes(new_size);
+ used_node_count_ = 0;
+ return;
+ }
+
+ auto old_nodes = nodes_;
+ uint32 old_size = used_node_count_;
+ uint32 old_bucket_count = bucket_count_;
+ allocate_nodes(new_size);
+ used_node_count_ = old_size;
+
+ auto old_nodes_end = old_nodes + old_bucket_count;
+ for (NodeT *old_node = old_nodes; old_node != old_nodes_end; ++old_node) {
+ if (old_node->empty()) {
+ continue;
+ }
+ auto bucket = calc_bucket(old_node->key());
+ while (!nodes_[bucket].empty()) {
+ next_bucket(bucket);
+ }
+ nodes_[bucket] = std::move(*old_node);
+ }
+ clear_nodes(old_nodes);
+ }
+
+ void erase_node(NodeT *it) {
+ DCHECK(nodes_ <= it && static_cast<size_t>(it - nodes_) < bucket_count());
+ it->clear();
+ used_node_count_--;
+
+ const auto bucket_count = bucket_count_;
+ const auto *end = nodes_ + bucket_count;
+ for (auto *test_node = it + 1; test_node != end; test_node++) {
+ if (likely(test_node->empty())) {
+ return;
+ }
+
+ auto want_node = nodes_ + calc_bucket(test_node->key());
+ if (want_node <= it || want_node > test_node) {
+ *it = std::move(*test_node);
+ it = test_node;
+ }
+ }
+
+ auto empty_i = static_cast<uint32>(it - nodes_);
+ auto empty_bucket = empty_i;
+ for (uint32 test_i = bucket_count;; test_i++) {
+ auto test_bucket = test_i - bucket_count_;
+ if (nodes_[test_bucket].empty()) {
+ return;
+ }
+
+ auto want_i = calc_bucket(nodes_[test_bucket].key());
+ if (want_i < empty_i) {
+ want_i += bucket_count;
+ }
+
+ if (want_i <= empty_i || want_i > test_i) {
+ nodes_[empty_bucket] = std::move(nodes_[test_bucket]);
+ empty_i = test_i;
+ empty_bucket = test_bucket;
+ }
+ }
+ }
+
+ Iterator create_iterator(NodeT *node) {
+ return Iterator(node, nodes_, nodes_ + bucket_count());
+ }
+
+ void invalidate_iterators() {
+ begin_bucket_ = INVALID_BUCKET;
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlFast.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlFast.h
index 9f047881aa..e5570f6d2a 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlFast.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlFast.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,56 +7,81 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/TimedStat.h"
namespace td {
class FloodControlFast {
public:
- uint32 add_event(int32 now) {
- for (auto &limit : limits_) {
- limit.stat_.add_event(CounterStat::Event(), now);
- if (limit.stat_.get_stat(now).count_ > limit.count_) {
- wakeup_at_ = max(wakeup_at_, now + limit.duration_ * 2);
- }
+ void add_event(double now) {
+ for (auto &bucket : buckets_) {
+ bucket.add_event(now);
+ wakeup_at_ = td::max(wakeup_at_, bucket.get_wakeup_at());
}
- return wakeup_at_;
}
- uint32 get_wakeup_at() {
+
+ double get_wakeup_at() const {
return wakeup_at_;
}
- void add_limit(uint32 duration, int32 count) {
- limits_.push_back({TimedStat<CounterStat>(duration, 0), duration, count});
+ void add_limit(double duration, double count) {
+ buckets_.emplace_back(duration, count);
}
void clear_events() {
- for (auto &limit : limits_) {
- limit.stat_.clear_events();
+ for (auto &bucket : buckets_) {
+ bucket.clear_events();
}
wakeup_at_ = 0;
}
private:
- class CounterStat {
+ class FloodControlBucket {
public:
- struct Event {};
- int32 count_ = 0;
- void on_event(Event e) {
- count_++;
+ FloodControlBucket(double duration, double count)
+ : max_capacity_(count - 1), speed_(count / duration), volume_(max_capacity_) {
}
- void clear() {
- count_ = 0;
+
+ void add_event(double now, double size = 1) {
+ CHECK(now >= wakeup_at_);
+ update_volume(now);
+ if (volume_ >= size) {
+ volume_ -= size;
+ return;
+ }
+ size -= volume_;
+ volume_ = 0;
+ wakeup_at_ = volume_at_ + size / speed_;
+ volume_at_ = wakeup_at_;
}
- };
- uint32 wakeup_at_ = 0;
- struct Limit {
- TimedStat<CounterStat> stat_;
- uint32 duration_;
- int32 count_;
+ double get_wakeup_at() const {
+ return wakeup_at_;
+ }
+
+ void clear_events() {
+ volume_ = max_capacity_;
+ volume_at_ = 0;
+ wakeup_at_ = 0;
+ }
+
+ private:
+ const double max_capacity_{1};
+ const double speed_{1};
+ double volume_{1};
+
+ double volume_at_{0};
+ double wakeup_at_{0};
+
+ void update_volume(double now) {
+ CHECK(now >= volume_at_);
+ auto passed = now - volume_at_;
+ volume_ = td::min(volume_ + passed * speed_, max_capacity_);
+ volume_at_ = now;
+ }
};
- std::vector<Limit> limits_;
+
+ double wakeup_at_ = 0;
+ vector<FloodControlBucket> buckets_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.cpp
new file mode 100644
index 0000000000..98709c6b96
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.cpp
@@ -0,0 +1,32 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/FloodControlGlobal.h"
+
+namespace td {
+
+FloodControlGlobal::FloodControlGlobal(uint64 limit) : limit_(limit) {
+}
+
+void FloodControlGlobal::finish() {
+ auto old_value = active_count_.fetch_sub(1, std::memory_order_relaxed);
+ CHECK(old_value > 0);
+}
+
+FloodControlGlobal::Guard FloodControlGlobal::try_start() {
+ auto old_value = active_count_.fetch_add(1, std::memory_order_relaxed);
+ if (old_value >= limit_) {
+ finish();
+ return nullptr;
+ }
+ return Guard(this);
+}
+
+void FloodControlGlobal::Finish::operator()(FloodControlGlobal *ctrl) const {
+ ctrl->finish();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.h
new file mode 100644
index 0000000000..e4f318867b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.h
@@ -0,0 +1,35 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#include <atomic>
+#include <memory>
+
+namespace td {
+
+// Restricts the total number of events
+class FloodControlGlobal {
+ public:
+ explicit FloodControlGlobal(uint64 limit);
+
+ struct Finish {
+ void operator()(FloodControlGlobal *ctrl) const;
+ };
+ using Guard = std::unique_ptr<FloodControlGlobal, Finish>;
+
+ Guard try_start();
+
+ private:
+ std::atomic<uint64> active_count_{0};
+ uint64 limit_{0};
+
+ void finish();
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlStrict.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlStrict.h
index 521fbbedc0..42894cdf7f 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlStrict.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlStrict.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,6 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include <limits>
@@ -17,22 +16,23 @@ namespace td {
// Should be just fine for small counters.
class FloodControlStrict {
public:
- int32 add_event(int32 now) {
+ // there is no reason to return wakeup_at_, because it will be a time before the next allowed event, not current
+ void add_event(double now) {
events_.push_back(Event{now});
if (without_update_ > 0) {
without_update_--;
} else {
update(now);
}
- return wakeup_at_;
}
- // no more than count in each duration.
- void add_limit(int32 duration, int32 count) {
+ // no more than count in each duration
+ void add_limit(int32 duration, size_t count) {
limits_.push_back(Limit{duration, count, 0});
+ without_update_ = 0;
}
- int32 get_wakeup_at() {
+ double get_wakeup_at() const {
return wakeup_at_;
}
@@ -42,20 +42,22 @@ class FloodControlStrict {
limit.pos_ = 0;
}
without_update_ = 0;
- wakeup_at_ = 0;
+ wakeup_at_ = 0.0;
}
- int32 update(int32 now) {
+ private:
+ void update(double now) {
size_t min_pos = events_.size();
without_update_ = std::numeric_limits<size_t>::max();
for (auto &limit : limits_) {
- if (limit.pos_ + limit.count_ < events_.size()) {
+ if (limit.count_ < events_.size() - limit.pos_) {
limit.pos_ = events_.size() - limit.count_;
}
// binary-search? :D
- while (limit.pos_ < events_.size() && events_[limit.pos_].timestamp_ + limit.duration_ < now) {
+ auto end_time = now - limit.duration_;
+ while (limit.pos_ < events_.size() && events_[limit.pos_].timestamp_ < end_time) {
limit.pos_++;
}
@@ -64,7 +66,7 @@ class FloodControlStrict {
wakeup_at_ = max(wakeup_at_, events_[limit.pos_].timestamp_ + limit.duration_);
without_update_ = 0;
} else {
- without_update_ = min(without_update_, limit.count_ + limit.pos_ - events_.size());
+ without_update_ = min(without_update_, limit.count_ + limit.pos_ - events_.size() - 1);
}
min_pos = min(min_pos, limit.pos_);
@@ -76,22 +78,20 @@ class FloodControlStrict {
}
events_.erase(events_.begin(), events_.begin() + min_pos);
}
- return wakeup_at_;
}
- private:
- int32 wakeup_at_ = 0;
+ double wakeup_at_ = 0.0;
struct Event {
- int32 timestamp_;
+ double timestamp_;
};
struct Limit {
int32 duration_;
- int32 count_;
+ size_t count_;
size_t pos_;
};
size_t without_update_ = 0;
- std::vector<Event> events_;
- std::vector<Limit> limits_;
+ vector<Event> events_;
+ vector<Limit> limits_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.cpp
index d4e60d6e29..64b07a9c04 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,10 +9,11 @@
char disable_linker_warning_about_empty_file_gzip_cpp TD_UNUSED;
#if TD_HAVE_ZLIB
-#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
#include <cstring>
#include <limits>
+#include <utility>
#include <zlib.h>
@@ -32,23 +33,23 @@ class Gzip::Impl {
};
Status Gzip::init_encode() {
- CHECK(mode_ == Empty);
+ CHECK(mode_ == Mode::Empty);
init_common();
- mode_ = Encode;
+ mode_ = Mode::Encode;
int ret = deflateInit2(&impl_->stream_, 6, Z_DEFLATED, 15, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
if (ret != Z_OK) {
- return Status::Error("zlib deflate init failed");
+ return Status::Error(PSLICE() << "zlib deflate init failed: " << ret);
}
return Status::OK();
}
Status Gzip::init_decode() {
- CHECK(mode_ == Empty);
+ CHECK(mode_ == Mode::Empty);
init_common();
- mode_ = Decode;
+ mode_ = Mode::Decode;
int ret = inflateInit2(&impl_->stream_, MAX_WBITS + 32);
if (ret != Z_OK) {
- return Status::Error("zlib inflate init failed");
+ return Status::Error(PSLICE() << "zlib inflate init failed: " << ret);
}
return Status::OK();
}
@@ -75,19 +76,19 @@ void Gzip::set_output(MutableSlice output) {
Result<Gzip::State> Gzip::run() {
while (true) {
int ret;
- if (mode_ == Decode) {
+ if (mode_ == Mode::Decode) {
ret = inflate(&impl_->stream_, Z_NO_FLUSH);
} else {
ret = deflate(&impl_->stream_, close_input_flag_ ? Z_FINISH : Z_NO_FLUSH);
}
if (ret == Z_OK) {
- return Running;
+ return State::Running;
}
if (ret == Z_STREAM_END) {
// TODO(now): fail if input is not empty;
clear();
- return Done;
+ return State::Done;
}
clear();
return Status::Error(PSLICE() << "zlib error " << ret);
@@ -118,20 +119,36 @@ void Gzip::init_common() {
}
void Gzip::clear() {
- if (mode_ == Decode) {
+ if (mode_ == Mode::Decode) {
inflateEnd(&impl_->stream_);
- } else if (mode_ == Encode) {
+ } else if (mode_ == Mode::Encode) {
deflateEnd(&impl_->stream_);
}
- mode_ = Empty;
+ mode_ = Mode::Empty;
}
Gzip::Gzip() : impl_(make_unique<Impl>()) {
}
-Gzip::Gzip(Gzip &&other) = default;
+Gzip::Gzip(Gzip &&other) noexcept : Gzip() {
+ swap(other);
+}
-Gzip &Gzip::operator=(Gzip &&other) = default;
+Gzip &Gzip::operator=(Gzip &&other) noexcept {
+ CHECK(this != &other);
+ clear();
+ swap(other);
+ return *this;
+}
+
+void Gzip::swap(Gzip &other) {
+ using std::swap;
+ swap(impl_, other.impl_);
+ swap(input_size_, other.input_size_);
+ swap(output_size_, other.output_size_);
+ swap(close_input_flag_, other.close_input_flag_);
+ swap(mode_, other.mode_);
+}
Gzip::~Gzip() {
clear();
@@ -140,7 +157,7 @@ Gzip::~Gzip() {
BufferSlice gzdecode(Slice s) {
Gzip gzip;
gzip.init_decode().ensure();
- auto message = ChainBufferWriter::create_empty();
+ ChainBufferWriter message;
gzip.set_input(s);
gzip.close_input();
double k = 2;
@@ -151,7 +168,7 @@ BufferSlice gzdecode(Slice s) {
return BufferSlice();
}
auto state = r_state.ok();
- if (state == Gzip::Done) {
+ if (state == Gzip::State::Done) {
message.confirm_append(gzip.flush_output());
break;
}
@@ -167,12 +184,12 @@ BufferSlice gzdecode(Slice s) {
return message.extract_reader().move_as_buffer_slice();
}
-BufferSlice gzencode(Slice s, double k) {
+BufferSlice gzencode(Slice s, double max_compression_ratio) {
Gzip gzip;
gzip.init_encode().ensure();
gzip.set_input(s);
gzip.close_input();
- size_t max_size = static_cast<size_t>(static_cast<double>(s.size()) * k);
+ auto max_size = static_cast<size_t>(static_cast<double>(s.size()) * max_compression_ratio);
BufferWriter message{max_size};
gzip.set_output(message.prepare_append());
auto r_state = gzip.run();
@@ -180,7 +197,7 @@ BufferSlice gzencode(Slice s, double k) {
return BufferSlice();
}
auto state = r_state.ok();
- if (state != Gzip::Done) {
+ if (state != Gzip::State::Done) {
return BufferSlice();
}
message.confirm_append(gzip.flush_output());
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.h
index dd5fba5bf5..d7b68c5e45 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -20,15 +20,15 @@ class Gzip {
Gzip();
Gzip(const Gzip &) = delete;
Gzip &operator=(const Gzip &) = delete;
- Gzip(Gzip &&other);
- Gzip &operator=(Gzip &&other);
+ Gzip(Gzip &&other) noexcept;
+ Gzip &operator=(Gzip &&other) noexcept;
~Gzip();
- enum Mode { Empty, Encode, Decode };
+ enum class Mode { Empty, Encode, Decode };
Status init(Mode mode) TD_WARN_UNUSED_RESULT {
- if (mode == Encode) {
+ if (mode == Mode::Encode) {
return init_encode();
- } else if (mode == Decode) {
+ } else if (mode == Mode::Decode) {
return init_decode();
}
clear();
@@ -79,7 +79,7 @@ class Gzip {
return res;
}
- enum State { Running, Done };
+ enum class State { Running, Done };
Result<State> run() TD_WARN_UNUSED_RESULT;
private:
@@ -89,15 +89,17 @@ class Gzip {
size_t input_size_ = 0;
size_t output_size_ = 0;
bool close_input_flag_ = false;
- Mode mode_ = Empty;
+ Mode mode_ = Mode::Empty;
void init_common();
void clear();
+
+ void swap(Gzip &other);
};
BufferSlice gzdecode(Slice s);
-BufferSlice gzencode(Slice s, double k = 0.9);
+BufferSlice gzencode(Slice s, double max_compression_ratio);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.cpp
index d225ef800e..d321b68ccf 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,62 +9,57 @@
char disable_linker_warning_about_empty_file_gzipbyteflow_cpp TD_UNUSED;
#if TD_HAVE_ZLIB
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/Status.h"
namespace td {
-void GzipByteFlow::loop() {
- while (true) {
- if (gzip_.need_input()) {
- auto slice = input_->prepare_read();
- if (slice.empty()) {
- if (!is_input_active_) {
- gzip_.close_input();
- } else {
- break;
- }
+bool GzipByteFlow::loop() {
+ if (gzip_.need_input()) {
+ auto slice = input_->prepare_read();
+ if (slice.empty()) {
+ if (!is_input_active_) {
+ gzip_.close_input();
} else {
- gzip_.set_input(input_->prepare_read());
+ return false;
}
+ } else {
+ gzip_.set_input(input_->prepare_read());
}
- if (gzip_.need_output()) {
- auto slice = output_.prepare_append();
- CHECK(!slice.empty());
- gzip_.set_output(slice);
- }
- auto r_state = gzip_.run();
- auto output_size = gzip_.flush_output();
- if (output_size) {
- uncommited_size_ += output_size;
- total_output_size_ += output_size;
- if (total_output_size_ > max_output_size_) {
- return finish(Status::Error("Max output size limit exceeded"));
- }
- output_.confirm_append(output_size);
+ }
+ if (gzip_.need_output()) {
+ auto slice = output_.prepare_append();
+ CHECK(!slice.empty());
+ gzip_.set_output(slice);
+ }
+ auto r_state = gzip_.run();
+ auto output_size = gzip_.flush_output();
+ if (output_size) {
+ uncommitted_size_ += output_size;
+ total_output_size_ += output_size;
+ if (total_output_size_ > max_output_size_) {
+ finish(Status::Error("Max output size limit exceeded"));
+ return false;
}
+ output_.confirm_append(output_size);
+ }
- auto input_size = gzip_.flush_input();
- if (input_size) {
- input_->confirm_read(input_size);
- }
- if (r_state.is_error()) {
- return finish(r_state.move_as_error());
- }
- auto state = r_state.ok();
- if (state == Gzip::Done) {
- on_output_updated();
- return consume_input();
- }
+ auto input_size = gzip_.flush_input();
+ if (input_size) {
+ input_->confirm_read(input_size);
}
- if (uncommited_size_ >= MIN_UPDATE_SIZE) {
- uncommited_size_ = 0;
- on_output_updated();
+ if (r_state.is_error()) {
+ finish(r_state.move_as_error());
+ return false;
}
+ auto state = r_state.ok();
+ if (state == Gzip::State::Done) {
+ consume_input();
+ return false;
+ }
+ return true;
}
-constexpr size_t GzipByteFlow::MIN_UPDATE_SIZE;
-
} // namespace td
#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.h
index c7e07abd0a..94a3a3ea50 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -34,14 +34,13 @@ class GzipByteFlow final : public ByteFlowBase {
max_output_size_ = max_output_size;
}
- void loop() override;
+ bool loop() final;
private:
Gzip gzip_;
- size_t uncommited_size_ = 0;
+ size_t uncommitted_size_ = 0;
size_t total_output_size_ = 0;
size_t max_output_size_ = std::numeric_limits<size_t>::max();
- static constexpr size_t MIN_UPDATE_SIZE = 1 << 14;
};
#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hash.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hash.h
new file mode 100644
index 0000000000..8c500daf09
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hash.h
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#if TD_HAVE_ABSL
+#include <absl/hash/hash.h>
+#endif
+
+#include <utility>
+
+namespace td {
+// A simple wrapper for absl::flat_hash_map, std::unordered_map and probably some our implementaion of hash map in
+// the future
+
+// We will introduce out own Hashing utility like an absl one.
+class Hasher {
+ public:
+ Hasher() = default;
+ explicit Hasher(size_t init_value) : hash_(init_value) {
+ }
+ std::size_t finalize() const {
+ return hash_;
+ }
+
+ static Hasher combine(Hasher hasher, size_t value) {
+ hasher.hash_ ^= value;
+ return hasher;
+ }
+
+ template <class A, class B>
+ static Hasher combine(Hasher hasher, const std::pair<A, B> &value) {
+ hasher = AbslHashValue(std::move(hasher), value.first);
+ hasher = AbslHashValue(std::move(hasher), value.second);
+ return hasher;
+ }
+
+ private:
+ std::size_t hash_{0};
+};
+
+template <class IgnoreT>
+class TdHash {
+ public:
+ template <class T>
+ std::size_t operator()(const T &value) const noexcept {
+ return AbslHashValue(Hasher(), value).finalize();
+ }
+};
+
+#if TD_HAVE_ABSL
+template <class T>
+using AbslHash = absl::Hash<T>;
+#else
+template <class T>
+using AbslHash = TdHash<T>;
+#endif
+
+// default hash implementations
+template <class H, class T>
+decltype(H::combine(std::declval<H>(), std::declval<T>())) AbslHashValue(H hasher, const T &value) {
+ return H::combine(std::move(hasher), value);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HashMap.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashMap.h
new file mode 100644
index 0000000000..7e0ba4bf07
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashMap.h
@@ -0,0 +1,27 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/Hash.h"
+
+#if TD_HAVE_ABSL
+#include <absl/container/flat_hash_map.h>
+#else
+#include <unordered_map>
+#endif
+
+namespace td {
+
+#if TD_HAVE_ABSL
+template <class Key, class Value, class H = AbslHash<Key>>
+using HashMap = absl::flat_hash_map<Key, Value, H>;
+#else
+template <class Key, class Value, class H = AbslHash<Key>>
+using HashMap = std::unordered_map<Key, Value, H>;
+#endif
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HashSet.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashSet.h
new file mode 100644
index 0000000000..e49e8b94e3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashSet.h
@@ -0,0 +1,27 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/Hash.h"
+
+#if TD_HAVE_ABSL
+#include <absl/container/flat_hash_set.h>
+#else
+#include <unordered_set>
+#endif
+
+namespace td {
+
+#if TD_HAVE_ABSL
+template <class Key, class H = AbslHash<Key>>
+using HashSet = absl::flat_hash_set<Key, H>;
+#else
+template <class Key, class H = AbslHash<Key>>
+using HashSet = std::unordered_set<Key, H>;
+#endif
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HashTableUtils.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashTableUtils.h
new file mode 100644
index 0000000000..9d72f63c59
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashTableUtils.h
@@ -0,0 +1,72 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#include <cstdint>
+#include <functional>
+
+namespace td {
+
+template <class KeyT>
+bool is_hash_table_key_empty(const KeyT &key) {
+ return key == KeyT();
+}
+
+inline uint32 randomize_hash(uint32 h) {
+ h ^= h >> 16;
+ h *= 0x85ebca6b;
+ h ^= h >> 13;
+ h *= 0xc2b2ae35;
+ h ^= h >> 16;
+ return h;
+}
+
+template <class Type>
+struct Hash {
+ uint32 operator()(const Type &value) const;
+};
+
+template <class Type>
+struct Hash<Type *> {
+ uint32 operator()(Type *pointer) const {
+ return Hash<uint64>()(reinterpret_cast<std::uintptr_t>(pointer));
+ }
+};
+
+template <>
+inline uint32 Hash<char>::operator()(const char &value) const {
+ return randomize_hash(static_cast<uint32>(value));
+}
+
+template <>
+inline uint32 Hash<int32>::operator()(const int32 &value) const {
+ return randomize_hash(static_cast<uint32>(value));
+}
+
+template <>
+inline uint32 Hash<uint32>::operator()(const uint32 &value) const {
+ return randomize_hash(value);
+}
+
+template <>
+inline uint32 Hash<int64>::operator()(const int64 &value) const {
+ return randomize_hash(static_cast<uint32>(value + (value >> 32)));
+}
+
+template <>
+inline uint32 Hash<uint64>::operator()(const uint64 &value) const {
+ return randomize_hash(static_cast<uint32>(value + (value >> 32)));
+}
+
+template <>
+inline uint32 Hash<string>::operator()(const string &value) const {
+ return static_cast<uint32>(std::hash<string>()(value));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HazardPointers.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HazardPointers.h
index e13dc8022e..3ed41a0c9a 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/HazardPointers.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HazardPointers.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,20 +7,25 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include <array>
#include <atomic>
+#include <memory>
namespace td {
-template <class T, int MaxPointersN = 1>
+template <class T, int MaxPointersN = 1, class Deleter = std::default_delete<T>>
class HazardPointers {
public:
explicit HazardPointers(size_t threads_n) : threads_(threads_n) {
for (auto &data : threads_) {
- for (auto &ptr : data.hazard) {
+ for (auto &ptr : data.hazard_) {
+// workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64658
+#if TD_GCC && GCC_VERSION <= 40902
ptr = nullptr;
+#else
+ std::atomic_init(&ptr, static_cast<T *>(nullptr));
+#endif
}
}
}
@@ -31,12 +36,17 @@ class HazardPointers {
class Holder {
public:
- T *protect(std::atomic<T *> &to_protect) {
+ template <class S>
+ S *protect(std::atomic<S *> &to_protect) {
return do_protect(hazard_ptr_, to_protect);
}
+ Holder(HazardPointers &hp, size_t thread_id, size_t pos) : Holder(hp.get_hazard_ptr(thread_id, pos)) {
+ CHECK(hazard_ptr_.load() == 0);
+ hazard_ptr_.store(reinterpret_cast<T *>(1));
+ }
Holder(const Holder &other) = delete;
Holder &operator=(const Holder &other) = delete;
- Holder(Holder &&other) = default; // TODO
+ Holder(Holder &&other) = delete;
Holder &operator=(Holder &&other) = delete;
~Holder() {
clear();
@@ -52,20 +62,16 @@ class HazardPointers {
std::atomic<T *> &hazard_ptr_;
};
- Holder get_holder(size_t thread_id, size_t pos) {
- return Holder(get_hazard_ptr(thread_id, pos));
- }
-
void retire(size_t thread_id, T *ptr = nullptr) {
CHECK(thread_id < threads_.size());
auto &data = threads_[thread_id];
if (ptr) {
- data.to_delete.push_back(std::unique_ptr<T>(ptr));
+ data.to_delete_.push_back(std::unique_ptr<T, Deleter>(ptr));
}
- for (auto it = data.to_delete.begin(); it != data.to_delete.end();) {
+ for (auto it = data.to_delete_.begin(); it != data.to_delete_.end();) {
if (!is_protected(it->get())) {
it->reset();
- it = data.to_delete.erase(it);
+ it = data.to_delete_.erase(it);
} else {
++it;
}
@@ -82,32 +88,33 @@ class HazardPointers {
size_t to_delete_size_unsafe() const {
size_t res = 0;
- for (auto &thread : threads_) {
- res += thread.to_delete.size();
+ for (auto &thread_data : threads_) {
+ res += thread_data.to_delete_.size();
}
return res;
}
private:
struct ThreadData {
- std::array<std::atomic<T *>, MaxPointersN> hazard;
+ std::array<std::atomic<T *>, MaxPointersN> hazard_;
char pad[TD_CONCURRENCY_PAD - sizeof(std::array<std::atomic<T *>, MaxPointersN>)];
// stupid gc
- std::vector<std::unique_ptr<T>> to_delete;
- char pad2[TD_CONCURRENCY_PAD - sizeof(std::vector<std::unique_ptr<T>>)];
+ std::vector<std::unique_ptr<T, Deleter>> to_delete_;
+ char pad2[TD_CONCURRENCY_PAD - sizeof(std::vector<std::unique_ptr<T, Deleter>>)];
};
std::vector<ThreadData> threads_;
char pad2[TD_CONCURRENCY_PAD - sizeof(std::vector<ThreadData>)];
- static T *do_protect(std::atomic<T *> &hazard_ptr, std::atomic<T *> &to_protect) {
+ template <class S>
+ static S *do_protect(std::atomic<T *> &hazard_ptr, std::atomic<S *> &to_protect) {
T *saved = nullptr;
T *to_save;
while ((to_save = to_protect.load()) != saved) {
hazard_ptr.store(to_save);
saved = to_save;
}
- return saved;
+ return static_cast<S *>(saved);
}
static void do_clear(std::atomic<T *> &hazard_ptr) {
@@ -115,8 +122,8 @@ class HazardPointers {
}
bool is_protected(T *ptr) {
- for (auto &thread : threads_) {
- for (auto &hazard_ptr : thread.hazard) {
+ for (auto &thread_data : threads_) {
+ for (auto &hazard_ptr : thread_data.hazard_) {
if (hazard_ptr.load() == ptr) {
return true;
}
@@ -126,7 +133,8 @@ class HazardPointers {
}
std::atomic<T *> &get_hazard_ptr(size_t thread_id, size_t pos) {
- return threads_[thread_id].hazard[pos];
+ CHECK(thread_id < threads_.size());
+ return threads_[thread_id].hazard_[pos];
}
};
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Heap.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Heap.h
index 54ee391497..154b87089a 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Heap.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Heap.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,6 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/logging.h"
namespace td {
@@ -21,7 +20,7 @@ struct HeapNode {
void remove() {
pos_ = -1;
}
- int pos_ = -1;
+ int32 pos_ = -1;
};
template <class KeyT, int K = 4>
@@ -38,23 +37,33 @@ class KHeap {
return array_[0].key_;
}
+ KeyT get_key(const HeapNode *node) const {
+ auto pos = static_cast<size_t>(node->pos_);
+ CHECK(pos < array_.size());
+ return array_[pos].key_;
+ }
+
+ const HeapNode *top() const {
+ return array_[0].node_;
+ }
+
HeapNode *pop() {
CHECK(!empty());
HeapNode *result = array_[0].node_;
result->remove();
- erase(0);
+ erase(static_cast<size_t>(0));
return result;
}
void insert(KeyT key, HeapNode *node) {
CHECK(!node->in_heap());
array_.push_back({key, node});
- fix_up(static_cast<int>(array_.size()) - 1);
+ fix_up(array_.size() - 1);
}
void fix(KeyT key, HeapNode *node) {
- CHECK(node->in_heap());
- int pos = node->pos_;
+ auto pos = static_cast<size_t>(node->pos_);
+ CHECK(pos < array_.size());
KeyT old_key = array_[pos].key_;
array_[pos].key_ = key;
if (key < old_key) {
@@ -65,14 +74,21 @@ class KHeap {
}
void erase(HeapNode *node) {
- CHECK(node->in_heap());
- int pos = node->pos_;
+ auto pos = static_cast<size_t>(node->pos_);
node->remove();
+ CHECK(pos < array_.size());
erase(pos);
}
template <class F>
- void for_each(F &f) const {
+ void for_each(F &&f) const {
+ for (auto &it : array_) {
+ f(it.key_, it.node_);
+ }
+ }
+
+ template <class F>
+ void for_each(F &&f) {
for (auto &it : array_) {
f(it.key_, it.node_);
}
@@ -81,7 +97,7 @@ class KHeap {
void check() const {
for (size_t i = 0; i < array_.size(); i++) {
for (size_t j = i * K + 1; j < i * K + 1 + K && j < array_.size(); j++) {
- CHECK(array_[i].key_ <= array_[j].key_) << i << " " << j;
+ CHECK(array_[i].key_ <= array_[j].key_);
}
}
}
@@ -93,34 +109,34 @@ class KHeap {
};
vector<Item> array_;
- void fix_up(int pos) {
+ void fix_up(size_t pos) {
auto item = array_[pos];
while (pos) {
- int parent_pos = (pos - 1) / K;
+ auto parent_pos = (pos - 1) / K;
auto parent_item = array_[parent_pos];
if (parent_item.key_ < item.key_) {
break;
}
- parent_item.node_->pos_ = pos;
+ parent_item.node_->pos_ = static_cast<int32>(pos);
array_[pos] = parent_item;
pos = parent_pos;
}
- item.node_->pos_ = pos;
+ item.node_->pos_ = static_cast<int32>(pos);
array_[pos] = item;
}
- void fix_down(int pos) {
+ void fix_down(size_t pos) {
auto item = array_[pos];
while (true) {
- int left_pos = pos * K + 1;
- int right_pos = min(left_pos + K, static_cast<int>(array_.size()));
- int next_pos = pos;
+ auto left_pos = pos * K + 1;
+ auto right_pos = min(left_pos + K, array_.size());
+ auto next_pos = pos;
KeyT next_key = item.key_;
- for (int i = left_pos; i < right_pos; i++) {
+ for (auto i = left_pos; i < right_pos; i++) {
KeyT i_key = array_[i].key_;
if (i_key < next_key) {
next_key = i_key;
@@ -131,21 +147,24 @@ class KHeap {
break;
}
array_[pos] = array_[next_pos];
- array_[pos].node_->pos_ = pos;
+ array_[pos].node_->pos_ = static_cast<int32>(pos);
pos = next_pos;
}
- item.node_->pos_ = pos;
+ item.node_->pos_ = static_cast<int32>(pos);
array_[pos] = item;
}
- void erase(int pos) {
+ void erase(size_t pos) {
array_[pos] = array_.back();
array_.pop_back();
- if (pos < static_cast<int>(array_.size())) {
+ if (pos < array_.size()) {
fix_down(pos);
fix_up(pos);
}
+ if (array_.capacity() > 50 && array_.size() < array_.capacity() / 4) {
+ array_.shrink_to_fit();
+ }
}
};
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.cpp
index 1e7449a668..1041b95d8b 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.cpp
@@ -1,49 +1,23 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/Hints.h"
+#include "td/utils/algorithm.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
-#include "td/utils/unicode.h"
+#include "td/utils/translit.h"
#include "td/utils/utf8.h"
#include <algorithm>
namespace td {
-vector<string> Hints::get_words(Slice name) {
- bool in_word = false;
- string word;
- vector<string> words;
- auto pos = name.ubegin();
- auto end = name.uend();
- while (pos != end) {
- uint32 code;
- pos = next_utf8_unsafe(pos, &code);
-
- code = prepare_search_character(code);
- if (code == 0) {
- continue;
- }
- if (code == ' ') {
- if (in_word) {
- words.push_back(std::move(word));
- word.clear();
- in_word = false;
- }
- } else {
- in_word = true;
- append_utf8_character(word, code);
- }
- }
- if (in_word) {
- words.push_back(std::move(word));
- }
+vector<string> Hints::fix_words(vector<string> words) {
std::sort(words.begin(), words.end());
size_t new_words_size = 0;
@@ -52,14 +26,39 @@ vector<string> Hints::get_words(Slice name) {
if (i != new_words_size) {
words[new_words_size] = std::move(words[i]);
}
- // LOG(ERROR) << "Get word " << words[new_words_size];
new_words_size++;
}
}
+ if (new_words_size == 1 && words[0].empty()) {
+ new_words_size = 0;
+ }
words.resize(new_words_size);
return words;
}
+vector<string> Hints::get_words(Slice name) {
+ return fix_words(utf8_get_search_words(name));
+}
+
+void Hints::add_word(const string &word, KeyT key, std::map<string, vector<KeyT>> &word_to_keys) {
+ vector<KeyT> &keys = word_to_keys[word];
+ CHECK(!td::contains(keys, key));
+ keys.push_back(key);
+}
+
+void Hints::delete_word(const string &word, KeyT key, std::map<string, vector<KeyT>> &word_to_keys) {
+ vector<KeyT> &keys = word_to_keys[word];
+ auto key_it = std::find(keys.begin(), keys.end(), key);
+ CHECK(key_it != keys.end());
+ if (keys.size() == 1) {
+ word_to_keys.erase(word);
+ } else {
+ CHECK(keys.size() > 1);
+ *key_it = keys.back();
+ keys.pop_back();
+ }
+}
+
void Hints::add(KeyT key, Slice name) {
// LOG(ERROR) << "Add " << key << ": " << name;
auto it = key_to_name_.find(key);
@@ -67,19 +66,19 @@ void Hints::add(KeyT key, Slice name) {
if (it->second == name) {
return;
}
- auto old_words = get_words(it->second);
- for (auto &old_word : old_words) {
- vector<KeyT> &keys = word_to_keys_[old_word];
- auto key_it = std::find(keys.begin(), keys.end(), key);
- CHECK(key_it != keys.end());
- if (keys.size() == 1) {
- word_to_keys_.erase(old_word);
- } else {
- CHECK(keys.size() > 1);
- *key_it = keys.back();
- keys.pop_back();
+ vector<string> old_transliterations;
+ for (auto &old_word : get_words(it->second)) {
+ delete_word(old_word, key, word_to_keys_);
+
+ for (auto &w : get_word_transliterations(old_word, false)) {
+ if (w != old_word) {
+ old_transliterations.push_back(std::move(w));
+ }
}
}
+ for (auto &word : fix_words(old_transliterations)) {
+ delete_word(word, key, translit_word_to_keys_);
+ }
}
if (name.empty()) {
if (it != key_to_name_.end()) {
@@ -88,12 +87,21 @@ void Hints::add(KeyT key, Slice name) {
key_to_rating_.erase(key);
return;
}
- auto words = get_words(name);
- for (auto &word : words) {
- vector<KeyT> &keys = word_to_keys_[word];
- CHECK(std::find(keys.begin(), keys.end(), key) == keys.end());
- keys.push_back(key);
+
+ vector<string> transliterations;
+ for (auto &word : get_words(name)) {
+ add_word(word, key, word_to_keys_);
+
+ for (auto &w : get_word_transliterations(word, false)) {
+ if (w != word) {
+ transliterations.push_back(std::move(w));
+ }
+ }
+ }
+ for (auto &word : fix_words(transliterations)) {
+ add_word(word, key, translit_word_to_keys_);
}
+
key_to_name_[key] = name.str();
}
@@ -102,17 +110,24 @@ void Hints::set_rating(KeyT key, RatingT rating) {
key_to_rating_[key] = rating;
}
-vector<Hints::KeyT> Hints::search_word(const string &word) const {
- // LOG(ERROR) << "Search word " << word;
- vector<KeyT> results;
- auto it = word_to_keys_.lower_bound(word);
- while (it != word_to_keys_.end() && begins_with(it->first, word)) {
+void Hints::add_search_results(vector<KeyT> &results, const string &word,
+ const std::map<string, vector<KeyT>> &word_to_keys) {
+ LOG(DEBUG) << "Search for word " << word;
+ auto it = word_to_keys.lower_bound(word);
+ while (it != word_to_keys.end() && begins_with(it->first, word)) {
results.insert(results.end(), it->second.begin(), it->second.end());
++it;
}
+}
+
+vector<Hints::KeyT> Hints::search_word(const string &word) const {
+ vector<KeyT> results;
+ add_search_results(results, word, translit_word_to_keys_);
+ for (const auto &w : get_word_transliterations(word, true)) {
+ add_search_results(results, w, word_to_keys_);
+ }
- std::sort(results.begin(), results.end());
- results.erase(std::unique(results.begin(), results.end()), results.end());
+ td::unique(results);
return results;
}
@@ -169,7 +184,7 @@ std::pair<size_t, vector<Hints::KeyT>> Hints::search(Slice query, int32 limit, b
}
bool Hints::has_key(KeyT key) const {
- return key_to_name_.find(key) != key_to_name_.end();
+ return key_to_name_.count(key) > 0;
}
string Hints::key_to_string(KeyT key) const {
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.h
index 645896684a..f069da20d0 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,6 +7,7 @@
#pragma once
#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
#include "td/utils/Slice.h"
#include <map>
@@ -41,17 +42,26 @@ class Hints {
size_t size() const;
+ static vector<string> fix_words(vector<string> words);
+
private:
std::map<string, vector<KeyT>> word_to_keys_;
- std::unordered_map<KeyT, string> key_to_name_;
- std::unordered_map<KeyT, RatingT> key_to_rating_;
+ std::map<string, vector<KeyT>> translit_word_to_keys_;
+ std::unordered_map<KeyT, string, Hash<KeyT>> key_to_name_;
+ std::unordered_map<KeyT, RatingT, Hash<KeyT>> key_to_rating_;
+
+ static void add_word(const string &word, KeyT key, std::map<string, vector<KeyT>> &word_to_keys);
+ static void delete_word(const string &word, KeyT key, std::map<string, vector<KeyT>> &word_to_keys);
static vector<string> get_words(Slice name);
+ static void add_search_results(vector<KeyT> &results, const string &word,
+ const std::map<string, vector<KeyT>> &word_to_keys);
+
vector<KeyT> search_word(const string &word) const;
class CompareByRating {
- const std::unordered_map<KeyT, RatingT> &key_to_rating_;
+ const std::unordered_map<KeyT, RatingT, Hash<KeyT>> &key_to_rating_;
RatingT get_rating(const KeyT &key) const {
auto it = key_to_rating_.find(key);
@@ -62,7 +72,8 @@ class Hints {
}
public:
- explicit CompareByRating(const std::unordered_map<KeyT, RatingT> &key_to_rating) : key_to_rating_(key_to_rating) {
+ explicit CompareByRating(const std::unordered_map<KeyT, RatingT, Hash<KeyT>> &key_to_rating)
+ : key_to_rating_(key_to_rating) {
}
bool operator()(const KeyT &lhs, const KeyT &rhs) const {
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.cpp
index 55b66f7b3a..e793f940a8 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,16 +10,19 @@
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Parser.h"
+#include "td/utils/port/IPAddress.h"
+
+#include <algorithm>
namespace td {
string HttpUrl::get_url() const {
string result;
switch (protocol_) {
- case Protocol::HTTP:
+ case Protocol::Http:
result += "http://";
break;
- case Protocol::HTTPS:
+ case Protocol::Https:
result += "https://";
break;
default:
@@ -29,39 +32,32 @@ string HttpUrl::get_url() const {
result += userinfo_;
result += '@';
}
- if (is_ipv6) {
- result += '[';
- }
result += host_;
- if (is_ipv6) {
- result += ']';
- }
if (specified_port_ > 0) {
result += ':';
result += to_string(specified_port_);
}
- CHECK(!query_.empty() && query_[0] == '/');
+ LOG_CHECK(!query_.empty() && query_[0] == '/') << query_;
result += query_;
return result;
}
-Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol) {
+Result<HttpUrl> parse_url(Slice url, HttpUrl::Protocol default_protocol) {
// url == [https?://][userinfo@]host[:port]
- Parser parser(url);
- string protocol_str = to_lower(parser.read_till_nofail(':'));
+ ConstParser parser(url);
+ string protocol_str = to_lower(parser.read_till_nofail(":/?#@[]"));
HttpUrl::Protocol protocol;
- if (parser.start_with("://")) {
- parser.advance(3);
+ if (parser.try_skip("://")) {
if (protocol_str == "http") {
- protocol = HttpUrl::Protocol::HTTP;
+ protocol = HttpUrl::Protocol::Http;
} else if (protocol_str == "https") {
- protocol = HttpUrl::Protocol::HTTPS;
+ protocol = HttpUrl::Protocol::Https;
} else {
return Status::Error("Unsupported URL protocol");
}
} else {
- parser = Parser(url);
+ parser = ConstParser(url);
protocol = default_protocol;
}
Slice userinfo_host_port = parser.read_till_nofail("/?#");
@@ -73,7 +69,16 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol)
}
Slice userinfo_host;
if (colon > userinfo_host_port.begin() && *colon == ':') {
- port = to_integer<int>(Slice(colon + 1, userinfo_host_port.end()));
+ Slice port_slice(colon + 1, userinfo_host_port.end());
+ while (port_slice.size() > 1 && port_slice[0] == '0') {
+ port_slice.remove_prefix(1);
+ }
+ auto r_port = to_integer_safe<int>(port_slice);
+ if (r_port.is_error() || r_port.ok() == 0) {
+ port = -1;
+ } else {
+ port = r_port.ok();
+ }
userinfo_host = Slice(userinfo_host_port.begin(), colon);
} else {
userinfo_host = userinfo_host_port;
@@ -88,20 +93,26 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol)
bool is_ipv6 = false;
if (!host.empty() && host[0] == '[' && host.back() == ']') {
- host.remove_prefix(1);
- host.remove_suffix(1);
+ IPAddress ip_address;
+ if (ip_address.init_ipv6_port(host.str(), 1).is_error()) {
+ return Status::Error("Wrong IPv6 address specified in the URL");
+ }
+ CHECK(ip_address.is_ipv6());
is_ipv6 = true;
}
if (host.empty()) {
return Status::Error("URL host is empty");
}
+ if (host == ".") {
+ return Status::Error("Host is invalid");
+ }
int specified_port = port;
if (port == 0) {
- if (protocol == HttpUrl::Protocol::HTTP) {
+ if (protocol == HttpUrl::Protocol::Http) {
port = 80;
} else {
- CHECK(protocol == HttpUrl::Protocol::HTTPS);
+ CHECK(protocol == HttpUrl::Protocol::Https);
port = 443;
}
}
@@ -111,7 +122,7 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol)
query.remove_suffix(1);
}
if (query.empty()) {
- query = "/";
+ query = Slice("/");
}
string query_str;
if (query[0] != '/') {
@@ -130,6 +141,14 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol)
string host_str = to_lower(host);
for (size_t i = 0; i < host_str.size(); i++) {
char c = host_str[i];
+ if (is_ipv6) {
+ if (i == 0 || i + 1 == host_str.size() || c == ':' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') ||
+ c == '.') {
+ continue;
+ }
+ return Status::Error("Wrong IPv6 URL host");
+ }
+
if (('a' <= c && c <= 'z') || c == '.' || ('0' <= c && c <= '9') || c == '-' || c == '_' || c == '!' || c == '$' ||
c == ',' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')' || c == ';' || c == '&' || c == '+' ||
c == '=') {
@@ -145,9 +164,11 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol)
continue;
}
}
+ return Status::Error("Wrong percent-encoded symbol in URL host");
}
+
// all other symbols aren't allowed
- unsigned char uc = static_cast<unsigned char>(c);
+ auto uc = static_cast<unsigned char>(c);
if (uc >= 128) {
// but we allow plain UTF-8 symbols
continue;
@@ -159,11 +180,66 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol)
}
StringBuilder &operator<<(StringBuilder &sb, const HttpUrl &url) {
- sb << tag("protocol", url.protocol_ == HttpUrl::Protocol::HTTP ? "HTTP" : "HTTPS") << tag("userinfo", url.userinfo_)
+ sb << tag("protocol", url.protocol_ == HttpUrl::Protocol::Http ? "HTTP" : "HTTPS") << tag("userinfo", url.userinfo_)
<< tag("host", url.host_) << tag("port", url.port_) << tag("query", url.query_);
return sb;
}
+HttpUrlQuery parse_url_query(Slice query) {
+ if (!query.empty() && query[0] == '/') {
+ query.remove_prefix(1);
+ }
+
+ size_t path_size = 0;
+ while (path_size < query.size() && query[path_size] != '?' && query[path_size] != '#') {
+ path_size++;
+ }
+
+ HttpUrlQuery result;
+ result.path_ = full_split(url_decode(query.substr(0, path_size), false), '/');
+ while (!result.path_.empty() && result.path_.back().empty()) {
+ result.path_.pop_back();
+ }
+
+ if (path_size < query.size() && query[path_size] == '?') {
+ query = query.substr(path_size + 1);
+ query.truncate(query.find('#'));
+
+ ConstParser parser(query);
+ while (!parser.data().empty()) {
+ auto key_value = split(parser.read_till_nofail('&'), '=');
+ parser.skip_nofail('&');
+ auto key = url_decode(key_value.first, true);
+ if (!key.empty()) {
+ result.args_.emplace_back(std::move(key), url_decode(key_value.second, true));
+ }
+ }
+ CHECK(parser.status().is_ok());
+ }
+
+ return result;
+}
+
+bool HttpUrlQuery::has_arg(Slice key) const {
+ auto it =
+ std::find_if(args_.begin(), args_.end(), [&key](const std::pair<string, string> &s) { return s.first == key; });
+ return it != args_.end();
+}
+
+Slice HttpUrlQuery::get_arg(Slice key) const {
+ auto it =
+ std::find_if(args_.begin(), args_.end(), [&key](const std::pair<string, string> &s) { return s.first == key; });
+ return it == args_.end() ? Slice() : it->second;
+}
+
+string get_url_host(Slice url) {
+ auto r_http_url = parse_url(url);
+ if (r_http_url.is_error()) {
+ return string();
+ }
+ return r_http_url.ok().host_;
+}
+
string get_url_query_file_name(const string &query) {
Slice query_slice = query;
query_slice.truncate(query.find_first_of("?#"));
@@ -175,10 +251,8 @@ string get_url_query_file_name(const string &query) {
return query_slice.str();
}
-string get_url_file_name(const string &url) {
- // TODO remove copy
- string url_copy = url;
- auto r_http_url = parse_url(url_copy);
+string get_url_file_name(Slice url) {
+ auto r_http_url = parse_url(url);
if (r_http_url.is_error()) {
LOG(WARNING) << "Receive wrong URL \"" << url << '"';
return string();
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.h
index f7d1e4aaba..9b4e92edce 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,29 +11,54 @@
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
+#include <utility>
+
namespace td {
class HttpUrl {
public:
- enum class Protocol { HTTP, HTTPS } protocol_;
+ enum class Protocol { Http, Https } protocol_ = Protocol::Http;
string userinfo_;
string host_;
- bool is_ipv6;
- int specified_port_;
- int port_;
+ bool is_ipv6_ = false;
+ int specified_port_ = 0;
+ int port_ = 0;
string query_;
string get_url() const;
+
+ HttpUrl(Protocol protocol, string userinfo, string host, bool is_ipv6, int specified_port, int port, string query)
+ : protocol_(protocol)
+ , userinfo_(std::move(userinfo))
+ , host_(std::move(host))
+ , is_ipv6_(is_ipv6)
+ , specified_port_(specified_port)
+ , port_(port)
+ , query_(std::move(query)) {
+ }
};
-// TODO Slice instead of MutableSlice
-Result<HttpUrl> parse_url(MutableSlice url,
- HttpUrl::Protocol default_protocol = HttpUrl::Protocol::HTTP) TD_WARN_UNUSED_RESULT;
+Result<HttpUrl> parse_url(Slice url,
+ HttpUrl::Protocol default_protocol = HttpUrl::Protocol::Http) TD_WARN_UNUSED_RESULT;
StringBuilder &operator<<(StringBuilder &sb, const HttpUrl &url);
+class HttpUrlQuery {
+ public:
+ vector<string> path_;
+ vector<std::pair<string, string>> args_;
+
+ bool has_arg(Slice key) const;
+
+ Slice get_arg(Slice key) const;
+};
+
+HttpUrlQuery parse_url_query(Slice query);
+
+string get_url_host(Slice url);
+
string get_url_query_file_name(const string &query);
-string get_url_file_name(const string &url);
+string get_url_file_name(Slice url);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.cpp
index eb654f43cd..f5823ad806 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,10 +8,12 @@
#include "td/utils/misc.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
#include <cstring>
namespace td {
+
StringBuilder &operator<<(StringBuilder &sb, const JsonRawString &val) {
sb << '"';
SCOPE_EXIT {
@@ -94,11 +96,11 @@ StringBuilder &operator<<(StringBuilder &sb, const JsonString &val) {
break;
}
if (128 <= ch) {
- int a = s[pos];
+ uint32 a = ch;
CHECK((a & 0x40) != 0);
CHECK(pos + 1 < len);
- int b = s[++pos];
+ uint32 b = static_cast<unsigned char>(s[++pos]);
CHECK((b & 0xc0) == 0x80);
if ((a & 0x20) == 0) {
CHECK((a & 0x1e) > 0);
@@ -107,7 +109,7 @@ StringBuilder &operator<<(StringBuilder &sb, const JsonString &val) {
}
CHECK(pos + 1 < len);
- int c = s[++pos];
+ uint32 c = static_cast<unsigned char>(s[++pos]);
CHECK((c & 0xc0) == 0x80);
if ((a & 0x10) == 0) {
CHECK(((a & 0x0f) | (b & 0x20)) > 0);
@@ -116,7 +118,7 @@ StringBuilder &operator<<(StringBuilder &sb, const JsonString &val) {
}
CHECK(pos + 1 < len);
- int d = s[++pos];
+ uint32 d = static_cast<unsigned char>(s[++pos]);
CHECK((d & 0xc0) == 0x80);
if ((a & 0x08) == 0) {
CHECK(((a & 0x07) | (b & 0x30)) > 0);
@@ -133,6 +135,7 @@ StringBuilder &operator<<(StringBuilder &sb, const JsonString &val) {
}
return sb;
}
+
Result<MutableSlice> json_string_decode(Parser &parser) {
if (!parser.try_skip('"')) {
return Status::Error("Opening '\"' expected");
@@ -232,7 +235,7 @@ Result<MutableSlice> json_string_decode(Parser &parser) {
} else if (num < 0x800) {
*cur_dest++ = static_cast<char>(0xc0 + (num >> 6));
*cur_dest++ = static_cast<char>(0x80 + (num & 63));
- } else if (num < 0xffff) {
+ } else if (num <= 0xffff) {
*cur_dest++ = static_cast<char>(0xe0 + (num >> 12));
*cur_dest++ = static_cast<char>(0x80 + ((num >> 6) & 63));
*cur_dest++ = static_cast<char>(0x80 + (num & 63));
@@ -341,20 +344,20 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) {
parser.skip_whitespaces();
switch (parser.peek_char()) {
case 'f':
- if (parser.skip_start_with("false")) {
+ if (parser.try_skip("false")) {
return JsonValue::create_boolean(false);
}
- return Status::Error("Starts with 'f' -- false expected");
+ return Status::Error("Token starts with 'f' -- false expected");
case 't':
- if (parser.skip_start_with("true")) {
+ if (parser.try_skip("true")) {
return JsonValue::create_boolean(true);
}
- return Status::Error("Starts with 't' -- true expected");
+ return Status::Error("Token starts with 't' -- true expected");
case 'n':
- if (parser.skip_start_with("null")) {
+ if (parser.try_skip("null")) {
return JsonValue();
}
- return Status::Error("Starts with 'n' -- null expected");
+ return Status::Error("Token starts with 'n' -- null expected");
case '"': {
TRY_RESULT(slice, json_string_decode(parser));
return JsonValue::create_string(slice);
@@ -368,7 +371,7 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) {
}
while (true) {
if (parser.empty()) {
- return Status::Error("Unexpected end");
+ return Status::Error("Unexpected string end");
}
TRY_RESULT(value, do_json_decode(parser, max_depth - 1));
res.emplace_back(std::move(value));
@@ -381,20 +384,23 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) {
parser.skip_whitespaces();
continue;
}
- return Status::Error("Unexpected symbol");
+ if (parser.empty()) {
+ return Status::Error("Unexpected string end");
+ }
+ return Status::Error("Unexpected symbol while parsing JSON Array");
}
return JsonValue::create_array(std::move(res));
}
case '{': {
parser.skip('{');
parser.skip_whitespaces();
- std::vector<std::pair<MutableSlice, JsonValue> > res;
+ std::vector<std::pair<MutableSlice, JsonValue>> res;
if (parser.try_skip('}')) {
return JsonValue::make_object(std::move(res));
}
while (true) {
if (parser.empty()) {
- return Status::Error("Unexpected end");
+ return Status::Error("Unexpected string end");
}
TRY_RESULT(key, json_string_decode(parser));
parser.skip_whitespaces();
@@ -402,7 +408,7 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) {
return Status::Error("':' expected");
}
TRY_RESULT(value, do_json_decode(parser, max_depth - 1));
- res.emplace_back(std::move(key), std::move(value));
+ res.emplace_back(key, std::move(value));
parser.skip_whitespaces();
if (parser.try_skip('}')) {
@@ -412,7 +418,10 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) {
parser.skip_whitespaces();
continue;
}
- return Status::Error("Unexpected symbol");
+ if (parser.empty()) {
+ return Status::Error("Unexpected string end");
+ }
+ return Status::Error("Unexpected symbol while parsing JSON Object");
}
return JsonValue::make_object(std::move(res));
}
@@ -434,7 +443,7 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) {
return JsonValue::create_number(num);
}
case 0:
- return Status::Error("Unexpected end");
+ return Status::Error("Unexpected string end");
default: {
char next = parser.peek_char();
if (0 < next && next < 127) {
@@ -455,17 +464,17 @@ Status do_json_skip(Parser &parser, int32 max_depth) {
parser.skip_whitespaces();
switch (parser.peek_char()) {
case 'f':
- if (parser.skip_start_with("false")) {
+ if (parser.try_skip("false")) {
return Status::OK();
}
return Status::Error("Starts with 'f' -- false expected");
case 't':
- if (parser.skip_start_with("true")) {
+ if (parser.try_skip("true")) {
return Status::OK();
}
return Status::Error("Starts with 't' -- true expected");
case 'n':
- if (parser.skip_start_with("null")) {
+ if (parser.try_skip("null")) {
return Status::OK();
}
return Status::Error("Starts with 'n' -- null expected");
@@ -576,7 +585,7 @@ Slice JsonValue::get_type_name(Type type) {
}
}
-bool has_json_object_field(JsonObject &object, Slice name) {
+bool has_json_object_field(const JsonObject &object, Slice name) {
for (auto &field_value : object) {
if (field_value.first == name) {
return true;
@@ -585,6 +594,15 @@ bool has_json_object_field(JsonObject &object, Slice name) {
return false;
}
+JsonValue get_json_object_field_force(JsonObject &object, Slice name) {
+ for (auto &field_value : object) {
+ if (field_value.first == name) {
+ return std::move(field_value.second);
+ }
+ }
+ return JsonValue();
+}
+
Result<JsonValue> get_json_object_field(JsonObject &object, Slice name, JsonValue::Type type, bool is_optional) {
for (auto &field_value : object) {
if (field_value.first == name) {
@@ -611,11 +629,41 @@ Result<bool> get_json_object_bool_field(JsonObject &object, Slice name, bool is_
}
Result<int32> get_json_object_int_field(JsonObject &object, Slice name, bool is_optional, int32 default_value) {
- TRY_RESULT(value, get_json_object_field(object, name, JsonValue::Type::Number, is_optional));
- if (value.type() == JsonValue::Type::Null) {
+ for (auto &field_value : object) {
+ if (field_value.first == name) {
+ if (field_value.second.type() == JsonValue::Type::String) {
+ return to_integer_safe<int32>(field_value.second.get_string());
+ }
+ if (field_value.second.type() == JsonValue::Type::Number) {
+ return to_integer_safe<int32>(field_value.second.get_number());
+ }
+
+ return Status::Error(400, PSLICE() << "Field \"" << name << "\" must be of type Number");
+ }
+ }
+ if (is_optional) {
return default_value;
}
- return to_integer_safe<int32>(value.get_number());
+ return Status::Error(400, PSLICE() << "Can't find field \"" << name << "\"");
+}
+
+Result<int64> get_json_object_long_field(JsonObject &object, Slice name, bool is_optional, int64 default_value) {
+ for (auto &field_value : object) {
+ if (field_value.first == name) {
+ if (field_value.second.type() == JsonValue::Type::String) {
+ return to_integer_safe<int64>(field_value.second.get_string());
+ }
+ if (field_value.second.type() == JsonValue::Type::Number) {
+ return to_integer_safe<int64>(field_value.second.get_number());
+ }
+
+ return Status::Error(400, PSLICE() << "Field \"" << name << "\" must be a Number");
+ }
+ }
+ if (is_optional) {
+ return default_value;
+ }
+ return Status::Error(400, PSLICE() << "Can't find field \"" << name << "\"");
}
Result<double> get_json_object_double_field(JsonObject &object, Slice name, bool is_optional, double default_value) {
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.h
index 735c4b29ec..90035cd6e2 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,20 +15,11 @@
#include "td/utils/StringBuilder.h"
#include <new>
-#include <tuple>
#include <type_traits>
#include <utility>
namespace td {
-template <class... Args>
-std::tuple<const Args &...> ctie(const Args &... args) TD_WARN_UNUSED_RESULT;
-
-template <class... Args>
-std::tuple<const Args &...> ctie(const Args &... args) {
- return std::tie(args...);
-}
-
class JsonTrue {
public:
friend StringBuilder &operator<<(StringBuilder &sb, const JsonTrue &val) {
@@ -104,7 +95,7 @@ class JsonFloat {
class JsonOneChar {
public:
- explicit JsonOneChar(unsigned int c) : c_(c) {
+ explicit JsonOneChar(uint32 c) : c_(c) {
}
friend StringBuilder &operator<<(StringBuilder &sb, const JsonOneChar &val) {
@@ -114,12 +105,12 @@ class JsonOneChar {
}
private:
- unsigned int c_;
+ uint32 c_;
};
class JsonChar {
public:
- explicit JsonChar(unsigned int c) : c_(c) {
+ explicit JsonChar(uint32 c) : c_(c) {
}
friend StringBuilder &operator<<(StringBuilder &sb, const JsonChar &val) {
auto c = val.c_;
@@ -138,7 +129,7 @@ class JsonChar {
}
private:
- unsigned int c_;
+ uint32 c_;
};
class JsonRaw {
@@ -181,7 +172,7 @@ class JsonObjectScope;
class JsonBuilder {
public:
- explicit JsonBuilder(StringBuilder &&sb) : sb_(std::move(sb)) {
+ explicit JsonBuilder(StringBuilder &&sb = {}, int32 offset = -1) : sb_(std::move(sb)), offset_(offset) {
}
StringBuilder &string_builder() {
return sb_;
@@ -191,22 +182,48 @@ class JsonBuilder {
JsonArrayScope enter_array() TD_WARN_UNUSED_RESULT;
JsonObjectScope enter_object() TD_WARN_UNUSED_RESULT;
+ int32 offset() const {
+ return offset_;
+ }
+ bool is_pretty() const {
+ return offset_ >= 0;
+ }
+ void print_offset() {
+ if (offset_ >= 0) {
+ sb_ << '\n';
+ for (int x = 0; x < offset_; x++) {
+ sb_ << " ";
+ }
+ }
+ }
+ void dec_offset() {
+ if (offset_ >= 0) {
+ CHECK(offset_ > 0);
+ offset_--;
+ }
+ }
+ void inc_offset() {
+ if (offset_ >= 0) {
+ offset_++;
+ }
+ }
+
private:
StringBuilder sb_;
JsonScope *scope_ = nullptr;
+ int32 offset_;
};
class Jsonable {};
class JsonScope {
public:
- explicit JsonScope(JsonBuilder *jb) : sb_(&jb->sb_), jb_(jb) {
- save_scope_ = jb_->scope_;
+ explicit JsonScope(JsonBuilder *jb) : sb_(&jb->sb_), jb_(jb), save_scope_(jb->scope_) {
jb_->scope_ = this;
CHECK(is_active());
}
JsonScope(const JsonScope &other) = delete;
- JsonScope(JsonScope &&other) : sb_(other.sb_), jb_(other.jb_), save_scope_(other.save_scope_) {
+ JsonScope(JsonScope &&other) noexcept : sb_(other.sb_), jb_(other.jb_), save_scope_(other.save_scope_) {
other.jb_ = nullptr;
}
JsonScope &operator=(const JsonScope &) = delete;
@@ -272,9 +289,7 @@ class JsonScope {
*sb_ << x;
return *this;
}
- JsonScope &operator<<(bool x) {
- return *this << JsonBool(x);
- }
+ JsonScope &operator<<(bool x) = delete;
JsonScope &operator<<(int32 x) {
return *this << JsonInt(x);
}
@@ -284,8 +299,6 @@ class JsonScope {
JsonScope &operator<<(double x) {
return *this << JsonFloat(x);
}
- template <class T>
- JsonScope &operator<<(const T *x); // not implemented
template <size_t N>
JsonScope &operator<<(const char (&x)[N]) {
return *this << JsonString(Slice(x));
@@ -293,15 +306,12 @@ class JsonScope {
JsonScope &operator<<(const char *x) {
return *this << JsonString(Slice(x));
}
- JsonScope &operator<<(const string &x) {
- return *this << JsonString(Slice(x));
- }
JsonScope &operator<<(Slice x) {
return *this << JsonString(x);
}
};
-class JsonValueScope : public JsonScope {
+class JsonValueScope final : public JsonScope {
public:
using JsonScope::JsonScope;
template <class T>
@@ -326,9 +336,10 @@ class JsonValueScope : public JsonScope {
bool was_ = false;
};
-class JsonArrayScope : public JsonScope {
+class JsonArrayScope final : public JsonScope {
public:
explicit JsonArrayScope(JsonBuilder *jb) : JsonScope(jb) {
+ jb->inc_offset();
*sb_ << "[";
}
JsonArrayScope(JsonArrayScope &&other) = default;
@@ -338,10 +349,16 @@ class JsonArrayScope : public JsonScope {
}
}
void leave() {
+ jb_->dec_offset();
+ jb_->print_offset();
*sb_ << "]";
}
template <class T>
JsonArrayScope &operator<<(const T &x) {
+ return (*this)(x);
+ }
+ template <class T>
+ JsonArrayScope &operator()(const T &x) {
enter_value() << x;
return *this;
}
@@ -352,6 +369,7 @@ class JsonArrayScope : public JsonScope {
} else {
is_first_ = true;
}
+ jb_->print_offset();
return jb_->enter_value();
}
@@ -359,9 +377,10 @@ class JsonArrayScope : public JsonScope {
bool is_first_ = false;
};
-class JsonObjectScope : public JsonScope {
+class JsonObjectScope final : public JsonScope {
public:
explicit JsonObjectScope(JsonBuilder *jb) : JsonScope(jb) {
+ jb->inc_offset();
*sb_ << "{";
}
JsonObjectScope(JsonObjectScope &&other) = default;
@@ -371,23 +390,26 @@ class JsonObjectScope : public JsonScope {
}
}
void leave() {
+ jb_->dec_offset();
+ jb_->print_offset();
*sb_ << "}";
}
- template <class S, class T>
- JsonObjectScope &operator<<(std::tuple<S, T> key_value) {
- return *this << std::pair<S, T>(std::get<0>(key_value), std::get<1>(key_value));
- }
- template <class S, class T>
- JsonObjectScope &operator<<(std::pair<S, T> key_value) {
+ template <class T>
+ JsonObjectScope &operator()(Slice key, T &&value) {
CHECK(is_active());
if (is_first_) {
*sb_ << ",";
} else {
is_first_ = true;
}
- jb_->enter_value() << key_value.first;
- *sb_ << ":";
- jb_->enter_value() << key_value.second;
+ jb_->print_offset();
+ jb_->enter_value() << key;
+ if (jb_->is_pretty()) {
+ *sb_ << " : ";
+ } else {
+ *sb_ << ":";
+ }
+ jb_->enter_value() << value;
return *this;
}
JsonObjectScope &operator<<(const JsonRaw &key_value) {
@@ -426,7 +448,7 @@ class JsonValue;
using JsonObject = vector<std::pair<MutableSlice, JsonValue>>;
using JsonArray = vector<JsonValue>;
-class JsonValue : public Jsonable {
+class JsonValue final : private Jsonable {
public:
enum class Type { Null, Number, Boolean, String, Array, Object };
@@ -437,10 +459,10 @@ class JsonValue : public Jsonable {
~JsonValue() {
destroy();
}
- JsonValue(JsonValue &&other) : JsonValue() {
+ JsonValue(JsonValue &&other) noexcept : JsonValue() {
init(std::move(other));
}
- JsonValue &operator=(JsonValue &&other) {
+ JsonValue &operator=(JsonValue &&other) noexcept {
if (&other == this) {
return *this;
}
@@ -557,7 +579,7 @@ class JsonValue : public Jsonable {
case Type::Object: {
auto object = scope->enter_object();
for (auto &key_value : get_object()) {
- object << ctie(JsonString(key_value.first), key_value.second);
+ object(key_value.first, key_value.second);
}
break;
}
@@ -645,25 +667,25 @@ class JsonValue : public Jsonable {
inline StringBuilder &operator<<(StringBuilder &sb, JsonValue::Type type) {
switch (type) {
- case JsonValue::Type::Object:
- return sb << "JsonObject";
- case JsonValue::Type::Boolean:
- return sb << "JsonBoolean";
case JsonValue::Type::Null:
- return sb << "JsonNull";
+ return sb << "Null";
case JsonValue::Type::Number:
- return sb << "JsonNumber";
- case JsonValue::Type::Array:
- return sb << "JsonArray";
+ return sb << "Number";
+ case JsonValue::Type::Boolean:
+ return sb << "Boolean";
case JsonValue::Type::String:
- return sb << "JsonString";
+ return sb << "String";
+ case JsonValue::Type::Array:
+ return sb << "Array";
+ case JsonValue::Type::Object:
+ return sb << "Object";
default:
UNREACHABLE();
return sb;
}
}
-class VirtuallyJsonable : public Jsonable {
+class VirtuallyJsonable : private Jsonable {
public:
virtual void store(JsonValueScope *scope) const = 0;
VirtuallyJsonable() = default;
@@ -674,11 +696,11 @@ class VirtuallyJsonable : public Jsonable {
virtual ~VirtuallyJsonable() = default;
};
-class VirtuallyJsonableInt : public VirtuallyJsonable {
+class VirtuallyJsonableInt final : public VirtuallyJsonable {
public:
explicit VirtuallyJsonableInt(int32 value) : value_(value) {
}
- void store(JsonValueScope *scope) const override {
+ void store(JsonValueScope *scope) const final {
*scope << JsonInt(value_);
}
@@ -686,11 +708,11 @@ class VirtuallyJsonableInt : public VirtuallyJsonable {
int32 value_;
};
-class VirtuallyJsonableLong : public VirtuallyJsonable {
+class VirtuallyJsonableLong final : public VirtuallyJsonable {
public:
explicit VirtuallyJsonableLong(int64 value) : value_(value) {
}
- void store(JsonValueScope *scope) const override {
+ void store(JsonValueScope *scope) const final {
*scope << JsonLong(value_);
}
@@ -698,11 +720,11 @@ class VirtuallyJsonableLong : public VirtuallyJsonable {
int64 value_;
};
-class VirtuallyJsonableString : public VirtuallyJsonable {
+class VirtuallyJsonableString final : public VirtuallyJsonable {
public:
explicit VirtuallyJsonableString(Slice value) : value_(value) {
}
- void store(JsonValueScope *scope) const override {
+ void store(JsonValueScope *scope) const final {
*scope << JsonString(value_);
}
@@ -716,8 +738,8 @@ Status json_string_skip(Parser &parser) TD_WARN_UNUSED_RESULT;
Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) TD_WARN_UNUSED_RESULT;
Status do_json_skip(Parser &parser, int32 max_depth) TD_WARN_UNUSED_RESULT;
-inline Result<JsonValue> json_decode(MutableSlice from) {
- Parser parser(from);
+inline Result<JsonValue> json_decode(MutableSlice json) {
+ Parser parser(json);
const int32 DEFAULT_MAX_DEPTH = 100;
auto result = do_json_decode(parser, DEFAULT_MAX_DEPTH);
if (result.is_ok()) {
@@ -730,17 +752,92 @@ inline Result<JsonValue> json_decode(MutableSlice from) {
}
template <class StrT, class ValT>
-StrT json_encode(const ValT &val) {
- auto buf_len = 1 << 19;
+StrT json_encode(const ValT &val, bool pretty = false) {
+ auto buf_len = 1 << 18;
auto buf = StackAllocator::alloc(buf_len);
- JsonBuilder jb(StringBuilder(buf.as_slice()));
+ JsonBuilder jb(StringBuilder(buf.as_slice(), true), pretty ? 0 : -1);
jb.enter_value() << val;
- LOG_IF(ERROR, jb.string_builder().is_error()) << "Json buffer overflow";
+ if (pretty) {
+ jb.string_builder() << "\n";
+ }
+ LOG_IF(ERROR, jb.string_builder().is_error()) << "JSON buffer overflow";
auto slice = jb.string_builder().as_cslice();
return StrT(slice.begin(), slice.size());
}
-bool has_json_object_field(JsonObject &object, Slice name);
+template <class T>
+class ToJsonImpl final : private Jsonable {
+ public:
+ explicit ToJsonImpl(const T &value) : value_(value) {
+ }
+ void store(JsonValueScope *scope) const {
+ to_json(*scope, value_);
+ }
+
+ private:
+ const T &value_;
+};
+
+template <class T>
+auto ToJson(const T &value) {
+ return ToJsonImpl<T>(value);
+}
+
+template <class T>
+void to_json(JsonValueScope &jv, const T &value) {
+ jv << value;
+}
+
+template <class F>
+class JsonObjectImpl : private Jsonable {
+ public:
+ explicit JsonObjectImpl(F &&f) : f_(std::forward<F>(f)) {
+ }
+ void store(JsonValueScope *scope) const {
+ auto object = scope->enter_object();
+ f_(object);
+ }
+
+ private:
+ F f_;
+};
+
+template <class F>
+auto json_object(F &&f) {
+ return JsonObjectImpl<F>(std::forward<F>(f));
+}
+
+template <class F>
+class JsonArrayImpl : private Jsonable {
+ public:
+ explicit JsonArrayImpl(F &&f) : f_(std::forward<F>(f)) {
+ }
+ void store(JsonValueScope *scope) const {
+ auto array = scope->enter_array();
+ f_(array);
+ }
+
+ private:
+ F f_;
+};
+
+template <class F>
+auto json_array(F &&f) {
+ return JsonArrayImpl<F>(std::forward<F>(f));
+}
+
+template <class A, class F>
+auto json_array(const A &a, F &&f) {
+ return json_array([&a, &f](auto &arr) {
+ for (auto &x : a) {
+ arr(f(x));
+ }
+ });
+}
+
+bool has_json_object_field(const JsonObject &object, Slice name);
+
+JsonValue get_json_object_field_force(JsonObject &object, Slice name) TD_WARN_UNUSED_RESULT;
Result<JsonValue> get_json_object_field(JsonObject &object, Slice name, JsonValue::Type type,
bool is_optional = true) TD_WARN_UNUSED_RESULT;
@@ -751,6 +848,9 @@ Result<bool> get_json_object_bool_field(JsonObject &object, Slice name, bool is_
Result<int32> get_json_object_int_field(JsonObject &object, Slice name, bool is_optional = true,
int32 default_value = 0) TD_WARN_UNUSED_RESULT;
+Result<int64> get_json_object_long_field(JsonObject &object, Slice name, bool is_optional = true,
+ int64 default_value = 0) TD_WARN_UNUSED_RESULT;
+
Result<double> get_json_object_double_field(JsonObject &object, Slice name, bool is_optional = true,
double default_value = 0.0) TD_WARN_UNUSED_RESULT;
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/List.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/List.h
index 1606c44d2b..4f9bb9877f 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/List.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/List.h
@@ -1,12 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
namespace td {
@@ -24,23 +24,23 @@ struct ListNode {
ListNode(const ListNode &) = delete;
ListNode &operator=(const ListNode &) = delete;
- ListNode(ListNode &&other) {
+ ListNode(ListNode &&other) noexcept {
if (other.empty()) {
clear();
} else {
- ListNode *head = other.prev;
- other.remove();
- head->put(this);
+ init_from(std::move(other));
}
}
- ListNode &operator=(ListNode &&other) {
+ ListNode &operator=(ListNode &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+
this->remove();
if (!other.empty()) {
- ListNode *head = other.prev;
- other.remove();
- head->put(this);
+ init_from(std::move(other));
}
return *this;
@@ -58,11 +58,12 @@ struct ListNode {
}
void put(ListNode *other) {
- other->connect(next);
- this->connect(other);
+ DCHECK(other->empty());
+ put_unsafe(other);
}
void put_back(ListNode *other) {
+ DCHECK(other->empty());
prev->connect(other);
other->connect(this);
}
@@ -82,11 +83,47 @@ struct ListNode {
return next == this;
}
- private:
+ ListNode *begin() {
+ return next;
+ }
+ ListNode *end() {
+ return this;
+ }
+ const ListNode *begin() const {
+ return next;
+ }
+ const ListNode *end() const {
+ return this;
+ }
+ ListNode *get_next() {
+ return next;
+ }
+ ListNode *get_prev() {
+ return prev;
+ }
+ const ListNode *get_next() const {
+ return next;
+ }
+ const ListNode *get_prev() const {
+ return prev;
+ }
+
+ protected:
void clear() {
next = this;
prev = this;
}
+
+ void init_from(ListNode &&other) {
+ ListNode *head = other.prev;
+ other.remove();
+ head->put_unsafe(this);
+ }
+
+ void put_unsafe(ListNode *other) {
+ other->connect(next);
+ this->connect(other);
+ }
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MapNode.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MapNode.h
new file mode 100644
index 0000000000..cad2ae9b36
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MapNode.h
@@ -0,0 +1,166 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+
+#include <new>
+#include <type_traits>
+#include <utility>
+
+namespace td {
+
+template <class KeyT, class ValueT, class Enable = void>
+struct MapNode {
+ using first_type = KeyT;
+ using second_type = ValueT;
+ using public_key_type = KeyT;
+ using public_type = MapNode;
+
+ KeyT first{};
+ union {
+ ValueT second;
+ };
+
+ const KeyT &key() const {
+ return first;
+ }
+
+ MapNode &get_public() {
+ return *this;
+ }
+
+ const MapNode &get_public() const {
+ return *this;
+ }
+
+ MapNode() {
+ }
+ MapNode(KeyT key, ValueT value) : first(std::move(key)) {
+ new (&second) ValueT(std::move(value));
+ DCHECK(!empty());
+ }
+ MapNode(const MapNode &other) = delete;
+ MapNode &operator=(const MapNode &other) = delete;
+ MapNode(MapNode &&other) noexcept {
+ *this = std::move(other);
+ }
+ void operator=(MapNode &&other) noexcept {
+ DCHECK(empty());
+ DCHECK(!other.empty());
+ first = std::move(other.first);
+ other.first = KeyT();
+ new (&second) ValueT(std::move(other.second));
+ other.second.~ValueT();
+ }
+ ~MapNode() {
+ if (!empty()) {
+ second.~ValueT();
+ }
+ }
+
+ void copy_from(const MapNode &other) {
+ DCHECK(empty());
+ DCHECK(!other.empty());
+ first = other.first;
+ new (&second) ValueT(other.second);
+ }
+
+ bool empty() const {
+ return is_hash_table_key_empty(first);
+ }
+
+ void clear() {
+ DCHECK(!empty());
+ first = KeyT();
+ second.~ValueT();
+ DCHECK(empty());
+ }
+
+ template <class... ArgsT>
+ void emplace(KeyT key, ArgsT &&...args) {
+ DCHECK(empty());
+ first = std::move(key);
+ new (&second) ValueT(std::forward<ArgsT>(args)...);
+ DCHECK(!empty());
+ }
+};
+
+template <class KeyT, class ValueT>
+struct MapNode<KeyT, ValueT, typename std::enable_if_t<(sizeof(KeyT) + sizeof(ValueT) > 28 * sizeof(void *))>> {
+ struct Impl {
+ using first_type = KeyT;
+ using second_type = ValueT;
+
+ KeyT first{};
+ union {
+ ValueT second;
+ };
+
+ template <class InputKeyT, class... ArgsT>
+ Impl(InputKeyT &&key, ArgsT &&...args) : first(std::forward<InputKeyT>(key)) {
+ new (&second) ValueT(std::forward<ArgsT>(args)...);
+ DCHECK(!is_hash_table_key_empty(first));
+ }
+ Impl(const Impl &other) = delete;
+ Impl &operator=(const Impl &other) = delete;
+ Impl(Impl &&other) = delete;
+ void operator=(Impl &&other) = delete;
+ ~Impl() {
+ second.~ValueT();
+ }
+ };
+
+ using first_type = KeyT;
+ using second_type = ValueT;
+ using public_key_type = KeyT;
+ using public_type = Impl;
+
+ unique_ptr<Impl> impl_;
+
+ const KeyT &key() const {
+ DCHECK(!empty());
+ return impl_->first;
+ }
+
+ Impl &get_public() {
+ return *impl_;
+ }
+
+ const Impl &get_public() const {
+ return *impl_;
+ }
+
+ MapNode() {
+ }
+ MapNode(KeyT key, ValueT value) : impl_(td::make_unique<Impl>(std::move(key), std::move(value))) {
+ }
+
+ void copy_from(const MapNode &other) {
+ DCHECK(empty());
+ DCHECK(!other.empty());
+ impl_ = td::make_unique<Impl>(other.impl_->first, other.impl_->second);
+ }
+
+ bool empty() const {
+ return impl_ == nullptr;
+ }
+
+ void clear() {
+ DCHECK(!empty());
+ impl_ = nullptr;
+ }
+
+ template <class... ArgsT>
+ void emplace(KeyT key, ArgsT &&...args) {
+ DCHECK(empty());
+ impl_ = td::make_unique<Impl>(std::move(key), std::forward<ArgsT>(args)...);
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MemoryLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MemoryLog.h
index aa125df2f7..04b03b60ee 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MemoryLog.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MemoryLog.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -17,15 +17,27 @@
namespace td {
template <int buffer_size = 32 * (1 << 10)>
-class MemoryLog : public LogInterface {
+class MemoryLog final : public LogInterface {
static constexpr size_t MAX_OUTPUT_SIZE = buffer_size / 16 < (8 << 10) ? buffer_size / 16 : (8 << 10);
+ static_assert((buffer_size & (buffer_size - 1)) == 0, "Buffer size must be power of 2");
+ static_assert(buffer_size >= (8 << 10), "Too small buffer size");
+
public:
MemoryLog() {
std::memset(buffer_, ' ', sizeof(buffer_));
}
- void append(CSlice new_slice, int log_level) override {
+ Slice get_buffer() const {
+ return Slice(buffer_, sizeof(buffer_));
+ }
+
+ size_t get_pos() const {
+ return pos_ & (buffer_size - 1);
+ }
+
+ private:
+ void do_append(int log_level, CSlice new_slice) final {
Slice slice = new_slice;
slice.truncate(MAX_OUTPUT_SIZE);
while (!slice.empty() && slice.back() == '\n') {
@@ -34,50 +46,34 @@ class MemoryLog : public LogInterface {
size_t slice_size = slice.size();
CHECK(slice_size * 3 < buffer_size);
size_t pad_size = ((slice_size + 15) & ~15) - slice_size;
- constexpr size_t magic_size = 16;
- uint32 total_size = static_cast<uint32>(slice_size + pad_size + magic_size);
- uint32 real_pos = pos_.fetch_add(total_size, std::memory_order_relaxed);
+ constexpr size_t MAGIC_SIZE = 16;
+ auto total_size = static_cast<uint32>(slice_size + pad_size + MAGIC_SIZE);
+ auto real_pos = pos_.fetch_add(total_size, std::memory_order_relaxed);
CHECK((total_size & 15) == 0);
uint32 start_pos = real_pos & (buffer_size - 1);
uint32 end_pos = start_pos + total_size;
if (likely(end_pos <= buffer_size)) {
- std::memcpy(&buffer_[start_pos + magic_size], slice.data(), slice_size);
- std::memcpy(&buffer_[start_pos + magic_size + slice_size], " ", pad_size);
+ std::memcpy(&buffer_[start_pos + MAGIC_SIZE], slice.data(), slice_size);
+ std::memcpy(&buffer_[start_pos + MAGIC_SIZE + slice_size], " ", pad_size);
} else {
- size_t first = buffer_size - start_pos - magic_size;
+ size_t first = buffer_size - start_pos - MAGIC_SIZE;
size_t second = slice_size - first;
- std::memcpy(&buffer_[start_pos + magic_size], slice.data(), first);
+ std::memcpy(&buffer_[start_pos + MAGIC_SIZE], slice.data(), first);
std::memcpy(&buffer_[0], slice.data() + first, second);
- std::memcpy(&buffer_[second], " ", pad_size);
+ std::memcpy(&buffer_[second], " ", pad_size);
}
CHECK((start_pos & 15) == 0);
- CHECK(start_pos <= buffer_size - magic_size);
+ CHECK(start_pos <= buffer_size - MAGIC_SIZE);
buffer_[start_pos] = '\n';
- size_t printed = std::snprintf(&buffer_[start_pos + 1], magic_size - 1, "LOG:%08x: ", real_pos);
- CHECK(printed == magic_size - 2);
- buffer_[start_pos + magic_size - 1] = ' ';
-
- if (log_level == VERBOSITY_NAME(FATAL)) {
- process_fatal_error(new_slice);
- }
+ size_t printed = std::snprintf(&buffer_[start_pos + 1], MAGIC_SIZE - 1, "LOG:%08x: ", real_pos);
+ CHECK(printed == MAGIC_SIZE - 2);
+ buffer_[start_pos + MAGIC_SIZE - 1] = ' ';
}
- void rotate() override {
- }
-
- Slice get_buffer() const {
- return Slice(buffer_, sizeof(buffer_));
- }
-
- size_t get_pos() const {
- return pos_ & (buffer_size - 1);
- }
-
- private:
char buffer_[buffer_size];
- std::atomic<uint32> pos_;
+ std::atomic<uint32> pos_{0};
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.cpp
index 75c4fe34b5..a7dde2405f 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.h
index 11210ceb30..ccaf029a52 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MovableValue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MovableValue.h
index 939bf51f28..7a8ef459e0 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MovableValue.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MovableValue.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,16 +14,18 @@ class MovableValue {
MovableValue() = default;
MovableValue(T val) : val_(val) {
}
- MovableValue(MovableValue &&other) : val_(other.val_) {
+ MovableValue(MovableValue &&other) noexcept : val_(other.val_) {
other.clear();
}
- MovableValue &operator=(MovableValue &&other) {
- val_ = other.val_;
- other.clear();
+ MovableValue &operator=(MovableValue &&other) noexcept {
+ if (this != &other) {
+ val_ = other.val_;
+ other.clear();
+ }
return *this;
}
- MovableValue(const MovableValue &) = delete;
- MovableValue &operator=(const MovableValue &) = delete;
+ MovableValue(const MovableValue &) = default;
+ MovableValue &operator=(const MovableValue &) = default;
~MovableValue() = default;
void clear() {
diff --git a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCounter.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.cpp
index 1952e6abd9..1fcbc04341 100644
--- a/protocols/Telegram/tdlib/td/td/telegram/net/NetQueryCounter.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.cpp
@@ -1,13 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/telegram/net/NetQueryCounter.h"
+#include "td/utils/MpmcQueue.h"
namespace td {
+namespace detail {
-std::atomic<uint64> NetQueryCounter::net_query_cnt_{0};
+MpmcStat stat_;
+} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.h
index ae65554b72..f1f12a37f4 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,7 +14,7 @@
#include "td/utils/format.h"
#include "td/utils/HazardPointers.h"
#include "td/utils/logging.h"
-#include "td/utils/port/thread.h"
+#include "td/utils/port/sleep.h"
#include "td/utils/ScopeGuard.h"
#include <array>
@@ -23,6 +23,7 @@
namespace td {
namespace detail {
+
struct MpmcStat {
void alloc_ok(size_t thread_id) {
s(thread_id).alloc_ok_cnt++;
@@ -64,8 +65,10 @@ struct MpmcStat {
return arr[thread_id];
}
};
+
+extern MpmcStat stat_;
+
} // namespace detail
-//detail::MpmcStat stat_;
template <class T>
class OneValue {
@@ -95,31 +98,35 @@ class OneValue {
private:
enum Type : int { Empty = 0, Taken, Value };
std::atomic<int> state_{Empty};
- T value_;
+ T value_{};
};
template <class T>
class OneValue<T *> {
public:
bool set_value(T *value) {
- T *was = nullptr;
+ T *was = Empty();
return state_.compare_exchange_strong(was, value, std::memory_order_acq_rel);
}
bool get_value(T *&value) {
value = state_.exchange(Taken(), std::memory_order_acq_rel);
- return value != nullptr;
+ return value != Empty();
}
void reset() {
- state_ = nullptr;
+ state_ = Empty();
}
OneValue() {
}
private:
- std::atomic<T *> state_{nullptr};
- T *Taken() {
- static T xxx;
- return &xxx;
+ std::atomic<T *> state_{Empty()};
+ static T *Empty() {
+ static int64 xxx;
+ return reinterpret_cast<T *>(&xxx);
+ }
+ static T *Taken() {
+ static int64 xxx;
+ return reinterpret_cast<T *>(&xxx);
}
};
@@ -149,6 +156,11 @@ class MpmcQueueBlock {
//returns Ok, Empty or Closed
PopStatus try_pop(T &value) {
while (true) {
+ // this check slows 1:1 case but prevents writer starvation in 1:N case
+ if (write_pos_.load(std::memory_order_relaxed) <= read_pos_.load(std::memory_order_relaxed) &&
+ read_pos_.load(std::memory_order_relaxed) < nodes_.size()) {
+ return PopStatus::Empty;
+ }
auto read_pos = read_pos_.fetch_add(1, std::memory_order_relaxed);
if (read_pos >= nodes_.size()) {
return PopStatus::Closed;
@@ -199,7 +211,7 @@ class MpmcQueueOld {
return "Mpmc queue (fetch and add array queue)";
}
MpmcQueueOld(size_t block_size, size_t threads_n) : block_size_{block_size}, hazard_pointers_{threads_n} {
- auto node = std::make_unique<Node>(block_size_);
+ auto node = make_unique<Node>(block_size_);
write_pos_ = node.get();
read_pos_ = node.get();
node.release();
@@ -217,7 +229,7 @@ class MpmcQueueOld {
delete to_delete;
}
//stat_.dump();
- //stat_ = MpmcStat();
+ //stat_ = detail::MpmcStat();
}
size_t hazard_pointers_to_delele_size_unsafe() const {
@@ -231,7 +243,7 @@ class MpmcQueueOld {
using PopStatus = typename MpmcQueueBlock<T>::PopStatus;
void push(T value, size_t thread_id) {
- auto hazard_ptr_holder = hazard_pointers_.get_holder(thread_id, 0);
+ typename decltype(hazard_pointers_)::Holder hazard_ptr_holder(hazard_pointers_, thread_id, 0);
while (true) {
auto node = hazard_ptr_holder.protect(write_pos_);
auto status = node->block.push(value);
@@ -263,7 +275,7 @@ class MpmcQueueOld {
}
bool try_pop(T &value, size_t thread_id) {
- auto hazard_ptr_holder = hazard_pointers_.get_holder(thread_id, 0);
+ typename decltype(hazard_pointers_)::Holder hazard_ptr_holder(hazard_pointers_, thread_id, 0);
while (true) {
auto node = hazard_ptr_holder.protect(read_pos_);
auto status = node->block.try_pop(value);
@@ -293,7 +305,7 @@ class MpmcQueueOld {
if (try_pop(value, thread_id)) {
return value;
}
- td::this_thread::yield();
+ usleep_for(1);
}
}
@@ -306,9 +318,9 @@ class MpmcQueueOld {
MpmcQueueBlock<T> block;
//Got pad in MpmcQueueBlock
};
- std::atomic<Node *> write_pos_;
+ std::atomic<Node *> write_pos_{nullptr};
char pad[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)];
- std::atomic<Node *> read_pos_;
+ std::atomic<Node *> read_pos_{nullptr};
char pad2[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)];
size_t block_size_;
HazardPointers<Node, 1> hazard_pointers_;
@@ -324,7 +336,7 @@ class MpmcQueue {
return "NEW Mpmc queue (fetch and add array queue)";
}
MpmcQueue(size_t block_size, size_t threads_n) : hazard_pointers_{threads_n} {
- auto node = std::make_unique<Node>();
+ auto node = make_unique<Node>();
write_pos_ = node.get();
read_pos_ = node.get();
node.release();
@@ -417,7 +429,7 @@ class MpmcQueue {
if (try_pop(value, thread_id)) {
return value;
}
- td::this_thread::yield();
+ usleep_for(1);
}
}
@@ -438,9 +450,9 @@ class MpmcQueue {
char pad[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)];
//Got pad in MpmcQueueBlock
};
- std::atomic<Node *> write_pos_;
+ std::atomic<Node *> write_pos_{nullptr};
char pad[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)];
- std::atomic<Node *> read_pos_;
+ std::atomic<Node *> read_pos_{nullptr};
char pad2[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)];
HazardPointers<Node, 1> hazard_pointers_;
//Got pad in HazardPointers
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcWaiter.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcWaiter.h
index 0f48620e63..7ece498ff6 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcWaiter.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcWaiter.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,61 +7,81 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/port/thread.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/sleep.h"
+#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <mutex>
namespace td {
-class MpmcWaiter {
+class MpmcEagerWaiter {
public:
- int wait(int yields, uint32 worker_id) {
- if (yields < RoundsTillSleepy) {
- td::this_thread::yield();
- return yields + 1;
- } else if (yields == RoundsTillSleepy) {
+ struct Slot {
+ private:
+ friend class MpmcEagerWaiter;
+ int yields;
+ uint32 worker_id;
+ };
+ static void init_slot(Slot &slot, uint32 worker_id) {
+ slot.yields = 0;
+ slot.worker_id = worker_id;
+ }
+
+ void wait(Slot &slot) {
+ if (slot.yields < RoundsTillSleepy) {
+ yield();
+ slot.yields++;
+ } else if (slot.yields == RoundsTillSleepy) {
auto state = state_.load(std::memory_order_relaxed);
if (!State::has_worker(state)) {
- auto new_state = State::with_worker(state, worker_id);
- if (state_.compare_exchange_strong(state, new_state)) {
- td::this_thread::yield();
- return yields + 1;
+ auto new_state = State::with_worker(state, slot.worker_id);
+ if (state_.compare_exchange_strong(state, new_state, std::memory_order_acq_rel)) {
+ yield();
+ slot.yields++;
+ return;
}
if (state == State::awake()) {
- return 0;
+ slot.yields = 0;
+ return;
}
}
- td::this_thread::yield();
- return 0;
- } else if (yields < RoundsTillAsleep) {
+ yield();
+ slot.yields = 0;
+ } else if (slot.yields < RoundsTillAsleep) {
auto state = state_.load(std::memory_order_acquire);
- if (State::still_sleepy(state, worker_id)) {
- td::this_thread::yield();
- return yields + 1;
+ if (State::still_sleepy(state, slot.worker_id)) {
+ yield();
+ slot.yields++;
+ return;
}
- return 0;
+ slot.yields = 0;
} else {
auto state = state_.load(std::memory_order_acquire);
- if (State::still_sleepy(state, worker_id)) {
+ if (State::still_sleepy(state, slot.worker_id)) {
std::unique_lock<std::mutex> lock(mutex_);
if (state_.compare_exchange_strong(state, State::asleep(), std::memory_order_acq_rel)) {
condition_variable_.wait(lock);
}
}
- return 0;
+ slot.yields = 0;
}
}
- int stop_wait(int yields, uint32 worker_id) {
- if (yields > RoundsTillSleepy) {
+ void stop_wait(Slot &slot) {
+ if (slot.yields > RoundsTillSleepy) {
notify_cold();
}
- return 0;
+ slot.yields = 0;
+ }
+
+ void close() {
}
void notify() {
+ std::atomic_thread_fence(std::memory_order_seq_cst);
if (state_.load(std::memory_order_acquire) == State::awake()) {
return;
}
@@ -90,6 +110,7 @@ class MpmcWaiter {
}
};
enum { RoundsTillSleepy = 32, RoundsTillAsleep = 64 };
+ // enum { RoundsTillSleepy = 1, RoundsTillAsleep = 2 };
std::atomic<uint32> state_{State::awake()};
std::mutex mutex_;
std::condition_variable condition_variable_;
@@ -101,6 +122,216 @@ class MpmcWaiter {
condition_variable_.notify_all();
}
}
+ static void yield() {
+ // whatever, this is better than sched_yield
+ usleep_for(1);
+ }
};
+class MpmcSleepyWaiter {
+ public:
+ struct Slot {
+ private:
+ friend class MpmcSleepyWaiter;
+
+ enum State { Search, Work, Sleep } state_{Work};
+
+ void park() {
+ std::unique_lock<std::mutex> guard(mutex_);
+ condition_variable_.wait(guard, [&] { return unpark_flag_; });
+ unpark_flag_ = false;
+ }
+
+ bool cancel_park() {
+ auto res = unpark_flag_;
+ unpark_flag_ = false;
+ return res;
+ }
+
+ void unpark() {
+ //TODO: try to unlock guard before notify_all
+ std::unique_lock<std::mutex> guard(mutex_);
+ unpark_flag_ = true;
+ condition_variable_.notify_all();
+ }
+
+ std::mutex mutex_;
+ std::condition_variable condition_variable_;
+ bool unpark_flag_{false}; // TODO: move out of lock
+ int yield_cnt{0};
+ int32 worker_id{0};
+
+ public:
+ char padding[TD_CONCURRENCY_PAD];
+ };
+
+ // There are a lot of workers
+ // Each has a slot
+ //
+ // States of a worker:
+ // - searching for work | Search
+ // - processing work | Work
+ // - sleeping | Sleep
+ //
+ // When somebody adds a work it calls notify
+ //
+ // notify
+ // if there are workers in search phase do nothing.
+ // if all workers are awake do nothing
+ // otherwise wake some random worker
+ //
+ // Initially all workers are in Search mode.
+ //
+ // When worker found nothing it may try to call wait.
+ // This may put it in a Sleep for some time.
+ // After wait returns worker will be in Search state again.
+ //
+ // Suppose worker found a work and ready to process it.
+ // Then it may call stop_wait. This will cause transition from
+ // Search to Work state.
+ //
+ // Main invariant:
+ // After notify is called there should be at least on worker in Search or Work state.
+ // If possible - in Search state
+ //
+
+ static void init_slot(Slot &slot, int32 worker_id) {
+ slot.state_ = Slot::State::Work;
+ slot.unpark_flag_ = false;
+ slot.worker_id = worker_id;
+ VLOG(waiter) << "Init slot " << worker_id;
+ }
+
+ static constexpr int VERBOSITY_NAME(waiter) = VERBOSITY_NAME(DEBUG) + 10;
+ void wait(Slot &slot) {
+ if (slot.state_ == Slot::State::Work) {
+ VLOG(waiter) << "Work -> Search";
+ state_++;
+ slot.state_ = Slot::State::Search;
+ slot.yield_cnt = 0;
+ return;
+ }
+ if (slot.state_ == Slot::Search) {
+ if (slot.yield_cnt++ < 10 && false) {
+ // TODO some sleep backoff is possible
+ return;
+ }
+
+ slot.state_ = Slot::State::Sleep;
+ std::unique_lock<std::mutex> guard(sleepers_mutex_);
+ auto state_view = StateView(state_.fetch_add((1 << PARKING_SHIFT) - 1));
+ CHECK(state_view.searching_count != 0);
+ bool should_search = state_view.searching_count == 1;
+ if (closed_) {
+ return;
+ }
+ sleepers_.push_back(&slot);
+ LOG_CHECK(slot.unpark_flag_ == false) << slot.worker_id;
+ VLOG(waiter) << "Add to sleepers " << slot.worker_id;
+ //guard.unlock();
+ if (should_search) {
+ VLOG(waiter) << "Search -> Search once, then Sleep ";
+ return;
+ }
+ VLOG(waiter) << "Search -> Sleep " << state_view.searching_count << " " << state_view.parked_count;
+ }
+
+ CHECK(slot.state_ == Slot::State::Sleep);
+ VLOG(waiter) << "Park " << slot.worker_id;
+ slot.park();
+ VLOG(waiter) << "Resume " << slot.worker_id;
+ slot.state_ = Slot::State::Search;
+ slot.yield_cnt = 0;
+ }
+
+ void stop_wait(Slot &slot) {
+ if (slot.state_ == Slot::State::Work) {
+ return;
+ }
+ if (slot.state_ == Slot::State::Sleep) {
+ VLOG(waiter) << "Search once, then Sleep -> Work/Search " << slot.worker_id;
+ slot.state_ = Slot::State::Work;
+ std::unique_lock<std::mutex> guard(sleepers_mutex_);
+ auto it = std::find(sleepers_.begin(), sleepers_.end(), &slot);
+ if (it != sleepers_.end()) {
+ sleepers_.erase(it);
+ VLOG(waiter) << "Remove from sleepers " << slot.worker_id;
+ state_.fetch_sub((1 << PARKING_SHIFT) - 1);
+ guard.unlock();
+ } else {
+ guard.unlock();
+ VLOG(waiter) << "Not in sleepers" << slot.worker_id;
+ CHECK(slot.cancel_park());
+ }
+ }
+ VLOG(waiter) << "Search once, then Sleep -> Work " << slot.worker_id;
+ slot.state_ = Slot::State::Search;
+ auto state_view = StateView(state_.fetch_sub(1));
+ CHECK(state_view.searching_count != 0);
+ CHECK(state_view.searching_count < 1000);
+ bool should_notify = state_view.searching_count == 1;
+ if (should_notify) {
+ VLOG(waiter) << "Notify others";
+ notify();
+ }
+ VLOG(waiter) << "Search -> Work ";
+ slot.state_ = Slot::State::Work;
+ }
+
+ void notify() {
+ auto view = StateView(state_.load());
+ //LOG(ERROR) << view.parked_count;
+ if (view.searching_count > 0 || view.parked_count == 0) {
+ VLOG(waiter) << "Ingore notify: " << view.searching_count << " " << view.parked_count;
+ return;
+ }
+
+ VLOG(waiter) << "Notify: " << view.searching_count << " " << view.parked_count;
+ std::unique_lock<std::mutex> guard(sleepers_mutex_);
+
+ view = StateView(state_.load());
+ if (view.searching_count > 0) {
+ VLOG(waiter) << "Skip notify: got searching";
+ return;
+ }
+
+ CHECK(view.parked_count == static_cast<int>(sleepers_.size()));
+ if (sleepers_.empty()) {
+ VLOG(waiter) << "Skip notify: no sleepers";
+ return;
+ }
+
+ auto sleeper = sleepers_.back();
+ sleepers_.pop_back();
+ state_.fetch_sub((1 << PARKING_SHIFT) - 1);
+ VLOG(waiter) << "Unpark " << sleeper->worker_id;
+ sleeper->unpark();
+ }
+
+ void close() {
+ StateView state(state_.load());
+ LOG_CHECK(state.parked_count == 0) << state.parked_count;
+ LOG_CHECK(state.searching_count == 0) << state.searching_count;
+ }
+
+ private:
+ static constexpr int32 PARKING_SHIFT = 16;
+ struct StateView {
+ int32 parked_count;
+ int32 searching_count;
+ explicit StateView(int32 x) {
+ parked_count = x >> PARKING_SHIFT;
+ searching_count = x & ((1 << PARKING_SHIFT) - 1);
+ }
+ };
+ std::atomic<int32> state_{0};
+
+ std::mutex sleepers_mutex_;
+ vector<Slot *> sleepers_;
+
+ bool closed_ = false;
+};
+
+using MpmcWaiter = MpmcSleepyWaiter;
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscLinkQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscLinkQueue.h
index 4398c7503d..a1b05c2791 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscLinkQueue.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscLinkQueue.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,6 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include <atomic>
@@ -149,17 +148,17 @@ template <class Value>
class MpscLinkQueueUniquePtrNode {
public:
MpscLinkQueueUniquePtrNode() = default;
- explicit MpscLinkQueueUniquePtrNode(std::unique_ptr<Value> ptr) : ptr_(std::move(ptr)) {
+ explicit MpscLinkQueueUniquePtrNode(unique_ptr<Value> ptr) : ptr_(std::move(ptr)) {
}
MpscLinkQueueImpl::Node *to_mpsc_link_queue_node() {
return ptr_.release()->to_mpsc_link_queue_node();
}
- static MpscLinkQueueUniquePtrNode<Value> from_mpsc_link_queue_node(td::MpscLinkQueueImpl::Node *node) {
- return MpscLinkQueueUniquePtrNode<Value>(std::unique_ptr<Value>(Value::from_mpsc_link_queue_node(node)));
+ static MpscLinkQueueUniquePtrNode<Value> from_mpsc_link_queue_node(MpscLinkQueueImpl::Node *node) {
+ return MpscLinkQueueUniquePtrNode<Value>(unique_ptr<Value>(Value::from_mpsc_link_queue_node(node)));
}
- explicit operator bool() {
+ explicit operator bool() const noexcept {
return ptr_ != nullptr;
}
@@ -168,7 +167,7 @@ class MpscLinkQueueUniquePtrNode {
}
private:
- std::unique_ptr<Value> ptr_;
+ unique_ptr<Value> ptr_;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscPollableQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscPollableQueue.h
index 89d2df8693..f6de4bf280 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscPollableQueue.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscPollableQueue.h
@@ -1,57 +1,63 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/utils/common.h"
#include "td/utils/misc.h"
#include "td/utils/port/EventFd.h"
-#include "td/utils/SpinLock.h"
#if !TD_EVENTFD_UNSUPPORTED
-#if !TD_WINDOWS
-#include <poll.h>
-#include <sched.h>
-#endif
+
+#include "td/utils/port/Mutex.h"
#include <utility>
namespace td {
// interface like in PollableQueue
-template <class ValueT>
+template <class T>
class MpscPollableQueue {
public:
+ using ValueType = T;
+
int reader_wait_nonblock() {
auto ready = reader_vector_.size() - reader_pos_;
if (ready != 0) {
return narrow_cast<int>(ready);
}
- auto guard = lock_.lock();
- if (writer_vector_.empty()) {
+ for (int i = 0; i < 2; i++) {
+ auto guard = lock_.lock();
+ if (writer_vector_.empty()) {
+ if (i == 1) {
+ wait_event_fd_ = true;
+ return 0;
+ }
+ } else {
+ reader_vector_.clear();
+ reader_pos_ = 0;
+ std::swap(writer_vector_, reader_vector_);
+ return narrow_cast<int>(reader_vector_.size());
+ }
event_fd_.acquire();
- wait_event_fd_ = true;
- return 0;
- } else {
- reader_vector_.clear();
- reader_pos_ = 0;
- std::swap(writer_vector_, reader_vector_);
- return narrow_cast<int>(reader_vector_.size());
}
+ UNREACHABLE();
}
- ValueT reader_get_unsafe() {
+ ValueType reader_get_unsafe() {
return std::move(reader_vector_[reader_pos_++]);
}
void reader_flush() {
//nop
}
- void writer_put(ValueT value) {
+ void writer_put(ValueType value) {
auto guard = lock_.lock();
writer_vector_.push_back(std::move(value));
if (wait_event_fd_) {
wait_event_fd_ = false;
+ guard.reset();
event_fd_.release();
}
}
@@ -62,6 +68,11 @@ class MpscPollableQueue {
//nop
}
+ bool is_empty() {
+ auto guard = lock_.lock();
+ return writer_vector_.empty() && reader_vector_.empty();
+ }
+
void init() {
event_fd_.init();
}
@@ -75,35 +86,27 @@ class MpscPollableQueue {
}
}
-// Just example of usage
-#if !TD_WINDOWS
+ // Just an example of usage
int reader_wait() {
int res;
-
while ((res = reader_wait_nonblock()) == 0) {
- // TODO: reader_flush?
- pollfd fd;
- fd.fd = reader_get_event_fd().get_fd().get_native_fd();
- fd.events = POLLIN;
- poll(&fd, 1, -1);
+ reader_get_event_fd().wait(1000);
}
return res;
}
-#endif
private:
- SpinLock lock_;
+ Mutex lock_;
bool wait_event_fd_{false};
EventFd event_fd_;
- std::vector<ValueT> writer_vector_;
- std::vector<ValueT> reader_vector_;
+ std::vector<ValueType> writer_vector_;
+ std::vector<ValueType> reader_vector_;
size_t reader_pos_{0};
};
} // namespace td
#else
-#include "td/utils/logging.h"
namespace td {
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Named.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Named.h
index 202de5f7d4..251f14b86d 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Named.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Named.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/NullLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/NullLog.h
new file mode 100644
index 0000000000..71b9ad7d7c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/NullLog.h
@@ -0,0 +1,19 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+class NullLog final : public LogInterface {
+ void do_append(int /*log_level*/, CSlice /*slice*/) final {
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ObjectPool.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ObjectPool.h
index e6e4549dbb..8ee2a58566 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/ObjectPool.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ObjectPool.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -45,7 +45,7 @@ class ObjectPool {
// It is not very usual case of acquire/release use.
// Instead of publishing an object via some flag we do the opposite.
// We publish new generation via destruction of the data.
- // In usual case if we see a flag then we are able to use an object.
+ // In usual case if we see a flag, then we are able to use an object.
// In our case if we have used an object and it is already invalid, then generation will mismatch
bool is_alive() const {
if (!storage_) {
@@ -84,11 +84,11 @@ class ObjectPool {
OwnerPtr() = default;
OwnerPtr(const OwnerPtr &) = delete;
OwnerPtr &operator=(const OwnerPtr &) = delete;
- OwnerPtr(OwnerPtr &&other) : storage_(other.storage_), parent_(other.parent_) {
+ OwnerPtr(OwnerPtr &&other) noexcept : storage_(other.storage_), parent_(other.parent_) {
other.storage_ = nullptr;
other.parent_ = nullptr;
}
- OwnerPtr &operator=(OwnerPtr &&other) {
+ OwnerPtr &operator=(OwnerPtr &&other) noexcept {
if (this != &other) {
storage_ = other.storage_;
parent_ = other.parent_;
@@ -156,7 +156,7 @@ class ObjectPool {
};
template <class... ArgsT>
- OwnerPtr create(ArgsT &&... args) {
+ OwnerPtr create(ArgsT &&...args) {
Storage *storage = get_storage();
storage->init_data(std::forward<ArgsT>(args)...);
return OwnerPtr(storage, this);
@@ -189,7 +189,7 @@ class ObjectPool {
delete to_delete;
storage_count_--;
}
- CHECK(storage_count_.load() == 0) << storage_count_.load();
+ LOG_CHECK(storage_count_.load() == 0) << storage_count_.load();
}
private:
@@ -201,7 +201,7 @@ class ObjectPool {
std::atomic<int32> generation{1};
template <class... ArgsT>
- void init_data(ArgsT &&... args) {
+ void init_data(ArgsT &&...args) {
// new (&data) DataT(std::forward<ArgsT>(args)...);
data = DataT(std::forward<ArgsT>(args)...);
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Observer.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Observer.h
index 8511e0ce8b..33e8bc1a4c 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Observer.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Observer.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -22,13 +22,13 @@ class ObserverBase {
virtual void notify() = 0;
};
-class Observer : ObserverBase {
+class Observer final : private ObserverBase {
public:
Observer() = default;
explicit Observer(unique_ptr<ObserverBase> &&ptr) : observer_ptr_(std::move(ptr)) {
}
- void notify() override {
+ void notify() final {
if (observer_ptr_) {
observer_ptr_->notify();
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.cpp
new file mode 100644
index 0000000000..76571d4954
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.cpp
@@ -0,0 +1,263 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/OptionParser.h"
+
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/logging.h"
+#include "td/utils/PathView.h"
+#include "td/utils/SliceBuilder.h"
+
+#if TD_PORT_WINDOWS
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+#include "td/utils/port/wstring_convert.h"
+#endif
+#endif
+
+#if TD_PORT_WINDOWS
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+#include <shellapi.h>
+#endif
+#endif
+
+namespace td {
+
+void OptionParser::set_usage(Slice executable_name, Slice usage) {
+ PathView path_view(executable_name);
+ usage_ = PSTRING() << path_view.file_name() << " " << usage;
+}
+
+void OptionParser::set_description(string description) {
+ description_ = std::move(description);
+}
+
+void OptionParser::add_option(Option::Type type, char short_key, Slice long_key, Slice description,
+ std::function<Status(Slice)> callback) {
+ for (auto &option : options_) {
+ if ((short_key != '\0' && option.short_key == short_key) || (!long_key.empty() && long_key == option.long_key)) {
+ LOG(ERROR) << "Ignore duplicated option '" << (short_key == '\0' ? '-' : short_key) << "' '" << long_key << "'";
+ }
+ }
+ options_.push_back(Option{type, short_key, long_key.str(), description.str(), std::move(callback)});
+}
+
+void OptionParser::add_checked_option(char short_key, Slice long_key, Slice description,
+ std::function<Status(Slice)> callback) {
+ add_option(Option::Type::Arg, short_key, long_key, description, std::move(callback));
+}
+
+void OptionParser::add_checked_option(char short_key, Slice long_key, Slice description,
+ std::function<Status(void)> callback) {
+ add_option(Option::Type::NoArg, short_key, long_key, description,
+ [callback = std::move(callback)](Slice) { return callback(); });
+}
+
+void OptionParser::add_option(char short_key, Slice long_key, Slice description, std::function<void(Slice)> callback) {
+ add_option(Option::Type::Arg, short_key, long_key, description, [callback = std::move(callback)](Slice parameter) {
+ callback(parameter);
+ return Status::OK();
+ });
+}
+
+void OptionParser::add_option(char short_key, Slice long_key, Slice description, std::function<void(void)> callback) {
+ add_option(Option::Type::NoArg, short_key, long_key, description, [callback = std::move(callback)](Slice) {
+ callback();
+ return Status::OK();
+ });
+}
+
+void OptionParser::add_check(std::function<Status()> check) {
+ checks_.push_back(std::move(check));
+}
+
+Result<vector<char *>> OptionParser::run(int argc, char *argv[], int expected_non_option_count) {
+#if TD_PORT_WINDOWS
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+ LPWSTR *utf16_argv = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (utf16_argv == nullptr) {
+ return Status::Error("Failed to parse command line");
+ }
+ vector<string> args_storage(argc);
+ vector<char *> args(argc);
+ for (int i = 0; i < argc; i++) {
+ TRY_RESULT_ASSIGN(args_storage[i], from_wstring(utf16_argv[i]));
+ args[i] = &args_storage[i][0];
+ }
+ LocalFree(utf16_argv);
+ argv = &args[0];
+#endif
+#endif
+
+ return run_impl(argc, argv, expected_non_option_count);
+}
+
+Result<vector<char *>> OptionParser::run_impl(int argc, char *argv[], int expected_non_option_count) {
+ FlatHashMap<char, const Option *> short_options;
+ FlatHashMap<string, const Option *> long_options;
+ for (auto &opt : options_) {
+ if (opt.short_key != '\0') {
+ short_options[opt.short_key] = &opt;
+ }
+ if (!opt.long_key.empty()) {
+ long_options[opt.long_key] = &opt;
+ }
+ }
+
+ vector<char *> non_options;
+ for (int arg_pos = 1; arg_pos < argc; arg_pos++) {
+ const char *arg = argv[arg_pos];
+ if (arg[0] != '-' || arg[1] == '\0') {
+ non_options.push_back(argv[arg_pos]);
+ continue;
+ }
+ if (arg[1] == '-' && arg[2] == '\0') {
+ // "--"; after it everything is non-option
+ while (++arg_pos < argc) {
+ non_options.push_back(argv[arg_pos]);
+ }
+ break;
+ }
+
+ if (arg[1] == '-') {
+ // long option
+ Slice long_arg(arg + 2);
+ Slice parameter;
+ auto equal_pos = long_arg.find('=');
+ bool has_equal = equal_pos != Slice::npos;
+ if (has_equal) {
+ parameter = long_arg.substr(equal_pos + 1);
+ long_arg = long_arg.substr(0, equal_pos);
+ }
+
+ auto it = long_options.find(long_arg.str());
+ if (it == long_options.end()) {
+ return Status::Error(PSLICE() << "Option \"" << long_arg << "\" is unrecognized");
+ }
+
+ auto option = it->second;
+ switch (option->type) {
+ case Option::Type::NoArg:
+ if (has_equal) {
+ return Status::Error(PSLICE() << "Option \"" << long_arg << "\" must not have an argument");
+ }
+ break;
+ case Option::Type::Arg:
+ if (!has_equal) {
+ if (++arg_pos == argc) {
+ return Status::Error(PSLICE() << "Option \"" << long_arg << "\" requires an argument");
+ }
+ parameter = Slice(argv[arg_pos]);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ TRY_STATUS(option->arg_callback(parameter));
+ continue;
+ }
+
+ for (size_t opt_pos = 1; arg[opt_pos] != '\0'; opt_pos++) {
+ auto it = short_options.find(arg[opt_pos]);
+ if (it == short_options.end()) {
+ return Status::Error(PSLICE() << "Option \"" << arg[opt_pos] << "\" is unrecognized");
+ }
+
+ auto option = it->second;
+ Slice parameter;
+ switch (option->type) {
+ case Option::Type::NoArg:
+ // nothing to do
+ break;
+ case Option::Type::Arg:
+ if (arg[opt_pos + 1] == '\0') {
+ if (++arg_pos == argc) {
+ return Status::Error(PSLICE() << "Option \"" << arg[opt_pos] << "\" requires an argument");
+ }
+ parameter = Slice(argv[arg_pos]);
+ } else {
+ parameter = Slice(arg + opt_pos + 1);
+ opt_pos += parameter.size();
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ TRY_STATUS(option->arg_callback(parameter));
+ }
+ }
+ if (expected_non_option_count >= 0 && non_options.size() != static_cast<size_t>(expected_non_option_count)) {
+ if (expected_non_option_count == 0) {
+ return Status::Error("Unexpected non-option parameters specified");
+ }
+ if (non_options.size() > static_cast<size_t>(expected_non_option_count)) {
+ return Status::Error("Too many non-option parameters specified");
+ } else {
+ return Status::Error("Too few non-option parameters specified");
+ }
+ }
+ for (auto &check : checks_) {
+ TRY_STATUS(check());
+ }
+
+ return std::move(non_options);
+}
+
+StringBuilder &operator<<(StringBuilder &sb, const OptionParser &o) {
+ if (!o.usage_.empty()) {
+ sb << "Usage: " << o.usage_ << "\n\n";
+ }
+ if (!o.description_.empty()) {
+ sb << o.description_ << ". ";
+ }
+ sb << "Options:\n";
+
+ size_t max_length = 0;
+ for (auto &opt : o.options_) {
+ size_t length = 2;
+ if (!opt.long_key.empty()) {
+ length += 4 + opt.long_key.size();
+ }
+ if (opt.type != OptionParser::Option::Type::NoArg) {
+ length += 6;
+ }
+ if (length > max_length) {
+ max_length = length;
+ }
+ }
+ max_length++;
+
+ for (auto &opt : o.options_) {
+ bool has_short_key = opt.short_key != '\0';
+ sb << " ";
+ size_t length = max_length;
+ if (has_short_key) {
+ sb << '-' << opt.short_key;
+ } else {
+ sb << " ";
+ }
+ length -= 2;
+ if (!opt.long_key.empty()) {
+ if (has_short_key) {
+ sb << ", ";
+ } else {
+ sb << " ";
+ }
+ sb << "--" << opt.long_key;
+ length -= 4 + opt.long_key.size();
+ }
+ if (opt.type != OptionParser::Option::Type::NoArg) {
+ sb << "=<arg>";
+ length -= 6;
+ }
+ sb << string(length, ' ') << opt.description;
+ sb << '\n';
+ }
+ return sb;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.h
new file mode 100644
index 0000000000..82d4296dca
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.h
@@ -0,0 +1,81 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+#include <functional>
+
+namespace td {
+
+class OptionParser {
+ class Option {
+ public:
+ enum class Type { NoArg, Arg };
+ Type type;
+ char short_key;
+ string long_key;
+ string description;
+ std::function<Status(Slice)> arg_callback;
+ };
+
+ void add_option(Option::Type type, char short_key, Slice long_key, Slice description,
+ std::function<Status(Slice)> callback);
+
+ public:
+ template <class T>
+ static std::function<Status(Slice)> parse_integer(T &value) {
+ return [&value](Slice value_str) {
+ TRY_RESULT_ASSIGN(value, to_integer_safe<T>(value_str));
+ return Status::OK();
+ };
+ }
+
+ static std::function<void(Slice)> parse_string(string &value) {
+ return [&value](Slice value_str) {
+ value = value_str.str();
+ };
+ }
+
+ void set_usage(Slice executable_name, Slice usage);
+
+ void set_description(string description);
+
+ void add_checked_option(char short_key, Slice long_key, Slice description, std::function<Status(Slice)> callback);
+
+ void add_checked_option(char short_key, Slice long_key, Slice description, std::function<Status(void)> callback);
+
+ void add_option(char short_key, Slice long_key, Slice description, std::function<Status(Slice)> callback) = delete;
+
+ void add_option(char short_key, Slice long_key, Slice description, std::function<Status(void)> callback) = delete;
+
+ void add_option(char short_key, Slice long_key, Slice description, std::function<void(Slice)> callback);
+
+ void add_option(char short_key, Slice long_key, Slice description, std::function<void(void)> callback);
+
+ void add_check(std::function<Status()> check);
+
+ // returns found non-option parameters
+ Result<vector<char *>> run(int argc, char *argv[], int expected_non_option_count = -1) TD_WARN_UNUSED_RESULT;
+
+ // for testing only
+ Result<vector<char *>> run_impl(int argc, char *argv[], int expected_non_option_count) TD_WARN_UNUSED_RESULT;
+
+ friend StringBuilder &operator<<(StringBuilder &sb, const OptionParser &o);
+
+ private:
+ vector<Option> options_;
+ vector<std::function<Status()>> checks_;
+ string usage_;
+ string description_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionsParser.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionsParser.h
deleted file mode 100644
index 6ac0385575..0000000000
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionsParser.h
+++ /dev/null
@@ -1,150 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/utils/common.h"
-#include "td/utils/logging.h"
-#include "td/utils/Slice.h"
-#include "td/utils/Status.h"
-#include "td/utils/StringBuilder.h"
-
-#include <functional>
-#include <string>
-
-#if !TD_WINDOWS
-#include <getopt.h>
-#endif
-
-namespace td {
-
-class OptionsParser {
- public:
- class Option {
- public:
- enum Type { NoArg, Arg, OptionalArg };
- Type type;
- char short_key;
- std::string long_key;
- std::string description;
- std::function<Status(Slice)> arg_callback;
- };
-
- void set_description(std::string description) {
- description_ = std::move(description);
- }
-
- void add_option(Option::Type type, char short_key, Slice long_key, Slice description,
- std::function<Status(Slice)> callback) {
- options_.push_back(Option{type, short_key, long_key.str(), description.str(), std::move(callback)});
- }
-
- void add_option(char short_key, Slice long_key, Slice description, std::function<Status(Slice)> callback) {
- add_option(Option::Type::Arg, short_key, long_key, description, std::move(callback));
- }
-
- void add_option(char short_key, Slice long_key, Slice description, std::function<Status(void)> callback) {
- // Ouch. There must be some better way
- add_option(Option::Type::NoArg, short_key, long_key, description,
- std::bind([](std::function<Status(void)> &func, Slice) { return func(); }, std::move(callback),
- std::placeholders::_1));
- }
-
- Result<int> run(int argc, char *argv[]) TD_WARN_UNUSED_RESULT {
-#if TD_WINDOWS
- return -1;
-#else
- // use getopt. long keys are not supported for now
- char buff[1024];
- StringBuilder sb({buff, sizeof(buff)});
- for (auto &opt : options_) {
- CHECK(opt.type != Option::OptionalArg);
- sb << opt.short_key;
- if (opt.type == Option::Arg) {
- sb << ":";
- }
- }
- if (sb.is_error()) {
- return Status::Error("Can't parse options");
- }
- CSlice short_options = sb.as_cslice();
-
- vector<option> long_options;
- for (auto &opt : options_) {
- if (opt.long_key.empty()) {
- continue;
- }
- option o;
- o.flag = nullptr;
- o.val = opt.short_key;
- o.has_arg = opt.type == Option::Arg ? required_argument : no_argument;
- o.name = opt.long_key.c_str();
- long_options.push_back(o);
- }
- long_options.push_back({nullptr, 0, nullptr, 0});
-
- while (true) {
- int opt_i = getopt_long(argc, argv, short_options.c_str(), &long_options[0], nullptr);
- if (opt_i == ':') {
- return Status::Error("Missing argument");
- }
- if (opt_i == '?') {
- return Status::Error("Unrecognized option");
- }
- if (opt_i == -1) {
- break;
- }
- bool found = false;
- for (auto &opt : options_) {
- if (opt.short_key == opt_i) {
- Slice arg;
- if (opt.type == Option::Arg) {
- arg = Slice(optarg);
- }
- auto status = opt.arg_callback(arg);
- if (status.is_error()) {
- return std::move(status);
- }
- found = true;
- break;
- }
- }
- if (!found) {
- return Status::Error("Unknown argument");
- }
- }
- return optind;
-#endif
- }
-
- friend StringBuilder &operator<<(StringBuilder &sb, const OptionsParser &o) {
- sb << o.description_ << "\n";
- for (auto &opt : o.options_) {
- sb << "-" << opt.short_key;
- if (!opt.long_key.empty()) {
- sb << "|--" << opt.long_key;
- }
- if (opt.type == Option::OptionalArg) {
- sb << "[";
- }
- if (opt.type != Option::NoArg) {
- sb << "<arg>";
- }
- if (opt.type == Option::OptionalArg) {
- sb << "]";
- }
- sb << "\t" << opt.description;
- sb << "\n";
- }
- return sb;
- }
-
- private:
- std::vector<Option> options_;
- std::string description_;
-};
-
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/OrderedEventsProcessor.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/OrderedEventsProcessor.h
index 4515b74684..8b3474ab57 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/OrderedEventsProcessor.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/OrderedEventsProcessor.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -23,9 +23,21 @@ class OrderedEventsProcessor {
explicit OrderedEventsProcessor(SeqNo offset) : offset_(offset), begin_(offset_), end_(offset_) {
}
+ template <class FunctionT>
+ void clear(FunctionT &&function) {
+ for (auto &it : data_array_) {
+ if (it.second) {
+ function(std::move(it.first));
+ }
+ }
+ *this = OrderedEventsProcessor();
+ }
+ void clear() {
+ *this = OrderedEventsProcessor();
+ }
template <class FromDataT, class FunctionT>
void add(SeqNo seq_no, FromDataT &&data, FunctionT &&function) {
- CHECK(seq_no >= begin_) << seq_no << ">=" << begin_; // or ignore?
+ LOG_CHECK(seq_no >= begin_) << seq_no << ">=" << begin_; // or ignore?
if (seq_no == begin_) { // run now
begin_++;
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Parser.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Parser.h
index 06e95bf807..731f885171 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Parser.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Parser.h
@@ -1,14 +1,14 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/format.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include <cstring>
@@ -16,14 +16,17 @@
namespace td {
-class Parser {
+namespace detail {
+
+template <class SliceT>
+class ParserImpl {
public:
- explicit Parser(MutableSlice data) : ptr_(data.begin()), end_(data.end()), status_() {
+ explicit ParserImpl(SliceT data) : ptr_(data.begin()), end_(data.end()), status_() {
}
- Parser(Parser &&other) : ptr_(other.ptr_), end_(other.end_), status_(std::move(other.status_)) {
+ ParserImpl(ParserImpl &&other) noexcept : ptr_(other.ptr_), end_(other.end_), status_(std::move(other.status_)) {
other.clear();
}
- Parser &operator=(Parser &&other) {
+ ParserImpl &operator=(ParserImpl &&other) noexcept {
if (&other == this) {
return *this;
}
@@ -33,70 +36,70 @@ class Parser {
other.clear();
return *this;
}
- Parser(const Parser &) = delete;
- Parser &operator=(const Parser &) = delete;
- ~Parser() = default;
+ ParserImpl(const ParserImpl &) = delete;
+ ParserImpl &operator=(const ParserImpl &) = delete;
+ ~ParserImpl() = default;
bool empty() const {
return ptr_ == end_;
}
void clear() {
- ptr_ = nullptr;
+ ptr_ = SliceT().begin();
end_ = ptr_;
status_ = Status::OK();
}
- MutableSlice read_till_nofail(char c) {
+ SliceT read_till_nofail(char c) {
if (status_.is_error()) {
- return MutableSlice();
+ return SliceT();
}
- char *till = reinterpret_cast<char *>(std::memchr(ptr_, c, end_ - ptr_));
+ auto till = static_cast<decltype(ptr_)>(std::memchr(ptr_, c, end_ - ptr_));
if (till == nullptr) {
till = end_;
}
- MutableSlice result(ptr_, till);
+ SliceT result(ptr_, till);
ptr_ = till;
return result;
}
- MutableSlice read_till_nofail(Slice str) {
+ SliceT read_till_nofail(Slice str) {
if (status_.is_error()) {
- return MutableSlice();
+ return SliceT();
}
- char *best_till = end_;
+ auto best_till = end_;
for (auto c : str) {
- char *till = reinterpret_cast<char *>(std::memchr(ptr_, c, end_ - ptr_));
+ auto till = static_cast<decltype(ptr_)>(std::memchr(ptr_, c, end_ - ptr_));
if (till != nullptr && till < best_till) {
best_till = till;
}
}
- MutableSlice result(ptr_, best_till);
+ SliceT result(ptr_, best_till);
ptr_ = best_till;
return result;
}
template <class F>
- MutableSlice read_while(const F &f) {
+ SliceT read_while(const F &f) {
auto save_ptr = ptr_;
while (ptr_ != end_ && f(*ptr_)) {
ptr_++;
}
- return MutableSlice(save_ptr, ptr_);
+ return SliceT(save_ptr, ptr_);
}
- MutableSlice read_all() {
+ SliceT read_all() {
auto save_ptr = ptr_;
ptr_ = end_;
- return MutableSlice(save_ptr, ptr_);
+ return SliceT(save_ptr, ptr_);
}
- MutableSlice read_till(char c) {
+ SliceT read_till(char c) {
if (status_.is_error()) {
- return MutableSlice();
+ return SliceT();
}
- MutableSlice res = read_till_nofail(c);
+ SliceT res = read_till_nofail(c);
if (ptr_ == end_ || ptr_[0] != c) {
- status_ = Status::Error(PSLICE() << "Read till " << tag("char", c) << " failed");
- return MutableSlice();
+ status_ = Status::Error(PSLICE() << "Read till '" << c << "' failed");
+ return SliceT();
}
return res;
}
@@ -122,7 +125,7 @@ class Parser {
return;
}
if (ptr_ == end_ || ptr_[0] != c) {
- status_ = Status::Error(PSLICE() << "Skip " << tag("char", c) << " failed");
+ status_ = Status::Error(PSLICE() << "Skip '" << c << "' failed");
return;
}
ptr_++;
@@ -135,6 +138,14 @@ class Parser {
return false;
}
+ bool try_skip(Slice prefix) {
+ if (prefix.size() > static_cast<size_t>(end_ - ptr_) || prefix != Slice(ptr_, prefix.size())) {
+ return false;
+ }
+ advance(prefix.size());
+ return true;
+ }
+
void skip_till_not(Slice str) {
while (ptr_ != end_) {
if (std::memchr(str.data(), *ptr_, str.size()) == nullptr) {
@@ -146,38 +157,33 @@ class Parser {
void skip_whitespaces() {
skip_till_not(" \t\r\n");
}
+ SliceT read_word() {
+ skip_whitespaces();
+ return read_till_nofail(" \t\r\n");
+ }
- MutableSlice data() const {
- return MutableSlice(ptr_, end_);
+ SliceT data() const {
+ return SliceT(ptr_, end_);
}
Status &status() {
return status_;
}
- bool start_with(Slice prefix) {
- if (prefix.size() + ptr_ > end_) {
- return false;
- }
- return std::memcmp(prefix.begin(), ptr_, prefix.size()) == 0;
- }
-
- bool skip_start_with(Slice prefix) {
- if (start_with(prefix)) {
- advance(prefix.size());
- return true;
- }
- return false;
- }
-
void advance(size_t diff) {
ptr_ += diff;
CHECK(ptr_ <= end_);
}
private:
- char *ptr_;
- char *end_;
+ decltype(std::declval<SliceT>().begin()) ptr_;
+ decltype(std::declval<SliceT>().end()) end_;
Status status_;
};
+
+} // namespace detail
+
+using Parser = detail::ParserImpl<MutableSlice>;
+using ConstParser = detail::ParserImpl<Slice>;
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.cpp
new file mode 100644
index 0000000000..943d8a8c84
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.cpp
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/PathView.h"
+
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+
+namespace td {
+
+PathView::PathView(Slice path) : path_(path) {
+ last_slash_ = narrow_cast<int32>(path_.size()) - 1;
+ while (last_slash_ >= 0 && !is_slash(path_[last_slash_])) {
+ last_slash_--;
+ }
+
+ last_dot_ = static_cast<int32>(path_.size());
+ for (auto i = last_dot_ - 1; i > last_slash_ + 1; i--) {
+ if (path_[i] == '.') {
+ last_dot_ = i;
+ break;
+ }
+ }
+}
+
+Slice PathView::parent_dir_noslash() const {
+ if (last_slash_ < 0) {
+ return Slice(".");
+ }
+ if (last_slash_ == 0) {
+ static char buf[1];
+ buf[0] = TD_DIR_SLASH;
+ return Slice(buf, 1);
+ }
+ return path_.substr(0, last_slash_);
+}
+
+Slice PathView::relative(Slice path, Slice dir, bool force) {
+ if (begins_with(path, dir)) {
+ path.remove_prefix(dir.size());
+ return path;
+ }
+ if (force) {
+ return Slice();
+ }
+ return path;
+}
+
+Slice PathView::dir_and_file(Slice path) {
+ auto last_slash = static_cast<int32>(path.size()) - 1;
+ while (last_slash >= 0 && !is_slash(path[last_slash])) {
+ last_slash--;
+ }
+ if (last_slash < 0) {
+ return Slice();
+ }
+ last_slash--;
+ while (last_slash >= 0 && !is_slash(path[last_slash])) {
+ last_slash--;
+ }
+ if (last_slash < 0) {
+ return Slice();
+ }
+ return path.substr(last_slash + 1);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.h
index edb5d7c127..1bafc1d038 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.h
@@ -1,32 +1,18 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/misc.h"
#include "td/utils/Slice.h"
namespace td {
class PathView {
public:
- explicit PathView(Slice path) : path_(path) {
- last_slash_ = narrow_cast<int32>(path_.size()) - 1;
- while (last_slash_ >= 0 && !is_slash(path_[last_slash_])) {
- last_slash_--;
- }
-
- last_dot_ = static_cast<int32>(path_.size());
- for (auto i = last_dot_ - 1; i > last_slash_ + 1; i--) {
- if (path_[i] == '.') {
- last_dot_ = i;
- break;
- }
- }
- }
+ explicit PathView(Slice path);
bool empty() const {
return path_.empty();
@@ -40,8 +26,9 @@ class PathView {
}
Slice parent_dir() const {
- return Slice(path_.begin(), last_slash_ + 1);
+ return path_.substr(0, last_slash_ + 1);
}
+ Slice parent_dir_noslash() const;
Slice extension() const {
if (last_dot_ == static_cast<int32>(path_.size())) {
@@ -51,7 +38,7 @@ class PathView {
}
Slice without_extension() const {
- return Slice(path_.begin(), last_dot_);
+ return path_.substr(0, last_dot_);
}
Slice file_stem() const {
@@ -62,6 +49,10 @@ class PathView {
return path_.substr(last_slash_ + 1);
}
+ Slice file_name_without_extension() const {
+ return path_.substr(last_slash_ + 1, last_dot_ - last_slash_ - 1);
+ }
+
Slice path() const {
return path_;
}
@@ -74,34 +65,8 @@ class PathView {
return !is_absolute();
}
- static Slice relative(Slice path, Slice dir, bool force = false) {
- if (begins_with(path, dir)) {
- path.remove_prefix(dir.size());
- return path;
- }
- if (force) {
- return Slice();
- }
- return path;
- }
-
- static Slice dir_and_file(Slice path) {
- auto last_slash = static_cast<int32>(path.size()) - 1;
- while (last_slash >= 0 && !is_slash(path[last_slash])) {
- last_slash--;
- }
- if (last_slash < 0) {
- return Slice();
- }
- last_slash--;
- while (last_slash >= 0 && !is_slash(path[last_slash])) {
- last_slash--;
- }
- if (last_slash < 0) {
- return Slice();
- }
- return path.substr(last_slash + 1);
- }
+ static Slice relative(Slice path, Slice dir, bool force = false);
+ static Slice dir_and_file(Slice path);
private:
static bool is_slash(char c) {
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Promise.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Promise.h
new file mode 100644
index 0000000000..4e83045d61
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Promise.h
@@ -0,0 +1,373 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/CancellationToken.h"
+#include "td/utils/common.h"
+#include "td/utils/invoke.h"
+#include "td/utils/MovableValue.h"
+#include "td/utils/Status.h"
+
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+namespace td {
+
+template <class T = Unit>
+class PromiseInterface {
+ public:
+ PromiseInterface() = default;
+ PromiseInterface(const PromiseInterface &) = delete;
+ PromiseInterface &operator=(const PromiseInterface &) = delete;
+ PromiseInterface(PromiseInterface &&) = default;
+ PromiseInterface &operator=(PromiseInterface &&) = default;
+ virtual ~PromiseInterface() = default;
+
+ virtual void set_value(T &&value) {
+ set_result(std::move(value));
+ }
+ virtual void set_error(Status &&error) {
+ set_result(std::move(error));
+ }
+ virtual void set_result(Result<T> &&result) {
+ if (result.is_ok()) {
+ set_value(result.move_as_ok());
+ } else {
+ set_error(result.move_as_error());
+ }
+ }
+
+ virtual bool is_cancellable() const {
+ return false;
+ }
+ virtual bool is_canceled() const {
+ return false;
+ }
+};
+
+template <class T>
+class SafePromise;
+
+template <class T = Unit>
+class Promise;
+
+namespace detail {
+
+template <typename T>
+struct GetArg final : public GetArg<decltype(&T::operator())> {};
+
+template <class C, class R, class Arg>
+class GetArg<R (C::*)(Arg)> {
+ public:
+ using type = Arg;
+};
+template <class C, class R, class Arg>
+class GetArg<R (C::*)(Arg) const> {
+ public:
+ using type = Arg;
+};
+
+template <class T>
+using get_arg_t = std::decay_t<typename GetArg<T>::type>;
+
+template <class T>
+struct DropResult {
+ using type = T;
+};
+
+template <class T>
+struct DropResult<Result<T>> {
+ using type = T;
+};
+
+template <class T>
+using drop_result_t = typename DropResult<T>::type;
+
+template <class ValueT, class FunctionT>
+class LambdaPromise : public PromiseInterface<ValueT> {
+ enum class State : int32 { Empty, Ready, Complete };
+
+ public:
+ void set_value(ValueT &&value) override {
+ CHECK(state_.get() == State::Ready);
+ do_ok(std::move(value));
+ state_ = State::Complete;
+ }
+
+ void set_error(Status &&error) override {
+ if (state_.get() == State::Ready) {
+ do_error(std::move(error));
+ state_ = State::Complete;
+ }
+ }
+ LambdaPromise(const LambdaPromise &other) = delete;
+ LambdaPromise &operator=(const LambdaPromise &other) = delete;
+ LambdaPromise(LambdaPromise &&other) = default;
+ LambdaPromise &operator=(LambdaPromise &&other) = default;
+ ~LambdaPromise() override {
+ if (state_.get() == State::Ready) {
+ do_error(Status::Error("Lost promise"));
+ }
+ }
+
+ template <class FromT>
+ explicit LambdaPromise(FromT &&func) : func_(std::forward<FromT>(func)), state_(State::Ready) {
+ }
+
+ private:
+ FunctionT func_;
+ MovableValue<State> state_{State::Empty};
+
+ template <class F = FunctionT>
+ std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_error(Status &&status) {
+ func_(Result<ValueT>(std::move(status)));
+ }
+ template <class Y, class F = FunctionT>
+ std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_error(Y &&status) {
+ func_(Auto());
+ }
+ template <class F = FunctionT>
+ std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_ok(ValueT &&value) {
+ func_(Result<ValueT>(std::move(value)));
+ }
+ template <class F = FunctionT>
+ std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_ok(ValueT &&value) {
+ func_(std::move(value));
+ }
+};
+
+template <class T>
+struct is_promise_interface : std::false_type {};
+
+template <class U>
+struct is_promise_interface<PromiseInterface<U>> : std::true_type {};
+
+template <class U>
+struct is_promise_interface<Promise<U>> : std::true_type {};
+
+template <class T>
+struct is_promise_interface_ptr : std::false_type {};
+
+template <class U>
+struct is_promise_interface_ptr<unique_ptr<U>> : std::true_type {};
+
+template <class T = void, class F = void, std::enable_if_t<std::is_same<T, void>::value, bool> has_t = false>
+auto lambda_promise(F &&f) {
+ return LambdaPromise<drop_result_t<get_arg_t<std::decay_t<F>>>, std::decay_t<F>>(std::forward<F>(f));
+}
+template <class T = void, class F = void, std::enable_if_t<!std::is_same<T, void>::value, bool> has_t = true>
+auto lambda_promise(F &&f) {
+ return LambdaPromise<T, std::decay_t<F>>(std::forward<F>(f));
+}
+
+template <class T, class F,
+ std::enable_if_t<is_promise_interface<std::decay_t<F>>::value, bool> from_promise_interface = true>
+auto &&promise_interface(F &&f) {
+ return std::forward<F>(f);
+}
+
+template <class T, class F,
+ std::enable_if_t<!is_promise_interface<std::decay_t<F>>::value, bool> from_promise_interface = false>
+auto promise_interface(F &&f) {
+ return lambda_promise<T>(std::forward<F>(f));
+}
+
+template <class T, class F,
+ std::enable_if_t<is_promise_interface_ptr<std::decay_t<F>>::value, bool> from_promise_interface = true>
+auto promise_interface_ptr(F &&f) {
+ return std::forward<F>(f);
+}
+
+template <class T, class F,
+ std::enable_if_t<!is_promise_interface_ptr<std::decay_t<F>>::value, bool> from_promise_interface = false>
+auto promise_interface_ptr(F &&f) {
+ return td::make_unique<std::decay_t<decltype(promise_interface<T>(std::forward<F>(f)))>>(
+ promise_interface<T>(std::forward<F>(f)));
+}
+} // namespace detail
+
+template <class T>
+class Promise {
+ public:
+ void set_value(T &&value) {
+ if (!promise_) {
+ return;
+ }
+ promise_->set_value(std::move(value));
+ promise_.reset();
+ }
+ void set_error(Status &&error) {
+ if (!promise_) {
+ return;
+ }
+ promise_->set_error(std::move(error));
+ promise_.reset();
+ }
+ void set_result(Result<T> &&result) {
+ if (!promise_) {
+ return;
+ }
+ promise_->set_result(std::move(result));
+ promise_.reset();
+ }
+ void reset() {
+ promise_.reset();
+ }
+ bool is_cancellable() const {
+ if (!promise_) {
+ return false;
+ }
+ return promise_->is_cancellable();
+ }
+ bool is_canceled() const {
+ if (!promise_) {
+ return false;
+ }
+ return promise_->is_canceled();
+ }
+ unique_ptr<PromiseInterface<T>> release() {
+ return std::move(promise_);
+ }
+
+ Promise() = default;
+ explicit Promise(unique_ptr<PromiseInterface<T>> promise) : promise_(std::move(promise)) {
+ }
+ Promise(Auto) {
+ }
+ Promise(SafePromise<T> &&other);
+ Promise &operator=(SafePromise<T> &&other);
+ template <class F, std::enable_if_t<!std::is_same<std::decay_t<F>, Promise>::value, int> = 0>
+ Promise(F &&f) : promise_(detail::promise_interface_ptr<T>(std::forward<F>(f))) {
+ }
+
+ explicit operator bool() const noexcept {
+ return static_cast<bool>(promise_);
+ }
+
+ private:
+ unique_ptr<PromiseInterface<T>> promise_;
+};
+
+template <class T = Unit>
+class SafePromise {
+ public:
+ SafePromise(Promise<T> promise, Result<T> result) : promise_(std::move(promise)), result_(std::move(result)) {
+ }
+ SafePromise(const SafePromise &other) = delete;
+ SafePromise &operator=(const SafePromise &other) = delete;
+ SafePromise(SafePromise &&other) = default;
+ SafePromise &operator=(SafePromise &&other) = default;
+ ~SafePromise() {
+ if (promise_) {
+ promise_.set_result(std::move(result_));
+ }
+ }
+ Promise<T> release() {
+ return std::move(promise_);
+ }
+
+ private:
+ Promise<T> promise_;
+ Result<T> result_;
+};
+
+template <class T>
+Promise<T>::Promise(SafePromise<T> &&other) : Promise(other.release()) {
+}
+
+template <class T>
+Promise<T> &Promise<T>::operator=(SafePromise<T> &&other) {
+ *this = other.release();
+ return *this;
+}
+
+namespace detail {
+template <class PromiseT>
+class CancellablePromise final : public PromiseT {
+ public:
+ template <class... ArgsT>
+ CancellablePromise(CancellationToken cancellation_token, ArgsT &&...args)
+ : PromiseT(std::forward<ArgsT>(args)...), cancellation_token_(std::move(cancellation_token)) {
+ }
+ bool is_cancellable() const final {
+ return true;
+ }
+ bool is_canceled() const final {
+ return static_cast<bool>(cancellation_token_);
+ }
+
+ private:
+ CancellationToken cancellation_token_;
+};
+
+template <class... ArgsT>
+class JoinPromise final : public PromiseInterface<Unit> {
+ public:
+ explicit JoinPromise(ArgsT &&...arg) : promises_(std::forward<ArgsT>(arg)...) {
+ }
+ void set_value(Unit &&) final {
+ tuple_for_each(promises_, [](auto &promise) { promise.set_value(Unit()); });
+ }
+ void set_error(Status &&error) final {
+ tuple_for_each(promises_, [&error](auto &promise) { promise.set_error(error.clone()); });
+ }
+
+ private:
+ std::tuple<std::decay_t<ArgsT>...> promises_;
+};
+} // namespace detail
+
+class PromiseCreator {
+ public:
+ template <class OkT, class ArgT = detail::drop_result_t<detail::get_arg_t<OkT>>>
+ static Promise<ArgT> lambda(OkT &&ok) {
+ return Promise<ArgT>(td::make_unique<detail::LambdaPromise<ArgT, std::decay_t<OkT>>>(std::forward<OkT>(ok)));
+ }
+
+ template <class OkT, class ArgT = detail::drop_result_t<detail::get_arg_t<OkT>>>
+ static auto cancellable_lambda(CancellationToken cancellation_token, OkT &&ok) {
+ return Promise<ArgT>(td::make_unique<detail::CancellablePromise<detail::LambdaPromise<ArgT, std::decay_t<OkT>>>>(
+ std::move(cancellation_token), std::forward<OkT>(ok)));
+ }
+
+ template <class... ArgsT>
+ static Promise<> join(ArgsT &&...args) {
+ return Promise<>(td::make_unique<detail::JoinPromise<ArgsT...>>(std::forward<ArgsT>(args)...));
+ }
+};
+
+inline void set_promises(vector<Promise<Unit>> &promises) {
+ auto moved_promises = std::move(promises);
+ promises.clear();
+
+ for (auto &promise : moved_promises) {
+ promise.set_value(Unit());
+ }
+}
+
+template <class T>
+void fail_promises(vector<Promise<T>> &promises, Status &&error) {
+ CHECK(error.is_error());
+ auto moved_promises = std::move(promises);
+ promises.clear();
+
+ auto size = moved_promises.size();
+ if (size == 0) {
+ return;
+ }
+ size--;
+ for (size_t i = 0; i < size; i++) {
+ auto &promise = moved_promises[i];
+ if (promise) {
+ promise.set_error(error.clone());
+ }
+ }
+ moved_promises[size].set_error(std::move(error));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.cpp
index db11df4dfa..5b4c78d675 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -13,6 +13,7 @@
#include <openssl/rand.h>
#endif
+#include <atomic>
#include <cstring>
#include <limits>
#include <random>
@@ -20,19 +21,35 @@
namespace td {
#if TD_HAVE_OPENSSL
+
+namespace {
+std::atomic<int64> random_seed_generation{0};
+} // namespace
+
void Random::secure_bytes(MutableSlice dest) {
Random::secure_bytes(dest.ubegin(), dest.size());
}
void Random::secure_bytes(unsigned char *ptr, size_t size) {
- constexpr size_t buf_size = 512;
+ constexpr size_t BUF_SIZE = 512;
static TD_THREAD_LOCAL unsigned char *buf; // static zero-initialized
static TD_THREAD_LOCAL size_t buf_pos;
- if (init_thread_local<unsigned char[]>(buf, buf_size)) {
- buf_pos = buf_size;
+ static TD_THREAD_LOCAL int64 generation;
+ if (init_thread_local<unsigned char[]>(buf, BUF_SIZE)) {
+ buf_pos = BUF_SIZE;
+ generation = 0;
+ }
+ if (ptr == nullptr) {
+ MutableSlice(buf, BUF_SIZE).fill_zero_secure();
+ buf_pos = BUF_SIZE;
+ return;
+ }
+ if (generation != random_seed_generation.load(std::memory_order_relaxed)) {
+ generation = random_seed_generation.load(std::memory_order_acquire);
+ buf_pos = BUF_SIZE;
}
- auto ready = min(size, buf_size - buf_pos);
+ auto ready = min(size, BUF_SIZE - buf_pos);
if (ready != 0) {
std::memcpy(ptr, buf + buf_pos, ready);
buf_pos += ready;
@@ -42,8 +59,8 @@ void Random::secure_bytes(unsigned char *ptr, size_t size) {
return;
}
}
- if (size < buf_size) {
- int err = RAND_bytes(buf, static_cast<int>(buf_size));
+ if (size < BUF_SIZE) {
+ int err = RAND_bytes(buf, static_cast<int>(BUF_SIZE));
// TODO: it CAN fail
LOG_IF(FATAL, err != 1);
buf_pos = size;
@@ -68,6 +85,27 @@ int64 Random::secure_int64() {
secure_bytes(reinterpret_cast<unsigned char *>(&res), sizeof(int64));
return res;
}
+
+uint32 Random::secure_uint32() {
+ uint32 res = 0;
+ secure_bytes(reinterpret_cast<unsigned char *>(&res), sizeof(uint32));
+ return res;
+}
+
+uint64 Random::secure_uint64() {
+ uint64 res = 0;
+ secure_bytes(reinterpret_cast<unsigned char *>(&res), sizeof(uint64));
+ return res;
+}
+
+void Random::add_seed(Slice bytes, double entropy) {
+ RAND_add(bytes.data(), static_cast<int>(bytes.size()), entropy);
+ random_seed_generation++;
+}
+
+void Random::secure_cleanup() {
+ Random::secure_bytes(nullptr, 0);
+}
#endif
static unsigned int rand_device_helper() {
@@ -96,13 +134,70 @@ uint64 Random::fast_uint64() {
return static_cast<uint64>((*gen)());
}
-int Random::fast(int min, int max) {
- if (min == std::numeric_limits<int>::min() && max == std::numeric_limits<int>::max()) {
+int Random::fast(int min_value, int max_value) {
+ if (min_value == std::numeric_limits<int>::min() && max_value == std::numeric_limits<int>::max()) {
// to prevent integer overflow and division by zero
- min++;
+ min_value++;
+ }
+ DCHECK(min_value <= max_value);
+ return static_cast<int>(min_value + fast_uint32() % (max_value - min_value + 1)); // TODO signed_cast
+}
+
+double Random::fast(double min_value, double max_value) {
+ DCHECK(min_value <= max_value);
+ return min_value + fast_uint32() * 1.0 / std::numeric_limits<uint32>::max() * (max_value - min_value);
+}
+
+bool Random::fast_bool() {
+ return (fast_uint32() & 1) != 0;
+}
+
+Random::Xorshift128plus::Xorshift128plus(uint64 seed) {
+ auto next = [&] {
+ // splitmix64
+ seed += static_cast<uint64>(0x9E3779B97F4A7C15ull);
+ uint64 z = seed;
+ z = (z ^ (z >> 30)) * static_cast<uint64>(0xBF58476D1CE4E5B9ull);
+ z = (z ^ (z >> 27)) * static_cast<uint64>(0x94D049BB133111EBull);
+ return z ^ (z >> 31);
+ };
+ seed_[0] = next();
+ seed_[1] = next();
+}
+
+Random::Xorshift128plus::Xorshift128plus(uint64 seed_a, uint64 seed_b) {
+ seed_[0] = seed_a;
+ seed_[1] = seed_b;
+}
+
+uint64 Random::Xorshift128plus::operator()() {
+ uint64 x = seed_[0];
+ const uint64 y = seed_[1];
+ seed_[0] = y;
+ x ^= x << 23;
+ seed_[1] = x ^ y ^ (x >> 17) ^ (y >> 26);
+ return seed_[1] + y;
+}
+
+int Random::Xorshift128plus::fast(int min_value, int max_value) {
+ return static_cast<int>((*this)() % (max_value - min_value + 1) + min_value);
+}
+int64 Random::Xorshift128plus::fast64(int64 min_value, int64 max_value) {
+ return static_cast<int64>((*this)() % (max_value - min_value + 1) + min_value);
+}
+
+void Random::Xorshift128plus::bytes(MutableSlice dest) {
+ int cnt = 0;
+ uint64 buf = 0;
+ for (auto &c : dest) {
+ if (cnt == 0) {
+ buf = operator()();
+ cnt = 8;
+ }
+ cnt--;
+ c = static_cast<char>(buf & 255);
+ buf >>= 8;
}
- CHECK(min <= max);
- return static_cast<int>(min + fast_uint32() % (max - min + 1)); // TODO signed_cast
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.h
index efe5d64618..32cb71f770 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,6 +8,9 @@
#include "td/utils/common.h"
#include "td/utils/Slice.h"
+#include "td/utils/Span.h"
+
+#include <utility>
namespace td {
@@ -18,13 +21,49 @@ class Random {
static void secure_bytes(unsigned char *ptr, size_t size);
static int32 secure_int32();
static int64 secure_int64();
+ static uint32 secure_uint32();
+ static uint64 secure_uint64();
+
+ // works only for current thread
+ static void add_seed(Slice bytes, double entropy = 0);
+ static void secure_cleanup();
#endif
static uint32 fast_uint32();
static uint64 fast_uint64();
- // distribution is not uniform, min and max are included
- static int fast(int min, int max);
+ // distribution is not uniform, min_value and max_value are included
+ static int fast(int min_value, int max_value);
+ static double fast(double min_value, double max_value);
+ static bool fast_bool();
+
+ class Fast {
+ public:
+ uint64 operator()() {
+ return fast_uint64();
+ }
+ };
+ class Xorshift128plus {
+ public:
+ explicit Xorshift128plus(uint64 seed);
+ Xorshift128plus(uint64 seed_a, uint64 seed_b);
+ uint64 operator()();
+ int fast(int min_value, int max_value);
+ int64 fast64(int64 min_value, int64 max_value);
+ void bytes(MutableSlice dest);
+
+ private:
+ uint64 seed_[2];
+ };
};
+template <class T, class R>
+void random_shuffle(MutableSpan<T> v, R &rnd) {
+ for (size_t i = 1; i < v.size(); i++) {
+ auto pos = static_cast<size_t>(rnd()) % (i + 1);
+ using std::swap;
+ swap(v[i], v[pos]);
+ }
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ScopeGuard.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ScopeGuard.h
index a914ce357c..577514a44b 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/ScopeGuard.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ScopeGuard.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,6 +14,7 @@
#include <utility>
namespace td {
+
class Guard {
public:
Guard() = default;
@@ -28,7 +29,7 @@ class Guard {
};
template <class FunctionT>
-class LambdaGuard : public Guard {
+class LambdaGuard final : public Guard {
public:
explicit LambdaGuard(const FunctionT &func) : func_(func) {
}
@@ -41,11 +42,11 @@ class LambdaGuard : public Guard {
}
LambdaGuard &operator=(LambdaGuard &&other) = delete;
- void dismiss() {
+ void dismiss() final {
dismissed_ = true;
}
- ~LambdaGuard() {
+ ~LambdaGuard() final {
if (!dismissed_) {
func_();
}
@@ -57,8 +58,8 @@ class LambdaGuard : public Guard {
};
template <class F>
-std::unique_ptr<Guard> create_lambda_guard(F &&f) {
- return std::make_unique<LambdaGuard<F>>(std::forward<F>(f));
+unique_ptr<Guard> create_lambda_guard(F &&f) {
+ return make_unique<LambdaGuard<F>>(std::forward<F>(f));
}
template <class F>
std::shared_ptr<Guard> create_shared_lambda_guard(F &&f) {
@@ -73,4 +74,4 @@ auto operator+(ScopeExit, FunctionT &&func) {
} // namespace td
-#define SCOPE_EXIT auto TD_CONCAT(SCOPE_EXIT_VAR_, __LINE__) = ::td::ScopeExit() + [&]()
+#define SCOPE_EXIT auto TD_CONCAT(SCOPE_EXIT_VAR_, __LINE__) = ::td::ScopeExit() + [&]
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SetNode.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SetNode.h
new file mode 100644
index 0000000000..6e5960553d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SetNode.h
@@ -0,0 +1,129 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/HashTableUtils.h"
+
+#include <type_traits>
+#include <utility>
+
+namespace td {
+
+template <class KeyT, class Enable = void>
+struct SetNode {
+ using public_key_type = KeyT;
+ using public_type = const KeyT;
+ using second_type = KeyT; // TODO: remove second_type?
+
+ KeyT first;
+
+ const KeyT &key() const {
+ return first;
+ }
+
+ const KeyT &get_public() {
+ return first;
+ }
+
+ SetNode() : first() {
+ }
+ explicit SetNode(KeyT key) : first(std::move(key)) {
+ }
+ SetNode(const SetNode &other) = delete;
+ SetNode &operator=(const SetNode &other) = delete;
+ SetNode(SetNode &&other) noexcept {
+ *this = std::move(other);
+ }
+ void operator=(SetNode &&other) noexcept {
+ DCHECK(empty());
+ DCHECK(!other.empty());
+ first = std::move(other.first);
+ other.first = KeyT();
+ }
+ ~SetNode() = default;
+
+ void copy_from(const SetNode &other) {
+ DCHECK(empty());
+ first = other.first;
+ DCHECK(!empty());
+ }
+
+ bool empty() const {
+ return is_hash_table_key_empty(first);
+ }
+
+ void clear() {
+ first = KeyT();
+ DCHECK(empty());
+ }
+
+ void emplace(KeyT key) {
+ first = std::move(key);
+ }
+};
+
+template <class KeyT>
+struct SetNode<KeyT, typename std::enable_if_t<(sizeof(KeyT) > 28 * sizeof(void *))>> {
+ struct Impl {
+ using second_type = KeyT;
+
+ KeyT first;
+
+ template <class InputKeyT>
+ explicit Impl(InputKeyT &&key) : first(std::forward<InputKeyT>(key)) {
+ DCHECK(!is_hash_table_key_empty(first));
+ }
+ Impl(const Impl &other) = delete;
+ Impl &operator=(const Impl &other) = delete;
+ Impl(Impl &&other) = delete;
+ void operator=(Impl &&other) = delete;
+ };
+
+ using public_key_type = KeyT;
+ using public_type = const KeyT;
+ using second_type = KeyT; // TODO: remove second_type?
+
+ unique_ptr<Impl> impl_;
+
+ const KeyT &key() const {
+ DCHECK(!empty());
+ return impl_->first;
+ }
+
+ const KeyT &get_public() {
+ DCHECK(!empty());
+ return impl_->first;
+ }
+
+ SetNode() : impl_() {
+ }
+ explicit SetNode(KeyT key) : impl_(td::make_unique<Impl>(std::move(key))) {
+ }
+
+ void copy_from(const SetNode &other) {
+ DCHECK(empty());
+ impl_ = td::make_unique<Impl>(other.impl_->first);
+ DCHECK(!empty());
+ }
+
+ bool empty() const {
+ return impl_ == nullptr;
+ }
+
+ void clear() {
+ DCHECK(!empty());
+ impl_ = nullptr;
+ }
+
+ void emplace(KeyT key) {
+ DCHECK(empty());
+ impl_ = td::make_unique<Impl>(std::move(key));
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedObjectPool.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedObjectPool.h
index dc8512b268..6a7dcc8256 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedObjectPool.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedObjectPool.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,6 @@
#pragma once
#include "td/utils/common.h"
-
#include "td/utils/logging.h"
#include "td/utils/MpscLinkQueue.h"
@@ -34,7 +33,7 @@ class AtomicRefCnt {
}
private:
- std::atomic<uint64> cnt_;
+ std::atomic<uint64> cnt_{0};
};
template <class DataT, class DeleterT>
@@ -50,7 +49,7 @@ class SharedPtrRaw
CHECK(option_magic_ == Magic);
}
template <class... ArgsT>
- void init_data(ArgsT &&... args) {
+ void init_data(ArgsT &&...args) {
new (&option_data_) DataT(std::forward<ArgsT>(args)...);
}
void destroy_data() {
@@ -89,6 +88,7 @@ template <class T, class DeleterT = std::default_delete<T>>
class SharedPtr {
public:
using Raw = detail::SharedPtrRaw<T, DeleterT>;
+ struct acquire_t {};
SharedPtr() = default;
~SharedPtr() {
if (!raw_) {
@@ -97,27 +97,36 @@ class SharedPtr {
reset();
}
explicit SharedPtr(Raw *raw) : raw_(raw) {
- raw_->inc();
+ if (raw_) {
+ raw_->inc();
+ }
+ }
+ SharedPtr(acquire_t, Raw *raw) : raw_(raw) {
}
SharedPtr(const SharedPtr &other) : SharedPtr(other.raw_) {
}
SharedPtr &operator=(const SharedPtr &other) {
- other.raw_->inc();
+ if (this == &other) {
+ return *this;
+ }
+ if (other.raw_) {
+ other.raw_->inc();
+ }
reset(other.raw_);
return *this;
}
- SharedPtr(SharedPtr &&other) : raw_(other.raw_) {
+ SharedPtr(SharedPtr &&other) noexcept : raw_(other.raw_) {
other.raw_ = nullptr;
}
- SharedPtr &operator=(SharedPtr &&other) {
+ SharedPtr &operator=(SharedPtr &&other) noexcept {
reset(other.raw_);
other.raw_ = nullptr;
return *this;
}
- bool empty() const {
+ bool empty() const noexcept {
return raw_ == nullptr;
}
- explicit operator bool() const {
+ explicit operator bool() const noexcept {
return !empty();
}
uint64 use_cnt() const {
@@ -149,17 +158,20 @@ class SharedPtr {
}
template <class... ArgsT>
- static SharedPtr<T, DeleterT> create(ArgsT &&... args) {
- auto raw = std::make_unique<Raw>(DeleterT());
+ static SharedPtr<T, DeleterT> create(ArgsT &&...args) {
+ auto raw = make_unique<Raw>(DeleterT());
raw->init_data(std::forward<ArgsT>(args)...);
return SharedPtr<T, DeleterT>(raw.release());
}
template <class D, class... ArgsT>
- static SharedPtr<T, DeleterT> create_with_deleter(D &&d, ArgsT &&... args) {
- auto raw = std::make_unique<Raw>(std::forward<D>(d));
+ static SharedPtr<T, DeleterT> create_with_deleter(D &&d, ArgsT &&...args) {
+ auto raw = make_unique<Raw>(std::forward<D>(d));
raw->init_data(std::forward<ArgsT>(args)...);
return SharedPtr<T, DeleterT>(raw.release());
}
+ bool operator==(const SharedPtr<T, DeleterT> &other) const {
+ return raw_ == other.raw_;
+ }
private:
Raw *raw_{nullptr};
@@ -185,11 +197,11 @@ class SharedObjectPool {
while (free_queue_reader_.read()) {
free_cnt++;
}
- CHECK(free_cnt == allocated_.size()) << free_cnt << " " << allocated_.size();
+ LOG_CHECK(free_cnt == allocated_.size()) << free_cnt << " " << allocated_.size();
}
template <class... ArgsT>
- Ptr alloc(ArgsT &&... args) {
+ Ptr alloc(ArgsT &&...args) {
auto *raw = alloc_raw();
raw->init_data(std::forward<ArgsT>(args)...);
return Ptr(raw);
@@ -202,7 +214,7 @@ class SharedObjectPool {
return free_queue_reader_.calc_size();
}
- //non thread safe
+ // non-thread-safe
template <class F>
void for_each(F &&f) {
for (auto &raw : allocated_) {
@@ -220,7 +232,7 @@ class SharedObjectPool {
if (raw) {
return raw;
}
- allocated_.push_back(std::make_unique<Raw>(deleter()));
+ allocated_.push_back(make_unique<Raw>(deleter()));
return allocated_.back().get();
}
@@ -243,7 +255,7 @@ class SharedObjectPool {
Raw *get() const {
return raw_;
}
- explicit operator bool() const {
+ explicit operator bool() const noexcept {
return raw_ != nullptr;
}
@@ -268,7 +280,7 @@ class SharedObjectPool {
return Deleter(this);
}
- std::vector<std::unique_ptr<Raw>> allocated_;
+ std::vector<unique_ptr<Raw>> allocated_;
MpscLinkQueue<Node> free_queue_;
typename MpscLinkQueue<Node>::Reader free_queue_reader_;
};
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.cpp
index 976286b923..df157f4e6d 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.cpp
@@ -1,20 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/GitInfo.h"
+#include "td/utils/SharedSlice.h"
-#include "auto/git_info.h"
+#include "td/utils/buffer.h"
namespace td {
-CSlice GitInfo::commit() {
- return GIT_COMMIT;
-}
-bool GitInfo::is_dirty() {
- return GIT_DIRTY;
+BufferSlice SharedSlice::clone_as_buffer_slice() const {
+ return BufferSlice{as_slice()};
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.h
new file mode 100644
index 0000000000..e0ea0c17c0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.h
@@ -0,0 +1,382 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+
+#include <atomic>
+#include <memory>
+#include <new>
+#include <type_traits>
+
+namespace td {
+
+namespace detail {
+struct SharedSliceHeader {
+ explicit SharedSliceHeader(size_t size) : size_{size} {
+ }
+
+ void inc() {
+ refcnt_.fetch_add(1, std::memory_order_relaxed);
+ }
+
+ bool dec() {
+ return refcnt_.fetch_sub(1, std::memory_order_acq_rel) == 1;
+ }
+
+ bool is_unique() const {
+ // NB: race if std::memory_order_relaxed is used
+ // reader may see a change by a new writer
+ return refcnt_.load(std::memory_order_acquire) == 1;
+ }
+
+ size_t size() const {
+ return size_;
+ }
+
+ private:
+ std::atomic<uint64> refcnt_{1};
+ size_t size_;
+};
+
+struct UniqueSliceHeader {
+ explicit UniqueSliceHeader(size_t size) : size_{size} {
+ }
+
+ void inc() {
+ }
+
+ bool dec() {
+ return true;
+ }
+
+ bool is_unique() const {
+ return true;
+ }
+
+ size_t size() const {
+ return size_;
+ }
+
+ private:
+ size_t size_;
+};
+
+template <class HeaderT, bool zero_on_destruct = false>
+class UnsafeSharedSlice {
+ public:
+ UnsafeSharedSlice() = default;
+ UnsafeSharedSlice clone() const {
+ if (is_null()) {
+ return UnsafeSharedSlice();
+ }
+ header()->inc();
+ return UnsafeSharedSlice(ptr_.get());
+ }
+
+ bool is_null() const {
+ return !ptr_;
+ }
+
+ bool is_unique() const {
+ if (is_null()) {
+ return true;
+ }
+ return header()->is_unique();
+ }
+
+ MutableSlice as_mutable_slice() {
+ if (is_null()) {
+ return MutableSlice();
+ }
+ return MutableSlice(ptr_.get() + sizeof(HeaderT), header()->size());
+ }
+
+ Slice as_slice() const {
+ if (is_null()) {
+ return Slice();
+ }
+ return Slice(ptr_.get() + sizeof(HeaderT), header()->size());
+ }
+
+ size_t size() const {
+ if (is_null()) {
+ return 0;
+ }
+ return header()->size();
+ }
+
+ static UnsafeSharedSlice create(size_t size) {
+ static_assert(std::is_standard_layout<HeaderT>::value, "HeaderT must have statdard layout");
+ auto ptr = std::make_unique<char[]>(sizeof(HeaderT) + size);
+ auto header_ptr = new (ptr.get()) HeaderT(size);
+ CHECK(header_ptr == reinterpret_cast<HeaderT *>(ptr.get()));
+
+ return UnsafeSharedSlice(std::move(ptr));
+ }
+
+ static UnsafeSharedSlice create(Slice slice) {
+ auto res = create(slice.size());
+ res.as_mutable_slice().copy_from(slice);
+ return res;
+ }
+
+ void clear() {
+ ptr_.reset();
+ }
+
+ private:
+ explicit UnsafeSharedSlice(char *ptr) : ptr_(ptr) {
+ }
+ explicit UnsafeSharedSlice(std::unique_ptr<char[]> from) : ptr_(from.release()) {
+ }
+
+ HeaderT *header() const {
+ return reinterpret_cast<HeaderT *>(ptr_.get());
+ }
+
+ struct SharedSliceDestructor {
+ void operator()(char *ptr) {
+ auto header = reinterpret_cast<HeaderT *>(ptr);
+ if (header->dec()) {
+ if (zero_on_destruct) {
+ MutableSlice(ptr, sizeof(HeaderT) + header->size()).fill_zero_secure();
+ }
+ std::default_delete<char[]>()(ptr);
+ }
+ }
+ };
+
+ std::unique_ptr<char[], SharedSliceDestructor> ptr_;
+};
+} // namespace detail
+
+class BufferSlice;
+
+class UniqueSharedSlice;
+
+class SharedSlice {
+ using Impl = detail::UnsafeSharedSlice<detail::SharedSliceHeader>;
+
+ public:
+ SharedSlice() = default;
+
+ explicit SharedSlice(Slice slice) : impl_(Impl::create(slice)) {
+ }
+
+ explicit SharedSlice(UniqueSharedSlice from);
+
+ SharedSlice(const char *ptr, size_t size) : SharedSlice(Slice(ptr, size)) {
+ }
+
+ SharedSlice clone() const {
+ return SharedSlice(impl_.clone());
+ }
+
+ Slice as_slice() const {
+ return impl_.as_slice();
+ }
+
+ BufferSlice clone_as_buffer_slice() const;
+
+ operator Slice() const {
+ return as_slice();
+ }
+
+ // like in std::string
+ const char *data() const {
+ return as_slice().data();
+ }
+
+ char operator[](size_t at) const {
+ return as_slice()[at];
+ }
+
+ bool empty() const {
+ return size() == 0;
+ }
+
+ size_t size() const {
+ return impl_.size();
+ }
+
+ // like in std::string
+ size_t length() const {
+ return size();
+ }
+
+ void clear() {
+ impl_.clear();
+ }
+
+ private:
+ friend class UniqueSharedSlice;
+ explicit SharedSlice(Impl impl) : impl_(std::move(impl)) {
+ }
+ Impl impl_;
+};
+
+class UniqueSharedSlice {
+ using Impl = detail::UnsafeSharedSlice<detail::SharedSliceHeader>;
+
+ public:
+ UniqueSharedSlice() = default;
+
+ explicit UniqueSharedSlice(size_t size) : impl_(Impl::create(size)) {
+ }
+ explicit UniqueSharedSlice(Slice slice) : impl_(Impl::create(slice)) {
+ }
+
+ UniqueSharedSlice(const char *ptr, size_t size) : UniqueSharedSlice(Slice(ptr, size)) {
+ }
+ explicit UniqueSharedSlice(SharedSlice from) : impl_() {
+ if (from.impl_.is_unique()) {
+ impl_ = std::move(from.impl_);
+ } else {
+ impl_ = Impl::create(from.as_slice());
+ }
+ }
+
+ UniqueSharedSlice copy() const {
+ return UniqueSharedSlice(as_slice());
+ }
+
+ Slice as_slice() const {
+ return impl_.as_slice();
+ }
+
+ MutableSlice as_mutable_slice() {
+ return impl_.as_mutable_slice();
+ }
+
+ operator Slice() const {
+ return as_slice();
+ }
+
+ // like in std::string
+ char *data() {
+ return as_mutable_slice().data();
+ }
+ const char *data() const {
+ return as_slice().data();
+ }
+ char operator[](size_t at) const {
+ return as_slice()[at];
+ }
+
+ bool empty() const {
+ return size() == 0;
+ }
+
+ size_t size() const {
+ return impl_.size();
+ }
+
+ // like in std::string
+ size_t length() const {
+ return size();
+ }
+
+ void clear() {
+ impl_.clear();
+ }
+
+ private:
+ friend class SharedSlice;
+ explicit UniqueSharedSlice(Impl impl) : impl_(std::move(impl)) {
+ }
+ Impl impl_;
+};
+
+inline SharedSlice::SharedSlice(UniqueSharedSlice from) : impl_(std::move(from.impl_)) {
+}
+
+template <bool zero_on_destruct>
+class UniqueSliceImpl {
+ using Impl = detail::UnsafeSharedSlice<detail::UniqueSliceHeader, zero_on_destruct>;
+
+ public:
+ UniqueSliceImpl() = default;
+
+ explicit UniqueSliceImpl(size_t size) : impl_(Impl::create(size)) {
+ }
+ UniqueSliceImpl(size_t size, char c) : impl_(Impl::create(size)) {
+ as_mutable_slice().fill(c);
+ }
+ explicit UniqueSliceImpl(Slice slice) : impl_(Impl::create(slice)) {
+ }
+
+ UniqueSliceImpl(const char *ptr, size_t size) : UniqueSliceImpl(Slice(ptr, size)) {
+ }
+
+ UniqueSliceImpl copy() const {
+ return UniqueSliceImpl(as_slice());
+ }
+
+ Slice as_slice() const {
+ return impl_.as_slice();
+ }
+
+ MutableSlice as_mutable_slice() {
+ return impl_.as_mutable_slice();
+ }
+
+ operator Slice() const {
+ return as_slice();
+ }
+
+ // like in std::string
+ char *data() {
+ return as_mutable_slice().data();
+ }
+ const char *data() const {
+ return as_slice().data();
+ }
+ char operator[](size_t at) const {
+ return as_slice()[at];
+ }
+
+ bool empty() const {
+ return size() == 0;
+ }
+
+ size_t size() const {
+ return impl_.size();
+ }
+
+ // like in std::string
+ size_t length() const {
+ return size();
+ }
+
+ void clear() {
+ impl_.clear();
+ }
+
+ private:
+ explicit UniqueSliceImpl(Impl impl) : impl_(std::move(impl)) {
+ }
+ Impl impl_;
+};
+
+using UniqueSlice = UniqueSliceImpl<false>;
+using SecureString = UniqueSliceImpl<true>;
+
+inline MutableSlice as_mutable_slice(UniqueSharedSlice &unique_shared_slice) {
+ return unique_shared_slice.as_mutable_slice();
+}
+
+inline MutableSlice as_mutable_slice(UniqueSlice &unique_slice) {
+ return unique_slice.as_mutable_slice();
+}
+
+inline MutableSlice as_mutable_slice(SecureString &secure_string) {
+ return secure_string.as_mutable_slice();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice-decl.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice-decl.h
index 69b4a4ad21..12d0382861 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice-decl.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice-decl.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,10 +8,10 @@
#include "td/utils/common.h"
-#include <cstring>
#include <type_traits>
namespace td {
+
class Slice;
class MutableSlice {
@@ -26,9 +26,7 @@ class MutableSlice {
MutableSlice(unsigned char *s, size_t len);
MutableSlice(string &s);
template <class T>
- explicit MutableSlice(T s, std::enable_if_t<std::is_same<char *, T>::value, private_tag> = {})
- : MutableSlice(s, std::strlen(s)) {
- }
+ explicit MutableSlice(T s, std::enable_if_t<std::is_same<char *, T>::value, private_tag> = {});
MutableSlice(char *s, char *t);
MutableSlice(unsigned char *s, unsigned char *t);
template <size_t N>
@@ -54,11 +52,16 @@ class MutableSlice {
MutableSlice substr(size_t from, size_t size) const;
size_t find(char c) const;
size_t rfind(char c) const;
+ void fill(char c);
+ void fill_zero();
+ void fill_zero_secure();
void copy_from(Slice from);
char &back();
char &operator[](size_t i);
+
+ static const size_t npos = static_cast<size_t>(-1);
};
class Slice {
@@ -74,13 +77,9 @@ class Slice {
Slice(const unsigned char *s, size_t len);
Slice(const string &s);
template <class T>
- explicit Slice(T s, std::enable_if_t<std::is_same<char *, std::remove_const_t<T>>::value, private_tag> = {})
- : Slice(s, std::strlen(s)) {
- }
+ explicit Slice(T s, std::enable_if_t<std::is_same<char *, std::remove_const_t<T>>::value, private_tag> = {});
template <class T>
- explicit Slice(T s, std::enable_if_t<std::is_same<const char *, std::remove_const_t<T>>::value, private_tag> = {})
- : Slice(s, std::strlen(s)) {
- }
+ explicit Slice(T s, std::enable_if_t<std::is_same<const char *, std::remove_const_t<T>>::value, private_tag> = {});
Slice(const char *s, const char *t);
Slice(const unsigned char *s, const unsigned char *t);
@@ -91,6 +90,18 @@ class Slice {
constexpr Slice(const char (&a)[N]) : s_(a), len_(N - 1) {
}
+ Slice &operator=(string &&s) = delete;
+
+ template <size_t N>
+ constexpr Slice &operator=(char (&a)[N]) = delete;
+
+ template <size_t N>
+ constexpr Slice &operator=(const char (&a)[N]) {
+ s_ = a;
+ len_ = N - 1;
+ return *this;
+ }
+
bool empty() const;
size_t size() const;
@@ -114,10 +125,13 @@ class Slice {
char back() const;
char operator[](size_t i) const;
+
+ static const size_t npos = static_cast<size_t>(-1);
};
bool operator==(const Slice &a, const Slice &b);
bool operator!=(const Slice &a, const Slice &b);
+bool operator<(const Slice &a, const Slice &b);
class MutableCSlice : public MutableSlice {
struct private_tag {};
@@ -175,13 +189,24 @@ class CSlice : public Slice {
CSlice() : CSlice("") {
}
+ CSlice &operator=(string &&s) = delete;
+
+ template <size_t N>
+ constexpr CSlice &operator=(char (&a)[N]) = delete;
+
+ template <size_t N>
+ constexpr CSlice &operator=(const char (&a)[N]) {
+ this->Slice::operator=(a);
+ return *this;
+ }
+
const char *c_str() const {
return begin();
}
};
struct SliceHash {
- std::size_t operator()(Slice slice) const;
+ uint32 operator()(Slice slice) const;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.cpp
new file mode 100644
index 0000000000..921a824479
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.cpp
@@ -0,0 +1,34 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/Slice.h"
+
+#if TD_HAVE_OPENSSL
+#include <openssl/crypto.h>
+#endif
+
+namespace td {
+
+void MutableSlice::fill(char c) {
+ std::memset(data(), c, size());
+}
+
+void MutableSlice::fill_zero() {
+ fill('\0');
+}
+
+void MutableSlice::fill_zero_secure() {
+#if TD_HAVE_OPENSSL
+ OPENSSL_cleanse(begin(), size());
+#else
+ volatile char *ptr = begin();
+ for (size_t i = 0; i < size(); i++) {
+ ptr[i] = '\0';
+ }
+#endif
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.h
index a9bc6a7551..6c1efb0a69 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.h
@@ -1,20 +1,20 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/utils/common.h"
#include "td/utils/Slice-decl.h"
-#include "td/utils/logging.h"
-
#include <cstring>
+#include <type_traits>
namespace td {
-/*** MutableSlice ***/
-inline MutableSlice::MutableSlice() : MutableSlice(const_cast<char *>(""), static_cast<size_t>(0)) {
+
+inline MutableSlice::MutableSlice() : s_(const_cast<char *>("")), len_(0) {
}
inline MutableSlice::MutableSlice(char *s, size_t len) : s_(s), len_(len) {
@@ -25,7 +25,13 @@ inline MutableSlice::MutableSlice(unsigned char *s, size_t len) : s_(reinterpret
CHECK(s_ != nullptr);
}
-inline MutableSlice::MutableSlice(string &s) : MutableSlice(&s[0], s.size()) {
+inline MutableSlice::MutableSlice(string &s) : s_(&s[0]), len_(s.size()) {
+}
+
+template <class T>
+MutableSlice::MutableSlice(T s, std::enable_if_t<std::is_same<char *, T>::value, private_tag>) : s_(s) {
+ CHECK(s_ != nullptr);
+ len_ = std::strlen(s_);
}
inline MutableSlice::MutableSlice(char *s, char *t) : MutableSlice(s, t - s) {
@@ -104,7 +110,7 @@ inline size_t MutableSlice::find(char c) const {
return pos;
}
}
- return static_cast<size_t>(-1);
+ return npos;
}
inline size_t MutableSlice::rfind(char c) const {
@@ -113,7 +119,7 @@ inline size_t MutableSlice::rfind(char c) const {
return pos;
}
}
- return static_cast<size_t>(-1);
+ return npos;
}
inline void MutableSlice::copy_from(Slice from) {
@@ -130,11 +136,10 @@ inline char &MutableSlice::operator[](size_t i) {
return s_[i];
}
-/*** Slice ***/
-inline Slice::Slice() : Slice("", static_cast<size_t>(0)) {
+inline Slice::Slice() : s_(""), len_(0) {
}
-inline Slice::Slice(const MutableSlice &other) : Slice(other.begin(), other.size()) {
+inline Slice::Slice(const MutableSlice &other) : s_(other.begin()), len_(other.size()) {
}
inline Slice::Slice(const char *s, size_t len) : s_(s), len_(len) {
@@ -145,13 +150,28 @@ inline Slice::Slice(const unsigned char *s, size_t len) : s_(reinterpret_cast<co
CHECK(s_ != nullptr);
}
-inline Slice::Slice(const string &s) : Slice(s.c_str(), s.size()) {
+inline Slice::Slice(const string &s) : s_(s.c_str()), len_(s.size()) {
+}
+
+template <class T>
+Slice::Slice(T s, std::enable_if_t<std::is_same<char *, std::remove_const_t<T>>::value, private_tag>) : s_(s) {
+ CHECK(s_ != nullptr);
+ len_ = std::strlen(s_);
+}
+
+template <class T>
+Slice::Slice(T s, std::enable_if_t<std::is_same<const char *, std::remove_const_t<T>>::value, private_tag>) : s_(s) {
+ CHECK(s_ != nullptr);
+ len_ = std::strlen(s_);
}
-inline Slice::Slice(const char *s, const char *t) : Slice(s, t - s) {
+inline Slice::Slice(const char *s, const char *t) : s_(s), len_(t - s) {
+ CHECK(s_ != nullptr);
}
-inline Slice::Slice(const unsigned char *s, const unsigned char *t) : Slice(s, t - s) {
+inline Slice::Slice(const unsigned char *s, const unsigned char *t)
+ : s_(reinterpret_cast<const char *>(s)), len_(t - s) {
+ CHECK(s_ != nullptr);
}
inline size_t Slice::size() const {
@@ -225,7 +245,7 @@ inline size_t Slice::find(char c) const {
return pos;
}
}
- return static_cast<size_t>(-1);
+ return npos;
}
inline size_t Slice::rfind(char c) const {
@@ -234,7 +254,7 @@ inline size_t Slice::rfind(char c) const {
return pos;
}
}
- return static_cast<size_t>(-1);
+ return npos;
}
inline char Slice::back() const {
@@ -254,6 +274,14 @@ inline bool operator!=(const Slice &a, const Slice &b) {
return !(a == b);
}
+inline bool operator<(const Slice &a, const Slice &b) {
+ auto x = std::memcmp(a.data(), b.data(), td::min(a.size(), b.size()));
+ if (x == 0) {
+ return a.size() < b.size();
+ }
+ return x < 0;
+}
+
inline MutableCSlice::MutableCSlice(char *s, char *t) : MutableSlice(s, t) {
CHECK(*t == '\0');
}
@@ -262,14 +290,38 @@ inline CSlice::CSlice(const char *s, const char *t) : Slice(s, t) {
CHECK(*t == '\0');
}
-inline std::size_t SliceHash::operator()(Slice slice) const {
+inline uint32 SliceHash::operator()(Slice slice) const {
// simple string hash
- std::size_t result = 0;
- constexpr std::size_t MUL = 123456789;
+ uint32 result = 0;
+ constexpr uint32 MUL = 123456789;
for (auto c : slice) {
result = result * MUL + c;
}
return result;
}
+inline Slice as_slice(Slice slice) {
+ return slice;
+}
+
+inline MutableSlice as_slice(MutableSlice slice) {
+ return slice;
+}
+
+inline Slice as_slice(const string &str) {
+ return str;
+}
+
+inline MutableSlice as_slice(string &str) {
+ return str;
+}
+
+inline MutableSlice as_mutable_slice(MutableSlice slice) {
+ return slice;
+}
+
+inline MutableSlice as_mutable_slice(string &str) {
+ return str;
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SliceBuilder.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SliceBuilder.h
new file mode 100644
index 0000000000..062ea136a0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SliceBuilder.h
@@ -0,0 +1,57 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StackAllocator.h"
+#include "td/utils/StringBuilder.h"
+
+#define PSLICE() ::td::detail::Slicify() & ::td::SliceBuilder().ref()
+#define PSTRING() ::td::detail::Stringify() & ::td::SliceBuilder().ref()
+
+namespace td {
+
+class SliceBuilder {
+ public:
+ template <class T>
+ SliceBuilder &operator<<(T &&other) {
+ sb_ << other;
+ return *this;
+ }
+
+ MutableCSlice as_cslice() {
+ return sb_.as_cslice();
+ }
+
+ SliceBuilder &ref() {
+ return *this;
+ }
+
+ private:
+ static const size_t DEFAULT_BUFFER_SIZE = 1024;
+ decltype(StackAllocator::alloc(0)) buffer_ = StackAllocator::alloc(DEFAULT_BUFFER_SIZE);
+ StringBuilder sb_ = StringBuilder(buffer_.as_slice(), true);
+};
+
+namespace detail {
+class Slicify {
+ public:
+ CSlice operator&(SliceBuilder &slice_builder) {
+ return slice_builder.as_cslice();
+ }
+};
+
+class Stringify {
+ public:
+ string operator&(SliceBuilder &slice_builder) {
+ return slice_builder.as_cslice().str();
+ }
+};
+} // namespace detail
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Span.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Span.h
new file mode 100644
index 0000000000..877adcf78a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Span.h
@@ -0,0 +1,158 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#include <array>
+#include <iterator>
+
+namespace td {
+
+namespace detail {
+template <class T, class InnerT>
+class SpanImpl {
+ InnerT *data_{nullptr};
+ size_t size_{0};
+
+ public:
+ SpanImpl() = default;
+ SpanImpl(InnerT *data, size_t size) : data_(data), size_(size) {
+ }
+ SpanImpl(InnerT &data) : SpanImpl(&data, 1) {
+ }
+
+ template <class OtherInnerT>
+ SpanImpl(const SpanImpl<T, OtherInnerT> &other) : SpanImpl(other.data(), other.size()) {
+ }
+
+ template <size_t N>
+ SpanImpl(const std::array<T, N> &arr) : SpanImpl(arr.data(), arr.size()) {
+ }
+ template <size_t N>
+ SpanImpl(std::array<T, N> &arr) : SpanImpl(arr.data(), arr.size()) {
+ }
+ template <size_t N>
+ SpanImpl(const T (&arr)[N]) : SpanImpl(arr, N) {
+ }
+ template <size_t N>
+ SpanImpl(T (&arr)[N]) : SpanImpl(arr, N) {
+ }
+ SpanImpl(const vector<T> &v) : SpanImpl(v.data(), v.size()) {
+ }
+ SpanImpl(vector<T> &v) : SpanImpl(v.data(), v.size()) {
+ }
+
+ template <class OtherInnerT>
+ SpanImpl &operator=(const SpanImpl<T, OtherInnerT> &other) {
+ SpanImpl copy{other};
+ *this = copy;
+ }
+ template <class OtherInnerT>
+ bool operator==(const SpanImpl<T, OtherInnerT> &other) const {
+ if (size() != other.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < size(); i++) {
+ if (!((*this)[i] == other[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ InnerT &operator[](size_t i) {
+ DCHECK(i < size());
+ return data_[i];
+ }
+
+ const InnerT &operator[](size_t i) const {
+ DCHECK(i < size());
+ return data_[i];
+ }
+
+ InnerT &back() {
+ DCHECK(!empty());
+ return data_[size() - 1];
+ }
+
+ const InnerT &back() const {
+ DCHECK(!empty());
+ return data_[size() - 1];
+ }
+
+ InnerT &front() {
+ DCHECK(!empty());
+ return data_[0];
+ }
+
+ const InnerT &front() const {
+ DCHECK(!empty());
+ return data_[0];
+ }
+
+ InnerT *data() const {
+ return data_;
+ }
+
+ InnerT *begin() const {
+ return data_;
+ }
+ InnerT *end() const {
+ return data_ + size_;
+ }
+
+ std::reverse_iterator<InnerT *> rbegin() const {
+ return std::reverse_iterator<InnerT *>(end());
+ }
+ std::reverse_iterator<InnerT *> rend() const {
+ return std::reverse_iterator<InnerT *>(begin());
+ }
+
+ size_t size() const {
+ return size_;
+ }
+ bool empty() const {
+ return size() == 0;
+ }
+
+ SpanImpl &truncate(size_t size) {
+ if (size < size_) {
+ size_ = size;
+ }
+ return *this;
+ }
+
+ SpanImpl substr(size_t offset) const {
+ CHECK(offset <= size_);
+ return SpanImpl(begin() + offset, size_ - offset);
+ }
+ SpanImpl substr(size_t offset, size_t size) const {
+ CHECK(offset <= size_);
+ CHECK(size_ - offset >= size);
+ return SpanImpl(begin() + offset, size);
+ }
+};
+} // namespace detail
+
+template <class T>
+using Span = detail::SpanImpl<T, const T>;
+
+template <class T>
+using MutableSpan = detail::SpanImpl<T, T>;
+
+template <class T>
+Span<T> as_span(const std::vector<T> &vec) {
+ return Span<T>(vec);
+}
+
+template <class T>
+MutableSpan<T> as_mutable_span(std::vector<T> &vec) {
+ return MutableSpan<T>(vec);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SpinLock.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SpinLock.h
index d726b0b2f6..3413f6c5b3 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/SpinLock.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SpinLock.h
@@ -1,14 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/port/thread.h"
+#include "td/utils/port/sleep.h"
#include <atomic>
+#include <memory>
namespace td {
@@ -26,9 +27,10 @@ class SpinLock {
bool next() {
cnt++;
if (cnt < 50) {
+ //TODO pause
return true;
} else {
- td::this_thread::yield();
+ usleep_for(1);
return true;
}
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.cpp
index 4db905368b..947238165e 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,11 +8,73 @@
#include "td/utils/port/thread_local.h"
+#include <array>
+#include <cstdlib>
+
namespace td {
+namespace {
+class ArrayAllocator final : public StackAllocator::AllocatorImpl {
+ static const size_t MEM_SIZE = 1024 * 1024;
+ std::array<char, MEM_SIZE> mem;
+ size_t pos{0};
+
+ MutableSlice allocate(size_t size) final {
+ if (size > MEM_SIZE) {
+ std::abort(); // too much memory requested
+ }
+ char *res = mem.data() + pos;
+ pos += (size + 7) & -8;
+ if (pos > MEM_SIZE) {
+ std::abort(); // memory is over
+ }
+ return {res, size};
+ }
+
+ void free_ptr(char *ptr, size_t size) final {
+ size = (size + 7) & -8;
+ if (size > pos || ptr != mem.data() + (pos - size)) {
+ std::abort(); // shouldn't happen
+ }
+ pos -= size;
+ }
+
+ public:
+ ~ArrayAllocator() final {
+ if (pos != 0) {
+ std::abort(); // shouldn't happen
+ }
+ }
+};
+
+class NewAllocator final : public StackAllocator::AllocatorImpl {
+ MutableSlice allocate(size_t size) final {
+ return {new char[size], size};
+ }
-StackAllocator::Impl &StackAllocator::impl() {
- static TD_THREAD_LOCAL StackAllocator::Impl *impl; // static zero-initialized
- init_thread_local<Impl>(impl);
- return *impl;
+ void free_ptr(char *ptr, size_t size) final {
+ delete[] ptr;
+ }
+
+ public:
+ ~NewAllocator() final = default;
+};
+} // namespace
+
+StackAllocator::Ptr::~Ptr() {
+ if (!slice_.empty()) {
+ allocator_->free_ptr(slice_.data(), slice_.size());
+ }
}
+
+StackAllocator::AllocatorImpl *StackAllocator::impl() {
+ if (get_thread_id() != 0) {
+ static TD_THREAD_LOCAL ArrayAllocator *array_allocator; // static zero-initialized
+ init_thread_local<ArrayAllocator>(array_allocator);
+ return array_allocator;
+ } else {
+ static NewAllocator new_allocator;
+ return &new_allocator;
+ }
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.h
index d2399b9526..7b87aef963 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,75 +7,54 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/MovableValue.h"
-#include "td/utils/Slice-decl.h"
-
-#include <array>
-#include <cstdlib>
+#include "td/utils/Slice.h"
namespace td {
class StackAllocator {
- class Deleter {
+ public:
+ class AllocatorImpl {
public:
- void operator()(char *ptr) {
- free_ptr(ptr);
- }
+ AllocatorImpl() = default;
+ AllocatorImpl(const AllocatorImpl &) = delete;
+ AllocatorImpl &operator=(const AllocatorImpl &) = delete;
+ AllocatorImpl(AllocatorImpl &&) = delete;
+ AllocatorImpl &operator=(AllocatorImpl &&) = delete;
+ virtual ~AllocatorImpl() = default;
+
+ virtual MutableSlice allocate(size_t size) = 0;
+
+ virtual void free_ptr(char *ptr, size_t size) = 0;
};
- // TODO: alloc memory with mmap and unload unused pages
- // memory still can be corrupted, but it is better than explicit free function
- // TODO: use pointer that can't be even copied
- using PtrImpl = std::unique_ptr<char, Deleter>;
+ private:
class Ptr {
public:
- Ptr(char *ptr, size_t size) : ptr_(ptr), size_(size) {
+ Ptr(AllocatorImpl *allocator, size_t size) : allocator_(allocator), slice_(allocator_->allocate(size)) {
+ }
+ Ptr(const Ptr &other) = delete;
+ Ptr &operator=(const Ptr &other) = delete;
+ Ptr(Ptr &&other) noexcept : allocator_(other.allocator_), slice_(other.slice_) {
+ other.allocator_ = nullptr;
+ other.slice_ = MutableSlice();
}
+ Ptr &operator=(Ptr &&other) = delete;
+ ~Ptr();
MutableSlice as_slice() const {
- return MutableSlice(ptr_.get(), size_.get());
+ return slice_;
}
private:
- PtrImpl ptr_;
- MovableValue<size_t> size_;
- };
-
- static void free_ptr(char *ptr) {
- impl().free_ptr(ptr);
- }
-
- struct Impl {
- static const size_t MEM_SIZE = 1024 * 1024;
- std::array<char, MEM_SIZE> mem;
-
- size_t pos{0};
- char *alloc(size_t size) {
- if (size == 0) {
- size = 1;
- }
- char *res = mem.data() + pos;
- size = (size + 7) & -8;
- pos += size;
- if (pos > MEM_SIZE) {
- std::abort(); // memory is over
- }
- return res;
- }
- void free_ptr(char *ptr) {
- size_t new_pos = ptr - mem.data();
- if (new_pos >= pos) {
- std::abort(); // shouldn't happen
- }
- pos = new_pos;
- }
+ AllocatorImpl *allocator_;
+ MutableSlice slice_;
};
- static Impl &impl();
+ static AllocatorImpl *impl();
public:
static Ptr alloc(size_t size) {
- return Ptr(impl().alloc(size), size);
+ return Ptr(impl(), size);
}
};
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.cpp
index b8bb169e60..5a49b1097f 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.cpp
@@ -1,11 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/Status.h"
+#include "td/utils/SliceBuilder.h"
+
#if TD_PORT_WINDOWS
#include "td/utils/port/wstring_convert.h"
#endif
@@ -42,13 +44,45 @@ string winerror_to_string(int code) {
wchar_t wbuf[size];
auto res_size = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, code, 0, wbuf, size - 1, nullptr);
if (res_size == 0) {
- return "Unknown windows error";
+ return "Unknown Windows error";
}
while (res_size != 0 && (wbuf[res_size - 1] == '\n' || wbuf[res_size - 1] == '\r')) {
res_size--;
}
- return from_wstring(wbuf, res_size).ok();
+ auto error_message = from_wstring(wbuf, res_size);
+ if (error_message.is_error()) {
+ return "Invalid Windows error";
+ }
+ return error_message.move_as_ok();
}
#endif
+Status Status::move_as_error_prefix(Slice prefix) const {
+ CHECK(is_error());
+ Info info = get_info();
+ switch (info.error_type) {
+ case ErrorType::General:
+ return Error(code(), PSLICE() << prefix << message());
+ case ErrorType::Os:
+ return Status(false, ErrorType::Os, code(), PSLICE() << prefix << message());
+ default:
+ UNREACHABLE();
+ return {};
+ }
+}
+
+Status Status::move_as_error_suffix(Slice suffix) const {
+ CHECK(is_error());
+ Info info = get_info();
+ switch (info.error_type) {
+ case ErrorType::General:
+ return Error(code(), PSLICE() << message() << suffix);
+ case ErrorType::Os:
+ return Status(false, ErrorType::Os, code(), PSLICE() << message() << suffix);
+ default:
+ UNREACHABLE();
+ return {};
+ }
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.h
index 8ef2846df1..83e1abcb23 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,7 +15,9 @@
#include <cerrno>
#include <cstring>
+#include <memory>
#include <new>
+#include <type_traits>
#include <utility>
#define TRY_STATUS(status) \
@@ -25,14 +27,84 @@
return try_status.move_as_error(); \
} \
}
-#define TRY_RESULT(name, result) TRY_RESULT_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result)
+
+#define TRY_STATUS_PREFIX(status, prefix) \
+ { \
+ auto try_status = (status); \
+ if (try_status.is_error()) { \
+ return try_status.move_as_error_prefix(prefix); \
+ } \
+ }
+
+#define TRY_STATUS_PROMISE(promise_name, status) \
+ { \
+ auto try_status = (status); \
+ if (try_status.is_error()) { \
+ promise_name.set_error(try_status.move_as_error()); \
+ return; \
+ } \
+ }
+
+#define TRY_STATUS_PROMISE_PREFIX(promise_name, status, prefix) \
+ { \
+ auto try_status = (status); \
+ if (try_status.is_error()) { \
+ promise_name.set_error(try_status.move_as_error_prefix(prefix)); \
+ return; \
+ } \
+ }
+
+#define TRY_RESULT(name, result) TRY_RESULT_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result)
+
+#define TRY_RESULT_PROMISE(promise_name, name, result) \
+ TRY_RESULT_PROMISE_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result)
+
+#define TRY_RESULT_ASSIGN(name, result) TRY_RESULT_IMPL(TD_CONCAT(r_response, __LINE__), name, result)
+
+#define TRY_RESULT_PROMISE_ASSIGN(promise_name, name, result) \
+ TRY_RESULT_PROMISE_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result)
+
+#define TRY_RESULT_PREFIX(name, result, prefix) \
+ TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix)
+
+#define TRY_RESULT_PREFIX_ASSIGN(name, result, prefix) \
+ TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result, prefix)
+
+#define TRY_RESULT_PROMISE_PREFIX(promise_name, name, result, prefix) \
+ TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix)
+
+#define TRY_RESULT_PROMISE_PREFIX_ASSIGN(promise_name, name, result, prefix) \
+ TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result, prefix)
#define TRY_RESULT_IMPL(r_name, name, result) \
auto r_name = (result); \
if (r_name.is_error()) { \
return r_name.move_as_error(); \
} \
- auto name = r_name.move_as_ok();
+ name = r_name.move_as_ok();
+
+#define TRY_RESULT_PREFIX_IMPL(r_name, name, result, prefix) \
+ auto r_name = (result); \
+ if (r_name.is_error()) { \
+ return r_name.move_as_error_prefix(prefix); \
+ } \
+ name = r_name.move_as_ok();
+
+#define TRY_RESULT_PROMISE_IMPL(promise_name, r_name, name, result) \
+ auto r_name = (result); \
+ if (r_name.is_error()) { \
+ promise_name.set_error(r_name.move_as_error()); \
+ return; \
+ } \
+ name = r_name.move_as_ok();
+
+#define TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, r_name, name, result, prefix) \
+ auto r_name = (result); \
+ if (r_name.is_error()) { \
+ promise_name.set_error(r_name.move_as_error_prefix(prefix)); \
+ return; \
+ } \
+ name = r_name.move_as_ok();
#define LOG_STATUS(status) \
{ \
@@ -49,19 +121,19 @@
#if TD_PORT_POSIX
#define OS_ERROR(message) \
- [&]() { \
+ [&] { \
auto saved_errno = errno; \
return ::td::Status::PosixError(saved_errno, (message)); \
}()
#define OS_SOCKET_ERROR(message) OS_ERROR(message)
#elif TD_PORT_WINDOWS
#define OS_ERROR(message) \
- [&]() { \
+ [&] { \
auto saved_error = ::GetLastError(); \
return ::td::Status::WindowsError(saved_error, (message)); \
}()
#define OS_SOCKET_ERROR(message) \
- [&]() { \
+ [&] { \
auto saved_error = ::WSAGetLastError(); \
return ::td::Status::WindowsError(saved_error, (message)); \
}()
@@ -78,16 +150,13 @@ string winerror_to_string(int code);
#endif
class Status {
- enum class ErrorType : int8 { general, os };
+ enum class ErrorType : int8 { General, Os };
public:
Status() = default;
bool operator==(const Status &other) const {
- if (get_info().static_flag) {
- return ptr_ == other.ptr_;
- }
- return false;
+ return ptr_ == other.ptr_;
}
Status clone() const TD_WARN_UNUSED_RESULT {
@@ -106,7 +175,7 @@ class Status {
}
static Status Error(int err, Slice message = Slice()) TD_WARN_UNUSED_RESULT {
- return Status(false, ErrorType::general, err, message);
+ return Status(false, ErrorType::General, err, message);
}
static Status Error(Slice message) TD_WARN_UNUSED_RESULT {
@@ -115,13 +184,13 @@ class Status {
#if TD_PORT_WINDOWS
static Status WindowsError(int saved_error, Slice message) TD_WARN_UNUSED_RESULT {
- return Status(false, ErrorType::os, saved_error, message);
+ return Status(false, ErrorType::Os, saved_error, message);
}
#endif
#if TD_PORT_POSIX
static Status PosixError(int32 saved_errno, Slice message) TD_WARN_UNUSED_RESULT {
- return Status(false, ErrorType::os, saved_errno, message);
+ return Status(false, ErrorType::Os, saved_errno, message);
}
#endif
@@ -131,17 +200,7 @@ class Status {
template <int Code>
static Status Error() {
- static Status status(true, ErrorType::general, Code, "");
- return status.clone_static();
- }
-
- static Status InvalidId() TD_WARN_UNUSED_RESULT {
- static Status status(true, ErrorType::general, 0, "Invalid Id");
- return status.clone_static();
- }
-
- static Status Hangup() TD_WARN_UNUSED_RESULT {
- static Status status(true, ErrorType::general, 0, "Hangup");
+ static Status status(true, ErrorType::General, Code, "");
return status.clone_static();
}
@@ -151,10 +210,10 @@ class Status {
}
Info info = get_info();
switch (info.error_type) {
- case ErrorType::general:
+ case ErrorType::General:
sb << "[Error";
break;
- case ErrorType::os:
+ case ErrorType::Os:
#if TD_PORT_POSIX
sb << "[PosixError : " << strerror_safe(info.error_code);
#elif TD_PORT_WINDOWS
@@ -162,7 +221,6 @@ class Status {
#endif
break;
default:
- LOG(FATAL) << "Unknown status type: " << static_cast<int8>(info.error_type);
UNREACHABLE();
break;
}
@@ -186,9 +244,21 @@ class Status {
return ptr_ != nullptr;
}
+#ifdef TD_STATUS_NO_ENSURE
+ void ensure() const {
+ if (!is_ok()) {
+ LOG(FATAL) << "Unexpected Status " << to_string();
+ }
+ }
+ void ensure_error() const {
+ if (is_ok()) {
+ LOG(FATAL) << "Unexpected Status::OK";
+ }
+ }
+#else
void ensure_impl(CSlice file_name, int line) const {
if (!is_ok()) {
- LOG(FATAL) << "Unexpexted Status " << to_string() << " in file " << file_name << " at line " << line;
+ LOG(FATAL) << "Unexpected Status " << to_string() << " in file " << file_name << " at line " << line;
}
}
void ensure_error_impl(CSlice file_name, int line) const {
@@ -196,6 +266,7 @@ class Status {
LOG(FATAL) << "Unexpected Status::OK in file " << file_name << " at line " << line;
}
}
+#endif
void ignore() const {
// nop
@@ -221,16 +292,15 @@ class Status {
}
Info info = get_info();
switch (info.error_type) {
- case ErrorType::general:
+ case ErrorType::General:
return message().str();
- case ErrorType::os:
+ case ErrorType::Os:
#if TD_PORT_POSIX
return strerror_safe(info.error_code).str();
#elif TD_PORT_WINDOWS
return winerror_to_string(info.error_code);
#endif
default:
- LOG(FATAL) << "Unknown status type: " << static_cast<int8>(info.error_type);
UNREACHABLE();
return "";
}
@@ -248,6 +318,16 @@ class Status {
return std::move(*this);
}
+ Status move_as_ok() = delete;
+
+ Status move_as_error_prefix(const Status &status) const TD_WARN_UNUSED_RESULT {
+ return status.move_as_error_suffix(message());
+ }
+
+ Status move_as_error_prefix(Slice prefix) const TD_WARN_UNUSED_RESULT;
+
+ Status move_as_error_suffix(Slice suffix) const TD_WARN_UNUSED_RESULT;
+
private:
struct Info {
bool static_flag : 1;
@@ -277,10 +357,13 @@ class Status {
Status(bool static_flag, ErrorType error_type, int error_code, Slice message)
: Status(to_info(static_flag, error_type, error_code), message) {
+ if (static_flag) {
+ TD_LSAN_IGNORE(ptr_.get());
+ }
}
Status clone_static() const TD_WARN_UNUSED_RESULT {
- CHECK(is_ok() || get_info().static_flag);
+ CHECK(ptr_ != nullptr && get_info().static_flag);
Status result;
result.ptr_ = std::unique_ptr<char[], Deleter>(ptr_.get());
return result;
@@ -325,24 +408,30 @@ class Status {
template <class T = Unit>
class Result {
public:
- Result() : status_(Status::Error()) {
+ using ValueT = T;
+ Result() : status_(Status::Error<-1>()) {
}
- template <class S>
+ template <class S, std::enable_if_t<!std::is_same<std::decay_t<S>, Result>::value, int> = 0>
Result(S &&x) : status_(), value_(std::forward<S>(x)) {
}
+ struct emplace_t {};
+ template <class... ArgsT>
+ Result(emplace_t, ArgsT &&...args) : status_(), value_(std::forward<ArgsT>(args)...) {
+ }
Result(Status &&status) : status_(std::move(status)) {
CHECK(status_.is_error());
}
Result(const Result &) = delete;
Result &operator=(const Result &) = delete;
- Result(Result &&other) : status_(std::move(other.status_)) {
+ Result(Result &&other) noexcept : status_(std::move(other.status_)) {
if (status_.is_ok()) {
new (&value_) T(std::move(other.value_));
other.value_.~T();
}
- other.status_ = Status::Error();
+ other.status_ = Status::Error<-2>();
}
- Result &operator=(Result &&other) {
+ Result &operator=(Result &&other) noexcept {
+ CHECK(this != &other);
if (status_.is_ok()) {
value_.~T();
}
@@ -358,21 +447,38 @@ class Result {
other.value_.~T();
}
status_ = std::move(other.status_);
- other.status_ = Status::Error();
+ other.status_ = Status::Error<-3>();
return *this;
}
+ template <class... ArgsT>
+ void emplace(ArgsT &&...args) {
+ if (status_.is_ok()) {
+ value_.~T();
+ }
+ new (&value_) T(std::forward<ArgsT>(args)...);
+ status_ = Status::OK();
+ }
~Result() {
if (status_.is_ok()) {
value_.~T();
}
}
+#ifdef TD_STATUS_NO_ENSURE
+ void ensure() const {
+ status_.ensure();
+ }
+ void ensure_error() const {
+ status_.ensure_error();
+ }
+#else
void ensure_impl(CSlice file_name, int line) const {
status_.ensure_impl(file_name, line);
}
void ensure_error_impl(CSlice file_name, int line) const {
status_.ensure_error_impl(file_name, line);
}
+#endif
void ignore() const {
status_.ignore();
}
@@ -389,20 +495,42 @@ class Result {
Status move_as_error() TD_WARN_UNUSED_RESULT {
CHECK(status_.is_error());
SCOPE_EXIT {
- status_ = Status::Error();
+ status_ = Status::Error<-4>();
};
return std::move(status_);
}
+ Status move_as_error_prefix(Slice prefix) TD_WARN_UNUSED_RESULT {
+ SCOPE_EXIT {
+ status_ = Status::Error<-5>();
+ };
+ return status_.move_as_error_prefix(prefix);
+ }
+ Status move_as_error_prefix(const Status &prefix) TD_WARN_UNUSED_RESULT {
+ SCOPE_EXIT {
+ status_ = Status::Error<-6>();
+ };
+ return status_.move_as_error_prefix(prefix);
+ }
+ Status move_as_error_suffix(Slice suffix) TD_WARN_UNUSED_RESULT {
+ SCOPE_EXIT {
+ status_ = Status::Error<-7>();
+ };
+ return status_.move_as_error_suffix(suffix);
+ }
const T &ok() const {
- CHECK(status_.is_ok()) << status_;
+ LOG_CHECK(status_.is_ok()) << status_;
return value_;
}
T &ok_ref() {
- CHECK(status_.is_ok()) << status_;
+ LOG_CHECK(status_.is_ok()) << status_;
+ return value_;
+ }
+ const T &ok_ref() const {
+ LOG_CHECK(status_.is_ok()) << status_;
return value_;
}
T move_as_ok() {
- CHECK(status_.is_ok()) << status_;
+ LOG_CHECK(status_.is_ok()) << status_;
return std::move(value_);
}
@@ -416,6 +544,22 @@ class Result {
*this = Result<T>();
}
+ template <class F>
+ Result<decltype(std::declval<F>()(std::declval<T>()))> move_map(F &&f) {
+ if (is_error()) {
+ return move_as_error();
+ }
+ return f(move_as_ok());
+ }
+
+ template <class F>
+ decltype(std::declval<F>()(std::declval<T>())) move_fmap(F &&f) {
+ if (is_error()) {
+ return move_as_error();
+ }
+ return f(move_as_ok());
+ }
+
private:
Status status_;
union {
@@ -432,27 +576,4 @@ inline StringBuilder &operator<<(StringBuilder &string_builder, const Status &st
return status.print(string_builder);
}
-namespace detail {
-
-class SlicifySafe {
- public:
- Result<CSlice> operator&(Logger &logger) {
- if (logger.is_error()) {
- return Status::Error("Buffer overflow");
- }
- return logger.as_cslice();
- }
-};
-
-class StringifySafe {
- public:
- Result<string> operator&(Logger &logger) {
- if (logger.is_error()) {
- return Status::Error("Buffer overflow");
- }
- return logger.as_cslice().str();
- }
-};
-
-} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StealingQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/StealingQueue.h
new file mode 100644
index 0000000000..d2fb773f3a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StealingQueue.h
@@ -0,0 +1,125 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+
+#include <array>
+#include <atomic>
+
+namespace td {
+
+template <class T, size_t N = 256>
+class StealingQueue {
+ public:
+ static_assert(N > 0 && (N & (N - 1)) == 0, "");
+
+ // tries to put a value
+ // returns if succeeded
+ // only owner is allowed to to do this
+ template <class F>
+ void local_push(T value, F &&overflow_f) {
+ while (true) {
+ auto tail = tail_.load(std::memory_order_relaxed);
+ auto head = head_.load(); // TODO: memory order
+
+ if (static_cast<size_t>(tail - head) < N) {
+ buf_[tail & MASK].store(value, std::memory_order_relaxed);
+ tail_.store(tail + 1, std::memory_order_release);
+ return;
+ }
+
+ // queue is full
+ // TODO: batch insert into global queue?
+ auto n = N / 2 + 1;
+ auto new_head = head + n;
+ if (!head_.compare_exchange_strong(head, new_head)) {
+ continue;
+ }
+
+ for (size_t i = 0; i < n; i++) {
+ overflow_f(buf_[(i + head) & MASK].load(std::memory_order_relaxed));
+ }
+ overflow_f(value);
+
+ return;
+ }
+ }
+
+ // tries to pop a value
+ // returns if succeeded
+ // only owner is allowed to do this
+ bool local_pop(T &value) {
+ auto tail = tail_.load(std::memory_order_relaxed);
+ auto head = head_.load();
+
+ if (head == tail) {
+ return false;
+ }
+
+ value = buf_[head & MASK].load(std::memory_order_relaxed);
+ return head_.compare_exchange_strong(head, head + 1);
+ }
+
+ bool steal(T &value, StealingQueue<T, N> &other) {
+ while (true) {
+ auto tail = tail_.load(std::memory_order_relaxed);
+ auto head = head_.load(); // TODO: memory order
+
+ auto other_head = other.head_.load();
+ auto other_tail = other.tail_.load(std::memory_order_acquire);
+
+ if (other_tail < other_head) {
+ continue;
+ }
+ auto n = narrow_cast<size_t>(other_tail - other_head);
+ if (n > N) {
+ continue;
+ }
+ n -= n / 2;
+ n = td::min(n, static_cast<size_t>(head + N - tail));
+ if (n == 0) {
+ return false;
+ }
+
+ for (size_t i = 0; i < n; i++) {
+ buf_[(i + tail) & MASK].store(other.buf_[(i + other_head) & MASK].load(std::memory_order_relaxed),
+ std::memory_order_relaxed);
+ }
+
+ if (!other.head_.compare_exchange_strong(other_head, other_head + n)) {
+ continue;
+ }
+
+ n--;
+ value = buf_[(tail + n) & MASK].load(std::memory_order_relaxed);
+ tail_.store(tail + n, std::memory_order_release);
+ return true;
+ }
+ }
+
+ StealingQueue() {
+ for (auto &x : buf_) {
+// workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64658
+#if TD_GCC && GCC_VERSION <= 40902
+ x = T();
+#else
+ std::atomic_init(&x, T());
+#endif
+ }
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+ }
+
+ private:
+ std::atomic<int64> head_{0};
+ std::atomic<int64> tail_{0};
+ static constexpr size_t MASK{N - 1};
+ std::array<std::atomic<T>, N> buf_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Storer.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Storer.h
index 91750dcd44..7743b81ff5 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Storer.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Storer.h
@@ -1,10 +1,11 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/utils/StorerBase.h"
#include "td/utils/common.h"
@@ -15,16 +16,17 @@
#include <limits>
namespace td {
-class SliceStorer : public Storer {
+
+class SliceStorer final : public Storer {
Slice slice;
public:
explicit SliceStorer(Slice slice) : slice(slice) {
}
- size_t size() const override {
+ size_t size() const final {
return slice.size();
}
- size_t store(uint8 *ptr) const override {
+ size_t store(uint8 *ptr) const final {
std::memcpy(ptr, slice.ubegin(), slice.size());
return slice.size();
}
@@ -34,7 +36,7 @@ inline SliceStorer create_storer(Slice slice) {
return SliceStorer(slice);
}
-class ConcatStorer : public Storer {
+class ConcatStorer final : public Storer {
const Storer &a_;
const Storer &b_;
@@ -42,11 +44,11 @@ class ConcatStorer : public Storer {
ConcatStorer(const Storer &a, const Storer &b) : a_(a), b_(b) {
}
- size_t size() const override {
+ size_t size() const final {
return a_.size() + b_.size();
}
- size_t store(uint8 *ptr) const override {
+ size_t store(uint8 *ptr) const final {
uint8 *ptr_save = ptr;
ptr += a_.store(ptr);
ptr += b_.store(ptr);
@@ -59,17 +61,17 @@ inline ConcatStorer create_storer(const Storer &a, const Storer &b) {
}
template <class T>
-class DefaultStorer : public Storer {
+class DefaultStorer final : public Storer {
public:
explicit DefaultStorer(const T &object) : object_(object) {
}
- size_t size() const override {
+ size_t size() const final {
if (size_ == std::numeric_limits<size_t>::max()) {
size_ = tl_calc_length(object_);
}
return size_;
}
- size_t store(uint8 *ptr) const override {
+ size_t store(uint8 *ptr) const final {
return tl_store_unsafe(object_, ptr);
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StorerBase.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/StorerBase.h
index e6fea28e16..05e5edc714 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StorerBase.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StorerBase.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -19,7 +19,7 @@ class Storer {
Storer &operator=(Storer &&) = default;
virtual ~Storer() = default;
virtual size_t size() const = 0;
- virtual size_t store(uint8 *ptr) const = 0;
+ virtual size_t store(uint8 *ptr) const TD_WARN_UNUSED_RESULT = 0;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.cpp
index ce64bbc9a6..d82fdbea53 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,70 +8,181 @@
#include "td/utils/misc.h"
#include "td/utils/port/thread_local.h"
+#include "td/utils/Slice.h"
#include <cstdio>
+#include <cstring>
+#include <limits>
#include <locale>
+#include <memory>
#include <sstream>
+#include <utility>
namespace td {
-// TODO: optimize
+StringBuilder::StringBuilder(MutableSlice slice, bool use_buffer)
+ : begin_ptr_(slice.begin()), current_ptr_(begin_ptr_), use_buffer_(use_buffer) {
+ if (slice.size() <= RESERVED_SIZE) {
+ auto buffer_size = RESERVED_SIZE + 100;
+ buffer_ = std::make_unique<char[]>(buffer_size);
+ begin_ptr_ = buffer_.get();
+ current_ptr_ = begin_ptr_;
+ end_ptr_ = begin_ptr_ + buffer_size - RESERVED_SIZE;
+ } else {
+ end_ptr_ = slice.end() - RESERVED_SIZE;
+ }
+}
+
+StringBuilder &StringBuilder::operator<<(Slice slice) {
+ size_t size = slice.size();
+ if (unlikely(!reserve(size))) {
+ if (end_ptr_ < current_ptr_) {
+ return on_error();
+ }
+ auto available_size = static_cast<size_t>(end_ptr_ + RESERVED_SIZE - 1 - current_ptr_);
+ if (size > available_size) {
+ error_flag_ = true;
+ size = available_size;
+ }
+ }
+
+ std::memcpy(current_ptr_, slice.begin(), size);
+ current_ptr_ += size;
+ return *this;
+}
+
+template <class T>
+static char *print_uint(char *current_ptr, T x) {
+ if (x < 100) {
+ if (x < 10) {
+ *current_ptr++ = static_cast<char>('0' + x);
+ } else {
+ *current_ptr++ = static_cast<char>('0' + x / 10);
+ *current_ptr++ = static_cast<char>('0' + x % 10);
+ }
+ return current_ptr;
+ }
+
+ auto begin_ptr = current_ptr;
+ do {
+ *current_ptr++ = static_cast<char>('0' + x % 10);
+ x /= 10;
+ } while (x > 0);
+
+ auto end_ptr = current_ptr - 1;
+ while (begin_ptr < end_ptr) {
+ std::swap(*begin_ptr++, *end_ptr--);
+ }
+
+ return current_ptr;
+}
+
+template <class T>
+static char *print_int(char *current_ptr, T x) {
+ if (x < 0) {
+ if (x == std::numeric_limits<T>::min()) {
+ current_ptr = print_int(current_ptr, x + 1);
+ CHECK(current_ptr[-1] != '9');
+ current_ptr[-1]++;
+ return current_ptr;
+ }
+
+ *current_ptr++ = '-';
+ x = -x;
+ }
+
+ return print_uint(current_ptr, x);
+}
+
+bool StringBuilder::reserve_inner(size_t size) {
+ if (!use_buffer_) {
+ return false;
+ }
+
+ size_t old_data_size = current_ptr_ - begin_ptr_;
+ if (size >= std::numeric_limits<size_t>::max() - RESERVED_SIZE - old_data_size - 1) {
+ return false;
+ }
+ size_t need_data_size = old_data_size + size;
+ size_t old_buffer_size = end_ptr_ - begin_ptr_;
+ if (old_buffer_size >= (std::numeric_limits<size_t>::max() - RESERVED_SIZE) / 2 - 2) {
+ return false;
+ }
+ size_t new_buffer_size = (old_buffer_size + 1) * 2;
+ if (new_buffer_size < need_data_size) {
+ new_buffer_size = need_data_size;
+ }
+ if (new_buffer_size < 100) {
+ new_buffer_size = 100;
+ }
+ new_buffer_size += RESERVED_SIZE;
+ auto new_buffer = std::make_unique<char[]>(new_buffer_size);
+ std::memcpy(new_buffer.get(), begin_ptr_, old_data_size);
+ buffer_ = std::move(new_buffer);
+ begin_ptr_ = buffer_.get();
+ current_ptr_ = begin_ptr_ + old_data_size;
+ end_ptr_ = begin_ptr_ + new_buffer_size - RESERVED_SIZE;
+ CHECK(end_ptr_ > current_ptr_);
+ CHECK(static_cast<size_t>(end_ptr_ - current_ptr_) >= size);
+ return true;
+}
+
StringBuilder &StringBuilder::operator<<(int x) {
- if (unlikely(end_ptr_ < current_ptr_)) {
+ if (unlikely(!reserve())) {
return on_error();
}
- current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%d", x);
+ current_ptr_ = print_int(current_ptr_, x);
return *this;
}
StringBuilder &StringBuilder::operator<<(unsigned int x) {
- if (unlikely(end_ptr_ < current_ptr_)) {
+ if (unlikely(!reserve())) {
return on_error();
}
- current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%u", x);
+ current_ptr_ = print_uint(current_ptr_, x);
return *this;
}
StringBuilder &StringBuilder::operator<<(long int x) {
- if (unlikely(end_ptr_ < current_ptr_)) {
+ if (unlikely(!reserve())) {
return on_error();
}
- current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%ld", x);
+ current_ptr_ = print_int(current_ptr_, x);
return *this;
}
StringBuilder &StringBuilder::operator<<(long unsigned int x) {
- if (unlikely(end_ptr_ < current_ptr_)) {
+ if (unlikely(!reserve())) {
return on_error();
}
- current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%lu", x);
+ current_ptr_ = print_uint(current_ptr_, x);
return *this;
}
StringBuilder &StringBuilder::operator<<(long long int x) {
- if (unlikely(end_ptr_ < current_ptr_)) {
+ if (unlikely(!reserve())) {
return on_error();
}
- current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%lld", x);
+ current_ptr_ = print_int(current_ptr_, x);
return *this;
}
StringBuilder &StringBuilder::operator<<(long long unsigned int x) {
- if (unlikely(end_ptr_ < current_ptr_)) {
+ if (unlikely(!reserve())) {
return on_error();
}
- current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%llu", x);
+ current_ptr_ = print_uint(current_ptr_, x);
return *this;
}
StringBuilder &StringBuilder::operator<<(FixedDouble x) {
- if (unlikely(end_ptr_ < current_ptr_)) {
+ if (unlikely(!reserve(std::numeric_limits<double>::max_exponent10 + x.precision + 4))) {
return on_error();
}
static TD_THREAD_LOCAL std::stringstream *ss;
if (init_thread_local<std::stringstream>(ss)) {
- ss->imbue(std::locale::classic());
+ auto previous_locale = ss->imbue(std::locale::classic());
ss->setf(std::ios_base::fixed, std::ios_base::floatfield);
} else {
ss->str(std::string());
@@ -80,8 +191,8 @@ StringBuilder &StringBuilder::operator<<(FixedDouble x) {
ss->precision(x.precision);
*ss << x.d;
- int len = narrow_cast<int>(static_cast<std::streamoff>(ss->tellp()));
- auto left = end_ptr_ + reserved_size - current_ptr_;
+ auto len = narrow_cast<int>(static_cast<std::streamoff>(ss->tellp()));
+ auto left = end_ptr_ + RESERVED_SIZE - current_ptr_;
if (unlikely(len >= left)) {
error_flag_ = true;
len = left ? narrow_cast<int>(left - 1) : 0;
@@ -92,10 +203,10 @@ StringBuilder &StringBuilder::operator<<(FixedDouble x) {
}
StringBuilder &StringBuilder::operator<<(const void *ptr) {
- if (unlikely(end_ptr_ < current_ptr_)) {
+ if (unlikely(!reserve())) {
return on_error();
}
- current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%p", ptr);
+ current_ptr_ += std::snprintf(current_ptr_, RESERVED_SIZE, "%p", ptr);
return *this;
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.h
index a6345a9273..37adab338e 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,22 +7,19 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/Slice-decl.h"
+#include "td/utils/Slice.h"
#include "td/utils/StackAllocator.h"
#include <cstdlib>
-#include <cstring>
+#include <memory>
#include <type_traits>
namespace td {
class StringBuilder {
public:
- explicit StringBuilder(MutableSlice slice)
- : begin_ptr_(slice.begin()), current_ptr_(begin_ptr_), end_ptr_(slice.end() - reserved_size) {
- if (slice.size() <= reserved_size) {
- std::abort(); // shouldn't happen
- }
+ explicit StringBuilder(MutableSlice slice, bool use_buffer = false);
+ StringBuilder() : StringBuilder({}, true) {
}
void clear() {
@@ -30,8 +27,13 @@ class StringBuilder {
error_flag_ = false;
}
+ void pop_back() {
+ CHECK(current_ptr_ > begin_ptr_);
+ current_ptr_--;
+ }
+
MutableCSlice as_cslice() {
- if (current_ptr_ >= end_ptr_ + reserved_size) {
+ if (current_ptr_ >= end_ptr_ + RESERVED_SIZE) {
std::abort(); // shouldn't happen
}
*current_ptr_ = 0;
@@ -42,31 +44,33 @@ class StringBuilder {
return error_flag_;
}
- StringBuilder &operator<<(const char *str) {
+ template <class T>
+ std::enable_if_t<std::is_same<char *, std::remove_const_t<T>>::value, StringBuilder> &operator<<(T str) {
+ return *this << Slice(str);
+ }
+ template <class T>
+ std::enable_if_t<std::is_same<const char *, std::remove_const_t<T>>::value, StringBuilder> &operator<<(T str) {
return *this << Slice(str);
}
- StringBuilder &operator<<(Slice slice) {
- if (unlikely(end_ptr_ < current_ptr_)) {
- return on_error();
- }
- auto size = static_cast<size_t>(end_ptr_ + reserved_size - 1 - current_ptr_);
- if (unlikely(slice.size() > size)) {
- error_flag_ = true;
- } else {
- size = slice.size();
- }
- std::memcpy(current_ptr_, slice.begin(), size);
- current_ptr_ += size;
- return *this;
+ template <size_t N>
+ StringBuilder &operator<<(char (&str)[N]) = delete;
+
+ template <size_t N>
+ StringBuilder &operator<<(const char (&str)[N]) {
+ return *this << Slice(str, N - 1);
}
+ StringBuilder &operator<<(const wchar_t *str) = delete;
+
+ StringBuilder &operator<<(Slice slice);
+
StringBuilder &operator<<(bool b) {
return *this << (b ? Slice("true") : Slice("false"));
}
StringBuilder &operator<<(char c) {
- if (unlikely(end_ptr_ < current_ptr_)) {
+ if (unlikely(!reserve())) {
return on_error();
}
*current_ptr_++ = c;
@@ -108,22 +112,33 @@ class StringBuilder {
StringBuilder &operator<<(const void *ptr);
- template <class T>
- StringBuilder &operator<<(const T *ptr) {
- return *this << static_cast<const void *>(ptr);
- }
-
private:
char *begin_ptr_;
char *current_ptr_;
char *end_ptr_;
bool error_flag_ = false;
- static constexpr size_t reserved_size = 30;
+ bool use_buffer_ = false;
+ std::unique_ptr<char[]> buffer_;
+ static constexpr size_t RESERVED_SIZE = 30;
StringBuilder &on_error() {
error_flag_ = true;
return *this;
}
+
+ bool reserve() {
+ if (end_ptr_ > current_ptr_) {
+ return true;
+ }
+ return reserve_inner(RESERVED_SIZE);
+ }
+ bool reserve(size_t size) {
+ if (end_ptr_ > current_ptr_ && static_cast<size_t>(end_ptr_ - current_ptr_) >= size) {
+ return true;
+ }
+ return reserve_inner(size);
+ }
+ bool reserve_inner(size_t size);
};
template <class T>
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadLocalStorage.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadLocalStorage.h
new file mode 100644
index 0000000000..da42c4ac0e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadLocalStorage.h
@@ -0,0 +1,55 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/port/thread_local.h"
+
+#include <array>
+#include <atomic>
+
+namespace td {
+
+template <class T>
+class ThreadLocalStorage {
+ public:
+ T &get() {
+ return thread_local_node().value;
+ }
+
+ template <class F>
+ void for_each(F &&f) {
+ int32 n = max_thread_id_.load();
+ for (int32 i = 0; i < n; i++) {
+ f(nodes_[i].value);
+ }
+ }
+ template <class F>
+ void for_each(F &&f) const {
+ int32 n = max_thread_id_.load();
+ for (int32 i = 0; i < n; i++) {
+ f(nodes_[i].value);
+ }
+ }
+
+ private:
+ struct Node {
+ T value;
+ char padding[TD_CONCURRENCY_PAD];
+ };
+ static constexpr int32 MAX_THREAD_ID = 128;
+ std::atomic<int32> max_thread_id_{MAX_THREAD_ID};
+ std::array<Node, MAX_THREAD_ID> nodes_;
+
+ Node &thread_local_node() {
+ auto thread_id = get_thread_id();
+ CHECK(0 <= thread_id && static_cast<size_t>(thread_id) < nodes_.size());
+ return nodes_[thread_id];
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadSafeCounter.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadSafeCounter.h
new file mode 100644
index 0000000000..2d6f3f2950
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadSafeCounter.h
@@ -0,0 +1,132 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/ThreadLocalStorage.h"
+
+#include <array>
+#include <atomic>
+#include <mutex>
+
+namespace td {
+
+template <size_t N>
+class ThreadSafeMultiCounter {
+ public:
+ void add(size_t index, int64 diff) {
+ CHECK(index < N);
+ tls_.get()[index].fetch_add(diff, std::memory_order_relaxed);
+ }
+
+ int64 sum(size_t index) const {
+ CHECK(index < N);
+ int64 res = 0;
+ tls_.for_each([&res, &index](auto &value) { res += value[index].load(std::memory_order_relaxed); });
+ return res;
+ }
+ void clear() {
+ tls_.for_each([](auto &value) {
+ for (auto &x : value) {
+ x = 0;
+ }
+ });
+ }
+
+ private:
+ ThreadLocalStorage<std::array<std::atomic<int64>, N>> tls_;
+};
+
+class ThreadSafeCounter {
+ public:
+ void add(int64 diff) {
+ counter_.add(0, diff);
+ }
+
+ int64 sum() const {
+ return counter_.sum(0);
+ }
+
+ void clear() {
+ counter_.clear();
+ }
+
+ private:
+ ThreadSafeMultiCounter<1> counter_;
+};
+
+class NamedThreadSafeCounter {
+ static constexpr int N = 128;
+ using Counter = ThreadSafeMultiCounter<N>;
+
+ public:
+ class CounterRef {
+ public:
+ CounterRef() = default;
+ CounterRef(size_t index, Counter *counter) : index_(index), counter_(counter) {
+ }
+ void add(int64 diff) {
+ counter_->add(index_, diff);
+ }
+ int64 sum() const {
+ return counter_->sum(index_);
+ }
+
+ private:
+ size_t index_{0};
+ Counter *counter_{nullptr};
+ };
+
+ CounterRef get_counter(Slice name) {
+ std::unique_lock<std::mutex> guard(mutex_);
+ for (size_t i = 0; i < names_.size(); i++) {
+ if (names_[i] == name) {
+ return get_counter_ref(i);
+ }
+ }
+ CHECK(names_.size() < N);
+ names_.emplace_back(name.begin(), name.size());
+ return get_counter_ref(names_.size() - 1);
+ }
+
+ CounterRef get_counter_ref(size_t index) {
+ return CounterRef(index, &counter_);
+ }
+
+ static NamedThreadSafeCounter &get_default() {
+ static NamedThreadSafeCounter res;
+ return res;
+ }
+
+ template <class F>
+ void for_each(F &&f) const {
+ std::unique_lock<std::mutex> guard(mutex_);
+ for (size_t i = 0; i < names_.size(); i++) {
+ f(names_[i], counter_.sum(i));
+ }
+ }
+
+ void clear() {
+ std::unique_lock<std::mutex> guard(mutex_);
+ counter_.clear();
+ }
+
+ friend StringBuilder &operator<<(StringBuilder &sb, const NamedThreadSafeCounter &counter) {
+ counter.for_each([&sb](Slice name, int64 cnt) { sb << name << ": " << cnt << "\n"; });
+ return sb;
+ }
+
+ private:
+ mutable std::mutex mutex_;
+ std::vector<std::string> names_;
+
+ Counter counter_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.cpp
index 3e62002c18..93ed7d1acd 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.cpp
@@ -1,19 +1,49 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/Time.h"
+#include "td/utils/port/Clocks.h"
+
+#include <atomic>
#include <cmath>
namespace td {
-std::atomic<double> Time::now_;
-
bool operator==(Timestamp a, Timestamp b) {
return std::abs(a.at() - b.at()) < 1e-6;
}
+static std::atomic<double> time_diff;
+
+double Time::now() {
+ auto result = now_unadjusted() + time_diff.load(std::memory_order_relaxed);
+ while (result < 0) {
+ auto old_time_diff = time_diff.load();
+ time_diff.compare_exchange_strong(old_time_diff, old_time_diff - result);
+ result = now_unadjusted() + time_diff.load(std::memory_order_relaxed);
+ }
+ return result;
+}
+
+double Time::now_unadjusted() {
+ return Clocks::monotonic();
+}
+
+void Time::jump_in_future(double at) {
+ while (true) {
+ auto old_time_diff = time_diff.load();
+ auto diff = at - now();
+ if (diff < 0) {
+ return;
+ }
+ if (time_diff.compare_exchange_strong(old_time_diff, old_time_diff + diff)) {
+ return;
+ }
+ }
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.h
index acdb8b52ef..4149c93d9d 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,25 +7,30 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/port/Clocks.h"
-
-#include <atomic>
namespace td {
class Time {
public:
- static double now() {
- double now = Clocks::monotonic();
- now_.store(now, std::memory_order_relaxed);
- return now;
- }
+ static double now();
static double now_cached() {
- return now_.load(std::memory_order_relaxed);
+ // Temporary(?) use now in now_cached
+ // Problem:
+ // thread A: check that now() > timestamp and notifies thread B
+ // thread B: must see that now() > timestamp()
+ //
+ // now() and now_cached() must be monotonic
+ //
+ // if a=now[_cached]() happens before b=now[_cached] than
+ // a <= b
+ //
+ // As an alternative we may say that now_cached is a thread local copy of now
+ return now();
}
+ static double now_unadjusted();
- private:
- static std::atomic<double> now_;
+ // Used for testing. After jump_in_future(at) is called, now() >= at.
+ static void jump_in_future(double at);
};
inline void relax_timeout_at(double *timeout, double new_timeout) {
@@ -53,15 +58,18 @@ class Timestamp {
return Timestamp{timeout};
}
- static Timestamp in(double timeout) {
- return Timestamp{Time::now_cached() + timeout};
+ static Timestamp in(double timeout, Timestamp now = now_cached()) {
+ return Timestamp{now.at() + timeout};
}
+ bool is_in_past(Timestamp now) const {
+ return at_ <= now.at();
+ }
bool is_in_past() const {
- return at_ <= Time::now_cached();
+ return is_in_past(now_cached());
}
- explicit operator bool() const {
+ explicit operator bool() const noexcept {
return at_ > 0;
}
@@ -91,14 +99,8 @@ class Timestamp {
}
};
-template <class T>
-void parse(Timestamp &timestamp, T &parser) {
- timestamp = Timestamp::in(parser.fetch_double() - Clocks::system());
-}
-
-template <class T>
-void store(const Timestamp &timestamp, T &storer) {
- storer.store_binary(timestamp.at() - Time::now() + Clocks::system());
+inline bool operator<(const Timestamp &a, const Timestamp &b) {
+ return a.at() < b.at();
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TimedStat.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TimedStat.h
index fc4197470d..db0e07495b 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/TimedStat.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TimedStat.h
@@ -1,13 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
+#include "td/utils/optional.h"
+#include <functional>
#include <utility>
namespace td {
@@ -16,7 +18,7 @@ template <class StatT>
class TimedStat {
public:
TimedStat(double duration, double now)
- : duration_(duration), current_(), current_timestamp_(now), next_(), next_timestamp_(now) {
+ : duration_(duration), current_(), current_timestamp_(now - 1), next_(), next_timestamp_(now) {
}
TimedStat() : TimedStat(0, 0) {
}
@@ -48,7 +50,7 @@ class TimedStat {
void update(double &now) {
if (now < next_timestamp_) {
- CHECK(now >= next_timestamp_ * (1 - 1e-14)) << now << " " << next_timestamp_;
+ // LOG_CHECK(now >= next_timestamp_ * (1 - 1e-14)) << now << " " << next_timestamp_;
now = next_timestamp_;
}
if (duration_ == 0) {
@@ -56,7 +58,7 @@ class TimedStat {
}
if (next_timestamp_ + 2 * duration_ < now) {
current_ = StatT();
- current_timestamp_ = now;
+ current_timestamp_ = now - duration_;
next_ = StatT();
next_timestamp_ = now;
} else if (next_timestamp_ + duration_ < now) {
@@ -68,4 +70,28 @@ class TimedStat {
}
};
+namespace detail {
+template <class T, class Cmp>
+struct MinMaxStat {
+ using Event = T;
+ void on_event(Event event) {
+ if (!best_ || Cmp()(event, best_.value())) {
+ best_ = event;
+ }
+ }
+ optional<T> get_stat() const {
+ return best_.copy();
+ }
+
+ private:
+ optional<T> best_;
+};
+} // namespace detail
+
+template <class T>
+using MinStat = detail::MinMaxStat<T, std::less<void>>;
+
+template <class T>
+using MaxStat = detail::MinMaxStat<T, std::greater<void>>;
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.cpp
index dc35721caa..215e1664af 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,34 +8,65 @@
#include "td/utils/format.h"
#include "td/utils/logging.h"
-//#include "td/utils/Slice.h" // TODO move StringBuilder implementation to cpp, remove header
#include "td/utils/Time.h"
namespace td {
-Timer::Timer() : start_time_(Time::now()) {
+Timer::Timer(bool is_paused) {
+ if (!is_paused) {
+ resume();
+ }
+}
+
+void Timer::pause() {
+ if (is_paused_) {
+ return;
+ }
+ elapsed_ += Time::now() - start_time_;
+ is_paused_ = true;
+}
+
+void Timer::resume() {
+ if (!is_paused_) {
+ return;
+ }
+ start_time_ = Time::now();
+ is_paused_ = false;
+}
+
+double Timer::elapsed() const {
+ double res = elapsed_;
+ if (!is_paused_) {
+ res += Time::now() - start_time_;
+ }
+ return res;
}
StringBuilder &operator<<(StringBuilder &string_builder, const Timer &timer) {
- return string_builder << "in " << Time::now() - timer.start_time_;
+ return string_builder << " in " << format::as_time(timer.elapsed());
}
PerfWarningTimer::PerfWarningTimer(string name, double max_duration)
: name_(std::move(name)), start_at_(Time::now()), max_duration_(max_duration) {
}
-PerfWarningTimer::PerfWarningTimer(PerfWarningTimer &&other)
+PerfWarningTimer::PerfWarningTimer(PerfWarningTimer &&other) noexcept
: name_(std::move(other.name_)), start_at_(other.start_at_), max_duration_(other.max_duration_) {
other.start_at_ = 0;
}
PerfWarningTimer::~PerfWarningTimer() {
+ reset();
+}
+
+void PerfWarningTimer::reset() {
if (start_at_ == 0) {
return;
}
double duration = Time::now() - start_at_;
LOG_IF(WARNING, duration > max_duration_)
<< "SLOW: " << tag("name", name_) << tag("duration", format::as_time(duration));
+ start_at_ = 0;
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.h
index 65b879088d..bedab6ad4b 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -12,12 +12,22 @@ namespace td {
class Timer {
public:
- Timer();
+ Timer() : Timer(false) {
+ }
+ explicit Timer(bool is_paused);
+
+ double elapsed() const;
+
+ void pause();
+
+ void resume();
private:
friend StringBuilder &operator<<(StringBuilder &string_builder, const Timer &timer);
- double start_time_;
+ double elapsed_{0};
+ double start_time_{0};
+ bool is_paused_{true};
};
class PerfWarningTimer {
@@ -25,9 +35,10 @@ class PerfWarningTimer {
explicit PerfWarningTimer(string name, double max_duration = 0.1);
PerfWarningTimer(const PerfWarningTimer &) = delete;
PerfWarningTimer &operator=(const PerfWarningTimer &) = delete;
- PerfWarningTimer(PerfWarningTimer &&other);
+ PerfWarningTimer(PerfWarningTimer &&other) noexcept;
PerfWarningTimer &operator=(PerfWarningTimer &&) = delete;
~PerfWarningTimer();
+ void reset();
private:
string name_;
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TlDowncastHelper.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TlDowncastHelper.h
new file mode 100644
index 0000000000..3f0c09a1cf
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TlDowncastHelper.h
@@ -0,0 +1,29 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/TlStorerToString.h"
+
+namespace td {
+
+template <class T>
+class TlDowncastHelper final : public T {
+ public:
+ explicit TlDowncastHelper(int32 constructor) : constructor_(constructor) {
+ }
+ int32 get_id() const final {
+ return constructor_;
+ }
+ void store(TlStorerToString &s, const char *field_name) const final {
+ }
+
+ private:
+ int32 constructor_{0};
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TlStorerToString.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TlStorerToString.h
new file mode 100644
index 0000000000..db246135bf
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TlStorerToString.h
@@ -0,0 +1,180 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/SharedSlice.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/UInt.h"
+
+namespace td {
+
+class TlStorerToString {
+ string result;
+ size_t shift = 0;
+
+ void store_field_begin(const char *name) {
+ result.append(shift, ' ');
+ if (name && name[0]) {
+ result += name;
+ result += " = ";
+ }
+ }
+
+ void store_field_end() {
+ result += '\n';
+ }
+
+ void store_long(int64 value) {
+ result += (PSLICE() << value).c_str();
+ }
+
+ void store_binary(Slice data) {
+ static const char *hex = "0123456789ABCDEF";
+
+ result.append("{ ", 2);
+ for (auto c : data) {
+ unsigned char byte = c;
+ result += hex[byte >> 4];
+ result += hex[byte & 15];
+ result += ' ';
+ }
+ result += '}';
+ }
+
+ public:
+ TlStorerToString() = default;
+ TlStorerToString(const TlStorerToString &other) = delete;
+ TlStorerToString &operator=(const TlStorerToString &other) = delete;
+
+ void store_field(const char *name, bool value) {
+ store_field_begin(name);
+ result += (value ? "true" : "false");
+ store_field_end();
+ }
+
+ void store_field(const char *name, int32 value) {
+ store_field(name, static_cast<int64>(value));
+ }
+
+ void store_field(const char *name, int64 value) {
+ store_field_begin(name);
+ store_long(value);
+ store_field_end();
+ }
+
+ void store_field(const char *name, double value) {
+ store_field_begin(name);
+ result += (PSLICE() << value).c_str();
+ store_field_end();
+ }
+
+ void store_field(const char *name, const char *value) {
+ store_field_begin(name);
+ result += value;
+ store_field_end();
+ }
+
+ void store_field(const char *name, const string &value) {
+ store_field_begin(name);
+ result += '"';
+ result += value;
+ result += '"';
+ store_field_end();
+ }
+
+ void store_field(const char *name, const SecureString &value) {
+ store_field_begin(name);
+ result.append("<secret>");
+ store_field_end();
+ }
+
+ template <class T>
+ void store_field(const char *name, const T &value) {
+ store_field_begin(name);
+ result.append(value.data(), value.size());
+ store_field_end();
+ }
+
+ void store_bytes_field(const char *name, const SecureString &value) {
+ store_field_begin(name);
+ result.append("<secret>");
+ store_field_end();
+ }
+
+ template <class BytesT>
+ void store_bytes_field(const char *name, const BytesT &value) {
+ static const char *hex = "0123456789ABCDEF";
+
+ store_field_begin(name);
+ result.append("bytes [");
+ store_long(static_cast<int64>(value.size()));
+ result.append("] { ");
+ size_t len = min(static_cast<size_t>(64), value.size());
+ for (size_t i = 0; i < len; i++) {
+ int b = value[static_cast<int>(i)] & 0xff;
+ result += hex[b >> 4];
+ result += hex[b & 15];
+ result += ' ';
+ }
+ if (len < value.size()) {
+ result.append("...");
+ }
+ result += '}';
+ store_field_end();
+ }
+
+ template <class ObjectT>
+ void store_object_field(const char *name, const ObjectT *value) {
+ if (value == nullptr) {
+ store_field(name, "null");
+ } else {
+ value->store(*this, name);
+ }
+ }
+
+ void store_field(const char *name, const UInt128 &value) {
+ store_field_begin(name);
+ store_binary(as_slice(value));
+ store_field_end();
+ }
+
+ void store_field(const char *name, const UInt256 &value) {
+ store_field_begin(name);
+ store_binary(as_slice(value));
+ store_field_end();
+ }
+
+ void store_vector_begin(const char *field_name, size_t vector_size) {
+ store_field_begin(field_name);
+ result += "vector[";
+ result += (PSLICE() << vector_size).c_str();
+ result += "] {\n";
+ shift += 2;
+ }
+
+ void store_class_begin(const char *field_name, const char *class_name) {
+ store_field_begin(field_name);
+ result += class_name;
+ result += " {\n";
+ shift += 2;
+ }
+
+ void store_class_end() {
+ CHECK(shift >= 2);
+ shift -= 2;
+ result.append(shift, ' ');
+ result += "}\n";
+ }
+
+ string move_as_string() {
+ return std::move(result);
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.cpp
new file mode 100644
index 0000000000..df7080b68d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.cpp
@@ -0,0 +1,64 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/TsCerr.h"
+
+#include "td/utils/ExitGuard.h"
+#include "td/utils/port/StdStreams.h"
+#include "td/utils/Time.h"
+
+#include <cerrno>
+
+namespace td {
+
+std::atomic_flag TsCerr::lock_ = ATOMIC_FLAG_INIT;
+
+TsCerr::TsCerr() {
+ enterCritical();
+}
+
+TsCerr::~TsCerr() {
+ exitCritical();
+}
+
+TsCerr &TsCerr::operator<<(Slice slice) {
+ auto &fd = Stderr();
+ if (fd.empty()) {
+ return *this;
+ }
+ double end_time = 0;
+ while (!slice.empty()) {
+ auto res = fd.write(slice);
+ if (res.is_error()) {
+ if (res.error().code() == EPIPE) {
+ break;
+ }
+ // Resource temporary unavailable
+ if (end_time == 0) {
+ end_time = Time::now() + 0.01;
+ } else if (Time::now() > end_time) {
+ break;
+ }
+ continue;
+ }
+ slice.remove_prefix(res.ok());
+ }
+ return *this;
+}
+
+void TsCerr::enterCritical() {
+ while (lock_.test_and_set(std::memory_order_acquire) && !ExitGuard::is_exited()) {
+ // spin
+ }
+}
+
+void TsCerr::exitCritical() {
+ lock_.clear(std::memory_order_release);
+}
+
+static ExitGuard exit_guard;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.h
new file mode 100644
index 0000000000..686003df3b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.h
@@ -0,0 +1,33 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/Slice.h"
+
+#include <atomic>
+
+namespace td {
+
+class TsCerr {
+ public:
+ TsCerr();
+ TsCerr(const TsCerr &) = delete;
+ TsCerr &operator=(const TsCerr &) = delete;
+ TsCerr(TsCerr &&) = delete;
+ TsCerr &operator=(TsCerr &&) = delete;
+ ~TsCerr();
+
+ TsCerr &operator<<(Slice slice);
+
+ private:
+ static std::atomic_flag lock_;
+
+ static void enterCritical();
+ static void exitCritical();
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.cpp
new file mode 100644
index 0000000000..44d9a420f7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.cpp
@@ -0,0 +1,106 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/TsFileLog.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FileLog.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/thread_local.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+
+#include <array>
+#include <atomic>
+#include <limits>
+#include <mutex>
+
+namespace td {
+
+namespace detail {
+class TsFileLog final : public LogInterface {
+ public:
+ Status init(string path, int64 rotate_threshold, bool redirect_stderr) {
+ path_ = std::move(path);
+ rotate_threshold_ = rotate_threshold;
+ redirect_stderr_ = redirect_stderr;
+ for (size_t i = 0; i < logs_.size(); i++) {
+ logs_[i].id = i;
+ }
+ return init_info(&logs_[0]);
+ }
+
+ void rotate() {
+ for (auto &info : logs_) {
+ if (info.is_inited.load(std::memory_order_acquire)) {
+ info.log.lazy_rotate();
+ }
+ }
+ }
+
+ private:
+ struct Info {
+ FileLog log;
+ std::atomic<bool> is_inited{false};
+ size_t id;
+ };
+
+ static constexpr size_t MAX_THREAD_ID = 128;
+ int64 rotate_threshold_ = 0;
+ bool redirect_stderr_ = false;
+ std::string path_;
+ std::array<Info, MAX_THREAD_ID> logs_;
+ std::mutex init_mutex_;
+
+ LogInterface *get_current_logger() {
+ auto *info = get_current_info();
+ if (!info->is_inited.load(std::memory_order_relaxed)) {
+ std::unique_lock<std::mutex> lock(init_mutex_);
+ if (!info->is_inited.load(std::memory_order_relaxed)) {
+ init_info(info).ensure();
+ }
+ }
+ return &info->log;
+ }
+
+ Info *get_current_info() {
+ return &logs_[get_thread_id()];
+ }
+
+ Status init_info(Info *info) {
+ TRY_STATUS(info->log.init(get_path(info), std::numeric_limits<int64>::max(), info->id == 0 && redirect_stderr_));
+ info->is_inited = true;
+ return Status::OK();
+ }
+
+ string get_path(const Info *info) const {
+ if (info->id == 0) {
+ return path_;
+ }
+ return PSTRING() << path_ << ".thread" << info->id << ".log";
+ }
+
+ void do_append(int log_level, CSlice slice) final {
+ get_current_logger()->do_append(log_level, slice);
+ }
+
+ vector<string> get_file_paths() final {
+ vector<string> res;
+ for (auto &log : logs_) {
+ res.push_back(get_path(&log));
+ }
+ return res;
+ }
+};
+} // namespace detail
+
+Result<unique_ptr<LogInterface>> TsFileLog::create(string path, int64 rotate_threshold, bool redirect_stderr) {
+ auto res = make_unique<detail::TsFileLog>();
+ TRY_STATUS(res->init(std::move(path), rotate_threshold, redirect_stderr));
+ return std::move(res);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/SchedulerId.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.h
index 5850f1a94c..2a84fec2a1 100644
--- a/protocols/Telegram/tdlib/td/tdactor/td/actor/impl2/SchedulerId.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,25 +8,16 @@
#include "td/utils/common.h"
#include "td/utils/logging.h"
+#include "td/utils/Status.h"
namespace td {
-namespace actor2 {
-class SchedulerId {
- public:
- SchedulerId() : id_(-1) {
- }
- explicit SchedulerId(uint8 id) : id_(id) {
- }
- bool is_valid() const {
- return id_ >= 0;
- }
- uint8 value() const {
- CHECK(is_valid());
- return static_cast<uint8>(id_);
- }
- private:
- int32 id_{0};
+class TsFileLog {
+ static constexpr int64 DEFAULT_ROTATE_THRESHOLD = 10 * (1 << 20);
+
+ public:
+ static Result<unique_ptr<LogInterface>> create(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD,
+ bool redirect_stderr = true);
};
-} // namespace actor2
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsList.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsList.h
new file mode 100644
index 0000000000..8302a6d500
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsList.h
@@ -0,0 +1,214 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/List.h"
+
+#include <mutex>
+
+namespace td {
+
+template <class DataT>
+class TsList;
+
+template <class DataT>
+class TsListNode : protected ListNode {
+ public:
+ TsListNode() {
+ clear();
+ }
+ explicit TsListNode(DataT &&data) : data_(std::move(data)) {
+ clear();
+ }
+
+ ~TsListNode() {
+ remove();
+ }
+
+ std::unique_lock<std::mutex> lock() TD_WARN_UNUSED_RESULT;
+
+ TsListNode(const TsListNode &) = delete;
+ TsListNode &operator=(const TsListNode &) = delete;
+
+ TsListNode(TsListNode &&other) noexcept {
+ other.validate();
+ if (other.empty()) {
+ data_ = std::move(other.data_);
+ clear();
+ } else {
+ auto guard = other.lock();
+ init_from(std::move(other));
+ }
+ validate();
+ other.validate();
+ }
+
+ TsListNode &operator=(TsListNode &&other) noexcept {
+ validate();
+ if (this == &other) {
+ return *this;
+ }
+ other.validate();
+ remove();
+
+ if (other.empty()) {
+ data_ = std::move(other.data_);
+ } else {
+ auto guard = other.lock();
+ init_from(std::move(other));
+ }
+
+ validate();
+ other.validate();
+ return *this;
+ }
+
+ void validate() {
+ if (empty()) {
+ CHECK(ListNode::empty());
+ } else {
+ auto guard = lock();
+ CHECK(!ListNode::empty() || is_root);
+ }
+ }
+
+ void remove() {
+ validate();
+ if (is_root) {
+ CHECK(ListNode::empty());
+ return;
+ }
+ if (empty()) {
+ CHECK(ListNode::empty());
+ return;
+ }
+ {
+ auto guard = lock();
+ ListNode::remove();
+ if (!is_root) {
+ parent = nullptr;
+ }
+ }
+ validate();
+ }
+
+ void put(TsListNode *other) {
+ validate();
+ other->validate();
+ DCHECK(other->empty());
+ DCHECK(!empty());
+ DCHECK(!other->is_root);
+ {
+ auto guard = lock();
+ ListNode::put(other);
+ other->parent = parent;
+ }
+ validate();
+ other->validate();
+ }
+
+ void put_back(TsListNode *other) {
+ DCHECK(other->empty());
+ DCHECK(!empty());
+ DCHECK(!other->is_root);
+ auto guard = lock();
+ ListNode::put_back(other);
+ other->parent = parent;
+ }
+
+ bool empty() const {
+ return parent == nullptr;
+ }
+
+ TsListNode *get_next() {
+ return static_cast<TsListNode *>(next);
+ }
+ TsListNode *get_prev() {
+ return static_cast<TsListNode *>(prev);
+ }
+
+ DataT &get_data_unsafe() {
+ return data_;
+ }
+
+ private:
+ TsList<DataT> *parent;
+ bool is_root{false};
+ DataT data_;
+
+ friend class TsList<DataT>;
+
+ void clear() {
+ ListNode::clear();
+ if (!is_root) {
+ parent = nullptr;
+ }
+ }
+
+ void init_from(TsListNode &&other) {
+ ListNode::init_from(std::move(other));
+ parent = other.parent;
+ other.parent = nullptr;
+ data_ = std::move(other.data_);
+ }
+};
+
+template <class DataT>
+class TsList final : public TsListNode<DataT> {
+ public:
+ TsList() {
+ this->parent = this;
+ this->is_root = true;
+ }
+ TsList(const TsList &) = delete;
+ TsList &operator=(const TsList &) = delete;
+ TsList(TsList &&) = delete;
+ TsList &operator=(TsList &&) = delete;
+ ~TsList() {
+ auto guard = lock();
+ while (true) {
+ auto res = static_cast<TsListNode<DataT> *>(ListNode::get());
+ if (!res) {
+ break;
+ }
+ res->parent = nullptr;
+ }
+ this->parent = nullptr;
+ }
+ std::unique_lock<std::mutex> lock() TD_WARN_UNUSED_RESULT {
+ return std::unique_lock<std::mutex>(mutex_);
+ }
+ TsListNode<DataT> *begin() {
+ return this->get_next();
+ }
+ TsListNode<DataT> *end() {
+ return this;
+ }
+ TsListNode<DataT> *get() {
+ auto guard = lock();
+ auto res = static_cast<TsListNode<DataT> *>(ListNode::get());
+ if (res) {
+ res->parent = nullptr;
+ }
+ return res;
+ }
+
+ private:
+ std::mutex mutex_;
+};
+
+template <class DataT>
+std::unique_lock<std::mutex> TsListNode<DataT>::lock() {
+ if (parent == nullptr) {
+ return {};
+ }
+ CHECK(parent != nullptr);
+ return parent->lock();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.cpp
new file mode 100644
index 0000000000..0dba99de8b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.cpp
@@ -0,0 +1,21 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/TsLog.h"
+
+#include "td/utils/ExitGuard.h"
+
+namespace td {
+
+void TsLog::enter_critical() {
+ while (lock_.test_and_set(std::memory_order_acquire) && !ExitGuard::is_exited()) {
+ // spin
+ }
+}
+
+static ExitGuard exit_guard;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.h
new file mode 100644
index 0000000000..7ef9f2c4b9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.h
@@ -0,0 +1,55 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+
+#include <atomic>
+
+namespace td {
+
+class TsLog final : public LogInterface {
+ public:
+ explicit TsLog(LogInterface *log) : log_(log) {
+ }
+ void init(LogInterface *log) {
+ enter_critical();
+ log_ = log;
+ exit_critical();
+ }
+ void after_rotation() final {
+ enter_critical();
+ log_->after_rotation();
+ exit_critical();
+ }
+ vector<string> get_file_paths() final {
+ enter_critical();
+ auto result = log_->get_file_paths();
+ exit_critical();
+ return result;
+ }
+
+ private:
+ void do_append(int log_level, CSlice slice) final {
+ enter_critical();
+ log_->do_append(log_level, slice);
+ exit_critical();
+ }
+
+ void enter_critical();
+
+ void exit_critical() {
+ lock_.clear(std::memory_order_release);
+ }
+
+ LogInterface *log_ = nullptr;
+ std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/UInt.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/UInt.h
new file mode 100644
index 0000000000..03714d7a4f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/UInt.h
@@ -0,0 +1,91 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+template <size_t size>
+struct UInt {
+ static_assert(size % 8 == 0, "size should be divisible by 8");
+ uint8 raw[size / 8];
+
+ Slice as_slice() const {
+ return Slice(raw, size / 8);
+ }
+
+ MutableSlice as_slice() {
+ return MutableSlice(raw, size / 8);
+ }
+
+ bool is_zero() const {
+ for (size_t i = 0; i < size / 8; i++) {
+ if (raw[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ void set_zero() {
+ for (size_t i = 0; i < size / 8; i++) {
+ raw[i] = 0;
+ }
+ }
+ static UInt zero() {
+ UInt v;
+ v.set_zero();
+ return v;
+ }
+};
+
+template <size_t size>
+bool operator==(const UInt<size> &a, const UInt<size> &b) {
+ return a.as_slice() == b.as_slice();
+}
+
+template <size_t size>
+bool operator!=(const UInt<size> &a, const UInt<size> &b) {
+ return !(a == b);
+}
+
+template <size_t size>
+UInt<size> operator^(const UInt<size> &a, const UInt<size> &b) {
+ UInt<size> res;
+ for (size_t i = 0; i < size / 8; i++) {
+ res.raw[i] = static_cast<uint8>(a.raw[i] ^ b.raw[i]);
+ }
+ return res;
+}
+
+template <size_t size>
+int get_kth_bit(const UInt<size> &a, uint32 bit) {
+ uint8 b = a.raw[bit / 8];
+ bit &= 7;
+ return (b >> (7 - bit)) & 1;
+}
+
+template <size_t size>
+Slice as_slice(const UInt<size> &value) {
+ return value.as_slice();
+}
+
+template <size_t size>
+MutableSlice as_slice(UInt<size> &value) {
+ return value.as_slice();
+}
+
+template <size_t size>
+bool operator<(const UInt<size> &a, const UInt<size> &b) {
+ return a.as_slice() < b.as_slice();
+}
+
+using UInt128 = UInt<128>;
+using UInt256 = UInt<256>;
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Variant.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Variant.h
index 9b6e0561cc..9d064265fb 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Variant.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Variant.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -55,7 +55,7 @@ class IthTypeImpl<pos, Skip, Args...> : public IthTypeImpl<pos - 1, Args...> {};
class Dummy {};
template <size_t pos, class... Args>
-class IthType : public IthTypeImpl<pos, Args..., Dummy> {};
+class IthType final : public IthTypeImpl<pos, Args..., Dummy> {};
template <bool ok, int offset, class... Types>
class FindTypeOffsetImpl {};
@@ -69,7 +69,7 @@ template <int offset, class T, class S, class... Types>
class FindTypeOffsetImpl<false, offset, T, S, Types...>
: public FindTypeOffsetImpl<std::is_same<T, S>::value, offset + 1, T, Types...> {};
template <class T, class... Types>
-class FindTypeOffset : public FindTypeOffsetImpl<false, -1, T, Types...> {};
+class FindTypeOffset final : public FindTypeOffsetImpl<false, -1, T, Types...> {};
template <int offset, class... Types>
class ForEachTypeImpl {};
@@ -109,18 +109,21 @@ class Variant {
static constexpr int npos = -1;
Variant() {
}
- Variant(Variant &&other) {
+ Variant(Variant &&other) noexcept {
other.visit([&](auto &&value) { this->init_empty(std::forward<decltype(value)>(value)); });
}
Variant(const Variant &other) {
other.visit([&](auto &&value) { this->init_empty(std::forward<decltype(value)>(value)); });
}
- Variant &operator=(Variant &&other) {
+ Variant &operator=(Variant &&other) noexcept {
clear();
other.visit([&](auto &&value) { this->init_empty(std::forward<decltype(value)>(value)); });
return *this;
}
Variant &operator=(const Variant &other) {
+ if (this == &other) {
+ return *this;
+ }
clear();
other.visit([&](auto &&value) { this->init_empty(std::forward<decltype(value)>(value)); });
return *this;
@@ -153,11 +156,11 @@ class Variant {
return res;
}
- template <class T>
+ template <class T, std::enable_if_t<!std::is_same<std::decay_t<T>, Variant>::value, int> = 0>
Variant(T &&t) {
init_empty(std::forward<T>(t));
}
- template <class T>
+ template <class T, std::enable_if_t<!std::is_same<std::decay_t<T>, Variant>::value, int> = 0>
Variant &operator=(T &&t) {
clear();
init_empty(std::forward<T>(t));
@@ -170,7 +173,11 @@ class Variant {
template <class T>
void init_empty(T &&t) {
- CHECK(offset_ == npos);
+ LOG_CHECK(offset_ == npos) << offset_
+#if TD_CLANG || TD_GCC
+ << ' ' << __PRETTY_FUNCTION__
+#endif
+ ;
offset_ = offset<T>();
new (&get<T>()) std::decay_t<T>(std::forward<T>(t));
}
@@ -237,6 +244,10 @@ class Variant {
return offset_;
}
+ bool empty() const {
+ return offset_ == npos;
+ }
+
private:
union {
int64 align_;
@@ -283,4 +294,5 @@ template <int T, class... Types>
auto &get(const Variant<Types...> &v) {
return v.template get<T>();
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/VectorQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/VectorQueue.h
new file mode 100644
index 0000000000..c67a440caf
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/VectorQueue.h
@@ -0,0 +1,94 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Span.h"
+
+#include <utility>
+
+namespace td {
+
+template <class T>
+class VectorQueue {
+ public:
+ template <class S>
+ void push(S &&s) {
+ vector_.emplace_back(std::forward<S>(s));
+ }
+
+ template <class... Args>
+ void emplace(Args &&...args) {
+ vector_.emplace_back(std::forward<Args>(args)...);
+ }
+
+ T pop() {
+ try_shrink();
+ return std::move(vector_[read_pos_++]);
+ }
+
+ template <class RndT>
+ T pop_rand(RndT &rnd) {
+ auto i = rnd() % size();
+ std::swap(vector_[i], vector_[read_pos_]);
+ return pop();
+ }
+
+ void pop_n(size_t n) {
+ read_pos_ += n;
+ try_shrink();
+ }
+
+ const T &front() const {
+ return vector_[read_pos_];
+ }
+ T &front() {
+ return vector_[read_pos_];
+ }
+
+ const T &back() const {
+ return vector_.back();
+ }
+ T &back() {
+ return vector_.back();
+ }
+
+ bool empty() const {
+ return size() == 0;
+ }
+
+ size_t size() const {
+ return vector_.size() - read_pos_;
+ }
+
+ const T *data() const {
+ return vector_.data() + read_pos_;
+ }
+ T *data() {
+ return vector_.data() + read_pos_;
+ }
+
+ Span<T> as_span() const {
+ return {data(), size()};
+ }
+ MutableSpan<T> as_mutable_span() {
+ return {vector_.data() + read_pos_, size()};
+ }
+
+ private:
+ vector<T> vector_;
+ size_t read_pos_{0};
+
+ void try_shrink() {
+ if (read_pos_ * 2 > vector_.size() && read_pos_ > 4) {
+ vector_.erase(vector_.begin(), vector_.begin() + read_pos_);
+ read_pos_ = 0;
+ }
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashMap.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashMap.h
new file mode 100644
index 0000000000..2b34bebaa1
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashMap.h
@@ -0,0 +1,190 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/HashTableUtils.h"
+
+#include <functional>
+
+namespace td {
+
+template <class KeyT, class ValueT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>>
+class WaitFreeHashMap {
+ static constexpr size_t MAX_STORAGE_COUNT = 1 << 8;
+ static_assert((MAX_STORAGE_COUNT & (MAX_STORAGE_COUNT - 1)) == 0, "");
+ static constexpr uint32 DEFAULT_STORAGE_SIZE = 1 << 12;
+
+ FlatHashMap<KeyT, ValueT, HashT, EqT> default_map_;
+ struct WaitFreeStorage {
+ WaitFreeHashMap maps_[MAX_STORAGE_COUNT];
+ };
+ unique_ptr<WaitFreeStorage> wait_free_storage_;
+ uint32 hash_mult_ = 1;
+ uint32 max_storage_size_ = DEFAULT_STORAGE_SIZE;
+
+ uint32 get_wait_free_index(const KeyT &key) const {
+ return randomize_hash(HashT()(key) * hash_mult_) & (MAX_STORAGE_COUNT - 1);
+ }
+
+ WaitFreeHashMap &get_wait_free_storage(const KeyT &key) {
+ return wait_free_storage_->maps_[get_wait_free_index(key)];
+ }
+
+ const WaitFreeHashMap &get_wait_free_storage(const KeyT &key) const {
+ return wait_free_storage_->maps_[get_wait_free_index(key)];
+ }
+
+ void split_storage() {
+ CHECK(wait_free_storage_ == nullptr);
+ wait_free_storage_ = make_unique<WaitFreeStorage>();
+ uint32 next_hash_mult = hash_mult_ * 1000000007;
+ for (uint32 i = 0; i < MAX_STORAGE_COUNT; i++) {
+ auto &map = wait_free_storage_->maps_[i];
+ map.hash_mult_ = next_hash_mult;
+ map.max_storage_size_ = DEFAULT_STORAGE_SIZE + i * next_hash_mult % DEFAULT_STORAGE_SIZE;
+ }
+ for (auto &it : default_map_) {
+ get_wait_free_storage(it.first).set(it.first, std::move(it.second));
+ }
+ default_map_.clear();
+ }
+
+ public:
+ void set(const KeyT &key, ValueT value) {
+ if (wait_free_storage_ != nullptr) {
+ return get_wait_free_storage(key).set(key, std::move(value));
+ }
+
+ default_map_[key] = std::move(value);
+ if (default_map_.size() == max_storage_size_) {
+ split_storage();
+ }
+ }
+
+ ValueT get(const KeyT &key) const {
+ if (wait_free_storage_ != nullptr) {
+ return get_wait_free_storage(key).get(key);
+ }
+
+ auto it = default_map_.find(key);
+ if (it == default_map_.end()) {
+ return {};
+ }
+ return it->second;
+ }
+
+ size_t count(const KeyT &key) const {
+ if (wait_free_storage_ != nullptr) {
+ return get_wait_free_storage(key).count(key);
+ }
+
+ return default_map_.count(key);
+ }
+
+ // specialization for WaitFreeHashMap<..., unique_ptr<T>>
+ template <class T = ValueT>
+ typename T::element_type *get_pointer(const KeyT &key) {
+ if (wait_free_storage_ != nullptr) {
+ return get_wait_free_storage(key).get_pointer(key);
+ }
+
+ auto it = default_map_.find(key);
+ if (it == default_map_.end()) {
+ return nullptr;
+ }
+ return it->second.get();
+ }
+
+ template <class T = ValueT>
+ const typename T::element_type *get_pointer(const KeyT &key) const {
+ if (wait_free_storage_ != nullptr) {
+ return get_wait_free_storage(key).get_pointer(key);
+ }
+
+ auto it = default_map_.find(key);
+ if (it == default_map_.end()) {
+ return nullptr;
+ }
+ return it->second.get();
+ }
+
+ ValueT &operator[](const KeyT &key) {
+ if (wait_free_storage_ == nullptr) {
+ ValueT &result = default_map_[key];
+ if (default_map_.size() != max_storage_size_) {
+ return result;
+ }
+
+ split_storage();
+ }
+
+ return get_wait_free_storage(key)[key];
+ }
+
+ size_t erase(const KeyT &key) {
+ if (wait_free_storage_ != nullptr) {
+ return get_wait_free_storage(key).erase(key);
+ }
+
+ return default_map_.erase(key);
+ }
+
+ void foreach(const std::function<void(const KeyT &key, ValueT &value)> &callback) {
+ if (wait_free_storage_ == nullptr) {
+ for (auto &it : default_map_) {
+ callback(it.first, it.second);
+ }
+ return;
+ }
+
+ for (auto &it : wait_free_storage_->maps_) {
+ it.foreach(callback);
+ }
+ }
+
+ void foreach(const std::function<void(const KeyT &key, const ValueT &value)> &callback) const {
+ if (wait_free_storage_ == nullptr) {
+ for (auto &it : default_map_) {
+ callback(it.first, it.second);
+ }
+ return;
+ }
+
+ for (auto &it : wait_free_storage_->maps_) {
+ it.foreach(callback);
+ }
+ }
+
+ size_t calc_size() const {
+ if (wait_free_storage_ == nullptr) {
+ return default_map_.size();
+ }
+
+ size_t result = 0;
+ for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) {
+ result += wait_free_storage_->maps_[i].calc_size();
+ }
+ return result;
+ }
+
+ bool empty() const {
+ if (wait_free_storage_ == nullptr) {
+ return default_map_.empty();
+ }
+
+ for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) {
+ if (!wait_free_storage_->maps_[i].empty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashSet.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashSet.h
new file mode 100644
index 0000000000..5fddc0903d
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashSet.h
@@ -0,0 +1,141 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/HashTableUtils.h"
+
+#include <functional>
+
+namespace td {
+
+template <class KeyT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>>
+class WaitFreeHashSet {
+ static constexpr size_t MAX_STORAGE_COUNT = 1 << 8;
+ static_assert((MAX_STORAGE_COUNT & (MAX_STORAGE_COUNT - 1)) == 0, "");
+ static constexpr uint32 DEFAULT_STORAGE_SIZE = 1 << 12;
+
+ FlatHashSet<KeyT, HashT, EqT> default_set_;
+ struct WaitFreeStorage {
+ WaitFreeHashSet sets_[MAX_STORAGE_COUNT];
+ };
+ unique_ptr<WaitFreeStorage> wait_free_storage_;
+ uint32 hash_mult_ = 1;
+ uint32 max_storage_size_ = DEFAULT_STORAGE_SIZE;
+
+ uint32 get_wait_free_index(const KeyT &key) const {
+ return randomize_hash(HashT()(key) * hash_mult_) & (MAX_STORAGE_COUNT - 1);
+ }
+
+ WaitFreeHashSet &get_wait_free_storage(const KeyT &key) {
+ return wait_free_storage_->sets_[get_wait_free_index(key)];
+ }
+
+ const WaitFreeHashSet &get_wait_free_storage(const KeyT &key) const {
+ return wait_free_storage_->sets_[get_wait_free_index(key)];
+ }
+
+ void split_storage() {
+ CHECK(wait_free_storage_ == nullptr);
+ wait_free_storage_ = make_unique<WaitFreeStorage>();
+ uint32 next_hash_mult = hash_mult_ * 1000000007;
+ for (uint32 i = 0; i < MAX_STORAGE_COUNT; i++) {
+ auto &set = wait_free_storage_->sets_[i];
+ set.hash_mult_ = next_hash_mult;
+ set.max_storage_size_ = DEFAULT_STORAGE_SIZE + i * next_hash_mult % DEFAULT_STORAGE_SIZE;
+ }
+ for (auto &it : default_set_) {
+ get_wait_free_storage(it).insert(it);
+ }
+ default_set_.clear();
+ }
+
+ public:
+ void insert(const KeyT &key) {
+ if (wait_free_storage_ != nullptr) {
+ return get_wait_free_storage(key).insert(key);
+ }
+
+ default_set_.insert(key);
+ if (default_set_.size() == max_storage_size_) {
+ split_storage();
+ }
+ }
+
+ size_t count(const KeyT &key) const {
+ if (wait_free_storage_ != nullptr) {
+ return get_wait_free_storage(key).count(key);
+ }
+
+ return default_set_.count(key);
+ }
+
+ size_t erase(const KeyT &key) {
+ if (wait_free_storage_ != nullptr) {
+ return get_wait_free_storage(key).erase(key);
+ }
+
+ return default_set_.erase(key);
+ }
+
+ void foreach(const std::function<void(const KeyT &key)> &callback) const {
+ if (wait_free_storage_ == nullptr) {
+ for (auto &it : default_set_) {
+ callback(it);
+ }
+ return;
+ }
+
+ for (auto &it : wait_free_storage_->sets_) {
+ it.foreach(callback);
+ }
+ }
+
+ KeyT get_random() const {
+ if (wait_free_storage_ != nullptr) {
+ for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) {
+ if (!wait_free_storage_->sets_[i].empty()) {
+ return wait_free_storage_->sets_[i].get_random();
+ }
+ }
+ // no need to explicitly return KeyT()
+ }
+
+ if (default_set_.empty()) {
+ return KeyT();
+ }
+ return *default_set_.begin();
+ }
+
+ size_t calc_size() const {
+ if (wait_free_storage_ == nullptr) {
+ return default_set_.size();
+ }
+
+ size_t result = 0;
+ for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) {
+ result += wait_free_storage_->sets_[i].calc_size();
+ }
+ return result;
+ }
+
+ bool empty() const {
+ if (wait_free_storage_ == nullptr) {
+ return default_set_.empty();
+ }
+
+ for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) {
+ if (!wait_free_storage_->sets_[i].empty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeVector.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeVector.h
new file mode 100644
index 0000000000..40ffd41c6b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeVector.h
@@ -0,0 +1,69 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#include <utility>
+
+namespace td {
+
+template <class T>
+class WaitFreeVector {
+ static constexpr size_t MAX_VECTOR_SIZE = (1 << 15) - 10;
+
+ vector<vector<T>> storage_;
+
+ public:
+ template <class... ArgsT>
+ void emplace_back(ArgsT &&...args) {
+ if (storage_.empty() || storage_.back().size() == MAX_VECTOR_SIZE) {
+ storage_.emplace_back();
+ }
+ storage_.back().emplace_back(std::forward<ArgsT>(args)...);
+ }
+
+ void pop_back() {
+ storage_.back().pop_back();
+ if (storage_.back().empty()) {
+ storage_.pop_back();
+ }
+ }
+
+ void push_back(T &&value) {
+ emplace_back(std::move(value));
+ }
+
+ void push_back(const T &value) {
+ emplace_back(value);
+ }
+
+ const T &back() const {
+ return storage_.back().back();
+ }
+
+ T &operator[](size_t index) {
+ return storage_[index / MAX_VECTOR_SIZE][index % MAX_VECTOR_SIZE];
+ }
+
+ const T &operator[](size_t index) const {
+ return storage_[index / MAX_VECTOR_SIZE][index % MAX_VECTOR_SIZE];
+ }
+
+ size_t size() const {
+ if (storage_.empty()) {
+ return 0;
+ }
+ return (storage_.size() - 1) * MAX_VECTOR_SIZE + storage_.back().size();
+ }
+
+ bool empty() const {
+ return storage_.empty();
+ }
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/algorithm.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/algorithm.h
new file mode 100644
index 0000000000..0c3702d8c7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/algorithm.h
@@ -0,0 +1,217 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+namespace td {
+
+namespace detail {
+
+template <typename V>
+struct transform_helper {
+ template <class Func>
+ auto transform(const V &v, const Func &f) {
+ vector<decltype(f(*v.begin()))> result;
+ result.reserve(v.size());
+ for (auto &x : v) {
+ result.push_back(f(x));
+ }
+ return result;
+ }
+
+ template <class Func>
+ auto transform(V &&v, const Func &f) {
+ vector<decltype(f(std::move(*v.begin())))> result;
+ result.reserve(v.size());
+ for (auto &x : v) {
+ result.push_back(f(std::move(x)));
+ }
+ return result;
+ }
+};
+
+} // namespace detail
+
+template <class V, class Func>
+auto transform(V &&v, const Func &f) {
+ return detail::transform_helper<std::decay_t<V>>().transform(std::forward<V>(v), f);
+}
+
+template <class V, class Func>
+bool remove_if(V &v, const Func &f) {
+ size_t i = 0;
+ while (i != v.size() && !f(v[i])) {
+ i++;
+ }
+ if (i == v.size()) {
+ return false;
+ }
+
+ size_t j = i;
+ while (++i != v.size()) {
+ if (!f(v[i])) {
+ v[j++] = std::move(v[i]);
+ }
+ }
+ v.erase(v.begin() + j, v.end());
+ return true;
+}
+
+template <class V, class T>
+bool remove(V &v, const T &value) {
+ size_t i = 0;
+ while (i != v.size() && v[i] != value) {
+ i++;
+ }
+ if (i == v.size()) {
+ return false;
+ }
+
+ size_t j = i;
+ while (++i != v.size()) {
+ if (v[i] != value) {
+ v[j++] = std::move(v[i]);
+ }
+ }
+ v.erase(v.begin() + j, v.end());
+ return true;
+}
+
+template <class V>
+void unique(V &v) {
+ if (v.empty()) {
+ return;
+ }
+
+ // use ADL to find std::sort
+ // caller will need to #include <algorithm>
+ sort(v.begin(), v.end(), std::less<void>());
+
+ size_t j = 1;
+ for (size_t i = 1; i < v.size(); i++) {
+ if (v[i] != v[j - 1]) {
+ if (i != j) {
+ v[j] = std::move(v[i]);
+ }
+ j++;
+ }
+ }
+ v.resize(j);
+}
+
+template <class V, class T>
+bool contains(const V &v, const T &value) {
+ for (auto &x : v) {
+ if (x == value) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <class V, class F>
+bool all_of(const V &v, F &&f) {
+ for (const auto &x : v) {
+ if (!f(x)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <class T>
+void reset_to_empty(T &value) {
+ using std::swap;
+ std::decay_t<T> tmp;
+ swap(tmp, value);
+}
+
+template <class T>
+void append(vector<T> &destination, const vector<T> &source) {
+ destination.insert(destination.end(), source.begin(), source.end());
+}
+
+template <class T>
+void append(vector<T> &destination, vector<T> &&source) {
+ if (destination.empty()) {
+ destination.swap(source);
+ return;
+ }
+ destination.reserve(destination.size() + source.size());
+ for (auto &elem : source) {
+ destination.push_back(std::move(elem));
+ }
+ reset_to_empty(source);
+}
+
+template <class T>
+void combine(vector<T> &destination, const vector<T> &source) {
+ append(destination, source);
+}
+
+template <class T>
+void combine(vector<T> &destination, vector<T> &&source) {
+ if (destination.size() < source.size()) {
+ destination.swap(source);
+ }
+ if (source.empty()) {
+ return;
+ }
+ destination.reserve(destination.size() + source.size());
+ for (auto &elem : source) {
+ destination.push_back(std::move(elem));
+ }
+ reset_to_empty(source);
+}
+
+namespace detail {
+template <typename T>
+struct reversion_wrapper {
+ T &iterable;
+};
+
+template <typename T>
+auto begin(reversion_wrapper<T> w) {
+ return w.iterable.rbegin();
+}
+
+template <typename T>
+auto end(reversion_wrapper<T> w) {
+ return w.iterable.rend();
+}
+} // namespace detail
+
+template <typename T>
+detail::reversion_wrapper<T> reversed(T &iterable) {
+ return {iterable};
+}
+
+template <class TableT, class FuncT>
+void table_remove_if(TableT &table, FuncT &&func) {
+ for (auto it = table.begin(); it != table.end();) {
+ if (func(*it)) {
+ it = table.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+template <class NodeT, class HashT, class EqT>
+class FlatHashTable;
+
+template <class NodeT, class HashT, class EqT, class FuncT>
+void table_remove_if(FlatHashTable<NodeT, HashT, EqT> &table, FuncT &&func) {
+ table.remove_if(func);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/as.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/as.h
new file mode 100644
index 0000000000..e73c4a1b3b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/as.h
@@ -0,0 +1,81 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/type_traits.h"
+
+#include <cstring>
+#include <type_traits>
+
+namespace td {
+
+namespace detail {
+
+template <class T>
+class As {
+ public:
+ explicit As(void *ptr) : ptr_(ptr) {
+ }
+
+ As(const As &new_value) = delete;
+ As &operator=(const As &) = delete;
+ As(As &&) = default;
+ As &operator=(As &&new_value) &&noexcept {
+ std::memcpy(ptr_, new_value.ptr_, sizeof(T));
+ return *this;
+ }
+ ~As() = default;
+
+ As &operator=(const T &new_value) && {
+ std::memcpy(ptr_, &new_value, sizeof(T));
+ return *this;
+ }
+
+ operator T() const {
+ T res;
+ std::memcpy(&res, ptr_, sizeof(T));
+ return res;
+ }
+ bool operator==(const As &other) const {
+ return this->operator T() == other.operator T();
+ }
+
+ private:
+ void *ptr_;
+};
+
+template <class T>
+class ConstAs {
+ public:
+ explicit ConstAs(const void *ptr) : ptr_(ptr) {
+ }
+
+ operator T() const {
+ T res;
+ std::memcpy(&res, ptr_, sizeof(T));
+ return res;
+ }
+
+ private:
+ const void *ptr_;
+};
+
+} // namespace detail
+
+template <class ToT, class FromT,
+ std::enable_if_t<TD_IS_TRIVIALLY_COPYABLE(ToT) && TD_IS_TRIVIALLY_COPYABLE(FromT), int> = 0>
+detail::As<ToT> as(FromT *from) {
+ return detail::As<ToT>(from);
+}
+
+template <class ToT, class FromT,
+ std::enable_if_t<TD_IS_TRIVIALLY_COPYABLE(ToT) && TD_IS_TRIVIALLY_COPYABLE(FromT), int> = 0>
+detail::ConstAs<ToT> as(const FromT *from) {
+ return detail::ConstAs<ToT>(from);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.cpp
index 4016feaa58..2e06a21b86 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,6 @@
#include "td/utils/base64.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
@@ -15,57 +14,68 @@
#include <iterator>
namespace td {
-//TODO: fix copypaste
-static const char *const symbols64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+template <bool is_url>
+static const char *get_characters() {
+ return is_url ? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+ : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+}
-string base64_encode(Slice input) {
+template <bool is_url>
+static const unsigned char *get_character_table() {
+ static unsigned char char_to_value[256];
+ static bool is_inited = [] {
+ auto characters = get_characters<is_url>();
+ std::fill(std::begin(char_to_value), std::end(char_to_value), static_cast<unsigned char>(64));
+ for (unsigned char i = 0; i < 64; i++) {
+ char_to_value[static_cast<size_t>(characters[i])] = i;
+ }
+ return true;
+ }();
+ CHECK(is_inited);
+ return char_to_value;
+}
+
+template <bool is_url>
+string base64_encode_impl(Slice input) {
+ auto characters = get_characters<is_url>();
string base64;
base64.reserve((input.size() + 2) / 3 * 4);
for (size_t i = 0; i < input.size();) {
size_t left = min(input.size() - i, static_cast<size_t>(3));
int c = input.ubegin()[i++] << 16;
- base64 += symbols64[c >> 18];
+ base64 += characters[c >> 18];
if (left != 1) {
c |= input.ubegin()[i++] << 8;
}
- base64 += symbols64[(c >> 12) & 63];
+ base64 += characters[(c >> 12) & 63];
if (left == 3) {
c |= input.ubegin()[i++];
}
if (left != 1) {
- base64 += symbols64[(c >> 6) & 63];
- } else {
+ base64 += characters[(c >> 6) & 63];
+ } else if (!is_url) {
base64 += '=';
}
if (left == 3) {
- base64 += symbols64[c & 63];
- } else {
+ base64 += characters[c & 63];
+ } else if (!is_url) {
base64 += '=';
}
}
return base64;
}
-static unsigned char char_to_value[256];
-static void init_base64_table() {
- static bool is_inited = []() {
- std::fill(std::begin(char_to_value), std::end(char_to_value), 64);
- for (unsigned char i = 0; i < 64; i++) {
- char_to_value[static_cast<size_t>(symbols64[i])] = i;
- }
- return true;
- }();
- CHECK(is_inited);
+string base64_encode(Slice input) {
+ return base64_encode_impl<false>(input);
}
-Result<string> base64_decode(Slice base64) {
- init_base64_table();
-
- if ((base64.size() & 3) != 0) {
- return Status::Error("Wrong string length");
- }
+string base64url_encode(Slice input) {
+ return base64_encode_impl<true>(input);
+}
+template <bool is_url>
+Result<Slice> base64_drop_padding(Slice base64) {
size_t padding_length = 0;
while (!base64.empty() && base64.back() == '=') {
base64.remove_suffix(1);
@@ -74,121 +84,80 @@ Result<string> base64_decode(Slice base64) {
if (padding_length >= 3) {
return Status::Error("Wrong string padding");
}
+ if ((!is_url || padding_length > 0) && ((base64.size() + padding_length) & 3) != 0) {
+ return Status::Error("Wrong padding length");
+ }
+ if (is_url && (base64.size() & 3) == 1) {
+ return Status::Error("Wrong string length");
+ }
+ return base64;
+}
- string output;
- output.reserve(((base64.size() + 3) >> 2) * 3);
+static Status do_base64_decode_impl(Slice base64, const unsigned char *table, char *ptr) {
for (size_t i = 0; i < base64.size();) {
size_t left = min(base64.size() - i, static_cast<size_t>(4));
int c = 0;
for (size_t t = 0; t < left; t++) {
- auto value = char_to_value[base64.ubegin()[i++]];
+ auto value = table[base64.ubegin()[i++]];
if (value == 64) {
return Status::Error("Wrong character in the string");
}
c |= value << ((3 - t) * 6);
}
- output += static_cast<char>(static_cast<unsigned char>(c >> 16)); // implementation-defined
+ *ptr++ = static_cast<char>(static_cast<unsigned char>(c >> 16)); // implementation-defined
if (left == 2) {
if ((c & ((1 << 16) - 1)) != 0) {
return Status::Error("Wrong padding in the string");
}
} else {
- output += static_cast<char>(static_cast<unsigned char>(c >> 8)); // implementation-defined
+ *ptr++ = static_cast<char>(static_cast<unsigned char>(c >> 8)); // implementation-defined
if (left == 3) {
if ((c & ((1 << 8) - 1)) != 0) {
return Status::Error("Wrong padding in the string");
}
} else {
- output += static_cast<char>(static_cast<unsigned char>(c)); // implementation-defined
+ *ptr++ = static_cast<char>(static_cast<unsigned char>(c)); // implementation-defined
}
}
}
- return output;
+ return Status::OK();
}
-static const char *const url_symbols64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+template <class T>
+static T create_empty(size_t size);
-string base64url_encode(Slice input) {
- string base64;
- base64.reserve((input.size() + 2) / 3 * 4);
- for (size_t i = 0; i < input.size();) {
- size_t left = min(input.size() - i, static_cast<size_t>(3));
- int c = input.ubegin()[i++] << 16;
- base64 += url_symbols64[c >> 18];
- if (left != 1) {
- c |= input.ubegin()[i++] << 8;
- }
- base64 += url_symbols64[(c >> 12) & 63];
- if (left == 3) {
- c |= input.ubegin()[i++];
- }
- if (left != 1) {
- base64 += url_symbols64[(c >> 6) & 63];
- }
- if (left == 3) {
- base64 += url_symbols64[c & 63];
- }
- }
- return base64;
+template <>
+string create_empty<string>(size_t size) {
+ return string(size, '\0');
}
-static unsigned char url_char_to_value[256];
-static void init_base64url_table() {
- static bool is_inited = []() {
- std::fill(std::begin(url_char_to_value), std::end(url_char_to_value), 64);
- for (unsigned char i = 0; i < 64; i++) {
- url_char_to_value[static_cast<size_t>(url_symbols64[i])] = i;
- }
- return true;
- }();
- CHECK(is_inited);
+template <>
+SecureString create_empty<SecureString>(size_t size) {
+ return SecureString{size};
}
-Result<string> base64url_decode(Slice base64) {
- init_base64url_table();
+template <bool is_url, class T>
+static Result<T> base64_decode_impl(Slice base64) {
+ TRY_RESULT_ASSIGN(base64, base64_drop_padding<is_url>(base64));
- size_t padding_length = 0;
- while (!base64.empty() && base64.back() == '=') {
- base64.remove_suffix(1);
- padding_length++;
- }
- if (padding_length >= 3 || (padding_length > 0 && ((base64.size() + padding_length) & 3) != 0)) {
- return Status::Error("Wrong string padding");
- }
+ T result = create_empty<T>(base64.size() / 4 * 3 + ((base64.size() & 3) + 1) / 2);
+ TRY_STATUS(do_base64_decode_impl(base64, get_character_table<is_url>(), as_mutable_slice(result).begin()));
+ return std::move(result);
+}
- if ((base64.size() & 3) == 1) {
- return Status::Error("Wrong string length");
- }
+Result<string> base64_decode(Slice base64) {
+ return base64_decode_impl<false, string>(base64);
+}
- string output;
- output.reserve(((base64.size() + 3) >> 2) * 3);
- for (size_t i = 0; i < base64.size();) {
- size_t left = min(base64.size() - i, static_cast<size_t>(4));
- int c = 0;
- for (size_t t = 0; t < left; t++) {
- auto value = url_char_to_value[base64.ubegin()[i++]];
- if (value == 64) {
- return Status::Error("Wrong character in the string");
- }
- c |= value << ((3 - t) * 6);
- }
- output += static_cast<char>(static_cast<unsigned char>(c >> 16)); // implementation-defined
- if (left == 2) {
- if ((c & ((1 << 16) - 1)) != 0) {
- return Status::Error("Wrong padding in the string");
- }
- } else {
- output += static_cast<char>(static_cast<unsigned char>(c >> 8)); // implementation-defined
- if (left == 3) {
- if ((c & ((1 << 8) - 1)) != 0) {
- return Status::Error("Wrong padding in the string");
- }
- } else {
- output += static_cast<char>(static_cast<unsigned char>(c)); // implementation-defined
- }
- }
- }
- return output;
+Result<SecureString> base64_decode_secure(Slice base64) {
+ return base64_decode_impl<false, SecureString>(base64);
+}
+
+Result<string> base64url_decode(Slice base64) {
+ return base64_decode_impl<true, string>(base64);
+}
+Result<SecureString> base64url_decode_secure(Slice base64) {
+ return base64_decode_impl<true, SecureString>(base64);
}
template <bool is_url>
@@ -208,14 +177,7 @@ static bool is_base64_impl(Slice input) {
return false;
}
- unsigned char *table;
- if (is_url) {
- init_base64url_table();
- table = url_char_to_value;
- } else {
- init_base64_table();
- table = char_to_value;
- }
+ auto table = get_character_table<is_url>();
for (auto c : input) {
if (table[static_cast<unsigned char>(c)] == 64) {
return false;
@@ -223,13 +185,13 @@ static bool is_base64_impl(Slice input) {
}
if ((input.size() & 3) == 2) {
- auto value = table[static_cast<int>(input.back())];
+ auto value = table[static_cast<unsigned char>(input.back())];
if ((value & 15) != 0) {
return false;
}
}
if ((input.size() & 3) == 3) {
- auto value = table[static_cast<int>(input.back())];
+ auto value = table[static_cast<unsigned char>(input.back())];
if ((value & 3) != 0) {
return false;
}
@@ -246,16 +208,101 @@ bool is_base64url(Slice input) {
return is_base64_impl<true>(input);
}
+template <bool is_url>
+static bool is_base64_characters_impl(Slice input) {
+ auto table = get_character_table<is_url>();
+ for (auto c : input) {
+ if (table[static_cast<unsigned char>(c)] == 64) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool is_base64_characters(Slice input) {
+ return is_base64_characters_impl<false>(input);
+}
+
+bool is_base64url_characters(Slice input) {
+ return is_base64_characters_impl<true>(input);
+}
+
string base64_filter(Slice input) {
+ auto table = get_character_table<false>();
string res;
res.reserve(input.size());
- init_base64_table();
for (auto c : input) {
- if (char_to_value[static_cast<unsigned char>(c)] != 64 || c == '=') {
+ if (table[static_cast<unsigned char>(c)] != 64 || c == '=') {
res += c;
}
}
return res;
}
+static const char *get_base32_characters(bool upper_case) {
+ return upper_case ? "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" : "abcdefghijklmnopqrstuvwxyz234567";
+}
+
+static const unsigned char *get_base32_character_table() {
+ static unsigned char char_to_value[256];
+ static bool is_inited = [] {
+ std::fill(std::begin(char_to_value), std::end(char_to_value), static_cast<unsigned char>(32));
+ auto characters_lc = get_base32_characters(false);
+ auto characters_uc = get_base32_characters(true);
+ for (unsigned char i = 0; i < 32; i++) {
+ char_to_value[static_cast<size_t>(characters_lc[i])] = i;
+ char_to_value[static_cast<size_t>(characters_uc[i])] = i;
+ }
+ return true;
+ }();
+ CHECK(is_inited);
+ return char_to_value;
+}
+
+string base32_encode(Slice input, bool upper_case) {
+ auto *characters = get_base32_characters(upper_case);
+ string base32;
+ base32.reserve((input.size() * 8 + 4) / 5);
+ uint32 c = 0;
+ uint32 length = 0;
+ for (size_t i = 0; i < input.size(); i++) {
+ c = (c << 8) | input.ubegin()[i];
+ length += 8;
+ while (length >= 5) {
+ length -= 5;
+ base32.push_back(characters[(c >> length) & 31]);
+ }
+ }
+ if (length != 0) {
+ base32.push_back(characters[(c << (5 - length)) & 31]);
+ }
+ //TODO: optional padding
+ return base32;
+}
+
+Result<string> base32_decode(Slice base32) {
+ string res;
+ res.reserve(base32.size() * 5 / 8);
+ uint32 c = 0;
+ uint32 length = 0;
+ auto *table = get_base32_character_table();
+ for (size_t i = 0; i < base32.size(); i++) {
+ auto value = table[base32.ubegin()[i]];
+ if (value == 32) {
+ return Status::Error("Wrong character in the string");
+ }
+ c = (c << 5) | value;
+ length += 5;
+ if (length >= 8) {
+ length -= 8;
+ res.push_back(static_cast<char>((c >> length) & 255));
+ }
+ }
+ if ((c & ((1 << length) - 1)) != 0) {
+ return Status::Error("Nonzero padding");
+ }
+ //TODO: check padding
+ return res;
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.h
index cef2b4cb34..4b7449aa65 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,6 +7,7 @@
#pragma once
#include "td/utils/common.h"
+#include "td/utils/SharedSlice.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
@@ -14,13 +15,21 @@ namespace td {
string base64_encode(Slice input);
Result<string> base64_decode(Slice base64);
+Result<SecureString> base64_decode_secure(Slice base64);
string base64url_encode(Slice input);
Result<string> base64url_decode(Slice base64);
+Result<SecureString> base64url_decode_secure(Slice base64);
bool is_base64(Slice input);
bool is_base64url(Slice input);
+bool is_base64_characters(Slice input);
+bool is_base64url_characters(Slice input);
+
string base64_filter(Slice input);
+string base32_encode(Slice input, bool upper_case = false);
+Result<string> base32_decode(Slice base32);
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/benchmark.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/benchmark.h
index ddc7ad75e6..65a03cb7a3 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/benchmark.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/benchmark.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,14 +15,14 @@
#include <tuple>
#include <utility>
-#define BENCH(name, desc) \
- class name##Bench : public ::td::Benchmark { \
- public: \
- std::string get_description() const override { \
- return (desc); \
- } \
- void run(int n) override; \
- }; \
+#define BENCH(name, desc) \
+ class name##Bench final : public ::td::Benchmark { \
+ public: \
+ std::string get_description() const final { \
+ return (desc); \
+ } \
+ void run(int n) final; \
+ }; \
void name##Bench::run(int n)
namespace td {
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/bits.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/bits.h
new file mode 100644
index 0000000000..69bd7ef8c9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/bits.h
@@ -0,0 +1,310 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#if TD_MSVC
+#include <intrin.h>
+#endif
+
+#ifdef bswap32
+#undef bswap32
+#endif
+
+#ifdef bswap64
+#undef bswap64
+#endif
+
+namespace td {
+
+int32 count_leading_zeroes32(uint32 x);
+int32 count_leading_zeroes64(uint64 x);
+int32 count_trailing_zeroes32(uint32 x);
+int32 count_trailing_zeroes64(uint64 x);
+uint32 bswap32(uint32 x);
+uint64 bswap64(uint64 x);
+int32 count_bits32(uint32 x);
+int32 count_bits64(uint64 x);
+
+inline uint32 bits_negate32(uint32 x) {
+ return ~x + 1;
+}
+
+inline uint64 bits_negate64(uint64 x) {
+ return ~x + 1;
+}
+
+inline uint32 lower_bit32(uint32 x) {
+ return x & bits_negate32(x);
+}
+
+inline uint64 lower_bit64(uint64 x) {
+ return x & bits_negate64(x);
+}
+
+inline uint64 host_to_big_endian64(uint64 x) {
+ // NB: works only for little-endian systems
+ return bswap64(x);
+}
+inline uint64 big_endian_to_host64(uint64 x) {
+ // NB: works only for little-endian systems
+ return bswap64(x);
+}
+
+//TODO: optimize
+inline int32 count_leading_zeroes_non_zero32(uint32 x) {
+ DCHECK(x != 0);
+ return count_leading_zeroes32(x);
+}
+inline int32 count_leading_zeroes_non_zero64(uint64 x) {
+ DCHECK(x != 0);
+ return count_leading_zeroes64(x);
+}
+inline int32 count_trailing_zeroes_non_zero32(uint32 x) {
+ DCHECK(x != 0);
+ return count_trailing_zeroes32(x);
+}
+inline int32 count_trailing_zeroes_non_zero64(uint64 x) {
+ DCHECK(x != 0);
+ return count_trailing_zeroes64(x);
+}
+
+//
+// Platform specific implementation
+//
+#if TD_MSVC
+
+inline int32 count_leading_zeroes32(uint32 x) {
+ unsigned long res = 0;
+ if (_BitScanReverse(&res, x)) {
+ return 31 - res;
+ }
+ return 32;
+}
+
+inline int32 count_leading_zeroes64(uint64 x) {
+#if defined(_M_X64)
+ unsigned long res = 0;
+ if (_BitScanReverse64(&res, x)) {
+ return 63 - res;
+ }
+ return 64;
+#else
+ if ((x >> 32) == 0) {
+ return count_leading_zeroes32(static_cast<uint32>(x)) + 32;
+ } else {
+ return count_leading_zeroes32(static_cast<uint32>(x >> 32));
+ }
+#endif
+}
+
+inline int32 count_trailing_zeroes32(uint32 x) {
+ unsigned long res = 0;
+ if (_BitScanForward(&res, x)) {
+ return res;
+ }
+ return 32;
+}
+
+inline int32 count_trailing_zeroes64(uint64 x) {
+#if defined(_M_X64)
+ unsigned long res = 0;
+ if (_BitScanForward64(&res, x)) {
+ return res;
+ }
+ return 64;
+#else
+ if (static_cast<uint32>(x) == 0) {
+ return count_trailing_zeroes32(static_cast<uint32>(x >> 32)) + 32;
+ } else {
+ return count_trailing_zeroes32(static_cast<uint32>(x));
+ }
+#endif
+}
+
+inline uint32 bswap32(uint32 x) {
+ return _byteswap_ulong(x);
+}
+
+inline uint64 bswap64(uint64 x) {
+ return _byteswap_uint64(x);
+}
+
+inline int32 count_bits32(uint32 x) {
+ // Do not use __popcnt because it will fail on some platforms.
+ x -= (x >> 1) & 0x55555555;
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F;
+ x += x >> 8;
+ return (x + (x >> 16)) & 0x3F;
+}
+
+inline int32 count_bits64(uint64 x) {
+#if defined(_M_X64)
+ return static_cast<int32>(__popcnt64(x));
+#else
+ return count_bits32(static_cast<uint32>(x >> 32)) + count_bits32(static_cast<uint32>(x));
+#endif
+}
+
+#elif TD_INTEL
+
+inline int32 count_leading_zeroes32(uint32 x) {
+ unsigned __int32 res = 0;
+ if (_BitScanReverse(&res, x)) {
+ return 31 - res;
+ }
+ return 32;
+}
+
+inline int32 count_leading_zeroes64(uint64 x) {
+#if defined(_M_X64) || defined(__x86_64__)
+ unsigned __int32 res = 0;
+ if (_BitScanReverse64(&res, x)) {
+ return 63 - res;
+ }
+ return 64;
+#else
+ if ((x >> 32) == 0) {
+ return count_leading_zeroes32(static_cast<uint32>(x)) + 32;
+ } else {
+ return count_leading_zeroes32(static_cast<uint32>(x >> 32));
+ }
+#endif
+}
+
+inline int32 count_trailing_zeroes32(uint32 x) {
+ unsigned __int32 res = 0;
+ if (_BitScanForward(&res, x)) {
+ return res;
+ }
+ return 32;
+}
+
+inline int32 count_trailing_zeroes64(uint64 x) {
+#if defined(_M_X64) || defined(__x86_64__)
+ unsigned __int32 res = 0;
+ if (_BitScanForward64(&res, x)) {
+ return res;
+ }
+ return 64;
+#else
+ if (static_cast<uint32>(x) == 0) {
+ return count_trailing_zeroes32(static_cast<uint32>(x >> 32)) + 32;
+ } else {
+ return count_trailing_zeroes32(static_cast<uint32>(x));
+ }
+#endif
+}
+
+inline uint32 bswap32(uint32 x) {
+ return _bswap(static_cast<int>(x));
+}
+
+inline uint64 bswap64(uint64 x) {
+ return _bswap64(static_cast<__int64>(x));
+}
+
+inline int32 count_bits32(uint32 x) {
+ x -= (x >> 1) & 0x55555555;
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0F0F0F0F;
+ x += x >> 8;
+ return (x + (x >> 16)) & 0x3F;
+}
+
+inline int32 count_bits64(uint64 x) {
+ return count_bits32(static_cast<uint32>(x >> 32)) + count_bits32(static_cast<uint32>(x));
+}
+
+#else
+
+inline int32 count_leading_zeroes32(uint32 x) {
+ if (x == 0) {
+ return 32;
+ }
+ return __builtin_clz(x);
+}
+
+inline int32 count_leading_zeroes64(uint64 x) {
+ if (x == 0) {
+ return 64;
+ }
+ return __builtin_clzll(x);
+}
+
+inline int32 count_trailing_zeroes32(uint32 x) {
+ if (x == 0) {
+ return 32;
+ }
+ return __builtin_ctz(x);
+}
+
+inline int32 count_trailing_zeroes64(uint64 x) {
+ if (x == 0) {
+ return 64;
+ }
+ return __builtin_ctzll(x);
+}
+
+inline uint32 bswap32(uint32 x) {
+ return __builtin_bswap32(x);
+}
+
+inline uint64 bswap64(uint64 x) {
+ return __builtin_bswap64(x);
+}
+
+inline int32 count_bits32(uint32 x) {
+ return __builtin_popcount(x);
+}
+
+inline int32 count_bits64(uint64 x) {
+ return __builtin_popcountll(x);
+}
+
+#endif
+
+struct BitsRange {
+ explicit BitsRange(uint64 bits = 0) : bits{bits}, pos{-1} {
+ }
+
+ BitsRange begin() const {
+ return *this;
+ }
+
+ BitsRange end() const {
+ return BitsRange{};
+ }
+
+ int32 operator*() const {
+ if (pos == -1) {
+ pos = count_trailing_zeroes64(bits);
+ }
+ return pos;
+ }
+
+ bool operator!=(const BitsRange &other) const {
+ return bits != other.bits;
+ }
+
+ BitsRange &operator++() {
+ auto i = **this;
+ if (i != 64) {
+ bits ^= 1ull << i;
+ }
+ pos = -1;
+ return *this;
+ }
+
+ private:
+ uint64 bits{0};
+ mutable int32 pos{-1};
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.cpp
index c1a123031c..f767e23ae4 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.cpp
@@ -1,21 +1,34 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/buffer.h"
+#include "td/utils/logging.h"
#include "td/utils/port/thread_local.h"
+#include <cstddef>
#include <new>
+// fixes https://bugs.llvm.org/show_bug.cgi?id=33723 for clang >= 3.6 + c++11 + libc++
+#if TD_CLANG && _LIBCPP_VERSION
+#define TD_OFFSETOF __builtin_offsetof
+#else
+#define TD_OFFSETOF offsetof
+#endif
+
namespace td {
TD_THREAD_LOCAL BufferAllocator::BufferRawTls *BufferAllocator::buffer_raw_tls; // static zero-initialized
std::atomic<size_t> BufferAllocator::buffer_mem;
+int64 BufferAllocator::get_buffer_slice_size() {
+ return 0;
+}
+
size_t BufferAllocator::get_buffer_mem() {
return buffer_mem;
}
@@ -76,30 +89,113 @@ BufferAllocator::ReaderPtr BufferAllocator::create_reader(const ReaderPtr &raw)
void BufferAllocator::dec_ref_cnt(BufferRaw *ptr) {
int left = ptr->ref_cnt_.fetch_sub(1, std::memory_order_acq_rel);
if (left == 1) {
- auto buf_size = max(sizeof(BufferRaw), offsetof(BufferRaw, data_) + ptr->data_size_);
+ auto buf_size = max(sizeof(BufferRaw), TD_OFFSETOF(BufferRaw, data_) + ptr->data_size_);
buffer_mem -= buf_size;
ptr->~BufferRaw();
delete[] ptr;
}
}
+size_t ChainBufferReader::advance(size_t offset, MutableSlice dest) {
+ LOG_CHECK(offset <= size()) << offset << " " << size() << " " << end_.offset() << " " << begin_.offset() << " "
+ << sync_flag_ << " " << dest.size();
+ return begin_.advance(offset, dest);
+}
+
BufferRaw *BufferAllocator::create_buffer_raw(size_t size) {
size = (size + 7) & -8;
- auto buf_size = offsetof(BufferRaw, data_) + size;
+ auto buf_size = TD_OFFSETOF(BufferRaw, data_) + size;
if (buf_size < sizeof(BufferRaw)) {
buf_size = sizeof(BufferRaw);
}
buffer_mem += buf_size;
auto *buffer_raw = reinterpret_cast<BufferRaw *>(new char[buf_size]);
- new (buffer_raw) BufferRaw();
- buffer_raw->data_size_ = size;
- buffer_raw->begin_ = 0;
- buffer_raw->end_ = 0;
+ return new (buffer_raw) BufferRaw(size);
+}
+
+void BufferBuilder::append(BufferSlice slice) {
+ if (append_inplace(slice.as_slice())) {
+ return;
+ }
+ append_slow(std::move(slice));
+}
+
+void BufferBuilder::append(Slice slice) {
+ if (append_inplace(slice)) {
+ return;
+ }
+ append_slow(BufferSlice(slice));
+}
+
+void BufferBuilder::prepend(BufferSlice slice) {
+ if (prepend_inplace(slice.as_slice())) {
+ return;
+ }
+ prepend_slow(std::move(slice));
+}
+
+void BufferBuilder::prepend(Slice slice) {
+ if (prepend_inplace(slice)) {
+ return;
+ }
+ prepend_slow(BufferSlice(slice));
+}
- buffer_raw->ref_cnt_.store(1, std::memory_order_relaxed);
- buffer_raw->has_writer_.store(true, std::memory_order_relaxed);
- buffer_raw->was_reader_ = false;
- return buffer_raw;
+BufferSlice BufferBuilder::extract() {
+ if (to_append_.empty() && to_prepend_.empty()) {
+ return buffer_writer_.as_buffer_slice();
+ }
+ size_t total_size = size();
+ BufferWriter writer(0, 0, total_size);
+ std::move(*this).for_each([&](auto &&slice) {
+ writer.prepare_append().truncate(slice.size()).copy_from(slice.as_slice());
+ writer.confirm_append(slice.size());
+ });
+ *this = {};
+ return writer.as_buffer_slice();
}
+
+size_t BufferBuilder::size() const {
+ size_t total_size = 0;
+ for_each([&](auto &&slice) { total_size += slice.size(); });
+ return total_size;
+}
+
+bool BufferBuilder::append_inplace(Slice slice) {
+ if (!to_append_.empty()) {
+ return false;
+ }
+ auto dest = buffer_writer_.prepare_append();
+ if (dest.size() < slice.size()) {
+ return false;
+ }
+ dest.remove_suffix(dest.size() - slice.size());
+ dest.copy_from(slice);
+ buffer_writer_.confirm_append(slice.size());
+ return true;
+}
+
+void BufferBuilder::append_slow(BufferSlice slice) {
+ to_append_.push_back(std::move(slice));
+}
+
+bool BufferBuilder::prepend_inplace(Slice slice) {
+ if (!to_prepend_.empty()) {
+ return false;
+ }
+ auto dest = buffer_writer_.prepare_prepend();
+ if (dest.size() < slice.size()) {
+ return false;
+ }
+ dest.remove_prefix(dest.size() - slice.size());
+ dest.copy_from(slice);
+ buffer_writer_.confirm_prepend(slice.size());
+ return true;
+}
+
+void BufferBuilder::prepend_slow(BufferSlice slice) {
+ to_prepend_.push_back(std::move(slice));
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.h
index aa4ef8db26..798ceb9d50 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,33 +7,34 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include "td/utils/port/thread_local.h"
#include "td/utils/Slice.h"
#include <atomic>
-#include <cstring>
#include <limits>
+#include <memory>
namespace td {
struct BufferRaw {
+ explicit BufferRaw(size_t size) : data_size_(size) {
+ }
size_t data_size_;
// Constant after first reader is created.
// May be change by writer before it.
// So writer may do prepends till there is no reader created.
- size_t begin_;
+ size_t begin_ = 0;
// Write by writer.
// Read by reader.
- std::atomic<size_t> end_;
+ std::atomic<size_t> end_{0};
- mutable std::atomic<int32> ref_cnt_;
- std::atomic<bool> has_writer_;
- bool was_reader_;
+ mutable std::atomic<int32> ref_cnt_{1};
+ std::atomic<bool> has_writer_{true};
+ bool was_reader_{false};
- alignas(4) char data_[1];
+ alignas(4) unsigned char data_[1];
};
class BufferAllocator {
@@ -66,10 +67,16 @@ class BufferAllocator {
static ReaderPtr create_reader(const ReaderPtr &raw);
static size_t get_buffer_mem();
+ static int64 get_buffer_slice_size();
static void clear_thread_local();
private:
+ friend class BufferSlice;
+
+ static void track_buffer_slice(int64 size) {
+ }
+
static ReaderPtr create_reader_fast(size_t size);
static WriterPtr create_writer_exact(size_t size);
@@ -103,25 +110,54 @@ class BufferSlice {
return;
}
begin_ = buffer_->begin_;
+ end_ = begin_;
sync_with_writer();
}
BufferSlice(BufferReaderPtr buffer_ptr, size_t begin, size_t end)
: buffer_(std::move(buffer_ptr)), begin_(begin), end_(end) {
+ debug_track();
+ }
+ BufferSlice(const BufferSlice &other) = delete;
+ BufferSlice &operator=(const BufferSlice &other) = delete;
+ BufferSlice(BufferSlice &&other) noexcept : BufferSlice(std::move(other.buffer_), other.begin_, other.end_) {
+ debug_untrack(); // yes, debug_untrack
+ }
+ BufferSlice &operator=(BufferSlice &&other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+ debug_untrack();
+ buffer_ = std::move(other.buffer_);
+ begin_ = other.begin_;
+ end_ = other.end_;
+ return *this;
}
explicit BufferSlice(size_t size) : buffer_(BufferAllocator::create_reader(size)) {
end_ = buffer_->end_.load(std::memory_order_relaxed);
begin_ = end_ - ((size + 7) & -8);
end_ = begin_ + size;
+ debug_track();
}
explicit BufferSlice(Slice slice) : BufferSlice(slice.size()) {
- std::memcpy(as_slice().begin(), slice.begin(), slice.size());
+ as_slice().copy_from(slice);
}
BufferSlice(const char *ptr, size_t size) : BufferSlice(Slice(ptr, size)) {
}
+ ~BufferSlice() {
+ debug_untrack();
+ }
+
+ void debug_track() const {
+ BufferAllocator::track_buffer_slice(static_cast<int64>(size()));
+ }
+ void debug_untrack() const {
+ BufferAllocator::track_buffer_slice(-static_cast<int64>(size()));
+ }
+
BufferSlice clone() const {
if (is_null()) {
return BufferSlice(BufferReaderPtr(), begin_, end_);
@@ -143,6 +179,10 @@ class BufferSlice {
return Slice(buffer_->data_ + begin_, size());
}
+ operator Slice() const {
+ return as_slice();
+ }
+
MutableSlice as_slice() {
if (is_null()) {
return MutableSlice();
@@ -161,21 +201,27 @@ class BufferSlice {
}
bool confirm_read(size_t size) {
+ debug_untrack();
begin_ += size;
CHECK(begin_ <= end_);
+ debug_track();
return begin_ == end_;
}
void truncate(size_t limit) {
if (size() > limit) {
+ debug_untrack();
end_ = begin_ + limit;
+ debug_track();
}
}
BufferSlice from_slice(Slice slice) const {
auto res = BufferSlice(BufferAllocator::create_reader(buffer_));
- res.begin_ = slice.begin() - buffer_->data_;
- res.end_ = slice.end() - buffer_->data_;
+ res.debug_untrack();
+ res.begin_ = static_cast<size_t>(slice.ubegin() - buffer_->data_);
+ res.end_ = static_cast<size_t>(slice.uend() - buffer_->data_);
+ res.debug_track();
CHECK(buffer_->begin_ <= res.begin_);
CHECK(res.begin_ <= res.end_);
CHECK(res.end_ <= buffer_->end_.load(std::memory_order_relaxed));
@@ -202,20 +248,36 @@ class BufferSlice {
}
size_t size() const {
+ if (is_null()) {
+ return 0;
+ }
return end_ - begin_;
}
+ // like in std::string
+ size_t length() const {
+ return size();
+ }
+
// set end_ into writer's end_
size_t sync_with_writer() {
+ debug_untrack();
CHECK(!is_null());
auto old_end = end_;
end_ = buffer_->end_.load(std::memory_order_acquire);
+ debug_track();
return end_ - old_end;
}
bool is_writer_alive() const {
CHECK(!is_null());
return buffer_->has_writer_.load(std::memory_order_acquire);
}
+ void clear() {
+ debug_untrack();
+ begin_ = 0;
+ end_ = 0;
+ buffer_ = nullptr;
+ }
private:
BufferReaderPtr buffer_;
@@ -241,6 +303,10 @@ class BufferWriter {
BufferWriter(size_t size, size_t prepend, size_t append)
: BufferWriter(BufferAllocator::create_writer(size, prepend, append)) {
}
+ BufferWriter(Slice slice, size_t prepend, size_t append)
+ : BufferWriter(BufferAllocator::create_writer(slice.size(), prepend, append)) {
+ as_slice().copy_from(slice);
+ }
explicit BufferWriter(BufferWriterPtr buffer_ptr) : buffer_(std::move(buffer_ptr)) {
}
@@ -263,6 +329,10 @@ class BufferWriter {
auto end = buffer_->end_.load(std::memory_order_relaxed);
return MutableSlice(buffer_->data_ + buffer_->begin_, buffer_->data_ + end);
}
+ Slice as_slice() const {
+ auto end = buffer_->end_.load(std::memory_order_relaxed);
+ return Slice(buffer_->data_ + buffer_->begin_, buffer_->data_ + end);
+ }
MutableSlice prepare_prepend() {
if (is_null()) {
@@ -481,7 +551,7 @@ class ChainBufferIterator {
// copy to dest if possible
auto to_dest_size = min(ready.size(), dest.size());
if (to_dest_size != 0) {
- std::memcpy(dest.data(), ready.data(), to_dest_size);
+ dest.copy_from(ready.substr(0, to_dest_size));
dest.remove_prefix(to_dest_size);
}
@@ -541,10 +611,7 @@ class ChainBufferReader {
begin_.confirm_read(size);
}
- size_t advance(size_t offset, MutableSlice dest = MutableSlice()) {
- CHECK(offset <= size());
- return begin_.advance(offset, dest);
- }
+ size_t advance(size_t offset, MutableSlice dest = MutableSlice());
size_t size() const {
return end_.offset() - begin_.offset();
@@ -570,14 +637,14 @@ class ChainBufferReader {
// Return [begin_, tail.begin_)
// *this = tail
- ChainBufferReader cut_head(ChainBufferIterator pos) {
+ ChainBufferReader cut_head(ChainBufferIterator pos) TD_WARN_UNUSED_RESULT {
auto tmp = begin_.clone();
begin_ = pos.clone();
return ChainBufferReader(std::move(tmp), std::move(pos), false);
}
- ChainBufferReader cut_head(size_t offset) {
- CHECK(offset <= size()) << offset << " " << size();
+ ChainBufferReader cut_head(size_t offset) TD_WARN_UNUSED_RESULT {
+ CHECK(offset <= size());
auto it = begin_.clone();
it.advance(offset);
return cut_head(std::move(it));
@@ -611,19 +678,10 @@ class ChainBufferReader {
class ChainBufferWriter {
public:
- ChainBufferWriter() {
- init();
- }
-
- // legacy
- static ChainBufferWriter create_empty(size_t size = 0) {
- return ChainBufferWriter();
- }
-
- void init(size_t size = 0) {
- writer_ = BufferWriter(size);
- tail_ = ChainBufferNodeAllocator::create(writer_.as_buffer_slice(), true);
- head_ = ChainBufferNodeAllocator::clone(tail_);
+ ChainBufferWriter()
+ : writer_(0)
+ , tail_(ChainBufferNodeAllocator::create(writer_.as_buffer_slice(), true))
+ , head_(ChainBufferNodeAllocator::clone(tail_)) {
}
MutableSlice prepare_append(size_t hint = 0) {
@@ -634,6 +692,14 @@ class ChainBufferWriter {
}
return res;
}
+ MutableSlice prepare_append_at_least(size_t size) {
+ CHECK(!empty());
+ auto res = prepare_append_inplace();
+ if (res.size() < size) {
+ return prepare_append_alloc(size);
+ }
+ return res;
+ }
MutableSlice prepare_append_inplace() {
CHECK(!empty());
return writer_.prepare_append();
@@ -655,11 +721,11 @@ class ChainBufferWriter {
writer_.confirm_append(size);
}
- void append(Slice slice) {
+ void append(Slice slice, size_t hint = 0) {
while (!slice.empty()) {
- auto ready = prepare_append(slice.size());
+ auto ready = prepare_append(td::max(slice.size(), hint));
auto shift = min(ready.size(), slice.size());
- std::memcpy(ready.data(), slice.data(), shift);
+ ready.copy_from(slice.substr(0, shift));
confirm_append(shift);
slice.remove_prefix(shift);
}
@@ -700,9 +766,73 @@ class ChainBufferWriter {
return !tail_;
}
- ChainBufferNodeReaderPtr head_;
- ChainBufferNodeWriterPtr tail_;
BufferWriter writer_;
+ ChainBufferNodeWriterPtr tail_;
+ ChainBufferNodeReaderPtr head_;
+};
+
+class BufferBuilder {
+ public:
+ BufferBuilder() = default;
+ BufferBuilder(Slice slice, size_t prepend_size, size_t append_size)
+ : buffer_writer_(slice, prepend_size, append_size) {
+ }
+ explicit BufferBuilder(BufferWriter &&buffer_writer) : buffer_writer_(std::move(buffer_writer)) {
+ }
+
+ void append(BufferSlice slice);
+ void append(Slice slice);
+
+ void prepend(BufferSlice slice);
+ void prepend(Slice slice);
+
+ template <class F>
+ void for_each(F &&f) const & {
+ for (auto i = to_prepend_.size(); i > 0; i--) {
+ f(to_prepend_[i - 1].as_slice());
+ }
+ if (!buffer_writer_.empty()) {
+ f(buffer_writer_.as_slice());
+ }
+ for (auto &slice : to_append_) {
+ f(slice.as_slice());
+ }
+ }
+ template <class F>
+ void for_each(F &&f) && {
+ for (auto i = to_prepend_.size(); i > 0; i--) {
+ f(std::move(to_prepend_[i - 1]));
+ }
+ if (!buffer_writer_.empty()) {
+ f(buffer_writer_.as_buffer_slice());
+ }
+ for (auto &slice : to_append_) {
+ f(std::move(slice));
+ }
+ }
+ size_t size() const;
+
+ BufferSlice extract();
+
+ private:
+ BufferWriter buffer_writer_;
+ std::vector<BufferSlice> to_append_;
+ std::vector<BufferSlice> to_prepend_;
+
+ bool append_inplace(Slice slice);
+ void append_slow(BufferSlice slice);
+ bool prepend_inplace(Slice slice);
+ void prepend_slow(BufferSlice slice);
};
+inline Slice as_slice(const BufferSlice &value) {
+ return value.as_slice();
+}
+inline MutableSlice as_slice(BufferSlice &value) {
+ return value.as_slice();
+}
+inline MutableSlice as_mutable_slice(BufferSlice &value) {
+ return value.as_slice();
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/check.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/check.cpp
new file mode 100644
index 0000000000..3651452e52
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/check.cpp
@@ -0,0 +1,23 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/check.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+
+namespace td {
+namespace detail {
+
+void process_check_error(const char *message, const char *file, int line) {
+ ::td::Logger(*log_interface, log_options, VERBOSITY_NAME(FATAL), Slice(file), line, Slice())
+ << "Check `" << message << "` failed";
+ ::td::process_fatal_error(PSLICE() << "Check `" << message << "` failed in " << file << " at " << line << '\n');
+}
+
+} // namespace detail
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/check.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/check.h
new file mode 100644
index 0000000000..e6fba40c6f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/check.h
@@ -0,0 +1,32 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#define TD_DUMMY_CHECK(condition) ((void)(condition))
+
+#define CHECK(condition) \
+ if (!(condition)) { \
+ ::td::detail::process_check_error(#condition, __FILE__, __LINE__); \
+ }
+
+// clang-format off
+#ifdef NDEBUG
+ #define DCHECK TD_DUMMY_CHECK
+#else
+ #define DCHECK CHECK
+#endif
+// clang-format on
+
+#define UNREACHABLE() ::td::detail::process_check_error("Unreachable", __FILE__, __LINE__)
+
+namespace td {
+namespace detail {
+
+[[noreturn]] void process_check_error(const char *message, const char *file, int line);
+
+} // namespace detail
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/common.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/common.h
index d1217016e3..5bf9357f8a 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/common.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/common.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -33,20 +33,29 @@
#define _CRT_SECURE_NO_WARNINGS
#endif
- #include <Winsock2.h>
- #include <ws2tcpip.h>
+ #ifdef __MINGW32__
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+
+ #include <mswsock.h>
+ #include <windows.h>
+ #else
+ #include <WinSock2.h>
+ #include <WS2tcpip.h>
+
+ #include <MSWSock.h>
+ #include <Windows.h>
+ #endif
- #include <Mswsock.h>
- #include <Windows.h>
#undef ERROR
#endif
// clang-format on
+#include "td/utils/check.h"
#include "td/utils/int_types.h"
+#include "td/utils/unique_ptr.h"
-#include <memory>
#include <string>
-#include <utility>
#include <vector>
#define TD_DEBUG
@@ -64,6 +73,13 @@
#endif
// clang-format on
+#if TD_USE_ASAN
+#include <sanitizer/lsan_interface.h>
+#define TD_LSAN_IGNORE(x) __lsan_ignore_object(x)
+#else
+#define TD_LSAN_IGNORE(x) (void)(x)
+#endif
+
namespace td {
inline bool likely(bool x) {
@@ -99,11 +115,6 @@ using string = std::string;
template <class ValueT>
using vector = std::vector<ValueT>;
-template <class ValueT>
-using unique_ptr = std::unique_ptr<ValueT>;
-
-using std::make_unique;
-
struct Unit {};
struct Auto {
@@ -113,14 +124,4 @@ struct Auto {
}
};
-template <class ToT, class FromT>
-ToT &as(FromT *from) {
- return *reinterpret_cast<ToT *>(from);
-}
-
-template <class ToT, class FromT>
-const ToT &as(const FromT *from) {
- return *reinterpret_cast<const ToT *>(from);
-}
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h
index ac7462480d..bb346600e8 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h
@@ -1,3 +1,8 @@
#pragma once
+
#define TD_HAVE_OPENSSL 1
#define TD_HAVE_ZLIB 1
+#define TD_HAVE_CRC32C 0
+#define TD_HAVE_COROUTINES 1
+#define TD_HAVE_ABSL 1
+#define TD_FD_DEBUG 1
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h.in b/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h.in
index 92cbd5cdc6..f8b89aeb5c 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h.in
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h.in
@@ -1,3 +1,8 @@
#pragma once
+
#cmakedefine01 TD_HAVE_OPENSSL
#cmakedefine01 TD_HAVE_ZLIB
+#cmakedefine01 TD_HAVE_CRC32C
+#cmakedefine01 TD_HAVE_COROUTINES
+#cmakedefine01 TD_HAVE_ABSL
+#cmakedefine01 TD_FD_DEBUG
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.cpp
index 3e54e673ab..4fcda63e40 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.cpp
@@ -1,103 +1,123 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/crypto.h"
+#include "td/utils/as.h"
#include "td/utils/BigNum.h"
+#include "td/utils/bits.h"
+#include "td/utils/common.h"
+#include "td/utils/Destructor.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/RwMutex.h"
#include "td/utils/port/thread_local.h"
#include "td/utils/Random.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/SharedSlice.h"
+#include "td/utils/StackAllocator.h"
+#include "td/utils/StringBuilder.h"
#if TD_HAVE_OPENSSL
#include <openssl/aes.h>
+#include <openssl/bio.h>
#include <openssl/crypto.h>
+#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/md5.h>
+#include <openssl/opensslv.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
#include <openssl/sha.h>
#endif
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+#include <openssl/core_names.h>
+#include <openssl/params.h>
+#endif
+
#if TD_HAVE_ZLIB
#include <zlib.h>
#endif
+#if TD_HAVE_CRC32C
+#include "crc32c/crc32c.h"
+#endif
+
#include <algorithm>
+#include <cerrno>
#include <cstring>
+#include <mutex>
#include <utility>
namespace td {
-static uint64 gcd(uint64 a, uint64 b) {
+static uint64 pq_gcd(uint64 a, uint64 b) {
if (a == 0) {
return b;
}
- if (b == 0) {
- return a;
- }
-
- int shift = 0;
- while ((a & 1) == 0 && (b & 1) == 0) {
+ while ((a & 1) == 0) {
a >>= 1;
- b >>= 1;
- shift++;
}
+ DCHECK((b & 1) != 0);
while (true) {
- while ((a & 1) == 0) {
- a >>= 1;
- }
- while ((b & 1) == 0) {
- b >>= 1;
- }
if (a > b) {
- a -= b;
+ a = (a - b) >> 1;
+ while ((a & 1) == 0) {
+ a >>= 1;
+ }
} else if (b > a) {
- b -= a;
+ b = (b - a) >> 1;
+ while ((b & 1) == 0) {
+ b >>= 1;
+ }
} else {
- return a << shift;
+ return a;
+ }
+ }
+}
+
+// returns (c + a * b) % pq
+static uint64 pq_add_mul(uint64 c, uint64 a, uint64 b, uint64 pq) {
+ while (b) {
+ if (b & 1) {
+ c += a;
+ if (c >= pq) {
+ c -= pq;
+ }
+ }
+ a += a;
+ if (a >= pq) {
+ a -= pq;
}
+ b >>= 1;
}
+ return c;
}
uint64 pq_factorize(uint64 pq) {
- if (pq < 2 || pq > (static_cast<uint64>(1) << 63)) {
+ if (pq <= 2 || pq > (static_cast<uint64>(1) << 63)) {
return 1;
}
+ if ((pq & 1) == 0) {
+ return 2;
+ }
uint64 g = 0;
- for (int i = 0, it = 0; i < 3 || it < 1000; i++) {
+ for (int i = 0, iter = 0; i < 3 || iter < 1000; i++) {
uint64 q = Random::fast(17, 32) % (pq - 1);
uint64 x = Random::fast_uint64() % (pq - 1) + 1;
uint64 y = x;
int lim = 1 << (min(5, i) + 18);
for (int j = 1; j < lim; j++) {
- it++;
- uint64 a = x;
- uint64 b = x;
- uint64 c = q;
-
- // c += a * b
- while (b) {
- if (b & 1) {
- c += a;
- if (c >= pq) {
- c -= pq;
- }
- }
- a += a;
- if (a >= pq) {
- a -= pq;
- }
- b >>= 1;
- }
-
- x = c;
+ iter++;
+ x = pq_add_mul(q, x, x, pq);
uint64 z = x < y ? pq + x - y : x - y;
- g = gcd(z, pq);
+ g = pq_gcd(z, pq);
if (g != 1) {
break;
}
@@ -123,31 +143,29 @@ uint64 pq_factorize(uint64 pq) {
void init_crypto() {
static bool is_inited = [] {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
- return OPENSSL_init_crypto(0, nullptr) != 0;
+ bool result = OPENSSL_init_crypto(0, nullptr) != 0;
#else
OpenSSL_add_all_algorithms();
- return true;
+ bool result = true;
#endif
+ clear_openssl_errors("Init crypto");
+ return result;
}();
CHECK(is_inited);
}
template <class FromT>
static string as_big_endian_string(const FromT &from) {
- size_t size = sizeof(from);
- string res(size, '\0');
-
- auto ptr = reinterpret_cast<const unsigned char *>(&from);
- std::memcpy(&res[0], ptr, size);
+ char res[sizeof(FromT)];
+ as<FromT>(res) = from;
- size_t i = size;
+ size_t i = sizeof(FromT);
while (i && res[i - 1] == 0) {
i--;
}
- res.resize(i);
- std::reverse(res.begin(), res.end());
- return res;
+ std::reverse(res, res + i);
+ return string(res, res + i);
}
static int pq_factorize_big(Slice pq_str, string *p_str, string *q_str) {
@@ -164,14 +182,14 @@ static int pq_factorize_big(Slice pq_str, string *p_str, string *q_str) {
BigNum pq = BigNum::from_binary(pq_str);
bool found = false;
- for (int i = 0, it = 0; !found && (i < 3 || it < 1000); i++) {
+ for (int i = 0, iter = 0; !found && (i < 3 || iter < 1000); i++) {
int32 t = Random::fast(17, 32);
a.set_value(Random::fast_uint32());
b = a;
int32 lim = 1 << (i + 23);
for (int j = 1; j < lim; j++) {
- it++;
+ iter++;
BigNum::mod_mul(a, a, a, pq, context);
a += t;
if (BigNum::compare(a, pq) >= 0) {
@@ -236,156 +254,679 @@ int pq_factorize(Slice pq_str, string *p_str, string *q_str) {
return 0;
}
-/*** AES ***/
-static void aes_ige_xcrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to, bool encrypt_flag) {
- AES_KEY key;
- int err;
- if (encrypt_flag) {
- err = AES_set_encrypt_key(aes_key.raw, 256, &key);
- } else {
- err = AES_set_decrypt_key(aes_key.raw, 256, &key);
+struct AesBlock {
+ uint64 hi;
+ uint64 lo;
+
+ uint8 *raw() {
+ return reinterpret_cast<uint8 *>(this);
+ }
+ const uint8 *raw() const {
+ return reinterpret_cast<const uint8 *>(this);
+ }
+ Slice as_slice() const {
+ return Slice(raw(), AES_BLOCK_SIZE);
}
- LOG_IF(FATAL, err != 0);
- CHECK(from.size() <= to.size());
- AES_ige_encrypt(from.ubegin(), to.ubegin(), from.size(), &key, aes_iv->raw, encrypt_flag);
-}
-void aes_ige_encrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to) {
- aes_ige_xcrypt(aes_key, aes_iv, from, to, true);
-}
+ AesBlock operator^(const AesBlock &b) const {
+ AesBlock res;
+ res.hi = hi ^ b.hi;
+ res.lo = lo ^ b.lo;
+ return res;
+ }
+ void operator^=(const AesBlock &b) {
+ hi ^= b.hi;
+ lo ^= b.lo;
+ }
-void aes_ige_decrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to) {
- aes_ige_xcrypt(aes_key, aes_iv, from, to, false);
-}
+ void load(const uint8 *from) {
+ *this = as<AesBlock>(from);
+ }
+ void store(uint8 *to) {
+ as<AesBlock>(to) = *this;
+ }
+
+ AesBlock inc() const {
+#if SIZE_MAX == UINT64_MAX
+ AesBlock res;
+ res.lo = host_to_big_endian64(big_endian_to_host64(lo) + 1);
+ if (res.lo == 0) {
+ res.hi = host_to_big_endian64(big_endian_to_host64(hi) + 1);
+ } else {
+ res.hi = hi;
+ }
+ return res;
+#else
+ AesBlock res = *this;
+ auto ptr = res.raw();
+ if (++ptr[15] == 0) {
+ for (int i = 14; i >= 0; i--) {
+ if (++ptr[i] != 0) {
+ break;
+ }
+ }
+ }
+ return res;
+#endif
+ }
+};
+static_assert(sizeof(AesBlock) == 16, "");
+static_assert(sizeof(AesBlock) == AES_BLOCK_SIZE, "");
+
+class Evp {
+ public:
+ Evp() {
+ ctx_ = EVP_CIPHER_CTX_new();
+ LOG_IF(FATAL, ctx_ == nullptr);
+ }
+ Evp(const Evp &from) = delete;
+ Evp &operator=(const Evp &from) = delete;
+ Evp(Evp &&from) = delete;
+ Evp &operator=(Evp &&from) = delete;
+ ~Evp() {
+ CHECK(ctx_ != nullptr);
+ EVP_CIPHER_CTX_free(ctx_);
+ }
+
+ void init_encrypt_ecb(Slice key) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher;
+ if (unlikely(evp_cipher == nullptr)) {
+ init_thread_local_evp_cipher(evp_cipher, "AES-256-ECB");
+ }
+#else
+ const EVP_CIPHER *evp_cipher = EVP_aes_256_ecb();
+#endif
+ init(true, evp_cipher, key);
+ }
+
+ void init_decrypt_ecb(Slice key) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher;
+ if (unlikely(evp_cipher == nullptr)) {
+ init_thread_local_evp_cipher(evp_cipher, "AES-256-ECB");
+ }
+#else
+ const EVP_CIPHER *evp_cipher = EVP_aes_256_ecb();
+#endif
+ init(false, evp_cipher, key);
+ }
+
+ void init_encrypt_cbc(Slice key) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher;
+ if (unlikely(evp_cipher == nullptr)) {
+ init_thread_local_evp_cipher(evp_cipher, "AES-256-CBC");
+ }
+#else
+ const EVP_CIPHER *evp_cipher = EVP_aes_256_cbc();
+#endif
+ init(true, evp_cipher, key);
+ }
+
+ void init_decrypt_cbc(Slice key) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher;
+ if (unlikely(evp_cipher == nullptr)) {
+ init_thread_local_evp_cipher(evp_cipher, "AES-256-CBC");
+ }
+#else
+ const EVP_CIPHER *evp_cipher = EVP_aes_256_cbc();
+#endif
+ init(false, evp_cipher, key);
+ }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ void init_encrypt_ctr(Slice key) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher;
+ if (unlikely(evp_cipher == nullptr)) {
+ init_thread_local_evp_cipher(evp_cipher, "AES-256-CTR");
+ }
+#else
+ const EVP_CIPHER *evp_cipher = EVP_aes_256_ctr();
+#endif
+ init(true, evp_cipher, key);
+ }
+#endif
+
+ void init_iv(Slice iv) {
+ int res = EVP_CipherInit_ex(ctx_, nullptr, nullptr, nullptr, iv.ubegin(), -1);
+ LOG_IF(FATAL, res != 1);
+ }
+
+ void encrypt(const uint8 *src, uint8 *dst, int size) {
+ int len;
+ int res = EVP_EncryptUpdate(ctx_, dst, &len, src, size);
+ LOG_IF(FATAL, res != 1);
+ CHECK(len == size);
+ }
-static void aes_cbc_xcrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to, bool encrypt_flag) {
- AES_KEY key;
- int err;
- if (encrypt_flag) {
- err = AES_set_encrypt_key(aes_key.raw, 256, &key);
+ void decrypt(const uint8 *src, uint8 *dst, int size) {
+ CHECK(size % AES_BLOCK_SIZE == 0);
+ int len;
+ int res = EVP_DecryptUpdate(ctx_, dst, &len, src, size);
+ LOG_IF(FATAL, res != 1);
+ CHECK(len == size);
+ }
+
+ private:
+ EVP_CIPHER_CTX *ctx_{nullptr};
+
+ void init(bool is_encrypt, const EVP_CIPHER *evp_cipher, Slice key) {
+ int res = EVP_CipherInit_ex(ctx_, evp_cipher, nullptr, key.ubegin(), nullptr, is_encrypt ? 1 : 0);
+ LOG_IF(FATAL, res != 1);
+ EVP_CIPHER_CTX_set_padding(ctx_, 0);
+ }
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static void init_thread_local_evp_cipher(const EVP_CIPHER *&evp_cipher, const char *algorithm) {
+ evp_cipher = EVP_CIPHER_fetch(nullptr, algorithm, nullptr);
+ LOG_IF(FATAL, evp_cipher == nullptr);
+ detail::add_thread_local_destructor(create_destructor([&evp_cipher]() mutable {
+ EVP_CIPHER_free(const_cast<EVP_CIPHER *>(evp_cipher));
+ evp_cipher = nullptr;
+ }));
+ }
+#endif
+};
+
+struct AesState::Impl {
+ Evp evp;
+};
+
+AesState::AesState() = default;
+AesState::AesState(AesState &&from) noexcept = default;
+AesState &AesState::operator=(AesState &&from) noexcept = default;
+AesState::~AesState() = default;
+
+void AesState::init(Slice key, bool encrypt) {
+ CHECK(key.size() == 32);
+ if (!impl_) {
+ impl_ = make_unique<Impl>();
+ }
+ if (encrypt) {
+ impl_->evp.init_encrypt_ecb(key);
} else {
- err = AES_set_decrypt_key(aes_key.raw, 256, &key);
+ impl_->evp.init_decrypt_ecb(key);
}
- LOG_IF(FATAL, err != 0);
- CHECK(from.size() <= to.size());
- AES_cbc_encrypt(from.ubegin(), to.ubegin(), from.size(), &key, aes_iv->raw, encrypt_flag);
}
-void aes_cbc_encrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to) {
- aes_cbc_xcrypt(aes_key, aes_iv, from, to, true);
+void AesState::encrypt(const uint8 *src, uint8 *dst, int size) {
+ CHECK(impl_);
+ impl_->evp.encrypt(src, dst, size);
}
-void aes_cbc_decrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to) {
- aes_cbc_xcrypt(aes_key, aes_iv, from, to, false);
+void AesState::decrypt(const uint8 *src, uint8 *dst, int size) {
+ CHECK(impl_);
+ impl_->evp.decrypt(src, dst, size);
}
-class AesCtrState::Impl {
+class AesIgeStateImpl {
public:
- Impl(const UInt256 &key, const UInt128 &iv) {
- if (AES_set_encrypt_key(key.raw, 256, &aes_key) < 0) {
- LOG(FATAL) << "Failed to set encrypt key";
+ void init(Slice key, Slice iv, bool encrypt) {
+ CHECK(key.size() == 32);
+ CHECK(iv.size() == 32);
+ if (encrypt) {
+ evp_.init_encrypt_cbc(key);
+ } else {
+ evp_.init_decrypt_ecb(key);
}
- MutableSlice(counter, AES_BLOCK_SIZE).copy_from({iv.raw, AES_BLOCK_SIZE});
- current_pos = 0;
+
+ encrypted_iv_.load(iv.ubegin());
+ plaintext_iv_.load(iv.ubegin() + AES_BLOCK_SIZE);
+ }
+
+ void get_iv(MutableSlice iv) {
+ CHECK(iv.size() == 32);
+ encrypted_iv_.store(iv.ubegin());
+ plaintext_iv_.store(iv.ubegin() + AES_BLOCK_SIZE);
}
void encrypt(Slice from, MutableSlice to) {
+ CHECK(from.size() % AES_BLOCK_SIZE == 0);
CHECK(to.size() >= from.size());
- for (size_t i = 0; i < from.size(); i++) {
- if (current_pos == 0) {
- AES_encrypt(counter, encrypted_counter, &aes_key);
- for (int j = 15; j >= 0; j--) {
- if (++counter[j] != 0) {
- break;
- }
+ auto len = to.size() / AES_BLOCK_SIZE;
+ auto in = from.ubegin();
+ auto out = to.ubegin();
+
+ static constexpr size_t BLOCK_COUNT = 31;
+ while (len != 0) {
+ AesBlock data[BLOCK_COUNT];
+ AesBlock data_xored[BLOCK_COUNT];
+
+ auto count = td::min(BLOCK_COUNT, len);
+ std::memcpy(data, in, AES_BLOCK_SIZE * count);
+ data_xored[0] = data[0];
+ if (count > 1) {
+ data_xored[1] = plaintext_iv_ ^ data[1];
+ for (size_t i = 2; i < count; i++) {
+ data_xored[i] = data[i - 2] ^ data[i];
}
}
- to[i] = static_cast<char>(from[i] ^ encrypted_counter[current_pos]);
- current_pos = (current_pos + 1) & 15;
+
+ evp_.init_iv(encrypted_iv_.as_slice());
+ auto inlen = static_cast<int>(AES_BLOCK_SIZE * count);
+ evp_.encrypt(data_xored[0].raw(), data_xored[0].raw(), inlen);
+
+ data_xored[0] ^= plaintext_iv_;
+ for (size_t i = 1; i < count; i++) {
+ data_xored[i] ^= data[i - 1];
+ }
+ plaintext_iv_ = data[count - 1];
+ encrypted_iv_ = data_xored[count - 1];
+
+ std::memcpy(out, data_xored, AES_BLOCK_SIZE * count);
+ len -= count;
+ in += AES_BLOCK_SIZE * count;
+ out += AES_BLOCK_SIZE * count;
+ }
+ }
+
+ void decrypt(Slice from, MutableSlice to) {
+ CHECK(from.size() % AES_BLOCK_SIZE == 0);
+ CHECK(to.size() >= from.size());
+ auto len = to.size() / AES_BLOCK_SIZE;
+ auto in = from.ubegin();
+ auto out = to.ubegin();
+
+ AesBlock encrypted;
+
+ while (len) {
+ encrypted.load(in);
+
+ plaintext_iv_ ^= encrypted;
+ evp_.decrypt(plaintext_iv_.raw(), plaintext_iv_.raw(), AES_BLOCK_SIZE);
+ plaintext_iv_ ^= encrypted_iv_;
+
+ plaintext_iv_.store(out);
+ encrypted_iv_ = encrypted;
+
+ --len;
+ in += AES_BLOCK_SIZE;
+ out += AES_BLOCK_SIZE;
}
}
private:
- AES_KEY aes_key;
- uint8 counter[AES_BLOCK_SIZE];
- uint8 encrypted_counter[AES_BLOCK_SIZE];
- uint8 current_pos;
+ Evp evp_;
+ AesBlock encrypted_iv_;
+ AesBlock plaintext_iv_;
+};
+
+AesIgeState::AesIgeState() = default;
+AesIgeState::AesIgeState(AesIgeState &&from) noexcept = default;
+AesIgeState &AesIgeState::operator=(AesIgeState &&from) noexcept = default;
+AesIgeState::~AesIgeState() = default;
+
+void AesIgeState::init(Slice key, Slice iv, bool encrypt) {
+ if (!impl_) {
+ impl_ = make_unique<AesIgeStateImpl>();
+ }
+
+ impl_->init(key, iv, encrypt);
+}
+
+void AesIgeState::encrypt(Slice from, MutableSlice to) {
+ impl_->encrypt(from, to);
+}
+
+void AesIgeState::decrypt(Slice from, MutableSlice to) {
+ impl_->decrypt(from, to);
+}
+
+void aes_ige_encrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to) {
+ AesIgeStateImpl state;
+ state.init(aes_key, aes_iv, true);
+ state.encrypt(from, to);
+ state.get_iv(aes_iv);
+}
+
+void aes_ige_decrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to) {
+ AesIgeStateImpl state;
+ state.init(aes_key, aes_iv, false);
+ state.decrypt(from, to);
+ state.get_iv(aes_iv);
+}
+
+void aes_cbc_encrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to) {
+ CHECK(from.size() <= to.size());
+ CHECK(from.size() % 16 == 0);
+
+ Evp evp;
+ evp.init_encrypt_cbc(aes_key);
+ evp.init_iv(aes_iv);
+ evp.encrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size()));
+ aes_iv.copy_from(to.substr(from.size() - 16));
+}
+
+void aes_cbc_decrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to) {
+ CHECK(from.size() <= to.size());
+ CHECK(from.size() % 16 == 0);
+
+ Evp evp;
+ evp.init_decrypt_cbc(aes_key);
+ evp.init_iv(aes_iv);
+ aes_iv.copy_from(from.substr(from.size() - 16));
+ evp.decrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size()));
+}
+
+struct AesCbcState::Impl {
+ Evp evp_;
+};
+
+AesCbcState::AesCbcState(Slice key256, Slice iv128) : raw_{SecureString(key256), SecureString(iv128)} {
+ CHECK(raw_.key.size() == 32);
+ CHECK(raw_.iv.size() == 16);
+}
+
+AesCbcState::AesCbcState(AesCbcState &&from) noexcept = default;
+AesCbcState &AesCbcState::operator=(AesCbcState &&from) noexcept = default;
+AesCbcState::~AesCbcState() = default;
+
+void AesCbcState::encrypt(Slice from, MutableSlice to) {
+ if (from.empty()) {
+ return;
+ }
+
+ CHECK(from.size() <= to.size());
+ CHECK(from.size() % 16 == 0);
+ if (ctx_ == nullptr) {
+ ctx_ = make_unique<AesCbcState::Impl>();
+ ctx_->evp_.init_encrypt_cbc(raw_.key.as_slice());
+ ctx_->evp_.init_iv(raw_.iv.as_slice());
+ is_encrypt_ = true;
+ } else {
+ CHECK(is_encrypt_);
+ }
+ ctx_->evp_.encrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size()));
+ raw_.iv.as_mutable_slice().copy_from(to.substr(from.size() - 16));
+}
+
+void AesCbcState::decrypt(Slice from, MutableSlice to) {
+ if (from.empty()) {
+ return;
+ }
+
+ CHECK(from.size() <= to.size());
+ CHECK(from.size() % 16 == 0);
+ if (ctx_ == nullptr) {
+ ctx_ = make_unique<AesCbcState::Impl>();
+ ctx_->evp_.init_decrypt_cbc(raw_.key.as_slice());
+ ctx_->evp_.init_iv(raw_.iv.as_slice());
+ is_encrypt_ = false;
+ } else {
+ CHECK(!is_encrypt_);
+ }
+ raw_.iv.as_mutable_slice().copy_from(from.substr(from.size() - 16));
+ ctx_->evp_.decrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size()));
+}
+
+struct AesCtrState::Impl {
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ Evp evp_;
+#else
+ AES_KEY aes_key_;
+ uint8 counter_[AES_BLOCK_SIZE];
+ uint8 encrypted_counter_[AES_BLOCK_SIZE];
+ uint8 current_pos_;
+#endif
};
AesCtrState::AesCtrState() = default;
-AesCtrState::AesCtrState(AesCtrState &&from) = default;
-AesCtrState &AesCtrState::operator=(AesCtrState &&from) = default;
+AesCtrState::AesCtrState(AesCtrState &&from) noexcept = default;
+AesCtrState &AesCtrState::operator=(AesCtrState &&from) noexcept = default;
AesCtrState::~AesCtrState() = default;
-void AesCtrState::init(const UInt256 &key, const UInt128 &iv) {
- ctx_ = std::make_unique<AesCtrState::Impl>(key, iv);
+void AesCtrState::init(Slice key, Slice iv) {
+ CHECK(key.size() == 32);
+ CHECK(iv.size() == 16);
+ ctx_ = make_unique<AesCtrState::Impl>();
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ ctx_->evp_.init_encrypt_ctr(key);
+ ctx_->evp_.init_iv(iv);
+#else
+ if (AES_set_encrypt_key(key.ubegin(), 256, &ctx_->aes_key_) < 0) {
+ LOG(FATAL) << "Failed to set encrypt key";
+ }
+ MutableSlice(ctx_->counter_, AES_BLOCK_SIZE).copy_from(iv);
+ ctx_->current_pos_ = 0;
+#endif
}
void AesCtrState::encrypt(Slice from, MutableSlice to) {
- ctx_->encrypt(from, to);
+ CHECK(from.size() <= to.size());
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ ctx_->evp_.encrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size()));
+#else
+ auto from_ptr = from.ubegin();
+ auto to_ptr = to.ubegin();
+ for (size_t i = 0; i < from.size(); i++) {
+ if (ctx_->current_pos_ == 0) {
+ AES_encrypt(ctx_->counter_, ctx_->encrypted_counter_, &ctx_->aes_key_);
+ for (int j = 15; j >= 0; j--) {
+ if (++ctx_->counter_[j] != 0) {
+ break;
+ }
+ }
+ }
+ to_ptr[i] = static_cast<uint8>(from_ptr[i] ^ ctx_->encrypted_counter_[ctx_->current_pos_]);
+ ctx_->current_pos_ = (ctx_->current_pos_ + 1) & 15;
+ }
+#endif
}
void AesCtrState::decrypt(Slice from, MutableSlice to) {
- encrypt(from, to); // it is the same as decrypt
+ encrypt(from, to);
}
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+static void make_digest(Slice data, MutableSlice output, const EVP_MD *evp_md) {
+ static TD_THREAD_LOCAL EVP_MD_CTX *ctx;
+ if (unlikely(ctx == nullptr)) {
+ ctx = EVP_MD_CTX_new();
+ LOG_IF(FATAL, ctx == nullptr);
+ detail::add_thread_local_destructor(create_destructor([] {
+ EVP_MD_CTX_free(ctx);
+ ctx = nullptr;
+ }));
+ }
+ int res = EVP_DigestInit_ex(ctx, evp_md, nullptr);
+ LOG_IF(FATAL, res != 1);
+ res = EVP_DigestUpdate(ctx, data.ubegin(), data.size());
+ LOG_IF(FATAL, res != 1);
+ res = EVP_DigestFinal_ex(ctx, output.ubegin(), nullptr);
+ LOG_IF(FATAL, res != 1);
+ EVP_MD_CTX_reset(ctx);
+}
+
+static void init_thread_local_evp_md(const EVP_MD *&evp_md, const char *algorithm) {
+ evp_md = EVP_MD_fetch(nullptr, algorithm, nullptr);
+ LOG_IF(FATAL, evp_md == nullptr);
+ detail::add_thread_local_destructor(create_destructor([&evp_md]() mutable {
+ EVP_MD_free(const_cast<EVP_MD *>(evp_md));
+ evp_md = nullptr;
+ }));
+}
+#endif
+
void sha1(Slice data, unsigned char output[20]) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_MD *evp_md;
+ if (unlikely(evp_md == nullptr)) {
+ init_thread_local_evp_md(evp_md, "sha1");
+ }
+ make_digest(data, MutableSlice(output, 20), evp_md);
+#else
auto result = SHA1(data.ubegin(), data.size(), output);
CHECK(result == output);
+#endif
}
void sha256(Slice data, MutableSlice output) {
CHECK(output.size() >= 32);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_MD *evp_md;
+ if (unlikely(evp_md == nullptr)) {
+ init_thread_local_evp_md(evp_md, "sha256");
+ }
+ make_digest(data, output, evp_md);
+#else
auto result = SHA256(data.ubegin(), data.size(), output.ubegin());
CHECK(result == output.ubegin());
+#endif
+}
+
+void sha512(Slice data, MutableSlice output) {
+ CHECK(output.size() >= 64);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_MD *evp_md;
+ if (unlikely(evp_md == nullptr)) {
+ init_thread_local_evp_md(evp_md, "sha512");
+ }
+ make_digest(data, output, evp_md);
+#else
+ auto result = SHA512(data.ubegin(), data.size(), output.ubegin());
+ CHECK(result == output.ubegin());
+#endif
}
-struct Sha256StateImpl {
- SHA256_CTX ctx;
+string sha1(Slice data) {
+ string result(20, '\0');
+ sha1(data, MutableSlice(result).ubegin());
+ return result;
+}
+
+string sha256(Slice data) {
+ string result(32, '\0');
+ sha256(data, result);
+ return result;
+}
+
+string sha512(Slice data) {
+ string result(64, '\0');
+ sha512(data, result);
+ return result;
+}
+
+class Sha256State::Impl {
+ public:
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ EVP_MD_CTX *ctx_;
+
+ Impl() {
+ ctx_ = EVP_MD_CTX_new();
+ LOG_IF(FATAL, ctx_ == nullptr);
+ }
+ ~Impl() {
+ CHECK(ctx_ != nullptr);
+ EVP_MD_CTX_free(ctx_);
+ }
+#else
+ SHA256_CTX ctx_;
+ Impl() = default;
+ ~Impl() = default;
+#endif
+
+ Impl(const Impl &from) = delete;
+ Impl &operator=(const Impl &from) = delete;
+ Impl(Impl &&from) = delete;
+ Impl &operator=(Impl &&from) = delete;
};
Sha256State::Sha256State() = default;
-Sha256State::Sha256State(Sha256State &&from) = default;
-Sha256State &Sha256State::operator=(Sha256State &&from) = default;
-Sha256State::~Sha256State() = default;
-void sha256_init(Sha256State *state) {
- state->impl = std::make_unique<Sha256StateImpl>();
- int err = SHA256_Init(&state->impl->ctx);
+Sha256State::Sha256State(Sha256State &&other) noexcept {
+ impl_ = std::move(other.impl_);
+ is_inited_ = other.is_inited_;
+ other.is_inited_ = false;
+}
+
+Sha256State &Sha256State::operator=(Sha256State &&other) noexcept {
+ Sha256State copy(std::move(other));
+ using std::swap;
+ swap(impl_, copy.impl_);
+ swap(is_inited_, copy.is_inited_);
+ return *this;
+}
+
+Sha256State::~Sha256State() {
+ if (is_inited_) {
+ char result[32];
+ extract(MutableSlice{result, 32});
+ CHECK(!is_inited_);
+ }
+}
+
+void Sha256State::init() {
+ if (!impl_) {
+ impl_ = make_unique<Sha256State::Impl>();
+ }
+ CHECK(!is_inited_);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_MD *evp_md;
+ if (unlikely(evp_md == nullptr)) {
+ init_thread_local_evp_md(evp_md, "sha256");
+ }
+ int err = EVP_DigestInit_ex(impl_->ctx_, evp_md, nullptr);
+#else
+ int err = SHA256_Init(&impl_->ctx_);
+#endif
LOG_IF(FATAL, err != 1);
+ is_inited_ = true;
}
-void sha256_update(Slice data, Sha256State *state) {
- CHECK(state->impl);
- int err = SHA256_Update(&state->impl->ctx, data.ubegin(), data.size());
+void Sha256State::feed(Slice data) {
+ CHECK(impl_);
+ CHECK(is_inited_);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ int err = EVP_DigestUpdate(impl_->ctx_, data.ubegin(), data.size());
+#else
+ int err = SHA256_Update(&impl_->ctx_, data.ubegin(), data.size());
+#endif
LOG_IF(FATAL, err != 1);
}
-void sha256_final(Sha256State *state, MutableSlice output) {
+void Sha256State::extract(MutableSlice output, bool destroy) {
CHECK(output.size() >= 32);
- CHECK(state->impl);
- int err = SHA256_Final(output.ubegin(), &state->impl->ctx);
+ CHECK(impl_);
+ CHECK(is_inited_);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ int err = EVP_DigestFinal_ex(impl_->ctx_, output.ubegin(), nullptr);
+#else
+ int err = SHA256_Final(output.ubegin(), &impl_->ctx_);
+#endif
LOG_IF(FATAL, err != 1);
- state->impl.reset();
+ is_inited_ = false;
+ if (destroy) {
+ impl_.reset();
+ }
}
-/*** md5 ***/
void md5(Slice input, MutableSlice output) {
- CHECK(output.size() >= MD5_DIGEST_LENGTH);
+ CHECK(output.size() >= 16);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ static TD_THREAD_LOCAL const EVP_MD *evp_md;
+ if (unlikely(evp_md == nullptr)) {
+ init_thread_local_evp_md(evp_md, "md5");
+ }
+ make_digest(input, output, evp_md);
+#else
auto result = MD5(input.ubegin(), input.size(), output.ubegin());
CHECK(result == output.ubegin());
+#endif
}
-void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice dest) {
- CHECK(dest.size() == 256 / 8) << dest.size();
- CHECK(iteration_count > 0);
- auto evp_md = EVP_sha256();
+static void pbkdf2_impl(Slice password, Slice salt, int iteration_count, MutableSlice dest, const EVP_MD *evp_md) {
CHECK(evp_md != nullptr);
+ int hash_size = EVP_MD_size(evp_md);
+ CHECK(dest.size() == static_cast<size_t>(hash_size));
+ CHECK(iteration_count > 0);
#if OPENSSL_VERSION_NUMBER < 0x10000000L
HMAC_CTX ctx;
HMAC_CTX_init(&ctx);
unsigned char counter[4] = {0, 0, 0, 1};
- int password_len = narrow_cast<int>(password.size());
+ auto password_len = narrow_cast<int>(password.size());
HMAC_Init_ex(&ctx, password.data(), password_len, evp_md, nullptr);
HMAC_Update(&ctx, salt.ubegin(), narrow_cast<int>(salt.size()));
HMAC_Update(&ctx, counter, 4);
@@ -393,14 +934,15 @@ void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice
HMAC_CTX_cleanup(&ctx);
if (iteration_count > 1) {
- unsigned char buf[32];
+ CHECK(hash_size <= 64);
+ unsigned char buf[64];
std::copy(dest.ubegin(), dest.uend(), buf);
for (int iter = 1; iter < iteration_count; iter++) {
- if (HMAC(evp_md, password.data(), password_len, buf, 32, buf, nullptr) == nullptr) {
+ if (HMAC(evp_md, password.data(), password_len, buf, hash_size, buf, nullptr) == nullptr) {
LOG(FATAL) << "Failed to HMAC";
}
- for (int i = 0; i < 32; i++) {
- dest[i] ^= buf[i];
+ for (int i = 0; i < hash_size; i++) {
+ dest[i] = static_cast<unsigned char>(dest[i] ^ buf[i]);
}
}
}
@@ -412,14 +954,179 @@ void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice
#endif
}
-void hmac_sha256(Slice key, Slice message, MutableSlice dest) {
- CHECK(dest.size() == 256 / 8);
+void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice dest) {
+ pbkdf2_impl(password, salt, iteration_count, dest, EVP_sha256());
+}
+
+void pbkdf2_sha512(Slice password, Slice salt, int iteration_count, MutableSlice dest) {
+ pbkdf2_impl(password, salt, iteration_count, dest, EVP_sha512());
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+static void hmac_impl(const char *digest, Slice key, Slice message, MutableSlice dest) {
+ EVP_MAC *hmac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
+ LOG_IF(FATAL, hmac == nullptr);
+
+ EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(hmac);
+ LOG_IF(FATAL, ctx == nullptr);
+
+ OSSL_PARAM params[2];
+ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, const_cast<char *>(digest), 0);
+ params[1] = OSSL_PARAM_construct_end();
+
+ int res = EVP_MAC_init(ctx, const_cast<unsigned char *>(key.ubegin()), key.size(), params);
+ LOG_IF(FATAL, res != 1);
+ res = EVP_MAC_update(ctx, message.ubegin(), message.size());
+ LOG_IF(FATAL, res != 1);
+ res = EVP_MAC_final(ctx, dest.ubegin(), nullptr, dest.size());
+ LOG_IF(FATAL, res != 1);
+
+ EVP_MAC_CTX_free(ctx);
+ EVP_MAC_free(hmac);
+}
+#else
+static void hmac_impl(const EVP_MD *evp_md, Slice key, Slice message, MutableSlice dest) {
unsigned int len = 0;
- auto result = HMAC(EVP_sha256(), key.ubegin(), narrow_cast<int>(key.size()), message.ubegin(),
+ auto result = HMAC(evp_md, key.ubegin(), narrow_cast<int>(key.size()), message.ubegin(),
narrow_cast<int>(message.size()), dest.ubegin(), &len);
CHECK(result == dest.ubegin());
CHECK(len == dest.size());
}
+#endif
+
+void hmac_sha256(Slice key, Slice message, MutableSlice dest) {
+ CHECK(dest.size() == 256 / 8);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ hmac_impl("SHA256", key, message, dest);
+#else
+ hmac_impl(EVP_sha256(), key, message, dest);
+#endif
+}
+
+void hmac_sha512(Slice key, Slice message, MutableSlice dest) {
+ CHECK(dest.size() == 512 / 8);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ hmac_impl("SHA512", key, message, dest);
+#else
+ hmac_impl(EVP_sha512(), key, message, dest);
+#endif
+}
+
+static int get_evp_pkey_type(EVP_PKEY *pkey) {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ return EVP_PKEY_type(pkey->type);
+#else
+ return EVP_PKEY_base_id(pkey);
+#endif
+}
+
+Result<BufferSlice> rsa_encrypt_pkcs1_oaep(Slice public_key, Slice data) {
+ BIO *mem_bio = BIO_new_mem_buf(const_cast<void *>(static_cast<const void *>(public_key.data())),
+ narrow_cast<int>(public_key.size()));
+ SCOPE_EXIT {
+ BIO_vfree(mem_bio);
+ };
+
+ EVP_PKEY *pkey = PEM_read_bio_PUBKEY(mem_bio, nullptr, nullptr, nullptr);
+ if (!pkey) {
+ return Status::Error("Cannot read public key");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_free(pkey);
+ };
+ if (get_evp_pkey_type(pkey) != EVP_PKEY_RSA) {
+ return Status::Error("Wrong key type, expected RSA");
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10000000L
+ RSA *rsa = pkey->pkey.rsa;
+ int outlen = RSA_size(rsa);
+ BufferSlice res(outlen);
+ if (RSA_public_encrypt(narrow_cast<int>(data.size()), const_cast<unsigned char *>(data.ubegin()),
+ res.as_slice().ubegin(), rsa, RSA_PKCS1_OAEP_PADDING) != outlen) {
+#else
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, nullptr);
+ if (!ctx) {
+ return Status::Error("Cannot create EVP_PKEY_CTX");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_CTX_free(ctx);
+ };
+
+ if (EVP_PKEY_encrypt_init(ctx) <= 0) {
+ return Status::Error("Cannot init EVP_PKEY_CTX");
+ }
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
+ return Status::Error("Cannot set RSA_PKCS1_OAEP padding in EVP_PKEY_CTX");
+ }
+
+ size_t outlen;
+ if (EVP_PKEY_encrypt(ctx, nullptr, &outlen, data.ubegin(), data.size()) <= 0) {
+ return Status::Error("Cannot calculate encrypted length");
+ }
+ BufferSlice res(outlen);
+ if (EVP_PKEY_encrypt(ctx, res.as_slice().ubegin(), &outlen, data.ubegin(), data.size()) <= 0) {
+#endif
+ return Status::Error("Cannot encrypt");
+ }
+ return std::move(res);
+}
+
+Result<BufferSlice> rsa_decrypt_pkcs1_oaep(Slice private_key, Slice data) {
+ BIO *mem_bio = BIO_new_mem_buf(const_cast<void *>(static_cast<const void *>(private_key.data())),
+ narrow_cast<int>(private_key.size()));
+ SCOPE_EXIT {
+ BIO_vfree(mem_bio);
+ };
+
+ EVP_PKEY *pkey = PEM_read_bio_PrivateKey(mem_bio, nullptr, nullptr, nullptr);
+ if (!pkey) {
+ return Status::Error("Cannot read private key");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_free(pkey);
+ };
+ if (get_evp_pkey_type(pkey) != EVP_PKEY_RSA) {
+ return Status::Error("Wrong key type, expected RSA");
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10000000L
+ RSA *rsa = pkey->pkey.rsa;
+ size_t outlen = RSA_size(rsa);
+ BufferSlice res(outlen);
+ auto inlen = RSA_private_decrypt(narrow_cast<int>(data.size()), const_cast<unsigned char *>(data.ubegin()),
+ res.as_slice().ubegin(), rsa, RSA_PKCS1_OAEP_PADDING);
+ if (inlen == -1) {
+ return Status::Error("Cannot decrypt");
+ }
+ res.truncate(inlen);
+#else
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, nullptr);
+ if (!ctx) {
+ return Status::Error("Cannot create EVP_PKEY_CTX");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_CTX_free(ctx);
+ };
+
+ if (EVP_PKEY_decrypt_init(ctx) <= 0) {
+ return Status::Error("Cannot init EVP_PKEY_CTX");
+ }
+ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
+ return Status::Error("Cannot set RSA_PKCS1_OAEP padding in EVP_PKEY_CTX");
+ }
+
+ size_t outlen;
+ if (EVP_PKEY_decrypt(ctx, nullptr, &outlen, data.ubegin(), data.size()) <= 0) {
+ return Status::Error("Cannot calculate decrypted length");
+ }
+ BufferSlice res(outlen);
+ if (EVP_PKEY_decrypt(ctx, res.as_slice().ubegin(), &outlen, data.ubegin(), data.size()) <= 0) {
+ return Status::Error("Cannot decrypt");
+ }
+#endif
+ return std::move(res);
+}
#if OPENSSL_VERSION_NUMBER < 0x10100000L
namespace {
@@ -456,6 +1163,8 @@ void openssl_locking_function(int mode, int n, const char *file, int line) {
void init_openssl_threads() {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ static std::mutex init_mutex;
+ std::lock_guard<std::mutex> lock(init_mutex);
if (CRYPTO_get_locking_callback() == nullptr) {
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
CRYPTO_THREADID_set_callback(openssl_threadid_callback);
@@ -464,6 +1173,38 @@ void init_openssl_threads() {
}
#endif
}
+
+Status create_openssl_error(int code, Slice message) {
+ const int max_result_size = 1 << 12;
+ auto result = StackAllocator::alloc(max_result_size);
+ StringBuilder sb(result.as_slice());
+
+ sb << message;
+ while (unsigned long error_code = ERR_get_error()) {
+ char error_buf[1024];
+ ERR_error_string_n(error_code, error_buf, sizeof(error_buf));
+ Slice error(error_buf, std::strlen(error_buf));
+ sb << "{" << error << "}";
+ }
+ LOG_IF(ERROR, sb.is_error()) << "OpenSSL error buffer overflow";
+ LOG(DEBUG) << sb.as_cslice();
+ return Status::Error(code, sb.as_cslice());
+}
+
+void clear_openssl_errors(Slice source) {
+ if (ERR_peek_error() != 0) {
+ auto error = create_openssl_error(0, "Unprocessed OPENSSL_ERROR");
+ if (!ends_with(error.message(), ":def_load:system lib}")) {
+ LOG(ERROR) << source << ": " << error;
+ }
+ }
+#if TD_PORT_WINDOWS
+ WSASetLastError(0);
+#else
+ errno = 0;
+#endif
+}
+
#endif
#if TD_HAVE_ZLIB
@@ -472,6 +1213,68 @@ uint32 crc32(Slice data) {
}
#endif
+#if TD_HAVE_CRC32C
+uint32 crc32c(Slice data) {
+ return crc32c::Crc32c(data.data(), data.size());
+}
+
+uint32 crc32c_extend(uint32 old_crc, Slice data) {
+ return crc32c::Extend(old_crc, data.ubegin(), data.size());
+}
+
+namespace {
+
+uint32 gf32_matrix_times(const uint32 *matrix, uint32 vector) {
+ uint32 sum = 0;
+ while (vector) {
+ if (vector & 1) {
+ sum ^= *matrix;
+ }
+ vector >>= 1;
+ matrix++;
+ }
+ return sum;
+}
+
+void gf32_matrix_square(uint32 *square, const uint32 *matrix) {
+ for (int n = 0; n < 32; n++) {
+ square[n] = gf32_matrix_times(matrix, matrix[n]);
+ }
+}
+
+} // namespace
+
+uint32 crc32c_extend(uint32 old_crc, uint32 data_crc, size_t data_size) {
+ static uint32 power_buf_raw[1024];
+ static const uint32 *power_buf = [&] {
+ auto *buf = power_buf_raw;
+ buf[0] = 0x82F63B78u;
+ for (int n = 0; n < 31; n++) {
+ buf[n + 1] = 1u << n;
+ }
+ for (int n = 1; n < 32; n++) {
+ gf32_matrix_square(buf + (n << 5), buf + ((n - 1) << 5));
+ }
+ return buf;
+ }();
+
+ if (data_size == 0) {
+ return old_crc;
+ }
+
+ const uint32 *p = power_buf + 64;
+ do {
+ p += 32;
+ if (data_size & 1) {
+ old_crc = gf32_matrix_times(p, old_crc);
+ }
+ data_size >>= 1;
+ } while (data_size != 0);
+ return old_crc ^ data_crc;
+}
+
+#endif
+
static const uint64 crc64_table[256] = {
0x0000000000000000, 0xb32e4cbe03a75f6f, 0xf4843657a840a05b, 0x47aa7ae9abe7ff34, 0x7bd0c384ff8f5e33,
0xc8fe8f3afc28015c, 0x8f54f5d357cffe68, 0x3c7ab96d5468a107, 0xf7a18709ff1ebc66, 0x448fcbb7fcb9e309,
@@ -538,4 +1341,34 @@ uint64 crc64(Slice data) {
return crc64_partial(data, static_cast<uint64>(-1)) ^ static_cast<uint64>(-1);
}
+static const uint16 crc16_table[256] = {
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad,
+ 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a,
+ 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b,
+ 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
+ 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861,
+ 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96,
+ 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87,
+ 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
+ 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,
+ 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3,
+ 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290,
+ 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
+ 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e,
+ 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f,
+ 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
+ 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
+ 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83,
+ 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
+ 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0};
+
+uint16 crc16(Slice data) {
+ uint32 crc = 0;
+ for (auto c : data) {
+ auto t = (static_cast<unsigned char>(c) ^ (crc >> 8)) & 0xff;
+ crc = crc16_table[t] ^ (crc << 8);
+ }
+ return static_cast<uint16>(crc);
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.h
index 23ac694bfb..d4c2558035 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.h
@@ -1,13 +1,16 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/utils/buffer.h"
#include "td/utils/common.h"
+#include "td/utils/SharedSlice.h"
#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
namespace td {
@@ -18,62 +21,164 @@ void init_crypto();
int pq_factorize(Slice pq_str, string *p_str, string *q_str);
-void aes_ige_encrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to);
-void aes_ige_decrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to);
+class AesState {
+ public:
+ AesState();
+ AesState(const AesState &from) = delete;
+ AesState &operator=(const AesState &from) = delete;
+ AesState(AesState &&from) noexcept;
+ AesState &operator=(AesState &&from) noexcept;
+ ~AesState();
+
+ void init(Slice key, bool encrypt);
+
+ void encrypt(const uint8 *src, uint8 *dst, int size);
+
+ void decrypt(const uint8 *src, uint8 *dst, int size);
+
+ private:
+ struct Impl;
+ unique_ptr<Impl> impl_;
+};
+
+void aes_ige_encrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to);
+void aes_ige_decrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to);
+
+class AesIgeStateImpl;
+
+class AesIgeState {
+ public:
+ AesIgeState();
+ AesIgeState(const AesIgeState &from) = delete;
+ AesIgeState &operator=(const AesIgeState &from) = delete;
+ AesIgeState(AesIgeState &&from) noexcept;
+ AesIgeState &operator=(AesIgeState &&from) noexcept;
+ ~AesIgeState();
+
+ void init(Slice key, Slice iv, bool encrypt);
-void aes_cbc_encrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to);
-void aes_cbc_decrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to);
+ void encrypt(Slice from, MutableSlice to);
+
+ void decrypt(Slice from, MutableSlice to);
+
+ private:
+ unique_ptr<AesIgeStateImpl> impl_;
+};
+
+void aes_cbc_encrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to);
+void aes_cbc_decrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to);
class AesCtrState {
public:
AesCtrState();
AesCtrState(const AesCtrState &from) = delete;
AesCtrState &operator=(const AesCtrState &from) = delete;
- AesCtrState(AesCtrState &&from);
- AesCtrState &operator=(AesCtrState &&from);
+ AesCtrState(AesCtrState &&from) noexcept;
+ AesCtrState &operator=(AesCtrState &&from) noexcept;
~AesCtrState();
- void init(const UInt256 &key, const UInt128 &iv);
+ void init(Slice key, Slice iv);
void encrypt(Slice from, MutableSlice to);
void decrypt(Slice from, MutableSlice to);
private:
- class Impl;
- std::unique_ptr<Impl> ctx_;
+ struct Impl;
+ unique_ptr<Impl> ctx_;
+};
+
+class AesCbcState {
+ public:
+ AesCbcState(Slice key256, Slice iv128);
+ AesCbcState(const AesCbcState &from) = delete;
+ AesCbcState &operator=(const AesCbcState &from) = delete;
+ AesCbcState(AesCbcState &&from) noexcept;
+ AesCbcState &operator=(AesCbcState &&from) noexcept;
+ ~AesCbcState();
+
+ void encrypt(Slice from, MutableSlice to);
+ void decrypt(Slice from, MutableSlice to);
+
+ struct Raw {
+ SecureString key;
+ SecureString iv;
+ };
+ const Raw &raw() const {
+ return raw_;
+ }
+
+ private:
+ struct Impl;
+ unique_ptr<Impl> ctx_;
+
+ Raw raw_;
+ bool is_encrypt_ = false;
};
void sha1(Slice data, unsigned char output[20]);
void sha256(Slice data, MutableSlice output);
-struct Sha256StateImpl;
+void sha512(Slice data, MutableSlice output);
-struct Sha256State {
+string sha1(Slice data) TD_WARN_UNUSED_RESULT;
+
+string sha256(Slice data) TD_WARN_UNUSED_RESULT;
+
+string sha512(Slice data) TD_WARN_UNUSED_RESULT;
+
+class Sha256State {
+ public:
Sha256State();
- Sha256State(Sha256State &&from);
- Sha256State &operator=(Sha256State &&from);
+ Sha256State(const Sha256State &other) = delete;
+ Sha256State &operator=(const Sha256State &other) = delete;
+ Sha256State(Sha256State &&other) noexcept;
+ Sha256State &operator=(Sha256State &&other) noexcept;
~Sha256State();
- std::unique_ptr<Sha256StateImpl> impl;
-};
-void sha256_init(Sha256State *state);
-void sha256_update(Slice data, Sha256State *state);
-void sha256_final(Sha256State *state, MutableSlice output);
+ void init();
+
+ void feed(Slice data);
+
+ void extract(MutableSlice output, bool destroy = false);
+
+ private:
+ class Impl;
+ unique_ptr<Impl> impl_;
+ bool is_inited_ = false;
+};
void md5(Slice input, MutableSlice output);
void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice dest);
+void pbkdf2_sha512(Slice password, Slice salt, int iteration_count, MutableSlice dest);
+
void hmac_sha256(Slice key, Slice message, MutableSlice dest);
+void hmac_sha512(Slice key, Slice message, MutableSlice dest);
+
+// Interface may be improved
+Result<BufferSlice> rsa_encrypt_pkcs1_oaep(Slice public_key, Slice data);
+Result<BufferSlice> rsa_decrypt_pkcs1_oaep(Slice private_key, Slice data);
void init_openssl_threads();
+
+Status create_openssl_error(int code, Slice message);
+
+void clear_openssl_errors(Slice source);
#endif
#if TD_HAVE_ZLIB
uint32 crc32(Slice data);
#endif
+#if TD_HAVE_CRC32C
+uint32 crc32c(Slice data);
+uint32 crc32c_extend(uint32 old_crc, Slice data);
+uint32 crc32c_extend(uint32 old_crc, uint32 new_crc, size_t data_size);
+#endif
+
uint64 crc64(Slice data);
+uint16 crc16(Slice data);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.cpp
new file mode 100644
index 0000000000..c5f47d8ac9
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.cpp
@@ -0,0 +1,302 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/emoji.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/Gzip.h"
+
+namespace td {
+
+bool is_emoji(Slice str) {
+ constexpr size_t MAX_EMOJI_LENGTH = 35;
+ static const FlatHashSet<Slice, SliceHash> emojis = [max_emoji_length = MAX_EMOJI_LENGTH] {
+#if TD_HAVE_ZLIB
+ Slice packed_emojis(
+ "eJyNnety27i2rV8lVfvf-XXul7frVmfZ6cS6kJJskrLj7tUUaYmOI1uObcUEWLUfJecBdledFzgaJIfJiUkyqyqAJ4wxJzE_QABF2fGPs-"
+ "jdj7PVux_TzbFsjyU7lttj2R3L_t2PC3Ms9t2PYHEsy3c_wg_H8vuxfDyWT8dydizjY5kcy_RYZsfiHYt_LPNjKd79iI5foz-P5Rg_"
+ "OsaPjjGjY8zV-2P5x7EcfVfH-Ktj_-rhWI7XXX07lu_HctReHjWXx-tdHq93efPux9XxmldHn6tj3Kuj39VxXFcX7358Pn79fH4ssI_j_"
+ "3y89u0xt9vLdz--HMf1Zfnu7-tf3h-r36bH6mSCykPlo5qjWqCC7uQc1QWqAFWIKjpWH35FVVpfUT2g2qN6RPUN1ROqF1QHVN-P1e8zVLja2S-"
+ "oEOVshOo3VBjV2T9QnaA6RfUB1e-oPqL6hOoM1RgVRn-GPM4Q-ayMjDzOkMcZ8jhDHmfI4wx5nCGPM-RxhtEfZ_"
+ "1YXaK6QvUZ1TWqP1B9QXWHClme7VDdo0K-Z8j3DPmeId-zZ1TI9wz5niHfs1dUOSqDyqIqjtUY6Y-R_hjpj5H-GOmPkf4Y6Y-R_hjpj5H-"
+ "GOmPkf4Y6Y-R_hjpj5H-GOmPkf4Y6Y-R_hjpj5H-GOmPkf4Y6Y-R_hjpj5H-GOmPkf4Y6Y-R_hjpj_"
+ "9E9U9Uf6GKUa1RJahSVDeoNqi2qDJUt6iAbgx0Y6AbA90Y6MZANwa6MdCNgW6MpTIGvzH4jcFvDH5j8BuD3xjoxkA3AboJ0E2AbgJ0E6CbAN0E"
+ "6CZANwG6CdBNgG4CdBOgmwDdBOgmQDcBugnQTYBuAnQTpD9B-hOkP0H6E6Q_QfoTpD9B-hOkP0H6E6Q_QfoTpD9B-hOkP0H6E6Q_QfoTpD9B-"
+ "hOkP0H6E6Q_QfoTpD9B-hOkP0H6E6Q_wfKZgMEEDKZgMAWDKRhMwWAKBlMwmILBFAymYDAFgykYTJHvFPlOke8U-U6R7xT5TpHvFPlOke8U-"
+ "U6R7xT5TpHvFPlOke8U-U6R7xT5TpHvFPlOke8U-"
+ "U6R4BQZTZHRFBlNkdEUGU2R0RQZTZHRDBnNkNEMGc2Q0QwZzZDRDBnNkNEMGc2Q0QwZzTCrM8zqDLM6w6zOkOUMWc6Q5QxZzpDlDC-"
+ "IGV4QM7wgZnhBzPCCmOEFMcMLYoYXxAwviBleEDO8IGZ4QcxAaAZCMxCagdAMhGYgNAOhGQjNQGgGQjMQmoHQDIRmIDQDoRkIzUBohhUxw4qYY"
+ "UXMAGyGFTHDiphhRczAbwZ-M_Cbgd8M_GbgNwM_D-"
+ "g8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8"
+ "UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9S8khpWnQ90PladD34--Png54OfD34--"
+ "Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--"
+ "Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--M3Bbw5-c_Cbg98c_ObgNwe_"
+ "OfjNwW8OfnPwm4PfHPzm4DcHvzn4zcFvDn5z8JuD3xz85uA3B785-M3Bbw5-c_Cbg98c_ObgNwe_"
+ "OfjNwW8OfnPwm4PfHPzm4DcHvzn4zcFvDn5z8JuD3xz85uA3B785-M3Bbw5-c_Cbg98c_ObgNwe_"
+ "OfjNwW8OfnPwm4PfHPzmoLYAtQWoLUBtAWoLUFuA2gLUFqC2ALUFqC1AbQFqC1BbgNoC1BagtgC1BagtQG0BagtQW4DaAtQWoLYAtQWoLUBtAW"
+ "oLUFuA2gLUFqC2ALUFqC1AbQFqC1BbgNoC1BagtgC1BagtQG0BagtQW4DaAtQWoLYAtQWoLUBtAWoLUFuA2gLUFqC2ALUFqC1AbQFqC1BbYNUt"
+ "kf4S6S-R_hLpL5H5EpkvkfkSmS-R-RKZL5H5EpkvkfkSmS-R-RKZL5H5EpkvkfkSmS-R-RKZL5H5EpkvkfkSmS-R-RKZLzG-c1zjHNc4h-"
+ "QCw73AcC8w3Au8SC4w3QGmO8B0B5juANMdYLoDTHeA6Q4w3QGmO8B0B5juAPkGyDdAvgHyDTDdAZIOkHSApAMkHSDpAAMKMKAASQdIOkDSAZIO"
+ "kHSApAMkHSDpAEkHSDpA0gGSDpB0gIwCJB0g6QBJB5juANMdYLoDTHeA6Q4w3QGmO8B0B5juANMdYLoDTHeA6Q4w3QGmO8B0B5juANMdAGcAfg"
+ "H4BeAXgF8AfiH4heAXgl8IfiH4heAXgl8IfiH4heAXgl8IfiH4heAXgl8IfhHiRYgXIV6EeBHiRYgXIV6EeBHiRYgXIV6EeBHiRYgXIV5UxsN8"
+ "RJiPCPMRYT4izEeE-YgwHxHmI8J8RJiPCPMRYT4izEeE-YgwHxHmI8J8RJiPCPMRYT4izEeE-YgwHxHmI8J8RJiPCPMRYT4izEeE-"
+ "YgwHxHmI8J8RJiPCPMRYT4izEeE-YgwHxHmI8J8RJiPCPMRYT4izEeE-YgwHxHmYwV-K_Bbgd8K_FbgtwK_"
+ "FQitgGQFJCsgWQHECiBWALFC0iskvULSKyS9wuhXGP0KQ1thaCsMbYWhrTC0FYa2wtBWGNoKQ7sGq2uwugara7C6BqtrsLoGq2uwugara7C6Bq"
+ "trXO0aXGIMN8aExpjQGBMaY_"
+ "QxRh9j9DEmNMaExsgjRh4x8ogxoTEmNMaExpjQGLnFyC1GbjFyizHIGIOMMcgYg4wxyBiDjDHIGIOMMcgYg4wxyBiDjIEkxoTGmNAYExqXA8eE"
+ "xpjQGBMag1oMajGoxaAWg1oMajGoxQAWYy5jzGWMuVxjLteYyzXmco25XGMu15jLNV4Ga7wM1ngZrPEyWONlsAa1NaitQW0NamtQW4PaGtTWoL"
+ "YGtTWorUFtDWprUFuD2hrU1qC2BrU1qK1BbQ1qa1Bbg9oa1Nagtga1NaitQW0NamtQW4PaGtTWoLYGtTWorUFtDWprUFuD2hrU1qC2BrU1qK1B"
+ "bQ1qa1Bbg9oa1NZYa2ugWwPdGujWQJcAXQJ0CdAlQJcAXQJ0CbaRBPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_"
+ "BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_"
+ "BPwS8EvALwG_BPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_"
+ "FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_"
+ "FPw2iLdBvA3ibRBvg3gbxNsg3gbxNoi3QbwN4m0Qb4N4W0zAFhOwxQRsMQFbTMAWE7DFBGwxAVtMwBZ0t6C7Bd0t6G5Bdwu6W9Ddgu4WdLeguw"
+ "XdLehuQXcLulvQ3YLuFnS3oLsF3S3obkF3C7pb0N2C7hZ0t6C7Bd0t6G5Bdwu6W9Ddgu4WNLagsQWNLWhsQWMLGlvQ2ILGFjS2oLEFjS1obEsa"
+ "oLsF2AxcMnDJwCUDlwxcMnDJsOAyLLgMSDIgyYAkA5IMSDIgyYAkA5IMSDIgyYAkA5IM6WdIP0P6GdLPkH6G9DOknyH9DOlnyC1Dbhlyy5Bbht"
+ "wy5JYhtwy5ZS_v_n1z_PcfL9N3_353_Afjxy85Smn--jtKab7_"
+ "J0plHlBK82SBUplLlMo8R6nMC5TKDFAqM0SpzA1KZW5RSvPsBqU0p2WpzC8olXmHUplfUSrzHqUyH1Aq8wWlMg8olfkdpTSP70WPpTTPtyiVma"
+ "FU5hNKaV78glKZryiVmaOU5vHu_Vgq81eUyhyhVOZvKJX5HqUyJyiV6aFUZoBSmVcolfkHSmX-E6Uy_"
+ "0KpzASlMrcolXmHUplfUSrzBaUyDyiV-R2lNI-308dSmSOUyrxGqcw_"
+ "UCrzL5TKXKNUZoJSmTcolfmKUpkWpTSPt8THUpkLlMpcolTmOUplXqBUZohSmSuUyrxEqcw_"
+ "UCozRanMHUpl3qOU5uoDSmWWpTI9lMqco1TmBqUydyiVeY9SmY8olfmMUpkvKJV5QCnNyxFKZX5AqczfUSrzDKUyxyiVWZbK9FEqc4FSmecolX"
+ "mFUpl_olTmHqUyH1FK8-o9SmWeolTmXyiVGaOU5uc_UUrzeGt5LJX5DaU0b_-BUpknKJV5igLz__"
+ "6yQ6lMg1KaHy9QKjNEgfn39T92ZVU37suqbtiyqhtFWVWND6OyqhvPZVU1zv4sq7oRl1XdWJdV3UjKqm6kZVU3bsqqbmzKqm5sy6puZGVVN27L"
+ "qm48lVXVGJuyqhqT87KqGxdlVTfCsqobUVnVjVVZ1Y3PZVU3rsuqakw_"
+ "lVXdOCurujEuq7oxKau6sSirurEsq7pxXlZ146Ks6kZQVnUjLKu6EZVV3ViVVd24LKu6cVVWdeNzWdWN67KqG_uyqhvfyqpuPJdV1ZiVnx_"
+ "Ui8L7tayqxtyUVdVY_"
+ "l5WdeNjWdWNr2VVN3ZlVTf2ZVU3Hsuqbnwrq7rxVFZ147ms6sZLWdWNQ1lVjfPTsqobH8uqbnwqq7pxVlZ1Y1xWdWNWVnVjXVZ146as6sZ9WdW"
+ "Nh7KqG3lZVY2LUVnVjd_Kqm68L6u64ZVV3fDLqm7My6puXJZV3bgqq7rxuazqxp9lVTf-"
+ "Kqu6cVNWdeNrWdWNfVnVje9lVTVWn8qqbozLqm5MyqpuVFXd-KOs6safZVU3_llWdeOvsqobcVnVjXVZ1Y1NWdWNXVnVjX1ZofFvP377692_"
+ "HS18_U9lVTf-c1nVjf9SVnXjv5ZV3fhvZVU3_ntZ1Y3_UVZ143-WVd34X2VVN_53WdWN_"
+ "1NWdePv69PkWG5obGhsaWQ0bmnc0bin8UDjkcYTjWcaLzQONL7TyGkYGniBn6Zo0EhpbGhsaWQ0bml8oXFH4yuNexoPNPY0Hmk80Xim8ULjQOO"
+ "VRk7D0igHf8PB3xDvDQd_wzHfcMw3HPMNxwxjR-OexgONPY1HGt9oPNP4TuOVRk7D0LA0yjFvSHXDgW2IbsPxbDiMDa--ofuWKW-"
+ "Z8pYBtwy4ZaZbDnVLvFvi3XLwGWlkHEbGYWQcRsZhZAx4y2Hcctnckvwtx3PLKbjlwG45sFte9Jbkb3mtW5K_"
+ "JfBbrpbb9tVfaBxofKeR0yjJf2E6X3iJL7zEFwb8wjhfGOeOeO-Y1x3zuuOY7xjwjgHvCOqOY77jJe445jte6ysDfmWcr3T_"
+ "ytx31OzIcEeGOzLc0X3HYeza7s80chqWRrmi7jmV95zKe-Z-z0vck-E9A94znXumc090MF5plNd64CUeGPmBVB-Y4ANXywMzfWCmD7z6A8k_"
+ "MOUHpvxAdA_M_YFT8MAxP3DMDxzzA8f8wDE_EBQMQ-Mti5LYnunsmc6eWeyZxZ5Z7Mlwz8HvOdQ9h7rnCPccz57XemSm33jRb7zWN17rG6_"
+ "1jcS-kdg3XvRbO86exjONFxoHGjmNMvcnXv2ZV39mFs90f-bgn-n-"
+ "Qi8YKY0bGhsaWxq3NL7QuKPxlcaOxj2NBxp7Go80nmm80DjQeKVhaFgaJfkDB3_gmA8c84HkDxzzgWM-"
+ "cKgHDvXAoR441AOHeuBQDxzqgSM8cIQHwjxwYN85sO-8-ndG_s7I35nyd-b1ne6vdH9lXq-"
+ "cglcGfCX5VwZ85eTmzD3nJQwztYxjmUXBaxUcYVGnM338f3_8mh7LP49lfSx3dbtwO_fH8texPHZ1Ph_LbS0q8Oin_GGo6mtefzX1V1t_"
+ "LfAkodKVX_P6q6m_2vprgR8NrXTl17z-auqvtv4K3ada96nWfap1n2rdp1p3VuvOat1ZrTurdWe1blzrxrVuXOvGtW5c_4DX5B_1T3-"
+ "VRk7D0LA0SnIj_qjYiD8uNuKPjI34Y2Mjin-j-DeKf6P4N4p_o_g9xe8pfk_xe4rfU3xK8SnFpxSfUnxK8UeKP1L8keKPFH-k-BPFnyj-"
+ "RPEnij9RfEbxGcVnFJ9RfFaLPaLziM4jOo_oPKLziM4jOo_oPKLziM47ofiE4hOKTyg-oZjoPKLziM4jOo_ovA8Uf6D4A8UfKP5A8e8U_"
+ "07x7xT_TvHvFHNSPE6Kx0nxOCkeJ8XjpHicFI-"
+ "T4nFSPE6Kx0nxOCkeJ8XjpHhvkzKmeEzxmOIxxXyleBOKJxRPKJ5QPKF4SvGU4inFU4qnFM8onlE8o3hG8YzihOKE4oTihOKE4pTilOKU4pTil"
+ "OIbim8ovqH4huIbijcUbyjeULyheENxRnFGcUZxRnFG8S3FtxTfUnxL8S3FXyj-QvEXir9Q_IXiO4rvKL6j-"
+ "I7iO4p3FO8o3lG8o3hH8T3F9xTfU3xP8T3FDxQ_UPxA8QPFDxTvKd5TvKd4T_Ge4keKHyl-pPiR4keKv1H8jeJvFH-j-BvFTxQ_"
+ "UfxE8RPFTxQ_U_xM8TPFzxQ_U_xC8QvFLxS_UPxCcU5xTnFOcU5xXov9X2txaeQ0DA1LoxRzf_a5P_vcn33uzz73Z5_7s8_92ef-7HN_9rk_-"
+ "zxhfZ6wPk9YnyeszxPW52buczP3uZn73Mx9buY-N3Ofm7nPzdznZu5zM_e5I_"
+ "nckXzuSD53JJ87ku9R7FHsUexR7FG8pXhL8ZbiLcXbWrzkEl1yiS65RJdcoksu0SWX6JJLdMkluuQSXXKJLr9T_J3i7xR_p_"
+ "h7LT6f1eLSyGkYGpZGKV5SvKR4SfGS4iXF5xSfU3xO8TnF57U45NoIuTZCro2QayPk2gi5NkKujZBrI-"
+ "TaCLk2Qq6NkGsj5NoIuTZCro2QJ2zIEzbkCRvyhA15woY8YUOesCFP2JAnbMgTNuQJG_KEDXnChjxhQ56wIU_"
+ "YkCdsyBM25Akb8oQNuZ5DrueQ6znkeg65nqO_"
+ "anFp5DQMDUujFHOJRlyiEZdoxCUacYlGXKIRl2jEJRpxiUZcohF30Yi7aMRdNOIuGnEXXf1Si0sjp2FoWBqlmJOy4qSsOCkrTsqKkxJTHFMcUx"
+ "xTHL-"
+ "JyTkm55icY3KOyTkOKA4oDigOKA4oDikOKQ4pDikOKY4ojiiOKI4ojiheUbyieEXxiuIVxZcUX1J8SfElxZcUX1F8RfEVxVcUX1H8meLPFH-m-"
+ "DPFnym-pvia4muKrym-ppg3gTFvAmPeBMa8CYx5ExjvKN5RvKN4R_GOYt7JxLyTiXknE_"
+ "NOJuadTMw7mZh3MjHvZGLeycS8k4l5JxPzTibmnUzMO5mYdzIxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4Mx"
+ "72Ri3snEvJOJeScT804m5p1MzDuZmHcyMe9kYt7JxAeKDxQfKD5QfKDYUGwoNhQbig3FlmJLsaXYUmxr8ZoJrpngmgmumeCaCSbknJBzQs4JOS"
+ "fknJBzQs4JOSfknJBzQnQJ0SVElxBdQnQJ0SVElxBdQnQJ0SWvFL9S_"
+ "ErxK8WvtTjlaZXytEp5WqU8rVKeVilPq5SnVcrTKuVplfK0SrmLptxFU-6iKXfRlLtoyruvlHdfKe--"
+ "Ut59pbz7Sn2KfYp9in2KfYrnFM8pnlM8p3hO8YLiBcULihcULyjmPVLKe6SU90gp75FS3iOlvEdKeY-U8h4p5T1Synuk9ILiC4ovKL6g-"
+ "IJiHkApD6CUB1DKAyjlAZTyAEp5AKU8gFIeQCkPoJQHUMoDKOUBlPIASnkApTyAUh5AKQ-"
+ "glAdQygMo5QGU8gBKeQClPIBSHkApD6CUB1DKAyjlAZTyAMr4PiXj-5SM71Myvk_J-D4l4yO4jI_gMj6Cy_"
+ "gILuMjuIw3rhlvXDPeuGa8cc1445rtKN5RvKN4R_GOYh5AGQ-"
+ "gjAdQxgMo4wGU8QDKeABlPIAyHkAZD6CMB1DGAyjjAZTxAMp4AGU8gDIeQBkPoIwHUMYDKOPGmHFjzLgxZtwYM26MGTfGjBtjxo0x48aYcWPMu"
+ "D9n3J8z7s8Z9-eM-3PGXTTjLppxF824i2Zvb6Vvfvwy_nEVH-u_61_0rL-DHzpQ3yy_43_qV6u-v-tfJKWtv5W27Y5ureqI2x--4yr9F-u_"
+ "5kaLN73X3PT6d1xz03FNzKCYmL9bz-"
+ "Re3Qnq6Gx68iE3t7PpMUNubmfTY4fc3M6mpxhyczvlQhsgM6RRgj5OQxol6KM2pFGCPoZDGiXoI9qrwTuWnkxll-nvsv1db9fKe9d1PrSulVs-"
+ "5OZ2doy-y83t7Misy83t7M-6e1233TrXrOM9pOlfswNBOnF1rtmBIJ3wOtfsQJBOlJ1rdiCI0Mhl-NrfZfq7bH_X27VM77o2Q-taueVDbm5nx-"
+ "i73NzOjsy63NzO_qy713XbrXPNOt5Dmv41OxCkE1fnmh0I0gmvc80OBOlE2blmB4IITc-61l15f5ft73q7lu1d13ZoXSu3fMjN7eyg3-"
+ "XmdnZk1uXmdvZn3b2u226da9bxHtL0r9mBIJ24OtfsQJBOeJ1rdiBIJ8rONTsQRGh61rXuyvu7TH_"
+ "X27WK3nVdDK1r5ZYPubmdHUPscnM7O3h3ubmd_Vl3r-u2W-eadbyHNP1rdiBIJ67ONTsQpBNe55odCNKJsnPNDgQRmp51rbvy_"
+ "i7T31X9pIWY99Z35Jt5Idt0yTauTLyv3_Sk3OfUdQnV93f9rrjjXXX77XTH--iON9D1tzri9ofvuEr_xbqu-Xffm_"
+ "ZWj95UOt3yITe301kffW5up7t2etzcTmd597m5nTW2ISRdnU1PL5KuzqanF0lXZ9PTi6Srs-npRdLV2b2HdngPabr30J8E6cSl9tCfBOmEp_"
+ "bQnwTpRKn20J8EGQQ7tOSGNErwM7C967At-BnY3lXZFvwMbO8abQt-BlZpOk6p7i7T32X7u3qupajKrp5rKQKy6-1a3Q-jWj3d-3X3w6g-"
+ "N7ezg1Tvw6hONzvk5nZ2EO59GKXcNkNIujo7Jq3LrRvJZghJV2fHZHe5dSPpWgjd-_Xg87kO7yFN_178s-dzQ0H6qA1p-"
+ "vfinz2fGwrSR3RI078X9wTpXXmde_FAkEGwQ8txSNO_Fw8EGQQ7tFR7NR1vOLq7TH-X7e_"
+ "quZaamp79Wnf1XEvk1f2QtdXTvV93P2Ttc3M7O0j1PmTtdLNDbm5nB-Heh6zKbTOEpKuz46XQ5daNZDOEpKuzY7K73LqRdC2E7v168Llzh_"
+ "eQpn8v_tlz56EgfdSGNP178c-eOw8F6SM6pOnfi3uC9K68zr14IMgg2KHlOKTp34sHggyCHVqqvZqe_brngX53l-3v6rmWmpqeW2_"
+ "d1XMtkVf3hwetnu79uvvDgz43t7PjJdT74UGnmx1yczs7CPd-eKDcNkNIujo7ZqbLrRvJZghJV2fHZHe5dSPpWgjd-_Xg5ykd3kOa_r34Z5-"
+ "nDAXpozak6d-Lf_Z5ylCQPqJDmv69uCdI78rr3IsHggyCHVqOQ5r-vXggyCDYoaXaq-nZr3s-qOruMv1dPddSU9OzX-uunmuJvLo_FGv1dO_"
+ "X3R-K9bm5nR04ej8U63SzQ25uZwfh3g_FlNtmCElXZ8fMdLl1I9kMIenq7FjuXW7dSLoWQvd-3feZWB-ZIU3_"
+ "XjwQpBNX5148EKQTXudePBCkE2XnXjwQZBDs0JIb0vTvxQNBBsEOLcchTf9ePBBkEOzQUu3V9OzXuivv7zL9Xba7S01Nz36tu3quRTip1_"
+ "529V9rl78xonnJXyDpft7fqVEC8y8EcTVKYP-FIK5GCYp_IYir-TmMfgqD6ffnPZhwf6aDKerO-m-stdpvy6Jefj1deX-X6e-y_V1vw-"
+ "p4jOn8QlP_vLma_iU2EMTV9C-xgSCupn-JDQTpZDIIo5_CYPr9eQ8m3J_pYIq6s70MxUNcnU_PMtRdpr_L9ne9Davj6YwcTPfTmU5N_"
+ "3Y5EMTV9C-xgSCupn-JDQTpZDIIo5_CYPr9eQ8m3J_pYIq6s70MxbMpnU_PMtRdpr_L9ne9DavjTaccTPebzk5N_xIbCOJq-rfLgSCupn-"
+ "JDQTpZDIIo5_CYPr9eQ8m3J_pYIq6s70MxVtunU_PMtRdpr_L9ne9DavjXloOpvteulPTv8QGgria_iU2EMTV9G-XA0Fczc9h9FMYTL8_78GE-"
+ "zMdTFF3tpeheCeh8-lZhrrL9HfZ_q7m93rL7zW_rOt8z3R8z3Z87y1e3nzvteN7puN7tuN7b_FMRzzTMWbTEc90xLMd8WxHPNsxZtsRr-"
+ "iIV3TEKzriFXLM1e_vln8Lj-Z_VP9Vf9U6b5ntjssPLbPVUf8Z7dou_5I07byxyz_jTDtu2a1fGy7_"
+ "fDHtL43tv7bsVsxF67dty79zWdvlHxSk7Td2-"
+ "WezaLfiJPL3M2s0TZPJvn3n3Gm6ghpV03QEb8je2jW2pu38NmaNr2nHTvtGtmuUTfuLbPuvTtu53iJ12reyXSNu2r5s16ibthO_"
+ "QZ5L5LlCnkvkuUKeS-"
+ "S5Qp47yHMHee4gzx3kuYM8d5DnDvLcQZ47yHMHee4gzx3kuYM8d5DnDvLcQZ47yI1EbhRyI5EbhdxI5EYhNw5y4yA3DnLjIDcOcuMgNw5y4yA3"
+ "DnLjIDcOcuMgNw5y4yA3DnLjIDcOciuRW4XcSuRWIbcSuVXIrYPcOsitg9w6yK2D3DrIrYPcOsitg9w6yK2D3DrIrYPcOsitg9w6yK2DvJDIC4"
+ "W8kMgLhbyQyAuFvHCQFw7ywkFeOMgLB3nhIC8c5IWDvHCQFw7ywkFeOMgLB3nhIC8c5IWDvGgh3zS4NwL1psG8EYg3Dd6NQLtpYd20kG5aODct"
+ "lJsWxk0L4aaFb9NCt2lh27SQbVq4Ni1UmxamTQvRpoVn00KzaWORdxWtJpOVdxWtpiuoUam7Cn7nDZlzV9FqOz9WX-"
+ "Nz7ipa7RvZrlE6dxVN23912s71FqnTvpXtGrFzV9G0a9TOXUWrLX8ut0GeK-"
+ "S5RJ4r5LlEnivkuYM8d5DnDvLcQZ47yHMHee4gzx3kuYM8d5DnDvLcQZ47yHMHee4gzx3kuYPcSORGITcSuVHIjURuFHLjIDcOcuMgNw5y4yA3"
+ "DnLjIDcOcuMgNw5y4yA3DnLjIDcOcuMgNw5y4yC3ErlVyK1EbhVyK5Fbhdw6yK2D3DrIrYPcOsitg9w6yK2D3DrIrYPcOsitg9w6yK2D3DrIrY"
+ "PcOsgLibxQyAuJvFDIC4m8UMgLB3nhIC8c5IWDvHCQFw7ywkFeOMgLB3nhIC8c5IWDvHCQFw7ywkFeOMhbdxXlJ94VbpoVqLp13jLbHSVemq2O"
+ "Gmttl0hp541doqQdt-"
+ "ybxi7x0f7S2P5ry27FLHHRvm3sEhNtv7FLPLRbcRL5mXONxr2raH3n3Gm6ghqVe1fx9p03ZPKuot12PlKu8cm7inb7RrZrlPKuotX2nU-"
+ "zfed6NVp5V9Fq14jlXUWrXaOWdxXttvx8tUGeK-"
+ "S5RJ4r5LlEnivkuYM8d5DnDvLcQZ47yHMHee4gzx3kuYM8d5DnDvLcQZ47yHMHee4gzx3kuYPcSORGITcSuVHIjURuFHLjIDcOcuMgNw5y4yA3"
+ "DnLjIDcOcuMgNw5y4yA3DnLjIDcOcuMgNw5y4yC3ErlVyK1EbhVyK5Fbhdw6yK2D3DrIrYPcOsitg9w6yK2D3DrIrYPcOsitg9w6yK2D3DrIrY"
+ "PcOsgLibxQyAuJvFDIC4m8UMgLB3nhIC8c5IWDvHCQFw7ywkFeOMgLB3nhIC8c5IWDvHCQFw7ywkFeOMjf7irqP-EFQPjT6-0WcLW_"
+ "MZKtpjsX3rnrnQvv3PU2wtu43kZ4G9fbCm_relvhbV3vQngXrnchvAvh3XhWHz02jo0Te-o_"
+ "XDz9rXZqTNExapnsaE2ObCrByGm2BLmMkKsIuYyQqwhGRjAqgpERjIpgZQSrIlgZwaoIhYxQqAiFjFCICO8b7_"
+ "fC833j9V56SPatphKMnGZLkMsIuYqQywi5imBkBKMiGBnBqAhWRrAqgpURrIpQyAiFilDICJL9x8b7o_"
+ "D82Hh9lB6SfaupBCOn2RLkMkKuIuQyQq4iGBnBqAhGRjAqgpURrIpgZQSrIhQyQqEiFDKCZP9Jkmw1lWDkNFuCXEbIVYRcRshVBCMjGBXByAhG"
+ "RbAyglURrIxgVYRCRihUhEJGcEi2vMWe_9Y3En30O5Mz0GoqwchptgS5jJCrCLmMkKsIRkYwKoKREYyKYGUEqyJYGcGqCIWMUKgIhYwgZ-"
+ "Cs5e3OwFnL0ZkB7-7Nj6boGLVMdog5azeVYOQ0W4JcRshVhFxGyFUEIyMYFcHICEZFsDKCVRGsjGBVhEJGKFSEQkYQc-Z9bby_Cs-"
+ "vjddX4bFrPHbCY9d47KSHnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq37xvteeN43X"
+ "vfSQ7K_V-zvJft7xf5esr9X7O8l-3vF_l6yv1fs7yX7e8X-XrK_V-zvJft7xf5esr9X7O8l-3vFft9474XnvvHaSw_Jfq_Y7yX7vWK_l-"
+ "z3iv1est8r9nvJfq_Y7yX7vWK_l-z3iv1est8r9nvJfq_Y7yX7vWL_3Hg_C8_nxutZekj2z4r9s2T_rNg_S_bPiv2zZP-s2D9L9s-K_bNk_"
+ "6zYP0v2z4r9s2T_rNg_S_bPiv2zZP_ssvd_"
+ "ffOmKTpGLZMdgn27qQQjp9kS5DJCriLkMkKuIhgZwagIRkYwKoKVEayKYGUEqyIUMkKhIhQygmQ_arxHwnPUeI2kh2Q_"
+ "UuxHkv1IsR9J9iPFfiTZjxT7kWQ_UuxHkv1IsR9J9iPFfiTZjxT7kWQ_UuxHkv1IsT9pvE-E50njdSI9JPsTxf5Esj9R7E8k-xPF_"
+ "kSyP1HsTyT7E8X-RLI_UexPJPsTxf5Esj9R7E8k-xPF_kSyP1HsTxvvU-F52nidSg_J_lSxP5XsTxX7U8n-VLE_lexPFftTyf5UsT-V7E8V-"
+ "1PJ_lSxP5XsTxX7U8n-VLE_lexPXfbVXzN-i9BuKsHIabYEuYyQqwi5jJCrCEZGMCqCkRGMimBlBKsiWBnBqgiFjFCoCIWM4JBseTvvh-u-"
+ "keir_cJ_"
+ "vPnRFB2jlskOMWftphKMnGZLkMsIuYqQywi5imBkBKMiGBnBqAhWRrAqgpURrIpQyAiFilDICGLOwmbXD8WuHza7fngiPSR7teuHctcP1a4fyl"
+ "0_VLt-KHf9UO36odz1Q7Xrh3LXD9WuH8pdP1S7fih3_VDt-qHc9UO164dy1w_Vrh82u34odv2w2fXDU-kh2atdP5S7fqh2_VDu-"
+ "qHa9UO564dq1w_lrh-qXT-Uu36odv1Q7vqh2vVDueuHatcP5a4fql0_lLt-qHb9sHl6SlN0jFomOyR79eQ6lE-uQ_"
+ "XkOpRPrkP15DqUT65D9eQ6lE-uQ_XkOpRPrkP15DqUT65D9eQ6lE-uQ_XkOpRPrkP15DqUT65D9eQ6HDfeY-E5brzG0kOyHyv2Y8l-"
+ "rNiPJfuxYj-W7MeK_ViyHyv2Y8l-rNiPJfuxYj-W7MeK_ViyHyv2Y8l-rNhPGu-J8Jw0XhPpIdlPFPuJZD9R7CeS_USxn0j2E8V-"
+ "ItlPFPuJZD9R7CeS_USxn0j2E8V-ItlPFPuJZD9x2Ud_"
+ "vXnTFB2jlskOwb7dVIKR02wJchkhVxFyGSFXEYyMYFQEIyMYFcHKCFZFsDKCVREKGaFQEQoZQbJ_bLwfhedj4_UoPST7R8X-UbJ_"
+ "VOwfJftHxf5Rsn9U7B8l-0fF_lGyf1TsHyX7R8X-UbJ_VOwfJftHxf5Rsn9U7Jv3BTRFx6hlskOyV-_"
+ "JWt8ZOc2WIJcRchUhlxFyFcHICEZFMDKCURGsjGBVBCsjWBWhkBEKFaGQEST7p8b7SXg-NV5P0kOyf1LsnyT7J8X-SbJ_"
+ "UuyfJPsnxf5Jsn9S7J8k-yfF_kmyf1LsnyT7J8X-SbJ_"
+ "UuyfJPsnl32cvHnTFB2jlskOwb7dVIKR02wJchkhVxFyGSFXEYyMYFQEIyMYFcHKCFZFsDKCVREKGaFQEQoZQbJv9pxY7Dlxs-fE36SHZK_"
+ "2nFjuObHac2K558Rqz4nlnhOrPSeWe06s9pxY7jmx2nNiuefEas-J5Z4Tqz0nlntOrPacWO45sdpz4ubTq1h8ehU3n17Fz9JDslefXrW-"
+ "M3KaLUEuI-QqQi4j5CqCkRGMimBkBKMiWBnBqghWRrAqQiEjFCpCISNI9i-N94vwfGm8XqSHZP-i2L9I9i-K_Ytk_6LYv0j2L4r9i2T_oti_"
+ "SPYviv2LZP-i2L9I9i-K_Ytk_6LYv0j2L4r9ofE-CM9D43WQHpL9QbE_SPYHxf4g2R8U-4Nkf1DsD5L9QbE_SPYHxf4g2R8U-"
+ "4Nkf1DsD5L9QbE_"
+ "SPYHxb7FQObfyl3m3cpZ5tvKVeZp5GwZNVtGzpZRs2XkbBk1W0bOllGzZeRsGTVbRs6WUbNl5GwZNVtGzpZRs2XkbBk1W0bOllGz1bq-"
+ "vHbruvKaVrK3ir2V7K1ibyV7q9hbyd4q9layt4q9leytYm8le6vYW8neKvZWsreKvZXsrcs-aU6IRJwQSXNCJC_SQ7BP1AmRyBMiUSdEIk-"
+ "IRJ0QiTwhEnVCJPKESNQJkcgTIlEnRCJPiESdEIk8IRJ1QiTyhEjUCZHIEyJRJ0TSnBCJOCGS5oRIDtJDslcnRCJPiESdEIk8IRJ1QiTyhEjUC"
+ "ZHIEyJRJ0QiT4hEnRCJPCESdUIk8oRI1AmRyBMiUSdEIk-"
+ "IRJ0QafPUmaboGLVMdgj27aYSjJxmS5DLCLmKkMsIuYpgZASjIhgZwagIVkawKoKVEayKUMgIhYpQyAiSffPUmaboGLVMdkj26qlz6zsjp9kS5"
+ "DJCriLkMkKuIhgZwagIRkYwKoKVEayKYGUEqyIUMkKhIhQygmQ_bbzFR_J1a9Qy2SHZTxX7qWQ_Veynkv1UsZ9K9lPFfirZTxX7qWQ_"
+ "Veynkv1UsZ9K9lPFfirZTxX7qWQ_VewXjfdCeC4ar4X0kOwXiv1Csl8o9gvJfqHYLyT7hWK_kOwXiv1Csl8o9gvJfqHYLyT7hWK_"
+ "kOwXiv1Csl8o9ueN97nwPG-8zqWHZH-u2J9L9ueK_blkf67Yn0v254r9uWR_rtifS_bniv25ZH-u2J9L9ueK_"
+ "blkf67Yn0v254r9ReN9ITwvGq8L6SHZXyj2F5L9hWJ_IdlfKPYXkv2FYn8h2V8o9heS_YVifyHZXyj2F5L9hWJ_"
+ "IdlfKPYXkv2FYh803oHwDBqvQHpI9oFiH0j2gWIfSPaBYh9I9oFiH0j2gWIfSPaBYh9I9oFiH0j2gWIfSPaBYh9I9oFiHzbeofAMG69Qekj2oW"
+ "IfSvahYh9K9qFiH0r2oWIfSvahYh9K9qFiH0r2oWIfSvahYh9K9qFiH0r2oWIfNd6R8Iwar0h6SPaRYh9J9pFiH0n2kWIfSfaRYh9J9pFiH0n2"
+ "kWIfSfaRYh9J9pFiH0n2kWIfSfaRYr9qvFfCc9V4raSHZL9S7FeS_UqxX0n2K8V-JdmvFPuVZL9S7FeS_UqxX0n2K8V-JdmvFPuVZL9S7FeS_"
+ "Uqxv2y8L4XnZeN1KT0k-0vF_lKyv1TsLyX7S8X-UrK_VOwvJftLxf5Ssr9U7C8l-0vF_lKyv1TsLyX7S8X-UrK_VOyvGu8r4XnVeF1JD8n-"
+ "SrG_kuyvFPsryf5Ksb-S7K8U-yvJ_kqxv5LsrxT7K8n-SrG_kuyvFPsryf5Ksb-S7K8U-8-N92fh-bnx-iw8rhuPa-"
+ "Fx3Xhctz34X9DvWvZ9y35o2c7_EV_7NO17p_3gtJ3_8Lzxzx3_3PHPHX_j-BvH3zj-xvG3jr91_K3jbx3_wvEvHP_C8S9a_"
+ "puW76blt2n5bNp6h_fG4b1xeG8c3huH98bhvXF4bxzeG4f3xuG9cXhvHN4bh_fG4b1xeG8c3huH98bhvXF4byRv_reFu5Z937IfWrbz_"
+ "wrWPpJ3u_3gtJ3_JK_xzx3_3PHPHX_j-BvH3zj-xvG3jr91_K3jbx3_wvEvHP_C8X_jzT8Ys1i_a_2FmKpVmptDu-"
+ "PYOnqXv2EdpaVZ9Rxb1e5U9v19ffah1clm-aOMwR8ts_KZfUDrdgVzWQ3sO-yk-XZ52ly9b5n1Zvhrqb8o_6vSX3m5i5uqN7grmz56g2-"
+ "Vjf9uLHiqRiXsetut_-PO9-_-P_-VKLY");
+ static string all_emojis_str = gzdecode(base64url_decode(packed_emojis).ok()).as_slice().str();
+ constexpr size_t EMOJI_COUNT = 4713;
+#else
+ string all_emojis_str;
+ constexpr size_t EMOJI_COUNT = 0;
+#endif
+ FlatHashSet<Slice, SliceHash> all_emojis;
+ all_emojis.reserve(EMOJI_COUNT);
+ for (size_t i = 0; i < all_emojis_str.size(); i++) {
+ CHECK(all_emojis_str[i] != ' ');
+ CHECK(all_emojis_str[i + 1] != ' ');
+ size_t j = i + 2;
+ while (j < all_emojis_str.size() && all_emojis_str[j] != ' ') {
+ j++;
+ }
+ CHECK(j < all_emojis_str.size());
+ all_emojis.insert(Slice(&all_emojis_str[i], &all_emojis_str[j]));
+ CHECK(j - i <= max_emoji_length);
+ i = j;
+ }
+ CHECK(all_emojis.size() == EMOJI_COUNT);
+ return all_emojis;
+ }();
+ if (str.size() > MAX_EMOJI_LENGTH) {
+ return false;
+ }
+ return emojis.count(str) != 0;
+}
+
+int get_fitzpatrick_modifier(Slice emoji) {
+ if (emoji.size() < 4 || emoji[emoji.size() - 4] != '\xF0' || emoji[emoji.size() - 3] != '\x9F' ||
+ emoji[emoji.size() - 2] != '\x8F') {
+ return 0;
+ }
+ auto c = static_cast<unsigned char>(emoji.back());
+ if (c < 0xBB || c > 0xBF) {
+ return 0;
+ }
+ return (c - 0xBB) + 2;
+}
+
+Slice remove_fitzpatrick_modifier(Slice emoji) {
+ while (get_fitzpatrick_modifier(emoji) != 0) {
+ emoji.remove_suffix(4);
+ }
+ return emoji;
+}
+
+string remove_emoji_modifiers(Slice emoji) {
+ string result = emoji.str();
+ remove_emoji_modifiers_in_place(result);
+ return result;
+}
+
+void remove_emoji_modifiers_in_place(string &emoji) {
+ static const Slice modifiers[] = {u8"\uFE0F" /* variation selector-16 */,
+ u8"\u200D\u2640" /* zero width joiner + female sign */,
+ u8"\u200D\u2642" /* zero width joiner + male sign */,
+ u8"\U0001F3FB" /* emoji modifier fitzpatrick type-1-2 */,
+ u8"\U0001F3FC" /* emoji modifier fitzpatrick type-3 */,
+ u8"\U0001F3FD" /* emoji modifier fitzpatrick type-4 */,
+ u8"\U0001F3FE" /* emoji modifier fitzpatrick type-5 */,
+ u8"\U0001F3FF" /* emoji modifier fitzpatrick type-6 */};
+ size_t j = 0;
+ for (size_t i = 0; i < emoji.size();) {
+ bool is_found = false;
+ for (auto &modifier : modifiers) {
+ auto length = modifier.size();
+ if (i + length <= emoji.size() && Slice(&emoji[i], length) == modifier) {
+ // skip modifier
+ i += length;
+ is_found = true;
+ break;
+ }
+ }
+ if (!is_found) {
+ emoji[j++] = emoji[i++];
+ }
+ }
+ if (j != 0) {
+ emoji.resize(j);
+ }
+}
+
+string remove_emoji_selectors(Slice emoji) {
+ if (!is_emoji(emoji)) {
+ return emoji.str();
+ }
+ string str;
+ for (size_t i = 0; i < emoji.size(); i++) {
+ if (i + 3 <= emoji.size() && emoji[i] == '\xEF' && emoji[i + 1] == '\xB8' && emoji[i + 2] == '\x8F') {
+ // skip \uFE0F
+ i += 2;
+ } else {
+ str += emoji[i];
+ }
+ }
+ CHECK(is_emoji(str));
+ return str;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.h
new file mode 100644
index 0000000000..8bbc2904b5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.h
@@ -0,0 +1,32 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+// checks whether the string is an emoji; variation selectors are ignored
+bool is_emoji(Slice str);
+
+// checks whether emoji ends on a Fitzpatrick modifier and returns it's number or 0
+int get_fitzpatrick_modifier(Slice emoji);
+
+// removes all Fitzpatrick modifier from the end of the string
+Slice remove_fitzpatrick_modifier(Slice emoji);
+
+// removes all emoji modifiers from the string
+string remove_emoji_modifiers(Slice emoji);
+
+// removes all emoji modifiers from the string in-place
+void remove_emoji_modifiers_in_place(string &emoji);
+
+// removes all emoji selectors from the string if it is an emoji
+string remove_emoji_selectors(Slice emoji);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.cpp
index b22418151c..edb5df8677 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,28 +7,69 @@
#include "td/utils/filesystem.h"
#include "td/utils/buffer.h"
-#include "td/utils/logging.h"
+#include "td/utils/misc.h"
#include "td/utils/PathView.h"
#include "td/utils/port/FileFd.h"
+#include "td/utils/port/path.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/unicode.h"
#include "td/utils/utf8.h"
namespace td {
-Result<BufferSlice> read_file(CSlice path, int64 size) {
+namespace {
+
+template <class T>
+T create_empty(size_t size);
+
+template <>
+string create_empty<string>(size_t size) {
+ return string(size, '\0');
+}
+
+template <>
+BufferSlice create_empty<BufferSlice>(size_t size) {
+ return BufferSlice{size};
+}
+
+template <>
+SecureString create_empty<SecureString>(size_t size) {
+ return SecureString{size};
+}
+
+template <class T>
+Result<T> read_file_impl(CSlice path, int64 size, int64 offset) {
TRY_RESULT(from_file, FileFd::open(path, FileFd::Read));
- if (size == -1) {
- size = from_file.get_size();
+ TRY_RESULT(file_size, from_file.get_size());
+ if (offset < 0 || offset > file_size) {
+ return Status::Error("Failed to read file: invalid offset");
+ }
+ if (size < 0 || size > file_size - offset) {
+ size = file_size - offset;
}
- BufferWriter content{static_cast<size_t>(size), 0, 0};
- TRY_RESULT(got_size, from_file.read(content.as_slice()));
+ auto content = create_empty<T>(narrow_cast<size_t>(size));
+ TRY_RESULT(got_size, from_file.pread(as_mutable_slice(content), offset));
if (got_size != static_cast<size_t>(size)) {
return Status::Error("Failed to read file");
}
from_file.close();
- return content.as_buffer_slice();
+ return std::move(content);
+}
+
+} // namespace
+
+Result<BufferSlice> read_file(CSlice path, int64 size, int64 offset) {
+ return read_file_impl<BufferSlice>(path, size, offset);
+}
+
+Result<string> read_file_str(CSlice path, int64 size, int64 offset) {
+ return read_file_impl<string>(path, size, offset);
+}
+
+Result<SecureString> read_file_secure(CSlice path, int64 size, int64 offset) {
+ return read_file_impl<SecureString>(path, size, offset);
}
// Very straightforward function. Don't expect much of it.
@@ -37,18 +78,28 @@ Status copy_file(CSlice from, CSlice to, int64 size) {
return write_file(to, content.as_slice());
}
-Status write_file(CSlice to, Slice data) {
+Status write_file(CSlice to, Slice data, WriteFileOptions options) {
auto size = data.size();
TRY_RESULT(to_file, FileFd::open(to, FileFd::Truncate | FileFd::Create | FileFd::Write));
+ if (options.need_lock) {
+ TRY_STATUS(to_file.lock(FileFd::LockFlags::Write, to.str(), 10));
+ TRY_STATUS(to_file.truncate_to_current_position(0));
+ }
TRY_RESULT(written, to_file.write(data));
- if (written != static_cast<size_t>(size)) {
+ if (written != size) {
return Status::Error(PSLICE() << "Failed to write file: written " << written << " bytes instead of " << size);
}
+ if (options.need_sync) {
+ TRY_STATUS(to_file.sync());
+ }
+ if (options.need_lock) {
+ to_file.lock(FileFd::LockFlags::Unlock, to.str(), 10).ignore();
+ }
to_file.close();
return Status::OK();
}
-static std::string clean_filename_part(Slice name, int max_length) {
+static string clean_filename_part(Slice name, int max_length) {
auto is_ok = [](uint32 code) {
if (code < 32) {
return false;
@@ -84,6 +135,9 @@ static std::string clean_filename_part(Slice name, int max_length) {
uint32 code;
it = next_utf8_unsafe(it, &code);
if (!is_ok(code)) {
+ if (prepare_search_character(code) == 0) {
+ continue;
+ }
code = ' ';
}
if (new_name.empty() && (code == ' ' || code == '.')) {
@@ -99,14 +153,14 @@ static std::string clean_filename_part(Slice name, int max_length) {
return new_name;
}
-std::string clean_filename(CSlice name) {
+string clean_filename(CSlice name) {
if (!check_utf8(name)) {
return {};
}
PathView path_view(name);
- auto filename = clean_filename_part(path_view.file_stem(), 60);
- auto extension = clean_filename_part(path_view.extension(), 20);
+ auto filename = clean_filename_part(path_view.file_stem(), 64);
+ auto extension = clean_filename_part(path_view.extension(), 16);
if (!extension.empty()) {
if (filename.empty()) {
filename = std::move(extension);
@@ -120,4 +174,18 @@ std::string clean_filename(CSlice name) {
return filename;
}
+Status atomic_write_file(CSlice path, Slice data, CSlice path_tmp) {
+ string path_tmp_buf;
+ if (path_tmp.empty()) {
+ path_tmp_buf = path.str() + ".tmp";
+ path_tmp = path_tmp_buf;
+ }
+
+ WriteFileOptions options;
+ options.need_sync = true;
+ options.need_lock = true;
+ TRY_STATUS(write_file(path_tmp, data, options));
+ return rename(path_tmp, path);
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.h
index 4bb1b17191..b437105819 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,16 +7,28 @@
#pragma once
#include "td/utils/buffer.h"
+#include "td/utils/SharedSlice.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
namespace td {
-Result<BufferSlice> read_file(CSlice path, int64 size = -1);
+Result<BufferSlice> read_file(CSlice path, int64 size = -1, int64 offset = 0);
+Result<string> read_file_str(CSlice path, int64 size = -1, int64 offset = 0);
+Result<SecureString> read_file_secure(CSlice path, int64 size = -1, int64 offset = 0);
-Status copy_file(CSlice from, CSlice to, int64 size = -1);
+Status copy_file(CSlice from, CSlice to, int64 size = -1) TD_WARN_UNUSED_RESULT;
-Status write_file(CSlice to, Slice data);
+struct WriteFileOptions {
+ bool need_sync = false;
+ bool need_lock = true;
+};
+Status write_file(CSlice to, Slice data, WriteFileOptions options = {}) TD_WARN_UNUSED_RESULT;
+
+string clean_filename(CSlice name);
+
+// writes data to file and ensures that the file is either fully overriden, or is left intact
+// uses path_tmp to temporary store data, then calls rename
+Status atomic_write_file(CSlice path, Slice data, CSlice path_tmp = {});
-std::string clean_filename(CSlice name);
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.cpp
index 44fc264ab5..d3dd6bcd58 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.cpp
@@ -1,13 +1,11 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/find_boundary.h"
-#include "td/utils/logging.h"
-
#include <cstring>
namespace td {
@@ -26,7 +24,7 @@ bool find_boundary(ChainBufferReader range, Slice boundary, size_t &already_read
auto save_range = range.clone();
char x[MAX_BOUNDARY_LENGTH + 4];
range.advance(boundary.size(), {x, sizeof(x)});
- if (std::memcmp(x, boundary.data(), boundary.size()) == 0) {
+ if (Slice(x, boundary.size()) == boundary) {
return true;
}
@@ -35,7 +33,7 @@ bool find_boundary(ChainBufferReader range, Slice boundary, size_t &already_read
range.advance(1);
already_read++;
} else {
- const char *ptr = static_cast<const char *>(std::memchr(ready.data(), boundary[0], ready.size()));
+ const auto *ptr = static_cast<const char *>(std::memchr(ready.data(), boundary[0], ready.size()));
size_t shift;
if (ptr == nullptr) {
shift = ready.size();
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.h
index 5b424cf23c..a0c6f4ff54 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/fixed_vector.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/fixed_vector.h
new file mode 100644
index 0000000000..a4bf0af794
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/fixed_vector.h
@@ -0,0 +1,79 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#include <utility>
+
+namespace td {
+
+template <class T>
+class fixed_vector {
+ public:
+ fixed_vector() = default;
+ explicit fixed_vector(size_t size) : ptr_(new T[size]), size_(size) {
+ }
+ fixed_vector(fixed_vector &&other) noexcept {
+ swap(other);
+ }
+ fixed_vector &operator=(fixed_vector &&other) noexcept {
+ swap(other);
+ return *this;
+ }
+ fixed_vector(const fixed_vector &) = delete;
+ fixed_vector &operator=(const fixed_vector &) = delete;
+ ~fixed_vector() {
+ delete[] ptr_;
+ }
+
+ using iterator = T *;
+ using const_iterator = const T *;
+
+ T &operator[](size_t i) {
+ return ptr_[i];
+ }
+ const T &operator[](size_t i) const {
+ return ptr_[i];
+ }
+
+ T *begin() {
+ return ptr_;
+ }
+ const T *begin() const {
+ return ptr_;
+ }
+ T *end() {
+ return ptr_ + size_;
+ }
+ const T *end() const {
+ return ptr_ + size_;
+ }
+
+ bool empty() const {
+ return size() == 0;
+ }
+ size_t size() const {
+ return size_;
+ }
+
+ void swap(fixed_vector<T> &other) {
+ std::swap(ptr_, other.ptr_);
+ std::swap(size_, other.size_);
+ }
+
+ private:
+ T *ptr_{};
+ size_t size_{0};
+};
+
+template <class T>
+void swap(fixed_vector<T> &lhs, fixed_vector<T> &rhs) {
+ lhs.swap(rhs);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/format.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/format.h
index 745ad0d8a5..0baf9b8be0 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/format.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/format.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,7 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/invoke.h" // for tuple_for_each
+#include "td/utils/invoke.h"
#include "td/utils/Slice.h"
#include "td/utils/StringBuilder.h"
@@ -17,7 +17,7 @@
namespace td {
namespace format {
/*** HexDump ***/
-template <std::size_t size, bool reversed = true>
+template <std::size_t size, bool is_reversed = true>
struct HexDumpSize {
const unsigned char *data;
};
@@ -26,12 +26,12 @@ inline char hex_digit(int x) {
return "0123456789abcdef"[x];
}
-template <std::size_t size, bool reversed>
-StringBuilder &operator<<(StringBuilder &builder, const HexDumpSize<size, reversed> &dump) {
+template <std::size_t size, bool is_reversed>
+StringBuilder &operator<<(StringBuilder &builder, const HexDumpSize<size, is_reversed> &dump) {
const unsigned char *ptr = dump.data;
// TODO: append unsafe
for (std::size_t i = 0; i < size; i++) {
- int xy = ptr[reversed ? size - 1 - i : i];
+ int xy = ptr[is_reversed ? size - 1 - i : i];
int x = xy >> 4;
int y = xy & 15;
builder << hex_digit(x) << hex_digit(y);
@@ -46,21 +46,18 @@ struct HexDumpSlice {
template <std::size_t align>
StringBuilder &operator<<(StringBuilder &builder, const HexDumpSlice<align> &dump) {
- std::size_t size = dump.slice.size();
- const unsigned char *ptr = dump.slice.ubegin();
+ const auto str = dump.slice;
+ const auto size = str.size();
builder << '\n';
- const std::size_t part = size % align;
- if (part) {
- builder << HexDumpSlice<1>{Slice(ptr, part)} << '\n';
+ const std::size_t first_part_size = size % align;
+ if (first_part_size) {
+ builder << HexDumpSlice<1>{str.substr(0, first_part_size)} << '\n';
}
- size -= part;
- ptr += part;
- for (std::size_t i = 0; i < size; i += align) {
- builder << HexDumpSize<align>{ptr};
- ptr += align;
+ for (std::size_t i = first_part_size; i < size; i += align) {
+ builder << HexDumpSize<align>{str.ubegin() + i};
if (((i / align) & 15) == 15 || i + align >= size) {
builder << '\n';
@@ -148,7 +145,7 @@ inline StringBuilder &operator<<(StringBuilder &builder, const Escaped &escaped)
builder << static_cast<char>(c);
} else {
const char *oct = "01234567";
- builder << "\\0" << oct[c >> 6] << oct[(c >> 3) & 7] << oct[c & 7];
+ builder << '\\' << oct[c >> 6] << oct[(c >> 3) & 7] << oct[c & 7];
}
}
return builder;
@@ -176,7 +173,7 @@ inline StringBuilder &operator<<(StringBuilder &logger, Time t) {
while (i + 1 < durations_n && t.seconds_ > 10 * durations[i + 1].value) {
i++;
}
- logger << StringBuilder::FixedDouble(t.seconds_ / durations[i].value, 1) << durations[i].name;
+ logger << StringBuilder::FixedDouble(t.seconds_ / durations[i].value, 1) << Slice(durations[i].name);
return logger;
}
@@ -199,10 +196,10 @@ inline StringBuilder &operator<<(StringBuilder &logger, Size t) {
static constexpr size_t sizes_n = sizeof(sizes) / sizeof(NamedValue);
size_t i = 0;
- while (i + 1 < sizes_n && t.size_ > 10 * sizes[i + 1].value) {
+ while (i + 1 < sizes_n && t.size_ >= 100000 * sizes[i].value) {
i++;
}
- logger << t.size_ / sizes[i].value << sizes[i].name;
+ logger << t.size_ / sizes[i].value << Slice(sizes[i].name);
return logger;
}
@@ -230,6 +227,19 @@ StringBuilder &operator<<(StringBuilder &stream, const Array<ArrayT> &array) {
return stream << Slice("}");
}
+inline StringBuilder &operator<<(StringBuilder &stream, const Array<vector<bool>> &array) {
+ bool first = true;
+ stream << Slice("{");
+ for (bool x : array.ref) {
+ if (!first) {
+ stream << Slice(", ");
+ }
+ stream << x;
+ first = false;
+ }
+ return stream << Slice("}");
+}
+
template <class ArrayT>
Array<ArrayT> as_array(const ArrayT &array) {
return Array<ArrayT>{array};
@@ -291,10 +301,26 @@ StringBuilder &operator<<(StringBuilder &sb, const Concat<T> &concat) {
}
template <class... ArgsT>
-auto concat(const ArgsT &... args) {
+auto concat(const ArgsT &...args) {
return Concat<decltype(std::tie(args...))>{std::tie(args...)};
}
+template <class F>
+struct Lambda {
+ const F &f;
+};
+
+template <class F>
+StringBuilder &operator<<(StringBuilder &sb, const Lambda<F> &f) {
+ f.f(sb);
+ return sb;
+}
+
+template <class LambdaT>
+Lambda<LambdaT> lambda(const LambdaT &lambda) {
+ return Lambda<LambdaT>{lambda};
+}
+
} // namespace format
using format::tag;
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/int_types.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/int_types.h
index 08ff1099c2..25493fa183 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/int_types.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/int_types.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,7 +10,6 @@
#include <cstddef>
#include <cstdint>
-#include <cstring>
namespace td {
@@ -43,23 +42,4 @@ static_assert(static_cast<char>(-256) == 0, "Unexpected cast to char implementat
#pragma warning(pop)
#endif
-template <size_t size>
-struct UInt {
- static_assert(size % 8 == 0, "size should be divisible by 8");
- uint8 raw[size / 8];
-};
-
-template <size_t size>
-inline bool operator==(const UInt<size> &a, const UInt<size> &b) {
- return std::memcmp(a.raw, b.raw, sizeof(a.raw)) == 0;
-}
-
-template <size_t size>
-inline bool operator!=(const UInt<size> &a, const UInt<size> &b) {
- return !(a == b);
-}
-
-using UInt128 = UInt<128>;
-using UInt256 = UInt<256>;
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/invoke.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/invoke.h
index e9e56fc2c5..d69a8422e3 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/invoke.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/invoke.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -54,7 +54,7 @@ struct is_reference_wrapper<std::reference_wrapper<U>> : std::true_type {};
template <class Base, class T, class Derived, class... Args>
auto invoke_impl(T Base::*pmf, Derived &&ref,
- Args &&... args) noexcept(noexcept((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...)))
+ Args &&...args) noexcept(noexcept((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...)))
-> std::enable_if_t<std::is_function<T>::value && std::is_base_of<Base, std::decay<Derived>>::value,
decltype((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...))> {
return (std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...);
@@ -62,7 +62,7 @@ auto invoke_impl(T Base::*pmf, Derived &&ref,
template <class Base, class T, class RefWrap, class... Args>
auto invoke_impl(T Base::*pmf, RefWrap &&ref,
- Args &&... args) noexcept(noexcept((ref.get().*pmf)(std::forward<Args>(args)...)))
+ Args &&...args) noexcept(noexcept((ref.get().*pmf)(std::forward<Args>(args)...)))
-> std::enable_if_t<std::is_function<T>::value && is_reference_wrapper<std::decay_t<RefWrap>>::value,
decltype((ref.get().*pmf)(std::forward<Args>(args)...))>
@@ -72,7 +72,7 @@ auto invoke_impl(T Base::*pmf, RefWrap &&ref,
template <class Base, class T, class Pointer, class... Args>
auto invoke_impl(T Base::*pmf, Pointer &&ptr,
- Args &&... args) noexcept(noexcept(((*std::forward<Pointer>(ptr)).*pmf)(std::forward<Args>(args)...)))
+ Args &&...args) noexcept(noexcept(((*std::forward<Pointer>(ptr)).*pmf)(std::forward<Args>(args)...)))
-> std::enable_if_t<std::is_function<T>::value && !is_reference_wrapper<std::decay_t<Pointer>>::value &&
!std::is_base_of<Base, std::decay_t<Pointer>>::value,
decltype(((*std::forward<Pointer>(ptr)).*pmf)(std::forward<Args>(args)...))> {
@@ -102,7 +102,7 @@ auto invoke_impl(T Base::*pmd, Pointer &&ptr) noexcept(noexcept((*std::forward<P
}
template <class F, class... Args>
-auto invoke_impl(F &&f, Args &&... args) noexcept(noexcept(std::forward<F>(f)(std::forward<Args>(args)...)))
+auto invoke_impl(F &&f, Args &&...args) noexcept(noexcept(std::forward<F>(f)(std::forward<Args>(args)...)))
-> std::enable_if_t<!std::is_member_pointer<std::decay_t<F>>::value,
decltype(std::forward<F>(f)(std::forward<Args>(args)...))> {
return std::forward<F>(f)(std::forward<Args>(args)...);
@@ -110,24 +110,24 @@ auto invoke_impl(F &&f, Args &&... args) noexcept(noexcept(std::forward<F>(f)(st
template <class F, class... ArgTypes>
auto invoke(F &&f,
- ArgTypes &&... args) noexcept(noexcept(invoke_impl(std::forward<F>(f), std::forward<ArgTypes>(args)...)))
+ ArgTypes &&...args) noexcept(noexcept(invoke_impl(std::forward<F>(f), std::forward<ArgTypes>(args)...)))
-> decltype(invoke_impl(std::forward<F>(f), std::forward<ArgTypes>(args)...)) {
return invoke_impl(std::forward<F>(f), std::forward<ArgTypes>(args)...);
}
template <class F, class... Args, std::size_t... S>
-void call_tuple_impl(F &func, std::tuple<Args...> &&tuple, IntSeq<S...>) {
- func(std::forward<Args>(std::get<S>(tuple))...);
+auto call_tuple_impl(F &&func, std::tuple<Args...> &&tuple, IntSeq<S...>) {
+ return func(std::forward<Args>(std::get<S>(tuple))...);
}
template <class... Args, std::size_t... S>
-void invoke_tuple_impl(std::tuple<Args...> &&tuple, IntSeq<S...>) {
- invoke(std::forward<Args>(std::get<S>(tuple))...);
+auto invoke_tuple_impl(std::tuple<Args...> &&tuple, IntSeq<S...>) {
+ return invoke(std::forward<Args>(std::get<S>(tuple))...);
}
-template <class Actor, class F, class... Args, std::size_t... S>
-void mem_call_tuple_impl(Actor *actor, F &func, std::tuple<Args...> &&tuple, IntSeq<S...>) {
- (actor->*func)(std::forward<Args>(std::get<S>(tuple))...);
+template <class ActorT, class F, class... Args, std::size_t... S>
+auto mem_call_tuple_impl(ActorT *actor, std::tuple<F, Args...> &&tuple, IntSeq<0, S...>) {
+ return (actor->*std::get<0>(tuple))(std::forward<Args>(std::get<S>(tuple))...);
}
template <class F, class... Args, std::size_t... S>
@@ -151,18 +151,18 @@ class LogicAnd {
};
template <class F, class... Args>
-void call_tuple(F &func, std::tuple<Args...> &&tuple) {
- detail::call_tuple_impl(func, std::move(tuple), detail::IntRange<sizeof...(Args)>());
+auto call_tuple(F &&func, std::tuple<Args...> &&tuple) {
+ return detail::call_tuple_impl(func, std::move(tuple), detail::IntRange<sizeof...(Args)>());
}
template <class... Args>
-void invoke_tuple(std::tuple<Args...> &&tuple) {
- detail::invoke_tuple_impl(std::move(tuple), detail::IntRange<sizeof...(Args)>());
+auto invoke_tuple(std::tuple<Args...> &&tuple) {
+ return detail::invoke_tuple_impl(std::move(tuple), detail::IntRange<sizeof...(Args)>());
}
-template <class Actor, class F, class... Args>
-void mem_call_tuple(Actor *actor, F &func, std::tuple<Args...> &&tuple) {
- detail::mem_call_tuple_impl(actor, func, std::move(tuple), detail::IntRange<sizeof...(Args)>());
+template <class ActorT, class... Args>
+auto mem_call_tuple(ActorT *actor, std::tuple<Args...> &&tuple) {
+ return detail::mem_call_tuple_impl(actor, std::move(tuple), detail::IntRange<sizeof...(Args)>());
}
template <class F, class... Args>
@@ -175,4 +175,36 @@ void tuple_for_each(const std::tuple<Args...> &tuple, const F &func) {
detail::tuple_for_each_impl(tuple, func, detail::IntRange<sizeof...(Args)>());
}
+template <size_t N, class Arg, class... Args, std::enable_if_t<N == 0, int> = 0>
+auto &&get_nth_argument(Arg &&arg, Args &&...args) {
+ return std::forward<Arg>(arg);
+}
+
+template <size_t N, class Arg, class... Args, std::enable_if_t<N != 0, int> = 0>
+auto &&get_nth_argument(Arg &&arg, Args &&...args) {
+ return get_nth_argument<N - 1>(std::forward<Args &&>(args)...);
+}
+
+template <class... Args>
+auto &&get_last_argument(Args &&...args) {
+ return get_nth_argument<sizeof...(Args) - 1>(std::forward<Args &&>(args)...);
+}
+
+namespace detail {
+template <class F, class... Args, std::size_t... S>
+auto call_n_arguments_impl(IntSeq<S...>, F &&f, Args &&...args) {
+ return f(get_nth_argument<S>(std::forward<Args>(args)...)...);
+}
+} // namespace detail
+
+template <size_t N, class F, class... Args>
+auto call_n_arguments(F &&f, Args &&...args) {
+ return detail::call_n_arguments_impl(detail::IntRange<N>(), f, std::forward<Args>(args)...);
+}
+
+template <class F, class X, class = void>
+struct is_callable final : public std::false_type {};
+template <class F, class X>
+struct is_callable<F, X, decltype(std::declval<F>()(std::declval<X>()))> final : public std::true_type {};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.cpp
index 17403ff87b..563575bdba 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.cpp
@@ -1,19 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/logging.h"
+#include "td/utils/ExitGuard.h"
#include "td/utils/port/Clocks.h"
-#include "td/utils/port/Fd.h"
#include "td/utils/port/thread_local.h"
#include "td/utils/Slice.h"
-#include "td/utils/Time.h"
+#include "td/utils/TsCerr.h"
#include <atomic>
#include <cstdlib>
+#include <limits>
+#include <mutex>
#if TD_ANDROID
#include <android/log.h>
@@ -27,115 +29,129 @@
namespace td {
-int VERBOSITY_NAME(level) = VERBOSITY_NAME(DEBUG) + 1;
-int VERBOSITY_NAME(net_query) = VERBOSITY_NAME(INFO);
-int VERBOSITY_NAME(td_requests) = VERBOSITY_NAME(INFO);
-int VERBOSITY_NAME(dc) = VERBOSITY_NAME(DEBUG) + 2;
-int VERBOSITY_NAME(files) = VERBOSITY_NAME(DEBUG) + 2;
-int VERBOSITY_NAME(mtproto) = VERBOSITY_NAME(DEBUG) + 7;
-int VERBOSITY_NAME(connections) = VERBOSITY_NAME(DEBUG) + 8;
-int VERBOSITY_NAME(raw_mtproto) = VERBOSITY_NAME(DEBUG) + 10;
-int VERBOSITY_NAME(fd) = VERBOSITY_NAME(DEBUG) + 9;
-int VERBOSITY_NAME(actor) = VERBOSITY_NAME(DEBUG) + 10;
-int VERBOSITY_NAME(buffer) = VERBOSITY_NAME(DEBUG) + 10;
-int VERBOSITY_NAME(sqlite) = VERBOSITY_NAME(DEBUG) + 10;
+LogOptions log_options;
+
+static std::atomic<int> max_callback_verbosity_level{-2};
+static std::atomic<OnLogMessageCallback> on_log_message_callback{nullptr};
+
+void set_log_message_callback(int max_verbosity_level, OnLogMessageCallback callback) {
+ if (callback == nullptr) {
+ max_verbosity_level = -2;
+ }
+
+ max_callback_verbosity_level = max_verbosity_level;
+ on_log_message_callback = callback;
+}
+
+void LogInterface::append(int log_level, CSlice slice) {
+ do_append(log_level, slice);
+ if (log_level == VERBOSITY_NAME(FATAL)) {
+ process_fatal_error(slice);
+ } else if (log_level <= max_callback_verbosity_level.load(std::memory_order_relaxed)) {
+ auto callback = on_log_message_callback.load(std::memory_order_relaxed);
+ if (callback != nullptr) {
+ callback(log_level, slice);
+ }
+ }
+}
TD_THREAD_LOCAL const char *Logger::tag_ = nullptr;
TD_THREAD_LOCAL const char *Logger::tag2_ = nullptr;
-Logger::Logger(LogInterface &log, int log_level, Slice file_name, int line_num, Slice comment, bool simple_mode)
- : Logger(log, log_level, simple_mode) {
- if (simple_mode) {
+Logger::Logger(LogInterface &log, const LogOptions &options, int log_level, Slice file_name, int line_num,
+ Slice comment)
+ : Logger(log, options, log_level) {
+ if (log_level == VERBOSITY_NAME(PLAIN) && &options == &log_options) {
+ return;
+ }
+ if (!options_.add_info) {
+ return;
+ }
+ if (ExitGuard::is_exited()) {
return;
}
- auto last_slash_ = static_cast<int32>(file_name.size()) - 1;
- while (last_slash_ >= 0 && file_name[last_slash_] != '/' && file_name[last_slash_] != '\\') {
- last_slash_--;
+ // log level
+ sb_ << '[';
+ if (static_cast<uint32>(log_level) < 10) {
+ sb_ << ' ' << static_cast<char>('0' + log_level);
+ } else {
+ sb_ << log_level;
}
- file_name = file_name.substr(last_slash_ + 1);
+ sb_ << ']';
+ // thread id
auto thread_id = get_thread_id();
+ sb_ << "[t";
+ if (static_cast<uint32>(thread_id) < 10) {
+ sb_ << ' ' << static_cast<char>('0' + thread_id);
+ } else {
+ sb_ << thread_id;
+ }
+ sb_ << ']';
- (*this) << '[';
- if (log_level < 10) {
- (*this) << ' ';
+ // timestamp
+ auto time = Clocks::system();
+ auto unix_time = static_cast<uint32>(time);
+ auto nanoseconds = static_cast<uint32>((time - unix_time) * 1e9);
+ sb_ << '[' << unix_time << '.';
+ uint32 limit = 100000000;
+ while (nanoseconds < limit && limit > 1) {
+ sb_ << '0';
+ limit /= 10;
}
- (*this) << log_level << "][t";
- if (thread_id < 10) {
- (*this) << ' ';
+ sb_ << nanoseconds << ']';
+
+ // file : line
+ if (!file_name.empty()) {
+ auto last_slash_ = static_cast<int32>(file_name.size()) - 1;
+ while (last_slash_ >= 0 && file_name[last_slash_] != '/' && file_name[last_slash_] != '\\') {
+ last_slash_--;
+ }
+ file_name = file_name.substr(last_slash_ + 1);
+ sb_ << '[' << file_name << ':' << static_cast<uint32>(line_num) << ']';
}
- (*this) << thread_id << "][" << StringBuilder::FixedDouble(Clocks::system(), 9) << "][" << file_name << ':'
- << line_num << ']';
+
+ // context from tag_
if (tag_ != nullptr && *tag_) {
- (*this) << "[#" << Slice(tag_) << "]";
+ sb_ << "[#" << Slice(tag_) << ']';
}
+
+ // context from tag2_
if (tag2_ != nullptr && *tag2_) {
- (*this) << "[!" << Slice(tag2_) << "]";
+ sb_ << "[!" << Slice(tag2_) << ']';
}
+
+ // comment (e.g. condition in LOG_IF)
if (!comment.empty()) {
- (*this) << "[&" << comment << "]";
+ sb_ << "[&" << comment << ']';
}
- (*this) << "\t";
+
+ sb_ << '\t';
}
Logger::~Logger() {
- if (!simple_mode_) {
+ if (ExitGuard::is_exited()) {
+ return;
+ }
+ if (options_.fix_newlines) {
sb_ << '\n';
auto slice = as_cslice();
if (slice.back() != '\n') {
slice.back() = '\n';
}
- }
-
- log_.append(as_cslice(), log_level_);
-}
-
-TsCerr::TsCerr() {
- enterCritical();
-}
-TsCerr::~TsCerr() {
- exitCritical();
-}
-TsCerr &TsCerr::operator<<(Slice slice) {
- auto &fd = Fd::Stderr();
- if (fd.empty()) {
- return *this;
- }
- double end_time = 0;
- while (!slice.empty()) {
- auto res = fd.write(slice);
- if (res.is_error()) {
- if (res.error().code() == EPIPE) {
- break;
- }
- // Resource temporary unavailable
- if (end_time == 0) {
- end_time = Time::now() + 0.01;
- } else if (Time::now() > end_time) {
- break;
- }
- continue;
+ while (slice.size() > 1 && slice[slice.size() - 2] == '\n') {
+ slice.back() = '\0';
+ slice = MutableCSlice(slice.begin(), slice.begin() + slice.size() - 1);
}
- slice.remove_prefix(res.ok());
+ log_.append(log_level_, slice);
+ } else {
+ log_.append(log_level_, as_cslice());
}
- return *this;
}
-void TsCerr::enterCritical() {
- while (lock_.test_and_set(std::memory_order_acquire)) {
- // spin
- }
-}
-
-void TsCerr::exitCritical() {
- lock_.clear(std::memory_order_release);
-}
-TsCerr::Lock TsCerr::lock_ = ATOMIC_FLAG_INIT;
-
-class DefaultLog : public LogInterface {
- public:
- void append(CSlice slice, int log_level) override {
+class DefaultLog final : public LogInterface {
+ void do_append(int log_level, CSlice slice) final {
#if TD_ANDROID
switch (log_level) {
case VERBOSITY_NAME(FATAL):
@@ -157,27 +173,26 @@ class DefaultLog : public LogInterface {
#elif TD_TIZEN
switch (log_level) {
case VERBOSITY_NAME(FATAL):
- dlog_print(DLOG_ERROR, DLOG_TAG, slice.c_str());
+ dlog_print(DLOG_ERROR, DLOG_TAG, "%s", slice.c_str());
break;
case VERBOSITY_NAME(ERROR):
- dlog_print(DLOG_ERROR, DLOG_TAG, slice.c_str());
+ dlog_print(DLOG_ERROR, DLOG_TAG, "%s", slice.c_str());
break;
case VERBOSITY_NAME(WARNING):
- dlog_print(DLOG_WARN, DLOG_TAG, slice.c_str());
+ dlog_print(DLOG_WARN, DLOG_TAG, "%s", slice.c_str());
break;
case VERBOSITY_NAME(INFO):
- dlog_print(DLOG_INFO, DLOG_TAG, slice.c_str());
+ dlog_print(DLOG_INFO, DLOG_TAG, "%s", slice.c_str());
break;
default:
- dlog_print(DLOG_DEBUG, DLOG_TAG, slice.c_str());
+ dlog_print(DLOG_DEBUG, DLOG_TAG, "%s", slice.c_str());
break;
}
#elif TD_EMSCRIPTEN
switch (log_level) {
case VERBOSITY_NAME(FATAL):
- emscripten_log(
- EM_LOG_ERROR | EM_LOG_CONSOLE | EM_LOG_C_STACK | EM_LOG_JS_STACK | EM_LOG_DEMANGLE | EM_LOG_FUNC_PARAMS,
- "%s", slice.c_str());
+ emscripten_log(EM_LOG_ERROR | EM_LOG_CONSOLE | EM_LOG_C_STACK | EM_LOG_JS_STACK | EM_LOG_FUNC_PARAMS, "%s",
+ slice.c_str());
EM_ASM(throw(UTF8ToString($0)), slice.c_str());
break;
case VERBOSITY_NAME(ERROR):
@@ -192,28 +207,31 @@ class DefaultLog : public LogInterface {
}
#elif !TD_WINDOWS
Slice color;
+ Slice no_color("\x1b[0m");
switch (log_level) {
case VERBOSITY_NAME(FATAL):
case VERBOSITY_NAME(ERROR):
- color = TC_RED;
+ color = Slice("\x1b[1;31m"); // red
break;
case VERBOSITY_NAME(WARNING):
- color = TC_YELLOW;
+ color = Slice("\x1b[1;33m"); // yellow
break;
case VERBOSITY_NAME(INFO):
- color = TC_CYAN;
+ color = Slice("\x1b[1;36m"); // cyan
+ break;
+ default:
+ no_color = Slice();
break;
}
- TsCerr() << color << slice << TC_EMPTY;
+ if (!slice.empty() && slice.back() == '\n') {
+ TsCerr() << color << slice.substr(0, slice.size() - 1) << no_color << "\n";
+ } else {
+ TsCerr() << color << slice << no_color;
+ }
#else
// TODO: color
TsCerr() << slice;
#endif
- if (log_level == VERBOSITY_NAME(FATAL)) {
- process_fatal_error(slice);
- }
- }
- void rotate() override {
}
};
static DefaultLog default_log;
@@ -221,18 +239,59 @@ static DefaultLog default_log;
LogInterface *const default_log_interface = &default_log;
LogInterface *log_interface = default_log_interface;
-static OnFatalErrorCallback on_fatal_error_callback = nullptr;
+void process_fatal_error(CSlice message) {
+ if (0 <= max_callback_verbosity_level.load(std::memory_order_relaxed)) {
+ auto callback = on_log_message_callback.load(std::memory_order_relaxed);
+ if (callback != nullptr) {
+ callback(0, message);
+ }
+ }
-void set_log_fatal_error_callback(OnFatalErrorCallback callback) {
- on_fatal_error_callback = callback;
+ std::abort();
}
-void process_fatal_error(CSlice message) {
- auto callback = on_fatal_error_callback;
- if (callback) {
- callback(message);
+static std::atomic<uint32> log_guard;
+
+LogGuard::LogGuard() {
+ uint32 expected = 0;
+ while (!log_guard.compare_exchange_strong(expected, 1, std::memory_order_relaxed, std::memory_order_relaxed)) {
+ // spin
+ CHECK(expected == 1);
+ expected = 0;
}
- std::abort();
}
+LogGuard::~LogGuard() {
+ CHECK(log_guard.load(std::memory_order_relaxed) == 1);
+ log_guard.store(0, std::memory_order_relaxed);
+}
+
+bool has_log_guard() {
+ return log_guard.load(std::memory_order_relaxed) == 1;
+}
+
+namespace {
+std::mutex sdl_mutex;
+int sdl_cnt = 0;
+int sdl_verbosity = 0;
+} // namespace
+
+ScopedDisableLog::ScopedDisableLog() {
+ std::unique_lock<std::mutex> guard(sdl_mutex);
+ if (sdl_cnt == 0) {
+ sdl_verbosity = set_verbosity_level(std::numeric_limits<int>::min());
+ }
+ sdl_cnt++;
+}
+
+ScopedDisableLog::~ScopedDisableLog() {
+ std::unique_lock<std::mutex> guard(sdl_mutex);
+ sdl_cnt--;
+ if (sdl_cnt == 0) {
+ set_verbosity_level(sdl_verbosity);
+ }
+}
+
+static ExitGuard exit_guard;
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.h
index 629a4f248a..b6f45eb686 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -25,38 +25,33 @@
#include "td/utils/common.h"
#include "td/utils/port/thread_local.h"
-#include "td/utils/Slice-decl.h"
+#include "td/utils/Slice.h"
#include "td/utils/StackAllocator.h"
#include "td/utils/StringBuilder.h"
#include <atomic>
#include <type_traits>
-#define PSTR_IMPL() ::td::Logger(::td::NullLog().ref(), 0, true)
-#define PSLICE() ::td::detail::Slicify() & PSTR_IMPL()
-#define PSTRING() ::td::detail::Stringify() & PSTR_IMPL()
-#define PSLICE_SAFE() ::td::detail::SlicifySafe() & PSTR_IMPL()
-#define PSTRING_SAFE() ::td::detail::StringifySafe() & PSTR_IMPL()
-
#define VERBOSITY_NAME(x) verbosity_##x
-#define GET_VERBOSITY_LEVEL() (::td::VERBOSITY_NAME(level))
-#define SET_VERBOSITY_LEVEL(new_level) (::td::VERBOSITY_NAME(level) = (new_level))
+#define GET_VERBOSITY_LEVEL() (::td::get_verbosity_level())
+#define SET_VERBOSITY_LEVEL(new_level) (::td::set_verbosity_level(new_level))
#ifndef STRIP_LOG
#define STRIP_LOG VERBOSITY_NAME(DEBUG)
#endif
#define LOG_IS_STRIPPED(strip_level) \
- (std::integral_constant<int, VERBOSITY_NAME(strip_level)>() > std::integral_constant<int, STRIP_LOG>())
+ (::std::integral_constant<int, VERBOSITY_NAME(strip_level)>() > ::std::integral_constant<int, STRIP_LOG>())
+
+#define LOGGER(interface, options, level, comment) ::td::Logger(interface, options, level, __FILE__, __LINE__, comment)
-#define LOGGER(level, comment) \
- ::td::Logger(*::td::log_interface, VERBOSITY_NAME(level), __FILE__, __LINE__, comment, \
- VERBOSITY_NAME(level) == VERBOSITY_NAME(PLAIN))
+#define LOG_IMPL_FULL(interface, options, strip_level, runtime_level, condition, comment) \
+ LOG_IS_STRIPPED(strip_level) || runtime_level > options.get_level() || !(condition) \
+ ? (void)0 \
+ : ::td::detail::Voidify() & LOGGER(interface, options, runtime_level, comment)
-#define LOG_IMPL(strip_level, level, condition, comment) \
- LOG_IS_STRIPPED(strip_level) || VERBOSITY_NAME(level) > GET_VERBOSITY_LEVEL() || !(condition) \
- ? (void)0 \
- : ::td::detail::Voidify() & LOGGER(level, comment)
+#define LOG_IMPL(strip_level, level, condition, comment) \
+ LOG_IMPL_FULL(*::td::log_interface, ::td::log_options, strip_level, VERBOSITY_NAME(level), condition, comment)
#define LOG(level) LOG_IMPL(level, level, true, ::td::Slice())
#define LOG_IF(level, condition) LOG_IMPL(level, level, condition, #condition)
@@ -64,8 +59,6 @@
#define VLOG(level) LOG_IMPL(DEBUG, level, true, TD_DEFINE_STR(level))
#define VLOG_IF(level, condition) LOG_IMPL(DEBUG, level, condition, TD_DEFINE_STR(level) " " #condition)
-#define LOG_ROTATE() ::td::log_interface->rotate()
-
#define LOG_TAG ::td::Logger::tag_
#define LOG_TAG2 ::td::Logger::tag2_
@@ -78,25 +71,26 @@ inline bool no_return_func() {
}
// clang-format off
-#ifdef CHECK
- #undef CHECK
-#endif
+#define DUMMY_LOG_CHECK(condition) LOG_IF(NEVER, !(condition))
+
#ifdef TD_DEBUG
#if TD_MSVC
- #define CHECK(condition) \
+ #define LOG_CHECK(condition) \
__analysis_assume(!!(condition)); \
LOG_IMPL(FATAL, FATAL, !(condition), #condition)
#else
- #define CHECK(condition) LOG_IMPL(FATAL, FATAL, !(condition) && no_return_func(), #condition)
+ #define LOG_CHECK(condition) LOG_IMPL(FATAL, FATAL, !(condition) && no_return_func(), #condition)
#endif
#else
- #define CHECK(condition) LOG_IF(NEVER, !(condition))
+ #define LOG_CHECK DUMMY_LOG_CHECK
#endif
-// clang-format on
-#define UNREACHABLE() \
- LOG(FATAL); \
- ::td::process_fatal_error("Unreachable in " __FILE__ " at " TD_DEFINE_STR(__LINE__))
+#if NDEBUG
+ #define LOG_DCHECK DUMMY_LOG_CHECK
+#else
+ #define LOG_DCHECK LOG_CHECK
+#endif
+// clang-format on
constexpr int VERBOSITY_NAME(PLAIN) = -1;
constexpr int VERBOSITY_NAME(FATAL) = 0;
@@ -107,19 +101,53 @@ constexpr int VERBOSITY_NAME(DEBUG) = 4;
constexpr int VERBOSITY_NAME(NEVER) = 1024;
namespace td {
-extern int VERBOSITY_NAME(level);
-// TODO Not part of utils. Should be in some separate file
-extern int VERBOSITY_NAME(mtproto);
-extern int VERBOSITY_NAME(raw_mtproto);
-extern int VERBOSITY_NAME(connections);
-extern int VERBOSITY_NAME(dc);
-extern int VERBOSITY_NAME(fd);
-extern int VERBOSITY_NAME(net_query);
-extern int VERBOSITY_NAME(td_requests);
-extern int VERBOSITY_NAME(actor);
-extern int VERBOSITY_NAME(buffer);
-extern int VERBOSITY_NAME(files);
-extern int VERBOSITY_NAME(sqlite);
+
+struct LogOptions {
+ std::atomic<int> level{VERBOSITY_NAME(DEBUG) + 1};
+ bool fix_newlines{true};
+ bool add_info{true};
+
+ int get_level() const {
+ return level.load(std::memory_order_relaxed);
+ }
+ int set_level(int new_level) {
+ return level.exchange(new_level);
+ }
+
+ static const LogOptions &plain() {
+ static LogOptions plain_options{0, false, false};
+ return plain_options;
+ }
+
+ constexpr LogOptions() = default;
+ constexpr LogOptions(int level, bool fix_newlines, bool add_info)
+ : level(level), fix_newlines(fix_newlines), add_info(add_info) {
+ }
+
+ LogOptions(const LogOptions &other) : LogOptions(other.level.load(), other.fix_newlines, other.add_info) {
+ }
+
+ LogOptions &operator=(const LogOptions &other) {
+ if (this == &other) {
+ return *this;
+ }
+ level = other.level.load();
+ fix_newlines = other.fix_newlines;
+ add_info = other.add_info;
+ return *this;
+ }
+ LogOptions(LogOptions &&) = delete;
+ LogOptions &operator=(LogOptions &&) = delete;
+ ~LogOptions() = default;
+};
+
+extern LogOptions log_options;
+inline int set_verbosity_level(int level) {
+ return log_options.set_level(level);
+}
+inline int get_verbosity_level() {
+ return log_options.get_level();
+}
class LogInterface {
public:
@@ -129,69 +157,43 @@ class LogInterface {
LogInterface(LogInterface &&) = delete;
LogInterface &operator=(LogInterface &&) = delete;
virtual ~LogInterface() = default;
- virtual void append(CSlice slice, int log_level_) = 0;
- virtual void rotate() = 0;
-};
-class NullLog : public LogInterface {
- public:
- void append(CSlice slice, int log_level_) override {
- }
- void rotate() override {
+ void append(int log_level, CSlice slice);
+
+ virtual void after_rotation() {
}
- NullLog &ref() {
- return *this;
+
+ virtual vector<string> get_file_paths() {
+ return {};
}
+
+ virtual void do_append(int log_level, CSlice slice) = 0;
};
extern LogInterface *const default_log_interface;
extern LogInterface *log_interface;
-using OnFatalErrorCallback = void (*)(CSlice message);
-void set_log_fatal_error_callback(OnFatalErrorCallback callback);
-
[[noreturn]] void process_fatal_error(CSlice message);
-#define TC_RED "\e[1;31m"
-#define TC_BLUE "\e[1;34m"
-#define TC_CYAN "\e[1;36m"
-#define TC_GREEN "\e[1;32m"
-#define TC_YELLOW "\e[1;33m"
-#define TC_EMPTY "\e[0m"
-
-class TsCerr {
- public:
- TsCerr();
- TsCerr(const TsCerr &) = delete;
- TsCerr &operator=(const TsCerr &) = delete;
- TsCerr(TsCerr &&) = delete;
- TsCerr &operator=(TsCerr &&) = delete;
- ~TsCerr();
- TsCerr &operator<<(Slice slice);
-
- private:
- using Lock = std::atomic_flag;
- static Lock lock_;
-
- void enterCritical();
- void exitCritical();
-};
+using OnLogMessageCallback = void (*)(int verbosity_level, CSlice message);
+void set_log_message_callback(int max_verbosity_level, OnLogMessageCallback callback);
class Logger {
+ static const size_t BUFFER_SIZE = 128 * 1024;
+
public:
- static const int BUFFER_SIZE = 128 * 1024;
- Logger(LogInterface &log, int log_level, bool simple_mode = false)
+ Logger(LogInterface &log, const LogOptions &options, int log_level)
: buffer_(StackAllocator::alloc(BUFFER_SIZE))
, log_(log)
- , log_level_(log_level)
, sb_(buffer_.as_slice())
- , simple_mode_(simple_mode) {
+ , options_(options)
+ , log_level_(log_level) {
}
- Logger(LogInterface &log, int log_level, Slice file_name, int line_num, Slice comment, bool simple_mode);
+ Logger(LogInterface &log, const LogOptions &options, int log_level, Slice file_name, int line_num, Slice comment);
template <class T>
- Logger &operator<<(const T &other) {
+ Logger &operator<<(T &&other) {
sb_ << other;
return *this;
}
@@ -214,66 +216,40 @@ class Logger {
private:
decltype(StackAllocator::alloc(0)) buffer_;
LogInterface &log_;
- int log_level_;
StringBuilder sb_;
- bool simple_mode_;
+ const LogOptions &options_;
+ int log_level_;
};
-namespace detail {
-class Voidify {
+class LogGuard {
public:
- template <class T>
- void operator&(const T &) {
- }
+ LogGuard();
+ LogGuard(const LogGuard &) = delete;
+ LogGuard &operator=(const LogGuard &) = delete;
+ LogGuard(LogGuard &&) = delete;
+ LogGuard &operator=(LogGuard &&) = delete;
+ ~LogGuard();
};
-class Slicify {
+bool has_log_guard();
+
+class ScopedDisableLog {
public:
- CSlice operator&(Logger &logger) {
- return logger.as_cslice();
- }
+ ScopedDisableLog();
+ ScopedDisableLog(const ScopedDisableLog &) = delete;
+ ScopedDisableLog &operator=(const ScopedDisableLog &) = delete;
+ ScopedDisableLog(ScopedDisableLog &&) = delete;
+ ScopedDisableLog &operator=(ScopedDisableLog &&) = delete;
+ ~ScopedDisableLog();
};
-class Stringify {
+namespace detail {
+class Voidify {
public:
- string operator&(Logger &logger) {
- return logger.as_cslice().str();
+ template <class T>
+ void operator&(const T &) {
}
};
} // namespace detail
-class TsLog : public LogInterface {
- public:
- explicit TsLog(LogInterface *log) : log_(log) {
- }
- void init(LogInterface *log) {
- enter_critical();
- log_ = log;
- exit_critical();
- }
- void append(CSlice slice, int level) override {
- enter_critical();
- log_->append(slice, level);
- exit_critical();
- }
- void rotate() override {
- enter_critical();
- log_->rotate();
- exit_critical();
- }
-
- private:
- LogInterface *log_ = nullptr;
- std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
- void enter_critical() {
- while (lock_.test_and_set(std::memory_order_acquire)) {
- // spin
- }
- }
- void exit_critical() {
- lock_.clear(std::memory_order_release);
- }
-};
} // namespace td
-
-#include "td/utils/Slice.h"
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.cpp
index f3068ca6d3..df86382031 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,6 +7,7 @@
#include "td/utils/misc.h"
#include "td/utils/port/thread_local.h"
+#include "td/utils/utf8.h"
#include <algorithm>
#include <cstdlib>
@@ -16,7 +17,7 @@
namespace td {
char *str_dup(Slice str) {
- char *res = static_cast<char *>(std::malloc(str.size() + 1));
+ auto *res = static_cast<char *>(std::malloc(str.size() + 1));
if (res == nullptr) {
return nullptr;
}
@@ -25,23 +26,41 @@ char *str_dup(Slice str) {
return res;
}
-string implode(vector<string> v, char delimiter) {
+string implode(const vector<string> &v, char delimiter) {
string result;
- for (auto &str : v) {
- if (!result.empty()) {
+ for (size_t i = 0; i < v.size(); i++) {
+ if (i != 0) {
result += delimiter;
}
- result += str;
+ result += v[i];
}
return result;
}
+string lpad(string str, size_t size, char c) {
+ if (str.size() >= size) {
+ return str;
+ }
+ return string(size - str.size(), c) + str;
+}
+
+string lpad0(string str, size_t size) {
+ return lpad(std::move(str), size, '0');
+}
+
+string rpad(string str, size_t size, char c) {
+ if (str.size() >= size) {
+ return str;
+ }
+ return str + string(size - str.size(), c);
+}
+
string oneline(Slice str) {
string result;
result.reserve(str.size());
bool after_new_line = true;
for (auto c : str) {
- if (c != '\n') {
+ if (c != '\n' && c != '\r') {
if (after_new_line) {
if (c == ' ') {
continue;
@@ -49,7 +68,7 @@ string oneline(Slice str) {
after_new_line = false;
}
result += c;
- } else {
+ } else if (!after_new_line) {
after_new_line = true;
result += ' ';
}
@@ -60,10 +79,20 @@ string oneline(Slice str) {
return result;
}
+namespace detail {
+Status get_to_integer_safe_error(Slice str) {
+ auto status = Status::Error(PSLICE() << "Can't parse \"" << str << "\" as an integer");
+ if (!check_utf8(status.message())) {
+ status = Status::Error("Strings must be encoded in UTF-8");
+ }
+ return status;
+}
+} // namespace detail
+
double to_double(Slice str) {
static TD_THREAD_LOCAL std::stringstream *ss;
if (init_thread_local<std::stringstream>(ss)) {
- ss->imbue(std::locale::classic());
+ auto previous_locale = ss->imbue(std::locale::classic());
} else {
ss->str(std::string());
ss->clear();
@@ -75,4 +104,157 @@ double to_double(Slice str) {
return result;
}
+Result<string> hex_decode(Slice hex) {
+ if (hex.size() % 2 != 0) {
+ return Status::Error("Wrong hex string length");
+ }
+ string result(hex.size() / 2, '\0');
+ for (size_t i = 0; i < result.size(); i++) {
+ int high = hex_to_int(hex[i + i]);
+ int low = hex_to_int(hex[i + i + 1]);
+ if (high == 16 || low == 16) {
+ return Status::Error("Wrong hex string");
+ }
+ result[i] = static_cast<char>(high * 16 + low); // TODO implementation-defined
+ }
+ return std::move(result);
+}
+
+string hex_encode(Slice data) {
+ const char *hex = "0123456789abcdef";
+ string res;
+ res.reserve(2 * data.size());
+ for (unsigned char c : data) {
+ res.push_back(hex[c >> 4]);
+ res.push_back(hex[c & 15]);
+ }
+ return res;
+}
+
+static bool is_url_char(char c) {
+ return is_alnum(c) || c == '-' || c == '.' || c == '_' || c == '~';
+}
+
+string url_encode(Slice data) {
+ size_t length = 3 * data.size();
+ for (auto c : data) {
+ length -= 2 * is_url_char(c);
+ }
+ if (length == data.size()) {
+ return data.str();
+ }
+ string result;
+ result.reserve(length);
+ for (auto c : data) {
+ if (is_url_char(c)) {
+ result += c;
+ } else {
+ auto ch = static_cast<unsigned char>(c);
+ result += '%';
+ result += "0123456789ABCDEF"[ch / 16];
+ result += "0123456789ABCDEF"[ch % 16];
+ }
+ }
+ CHECK(result.size() == length);
+ return result;
+}
+
+size_t url_decode(Slice from, MutableSlice to, bool decode_plus_sign_as_space) {
+ size_t to_i = 0;
+ CHECK(to.size() >= from.size());
+ for (size_t from_i = 0, n = from.size(); from_i < n; from_i++) {
+ if (from[from_i] == '%' && from_i + 2 < n) {
+ int high = hex_to_int(from[from_i + 1]);
+ int low = hex_to_int(from[from_i + 2]);
+ if (high < 16 && low < 16) {
+ to[to_i++] = static_cast<char>(high * 16 + low);
+ from_i += 2;
+ continue;
+ }
+ }
+ to[to_i++] = decode_plus_sign_as_space && from[from_i] == '+' ? ' ' : from[from_i];
+ }
+ return to_i;
+}
+
+string url_decode(Slice from, bool decode_plus_sign_as_space) {
+ string to;
+ to.resize(from.size());
+ to.resize(url_decode(from, to, decode_plus_sign_as_space));
+ return to;
+}
+
+MutableSlice url_decode_inplace(MutableSlice str, bool decode_plus_sign_as_space) {
+ size_t result_size = url_decode(str, str, decode_plus_sign_as_space);
+ str.truncate(result_size);
+ return str;
+}
+
+string buffer_to_hex(Slice buffer) {
+ const char *hex = "0123456789ABCDEF";
+ string res(2 * buffer.size(), '\0');
+ for (std::size_t i = 0; i < buffer.size(); i++) {
+ auto c = buffer.ubegin()[i];
+ res[2 * i] = hex[c & 15];
+ res[2 * i + 1] = hex[c >> 4];
+ }
+ return res;
+}
+
+namespace {
+
+template <class F>
+string x_decode(Slice s, F &&f) {
+ string res;
+ for (size_t n = s.size(), i = 0; i < n; i++) {
+ if (i + 1 < n && f(s[i])) {
+ res.append(static_cast<unsigned char>(s[i + 1]), s[i]);
+ i++;
+ continue;
+ }
+ res.push_back(s[i]);
+ }
+ return res;
+}
+
+template <class F>
+string x_encode(Slice s, F &&f) {
+ string res;
+ for (size_t n = s.size(), i = 0; i < n; i++) {
+ res.push_back(s[i]);
+ if (f(s[i])) {
+ unsigned char cnt = 1;
+ while (cnt < 250 && i + cnt < n && s[i + cnt] == s[i]) {
+ cnt++;
+ }
+ res.push_back(static_cast<char>(cnt));
+ i += cnt - 1;
+ }
+ }
+ return res;
+}
+
+bool is_zero(unsigned char c) {
+ return c == 0;
+}
+
+bool is_zero_or_one(unsigned char c) {
+ return c == 0 || c == 0xff;
+}
+
+} // namespace
+
+std::string zero_encode(Slice data) {
+ return x_encode(data, is_zero);
+}
+std::string zero_decode(Slice data) {
+ return x_decode(data, is_zero);
+}
+std::string zero_one_encode(Slice data) {
+ return x_encode(data, is_zero_or_one);
+}
+std::string zero_one_decode(Slice data) {
+ return x_decode(data, is_zero_or_one);
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.h
index 62b01794ab..5d9292f43a 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,12 +9,12 @@
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include <cstdint>
#include <limits>
-#include <tuple>
#include <type_traits>
#include <utility>
@@ -33,75 +33,26 @@ std::pair<T, T> split(T s, char delimiter = ' ') {
}
template <class T>
-vector<T> full_split(T s, char delimiter = ' ') {
- T next;
+vector<T> full_split(T s, char delimiter = ' ', size_t max_parts = std::numeric_limits<size_t>::max()) {
vector<T> result;
- while (!s.empty()) {
- std::tie(next, s) = split(s, delimiter);
- result.push_back(next);
- }
- return result;
-}
-
-string implode(vector<string> v, char delimiter = ' ');
-
-namespace detail {
-
-template <typename T>
-struct transform_helper {
- template <class Func>
- auto transform(const T &v, const Func &f) {
- vector<decltype(f(*v.begin()))> result;
- result.reserve(v.size());
- for (auto &x : v) {
- result.push_back(f(x));
- }
+ if (s.empty()) {
return result;
}
-
- template <class Func>
- auto transform(T &&v, const Func &f) {
- vector<decltype(f(std::move(*v.begin())))> result;
- result.reserve(v.size());
- for (auto &x : v) {
- result.push_back(f(std::move(x)));
+ while (result.size() + 1 < max_parts) {
+ auto delimiter_pos = s.find(delimiter);
+ if (delimiter_pos == string::npos) {
+ break;
}
- return result;
- }
-};
-
-} // namespace detail
-template <class T, class Func>
-auto transform(T &&v, const Func &f) {
- return detail::transform_helper<std::decay_t<T>>().transform(std::forward<T>(v), f);
-}
-
-template <class T>
-void reset_to_empty(T &value) {
- using std::swap;
- std::decay_t<T> tmp;
- swap(tmp, value);
-}
-
-template <class T>
-auto append(vector<T> &destination, const vector<T> &source) {
- destination.insert(destination.end(), source.begin(), source.end());
-}
-
-template <class T>
-auto append(vector<T> &destination, vector<T> &&source) {
- if (destination.empty()) {
- destination.swap(source);
- return;
+ result.push_back(s.substr(0, delimiter_pos));
+ s = s.substr(delimiter_pos + 1);
}
- destination.reserve(destination.size() + source.size());
- for (auto &elem : source) {
- destination.push_back(std::move(elem));
- }
- reset_to_empty(source);
+ result.push_back(std::move(s));
+ return result;
}
+string implode(const vector<string> &v, char delimiter = ' ');
+
inline bool begins_with(Slice str, Slice prefix) {
return prefix.size() <= str.size() && prefix == Slice(str.data(), prefix.size());
}
@@ -118,10 +69,11 @@ inline char to_lower(char c) {
return c;
}
-inline void to_lower_inplace(MutableSlice slice) {
+inline MutableSlice to_lower_inplace(MutableSlice slice) {
for (auto &c : slice) {
c = to_lower(c);
}
+ return slice;
}
inline string to_lower(Slice slice) {
@@ -191,6 +143,12 @@ T trim(T str) {
return T(begin, end);
}
+string lpad(string str, size_t size, char c);
+
+string lpad0(string str, size_t size);
+
+string rpad(string str, size_t size, char c);
+
string oneline(Slice str);
template <class T>
@@ -205,7 +163,7 @@ std::enable_if_t<std::is_signed<T>::value, T> to_integer(Slice str) {
begin++;
}
while (begin != end && is_digit(*begin)) {
- integer_value = static_cast<unsigned_T>(integer_value * 10 + (*begin++ - '0'));
+ integer_value = static_cast<unsigned_T>(integer_value * 10 + static_cast<unsigned_T>(*begin++ - '0'));
}
if (integer_value > static_cast<unsigned_T>(std::numeric_limits<T>::max())) {
static_assert(~0 + 1 == 0, "Two's complement");
@@ -227,16 +185,20 @@ std::enable_if_t<std::is_unsigned<T>::value, T> to_integer(Slice str) {
auto begin = str.begin();
auto end = str.end();
while (begin != end && is_digit(*begin)) {
- integer_value = static_cast<T>(integer_value * 10 + (*begin++ - '0'));
+ integer_value = static_cast<T>(integer_value * 10 + static_cast<T>(*begin++ - '0'));
}
return integer_value;
}
+namespace detail {
+Status get_to_integer_safe_error(Slice str);
+} // namespace detail
+
template <class T>
Result<T> to_integer_safe(Slice str) {
auto res = to_integer<T>(str);
- if (to_string(res) != str) {
- return Status::Error(PSLICE() << "Can't parse \"" << str << "\" as number");
+ if ((PSLICE() << res) != str) {
+ return detail::get_to_integer_safe_error(str);
}
return res;
}
@@ -263,6 +225,27 @@ typename std::enable_if<std::is_unsigned<T>::value, T>::type hex_to_integer(Slic
return integer_value;
}
+template <class T>
+Result<typename std::enable_if<std::is_unsigned<T>::value, T>::type> hex_to_integer_safe(Slice str) {
+ T integer_value = 0;
+ auto begin = str.begin();
+ auto end = str.end();
+ if (begin == end) {
+ return Status::Error("String is empty");
+ }
+ while (begin != end) {
+ T digit = hex_to_int(*begin++);
+ if (digit == 16) {
+ return Status::Error("String contains non-hex digit");
+ }
+ if (integer_value > std::numeric_limits<T>::max() / 16) {
+ return Status::Error("String hex number overflows");
+ }
+ integer_value = integer_value * 16 + digit;
+ }
+ return integer_value;
+}
+
double to_double(Slice str);
template <class T>
@@ -276,11 +259,23 @@ T clamp(T value, T min_value, T max_value) {
return value;
}
+Result<string> hex_decode(Slice hex);
+
+string hex_encode(Slice data);
+
+string url_encode(Slice data);
+
+size_t url_decode(Slice from, MutableSlice to, bool decode_plus_sign_as_space);
+
+string url_decode(Slice from, bool decode_plus_sign_as_space);
+
+MutableSlice url_decode_inplace(MutableSlice str, bool decode_plus_sign_as_space);
+
// run-time checked narrowing cast (type conversion):
namespace detail {
template <class T, class U>
-struct is_same_signedness
+struct is_same_signedness final
: public std::integral_constant<bool, std::is_signed<T>::value == std::is_signed<U>::value> {};
template <class T, class Enable = void>
@@ -292,22 +287,34 @@ template <class T>
struct safe_undeflying_type<T, std::enable_if_t<std::is_enum<T>::value>> {
using type = std::underlying_type_t<T>;
};
-} // namespace detail
-template <class R, class A>
-R narrow_cast(const A &a) {
- using RT = typename detail::safe_undeflying_type<R>::type;
- using AT = typename detail::safe_undeflying_type<A>::type;
+class NarrowCast {
+ const char *file_;
+ int line_;
- static_assert(std::is_integral<RT>::value, "expected integral type to cast to");
- static_assert(std::is_integral<AT>::value, "expected integral type to cast from");
+ public:
+ NarrowCast(const char *file, int line) : file_(file), line_(line) {
+ }
- auto r = R(a);
- CHECK(A(r) == a);
- CHECK((detail::is_same_signedness<RT, AT>::value) || ((static_cast<RT>(r) < RT{}) == (static_cast<AT>(a) < AT{})));
+ template <class R, class A>
+ R cast(const A &a) {
+ using RT = typename safe_undeflying_type<R>::type;
+ using AT = typename safe_undeflying_type<A>::type;
- return r;
-}
+ static_assert(std::is_integral<RT>::value, "expected integral type to cast to");
+ static_assert(std::is_integral<AT>::value, "expected integral type to cast from");
+
+ auto r = R(a);
+ LOG_CHECK(A(r) == a) << static_cast<AT>(a) << " " << static_cast<RT>(r) << " " << file_ << " " << line_;
+ LOG_CHECK((is_same_signedness<RT, AT>::value) || ((static_cast<RT>(r) < RT{}) == (static_cast<AT>(a) < AT{})))
+ << static_cast<AT>(a) << " " << static_cast<RT>(r) << " " << file_ << " " << line_;
+
+ return r;
+ }
+};
+} // namespace detail
+
+#define narrow_cast detail::NarrowCast(__FILE__, __LINE__).cast
template <class R, class A>
Result<R> narrow_cast_safe(const A &a) {
@@ -334,4 +341,14 @@ bool is_aligned_pointer(const T *pointer) {
return (reinterpret_cast<std::uintptr_t>(static_cast<const void *>(pointer)) & (Alignment - 1)) == 0;
}
+string buffer_to_hex(Slice buffer);
+
+string zero_encode(Slice data);
+
+string zero_decode(Slice data);
+
+string zero_one_encode(Slice data);
+
+string zero_one_decode(Slice data);
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/optional.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/optional.h
index 450b60f94c..579127f625 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/optional.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/optional.h
@@ -1,36 +1,99 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/utils/common.h"
#include "td/utils/Status.h"
+#include <type_traits>
#include <utility>
namespace td {
-template <class T>
+template <class T, bool = std::is_copy_constructible<T>::value>
class optional {
public:
optional() = default;
- template <class T1>
+ template <class T1,
+ std::enable_if_t<!std::is_same<std::decay_t<T1>, optional>::value && std::is_constructible<T, T1>::value,
+ int> = 0>
optional(T1 &&t) : impl_(std::forward<T1>(t)) {
}
- explicit operator bool() {
+
+ optional(const optional &other) {
+ if (other) {
+ impl_ = Result<T>(other.value());
+ }
+ }
+
+ optional &operator=(const optional &other) {
+ if (this == &other) {
+ return *this;
+ }
+ if (other) {
+ impl_ = Result<T>(other.value());
+ } else {
+ impl_ = Result<T>();
+ }
+ return *this;
+ }
+
+ optional(optional &&other) = default;
+ optional &operator=(optional &&other) = default;
+ ~optional() = default;
+
+ explicit operator bool() const noexcept {
return impl_.is_ok();
}
T &value() {
+ DCHECK(*this);
+ return impl_.ok_ref();
+ }
+ const T &value() const {
+ DCHECK(*this);
return impl_.ok_ref();
}
T &operator*() {
return value();
}
+ T unwrap() {
+ CHECK(*this);
+ auto res = std::move(value());
+ impl_ = {};
+ return res;
+ }
+
+ optional<T> copy() const {
+ if (*this) {
+ return value();
+ }
+ return {};
+ }
+
+ template <class... ArgsT>
+ void emplace(ArgsT &&...args) {
+ impl_.emplace(std::forward<ArgsT>(args)...);
+ }
private:
Result<T> impl_;
};
+template <typename T>
+struct optional<T, false> : optional<T, true> {
+ optional() = default;
+
+ using optional<T, true>::optional;
+
+ optional(const optional &other) = delete;
+ optional &operator=(const optional &other) = delete;
+ optional(optional &&) = default;
+ optional &operator=(optional &&) = default;
+ ~optional() = default;
+};
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/overloaded.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/overloaded.h
index 6c6186f6a2..3c54151c55 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/overloaded.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/overloaded.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -22,7 +22,7 @@ struct overload<F> : public F {
template <class F, class... Fs>
struct overload<F, Fs...>
: public overload<F>
- , overload<Fs...> {
+ , public overload<Fs...> {
overload(F f, Fs... fs) : overload<F>(f), overload<Fs...>(fs...) {
}
using overload<F>::operator();
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.cpp
index da68754a61..b20beef74f 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.cpp
@@ -1,23 +1,98 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/port/Clocks.h"
+#include "td/utils/port/platform.h"
+
#include <chrono>
+#include <ctime>
+
+#if TD_PORT_POSIX
+#include <time.h>
+#endif
namespace td {
-ClocksDefault::Duration ClocksDefault::monotonic() {
+double Clocks::monotonic() {
+#if TD_PORT_POSIX
+ // use system specific functions, because std::chrono::steady_clock is steady only under Windows
+
+#ifdef CLOCK_BOOTTIME
+ {
+ static bool skip = [] {
+ struct timespec spec;
+ return clock_gettime(CLOCK_BOOTTIME, &spec) != 0;
+ }();
+ struct timespec spec;
+ if (!skip && clock_gettime(CLOCK_BOOTTIME, &spec) == 0) {
+ return static_cast<double>(spec.tv_nsec) * 1e-9 + static_cast<double>(spec.tv_sec);
+ }
+ }
+#endif
+#ifdef CLOCK_MONOTONIC_RAW
+ {
+ static bool skip = [] {
+ struct timespec spec;
+ return clock_gettime(CLOCK_MONOTONIC_RAW, &spec) != 0;
+ }();
+ struct timespec spec;
+ if (!skip && clock_gettime(CLOCK_MONOTONIC_RAW, &spec) == 0) {
+ return static_cast<double>(spec.tv_nsec) * 1e-9 + static_cast<double>(spec.tv_sec);
+ }
+ }
+#endif
+
+#endif
+
auto duration = std::chrono::steady_clock::now().time_since_epoch();
return static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count()) * 1e-9;
}
-ClocksDefault::Duration ClocksDefault::system() {
+double Clocks::system() {
auto duration = std::chrono::system_clock::now().time_since_epoch();
return static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count()) * 1e-9;
}
+int Clocks::tz_offset() {
+ // not thread-safe on POSIX, so calculate the offset only once
+ static int offset = [] {
+ auto now = std::time(nullptr);
+
+ auto time_ptr = std::localtime(&now);
+ if (time_ptr == nullptr) {
+ return 0;
+ }
+ auto local_time = *time_ptr;
+
+ time_ptr = std::gmtime(&now);
+ if (time_ptr == nullptr) {
+ return 0;
+ }
+ auto utc_time = *time_ptr;
+
+ int minute_offset = local_time.tm_min - utc_time.tm_min;
+ int hour_offset = local_time.tm_hour - utc_time.tm_hour;
+ int day_offset = local_time.tm_mday - utc_time.tm_mday;
+ if (day_offset >= 20) {
+ day_offset = -1;
+ } else if (day_offset <= -20) {
+ day_offset = 1;
+ }
+ int sec_offset = day_offset * 86400 + hour_offset * 3600 + minute_offset * 60;
+ if (sec_offset >= 15 * 3600 || sec_offset <= -15 * 3600) {
+ return 0;
+ }
+ return sec_offset / 900 * 900; // round to 900 just in case
+ }();
+ return offset;
+}
+
+namespace detail {
+int init_tz_offset_private = Clocks::tz_offset();
+} // namespace detail
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.h
index a4270df4ad..d663d129d7 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,21 +8,12 @@
namespace td {
-class ClocksBase {
- public:
- using Duration = double;
-};
-
-// TODO: (maybe) write system specific functions.
-class ClocksDefault {
- public:
- using Duration = ClocksBase::Duration;
+struct Clocks {
+ static double monotonic();
- static Duration monotonic();
+ static double system();
- static Duration system();
+ static int tz_offset();
};
-using Clocks = ClocksDefault;
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/CxCli.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/CxCli.h
index b7f01fa401..f88ef5bad8 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/CxCli.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/CxCli.h
@@ -1,28 +1,37 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#pragma managed(push, off)
#include "td/utils/port/config.h"
+#pragma managed(pop)
#include "td/utils/common.h"
-#undef small
#if TD_WINRT
+#pragma managed(push, off)
#include "td/utils/port/wstring_convert.h"
+#pragma managed(pop)
#include "collection.h"
+#pragma managed(push, off)
#include <cstdint>
#include <map>
#include <mutex>
+#pragma managed(pop)
+
+#undef small
#define REF_NEW ref new
#define CLRCALL
+#define DEPRECATED_ATTRIBUTE(message) \
+ ::Windows::Foundation::Metadata::Deprecated(message, ::Windows::Foundation::Metadata::DeprecationType::Deprecate, 0x0)
namespace CxCli {
@@ -38,8 +47,9 @@ using Platform::String;
using Platform::NullReferenceException;
-template <class Key, class Value> class ConcurrentDictionary {
-public:
+template <class Key, class Value>
+class ConcurrentDictionary {
+ public:
bool TryGetValue(Key key, Value &value) {
std::lock_guard<std::mutex> guard(mutex_);
auto it = impl_.find(key);
@@ -59,11 +69,12 @@ public:
impl_.erase(it);
return true;
}
- Value &operator [] (Key key) {
+ Value &operator[](Key key) {
std::lock_guard<std::mutex> guard(mutex_);
return impl_[key];
}
-private:
+
+ private:
std::mutex mutex_;
std::map<Key, Value> impl_;
};
@@ -72,26 +83,31 @@ inline std::int64_t Increment(volatile std::int64_t &value) {
return InterlockedIncrement64(&value);
}
-inline std::string string_to_unmanaged(String^ str) {
+inline std::string string_to_unmanaged(String ^ str) {
if (!str) {
return std::string();
}
- return td::from_wstring(str->Data(), str->Length()).ok();
+ auto r_unmanaged_str = td::from_wstring(str->Data(), str->Length());
+ if (r_unmanaged_str.is_error()) {
+ return std::string();
+ }
+ return r_unmanaged_str.move_as_ok();
}
-inline String^ string_from_unmanaged(const std::string &from) {
+inline String ^ string_from_unmanaged(const std::string &from) {
auto tmp = td::to_wstring(from).ok();
return REF_NEW String(tmp.c_str(), static_cast<unsigned>(tmp.size()));
}
-} // namespace CxCli
+} // namespace CxCli
#elif TD_CLI
-#include <msclr\marshal_cppstd.h>
+#undef small
#define REF_NEW gcnew
#define CLRCALL __clrcall
+#define DEPRECATED_ATTRIBUTE(message) System::ObsoleteAttribute(message)
namespace CxCli {
@@ -113,21 +129,34 @@ using System::NullReferenceException;
using System::Collections::Concurrent::ConcurrentDictionary;
-inline std::int64_t Increment(std::int64_t %value) {
+inline std::int64_t Increment(std::int64_t % value) {
return System::Threading::Interlocked::Increment(value);
}
-inline std::string string_to_unmanaged(String^ str) {
- if (!str) {
+inline std::string string_to_unmanaged(String ^ str) {
+ if (!str || str->Length == 0) {
return std::string();
}
- return msclr::interop::marshal_as<std::string>(str);
+
+ Array<System::Byte> ^ bytes = System::Text::Encoding::UTF8->GetBytes(str);
+ cli::pin_ptr<System::Byte> pinned_ptr = &bytes[0];
+ std::string result(reinterpret_cast<const char *>(&pinned_ptr[0]), bytes->Length);
+ return result;
}
-inline String^ string_from_unmanaged(const std::string &from) {
- return msclr::interop::marshal_as<String^>(from);
+inline String ^ string_from_unmanaged(const std::string &from) {
+ if (from.empty()) {
+ return String::Empty;
+ }
+
+ Array<System::Byte> ^ bytes = REF_NEW Vector<System::Byte>(static_cast<ArrayIndexType>(from.size()));
+ cli::pin_ptr<System::Byte> pinned_ptr = &bytes[0];
+ for (size_t i = 0; i < from.size(); ++i) {
+ pinned_ptr[i] = from[i];
+ }
+ return System::Text::Encoding::UTF8->GetString(bytes);
}
-} // namespace CxCli
+} // namespace CxCli
#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFd.h
index ba2edabefd..8972259102 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFd.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFd.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFdBase.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFdBase.h
index e119a3c0eb..d4f586c303 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFdBase.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFdBase.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,7 +7,7 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/Status.h"
namespace td {
@@ -23,10 +23,10 @@ class EventFdBase {
virtual void init() = 0;
virtual bool empty() = 0;
virtual void close() = 0;
- virtual const Fd &get_fd() const = 0;
- virtual Fd &get_fd() = 0;
+ virtual PollableFdInfo &get_poll_info() = 0;
virtual Status get_pending_error() TD_WARN_UNUSED_RESULT = 0;
virtual void release() = 0;
virtual void acquire() = 0;
+ virtual void wait(int timeout_ms) = 0;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.cpp
deleted file mode 100644
index cb4cb27306..0000000000
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.cpp
+++ /dev/null
@@ -1,1104 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#include "td/utils/port/Fd.h"
-
-#include "td/utils/common.h"
-#include "td/utils/format.h"
-#include "td/utils/logging.h"
-#include "td/utils/misc.h"
-#include "td/utils/Observer.h"
-
-#if TD_PORT_POSIX
-
-#include <atomic>
-
-#include <fcntl.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#endif
-
-#if TD_PORT_WINDOWS
-
-#include "td/utils/buffer.h"
-#include "td/utils/misc.h"
-
-#include <cstring>
-
-#endif
-
-namespace td {
-
-#if TD_PORT_POSIX
-
-Fd::InfoSet::InfoSet() {
- get_info(0).refcnt = 1;
- get_info(1).refcnt = 1;
- get_info(2).refcnt = 1;
-}
-Fd::Info &Fd::InfoSet::get_info(int32 id) {
- CHECK(0 <= id && id < InfoSet::MAX_FD) << tag("fd", id);
- return fd_array_[id];
-}
-Fd::InfoSet Fd::fd_info_set_;
-
-// TODO(bug) if constuctor call tries to output something to the LOG it will fail, because log is not initialized
-Fd Fd::stderr_(2, Mode::Reference);
-Fd Fd::stdout_(1, Mode::Reference);
-Fd Fd::stdin_(0, Mode::Reference);
-
-Fd::Fd() = default;
-
-Fd::Fd(int fd, Mode mode) : mode_(mode), fd_(fd) {
- auto *info = get_info();
- int old_ref_cnt = info->refcnt.load(std::memory_order_relaxed);
- if (old_ref_cnt == 0) {
- old_ref_cnt = info->refcnt.load(std::memory_order_acquire);
- CHECK(old_ref_cnt == 0);
- CHECK(mode_ == Mode::Owner) << tag("fd", fd_);
- VLOG(fd) << "FD created [fd:" << fd_ << "]";
-
- auto fcntl_res = fcntl(fd_, F_GETFD);
- auto fcntl_errno = errno;
- LOG_IF(FATAL, fcntl_res == -1) << Status::PosixError(fcntl_errno, "fcntl F_GET_FD failed");
-
- info->refcnt.store(1, std::memory_order_relaxed);
- CHECK(mode_ != Mode::Reference);
- CHECK(info->observer == nullptr);
- info->flags = 0;
- info->observer = nullptr;
- } else {
- CHECK(mode_ == Mode::Reference) << tag("fd", fd_);
- auto fcntl_res = fcntl(fd_, F_GETFD);
- auto fcntl_errno = errno;
- LOG_IF(FATAL, fcntl_res == -1) << Status::PosixError(fcntl_errno, "fcntl F_GET_FD failed");
-
- CHECK(mode_ == Mode::Reference);
- info->refcnt.fetch_add(1, std::memory_order_relaxed);
- }
-}
-
-int Fd::move_as_native_fd() {
- clear_info();
- auto res = fd_;
- fd_ = -1;
- return res;
-}
-
-Fd::~Fd() {
- close();
-}
-
-Fd::Fd(Fd &&other) {
- fd_ = other.fd_;
- mode_ = other.mode_;
- other.fd_ = -1;
-}
-
-Fd &Fd::operator=(Fd &&other) {
- if (this != &other) {
- close();
-
- fd_ = other.fd_;
- mode_ = other.mode_;
- other.fd_ = -1;
- }
- return *this;
-}
-
-Fd Fd::clone() const {
- return Fd(fd_, Mode::Reference);
-}
-
-Fd &Fd::Stderr() {
- return stderr_;
-}
-Fd &Fd::Stdout() {
- return stdout_;
-}
-Fd &Fd::Stdin() {
- return stdin_;
-}
-
-Status Fd::duplicate(const Fd &from, Fd &to) {
- CHECK(!from.empty());
- CHECK(!to.empty());
- if (dup2(from.get_native_fd(), to.get_native_fd()) == -1) {
- return OS_ERROR("dup2 failed");
- }
- return Status::OK();
-}
-
-bool Fd::empty() const {
- return fd_ == -1;
-}
-
-const Fd &Fd::get_fd() const {
- return *this;
-}
-
-Fd &Fd::get_fd() {
- return *this;
-}
-
-int Fd::get_native_fd() const {
- CHECK(!empty());
- return fd_;
-}
-
-void Fd::set_observer(ObserverBase *observer) {
- auto *info = get_info();
- CHECK(observer == nullptr || info->observer == nullptr);
- info->observer = observer;
-}
-
-ObserverBase *Fd::get_observer() const {
- auto *info = get_info();
- return info->observer;
-}
-
-void Fd::close_ref() {
- CHECK(mode_ == Mode::Reference);
- auto *info = get_info();
-
- int old_ref_cnt = info->refcnt.fetch_sub(1, std::memory_order_relaxed);
- CHECK(old_ref_cnt > 1) << tag("fd", fd_);
- fd_ = -1;
-}
-
-void Fd::close_own() {
- CHECK(mode_ == Mode::Owner);
- VLOG(fd) << "FD closed [fd:" << fd_ << "]";
-
- clear_info();
- ::close(fd_);
- fd_ = -1;
-}
-
-void Fd::close() {
- if (!empty()) {
- switch (mode_) {
- case Mode::Reference:
- close_ref();
- break;
- case Mode::Owner:
- close_own();
- break;
- }
- }
-}
-
-Fd::Info *Fd::get_info() {
- CHECK(!empty());
- return &fd_info_set_.get_info(fd_);
-}
-
-const Fd::Info *Fd::get_info() const {
- CHECK(!empty());
- return &fd_info_set_.get_info(fd_);
-}
-
-void Fd::clear_info() {
- CHECK(!empty());
- CHECK(mode_ != Mode::Reference);
-
- auto *info = get_info();
- int old_ref_cnt = info->refcnt.load(std::memory_order_relaxed);
- CHECK(old_ref_cnt == 1);
- info->flags = 0;
- info->observer = nullptr;
- info->refcnt.store(0, std::memory_order_release);
-}
-
-void Fd::update_flags_notify(Flags flags) {
- update_flags_inner(flags, true);
-}
-
-void Fd::update_flags(Flags flags) {
- update_flags_inner(flags, false);
-}
-
-void Fd::update_flags_inner(int32 new_flags, bool notify_flag) {
- if (new_flags & Error) {
- new_flags |= Error;
- new_flags |= Close;
- }
- auto *info = get_info();
- int32 &flags = info->flags;
- int32 old_flags = flags;
- flags |= new_flags;
- if (new_flags & Close) {
- // TODO: ???
- flags &= ~Write;
- }
- if (flags != old_flags) {
- VLOG(fd) << "Update flags " << tag("fd", fd_) << tag("from", format::as_binary(old_flags))
- << tag("to", format::as_binary(flags));
- }
- if (flags != old_flags && notify_flag) {
- auto observer = info->observer;
- if (observer != nullptr) {
- observer->notify();
- }
- }
-}
-
-Fd::Flags Fd::get_flags() const {
- return get_info()->flags;
-}
-
-void Fd::clear_flags(Flags flags) {
- get_info()->flags &= ~flags;
-}
-
-bool Fd::has_pending_error() const {
- return (get_flags() & Fd::Flag::Error) != 0;
-}
-
-Status Fd::get_pending_error() {
- if (!has_pending_error()) {
- return Status::OK();
- }
- clear_flags(Fd::Error);
- int error = 0;
- socklen_t errlen = sizeof(error);
- if (getsockopt(fd_, SOL_SOCKET, SO_ERROR, static_cast<void *>(&error), &errlen) == 0) {
- if (error == 0) {
- return Status::OK();
- }
- return Status::PosixError(error, PSLICE() << "Error on socket [fd_ = " << fd_ << "]");
- }
- auto status = OS_SOCKET_ERROR(PSLICE() << "Can't load error on socket [fd_ = " << fd_ << "]");
- LOG(INFO) << "Can't load pending socket error: " << status;
- return status;
-}
-
-Result<size_t> Fd::write_unsafe(Slice slice) {
- int native_fd = get_native_fd();
- auto write_res = skip_eintr([&] { return ::write(native_fd, slice.begin(), slice.size()); });
- auto write_errno = errno;
- if (write_res >= 0) {
- return narrow_cast<size_t>(write_res);
- }
- return Status::PosixError(write_errno, PSLICE() << "Write to fd " << native_fd << " has failed");
-}
-
-Result<size_t> Fd::write(Slice slice) {
- int native_fd = get_native_fd();
- auto write_res = skip_eintr([&] { return ::write(native_fd, slice.begin(), slice.size()); });
- auto write_errno = errno;
- if (write_res >= 0) {
- return narrow_cast<size_t>(write_res);
- }
-
- if (write_errno == EAGAIN
-#if EAGAIN != EWOULDBLOCK
- || write_errno == EWOULDBLOCK
-#endif
- ) {
- clear_flags(Write);
- return 0;
- }
-
- auto error = Status::PosixError(write_errno, PSLICE() << "Write to fd " << native_fd << " has failed");
- switch (write_errno) {
- case EBADF:
- case ENXIO:
- case EFAULT:
- case EINVAL:
- LOG(FATAL) << error;
- UNREACHABLE();
- default:
- LOG(WARNING) << error;
- // fallthrough
- case ECONNRESET:
- case EDQUOT:
- case EFBIG:
- case EIO:
- case ENETDOWN:
- case ENETUNREACH:
- case ENOSPC:
- case EPIPE:
- clear_flags(Write);
- update_flags(Close);
- return std::move(error);
- }
-}
-
-Result<size_t> Fd::read(MutableSlice slice) {
- int native_fd = get_native_fd();
- CHECK(slice.size() > 0);
- auto read_res = skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); });
- auto read_errno = errno;
- if (read_res >= 0) {
- if (read_res == 0) {
- errno = 0;
- clear_flags(Read);
- update_flags(Close);
- }
- return narrow_cast<size_t>(read_res);
- }
- if (read_errno == EAGAIN
-#if EAGAIN != EWOULDBLOCK
- || read_errno == EWOULDBLOCK
-#endif
- ) {
- clear_flags(Read);
- return 0;
- }
- auto error = Status::PosixError(read_errno, PSLICE() << "Read from fd " << native_fd << " has failed");
- switch (read_errno) {
- case EISDIR:
- case EBADF:
- case ENXIO:
- case EFAULT:
- case EINVAL:
- case ENOTCONN:
- LOG(FATAL) << error;
- UNREACHABLE();
- default:
- LOG(WARNING) << error;
- // fallthrough
- case EIO:
- case ENOBUFS:
- case ENOMEM:
- case ECONNRESET:
- case ETIMEDOUT:
- clear_flags(Read);
- update_flags(Close);
- return std::move(error);
- }
-}
-
-Status Fd::set_is_blocking(bool is_blocking) {
- auto old_flags = fcntl(fd_, F_GETFL);
- if (old_flags == -1) {
- return OS_SOCKET_ERROR("Failed to get socket flags");
- }
- auto new_flags = is_blocking ? old_flags & ~O_NONBLOCK : old_flags | O_NONBLOCK;
- if (new_flags != old_flags && fcntl(fd_, F_SETFL, new_flags) == -1) {
- return OS_SOCKET_ERROR("Failed to set socket flags");
- }
-
- return Status::OK();
-}
-
-#endif
-
-#if TD_PORT_WINDOWS
-
-class Fd::FdImpl {
- public:
- FdImpl(Fd::Type type, HANDLE handle)
- : type_(type), handle_(handle), async_mode_(type_ == Fd::Type::EventFd || type_ == Fd::Type::StdinFileFd) {
- init();
- }
- FdImpl(Fd::Type type, SOCKET socket, int socket_family)
- : type_(type), socket_(socket), socket_family_(socket_family), async_mode_(true) {
- init();
- }
-
- FdImpl(const FdImpl &) = delete;
- FdImpl &operator=(const FdImpl &) = delete;
- FdImpl(FdImpl &&) = delete;
- FdImpl &operator=(FdImpl &&) = delete;
-
- ~FdImpl() {
- close();
- }
- void set_observer(ObserverBase *observer) {
- observer_ = observer;
- }
- ObserverBase *get_observer() const {
- return observer_;
- }
-
- void update_flags_notify(Fd::Flags flags) {
- update_flags_inner(flags, true);
- }
-
- void update_flags(Fd::Flags flags) {
- update_flags_inner(flags, false);
- }
-
- void update_flags_inner(int32 new_flags, bool notify_flag) {
- if (new_flags & Fd::Error) {
- new_flags |= Fd::Error;
- new_flags |= Fd::Close;
- }
- int32 old_flags = flags_;
- flags_ |= new_flags;
- if (new_flags & Fd::Close) {
- // TODO: ???
- flags_ &= ~Fd::Write;
- internal_flags_ &= ~Fd::Write;
- }
- if (flags_ != old_flags) {
- VLOG(fd) << "Update flags " << tag("fd", get_io_handle()) << tag("from", format::as_binary(old_flags))
- << tag("to", format::as_binary(flags_));
- }
- if (flags_ != old_flags && notify_flag) {
- auto observer = get_observer();
- if (observer != nullptr) {
- observer->notify();
- }
- }
- }
-
- int32 get_flags() const {
- return flags_;
- }
-
- void clear_flags(Fd::Flags mask) {
- flags_ &= ~mask;
- }
-
- Status get_pending_error() {
- if (!has_pending_error()) {
- return Status::OK();
- }
- clear_flags(Fd::Error);
- return std::move(pending_error_);
- }
- bool has_pending_error() const {
- return (get_flags() & Fd::Flag::Error) != 0;
- }
-
- HANDLE get_read_event() {
- if (type() == Fd::Type::StdinFileFd) {
- return get_io_handle();
- }
- return read_event_;
- }
- void on_read_event() {
- if (type_ == Fd::Type::StdinFileFd) {
- return try_read_stdin();
- }
- ResetEvent(read_event_);
- if (type_ == Fd::Type::EventFd) {
- return update_flags_notify(Fd::Flag::Read);
- }
- if (type_ == Fd::Type::SocketFd && !connected_) {
- on_connect_ready();
- } else {
- if (!async_read_flag_) {
- return;
- }
-
- if (type_ == Fd::Type::ServerSocketFd) {
- on_accept_ready();
- } else {
- on_read_ready();
- }
- }
- loop();
- }
- HANDLE get_write_event() {
- return write_event_;
- }
- void on_write_event() {
- CHECK(async_write_flag_);
- ResetEvent(write_event_);
- on_write_ready();
- loop();
- }
-
- SOCKET get_native_socket() const {
- return socket_;
- }
-
- HANDLE get_io_handle() const {
- CHECK(!empty());
- if (type() == Fd::Type::FileFd || type() == Fd::Type::StdinFileFd) {
- return handle_;
- }
- return reinterpret_cast<HANDLE>(socket_);
- }
-
- Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT {
- if (async_mode_) {
- return write_async(slice);
- } else {
- return write_sync(slice);
- }
- }
-
- Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT {
- if (async_mode_) {
- return read_async(slice);
- } else {
- return read_sync(slice);
- }
- }
-
- Result<size_t> write_async(Slice slice) TD_WARN_UNUSED_RESULT {
- CHECK(async_mode_);
- output_writer_.append(slice);
- output_reader_.sync_with_writer();
- loop();
- return slice.size();
- }
- Result<size_t> write_sync(Slice slice) TD_WARN_UNUSED_RESULT {
- CHECK(!async_mode_);
- DWORD bytes_written = 0;
- auto res = WriteFile(get_io_handle(), slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_written, nullptr);
- if (!res) {
- return OS_ERROR("Failed to write_sync");
- }
- return bytes_written;
- }
- Result<size_t> read_async(MutableSlice slice) TD_WARN_UNUSED_RESULT {
- CHECK(async_mode_);
- auto res = input_reader_.advance(min(slice.size(), input_reader_.size()), slice);
- if (res == 0) {
- clear_flags(Fd::Flag::Read);
- }
- return res;
- }
- Result<size_t> read_sync(MutableSlice slice) TD_WARN_UNUSED_RESULT {
- CHECK(!async_mode_);
- DWORD bytes_read = 0;
- auto res = ReadFile(get_io_handle(), slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, nullptr);
- if (!res) {
- return OS_ERROR("Failed to read_sync");
- }
- if (bytes_read == 0) {
- clear_flags(Fd::Flag::Read);
- }
- return bytes_read;
- }
-
- // for ServerSocket
- Result<Fd> accept() {
- if (accepted_.empty()) {
- clear_flags(Fd::Flag::Read);
- return Status::Error(-1, "Operation would block");
- }
- auto res = std::move(accepted_.back());
- accepted_.pop_back();
- return std::move(res);
- }
-
- void connect(const IPAddress &addr) {
- CHECK(!connected_);
- CHECK(type_ == Fd::Type::SocketFd);
- DWORD bytes_read;
- std::memset(&read_overlapped_, 0, sizeof(read_overlapped_));
- read_overlapped_.hEvent = read_event_;
- LPFN_CONNECTEX ConnectExPtr = nullptr;
- GUID guid = WSAID_CONNECTEX;
- DWORD numBytes;
- int error = ::WSAIoctl(socket_, SIO_GET_EXTENSION_FUNCTION_POINTER, static_cast<void *>(&guid), sizeof(guid),
- static_cast<void *>(&ConnectExPtr), sizeof(ConnectExPtr), &numBytes, nullptr, nullptr);
- if (error) {
- return on_error(OS_SOCKET_ERROR("WSAIoctl failed"), Fd::Flag::Read);
- }
- auto status = ConnectExPtr(socket_, addr.get_sockaddr(), narrow_cast<int>(addr.get_sockaddr_len()), nullptr, 0,
- &bytes_read, &read_overlapped_);
- if (status != 0) {
- ResetEvent(read_event_);
- connected_ = true;
- update_flags_notify(Fd::Flag::Read);
- return;
- }
-
- auto last_error = GetLastError();
- if (last_error == ERROR_IO_PENDING) {
- return;
- }
- on_error(OS_SOCKET_ERROR("ConnectEx failed"), Fd::Flag::Read);
- }
-
- // for EventFd
- void release() {
- CHECK(type_ == Fd::Type::EventFd);
- SetEvent(read_event_);
- }
-
- void acquire() {
- CHECK(type_ == Fd::Type::EventFd);
- ResetEvent(read_event_);
- clear_flags(Fd::Flag::Read);
- }
-
- // TODO: interface for BufferedFd optimization.
-
- bool empty() const {
- return type() == Fd::Type::Empty;
- }
- void close() {
- if (empty()) {
- return;
- }
- switch (type()) {
- case Fd::Type::StdinFileFd:
- case Fd::Type::FileFd: {
- if (!CloseHandle(handle_)) {
- auto error = OS_ERROR("Failed to close file");
- LOG(ERROR) << error;
- }
- handle_ = INVALID_HANDLE_VALUE;
- break;
- }
- case Fd::Type::ServerSocketFd:
- case Fd::Type::SocketFd: {
- if (closesocket(socket_) != 0) {
- auto error = OS_SOCKET_ERROR("Failed to close socket");
- LOG(ERROR) << error;
- }
- socket_ = INVALID_SOCKET;
- break;
- }
- case Fd::Type::EventFd:
- break;
- default:
- UNREACHABLE();
- }
-
- if (read_event_ != INVALID_HANDLE_VALUE) {
- if (!CloseHandle(read_event_)) {
- auto error = OS_ERROR("Failed to close event");
- LOG(ERROR) << error;
- }
- read_event_ = INVALID_HANDLE_VALUE;
- }
- if (write_event_ != INVALID_HANDLE_VALUE) {
- if (!CloseHandle(write_event_)) {
- auto error = OS_ERROR("Failed to close event");
- LOG(ERROR) << error;
- }
- write_event_ = INVALID_HANDLE_VALUE;
- }
-
- type_ = Fd::Type::Empty;
- }
-
- private:
- Fd::Type type_;
- HANDLE handle_ = INVALID_HANDLE_VALUE;
- SOCKET socket_ = INVALID_SOCKET;
-
- int socket_family_ = 0;
-
- bool async_mode_ = false;
-
- ObserverBase *observer_ = nullptr;
- Fd::Flags flags_ = Fd::Flag::Write;
- Status pending_error_;
- Fd::Flags internal_flags_ = Fd::Flag::Write | Fd::Flag::Read;
-
- HANDLE read_event_ = INVALID_HANDLE_VALUE; // used by WineventPoll
- bool async_read_flag_ = false; // do we have pending read?
- OVERLAPPED read_overlapped_;
- ChainBufferWriter input_writer_;
- ChainBufferReader input_reader_ = input_writer_.extract_reader();
-
- bool connected_ = false;
-
- std::vector<Fd> accepted_;
- SOCKET accept_socket_ = INVALID_SOCKET;
- static constexpr size_t MAX_ADDR_SIZE = sizeof(sockaddr_in6) + 16;
- char addr_buf_[MAX_ADDR_SIZE * 2];
-
- HANDLE write_event_ = INVALID_HANDLE_VALUE; // used by WineventPoll
- bool async_write_flag_ = false; // do we have pending write?
- OVERLAPPED write_overlapped_;
- ChainBufferWriter output_writer_;
- ChainBufferReader output_reader_ = output_writer_.extract_reader();
-
- void init() {
- if (async_mode_) {
- if (type_ != Fd::Type::EventFd) {
- write_event_ = CreateEventW(nullptr, true, false, nullptr);
- }
- read_event_ = CreateEventW(nullptr, true, false, nullptr);
- loop();
- }
- }
-
- Fd::Type type() const {
- return type_;
- }
-
- void on_error(Status error, Fd::Flag flag) {
- VLOG(fd) << tag("fd", get_io_handle()) << error;
- pending_error_ = std::move(error);
- internal_flags_ &= ~flag;
- update_flags_notify(Fd::Flag::Error);
- }
- void on_eof() {
- internal_flags_ &= ~Fd::Flag::Read;
- update_flags_notify(Fd::Flag::Close);
- }
-
- void on_read_ready() {
- async_read_flag_ = false;
- DWORD bytes_read;
- auto status = GetOverlappedResult(get_io_handle(), &read_overlapped_, &bytes_read, false);
- if (status == 0) {
- return on_error(OS_ERROR("ReadFile failed"), Fd::Flag::Read);
- }
-
- VLOG(fd) << "Read " << tag("fd", get_io_handle()) << tag("size", bytes_read);
- if (bytes_read == 0) { // eof
- return on_eof();
- }
- input_writer_.confirm_append(bytes_read);
- input_reader_.sync_with_writer();
- update_flags_notify(Fd::Flag::Read);
- }
- void on_write_ready() {
- async_write_flag_ = false;
- DWORD bytes_written;
- auto status = GetOverlappedResult(get_io_handle(), &write_overlapped_, &bytes_written, false);
- if (status == 0) {
- return on_error(OS_ERROR("WriteFile failed"), Fd::Flag::Write);
- }
- if (bytes_written != 0) {
- VLOG(fd) << "Write " << tag("fd", get_io_handle()) << tag("size", bytes_written);
- output_reader_.confirm_read(bytes_written);
- update_flags_notify(Fd::Flag::Write);
- }
- }
-
- void on_accept_ready() {
- async_read_flag_ = false;
- DWORD bytes_read;
- auto status = GetOverlappedResult(get_io_handle(), &read_overlapped_, &bytes_read, false);
- if (status == 0) {
- return on_error(OS_ERROR("AcceptEx failed"), Fd::Flag::Write);
- }
- accepted_.push_back(Fd::create_socket_fd(accept_socket_));
- accept_socket_ = INVALID_SOCKET;
- update_flags_notify(Fd::Flag::Read);
- }
-
- void on_connect_ready() {
- async_read_flag_ = false;
- DWORD bytes_read;
- VLOG(fd) << "on_connect_ready";
- auto status = GetOverlappedResult(get_io_handle(), &read_overlapped_, &bytes_read, false);
- if (status == 0) {
- return on_error(OS_ERROR("ConnectEx failed"), Fd::Flag::Write);
- }
- connected_ = true;
- VLOG(fd) << "connected = true";
- }
-
- void try_read_stdin() {
- }
- void try_start_read() {
- auto dest = input_writer_.prepare_append();
- DWORD bytes_read;
- std::memset(&read_overlapped_, 0, sizeof(read_overlapped_));
- read_overlapped_.hEvent = read_event_;
- VLOG(fd) << "try_read..";
- auto status =
- ReadFile(get_io_handle(), dest.data(), narrow_cast<DWORD>(dest.size()), &bytes_read, &read_overlapped_);
- if (status != 0) { // ok
- ResetEvent(read_event_);
- VLOG(fd) << "Read " << tag("fd", get_io_handle()) << tag("size", bytes_read);
- if (bytes_read == 0) { // eof
- return on_eof();
- }
- input_writer_.confirm_append(bytes_read);
- input_reader_.sync_with_writer();
- update_flags_notify(Fd::Flag::Read);
- return;
- }
- auto last_error = GetLastError();
- if (last_error == ERROR_IO_PENDING) {
- async_read_flag_ = true;
- return;
- }
- on_error(OS_ERROR("ReadFile failed"), Fd::Flag::Read);
- }
-
- void try_start_write() {
- auto dest = output_reader_.prepare_read();
- DWORD bytes_written;
- std::memset(&write_overlapped_, 0, sizeof(write_overlapped_));
- write_overlapped_.hEvent = write_event_;
- VLOG(fd) << "try_start_write";
- auto status =
- WriteFile(get_io_handle(), dest.data(), narrow_cast<DWORD>(dest.size()), &bytes_written, &write_overlapped_);
- if (status != 0) { // ok
- VLOG(fd) << "Write " << tag("fd", get_io_handle()) << tag("size", bytes_written);
- ResetEvent(write_event_);
- output_reader_.confirm_read(bytes_written);
- update_flags_notify(Fd::Flag::Write);
- return;
- }
- auto last_error = GetLastError();
- if (last_error == ERROR_IO_PENDING) {
- VLOG(fd) << "try_start_write: ERROR_IO_PENDING";
- async_write_flag_ = true;
- return;
- }
- CHECK(WaitForSingleObject(write_event_, 0) != WAIT_OBJECT_0);
- on_error(OS_ERROR("WriteFile failed"), Fd::Flag::Write);
- }
-
- void try_start_accept() {
- if (async_read_flag_ == true) {
- return;
- }
- accept_socket_ = socket(socket_family_, SOCK_STREAM, 0);
- DWORD bytes_read;
- std::memset(&read_overlapped_, 0, sizeof(read_overlapped_));
- read_overlapped_.hEvent = read_event_;
- auto status =
- AcceptEx(socket_, accept_socket_, addr_buf_, 0, MAX_ADDR_SIZE, MAX_ADDR_SIZE, &bytes_read, &read_overlapped_);
- if (status != 0) {
- ResetEvent(read_event_);
- accepted_.push_back(Fd::create_socket_fd(accept_socket_));
- accept_socket_ = INVALID_SOCKET;
- update_flags_notify(Fd::Flag::Read);
- return;
- }
-
- auto last_error = GetLastError();
- if (last_error == ERROR_IO_PENDING) {
- async_read_flag_ = true;
- return;
- }
- on_error(OS_SOCKET_ERROR("AcceptExFailed"), Fd::Flag::Read);
- }
-
- void loop() {
- CHECK(async_mode_);
-
- if (type_ == Fd::Type::EventFd) {
- return;
- }
- if (type_ == Fd::Type::ServerSocketFd) {
- while (async_read_flag_ == false && (internal_flags_ & Fd::Flag::Read) != 0) {
- // read always
- try_start_accept();
- }
- return;
- }
- if (!connected_) {
- return;
- }
- while (async_read_flag_ == false && (internal_flags_ & Fd::Flag::Read) != 0) {
- // read always
- try_start_read();
- }
- VLOG(fd) << (async_write_flag_ == false) << " " << output_reader_.size() << " "
- << ((internal_flags_ & Fd::Flag::Write) != 0);
- while (async_write_flag_ == false && output_reader_.size() && (internal_flags_ & Fd::Flag::Write) != 0) {
- // write if we have data to write
- try_start_write();
- }
- }
-};
-
-Fd::Fd() = default;
-
-Fd::Fd(Fd &&other) = default;
-
-Fd &Fd::operator=(Fd &&other) = default;
-
-Fd::~Fd() = default;
-
-Fd Fd::create_file_fd(HANDLE handle) {
- return Fd(Fd::Type::FileFd, Fd::Mode::Owner, handle);
-}
-
-Fd Fd::create_socket_fd(SOCKET sock) {
- return Fd(Fd::Type::SocketFd, Fd::Mode::Owner, sock, AF_UNSPEC);
-}
-
-Fd Fd::create_server_socket_fd(SOCKET sock, int socket_family) {
- return Fd(Fd::Type::ServerSocketFd, Fd::Mode::Owner, sock, socket_family);
-}
-
-Fd Fd::create_event_fd() {
- return Fd(Fd::Type::EventFd, Fd::Mode::Owner, INVALID_HANDLE_VALUE);
-}
-
-const Fd &Fd::get_fd() const {
- return *this;
-}
-
-Fd &Fd::get_fd() {
- return *this;
-}
-
-Result<size_t> Fd::read(MutableSlice slice) {
- return impl_->read(slice);
-}
-
-Result<size_t> Fd::write(Slice slice) {
- CHECK(!empty());
- return impl_->write(slice);
-}
-
-bool Fd::empty() const {
- return !impl_;
-}
-
-void Fd::close() {
- impl_.reset();
-}
-
-Result<Fd> Fd::accept() {
- return impl_->accept();
-}
-void Fd::connect(const IPAddress &addr) {
- return impl_->connect(addr);
-}
-
-Fd Fd::clone() const {
- return Fd(impl_);
-}
-
-uint64 Fd::get_key() const {
- return reinterpret_cast<uint64>(impl_.get());
-}
-
-void Fd::set_observer(ObserverBase *observer) {
- return impl_->set_observer(observer);
-}
-ObserverBase *Fd::get_observer() const {
- return impl_->get_observer();
-}
-
-Fd::Flags Fd::get_flags() const {
- return impl_->get_flags();
-}
-void Fd::update_flags(Flags flags) {
- impl_->update_flags(flags);
-}
-
-void Fd::on_read_event() {
- impl_->on_read_event();
-}
-void Fd::on_write_event() {
- impl_->on_write_event();
-}
-
-bool Fd::has_pending_error() const {
- return impl_->has_pending_error();
-}
-Status Fd::get_pending_error() {
- return impl_->get_pending_error();
-}
-
-HANDLE Fd::get_read_event() {
- return impl_->get_read_event();
-}
-HANDLE Fd::get_write_event() {
- return impl_->get_write_event();
-}
-
-SOCKET Fd::get_native_socket() const {
- return impl_->get_native_socket();
-}
-
-HANDLE Fd::get_io_handle() const {
- return impl_->get_io_handle();
-}
-
-#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
-Fd &Fd::Stderr() {
- static auto handle = GetStdHandle(STD_ERROR_HANDLE);
- LOG_IF(FATAL, handle == INVALID_HANDLE_VALUE) << "Failed to get stderr";
- static auto fd = Fd(Fd::Type::FileFd, Fd::Mode::Reference, handle);
- return fd;
-}
-Fd &Fd::Stdin() {
- static auto handle = GetStdHandle(STD_INPUT_HANDLE);
- LOG_IF(FATAL, handle == INVALID_HANDLE_VALUE) << "Failed to get stdin";
- static auto fd = Fd(Fd::Type::FileFd, Fd::Mode::Reference, handle);
- return fd;
-}
-Fd &Fd::Stdout() {
- static auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
- LOG_IF(FATAL, handle == INVALID_HANDLE_VALUE) << "Failed to get stdout";
- static auto fd = Fd(Fd::Type::FileFd, Fd::Mode::Reference, handle);
- return fd;
-}
-#else
-Fd &Fd::Stderr() {
- static Fd result;
- result = Fd();
- return result;
-}
-Fd &Fd::Stdin() {
- static Fd result;
- result = Fd();
- return result;
-}
-Fd &Fd::Stdout() {
- static Fd result;
- result = Fd();
- return result;
-}
-#endif
-
-Status Fd::duplicate(const Fd &from, Fd &to) {
- return Status::Error("Not supported");
-}
-
-Status Fd::set_is_blocking(bool is_blocking) {
- return detail::set_native_socket_is_blocking(get_native_socket(), is_blocking);
-}
-
-Fd::Fd(Type type, Mode mode, HANDLE handle) : mode_(mode), impl_(std::make_shared<FdImpl>(type, handle)) {
-}
-
-Fd::Fd(Type type, Mode mode, SOCKET sock, int socket_family)
- : mode_(mode), impl_(std::make_shared<FdImpl>(type, sock, socket_family)) {
-}
-
-Fd::Fd(std::shared_ptr<FdImpl> impl) : mode_(Mode::Reference), impl_(std::move(impl)) {
-}
-
-void Fd::acquire() {
- return impl_->acquire();
-}
-
-void Fd::release() {
- return impl_->release();
-}
-
-class InitWSA {
- public:
- InitWSA() {
- /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
- WORD wVersionRequested = MAKEWORD(2, 2);
- WSADATA wsaData;
- if (WSAStartup(wVersionRequested, &wsaData) != 0) {
- auto error = OS_SOCKET_ERROR("Failed to init WSA");
- LOG(FATAL) << error;
- }
- }
-};
-
-static InitWSA init_wsa;
-
-#endif
-
-namespace detail {
-#if TD_PORT_POSIX
-Status set_native_socket_is_blocking(int fd, bool is_blocking) {
- if (fcntl(fd, F_SETFL, is_blocking ? 0 : O_NONBLOCK) == -1) {
-#elif TD_PORT_WINDOWS
-Status set_native_socket_is_blocking(SOCKET fd, bool is_blocking) {
- u_long mode = is_blocking;
- if (ioctlsocket(fd, FIONBIO, &mode) != 0) {
-#endif
- return OS_SOCKET_ERROR("Failed to change socket flags");
- }
- return Status::OK();
-}
-} // namespace detail
-
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.h
deleted file mode 100644
index 5ce9b6cedc..0000000000
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.h
+++ /dev/null
@@ -1,226 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#pragma once
-
-#include "td/utils/port/config.h"
-
-#include "td/utils/common.h"
-#include "td/utils/Slice.h"
-#include "td/utils/Status.h"
-
-#if TD_PORT_WINDOWS
-#include "td/utils/port/IPAddress.h"
-
-#include <memory>
-#endif
-
-#if TD_PORT_POSIX
-#include <errno.h>
-
-#include <atomic>
-#include <type_traits>
-#endif
-
-namespace td {
-
-class ObserverBase;
-
-#if TD_PORT_WINDOWS
-namespace detail {
-class EventFdWindows;
-} // namespace detail
-#endif
-
-class Fd {
- public:
- // TODO: Close may be not enough
- // Sometimes descriptor is half-closed.
-
- enum Flag : int32 {
- Write = 0x001,
- Read = 0x002,
- Close = 0x004,
- Error = 0x008,
- All = Write | Read | Close | Error,
- None = 0
- };
- using Flags = int32;
- enum class Mode { Reference, Owner };
-
- Fd();
- Fd(const Fd &) = delete;
- Fd &operator=(const Fd &) = delete;
- Fd(Fd &&other);
- Fd &operator=(Fd &&other);
- ~Fd();
-
-#if TD_PORT_POSIX
- Fd(int fd, Mode mode);
-#endif
-#if TD_PORT_WINDOWS
- static Fd create_file_fd(HANDLE handle);
-
- static Fd create_socket_fd(SOCKET sock);
-
- static Fd create_server_socket_fd(SOCKET sock, int socket_family);
-
- static Fd create_event_fd();
-#endif
-
- Fd clone() const;
-
- static Fd &Stderr();
- static Fd &Stdout();
- static Fd &Stdin();
-
- static Status duplicate(const Fd &from, Fd &to);
-
- bool empty() const;
-
- const Fd &get_fd() const;
- Fd &get_fd();
-
- void set_observer(ObserverBase *observer);
- ObserverBase *get_observer() const;
-
- void close();
-
- void update_flags(Flags flags);
-
- Flags get_flags() const;
-
- bool has_pending_error() const;
- Status get_pending_error() TD_WARN_UNUSED_RESULT;
-
- Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT;
- Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT;
-
- Status set_is_blocking(bool is_blocking);
-
-#if TD_PORT_POSIX
- void update_flags_notify(Flags flags);
- void clear_flags(Flags flags);
-
- Result<size_t> write_unsafe(Slice slice) TD_WARN_UNUSED_RESULT;
-
- int get_native_fd() const;
- int move_as_native_fd();
-#endif
-
-#if TD_PORT_WINDOWS
- Result<Fd> accept() TD_WARN_UNUSED_RESULT;
- void connect(const IPAddress &addr);
-
- uint64 get_key() const;
-
- HANDLE get_read_event();
- HANDLE get_write_event();
- void on_read_event();
- void on_write_event();
-
- SOCKET get_native_socket() const;
- HANDLE get_io_handle() const;
-#endif
-
- private:
- Mode mode_ = Mode::Owner;
-
-#if TD_PORT_POSIX
- struct Info {
- std::atomic<int> refcnt;
- int32 flags;
- ObserverBase *observer;
- };
- struct InfoSet {
- InfoSet();
- Info &get_info(int32 id);
-
- private:
- static constexpr int MAX_FD = 1 << 18;
- Info fd_array_[MAX_FD];
- };
- static InfoSet fd_info_set_;
-
- static Fd stderr_;
- static Fd stdout_;
- static Fd stdin_;
-
- void update_flags_inner(int32 new_flags, bool notify_flag);
- Info *get_info();
- const Info *get_info() const;
- void clear_info();
-
- void close_ref();
- void close_own();
-
- int fd_ = -1;
-#endif
-#if TD_PORT_WINDOWS
- class FdImpl;
-
- enum class Type { Empty, EventFd, FileFd, StdinFileFd, SocketFd, ServerSocketFd };
-
- Fd(Type type, Mode mode, HANDLE handle);
- Fd(Type type, Mode mode, SOCKET sock, int socket_family);
- explicit Fd(std::shared_ptr<FdImpl> impl);
-
- friend class detail::EventFdWindows; // for release and acquire
-
- void acquire();
- void release();
-
- std::shared_ptr<FdImpl> impl_;
-#endif
-};
-
-#if TD_PORT_POSIX
-template <class F>
-auto skip_eintr(F &&f) {
- decltype(f()) res;
- static_assert(std::is_integral<decltype(res)>::value, "integral type expected");
- do {
- errno = 0; // just in case
- res = f();
- } while (res < 0 && errno == EINTR);
- return res;
-}
-template <class F>
-auto skip_eintr_cstr(F &&f) {
- char *res;
- do {
- errno = 0; // just in case
- res = f();
- } while (res == nullptr && errno == EINTR);
- return res;
-}
-#endif
-
-template <class FdT>
-bool can_read(const FdT &fd) {
- return (fd.get_flags() & Fd::Read) != 0;
-}
-
-template <class FdT>
-bool can_write(const FdT &fd) {
- return (fd.get_flags() & Fd::Write) != 0;
-}
-
-template <class FdT>
-bool can_close(const FdT &fd) {
- return (fd.get_flags() & Fd::Close) != 0;
-}
-
-namespace detail {
-#if TD_PORT_POSIX
-Status set_native_socket_is_blocking(int fd, bool is_blocking);
-#endif
-#if TD_PORT_WINDOWS
-Status set_native_socket_is_blocking(SOCKET fd, bool is_blocking);
-#endif
-} // namespace detail
-
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.cpp
index 17a2727f64..6300f9f85f 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,25 +7,42 @@
#include "td/utils/port/FileFd.h"
#if TD_PORT_WINDOWS
-#include "td/utils/misc.h" // for narrow_cast
-
+#include "td/utils/port/FromApp.h"
#include "td/utils/port/Stat.h"
#include "td/utils/port/wstring_convert.h"
#endif
+#include "td/utils/common.h"
+#include "td/utils/ExitGuard.h"
+#include "td/utils/FlatHashSet.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/detail/skip_eintr.h"
+#include "td/utils/port/PollFlags.h"
#include "td/utils/port/sleep.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/StringBuilder.h"
#include <cstring>
+#include <mutex>
+#include <utility>
#if TD_PORT_POSIX
+#include <cerrno>
+
#include <fcntl.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
+#else
+#include <limits>
+#endif
+
+#if TD_PORT_WINDOWS && defined(WIN32_LEAN_AND_MEAN)
+#include <winioctl.h>
#endif
namespace td {
@@ -38,8 +55,8 @@ struct PrintFlags {
StringBuilder &operator<<(StringBuilder &sb, const PrintFlags &print_flags) {
auto flags = print_flags.flags;
- if (flags &
- ~(FileFd::Write | FileFd::Read | FileFd::Truncate | FileFd::Create | FileFd::Append | FileFd::CreateNew)) {
+ if (flags & ~(FileFd::Write | FileFd::Read | FileFd::Truncate | FileFd::Create | FileFd::Append | FileFd::CreateNew |
+ FileFd::Direct | FileFd::WinStat)) {
return sb << "opened with invalid flags " << flags;
}
@@ -72,21 +89,34 @@ StringBuilder &operator<<(StringBuilder &sb, const PrintFlags &print_flags) {
if (flags & FileFd::Truncate) {
sb << " with truncation";
}
+ if (flags & FileFd::Direct) {
+ sb << " for direct io";
+ }
+ if (flags & FileFd::WinStat) {
+ sb << " for stat";
+ }
return sb;
}
} // namespace
-const Fd &FileFd::get_fd() const {
- return fd_;
-}
+namespace detail {
+class FileFdImpl {
+ public:
+ PollableFdInfo info_;
+};
+} // namespace detail
+
+FileFd::FileFd() = default;
+FileFd::FileFd(FileFd &&) noexcept = default;
+FileFd &FileFd::operator=(FileFd &&) noexcept = default;
+FileFd::~FileFd() = default;
-Fd &FileFd::get_fd() {
- return fd_;
+FileFd::FileFd(unique_ptr<detail::FileFdImpl> impl) : impl_(std::move(impl)) {
}
Result<FileFd> FileFd::open(CSlice filepath, int32 flags, int32 mode) {
- if (flags & ~(Write | Read | Truncate | Create | Append | CreateNew)) {
+ if (flags & ~(Write | Read | Truncate | Create | Append | CreateNew | Direct | WinStat)) {
return Status::Error(PSLICE() << "File \"" << filepath << "\" has failed to be " << PrintFlags{flags});
}
@@ -121,13 +151,17 @@ Result<FileFd> FileFd::open(CSlice filepath, int32 flags, int32 mode) {
native_flags |= O_APPEND;
}
- int native_fd = skip_eintr([&] { return ::open(filepath.c_str(), native_flags, static_cast<mode_t>(mode)); });
+#if TD_LINUX
+ if (flags & Direct) {
+ native_flags |= O_DIRECT;
+ }
+#endif
+
+ int native_fd = detail::skip_eintr([&] { return ::open(filepath.c_str(), native_flags, static_cast<mode_t>(mode)); });
if (native_fd < 0) {
return OS_ERROR(PSLICE() << "File \"" << filepath << "\" can't be " << PrintFlags{flags});
}
-
- FileFd result;
- result.fd_ = Fd(native_fd, Fd::Mode::Owner);
+ return from_native_fd(NativeFd(native_fd));
#elif TD_PORT_WINDOWS
// TODO: support modes
auto r_filepath = to_wstring(filepath);
@@ -165,165 +199,243 @@ Result<FileFd> FileFd::open(CSlice filepath, int32 flags, int32 mode) {
}
}
+ DWORD native_flags = 0;
+ if (flags & Direct) {
+ native_flags |= FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING;
+ }
+ if (flags & WinStat) {
+ native_flags |= FILE_FLAG_BACKUP_SEMANTICS;
+ }
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
- auto handle = CreateFile(w_filepath.c_str(), desired_access, share_mode, nullptr, creation_disposition, 0, nullptr);
+ auto handle =
+ CreateFile(w_filepath.c_str(), desired_access, share_mode, nullptr, creation_disposition, native_flags, nullptr);
#else
- auto handle = CreateFile2(w_filepath.c_str(), desired_access, share_mode, creation_disposition, nullptr);
+ CREATEFILE2_EXTENDED_PARAMETERS extended_parameters;
+ std::memset(&extended_parameters, 0, sizeof(extended_parameters));
+ extended_parameters.dwSize = sizeof(extended_parameters);
+ extended_parameters.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
+ extended_parameters.dwFileFlags = native_flags;
+ auto handle = td::CreateFile2FromAppW(w_filepath.c_str(), desired_access, share_mode, creation_disposition,
+ &extended_parameters);
#endif
if (handle == INVALID_HANDLE_VALUE) {
return OS_ERROR(PSLICE() << "File \"" << filepath << "\" can't be " << PrintFlags{flags});
}
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
+ if (flags & Write) {
+ DWORD bytes_returned = 0;
+ DeviceIoControl(handle, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytes_returned, nullptr);
+ }
+#endif
+ auto native_fd = NativeFd(handle);
if (flags & Append) {
LARGE_INTEGER offset;
offset.QuadPart = 0;
auto set_pointer_res = SetFilePointerEx(handle, offset, nullptr, FILE_END);
if (!set_pointer_res) {
- auto res = OS_ERROR(PSLICE() << "Failed to seek to the end of file \"" << filepath << "\"");
- CloseHandle(handle);
- return res;
+ return OS_ERROR(PSLICE() << "Failed to seek to the end of file \"" << filepath << "\"");
}
}
- FileFd result;
- result.fd_ = Fd::create_file_fd(handle);
+ return from_native_fd(std::move(native_fd));
#endif
- result.fd_.update_flags(Fd::Flag::Write);
- return std::move(result);
+}
+
+FileFd FileFd::from_native_fd(NativeFd native_fd) {
+ auto impl = make_unique<detail::FileFdImpl>();
+ impl->info_.set_native_fd(std::move(native_fd));
+ impl->info_.add_flags(PollFlags::Write());
+ return FileFd(std::move(impl));
}
Result<size_t> FileFd::write(Slice slice) {
+ auto native_fd = get_native_fd().fd();
#if TD_PORT_POSIX
- CHECK(!fd_.empty());
- int native_fd = get_native_fd();
- auto write_res = skip_eintr([&] { return ::write(native_fd, slice.begin(), slice.size()); });
- if (write_res >= 0) {
- return narrow_cast<size_t>(write_res);
- }
-
- auto write_errno = errno;
- auto error = Status::PosixError(write_errno, PSLICE() << "Write to [fd = " << native_fd << "] has failed");
- if (write_errno != EAGAIN
-#if EAGAIN != EWOULDBLOCK
- && write_errno != EWOULDBLOCK
-#endif
- && write_errno != EIO) {
- LOG(ERROR) << error;
- }
- return std::move(error);
+ auto bytes_written = detail::skip_eintr([&] { return ::write(native_fd, slice.begin(), slice.size()); });
+ bool success = bytes_written >= 0;
#elif TD_PORT_WINDOWS
- return fd_.write(slice);
+ DWORD bytes_written = 0;
+ BOOL success = WriteFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_written, nullptr);
#endif
+ if (success) {
+ auto result = narrow_cast<size_t>(bytes_written);
+ CHECK(result <= slice.size());
+ return result;
+ }
+ return OS_ERROR(PSLICE() << "Write to " << get_native_fd() << " has failed");
}
-Result<size_t> FileFd::read(MutableSlice slice) {
+Result<size_t> FileFd::writev(Span<IoSlice> slices) {
#if TD_PORT_POSIX
- CHECK(!fd_.empty());
- int native_fd = get_native_fd();
- auto read_res = skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); });
- auto read_errno = errno;
-
- if (read_res >= 0) {
- if (narrow_cast<size_t>(read_res) < slice.size()) {
- fd_.clear_flags(Read);
+ auto native_fd = get_native_fd().fd();
+ TRY_RESULT(slices_size, narrow_cast_safe<int>(slices.size()));
+ auto bytes_written = detail::skip_eintr([&] { return ::writev(native_fd, slices.begin(), slices_size); });
+ bool success = bytes_written >= 0;
+ if (success) {
+ auto result = narrow_cast<size_t>(bytes_written);
+ auto left = result;
+ for (const auto &slice : slices) {
+ if (left <= slice.iov_len) {
+ return result;
+ }
+ left -= slice.iov_len;
+ }
+ UNREACHABLE();
+ }
+ return OS_ERROR(PSLICE() << "Writev to " << get_native_fd() << " has failed");
+#else
+ size_t res = 0;
+ for (const auto &slice : slices) {
+ if (slice.size() > std::numeric_limits<size_t>::max() - res) {
+ break;
+ }
+ TRY_RESULT(size, write(slice));
+ res += size;
+ if (size != slice.size()) {
+ CHECK(size < slice.size());
+ break;
}
- return static_cast<size_t>(read_res);
}
+ return res;
+#endif
+}
- auto error = Status::PosixError(read_errno, PSLICE() << "Read from [fd = " << native_fd << "] has failed");
- if (read_errno != EAGAIN
+Result<size_t> FileFd::read(MutableSlice slice) {
+ auto native_fd = get_native_fd().fd();
+#if TD_PORT_POSIX
+ auto bytes_read = detail::skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); });
+ bool success = bytes_read >= 0;
+ if (!success) {
+ auto read_errno = errno;
+ if (read_errno == EAGAIN
#if EAGAIN != EWOULDBLOCK
- && read_errno != EWOULDBLOCK
+ || read_errno == EWOULDBLOCK
#endif
- && read_errno != EIO) {
- LOG(ERROR) << error;
+ ) {
+ success = true;
+ bytes_read = 0;
+ }
}
- return std::move(error);
+ bool is_eof = success && narrow_cast<size_t>(bytes_read) < slice.size();
#elif TD_PORT_WINDOWS
- return fd_.read(slice);
+ DWORD bytes_read = 0;
+ BOOL success = ReadFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, nullptr);
+ bool is_eof = bytes_read == 0;
#endif
+ if (success) {
+ if (is_eof) {
+ get_poll_info().clear_flags(PollFlags::Read());
+ }
+ auto result = narrow_cast<size_t>(bytes_read);
+ CHECK(result <= slice.size());
+ return result;
+ }
+ return OS_ERROR(PSLICE() << "Read from " << get_native_fd() << " has failed");
}
Result<size_t> FileFd::pwrite(Slice slice, int64 offset) {
if (offset < 0) {
return Status::Error("Offset must be non-negative");
}
+ auto native_fd = get_native_fd().fd();
#if TD_PORT_POSIX
TRY_RESULT(offset_off_t, narrow_cast_safe<off_t>(offset));
- CHECK(!fd_.empty());
- int native_fd = get_native_fd();
- auto pwrite_res = skip_eintr([&] { return ::pwrite(native_fd, slice.begin(), slice.size(), offset_off_t); });
- if (pwrite_res >= 0) {
- return narrow_cast<size_t>(pwrite_res);
- }
-
- auto pwrite_errno = errno;
- auto error = Status::PosixError(
- pwrite_errno, PSLICE() << "Pwrite to [fd = " << native_fd << "] at [offset = " << offset << "] has failed");
- if (pwrite_errno != EAGAIN
-#if EAGAIN != EWOULDBLOCK
- && pwrite_errno != EWOULDBLOCK
-#endif
- && pwrite_errno != EIO) {
- LOG(ERROR) << error;
- }
- return std::move(error);
+ auto bytes_written =
+ detail::skip_eintr([&] { return ::pwrite(native_fd, slice.begin(), slice.size(), offset_off_t); });
+ bool success = bytes_written >= 0;
#elif TD_PORT_WINDOWS
DWORD bytes_written = 0;
OVERLAPPED overlapped;
std::memset(&overlapped, 0, sizeof(overlapped));
overlapped.Offset = static_cast<DWORD>(offset);
overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
- auto res =
- WriteFile(fd_.get_io_handle(), slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_written, &overlapped);
- if (!res) {
- return OS_ERROR("Failed to pwrite");
- }
- return bytes_written;
+ BOOL success = WriteFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_written, &overlapped);
#endif
+ if (success) {
+ auto result = narrow_cast<size_t>(bytes_written);
+ CHECK(result <= slice.size());
+ return result;
+ }
+ return OS_ERROR(PSLICE() << "Pwrite to " << get_native_fd() << " at offset " << offset << " has failed");
}
-Result<size_t> FileFd::pread(MutableSlice slice, int64 offset) {
+Result<size_t> FileFd::pread(MutableSlice slice, int64 offset) const {
if (offset < 0) {
return Status::Error("Offset must be non-negative");
}
+ auto native_fd = get_native_fd().fd();
#if TD_PORT_POSIX
TRY_RESULT(offset_off_t, narrow_cast_safe<off_t>(offset));
- CHECK(!fd_.empty());
- int native_fd = get_native_fd();
- auto pread_res = skip_eintr([&] { return ::pread(native_fd, slice.begin(), slice.size(), offset_off_t); });
- if (pread_res >= 0) {
- return narrow_cast<size_t>(pread_res);
- }
-
- auto pread_errno = errno;
- auto error = Status::PosixError(
- pread_errno, PSLICE() << "Pread from [fd = " << native_fd << "] at [offset = " << offset << "] has failed");
- if (pread_errno != EAGAIN
-#if EAGAIN != EWOULDBLOCK
- && pread_errno != EWOULDBLOCK
-#endif
- && pread_errno != EIO) {
- LOG(ERROR) << error;
- }
- return std::move(error);
+ auto bytes_read = detail::skip_eintr([&] { return ::pread(native_fd, slice.begin(), slice.size(), offset_off_t); });
+ bool success = bytes_read >= 0;
#elif TD_PORT_WINDOWS
DWORD bytes_read = 0;
OVERLAPPED overlapped;
std::memset(&overlapped, 0, sizeof(overlapped));
overlapped.Offset = static_cast<DWORD>(offset);
overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
- auto res = ReadFile(fd_.get_io_handle(), slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, &overlapped);
- if (!res) {
- return OS_ERROR("Failed to pread");
- }
- return bytes_read;
+ BOOL success = ReadFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, &overlapped);
#endif
+ if (success) {
+ auto result = narrow_cast<size_t>(bytes_read);
+ CHECK(result <= slice.size());
+ return result;
+ }
+ return OS_ERROR(PSLICE() << "Pread from " << get_native_fd() << " at offset " << offset << " has failed");
+}
+
+static std::mutex in_process_lock_mutex;
+static FlatHashSet<string> locked_files;
+static ExitGuard exit_guard;
+
+static Status create_local_lock(const string &path, int32 &max_tries) {
+ while (true) {
+ { // mutex lock scope
+ std::lock_guard<std::mutex> lock(in_process_lock_mutex);
+ if (!path.empty() && locked_files.count(path) == 0) {
+ VLOG(fd) << "Lock file \"" << path << '"';
+ locked_files.insert(path);
+ return Status::OK();
+ }
+ }
+
+ if (--max_tries <= 0) {
+ return Status::Error(
+ 0, PSLICE() << "Can't lock file \"" << path << "\", because it is already in use by current program");
+ }
+
+ usleep_for(100000);
+ }
}
-Status FileFd::lock(FileFd::LockFlags flags, int32 max_tries) {
+Status FileFd::lock(LockFlags flags, const string &path, int32 max_tries) {
if (max_tries <= 0) {
- return Status::Error(0, "Can't lock file: wrong max_tries");
+ return Status::Error("Can't lock file: wrong max_tries");
+ }
+
+ bool need_local_unlock = false;
+ if (!path.empty()) {
+ if (flags == LockFlags::Unlock) {
+ need_local_unlock = true;
+ } else if (flags == LockFlags::Read) {
+ LOG(FATAL) << "Local locking in Read mode is unsupported";
+ } else {
+ CHECK(flags == LockFlags::Write);
+ VLOG(fd) << "Trying to lock file \"" << path << '"';
+ TRY_STATUS(create_local_lock(path, max_tries));
+ need_local_unlock = true;
+ }
}
+ SCOPE_EXIT {
+ if (need_local_unlock) {
+ remove_local_lock(path);
+ }
+ };
+#if TD_PORT_POSIX
+ auto native_fd = get_native_fd().fd();
+#elif TD_PORT_WINDOWS
+ auto native_fd = get_native_fd().fd();
+#endif
while (true) {
#if TD_PORT_POSIX
struct flock lock;
@@ -344,95 +456,148 @@ Status FileFd::lock(FileFd::LockFlags flags, int32 max_tries) {
}());
lock.l_whence = SEEK_SET;
- if (fcntl(get_native_fd(), F_SETLK, &lock) == -1) {
- if (errno == EAGAIN && --max_tries > 0) {
+ if (fcntl(native_fd, F_SETLK, &lock) == -1) {
+ if (errno == EAGAIN) {
#elif TD_PORT_WINDOWS
OVERLAPPED overlapped;
std::memset(&overlapped, 0, sizeof(overlapped));
BOOL result;
if (flags == LockFlags::Unlock) {
- result = UnlockFileEx(fd_.get_io_handle(), 0, MAXDWORD, MAXDWORD, &overlapped);
+ result = UnlockFileEx(native_fd, 0, MAXDWORD, MAXDWORD, &overlapped);
} else {
DWORD dw_flags = LOCKFILE_FAIL_IMMEDIATELY;
if (flags == LockFlags::Write) {
dw_flags |= LOCKFILE_EXCLUSIVE_LOCK;
}
- result = LockFileEx(fd_.get_io_handle(), dw_flags, 0, MAXDWORD, MAXDWORD, &overlapped);
+ result = LockFileEx(native_fd, dw_flags, 0, MAXDWORD, MAXDWORD, &overlapped);
}
if (!result) {
- if (GetLastError() == ERROR_LOCK_VIOLATION && --max_tries > 0) {
+ if (GetLastError() == ERROR_LOCK_VIOLATION) {
#endif
- usleep_for(100000);
- continue;
+ if (--max_tries > 0) {
+ usleep_for(100000);
+ continue;
+ }
+
+ return OS_ERROR(PSLICE() << "Can't lock file \"" << path
+ << "\", because it is already in use; check for another program instance running");
}
return OS_ERROR("Can't lock file");
}
- return Status::OK();
+
+ break;
}
-}
-void FileFd::close() {
- fd_.close();
+ if (flags == LockFlags::Write) {
+ need_local_unlock = false;
+ }
+ return Status::OK();
}
-bool FileFd::empty() const {
- return fd_.empty();
+void FileFd::remove_local_lock(const string &path) {
+ if (path.empty() || ExitGuard::is_exited()) {
+ return;
+ }
+ VLOG(fd) << "Unlock file \"" << path << '"';
+ std::unique_lock<std::mutex> lock(in_process_lock_mutex);
+ auto erased_count = locked_files.erase(path);
+ CHECK(erased_count > 0 || path.empty() || ExitGuard::is_exited());
}
-#if TD_PORT_POSIX
-int FileFd::get_native_fd() const {
- return fd_.get_native_fd();
+void FileFd::close() {
+ impl_.reset();
}
-#endif
-int32 FileFd::get_flags() const {
- return fd_.get_flags();
+bool FileFd::empty() const {
+ return !impl_;
}
-void FileFd::update_flags(Fd::Flags mask) {
- fd_.update_flags(mask);
+const NativeFd &FileFd::get_native_fd() const {
+ return get_poll_info().native_fd();
}
-int64 FileFd::get_size() {
- return stat().size_;
+NativeFd FileFd::move_as_native_fd() {
+ auto res = get_poll_info().move_as_native_fd();
+ impl_.reset();
+ return res;
}
#if TD_PORT_WINDOWS
-static uint64 filetime_to_unix_time_nsec(LONGLONG filetime) {
+namespace {
+
+uint64 filetime_to_unix_time_nsec(LONGLONG filetime) {
const auto FILETIME_UNIX_TIME_DIFF = 116444736000000000ll;
return static_cast<uint64>((filetime - FILETIME_UNIX_TIME_DIFF) * 100);
}
+
+struct FileSize {
+ int64 size_;
+ int64 real_size_;
+};
+
+Result<FileSize> get_file_size(const FileFd &file_fd) {
+ FILE_STANDARD_INFO standard_info;
+ if (!GetFileInformationByHandleEx(file_fd.get_native_fd().fd(), FileStandardInfo, &standard_info,
+ sizeof(standard_info))) {
+ return OS_ERROR("Get FileStandardInfo failed");
+ }
+ FileSize res;
+ res.size_ = standard_info.EndOfFile.QuadPart;
+ res.real_size_ = standard_info.AllocationSize.QuadPart;
+
+ if (res.size_ > 0 && res.real_size_ <= 0) { // just in case
+ LOG(ERROR) << "Fix real file size from " << res.real_size_ << " to " << res.size_;
+ res.real_size_ = res.size_;
+ }
+
+ return res;
+}
+
+} // namespace
+#endif
+
+Result<int64> FileFd::get_size() const {
+#if TD_PORT_POSIX
+ TRY_RESULT(s, stat());
+#elif TD_PORT_WINDOWS
+ TRY_RESULT(s, get_file_size(*this));
#endif
+ return s.size_;
+}
-Stat FileFd::stat() {
+Result<int64> FileFd::get_real_size() const {
+#if TD_PORT_POSIX
+ TRY_RESULT(s, stat());
+#elif TD_PORT_WINDOWS
+ TRY_RESULT(s, get_file_size(*this));
+#endif
+ return s.real_size_;
+}
+
+Result<Stat> FileFd::stat() const {
CHECK(!empty());
#if TD_PORT_POSIX
- return detail::fstat(get_native_fd());
+ return detail::fstat(get_native_fd().fd());
#elif TD_PORT_WINDOWS
Stat res;
FILE_BASIC_INFO basic_info;
- auto status = GetFileInformationByHandleEx(fd_.get_io_handle(), FileBasicInfo, &basic_info, sizeof(basic_info));
+ auto status = GetFileInformationByHandleEx(get_native_fd().fd(), FileBasicInfo, &basic_info, sizeof(basic_info));
if (!status) {
- auto error = OS_ERROR("Stat failed");
- LOG(FATAL) << error;
+ return OS_ERROR("Get FileBasicInfo failed");
}
res.atime_nsec_ = filetime_to_unix_time_nsec(basic_info.LastAccessTime.QuadPart);
res.mtime_nsec_ = filetime_to_unix_time_nsec(basic_info.LastWriteTime.QuadPart);
res.is_dir_ = (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
- res.is_reg_ = true;
+ res.is_reg_ = !res.is_dir_; // TODO this is still wrong
- FILE_STANDARD_INFO standard_info;
- status = GetFileInformationByHandleEx(fd_.get_io_handle(), FileStandardInfo, &standard_info, sizeof(standard_info));
- if (!status) {
- auto error = OS_ERROR("Stat failed");
- LOG(FATAL) << error;
- }
- res.size_ = standard_info.EndOfFile.QuadPart;
+ TRY_RESULT(file_size, get_file_size(*this));
+ res.size_ = file_size.size_;
+ res.real_size_ = file_size.real_size_;
return res;
#endif
@@ -441,9 +606,13 @@ Stat FileFd::stat() {
Status FileFd::sync() {
CHECK(!empty());
#if TD_PORT_POSIX
- if (fsync(fd_.get_native_fd()) != 0) {
+#if TD_DARWIN
+ if (detail::skip_eintr([&] { return fcntl(get_native_fd().fd(), F_FULLFSYNC); }) == -1) {
+#else
+ if (detail::skip_eintr([&] { return fsync(get_native_fd().fd()); }) != 0) {
+#endif
#elif TD_PORT_WINDOWS
- if (FlushFileBuffers(fd_.get_io_handle()) == 0) {
+ if (FlushFileBuffers(get_native_fd().fd()) == 0) {
#endif
return OS_ERROR("Sync failed");
}
@@ -454,11 +623,11 @@ Status FileFd::seek(int64 position) {
CHECK(!empty());
#if TD_PORT_POSIX
TRY_RESULT(position_off_t, narrow_cast_safe<off_t>(position));
- if (skip_eintr([&] { return ::lseek(fd_.get_native_fd(), position_off_t, SEEK_SET); }) < 0) {
+ if (detail::skip_eintr([&] { return ::lseek(get_native_fd().fd(), position_off_t, SEEK_SET); }) < 0) {
#elif TD_PORT_WINDOWS
LARGE_INTEGER offset;
offset.QuadPart = position;
- if (SetFilePointerEx(fd_.get_io_handle(), offset, nullptr, FILE_BEGIN) == 0) {
+ if (SetFilePointerEx(get_native_fd().fd(), offset, nullptr, FILE_BEGIN) == 0) {
#endif
return OS_ERROR("Seek failed");
}
@@ -469,13 +638,21 @@ Status FileFd::truncate_to_current_position(int64 current_position) {
CHECK(!empty());
#if TD_PORT_POSIX
TRY_RESULT(current_position_off_t, narrow_cast_safe<off_t>(current_position));
- if (skip_eintr([&] { return ::ftruncate(fd_.get_native_fd(), current_position_off_t); }) < 0) {
+ if (detail::skip_eintr([&] { return ::ftruncate(get_native_fd().fd(), current_position_off_t); }) < 0) {
#elif TD_PORT_WINDOWS
- if (SetEndOfFile(fd_.get_io_handle()) == 0) {
+ if (SetEndOfFile(get_native_fd().fd()) == 0) {
#endif
return OS_ERROR("Truncate failed");
}
return Status::OK();
}
+PollableFdInfo &FileFd::get_poll_info() {
+ CHECK(!empty());
+ return impl_->info_;
+}
+const PollableFdInfo &FileFd::get_poll_info() const {
+ CHECK(!empty());
+ return impl_->info_;
+}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.h
index bf7166c1de..3dc88da5cb 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,42 +9,57 @@
#include "td/utils/port/config.h"
#include "td/utils/common.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/IoSlice.h"
#include "td/utils/port/Stat.h"
#include "td/utils/Slice.h"
+#include "td/utils/Span.h"
#include "td/utils/Status.h"
namespace td {
+namespace detail {
+class FileFdImpl;
+} // namespace detail
class FileFd {
public:
- FileFd() = default;
+ FileFd();
+ FileFd(FileFd &&) noexcept;
+ FileFd &operator=(FileFd &&) noexcept;
+ ~FileFd();
+ FileFd(const FileFd &) = delete;
+ FileFd &operator=(const FileFd &) = delete;
- enum Flags : int32 { Write = 1, Read = 2, Truncate = 4, Create = 8, Append = 16, CreateNew = 32 };
-
- const Fd &get_fd() const;
- Fd &get_fd();
+ enum Flags : int32 { Write = 1, Read = 2, Truncate = 4, Create = 8, Append = 16, CreateNew = 32, Direct = 64 };
+ enum PrivateFlags : int32 { WinStat = 128 };
static Result<FileFd> open(CSlice filepath, int32 flags, int32 mode = 0600) TD_WARN_UNUSED_RESULT;
+ static FileFd from_native_fd(NativeFd fd) TD_WARN_UNUSED_RESULT;
Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT;
+ Result<size_t> writev(Span<IoSlice> slices) TD_WARN_UNUSED_RESULT;
Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT;
Result<size_t> pwrite(Slice slice, int64 offset) TD_WARN_UNUSED_RESULT;
- Result<size_t> pread(MutableSlice slice, int64 offset) TD_WARN_UNUSED_RESULT;
+ Result<size_t> pread(MutableSlice slice, int64 offset) const TD_WARN_UNUSED_RESULT;
enum class LockFlags { Write, Read, Unlock };
- Status lock(LockFlags flags, int32 max_tries = 1) TD_WARN_UNUSED_RESULT;
+ Status lock(LockFlags flags, const string &path, int32 max_tries) TD_WARN_UNUSED_RESULT;
+ static void remove_local_lock(const string &path);
+
+ PollableFdInfo &get_poll_info();
+ const PollableFdInfo &get_poll_info() const;
void close();
+
bool empty() const;
- int32 get_flags() const;
- void update_flags(Fd::Flags mask);
+ Result<int64> get_size() const;
- int64 get_size();
+ Result<int64> get_real_size() const;
- Stat stat();
+ Result<Stat> stat() const;
Status sync() TD_WARN_UNUSED_RESULT;
@@ -52,12 +67,13 @@ class FileFd {
Status truncate_to_current_position(int64 current_position) TD_WARN_UNUSED_RESULT;
-#if TD_PORT_POSIX
- int get_native_fd() const;
-#endif
+ const NativeFd &get_native_fd() const;
+ NativeFd move_as_native_fd();
private:
- Fd fd_;
+ unique_ptr<detail::FileFdImpl> impl_;
+
+ explicit FileFd(unique_ptr<detail::FileFdImpl> impl);
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FromApp.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FromApp.h
new file mode 100644
index 0000000000..b33cbf070c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FromApp.h
@@ -0,0 +1,100 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+#ifdef TD_PORT_WINDOWS
+
+namespace td {
+
+#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
+inline HMODULE GetKernelModule() {
+ static const auto kernel_module = []() -> HMODULE {
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery(VirtualQuery, &mbi, sizeof(MEMORY_BASIC_INFORMATION))) {
+ return reinterpret_cast<HMODULE>(mbi.AllocationBase);
+ }
+ return nullptr;
+ }();
+ return kernel_module;
+}
+
+inline HMODULE LoadLibrary(LPCTSTR lpFileName) {
+ using pLoadLibrary = HMODULE(WINAPI *)(_In_ LPCTSTR);
+ static const auto proc_load_library =
+ reinterpret_cast<pLoadLibrary>(GetProcAddress(GetKernelModule(), "LoadLibraryW"));
+ return proc_load_library(lpFileName);
+}
+
+inline HMODULE GetFromAppModule() {
+ static const HMODULE from_app_module = LoadLibrary(L"api-ms-win-core-file-fromapp-l1-1-0.dll");
+ return from_app_module;
+}
+#endif
+
+template <int num, class T>
+T *get_from_app_function(const char *name, T *original_func) {
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
+ return original_func;
+#else
+ static T *func = [name, original_func]() -> T * {
+ auto func_pointer = GetProcAddress(GetFromAppModule(), name);
+ if (func_pointer == nullptr) {
+ return original_func;
+ }
+ return reinterpret_cast<T *>(func_pointer);
+ }();
+ return func;
+#endif
+}
+
+inline HANDLE CreateFile2FromAppW(_In_ LPCWSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode,
+ _In_ DWORD dwCreationDisposition,
+ _In_opt_ LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams) {
+ auto func = get_from_app_function<0>("CreateFile2FromAppW", &CreateFile2);
+ return func(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams);
+}
+
+inline BOOL CreateDirectoryFromAppW(_In_ LPCWSTR lpPathName, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes) {
+ auto func = get_from_app_function<1>("CreateDirectoryFromAppW", &CreateDirectory);
+ return func(lpPathName, lpSecurityAttributes);
+}
+
+inline BOOL RemoveDirectoryFromAppW(_In_ LPCWSTR lpPathName) {
+ auto func = get_from_app_function<2>("RemoveDirectoryFromAppW", &RemoveDirectory);
+ return func(lpPathName);
+}
+
+inline BOOL DeleteFileFromAppW(_In_ LPCWSTR lpFileName) {
+ auto func = get_from_app_function<3>("DeleteFileFromAppW", &DeleteFile);
+ return func(lpFileName);
+}
+
+inline BOOL MoveFileExFromAppW(_In_ LPCWSTR lpExistingFileName, _In_ LPCWSTR lpNewFileName, _In_ DWORD dwFlags) {
+ auto func = get_from_app_function<4>("MoveFileFromAppW", static_cast<BOOL(WINAPI *)(LPCWSTR, LPCWSTR)>(nullptr));
+ if (func == nullptr || (dwFlags & ~MOVEFILE_REPLACE_EXISTING) != 0) {
+ // if can't find MoveFileFromAppW or have unsupported flags, call MoveFileEx directly
+ return MoveFileEx(lpExistingFileName, lpNewFileName, dwFlags);
+ }
+ if ((dwFlags & MOVEFILE_REPLACE_EXISTING) != 0) {
+ td::DeleteFileFromAppW(lpNewFileName);
+ }
+ return func(lpExistingFileName, lpNewFileName);
+}
+
+inline HANDLE FindFirstFileExFromAppW(_In_ LPCWSTR lpFileName, _In_ FINDEX_INFO_LEVELS fInfoLevelId,
+ _Out_writes_bytes_(sizeof(WIN32_FIND_DATAW)) LPVOID lpFindFileData,
+ _In_ FINDEX_SEARCH_OPS fSearchOp, _Reserved_ LPVOID lpSearchFilter,
+ _In_ DWORD dwAdditionalFlags) {
+ auto func = get_from_app_function<5>("FindFirstFileExFromAppW", &FindFirstFileEx);
+ return func(lpFileName, fInfoLevelId, lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags);
+}
+
+} // namespace td
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.cpp
index 2d3a3cdbc0..32c26403d5 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.cpp
@@ -1,9 +1,11 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#define _WINSOCK_DEPRECATED_NO_WARNINGS // we need to use inet_addr instead of inet_pton
+
#include "td/utils/port/IPAddress.h"
#include "td/utils/format.h"
@@ -12,19 +14,185 @@
#include "td/utils/port/SocketFd.h"
#include "td/utils/port/thread_local.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/utf8.h"
-#if !TD_WINDOWS
+#if TD_WINDOWS
+#include "td/utils/port/wstring_convert.h"
+#else
#include <netdb.h>
-#include <netinet/in.h>
#include <netinet/tcp.h>
-#include <sys/socket.h>
#include <sys/types.h>
#endif
#include <cstring>
+#include <limits>
namespace td {
+static bool is_ascii_host_char(char c) {
+ return static_cast<unsigned char>(c) <= 127;
+}
+
+static bool is_ascii_host(Slice host) {
+ for (auto c : host) {
+ if (!is_ascii_host_char(c)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+#if !TD_WINDOWS
+static void punycode(string &result, Slice part) {
+ vector<uint32> codes;
+ codes.reserve(utf8_length(part));
+ uint32 processed = 0;
+ auto begin = part.ubegin();
+ auto end = part.uend();
+ while (begin != end) {
+ uint32 code;
+ begin = next_utf8_unsafe(begin, &code);
+ if (code <= 127u) {
+ result += to_lower(static_cast<char>(code));
+ processed++;
+ }
+ codes.push_back(code);
+ }
+
+ if (processed > 0) {
+ result += '-';
+ }
+
+ uint32 n = 127;
+ uint32 delta = 0;
+ int bias = -72;
+ bool is_first = true;
+ while (processed < codes.size()) {
+ // choose lowest not processed code
+ uint32 next_n = 0x110000;
+ for (auto code : codes) {
+ if (code > n && code < next_n) {
+ next_n = code;
+ }
+ }
+ delta += (next_n - n - 1) * (processed + 1);
+
+ for (auto code : codes) {
+ if (code < next_n) {
+ delta++;
+ }
+
+ if (code == next_n) {
+ // found next symbol, encode delta
+ auto left = static_cast<int>(delta);
+ while (true) {
+ bias += 36;
+ auto t = clamp(bias, 1, 26);
+ if (left < t) {
+ result += static_cast<char>(left + 'a');
+ break;
+ }
+
+ left -= t;
+ auto digit = t + left % (36 - t);
+ result += static_cast<char>(digit < 26 ? digit + 'a' : digit - 26 + '0');
+ left /= 36 - t;
+ }
+ processed++;
+
+ // update bias
+ if (is_first) {
+ delta /= 700;
+ is_first = false;
+ } else {
+ delta /= 2;
+ }
+ delta += delta / processed;
+
+ bias = 0;
+ while (delta > 35 * 13) {
+ delta /= 35;
+ bias -= 36;
+ }
+ bias -= static_cast<int>(36 * delta / (delta + 38));
+ delta = 0;
+ }
+ }
+
+ delta++;
+ n = next_n;
+ }
+}
+#endif
+
+Result<string> idn_to_ascii(CSlice host) {
+ if (is_ascii_host(host)) {
+ return to_lower(host);
+ }
+ if (!check_utf8(host)) {
+ return Status::Error("Host name must be encoded in UTF-8");
+ }
+
+ const int MAX_DNS_NAME_LENGTH = 255;
+ if (host.size() >= MAX_DNS_NAME_LENGTH * 4) { // upper bound, 4 characters per symbol
+ return Status::Error("Host name is too long");
+ }
+
+#if TD_WINDOWS
+ TRY_RESULT(whost, to_wstring(host));
+ wchar_t punycode[MAX_DNS_NAME_LENGTH + 1];
+ int result_length =
+ IdnToAscii(IDN_ALLOW_UNASSIGNED, whost.c_str(), narrow_cast<int>(whost.size()), punycode, MAX_DNS_NAME_LENGTH);
+ if (result_length == 0) {
+ return Status::Error("Host can't be converted to ASCII");
+ }
+
+ TRY_RESULT(idn_host, from_wstring(punycode, result_length));
+ return idn_host;
+#else
+ auto parts = full_split(Slice(host), '.');
+ bool is_first = true;
+ string result;
+ result.reserve(host.size());
+ for (auto part : parts) {
+ if (!is_first) {
+ result += '.';
+ }
+ if (is_ascii_host(part)) {
+ result.append(part.data(), part.size());
+ } else {
+ // TODO nameprep should be applied first, but punycode is better than nothing.
+ // It is better to use libidn/ICU here if available
+ result += "xn--";
+ punycode(result, part);
+ }
+ is_first = false;
+ }
+ return result;
+#endif
+}
+
+static CSlice get_ip_str(int family, const void *addr) {
+ const int buf_size = INET6_ADDRSTRLEN;
+ static TD_THREAD_LOCAL char *buf;
+ init_thread_local<char[]>(buf, buf_size);
+
+ const char *res = inet_ntop(family,
+#if TD_WINDOWS
+ const_cast<PVOID>(addr),
+#else
+ addr,
+#endif
+ buf, buf_size);
+ if (res == nullptr) {
+ return CSlice();
+ } else {
+ return CSlice(res);
+ }
+}
+
IPAddress::IPAddress() : is_valid_(false) {
}
@@ -32,19 +200,52 @@ bool IPAddress::is_valid() const {
return is_valid_;
}
+bool IPAddress::is_reserved() const {
+ CHECK(is_valid());
+
+ if (is_ipv6()) {
+ // TODO proper check for reserved IPv6 addresses
+ return true;
+ }
+
+ uint32 ip = get_ipv4();
+ struct IpBlock {
+ CSlice ip;
+ int mask;
+ IpBlock(CSlice ip, int mask) : ip(ip), mask(mask) {
+ }
+ };
+ static const IpBlock blocks[] = {{"0.0.0.0", 8}, {"10.0.0.0", 8}, {"100.64.0.0", 10}, {"127.0.0.0", 8},
+ {"169.254.0.0", 16}, {"172.16.0.0", 12}, {"192.0.0.0", 24}, {"192.0.2.0", 24},
+ {"192.88.99.0", 24}, {"192.168.0.0", 16}, {"198.18.0.0", 15}, {"198.51.100.0", 24},
+ {"203.0.113.0", 24}, {"224.0.0.0", 3}};
+ for (auto &block : blocks) {
+ IPAddress block_ip_address;
+ block_ip_address.init_ipv4_port(block.ip, 80).ensure();
+ uint32 range = block_ip_address.get_ipv4();
+ CHECK(block.mask != 0);
+ uint32 mask = std::numeric_limits<uint32>::max() >> (32 - block.mask) << (32 - block.mask);
+ if ((ip & mask) == (range & mask)) {
+ return true;
+ }
+ }
+ return false;
+}
+
const sockaddr *IPAddress::get_sockaddr() const {
+ CHECK(is_valid());
return &sockaddr_;
}
size_t IPAddress::get_sockaddr_len() const {
CHECK(is_valid());
- switch (addr_.ss_family) {
+ switch (sockaddr_.sa_family) {
case AF_INET6:
return sizeof(ipv6_addr_);
case AF_INET:
return sizeof(ipv4_addr_);
default:
- LOG(FATAL) << "Unknown address family";
+ UNREACHABLE();
return 0;
}
}
@@ -54,20 +255,24 @@ int IPAddress::get_address_family() const {
}
bool IPAddress::is_ipv4() const {
- return get_address_family() == AF_INET;
+ return is_valid() && get_address_family() == AF_INET;
+}
+
+bool IPAddress::is_ipv6() const {
+ return is_valid() && get_address_family() == AF_INET6;
}
uint32 IPAddress::get_ipv4() const {
CHECK(is_valid());
CHECK(is_ipv4());
- return ipv4_addr_.sin_addr.s_addr;
+ return htonl(ipv4_addr_.sin_addr.s_addr);
}
-Slice IPAddress::get_ipv6() const {
+string IPAddress::get_ipv6() const {
static_assert(sizeof(ipv6_addr_.sin6_addr) == 16, "ipv6 size == 16");
CHECK(is_valid());
CHECK(!is_ipv4());
- return Slice(ipv6_addr_.sin6_addr.s6_addr, 16);
+ return Slice(ipv6_addr_.sin6_addr.s6_addr, 16).str();
}
IPAddress IPAddress::get_any_addr() const {
@@ -80,18 +285,23 @@ IPAddress IPAddress::get_any_addr() const {
res.init_ipv4_any();
break;
default:
- LOG(FATAL) << "Unknown address family";
+ UNREACHABLE();
+ break;
}
return res;
}
+
void IPAddress::init_ipv4_any() {
is_valid_ = true;
+ std::memset(&ipv4_addr_, 0, sizeof(ipv4_addr_));
ipv4_addr_.sin_family = AF_INET;
ipv4_addr_.sin_addr.s_addr = INADDR_ANY;
ipv4_addr_.sin_port = 0;
}
+
void IPAddress::init_ipv6_any() {
is_valid_ = true;
+ std::memset(&ipv6_addr_, 0, sizeof(ipv6_addr_));
ipv6_addr_.sin6_family = AF_INET6;
ipv6_addr_.sin6_addr = in6addr_any;
ipv6_addr_.sin6_port = 0;
@@ -100,7 +310,12 @@ void IPAddress::init_ipv6_any() {
Status IPAddress::init_ipv6_port(CSlice ipv6, int port) {
is_valid_ = false;
if (port <= 0 || port >= (1 << 16)) {
- return Status::Error(PSLICE() << "Invalid [port=" << port << "]");
+ return Status::Error(PSLICE() << "Invalid [IPv6 address port=" << port << "]");
+ }
+ string ipv6_plain;
+ if (ipv6.size() > 2 && ipv6[0] == '[' && ipv6.back() == ']') {
+ ipv6_plain.assign(ipv6.begin() + 1, ipv6.size() - 2);
+ ipv6 = ipv6_plain;
}
std::memset(&ipv6_addr_, 0, sizeof(ipv6_addr_));
ipv6_addr_.sin6_family = AF_INET6;
@@ -122,7 +337,7 @@ Status IPAddress::init_ipv6_as_ipv4_port(CSlice ipv4, int port) {
Status IPAddress::init_ipv4_port(CSlice ipv4, int port) {
is_valid_ = false;
if (port <= 0 || port >= (1 << 16)) {
- return Status::Error(PSLICE() << "Invalid [port=" << port << "]");
+ return Status::Error(PSLICE() << "Invalid [IPv4 address port=" << port << "]");
}
std::memset(&ipv4_addr_, 0, sizeof(ipv4_addr_));
ipv4_addr_.sin_family = AF_INET;
@@ -137,36 +352,116 @@ Status IPAddress::init_ipv4_port(CSlice ipv4, int port) {
return Status::OK();
}
-Status IPAddress::init_host_port(CSlice host, int port) {
- auto str_port = to_string(port);
- return init_host_port(host, str_port);
+Result<IPAddress> IPAddress::get_ip_address(CSlice host) {
+ auto r_address = get_ipv4_address(host);
+ if (r_address.is_ok()) {
+ return r_address.move_as_ok();
+ }
+ r_address = get_ipv6_address(host);
+ if (r_address.is_ok()) {
+ return r_address.move_as_ok();
+ }
+ return Status::Error(PSLICE() << '"' << host << "\" is not a valid IP address");
+}
+
+Result<IPAddress> IPAddress::get_ipv4_address(CSlice host) {
+ // sometimes inet_addr allows much more valid IPv4 hosts than inet_pton,
+ // like 0x12.0x34.0x56.0x78, or 0x12345678, or 0x7f.001
+ auto ipv4_numeric_addr = inet_addr(host.c_str());
+ if (ipv4_numeric_addr == INADDR_NONE) {
+ return Status::Error(PSLICE() << '"' << host << "\" is not a valid IPv4 address");
+ }
+
+ host = ::td::get_ip_str(AF_INET, &ipv4_numeric_addr);
+ IPAddress result;
+ auto status = result.init_ipv4_port(host, 1);
+ if (status.is_error()) {
+ return std::move(status);
+ }
+ return std::move(result);
+}
+
+Result<IPAddress> IPAddress::get_ipv6_address(CSlice host) {
+ IPAddress result;
+ auto status = result.init_ipv6_port(host, 1);
+ if (status.is_error()) {
+ return Status::Error(PSLICE() << '"' << host << "\" is not a valid IPv6 address");
+ }
+ return std::move(result);
+}
+
+Status IPAddress::init_host_port(CSlice host, int port, bool prefer_ipv6) {
+ if (host.size() > 2 && host[0] == '[' && host.back() == ']') {
+ return init_ipv6_port(host, port == 0 ? 1 : port);
+ }
+
+ return init_host_port(host, PSLICE() << port, prefer_ipv6);
}
-Status IPAddress::init_host_port(CSlice host, CSlice port) {
+Status IPAddress::init_host_port(CSlice host, CSlice port, bool prefer_ipv6) {
+ is_valid_ = false;
+ if (host.empty()) {
+ return Status::Error("Host is empty");
+ }
+#if TD_WINDOWS
+ if (host == "..localmachine") {
+ return Status::Error("Host is invalid");
+ }
+#endif
+ TRY_RESULT(ascii_host, idn_to_ascii(host));
+ host = ascii_host; // assign string to CSlice
+
+ if (host[0] == '[' && host.back() == ']') {
+ auto port_int = to_integer<int>(port);
+ return init_ipv6_port(host, port_int == 0 ? 1 : port_int);
+ }
+
+ // some getaddrinfo implementations use inet_pton instead of inet_aton and support only decimal-dotted IPv4 form,
+ // and so doesn't recognize 0x12.0x34.0x56.0x78, or 0x12345678, or 0x7f.001 as valid IPv4 addresses
+ auto ipv4_numeric_addr = inet_addr(host.c_str());
+ if (ipv4_numeric_addr != INADDR_NONE) {
+ host = ::td::get_ip_str(AF_INET, &ipv4_numeric_addr);
+ }
+
addrinfo hints;
addrinfo *info = nullptr;
std::memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET; // TODO AF_UNSPEC;
+ hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
- LOG(INFO) << "Try to init IP address of " << host << " with port " << port;
- auto s = getaddrinfo(host.c_str(), port.c_str(), &hints, &info);
- if (s != 0) {
- return Status::Error(PSLICE() << "getaddrinfo: " << gai_strerror(s));
+ hints.ai_protocol = IPPROTO_TCP;
+ LOG(DEBUG + 10) << "Trying to init IP address of " << host << " with port " << port;
+ auto err = getaddrinfo(host.c_str(), port.c_str(), &hints, &info);
+ if (err != 0) {
+#if TD_WINDOWS
+ return OS_SOCKET_ERROR("Failed to resolve host");
+#else
+ return Status::Error(PSLICE() << "Failed to resolve host: " << gai_strerror(err));
+#endif
}
SCOPE_EXIT {
freeaddrinfo(info);
};
- // prefer ipv4
- addrinfo *best_info = info;
- for (auto *ptr = info->ai_next; ptr != nullptr; ptr = ptr->ai_next) {
- if (ptr->ai_socktype == AF_INET) {
+ addrinfo *best_info = nullptr;
+ for (auto *ptr = info; ptr != nullptr; ptr = ptr->ai_next) {
+ if (ptr->ai_family == AF_INET && (!prefer_ipv6 || best_info == nullptr)) {
+ // just use first IPv4 address if there is no IPv6 and it isn't preferred
best_info = ptr;
- break;
+ if (!prefer_ipv6) {
+ break;
+ }
}
+ if (ptr->ai_family == AF_INET6 && (prefer_ipv6 || best_info == nullptr)) {
+ // or first IPv6 address if there is no IPv4 and it isn't preferred
+ best_info = ptr;
+ if (prefer_ipv6) {
+ break;
+ }
+ }
+ }
+ if (best_info == nullptr) {
+ return Status::Error("Failed to find IPv4/IPv6 address");
}
- // just use first address
- CHECK(best_info != nullptr);
return init_sockaddr(best_info->ai_addr, narrow_cast<socklen_t>(best_info->ai_addrlen));
}
@@ -178,32 +473,39 @@ Status IPAddress::init_host_port(CSlice host_port) {
return init_host_port(host_port.substr(0, pos).str(), host_port.substr(pos + 1).str());
}
+Status IPAddress::init_sockaddr(sockaddr *addr) {
+ if (addr->sa_family == AF_INET6) {
+ return init_sockaddr(addr, sizeof(ipv6_addr_));
+ } else if (addr->sa_family == AF_INET) {
+ return init_sockaddr(addr, sizeof(ipv4_addr_));
+ } else {
+ return init_sockaddr(addr, 0);
+ }
+}
Status IPAddress::init_sockaddr(sockaddr *addr, socklen_t len) {
if (addr->sa_family == AF_INET6) {
CHECK(len == sizeof(ipv6_addr_));
std::memcpy(&ipv6_addr_, reinterpret_cast<sockaddr_in6 *>(addr), sizeof(ipv6_addr_));
- LOG(INFO) << "Have ipv6 address " << get_ip_str() << " with port " << get_port();
} else if (addr->sa_family == AF_INET) {
CHECK(len == sizeof(ipv4_addr_));
std::memcpy(&ipv4_addr_, reinterpret_cast<sockaddr_in *>(addr), sizeof(ipv4_addr_));
- LOG(INFO) << "Have ipv4 address " << get_ip_str() << " with port " << get_port();
} else {
return Status::Error(PSLICE() << "Unknown " << tag("sa_family", addr->sa_family));
}
is_valid_ = true;
+ LOG(DEBUG + 10) << "Have address " << get_ip_str() << " with port " << get_port();
return Status::OK();
}
Status IPAddress::init_socket_address(const SocketFd &socket_fd) {
is_valid_ = false;
-#if TD_WINDOWS
- auto fd = socket_fd.get_fd().get_native_socket();
-#else
- auto fd = socket_fd.get_fd().get_native_fd();
-#endif
- socklen_t len = sizeof(addr_);
- int ret = getsockname(fd, &sockaddr_, &len);
+ if (socket_fd.empty()) {
+ return Status::Error("Socket is empty");
+ }
+ auto socket = socket_fd.get_native_fd().socket();
+ socklen_t len = storage_size();
+ int ret = getsockname(socket, &sockaddr_, &len);
if (ret != 0) {
return OS_SOCKET_ERROR("Failed to get socket address");
}
@@ -213,13 +515,12 @@ Status IPAddress::init_socket_address(const SocketFd &socket_fd) {
Status IPAddress::init_peer_address(const SocketFd &socket_fd) {
is_valid_ = false;
-#if TD_WINDOWS
- auto fd = socket_fd.get_fd().get_native_socket();
-#else
- auto fd = socket_fd.get_fd().get_native_fd();
-#endif
- socklen_t len = sizeof(addr_);
- int ret = getpeername(fd, &sockaddr_, &len);
+ if (socket_fd.empty()) {
+ return Status::Error("Socket is empty");
+ }
+ auto socket = socket_fd.get_native_fd().socket();
+ socklen_t len = storage_size();
+ int ret = getpeername(socket, &sockaddr_, &len);
if (ret != 0) {
return OS_SOCKET_ERROR("Failed to get peer socket address");
}
@@ -227,48 +528,57 @@ Status IPAddress::init_peer_address(const SocketFd &socket_fd) {
return Status::OK();
}
-static CSlice get_ip_str(int family, const void *addr) {
- const int buf_size = INET6_ADDRSTRLEN; //, INET_ADDRSTRLEN;
- static TD_THREAD_LOCAL char *buf;
- init_thread_local<char[]>(buf, buf_size);
-
- const char *res = inet_ntop(family,
-#if TD_WINDOWS
- const_cast<PVOID>(addr),
-#else
- addr,
-#endif
- buf, buf_size);
- if (res == nullptr) {
- return CSlice();
- } else {
- return CSlice(res);
+void IPAddress::clear_ipv6_interface() {
+ if (!is_valid() || get_address_family() != AF_INET6) {
+ return;
}
+
+ auto *begin = ipv6_addr_.sin6_addr.s6_addr;
+ static_assert(sizeof(ipv6_addr_.sin6_addr.s6_addr) == 16, "expected 16 bytes buffer for ipv6");
+ static_assert(sizeof(*begin) == 1, "expected array of bytes");
+ std::memset(begin + 8, 0, 8 * sizeof(*begin));
}
-CSlice IPAddress::ipv4_to_str(int32 ipv4) {
- auto tmp_ipv4 = ntohl(ipv4);
- return ::td::get_ip_str(AF_INET, &tmp_ipv4);
+string IPAddress::ipv4_to_str(uint32 ipv4) {
+ ipv4 = ntohl(ipv4);
+ return ::td::get_ip_str(AF_INET, &ipv4).str();
}
-Slice IPAddress::get_ip_str() const {
+string IPAddress::ipv6_to_str(Slice ipv6) {
+ CHECK(ipv6.size() == 16);
+ return ::td::get_ip_str(AF_INET6, ipv6.ubegin()).str();
+}
+
+CSlice IPAddress::get_ip_str() const {
if (!is_valid()) {
- return Slice("0.0.0.0");
+ return CSlice("0.0.0.0");
}
- const void *addr;
switch (get_address_family()) {
case AF_INET6:
- addr = &ipv6_addr_.sin6_addr;
- break;
+ return ::td::get_ip_str(AF_INET6, &ipv6_addr_.sin6_addr);
case AF_INET:
- addr = &ipv4_addr_.sin_addr;
- break;
+ return ::td::get_ip_str(AF_INET, &ipv4_addr_.sin_addr);
default:
UNREACHABLE();
- return Slice();
+ return CSlice();
+ }
+}
+
+string IPAddress::get_ip_host() const {
+ if (!is_valid()) {
+ return "0.0.0.0";
+ }
+
+ switch (get_address_family()) {
+ case AF_INET6:
+ return PSTRING() << '[' << ::td::get_ip_str(AF_INET6, &ipv6_addr_.sin6_addr) << ']';
+ case AF_INET:
+ return ::td::get_ip_str(AF_INET, &ipv4_addr_.sin_addr).str();
+ default:
+ UNREACHABLE();
+ return string();
}
- return ::td::get_ip_str(get_address_family(), addr);
}
int IPAddress::get_port() const {
@@ -304,7 +614,7 @@ void IPAddress::set_port(int port) {
bool operator==(const IPAddress &a, const IPAddress &b) {
if (!a.is_valid() || !b.is_valid()) {
- return false;
+ return !a.is_valid() && !b.is_valid();
}
if (a.get_address_family() != b.get_address_family()) {
return false;
@@ -318,13 +628,13 @@ bool operator==(const IPAddress &a, const IPAddress &b) {
std::memcmp(&a.ipv6_addr_.sin6_addr, &b.ipv6_addr_.sin6_addr, sizeof(a.ipv6_addr_.sin6_addr)) == 0;
}
- LOG(FATAL) << "Unknown address family";
+ UNREACHABLE();
return false;
}
bool operator<(const IPAddress &a, const IPAddress &b) {
- if (a.is_valid() != b.is_valid()) {
- return a.is_valid() < b.is_valid();
+ if (!a.is_valid() || !b.is_valid()) {
+ return !a.is_valid() && b.is_valid();
}
if (a.get_address_family() != b.get_address_family()) {
return a.get_address_family() < b.get_address_family();
@@ -342,7 +652,7 @@ bool operator<(const IPAddress &a, const IPAddress &b) {
return std::memcmp(&a.ipv6_addr_.sin6_addr, &b.ipv6_addr_.sin6_addr, sizeof(a.ipv6_addr_.sin6_addr)) < 0;
}
- LOG(FATAL) << "Unknown address family";
+ UNREACHABLE();
return false;
}
@@ -350,12 +660,7 @@ StringBuilder &operator<<(StringBuilder &builder, const IPAddress &address) {
if (!address.is_valid()) {
return builder << "[invalid]";
}
- if (address.get_address_family() == AF_INET) {
- return builder << "[" << address.get_ip_str() << ":" << address.get_port() << "]";
- } else {
- CHECK(address.get_address_family() == AF_INET6);
- return builder << "[[" << address.get_ip_str() << "]:" << address.get_port() << "]";
- }
+ return builder << "[" << address.get_ip_host() << ":" << address.get_port() << "]";
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.h
index 116a4c5425..f71bc5da91 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -15,53 +15,79 @@
#if !TD_WINDOWS
#include <arpa/inet.h>
+#include <netinet/in.h>
#include <sys/socket.h>
#endif
namespace td {
+
+Result<string> idn_to_ascii(CSlice host);
+
class SocketFd;
+
class IPAddress {
public:
IPAddress();
bool is_valid() const;
-
- const sockaddr *get_sockaddr() const;
- size_t get_sockaddr_len() const;
- int get_address_family() const;
- Slice get_ip_str() const;
bool is_ipv4() const;
- uint32 get_ipv4() const;
- Slice get_ipv6() const;
+ bool is_ipv6() const;
+
+ bool is_reserved() const;
+
int get_port() const;
void set_port(int port);
+ uint32 get_ipv4() const;
+ string get_ipv6() const;
+
+ // returns result in a static thread-local buffer, which may be overwritten by any subsequent method call
+ CSlice get_ip_str() const;
+
+ // returns IP address as a host, i.e. IPv4 or [IPv6]
+ string get_ip_host() const;
+
+ static string ipv4_to_str(uint32 ipv4);
+ static string ipv6_to_str(Slice ipv6);
+
IPAddress get_any_addr() const;
+ static Result<IPAddress> get_ip_address(CSlice host); // host must be any IPv4 or IPv6
+ static Result<IPAddress> get_ipv4_address(CSlice host);
+ static Result<IPAddress> get_ipv6_address(CSlice host);
+
Status init_ipv6_port(CSlice ipv6, int port) TD_WARN_UNUSED_RESULT;
Status init_ipv6_as_ipv4_port(CSlice ipv4, int port) TD_WARN_UNUSED_RESULT;
Status init_ipv4_port(CSlice ipv4, int port) TD_WARN_UNUSED_RESULT;
- Status init_host_port(CSlice host, int port) TD_WARN_UNUSED_RESULT;
- Status init_host_port(CSlice host, CSlice port) TD_WARN_UNUSED_RESULT;
+ Status init_host_port(CSlice host, int port, bool prefer_ipv6 = false) TD_WARN_UNUSED_RESULT;
+ Status init_host_port(CSlice host, CSlice port, bool prefer_ipv6 = false) TD_WARN_UNUSED_RESULT;
Status init_host_port(CSlice host_port) TD_WARN_UNUSED_RESULT;
Status init_socket_address(const SocketFd &socket_fd) TD_WARN_UNUSED_RESULT;
Status init_peer_address(const SocketFd &socket_fd) TD_WARN_UNUSED_RESULT;
+ void clear_ipv6_interface();
+
friend bool operator==(const IPAddress &a, const IPAddress &b);
friend bool operator<(const IPAddress &a, const IPAddress &b);
- static CSlice ipv4_to_str(int32 ipv4);
+ // for internal usage only
+ const sockaddr *get_sockaddr() const;
+ size_t get_sockaddr_len() const;
+ int get_address_family() const;
+ Status init_sockaddr(sockaddr *addr);
+ Status init_sockaddr(sockaddr *addr, socklen_t len) TD_WARN_UNUSED_RESULT;
private:
union {
- sockaddr_storage addr_;
sockaddr sockaddr_;
sockaddr_in ipv4_addr_;
sockaddr_in6 ipv6_addr_;
};
+ static constexpr socklen_t storage_size() {
+ return sizeof(ipv6_addr_);
+ }
bool is_valid_;
- Status init_sockaddr(sockaddr *addr, socklen_t len) TD_WARN_UNUSED_RESULT;
void init_ipv4_any();
void init_ipv6_any();
};
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IoSlice.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IoSlice.h
new file mode 100644
index 0000000000..5044fcec37
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IoSlice.h
@@ -0,0 +1,42 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/Slice.h"
+
+#if TD_PORT_POSIX
+#include <sys/uio.h>
+#endif
+
+namespace td {
+
+#if TD_PORT_POSIX
+
+using IoSlice = struct iovec;
+
+inline IoSlice as_io_slice(Slice slice) {
+ IoSlice res;
+ res.iov_len = slice.size();
+ res.iov_base = const_cast<char *>(slice.data());
+ return res;
+}
+
+inline Slice as_slice(const IoSlice &io_slice) {
+ return Slice(static_cast<const char *>(io_slice.iov_base), io_slice.iov_len);
+}
+
+#else
+
+using IoSlice = Slice;
+
+inline IoSlice as_io_slice(Slice slice) {
+ return slice;
+}
+
+#endif
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.cpp
new file mode 100644
index 0000000000..0781b609b3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.cpp
@@ -0,0 +1,109 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/MemoryMapping.h"
+
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
+
+// TODO:
+// windows,
+// anonymous maps,
+// huge pages?
+
+#if TD_WINDOWS
+#else
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+
+namespace td {
+
+class MemoryMapping::Impl {
+ public:
+ Impl(MutableSlice data, int64 offset) : data_(data), offset_(offset) {
+ }
+ Slice as_slice() const {
+ return data_.substr(narrow_cast<size_t>(offset_));
+ }
+ MutableSlice as_mutable_slice() const {
+ return {};
+ }
+
+ private:
+ MutableSlice data_;
+ int64 offset_;
+};
+
+#if !TD_WINDOWS
+static Result<int64> get_page_size() {
+ static Result<int64> page_size = []() -> Result<int64> {
+ auto page_size = sysconf(_SC_PAGESIZE);
+ if (page_size < 0) {
+ return OS_ERROR("Can't load page size from sysconf");
+ }
+ return page_size;
+ }();
+ return page_size.clone();
+}
+#endif
+
+Result<MemoryMapping> MemoryMapping::create_anonymous(const MemoryMapping::Options &options) {
+ return Status::Error("Unsupported yet");
+}
+
+Result<MemoryMapping> MemoryMapping::create_from_file(const FileFd &file_fd, const MemoryMapping::Options &options) {
+#if TD_WINDOWS
+ return Status::Error("Unsupported yet");
+#else
+ if (file_fd.empty()) {
+ return Status::Error("Can't create memory mapping: file is empty");
+ }
+ TRY_RESULT(stat, file_fd.stat());
+ auto fd = file_fd.get_native_fd().fd();
+ auto begin = options.offset;
+ if (begin < 0) {
+ return Status::Error(PSLICE() << "Can't create memory mapping: negative offset " << options.offset);
+ }
+
+ int64 end;
+ if (options.size < 0) {
+ end = stat.size_;
+ } else {
+ end = begin + stat.size_;
+ }
+
+ TRY_RESULT(page_size, get_page_size());
+ auto fixed_begin = begin / page_size * page_size;
+
+ auto data_offset = begin - fixed_begin;
+ TRY_RESULT(data_size, narrow_cast_safe<size_t>(end - fixed_begin));
+
+ void *data = mmap(nullptr, data_size, PROT_READ, MAP_PRIVATE, fd, narrow_cast<off_t>(fixed_begin));
+ if (data == MAP_FAILED) {
+ return OS_ERROR("mmap call failed");
+ }
+
+ return MemoryMapping(make_unique<Impl>(MutableSlice(static_cast<char *>(data), data_size), data_offset));
+#endif
+}
+
+MemoryMapping::MemoryMapping(MemoryMapping &&other) noexcept = default;
+MemoryMapping &MemoryMapping::operator=(MemoryMapping &&other) noexcept = default;
+MemoryMapping::~MemoryMapping() = default;
+
+MemoryMapping::MemoryMapping(unique_ptr<Impl> impl) : impl_(std::move(impl)) {
+}
+
+Slice MemoryMapping::as_slice() const {
+ return impl_->as_slice();
+}
+
+MutableSlice MemoryMapping::as_mutable_slice() {
+ return impl_->as_mutable_slice();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.h
new file mode 100644
index 0000000000..040e45f989
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.h
@@ -0,0 +1,52 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+class MemoryMapping {
+ public:
+ struct Options {
+ int64 offset{0};
+ int64 size{-1};
+
+ Options() {
+ }
+ Options &with_offset(int64 new_offset) {
+ offset = new_offset;
+ return *this;
+ }
+ Options &with_size(int64 new_size) {
+ size = new_size;
+ return *this;
+ }
+ };
+
+ static Result<MemoryMapping> create_anonymous(const Options &options = {});
+ static Result<MemoryMapping> create_from_file(const FileFd &file, const Options &options = {});
+
+ Slice as_slice() const;
+ MutableSlice as_mutable_slice(); // returns empty slice if memory is read-only
+
+ MemoryMapping(const MemoryMapping &other) = delete;
+ const MemoryMapping &operator=(const MemoryMapping &other) = delete;
+ MemoryMapping(MemoryMapping &&other) noexcept;
+ MemoryMapping &operator=(MemoryMapping &&other) noexcept;
+ ~MemoryMapping();
+
+ private:
+ class Impl;
+ unique_ptr<Impl> impl_;
+ explicit MemoryMapping(unique_ptr<Impl> impl);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Mutex.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Mutex.h
new file mode 100644
index 0000000000..40d9f482db
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Mutex.h
@@ -0,0 +1,30 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include <mutex>
+
+namespace td {
+
+class Mutex {
+ public:
+ struct Guard {
+ std::unique_lock<std::mutex> guard;
+ void reset() {
+ guard.unlock();
+ }
+ };
+
+ Guard lock() {
+ return {std::unique_lock<std::mutex>(mutex_)};
+ }
+
+ private:
+ std::mutex mutex_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Poll.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Poll.h
index e23f4382d0..92629d6693 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Poll.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Poll.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollBase.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollBase.h
index eb71367ab9..674f8da677 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollBase.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollBase.h
@@ -1,12 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/PollFlags.h"
namespace td {
class PollBase {
@@ -19,9 +20,9 @@ class PollBase {
virtual ~PollBase() = default;
virtual void init() = 0;
virtual void clear() = 0;
- virtual void subscribe(const Fd &fd, Fd::Flags flags) = 0;
- virtual void unsubscribe(const Fd &fd) = 0;
- virtual void unsubscribe_before_close(const Fd &fd) = 0;
+ virtual void subscribe(PollableFd fd, PollFlags flags) = 0;
+ virtual void unsubscribe(PollableFdRef fd) = 0;
+ virtual void unsubscribe_before_close(PollableFdRef fd) = 0;
virtual void run(int timeout_ms) = 0;
};
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.cpp
new file mode 100644
index 0000000000..d729dcfa1e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.cpp
@@ -0,0 +1,71 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/PollFlags.h"
+
+namespace td {
+
+bool PollFlagsSet::write_flags(PollFlags flags) {
+ if (flags.empty()) {
+ return false;
+ }
+ auto old_flags = to_write_.fetch_or(flags.raw(), std::memory_order_relaxed);
+ return (flags.raw() & ~old_flags) != 0;
+}
+
+bool PollFlagsSet::write_flags_local(PollFlags flags) {
+ return flags_.add_flags(flags);
+}
+
+bool PollFlagsSet::flush() const {
+ if (to_write_.load(std::memory_order_relaxed) == 0) {
+ return false;
+ }
+ auto to_write = to_write_.exchange(0, std::memory_order_relaxed);
+ auto old_flags = flags_;
+ flags_.add_flags(PollFlags::from_raw(to_write));
+ if (flags_.can_close()) {
+ flags_.remove_flags(PollFlags::Write());
+ }
+ return flags_ != old_flags;
+}
+
+PollFlags PollFlagsSet::read_flags() const {
+ flush();
+ return flags_;
+}
+
+PollFlags PollFlagsSet::read_flags_local() const {
+ return flags_;
+}
+
+void PollFlagsSet::clear_flags(PollFlags flags) {
+ flags_.remove_flags(flags);
+}
+
+void PollFlagsSet::clear() {
+ to_write_ = 0;
+ flags_ = {};
+}
+
+StringBuilder &operator<<(StringBuilder &sb, PollFlags flags) {
+ sb << '[';
+ if (flags.can_read()) {
+ sb << 'R';
+ }
+ if (flags.can_write()) {
+ sb << 'W';
+ }
+ if (flags.can_close()) {
+ sb << 'C';
+ }
+ if (flags.has_pending_error()) {
+ sb << 'E';
+ }
+ return sb << ']';
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.h
new file mode 100644
index 0000000000..f759cb44cb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.h
@@ -0,0 +1,122 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/StringBuilder.h"
+
+#include <atomic>
+
+namespace td {
+
+class PollFlags {
+ public:
+ using Raw = int32;
+ bool can_read() const {
+ return has_flags(Read());
+ }
+ bool can_write() const {
+ return has_flags(Write());
+ }
+ bool can_close() const {
+ return has_flags(Close());
+ }
+ bool has_pending_error() const {
+ return has_flags(Error());
+ }
+ void remove_flags(PollFlags flags) {
+ remove_flags(flags.raw());
+ }
+ bool add_flags(PollFlags flags) {
+ auto old_flags = flags_;
+ add_flags(flags.raw());
+ return old_flags != flags_;
+ }
+ bool has_flags(PollFlags flags) const {
+ return has_flags(flags.raw());
+ }
+
+ bool empty() const {
+ return flags_ == 0;
+ }
+ Raw raw() const {
+ return flags_;
+ }
+ static PollFlags from_raw(Raw raw) {
+ return PollFlags(raw);
+ }
+ PollFlags() = default;
+
+ bool operator==(const PollFlags &other) const {
+ return flags_ == other.flags_;
+ }
+ bool operator!=(const PollFlags &other) const {
+ return !(*this == other);
+ }
+ PollFlags operator|(const PollFlags other) const {
+ return from_raw(raw() | other.raw());
+ }
+
+ static PollFlags Write() {
+ return PollFlags(Flag::Write);
+ }
+ static PollFlags Error() {
+ return PollFlags(Flag::Error);
+ }
+ static PollFlags Close() {
+ return PollFlags(Flag::Close);
+ }
+ static PollFlags Read() {
+ return PollFlags(Flag::Read);
+ }
+ static PollFlags ReadWrite() {
+ return Read() | Write();
+ }
+
+ private:
+ enum class Flag : Raw { Write = 0x001, Read = 0x002, Close = 0x004, Error = 0x008, None = 0 };
+ Raw flags_{static_cast<Raw>(Flag::None)};
+
+ explicit PollFlags(Raw raw) : flags_(raw) {
+ }
+ explicit PollFlags(Flag flag) : PollFlags(static_cast<Raw>(flag)) {
+ }
+
+ PollFlags &add_flags(Raw flags) {
+ flags_ |= flags;
+ return *this;
+ }
+ PollFlags &remove_flags(Raw flags) {
+ flags_ &= ~flags;
+ return *this;
+ }
+ bool has_flags(Raw flags) const {
+ return (flags_ & flags) == flags;
+ }
+};
+
+StringBuilder &operator<<(StringBuilder &sb, PollFlags flags);
+
+class PollFlagsSet {
+ public:
+ // write flags from any thread
+ // this is the only function that should be called from other threads
+ bool write_flags(PollFlags flags);
+
+ bool write_flags_local(PollFlags flags);
+ bool flush() const;
+
+ PollFlags read_flags() const;
+ PollFlags read_flags_local() const;
+ void clear_flags(PollFlags flags);
+ void clear();
+
+ private:
+ mutable std::atomic<PollFlags::Raw> to_write_{0};
+ mutable PollFlags flags_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/RwMutex.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/RwMutex.h
index eee5f3dcdb..01de499bd0 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/RwMutex.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/RwMutex.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,14 +9,16 @@
#include "td/utils/port/config.h"
#include "td/utils/common.h"
-#include "td/utils/logging.h"
#include "td/utils/Status.h"
#if TD_PORT_POSIX
#include <pthread.h>
#endif
+#include <memory>
+
namespace td {
+
class RwMutex {
public:
RwMutex() {
@@ -24,11 +26,11 @@ class RwMutex {
}
RwMutex(const RwMutex &) = delete;
RwMutex &operator=(const RwMutex &) = delete;
- RwMutex(RwMutex &&other) {
+ RwMutex(RwMutex &&other) noexcept {
init();
other.clear();
}
- RwMutex &operator=(RwMutex &&other) {
+ RwMutex &operator=(RwMutex &&other) noexcept {
other.clear();
return *this;
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.cpp
index ead43a3d4b..139252b405 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,10 +8,15 @@
#include "td/utils/port/config.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
+#include "td/utils/port/detail/skip_eintr.h"
#include "td/utils/port/IPAddress.h"
+#include "td/utils/port/PollFlags.h"
+#include "td/utils/SliceBuilder.h"
#if TD_PORT_POSIX
+#include <cerrno>
#include <arpa/inet.h>
#include <fcntl.h>
@@ -20,141 +25,348 @@
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
+#endif
+#if TD_PORT_WINDOWS
+#include "td/utils/port/detail/Iocp.h"
+#include "td/utils/port/Mutex.h"
+#include "td/utils/VectorQueue.h"
#endif
+#include <atomic>
+#include <cstring>
+
namespace td {
-Result<ServerSocketFd> ServerSocketFd::open(int32 port, CSlice addr) {
- ServerSocketFd socket;
- TRY_STATUS(socket.init(port, addr));
- return std::move(socket);
-}
+namespace detail {
+#if TD_PORT_WINDOWS
+class ServerSocketFdImpl final : private Iocp::Callback {
+ public:
+ ServerSocketFdImpl(NativeFd fd, int socket_family) : info_(std::move(fd)), socket_family_(socket_family) {
+ VLOG(fd) << get_native_fd() << " create ServerSocketFd";
+ Iocp::get()->subscribe(get_native_fd(), this);
+ notify_iocp_read();
+ }
+ void close() {
+ notify_iocp_close();
+ }
+ PollableFdInfo &get_poll_info() {
+ return info_;
+ }
+ const PollableFdInfo &get_poll_info() const {
+ return info_;
+ }
-const Fd &ServerSocketFd::get_fd() const {
- return fd_;
-}
+ const NativeFd &get_native_fd() const {
+ return info_.native_fd();
+ }
-Fd &ServerSocketFd::get_fd() {
- return fd_;
-}
+ Result<SocketFd> accept() {
+ auto lock = lock_.lock();
+ if (accepted_.empty()) {
+ get_poll_info().clear_flags(PollFlags::Read());
+ return Status::Error(-1, "Operation would block");
+ }
+ return accepted_.pop();
+ }
-int32 ServerSocketFd::get_flags() const {
- return fd_.get_flags();
-}
+ Status get_pending_error() {
+ Status res;
+ {
+ auto lock = lock_.lock();
+ if (!pending_errors_.empty()) {
+ res = pending_errors_.pop();
+ }
+ if (res.is_ok()) {
+ get_poll_info().clear_flags(PollFlags::Error());
+ }
+ }
+ return res;
+ }
-Status ServerSocketFd::get_pending_error() {
- return fd_.get_pending_error();
+ private:
+ PollableFdInfo info_;
+
+ Mutex lock_;
+ VectorQueue<SocketFd> accepted_;
+ VectorQueue<Status> pending_errors_;
+ static constexpr size_t MAX_ADDR_SIZE = sizeof(sockaddr_in6) + 16;
+ char addr_buf_[MAX_ADDR_SIZE * 2];
+
+ bool close_flag_{false};
+ std::atomic<int> refcnt_{1};
+ bool is_read_active_{false};
+ WSAOVERLAPPED read_overlapped_;
+
+ char close_overlapped_;
+
+ NativeFd accept_socket_;
+ int socket_family_;
+
+ void on_close() {
+ close_flag_ = true;
+ info_.set_native_fd({});
+ }
+ void on_read() {
+ VLOG(fd) << get_native_fd() << " on_read";
+ if (is_read_active_) {
+ is_read_active_ = false;
+ auto r_socket = [&]() -> Result<SocketFd> {
+ auto from = get_native_fd().socket();
+ auto status = setsockopt(accept_socket_.socket(), SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
+ reinterpret_cast<const char *>(&from), sizeof(from));
+ if (status != 0) {
+ return OS_SOCKET_ERROR("Failed to set SO_UPDATE_ACCEPT_CONTEXT options");
+ }
+ return SocketFd::from_native_fd(std::move(accept_socket_));
+ }();
+ VLOG(fd) << get_native_fd() << " finish accept";
+ if (r_socket.is_error()) {
+ return on_error(r_socket.move_as_error());
+ }
+ {
+ auto lock = lock_.lock();
+ accepted_.push(r_socket.move_as_ok());
+ }
+ get_poll_info().add_flags_from_poll(PollFlags::Read());
+ }
+ loop_read();
+ }
+ void loop_read() {
+ CHECK(!is_read_active_);
+ accept_socket_ = NativeFd(socket(socket_family_, SOCK_STREAM, 0));
+ std::memset(&read_overlapped_, 0, sizeof(read_overlapped_));
+ VLOG(fd) << get_native_fd() << " start accept";
+ BOOL status = AcceptEx(get_native_fd().socket(), accept_socket_.socket(), addr_buf_, 0, MAX_ADDR_SIZE,
+ MAX_ADDR_SIZE, nullptr, &read_overlapped_);
+ if (status == TRUE || check_status("Failed to accept connection")) {
+ inc_refcnt();
+ is_read_active_ = true;
+ }
+ }
+ bool check_status(Slice message) {
+ auto last_error = WSAGetLastError();
+ if (last_error == ERROR_IO_PENDING) {
+ return true;
+ }
+ on_error(OS_SOCKET_ERROR(message));
+ return false;
+ }
+ bool dec_refcnt() {
+ if (--refcnt_ == 0) {
+ delete this;
+ return true;
+ }
+ return false;
+ }
+ void inc_refcnt() {
+ CHECK(refcnt_ != 0);
+ refcnt_++;
+ }
+
+ void on_error(Status status) {
+ {
+ auto lock = lock_.lock();
+ pending_errors_.push(std::move(status));
+ }
+ get_poll_info().add_flags_from_poll(PollFlags::Error());
+ }
+
+ void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) final {
+ // called from other thread
+ if (dec_refcnt() || close_flag_) {
+ VLOG(fd) << "Ignore IOCP (server socket is closing)";
+ return;
+ }
+ if (r_size.is_error()) {
+ return on_error(get_socket_pending_error(get_native_fd(), overlapped, r_size.move_as_error()));
+ }
+
+ if (overlapped == nullptr) {
+ return on_read();
+ }
+ if (overlapped == &read_overlapped_) {
+ return on_read();
+ }
+
+ if (overlapped == reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_)) {
+ return on_close();
+ }
+ UNREACHABLE();
+ }
+ void notify_iocp_read() {
+ VLOG(fd) << get_native_fd() << " notify_read";
+ inc_refcnt();
+ Iocp::get()->post(0, this, nullptr);
+ }
+ void notify_iocp_close() {
+ VLOG(fd) << get_native_fd() << " notify_close";
+ Iocp::get()->post(0, this, reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_));
+ }
+};
+void ServerSocketFdImplDeleter::operator()(ServerSocketFdImpl *impl) {
+ impl->close();
}
+#elif TD_PORT_POSIX
+class ServerSocketFdImpl {
+ public:
+ explicit ServerSocketFdImpl(NativeFd fd) : info_(std::move(fd)) {
+ }
+ PollableFdInfo &get_poll_info() {
+ return info_;
+ }
+ const PollableFdInfo &get_poll_info() const {
+ return info_;
+ }
-Result<SocketFd> ServerSocketFd::accept() {
-#if TD_PORT_POSIX
- sockaddr_storage addr;
- socklen_t addr_len = sizeof(addr);
- int native_fd = fd_.get_native_fd();
- int r_fd = skip_eintr([&] { return ::accept(native_fd, reinterpret_cast<sockaddr *>(&addr), &addr_len); });
- auto accept_errno = errno;
- if (r_fd >= 0) {
- return SocketFd::from_native_fd(r_fd);
+ const NativeFd &get_native_fd() const {
+ return info_.native_fd();
}
+ Result<SocketFd> accept() {
+ sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+ int native_fd = get_native_fd().socket();
+ int r_fd = detail::skip_eintr([&] { return ::accept(native_fd, reinterpret_cast<sockaddr *>(&addr), &addr_len); });
+ auto accept_errno = errno;
+ if (r_fd >= 0) {
+ return SocketFd::from_native_fd(NativeFd(r_fd));
+ }
- if (accept_errno == EAGAIN
+ if (accept_errno == EAGAIN
#if EAGAIN != EWOULDBLOCK
- || accept_errno == EWOULDBLOCK
+ || accept_errno == EWOULDBLOCK
#endif
- ) {
- fd_.clear_flags(Fd::Read);
- return Status::Error(-1, "Operation would block");
- }
-
- auto error = Status::PosixError(accept_errno, PSLICE() << "Accept from [fd = " << native_fd << "] has failed");
- switch (accept_errno) {
- case EBADF:
- case EFAULT:
- case EINVAL:
- case ENOTSOCK:
- case EOPNOTSUPP:
- LOG(FATAL) << error;
- UNREACHABLE();
- break;
- default:
- LOG(ERROR) << error;
- // fallthrough
- case EMFILE:
- case ENFILE:
- case ECONNABORTED: //???
- fd_.clear_flags(Fd::Read);
- fd_.update_flags(Fd::Close);
- return std::move(error);
+ ) {
+ get_poll_info().clear_flags(PollFlags::Read());
+ return Status::Error(-1, "Operation would block");
+ }
+
+ auto error = Status::PosixError(accept_errno, PSLICE() << "Accept from " << get_native_fd() << " has failed");
+ switch (accept_errno) {
+ case EBADF:
+ case EFAULT:
+ case EINVAL:
+ case ENOTSOCK:
+ case EOPNOTSUPP:
+ LOG(FATAL) << error;
+ UNREACHABLE();
+ break;
+ default:
+ LOG(ERROR) << error;
+ // fallthrough
+ case EMFILE:
+ case ENFILE:
+ case ECONNABORTED: //???
+ get_poll_info().clear_flags(PollFlags::Read());
+ get_poll_info().add_flags(PollFlags::Close());
+ return std::move(error);
+ }
}
-#elif TD_PORT_WINDOWS
- TRY_RESULT(socket_fd, fd_.accept());
- return SocketFd(std::move(socket_fd));
+
+ Status get_pending_error() {
+ if (!get_poll_info().get_flags_local().has_pending_error()) {
+ return Status::OK();
+ }
+ TRY_STATUS(detail::get_socket_pending_error(get_native_fd()));
+ get_poll_info().clear_flags(PollFlags::Error());
+ return Status::OK();
+ }
+
+ private:
+ PollableFdInfo info_;
+};
+void ServerSocketFdImplDeleter::operator()(ServerSocketFdImpl *impl) {
+ delete impl;
+}
#endif
+} // namespace detail
+
+ServerSocketFd::ServerSocketFd() = default;
+ServerSocketFd::ServerSocketFd(ServerSocketFd &&) noexcept = default;
+ServerSocketFd &ServerSocketFd::operator=(ServerSocketFd &&) noexcept = default;
+ServerSocketFd::~ServerSocketFd() = default;
+ServerSocketFd::ServerSocketFd(unique_ptr<detail::ServerSocketFdImpl> impl) : impl_(impl.release()) {
+}
+PollableFdInfo &ServerSocketFd::get_poll_info() {
+ return impl_->get_poll_info();
+}
+
+const PollableFdInfo &ServerSocketFd::get_poll_info() const {
+ return impl_->get_poll_info();
+}
+
+Status ServerSocketFd::get_pending_error() {
+ return impl_->get_pending_error();
+}
+
+const NativeFd &ServerSocketFd::get_native_fd() const {
+ return impl_->get_native_fd();
+}
+
+Result<SocketFd> ServerSocketFd::accept() {
+ return impl_->accept();
}
void ServerSocketFd::close() {
- fd_.close();
+ impl_.reset();
}
bool ServerSocketFd::empty() const {
- return fd_.empty();
+ return !impl_;
}
-Status ServerSocketFd::init(int32 port, CSlice addr) {
- IPAddress address;
- TRY_STATUS(address.init_ipv4_port(addr, port));
- auto fd = socket(address.get_address_family(), SOCK_STREAM, 0);
-#if TD_PORT_POSIX
- if (fd == -1) {
-#elif TD_PORT_WINDOWS
- if (fd == INVALID_SOCKET) {
-#endif
+Result<ServerSocketFd> ServerSocketFd::open(int32 port, CSlice addr) {
+ if (port <= 0 || port >= (1 << 16)) {
+ return Status::Error(PSLICE() << "Invalid server port " << port << " specified");
+ }
+
+ TRY_RESULT(address, IPAddress::get_ip_address(addr));
+ address.set_port(port);
+
+ NativeFd fd{socket(address.get_address_family(), SOCK_STREAM, 0)};
+ if (!fd) {
return OS_SOCKET_ERROR("Failed to create a socket");
}
- auto fd_quard = ScopeExit() + [fd]() {
-#if TD_PORT_POSIX
- ::close(fd);
-#elif TD_PORT_WINDOWS
- ::closesocket(fd);
-#endif
- };
- TRY_STATUS(detail::set_native_socket_is_blocking(fd, false));
+ TRY_STATUS(fd.set_is_blocking_unsafe(false));
+ auto sock = fd.socket();
linger ling = {0, 0};
#if TD_PORT_POSIX
int flags = 1;
#ifdef SO_REUSEPORT
- setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<const char *>(&flags), sizeof(flags));
+ setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<const char *>(&flags), sizeof(flags));
#endif
#elif TD_PORT_WINDOWS
- BOOL flags = TRUE;
+ BOOL flags = FALSE;
+ if (address.is_ipv6()) {
+ setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&flags), sizeof(flags));
+ }
+ flags = TRUE;
#endif
- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags));
- setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags));
- setsockopt(fd, SOL_SOCKET, SO_LINGER, reinterpret_cast<const char *>(&ling), sizeof(ling));
- setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags));
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags));
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags));
+ setsockopt(sock, SOL_SOCKET, SO_LINGER, reinterpret_cast<const char *>(&ling), sizeof(ling));
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags));
- int e_bind = bind(fd, address.get_sockaddr(), static_cast<socklen_t>(address.get_sockaddr_len()));
+ int e_bind = bind(sock, address.get_sockaddr(), static_cast<socklen_t>(address.get_sockaddr_len()));
if (e_bind != 0) {
return OS_SOCKET_ERROR("Failed to bind a socket");
}
// TODO: magic constant
- int e_listen = listen(fd, 8192);
+ int e_listen = listen(sock, 8192);
if (e_listen != 0) {
return OS_SOCKET_ERROR("Failed to listen on a socket");
}
#if TD_PORT_POSIX
- fd_ = Fd(fd, Fd::Mode::Owner);
+ auto impl = make_unique<detail::ServerSocketFdImpl>(std::move(fd));
#elif TD_PORT_WINDOWS
- fd_ = Fd::create_server_socket_fd(fd, address.get_address_family());
+ auto impl = make_unique<detail::ServerSocketFdImpl>(std::move(fd), address.get_address_family());
#endif
- fd_quard.dismiss();
- return Status::OK();
+ return ServerSocketFd(std::move(impl));
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.h
index 67b43ad02d..dfff0741bd 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.h
@@ -1,32 +1,43 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include <memory>
+
namespace td {
+namespace detail {
+class ServerSocketFdImpl;
+class ServerSocketFdImplDeleter {
+ public:
+ void operator()(ServerSocketFdImpl *impl);
+};
+} // namespace detail
class ServerSocketFd {
public:
- ServerSocketFd() = default;
+ ServerSocketFd();
ServerSocketFd(const ServerSocketFd &) = delete;
ServerSocketFd &operator=(const ServerSocketFd &) = delete;
- ServerSocketFd(ServerSocketFd &&) = default;
- ServerSocketFd &operator=(ServerSocketFd &&) = default;
+ ServerSocketFd(ServerSocketFd &&) noexcept;
+ ServerSocketFd &operator=(ServerSocketFd &&) noexcept;
+ ~ServerSocketFd();
static Result<ServerSocketFd> open(int32 port, CSlice addr = CSlice("0.0.0.0")) TD_WARN_UNUSED_RESULT;
- const Fd &get_fd() const;
- Fd &get_fd();
- int32 get_flags() const;
+ PollableFdInfo &get_poll_info();
+ const PollableFdInfo &get_poll_info() const;
+
Status get_pending_error() TD_WARN_UNUSED_RESULT;
Result<SocketFd> accept() TD_WARN_UNUSED_RESULT;
@@ -34,10 +45,10 @@ class ServerSocketFd {
void close();
bool empty() const;
- private:
- Fd fd_;
+ const NativeFd &get_native_fd() const;
- Status init(int32 port, CSlice addr) TD_WARN_UNUSED_RESULT;
+ private:
+ std::unique_ptr<detail::ServerSocketFdImpl, detail::ServerSocketFdImplDeleter> impl_;
+ explicit ServerSocketFd(unique_ptr<detail::ServerSocketFdImpl> impl);
};
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.cpp
index 790bcd1bbd..90d516db36 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.cpp
@@ -1,18 +1,31 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/port/SocketFd.h"
+#include "td/utils/common.h"
+#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/detail/skip_eintr.h"
+#include "td/utils/port/PollFlags.h"
+#include "td/utils/SliceBuilder.h"
#if TD_PORT_WINDOWS
-#include "td/utils/misc.h"
+#include "td/utils/buffer.h"
+#include "td/utils/port/detail/Iocp.h"
+#include "td/utils/port/Mutex.h"
+#include "td/utils/VectorQueue.h"
+
+#include <limits>
#endif
#if TD_PORT_POSIX
+#include <cerrno>
+
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
@@ -22,118 +35,670 @@
#include <unistd.h>
#endif
+#include <atomic>
+#include <cstring>
+
namespace td {
+namespace detail {
+#if TD_PORT_WINDOWS
+class SocketFdImpl final : private Iocp::Callback {
+ public:
+ explicit SocketFdImpl(NativeFd native_fd) : info_(std::move(native_fd)) {
+ VLOG(fd) << get_native_fd() << " create from native_fd";
+ get_poll_info().add_flags(PollFlags::Write());
+ Iocp::get()->subscribe(get_native_fd(), this);
+ is_read_active_ = true;
+ notify_iocp_connected();
+ }
-Result<SocketFd> SocketFd::open(const IPAddress &address) {
- SocketFd socket;
- TRY_STATUS(socket.init(address));
- return std::move(socket);
-}
+ SocketFdImpl(NativeFd native_fd, const IPAddress &addr) : info_(std::move(native_fd)) {
+ VLOG(fd) << get_native_fd() << " create from native_fd and connect";
+ get_poll_info().add_flags(PollFlags::Write());
+ Iocp::get()->subscribe(get_native_fd(), this);
+ LPFN_CONNECTEX ConnectExPtr = nullptr;
+ GUID guid = WSAID_CONNECTEX;
+ DWORD numBytes;
+ auto error =
+ ::WSAIoctl(get_native_fd().socket(), SIO_GET_EXTENSION_FUNCTION_POINTER, static_cast<void *>(&guid),
+ sizeof(guid), static_cast<void *>(&ConnectExPtr), sizeof(ConnectExPtr), &numBytes, nullptr, nullptr);
+ if (error) {
+ on_error(OS_SOCKET_ERROR("WSAIoctl failed"));
+ return;
+ }
+ std::memset(&read_overlapped_, 0, sizeof(read_overlapped_));
+ inc_refcnt();
+ is_read_active_ = true;
+ auto status = ConnectExPtr(get_native_fd().socket(), addr.get_sockaddr(), narrow_cast<int>(addr.get_sockaddr_len()),
+ nullptr, 0, nullptr, &read_overlapped_);
-#if TD_PORT_POSIX
-Result<SocketFd> SocketFd::from_native_fd(int fd) {
- auto fd_guard = ScopeExit() + [fd]() { ::close(fd); };
+ if (status == TRUE || !check_status("Failed to connect")) {
+ is_read_active_ = false;
+ dec_refcnt();
+ }
+ }
- TRY_STATUS(detail::set_native_socket_is_blocking(fd, false));
+ void close() {
+ if (!is_write_waiting_ && is_connected_) {
+ VLOG(fd) << get_native_fd() << " will close after ongoing write";
+ auto lock = lock_.lock();
+ if (!is_write_waiting_) {
+ need_close_after_write_ = true;
+ return;
+ }
+ }
+ notify_iocp_close();
+ }
- // TODO remove copypaste
- int flags = 1;
- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags));
- setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags));
- setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags));
- // TODO: SO_REUSEADDR, SO_KEEPALIVE, TCP_NODELAY, SO_SNDBUF, SO_RCVBUF, TCP_QUICKACK, SO_LINGER
+ PollableFdInfo &get_poll_info() {
+ return info_;
+ }
+ const PollableFdInfo &get_poll_info() const {
+ return info_;
+ }
+
+ const NativeFd &get_native_fd() const {
+ return info_.native_fd();
+ }
+
+ Result<size_t> write(Slice data) {
+ // LOG(ERROR) << "Write: " << format::as_hex_dump<0>(data);
+ output_writer_.append(data);
+ return write_finish(data.size());
+ }
+
+ Result<size_t> writev(Span<IoSlice> slices) {
+ size_t total_size = 0;
+ for (auto io_slice : slices) {
+ auto size = as_slice(io_slice).size();
+ CHECK(size <= std::numeric_limits<size_t>::max() - total_size);
+ total_size += size;
+ }
+
+ auto left_size = total_size;
+ for (auto io_slice : slices) {
+ auto slice = as_slice(io_slice);
+ output_writer_.append(slice, left_size);
+ left_size -= slice.size();
+ }
+
+ return write_finish(total_size);
+ }
+
+ Result<size_t> write_finish(size_t total_size) {
+ if (is_write_waiting_) {
+ auto lock = lock_.lock();
+ is_write_waiting_ = false;
+ lock.reset();
+ notify_iocp_write();
+ }
+ return total_size;
+ }
+
+ Result<size_t> read(MutableSlice slice) {
+ if (get_poll_info().get_flags_local().has_pending_error()) {
+ TRY_STATUS(get_pending_error());
+ }
+ input_reader_.sync_with_writer();
+ auto res = input_reader_.advance(td::min(slice.size(), input_reader_.size()), slice);
+ if (res == 0) {
+ get_poll_info().clear_flags(PollFlags::Read());
+ } else {
+ // LOG(ERROR) << "Read: " << format::as_hex_dump<0>(Slice(slice.substr(0, res)));
+ }
+ return res;
+ }
+
+ Status get_pending_error() {
+ Status res;
+ {
+ auto lock = lock_.lock();
+ if (!pending_errors_.empty()) {
+ res = pending_errors_.pop();
+ }
+ if (res.is_ok()) {
+ get_poll_info().clear_flags(PollFlags::Error());
+ }
+ }
+ return res;
+ }
+
+ private:
+ PollableFdInfo info_;
+ Mutex lock_;
+
+ std::atomic<int> refcnt_{1};
+ bool close_flag_{false};
+ bool need_close_after_write_{false};
- fd_guard.dismiss();
+ std::atomic<bool> is_connected_{false};
+ bool is_read_active_{false};
+ ChainBufferWriter input_writer_;
+ ChainBufferReader input_reader_ = input_writer_.extract_reader();
+ WSAOVERLAPPED read_overlapped_;
+ VectorQueue<Status> pending_errors_;
- SocketFd socket;
- socket.fd_ = Fd(fd, Fd::Mode::Owner);
- return std::move(socket);
+ bool is_write_active_{false};
+ std::atomic<bool> is_write_waiting_{false};
+ ChainBufferWriter output_writer_;
+ ChainBufferReader output_reader_ = output_writer_.extract_reader();
+ WSAOVERLAPPED write_overlapped_;
+
+ char close_overlapped_;
+
+ bool check_status(Slice message) {
+ auto last_error = WSAGetLastError();
+ if (last_error == ERROR_IO_PENDING) {
+ return true;
+ }
+ on_error(OS_SOCKET_ERROR(message));
+ return false;
+ }
+
+ void loop_read() {
+ CHECK(is_connected_);
+ CHECK(!is_read_active_);
+ if (close_flag_ || need_close_after_write_) {
+ return;
+ }
+ std::memset(&read_overlapped_, 0, sizeof(read_overlapped_));
+ auto dest = input_writer_.prepare_append();
+ WSABUF buf;
+ buf.len = narrow_cast<ULONG>(dest.size());
+ buf.buf = dest.data();
+ DWORD flags = 0;
+ int status = WSARecv(get_native_fd().socket(), &buf, 1, nullptr, &flags, &read_overlapped_, nullptr);
+ if (status == 0 || check_status("Failed to read from connection")) {
+ inc_refcnt();
+ is_read_active_ = true;
+ }
+ }
+
+ void loop_write() {
+ CHECK(is_connected_);
+ CHECK(!is_write_active_);
+
+ output_reader_.sync_with_writer();
+ auto to_write = output_reader_.prepare_read();
+ if (to_write.empty()) {
+ auto lock = lock_.lock();
+ output_reader_.sync_with_writer();
+ to_write = output_reader_.prepare_read();
+ if (to_write.empty()) {
+ is_write_waiting_ = true;
+ if (need_close_after_write_) {
+ notify_iocp_close();
+ }
+ return;
+ }
+ }
+ if (to_write.empty()) {
+ return;
+ }
+ std::memset(&write_overlapped_, 0, sizeof(write_overlapped_));
+ constexpr size_t BUF_SIZE = 20;
+ WSABUF buf[BUF_SIZE];
+ auto it = output_reader_.clone();
+ size_t buf_i;
+ for (buf_i = 0; buf_i < BUF_SIZE; buf_i++) {
+ auto src = it.prepare_read();
+ if (src.empty()) {
+ break;
+ }
+ buf[buf_i].len = narrow_cast<ULONG>(src.size());
+ buf[buf_i].buf = const_cast<CHAR *>(src.data());
+ it.confirm_read(src.size());
+ }
+ int status =
+ WSASend(get_native_fd().socket(), buf, narrow_cast<DWORD>(buf_i), nullptr, 0, &write_overlapped_, nullptr);
+ if (status == 0 || check_status("Failed to write to connection")) {
+ inc_refcnt();
+ is_write_active_ = true;
+ }
+ }
+
+ void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) final {
+ // called from other thread
+ if (dec_refcnt() || close_flag_) {
+ VLOG(fd) << "Ignore IOCP (socket is closing)";
+ return;
+ }
+ if (r_size.is_error()) {
+ return on_error(get_socket_pending_error(get_native_fd(), overlapped, r_size.move_as_error()));
+ }
+
+ if (!is_connected_ && overlapped == &read_overlapped_) {
+ return on_connected();
+ }
+
+ auto size = r_size.move_as_ok();
+ if (overlapped == &write_overlapped_) {
+ return on_write(size);
+ }
+ if (overlapped == nullptr) {
+ CHECK(size == 0);
+ return on_write(size);
+ }
+
+ if (overlapped == &read_overlapped_) {
+ return on_read(size);
+ }
+ if (overlapped == reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_)) {
+ return on_close();
+ }
+ LOG(ERROR) << this << ' ' << overlapped << ' ' << &read_overlapped_ << ' ' << &write_overlapped_ << ' '
+ << reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_) << ' ' << size;
+ LOG(FATAL) << get_native_fd() << ' ' << info_.get_flags_local() << ' ' << refcnt_.load() << ' ' << close_flag_
+ << ' ' << need_close_after_write_ << ' ' << is_connected_ << ' ' << is_read_active_ << ' '
+ << is_write_active_ << ' ' << is_write_waiting_.load() << ' ' << input_reader_.size() << ' '
+ << output_reader_.size();
+ }
+
+ void on_error(Status status) {
+ VLOG(fd) << get_native_fd() << " on error " << status;
+ {
+ auto lock = lock_.lock();
+ pending_errors_.push(std::move(status));
+ }
+ get_poll_info().add_flags_from_poll(PollFlags::Error());
+ }
+
+ void on_connected() {
+ VLOG(fd) << get_native_fd() << " on connected";
+ CHECK(!is_connected_);
+ CHECK(is_read_active_);
+ is_connected_ = true;
+ is_read_active_ = false;
+ loop_read();
+ loop_write();
+ }
+
+ void on_read(size_t size) {
+ VLOG(fd) << get_native_fd() << " on read " << size;
+ CHECK(is_read_active_);
+ is_read_active_ = false;
+ if (size == 0) {
+ get_poll_info().add_flags_from_poll(PollFlags::Close());
+ return;
+ }
+ input_writer_.confirm_append(size);
+ get_poll_info().add_flags_from_poll(PollFlags::Read());
+ loop_read();
+ }
+
+ void on_write(size_t size) {
+ VLOG(fd) << get_native_fd() << " on write " << size;
+ if (size == 0) {
+ if (is_write_active_) {
+ return;
+ }
+ is_write_active_ = true;
+ }
+ CHECK(is_write_active_);
+ is_write_active_ = false;
+ output_reader_.advance(size);
+ loop_write();
+ }
+
+ void on_close() {
+ VLOG(fd) << get_native_fd() << " on close";
+ close_flag_ = true;
+ info_.set_native_fd({});
+ }
+ bool dec_refcnt() {
+ VLOG(fd) << get_native_fd() << " dec_refcnt from " << refcnt_;
+ if (--refcnt_ == 0) {
+ delete this;
+ return true;
+ }
+ return false;
+ }
+ void inc_refcnt() {
+ CHECK(refcnt_ != 0);
+ refcnt_++;
+ VLOG(fd) << get_native_fd() << " inc_refcnt to " << refcnt_;
+ }
+
+ void notify_iocp_write() {
+ inc_refcnt();
+ Iocp::get()->post(0, this, nullptr);
+ }
+ void notify_iocp_close() {
+ Iocp::get()->post(0, this, reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_));
+ }
+ void notify_iocp_connected() {
+ inc_refcnt();
+ Iocp::get()->post(0, this, &read_overlapped_);
+ }
+};
+
+void SocketFdImplDeleter::operator()(SocketFdImpl *impl) {
+ impl->close();
}
+
+class InitWSA {
+ public:
+ InitWSA() {
+ /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
+ WORD wVersionRequested = MAKEWORD(2, 2);
+ WSADATA wsaData;
+ if (WSAStartup(wVersionRequested, &wsaData) != 0) {
+ auto error = OS_SOCKET_ERROR("Failed to init WSA");
+ LOG(FATAL) << error;
+ }
+ }
+};
+
+static InitWSA init_wsa;
+
+#else
+class SocketFdImpl {
+ public:
+ PollableFdInfo info_;
+ explicit SocketFdImpl(NativeFd fd) : info_(std::move(fd)) {
+ }
+ PollableFdInfo &get_poll_info() {
+ return info_;
+ }
+ const PollableFdInfo &get_poll_info() const {
+ return info_;
+ }
+
+ const NativeFd &get_native_fd() const {
+ return info_.native_fd();
+ }
+
+ Result<size_t> writev(Span<IoSlice> slices) {
+ int native_fd = get_native_fd().socket();
+ TRY_RESULT(slices_size, narrow_cast_safe<int>(slices.size()));
+ auto write_res = detail::skip_eintr([&] {
+ // sendmsg can erroneously return 2^32 - 1 on Android 5.1 and Android 6.0, so it must not be used there
+#if defined(MSG_NOSIGNAL) && !TD_ANDROID
+ msghdr msg;
+ std::memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = const_cast<iovec *>(slices.begin());
+ msg.msg_iovlen = slices_size;
+ return sendmsg(native_fd, &msg, MSG_NOSIGNAL);
+#else
+ return ::writev(native_fd, slices.begin(), slices_size);
+#endif
+ });
+ if (write_res >= 0) {
+ auto result = narrow_cast<size_t>(write_res);
+ auto left = result;
+ for (const auto &slice : slices) {
+ if (left <= slice.iov_len) {
+ return result;
+ }
+ left -= slice.iov_len;
+ }
+ LOG(FATAL) << "Receive " << write_res << " as writev response, but tried to write only " << result - left
+ << " bytes";
+ }
+ return write_finish();
+ }
+
+ Result<size_t> write(Slice slice) {
+ int native_fd = get_native_fd().socket();
+ auto write_res = detail::skip_eintr([&] {
+ return
+#ifdef MSG_NOSIGNAL
+ send(native_fd, slice.begin(), slice.size(), MSG_NOSIGNAL);
+#else
+ ::write(native_fd, slice.begin(), slice.size());
#endif
+ });
+ if (write_res >= 0) {
+ auto result = narrow_cast<size_t>(write_res);
+ LOG_CHECK(result <= slice.size()) << "Receive " << write_res << " as write response, but tried to write only "
+ << slice.size() << " bytes";
+ return result;
+ }
+ return write_finish();
+ }
-Status SocketFd::init(const IPAddress &address) {
- auto fd = socket(address.get_address_family(), SOCK_STREAM, 0);
-#if TD_PORT_POSIX
- if (fd == -1) {
-#elif TD_PORT_WINDOWS
- if (fd == INVALID_SOCKET) {
+ Result<size_t> write_finish() {
+ auto write_errno = errno;
+ if (write_errno == EAGAIN
+#if EAGAIN != EWOULDBLOCK
+ || write_errno == EWOULDBLOCK
#endif
- return OS_SOCKET_ERROR("Failed to create a socket");
+ ) {
+ get_poll_info().clear_flags(PollFlags::Write());
+ return 0;
+ }
+
+ auto error = Status::PosixError(write_errno, PSLICE() << "Write to " << get_native_fd() << " has failed");
+ switch (write_errno) {
+ case EBADF:
+ case ENXIO:
+ case EFAULT:
+ case EINVAL:
+ LOG(FATAL) << error;
+ UNREACHABLE();
+ default:
+ LOG(WARNING) << error;
+ // fallthrough
+ case ECONNRESET:
+ case EDQUOT:
+ case EFBIG:
+ case EIO:
+ case ENETDOWN:
+ case ENETUNREACH:
+ case ENOSPC:
+ case EPIPE:
+ get_poll_info().clear_flags(PollFlags::Write());
+ get_poll_info().add_flags(PollFlags::Close());
+ return std::move(error);
+ }
+ }
+ Result<size_t> read(MutableSlice slice) {
+ if (get_poll_info().get_flags_local().has_pending_error()) {
+ TRY_STATUS(get_pending_error());
+ }
+ int native_fd = get_native_fd().socket();
+ CHECK(!slice.empty());
+ auto read_res = detail::skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); });
+ auto read_errno = errno;
+ if (read_res >= 0) {
+ if (read_res == 0) {
+ errno = 0;
+ get_poll_info().clear_flags(PollFlags::Read());
+ get_poll_info().add_flags(PollFlags::Close());
+ }
+ auto result = narrow_cast<size_t>(read_res);
+ CHECK(result <= slice.size());
+ return result;
+ }
+ if (read_errno == EAGAIN
+#if EAGAIN != EWOULDBLOCK
+ || read_errno == EWOULDBLOCK
+#endif
+ ) {
+ get_poll_info().clear_flags(PollFlags::Read());
+ return 0;
+ }
+ auto error = Status::PosixError(read_errno, PSLICE() << "Read from " << get_native_fd() << " has failed");
+ switch (read_errno) {
+ case EISDIR:
+ case EBADF:
+ case ENXIO:
+ case EFAULT:
+ case EINVAL:
+ LOG(FATAL) << error;
+ UNREACHABLE();
+ default:
+ LOG(WARNING) << error;
+ // fallthrough
+ case ENOTCONN:
+ case EIO:
+ case ENOBUFS:
+ case ENOMEM:
+ case ECONNRESET:
+ case ETIMEDOUT:
+ get_poll_info().clear_flags(PollFlags::Read());
+ get_poll_info().add_flags(PollFlags::Close());
+ return std::move(error);
+ }
}
- auto fd_quard = ScopeExit() + [fd]() {
+ Status get_pending_error() {
+ if (!get_poll_info().get_flags_local().has_pending_error()) {
+ return Status::OK();
+ }
+ TRY_STATUS(detail::get_socket_pending_error(get_native_fd()));
+ get_poll_info().clear_flags(PollFlags::Error());
+ return Status::OK();
+ }
+};
+
+void SocketFdImplDeleter::operator()(SocketFdImpl *impl) {
+ delete impl;
+}
+
+#endif
+
#if TD_PORT_POSIX
- ::close(fd);
+Status get_socket_pending_error(const NativeFd &fd) {
+ int error = 0;
+ socklen_t errlen = sizeof(error);
+ if (getsockopt(fd.socket(), SOL_SOCKET, SO_ERROR, static_cast<void *>(&error), &errlen) == 0) {
+ if (error == 0) {
+ return Status::OK();
+ }
+ return Status::PosixError(error, PSLICE() << "Error on " << fd);
+ }
+ auto status = OS_SOCKET_ERROR(PSLICE() << "Can't load error on socket " << fd);
+ LOG(INFO) << "Can't load pending socket error: " << status;
+ return status;
+}
#elif TD_PORT_WINDOWS
- ::closesocket(fd);
+Status get_socket_pending_error(const NativeFd &fd, WSAOVERLAPPED *overlapped, Status iocp_error) {
+ // We need to call WSAGetOverlappedResult() just so WSAGetLastError() will return the correct error. See
+ // https://stackoverflow.com/questions/28925003/calling-wsagetlasterror-from-an-iocp-thread-return-incorrect-result
+ DWORD num_bytes = 0;
+ DWORD flags = 0;
+ BOOL success = WSAGetOverlappedResult(fd.socket(), overlapped, &num_bytes, false, &flags);
+ if (success) {
+ LOG(ERROR) << "WSAGetOverlappedResult succeded after " << iocp_error;
+ return iocp_error;
+ }
+ return OS_SOCKET_ERROR(PSLICE() << "Error on " << fd);
+}
#endif
- };
- TRY_STATUS(detail::set_native_socket_is_blocking(fd, false));
+Status init_socket_options(NativeFd &native_fd) {
+ TRY_STATUS(native_fd.set_is_blocking_unsafe(false));
+ auto sock = native_fd.socket();
#if TD_PORT_POSIX
int flags = 1;
#elif TD_PORT_WINDOWS
BOOL flags = TRUE;
#endif
- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags));
- setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags));
- setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags));
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags));
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags));
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags));
+#if TD_PORT_POSIX
+#ifndef MSG_NOSIGNAL // Darwin
+
+#ifdef SO_NOSIGPIPE
+ setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<const char *>(&flags), sizeof(flags));
+#else
+#warning "Failed to suppress SIGPIPE signals. Use signal(SIGPIPE, SIG_IGN) to suppress them."
+#endif
+
+#endif
+#endif
// TODO: SO_REUSEADDR, SO_KEEPALIVE, TCP_NODELAY, SO_SNDBUF, SO_RCVBUF, TCP_QUICKACK, SO_LINGER
+ return Status::OK();
+}
+
+} // namespace detail
+
+SocketFd::SocketFd() = default;
+SocketFd::SocketFd(SocketFd &&) noexcept = default;
+SocketFd &SocketFd::operator=(SocketFd &&) noexcept = default;
+SocketFd::~SocketFd() = default;
+
+SocketFd::SocketFd(unique_ptr<detail::SocketFdImpl> impl) : impl_(impl.release()) {
+}
+
+Result<SocketFd> SocketFd::from_native_fd(NativeFd fd) {
+ TRY_STATUS(detail::init_socket_options(fd));
+ return SocketFd(make_unique<detail::SocketFdImpl>(std::move(fd)));
+}
+
+Result<SocketFd> SocketFd::open(const IPAddress &address) {
+#if TD_DARWIN_WATCH_OS
+ return SocketFd{};
+#endif
+
+ NativeFd native_fd{socket(address.get_address_family(), SOCK_STREAM, IPPROTO_TCP)};
+ if (!native_fd) {
+ return OS_SOCKET_ERROR("Failed to create a socket");
+ }
+ TRY_STATUS(detail::init_socket_options(native_fd));
+
#if TD_PORT_POSIX
- int e_connect = connect(fd, address.get_sockaddr(), static_cast<socklen_t>(address.get_sockaddr_len()));
+ int e_connect =
+ connect(native_fd.socket(), address.get_sockaddr(), narrow_cast<socklen_t>(address.get_sockaddr_len()));
if (e_connect == -1) {
auto connect_errno = errno;
if (connect_errno != EINPROGRESS) {
return Status::PosixError(connect_errno, PSLICE() << "Failed to connect to " << address);
}
}
- fd_ = Fd(fd, Fd::Mode::Owner);
+ return SocketFd(make_unique<detail::SocketFdImpl>(std::move(native_fd)));
#elif TD_PORT_WINDOWS
auto bind_addr = address.get_any_addr();
- auto e_bind = bind(fd, bind_addr.get_sockaddr(), narrow_cast<int>(bind_addr.get_sockaddr_len()));
+ auto e_bind = bind(native_fd.socket(), bind_addr.get_sockaddr(), narrow_cast<int>(bind_addr.get_sockaddr_len()));
if (e_bind != 0) {
return OS_SOCKET_ERROR("Failed to bind a socket");
}
-
- fd_ = Fd::create_socket_fd(fd);
- fd_.connect(address);
+ return SocketFd(make_unique<detail::SocketFdImpl>(std::move(native_fd), address));
#endif
-
- fd_quard.dismiss();
- return Status::OK();
}
-const Fd &SocketFd::get_fd() const {
- return fd_;
+void SocketFd::close() {
+ impl_.reset();
}
-Fd &SocketFd::get_fd() {
- return fd_;
+bool SocketFd::empty() const {
+ return !impl_;
}
-void SocketFd::close() {
- fd_.close();
+PollableFdInfo &SocketFd::get_poll_info() {
+ CHECK(!empty());
+ return impl_->get_poll_info();
}
-
-bool SocketFd::empty() const {
- return fd_.empty();
+const PollableFdInfo &SocketFd::get_poll_info() const {
+ CHECK(!empty());
+ return impl_->get_poll_info();
}
-int32 SocketFd::get_flags() const {
- return fd_.get_flags();
+const NativeFd &SocketFd::get_native_fd() const {
+ CHECK(!empty());
+ return impl_->get_native_fd();
}
Status SocketFd::get_pending_error() {
- return fd_.get_pending_error();
+ CHECK(!empty());
+ return impl_->get_pending_error();
}
Result<size_t> SocketFd::write(Slice slice) {
- return fd_.write(slice);
+ CHECK(!empty());
+ return impl_->write(slice);
+}
+
+Result<size_t> SocketFd::writev(Span<IoSlice> slices) {
+ CHECK(!empty());
+ return impl_->writev(slices);
}
Result<size_t> SocketFd::read(MutableSlice slice) {
- return fd_.read(slice);
+ CHECK(!empty());
+ return impl_->read(slice);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.h
index c88dd7d789..72aa80de24 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,50 +8,63 @@
#include "td/utils/port/config.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/IoSlice.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/Slice.h"
+#include "td/utils/Span.h"
#include "td/utils/Status.h"
+#include <memory>
+
namespace td {
+namespace detail {
+class SocketFdImpl;
+class SocketFdImplDeleter {
+ public:
+ void operator()(SocketFdImpl *impl);
+};
+} // namespace detail
+
class SocketFd {
public:
- SocketFd() = default;
+ SocketFd();
SocketFd(const SocketFd &) = delete;
SocketFd &operator=(const SocketFd &) = delete;
- SocketFd(SocketFd &&) = default;
- SocketFd &operator=(SocketFd &&) = default;
+ SocketFd(SocketFd &&) noexcept;
+ SocketFd &operator=(SocketFd &&) noexcept;
+ ~SocketFd();
static Result<SocketFd> open(const IPAddress &address) TD_WARN_UNUSED_RESULT;
- const Fd &get_fd() const;
- Fd &get_fd();
-
- int32 get_flags() const;
+ PollableFdInfo &get_poll_info();
+ const PollableFdInfo &get_poll_info() const;
Status get_pending_error() TD_WARN_UNUSED_RESULT;
Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT;
+ Result<size_t> writev(Span<IoSlice> slices) TD_WARN_UNUSED_RESULT;
Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT;
+ const NativeFd &get_native_fd() const;
+ static Result<SocketFd> from_native_fd(NativeFd fd);
+
void close();
bool empty() const;
private:
- Fd fd_;
-
- friend class ServerSocketFd;
-
- Status init(const IPAddress &address) TD_WARN_UNUSED_RESULT;
+ std::unique_ptr<detail::SocketFdImpl, detail::SocketFdImplDeleter> impl_;
+ explicit SocketFd(unique_ptr<detail::SocketFdImpl> impl);
+};
+namespace detail {
#if TD_PORT_POSIX
- static Result<SocketFd> from_native_fd(int fd);
+Status get_socket_pending_error(const NativeFd &fd);
+#elif TD_PORT_WINDOWS
+Status get_socket_pending_error(const NativeFd &fd, WSAOVERLAPPED *overlapped, Status iocp_error);
#endif
-#if TD_PORT_WINDOWS
- explicit SocketFd(Fd fd) : fd_(std::move(fd)) {
- }
-#endif
-};
+} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.cpp
index edc882761b..3000598719 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,7 +14,9 @@
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/Clocks.h"
+#include "td/utils/port/detail/skip_eintr.h"
#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
#include <utility>
@@ -37,8 +39,24 @@
#include <sys/syscall.h>
#endif
+#elif TD_PORT_WINDOWS
+
+#include "td/utils/port/thread.h"
+
+#ifndef PSAPI_VERSION
+#define PSAPI_VERSION 1
+#endif
+#ifdef __MINGW32__
+#include <psapi.h>
+#else
+#include <Psapi.h>
+#endif
+
+#endif
+
namespace td {
+#if TD_PORT_POSIX
namespace detail {
template <class...>
@@ -56,6 +74,19 @@ struct TimeNsec {
}
};
+// remove libc compatibility hacks if any: we have our own hacks
+#ifdef st_atimespec
+#undef st_atimespec
+#endif
+
+#ifdef st_atimensec
+#undef st_atimensec
+#endif
+
+#ifdef st_atime_nsec
+#undef st_atime_nsec
+#endif
+
template <class T>
struct TimeNsec<T, void_t<char, decltype(T::st_atimespec), decltype(T::st_mtimespec)>> {
static std::pair<decltype(decltype(T::st_atimespec)::tv_nsec), decltype(decltype(T::st_mtimespec)::tv_nsec)> get(
@@ -78,6 +109,13 @@ struct TimeNsec<T, void_t<int, decltype(T::st_atim), decltype(T::st_mtim)>> {
}
};
+template <class T>
+struct TimeNsec<T, void_t<long, decltype(T::st_atime_nsec), decltype(T::st_mtime_nsec)>> {
+ static std::pair<decltype(T::st_atime_nsec), decltype(T::st_mtime_nsec)> get(const T &s) {
+ return {s.st_atime_nsec, s.st_mtime_nsec};
+ }
+};
+
Stat from_native_stat(const struct ::stat &buf) {
auto time_nsec = TimeNsec<struct ::stat>::get(buf);
@@ -85,16 +123,17 @@ Stat from_native_stat(const struct ::stat &buf) {
res.atime_nsec_ = static_cast<uint64>(buf.st_atime) * 1000000000 + time_nsec.first;
res.mtime_nsec_ = static_cast<uint64>(buf.st_mtime) * 1000000000 + time_nsec.second / 1000 * 1000;
res.size_ = buf.st_size;
+ res.real_size_ = buf.st_blocks * 512;
res.is_dir_ = (buf.st_mode & S_IFMT) == S_IFDIR;
res.is_reg_ = (buf.st_mode & S_IFMT) == S_IFREG;
return res;
}
-Stat fstat(int native_fd) {
+Result<Stat> fstat(int native_fd) {
struct ::stat buf;
- int err = fstat(native_fd, &buf);
- auto fstat_errno = errno;
- LOG_IF(FATAL, err < 0) << Status::PosixError(fstat_errno, PSLICE() << "stat for fd " << native_fd << " failed");
+ if (detail::skip_eintr([&] { return ::fstat(native_fd, &buf); }) < 0) {
+ return OS_ERROR(PSLICE() << "Stat for fd " << native_fd << " failed");
+ }
return detail::from_native_stat(buf);
}
@@ -103,8 +142,10 @@ Status update_atime(int native_fd) {
timespec times[2];
// access time
times[0].tv_nsec = UTIME_NOW;
+ times[0].tv_sec = 0;
// modify time
times[1].tv_nsec = UTIME_OMIT;
+ times[1].tv_sec = 0;
if (futimens(native_fd, times) < 0) {
auto status = OS_ERROR(PSLICE() << "futimens " << tag("fd", native_fd));
LOG(WARNING) << status;
@@ -112,7 +153,7 @@ Status update_atime(int native_fd) {
}
return Status::OK();
#elif TD_DARWIN
- auto info = fstat(native_fd);
+ TRY_RESULT(info, fstat(native_fd));
timeval upd[2];
auto now = Clocks::system();
// access time
@@ -150,15 +191,22 @@ Status update_atime(CSlice path) {
SCOPE_EXIT {
file.close();
};
- return detail::update_atime(file.get_native_fd());
+ return detail::update_atime(file.get_native_fd().fd());
}
+#endif
Result<Stat> stat(CSlice path) {
+#if TD_PORT_POSIX
struct ::stat buf;
- if (stat(path.c_str(), &buf) < 0) {
- return OS_ERROR(PSLICE() << "stat for " << tag("file", path) << " failed");
+ int err = detail::skip_eintr([&] { return ::stat(path.c_str(), &buf); });
+ if (err < 0) {
+ return OS_ERROR(PSLICE() << "Stat for file \"" << path << "\" failed");
}
return detail::from_native_stat(buf);
+#elif TD_PORT_WINDOWS
+ TRY_RESULT(fd, FileFd::open(path, FileFd::Flags::Read | FileFd::PrivateFlags::WinStat));
+ return fd.stat();
+#endif
}
Result<MemStat> mem_stat() {
@@ -168,7 +216,7 @@ Result<MemStat> mem_stat() {
if (KERN_SUCCESS !=
task_info(mach_task_self(), TASK_BASIC_INFO, reinterpret_cast<task_info_t>(&t_info), &t_info_count)) {
- return Status::Error("task_info failed");
+ return Status::Error("Call to task_info failed");
}
MemStat res;
res.resident_size_ = t_info.resident_size;
@@ -226,7 +274,7 @@ Result<MemStat> mem_stat() {
LOG(ERROR) << "Failed to parse memory stats " << tag("name", name) << tag("value", value);
*x = static_cast<uint64>(-1);
} else {
- *x = r_mem.ok() * 1024; // memory is in kB
+ *x = r_mem.ok() * 1024; // memory is in KB
}
}
if (*s == 0) {
@@ -236,6 +284,21 @@ Result<MemStat> mem_stat() {
}
return res;
+#elif TD_WINDOWS
+ PROCESS_MEMORY_COUNTERS_EX counters;
+ if (!GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast<PROCESS_MEMORY_COUNTERS *>(&counters),
+ sizeof(counters))) {
+ return Status::Error("Call to GetProcessMemoryInfo failed");
+ }
+
+ // Working set = all non-virtual memory in RAM, including memory-mapped files
+ // PrivateUsage = Commit charge = all non-virtual memory in RAM and swap file, but not in memory-mapped files
+ MemStat res;
+ res.resident_size_ = counters.WorkingSetSize;
+ res.resident_size_peak_ = counters.PeakWorkingSetSize;
+ res.virtual_size_ = counters.PrivateUsage;
+ res.virtual_size_peak_ = counters.PeakPagefileUsage;
+ return res;
#else
return Status::Error("Not supported");
#endif
@@ -251,7 +314,9 @@ Status cpu_stat_self(CpuStat &stat) {
constexpr int TMEM_SIZE = 10000;
char mem[TMEM_SIZE];
TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1)));
- CHECK(size < TMEM_SIZE - 1);
+ if (size >= TMEM_SIZE - 1) {
+ return Status::Error("Failed for read /proc/self/stat");
+ }
mem[size] = 0;
char *s = mem;
@@ -260,10 +325,10 @@ Status cpu_stat_self(CpuStat &stat) {
while (pass_cnt < 15) {
if (pass_cnt == 13) {
- stat.process_user_ticks = to_integer<uint64>(Slice(s, t));
+ stat.process_user_ticks_ = to_integer<uint64>(Slice(s, t));
}
if (pass_cnt == 14) {
- stat.process_system_ticks = to_integer<uint64>(Slice(s, t));
+ stat.process_system_ticks_ = to_integer<uint64>(Slice(s, t));
}
while (*s && *s != ' ') {
s++;
@@ -272,11 +337,12 @@ Status cpu_stat_self(CpuStat &stat) {
s++;
pass_cnt++;
} else {
- return Status::Error("unexpected end of proc file");
+ return Status::Error("Unexpected end of proc file");
}
}
return Status::OK();
}
+
Status cpu_stat_total(CpuStat &stat) {
TRY_RESULT(fd, FileFd::open("/proc/stat", FileFd::Read));
SCOPE_EXIT {
@@ -286,14 +352,17 @@ Status cpu_stat_total(CpuStat &stat) {
constexpr int TMEM_SIZE = 10000;
char mem[TMEM_SIZE];
TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1)));
- CHECK(size < TMEM_SIZE - 1);
+ if (size >= TMEM_SIZE - 1) {
+ return Status::Error("Failed for read /proc/stat");
+ }
mem[size] = 0;
- uint64 sum = 0, cur = 0;
+ uint64 sum = 0;
+ uint64 cur = 0;
for (size_t i = 0; i < size; i++) {
- int c = mem[i];
+ char c = mem[i];
if (c >= '0' && c <= '9') {
- cur = cur * 10 + (uint64)c - '0';
+ cur = cur * 10 + static_cast<uint64>(c) - '0';
} else {
sum += cur;
cur = 0;
@@ -303,7 +372,7 @@ Status cpu_stat_total(CpuStat &stat) {
}
}
- stat.total_ticks = sum;
+ stat.total_ticks_ = sum;
return Status::OK();
}
#endif
@@ -314,24 +383,27 @@ Result<CpuStat> cpu_stat() {
TRY_STATUS(cpu_stat_self(stat));
TRY_STATUS(cpu_stat_total(stat));
return stat;
-#else
- return Status::Error("Not supported");
-#endif
-}
-} // namespace td
-#endif
-
-#if TD_PORT_WINDOWS
-namespace td {
+#elif TD_WINDOWS
+ CpuStat stat;
+ stat.total_ticks_ = static_cast<uint64>(GetTickCount64()) * 10000;
+ auto hardware_concurrency = thread::hardware_concurrency();
+ if (hardware_concurrency != 0) {
+ stat.total_ticks_ *= hardware_concurrency;
+ }
-Result<Stat> stat(CSlice path) {
- TRY_RESULT(fd, FileFd::open(path, FileFd::Flags::Read));
- return fd.stat();
-}
+ FILETIME ignored_time;
+ FILETIME kernel_time;
+ FILETIME user_time;
+ if (!GetProcessTimes(GetCurrentProcess(), &ignored_time, &ignored_time, &kernel_time, &user_time)) {
+ return Status::Error("Failed to call GetProcessTimes");
+ }
+ stat.process_system_ticks_ = kernel_time.dwLowDateTime + (static_cast<uint64>(kernel_time.dwHighDateTime) << 32);
+ stat.process_user_ticks_ = user_time.dwLowDateTime + (static_cast<uint64>(user_time.dwHighDateTime) << 32);
-Result<CpuStat> cpu_stat() {
+ return stat;
+#else
return Status::Error("Not supported");
+#endif
}
} // namespace td
-#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.h
index d0a98db141..ed8fc3de82 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -18,6 +18,7 @@ struct Stat {
bool is_dir_;
bool is_reg_;
int64 size_;
+ int64 real_size_;
uint64 atime_nsec_;
uint64 mtime_nsec_;
};
@@ -25,19 +26,12 @@ struct Stat {
Result<Stat> stat(CSlice path) TD_WARN_UNUSED_RESULT;
struct CpuStat {
- uint64 total_ticks{0};
- uint64 process_user_ticks{0};
- uint64 process_system_ticks{0};
+ uint64 total_ticks_{0};
+ uint64 process_user_ticks_{0};
+ uint64 process_system_ticks_{0};
};
-Result<CpuStat> cpu_stat() TD_WARN_UNUSED_RESULT;
-
-#if TD_PORT_POSIX
-
-namespace detail {
-Stat fstat(int native_fd); // TODO return Result<Stat>
-} // namespace detail
-Status update_atime(CSlice path) TD_WARN_UNUSED_RESULT;
+Result<CpuStat> cpu_stat() TD_WARN_UNUSED_RESULT;
struct MemStat {
uint64 resident_size_ = 0;
@@ -48,6 +42,14 @@ struct MemStat {
Result<MemStat> mem_stat() TD_WARN_UNUSED_RESULT;
+#if TD_PORT_POSIX
+
+namespace detail {
+Result<Stat> fstat(int native_fd);
+} // namespace detail
+
+Status update_atime(CSlice path) TD_WARN_UNUSED_RESULT;
+
#endif
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.cpp
new file mode 100644
index 0000000000..b2064a9fe6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.cpp
@@ -0,0 +1,251 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/StdStreams.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/detail/Iocp.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/PollFlags.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+
+#include <atomic>
+
+namespace td {
+
+#if TD_PORT_POSIX
+template <int id>
+static FileFd &get_file_fd() {
+ static FileFd result = FileFd::from_native_fd(NativeFd(id, true));
+ static auto guard = ScopeExit() + [&] {
+ result.move_as_native_fd().release();
+ };
+ return result;
+}
+
+FileFd &Stdin() {
+ return get_file_fd<0>();
+}
+FileFd &Stdout() {
+ return get_file_fd<1>();
+}
+FileFd &Stderr() {
+ return get_file_fd<2>();
+}
+#elif TD_PORT_WINDOWS
+template <DWORD id>
+static FileFd &get_file_fd() {
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
+ static auto handle = GetStdHandle(id);
+ LOG_IF(FATAL, handle == INVALID_HANDLE_VALUE) << "Failed to GetStdHandle " << id;
+ static FileFd result = handle == nullptr ? FileFd() : FileFd::from_native_fd(NativeFd(handle, true));
+ static auto guard = ScopeExit() + [&] {
+ if (handle != nullptr) {
+ result.move_as_native_fd().release();
+ }
+ };
+#else
+ static FileFd result;
+#endif
+ return result;
+}
+
+FileFd &Stdin() {
+ return get_file_fd<STD_INPUT_HANDLE>();
+}
+FileFd &Stdout() {
+ return get_file_fd<STD_OUTPUT_HANDLE>();
+}
+FileFd &Stderr() {
+ return get_file_fd<STD_ERROR_HANDLE>();
+}
+#endif
+
+#if TD_PORT_WINDOWS
+namespace detail {
+class BufferedStdinImpl final : private Iocp::Callback {
+ public:
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
+ BufferedStdinImpl() : info_(NativeFd(GetStdHandle(STD_INPUT_HANDLE), true)) {
+ iocp_ref_ = Iocp::get()->get_ref();
+ read_thread_ = thread([this] { this->read_loop(); });
+ }
+#else
+ BufferedStdinImpl() {
+ close();
+ }
+#endif
+ BufferedStdinImpl(const BufferedStdinImpl &) = delete;
+ BufferedStdinImpl &operator=(const BufferedStdinImpl &) = delete;
+ BufferedStdinImpl(BufferedStdinImpl &&) = delete;
+ BufferedStdinImpl &operator=(BufferedStdinImpl &&) = delete;
+ ~BufferedStdinImpl() {
+ info_.move_as_native_fd().release();
+ }
+ void close() {
+ close_flag_ = true;
+ }
+
+ ChainBufferReader &input_buffer() {
+ return reader_;
+ }
+
+ PollableFdInfo &get_poll_info() {
+ return info_;
+ }
+ const PollableFdInfo &get_poll_info() const {
+ return info_;
+ }
+
+ Result<size_t> flush_read(size_t max_read = std::numeric_limits<size_t>::max()) TD_WARN_UNUSED_RESULT {
+ info_.sync_with_poll();
+ info_.clear_flags(PollFlags::Read());
+ reader_.sync_with_writer();
+ return reader_.size();
+ }
+
+ private:
+ PollableFdInfo info_;
+ ChainBufferWriter writer_;
+ ChainBufferReader reader_ = writer_.extract_reader();
+ thread read_thread_;
+ std::atomic<bool> close_flag_{false};
+ IocpRef iocp_ref_;
+ std::atomic<int> refcnt_{1};
+
+ void read_loop() {
+ while (!close_flag_) {
+ auto slice = writer_.prepare_append();
+ auto r_size = read(slice);
+ if (r_size.is_error()) {
+ LOG(ERROR) << "Stop read stdin loop: " << r_size.error();
+ break;
+ }
+ writer_.confirm_append(r_size.ok());
+ inc_refcnt();
+ if (!iocp_ref_.post(0, this, nullptr)) {
+ dec_refcnt();
+ }
+ }
+ if (!iocp_ref_.post(0, this, nullptr)) {
+ read_thread_.detach();
+ dec_refcnt();
+ }
+ }
+ void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) final {
+ info_.add_flags_from_poll(PollFlags::Read());
+ dec_refcnt();
+ }
+
+ bool dec_refcnt() {
+ if (--refcnt_ == 0) {
+ delete this;
+ return true;
+ }
+ return false;
+ }
+ void inc_refcnt() {
+ CHECK(refcnt_ != 0);
+ refcnt_++;
+ }
+
+ Result<size_t> read(MutableSlice slice) {
+ auto native_fd = info_.native_fd().fd();
+ DWORD bytes_read = 0;
+ auto res = ReadFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, nullptr);
+ if (res) {
+ return static_cast<size_t>(bytes_read);
+ }
+ return OS_ERROR(PSLICE() << "Read from " << info_.native_fd() << " has failed");
+ }
+};
+void BufferedStdinImplDeleter::operator()(BufferedStdinImpl *impl) {
+ // LOG(ERROR) << "Close";
+ impl->close();
+}
+} // namespace detail
+#elif TD_PORT_POSIX
+namespace detail {
+class BufferedStdinImpl {
+ public:
+ BufferedStdinImpl() {
+ file_fd_ = FileFd::from_native_fd(NativeFd(Stdin().get_native_fd().fd()));
+ file_fd_.get_native_fd().set_is_blocking(false);
+ }
+ BufferedStdinImpl(const BufferedStdinImpl &) = delete;
+ BufferedStdinImpl &operator=(const BufferedStdinImpl &) = delete;
+ BufferedStdinImpl(BufferedStdinImpl &&) = delete;
+ BufferedStdinImpl &operator=(BufferedStdinImpl &&) = delete;
+ ~BufferedStdinImpl() {
+ file_fd_.get_native_fd().set_is_blocking(true);
+ file_fd_.move_as_native_fd().release();
+ }
+
+ ChainBufferReader &input_buffer() {
+ return reader_;
+ }
+
+ PollableFdInfo &get_poll_info() {
+ return file_fd_.get_poll_info();
+ }
+ const PollableFdInfo &get_poll_info() const {
+ return file_fd_.get_poll_info();
+ }
+
+ Result<size_t> flush_read(size_t max_read = std::numeric_limits<size_t>::max()) TD_WARN_UNUSED_RESULT {
+ size_t result = 0;
+ ::td::sync_with_poll(*this);
+ while (::td::can_read_local(*this) && max_read) {
+ MutableSlice slice = writer_.prepare_append();
+ slice.truncate(max_read);
+ TRY_RESULT(x, file_fd_.read(slice));
+ slice.truncate(x);
+ writer_.confirm_append(x);
+ result += x;
+ max_read -= x;
+ }
+ if (result) {
+ reader_.sync_with_writer();
+ }
+ return result;
+ }
+
+ private:
+ FileFd file_fd_;
+ ChainBufferWriter writer_;
+ ChainBufferReader reader_ = writer_.extract_reader();
+};
+void BufferedStdinImplDeleter::operator()(BufferedStdinImpl *impl) {
+ delete impl;
+}
+} // namespace detail
+#endif
+
+BufferedStdin::BufferedStdin() : impl_(make_unique<detail::BufferedStdinImpl>().release()) {
+}
+BufferedStdin::BufferedStdin(BufferedStdin &&) noexcept = default;
+BufferedStdin &BufferedStdin::operator=(BufferedStdin &&) noexcept = default;
+BufferedStdin::~BufferedStdin() = default;
+
+ChainBufferReader &BufferedStdin::input_buffer() {
+ return impl_->input_buffer();
+}
+PollableFdInfo &BufferedStdin::get_poll_info() {
+ return impl_->get_poll_info();
+}
+const PollableFdInfo &BufferedStdin::get_poll_info() const {
+ return impl_->get_poll_info();
+}
+Result<size_t> BufferedStdin::flush_read(size_t max_read) {
+ return impl_->flush_read(max_read);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.h
new file mode 100644
index 0000000000..0922594c9a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.h
@@ -0,0 +1,49 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/Status.h"
+
+#include <limits>
+#include <memory>
+
+namespace td {
+
+FileFd &Stdin();
+FileFd &Stdout();
+FileFd &Stderr();
+
+namespace detail {
+class BufferedStdinImpl;
+class BufferedStdinImplDeleter {
+ public:
+ void operator()(BufferedStdinImpl *impl);
+};
+} // namespace detail
+
+class BufferedStdin {
+ public:
+ BufferedStdin();
+ BufferedStdin(const BufferedStdin &) = delete;
+ BufferedStdin &operator=(const BufferedStdin &) = delete;
+ BufferedStdin(BufferedStdin &&) noexcept;
+ BufferedStdin &operator=(BufferedStdin &&) noexcept;
+ ~BufferedStdin();
+ ChainBufferReader &input_buffer();
+ PollableFdInfo &get_poll_info();
+ const PollableFdInfo &get_poll_info() const;
+ Result<size_t> flush_read(size_t max_read = std::numeric_limits<size_t>::max()) TD_WARN_UNUSED_RESULT;
+
+ private:
+ std::unique_ptr<detail::BufferedStdinImpl, detail::BufferedStdinImplDeleter> impl_;
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.cpp
new file mode 100644
index 0000000000..eee487d44b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.cpp
@@ -0,0 +1,873 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/UdpSocketFd.h"
+
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/detail/skip_eintr.h"
+#include "td/utils/port/PollFlags.h"
+#include "td/utils/port/SocketFd.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/VectorQueue.h"
+
+#if TD_PORT_WINDOWS
+#include "td/utils/port/detail/Iocp.h"
+#include "td/utils/port/Mutex.h"
+#endif
+
+#if TD_PORT_POSIX
+#include <cerrno>
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#if TD_LINUX
+#include <linux/errqueue.h>
+#endif
+#endif
+
+#include <array>
+#include <atomic>
+#include <cstring>
+
+namespace td {
+namespace detail {
+#if TD_PORT_WINDOWS
+class UdpSocketReceiveHelper {
+ public:
+ void to_native(const UdpMessage &message, WSAMSG &message_header) {
+ socklen_t addr_len{narrow_cast<socklen_t>(sizeof(addr_))};
+ message_header.name = reinterpret_cast<sockaddr *>(&addr_);
+ message_header.namelen = addr_len;
+ buf_.buf = const_cast<char *>(message.data.as_slice().begin());
+ buf_.len = narrow_cast<DWORD>(message.data.size());
+ message_header.lpBuffers = &buf_;
+ message_header.dwBufferCount = 1;
+ message_header.Control.buf = nullptr; // control_buf_.data();
+ message_header.Control.len = 0; // narrow_cast<decltype(message_header.Control.len)>(control_buf_.size());
+ message_header.dwFlags = 0;
+ }
+
+ static void from_native(WSAMSG &message_header, size_t message_size, UdpMessage &message) {
+ message.address.init_sockaddr(reinterpret_cast<sockaddr *>(message_header.name), message_header.namelen).ignore();
+ message.error = Status::OK();
+
+ if ((message_header.dwFlags & (MSG_TRUNC | MSG_CTRUNC)) != 0) {
+ message.error = Status::Error(501, "Message too long");
+ message.data = BufferSlice();
+ return;
+ }
+
+ CHECK(message_size <= message.data.size());
+ message.data.truncate(message_size);
+ CHECK(message_size == message.data.size());
+ }
+
+ private:
+ std::array<char, 1024> control_buf_;
+ sockaddr_storage addr_;
+ WSABUF buf_;
+};
+
+class UdpSocketSendHelper {
+ public:
+ void to_native(const UdpMessage &message, WSAMSG &message_header) {
+ message_header.name = const_cast<sockaddr *>(message.address.get_sockaddr());
+ message_header.namelen = narrow_cast<socklen_t>(message.address.get_sockaddr_len());
+ buf_.buf = const_cast<char *>(message.data.as_slice().begin());
+ buf_.len = narrow_cast<DWORD>(message.data.size());
+ message_header.lpBuffers = &buf_;
+ message_header.dwBufferCount = 1;
+
+ message_header.Control.buf = nullptr;
+ message_header.Control.len = 0;
+ message_header.dwFlags = 0;
+ }
+
+ private:
+ WSABUF buf_;
+};
+
+class UdpSocketFdImpl final : private Iocp::Callback {
+ public:
+ explicit UdpSocketFdImpl(NativeFd fd) : info_(std::move(fd)) {
+ get_poll_info().add_flags(PollFlags::Write());
+ Iocp::get()->subscribe(get_native_fd(), this);
+ is_receive_active_ = true;
+ notify_iocp_connected();
+ }
+ PollableFdInfo &get_poll_info() {
+ return info_;
+ }
+ const PollableFdInfo &get_poll_info() const {
+ return info_;
+ }
+
+ const NativeFd &get_native_fd() const {
+ return info_.native_fd();
+ }
+
+ void close() {
+ notify_iocp_close();
+ }
+
+ Result<optional<UdpMessage>> receive() {
+ auto lock = lock_.lock();
+ if (!pending_errors_.empty()) {
+ auto status = pending_errors_.pop();
+ if (!UdpSocketFd::is_critical_read_error(status)) {
+ return UdpMessage{{}, {}, std::move(status)};
+ }
+ return std::move(status);
+ }
+ if (!receive_queue_.empty()) {
+ return receive_queue_.pop();
+ }
+
+ return optional<UdpMessage>{};
+ }
+
+ void send(UdpMessage message) {
+ auto lock = lock_.lock();
+ send_queue_.push(std::move(message));
+ }
+
+ Status flush_send() {
+ if (is_send_waiting_) {
+ auto lock = lock_.lock();
+ is_send_waiting_ = false;
+ notify_iocp_send();
+ }
+ return Status::OK();
+ }
+
+ private:
+ PollableFdInfo info_;
+ Mutex lock_;
+
+ std::atomic<int> refcnt_{1};
+ bool is_connected_{false};
+ bool close_flag_{false};
+
+ bool is_send_active_{false};
+ bool is_send_waiting_{false};
+ VectorQueue<UdpMessage> send_queue_;
+ WSAOVERLAPPED send_overlapped_;
+
+ bool is_receive_active_{false};
+ VectorQueue<UdpMessage> receive_queue_;
+ VectorQueue<Status> pending_errors_;
+ UdpMessage to_receive_;
+ WSAMSG receive_message_;
+ UdpSocketReceiveHelper receive_helper_;
+ static constexpr size_t MAX_PACKET_SIZE = 2048;
+ static constexpr size_t RESERVED_SIZE = MAX_PACKET_SIZE * 8;
+ BufferSlice receive_buffer_;
+
+ UdpMessage to_send_;
+ WSAOVERLAPPED receive_overlapped_;
+
+ char close_overlapped_;
+
+ bool check_status(Slice message) {
+ auto last_error = WSAGetLastError();
+ if (last_error == ERROR_IO_PENDING) {
+ return true;
+ }
+ on_error(OS_SOCKET_ERROR(message));
+ return false;
+ }
+
+ void loop_receive() {
+ CHECK(!is_receive_active_);
+ if (close_flag_) {
+ return;
+ }
+ std::memset(&receive_overlapped_, 0, sizeof(receive_overlapped_));
+ if (receive_buffer_.size() < MAX_PACKET_SIZE) {
+ receive_buffer_ = BufferSlice(RESERVED_SIZE);
+ }
+ to_receive_.data = receive_buffer_.clone();
+ receive_helper_.to_native(to_receive_, receive_message_);
+
+ LPFN_WSARECVMSG WSARecvMsgPtr = nullptr;
+ GUID guid = WSAID_WSARECVMSG;
+ DWORD numBytes;
+ auto error = ::WSAIoctl(get_native_fd().socket(), SIO_GET_EXTENSION_FUNCTION_POINTER, static_cast<void *>(&guid),
+ sizeof(guid), static_cast<void *>(&WSARecvMsgPtr), sizeof(WSARecvMsgPtr), &numBytes,
+ nullptr, nullptr);
+ if (error) {
+ on_error(OS_SOCKET_ERROR("WSAIoctl failed"));
+ return;
+ }
+
+ auto status = WSARecvMsgPtr(get_native_fd().socket(), &receive_message_, nullptr, &receive_overlapped_, nullptr);
+ if (status == 0 || check_status("WSARecvMsg failed")) {
+ inc_refcnt();
+ is_receive_active_ = true;
+ }
+ }
+
+ void loop_send() {
+ CHECK(!is_send_active_);
+
+ {
+ auto lock = lock_.lock();
+ if (send_queue_.empty()) {
+ is_send_waiting_ = true;
+ return;
+ }
+ to_send_ = send_queue_.pop();
+ }
+ std::memset(&send_overlapped_, 0, sizeof(send_overlapped_));
+ WSAMSG message;
+ UdpSocketSendHelper send_helper;
+ send_helper.to_native(to_send_, message);
+ auto status = WSASendMsg(get_native_fd().socket(), &message, 0, nullptr, &send_overlapped_, nullptr);
+ if (status == 0 || check_status("WSASendMsg failed")) {
+ inc_refcnt();
+ is_send_active_ = true;
+ }
+ }
+
+ void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) final {
+ // called from other thread
+ if (dec_refcnt() || close_flag_) {
+ VLOG(fd) << "Ignore IOCP (UDP socket is closing)";
+ return;
+ }
+ if (r_size.is_error()) {
+ return on_error(get_socket_pending_error(get_native_fd(), overlapped, r_size.move_as_error()));
+ }
+
+ if (!is_connected_ && overlapped == &receive_overlapped_) {
+ return on_connected();
+ }
+
+ auto size = r_size.move_as_ok();
+ if (overlapped == &send_overlapped_) {
+ return on_send(size);
+ }
+ if (overlapped == nullptr) {
+ CHECK(size == 0);
+ return on_send(size);
+ }
+
+ if (overlapped == &receive_overlapped_) {
+ return on_receive(size);
+ }
+ if (overlapped == reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_)) {
+ return on_close();
+ }
+ UNREACHABLE();
+ }
+
+ void on_error(Status status) {
+ VLOG(fd) << get_native_fd() << " on error " << status;
+ {
+ auto lock = lock_.lock();
+ pending_errors_.push(std::move(status));
+ }
+ get_poll_info().add_flags_from_poll(PollFlags::Error());
+ }
+
+ void on_connected() {
+ VLOG(fd) << get_native_fd() << " on connected";
+ CHECK(!is_connected_);
+ CHECK(is_receive_active_);
+ is_connected_ = true;
+ is_receive_active_ = false;
+ loop_receive();
+ loop_send();
+ }
+
+ void on_receive(size_t size) {
+ VLOG(fd) << get_native_fd() << " on receive " << size;
+ CHECK(is_receive_active_);
+ is_receive_active_ = false;
+ UdpSocketReceiveHelper::from_native(receive_message_, size, to_receive_);
+ receive_buffer_.confirm_read((to_receive_.data.size() + 7) & ~7);
+ {
+ auto lock = lock_.lock();
+ // LOG(ERROR) << format::escaped(to_receive_.data.as_slice());
+ receive_queue_.push(std::move(to_receive_));
+ }
+ get_poll_info().add_flags_from_poll(PollFlags::Read());
+ loop_receive();
+ }
+
+ void on_send(size_t size) {
+ VLOG(fd) << get_native_fd() << " on send " << size;
+ if (size == 0) {
+ if (is_send_active_) {
+ return;
+ }
+ is_send_active_ = true;
+ }
+ CHECK(is_send_active_);
+ is_send_active_ = false;
+ loop_send();
+ }
+
+ void on_close() {
+ VLOG(fd) << get_native_fd() << " on close";
+ close_flag_ = true;
+ info_.set_native_fd({});
+ }
+
+ bool dec_refcnt() {
+ if (--refcnt_ == 0) {
+ delete this;
+ return true;
+ }
+ return false;
+ }
+
+ void inc_refcnt() {
+ CHECK(refcnt_ != 0);
+ refcnt_++;
+ }
+
+ void notify_iocp_send() {
+ inc_refcnt();
+ Iocp::get()->post(0, this, nullptr);
+ }
+ void notify_iocp_close() {
+ Iocp::get()->post(0, this, reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_));
+ }
+ void notify_iocp_connected() {
+ inc_refcnt();
+ Iocp::get()->post(0, this, reinterpret_cast<WSAOVERLAPPED *>(&receive_overlapped_));
+ }
+};
+
+void UdpSocketFdImplDeleter::operator()(UdpSocketFdImpl *impl) {
+ impl->close();
+}
+
+#elif TD_PORT_POSIX
+//struct iovec { [> Scatter/gather array items <]
+// void *iov_base; [> Starting address <]
+// size_t iov_len; [> Number of bytes to transfer <]
+//};
+
+//struct msghdr {
+// void *msg_name; [> optional address <]
+// socklen_t msg_namelen; [> size of address <]
+// struct iovec *msg_iov; [> scatter/gather array <]
+// size_t msg_iovlen; [> # elements in msg_iov <]
+// void *msg_control; [> ancillary data, see below <]
+// size_t msg_controllen; [> ancillary data buffer len <]
+// int msg_flags; [> flags on received message <]
+//};
+
+class UdpSocketReceiveHelper {
+ public:
+ void to_native(const UdpSocketFd::InboundMessage &message, msghdr &message_header) {
+ socklen_t addr_len{narrow_cast<socklen_t>(sizeof(addr_))};
+
+ message_header.msg_name = &addr_;
+ message_header.msg_namelen = addr_len;
+ io_vec_.iov_base = message.data.begin();
+ io_vec_.iov_len = message.data.size();
+ message_header.msg_iov = &io_vec_;
+ message_header.msg_iovlen = 1;
+ message_header.msg_control = control_buf_.data();
+ message_header.msg_controllen = narrow_cast<decltype(message_header.msg_controllen)>(control_buf_.size());
+ message_header.msg_flags = 0;
+ }
+
+ static void from_native(msghdr &message_header, size_t message_size, UdpSocketFd::InboundMessage &message) {
+#if TD_LINUX
+ cmsghdr *cmsg;
+ sock_extended_err *ee = nullptr;
+ for (cmsg = CMSG_FIRSTHDR(&message_header); cmsg != nullptr; cmsg = CMSG_NXTHDR(&message_header, cmsg)) {
+ if (cmsg->cmsg_type == IP_PKTINFO && cmsg->cmsg_level == IPPROTO_IP) {
+ //auto *pi = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cmsg));
+ } else if (cmsg->cmsg_type == IPV6_PKTINFO && cmsg->cmsg_level == IPPROTO_IPV6) {
+ //auto *pi = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsg));
+ } else if ((cmsg->cmsg_type == IP_RECVERR && cmsg->cmsg_level == IPPROTO_IP) ||
+ (cmsg->cmsg_type == IPV6_RECVERR && cmsg->cmsg_level == IPPROTO_IPV6)) {
+ ee = reinterpret_cast<sock_extended_err *>(CMSG_DATA(cmsg));
+ }
+ }
+ if (ee != nullptr) {
+ auto *addr = reinterpret_cast<sockaddr *>(SO_EE_OFFENDER(ee));
+ IPAddress address;
+ address.init_sockaddr(addr).ignore();
+ if (message.from != nullptr) {
+ *message.from = address;
+ }
+ if (message.error) {
+ *message.error = Status::PosixError(ee->ee_errno, "");
+ }
+ //message.data = MutableSlice();
+ message.data.truncate(0);
+ return;
+ }
+#endif
+ if (message.from != nullptr) {
+ message.from->init_sockaddr(reinterpret_cast<sockaddr *>(message_header.msg_name), message_header.msg_namelen)
+ .ignore();
+ }
+ if (message.error) {
+ *message.error = Status::OK();
+ }
+ if (message_header.msg_flags & MSG_TRUNC) {
+ if (message.error) {
+ *message.error = Status::Error(501, "Message too long");
+ }
+ message.data.truncate(0);
+ return;
+ }
+ CHECK(message_size <= message.data.size());
+ message.data.truncate(message_size);
+ CHECK(message_size == message.data.size());
+ }
+
+ private:
+ std::array<char, 1024> control_buf_;
+ sockaddr_storage addr_;
+ iovec io_vec_;
+};
+
+class UdpSocketSendHelper {
+ public:
+ void to_native(const UdpSocketFd::OutboundMessage &message, msghdr &message_header) {
+ CHECK(message.to != nullptr && message.to->is_valid());
+ message_header.msg_name = const_cast<sockaddr *>(message.to->get_sockaddr());
+ message_header.msg_namelen = narrow_cast<socklen_t>(message.to->get_sockaddr_len());
+ io_vec_.iov_base = const_cast<char *>(message.data.begin());
+ io_vec_.iov_len = message.data.size();
+ message_header.msg_iov = &io_vec_;
+ message_header.msg_iovlen = 1;
+ //TODO
+ message_header.msg_control = nullptr;
+ message_header.msg_controllen = 0;
+ message_header.msg_flags = 0;
+ }
+
+ private:
+ iovec io_vec_;
+};
+
+class UdpSocketFdImpl {
+ public:
+ explicit UdpSocketFdImpl(NativeFd fd) : info_(std::move(fd)) {
+ }
+ PollableFdInfo &get_poll_info() {
+ return info_;
+ }
+ const PollableFdInfo &get_poll_info() const {
+ return info_;
+ }
+
+ const NativeFd &get_native_fd() const {
+ return info_.native_fd();
+ }
+ Status get_pending_error() {
+ if (!get_poll_info().get_flags_local().has_pending_error()) {
+ return Status::OK();
+ }
+ TRY_STATUS(detail::get_socket_pending_error(get_native_fd()));
+ get_poll_info().clear_flags(PollFlags::Error());
+ return Status::OK();
+ }
+ Status receive_message(UdpSocketFd::InboundMessage &message, bool &is_received) {
+ is_received = false;
+ int flags = 0;
+ if (get_poll_info().get_flags_local().has_pending_error()) {
+#ifdef MSG_ERRQUEUE
+ flags = MSG_ERRQUEUE;
+#else
+ return get_pending_error();
+#endif
+ }
+
+ msghdr message_header;
+ detail::UdpSocketReceiveHelper helper;
+ helper.to_native(message, message_header);
+
+ auto native_fd = get_native_fd().socket();
+ auto recvmsg_res = detail::skip_eintr([&] { return recvmsg(native_fd, &message_header, flags); });
+ auto recvmsg_errno = errno;
+ if (recvmsg_res >= 0) {
+ UdpSocketReceiveHelper::from_native(message_header, recvmsg_res, message);
+ is_received = true;
+ return Status::OK();
+ }
+ return process_recvmsg_error(recvmsg_errno, is_received);
+ }
+
+ Status process_recvmsg_error(int recvmsg_errno, bool &is_received) {
+ is_received = false;
+ if (recvmsg_errno == EAGAIN
+#if EAGAIN != EWOULDBLOCK
+ || recvmsg_errno == EWOULDBLOCK
+#endif
+ ) {
+ if (get_poll_info().get_flags_local().has_pending_error()) {
+ get_poll_info().clear_flags(PollFlags::Error());
+ } else {
+ get_poll_info().clear_flags(PollFlags::Read());
+ }
+ return Status::OK();
+ }
+
+ auto error = Status::PosixError(recvmsg_errno, PSLICE() << "Receive from " << get_native_fd() << " has failed");
+ switch (recvmsg_errno) {
+ case EBADF:
+ case EFAULT:
+ case EINVAL:
+ case ENOTCONN:
+ case ECONNRESET:
+ case ETIMEDOUT:
+ LOG(FATAL) << error;
+ UNREACHABLE();
+ default:
+ LOG(WARNING) << "Unknown error: " << error;
+ // fallthrough
+ case ENOBUFS:
+ case ENOMEM:
+#ifdef MSG_ERRQUEUE
+ get_poll_info().add_flags(PollFlags::Error());
+#endif
+ return error;
+ }
+ }
+
+ Status send_message(const UdpSocketFd::OutboundMessage &message, bool &is_sent) {
+ is_sent = false;
+ msghdr message_header;
+ detail::UdpSocketSendHelper helper;
+ helper.to_native(message, message_header);
+
+ auto native_fd = get_native_fd().socket();
+ auto sendmsg_res = detail::skip_eintr([&] { return sendmsg(native_fd, &message_header, 0); });
+ auto sendmsg_errno = errno;
+ if (sendmsg_res >= 0) {
+ is_sent = true;
+ return Status::OK();
+ }
+ return process_sendmsg_error(sendmsg_errno, is_sent);
+ }
+ Status process_sendmsg_error(int sendmsg_errno, bool &is_sent) {
+ if (sendmsg_errno == EAGAIN
+#if EAGAIN != EWOULDBLOCK
+ || sendmsg_errno == EWOULDBLOCK
+#endif
+ ) {
+ get_poll_info().clear_flags(PollFlags::Write());
+ return Status::OK();
+ }
+
+ auto error = Status::PosixError(sendmsg_errno, PSLICE() << "Send from " << get_native_fd() << " has failed");
+ switch (sendmsg_errno) {
+ // Still may send some other packets, but there is no point to resend this particular message
+ case EACCES:
+ case EMSGSIZE:
+ case EPERM:
+ LOG(WARNING) << "Silently drop packet :( " << error;
+ //TODO: get errors from MSG_ERRQUEUE is possible
+ is_sent = true;
+ return error;
+
+ // Some general problems, which may be fixed in future
+ case ENOMEM:
+ case EDQUOT:
+ case EFBIG:
+ case ENETDOWN:
+ case ENETUNREACH:
+ case ENOSPC:
+ case EHOSTUNREACH:
+ case ENOBUFS:
+ default:
+#ifdef MSG_ERRQUEUE
+ get_poll_info().add_flags(PollFlags::Error());
+#endif
+ return error;
+
+ case EBADF: // impossible
+ case ENOTSOCK: // impossible
+ case EPIPE: // impossible for udp
+ case ECONNRESET: // impossible for udp
+ case EDESTADDRREQ: // we checked that address is valid
+ case ENOTCONN: // we checked that address is valid
+ case EINTR: // we already skipped all EINTR
+ case EISCONN: // impossible for udp socket
+ case EOPNOTSUPP:
+ case ENOTDIR:
+ case EFAULT:
+ case EINVAL:
+ case EAFNOSUPPORT:
+ LOG(FATAL) << error;
+ UNREACHABLE();
+ return error;
+ }
+ }
+
+ Status send_messages(Span<UdpSocketFd::OutboundMessage> messages, size_t &cnt) {
+#if TD_HAS_MMSG
+ return send_messages_fast(messages, cnt);
+#else
+ return send_messages_slow(messages, cnt);
+#endif
+ }
+
+ Status receive_messages(MutableSpan<UdpSocketFd::InboundMessage> messages, size_t &cnt) {
+#if TD_HAS_MMSG
+ return receive_messages_fast(messages, cnt);
+#else
+ return receive_messages_slow(messages, cnt);
+#endif
+ }
+
+ private:
+ PollableFdInfo info_;
+
+ Status send_messages_slow(Span<UdpSocketFd::OutboundMessage> messages, size_t &cnt) {
+ cnt = 0;
+ for (auto &message : messages) {
+ CHECK(!message.data.empty());
+ bool is_sent;
+ auto error = send_message(message, is_sent);
+ cnt += is_sent;
+ TRY_STATUS(std::move(error));
+ }
+ return Status::OK();
+ }
+
+#if TD_HAS_MMSG
+ Status send_messages_fast(Span<UdpSocketFd::OutboundMessage> messages, size_t &cnt) {
+ //struct mmsghdr {
+ // msghdr msg_hdr; [> Message header <]
+ // unsigned int msg_len; [> Number of bytes transmitted <]
+ //};
+ std::array<detail::UdpSocketSendHelper, 16> helpers;
+ std::array<mmsghdr, 16> headers;
+ size_t to_send = min(messages.size(), headers.size());
+ for (size_t i = 0; i < to_send; i++) {
+ helpers[i].to_native(messages[i], headers[i].msg_hdr);
+ headers[i].msg_len = 0;
+ }
+
+ auto native_fd = get_native_fd().socket();
+ auto sendmmsg_res =
+ detail::skip_eintr([&] { return sendmmsg(native_fd, headers.data(), narrow_cast<unsigned int>(to_send), 0); });
+ auto sendmmsg_errno = errno;
+ if (sendmmsg_res >= 0) {
+ cnt = sendmmsg_res;
+ return Status::OK();
+ }
+
+ bool is_sent = false;
+ auto status = process_sendmsg_error(sendmmsg_errno, is_sent);
+ cnt = is_sent;
+ return status;
+ }
+#endif
+ Status receive_messages_slow(MutableSpan<UdpSocketFd::InboundMessage> messages, size_t &cnt) {
+ cnt = 0;
+ while (cnt < messages.size() && get_poll_info().get_flags_local().can_read()) {
+ auto &message = messages[cnt];
+ CHECK(!message.data.empty());
+ bool is_received;
+ auto error = receive_message(message, is_received);
+ cnt += is_received;
+ TRY_STATUS(std::move(error));
+ }
+ return Status::OK();
+ }
+
+#if TD_HAS_MMSG
+ Status receive_messages_fast(MutableSpan<UdpSocketFd::InboundMessage> messages, size_t &cnt) {
+ int flags = 0;
+ cnt = 0;
+ if (get_poll_info().get_flags_local().has_pending_error()) {
+#ifdef MSG_ERRQUEUE
+ flags = MSG_ERRQUEUE;
+#else
+ return get_pending_error();
+#endif
+ }
+ //struct mmsghdr {
+ // msghdr msg_hdr; [> Message header <]
+ // unsigned int msg_len; [> Number of bytes transmitted <]
+ //};
+ std::array<detail::UdpSocketReceiveHelper, 16> helpers;
+ std::array<mmsghdr, 16> headers;
+ size_t to_receive = min(messages.size(), headers.size());
+ for (size_t i = 0; i < to_receive; i++) {
+ helpers[i].to_native(messages[i], headers[i].msg_hdr);
+ headers[i].msg_len = 0;
+ }
+
+ auto native_fd = get_native_fd().socket();
+ auto recvmmsg_res = detail::skip_eintr(
+ [&] { return recvmmsg(native_fd, headers.data(), narrow_cast<unsigned int>(to_receive), flags, nullptr); });
+ auto recvmmsg_errno = errno;
+ if (recvmmsg_res >= 0) {
+ cnt = narrow_cast<size_t>(recvmmsg_res);
+ for (size_t i = 0; i < cnt; i++) {
+ UdpSocketReceiveHelper::from_native(headers[i].msg_hdr, headers[i].msg_len, messages[i]);
+ }
+ return Status::OK();
+ }
+
+ bool is_received;
+ auto status = process_recvmsg_error(recvmmsg_errno, is_received);
+ cnt = is_received;
+ return status;
+ }
+#endif
+};
+void UdpSocketFdImplDeleter::operator()(UdpSocketFdImpl *impl) {
+ delete impl;
+}
+#endif
+} // namespace detail
+
+UdpSocketFd::UdpSocketFd() = default;
+UdpSocketFd::UdpSocketFd(UdpSocketFd &&) noexcept = default;
+UdpSocketFd &UdpSocketFd::operator=(UdpSocketFd &&) noexcept = default;
+UdpSocketFd::~UdpSocketFd() = default;
+PollableFdInfo &UdpSocketFd::get_poll_info() {
+ return impl_->get_poll_info();
+}
+const PollableFdInfo &UdpSocketFd::get_poll_info() const {
+ return impl_->get_poll_info();
+}
+
+Result<UdpSocketFd> UdpSocketFd::open(const IPAddress &address) {
+ NativeFd native_fd{socket(address.get_address_family(), SOCK_DGRAM, IPPROTO_UDP)};
+ if (!native_fd) {
+ return OS_SOCKET_ERROR("Failed to create a socket");
+ }
+ TRY_STATUS(native_fd.set_is_blocking_unsafe(false));
+
+ auto sock = native_fd.socket();
+#if TD_PORT_POSIX
+ int flags = 1;
+#elif TD_PORT_WINDOWS
+ BOOL flags = TRUE;
+#endif
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags));
+ // TODO: SO_REUSEADDR, SO_KEEPALIVE, TCP_NODELAY, SO_SNDBUF, SO_RCVBUF, TCP_QUICKACK, SO_LINGER
+
+ auto bind_addr = address.get_any_addr();
+ bind_addr.set_port(address.get_port());
+ auto e_bind = bind(sock, bind_addr.get_sockaddr(), narrow_cast<int>(bind_addr.get_sockaddr_len()));
+ if (e_bind != 0) {
+ return OS_SOCKET_ERROR("Failed to bind a socket");
+ }
+ return UdpSocketFd(make_unique<detail::UdpSocketFdImpl>(std::move(native_fd)));
+}
+
+UdpSocketFd::UdpSocketFd(unique_ptr<detail::UdpSocketFdImpl> impl) : impl_(impl.release()) {
+}
+
+void UdpSocketFd::close() {
+ impl_.reset();
+}
+
+bool UdpSocketFd::empty() const {
+ return !impl_;
+}
+
+const NativeFd &UdpSocketFd::get_native_fd() const {
+ return get_poll_info().native_fd();
+}
+
+#if TD_PORT_POSIX
+static Result<uint32> maximize_buffer(int socket_fd, int optname, uint32 max_size) {
+ if (setsockopt(socket_fd, SOL_SOCKET, optname, &max_size, sizeof(max_size)) == 0) {
+ // fast path
+ return max_size;
+ }
+
+ /* Start with the default size. */
+ uint32 old_size = 0;
+ socklen_t intsize = sizeof(old_size);
+ if (getsockopt(socket_fd, SOL_SOCKET, optname, &old_size, &intsize)) {
+ return OS_ERROR("getsockopt() failed");
+ }
+#if TD_LINUX
+ old_size /= 2;
+#endif
+
+ /* Binary-search for the real maximum. */
+ uint32 last_good_size = old_size;
+ uint32 min_size = old_size;
+ while (min_size <= max_size) {
+ uint32 avg_size = min_size + (max_size - min_size) / 2;
+ if (setsockopt(socket_fd, SOL_SOCKET, optname, &avg_size, sizeof(avg_size)) == 0) {
+ last_good_size = avg_size;
+ min_size = avg_size + 1;
+ } else {
+ max_size = avg_size - 1;
+ }
+ }
+ return last_good_size;
+}
+
+Result<uint32> UdpSocketFd::maximize_snd_buffer(uint32 max_size) {
+ return maximize_buffer(get_native_fd().fd(), SO_SNDBUF, max_size == 0 ? DEFAULT_UDP_MAX_SND_BUFFER_SIZE : max_size);
+}
+
+Result<uint32> UdpSocketFd::maximize_rcv_buffer(uint32 max_size) {
+ return maximize_buffer(get_native_fd().fd(), SO_RCVBUF, max_size == 0 ? DEFAULT_UDP_MAX_RCV_BUFFER_SIZE : max_size);
+}
+#else
+Result<uint32> UdpSocketFd::maximize_snd_buffer(uint32 max_size) {
+ return 0;
+}
+Result<uint32> UdpSocketFd::maximize_rcv_buffer(uint32 max_size) {
+ return 0;
+}
+#endif
+
+#if TD_PORT_POSIX
+Status UdpSocketFd::send_message(const OutboundMessage &message, bool &is_sent) {
+ return impl_->send_message(message, is_sent);
+}
+Status UdpSocketFd::receive_message(InboundMessage &message, bool &is_received) {
+ return impl_->receive_message(message, is_received);
+}
+
+Status UdpSocketFd::send_messages(Span<OutboundMessage> messages, size_t &count) {
+ return impl_->send_messages(messages, count);
+}
+Status UdpSocketFd::receive_messages(MutableSpan<InboundMessage> messages, size_t &count) {
+ return impl_->receive_messages(messages, count);
+}
+#endif
+#if TD_PORT_WINDOWS
+Result<optional<UdpMessage>> UdpSocketFd::receive() {
+ return impl_->receive();
+}
+
+void UdpSocketFd::send(UdpMessage message) {
+ return impl_->send(std::move(message));
+}
+
+Status UdpSocketFd::flush_send() {
+ return impl_->flush_send();
+}
+#endif
+
+bool UdpSocketFd::is_critical_read_error(const Status &status) {
+ return status.code() == ENOMEM || status.code() == ENOBUFS;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.h
new file mode 100644
index 0000000000..6ff5cd1491
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.h
@@ -0,0 +1,93 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/port/config.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/optional.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/PollableFd.h"
+#include "td/utils/port/IPAddress.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Span.h"
+#include "td/utils/Status.h"
+
+#include <memory>
+
+namespace td {
+// Udp and errors
+namespace detail {
+class UdpSocketFdImpl;
+class UdpSocketFdImplDeleter {
+ public:
+ void operator()(UdpSocketFdImpl *impl);
+};
+} // namespace detail
+
+struct UdpMessage {
+ IPAddress address;
+ BufferSlice data;
+ Status error;
+};
+
+class UdpSocketFd {
+ public:
+ UdpSocketFd();
+ UdpSocketFd(UdpSocketFd &&) noexcept;
+ UdpSocketFd &operator=(UdpSocketFd &&) noexcept;
+ ~UdpSocketFd();
+
+ UdpSocketFd(const UdpSocketFd &) = delete;
+ UdpSocketFd &operator=(const UdpSocketFd &) = delete;
+
+ Result<uint32> maximize_snd_buffer(uint32 max_size = 0);
+ Result<uint32> maximize_rcv_buffer(uint32 max_size = 0);
+
+ static Result<UdpSocketFd> open(const IPAddress &address) TD_WARN_UNUSED_RESULT;
+
+ PollableFdInfo &get_poll_info();
+ const PollableFdInfo &get_poll_info() const;
+ const NativeFd &get_native_fd() const;
+
+ void close();
+ bool empty() const;
+
+ static bool is_critical_read_error(const Status &status);
+
+#if TD_PORT_POSIX
+ struct OutboundMessage {
+ const IPAddress *to;
+ Slice data;
+ };
+ struct InboundMessage {
+ IPAddress *from;
+ MutableSlice data;
+ Status *error;
+ };
+
+ Status send_message(const OutboundMessage &message, bool &is_sent) TD_WARN_UNUSED_RESULT;
+ Status receive_message(InboundMessage &message, bool &is_received) TD_WARN_UNUSED_RESULT;
+
+ Status send_messages(Span<OutboundMessage> messages, size_t &count) TD_WARN_UNUSED_RESULT;
+ Status receive_messages(MutableSpan<InboundMessage> messages, size_t &count) TD_WARN_UNUSED_RESULT;
+#elif TD_PORT_WINDOWS
+ Result<optional<UdpMessage> > receive();
+
+ void send(UdpMessage message);
+
+ Status flush_send();
+#endif
+
+ private:
+ static constexpr uint32 DEFAULT_UDP_MAX_SND_BUFFER_SIZE = (1 << 24);
+ static constexpr uint32 DEFAULT_UDP_MAX_RCV_BUFFER_SIZE = (1 << 24);
+ std::unique_ptr<detail::UdpSocketFdImpl, detail::UdpSocketFdImplDeleter> impl_;
+ explicit UdpSocketFd(unique_ptr<detail::UdpSocketFdImpl> impl);
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/config.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/config.h
index 0ffdb3c3bf..59fd94bf00 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/config.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/config.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -19,6 +19,9 @@
#if TD_LINUX || TD_ANDROID || TD_TIZEN
#define TD_POLL_EPOLL 1
#define TD_EVENTFD_LINUX 1
+#elif TD_FREEBSD || TD_OPENBSD || TD_NETBSD
+ #define TD_POLL_KQUEUE 1
+ #define TD_EVENTFD_BSD 1
#elif TD_CYGWIN
#define TD_POLL_SELECT 1
#define TD_EVENTFD_BSD 1
@@ -31,16 +34,26 @@
#elif TD_WINDOWS
#define TD_POLL_WINEVENT 1
#define TD_EVENTFD_WINDOWS 1
+#elif TD_ILLUMOS
+ #define TD_POLL_EPOLL 1
+ #define TD_EVENTFD_LINUX 1
+#elif TD_SOLARIS
+ #define TD_POLL_POLL 1
+ #define TD_EVENTFD_BSD 1
#else
#error "Poll's implementation is not defined"
#endif
#if TD_EMSCRIPTEN
#define TD_THREAD_UNSUPPORTED 1
-#elif TD_TIZEN
- #define TD_THREAD_PTHREAD 1
-#else
+#elif TD_WINDOWS
#define TD_THREAD_STL 1
+#else
+ #define TD_THREAD_PTHREAD 1
+#endif
+
+#if TD_LINUX
+ #define TD_HAS_MMSG 1
#endif
// clang-format on
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.cpp
index 2ef026d164..771b06a954 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -14,98 +14,110 @@ char disable_linker_warning_about_empty_file_epoll_cpp TD_UNUSED;
#include "td/utils/logging.h"
#include "td/utils/Status.h"
+#include <cerrno>
+
#include <unistd.h>
namespace td {
namespace detail {
void Epoll::init() {
- CHECK(epoll_fd == -1);
- epoll_fd = epoll_create(1);
+ CHECK(!epoll_fd_);
+ epoll_fd_ = NativeFd(epoll_create(1));
auto epoll_create_errno = errno;
- LOG_IF(FATAL, epoll_fd == -1) << Status::PosixError(epoll_create_errno, "epoll_create failed");
+ LOG_IF(FATAL, !epoll_fd_) << Status::PosixError(epoll_create_errno, "epoll_create failed");
- events.resize(1000);
+ events_.resize(1000);
}
void Epoll::clear() {
- if (epoll_fd == -1) {
+ if (!epoll_fd_) {
return;
}
- events.clear();
+ events_.clear();
+
+ epoll_fd_.close();
- close(epoll_fd);
- epoll_fd = -1;
+ for (auto *list_node = list_root_.next; list_node != &list_root_;) {
+ auto pollable_fd = PollableFd::from_list_node(list_node);
+ list_node = list_node->next;
+ }
}
-void Epoll::subscribe(const Fd &fd, Fd::Flags flags) {
+void Epoll::subscribe(PollableFd fd, PollFlags flags) {
epoll_event event;
event.events = EPOLLHUP | EPOLLERR | EPOLLET;
#ifdef EPOLLRDHUP
event.events |= EPOLLRDHUP;
#endif
- if (flags & Fd::Read) {
+ if (flags.can_read()) {
event.events |= EPOLLIN;
}
- if (flags & Fd::Write) {
+ if (flags.can_write()) {
event.events |= EPOLLOUT;
}
- auto native_fd = fd.get_native_fd();
- event.data.fd = native_fd;
- int err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, native_fd, &event);
+ auto native_fd = fd.native_fd().fd();
+ auto *list_node = fd.release_as_list_node();
+ list_root_.put(list_node);
+ event.data.ptr = list_node;
+
+ int err = epoll_ctl(epoll_fd_.fd(), EPOLL_CTL_ADD, native_fd, &event);
auto epoll_ctl_errno = errno;
- LOG_IF(FATAL, err == -1) << Status::PosixError(epoll_ctl_errno, "epoll_ctl ADD failed") << ", epoll_fd = " << epoll_fd
- << ", fd = " << native_fd;
+ LOG_IF(FATAL, err == -1) << Status::PosixError(epoll_ctl_errno, "epoll_ctl ADD failed")
+ << ", epoll_fd = " << epoll_fd_.fd() << ", fd = " << native_fd;
}
-void Epoll::unsubscribe(const Fd &fd) {
- auto native_fd = fd.get_native_fd();
- int err = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, native_fd, nullptr);
+void Epoll::unsubscribe(PollableFdRef fd_ref) {
+ auto fd = fd_ref.lock();
+ auto native_fd = fd.native_fd().fd();
+ int err = epoll_ctl(epoll_fd_.fd(), EPOLL_CTL_DEL, native_fd, nullptr);
auto epoll_ctl_errno = errno;
- LOG_IF(FATAL, err == -1) << Status::PosixError(epoll_ctl_errno, "epoll_ctl DEL failed") << ", epoll_fd = " << epoll_fd
- << ", fd = " << native_fd;
+ LOG_IF(FATAL, err == -1) << Status::PosixError(epoll_ctl_errno, "epoll_ctl DEL failed")
+ << ", epoll_fd = " << epoll_fd_.fd() << ", fd = " << native_fd
+ << ", status = " << fd.native_fd().validate();
}
-void Epoll::unsubscribe_before_close(const Fd &fd) {
+void Epoll::unsubscribe_before_close(PollableFdRef fd) {
unsubscribe(fd);
}
void Epoll::run(int timeout_ms) {
- int ready_n = epoll_wait(epoll_fd, &events[0], static_cast<int>(events.size()), timeout_ms);
+ int ready_n = epoll_wait(epoll_fd_.fd(), &events_[0], static_cast<int>(events_.size()), timeout_ms);
auto epoll_wait_errno = errno;
LOG_IF(FATAL, ready_n == -1 && epoll_wait_errno != EINTR)
<< Status::PosixError(epoll_wait_errno, "epoll_wait failed");
for (int i = 0; i < ready_n; i++) {
- Fd::Flags flags = 0;
- epoll_event *event = &events[i];
+ PollFlags flags;
+ epoll_event *event = &events_[i];
if (event->events & EPOLLIN) {
event->events &= ~EPOLLIN;
- flags |= Fd::Read;
+ flags = flags | PollFlags::Read();
}
if (event->events & EPOLLOUT) {
event->events &= ~EPOLLOUT;
- flags |= Fd::Write;
+ flags = flags | PollFlags::Write();
}
#ifdef EPOLLRDHUP
if (event->events & EPOLLRDHUP) {
event->events &= ~EPOLLRDHUP;
- // flags |= Fd::Close;
- // TODO
+ flags = flags | PollFlags::Close();
}
#endif
if (event->events & EPOLLHUP) {
event->events &= ~EPOLLHUP;
- flags |= Fd::Close;
+ flags = flags | PollFlags::Close();
}
if (event->events & EPOLLERR) {
event->events &= ~EPOLLERR;
- flags |= Fd::Error;
+ flags = flags | PollFlags::Error();
}
if (event->events) {
- LOG(FATAL) << "Unsupported epoll events: " << event->events;
+ LOG(FATAL) << "Unsupported epoll events: " << static_cast<int32>(event->events);
}
- // LOG(DEBUG) << "Epoll event " << tag("fd", event->data.fd) << tag("flags", format::as_binary(flags));
- Fd(event->data.fd, Fd::Mode::Reference).update_flags_notify(flags);
+ //LOG(DEBUG) << "Epoll event " << tag("fd", event->data.fd) << tag("flags", format::as_binary(flags));
+ auto pollable_fd = PollableFd::from_list_node(static_cast<ListNode *>(event->data.ptr));
+ pollable_fd.add_flags(flags);
+ pollable_fd.release_as_list_node();
}
}
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.h
index db4f66e5a7..da02de7c7b 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,8 +11,11 @@
#ifdef TD_POLL_EPOLL
#include "td/utils/common.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/List.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/PollBase.h"
+#include "td/utils/port/PollFlags.h"
#include <sys/epoll.h>
@@ -26,23 +29,28 @@ class Epoll final : public PollBase {
Epoll &operator=(const Epoll &) = delete;
Epoll(Epoll &&) = delete;
Epoll &operator=(Epoll &&) = delete;
- ~Epoll() override = default;
+ ~Epoll() final = default;
- void init() override;
+ void init() final;
- void clear() override;
+ void clear() final;
- void subscribe(const Fd &fd, Fd::Flags flags) override;
+ void subscribe(PollableFd fd, PollFlags flags) final;
- void unsubscribe(const Fd &fd) override;
+ void unsubscribe(PollableFdRef fd) final;
- void unsubscribe_before_close(const Fd &fd) override;
+ void unsubscribe_before_close(PollableFdRef fd) final;
- void run(int timeout_ms) override;
+ void run(int timeout_ms) final;
+
+ static bool is_edge_triggered() {
+ return true;
+ }
private:
- int epoll_fd = -1;
- vector<struct epoll_event> events;
+ NativeFd epoll_fd_;
+ vector<struct epoll_event> events_;
+ ListNode list_root_;
};
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.cpp
index d51e99ac0a..8f22aeb5c1 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,9 +11,16 @@ char disable_linker_warning_about_empty_file_event_fd_bsd_cpp TD_UNUSED;
#ifdef TD_EVENTFD_BSD
#include "td/utils/logging.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/skip_eintr.h"
+#include "td/utils/port/PollFlags.h"
+#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
+#include <cerrno>
+
#include <fcntl.h>
+#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
@@ -36,11 +43,13 @@ void EventFdBsd::init() {
#endif
LOG_IF(FATAL, err == -1) << Status::PosixError(socketpair_errno, "socketpair failed");
- detail::set_native_socket_is_blocking(fds[0], false).ensure();
- detail::set_native_socket_is_blocking(fds[1], false).ensure();
+ auto fd_a = NativeFd(fds[0]);
+ auto fd_b = NativeFd(fds[1]);
+ fd_a.set_is_blocking_unsafe(false).ensure();
+ fd_b.set_is_blocking_unsafe(false).ensure();
- in_ = Fd(fds[0], Fd::Mode::Owner);
- out_ = Fd(fds[1], Fd::Mode::Owner);
+ in_ = SocketFd::from_native_fd(std::move(fd_a)).move_as_ok();
+ out_ = SocketFd::from_native_fd(std::move(fd_b)).move_as_ok();
}
bool EventFdBsd::empty() {
@@ -56,12 +65,8 @@ Status EventFdBsd::get_pending_error() {
return Status::OK();
}
-const Fd &EventFdBsd::get_fd() const {
- return out_;
-}
-
-Fd &EventFdBsd::get_fd() {
- return out_;
+PollableFdInfo &EventFdBsd::get_poll_info() {
+ return out_.get_poll_info();
}
void EventFdBsd::release() {
@@ -72,13 +77,14 @@ void EventFdBsd::release() {
}
size_t size = result.ok();
if (size != sizeof(value)) {
- LOG(FATAL) << "EventFdBsd write returned " << value << " instead of " << sizeof(value);
+ LOG(FATAL) << "EventFdBsd write returned " << size << " instead of " << sizeof(value);
}
}
void EventFdBsd::acquire() {
- out_.update_flags(Fd::Read);
- while (can_read(out_)) {
+ sync_with_poll(out_);
+ out_.get_poll_info().add_flags(PollFlags::Read());
+ while (can_read_local(out_)) {
uint8 value[1024];
auto result = out_.read(MutableSlice(value, sizeof(value)));
if (result.is_error()) {
@@ -87,6 +93,17 @@ void EventFdBsd::acquire() {
}
}
+void EventFdBsd::wait(int timeout_ms) {
+ detail::skip_eintr_timeout(
+ [this](int timeout_ms) {
+ pollfd fd;
+ fd.fd = get_poll_info().native_fd().fd();
+ fd.events = POLLIN;
+ return poll(&fd, 1, timeout_ms);
+ },
+ timeout_ms);
+}
+
} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.h
index 08f7ddd308..5278f74b23 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,34 +11,36 @@
#ifdef TD_EVENTFD_BSD
#include "td/utils/common.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/EventFdBase.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/SocketFd.h"
#include "td/utils/Status.h"
namespace td {
namespace detail {
class EventFdBsd final : public EventFdBase {
- Fd in_;
- Fd out_;
+ SocketFd in_;
+ SocketFd out_;
public:
EventFdBsd() = default;
- void init() override;
+ void init() final;
- bool empty() override;
+ bool empty() final;
- void close() override;
+ void close() final;
- Status get_pending_error() override TD_WARN_UNUSED_RESULT;
+ Status get_pending_error() final TD_WARN_UNUSED_RESULT;
- const Fd &get_fd() const override;
- Fd &get_fd() override;
+ PollableFdInfo &get_poll_info() final;
- void release() override;
+ void release() final;
- void acquire() override;
+ void acquire() final;
+
+ void wait(int timeout_ms) final;
};
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.cpp
index fd08c9af08..bcab560af7 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,45 +11,71 @@ char disable_linker_warning_about_empty_file_event_fd_linux_cpp TD_UNUSED;
#ifdef TD_EVENTFD_LINUX
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/skip_eintr.h"
+#include "td/utils/port/PollFlags.h"
+#include "td/utils/ScopeGuard.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include <cerrno>
+
+#include <poll.h>
#include <sys/eventfd.h>
+#include <unistd.h>
namespace td {
namespace detail {
+class EventFdLinuxImpl {
+ public:
+ PollableFdInfo info_;
+};
+
+EventFdLinux::EventFdLinux() = default;
+EventFdLinux::EventFdLinux(EventFdLinux &&) noexcept = default;
+EventFdLinux &EventFdLinux::operator=(EventFdLinux &&) noexcept = default;
+EventFdLinux::~EventFdLinux() = default;
void EventFdLinux::init() {
- int fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ auto fd = NativeFd(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
auto eventfd_errno = errno;
- LOG_IF(FATAL, fd == -1) << Status::PosixError(eventfd_errno, "eventfd call failed");
-
- fd_ = Fd(fd, Fd::Mode::Owner);
+ LOG_IF(FATAL, !fd) << Status::PosixError(eventfd_errno, "eventfd call failed");
+ impl_ = make_unique<EventFdLinuxImpl>();
+ impl_->info_.set_native_fd(std::move(fd));
}
bool EventFdLinux::empty() {
- return fd_.empty();
+ return !impl_;
}
void EventFdLinux::close() {
- fd_.close();
+ impl_.reset();
}
Status EventFdLinux::get_pending_error() {
return Status::OK();
}
-const Fd &EventFdLinux::get_fd() const {
- return fd_;
-}
-
-Fd &EventFdLinux::get_fd() {
- return fd_;
+PollableFdInfo &EventFdLinux::get_poll_info() {
+ return impl_->info_;
}
+// NB: will be called from multiple threads
void EventFdLinux::release() {
const uint64 value = 1;
- // NB: write_unsafe is used, because release will be called from multiple threads
- auto result = fd_.write_unsafe(Slice(reinterpret_cast<const char *>(&value), sizeof(value)));
+ auto slice = Slice(reinterpret_cast<const char *>(&value), sizeof(value));
+ auto native_fd = impl_->info_.native_fd().fd();
+
+ auto result = [&]() -> Result<size_t> {
+ auto write_res = detail::skip_eintr([&] { return write(native_fd, slice.begin(), slice.size()); });
+ auto write_errno = errno;
+ if (write_res >= 0) {
+ return narrow_cast<size_t>(write_res);
+ }
+ return Status::PosixError(write_errno, PSLICE() << "Write to fd " << native_fd << " has failed");
+ }();
+
if (result.is_error()) {
LOG(FATAL) << "EventFdLinux write failed: " << result.error();
}
@@ -60,12 +86,46 @@ void EventFdLinux::release() {
}
void EventFdLinux::acquire() {
+ impl_->info_.sync_with_poll();
+ SCOPE_EXIT {
+ // Clear flags without EAGAIN and EWOULDBLOCK
+ // Looks like it is safe thing to do with eventfd
+ get_poll_info().clear_flags(PollFlags::Read());
+ };
uint64 res;
- auto result = fd_.read(MutableSlice(reinterpret_cast<char *>(&res), sizeof(res)));
+ auto slice = MutableSlice(reinterpret_cast<char *>(&res), sizeof(res));
+ auto native_fd = impl_->info_.native_fd().fd();
+ auto result = [&]() -> Result<size_t> {
+ CHECK(!slice.empty());
+ auto read_res = detail::skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); });
+ auto read_errno = errno;
+ if (read_res >= 0) {
+ CHECK(read_res != 0);
+ return narrow_cast<size_t>(read_res);
+ }
+ if (read_errno == EAGAIN
+#if EAGAIN != EWOULDBLOCK
+ || read_errno == EWOULDBLOCK
+#endif
+ ) {
+ return 0;
+ }
+ return Status::PosixError(read_errno, PSLICE() << "Read from fd " << native_fd << " has failed");
+ }();
if (result.is_error()) {
LOG(FATAL) << "EventFdLinux read failed: " << result.error();
}
- fd_.clear_flags(Fd::Read);
+}
+
+void EventFdLinux::wait(int timeout_ms) {
+ detail::skip_eintr_timeout(
+ [this](int timeout_ms) {
+ pollfd fd;
+ fd.fd = get_poll_info().native_fd().fd();
+ fd.events = POLLIN;
+ return poll(&fd, 1, timeout_ms);
+ },
+ timeout_ms);
}
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.h
index 3df7ce3a5d..2cd4d12210 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,31 +11,38 @@
#ifdef TD_EVENTFD_LINUX
#include "td/utils/common.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/EventFdBase.h"
-#include "td/utils/port/Fd.h"
#include "td/utils/Status.h"
namespace td {
namespace detail {
+class EventFdLinuxImpl;
class EventFdLinux final : public EventFdBase {
- Fd fd_;
+ unique_ptr<EventFdLinuxImpl> impl_;
public:
- void init() override;
+ EventFdLinux();
+ EventFdLinux(EventFdLinux &&) noexcept;
+ EventFdLinux &operator=(EventFdLinux &&) noexcept;
+ ~EventFdLinux() final;
- bool empty() override;
+ void init() final;
- void close() override;
+ bool empty() final;
- Status get_pending_error() override TD_WARN_UNUSED_RESULT;
+ void close() final;
- const Fd &get_fd() const override;
- Fd &get_fd() override;
+ Status get_pending_error() final TD_WARN_UNUSED_RESULT;
- void release() override;
+ PollableFdInfo &get_poll_info() final;
- void acquire() override;
+ void release() final;
+
+ void acquire() final;
+
+ void wait(int timeout_ms) final;
};
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.cpp
index 8adfd5a686..7acaa3f17b 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,39 +10,56 @@ char disable_linker_warning_about_empty_file_event_fd_windows_cpp TD_UNUSED;
#ifdef TD_EVENTFD_WINDOWS
+#include "td/utils/logging.h"
+
namespace td {
namespace detail {
void EventFdWindows::init() {
- fd_ = Fd::create_event_fd();
+ auto handle = CreateEventW(nullptr, true, false, nullptr);
+ if (handle == nullptr) {
+ auto error = OS_ERROR("CreateEventW failed");
+ LOG(FATAL) << error;
+ }
+ event_ = NativeFd(handle);
}
bool EventFdWindows::empty() {
- return fd_.empty();
+ return !event_;
}
void EventFdWindows::close() {
- fd_.close();
+ event_.close();
}
Status EventFdWindows::get_pending_error() {
return Status::OK();
}
-const Fd &EventFdWindows::get_fd() const {
- return fd_;
-}
-
-Fd &EventFdWindows::get_fd() {
- return fd_;
+PollableFdInfo &EventFdWindows::get_poll_info() {
+ UNREACHABLE();
}
void EventFdWindows::release() {
- fd_.release();
+ if (SetEvent(event_.fd()) == 0) {
+ auto error = OS_ERROR("SetEvent failed");
+ LOG(FATAL) << error;
+ }
}
void EventFdWindows::acquire() {
- fd_.acquire();
+ if (ResetEvent(event_.fd()) == 0) {
+ auto error = OS_ERROR("ResetEvent failed");
+ LOG(FATAL) << error;
+ }
+}
+
+void EventFdWindows::wait(int timeout_ms) {
+ WaitForSingleObject(event_.fd(), timeout_ms);
+ if (ResetEvent(event_.fd()) == 0) {
+ auto error = OS_ERROR("ResetEvent failed");
+ LOG(FATAL) << error;
+ }
}
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.h
index 48e1c763b3..5794e6c7ca 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,33 +11,35 @@
#ifdef TD_EVENTFD_WINDOWS
#include "td/utils/common.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/EventFdBase.h"
-#include "td/utils/port/Fd.h"
#include "td/utils/Status.h"
namespace td {
namespace detail {
class EventFdWindows final : public EventFdBase {
- Fd fd_;
+ NativeFd event_;
public:
EventFdWindows() = default;
- void init() override;
+ void init() final;
- bool empty() override;
+ bool empty() final;
- void close() override;
+ void close() final;
- Status get_pending_error() override TD_WARN_UNUSED_RESULT;
+ Status get_pending_error() final TD_WARN_UNUSED_RESULT;
- const Fd &get_fd() const override;
- Fd &get_fd() override;
+ PollableFdInfo &get_poll_info() final;
- void release() override;
+ void release() final;
- void acquire() override;
+ void acquire() final;
+
+ void wait(int timeout_ms) final;
};
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.cpp
new file mode 100644
index 0000000000..1e472df67f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.cpp
@@ -0,0 +1,110 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/detail/Iocp.h"
+
+char disable_linker_warning_about_empty_file_iocp_cpp TD_UNUSED;
+
+#ifdef TD_PORT_WINDOWS
+
+#include "td/utils/logging.h"
+
+namespace td {
+namespace detail {
+Iocp::~Iocp() {
+ clear();
+}
+
+void Iocp::loop() {
+ Iocp::Guard guard(this);
+ while (true) {
+ DWORD bytes = 0;
+ ULONG_PTR key = 0;
+ WSAOVERLAPPED *overlapped = nullptr;
+ BOOL ok =
+ GetQueuedCompletionStatus(iocp_handle_->fd(), &bytes, &key, reinterpret_cast<OVERLAPPED **>(&overlapped), 1000);
+ if (bytes || key || overlapped) {
+ // LOG(ERROR) << "Got IOCP " << bytes << " " << key << " " << overlapped;
+ }
+ if (ok) {
+ auto callback = reinterpret_cast<Iocp::Callback *>(key);
+ if (callback == nullptr) {
+ // LOG(ERROR) << "Interrupt IOCP loop";
+ return;
+ }
+ callback->on_iocp(bytes, overlapped);
+ } else {
+ if (overlapped != nullptr) {
+ auto error = OS_ERROR("Received from IOCP");
+ auto callback = reinterpret_cast<Iocp::Callback *>(key);
+ CHECK(callback != nullptr);
+ callback->on_iocp(std::move(error), overlapped);
+ }
+ }
+ }
+}
+
+void Iocp::interrupt_loop() {
+ post(0, nullptr, nullptr);
+}
+
+void Iocp::init() {
+ CHECK(!iocp_handle_);
+ auto res = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0);
+ if (res == nullptr) {
+ auto error = OS_ERROR("IOCP creation failed");
+ LOG(FATAL) << error;
+ }
+ iocp_handle_ = std::make_shared<NativeFd>(res);
+}
+
+void Iocp::clear() {
+ iocp_handle_.reset();
+}
+
+void Iocp::subscribe(const NativeFd &native_fd, Callback *callback) {
+ CHECK(iocp_handle_);
+ auto iocp_handle =
+ CreateIoCompletionPort(native_fd.fd(), iocp_handle_->fd(), reinterpret_cast<ULONG_PTR>(callback), 0);
+ if (iocp_handle == nullptr) {
+ auto error = OS_ERROR("CreateIoCompletionPort");
+ LOG(FATAL) << error;
+ }
+ LOG_CHECK(iocp_handle == iocp_handle_->fd()) << iocp_handle << " " << iocp_handle_->fd();
+}
+
+IocpRef Iocp::get_ref() const {
+ return IocpRef(iocp_handle_);
+}
+
+static void iocp_post(NativeFd &iocp_handle, size_t size, Iocp::Callback *callback, WSAOVERLAPPED *overlapped) {
+ if (PostQueuedCompletionStatus(iocp_handle.fd(), DWORD(size), reinterpret_cast<ULONG_PTR>(callback),
+ reinterpret_cast<OVERLAPPED *>(overlapped)) == 0) {
+ auto error = OS_ERROR("IOCP post failed");
+ LOG(FATAL) << error;
+ }
+}
+
+void Iocp::post(size_t size, Callback *callback, WSAOVERLAPPED *overlapped) {
+ iocp_post(*iocp_handle_, size, callback, overlapped);
+}
+
+IocpRef::IocpRef(std::weak_ptr<NativeFd> iocp_handle) : iocp_handle_(std::move(iocp_handle)) {
+}
+
+bool IocpRef::post(size_t size, Iocp::Callback *callback, WSAOVERLAPPED *overlapped) {
+ auto iocp_handle = iocp_handle_.lock();
+ if (!iocp_handle) {
+ return false;
+ }
+ iocp_post(*iocp_handle, size, callback, overlapped);
+ return true;
+}
+
+} // namespace detail
+} // namespace td
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.h
new file mode 100644
index 0000000000..a37cce7e57
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.h
@@ -0,0 +1,71 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/port/config.h"
+
+#ifdef TD_PORT_WINDOWS
+
+#include "td/utils/common.h"
+#include "td/utils/Context.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/Status.h"
+
+#include <memory>
+
+namespace td {
+namespace detail {
+
+class IocpRef;
+class Iocp final : public Context<Iocp> {
+ public:
+ Iocp() = default;
+ Iocp(const Iocp &) = delete;
+ Iocp &operator=(const Iocp &) = delete;
+ Iocp(Iocp &&) = delete;
+ Iocp &operator=(Iocp &&) = delete;
+ ~Iocp();
+
+ class Callback {
+ public:
+ virtual ~Callback() = default;
+ virtual void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) = 0;
+ };
+
+ void init();
+ void subscribe(const NativeFd &fd, Callback *callback);
+ void post(size_t size, Callback *callback, WSAOVERLAPPED *overlapped);
+ void loop();
+ void interrupt_loop();
+ void clear();
+
+ IocpRef get_ref() const;
+
+ private:
+ std::shared_ptr<NativeFd> iocp_handle_;
+};
+
+class IocpRef {
+ public:
+ IocpRef() = default;
+ IocpRef(const Iocp &) = delete;
+ IocpRef &operator=(const Iocp &) = delete;
+ IocpRef(IocpRef &&) = default;
+ IocpRef &operator=(IocpRef &&) = default;
+
+ explicit IocpRef(std::weak_ptr<NativeFd> iocp_handle);
+
+ bool post(size_t size, Iocp::Callback *callback, WSAOVERLAPPED *overlapped);
+
+ private:
+ std::weak_ptr<NativeFd> iocp_handle_;
+};
+
+} // namespace detail
+} // namespace td
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.cpp
index 351f8d7d6c..64c3c54302 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -13,54 +13,58 @@ char disable_linker_warning_about_empty_file_kqueue_cpp TD_UNUSED;
#include "td/utils/logging.h"
#include "td/utils/Status.h"
+#include <cerrno>
#include <utility>
+#include <sys/time.h>
#include <unistd.h>
namespace td {
namespace detail {
-KQueue::KQueue() {
- kq = -1;
-}
KQueue::~KQueue() {
clear();
}
void KQueue::init() {
- kq = kqueue();
+ kq_ = NativeFd(kqueue());
auto kqueue_errno = errno;
- LOG_IF(FATAL, kq == -1) << Status::PosixError(kqueue_errno, "kqueue creation failed");
+ LOG_IF(FATAL, !kq_) << Status::PosixError(kqueue_errno, "kqueue creation failed");
// TODO: const
- events.resize(1000);
- changes_n = 0;
+ events_.resize(1000);
+ changes_n_ = 0;
}
void KQueue::clear() {
- if (kq == -1) {
+ if (!kq_) {
return;
}
- events.clear();
- close(kq);
- kq = -1;
+ events_.clear();
+ kq_.close();
+ for (auto *list_node = list_root_.next; list_node != &list_root_;) {
+ auto pollable_fd = PollableFd::from_list_node(list_node);
+ list_node = list_node->next;
+ }
}
int KQueue::update(int nevents, const timespec *timeout, bool may_fail) {
- int err = kevent(kq, &events[0], changes_n, &events[0], nevents, timeout);
+ int err = kevent(kq_.fd(), &events_[0], changes_n_, &events_[0], nevents, timeout);
auto kevent_errno = errno;
bool is_fatal_error = [&] {
if (err != -1) {
return false;
}
- if (may_fail) {
- return kevent_errno != ENOENT;
+ if (may_fail && kevent_errno == ENOENT) {
+ return false;
}
return kevent_errno != EINTR;
}();
- LOG_IF(FATAL, is_fatal_error) << Status::PosixError(kevent_errno, "kevent failed");
+ if (is_fatal_error) {
+ LOG(FATAL) << Status::PosixError(kevent_errno, "kevent failed");
+ }
- changes_n = 0;
+ changes_n_ = 0;
if (err < 0) {
return 0;
}
@@ -68,7 +72,7 @@ int KQueue::update(int nevents, const timespec *timeout, bool may_fail) {
}
void KQueue::flush_changes(bool may_fail) {
- if (!changes_n) {
+ if (!changes_n_) {
return;
}
int n = update(0, nullptr, may_fail);
@@ -77,47 +81,59 @@ void KQueue::flush_changes(bool may_fail) {
void KQueue::add_change(std::uintptr_t ident, int16 filter, uint16 flags, uint32 fflags, std::intptr_t data,
void *udata) {
- if (changes_n == static_cast<int>(events.size())) {
+ if (changes_n_ == static_cast<int>(events_.size())) {
flush_changes();
}
- EV_SET(&events[changes_n], ident, filter, flags, fflags, data, udata);
+#if TD_NETBSD
+ auto set_udata = reinterpret_cast<std::intptr_t>(udata);
+#else
+ auto set_udata = udata;
+#endif
+ EV_SET(&events_[changes_n_], ident, filter, flags, fflags, data, set_udata);
VLOG(fd) << "Subscribe [fd:" << ident << "] [filter:" << filter << "] [udata: " << udata << "]";
- changes_n++;
+ changes_n_++;
}
-void KQueue::subscribe(const Fd &fd, Fd::Flags flags) {
- if (flags & Fd::Read) {
- add_change(fd.get_native_fd(), EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, nullptr);
+void KQueue::subscribe(PollableFd fd, PollFlags flags) {
+ auto native_fd = fd.native_fd().fd();
+ auto list_node = fd.release_as_list_node();
+ list_root_.put(list_node);
+ if (flags.can_read()) {
+ add_change(native_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, list_node);
}
- if (flags & Fd::Write) {
- add_change(fd.get_native_fd(), EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, nullptr);
+ if (flags.can_write()) {
+ add_change(native_fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, list_node);
}
}
-void KQueue::invalidate(const Fd &fd) {
- for (int i = 0; i < changes_n; i++) {
- if (events[i].ident == static_cast<std::uintptr_t>(fd.get_native_fd())) {
- changes_n--;
- std::swap(events[i], events[changes_n]);
+void KQueue::invalidate(int native_fd) {
+ for (int i = 0; i < changes_n_; i++) {
+ if (events_[i].ident == static_cast<std::uintptr_t>(native_fd)) {
+ changes_n_--;
+ std::swap(events_[i], events_[changes_n_]);
i--;
}
}
}
-void KQueue::unsubscribe(const Fd &fd) {
+void KQueue::unsubscribe(PollableFdRef fd_ref) {
+ auto pollable_fd = fd_ref.lock();
+ auto native_fd = pollable_fd.native_fd().fd();
+
// invalidate(fd);
flush_changes();
- add_change(fd.get_native_fd(), EVFILT_READ, EV_DELETE, 0, 0, nullptr);
+ add_change(native_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr);
flush_changes(true);
- add_change(fd.get_native_fd(), EVFILT_WRITE, EV_DELETE, 0, 0, nullptr);
+ add_change(native_fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr);
flush_changes(true);
}
-void KQueue::unsubscribe_before_close(const Fd &fd) {
- invalidate(fd);
+void KQueue::unsubscribe_before_close(PollableFdRef fd_ref) {
+ auto pollable_fd = fd_ref.lock();
+ invalidate(pollable_fd.native_fd().fd());
// just to avoid O(changes_n ^ 2)
- if (changes_n != 0) {
+ if (changes_n_ != 0) {
flush_changes();
}
}
@@ -133,25 +149,32 @@ void KQueue::run(int timeout_ms) {
timeout_ptr = &timeout_data;
}
- int n = update(static_cast<int>(events.size()), timeout_ptr);
+ int n = update(static_cast<int>(events_.size()), timeout_ptr);
for (int i = 0; i < n; i++) {
- struct kevent *event = &events[i];
- Fd::Flags flags = 0;
+ struct kevent *event = &events_[i];
+ PollFlags flags;
if (event->filter == EVFILT_WRITE) {
- flags |= Fd::Write;
+ flags.add_flags(PollFlags::Write());
}
if (event->filter == EVFILT_READ) {
- flags |= Fd::Read;
+ flags.add_flags(PollFlags::Read());
}
if (event->flags & EV_EOF) {
- flags |= Fd::Close;
+ flags.add_flags(PollFlags::Close());
}
if (event->fflags & EV_ERROR) {
LOG(FATAL) << "EV_ERROR in kqueue is not supported";
}
- VLOG(fd) << "Event [fd:" << event->ident << "] [filter:" << event->filter << "] [udata: " << event->udata << "]";
- // LOG(WARNING) << "event->ident = " << event->ident << "event->filter = " << event->filter;
- Fd(static_cast<int>(event->ident), Fd::Mode::Reference).update_flags_notify(flags);
+#if TD_NETBSD
+ auto udata = reinterpret_cast<void *>(event->udata);
+#else
+ auto udata = event->udata;
+#endif
+ VLOG(fd) << "Event [fd:" << event->ident << "] [filter:" << event->filter << "] [udata: " << udata << "]";
+ // LOG(WARNING) << "Have event->ident = " << event->ident << "event->filter = " << event->filter;
+ auto pollable_fd = PollableFd::from_list_node(static_cast<ListNode *>(udata));
+ pollable_fd.add_flags(flags);
+ pollable_fd.release_as_list_node();
}
}
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.h
index e1b71e5fa5..d74443c475 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,11 +11,16 @@
#ifdef TD_POLL_KQUEUE
#include "td/utils/common.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/List.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/PollBase.h"
+#include "td/utils/port/PollFlags.h"
#include <cstdint>
+#include <sys/types.h> // must be included before sys/event.h, which depends on sys/types.h on FreeBSD
+
#include <sys/event.h>
namespace td {
@@ -23,33 +28,38 @@ namespace detail {
class KQueue final : public PollBase {
public:
- KQueue();
+ KQueue() = default;
KQueue(const KQueue &) = delete;
KQueue &operator=(const KQueue &) = delete;
KQueue(KQueue &&) = delete;
KQueue &operator=(KQueue &&) = delete;
- ~KQueue() override;
+ ~KQueue() final;
+
+ void init() final;
- void init() override;
+ void clear() final;
- void clear() override;
+ void subscribe(PollableFd fd, PollFlags flags) final;
- void subscribe(const Fd &fd, Fd::Flags flags) override;
+ void unsubscribe(PollableFdRef fd) final;
- void unsubscribe(const Fd &fd) override;
+ void unsubscribe_before_close(PollableFdRef fd) final;
- void unsubscribe_before_close(const Fd &fd) override;
+ void run(int timeout_ms) final;
- void run(int timeout_ms) override;
+ static bool is_edge_triggered() {
+ return true;
+ }
private:
- vector<struct kevent> events;
- int changes_n;
- int kq;
+ vector<struct kevent> events_;
+ int changes_n_;
+ NativeFd kq_;
+ ListNode list_root_;
int update(int nevents, const timespec *timeout, bool may_fail = false);
- void invalidate(const Fd &fd);
+ void invalidate(int native_fd);
void flush_changes(bool may_fail = false);
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.cpp
new file mode 100644
index 0000000000..7a492857ec
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.cpp
@@ -0,0 +1,261 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/detail/NativeFd.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/SliceBuilder.h"
+
+#if TD_PORT_POSIX
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
+#if TD_FD_DEBUG
+#include <mutex>
+#include <set>
+#endif
+
+namespace td {
+
+int VERBOSITY_NAME(fd) = VERBOSITY_NAME(DEBUG) + 9;
+
+#if TD_FD_DEBUG
+class FdSet {
+ public:
+ void on_create_fd(NativeFd::Fd fd) {
+ if (!is_valid(fd)) {
+ return;
+ }
+ if (is_stdio(fd)) {
+ return;
+ }
+ std::unique_lock<std::mutex> guard(mutex_);
+ if (fds_.count(fd) >= 1) {
+ LOG(FATAL) << "Create duplicated fd: " << fd;
+ }
+ fds_.insert(fd);
+ }
+
+ Status validate(NativeFd::Fd fd) {
+ if (!is_valid(fd)) {
+ return Status::Error(PSLICE() << "Invalid fd: " << fd);
+ }
+ if (is_stdio(fd)) {
+ return Status::OK();
+ }
+ std::unique_lock<std::mutex> guard(mutex_);
+ if (fds_.count(fd) != 1) {
+ return Status::Error(PSLICE() << "Unknown fd: " << fd);
+ }
+ return Status::OK();
+ }
+
+ void on_close_fd(NativeFd::Fd fd) {
+ if (!is_valid(fd)) {
+ return;
+ }
+ if (is_stdio(fd)) {
+ return;
+ }
+ std::unique_lock<std::mutex> guard(mutex_);
+ if (fds_.count(fd) != 1) {
+ LOG(FATAL) << "Close unknown fd: " << fd;
+ }
+ fds_.erase(fd);
+ }
+
+ private:
+ std::mutex mutex_;
+ std::set<NativeFd::Fd> fds_;
+
+ bool is_stdio(NativeFd::Fd fd) const {
+#if TD_PORT_WINDOWS
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
+ return fd == GetStdHandle(STD_INPUT_HANDLE) || fd == GetStdHandle(STD_OUTPUT_HANDLE) ||
+ fd == GetStdHandle(STD_ERROR_HANDLE);
+#else
+ return false;
+#endif
+#else
+ return fd >= 0 && fd <= 2;
+#endif
+ }
+
+ bool is_valid(NativeFd::Fd fd) const {
+#if TD_PORT_WINDOWS
+ return fd != INVALID_HANDLE_VALUE;
+#else
+ return fd >= 0;
+#endif
+ }
+};
+
+namespace {
+FdSet &get_fd_set() {
+ static FdSet res;
+ return res;
+}
+} // namespace
+
+#endif
+
+Status NativeFd::validate() const {
+#if TD_FD_DEBUG
+ return get_fd_set().validate(fd_);
+#else
+ return Status::OK();
+#endif
+}
+
+NativeFd::NativeFd(Fd fd) : fd_(fd) {
+ VLOG(fd) << *this << " create";
+#if TD_FD_DEBUG
+ get_fd_set().on_create_fd(fd_);
+#endif
+}
+
+NativeFd::NativeFd(Fd fd, bool nolog) : fd_(fd) {
+#if TD_FD_DEBUG
+ get_fd_set().on_create_fd(fd_);
+#endif
+}
+
+#if TD_PORT_WINDOWS
+NativeFd::NativeFd(Socket socket) : fd_(reinterpret_cast<Fd>(socket)), is_socket_(true) {
+ VLOG(fd) << *this << " create";
+#if TD_FD_DEBUG
+ get_fd_set().on_create_fd(fd_);
+#endif
+}
+#endif
+
+NativeFd::NativeFd(NativeFd &&other) noexcept : fd_(other.fd_) {
+#if TD_PORT_WINDOWS
+ is_socket_ = other.is_socket_;
+#endif
+ other.fd_ = empty_fd();
+}
+
+NativeFd &NativeFd::operator=(NativeFd &&other) noexcept {
+ CHECK(this != &other);
+ close();
+ fd_ = other.fd_;
+#if TD_PORT_WINDOWS
+ is_socket_ = other.is_socket_;
+#endif
+ other.fd_ = empty_fd();
+ return *this;
+}
+
+NativeFd::~NativeFd() {
+ close();
+}
+
+NativeFd::operator bool() const noexcept {
+ return fd_ != empty_fd();
+}
+
+NativeFd::Fd NativeFd::empty_fd() {
+#if TD_PORT_POSIX
+ return -1;
+#elif TD_PORT_WINDOWS
+ return INVALID_HANDLE_VALUE;
+#endif
+}
+
+NativeFd::Fd NativeFd::fd() const {
+ return fd_;
+}
+
+NativeFd::Socket NativeFd::socket() const {
+#if TD_PORT_POSIX
+ return fd();
+#elif TD_PORT_WINDOWS
+ CHECK(is_socket_);
+ return reinterpret_cast<Socket>(fd_);
+#endif
+}
+
+Status NativeFd::set_is_blocking(bool is_blocking) const {
+#if TD_PORT_POSIX
+ auto old_flags = fcntl(fd(), F_GETFL);
+ if (old_flags == -1) {
+ return OS_SOCKET_ERROR("Failed to get socket flags");
+ }
+ auto new_flags = is_blocking ? old_flags & ~O_NONBLOCK : old_flags | O_NONBLOCK;
+ if (new_flags != old_flags && fcntl(fd(), F_SETFL, new_flags) == -1) {
+ return OS_SOCKET_ERROR("Failed to set socket flags");
+ }
+
+ return Status::OK();
+#elif TD_PORT_WINDOWS
+ return set_is_blocking_unsafe(is_blocking);
+#endif
+}
+
+Status NativeFd::set_is_blocking_unsafe(bool is_blocking) const {
+#if TD_PORT_POSIX
+ if (fcntl(fd(), F_SETFL, is_blocking ? 0 : O_NONBLOCK) == -1) {
+#elif TD_PORT_WINDOWS
+ u_long mode = is_blocking;
+ if (ioctlsocket(socket(), FIONBIO, &mode) != 0) {
+#endif
+ return OS_SOCKET_ERROR("Failed to change socket flags");
+ }
+ return Status::OK();
+}
+
+Status NativeFd::duplicate(const NativeFd &to) const {
+#if TD_PORT_POSIX
+ CHECK(*this);
+ CHECK(to);
+ if (dup2(fd(), to.fd()) == -1) {
+ return OS_ERROR("Failed to duplicate file descriptor");
+ }
+ return Status::OK();
+#elif TD_PORT_WINDOWS
+ return Status::Error("Not supported");
+#endif
+}
+
+void NativeFd::close() {
+ if (!*this) {
+ return;
+ }
+
+#if TD_FD_DEBUG
+ get_fd_set().on_close_fd(fd());
+#endif
+
+ VLOG(fd) << *this << " close";
+#if TD_PORT_WINDOWS
+ if (is_socket_ ? closesocket(socket()) : !CloseHandle(fd())) {
+#elif TD_PORT_POSIX
+ if (::close(fd()) < 0) {
+#endif
+ auto error = OS_ERROR("Close fd");
+ LOG(ERROR) << error;
+ }
+ fd_ = empty_fd();
+}
+
+NativeFd::Fd NativeFd::release() {
+ VLOG(fd) << *this << " release";
+ auto res = fd_;
+ fd_ = empty_fd();
+#if TD_FD_DEBUG
+ get_fd_set().on_close_fd(res);
+#endif
+ return res;
+}
+
+StringBuilder &operator<<(StringBuilder &sb, const NativeFd &fd) {
+ return sb << tag("fd", fd.fd());
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.h
new file mode 100644
index 0000000000..ac4bddccf2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.h
@@ -0,0 +1,68 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/port/config.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+
+namespace td {
+
+extern int VERBOSITY_NAME(fd);
+
+class NativeFd {
+ public:
+#if TD_PORT_POSIX
+ using Fd = int;
+ using Socket = int;
+#elif TD_PORT_WINDOWS
+ using Fd = HANDLE;
+ using Socket = SOCKET;
+#endif
+ NativeFd() = default;
+ explicit NativeFd(Fd fd);
+ NativeFd(Fd fd, bool nolog);
+#if TD_PORT_WINDOWS
+ explicit NativeFd(Socket socket);
+#endif
+ NativeFd(const NativeFd &) = delete;
+ NativeFd &operator=(const NativeFd &) = delete;
+ NativeFd(NativeFd &&other) noexcept;
+ NativeFd &operator=(NativeFd &&other) noexcept;
+ ~NativeFd();
+
+ explicit operator bool() const noexcept;
+
+ Fd fd() const;
+ Socket socket() const;
+
+ Status set_is_blocking(bool is_blocking) const;
+
+ Status set_is_blocking_unsafe(bool is_blocking) const; // may drop other Fd flags on non-Windows
+
+ Status duplicate(const NativeFd &to) const;
+
+ void close();
+ Fd release();
+
+ Status validate() const;
+
+ private:
+ static Fd empty_fd();
+
+ Fd fd_ = empty_fd();
+#if TD_PORT_WINDOWS
+ bool is_socket_{false};
+#endif
+};
+
+StringBuilder &operator<<(StringBuilder &sb, const NativeFd &fd);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.cpp
index 87a7391802..c4431207f5 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -13,8 +13,11 @@ char disable_linker_warning_about_empty_file_poll_cpp TD_UNUSED;
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/ScopeGuard.h"
#include "td/utils/Status.h"
+#include <cerrno>
+
namespace td {
namespace detail {
@@ -25,31 +28,37 @@ void Poll::clear() {
pollfds_.clear();
}
-void Poll::subscribe(const Fd &fd, Fd::Flags flags) {
- unsubscribe(fd);
+void Poll::subscribe(PollableFd fd, PollFlags flags) {
+ unsubscribe(fd.ref());
struct pollfd pollfd;
- pollfd.fd = fd.get_native_fd();
+ pollfd.fd = fd.native_fd().fd();
pollfd.events = 0;
- if (flags & Fd::Read) {
+ if (flags.can_read()) {
pollfd.events |= POLLIN;
}
- if (flags & Fd::Write) {
+ if (flags.can_write()) {
pollfd.events |= POLLOUT;
}
pollfd.revents = 0;
pollfds_.push_back(pollfd);
+ fds_.push_back(std::move(fd));
}
-void Poll::unsubscribe(const Fd &fd) {
+void Poll::unsubscribe(PollableFdRef fd_ref) {
+ auto fd = fd_ref.lock();
+ SCOPE_EXIT {
+ fd.release_as_list_node();
+ };
for (auto it = pollfds_.begin(); it != pollfds_.end(); ++it) {
- if (it->fd == fd.get_native_fd()) {
+ if (it->fd == fd.native_fd().fd()) {
pollfds_.erase(it);
+ fds_.erase(fds_.begin() + (it - pollfds_.begin()));
return;
}
}
}
-void Poll::unsubscribe_before_close(const Fd &fd) {
+void Poll::unsubscribe_before_close(PollableFdRef fd) {
unsubscribe(fd);
}
@@ -58,23 +67,26 @@ void Poll::run(int timeout_ms) {
auto poll_errno = errno;
LOG_IF(FATAL, err == -1 && poll_errno != EINTR) << Status::PosixError(poll_errno, "poll failed");
- for (auto &pollfd : pollfds_) {
- Fd::Flags flags = 0;
+ for (size_t i = 0; i < pollfds_.size(); i++) {
+ auto &pollfd = pollfds_[i];
+ auto &fd = fds_[i];
+
+ PollFlags flags;
if (pollfd.revents & POLLIN) {
pollfd.revents &= ~POLLIN;
- flags |= Fd::Read;
+ flags = flags | PollFlags::Read();
}
if (pollfd.revents & POLLOUT) {
pollfd.revents &= ~POLLOUT;
- flags |= Fd::Write;
+ flags = flags | PollFlags::Write();
}
if (pollfd.revents & POLLHUP) {
pollfd.revents &= ~POLLHUP;
- flags |= Fd::Close;
+ flags = flags | PollFlags::Close();
}
if (pollfd.revents & POLLERR) {
pollfd.revents &= ~POLLERR;
- flags |= Fd::Error;
+ flags = flags | PollFlags::Error();
}
if (pollfd.revents & POLLNVAL) {
LOG(FATAL) << "Unexpected POLLNVAL " << tag("fd", pollfd.fd);
@@ -82,7 +94,7 @@ void Poll::run(int timeout_ms) {
if (pollfd.revents) {
LOG(FATAL) << "Unsupported poll events: " << pollfd.revents;
}
- Fd(pollfd.fd, Fd::Mode::Reference).update_flags_notify(flags);
+ fd.add_flags(flags);
}
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.h
index 32eca75399..5f9f0e94f6 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,8 +11,9 @@
#ifdef TD_POLL_POLL
#include "td/utils/common.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/PollBase.h"
+#include "td/utils/port/PollFlags.h"
#include <poll.h>
@@ -26,22 +27,27 @@ class Poll final : public PollBase {
Poll &operator=(const Poll &) = delete;
Poll(Poll &&) = delete;
Poll &operator=(Poll &&) = delete;
- ~Poll() override = default;
+ ~Poll() final = default;
- void init() override;
+ void init() final;
- void clear() override;
+ void clear() final;
- void subscribe(const Fd &fd, Fd::Flags flags) override;
+ void subscribe(PollableFd fd, PollFlags flags) final;
- void unsubscribe(const Fd &fd) override;
+ void unsubscribe(PollableFdRef fd) final;
- void unsubscribe_before_close(const Fd &fd) override;
+ void unsubscribe_before_close(PollableFdRef fd) final;
- void run(int timeout_ms) override;
+ void run(int timeout_ms) final;
+
+ static bool is_edge_triggered() {
+ return false;
+ }
private:
vector<pollfd> pollfds_;
+ vector<PollableFd> fds_;
};
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/PollableFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/PollableFd.h
new file mode 100644
index 0000000000..01fb5857e4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/PollableFd.h
@@ -0,0 +1,230 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/format.h"
+#include "td/utils/List.h"
+#include "td/utils/logging.h"
+#include "td/utils/Observer.h"
+#include "td/utils/port/detail/NativeFd.h"
+#include "td/utils/port/Mutex.h"
+#include "td/utils/port/PollFlags.h"
+
+#include <atomic>
+#include <memory>
+
+namespace td {
+
+class PollableFdInfo;
+class PollableFdInfoUnlock {
+ public:
+ void operator()(PollableFdInfo *ptr);
+};
+
+class PollableFd;
+class PollableFdRef {
+ public:
+ explicit PollableFdRef(ListNode *list_node) : list_node_(list_node) {
+ }
+ PollableFd lock();
+
+ private:
+ ListNode *list_node_;
+};
+
+class PollableFd {
+ public:
+ // Interface for kqueue, epoll and e.t.c.
+ const NativeFd &native_fd() const;
+
+ ListNode *release_as_list_node();
+ PollableFdRef ref();
+ static PollableFd from_list_node(ListNode *node);
+ void add_flags(PollFlags flags);
+ PollFlags get_flags_unsafe() const;
+
+ private:
+ std::unique_ptr<PollableFdInfo, PollableFdInfoUnlock> fd_info_;
+ friend class PollableFdInfo;
+
+ explicit PollableFd(std::unique_ptr<PollableFdInfo, PollableFdInfoUnlock> fd_info) : fd_info_(std::move(fd_info)) {
+ }
+};
+
+inline PollableFd PollableFdRef::lock() {
+ return PollableFd::from_list_node(list_node_);
+}
+
+class PollableFdInfo final : private ListNode {
+ public:
+ PollableFdInfo() = default;
+ PollableFdInfo(const PollableFdInfo &) = delete;
+ PollableFdInfo &operator=(const PollableFdInfo &) = delete;
+ PollableFdInfo(PollableFdInfo &&) = delete;
+ PollableFdInfo &operator=(PollableFdInfo &&) = delete;
+
+ PollableFd extract_pollable_fd(ObserverBase *observer) {
+ VLOG(fd) << native_fd() << " extract pollable fd " << tag("observer", observer);
+ CHECK(!empty());
+ bool was_locked = lock_.test_and_set(std::memory_order_acquire);
+ CHECK(!was_locked);
+ set_observer(observer);
+ return PollableFd{std::unique_ptr<PollableFdInfo, PollableFdInfoUnlock>{this}};
+ }
+ PollableFdRef get_pollable_fd_ref() {
+ CHECK(!empty());
+ bool was_locked = lock_.test_and_set(std::memory_order_acquire);
+ CHECK(was_locked);
+ return PollableFdRef{as_list_node()};
+ }
+
+ void add_flags(PollFlags flags) {
+ flags_.write_flags_local(flags);
+ }
+
+ void clear_flags(PollFlags flags) {
+ flags_.clear_flags(flags);
+ }
+ PollFlags sync_with_poll() const {
+ return flags_.read_flags();
+ }
+ PollFlags get_flags_local() const {
+ return flags_.read_flags_local();
+ }
+
+ bool empty() const {
+ return !fd_;
+ }
+
+ void set_native_fd(NativeFd new_native_fd) {
+ if (fd_) {
+ CHECK(!new_native_fd);
+ bool was_locked = lock_.test_and_set(std::memory_order_acquire);
+ CHECK(!was_locked);
+ lock_.clear(std::memory_order_release);
+ }
+
+ fd_ = std::move(new_native_fd);
+ }
+ explicit PollableFdInfo(NativeFd native_fd) {
+ set_native_fd(std::move(native_fd));
+ }
+ const NativeFd &native_fd() const {
+ //CHECK(!empty());
+ return fd_;
+ }
+ NativeFd move_as_native_fd() {
+ return std::move(fd_);
+ }
+
+ ~PollableFdInfo() {
+ VLOG(fd) << native_fd() << " destroy PollableFdInfo";
+ bool was_locked = lock_.test_and_set(std::memory_order_acquire);
+ CHECK(!was_locked);
+ }
+
+ void add_flags_from_poll(PollFlags flags) {
+ VLOG(fd) << native_fd() << " add flags from poll " << flags;
+ if (flags_.write_flags(flags)) {
+ notify_observer();
+ }
+ }
+
+ private:
+ NativeFd fd_{};
+ std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
+ PollFlagsSet flags_;
+#if TD_PORT_WINDOWS
+ Mutex observer_lock_;
+#endif
+ ObserverBase *observer_{nullptr};
+
+ friend class PollableFd;
+ friend class PollableFdInfoUnlock;
+
+ void set_observer(ObserverBase *observer) {
+#if TD_PORT_WINDOWS
+ auto lock = observer_lock_.lock();
+#endif
+ CHECK(observer_ == nullptr);
+ observer_ = observer;
+ }
+ void clear_observer() {
+#if TD_PORT_WINDOWS
+ auto lock = observer_lock_.lock();
+#endif
+ observer_ = nullptr;
+ }
+ void notify_observer() {
+#if TD_PORT_WINDOWS
+ auto lock = observer_lock_.lock();
+#endif
+ VLOG(fd) << native_fd() << " notify " << tag("observer", observer_);
+ if (observer_ != nullptr) {
+ observer_->notify();
+ }
+ }
+
+ void unlock() {
+ clear_observer();
+ lock_.clear(std::memory_order_release);
+ as_list_node()->remove();
+ }
+
+ ListNode *as_list_node() {
+ return static_cast<ListNode *>(this);
+ }
+ static PollableFdInfo *from_list_node(ListNode *list_node) {
+ return static_cast<PollableFdInfo *>(list_node);
+ }
+};
+inline void PollableFdInfoUnlock::operator()(PollableFdInfo *ptr) {
+ ptr->unlock();
+}
+
+inline ListNode *PollableFd::release_as_list_node() {
+ return fd_info_.release()->as_list_node();
+}
+inline PollableFdRef PollableFd::ref() {
+ return PollableFdRef{fd_info_->as_list_node()};
+}
+inline PollableFd PollableFd::from_list_node(ListNode *node) {
+ return PollableFd(std::unique_ptr<PollableFdInfo, PollableFdInfoUnlock>(PollableFdInfo::from_list_node(node)));
+}
+
+inline void PollableFd::add_flags(PollFlags flags) {
+ fd_info_->add_flags_from_poll(flags);
+}
+inline PollFlags PollableFd::get_flags_unsafe() const {
+ return fd_info_->get_flags_local();
+}
+inline const NativeFd &PollableFd::native_fd() const {
+ return fd_info_->native_fd();
+}
+
+template <class FdT>
+void sync_with_poll(const FdT &fd) {
+ fd.get_poll_info().sync_with_poll();
+}
+
+template <class FdT>
+bool can_read_local(const FdT &fd) {
+ return fd.get_poll_info().get_flags_local().can_read() || fd.get_poll_info().get_flags_local().has_pending_error();
+}
+
+template <class FdT>
+bool can_write_local(const FdT &fd) {
+ return fd.get_poll_info().get_flags_local().can_write();
+}
+
+template <class FdT>
+bool can_close_local(const FdT &fd) {
+ return fd.get_poll_info().get_flags_local().can_close();
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.cpp
index b532a0464c..d149246fd1 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -29,22 +29,25 @@ void Select::clear() {
fds_.clear();
}
-void Select::subscribe(const Fd &fd, Fd::Flags flags) {
- int native_fd = fd.get_native_fd();
+void Select::subscribe(PollableFd fd, PollFlags flags) {
+ int native_fd = fd.native_fd().fd();
for (auto &it : fds_) {
- CHECK(it.fd_ref.get_native_fd() != native_fd);
+ CHECK(it.fd.native_fd().fd() != native_fd);
}
- fds_.push_back(FdInfo{Fd(native_fd, Fd::Mode::Reference), flags});
- CHECK(0 <= native_fd && native_fd < FD_SETSIZE) << native_fd << " " << FD_SETSIZE;
+ fds_.push_back(FdInfo{std::move(fd), flags});
+ LOG_CHECK(0 <= native_fd && native_fd < FD_SETSIZE) << native_fd << " " << FD_SETSIZE;
FD_SET(native_fd, &all_fd_);
if (native_fd > max_fd_) {
max_fd_ = native_fd;
}
}
-void Select::unsubscribe(const Fd &fd) {
- int native_fd = fd.get_native_fd();
- CHECK(0 <= native_fd && native_fd < FD_SETSIZE) << native_fd << " " << FD_SETSIZE;
+void Select::unsubscribe(PollableFdRef fd) {
+ auto fd_locked = fd.lock();
+ int native_fd = fd_locked.native_fd().fd();
+ fd_locked.release_as_list_node();
+
+ LOG_CHECK(0 <= native_fd && native_fd < FD_SETSIZE) << native_fd << " " << FD_SETSIZE;
FD_CLR(native_fd, &all_fd_);
FD_CLR(native_fd, &read_fd_);
FD_CLR(native_fd, &write_fd_);
@@ -53,17 +56,17 @@ void Select::unsubscribe(const Fd &fd) {
max_fd_--;
}
for (auto it = fds_.begin(); it != fds_.end();) {
- if (it->fd_ref.get_native_fd() == native_fd) {
+ if (it->fd.native_fd().fd() == native_fd) {
std::swap(*it, fds_.back());
fds_.pop_back();
break;
} else {
- it++;
+ ++it;
}
}
}
-void Select::unsubscribe_before_close(const Fd &fd) {
+void Select::unsubscribe_before_close(PollableFdRef fd) {
unsubscribe(fd);
}
@@ -79,14 +82,14 @@ void Select::run(int timeout_ms) {
}
for (auto &it : fds_) {
- int native_fd = it.fd_ref.get_native_fd();
- Fd::Flags fd_flags = it.fd_ref.get_flags();
- if ((it.flags & Fd::Write) && !(fd_flags & Fd::Write)) {
+ int native_fd = it.fd.native_fd().fd();
+ PollFlags fd_flags = it.fd.get_flags_unsafe(); // concurrent calls are UB
+ if (it.flags.can_write() && !fd_flags.can_write()) {
FD_SET(native_fd, &write_fd_);
} else {
FD_CLR(native_fd, &write_fd_);
}
- if ((it.flags & Fd::Read) && !(fd_flags & Fd::Read)) {
+ if (it.flags.can_read() && !fd_flags.can_read()) {
FD_SET(native_fd, &read_fd_);
} else {
FD_CLR(native_fd, &read_fd_);
@@ -96,20 +99,18 @@ void Select::run(int timeout_ms) {
select(max_fd_ + 1, &read_fd_, &write_fd_, &except_fd_, timeout_ptr);
for (auto &it : fds_) {
- int native_fd = it.fd_ref.get_native_fd();
- Fd::Flags flags = 0;
+ int native_fd = it.fd.native_fd().fd();
+ PollFlags flags;
if (FD_ISSET(native_fd, &read_fd_)) {
- flags |= Fd::Read;
+ flags = flags | PollFlags::Read();
}
if (FD_ISSET(native_fd, &write_fd_)) {
- flags |= Fd::Write;
+ flags = flags | PollFlags::Write();
}
if (FD_ISSET(native_fd, &except_fd_)) {
- flags |= Fd::Error;
- }
- if (flags != 0) {
- it.fd_ref.update_flags_notify(flags);
+ flags = flags | PollFlags::Error();
}
+ it.fd.add_flags(flags);
}
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.h
index 17f2876f3a..cc8d6a3bc0 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,8 +11,9 @@
#ifdef TD_POLL_SELECT
#include "td/utils/common.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/PollBase.h"
+#include "td/utils/port/PollFlags.h"
#include <sys/select.h>
@@ -26,24 +27,28 @@ class Select final : public PollBase {
Select &operator=(const Select &) = delete;
Select(Select &&) = delete;
Select &operator=(Select &&) = delete;
- ~Select() override = default;
+ ~Select() final = default;
- void init() override;
+ void init() final;
- void clear() override;
+ void clear() final;
- void subscribe(const Fd &fd, Fd::Flags flags) override;
+ void subscribe(PollableFd fd, PollFlags flags) final;
- void unsubscribe(const Fd &fd) override;
+ void unsubscribe(PollableFdRef fd) final;
- void unsubscribe_before_close(const Fd &fd) override;
+ void unsubscribe_before_close(PollableFdRef fd) final;
- void run(int timeout_ms) override;
+ void run(int timeout_ms) final;
+
+ static bool is_edge_triggered() {
+ return false;
+ }
private:
struct FdInfo {
- Fd fd_ref;
- Fd::Flags flags;
+ PollableFd fd;
+ PollFlags flags;
};
vector<FdInfo> fds_;
fd_set all_fd_;
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.cpp
index d949945e1d..4b6366980e 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.cpp
@@ -1,16 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/port/detail/ThreadIdGuard.h"
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
+#include "td/utils/ExitGuard.h"
#include "td/utils/port/thread_local.h"
-#include <array>
#include <mutex>
+#include <set>
namespace td {
namespace detail {
@@ -18,34 +19,37 @@ class ThreadIdManager {
public:
int32 register_thread() {
std::lock_guard<std::mutex> guard(mutex_);
- for (size_t i = 0; i < is_id_used_.size(); i++) {
- if (!is_id_used_[i]) {
- is_id_used_[i] = true;
- return static_cast<int32>(i + 1);
- }
+ if (unused_thread_ids_.empty()) {
+ return ++max_thread_id_;
}
- LOG(FATAL) << "Cannot create more than " << max_thread_count() << " threads";
- return 0;
+ auto it = unused_thread_ids_.begin();
+ auto result = *it;
+ unused_thread_ids_.erase(it);
+ return result;
}
void unregister_thread(int32 thread_id) {
- thread_id--;
std::lock_guard<std::mutex> guard(mutex_);
- CHECK(is_id_used_.at(thread_id));
- is_id_used_[thread_id] = false;
+ CHECK(0 < thread_id && thread_id <= max_thread_id_);
+ bool is_inserted = unused_thread_ids_.insert(thread_id).second;
+ CHECK(is_inserted);
}
private:
std::mutex mutex_;
- std::array<bool, max_thread_count()> is_id_used_{{false}};
+ std::set<int32> unused_thread_ids_;
+ int32 max_thread_id_ = 0;
};
static ThreadIdManager thread_id_manager;
+static ExitGuard exit_guard;
ThreadIdGuard::ThreadIdGuard() {
thread_id_ = thread_id_manager.register_thread();
set_thread_id(thread_id_);
}
ThreadIdGuard::~ThreadIdGuard() {
- thread_id_manager.unregister_thread(thread_id_);
+ if (!ExitGuard::is_exited()) {
+ thread_id_manager.unregister_thread(thread_id_);
+ }
set_thread_id(0);
}
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.h
index 434bd5ac4d..b05d9a62a2 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.cpp
new file mode 100644
index 0000000000..3fa0d19389
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.cpp
@@ -0,0 +1,217 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/detail/ThreadPthread.h"
+
+char disable_linker_warning_about_empty_file_thread_pthread_cpp TD_UNUSED;
+
+#if TD_THREAD_PTHREAD
+
+#include "td/utils/misc.h"
+#include "td/utils/port/detail/skip_eintr.h"
+#if TD_NETBSD
+#include "td/utils/ScopeGuard.h"
+#endif
+
+#include <pthread.h>
+#if TD_FREEBSD
+#include <pthread_np.h>
+#endif
+#include <sched.h>
+#include <signal.h>
+#if TD_FREEBSD
+#include <sys/cpuset.h>
+#endif
+#if TD_FREEBSD || TD_OPENBSD || TD_NETBSD
+#include <sys/sysctl.h>
+#endif
+#include <unistd.h>
+
+namespace td {
+namespace detail {
+unsigned ThreadPthread::hardware_concurrency() {
+// Linux and macOS
+#if defined(_SC_NPROCESSORS_ONLN)
+ {
+ auto res = sysconf(_SC_NPROCESSORS_ONLN);
+ if (res > 0) {
+ return narrow_cast<unsigned>(res);
+ }
+ }
+#endif
+
+#if TD_FREEBSD || TD_OPENBSD || TD_NETBSD
+#if defined(HW_AVAILCPU) && defined(CTL_HW)
+ {
+ int mib[2] = {CTL_HW, HW_AVAILCPU};
+ int res{0};
+ size_t len = sizeof(res);
+ if (sysctl(mib, 2, &res, &len, nullptr, 0) == 0 && res != 0) {
+ return res;
+ }
+ }
+#endif
+
+#if defined(HW_NCPU) && defined(CTL_HW)
+ {
+ int mib[2] = {CTL_HW, HW_NCPU};
+ int res{0};
+ size_t len = sizeof(res);
+ if (sysctl(mib, 2, &res, &len, nullptr, 0) == 0 && res != 0) {
+ return res;
+ }
+ }
+#endif
+#endif
+
+ // Just in case
+ return 8;
+}
+
+void ThreadPthread::set_name(CSlice name) {
+#if defined(_GNU_SOURCE) && defined(__GLIBC_PREREQ)
+#if __GLIBC_PREREQ(2, 12)
+ pthread_setname_np(thread_, name.c_str());
+#endif
+#endif
+}
+
+void ThreadPthread::join() {
+ if (is_inited_.get()) {
+ is_inited_ = false;
+ pthread_join(thread_, nullptr);
+ }
+}
+
+void ThreadPthread::detach() {
+ if (is_inited_.get()) {
+ is_inited_ = false;
+ pthread_detach(thread_);
+ }
+}
+
+void ThreadPthread::send_real_time_signal(id thread_id, int real_time_signal_number) {
+#ifdef SIGRTMIN
+ pthread_kill(thread_id, SIGRTMIN + real_time_signal_number);
+#endif
+}
+
+int ThreadPthread::do_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *),
+ void *arg) {
+ return pthread_create(thread, attr, start_routine, arg);
+}
+
+#if TD_HAVE_THREAD_AFFINITY
+Status ThreadPthread::set_affinity_mask(id thread_id, uint64 mask) {
+#if TD_LINUX || TD_FREEBSD
+#if TD_FREEBSD
+ cpuset_t cpuset;
+#else
+ cpu_set_t cpuset;
+#endif
+ CPU_ZERO(&cpuset);
+ for (int j = 0; j < 64 && j < CPU_SETSIZE; j++) {
+ if ((mask >> j) & 1) {
+ CPU_SET(j, &cpuset);
+ }
+ }
+
+ auto res = skip_eintr([&] { return pthread_setaffinity_np(thread_id, sizeof(cpuset), &cpuset); });
+ if (res) {
+ return OS_ERROR("Failed to set thread affinity mask");
+ }
+ return Status::OK();
+#elif TD_NETBSD
+ cpuset_t *cpuset = cpuset_create();
+ if (cpuset == nullptr) {
+ return OS_ERROR("Failed to create cpuset");
+ }
+ SCOPE_EXIT {
+ cpuset_destroy(cpuset);
+ };
+ for (int j = 0; j < 64; j++) {
+ if ((mask >> j) & 1) {
+ if (cpuset_set(j, cpuset) != 0) {
+ return OS_ERROR("Failed to set CPU identifier");
+ }
+ }
+ }
+
+ auto res = skip_eintr([&] { return pthread_setaffinity_np(thread_id, cpuset_size(cpuset), cpuset); });
+ if (res) {
+ return OS_ERROR("Failed to set thread affinity mask");
+ }
+ if (get_affinity_mask(thread_id) != mask) {
+ return Status::Error("Failed to set exact thread affinity mask");
+ }
+ return Status::OK();
+#else
+ return Status::Error("Unsupported");
+#endif
+}
+
+uint64 ThreadPthread::get_affinity_mask(id thread_id) {
+#if TD_LINUX || TD_FREEBSD
+#if TD_FREEBSD
+ cpuset_t cpuset;
+#else
+ cpu_set_t cpuset;
+#endif
+ CPU_ZERO(&cpuset);
+ auto res = skip_eintr([&] { return pthread_getaffinity_np(thread_id, sizeof(cpuset), &cpuset); });
+ if (res) {
+ return 0;
+ }
+
+ uint64 mask = 0;
+ for (int j = 0; j < 64 && j < CPU_SETSIZE; j++) {
+ if (CPU_ISSET(j, &cpuset)) {
+ mask |= static_cast<uint64>(1) << j;
+ }
+ }
+ return mask;
+#elif TD_NETBSD
+ cpuset_t *cpuset = cpuset_create();
+ if (cpuset == nullptr) {
+ return 0;
+ }
+ SCOPE_EXIT {
+ cpuset_destroy(cpuset);
+ };
+ auto res = skip_eintr([&] { return pthread_getaffinity_np(thread_id, cpuset_size(cpuset), cpuset); });
+ if (res) {
+ return 0;
+ }
+
+ uint64 mask = 0;
+ for (int j = 0; j < 64; j++) {
+ if (cpuset_isset(j, cpuset) > 0) {
+ mask |= static_cast<uint64>(1) << j;
+ }
+ }
+ if (mask == 0) {
+ // the mask wasn't set, all CPUs are allowed
+ auto proc_count = sysconf(_SC_NPROCESSORS_ONLN);
+ for (int j = 0; j < 64 && j < proc_count; j++) {
+ mask |= static_cast<uint64>(1) << j;
+ }
+ }
+ return mask;
+#else
+ return 0;
+#endif
+}
+#endif
+
+namespace this_thread_pthread {
+ThreadPthread::id get_id() {
+ return pthread_self();
+}
+} // namespace this_thread_pthread
+
+} // namespace detail
+} // namespace td
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.h
index e42efc3771..b938b58c7e 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.h
@@ -1,89 +1,112 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/utils/port/config.h"
#ifdef TD_THREAD_PTHREAD
#include "td/utils/common.h"
+#include "td/utils/Destructor.h"
#include "td/utils/invoke.h"
#include "td/utils/MovableValue.h"
#include "td/utils/port/detail/ThreadIdGuard.h"
#include "td/utils/port/thread_local.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
#include <tuple>
#include <type_traits>
#include <utility>
+#if TD_OPENBSD || TD_SOLARIS
#include <pthread.h>
-#include <sched.h>
+#endif
+#include <sys/types.h>
+
+#if TD_LINUX || TD_FREEBSD || TD_NETBSD
+#define TD_HAVE_THREAD_AFFINITY 1
+#endif
namespace td {
namespace detail {
+
class ThreadPthread {
public:
ThreadPthread() = default;
ThreadPthread(const ThreadPthread &other) = delete;
ThreadPthread &operator=(const ThreadPthread &other) = delete;
- ThreadPthread(ThreadPthread &&) = default;
- ThreadPthread &operator=(ThreadPthread &&) = default;
+ ThreadPthread(ThreadPthread &&other) noexcept : is_inited_(std::move(other.is_inited_)), thread_(other.thread_) {
+ }
+ ThreadPthread &operator=(ThreadPthread &&other) noexcept {
+ join();
+ is_inited_ = std::move(other.is_inited_);
+ thread_ = other.thread_;
+ return *this;
+ }
template <class Function, class... Args>
- explicit ThreadPthread(Function &&f, Args &&... args) {
- func_ = std::make_unique<std::unique_ptr<Destructor>>(
- create_destructor([args = std::make_tuple(decay_copy(std::forward<Function>(f)),
- decay_copy(std::forward<Args>(args))...)]() mutable {
- invoke_tuple(std::move(args));
- clear_thread_locals();
- }));
- pthread_create(&thread_, nullptr, run_thread, func_.get());
+ explicit ThreadPthread(Function &&f, Args &&...args) {
+ auto func = create_destructor([args = std::make_tuple(decay_copy(std::forward<Function>(f)),
+ decay_copy(std::forward<Args>(args))...)]() mutable {
+ invoke_tuple(std::move(args));
+ clear_thread_locals();
+ });
+ do_pthread_create(&thread_, nullptr, run_thread, func.release());
is_inited_ = true;
}
- void join() {
- if (is_inited_.get()) {
- is_inited_ = false;
- pthread_join(thread_, nullptr);
- }
- }
~ThreadPthread() {
join();
}
- static unsigned hardware_concurrency() {
- return 8;
- }
+ void set_name(CSlice name);
+
+ void join();
+
+ void detach();
+
+ static unsigned hardware_concurrency();
using id = pthread_t;
+ id get_id() noexcept {
+ return thread_;
+ }
+
+ static void send_real_time_signal(id thread_id, int real_time_signal_number);
+
+#if TD_HAVE_THREAD_AFFINITY
+ static Status set_affinity_mask(id thread_id, uint64 mask);
+
+ static uint64 get_affinity_mask(id thread_id);
+#endif
+
private:
MovableValue<bool> is_inited_;
pthread_t thread_;
- std::unique_ptr<std::unique_ptr<Destructor>> func_;
template <class T>
std::decay_t<T> decay_copy(T &&v) {
return std::forward<T>(v);
}
+ static int do_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *),
+ void *arg);
+
static void *run_thread(void *ptr) {
ThreadIdGuard thread_id_guard;
- auto func = static_cast<decltype(func_.get())>(ptr);
- func->reset();
+ auto func = unique_ptr<Destructor>(static_cast<Destructor *>(ptr));
return nullptr;
}
};
namespace this_thread_pthread {
-inline void yield() {
- sched_yield();
-}
-inline ThreadPthread::id get_id() {
- return pthread_self();
-}
+ThreadPthread::id get_id();
} // namespace this_thread_pthread
+
} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadStl.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadStl.h
index 64bf3213cf..06dec7ccc0 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadStl.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadStl.h
@@ -1,26 +1,37 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/utils/port/config.h"
#ifdef TD_THREAD_STL
#include "td/utils/common.h"
#include "td/utils/invoke.h"
+#if TD_WINDOWS && TD_MSVC
+#include "td/utils/port/detail/NativeFd.h"
+#endif
#include "td/utils/port/detail/ThreadIdGuard.h"
#include "td/utils/port/thread_local.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
#include <thread>
#include <tuple>
#include <type_traits>
#include <utility>
+#if TD_WINDOWS && TD_MSVC
+#define TD_HAVE_THREAD_AFFINITY 1
+#endif
+
namespace td {
namespace detail {
+
class ThreadStl {
public:
ThreadStl() = default;
@@ -28,9 +39,12 @@ class ThreadStl {
ThreadStl &operator=(const ThreadStl &other) = delete;
ThreadStl(ThreadStl &&) = default;
ThreadStl &operator=(ThreadStl &&) = default;
- ~ThreadStl() = default;
+ ~ThreadStl() {
+ join();
+ }
+
template <class Function, class... Args>
- explicit ThreadStl(Function &&f, Args &&... args) {
+ explicit ThreadStl(Function &&f, Args &&...args) {
thread_ = std::thread([args = std::make_tuple(decay_copy(std::forward<Function>(f)),
decay_copy(std::forward<Args>(args))...)]() mutable {
ThreadIdGuard thread_id_guard;
@@ -40,14 +54,79 @@ class ThreadStl {
}
void join() {
- thread_.join();
+ if (thread_.joinable()) {
+ thread_.join();
+ }
+ }
+
+ void detach() {
+ if (thread_.joinable()) {
+ thread_.detach();
+ }
+ }
+
+ void set_name(CSlice name) {
+ // not supported
}
static unsigned hardware_concurrency() {
return std::thread::hardware_concurrency();
}
+#if TD_WINDOWS && TD_MSVC
+ using id = DWORD;
+#else
using id = std::thread::id;
+#endif
+
+ id get_id() noexcept {
+#if TD_WINDOWS && TD_MSVC
+ static_assert(std::is_same<decltype(thread_.native_handle()), HANDLE>::value,
+ "Expected HANDLE as native thread type");
+ return GetThreadId(thread_.native_handle());
+#else
+ return thread_.get_id();
+#endif
+ }
+
+ static void send_real_time_signal(id thread_id, int real_time_signal_number) {
+ // not supported
+ }
+
+#if TD_HAVE_THREAD_AFFINITY
+ static Status set_affinity_mask(id thread_id, uint64 mask) {
+ if (static_cast<DWORD_PTR>(mask) != mask) {
+ return Status::Error("Invalid thread affinity mask specified");
+ }
+ auto handle = OpenThread(THREAD_SET_LIMITED_INFORMATION | THREAD_QUERY_LIMITED_INFORMATION, FALSE, thread_id);
+ if (handle == nullptr) {
+ return Status::Error("Failed to access thread");
+ }
+ NativeFd thread_handle(handle);
+ if (SetThreadAffinityMask(thread_handle.fd(), static_cast<DWORD_PTR>(mask))) {
+ return Status::OK();
+ }
+ return OS_ERROR("Failed to set thread affinity mask");
+ }
+
+ static uint64 get_affinity_mask(id thread_id) {
+ DWORD_PTR process_mask = 0;
+ DWORD_PTR system_mask = 0;
+ if (GetProcessAffinityMask(GetCurrentProcess(), &process_mask, &system_mask)) {
+ auto handle = OpenThread(THREAD_SET_LIMITED_INFORMATION | THREAD_QUERY_LIMITED_INFORMATION, FALSE, thread_id);
+ if (handle == nullptr) {
+ return 0;
+ }
+ NativeFd thread_handle(handle);
+ auto result = SetThreadAffinityMask(thread_handle.fd(), process_mask);
+ if (result != 0 && result != process_mask) {
+ SetThreadAffinityMask(thread_handle.fd(), result);
+ }
+ return result;
+ }
+ return 0;
+ }
+#endif
private:
std::thread thread_;
@@ -57,7 +136,17 @@ class ThreadStl {
return std::forward<T>(v);
}
};
-namespace this_thread_stl = std::this_thread;
+
+namespace this_thread_stl {
+#if TD_WINDOWS && TD_MSVC
+inline ThreadStl::id get_id() {
+ return GetCurrentThreadId();
+}
+#else
+using std::this_thread::get_id;
+#endif
+} // namespace this_thread_stl
+
} // namespace detail
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.cpp
index 8f443d29ab..6f40ee4a13 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,84 +11,30 @@ char disable_linker_warning_about_empty_file_wineventpoll_cpp TD_UNUSED;
#ifdef TD_POLL_WINEVENT
#include "td/utils/common.h"
-#include "td/utils/logging.h"
-#include "td/utils/misc.h"
-#include "td/utils/port/Fd.h"
-#include "td/utils/port/PollBase.h"
-#include "td/utils/port/sleep.h"
-#include "td/utils/Status.h"
-
-#include <utility>
namespace td {
namespace detail {
void WineventPoll::init() {
- clear();
}
void WineventPoll::clear() {
- fds_.clear();
}
-void WineventPoll::subscribe(const Fd &fd, Fd::Flags flags) {
- for (auto &it : fds_) {
- if (it.fd_ref.get_key() == fd.get_key()) {
- it.flags = flags;
- return;
- }
- }
- fds_.push_back({fd.clone(), flags});
+void WineventPoll::subscribe(PollableFd fd, PollFlags flags) {
+ fd.release_as_list_node();
}
-void WineventPoll::unsubscribe(const Fd &fd) {
- for (auto it = fds_.begin(); it != fds_.end(); ++it) {
- if (it->fd_ref.get_key() == fd.get_key()) {
- std::swap(*it, fds_.back());
- fds_.pop_back();
- return;
- }
- }
+void WineventPoll::unsubscribe(PollableFdRef fd) {
+ auto pollable_fd = fd.lock(); // unlocked in destructor
}
-void WineventPoll::unsubscribe_before_close(const Fd &fd) {
- unsubscribe(fd);
+void WineventPoll::unsubscribe_before_close(PollableFdRef fd) {
+ unsubscribe(std::move(fd));
}
void WineventPoll::run(int timeout_ms) {
- vector<std::pair<size_t, Fd::Flag>> events_desc;
- vector<HANDLE> events;
- for (size_t i = 0; i < fds_.size(); i++) {
- auto &fd_info = fds_[i];
- if (fd_info.flags & Fd::Flag::Write) {
- events_desc.emplace_back(i, Fd::Flag::Write);
- events.push_back(fd_info.fd_ref.get_write_event());
- }
- if (fd_info.flags & Fd::Flag::Read) {
- events_desc.emplace_back(i, Fd::Flag::Read);
- events.push_back(fd_info.fd_ref.get_read_event());
- }
- }
- if (events.empty()) {
- usleep_for(timeout_ms * 1000);
- return;
- }
-
- auto status = WaitForMultipleObjects(narrow_cast<DWORD>(events.size()), events.data(), false, timeout_ms);
- if (status == WAIT_FAILED) {
- auto error = OS_ERROR("WaitForMultipleObjects failed");
- LOG(FATAL) << events.size() << " " << timeout_ms << " " << error;
- }
- for (size_t i = 0; i < events.size(); i++) {
- if (WaitForSingleObject(events[i], 0) == WAIT_OBJECT_0) {
- auto &fd = fds_[events_desc[i].first].fd_ref;
- if (events_desc[i].second == Fd::Flag::Read) {
- fd.on_read_event();
- } else {
- fd.on_write_event();
- }
- }
- }
+ UNREACHABLE();
}
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.h
index ecc93f33fa..f68bbac48c 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -11,8 +11,9 @@
#ifdef TD_POLL_WINEVENT
#include "td/utils/common.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/PollBase.h"
+#include "td/utils/port/PollFlags.h"
namespace td {
namespace detail {
@@ -24,26 +25,23 @@ class WineventPoll final : public PollBase {
WineventPoll &operator=(const WineventPoll &) = delete;
WineventPoll(WineventPoll &&) = delete;
WineventPoll &operator=(WineventPoll &&) = delete;
- ~WineventPoll() override = default;
+ ~WineventPoll() final = default;
- void init() override;
+ void init() final;
- void clear() override;
+ void clear() final;
- void subscribe(const Fd &fd, Fd::Flags flags) override;
+ void subscribe(PollableFd fd, PollFlags flags) final;
- void unsubscribe(const Fd &fd) override;
+ void unsubscribe(PollableFdRef fd) final;
- void unsubscribe_before_close(const Fd &fd) override;
+ void unsubscribe_before_close(PollableFdRef fd) final;
- void run(int timeout_ms) override;
+ void run(int timeout_ms) final;
- private:
- struct FdInfo {
- Fd fd_ref;
- Fd::Flags flags;
- };
- vector<FdInfo> fds_;
+ static bool is_edge_triggered() {
+ return true;
+ }
};
} // namespace detail
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/skip_eintr.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/skip_eintr.h
new file mode 100644
index 0000000000..6fde635680
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/skip_eintr.h
@@ -0,0 +1,63 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#if TD_PORT_POSIX
+#include "td/utils/common.h"
+#include "td/utils/Time.h"
+
+#include <cerrno>
+#include <type_traits>
+#endif
+
+namespace td {
+
+#if TD_PORT_POSIX
+namespace detail {
+template <class F>
+auto skip_eintr(F &&f) {
+ decltype(f()) res;
+ static_assert(std::is_integral<decltype(res)>::value, "integral type expected");
+ do {
+ errno = 0; // just in case
+ res = f();
+ } while (res < 0 && errno == EINTR);
+ return res;
+}
+
+template <class F>
+auto skip_eintr_cstr(F &&f) {
+ char *res;
+ do {
+ errno = 0; // just in case
+ res = f();
+ } while (res == nullptr && errno == EINTR);
+ return res;
+}
+
+template <class F>
+auto skip_eintr_timeout(F &&f, int32 timeout_ms) {
+ decltype(f(timeout_ms)) res;
+ static_assert(std::is_integral<decltype(res)>::value, "integral type expected");
+
+ auto start = Timestamp::now();
+ auto left_timeout_ms = timeout_ms;
+ while (true) {
+ errno = 0; // just in case
+ res = f(left_timeout_ms);
+ if (res >= 0 || errno != EINTR) {
+ break;
+ }
+ left_timeout_ms =
+ static_cast<int32>(td::max((start.at() - Timestamp::now().at()) * 1000 + timeout_ms + 1 - 1e-9, 0.0));
+ }
+ return res;
+}
+} // namespace detail
+#endif
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.cpp
index 8b169fefa4..2f77457ac4 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.cpp
@@ -1,19 +1,28 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/port/path.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/config.h"
-#if TD_WINDOWS
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/detail/skip_eintr.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
+
+#if TD_PORT_WINDOWS
+#include "td/utils/port/FromApp.h"
+#include "td/utils/port/wstring_convert.h"
#include "td/utils/Random.h"
#endif
#if TD_PORT_POSIX
+#include <dirent.h>
#include <limits.h>
#include <stdio.h>
@@ -32,7 +41,13 @@
#endif
+#if TD_DARWIN
+#include <sys/syslimits.h>
+#endif
+
+#include <cerrno>
#include <cstdlib>
+#include <string>
namespace td {
@@ -44,8 +59,7 @@ Status set_temporary_dir(CSlice dir) {
input_dir += TD_DIR_SLASH;
}
TRY_STATUS(mkpath(input_dir, 0750));
- TRY_RESULT(real_dir, realpath(input_dir));
- temporary_dir = std::move(real_dir);
+ TRY_RESULT_ASSIGN(temporary_dir, realpath(input_dir));
return Status::OK();
}
@@ -54,22 +68,47 @@ Status mkpath(CSlice path, int32 mode) {
Status last_error = Status::OK();
for (size_t i = 1; i < path.size(); i++) {
if (path[i] == TD_DIR_SLASH) {
- last_error = mkdir(path.substr(0, i).str(), mode);
+ last_error = mkdir(PSLICE() << path.substr(0, i), mode);
if (last_error.is_error() && first_error.is_ok()) {
first_error = last_error.clone();
}
}
}
if (last_error.is_error()) {
- return first_error;
+ if (last_error.message() == first_error.message() && last_error.code() == first_error.code()) {
+ return first_error;
+ }
+ return last_error.move_as_error_suffix(PSLICE() << ": " << first_error);
}
return Status::OK();
}
+Status rmrf(CSlice path) {
+ return walk_path(path, [](CSlice path, WalkPath::Type type) {
+ switch (type) {
+ case WalkPath::Type::EnterDir:
+ break;
+ case WalkPath::Type::ExitDir:
+ rmdir(path).ignore();
+ break;
+ case WalkPath::Type::NotDir:
+ unlink(path).ignore();
+ break;
+ }
+ });
+}
+
#if TD_PORT_POSIX
Status mkdir(CSlice dir, int32 mode) {
- int mkdir_res = skip_eintr([&] { return ::mkdir(dir.c_str(), static_cast<mode_t>(mode)); });
+ int mkdir_res = [&] {
+ int res;
+ do {
+ errno = 0; // just in case
+ res = ::mkdir(dir.c_str(), static_cast<mode_t>(mode));
+ } while (res < 0 && (errno == EINTR || errno == EAGAIN));
+ return res;
+ }();
if (mkdir_res == 0) {
return Status::OK();
}
@@ -82,7 +121,7 @@ Status mkdir(CSlice dir, int32 mode) {
}
Status rename(CSlice from, CSlice to) {
- int rename_res = skip_eintr([&] { return ::rename(from.c_str(), to.c_str()); });
+ int rename_res = detail::skip_eintr([&] { return ::rename(from.c_str(), to.c_str()); });
if (rename_res < 0) {
return OS_ERROR(PSLICE() << "Can't rename \"" << from << "\" to \"" << to << '\"');
}
@@ -92,9 +131,9 @@ Status rename(CSlice from, CSlice to) {
Result<string> realpath(CSlice slice, bool ignore_access_denied) {
char full_path[PATH_MAX + 1];
string res;
- char *err = skip_eintr_cstr([&] { return ::realpath(slice.c_str(), full_path); });
+ char *err = detail::skip_eintr_cstr([&] { return ::realpath(slice.c_str(), full_path); });
if (err != full_path) {
- if (ignore_access_denied && errno == EACCES) {
+ if (ignore_access_denied && (errno == EACCES || errno == EPERM)) {
res = slice.str();
} else {
return OS_ERROR(PSLICE() << "Realpath failed for \"" << slice << '"');
@@ -114,7 +153,7 @@ Result<string> realpath(CSlice slice, bool ignore_access_denied) {
}
Status chdir(CSlice dir) {
- int chdir_res = skip_eintr([&] { return ::chdir(dir.c_str()); });
+ int chdir_res = detail::skip_eintr([&] { return ::chdir(dir.c_str()); });
if (chdir_res) {
return OS_ERROR(PSLICE() << "Can't change directory to \"" << dir << '"');
}
@@ -122,7 +161,7 @@ Status chdir(CSlice dir) {
}
Status rmdir(CSlice dir) {
- int rmdir_res = skip_eintr([&] { return ::rmdir(dir.c_str()); });
+ int rmdir_res = detail::skip_eintr([&] { return ::rmdir(dir.c_str()); });
if (rmdir_res) {
return OS_ERROR(PSLICE() << "Can't delete directory \"" << dir << '"');
}
@@ -130,7 +169,7 @@ Status rmdir(CSlice dir) {
}
Status unlink(CSlice path) {
- int unlink_res = skip_eintr([&] { return ::unlink(path.c_str()); });
+ int unlink_res = detail::skip_eintr([&] { return ::unlink(path.c_str()); });
if (unlink_res) {
return OS_ERROR(PSLICE() << "Can't unlink \"" << path << '"');
}
@@ -177,7 +216,7 @@ Result<std::pair<FileFd, string>> mkstemp(CSlice dir) {
}
file_pattern += "tmpXXXXXXXXXX";
- int fd = skip_eintr([&] { return ::mkstemp(&file_pattern[0]); });
+ int fd = detail::skip_eintr([&] { return ::mkstemp(&file_pattern[0]); });
if (fd == -1) {
return OS_ERROR(PSLICE() << "Can't create temporary file \"" << file_pattern << '"');
}
@@ -209,20 +248,159 @@ Result<string> mkdtemp(CSlice dir, Slice prefix) {
dir_pattern.append(prefix.begin(), prefix.size());
dir_pattern += "XXXXXX";
- char *result = skip_eintr_cstr([&] { return ::mkdtemp(&dir_pattern[0]); });
+ char *result = detail::skip_eintr_cstr([&] { return ::mkdtemp(&dir_pattern[0]); });
if (result == nullptr) {
return OS_ERROR(PSLICE() << "Can't create temporary directory \"" << dir_pattern << '"');
}
return result;
}
+namespace detail {
+using WalkFunction = std::function<WalkPath::Action(CSlice name, WalkPath::Type type)>;
+Result<bool> walk_path_dir(string &path, FileFd fd, const WalkFunction &func) TD_WARN_UNUSED_RESULT;
+
+Result<bool> walk_path_dir(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT;
+
+Result<bool> walk_path_file(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT;
+
+Result<bool> walk_path(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT;
+
+Result<bool> walk_path_subdir(string &path, DIR *dir, const WalkFunction &func) {
+ while (true) {
+ errno = 0;
+ auto *entry = readdir(dir);
+ auto readdir_errno = errno;
+ if (readdir_errno) {
+ return Status::PosixError(readdir_errno, "readdir");
+ }
+ if (entry == nullptr) {
+ return true;
+ }
+ Slice name = Slice(static_cast<const char *>(entry->d_name));
+ if (name == "." || name == "..") {
+ continue;
+ }
+ auto size = path.size();
+ if (path.back() != TD_DIR_SLASH) {
+ path += TD_DIR_SLASH;
+ }
+ path.append(name.begin(), name.size());
+ SCOPE_EXIT {
+ path.resize(size);
+ };
+ Result<bool> status = true;
+#ifdef DT_DIR
+ if (entry->d_type == DT_UNKNOWN) {
+ status = walk_path(path, func);
+ } else if (entry->d_type == DT_DIR) {
+ status = walk_path_dir(path, func);
+ } else if (entry->d_type == DT_REG) {
+ status = walk_path_file(path, func);
+ }
+#else
+#if !TD_SOLARIS
+#warning "Slow walk_path"
+#endif
+ status = walk_path(path, func);
+#endif
+ if (status.is_error() || !status.ok()) {
+ return status;
+ }
+ }
+}
+
+Result<bool> walk_path_dir(string &path, DIR *subdir, const WalkFunction &func) {
+ SCOPE_EXIT {
+ closedir(subdir);
+ };
+ switch (func(path, WalkPath::Type::EnterDir)) {
+ case WalkPath::Action::Abort:
+ return false;
+ case WalkPath::Action::SkipDir:
+ return true;
+ case WalkPath::Action::Continue:
+ break;
+ }
+ auto status = walk_path_subdir(path, subdir, func);
+ if (status.is_error() || !status.ok()) {
+ return status;
+ }
+ switch (func(path, WalkPath::Type::ExitDir)) {
+ case WalkPath::Action::Abort:
+ return false;
+ case WalkPath::Action::SkipDir:
+ case WalkPath::Action::Continue:
+ break;
+ }
+ return true;
+}
+
+Result<bool> walk_path_dir(string &path, FileFd fd, const WalkFunction &func) {
+ auto native_fd = fd.move_as_native_fd();
+ auto *subdir = fdopendir(native_fd.fd());
+ if (subdir == nullptr) {
+ return OS_ERROR("fdopendir");
+ }
+ native_fd.release();
+ return walk_path_dir(path, subdir, func);
+}
+
+Result<bool> walk_path_dir(string &path, const WalkFunction &func) {
+ auto *subdir = opendir(path.c_str());
+ if (subdir == nullptr) {
+ return OS_ERROR(PSLICE() << tag("opendir", path));
+ }
+ return walk_path_dir(path, subdir, func);
+}
+
+Result<bool> walk_path_file(string &path, const WalkFunction &func) {
+ switch (func(path, WalkPath::Type::NotDir)) {
+ case WalkPath::Action::Abort:
+ return false;
+ case WalkPath::Action::SkipDir:
+ case WalkPath::Action::Continue:
+ break;
+ }
+ return true;
+}
+
+Result<bool> walk_path(string &path, const WalkFunction &func) {
+ TRY_RESULT(fd, FileFd::open(path, FileFd::Read));
+ TRY_RESULT(stat, fd.stat());
+
+ bool is_dir = stat.is_dir_;
+ bool is_reg = stat.is_reg_;
+ if (is_dir) {
+ return walk_path_dir(path, std::move(fd), func);
+ }
+
+ fd.close();
+ if (is_reg) {
+ return walk_path_file(path, func);
+ }
+
+ return true;
+}
+} // namespace detail
+
+Status WalkPath::do_run(CSlice path, const detail::WalkFunction &func) {
+ string curr_path;
+ curr_path.reserve(PATH_MAX + 10);
+ curr_path = path.c_str();
+ TRY_STATUS(detail::walk_path(curr_path, func));
+ return Status::OK();
+}
+
#endif
#if TD_PORT_WINDOWS
Status mkdir(CSlice dir, int32 mode) {
TRY_RESULT(wdir, to_wstring(dir));
- auto status = CreateDirectoryW(wdir.c_str(), nullptr);
+ while (!wdir.empty() && (wdir.back() == L'/' || wdir.back() == L'\\')) {
+ wdir.pop_back();
+ }
+ auto status = td::CreateDirectoryFromAppW(wdir.c_str(), nullptr);
if (status == 0 && GetLastError() != ERROR_ALREADY_EXISTS) {
return OS_ERROR(PSLICE() << "Can't create directory \"" << dir << '"');
}
@@ -232,7 +410,7 @@ Status mkdir(CSlice dir, int32 mode) {
Status rename(CSlice from, CSlice to) {
TRY_RESULT(wfrom, to_wstring(from));
TRY_RESULT(wto, to_wstring(to));
- auto status = MoveFileExW(wfrom.c_str(), wto.c_str(), MOVEFILE_REPLACE_EXISTING);
+ auto status = td::MoveFileExFromAppW(wfrom.c_str(), wto.c_str(), MOVEFILE_REPLACE_EXISTING);
if (status == 0) {
return OS_ERROR(PSLICE() << "Can't rename \"" << from << "\" to \"" << to << '\"');
}
@@ -251,8 +429,7 @@ Result<string> realpath(CSlice slice, bool ignore_access_denied) {
return OS_ERROR(PSLICE() << "GetFullPathNameW failed for \"" << slice << '"');
}
} else {
- TRY_RESULT(t_res, from_wstring(buf));
- res = std::move(t_res);
+ TRY_RESULT_ASSIGN(res, from_wstring(buf));
}
if (res.empty()) {
return Status::Error("Empty path");
@@ -276,7 +453,7 @@ Status chdir(CSlice dir) {
Status rmdir(CSlice dir) {
TRY_RESULT(wdir, to_wstring(dir));
- int status = RemoveDirectoryW(wdir.c_str());
+ int status = td::RemoveDirectoryFromAppW(wdir.c_str());
if (!status) {
return OS_ERROR(PSLICE() << "Can't delete directory \"" << dir << '"');
}
@@ -285,7 +462,7 @@ Status rmdir(CSlice dir) {
Status unlink(CSlice path) {
TRY_RESULT(wpath, to_wstring(path));
- int status = DeleteFileW(wpath.c_str());
+ int status = td::DeleteFileFromAppW(wpath.c_str());
if (!status) {
return OS_ERROR(PSLICE() << "Can't unlink \"" << path << '"');
}
@@ -302,7 +479,7 @@ CSlice get_temporary_dir() {
}
auto rs = from_wstring(buf);
LOG_IF(FATAL, rs.is_error()) << "GetTempPathW failed: " << rs.error();
- temporary_dir = rs.ok();
+ temporary_dir = rs.move_as_ok();
}
if (temporary_dir.size() > 1 && temporary_dir.back() == TD_DIR_SLASH) {
temporary_dir.pop_back();
@@ -332,9 +509,9 @@ Result<string> mkdtemp(CSlice dir, Slice prefix) {
}
dir_pattern.append(prefix.begin(), prefix.size());
- for (auto it = 0; it < 20; it++) {
+ for (auto iter = 0; iter < 20; iter++) {
auto path = dir_pattern;
- for (int i = 0; i < 6 + it / 5; i++) {
+ for (int i = 0; i < 6 + iter / 5; i++) {
path += static_cast<char>(Random::fast('a', 'z'));
}
auto status = mkdir(path);
@@ -364,9 +541,9 @@ Result<std::pair<FileFd, string>> mkstemp(CSlice dir) {
}
file_pattern += "tmp";
- for (auto it = 0; it < 20; it++) {
+ for (auto iter = 0; iter < 20; iter++) {
auto path = file_pattern;
- for (int i = 0; i < 6 + it / 5; i++) {
+ for (int i = 0; i < 6 + iter / 5; i++) {
path += static_cast<char>(Random::fast('a', 'z'));
}
auto r_file = FileFd::open(path, FileFd::Write | FileFd::Read | FileFd::CreateNew);
@@ -378,6 +555,79 @@ Result<std::pair<FileFd, string>> mkstemp(CSlice dir) {
return Status::Error(PSLICE() << "Can't create temporary file \"" << file_pattern << '"');
}
+static Result<bool> walk_path_dir(const std::wstring &dir_name,
+ const std::function<WalkPath::Action(CSlice name, WalkPath::Type type)> &func) {
+ std::wstring name = dir_name + L"\\*";
+ WIN32_FIND_DATA file_data;
+ auto handle =
+ td::FindFirstFileExFromAppW(name.c_str(), FindExInfoStandard, &file_data, FindExSearchNameMatch, nullptr, 0);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return OS_ERROR(PSLICE() << "FindFirstFileEx" << tag("name", from_wstring(name).ok()));
+ }
+
+ SCOPE_EXIT {
+ FindClose(handle);
+ };
+
+ TRY_RESULT(dir_entry_name, from_wstring(dir_name));
+ switch (func(dir_entry_name, WalkPath::Type::EnterDir)) {
+ case WalkPath::Action::Abort:
+ return false;
+ case WalkPath::Action::SkipDir:
+ return true;
+ case WalkPath::Action::Continue:
+ break;
+ }
+
+ while (true) {
+ auto full_name = dir_name + L"\\" + file_data.cFileName;
+ TRY_RESULT(entry_name, from_wstring(full_name));
+ if (file_data.cFileName[0] != '.') {
+ if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+ TRY_RESULT(is_ok, walk_path_dir(full_name, func));
+ if (!is_ok) {
+ return false;
+ }
+ } else {
+ switch (func(entry_name, WalkPath::Type::NotDir)) {
+ case WalkPath::Action::Abort:
+ return false;
+ case WalkPath::Action::SkipDir:
+ case WalkPath::Action::Continue:
+ break;
+ }
+ }
+ }
+ auto status = FindNextFileW(handle, &file_data);
+ if (status == 0) {
+ auto last_error = GetLastError();
+ if (last_error == ERROR_NO_MORE_FILES) {
+ break;
+ }
+ return OS_ERROR("FindNextFileW");
+ }
+ }
+ switch (func(dir_entry_name, WalkPath::Type::ExitDir)) {
+ case WalkPath::Action::Abort:
+ return false;
+ case WalkPath::Action::SkipDir:
+ case WalkPath::Action::Continue:
+ break;
+ }
+ return true;
+}
+
+Status WalkPath::do_run(CSlice path, const std::function<Action(CSlice name, Type)> &func) {
+ TRY_RESULT(wpath, to_wstring(path));
+ Slice path_slice = path;
+ while (!path_slice.empty() && (path_slice.back() == '/' || path_slice.back() == '\\')) {
+ path_slice.remove_suffix(1);
+ wpath.pop_back();
+ }
+ TRY_STATUS(walk_path_dir(wpath, func));
+ return Status::OK();
+}
+
#endif
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.h
index 47b7d3a350..0ac73ccb31 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.h
@@ -1,225 +1,72 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/port/config.h"
-
#include "td/utils/common.h"
-#include "td/utils/format.h"
-#include "td/utils/logging.h"
#include "td/utils/port/FileFd.h"
-#include "td/utils/ScopeGuard.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
+#include <functional>
+#include <type_traits>
#include <utility>
-#if TD_PORT_POSIX
-#include <dirent.h>
-#include <sys/types.h>
-#endif
-
-#if TD_DARWIN
-#include <sys/syslimits.h>
-#endif
-
-#if TD_PORT_WINDOWS
-#include "td/utils/port/wstring_convert.h"
-
-#include <string>
-#endif
-
namespace td {
Status mkdir(CSlice dir, int32 mode = 0700) TD_WARN_UNUSED_RESULT;
+
Status mkpath(CSlice path, int32 mode = 0700) TD_WARN_UNUSED_RESULT;
+
Status rename(CSlice from, CSlice to) TD_WARN_UNUSED_RESULT;
+
Result<string> realpath(CSlice slice, bool ignore_access_denied = false) TD_WARN_UNUSED_RESULT;
+
Status chdir(CSlice dir) TD_WARN_UNUSED_RESULT;
+
Status rmdir(CSlice dir) TD_WARN_UNUSED_RESULT;
+
Status unlink(CSlice path) TD_WARN_UNUSED_RESULT;
-Status set_temporary_dir(CSlice dir) TD_WARN_UNUSED_RESULT;
-CSlice get_temporary_dir();
-Result<std::pair<FileFd, string>> mkstemp(CSlice dir) TD_WARN_UNUSED_RESULT;
-Result<string> mkdtemp(CSlice dir, Slice prefix) TD_WARN_UNUSED_RESULT;
-template <class Func>
-Status walk_path(CSlice path, Func &func) TD_WARN_UNUSED_RESULT;
-
-#if TD_PORT_POSIX
-
-// TODO move details somewhere else
-namespace detail {
-template <class Func>
-Status walk_path_dir(string &path, FileFd fd, Func &&func) TD_WARN_UNUSED_RESULT;
-template <class Func>
-Status walk_path_dir(string &path, Func &&func) TD_WARN_UNUSED_RESULT;
-template <class Func>
-Status walk_path_file(string &path, Func &&func) TD_WARN_UNUSED_RESULT;
-template <class Func>
-Status walk_path(string &path, Func &&func) TD_WARN_UNUSED_RESULT;
-
-template <class Func>
-Status walk_path_subdir(string &path, DIR *dir, Func &&func) {
- while (true) {
- errno = 0;
- auto *entry = readdir(dir);
- auto readdir_errno = errno;
- if (readdir_errno) {
- return Status::PosixError(readdir_errno, "readdir");
- }
- if (entry == nullptr) {
- return Status::OK();
- }
- Slice name = Slice(&*entry->d_name);
- if (name == "." || name == "..") {
- continue;
- }
- auto size = path.size();
- if (path.back() != TD_DIR_SLASH) {
- path += TD_DIR_SLASH;
- }
- path.append(name.begin(), name.size());
- SCOPE_EXIT {
- path.resize(size);
- };
- Status status;
-#ifdef DT_DIR
- if (entry->d_type == DT_UNKNOWN) {
- status = walk_path(path, std::forward<Func>(func));
- } else if (entry->d_type == DT_DIR) {
- status = walk_path_dir(path, std::forward<Func>(func));
- } else if (entry->d_type == DT_REG) {
- status = walk_path_file(path, std::forward<Func>(func));
- }
-#else
-#warning "Slow walk_path"
- status = walk_path(path, std::forward<Func>(func));
-#endif
- if (status.is_error()) {
- return status;
- }
- }
-}
+Status rmrf(CSlice path) TD_WARN_UNUSED_RESULT;
-template <class Func>
-Status walk_path_dir(string &path, DIR *subdir, Func &&func) {
- SCOPE_EXIT {
- closedir(subdir);
- };
- TRY_STATUS(walk_path_subdir(path, subdir, std::forward<Func>(func)));
- std::forward<Func>(func)(path, true);
- return Status::OK();
-}
+Status set_temporary_dir(CSlice dir) TD_WARN_UNUSED_RESULT;
-template <class Func>
-Status walk_path_dir(string &path, FileFd fd, Func &&func) {
- auto *subdir = fdopendir(fd.get_fd().move_as_native_fd());
- if (subdir == nullptr) {
- auto error = OS_ERROR("fdopendir");
- fd.close();
- return error;
- }
- return walk_path_dir(path, subdir, std::forward<Func>(func));
-}
+CSlice get_temporary_dir();
-template <class Func>
-Status walk_path_dir(string &path, Func &&func) {
- auto *subdir = opendir(path.c_str());
- if (subdir == nullptr) {
- return OS_ERROR(PSLICE() << tag("opendir", path));
- }
- return walk_path_dir(path, subdir, std::forward<Func>(func));
-}
+Result<std::pair<FileFd, string>> mkstemp(CSlice dir) TD_WARN_UNUSED_RESULT;
-template <class Func>
-Status walk_path_file(string &path, Func &&func) {
- std::forward<Func>(func)(path, false);
- return Status::OK();
-}
+Result<string> mkdtemp(CSlice dir, Slice prefix) TD_WARN_UNUSED_RESULT;
-template <class Func>
-Status walk_path(string &path, Func &&func) {
- TRY_RESULT(fd, FileFd::open(path, FileFd::Read));
- auto stat = fd.stat();
- bool is_dir = stat.is_dir_;
- bool is_reg = stat.is_reg_;
- if (is_dir) {
- return walk_path_dir(path, std::move(fd), std::forward<Func>(func));
- }
+class WalkPath {
+ public:
+ enum class Action { Continue, Abort, SkipDir };
+ enum class Type { EnterDir, ExitDir, NotDir };
- fd.close();
- if (is_reg) {
- return walk_path_file(path, std::forward<Func>(func));
+ template <class F, class R = decltype(std::declval<F>()("", Type::ExitDir))>
+ static TD_WARN_UNUSED_RESULT std::enable_if_t<std::is_same<R, Action>::value, Status> run(CSlice path, F &&func) {
+ return do_run(path, func);
}
-
- return Status::OK();
-}
-} // namespace detail
-
-template <class Func>
-Status walk_path(CSlice path, Func &&func) {
- string curr_path;
- curr_path.reserve(PATH_MAX + 10);
- curr_path = path.c_str();
- return detail::walk_path(curr_path, std::forward<Func>(func));
-}
-
-#endif
-
-#if TD_PORT_WINDOWS
-
-namespace detail {
-template <class Func>
-Status walk_path_dir(const std::wstring &dir_name, Func &&func) {
- std::wstring name = dir_name + L"\\*";
-
- WIN32_FIND_DATA file_data;
- auto handle = FindFirstFileExW(name.c_str(), FindExInfoStandard, &file_data, FindExSearchNameMatch, nullptr, 0);
- if (handle == INVALID_HANDLE_VALUE) {
- return OS_ERROR(PSLICE() << "FindFirstFileEx" << tag("name", from_wstring(name).ok()));
+ template <class F, class R = decltype(std::declval<F>()("", Type::ExitDir))>
+ static TD_WARN_UNUSED_RESULT std::enable_if_t<!std::is_same<R, Action>::value, Status> run(CSlice path, F &&func) {
+ return do_run(path, [&](CSlice name, Type type) {
+ func(name, type);
+ return Action::Continue;
+ });
}
- SCOPE_EXIT {
- FindClose(handle);
- };
- while (true) {
- auto full_name = dir_name + L"\\" + file_data.cFileName;
- TRY_RESULT(entry_name, from_wstring(full_name));
- if (file_data.cFileName[0] != '.') {
- if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
- TRY_STATUS(walk_path_dir(full_name, func));
- func(entry_name, true);
- } else {
- func(entry_name, false);
- }
- }
- auto status = FindNextFileW(handle, &file_data);
- if (status == 0) {
- auto last_error = GetLastError();
- if (last_error == ERROR_NO_MORE_FILES) {
- return Status::OK();
- }
- return OS_ERROR("FindNextFileW");
- }
- }
-}
-} // namespace detail
+ private:
+ static TD_WARN_UNUSED_RESULT Status do_run(CSlice path,
+ const std::function<WalkPath::Action(CSlice name, Type type)> &func);
+};
-template <class Func>
-Status walk_path(CSlice path, Func &&func) {
- Slice path_slice = path;
- while (!path_slice.empty() && (path_slice.back() == '/' || path_slice.back() == '\\')) {
- path_slice.remove_suffix(1);
- }
- TRY_RESULT(wpath, to_wstring(path_slice));
- return detail::walk_path_dir(wpath.c_str(), func);
+// deprecated interface
+template <class F>
+TD_WARN_UNUSED_RESULT Status walk_path(CSlice path, F &&func) {
+ return WalkPath::run(path, func);
}
-#endif
-
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.cpp
new file mode 100644
index 0000000000..9fdd36528e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.cpp
@@ -0,0 +1,25 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/platform.h"
+
+char disable_linker_warning_about_empty_file_platform_cpp TD_UNUSED;
+
+#if !TD_MSVC
+
+#if TD_DARWIN_UNKNOWN
+#warning "Probably unsupported Apple operating system. Feel free to try to compile"
+#endif
+
+#if TD_UNIX_UNKNOWN
+#warning "Probably unsupported Unix operating system. Feel free to try to compile"
+#endif
+
+#if TD_COMPILER_UNKNOWN
+#warning "Probably unsupported compiler. Feel free to try to compile"
+#endif
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.h
index a1c3776a40..0d896ff3c2 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,7 +9,7 @@
// clang-format off
/*** Platform macros ***/
-#if defined(_WIN32)
+#if defined(_WIN32) || defined(_WINDOWS) // _WINDOWS is defined by CMake
#if defined(__cplusplus_winrt)
#define TD_WINRT 1
#endif
@@ -20,7 +20,7 @@
#elif defined(__APPLE__)
#include "TargetConditionals.h"
#if TARGET_OS_IPHONE
- // iOS/Apple Watch OS/Apple TV OS
+ // iOS/iPadOS/watchOS/tvOS
#if TARGET_OS_IOS
#define TD_DARWIN_IOS 1
#elif TARGET_OS_TV
@@ -28,13 +28,13 @@
#elif TARGET_OS_WATCH
#define TD_DARWIN_WATCH_OS 1
#else
- #warning "Probably unsupported Apple iPhone platform. Feel free to try to compile"
+ #define TD_DARWIN_UNKNOWN 1
#endif
#elif TARGET_OS_MAC
- // Other kinds of Mac OS
+ // Other kinds of macOS
#define TD_DARWIN_MAC 1
#else
- #warning "Probably unsupported Apple platform. Feel free to try to compile"
+ #define TD_DARWIN_UNKNOWN 1
#endif
#define TD_DARWIN 1
#elif defined(ANDROID) || defined(__ANDROID__)
@@ -43,12 +43,21 @@
#define TD_TIZEN 1
#elif defined(__linux__)
#define TD_LINUX 1
+#elif defined(__FreeBSD__)
+ #define TD_FREEBSD 1
+#elif defined(__OpenBSD__)
+ #define TD_OPENBSD 1
+#elif defined(__NetBSD__)
+ #define TD_NETBSD 1
#elif defined(__CYGWIN__)
#define TD_CYGWIN 1
#elif defined(__EMSCRIPTEN__)
#define TD_EMSCRIPTEN 1
+#elif defined(__sun)
+ #define TD_SOLARIS 1
+ // TD_ILLUMOS can be already defined by CMake
#elif defined(__unix__) // all unices not caught above
- #warning "Probably unsupported Unix platform. Feel free to try to compile"
+ #define TD_UNIX_UNKNOWN 1
#define TD_CYGWIN 1
#else
#error "Probably unsupported platform. Feel free to remove the error and try to recompile"
@@ -63,7 +72,7 @@
#elif defined(_MSC_VER)
#define TD_MSVC 1
#else
- #warning "Probably unsupported compiler. Feel free to try to compile"
+ #define TD_COMPILER_UNKNOWN 1
#endif
#if TD_GCC || TD_CLANG || TD_INTEL
@@ -103,4 +112,8 @@
#define TD_CONCURRENCY_PAD 128
+#if !TD_WINDOWS && defined(__SIZEOF_INT128__)
+#define TD_HAVE_INT128 1
+#endif
+
// clang-format on
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.cpp
new file mode 100644
index 0000000000..8bc94dbfa2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.cpp
@@ -0,0 +1,97 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/rlimit.h"
+
+#include "td/utils/port/config.h"
+
+#include "td/utils/misc.h"
+
+#if TD_PORT_POSIX
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#endif
+
+namespace td {
+
+#if TD_PORT_POSIX
+static int get_resource(ResourceLimitType type) {
+ switch (type) {
+ case ResourceLimitType::NoFile:
+ return RLIMIT_NOFILE;
+ default:
+ UNREACHABLE();
+ return -1;
+ }
+}
+#endif
+
+Status set_resource_limit(ResourceLimitType type, uint64 value, uint64 max_value) {
+#if TD_PORT_POSIX
+ if (max_value != 0 && value > max_value) {
+ return Status::Error("New resource limit value must not be bigger than max_value");
+ }
+
+ int resource = get_resource(type);
+
+ rlimit rlim;
+ if (getrlimit(resource, &rlim) == -1) {
+ return OS_ERROR("Failed to get current resource limit");
+ }
+
+ TRY_RESULT(new_value, narrow_cast_safe<rlim_t>(value));
+ TRY_RESULT(new_max_value, narrow_cast_safe<rlim_t>(max_value));
+ if (new_max_value) {
+ rlim.rlim_max = new_max_value;
+ } else if (rlim.rlim_max < new_value) {
+ rlim.rlim_max = new_value;
+ }
+ rlim.rlim_cur = new_value;
+
+ if (setrlimit(resource, &rlim) < 0) {
+ return OS_ERROR("Failed to set resource limit");
+ }
+ return Status::OK();
+#elif TD_PORT_WINDOWS
+ return Status::OK(); // Windows has no limits
+#endif
+}
+
+Status set_maximize_resource_limit(ResourceLimitType type, uint64 value) {
+#if TD_PORT_POSIX
+ int resource = get_resource(type);
+
+ rlimit rlim;
+ if (getrlimit(resource, &rlim) == -1) {
+ return OS_ERROR("Failed to get current resource limit");
+ }
+
+ TRY_RESULT(new_value, narrow_cast_safe<rlim_t>(value));
+ if (rlim.rlim_max < new_value) {
+ // trying to increase rlim_max
+ rlimit new_rlim;
+ new_rlim.rlim_cur = new_value;
+ new_rlim.rlim_max = new_value;
+ if (setrlimit(resource, &new_rlim) >= 0) {
+ return Status::OK();
+ }
+
+ // do not increase rlim_max if have no rights
+ new_value = rlim.rlim_max;
+ }
+ rlim.rlim_cur = new_value;
+
+ if (setrlimit(resource, &rlim) < 0) {
+ return OS_ERROR("Failed to set resource limit");
+ }
+ return Status::OK();
+#elif TD_PORT_WINDOWS
+ return Status::OK(); // Windows has no limits
+#endif
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.h
new file mode 100644
index 0000000000..a987a7bdac
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.h
@@ -0,0 +1,20 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+enum class ResourceLimitType { NoFile };
+
+Status set_resource_limit(ResourceLimitType type, uint64 value, uint64 max_value = 0);
+
+Status set_maximize_resource_limit(ResourceLimitType type, uint64 value);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.cpp
index 8627474d63..d61cf69d94 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,9 +7,11 @@
#include "td/utils/port/signals.h"
#include "td/utils/port/config.h"
+#include "td/utils/port/stacktrace.h"
+#include "td/utils/port/StdStreams.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
-#include "td/utils/logging.h"
#if TD_PORT_POSIX
#include <signal.h>
@@ -22,6 +24,7 @@
#include <cerrno>
#include <cstdint>
+#include <cstdlib>
#include <cstring>
#include <ctime>
#include <limits>
@@ -63,23 +66,24 @@ Status setup_signals_alt_stack() {
}
#if TD_PORT_POSIX
+static void set_handler(struct sigaction &act, decltype(act.sa_handler) handler) {
+ act.sa_handler = handler;
+}
+static void set_handler(struct sigaction &act, decltype(act.sa_sigaction) handler) {
+ act.sa_sigaction = handler;
+ act.sa_flags |= SA_SIGINFO;
+}
template <class F>
-static Status set_signal_handler_impl(vector<int> signals, F func, bool is_extended = false) {
+static Status set_signal_handler_impl(vector<int> &&signals, F func) {
struct sigaction act;
std::memset(&act, '\0', sizeof(act));
- if (is_extended) { // TODO if constexpr, remove useless reinterpret_cast
- act.sa_handler = reinterpret_cast<decltype(act.sa_handler)>(func);
- } else {
- act.sa_sigaction = reinterpret_cast<decltype(act.sa_sigaction)>(func);
- }
+
sigemptyset(&act.sa_mask);
for (auto signal : signals) {
sigaddset(&act.sa_mask, signal);
}
act.sa_flags = SA_RESTART | SA_ONSTACK;
- if (is_extended) {
- act.sa_flags |= SA_SIGINFO;
- }
+ set_handler(act, func);
for (auto signal : signals) {
if (sigaction(signal, &act, nullptr) != 0) {
@@ -109,10 +113,23 @@ static vector<int> get_native_signals(SignalType type) {
return {};
}
}
-#endif
-#if TD_PORT_WINDOWS
-static Status set_signal_handler_impl(vector<int> signals, void (*func)(int sig), bool /*unused*/ = true) {
+#elif TD_PORT_WINDOWS
+using signal_handler = void (*)(int sig);
+static signal_handler signal_handlers[NSIG] = {};
+
+static void signal_handler_func(int sig) {
+ std::signal(sig, signal_handler_func);
+ auto handler = signal_handlers[sig];
+ handler(sig);
+}
+
+static Status set_signal_handler_impl(vector<int> &&signals, void (*func)(int sig)) {
for (auto signal : signals) {
+ CHECK(0 <= signal && signal < NSIG);
+ if (func != SIG_IGN && func != SIG_DFL) {
+ signal_handlers[signal] = func;
+ func = signal_handler_func;
+ }
if (std::signal(signal, func) == SIG_ERR) {
return Status::Error("Failed to set signal handler");
}
@@ -142,7 +159,7 @@ static vector<int> get_native_signals(SignalType type) {
}
#endif
-Status set_signal_handler(SignalType type, void (*func)(int)) {
+Status set_signal_handler(SignalType type, void (*func)(int sig)) {
return set_signal_handler_impl(get_native_signals(type), func == nullptr ? SIG_DFL : func);
}
@@ -171,13 +188,13 @@ Status set_extended_signal_handler(SignalType type, extended_signal_handler func
UNREACHABLE();
}
}
- return set_signal_handler_impl(std::move(signals), siginfo_handler, true);
+ return set_signal_handler_impl(std::move(signals), siginfo_handler);
}
-Status set_runtime_signal_handler(int runtime_signal_number, void (*func)(int)) {
+Status set_real_time_signal_handler(int real_time_signal_number, void (*func)(int)) {
#ifdef SIGRTMIN
- CHECK(SIGRTMIN + runtime_signal_number <= SIGRTMAX);
- return set_signal_handler_impl({SIGRTMIN + runtime_signal_number}, func == nullptr ? SIG_DFL : func);
+ CHECK(SIGRTMIN + real_time_signal_number <= SIGRTMAX);
+ return set_signal_handler_impl({SIGRTMIN + real_time_signal_number}, func == nullptr ? SIG_DFL : func);
#else
return Status::OK();
#endif
@@ -279,13 +296,13 @@ void signal_safe_write_signal_number(int sig, bool add_header) {
}
void signal_safe_write_pointer(void *p, bool add_header) {
- std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(p);
+ auto addr = reinterpret_cast<std::uintptr_t>(p);
char buf[100];
char *end = buf + sizeof(buf);
char *ptr = end;
*--ptr = '\n';
do {
- *--ptr = td::format::hex_digit(addr % 16);
+ *--ptr = format::hex_digit(addr % 16);
addr /= 16;
} while (addr != 0);
*--ptr = 'x';
@@ -295,4 +312,33 @@ void signal_safe_write_pointer(void *p, bool add_header) {
signal_safe_write(Slice(ptr, end), add_header);
}
+static void block_stdin() {
+#if TD_PORT_POSIX
+ Stdin().get_native_fd().set_is_blocking(true).ignore();
+#endif
+}
+
+static void default_failure_signal_handler(int sig) {
+ Stacktrace::init();
+ signal_safe_write_signal_number(sig);
+
+ Stacktrace::PrintOptions options;
+ options.use_gdb = true;
+ Stacktrace::print_to_stderr(options);
+
+ block_stdin();
+ _Exit(EXIT_FAILURE);
+}
+
+Status set_default_failure_signal_handler() {
+#if TD_PORT_POSIX
+ Stdin(); // init static variables before atexit
+ std::atexit(block_stdin);
+#endif
+ TRY_STATUS(setup_signals_alt_stack());
+ TRY_STATUS(set_signal_handler(SignalType::Abort, default_failure_signal_handler));
+ TRY_STATUS(set_signal_handler(SignalType::Error, default_failure_signal_handler));
+ return Status::OK();
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.h
index 1f6ed24732..fa5fbae976 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -20,7 +20,7 @@ Status set_signal_handler(SignalType type, void (*func)(int sig)) TD_WARN_UNUSED
Status set_extended_signal_handler(SignalType type, void (*func)(int sig, void *addr)) TD_WARN_UNUSED_RESULT;
-Status set_runtime_signal_handler(int runtime_signal_number, void (*func)(int sig)) TD_WARN_UNUSED_RESULT;
+Status set_real_time_signal_handler(int real_time_signal_number, void (*func)(int sig)) TD_WARN_UNUSED_RESULT;
Status ignore_signal(SignalType type) TD_WARN_UNUSED_RESULT;
@@ -31,4 +31,6 @@ void signal_safe_write_signal_number(int sig, bool add_header = true);
void signal_safe_write_pointer(void *p, bool add_header = true);
+Status set_default_failure_signal_handler();
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.cpp
index 4f02f69dfb..c35de1f2ad 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.h
index 56cd9f7bcd..2f9d27a3d5 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.cpp
new file mode 100644
index 0000000000..7fd07b2a62
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.cpp
@@ -0,0 +1,139 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/stacktrace.h"
+
+#include "td/utils/port/signals.h"
+
+#if __GLIBC__
+#include <execinfo.h>
+#endif
+
+#if TD_LINUX || TD_FREEBSD
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#if TD_LINUX
+#include <sys/prctl.h>
+#endif
+#endif
+
+namespace td {
+
+namespace {
+
+void print_backtrace() {
+#if TD_PORT_WINDOWS
+ void *buffer[128];
+ USHORT nptrs = CaptureStackBackTrace(0, 128, buffer, nullptr);
+#elif __GLIBC__
+ void *buffer[128];
+ int nptrs = backtrace(buffer, 128);
+#else
+ return;
+#endif
+
+ signal_safe_write("------- Stack Backtrace -------\n", false);
+#if TD_PORT_WINDOWS
+ for (USHORT i = 0; i < nptrs; i++) {
+ signal_safe_write_pointer(buffer[i], false);
+ }
+#elif __GLIBC__
+ backtrace_symbols_fd(buffer, nptrs, 2);
+#endif
+ signal_safe_write("-------------------------------\n", false);
+}
+
+void print_backtrace_gdb() {
+#if TD_LINUX || TD_FREEBSD
+ char pid_buf[30];
+ char *pid_buf_begin = pid_buf + sizeof(pid_buf);
+ pid_t pid = getpid();
+ *--pid_buf_begin = '\0';
+ do {
+ *--pid_buf_begin = static_cast<char>(pid % 10 + '0');
+ pid /= 10;
+ } while (pid > 0);
+
+ char name_buf[512];
+ ssize_t res = readlink("/proc/self/exe", name_buf, 511); // TODO works only under Linux
+ if (res >= 0) {
+ name_buf[res] = 0;
+
+#if TD_LINUX
+#if defined(PR_SET_DUMPABLE)
+ if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) {
+ signal_safe_write("Can't set dumpable\n");
+ return;
+ }
+#endif
+#if defined(PR_SET_PTRACER)
+ // We can't use event fd because we are in a signal handler
+ int fds[2];
+ bool need_set_ptracer = true;
+ if (pipe(fds) < 0) {
+ need_set_ptracer = false;
+ signal_safe_write("Can't create a pipe\n");
+ }
+#endif
+#endif
+
+ int child_pid = fork();
+ if (child_pid < 0) {
+ signal_safe_write("Can't fork() to run gdb\n");
+ return;
+ }
+ if (!child_pid) {
+#if TD_LINUX && defined(PR_SET_PTRACER)
+ if (need_set_ptracer) {
+ char c;
+ if (read(fds[0], &c, 1) < 0) {
+ signal_safe_write("Failed to read from pipe\n");
+ }
+ }
+#endif
+ dup2(2, 1); // redirect output to stderr
+ execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "thread apply all bt full", name_buf, pid_buf_begin,
+ nullptr);
+ return;
+ } else {
+#if TD_LINUX && defined(PR_SET_PTRACER)
+ if (need_set_ptracer) {
+ if (prctl(PR_SET_PTRACER, child_pid, 0, 0, 0) < 0) {
+ signal_safe_write("Can't set ptracer\n");
+ }
+ if (write(fds[1], "a", 1) != 1) {
+ signal_safe_write("Can't write to pipe\n");
+ }
+ }
+#endif
+ waitpid(child_pid, nullptr, 0);
+ }
+ } else {
+ signal_safe_write("Can't get name of executable file to pass to gdb\n");
+ }
+#endif
+}
+
+} // namespace
+
+void Stacktrace::print_to_stderr(const PrintOptions &options) {
+ print_backtrace();
+ if (options.use_gdb) {
+ print_backtrace_gdb();
+ }
+}
+
+void Stacktrace::init() {
+#if __GLIBC__
+ // backtrace needs to be called once to ensure that next calls are async-signal-safe
+ void *buffer[1];
+ backtrace(buffer, 1);
+#endif
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.h
new file mode 100644
index 0000000000..469a20dfe6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.h
@@ -0,0 +1,23 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+namespace td {
+
+class Stacktrace {
+ public:
+ struct PrintOptions {
+ bool use_gdb = false;
+ PrintOptions() {
+ }
+ };
+ static void print_to_stderr(const PrintOptions &options = PrintOptions());
+
+ static void init();
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread.h
index 3034e456e8..35053a32fb 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -22,9 +22,6 @@ namespace td {
using thread = detail::ThreadStl;
namespace this_thread = detail::this_thread_stl;
#elif TD_THREAD_UNSUPPORTED
- namespace this_thread {
- inline void yield() {}
- }
#else
#error "Thread's implementation is not defined"
#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.cpp
index aa4e371405..ee56a119cf 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.cpp
@@ -1,23 +1,21 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/port/thread_local.h"
-#include "td/utils/logging.h"
-
namespace td {
namespace detail {
static TD_THREAD_LOCAL int32 thread_id_;
-static TD_THREAD_LOCAL std::vector<std::unique_ptr<Guard>> *thread_local_destructors;
+static TD_THREAD_LOCAL std::vector<unique_ptr<Destructor>> *thread_local_destructors;
-void add_thread_local_destructor(std::unique_ptr<Guard> destructor) {
+void add_thread_local_destructor(unique_ptr<Destructor> destructor) {
if (thread_local_destructors == nullptr) {
- thread_local_destructors = new std::vector<std::unique_ptr<Guard>>();
+ thread_local_destructors = new std::vector<unique_ptr<Destructor>>();
}
thread_local_destructors->push_back(std::move(destructor));
}
@@ -31,6 +29,7 @@ void clear_thread_locals() {
delete to_delete;
CHECK(detail::thread_local_destructors == nullptr);
}
+
void set_thread_id(int32 id) {
detail::thread_id_ = id;
}
@@ -38,4 +37,5 @@ void set_thread_id(int32 id) {
int32 get_thread_id() {
return detail::thread_id_;
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.h
index 6d8c135e88..deaeb09508 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,7 +9,7 @@
#include "td/utils/port/config.h"
#include "td/utils/common.h"
-#include "td/utils/ScopeGuard.h"
+#include "td/utils/Destructor.h"
#include <memory>
#include <utility>
@@ -27,13 +27,9 @@ namespace td {
#endif
// clang-format on
-inline constexpr size_t max_thread_count() {
- return 256;
-}
-
// If raw_ptr is not nullptr, allocate T as in std::make_unique<T>(args...) and store pointer into raw_ptr
template <class T, class P, class... ArgsT>
-bool init_thread_local(P &raw_ptr, ArgsT &&... args);
+bool init_thread_local(P &raw_ptr, ArgsT &&...args);
// Destroy all thread locals, and store nullptr into corresponding pointers
void clear_thread_locals();
@@ -43,14 +39,14 @@ void set_thread_id(int32 id);
int32 get_thread_id();
namespace detail {
-void add_thread_local_destructor(std::unique_ptr<Guard> destructor);
+void add_thread_local_destructor(unique_ptr<Destructor> destructor);
template <class T, class P, class... ArgsT>
-void do_init_thread_local(P &raw_ptr, ArgsT &&... args) {
+void do_init_thread_local(P &raw_ptr, ArgsT &&...args) {
auto ptr = std::make_unique<T>(std::forward<ArgsT>(args)...);
raw_ptr = ptr.get();
- detail::add_thread_local_destructor(create_lambda_guard([ptr = std::move(ptr), &raw_ptr]() mutable {
+ detail::add_thread_local_destructor(create_destructor([ptr = std::move(ptr), &raw_ptr]() mutable {
ptr.reset();
raw_ptr = nullptr;
}));
@@ -58,7 +54,7 @@ void do_init_thread_local(P &raw_ptr, ArgsT &&... args) {
} // namespace detail
template <class T, class P, class... ArgsT>
-bool init_thread_local(P &raw_ptr, ArgsT &&... args) {
+bool init_thread_local(P &raw_ptr, ArgsT &&...args) {
if (likely(raw_ptr != nullptr)) {
return false;
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.cpp
new file mode 100644
index 0000000000..5c380b5e05
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.cpp
@@ -0,0 +1,289 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/uname.h"
+
+#include "td/utils/port/config.h"
+
+#include "td/utils/common.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/Stat.h"
+#include "td/utils/SliceBuilder.h"
+
+#if TD_PORT_POSIX
+
+#include <cstring>
+
+#if TD_ANDROID
+#include <sys/system_properties.h>
+#elif TD_EMSCRIPTEN
+#include <cstdlib>
+
+#include <emscripten.h>
+#else
+#if TD_DARWIN
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#endif
+#include <sys/utsname.h>
+#endif
+
+#endif
+
+namespace td {
+
+#if TD_DARWIN || TD_LINUX
+static string read_os_name(CSlice os_version_file_path, CSlice prefix, CSlice suffix) {
+ auto r_stat = stat(os_version_file_path);
+ if (r_stat.is_ok() && r_stat.ok().is_reg_ && r_stat.ok().size_ < (1 << 16)) {
+ auto r_file = read_file_str(os_version_file_path, r_stat.ok().size_);
+ if (r_file.is_ok()) {
+ auto begin_pos = r_file.ok().find(prefix.c_str());
+ if (begin_pos != string::npos) {
+ begin_pos += prefix.size();
+ auto end_pos = r_file.ok().find(suffix.c_str(), begin_pos);
+ if (end_pos != string::npos) {
+ auto os_version = trim(r_file.ok().substr(begin_pos, end_pos - begin_pos));
+ if (os_version.find('\n') == string::npos) {
+ return os_version;
+ }
+ }
+ }
+ }
+ }
+ return string();
+}
+#endif
+
+Slice get_operating_system_version() {
+ static string result = []() -> string {
+#if TD_DARWIN
+ char version[256];
+ size_t size = sizeof(version);
+ string os_version;
+ if (sysctlbyname("kern.osproductversion", version, &size, nullptr, 0) == 0) {
+ os_version = trim(string(version, size));
+ }
+ if (os_version.empty()) {
+ os_version = read_os_name("/System/Library/CoreServices/SystemVersion.plist",
+ "<key>ProductUserVisibleVersion</key>\n\t<string>", "</string>\n");
+ }
+ if (!os_version.empty()) {
+ os_version = " " + os_version;
+ }
+
+#if TD_DARWIN_IOS
+ return "iOS" + os_version;
+#elif TD_DARWIN_TV_OS
+ return "tvOS" + os_version;
+#elif TD_DARWIN_WATCH_OS
+ return "watchOS" + os_version;
+#elif TD_DARWIN_MAC
+ return "macOS" + os_version;
+#else
+ return "Darwin" + os_version;
+#endif
+#elif TD_PORT_POSIX
+#if TD_ANDROID
+ char version[PROP_VALUE_MAX + 1];
+ int length = __system_property_get("ro.build.version.release", version);
+ if (length > 0) {
+ return "Android " + string(version, length);
+ }
+#elif TD_EMSCRIPTEN
+ // clang-format off
+ char *os_name_js = (char*)EM_ASM_INT(({
+ function detectOsName() {
+ if (typeof process === 'object' && typeof process.platform === 'string') { // Node.js
+ switch (process.platform) {
+ case 'aix':
+ return 'IBM AIX';
+ case 'android':
+ return 'Android';
+ case 'darwin':
+ return 'macOS';
+ case 'freebsd':
+ return 'FreeBSD';
+ case 'linux':
+ return 'Linux';
+ case 'openbsd':
+ return 'OpenBSD';
+ case 'sunos':
+ return 'SunOS';
+ case 'win32':
+ return 'Windows';
+ case 'darwin':
+ return 'macOS';
+ default:
+ return 'Node.js';
+ }
+ }
+
+ var userAgent = 'Unknown';
+ if (typeof window === 'object') { // Web
+ userAgent = window.navigator.userAgent;
+ } else if (typeof importScripts === 'function') { // Web Worker
+ userAgent = navigator.userAgent;
+ }
+
+ var match = /(Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([._0-9]+)/.exec(userAgent);
+ if (match !== null) {
+ return 'macOS ' + match[2].replace('_', '.');
+ }
+
+ match = /Android [._0-9]+/.exec(userAgent);
+ if (match !== null) {
+ return match[0].replace('_', '.');
+ }
+
+ if (/(iPhone|iPad|iPod)/.test(userAgent)) {
+ match = /OS ([._0-9]+)/.exec(userAgent);
+ if (match !== null) {
+ return 'iOS ' + match[1].replace('_', '.');
+ }
+ return 'iOS';
+ }
+
+ var clientStrings = [
+ {s:'Windows 11', r:/(Windows 11|Windows NT 11)/},
+ // there is no way to distinguish Windows 10 from newer versions, so report it as just Windows.
+ // {s:'Windows 10 or later', r:/(Windows 10|Windows NT 10)/},
+ {s:'Windows 8.1', r:/(Windows 8.1|Windows NT 6.3)/},
+ {s:'Windows 8', r:/(Windows 8|Windows NT 6.2)/},
+ {s:'Windows 7', r:/(Windows 7|Windows NT 6.1)/},
+ {s:'Windows Vista', r:/Windows NT 6.0/},
+ {s:'Windows Server 2003', r:/Windows NT 5.2/},
+ {s:'Windows XP', r:/(Windows XP|Windows NT 5.1)/},
+ {s:'Windows', r:/Windows/},
+ {s:'Android', r:/Android/},
+ {s:'FreeBSD', r:/FreeBSD/},
+ {s:'OpenBSD', r:/OpenBSD/},
+ {s:'Chrome OS', r:/CrOS/},
+ {s:'Linux', r:/(Linux|X11)/},
+ {s:'macOS', r:/(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},
+ {s:'QNX', r:/QNX/},
+ {s:'BeOS', r:/BeOS/}
+ ];
+ for (var id in clientStrings) {
+ var cs = clientStrings[id];
+ if (cs.r.test(userAgent)) {
+ return cs.s;
+ }
+ }
+ return 'Emscripten';
+ }
+
+ var os_name = detectOsName();
+ var length = lengthBytesUTF8(os_name) + 1;
+ var result = _malloc(length);
+ stringToUTF8(os_name, result, length);
+ return result;
+ }));
+ // clang-format on
+ string os_name(os_name_js);
+ std::free(os_name_js);
+
+ return os_name;
+#else
+#if TD_LINUX
+ auto os_name = read_os_name("/etc/os-release", "PRETTY_NAME=\"", "\"\n");
+ if (!os_name.empty()) {
+ return os_name;
+ }
+#endif
+
+ struct utsname name;
+ int err = uname(&name);
+ if (err == 0) {
+ auto os_name = trim(PSTRING() << Slice(name.sysname, std::strlen(name.sysname)) << " "
+ << Slice(name.release, std::strlen(name.release)));
+ if (!os_name.empty()) {
+ return os_name;
+ }
+ }
+#endif
+ LOG(ERROR) << "Failed to identify OS name; use generic one";
+
+#if TD_ANDROID
+ return "Android";
+#elif TD_TIZEN
+ return "Tizen";
+#elif TD_LINUX
+ return "Linux";
+#elif TD_FREEBSD
+ return "FreeBSD";
+#elif TD_OPENBSD
+ return "OpenBSD";
+#elif TD_NETBSD
+ return "NetBSD";
+#elif TD_CYGWIN
+ return "Cygwin";
+#else
+ return "Unix";
+#endif
+
+#else
+
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
+ auto handle = GetModuleHandle(L"ntdll.dll");
+ if (handle != nullptr) {
+ using RtlGetVersionPtr = LONG(WINAPI *)(_Out_ PRTL_OSVERSIONINFOEXW);
+ RtlGetVersionPtr RtlGetVersion = reinterpret_cast<RtlGetVersionPtr>(GetProcAddress(handle, "RtlGetVersion"));
+ if (RtlGetVersion != nullptr) {
+ RTL_OSVERSIONINFOEXW os_version_info = {};
+ os_version_info.dwOSVersionInfoSize = sizeof(os_version_info);
+ if (RtlGetVersion(&os_version_info) == 0) {
+ auto major = os_version_info.dwMajorVersion;
+ auto minor = os_version_info.dwMinorVersion;
+ bool is_server = os_version_info.wProductType != VER_NT_WORKSTATION;
+
+ if (major == 10) {
+ if (is_server) {
+ if (os_version_info.dwBuildNumber >= 20201) {
+ // https://techcommunity.microsoft.com/t5/windows-server-insiders/announcing/m-p/1614436
+ return "Windows Server 2022";
+ }
+ if (os_version_info.dwBuildNumber >= 17623) {
+ // https://techcommunity.microsoft.com/t5/windows-server-insiders/announcing/m-p/173715
+ return "Windows Server 2019";
+ }
+ return "Windows Server 2016";
+ }
+ if (os_version_info.dwBuildNumber >= 21900) { // build numbers between 21391 and 21999 aren't used
+ return "Windows 11";
+ }
+ return "Windows 10";
+ }
+ if (major == 6 && minor == 3) {
+ return is_server ? "Windows Server 2012 R2" : "Windows 8.1";
+ }
+ if (major == 6 && minor == 2) {
+ return is_server ? "Windows Server 2012" : "Windows 8";
+ }
+ if (major == 6 && minor == 1) {
+ return is_server ? "Windows Server 2008 R2" : "Windows 7";
+ }
+ if (major == 6 && minor == 0) {
+ return is_server ? "Windows Server 2008" : "Windows Vista";
+ }
+ return is_server ? "Windows Server" : "Windows";
+ }
+ }
+ }
+#elif TD_WINRT
+ return "Windows 10";
+#endif
+
+ LOG(ERROR) << "Failed to identify OS name; use generic one";
+ return "Windows";
+#endif
+ }();
+ return result;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.h
index a3ba32602f..aacb0210aa 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,10 +10,6 @@
namespace td {
-class GitInfo {
- public:
- static CSlice commit();
- static bool is_dirty();
-};
+Slice get_operating_system_version();
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.cpp
new file mode 100644
index 0000000000..b793405b82
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.cpp
@@ -0,0 +1,57 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/port/user.h"
+
+#include "td/utils/port/config.h"
+
+#if TD_PORT_POSIX
+#include "td/utils/SliceBuilder.h"
+
+#include <grp.h>
+#include <pwd.h>
+#if TD_DARWIN || TD_FREEBSD || TD_NETBSD
+#include <sys/param.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+namespace td {
+
+Status change_user(CSlice username, CSlice groupname) {
+#if TD_PORT_POSIX
+ passwd *pw = getpwnam(username.c_str());
+ if (pw == nullptr) {
+ return OS_ERROR(PSTRING() << "Can't find the user '" << username << "' to switch to");
+ }
+ uid_t uid = pw->pw_uid;
+ gid_t gid = pw->pw_gid;
+ if (setgroups(1, &gid) == -1) {
+ return OS_ERROR("Failed to clear supplementary group list");
+ }
+ if (!groupname.empty()) {
+ group *g = getgrnam(groupname.c_str());
+ if (g == nullptr) {
+ return OS_ERROR("Can't find the group to switch to");
+ }
+ gid = g->gr_gid;
+ } else if (initgroups(username.c_str(), gid) == -1) {
+ return OS_ERROR("Failed to load groups of user");
+ }
+ if (setgid(gid) == -1) {
+ return OS_ERROR("failed to set effective group ID");
+ }
+ if (setuid(uid) == -1) {
+ return OS_ERROR("failed to set effective user ID");
+ }
+ return Status::OK();
+#else
+ return Status::Error("Changing effective user is not supported");
+#endif
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.h
new file mode 100644
index 0000000000..041df03e83
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.h
@@ -0,0 +1,16 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+namespace td {
+
+Status change_user(CSlice username, CSlice groupname = CSlice());
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.cpp
index 56da62b96d..fd6f4dfa23 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,44 +10,95 @@ char disable_linker_warning_about_empty_file_wstring_convert_cpp TD_UNUSED;
#if TD_PORT_WINDOWS
-#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
+#include "td/utils/base64.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/utf8.h"
-#include "td/utils/port/wstring_convert.h"
-
-#include <codecvt>
-#include <locale>
-#include <utility>
+#include <cwchar>
namespace td {
-namespace detail {
-template <class Facet>
-class UsableFacet : public Facet {
- public:
- template <class... Args>
- explicit UsableFacet(Args &&... args) : Facet(std::forward<Args>(args)...) {
+Result<std::wstring> to_wstring(CSlice slice) {
+ if (!check_utf8(slice)) {
+ return Status::Error(PSLICE() << "String was expected to be encoded in UTF-8: " << base64_encode(slice));
}
- ~UsableFacet() = default;
-};
-} // namespace detail
-
-Result<std::wstring> to_wstring(Slice slice) {
- // TODO(perf): optimize
- std::wstring_convert<detail::UsableFacet<std::codecvt_utf8_utf16<wchar_t>>> converter;
- auto res = converter.from_bytes(slice.begin(), slice.end());
- if (converter.converted() != slice.size()) {
- return Status::Error("Wrong encoding");
+
+ size_t wstring_len = utf8_utf16_length(slice);
+
+ std::wstring result(wstring_len, static_cast<wchar_t>(0));
+ if (wstring_len) {
+ wchar_t *res = &result[0];
+ for (size_t i = 0; i < slice.size();) {
+ uint32 a = static_cast<unsigned char>(slice[i++]);
+ if (a >= 0x80) {
+ uint32 b = static_cast<unsigned char>(slice[i++]);
+ if (a >= 0xe0) {
+ uint32 c = static_cast<unsigned char>(slice[i++]);
+ if (a >= 0xf0) {
+ uint32 d = static_cast<unsigned char>(slice[i++]);
+ uint32 val = ((a & 0x07) << 18) + ((b & 0x3f) << 12) + ((c & 0x3f) << 6) + (d & 0x3f) - 0x10000;
+ *res++ = static_cast<wchar_t>(0xD800 + (val >> 10));
+ *res++ = static_cast<wchar_t>(0xDC00 + (val & 0x3ff));
+ } else {
+ *res++ = static_cast<wchar_t>(((a & 0x0f) << 12) + ((b & 0x3f) << 6) + (c & 0x3f));
+ }
+ } else {
+ *res++ = static_cast<wchar_t>(((a & 0x1f) << 6) + (b & 0x3f));
+ }
+ } else {
+ *res++ = static_cast<wchar_t>(a);
+ }
+ }
+ CHECK(res == &result[0] + wstring_len);
}
- return res;
+ return result;
}
Result<string> from_wstring(const wchar_t *begin, size_t size) {
- std::wstring_convert<detail::UsableFacet<std::codecvt_utf8_utf16<wchar_t>>> converter;
- auto res = converter.to_bytes(begin, begin + size);
- if (converter.converted() != size) {
- return Status::Error("Wrong encoding");
+ size_t result_len = 0;
+ for (size_t i = 0; i < size; i++) {
+ uint32 cur = begin[i];
+ if ((cur & 0xF800) == 0xD800) {
+ if (i < size) {
+ uint32 next = begin[++i];
+ if ((next & 0xFC00) == 0xDC00 && (cur & 0x400) == 0) {
+ result_len += 4;
+ continue;
+ }
+ }
+
+ return Status::Error("Wrong wstring encoding");
+ }
+ result_len += 1 + (cur >= 0x80) + (cur >= 0x800);
+ }
+
+ std::string result(result_len, '\0');
+ if (result_len) {
+ char *res = &result[0];
+ for (size_t i = 0; i < size; i++) {
+ uint32 cur = begin[i];
+ // TODO conversion uint32 -> signed char is implementation defined
+ if (cur <= 0x7f) {
+ *res++ = static_cast<char>(cur);
+ } else if (cur <= 0x7ff) {
+ *res++ = static_cast<char>(0xc0 | (cur >> 6));
+ *res++ = static_cast<char>(0x80 | (cur & 0x3f));
+ } else if ((cur & 0xF800) != 0xD800) {
+ *res++ = static_cast<char>(0xe0 | (cur >> 12));
+ *res++ = static_cast<char>(0x80 | ((cur >> 6) & 0x3f));
+ *res++ = static_cast<char>(0x80 | (cur & 0x3f));
+ } else {
+ uint32 next = begin[++i];
+ uint32 val = ((cur - 0xD800) << 10) + next - 0xDC00 + 0x10000;
+
+ *res++ = static_cast<char>(0xf0 | (val >> 18));
+ *res++ = static_cast<char>(0x80 | ((val >> 12) & 0x3f));
+ *res++ = static_cast<char>(0x80 | ((val >> 6) & 0x3f));
+ *res++ = static_cast<char>(0x80 | (val & 0x3f));
+ }
+ }
}
- return res;
+ return result;
}
Result<string> from_wstring(const std::wstring &str) {
@@ -55,7 +106,7 @@ Result<string> from_wstring(const std::wstring &str) {
}
Result<string> from_wstring(const wchar_t *begin) {
- return from_wstring(begin, wcslen(begin));
+ return from_wstring(begin, std::wcslen(begin));
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.h
index a795d2bd92..fe453380d3 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -18,7 +18,7 @@
namespace td {
-Result<std::wstring> to_wstring(Slice slice);
+Result<std::wstring> to_wstring(CSlice slice);
Result<string> from_wstring(const std::wstring &str);
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/queue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/queue.h
index 6d107e37f2..e6d69aae3e 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/queue.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/queue.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,15 +7,10 @@
#pragma once
#include "td/utils/port/EventFd.h"
-#include "td/utils/port/thread.h"
+#include "td/utils/port/sleep.h"
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
-#if !TD_WINDOWS
-#include <poll.h>
-#include <sched.h>
-#endif
-
#include <atomic>
#include <type_traits>
#include <utility>
@@ -37,7 +32,7 @@ class Backoff {
if (cnt < 1) { // 50
return true;
} else {
- td::this_thread::yield();
+ usleep_for(1);
return cnt < 3; // 500
}
}
@@ -52,7 +47,7 @@ class InfBackoff {
if (cnt < 50) {
return true;
} else {
- td::this_thread::yield();
+ usleep_for(1);
return true;
}
}
@@ -72,7 +67,7 @@ class SPSCBlockQueue {
}
struct Position {
- std::atomic<uint32> i;
+ std::atomic<uint32> i{0};
char pad[64 - sizeof(std::atomic<uint32>)];
uint32 local_writer_i;
char pad2[64 - sizeof(uint32)];
@@ -155,7 +150,7 @@ class SPSCBlockQueue {
}
};
-template <class T, class BlockQueueT = SPSCBlockQueue<T> >
+template <class T, class BlockQueueT = SPSCBlockQueue<T>>
class SPSCChainQueue {
public:
using ValueType = T;
@@ -257,7 +252,7 @@ class SPSCChainQueue {
private:
struct Node {
BlockQueueT q_;
- std::atomic<bool> is_closed_;
+ std::atomic<bool> is_closed_{false};
Node *next_;
void init() {
@@ -313,11 +308,11 @@ class BackoffQueue : public QueueT {
}
};
-template <class T, class QueueT = SPSCChainQueue<T> >
+template <class T, class QueueT = SPSCChainQueue<T>>
using InfBackoffQueue = BackoffQueue<T, QueueT, detail::InfBackoff>;
-template <class T, class QueueT = BackoffQueue<T> >
-class PollQueue : public QueueT {
+template <class T, class QueueT = BackoffQueue<T>>
+class PollQueue final : public QueueT {
public:
using ValueType = T;
using QueueType = QueueT;
@@ -393,25 +388,18 @@ class PollQueue : public QueueT {
return res;
}
-// Just example of usage
-#if !TD_WINDOWS
+ // Just an example of usage
int reader_wait() {
int res;
-
while ((res = reader_wait_nonblock()) == 0) {
- // TODO: reader_flush?
- pollfd fd;
- fd.fd = reader_get_event_fd().get_fd().get_native_fd();
- fd.events = POLLIN;
- poll(&fd, 1, -1);
+ reader_get_event_fd().wait(1000);
}
return res;
}
-#endif
private:
EventFd event_fd_;
- std::atomic<int> wait_state_;
+ std::atomic<int> wait_state_{0};
int writer_wait_state_;
int get_wait_state() {
@@ -433,7 +421,7 @@ class PollQueue : public QueueT {
#else
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
namespace td {
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.cpp
new file mode 100644
index 0000000000..54fda579ed
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.cpp
@@ -0,0 +1,271 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/tests.h"
+
+#include "td/utils/crypto.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/Parser.h"
+#include "td/utils/PathView.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/Stat.h"
+#include "td/utils/Random.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/Time.h"
+
+#include <map>
+
+namespace td {
+
+string rand_string(int from, int to, size_t len) {
+ string res(len, '\0');
+ for (auto &c : res) {
+ c = static_cast<char>(Random::fast(from, to));
+ }
+ return res;
+}
+
+vector<string> rand_split(Slice str) {
+ vector<string> res;
+ size_t pos = 0;
+ while (pos < str.size()) {
+ size_t len;
+ if (Random::fast_bool()) {
+ len = Random::fast(1, 10);
+ } else {
+ len = Random::fast(100, 200);
+ }
+ res.push_back(str.substr(pos, len).str());
+ pos += len;
+ }
+ return res;
+}
+
+struct TestInfo {
+ string name;
+ string result_hash; // base64
+};
+
+StringBuilder &operator<<(StringBuilder &sb, const TestInfo &info) {
+ // should I use JSON?
+ CHECK(!info.name.empty());
+ CHECK(!info.result_hash.empty());
+ return sb << info.name << " " << info.result_hash << "\n";
+}
+
+class RegressionTesterImpl final : public RegressionTester {
+ public:
+ static void destroy(CSlice db_path) {
+ unlink(db_path).ignore();
+ }
+
+ RegressionTesterImpl(string db_path, string db_cache_dir)
+ : db_path_(std::move(db_path)), db_cache_dir_(std::move(db_cache_dir)) {
+ load_db(db_path_).ignore();
+ if (db_cache_dir_.empty()) {
+ db_cache_dir_ = PathView(db_path_).without_extension().str() + ".cache/";
+ }
+ mkdir(db_cache_dir_).ensure();
+ }
+
+ Status verify_test(Slice name, Slice result) final {
+#if TD_HAVE_OPENSSL
+ auto hash = PSTRING() << format::as_hex_dump<0>(Slice(sha256(result)));
+#else
+ auto hash = to_string(crc64(result));
+#endif
+ TestInfo &old_test_info = tests_[name.str()];
+ if (!old_test_info.result_hash.empty() && old_test_info.result_hash != hash) {
+ auto wa_path = db_cache_dir_ + "WA";
+ write_file(wa_path, result).ensure();
+ return Status::Error(PSLICE() << "Test " << name << " changed: " << tag("expected", old_test_info.result_hash)
+ << tag("got", hash));
+ }
+ auto result_cache_path = db_cache_dir_ + hash;
+ if (stat(result_cache_path).is_error()) {
+ write_file(result_cache_path, result).ensure();
+ }
+ if (!old_test_info.result_hash.empty()) {
+ return Status::OK();
+ }
+ old_test_info.name = name.str();
+ old_test_info.result_hash = hash;
+ is_dirty_ = true;
+
+ return Status::OK();
+ }
+
+ void save_db() final {
+ if (!is_dirty_) {
+ return;
+ }
+ SCOPE_EXIT {
+ is_dirty_ = false;
+ };
+ string buf(2000000, ' ');
+ StringBuilder sb(buf);
+ save_db(sb);
+ string new_db_path = db_path_ + ".new";
+ write_file(new_db_path, sb.as_cslice()).ensure();
+ rename(new_db_path, db_path_).ensure();
+ }
+
+ Slice magic() const {
+ return Slice("abce");
+ }
+
+ void save_db(StringBuilder &sb) {
+ sb << magic() << "\n";
+ for (const auto &it : tests_) {
+ sb << it.second;
+ }
+ }
+
+ Status load_db(CSlice path) {
+ TRY_RESULT(data, read_file(path));
+ ConstParser parser(data.as_slice());
+ auto db_magic = parser.read_word();
+ if (db_magic != magic()) {
+ return Status::Error(PSLICE() << "Wrong magic " << db_magic);
+ }
+ while (true) {
+ TestInfo info;
+ info.name = parser.read_word().str();
+ if (info.name.empty()) {
+ break;
+ }
+ info.result_hash = parser.read_word().str();
+ tests_[info.name] = info;
+ }
+ return Status::OK();
+ }
+
+ private:
+ string db_path_;
+ string db_cache_dir_;
+ bool is_dirty_{false};
+
+ std::map<string, TestInfo> tests_;
+};
+
+void RegressionTester::destroy(CSlice path) {
+ RegressionTesterImpl::destroy(path);
+}
+
+unique_ptr<RegressionTester> RegressionTester::create(string db_path, string db_cache_dir) {
+ return td::make_unique<RegressionTesterImpl>(std::move(db_path), std::move(db_cache_dir));
+}
+
+TestsRunner &TestsRunner::get_default() {
+ static TestsRunner default_runner;
+ return default_runner;
+}
+
+void TestsRunner::add_test(string name, std::function<unique_ptr<Test>()> test) {
+ for (auto &it : tests_) {
+ if (it.first == name) {
+ LOG(FATAL) << "Test name collision " << name;
+ }
+ }
+ tests_.emplace_back(std::move(name), TestInfo{std::move(test), nullptr});
+}
+
+void TestsRunner::add_substr_filter(string str) {
+ if (str[0] != '+' && str[0] != '-') {
+ str = "+" + str;
+ }
+ substr_filters_.push_back(std::move(str));
+}
+
+void TestsRunner::set_regression_tester(unique_ptr<RegressionTester> regression_tester) {
+ regression_tester_ = std::move(regression_tester);
+}
+
+void TestsRunner::set_stress_flag(bool flag) {
+ stress_flag_ = flag;
+}
+
+void TestsRunner::run_all() {
+ while (run_all_step()) {
+ }
+}
+
+bool TestsRunner::run_all_step() {
+ Guard guard(this);
+ if (state_.it == state_.end) {
+ state_.end = tests_.size();
+ state_.it = 0;
+ }
+
+ while (state_.it != state_.end) {
+ auto &name = tests_[state_.it].first;
+ auto &test = tests_[state_.it].second.test;
+ if (!state_.is_running) {
+ bool ok = true;
+ for (const auto &filter : substr_filters_) {
+ bool is_match = name.find(filter.substr(1)) != string::npos;
+ if (is_match != (filter[0] == '+')) {
+ ok = false;
+ break;
+ }
+ }
+ if (!ok) {
+ ++state_.it;
+ continue;
+ }
+ LOG(ERROR) << "Run test " << tag("name", name);
+ state_.start = Time::now();
+ state_.start_unadjusted = Time::now_unadjusted();
+ state_.is_running = true;
+
+ CHECK(!test);
+ test = tests_[state_.it].second.creator();
+ }
+
+ if (test->step()) {
+ break;
+ }
+
+ test = {};
+
+ auto passed = Time::now() - state_.start;
+ auto real_passed = Time::now_unadjusted() - state_.start_unadjusted;
+ if (real_passed + 1e-1 > passed) {
+ LOG(ERROR) << format::as_time(passed);
+ } else {
+ LOG(ERROR) << format::as_time(real_passed) << " adjusted [" << format::as_time(real_passed) << "]";
+ }
+ if (regression_tester_) {
+ regression_tester_->save_db();
+ }
+ state_.is_running = false;
+ ++state_.it;
+ }
+
+ auto ret = state_.it != state_.end;
+ if (!ret) {
+ state_ = State();
+ }
+ return ret || stress_flag_;
+}
+
+Slice TestsRunner::name() {
+ CHECK(state_.is_running);
+ return tests_[state_.it].first;
+}
+
+Status TestsRunner::verify(Slice data) {
+ if (!regression_tester_) {
+ LOG(INFO) << data;
+ LOG(ERROR) << "Cannot verify and save <" << name() << "> answer. Use --regression <regression_db> option";
+ return Status::OK();
+ }
+ return regression_tester_->verify_test(PSLICE() << name() << "_default", data);
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.h
index 24e2f3fe22..69b9206268 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,139 +7,133 @@
#pragma once
#include "td/utils/common.h"
+#include "td/utils/Context.h"
#include "td/utils/format.h"
-#include "td/utils/List.h"
#include "td/utils/logging.h"
-#include "td/utils/port/thread.h"
-#include "td/utils/Random.h"
+#include "td/utils/port/sleep.h"
#include "td/utils/Slice.h"
-#include "td/utils/Time.h"
+#include "td/utils/Status.h"
#include <atomic>
-
-#define REGISTER_TESTS(x) \
- void TD_CONCAT(register_tests_, x)() { \
- }
-#define DESC_TESTS(x) void TD_CONCAT(register_tests_, x)()
-#define LOAD_TESTS(x) TD_CONCAT(register_tests_, x)()
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <utility>
namespace td {
-class Test : private ListNode {
+class RandomSteps {
public:
- explicit Test(CSlice name) : name_(name) {
- get_tests_list()->put_back(this);
- }
-
- Test(const Test &) = delete;
- Test &operator=(const Test &) = delete;
- Test(Test &&) = delete;
- Test &operator=(Test &&) = delete;
- virtual ~Test() = default;
+ struct Step {
+ std::function<void()> func;
+ uint32 weight;
+ };
- static void add_substr_filter(std::string str) {
- if (str[0] != '+' && str[0] != '-') {
- str = "+" + str;
+ explicit RandomSteps(vector<Step> steps) : steps_(std::move(steps)) {
+ for (const auto &step : steps_) {
+ steps_sum_ += step.weight;
}
- get_substr_filters()->push_back(std::move(str));
}
- static void set_stress_flag(bool flag) {
- get_stress_flag() = flag;
- }
- static void run_all() {
- while (run_all_step()) {
- }
- }
-
- static bool run_all_step() {
- auto *state = get_state();
- if (state->it == nullptr) {
- state->end = get_tests_list();
- state->it = state->end->next;
- }
- while (state->it != state->end) {
- auto test = static_cast<td::Test *>(state->it);
- if (!state->is_running) {
- bool ok = true;
- for (const auto &filter : *get_substr_filters()) {
- bool is_match = test->name_.str().find(filter.substr(1)) != std::string::npos;
- if (is_match != (filter[0] == '+')) {
- ok = false;
- break;
- }
- }
- if (!ok) {
- state->it = state->it->next;
- continue;
- }
- LOG(ERROR) << "Run test " << tag("name", test->name_);
- state->start = Time::now();
- state->is_running = true;
- }
-
- if (test->step()) {
+ template <class Random>
+ void step(Random &rnd) const {
+ auto w = rnd() % steps_sum_;
+ for (const auto &step : steps_) {
+ if (w < step.weight) {
+ step.func();
break;
}
-
- LOG(ERROR) << format::as_time(Time::now() - state->start);
- state->is_running = false;
- state->it = state->it->next;
+ w -= step.weight;
}
-
- auto ret = state->it != state->end;
- if (!ret) {
- *state = State();
- }
- return ret || get_stress_flag();
}
private:
- CSlice name_;
- struct State {
- ListNode *it = nullptr;
- bool is_running = false;
- double start;
- ListNode *end = nullptr;
- };
- static State *get_state() {
- static State state;
- return &state;
- }
- static std::vector<std::string> *get_substr_filters() {
- static std::vector<std::string> substr_filters_;
- return &substr_filters_;
- }
+ vector<Step> steps_;
+ int32 steps_sum_ = 0;
+};
- static ListNode *get_tests_list() {
- static ListNode root;
- return &root;
- }
- static bool &get_ok_flag() {
- static bool is_ok = true;
- return is_ok;
- }
- static bool &get_stress_flag() {
- static bool stress_flag = false;
- return stress_flag;
- }
+class RegressionTester {
+ public:
+ virtual ~RegressionTester() = default;
+ static void destroy(CSlice db_path);
+ static unique_ptr<RegressionTester> create(string db_path, string db_cache_dir = "");
+
+ virtual Status verify_test(Slice name, Slice result) = 0;
+ virtual void save_db() = 0;
+};
+
+class Test {
+ public:
+ virtual ~Test() = default;
virtual void run() {
while (step()) {
}
}
-
virtual bool step() {
run();
return false;
}
+ Test() = default;
+ Test(const Test &) = delete;
+ Test &operator=(const Test &) = delete;
+ Test(Test &&) = delete;
+ Test &operator=(Test &&) = delete;
+};
+
+class TestContext : public Context<TestContext> {
+ public:
+ virtual ~TestContext() = default;
+ virtual Slice name() = 0;
+ virtual Status verify(Slice data) = 0;
+};
+
+class TestsRunner final : public TestContext {
+ public:
+ static TestsRunner &get_default();
+
+ void add_test(string name, std::function<unique_ptr<Test>()> test);
+ void add_substr_filter(string str);
+ void set_stress_flag(bool flag);
+ void run_all();
+ bool run_all_step();
+ void set_regression_tester(unique_ptr<RegressionTester> regression_tester);
+
+ private:
+ struct State {
+ size_t it{0};
+ bool is_running = false;
+ double start{0};
+ double start_unadjusted{0};
+ size_t end{0};
+ };
+ bool stress_flag_{false};
+ vector<string> substr_filters_;
+ struct TestInfo {
+ std::function<unique_ptr<Test>()> creator;
+ unique_ptr<Test> test;
+ };
+ vector<std::pair<string, TestInfo>> tests_;
+ State state_;
+ unique_ptr<RegressionTester> regression_tester_;
+
+ Slice name() final;
+ Status verify(Slice data) final;
+};
+
+template <class T>
+class RegisterTest {
+ public:
+ explicit RegisterTest(string name, TestsRunner &runner = TestsRunner::get_default()) {
+ runner.add_test(std::move(name), [] { return make_unique<T>(); });
+ }
};
-class Stage {
+class StageWait {
public:
void wait(uint64 need) {
value_.fetch_add(1, std::memory_order_release);
while (value_.load(std::memory_order_acquire) < need) {
- td::this_thread::yield();
+ usleep_for(1);
}
};
@@ -147,38 +141,38 @@ class Stage {
std::atomic<uint64> value_{0};
};
-inline string rand_string(char from, char to, int len) {
- string res(len, 0);
- for (auto &c : res) {
- c = static_cast<char>(Random::fast(from, to));
- }
- return res;
-}
-
-inline std::vector<string> rand_split(string str) {
- std::vector<string> res;
- size_t pos = 0;
- while (pos < str.size()) {
- size_t len;
- if (Random::fast(0, 1) == 1) {
- len = Random::fast(1, 10);
- } else {
- len = Random::fast(100, 200);
+class StageMutex {
+ public:
+ void wait(uint64 need) {
+ std::unique_lock<std::mutex> lock{mutex_};
+ value_++;
+ if (value_ == need) {
+ cond_.notify_all();
+ return;
}
- res.push_back(str.substr(pos, len));
- pos += len;
- }
- return res;
-}
+ cond_.wait(lock, [&] { return value_ >= need; });
+ };
+
+ private:
+ std::mutex mutex_;
+ std::condition_variable cond_;
+ uint64 value_{0};
+};
+
+using Stage = StageMutex;
+
+string rand_string(int from, int to, size_t len);
+
+vector<string> rand_split(Slice str);
template <class T1, class T2>
void assert_eq_impl(const T1 &expected, const T2 &got, const char *file, int line) {
- CHECK(expected == got) << tag("expected", expected) << tag("got", got) << " in " << file << " at line " << line;
+ LOG_CHECK(expected == got) << tag("expected", expected) << tag("got", got) << " in " << file << " at line " << line;
}
template <class T>
void assert_true_impl(const T &got, const char *file, int line) {
- CHECK(got) << "Expected true in " << file << " at line " << line;
+ LOG_CHECK(got) << "Expected true in " << file << " at line " << line;
}
} // namespace td
@@ -190,16 +184,18 @@ void assert_true_impl(const T &got, const char *file, int line) {
#define ASSERT_STREQ(expected, got) \
::td::assert_eq_impl(::td::Slice((expected)), ::td::Slice((got)), __FILE__, __LINE__)
+#define REGRESSION_VERIFY(data) ::td::TestContext::get()->verify(data).ensure()
+
#define TEST_NAME(test_case_name, test_name) \
TD_CONCAT(Test, TD_CONCAT(_, TD_CONCAT(test_case_name, TD_CONCAT(_, test_name))))
#define TEST(test_case_name, test_name) TEST_IMPL(TEST_NAME(test_case_name, test_name))
-#define TEST_IMPL(test_name) \
- class test_name : public ::td::Test { \
- public: \
- using Test::Test; \
- void run() final; \
- }; \
- test_name TD_CONCAT(test_instance_, TD_CONCAT(test_name, __LINE__))(TD_DEFINE_STR(test_name)); \
+#define TEST_IMPL(test_name) \
+ class test_name final : public ::td::Test { \
+ public: \
+ using Test::Test; \
+ void run() final; \
+ }; \
+ ::td::RegisterTest<test_name> TD_CONCAT(test_instance_, TD_CONCAT(test_name, __LINE__))(TD_DEFINE_STR(test_name)); \
void test_name::run()
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_helpers.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_helpers.h
index 686dacbeef..ee5c9e6c3e 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_helpers.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_helpers.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,43 +7,57 @@
#pragma once
#include "td/utils/common.h"
-#include "td/utils/logging.h"
+#include "td/utils/FlatHashSet.h"
#include "td/utils/misc.h"
+#include "td/utils/SharedSlice.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/StackAllocator.h"
#include "td/utils/Status.h"
#include "td/utils/tl_parsers.h"
#include "td/utils/tl_storers.h"
+#include "td/utils/Variant.h"
#include <type_traits>
-#include <unordered_set>
+#include <utility>
-#define BEGIN_STORE_FLAGS() \
- uint32 flags_store = 0; \
- uint32 bit_offset_store = 0
+#define BEGIN_STORE_FLAGS() \
+ do { \
+ ::td::uint32 flags_store = 0; \
+ ::td::uint32 bit_offset_store = 0
-#define STORE_FLAG(flag) \
- flags_store |= (flag) << bit_offset_store; \
+#define STORE_FLAG(flag) \
+ static_assert(std::is_same<decltype(flag), bool>::value, "flag must be bool"); \
+ flags_store |= (flag) << bit_offset_store; \
bit_offset_store++
-#define END_STORE_FLAGS() \
- CHECK(bit_offset_store < 31); \
- td::store(flags_store, storer)
+#define END_STORE_FLAGS() \
+ CHECK(bit_offset_store < 31); \
+ ::td::store(flags_store, storer); \
+ } \
+ while (false)
-#define BEGIN_PARSE_FLAGS() \
- uint32 flags_parse; \
- uint32 bit_offset_parse = 0; \
- td::parse(flags_parse, parser)
+#define BEGIN_PARSE_FLAGS() \
+ do { \
+ ::td::uint32 flags_parse; \
+ ::td::uint32 bit_offset_parse = 0; \
+ ::td::parse(flags_parse, parser)
-#define PARSE_FLAG(flag) \
- flag = ((flags_parse >> bit_offset_parse) & 1) != 0; \
+#define PARSE_FLAG(flag) \
+ static_assert(std::is_same<decltype(flag), bool>::value, "flag must be bool"); \
+ flag = ((flags_parse >> bit_offset_parse) & 1) != 0; \
bit_offset_parse++
-#define END_PARSE_FLAGS() \
- CHECK(bit_offset_parse < 31); \
- CHECK((flags_parse & ~((1 << bit_offset_parse) - 1)) == 0) << flags_parse << " " << bit_offset_parse;
+#define END_PARSE_FLAGS() \
+ CHECK(bit_offset_parse < 31); \
+ if ((flags_parse & ~((1 << bit_offset_parse) - 1)) != 0) { \
+ parser.set_error(PSTRING() << "Invalid flags " << flags_parse << " left, current bit is " << bit_offset_parse); \
+ } \
+ } \
+ while (false)
namespace td {
+
template <class StorerT>
void store(bool x, StorerT &storer) {
storer.store_binary(static_cast<int32>(x));
@@ -105,11 +119,20 @@ template <class StorerT>
void store(const string &x, StorerT &storer) {
storer.store_string(x);
}
+template <class StorerT>
+void store(const SecureString &x, StorerT &storer) {
+ storer.store_string(x.as_slice());
+}
template <class ParserT>
void parse(string &x, ParserT &parser) {
x = parser.template fetch_string<string>();
}
+template <class ParserT>
+void parse(SecureString &x, ParserT &parser) {
+ x = parser.template fetch_string<SecureString>();
+}
+
template <class T, class StorerT>
void store(const vector<T> &vec, StorerT &storer) {
storer.store_binary(narrow_cast<int32>(vec.size()));
@@ -117,6 +140,13 @@ void store(const vector<T> &vec, StorerT &storer) {
store(val, storer);
}
}
+template <class T, class StorerT>
+void store(const vector<T *> &vec, StorerT &storer) {
+ storer.store_binary(narrow_cast<int32>(vec.size()));
+ for (auto &val : vec) {
+ store(*val, storer);
+ }
+}
template <class T, class ParserT>
void parse(vector<T> &vec, ParserT &parser) {
uint32 size = parser.fetch_int();
@@ -130,28 +160,51 @@ void parse(vector<T> &vec, ParserT &parser) {
}
}
-template <class Key, class Hash, class KeyEqual, class Allocator, class StorerT>
-void store(const std::unordered_set<Key, Hash, KeyEqual, Allocator> &s, StorerT &storer) {
+template <class T, class StorerT>
+void store(const unique_ptr<T> &ptr, StorerT &storer) {
+ CHECK(ptr != nullptr);
+ store(*ptr, storer);
+}
+template <class T, class ParserT>
+void parse(unique_ptr<T> &ptr, ParserT &parser) {
+ CHECK(ptr == nullptr);
+ ptr = make_unique<T>();
+ parse(*ptr, parser);
+}
+
+template <class Key, class Hash, class KeyEqual, class StorerT>
+void store(const FlatHashSet<Key, Hash, KeyEqual> &s, StorerT &storer) {
storer.store_binary(narrow_cast<int32>(s.size()));
for (auto &val : s) {
store(val, storer);
}
}
-template <class Key, class Hash, class KeyEqual, class Allocator, class ParserT>
-void parse(std::unordered_set<Key, Hash, KeyEqual, Allocator> &s, ParserT &parser) {
+template <class Key, class Hash, class KeyEqual, class ParserT>
+void parse(FlatHashSet<Key, Hash, KeyEqual> &s, ParserT &parser) {
uint32 size = parser.fetch_int();
if (parser.get_left_len() < size) {
parser.set_error("Wrong set length");
return;
}
s.clear();
- Key val;
for (uint32 i = 0; i < size; i++) {
+ Key val;
parse(val, parser);
s.insert(std::move(val));
}
}
+template <class U, class V, class StorerT>
+void store(const std::pair<U, V> &pair, StorerT &storer) {
+ store(pair.first, storer);
+ store(pair.second, storer);
+}
+template <class U, class V, class ParserT>
+void parse(std::pair<U, V> &pair, ParserT &parser) {
+ parse(pair.first, parser);
+ parse(pair.second, parser);
+}
+
template <class T, class StorerT>
std::enable_if_t<std::is_enum<T>::value> store(const T &val, StorerT &storer) {
store(static_cast<int32>(val), storer);
@@ -172,6 +225,29 @@ std::enable_if_t<!std::is_enum<T>::value> parse(T &val, ParserT &parser) {
val.parse(parser);
}
+template <class... Types, class StorerT>
+void store(const Variant<Types...> &variant, StorerT &storer) {
+ store(variant.get_offset(), storer);
+ variant.visit([&storer](auto &&value) {
+ using td::store;
+ store(value, storer);
+ });
+}
+template <class... Types, class ParserT>
+void parse(Variant<Types...> &variant, ParserT &parser) {
+ auto type_offset = parser.fetch_int();
+ if (type_offset < 0 || type_offset >= static_cast<int32>(sizeof...(Types))) {
+ return parser.set_error("Invalid type");
+ }
+ variant.for_each([type_offset, &parser, &variant](int offset, auto *ptr) {
+ using T = std::decay_t<decltype(*ptr)>;
+ if (offset == type_offset) {
+ variant = T();
+ parse(variant.template get<T>(), parser);
+ }
+ });
+}
+
template <class T>
string serialize(const T &object) {
TlStorerCalcLength calc_length;
@@ -182,22 +258,40 @@ string serialize(const T &object) {
if (!is_aligned_pointer<4>(key.data())) {
auto ptr = StackAllocator::alloc(length);
MutableSlice data = ptr.as_slice();
- TlStorerUnsafe storer(data.begin());
+ TlStorerUnsafe storer(data.ubegin());
store(object, storer);
+ CHECK(storer.get_buf() == data.uend());
key.assign(data.begin(), data.size());
} else {
MutableSlice data = key;
- TlStorerUnsafe storer(data.begin());
+ TlStorerUnsafe storer(data.ubegin());
store(object, storer);
+ CHECK(storer.get_buf() == data.uend());
}
return key;
}
template <class T>
+SecureString serialize_secure(const T &object) {
+ TlStorerCalcLength calc_length;
+ store(object, calc_length);
+ size_t length = calc_length.get_length();
+
+ SecureString key(length, '\0');
+ CHECK(is_aligned_pointer<4>(key.data()));
+ MutableSlice data = key.as_mutable_slice();
+ TlStorerUnsafe storer(data.ubegin());
+ store(object, storer);
+ CHECK(storer.get_buf() == data.uend());
+ return key;
+}
+
+template <class T>
TD_WARN_UNUSED_RESULT Status unserialize(T &object, Slice data) {
TlParser parser(data);
parse(object, parser);
parser.fetch_end();
return parser.get_status();
}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.cpp
index 534e7793cf..d4315a43de 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.cpp
@@ -1,15 +1,35 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/tl_parsers.h"
+#include "td/utils/misc.h"
+
namespace td {
alignas(4) const unsigned char TlParser::empty_data[sizeof(UInt256)] = {}; // static zero-initialized
+TlParser::TlParser(Slice slice) {
+ data_len = left_len = slice.size();
+ if (is_aligned_pointer<4>(slice.begin())) {
+ data = slice.ubegin();
+ } else {
+ int32 *buf;
+ if (data_len <= small_data_array.size() * sizeof(int32)) {
+ buf = &small_data_array[0];
+ } else {
+ LOG(ERROR) << "Unexpected big unaligned data pointer of length " << slice.size() << " at " << slice.begin();
+ data_buf = std::make_unique<int32[]>(1 + data_len / sizeof(int32));
+ buf = data_buf.get();
+ }
+ std::memcpy(buf, slice.begin(), slice.size());
+ data = reinterpret_cast<unsigned char *>(buf);
+ }
+}
+
void TlParser::set_error(const string &error_message) {
if (error.empty()) {
CHECK(!error_message.empty());
@@ -19,11 +39,21 @@ void TlParser::set_error(const string &error_message) {
left_len = 0;
data_len = 0;
} else {
+ LOG_CHECK(error_pos != std::numeric_limits<size_t>::max() && data_len == 0 && left_len == 0)
+ << data_len << " " << left_len << " " << data << " " << &empty_data[0] << " " << error_pos << " " << error
+ << " " << data << " " << &empty_data;
data = empty_data;
- CHECK(error_pos != std::numeric_limits<size_t>::max());
- CHECK(data_len == 0);
- CHECK(left_len == 0);
}
}
+BufferSlice TlBufferParser::as_buffer_slice(Slice slice) {
+ if (slice.empty()) {
+ return BufferSlice();
+ }
+ if (is_aligned_pointer<4>(slice.data())) {
+ return parent_->from_slice(slice);
+ }
+ return BufferSlice(slice);
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.h
index ffb669bdeb..da9f2144e4 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -10,14 +10,16 @@
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
-#include "td/utils/misc.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
+#include "td/utils/UInt.h"
#include "td/utils/utf8.h"
#include <array>
#include <cstring>
#include <limits>
+#include <memory>
#include <string>
namespace td {
@@ -29,35 +31,14 @@ class TlParser {
size_t error_pos = std::numeric_limits<size_t>::max();
std::string error;
- unique_ptr<int32[]> data_buf;
+ std::unique_ptr<int32[]> data_buf;
static constexpr size_t SMALL_DATA_ARRAY_SIZE = 6;
std::array<int32, SMALL_DATA_ARRAY_SIZE> small_data_array;
alignas(4) static const unsigned char empty_data[sizeof(UInt256)];
public:
- explicit TlParser(Slice slice) {
- if (slice.size() % sizeof(int32) != 0) {
- set_error("Wrong length");
- return;
- }
-
- data_len = left_len = slice.size();
- if (is_aligned_pointer<4>(slice.begin())) {
- data = slice.ubegin();
- } else {
- int32 *buf;
- if (data_len <= small_data_array.size() * sizeof(int32)) {
- buf = &small_data_array[0];
- } else {
- LOG(ERROR) << "Unexpected big unaligned data pointer of length " << slice.size() << " at " << slice.begin();
- data_buf = make_unique<int32[]>(data_len / sizeof(int32));
- buf = data_buf.get();
- }
- std::memcpy(static_cast<void *>(buf), static_cast<const void *>(slice.begin()), slice.size());
- data = reinterpret_cast<unsigned char *>(buf);
- }
- }
+ explicit TlParser(Slice slice);
TlParser(const TlParser &other) = delete;
TlParser &operator=(const TlParser &other) = delete;
@@ -90,8 +71,19 @@ class TlParser {
}
}
+ bool can_prefetch_int() const {
+ return get_left_len() >= sizeof(int32);
+ }
+
+ int32 prefetch_int_unsafe() const {
+ int32 result;
+ std::memcpy(&result, data, sizeof(int32));
+ return result;
+ }
+
int32 fetch_int_unsafe() {
- int32 result = *reinterpret_cast<const int32 *>(data);
+ int32 result;
+ std::memcpy(&result, data, sizeof(int32));
data += sizeof(int32);
return result;
}
@@ -103,7 +95,7 @@ class TlParser {
int64 fetch_long_unsafe() {
int64 result;
- std::memcpy(reinterpret_cast<unsigned char *>(&result), data, sizeof(int64));
+ std::memcpy(&result, data, sizeof(int64));
data += sizeof(int64);
return result;
}
@@ -115,7 +107,7 @@ class TlParser {
double fetch_double_unsafe() {
double result;
- std::memcpy(reinterpret_cast<unsigned char *>(&result), data, sizeof(double));
+ std::memcpy(&result, data, sizeof(double));
data += sizeof(double);
return result;
}
@@ -128,7 +120,7 @@ class TlParser {
template <class T>
T fetch_binary_unsafe() {
T result;
- std::memcpy(reinterpret_cast<unsigned char *>(&result), data, sizeof(T));
+ std::memcpy(&result, data, sizeof(T));
data += sizeof(T);
return result;
}
@@ -136,7 +128,7 @@ class TlParser {
template <class T>
T fetch_binary() {
static_assert(sizeof(T) <= sizeof(empty_data), "too big fetch_binary");
- static_assert(sizeof(T) % sizeof(int32) == 0, "wrong call to fetch_binary");
+ //static_assert(sizeof(T) % sizeof(int32) == 0, "wrong call to fetch_binary");
check_len(sizeof(T));
return fetch_binary_unsafe<T>();
}
@@ -145,29 +137,48 @@ class TlParser {
T fetch_string() {
check_len(sizeof(int32));
size_t result_len = *data;
- const char *result_begin;
+ const unsigned char *result_begin;
size_t result_aligned_len;
if (result_len < 254) {
- result_begin = reinterpret_cast<const char *>(data + 1);
+ result_begin = data + 1;
result_aligned_len = (result_len >> 2) << 2;
+ data += sizeof(int32);
} else if (result_len == 254) {
result_len = data[1] + (data[2] << 8) + (data[3] << 16);
- result_begin = reinterpret_cast<const char *>(data + 4);
+ result_begin = data + 4;
result_aligned_len = ((result_len + 3) >> 2) << 2;
+ data += sizeof(int32);
} else {
- set_error("Can't fetch string, 255 found");
- return T();
+ check_len(sizeof(int32));
+ auto result_len_uint64 = static_cast<uint64>(data[1]) + (static_cast<uint64>(data[2]) << 8) +
+ (static_cast<uint64>(data[3]) << 16) + (static_cast<uint64>(data[4]) << 24) +
+ (static_cast<uint64>(data[5]) << 32) + (static_cast<uint64>(data[6]) << 40) +
+ (static_cast<uint64>(data[7]) << 48);
+ if (result_len_uint64 > std::numeric_limits<size_t>::max() - 3) {
+ set_error("Too big string found");
+ return T();
+ }
+ result_len = static_cast<size_t>(result_len_uint64);
+ result_begin = data + 8;
+ result_aligned_len = ((result_len + 3) >> 2) << 2;
+ data += sizeof(int64);
}
check_len(result_aligned_len);
- data += result_aligned_len + sizeof(int32);
- return T(result_begin, result_len);
+ if (!error.empty()) {
+ return T();
+ }
+ data += result_aligned_len;
+ return T(reinterpret_cast<const char *>(result_begin), result_len);
}
template <class T>
T fetch_string_raw(const size_t size) {
- CHECK(size % sizeof(int32) == 0);
+ //CHECK(size % sizeof(int32) == 0);
check_len(size);
- const char *result = reinterpret_cast<const char *>(data);
+ if (!error.empty()) {
+ return T();
+ }
+ auto result = reinterpret_cast<const char *>(data);
data += size;
return T(result, size);
}
@@ -187,6 +198,7 @@ class TlBufferParser : public TlParser {
public:
explicit TlBufferParser(const BufferSlice *buffer_slice) : TlParser(buffer_slice->as_slice()), parent_(buffer_slice) {
}
+
template <class T>
T fetch_string() {
auto result = TlParser::fetch_string<T>();
@@ -213,6 +225,7 @@ class TlBufferParser : public TlParser {
return T();
}
+
template <class T>
T fetch_string_raw(const size_t size) {
return TlParser::fetch_string_raw<T>(size);
@@ -221,12 +234,7 @@ class TlBufferParser : public TlParser {
private:
const BufferSlice *parent_;
- BufferSlice as_buffer_slice(Slice slice) {
- if (is_aligned_pointer<4>(slice.data())) {
- return parent_->from_slice(slice);
- }
- return BufferSlice(slice);
- }
+ BufferSlice as_buffer_slice(Slice slice);
};
template <>
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_storers.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_storers.h
index f389451d8a..6264226a01 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_storers.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_storers.h
@@ -1,14 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/int_types.h"
+#include "td/utils/common.h"
#include "td/utils/logging.h"
-#include "td/utils/misc.h"
#include "td/utils/Slice.h"
#include "td/utils/StorerBase.h"
@@ -17,11 +16,10 @@
namespace td {
class TlStorerUnsafe {
- char *buf;
+ unsigned char *buf_;
public:
- explicit TlStorerUnsafe(char *buf) : buf(buf) {
- CHECK(is_aligned_pointer<4>(buf));
+ explicit TlStorerUnsafe(unsigned char *buf) : buf_(buf) {
}
TlStorerUnsafe(const TlStorerUnsafe &other) = delete;
@@ -29,13 +27,12 @@ class TlStorerUnsafe {
template <class T>
void store_binary(const T &x) {
- std::memcpy(buf, reinterpret_cast<const unsigned char *>(&x), sizeof(T));
- buf += sizeof(T);
+ std::memcpy(buf_, &x, sizeof(T));
+ buf_ += sizeof(T);
}
void store_int(int32 x) {
- *reinterpret_cast<int32 *>(buf) = x;
- buf += sizeof(int32);
+ store_binary<int32>(x);
}
void store_long(int64 x) {
@@ -43,45 +40,55 @@ class TlStorerUnsafe {
}
void store_slice(Slice slice) {
- std::memcpy(buf, slice.begin(), slice.size());
- buf += slice.size();
+ std::memcpy(buf_, slice.begin(), slice.size());
+ buf_ += slice.size();
}
+
void store_storer(const Storer &storer) {
- size_t size = storer.store(reinterpret_cast<unsigned char *>(buf));
- buf += size;
+ size_t size = storer.store(buf_);
+ buf_ += size;
}
template <class T>
void store_string(const T &str) {
size_t len = str.size();
if (len < 254) {
- *buf++ = static_cast<char>(len);
+ *buf_++ = static_cast<unsigned char>(len);
len++;
} else if (len < (1 << 24)) {
- *buf++ = static_cast<char>(static_cast<unsigned char>(254));
- *buf++ = static_cast<char>(len & 255);
- *buf++ = static_cast<char>((len >> 8) & 255);
- *buf++ = static_cast<char>(len >> 16);
+ *buf_++ = static_cast<unsigned char>(254);
+ *buf_++ = static_cast<unsigned char>(len & 255);
+ *buf_++ = static_cast<unsigned char>((len >> 8) & 255);
+ *buf_++ = static_cast<unsigned char>(len >> 16);
+ } else if (static_cast<uint64>(len) < (static_cast<uint64>(1) << 32)) {
+ *buf_++ = static_cast<unsigned char>(255);
+ *buf_++ = static_cast<unsigned char>(len & 255);
+ *buf_++ = static_cast<unsigned char>((len >> 8) & 255);
+ *buf_++ = static_cast<unsigned char>((len >> 16) & 255);
+ *buf_++ = static_cast<unsigned char>((len >> 24) & 255);
+ *buf_++ = static_cast<unsigned char>(0);
+ *buf_++ = static_cast<unsigned char>(0);
+ *buf_++ = static_cast<unsigned char>(0);
} else {
LOG(FATAL) << "String size " << len << " is too big to be stored";
}
- std::memcpy(buf, str.data(), str.size());
- buf += str.size();
+ std::memcpy(buf_, str.data(), str.size());
+ buf_ += str.size();
switch (len & 3) {
case 1:
- *buf++ = '\0';
- // fallthrough
+ *buf_++ = 0;
+ // fallthrough
case 2:
- *buf++ = '\0';
- // fallthrough
+ *buf_++ = 0;
+ // fallthrough
case 3:
- *buf++ = '\0';
+ *buf_++ = 0;
}
}
- char *get_buf() const {
- return buf;
+ unsigned char *get_buf() const {
+ return buf_;
}
};
@@ -119,8 +126,10 @@ class TlStorerCalcLength {
size_t add = str.size();
if (add < 254) {
add += 1;
- } else {
+ } else if (add < (1 << 24)) {
add += 4;
+ } else {
+ add += 8;
}
add = (add + 3) & -4;
length += add;
@@ -131,138 +140,6 @@ class TlStorerCalcLength {
}
};
-class TlStorerToString {
- std::string result;
- int shift = 0;
-
- void store_field_begin(const char *name) {
- for (int i = 0; i < shift; i++) {
- result += ' ';
- }
- if (name && name[0]) {
- result += name;
- result += " = ";
- }
- }
-
- void store_field_end() {
- result += "\n";
- }
-
- void store_long(int64 value) {
- result += (PSLICE() << value).c_str();
- }
-
- void store_binary(Slice data) {
- static const char *hex = "0123456789ABCDEF";
-
- result.append("{ ");
- for (auto c : data) {
- unsigned char byte = c;
- result += hex[byte >> 4];
- result += hex[byte & 15];
- result += ' ';
- }
- result.append("}");
- }
-
- public:
- TlStorerToString() = default;
- TlStorerToString(const TlStorerToString &other) = delete;
- TlStorerToString &operator=(const TlStorerToString &other) = delete;
-
- void store_field(const char *name, bool value) {
- store_field_begin(name);
- result += (value ? "true" : "false");
- store_field_end();
- }
-
- void store_field(const char *name, int32 value) {
- store_field(name, static_cast<int64>(value));
- }
-
- void store_field(const char *name, int64 value) {
- store_field_begin(name);
- store_long(value);
- store_field_end();
- }
-
- void store_field(const char *name, double value) {
- store_field_begin(name);
- result += (PSLICE() << value).c_str();
- store_field_end();
- }
-
- void store_field(const char *name, const char *value) {
- store_field_begin(name);
- result += value;
- store_field_end();
- }
-
- void store_field(const char *name, const string &value) {
- store_field_begin(name);
- result += '"';
- result.append(value.data(), value.size());
- result += '"';
- store_field_end();
- }
-
- template <class T>
- void store_field(const char *name, const T &value) {
- store_field_begin(name);
- result.append(value.data(), value.size());
- store_field_end();
- }
-
- template <class BytesT>
- void store_bytes_field(const char *name, const BytesT &value) {
- static const char *hex = "0123456789ABCDEF";
-
- store_field_begin(name);
- result.append("bytes { ");
- for (size_t i = 0; i < value.size(); i++) {
- int b = value[static_cast<int>(i)] & 0xff;
- result += hex[b >> 4];
- result += hex[b & 15];
- result += ' ';
- }
- result.append("}");
- store_field_end();
- }
-
- void store_field(const char *name, const UInt128 &value) {
- store_field_begin(name);
- store_binary(Slice(reinterpret_cast<const unsigned char *>(&value), sizeof(value)));
- store_field_end();
- }
-
- void store_field(const char *name, const UInt256 &value) {
- store_field_begin(name);
- store_binary(Slice(reinterpret_cast<const unsigned char *>(&value), sizeof(value)));
- store_field_end();
- }
-
- void store_class_begin(const char *field_name, const char *class_name) {
- store_field_begin(field_name);
- result += class_name;
- result += " {\n";
- shift += 2;
- }
-
- void store_class_end() {
- shift -= 2;
- for (int i = 0; i < shift; i++) {
- result += ' ';
- }
- result += "}\n";
- CHECK(shift >= 0);
- }
-
- std::string str() const {
- return result;
- }
-};
-
template <class T>
size_t tl_calc_length(const T &data) {
TlStorerCalcLength storer_calc_length;
@@ -270,12 +147,14 @@ size_t tl_calc_length(const T &data) {
return storer_calc_length.get_length();
}
-template <class T, class CharT>
-size_t tl_store_unsafe(const T &data, CharT *dst) {
- char *start = reinterpret_cast<char *>(dst);
- TlStorerUnsafe storer_unsafe(start);
+template <class T>
+size_t tl_store_unsafe(const T &data, unsigned char *dst) TD_WARN_UNUSED_RESULT;
+
+template <class T>
+size_t tl_store_unsafe(const T &data, unsigned char *dst) {
+ TlStorerUnsafe storer_unsafe(dst);
data.store(storer_unsafe);
- return storer_unsafe.get_buf() - start;
+ return static_cast<size_t>(storer_unsafe.get_buf() - dst);
}
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.cpp
new file mode 100644
index 0000000000..65cfcf0074
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.cpp
@@ -0,0 +1,115 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/translit.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/misc.h"
+#include "td/utils/utf8.h"
+
+#include <algorithm>
+#include <utility>
+
+namespace td {
+
+static const FlatHashMap<uint32, string> &get_en_to_ru_simple_rules() {
+ static const FlatHashMap<uint32, string> rules{
+ {'a', "а"}, {'b', "б"}, {'c', "к"}, {'d', "д"}, {'e', "е"}, {'f', "ф"}, {'g', "г"}, {'h', "х"}, {'i', "и"},
+ {'j', "й"}, {'k', "к"}, {'l', "л"}, {'m', "м"}, {'n', "н"}, {'o', "о"}, {'p', "п"}, {'q', "к"}, {'r', "р"},
+ {'s', "с"}, {'t', "т"}, {'u', "у"}, {'v', "в"}, {'w', "в"}, {'x', "кс"}, {'y', "и"}, {'z', "з"}};
+ return rules;
+}
+
+static const std::vector<std::pair<string, string>> &get_en_to_ru_complex_rules() {
+ static const std::vector<std::pair<string, string>> rules{
+ {"ch", "ч"}, {"ei", "ей"}, {"ey", "ей"}, {"ia", "ия"}, {"iy", "ий"}, {"jo", "е"},
+ {"ju", "ю"}, {"ja", "я"}, {"kh", "х"}, {"shch", "щ"}, {"sh", "ш"}, {"sch", "щ"},
+ {"ts", "ц"}, {"yo", "е"}, {"yu", "ю"}, {"ya", "я"}, {"zh", "ж"}};
+ return rules;
+}
+
+static const FlatHashMap<uint32, string> &get_ru_to_en_simple_rules() {
+ static const FlatHashMap<uint32, string> rules{
+ {0x430, "a"}, {0x431, "b"}, {0x432, "v"}, {0x433, "g"}, {0x434, "d"}, {0x435, "e"}, {0x451, "e"},
+ {0x436, "zh"}, {0x437, "z"}, {0x438, "i"}, {0x439, "y"}, {0x43a, "k"}, {0x43b, "l"}, {0x43c, "m"},
+ {0x43d, "n"}, {0x43e, "o"}, {0x43f, "p"}, {0x440, "r"}, {0x441, "s"}, {0x442, "t"}, {0x443, "u"},
+ {0x444, "f"}, {0x445, "kh"}, {0x446, "ts"}, {0x447, "ch"}, {0x448, "sh"}, {0x449, "sch"}, {0x44a, ""},
+ {0x44b, "y"}, {0x44c, ""}, {0x44d, "e"}, {0x44e, "yu"}, {0x44f, "ya"}};
+ return rules;
+}
+
+static const std::vector<std::pair<string, string>> &get_ru_to_en_complex_rules() {
+ static const std::vector<std::pair<string, string>> rules{
+ {"ий", "y"}, {"ия", "ia"}, {"кс", "x"}, {"yo", "e"}, {"jo", "e"}};
+ return rules;
+}
+
+static void add_word_transliterations(vector<string> &result, Slice word, bool allow_partial,
+ const FlatHashMap<uint32, string> &simple_rules,
+ const vector<std::pair<string, string>> &complex_rules) {
+ string s;
+ auto pos = word.ubegin();
+ auto end = word.uend();
+ while (pos != end) {
+ uint32 code;
+ pos = next_utf8_unsafe(pos, &code);
+ auto it = simple_rules.find(code);
+ if (it != simple_rules.end()) {
+ s += it->second;
+ } else {
+ append_utf8_character(s, code);
+ }
+ }
+ if (!s.empty()) {
+ result.push_back(std::move(s));
+ s.clear();
+ }
+
+ pos = word.ubegin();
+ while (pos != end) {
+ auto suffix = Slice(pos, end);
+ bool found = false;
+ for (auto &rule : complex_rules) {
+ if (begins_with(suffix, rule.first)) {
+ found = true;
+ pos += rule.first.size();
+ s.append(rule.second);
+ break;
+ }
+ if (allow_partial && begins_with(rule.first, suffix)) {
+ result.push_back(s + rule.second);
+ }
+ }
+ if (found) {
+ continue;
+ }
+
+ uint32 code;
+ pos = next_utf8_unsafe(pos, &code);
+ auto it = simple_rules.find(code);
+ if (it != simple_rules.end()) {
+ s += it->second;
+ } else {
+ append_utf8_character(s, code);
+ }
+ }
+ if (!s.empty()) {
+ result.push_back(std::move(s));
+ }
+}
+
+vector<string> get_word_transliterations(Slice word, bool allow_partial) {
+ vector<string> result;
+
+ add_word_transliterations(result, word, allow_partial, get_en_to_ru_simple_rules(), get_en_to_ru_complex_rules());
+ add_word_transliterations(result, word, allow_partial, get_ru_to_en_simple_rules(), get_ru_to_en_complex_rules());
+
+ td::unique(result);
+ return result;
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.h
new file mode 100644
index 0000000000..e72cb5c8e5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.h
@@ -0,0 +1,16 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+#include "td/utils/Slice.h"
+
+namespace td {
+
+vector<string> get_word_transliterations(Slice word, bool allow_partial);
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/type_traits.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/type_traits.h
index ef9c159420..71bbf77b79 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/type_traits.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/type_traits.h
@@ -1,22 +1,41 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+#include "td/utils/int_types.h"
+
+#include <type_traits>
+
namespace td {
template <class FunctionT>
struct member_function_class;
-template <class ReturnType, class Type>
-struct member_function_class<ReturnType Type::*> {
+template <class ReturnType, class Type, class... Args>
+struct member_function_class<ReturnType (Type::*)(Args...)> {
using type = Type;
+ static constexpr size_t argument_count() {
+ return sizeof...(Args);
+ }
};
template <class FunctionT>
using member_function_class_t = typename member_function_class<FunctionT>::type;
+template <class FunctionT>
+constexpr size_t member_function_argument_count() {
+ return member_function_class<FunctionT>::argument_count();
+}
+
+// no std::is_trivially_copyable in libstdc++ before 5.0
+#if __GLIBCXX__
+#define TD_IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T)
+#else
+#define TD_IS_TRIVIALLY_COPYABLE(T) ::std::is_trivially_copyable<T>::value
+#endif
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/uint128.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/uint128.h
new file mode 100644
index 0000000000..902900ef27
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/uint128.h
@@ -0,0 +1,293 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/bits.h"
+#include "td/utils/common.h"
+
+#include <limits>
+#include <type_traits>
+
+namespace td {
+
+class uint128_emulated {
+ public:
+ using uint128 = uint128_emulated;
+ uint128_emulated(uint64 hi, uint64 lo) : hi_(hi), lo_(lo) {
+ }
+ template <class T, typename = std::enable_if_t<std::is_unsigned<T>::value>>
+ uint128_emulated(T lo) : uint128_emulated(0, lo) {
+ }
+ uint128_emulated() = default;
+
+ uint64 hi() const {
+ return hi_;
+ }
+ uint64 lo() const {
+ return lo_;
+ }
+ uint64 rounded_hi() const {
+ return hi_ + (lo_ >> 63);
+ }
+ static uint128 from_signed(int64 x) {
+ if (x >= 0) {
+ return uint128(0, x);
+ }
+ return uint128(std::numeric_limits<uint64>::max(), static_cast<uint64>(x));
+ }
+ static uint128 from_unsigned(uint64 x) {
+ return uint128(0, x);
+ }
+
+ uint128 add(uint128 other) const {
+ uint128 res(other.hi() + hi(), other.lo() + lo());
+ if (res.lo() < lo()) {
+ res.hi_++;
+ }
+ return res;
+ }
+
+ uint128 shl(int cnt) const {
+ if (cnt == 0) {
+ return *this;
+ }
+ if (cnt < 64) {
+ return uint128((hi() << cnt) | (lo() >> (64 - cnt)), lo() << cnt);
+ }
+ if (cnt < 128) {
+ return uint128(lo() << (cnt - 64), 0);
+ }
+ return uint128();
+ }
+ uint128 shr(int cnt) const {
+ if (cnt == 0) {
+ return *this;
+ }
+ if (cnt < 64) {
+ return uint128(hi() >> cnt, (lo() >> cnt) | (hi() << (64 - cnt)));
+ }
+ if (cnt < 128) {
+ return uint128(0, hi() >> (cnt - 64));
+ }
+ return uint128();
+ }
+
+ uint128 mult(uint128 other) const {
+ uint64 a_lo = lo() & 0xffffffff;
+ uint64 a_hi = lo() >> 32;
+ uint64 b_lo = other.lo() & 0xffffffff;
+ uint64 b_hi = other.lo() >> 32;
+ uint128 res(lo() * other.hi() + hi() * other.lo() + a_hi * b_hi, a_lo * b_lo);
+ uint128 add1(0, a_lo * b_hi);
+ uint128 add2(0, a_hi * b_lo);
+ return res.add(add1.shl(32)).add(add2.shl(32));
+ }
+ uint128 mult(uint64 other) const {
+ return mult(uint128(0, other));
+ }
+ uint128 mult_signed(int64 other) const {
+ return mult(uint128::from_signed(other));
+ }
+ bool is_zero() const {
+ return lo() == 0 && hi() == 0;
+ }
+ uint128 sub(uint128 other) const {
+ uint32 carry = 0;
+ if (other.lo() > lo()) {
+ carry = 1;
+ }
+ return uint128(hi() - other.hi() - carry, lo() - other.lo());
+ }
+ void divmod(uint128 other, uint128 *div_res, uint128 *mod_res) const {
+ CHECK(!other.is_zero());
+
+ auto from = *this;
+ auto ctz = from.count_leading_zeroes();
+ auto other_ctz = other.count_leading_zeroes();
+ if (ctz > other_ctz) {
+ *div_res = uint128();
+ *mod_res = from;
+ return;
+ }
+ auto shift = other_ctz - ctz;
+ auto res = uint128();
+ for (int i = shift; i >= 0; i--) {
+ auto sub = other.shl(i);
+ res = res.shl(1);
+ if (from.greater_or_equal(sub)) {
+ from = from.sub(sub);
+ res = res.set_lower_bit();
+ }
+ }
+
+ *div_res = res;
+ *mod_res = from;
+ }
+ uint128 div(uint128 other) const {
+ uint128 a;
+ uint128 b;
+ divmod(other, &a, &b);
+ return a;
+ }
+ uint128 mod(uint128 other) const {
+ uint128 a;
+ uint128 b;
+ divmod(other, &a, &b);
+ return b;
+ }
+
+ void divmod_signed(int64 y, int64 *quot, int64 *rem) const {
+ CHECK(y != 0);
+ auto x = *this;
+ int x_sgn = x.is_negative();
+ int y_sgn = y < 0;
+ if (x_sgn) {
+ x = x.negate();
+ }
+ uint128 uy = from_signed(y);
+ if (uy.is_negative()) {
+ uy = uy.negate();
+ }
+
+ uint128 t_quot;
+ uint128 t_mod;
+ x.divmod(uy, &t_quot, &t_mod);
+ *quot = t_quot.lo();
+ *rem = t_mod.lo();
+ if (x_sgn != y_sgn) {
+ *quot = -*quot;
+ }
+ if (x_sgn) {
+ *rem = -*rem;
+ }
+ }
+
+ private:
+ uint64 hi_{0};
+ uint64 lo_{0};
+
+ bool is_negative() const {
+ return (hi_ >> 63) == 1;
+ }
+
+ int32 count_leading_zeroes() const {
+ if (hi() == 0) {
+ return 64 + count_leading_zeroes64(lo());
+ }
+ return count_leading_zeroes64(hi());
+ }
+ uint128 set_lower_bit() const {
+ return uint128(hi(), lo() | 1);
+ }
+ bool greater_or_equal(uint128 other) const {
+ return hi() > other.hi() || (hi() == other.hi() && lo() >= other.lo());
+ }
+ uint128 negate() const {
+ uint128 res(~hi(), ~lo() + 1);
+ if (res.lo() == 0) {
+ return uint128(res.hi() + 1, 0);
+ }
+ return res;
+ }
+};
+
+#if TD_HAVE_INT128
+class uint128_intrinsic {
+ public:
+ using ValueT = unsigned __int128;
+ using uint128 = uint128_intrinsic;
+ explicit uint128_intrinsic(ValueT value) : value_(value) {
+ }
+ uint128_intrinsic(uint64 hi, uint64 lo) : value_((ValueT(hi) << 64) | lo) {
+ }
+ uint128_intrinsic() = default;
+
+ static uint128 from_signed(int64 x) {
+ return uint128(static_cast<ValueT>(x));
+ }
+ static uint128 from_unsigned(uint64 x) {
+ return uint128(static_cast<ValueT>(x));
+ }
+ uint64 hi() const {
+ return uint64(value() >> 64);
+ }
+ uint64 lo() const {
+ return uint64(value() & std::numeric_limits<uint64>::max());
+ }
+ uint64 rounded_hi() const {
+ return uint64((value() + (1ULL << 63)) >> 64);
+ }
+ uint128 add(uint128 other) const {
+ return uint128(value() + other.value());
+ }
+ uint128 sub(uint128 other) const {
+ return uint128(value() - other.value());
+ }
+
+ uint128 shl(int cnt) const {
+ if (cnt >= 128) {
+ return uint128();
+ }
+ return uint128(value() << cnt);
+ }
+
+ uint128 shr(int cnt) const {
+ if (cnt >= 128) {
+ return uint128();
+ }
+ return uint128(value() >> cnt);
+ }
+
+ uint128 mult(uint128 other) const {
+ return uint128(value() * other.value());
+ }
+ uint128 mult(uint64 other) const {
+ return uint128(value() * other);
+ }
+ uint128 mult_signed(int64 other) const {
+ return uint128(value() * other);
+ }
+ bool is_zero() const {
+ return value() == 0;
+ }
+ void divmod(uint128 other, uint128 *div_res, uint128 *mod_res) const {
+ CHECK(!other.is_zero());
+ *div_res = uint128(value() / other.value());
+ *mod_res = uint128(value() % other.value());
+ }
+ uint128 div(uint128 other) const {
+ CHECK(!other.is_zero());
+ return uint128(value() / other.value());
+ }
+ uint128 mod(uint128 other) const {
+ CHECK(!other.is_zero());
+ return uint128(value() % other.value());
+ }
+ void divmod_signed(int64 y, int64 *quot, int64 *rem) const {
+ CHECK(y != 0);
+ *quot = static_cast<int64>(signed_value() / y);
+ *rem = static_cast<int64>(signed_value() % y);
+ }
+
+ private:
+ unsigned __int128 value_{0};
+ ValueT value() const {
+ return value_;
+ }
+ __int128 signed_value() const {
+ return static_cast<__int128>(value());
+ }
+};
+#endif
+
+#if TD_HAVE_INT128
+using uint128 = uint128_intrinsic;
+#else
+using uint128 = uint128_emulated;
+#endif
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.cpp
index 11e76b7979..1b16809f0c 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,130 +8,209 @@
#include "td/utils/logging.h"
-#include <algorithm>
-#include <iterator>
-
namespace td {
// list of [(range_begin << 5) + range_type]
static const uint32 unicode_simple_category_ranges[] = {
- 0, 1028, 1056, 1538, 1856, 2081, 2912, 3105, 3936, 5124, 5152, 5441,
- 5472, 5699, 5760, 5793, 5824, 5923, 5953, 5984, 6019, 6112, 6145, 6880,
- 6913, 7904, 7937, 22592, 22721, 23104, 23553, 23712, 23937, 23968, 24001, 24032,
- 28161, 28320, 28353, 28416, 28481, 28608, 28641, 28672, 28865, 28896, 28929, 29024,
- 29057, 29088, 29121, 29760, 29793, 32448, 32481, 36928, 37185, 42496, 42529, 43744,
- 43809, 43840, 44065, 45312, 47617, 48480, 48641, 48736, 50177, 51552, 52226, 52544,
- 52673, 52736, 52769, 55936, 55969, 56000, 56481, 56544, 56769, 56834, 57153, 57248,
- 57313, 57344, 57857, 57888, 57921, 58880, 59809, 62656, 63009, 63040, 63490, 63809,
- 64864, 65153, 65216, 65345, 65376, 65537, 66240, 66369, 66400, 66689, 66720, 66817,
- 66848, 67585, 68384, 70657, 71328, 71361, 71616, 73857, 75584, 75681, 75712, 76289,
- 76320, 76545, 76864, 76994, 77312, 77345, 77856, 77985, 78240, 78305, 78368, 78433,
- 79136, 79169, 79392, 79425, 79456, 79553, 79680, 79777, 79808, 80321, 80352, 80769,
- 80832, 80865, 80960, 81090, 81409, 81472, 81539, 81728, 82081, 82272, 82401, 82464,
- 82529, 83232, 83265, 83488, 83521, 83584, 83617, 83680, 83713, 83776, 84769, 84896,
- 84929, 84960, 85186, 85504, 85569, 85664, 86177, 86464, 86497, 86592, 86625, 87328,
- 87361, 87584, 87617, 87680, 87713, 87872, 87969, 88000, 88577, 88608, 89089, 89152,
- 89282, 89600, 89889, 89920, 90273, 90528, 90593, 90656, 90721, 91424, 91457, 91680,
- 91713, 91776, 91809, 91968, 92065, 92096, 93057, 93120, 93153, 93248, 93378, 93696,
- 93729, 93763, 93952, 94305, 94336, 94369, 94560, 94657, 94752, 94785, 94912, 95009,
- 95072, 95105, 95136, 95169, 95232, 95329, 95392, 95489, 95584, 95681, 96064, 96769,
- 96800, 97474, 97795, 97888, 98465, 98720, 98753, 98848, 98881, 99616, 99649, 100160,
- 100257, 100288, 101121, 101216, 101377, 101440, 101570, 101888, 102147, 102368, 102401, 102432,
- 102561, 102816, 102849, 102944, 102977, 103712, 103745, 104064, 104097, 104256, 104353, 104384,
- 105409, 105440, 105473, 105536, 105666, 105984, 106017, 106080, 106657, 106912, 106945, 107040,
- 107073, 108384, 108449, 108480, 108993, 109024, 109185, 109280, 109315, 109537, 109632, 109762,
- 110083, 110368, 110401, 110592, 110753, 111328, 111425, 112192, 112225, 112512, 112545, 112576,
- 112641, 112864, 113858, 114176, 114721, 116256, 116289, 116352, 116737, 116960, 117250, 117568,
- 118817, 118880, 118913, 118944, 119009, 119072, 119105, 119136, 119201, 119232, 119425, 119552,
- 119585, 119808, 119841, 119936, 119969, 120000, 120033, 120064, 120129, 120192, 120225, 120352,
- 120385, 120448, 120737, 120768, 120833, 120992, 121025, 121056, 121346, 121664, 121729, 121856,
- 122881, 122912, 123906, 124227, 124544, 124929, 125184, 125217, 126368, 127233, 127392, 131073,
- 132448, 133089, 133122, 133440, 133633, 133824, 133953, 134080, 134177, 134208, 134305, 134368,
- 134593, 134688, 134817, 135232, 135617, 135648, 135682, 136000, 136193, 137408, 137441, 137472,
- 137633, 137664, 137729, 139104, 139137, 149792, 149825, 149952, 150017, 150240, 150273, 150304,
- 150337, 150464, 150529, 151840, 151873, 152000, 152065, 153120, 153153, 153280, 153345, 153568,
- 153601, 153632, 153665, 153792, 153857, 154336, 154369, 156192, 156225, 156352, 156417, 158560,
- 159011, 159648, 159745, 160256, 160769, 163520, 163585, 163776, 163873, 183712, 183777, 184324,
- 184353, 185184, 185345, 187744, 187843, 187937, 188192, 188417, 188832, 188865, 188992, 189441,
- 190016, 190465, 191040, 191489, 191904, 191937, 192032, 192513, 194176, 195297, 195328, 195457,
- 195488, 195586, 195904, 196099, 196416, 197122, 197440, 197633, 200448, 200705, 200864, 200929,
- 202016, 202049, 202080, 202241, 204480, 204801, 205792, 207042, 207361, 208320, 208385, 208544,
- 208897, 210304, 210433, 211264, 211458, 211779, 211808, 212993, 213728, 214017, 215712, 217090,
- 217408, 217602, 217920, 218337, 218368, 221345, 222848, 223393, 223616, 223746, 224064, 225377,
- 226336, 226753, 226818, 227137, 228544, 229377, 230528, 231426, 231744, 231841, 231938, 232257,
- 233408, 233473, 233760, 236833, 236960, 236993, 237120, 237217, 237280, 237569, 243712, 245761,
- 254656, 254721, 254912, 254977, 256192, 256257, 256448, 256513, 256768, 256801, 256832, 256865,
- 256896, 256929, 256960, 256993, 257984, 258049, 259744, 259777, 260000, 260033, 260064, 260161,
- 260256, 260289, 260512, 260609, 260736, 260801, 260992, 261121, 261536, 261697, 261792, 261825,
- 262048, 262148, 262496, 263428, 263488, 263652, 263680, 265188, 265216, 265731, 265761, 265792,
- 265859, 266048, 266209, 266243, 266560, 266753, 267168, 270401, 270432, 270561, 270592, 270657,
- 270976, 271009, 271040, 271137, 271296, 271489, 271520, 271553, 271584, 271617, 271648, 271681,
- 271808, 271841, 272192, 272257, 272384, 272545, 272704, 272833, 272864, 272899, 274529, 274595,
- 274752, 297987, 299904, 302403, 303104, 323267, 324224, 360449, 361952, 361985, 363488, 363521,
- 367776, 367969, 368096, 368193, 368256, 368547, 368576, 368641, 369856, 369889, 369920, 370081,
- 370112, 370177, 371968, 372193, 372224, 372737, 373472, 373761, 373984, 374017, 374240, 374273,
- 374496, 374529, 374752, 374785, 375008, 375041, 375264, 375297, 375520, 375553, 375776, 378337,
- 378368, 393220, 393248, 393377, 393443, 393472, 394275, 394560, 394785, 394944, 395011, 395105,
- 395168, 395297, 398048, 398241, 398336, 398369, 401248, 401281, 401408, 401569, 402880, 402977,
- 405984, 406083, 406208, 406529, 407392, 409089, 409600, 410627, 410944, 411907, 412160, 412195,
- 412672, 413699, 414016, 415267, 415744, 425985, 636608, 638977, 1309376, 1310721, 1348000, 1350145,
- 1351616, 1351681, 1360288, 1360385, 1360898, 1361217, 1361280, 1361921, 1363424, 1363937, 1364928, 1364993,
- 1367235, 1367552, 1368801, 1369088, 1369153, 1372448, 1372513, 1373664, 1373697, 1373952, 1375969, 1376320,
- 1376353, 1376448, 1376481, 1376608, 1376641, 1377376, 1377795, 1377984, 1378305, 1379968, 1380417, 1382016,
- 1382914, 1383232, 1384001, 1384192, 1384289, 1384320, 1384353, 1384384, 1384450, 1384769, 1385664, 1385985,
- 1386720, 1387521, 1388448, 1388673, 1390176, 1391073, 1391106, 1391424, 1391617, 1391776, 1391809, 1392130,
- 1392449, 1392608, 1392641, 1393952, 1394689, 1394784, 1394817, 1395072, 1395202, 1395520, 1395713, 1396448,
- 1396545, 1396576, 1396673, 1398272, 1398305, 1398336, 1398433, 1398496, 1398561, 1398720, 1398785, 1398816,
- 1398849, 1398880, 1399649, 1399744, 1399809, 1400160, 1400385, 1400480, 1400865, 1401056, 1401121, 1401312,
- 1401377, 1401568, 1401857, 1402080, 1402113, 1402336, 1402369, 1403744, 1403777, 1404096, 1404417, 1408096,
- 1408514, 1408832, 1409025, 1766528, 1766913, 1767648, 1767777, 1769344, 2039809, 2051520, 2051585, 2054976,
- 2056193, 2056416, 2056801, 2056960, 2057121, 2057152, 2057185, 2057504, 2057537, 2057952, 2057985, 2058144,
- 2058177, 2058208, 2058241, 2058304, 2058337, 2058400, 2058433, 2061888, 2062945, 2074560, 2075137, 2077184,
- 2077249, 2078976, 2080257, 2080640, 2084353, 2084512, 2084545, 2088864, 2089474, 2089792, 2090017, 2090848,
- 2091041, 2091872, 2092225, 2095072, 2095169, 2095360, 2095425, 2095616, 2095681, 2095872, 2095937, 2096032,
- 2097153, 2097536, 2097569, 2098400, 2098433, 2099040, 2099073, 2099136, 2099169, 2099648, 2099713, 2100160,
- 2101249, 2105184, 2105571, 2107008, 2107395, 2109216, 2109763, 2109824, 2117633, 2118560, 2118657, 2120224,
- 2120739, 2121600, 2121729, 2122755, 2122880, 2123265, 2123811, 2123841, 2124099, 2124128, 2124289, 2125504,
- 2125825, 2126784, 2126849, 2128000, 2128129, 2128384, 2128419, 2128576, 2129921, 2134976, 2135042, 2135360,
- 2135553, 2136704, 2136833, 2137984, 2138113, 2139392, 2139649, 2141312, 2146305, 2156256, 2156545, 2157248,
- 2157569, 2157824, 2162689, 2162880, 2162945, 2162976, 2163009, 2164416, 2164449, 2164512, 2164609, 2164640,
- 2164705, 2165440, 2165507, 2165761, 2166496, 2166563, 2166785, 2167776, 2168035, 2168320, 2169857, 2170464,
- 2170497, 2170560, 2170723, 2170881, 2171587, 2171776, 2171905, 2172736, 2174977, 2176768, 2176899, 2176961,
- 2177027, 2177536, 2177603, 2179073, 2179104, 2179585, 2179712, 2179745, 2179840, 2179873, 2180736, 2181123,
- 2181376, 2182145, 2183075, 2183136, 2183169, 2184099, 2184192, 2185217, 2185472, 2185505, 2186400, 2186595,
- 2186752, 2187265, 2188992, 2189313, 2190016, 2190083, 2190337, 2190944, 2191107, 2191361, 2191936, 2192675,
- 2192896, 2195457, 2197792, 2199553, 2201184, 2201601, 2203232, 2203459, 2203648, 2214915, 2215904, 2228321,
- 2230016, 2230851, 2231490, 2231808, 2232417, 2233856, 2234881, 2235680, 2235906, 2236224, 2236513, 2237664,
- 2238146, 2238464, 2238977, 2240096, 2240193, 2240224, 2240609, 2242144, 2242593, 2242720, 2243074, 2243393,
- 2243424, 2243457, 2243488, 2243619, 2244256, 2244609, 2245184, 2245217, 2246016, 2248705, 2248928, 2248961,
- 2248992, 2249025, 2249152, 2249185, 2249664, 2249697, 2250016, 2250241, 2251744, 2252290, 2252608, 2252961,
- 2253216, 2253281, 2253344, 2253409, 2254112, 2254145, 2254368, 2254401, 2254464, 2254497, 2254656, 2254753,
- 2254784, 2255361, 2255392, 2255777, 2255936, 2260993, 2262688, 2263265, 2263392, 2263554, 2263872, 2265089,
- 2266624, 2267265, 2267328, 2267361, 2267392, 2267650, 2267968, 2273281, 2274784, 2276097, 2276224, 2277377,
- 2278912, 2279553, 2279584, 2279938, 2280256, 2281473, 2282848, 2283522, 2283840, 2285569, 2286400, 2287106,
- 2287427, 2287488, 2298881, 2300930, 2301251, 2301536, 2301921, 2301952, 2316289, 2318112, 2326529, 2326816,
- 2326849, 2328032, 2328577, 2328608, 2329090, 2329411, 2330016, 2330177, 2331136, 2359297, 2388800, 2392067,
- 2395616, 2396161, 2402432, 2490369, 2524640, 2654209, 2672864, 2949121, 2967328, 2967553, 2968544, 2968578,
- 2968896, 2972161, 2973120, 2973697, 2975232, 2975745, 2975872, 2976258, 2976576, 2976611, 2976832, 2976865,
- 2977536, 2977697, 2978304, 3006465, 3008672, 3009025, 3009056, 3011169, 3011584, 3013633, 3013664, 3014657,
- 3210656, 3211265, 3235424, 3538945, 3539008, 3637249, 3640672, 3640833, 3641248, 3641345, 3641632, 3641857,
- 3642176, 3828739, 3829312, 3833857, 3836576, 3836609, 3838880, 3838913, 3838976, 3839041, 3839072, 3839137,
- 3839200, 3839265, 3839392, 3839425, 3839808, 3839841, 3839872, 3839905, 3840128, 3840161, 3842240, 3842273,
- 3842400, 3842465, 3842720, 3842753, 3842976, 3843009, 3843904, 3843937, 3844064, 3844097, 3844256, 3844289,
- 3844320, 3844417, 3844640, 3844673, 3855552, 3855617, 3856416, 3856449, 3857248, 3857281, 3858272, 3858305,
- 3859104, 3859137, 3860128, 3860161, 3860960, 3860993, 3861984, 3862017, 3862816, 3862849, 3863840, 3863873,
- 3864672, 3864705, 3864960, 3865026, 3866624, 3997697, 4004000, 4004067, 4004352, 4005889, 4008064, 4008450,
- 4008768, 4046849, 4046976, 4047009, 4047872, 4047905, 4047968, 4048001, 4048032, 4048097, 4048128, 4048161,
- 4048480, 4048513, 4048640, 4048673, 4048704, 4048737, 4048768, 4048961, 4048992, 4049121, 4049152, 4049185,
- 4049216, 4049249, 4049280, 4049313, 4049408, 4049441, 4049504, 4049537, 4049568, 4049633, 4049664, 4049697,
- 4049728, 4049761, 4049792, 4049825, 4049856, 4049889, 4049920, 4049953, 4050016, 4050049, 4050080, 4050145,
- 4050272, 4050305, 4050528, 4050561, 4050688, 4050721, 4050848, 4050881, 4050912, 4050945, 4051264, 4051297,
- 4051840, 4052001, 4052096, 4052129, 4052288, 4052321, 4052864, 4071427, 4071840, 4194305, 5561056, 5562369,
- 5695136, 5695489, 5702592, 5702657, 5887040, 6225921, 6243264, 4294967295};
+ 0, 1028, 1056, 1538, 1856, 2081, 2912, 3105, 3936, 5124, 5152, 5441,
+ 5472, 5699, 5760, 5793, 5824, 5923, 5953, 5984, 6019, 6112, 6145, 6880,
+ 6913, 7904, 7937, 22592, 22721, 23104, 23553, 23712, 23937, 23968, 24001, 24032,
+ 28161, 28320, 28353, 28416, 28481, 28608, 28641, 28672, 28865, 28896, 28929, 29024,
+ 29057, 29088, 29121, 29760, 29793, 32448, 32481, 36928, 37185, 42496, 42529, 43744,
+ 43809, 43840, 44033, 45344, 47617, 48480, 48609, 48736, 50177, 51552, 52226, 52544,
+ 52673, 52736, 52769, 55936, 55969, 56000, 56481, 56544, 56769, 56834, 57153, 57248,
+ 57313, 57344, 57857, 57888, 57921, 58880, 59809, 62656, 63009, 63040, 63490, 63809,
+ 64864, 65153, 65216, 65345, 65376, 65537, 66240, 66369, 66400, 66689, 66720, 66817,
+ 66848, 67585, 68384, 68609, 68960, 69121, 69888, 69921, 70112, 70657, 72000, 73857,
+ 75584, 75681, 75712, 76289, 76320, 76545, 76864, 76994, 77312, 77345, 77856, 77985,
+ 78240, 78305, 78368, 78433, 79136, 79169, 79392, 79425, 79456, 79553, 79680, 79777,
+ 79808, 80321, 80352, 80769, 80832, 80865, 80960, 81090, 81409, 81472, 81539, 81728,
+ 81793, 81824, 82081, 82272, 82401, 82464, 82529, 83232, 83265, 83488, 83521, 83584,
+ 83617, 83680, 83713, 83776, 84769, 84896, 84929, 84960, 85186, 85504, 85569, 85664,
+ 86177, 86464, 86497, 86592, 86625, 87328, 87361, 87584, 87617, 87680, 87713, 87872,
+ 87969, 88000, 88577, 88608, 89089, 89152, 89282, 89600, 89889, 89920, 90273, 90528,
+ 90593, 90656, 90721, 91424, 91457, 91680, 91713, 91776, 91809, 91968, 92065, 92096,
+ 93057, 93120, 93153, 93248, 93378, 93696, 93729, 93763, 93952, 94305, 94336, 94369,
+ 94560, 94657, 94752, 94785, 94912, 95009, 95072, 95105, 95136, 95169, 95232, 95329,
+ 95392, 95489, 95584, 95681, 96064, 96769, 96800, 97474, 97795, 97888, 98465, 98720,
+ 98753, 98848, 98881, 99616, 99649, 100160, 100257, 100288, 101121, 101216, 101281, 101312,
+ 101377, 101440, 101570, 101888, 102147, 102368, 102401, 102432, 102561, 102816, 102849, 102944,
+ 102977, 103712, 103745, 104064, 104097, 104256, 104353, 104384, 105377, 105440, 105473, 105536,
+ 105666, 105984, 106017, 106080, 106625, 106912, 106945, 107040, 107073, 108384, 108449, 108480,
+ 108993, 109024, 109185, 109280, 109315, 109537, 109632, 109762, 110083, 110368, 110401, 110592,
+ 110753, 111328, 111425, 112192, 112225, 112512, 112545, 112576, 112641, 112864, 113858, 114176,
+ 114721, 116256, 116289, 116352, 116737, 116960, 117250, 117568, 118817, 118880, 118913, 118944,
+ 118977, 119136, 119169, 119936, 119969, 120000, 120033, 120352, 120385, 120448, 120737, 120768,
+ 120833, 120992, 121025, 121056, 121346, 121664, 121729, 121856, 122881, 122912, 123906, 124227,
+ 124544, 124929, 125184, 125217, 126368, 127233, 127392, 131073, 132448, 133089, 133122, 133440,
+ 133633, 133824, 133953, 134080, 134177, 134208, 134305, 134368, 134593, 134688, 134817, 135232,
+ 135617, 135648, 135682, 136000, 136193, 137408, 137441, 137472, 137633, 137664, 137729, 139104,
+ 139137, 149792, 149825, 149952, 150017, 150240, 150273, 150304, 150337, 150464, 150529, 151840,
+ 151873, 152000, 152065, 153120, 153153, 153280, 153345, 153568, 153601, 153632, 153665, 153792,
+ 153857, 154336, 154369, 156192, 156225, 156352, 156417, 158560, 159011, 159648, 159745, 160256,
+ 160769, 163520, 163585, 163776, 163873, 183712, 183777, 184324, 184353, 185184, 185345, 187744,
+ 187843, 187937, 188192, 188417, 188992, 189409, 190016, 190465, 191040, 191489, 191904, 191937,
+ 192032, 192513, 194176, 195297, 195328, 195457, 195488, 195586, 195904, 196099, 196416, 197122,
+ 197440, 197633, 200480, 200705, 200864, 200929, 202016, 202049, 202080, 202241, 204480, 204801,
+ 205792, 207042, 207361, 208320, 208385, 208544, 208897, 210304, 210433, 211264, 211458, 211779,
+ 211808, 212993, 213728, 214017, 215712, 217090, 217408, 217602, 217920, 218337, 218368, 221345,
+ 222848, 223393, 223648, 223746, 224064, 225377, 226336, 226753, 226818, 227137, 228544, 229377,
+ 230528, 231426, 231744, 231841, 231938, 232257, 233408, 233473, 233760, 233985, 235360, 235425,
+ 235520, 236833, 236960, 236993, 237184, 237217, 237280, 237377, 237408, 237569, 243712, 245761,
+ 254656, 254721, 254912, 254977, 256192, 256257, 256448, 256513, 256768, 256801, 256832, 256865,
+ 256896, 256929, 256960, 256993, 257984, 258049, 259744, 259777, 260000, 260033, 260064, 260161,
+ 260256, 260289, 260512, 260609, 260736, 260801, 260992, 261121, 261536, 261697, 261792, 261825,
+ 262048, 262148, 262496, 263428, 263488, 263652, 263680, 265188, 265216, 265731, 265761, 265792,
+ 265859, 266048, 266209, 266243, 266560, 266753, 267168, 270401, 270432, 270561, 270592, 270657,
+ 270976, 271009, 271040, 271137, 271296, 271489, 271520, 271553, 271584, 271617, 271648, 271681,
+ 271808, 271841, 272192, 272257, 272384, 272545, 272704, 272833, 272864, 272899, 274529, 274595,
+ 274752, 297987, 299904, 302403, 303104, 323267, 324224, 360449, 367776, 367969, 368096, 368193,
+ 368256, 368547, 368576, 368641, 369856, 369889, 369920, 370081, 370112, 370177, 371968, 372193,
+ 372224, 372737, 373472, 373761, 373984, 374017, 374240, 374273, 374496, 374529, 374752, 374785,
+ 375008, 375041, 375264, 375297, 375520, 375553, 375776, 378337, 378368, 393220, 393248, 393377,
+ 393443, 393472, 394275, 394560, 394785, 394944, 395011, 395105, 395168, 395297, 398048, 398241,
+ 398336, 398369, 401248, 401281, 401408, 401569, 402944, 402977, 405984, 406083, 406208, 406529,
+ 407552, 409089, 409600, 410627, 410944, 411907, 412160, 412195, 412672, 413699, 414016, 415267,
+ 415744, 425985, 636928, 638977, 1348000, 1350145, 1351616, 1351681, 1360288, 1360385, 1360898, 1361217,
+ 1361280, 1361921, 1363424, 1363937, 1364928, 1364993, 1367235, 1367552, 1368801, 1369088, 1369153, 1372448,
+ 1372513, 1374560, 1374721, 1374784, 1374817, 1374848, 1374881, 1375040, 1375809, 1376320, 1376353, 1376448,
+ 1376481, 1376608, 1376641, 1377376, 1377795, 1377984, 1378305, 1379968, 1380417, 1382016, 1382914, 1383232,
+ 1384001, 1384192, 1384289, 1384320, 1384353, 1384416, 1384450, 1384769, 1385664, 1385985, 1386720, 1387521,
+ 1388448, 1388673, 1390176, 1391073, 1391106, 1391424, 1391617, 1391776, 1391809, 1392130, 1392449, 1392608,
+ 1392641, 1393952, 1394689, 1394784, 1394817, 1395072, 1395202, 1395520, 1395713, 1396448, 1396545, 1396576,
+ 1396673, 1398272, 1398305, 1398336, 1398433, 1398496, 1398561, 1398720, 1398785, 1398816, 1398849, 1398880,
+ 1399649, 1399744, 1399809, 1400160, 1400385, 1400480, 1400865, 1401056, 1401121, 1401312, 1401377, 1401568,
+ 1401857, 1402080, 1402113, 1402336, 1402369, 1403744, 1403777, 1404224, 1404417, 1408096, 1408514, 1408832,
+ 1409025, 1766528, 1766913, 1767648, 1767777, 1769344, 2039809, 2051520, 2051585, 2054976, 2056193, 2056416,
+ 2056801, 2056960, 2057121, 2057152, 2057185, 2057504, 2057537, 2057952, 2057985, 2058144, 2058177, 2058208,
+ 2058241, 2058304, 2058337, 2058400, 2058433, 2061888, 2062945, 2074560, 2075137, 2077184, 2077249, 2078976,
+ 2080257, 2080640, 2084353, 2084512, 2084545, 2088864, 2089474, 2089792, 2090017, 2090848, 2091041, 2091872,
+ 2092225, 2095072, 2095169, 2095360, 2095425, 2095616, 2095681, 2095872, 2095937, 2096032, 2097153, 2097536,
+ 2097569, 2098400, 2098433, 2099040, 2099073, 2099136, 2099169, 2099648, 2099713, 2100160, 2101249, 2105184,
+ 2105571, 2107008, 2107395, 2109216, 2109763, 2109824, 2117633, 2118560, 2118657, 2120224, 2120739, 2121600,
+ 2121729, 2122755, 2122880, 2123169, 2123811, 2123841, 2124099, 2124128, 2124289, 2125504, 2125825, 2126784,
+ 2126849, 2128000, 2128129, 2128384, 2128419, 2128576, 2129921, 2134976, 2135042, 2135360, 2135553, 2136704,
+ 2136833, 2137984, 2138113, 2139392, 2139649, 2141312, 2141697, 2142048, 2142081, 2142560, 2142593, 2142816,
+ 2142849, 2142912, 2142945, 2143296, 2143329, 2143808, 2143841, 2144064, 2144097, 2144160, 2146305, 2156256,
+ 2156545, 2157248, 2157569, 2157824, 2158593, 2158784, 2158817, 2160160, 2160193, 2160480, 2162689, 2162880,
+ 2162945, 2162976, 2163009, 2164416, 2164449, 2164512, 2164609, 2164640, 2164705, 2165440, 2165507, 2165761,
+ 2166496, 2166563, 2166785, 2167776, 2168035, 2168320, 2169857, 2170464, 2170497, 2170560, 2170723, 2170881,
+ 2171587, 2171776, 2171905, 2172736, 2174977, 2176768, 2176899, 2176961, 2177027, 2177536, 2177603, 2179073,
+ 2179104, 2179585, 2179712, 2179745, 2179840, 2179873, 2180800, 2181123, 2181408, 2182145, 2183075, 2183136,
+ 2183169, 2184099, 2184192, 2185217, 2185472, 2185505, 2186400, 2186595, 2186752, 2187265, 2188992, 2189313,
+ 2190016, 2190083, 2190337, 2190944, 2191107, 2191361, 2191936, 2192675, 2192896, 2195457, 2197792, 2199553,
+ 2201184, 2201601, 2203232, 2203459, 2203649, 2204800, 2205186, 2205504, 2214915, 2215904, 2215937, 2217280,
+ 2217473, 2217536, 2220033, 2220963, 2221281, 2221312, 2221569, 2222272, 2222627, 2222752, 2223617, 2224192,
+ 2225665, 2226339, 2226560, 2227201, 2227936, 2228321, 2230016, 2230851, 2231490, 2231808, 2231841, 2231904,
+ 2231969, 2232000, 2232417, 2233856, 2234881, 2235680, 2235906, 2236224, 2236513, 2237664, 2238146, 2238464,
+ 2238593, 2238624, 2238689, 2238720, 2238977, 2240096, 2240193, 2240224, 2240609, 2242144, 2242593, 2242720,
+ 2243074, 2243393, 2243424, 2243457, 2243488, 2243619, 2244256, 2244609, 2245184, 2245217, 2246016, 2246625,
+ 2246688, 2248705, 2248928, 2248961, 2248992, 2249025, 2249152, 2249185, 2249664, 2249697, 2250016, 2250241,
+ 2251744, 2252290, 2252608, 2252961, 2253216, 2253281, 2253344, 2253409, 2254112, 2254145, 2254368, 2254401,
+ 2254464, 2254497, 2254656, 2254753, 2254784, 2255361, 2255392, 2255777, 2255936, 2260993, 2262688, 2263265,
+ 2263392, 2263554, 2263872, 2264033, 2264128, 2265089, 2266624, 2267265, 2267328, 2267361, 2267392, 2267650,
+ 2267968, 2273281, 2274784, 2276097, 2276224, 2277377, 2278912, 2279553, 2279584, 2279938, 2280256, 2281473,
+ 2282848, 2283265, 2283296, 2283522, 2283840, 2285569, 2286432, 2287106, 2287427, 2287488, 2287617, 2287840,
+ 2293761, 2295168, 2298881, 2300930, 2301251, 2301536, 2301921, 2302176, 2302241, 2302272, 2302337, 2302592,
+ 2302625, 2302688, 2302721, 2303488, 2303969, 2304000, 2304033, 2304064, 2304514, 2304832, 2307073, 2307328,
+ 2307393, 2308640, 2309153, 2309184, 2309217, 2309248, 2310145, 2310176, 2310497, 2311776, 2312001, 2312032,
+ 2312705, 2312736, 2313089, 2314560, 2315169, 2315200, 2315777, 2318112, 2326529, 2326816, 2326849, 2328032,
+ 2328577, 2328608, 2329090, 2329411, 2330016, 2330177, 2331136, 2334721, 2334944, 2334977, 2335040, 2335073,
+ 2336288, 2336961, 2336992, 2337282, 2337600, 2337793, 2337984, 2338017, 2338080, 2338113, 2339136, 2339585,
+ 2339616, 2339842, 2340160, 2350081, 2350688, 2351169, 2351200, 2351233, 2351648, 2351681, 2352768, 2353666,
+ 2353984, 2356737, 2356768, 2357251, 2357920, 2359297, 2388800, 2392067, 2395616, 2396161, 2402432, 2486785,
+ 2489888, 2490369, 2524672, 2525217, 2525408, 2654209, 2672864, 2949121, 2967328, 2967553, 2968544, 2968578,
+ 2968896, 2969089, 2971616, 2971650, 2971968, 2972161, 2973120, 2973697, 2975232, 2975745, 2975872, 2976258,
+ 2976576, 2976611, 2976832, 2976865, 2977536, 2977697, 2978304, 3000321, 3002371, 3003104, 3006465, 3008864,
+ 3009025, 3009056, 3011169, 3011584, 3013633, 3013696, 3013729, 3013760, 3014657, 3211008, 3211265, 3250880,
+ 3252225, 3252512, 3538433, 3538560, 3538593, 3538816, 3538849, 3538912, 3538945, 3548256, 3548737, 3548768,
+ 3549697, 3549792, 3549857, 3549888, 3550337, 3550464, 3550721, 3563392, 3637249, 3640672, 3640833, 3641248,
+ 3641345, 3641632, 3641857, 3642176, 3823619, 3824256, 3824643, 3825280, 3828739, 3829536, 3833857, 3836576,
+ 3836609, 3838880, 3838913, 3838976, 3839041, 3839072, 3839137, 3839200, 3839265, 3839392, 3839425, 3839808,
+ 3839841, 3839872, 3839905, 3840128, 3840161, 3842240, 3842273, 3842400, 3842465, 3842720, 3842753, 3842976,
+ 3843009, 3843904, 3843937, 3844064, 3844097, 3844256, 3844289, 3844320, 3844417, 3844640, 3844673, 3855552,
+ 3855617, 3856416, 3856449, 3857248, 3857281, 3858272, 3858305, 3859104, 3859137, 3860128, 3860161, 3860960,
+ 3860993, 3861984, 3862017, 3862816, 3862849, 3863840, 3863873, 3864672, 3864705, 3864960, 3865026, 3866624,
+ 3923969, 3924960, 3925153, 3925344, 3933697, 3935680, 3940353, 3941792, 3942113, 3942336, 3942402, 3942720,
+ 3942849, 3942880, 3953153, 3954112, 3954689, 3956096, 3956226, 3956544, 3971585, 3972480, 3972610, 3972928,
+ 3996673, 3996896, 3996929, 3997056, 3997089, 3997152, 3997185, 3997664, 3997697, 4004000, 4004067, 4004352,
+ 4005889, 4008064, 4008289, 4008320, 4008450, 4008768, 4034083, 4035968, 4036003, 4036096, 4036131, 4036256,
+ 4038691, 4040128, 4040163, 4040640, 4046849, 4046976, 4047009, 4047872, 4047905, 4047968, 4048001, 4048032,
+ 4048097, 4048128, 4048161, 4048480, 4048513, 4048640, 4048673, 4048704, 4048737, 4048768, 4048961, 4048992,
+ 4049121, 4049152, 4049185, 4049216, 4049249, 4049280, 4049313, 4049408, 4049441, 4049504, 4049537, 4049568,
+ 4049633, 4049664, 4049697, 4049728, 4049761, 4049792, 4049825, 4049856, 4049889, 4049920, 4049953, 4050016,
+ 4050049, 4050080, 4050145, 4050272, 4050305, 4050528, 4050561, 4050688, 4050721, 4050848, 4050881, 4050912,
+ 4050945, 4051264, 4051297, 4051840, 4052001, 4052096, 4052129, 4052288, 4052321, 4052864, 4071427, 4071840,
+ 4161026, 4161344, 4194305, 5561344, 5562369, 5695296, 5695489, 5702592, 5702657, 5887040, 5887489, 6126624,
+ 6225921, 6243264, 6291457, 6449504, 6449665, 6583808, 4294967295};
+
+static const uint16 unicode_simple_category_jump_pos[] = {
+ 1, 9, 27, 27, 27, 27, 36, 44, 55, 55, 57, 63, 68, 75, 86, 91, 102, 114, 119,
+ 130, 158, 180, 202, 225, 250, 271, 292, 312, 324, 332, 357, 365, 368, 383, 397, 397, 397, 407,
+ 423, 431, 436, 437, 437, 437, 437, 440, 448, 458, 467, 472, 480, 487, 494, 498, 503, 509, 516,
+ 524, 538, 538, 540, 540, 540, 558, 578, 592, 595, 622, 625, 625, 625, 625, 625, 626, 629, 629,
+ 629, 629, 629, 630, 631, 631, 631, 631, 631, 631, 631, 631, 632, 632, 640, 650, 667, 669, 669,
+ 669, 670, 682, 689, 692, 699, 706, 709, 709, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710,
+ 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710,
+ 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710,
+ 710, 710, 710, 710, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
+ 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
+ 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
+ 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
+ 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
+ 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
+ 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
+ 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
+ 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
+ 712, 712, 712, 712, 712, 712, 712, 716, 716, 716, 724, 728, 731, 741, 752, 763, 769, 781, 793,
+ 810, 825, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829,
+ 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829,
+ 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829,
+ 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829,
+ 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 834, 834, 834, 834, 834,
+ 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834,
+ 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834,
+ 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834,
+ 834, 834, 834, 834, 835, 835, 835, 837, 839, 857, 859, 859, 859, 861, 866, 869, 870, 877, 887,
+ 899, 900, 904, 906, 907, 913, 923, 931, 931, 939, 945, 959, 959, 959, 965, 971, 987, 996, 1001,
+ 1008, 1021, 1030, 1038, 1042, 1044, 1049, 1052, 1052, 1055, 1059, 1067, 1073, 1082, 1088, 1100, 1112, 1118, 1131,
+ 1149, 1150, 1158, 1165, 1166, 1170, 1176, 1182, 1188, 1189, 1190, 1195, 1210, 1219, 1227, 1232, 1232, 1233, 1242,
+ 1244, 1258, 1263, 1263, 1265, 1273, 1278, 1278, 1278, 1278, 1278, 1278, 1278, 1278, 1280, 1282, 1282, 1283, 1283,
+ 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283,
+ 1286, 1286, 1286, 1286, 1286, 1286, 1286, 1286, 1286, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289,
+ 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289,
+ 1289, 1289, 1290, 1290, 1290, 1290, 1290, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291,
+ 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291,
+ 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291,
+ 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1292, 1292,
+ 1292, 1292, 1292, 1298, 1304, 1314, 1315, 1315, 1315, 1315, 1315, 1317, 1319, 1322, 1329, 1329, 1329, 1329, 1329,
+ 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329,
+ 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329,
+ 1329, 1329, 1329, 1329, 1329, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1333, 1334, 1334, 1334,
+ 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334,
+ 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334,
+ 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334,
+ 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1341, 1341, 1341, 1351, 1351, 1351, 1352, 1352, 1352, 1352,
+ 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1353, 1357, 1360, 1360, 1360,
+ 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360,
+ 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360,
+ 1360, 1360, 1360, 1364, 1366, 1367, 1369, 1385, 1403, 1403, 1403, 1411, 1419, 1428, 1428, 1428, 1428, 1428, 1428,
+ 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1429, 1432, 1432, 1434, 1435, 1442, 1442, 1442, 1448, 1448, 1448,
+ 1448, 1452, 1452, 1452, 1452, 1452, 1452, 1461, 1461, 1465, 1470, 1470, 1470, 1470, 1470, 1470, 1471, 1476, 1480,
+ 1481, 1537, 1546, 1546, 1546, 1546, 1547, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548,
+ 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1551, 1563,
+ 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566};
+
+static const char *unicode_simple_category_table =
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x02\x02\x02\x02\x02\x02"
+ "\x02\x02\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+ "\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
+ "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00";
static constexpr uint32 TABLE_SIZE = 1280;
-static int16 prepare_search_character_table[TABLE_SIZE] = {
+static const int16 prepare_search_character_table[TABLE_SIZE] = {
0, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 48, 49, 50, 51, 52, 53, 54, 55, 56,
@@ -202,219 +281,252 @@ static int16 prepare_search_character_table[TABLE_SIZE] = {
1273, 1275, 1275, 1277, 1277, 1279, 1279};
static const int32 prepare_search_character_ranges[] = {
- 1280, 2097153, 1328, 1328, 1329, -1378, 1367, -1368, 1370, 32, 1376, -1377,
- 1417, 32, 1419, -1420, 1421, 32, 1424, 1424, 1425, 0, 1470, 32,
- 1471, 0, 1472, 32, 1473, 0, 1475, 32, 1476, 0, 1478, 32,
- 1479, 0, 1480, -1481, 1523, 32, 1525, -1526, 1536, 0, 1542, 32,
- 1552, 0, 1563, 32, 1564, 0, 1565, 1565, 1566, 32, 1568, -1569,
- 1611, 0, 1632, -1633, 1642, 32, 1646, -1647, 1648, 0, 1649, -1650,
- 1748, 32, 1749, 1749, 1750, 0, 1758, 32, 1759, 0, 1765, -1766,
- 1767, 0, 1769, 32, 1770, 0, 1774, -1775, 1789, 32, 1791, 1791,
- 1792, 32, 1806, 1806, 1807, 0, 1808, 1808, 1809, 0, 1810, -1811,
- 1840, 0, 1867, -1868, 1958, 0, 1969, -1970, 2027, 0, 2036, -2037,
- 2038, 32, 2042, -2043, 2070, 0, 2074, 2074, 2075, 0, 2084, 2084,
- 2085, 0, 2088, 2088, 2089, 0, 2094, -2095, 2096, 32, 2111, -2112,
- 2137, 0, 2140, -2141, 2142, 32, 2143, -2144, 2260, 0, 2308, -2309,
- 2362, 0, 2365, 2365, 2366, 0, 2384, 2384, 2385, 0, 2392, -2393,
- 2402, 0, 2404, 32, 2406, -2407, 2416, 32, 2417, -2418, 2433, 0,
- 2436, -2437, 2492, 0, 2493, 2493, 2494, 0, 2501, -2502, 2503, 0,
- 2505, -2506, 2507, 0, 2510, -2511, 2519, 0, 2520, -2521, 2530, 0,
- 2532, -2533, 2546, 32, 2548, -2549, 2554, 32, 2556, -2557, 2561, 0,
- 2564, -2565, 2620, 0, 2621, 2621, 2622, 0, 2627, -2628, 2631, 0,
- 2633, -2634, 2635, 0, 2638, -2639, 2641, 0, 2642, -2643, 2672, 0,
- 2674, -2675, 2677, 0, 2678, -2679, 2689, 0, 2692, -2693, 2748, 0,
- 2749, 2749, 2750, 0, 2758, 2758, 2759, 0, 2762, 2762, 2763, 0,
- 2766, -2767, 2786, 0, 2788, -2789, 2800, 32, 2802, -2803, 2817, 0,
- 2820, -2821, 2876, 0, 2877, 2877, 2878, 0, 2885, -2886, 2887, 0,
- 2889, -2890, 2891, 0, 2894, -2895, 2902, 0, 2904, -2905, 2914, 0,
- 2916, -2917, 2928, 32, 2929, -2930, 2946, 0, 2947, -2948, 3006, 0,
- 3011, -3012, 3014, 0, 3017, 3017, 3018, 0, 3022, -3023, 3031, 0,
- 3032, -3033, 3059, 32, 3067, -3068, 3072, 0, 3076, -3077, 3134, 0,
- 3141, 3141, 3142, 0, 3145, 3145, 3146, 0, 3150, -3151, 3157, 0,
- 3159, -3160, 3170, 0, 3172, -3173, 3199, 32, 3200, 3200, 3201, 0,
- 3204, -3205, 3260, 0, 3261, 3261, 3262, 0, 3269, 3269, 3270, 0,
- 3273, 3273, 3274, 0, 3278, -3279, 3285, 0, 3287, -3288, 3298, 0,
- 3300, -3301, 3329, 0, 3332, -3333, 3390, 0, 3397, 3397, 3398, 0,
- 3401, 3401, 3402, 0, 3406, 3406, 3407, 32, 3408, -3409, 3415, 0,
- 3416, -3417, 3426, 0, 3428, -3429, 3449, 32, 3450, -3451, 3458, 0,
- 3460, -3461, 3530, 0, 3531, -3532, 3535, 0, 3541, 3541, 3542, 0,
- 3543, 3543, 3544, 0, 3552, -3553, 3570, 0, 3572, 32, 3573, -3574,
- 3633, 0, 3634, -3635, 3636, 0, 3643, -3644, 3647, 32, 3648, -3649,
- 3655, 0, 3663, 32, 3664, -3665, 3674, 32, 3676, -3677, 3761, 0,
- 3762, -3763, 3764, 0, 3770, 3770, 3771, 0, 3773, -3774, 3784, 0,
- 3790, -3791, 3841, 32, 3864, 0, 3866, 32, 3872, -3873, 3892, 32,
- 3893, 0, 3894, 32, 3895, 0, 3896, 32, 3897, 0, 3898, 32,
- 3902, 0, 3904, -3905, 3953, 0, 3973, 32, 3974, 0, 3976, -3977,
- 3981, 0, 3992, 3992, 3993, 0, 4029, 4029, 4030, 32, 4038, 0,
- 4039, 32, 4045, 4045, 4046, 32, 4059, -4060, 4139, 0, 4159, -4160,
- 4170, 32, 4176, -4177, 4182, 0, 4186, -4187, 4190, 0, 4193, 4193,
- 4194, 0, 4197, -4198, 4199, 0, 4206, -4207, 4209, 0, 4213, -4214,
- 4226, 0, 4238, 4238, 4239, 0, 4240, -4241, 4250, 0, 4254, 32,
- 4256, -11521, 4294, 4294, 4295, 11559, 4296, -4297, 4301, 11565, 4302, -4303,
- 4347, 32, 4348, -4349, 4957, 0, 4960, 32, 4969, -4970, 5008, 32,
- 5018, -5019, 5112, -5105, 5118, -5119, 5120, 32, 5121, -5122, 5741, 32,
- 5743, -5744, 5760, 32, 5761, -5762, 5787, 32, 5789, -5790, 5867, 32,
- 5870, -5871, 5906, 0, 5909, -5910, 5938, 0, 5941, 32, 5943, -5944,
- 5970, 0, 5972, -5973, 6002, 0, 6004, -6005, 6068, 0, 6100, 32,
- 6103, 6103, 6104, 32, 6108, 6108, 6109, 0, 6110, -6111, 6144, 32,
- 6155, 0, 6159, -6160, 6277, 0, 6279, -6280, 6313, 0, 6314, -6315,
- 6432, 0, 6444, -6445, 6448, 0, 6460, -6461, 6464, 32, 6465, -6466,
- 6468, 32, 6470, -6471, 6622, 32, 6656, -6657, 6679, 0, 6684, -6685,
- 6686, 32, 6688, -6689, 6741, 0, 6751, 6751, 6752, 0, 6781, -6782,
- 6783, 0, 6784, -6785, 6816, 32, 6823, 6823, 6824, 32, 6830, -6831,
- 6832, 0, 6847, -6848, 6912, 0, 6917, -6918, 6964, 0, 6981, -6982,
- 7002, 32, 7019, 0, 7028, 32, 7037, -7038, 7040, 0, 7043, -7044,
- 7073, 0, 7086, -7087, 7142, 0, 7156, -7157, 7164, 32, 7168, -7169,
- 7204, 0, 7224, -7225, 7227, 32, 7232, -7233, 7294, 32, 7296, 1074,
- 7297, 1076, 7298, 1086, 7299, -1090, 7301, 1090, 7302, 1098, 7303, 1123,
- 7304, 42571, 7305, -7306, 7360, 32, 7368, -7369, 7376, 0, 7379, 32,
- 7380, 0, 7401, -7402, 7405, 0, 7406, -7407, 7410, 0, 7413, -7414,
- 7416, 0, 7418, -7419, 7468, 97, 7469, 230, 7470, 98, 7471, 7471,
- 7472, -101, 7474, 477, 7475, -104, 7483, 7483, 7484, 111, 7485, 547,
- 7486, 112, 7487, 114, 7488, -117, 7490, 119, 7491, -7492, 7616, 0,
- 7670, -7671, 7675, 0, 7680, 2097153, 7830, -7831, 7835, 7777, 7836, -7837,
- 7838, 223, 7839, 2097153, 7936, -7937, 7944, -7937, 7952, -7953, 7960, -7953,
- 7966, -7967, 7976, -7969, 7984, -7985, 7992, -7985, 8000, -8001, 8008, -8001,
- 8014, -8015, 8025, 8017, 8026, 8026, 8027, 8019, 8028, 8028, 8029, 8021,
- 8030, 8030, 8031, 8023, 8032, -8033, 8040, -8033, 8048, -8049, 8072, -8065,
- 8080, -8081, 8088, -8081, 8096, -8097, 8104, -8097, 8112, -8113, 8120, -8113,
- 8122, -8049, 8124, 8115, 8125, 32, 8126, 953, 8127, 32, 8130, -8131,
- 8136, -8051, 8140, 8131, 8141, 32, 8144, -8145, 8152, -8145, 8154, -8055,
- 8156, 8156, 8157, 32, 8160, -8161, 8168, -8161, 8170, -8059, 8172, 8165,
- 8173, 32, 8176, -8177, 8184, -8057, 8186, -8061, 8188, 8179, 8189, 32,
- 8191, 8191, 8192, 32, 8203, 0, 8208, 32, 8234, 0, 8239, 32,
- 8288, 0, 8293, 8293, 8294, 0, 8304, -8305, 8314, 32, 8319, -8320,
- 8330, 32, 8335, -8336, 8352, 32, 8383, -8384, 8400, 0, 8433, -8434,
- 8448, 32, 8450, 99, 8452, 32, 8455, 603, 8456, 32, 8457, 102,
- 8458, 8458, 8459, 104, 8462, -8463, 8464, 105, 8466, 108, 8467, 8467,
- 8468, 32, 8469, 110, 8470, 32, 8473, -113, 8476, 114, 8478, 32,
- 8484, 122, 8485, 32, 8486, 969, 8487, 32, 8488, 122, 8489, 32,
- 8490, 107, 8491, 229, 8492, -99, 8494, 32, 8495, 8495, 8496, -102,
- 8498, 8526, 8499, 109, 8500, -8501, 8506, 32, 8508, -8509, 8510, 947,
- 8511, 960, 8512, 32, 8517, 100, 8518, -8519, 8522, 32, 8526, 8526,
- 8527, 32, 8528, -8529, 8544, -8561, 8560, -8561, 8579, 8580, 8581, -8582,
- 8586, 32, 8588, -8589, 8592, 32, 9215, 9215, 9216, 32, 9255, -9256,
- 9280, 32, 9291, -9292, 9372, 32, 9398, -9425, 9424, -9425, 9472, 32,
- 10102, -10103, 10132, 32, 11124, -11125, 11126, 32, 11158, -11159, 11160, 32,
- 11194, -11195, 11197, 32, 11209, 11209, 11210, 32, 11218, -11219, 11244, 32,
- 11248, -11249, 11264, -11313, 11311, -11312, 11360, 11361, 11362, 619, 11363, 7549,
- 11364, 637, 11365, -11366, 11367, 11368, 11369, 11370, 11371, 11372, 11373, 593,
- 11374, 625, 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380, 11381, 11382,
- 11383, -11384, 11389, 118, 11390, -576, 11392, 2097153, 11492, 11492, 11493, 32,
- 11499, 11500, 11501, 11502, 11503, 0, 11506, 11507, 11508, -11509, 11513, 32,
- 11517, 11517, 11518, 32, 11520, -11521, 11632, 32, 11633, -11634, 11647, 0,
- 11648, -11649, 11744, 0, 11776, 32, 11823, 11823, 11824, 32, 11845, -11846,
- 11904, 32, 11930, 11930, 11931, 32, 11935, 11935, 11936, 32, 12019, -12020,
- 12272, 32, 12284, -12285, 12288, 32, 12293, -12294, 12296, 32, 12321, -12322,
- 12330, 0, 12336, 32, 12337, -12338, 12342, 32, 12344, -12345, 12349, 32,
- 12352, -12353, 12441, 0, 12443, 32, 12445, -12446, 12448, 32, 12449, -12450,
- 12539, 32, 12540, 0, 12541, -12542, 12688, 32, 12690, -12691, 12736, 32,
- 12772, -12773, 12800, 32, 12831, -12832, 12842, 32, 12868, -12869, 12880, 32,
- 12881, -12882, 12910, 32, 12928, -12929, 12992, 32, 13008, -13009, 13056, 32,
- 13312, -13313, 19904, 32, 19968, -19969, 42128, 32, 42183, -42184, 42238, 32,
- 42240, -42241, 42509, 32, 42512, -42513, 42560, 2097153, 42606, 42606, 42607, 0,
- 42611, 32, 42612, 0, 42622, 32, 42623, 2097153, 42652, -42653, 42654, 0,
- 42656, -42657, 42736, 0, 42738, 32, 42744, -42745, 42752, 32, 42775, -42776,
- 42784, 32, 42786, 2097153, 42800, -42801, 42802, 2097153, 42864, -42865, 42873, 42874,
- 42875, 42876, 42877, 7545, 42878, 2097153, 42888, 42888, 42889, 32, 42891, 42892,
- 42893, 613, 42894, -42895, 42896, 2097153, 42900, -42901, 42902, 2097153, 42922, 614,
- 42923, 604, 42924, 609, 42925, 620, 42926, 618, 42927, 42927, 42928, 670,
- 42929, 647, 42930, 669, 42931, 43859, 42932, 2097153, 42936, -42937, 43000, 295,
- 43001, -43002, 43010, 0, 43011, -43012, 43014, 0, 43015, -43016, 43019, 0,
- 43020, -43021, 43043, 0, 43048, 32, 43052, -43053, 43062, 32, 43066, -43067,
- 43124, 32, 43128, -43129, 43136, 0, 43138, -43139, 43188, 0, 43206, -43207,
- 43214, 32, 43216, -43217, 43232, 0, 43250, -43251, 43256, 32, 43259, 43259,
- 43260, 32, 43261, -43262, 43302, 0, 43310, 32, 43312, -43313, 43335, 0,
- 43348, -43349, 43359, 32, 43360, -43361, 43392, 0, 43396, -43397, 43443, 0,
- 43457, 32, 43470, -43471, 43486, 32, 43488, -43489, 43493, 0, 43494, -43495,
- 43561, 0, 43575, -43576, 43587, 0, 43588, -43589, 43596, 0, 43598, -43599,
- 43612, 32, 43616, -43617, 43639, 32, 43642, 43642, 43643, 0, 43646, -43647,
- 43696, 0, 43697, 43697, 43698, 0, 43701, -43702, 43703, 0, 43705, -43706,
- 43710, 0, 43712, 43712, 43713, 0, 43714, -43715, 43742, 32, 43744, -43745,
- 43755, 0, 43760, 32, 43762, -43763, 43765, 0, 43767, -43768, 43867, 32,
- 43868, -43869, 43888, -5025, 43968, -43969, 44003, 0, 44011, 32, 44012, 0,
- 44014, -44015, 55296, 0, 57344, -57345, 64286, 0, 64287, -64288, 64297, 32,
- 64298, -64299, 64434, 32, 64450, -64451, 64830, 32, 64832, -64833, 64976, 32,
- 65008, -65009, 65020, 32, 65022, -65023, 65024, 0, 65040, 32, 65050, -65051,
- 65056, 0, 65072, 32, 65107, 65107, 65108, 32, 65127, 65127, 65128, 32,
- 65132, -65133, 65279, 0, 65280, 65280, 65281, 32, 65296, -65297, 65306, 32,
- 65313, -65346, 65339, 32, 65345, -65346, 65371, 32, 65382, -65383, 65504, 32,
- 65511, 65511, 65512, 32, 65519, -65520, 65529, 0, 65532, 32, 65536, -65537,
- 65792, 32, 65795, -65796, 65847, 32, 65856, -65857, 65913, 32, 65930, -65931,
- 65932, 32, 65935, 65935, 65936, 32, 65948, -65949, 65952, 32, 65953, -65954,
- 66000, 32, 66045, 0, 66046, -66047, 66272, 0, 66273, -66274, 66422, 0,
- 66427, -66428, 66463, 32, 66464, -66465, 66512, 32, 66513, -66514, 66560, -66601,
- 66600, -66601, 66736, -66777, 66772, -66773, 66927, 32, 66928, -66929, 67671, 32,
- 67672, -67673, 67703, 32, 67705, -67706, 67871, 32, 67872, -67873, 67903, 32,
- 67904, -67905, 68097, 0, 68100, 68100, 68101, 0, 68103, -68104, 68108, 0,
- 68112, -68113, 68152, 0, 68155, -68156, 68159, 0, 68160, -68161, 68176, 32,
- 68185, -68186, 68223, 32, 68224, -68225, 68296, 32, 68297, -68298, 68325, 0,
- 68327, -68328, 68336, 32, 68343, -68344, 68409, 32, 68416, -68417, 68505, 32,
- 68509, -68510, 68736, -68801, 68787, -68788, 69632, 0, 69635, -69636, 69688, 0,
- 69703, 32, 69710, -69711, 69759, 0, 69763, -69764, 69808, 0, 69819, 32,
- 69821, 0, 69822, 32, 69826, -69827, 69888, 0, 69891, -69892, 69927, 0,
- 69941, -69942, 69952, 32, 69956, -69957, 70003, 0, 70004, 32, 70006, -70007,
- 70016, 0, 70019, -70020, 70067, 0, 70081, -70082, 70085, 32, 70090, 0,
- 70093, 32, 70094, -70095, 70107, 32, 70108, 70108, 70109, 32, 70112, -70113,
- 70188, 0, 70200, 32, 70206, 0, 70207, -70208, 70313, 32, 70314, -70315,
- 70367, 0, 70379, -70380, 70400, 0, 70404, -70405, 70460, 0, 70461, 70461,
- 70462, 0, 70469, -70470, 70471, 0, 70473, -70474, 70475, 0, 70478, -70479,
- 70487, 0, 70488, -70489, 70498, 0, 70500, -70501, 70502, 0, 70509, -70510,
- 70512, 0, 70517, -70518, 70709, 0, 70727, -70728, 70731, 32, 70736, -70737,
- 70747, 32, 70748, 70748, 70749, 32, 70750, -70751, 70832, 0, 70852, -70853,
- 70854, 32, 70855, -70856, 71087, 0, 71094, -71095, 71096, 0, 71105, 32,
- 71128, -71129, 71132, 0, 71134, -71135, 71216, 0, 71233, 32, 71236, -71237,
- 71264, 32, 71277, -71278, 71339, 0, 71352, -71353, 71453, 0, 71468, -71469,
- 71484, 32, 71488, -71489, 71840, -71873, 71872, -71873, 72751, 0, 72759, 72759,
- 72760, 0, 72768, 72768, 72769, 32, 72774, -72775, 72816, 32, 72818, -72819,
- 72850, 0, 72872, 72872, 72873, 0, 72887, -72888, 74864, 32, 74869, -74870,
- 92782, 32, 92784, -92785, 92912, 0, 92917, 32, 92918, -92919, 92976, 0,
- 92983, 32, 92992, -92993, 92996, 32, 92998, -92999, 94033, 0, 94079, -94080,
- 94095, 0, 94099, -94100, 113820, 32, 113821, 0, 113823, 32, 113824, 0,
- 113828, -113829, 118784, 32, 119030, -119031, 119040, 32, 119079, -119080, 119081, 32,
- 119141, 0, 119146, 32, 119149, 0, 119171, 32, 119173, 0, 119180, 32,
- 119210, 0, 119214, 32, 119273, -119274, 119296, 32, 119362, 0, 119365, 32,
- 119366, -119367, 119552, 32, 119639, -119640, 119808, -98, 119834, -119835, 119860, -98,
- 119886, -119887, 119912, -98, 119938, -119939, 119964, 97, 119965, 119965, 119966, -100,
- 119968, -119969, 119970, 103, 119971, -119972, 119973, -107, 119975, -119976, 119977, -111,
- 119981, 119981, 119982, -116, 119990, -119991, 120016, -98, 120042, -120043, 120068, -98,
- 120070, 120070, 120071, -101, 120075, -120076, 120077, -107, 120085, 120085, 120086, -116,
- 120093, -120094, 120120, -98, 120122, 120122, 120123, -101, 120127, 120127, 120128, -106,
- 120133, 120133, 120134, 111, 120135, -120136, 120138, -116, 120145, -120146, 120172, -98,
- 120198, -120199, 120224, -98, 120250, -120251, 120276, -98, 120302, -120303, 120328, -98,
- 120354, -120355, 120380, -98, 120406, -120407, 120432, -98, 120458, -120459, 120488, -946,
- 120505, 952, 120506, -964, 120513, 32, 120514, -120515, 120531, 963, 120532, -120533,
- 120539, 32, 120540, -120541, 120546, -946, 120563, 952, 120564, -964, 120571, 32,
- 120572, -120573, 120589, 963, 120590, -120591, 120597, 32, 120598, -120599, 120604, -946,
- 120621, 952, 120622, -964, 120629, 32, 120630, -120631, 120647, 963, 120648, -120649,
- 120655, 32, 120656, -120657, 120662, -946, 120679, 952, 120680, -964, 120687, 32,
- 120688, -120689, 120705, 963, 120706, -120707, 120713, 32, 120714, -120715, 120720, -946,
- 120737, 952, 120738, -964, 120745, 32, 120746, -120747, 120763, 963, 120764, -120765,
- 120771, 32, 120772, -120773, 120778, 989, 120779, -120780, 120832, 32, 121344, 0,
- 121399, 32, 121403, 0, 121453, 32, 121461, 0, 121462, 32, 121476, 0,
- 121477, 32, 121484, -121485, 121499, 0, 121504, 121504, 121505, 0, 121520, -121521,
- 122880, 0, 122887, 122887, 122888, 0, 122905, -122906, 122907, 0, 122914, 122914,
- 122915, 0, 122917, 122917, 122918, 0, 122923, -122924, 125136, 0, 125143, -125144,
- 125184, -125219, 125218, -125219, 125252, 0, 125259, -125260, 125278, 32, 125280, -125281,
- 126704, 32, 126706, -126707, 126976, 32, 127020, -127021, 127024, 32, 127124, -127125,
- 127136, 32, 127151, -127152, 127153, 32, 127168, 127168, 127169, 32, 127184, 127184,
- 127185, 32, 127222, -127223, 127248, 32, 127275, 99, 127276, 114, 127277, 32,
- 127279, 127279, 127280, -98, 127306, 32, 127340, -127341, 127344, 32, 127405, -127406,
- 127462, 32, 127490, -127491, 127552, 32, 127561, -127562, 127744, 32, 128723, -128724,
- 128736, 32, 128749, -128750, 128752, 32, 128759, -128760, 128768, 32, 128884, -128885,
- 128896, 32, 128981, -128982, 129024, 32, 129036, -129037, 129040, 32, 129096, -129097,
- 129104, 32, 129114, -129115, 129120, 32, 129160, -129161, 129168, 32, 129198, -129199,
- 129296, 32, 129311, 129311, 129312, 32, 129320, -129321, 129328, 32, 129329, -129330,
- 129331, 32, 129343, 129343, 129344, 32, 129356, -129357, 129360, 32, 129375, -129376,
- 129408, 32, 129426, -129427, 129472, 32, 129473, -129474, 131070, 32, 131072, -131073,
- 196606, 32, 196608, -196609, 262142, 32, 262144, -262145, 327678, 32, 327680, -327681,
- 393214, 32, 393216, -393217, 458750, 32, 458752, -458753, 524286, 32, 524288, -524289,
- 589822, 32, 589824, -589825, 655358, 32, 655360, -655361, 720894, 32, 720896, -720897,
- 786430, 32, 786432, -786433, 851966, 32, 851968, -851969, 917502, 32, 917504, 917504,
- 917505, 0, 917506, -917507, 917536, 0, 917632, -917633, 917760, 0, 918000, -918001,
- 983038, 32, 983040, -983041, 1048574, 32, 1048576, -1048577, 1114110, 32, 2147483647, 0};
+ 1280, 2097153, 1328, 1328, 1329, -1378, 1367, -1368, 1370, 32, 1376, -1377,
+ 1417, 32, 1419, -1420, 1421, 32, 1424, 1424, 1425, 0, 1470, 32,
+ 1471, 0, 1472, 32, 1473, 0, 1475, 32, 1476, 0, 1478, 32,
+ 1479, 0, 1480, -1481, 1523, 32, 1525, -1526, 1536, 0, 1542, 32,
+ 1552, 0, 1563, 32, 1564, 0, 1565, 32, 1568, -1569, 1611, 0,
+ 1632, -1633, 1642, 32, 1646, -1647, 1648, 0, 1649, -1650, 1748, 32,
+ 1749, 1749, 1750, 0, 1758, 32, 1759, 0, 1765, -1766, 1767, 0,
+ 1769, 32, 1770, 0, 1774, -1775, 1789, 32, 1791, 1791, 1792, 32,
+ 1806, 1806, 1807, 0, 1808, 1808, 1809, 0, 1810, -1811, 1840, 0,
+ 1867, -1868, 1958, 0, 1969, -1970, 2027, 0, 2036, -2037, 2038, 32,
+ 2042, -2043, 2045, 0, 2046, 32, 2048, -2049, 2070, 0, 2074, 2074,
+ 2075, 0, 2084, 2084, 2085, 0, 2088, 2088, 2089, 0, 2094, -2095,
+ 2096, 32, 2111, -2112, 2137, 0, 2140, -2141, 2142, 32, 2143, -2144,
+ 2184, 32, 2185, -2186, 2192, 0, 2194, -2195, 2200, 0, 2208, -2209,
+ 2250, 0, 2308, -2309, 2362, 0, 2365, 2365, 2366, 0, 2384, 2384,
+ 2385, 0, 2392, -2393, 2402, 0, 2404, 32, 2406, -2407, 2416, 32,
+ 2417, -2418, 2433, 0, 2436, -2437, 2492, 0, 2493, 2493, 2494, 0,
+ 2501, -2502, 2503, 0, 2505, -2506, 2507, 0, 2510, -2511, 2519, 0,
+ 2520, -2521, 2530, 0, 2532, -2533, 2546, 32, 2548, -2549, 2554, 32,
+ 2556, 2556, 2557, 32, 2558, 0, 2559, -2560, 2561, 0, 2564, -2565,
+ 2620, 0, 2621, 2621, 2622, 0, 2627, -2628, 2631, 0, 2633, -2634,
+ 2635, 0, 2638, -2639, 2641, 0, 2642, -2643, 2672, 0, 2674, -2675,
+ 2677, 0, 2678, 32, 2679, -2680, 2689, 0, 2692, -2693, 2748, 0,
+ 2749, 2749, 2750, 0, 2758, 2758, 2759, 0, 2762, 2762, 2763, 0,
+ 2766, -2767, 2786, 0, 2788, -2789, 2800, 32, 2802, -2803, 2810, 0,
+ 2816, 2816, 2817, 0, 2820, -2821, 2876, 0, 2877, 2877, 2878, 0,
+ 2885, -2886, 2887, 0, 2889, -2890, 2891, 0, 2894, -2895, 2901, 0,
+ 2904, -2905, 2914, 0, 2916, -2917, 2928, 32, 2929, -2930, 2946, 0,
+ 2947, -2948, 3006, 0, 3011, -3012, 3014, 0, 3017, 3017, 3018, 0,
+ 3022, -3023, 3031, 0, 3032, -3033, 3059, 32, 3067, -3068, 3072, 0,
+ 3077, -3078, 3132, 0, 3133, 3133, 3134, 0, 3141, 3141, 3142, 0,
+ 3145, 3145, 3146, 0, 3150, -3151, 3157, 0, 3159, -3160, 3170, 0,
+ 3172, -3173, 3191, 32, 3192, -3193, 3199, 32, 3200, 3200, 3201, 0,
+ 3204, 32, 3205, -3206, 3260, 0, 3261, 3261, 3262, 0, 3269, 3269,
+ 3270, 0, 3273, 3273, 3274, 0, 3278, -3279, 3285, 0, 3287, -3288,
+ 3298, 0, 3300, -3301, 3315, 0, 3316, -3317, 3328, 0, 3332, -3333,
+ 3387, 0, 3389, 3389, 3390, 0, 3397, 3397, 3398, 0, 3401, 3401,
+ 3402, 0, 3406, 3406, 3407, 32, 3408, -3409, 3415, 0, 3416, -3417,
+ 3426, 0, 3428, -3429, 3449, 32, 3450, -3451, 3457, 0, 3460, -3461,
+ 3530, 0, 3531, -3532, 3535, 0, 3541, 3541, 3542, 0, 3543, 3543,
+ 3544, 0, 3552, -3553, 3570, 0, 3572, 32, 3573, -3574, 3633, 0,
+ 3634, -3635, 3636, 0, 3643, -3644, 3647, 32, 3648, -3649, 3655, 0,
+ 3663, 32, 3664, -3665, 3674, 32, 3676, -3677, 3761, 0, 3762, -3763,
+ 3764, 0, 3773, -3774, 3784, 0, 3791, -3792, 3841, 32, 3864, 0,
+ 3866, 32, 3872, -3873, 3892, 32, 3893, 0, 3894, 32, 3895, 0,
+ 3896, 32, 3897, 0, 3898, 32, 3902, 0, 3904, -3905, 3953, 0,
+ 3973, 32, 3974, 0, 3976, -3977, 3981, 0, 3992, 3992, 3993, 0,
+ 4029, 4029, 4030, 32, 4038, 0, 4039, 32, 4045, 4045, 4046, 32,
+ 4059, -4060, 4139, 0, 4159, -4160, 4170, 32, 4176, -4177, 4182, 0,
+ 4186, -4187, 4190, 0, 4193, 4193, 4194, 0, 4197, -4198, 4199, 0,
+ 4206, -4207, 4209, 0, 4213, -4214, 4226, 0, 4238, 4238, 4239, 0,
+ 4240, -4241, 4250, 0, 4254, 32, 4256, -11521, 4294, 4294, 4295, 11559,
+ 4296, -4297, 4301, 11565, 4302, -4303, 4347, 32, 4348, -4349, 4957, 0,
+ 4960, 32, 4969, -4970, 5008, 32, 5018, -5019, 5112, -5105, 5118, -5119,
+ 5120, 32, 5121, -5122, 5741, 32, 5743, -5744, 5760, 32, 5761, -5762,
+ 5787, 32, 5789, -5790, 5867, 32, 5870, -5871, 5906, 0, 5910, -5911,
+ 5938, 0, 5941, 32, 5943, -5944, 5970, 0, 5972, -5973, 6002, 0,
+ 6004, -6005, 6068, 0, 6100, 32, 6103, 6103, 6104, 32, 6108, 6108,
+ 6109, 0, 6110, -6111, 6144, 32, 6155, 0, 6160, -6161, 6277, 0,
+ 6279, -6280, 6313, 0, 6314, -6315, 6432, 0, 6444, -6445, 6448, 0,
+ 6460, -6461, 6464, 32, 6465, -6466, 6468, 32, 6470, -6471, 6622, 32,
+ 6656, -6657, 6679, 0, 6684, -6685, 6686, 32, 6688, -6689, 6741, 0,
+ 6751, 6751, 6752, 0, 6781, -6782, 6783, 0, 6784, -6785, 6816, 32,
+ 6823, 6823, 6824, 32, 6830, -6831, 6832, 0, 6863, -6864, 6912, 0,
+ 6917, -6918, 6964, 0, 6981, -6982, 7002, 32, 7019, 0, 7028, 32,
+ 7039, 7039, 7040, 0, 7043, -7044, 7073, 0, 7086, -7087, 7142, 0,
+ 7156, -7157, 7164, 32, 7168, -7169, 7204, 0, 7224, -7225, 7227, 32,
+ 7232, -7233, 7294, 32, 7296, 1074, 7297, 1076, 7298, 1086, 7299, -1090,
+ 7301, 1090, 7302, 1098, 7303, 1123, 7304, 42571, 7305, -7306, 7312, -4305,
+ 7355, -7356, 7357, -4350, 7360, 32, 7368, -7369, 7376, 0, 7379, 32,
+ 7380, 0, 7401, -7402, 7405, 0, 7406, -7407, 7412, 0, 7413, -7414,
+ 7415, 0, 7418, -7419, 7468, 97, 7469, 230, 7470, 98, 7471, 7471,
+ 7472, -101, 7474, 477, 7475, -104, 7483, 7483, 7484, 111, 7485, 547,
+ 7486, 112, 7487, 114, 7488, -117, 7490, 119, 7491, -7492, 7616, 0,
+ 7680, 2097153, 7830, -7831, 7835, 7777, 7836, -7837, 7838, 223, 7839, 2097153,
+ 7936, -7937, 7944, -7937, 7952, -7953, 7960, -7953, 7966, -7967, 7976, -7969,
+ 7984, -7985, 7992, -7985, 8000, -8001, 8008, -8001, 8014, -8015, 8025, 8017,
+ 8026, 8026, 8027, 8019, 8028, 8028, 8029, 8021, 8030, 8030, 8031, 8023,
+ 8032, -8033, 8040, -8033, 8048, -8049, 8072, -8065, 8080, -8081, 8088, -8081,
+ 8096, -8097, 8104, -8097, 8112, -8113, 8120, -8113, 8122, -8049, 8124, 8115,
+ 8125, 32, 8126, 953, 8127, 32, 8130, -8131, 8136, -8051, 8140, 8131,
+ 8141, 32, 8144, -8145, 8152, -8145, 8154, -8055, 8156, 8156, 8157, 32,
+ 8160, -8161, 8168, -8161, 8170, -8059, 8172, 8165, 8173, 32, 8176, -8177,
+ 8184, -8057, 8186, -8061, 8188, 8179, 8189, 32, 8191, 8191, 8192, 32,
+ 8203, 0, 8208, 32, 8234, 0, 8239, 32, 8288, 0, 8293, 8293,
+ 8294, 0, 8304, -8305, 8314, 32, 8319, -8320, 8330, 32, 8335, -8336,
+ 8352, 32, 8385, -8386, 8400, 0, 8433, -8434, 8448, 32, 8450, 99,
+ 8452, 32, 8455, 603, 8456, 32, 8457, 102, 8458, 8458, 8459, 104,
+ 8462, -8463, 8464, 105, 8466, 108, 8467, 8467, 8468, 32, 8469, 110,
+ 8470, 32, 8473, -113, 8476, 114, 8478, 32, 8484, 122, 8485, 32,
+ 8486, 969, 8487, 32, 8488, 122, 8489, 32, 8490, 107, 8491, 229,
+ 8492, -99, 8494, 32, 8495, 8495, 8496, -102, 8498, 8526, 8499, 109,
+ 8500, -8501, 8506, 32, 8508, -8509, 8510, 947, 8511, 960, 8512, 32,
+ 8517, 100, 8518, -8519, 8522, 32, 8526, 8526, 8527, 32, 8528, -8529,
+ 8544, -8561, 8560, -8561, 8579, 8580, 8581, -8582, 8586, 32, 8588, -8589,
+ 8592, 32, 9255, -9256, 9280, 32, 9291, -9292, 9372, 32, 9398, -9425,
+ 9424, -9425, 9472, 32, 10102, -10103, 10132, 32, 11124, -11125, 11126, 32,
+ 11158, 11158, 11159, 32, 11264, -11313, 11312, -11313, 11360, 11361, 11362, 619,
+ 11363, 7549, 11364, 637, 11365, -11366, 11367, 11368, 11369, 11370, 11371, 11372,
+ 11373, 593, 11374, 625, 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380,
+ 11381, 11382, 11383, -11384, 11389, 118, 11390, -576, 11392, 2097153, 11492, 11492,
+ 11493, 32, 11499, 11500, 11501, 11502, 11503, 0, 11506, 11507, 11508, -11509,
+ 11513, 32, 11517, 11517, 11518, 32, 11520, -11521, 11632, 32, 11633, -11634,
+ 11647, 0, 11648, -11649, 11744, 0, 11776, 32, 11823, 11823, 11824, 32,
+ 11870, -11871, 11904, 32, 11930, 11930, 11931, 32, 11935, 11935, 11936, 32,
+ 12019, -12020, 12272, 32, 12284, -12285, 12288, 32, 12293, -12294, 12296, 32,
+ 12321, -12322, 12330, 0, 12336, 32, 12337, -12338, 12342, 32, 12344, -12345,
+ 12349, 32, 12352, -12353, 12441, 0, 12443, 32, 12445, -12446, 12448, 32,
+ 12449, -12450, 12539, 32, 12540, 0, 12541, -12542, 12688, 32, 12690, -12691,
+ 12736, 32, 12772, -12773, 12800, 32, 12831, -12832, 12842, 32, 12868, -12869,
+ 12880, 32, 12881, -12882, 12910, 32, 12928, -12929, 12992, 32, 13008, -13009,
+ 13055, 32, 13312, -13313, 19904, 32, 19968, -19969, 42128, 32, 42183, -42184,
+ 42238, 32, 42240, -42241, 42509, 32, 42512, -42513, 42560, 2097153, 42606, 42606,
+ 42607, 0, 42611, 32, 42612, 0, 42622, 32, 42623, 2097153, 42652, -42653,
+ 42654, 0, 42656, -42657, 42736, 0, 42738, 32, 42744, -42745, 42752, 32,
+ 42775, -42776, 42784, 32, 42786, 2097153, 42800, -42801, 42802, 2097153, 42864, -42865,
+ 42873, 42874, 42875, 42876, 42877, 7545, 42878, 2097153, 42888, 42888, 42889, 32,
+ 42891, 42892, 42893, 613, 42894, -42895, 42896, 2097153, 42900, -42901, 42902, 2097153,
+ 42922, 614, 42923, 604, 42924, 609, 42925, 620, 42926, 618, 42927, 42927,
+ 42928, 670, 42929, 647, 42930, 669, 42931, 43859, 42932, 2097153, 42948, 42900,
+ 42949, 642, 42950, 7566, 42951, 42952, 42953, 42954, 42955, -42956, 42960, 42961,
+ 42962, -42963, 42966, 2097153, 42970, -42971, 42994, 99, 42995, 102, 42996, 113,
+ 42997, 42998, 42999, 42999, 43000, 295, 43001, -43002, 43010, 0, 43011, -43012,
+ 43014, 0, 43015, -43016, 43019, 0, 43020, -43021, 43043, 0, 43048, 32,
+ 43052, 0, 43053, -43054, 43062, 32, 43066, -43067, 43124, 32, 43128, -43129,
+ 43136, 0, 43138, -43139, 43188, 0, 43206, -43207, 43214, 32, 43216, -43217,
+ 43232, 0, 43250, -43251, 43256, 32, 43259, 43259, 43260, 32, 43261, -43262,
+ 43263, 0, 43264, -43265, 43302, 0, 43310, 32, 43312, -43313, 43335, 0,
+ 43348, -43349, 43359, 32, 43360, -43361, 43392, 0, 43396, -43397, 43443, 0,
+ 43457, 32, 43470, -43471, 43486, 32, 43488, -43489, 43493, 0, 43494, -43495,
+ 43561, 0, 43575, -43576, 43587, 0, 43588, -43589, 43596, 0, 43598, -43599,
+ 43612, 32, 43616, -43617, 43639, 32, 43642, 43642, 43643, 0, 43646, -43647,
+ 43696, 0, 43697, 43697, 43698, 0, 43701, -43702, 43703, 0, 43705, -43706,
+ 43710, 0, 43712, 43712, 43713, 0, 43714, -43715, 43742, 32, 43744, -43745,
+ 43755, 0, 43760, 32, 43762, -43763, 43765, 0, 43767, -43768, 43867, 32,
+ 43868, -43869, 43882, 32, 43884, -43885, 43888, -5025, 43968, -43969, 44003, 0,
+ 44011, 32, 44012, 0, 44014, -44015, 55296, 0, 57344, -57345, 64286, 0,
+ 64287, -64288, 64297, 32, 64298, -64299, 64434, 32, 64451, -64452, 64830, 32,
+ 64848, -64849, 64975, 32, 65008, -65009, 65020, 32, 65024, 0, 65040, 32,
+ 65050, -65051, 65056, 0, 65072, 32, 65107, 65107, 65108, 32, 65127, 65127,
+ 65128, 32, 65132, -65133, 65279, 0, 65280, 65280, 65281, 32, 65296, -65297,
+ 65306, 32, 65313, -65346, 65339, 32, 65345, -65346, 65371, 32, 65382, -65383,
+ 65504, 32, 65511, 65511, 65512, 32, 65519, -65520, 65529, 0, 65532, 32,
+ 65536, -65537, 65792, 32, 65795, -65796, 65847, 32, 65856, -65857, 65913, 32,
+ 65930, -65931, 65932, 32, 65935, 65935, 65936, 32, 65949, -65950, 65952, 32,
+ 65953, -65954, 66000, 32, 66045, 0, 66046, -66047, 66272, 0, 66273, -66274,
+ 66422, 0, 66427, -66428, 66463, 32, 66464, -66465, 66512, 32, 66513, -66514,
+ 66560, -66601, 66600, -66601, 66736, -66777, 66772, -66773, 66927, 32, 66928, -66968,
+ 66939, 66939, 66940, -66980, 66955, 66955, 66956, -66996, 66963, 66963, 66964, -67004,
+ 66966, -66967, 67671, 32, 67672, -67673, 67703, 32, 67705, -67706, 67871, 32,
+ 67872, -67873, 67903, 32, 67904, -67905, 68097, 0, 68100, 68100, 68101, 0,
+ 68103, -68104, 68108, 0, 68112, -68113, 68152, 0, 68155, -68156, 68159, 0,
+ 68160, -68161, 68176, 32, 68185, -68186, 68223, 32, 68224, -68225, 68296, 32,
+ 68297, -68298, 68325, 0, 68327, -68328, 68336, 32, 68343, -68344, 68409, 32,
+ 68416, -68417, 68505, 32, 68509, -68510, 68736, -68801, 68787, -68788, 68900, 0,
+ 68904, -68905, 69291, 0, 69293, 32, 69294, -69295, 69373, 0, 69376, -69377,
+ 69446, 0, 69457, -69458, 69461, 32, 69466, -69467, 69506, 0, 69510, 32,
+ 69514, -69515, 69632, 0, 69635, -69636, 69688, 0, 69703, 32, 69710, -69711,
+ 69744, 0, 69745, -69746, 69747, 0, 69749, -69750, 69759, 0, 69763, -69764,
+ 69808, 0, 69819, 32, 69821, 0, 69822, 32, 69826, 0, 69827, -69828,
+ 69837, 0, 69838, -69839, 69888, 0, 69891, -69892, 69927, 0, 69941, -69942,
+ 69952, 32, 69956, 69956, 69957, 0, 69959, -69960, 70003, 0, 70004, 32,
+ 70006, -70007, 70016, 0, 70019, -70020, 70067, 0, 70081, -70082, 70085, 32,
+ 70089, 0, 70093, 32, 70094, 0, 70096, -70097, 70107, 32, 70108, 70108,
+ 70109, 32, 70112, -70113, 70188, 0, 70200, 32, 70206, 0, 70207, -70208,
+ 70209, 0, 70210, -70211, 70313, 32, 70314, -70315, 70367, 0, 70379, -70380,
+ 70400, 0, 70404, -70405, 70459, 0, 70461, 70461, 70462, 0, 70469, -70470,
+ 70471, 0, 70473, -70474, 70475, 0, 70478, -70479, 70487, 0, 70488, -70489,
+ 70498, 0, 70500, -70501, 70502, 0, 70509, -70510, 70512, 0, 70517, -70518,
+ 70709, 0, 70727, -70728, 70731, 32, 70736, -70737, 70746, 32, 70748, 70748,
+ 70749, 32, 70750, 0, 70751, -70752, 70832, 0, 70852, -70853, 70854, 32,
+ 70855, -70856, 71087, 0, 71094, -71095, 71096, 0, 71105, 32, 71128, -71129,
+ 71132, 0, 71134, -71135, 71216, 0, 71233, 32, 71236, -71237, 71264, 32,
+ 71277, -71278, 71339, 0, 71352, 71352, 71353, 32, 71354, -71355, 71453, 0,
+ 71468, -71469, 71484, 32, 71488, -71489, 71724, 0, 71739, 32, 71740, -71741,
+ 71840, -71873, 71872, -71873, 71984, 0, 71990, 71990, 71991, 0, 71993, -71994,
+ 71995, 0, 71999, 71999, 72000, 0, 72001, 72001, 72002, 0, 72004, 32,
+ 72007, -72008, 72145, 0, 72152, -72153, 72154, 0, 72161, 72161, 72162, 32,
+ 72163, 72163, 72164, 0, 72165, -72166, 72193, 0, 72203, -72204, 72243, 0,
+ 72250, 72250, 72251, 0, 72255, 32, 72263, 0, 72264, -72265, 72273, 0,
+ 72284, -72285, 72330, 0, 72346, 32, 72349, 72349, 72350, 32, 72355, -72356,
+ 72448, 32, 72458, -72459, 72751, 0, 72759, 72759, 72760, 0, 72768, 72768,
+ 72769, 32, 72774, -72775, 72816, 32, 72818, -72819, 72850, 0, 72872, 72872,
+ 72873, 0, 72887, -72888, 73009, 0, 73015, -73016, 73018, 0, 73019, 73019,
+ 73020, 0, 73022, 73022, 73023, 0, 73030, 73030, 73031, 0, 73032, -73033,
+ 73098, 0, 73103, 73103, 73104, 0, 73106, 73106, 73107, 0, 73112, -73113,
+ 73459, 0, 73463, 32, 73465, -73466, 73472, 0, 73474, 73474, 73475, 0,
+ 73476, -73477, 73524, 0, 73531, -73532, 73534, 0, 73539, 32, 73552, -73553,
+ 73685, 32, 73714, -73715, 73727, 32, 73728, -73729, 74864, 32, 74869, -74870,
+ 77809, 32, 77811, -77812, 78896, 0, 78913, -78914, 78919, 0, 78934, -78935,
+ 92782, 32, 92784, -92785, 92912, 0, 92917, 32, 92918, -92919, 92976, 0,
+ 92983, 32, 92992, -92993, 92996, 32, 92998, -92999, 93760, -93793, 93792, -93793,
+ 93847, 32, 93851, -93852, 94031, 0, 94032, 94032, 94033, 0, 94088, -94089,
+ 94095, 0, 94099, -94100, 94178, 32, 94179, 94179, 94180, 0, 94181, -94182,
+ 94192, 0, 94194, -94195, 113820, 32, 113821, 0, 113823, 32, 113824, 0,
+ 113828, -113829, 118528, 0, 118574, -118575, 118576, 0, 118599, -118600, 118608, 32,
+ 118724, -118725, 118784, 32, 119030, -119031, 119040, 32, 119079, -119080, 119081, 32,
+ 119141, 0, 119146, 32, 119149, 0, 119171, 32, 119173, 0, 119180, 32,
+ 119210, 0, 119214, 32, 119275, -119276, 119296, 32, 119362, 0, 119365, 32,
+ 119366, -119367, 119552, 32, 119639, -119640, 119808, -98, 119834, -119835, 119860, -98,
+ 119886, -119887, 119912, -98, 119938, -119939, 119964, 97, 119965, 119965, 119966, -100,
+ 119968, -119969, 119970, 103, 119971, -119972, 119973, -107, 119975, -119976, 119977, -111,
+ 119981, 119981, 119982, -116, 119990, -119991, 120016, -98, 120042, -120043, 120068, -98,
+ 120070, 120070, 120071, -101, 120075, -120076, 120077, -107, 120085, 120085, 120086, -116,
+ 120093, -120094, 120120, -98, 120122, 120122, 120123, -101, 120127, 120127, 120128, -106,
+ 120133, 120133, 120134, 111, 120135, -120136, 120138, -116, 120145, -120146, 120172, -98,
+ 120198, -120199, 120224, -98, 120250, -120251, 120276, -98, 120302, -120303, 120328, -98,
+ 120354, -120355, 120380, -98, 120406, -120407, 120432, -98, 120458, -120459, 120488, -946,
+ 120505, 952, 120506, -964, 120513, 32, 120514, -120515, 120531, 963, 120532, -120533,
+ 120539, 32, 120540, -120541, 120546, -946, 120563, 952, 120564, -964, 120571, 32,
+ 120572, -120573, 120589, 963, 120590, -120591, 120597, 32, 120598, -120599, 120604, -946,
+ 120621, 952, 120622, -964, 120629, 32, 120630, -120631, 120647, 963, 120648, -120649,
+ 120655, 32, 120656, -120657, 120662, -946, 120679, 952, 120680, -964, 120687, 32,
+ 120688, -120689, 120705, 963, 120706, -120707, 120713, 32, 120714, -120715, 120720, -946,
+ 120737, 952, 120738, -964, 120745, 32, 120746, -120747, 120763, 963, 120764, -120765,
+ 120771, 32, 120772, -120773, 120778, 989, 120779, -120780, 120832, 32, 121344, 0,
+ 121399, 32, 121403, 0, 121453, 32, 121461, 0, 121462, 32, 121476, 0,
+ 121477, 32, 121484, -121485, 121499, 0, 121504, 121504, 121505, 0, 121520, -121521,
+ 122880, 0, 122887, 122887, 122888, 0, 122905, -122906, 122907, 0, 122914, 122914,
+ 122915, 0, 122917, 122917, 122918, 0, 122923, -122924, 123023, 0, 123024, -123025,
+ 123184, 0, 123191, -123192, 123215, 32, 123216, -123217, 123566, 0, 123567, -123568,
+ 123628, 0, 123632, -123633, 123647, 32, 123648, -123649, 124140, 0, 124144, -124145,
+ 125136, 0, 125143, -125144, 125184, -125219, 125218, -125219, 125252, 0, 125259, -125260,
+ 125278, 32, 125280, -125281, 126124, 32, 126125, -126126, 126128, 32, 126129, -126130,
+ 126254, 32, 126255, -126256, 126704, 32, 126706, -126707, 126976, 32, 127020, -127021,
+ 127024, 32, 127124, -127125, 127136, 32, 127151, -127152, 127153, 32, 127168, 127168,
+ 127169, 32, 127184, 127184, 127185, 32, 127222, -127223, 127245, 32, 127275, 99,
+ 127276, 114, 127277, 32, 127280, -98, 127306, 32, 127406, -127407, 127462, 32,
+ 127490, -127491, 127552, 32, 127561, -127562, 127584, 32, 127590, -127591, 127744, 32,
+ 128728, -128729, 128732, 32, 128749, -128750, 128752, 32, 128765, -128766, 128768, 32,
+ 128887, -128888, 128891, 32, 128986, -128987, 128992, 32, 129004, -129005, 129008, 32,
+ 129009, -129010, 129024, 32, 129036, -129037, 129040, 32, 129096, -129097, 129104, 32,
+ 129114, -129115, 129120, 32, 129160, -129161, 129168, 32, 129198, -129199, 129200, 32,
+ 129202, -129203, 129280, 32, 129620, -129621, 129632, 32, 129646, -129647, 129648, 32,
+ 129661, -129662, 129664, 32, 129673, -129674, 129680, 32, 129726, 129726, 129727, 32,
+ 129734, -129735, 129742, 32, 129756, -129757, 129760, 32, 129769, -129770, 129776, 32,
+ 129785, -129786, 129792, 32, 129939, 129939, 129940, 32, 129995, -129996, 131070, 32,
+ 131072, -131073, 196606, 32, 196608, -196609, 262142, 32, 262144, -262145, 327678, 32,
+ 327680, -327681, 393214, 32, 393216, -393217, 458750, 32, 458752, -458753, 524286, 32,
+ 524288, -524289, 589822, 32, 589824, -589825, 655358, 32, 655360, -655361, 720894, 32,
+ 720896, -720897, 786430, 32, 786432, -786433, 851966, 32, 851968, -851969, 917502, 32,
+ 917504, 917504, 917505, 0, 917506, -917507, 917536, 0, 917632, -917633, 917760, 0,
+ 918000, -918001, 983038, 32, 983040, -983041, 1048574, 32, 1048576, -1048577, 1114110, 32,
+ 2147483647, 0};
-static int16 to_lower_table[TABLE_SIZE] = {
+static const int16 to_lower_table[TABLE_SIZE] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
@@ -485,33 +597,680 @@ static int16 to_lower_table[TABLE_SIZE] = {
1273, 1275, 1275, 1277, 1277, 1279, 1279};
static const int32 to_lower_ranges[] = {
- 1280, 2097153, 1328, 1328, 1329, -1378, 1367, -1368, 4256, -11521, 4294, 4294, 4295,
- 11559, 4296, -4297, 4301, 11565, 4302, -4303, 5024, -43889, 5104, -5113, 5110, -5111,
- 7680, 2097153, 7830, -7831, 7838, 223, 7839, 2097153, 7936, -7937, 7944, -7937, 7952,
- -7953, 7960, -7953, 7966, -7967, 7976, -7969, 7984, -7985, 7992, -7985, 8000, -8001,
- 8008, -8001, 8014, -8015, 8025, 8017, 8026, 8026, 8027, 8019, 8028, 8028, 8029,
- 8021, 8030, 8030, 8031, 8023, 8032, -8033, 8040, -8033, 8048, -8049, 8072, -8065,
- 8080, -8081, 8088, -8081, 8096, -8097, 8104, -8097, 8112, -8113, 8120, -8113, 8122,
- -8049, 8124, 8115, 8125, -8126, 8136, -8051, 8140, 8131, 8141, -8142, 8152, -8145,
- 8154, -8055, 8156, -8157, 8168, -8161, 8170, -8059, 8172, 8165, 8173, -8174, 8184,
- -8057, 8186, -8061, 8188, 8179, 8189, -8190, 8486, 969, 8487, -8488, 8490, 107,
- 8491, 229, 8492, -8493, 8498, 8526, 8499, -8500, 8544, -8561, 8560, -8561, 8579,
- 8580, 8581, -8582, 9398, -9425, 9424, -9425, 11264, -11313, 11311, -11312, 11360, 11361,
- 11362, 619, 11363, 7549, 11364, 637, 11365, -11366, 11367, 11368, 11369, 11370, 11371,
- 11372, 11373, 593, 11374, 625, 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380,
- 11381, 11382, 11383, -11384, 11390, -576, 11392, 2097153, 11492, -11493, 11499, 11500, 11501,
- 11502, 11503, -11504, 11506, 11507, 11508, -11509, 42560, 2097153, 42606, -42607, 42624, 2097153,
- 42652, -42653, 42786, 2097153, 42800, -42801, 42802, 2097153, 42864, -42865, 42873, 42874, 42875,
- 42876, 42877, 7545, 42878, 2097153, 42888, -42889, 42891, 42892, 42893, 613, 42894, -42895,
- 42896, 2097153, 42900, -42901, 42902, 2097153, 42922, 614, 42923, 604, 42924, 609, 42925,
- 620, 42926, 618, 42927, 42927, 42928, 670, 42929, 647, 42930, 669, 42931, 43859,
- 42932, 2097153, 42936, -42937, 65313, -65346, 65339, -65340, 66560, -66601, 66600, -66601, 66736,
- -66777, 66772, -66773, 68736, -68801, 68787, -68788, 71840, -71873, 71872, -71873, 125184, -125219,
- 125218, -125219, 2147483647, 0};
+ 1280, 2097153, 1328, 1328, 1329, -1378, 1367, -1368, 4256, -11521, 4294, 4294, 4295,
+ 11559, 4296, -4297, 4301, 11565, 4302, -4303, 5024, -43889, 5104, -5113, 5110, -5111,
+ 7312, -4305, 7355, -7356, 7357, -4350, 7360, -7361, 7680, 2097153, 7830, -7831, 7838,
+ 223, 7839, 2097153, 7936, -7937, 7944, -7937, 7952, -7953, 7960, -7953, 7966, -7967,
+ 7976, -7969, 7984, -7985, 7992, -7985, 8000, -8001, 8008, -8001, 8014, -8015, 8025,
+ 8017, 8026, 8026, 8027, 8019, 8028, 8028, 8029, 8021, 8030, 8030, 8031, 8023,
+ 8032, -8033, 8040, -8033, 8048, -8049, 8072, -8065, 8080, -8081, 8088, -8081, 8096,
+ -8097, 8104, -8097, 8112, -8113, 8120, -8113, 8122, -8049, 8124, 8115, 8125, -8126,
+ 8136, -8051, 8140, 8131, 8141, -8142, 8152, -8145, 8154, -8055, 8156, -8157, 8168,
+ -8161, 8170, -8059, 8172, 8165, 8173, -8174, 8184, -8057, 8186, -8061, 8188, 8179,
+ 8189, -8190, 8486, 969, 8487, -8488, 8490, 107, 8491, 229, 8492, -8493, 8498,
+ 8526, 8499, -8500, 8544, -8561, 8560, -8561, 8579, 8580, 8581, -8582, 9398, -9425,
+ 9424, -9425, 11264, -11313, 11312, -11313, 11360, 11361, 11362, 619, 11363, 7549, 11364,
+ 637, 11365, -11366, 11367, 11368, 11369, 11370, 11371, 11372, 11373, 593, 11374, 625,
+ 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380, 11381, 11382, 11383, -11384, 11390,
+ -576, 11392, 2097153, 11492, -11493, 11499, 11500, 11501, 11502, 11503, -11504, 11506, 11507,
+ 11508, -11509, 42560, 2097153, 42606, -42607, 42624, 2097153, 42652, -42653, 42786, 2097153, 42800,
+ -42801, 42802, 2097153, 42864, -42865, 42873, 42874, 42875, 42876, 42877, 7545, 42878, 2097153,
+ 42888, -42889, 42891, 42892, 42893, 613, 42894, -42895, 42896, 2097153, 42900, -42901, 42902,
+ 2097153, 42922, 614, 42923, 604, 42924, 609, 42925, 620, 42926, 618, 42927, 42927,
+ 42928, 670, 42929, 647, 42930, 669, 42931, 43859, 42932, 2097153, 42948, 42900, 42949,
+ 642, 42950, 7566, 42951, 42952, 42953, 42954, 42955, -42956, 42960, 42961, 42962, -42963,
+ 42966, 2097153, 42970, -42971, 42997, 42998, 42999, -43000, 65313, -65346, 65339, -65340, 66560,
+ -66601, 66600, -66601, 66736, -66777, 66772, -66773, 66928, -66968, 66939, 66939, 66940, -66980,
+ 66955, 66955, 66956, -66996, 66963, 66963, 66964, -67004, 66966, -66967, 68736, -68801, 68787,
+ -68788, 71840, -71873, 71872, -71873, 93760, -93793, 93792, -93793, 125184, -125219, 125218, -125219,
+ 2147483647, 0};
+
+static const int16 without_diacritics_table[TABLE_SIZE] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94,
+ 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
+ 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132,
+ 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151,
+ 152, 153, 154, 155, 156, 157, 158, 159, 32, 161, 162, 163, 164, 165, 166, 167, 32, 169, 97,
+ 171, 172, 0, 174, 32, 176, 177, 50, 51, 32, 956, 182, 0, 32, 49, 111, 187, 188, 189,
+ 190, 191, 65, 65, 65, 65, 65, 65, 198, 67, 69, 69, 69, 69, 73, 73, 73, 73, 208,
+ 78, 79, 79, 79, 79, 79, 215, 216, 85, 85, 85, 85, 89, 222, 223, 97, 97, 97, 97,
+ 97, 97, 230, 99, 101, 101, 101, 101, 105, 105, 105, 105, 240, 110, 111, 111, 111, 111, 111,
+ 247, 248, 117, 117, 117, 117, 121, 254, 121, 65, 97, 65, 97, 65, 97, 67, 99, 67, 99,
+ 67, 99, 67, 99, 68, 100, 272, 273, 69, 101, 69, 101, 69, 101, 69, 101, 69, 101, 71,
+ 103, 71, 103, 71, 103, 71, 103, 72, 104, 294, 295, 73, 105, 73, 105, 73, 105, 73, 105,
+ 73, 305, 306, 307, 74, 106, 75, 107, 312, 76, 108, 76, 108, 76, 108, 76, 108, 321, 322,
+ 78, 110, 78, 110, 78, 110, 110, 330, 331, 79, 111, 79, 111, 79, 111, 338, 339, 82, 114,
+ 82, 114, 82, 114, 83, 115, 83, 115, 83, 115, 83, 115, 84, 116, 84, 116, 358, 359, 85,
+ 117, 85, 117, 85, 117, 85, 117, 85, 117, 85, 117, 87, 119, 89, 121, 89, 90, 122, 90,
+ 122, 90, 122, 115, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398,
+ 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 79, 111,
+ 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 85, 117, 433, 434, 435, 436,
+ 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455,
+ 456, 457, 458, 459, 460, 65, 97, 73, 105, 79, 111, 85, 117, 85, 117, 85, 117, 85, 117,
+ 85, 117, 477, 65, 97, 65, 97, 198, 230, 484, 485, 71, 103, 75, 107, 79, 111, 79, 111,
+ 439, 658, 106, 497, 498, 499, 71, 103, 502, 503, 78, 110, 65, 97, 198, 230, 216, 248, 65,
+ 97, 65, 97, 69, 101, 69, 101, 73, 105, 73, 105, 79, 111, 79, 111, 82, 114, 82, 114,
+ 85, 117, 85, 117, 83, 115, 84, 116, 540, 541, 72, 104, 544, 545, 546, 547, 548, 549, 65,
+ 97, 69, 101, 79, 111, 79, 111, 79, 111, 79, 111, 89, 121, 564, 565, 566, 567, 568, 569,
+ 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588,
+ 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607,
+ 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626,
+ 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645,
+ 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664,
+ 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683,
+ 684, 685, 686, 687, 104, 614, 106, 114, 633, 635, 641, 119, 121, 697, 698, 699, 0, 701, 0,
+ 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721,
+ 722, 723, 724, 725, 726, 727, 32, 32, 32, 32, 32, 32, 734, 735, 611, 108, 115, 120, 661,
+ 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759,
+ 760, 761, 762, 763, 764, 765, 766, 767, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 837, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 880, 881, 882, 883, 697, 885, 886, 887, 888, 889, 32, 891, 892,
+ 893, 59, 895, 896, 897, 898, 899, 32, 32, 913, 903, 917, 919, 921, 907, 927, 909, 933, 937,
+ 953, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930,
+ 931, 932, 933, 934, 935, 936, 937, 921, 933, 945, 949, 951, 953, 965, 945, 946, 947, 948, 949,
+ 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968,
+ 969, 953, 965, 959, 965, 969, 975, 946, 952, 933, 933, 933, 966, 960, 983, 984, 985, 986, 987,
+ 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006,
+ 1007, 954, 961, 962, 1011, 920, 949, 1014, 1015, 1016, 931, 1018, 1019, 1020, 1021, 1022, 1023, 1045, 1045,
+ 1026, 1043, 1028, 1029, 1030, 1030, 1032, 1033, 1034, 1035, 1050, 1048, 1059, 1039, 1040, 1041, 1042, 1043, 1044,
+ 1045, 1046, 1047, 1048, 1048, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063,
+ 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1080, 1082,
+ 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101,
+ 1102, 1103, 1077, 1077, 1106, 1075, 1108, 1109, 1110, 1110, 1112, 1113, 1114, 1115, 1082, 1080, 1091, 1119, 1120,
+ 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139,
+ 1140, 1141, 1140, 1141, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154, 0, 0, 0, 0,
+ 0, 0, 0, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177,
+ 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1195, 1196,
+ 1197, 1198, 1199, 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1213, 1214, 1215,
+ 1216, 1046, 1078, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1231, 1040, 1072, 1040,
+ 1072, 1236, 1237, 1045, 1077, 1240, 1241, 1240, 1241, 1046, 1078, 1047, 1079, 1248, 1249, 1048, 1080, 1048, 1080,
+ 1054, 1086, 1256, 1257, 1256, 1257, 1069, 1101, 1059, 1091, 1059, 1091, 1059, 1091, 1063, 1095, 1270, 1271, 1067,
+ 1099, 1274, 1275, 1276, 1277, 1278, 1279};
+
+static const int32 without_diacritics_ranges[] = {
+ 1280, -1281, 1425, 0, 1470, 1470, 1471, 0, 1472, 1472, 1473, 0,
+ 1475, 1475, 1476, 0, 1478, 1478, 1479, 0, 1480, -1481, 1536, 0,
+ 1542, -1543, 1552, 0, 1563, 1563, 1564, 0, 1565, -1566, 1570, 1575,
+ 1572, 1608, 1573, 1575, 1574, 1610, 1575, -1576, 1611, 0, 1632, -1633,
+ 1648, 0, 1649, -1650, 1728, 1749, 1729, 2097154, 1732, -1733, 1747, 2097152,
+ 1749, 1749, 1750, 0, 1758, 1758, 1759, 0, 1765, -1766, 1767, 0,
+ 1769, 1769, 1770, 0, 1774, -1775, 1807, 0, 1808, 1808, 1809, 0,
+ 1810, -1811, 1840, 0, 1867, -1868, 1958, 0, 1969, -1970, 2027, 0,
+ 2036, -2037, 2045, 0, 2046, -2047, 2070, 0, 2074, 2074, 2075, 0,
+ 2084, 2084, 2085, 0, 2088, 2088, 2089, 0, 2094, -2095, 2137, 0,
+ 2140, -2141, 2192, 0, 2194, -2195, 2200, 0, 2208, -2209, 2250, 0,
+ 2308, -2309, 2345, 2097152, 2347, -2348, 2353, 2097152, 2355, 2097154, 2358, -2359,
+ 2362, 0, 2365, 2365, 2366, 0, 2384, 2384, 2385, 0, 2392, -2326,
+ 2395, 2332, 2396, -2338, 2398, 2347, 2399, 2351, 2400, -2401, 2402, 0,
+ 2404, -2405, 2433, 0, 2436, -2437, 2492, 0, 2493, 2493, 2494, 0,
+ 2501, -2502, 2503, 0, 2505, -2506, 2507, 0, 2510, -2511, 2519, 0,
+ 2520, -2521, 2524, -2466, 2526, 2526, 2527, 2479, 2528, -2529, 2530, 0,
+ 2532, -2533, 2558, 0, 2559, -2560, 2561, 0, 2564, -2565, 2611, 2097152,
+ 2613, 2613, 2614, 2616, 2615, -2616, 2620, 0, 2621, 2621, 2622, 0,
+ 2627, -2628, 2631, 0, 2633, -2634, 2635, 0, 2638, -2639, 2641, 0,
+ 2642, -2643, 2649, -2583, 2651, 2588, 2652, -2653, 2654, 2603, 2655, -2656,
+ 2672, 0, 2674, -2675, 2677, 0, 2678, -2679, 2689, 0, 2692, -2693,
+ 2748, 0, 2749, 2749, 2750, 0, 2758, 2758, 2759, 0, 2762, 2762,
+ 2763, 0, 2766, -2767, 2786, 0, 2788, -2789, 2810, 0, 2816, 2816,
+ 2817, 0, 2820, -2821, 2876, 0, 2877, 2877, 2878, 0, 2885, -2886,
+ 2887, 0, 2889, -2890, 2891, 0, 2894, -2895, 2901, 0, 2904, -2905,
+ 2908, -2850, 2910, -2911, 2914, 0, 2916, -2917, 2946, 0, 2947, -2948,
+ 2964, 2962, 2965, -2966, 3006, 0, 3011, -3012, 3014, 0, 3017, 3017,
+ 3018, 0, 3022, -3023, 3031, 0, 3032, -3033, 3072, 0, 3077, -3078,
+ 3132, 0, 3133, 3133, 3134, 0, 3141, 3141, 3142, 0, 3145, 3145,
+ 3146, 0, 3150, -3151, 3157, 0, 3159, -3160, 3170, 0, 3172, -3173,
+ 3201, 0, 3204, -3205, 3260, 0, 3261, 3261, 3262, 0, 3269, 3269,
+ 3270, 0, 3273, 3273, 3274, 0, 3278, -3279, 3285, 0, 3287, -3288,
+ 3298, 0, 3300, -3301, 3315, 0, 3316, -3317, 3328, 0, 3332, -3333,
+ 3387, 0, 3389, 3389, 3390, 0, 3397, 3397, 3398, 0, 3401, 3401,
+ 3402, 0, 3406, -3407, 3415, 0, 3416, -3417, 3426, 0, 3428, -3429,
+ 3457, 0, 3460, -3461, 3530, 0, 3531, -3532, 3535, 0, 3541, 3541,
+ 3542, 0, 3543, 3543, 3544, 0, 3552, -3553, 3570, 0, 3572, -3573,
+ 3633, 0, 3634, 3634, 3636, 0, 3643, -3644, 3655, 0, 3663, -3664,
+ 3761, 0, 3762, 3762, 3764, 0, 3773, -3774, 3784, 0, 3791, -3792,
+ 3852, 2097154, 3854, -3855, 3864, 0, 3866, -3867, 3893, 0, 3894, 3894,
+ 3895, 0, 3896, 3896, 3897, 0, 3898, -3899, 3902, 0, 3904, -3905,
+ 3907, 2097152, 3909, -3910, 3917, 2097152, 3919, -3920, 3922, 2097154, 3924, -3925,
+ 3927, 2097152, 3929, -3930, 3932, 2097154, 3934, -3935, 3945, 3904, 3946, -3947,
+ 3953, 0, 3973, 3973, 3974, 0, 3976, -3977, 3981, 0, 3992, 3992,
+ 3993, 0, 4029, -4030, 4038, 0, 4039, -4040, 4134, 2097154, 4136, -4137,
+ 4139, 0, 4159, -4160, 4182, 0, 4186, -4187, 4190, 0, 4193, 4193,
+ 4194, 0, 4197, -4198, 4199, 0, 4206, -4207, 4209, 0, 4213, -4214,
+ 4226, 0, 4238, 4238, 4239, 0, 4240, -4241, 4250, 0, 4254, -4255,
+ 4348, 4316, 4349, -4350, 4957, 0, 4960, -4961, 5906, 0, 5910, -5911,
+ 5938, 0, 5941, -5942, 5970, 0, 5972, -5973, 6002, 0, 6004, -6005,
+ 6068, 0, 6100, -6101, 6109, 0, 6110, -6111, 6155, 0, 6160, -6161,
+ 6277, 0, 6279, -6280, 6313, 0, 6314, -6315, 6432, 0, 6444, -6445,
+ 6448, 0, 6460, -6461, 6679, 0, 6684, -6685, 6741, 0, 6751, 6751,
+ 6752, 0, 6781, -6782, 6783, 0, 6784, -6785, 6832, 0, 6863, -6864,
+ 6912, 0, 6917, 2097154, 6928, -6929, 6930, 2097154, 6932, -6933, 6964, 0,
+ 6981, -6982, 7019, 0, 7028, -7029, 7040, 0, 7043, -7044, 7073, 0,
+ 7086, -7087, 7142, 0, 7156, -7157, 7204, 0, 7224, -7225, 7376, 0,
+ 7379, 7379, 7380, 0, 7401, -7402, 7405, 0, 7406, -7407, 7412, 0,
+ 7413, -7414, 7415, 0, 7418, -7419, 7468, 65, 7469, 198, 7470, 66,
+ 7471, 7471, 7472, -69, 7474, 398, 7475, -72, 7483, 7483, 7484, 79,
+ 7485, 546, 7486, 80, 7487, 82, 7488, -85, 7490, 87, 7491, 97,
+ 7492, -593, 7494, 7426, 7495, 98, 7496, -101, 7498, 601, 7499, -604,
+ 7501, 103, 7502, 7502, 7503, 107, 7504, 109, 7505, 331, 7506, 111,
+ 7507, 596, 7508, -7447, 7510, 112, 7511, -117, 7513, 7453, 7514, 623,
+ 7515, 118, 7516, 7461, 7517, -947, 7520, -967, 7522, 105, 7523, 114,
+ 7524, -118, 7526, -947, 7528, 961, 7529, -967, 7531, -7532, 7544, 1085,
+ 7545, -7546, 7579, 594, 7580, 99, 7581, 597, 7582, 240, 7583, 604,
+ 7584, 102, 7585, 607, 7586, 609, 7587, 613, 7588, -617, 7591, 7547,
+ 7592, 669, 7593, 621, 7594, 7557, 7595, 671, 7596, 625, 7597, 624,
+ 7598, -627, 7602, 632, 7603, -643, 7605, 427, 7606, -650, 7608, 7452,
+ 7609, -652, 7611, 122, 7612, -657, 7615, 952, 7616, 0, 7680, 65,
+ 7681, 97, 7682, 66, 7683, 98, 7684, 66, 7685, 98, 7686, 66,
+ 7687, 98, 7688, 67, 7689, 99, 7690, 68, 7691, 100, 7692, 68,
+ 7693, 100, 7694, 68, 7695, 100, 7696, 68, 7697, 100, 7698, 68,
+ 7699, 100, 7700, 69, 7701, 101, 7702, 69, 7703, 101, 7704, 69,
+ 7705, 101, 7706, 69, 7707, 101, 7708, 69, 7709, 101, 7710, 70,
+ 7711, 102, 7712, 71, 7713, 103, 7714, 72, 7715, 104, 7716, 72,
+ 7717, 104, 7718, 72, 7719, 104, 7720, 72, 7721, 104, 7722, 72,
+ 7723, 104, 7724, 73, 7725, 105, 7726, 73, 7727, 105, 7728, 75,
+ 7729, 107, 7730, 75, 7731, 107, 7732, 75, 7733, 107, 7734, 76,
+ 7735, 108, 7736, 76, 7737, 108, 7738, 76, 7739, 108, 7740, 76,
+ 7741, 108, 7742, 77, 7743, 109, 7744, 77, 7745, 109, 7746, 77,
+ 7747, 109, 7748, 78, 7749, 110, 7750, 78, 7751, 110, 7752, 78,
+ 7753, 110, 7754, 78, 7755, 110, 7756, 79, 7757, 111, 7758, 79,
+ 7759, 111, 7760, 79, 7761, 111, 7762, 79, 7763, 111, 7764, 80,
+ 7765, 112, 7766, 80, 7767, 112, 7768, 82, 7769, 114, 7770, 82,
+ 7771, 114, 7772, 82, 7773, 114, 7774, 82, 7775, 114, 7776, 83,
+ 7777, 115, 7778, 83, 7779, 115, 7780, 83, 7781, 115, 7782, 83,
+ 7783, 115, 7784, 83, 7785, 115, 7786, 84, 7787, 116, 7788, 84,
+ 7789, 116, 7790, 84, 7791, 116, 7792, 84, 7793, 116, 7794, 85,
+ 7795, 117, 7796, 85, 7797, 117, 7798, 85, 7799, 117, 7800, 85,
+ 7801, 117, 7802, 85, 7803, 117, 7804, 86, 7805, 118, 7806, 86,
+ 7807, 118, 7808, 87, 7809, 119, 7810, 87, 7811, 119, 7812, 87,
+ 7813, 119, 7814, 87, 7815, 119, 7816, 87, 7817, 119, 7818, 88,
+ 7819, 120, 7820, 88, 7821, 120, 7822, 89, 7823, 121, 7824, 90,
+ 7825, 122, 7826, 90, 7827, 122, 7828, 90, 7829, 122, 7830, 104,
+ 7831, 116, 7832, 119, 7833, 121, 7834, 97, 7835, 115, 7836, -7837,
+ 7840, 65, 7841, 97, 7842, 65, 7843, 97, 7844, 65, 7845, 97,
+ 7846, 65, 7847, 97, 7848, 65, 7849, 97, 7850, 65, 7851, 97,
+ 7852, 65, 7853, 97, 7854, 65, 7855, 97, 7856, 65, 7857, 97,
+ 7858, 65, 7859, 97, 7860, 65, 7861, 97, 7862, 65, 7863, 97,
+ 7864, 69, 7865, 101, 7866, 69, 7867, 101, 7868, 69, 7869, 101,
+ 7870, 69, 7871, 101, 7872, 69, 7873, 101, 7874, 69, 7875, 101,
+ 7876, 69, 7877, 101, 7878, 69, 7879, 101, 7880, 73, 7881, 105,
+ 7882, 73, 7883, 105, 7884, 79, 7885, 111, 7886, 79, 7887, 111,
+ 7888, 79, 7889, 111, 7890, 79, 7891, 111, 7892, 79, 7893, 111,
+ 7894, 79, 7895, 111, 7896, 79, 7897, 111, 7898, 79, 7899, 111,
+ 7900, 79, 7901, 111, 7902, 79, 7903, 111, 7904, 79, 7905, 111,
+ 7906, 79, 7907, 111, 7908, 85, 7909, 117, 7910, 85, 7911, 117,
+ 7912, 85, 7913, 117, 7914, 85, 7915, 117, 7916, 85, 7917, 117,
+ 7918, 85, 7919, 117, 7920, 85, 7921, 117, 7922, 89, 7923, 121,
+ 7924, 89, 7925, 121, 7926, 89, 7927, 121, 7928, 89, 7929, 121,
+ 7930, -7931, 7936, 945, 7944, 913, 7952, 949, 7958, -7959, 7960, 917,
+ 7966, -7967, 7968, 951, 7976, 919, 7984, 953, 7992, 921, 8000, 959,
+ 8006, -8007, 8008, 927, 8014, -8015, 8016, 965, 8024, 8024, 8025, 933,
+ 8026, 8026, 8027, 933, 8028, 8028, 8029, 933, 8030, 8030, 8031, 933,
+ 8032, 969, 8040, 937, 8048, 945, 8050, 949, 8052, 951, 8054, 953,
+ 8056, 959, 8058, 965, 8060, 969, 8062, -8063, 8064, 945, 8072, 913,
+ 8080, 951, 8088, 919, 8096, 969, 8104, 937, 8112, 945, 8117, 8117,
+ 8118, 945, 8120, 913, 8125, 32, 8126, 953, 8127, 32, 8130, 951,
+ 8133, 8133, 8134, 951, 8136, 917, 8138, 919, 8141, 32, 8144, 953,
+ 8148, -8149, 8150, 953, 8152, 921, 8156, 8156, 8157, 32, 8160, 965,
+ 8164, 961, 8166, 965, 8168, 933, 8172, 929, 8173, 32, 8175, 96,
+ 8176, -8177, 8178, 969, 8181, 8181, 8182, 969, 8184, 927, 8186, 937,
+ 8189, 32, 8191, 8191, 8192, 32, 8203, 0, 8208, 2097152, 8211, -8212,
+ 8215, 32, 8216, -8217, 8228, 46, 8229, -8230, 8234, 0, 8239, 32,
+ 8240, -8241, 8254, 32, 8255, -8256, 8287, 32, 8288, 0, 8293, 8293,
+ 8294, 0, 8304, 48, 8305, 105, 8306, -8307, 8308, -53, 8314, 43,
+ 8315, 8722, 8316, 61, 8317, -41, 8319, 110, 8320, -49, 8330, 43,
+ 8331, 8722, 8332, 61, 8333, -41, 8335, 8335, 8336, 97, 8337, 101,
+ 8338, 111, 8339, 120, 8340, 601, 8341, 104, 8342, -108, 8346, 112,
+ 8347, -116, 8349, -8350, 8400, 0, 8433, -8434, 8450, 67, 8452, -8453,
+ 8455, 400, 8456, 8456, 8457, 70, 8458, 103, 8459, 72, 8462, 104,
+ 8463, 295, 8464, 73, 8466, 76, 8467, 108, 8468, 8468, 8469, 78,
+ 8470, -8471, 8473, -81, 8476, 82, 8478, -8479, 8484, 90, 8485, 8485,
+ 8486, 937, 8487, 8487, 8488, 90, 8489, 8489, 8490, 75, 8491, -66,
+ 8494, 8494, 8495, 101, 8496, -70, 8498, 8498, 8499, 77, 8500, 111,
+ 8501, -1489, 8505, 105, 8506, -8507, 8508, 960, 8509, 947, 8510, 915,
+ 8511, 928, 8512, 8721, 8513, -8514, 8517, 68, 8518, -101, 8520, -106,
+ 8522, -8523, 8543, 49, 8544, 73, 8545, -8546, 8548, 86, 8549, -8550,
+ 8553, 88, 8554, -8555, 8556, 76, 8557, -68, 8559, 77, 8560, 105,
+ 8561, -8562, 8564, 118, 8565, -8566, 8569, 120, 8570, -8571, 8572, 108,
+ 8573, -100, 8575, 109, 8576, -8577, 8602, 8592, 8603, 8594, 8604, -8605,
+ 8622, 8596, 8623, -8624, 8653, 8656, 8654, 8660, 8655, 8658, 8656, -8657,
+ 8708, 2097154, 8710, -8711, 8713, 2097152, 8715, 2097154, 8718, -8719, 8740, 2097154,
+ 8744, -8745, 8769, 8764, 8770, -8771, 8772, 2097154, 8774, 8774, 8775, 8773,
+ 8776, 2097152, 8779, -8780, 8800, 61, 8801, 2097154, 8804, -8805, 8813, 8781,
+ 8814, 60, 8815, 62, 8816, -8805, 8818, -8819, 8820, -8819, 8822, -8823,
+ 8824, -8823, 8826, -8827, 8832, -8827, 8834, -8835, 8836, -8835, 8838, -8839,
+ 8840, -8839, 8842, -8843, 8876, 8866, 8877, -8873, 8879, 8875, 8880, -8881,
+ 8928, -8829, 8930, -8850, 8932, -8933, 8938, -8883, 8942, -8943, 9001, -12297,
+ 9003, -9004, 9312, -50, 9321, -9322, 9352, -50, 9361, -9362, 9398, -66,
+ 9424, -98, 9450, 48, 9451, -9452, 10972, 10973, 10974, -10975, 11388, 106,
+ 11389, 86, 11390, -11391, 11503, 0, 11506, -11507, 11631, 11617, 11632, -11633,
+ 11647, 0, 11648, -11649, 11744, 0, 11776, -11777, 11935, 27597, 11936, -11937,
+ 12019, 40863, 12020, -12021, 12032, 19968, 12033, 20008, 12034, 20022, 12035, 20031,
+ 12036, 20057, 12037, 20101, 12038, 20108, 12039, 20128, 12040, 20154, 12041, 20799,
+ 12042, 20837, 12043, 20843, 12044, 20866, 12045, 20886, 12046, 20907, 12047, 20960,
+ 12048, 20981, 12049, 20992, 12050, 21147, 12051, 21241, 12052, 21269, 12053, 21274,
+ 12054, 21304, 12055, 21313, 12056, 21340, 12057, 21353, 12058, 21378, 12059, 21430,
+ 12060, 21448, 12061, 21475, 12062, 22231, 12063, 22303, 12064, 22763, 12065, 22786,
+ 12066, 22794, 12067, 22805, 12068, 22823, 12069, 22899, 12070, 23376, 12071, 23424,
+ 12072, 23544, 12073, 23567, 12074, 23586, 12075, 23608, 12076, 23662, 12077, 23665,
+ 12078, 24027, 12079, 24037, 12080, 24049, 12081, 24062, 12082, 24178, 12083, 24186,
+ 12084, 24191, 12085, 24308, 12086, 24318, 12087, 24331, 12088, 24339, 12089, 24400,
+ 12090, 24417, 12091, 24435, 12092, 24515, 12093, 25096, 12094, 25142, 12095, 25163,
+ 12096, 25903, 12097, 25908, 12098, 25991, 12099, 26007, 12100, 26020, 12101, 26041,
+ 12102, 26080, 12103, 26085, 12104, 26352, 12105, 26376, 12106, 26408, 12107, 27424,
+ 12108, 27490, 12109, 27513, 12110, 27571, 12111, 27595, 12112, 27604, 12113, 27611,
+ 12114, 27663, 12115, 27668, 12116, 27700, 12117, 28779, 12118, 29226, 12119, 29238,
+ 12120, 29243, 12121, 29247, 12122, 29255, 12123, 29273, 12124, 29275, 12125, 29356,
+ 12126, 29572, 12127, 29577, 12128, 29916, 12129, 29926, 12130, 29976, 12131, 29983,
+ 12132, 29992, 12133, 30000, 12134, 30091, 12135, 30098, 12136, 30326, 12137, 30333,
+ 12138, 30382, 12139, 30399, 12140, 30446, 12141, 30683, 12142, 30690, 12143, 30707,
+ 12144, 31034, 12145, 31160, 12146, 31166, 12147, 31348, 12148, 31435, 12149, 31481,
+ 12150, 31859, 12151, 31992, 12152, 32566, 12153, 32593, 12154, 32650, 12155, 32701,
+ 12156, 32769, 12157, 32780, 12158, 32786, 12159, 32819, 12160, 32895, 12161, 32905,
+ 12162, 33251, 12163, 33258, 12164, 33267, 12165, 33276, 12166, 33292, 12167, 33307,
+ 12168, 33311, 12169, 33390, 12170, 33394, 12171, 33400, 12172, 34381, 12173, 34411,
+ 12174, 34880, 12175, 34892, 12176, 34915, 12177, 35198, 12178, 35211, 12179, 35282,
+ 12180, 35328, 12181, 35895, 12182, 35910, 12183, 35925, 12184, 35960, 12185, 35997,
+ 12186, 36196, 12187, 36208, 12188, 36275, 12189, 36523, 12190, 36554, 12191, 36763,
+ 12192, 36784, 12193, 36789, 12194, 37009, 12195, 37193, 12196, 37318, 12197, 37324,
+ 12198, 37329, 12199, 38263, 12200, 38272, 12201, 38428, 12202, 38582, 12203, 38585,
+ 12204, 38632, 12205, 38737, 12206, 38750, 12207, 38754, 12208, 38761, 12209, 38859,
+ 12210, 38893, 12211, 38899, 12212, 38913, 12213, 39080, 12214, 39131, 12215, 39135,
+ 12216, 39318, 12217, 39321, 12218, 39340, 12219, 39592, 12220, 39640, 12221, 39647,
+ 12222, 39717, 12223, 39727, 12224, 39730, 12225, 39740, 12226, 39770, 12227, 40165,
+ 12228, 40565, 12229, 40575, 12230, 40613, 12231, 40635, 12232, 40643, 12233, 40653,
+ 12234, 40657, 12235, 40697, 12236, 40701, 12237, 40718, 12238, 40723, 12239, 40736,
+ 12240, 40763, 12241, 40778, 12242, 40786, 12243, 40845, 12244, 40860, 12245, 40864,
+ 12246, -12247, 12288, 32, 12289, -12290, 12330, 0, 12336, -12337, 12342, 12306,
+ 12343, 12343, 12344, 21313, 12345, -21317, 12347, -12348, 12364, 2097154, 12388, 2097152,
+ 12395, -12396, 12400, 12399, 12402, 12402, 12405, 12405, 12408, 12408, 12411, 12411,
+ 12414, -12415, 12436, 12358, 12437, -12438, 12441, 0, 12443, 32, 12445, 2097154,
+ 12448, -12449, 12460, 2097154, 12484, 2097152, 12491, -12492, 12496, 12495, 12498, 12498,
+ 12501, 12501, 12504, 12504, 12507, 12507, 12510, -12511, 12532, 12454, 12533, -12534,
+ 12535, -12528, 12539, 12539, 12540, 0, 12541, 2097154, 12544, -12545, 12593, -4353,
+ 12595, 4522, 12596, 4354, 12597, -4525, 12599, -4356, 12602, -4529, 12608, 4378,
+ 12609, -4359, 12612, 4385, 12613, -4362, 12623, -4450, 12644, 4448, 12645, -4373,
+ 12647, -4552, 12649, 4556, 12650, 4558, 12651, 4563, 12652, 4567, 12653, 4569,
+ 12654, 4380, 12655, 4573, 12656, 4575, 12657, -4382, 12659, 4384, 12660, -4387,
+ 12662, 4391, 12663, 4393, 12664, -4396, 12669, 4402, 12670, 4406, 12671, 4416,
+ 12672, 4423, 12673, 4428, 12674, -4594, 12676, -4440, 12679, -4485, 12681, 4488,
+ 12682, -4498, 12684, 4500, 12685, 4510, 12686, 4513, 12687, -12688, 12690, 19968,
+ 12691, 20108, 12692, 19977, 12693, 22235, 12694, 19978, 12695, 20013, 12696, 19979,
+ 12697, 30002, 12698, 20057, 12699, 19993, 12700, 19969, 12701, 22825, 12702, 22320,
+ 12703, 20154, 12704, -12705, 12868, 21839, 12869, 24188, 12870, 25991, 12871, 31631,
+ 12872, -12873, 12896, 4352, 12897, -4355, 12899, -4358, 12902, 4361, 12903, -4364,
+ 12905, -4367, 12910, -12911, 12928, 19968, 12929, 20108, 12930, 19977, 12931, 22235,
+ 12932, 20116, 12933, 20845, 12934, 19971, 12935, 20843, 12936, 20061, 12937, 21313,
+ 12938, 26376, 12939, 28779, 12940, 27700, 12941, 26408, 12942, 37329, 12943, 22303,
+ 12944, 26085, 12945, 26666, 12946, 26377, 12947, 31038, 12948, 21517, 12949, 29305,
+ 12950, 36001, 12951, 31069, 12952, 21172, 12953, 31192, 12954, 30007, 12955, 22899,
+ 12956, 36969, 12957, 20778, 12958, 21360, 12959, 27880, 12960, 38917, 12961, 20241,
+ 12962, 20889, 12963, 27491, 12964, 19978, 12965, 20013, 12966, 19979, 12967, 24038,
+ 12968, 21491, 12969, 21307, 12970, 23447, 12971, 23398, 12972, 30435, 12973, 20225,
+ 12974, 36039, 12975, 21332, 12976, 22812, 12977, -12978, 13008, 12450, 13009, 12452,
+ 13010, 12454, 13011, 12456, 13012, -12459, 13014, 12461, 13015, 12463, 13016, 12465,
+ 13017, 12467, 13018, 12469, 13019, 12471, 13020, 12473, 13021, 12475, 13022, 12477,
+ 13023, 12479, 13024, 12481, 13025, 12484, 13026, 12486, 13027, 12488, 13028, -12491,
+ 13034, 12498, 13035, 12501, 13036, 12504, 13037, 12507, 13038, -12511, 13043, 12516,
+ 13044, 12518, 13045, -12521, 13051, -12528, 13055, -13056, 42607, 0, 42611, 42611,
+ 42612, 0, 42622, -42623, 42652, 1098, 42653, 1100, 42654, 0, 42656, -42657,
+ 42736, 0, 42738, -42739, 42864, 2097154, 42866, -42867, 42994, 67, 42995, 70,
+ 42996, 81, 42997, -42998, 43000, 294, 43001, 339, 43002, -43003, 43010, 0,
+ 43011, -43012, 43014, 0, 43015, -43016, 43019, 0, 43020, -43021, 43043, 0,
+ 43048, -43049, 43052, 0, 43053, -43054, 43136, 0, 43138, -43139, 43188, 0,
+ 43206, -43207, 43232, 0, 43250, -43251, 43263, 0, 43264, -43265, 43302, 0,
+ 43310, -43311, 43335, 0, 43348, -43349, 43392, 0, 43396, -43397, 43443, 0,
+ 43457, -43458, 43493, 0, 43494, -43495, 43561, 0, 43575, -43576, 43587, 0,
+ 43588, -43589, 43596, 0, 43598, -43599, 43643, 0, 43646, -43647, 43696, 0,
+ 43697, 43697, 43698, 0, 43701, -43702, 43703, 0, 43705, -43706, 43710, 0,
+ 43712, 43712, 43713, 0, 43714, -43715, 43755, 0, 43760, -43761, 43765, 0,
+ 43767, -43768, 43868, 42791, 43869, 43831, 43870, 619, 43871, 43858, 43872, -43873,
+ 43881, 653, 43882, -43883, 44003, 0, 44011, 44011, 44012, 0, 44014, -44015,
+ 55296, 0, 57344, -57345, 63744, 35912, 63745, 26356, 63746, 36554, 63747, 36040,
+ 63748, 28369, 63749, 20018, 63750, 21477, 63751, 40860, 63753, 22865, 63754, 37329,
+ 63755, 21895, 63756, 22856, 63757, 25078, 63758, 30313, 63759, 32645, 63760, 34367,
+ 63761, 34746, 63762, 35064, 63763, 37007, 63764, 27138, 63765, 27931, 63766, 28889,
+ 63767, 29662, 63768, 33853, 63769, 37226, 63770, 39409, 63771, 20098, 63772, 21365,
+ 63773, 27396, 63774, 29211, 63775, 34349, 63776, 40478, 63777, 23888, 63778, 28651,
+ 63779, 34253, 63780, 35172, 63781, 25289, 63782, 33240, 63783, 34847, 63784, 24266,
+ 63785, 26391, 63786, 28010, 63787, 29436, 63788, 37070, 63789, 20358, 63790, 20919,
+ 63791, 21214, 63792, 25796, 63793, 27347, 63794, 29200, 63795, 30439, 63796, 32769,
+ 63797, 34310, 63798, 34396, 63799, 36335, 63800, 38706, 63801, 39791, 63802, 40442,
+ 63803, 30860, 63804, 31103, 63805, 32160, 63806, 33737, 63807, 37636, 63808, 40575,
+ 63809, 35542, 63810, 22751, 63811, 24324, 63812, 31840, 63813, 32894, 63814, 29282,
+ 63815, 30922, 63816, 36034, 63817, 38647, 63818, 22744, 63819, 23650, 63820, 27155,
+ 63821, 28122, 63822, 28431, 63823, 32047, 63824, 32311, 63825, 38475, 63826, 21202,
+ 63827, 32907, 63828, 20956, 63829, 20940, 63830, 31260, 63831, 32190, 63832, 33777,
+ 63833, 38517, 63834, 35712, 63835, 25295, 63836, 27138, 63837, 35582, 63838, 20025,
+ 63839, 23527, 63840, 24594, 63841, 29575, 63842, 30064, 63843, 21271, 63844, 30971,
+ 63845, 20415, 63846, 24489, 63847, 19981, 63848, 27852, 63849, 25976, 63850, 32034,
+ 63851, 21443, 63852, 22622, 63853, 30465, 63854, 33865, 63855, 35498, 63856, 27578,
+ 63857, 36784, 63858, 27784, 63859, 25342, 63860, 33509, 63861, 25504, 63862, 30053,
+ 63863, 20142, 63864, 20841, 63865, 20937, 63866, 26753, 63867, 31975, 63868, 33391,
+ 63869, 35538, 63870, 37327, 63871, 21237, 63872, 21570, 63873, 22899, 63874, 24300,
+ 63875, 26053, 63876, 28670, 63877, 31018, 63878, 38317, 63879, 39530, 63880, 40599,
+ 63881, 40654, 63882, 21147, 63883, 26310, 63884, 27511, 63885, 36706, 63886, 24180,
+ 63887, 24976, 63888, 25088, 63889, 25754, 63890, 28451, 63891, 29001, 63892, 29833,
+ 63893, 31178, 63894, 32244, 63895, 32879, 63896, 36646, 63897, 34030, 63898, 36899,
+ 63899, 37706, 63900, 21015, 63901, 21155, 63902, 21693, 63903, 28872, 63904, 35010,
+ 63905, 35498, 63906, 24265, 63907, 24565, 63908, 25467, 63909, 27566, 63910, 31806,
+ 63911, 29557, 63912, 20196, 63913, 22265, 63914, 23527, 63915, 23994, 63916, 24604,
+ 63917, 29618, 63918, 29801, 63919, 32666, 63920, 32838, 63921, 37428, 63922, 38646,
+ 63923, 38728, 63924, 38936, 63925, 20363, 63926, 31150, 63927, 37300, 63928, 38584,
+ 63929, 24801, 63930, 20102, 63931, 20698, 63932, 23534, 63933, 23615, 63934, 26009,
+ 63935, 27138, 63936, 29134, 63937, 30274, 63938, 34044, 63939, 36988, 63940, 40845,
+ 63941, 26248, 63942, 38446, 63943, 21129, 63944, 26491, 63945, 26611, 63946, 27969,
+ 63947, 28316, 63948, 29705, 63949, 30041, 63950, 30827, 63951, 32016, 63952, 39006,
+ 63953, 20845, 63954, 25134, 63955, 38520, 63956, 20523, 63957, 23833, 63958, 28138,
+ 63959, 36650, 63960, 24459, 63961, 24900, 63962, 26647, 63963, 29575, 63964, 38534,
+ 63965, 21033, 63966, 21519, 63967, 23653, 63968, 26131, 63969, 26446, 63970, 26792,
+ 63971, 27877, 63972, 29702, 63973, 30178, 63974, 32633, 63975, 35023, 63976, 35041,
+ 63977, 37324, 63978, 38626, 63979, 21311, 63980, 28346, 63981, 21533, 63982, 29136,
+ 63983, 29848, 63984, 34298, 63985, 38563, 63986, 40023, 63987, 40607, 63988, 26519,
+ 63989, 28107, 63990, 33256, 63991, 31435, 63992, 31520, 63993, 31890, 63994, 29376,
+ 63995, 28825, 63996, 35672, 63997, 20160, 63998, 33590, 63999, 21050, 64000, 20999,
+ 64001, 24230, 64002, 25299, 64003, 31958, 64004, 23429, 64005, 27934, 64006, 26292,
+ 64007, 36667, 64008, 34892, 64009, 38477, 64010, 35211, 64011, 24275, 64012, 20800,
+ 64013, 21952, 64014, -64015, 64016, 22618, 64017, 64017, 64018, 26228, 64019, -64020,
+ 64021, 20958, 64022, 29482, 64023, 30410, 64024, 31036, 64025, 31070, 64026, 31077,
+ 64027, 31119, 64028, 38742, 64029, 31934, 64030, 32701, 64031, 64031, 64032, 34322,
+ 64033, 64033, 64034, 35576, 64035, -64036, 64037, 36920, 64038, 37117, 64039, -64040,
+ 64042, 39151, 64043, 39164, 64044, 39208, 64045, 40372, 64046, 37086, 64047, 38583,
+ 64048, 20398, 64049, 20711, 64050, 20813, 64051, 21193, 64052, 21220, 64053, 21329,
+ 64054, 21917, 64055, 22022, 64056, 22120, 64057, 22592, 64058, 22696, 64059, 23652,
+ 64060, 23662, 64061, 24724, 64062, 24936, 64063, 24974, 64064, 25074, 64065, 25935,
+ 64066, 26082, 64067, 26257, 64068, 26757, 64069, 28023, 64070, 28186, 64071, 28450,
+ 64072, 29038, 64073, 29227, 64074, 29730, 64075, 30865, 64076, 31038, 64077, 31049,
+ 64078, 31048, 64079, 31056, 64080, 31062, 64081, 31069, 64082, -31118, 64084, 31296,
+ 64085, 31361, 64086, 31680, 64087, 32244, 64088, 32265, 64089, 32321, 64090, 32626,
+ 64091, 32773, 64092, 33261, 64093, 33401, 64095, 33879, 64096, 35088, 64097, 35222,
+ 64098, 35585, 64099, 35641, 64100, 36051, 64101, 36104, 64102, 36790, 64103, 36920,
+ 64104, 38627, 64105, 38911, 64106, 38971, 64107, 24693, 64108, 148206, 64109, 33304,
+ 64110, -64111, 64112, 20006, 64113, 20917, 64114, 20840, 64115, 20352, 64116, 20805,
+ 64117, 20864, 64118, 21191, 64119, 21242, 64120, 21917, 64121, 21845, 64122, 21913,
+ 64123, 21986, 64124, 22618, 64125, 22707, 64126, 22852, 64127, 22868, 64128, 23138,
+ 64129, 23336, 64130, 24274, 64131, 24281, 64132, 24425, 64133, 24493, 64134, 24792,
+ 64135, 24910, 64136, 24840, 64137, 24974, 64138, 24928, 64139, 25074, 64140, 25140,
+ 64141, 25540, 64142, 25628, 64143, 25682, 64144, 25942, 64145, 26228, 64146, 26391,
+ 64147, 26395, 64148, 26454, 64149, 27513, 64150, 27578, 64151, 27969, 64152, 28379,
+ 64153, 28363, 64154, 28450, 64155, 28702, 64156, 29038, 64157, 30631, 64158, 29237,
+ 64159, 29359, 64160, 29482, 64161, 29809, 64162, 29958, 64163, 30011, 64164, 30237,
+ 64165, 30239, 64166, 30410, 64167, 30427, 64168, 30452, 64169, 30538, 64170, 30528,
+ 64171, 30924, 64172, 31409, 64173, 31680, 64174, 31867, 64175, 32091, 64176, 32244,
+ 64177, 32574, 64178, 32773, 64179, 33618, 64180, 33775, 64181, 34681, 64182, 35137,
+ 64183, 35206, 64184, 35222, 64185, 35519, 64186, 35576, 64187, 35531, 64188, 35585,
+ 64189, 35582, 64190, 35565, 64191, 35641, 64192, 35722, 64193, 36104, 64194, 36664,
+ 64195, 36978, 64196, 37273, 64197, 37494, 64198, 38524, 64199, 38627, 64200, 38742,
+ 64201, 38875, 64202, 38911, 64203, 38923, 64204, 38971, 64205, 39698, 64206, 40860,
+ 64207, 141386, 64208, 141380, 64209, 144341, 64210, 15261, 64211, 16408, 64212, 16441,
+ 64213, 152137, 64214, 154832, 64215, 163539, 64216, 40771, 64217, 40846, 64218, -64219,
+ 64285, 1497, 64286, 0, 64287, 1522, 64288, 1506, 64289, 1488, 64290, -1492,
+ 64292, -1500, 64295, 1512, 64296, 1514, 64297, 43, 64298, 1513, 64302, 1488,
+ 64305, -1490, 64311, 64311, 64312, -1497, 64317, 64317, 64318, 1502, 64319, 64319,
+ 64320, -1505, 64322, 64322, 64323, -1508, 64325, 64325, 64326, -1511, 64331, 1493,
+ 64332, 1489, 64333, 1499, 64334, 1508, 64335, 64335, 64336, 1649, 64338, 1659,
+ 64342, 1662, 64346, 1664, 64350, 1658, 64354, 1663, 64358, 1657, 64362, 1700,
+ 64366, 1702, 64370, 1668, 64374, 1667, 64378, 1670, 64382, 1671, 64386, 1677,
+ 64388, 1676, 64390, 1678, 64392, 1672, 64394, 1688, 64396, 1681, 64398, 1705,
+ 64402, 1711, 64406, 1715, 64410, 1713, 64414, 1722, 64416, 1723, 64420, 1749,
+ 64422, 1729, 64426, 1726, 64430, 1746, 64434, -64435, 64467, 1709, 64471, 1735,
+ 64473, 1734, 64475, 1736, 64477, 1655, 64478, 1739, 64480, 1733, 64482, 1737,
+ 64484, 1744, 64488, 1609, 64490, -64491, 64508, 1740, 64512, -64513, 64603, -1585,
+ 64605, 1609, 64606, 32, 64612, -64613, 64656, 1609, 64657, -64658, 64729, 1607,
+ 64730, -64731, 64754, 1600, 64757, -64758, 64828, 1575, 64830, -64831, 65024, 0,
+ 65040, 44, 65041, -12290, 65043, -59, 65045, 33, 65046, 63, 65047, -12311,
+ 65049, 8230, 65050, -65051, 65056, 0, 65072, 8229, 65073, 8212, 65074, 8211,
+ 65075, 95, 65077, -41, 65079, 123, 65080, 125, 65081, -12309, 65083, -12305,
+ 65085, -12299, 65087, -12297, 65089, -12301, 65093, -65094, 65095, 91, 65096, 93,
+ 65097, 32, 65101, 95, 65104, 44, 65105, 12289, 65106, 46, 65107, 65107,
+ 65108, 59, 65109, 58, 65110, 63, 65111, 33, 65112, 8212, 65113, -41,
+ 65115, 123, 65116, 125, 65117, -12309, 65119, 35, 65120, 38, 65121, -43,
+ 65123, 45, 65124, 60, 65125, 62, 65126, 61, 65127, 65127, 65128, 92,
+ 65129, -37, 65131, 64, 65132, -65133, 65136, 32, 65137, 1600, 65138, 32,
+ 65139, 65139, 65140, 32, 65141, 65141, 65142, 32, 65143, 1600, 65144, 32,
+ 65145, 1600, 65146, 32, 65147, 1600, 65148, 32, 65149, 1600, 65150, 32,
+ 65151, 1600, 65152, 1569, 65153, 1575, 65157, 1608, 65159, 1575, 65161, 1610,
+ 65165, 1575, 65167, 1576, 65171, 1577, 65173, 1578, 65177, 1579, 65181, 1580,
+ 65185, 1581, 65189, 1582, 65193, 1583, 65195, 1584, 65197, 1585, 65199, 1586,
+ 65201, 1587, 65205, 1588, 65209, 1589, 65213, 1590, 65217, 1591, 65221, 1592,
+ 65225, 1593, 65229, 1594, 65233, 1601, 65237, 1602, 65241, 1603, 65245, 1604,
+ 65249, 1605, 65253, 1606, 65257, 1607, 65261, 1608, 65263, 1609, 65265, 1610,
+ 65269, -65270, 65279, 0, 65280, 65280, 65281, -34, 65375, -10630, 65377, 12290,
+ 65378, -12301, 65380, 12289, 65381, 12539, 65382, 12530, 65383, 12449, 65384, 12451,
+ 65385, 12453, 65386, 12455, 65387, 12457, 65388, 12515, 65389, 12517, 65390, 12519,
+ 65391, 12483, 65392, 65392, 65393, 12450, 65394, 12452, 65395, 12454, 65396, 12456,
+ 65397, -12459, 65399, 12461, 65400, 12463, 65401, 12465, 65402, 12467, 65403, 12469,
+ 65404, 12471, 65405, 12473, 65406, 12475, 65407, 12477, 65408, 12479, 65409, 12481,
+ 65410, 12484, 65411, 12486, 65412, 12488, 65413, -12491, 65419, 12498, 65420, 12501,
+ 65421, 12504, 65422, 12507, 65423, -12511, 65428, 12516, 65429, 12518, 65430, -12521,
+ 65436, 12527, 65437, 12531, 65438, -65439, 65440, 4448, 65441, -4353, 65443, 4522,
+ 65444, 4354, 65445, -4525, 65447, -4356, 65450, -4529, 65456, 4378, 65457, -4359,
+ 65460, 4385, 65461, -4362, 65471, -65472, 65474, -4450, 65480, -65481, 65482, -4456,
+ 65488, -65489, 65490, -4462, 65496, -65497, 65498, -4468, 65501, -65502, 65504, -163,
+ 65506, 172, 65507, 32, 65508, 166, 65509, 165, 65510, 8361, 65511, 65511,
+ 65512, 9474, 65513, -8593, 65517, 9632, 65518, 9675, 65519, -65520, 65529, 0,
+ 65532, -65533, 66045, 0, 66046, -66047, 66272, 0, 66273, -66274, 66422, 0,
+ 66427, -66428, 67457, -721, 67459, 230, 67460, 665, 67461, 595, 67462, 67462,
+ 67463, 675, 67464, 43878, 67465, 677, 67466, 676, 67467, -599, 67469, 7569,
+ 67470, 600, 67471, 606, 67472, 681, 67473, 612, 67474, 610, 67475, 608,
+ 67476, 667, 67477, 295, 67478, 668, 67479, 615, 67480, 644, 67481, -683,
+ 67483, 620, 67484, 122628, 67485, 42894, 67486, 622, 67487, 122629, 67488, 654,
+ 67489, 122630, 67490, 248, 67491, -631, 67493, 113, 67494, 634, 67495, 122632,
+ 67496, -638, 67498, 640, 67499, 680, 67500, 678, 67501, 43879, 67502, 679,
+ 67503, 648, 67504, 11377, 67505, 67505, 67506, 655, 67507, -674, 67509, 664,
+ 67510, -449, 67513, 122634, 67514, 122654, 67515, -67516, 68097, 0, 68100, 68100,
+ 68101, 0, 68103, -68104, 68108, 0, 68112, -68113, 68152, 0, 68155, -68156,
+ 68159, 0, 68160, -68161, 68325, 0, 68327, -68328, 68900, 0, 68904, -68905,
+ 69291, 0, 69293, -69294, 69373, 0, 69376, -69377, 69446, 0, 69457, -69458,
+ 69506, 0, 69510, -69511, 69632, 0, 69635, -69636, 69688, 0, 69703, -69704,
+ 69744, 0, 69745, -69746, 69747, 0, 69749, -69750, 69759, 0, 69763, -69764,
+ 69786, 2097154, 69790, -69791, 69803, 69797, 69804, -69805, 69808, 0, 69819, -69820,
+ 69821, 0, 69822, -69823, 69826, 0, 69827, -69828, 69837, 0, 69838, -69839,
+ 69888, 0, 69891, -69892, 69927, 0, 69941, -69942, 69957, 0, 69959, -69960,
+ 70003, 0, 70004, -70005, 70016, 0, 70019, -70020, 70067, 0, 70081, -70082,
+ 70089, 0, 70093, 70093, 70094, 0, 70096, -70097, 70188, 0, 70200, -70201,
+ 70206, 0, 70207, -70208, 70209, 0, 70210, -70211, 70367, 0, 70379, -70380,
+ 70400, 0, 70404, -70405, 70459, 0, 70461, 70461, 70462, 0, 70469, -70470,
+ 70471, 0, 70473, -70474, 70475, 0, 70478, -70479, 70487, 0, 70488, -70489,
+ 70498, 0, 70500, -70501, 70502, 0, 70509, -70510, 70512, 0, 70517, -70518,
+ 70709, 0, 70727, -70728, 70750, 0, 70751, -70752, 70832, 0, 70852, -70853,
+ 71087, 0, 71094, -71095, 71096, 0, 71105, -71106, 71132, 0, 71134, -71135,
+ 71216, 0, 71233, -71234, 71339, 0, 71352, -71353, 71453, 0, 71468, -71469,
+ 71724, 0, 71739, -71740, 71984, 0, 71990, 71990, 71991, 0, 71993, -71994,
+ 71995, 0, 71999, 71999, 72000, 0, 72001, 72001, 72002, 0, 72004, -72005,
+ 72145, 0, 72152, -72153, 72154, 0, 72161, -72162, 72164, 0, 72165, -72166,
+ 72193, 0, 72203, -72204, 72243, 0, 72250, 72250, 72251, 0, 72255, -72256,
+ 72263, 0, 72264, -72265, 72273, 0, 72284, -72285, 72330, 0, 72346, -72347,
+ 72751, 0, 72759, 72759, 72760, 0, 72768, -72769, 72850, 0, 72872, 72872,
+ 72873, 0, 72887, -72888, 73009, 0, 73015, -73016, 73018, 0, 73019, 73019,
+ 73020, 0, 73022, 73022, 73023, 0, 73030, 73030, 73031, 0, 73032, -73033,
+ 73098, 0, 73103, 73103, 73104, 0, 73106, 73106, 73107, 0, 73112, -73113,
+ 73459, 0, 73463, -73464, 73472, 0, 73474, 73474, 73475, 0, 73476, -73477,
+ 73524, 0, 73531, -73532, 73534, 0, 73539, -73540, 78896, 0, 78913, -78914,
+ 78919, 0, 78934, -78935, 92912, 0, 92917, -92918, 92976, 0, 92983, -92984,
+ 94031, 0, 94032, 94032, 94033, 0, 94088, -94089, 94095, 0, 94099, -94100,
+ 94180, 0, 94181, -94182, 94192, 0, 94194, -94195, 113821, 0, 113823, 113823,
+ 113824, 0, 113828, -113829, 118528, 0, 118574, -118575, 118576, 0, 118599, -118600,
+ 119134, -119128, 119136, 119128, 119141, 0, 119146, -119147, 119149, 0, 119171, -119172,
+ 119173, 0, 119180, -119181, 119210, 0, 119214, -119215, 119227, -119226, 119229, -119226,
+ 119231, -119226, 119233, -119234, 119362, 0, 119365, -119366, 119808, -66, 119834, -98,
+ 119860, -66, 119886, -98, 119893, 119893, 119894, -106, 119912, -66, 119938, -98,
+ 119964, 65, 119965, 119965, 119966, -68, 119968, -119969, 119970, 71, 119971, -119972,
+ 119973, -75, 119975, -119976, 119977, -79, 119981, 119981, 119982, -84, 119990, -98,
+ 119994, 119994, 119995, 102, 119996, 119996, 119997, -105, 120004, 120004, 120005, -113,
+ 120016, -66, 120042, -98, 120068, -66, 120070, 120070, 120071, -69, 120075, -120076,
+ 120077, -75, 120085, 120085, 120086, -84, 120093, 120093, 120094, -98, 120120, -66,
+ 120122, 120122, 120123, -69, 120127, 120127, 120128, -74, 120133, 120133, 120134, 79,
+ 120135, -120136, 120138, -84, 120145, 120145, 120146, -98, 120172, -66, 120198, -98,
+ 120224, -66, 120250, -98, 120276, -66, 120302, -98, 120328, -66, 120354, -98,
+ 120380, -66, 120406, -98, 120432, -66, 120458, -98, 120484, 305, 120485, 567,
+ 120486, -120487, 120488, -914, 120505, 920, 120506, -932, 120513, 8711, 120514, -946,
+ 120539, 8706, 120540, 949, 120541, 952, 120542, 954, 120543, 966, 120544, 961,
+ 120545, 960, 120546, -914, 120563, 920, 120564, -932, 120571, 8711, 120572, -946,
+ 120597, 8706, 120598, 949, 120599, 952, 120600, 954, 120601, 966, 120602, 961,
+ 120603, 960, 120604, -914, 120621, 920, 120622, -932, 120629, 8711, 120630, -946,
+ 120655, 8706, 120656, 949, 120657, 952, 120658, 954, 120659, 966, 120660, 961,
+ 120661, 960, 120662, -914, 120679, 920, 120680, -932, 120687, 8711, 120688, -946,
+ 120713, 8706, 120714, 949, 120715, 952, 120716, 954, 120717, 966, 120718, 961,
+ 120719, 960, 120720, -914, 120737, 920, 120738, -932, 120745, 8711, 120746, -946,
+ 120771, 8706, 120772, 949, 120773, 952, 120774, 954, 120775, 966, 120776, 961,
+ 120777, 960, 120778, -989, 120780, -120781, 120782, -49, 120792, -49, 120802, -49,
+ 120812, -49, 120822, -49, 120832, -120833, 121344, 0, 121399, -121400, 121403, 0,
+ 121453, -121454, 121461, 0, 121462, -121463, 121476, 0, 121477, -121478, 121499, 0,
+ 121504, 121504, 121505, 0, 121520, -121521, 122880, 0, 122887, 122887, 122888, 0,
+ 122905, -122906, 122907, 0, 122914, 122914, 122915, 0, 122917, 122917, 122918, 0,
+ 122923, -122924, 122928, -1073, 122937, -1083, 122940, -1087, 122951, 1099, 122952, -1102,
+ 122954, 42633, 122955, 1241, 122956, 1110, 122957, 1112, 122958, 1257, 122959, 1199,
+ 122960, 1231, 122961, -1073, 122970, -1083, 122972, -1087, 122974, 1089, 122975, -1092,
+ 122981, -1099, 122983, 1169, 122984, 1110, 122985, 1109, 122986, 1119, 122987, 1195,
+ 122988, 42577, 122989, 1201, 122990, -122991, 123023, 0, 123024, -123025, 123184, 0,
+ 123191, -123192, 123566, 0, 123567, -123568, 123628, 0, 123632, -123633, 124140, 0,
+ 124144, -124145, 125136, 0, 125143, -125144, 125252, 0, 125259, -125260, 126464, -1576,
+ 126466, 1580, 126467, 1583, 126468, 126468, 126469, 1608, 126470, 1586, 126471, 1581,
+ 126472, 1591, 126473, 1610, 126474, -1604, 126478, 1587, 126479, 1593, 126480, 1601,
+ 126481, 1589, 126482, 1602, 126483, 1585, 126484, 1588, 126485, -1579, 126487, 1582,
+ 126488, 1584, 126489, 1590, 126490, 1592, 126491, 1594, 126492, 1646, 126493, 1722,
+ 126494, 1697, 126495, 1647, 126496, 126496, 126497, 1576, 126498, 1580, 126499, 126499,
+ 126500, 1607, 126501, -126502, 126503, 1581, 126504, 126504, 126505, 1610, 126506, -1604,
+ 126510, 1587, 126511, 1593, 126512, 1601, 126513, 1589, 126514, 1602, 126515, 126515,
+ 126516, 1588, 126517, -1579, 126519, 1582, 126520, 126520, 126521, 1590, 126522, 126522,
+ 126523, 1594, 126524, -126525, 126530, 1580, 126531, -126532, 126535, 1581, 126536, 126536,
+ 126537, 1610, 126538, 126538, 126539, 1604, 126540, 126540, 126541, 1606, 126542, 1587,
+ 126543, 1593, 126544, 126544, 126545, 1589, 126546, 1602, 126547, 126547, 126548, 1588,
+ 126549, -126550, 126551, 1582, 126552, 126552, 126553, 1590, 126554, 126554, 126555, 1594,
+ 126556, 126556, 126557, 1722, 126558, 126558, 126559, 1647, 126560, 126560, 126561, 1576,
+ 126562, 1580, 126563, 126563, 126564, 1607, 126565, -126566, 126567, 1581, 126568, 1591,
+ 126569, 1610, 126570, 1603, 126571, 126571, 126572, -1606, 126574, 1587, 126575, 1593,
+ 126576, 1601, 126577, 1589, 126578, 1602, 126579, 126579, 126580, 1588, 126581, -1579,
+ 126583, 1582, 126584, 126584, 126585, 1590, 126586, 1592, 126587, 1594, 126588, 1646,
+ 126589, 126589, 126590, 1697, 126591, 126591, 126592, -1576, 126594, 1580, 126595, 1583,
+ 126596, -1608, 126598, 1586, 126599, 1581, 126600, 1591, 126601, 1610, 126602, 126602,
+ 126603, -1605, 126606, 1587, 126607, 1593, 126608, 1601, 126609, 1589, 126610, 1602,
+ 126611, 1585, 126612, 1588, 126613, -1579, 126615, 1582, 126616, 1584, 126617, 1590,
+ 126618, 1592, 126619, 1594, 126620, -126621, 126625, 1576, 126626, 1580, 126627, 1583,
+ 126628, 126628, 126629, 1608, 126630, 1586, 126631, 1581, 126632, 1591, 126633, 1610,
+ 126634, 126634, 126635, -1605, 126638, 1587, 126639, 1593, 126640, 1601, 126641, 1589,
+ 126642, 1602, 126643, 1585, 126644, 1588, 126645, -1579, 126647, 1582, 126648, 1584,
+ 126649, 1590, 126650, 1592, 126651, 1594, 126652, -126653, 127232, 48, 127234, -50,
+ 127243, -127244, 127275, 67, 127276, 82, 127277, -127278, 127280, -66, 127306, -127307,
+ 127490, 12469, 127491, -127492, 127504, 25163, 127505, 23383, 127506, 21452, 127507, 12486,
+ 127508, 20108, 127509, 22810, 127510, 35299, 127511, 22825, 127512, 20132, 127513, 26144,
+ 127514, 28961, 127515, 26009, 127516, 21069, 127517, 24460, 127518, 20877, 127519, 26032,
+ 127520, 21021, 127521, 32066, 127522, 29983, 127523, 36009, 127524, 22768, 127525, 21561,
+ 127526, 28436, 127527, 25237, 127528, 25429, 127529, 19968, 127530, 19977, 127531, 36938,
+ 127532, 24038, 127533, 20013, 127534, 21491, 127535, 25351, 127536, 36208, 127537, 25171,
+ 127538, 31105, 127539, 31354, 127540, 21512, 127541, 28288, 127542, 26377, 127543, 26376,
+ 127544, 30003, 127545, 21106, 127546, 21942, 127547, 37197, 127548, -127549, 127568, 24471,
+ 127569, 21487, 127570, -127571, 130032, -49, 130042, -130043, 194560, 20029, 194561, 20024,
+ 194562, 20033, 194563, 131362, 194564, 20320, 194565, 20398, 194566, 20411, 194567, 20482,
+ 194568, 20602, 194569, 20633, 194570, 20711, 194571, 20687, 194572, 13470, 194573, 132666,
+ 194574, 20813, 194575, 20820, 194576, 20836, 194577, 20855, 194578, 132380, 194579, 13497,
+ 194580, 20839, 194581, 20877, 194582, 132427, 194583, 20887, 194584, 20900, 194585, 20172,
+ 194586, 20908, 194587, 20917, 194588, 168415, 194589, 20981, 194590, 20995, 194591, 13535,
+ 194592, 21051, 194593, 21062, 194594, 21106, 194595, 21111, 194596, 13589, 194597, 21191,
+ 194598, 21193, 194599, 21220, 194600, 21242, 194601, -21254, 194603, 21271, 194604, 21321,
+ 194605, 21329, 194606, 21338, 194607, 21363, 194608, 21373, 194609, 21375, 194612, 133676,
+ 194613, 28784, 194614, 21450, 194615, 21471, 194616, 133987, 194617, 21483, 194618, 21489,
+ 194619, 21510, 194620, 21662, 194621, 21560, 194622, 21576, 194623, 21608, 194624, 21666,
+ 194625, 21750, 194626, 21776, 194627, 21843, 194628, 21859, 194629, 21892, 194631, 21913,
+ 194632, 21931, 194633, 21939, 194634, 21954, 194635, 22294, 194636, 22022, 194637, 22295,
+ 194638, 22097, 194639, 22132, 194640, 20999, 194641, 22766, 194642, 22478, 194643, 22516,
+ 194644, 22541, 194645, 22411, 194646, 22578, 194647, 22577, 194648, 22700, 194649, 136420,
+ 194650, 22770, 194651, 22775, 194652, 22790, 194653, 22810, 194654, 22818, 194655, 22882,
+ 194656, 136872, 194657, 136938, 194658, 23020, 194659, 23067, 194660, 23079, 194661, 23000,
+ 194662, 23142, 194663, 14062, 194664, 14076, 194665, 23304, 194666, 23358, 194668, 137672,
+ 194669, 23491, 194670, 23512, 194671, 23527, 194672, 23539, 194673, 138008, 194674, 23551,
+ 194675, 23558, 194676, 24403, 194677, 23586, 194678, 14209, 194679, 23648, 194680, 23662,
+ 194681, 23744, 194682, 23693, 194683, 138724, 194684, 23875, 194685, 138726, 194686, 23918,
+ 194687, 23915, 194688, 23932, 194689, -24034, 194691, 14383, 194692, 24061, 194693, 24104,
+ 194694, 24125, 194695, 24169, 194696, 14434, 194697, 139651, 194698, 14460, 194699, 24240,
+ 194700, 24243, 194701, 24246, 194702, 24266, 194703, 172946, 194704, 24318, 194705, 140081,
+ 194707, 33281, 194708, 24354, 194710, 14535, 194711, 144056, 194712, 156122, 194713, 24418,
+ 194714, 24427, 194715, 14563, 194716, 24474, 194717, 24525, 194718, 24535, 194719, 24569,
+ 194720, 24705, 194721, 14650, 194722, 14620, 194723, 24724, 194724, 141012, 194725, 24775,
+ 194726, 24904, 194727, 24908, 194728, 24910, 194729, 24908, 194730, 24954, 194731, 24974,
+ 194732, 25010, 194733, 24996, 194734, 25007, 194735, 25054, 194736, 25074, 194737, 25078,
+ 194738, 25104, 194739, 25115, 194740, 25181, 194741, 25265, 194742, 25300, 194743, 25424,
+ 194744, 142092, 194745, 25405, 194746, 25340, 194747, 25448, 194748, 25475, 194749, 25572,
+ 194750, 142321, 194751, 25634, 194752, 25541, 194753, 25513, 194754, 14894, 194755, 25705,
+ 194756, 25726, 194757, 25757, 194758, 25719, 194759, 14956, 194760, 25935, 194761, 25964,
+ 194762, 143370, 194763, 26083, 194764, 26360, 194765, 26185, 194766, 15129, 194767, 26257,
+ 194768, 15112, 194769, 15076, 194770, 20882, 194771, 20885, 194772, 26368, 194773, 26268,
+ 194774, 32941, 194775, 17369, 194776, 26391, 194777, 26395, 194778, 26401, 194779, 26462,
+ 194780, 26451, 194781, 144323, 194782, 15177, 194783, 26618, 194784, 26501, 194785, 26706,
+ 194786, 26757, 194787, 144493, 194788, 26766, 194789, 26655, 194790, 26900, 194791, 15261,
+ 194792, 26946, 194793, 27043, 194794, 27114, 194795, 27304, 194796, 145059, 194797, 27355,
+ 194798, 15384, 194799, 27425, 194800, 145575, 194801, 27476, 194802, 15438, 194803, 27506,
+ 194804, 27551, 194805, -27579, 194807, 146061, 194808, 138507, 194809, 146170, 194810, 27726,
+ 194811, 146620, 194812, 27839, 194813, 27853, 194814, 27751, 194815, 27926, 194816, 27966,
+ 194817, 28023, 194818, 27969, 194819, 28009, 194820, 28024, 194821, 28037, 194822, 146718,
+ 194823, 27956, 194824, 28207, 194825, 28270, 194826, 15667, 194827, 28363, 194828, 28359,
+ 194829, 147153, 194830, 28153, 194831, 28526, 194832, 147294, 194833, 147342, 194834, 28614,
+ 194835, 28729, 194836, 28702, 194837, 28699, 194838, 15766, 194839, 28746, 194840, 28797,
+ 194841, 28791, 194842, 28845, 194843, 132389, 194844, 28997, 194845, 148067, 194846, 29084,
+ 194847, 148395, 194848, 29224, 194849, 29237, 194850, 29264, 194851, 149000, 194852, 29312,
+ 194853, 29333, 194854, 149301, 194855, 149524, 194856, 29562, 194857, 29579, 194858, 16044,
+ 194859, 29605, 194860, 16056, 194862, 29767, 194863, 29788, 194864, 29809, 194865, 29829,
+ 194866, 29898, 194867, 16155, 194868, 29988, 194869, 150582, 194870, 30014, 194871, 150674,
+ 194872, 30064, 194873, 139679, 194874, 30224, 194875, 151457, 194876, 151480, 194877, 151620,
+ 194878, 16380, 194879, 16392, 194880, 30452, 194881, 151795, 194882, 151794, 194883, 151833,
+ 194884, 151859, 194885, -30495, 194887, 30495, 194888, 30538, 194889, 16441, 194890, 30603,
+ 194891, 16454, 194892, 16534, 194893, 152605, 194894, 30798, 194895, 30860, 194896, 30924,
+ 194897, 16611, 194898, 153126, 194899, 31062, 194900, 153242, 194901, 153285, 194902, 31119,
+ 194903, 31211, 194904, 16687, 194905, 31296, 194906, 31306, 194907, 31311, 194908, 153980,
+ 194909, 154279, 194911, 31470, 194912, 16898, 194913, 154539, 194914, 31686, 194915, 31689,
+ 194916, 16935, 194917, 154752, 194918, 31954, 194919, 17056, 194920, 31976, 194921, 31971,
+ 194922, 32000, 194923, 155526, 194924, 32099, 194925, 17153, 194926, 32199, 194927, 32258,
+ 194928, 32325, 194929, 17204, 194930, 156200, 194931, 156231, 194932, 17241, 194933, 156377,
+ 194934, 32634, 194935, 156478, 194936, 32661, 194937, 32762, 194938, 32773, 194939, 156890,
+ 194940, 156963, 194941, 32864, 194942, 157096, 194943, 32880, 194944, 144223, 194945, 17365,
+ 194946, 32946, 194947, 33027, 194948, 17419, 194949, 33086, 194950, 23221, 194951, 157607,
+ 194952, 157621, 194953, 144275, 194954, 144284, 194955, 33281, 194956, 33284, 194957, 36766,
+ 194958, 17515, 194959, 33425, 194960, 33419, 194961, 33437, 194962, 21171, 194963, 33457,
+ 194964, 33459, 194965, 33469, 194966, 33510, 194967, 158524, 194968, 33509, 194969, 33565,
+ 194970, 33635, 194971, 33709, 194972, 33571, 194973, 33725, 194974, 33767, 194975, 33879,
+ 194976, 33619, 194977, 33738, 194978, 33740, 194979, 33756, 194980, 158774, 194981, 159083,
+ 194982, 158933, 194983, 17707, 194984, 34033, 194985, 34035, 194986, 34070, 194987, 160714,
+ 194988, 34148, 194989, 159532, 194990, 17757, 194991, 17761, 194992, 159665, 194993, 159954,
+ 194994, 17771, 194995, 34384, 194996, 34396, 194997, 34407, 194998, 34409, 194999, 34473,
+ 195000, 34440, 195001, 34574, 195002, 34530, 195003, 34681, 195004, 34600, 195005, 34667,
+ 195006, 34694, 195007, 17879, 195008, 34785, 195009, 34817, 195010, 17913, 195011, 34912,
+ 195012, 34915, 195013, 161383, 195014, 35031, 195015, 35038, 195016, 17973, 195017, 35066,
+ 195018, 13499, 195019, 161966, 195020, 162150, 195021, 18110, 195022, 18119, 195023, 35488,
+ 195024, 35565, 195025, 35722, 195026, 35925, 195027, 162984, 195028, 36011, 195029, 36033,
+ 195030, 36123, 195031, 36215, 195032, 163631, 195033, 133124, 195034, 36299, 195035, 36284,
+ 195036, 36336, 195037, 133342, 195038, 36564, 195039, 36664, 195040, 165330, 195041, 165357,
+ 195042, 37012, 195043, 37105, 195044, 37137, 195045, 165678, 195046, 37147, 195047, 37432,
+ 195048, -37592, 195050, 37500, 195051, 37881, 195052, 37909, 195053, 166906, 195054, 38283,
+ 195055, 18837, 195056, 38327, 195057, 167287, 195058, 18918, 195059, 38595, 195060, 23986,
+ 195061, 38691, 195062, 168261, 195063, 168474, 195064, 19054, 195065, 19062, 195066, 38880,
+ 195067, 168970, 195068, 19122, 195069, 169110, 195070, 38923, 195072, 38953, 195073, 169398,
+ 195074, 39138, 195075, 19251, 195076, 39209, 195077, 39335, 195078, 39362, 195079, 39422,
+ 195080, 19406, 195081, 170800, 195082, 39698, 195083, 40000, 195084, 40189, 195085, 19662,
+ 195086, 19693, 195087, 40295, 195088, 172238, 195089, 19704, 195090, 172293, 195091, 172558,
+ 195092, 172689, 195093, 40635, 195094, 19798, 195095, 40697, 195096, 40702, 195097, 40709,
+ 195098, 40719, 195099, 40726, 195100, 40763, 195101, 173568, 195102, -195103, 917505, 0,
+ 917506, -917507, 917536, 0, 917632, -917633, 917760, 0, 918000, -918001, 2147483647, 0};
UnicodeSimpleCategory get_unicode_simple_category(uint32 code) {
- auto it = std::upper_bound(std::begin(unicode_simple_category_ranges), std::end(unicode_simple_category_ranges),
- (code << 5) + 30);
+ if (code < 128) {
+ return static_cast<UnicodeSimpleCategory>(unicode_simple_category_table[code]);
+ }
+ auto jump_pos_index = code <= 0x20000 ? code >> 7 : (0x20000 >> 7) - (0x20000 >> 16) + (code >> 16);
+ // CHECK(jump_pos_index < sizeof(unicode_simple_category_ranges) / sizeof(unicode_simple_category_ranges[0]));
+ auto it = unicode_simple_category_ranges + unicode_simple_category_jump_pos[jump_pos_index];
+ code = (code << 5) + 30;
+ // CHECK(unicode_simple_category_ranges[unicode_simple_category_jump_pos[jump_pos_index + 1]] > code);
+ while (*it <= code) {
+ ++it;
+ }
return static_cast<UnicodeSimpleCategory>(*(it - 1) & 31);
}
@@ -524,8 +1283,9 @@ static uint32 binary_search_ranges(const int32 (&ranges)[N], uint32 code) {
return 0;
}
- int32 code_int = static_cast<int32>(code);
- size_t l = 0, r = N;
+ auto code_int = static_cast<int32>(code);
+ size_t l = 0;
+ size_t r = N;
while (l < r) {
size_t m = ((l + r + 2) >> 2) << 1;
if (ranges[m] <= code_int) {
@@ -550,7 +1310,7 @@ static uint32 binary_search_ranges(const int32 (&ranges)[N], uint32 code) {
case 2:
return ((code - 1) | 1);
default:
- UNREACHABLE();
+ LOG(FATAL) << code << " " << l << " " << r << " " << t;
return 0;
}
}
@@ -571,4 +1331,12 @@ uint32 unicode_to_lower(uint32 code) {
}
}
+uint32 remove_diacritics(uint32 code) {
+ if (code < TABLE_SIZE) {
+ return without_diacritics_table[code];
+ } else {
+ return binary_search_ranges(without_diacritics_ranges, code);
+ }
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.h
index 1c75397d6e..9012e4633f 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.h
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -16,7 +16,7 @@ UnicodeSimpleCategory get_unicode_simple_category(uint32 code);
/**
* Prepares unicode character for search, leaving only digits and lowercased letters.
- * Return code of replacing character or 0 if the character should be skipped.
+ * Returns code of replacing character or 0 if the character should be skipped.
*/
uint32 prepare_search_character(uint32 code);
@@ -25,4 +25,9 @@ uint32 prepare_search_character(uint32 code);
*/
uint32 unicode_to_lower(uint32 code);
+/**
+ * Removes diacritics from a unicode character.
+ */
+uint32 remove_diacritics(uint32 code);
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/unique_ptr.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/unique_ptr.h
new file mode 100644
index 0000000000..c8f3b48d1f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/unique_ptr.h
@@ -0,0 +1,106 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+namespace td {
+
+// const-correct and compiler-friendly (g++ RAM and CPU usage 10 times less than for std::unique_ptr)
+// replacement for std::unique_ptr
+template <class T>
+class unique_ptr final {
+ public:
+ using pointer = T *;
+ using element_type = T;
+
+ unique_ptr() noexcept = default;
+ unique_ptr(const unique_ptr &other) = delete;
+ unique_ptr &operator=(const unique_ptr &other) = delete;
+ unique_ptr(unique_ptr &&other) noexcept : ptr_(other.release()) {
+ }
+ unique_ptr &operator=(unique_ptr &&other) noexcept {
+ reset(other.release());
+ return *this;
+ }
+ ~unique_ptr() {
+ reset();
+ }
+
+ unique_ptr(std::nullptr_t) noexcept {
+ }
+ explicit unique_ptr(T *ptr) noexcept : ptr_(ptr) {
+ }
+ template <class S, class = std::enable_if_t<std::is_base_of<T, S>::value>>
+ unique_ptr(unique_ptr<S> &&other) noexcept : ptr_(static_cast<S *>(other.release())) {
+ }
+ template <class S, class = std::enable_if_t<std::is_base_of<T, S>::value>>
+ unique_ptr &operator=(unique_ptr<S> &&other) noexcept {
+ reset(static_cast<T *>(other.release()));
+ return *this;
+ }
+ void reset(T *new_ptr = nullptr) noexcept {
+ static_assert(sizeof(T) > 0, "Can't destroy unique_ptr with incomplete type");
+ delete ptr_;
+ ptr_ = new_ptr;
+ }
+ T *release() noexcept {
+ auto res = ptr_;
+ ptr_ = nullptr;
+ return res;
+ }
+ T *get() noexcept {
+ return ptr_;
+ }
+ const T *get() const noexcept {
+ return ptr_;
+ }
+ T *operator->() noexcept {
+ return ptr_;
+ }
+ const T *operator->() const noexcept {
+ return ptr_;
+ }
+ T &operator*() noexcept {
+ return *ptr_;
+ }
+ const T &operator*() const noexcept {
+ return *ptr_;
+ }
+ explicit operator bool() const noexcept {
+ return ptr_ != nullptr;
+ }
+
+ private:
+ T *ptr_{nullptr};
+};
+
+template <class T>
+bool operator==(std::nullptr_t, const unique_ptr<T> &p) {
+ return !p;
+}
+template <class T>
+bool operator==(const unique_ptr<T> &p, std::nullptr_t) {
+ return !p;
+}
+template <class T>
+bool operator!=(std::nullptr_t, const unique_ptr<T> &p) {
+ return static_cast<bool>(p);
+}
+template <class T>
+bool operator!=(const unique_ptr<T> &p, std::nullptr_t) {
+ return static_cast<bool>(p);
+}
+
+template <class Type, class... Args>
+unique_ptr<Type> make_unique(Args &&...args) {
+ return unique_ptr<Type>(new Type(std::forward<Args>(args)...));
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.cpp
index 50f82d6393..16c31e5b2d 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.cpp
@@ -1,12 +1,13 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/utf8.h"
-#include "td/utils/logging.h" // for UNREACHABLE
+#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/unicode.h"
namespace td {
@@ -15,7 +16,7 @@ bool check_utf8(CSlice str) {
const char *data = str.data();
const char *data_end = data + str.size();
do {
- unsigned int a = static_cast<unsigned char>(*data++);
+ uint32 a = static_cast<unsigned char>(*data++);
if ((a & 0x80) == 0) {
if (data == data_end + 1) {
return true;
@@ -30,25 +31,25 @@ bool check_utf8(CSlice str) {
ENSURE((a & 0x40) != 0);
- unsigned int b = static_cast<unsigned char>(*data++);
+ uint32 b = static_cast<unsigned char>(*data++);
ENSURE((b & 0xc0) == 0x80);
if ((a & 0x20) == 0) {
ENSURE((a & 0x1e) > 0);
continue;
}
- unsigned int c = static_cast<unsigned char>(*data++);
+ uint32 c = static_cast<unsigned char>(*data++);
ENSURE((c & 0xc0) == 0x80);
if ((a & 0x10) == 0) {
- int x = (((a & 0x0f) << 6) | (b & 0x20));
+ uint32 x = (((a & 0x0f) << 6) | (b & 0x20));
ENSURE(x != 0 && x != 0x360); // surrogates
continue;
}
- unsigned int d = static_cast<unsigned char>(*data++);
+ uint32 d = static_cast<unsigned char>(*data++);
ENSURE((d & 0xc0) == 0x80);
if ((a & 0x08) == 0) {
- int t = (((a & 0x07) << 6) | (b & 0x30));
+ uint32 t = (((a & 0x07) << 6) | (b & 0x30));
ENSURE(0 < t && t < 0x110); // end of unicode
continue;
}
@@ -82,30 +83,20 @@ void append_utf8_character(string &str, uint32 ch) {
const unsigned char *next_utf8_unsafe(const unsigned char *ptr, uint32 *code) {
uint32 a = ptr[0];
if ((a & 0x80) == 0) {
- if (code) {
- *code = a;
- }
+ *code = a;
return ptr + 1;
} else if ((a & 0x20) == 0) {
- if (code) {
- *code = ((a & 0x1f) << 6) | (ptr[1] & 0x3f);
- }
+ *code = ((a & 0x1f) << 6) | (ptr[1] & 0x3f);
return ptr + 2;
} else if ((a & 0x10) == 0) {
- if (code) {
- *code = ((a & 0x0f) << 12) | ((ptr[1] & 0x3f) << 6) | (ptr[2] & 0x3f);
- }
+ *code = ((a & 0x0f) << 12) | ((ptr[1] & 0x3f) << 6) | (ptr[2] & 0x3f);
return ptr + 3;
} else if ((a & 0x08) == 0) {
- if (code) {
- *code = ((a & 0x07) << 18) | ((ptr[1] & 0x3f) << 12) | ((ptr[2] & 0x3f) << 6) | (ptr[3] & 0x3f);
- }
+ *code = ((a & 0x07) << 18) | ((ptr[1] & 0x3f) << 12) | ((ptr[2] & 0x3f) << 6) | (ptr[3] & 0x3f);
return ptr + 4;
}
UNREACHABLE();
- if (code) {
- *code = 0;
- }
+ *code = 0;
return ptr;
}
@@ -121,4 +112,84 @@ string utf8_to_lower(Slice str) {
return result;
}
+vector<string> utf8_get_search_words(Slice str) {
+ bool in_word = false;
+ string word;
+ vector<string> words;
+ auto pos = str.ubegin();
+ auto end = str.uend();
+ while (pos != end) {
+ uint32 code;
+ pos = next_utf8_unsafe(pos, &code);
+
+ code = prepare_search_character(code);
+ if (code == 0) {
+ continue;
+ }
+ if (code == ' ') {
+ if (in_word) {
+ words.push_back(std::move(word));
+ word.clear();
+ in_word = false;
+ }
+ } else {
+ in_word = true;
+ code = remove_diacritics(code);
+ append_utf8_character(word, code);
+ }
+ }
+ if (in_word) {
+ words.push_back(std::move(word));
+ }
+ return words;
+}
+
+string utf8_prepare_search_string(Slice str) {
+ return implode(utf8_get_search_words(str));
+}
+
+string utf8_encode(CSlice data) {
+ if (check_utf8(data)) {
+ return data.str();
+ }
+ return PSTRING() << "url_decode(" << url_encode(data) << ')';
+}
+
+size_t utf8_utf16_length(Slice str) {
+ size_t result = 0;
+ for (auto c : str) {
+ result += is_utf8_character_first_code_unit(c) + ((c & 0xf8) == 0xf0);
+ }
+ return result;
+}
+
+Slice utf8_utf16_truncate(Slice str, size_t length) {
+ for (size_t i = 0; i < str.size(); i++) {
+ auto c = static_cast<unsigned char>(str[i]);
+ if (is_utf8_character_first_code_unit(c)) {
+ if (length <= 0) {
+ return str.substr(0, i);
+ } else {
+ length--;
+ if (c >= 0xf0) { // >= 4 bytes in symbol => surrogate pair
+ length--;
+ }
+ }
+ }
+ }
+ return str;
+}
+
+Slice utf8_utf16_substr(Slice str, size_t offset) {
+ if (offset == 0) {
+ return str;
+ }
+ auto offset_pos = utf8_utf16_truncate(str, offset).size();
+ return str.substr(offset_pos);
+}
+
+Slice utf8_utf16_substr(Slice str, size_t offset, size_t length) {
+ return utf8_utf16_truncate(utf8_utf16_substr(str, offset), length);
+}
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.h
index 6be1952c19..27c8b5bd5d 100644
--- a/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.h
+++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.h
@@ -1,12 +1,12 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
-#include "td/utils/int_types.h"
+#include "td/utils/common.h"
#include "td/utils/Slice.h"
namespace td {
@@ -28,6 +28,9 @@ inline size_t utf8_length(Slice str) {
return result;
}
+/// returns length of UTF-8 string in UTF-16 code units
+size_t utf8_utf16_length(Slice str);
+
/// appends a Unicode character using UTF-8 encoding
void append_utf8_character(string &str, uint32 ch);
@@ -60,26 +63,13 @@ T utf8_truncate(T str, size_t length) {
}
/// truncates UTF-8 string to the given length given in UTF-16 code units
-template <class T>
-T utf8_utf16_truncate(T str, size_t length) {
- for (size_t i = 0; i < str.size(); i++) {
- auto c = static_cast<unsigned char>(str[i]);
- if (is_utf8_character_first_code_unit(c)) {
- if (length <= 0) {
- return str.substr(0, i);
- } else {
- length--;
- if (c >= 0xf0) { // >= 4 bytes in symbol => surrogaite pair
- length--;
- }
- }
- }
- }
- return str;
-}
+Slice utf8_utf16_truncate(Slice str, size_t length);
template <class T>
T utf8_substr(T str, size_t offset) {
+ if (offset == 0) {
+ return str;
+ }
auto offset_pos = utf8_truncate(str, offset).size();
return str.substr(offset_pos);
}
@@ -89,18 +79,20 @@ T utf8_substr(T str, size_t offset, size_t length) {
return utf8_truncate(utf8_substr(str, offset), length);
}
-template <class T>
-T utf8_utf16_substr(T str, size_t offset) {
- auto offset_pos = utf8_utf16_truncate(str, offset).size();
- return str.substr(offset_pos);
-}
+Slice utf8_utf16_substr(Slice str, size_t offset);
-template <class T>
-T utf8_utf16_substr(T str, size_t offset, size_t length) {
- return utf8_utf16_truncate(utf8_utf16_substr(str, offset), length);
-}
+Slice utf8_utf16_substr(Slice str, size_t offset, size_t length);
/// Returns UTF-8 string converted to lower case.
string utf8_to_lower(Slice str);
+/// Returns UTF-8 string split by words for search.
+vector<string> utf8_get_search_words(Slice str);
+
+/// Returns UTF-8 string prepared for search, leaving only digits and lowercased letters.
+string utf8_prepare_search_string(Slice str);
+
+/// Returns valid UTF-8 representation of the string.
+string utf8_encode(CSlice data);
+
} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/ChainScheduler.cpp b/protocols/Telegram/tdlib/td/tdutils/test/ChainScheduler.cpp
new file mode 100644
index 0000000000..d3bcb934fc
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/ChainScheduler.cpp
@@ -0,0 +1,244 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/algorithm.h"
+#include "td/utils/ChainScheduler.h"
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/Span.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/tests.h"
+
+#include <memory>
+#include <numeric>
+
+TEST(ChainScheduler, CreateAfterActive) {
+ td::ChainScheduler<int> scheduler;
+ td::vector<td::ChainScheduler<int>::ChainId> chains{1};
+
+ auto first_task_id = scheduler.create_task(chains, 1);
+ ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id);
+ auto second_task_id = scheduler.create_task(chains, 2);
+ ASSERT_EQ(second_task_id, scheduler.start_next_task().unwrap().task_id);
+}
+
+TEST(ChainScheduler, RestartAfterActive) {
+ td::ChainScheduler<int> scheduler;
+ std::vector<td::ChainScheduler<int>::ChainId> chains{1};
+
+ auto first_task_id = scheduler.create_task(chains, 1);
+ auto second_task_id = scheduler.create_task(chains, 2);
+ ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id);
+ ASSERT_EQ(second_task_id, scheduler.start_next_task().unwrap().task_id);
+
+ scheduler.reset_task(first_task_id);
+ ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id);
+
+ scheduler.reset_task(second_task_id);
+ ASSERT_EQ(second_task_id, scheduler.start_next_task().unwrap().task_id);
+}
+
+TEST(ChainScheduler, SendAfterRestart) {
+ td::ChainScheduler<int> scheduler;
+ std::vector<td::ChainScheduler<int>::ChainId> chains{1};
+
+ auto first_task_id = scheduler.create_task(chains, 1);
+ auto second_task_id = scheduler.create_task(chains, 2);
+ ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id);
+ ASSERT_EQ(second_task_id, scheduler.start_next_task().unwrap().task_id);
+
+ scheduler.reset_task(first_task_id);
+
+ scheduler.create_task(chains, 3);
+
+ ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id);
+ ASSERT_TRUE(!scheduler.start_next_task());
+}
+
+TEST(ChainScheduler, Basic) {
+ td::ChainScheduler<int> scheduler;
+ for (int i = 0; i < 100; i++) {
+ scheduler.create_task({td::ChainScheduler<int>::ChainId{1}}, i);
+ }
+ int j = 0;
+ while (j != 100) {
+ td::vector<td::ChainScheduler<int>::TaskId> tasks;
+ while (true) {
+ auto o_task_id = scheduler.start_next_task();
+ if (!o_task_id) {
+ break;
+ }
+ auto task_id = o_task_id.value().task_id;
+ auto extra = *scheduler.get_task_extra(task_id);
+ auto parents =
+ td::transform(o_task_id.value().parents, [&](auto parent) { return *scheduler.get_task_extra(parent); });
+ LOG(INFO) << "Start " << extra << parents;
+ CHECK(extra == j);
+ j++;
+ tasks.push_back(task_id);
+ }
+ for (auto &task_id : tasks) {
+ auto extra = *scheduler.get_task_extra(task_id);
+ LOG(INFO) << "Finish " << extra;
+ scheduler.finish_task(task_id);
+ }
+ }
+}
+
+struct ChainSchedulerQuery;
+using QueryPtr = std::shared_ptr<ChainSchedulerQuery>;
+using ChainId = td::ChainScheduler<QueryPtr>::ChainId;
+using TaskId = td::ChainScheduler<QueryPtr>::TaskId;
+
+struct ChainSchedulerQuery {
+ int id{};
+ TaskId task_id{};
+ bool is_ok{};
+ bool skipped{};
+};
+
+TEST(ChainScheduler, Stress) {
+ td::Random::Xorshift128plus rnd(123);
+ int max_query_id = 100000;
+ int MAX_INFLIGHT_QUERIES = 20;
+ int ChainsN = 4;
+
+ struct QueryWithParents {
+ TaskId task_id;
+ QueryPtr id;
+ td::vector<QueryPtr> parents;
+ };
+ td::vector<QueryWithParents> active_queries;
+
+ td::ChainScheduler<QueryPtr> scheduler;
+ td::vector<td::vector<QueryPtr>> chains(ChainsN);
+ int inflight_queries{};
+ int current_query_id{};
+ int sent_cnt{};
+ bool done = false;
+ std::vector<TaskId> pending_queries;
+
+ auto schedule_new_query = [&] {
+ if (current_query_id > max_query_id) {
+ if (inflight_queries == 0) {
+ done = true;
+ }
+ return;
+ }
+ if (inflight_queries >= MAX_INFLIGHT_QUERIES) {
+ return;
+ }
+ auto query_id = current_query_id++;
+ auto query = std::make_shared<ChainSchedulerQuery>();
+ query->id = query_id;
+ int chain_n = rnd.fast(1, ChainsN);
+ td::vector<ChainId> chain_ids(ChainsN);
+ std::iota(chain_ids.begin(), chain_ids.end(), 0);
+ td::random_shuffle(td::as_mutable_span(chain_ids), rnd);
+ chain_ids.resize(chain_n);
+ for (auto chain_id : chain_ids) {
+ chains[td::narrow_cast<size_t>(chain_id)].push_back(query);
+ }
+ auto task_id = scheduler.create_task(chain_ids, query);
+ query->task_id = task_id;
+ pending_queries.push_back(task_id);
+ inflight_queries++;
+ };
+
+ auto check_parents_ok = [&](const QueryWithParents &query_with_parents) -> bool {
+ return td::all_of(query_with_parents.parents, [](auto &parent) { return parent->is_ok; });
+ };
+
+ auto to_query_ptr = [&](TaskId task_id) {
+ return *scheduler.get_task_extra(task_id);
+ };
+ auto flush_pending_queries = [&] {
+ while (true) {
+ auto o_task_with_parents = scheduler.start_next_task();
+ if (!o_task_with_parents) {
+ break;
+ }
+ auto task_with_parents = o_task_with_parents.unwrap();
+ QueryWithParents query_with_parents;
+ query_with_parents.task_id = task_with_parents.task_id;
+ query_with_parents.id = to_query_ptr(task_with_parents.task_id);
+ query_with_parents.parents = td::transform(task_with_parents.parents, to_query_ptr);
+ active_queries.push_back(query_with_parents);
+ sent_cnt++;
+ }
+ };
+ auto skip_one_query = [&] {
+ if (pending_queries.empty()) {
+ return;
+ }
+ auto it = pending_queries.begin() + rnd.fast(0, static_cast<int>(pending_queries.size()) - 1);
+ auto task_id = *it;
+ pending_queries.erase(it);
+ td::remove_if(active_queries, [&](auto &q) { return q.task_id == task_id; });
+
+ auto query = *scheduler.get_task_extra(task_id);
+ query->skipped = true;
+ scheduler.finish_task(task_id);
+ inflight_queries--;
+ LOG(INFO) << "Skip " << query->id;
+ };
+ auto execute_one_query = [&] {
+ if (active_queries.empty()) {
+ return;
+ }
+ auto it = active_queries.begin() + rnd.fast(0, static_cast<int>(active_queries.size()) - 1);
+ auto query_with_parents = *it;
+ active_queries.erase(it);
+
+ auto query = query_with_parents.id;
+ if (rnd.fast(0, 20) == 0) {
+ scheduler.finish_task(query->task_id);
+ td::remove(pending_queries, query->task_id);
+ inflight_queries--;
+ LOG(INFO) << "Fail " << query->id;
+ } else if (check_parents_ok(query_with_parents)) {
+ query->is_ok = true;
+ scheduler.finish_task(query->task_id);
+ td::remove(pending_queries, query->task_id);
+ inflight_queries--;
+ LOG(INFO) << "OK " << query->id;
+ } else {
+ scheduler.reset_task(query->task_id);
+ LOG(INFO) << "Reset " << query->id;
+ }
+ };
+
+ td::RandomSteps steps({{schedule_new_query, 100}, {execute_one_query, 100}, {skip_one_query, 10}});
+ while (!done) {
+ steps.step(rnd);
+ flush_pending_queries();
+ // LOG(INFO) << scheduler;
+ }
+ LOG(INFO) << "Sent queries count " << sent_cnt;
+ LOG(INFO) << "Total queries " << current_query_id;
+ for (auto &chain : chains) {
+ int prev_ok = -1;
+ int failed_cnt = 0;
+ int ok_cnt = 0;
+ int skipped_cnt = 0;
+ for (auto &q : chain) {
+ if (q->is_ok) {
+ CHECK(prev_ok < q->id);
+ prev_ok = q->id;
+ ok_cnt++;
+ } else {
+ if (q->skipped) {
+ skipped_cnt++;
+ } else {
+ failed_cnt++;
+ }
+ }
+ }
+ LOG(INFO) << "Chain ok " << ok_cnt << " failed " << failed_cnt << " skipped " << skipped_cnt;
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/ConcurrentHashMap.cpp b/protocols/Telegram/tdlib/td/tdutils/test/ConcurrentHashMap.cpp
new file mode 100644
index 0000000000..a90f11d525
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/ConcurrentHashMap.cpp
@@ -0,0 +1,252 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/benchmark.h"
+#include "td/utils/common.h"
+#include "td/utils/ConcurrentHashTable.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/Mutex.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/SpinLock.h"
+#include "td/utils/tests.h"
+
+#include <atomic>
+
+#if !TD_THREAD_UNSUPPORTED
+
+#if TD_HAVE_ABSL
+#include <absl/container/flat_hash_map.h>
+#else
+#include <unordered_map>
+#endif
+
+#if TD_WITH_LIBCUCKOO
+#include <third-party/libcuckoo/libcuckoo/cuckoohash_map.hh>
+#endif
+
+#if TD_WITH_JUNCTION
+#include <junction/ConcurrentMap_Grampa.h>
+#include <junction/ConcurrentMap_Leapfrog.h>
+#include <junction/ConcurrentMap_Linear.h>
+#endif
+
+// Non resizable HashMap. Just an example
+template <class KeyT, class ValueT>
+class ArrayHashMap {
+ public:
+ explicit ArrayHashMap(std::size_t n) : array_(n) {
+ }
+ struct Node {
+ std::atomic<KeyT> key{KeyT{}};
+ std::atomic<ValueT> value{ValueT{}};
+ };
+ static td::string get_name() {
+ return "ArrayHashMap";
+ }
+ KeyT empty_key() const {
+ return KeyT{};
+ }
+
+ void insert(KeyT key, ValueT value) {
+ array_.with_value(key, true, [&](auto &node_value) { node_value.store(value, std::memory_order_release); });
+ }
+ ValueT find(KeyT key, ValueT value) {
+ array_.with_value(key, false, [&](auto &node_value) { value = node_value.load(std::memory_order_acquire); });
+ return value;
+ }
+
+ private:
+ td::AtomicHashArray<KeyT, std::atomic<ValueT>> array_;
+};
+
+template <class KeyT, class ValueT>
+class ConcurrentHashMapMutex {
+ public:
+ explicit ConcurrentHashMapMutex(std::size_t) {
+ }
+ static td::string get_name() {
+ return "ConcurrentHashMapMutex";
+ }
+ void insert(KeyT key, ValueT value) {
+ auto guard = mutex_.lock();
+ hash_map_.emplace(key, value);
+ }
+ ValueT find(KeyT key, ValueT default_value) {
+ auto guard = mutex_.lock();
+ auto it = hash_map_.find(key);
+ if (it == hash_map_.end()) {
+ return default_value;
+ }
+ return it->second;
+ }
+
+ private:
+ td::Mutex mutex_;
+#if TD_HAVE_ABSL
+ absl::flat_hash_map<KeyT, ValueT> hash_map_;
+#else
+ std::unordered_map<KeyT, ValueT, td::Hash<KeyT>> hash_map_;
+#endif
+};
+
+template <class KeyT, class ValueT>
+class ConcurrentHashMapSpinlock {
+ public:
+ explicit ConcurrentHashMapSpinlock(size_t) {
+ }
+ static td::string get_name() {
+ return "ConcurrentHashMapSpinlock";
+ }
+ void insert(KeyT key, ValueT value) {
+ auto guard = spinlock_.lock();
+ hash_map_.emplace(key, value);
+ }
+ ValueT find(KeyT key, ValueT default_value) {
+ auto guard = spinlock_.lock();
+ auto it = hash_map_.find(key);
+ if (it == hash_map_.end()) {
+ return default_value;
+ }
+ return it->second;
+ }
+
+ private:
+ td::SpinLock spinlock_;
+#if TD_HAVE_ABSL
+ absl::flat_hash_map<KeyT, ValueT> hash_map_;
+#else
+ std::unordered_map<KeyT, ValueT, td::Hash<KeyT>> hash_map_;
+#endif
+};
+
+#if TD_WITH_LIBCUCKOO
+template <class KeyT, class ValueT>
+class ConcurrentHashMapLibcuckoo {
+ public:
+ explicit ConcurrentHashMapLibcuckoo(size_t) {
+ }
+ static td::string get_name() {
+ return "ConcurrentHashMapLibcuckoo";
+ }
+ void insert(KeyT key, ValueT value) {
+ hash_map_.insert(key, value);
+ }
+ ValueT find(KeyT key, ValueT default_value) {
+ hash_map_.find(key, default_value);
+ return default_value;
+ }
+
+ private:
+ cuckoohash_map<KeyT, ValueT> hash_map_;
+};
+#endif
+
+#if TD_WITH_JUNCTION
+template <class KeyT, class ValueT>
+class ConcurrentHashMapJunction {
+ public:
+ explicit ConcurrentHashMapJunction(std::size_t size) : hash_map_() {
+ }
+ static td::string get_name() {
+ return "ConcurrentHashMapJunction";
+ }
+ void insert(KeyT key, ValueT value) {
+ hash_map_.assign(key, value);
+ }
+ ValueT find(KeyT key, ValueT default_value) {
+ return hash_map_.get(key);
+ }
+
+ ConcurrentHashMapJunction(const ConcurrentHashMapJunction &) = delete;
+ ConcurrentHashMapJunction &operator=(const ConcurrentHashMapJunction &) = delete;
+ ConcurrentHashMapJunction(ConcurrentHashMapJunction &&other) = delete;
+ ConcurrentHashMapJunction &operator=(ConcurrentHashMapJunction &&) = delete;
+ ~ConcurrentHashMapJunction() {
+ junction::DefaultQSBR.flush();
+ }
+
+ private:
+ junction::ConcurrentMap_Leapfrog<KeyT, ValueT> hash_map_;
+};
+#endif
+
+template <class HashMap>
+class HashMapBenchmark final : public td::Benchmark {
+ struct Query {
+ int key;
+ int value;
+ };
+ td::vector<Query> queries;
+ td::unique_ptr<HashMap> hash_map;
+
+ std::size_t threads_n = 16;
+ static constexpr std::size_t MUL = 7273; //1000000000 + 7;
+ int n_ = 0;
+
+ public:
+ explicit HashMapBenchmark(std::size_t threads_n) : threads_n(threads_n) {
+ }
+ td::string get_description() const final {
+ return HashMap::get_name();
+ }
+ void start_up_n(int n) final {
+ n *= static_cast<int>(threads_n);
+ n_ = n;
+ hash_map = td::make_unique<HashMap>(n * 2);
+ }
+
+ void run(int n) final {
+ n = n_;
+ td::vector<td::thread> threads;
+
+ for (std::size_t i = 0; i < threads_n; i++) {
+ std::size_t l = n * i / threads_n;
+ std::size_t r = n * (i + 1) / threads_n;
+ threads.emplace_back([l, r, this] {
+ for (size_t i = l; i < r; i++) {
+ auto x = td::narrow_cast<int>((i + 1) * MUL % n_) + 3;
+ auto y = td::narrow_cast<int>(i + 2);
+ hash_map->insert(x, y);
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+ }
+
+ void tear_down() final {
+ for (int i = 0; i < n_; i++) {
+ auto x = td::narrow_cast<int>((i + 1) * MUL % n_) + 3;
+ auto y = td::narrow_cast<int>(i + 2);
+ ASSERT_EQ(y, hash_map->find(x, -1));
+ }
+ queries.clear();
+ hash_map.reset();
+ }
+};
+
+template <class HashMap>
+static void bench_hash_map() {
+ td::bench(HashMapBenchmark<HashMap>(16));
+ td::bench(HashMapBenchmark<HashMap>(1));
+}
+
+TEST(ConcurrentHashMap, Benchmark) {
+ bench_hash_map<td::ConcurrentHashMap<td::int32, td::int32>>();
+ bench_hash_map<ArrayHashMap<td::int32, td::int32>>();
+ bench_hash_map<ConcurrentHashMapSpinlock<td::int32, td::int32>>();
+ bench_hash_map<ConcurrentHashMapMutex<td::int32, td::int32>>();
+#if TD_WITH_LIBCUCKOO
+ bench_hash_map<ConcurrentHashMapLibcuckoo<td::int32, td::int32>>();
+#endif
+#if TD_WITH_JUNCTION
+ bench_hash_map<ConcurrentHashMapJunction<td::int32, td::int32>>();
+#endif
+}
+
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/Enumerator.cpp b/protocols/Telegram/tdlib/td/tdutils/test/Enumerator.cpp
index b617485462..210ab415cc 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/Enumerator.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/Enumerator.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/EpochBasedMemoryReclamation.cpp b/protocols/Telegram/tdlib/td/tdutils/test/EpochBasedMemoryReclamation.cpp
new file mode 100644
index 0000000000..c97679bb83
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/EpochBasedMemoryReclamation.cpp
@@ -0,0 +1,68 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/common.h"
+#include "td/utils/EpochBasedMemoryReclamation.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Random.h"
+#include "td/utils/tests.h"
+
+#include <atomic>
+
+#if !TD_THREAD_UNSUPPORTED
+TEST(EpochBaseMemoryReclamation, stress) {
+ struct Node {
+ std::atomic<std::string *> name_{nullptr};
+ char pad[64];
+ };
+
+ int threads_n = 10;
+ std::vector<Node> nodes(threads_n);
+ td::EpochBasedMemoryReclamation<std::string> ebmr(threads_n + 1);
+ auto locker = ebmr.get_locker(threads_n);
+ locker.lock();
+ locker.unlock();
+ std::vector<td::thread> threads(threads_n);
+ int thread_id = 0;
+ for (auto &thread : threads) {
+ thread = td::thread([&, thread_id] {
+ auto locker = ebmr.get_locker(thread_id);
+ locker.lock();
+ for (int i = 0; i < 1000000; i++) {
+ auto &node = nodes[td::Random::fast(0, threads_n - 1)];
+ auto *str = node.name_.load(std::memory_order_acquire);
+ if (str) {
+ CHECK(*str == "one" || *str == "twotwo");
+ }
+ if ((i + 1) % 100 == 0) {
+ locker.retire();
+ }
+ if (td::Random::fast(0, 5) == 0) {
+ auto *new_str = new td::string(td::Random::fast_bool() ? "one" : "twotwo");
+ if (node.name_.compare_exchange_strong(str, new_str, std::memory_order_acq_rel)) {
+ locker.retire(str);
+ } else {
+ delete new_str;
+ }
+ }
+ }
+ locker.retire_sync();
+ locker.unlock();
+ });
+ thread_id++;
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+ LOG(INFO) << "Undeleted pointers: " << ebmr.to_delete_size_unsafe();
+ //CHECK(static_cast<int>(ebmr.to_delete_size_unsafe()) <= threads_n * threads_n);
+ for (int i = 0; i < threads_n; i++) {
+ ebmr.get_locker(i).retire_sync();
+ }
+ CHECK(ebmr.to_delete_size_unsafe() == 0);
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/HashSet.cpp b/protocols/Telegram/tdlib/td/tdutils/test/HashSet.cpp
new file mode 100644
index 0000000000..94ebf8733b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/HashSet.cpp
@@ -0,0 +1,438 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashMapChunks.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/logging.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/tests.h"
+
+#include <algorithm>
+#include <array>
+#include <random>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+template <class T>
+static auto extract_kv(const T &reference) {
+ auto expected = td::transform(reference, [](auto &it) { return std::make_pair(it.first, it.second); });
+ std::sort(expected.begin(), expected.end());
+ return expected;
+}
+
+template <class T>
+static auto extract_k(const T &reference) {
+ auto expected = td::transform(reference, [](auto &it) { return it; });
+ std::sort(expected.begin(), expected.end());
+ return expected;
+}
+
+TEST(FlatHashMapChunks, basic) {
+ td::FlatHashMapChunks<int, int> kv;
+ kv[5] = 3;
+ ASSERT_EQ(3, kv[5]);
+ kv[3] = 4;
+ ASSERT_EQ(4, kv[3]);
+}
+
+TEST(FlatHashMap, probing) {
+ auto test = [](int buckets, int elements) {
+ CHECK(buckets >= elements);
+ td::vector<bool> data(buckets, false);
+ std::random_device rnd;
+ std::mt19937 mt(rnd());
+ std::uniform_int_distribution<td::int32> d(0, buckets - 1);
+ for (int i = 0; i < elements; i++) {
+ int pos = d(mt);
+ while (data[pos]) {
+ pos++;
+ if (pos == buckets) {
+ pos = 0;
+ }
+ }
+ data[pos] = true;
+ }
+ int max_chain = 0;
+ int cur_chain = 0;
+ for (auto x : data) {
+ if (x) {
+ cur_chain++;
+ max_chain = td::max(max_chain, cur_chain);
+ } else {
+ cur_chain = 0;
+ }
+ }
+ LOG(INFO) << "Buckets=" << buckets << " elements=" << elements << " max_chain=" << max_chain;
+ };
+ test(8192, static_cast<int>(8192 * 0.8));
+ test(8192, static_cast<int>(8192 * 0.6));
+ test(8192, static_cast<int>(8192 * 0.3));
+}
+
+struct A {
+ int a;
+};
+
+struct AHash {
+ td::uint32 operator()(A a) const {
+ return td::Hash<int>()(a.a);
+ }
+};
+
+static bool operator==(const A &lhs, const A &rhs) {
+ return lhs.a == rhs.a;
+}
+
+TEST(FlatHashSet, foreach) {
+ td::FlatHashSet<A, AHash> s;
+ for (auto it : s) {
+ LOG(ERROR) << it.a;
+ }
+ s.insert({1});
+ LOG(INFO) << s.begin()->a;
+}
+
+TEST(FlatHashSet, TL) {
+ td::FlatHashSet<int> s;
+ int N = 100000;
+ for (int i = 0; i < 10000000; i++) {
+ s.insert((i + N / 2) % N + 1);
+ s.erase(i % N + 1);
+ }
+}
+
+TEST(FlatHashMap, basic) {
+ {
+ td::FlatHashMap<td::int32, int> map;
+ map[1] = 2;
+ ASSERT_EQ(2, map[1]);
+ ASSERT_EQ(1, map.find(1)->first);
+ ASSERT_EQ(2, map.find(1)->second);
+ // ASSERT_EQ(1, map.find(1)->key());
+ // ASSERT_EQ(2, map.find(1)->value());
+ for (auto &kv : map) {
+ ASSERT_EQ(1, kv.first);
+ ASSERT_EQ(2, kv.second);
+ }
+ map.erase(map.find(1));
+ }
+
+ td::FlatHashMap<td::int32, std::array<td::unique_ptr<td::string>, 10>> x;
+ auto y = std::move(x);
+ x[12];
+ x.erase(x.find(12));
+
+ {
+ td::FlatHashMap<td::int32, td::string> map = {{1, "hello"}, {2, "world"}};
+ ASSERT_EQ("hello", map[1]);
+ ASSERT_EQ("world", map[2]);
+ ASSERT_EQ(2u, map.size());
+ ASSERT_EQ("", map[3]);
+ ASSERT_EQ(3u, map.size());
+ }
+
+ {
+ td::FlatHashMap<td::int32, td::string> map = {{1, "hello"}, {1, "world"}};
+ ASSERT_EQ("hello", map[1]);
+ ASSERT_EQ(1u, map.size());
+ }
+
+ using KV = td::FlatHashMap<td::string, td::string>;
+ using Data = td::vector<std::pair<td::string, td::string>>;
+ auto data = Data{{"a", "b"}, {"c", "d"}};
+ { ASSERT_EQ(Data{}, extract_kv(KV())); }
+
+ {
+ KV kv;
+ for (auto &pair : data) {
+ kv.emplace(pair.first, pair.second);
+ }
+ ASSERT_EQ(data, extract_kv(kv));
+
+ KV moved_kv(std::move(kv));
+ ASSERT_EQ(data, extract_kv(moved_kv));
+ ASSERT_EQ(Data{}, extract_kv(kv));
+ ASSERT_TRUE(kv.empty());
+ kv = std::move(moved_kv);
+ ASSERT_EQ(data, extract_kv(kv));
+
+ KV assign_moved_kv;
+ assign_moved_kv = std::move(kv);
+ ASSERT_EQ(data, extract_kv(assign_moved_kv));
+ ASSERT_EQ(Data{}, extract_kv(kv));
+ ASSERT_TRUE(kv.empty());
+ kv = std::move(assign_moved_kv);
+
+ KV it_copy_kv;
+ for (auto &pair : kv) {
+ it_copy_kv.emplace(pair.first, pair.second);
+ }
+ ASSERT_EQ(data, extract_kv(it_copy_kv));
+ }
+
+ {
+ KV kv;
+ ASSERT_TRUE(kv.empty());
+ ASSERT_EQ(0u, kv.size());
+ for (auto &pair : data) {
+ kv.emplace(pair.first, pair.second);
+ }
+ ASSERT_TRUE(!kv.empty());
+ ASSERT_EQ(2u, kv.size());
+
+ ASSERT_EQ("a", kv.find("a")->first);
+ ASSERT_EQ("b", kv.find("a")->second);
+ kv.find("a")->second = "c";
+ ASSERT_EQ("c", kv.find("a")->second);
+ ASSERT_EQ("c", kv["a"]);
+
+ ASSERT_EQ(0u, kv.count("x"));
+ ASSERT_EQ(1u, kv.count("a"));
+ }
+ {
+ KV kv;
+ kv["d"];
+ ASSERT_EQ((Data{{"d", ""}}), extract_kv(kv));
+ kv.erase(kv.find("d"));
+ ASSERT_EQ(Data{}, extract_kv(kv));
+ }
+}
+
+TEST(FlatHashMap, remove_if_basic) {
+ td::Random::Xorshift128plus rnd(123);
+
+ constexpr int TESTS_N = 1000;
+ constexpr int MAX_TABLE_SIZE = 1000;
+ for (int test_i = 0; test_i < TESTS_N; test_i++) {
+ std::unordered_map<td::uint64, td::uint64, td::Hash<td::uint64>> reference;
+ td::FlatHashMap<td::uint64, td::uint64> table;
+ int N = rnd.fast(1, MAX_TABLE_SIZE);
+ for (int i = 0; i < N; i++) {
+ auto key = rnd();
+ auto value = i;
+ reference[key] = value;
+ table[key] = value;
+ }
+ ASSERT_EQ(extract_kv(reference), extract_kv(table));
+
+ td::vector<std::pair<td::uint64, td::uint64>> kv;
+ td::table_remove_if(table, [&](auto &it) {
+ kv.emplace_back(it.first, it.second);
+ return it.second % 2 == 0;
+ });
+ std::sort(kv.begin(), kv.end());
+ ASSERT_EQ(extract_kv(reference), kv);
+
+ td::table_remove_if(reference, [](auto &it) { return it.second % 2 == 0; });
+ ASSERT_EQ(extract_kv(reference), extract_kv(table));
+ }
+}
+
+static constexpr size_t MAX_TABLE_SIZE = 1000;
+TEST(FlatHashMap, stress_test) {
+ td::Random::Xorshift128plus rnd(123);
+ size_t max_table_size = MAX_TABLE_SIZE; // dynamic value
+ std::unordered_map<td::uint64, td::uint64, td::Hash<td::uint64>> ref;
+ td::FlatHashMap<td::uint64, td::uint64> tbl;
+
+ auto validate = [&] {
+ ASSERT_EQ(ref.empty(), tbl.empty());
+ ASSERT_EQ(ref.size(), tbl.size());
+ ASSERT_EQ(extract_kv(ref), extract_kv(tbl));
+ for (auto &kv : ref) {
+ auto tbl_it = tbl.find(kv.first);
+ ASSERT_TRUE(tbl_it != tbl.end());
+ ASSERT_EQ(kv.second, tbl_it->second);
+ }
+ };
+
+ td::vector<td::RandomSteps::Step> steps;
+ auto add_step = [&](td::Slice step_name, td::uint32 weight, auto f) {
+ auto g = [&, f = std::move(f)] {
+ //ASSERT_EQ(ref.size(), tbl.size());
+ f();
+ ASSERT_EQ(ref.size(), tbl.size());
+ //validate();
+ };
+ steps.emplace_back(td::RandomSteps::Step{std::move(g), weight});
+ };
+
+ auto gen_key = [&] {
+ auto key = rnd() % 4000 + 1;
+ return key;
+ };
+
+ add_step("Reset hash table", 1, [&] {
+ validate();
+ td::reset_to_empty(ref);
+ td::reset_to_empty(tbl);
+ max_table_size = rnd.fast(1, MAX_TABLE_SIZE);
+ });
+ add_step("Clear hash table", 1, [&] {
+ validate();
+ ref.clear();
+ tbl.clear();
+ max_table_size = rnd.fast(1, MAX_TABLE_SIZE);
+ });
+
+ add_step("Insert random value", 1000, [&] {
+ if (tbl.size() > max_table_size) {
+ return;
+ }
+ auto key = gen_key();
+ auto value = rnd();
+ ref[key] = value;
+ tbl[key] = value;
+ ASSERT_EQ(ref[key], tbl[key]);
+ });
+
+ add_step("Emplace random value", 1000, [&] {
+ if (tbl.size() > max_table_size) {
+ return;
+ }
+ auto key = gen_key();
+ auto value = rnd();
+ auto ref_it = ref.emplace(key, value);
+ auto tbl_it = tbl.emplace(key, value);
+ ASSERT_EQ(ref_it.second, tbl_it.second);
+ ASSERT_EQ(key, tbl_it.first->first);
+ });
+
+ add_step("empty operator[]", 1000, [&] {
+ if (tbl.size() > max_table_size) {
+ return;
+ }
+ auto key = gen_key();
+ ASSERT_EQ(ref[key], tbl[key]);
+ });
+
+ add_step("reserve", 10, [&] { tbl.reserve(static_cast<size_t>(rnd() % max_table_size)); });
+
+ add_step("find", 1000, [&] {
+ auto key = gen_key();
+ auto ref_it = ref.find(key);
+ auto tbl_it = tbl.find(key);
+ ASSERT_EQ(ref_it == ref.end(), tbl_it == tbl.end());
+ if (ref_it != ref.end()) {
+ ASSERT_EQ(ref_it->first, tbl_it->first);
+ ASSERT_EQ(ref_it->second, tbl_it->second);
+ }
+ });
+
+ add_step("find_and_erase", 100, [&] {
+ auto key = gen_key();
+ auto ref_it = ref.find(key);
+ auto tbl_it = tbl.find(key);
+ ASSERT_EQ(ref_it == ref.end(), tbl_it == tbl.end());
+ if (ref_it != ref.end()) {
+ ref.erase(ref_it);
+ tbl.erase(tbl_it);
+ }
+ });
+
+ add_step("remove_if", 5, [&] {
+ auto mul = rnd();
+ auto bit = rnd() % 64;
+ auto condition = [&](auto &it) {
+ return (((it.second * mul) >> bit) & 1) == 0;
+ };
+ td::table_remove_if(tbl, condition);
+ td::table_remove_if(ref, condition);
+ });
+
+ td::RandomSteps runner(std::move(steps));
+ for (size_t i = 0; i < 1000000; i++) {
+ runner.step(rnd);
+ }
+}
+
+TEST(FlatHashSet, stress_test) {
+ td::vector<td::RandomSteps::Step> steps;
+ auto add_step = [&steps](td::Slice, td::uint32 weight, auto f) {
+ steps.emplace_back(td::RandomSteps::Step{std::move(f), weight});
+ };
+
+ td::Random::Xorshift128plus rnd(123);
+ size_t max_table_size = MAX_TABLE_SIZE; // dynamic value
+ std::unordered_set<td::uint64, td::Hash<td::uint64>> ref;
+ td::FlatHashSet<td::uint64> tbl;
+
+ auto validate = [&] {
+ ASSERT_EQ(ref.empty(), tbl.empty());
+ ASSERT_EQ(ref.size(), tbl.size());
+ ASSERT_EQ(extract_k(ref), extract_k(tbl));
+ };
+ auto gen_key = [&] {
+ auto key = rnd() % 4000 + 1;
+ return key;
+ };
+
+ add_step("Reset hash table", 1, [&] {
+ validate();
+ td::reset_to_empty(ref);
+ td::reset_to_empty(tbl);
+ max_table_size = rnd.fast(1, MAX_TABLE_SIZE);
+ });
+ add_step("Clear hash table", 1, [&] {
+ validate();
+ ref.clear();
+ tbl.clear();
+ max_table_size = rnd.fast(1, MAX_TABLE_SIZE);
+ });
+
+ add_step("Insert random value", 1000, [&] {
+ if (tbl.size() > max_table_size) {
+ return;
+ }
+ auto key = gen_key();
+ ref.insert(key);
+ tbl.insert(key);
+ });
+
+ add_step("reserve", 10, [&] { tbl.reserve(static_cast<size_t>(rnd() % max_table_size)); });
+
+ add_step("find", 1000, [&] {
+ auto key = gen_key();
+ auto ref_it = ref.find(key);
+ auto tbl_it = tbl.find(key);
+ ASSERT_EQ(ref_it == ref.end(), tbl_it == tbl.end());
+ if (ref_it != ref.end()) {
+ ASSERT_EQ(*ref_it, *tbl_it);
+ }
+ });
+
+ add_step("find_and_erase", 100, [&] {
+ auto key = gen_key();
+ auto ref_it = ref.find(key);
+ auto tbl_it = tbl.find(key);
+ ASSERT_EQ(ref_it == ref.end(), tbl_it == tbl.end());
+ if (ref_it != ref.end()) {
+ ref.erase(ref_it);
+ tbl.erase(tbl_it);
+ }
+ });
+
+ add_step("remove_if", 5, [&] {
+ auto mul = rnd();
+ auto bit = rnd() % 64;
+ auto condition = [&](auto &it) {
+ return (((it * mul) >> bit) & 1) == 0;
+ };
+ td::table_remove_if(tbl, condition);
+ td::table_remove_if(ref, condition);
+ });
+
+ td::RandomSteps runner(std::move(steps));
+ for (size_t i = 0; i < 10000000; i++) {
+ runner.step(rnd);
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/HazardPointers.cpp b/protocols/Telegram/tdlib/td/tdutils/test/HazardPointers.cpp
index 36b0570530..0c4174db0f 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/HazardPointers.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/HazardPointers.cpp
@@ -1,13 +1,15 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#include "td/utils/common.h"
#include "td/utils/HazardPointers.h"
#include "td/utils/logging.h"
#include "td/utils/port/thread.h"
#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
#include "td/utils/tests.h"
#include <atomic>
@@ -15,7 +17,7 @@
#if !TD_THREAD_UNSUPPORTED
TEST(HazardPointers, stress) {
struct Node {
- std::atomic<std::string *> name_;
+ std::atomic<std::string *> name_{nullptr};
char pad[64];
};
int threads_n = 10;
@@ -25,16 +27,16 @@ TEST(HazardPointers, stress) {
int thread_id = 0;
for (auto &thread : threads) {
thread = td::thread([&, thread_id] {
- auto holder = hazard_pointers.get_holder(thread_id, 0);
+ std::remove_reference_t<decltype(hazard_pointers)>::Holder holder(hazard_pointers, thread_id, 0);
for (int i = 0; i < 1000000; i++) {
auto &node = nodes[td::Random::fast(0, threads_n - 1)];
auto *str = holder.protect(node.name_);
if (str) {
- CHECK(*str == "one" || *str == "twotwo");
+ CHECK(*str == td::Slice("one") || *str == td::Slice("twotwo"));
}
holder.clear();
if (td::Random::fast(0, 5) == 0) {
- std::string *new_str = new std::string(td::Random::fast(0, 1) == 0 ? "one" : "twotwo");
+ auto *new_str = new td::string(td::Random::fast_bool() ? "one" : "twotwo");
if (node.name_.compare_exchange_strong(str, new_str, std::memory_order_acq_rel)) {
hazard_pointers.retire(thread_id, str);
} else {
@@ -48,11 +50,11 @@ TEST(HazardPointers, stress) {
for (auto &thread : threads) {
thread.join();
}
- LOG(ERROR) << "Undeleted pointers: " << hazard_pointers.to_delete_size_unsafe();
+ LOG(INFO) << "Undeleted pointers: " << hazard_pointers.to_delete_size_unsafe();
CHECK(static_cast<int>(hazard_pointers.to_delete_size_unsafe()) <= threads_n * threads_n);
for (int i = 0; i < threads_n; i++) {
hazard_pointers.retire(i);
}
CHECK(hazard_pointers.to_delete_size_unsafe() == 0);
}
-#endif //!TD_THREAD_UNSUPPORTED
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/HttpUrl.cpp b/protocols/Telegram/tdlib/td/tdutils/test/HttpUrl.cpp
new file mode 100644
index 0000000000..6e91d48803
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/HttpUrl.cpp
@@ -0,0 +1,54 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/common.h"
+#include "td/utils/HttpUrl.h"
+#include "td/utils/tests.h"
+
+#include <utility>
+
+static void test_get_url_query_file_name(const char *prefix, const char *suffix, const char *file_name) {
+ auto path = td::string(prefix) + td::string(file_name) + td::string(suffix);
+ ASSERT_STREQ(file_name, td::get_url_query_file_name(path));
+ ASSERT_STREQ(file_name, td::get_url_file_name("http://telegram.org" + path));
+ ASSERT_STREQ(file_name, td::get_url_file_name("http://telegram.org:80" + path));
+ ASSERT_STREQ(file_name, td::get_url_file_name("telegram.org" + path));
+}
+
+TEST(HttpUrl, get_url_query_file_name) {
+ for (auto suffix : {"?t=1#test", "#test?t=1", "#?t=1", "?t=1#", "#test", "?t=1", "#", "?", ""}) {
+ test_get_url_query_file_name("", suffix, "");
+ test_get_url_query_file_name("/", suffix, "");
+ test_get_url_query_file_name("/a/adasd/", suffix, "");
+ test_get_url_query_file_name("/a/lklrjetn/", suffix, "adasd.asdas");
+ test_get_url_query_file_name("/", suffix, "a123asadas");
+ test_get_url_query_file_name("/", suffix, "\\a\\1\\2\\3\\a\\s\\a\\das");
+ }
+}
+
+static void test_parse_url_query(const td::string &query, const td::vector<td::string> &path,
+ const td::vector<std::pair<td::string, td::string>> &args) {
+ for (auto hash : {"", "#", "#?t=1", "#t=1&a=b"}) {
+ auto url_query = td::parse_url_query(query + hash);
+ ASSERT_EQ(path, url_query.path_);
+ ASSERT_EQ(args, url_query.args_);
+ }
+}
+
+TEST(HttpUrl, parse_url_query) {
+ test_parse_url_query("", {}, {});
+ test_parse_url_query("a", {"a"}, {});
+ test_parse_url_query("/", {}, {});
+ test_parse_url_query("//", {}, {});
+ test_parse_url_query("///?a", {}, {{"a", ""}});
+ test_parse_url_query("/a/b/c/", {"a", "b", "c"}, {});
+ test_parse_url_query("/a/b/?c/", {td::string("a"), td::string("b")}, {{"c/", ""}});
+ test_parse_url_query("?", {}, {});
+ test_parse_url_query("???", {}, {{"??", ""}});
+ test_parse_url_query("?a=b=c=d?e=f=g=h&x=y=z?d=3&", {}, {{"a", "b=c=d?e=f=g=h"}, {"x", "y=z?d=3"}});
+ test_parse_url_query("c?&&&a=b", {"c"}, {{"a", "b"}});
+ test_parse_url_query("c?&&&=b", {"c"}, {});
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/List.cpp b/protocols/Telegram/tdlib/td/tdutils/test/List.cpp
new file mode 100644
index 0000000000..02ab080d89
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/List.cpp
@@ -0,0 +1,169 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/common.h"
+#include "td/utils/List.h"
+#include "td/utils/MovableValue.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Random.h"
+#include "td/utils/tests.h"
+#include "td/utils/TsList.h"
+
+#include <atomic>
+#include <mutex>
+#include <set>
+#include <utility>
+
+struct ListData {
+ td::MovableValue<td::uint64> value_;
+ td::MovableValue<bool> in_list_;
+
+ ListData() = default;
+ ListData(td::uint64 value, bool in_list) : value_(value), in_list_(in_list) {
+ }
+};
+
+struct Node final : public td::ListNode {
+ Node() = default;
+ explicit Node(ListData data) : data_(std::move(data)) {
+ }
+
+ ListData data_;
+};
+
+static ListData &get_data(Node &node) {
+ return node.data_;
+}
+
+static ListData &get_data(td::TsListNode<ListData> &node) {
+ return node.get_data_unsafe();
+}
+
+static std::unique_lock<std::mutex> lock(td::ListNode &node) {
+ return {};
+}
+
+static std::unique_lock<std::mutex> lock(td::TsListNode<ListData> &node) {
+ return node.lock();
+}
+
+template <class ListNodeT, class ListRootT, class NodeT>
+static void do_run_list_test(ListRootT &root, std::atomic<td::uint64> &id) {
+ td::vector<NodeT> nodes;
+
+ td::Random::Xorshift128plus rnd(123);
+
+ auto next_id = [&] {
+ return ++id;
+ };
+ auto add_node = [&] {
+ if (nodes.size() >= 20) {
+ return;
+ }
+ nodes.push_back(NodeT({next_id(), false}));
+ };
+ auto pop_node = [&] {
+ if (nodes.empty()) {
+ return;
+ }
+ nodes.pop_back();
+ };
+ auto random_node_index = [&] {
+ CHECK(!nodes.empty());
+ return rnd.fast(0, static_cast<int>(nodes.size()) - 1);
+ };
+
+ auto link_node = [&] {
+ if (nodes.empty()) {
+ return;
+ }
+ auto i = random_node_index();
+ nodes[i].remove();
+ get_data(nodes[i]) = ListData(next_id(), true);
+ root.put(&nodes[i]);
+ };
+ auto unlink_node = [&] {
+ if (nodes.empty()) {
+ return;
+ }
+ auto i = random_node_index();
+ nodes[i].remove();
+ get_data(nodes[i]).in_list_ = false;
+ };
+ auto swap_nodes = [&] {
+ if (nodes.empty()) {
+ return;
+ }
+ auto i = random_node_index();
+ auto j = random_node_index();
+ std::swap(nodes[i], nodes[j]);
+ };
+ auto set_node = [&] {
+ if (nodes.empty()) {
+ return;
+ }
+ auto i = random_node_index();
+ auto j = random_node_index();
+ nodes[i] = std::move(nodes[j]);
+ };
+ auto validate = [&] {
+ std::multiset<td::uint64> in_list;
+ std::multiset<td::uint64> not_in_list;
+ for (auto &node : nodes) {
+ if (get_data(node).in_list_.get()) {
+ in_list.insert(get_data(node).value_.get());
+ } else {
+ not_in_list.insert(get_data(node).value_.get());
+ }
+ }
+ auto guard = lock(root);
+ for (auto *begin = root.begin(), *end = root.end(); begin != end; begin = begin->get_next()) {
+ auto &data = get_data(*static_cast<NodeT *>(begin));
+ CHECK(data.in_list_.get());
+ CHECK(data.value_.get() != 0);
+ auto it = in_list.find(data.value_.get());
+ if (it != in_list.end()) {
+ in_list.erase(it);
+ } else {
+ ASSERT_EQ(0u, not_in_list.count(data.value_.get()));
+ }
+ }
+ ASSERT_EQ(0u, in_list.size());
+ };
+ td::RandomSteps steps(
+ {{add_node, 3}, {pop_node, 1}, {unlink_node, 1}, {link_node, 3}, {swap_nodes, 1}, {set_node, 1}, {validate, 1}});
+ for (int i = 0; i < 10000; i++) {
+ steps.step(rnd);
+ }
+}
+
+TEST(Misc, List) {
+ td::ListNode root;
+ std::atomic<td::uint64> id{0};
+ for (std::size_t i = 0; i < 4; i++) {
+ do_run_list_test<td::ListNode, td::ListNode, Node>(root, id);
+ }
+}
+
+TEST(Misc, TsList) {
+ td::TsList<ListData> root;
+ std::atomic<td::uint64> id{0};
+ for (std::size_t i = 0; i < 4; i++) {
+ do_run_list_test<td::TsListNode<ListData>, td::TsList<ListData>, td::TsListNode<ListData>>(root, id);
+ }
+}
+
+#if !TD_THREAD_UNSUPPORTED
+TEST(Misc, TsListConcurrent) {
+ td::TsList<ListData> root;
+ td::vector<td::thread> threads;
+ std::atomic<td::uint64> id{0};
+ for (std::size_t i = 0; i < 4; i++) {
+ threads.emplace_back(
+ [&] { do_run_list_test<td::TsListNode<ListData>, td::TsList<ListData>, td::TsListNode<ListData>>(root, id); });
+ }
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/MpmcQueue.cpp b/protocols/Telegram/tdlib/td/tdutils/test/MpmcQueue.cpp
index 2da3f0cd3f..c038303c37 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/MpmcQueue.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/MpmcQueue.cpp
@@ -1,9 +1,10 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/MpmcQueue.h"
#include "td/utils/port/thread.h"
@@ -49,7 +50,7 @@ TEST(OneValue, stress) {
std::vector<td::thread> threads;
td::OneValue<std::string> value;
for (size_t i = 0; i < 2; i++) {
- threads.push_back(td::thread([&, id = i] {
+ threads.emplace_back([&, id = i] {
for (td::uint64 round = 1; round < 100000; round++) {
if (id == 0) {
value.reset();
@@ -68,7 +69,7 @@ TEST(OneValue, stress) {
if (set_status) {
CHECK(get_status);
CHECK(from.empty());
- CHECK(to == "hello") << to;
+ LOG_CHECK(to == "hello") << to;
} else {
CHECK(!get_status);
CHECK(from == "hello");
@@ -76,17 +77,17 @@ TEST(OneValue, stress) {
}
}
}
- }));
+ });
}
for (auto &thread : threads) {
thread.join();
}
}
-#endif //!TD_THREAD_UNSUPPORTED
+#endif
TEST(MpmcQueueBlock, simple) {
// Test doesn't work now and it is ok, try_pop, logic changed
- return;
+ /*
td::MpmcQueueBlock<std::string> block(2);
std::string x = "hello";
using PushStatus = td::MpmcQueueBlock<std::string>::PushStatus;
@@ -111,6 +112,7 @@ TEST(MpmcQueueBlock, simple) {
CHECK(pop_status == PopStatus::Ok);
pop_status = block.try_pop(x);
CHECK(pop_status == PopStatus::Closed);
+ */
}
TEST(MpmcQueue, simple) {
@@ -121,7 +123,7 @@ TEST(MpmcQueue, simple) {
}
for (int i = 0; i < 100; i++) {
int x = q.pop(0);
- CHECK(x == i) << x << " expected " << i;
+ LOG_CHECK(x == i) << x << " expected " << i;
}
}
}
@@ -188,18 +190,18 @@ TEST(MpmcQueue, multi_thread) {
from[data.from] = data.value;
}
}
- CHECK(all.size() == n * qn) << all.size();
+ LOG_CHECK(all.size() == n * qn) << all.size();
std::sort(all.begin(), all.end(),
[](const auto &a, const auto &b) { return std::tie(a.from, a.value) < std::tie(b.from, b.value); });
for (size_t i = 0; i < n * qn; i++) {
CHECK(all[i].from == i / qn);
CHECK(all[i].value == i % qn + 1);
}
- LOG(ERROR) << "Undeleted pointers: " << q.hazard_pointers_to_delele_size_unsafe();
+ LOG(INFO) << "Undeleted pointers: " << q.hazard_pointers_to_delele_size_unsafe();
CHECK(q.hazard_pointers_to_delele_size_unsafe() <= (n + m + 1) * (n + m + 1));
for (size_t id = 0; id < n + m + 1; id++) {
q.gc(id);
}
- CHECK(q.hazard_pointers_to_delele_size_unsafe() == 0) << q.hazard_pointers_to_delele_size_unsafe();
+ LOG_CHECK(q.hazard_pointers_to_delele_size_unsafe() == 0) << q.hazard_pointers_to_delele_size_unsafe();
}
-#endif //!TD_THREAD_UNSUPPORTED
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/MpmcWaiter.cpp b/protocols/Telegram/tdlib/td/tdutils/test/MpmcWaiter.cpp
index e27e217713..4ac882dcaf 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/MpmcWaiter.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/MpmcWaiter.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -13,21 +13,22 @@
#include <atomic>
#if !TD_THREAD_UNSUPPORTED
-TEST(MpmcWaiter, stress_one_one) {
+template <class W>
+static void test_waiter_stress_one_one() {
td::Stage run;
td::Stage check;
std::vector<td::thread> threads;
- std::atomic<size_t> value;
+ std::atomic<size_t> value{0};
size_t write_cnt = 10;
- std::unique_ptr<td::MpmcWaiter> waiter;
+ td::unique_ptr<W> waiter;
size_t threads_n = 2;
for (size_t i = 0; i < threads_n; i++) {
threads.push_back(td::thread([&, id = static_cast<td::uint32>(i)] {
for (td::uint64 round = 1; round < 100000; round++) {
if (id == 0) {
value = 0;
- waiter = std::make_unique<td::MpmcWaiter>();
+ waiter = td::make_unique<W>();
write_cnt = td::Random::fast(1, 10);
}
run.wait(round * threads_n);
@@ -37,17 +38,19 @@ TEST(MpmcWaiter, stress_one_one) {
waiter->notify();
}
} else {
- int yields = 0;
+ typename W::Slot slot;
+ W::init_slot(slot, id);
for (size_t i = 1; i <= write_cnt; i++) {
while (true) {
auto x = value.load(std::memory_order_relaxed);
if (x >= i) {
break;
}
- yields = waiter->wait(yields, id);
+ waiter->wait(slot);
}
- yields = waiter->stop_wait(yields, id);
+ waiter->stop_wait(slot);
}
+ waiter->stop_wait(slot);
}
check.wait(round * threads_n);
}
@@ -57,19 +60,29 @@ TEST(MpmcWaiter, stress_one_one) {
thread.join();
}
}
-TEST(MpmcWaiter, stress) {
+
+TEST(MpmcEagerWaiter, stress_one_one) {
+ test_waiter_stress_one_one<td::MpmcEagerWaiter>();
+}
+
+TEST(MpmcSleepyWaiter, stress_one_one) {
+ test_waiter_stress_one_one<td::MpmcSleepyWaiter>();
+}
+
+template <class W>
+static void test_waiter_stress() {
td::Stage run;
td::Stage check;
std::vector<td::thread> threads;
size_t write_n;
size_t read_n;
- std::atomic<size_t> write_pos;
- std::atomic<size_t> read_pos;
+ std::atomic<size_t> write_pos{0};
+ std::atomic<size_t> read_pos{0};
size_t end_pos;
size_t write_cnt;
size_t threads_n = 20;
- std::unique_ptr<td::MpmcWaiter> waiter;
+ td::unique_ptr<W> waiter;
for (size_t i = 0; i < threads_n; i++) {
threads.push_back(td::thread([&, id = static_cast<td::uint32>(i)] {
for (td::uint64 round = 1; round < 1000; round++) {
@@ -80,7 +93,7 @@ TEST(MpmcWaiter, stress) {
end_pos = write_n * write_cnt;
write_pos = 0;
read_pos = 0;
- waiter = std::make_unique<td::MpmcWaiter>();
+ waiter = td::make_unique<W>();
}
run.wait(round * threads_n);
if (id <= write_n) {
@@ -92,21 +105,26 @@ TEST(MpmcWaiter, stress) {
waiter->notify();
}
} else if (id > 10 && id - 10 <= read_n) {
- int yields = 0;
+ typename W::Slot slot;
+ W::init_slot(slot, id);
while (true) {
auto x = read_pos.load(std::memory_order_relaxed);
if (x == end_pos) {
+ waiter->stop_wait(slot);
break;
}
if (x == write_pos.load(std::memory_order_relaxed)) {
- yields = waiter->wait(yields, id);
+ waiter->wait(slot);
continue;
}
- yields = waiter->stop_wait(yields, id);
+ waiter->stop_wait(slot);
read_pos.compare_exchange_strong(x, x + 1, std::memory_order_relaxed);
}
}
check.wait(round * threads_n);
+ if (id == 0) {
+ waiter->close();
+ }
}
}));
}
@@ -114,4 +132,12 @@ TEST(MpmcWaiter, stress) {
thread.join();
}
}
-#endif // !TD_THREAD_UNSUPPORTED
+
+TEST(MpmcEagerWaiter, stress_multi) {
+ test_waiter_stress<td::MpmcEagerWaiter>();
+}
+
+TEST(MpmcSleepyWaiter, stress_multi) {
+ test_waiter_stress<td::MpmcSleepyWaiter>();
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/MpscLinkQueue.cpp b/protocols/Telegram/tdlib/td/tdutils/test/MpscLinkQueue.cpp
index 629e5b7223..43b0ccf086 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/MpscLinkQueue.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/MpscLinkQueue.cpp
@@ -1,16 +1,17 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/MpscLinkQueue.h"
#include "td/utils/port/thread.h"
#include "td/utils/tests.h"
-class NodeX : public td::MpscLinkQueueImpl::Node {
+class NodeX final : public td::MpscLinkQueueImpl::Node {
public:
explicit NodeX(int value) : value_(value) {
}
@@ -29,8 +30,8 @@ class NodeX : public td::MpscLinkQueueImpl::Node {
};
using QueueNode = td::MpscLinkQueueUniquePtrNode<NodeX>;
-QueueNode create_node(int value) {
- return QueueNode(std::make_unique<NodeX>(value));
+static QueueNode create_node(int value) {
+ return QueueNode(td::make_unique<NodeX>(value));
}
TEST(MpscLinkQueue, one_thread) {
@@ -48,7 +49,7 @@ TEST(MpscLinkQueue, one_thread) {
while (auto node = reader.read()) {
v.push_back(node.value().value());
}
- CHECK((v == std::vector<int>{1, 2, 3, 4})) << td::format::as_array(v);
+ LOG_CHECK((v == std::vector<int>{1, 2, 3, 4})) << td::format::as_array(v);
v.clear();
queue.push(create_node(5));
@@ -56,7 +57,7 @@ TEST(MpscLinkQueue, one_thread) {
while (auto node = reader.read()) {
v.push_back(node.value().value());
}
- CHECK((v == std::vector<int>{5})) << td::format::as_array(v);
+ LOG_CHECK((v == std::vector<int>{5})) << td::format::as_array(v);
}
{
@@ -70,7 +71,7 @@ TEST(MpscLinkQueue, one_thread) {
while (auto node = reader.read()) {
v.push_back(node.value().value());
}
- CHECK((v == std::vector<int>{3, 2, 1, 0})) << td::format::as_array(v);
+ LOG_CHECK((v == std::vector<int>{3, 2, 1, 0})) << td::format::as_array(v);
}
}
@@ -112,4 +113,4 @@ TEST(MpscLinkQueue, multi_thread) {
thread.join();
}
}
-#endif //!TD_THREAD_UNSUPPORTED
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/OptionParser.cpp b/protocols/Telegram/tdlib/td/tdutils/test/OptionParser.cpp
new file mode 100644
index 0000000000..8600eb9f19
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/OptionParser.cpp
@@ -0,0 +1,82 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+#include "td/utils/OptionParser.h"
+#include "td/utils/Slice.h"
+#include "td/utils/tests.h"
+
+TEST(OptionParser, run) {
+ td::OptionParser options;
+ options.set_description("test description");
+
+ td::string exename = "exename";
+ td::vector<td::string> args;
+ auto run_option_parser = [&](td::string command_line) {
+ args = td::full_split(std::move(command_line), ' ');
+ td::vector<char *> argv;
+ argv.push_back(&exename[0]);
+ for (auto &arg : args) {
+ argv.push_back(&arg[0]);
+ }
+ return options.run_impl(static_cast<int>(argv.size()), &argv[0], -1);
+ };
+
+ td::uint64 chosen_options = 0;
+ td::vector<td::string> chosen_parameters;
+ auto test_success = [&](td::string command_line, td::uint64 expected_options,
+ const td::vector<td::string> &expected_parameters,
+ const td::vector<td::string> &expected_result) {
+ chosen_options = 0;
+ chosen_parameters.clear();
+ auto result = run_option_parser(std::move(command_line));
+ ASSERT_TRUE(result.is_ok());
+ ASSERT_EQ(expected_options, chosen_options);
+ ASSERT_EQ(expected_parameters, chosen_parameters);
+ ASSERT_EQ(expected_result.size(), result.ok().size());
+ for (size_t i = 0; i < expected_result.size(); i++) {
+ ASSERT_STREQ(expected_result[i], td::string(result.ok()[i]));
+ }
+ };
+ auto test_fail = [&](td::string command_line) {
+ auto result = run_option_parser(std::move(command_line));
+ ASSERT_TRUE(result.is_error());
+ };
+
+ options.add_option('q', "", "", [&] { chosen_options += 1; });
+ options.add_option('\0', "http-port2", "", [&] { chosen_options += 10; });
+ options.add_option('p', "http-port", "", [&](td::Slice parameter) {
+ chosen_options += 100;
+ chosen_parameters.push_back(parameter.str());
+ });
+ options.add_option('v', "test", "", [&] { chosen_options += 1000; });
+
+ test_fail("-http-port2");
+ test_success("-", 0, {}, {"-"});
+ test_fail("--http-port");
+ test_fail("--http-port3");
+ test_fail("--http-por");
+ test_fail("--http-port2=1");
+ test_fail("--q");
+ test_fail("-qvp");
+ test_fail("-p");
+ test_fail("-u");
+ test_success("-q", 1, {}, {});
+ test_success("-vvvvvvvvvv", 10000, {}, {});
+ test_success("-qpv", 101, {"v"}, {});
+ test_success("-qp -v", 101, {"-v"}, {});
+ test_success("-qp --http-port2", 101, {"--http-port2"}, {});
+ test_success("-qp -- -v", 1101, {"--"}, {});
+ test_success("-qvqvpqv", 2102, {"qv"}, {});
+ test_success("aba --http-port2 caba --http-port2 dabacaba", 20, {}, {"aba", "caba", "dabacaba"});
+ test_success("das -pqwerty -- -v asd --http-port", 100, {"qwerty"}, {"das", "-v", "asd", "--http-port"});
+ test_success("-p option --http-port option2 --http-port=option3 --http-port=", 400,
+ {"option", "option2", "option3", ""}, {});
+ test_success("", 0, {}, {});
+ test_success("a", 0, {}, {"a"});
+ test_success("", 0, {}, {});
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/OrderedEventsProcessor.cpp b/protocols/Telegram/tdlib/td/tdutils/test/OrderedEventsProcessor.cpp
index 6a5a20015f..c5c963bedc 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/OrderedEventsProcessor.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/OrderedEventsProcessor.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -18,8 +18,8 @@ TEST(OrderedEventsProcessor, random) {
int offset = 1000000;
std::vector<std::pair<int, int>> v;
for (int i = 0; i < n; i++) {
- auto shift = td::Random::fast(0, 1) ? td::Random::fast(0, d) : td::Random::fast(0, 1) * d;
- v.push_back({i + shift, i + offset});
+ auto shift = td::Random::fast_bool() ? td::Random::fast(0, d) : td::Random::fast(0, 1) * d;
+ v.emplace_back(i + shift, i + offset);
}
std::sort(v.begin(), v.end());
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/SharedObjectPool.cpp b/protocols/Telegram/tdlib/td/tdutils/test/SharedObjectPool.cpp
index 61d956f4e6..a4762e25f3 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/SharedObjectPool.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/SharedObjectPool.cpp
@@ -1,10 +1,10 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/logging.h"
+#include "td/utils/common.h"
#include "td/utils/SharedObjectPool.h"
#include "td/utils/tests.h"
@@ -56,7 +56,16 @@ TEST(SharedPtr, simple) {
ptr2 = std::move(ptr);
CHECK(ptr.empty());
CHECK(*ptr2 == "hello");
+#if TD_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunknown-pragmas"
+#pragma clang diagnostic ignored "-Wunknown-warning-option"
+#pragma clang diagnostic ignored "-Wself-assign-overloaded"
+#endif
ptr2 = ptr2;
+#if TD_CLANG
+#pragma clang diagnostic pop
+#endif
CHECK(*ptr2 == "hello");
CHECK(!Deleter::was_delete());
ptr2.reset();
@@ -80,15 +89,15 @@ TEST(SharedObjectPool, simple) {
};
{
td::SharedObjectPool<Node> pool;
- pool.alloc();
- pool.alloc();
- pool.alloc();
- pool.alloc();
- pool.alloc();
+ { auto ptr1 = pool.alloc(); }
+ { auto ptr2 = pool.alloc(); }
+ { auto ptr3 = pool.alloc(); }
+ { auto ptr4 = pool.alloc(); }
+ { auto ptr5 = pool.alloc(); }
CHECK(Node::cnt() == 0);
CHECK(pool.total_size() == 1);
CHECK(pool.calc_free_size() == 1);
- pool.alloc(), pool.alloc(), pool.alloc();
+ { auto ptr6 = pool.alloc(), ptr7 = pool.alloc(), ptr8 = pool.alloc(); }
CHECK(pool.total_size() == 3);
CHECK(pool.calc_free_size() == 3);
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/SharedSlice.cpp b/protocols/Telegram/tdlib/td/tdutils/test/SharedSlice.cpp
new file mode 100644
index 0000000000..7327f0dbb3
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/SharedSlice.cpp
@@ -0,0 +1,91 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/common.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/SharedSlice.h"
+#include "td/utils/tests.h"
+
+char disable_linker_warning_about_empty_file_tdutils_test_shared_slice_cpp TD_UNUSED;
+
+#if !TD_THREAD_UNSUPPORTED
+TEST(SharedSlice, Hands) {
+ {
+ td::SharedSlice h("hello");
+ ASSERT_EQ("hello", h.as_slice());
+ // auto g = h; // CE
+ auto g = h.clone();
+ ASSERT_EQ("hello", h.as_slice());
+ ASSERT_EQ("hello", g.as_slice());
+ }
+
+ {
+ td::SharedSlice h("hello");
+ td::UniqueSharedSlice g(std::move(h));
+ ASSERT_EQ("", h.as_slice());
+ ASSERT_EQ("hello", g.as_slice());
+ }
+ {
+ td::SharedSlice h("hello");
+ td::SharedSlice t = h.clone();
+ td::UniqueSharedSlice g(std::move(h));
+ ASSERT_EQ("", h.as_slice());
+ ASSERT_EQ("hello", g.as_slice());
+ ASSERT_EQ("hello", t.as_slice());
+ }
+
+ {
+ td::UniqueSharedSlice g(5);
+ g.as_mutable_slice().copy_from("hello");
+ td::SharedSlice h(std::move(g));
+ ASSERT_EQ("hello", h);
+ ASSERT_EQ("", g);
+ }
+
+ {
+ td::UniqueSlice h("hello");
+ td::UniqueSlice g(std::move(h));
+ ASSERT_EQ("", h.as_slice());
+ ASSERT_EQ("hello", g.as_slice());
+ }
+
+ {
+ td::SecureString h("hello");
+ td::SecureString g(std::move(h));
+ ASSERT_EQ("", h.as_slice());
+ ASSERT_EQ("hello", g.as_slice());
+ }
+
+ {
+ td::Stage stage;
+ td::SharedSlice a;
+ td::SharedSlice b;
+ td::vector<td::thread> threads(2);
+ for (int i = 0; i < 2; i++) {
+ threads[i] = td::thread([i, &stage, &a, &b] {
+ for (int j = 0; j < 10000; j++) {
+ if (i == 0) {
+ a = td::SharedSlice("hello");
+ b = a.clone();
+ }
+ stage.wait((2 * j + 1) * 2);
+ if (i == 0) {
+ ASSERT_EQ('h', a[0]);
+ a.clear();
+ } else {
+ td::UniqueSharedSlice c(std::move(b));
+ c.as_mutable_slice()[0] = '!';
+ }
+ stage.wait((2 * j + 2) * 2);
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+ }
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/StealingQueue.cpp b/protocols/Telegram/tdlib/td/tdutils/test/StealingQueue.cpp
new file mode 100644
index 0000000000..453a63179f
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/StealingQueue.cpp
@@ -0,0 +1,180 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/AtomicRead.h"
+#include "td/utils/benchmark.h"
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/MpmcQueue.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Random.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/StealingQueue.h"
+#include "td/utils/tests.h"
+
+#include <atomic>
+#include <cstring>
+
+TEST(StealingQueue, very_simple) {
+ td::StealingQueue<int, 8> q;
+ q.local_push(1, [](auto x) { UNREACHABLE(); });
+ int x;
+ CHECK(q.local_pop(x));
+ ASSERT_EQ(1, x);
+}
+
+#if !TD_THREAD_UNSUPPORTED
+TEST(AtomicRead, simple) {
+ td::Stage run;
+ td::Stage check;
+
+ std::size_t threads_n = 10;
+ td::vector<td::thread> threads;
+
+ int x{0};
+ std::atomic<int> version{0};
+
+ td::int64 res = 0;
+ for (std::size_t i = 0; i < threads_n; i++) {
+ threads.emplace_back([&, id = static_cast<td::uint32>(i)] {
+ for (td::uint64 round = 1; round < 10000; round++) {
+ run.wait(round * threads_n);
+ if (id == 0) {
+ version++;
+ x++;
+ version++;
+ } else {
+ int y = 0;
+ auto v1 = version.load();
+ y = x;
+ auto v2 = version.load();
+ if (v1 == v2 && v1 % 2 == 0) {
+ res += y;
+ }
+ }
+
+ check.wait(round * threads_n);
+ }
+ });
+ }
+ td::do_not_optimize_away(res);
+ for (auto &thread : threads) {
+ thread.join();
+ }
+}
+
+TEST(AtomicRead, simple2) {
+ td::Stage run;
+ td::Stage check;
+
+ std::size_t threads_n = 10;
+ td::vector<td::thread> threads;
+
+ struct Value {
+ td::uint64 value = 0;
+ char str[50] = "0 0 0 0";
+ };
+ td::AtomicRead<Value> value;
+
+ auto to_str = [](td::uint64 i) {
+ return PSTRING() << i << " " << i << " " << i << " " << i;
+ };
+ for (std::size_t i = 0; i < threads_n; i++) {
+ threads.emplace_back([&, id = static_cast<td::uint32>(i)] {
+ for (td::uint64 round = 1; round < 10000; round++) {
+ run.wait(round * threads_n);
+ if (id == 0) {
+ auto x = value.lock();
+ x->value = round;
+ auto str = to_str(round);
+ std::memcpy(x->str, str.c_str(), str.size() + 1);
+ } else {
+ Value x;
+ value.read(x);
+ LOG_CHECK(x.value == round || x.value == round - 1) << x.value << " " << round;
+ CHECK(x.str == to_str(x.value));
+ }
+ check.wait(round * threads_n);
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+}
+
+TEST(StealingQueue, simple) {
+ td::uint64 sum = 0;
+ std::atomic<td::uint64> got_sum{0};
+
+ td::Stage run;
+ td::Stage check;
+
+ std::size_t threads_n = 10;
+ td::vector<td::thread> threads;
+ td::vector<td::StealingQueue<int, 8>> lq(threads_n);
+ td::MpmcQueue<int> gq(threads_n);
+
+ constexpr td::uint64 XN = 20;
+ td::uint64 x_sum[XN];
+ x_sum[0] = 0;
+ x_sum[1] = 1;
+ for (td::uint64 i = 2; i < XN; i++) {
+ x_sum[i] = i + x_sum[i - 1] + x_sum[i - 2];
+ }
+
+ td::Random::Xorshift128plus rnd(123);
+ for (std::size_t i = 0; i < threads_n; i++) {
+ threads.emplace_back([&, id = static_cast<td::uint32>(i)] {
+ for (td::uint64 round = 1; round < 1000; round++) {
+ if (id == 0) {
+ sum = 0;
+ auto n = static_cast<int>(rnd() % 5);
+ for (int j = 0; j < n; j++) {
+ auto x = static_cast<int>(rnd() % XN);
+ sum += x_sum[x];
+ gq.push(x, id);
+ }
+ got_sum = 0;
+ }
+ run.wait(round * threads_n);
+ while (got_sum.load() != sum) {
+ auto x = [&] {
+ int res;
+ if (lq[id].local_pop(res)) {
+ return res;
+ }
+ if (gq.try_pop(res, id)) {
+ return res;
+ }
+ if (lq[id].steal(res, lq[static_cast<size_t>(rnd()) % threads_n])) {
+ //LOG(ERROR) << "STEAL";
+ return res;
+ }
+ return 0;
+ }();
+ if (x == 0) {
+ continue;
+ }
+ //LOG(ERROR) << x << " " << got_sum.load() << " " << sum;
+ got_sum.fetch_add(x, std::memory_order_relaxed);
+ lq[id].local_push(x - 1, [&](auto y) {
+ //LOG(ERROR) << "OVERFLOW";
+ gq.push(y, id);
+ });
+ if (x > 1) {
+ lq[id].local_push(x - 2, [&](auto y) { gq.push(y, id); });
+ }
+ }
+ check.wait(round * threads_n);
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashMap.cpp b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashMap.cpp
new file mode 100644
index 0000000000..38def77772
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashMap.cpp
@@ -0,0 +1,95 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/Random.h"
+#include "td/utils/tests.h"
+#include "td/utils/WaitFreeHashMap.h"
+
+TEST(WaitFreeHashMap, stress_test) {
+ td::Random::Xorshift128plus rnd(123);
+ td::FlatHashMap<td::uint64, td::uint64> reference;
+ td::WaitFreeHashMap<td::uint64, td::uint64> map;
+
+ td::vector<td::RandomSteps::Step> steps;
+ auto add_step = [&](td::uint32 weight, auto f) {
+ steps.emplace_back(td::RandomSteps::Step{std::move(f), weight});
+ };
+
+ auto gen_key = [&] {
+ return rnd() % 100000 + 1;
+ };
+
+ auto check = [&](bool check_size = false) {
+ if (check_size) {
+ ASSERT_EQ(reference.size(), map.calc_size());
+ }
+ ASSERT_EQ(reference.empty(), map.empty());
+
+ if (reference.size() < 100) {
+ td::uint64 result = 0;
+ for (auto &it : reference) {
+ result += it.first * 101;
+ result += it.second;
+ }
+ map.foreach([&](const td::uint64 &key, td::uint64 &value) {
+ result -= key * 101;
+ result -= value;
+ });
+ ASSERT_EQ(0u, result);
+ }
+ };
+
+ add_step(2000, [&] {
+ auto key = gen_key();
+ auto value = rnd();
+ reference[key] = value;
+ if (td::Random::fast_bool()) {
+ map.set(key, value);
+ } else {
+ map[key] = value;
+ }
+ ASSERT_EQ(reference[key], map.get(key));
+ check();
+ });
+
+ add_step(200, [&] {
+ auto key = gen_key();
+ ASSERT_EQ(reference[key], map[key]);
+ check(true);
+ });
+
+ add_step(2000, [&] {
+ auto key = gen_key();
+ auto ref_it = reference.find(key);
+ auto ref_value = ref_it == reference.end() ? 0 : ref_it->second;
+ ASSERT_EQ(ref_value, map.get(key));
+ check();
+ });
+
+ add_step(500, [&] {
+ auto key = gen_key();
+ size_t reference_erased_count = reference.erase(key);
+ size_t map_erased_count = map.erase(key);
+ ASSERT_EQ(reference_erased_count, map_erased_count);
+ check();
+ });
+
+ td::RandomSteps runner(std::move(steps));
+ for (size_t i = 0; i < 1000000; i++) {
+ runner.step(rnd);
+ }
+
+ for (size_t test = 0; test < 1000; test++) {
+ reference = {};
+ map = {};
+
+ for (size_t i = 0; i < 100; i++) {
+ runner.step(rnd);
+ }
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashSet.cpp b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashSet.cpp
new file mode 100644
index 0000000000..ec4096c850
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashSet.cpp
@@ -0,0 +1,73 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/common.h"
+#include "td/utils/FlatHashSet.h"
+#include "td/utils/Random.h"
+#include "td/utils/tests.h"
+#include "td/utils/WaitFreeHashSet.h"
+
+TEST(WaitFreeHashSet, stress_test) {
+ td::Random::Xorshift128plus rnd(123);
+ td::FlatHashSet<td::uint64> reference;
+ td::WaitFreeHashSet<td::uint64> set;
+
+ td::vector<td::RandomSteps::Step> steps;
+ auto add_step = [&](td::uint32 weight, auto f) {
+ steps.emplace_back(td::RandomSteps::Step{std::move(f), weight});
+ };
+
+ auto gen_key = [&] {
+ return rnd() % 100000 + 1;
+ };
+
+ auto check = [&](bool check_size = false) {
+ if (check_size) {
+ ASSERT_EQ(reference.size(), set.calc_size());
+ }
+ ASSERT_EQ(reference.empty(), set.empty());
+
+ if (reference.size() < 100) {
+ td::uint64 result = 0;
+ for (auto &it : reference) {
+ result += it * 101;
+ }
+ set.foreach([&](const td::uint64 &key) { result -= key * 101; });
+ ASSERT_EQ(0u, result);
+ }
+ };
+
+ add_step(2000, [&] {
+ auto key = gen_key();
+ ASSERT_EQ(reference.count(key), set.count(key));
+ reference.insert(key);
+ set.insert(key);
+ ASSERT_EQ(reference.count(key), set.count(key));
+ check();
+ });
+
+ add_step(500, [&] {
+ auto key = gen_key();
+ size_t reference_erased_count = reference.erase(key);
+ size_t set_erased_count = set.erase(key);
+ ASSERT_EQ(reference_erased_count, set_erased_count);
+ check();
+ });
+
+ td::RandomSteps runner(std::move(steps));
+ for (size_t i = 0; i < 1000000; i++) {
+ runner.step(rnd);
+ }
+
+ for (size_t test = 0; test < 1000; test++) {
+ reference = {};
+ set = {};
+
+ for (size_t i = 0; i < 100; i++) {
+ runner.step(rnd);
+ }
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeVector.cpp b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeVector.cpp
new file mode 100644
index 0000000000..0f0cc58796
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeVector.cpp
@@ -0,0 +1,69 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/common.h"
+#include "td/utils/Random.h"
+#include "td/utils/tests.h"
+#include "td/utils/WaitFreeVector.h"
+
+TEST(WaitFreeVector, stress_test) {
+ td::Random::Xorshift128plus rnd(123);
+ td::vector<td::uint64> reference;
+ td::WaitFreeVector<td::uint64> vector;
+
+ td::vector<td::RandomSteps::Step> steps;
+ auto add_step = [&](td::uint32 weight, auto f) {
+ steps.emplace_back(td::RandomSteps::Step{std::move(f), weight});
+ };
+
+ auto gen_key = [&] {
+ return static_cast<size_t>(rnd() % reference.size());
+ };
+
+ add_step(2000, [&] {
+ ASSERT_EQ(reference.size(), vector.size());
+ ASSERT_EQ(reference.empty(), vector.empty());
+ if (reference.empty()) {
+ return;
+ }
+ auto key = gen_key();
+ ASSERT_EQ(reference[key], vector[key]);
+ auto value = rnd();
+ reference[key] = value;
+ vector[key] = value;
+ ASSERT_EQ(reference[key], vector[key]);
+ });
+
+ add_step(2000, [&] {
+ ASSERT_EQ(reference.size(), vector.size());
+ ASSERT_EQ(reference.empty(), vector.empty());
+ auto value = rnd();
+ reference.emplace_back(value);
+ if (rnd() & 1) {
+ vector.emplace_back(value);
+ } else if (rnd() & 1) {
+ vector.push_back(value);
+ } else {
+ vector.push_back(std::move(value));
+ }
+ ASSERT_EQ(reference.back(), vector.back());
+ });
+
+ add_step(500, [&] {
+ ASSERT_EQ(reference.size(), vector.size());
+ ASSERT_EQ(reference.empty(), vector.empty());
+ if (reference.empty()) {
+ return;
+ }
+ reference.pop_back();
+ vector.pop_back();
+ });
+
+ td::RandomSteps runner(std::move(steps));
+ for (size_t i = 0; i < 1000000; i++) {
+ runner.step(rnd);
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/bitmask.cpp b/protocols/Telegram/tdlib/td/tdutils/test/bitmask.cpp
new file mode 100644
index 0000000000..e81c406bbe
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/bitmask.cpp
@@ -0,0 +1,249 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/tests.h"
+#include "td/utils/utf8.h"
+
+#include <algorithm>
+
+namespace td {
+
+class RangeSet {
+ template <class T>
+ static auto find(T &ranges, int64 begin) {
+ return std::lower_bound(ranges.begin(), ranges.end(), begin,
+ [](const Range &range, int64 begin) { return range.end < begin; });
+ }
+ auto find(int64 begin) const {
+ return find(ranges_, begin);
+ }
+ auto find(int64 begin) {
+ return find(ranges_, begin);
+ }
+
+ public:
+ struct Range {
+ int64 begin;
+ int64 end;
+ };
+
+ static constexpr int64 BIT_SIZE = 1024;
+
+ static RangeSet create_one_range(int64 end, int64 begin = 0) {
+ RangeSet res;
+ res.ranges_.push_back({begin, end});
+ return res;
+ }
+ static Result<RangeSet> decode(CSlice data) {
+ if (!check_utf8(data)) {
+ return Status::Error("Invalid encoding");
+ }
+ uint32 curr = 0;
+ bool is_empty = false;
+ RangeSet res;
+ for (auto begin = data.ubegin(); begin != data.uend();) {
+ uint32 size;
+ begin = next_utf8_unsafe(begin, &size);
+
+ if (!is_empty && size != 0) {
+ res.ranges_.push_back({curr * BIT_SIZE, (curr + size) * BIT_SIZE});
+ }
+ curr += size;
+ is_empty = !is_empty;
+ }
+ return res;
+ }
+
+ string encode(int64 prefix_size = -1) const {
+ vector<uint32> sizes;
+ uint32 all_end = 0;
+
+ if (prefix_size != -1) {
+ prefix_size = (prefix_size + BIT_SIZE - 1) / BIT_SIZE * BIT_SIZE;
+ }
+ for (auto it : ranges_) {
+ if (prefix_size != -1 && it.begin >= prefix_size) {
+ break;
+ }
+ if (prefix_size != -1 && it.end > prefix_size) {
+ it.end = prefix_size;
+ }
+
+ CHECK(it.begin % BIT_SIZE == 0);
+ CHECK(it.end % BIT_SIZE == 0);
+ auto begin = narrow_cast<uint32>(it.begin / BIT_SIZE);
+ auto end = narrow_cast<uint32>(it.end / BIT_SIZE);
+ if (sizes.empty()) {
+ if (begin != 0) {
+ sizes.push_back(0);
+ sizes.push_back(begin);
+ }
+ } else {
+ sizes.push_back(begin - all_end);
+ }
+ sizes.push_back(end - begin);
+ all_end = end;
+ }
+
+ string res;
+ for (auto c : sizes) {
+ append_utf8_character(res, c);
+ }
+ return res;
+ }
+
+ int64 get_ready_prefix_size(int64 offset, int64 file_size = -1) const {
+ auto it = find(offset);
+ if (it == ranges_.end()) {
+ return 0;
+ }
+ if (it->begin > offset) {
+ return 0;
+ }
+ CHECK(offset >= it->begin);
+ CHECK(offset <= it->end);
+ auto end = it->end;
+ if (file_size != -1 && end > file_size) {
+ end = file_size;
+ }
+ if (end < offset) {
+ return 0;
+ }
+ return end - offset;
+ }
+ int64 get_total_size(int64 file_size) const {
+ int64 res = 0;
+ for (auto it : ranges_) {
+ if (it.begin >= file_size) {
+ break;
+ }
+ if (it.end > file_size) {
+ it.end = file_size;
+ }
+ res += it.end - it.begin;
+ }
+ return res;
+ }
+ int64 get_ready_parts(int64 offset_part, int32 part_size) const {
+ auto offset = offset_part * part_size;
+ auto it = find(offset);
+ if (it == ranges_.end()) {
+ return 0;
+ }
+ if (it->begin > offset) {
+ return 0;
+ }
+ return (it->end - offset) / part_size;
+ }
+
+ bool is_ready(int64 begin, int64 end) const {
+ auto it = find(begin);
+ if (it == ranges_.end()) {
+ return false;
+ }
+ return it->begin <= begin && end <= it->end;
+ }
+
+ void set(int64 begin, int64 end) {
+ CHECK(begin % BIT_SIZE == 0);
+ CHECK(end % BIT_SIZE == 0);
+ // 1. skip all with r.end < begin
+ auto it_begin = find(begin);
+
+ // 2. combine with all r.begin <= end
+ auto it_end = it_begin;
+ for (; it_end != ranges_.end() && it_end->begin <= end; ++it_end) {
+ }
+
+ if (it_begin == it_end) {
+ ranges_.insert(it_begin, Range{begin, end});
+ } else {
+ begin = td::min(begin, it_begin->begin);
+ --it_end;
+ end = td::max(end, it_end->end);
+ *it_end = Range{begin, end};
+ ranges_.erase(it_begin, it_end);
+ }
+ }
+
+ vector<int32> as_vector(int32 part_size) const {
+ vector<int32> res;
+ for (const auto &it : ranges_) {
+ auto begin = narrow_cast<int32>((it.begin + part_size - 1) / part_size);
+ auto end = narrow_cast<int32>(it.end / part_size);
+ while (begin < end) {
+ res.push_back(begin++);
+ }
+ }
+ return res;
+ }
+
+ private:
+ vector<Range> ranges_;
+};
+
+TEST(Bitmask, simple) {
+ auto validate_encoding = [](auto &rs) {
+ auto str = rs.encode();
+ RangeSet rs2 = RangeSet::decode(str).move_as_ok();
+ auto str2 = rs2.encode();
+ rs = std::move(rs2);
+ CHECK(str2 == str);
+ };
+ {
+ RangeSet rs;
+ int32 S = 128 * 1024;
+ int32 O = S * 5000;
+ for (int i = 1; i < 30; i++) {
+ if (i % 2 == 0) {
+ rs.set(O + S * i, O + S * (i + 1));
+ }
+ }
+ validate_encoding(rs);
+ }
+ {
+ RangeSet rs;
+ int32 S = 1024;
+ auto get = [&](auto p) {
+ return rs.get_ready_prefix_size(p * S) / S;
+ };
+ auto set = [&](auto l, auto r) {
+ rs.set(l * S, r * S);
+ validate_encoding(rs);
+ ASSERT_TRUE(rs.is_ready(l * S, r * S));
+ ASSERT_TRUE(get(l) >= (r - l));
+ };
+ set(6, 7);
+ ASSERT_EQ(1, get(6));
+ ASSERT_EQ(0, get(5));
+ set(8, 9);
+ ASSERT_EQ(0, get(7));
+ set(7, 8);
+ ASSERT_EQ(2, get(7));
+ ASSERT_EQ(3, get(6));
+ set(3, 5);
+ ASSERT_EQ(1, get(4));
+ set(4, 6);
+ ASSERT_EQ(5, get(4));
+ set(10, 11);
+ set(9, 10);
+ ASSERT_EQ(8, get(3));
+ set(14, 16);
+ set(12, 13);
+ ASSERT_EQ(8, get(3));
+
+ ASSERT_EQ(10, rs.get_ready_prefix_size(S * 3, S * 3 + 10));
+ ASSERT_TRUE(!rs.is_ready(S * 11, S * 12));
+ ASSERT_EQ(3, rs.get_ready_parts(2, S * 2));
+ ASSERT_EQ(vector<int32>({2, 3, 4, 7}), rs.as_vector(S * 2));
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/buffer.cpp b/protocols/Telegram/tdlib/td/tdutils/test/buffer.cpp
new file mode 100644
index 0000000000..4bc406cc64
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/buffer.cpp
@@ -0,0 +1,56 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/tests.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/Random.h"
+
+TEST(Buffer, buffer_builder) {
+ {
+ td::BufferBuilder builder;
+ builder.append("b");
+ builder.prepend("a");
+ builder.append("c");
+ ASSERT_EQ(builder.extract().as_slice(), "abc");
+ }
+ {
+ td::BufferBuilder builder{"hello", 0, 0};
+ ASSERT_EQ(builder.extract().as_slice(), "hello");
+ }
+ {
+ td::BufferBuilder builder{"hello", 1, 1};
+ builder.prepend("A ");
+ builder.append(" B");
+ ASSERT_EQ(builder.extract().as_slice(), "A hello B");
+ }
+ {
+ auto str = td::rand_string('a', 'z', 10000);
+ auto split_str = td::rand_split(str);
+
+ int l = td::Random::fast(0, static_cast<int>(split_str.size() - 1));
+ int r = l;
+ td::BufferBuilder builder(split_str[l], 123, 1000);
+ while (l != 0 || r != static_cast<int>(split_str.size()) - 1) {
+ if (l == 0 || (td::Random::fast_bool() && r != static_cast<int>(split_str.size() - 1))) {
+ r++;
+ if (td::Random::fast_bool()) {
+ builder.append(split_str[r]);
+ } else {
+ builder.append(td::BufferSlice(split_str[r]));
+ }
+ } else {
+ l--;
+ if (td::Random::fast_bool()) {
+ builder.prepend(split_str[l]);
+ } else {
+ builder.prepend(td::BufferSlice(split_str[l]));
+ }
+ }
+ }
+ ASSERT_EQ(builder.extract().as_slice(), str);
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/crypto.cpp b/protocols/Telegram/tdlib/td/tdutils/test/crypto.cpp
index faf4ef61a4..9e81ef132c 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/crypto.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/crypto.cpp
@@ -1,20 +1,47 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/base64.h"
+#include "td/utils/benchmark.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
+#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/tests.h"
+#include "td/utils/UInt.h"
#include <limits>
static td::vector<td::string> strings{"", "1", "short test string", td::string(1000000, 'a')};
#if TD_HAVE_OPENSSL
+#if TD_HAVE_ZLIB
+TEST(Crypto, Aes) {
+ td::Random::Xorshift128plus rnd(123);
+ td::UInt256 key;
+ rnd.bytes(as_slice(key));
+ td::string plaintext(16, '\0');
+ td::string encrypted(16, '\0');
+ td::string decrypted(16, '\0');
+ rnd.bytes(plaintext);
+
+ td::AesState encryptor;
+ encryptor.init(as_slice(key), true);
+ td::AesState decryptor;
+ decryptor.init(as_slice(key), false);
+
+ encryptor.encrypt(td::as_slice(plaintext).ubegin(), td::as_slice(encrypted).ubegin(), 16);
+ decryptor.decrypt(td::as_slice(encrypted).ubegin(), td::as_slice(decrypted).ubegin(), 16);
+
+ CHECK(decrypted == plaintext);
+ CHECK(decrypted != encrypted);
+ CHECK(td::crc32(encrypted) == 178892237);
+}
+
TEST(Crypto, AesCtrState) {
td::vector<td::uint32> answers1{0u, 1141589763u, 596296607u, 3673001485u, 2302125528u,
330967191u, 2047392231u, 3537459563u, 307747798u, 2149598133u};
@@ -42,46 +69,177 @@ TEST(Crypto, AesCtrState) {
}
td::AesCtrState state;
- state.init(key, iv);
+ state.init(as_slice(key), as_slice(iv));
td::string t(length, '\0');
- state.encrypt(s, t);
+ std::size_t pos = 0;
+ for (const auto &str : td::rand_split(td::string(length, '\0'))) {
+ auto len = str.size();
+ state.encrypt(td::Slice(s).substr(pos, len), td::MutableSlice(t).substr(pos, len));
+ pos += len;
+ }
ASSERT_EQ(answers1[i], td::crc32(t));
- state.init(key, iv);
- state.decrypt(t, t);
- ASSERT_STREQ(s, t);
+ state.init(as_slice(key), as_slice(iv));
+ pos = 0;
+ for (const auto &str : td::rand_split(td::string(length, '\0'))) {
+ auto len = str.size();
+ state.decrypt(td::Slice(t).substr(pos, len), td::MutableSlice(t).substr(pos, len));
+ pos += len;
+ }
+ ASSERT_STREQ(td::base64_encode(s), td::base64_encode(t));
for (auto &c : iv.raw) {
c = 0xFF;
}
- state.init(key, iv);
- state.encrypt(s, t);
+ state.init(as_slice(key), as_slice(iv));
+ pos = 0;
+ for (const auto &str : td::rand_split(td::string(length, '\0'))) {
+ auto len = str.size();
+ state.encrypt(td::Slice(s).substr(pos, len), td::MutableSlice(t).substr(pos, len));
+ pos += len;
+ }
ASSERT_EQ(answers2[i], td::crc32(t));
i++;
}
}
+TEST(Crypto, AesIgeState) {
+ td::vector<td::uint32> answers1{0u, 2045698207u, 2423540300u, 525522475u, 1545267325u, 724143417u};
+
+ std::size_t i = 0;
+ for (auto length : {0, 16, 32, 256, 1024, 65536}) {
+ td::uint32 seed = length;
+ td::string s(length, '\0');
+ for (auto &c : s) {
+ seed = seed * 123457567u + 987651241u;
+ c = static_cast<char>((seed >> 23) & 255);
+ }
+
+ td::UInt256 key;
+ for (auto &c : key.raw) {
+ seed = seed * 123457567u + 987651241u;
+ c = (seed >> 23) & 255;
+ }
+ td::UInt256 iv;
+ for (auto &c : iv.raw) {
+ seed = seed * 123457567u + 987651241u;
+ c = (seed >> 23) & 255;
+ }
+
+ td::AesIgeState state;
+ state.init(as_slice(key), as_slice(iv), true);
+ td::string t(length, '\0');
+ td::UInt256 iv_copy = iv;
+ td::string u(length, '\0');
+ std::size_t pos = 0;
+ for (const auto &str : td::rand_split(td::string(length / 16, '\0'))) {
+ auto len = 16 * str.size();
+ state.encrypt(td::Slice(s).substr(pos, len), td::MutableSlice(t).substr(pos, len));
+ td::aes_ige_encrypt(as_slice(key), as_slice(iv_copy), td::Slice(s).substr(pos, len),
+ td::MutableSlice(u).substr(pos, len));
+ pos += len;
+ }
+
+ ASSERT_EQ(answers1[i], td::crc32(t));
+ ASSERT_EQ(answers1[i], td::crc32(u));
+
+ state.init(as_slice(key), as_slice(iv), false);
+ iv_copy = iv;
+ pos = 0;
+ for (const auto &str : td::rand_split(td::string(length / 16, '\0'))) {
+ auto len = 16 * str.size();
+ state.decrypt(td::Slice(t).substr(pos, len), td::MutableSlice(t).substr(pos, len));
+ td::aes_ige_decrypt(as_slice(key), as_slice(iv_copy), td::Slice(u).substr(pos, len),
+ td::MutableSlice(u).substr(pos, len));
+ pos += len;
+ }
+ ASSERT_STREQ(td::base64_encode(s), td::base64_encode(t));
+ ASSERT_STREQ(td::base64_encode(s), td::base64_encode(u));
+
+ i++;
+ }
+}
+
+TEST(Crypto, AesCbcState) {
+ td::vector<td::uint32> answers1{0u, 3617355989u, 3449188102u, 186999968u, 4244808847u, 2626031206u};
+
+ std::size_t i = 0;
+ for (auto length : {0, 16, 32, 256, 1024, 65536}) {
+ td::uint32 seed = length;
+ td::string s(length, '\0');
+ for (auto &c : s) {
+ seed = seed * 123457567u + 987651241u;
+ c = static_cast<char>((seed >> 23) & 255);
+ }
+
+ td::UInt256 key;
+ for (auto &c : key.raw) {
+ seed = seed * 123457567u + 987651241u;
+ c = (seed >> 23) & 255;
+ }
+ td::UInt128 iv;
+ for (auto &c : iv.raw) {
+ seed = seed * 123457567u + 987651241u;
+ c = (seed >> 23) & 255;
+ }
+
+ td::AesCbcState state(as_slice(key), as_slice(iv));
+ td::string t(length, '\0');
+ td::UInt128 iv_copy = iv;
+ td::string u(length, '\0');
+ std::size_t pos = 0;
+ for (const auto &str : td::rand_split(td::string(length / 16, '\0'))) {
+ auto len = 16 * str.size();
+ state.encrypt(td::Slice(s).substr(pos, len), td::MutableSlice(t).substr(pos, len));
+ td::aes_cbc_encrypt(as_slice(key), as_slice(iv_copy), td::Slice(s).substr(pos, len),
+ td::MutableSlice(u).substr(pos, len));
+ pos += len;
+ }
+
+ ASSERT_EQ(answers1[i], td::crc32(t));
+ ASSERT_EQ(answers1[i], td::crc32(u));
+
+ state = td::AesCbcState(as_slice(key), as_slice(iv));
+ iv_copy = iv;
+ pos = 0;
+ for (const auto &str : td::rand_split(td::string(length / 16, '\0'))) {
+ auto len = 16 * str.size();
+ state.decrypt(td::Slice(t).substr(pos, len), td::MutableSlice(t).substr(pos, len));
+ td::aes_cbc_decrypt(as_slice(key), as_slice(iv_copy), td::Slice(u).substr(pos, len),
+ td::MutableSlice(u).substr(pos, len));
+ pos += len;
+ }
+ ASSERT_STREQ(td::base64_encode(s), td::base64_encode(t));
+ ASSERT_STREQ(td::base64_encode(s), td::base64_encode(u));
+
+ i++;
+ }
+}
+#endif
+
TEST(Crypto, Sha256State) {
for (auto length : {0, 1, 31, 32, 33, 9999, 10000, 10001, 999999, 1000001}) {
auto s = td::rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), length);
td::UInt256 baseline;
- td::sha256(s, td::MutableSlice(baseline.raw, 32));
+ td::sha256(s, as_slice(baseline));
td::Sha256State state;
- td::sha256_init(&state);
+ state.init();
+ td::Sha256State state2 = std::move(state);
auto v = td::rand_split(s);
for (auto &x : v) {
- td::sha256_update(x, &state);
+ state2.feed(x);
}
+ state = std::move(state2);
td::UInt256 result;
- td::sha256_final(&state, td::MutableSlice(result.raw, 32));
+ state.extract(as_slice(result));
ASSERT_TRUE(baseline == result);
}
}
TEST(Crypto, PBKDF) {
- td::vector<td::string> passwords{"", "qwerty", std::string(1000, 'a')};
- td::vector<td::string> salts{"", "qwerty", std::string(1000, 'a')};
+ td::vector<td::string> passwords{"", "qwerty", td::string(1000, 'a')};
+ td::vector<td::string> salts{"", "qwerty", td::string(1000, 'a')};
td::vector<int> iteration_counts{1, 2, 1000};
td::vector<td::Slice> answers{
"984LZT0tcqQQjPWr6RL/3Xd2Ftu7J6cOggTzri0Pb60=", "lzmEEdaupDp3rO+SImq4J41NsGaL0denanJfdoCsRcU=",
@@ -145,6 +303,32 @@ TEST(Crypto, md5) {
ASSERT_STREQ(answers[i], td::base64_encode(output));
}
}
+
+TEST(Crypto, hmac_sha256) {
+ td::vector<td::Slice> answers{
+ "t33rfT85UOe6N00BhsNwobE+f2TnW331HhdvQ4GdJp8=", "BQl5HF2jqhCz4JTqhAs+H364oxboh7QlluOMHuuRVh8=",
+ "NCCPuZBsAPBd/qr3SyeYE+e1RNgzkKJCS/+eXDBw8zU=", "mo3ahTkyLKfoQoYA0s7vRZULuH++vqwFJD0U5n9HHw0="};
+
+ for (std::size_t i = 0; i < strings.size(); i++) {
+ td::string output(32, '\0');
+ td::hmac_sha256("cucumber", strings[i], output);
+ ASSERT_STREQ(answers[i], td::base64_encode(output));
+ }
+}
+
+TEST(Crypto, hmac_sha512) {
+ td::vector<td::Slice> answers{
+ "o28hTN1m/TGlm/VYxDIzOdUE4wMpQzO8hVcTkiP2ezEJXtrOvCjRnl20aOV1S8axA5Te0TzIjfIoEAtpzamIsA==",
+ "32X3GslSz0HDznSrCNt++ePRcFVSUSD+tfOVannyxS+yLt/om11qILCE64RFTS8/B84gByMzC3FuAlfcIam/KA==",
+ "BVqe5rK1Fg1i+C7xXTAzT9vDPcf3kQQpTtse6rT/EVDzKo9AUo4ZwyUyJ0KcLHoffIjul/TuJoBg+wLz7Z7r7g==",
+ "WASmeku5Pcfz7N0Kp4Q3I9sxtO2MiaBXA418CY0HvjdtmAo7QY+K3E0o9UemgGzz41KqeypzRC92MwOAOnXJLA=="};
+
+ for (std::size_t i = 0; i < strings.size(); i++) {
+ td::string output(64, '\0');
+ td::hmac_sha512("cucumber", strings[i], output);
+ ASSERT_STREQ(answers[i], td::base64_encode(output));
+ }
+}
#endif
#if TD_HAVE_ZLIB
@@ -157,6 +341,69 @@ TEST(Crypto, crc32) {
}
#endif
+#if TD_HAVE_CRC32C
+TEST(Crypto, crc32c) {
+ td::vector<td::uint32> answers{0u, 2432014819u, 1077264849u, 1131405888u};
+
+ for (std::size_t i = 0; i < strings.size(); i++) {
+ ASSERT_EQ(answers[i], td::crc32c(strings[i]));
+
+ auto v = td::rand_split(strings[i]);
+ td::uint32 a = 0;
+ td::uint32 b = 0;
+ for (auto &x : v) {
+ a = td::crc32c_extend(a, x);
+ auto x_crc = td::crc32c(x);
+ b = td::crc32c_extend(b, x_crc, x.size());
+ }
+ ASSERT_EQ(answers[i], a);
+ ASSERT_EQ(answers[i], b);
+ }
+}
+
+TEST(Crypto, crc32c_benchmark) {
+ class Crc32cExtendBenchmark final : public td::Benchmark {
+ public:
+ explicit Crc32cExtendBenchmark(size_t chunk_size) : chunk_size_(chunk_size) {
+ }
+ td::string get_description() const final {
+ return PSTRING() << "Crc32c with chunk_size=" << chunk_size_;
+ }
+ void start_up_n(int n) final {
+ if (n > (1 << 20)) {
+ cnt_ = n / (1 << 20);
+ n = (1 << 20);
+ } else {
+ cnt_ = 1;
+ }
+ data_ = td::string(n, 'a');
+ }
+ void run(int n) final {
+ td::uint32 res = 0;
+ for (int i = 0; i < cnt_; i++) {
+ td::Slice data(data_);
+ while (!data.empty()) {
+ auto head = data.substr(0, chunk_size_);
+ data = data.substr(head.size());
+ res = td::crc32c_extend(res, head);
+ }
+ }
+ td::do_not_optimize_away(res);
+ }
+
+ private:
+ size_t chunk_size_;
+ td::string data_;
+ int cnt_;
+ };
+ bench(Crc32cExtendBenchmark(2));
+ bench(Crc32cExtendBenchmark(8));
+ bench(Crc32cExtendBenchmark(32));
+ bench(Crc32cExtendBenchmark(128));
+ bench(Crc32cExtendBenchmark(65536));
+}
+#endif
+
TEST(Crypto, crc64) {
td::vector<td::uint64> answers{0ull, 3039664240384658157ull, 17549519902062861804ull, 8794730974279819706ull};
@@ -164,3 +411,61 @@ TEST(Crypto, crc64) {
ASSERT_EQ(answers[i], td::crc64(strings[i]));
}
}
+
+TEST(Crypto, crc16) {
+ td::vector<td::uint16> answers{0, 9842, 25046, 37023};
+
+ for (std::size_t i = 0; i < strings.size(); i++) {
+ ASSERT_EQ(answers[i], td::crc16(strings[i]));
+ }
+}
+
+static td::Slice rsa_private_key = R"ABCD(
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDeYT5/prmLEa2Q
+tZND+UwTmif8kl2VlXaMCjj1k1lJJq8BqS8cVM2vPnOPzFoiC2LYykhm4kk7goCC
+ZH6wez9yakg28fcq0Ycv0x8DL1K+VKHJuwIhVfQs//IY1/cBOrMESc+NQowPbv1t
+TIFxBO2gebnpLuseht8ix7XtpGC4qAaHN2aEvT2cRsnA76TAK1RVxf1OYGUFBDzY
+318WpVZfVIjcQ7K9+eU6b2Yb84VLlvJXw3e1rvw+fBzx2EjpD4zhXy11YppWDyV6
+HEb2hs3cGS/LbHfHvdcSfil2omaJP97MDEEY2HFxjR/E5CEf2suvPzX4XS3RE+S3
+2aEJaaQbAgMBAAECggEAKo3XRNwls0wNt5xXcvF4smOUdUuY5u/0AHZQUgYBVvM1
+GA9E+ZnsxjUgLgs/0DX3k16aHj39H4sohksuxxy+lmlqKkGBN8tioC85RwW+Qre1
+QgIsNS7ai+XqcQCavrx51z88nV53qNhnXIwAVR1JT6Ubg1i8G1pZxrEKyk/jRlJd
+mGjf6vjitH//PPkghPJ/D42k93YRcy+duOgqYDQpLZp8DiEGfYrX10B1H7HrWLV+
+Wp5KO1YXtKgQUplj6kYy72bVajbxYTvzgjaaKsh74jBO0uT3tHTtXG0dcKGb0VR/
+cqP/1H/lC9bAnAqAGefNusGJQZIElvTsrpIQXOeZsQKBgQD2W04S+FjqYYFjnEFX
+6eL4it01afs5M3/C6CcI5JQtN6p+Na4NCSILol33xwhakn87zqdADHawBYQVQ8Uw
+dPurl805wfkzN3AbfdDmtx0IJ8vK4HFpktRjfpwBVhlVtm1doAYFqqsuCF2vWW1t
+mM2YOSq4AnRHCeBb/P6kRIW0MwKBgQDnFawKKqiC4tuyBOkkEhexlm7x9he0md7D
+3Z2hc3Bmdcq1niw4wBq3HUxGLReGCcSr5epKSQwkunlTn5ZSC6Rmbe4zxsGIwbb3
+5W3342swBaoxEIuBokBvZ/xUOXVwiqKj+S/NzVkZcnT6K9V/HnUCQR+JBbQxFQaX
+iiezcjKoeQKBgCIVUcDoIQ0UPl10ocmy7xbpx177calhSZzCl5vwW9vBptHdRV5C
+VDZ92ThNjgdR205/8b23u7fwm2yBusdQd/0ufFMwVfTTB6yWBI/W56pYLya7VJWB
+nebB/n1k1w53tbvNRugDy7kLqUJ4Qd521ILp7dIVbNbjM+omH2jEnibnAoGBAIM5
+a1jaoJay/M86uqohHBNcuePtO8jzF+1iDAGC7HFCsrov+CzB6mnR2V6AfLtBEM4M
+4d8NXDf/LKawGUy+D72a74m3dG+UkbJ0Nt5t5pB+pwb1vkL/QFgDVOb/OhGOqI01
+FFBqLA6nUIZAHhzxzsBY+u90rb6xkey8J49faiUBAoGAaMgOgEvQB5H19ZL5tMkl
+A/DKtTz/NFzN4Zw/vNPVb7eNn4jg9M25d9xqvL4acOa+nuV3nLHbcUWE1/7STXw1
+gT58CvoEmD1AiP95nup+HKHENJ1DWMgF5MDfVQwGCvWP5/Qy89ybr0eG8HjbldbN
+MpSmzz2wOz152oGdOd3syT4=
+-----END PRIVATE KEY-----
+)ABCD";
+
+static td::Slice rsa_public_key = R"ABCD(
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3mE+f6a5ixGtkLWTQ/lM
+E5on/JJdlZV2jAo49ZNZSSavAakvHFTNrz5zj8xaIgti2MpIZuJJO4KAgmR+sHs/
+cmpINvH3KtGHL9MfAy9SvlShybsCIVX0LP/yGNf3ATqzBEnPjUKMD279bUyBcQTt
+oHm56S7rHobfIse17aRguKgGhzdmhL09nEbJwO+kwCtUVcX9TmBlBQQ82N9fFqVW
+X1SI3EOyvfnlOm9mG/OFS5byV8N3ta78Pnwc8dhI6Q+M4V8tdWKaVg8lehxG9obN
+3Bkvy2x3x73XEn4pdqJmiT/ezAxBGNhxcY0fxOQhH9rLrz81+F0t0RPkt9mhCWmk
+GwIDAQAB
+-----END PUBLIC KEY-----
+)ABCD";
+
+TEST(Crypto, rsa) {
+ auto value = td::rand_string('a', 'z', 200);
+ auto encrypted_value = td::rsa_encrypt_pkcs1_oaep(rsa_public_key, value).move_as_ok();
+ auto decrypted_value = td::rsa_decrypt_pkcs1_oaep(rsa_private_key, encrypted_value.as_slice()).move_as_ok();
+ ASSERT_TRUE(decrypted_value.as_slice().truncate(value.size()) == value);
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/emoji.cpp b/protocols/Telegram/tdlib/td/tdutils/test/emoji.cpp
new file mode 100644
index 0000000000..c8e6539a99
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/emoji.cpp
@@ -0,0 +1,128 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/emoji.h"
+#include "td/utils/tests.h"
+
+TEST(Emoji, is_emoji) {
+ ASSERT_TRUE(!td::is_emoji(""));
+ ASSERT_TRUE(td::is_emoji("👩🏼‍❤‍💋‍👩🏻"));
+ ASSERT_TRUE(td::is_emoji("👩🏼‍❤️‍💋‍👩🏻"));
+ ASSERT_TRUE(!td::is_emoji("👩🏼‍❤️️‍💋‍👩🏻"));
+ ASSERT_TRUE(td::is_emoji("⌚"));
+ ASSERT_TRUE(td::is_emoji("↔"));
+ ASSERT_TRUE(td::is_emoji("🪗"));
+ ASSERT_TRUE(td::is_emoji("2️⃣"));
+ ASSERT_TRUE(td::is_emoji("2⃣"));
+ ASSERT_TRUE(!td::is_emoji(" 2⃣"));
+ ASSERT_TRUE(!td::is_emoji("2⃣ "));
+ ASSERT_TRUE(!td::is_emoji(" "));
+ ASSERT_TRUE(!td::is_emoji(""));
+ ASSERT_TRUE(!td::is_emoji("1234567890123456789012345678901234567890123456789012345678901234567890"));
+ ASSERT_TRUE(td::is_emoji("❤️"));
+ ASSERT_TRUE(td::is_emoji("❤"));
+ ASSERT_TRUE(td::is_emoji("⌚"));
+ ASSERT_TRUE(td::is_emoji("🎄"));
+ ASSERT_TRUE(td::is_emoji("🧑‍🎄"));
+}
+
+static void test_get_fitzpatrick_modifier(td::string emoji, int result) {
+ ASSERT_EQ(result, td::get_fitzpatrick_modifier(emoji));
+}
+
+TEST(Emoji, get_fitzpatrick_modifier) {
+ test_get_fitzpatrick_modifier("", 0);
+ test_get_fitzpatrick_modifier("👩🏼‍❤‍💋‍👩🏻", 2);
+ test_get_fitzpatrick_modifier("👩🏼‍❤️‍💋‍👩🏻", 2);
+ test_get_fitzpatrick_modifier("👋", 0);
+ test_get_fitzpatrick_modifier("👋🏻", 2);
+ test_get_fitzpatrick_modifier("👋🏼", 3);
+ test_get_fitzpatrick_modifier("👋🏽", 4);
+ test_get_fitzpatrick_modifier("👋🏾", 5);
+ test_get_fitzpatrick_modifier("👋🏿", 6);
+ test_get_fitzpatrick_modifier("🏻", 2);
+ test_get_fitzpatrick_modifier("🏼", 3);
+ test_get_fitzpatrick_modifier("🏽", 4);
+ test_get_fitzpatrick_modifier("🏾", 5);
+ test_get_fitzpatrick_modifier("🏿", 6);
+ test_get_fitzpatrick_modifier("⌚", 0);
+ test_get_fitzpatrick_modifier("↔", 0);
+ test_get_fitzpatrick_modifier("🪗", 0);
+ test_get_fitzpatrick_modifier("2️⃣", 0);
+ test_get_fitzpatrick_modifier("2⃣", 0);
+ test_get_fitzpatrick_modifier("❤️", 0);
+ test_get_fitzpatrick_modifier("❤", 0);
+ test_get_fitzpatrick_modifier("⌚", 0);
+ test_get_fitzpatrick_modifier("🎄", 0);
+ test_get_fitzpatrick_modifier("🧑‍🎄", 0);
+}
+
+static void test_remove_emoji_modifiers(td::string emoji, const td::string &result) {
+ ASSERT_STREQ(result, td::remove_emoji_modifiers(emoji));
+ td::remove_emoji_modifiers_in_place(emoji);
+ ASSERT_STREQ(result, emoji);
+ ASSERT_STREQ(emoji, td::remove_emoji_modifiers(emoji));
+}
+
+TEST(Emoji, remove_emoji_modifiers) {
+ test_remove_emoji_modifiers("", "");
+ test_remove_emoji_modifiers("👩🏼‍❤‍💋‍👩🏻", "👩‍❤‍💋‍👩");
+ test_remove_emoji_modifiers("👩🏼‍❤️‍💋‍👩🏻", "👩‍❤‍💋‍👩");
+ test_remove_emoji_modifiers("👋🏻", "👋");
+ test_remove_emoji_modifiers("👋🏼", "👋");
+ test_remove_emoji_modifiers("👋🏽", "👋");
+ test_remove_emoji_modifiers("👋🏾", "👋");
+ test_remove_emoji_modifiers("👋🏿", "👋");
+ test_remove_emoji_modifiers("🏻", "🏻");
+ test_remove_emoji_modifiers("🏼", "🏼");
+ test_remove_emoji_modifiers("🏽", "🏽");
+ test_remove_emoji_modifiers("🏾", "🏾");
+ test_remove_emoji_modifiers("🏿", "🏿");
+ test_remove_emoji_modifiers("⌚", "⌚");
+ test_remove_emoji_modifiers("↔", "↔");
+ test_remove_emoji_modifiers("🪗", "🪗");
+ test_remove_emoji_modifiers("2️⃣", "2⃣");
+ test_remove_emoji_modifiers("2⃣", "2⃣");
+ test_remove_emoji_modifiers("❤️", "❤");
+ test_remove_emoji_modifiers("❤", "❤");
+ test_remove_emoji_modifiers("⌚", "⌚");
+ test_remove_emoji_modifiers("️", "️");
+ test_remove_emoji_modifiers("️️️🏻", "️️️🏻");
+ test_remove_emoji_modifiers("️️️🏻a", "a");
+ test_remove_emoji_modifiers("🎄", "🎄");
+ test_remove_emoji_modifiers("🧑‍🎄", "🧑‍🎄");
+}
+
+static void test_remove_emoji_selectors(td::string emoji, const td::string &result) {
+ ASSERT_STREQ(result, td::remove_emoji_selectors(result));
+ ASSERT_STREQ(result, td::remove_emoji_selectors(emoji));
+}
+
+TEST(Emoji, remove_emoji_selectors) {
+ test_remove_emoji_selectors("", "");
+ test_remove_emoji_selectors("👩🏼‍❤‍💋‍👩🏻", "👩🏼‍❤‍💋‍👩🏻");
+ test_remove_emoji_selectors("👩🏼‍❤️‍💋‍👩🏻", "👩🏼‍❤‍💋‍👩🏻");
+ test_remove_emoji_selectors("👋🏻", "👋🏻");
+ test_remove_emoji_selectors("👋🏼", "👋🏼");
+ test_remove_emoji_selectors("👋🏽", "👋🏽");
+ test_remove_emoji_selectors("👋🏾", "👋🏾");
+ test_remove_emoji_selectors("👋🏿", "👋🏿");
+ test_remove_emoji_selectors("🏻", "🏻");
+ test_remove_emoji_selectors("🏼", "🏼");
+ test_remove_emoji_selectors("🏽", "🏽");
+ test_remove_emoji_selectors("🏾", "🏾");
+ test_remove_emoji_selectors("🏿", "🏿");
+ test_remove_emoji_selectors("⌚", "⌚");
+ test_remove_emoji_selectors("↔", "↔");
+ test_remove_emoji_selectors("🪗", "🪗");
+ test_remove_emoji_selectors("2️⃣", "2⃣");
+ test_remove_emoji_selectors("2⃣", "2⃣");
+ test_remove_emoji_selectors("❤️", "❤");
+ test_remove_emoji_selectors("❤", "❤");
+ test_remove_emoji_selectors("⌚", "⌚");
+ test_remove_emoji_selectors("🎄", "🎄");
+ test_remove_emoji_selectors("🧑‍🎄", "🧑‍🎄");
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/filesystem.cpp b/protocols/Telegram/tdlib/td/tdutils/test/filesystem.cpp
index a0a92c14eb..de2def1e5e 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/filesystem.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/filesystem.cpp
@@ -1,41 +1,47 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/filesystem.h"
+#include "td/utils/Slice.h"
#include "td/utils/tests.h"
+static void test_clean_filename(td::CSlice name, td::Slice result) {
+ ASSERT_STREQ(td::clean_filename(name), result);
+}
+
TEST(Misc, clean_filename) {
- using td::clean_filename;
- ASSERT_STREQ(clean_filename("-1234567"), "-1234567");
- ASSERT_STREQ(clean_filename(".git"), "git");
- ASSERT_STREQ(clean_filename("../../.git"), "git");
- ASSERT_STREQ(clean_filename(".././.."), "");
- ASSERT_STREQ(clean_filename("../"), "");
- ASSERT_STREQ(clean_filename(".."), "");
- ASSERT_STREQ(clean_filename("test/git/ as dsa . a"), "as dsa.a");
- ASSERT_STREQ(clean_filename(" . "), "");
- ASSERT_STREQ(clean_filename("!@#$%^&*()_+-=[]{;|:\"}'<>?,.`~"), "!@#$%^ ()_+-=[]{; } ,.~");
- ASSERT_STREQ(clean_filename("!@#$%^&*()_+-=[]{}\\|:\";'<>?,.`~"), "; ,.~");
- ASSERT_STREQ(clean_filename("عرفها بعد قد. هذا مع تاريخ اليميني واندونيسيا،, لعدم تاريخ لهيمنة الى"),
- "عرفها بعد قد.هذا مع تاريخ اليميني");
- ASSERT_STREQ(
- clean_filename(
- "012345678901234567890123456789012345678901234567890123456789adsasdasdsaa.01234567890123456789asdasdasdasd"),
- "012345678901234567890123456789012345678901234567890123456789.01234567890123456789");
- ASSERT_STREQ(clean_filename("01234567890123456789012345678901234567890123456789<>*?: <>*?:0123456789adsasdasdsaa. "
- "0123456789`<><<>><><>0123456789asdasdasdasd"),
- "01234567890123456789012345678901234567890123456789.0123456789");
- ASSERT_STREQ(clean_filename("01234567890123456789012345678901234567890123456789<>*?: <>*?:0123456789adsasdasdsaa. "
- "0123456789`<><><>0123456789asdasdasdasd"),
- "01234567890123456789012345678901234567890123456789.0123456789 012");
- ASSERT_STREQ(clean_filename("C:/document.tar.gz"), "document.tar.gz");
- ASSERT_STREQ(clean_filename("test...."), "test");
- ASSERT_STREQ(clean_filename("....test"), "test");
- ASSERT_STREQ(clean_filename("test.exe...."), "test.exe"); // extension has changed
- ASSERT_STREQ(clean_filename("test.exe01234567890123456789...."),
- "test.exe01234567890123456789"); // extension may be more then 20 characters
- ASSERT_STREQ(clean_filename("....test....asdf"), "test.asdf");
+ test_clean_filename("-1234567", "-1234567");
+ test_clean_filename(".git", "git");
+ test_clean_filename("../../.git", "git");
+ test_clean_filename(".././..", "");
+ test_clean_filename("../", "");
+ test_clean_filename("..", "");
+ test_clean_filename("test/git/ as dsa . a", "as dsa.a");
+ test_clean_filename(" . ", "");
+ test_clean_filename("!@#$%^&*()_+-=[]{;|:\"}'<>?,.`~", "!@#$%^ ()_+-=[]{; } ,.~");
+ test_clean_filename("!@#$%^&*()_+-=[]{}\\|:\";'<>?,.`~", "; ,.~");
+ test_clean_filename("عرفها بعد قد. هذا مع تاريخ اليميني واندونيسيا،, لعدم تاريخ لهيمنة الى",
+ "عرفها بعد قد.هذا مع تاريخ الي");
+ test_clean_filename(
+ "012345678901234567890123456789012345678901234567890123456789adsasdasdsaa.01234567890123456789asdasdasdasd",
+ "012345678901234567890123456789012345678901234567890123456789adsa.0123456789012345");
+ test_clean_filename(
+ "01234567890123456789012345678901234567890123456789adsa<>*?: <>*?:0123456789adsasdasdsaa. "
+ "0123456789`<><<>><><>0123456789asdasdasdasd",
+ "01234567890123456789012345678901234567890123456789adsa.0123456789");
+ test_clean_filename(
+ "012345678901234567890123456789012345678901234567890123<>*?: <>*?:0123456789adsasdasdsaa. "
+ "0123456789`<>0123456789asdasdasdasd",
+ "012345678901234567890123456789012345678901234567890123.0123456789 012");
+ test_clean_filename("C:/document.tar.gz", "document.tar.gz");
+ test_clean_filename("test....", "test");
+ test_clean_filename("....test", "test");
+ test_clean_filename("test.exe....", "test.exe"); // extension has changed
+ test_clean_filename("test.exe01234567890123456789....",
+ "test.exe01234567890123456789"); // extension may be more than 16 characters
+ test_clean_filename("....test....asdf", "test.asdf");
+ test_clean_filename("കറുപ്പ്.txt", "കറപപ.txt");
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/gzip.cpp b/protocols/Telegram/tdlib/td/tdutils/test/gzip.cpp
index e4bd81eb0d..32d75474e8 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/gzip.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/gzip.cpp
@@ -1,49 +1,59 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#include "td/utils/algorithm.h"
#include "td/utils/buffer.h"
#include "td/utils/ByteFlow.h"
+#include "td/utils/common.h"
#include "td/utils/Gzip.h"
#include "td/utils/GzipByteFlow.h"
#include "td/utils/logging.h"
+#include "td/utils/port/thread_local.h"
+#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/tests.h"
+#include "td/utils/Time.h"
-static void encode_decode(td::string s) {
+static void encode_decode(const td::string &s) {
auto r = td::gzencode(s, 2);
ASSERT_TRUE(!r.empty());
- if (r.empty()) {
- return;
- }
- auto new_s = td::gzdecode(r.as_slice());
- ASSERT_TRUE(!new_s.empty());
- if (new_s.empty()) {
- return;
- }
- ASSERT_EQ(s, new_s.as_slice().str());
+ ASSERT_EQ(s, td::gzdecode(r.as_slice()));
}
TEST(Gzip, gzencode_gzdecode) {
- auto str = td::rand_string(0, 127, 1000);
- encode_decode(str);
- str = td::rand_string('a', 'z', 1000000);
- encode_decode(str);
- str = td::string(1000000, 'a');
- encode_decode(str);
+ encode_decode(td::rand_string(0, 255, 1000));
+ encode_decode(td::rand_string('a', 'z', 1000000));
+ encode_decode(td::string(1000000, 'a'));
+}
+
+static void test_gzencode(const td::string &s) {
+ auto begin_time = td::Time::now();
+ auto r = td::gzencode(s, td::max(2, static_cast<int>(100 / s.size())));
+ ASSERT_TRUE(!r.empty());
+ LOG(INFO) << "Encoded string of size " << s.size() << " in " << (td::Time::now() - begin_time)
+ << " with compression ratio " << static_cast<double>(r.size()) / static_cast<double>(s.size());
+}
+
+TEST(Gzip, gzencode) {
+ for (size_t len = 1; len <= 10000000; len *= 10) {
+ test_gzencode(td::rand_string('a', 'a', len));
+ test_gzencode(td::rand_string('a', 'z', len));
+ test_gzencode(td::rand_string(0, 255, len));
+ }
}
TEST(Gzip, flow) {
auto str = td::rand_string('a', 'z', 1000000);
auto parts = td::rand_split(str);
- auto input_writer = td::ChainBufferWriter::create_empty();
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
td::ByteFlowSource source(&input);
- td::GzipByteFlow gzip_flow(td::Gzip::Encode);
- gzip_flow = td::GzipByteFlow(td::Gzip::Encode);
+ td::GzipByteFlow gzip_flow(td::Gzip::Mode::Encode);
+ gzip_flow = td::GzipByteFlow(td::Gzip::Mode::Encode);
td::ByteFlowSink sink;
source >> gzip_flow >> sink;
@@ -63,14 +73,15 @@ TEST(Gzip, flow) {
}
TEST(Gzip, flow_error) {
auto str = td::rand_string('a', 'z', 1000000);
- auto zip = td::gzencode(str).as_slice().str();
+ auto zip = td::gzencode(str, 0.9).as_slice().str();
+ ASSERT_TRUE(!zip.empty());
zip.resize(zip.size() - 1);
auto parts = td::rand_split(zip);
auto input_writer = td::ChainBufferWriter();
auto input = input_writer.extract_reader();
td::ByteFlowSource source(&input);
- td::GzipByteFlow gzip_flow(td::Gzip::Decode);
+ td::GzipByteFlow gzip_flow(td::Gzip::Mode::Decode);
td::ByteFlowSink sink;
source >> gzip_flow >> sink;
@@ -89,13 +100,13 @@ TEST(Gzip, flow_error) {
TEST(Gzip, encode_decode_flow) {
auto str = td::rand_string('a', 'z', 1000000);
auto parts = td::rand_split(str);
- auto input_writer = td::ChainBufferWriter::create_empty();
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
td::ByteFlowSource source(&input);
- td::GzipByteFlow gzip_encode_flow(td::Gzip::Encode);
- td::GzipByteFlow gzip_decode_flow(td::Gzip::Decode);
- td::GzipByteFlow gzip_encode_flow2(td::Gzip::Encode);
- td::GzipByteFlow gzip_decode_flow2(td::Gzip::Decode);
+ td::GzipByteFlow gzip_encode_flow(td::Gzip::Mode::Encode);
+ td::GzipByteFlow gzip_decode_flow(td::Gzip::Mode::Decode);
+ td::GzipByteFlow gzip_encode_flow2(td::Gzip::Mode::Encode);
+ td::GzipByteFlow gzip_decode_flow2(td::Gzip::Mode::Decode);
td::ByteFlowSink sink;
source >> gzip_encode_flow >> gzip_decode_flow >> gzip_encode_flow2 >> gzip_decode_flow2 >> sink;
@@ -111,3 +122,126 @@ TEST(Gzip, encode_decode_flow) {
ASSERT_TRUE(sink.status().is_ok());
ASSERT_EQ(str, sink.result()->move_as_buffer_slice().as_slice().str());
}
+
+TEST(Gzip, encode_decode_flow_big) {
+ td::clear_thread_locals();
+ auto start_mem = td::BufferAllocator::get_buffer_mem();
+ {
+ auto str = td::string(200000, 'a');
+ td::ChainBufferWriter input_writer;
+ auto input = input_writer.extract_reader();
+ td::ByteFlowSource source(&input);
+ td::GzipByteFlow gzip_encode_flow(td::Gzip::Mode::Encode);
+ td::GzipByteFlow gzip_decode_flow(td::Gzip::Mode::Decode);
+ td::GzipByteFlow gzip_encode_flow2(td::Gzip::Mode::Encode);
+ td::GzipByteFlow gzip_decode_flow2(td::Gzip::Mode::Decode);
+ td::ByteFlowSink sink;
+ source >> gzip_encode_flow >> gzip_decode_flow >> gzip_encode_flow2 >> gzip_decode_flow2 >> sink;
+
+ ASSERT_TRUE(!sink.is_ready());
+ size_t n = 200;
+ size_t left_size = n * str.size();
+ auto validate = [&](td::Slice chunk) {
+ CHECK(chunk.size() <= left_size);
+ left_size -= chunk.size();
+ ASSERT_TRUE(td::all_of(chunk, [](auto c) { return c == 'a'; }));
+ };
+
+ for (size_t i = 0; i < n; i++) {
+ input_writer.append(str);
+ source.wakeup();
+ auto extra_mem = td::BufferAllocator::get_buffer_mem() - start_mem;
+ // limit means nothing. just check that we do not use 200Mb or so
+ CHECK(extra_mem < (10 << 20));
+
+ auto size = sink.get_output()->size();
+ validate(sink.get_output()->cut_head(size).move_as_buffer_slice().as_slice());
+ }
+ ASSERT_TRUE(!sink.is_ready());
+ source.close_input(td::Status::OK());
+ ASSERT_TRUE(sink.is_ready());
+ LOG_IF(ERROR, sink.status().is_error()) << sink.status();
+ ASSERT_TRUE(sink.status().is_ok());
+ validate(sink.result()->move_as_buffer_slice().as_slice());
+ ASSERT_EQ(0u, left_size);
+ }
+ td::clear_thread_locals();
+ ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem());
+}
+
+TEST(Gzip, decode_encode_flow_bomb) {
+ td::string gzip_bomb_str;
+ size_t N = 200;
+ {
+ td::ChainBufferWriter input_writer;
+ auto input = input_writer.extract_reader();
+ td::GzipByteFlow gzip_flow(td::Gzip::Mode::Encode);
+ td::ByteFlowSource source(&input);
+ td::ByteFlowSink sink;
+ source >> gzip_flow >> sink;
+
+ td::string s(1 << 16, 'a');
+ for (size_t i = 0; i < N; i++) {
+ input_writer.append(s);
+ source.wakeup();
+ }
+ source.close_input(td::Status::OK());
+ ASSERT_TRUE(sink.is_ready());
+ LOG_IF(ERROR, sink.status().is_error()) << sink.status();
+ ASSERT_TRUE(sink.status().is_ok());
+ gzip_bomb_str = sink.result()->move_as_buffer_slice().as_slice().str();
+ }
+
+ td::clear_thread_locals();
+ auto start_mem = td::BufferAllocator::get_buffer_mem();
+ {
+ td::ChainBufferWriter input_writer;
+ auto input = input_writer.extract_reader();
+ td::ByteFlowSource source(&input);
+ td::GzipByteFlow::Options decode_options;
+ decode_options.write_watermark.low = 2 << 20;
+ decode_options.write_watermark.high = 4 << 20;
+ td::GzipByteFlow::Options encode_options;
+ encode_options.read_watermark.low = 2 << 20;
+ encode_options.read_watermark.high = 4 << 20;
+ td::GzipByteFlow gzip_decode_flow(td::Gzip::Mode::Decode);
+ gzip_decode_flow.set_options(decode_options);
+ td::GzipByteFlow gzip_encode_flow(td::Gzip::Mode::Encode);
+ gzip_encode_flow.set_options(encode_options);
+ td::GzipByteFlow gzip_decode_flow2(td::Gzip::Mode::Decode);
+ gzip_decode_flow2.set_options(decode_options);
+ td::GzipByteFlow gzip_encode_flow2(td::Gzip::Mode::Encode);
+ gzip_encode_flow2.set_options(encode_options);
+ td::GzipByteFlow gzip_decode_flow3(td::Gzip::Mode::Decode);
+ gzip_decode_flow3.set_options(decode_options);
+ td::ByteFlowSink sink;
+ source >> gzip_decode_flow >> gzip_encode_flow >> gzip_decode_flow2 >> gzip_encode_flow2 >> gzip_decode_flow3 >>
+ sink;
+
+ ASSERT_TRUE(!sink.is_ready());
+ size_t left_size = N * (1 << 16);
+ auto validate = [&](td::Slice chunk) {
+ CHECK(chunk.size() <= left_size);
+ left_size -= chunk.size();
+ ASSERT_TRUE(td::all_of(chunk, [](auto c) { return c == 'a'; }));
+ };
+
+ input_writer.append(gzip_bomb_str);
+ source.close_input(td::Status::OK());
+
+ do {
+ gzip_decode_flow3.wakeup();
+ gzip_decode_flow2.wakeup();
+ gzip_decode_flow.wakeup();
+ source.wakeup();
+ auto extra_mem = td::BufferAllocator::get_buffer_mem() - start_mem;
+ // limit means nothing. just check that we do not use 15Mb or so
+ CHECK(extra_mem < (5 << 20));
+ auto size = sink.get_output()->size();
+ validate(sink.get_output()->cut_head(size).move_as_buffer_slice().as_slice());
+ } while (!sink.is_ready());
+ ASSERT_EQ(0u, left_size);
+ }
+ td::clear_thread_locals();
+ ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem());
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/hashset_benchmark.cpp b/protocols/Telegram/tdlib/td/tdutils/test/hashset_benchmark.cpp
new file mode 100644
index 0000000000..f07f58c8f0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/hashset_benchmark.cpp
@@ -0,0 +1,647 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/FlatHashMap.h"
+#include "td/utils/FlatHashMapChunks.h"
+#include "td/utils/FlatHashTable.h"
+#include "td/utils/format.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/logging.h"
+#include "td/utils/MapNode.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Span.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/Time.h"
+#include "td/utils/VectorQueue.h"
+
+#ifdef SCOPE_EXIT
+#undef SCOPE_EXIT
+#endif
+
+#include <absl/container/flat_hash_map.h>
+#include <absl/hash/hash.h>
+#include <algorithm>
+#include <benchmark/benchmark.h>
+#include <folly/container/F14Map.h>
+#include <functional>
+#include <map>
+#include <random>
+#include <unordered_map>
+#include <utility>
+
+template <class TableT>
+static void reserve(TableT &table, std::size_t size) {
+ table.reserve(size);
+}
+
+template <class A, class B>
+static void reserve(std::map<A, B> &table, std::size_t size) {
+}
+
+template <class KeyT, class ValueT>
+class NoOpTable {
+ public:
+ using key_type = KeyT;
+ using value_type = std::pair<const KeyT, ValueT>;
+ template <class It>
+ NoOpTable(It begin, It end) {
+ }
+
+ ValueT &operator[](const KeyT &) const {
+ static ValueT dummy;
+ return dummy;
+ }
+
+ KeyT find(const KeyT &key) const {
+ return key;
+ }
+};
+
+template <class KeyT, class ValueT>
+class VectorTable {
+ public:
+ using key_type = KeyT;
+ using value_type = std::pair<const KeyT, ValueT>;
+ template <class It>
+ VectorTable(It begin, It end) : table_(begin, end) {
+ }
+
+ ValueT &operator[](const KeyT &needle) {
+ auto it = find(needle);
+ if (it == table_.end()) {
+ table_.emplace_back(needle, ValueT{});
+ return table_.back().second;
+ }
+ return it->second;
+ }
+ auto find(const KeyT &needle) {
+ return std::find_if(table_.begin(), table_.end(), [&](auto &key) { return key.first == needle; });
+ }
+
+ private:
+ using KeyValue = value_type;
+ td::vector<KeyValue> table_;
+};
+
+template <class KeyT, class ValueT>
+class SortedVectorTable {
+ public:
+ using key_type = KeyT;
+ using value_type = std::pair<KeyT, ValueT>;
+ template <class It>
+ SortedVectorTable(It begin, It end) : table_(begin, end) {
+ std::sort(table_.begin(), table_.end());
+ }
+
+ ValueT &operator[](const KeyT &needle) {
+ auto it = std::lower_bound(table_.begin(), table_.end(), needle,
+ [](const auto &l, const auto &r) { return l.first < r; });
+ if (it == table_.end() || it->first != needle) {
+ it = table_.insert(it, {needle, ValueT{}});
+ }
+ return it->second;
+ }
+
+ auto find(const KeyT &needle) {
+ auto it = std::lower_bound(table_.begin(), table_.end(), needle,
+ [](const auto &l, const auto &r) { return l.first < r; });
+ if (it != table_.end() && it->first == needle) {
+ return it;
+ }
+ return table_.end();
+ }
+
+ private:
+ using KeyValue = value_type;
+ td::vector<KeyValue> table_;
+};
+
+template <class KeyT, class ValueT, class HashT = td::Hash<KeyT>>
+class SimpleHashTable {
+ public:
+ using key_type = KeyT;
+ using value_type = std::pair<KeyT, ValueT>;
+ template <class It>
+ SimpleHashTable(It begin, It end) {
+ nodes_.resize((end - begin) * 2);
+ for (; begin != end; ++begin) {
+ insert(begin->first, begin->second);
+ }
+ }
+
+ ValueT &operator[](const KeyT &needle) {
+ UNREACHABLE();
+ }
+
+ ValueT *find(const KeyT &needle) {
+ auto hash = HashT()(needle);
+ std::size_t i = hash % nodes_.size();
+ while (true) {
+ if (nodes_[i].key == needle) {
+ return &nodes_[i].value;
+ }
+ if (nodes_[i].hash == 0) {
+ return nullptr;
+ }
+ i++;
+ if (i == nodes_.size()) {
+ i = 0;
+ }
+ }
+ }
+
+ private:
+ using KeyValue = value_type;
+ struct Node {
+ std::size_t hash{0};
+ KeyT key;
+ ValueT value;
+ };
+ td::vector<Node> nodes_;
+
+ void insert(KeyT key, ValueT value) {
+ auto hash = HashT()(key);
+ std::size_t i = hash % nodes_.size();
+ while (true) {
+ if (nodes_[i].hash == 0 || (nodes_[i].hash == hash && nodes_[i].key == key)) {
+ nodes_[i].value = value;
+ nodes_[i].key = key;
+ nodes_[i].hash = hash;
+ return;
+ }
+ i++;
+ if (i == nodes_.size()) {
+ i = 0;
+ }
+ }
+ }
+};
+
+template <typename TableT>
+static void BM_Get(benchmark::State &state) {
+ std::size_t n = state.range(0);
+ constexpr std::size_t BATCH_SIZE = 1024;
+ td::Random::Xorshift128plus rnd(123);
+ using Key = typename TableT::key_type;
+ using Value = typename TableT::value_type::second_type;
+ using KeyValue = std::pair<Key, Value>;
+ td::vector<KeyValue> data;
+ td::vector<Key> keys;
+
+ TableT table;
+ for (std::size_t i = 0; i < n; i++) {
+ auto key = rnd();
+ auto value = rnd();
+ data.emplace_back(key, value);
+ table.emplace(key, value);
+ keys.push_back(key);
+ }
+
+ std::size_t key_i = 0;
+ td::random_shuffle(td::as_mutable_span(keys), rnd);
+ auto next_key = [&] {
+ key_i++;
+ if (key_i == data.size()) {
+ key_i = 0;
+ }
+ return keys[key_i];
+ };
+
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ benchmark::DoNotOptimize(table.find(next_key()));
+ }
+ }
+}
+
+template <typename TableT>
+static void BM_find_same(benchmark::State &state) {
+ td::Random::Xorshift128plus rnd(123);
+ TableT table;
+ constexpr std::size_t N = 100000;
+ constexpr std::size_t BATCH_SIZE = 1024;
+ reserve(table, N);
+
+ for (std::size_t i = 0; i < N; i++) {
+ table.emplace(rnd(), i);
+ }
+
+ auto key = td::Random::secure_uint64();
+ table[key] = 123;
+
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ benchmark::DoNotOptimize(table.find(key));
+ }
+ }
+}
+
+template <typename TableT>
+static void BM_emplace_same(benchmark::State &state) {
+ td::Random::Xorshift128plus rnd(123);
+ TableT table;
+ constexpr std::size_t N = 100000;
+ constexpr std::size_t BATCH_SIZE = 1024;
+ reserve(table, N);
+
+ for (std::size_t i = 0; i < N; i++) {
+ table.emplace(rnd(), i);
+ }
+
+ auto key = 123743;
+ table[key] = 123;
+
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ benchmark::DoNotOptimize(table.emplace(key + (i & 15) * 100, 43784932));
+ }
+ }
+}
+
+template <typename TableT>
+static void BM_emplace_string(benchmark::State &state) {
+ td::Random::Xorshift128plus rnd(123);
+ TableT table;
+ constexpr std::size_t N = 100000;
+ constexpr std::size_t BATCH_SIZE = 1024;
+ reserve(table, N);
+
+ for (std::size_t i = 0; i < N; i++) {
+ table.emplace(td::to_string(rnd()), i);
+ }
+
+ table["0"] = 123;
+ td::vector<td::string> strings;
+ for (std::size_t i = 0; i < 16; i++) {
+ strings.emplace_back(1, static_cast<char>('0' + i));
+ }
+
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ benchmark::DoNotOptimize(table.emplace(strings[i & 15], 43784932));
+ }
+ }
+}
+
+namespace td {
+template <class K, class V, class FunctT>
+static void table_remove_if(absl::flat_hash_map<K, V> &table, FunctT &&func) {
+ for (auto it = table.begin(); it != table.end();) {
+ if (func(*it)) {
+ auto copy = it;
+ ++it;
+ table.erase(copy);
+ } else {
+ ++it;
+ }
+ }
+}
+} // namespace td
+
+template <typename TableT>
+static void BM_remove_if(benchmark::State &state) {
+ constexpr std::size_t N = 100000;
+ constexpr std::size_t BATCH_SIZE = N;
+
+ TableT table;
+ reserve(table, N);
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ state.PauseTiming();
+ td::Random::Xorshift128plus rnd(123);
+ for (std::size_t i = 0; i < N; i++) {
+ table.emplace(rnd(), i);
+ }
+ state.ResumeTiming();
+
+ td::table_remove_if(table, [](auto &it) { return it.second % 2 == 0; });
+ }
+}
+
+template <typename TableT>
+static void BM_erase_all_with_begin(benchmark::State &state) {
+ constexpr std::size_t N = 100000;
+ constexpr std::size_t BATCH_SIZE = N;
+
+ TableT table;
+ td::Random::Xorshift128plus rnd(123);
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ table.emplace(rnd() + 1, i);
+ }
+ while (!table.empty()) {
+ table.erase(table.begin());
+ }
+ }
+}
+
+template <typename TableT>
+static void BM_cache(benchmark::State &state) {
+ constexpr std::size_t N = 1000;
+ constexpr std::size_t BATCH_SIZE = 1000000;
+
+ TableT table;
+ td::Random::Xorshift128plus rnd(123);
+ td::VectorQueue<td::uint64> keys;
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ auto key = rnd() + 1;
+ keys.push(key);
+ table.emplace(key, i);
+ if (table.size() > N) {
+ table.erase(keys.pop());
+ }
+ }
+ }
+}
+
+template <typename TableT>
+static void BM_cache2(benchmark::State &state) {
+ constexpr std::size_t N = 1000;
+ constexpr std::size_t BATCH_SIZE = 1000000;
+
+ TableT table;
+ td::Random::Xorshift128plus rnd(123);
+ td::VectorQueue<td::uint64> keys;
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ auto key = rnd() + 1;
+ keys.push(key);
+ table.emplace(key, i);
+ if (table.size() > N) {
+ table.erase(keys.pop_rand(rnd));
+ }
+ }
+ }
+}
+
+template <typename TableT>
+static void BM_cache3(benchmark::State &state) {
+ std::size_t N = state.range(0);
+ constexpr std::size_t BATCH_SIZE = 1000000;
+
+ TableT table;
+ td::Random::Xorshift128plus rnd(123);
+ td::VectorQueue<td::uint64> keys;
+ std::size_t step = 20;
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i += step) {
+ auto key = rnd() + 1;
+ keys.push(key);
+ table.emplace(key, i);
+
+ for (std::size_t j = 1; j < step; j++) {
+ auto key_to_find = keys.data()[rnd() % keys.size()];
+ benchmark::DoNotOptimize(table.find(key_to_find));
+ }
+
+ if (table.size() > N) {
+ table.erase(keys.pop_rand(rnd));
+ }
+ }
+ }
+}
+
+template <typename TableT>
+static void BM_remove_if_slow(benchmark::State &state) {
+ constexpr std::size_t N = 5000;
+ constexpr std::size_t BATCH_SIZE = 500000;
+
+ TableT table;
+ td::Random::Xorshift128plus rnd(123);
+ for (std::size_t i = 0; i < N; i++) {
+ table.emplace(rnd() + 1, i);
+ }
+ auto first_key = table.begin()->first;
+ {
+ std::size_t cnt = 0;
+ td::table_remove_if(table, [&cnt, n = N](auto &) {
+ cnt += 2;
+ return cnt <= n;
+ });
+ }
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ table.emplace(first_key, i);
+ table.erase(first_key);
+ }
+ }
+}
+
+template <typename TableT>
+static void BM_remove_if_slow_old(benchmark::State &state) {
+ constexpr std::size_t N = 100000;
+ constexpr std::size_t BATCH_SIZE = 5000000;
+
+ TableT table;
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ td::Random::Xorshift128plus rnd(123);
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ table.emplace(rnd() + 1, i);
+ if (table.size() > N) {
+ std::size_t cnt = 0;
+ td::table_remove_if(table, [&cnt, n = N](auto &) {
+ cnt += 2;
+ return cnt <= n;
+ });
+ }
+ }
+ }
+}
+
+template <typename TableT>
+static void benchmark_create(td::Slice name) {
+ td::Random::Xorshift128plus rnd(123);
+ {
+ constexpr std::size_t N = 10000000;
+ TableT table;
+ reserve(table, N);
+ auto start = td::Timestamp::now();
+ for (std::size_t i = 0; i < N; i++) {
+ table.emplace(rnd(), i);
+ }
+ auto end = td::Timestamp::now();
+ LOG(INFO) << name << ": create " << N << " elements: " << td::format::as_time(end.at() - start.at());
+
+ double res = 0;
+ td::vector<std::pair<std::size_t, td::format::Time>> pauses;
+ for (std::size_t i = 0; i < N; i++) {
+ auto emplace_start = td::Timestamp::now();
+ table.emplace(rnd(), i);
+ auto emplace_end = td::Timestamp::now();
+ auto pause = emplace_end.at() - emplace_start.at();
+ res = td::max(pause, res);
+ if (pause > 0.001) {
+ pauses.emplace_back(i, td::format::as_time(pause));
+ }
+ }
+
+ LOG(INFO) << name << ": create another " << N << " elements, max pause = " << td::format::as_time(res) << " "
+ << pauses;
+ }
+}
+
+struct CacheMissNode {
+ td::uint32 data{};
+ char padding[64 - sizeof(data)];
+};
+
+class IterateFast {
+ public:
+ static td::uint32 iterate(CacheMissNode *ptr, std::size_t max_shift) {
+ td::uint32 res = 1;
+ for (std::size_t i = 0; i < max_shift; i++) {
+ if (ptr[i].data % max_shift != 0) {
+ res *= ptr[i].data;
+ } else {
+ res /= ptr[i].data;
+ }
+ }
+ return res;
+ }
+};
+
+class IterateSlow {
+ public:
+ static td::uint32 iterate(CacheMissNode *ptr, std::size_t max_shift) {
+ td::uint32 res = 1;
+ for (std::size_t i = 0;; i++) {
+ if (ptr[i].data % max_shift != 0) {
+ res *= ptr[i].data;
+ } else {
+ break;
+ }
+ }
+ return res;
+ }
+};
+
+template <class F>
+static void BM_cache_miss(benchmark::State &state) {
+ td::uint32 max_shift = state.range(0);
+ bool flag = state.range(1);
+ std::random_device rd;
+ std::mt19937 rnd(rd());
+ int N = 50000000;
+ td::vector<CacheMissNode> nodes(N);
+ td::uint32 i = 0;
+ for (auto &node : nodes) {
+ if (flag) {
+ node.data = i++ % max_shift;
+ } else {
+ node.data = rnd();
+ }
+ }
+
+ td::vector<int> positions(N);
+ std::uniform_int_distribution<td::uint32> rnd_pos(0, N - 1000);
+ for (auto &pos : positions) {
+ pos = rnd_pos(rnd);
+ if (flag) {
+ pos = pos / max_shift * max_shift + 1;
+ }
+ }
+
+ while (state.KeepRunningBatch(positions.size())) {
+ for (const auto pos : positions) {
+ auto *ptr = &nodes[pos];
+ auto res = F::iterate(ptr, max_shift);
+ benchmark::DoNotOptimize(res);
+ }
+ }
+}
+
+static td::uint64 equal_mask_slow(td::uint8 *bytes, td::uint8 needle) {
+ td::uint64 mask = 0;
+ for (int i = 0; i < 16; i++) {
+ mask |= (bytes[i] == needle) << i;
+ }
+ return mask;
+}
+
+template <class MaskT>
+static void BM_mask(benchmark::State &state) {
+ std::size_t BATCH_SIZE = 1024;
+ td::vector<td::uint8> bytes(BATCH_SIZE + 16);
+ for (auto &b : bytes) {
+ b = static_cast<td::uint8>(td::Random::fast(0, 17));
+ }
+
+ while (state.KeepRunningBatch(BATCH_SIZE)) {
+ for (std::size_t i = 0; i < BATCH_SIZE; i++) {
+ benchmark::DoNotOptimize(MaskT::equal_mask(bytes.data() + i, 17));
+ }
+ }
+}
+
+BENCHMARK_TEMPLATE(BM_mask, td::MaskPortable);
+#ifdef __aarch64__
+BENCHMARK_TEMPLATE(BM_mask, td::MaskNeonFolly);
+BENCHMARK_TEMPLATE(BM_mask, td::MaskNeon);
+#endif
+#if TD_SSE2
+BENCHMARK_TEMPLATE(BM_mask, td::MaskSse2);
+#endif
+
+template <class KeyT, class ValueT, class HashT = td::Hash<KeyT>, class EqT = std::equal_to<KeyT>>
+using FlatHashMapImpl = td::FlatHashTable<td::MapNode<KeyT, ValueT>, HashT, EqT>;
+
+#define FOR_EACH_TABLE(F) \
+ F(FlatHashMapImpl) \
+ F(td::FlatHashMapChunks) \
+ F(folly::F14FastMap) \
+ F(absl::flat_hash_map) \
+ F(std::unordered_map) \
+ F(std::map)
+
+//BENCHMARK(BM_cache_miss<IterateSlow>)->Ranges({{1, 16}, {0, 1}});
+//BENCHMARK(BM_cache_miss<IterateFast>)->Ranges({{1, 16}, {0, 1}});
+//BENCHMARK_TEMPLATE(BM_Get, VectorTable<td::uint64, td::uint64>)->Range(1, 1 << 26);
+//BENCHMARK_TEMPLATE(BM_Get, SortedVectorTable<td::uint64, td::uint64>)->Range(1, 1 << 26);
+//BENCHMARK_TEMPLATE(BM_Get, NoOpTable<td::uint64, td::uint64>)->Range(1, 1 << 26);
+
+#define REGISTER_GET_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_Get, HT<td::uint64, td::uint64>)->Range(1, 1 << 23);
+
+#define REGISTER_FIND_BENCHMARK(HT) \
+ BENCHMARK_TEMPLATE(BM_find_same, HT<td::uint64, td::uint64>) \
+ ->ComputeStatistics("max", [](const td::vector<double> &v) { return *std::max_element(v.begin(), v.end()); }) \
+ ->ComputeStatistics("min", [](const td::vector<double> &v) { return *std::min_element(v.begin(), v.end()); }) \
+ ->Repetitions(20) \
+ ->DisplayAggregatesOnly(true);
+
+#define REGISTER_REMOVE_IF_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_remove_if, HT<td::uint64, td::uint64>);
+#define REGISTER_EMPLACE_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_emplace_same, HT<td::uint64, td::uint64>);
+#define REGISTER_EMPLACE_STRING_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_emplace_string, HT<td::string, td::uint64>);
+#define REGISTER_CACHE_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_cache, HT<td::uint64, td::uint64>);
+#define REGISTER_CACHE2_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_cache2, HT<td::uint64, td::uint64>);
+#define REGISTER_CACHE3_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_cache3, HT<td::uint64, td::uint64>)->Range(1, 1 << 23);
+#define REGISTER_ERASE_ALL_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_erase_all_with_begin, HT<td::uint64, td::uint64>);
+#define REGISTER_REMOVE_IF_SLOW_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_remove_if_slow, HT<td::uint64, td::uint64>);
+#define REGISTER_REMOVE_IF_SLOW_OLD_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_remove_if_slow_old, HT<td::uint64, td::uint64>);
+
+FOR_EACH_TABLE(REGISTER_GET_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_CACHE3_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_CACHE2_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_CACHE_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_REMOVE_IF_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_EMPLACE_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_EMPLACE_STRING_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_ERASE_ALL_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_FIND_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_REMOVE_IF_SLOW_OLD_BENCHMARK)
+FOR_EACH_TABLE(REGISTER_REMOVE_IF_SLOW_BENCHMARK)
+
+#define RUN_CREATE_BENCHMARK(HT) benchmark_create<HT<td::uint64, td::uint64>>(#HT);
+
+int main(int argc, char **argv) {
+ // FOR_EACH_TABLE(RUN_CREATE_BENCHMARK);
+
+ benchmark::Initialize(&argc, argv);
+ benchmark::RunSpecifiedBenchmarks();
+ benchmark::Shutdown();
+}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/heap.cpp b/protocols/Telegram/tdlib/td/tdutils/test/heap.cpp
index 0dcfcf98ff..02b6d81424 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/heap.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/heap.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -8,29 +8,24 @@
#include "td/utils/common.h"
#include "td/utils/Heap.h"
-#include "td/utils/logging.h"
#include "td/utils/Random.h"
+#include "td/utils/Span.h"
-#include <algorithm>
#include <cstdio>
-#include <cstdlib>
#include <set>
#include <utility>
-REGISTER_TESTS(heap)
-
-using namespace td;
-
TEST(Heap, sort_random_perm) {
int n = 1000000;
- std::vector<int> v(n);
+
+ td::vector<int> v(n);
for (int i = 0; i < n; i++) {
v[i] = i;
}
- std::srand(123);
- std::random_shuffle(v.begin(), v.end());
- std::vector<HeapNode> nodes(n);
- KHeap<int> kheap;
+ td::Random::Xorshift128plus rnd(123);
+ td::random_shuffle(td::as_mutable_span(v), rnd);
+ td::vector<td::HeapNode> nodes(n);
+ td::KHeap<int> kheap;
for (int i = 0; i < n; i++) {
kheap.insert(v[i], &nodes[i]);
}
@@ -38,7 +33,7 @@ TEST(Heap, sort_random_perm) {
ASSERT_EQ(i, kheap.top_key());
kheap.pop();
}
-};
+}
class CheckedHeap {
public:
@@ -51,7 +46,7 @@ class CheckedHeap {
nodes[i].value = i;
}
}
- static void xx(int key, const HeapNode *heap_node) {
+ static void xx(int key, const td::HeapNode *heap_node) {
const Node *node = static_cast<const Node *>(heap_node);
std::fprintf(stderr, "(%d;%d)", node->key, node->value);
}
@@ -66,9 +61,9 @@ class CheckedHeap {
}
int random_id() const {
CHECK(!empty());
- return ids[Random::fast(0, static_cast<int>(ids.size() - 1))];
+ return ids[td::Random::fast(0, static_cast<int>(ids.size() - 1))];
}
- size_t size() const {
+ std::size_t size() const {
return ids.size();
}
bool empty() const {
@@ -140,19 +135,19 @@ class CheckedHeap {
}
private:
- struct Node : public HeapNode {
+ struct Node final : public td::HeapNode {
Node() = default;
Node(int key, int value) : key(key), value(value) {
}
int key = 0;
int value = 0;
};
- vector<int> ids;
- vector<int> rev_ids;
- vector<int> free_ids;
- vector<Node> nodes;
+ td::vector<int> ids;
+ td::vector<int> rev_ids;
+ td::vector<int> free_ids;
+ td::vector<Node> nodes;
std::set<std::pair<int, int>> set_heap;
- KHeap<int> kheap;
+ td::KHeap<int> kheap;
};
TEST(Heap, random_events) {
@@ -163,11 +158,11 @@ TEST(Heap, random_events) {
heap.top_key();
}
- int x = Random::fast(0, 4);
+ int x = td::Random::fast(0, 4);
if (heap.empty() || (x < 2 && heap.size() < 1000)) {
- heap.insert(Random::fast(0, 99));
+ heap.insert(td::Random::fast(0, 99));
} else if (x < 3) {
- heap.fix_key(Random::fast(0, 99), heap.random_id());
+ heap.fix_key(td::Random::fast(0, 99), heap.random_id());
} else if (x < 4) {
heap.erase(heap.random_id());
} else if (x < 5) {
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/json.cpp b/protocols/Telegram/tdlib/td/tdutils/test/json.cpp
index deca81a791..4e57b2d562 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/json.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/json.cpp
@@ -1,31 +1,26 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/tests.h"
-
#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
#include "td/utils/StringBuilder.h"
+#include "td/utils/tests.h"
-#include <tuple>
#include <utility>
-REGISTER_TESTS(json)
-
-using namespace td;
-
-static void decode_encode(string str, string result = "") {
+static void decode_encode(const td::string &str, td::string result = td::string()) {
auto str_copy = str;
- auto r_value = json_decode(str_copy);
+ auto r_value = td::json_decode(str_copy);
ASSERT_TRUE(r_value.is_ok());
if (r_value.is_error()) {
LOG(INFO) << r_value.error();
return;
}
- auto new_str = json_encode<string>(r_value.ok());
+ auto new_str = td::json_encode<td::string>(r_value.ok());
if (result.empty()) {
result = str;
}
@@ -34,21 +29,22 @@ static void decode_encode(string str, string result = "") {
TEST(JSON, array) {
char tmp[1000];
- StringBuilder sb({tmp, sizeof(tmp)});
- JsonBuilder jb(std::move(sb));
+ td::StringBuilder sb(td::MutableSlice{tmp, sizeof(tmp)});
+ td::JsonBuilder jb(std::move(sb));
jb.enter_value().enter_array() << "Hello" << -123;
ASSERT_EQ(jb.string_builder().is_error(), false);
auto encoded = jb.string_builder().as_cslice().str();
ASSERT_EQ("[\"Hello\",-123]", encoded);
decode_encode(encoded);
}
+
TEST(JSON, object) {
char tmp[1000];
- StringBuilder sb({tmp, sizeof(tmp)});
- JsonBuilder jb(std::move(sb));
+ td::StringBuilder sb(td::MutableSlice{tmp, sizeof(tmp)});
+ td::JsonBuilder jb(std::move(sb));
auto c = jb.enter_object();
- c << std::tie("key", "value");
- c << std::make_pair("1", 2);
+ c("key", "value");
+ c("1", 2);
c.leave();
ASSERT_EQ(jb.string_builder().is_error(), false);
auto encoded = jb.string_builder().as_cslice().str();
@@ -58,8 +54,8 @@ TEST(JSON, object) {
TEST(JSON, nested) {
char tmp[1000];
- StringBuilder sb({tmp, sizeof(tmp)});
- JsonBuilder jb(std::move(sb));
+ td::StringBuilder sb(td::MutableSlice{tmp, sizeof(tmp)});
+ td::JsonBuilder jb(std::move(sb));
{
auto a = jb.enter_array();
a << 1;
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/log.cpp b/protocols/Telegram/tdlib/td/tdutils/test/log.cpp
new file mode 100644
index 0000000000..34af21353c
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/log.cpp
@@ -0,0 +1,187 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/AsyncFileLog.h"
+#include "td/utils/benchmark.h"
+#include "td/utils/CombinedLog.h"
+#include "td/utils/FileLog.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/MemoryLog.h"
+#include "td/utils/NullLog.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tests.h"
+#include "td/utils/TsFileLog.h"
+#include "td/utils/TsLog.h"
+
+#include <functional>
+#include <limits>
+
+char disable_linker_warning_about_empty_file_tdutils_test_log_cpp TD_UNUSED;
+
+#if !TD_THREAD_UNSUPPORTED
+template <class Log>
+class LogBenchmark final : public td::Benchmark {
+ public:
+ LogBenchmark(std::string name, int threads_n, bool test_full_logging, std::function<td::unique_ptr<Log>()> creator)
+ : name_(std::move(name))
+ , threads_n_(threads_n)
+ , test_full_logging_(test_full_logging)
+ , creator_(std::move(creator)) {
+ }
+ std::string get_description() const final {
+ return PSTRING() << name_ << " " << (test_full_logging_ ? "ERROR" : "PLAIN") << " "
+ << td::tag("threads_n", threads_n_);
+ }
+ void start_up() final {
+ log_ = creator_();
+ threads_.resize(threads_n_);
+ }
+ void tear_down() final {
+ if (log_ == nullptr) {
+ return;
+ }
+ for (const auto &path : log_->get_file_paths()) {
+ td::unlink(path).ignore();
+ }
+ log_.reset();
+ }
+ void run(int n) final {
+ auto old_log_interface = td::log_interface;
+ if (log_ != nullptr) {
+ td::log_interface = log_.get();
+ }
+
+ for (auto &thread : threads_) {
+ thread = td::thread([this, n] { this->run_thread(n); });
+ }
+ for (auto &thread : threads_) {
+ thread.join();
+ }
+
+ td::log_interface = old_log_interface;
+ }
+
+ void run_thread(int n) {
+ auto str = PSTRING() << "#" << n << " : fsjklfdjsklfjdsklfjdksl\n";
+ for (int i = 0; i < n; i++) {
+ if (i % 10000 == 0 && log_ != nullptr) {
+ log_->after_rotation();
+ }
+ if (test_full_logging_) {
+ LOG(ERROR) << str;
+ } else {
+ LOG(PLAIN) << str;
+ }
+ }
+ }
+
+ private:
+ std::string name_;
+ td::unique_ptr<td::LogInterface> log_;
+ int threads_n_{0};
+ bool test_full_logging_{false};
+ std::function<td::unique_ptr<Log>()> creator_;
+ std::vector<td::thread> threads_;
+};
+
+template <class F>
+static void bench_log(std::string name, F &&f) {
+ for (auto test_full_logging : {false, true}) {
+ for (auto threads_n : {1, 4, 8}) {
+ bench(LogBenchmark<typename decltype(f())::element_type>(name, threads_n, test_full_logging, f));
+ }
+ }
+}
+
+TEST(Log, Bench) {
+ bench_log("NullLog", [] { return td::make_unique<td::NullLog>(); });
+
+ // bench_log("Default", []() -> td::unique_ptr<td::NullLog> { return nullptr; });
+
+ bench_log("MemoryLog", [] { return td::make_unique<td::MemoryLog<1 << 20>>(); });
+
+ bench_log("CombinedLogEmpty", [] { return td::make_unique<td::CombinedLog>(); });
+
+ bench_log("CombinedLogMemory", [] {
+ auto result = td::make_unique<td::CombinedLog>();
+ static td::NullLog null_log;
+ static td::MemoryLog<1 << 20> memory_log;
+ result->set_first(&null_log);
+ result->set_second(&memory_log);
+ result->set_first_verbosity_level(VERBOSITY_NAME(DEBUG));
+ result->set_second_verbosity_level(VERBOSITY_NAME(DEBUG));
+ return result;
+ });
+
+ bench_log("TsFileLog",
+ [] { return td::TsFileLog::create("tmplog", std::numeric_limits<td::int64>::max(), false).move_as_ok(); });
+
+ bench_log("FileLog + TsLog", [] {
+ class FileLog final : public td::LogInterface {
+ public:
+ FileLog() {
+ file_log_.init("tmplog", std::numeric_limits<td::int64>::max(), false).ensure();
+ ts_log_.init(&file_log_);
+ }
+ void do_append(int log_level, td::CSlice slice) final {
+ static_cast<td::LogInterface &>(ts_log_).do_append(log_level, slice);
+ }
+ std::vector<std::string> get_file_paths() final {
+ return file_log_.get_file_paths();
+ }
+
+ private:
+ td::FileLog file_log_;
+ td::TsLog ts_log_{nullptr};
+ };
+ return td::make_unique<FileLog>();
+ });
+
+ bench_log("FileLog", [] {
+ class FileLog final : public td::LogInterface {
+ public:
+ FileLog() {
+ file_log_.init("tmplog", std::numeric_limits<td::int64>::max(), false).ensure();
+ }
+ void do_append(int log_level, td::CSlice slice) final {
+ static_cast<td::LogInterface &>(file_log_).do_append(log_level, slice);
+ }
+ std::vector<std::string> get_file_paths() final {
+ return file_log_.get_file_paths();
+ }
+
+ private:
+ td::FileLog file_log_;
+ };
+ return td::make_unique<FileLog>();
+ });
+
+#if !TD_EVENTFD_UNSUPPORTED
+ bench_log("AsyncFileLog", [] {
+ class AsyncFileLog final : public td::LogInterface {
+ public:
+ AsyncFileLog() {
+ file_log_.init("tmplog", std::numeric_limits<td::int64>::max()).ensure();
+ }
+ void do_append(int log_level, td::CSlice slice) final {
+ static_cast<td::LogInterface &>(file_log_).do_append(log_level, slice);
+ }
+ std::vector<std::string> get_file_paths() final {
+ return static_cast<td::LogInterface &>(file_log_).get_file_paths();
+ }
+
+ private:
+ td::AsyncFileLog file_log_;
+ };
+ return td::make_unique<AsyncFileLog>();
+ });
+#endif
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/misc.cpp b/protocols/Telegram/tdlib/td/tdutils/test/misc.cpp
index dd1f1ec457..7db990dad1 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/misc.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/misc.cpp
@@ -1,47 +1,91 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#include "td/utils/algorithm.h"
+#include "td/utils/as.h"
#include "td/utils/base64.h"
-#include "td/utils/HttpUrl.h"
+#include "td/utils/BigNum.h"
+#include "td/utils/bits.h"
+#include "td/utils/CancellationToken.h"
+#include "td/utils/common.h"
+#include "td/utils/ExitGuard.h"
+#include "td/utils/FloodControlFast.h"
+#include "td/utils/Hash.h"
+#include "td/utils/HashMap.h"
+#include "td/utils/HashSet.h"
+#include "td/utils/HashTableUtils.h"
+#include "td/utils/invoke.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/EventFd.h"
#include "td/utils/port/FileFd.h"
+#include "td/utils/port/IPAddress.h"
#include "td/utils/port/path.h"
#include "td/utils/port/sleep.h"
#include "td/utils/port/Stat.h"
#include "td/utils/port/thread.h"
+#include "td/utils/port/uname.h"
+#include "td/utils/port/wstring_convert.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tests.h"
+#include "td/utils/Time.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/translit.h"
+#include "td/utils/uint128.h"
+#include "td/utils/unicode.h"
+#include "td/utils/utf8.h"
+#include <algorithm>
#include <atomic>
-#include <clocale>
#include <limits>
#include <locale>
+#include <unordered_map>
+#include <utility>
-using namespace td;
+#if TD_HAVE_ABSL
+#include <absl/container/flat_hash_map.h>
+#include <absl/hash/hash.h>
+#endif
+
+struct CheckExitGuard {
+ explicit CheckExitGuard(bool expected_value) : expected_value_(expected_value) {
+ }
+ CheckExitGuard(CheckExitGuard &&) = delete;
+ CheckExitGuard &operator=(CheckExitGuard &&) = delete;
+ CheckExitGuard(const CheckExitGuard &) = delete;
+ CheckExitGuard &operator=(const CheckExitGuard &) = delete;
+ ~CheckExitGuard() {
+ ASSERT_EQ(expected_value_, td::ExitGuard::is_exited());
+ }
+
+ bool expected_value_;
+};
+
+static CheckExitGuard check_exit_guard_true{true};
+static td::ExitGuard exit_guard;
#if TD_LINUX || TD_DARWIN
TEST(Misc, update_atime_saves_mtime) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- std::string name = "test_file";
- unlink(name).ignore();
- auto r_file = FileFd::open(name, FileFd::Read | FileFd::Flags::Create | FileFd::Flags::Truncate);
+ td::string name = "test_file";
+ td::unlink(name).ignore();
+ auto r_file = td::FileFd::open(name, td::FileFd::Read | td::FileFd::Flags::Create | td::FileFd::Flags::Truncate);
LOG_IF(ERROR, r_file.is_error()) << r_file.error();
ASSERT_TRUE(r_file.is_ok());
r_file.move_as_ok().close();
- auto info = stat(name).ok();
- int32 tests_ok = 0;
- int32 tests_wa = 0;
+ auto info = td::stat(name).ok();
+ td::int32 tests_ok = 0;
+ td::int32 tests_wa = 0;
for (int i = 0; i < 10000; i++) {
- update_atime(name).ensure();
- auto new_info = stat(name).ok();
+ td::update_atime(name).ensure();
+ auto new_info = td::stat(name).ok();
if (info.mtime_nsec_ == new_info.mtime_nsec_) {
tests_ok++;
} else {
@@ -49,31 +93,30 @@ TEST(Misc, update_atime_saves_mtime) {
info.mtime_nsec_ = new_info.mtime_nsec_;
}
ASSERT_EQ(info.mtime_nsec_, new_info.mtime_nsec_);
- usleep_for(Random::fast(0, 1000));
+ td::usleep_for(td::Random::fast(0, 1000));
}
if (tests_wa > 0) {
LOG(ERROR) << "Access time was unexpectedly updated " << tests_wa << " times";
}
- unlink(name).ensure();
+ td::unlink(name).ensure();
}
TEST(Misc, update_atime_change_atime) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- std::string name = "test_file";
- unlink(name).ignore();
- auto r_file = FileFd::open(name, FileFd::Read | FileFd::Flags::Create | FileFd::Flags::Truncate);
+ td::string name = "test_file";
+ td::unlink(name).ignore();
+ auto r_file = td::FileFd::open(name, td::FileFd::Read | td::FileFd::Flags::Create | td::FileFd::Flags::Truncate);
LOG_IF(ERROR, r_file.is_error()) << r_file.error();
ASSERT_TRUE(r_file.is_ok());
r_file.move_as_ok().close();
- auto info = stat(name).ok();
+ auto info = td::stat(name).ok();
// not enough for fat and e.t.c.
- usleep_for(5000000);
- update_atime(name).ensure();
- auto new_info = stat(name).ok();
+ td::usleep_for(5000000);
+ td::update_atime(name).ensure();
+ auto new_info = td::stat(name).ok();
if (info.atime_nsec_ == new_info.atime_nsec_) {
LOG(ERROR) << "Access time was unexpectedly not changed";
}
- unlink(name).ensure();
+ td::unlink(name).ensure();
}
#endif
@@ -84,9 +127,9 @@ TEST(Misc, errno_tls_bug) {
// CHECK(errno == 0);
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
- EventFd test_event_fd;
+ td::EventFd test_event_fd;
test_event_fd.init();
- std::atomic<int> s(0);
+ std::atomic<int> s{0};
s = 1;
td::thread th([&] {
while (s != 1) {
@@ -96,21 +139,21 @@ TEST(Misc, errno_tls_bug) {
th.join();
for (int i = 0; i < 1000; i++) {
- vector<EventFd> events(10);
- vector<td::thread> threads;
+ td::vector<td::EventFd> events(10);
+ td::vector<td::thread> threads;
for (auto &event : events) {
event.init();
event.release();
}
for (auto &event : events) {
- threads.push_back(td::thread([&] {
+ threads.emplace_back([&] {
{
- EventFd tmp;
+ td::EventFd tmp;
tmp.init();
tmp.acquire();
}
event.acquire();
- }));
+ });
}
for (auto &thread : threads) {
thread.join();
@@ -119,81 +162,281 @@ TEST(Misc, errno_tls_bug) {
#endif
}
+TEST(Misc, get_last_argument) {
+ auto a = td::make_unique<int>(5);
+ ASSERT_EQ(*td::get_last_argument(std::move(a)), 5);
+ ASSERT_EQ(*td::get_last_argument(1, 2, 3, 4, a), 5);
+ ASSERT_EQ(*td::get_last_argument(a), 5);
+ auto b = td::get_last_argument(1, 2, 3, std::move(a));
+ ASSERT_TRUE(!a);
+ ASSERT_EQ(*b, 5);
+}
+
+TEST(Misc, call_n_arguments) {
+ auto f = [](int, int) {
+ };
+ td::call_n_arguments<2>(f, 1, 3, 4);
+}
+
TEST(Misc, base64) {
- ASSERT_TRUE(is_base64("dGVzdA==") == true);
- ASSERT_TRUE(is_base64("dGVzdB==") == false);
- ASSERT_TRUE(is_base64("dGVzdA=") == false);
- ASSERT_TRUE(is_base64("dGVzdA") == false);
- ASSERT_TRUE(is_base64("dGVz") == true);
- ASSERT_TRUE(is_base64("") == true);
- ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == true);
- ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") == false);
- ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false);
- ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == false);
- ASSERT_TRUE(is_base64("====") == false);
-
- ASSERT_TRUE(is_base64url("dGVzdA==") == true);
- ASSERT_TRUE(is_base64url("dGVzdB==") == false);
- ASSERT_TRUE(is_base64url("dGVzdA=") == false);
- ASSERT_TRUE(is_base64url("dGVzdA") == true);
- ASSERT_TRUE(is_base64url("dGVz") == true);
- ASSERT_TRUE(is_base64url("") == true);
- ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == true);
- ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=") == false);
- ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false);
- ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == false);
- ASSERT_TRUE(is_base64url("====") == false);
+ ASSERT_TRUE(td::is_base64("dGVzdA==") == true);
+ ASSERT_TRUE(td::is_base64("dGVzdB==") == false);
+ ASSERT_TRUE(td::is_base64("dGVzdA=") == false);
+ ASSERT_TRUE(td::is_base64("dGVzdA") == false);
+ ASSERT_TRUE(td::is_base64("dGVzd") == false);
+ ASSERT_TRUE(td::is_base64("dGVz") == true);
+ ASSERT_TRUE(td::is_base64("dGVz====") == false);
+ ASSERT_TRUE(td::is_base64("") == true);
+ ASSERT_TRUE(td::is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == true);
+ ASSERT_TRUE(td::is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") == false);
+ ASSERT_TRUE(td::is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false);
+ ASSERT_TRUE(td::is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == false);
+ ASSERT_TRUE(td::is_base64("====") == false);
+
+ ASSERT_TRUE(td::is_base64url("dGVzdA==") == true);
+ ASSERT_TRUE(td::is_base64url("dGVzdB==") == false);
+ ASSERT_TRUE(td::is_base64url("dGVzdA=") == false);
+ ASSERT_TRUE(td::is_base64url("dGVzdA") == true);
+ ASSERT_TRUE(td::is_base64url("dGVzd") == false);
+ ASSERT_TRUE(td::is_base64url("dGVz") == true);
+ ASSERT_TRUE(td::is_base64url("dGVz====") == false);
+ ASSERT_TRUE(td::is_base64url("") == true);
+ ASSERT_TRUE(td::is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == true);
+ ASSERT_TRUE(td::is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=") == false);
+ ASSERT_TRUE(td::is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false);
+ ASSERT_TRUE(td::is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == false);
+ ASSERT_TRUE(td::is_base64url("====") == false);
+
+ ASSERT_TRUE(td::is_base64_characters("dGVzdA==") == false);
+ ASSERT_TRUE(td::is_base64_characters("dGVzdB==") == false);
+ ASSERT_TRUE(td::is_base64_characters("dGVzdA=") == false);
+ ASSERT_TRUE(td::is_base64_characters("dGVzdA") == true);
+ ASSERT_TRUE(td::is_base64_characters("dGVz") == true);
+ ASSERT_TRUE(td::is_base64_characters("") == true);
+ ASSERT_TRUE(td::is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == true);
+ ASSERT_TRUE(td::is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") == false);
+ ASSERT_TRUE(td::is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false);
+ ASSERT_TRUE(td::is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == false);
+ ASSERT_TRUE(td::is_base64_characters("====") == false);
+
+ ASSERT_TRUE(td::is_base64url_characters("dGVzdA==") == false);
+ ASSERT_TRUE(td::is_base64url_characters("dGVzdB==") == false);
+ ASSERT_TRUE(td::is_base64url_characters("dGVzdA=") == false);
+ ASSERT_TRUE(td::is_base64url_characters("dGVzdA") == true);
+ ASSERT_TRUE(td::is_base64url_characters("dGVz") == true);
+ ASSERT_TRUE(td::is_base64url_characters("") == true);
+ ASSERT_TRUE(td::is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == true);
+ ASSERT_TRUE(td::is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=") ==
+ false);
+ ASSERT_TRUE(td::is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false);
+ ASSERT_TRUE(td::is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == false);
+ ASSERT_TRUE(td::is_base64url_characters("====") == false);
for (int l = 0; l < 300000; l += l / 20 + l / 1000 * 500 + 1) {
for (int t = 0; t < 10; t++) {
- string s = rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), l);
- string encoded = base64url_encode(s);
- auto decoded = base64url_decode(encoded);
+ auto s = td::rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), l);
+ auto encoded = td::base64url_encode(s);
+ auto decoded = td::base64url_decode(encoded);
ASSERT_TRUE(decoded.is_ok());
ASSERT_TRUE(decoded.ok() == s);
- encoded = base64_encode(s);
- decoded = base64_decode(encoded);
+ encoded = td::base64_encode(s);
+ decoded = td::base64_decode(encoded);
ASSERT_TRUE(decoded.is_ok());
ASSERT_TRUE(decoded.ok() == s);
+
+ auto decoded_secure = td::base64_decode_secure(encoded);
+ ASSERT_TRUE(decoded_secure.is_ok());
+ ASSERT_TRUE(decoded_secure.ok().as_slice() == s);
}
}
- ASSERT_TRUE(base64url_decode("dGVzdA").is_ok());
- ASSERT_TRUE(base64url_decode("dGVzdB").is_error());
- ASSERT_TRUE(base64_encode(base64url_decode("dGVzdA").ok()) == "dGVzdA==");
- ASSERT_TRUE(base64_encode("any carnal pleas") == "YW55IGNhcm5hbCBwbGVhcw==");
- ASSERT_TRUE(base64_encode("any carnal pleasu") == "YW55IGNhcm5hbCBwbGVhc3U=");
- ASSERT_TRUE(base64_encode("any carnal pleasur") == "YW55IGNhcm5hbCBwbGVhc3Vy");
- ASSERT_TRUE(base64_encode(" /'.;.';≤.];,].',[.;/,.;/]/..;!@#!*(%?::;!%\";") ==
- "ICAgICAgLycuOy4nO+KJpC5dOyxdLicsWy47LywuOy9dLy4uOyFAIyEqKCU/"
- "Ojo7ISUiOw==");
+ ASSERT_TRUE(td::base64url_decode("dGVzdA").is_ok());
+ ASSERT_TRUE(td::base64url_decode("dGVzdB").is_error());
+ ASSERT_TRUE(td::base64_encode(td::base64url_decode("dGVzdA").ok()) == "dGVzdA==");
+ ASSERT_TRUE(td::base64_encode("any carnal pleas") == "YW55IGNhcm5hbCBwbGVhcw==");
+ ASSERT_TRUE(td::base64_encode("any carnal pleasu") == "YW55IGNhcm5hbCBwbGVhc3U=");
+ ASSERT_TRUE(td::base64_encode("any carnal pleasur") == "YW55IGNhcm5hbCBwbGVhc3Vy");
+ ASSERT_TRUE(td::base64_encode(" /'.;.';≤.];,].',[.;/,.;/]/..;!@#!*(%?::;!%\";") ==
+ "ICAgICAgLycuOy4nO+KJpC5dOyxdLicsWy47LywuOy9dLy4uOyFAIyEqKCU/Ojo7ISUiOw==");
+ ASSERT_TRUE(td::base64url_encode("ab><") == "YWI-PA");
+ ASSERT_TRUE(td::base64url_encode("ab><c") == "YWI-PGM");
+ ASSERT_TRUE(td::base64url_encode("ab><cd") == "YWI-PGNk");
+}
+
+template <class T>
+static void test_remove_if(td::vector<int> v, const T &func, const td::vector<int> &expected) {
+ td::remove_if(v, func);
+ if (expected != v) {
+ LOG(FATAL) << "Receive " << v << ", expected " << expected << " in remove_if";
+ }
+}
+
+TEST(Misc, remove_if) {
+ auto odd = [](int x) {
+ return x % 2 == 1;
+ };
+ auto even = [](int x) {
+ return x % 2 == 0;
+ };
+ auto all = [](int x) {
+ return true;
+ };
+ auto none = [](int x) {
+ return false;
+ };
+
+ td::vector<int> v{1, 2, 3, 4, 5, 6};
+ test_remove_if(v, odd, {2, 4, 6});
+ test_remove_if(v, even, {1, 3, 5});
+ test_remove_if(v, all, {});
+ test_remove_if(v, none, v);
+
+ v = td::vector<int>{1, 3, 5, 2, 4, 6};
+ test_remove_if(v, odd, {2, 4, 6});
+ test_remove_if(v, even, {1, 3, 5});
+ test_remove_if(v, all, {});
+ test_remove_if(v, none, v);
+
+ v.clear();
+ test_remove_if(v, odd, v);
+ test_remove_if(v, even, v);
+ test_remove_if(v, all, v);
+ test_remove_if(v, none, v);
+
+ v.push_back(-1);
+ test_remove_if(v, odd, v);
+ test_remove_if(v, even, v);
+ test_remove_if(v, all, {});
+ test_remove_if(v, none, v);
+
+ v[0] = 1;
+ test_remove_if(v, odd, {});
+ test_remove_if(v, even, v);
+ test_remove_if(v, all, {});
+ test_remove_if(v, none, v);
+
+ v[0] = 2;
+ test_remove_if(v, odd, v);
+ test_remove_if(v, even, {});
+ test_remove_if(v, all, {});
+ test_remove_if(v, none, v);
+}
+
+static void test_remove(td::vector<int> v, int value, const td::vector<int> &expected) {
+ bool is_found = expected != v;
+ ASSERT_EQ(is_found, td::remove(v, value));
+ if (expected != v) {
+ LOG(FATAL) << "Receive " << v << ", expected " << expected << " in remove";
+ }
+}
+
+TEST(Misc, remove) {
+ td::vector<int> v{1, 2, 3, 4, 5, 6};
+ test_remove(v, 0, {1, 2, 3, 4, 5, 6});
+ test_remove(v, 1, {2, 3, 4, 5, 6});
+ test_remove(v, 2, {1, 3, 4, 5, 6});
+ test_remove(v, 3, {1, 2, 4, 5, 6});
+ test_remove(v, 4, {1, 2, 3, 5, 6});
+ test_remove(v, 5, {1, 2, 3, 4, 6});
+ test_remove(v, 6, {1, 2, 3, 4, 5});
+ test_remove(v, 7, {1, 2, 3, 4, 5, 6});
+
+ v.clear();
+ test_remove(v, -1, v);
+ test_remove(v, 0, v);
+ test_remove(v, 1, v);
+}
+
+static void test_unique(td::vector<int> v, const td::vector<int> &expected) {
+ auto v_str = td::transform(v, &td::to_string<int>);
+ auto expected_str = td::transform(expected, &td::to_string<int>);
+
+ td::unique(v);
+ ASSERT_EQ(expected, v);
+
+ td::unique(v_str);
+ ASSERT_EQ(expected_str, v_str);
+}
+
+TEST(Misc, unique) {
+ test_unique({1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6});
+ test_unique({5, 2, 1, 6, 3, 4}, {1, 2, 3, 4, 5, 6});
+ test_unique({}, {});
+ test_unique({0}, {0});
+ test_unique({0, 0}, {0});
+ test_unique({0, 1}, {0, 1});
+ test_unique({1, 0}, {0, 1});
+ test_unique({1, 1}, {1});
+ test_unique({1, 1, 0, 0}, {0, 1});
+ test_unique({3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 0}, {0, 1, 2, 3});
+ test_unique({3, 3, 3, 3, 3}, {3});
+ test_unique({3, 3, -1, 3, 3}, {-1, 3});
+}
+
+TEST(Misc, contains) {
+ td::vector<int> v{1, 3, 5, 7, 4, 2};
+ for (int i = -10; i < 20; i++) {
+ ASSERT_EQ(td::contains(v, i), (1 <= i && i <= 5) || i == 7);
+ }
+
+ v.clear();
+ ASSERT_TRUE(!td::contains(v, 0));
+ ASSERT_TRUE(!td::contains(v, 1));
+
+ td::string str = "abacaba";
+ ASSERT_TRUE(!td::contains(str, '0'));
+ ASSERT_TRUE(!td::contains(str, 0));
+ ASSERT_TRUE(!td::contains(str, 'd'));
+ ASSERT_TRUE(td::contains(str, 'a'));
+ ASSERT_TRUE(td::contains(str, 'b'));
+ ASSERT_TRUE(td::contains(str, 'c'));
+}
+
+TEST(Misc, base32) {
+ ASSERT_EQ("", td::base32_encode(""));
+ ASSERT_EQ("me", td::base32_encode("a"));
+ td::base32_decode("me").ensure();
+ ASSERT_EQ("mfra", td::base32_encode("ab"));
+ ASSERT_EQ("mfrgg", td::base32_encode("abc"));
+ ASSERT_EQ("mfrggza", td::base32_encode("abcd"));
+ ASSERT_EQ("mfrggzdg", td::base32_encode("abcdf"));
+ ASSERT_EQ("mfrggzdgm4", td::base32_encode("abcdfg"));
+ for (int l = 0; l < 300000; l += l / 20 + l / 1000 * 500 + 1) {
+ for (int t = 0; t < 10; t++) {
+ auto s = td::rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), l);
+ auto encoded = td::base32_encode(s);
+ auto decoded = td::base32_decode(encoded);
+ ASSERT_TRUE(decoded.is_ok());
+ ASSERT_TRUE(decoded.ok() == s);
+ }
+ }
}
TEST(Misc, to_integer) {
- ASSERT_EQ(to_integer<int32>("-1234567"), -1234567);
- ASSERT_EQ(to_integer<int64>("-1234567"), -1234567);
- ASSERT_EQ(to_integer<uint32>("-1234567"), 0u);
- ASSERT_EQ(to_integer<int16>("-1234567"), 10617);
- ASSERT_EQ(to_integer<uint16>("-1234567"), 0u);
- ASSERT_EQ(to_integer<int16>("-1254567"), -9383);
- ASSERT_EQ(to_integer<uint16>("1254567"), 9383u);
- ASSERT_EQ(to_integer<int64>("-12345678910111213"), -12345678910111213);
- ASSERT_EQ(to_integer<uint64>("12345678910111213"), 12345678910111213ull);
-
- ASSERT_EQ(to_integer_safe<int32>("-1234567").ok(), -1234567);
- ASSERT_EQ(to_integer_safe<int64>("-1234567").ok(), -1234567);
- ASSERT_TRUE(to_integer_safe<uint32>("-1234567").is_error());
- ASSERT_TRUE(to_integer_safe<int16>("-1234567").is_error());
- ASSERT_TRUE(to_integer_safe<uint16>("-1234567").is_error());
- ASSERT_TRUE(to_integer_safe<int16>("-1254567").is_error());
- ASSERT_TRUE(to_integer_safe<uint16>("1254567").is_error());
- ASSERT_EQ(to_integer_safe<int64>("-12345678910111213").ok(), -12345678910111213);
- ASSERT_EQ(to_integer_safe<uint64>("12345678910111213").ok(), 12345678910111213ull);
- ASSERT_TRUE(to_integer_safe<uint64>("-12345678910111213").is_error());
-}
-
-static void test_to_double_one(CSlice str, Slice expected, int precision = 6) {
+ ASSERT_EQ(td::to_integer<td::int32>("-1234567"), -1234567);
+ ASSERT_EQ(td::to_integer<td::int64>("-1234567"), -1234567);
+ ASSERT_EQ(td::to_integer<td::uint32>("-1234567"), 0u);
+ ASSERT_EQ(td::to_integer<td::int16>("-1234567"), 10617);
+ ASSERT_EQ(td::to_integer<td::uint16>("-1234567"), 0u);
+ ASSERT_EQ(td::to_integer<td::int16>("-1254567"), -9383);
+ ASSERT_EQ(td::to_integer<td::uint16>("1254567"), 9383u);
+ ASSERT_EQ(td::to_integer<td::int64>("-12345678910111213"), -12345678910111213);
+ ASSERT_EQ(td::to_integer<td::uint64>("12345678910111213"), 12345678910111213ull);
+
+ ASSERT_EQ(td::to_integer_safe<td::int32>("-1234567").ok(), -1234567);
+ ASSERT_EQ(td::to_integer_safe<td::int64>("-1234567").ok(), -1234567);
+ ASSERT_TRUE(td::to_integer_safe<td::uint32>("-1234567").is_error());
+ ASSERT_TRUE(td::to_integer_safe<td::int16>("-1234567").is_error());
+ ASSERT_TRUE(td::to_integer_safe<td::uint16>("-1234567").is_error());
+ ASSERT_TRUE(td::to_integer_safe<td::int16>("-1254567").is_error());
+ ASSERT_TRUE(td::to_integer_safe<td::uint16>("1254567").is_error());
+ ASSERT_EQ(td::to_integer_safe<td::int64>("-12345678910111213").ok(), -12345678910111213);
+ ASSERT_EQ(td::to_integer_safe<td::uint64>("12345678910111213").ok(), 12345678910111213ull);
+ ASSERT_TRUE(td::to_integer_safe<td::uint64>("-12345678910111213").is_error());
+}
+
+static void test_to_double_one(td::CSlice str, td::Slice expected, int precision = 6) {
auto result = PSTRING() << td::StringBuilder::FixedDouble(to_double(str), precision);
if (expected != result) {
LOG(ERROR) << "To double conversion failed: have " << str << ", expected " << expected << ", parsed "
@@ -234,29 +477,786 @@ static void test_to_double() {
TEST(Misc, to_double) {
test_to_double();
- const char *locale_name = (std::setlocale(LC_ALL, "fr-FR") == nullptr ? "" : "fr-FR");
- std::locale new_locale(locale_name);
- std::locale::global(new_locale);
+ std::locale new_locale("C");
+ auto host_locale = std::locale::global(new_locale);
+ test_to_double();
+ new_locale = std::locale::global(std::locale::classic());
test_to_double();
- std::locale::global(std::locale::classic());
+ auto classic_locale = std::locale::global(host_locale);
test_to_double();
}
-static void test_get_url_query_file_name_one(const char *prefix, const char *suffix, const char *file_name) {
- auto path = string(prefix) + string(file_name) + string(suffix);
- ASSERT_STREQ(file_name, get_url_query_file_name(path));
- ASSERT_STREQ(file_name, get_url_file_name("http://telegram.org" + path));
- ASSERT_STREQ(file_name, get_url_file_name("http://telegram.org:80" + path));
- ASSERT_STREQ(file_name, get_url_file_name("telegram.org" + path));
+TEST(Misc, print_int) {
+ ASSERT_STREQ("-9223372036854775808", PSLICE() << -9223372036854775807 - 1);
+ ASSERT_STREQ("-2147483649", PSLICE() << -2147483649ll);
+ ASSERT_STREQ("-2147483648", PSLICE() << -2147483647 - 1);
+ ASSERT_STREQ("-2147483647", PSLICE() << -2147483647);
+ ASSERT_STREQ("-123456789", PSLICE() << -123456789);
+ ASSERT_STREQ("-1", PSLICE() << -1);
+ ASSERT_STREQ("0", PSLICE() << 0);
+ ASSERT_STREQ("1", PSLICE() << 1);
+ ASSERT_STREQ("9", PSLICE() << 9);
+ ASSERT_STREQ("10", PSLICE() << 10);
+ ASSERT_STREQ("2147483647", PSLICE() << 2147483647);
+ ASSERT_STREQ("2147483648", PSLICE() << 2147483648ll);
+ ASSERT_STREQ("2147483649", PSLICE() << 2147483649ll);
+ ASSERT_STREQ("9223372036854775807", PSLICE() << 9223372036854775807ll);
+}
+
+TEST(Misc, print_uint) {
+ ASSERT_STREQ("0", PSLICE() << 0u);
+ ASSERT_STREQ("1", PSLICE() << 1u);
+ ASSERT_STREQ("9", PSLICE() << 9u);
+ ASSERT_STREQ("10", PSLICE() << 10u);
+ ASSERT_STREQ("2147483647", PSLICE() << 2147483647u);
+ ASSERT_STREQ("2147483648", PSLICE() << 2147483648u);
+ ASSERT_STREQ("2147483649", PSLICE() << 2147483649u);
+ ASSERT_STREQ("9223372036854775807", PSLICE() << 9223372036854775807u);
+}
+
+static void test_idn_to_ascii_one(const td::string &host, const td::string &result) {
+ if (result != td::idn_to_ascii(host).ok()) {
+ LOG(ERROR) << "Failed to convert " << host << " to " << result << ", got \"" << td::idn_to_ascii(host).ok() << "\"";
+ }
+}
+
+TEST(Misc, idn_to_ascii) {
+ test_idn_to_ascii_one("::::::::::::::::::::::::::::::::::::::@/", "::::::::::::::::::::::::::::::::::::::@/");
+ test_idn_to_ascii_one("", "");
+ test_idn_to_ascii_one("%30", "%30");
+ test_idn_to_ascii_one("127.0.0.1", "127.0.0.1");
+ test_idn_to_ascii_one("fe80::", "fe80::");
+ test_idn_to_ascii_one("fe80:0:0:0:200:f8ff:fe21:67cf", "fe80:0:0:0:200:f8ff:fe21:67cf");
+ test_idn_to_ascii_one("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d", "2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d");
+ test_idn_to_ascii_one("::ffff:192.0.2.1", "::ffff:192.0.2.1");
+ test_idn_to_ascii_one("ABCDEF", "abcdef");
+ test_idn_to_ascii_one("abcdef", "abcdef");
+ test_idn_to_ascii_one("abæcdöef", "xn--abcdef-qua4k");
+ test_idn_to_ascii_one("schön", "xn--schn-7qa");
+ test_idn_to_ascii_one("ยจฆฟคฏข", "xn--22cdfh1b8fsa");
+ test_idn_to_ascii_one("☺", "xn--74h");
+ test_idn_to_ascii_one("правда", "xn--80aafi6cg");
+ test_idn_to_ascii_one("büücher", "xn--bcher-kvaa");
+ test_idn_to_ascii_one("BüüCHER", "xn--bcher-kvaa");
+ test_idn_to_ascii_one("bücüher", "xn--bcher-kvab");
+ test_idn_to_ascii_one("bücherü", "xn--bcher-kvae");
+ test_idn_to_ascii_one("ýbücher", "xn--bcher-kvaf");
+ test_idn_to_ascii_one("übücher", "xn--bcher-jvab");
+ test_idn_to_ascii_one("bücher.tld", "xn--bcher-kva.tld");
+ test_idn_to_ascii_one("кто.рф", "xn--j1ail.xn--p1ai");
+ test_idn_to_ascii_one("wіkіреdіа.org", "xn--wkd-8cdx9d7hbd.org");
+ test_idn_to_ascii_one("cnwin2k8中国.avol.com", "xn--cnwin2k8-sd0mx14e.avol.com");
+ test_idn_to_ascii_one("win-2k12r2-addc.阿伯测阿伯测ad.hai.com", "win-2k12r2-addc.xn--ad-tl3ca3569aba8944eca.hai.com");
+ test_idn_to_ascii_one("✌.ws", "xn--7bi.ws");
+ // test_idn_to_ascii_one("✌️.ws", "xn--7bi.ws"); // needs nameprep to succeed
+ test_idn_to_ascii_one("⛧", "xn--59h");
+ test_idn_to_ascii_one("--рф.рф", "xn-----mmcq.xn--p1ai");
+ ASSERT_TRUE(td::idn_to_ascii("\xc0").is_error());
+}
+
+#if TD_WINDOWS
+static void test_to_wstring_one(const td::string &str) {
+ ASSERT_STREQ(str, td::from_wstring(td::to_wstring(str).ok()).ok());
+}
+
+TEST(Misc, to_wstring) {
+ test_to_wstring_one("");
+ for (int i = 0; i < 10; i++) {
+ test_to_wstring_one("test");
+ test_to_wstring_one("тест");
+ }
+ td::string str;
+ for (td::uint32 i = 0; i <= 0xD7FF; i++) {
+ td::append_utf8_character(str, i);
+ }
+ for (td::uint32 i = 0xE000; i <= 0x10FFFF; i++) {
+ td::append_utf8_character(str, i);
+ }
+ test_to_wstring_one(str);
+ ASSERT_TRUE(td::to_wstring("\xc0").is_error());
+ auto emoji = td::to_wstring("🏟").ok();
+ ASSERT_TRUE(td::from_wstring(emoji).ok() == "🏟");
+ ASSERT_TRUE(emoji.size() == 2);
+ auto emoji2 = emoji;
+ emoji[0] = emoji[1];
+ emoji2[1] = emoji2[0];
+ ASSERT_TRUE(td::from_wstring(emoji).is_error());
+ ASSERT_TRUE(td::from_wstring(emoji2).is_error());
+ emoji2[0] = emoji[0];
+ ASSERT_TRUE(td::from_wstring(emoji2).is_error());
+}
+#endif
+
+static void test_translit(const td::string &word, const td::vector<td::string> &result, bool allow_partial = true) {
+ ASSERT_EQ(result, td::get_word_transliterations(word, allow_partial));
+}
+
+TEST(Misc, translit) {
+ test_translit("word", {"word", "ворд"});
+ test_translit("", {});
+ test_translit("ььььььььь", {"ььььььььь"});
+ test_translit("крыло", {"krylo", "крыло"});
+ test_translit("krylo", {"krylo", "крило"});
+ test_translit("crylo", {"crylo", "крило"});
+ test_translit("cheiia", {"cheiia", "кхеииа", "чейия"});
+ test_translit("cheii", {"cheii", "кхеии", "чейи", "чейий", "чейия"});
+ test_translit("s", {"s", "с", "ш", "щ"});
+ test_translit("y", {"e", "y", "е", "и", "ю", "я"});
+ test_translit("j", {"e", "j", "е", "й", "ю", "я"});
+ test_translit("yo", {"e", "yo", "е", "ио"});
+ test_translit("artjom", {"artem", "artjom", "артем", "артйом"});
+ test_translit("artyom", {"artem", "artyom", "артем", "артиом"});
+ test_translit("arty", {"arte", "arty", "арте", "арти", "артю", "артя"});
+ test_translit("льи", {"li", "lia", "ly", "льи"});
+ test_translit("y", {"y", "и"}, false);
+ test_translit("yo", {"e", "yo", "е", "ио"}, false);
+}
+
+static void test_unicode(td::uint32 (*func)(td::uint32)) {
+ for (td::uint32 i = 0; i <= 0x110000; i++) {
+ auto res = func(i);
+ CHECK(res <= 0x10ffff);
+ }
+}
+
+TEST(Misc, unicode) {
+ test_unicode(td::prepare_search_character);
+ test_unicode(td::unicode_to_lower);
+ test_unicode(td::remove_diacritics);
+}
+
+TEST(Misc, get_unicode_simple_category) {
+ td::uint32 result = 0;
+ for (size_t t = 0; t < 100; t++) {
+ for (td::uint32 i = 0; i <= 0x10ffff; i++) {
+ result = result * 123 + static_cast<td::uint32>(static_cast<int>(td::get_unicode_simple_category(i)));
+ }
+ }
+ LOG(INFO) << result;
+}
+
+TEST(Misc, get_unicode_simple_category_small) {
+ td::uint32 result = 0;
+ for (size_t t = 0; t < 1000; t++) {
+ for (td::uint32 i = 0; i <= 0xffff; i++) {
+ result = result * 123 + static_cast<td::uint32>(static_cast<int>(td::get_unicode_simple_category(i)));
+ }
+ }
+ LOG(INFO) << result;
+}
+
+TEST(BigNum, from_decimal) {
+ ASSERT_TRUE(td::BigNum::from_decimal("").is_error());
+ ASSERT_TRUE(td::BigNum::from_decimal("a").is_error());
+ ASSERT_TRUE(td::BigNum::from_decimal("123a").is_error());
+ ASSERT_TRUE(td::BigNum::from_decimal("-123a").is_error());
+ // ASSERT_TRUE(td::BigNum::from_decimal("-").is_error());
+ ASSERT_TRUE(td::BigNum::from_decimal("123").is_ok());
+ ASSERT_TRUE(td::BigNum::from_decimal("-123").is_ok());
+ ASSERT_TRUE(td::BigNum::from_decimal("0").is_ok());
+ ASSERT_TRUE(td::BigNum::from_decimal("-0").is_ok());
+ ASSERT_TRUE(td::BigNum::from_decimal("-999999999999999999999999999999999999999999999999").is_ok());
+ ASSERT_TRUE(td::BigNum::from_decimal("999999999999999999999999999999999999999999999999").is_ok());
+}
+
+TEST(BigNum, from_binary) {
+ ASSERT_STREQ(td::BigNum::from_binary("").to_decimal(), "0");
+ ASSERT_STREQ(td::BigNum::from_binary("a").to_decimal(), "97");
+ ASSERT_STREQ(td::BigNum::from_binary("\x00\xff").to_decimal(), "255");
+ ASSERT_STREQ(td::BigNum::from_binary("\x00\x01\x00\x00").to_decimal(), "65536");
+ ASSERT_STREQ(td::BigNum::from_le_binary("").to_decimal(), "0");
+ ASSERT_STREQ(td::BigNum::from_le_binary("a").to_decimal(), "97");
+ ASSERT_STREQ(td::BigNum::from_le_binary("\x00\xff").to_decimal(), "65280");
+ ASSERT_STREQ(td::BigNum::from_le_binary("\x00\x01\x00\x00").to_decimal(), "256");
+ ASSERT_STREQ(td::BigNum::from_decimal("255").move_as_ok().to_binary(), "\xff");
+ ASSERT_STREQ(td::BigNum::from_decimal("255").move_as_ok().to_le_binary(), "\xff");
+ ASSERT_STREQ(td::BigNum::from_decimal("255").move_as_ok().to_binary(2), "\x00\xff");
+ ASSERT_STREQ(td::BigNum::from_decimal("255").move_as_ok().to_le_binary(2), "\xff\x00");
+ ASSERT_STREQ(td::BigNum::from_decimal("65280").move_as_ok().to_binary(), "\xff\x00");
+ ASSERT_STREQ(td::BigNum::from_decimal("65280").move_as_ok().to_le_binary(), "\x00\xff");
+ ASSERT_STREQ(td::BigNum::from_decimal("65280").move_as_ok().to_binary(2), "\xff\x00");
+ ASSERT_STREQ(td::BigNum::from_decimal("65280").move_as_ok().to_le_binary(2), "\x00\xff");
+ ASSERT_STREQ(td::BigNum::from_decimal("65536").move_as_ok().to_binary(), "\x01\x00\x00");
+ ASSERT_STREQ(td::BigNum::from_decimal("65536").move_as_ok().to_le_binary(), "\x00\x00\x01");
+ ASSERT_STREQ(td::BigNum::from_decimal("65536").move_as_ok().to_binary(4), "\x00\x01\x00\x00");
+ ASSERT_STREQ(td::BigNum::from_decimal("65536").move_as_ok().to_le_binary(4), "\x00\x00\x01\x00");
+}
+
+static void test_get_ipv4(td::uint32 ip) {
+ td::IPAddress ip_address;
+ ip_address.init_ipv4_port(td::IPAddress::ipv4_to_str(ip), 80).ensure();
+ ASSERT_EQ(ip_address.get_ipv4(), ip);
+}
+
+TEST(Misc, IPAddress_get_ipv4) {
+ test_get_ipv4(0x00000000);
+ test_get_ipv4(0x010000FF);
+ test_get_ipv4(0xFF000001);
+ test_get_ipv4(0x01020304);
+ test_get_ipv4(0x04030201);
+ test_get_ipv4(0xFFFFFFFF);
+}
+
+static void test_is_reserved(const td::string &ip, bool is_reserved) {
+ td::IPAddress ip_address;
+ ip_address.init_ipv4_port(ip, 80).ensure();
+ ASSERT_EQ(is_reserved, ip_address.is_reserved());
+}
+
+TEST(Misc, IPAddress_is_reserved) {
+ test_is_reserved("0.0.0.0", true);
+ test_is_reserved("0.255.255.255", true);
+ test_is_reserved("1.0.0.0", false);
+ test_is_reserved("5.0.0.0", false);
+ test_is_reserved("9.255.255.255", false);
+ test_is_reserved("10.0.0.0", true);
+ test_is_reserved("10.255.255.255", true);
+ test_is_reserved("11.0.0.0", false);
+ test_is_reserved("100.63.255.255", false);
+ test_is_reserved("100.64.0.0", true);
+ test_is_reserved("100.127.255.255", true);
+ test_is_reserved("100.128.0.0", false);
+ test_is_reserved("126.255.255.255", false);
+ test_is_reserved("127.0.0.0", true);
+ test_is_reserved("127.255.255.255", true);
+ test_is_reserved("128.0.0.0", false);
+ test_is_reserved("169.253.255.255", false);
+ test_is_reserved("169.254.0.0", true);
+ test_is_reserved("169.254.255.255", true);
+ test_is_reserved("169.255.0.0", false);
+ test_is_reserved("172.15.255.255", false);
+ test_is_reserved("172.16.0.0", true);
+ test_is_reserved("172.31.255.255", true);
+ test_is_reserved("172.32.0.0", false);
+ test_is_reserved("191.255.255.255", false);
+ test_is_reserved("192.0.0.0", true);
+ test_is_reserved("192.0.0.255", true);
+ test_is_reserved("192.0.1.0", false);
+ test_is_reserved("192.0.1.255", false);
+ test_is_reserved("192.0.2.0", true);
+ test_is_reserved("192.0.2.255", true);
+ test_is_reserved("192.0.3.0", false);
+ test_is_reserved("192.88.98.255", false);
+ test_is_reserved("192.88.99.0", true);
+ test_is_reserved("192.88.99.255", true);
+ test_is_reserved("192.88.100.0", false);
+ test_is_reserved("192.167.255.255", false);
+ test_is_reserved("192.168.0.0", true);
+ test_is_reserved("192.168.255.255", true);
+ test_is_reserved("192.169.0.0", false);
+ test_is_reserved("198.17.255.255", false);
+ test_is_reserved("198.18.0.0", true);
+ test_is_reserved("198.19.255.255", true);
+ test_is_reserved("198.20.0.0", false);
+ test_is_reserved("198.51.99.255", false);
+ test_is_reserved("198.51.100.0", true);
+ test_is_reserved("198.51.100.255", true);
+ test_is_reserved("198.51.101.0", false);
+ test_is_reserved("203.0.112.255", false);
+ test_is_reserved("203.0.113.0", true);
+ test_is_reserved("203.0.113.255", true);
+ test_is_reserved("203.0.114.0", false);
+ test_is_reserved("223.255.255.255", false);
+ test_is_reserved("224.0.0.0", true);
+ test_is_reserved("239.255.255.255", true);
+ test_is_reserved("240.0.0.0", true);
+ test_is_reserved("255.255.255.254", true);
+ test_is_reserved("255.255.255.255", true);
+}
+
+TEST(Misc, ipv6_clear) {
+ td::IPAddress ip_address;
+ ip_address.init_host_port("2001:0db8:85a3:0000:0000:8a2e:0370:7334", 123).ensure();
+ ASSERT_EQ("2001:db8:85a3::8a2e:370:7334", ip_address.get_ip_str());
+ ip_address.clear_ipv6_interface();
+ ASSERT_EQ("2001:db8:85a3::", ip_address.get_ip_str());
+}
+
+static void test_split(td::Slice str, std::pair<td::Slice, td::Slice> expected) {
+ ASSERT_EQ(expected, td::split(str));
+}
+
+TEST(Misc, split) {
+ test_split("", {"", ""});
+ test_split(" ", {"", ""});
+ test_split("abcdef", {"abcdef", ""});
+ test_split("abc def", {"abc", "def"});
+ test_split("a bcdef", {"a", "bcdef"});
+ test_split(" abcdef", {"", "abcdef"});
+ test_split("abcdef ", {"abcdef", ""});
+ test_split("ab cd ef", {"ab", "cd ef"});
+ test_split("ab cdef ", {"ab", "cdef "});
+ test_split(" abcd ef", {"", "abcd ef"});
+ test_split(" abcdef ", {"", "abcdef "});
}
-TEST(Misc, get_url_query_file_name) {
- for (auto suffix : {"?t=1#test", "#test?t=1", "#?t=1", "?t=1#", "#test", "?t=1", "#", "?", ""}) {
- test_get_url_query_file_name_one("", suffix, "");
- test_get_url_query_file_name_one("/", suffix, "");
- test_get_url_query_file_name_one("/a/adasd/", suffix, "");
- test_get_url_query_file_name_one("/a/lklrjetn/", suffix, "adasd.asdas");
- test_get_url_query_file_name_one("/", suffix, "a123asadas");
- test_get_url_query_file_name_one("/", suffix, "\\a\\1\\2\\3\\a\\s\\a\\das");
+static void test_full_split(td::Slice str, const td::vector<td::Slice> &expected) {
+ ASSERT_EQ(expected, td::full_split(str));
+}
+
+static void test_full_split(td::Slice str, char c, std::size_t max_parts, const td::vector<td::Slice> &expected) {
+ ASSERT_EQ(expected, td::full_split(str, c, max_parts));
+}
+
+TEST(Misc, full_split) {
+ test_full_split("", {});
+ test_full_split(" ", {"", ""});
+ test_full_split(" ", {"", "", ""});
+ test_full_split("abcdef", {"abcdef"});
+ test_full_split("abc def", {"abc", "def"});
+ test_full_split("a bcdef", {"a", "bcdef"});
+ test_full_split(" abcdef", {"", "abcdef"});
+ test_full_split("abcdef ", {"abcdef", ""});
+ test_full_split("ab cd ef", {"ab", "cd", "ef"});
+ test_full_split("ab cdef ", {"ab", "cdef", ""});
+ test_full_split(" abcd ef", {"", "abcd", "ef"});
+ test_full_split(" abcdef ", {"", "abcdef", ""});
+ test_full_split(" ab cd ef ", {"", "ab", "cd", "ef", ""});
+ test_full_split(" ab cd ef ", {"", "", "ab", "", "cd", "", "ef", "", ""});
+ test_full_split("ab cd ef gh", ' ', 3, {"ab", "cd", "ef gh"});
+}
+
+TEST(Misc, StringBuilder) {
+ auto small_str = td::string{"abcdefghij"};
+ auto big_str = td::string(1000, 'a');
+ using V = td::vector<td::string>;
+ for (auto use_buf : {false, true}) {
+ for (std::size_t initial_buffer_size : {0, 1, 5, 10, 100, 1000, 2000}) {
+ for (const auto &test :
+ {V{small_str}, V{small_str, big_str, big_str, small_str}, V{big_str, small_str, big_str}}) {
+ td::string buf(initial_buffer_size, '\0');
+ td::StringBuilder sb(buf, use_buf);
+ td::string res;
+ for (const auto &x : test) {
+ res += x;
+ sb << x;
+ }
+ if (use_buf) {
+ ASSERT_EQ(res, sb.as_cslice());
+ } else {
+ auto got = sb.as_cslice();
+ res.resize(got.size());
+ ASSERT_EQ(res, got);
+ }
+ }
+ }
+ }
+}
+
+TEST(Misc, As) {
+ char buf[100];
+ td::as<int>(buf) = 123;
+ ASSERT_EQ(123, td::as<int>(static_cast<const char *>(buf)));
+ ASSERT_EQ(123, td::as<int>(static_cast<char *>(buf)));
+ char buf2[100];
+ td::as<int>(buf2) = td::as<int>(buf);
+ ASSERT_EQ(123, td::as<int>(static_cast<const char *>(buf2)));
+ ASSERT_EQ(123, td::as<int>(static_cast<char *>(buf2)));
+}
+
+TEST(Misc, Regression) {
+ td::string name = "regression_db";
+ td::RegressionTester::destroy(name);
+
+ {
+ auto tester = td::RegressionTester::create(name);
+ tester->save_db();
+ tester->verify_test("one_plus_one", "two").ensure();
+ tester->verify_test("one_plus_one", "two").ensure();
+ tester->verify_test("two_plus_one", "three").ensure();
+ tester->verify_test("one_plus_one", "two").ensure();
+ tester->verify_test("two_plus_one", "three").ensure();
+ tester->save_db();
+ }
+ {
+ auto tester = td::RegressionTester::create(name);
+ tester->save_db();
+ tester->verify_test("one_plus_one", "two").ensure();
+ tester->verify_test("one_plus_one", "two").ensure();
+ tester->verify_test("two_plus_one", "three").ensure();
+ tester->verify_test("one_plus_one", "two").ensure();
+ tester->verify_test("two_plus_one", "three").ensure();
+ tester->save_db();
+ tester->verify_test("one_plus_one", "three").ensure_error();
+ tester->verify_test("two_plus_one", "two").ensure_error();
+ }
+ {
+ auto tester = td::RegressionTester::create(name);
+ tester->verify_test("one_plus_one", "three").ensure_error();
+ tester->verify_test("two_plus_one", "two").ensure_error();
+ }
+}
+
+TEST(Misc, Bits) {
+ ASSERT_EQ(32, td::count_leading_zeroes32(0));
+ ASSERT_EQ(64, td::count_leading_zeroes64(0));
+ ASSERT_EQ(32, td::count_trailing_zeroes32(0));
+ ASSERT_EQ(64, td::count_trailing_zeroes64(0));
+
+ for (int i = 0; i < 32; i++) {
+ ASSERT_EQ(31 - i, td::count_leading_zeroes32(1u << i));
+ ASSERT_EQ(i, td::count_trailing_zeroes32(1u << i));
+ ASSERT_EQ(31 - i, td::count_leading_zeroes_non_zero32(1u << i));
+ ASSERT_EQ(i, td::count_trailing_zeroes_non_zero32(1u << i));
+ }
+ for (int i = 0; i < 64; i++) {
+ ASSERT_EQ(63 - i, td::count_leading_zeroes64(1ull << i));
+ ASSERT_EQ(i, td::count_trailing_zeroes64(1ull << i));
+ ASSERT_EQ(63 - i, td::count_leading_zeroes_non_zero64(1ull << i));
+ ASSERT_EQ(i, td::count_trailing_zeroes_non_zero64(1ull << i));
+ }
+
+ ASSERT_EQ(0x12345678u, td::bswap32(0x78563412u));
+ ASSERT_EQ(0x12345678abcdef67ull, td::bswap64(0x67efcdab78563412ull));
+
+ td::uint8 buf[8] = {1, 90, 2, 18, 129, 255, 0, 2};
+ td::uint64 num2 = td::bswap64(td::as<td::uint64>(buf));
+ td::uint64 num = (static_cast<td::uint64>(buf[0]) << 56) | (static_cast<td::uint64>(buf[1]) << 48) |
+ (static_cast<td::uint64>(buf[2]) << 40) | (static_cast<td::uint64>(buf[3]) << 32) |
+ (static_cast<td::uint64>(buf[4]) << 24) | (static_cast<td::uint64>(buf[5]) << 16) |
+ (static_cast<td::uint64>(buf[6]) << 8) | (static_cast<td::uint64>(buf[7]));
+ ASSERT_EQ(num, num2);
+
+ ASSERT_EQ(0, td::count_bits32(0));
+ ASSERT_EQ(0, td::count_bits64(0));
+ ASSERT_EQ(4, td::count_bits32((1u << 31) | 7));
+ ASSERT_EQ(4, td::count_bits64((1ull << 63) | 7));
+}
+
+TEST(Misc, BitsRange) {
+ auto to_vec_a = [](td::uint64 x) {
+ td::vector<td::int32> bits;
+ for (auto i : td::BitsRange(x)) {
+ bits.push_back(i);
+ }
+ return bits;
+ };
+
+ auto to_vec_b = [](td::uint64 x) {
+ td::vector<td::int32> bits;
+ td::int32 pos = 0;
+ while (x != 0) {
+ if ((x & 1) != 0) {
+ bits.push_back(pos);
+ }
+ x >>= 1;
+ pos++;
+ }
+ return bits;
+ };
+
+ auto do_check = [](const td::vector<td::int32> &a, const td::vector<td::int32> &b) {
+ ASSERT_EQ(b, a);
+ };
+ auto check = [&](td::uint64 x) {
+ do_check(to_vec_a(x), to_vec_b(x));
+ };
+
+ do_check(to_vec_a(21), {0, 2, 4});
+ for (int x = 0; x < 100; x++) {
+ check(x);
+ check(std::numeric_limits<td::uint32>::max() - x);
+ check(std::numeric_limits<td::uint64>::max() - x);
+ }
+}
+
+#if !TD_THREAD_UNSUPPORTED
+TEST(Misc, Time) {
+ td::Stage run;
+ td::Stage check;
+ td::Stage finish;
+
+ std::size_t threads_n = 3;
+ td::vector<td::thread> threads;
+ td::vector<std::atomic<double>> ts(threads_n);
+ for (std::size_t i = 0; i < threads_n; i++) {
+ threads.emplace_back([&, thread_id = i] {
+ for (td::uint64 round = 1; round < 10000; round++) {
+ ts[thread_id] = 0;
+ run.wait(round * threads_n);
+ ts[thread_id] = td::Time::now();
+ check.wait(round * threads_n);
+ for (auto &ts_ref : ts) {
+ auto other_ts = ts_ref.load();
+ if (other_ts != 0) {
+ ASSERT_TRUE(other_ts <= td::Time::now_cached());
+ }
+ }
+
+ finish.wait(round * threads_n);
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+}
+#endif
+
+TEST(Misc, uint128) {
+ td::vector<td::uint64> parts = {0,
+ 1,
+ 2000,
+ 2001,
+ std::numeric_limits<td::uint64>::max(),
+ std::numeric_limits<td::uint64>::max() - 1,
+ std::numeric_limits<td::uint32>::max(),
+ static_cast<td::uint64>(std::numeric_limits<td::uint32>::max()) + 1};
+ td::vector<td::int64> signed_parts = {0,
+ 1,
+ 2000,
+ 2001,
+ -1,
+ -2000,
+ -2001,
+ std::numeric_limits<td::int64>::max(),
+ std::numeric_limits<td::int64>::max() - 1,
+ std::numeric_limits<td::int64>::min(),
+ std::numeric_limits<td::int64>::min() + 1,
+ std::numeric_limits<td::int32>::max(),
+ static_cast<td::int64>(std::numeric_limits<td::int32>::max()) + 1,
+ std::numeric_limits<td::int32>::max() - 1,
+ std::numeric_limits<td::int32>::min(),
+ std::numeric_limits<td::int32>::min() + 1,
+ static_cast<td::int64>(std::numeric_limits<td::int32>::min()) - 1};
+
+#if TD_HAVE_INT128
+ auto to_intrinsic = [](td::uint128_emulated num) {
+ return td::uint128_intrinsic(num.hi(), num.lo());
+ };
+ auto eq = [](td::uint128_emulated a, td::uint128_intrinsic b) {
+ return a.hi() == b.hi() && a.lo() == b.lo();
+ };
+ auto ensure_eq = [&](td::uint128_emulated a, td::uint128_intrinsic b) {
+ if (!eq(a, b)) {
+ LOG(FATAL) << "[" << a.hi() << ";" << a.lo() << "] vs [" << b.hi() << ";" << b.lo() << "]";
+ }
+ };
+#endif
+
+ td::vector<td::uint128_emulated> nums;
+ for (auto hi : parts) {
+ for (auto lo : parts) {
+ auto a = td::uint128_emulated(hi, lo);
+#if TD_HAVE_INT128
+ auto ia = td::uint128_intrinsic(hi, lo);
+ ensure_eq(a, ia);
+#endif
+ nums.push_back(a);
+ nums.pop_back();
+ nums.push_back({hi, lo});
+ }
+ }
+
+#if TD_HAVE_INT128
+ for (auto a : nums) {
+ auto ia = to_intrinsic(a);
+ ensure_eq(a, ia);
+ CHECK(a.is_zero() == ia.is_zero());
+ for (int i = 0; i <= 130; i++) {
+ ensure_eq(a.shl(i), ia.shl(i));
+ ensure_eq(a.shr(i), ia.shr(i));
+ }
+ for (auto b : parts) {
+ ensure_eq(a.mult(b), ia.mult(b));
+ }
+ for (auto b : signed_parts) {
+ ensure_eq(a.mult_signed(b), ia.mult_signed(b));
+ if (b == 0) {
+ continue;
+ }
+ td::int64 q;
+ td::int64 r;
+ a.divmod_signed(b, &q, &r);
+ td::int64 iq;
+ td::int64 ir;
+ ia.divmod_signed(b, &iq, &ir);
+ ASSERT_EQ(q, iq);
+ ASSERT_EQ(r, ir);
+ }
+ for (auto b : nums) {
+ auto ib = to_intrinsic(b);
+ //LOG(ERROR) << ia.hi() << ";" << ia.lo() << " " << ib.hi() << ";" << ib.lo();
+ ensure_eq(a.mult(b), ia.mult(ib));
+ ensure_eq(a.add(b), ia.add(ib));
+ ensure_eq(a.sub(b), ia.sub(ib));
+ if (!b.is_zero()) {
+ ensure_eq(a.div(b), ia.div(ib));
+ ensure_eq(a.mod(b), ia.mod(ib));
+ }
+ }
+ }
+
+ for (auto signed_part : signed_parts) {
+ auto a = td::uint128_emulated::from_signed(signed_part);
+ auto ia = td::uint128_intrinsic::from_signed(signed_part);
+ ensure_eq(a, ia);
+ }
+#endif
+}
+
+template <template <class T> class HashT, class ValueT>
+static td::Status test_hash(const td::vector<ValueT> &values) {
+ for (std::size_t i = 0; i < values.size(); i++) {
+ for (std::size_t j = i; j < values.size(); j++) {
+ auto &a = values[i];
+ auto &b = values[j];
+ auto a_hash = HashT<ValueT>()(a);
+ auto b_hash = HashT<ValueT>()(b);
+ if (a == b) {
+ if (a_hash != b_hash) {
+ return td::Status::Error("Hash differs for same values");
+ }
+ } else {
+ if (a_hash == b_hash) {
+ return td::Status::Error("Hash is the same for different values");
+ }
+ }
+ }
+ }
+ return td::Status::OK();
+}
+
+class BadValue {
+ public:
+ explicit BadValue(std::size_t value) : value_(value) {
+ }
+
+ template <class H>
+ friend H AbslHashValue(H hasher, const BadValue &value) {
+ return hasher;
+ }
+ bool operator==(const BadValue &other) const {
+ return value_ == other.value_;
+ }
+
+ private:
+ std::size_t value_;
+};
+
+class ValueA {
+ public:
+ explicit ValueA(std::size_t value) : value_(value) {
+ }
+ template <class H>
+ friend H AbslHashValue(H hasher, ValueA value) {
+ return H::combine(std::move(hasher), value.value_);
+ }
+ bool operator==(const ValueA &other) const {
+ return value_ == other.value_;
+ }
+
+ private:
+ std::size_t value_;
+};
+
+class ValueB {
+ public:
+ explicit ValueB(std::size_t value) : value_(value) {
+ }
+
+ template <class H>
+ friend H AbslHashValue(H hasher, ValueB value) {
+ return H::combine(std::move(hasher), value.value_);
+ }
+ bool operator==(const ValueB &other) const {
+ return value_ == other.value_;
+ }
+
+ private:
+ std::size_t value_;
+};
+
+template <template <class T> class HashT>
+static void test_hash() {
+ // Just check that the following compiles
+ AbslHashValue(td::Hasher(), ValueA{1});
+ HashT<ValueA>()(ValueA{1});
+ std::unordered_map<ValueA, int, HashT<ValueA>> s;
+ s[ValueA{1}] = 1;
+ td::HashMap<ValueA, int> su;
+ su[ValueA{1}] = 1;
+ td::HashSet<ValueA> su2;
+ su2.insert(ValueA{1});
+#if TD_HAVE_ABSL
+ std::unordered_map<ValueA, int, absl::Hash<ValueA>> x;
+ absl::flat_hash_map<ValueA, int, HashT<ValueA>> sa;
+ sa[ValueA{1}] = 1;
+#endif
+
+ test_hash<HashT, std::size_t>({1, 2, 3, 4, 5}).ensure();
+ test_hash<HashT, BadValue>({BadValue{1}, BadValue{2}}).ensure_error();
+ test_hash<HashT, ValueA>({ValueA{1}, ValueA{2}}).ensure();
+ test_hash<HashT, ValueB>({ValueB{1}, ValueB{2}}).ensure();
+ test_hash<HashT, std::pair<int, int>>({{1, 1}, {1, 2}}).ensure();
+ // FIXME: use some better hash
+ //test_hash<HashT, std::pair<int, int>>({{1, 1}, {1, 2}, {2, 1}, {2, 2}}).ensure();
+}
+
+TEST(Misc, Hasher) {
+ test_hash<td::TdHash>();
+#if TD_HAVE_ABSL
+ test_hash<td::AbslHash>();
+#endif
+}
+
+TEST(Misc, CancellationToken) {
+ td::CancellationTokenSource source;
+ source.cancel();
+ auto token1 = source.get_cancellation_token();
+ auto token2 = source.get_cancellation_token();
+ CHECK(!token1);
+ source.cancel();
+ CHECK(token1);
+ CHECK(token2);
+ auto token3 = source.get_cancellation_token();
+ CHECK(!token3);
+ source.cancel();
+ CHECK(token3);
+
+ auto token4 = source.get_cancellation_token();
+ CHECK(!token4);
+ source = td::CancellationTokenSource{};
+ CHECK(token4);
+}
+
+TEST(Misc, Xorshift128plus) {
+ td::Random::Xorshift128plus rnd(123);
+ ASSERT_EQ(11453256657207062272ull, rnd());
+ ASSERT_EQ(14917490455889357332ull, rnd());
+ ASSERT_EQ(5645917797309401285ull, rnd());
+ ASSERT_EQ(13554822455746959330ull, rnd());
+}
+
+TEST(Misc, uname) {
+ auto first_version = td::get_operating_system_version();
+ auto second_version = td::get_operating_system_version();
+ ASSERT_STREQ(first_version, second_version);
+ ASSERT_EQ(first_version.begin(), second_version.begin());
+ ASSERT_TRUE(!first_version.empty());
+}
+
+TEST(Misc, serialize) {
+ td::int32 x = 1;
+ ASSERT_EQ(td::base64_encode(td::serialize(x)), td::base64_encode(td::string("\x01\x00\x00\x00", 4)));
+ td::int64 y = -2;
+ ASSERT_EQ(td::base64_encode(td::serialize(y)), td::base64_encode(td::string("\xfe\xff\xff\xff\xff\xff\xff\xff", 8)));
+}
+
+TEST(Misc, check_reset_guard) {
+ CheckExitGuard check_exit_guard{false};
+}
+
+TEST(FloodControl, Fast) {
+ td::FloodControlFast fc;
+ fc.add_limit(1, 5);
+ fc.add_limit(5, 10);
+
+ td::int32 count = 0;
+ double now = 0;
+ for (int i = 0; i < 100; i++) {
+ now = fc.get_wakeup_at();
+ fc.add_event(now);
+ LOG(INFO) << ++count << ": " << now;
}
}
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/port.cpp b/protocols/Telegram/tdlib/td/tdutils/test/port.cpp
new file mode 100644
index 0000000000..92b1729977
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/tdutils/test/port.cpp
@@ -0,0 +1,316 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/EventFd.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/port/IoSlice.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/signals.h"
+#include "td/utils/port/sleep.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/port/thread_local.h"
+#include "td/utils/Random.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tests.h"
+#include "td/utils/Time.h"
+
+#if TD_PORT_POSIX && !TD_THREAD_UNSUPPORTED
+#include <algorithm>
+#include <atomic>
+#include <mutex>
+
+#include <pthread.h>
+#include <signal.h>
+#endif
+
+TEST(Port, files) {
+ td::CSlice main_dir = "test_dir";
+ td::rmrf(main_dir).ignore();
+ ASSERT_TRUE(td::FileFd::open(main_dir, td::FileFd::Write).is_error());
+ ASSERT_TRUE(td::walk_path(main_dir, [](td::CSlice name, td::WalkPath::Type type) { UNREACHABLE(); }).is_error());
+ td::mkdir(main_dir).ensure();
+ td::mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "A").ensure();
+ td::mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "B").ensure();
+ td::mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "B" << TD_DIR_SLASH << "D").ensure();
+ td::mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "C").ensure();
+ ASSERT_TRUE(td::FileFd::open(main_dir, td::FileFd::Write).is_error());
+ td::string fd_path = PSTRING() << main_dir << TD_DIR_SLASH << "t.txt";
+ td::string fd2_path = PSTRING() << main_dir << TD_DIR_SLASH << "C" << TD_DIR_SLASH << "t2.txt";
+
+ auto fd = td::FileFd::open(fd_path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok();
+ auto fd2 = td::FileFd::open(fd2_path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok();
+ fd2.close();
+
+ int cnt = 0;
+ const int ITER_COUNT = 1000;
+ for (int i = 0; i < ITER_COUNT; i++) {
+ td::walk_path(main_dir, [&](td::CSlice name, td::WalkPath::Type type) {
+ if (type == td::WalkPath::Type::NotDir) {
+ ASSERT_TRUE(name == fd_path || name == fd2_path);
+ }
+ cnt++;
+ }).ensure();
+ }
+ ASSERT_EQ((5 * 2 + 2) * ITER_COUNT, cnt);
+ bool was_abort = false;
+ td::walk_path(main_dir, [&](td::CSlice name, td::WalkPath::Type type) {
+ CHECK(!was_abort);
+ if (type == td::WalkPath::Type::EnterDir && ends_with(name, PSLICE() << TD_DIR_SLASH << "B")) {
+ was_abort = true;
+ return td::WalkPath::Action::Abort;
+ }
+ return td::WalkPath::Action::Continue;
+ }).ensure();
+ CHECK(was_abort);
+
+ cnt = 0;
+ bool is_first_dir = true;
+ td::walk_path(main_dir, [&](td::CSlice name, td::WalkPath::Type type) {
+ cnt++;
+ if (type == td::WalkPath::Type::EnterDir) {
+ if (is_first_dir) {
+ is_first_dir = false;
+ } else {
+ return td::WalkPath::Action::SkipDir;
+ }
+ }
+ return td::WalkPath::Action::Continue;
+ }).ensure();
+ ASSERT_EQ(6, cnt);
+
+ ASSERT_EQ(0u, fd.get_size().move_as_ok());
+ ASSERT_EQ(12u, fd.write("Hello world!").move_as_ok());
+ ASSERT_EQ(4u, fd.pwrite("abcd", 1).move_as_ok());
+ char buf[100];
+ td::MutableSlice buf_slice(buf, sizeof(buf));
+ ASSERT_TRUE(fd.pread(buf_slice.substr(0, 4), 2).is_error());
+ fd.seek(11).ensure();
+ ASSERT_EQ(2u, fd.write("?!").move_as_ok());
+
+ ASSERT_TRUE(td::FileFd::open(main_dir, td::FileFd::Read | td::FileFd::CreateNew).is_error());
+ fd = td::FileFd::open(fd_path, td::FileFd::Read | td::FileFd::Create).move_as_ok();
+ ASSERT_EQ(13u, fd.get_size().move_as_ok());
+ ASSERT_EQ(4u, fd.pread(buf_slice.substr(0, 4), 1).move_as_ok());
+ ASSERT_STREQ("abcd", buf_slice.substr(0, 4));
+
+ fd.seek(0).ensure();
+ ASSERT_EQ(13u, fd.read(buf_slice.substr(0, 13)).move_as_ok());
+ ASSERT_STREQ("Habcd world?!", buf_slice.substr(0, 13));
+}
+
+TEST(Port, SparseFiles) {
+ td::CSlice path = "sparse.txt";
+ td::unlink(path).ignore();
+ auto fd = td::FileFd::open(path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok();
+ ASSERT_EQ(0, fd.get_size().move_as_ok());
+ td::int64 offset = 100000000;
+ fd.pwrite("a", offset).ensure();
+ ASSERT_EQ(offset + 1, fd.get_size().move_as_ok());
+ auto real_size = fd.get_real_size().move_as_ok();
+ if (real_size >= offset + 1) {
+ LOG(ERROR) << "File system doesn't support sparse files, rewind during streaming can be slow";
+ }
+ td::unlink(path).ensure();
+}
+
+TEST(Port, LargeFiles) {
+ td::CSlice path = "large.txt";
+ td::unlink(path).ignore();
+ auto fd = td::FileFd::open(path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok();
+ ASSERT_EQ(0, fd.get_size().move_as_ok());
+ td::int64 offset = static_cast<td::int64>(3) << 30;
+ if (fd.pwrite("abcd", offset).is_error()) {
+ LOG(ERROR) << "Writing to large files isn't supported";
+ td::unlink(path).ensure();
+ return;
+ }
+ fd = td::FileFd::open(path, td::FileFd::Read).move_as_ok();
+ ASSERT_EQ(offset + 4, fd.get_size().move_as_ok());
+ td::string res(4, '\0');
+ if (fd.pread(res, offset).is_error()) {
+ LOG(ERROR) << "Reading of large files isn't supported";
+ td::unlink(path).ensure();
+ return;
+ }
+ ASSERT_STREQ(res, "abcd");
+ fd.close();
+ td::unlink(path).ensure();
+}
+
+TEST(Port, Writev) {
+ td::vector<td::IoSlice> vec;
+ td::CSlice test_file_path = "test.txt";
+ td::unlink(test_file_path).ignore();
+ auto fd = td::FileFd::open(test_file_path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok();
+ vec.push_back(td::as_io_slice("a"));
+ vec.push_back(td::as_io_slice("b"));
+ vec.push_back(td::as_io_slice("cd"));
+ ASSERT_EQ(4u, fd.writev(vec).move_as_ok());
+ vec.clear();
+ vec.push_back(td::as_io_slice("efg"));
+ vec.push_back(td::as_io_slice(""));
+ vec.push_back(td::as_io_slice("hi"));
+ ASSERT_EQ(5u, fd.writev(vec).move_as_ok());
+ fd.close();
+ fd = td::FileFd::open(test_file_path, td::FileFd::Read).move_as_ok();
+ td::Slice expected_content = "abcdefghi";
+ ASSERT_EQ(static_cast<td::int64>(expected_content.size()), fd.get_size().ok());
+ td::string content(expected_content.size(), '\0');
+ ASSERT_EQ(content.size(), fd.read(content).move_as_ok());
+ ASSERT_EQ(expected_content, content);
+}
+
+#if TD_PORT_POSIX && !TD_THREAD_UNSUPPORTED
+
+static std::mutex m;
+static td::vector<td::string> ptrs;
+static td::vector<int *> addrs;
+static TD_THREAD_LOCAL int thread_id;
+
+static void on_user_signal(int sig) {
+ int addr;
+ addrs[thread_id] = &addr;
+ std::unique_lock<std::mutex> guard(m);
+ ptrs.push_back(td::to_string(thread_id));
+}
+
+TEST(Port, SignalsAndThread) {
+ td::setup_signals_alt_stack().ensure();
+ td::set_signal_handler(td::SignalType::User, on_user_signal).ensure();
+ SCOPE_EXIT {
+ td::set_signal_handler(td::SignalType::User, nullptr).ensure();
+ };
+ td::vector<td::string> ans = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
+ {
+ td::vector<td::thread> threads;
+ int thread_n = 10;
+ td::vector<td::Stage> stages(thread_n);
+ ptrs.clear();
+ addrs.resize(thread_n);
+ for (int i = 0; i < 10; i++) {
+ threads.emplace_back([&, i] {
+ td::setup_signals_alt_stack().ensure();
+ if (i != 0) {
+ stages[i].wait(2);
+ }
+ thread_id = i;
+ pthread_kill(pthread_self(), SIGUSR1);
+ if (i + 1 < thread_n) {
+ stages[i + 1].wait(2);
+ }
+ });
+ }
+ for (auto &t : threads) {
+ t.join();
+ }
+ CHECK(ptrs == ans);
+
+ //LOG(ERROR) << ptrs;
+ //LOG(ERROR) << addrs;
+ }
+
+ {
+ td::Stage stage;
+ td::vector<td::thread> threads;
+ int thread_n = 10;
+ ptrs.clear();
+ addrs.resize(thread_n);
+ for (int i = 0; i < 10; i++) {
+ threads.emplace_back([&, i] {
+ stage.wait(thread_n);
+ thread_id = i;
+ pthread_kill(pthread_self(), SIGUSR1);
+ });
+ }
+ for (auto &t : threads) {
+ t.join();
+ }
+ std::sort(ptrs.begin(), ptrs.end());
+ CHECK(ptrs == ans);
+ auto addrs_size = addrs.size();
+ td::unique(addrs);
+ ASSERT_EQ(addrs_size, addrs.size());
+ //LOG(ERROR) << addrs;
+ }
+}
+
+#if !TD_EVENTFD_UNSUPPORTED
+TEST(Port, EventFdAndSignals) {
+ td::set_signal_handler(td::SignalType::User, [](int signal) {}).ensure();
+ SCOPE_EXIT {
+ td::set_signal_handler(td::SignalType::User, nullptr).ensure();
+ };
+
+ std::atomic_flag flag;
+ flag.test_and_set();
+ auto main_thread = pthread_self();
+ td::thread interrupt_thread{[&flag, &main_thread] {
+ td::setup_signals_alt_stack().ensure();
+ while (flag.test_and_set()) {
+ pthread_kill(main_thread, SIGUSR1);
+ td::usleep_for(1000 * td::Random::fast(1, 10)); // 0.001s - 0.01s
+ }
+ }};
+
+ for (int timeout_ms : {0, 1, 2, 10, 100, 500}) {
+ double min_diff = 10000000;
+ double max_diff = 0;
+ for (int t = 0; t < td::max(5, 1000 / td::max(timeout_ms, 1)); t++) {
+ td::EventFd event_fd;
+ event_fd.init();
+ auto start = td::Timestamp::now();
+ event_fd.wait(timeout_ms);
+ auto end = td::Timestamp::now();
+ auto passed = end.at() - start.at();
+ auto diff = passed * 1000 - timeout_ms;
+ min_diff = td::min(min_diff, diff);
+ max_diff = td::max(max_diff, diff);
+ }
+
+ LOG_CHECK(min_diff >= 0) << min_diff;
+ // LOG_CHECK(max_diff < 10) << max_diff;
+ LOG(INFO) << min_diff << " " << max_diff;
+ }
+ flag.clear();
+}
+#endif
+#endif
+
+#if TD_HAVE_THREAD_AFFINITY
+TEST(Port, ThreadAffinityMask) {
+ auto thread_id = td::this_thread::get_id();
+ auto old_mask = td::thread::get_affinity_mask(thread_id);
+ LOG(INFO) << "Initial thread " << thread_id << " affinity mask: " << old_mask;
+ for (size_t i = 0; i < 64; i++) {
+ auto mask = td::thread::get_affinity_mask(thread_id);
+ LOG(INFO) << mask;
+ auto result = td::thread::set_affinity_mask(thread_id, static_cast<td::uint64>(1) << i);
+ LOG(INFO) << i << ": " << result << ' ' << td::thread::get_affinity_mask(thread_id);
+
+ if (i <= 1) {
+ td::thread thread([] {
+ auto thread_id = td::this_thread::get_id();
+ auto mask = td::thread::get_affinity_mask(thread_id);
+ LOG(INFO) << "New thread " << thread_id << " affinity mask: " << mask;
+ auto result = td::thread::set_affinity_mask(thread_id, 1);
+ LOG(INFO) << "Thread " << thread_id << ": " << result << ' ' << td::thread::get_affinity_mask(thread_id);
+ });
+ LOG(INFO) << "Will join new thread " << thread.get_id()
+ << " with affinity mask: " << td::thread::get_affinity_mask(thread.get_id());
+ }
+ }
+ auto result = td::thread::set_affinity_mask(thread_id, old_mask);
+ LOG(INFO) << result;
+ old_mask = td::thread::get_affinity_mask(thread_id);
+ LOG(INFO) << old_mask;
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/pq.cpp b/protocols/Telegram/tdlib/td/tdutils/test/pq.cpp
index 5210cc2638..d919d661b5 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/pq.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/pq.cpp
@@ -1,29 +1,24 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/tests.h"
+#include "td/utils/algorithm.h"
#include "td/utils/BigNum.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
-#include "td/utils/misc.h"
+#include "td/utils/SliceBuilder.h"
-#include <algorithm>
#include <limits>
#include <utility>
-REGISTER_TESTS(pq)
-
-using namespace td;
-
-#if TD_HAVE_OPENSSL
-static bool is_prime(uint64 x) {
- for (uint64 d = 2; d < x && d * d <= x; d++) {
+static bool is_prime(td::uint64 x) {
+ for (td::uint64 d = 2; d < x && d * d <= x; d++) {
if (x % d == 0) {
return false;
}
@@ -31,9 +26,9 @@ static bool is_prime(uint64 x) {
return true;
}
-static std::vector<uint64> gen_primes(uint64 L, uint64 R, int limit = 0) {
- std::vector<uint64> res;
- for (auto x = L; x <= R && (limit <= 0 || res.size() < static_cast<std::size_t>(limit)); x++) {
+static td::vector<td::uint64> gen_primes(td::uint64 L, td::uint64 R, std::size_t limit = 0) {
+ td::vector<td::uint64> res;
+ for (auto x = L; x <= R && (limit <= 0 || res.size() < limit); x++) {
if (is_prime(x)) {
res.push_back(x);
}
@@ -41,22 +36,26 @@ static std::vector<uint64> gen_primes(uint64 L, uint64 R, int limit = 0) {
return res;
}
-static std::vector<uint64> gen_primes() {
- std::vector<uint64> result;
- append(result, gen_primes(1, 100));
- append(result, gen_primes((1ull << 31) - 500000, std::numeric_limits<uint64>::max(), 5));
- append(result, gen_primes((1ull << 32) - 500000, std::numeric_limits<uint64>::max(), 5));
- append(result, gen_primes((1ull << 39) - 500000, std::numeric_limits<uint64>::max(), 1));
+static td::vector<td::uint64> gen_primes(int mode) {
+ td::vector<td::uint64> result;
+ if (mode == 1) {
+ for (size_t i = 10; i <= 19; i++) {
+ td::append(result, gen_primes(i * 100000000, (i + 1) * 100000000, 1));
+ }
+ } else {
+ td::append(result, gen_primes(1, 100));
+ td::append(result, gen_primes((1ull << 31) - 500000, std::numeric_limits<td::uint64>::max(), 5));
+ td::append(result, gen_primes((1ull << 32) - 500000, std::numeric_limits<td::uint64>::max(), 2));
+ td::append(result, gen_primes((1ull << 39) - 500000, std::numeric_limits<td::uint64>::max(), 1));
+ }
return result;
}
-using PqQuery = std::pair<uint64, uint64>;
-static bool cmp(const PqQuery &a, const PqQuery &b) {
- return a.first * a.second < b.first * b.second;
-}
-static std::vector<PqQuery> gen_pq_queries() {
- std::vector<PqQuery> res;
- auto primes = gen_primes();
+using PqQuery = std::pair<td::uint64, td::uint64>;
+
+static td::vector<PqQuery> gen_pq_queries(int mode = 0) {
+ td::vector<PqQuery> res;
+ auto primes = gen_primes(mode);
for (auto q : primes) {
for (auto p : primes) {
if (p > q) {
@@ -65,28 +64,56 @@ static std::vector<PqQuery> gen_pq_queries() {
res.emplace_back(p, q);
}
}
- std::sort(res.begin(), res.end(), cmp);
return res;
}
-static void test_pq(uint64 first, uint64 second) {
- BigNum p = BigNum::from_decimal(PSLICE() << first);
- BigNum q = BigNum::from_decimal(PSLICE() << second);
+static td::string to_binary(td::uint64 x) {
+ td::string result;
+ do {
+ result = static_cast<char>(x & 255) + result;
+ x >>= 8;
+ } while (x > 0);
+ return result;
+}
+
+static void test_pq_fast(td::uint64 first, td::uint64 second) {
+ if ((static_cast<td::uint64>(1) << 63) / first <= second) {
+ return;
+ }
+
+ td::string p_str;
+ td::string q_str;
+ int err = td::pq_factorize(to_binary(first * second), &p_str, &q_str);
+ ASSERT_EQ(err, 0);
+
+ ASSERT_STREQ(p_str, to_binary(first));
+ ASSERT_STREQ(q_str, to_binary(second));
+}
+
+#if TD_HAVE_OPENSSL
+static void test_pq_slow(td::uint64 first, td::uint64 second) {
+ if ((static_cast<td::uint64>(1) << 63) / first > second) {
+ return;
+ }
+
+ td::BigNum p = td::BigNum::from_decimal(PSLICE() << first).move_as_ok();
+ td::BigNum q = td::BigNum::from_decimal(PSLICE() << second).move_as_ok();
- BigNum pq;
- BigNumContext context;
- BigNum::mul(pq, p, q, context);
- std::string pq_str = pq.to_binary();
+ td::BigNum pq;
+ td::BigNumContext context;
+ td::BigNum::mul(pq, p, q, context);
+ td::string pq_str = pq.to_binary();
- std::string p_str, q_str;
+ td::string p_str;
+ td::string q_str;
int err = td::pq_factorize(pq_str, &p_str, &q_str);
- CHECK(err == 0) << first << " * " << second;
+ LOG_CHECK(err == 0) << first << " * " << second;
- BigNum p_res = BigNum::from_binary(p_str);
- BigNum q_res = BigNum::from_binary(q_str);
+ td::BigNum p_res = td::BigNum::from_binary(p_str);
+ td::BigNum q_res = td::BigNum::from_binary(q_str);
- CHECK(p_str == p.to_binary()) << td::tag("got", p_res.to_decimal()) << td::tag("expected", first);
- CHECK(q_str == q.to_binary()) << td::tag("got", q_res.to_decimal()) << td::tag("expected", second);
+ LOG_CHECK(p_str == p.to_binary()) << td::tag("got", p_res.to_decimal()) << td::tag("expected", first);
+ LOG_CHECK(q_str == q.to_binary()) << td::tag("got", q_res.to_decimal()) << td::tag("expected", second);
}
#endif
@@ -101,18 +128,35 @@ TEST(CryptoPQ, hands) {
ASSERT_EQ(179424611ull, td::pq_factorize(179424611ull * 179424673ull));
#if TD_HAVE_OPENSSL
- test_pq(4294467311, 4294467449);
+ test_pq_slow(4294467311, 4294467449);
#endif
}
-#if TD_HAVE_OPENSSL
-TEST(CryptoPQ, generated_slow) {
+TEST(CryptoPQ, four) {
for (int i = 0; i < 100000; i++) {
- test_pq(2, 2);
+ test_pq_fast(2, 2);
+ }
+}
+
+TEST(CryptoPQ, generated_fast) {
+ auto queries = gen_pq_queries();
+ for (const auto &query : queries) {
+ test_pq_fast(query.first, query.second);
}
+}
+
+TEST(CryptoPQ, generated_server) {
+ auto queries = gen_pq_queries(1);
+ for (const auto &query : queries) {
+ test_pq_fast(query.first, query.second);
+ }
+}
+
+#if TD_HAVE_OPENSSL
+TEST(CryptoPQ, generated_slow) {
auto queries = gen_pq_queries();
- for (auto query : queries) {
- test_pq(query.first, query.second);
+ for (const auto &query : queries) {
+ test_pq_slow(query.first, query.second);
}
}
-#endif \ No newline at end of file
+#endif
diff --git a/protocols/Telegram/tdlib/td/tdutils/test/variant.cpp b/protocols/Telegram/tdlib/td/tdutils/test/variant.cpp
index 5c5e18d1d8..755acdfa98 100644
--- a/protocols/Telegram/tdlib/td/tdutils/test/variant.cpp
+++ b/protocols/Telegram/tdlib/td/tdutils/test/variant.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,22 +9,18 @@
#include "td/utils/tests.h"
#include "td/utils/Variant.h"
-REGISTER_TESTS(variant);
-
-using namespace td;
-
static const size_t BUF_SIZE = 1024 * 1024;
static char buf[BUF_SIZE], buf2[BUF_SIZE];
-static StringBuilder sb(MutableSlice(buf, BUF_SIZE - 1));
-static StringBuilder sb2(MutableSlice(buf2, BUF_SIZE - 1));
+static td::StringBuilder sb(td::MutableSlice(buf, BUF_SIZE - 1));
+static td::StringBuilder sb2(td::MutableSlice(buf2, BUF_SIZE - 1));
-static std::string move_sb() {
+static td::string move_sb() {
auto res = sb.as_cslice().str();
sb.clear();
return res;
}
-static std::string name(int id) {
+static td::string name(int id) {
if (id == 1) {
return "A";
}
@@ -58,18 +54,18 @@ using C = Class<3>;
TEST(Variant, simple) {
{
- Variant<std::unique_ptr<A>, std::unique_ptr<B>, std::unique_ptr<C>> abc;
+ td::Variant<td::unique_ptr<A>, td::unique_ptr<B>, td::unique_ptr<C>> abc;
ASSERT_STREQ("", sb.as_cslice());
- abc = std::make_unique<A>();
+ abc = td::make_unique<A>();
ASSERT_STREQ("+A", sb.as_cslice());
sb.clear();
- abc = std::make_unique<B>();
+ abc = td::make_unique<B>();
ASSERT_STREQ("+B-A", sb.as_cslice());
sb.clear();
- abc = std::make_unique<C>();
+ abc = td::make_unique<C>();
ASSERT_STREQ("+C-B", sb.as_cslice());
sb.clear();
}
ASSERT_STREQ("-C", move_sb());
sb.clear();
-};
+}
diff --git a/protocols/Telegram/tdlib/td/test/CMakeLists.txt b/protocols/Telegram/tdlib/td/test/CMakeLists.txt
index d120d8d3fb..2ac9ad32d6 100644
--- a/protocols/Telegram/tdlib/td/test/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/test/CMakeLists.txt
@@ -1,18 +1,22 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
#SOURCE SETS
set(TD_TEST_SOURCE
+ ${CMAKE_CURRENT_SOURCE_DIR}/country_info.cpp
${CMAKE_CURRENT_SOURCE_DIR}/db.cpp
${CMAKE_CURRENT_SOURCE_DIR}/http.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/mtproto.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/link.cpp
${CMAKE_CURRENT_SOURCE_DIR}/message_entities.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mtproto.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/poll.cpp
${CMAKE_CURRENT_SOURCE_DIR}/secret.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/secure_storage.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/set_with_position.cpp
${CMAKE_CURRENT_SOURCE_DIR}/string_cleaning.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TestsRunner.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/tests_runner.cpp
-
- ${CMAKE_CURRENT_SOURCE_DIR}/TestsRunner.h
- ${CMAKE_CURRENT_SOURCE_DIR}/tests_runner.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/tdclient.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/tqueue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/data.cpp
${CMAKE_CURRENT_SOURCE_DIR}/data.h
@@ -26,23 +30,44 @@ set(TESTS_MAIN
main.cpp
)
-add_library(all_tests STATIC ${TD_TEST_SOURCE})
-target_include_directories(all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
-target_link_libraries(all_tests PRIVATE tdactor tddb tdcore tdnet tdutils)
+#add_library(all_tests STATIC ${TD_TEST_SOURCE})
+#target_include_directories(all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+#target_link_libraries(all_tests PRIVATE tdcore tdclient)
if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN)
#Tests
+ if (OPENSSL_FOUND)
+ add_executable(test-crypto EXCLUDE_FROM_ALL crypto.cpp)
+ target_include_directories(test-crypto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
+ target_link_libraries(test-crypto PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES} tdutils tdcore)
+
+ if (WIN32)
+ if (MINGW)
+ target_link_libraries(test-crypto PRIVATE ws2_32 mswsock crypt32)
+ else()
+ target_link_libraries(test-crypto PRIVATE ws2_32 Mswsock Crypt32)
+ endif()
+ endif()
+ endif()
+
+ add_executable(test-tdutils EXCLUDE_FROM_ALL ${TESTS_MAIN} ${TDUTILS_TEST_SOURCE})
+ add_executable(test-online EXCLUDE_FROM_ALL online.cpp)
add_executable(run_all_tests ${TESTS_MAIN} ${TD_TEST_SOURCE})
- if (CLANG AND NOT CYGWIN AND NOT EMSCRIPTEN)
+ if (CLANG AND NOT CYGWIN AND NOT EMSCRIPTEN AND NOT (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") AND NOT (CMAKE_SIZEOF_VOID_P EQUAL 4))
+ target_compile_options(test-tdutils PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
target_compile_options(run_all_tests PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
+ target_compile_options(test-online PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fno-sanitize=vptr")
endif()
target_include_directories(run_all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
- target_link_libraries(run_all_tests PRIVATE tdactor tddb tdcore tdnet tdutils)
+ target_include_directories(test-tdutils PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+ target_link_libraries(test-tdutils PRIVATE tdutils)
+ target_link_libraries(run_all_tests PRIVATE tdcore tdclient)
+ target_link_libraries(test-online PRIVATE tdcore tdclient tdutils tdactor)
if (CLANG)
# add_executable(fuzz_url fuzz_url.cpp)
-# target_link_libraries(fuzz_url PRIVATE tdclient)
+# target_link_libraries(fuzz_url PRIVATE tdcore)
# target_compile_options(fuzz_url PRIVATE "-fsanitize-coverage=trace-pc-guard")
endif()
diff --git a/protocols/Telegram/tdlib/td/test/TestsRunner.cpp b/protocols/Telegram/tdlib/td/test/TestsRunner.cpp
deleted file mode 100644
index fbe155738e..0000000000
--- a/protocols/Telegram/tdlib/td/test/TestsRunner.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#include "test/TestsRunner.h"
-
-#include "td/utils/common.h"
-#include "td/utils/FileLog.h"
-#include "td/utils/format.h"
-#include "td/utils/logging.h"
-#include "td/utils/port/path.h"
-#include "td/utils/tests.h"
-
-#include <limits>
-
-DESC_TESTS(string_cleaning);
-DESC_TESTS(message_entities);
-DESC_TESTS(variant);
-DESC_TESTS(secret);
-DESC_TESTS(actors_main);
-DESC_TESTS(actors_simple);
-DESC_TESTS(actors_workers);
-DESC_TESTS(db);
-DESC_TESTS(json);
-DESC_TESTS(http);
-DESC_TESTS(heap);
-DESC_TESTS(pq);
-DESC_TESTS(mtproto);
-
-namespace td {
-
-void TestsRunner::run_all_tests() {
- LOAD_TESTS(string_cleaning);
- LOAD_TESTS(message_entities);
- LOAD_TESTS(variant);
- LOAD_TESTS(secret);
- LOAD_TESTS(actors_main);
- LOAD_TESTS(actors_simple);
- LOAD_TESTS(actors_workers);
- LOAD_TESTS(db);
- LOAD_TESTS(json);
- LOAD_TESTS(http);
- LOAD_TESTS(heap);
- LOAD_TESTS(pq);
- LOAD_TESTS(mtproto);
- Test::run_all();
-}
-
-static FileLog file_log;
-static TsLog ts_log(&file_log);
-
-void TestsRunner::init(string dir) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
- chdir(dir).ensure();
- LOG(WARNING) << "Redirect log into " << tag("file", dir + TD_DIR_SLASH + "log.txt");
- if (file_log.init("log.txt", std::numeric_limits<int64>::max())) {
- log_interface = &ts_log;
- }
-}
-
-} // namespace td
diff --git a/protocols/Telegram/tdlib/td/test/country_info.cpp b/protocols/Telegram/tdlib/td/test/country_info.cpp
new file mode 100644
index 0000000000..bf88173915
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/country_info.cpp
@@ -0,0 +1,87 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/CountryInfoManager.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tests.h"
+
+static void check_phone_number_info(td::string phone_number_prefix, const td::string &country_code,
+ const td::string &calling_code, const td::string &formatted_phone_number) {
+ auto result = td::CountryInfoManager::get_phone_number_info_sync(td::string(), std::move(phone_number_prefix));
+ CHECK(result != nullptr);
+ if (result->country_ == nullptr) {
+ CHECK(country_code.empty());
+ } else {
+ CHECK(result->country_->country_code_ == country_code);
+ }
+ CHECK(result->country_calling_code_ == calling_code);
+ CHECK(result->formatted_phone_number_ == formatted_phone_number);
+}
+
+TEST(CountryInfo, phone_number_info) {
+ check_phone_number_info("", "", "", "");
+ check_phone_number_info("aba c aba", "", "", "");
+
+ td::string str;
+ td::string reverse_str;
+ for (auto i = 0; i < 256; i++) {
+ str += static_cast<char>(i);
+ reverse_str += static_cast<char>(255 - i);
+ }
+ check_phone_number_info(str, "", "", "0123456789");
+ check_phone_number_info(reverse_str, "IR", "98", "765 432 10--");
+
+ check_phone_number_info("1", "US", "1", "--- --- ----");
+ check_phone_number_info("12", "US", "1", "2-- --- ----");
+ check_phone_number_info("126", "US", "1", "26- --- ----");
+ check_phone_number_info("128", "US", "1", "28- --- ----");
+ check_phone_number_info("1289", "CA", "1", "289 --- ----");
+ check_phone_number_info("1289123123", "CA", "1", "289 123 123-");
+ check_phone_number_info("128912312345", "CA", "1", "289 123 12345");
+ check_phone_number_info("1268", "AG", "1268", "--- ----");
+ check_phone_number_info("126801", "AG", "1268", "01- ----");
+ check_phone_number_info("12680123", "AG", "1268", "012 3---");
+ check_phone_number_info("12680123456", "AG", "1268", "012 3456");
+ check_phone_number_info("1268012345678", "AG", "1268", "012 345678");
+ check_phone_number_info("7", "RU", "7", "--- --- ----");
+ check_phone_number_info("71234567", "RU", "7", "123 456 7---");
+ check_phone_number_info("77654321", "KZ", "7", "765 432 1- --");
+ check_phone_number_info("3", "", "3", "");
+ check_phone_number_info("37", "", "37", "");
+ check_phone_number_info("372", "EE", "372", "---- ----");
+ check_phone_number_info("42", "", "42", "");
+ check_phone_number_info("420", "CZ", "420", "--- --- ---");
+ check_phone_number_info("421", "SK", "421", "--- --- ---");
+ check_phone_number_info("422", "", "", "422");
+ check_phone_number_info("423", "LI", "423", "--- ----");
+ check_phone_number_info("424", "YL", "42", "4");
+ check_phone_number_info("4241234567890", "YL", "42", "41234567890");
+ check_phone_number_info("4", "", "4", "");
+ check_phone_number_info("49", "DE", "49", "---- -------");
+ check_phone_number_info("491", "DE", "49", "1--- -------");
+ check_phone_number_info("492", "DE", "49", "2--- -------");
+ check_phone_number_info("4915", "DE", "49", "15-- -------");
+ check_phone_number_info("4916", "DE", "49", "16- -------");
+ check_phone_number_info("4917", "DE", "49", "17- -------");
+ check_phone_number_info("4918", "DE", "49", "18-- -------");
+ check_phone_number_info("493", "DE", "49", "3--- -------");
+ check_phone_number_info("4936", "DE", "49", "36-- -------");
+ check_phone_number_info("49360", "DE", "49", "360- -------");
+ check_phone_number_info("493601", "DE", "49", "3601 -------");
+ check_phone_number_info("4936014", "DE", "49", "3601 4------");
+ check_phone_number_info("4936015", "DE", "49", "3601 5------");
+ check_phone_number_info("493601419", "DE", "49", "3601 419----");
+ check_phone_number_info("4936014198", "DE", "49", "3601 4198--");
+ check_phone_number_info("49360141980", "DE", "49", "3601 41980-");
+ check_phone_number_info("841234567890", "VN", "84", "1234567890");
+ check_phone_number_info("31", "NL", "31", "- -- -- -- --");
+ check_phone_number_info("318", "NL", "31", "8 -- -- -- --");
+ check_phone_number_info("319", "NL", "31", "9 -- -- -- --");
+ check_phone_number_info("3196", "NL", "31", "9 6- -- -- --");
+ check_phone_number_info("3197", "NL", "31", "97 ---- -----");
+ check_phone_number_info("3198", "NL", "31", "9 8- -- -- --");
+}
diff --git a/protocols/Telegram/tdlib/td/test/crypto.cpp b/protocols/Telegram/tdlib/td/test/crypto.cpp
new file mode 100644
index 0000000000..1837cef631
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/crypto.cpp
@@ -0,0 +1,279 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/AuthKey.h"
+#include "td/mtproto/Transport.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/detail/ThreadIdGuard.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/SharedSlice.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/UInt.h"
+
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+class Handshake {
+ public:
+ struct KeyPair {
+ td::SecureString private_key;
+ td::SecureString public_key;
+ };
+
+ static td::Result<KeyPair> generate_key_pair() {
+ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(NID_X25519, nullptr);
+ if (pctx == nullptr) {
+ return td::Status::Error("Can't create EXP_PKEY_CTX");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_CTX_free(pctx);
+ };
+ if (EVP_PKEY_keygen_init(pctx) <= 0) {
+ return td::Status::Error("Can't init keygen");
+ }
+
+ EVP_PKEY *pkey = nullptr;
+ if (EVP_PKEY_keygen(pctx, &pkey) <= 0) {
+ return td::Status::Error("Can't generate key");
+ }
+
+ TRY_RESULT(private_key, X25519_key_from_PKEY(pkey, true));
+ TRY_RESULT(public_key, X25519_key_from_PKEY(pkey, false));
+
+ KeyPair res;
+ res.private_key = std::move(private_key);
+ res.public_key = std::move(public_key);
+
+ return std::move(res);
+ }
+
+ static td::SecureString expand_secret(td::Slice secret) {
+ td::SecureString res(128);
+ td::hmac_sha512(secret, "0", res.as_mutable_slice().substr(0, 64));
+ td::hmac_sha512(secret, "1", res.as_mutable_slice().substr(64, 64));
+ return res;
+ }
+
+ static td::Result<td::SecureString> privateKeyToPem(td::Slice key) {
+ auto pkey_private = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, nullptr, key.ubegin(), 32);
+ CHECK(pkey_private != nullptr);
+ auto res = X25519_pem_from_PKEY(pkey_private, true);
+ EVP_PKEY_free(pkey_private);
+ return res;
+ }
+
+ static td::Result<td::SecureString> calc_shared_secret(td::Slice private_key, td::Slice other_public_key) {
+ auto pkey_private = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, nullptr, private_key.ubegin(), 32);
+ if (pkey_private == nullptr) {
+ return td::Status::Error("Invalid X25520 private key");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_free(pkey_private);
+ };
+
+ auto pkey_public =
+ EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, nullptr, other_public_key.ubegin(), other_public_key.size());
+ if (pkey_public == nullptr) {
+ return td::Status::Error("Invalid X25519 public key");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_free(pkey_public);
+ };
+
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey_private, nullptr);
+ if (ctx == nullptr) {
+ return td::Status::Error("Can't create EVP_PKEY_CTX");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_CTX_free(ctx);
+ };
+
+ if (EVP_PKEY_derive_init(ctx) <= 0) {
+ return td::Status::Error("Can't init derive");
+ }
+ if (EVP_PKEY_derive_set_peer(ctx, pkey_public) <= 0) {
+ return td::Status::Error("Can't init derive");
+ }
+
+ size_t result_len = 0;
+ if (EVP_PKEY_derive(ctx, nullptr, &result_len) <= 0) {
+ return td::Status::Error("Can't get result length");
+ }
+ if (result_len != 32) {
+ return td::Status::Error("Unexpected result length");
+ }
+
+ td::SecureString result(result_len, '\0');
+ if (EVP_PKEY_derive(ctx, result.as_mutable_slice().ubegin(), &result_len) <= 0) {
+ return td::Status::Error("Failed to compute shared secret");
+ }
+ return std::move(result);
+ }
+
+ private:
+ static td::Result<td::SecureString> X25519_key_from_PKEY(EVP_PKEY *pkey, bool is_private) {
+ auto func = is_private ? &EVP_PKEY_get_raw_private_key : &EVP_PKEY_get_raw_public_key;
+ size_t len = 0;
+ if (func(pkey, nullptr, &len) == 0) {
+ return td::Status::Error("Failed to get raw key length");
+ }
+ CHECK(len == 32);
+
+ td::SecureString result(len);
+ if (func(pkey, result.as_mutable_slice().ubegin(), &len) == 0) {
+ return td::Status::Error("Failed to get raw key");
+ }
+ return std::move(result);
+ }
+
+ static td::Result<td::SecureString> X25519_pem_from_PKEY(EVP_PKEY *pkey, bool is_private) {
+ BIO *mem_bio = BIO_new(BIO_s_mem());
+ SCOPE_EXIT {
+ BIO_vfree(mem_bio);
+ };
+ if (is_private) {
+ PEM_write_bio_PrivateKey(mem_bio, pkey, nullptr, nullptr, 0, nullptr, nullptr);
+ } else {
+ PEM_write_bio_PUBKEY(mem_bio, pkey);
+ }
+ char *data_ptr = nullptr;
+ auto data_size = BIO_get_mem_data(mem_bio, &data_ptr);
+ return td::SecureString(data_ptr, data_size);
+ }
+};
+
+struct HandshakeTest {
+ Handshake::KeyPair alice;
+ Handshake::KeyPair bob;
+
+ td::SecureString shared_secret;
+ td::SecureString key;
+};
+
+static void KDF3(td::Slice auth_key, const td::UInt128 &msg_key, int X, td::UInt256 *aes_key, td::UInt128 *aes_iv) {
+ td::uint8 buf_raw[36 + 16];
+ td::MutableSlice buf(buf_raw, 36 + 16);
+ td::Slice msg_key_slice = td::as_slice(msg_key);
+
+ // sha256_a = SHA256 (msg_key + substr(auth_key, x, 36));
+ buf.copy_from(msg_key_slice);
+ buf.substr(16, 36).copy_from(auth_key.substr(X, 36));
+ td::uint8 sha256_a_raw[32];
+ td::MutableSlice sha256_a(sha256_a_raw, 32);
+ sha256(buf, sha256_a);
+
+ // sha256_b = SHA256 (substr(auth_key, 40+x, 36) + msg_key);
+ buf.copy_from(auth_key.substr(40 + X, 36));
+ buf.substr(36).copy_from(msg_key_slice);
+ td::uint8 sha256_b_raw[32];
+ td::MutableSlice sha256_b(sha256_b_raw, 32);
+ sha256(buf, sha256_b);
+
+ // aes_key = substr(sha256_a, 0, 8) + substr(sha256_b, 8, 16) + substr(sha256_a, 24, 8);
+ td::MutableSlice aes_key_slice(aes_key->raw, sizeof(aes_key->raw));
+ aes_key_slice.copy_from(sha256_a.substr(0, 8));
+ aes_key_slice.substr(8).copy_from(sha256_b.substr(8, 16));
+ aes_key_slice.substr(24).copy_from(sha256_a.substr(24, 8));
+
+ // aes_iv = substr(sha256_b, 0, 4) + substr(sha256_a, 8, 8) + substr(sha256_b, 24, 4);
+ td::MutableSlice aes_iv_slice(aes_iv->raw, sizeof(aes_iv->raw));
+ aes_iv_slice.copy_from(sha256_b.substr(0, 4));
+ aes_iv_slice.substr(4).copy_from(sha256_a.substr(8, 8));
+ aes_iv_slice.substr(12).copy_from(sha256_b.substr(24, 4));
+}
+
+static td::SecureString encrypt(td::Slice key, td::Slice data, td::int32 seqno, int X) {
+ td::SecureString res(data.size() + 4 + 16);
+ res.as_mutable_slice().substr(20).copy_from(data);
+
+ // big endian
+ td::uint8 *ptr = res.as_mutable_slice().substr(16).ubegin();
+ ptr[0] = static_cast<td::uint8>((seqno >> 24) & 255);
+ ptr[1] = static_cast<td::uint8>((seqno >> 16) & 255);
+ ptr[2] = static_cast<td::uint8>((seqno >> 8) & 255);
+ ptr[3] = static_cast<td::uint8>(seqno & 255);
+
+ td::mtproto::AuthKey auth_key(0, key.str());
+ auto payload = res.as_mutable_slice().substr(16);
+ td::UInt128 msg_key = td::mtproto::Transport::calc_message_key2(auth_key, X, payload).second;
+ td::UInt256 aes_key;
+ td::UInt128 aes_iv;
+ KDF3(key, msg_key, X, &aes_key, &aes_iv);
+ td::AesCtrState aes;
+ aes.init(aes_key.as_slice(), aes_iv.as_slice());
+ aes.encrypt(payload, payload);
+ res.as_mutable_slice().copy_from(msg_key.as_slice());
+ return res;
+}
+
+static HandshakeTest gen_test() {
+ HandshakeTest res;
+ res.alice = Handshake::generate_key_pair().move_as_ok();
+
+ res.bob = Handshake::generate_key_pair().move_as_ok();
+ res.shared_secret = Handshake::calc_shared_secret(res.alice.private_key, res.bob.public_key).move_as_ok();
+ res.key = Handshake::expand_secret(res.shared_secret);
+ return res;
+}
+
+static void run_test(const HandshakeTest &test) {
+ auto alice_secret = Handshake::calc_shared_secret(test.alice.private_key, test.bob.public_key).move_as_ok();
+ auto bob_secret = Handshake::calc_shared_secret(test.bob.private_key, test.alice.public_key).move_as_ok();
+ auto key = Handshake::expand_secret(alice_secret);
+ CHECK(alice_secret == bob_secret);
+ CHECK(alice_secret == test.shared_secret);
+ LOG(ERROR) << "Key\n\t" << td::base64url_encode(key) << "\n";
+ CHECK(key == test.key);
+}
+
+static td::StringBuilder &operator<<(td::StringBuilder &sb, const Handshake::KeyPair &key_pair) {
+ sb << "\tpublic_key (base64url) = " << td::base64url_encode(key_pair.public_key) << "\n";
+ sb << "\tprivate_key (base64url) = " << td::base64url_encode(key_pair.private_key) << "\n";
+ sb << "\tprivate_key (pem) = \n" << Handshake::privateKeyToPem(key_pair.private_key).ok() << "\n";
+ return sb;
+}
+
+static td::StringBuilder &operator<<(td::StringBuilder &sb, const HandshakeTest &test) {
+ sb << "Alice\n" << test.alice;
+ sb << "Bob\n" << test.bob;
+ sb << "SharedSecret\n\t" << td::base64url_encode(test.shared_secret) << "\n";
+ sb << "Key\n\t" << td::base64url_encode(test.key) << "\n";
+
+ std::string data = "hello world";
+ sb << "encrypt(\"" << data << "\", X=0) = \n\t" << td::base64url_encode(encrypt(test.key, data, 1, 0)) << "\n";
+ sb << "encrypt(\"" << data << "\", X=8) = \n\t" << td::base64url_encode(encrypt(test.key, data, 1, 8)) << "\n";
+ return sb;
+}
+
+static HandshakeTest pregenerated_test() {
+ HandshakeTest test;
+ test.alice.public_key = td::base64url_decode_secure("QlCME5fXLyyQQWeYnBiGAZbmzuD4ayOuADCFgmioOBY").move_as_ok();
+ test.alice.private_key = td::base64url_decode_secure("8NZGWKfRCJfiks74RG9_xHmYydarLiRsoq8VcJGPglg").move_as_ok();
+ test.bob.public_key = td::base64url_decode_secure("I1yzfmMCZzlI7xwMj1FJ3O3I3_aEUtv6CxbHiDGzr18").move_as_ok();
+ test.bob.private_key = td::base64url_decode_secure("YMGoowtnZ99roUM2y5JRwiQrwGaNJ-ZRE5boy-l4aHg").move_as_ok();
+ test.shared_secret = td::base64url_decode_secure("0IIvKJuXEwmAp41fYhjUnWqLTYQJ7QeKZKYuCG8mFz8").move_as_ok();
+ test.key = td::base64url_decode_secure(
+ "JHmD-XW9j-13G6KP0tArIhQNDRVbRkKxx0MG0pa2nOgwJHNfiggM0I0RiNIr23-1wRboRtan4WvqOHsxAt_cngYX15_"
+ "HYe8tJdEwHcmlnXq7LtprigzExaNJS7skfOo2irClj-7EL06-jMrhfwngSJFsak8JFSw8s6R4fwCsr50")
+ .move_as_ok();
+
+ return test;
+}
+
+int main() {
+ td::detail::ThreadIdGuard thread_id_guard;
+ auto test = gen_test();
+ run_test(test);
+ run_test(pregenerated_test());
+ LOG(ERROR) << "\n" << pregenerated_test();
+}
diff --git a/protocols/Telegram/tdlib/td/test/data.cpp b/protocols/Telegram/tdlib/td/test/data.cpp
index a57a9147c5..34aff9b333 100644
--- a/protocols/Telegram/tdlib/td/test/data.cpp
+++ b/protocols/Telegram/tdlib/td/test/data.cpp
@@ -1,11 +1,11 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "test/data.h"
-namespace td {
+#include "data.h"
+
static const char thumbnail_arr[] =
"_9j_4AAQSkZJRgABAQEASABIAAD_2wBDAAICAgICAQICAgIDAgIDAwYEAwMDAwcFBQQGCAcJCAgHCAgJCg0LCQoMCggICw8LDA0ODg8OCQsQERAOEQ"
"0ODg7_2wBDAQIDAwMDAwcEBAcOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7_wAARCAAyADIDASIA"
@@ -66,4 +66,438 @@ static const char gzip_bomb_arr[] =
"TLGznukoHjpzn3qgqJTV2ogJXaK6ctqTgxKUoPyfUeqmx4szeRJuj3josnbUu0O57kcXXX-bi25IozxSONDzF5jalI4";
const char *gzip_bomb = gzip_bomb_arr;
const size_t gzip_bomb_size = sizeof(gzip_bomb_arr) - 1;
-} // namespace td
+
+static const char gzip_arr[] =
+ "eJztxT1LQmEAgNGXMJEIukNI0pDS0hAUSiCa4uZfELqgQwUFuQjXhkDFisrARajBocmISFA0GstBg4owopuIgdAiKE0SfkSBf6HB5TnLmYnpxMBv89"
+ "ExVnBN7pWvll_ePZqMpPo2Fg6dT-GQdfbA_zH_eftzVE16g8l9Ze7cP33ZTn2dlPP9XEfpXbyeqtnrm50HS7G0dbzyZpPNSkW_"
+ "tLmeCCT0pbrzO21otbfjqqyNuIySTjNCRERERERERERERERERERERERERERERERERERE_2k3ZA8YhRBhcb_"
+ "2XHN7zoR5alwbvfMtEhERERERERERERERERER0ZCqTzREVzKNyvlV8Qf1dzn-";
+const char *gzip = gzip_arr;
+const size_t gzip_size = sizeof(gzip_arr) - 1;
+
+static const char sqlite_sample_db_v3_arr[] =
+ "olFZ1MdfY0Abj+LtR9ft6DTZgEHW7/"
+ "z7yAhC07NKr7pBAHWkbQyMPtyVSIW7PLdVaQIHYwLgd7ovQSzD7eTINxZh6Nxpwa8HTynvjhHIdQhtysRL9m3mTEj4mbjU48zq+jcFdsnzG+"
+ "GJAzk5MQRCi+NyDWGYnBoPhiiPc4QBoFgKoYGIhQwfVci5kf2fYIQrCM1H7QQZ8RHoqCuQ3IjMpZjt/Gzqy+P8kn2PXKWHG8/y5eDc5nQCk/"
+ "XhkY7A9Wl7ZUsgK7mkA1O1VHp9X0Kz/WCuWhsULuUSeshXmJ1zKciUmTMhS3T7/"
+ "mc2jVrR00SyT22XwpL6dRwaNtUXVJKJwtzxrTHTxWq33KdxKXFlMr5ffbWsOfpkEHlpeKybJF70bzOgKUF5pmCHOll+3gfZcJOVFI4DvHFSnt/"
+ "Y3XlhJDrHliXeXcfsSUNzpBsBO+/+O/MMGLi2m/kJ9GHXGpieJsHPSQG1RMNqe84QIymLEwnGptg/"
+ "I+gw6xqFsUqBo2Z3Hllgz0t9GqvI1ENZr16nwf9hUGMEpj2pdKGR+eSSHMac3BJaiGgoKE7BfUjaaXzI5a8FfZTyl+"
+ "JUMf3ou2YBuO9fiuR1AhpAyydLSAn93OdbIF+VUHea8944nyUQB2k6idA9zfOFOVPZlaWC8/KxJeLDIAYYv3s+aK3kz/"
+ "EyU4SSRvDLXhMdSrkNaGM4zbYc/8r4WJSAIgFq5yg3jidRq0xJ3eb+BHs6pCTz3ql50BtPd2ngzz2y51kiW2xVIP3K/"
+ "hI53loi9UWTIYQPxtWl8RBWD2J1rbMsG0c8CbCQAo3u0SJ2A+4gQ2q5ZRPl9rp0IEgHwhg1xtjZhz7Ss2X7HO9Bfb/"
+ "ioe6ZlRPPhLR5AueL6YjretqfCs0C6Y3D7Ax6ho3vae5a2HCzasqgD42OSn6YwSq4d85AurIf4GhGykmQnkHPiblLSi9LE8w59B1a9IQgrdnVHqto3"
+ "IYYJELdEHrEOzDOsHoXM3aiXi3kURyDw+L/ljvIwWpgrVic94CCBlwUkUCtbw+n9Gfn06GE4V1J7/"
+ "omTHBKJXfd1hEDBegyEf6l2mx6p6zWCRcBEIZNObMRsX0P7P9A09/ZyzQPAMJrfI25fy2PvEuHWpVmbFBm7Xsf/B/"
+ "pGuP3j4td+e62Fu8yhMpSNhPclCdz+MXO9yCCondvU9DUzPQxU26yQhYNasu4cLipXCYpNhc9+dpLakoqlBB7Qq52R1S/f3PgsbTYy/uJs0NwoQV7/"
+ "aQTgjWutM5Nb+"
+ "yxxebr6dfRG6HfN0AXvLq8ON2FimTK6mXa9YI5YXKROAX4VssccBw1dGM8RaaAvMKQKEjfzwcPMo0C2mCcfFB2Nkj3A2SLvWdL7APgkEnGATDjs8Kn"
+ "bbI5Idr9ReU38zyB2l3Ys+CVbl6dvXdCu9QMS5t9Ez6e6zu3ZJKbMDEmrD7+0QyryYwSTFMDR1LJsbHavYzfGm/"
+ "bAC2yFlwGuN1nqpxvHCrFJH4Gjvs/"
+ "IVsX1Hnr1IZFJdIaMI8DRkksKijAYxxizan62LjrGQ8Lx4SGEYqTKU182XJdG9joSEuIvh2BaLZwu6AGEO1BbtJpGeVUiupqORQMGD9+"
+ "jOzo3KN0u3amsHO9QASeJ2fhMM7ej7C2aBnUrsX3zayB+seS0PTpZPqhI4IImvRmuwWiWXlWqhuEMSrnfuUN+"
+ "oZ8LfhsUPrRriUsuM7ZQJhzeVHGLXKeO0uFhL2KW7EQs7bdnOtQw6vw2FEe6bzYUZdR7SNWvRpN1BFm48vSLnLyhjtRN0ZAI5j6FNB6sWGVXRp32uH"
+ "p0BH/Nuss6Q+xcn0ZhBKIUGPtVtyUZcgoy1JT23JusETl83vsEx5NkWhSUCdhThOyB/N7fl4lRgfF0VxRabLXa0RcPlF7m79R5Mn/+/mveGQtd/"
+ "3UurCuDd2bjJ2ZL+GNgwGpliPpjPa1LHDX7WXi/yyYHHjGq7uYuauQql0ID/"
+ "bZY4kx9W0YTpzeKRd3Y4FZifDtFkxbosTFsUL+svpNSmQLuGptxPrfIHadHBkwP4g5CvAswfos6FGAx+ntE9+"
+ "jVt9WF6n73F7awQY0RxKOPRz0ESRPI3Z3r5m4AXoaEWkzqBIcCsYLBf3gIuxQxCbo5kMxhj7+qAAbJlqUTZQZS21ZbQS/"
+ "CRxXEv5TIYrQv0h004kzRenULxyM2fVgnPF09eLCW1wIeLfy5q5ShfkewBJ2xoqon5Pq9MgyG+EkwZ5ZphjGTEQkRlLfVNeD6s+HBildUL1+"
+ "sQkICmRxIKhubnrJ0WpD4EkpvTOEZkFalGjkp3t1L7KeBseX/qFfCBlcNThLbBVZQMRLGeCYZ/OZ1Z1qkN/KB6ltAjI+ZALJ73G/"
+ "PzPF19S0vUmka0Aeq+Q13X1fD27HGTIW2zVhgQvgIk+shlQWLCDn9qDwNxyGWNplR8dIsnl1lAIVXokjA+qIz3RNAkwDkk60x+"
+ "rZKJSjLiy3RtbJ6ZTsDl4U6NIzln1zbPkbS6ttbtoI86aBtSQribN7zoG3zG9pkdr+"
+ "hGso6jP7QhhEVskGfdbHTtOkTOhGqcrg3jALtv5J4fTNK4kzyjJRYQ0HVuWtPMvP022bI4uA8SbjnWkH7Z2Kt8LujV9ji9RDs4+"
+ "Y3bQn2TlPiShyPeOJ0f2Tlaeo2liySol7PZT1rbDjr8Zp/"
+ "UIbkyeEl0BIg8O6BAJ3h8JMXRQTklu23Fkl6PMHsGHcz0GE58+WJMgM6RyCgc2ka7L4YeGu7mPjJRSo3umdhi2GqOfSgELUzWr/"
+ "gVmFljsiHJkg5xMrksDmGoG+kXv57iyNi8Dphr6SsT3lFdSG4Tx9WeMSSNOngSyK8yzJStqUmq5wiTNeBeIW2KgSQMzGETMpMVZqoAX/"
+ "fHdgAjYxwooM15G2R98Vs1xlX5fjEHz1DNJQ39pSPBJInvdZAQqDFVzD9A4IktiIthEIWj0RemW+HRQKQ/"
+ "rODTVeZB2bY+IAx+qr7JyUPkYziNWQKnXhRUm1t4ZJarr1Ud6/"
+ "G6RxdNjt3pX1HBfn0t4+6nOuWA7MAsdA+EvQjXd8XPLPEFReAHnIXqE0DAAQPmRdcnfDCfXtPQv+"
+ "hD82s4sQCsaqA1dwV3CsRzhfWiZlQ5iMPJtjU0A3UgGPg+"
+ "7MAXNfPUV8eiRyCwC4fEnSkj2LLMf1de1wlyjosXcM6GTMroBo8wFsUWxdTSGA1xsPWDAL3DWi6szxVLp7EGNssLZ8OWKHALYy9P6bN5qUS23FahYg"
+ "Tu6jVCGVAuCGwnaPLGi4yUdQIOTvTEgJiPNf8jZ2wKiOZZ4belGxh6H7Y54kfVCZwxGdhcyktUV+H3TgBZmR70x/"
+ "SF6ct1GeZd1W9IxEJTTcRc179RNcRk4pd5KjwyIO6/WFOtSRKOmPs+cb9bArVV/"
+ "HjeIDM0rvahdWvqjMo8q9BSyzjvrvLrSxkaLskfhu3KHnfftGZdiYypGsngBFN9RDmSHknNttmal5tnWZP/"
+ "5RhfDnfnDwCq9ARP3USRkaUpKl87l0qzZkHnSoKBUpcQ70qrhrMGyQyn7dobM7mHw8UZ4fOuCxY/vEQ3dql4RsczsaqHsl3z/"
+ "AgXKhs2+MI32L9lC5RSz/"
+ "ix2uzbExljLdYgUe6aNsUqKbYkZfugFEbTtwHXPsrSioXuusKGX00muMADmvxpqrVt63omwKlK4IJ9la10DCsJiv5NOFsIZhPTIb2fzJUR6eSRyNFL"
+ "Wt3IKpVTwsLXqI/"
+ "biB6vInyvJvqNWgc8wSIQWvngtSyJugYmFG3k9GuuJQ6VdbafPAR5BCeuJy8JvX0dANbaRvsZPsLWa7nNCM2iwmtkNMMLdQe6+"
+ "DdCHz5XGwW91spC1pORsSTM/"
+ "pegYHDptkICvczJSSTwDs1aATBZGMfVtKzed4Vxx9nxN23uqwcVCwDljkRB7AXbFaCbRLi0glGmK8CaeGq+KAuLj7eAm8w6ihS9IWGAD8oGhVyJR+"
+ "D4U4ah5YX/GAysJA8aK3g0xKmUfveHTUQKDs0dzdTOrTr18++t7IqK1x2jDCmU9/"
+ "C6saN75gcZr4Q9QyULNHFgtXCpydThPIMBJ47jZFuvFSmoBbqD6m8WUjZjDJ4+odZISIGBzUoCKut9jjok8o2RzK6oY265stKOE0Ud7EI+"
+ "7rAtQY71LhsUSC6Sb4QbIsJlG7vb/"
+ "l0fF6Kh5IRGrzNVBJFsd+0ws6JQxDEVFSuO5UZVviYt0GHS+Qtt4EnoWbARCPCQfq4EiZJSbUhIPGWLoh6ozv/"
+ "U+5LArEPD7JcAXvpe+ZwYyrS5c8HWtMng7B7eiNdfjv9HtpfTVidxawNwhgC4igZ18yigsRJvyGuqpCipA/"
+ "p5sfXFb4hibSZsif7HAPja0G4CFxGL96Vxx1eKiSCNyLYL42m2slvgI6rvYKFTVvdQtBFhiav7vg+"
+ "Oz2wwzYRWRlHsIdOfcNws8XAgTL384gT8km9UtO4WtUyJZL5xDb2iWGsSoqRCAFiNYsxpwcCfZa4p5YP8E7PX4tzdUJ+"
+ "m2XNxuM7esUyjDouRnVu49PP78Q5mFAsKUZ/4mwPGlzInGVr8g7piw4FueB5qFfCHibo8gsC355UnT6WB9Gdu2h4+dsI0wh51a5UW7/"
+ "GY8zQ7VDmorGfSiELm3SpS3xvRhBOHGBLgn68hyFNMpzg/"
+ "XfJkEUUlVvayVpmJsKqeQEVwPzLW02XkvsFmjY98JIIDg4PqRw+2vWTQu4yHDx5AwtAYZaCDUYS10OjYZgzVp0NqlA2Lmc4oRYRumyr8w+"
+ "gNFxX16vxrG6DMAJeMtYyACbiuAp3RsW8G3rAWifNmTx8mEkTzZHW6Xqt9nD1qPeVxCZMq+wq7LH333FAVFW/"
+ "V6qccLaqy2uquIW41fWrKW3MkrD3fKqjdGSz0rj278X4kLakpOVvr9ww6FNk8wRL0TcfRxurhhLtRLQp6zgyu5BFA44xFIbq+"
+ "0Yvmics8iY9He45gODgaWRB9vfM8JhpTpI6c6ovoxL8fOuWEPzadjg388Lhaqv/"
+ "CJa8IVbKO9BBMrrHQVbF1iVKDO5UrGR+4LY7NSe7Jt5vrwzhFPaOhDw6VoL3p5jUq3D7ABG3tKbcdrrL+"
+ "lCJ44J0T0fYMVlzFlRjc75oNqPiKDyKOI1e9Fvw9ybZw5b0iU0kyBf1hW+JTtgr/qsszaWe/mHDdGVNXk6OBBCzECTrUj4WdHGi/"
+ "VorxztlwJ5GXoTTdFMhckg//ExJweev/"
+ "s2aYgKZRSgZVkpKHVqyNTVuEaB8UQuPhGGwEerawjiK0hRJaF4Bg9UtJMPuIu8V1bis9oCjWKVcqgiyam4t6N06O5tM38wfPTWtyD4b8ZUDl0koAfa"
+ "n9+uE1mJQw1DSMixM4QDnGXFA9bxgt/"
+ "LvKWdz0MwN9wgI5yhYtmHau0l5lO2WRsRhZvJJof1z3LvZCEqQB2F2z7GHuzmmNnyvZ8QqrnV9EBScfY25vjjBisylp2dorLh7oI2W4z8RsvegvXn4"
+ "nsh8bsEj1DJtXNRIHuhQGY9ewKOYFTc3zUK1NJaNpzBZmW8ccWYJYi6Mk7ZLfpL1A6V/C0AyePBAysg4r/"
+ "VoaLnZxgvhCZvjTH2uqfX7iQuS4F0t+qAijrgPwmm5Vy9JJrgWPKujTZkCg6Px0VVtFC+EfJSx9QyvPY6w4St/"
+ "0D4EjTxRXuEoDGkr1oYBQ8kVBj5KHyIkHCEEBAmrwA0pwtdOyzpRMj79IahmbNNuWftCHx3LklCpaD3Yo0pjTfML4ToJl5S2sQ8wvHsd960fr5fdXZ"
+ "EORmJtkLLgu5Ca6eiKf5GIw/yKSLUwY0F7rMvySfHB3PzBjJjgnt04MzGFg9vSEt6+OsRMKhV1eRGgPpLai6WzxcGY5vuiqw/"
+ "14TsoLKeHbkRO6qJgiJzo2HlDc2+MCrYNp31aeyro7WWQr/"
+ "sPybVj7BVFLuplVprsOG5TWwpzP84I3euP12UlSUma6TgVgyQmoms9rutumle3d26+b7fmnS/aQ3ps9kqYwN0+oDXzkQaP7apliI1Ks/"
+ "3z3ZsGaiLlyVSkDxgLmov1p23Qy7yI1TxxsGt4hVyD5kSJIxzoFnqT09wKhHHa1/"
+ "smUNIqeDLoo5L5e+TiBEGsiHWPcG4bsUWBF9NkbjFxp34tnO9RlE5cFALntzx2418tlQ+"
+ "4CnAljWUAiKEFJrHGH5IBIOU4jMiHmA6w0WYa9bzTnvhaRXVtxWKK9VuNbkciK8+PZOVEz7F/foJ7ERZ2P+9w8htm9J/"
+ "rxKW17GAJN5VVEotpmOPao4PJ/bi3x7Tt/ckZr8444ax/w2BZw0aLt/DgA6Kd04+MOr+pEpi9J3WHKvNnYGFWgYvSg0alOsp/"
+ "Nnwjo6OK6fvTOokwi78PxOy7+yQ2uWZOTzDkFnZ1Ri7X11X1MlAXqGehz+QfjtF2FcPVDLVFsAiLlt/"
+ "Di7LUUhzf3xitZHFkphtSE2ilrIgQAF7cPeRJBFM2MIXNhhlMfAgychBtUcPgzSzkdUbRpZ2pKNtpNZr9Xq5GQLIcKJ4MkBOBUeoduEBjWPxiGcYWo"
+ "tFatmZRoxiYAHxNTcN2FrQ0I9E12UC3NcaFTkqvCaBonx2CvBayKeXuewhxbLB503TQi8E9FSrsi9efYPhGDqqX2EsJD/"
+ "3DHOg28NZvOpZGLs9AEWpgrl+x/"
+ "Wk6mXCxM++OZxA8K3MGlQuG7Gmodcz6FHh9mqoIZZh6OrObpBUrJfdoZeWXR+"
+ "GVt8zi3m0oPlAhNUyi3a6zeZcvqfwI3M7zoXxGU2q0ETZgfCE26H9E+PNxes7mw4SwEl78lclmnNhUlZ5C4Y8v2YJnmFn8+a6WdrgjTU2awQ/"
+ "osSJFtKuNgOw9n72uyhPOkEB4qcVZ1A=";
+
+const char *sqlite_sample_db_v3 = sqlite_sample_db_v3_arr;
+const size_t sqlite_sample_db_v3_size = sizeof(sqlite_sample_db_v3_arr) - 1;
+
+static const char sqlite_sample_db_v4_arr[] =
+ "6YZiAqwf2RbWHjDRoAmm2jMPm70HtfDOgIbyJR9Btp1RWtJiNmwmqsZCfh872BvqFVfYdhtfUJwVIpOe3se/"
+ "9d9v+"
+ "eMJFKyUxyxkeWjwLJiAEa1fQtF9C8RVtmiTUweBQFmuAKlIVjTHfM0C2IspViXoBYMElb6CpuwKIN0XptduYh3GPC2IBjBET0eGWycgrOPCeTSe8PC"
+ "SfBtaX7dlCOE3zTeRqR+iP3G5Tb67xB2e8CDp4EbPJiXbNwKMyAek73D8dVqeFcjtuUfRkah/Iu6hIkBLKiS02ut/"
+ "jD0b1Sy5RyR7NDiCOKVmqnwwKDrcDbofPYmll8ilK4/qzcmzRBJKhxJeaRRy1X9UvzSbYaj22wCKrQLMqHeZ7EVOsPXJEhLVsSWnym/"
+ "vkl9mLxhiDUhvP4pNK3KjmoK1bTEIpqABLhQELQjl+EzBDt+"
+ "tYjeyZ0vCXL2JUYScZcWHn0kD1jwIYGm7r4QIfEMOwwPkkIcsIUOQjW2ICqCVfffoP5JQO9e5352tpAYcUYyiagGKDYtt1yDmFqqvL8Jn+"
+ "r19aJHMsQgUgE7dEusHXiefgeaVRmmI8gW88xvYqyPcM5dz9t8eU7jl7ipq4EPYPOl6K4p0OU/"
+ "Ro2CJTekwHkaouF2T+"
+ "L9WOuRT5WgGsUpcvmY7xDtmS85Iy3uQHZuNeL6FWSCJZ8qjbFbx3zKrlSoUwef86H8OGNA4rYmpKaEpQAsiI1IUi21q26HlgjPE9+"
+ "m2M3HZH72fnJ6lZw6v+vy7Jz/M80nIv0A5A67DE/"
+ "Gu7FPHx4NmxLPbU3pWMIg8fUl3uB5Pmk6hL+Gxi2+W+jvpLk31Qk7HPDX2H+bdTEgetpKzgjcoh/"
+ "XbvrBS82jWgHpWzw9vlbd2FUKbT20ULRE9zSHFYbdWmqchJc4Ma6iNY8rDgH4slTCt3koEaIXTwCsBguVS9a/X1QLUcWlBzt7ot5hovXQUuKd/"
+ "XHDRvusNMvcyxSOwPpF3tbMO9or+BKUq4nmwnzFUzzijl/v0KYUaTmYXQ2HgLW71oUl0cqdJ9cYk0s4MSppzo1wYF3BdQ/"
+ "348785aLf4O3HwZSXfdu2juvZTMkksahRiRCQgUDaB9izTwe/PyfRYv/"
+ "fm5VY6xzoQc3RiCa4zAQBDj3JffYw6cAYk9RHh290Gcvf8QgfBthp7dwlS3XzTNsubyd4l4FRQkvpWInHiFEUPd7wwHQNCnxE20DK/"
+ "S8Jw86Q7VpGnmPlVGQn/vdW2RrJ/"
+ "ATsQnBQPUb2+"
+ "vIoVqFRhHWk81Qxp3LL0Jy1xWe8B7y7L47R8lzI98vOwze7KBr8IH3Sw0snD86vp0kqTqnmJVYrWIROMYm9be7dvc9GyqFPF3lAhnApCetJ4+"
+ "PSkh0hnW6a1rwhb/r1QFBrhChatbFGh2fJ0nyGV5i9+Y5EzB3hdgyupU5m+rulS2DngmhEl/PQ7L7ALIZksbYwi/"
+ "1O5qjRKGf8pp93LhMysP21f2GU/Yl6JX2QZT9139ag1M/Kh/"
+ "pqYTuqrLsE38Lc878Q4kcBX45tklajnY6wGCzqSCGnhDu79SvmgE8Ymw1klxen17FDtgjqJ9ZnkJsd4RoES9kGWyI1zEKFFVH7yq0K2Psl+"
+ "ft8MQjHXNn80H1plnpQ7SRINJZBZFq8PPhRVuiJdO1+byGy/"
+ "J4TLN4jrc0CVIufAa60SOqU2S236nXsF0fW0b1V8zrgEK4qULe7EA+ubk4RxHZIotRffpHmZS5enm/r+ENZL8lECFnnUPbOQ5VhL/"
+ "J6ICCsXykofIKiYGcegY+"
+ "GQD0QvqEXoFDQQeABbIzeGB42BqQRJzMFnBCzHoSaCDOrrmJpvn1aPZoJrdejZ6DRkDLauuNretHKXyqQNvoj6jNZzrhKWLSIoqy4Sk6bec2KGjX2j"
+ "d2h9U1RahlGIR8JkTP1LHmLnq2ZqHNyDgVfDHnHEeimbxrcv8vEtxxR0rJ2t0vL5dAsC4cyZTtBfX3kpOMjVZYH4etX1Ob4mpsLbGX+"
+ "4xhqP8Tmed3gfurVkremgANwmJO0cn3/CHI/5tzsJiN5/MN2zlADJGMUYASvOPNUvxtS7rfFu8dpRpLOQt5nmlx/Trtdomixj/DlHCu0hyK9j409V/"
+ "SpChv3oHn6x5h52IMjULbSyUDYbJigvTJVWJd1CZ9Ui34rKqNp/"
+ "R+q7TtBCioHuTDT0J8m94LO08nQyAGP8ZiS8HRlcfIRorLy4OXrQ7TVaYBC7eXDW8VxkxTJIlBMFQzRykAC4/"
+ "AXBPCIADMlnRr7ClxFxJeu8M6QA4J73WyaWIuRMN3xzcZ9xzAY1xQUMx8gF6MRtB7CdKB/sRSDy0+L0XN7dXhKDtlUykputDeNj4/"
+ "ff0h8YLW3p+aozOh42a44OhPj6IwkGee9R09pnRGPooY7ytNMpoXOMnbZ4UJAwz1yhDKtHV5DJZD+ubjN92SDrAjpDjwjEVmde9F4kbPPUlJ+"
+ "jDIeeVrAOUtmlL0owMk4yhRDYZzLkstHqsLUDKc/"
+ "YWjhPBWwzMcwdDvDJe6ygkj6FSv6OgKCuHVcZqx6McuuYK4dJXpneTnxRyDvzlLdFvnB4ZczLxgYkdQfHjzVfgKILYLWxuDsgHuLJME+"
+ "00HZf3ueP8OH/qF3uhfMKOVzNPBBHlwU4Tb1s48XogYWqBoaR4iTuzVYne6CjbbgAMGi19X8J3C6BImIlu7eOs6O1wtXeYCeR/"
+ "HVljBjQKTcfgT743P9z5ZswpDVb7z2altSnx5opnRPhQnw/"
+ "FEGy1CasmBRUtPBxR0ZJpD7w0AqDrd6PPEsqWJIUqs3IBzXNVN97nV5amhJG3ZXmDwC5/"
+ "myWCszP0pXKxK5j5+LQRuEoZ5SaVWNAP7WIp5zb3m2qNbnEOUlsOCXqDMHakgQWyQt98zLCvB/ekO9/NESy53FxGWlst7/"
+ "P3BYzYalrBwpeWiEtNHXhqWiRhjWyKYghMU2ydsYGuS5huQPUEYxNBjyIuuG3N4UF/"
+ "PJcPfZG7cJCy5HVY6SHA874Cd93biGb6l8GMDoYtxfTT6YTKwWxplNOwI8fppQvc9yuFexiLJFrXWFWSFMxTE+/"
+ "l9nEdCYT5PvG7EayejuXvEXkK2fUWlhm3BXCIcQI4M18kN95NM+fTFTpC9espq12gyh6vIJv4e++"
+ "93SvbldyE262VJzWFu3AXnmCLevgBwJr9Bu5QpQ5GBsDc0sniAxEZIyGrSl0kOvozlZ0D3nqVex8JV2/"
+ "+ms8IocZlfUbDPO8W0t9M3cwdGfXT5Vjf4wVhyX+mRih860iz8Gesub26IUt9xeqk+pAdh5gV+v3rBoYzWrm3/"
+ "fPr5E6LvOqziK+9L2m3nFCEPX02oM79QfX/9plwSH5aMx1iY+LUtWELbOq8p1cLc8tGBWP839Z3RN58D0HRwVTonEbxP6vYFte5Fjhr/"
+ "ljlhI0JpRcqLgJEoaYoWOLgNcrTiCUSVMtHEdY1OfwJlHWOXfQkXGBLjNnseVGEHUe2m2xSG2EyRaC+Jph+jfLlfbu3WPEh1ZgS2sT6R+"
+ "9cn60m2YoyADp1M8ZaJ/huIwbcpiE3fPtVlPgpoiuXUpFpFAPRCdTQ87vK9dQbWwwfgttgbNQW9LwxpbTxtmUdGFzokw9tV/"
+ "joebqeinBw0R2iAtESjbWyQKbhbRO9mJXVzb5IDkVRH6mkVLdkpShRAGd3EnBQH+et75wPZ5HOuSDWFVDg1GuJs3j/"
+ "8vCZI334ABSiPKuvyNGnkWAxxsCAiU+ekeFJ8GTK8qXxRLGhQRiDCM1ly2cB9TXqV3dT5+"
+ "mgXrxbdXUPV8oqFwJhBk4WGMbzghfEMPbrn60wL12Rou2w3SnmHbPOcHhu91ENmCRTN0gaXYzqE77fQ4OUfTwQR5xTtyH2Z5VSZGl6Z5tR3KunrOg7"
+ "27g8BeolNCgMLOgonGQMsuf+Uur/M4vxBMTheHNaKc4roUfB3x0ruRv6LGRygPRigtLOLEwehjYOTK/"
+ "ZpZ4w6Tu9277NAVQlNiD+yOx9lq94jXcmfTw7c0WtAPb3wjVUuaIpODD4MjYTmcuQgd8iRHm1htgXoAJW4dUQ/"
+ "z4benIDjcikAGgxEaWQr1G3pXHlFFuz1uOCv98d+x3wnTVaSerAaj6lxf33IG3LnwCalCL4DkKyLY+"
+ "MeiNtcDxPKvVSooXxB7p8r2t8Ljjawa0SUy9ob1mYPLkuOMyigr3ozWKsBH4RhuCF7hYiGgwMXwSH0HUSkoo+"
+ "NTAEndQNU1ayjzJgnfN6QlXWmspwu5hIbYtSmMmf+YjKHkiDeFcSMaZ742rR/LS9O36UA44vpjX5gtuwRp5Cx3W5ginYwTm5F9/"
+ "xVpYzsfabq+vpIoEKWpSyt75C3Fm0CSxNV8zEvcMEiZL+"
+ "kYXcsIGEWAD5bs66HDjwQ4nylBPXZn28v6E8PU1SKTSTPPK8U2fpbM2HVnnhUJzhAMc6ooMjOB1stiTPbNK+"
+ "t5G6GbguKbW0OH9COEALEpZ9WCAisRJVUVi3umqdzIUuOU0TR1LSPkdw58wUVwNko5Y/mTqET2Ew/8sbb9Lo/"
+ "LwABF09LWdz4rnhjMviM1ScKkv4n7Q2EeI/"
+ "VWskgjS+34Ue0xDDp0+JZfkS3B2xfs4eh9XHkpig95sedAqfDOyHXI4lX10FushcbP5fni6CpFpKxoQheFv8Ek8AHgeRlXSFn8MzE7Gl+"
+ "5bC7iJ2r1ZXh0lUeVDUwXgM7EHQMqTMwB0MxjJLsIWtGpE329hbAJvSvJWI24InXoa4M0jGd+x3ULJ3EUITYiqf1k7c+"
+ "nKYd9jlaWYkZKZb7xHlNcOrVWLYl9HqiiFICQpkcWfbbqElaUUmWWRrC2/RhQZQ/"
+ "1QUXa8KO61bZdH3oK8VgIbo2MGNveROktCFiyrHhGYZ49WCWznhRd7w7CThjXb89BdMyXzA+7kiBkr894Y1sx1gx+tzksmsKvY/"
+ "a4fOkDoClnVV8T0EF8AwvpkAJyBjF+Cgc72iXNxPAPNnqOxvdC/"
+ "ySZmWOPN459ctwNPhhbUpM+nOsGD0VaC1TlAn9Balw66P4ETQH7D1OjaNf7zQ3lc5yhz5Sw3a7hZLDu5QwA+NK/"
+ "7SuE1+xntGd846b1ozytfPvEW7w3paf35d8NpIUFs0fCzF7eG76vNp4hYNy88xi/zGQ080HfGk/"
+ "lojlEpRqjqbLAatkWBUuuaKKdKIVf3ykKHix3+eW7zyY2G4nCaM3fk+eOCp4+v85l37n7/"
+ "pLWhn3X1HA1OvLenhe8Cmub0AT1IDDG4NlstG9VRDoqtXOjizahX8BQXXziAqhdOUMObMrXZlAD/"
+ "ZhuOBxIDlg8EJCUhUzqTGWnX2TOA4RaceCQhIZHzoYTCHvpUtIAOTqdiFOjRPDMuUETNNtAFk8N5vaHqlWlZIltl7wzEd0mJc7DVL5ht/"
+ "nhcvLWEhfOZAZWVMIErnjyy9oJuOVU9GYsiRDkkvRmIBAk7ziRwg1562jDtYskgrPeVm56O9nODyQaEFbDpH+K5KmoWlsAyA3btgkZ/"
+ "Vp0HSePyXS3xRw5Aqa766McOpfDXMQe/"
+ "QEc6+"
+ "gTpjjWPa9zMgmMTOUEYxutV0iMFC86LWjsWuDnj3Uo4V1tNosEPebxYKkePTRxArsVV8AbDlKNkj9Njh5EvrxJv3Ukg5eLvXDZKvcsdOsaYvkRtkC1"
+ "8+mW6hF6TVJ2Aj/NJ4+JmdM6gdVp0QHBaEAgh5CGQ/"
+ "piPFthNmvCRpeDroD8tQD24C6ssTDl6pysRNorhzO261iGCEe+"
+ "uCdhnoBjsjZzfcPYsKEQN3uMR6s17FGESaCbv5Dhm0RzInu8YtUFevKxvAsGoZqiOXxhvA6Amvo2PEWyQXD2lS8a9YB1MvUTkY4aAXQZxynRb6STnu"
+ "bmzl0HewbhXSYQ+qejqdBRxCbFhu/uMNnxZbzGjBVyYNPPMgPtH5glPfLj89arPr12nxVIV4/9CfGiu0STliVhZLTvh4xS/"
+ "7lgXC9NqwD5VfQNcFDn5ybgVbWeF/KF4k7eHA4C3pPTpgEK4UQKqZeUXuZap6xUgmPkHAD2tY5S3KhaeiI1GURDk1LVEuYMqQYg0SszamHEf/xJHM/"
+ "CzmFENCzfFvW6iJw1f/lrMHDdRJmjPRB26/7JPitYpRYWASG1FRb8IzaxqXJ3vFXBn3RMpY5iTt6nk8mWgwLCjqLiTUZPpttGLEgrpeb5e/dD1P5k/"
+ "pIiqfQobv6DfN78w4tcQhpOI0Y0SwlyzKjLjohUmKEeD70ZRZB5CC/bT/Uk4lry+bGdwfdZg8KvCgT0P+/"
+ "ciaPyARWKJxkjyUsB7meweTZncXsOy87kJHhRtK+YeK0Ee0qW3q15L1CcbSWvAG+GdNjzKN9qpFTmGZaOrKUyk+QzuQp/"
+ "jghdOOe464NgK+V4LL1Z5kJFpsBhslpJY24E6i+laEIPi03wFcztYG45zi9H9IwxTEUifoljWEbcnFFYfUh/"
+ "hsAiUnNDdMlsqPwyA+KMnD9kYyTM5EXZ+RhkVebpNeBHjtUSwoTO4Hyeve/DrKs4BMnokPvbCybs0okso7JFA0o9YLq5/"
+ "VMh5Iihxyq3hxfTykJfbGID0TMTbzGQ1zJW8wh322l14VmnNa/"
+ "rSSWit5LsCCqgrlruqIEjD8Myx4nB6uu9Z4goffZKF5vtheukwq2fDEF9IsgoVQu4byzsIccL3OQ/JcXvXwjrrkDa5gqZKn/"
+ "c2pyVwQF0vLf5O+xIiskza/vu2KdAfXdjY2GA+s3GZeBauwyM4PKkgNwb4mdRSIxjPBd+lcu2CfT4YjeGz7Ckn9f1jaNHUEcPqGYtk4x/"
+ "AEwereiXAi+"
+ "CWxhChrjULbs43IDlcwHUyTXiltyCJTyP5KakHGUgQX80mcdUYRw4DZggwoBUgAH3VX9gow3GJgMoRfegqh2fF6ExNxcBDdL5ybHirQN3Dg2RKh1WE"
+ "G2KLKS922uj3qSZ9VwbAp8T5Y11Z0Tp2QataJuSlt1Yvv22SnXBHYTVvg29xdT88ZhC5ICiFqXWLc1x5WgL413ul7oUcQD+"
+ "VZeGFBB68835DbTDoghbmaklEVuS8Yn1bwP+tAiIK3QOFpJ4+"
+ "JhCRqPf2grJEarkcxuKvc3T6EoaGQnSKoOUwj48AozJAH1QGamhV3C1akpqv6f9rtidXuVwx3BAbYAIe7HRNZ+wA0klnmHjkLcsIIOZ6lGbN/"
+ "Iy8luQlV0KS4a1DN7NrognIZ8d5zYZc+vS2qe7V7dxOp5mrKrnUDA+lZYZGVt2IoACYVfmNVkQULqs6+"
+ "FpfLMBWp4Yr55gt1riZCxXJ9bBgMCBzt1PTUVsHzZW+q22S05MMgBKaO89yTEU++46jws+W+7QZxi7BwmEPrpj+"
+ "GuuWxtnBjdqqSsOwoxVendEeLbhOm+cXSDSnXuNQE4STC/mF3DyGrHHpITdw2yLpltGwNnICiTp4slwg0Htsgwd72Ciym9Qvv+rzIwN5WR0PT9QB/"
+ "K5q4Wu2u3yxzXHPF0qwhJjVPec09/"
+ "QeBFkVo8eR1mXG9jZhNcxPPYP10wqAMVtgL65nhzzEDyS9cRoovNExH6LzPz8uMUGhZBuiN1crX5EqkVeJ0WVQbj/"
+ "ixqM9vpxKRG2WNX5A9jfTjgqx/"
+ "LqAo2i9Hf5YPMuJdGH2Sa8cwUMxlNNsjdanERNuhDF7TFeFM97hXGhVRGn4nasYEQmxQYepmf3ZhDOPk5R99gJZPPDBYSB+"
+ "UfGmqkBD1imYvrviGj0KATGpFveUmwa2BHv22gMRdidv75fEbVfpbhvHU+Vj69rWBtrTSAodscfXvmwyOPJOK97Z4hEUA2T+n/"
+ "8x2P8NYjayHJv7HrYID130WV3bOJWgAUGz++314+W7EN/"
+ "agjWDVH78SbiaquPkdVTa4GmBu4hKDNxsZYm7p6ridKNQ6dqKWUR4lAhFyUug0ohYL7DytzCW+36ICW3AsZmoSybyvI5czXNTx0+z+"
+ "zo60gODtb7LbqNb1Ff2ixlc8okhgOomHg7mvAH23t72hgq7Xl9TFk06pKshGpljpm38G62rihKxgidJ1/OaGsrobnwRmEbF6PYDqjp1NAORsG4u//"
+ "c8wKAQlA51saIpVkHDfQemKwGuLsg4t4zXkCpQd2mW1QP2ZzOOS60qG/r/V0DVVFELJzNkOz8B3feH6CjDVgACgIeAlAKYfTS8JizfcCbVCFpv/"
+ "kaJ2sTle34TxH3oMKfJYFJ8kzXqni4UAyF0jAVJOblTbuySNKKkODQJjOa+rmivHGMtMS1o51oKU+vzXGavTm0Op+"
+ "1wTsOrlDbdv1ItenEycdXQnLci6xHT98AgjVmjBAqP5RKZXEEAtokNfLkq9+"
+ "GekgmiOPTtSbazIsuBFM7ZbqrhuNY3ielOPL0hO6PGxgHdBA79KfhDVswMtsPT68L/I+RmaEA571TgzUWRwMC4QlaOoPxJULJxiJ187KeXV/GS/"
+ "e7JpGQo7pAJBZfamo7+OLKeA+QoW3vIWWnKabByiKGgp6rpdUxPGUSn+/ny4UAV1zsNB2jMaE/"
+ "1OVxznFDpgsQ2TiYc3Bi6dzrD93xWn3BVgobD1oFdXynBWLdFn+"
+ "Y0IoN5GLCSElZ4h5IVT9UB54wZ05hC3zY9qOi7VWDSAQ2AeegtNxvYg3i2D4bLt3mEd0+"
+ "3PbhV1DB1XpR3Gdoxr2XYifvicmuxNUCcRcjAvTO82IENMWqd7+/kJsIW8ZJWUzP6BrsRaM/"
+ "HboweCRehOWOEftlwNRYrq00h6gkNqGUNtKl2CDoVqr28Anbn+5uKmKwLgf3Lq3Q3bdD1zadOc5fI7NELLLGZ70WOx+"
+ "EORCVRFDSDoDKQwdixEEJiJZcEhG/"
+ "Sai+"
+ "4iYVrRQ5hwyWKHEVrGcE0YPsT8PN08RDb3HR6L7mPNN5KIaKultAxESCVQMbTYeLKpuoCesrbWn7xtejikKGeMEyLI7bF60PZ3DBNzYF3jnEEcwj5P"
+ "d1sTubA6r+D7tpzaZwSg+5VRD4PLySG7hmT4BPP3QEJvtxfNFTbdW0Yp76tSZJi82zTVY9EgpGZ5t68ZhQtzmkTUmtAQfAuNEEZ+"
+ "UE8Uuq03bEkSlCjlyUVWedQT1Qs3+oT7DwOU8ystBo6HiwEji3c5sfopnwQ8myk3/ADrVWcuCPtIB0E5OtoJ6IpwIW7enMk41vd/"
+ "wdAKi8zFrjslkjZ4q1BKPcw4TR/"
+ "UT6P0WLQhfy1qV2qBiZA1Xjzb0to398aKws8WY0fAtR8OTJTmeyHbo0QlzhkDO5Ui+6YO41lYWC3DLzEdOQYKHHiYNJac08ai9PW5/"
+ "lgZwfa75WrXnrD9C5gBtd6h8S6Mctq/qLhXpw+3B4+4XerXf7P1UXQqsKioSXR82tvF84BmCn5XpeDjXm4drhq/vtgqI7780RqYz8OutXN/"
+ "3WnLx8ru+YpT8/YZqx4B+LzKO1kGxIxP5662vIVA8arp00OhEe+e+Ss5PQTTvStEybK/w9bWhGgZHgQEu365q7TeGXO97XTibUTU/"
+ "hhf899plPuWSf4jJZFbRdTq4OMDsbAi0hDnsdhb8KYjgRE5dnOFqZr3T74Z18OB2D5INP6qBJN57su7mYlLxmsEJlMMuub3KwNUHEQdwk+"
+ "7FACRTTIxi0rTGw5olnZKXOTiGVfI0OSzjSeSCxEruR7IUdNcWVJKc6CsZVOyIdLof/ZxJrlpa67DcoylqDLmh+di06tpVCkXyWjH/PAIC/WXA35r/"
+ "7kmGUilbf2jyWPBbaw84dq+14OyGvRad4T1kRLqIGTb+DB3rWZ+"
+ "cwMgIZskWGNQSCWYTXs3mHN0VrUqNrjNBVwqzyM0Y09bxubyeBwcWOIp4oa3wvm9is95vgvUS4THuKJ0NJjP1hrcqhVLJPyia2hmtqq092PdoMBLem"
+ "yCdIaK95P6twe/+47iwnURsbBkWx6oBy+tRcCFHTW/Weie2H/FwrreTMBWvOQzKqeQ1fF0/"
+ "zUAX01cBzQRyKorHZQmHhEPUoY1HK++VYrR3TgH05KxiSJ+"
+ "NlMFZlGW8zJT47w7fp8olfMnv5AreCesNwNMz6VHBEVgAdUzjvgQoUqho7kgBOz8kwCHIEg+6DXCom+"
+ "VeIPCqEnS627km5jy46vhZQJGeMvwxQ8OxLKtDw6SiEMTFstuJPjJfRiePMsC9nI0U9BxhCIDy89LjUbttuCRpvYg4lzVq8lSFOIzPB2jlk5w9ywVf"
+ "efdKaZBkOfHAycE4n7YzdxyN4EboBQbNcMBNIHblxCSgJ7jFx1b/"
+ "H1HD4CcTfRTcqPnalHkTaqluO9OzH8GfbP6XDTJlMGyn65BMhnIBZrfZwT0uIIFId/p/"
+ "vtMo4xsVIKzivp3ZAb1CdsDxzpYCduJWygwAdpHyZLc2nxmpSJ2dv8wjwLZmltmpTrP6GptTCdSl0P5F8+3gu3U/"
+ "vFjJGAnVuyhototIkOOoYH364DiaMVrGeSMGoZQc3ue6UifdZx8V9VlQywsBP1UqBXgwpUWCruPv8B7QzZnJJHf9cHSWz5xA4TjCymVqQ/"
+ "J9PSBUYn3+H9USYO9rXU2K8dvzFY8iuIf29w8Kdzlu9NV35vDMQCp2j8vZIoJqFgaydNcCDBOU3su6+"
+ "1LHhCGYtnZmJ6rfbymVHRiJPnYAL70iOBI0i3iqbCrNhHEqgA87+a0XIqncjJPwDRPYD+"
+ "uhSSoZEKpFohKVvE68tv0OmSX9UO096qeDemGJf3HyCtRsZ8oSRQBHDccn4JBDEsmq7Dyn8EnbxAm4G5u5GzokrK8tdVdd+"
+ "I70UpLj2T0COYQLS8enk0rSj4m3Cg2x67EPOPbTDfcJC9zJLSfMrYq5J5JjUWaaLqYzqy37IkSdYiLYhAQz5/"
+ "aCNaVW0T4aWrgLIadgP2HCeiYcM3ZXztJ9p3hmExBZG4MKyMzW3K7bTGZZxFxlTgPv2t75cMsZtTJyLB4f09cx+"
+ "P9sRFrU1EnC0iMzgBpvUXYGBsUCjW347TAyCbBQrca3Ntk/"
+ "ssfDODvQNxWYwDpkAl1SvSwp3XcIHZjQ7yP5i6f07xewXCfOH6oWFJV9z9ZzPdEYvGcMlJJZ75tIHDIvvADOGmpUWRnmMrr/"
+ "M7Sp7Pd4unBuTk9TtfYj9pvYesEKYWbUm/rXw4qrZ2msaaVKHfYZRqmx99zlipvzD6ejD10rPu46a8VbzVMKDElVkQpCoCmS5a4wMsk1nV8NR3/"
+ "ZKvMjocpVaFpjKjBziHWWERK8vu7j02FGM7fY+aXro5JdNi5Ikw14QYH9qscorwl9RF9Go/rKm7ax3Xrm/"
+ "mKsOtd5zqr58A4GnRY+lXx7702r1ejQ4EmJWsZnEULRmUbEYLsCIzgwG49EGATewor3QTs2habI3qSPHZpfbZtk7D4ltqZYq/"
+ "lLt+z44D+e+iz1sygH1ypyOtfAM0it9ncbAgPKzel2Lylv9yw26dtsxxmT3/dujL1FBDMAMMTrv91RgE+CtPZ/"
+ "KaXZdqNxmBXzhLQ3bIiFcHPdXAExBYc81/Dj0q2WWfwlUw9xibH+08Ekr5hRFaLk8auTfKfh/V599caOfKc2pw2RmLLjpuI+/ilqM/"
+ "YFb3RlNB5C2TOxWyQW6cEy5FrQ58AFfm4eAC8K3XuzTH5qLj32GhI4Du2Kwtqp+7k/mZptSJE4bO7pw3JD03kafimrcWF28t9x4TTtyDLUXbdxRJy/"
+ "q+C/eCIzhcBSwjokFuyD/E+yifABEVQ97jOVFCJWJ2KHU13vajRPyaa/t6COrUideYto4pX6jB8aMBiJ/749UK5Iu9XW1hxVrXs/"
+ "F8RbPrGzI+0hLE7Wp7Adwrd1kZOs/"
+ "VshtUIVMUt3fYTRSWGlK9DJ4U7B5+ASMn6OGXibPR+CjPCEOn0AnofaliR0zzf9pao1Mcclx0dBpg0IUjWNDu89De/"
+ "kccVxzmkh9DapZSq8Z57B3wtZ91T93KPNljzPLTbShwp87xMABC0W/j4VGmiB6/"
+ "UYYDG6DqEfZ9p41X18kH+dAI4cWFvlWSIEXfZcqo4T4T2sZm3bI9u48v3ESECI3v19ZF9fTWUEiEVCWQ6tZZxquL+"
+ "KW7aQZB8tYw9PmiB89ifP4bguhdf3y4t+iJZTg2byG3YecCDlcidosjcSXLXz8udeHDxbtVfDEYb7PzO4GYMDIAIevJ3froG3jygY3mHzh/"
+ "uQor+v1No9+7tCHrbuBLn2JzGbz/rIraN8y228siXpA6NPZ7eXIMZSmsYCBgfFrtxN7TMVAQrKWzBY2HS3O6HrBkGIlskUXzXI/"
+ "feeBiXUFaKvAxBoIyGKuCbirseFNRjmC6Lq/wh9+/xwjN/aDG/GXr9qzQdNQtT51hILFCg6H4/"
+ "gRJnwLeWX7j31raA8E4HcSaJldlsYlSVYzh7qLWdhmuSjJo8fK88wQRyTKiR2jykS/PCd1U0OCylFA6YqtwQNknw3HTbDVLGdH9FXHPV14/7DB/"
+ "JximCbtPRlO6WTo/qls/"
+ "Rl4vdwvKo4avT2LBALxm21vrFFeNjV97vUgthq+r9Tciemw3QxyOIafJeLue12Bv07MUS1VLklfaAn+XhHj21exq8RiVLL2vgT+"
+ "CAe4OYwjE3BPHO71L5NCCizIOBShWno6j+ndo0k4Y0FApp6UXsLgv4sQbrRp7eW8rJVlstEWP8nlhbtn5GiQlH2qFLfkdP3Djq46aHXfao0+"
+ "Q3SbTtw5lZMJoapqO1pcx9C5XQ6vZk64DpqXNWVKgAaVrM7aCNB9a7tcWwU2yVICOtPyDaOPg4874Bhe7sGUh/ONA1GihrnX/ikNYSZ7R+gtfZmZ/"
+ "AICmbhJ+nBicOMMfOmliJTTvS6Cae3X/Tp/bM6JjT5paEVX/NvDqlOWc7bjvLyIB9vTAUHSYX7bZITgXAnMfjN2ODxA/"
+ "KE0trGknKA5nRyeddqbSn4z+vouYydv48OHj7EYx9ntEZNVi8qNtEkOVIJaE3HMKxAq2A/kUSrxiFXynB3twj/D76c6AE3blSNgf5/"
+ "eviDke7vg0wAwbW2NBOT7HKvwdIesPLb00Q1xli78Gr2N6rN/cFxF3k7olYethpJXt30kyPRc56wWR/"
+ "jPLrWl4lChCf6JiWjjJT0ZYQV1x3hODAnVye7UFout05YlwDvzAw9XmbCUl868Sz12gLfYYD9YLjemZiR7wykpTS2lxaoWMWyZPo4CC3Gi8D3cxBfR"
+ "1aYk/S4KShnnbUkTfidQ/Eicc2z54i/BUUyeCmPort+siOZOizzsOl7HOMmLQGzB7jVACOEga4bsa4F0FpShXXuqTWvOP/oUlvxlqlKZLb5+1aoN/"
+ "hlgFwlnKDKh/P9HFAa/OrrkkqIxHOj9YzBZIoWeRna/"
+ "4aHx5mqlAHIqD9edVVwmSVhHMTl2fvcfDYWiyPbVgK5cKo7Gyw0AQGlBs62xYgEKAT+RLnqhuutHWHyWnlNK2PRnrR43+BwIWYByBG4LL5uituRr/"
+ "oMPHgJtc6KM0JqT9OeeQt7lksCDtN4mey3SNo5VU1ng3qwytI+t3KLveG9vL4Qx1BNRuP4Kz1UqRrbriZTM7bz7L/"
+ "HjnbjIGIg9c20pitmqJHKO71x2hA1mZ6PMH/ziDwkfI3kej3F8gqSKazY/"
+ "twjZrf1dn0DqG+"
+ "P5nI9oGw6Kf91o7gRclhv7pHqCKGRMwq8Grf6rpW6VlSnOisoKMmd7hE4hcGy7BH5VUTZkGUsZ30meBJgMySM2aty9dCHFbedF87+"
+ "kWrzWlzaTAZCGmb5Y5b9Afob/9oPbgK5IbDb9NPyhIStJiY5B313bCvrxPDZnqqyLkJ/"
+ "mAnVGXARrPWxV9x7NfnK07iVUZQ7Eh2x4kwKFJKpuIcG+GKpMPyh3FMI4IP0QJDL9e2sHH7CmtZoxtuCOZcLE8MfQ5/"
+ "+aYdvfddw+MN84IWiUMdnHWSx9mOjDpZbWCze/CVLgfIgscA1KOyqB83KUV1krfvv7ZpliagXDQBpkBHJR3TdIGynxcoKLMWqGU/"
+ "AjtjP1CZyFFlw1i0rrLfRICJUjWc156EhBzPh3yvTJOOrJ+mDNmFl/tvGRhvs7tZyWTGgNNejHoxK3HS50fRaaZ1t50jKiyEWIUD3fz7/nEZ/"
+ "jhcgmYWw7ZNZg45r6D7p4QCbaSv4ug8OjRmojMzDkZ4bD/"
+ "+Xxd35Der1XB8iacTdNtfu1BioT8P+eNSK9NITrVtbl+CeJkj4hm+gOSdKsMtfR5QpGTmklfZRVm9IR//"
+ "BZTTu35XJk1Iso+DK9UGXQzDpVSChouFo7Qt34cyNWfgT1mq+vYbPcDIOwrIVb34KiaxO+tP1f0+"
+ "uARYTyImrJz2IchLlIL4OtgKDalcVfUPS0IcUzvxguC74NMw4ZR0tO+"
+ "XmB4E5Ob5qNzA291p5ZsIn6blF4661oDE6CcItyEJD8z7fhWKlQnevgjSC5+E7yzTH/"
+ "OHZBobmNyWF4+Gk39c90YqtUVskfURQ1rjiBSr84Cako5vZKI0VW0zg65Q7vv0qujh6tMqMJDTTChzQTlmZFm+eMI7vSFMb/"
+ "qte9NHiqajYG5fqcmubCwqdsxQeJMz8Byfi3CqfP0WcoyV9HX8X8VRkF69maJzRHNSmUC/"
+ "P7WoUW79gAwIeg69OjuQpZrPdybJTNTZ0c5+ijSXGSA+rKNDNB2Xa6uCWkkNtfMjZwB/"
+ "PaNu2lAlXkuKtEDETuW63wcWTHMMadwVS7uVZ7tOfiOxIbRxfCjp095xqA+9Mot+qF0i4qi0/"
+ "VUmjaP8xkuFYXmcNESOFKtOINuQxbGk+wX1xxrWvklvIlLOeEY1OO4kOe4VLg9TEf48Pr/FEHnnejXG6ReVHI/"
+ "uSra6XCzXt4y8i7bPYsNXUlsKDtKkynjclWKWq1CuSGJ42QdRDqrIg3cob1gOVZgjgFFloEqyZ3bzq8QX2XP2Uf9FpFMh6n9E3rJI5TE0DO4lNEOLs"
+ "dDi11yGFnMPxhI92P3bkSqTiqS1gJvoPZsIIsJanaQMD6WLDY7ajr6j+"
+ "tIgehqVBPLUgbDEKOKy2acIknat7v3p5tWUob3F2I9HUDDQ2vdvxeDzVC18QssZx1OmxzQ6FYLcdP+"
+ "YXaw796hGoKcGLjUb68AXMX5d6cwwrgQFMYzDsGxh5Ev2jRe+3fxmQclO1iOOSwhAOrW/oLRLideFyZz5taRjhg7RyEvbSdLY1BJyxJTL+AKk9/m/"
+ "+QR1RUGQYbUOn4xYD3mTpeBHJ16rXvRVZbQ0T5M097mG7dJpb8TMLl18kSKJLeOdsD3No4UqhHz9kB5oH4iCh4QI84YN8urLb19KblcI7QmRVF4UZ6"
+ "zZWf02teRFSflUmgXrnQak/psMttftzsU4/"
+ "8hWqNu6vdDBHnXyJc196LEi7OGY9si68feBHDtAn+wfdPk9OsYMueq0Dhgc0Gt0XOkUO1MhzqZwg00bW/4VOVhO/2cq6YCB6l8r/"
+ "KPxjHZOBAcob5FcA9bkBRqEDttaIbf4WZPdxGx6nDjULZ30NJkHc8j3CaTG3sLyCxPXk2pnMmjtT8csQzzRniVas4PbQfhPXySsWPCJ+"
+ "pGefADdEZK1nprxxaOd9r5y6Db41UxuFqf8iqWvsH0tFZ57WJoSgrrMLCO13pH5w2emK2GWaPWH712QRvipEbKIRUnHJqCURLF8pKDOg4gHJcazCcU"
+ "tLil/EPgYmlVk6jyw5VNbZI5sn8LLSApe1EN+J2v7oMkWEzp0VHLJzKc/AiWLp+c+CUOBlFBU2jO+f/bMzVCgqNJWYGbCGdX2jXha0MBl+W/"
+ "IteUFsyaqZcyZLE2qQ2HK8677mUNJFSoaPvSTmxpoeHZnjHluuIcWIfWYJ4O8ZlXmMS1krUbZllung93W25Yc0DqE26HLZAT/EcU/"
+ "iuxXMSVt9mzpOULLmvmQloPIWvn25LZBZaIkAD62a/"
+ "V+rydpVu58OmXiD6ev5470oBCrES93AOrg+"
+ "xUVjrpc34gr8SnXcRAGeOTY4GRWvAOxq9gKhQhyF7DseiM7azcXzXKx7sdN05puCIUN13JaDmorKy42Bpd/X/"
+ "GYXVZyWO0BIJxs35T3U0RdAAxOzALwD0IxazvK/HhXfQhrb2frFmRVmXVzvbbMwx/4B3jnBNCQYIm0Km+hpCymTlBYJQLHMe/"
+ "RqSfEkbh62ofxy+SRsMYArcGRgltqM0tP0+rmz8cO4pGZXXL0b9viD/BlcIhgvnj/w9EsFB80A8/L3JkyJbNG6SSgTH7viJrTdAv3BE6/"
+ "xVE0IWDPqoU/NnGc2/NJ7//A5GzhpaVlIQV7MuLZXW+mwjcT8yl36JGef+3l+x8/"
+ "X5+wwGVmMGwSIWvG5Gatyq7gm0VNOQyWHGVKFysBBgF335vpkK7JUfI7WY7YxpgNYX94DwrSOjNtkORKbY4qfEj4w+"
+ "iA4cCFQNOZPfk9TBM9MjOTKAF0Ky0MhVE9/"
+ "72U9RNAM1+rI8mImTJpCZN67JjoBohbuB91rC9LFR2o95i4tgS7uHbABj67rDO+"
+ "2P11E6su0r0b8zXkHa6sk6DGYlyUPiD7j40uV9jTX3hfsV1D7iLypNpXWVjvDTfhDW92Upp50H8nYb4XyVFHo2BL5KrZHpsHYM4FFJZfcwRX2n0tDj"
+ "sCVD0jMVKBeom3CtKMtFFkFYfwZE/wKdTEjCdvZ51IDUteDLagSQtdFm03qch/"
+ "1jJoMneD5hV1EbB1l7EoGY19Sj53Oy+TETDCurHCO1xMT+3yTTdphK/"
+ "QweO9f7oUhuOW3aqt9lE+mggbHYOo3cHDpSKCTGst2buBJiPupLoXtcV7w5bpRluTIVt/"
+ "DXUqhNkZ763RJ0CCHX8DP2ARQIU+OL0og8NTB4pdpncOFiUkmhD4YOviNTn2uh9naxuc2YPP8VfASUDRdN+"
+ "d3MWUj4vtkXdGHKP1P7dy6KonOgss2kCT1eO5NdEEWGjCtMD7CsM8X+YVZAdZU7MGOamtzBGzeZzyP+jdBJ3eLl0Sqi73YkGt68sKSQ3G3Rr0sube+"
+ "Pud/8LMP9PzEg13AB6Z92cG0V8WmIn8KbOKRXziWNXg4rWNNYW5GE/Zsobxka4RufAhUCsz2JADImveFx0sn+N43Wd/"
+ "KmzbNQbsimMhmXlad70CXFaRc6CUP1p9vb2zwokW5T1UJ0zUgbDy1BTM6mfnmvWD4weTavswLUXonIxhSvxEP3OJXWDY6lfWYO/"
+ "hANyGHJRu46lD4m0KHMomR3PLCuo5mg5EGTdSe+FifRhM8P9gvpZ5TyqF4J3GJSlCudp7CmfSuhgPN9fYWIb9B0n04D20sPvP45grRvNj5sg3Jak6+"
+ "bBhLdqGyNl6ePO0RHUy5woMIVfM3cWvc3WfVwWYj4I+Nb8JafMo/0WO2fe0XjUjZVmzS9I5ZyrxTjBKl5VTQart+qc1hU15LaD4U+/VuuaF7/"
+ "qWtnUzZjRB4kv/"
+ "TIaP9mVO2SQfR5aYjqlWRC6hHkvjt1h2WHRqSSZq97EUmBH+"
+ "IaTQiuvuL9zMIPUCcL4TTLtot0C3e1u04D7Ur7P5GI6xfSw6teaxNcBYiQnVzpL7IHOZWYvvBGZNPznx+"
+ "JcjzM1q5lmEYr6sZNXuPM8x6wOsL7uzGGc3ej3KJrubXSJ2lxlogDNMyfa9Fgb2y21BlTNPDiSzgVODmNjr6GiTP6Pi+"
+ "991ivIirGVKe9KHaTq755IJzqscUkFDu/"
+ "TppL59J6seMaQPrqKs+JwQClvxVbeGdKsoFL2RVOeB5dvnDZ6yaHUq2RtW9QjyNxb7cderCh5ry+n5+5y+d+"
+ "EUsqnjaa8Rr83nQpgf81lOk3ty4KPSBAtOE+2l8Z4Vmk5GDGS3YBcqCv5/tqUN7rUxJ/ZkAWR4qixomL+u2y2/"
+ "UOhyK4wvUXxauXseraOFROoZGHyVMkeLoroi3eYZh3qsJqd+uIBlSkor3Cu+RYtNaEDvGhYQRmRnuDo9DbVf5SRt6lzrj/"
+ "w3cvMUJxlhlStMWUuGyjw4bV306JsOQvZeZa3NhUTnCCC2zBuPQyeUw4xZlD34nzzNIozP1JtU/"
+ "kFfmCZp50EpLqyVm00JD8V4omUP+k6XFgBTp73thc5Ys2fbXJiE/wL1OQ9yAQ/"
+ "jcZ0iO9nFHYNHcZpTFzGlb3LZ+V99u9GcIOjxdJCVv+eBMB21ZIvzMHDg9gUI09EvJy0nHLEkGpZ0Uxq/"
+ "vl6PPAUswq4GcrmjDFP3dqWdqY5ZavZjZiTiRRFYFZL6++fdeCUtQjVbQjI1B/b3BDq3ltaPUKGMm8j5FlwBo8OjUkYEtrUE4Z0QpCiTKAslT9gNh/"
+ "jZyNwhM8RwcYF5p5TT4oIHkayKac95NuSmH8HHw1dWfJQkRHTfoxMZ5W3LSAU6L4418P5D8rdbVS+"
+ "QkoiYExjomgDVk8D8EwLYaQhgKOCD15RC6R2KOy/VOSsOLLEaBOdZBeuVcnXFH7wfE4WdprH2N0xaGKpsSX2t7INGwxAwWltbUzmXaN2Q8O3HNo/"
+ "VVhpLqaoBucAVOgif0C6Q9vDDCXP+PbO2HB6uxudk4bHfkGOpCs1FvBndk8eHdBqBLLpJqicv1uh3Xz8iRpNXun5BH8SpdDJjXQMXNdN/"
+ "R8nE1MS3Zt4Ec9SVSZLvFG/23y3O1A1lLynTE0Llki+ns3pnby00kR4297Waimb4iBLTu3LLIvSr5TArjGOmDQ3lRRlPZoB87Fg5wFSvd7HHKU/"
+ "WKAzU4YiCB8Plc8+JJo9oHxugmsyBEnsnJYMLkyNxFzfvtKDaKHyv96e5jgunUjOE5cPl45hlK8OOk+"
+ "v4bPB8PqanUFPPzPi2PZSZ8aF9ISb0zCnGdXVzkMQ7aapmCWBcN43LnUcDbUHNiPZI3sZsPJnJtX8WEXgzBPNA5hjpKCezPzb+"
+ "rkMCvLzZIiboYwJzPIxWAZ5tFtMDwmKjyDVQA93krtINXoZF8QOHFYTzBW8zemPGapWPa0coAre9+9KJNMQRssSg+y+"
+ "mAudtnpYAeOhjgK2kA9wKjQytRhXl2wlR13QoeqhAQBhomlPOpScfly4iXskQFF2A2JTblUAQv5ZGl/"
+ "LyQB0Dcmd6VRcx3tk888wtL7K3gWKXk21NFI14Zja4/"
+ "3QH6rInL3Et002Yrujl7A83yZntGZzZ75hfgKh22ZEZFimaaD07dR6xv9iONeU+WkuOFJxnt3ER5XzX2Gp6BFgREujNF9/"
+ "kx9GP6hn7ZHTmceAesR84VT0Wa2pEVGnACbLAk0YucJXUmWT4yctFJyNUVfGibO53wVLat/iiANFX9u9Hdp5KxBgekgHXrNyx/"
+ "Msw4S53e73nMckP8BuRALtGIkl5Bgo6f3lMZPENtpku4NTqXFZaeFgjaOy9frPeDDDOinmP6SmdOsi8ekF4jAU9ix4jAYtS+E+Jy9baVQP+kF/"
+ "gx9MQkFwVuZCpM9ZxgteElStBY13shhfCaeufGIQe0UjUGs5VdnmFxiSwVxME+"
+ "fwkKxRMazAC9ycUGih0r0R9mpIUC2bzOmezrtLzUccJVyCg1q48ew3xGAfN5whzowPGS/"
+ "Lz1QHs+"
+ "mt2QGoDjtpZSSHyMcxJ4zcxcleXUWv9LLTfRkcyqhR3TG2OJYeA1FelKS42lDuaqrMbTET0XC4oUjYzUQAu0HBnJsa8gseM1YI5R85LknFf3rQYg82"
+ "DO1GJJEUngKeEb8RB6uvCcJ4xs6t5+X7vFEg/"
+ "1bWl82jRsCji59AFMuDZGBQ+nr3jKTf3wFKtJvPD5bMxPdbYdN9nkyn5yooMLQUtJXhSHgy1SpCoW/UshSbOU+o9/"
+ "i+Qf3BbhgC36kU0zH8KyPlq0U7a+jJc0BCyuZEUD1FlHBl5Iu5opdPxXB29NzDUIMozrfy+iGJ+e0wZUbkV/"
+ "25q+7PHiBQs6mV2iSPOxSK2aGKxv4Ti/Pav/4WsO5OyiFt2cPJkmMZaQh8mqvcY+1ewSp9rRiM/"
+ "yHLSExz0mMN3ENGeQGsSwNjIzuIJfX5SXI7rsRMLJUTKnbWKcNT4hYthBcMkmLbGZph5i35+qiZvhSkaRCrhACNM+MLHkO28TFyRyYvB4Ev8ck+"
+ "Pj76loJ0CU4JcsvIwdY7adXWEacSNPfP9oWAyMd9OUUoem0duSRK4Nsu1Cs3mQUITE6top4qEs2qG3eregjwDQYqSQWPXjOCop2VN1YlUnsv6d8Uum"
+ "nFmZkKvFWZBeUTs90VuEe9MWQpkj8kiA16R+s/uKByZP4vFjlBeDwn/RcvxL+K6fALw8e6QTE+80ZzXN/um85Tasidi8AiRS1JtOh+5Z65/"
+ "gesyNh9GnhONOJz2fNr2QmWJ87VfTmKQqhLI4eSOTDB4dgRaVOAbyfh1slzYQQFwfh5PBqixs/oRj/V3gYfW/"
+ "kXqcCfINgebEbhef8mqBejMpwzZfF9Blptc1h93Z2oKzESD+gYaxpSnzcVdXN38QY/"
+ "pBNkgDsok0+qsh7pe2Ansr2bPTcj2wLDkqBwHW3M6xGYowXWB43b8CGbLjDcGG3QQSJ3vjCOK8JqOkci2d+"
+ "5YihgrYEBcPBRevkOHqxejYHdxRGTWNhNWPDXXqFEyS6iP+7x1tawvC6An702tqDGf3ADMYP5CVSCdH0UcwisOHXjb7aH+PmQFCqn/"
+ "YYB2yvOwRwj9K1jB9T9n+0ZASF3c71tyS7cmwIfYw+Xmmkg1SbQEXKibnWxtnX2urZlRACJGv8zKCUeK8/"
+ "N7zUJRwNteIrerYy+giNU2dFtfvwQTK6uk+"
+ "4jsuh0J8ovZT1AaDKetaFgBJ1xmWCoZZZJ0wuNJbfzOFfnavTlmDJRlqAXPeQ1F6mogTwkSSmPKSjpG/rqDhQ3AwK/"
+ "hKnYkJafUcyvU0VTIapAgxXM1bPj+atxRfNBaa8h8+emoqPOIbDWYuwt5ObCzjcsyd401t0sUIoy43rBPRVrR+64VP8aafisJrb6x6/"
+ "kT5MtSSdOTY9n+dK+fbaf1j/EHfEPI2zyZWwQdyRgxo0eqYnuTcNQLOxS0k+DQYD1C557d3ELuKKopZGUyvgWP86oWPX4DwzqK3uqkMilJaeq4/"
+ "vCNP6+viUSIonmSjTxDZZj/WJR86UOlnWebpYmSZwEklU9tX6ScaaScfzTmjPe/yXgo3/"
+ "6GIC4XtXl8b7sb6QEbec+8c5+iJXrNhrYu6Bc99S1X7RVwFV9TjqAwRwjmZQtNZiK83/"
+ "UNj54ZwrHU2W5TSOCJ6Zr24yZzFkeyNvFzSwj4jeFrc0GKjRPHEJwXzAv4w4uvgADD5yRoNLNq/"
+ "xNLFEsu0Mt8hjLkEIBI1JLXfkfHsKZccjtJGbjRwiI8cFvc1PeKER0qeywNq/tL8Rzx/iXDeuqavrogwfLOnR+aRN8N66LZys/6rAcsSTNfHoXf/"
+ "7uiJS3rjuzcuWpPpxaPSJV3f8ZL8++duB80E94r4vaBJSHQ946FRyU11aj2x2mnyTl8o8oWndW5UcvFJjvTeBdCYHUTusH9G7FPTG9/"
+ "efdh1uHnXABe8EwdihKnAAGA5Glws9rbA3qjebj8q17Ade+M+01LIInfq8vpPY6Fn9s8kqqv9lC4rRmgECgAMQ86R9bbymaS/"
+ "8qKlzh22OGztZl2b77brFtvXxSQ7+u2gV8I8nef0A2SO/7OxxDRb+KiRgstoB5zMIEF7GdTZc5cwF+7Zf3qlsffJKE3/"
+ "2Pma4ITXT+OecySK3WV2UpkeYwiUduPXxq/LfnWK5IiKYtvBpeT0LVX/"
+ "9fXrvBcwUjUn6NOCfkwpUj2JjVC5cPeMESncTJoExArTrhTTD+gHSvBXuamgUhmnLR+cXc7VWV+x2Pj5ZvlJcpAO0V1GEya94o4rneF9vJ4kWCKda/"
+ "z/L3lEguEh8mK4ZuRPgeWiOKg3fQsSKIU2bVW9Kqs/avpAnrsY5Xov4bVrV4KzaLQkp8wZCwgpoAYWAFm5s+rTwuM0TPas8eWsSb/"
+ "7swP2H3rVlIEtb8wIODDBvWOw3Fn6KoqG+WkjGR1oF3YlqD2x1AswpyH1x29n52YSuKDT7LXIUyNfeX/"
+ "vRIU6jtq+DxE93u4cngBwDOVzVAQq2j5kflq5+"
+ "Y0JY3Th7JvFinclWhlQrZWOFmMpQOB9tObkt8fWn5uXVMgUX4QWtI10mw7TqYcvkzpfcz2FJyYN54OAbtefRGmbvGqqPZd+"
+ "lodOpyMGe7RKKUBJG8LMBtOkdmpsy1tjxsQe3BAydXH4ldwjgyWaw3SRryJIxu736T+wK9AoFLTpE4dxnunRndFzQRzTZy20iTmX+"
+ "3FD6ENJhfcuFW9E1w1fA982Dy+jh9EEOIe1KJe8TcLW/UIeeUFQYyhqLkEEW3cCj8/"
+ "CoK49aDvfanO0QhdCAqQf9WH0MsGOJEL5w1OcRvchd9HgXGeyvg3DVM8vQzqCSwC87OU2REEr9iRMSfcxUAVVhcoV0N7UBceYgUsiPqR6pXnH6oPPB"
+ "AYI9aBYkogPTgb1o0+WFRw9gyx0D6TG0L8h5t54EpMpYm2qVZejvw9+"
+ "Ey34KOlfTMJaL0wxenVYen0JDgbbeg7KyHA31Gnbz2iLnH2rFE3kPAj0XjW68SNY0B7u2XMJUs1qPFwBhLGEwRwN4Coo5iUo7mNn75yhMNNnZtqoEt"
+ "5JbV7kExbML4ho24GjazqZxeslDyJJcdb4NCq06FDIHO/1o4eK9Ui/"
+ "t7+rWD2ChLfTiDr7wV8XgqBqHbTNagFN3Ij7OK49yCsFfPu7U54MMwxRbslqVwsG+"
+ "Frqokly2o8iTQVGa7JEqJR824PuMbTYhobKR23pJRAEMDr5oImipjO8/"
+ "pPyeNRTBdx2oVnRc1JDITeDwlJZcrUNojHKuEZTLXx9UjomT1GlgzJYjmxhQrklLiu+"
+ "LVjSHsIWdElLYFFG6d0JlM292TpgsmOUapwEHNrEogdcaaqPqcyvsKdUNFF2WqgZBo5ZtBeP0blD2StUjwrAJmcdDNFruhyuJZ4Unowg667e20RDp5"
+ "5c3eyMqZ28mTZ3IbD/g5edMKDtkToX2QIrx1qk/HmBa6dNPMIpf3iFmT1jZx9mjQ0hE8A/"
+ "w2JW9rFPQQU8eFHXoIt5bDQ0Won66YodBTzHtGAaNxd1h1plysaM2AikXqd8R4NOntJ2mwwk7mGV+RBZ70pTj9mpwJOYGAvFLSFnb4/"
+ "2x6GQIJyCMYi4R7HgbgJE7ZOXHACNttN1swtJeeKyyHalHcRnp0qxUZnc7jSsPeSO6YdY1PwG3CRf3zAP8o+"
+ "yU6n11wsweIu39N5MeL4CoCaR7tfnU+25I0Q86M8GIBGyqAdLYJfBD8K9Hp8R+aKCAOaauUCYrm/"
+ "KSYFGffiVA2ZTWd4Mjgc+DP+BkBHDAQH3ckMVz0RYHlV6N7rKPBAH3vcZaqyVH+ROdSyGeYy8pxXA+ohoYqjKtdnqBXBwCFTnK8+/MNmwIljErYj/"
+ "mDy+j+"
+ "dB5QiIpe1tiaDDBrvD0GoQcC1nrBbC7mIrYAI13LhH55KzMUmLR5TpyFQaVsq1oEyhVpW1ReYm24FSViucw6CqxGWkX95LL7llVgamAYRhc6bwpmcm"
+ "9g0+pcTktPmKRhaiYes4dvkHrVtpO84cCYBchHVReSA2Rx63Rk3Vzmxzon9CN+ghrzLDcqpC+52B+Qj4z0fvJY3rE91VIGDUbtsoO3ajxId3+"
+ "sjHp5Y0IiCvgZhEeW7BVe+45U/h6mUJk53HBaJhutNBBRklF7Pcl8LWXWkGBZ0IiwgeT9SxbIxK9mFf1jTS5DWhF3pvq11/"
+ "3r4vTBBTDsu1Uw3IVQjJTe8fmvM+AJc7pD9hFfEoDSbaIWFJ86BIDV3uhWcQPxKiXMq829Lg9s33oni5gicFDceYPOVM/"
+ "xEGJzz2Gpsx9GQQXpEHiJDFCzIJLbu89mvrAToeetKlbx5Bz36WNZ3tQEp7QZjltZFOWAzcT4ONg+Jr+9aeXqqCYVus/Mx2V0DWg9v0LzLplYGd/"
+ "J3cevTtWsmXD4nyrhF7xggXzhjC0PxfdDTHvw4kE+SkXHJUE+qGGPaa4ESd1YiccfC7C/"
+ "N04+EV6hsW4s9IqhnzLwaq3LH9KDXToipOcgsbydKC83tcGC1sdfB30FVAkRZ64Q+"
+ "8rYyHqQYeizOkMEPsw11rQfm0inegLdbhIcWzW7s1343bu1u8g23GverRCKBL1Yu0Nz31KrMOwn0VkdFC4qn7W3PypUpieztKHGSUHu2q4mp/"
+ "YMHTIyff/2+XsvXWDeW+HD+CQykMSJreicbirWbRgtlDRyBJ7p288kJdZ8KYJzxtBesu1VZ3m0gk46/"
+ "LiIIIxlge3Pie+OoVla8uCpc4IQUkhfx6aD5UY1WECRfKrYw0Q+kd1bdm4WinXRWEkG8icUHghPElrYys7FLqqBJ1sRym9fmxCoAnyVm+"
+ "ay8w0THD9pIK41zMpiIa84kEuIQ9fIaQHPNmKnD+"
+ "VZokWdm1SbLUr4cxRyPhRrOQHHTGrXsE3o0ahx1alE9QU2q3doijOuGH0Jb0qih19OrdDO03rbS+Y47jEd+"
+ "eOyQcvDzwWfgsPeO2XbsnOF0ajYYMwodOqn7zSJqazVgd6xOo9y0VgGPyWt0ZsZhmb4h1PwJaO3BDuiZsQod7YMdkadil0rS6R9u6001tHVhwdtXGJ"
+ "v+UGFOqNySiew7IXiBeQd/LNsCPLi9ij0peDgkBecJRpKNJY9xkeLNl9Mzb7++jY37uUQUUB1ZdPdDKvEEPZP0gXRkC48iaIY+Cn9BaBZD+Topksw/"
+ "mGyGuYPYng3aS86DBPM7DEiM/Q8sCGhqWCRbY3mMejFEb090/eVAJR8eKp0yKztmcHQlvEvhP9nU4/"
+ "x7+IDgMUBcTCLYX5lO4FtfY1FkqaWE1h2OPND7YqMX22vqpyw6C5SalKPS7w+RxKlOQIpIBgn/"
+ "WBfiKftJZaBLwvzyYwkwi6rqx9sdVoA2THGEUapo3XSg4PJMg9xQZ0ebdma6Xai5xRAao3MOUBHzFDp1TtogGydcDGUzINqVv+JRlyMMCs+"
+ "BDwd1NhSrT91b4+8pZvNsGiNSrtcjZGNBU0KnGRt29b26mASwJ2eHikKnh/"
+ "Vy7F4Xjl+8tOwfrGEHoZpOyyMDkH7jAv+DgsAls4QmuCX4D6uQhFHLtZCSMMEr4phG/"
+ "Vl+ijApCRvMBuFWA6768J5YRnwsakj+uvCwEUfECB8A3NhfA3FY8ybv9yJuCTNuGYt81onqqXJM/"
+ "7hMww7f2ZSJPk7K8HBITJ+eiqIfDQMNoN8mJYpvRORI5/dftZSY/kUQT2/"
+ "mv9xK7qT660lVUK8XfIkMwHuLWQxY1roKACFuGJoVkax3dHqSEjkpvzFjIVXlFgPuB/"
+ "jvW2VGaGnaKyTd+ViD4volcecrmmqKYzfsxiReiD1TTdmudYLjfsmqNOH7tBou7s5EFMLJwKWQoW+"
+ "ZyXOJ2wZerCV6l4uCTsUAUhYEt34ssrxp1eTh2H0L+Uq+6YX1E/"
+ "EpGZCxz4hOpuwW7GemEf3nF0WI1dQ9GAr2gwUB7xLCAeSo6XM6V5RyU1nrVM6Py1JI6xe1uk3X2zH5R1rrA+"
+ "eHZFeEAutRhnG1Ptca2QMwrnBPRKusOkF4HL3XdJKIgCXW4WK0+"
+ "QyF28B9UtPVgarzkEOanc70CTC5kJgx0f0YU2E1gHQtvGZcTj8pUCsOudHqeVQGCWl0DbhMjfZ/4/"
+ "OoXI3oeOykBetHFDIA37MlGvZ5vpVL7OQwLaPk5qJ4L0NS5ZE0rypA+qrLzD6jhWlRBCkcNJXNi8/"
+ "mtZC3U5btyQ2M610i7cvgaY1P8CbFv5CS0bBblTBVlQR/"
+ "vHUmKA+BHGL9xnezUiGdQOP9jut+8rFta35AQz19Cnp5XNlRgreJFgFcuyyz2r0ks3Minfa1shk97zoHE+Ly8r23sIwgJ7NpRvIsg0A+"
+ "1hpp2rmOhpnWogq+ISx3sZzTgddeP8COZo9c0czbc9lraqfhHL3wOqutD6u89AS204cfM8oTjSIdV+"
+ "UA3NRpPV9Tsyo8nx10IIoRVMLrlrL5vGyt8pql5cCq2DUxWCfF+MNUPKlDJyb6ANJmZiWCP5VNapsQN1CfFUiDt9w9JKL8crZbRYkzIw4sy/"
+ "Zx5f57K50Oqb+zqykkFjO0opeHQ2/B2gRl5TEodpj5aB/z91lXE8y1WOxCmyOjrAul718DDr/oqBuTW+HwaJpx15nMZfC+mN4g3KT/5zye/"
+ "vPoACRcOksgTX2bDXuVx6BL7M0bewTUTa/J3eLSzK1ulhXGb/hUZnSUWUcn4FY3p6c/Axj9kae8AAh4AnOYYyjllzoaCloGZqoFKg3KXl2Z/Egp/"
+ "uiikZLaGHJK2SaL1zkxP+Dghknt0kEGJp/na/1JmYQVYZZ56kjtEqG38Jx/JTy1p2+a/q/fh6Xf6rvkXqSpEuNlNK0YTDkgHBkmXSTcn+njTzR/"
+ "HcCS4Wkktg7Fwb7VVpN2mWAMdiU4nnOes0e5/ETvGYh5vFGYKtTMAFr6nGORLVNauds4hb+nTlmQtQQq/ZDkpnSSw1+L/"
+ "yH4rpNdUsOVxeI+O7iObPbxoxoDAUBBb5s+HjoJ+NwxQGVXQJDVCG3oey3S9iDCThtE3LMlAP/"
+ "HUgg6GTY1D01o0ITi9ttFBnTJjsMw0sCeJxsMsn6GsAdqy6M3W7T1QsJCbbp3U+UCV/"
+ "dv1fgLJrz9dgY9ihWQnejzXcpXaqTM8sLAGZKr4Spqy98rMweMrjc0MhC8YKPk48daHp5woc5aJbtNLvNAbiBJDingnTKJYSa9bfZRMsnQdJBs/"
+ "Ksl1F/Dw9MTOhyvL/sTg7uCJwEcZGN5xoPbxlYbEEXu+yxzt7x0fry+hBrZrM/"
+ "4PsXkc2jGErOBgjv80Bmm1DeE0djpdHgLfjGwsQH8bTkXy1zNsbfQC3lptfXVQ4HTP1lA5/tv2jGDsU6ygQjdu+7Q2+tobC15ovL+/"
+ "7l9WfjdVuYp9DgInepSH8ujZKzGdKgDho4LE//9aUgste9TH88dhvAVwoLbDfMGl9I7wXRgGz3Z+3uH9Pmz9frCvI4D0V/"
+ "kKgJzAcFrmVPRWtJktxdJ8JEUdyORQpjDH7992Hwz2DpUGyI9YFovjziSLO4igHHYSWFD6JS9ePy2n0WFDFymghm/XW2ssm629dd59SGz5gGNaLm/"
+ "FPj386FpIR6lhZ999R2Fpf79H+KO4cwF8m/"
+ "EhXWk8irqHcAdQO5ctI7IiAARLkkpJCrS0Xnm7zZncjDmWBbsJXmW4yMCzTTeTZButuh30wuC1PJWlgvdVQASe0oM1jybVfCRlRn0jMRWRcyvNROvo"
+ "DutinVezcIAX9Ktp7C3d1JSd0uh6VTaD6L3l+ZAKA5by736BeBMZfSVqe2G3j577QqiVDCKT6D3uNA2x3r4cnT8oWdGcuy297H15I83LLp+1/RDo/"
+ "745K02B3ejdgO6adv3G3DEio9yWEyaIQfFSL12J3Xt4JG0hhD5d3GpjizCzTx+SrBcdZJ/"
+ "k2PwBeYFKhFTAWQkhDDdyAY0sMlGDQ6Ev1KaT1TFqcOxd22FRaNCj2DLwK70uFsnJAt6vzcVDxrxqI=";
+
+const char *sqlite_sample_db_v4 = sqlite_sample_db_v4_arr;
+const size_t sqlite_sample_db_v4_size = sizeof(sqlite_sample_db_v4_arr) - 1;
diff --git a/protocols/Telegram/tdlib/td/test/data.h b/protocols/Telegram/tdlib/td/test/data.h
index c447d5cba6..07d863a894 100644
--- a/protocols/Telegram/tdlib/td/test/data.h
+++ b/protocols/Telegram/tdlib/td/test/data.h
@@ -1,15 +1,24 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
+
#include "td/utils/common.h"
-namespace td {
+
extern const char *thumbnail;
extern const size_t thumbnail_size;
extern const char *gzip_bomb;
extern const size_t gzip_bomb_size;
-} // namespace td
+
+extern const char *gzip;
+extern const size_t gzip_size;
+
+extern const char *sqlite_sample_db_v3;
+extern const size_t sqlite_sample_db_v3_size;
+
+extern const char *sqlite_sample_db_v4;
+extern const size_t sqlite_sample_db_v4_size;
diff --git a/protocols/Telegram/tdlib/td/test/db.cpp b/protocols/Telegram/tdlib/td/test/db.cpp
index 8917dd65b8..61757e9893 100644
--- a/protocols/Telegram/tdlib/td/test/db.cpp
+++ b/protocols/Telegram/tdlib/td/test/db.cpp
@@ -1,17 +1,29 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#include "data.h"
+
#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/binlog/ConcurrentBinlog.h"
#include "td/db/BinlogKeyValue.h"
+#include "td/db/DbKey.h"
#include "td/db/SeqKeyValue.h"
+#include "td/db/SqliteConnectionSafe.h"
+#include "td/db/SqliteDb.h"
#include "td/db/SqliteKeyValue.h"
#include "td/db/SqliteKeyValueSafe.h"
#include "td/db/TsSeqKeyValue.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+
+#include "td/utils/base64.h"
#include "td/utils/common.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/logging.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/port/thread.h"
@@ -24,144 +36,239 @@
#include <map>
#include <memory>
-REGISTER_TESTS(db);
-
-using namespace td;
-
template <class ContainerT>
static typename ContainerT::value_type &rand_elem(ContainerT &cont) {
- CHECK(0 < cont.size() && cont.size() <= static_cast<size_t>(std::numeric_limits<int>::max()));
- return cont[Random::fast(0, static_cast<int>(cont.size()) - 1)];
+ CHECK(0 < cont.size() && cont.size() <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
+ return cont[td::Random::fast(0, static_cast<int>(cont.size()) - 1)];
+}
+
+TEST(DB, binlog_encryption_bug) {
+ td::CSlice binlog_name = "test_binlog";
+ td::Binlog::destroy(binlog_name).ignore();
+
+ auto cucumber = td::DbKey::password("cucu'\"mb er");
+ auto empty = td::DbKey::empty();
+ {
+ td::Binlog binlog;
+ binlog
+ .init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) {}, cucumber)
+ .ensure();
+ }
+ {
+ td::Binlog binlog;
+ binlog
+ .init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) {}, cucumber)
+ .ensure();
+ }
}
TEST(DB, binlog_encryption) {
- CSlice binlog_name = "test_binlog";
- Binlog::destroy(binlog_name).ignore();
+ td::CSlice binlog_name = "test_binlog";
+ td::Binlog::destroy(binlog_name).ignore();
- auto hello = DbKey::raw_key(std::string(32, 'A'));
- auto cucumber = DbKey::password("cucumber");
- auto empty = DbKey::empty();
- auto long_data = string(10000, 'Z');
+ auto hello = td::DbKey::raw_key(td::string(32, 'A'));
+ auto cucumber = td::DbKey::password("cucu'\"mb er");
+ auto empty = td::DbKey::empty();
+ auto long_data = td::string(10000, 'Z');
{
- Binlog binlog;
- binlog.init(binlog_name.str(), [](const BinlogEvent &x) {}).ensure();
- binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer("AAAA")));
- binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer("BBBB")));
- binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer(long_data)));
+ td::Binlog binlog;
+ binlog.init(binlog_name.str(), [](const td::BinlogEvent &x) {}).ensure();
+ binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer("AAAA")),
+ td::BinlogDebugInfo{__FILE__, __LINE__});
+ binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer("BBBB")),
+ td::BinlogDebugInfo{__FILE__, __LINE__});
+ binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer(long_data)),
+ td::BinlogDebugInfo{__FILE__, __LINE__});
LOG(INFO) << "SET PASSWORD";
binlog.change_key(cucumber);
binlog.change_key(hello);
LOG(INFO) << "OK";
- binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer("CCCC")));
+ binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer("CCCC")),
+ td::BinlogDebugInfo{__FILE__, __LINE__});
binlog.close().ensure();
}
+ return;
+
auto add_suffix = [&] {
- auto fd = FileFd::open(binlog_name, FileFd::Flags::Write | FileFd::Flags::Append).move_as_ok();
+ auto fd = td::FileFd::open(binlog_name, td::FileFd::Flags::Write | td::FileFd::Flags::Append).move_as_ok();
fd.write("abacabadaba").ensure();
};
add_suffix();
{
- std::vector<string> v;
+ td::vector<td::string> v;
LOG(INFO) << "RESTART";
- Binlog binlog;
- binlog.init(binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, hello).ensure();
- CHECK(v == std::vector<string>({"AAAA", "BBBB", long_data, "CCCC"}));
+ td::Binlog binlog;
+ binlog
+ .init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.data_.str()); }, hello)
+ .ensure();
+ CHECK(v == td::vector<td::string>({"AAAA", "BBBB", long_data, "CCCC"}));
}
add_suffix();
{
- std::vector<string> v;
+ td::vector<td::string> v;
LOG(INFO) << "RESTART";
- Binlog binlog;
- auto status = binlog.init(binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber);
+ td::Binlog binlog;
+ auto status = binlog.init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber);
CHECK(status.is_error());
}
add_suffix();
{
- std::vector<string> v;
+ td::vector<td::string> v;
LOG(INFO) << "RESTART";
- Binlog binlog;
- auto status =
- binlog.init(binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber, hello);
- CHECK(v == std::vector<string>({"AAAA", "BBBB", long_data, "CCCC"}));
+ td::Binlog binlog;
+ auto status = binlog.init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber, hello);
+ CHECK(v == td::vector<td::string>({"AAAA", "BBBB", long_data, "CCCC"}));
}
-};
+}
TEST(DB, sqlite_lfs) {
- string path = "test_sqlite_db";
- SqliteDb::destroy(path).ignore();
- SqliteDb db;
- db.init(path).ensure();
+ td::string path = "test_sqlite_db";
+ td::SqliteDb::destroy(path).ignore();
+ auto db = td::SqliteDb::open_with_key(path, true, td::DbKey::empty()).move_as_ok();
db.exec("PRAGMA journal_mode=WAL").ensure();
db.exec("PRAGMA user_version").ensure();
+ td::SqliteDb::destroy(path).ignore();
}
TEST(DB, sqlite_encryption) {
- string path = "test_sqlite_db";
- SqliteDb::destroy(path).ignore();
+ td::string path = "test_sqlite_db";
+ td::SqliteDb::destroy(path).ignore();
- auto empty = DbKey::empty();
- auto cucumber = DbKey::password("cucumber");
- auto tomato = DbKey::raw_key(string(32, 'a'));
+ auto empty = td::DbKey::empty();
+ auto cucumber = td::DbKey::password("cucu'\"mb er");
+ auto tomato = td::DbKey::raw_key(td::string(32, 'a'));
{
- auto db = SqliteDb::open_with_key(path, empty).move_as_ok();
- db.set_user_version(123);
- auto kv = SqliteKeyValue();
- kv.init_with_connection(db.clone(), "kv");
+ auto db = td::SqliteDb::open_with_key(path, true, empty).move_as_ok();
+ db.set_user_version(123).ensure();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
kv.set("a", "b");
}
- SqliteDb::open_with_key(path, cucumber).ensure_error(); // key was set...
+ td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
- SqliteDb::change_key(path, cucumber, empty).ensure();
+ td::SqliteDb::change_key(path, false, cucumber, empty).ensure();
+ td::SqliteDb::change_key(path, false, cucumber, empty).ensure();
- SqliteDb::open_with_key(path, tomato).ensure_error();
+ td::SqliteDb::open_with_key(path, false, tomato).ensure_error();
{
- auto db = SqliteDb::open_with_key(path, cucumber).move_as_ok();
- auto kv = SqliteKeyValue();
- kv.init_with_connection(db.clone(), "kv");
+ auto db = td::SqliteDb::open_with_key(path, false, cucumber).move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
CHECK(kv.get("a") == "b");
CHECK(db.user_version().ok() == 123);
}
- SqliteDb::change_key(path, tomato, cucumber).ensure();
- SqliteDb::change_key(path, tomato, cucumber).ensure();
+ td::SqliteDb::change_key(path, false, tomato, cucumber).ensure();
+ td::SqliteDb::change_key(path, false, tomato, cucumber).ensure();
- SqliteDb::open_with_key(path, cucumber).ensure_error();
+ td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
{
- auto db = SqliteDb::open_with_key(path, tomato).move_as_ok();
- auto kv = SqliteKeyValue();
- kv.init_with_connection(db.clone(), "kv");
+ auto db = td::SqliteDb::open_with_key(path, false, tomato).move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
CHECK(kv.get("a") == "b");
CHECK(db.user_version().ok() == 123);
}
- SqliteDb::change_key(path, empty, tomato).ensure();
- SqliteDb::change_key(path, empty, tomato).ensure();
+ td::SqliteDb::change_key(path, false, empty, tomato).ensure();
+ td::SqliteDb::change_key(path, false, empty, tomato).ensure();
{
- auto db = SqliteDb::open_with_key(path, empty).move_as_ok();
- auto kv = SqliteKeyValue();
- kv.init_with_connection(db.clone(), "kv");
+ auto db = td::SqliteDb::open_with_key(path, false, empty).move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
CHECK(kv.get("a") == "b");
CHECK(db.user_version().ok() == 123);
}
- SqliteDb::open_with_key(path, cucumber).ensure_error();
+ td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
+ td::SqliteDb::destroy(path).ignore();
+}
+
+TEST(DB, sqlite_encryption_migrate_v3) {
+ td::string path = "test_sqlite_db";
+ td::SqliteDb::destroy(path).ignore();
+ auto cucumber = td::DbKey::password("cucumber");
+ auto empty = td::DbKey::empty();
+ if (false) {
+ // sqlite_sample_db was generated by the following code using SQLCipher based on SQLite 3.15.2
+ {
+ auto db = td::SqliteDb::change_key(path, true, cucumber, empty).move_as_ok();
+ db.set_user_version(123).ensure();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
+ kv.set("hello", "world");
+ }
+ LOG(ERROR) << td::base64_encode(td::read_file(path).move_as_ok());
+ }
+ td::write_file(path, td::base64_decode(td::Slice(sqlite_sample_db_v3, sqlite_sample_db_v3_size)).move_as_ok())
+ .ensure();
+ {
+ auto db = td::SqliteDb::open_with_key(path, true, cucumber).move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
+ CHECK(kv.get("hello") == "world");
+ CHECK(db.user_version().ok() == 123);
+ }
+ td::SqliteDb::destroy(path).ignore();
+}
+
+TEST(DB, sqlite_encryption_migrate_v4) {
+ td::string path = "test_sqlite_db";
+ td::SqliteDb::destroy(path).ignore();
+ auto cucumber = td::DbKey::password("cucu'\"mb er");
+ auto empty = td::DbKey::empty();
+ if (false) {
+ // sqlite_sample_db was generated by the following code using SQLCipher 4.4.0
+ {
+ auto db = td::SqliteDb::change_key(path, true, cucumber, empty).move_as_ok();
+ db.set_user_version(123).ensure();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
+ kv.set("hello", "world");
+ }
+ LOG(ERROR) << td::base64_encode(td::read_file(path).move_as_ok());
+ }
+ td::write_file(path, td::base64_decode(td::Slice(sqlite_sample_db_v4, sqlite_sample_db_v4_size)).move_as_ok())
+ .ensure();
+ {
+ auto r_db = td::SqliteDb::open_with_key(path, true, cucumber);
+ if (r_db.is_error()) {
+ LOG(ERROR) << r_db.error();
+ return;
+ }
+ auto db = r_db.move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ auto status = kv.init_with_connection(db.clone(), "kv");
+ if (status.is_error()) {
+ LOG(ERROR) << status;
+ } else {
+ CHECK(kv.get("hello") == "world");
+ CHECK(db.user_version().ok() == 123);
+ }
+ }
+ td::SqliteDb::destroy(path).ignore();
}
-using SeqNo = uint64;
+using SeqNo = td::uint64;
struct DbQuery {
- enum Type { Get, Set, Erase } type;
+ enum class Type { Get, Set, Erase } type = Type::Get;
SeqNo tid = 0;
- int32 id = 0;
- string key;
- string value;
+ td::int32 id = 0;
+ td::string key;
+ td::string value;
};
template <class ImplT>
@@ -172,13 +279,39 @@ class QueryHandler {
}
void do_query(DbQuery &query) {
switch (query.type) {
- case DbQuery::Get:
+ case DbQuery::Type::Get:
+ query.value = impl_.get(query.key);
+ return;
+ case DbQuery::Type::Set:
+ impl_.set(query.key, query.value);
+ query.tid = 1;
+ return;
+ case DbQuery::Type::Erase:
+ impl_.erase(query.key);
+ query.tid = 1;
+ return;
+ }
+ }
+
+ private:
+ ImplT impl_;
+};
+
+template <class ImplT>
+class SeqQueryHandler {
+ public:
+ ImplT &impl() {
+ return impl_;
+ }
+ void do_query(DbQuery &query) {
+ switch (query.type) {
+ case DbQuery::Type::Get:
query.value = impl_.get(query.key);
return;
- case DbQuery::Set:
+ case DbQuery::Type::Set:
query.tid = impl_.set(query.key, query.value);
return;
- case DbQuery::Erase:
+ case DbQuery::Type::Erase:
query.tid = impl_.erase(query.key);
return;
}
@@ -190,93 +323,93 @@ class QueryHandler {
class SqliteKV {
public:
- string get(string key) {
+ td::string get(const td::string &key) {
return kv_->get().get(key);
}
- SeqNo set(string key, string value) {
+ SeqNo set(const td::string &key, const td::string &value) {
kv_->get().set(key, value);
return 0;
}
- SeqNo erase(string key) {
+ SeqNo erase(const td::string &key) {
kv_->get().erase(key);
return 0;
}
- Status init(string name) {
- auto sql_connection = std::make_shared<SqliteConnectionSafe>(name);
- kv_ = std::make_shared<SqliteKeyValueSafe>("kv", sql_connection);
- return Status::OK();
+ td::Status init(const td::string &name) {
+ auto sql_connection = std::make_shared<td::SqliteConnectionSafe>(name, td::DbKey::empty());
+ kv_ = std::make_shared<td::SqliteKeyValueSafe>("kv", sql_connection);
+ return td::Status::OK();
}
void close() {
kv_.reset();
}
private:
- std::shared_ptr<SqliteKeyValueSafe> kv_;
+ std::shared_ptr<td::SqliteKeyValueSafe> kv_;
};
class BaselineKV {
public:
- string get(string key) {
+ td::string get(const td::string &key) {
return map_[key];
}
- SeqNo set(string key, string value) {
- map_[key] = value;
+ SeqNo set(const td::string &key, td::string value) {
+ map_[key] = std::move(value);
return ++current_tid_;
}
- SeqNo erase(string key) {
+ SeqNo erase(const td::string &key) {
map_.erase(key);
return ++current_tid_;
}
private:
- std::map<string, string> map_;
+ std::map<td::string, td::string> map_;
SeqNo current_tid_ = 0;
};
TEST(DB, key_value) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO));
- std::vector<std::string> keys;
- std::vector<std::string> values;
+ td::vector<td::string> keys;
+ td::vector<td::string> values;
for (int i = 0; i < 100; i++) {
- keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
- for (int i = 0; i < 1000; i++) {
- values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ for (int i = 0; i < 10; i++) {
+ values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
- int queries_n = 300000;
- std::vector<DbQuery> queries(queries_n);
+ int queries_n = 1000;
+ td::vector<DbQuery> queries(queries_n);
for (auto &q : queries) {
- int op = Random::fast(0, 2);
+ int op = td::Random::fast(0, 2);
const auto &key = rand_elem(keys);
const auto &value = rand_elem(values);
if (op == 0) {
- q.type = DbQuery::Get;
+ q.type = DbQuery::Type::Get;
q.key = key;
} else if (op == 1) {
- q.type = DbQuery::Erase;
+ q.type = DbQuery::Type::Erase;
q.key = key;
} else if (op == 2) {
- q.type = DbQuery::Set;
+ q.type = DbQuery::Type::Set;
q.key = key;
q.value = value;
}
}
QueryHandler<BaselineKV> baseline;
- QueryHandler<SeqKeyValue> kv;
- QueryHandler<TsSeqKeyValue> ts_kv;
- QueryHandler<BinlogKeyValue<Binlog>> new_kv;
+ QueryHandler<td::SeqKeyValue> kv;
+ QueryHandler<td::TsSeqKeyValue> ts_kv;
+ QueryHandler<td::BinlogKeyValue<td::Binlog>> new_kv;
- CSlice new_kv_name = "test_new_kv";
- Binlog::destroy(new_kv_name).ignore();
+ td::CSlice new_kv_name = "test_new_kv";
+ td::Binlog::destroy(new_kv_name).ignore();
new_kv.impl().init(new_kv_name.str()).ensure();
- QueryHandler<SqliteKeyValue> sqlite_kv;
- CSlice name = "test_sqlite_kv";
- SqliteDb::destroy(name).ignore();
- sqlite_kv.impl().init(name.str()).ensure();
+ QueryHandler<td::SqliteKeyValue> sqlite_kv;
+ td::CSlice path = "test_sqlite_kv";
+ td::SqliteDb::destroy(path).ignore();
+ auto db = td::SqliteDb::open_with_key(path, true, td::DbKey::empty()).move_as_ok();
+ sqlite_kv.impl().init_with_connection(std::move(db), "KV").ensure();
int cnt = 0;
for (auto &q : queries) {
@@ -294,41 +427,82 @@ TEST(DB, key_value) {
ASSERT_EQ(a.value, c.value);
ASSERT_EQ(a.value, d.value);
ASSERT_EQ(a.value, e.value);
- if (cnt++ % 10000 == 0) {
+ if (cnt++ % 200 == 0) {
new_kv.impl().init(new_kv_name.str()).ensure();
}
}
+ td::SqliteDb::destroy(path).ignore();
+ td::Binlog::destroy(new_kv_name).ignore();
+}
+
+TEST(DB, key_value_set_all) {
+ td::vector<td::string> keys;
+ td::vector<td::string> values;
+
+ for (int i = 0; i < 100; i++) {
+ keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
+ }
+ for (int i = 0; i < 10; i++) {
+ values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
+ }
+
+ td::SqliteKeyValue sqlite_kv;
+ td::CSlice sqlite_kv_name = "test_sqlite_kv";
+ td::SqliteDb::destroy(sqlite_kv_name).ignore();
+ auto db = td::SqliteDb::open_with_key(sqlite_kv_name, true, td::DbKey::empty()).move_as_ok();
+ sqlite_kv.init_with_connection(std::move(db), "KV").ensure();
+
+ BaselineKV kv;
+
+ int queries_n = 100;
+ while (queries_n-- > 0) {
+ int cnt = td::Random::fast(0, 10);
+ td::FlatHashMap<td::string, td::string> key_values;
+ for (int i = 0; i < cnt; i++) {
+ auto key = rand_elem(keys);
+ auto value = rand_elem(values);
+ key_values[key] = value;
+ kv.set(key, value);
+ }
+
+ sqlite_kv.set_all(key_values);
+
+ for (auto &key : keys) {
+ CHECK(kv.get(key) == sqlite_kv.get(key));
+ }
+ }
+ td::SqliteDb::destroy(sqlite_kv_name).ignore();
}
-TEST(DB, thread_key_value) {
#if !TD_THREAD_UNSUPPORTED
- std::vector<std::string> keys;
- std::vector<std::string> values;
+TEST(DB, thread_key_value) {
+ td::vector<td::string> keys;
+ td::vector<td::string> values;
for (int i = 0; i < 100; i++) {
- keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 1000; i++) {
- values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
int threads_n = 4;
- int queries_n = 100000;
+ int queries_n = 10000;
- std::vector<std::vector<DbQuery>> queries(threads_n, std::vector<DbQuery>(queries_n));
+ td::vector<td::vector<DbQuery>> queries(threads_n, td::vector<DbQuery>(queries_n));
for (auto &qs : queries) {
for (auto &q : qs) {
- int op = Random::fast(0, 10);
+ int op = td::Random::fast(0, 10);
const auto &key = rand_elem(keys);
const auto &value = rand_elem(values);
if (op > 1) {
- q.type = DbQuery::Get;
+ q.type = DbQuery::Type::Get;
q.key = key;
} else if (op == 0) {
- q.type = DbQuery::Erase;
+ q.type = DbQuery::Type::Erase;
q.key = key;
} else if (op == 1) {
- q.type = DbQuery::Set;
+ q.type = DbQuery::Type::Set;
q.key = key;
q.value = value;
}
@@ -336,12 +510,12 @@ TEST(DB, thread_key_value) {
}
QueryHandler<BaselineKV> baseline;
- QueryHandler<TsSeqKeyValue> ts_kv;
+ SeqQueryHandler<td::TsSeqKeyValue> ts_kv;
- std::vector<thread> threads(threads_n);
- std::vector<std::vector<DbQuery>> res(threads_n);
+ td::vector<td::thread> threads(threads_n);
+ td::vector<td::vector<DbQuery>> res(threads_n);
for (int i = 0; i < threads_n; i++) {
- threads[i] = thread([&ts_kv, &queries, &res, i]() {
+ threads[i] = td::thread([&ts_kv, &queries, &res, i] {
for (auto q : queries[i]) {
ts_kv.do_query(q);
res[i].push_back(q);
@@ -352,7 +526,7 @@ TEST(DB, thread_key_value) {
thread.join();
}
- std::vector<std::size_t> pos(threads_n);
+ td::vector<std::size_t> pos(threads_n);
while (true) {
bool was = false;
for (int i = 0; i < threads_n; i++) {
@@ -362,7 +536,7 @@ TEST(DB, thread_key_value) {
}
auto &q = res[i][p];
if (q.tid == 0) {
- if (q.type == DbQuery::Get) {
+ if (q.type == DbQuery::Type::Get) {
auto nq = q;
baseline.do_query(nq);
if (nq.value == q.value) {
@@ -402,25 +576,23 @@ TEST(DB, thread_key_value) {
baseline.do_query(res[best][pos[best]]);
pos[best]++;
}
-#endif
}
+#endif
TEST(DB, persistent_key_value) {
- using KeyValue = BinlogKeyValue<ConcurrentBinlog>;
- // using KeyValue = PersistentKeyValue;
- // using KeyValue = SqliteKV;
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
- std::vector<std::string> keys;
- std::vector<std::string> values;
- CSlice name = "test_pmc";
- Binlog::destroy(name).ignore();
- SqliteDb::destroy(name).ignore();
+ using KeyValue = td::BinlogKeyValue<td::ConcurrentBinlog>;
+ // using KeyValue = td::SqliteKeyValue;
+ td::vector<td::string> keys;
+ td::vector<td::string> values;
+ td::CSlice path = "test_pmc";
+ td::Binlog::destroy(path).ignore();
+ td::SqliteDb::destroy(path).ignore();
for (int i = 0; i < 100; i++) {
- keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 1000; i++) {
- values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
QueryHandler<BaselineKV> baseline;
@@ -429,34 +601,34 @@ TEST(DB, persistent_key_value) {
int threads_n = 4;
int queries_n = 3000 / threads_n;
- std::vector<std::vector<DbQuery>> queries(threads_n, std::vector<DbQuery>(queries_n));
+ td::vector<td::vector<DbQuery>> queries(threads_n, td::vector<DbQuery>(queries_n));
for (auto &qs : queries) {
for (auto &q : qs) {
- int op = Random::fast(0, 10);
+ int op = td::Random::fast(0, 10);
const auto &key = rand_elem(keys);
const auto &value = rand_elem(values);
if (op > 1) {
- q.type = DbQuery::Get;
+ q.type = DbQuery::Type::Get;
q.key = key;
} else if (op == 0) {
- q.type = DbQuery::Erase;
+ q.type = DbQuery::Type::Erase;
q.key = key;
} else if (op == 1) {
- q.type = DbQuery::Set;
+ q.type = DbQuery::Type::Set;
q.key = key;
q.value = value;
}
}
}
- std::vector<std::vector<DbQuery>> res(threads_n);
- class Worker : public Actor {
+ td::vector<td::vector<DbQuery>> res(threads_n);
+ class Worker final : public td::Actor {
public:
- Worker(ActorShared<> parent, std::shared_ptr<QueryHandler<KeyValue>> kv, const std::vector<DbQuery> *queries,
- std::vector<DbQuery> *res)
+ Worker(td::ActorShared<> parent, std::shared_ptr<SeqQueryHandler<KeyValue>> kv,
+ const td::vector<DbQuery> *queries, td::vector<DbQuery> *res)
: parent_(std::move(parent)), kv_(std::move(kv)), queries_(queries), res_(res) {
}
- void loop() override {
+ void loop() final {
for (auto q : *queries_) {
kv_->do_query(q);
res_->push_back(q);
@@ -465,55 +637,54 @@ TEST(DB, persistent_key_value) {
}
private:
- ActorShared<> parent_;
- std::shared_ptr<QueryHandler<KeyValue>> kv_;
- const std::vector<DbQuery> *queries_;
- std::vector<DbQuery> *res_;
+ td::ActorShared<> parent_;
+ std::shared_ptr<SeqQueryHandler<KeyValue>> kv_;
+ const td::vector<DbQuery> *queries_;
+ td::vector<DbQuery> *res_;
};
- class Main : public Actor {
+ class Main final : public td::Actor {
public:
- Main(int threads_n, const std::vector<std::vector<DbQuery>> *queries, std::vector<std::vector<DbQuery>> *res)
- : threads_n_(threads_n), queries_(queries), res_(res) {
+ Main(int threads_n, const td::vector<td::vector<DbQuery>> *queries, td::vector<td::vector<DbQuery>> *res)
+ : threads_n_(threads_n), queries_(queries), res_(res), ref_cnt_(threads_n) {
}
- void start_up() override {
- LOG(INFO) << "start_up";
+ void start_up() final {
+ LOG(INFO) << "Start up";
kv_->impl().init("test_pmc").ensure();
- ref_cnt_ = threads_n_;
for (int i = 0; i < threads_n_; i++) {
- create_actor_on_scheduler<Worker>("Worker", i + 1, actor_shared(this, 2), kv_, &queries_->at(i), &res_->at(i))
+ td::create_actor_on_scheduler<Worker>("Worker", i + 1, actor_shared(this, 2), kv_, &queries_->at(i),
+ &res_->at(i))
.release();
}
}
- void tear_down() override {
- LOG(INFO) << "tear_down";
+ void tear_down() final {
+ LOG(INFO) << "Tear down";
// kv_->impl().close();
}
- void hangup_shared() override {
- LOG(INFO) << "hangup";
+ void hangup_shared() final {
+ LOG(INFO) << "Hang up";
ref_cnt_--;
if (ref_cnt_ == 0) {
kv_->impl().close();
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
stop();
}
}
- void hangup() override {
+ void hangup() final {
LOG(ERROR) << "BAD HANGUP";
}
private:
int threads_n_;
- const std::vector<std::vector<DbQuery>> *queries_;
- std::vector<std::vector<DbQuery>> *res_;
+ const td::vector<td::vector<DbQuery>> *queries_;
+ td::vector<td::vector<DbQuery>> *res_;
- std::shared_ptr<QueryHandler<KeyValue>> kv_{new QueryHandler<KeyValue>()};
+ std::shared_ptr<SeqQueryHandler<KeyValue>> kv_{new SeqQueryHandler<KeyValue>()};
int ref_cnt_;
};
- ConcurrentScheduler sched;
- sched.init(threads_n);
+ td::ConcurrentScheduler sched(threads_n, 0);
sched.create_actor_unsafe<Main>(0, "Main", threads_n, &queries, &res).release();
sched.start();
while (sched.run_main(10)) {
@@ -521,7 +692,7 @@ TEST(DB, persistent_key_value) {
}
sched.finish();
- std::vector<std::size_t> pos(threads_n);
+ td::vector<std::size_t> pos(threads_n);
while (true) {
bool was = false;
for (int i = 0; i < threads_n; i++) {
@@ -531,7 +702,7 @@ TEST(DB, persistent_key_value) {
}
auto &q = res[i][p];
if (q.tid == 0) {
- if (q.type == DbQuery::Get) {
+ if (q.type == DbQuery::Type::Get) {
auto nq = q;
baseline.do_query(nq);
if (nq.value == q.value) {
@@ -572,4 +743,5 @@ TEST(DB, persistent_key_value) {
pos[best]++;
}
}
+ td::SqliteDb::destroy(path).ignore();
}
diff --git a/protocols/Telegram/tdlib/td/test/fuzz_url.cpp b/protocols/Telegram/tdlib/td/test/fuzz_url.cpp
index 74047135c0..0a1e880cbb 100644
--- a/protocols/Telegram/tdlib/td/test/fuzz_url.cpp
+++ b/protocols/Telegram/tdlib/td/test/fuzz_url.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -17,7 +17,7 @@ static td::string get_utf_string(td::Slice from) {
td::string res;
td::string alph = " ab@./01#";
for (auto c : from) {
- res += alph[td::uint8(c) % alph.size()];
+ res += alph[static_cast<td::uint8>(c) % alph.size()];
}
LOG(ERROR) << res;
return res;
diff --git a/protocols/Telegram/tdlib/td/test/http.cpp b/protocols/Telegram/tdlib/td/test/http.cpp
index 98c94b2e8a..2498c53e5c 100644
--- a/protocols/Telegram/tdlib/td/test/http.cpp
+++ b/protocols/Telegram/tdlib/td/test/http.cpp
@@ -1,10 +1,14 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/tests.h"
+#include "data.h"
+
+#if TD_DARWIN_WATCH_OS
+#include "td/net/DarwinHttp.h"
+#endif
#include "td/net/HttpChunkedByteFlow.h"
#include "td/net/HttpHeaderCreator.h"
@@ -12,39 +16,40 @@
#include "td/net/HttpReader.h"
#include "td/utils/AesCtrByteFlow.h"
+#include "td/utils/algorithm.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
#include "td/utils/ByteFlow.h"
+#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/Gzip.h"
#include "td/utils/GzipByteFlow.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
-#include "td/utils/port/Fd.h"
+#include "td/utils/port/detail/PollableFd.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h"
+#include "td/utils/port/PollFlags.h"
#include "td/utils/port/thread_local.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
-
-#include "test/data.h"
+#include "td/utils/tests.h"
+#include "td/utils/UInt.h"
#include <algorithm>
-#include <cstdlib>
+#include <condition_variable>
#include <limits>
+#include <mutex>
-REGISTER_TESTS(http)
-
-using namespace td;
-
-static string make_chunked(string str) {
- auto v = rand_split(str);
- string res;
+static td::string make_chunked(const td::string &str) {
+ auto v = td::rand_split(str);
+ td::string res;
for (auto &s : v) {
- res += PSTRING() << format::as_hex_dump(int(s.size()));
+ res += PSTRING() << td::format::as_hex_dump(static_cast<td::int32>(s.size()));
res += "\r\n";
res += s;
res += "\r\n";
@@ -53,30 +58,33 @@ static string make_chunked(string str) {
return res;
}
-static string gen_http_content() {
- int t = Random::fast(0, 2);
+static td::string gen_http_content() {
+ int t = td::Random::fast(0, 2);
int len;
if (t == 0) {
- len = Random::fast(1, 10);
+ len = td::Random::fast(1, 10);
} else if (t == 1) {
- len = Random::fast(100, 200);
+ len = td::Random::fast(100, 200);
} else {
- len = Random::fast(1000, 20000);
+ len = td::Random::fast(1000, 20000);
}
- return rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), len);
+ return td::rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), len);
}
-static string make_http_query(string content, bool is_chunked, bool is_gzip, double gzip_k = 5,
- string zip_override = "") {
- HttpHeaderCreator hc;
+static td::string make_http_query(td::string content, bool is_json, bool is_chunked, bool is_gzip, double gzip_k = 5,
+ td::string zip_override = td::string()) {
+ td::HttpHeaderCreator hc;
hc.init_post("/");
- hc.add_header("jfkdlsahhjk", rand_string('a', 'z', Random::fast(1, 2000)));
+ hc.add_header("jfkdlsahhjk", td::rand_string('a', 'z', td::Random::fast(1, 2000)));
+ if (is_json) {
+ hc.add_header("content-type", "application/json");
+ }
if (is_gzip) {
- BufferSlice zip;
+ td::BufferSlice zip;
if (zip_override.empty()) {
- zip = gzencode(content, gzip_k);
+ zip = td::gzencode(content, gzip_k);
} else {
- zip = BufferSlice(zip_override);
+ zip = td::BufferSlice(zip_override);
}
if (!zip.empty()) {
hc.add_header("content-encoding", "gzip");
@@ -89,22 +97,19 @@ static string make_http_query(string content, bool is_chunked, bool is_gzip, dou
} else {
hc.set_content_size(content.size());
}
- string res;
auto r_header = hc.finish();
CHECK(r_header.is_ok());
- res += r_header.ok().str();
- res += content;
- return res;
+ return PSTRING() << r_header.ok() << content;
}
-static string rand_http_query(string content) {
- bool is_chunked = Random::fast(0, 1) == 0;
- bool is_gzip = Random::fast(0, 1) == 0;
- return make_http_query(std::move(content), is_chunked, is_gzip);
+static td::string rand_http_query(td::string content) {
+ bool is_chunked = td::Random::fast_bool();
+ bool is_gzip = td::Random::fast_bool();
+ return make_http_query(std::move(content), false, is_chunked, is_gzip);
}
-static string join(const std::vector<string> &v) {
- string res;
+static td::string join(const td::vector<td::string> &v) {
+ td::string res;
for (auto &s : v) {
res += s;
}
@@ -112,10 +117,10 @@ static string join(const std::vector<string> &v) {
}
TEST(Http, stack_overflow) {
- ChainBufferWriter writer;
- BufferSlice slice(string(256, 'A'));
+ td::ChainBufferWriter writer;
+ td::BufferSlice slice(td::string(256, 'A'));
for (int i = 0; i < 1000000; i++) {
- ChainBufferWriter tmp_writer;
+ td::ChainBufferWriter tmp_writer;
writer.append(slice.clone());
}
{
@@ -128,30 +133,50 @@ TEST(Http, reader) {
#if TD_ANDROID || TD_TIZEN
return;
#endif
- clear_thread_locals();
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO));
- auto start_mem = BufferAllocator::get_buffer_mem();
+ td::clear_thread_locals();
+ auto start_mem = td::BufferAllocator::get_buffer_mem();
+ auto start_size = td::BufferAllocator::get_buffer_slice_size();
{
- auto input_writer = ChainBufferWriter::create_empty();
+ td::BufferSlice a("test test");
+ td::BufferSlice b = std::move(a);
+#if TD_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunknown-pragmas"
+#pragma clang diagnostic ignored "-Wunknown-warning-option"
+#pragma clang diagnostic ignored "-Wself-move"
+#endif
+ a = std::move(a);
+ b = std::move(b);
+#if TD_CLANG
+#pragma clang diagnostic pop
+#endif
+ a = std::move(b);
+ td::BufferSlice c = a.from_slice(a);
+ CHECK(c.size() == a.size());
+ }
+ td::clear_thread_locals();
+ ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem());
+ ASSERT_EQ(start_size, td::BufferAllocator::get_buffer_slice_size());
+ for (int i = 0; i < 20; i++) {
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- HttpReader reader;
+ td::HttpReader reader;
int max_post_size = 10000;
reader.init(&input, max_post_size, 0);
- std::srand(4);
- std::vector<string> contents(1000);
+ td::vector<td::string> contents(100);
std::generate(contents.begin(), contents.end(), gen_http_content);
auto v = td::transform(contents, rand_http_query);
- auto vec_str = rand_split(join(v));
+ auto vec_str = td::rand_split(join(v));
- HttpQuery q;
- std::vector<string> res;
+ td::HttpQuery q;
+ td::vector<td::string> res;
for (auto &str : vec_str) {
input_writer.append(str);
input.sync_with_writer();
while (true) {
auto r_state = reader.read_next(&q);
- LOG_IF(ERROR, r_state.is_error()) << r_state.error() << tag("ok", res.size());
+ LOG_IF(ERROR, r_state.is_error()) << r_state.error() << td::tag("ok", res.size());
ASSERT_TRUE(r_state.is_ok());
auto state = r_state.ok();
if (state == 0) {
@@ -161,11 +186,11 @@ TEST(Http, reader) {
ASSERT_EQ(expected, q.content_.str());
res.push_back(q.content_.str());
} else {
- auto r_fd = FileFd::open(q.files_[0].temp_file_name, FileFd::Read);
+ auto r_fd = td::FileFd::open(q.files_[0].temp_file_name, td::FileFd::Read);
ASSERT_TRUE(r_fd.is_ok());
auto fd = r_fd.move_as_ok();
- string content(td::narrow_cast<size_t>(q.files_[0].size), '\0');
- auto r_size = fd.read(MutableSlice(content));
+ td::string content(td::narrow_cast<std::size_t>(q.files_[0].size), '\0');
+ auto r_size = fd.read(td::MutableSlice(content));
ASSERT_TRUE(r_size.is_ok());
ASSERT_TRUE(r_size.ok() == content.size());
ASSERT_TRUE(td::narrow_cast<int>(content.size()) > max_post_size);
@@ -181,23 +206,26 @@ TEST(Http, reader) {
ASSERT_EQ(contents.size(), res.size());
ASSERT_EQ(contents, res);
}
- clear_thread_locals();
- ASSERT_EQ(start_mem, BufferAllocator::get_buffer_mem());
+ td::clear_thread_locals();
+ ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem());
+ ASSERT_EQ(start_size, td::BufferAllocator::get_buffer_slice_size());
}
TEST(Http, gzip_bomb) {
-#if TD_ANDROID || TD_TIZEN || TD_EMSCRIPTEN // the test should be disabled on low-memory systems
+#if TD_ANDROID || TD_TIZEN || TD_EMSCRIPTEN // the test must be disabled on low-memory systems
return;
#endif
auto gzip_bomb_str =
- gzdecode(gzdecode(base64url_decode(Slice(gzip_bomb, gzip_bomb_size)).ok()).as_slice()).as_slice().str();
+ td::gzdecode(td::gzdecode(td::base64url_decode(td::Slice(gzip_bomb, gzip_bomb_size)).ok()).as_slice())
+ .as_slice()
+ .str();
- auto query = make_http_query("", false, true, 0.01, gzip_bomb_str);
- auto parts = rand_split(query);
- auto input_writer = ChainBufferWriter::create_empty();
+ auto query = make_http_query("", false, false, true, 0.01, gzip_bomb_str);
+ auto parts = td::rand_split(query);
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- HttpReader reader;
- HttpQuery q;
+ td::HttpReader reader;
+ td::HttpQuery q;
reader.init(&input, 100000000, 0);
for (auto &part : parts) {
input_writer.append(part);
@@ -211,21 +239,40 @@ TEST(Http, gzip_bomb) {
}
}
+TEST(Http, gzip) {
+ auto gzip_str = td::gzdecode(td::base64url_decode(td::Slice(gzip, gzip_size)).ok()).as_slice().str();
+
+ td::ChainBufferWriter input_writer;
+ auto input = input_writer.extract_reader();
+
+ td::HttpReader reader;
+ reader.init(&input, 0, 0);
+
+ auto query = make_http_query("", true, false, true, 0.01, gzip_str);
+ input_writer.append(query);
+ input.sync_with_writer();
+
+ td::HttpQuery q;
+ auto r_state = reader.read_next(&q);
+ ASSERT_TRUE(r_state.is_error());
+ ASSERT_EQ(413, r_state.error().code());
+}
+
TEST(Http, aes_ctr_encode_decode_flow) {
- auto str = rand_string('a', 'z', 1000000);
- auto parts = rand_split(str);
- auto input_writer = ChainBufferWriter::create_empty();
+ auto str = td::rand_string('a', 'z', 1000000);
+ auto parts = td::rand_split(str);
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- ByteFlowSource source(&input);
- UInt256 key;
- UInt128 iv;
- Random::secure_bytes(key.raw, sizeof(key));
- Random::secure_bytes(iv.raw, sizeof(iv));
- AesCtrByteFlow aes_encode;
+ td::ByteFlowSource source(&input);
+ td::UInt256 key;
+ td::UInt128 iv;
+ td::Random::secure_bytes(key.raw, sizeof(key));
+ td::Random::secure_bytes(iv.raw, sizeof(iv));
+ td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
- AesCtrByteFlow aes_decode;
+ td::AesCtrByteFlow aes_decode;
aes_decode.init(key, iv);
- ByteFlowSink sink;
+ td::ByteFlowSink sink;
source >> aes_encode >> aes_decode >> sink;
ASSERT_TRUE(!sink.is_ready());
@@ -234,7 +281,7 @@ TEST(Http, aes_ctr_encode_decode_flow) {
source.wakeup();
}
ASSERT_TRUE(!sink.is_ready());
- source.close_input(Status::OK());
+ source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@@ -242,25 +289,25 @@ TEST(Http, aes_ctr_encode_decode_flow) {
}
TEST(Http, aes_file_encryption) {
- auto str = rand_string('a', 'z', 1000000);
- CSlice name = "test_encryption";
- unlink(name).ignore();
- UInt256 key;
- UInt128 iv;
- Random::secure_bytes(key.raw, sizeof(key));
- Random::secure_bytes(iv.raw, sizeof(iv));
+ auto str = td::rand_string('a', 'z', 1000000);
+ td::CSlice name = "test_encryption";
+ td::unlink(name).ignore();
+ td::UInt256 key;
+ td::UInt128 iv;
+ td::Random::secure_bytes(key.raw, sizeof(key));
+ td::Random::secure_bytes(iv.raw, sizeof(iv));
{
- BufferedFdBase<FileFd> fd(FileFd::open(name, FileFd::Write | FileFd::Create).move_as_ok());
+ td::BufferedFdBase<td::FileFd> fd(td::FileFd::open(name, td::FileFd::Write | td::FileFd::Create).move_as_ok());
- auto parts = rand_split(str);
+ auto parts = td::rand_split(str);
- ChainBufferWriter output_writer;
+ td::ChainBufferWriter output_writer;
auto output_reader = output_writer.extract_reader();
- ByteFlowSource source(&output_reader);
- AesCtrByteFlow aes_encode;
+ td::ByteFlowSource source(&output_reader);
+ td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
- ByteFlowSink sink;
+ td::ByteFlowSink sink;
source >> aes_encode >> sink;
fd.set_output_reader(sink.get_output());
@@ -274,26 +321,26 @@ TEST(Http, aes_file_encryption) {
}
{
- BufferedFdBase<FileFd> fd(FileFd::open(name, FileFd::Read).move_as_ok());
+ td::BufferedFdBase<td::FileFd> fd(td::FileFd::open(name, td::FileFd::Read).move_as_ok());
- ChainBufferWriter input_writer;
+ td::ChainBufferWriter input_writer;
auto input_reader = input_writer.extract_reader();
- ByteFlowSource source(&input_reader);
- AesCtrByteFlow aes_encode;
+ td::ByteFlowSource source(&input_reader);
+ td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
- ByteFlowSink sink;
+ td::ByteFlowSink sink;
source >> aes_encode >> sink;
fd.set_input_writer(&input_writer);
- fd.update_flags(Fd::Flag::Read);
- while (can_read(fd)) {
+ fd.get_poll_info().add_flags(td::PollFlags::Read());
+ while (can_read_local(fd)) {
fd.flush_read(4096).ensure();
source.wakeup();
}
fd.close();
- source.close_input(Status::OK());
+ source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@@ -303,20 +350,20 @@ TEST(Http, aes_file_encryption) {
}
TEST(Http, chunked_flow) {
- auto str = rand_string('a', 'z', 100);
- auto parts = rand_split(make_chunked(str));
- auto input_writer = ChainBufferWriter::create_empty();
+ auto str = td::rand_string('a', 'z', 100);
+ auto parts = td::rand_split(make_chunked(str));
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- ByteFlowSource source(&input);
- HttpChunkedByteFlow chunked_flow;
- ByteFlowSink sink;
+ td::ByteFlowSource source(&input);
+ td::HttpChunkedByteFlow chunked_flow;
+ td::ByteFlowSink sink;
source >> chunked_flow >> sink;
for (auto &part : parts) {
input_writer.append(part);
source.wakeup();
}
- source.close_input(Status::OK());
+ source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@@ -326,16 +373,16 @@ TEST(Http, chunked_flow) {
}
TEST(Http, chunked_flow_error) {
- auto str = rand_string('a', 'z', 100000);
+ auto str = td::rand_string('a', 'z', 100000);
for (int d = 1; d < 100; d += 10) {
auto new_str = make_chunked(str);
new_str.resize(str.size() - d);
- auto parts = rand_split(new_str);
- auto input_writer = ChainBufferWriter::create_empty();
+ auto parts = td::rand_split(new_str);
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- ByteFlowSource source(&input);
- HttpChunkedByteFlow chunked_flow;
- ByteFlowSink sink;
+ td::ByteFlowSource source(&input);
+ td::HttpChunkedByteFlow chunked_flow;
+ td::ByteFlowSink sink;
source >> chunked_flow >> sink;
for (auto &part : parts) {
@@ -343,31 +390,106 @@ TEST(Http, chunked_flow_error) {
source.wakeup();
}
ASSERT_TRUE(!sink.is_ready());
- source.close_input(Status::OK());
+ source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
ASSERT_TRUE(!sink.status().is_ok());
}
}
TEST(Http, gzip_chunked_flow) {
- auto str = rand_string('a', 'z', 1000000);
- auto parts = rand_split(make_chunked(gzencode(str).as_slice().str()));
+ auto str = td::rand_string('a', 'z', 1000000);
+ auto parts = td::rand_split(make_chunked(td::gzencode(str, 2.0).as_slice().str()));
- auto input_writer = ChainBufferWriter::create_empty();
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- ByteFlowSource source(&input);
- HttpChunkedByteFlow chunked_flow;
- GzipByteFlow gzip_flow(Gzip::Decode);
- ByteFlowSink sink;
+ td::ByteFlowSource source(&input);
+ td::HttpChunkedByteFlow chunked_flow;
+ td::GzipByteFlow gzip_flow(td::Gzip::Mode::Decode);
+ td::ByteFlowSink sink;
source >> chunked_flow >> gzip_flow >> sink;
for (auto &part : parts) {
input_writer.append(part);
source.wakeup();
}
- source.close_input(Status::OK());
+ source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
ASSERT_EQ(str, sink.result()->move_as_buffer_slice().as_slice().str());
}
+
+TEST(Http, gzip_bomb_with_limit) {
+ td::string gzip_bomb_str;
+ {
+ td::ChainBufferWriter input_writer;
+ auto input = input_writer.extract_reader();
+ td::GzipByteFlow gzip_flow(td::Gzip::Mode::Encode);
+ td::ByteFlowSource source(&input);
+ td::ByteFlowSink sink;
+ source >> gzip_flow >> sink;
+
+ td::string s(1 << 16, 'a');
+ for (int i = 0; i < 1000; i++) {
+ input_writer.append(s);
+ source.wakeup();
+ }
+ source.close_input(td::Status::OK());
+ ASSERT_TRUE(sink.is_ready());
+ LOG_IF(ERROR, sink.status().is_error()) << sink.status();
+ ASSERT_TRUE(sink.status().is_ok());
+ gzip_bomb_str = sink.result()->move_as_buffer_slice().as_slice().str();
+ }
+
+ auto query = make_http_query("", false, false, true, 0.01, gzip_bomb_str);
+ auto parts = td::rand_split(query);
+ td::ChainBufferWriter input_writer;
+ auto input = input_writer.extract_reader();
+ td::HttpReader reader;
+ td::HttpQuery q;
+ reader.init(&input, 1000000);
+ bool ok = false;
+ for (auto &part : parts) {
+ input_writer.append(part);
+ input.sync_with_writer();
+ auto r_state = reader.read_next(&q);
+ if (r_state.is_error()) {
+ LOG(FATAL) << r_state.error();
+ return;
+ } else if (r_state.ok() == 0) {
+ ok = true;
+ }
+ }
+ ASSERT_TRUE(ok);
+}
+
+#if TD_DARWIN_WATCH_OS
+struct Baton {
+ std::mutex mutex;
+ std::condition_variable cond;
+ bool is_ready{false};
+
+ void wait() {
+ std::unique_lock<std::mutex> lock(mutex);
+ cond.wait(lock, [&] { return is_ready; });
+ }
+
+ void post() {
+ {
+ std::unique_lock<std::mutex> lock(mutex);
+ is_ready = true;
+ }
+ cond.notify_all();
+ }
+
+ void reset() {
+ is_ready = false;
+ }
+};
+
+TEST(Http, Darwin) {
+ Baton baton;
+ td::DarwinHttp::get("http://example.com", [&](td::BufferSlice data) { baton.post(); });
+ baton.wait();
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/test/link.cpp b/protocols/Telegram/tdlib/td/test/link.cpp
new file mode 100644
index 0000000000..13c4613174
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/link.cpp
@@ -0,0 +1,983 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/LinkManager.h"
+
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/tests.h"
+
+static void check_find_urls(const td::string &url, bool is_valid) {
+ auto url_lower = td::to_lower(url);
+ {
+ auto tg_urls = td::find_tg_urls(url);
+ if (is_valid && (td::begins_with(url_lower, "tg://") || td::begins_with(url_lower, "ton://"))) {
+ ASSERT_EQ(1u, tg_urls.size());
+ ASSERT_STREQ(url, tg_urls[0]);
+ } else {
+ ASSERT_TRUE(tg_urls.empty() || tg_urls[0] != url);
+ }
+ }
+
+ {
+ if (is_valid && (td::begins_with(url_lower, "http") || td::begins_with(url_lower, "t.me")) &&
+ url.find('.') != td::string::npos && url.find(' ') == td::string::npos && url != "http://.." &&
+ url.find("ra.ph") == td::string::npos && url.find("Aph") == td::string::npos) {
+ auto urls = td::find_urls(url);
+ ASSERT_EQ(1u, urls.size());
+ ASSERT_STREQ(url, urls[0].first);
+ }
+ }
+}
+
+static void check_link(const td::string &url, const td::string &expected) {
+ auto result = td::LinkManager::check_link(url);
+ if (result.is_ok()) {
+ ASSERT_STREQ(expected, result.ok());
+ } else {
+ ASSERT_TRUE(expected.empty());
+ }
+
+ check_find_urls(url, result.is_ok());
+}
+
+TEST(Link, check_link) {
+ check_link("sftp://google.com", "");
+ check_link("tg://google_com", "tg://google_com/");
+ check_link("tOn://google", "ton://google/");
+ check_link("httP://google.com?1#tes", "http://google.com/?1#tes");
+ check_link("httPs://google.com/?1#tes", "https://google.com/?1#tes");
+ check_link("http://google.com:0", "");
+ check_link("http://google.com:0000000001", "http://google.com:1/");
+ check_link("http://google.com:-1", "");
+ check_link("tg://google?1#tes", "tg://google?1#tes");
+ check_link("tg://google/?1#tes", "tg://google?1#tes");
+ check_link("TG:_", "tg://_/");
+ check_link("sftp://google.com", "");
+ check_link("sftp://google.com", "");
+ check_link("http:google.com", "");
+ check_link("tg://http://google.com", "");
+ check_link("tg:http://google.com", "");
+ check_link("tg:https://google.com", "");
+ check_link("tg:test@google.com", "");
+ check_link("tg:google.com:80", "");
+ check_link("tg:google-com", "tg://google-com/");
+ check_link("tg:google.com", "");
+ check_link("tg:google.com:0", "");
+ check_link("tg:google.com:a", "");
+ check_link("tg:[2001:db8:0:0:0:ff00:42:8329]", "");
+ check_link("tg:127.0.0.1", "");
+ check_link("http://[2001:db8:0:0:0:ff00:42:8329]", "http://[2001:db8:0:0:0:ff00:42:8329]/");
+ check_link("http://localhost", "");
+ check_link("http://..", "http://../");
+ check_link("..", "http://../");
+ check_link("https://.", "");
+}
+
+static void parse_internal_link(const td::string &url, td::td_api::object_ptr<td::td_api::InternalLinkType> expected) {
+ auto result = td::LinkManager::parse_internal_link(url);
+ if (result != nullptr) {
+ auto object = result->get_internal_link_type_object();
+ if (object->get_id() == td::td_api::internalLinkTypeMessageDraft::ID) {
+ static_cast<td::td_api::internalLinkTypeMessageDraft *>(object.get())->text_->entities_.clear();
+ }
+ ASSERT_STREQ(url + ' ' + to_string(expected), url + ' ' + to_string(object));
+ } else {
+ LOG_IF(ERROR, expected != nullptr) << url;
+ ASSERT_TRUE(expected == nullptr);
+ }
+
+ check_find_urls(url, result != nullptr);
+}
+
+TEST(Link, parse_internal_link) {
+ auto chat_administrator_rights = [](bool can_manage_chat, bool can_change_info, bool can_post_messages,
+ bool can_edit_messages, bool can_delete_messages, bool can_invite_users,
+ bool can_restrict_members, bool can_pin_messages, bool can_manage_topics,
+ bool can_promote_members, bool can_manage_video_chats, bool is_anonymous) {
+ return td::td_api::make_object<td::td_api::chatAdministratorRights>(
+ can_manage_chat, can_change_info, can_post_messages, can_edit_messages, can_delete_messages, can_invite_users,
+ can_restrict_members, can_pin_messages, can_manage_topics, can_promote_members, can_manage_video_chats,
+ is_anonymous);
+ };
+ auto target_chat_chosen = [](bool allow_users, bool allow_bots, bool allow_groups, bool allow_channels) {
+ return td::td_api::make_object<td::td_api::targetChatChosen>(allow_users, allow_bots, allow_groups, allow_channels);
+ };
+
+ auto active_sessions = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeActiveSessions>();
+ };
+ auto attachment_menu_bot = [](td::td_api::object_ptr<td::td_api::targetChatChosen> chat_types,
+ td::td_api::object_ptr<td::td_api::InternalLinkType> chat_link,
+ const td::string &bot_username, const td::string &start_parameter) {
+ td::td_api::object_ptr<td::td_api::TargetChat> target_chat;
+ if (chat_link != nullptr) {
+ target_chat = td::td_api::make_object<td::td_api::targetChatInternalLink>(std::move(chat_link));
+ } else if (chat_types != nullptr) {
+ target_chat = std::move(chat_types);
+ } else {
+ target_chat = td::td_api::make_object<td::td_api::targetChatCurrent>();
+ }
+ return td::td_api::make_object<td::td_api::internalLinkTypeAttachmentMenuBot>(
+ std::move(target_chat), bot_username, start_parameter.empty() ? td::string() : "start://" + start_parameter);
+ };
+ auto authentication_code = [](const td::string &code) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeAuthenticationCode>(code);
+ };
+ auto background = [](const td::string &background_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeBackground>(background_name);
+ };
+ auto bot_add_to_channel = [](const td::string &bot_username,
+ td::td_api::object_ptr<td::td_api::chatAdministratorRights> &&administrator_rights) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeBotAddToChannel>(bot_username,
+ std::move(administrator_rights));
+ };
+ auto bot_start = [](const td::string &bot_username, const td::string &start_parameter) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeBotStart>(bot_username, start_parameter, false);
+ };
+ auto bot_start_in_group = [](const td::string &bot_username, const td::string &start_parameter,
+ td::td_api::object_ptr<td::td_api::chatAdministratorRights> &&administrator_rights) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeBotStartInGroup>(bot_username, start_parameter,
+ std::move(administrator_rights));
+ };
+ auto change_phone_number = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeChangePhoneNumber>();
+ };
+ auto chat_invite = [](const td::string &hash) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeChatInvite>("tg:join?invite=" + hash);
+ };
+ auto filter_settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeFilterSettings>();
+ };
+ auto game = [](const td::string &bot_username, const td::string &game_short_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeGame>(bot_username, game_short_name);
+ };
+ auto instant_view = [](const td::string &url, const td::string &fallback_url) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeInstantView>(url, fallback_url);
+ };
+ auto invoice = [](const td::string &invoice_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeInvoice>(invoice_name);
+ };
+ auto language_pack = [](const td::string &language_pack_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeLanguagePack>(language_pack_name);
+ };
+ auto language_settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeLanguageSettings>();
+ };
+ auto message = [](const td::string &url) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeMessage>(url);
+ };
+ auto message_draft = [](td::string text, bool contains_url) {
+ auto formatted_text = td::td_api::make_object<td::td_api::formattedText>();
+ formatted_text->text_ = std::move(text);
+ return td::td_api::make_object<td::td_api::internalLinkTypeMessageDraft>(std::move(formatted_text), contains_url);
+ };
+ auto passport_data_request = [](td::int32 bot_user_id, const td::string &scope, const td::string &public_key,
+ const td::string &nonce, const td::string &callback_url) {
+ return td::td_api::make_object<td::td_api::internalLinkTypePassportDataRequest>(bot_user_id, scope, public_key,
+ nonce, callback_url);
+ };
+ auto phone_number_confirmation = [](const td::string &hash, const td::string &phone_number) {
+ return td::td_api::make_object<td::td_api::internalLinkTypePhoneNumberConfirmation>(hash, phone_number);
+ };
+ auto premium_features = [](const td::string &referrer) {
+ return td::td_api::make_object<td::td_api::internalLinkTypePremiumFeatures>(referrer);
+ };
+ auto privacy_and_security_settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypePrivacyAndSecuritySettings>();
+ };
+ auto proxy_mtproto = [](const td::string &server, td::int32 port, const td::string &secret) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeProxy>(
+ server, port, td::td_api::make_object<td::td_api::proxyTypeMtproto>(secret));
+ };
+ auto proxy_socks = [](const td::string &server, td::int32 port, const td::string &username,
+ const td::string &password) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeProxy>(
+ server, port, td::td_api::make_object<td::td_api::proxyTypeSocks5>(username, password));
+ };
+ auto public_chat = [](const td::string &chat_username) {
+ return td::td_api::make_object<td::td_api::internalLinkTypePublicChat>(chat_username);
+ };
+ auto qr_code_authentication = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeQrCodeAuthentication>();
+ };
+ auto restore_purchases = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeRestorePurchases>();
+ };
+ auto settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeSettings>();
+ };
+ auto sticker_set = [](const td::string &sticker_set_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeStickerSet>(sticker_set_name);
+ };
+ auto theme = [](const td::string &theme_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeTheme>(theme_name);
+ };
+ auto theme_settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeThemeSettings>();
+ };
+ auto unknown_deep_link = [](const td::string &link) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeUnknownDeepLink>(link);
+ };
+ auto unsupported_proxy = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeUnsupportedProxy>();
+ };
+ auto user_phone_number = [](const td::string &phone_number) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeUserPhoneNumber>(phone_number);
+ };
+ auto video_chat = [](const td::string &chat_username, const td::string &invite_hash, bool is_live_stream) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeVideoChat>(chat_username, invite_hash, is_live_stream);
+ };
+
+ parse_internal_link("t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("telegram.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("telegram.dog/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("www.t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("www%2etelegram.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("www%2Etelegram.dog/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("www%252Etelegram.dog/levlam/1", nullptr);
+ parse_internal_link("www.t.me/s/s/s/s/s/joinchat/1", chat_invite("1"));
+ parse_internal_link("www.t.me/s/%73/%73/s/%73/joinchat/1", chat_invite("1"));
+ parse_internal_link("http://t.me/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/joinchat/1", chat_invite("1"));
+ parse_internal_link("http://t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("https://t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("hTtp://www.t.me:443/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("httPs://t.me:80/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("https://t.me:200/levlam/1", nullptr);
+ parse_internal_link("http:t.me/levlam/1", nullptr);
+ parse_internal_link("t.dog/levlam/1", nullptr);
+ parse_internal_link("t.m/levlam/1", nullptr);
+ parse_internal_link("t.men/levlam/1", nullptr);
+
+ parse_internal_link("tg:resolve?domain=username&post=12345&single",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("tg:resolve?domain=username&post=12345&single&startattach=1&attach=test",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("tg:resolve?domain=user%31name&post=%312345&single&comment=456&t=789&single&thread=123%20%31",
+ message("tg:resolve?domain=user1name&post=12345&single&thread=123%201&comment=456&t=789"));
+ parse_internal_link("TG://resolve?domain=username&post=12345&single&voicechat=aasd",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("TG://test@resolve?domain=username&post=12345&single", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&post=12345&single", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&post=12345&single", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&post=12345&single", nullptr);
+ parse_internal_link("tg:resolve?domain=&post=12345&single",
+ unknown_deep_link("tg://resolve?domain=&post=12345&single"));
+ parse_internal_link("tg:resolve?domain=telegram&post=&single", public_chat("telegram"));
+ parse_internal_link("tg:resolve?domain=123456&post=&single",
+ unknown_deep_link("tg://resolve?domain=123456&post=&single"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach", attachment_menu_bot(nullptr, nullptr, "telegram", ""));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1",
+ attachment_menu_bot(nullptr, nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=cats+dogs",
+ attachment_menu_bot(nullptr, nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=users",
+ attachment_menu_bot(target_chat_chosen(true, false, false, false), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=bots",
+ attachment_menu_bot(target_chat_chosen(false, true, false, false), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=groups",
+ attachment_menu_bot(target_chat_chosen(false, false, true, false), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=channels",
+ attachment_menu_bot(target_chat_chosen(false, false, false, true), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=users+channels",
+ attachment_menu_bot(target_chat_chosen(true, false, false, true), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&attach=&startattach",
+ attachment_menu_bot(nullptr, nullptr, "telegram", ""));
+ parse_internal_link("tg:resolve?domain=telegram&attach=&startattach=1",
+ attachment_menu_bot(nullptr, nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&attach=test&startattach",
+ attachment_menu_bot(nullptr, public_chat("telegram"), "test", ""));
+ parse_internal_link("tg:resolve?domain=telegram&attach=test&startattach=1",
+ attachment_menu_bot(nullptr, public_chat("telegram"), "test", "1"));
+
+ parse_internal_link("tg:resolve?phone=1", user_phone_number("1"));
+ parse_internal_link("tg:resolve?phone=123456", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&startattach", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&startattach=123", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&attach=", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&attach=&startattach", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&attach=&startattach=123", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&attach=test",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "test", ""));
+ parse_internal_link("tg:resolve?phone=123456&attach=test&startattach&choose=users",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "test", ""));
+ parse_internal_link("tg:resolve?phone=123456&attach=test&startattach=123",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "test", "123"));
+ parse_internal_link("tg:resolve?phone=01234567890123456789012345678912",
+ user_phone_number("01234567890123456789012345678912"));
+ parse_internal_link("tg:resolve?phone=012345678901234567890123456789123",
+ unknown_deep_link("tg://resolve?phone=012345678901234567890123456789123"));
+ parse_internal_link("tg:resolve?phone=", unknown_deep_link("tg://resolve?phone="));
+ parse_internal_link("tg:resolve?phone=+123", unknown_deep_link("tg://resolve?phone=+123"));
+ parse_internal_link("tg:resolve?phone=123456 ", unknown_deep_link("tg://resolve?phone=123456 "));
+
+ parse_internal_link("t.me/username/12345?single", message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("t.me/username/12345?asdasd", message("tg:resolve?domain=username&post=12345"));
+ parse_internal_link("t.me/username/12345", message("tg:resolve?domain=username&post=12345"));
+ parse_internal_link("t.me/username/12345/", message("tg:resolve?domain=username&post=12345"));
+ parse_internal_link("t.me/username/12345#asdasd", message("tg:resolve?domain=username&post=12345"));
+ parse_internal_link("t.me/username/12345//?voicechat=&single",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("t.me/username/12345/asdasd//asd/asd/asd/?single",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("t.me/username/12345/67890/asdasd//asd/asd/asd/?single",
+ message("tg:resolve?domain=username&post=67890&single&thread=12345"));
+ parse_internal_link("t.me/username/1asdasdas/asdasd//asd/asd/asd/?single",
+ message("tg:resolve?domain=username&post=1&single"));
+ parse_internal_link("t.me/username/asd", public_chat("username"));
+ parse_internal_link("t.me/username/0", public_chat("username"));
+ parse_internal_link("t.me/username/-12345", public_chat("username"));
+ parse_internal_link("t.me//12345?single", nullptr);
+ parse_internal_link("https://telegram.dog/telegram/?single", public_chat("telegram"));
+ parse_internal_link("t.me/username?startattach", attachment_menu_bot(nullptr, nullptr, "username", ""));
+ parse_internal_link("t.me/username?startattach=1", attachment_menu_bot(nullptr, nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=cats+dogs",
+ attachment_menu_bot(nullptr, nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=users",
+ attachment_menu_bot(target_chat_chosen(true, false, false, false), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=bots",
+ attachment_menu_bot(target_chat_chosen(false, true, false, false), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=groups",
+ attachment_menu_bot(target_chat_chosen(false, false, true, false), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=channels",
+ attachment_menu_bot(target_chat_chosen(false, false, false, true), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=bots+groups",
+ attachment_menu_bot(target_chat_chosen(false, true, true, false), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?attach=", public_chat("username"));
+ parse_internal_link("t.me/username?attach=&startattach", attachment_menu_bot(nullptr, nullptr, "username", ""));
+ parse_internal_link("t.me/username?attach=&startattach=1", attachment_menu_bot(nullptr, nullptr, "username", "1"));
+ parse_internal_link("t.me/username?attach=bot", attachment_menu_bot(nullptr, public_chat("username"), "bot", ""));
+ parse_internal_link("t.me/username?attach=bot&startattach",
+ attachment_menu_bot(nullptr, public_chat("username"), "bot", ""));
+ parse_internal_link("t.me/username?attach=bot&startattach=1&choose=users",
+ attachment_menu_bot(nullptr, public_chat("username"), "bot", "1"));
+
+ parse_internal_link("tg:privatepost?domain=username/12345&single",
+ unknown_deep_link("tg://privatepost?domain=username/12345&single"));
+ parse_internal_link("tg:privatepost?channel=username/12345&single",
+ unknown_deep_link("tg://privatepost?channel=username/12345&single"));
+ parse_internal_link("tg:privatepost?channel=username&post=12345",
+ message("tg:privatepost?channel=username&post=12345"));
+
+ parse_internal_link("t.me/c/12345?single", nullptr);
+ parse_internal_link("t.me/c/1/c?single", nullptr);
+ parse_internal_link("t.me/c/c/1?single", nullptr);
+ parse_internal_link("t.me/c//1?single", nullptr);
+ parse_internal_link("t.me/c/12345/123", message("tg:privatepost?channel=12345&post=123"));
+ parse_internal_link("t.me/c/12345/123?single", message("tg:privatepost?channel=12345&post=123&single"));
+ parse_internal_link("t.me/c/12345/123/asd/asd////?single", message("tg:privatepost?channel=12345&post=123&single"));
+ parse_internal_link("t.me/c/12345/123/456/asd/asd////?single",
+ message("tg:privatepost?channel=12345&post=456&single&thread=123"));
+ parse_internal_link("t.me/c/%312345/%3123?comment=456&t=789&single&thread=123%20%31",
+ message("tg:privatepost?channel=12345&post=123&single&thread=123%201&comment=456&t=789"));
+
+ parse_internal_link("tg:bg?color=111111#asdasd", background("111111"));
+ parse_internal_link("tg:bg?color=11111%31", background("111111"));
+ parse_internal_link("tg:bg?color=11111%20", background("11111%20"));
+ parse_internal_link("tg:bg?gradient=111111-222222", background("111111-222222"));
+ parse_internal_link("tg:bg?rotation=180%20&gradient=111111-222222%20",
+ background("111111-222222%20?rotation=180%20"));
+ parse_internal_link("tg:bg?gradient=111111~222222", background("111111~222222"));
+ parse_internal_link("tg:bg?gradient=abacaba", background("abacaba"));
+ parse_internal_link("tg:bg?slug=111111~222222#asdasd", background("111111~222222"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12&text=1", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12&mode=1", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=test&mode=12&rotation=4&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3&rotation=4"));
+ parse_internal_link("tg:bg?mode=12&&slug=test&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3"));
+ parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3",
+ unknown_deep_link("tg://bg?mode=12&intensity=2&bg_color=3"));
+
+ parse_internal_link("tg:bg?color=111111#asdasd", background("111111"));
+ parse_internal_link("tg:bg?color=11111%31", background("111111"));
+ parse_internal_link("tg:bg?color=11111%20", background("11111%20"));
+ parse_internal_link("tg:bg?gradient=111111-222222", background("111111-222222"));
+ parse_internal_link("tg:bg?rotation=180%20&gradient=111111-222222%20",
+ background("111111-222222%20?rotation=180%20"));
+ parse_internal_link("tg:bg?gradient=111111~222222", background("111111~222222"));
+ parse_internal_link("tg:bg?gradient=abacaba", background("abacaba"));
+ parse_internal_link("tg:bg?slug=111111~222222#asdasd", background("111111~222222"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12&text=1", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12&mode=1", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=test&mode=12&rotation=4&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3&rotation=4"));
+ parse_internal_link("tg:bg?mode=12&&slug=test&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3"));
+ parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3",
+ unknown_deep_link("tg://bg?mode=12&intensity=2&bg_color=3"));
+
+ parse_internal_link("%54.me/bg/111111#asdasd", background("111111"));
+ parse_internal_link("t.me/bg/11111%31", background("111111"));
+ parse_internal_link("t.me/bg/11111%20", background("11111%20"));
+ parse_internal_link("t.me/bg/111111-222222", background("111111-222222"));
+ parse_internal_link("t.me/bg/111111-222222%20?rotation=180%20", background("111111-222222%20?rotation=180%20"));
+ parse_internal_link("t.me/bg/111111~222222", background("111111~222222"));
+ parse_internal_link("t.me/bg/abacaba", background("abacaba"));
+ parse_internal_link("t.me/Bg/abacaba", public_chat("Bg"));
+ parse_internal_link("t.me/bg/111111~222222#asdasd", background("111111~222222"));
+ parse_internal_link("t.me/bg/111111~222222?mode=12", background("111111~222222?mode=12"));
+ parse_internal_link("t.me/bg/111111~222222?mode=12&text=1", background("111111~222222?mode=12"));
+ parse_internal_link("t.me/bg/111111~222222?mode=12&mode=1", background("111111~222222?mode=12"));
+ parse_internal_link("t.me/bg/test?mode=12&rotation=4&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3&rotation=4"));
+ parse_internal_link("t.me/%62g/test/?mode=12&&&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3"));
+ parse_internal_link("t.me/bg//", nullptr);
+ parse_internal_link("t.me/bg/%20/", background("%20"));
+ parse_internal_link("t.me/bg/", nullptr);
+
+ parse_internal_link("t.me/invoice?slug=abcdef", nullptr);
+ parse_internal_link("t.me/invoice", nullptr);
+ parse_internal_link("t.me/invoice/", nullptr);
+ parse_internal_link("t.me/invoice//abcdef", nullptr);
+ parse_internal_link("t.me/invoice?/abcdef", nullptr);
+ parse_internal_link("t.me/invoice/?abcdef", nullptr);
+ parse_internal_link("t.me/invoice/#abcdef", nullptr);
+ parse_internal_link("t.me/invoice/abacaba", invoice("abacaba"));
+ parse_internal_link("t.me/invoice/aba%20aba", invoice("aba aba"));
+ parse_internal_link("t.me/invoice/123456a", invoice("123456a"));
+ parse_internal_link("t.me/invoice/12345678901", invoice("12345678901"));
+ parse_internal_link("t.me/invoice/123456", invoice("123456"));
+ parse_internal_link("t.me/invoice/123456/123123/12/31/a/s//21w/?asdas#test", invoice("123456"));
+
+ parse_internal_link("t.me/$?slug=abcdef", nullptr);
+ parse_internal_link("t.me/$", nullptr);
+ parse_internal_link("t.me/$/abcdef", nullptr);
+ parse_internal_link("t.me/$?/abcdef", nullptr);
+ parse_internal_link("t.me/$?abcdef", nullptr);
+ parse_internal_link("t.me/$#abcdef", nullptr);
+ parse_internal_link("t.me/$abacaba", invoice("abacaba"));
+ parse_internal_link("t.me/$aba%20aba", invoice("aba aba"));
+ parse_internal_link("t.me/$123456a", invoice("123456a"));
+ parse_internal_link("t.me/$12345678901", invoice("12345678901"));
+ parse_internal_link("t.me/$123456", invoice("123456"));
+ parse_internal_link("t.me/%24123456", invoice("123456"));
+ parse_internal_link("t.me/$123456/123123/12/31/a/s//21w/?asdas#test", invoice("123456"));
+
+ parse_internal_link("tg:invoice?slug=abcdef", invoice("abcdef"));
+ parse_internal_link("tg:invoice?slug=abc%30ef", invoice("abc0ef"));
+ parse_internal_link("tg://invoice?slug=", unknown_deep_link("tg://invoice?slug="));
+
+ parse_internal_link("tg:share?url=google.com&text=text#asdasd", message_draft("google.com\ntext", true));
+ parse_internal_link("tg:share?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("tg:share?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("tg:msg_url?url=google.com&text=text", message_draft("google.com\ntext", true));
+ parse_internal_link("tg:msg_url?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("tg:msg_url?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("tg:msg?url=google.com&text=text", message_draft("google.com\ntext", true));
+ parse_internal_link("tg:msg?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("tg:msg?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("tg:msg?url=&text=\n\n\n\n\n\n\n\n", nullptr);
+ parse_internal_link("tg:msg?url=%20\n&text=", nullptr);
+ parse_internal_link("tg:msg?url=%20\n&text=google.com", message_draft("google.com", false));
+ parse_internal_link("tg:msg?url=@&text=", message_draft(" @", false));
+ parse_internal_link("tg:msg?url=&text=@", message_draft(" @", false));
+ parse_internal_link("tg:msg?url=@&text=@", message_draft(" @\n@", true));
+ parse_internal_link("tg:msg?url=%FF&text=1", nullptr);
+
+ parse_internal_link("https://t.me/share?url=google.com&text=text#asdasd", message_draft("google.com\ntext", true));
+ parse_internal_link("https://t.me/share?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("https://t.me/share?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=google.com&text=text", message_draft("google.com\ntext", true));
+ parse_internal_link("https://t.me/msg?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=google.com&text=text", message_draft("google.com\ntext", true));
+ parse_internal_link("https://t.me/msg?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=&text=\n\n\n\n\n\n\n\n", nullptr);
+ parse_internal_link("https://t.me/msg?url=%20%0A&text=", nullptr);
+ parse_internal_link("https://t.me/msg?url=%20%0A&text=google.com", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=@&text=", message_draft(" @", false));
+ parse_internal_link("https://t.me/msg?url=&text=@", message_draft(" @", false));
+ parse_internal_link("https://t.me/msg?url=@&text=@", message_draft(" @\n@", true));
+ parse_internal_link("https://t.me/msg?url=%FF&text=1", nullptr);
+
+ parse_internal_link("tg:login?codec=12345", unknown_deep_link("tg://login?codec=12345"));
+ parse_internal_link("tg:login", unknown_deep_link("tg://login"));
+ parse_internal_link("tg:login?code=abacaba", authentication_code("abacaba"));
+ parse_internal_link("tg:login?code=123456", authentication_code("123456"));
+
+ parse_internal_link("t.me/login?codec=12345", nullptr);
+ parse_internal_link("t.me/login", nullptr);
+ parse_internal_link("t.me/login/", nullptr);
+ parse_internal_link("t.me/login//12345", nullptr);
+ parse_internal_link("t.me/login?/12345", nullptr);
+ parse_internal_link("t.me/login/?12345", nullptr);
+ parse_internal_link("t.me/login/#12345", nullptr);
+ parse_internal_link("t.me/login/abacaba", authentication_code("abacaba"));
+ parse_internal_link("t.me/login/aba%20aba", authentication_code("aba aba"));
+ parse_internal_link("t.me/login/123456a", authentication_code("123456a"));
+ parse_internal_link("t.me/login/12345678901", authentication_code("12345678901"));
+ parse_internal_link("t.me/login/123456", authentication_code("123456"));
+ parse_internal_link("t.me/login/123456/123123/12/31/a/s//21w/?asdas#test", authentication_code("123456"));
+
+ parse_internal_link("tg:login?token=abacaba", qr_code_authentication());
+ parse_internal_link("tg:login?token=", unknown_deep_link("tg://login?token="));
+
+ parse_internal_link("tg:restore_purchases?token=abacaba", restore_purchases());
+ parse_internal_link("tg:restore_purchases?#", restore_purchases());
+ parse_internal_link("tg:restore_purchases/?#", restore_purchases());
+ parse_internal_link("tg:restore_purchases", restore_purchases());
+ parse_internal_link("tg:restore_purchase", unknown_deep_link("tg://restore_purchase"));
+ parse_internal_link("tg:restore_purchasess", unknown_deep_link("tg://restore_purchasess"));
+ parse_internal_link("tg:restore_purchases/test?#", unknown_deep_link("tg://restore_purchases/test?"));
+
+ parse_internal_link("t.me/joinchat?invite=abcdef", nullptr);
+ parse_internal_link("t.me/joinchat", nullptr);
+ parse_internal_link("t.me/joinchat/", nullptr);
+ parse_internal_link("t.me/joinchat//abcdef", nullptr);
+ parse_internal_link("t.me/joinchat?/abcdef", nullptr);
+ parse_internal_link("t.me/joinchat/?abcdef", nullptr);
+ parse_internal_link("t.me/joinchat/#abcdef", nullptr);
+ parse_internal_link("t.me/joinchat/abacaba", chat_invite("abacaba"));
+ parse_internal_link("t.me/joinchat/aba%20aba", chat_invite("aba%20aba"));
+ parse_internal_link("t.me/joinchat/aba%30aba", chat_invite("aba0aba"));
+ parse_internal_link("t.me/joinchat/123456a", chat_invite("123456a"));
+ parse_internal_link("t.me/joinchat/12345678901", chat_invite("12345678901"));
+ parse_internal_link("t.me/joinchat/123456", chat_invite("123456"));
+ parse_internal_link("t.me/joinchat/123456/123123/12/31/a/s//21w/?asdas#test", chat_invite("123456"));
+
+ parse_internal_link("t.me/+?invite=abcdef", nullptr);
+ parse_internal_link("t.me/+a", chat_invite("a"));
+ parse_internal_link("t.me/+", nullptr);
+ parse_internal_link("t.me/+/abcdef", nullptr);
+ parse_internal_link("t.me/ ?/abcdef", nullptr);
+ parse_internal_link("t.me/+?abcdef", nullptr);
+ parse_internal_link("t.me/+#abcdef", nullptr);
+ parse_internal_link("t.me/ abacaba", chat_invite("abacaba"));
+ parse_internal_link("t.me/+aba%20aba", chat_invite("aba%20aba"));
+ parse_internal_link("t.me/+aba%30aba", chat_invite("aba0aba"));
+ parse_internal_link("t.me/+123456a", chat_invite("123456a"));
+ parse_internal_link("t.me/%2012345678901", user_phone_number("12345678901"));
+ parse_internal_link("t.me/+123456", user_phone_number("123456"));
+ parse_internal_link("t.me/ 123456/123123/12/31/a/s//21w/?asdas#test", user_phone_number("123456"));
+ parse_internal_link("t.me/ /123456/123123/12/31/a/s//21w/?asdas#test", nullptr);
+ parse_internal_link("t.me/+123456?startattach", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?startattach=1", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?attach=", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?attach=&startattach", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?attach=&startattach=1", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?attach=bot", attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", ""));
+ parse_internal_link("t.me/+123456?attach=bot&startattach",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", ""));
+ parse_internal_link("t.me/+123456?attach=bot&startattach=1",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", "1"));
+
+ parse_internal_link("tg:join?invite=abcdef", chat_invite("abcdef"));
+ parse_internal_link("tg:join?invite=abc%20def", chat_invite("abc%20def"));
+ parse_internal_link("tg://join?invite=abc%30def", chat_invite("abc0def"));
+ parse_internal_link("tg:join?invite=", unknown_deep_link("tg://join?invite="));
+
+ parse_internal_link("t.me/addstickers?set=abcdef", nullptr);
+ parse_internal_link("t.me/addstickers", nullptr);
+ parse_internal_link("t.me/addstickers/", nullptr);
+ parse_internal_link("t.me/addstickers//abcdef", nullptr);
+ parse_internal_link("t.me/addstickers?/abcdef", nullptr);
+ parse_internal_link("t.me/addstickers/?abcdef", nullptr);
+ parse_internal_link("t.me/addstickers/#abcdef", nullptr);
+ parse_internal_link("t.me/addstickers/abacaba", sticker_set("abacaba"));
+ parse_internal_link("t.me/addstickers/aba%20aba", sticker_set("aba aba"));
+ parse_internal_link("t.me/addstickers/123456a", sticker_set("123456a"));
+ parse_internal_link("t.me/addstickers/12345678901", sticker_set("12345678901"));
+ parse_internal_link("t.me/addstickers/123456", sticker_set("123456"));
+ parse_internal_link("t.me/addstickers/123456/123123/12/31/a/s//21w/?asdas#test", sticker_set("123456"));
+
+ parse_internal_link("tg:addstickers?set=abcdef", sticker_set("abcdef"));
+ parse_internal_link("tg:addstickers?set=abc%30ef", sticker_set("abc0ef"));
+ parse_internal_link("tg://addstickers?set=", unknown_deep_link("tg://addstickers?set="));
+
+ parse_internal_link("t.me/addemoji?set=abcdef", nullptr);
+ parse_internal_link("t.me/addemoji", nullptr);
+ parse_internal_link("t.me/addemoji/", nullptr);
+ parse_internal_link("t.me/addemoji//abcdef", nullptr);
+ parse_internal_link("t.me/addemoji?/abcdef", nullptr);
+ parse_internal_link("t.me/addemoji/?abcdef", nullptr);
+ parse_internal_link("t.me/addemoji/#abcdef", nullptr);
+ parse_internal_link("t.me/addemoji/abacaba", sticker_set("abacaba"));
+ parse_internal_link("t.me/addemoji/aba%20aba", sticker_set("aba aba"));
+ parse_internal_link("t.me/addemoji/123456a", sticker_set("123456a"));
+ parse_internal_link("t.me/addemoji/12345678901", sticker_set("12345678901"));
+ parse_internal_link("t.me/addemoji/123456", sticker_set("123456"));
+ parse_internal_link("t.me/addemoji/123456/123123/12/31/a/s//21w/?asdas#test", sticker_set("123456"));
+
+ parse_internal_link("tg:addemoji?set=abcdef", sticker_set("abcdef"));
+ parse_internal_link("tg:addemoji?set=abc%30ef", sticker_set("abc0ef"));
+ parse_internal_link("tg://addemoji?set=", unknown_deep_link("tg://addemoji?set="));
+
+ parse_internal_link("t.me/confirmphone?hash=abc%30ef&phone=", nullptr);
+ parse_internal_link("t.me/confirmphone/123456/123123/12/31/a/s//21w/?hash=abc%30ef&phone=123456789",
+ phone_number_confirmation("abc0ef", "123456789"));
+ parse_internal_link("t.me/confirmphone?hash=abc%30ef&phone=123456789",
+ phone_number_confirmation("abc0ef", "123456789"));
+
+ parse_internal_link("tg:confirmphone?hash=abc%30ef&phone=",
+ unknown_deep_link("tg://confirmphone?hash=abc%30ef&phone="));
+ parse_internal_link("tg:confirmphone?hash=abc%30ef&phone=123456789",
+ phone_number_confirmation("abc0ef", "123456789"));
+ parse_internal_link("tg://confirmphone?hash=123&phone=123456789123456789",
+ phone_number_confirmation("123", "123456789123456789"));
+ parse_internal_link("tg://confirmphone?hash=&phone=123456789123456789",
+ unknown_deep_link("tg://confirmphone?hash=&phone=123456789123456789"));
+ parse_internal_link("tg://confirmphone?hash=123456789123456789&phone=",
+ unknown_deep_link("tg://confirmphone?hash=123456789123456789&phone="));
+
+ parse_internal_link("t.me/setlanguage?lang=abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage", nullptr);
+ parse_internal_link("t.me/setlanguage/", nullptr);
+ parse_internal_link("t.me/setlanguage//abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage?/abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage/?abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage/#abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage/abacaba", language_pack("abacaba"));
+ parse_internal_link("t.me/setlanguage/aba%20aba", language_pack("aba aba"));
+ parse_internal_link("t.me/setlanguage/123456a", language_pack("123456a"));
+ parse_internal_link("t.me/setlanguage/12345678901", language_pack("12345678901"));
+ parse_internal_link("t.me/setlanguage/123456", language_pack("123456"));
+ parse_internal_link("t.me/setlanguage/123456/123123/12/31/a/s//21w/?asdas#test", language_pack("123456"));
+
+ parse_internal_link("tg:setlanguage?lang=abcdef", language_pack("abcdef"));
+ parse_internal_link("tg:setlanguage?lang=abc%30ef", language_pack("abc0ef"));
+ parse_internal_link("tg://setlanguage?lang=", unknown_deep_link("tg://setlanguage?lang="));
+
+ parse_internal_link(
+ "http://telegram.dog/iv?url=https://telegram.org&rhash=abcdef&test=1&tg_rhash=1",
+ instant_view("https://t.me/iv?url=https%3A%2F%2Ftelegram.org&rhash=abcdef", "https://telegram.org"));
+ parse_internal_link("t.me/iva?url=https://telegram.org&rhash=abcdef", public_chat("iva"));
+ parse_internal_link("t.me/iv?url=&rhash=abcdef", nullptr);
+ parse_internal_link("t.me/iv?url=https://telegram.org&rhash=",
+ instant_view("https://t.me/iv?url=https%3A%2F%2Ftelegram.org&rhash", "https://telegram.org"));
+ parse_internal_link("t.me/iv//////?url=https://telegram.org&rhash=",
+ instant_view("https://t.me/iv?url=https%3A%2F%2Ftelegram.org&rhash", "https://telegram.org"));
+ parse_internal_link("t.me/iv/////1/?url=https://telegram.org&rhash=", nullptr);
+ parse_internal_link("t.me/iv", nullptr);
+ parse_internal_link("t.me/iv?#url=https://telegram.org&rhash=abcdef", nullptr);
+ parse_internal_link("tg:iv?url=https://telegram.org&rhash=abcdef",
+ unknown_deep_link("tg://iv?url=https://telegram.org&rhash=abcdef"));
+
+ parse_internal_link("t.me/addtheme?slug=abcdef", nullptr);
+ parse_internal_link("t.me/addtheme", nullptr);
+ parse_internal_link("t.me/addtheme/", nullptr);
+ parse_internal_link("t.me/addtheme//abcdef", nullptr);
+ parse_internal_link("t.me/addtheme?/abcdef", nullptr);
+ parse_internal_link("t.me/addtheme/?abcdef", nullptr);
+ parse_internal_link("t.me/addtheme/#abcdef", nullptr);
+ parse_internal_link("t.me/addtheme/abacaba", theme("abacaba"));
+ parse_internal_link("t.me/addtheme/aba%20aba", theme("aba aba"));
+ parse_internal_link("t.me/addtheme/123456a", theme("123456a"));
+ parse_internal_link("t.me/addtheme/12345678901", theme("12345678901"));
+ parse_internal_link("t.me/addtheme/123456", theme("123456"));
+ parse_internal_link("t.me/addtheme/123456/123123/12/31/a/s//21w/?asdas#test", theme("123456"));
+
+ parse_internal_link("tg:addtheme?slug=abcdef", theme("abcdef"));
+ parse_internal_link("tg:addtheme?slug=abc%30ef", theme("abc0ef"));
+ parse_internal_link("tg://addtheme?slug=", unknown_deep_link("tg://addtheme?slug="));
+
+ parse_internal_link("t.me/proxy?server=1.2.3.4&port=80&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("t.me/proxy?server=1.2.3.4&port=80adasdas&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("t.me/proxy?server=1.2.3.4&port=adasdas&secret=1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=1.2.3.4&port=65536&secret=1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=", unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=12", unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("google.com", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=dd1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("google.com", 80, "dd1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=de1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=ee1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=ee1234567890abcdef1234567890ABCDEF0",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=ee1234567890abcdef1234567890ABCDEF%30%30",
+ proxy_mtproto("google.com", 80, "ee1234567890abcdef1234567890ABCDEF00"));
+ parse_internal_link(
+ "t.me/proxy?server=google.com&port=8%30&secret=ee1234567890abcdef1234567890ABCDEF010101010101010101",
+ proxy_mtproto("google.com", 80, "ee1234567890abcdef1234567890ABCDEF010101010101010101"));
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=7tAAAAAAAAAAAAAAAAAAAAAAAAcuZ29vZ2xlLmNvbQ",
+ proxy_mtproto("google.com", 80, "7tAAAAAAAAAAAAAAAAAAAAAAAAcuZ29vZ2xlLmNvbQ"));
+
+ parse_internal_link("tg:proxy?server=1.2.3.4&port=80&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("tg:proxy?server=1.2.3.4&port=80adasdas&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("tg:proxy?server=1.2.3.4&port=adasdas&secret=1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("tg:proxy?server=1.2.3.4&port=65536&secret=1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("tg:proxy?server=google.com&port=8%30&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("google.com", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("tg:proxy?server=google.com&port=8%30&secret=dd1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("google.com", 80, "dd1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("tg:proxy?server=google.com&port=8%30&secret=de1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+
+ parse_internal_link("t.me/socks?server=1.2.3.4&port=80", proxy_socks("1.2.3.4", 80, "", ""));
+ parse_internal_link("t.me/socks?server=1.2.3.4&port=80adasdas", proxy_socks("1.2.3.4", 80, "", ""));
+ parse_internal_link("t.me/socks?server=1.2.3.4&port=adasdas", unsupported_proxy());
+ parse_internal_link("t.me/socks?server=1.2.3.4&port=65536", unsupported_proxy());
+ parse_internal_link("t.me/socks?server=google.com&port=8%30", proxy_socks("google.com", 80, "", ""));
+ parse_internal_link("t.me/socks?server=google.com&port=8%30&user=1&pass=", proxy_socks("google.com", 80, "1", ""));
+ parse_internal_link("t.me/socks?server=google.com&port=8%30&user=&pass=2", proxy_socks("google.com", 80, "", "2"));
+ parse_internal_link("t.me/socks?server=google.com&port=80&user=1&pass=2", proxy_socks("google.com", 80, "1", "2"));
+
+ parse_internal_link("tg:socks?server=1.2.3.4&port=80", proxy_socks("1.2.3.4", 80, "", ""));
+ parse_internal_link("tg:socks?server=1.2.3.4&port=80adasdas", proxy_socks("1.2.3.4", 80, "", ""));
+ parse_internal_link("tg:socks?server=1.2.3.4&port=adasdas", unsupported_proxy());
+ parse_internal_link("tg:socks?server=1.2.3.4&port=65536", unsupported_proxy());
+ parse_internal_link("tg:socks?server=google.com&port=8%30", proxy_socks("google.com", 80, "", ""));
+ parse_internal_link("tg:socks?server=google.com&port=8%30&user=1&pass=", proxy_socks("google.com", 80, "1", ""));
+ parse_internal_link("tg:socks?server=google.com&port=8%30&user=&pass=2", proxy_socks("google.com", 80, "", "2"));
+ parse_internal_link("tg:socks?server=google.com&port=80&user=1&pass=2", proxy_socks("google.com", 80, "1", "2"));
+
+ parse_internal_link("tg:resolve?domain=username&voice%63hat=aasdasd", video_chat("username", "aasdasd", false));
+ parse_internal_link("tg:resolve?domain=username&video%63hat=aasdasd", video_chat("username", "aasdasd", false));
+ parse_internal_link("tg:resolve?domain=username&livestream=aasdasd", video_chat("username", "aasdasd", true));
+ parse_internal_link("TG://resolve?domain=username&voicechat=", video_chat("username", "", false));
+ parse_internal_link("TG://test@resolve?domain=username&voicechat=", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&voicechat=", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&voicechat=", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&voicechat=", nullptr);
+ parse_internal_link("tg:resolve?domain=&voicechat=", unknown_deep_link("tg://resolve?domain=&voicechat="));
+ parse_internal_link("tg:resolve?domain=telegram&&&&&&&voicechat=%30", video_chat("telegram", "0", false));
+
+ parse_internal_link("t.me/username/0/a//s/as?voicechat=", video_chat("username", "", false));
+ parse_internal_link("t.me/username/0/a//s/as?videochat=2", video_chat("username", "2", false));
+ parse_internal_link("t.me/username/0/a//s/as?livestream=3", video_chat("username", "3", true));
+ parse_internal_link("t.me/username/aasdas?test=1&voicechat=#12312", video_chat("username", "", false));
+ parse_internal_link("t.me/username/0?voicechat=", video_chat("username", "", false));
+ parse_internal_link("t.me/username/-1?voicechat=asdasd", video_chat("username", "asdasd", false));
+ parse_internal_link("t.me/username?voicechat=", video_chat("username", "", false));
+ parse_internal_link("t.me/username#voicechat=asdas", public_chat("username"));
+ parse_internal_link("t.me//username?voicechat=", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram?voi%63e%63hat=t%63st", video_chat("telecram", "tcst", false));
+
+ parse_internal_link("tg:resolve?domain=username&start=aasdasd", bot_start("username", "aasdasd"));
+ parse_internal_link("TG://resolve?domain=username&start=", bot_start("username", ""));
+ parse_internal_link("TG://test@resolve?domain=username&start=", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&start=", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&start=", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&start=", nullptr);
+ parse_internal_link("tg:resolve?domain=&start=", unknown_deep_link("tg://resolve?domain=&start="));
+ parse_internal_link("tg:resolve?domain=telegram&&&&&&&start=%30", bot_start("telegram", "0"));
+
+ parse_internal_link("t.me/username/0/a//s/as?start=", bot_start("username", ""));
+ parse_internal_link("t.me/username/aasdas?test=1&start=#12312", bot_start("username", ""));
+ parse_internal_link("t.me/username/0?start=", bot_start("username", ""));
+ parse_internal_link("t.me/username/-1?start=asdasd", bot_start("username", "asdasd"));
+ parse_internal_link("t.me/username?start=", bot_start("username", ""));
+ parse_internal_link("t.me/username#start=asdas", public_chat("username"));
+ parse_internal_link("t.me//username?start=", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram?start=t%63st", bot_start("telecram", "tcst"));
+
+ parse_internal_link("tg:resolve?domain=username&startgroup=aasdasd",
+ bot_start_in_group("username", "aasdasd", nullptr));
+ parse_internal_link("TG://resolve?domain=username&startgroup=", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("TG://test@resolve?domain=username&startgroup=", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&startgroup=", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&startgroup=", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&startgroup=", nullptr);
+ parse_internal_link("tg:resolve?domain=&startgroup=", unknown_deep_link("tg://resolve?domain=&startgroup="));
+ parse_internal_link("tg:resolve?domain=telegram&&&&&&&startgroup=%30", bot_start_in_group("telegram", "0", nullptr));
+
+ parse_internal_link("tg:resolve?domain=username&startgroup", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("tg:resolve?domain=username&startgroup&admin=asdas", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("tg:resolve?domain=username&startgroup&admin=post_messages",
+ bot_start_in_group("username", "", nullptr));
+ parse_internal_link("tg:resolve?domain=username&startgroup=1&admin=delete_messages+anonymous",
+ bot_start_in_group("username", "1",
+ chat_administrator_rights(true, false, false, false, true, false, false, false,
+ false, false, false, true)));
+ parse_internal_link(
+ "tg:resolve?domain=username&startgroup&admin=manage_chat+change_info+post_messages+edit_messages+delete_messages+"
+ "invite_users+restrict_members+pin_messages+manage_topics+promote_members+manage_video_chats+anonymous",
+ bot_start_in_group(
+ "username", "",
+ chat_administrator_rights(true, true, false, false, true, true, true, true, true, true, true, true)));
+
+ parse_internal_link("tg:resolve?domain=username&startchannel", public_chat("username"));
+ parse_internal_link("tg:resolve?domain=username&startchannel&admin=", public_chat("username"));
+ parse_internal_link(
+ "tg:resolve?domain=username&startchannel&admin=post_messages",
+ bot_add_to_channel("username", chat_administrator_rights(true, false, true, false, false, false, true, false,
+ false, false, false, false)));
+ parse_internal_link(
+ "tg:resolve?domain=username&startchannel&admin=manage_chat+change_info+post_messages+edit_messages+delete_"
+ "messages+invite_users+restrict_members+pin_messages+manage_topics+promote_members+manage_video_chats+anonymous",
+ bot_add_to_channel("username", chat_administrator_rights(true, true, true, true, true, true, true, false, false,
+ true, true, false)));
+
+ parse_internal_link("t.me/username/0/a//s/as?startgroup=", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username/aasdas?test=1&startgroup=#12312", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username/0?startgroup=", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username/-1?startgroup=asdasd", bot_start_in_group("username", "asdasd", nullptr));
+ parse_internal_link("t.me/username?startgroup=", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username#startgroup=asdas", public_chat("username"));
+ parse_internal_link("t.me//username?startgroup=", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram?startgroup=t%63st",
+ bot_start_in_group("telecram", "tcst", nullptr));
+
+ parse_internal_link("t.me/username?startgroup", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username?startgroup&admin=asdas", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username?startgroup&admin=post_messages", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username?startgroup=1&admin=delete_messages+anonymous",
+ bot_start_in_group("username", "1",
+ chat_administrator_rights(true, false, false, false, true, false, false, false,
+ false, false, false, true)));
+ parse_internal_link(
+ "t.me/"
+ "username?startgroup&admin=manage_chat+change_info+post_messages+edit_messages+delete_messages+invite_users+"
+ "restrict_members+pin_messages+manage_topics+promote_members+manage_video_chats+anonymous",
+ bot_start_in_group(
+ "username", "",
+ chat_administrator_rights(true, true, false, false, true, true, true, true, true, true, true, true)));
+
+ parse_internal_link("t.me/username?startchannel", public_chat("username"));
+ parse_internal_link("t.me/username?startchannel&admin=", public_chat("username"));
+ parse_internal_link(
+ "t.me/username?startchannel&admin=post_messages",
+ bot_add_to_channel("username", chat_administrator_rights(true, false, true, false, false, false, true, false,
+ false, false, false, false)));
+ parse_internal_link(
+ "t.me/"
+ "username?startchannel&admin=manage_chat+change_info+post_messages+edit_messages+delete_messages+invite_users+"
+ "restrict_members+pin_messages+manage_topics+promote_members+manage_video_chats+anonymous",
+ bot_add_to_channel("username", chat_administrator_rights(true, true, true, true, true, true, true, false, false,
+ true, true, false)));
+
+ parse_internal_link("tg:resolve?domain=username&game=aasdasd", game("username", "aasdasd"));
+ parse_internal_link("TG://resolve?domain=username&game=", public_chat("username"));
+ parse_internal_link("TG://test@resolve?domain=username&game=asd", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&game=asd", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&game=asd", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&game=asd", nullptr);
+ parse_internal_link("tg:resolve?domain=&game=asd", unknown_deep_link("tg://resolve?domain=&game=asd"));
+ parse_internal_link("tg:resolve?domain=telegram&&&&&&&game=%30", game("telegram", "0"));
+
+ parse_internal_link("t.me/username/0/a//s/as?game=asd", game("username", "asd"));
+ parse_internal_link("t.me/username/aasdas?test=1&game=asd#12312", game("username", "asd"));
+ parse_internal_link("t.me/username/0?game=asd", game("username", "asd"));
+ parse_internal_link("t.me/username/-1?game=asdasd", game("username", "asdasd"));
+ parse_internal_link("t.me/username?game=asd", game("username", "asd"));
+ parse_internal_link("t.me/username?game=", public_chat("username"));
+ parse_internal_link("t.me/username#game=asdas", public_chat("username"));
+ parse_internal_link("t.me//username?game=asd", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram?game=t%63st", game("telecram", "tcst"));
+
+ parse_internal_link("tg:resolve?domain=username&Game=asd", public_chat("username"));
+ parse_internal_link("TG://test@resolve?domain=username", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username", nullptr);
+ parse_internal_link("tg:resolve?domain=", unknown_deep_link("tg://resolve?domain="));
+ parse_internal_link("tg:resolve?&&&&&&&domain=telegram", public_chat("telegram"));
+
+ parse_internal_link("t.me/a", public_chat("a"));
+ parse_internal_link("t.me/abcdefghijklmnopqrstuvwxyz123456", public_chat("abcdefghijklmnopqrstuvwxyz123456"));
+ parse_internal_link("t.me/abcdefghijklmnopqrstuvwxyz1234567", nullptr);
+ parse_internal_link("t.me/abcdefghijklmnop-qrstuvwxyz", nullptr);
+ parse_internal_link("t.me/abcdefghijklmnop~qrstuvwxyz", nullptr);
+ parse_internal_link("t.me/_asdf", nullptr);
+ parse_internal_link("t.me/0asdf", nullptr);
+ parse_internal_link("t.me/9asdf", nullptr);
+ parse_internal_link("t.me/Aasdf", public_chat("Aasdf"));
+ parse_internal_link("t.me/asdf_", nullptr);
+ parse_internal_link("t.me/asdf0", public_chat("asdf0"));
+ parse_internal_link("t.me/asd__fg", nullptr);
+ parse_internal_link("t.me/username/0/a//s/as?gam=asd", public_chat("username"));
+ parse_internal_link("t.me/username/aasdas?test=1", public_chat("username"));
+ parse_internal_link("t.me/username/0", public_chat("username"));
+ parse_internal_link("t.me//username", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram", public_chat("telecram"));
+
+ parse_internal_link(
+ "tg://"
+ "resolve?domain=telegrampassport&bot_id=543260180&scope=%7B%22v%22%3A1%2C%22d%22%3A%5B%7B%22%22%5D%7D%5D%7D&"
+ "public_key=BEGIN%20PUBLIC%20KEY%0A&nonce=b8ee&callback_url=https%3A%2F%2Fcore.telegram.org%2Fpassport%2Fexample%"
+ "3Fpassport_ssid%3Db8ee&payload=nonce",
+ passport_data_request(543260180, "{\"v\":1,\"d\":[{\"\"]}]}", "BEGIN PUBLIC KEY\n", "b8ee",
+ "https://core.telegram.org/passport/example?passport_ssid=b8ee"));
+ parse_internal_link("tg://resolve?domain=telegrampassport&bot_id=12345&public_key=key&scope=asd&payload=nonce",
+ passport_data_request(12345, "asd", "key", "nonce", ""));
+ parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload=nonce",
+ passport_data_request(12345, "asd", "key", "nonce", ""));
+ parse_internal_link("tg://passport?bot_id=0&public_key=key&scope=asd&payload=nonce",
+ unknown_deep_link("tg://passport?bot_id=0&public_key=key&scope=asd&payload=nonce"));
+ parse_internal_link("tg://passport?bot_id=-1&public_key=key&scope=asd&payload=nonce",
+ unknown_deep_link("tg://passport?bot_id=-1&public_key=key&scope=asd&payload=nonce"));
+ parse_internal_link("tg://passport?bot_id=12345&public_key=&scope=asd&payload=nonce",
+ unknown_deep_link("tg://passport?bot_id=12345&public_key=&scope=asd&payload=nonce"));
+ parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=&payload=nonce",
+ unknown_deep_link("tg://passport?bot_id=12345&public_key=key&scope=&payload=nonce"));
+ parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload=",
+ unknown_deep_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload="));
+ parse_internal_link("t.me/telegrampassport?bot_id=12345&public_key=key&scope=asd&payload=nonce",
+ public_chat("telegrampassport"));
+
+ parse_internal_link("tg:premium_offer?ref=abcdef", premium_features("abcdef"));
+ parse_internal_link("tg:premium_offer?ref=abc%30ef", premium_features("abc0ef"));
+ parse_internal_link("tg://premium_offer?ref=", premium_features(""));
+
+ parse_internal_link("tg://settings", settings());
+ parse_internal_link("tg://setting", unknown_deep_link("tg://setting"));
+ parse_internal_link("tg://settings?asdsa?D?SADasD?asD", settings());
+ parse_internal_link("tg://settings#test", settings());
+ parse_internal_link("tg://settings/#test", settings());
+ parse_internal_link("tg://settings/aadsa#test", settings());
+ parse_internal_link("tg://settings/theme#test", settings());
+ parse_internal_link("tg://settings/themes#test", theme_settings());
+ parse_internal_link("tg://settings/themesa#test", settings());
+ parse_internal_link("tg://settings/themes/?as#rad", theme_settings());
+ parse_internal_link("tg://settings/themes/a", settings());
+ parse_internal_link("tg://settings/asdsathemesasdas/devices", settings());
+ parse_internal_link("tg://settings/devices", active_sessions());
+ parse_internal_link("tg://settings/change_number", change_phone_number());
+ parse_internal_link("tg://settings/folders", filter_settings());
+ parse_internal_link("tg://settings/filters", settings());
+ parse_internal_link("tg://settings/language", language_settings());
+ parse_internal_link("tg://settings/privacy", privacy_and_security_settings());
+
+ parse_internal_link("username.t.me////0/a//s/as?start=", bot_start("username", ""));
+ parse_internal_link("username.t.me?start=as", bot_start("username", "as"));
+ parse_internal_link("username.t.me", public_chat("username"));
+ parse_internal_link("aAAb.t.me/12345?single", message("tg:resolve?domain=aaab&post=12345&single"));
+ parse_internal_link("telegram.t.me/195", message("tg:resolve?domain=telegram&post=195"));
+ parse_internal_link("shares.t.me", public_chat("shares"));
+
+ parse_internal_link("c.t.me/12345?single", nullptr);
+ parse_internal_link("aaa.t.me/12345?single", nullptr);
+ parse_internal_link("aaa_.t.me/12345?single", nullptr);
+ parse_internal_link("0aaa.t.me/12345?single", nullptr);
+ parse_internal_link("_aaa.t.me/12345?single", nullptr);
+ parse_internal_link("addemoji.t.me", nullptr);
+ parse_internal_link("addstickers.t.me", nullptr);
+ parse_internal_link("addtheme.t.me", nullptr);
+ parse_internal_link("auth.t.me", nullptr);
+ parse_internal_link("confirmphone.t.me", nullptr);
+ parse_internal_link("invoice.t.me", nullptr);
+ parse_internal_link("joinchat.t.me", nullptr);
+ parse_internal_link("login.t.me", nullptr);
+ parse_internal_link("proxy.t.me", nullptr);
+ parse_internal_link("setlanguage.t.me", nullptr);
+ parse_internal_link("share.t.me", nullptr);
+ parse_internal_link("socks.t.me", nullptr);
+
+ parse_internal_link("www.telegra.ph/", nullptr);
+ parse_internal_link("www.telegrA.ph/#", nullptr);
+ parse_internal_link("www.telegrA.ph/?", instant_view("https://telegra.ph/?", "www.telegrA.ph/?"));
+ parse_internal_link("http://te.leGra.ph/?", instant_view("https://telegra.ph/?", "http://te.leGra.ph/?"));
+ parse_internal_link("https://grAph.org/12345", instant_view("https://telegra.ph/12345", "https://grAph.org/12345"));
+}
diff --git a/protocols/Telegram/tdlib/td/test/main.cpp b/protocols/Telegram/tdlib/td/test/main.cpp
index 0ef46c75b2..064871ae31 100644
--- a/protocols/Telegram/tdlib/td/test/main.cpp
+++ b/protocols/Telegram/tdlib/td/test/main.cpp
@@ -1,40 +1,64 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/tests.h"
-
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/ExitGuard.h"
#include "td/utils/logging.h"
-
-#include <cstring>
+#include "td/utils/OptionParser.h"
+#include "td/utils/port/detail/ThreadIdGuard.h"
+#include "td/utils/port/stacktrace.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/tests.h"
#if TD_EMSCRIPTEN
#include <emscripten.h>
#endif
int main(int argc, char **argv) {
- // TODO port OptionsParser to Windows
- for (int i = 1; i < argc; i++) {
- if (!std::strcmp(argv[i], "--filter")) {
- CHECK(i + 1 < argc);
- td::Test::add_substr_filter(argv[++i]);
- }
- if (!std::strcmp(argv[i], "--stress")) {
- td::Test::set_stress_flag(true);
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL));
+ td::ExitGuard exit_guard;
+ td::detail::ThreadIdGuard thread_id_guard;
+ td::Stacktrace::init();
+ td::init_openssl_threads();
+
+ td::TestsRunner &runner = td::TestsRunner::get_default();
+
+ int default_verbosity_level = 1;
+ td::OptionParser options;
+ options.add_option('f', "filter", "run only specified tests",
+ [&](td::Slice filter) { runner.add_substr_filter(filter.str()); });
+ options.add_option('s', "stress", "run tests infinitely", [&] { runner.set_stress_flag(true); });
+ options.add_checked_option('v', "verbosity", "log verbosity level",
+ td::OptionParser::parse_integer(default_verbosity_level));
+ options.add_check([&] {
+ if (default_verbosity_level < 0) {
+ return td::Status::Error("Wrong verbosity level specified");
}
+ return td::Status::OK();
+ });
+ auto r_non_options = options.run(argc, argv, 0);
+ if (r_non_options.is_error()) {
+ LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();
+ LOG(PLAIN) << options;
+ return 1;
}
+ SET_VERBOSITY_LEVEL(default_verbosity_level);
+
#if TD_EMSCRIPTEN
emscripten_set_main_loop(
[] {
- if (!td::Test::run_all_step()) {
+ td::TestsRunner &default_runner = td::TestsRunner::get_default();
+ if (!default_runner.run_all_step()) {
emscripten_cancel_main_loop();
}
},
10, 0);
#else
- td::Test::run_all();
+ runner.run_all();
#endif
- return 0;
}
diff --git a/protocols/Telegram/tdlib/td/test/message_entities.cpp b/protocols/Telegram/tdlib/td/test/message_entities.cpp
index 473a87140a..9c62415579 100644
--- a/protocols/Telegram/tdlib/td/test/message_entities.cpp
+++ b/protocols/Telegram/tdlib/td/test/message_entities.cpp
@@ -1,28 +1,36 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
+#include "td/telegram/CustomEmojiId.h"
#include "td/telegram/MessageEntity.h"
+#include "td/telegram/UserId.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/tests.h"
+#include "td/utils/utf8.h"
-REGISTER_TESTS(message_entities);
+#include <algorithm>
+#include <utility>
-using namespace td;
-
-static void check_mention(string str, std::vector<string> expected) {
- auto result_slice = find_mentions(str);
- std::vector<string> result;
+static void check_mention(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_mentions(str);
+ td::vector<td::string> result;
for (auto &it : result_slice) {
result.push_back(it.str());
}
if (result != expected) {
- LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result))
- << tag("expected", format::as_array(expected));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
}
}
@@ -39,20 +47,21 @@ TEST(MessageEntities, mention) {
check_mention("@abcdefghijklmnopqrstuvwxyz123456", {"@abcdefghijklmnopqrstuvwxyz123456"});
check_mention("@abcdefghijklmnopqrstuvwxyz1234567", {});
check_mention("нет@mention", {});
- check_mention("@ya @gif @wiki @vid @bing @pic @bold @imdb @coub @like @vote @giff @cap ya cap @y @yar @bingg @bin",
- {"@ya", "@gif", "@wiki", "@vid", "@bing", "@pic", "@bold", "@imdb", "@coub", "@like", "@vote", "@giff",
- "@cap", "@bingg"});
-};
-
-static void check_bot_command(string str, std::vector<string> expected) {
- auto result_slice = find_bot_commands(str);
- std::vector<string> result;
+ check_mention(
+ "@ya @gif @wiki @vid @bing @pic @bold @imdb @ImDb @coub @like @vote @giff @cap ya cap @y @yar @bingg @bin",
+ {"@gif", "@wiki", "@vid", "@bing", "@pic", "@bold", "@imdb", "@ImDb", "@coub", "@like", "@vote", "@giff",
+ "@bingg"});
+}
+
+static void check_bot_command(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_bot_commands(str);
+ td::vector<td::string> result;
for (auto &it : result_slice) {
result.push_back(it.str());
}
if (result != expected) {
- LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result))
- << tag("expected", format::as_array(expected));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
}
}
@@ -68,15 +77,15 @@ TEST(MessageEntities, bot_command) {
check_bot_command("/test/", {});
}
-static void check_hashtag(string str, std::vector<string> expected) {
- auto result_slice = find_hashtags(str);
- std::vector<string> result;
+static void check_hashtag(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_hashtags(str);
+ td::vector<td::string> result;
for (auto &it : result_slice) {
result.push_back(it.str());
}
if (result != expected) {
- LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result))
- << tag("expected", format::as_array(expected));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
}
}
@@ -95,29 +104,32 @@ TEST(MessageEntities, hashtag) {
check_hashtag(" #123a ", {"#123a"});
check_hashtag(" #a123 ", {"#a123"});
check_hashtag(" #123a# ", {});
- check_hashtag(" #" + string(300, '1'), {});
- check_hashtag(" #" + string(256, '1'), {});
- check_hashtag(" #" + string(256, '1') + "a ", {});
- check_hashtag(" #" + string(255, '1') + "a", {"#" + string(255, '1') + "a"});
- check_hashtag(" #" + string(255, '1') + "Я", {"#" + string(255, '1') + "Я"});
- check_hashtag(" #" + string(255, '1') + "a" + string(255, 'b') + "# ", {});
+ check_hashtag(" #" + td::string(300, '1'), {});
+ check_hashtag(" #" + td::string(256, '1'), {});
+ check_hashtag(" #" + td::string(256, '1') + "a ", {});
+ check_hashtag(" #" + td::string(255, '1') + "a", {"#" + td::string(255, '1') + "a"});
+ check_hashtag(" #" + td::string(255, '1') + "Я", {"#" + td::string(255, '1') + "Я"});
+ check_hashtag(" #" + td::string(255, '1') + "a" + td::string(255, 'b') + "# ", {});
check_hashtag("#a#b #c #d", {"#c", "#d"});
check_hashtag("#test", {"#test"});
- check_hashtag(u8"\U0001F604\U0001F604\U0001F604\U0001F604 \U0001F604\U0001F604\U0001F604#" + string(200, '1') +
- "ООО" + string(200, '2'),
- {"#" + string(200, '1') + "ООО" + string(53, '2')});
+ check_hashtag("#te·st", {"#te·st"});
+ check_hashtag(u8"\U0001F604\U0001F604\U0001F604\U0001F604 \U0001F604\U0001F604\U0001F604#" + td::string(200, '1') +
+ "ООО" + td::string(200, '2'),
+ {"#" + td::string(200, '1') + "ООО" + td::string(53, '2')});
check_hashtag(u8"#a\u2122", {"#a"});
+ check_hashtag("#a൹", {"#a"});
+ check_hashtag("#aඁං෴ก฿", {"#aඁං෴ก"});
}
-static void check_cashtag(string str, std::vector<string> expected) {
- auto result_slice = find_cashtags(str);
- std::vector<string> result;
+static void check_cashtag(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_cashtags(str);
+ td::vector<td::string> result;
for (auto &it : result_slice) {
result.push_back(it.str());
}
if (result != expected) {
- LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result))
- << tag("expected", format::as_array(expected));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
}
}
@@ -133,8 +145,9 @@ TEST(MessageEntities, cashtag) {
check_cashtag("$ab", {});
check_cashtag("$abc", {});
check_cashtag("$", {});
- check_cashtag("$A", {});
- check_cashtag("$AB", {});
+ check_cashtag("$A", {"$A"});
+ check_cashtag("$AB", {"$AB"});
+ check_cashtag("$ABС", {});
check_cashtag("$АBC", {});
check_cashtag("$АВС", {});
check_cashtag("$ABC", {"$ABC"});
@@ -156,13 +169,172 @@ TEST(MessageEntities, cashtag) {
check_cashtag(" А$ABC ", {});
check_cashtag("$ABC$DEF $GHI $KLM", {"$GHI", "$KLM"});
check_cashtag("$TEST", {"$TEST"});
+ check_cashtag("$1INC", {});
+ check_cashtag("$1INCH", {"$1INCH"});
+ check_cashtag("...$1INCH...", {"$1INCH"});
+ check_cashtag("$1inch", {});
+ check_cashtag("$1INCHA", {});
+ check_cashtag("$1INCHА", {});
check_cashtag(u8"$ABC\u2122", {"$ABC"});
check_cashtag(u8"\u2122$ABC", {"$ABC"});
check_cashtag(u8"\u2122$ABC\u2122", {"$ABC"});
+ check_cashtag("$ABC൹", {"$ABC"});
+ check_cashtag("$ABCඁ", {});
+ check_cashtag("$ABCං", {});
+ check_cashtag("$ABC෴", {});
+ check_cashtag("$ABCก", {});
+ check_cashtag("$ABC฿", {"$ABC"});
+}
+
+static void check_media_timestamp(const td::string &str, const td::vector<std::pair<td::string, td::int32>> &expected) {
+ auto result = td::transform(td::find_media_timestamps(str),
+ [](auto &&entity) { return std::make_pair(entity.first.str(), entity.second); });
+ if (result != expected) {
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, media_timestamp) {
+ check_media_timestamp("", {});
+ check_media_timestamp(":", {});
+ check_media_timestamp(":1", {});
+ check_media_timestamp("a:1", {});
+ check_media_timestamp("01", {});
+ check_media_timestamp("01:", {});
+ check_media_timestamp("01::", {});
+ check_media_timestamp("01::", {});
+ check_media_timestamp("a1:1a", {});
+ check_media_timestamp("a1::01a", {});
+ check_media_timestamp("2001:db8::8a2e:f70:13a4", {});
+ check_media_timestamp("0:00", {{"0:00", 0}});
+ check_media_timestamp("+0:00", {{"0:00", 0}});
+ check_media_timestamp("0:00+", {{"0:00", 0}});
+ check_media_timestamp("a0:00", {});
+ check_media_timestamp("0:00a", {});
+ check_media_timestamp("б0:00", {});
+ check_media_timestamp("0:00б", {});
+ check_media_timestamp("_0:00", {});
+ check_media_timestamp("0:00_", {});
+ check_media_timestamp("00:00:00:00", {});
+ check_media_timestamp("1:1:01 1:1:1", {{"1:1:01", 3661}});
+ check_media_timestamp("0:0:00 00:00 000:00 0000:00 00000:00 00:00:00 000:00:00 00:000:00 00:00:000",
+ {{"0:0:00", 0}, {"00:00", 0}, {"000:00", 0}, {"0000:00", 0}, {"00:00:00", 0}});
+ check_media_timestamp("00:0:00 0:00:00 00::00 :00:00 00:00: 00:00:0 00:00:", {{"00:0:00", 0}, {"0:00:00", 0}});
+ check_media_timestamp("1:1:59 1:1:-1 1:1:60", {{"1:1:59", 3719}});
+ check_media_timestamp("1:59:00 1:-1:00 1:60:00", {{"1:59:00", 7140}, {"1:00", 60}});
+ check_media_timestamp("59:59 60:00", {{"59:59", 3599}, {"60:00", 3600}});
+ check_media_timestamp("9999:59 99:59:59 99:60:59", {{"9999:59", 599999}, {"99:59:59", 360000 - 1}});
+ check_media_timestamp("2001:db8::8a2e:f70:13a4", {});
+}
+
+static void check_bank_card_number(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_bank_card_numbers(str);
+ td::vector<td::string> result;
+ for (auto &it : result_slice) {
+ result.push_back(it.str());
+ }
+ if (result != expected) {
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, bank_card_number) {
+ check_bank_card_number("", {});
+ check_bank_card_number("123456789015", {});
+ check_bank_card_number("1234567890120", {});
+ check_bank_card_number("1234567890121", {});
+ check_bank_card_number("1234567890122", {});
+ check_bank_card_number("1234567890123", {});
+ check_bank_card_number("1234567890124", {});
+ check_bank_card_number("1234567890125", {});
+ check_bank_card_number("1234567890126", {});
+ check_bank_card_number("1234567890127", {});
+ check_bank_card_number("1234567890128", {"1234567890128"});
+ check_bank_card_number("1234567890129", {});
+ check_bank_card_number("12345678901500", {"12345678901500"});
+ check_bank_card_number("123456789012800", {"123456789012800"});
+ check_bank_card_number("1234567890151800", {"1234567890151800"});
+ check_bank_card_number("12345678901280000", {"12345678901280000"});
+ check_bank_card_number("123456789015009100", {"123456789015009100"});
+ check_bank_card_number("1234567890128000000", {"1234567890128000000"});
+ check_bank_card_number("12345678901500910000", {});
+ check_bank_card_number(" - - - - 1 - -- 2 - - -- 34 - - - 56- - 7890150000 - - - -", {});
+ check_bank_card_number(" - - - - 1 - -- 234 - - 56- - 7890150000 - - - -", {"1 - -- 234 - - 56- - 7890150000"});
+ check_bank_card_number("4916-3385-0608-2832; 5280 9342 8317 1080 ;345936346788903",
+ {"4916-3385-0608-2832", "5280 9342 8317 1080", "345936346788903"});
+ check_bank_card_number("4556728228023269, 4916141675244747020, 49161416752447470, 4556728228023269",
+ {"4556728228023269", "4916141675244747020", "4556728228023269"});
+ check_bank_card_number("a1234567890128", {});
+ check_bank_card_number("1234567890128a", {});
+ check_bank_card_number("1234567890128а", {});
+ check_bank_card_number("а1234567890128", {});
+ check_bank_card_number("1234567890128_", {});
+ check_bank_card_number("_1234567890128", {});
+ check_bank_card_number("1234567890128/", {"1234567890128"});
+ check_bank_card_number("\"1234567890128", {"1234567890128"});
+ check_bank_card_number("+1234567890128", {});
}
-static void check_is_email_address(string str, bool expected) {
- bool result = is_email_address(str);
+static void check_tg_url(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_tg_urls(str);
+ td::vector<td::string> result;
+ for (auto &it : result_slice) {
+ result.push_back(it.str());
+ }
+ if (result != expected) {
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, tg_url) {
+ check_tg_url("", {});
+ check_tg_url("tg://", {});
+ check_tg_url("tg://a", {"tg://a"});
+ check_tg_url("a", {});
+ check_tg_url("stg://a", {"tg://a"});
+ check_tg_url("asd asdas das ton:asd tg:test ton://resolve tg://resolve TON://_-RESOLVE_- TG://-_RESOLVE-_",
+ {"ton://resolve", "tg://resolve", "TON://_-RESOLVE_-", "TG://-_RESOLVE-_"});
+ check_tg_url("tg:test/", {});
+ check_tg_url("tg:/test/", {});
+ check_tg_url("tg://test/", {"tg://test/"});
+ check_tg_url("tg://test/?", {"tg://test/"});
+ check_tg_url("tg://test/#", {"tg://test/#"});
+ check_tg_url("tg://test?", {"tg://test"});
+ check_tg_url("tg://test#", {"tg://test"});
+ check_tg_url("tg://test/―asd―?asd=asd&asdas=―#――――", {"tg://test/―asd―?asd=asd&asdas=―#――――"});
+ check_tg_url("tg://test/?asd", {"tg://test/?asd"});
+ check_tg_url("tg://test/?.:;,('?!`.:;,('?!`", {"tg://test/"});
+ check_tg_url("tg://test/#asdf", {"tg://test/#asdf"});
+ check_tg_url("tg://test?asdf", {"tg://test?asdf"});
+ check_tg_url("tg://test#asdf", {"tg://test#asdf"});
+ check_tg_url("tg://test?as‖df", {"tg://test?as"});
+ check_tg_url("tg://test?sa<df", {"tg://test?sa"});
+ check_tg_url("tg://test?as>df", {"tg://test?as"});
+ check_tg_url("tg://test?as\"df", {"tg://test?as"});
+ check_tg_url("tg://test?as«df", {"tg://test?as"});
+ check_tg_url("tg://test?as»df", {"tg://test?as"});
+ check_tg_url("tg://test?as(df", {"tg://test?as(df"});
+ check_tg_url("tg://test?as)df", {"tg://test?as)df"});
+ check_tg_url("tg://test?as[df", {"tg://test?as[df"});
+ check_tg_url("tg://test?as]df", {"tg://test?as]df"});
+ check_tg_url("tg://test?as{df", {"tg://test?as{df"});
+ check_tg_url("tg://test?as'df", {"tg://test?as'df"});
+ check_tg_url("tg://test?as}df", {"tg://test?as}df"});
+ check_tg_url("tg://test?as$df", {"tg://test?as$df"});
+ check_tg_url("tg://test?as%df", {"tg://test?as%df"});
+ check_tg_url("tg://%30/sccct", {});
+ check_tg_url("tg://test:asd@google.com:80", {"tg://test"});
+ check_tg_url("tg://google.com", {"tg://google"});
+ check_tg_url("tg://google/.com", {"tg://google/.com"});
+ check_tg_url("tg://127.0.0.1", {"tg://127"});
+ check_tg_url("tg://б.а.н.а.на", {});
+}
+
+static void check_is_email_address(const td::string &str, bool expected) {
+ bool result = td::is_email_address(str);
LOG_IF(FATAL, result != expected) << "Expected " << expected << " as result of is_email_address(" << str << ")";
}
@@ -183,79 +355,79 @@ TEST(MessageEntities, is_email_address) {
check_is_email_address("a.ab", false);
check_is_email_address("a.bc@d.ef", true);
- vector<string> bad_userdatas = {"",
- "a.a.a.a.a.a.a.a.a.a.a.a",
- "+.+.+.+.+.+",
- "*.a.a",
- "a.*.a",
- "a.a.*",
- "a.a.",
- "a.a.abcdefghijklmnopqrstuvwxyz0123456789",
- "a.abcdefghijklmnopqrstuvwxyz0.a",
- "abcdefghijklmnopqrstuvwxyz0.a.a"};
- vector<string> good_userdatas = {"a.a.a.a.a.a.a.a.a.a.a",
- "a+a+a+a+a+a+a+a+a+a+a",
- "+.+.+.+.+._",
- "aozAQZ0-5-9_+-aozAQZ0-5-9_.aozAQZ0-5-9_.-._.+-",
- "a.a.a",
- "a.a.abcdefghijklmnopqrstuvwxyz012345678",
- "a.abcdefghijklmnopqrstuvwxyz.a",
- "a..a",
- "abcdefghijklmnopqrstuvwxyz.a.a",
- ".a.a"};
-
- vector<string> bad_domains = {"",
- ".",
- "abc",
- "localhost",
- "a.a.a.a.a.a.a.ab",
- ".......",
- "a.a.a.a.a.a+ab",
- "a+a.a.a.a.a.ab",
- "a.a.a.a.a.a.a",
- "a.a.a.a.a.a.abcdefg",
- "a.a.a.a.a.a.ab0yz",
- "a.a.a.a.a.a.ab9yz",
- "a.a.a.a.a.a.ab-yz",
- "a.a.a.a.a.a.ab_yz",
- "a.a.a.a.a.a.ab*yz",
- ".ab",
- ".a.ab",
- "a..ab",
- "a.a.a..a.ab",
- ".a.a.a.a.ab",
- "abcdefghijklmnopqrstuvwxyz01234.ab",
- "ab0cd.abd.aA*sd.0.9.0-9.ABOYZ",
- "ab*cd.abd.aAasd.0.9.0-9.ABOYZ",
- "ab0cd.abd.aAasd.0.9.0*9.ABOYZ",
- "*b0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
- "ab0c*.ab_d.aA-sd.0.9.0-9.ABOYZ",
- "ab0cd.ab_d.aA-sd.0.9.0-*.ABOYZ",
- "ab0cd.ab_d.aA-sd.0.9.*-9.ABOYZ",
- "-b0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
- "ab0c-.ab_d.aA-sd.0.9.0-9.ABOYZ",
- "ab0cd.ab_d.aA-sd.-.9.0-9.ABOYZ",
- "ab0cd.ab_d.aA-sd.0.9.--9.ABOYZ",
- "ab0cd.ab_d.aA-sd.0.9.0--.ABOYZ",
- "_b0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
- "ab0c_.ab_d.aA-sd.0.9.0-9.ABOYZ",
- "ab0cd.ab_d.aA-sd._.9.0-9.ABOYZ",
- "ab0cd.ab_d.aA-sd.0.9._-9.ABOYZ",
- "ab0cd.ab_d.aA-sd.0.9.0-_.ABOYZ",
- "-.ab_d.aA-sd.0.9.0-9.ABOYZ",
- "ab0cd.ab_d.-.0.9.0-9.ABOYZ",
- "ab0cd.ab_d.aA-sd.0.9.-.ABOYZ",
- "_.ab_d.aA-sd.0.9.0-9.ABOYZ",
- "ab0cd.ab_d._.0.9.0-9.ABOYZ",
- "ab0cd.ab_d.aA-sd.0.9._.ABOYZ"};
- vector<string> good_domains = {"a.a.a.a.a.a.ab",
- "a.a.a.a.a.a.abcdef",
- "a.a.a.a.a.a.aboyz",
- "a.a.a.a.a.a.ABOYZ",
- "a.a.a.a.a.a.AbOyZ",
- "abcdefghijklmnopqrstuvwxyz0123.ab",
- "ab0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
- "A.Z.aA-sd.a.z.0-9.ABOYZ"};
+ td::vector<td::string> bad_userdatas = {"",
+ "a.a.a.a.a.a.a.a.a.a.a.a",
+ "+.+.+.+.+.+",
+ "*.a.a",
+ "a.*.a",
+ "a.a.*",
+ "a.a.",
+ "a.a.abcdefghijklmnopqrstuvwxyz0123456789",
+ "a.abcdefghijklmnopqrstuvwxyz0.a",
+ "abcdefghijklmnopqrstuvwxyz0.a.a"};
+ td::vector<td::string> good_userdatas = {"a.a.a.a.a.a.a.a.a.a.a",
+ "a+a+a+a+a+a+a+a+a+a+a",
+ "+.+.+.+.+._",
+ "aozAQZ0-5-9_+-aozAQZ0-5-9_.aozAQZ0-5-9_.-._.+-",
+ "a.a.a",
+ "a.a.abcdefghijklmnopqrstuvwxyz012345678",
+ "a.abcdefghijklmnopqrstuvwxyz.a",
+ "a..a",
+ "abcdefghijklmnopqrstuvwxyz.a.a",
+ ".a.a"};
+
+ td::vector<td::string> bad_domains = {"",
+ ".",
+ "abc",
+ "localhost",
+ "a.a.a.a.a.a.a.ab",
+ ".......",
+ "a.a.a.a.a.a+ab",
+ "a+a.a.a.a.a.ab",
+ "a.a.a.a.a.a.a",
+ "a.a.a.a.a.a.abcdefghi",
+ "a.a.a.a.a.a.ab0yz",
+ "a.a.a.a.a.a.ab9yz",
+ "a.a.a.a.a.a.ab-yz",
+ "a.a.a.a.a.a.ab_yz",
+ "a.a.a.a.a.a.ab*yz",
+ ".ab",
+ ".a.ab",
+ "a..ab",
+ "a.a.a..a.ab",
+ ".a.a.a.a.ab",
+ "abcdefghijklmnopqrstuvwxyz01234.ab",
+ "ab0cd.abd.aA*sd.0.9.0-9.ABOYZ",
+ "ab*cd.abd.aAasd.0.9.0-9.ABOYZ",
+ "ab0cd.abd.aAasd.0.9.0*9.ABOYZ",
+ "*b0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0c*.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.0-*.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.*-9.ABOYZ",
+ "-b0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0c-.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.-.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.--9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.0--.ABOYZ",
+ "_b0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0c_.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd._.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9._-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.0-_.ABOYZ",
+ "-.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.-.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.-.ABOYZ",
+ "_.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d._.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9._.ABOYZ"};
+ td::vector<td::string> good_domains = {"a.a.a.a.a.a.ab",
+ "a.a.a.a.a.a.abcdef",
+ "a.a.a.a.a.a.aboyz",
+ "a.a.a.a.a.a.ABOYZ",
+ "a.a.a.a.a.a.AbOyZ",
+ "abcdefghijklmnopqrstuvwxyz0123.ab",
+ "ab0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "A.Z.aA-sd.a.z.0-9.ABOYZ"};
for (auto &userdata : bad_userdatas) {
for (auto &domain : bad_domains) {
@@ -279,11 +451,11 @@ TEST(MessageEntities, is_email_address) {
}
}
-static void check_url(string str, std::vector<string> expected_urls,
- std::vector<string> expected_email_addresses = {}) {
- auto result_slice = find_urls(str);
- std::vector<string> result_urls;
- std::vector<string> result_email_addresses;
+static void check_url(const td::string &str, const td::vector<td::string> &expected_urls,
+ td::vector<td::string> expected_email_addresses = {}) {
+ auto result_slice = td::find_urls(str);
+ td::vector<td::string> result_urls;
+ td::vector<td::string> result_email_addresses;
for (auto &it : result_slice) {
if (!it.second) {
result_urls.push_back(it.first.str());
@@ -292,12 +464,12 @@ static void check_url(string str, std::vector<string> expected_urls,
}
}
if (result_urls != expected_urls) {
- LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result_urls))
- << tag("expected", format::as_array(expected_urls));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result_urls))
+ << td::tag("expected", td::format::as_array(expected_urls));
}
if (result_email_addresses != expected_email_addresses) {
- LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result_email_addresses))
- << tag("expected", format::as_array(expected_email_addresses));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result_email_addresses))
+ << td::tag("expected", td::format::as_array(expected_email_addresses));
}
}
@@ -320,7 +492,7 @@ TEST(MessageEntities, url) {
check_url(".", {});
check_url("http://@google.com", {});
check_url("http://@goog.com", {}); // TODO: server fix
- check_url("http://@@google.com", {"http://@@google.com"});
+ check_url("http://@@google.com", {});
check_url("http://a@google.com", {"http://a@google.com"});
check_url("http://test@google.com", {"http://test@google.com"});
check_url("google.com:᪉᪉᪉᪉᪉", {"google.com"});
@@ -328,7 +500,7 @@ TEST(MessageEntities, url) {
check_url("http://telegram.org", {"http://telegram.org"});
check_url("ftp://telegram.org", {"ftp://telegram.org"});
check_url("ftps://telegram.org", {});
- check_url("sftp://telegram.org", {"sftp://telegram.org"});
+ check_url("sftp://telegram.org", {});
check_url("hTtPs://telegram.org", {"hTtPs://telegram.org"});
check_url("HTTP://telegram.org", {"HTTP://telegram.org"});
check_url("аHTTP://telegram.org", {"HTTP://telegram.org"});
@@ -360,7 +532,7 @@ TEST(MessageEntities, url) {
check_url("http://a.0", {});
check_url("http://a.a", {});
check_url("google.com:1#ab c", {"google.com:1#ab"});
- check_url("google.com:1#", {"google.com:1#"});
+ check_url("google.com:1#", {"google.com:1"});
check_url("google.com:1#1", {"google.com:1#1"});
check_url("google.com:00000001/abs", {"google.com:00000001/abs"});
check_url("google.com:000000065535/abs", {"google.com:000000065535/abs"});
@@ -391,7 +563,8 @@ TEST(MessageEntities, url) {
check_url("http://ÀТеСт.МоСкВач", {"http://ÀТеСт.МоСкВач"});
check_url("ÀÁ.com. ÀÁ.com.", {"ÀÁ.com", "ÀÁ.com"});
check_url("ÀÁ.com,ÀÁ.com.", {"ÀÁ.com", "ÀÁ.com"});
- check_url("teiegram.org", {});
+ check_url("teiegram.org/test", {});
+ check_url("TeiegraM.org/test", {});
check_url("http://test.google.com/?q=abc()}[]def", {"http://test.google.com/?q=abc()"});
check_url("http://test.google.com/?q=abc([{)]}def", {"http://test.google.com/?q=abc([{)]}def"});
check_url("http://test.google.com/?q=abc(){}]def", {"http://test.google.com/?q=abc(){}"});
@@ -402,18 +575,20 @@ TEST(MessageEntities, url) {
check_url("http://google_.com", {});
check_url("http://google._com_", {});
check_url("http://[2001:4860:0:2001::68]/", {}); // TODO
+ check_url("tg://resolve", {});
check_url("test.abd", {});
check_url("/.b/..a @.....@/. a.ba", {"a.ba"});
check_url("bbbbbbbbbbbbbb.@.@", {});
check_url("http://google.com/", {"http://google.com/"});
check_url("http://google.com?", {"http://google.com"});
- check_url("http://google.com#", {"http://google.com#"});
+ check_url("http://google.com#", {"http://google.com"});
+ check_url("http://google.com##", {"http://google.com##"});
check_url("http://google.com/?", {"http://google.com/"});
check_url("https://www.google.com/ab,", {"https://www.google.com/ab"});
check_url("@.", {});
check_url(
"a.b.google.com dfsknnfs gsdfgsg http://códuia.de/ dffdg,\" 12)(cpia.de/())(\" http://гришка.рф/ sdufhdf "
- "http://xn--80afpi2a3c.xn--p1ai/ I have a good time.Thanks, guys!\n\n(hdfughidufhgdis)go#ogle.com гришка.рф "
+ "http://xn--80afpi2a3c.xn--p1ai/ I have a good time.Thanks, guys!\n\n(hdfughidufhgdis) go#ogle.com гришка.рф "
"hsighsdf gi почта.рф\n\n✪df.ws/123 "
"xn--80afpi2a3c.xn--p1ai\n\nhttp://foo.com/blah_blah\nhttp://foo.com/blah_blah/\n(Something like "
"http://foo.com/blah_blah)\nhttp://foo.com/blah_blah_(wikipedi8989a_Вася)\n(Something like "
@@ -469,7 +644,7 @@ TEST(MessageEntities, url) {
"google.com/",
"google.com/#",
"google.com",
- "google.com#"});
+ "google.com"});
check_url("https://t.…", {});
check_url("('http://telegram.org/a-b/?br=ie&lang=en',)", {"http://telegram.org/a-b/?br=ie&lang=en"});
check_url("https://ai.telegram.org/bot%20bot/test-...", {"https://ai.telegram.org/bot%20bot/test-"});
@@ -482,11 +657,11 @@ TEST(MessageEntities, url) {
check_url("https://a.de}bc@c.com", {"https://a.de"}, {"bc@c.com"});
check_url("https://a.de(bc@c.com", {"https://a.de"}, {"bc@c.com"});
check_url("https://a.de)bc@c.com", {"https://a.de"}, {"bc@c.com"});
- check_url("https://a.de\\bc@c.com", {"https://a.de\\bc@c.com"});
+ check_url("https://a.debc@c.com", {"https://a.debc@c.com"});
check_url("https://a.de'bc@c.com", {"https://a.de"}, {"bc@c.com"});
check_url("https://a.de`bc@c.com", {"https://a.de"}, {"bc@c.com"});
- check_url("https://a.bc:de.fg@c.com", {"https://a.bc:de.fg@c.com"});
- check_url("https://a:h.bc:de.fg@c.com", {"https://a:h.bc:de.fg@c.com"});
+ check_url("https://a.bcde.fg@c.com", {"https://a.bcde.fg@c.com"});
+ check_url("https://a:h.bcde.fg@c.com", {"https://a:h.bcde.fg@c.com"});
check_url("https://abc@c.com", {"https://abc@c.com"});
check_url("https://de[bc@c.com", {}, {"bc@c.com"});
check_url("https://de/bc@c.com", {});
@@ -515,8 +690,9 @@ TEST(MessageEntities, url) {
check_url("<http://www.ics.uci.edu/pub/ietf/uri/historical.html#WARNING>",
{"http://www.ics.uci.edu/pub/ietf/uri/historical.html#WARNING"});
check_url("Look :test@example.com", {":test@example.com"}, {}); // TODO {}, {"test@example.com"}
+ check_url("Look mailto:test@example.com", {}, {"test@example.com"});
check_url("http://test.com#a", {"http://test.com#a"});
- check_url("http://test.com#", {"http://test.com#"});
+ check_url("http://test.com#", {"http://test.com"});
check_url("http://test.com?#", {"http://test.com?#"});
check_url("http://test.com/?#", {"http://test.com/?#"});
check_url("https://t.me/abcdef…", {"https://t.me/abcdef"});
@@ -526,4 +702,1210 @@ TEST(MessageEntities, url) {
check_url("https://t…", {});
check_url("👉http://ab.com/cdefgh-1IJ", {"http://ab.com/cdefgh-1IJ"});
check_url("...👉http://ab.com/cdefgh-1IJ", {}); // TODO
+ check_url(".?", {});
+ check_url("http://test―‑@―google―.―com―/―–―‐―/―/―/―?―‑―#―――", {"http://test―‑@―google―.―com―/―–―‐―/―/―/―?―‑―#―――"});
+ check_url("http://google.com/‖", {"http://google.com/"});
+ check_url("a@b@c.com", {}, {});
+ check_url("abc@c.com@d.com", {});
+ check_url("a@b.com:c@1", {}, {"a@b.com"});
+ check_url("test@test.software", {}, {"test@test.software"});
+ check_url("a:b?@gmail.com", {});
+ check_url("a?:b@gmail.com", {});
+ check_url("a#:b@gmail.com", {});
+ check_url("a:b#@gmail.com", {});
+ check_url("a!:b@gmail.com", {"a!:b@gmail.com"});
+ check_url("a:b!@gmail.com", {"a:b!@gmail.com"});
+ check_url("http://test_.com", {});
+ check_url("test_.com", {});
+ check_url("_test.com", {});
+ check_url("_.test.com", {"_.test.com"});
+}
+
+static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntity> entities,
+ const td::string &expected_str,
+ const td::vector<td::MessageEntity> &expected_entities, bool allow_empty = true,
+ bool skip_new_entities = false, bool skip_bot_commands = false,
+ bool for_draft = true) {
+ ASSERT_TRUE(td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, true, for_draft)
+ .is_ok());
+ ASSERT_STREQ(expected_str, str);
+ ASSERT_EQ(expected_entities, entities);
+}
+
+static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntity> entities, bool allow_empty,
+ bool skip_new_entities, bool skip_bot_commands, bool for_draft) {
+ ASSERT_TRUE(td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, true, for_draft)
+ .is_error());
+}
+
+TEST(MessageEntities, fix_formatted_text) {
+ td::string str;
+ td::string fixed_str;
+ for (auto i = 0; i <= 32; i++) {
+ str += static_cast<char>(i);
+ if (i != 13) {
+ if (i != 10) {
+ fixed_str += ' ';
+ } else {
+ fixed_str += str.back();
+ }
+ }
+ }
+
+ check_fix_formatted_text(str, {}, "", {}, true, true, true, true);
+ check_fix_formatted_text(str, {}, "", {}, true, true, false, true);
+ check_fix_formatted_text(str, {}, "", {}, true, false, true, true);
+ check_fix_formatted_text(str, {}, "", {}, true, false, false, true);
+ check_fix_formatted_text(str, {}, "", {}, true, false, false, false);
+ check_fix_formatted_text(str, {}, false, false, false, false);
+ check_fix_formatted_text(str, {}, false, false, false, true);
+
+ check_fix_formatted_text(" aba\n ", {}, " aba\n ", {}, true, true, true, true);
+ check_fix_formatted_text(" aba\n ", {}, "aba", {}, true, true, true, false);
+ check_fix_formatted_text(" \n ", {}, "", {}, true, true, true, true);
+ check_fix_formatted_text(" \n ", {}, "", {}, true, true, true, false);
+ check_fix_formatted_text(" \n ", {}, false, true, true, false);
+
+ str += "a \r\n ";
+ fixed_str += "a \n ";
+
+ for (td::int32 i = 33; i <= 35; i++) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::Pre, 0, i);
+
+ td::vector<td::MessageEntity> fixed_entities = entities;
+ fixed_entities.back().length = i - 1;
+ check_fix_formatted_text(str, entities, fixed_str, fixed_entities, true, false, false, true);
+
+ td::string expected_str = fixed_str.substr(0, 33);
+ fixed_entities.back().length = i == 33 ? 32 : 33;
+ check_fix_formatted_text(str, entities, expected_str, fixed_entities, false, false, false, false);
+ }
+
+ for (td::int32 i = 33; i <= 35; i++) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::Bold, 0, i);
+
+ td::vector<td::MessageEntity> fixed_entities;
+ if (i != 33) {
+ fixed_entities.emplace_back(td::MessageEntity::Type::Bold, 32, i - 33);
+ }
+ check_fix_formatted_text(str, entities, fixed_str, fixed_entities, true, false, false, true);
+
+ td::string expected_str;
+ if (i != 33) {
+ fixed_entities.back().offset = 0;
+ fixed_entities.back().length = 1;
+ }
+ expected_str = "a";
+ check_fix_formatted_text(str, entities, expected_str, fixed_entities, false, false, false, false);
+ }
+
+ str = "👉 👉 ";
+ for (int i = 0; i < 10; i++) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::Bold, i, 1);
+ if (i != 2 && i != 5 && i != 6) {
+ check_fix_formatted_text(str, entities, true, true, true, true);
+ check_fix_formatted_text(str, entities, false, false, false, false);
+ } else {
+ check_fix_formatted_text(str, entities, str, {}, true, true, true, true);
+ check_fix_formatted_text(str, entities, str.substr(0, str.size() - 2), {}, false, false, false, false);
+ }
+ }
+
+ str = " /test @abaca #ORD $ABC telegram.org ";
+ for (auto for_draft : {false, true}) {
+ td::int32 shift = for_draft ? 2 : 0;
+ td::string expected_str = for_draft ? str : str.substr(2, str.size() - 3);
+
+ for (auto skip_new_entities : {false, true}) {
+ for (auto skip_bot_commands : {false, true}) {
+ td::vector<td::MessageEntity> entities;
+ if (!skip_new_entities) {
+ if (!skip_bot_commands) {
+ entities.emplace_back(td::MessageEntity::Type::BotCommand, shift, 5);
+ }
+ entities.emplace_back(td::MessageEntity::Type::Mention, shift + 6, 6);
+ entities.emplace_back(td::MessageEntity::Type::Hashtag, shift + 13, 4);
+ entities.emplace_back(td::MessageEntity::Type::Cashtag, shift + 18, 4);
+ entities.emplace_back(td::MessageEntity::Type::Url, shift + 24, 12);
+ }
+
+ check_fix_formatted_text(str, {}, expected_str, entities, true, skip_new_entities, skip_bot_commands,
+ for_draft);
+ check_fix_formatted_text(str, {}, expected_str, entities, false, skip_new_entities, skip_bot_commands,
+ for_draft);
+ }
+ }
+ }
+
+ str = "aba \r\n caba ";
+ td::UserId user_id(static_cast<td::int64>(1));
+ for (td::int32 length = 1; length <= 3; length++) {
+ for (td::int32 offset = 0; static_cast<size_t>(offset + length) <= str.size(); offset++) {
+ for (auto type : {td::MessageEntity::Type::Bold, td::MessageEntity::Type::Url, td::MessageEntity::Type::TextUrl,
+ td::MessageEntity::Type::MentionName}) {
+ for (auto for_draft : {false, true}) {
+ fixed_str = for_draft ? "aba \n caba " : "aba \n caba";
+ auto fixed_length = offset <= 4 && offset + length >= 5 ? length - 1 : length;
+ auto fixed_offset = offset >= 5 ? offset - 1 : offset;
+ if (static_cast<size_t>(fixed_offset) >= fixed_str.size()) {
+ fixed_length = 0;
+ }
+ while (static_cast<size_t>(fixed_offset + fixed_length) > fixed_str.size()) {
+ fixed_length--;
+ }
+ if (type == td::MessageEntity::Type::Bold || type == td::MessageEntity::Type::Url) {
+ while (fixed_length > 0 && (fixed_str[fixed_offset] == ' ' || fixed_str[fixed_offset] == '\n')) {
+ fixed_offset++;
+ fixed_length--;
+ }
+ }
+
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(type, offset, length);
+ if (type == td::MessageEntity::Type::TextUrl) {
+ entities.back().argument = "t.me";
+ } else if (type == td::MessageEntity::Type::MentionName) {
+ entities.back().user_id = user_id;
+ }
+ td::vector<td::MessageEntity> fixed_entities;
+ if (fixed_length > 0) {
+ for (auto i = 0; i < length; i++) {
+ if (!td::is_space(str[offset + i]) || type == td::MessageEntity::Type::TextUrl ||
+ type == td::MessageEntity::Type::MentionName) {
+ fixed_entities.emplace_back(type, fixed_offset, fixed_length);
+ if (type == td::MessageEntity::Type::TextUrl) {
+ fixed_entities.back().argument = "t.me";
+ } else if (type == td::MessageEntity::Type::MentionName) {
+ fixed_entities.back().user_id = user_id;
+ }
+ break;
+ }
+ }
+ }
+ check_fix_formatted_text(str, entities, fixed_str, fixed_entities, true, false, false, for_draft);
+ }
+ }
+ }
+ }
+
+ str = "aba caba";
+ for (td::int32 length = -10; length <= 10; length++) {
+ for (td::int32 offset = -10; offset <= 10; offset++) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::Bold, offset, length);
+ if (length < 0 || offset < 0 || (length > 0 && static_cast<size_t>(length + offset) > str.size())) {
+ check_fix_formatted_text(str, entities, true, false, false, false);
+ check_fix_formatted_text(str, entities, false, false, false, true);
+ continue;
+ }
+
+ td::vector<td::MessageEntity> fixed_entities;
+ if (length > 0) {
+ if (offset == 3) {
+ if (length >= 2) {
+ fixed_entities.emplace_back(td::MessageEntity::Type::Bold, offset + 1, length - 1);
+ }
+ } else {
+ fixed_entities.emplace_back(td::MessageEntity::Type::Bold, offset, length);
+ }
+ }
+
+ check_fix_formatted_text(str, entities, str, fixed_entities, true, false, false, false);
+ check_fix_formatted_text(str, entities, str, fixed_entities, false, false, false, true);
+ }
+ }
+
+ str = "abadcaba";
+ for (td::int32 length = 1; length <= 7; length++) {
+ for (td::int32 offset = 0; offset <= 8 - length; offset++) {
+ for (td::int32 length2 = 1; length2 <= 7; length2++) {
+ for (td::int32 offset2 = 0; offset2 <= 8 - length2; offset2++) {
+ if (offset != offset2) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::TextUrl, offset, length, "t.me");
+ entities.emplace_back(td::MessageEntity::Type::TextUrl, offset2, length2, "t.me");
+ entities.emplace_back(td::MessageEntity::Type::TextUrl, offset2 + length2, 1);
+ td::vector<td::MessageEntity> fixed_entities = entities;
+ fixed_entities.pop_back();
+ std::sort(fixed_entities.begin(), fixed_entities.end());
+ if (fixed_entities[0].offset + fixed_entities[0].length > fixed_entities[1].offset) {
+ fixed_entities.pop_back();
+ }
+ check_fix_formatted_text(str, entities, str, fixed_entities, false, false, false, false);
+ }
+ }
+ }
+ }
+ }
+
+ for (auto text : {" \n ➡️ ➡️ ➡️ ➡️ \n ", "\n\n\nab cd ef gh "}) {
+ str = text;
+ td::vector<td::MessageEntity> entities;
+ td::vector<td::MessageEntity> fixed_entities;
+
+ auto length = td::narrow_cast<int>(td::utf8_utf16_length(str));
+ for (int i = 0; i < 10; i++) {
+ if ((i + 1) * 3 + 2 <= length) {
+ entities.emplace_back(td::MessageEntity::Type::Bold, (i + 1) * 3, 2);
+ }
+ if ((i + 2) * 3 <= length) {
+ entities.emplace_back(td::MessageEntity::Type::Italic, (i + 1) * 3 + 2, 1);
+ }
+
+ if (i < 4) {
+ fixed_entities.emplace_back(td::MessageEntity::Type::Bold, i * 3, 2);
+ }
+ }
+
+ check_fix_formatted_text(str, entities, td::utf8_utf16_substr(str, 3, 11).str(), fixed_entities, false, false,
+ false, false);
+ }
+
+ for (td::string text : {"\t", "\r", "\n", "\t ", "\r ", "\n "}) {
+ for (auto type : {td::MessageEntity::Type::Bold, td::MessageEntity::Type::TextUrl}) {
+ check_fix_formatted_text(text, {{type, 0, 1, "http://telegram.org/"}}, "", {}, true, false, false, true);
+ }
+ }
+ check_fix_formatted_text("\r ", {{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Underline, 0, 1}},
+ "", {}, true, false, false, true);
+ check_fix_formatted_text("a \r", {{td::MessageEntity::Type::Bold, 0, 3}, {td::MessageEntity::Type::Underline, 2, 1}},
+ "a ", {{td::MessageEntity::Type::Bold, 0, 2}}, true, false, false, true);
+ check_fix_formatted_text("a \r ", {{td::MessageEntity::Type::Bold, 0, 4}, {td::MessageEntity::Type::Underline, 2, 1}},
+ "a ", {{td::MessageEntity::Type::Bold, 0, 2}}, true, false, false, true);
+ check_fix_formatted_text(
+ "a \r b", {{td::MessageEntity::Type::Bold, 0, 5}, {td::MessageEntity::Type::Underline, 2, 1}}, "a b",
+ {{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Bold, 3, 1}}, true, false, false, true);
+
+ check_fix_formatted_text("a\rbc\r",
+ {{td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 0, 2},
+ {td::MessageEntity::Type::Italic, 3, 2},
+ {td::MessageEntity::Type::Bold, 3, 1}},
+ "abc",
+ {{td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Italic, 2, 1}});
+ check_fix_formatted_text("a ", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Bold, 0, 1}}, "a",
+ {{td::MessageEntity::Type::Bold, 0, 1}, {td::MessageEntity::Type::Italic, 0, 1}}, false,
+ false, false, false);
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 1, 1}, {td::MessageEntity::Type::Italic, 0, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 2}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 1, 1}, {td::MessageEntity::Type::Italic, 1, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 1, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Italic, 1, 2}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 3}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Italic, 2, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 3}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 1}, {td::MessageEntity::Type::Italic, 2, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 1}, {td::MessageEntity::Type::Italic, 2, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Bold, 1, 2}},
+ "abc",
+ {{td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 1, 2},
+ {td::MessageEntity::Type::Italic, 1, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 1}, {td::MessageEntity::Type::Bold, 2, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 1}, {td::MessageEntity::Type::Bold, 2, 1}});
+ check_fix_formatted_text("@tests @tests", {{td::MessageEntity::Type::Italic, 0, 13}}, "@tests @tests",
+ {{td::MessageEntity::Type::Mention, 0, 6},
+ {td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Mention, 7, 6},
+ {td::MessageEntity::Type::Italic, 7, 6}});
+
+ // __a~b~__
+ check_fix_formatted_text(
+ "ab", {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Strikethrough, 1, 1}}, "ab",
+ {{td::MessageEntity::Type::Underline, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}});
+ check_fix_formatted_text("ab",
+ {{td::MessageEntity::Type::Underline, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}},
+ "ab",
+ {{td::MessageEntity::Type::Underline, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}});
+ check_fix_formatted_text(
+ "ab", {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Underline, 1, 1}}, "ab",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}});
+ check_fix_formatted_text("ab",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1},
+ {td::MessageEntity::Type::Underline, 1, 1}},
+ "ab",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}});
+
+ // __||a||b__
+ check_fix_formatted_text("ab", {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Spoiler, 0, 1}},
+ "ab",
+ {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Spoiler, 0, 1}});
+ check_fix_formatted_text("ab",
+ {{td::MessageEntity::Type::Underline, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Spoiler, 0, 1}},
+ "ab",
+ {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Spoiler, 0, 1}});
+
+ // _*a*_\r_*b*_
+ check_fix_formatted_text("a\rb",
+ {{td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Italic, 2, 1}},
+ "ab", {{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Italic, 0, 2}});
+ check_fix_formatted_text("a\nb",
+ {{td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Italic, 2, 1}},
+ "a\nb",
+ {{td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Italic, 2, 1}});
+
+ // ||`a`||
+ check_fix_formatted_text("a", {{td::MessageEntity::Type::Pre, 0, 1}, {td::MessageEntity::Type::Spoiler, 0, 1}}, "a",
+ {{td::MessageEntity::Type::Pre, 0, 1}});
+ check_fix_formatted_text("a", {{td::MessageEntity::Type::Spoiler, 0, 1}, {td::MessageEntity::Type::Pre, 0, 1}}, "a",
+ {{td::MessageEntity::Type::Pre, 0, 1}});
+
+ check_fix_formatted_text("abc",
+ {{td::MessageEntity::Type::Pre, 0, 3}, {td::MessageEntity::Type::Strikethrough, 1, 1}},
+ "abc", {{td::MessageEntity::Type::Pre, 0, 3}});
+ check_fix_formatted_text(
+ "abc", {{td::MessageEntity::Type::Pre, 1, 1}, {td::MessageEntity::Type::Strikethrough, 0, 3}}, "abc",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1},
+ {td::MessageEntity::Type::Pre, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 2, 1}});
+ check_fix_formatted_text(
+ "abc", {{td::MessageEntity::Type::Pre, 1, 1}, {td::MessageEntity::Type::Strikethrough, 1, 2}}, "abc",
+ {{td::MessageEntity::Type::Pre, 1, 1}, {td::MessageEntity::Type::Strikethrough, 2, 1}});
+ check_fix_formatted_text(
+ "abc", {{td::MessageEntity::Type::Pre, 1, 1}, {td::MessageEntity::Type::Strikethrough, 0, 2}}, "abc",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Pre, 0, 3}, {td::MessageEntity::Type::BlockQuote, 1, 1}},
+ "abc", {{td::MessageEntity::Type::BlockQuote, 1, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::BlockQuote, 0, 3}, {td::MessageEntity::Type::Pre, 1, 1}},
+ "abc", {{td::MessageEntity::Type::BlockQuote, 0, 3}, {td::MessageEntity::Type::Pre, 1, 1}});
+
+ check_fix_formatted_text("example.com", {}, "example.com", {{td::MessageEntity::Type::Url, 0, 11}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::Pre, 0, 3}}, "example.com",
+ {{td::MessageEntity::Type::Pre, 0, 3}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::BlockQuote, 0, 3}}, "example.com",
+ {{td::MessageEntity::Type::BlockQuote, 0, 3}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::BlockQuote, 0, 11}}, "example.com",
+ {{td::MessageEntity::Type::BlockQuote, 0, 11}, {td::MessageEntity::Type::Url, 0, 11}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::Italic, 0, 11}}, "example.com",
+ {{td::MessageEntity::Type::Url, 0, 11}, {td::MessageEntity::Type::Italic, 0, 11}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::Italic, 0, 3}}, "example.com",
+ {{td::MessageEntity::Type::Url, 0, 11}, {td::MessageEntity::Type::Italic, 0, 3}});
+ check_fix_formatted_text("example.com a", {{td::MessageEntity::Type::Italic, 0, 13}}, "example.com a",
+ {{td::MessageEntity::Type::Url, 0, 11},
+ {td::MessageEntity::Type::Italic, 0, 11},
+ {td::MessageEntity::Type::Italic, 12, 1}});
+ check_fix_formatted_text("a example.com", {{td::MessageEntity::Type::Italic, 0, 13}}, "a example.com",
+ {{td::MessageEntity::Type::Italic, 0, 2},
+ {td::MessageEntity::Type::Url, 2, 11},
+ {td::MessageEntity::Type::Italic, 2, 11}});
+
+ for (size_t test_n = 0; test_n < 100000; test_n++) {
+ bool is_url = td::Random::fast_bool();
+ td::int32 url_offset = 0;
+ td::int32 url_end = 0;
+ if (is_url) {
+ str = td::string(td::Random::fast(1, 5), 'a') + ":example.com:" + td::string(td::Random::fast(1, 5), 'a');
+ url_offset = static_cast<td::int32>(str.find('e'));
+ url_end = url_offset + 11;
+ } else {
+ str = td::string(td::Random::fast(1, 20), 'a');
+ }
+
+ auto n = td::Random::fast(1, 20);
+ td::vector<td::MessageEntity> entities;
+ for (int j = 0; j < n; j++) {
+ td::int32 type = td::Random::fast(4, 16);
+ td::int32 offset = td::Random::fast(0, static_cast<int>(str.size()) - 1);
+ auto max_length = static_cast<int>(str.size() - offset);
+ if ((test_n & 1) != 0 && max_length > 4) {
+ max_length = 4;
+ }
+ td::int32 length = td::Random::fast(0, max_length);
+ entities.emplace_back(static_cast<td::MessageEntity::Type>(type), offset, length);
+ }
+
+ auto get_type_mask = [](std::size_t length, const td::vector<td::MessageEntity> &entities) {
+ td::vector<td::int32> result(length);
+ for (auto &entity : entities) {
+ for (auto pos = 0; pos < entity.length; pos++) {
+ result[entity.offset + pos] |= (1 << static_cast<td::int32>(entity.type));
+ }
+ }
+ return result;
+ };
+ auto old_type_mask = get_type_mask(str.size(), entities);
+ ASSERT_TRUE(td::fix_formatted_text(str, entities, false, false, true, true, false).is_ok());
+ auto new_type_mask = get_type_mask(str.size(), entities);
+ auto splittable_mask = (1 << 5) | (1 << 6) | (1 << 14) | (1 << 15);
+ auto pre_mask = (1 << 7) | (1 << 8) | (1 << 9);
+ for (std::size_t pos = 0; pos < str.size(); pos++) {
+ if ((new_type_mask[pos] & pre_mask) != 0) {
+ ASSERT_EQ(0, new_type_mask[pos] & splittable_mask);
+ } else {
+ ASSERT_EQ(old_type_mask[pos] & splittable_mask, new_type_mask[pos] & splittable_mask);
+ }
+ }
+ bool keep_url = is_url;
+ td::MessageEntity url_entity(td::MessageEntity::Type::Url, url_offset, url_end - url_offset);
+ for (auto &entity : entities) {
+ if (entity == url_entity) {
+ continue;
+ }
+ td::int32 offset = entity.offset;
+ td::int32 end = offset + entity.length;
+
+ if (keep_url && ((1 << static_cast<td::int32>(entity.type)) & splittable_mask) == 0 &&
+ !(end <= url_offset || url_end <= offset)) {
+ keep_url = (entity.type == td::MessageEntity::Type::BlockQuote && offset <= url_offset && url_end <= end);
+ }
+ }
+ ASSERT_EQ(keep_url, std::count(entities.begin(), entities.end(), url_entity) == 1);
+
+ for (size_t i = 0; i < entities.size(); i++) {
+ auto type_mask = 1 << static_cast<td::int32>(entities[i].type);
+ for (size_t j = i + 1; j < entities.size(); j++) {
+ // sorted
+ ASSERT_TRUE(entities[j].offset > entities[i].offset ||
+ (entities[j].offset == entities[i].offset && entities[j].length <= entities[i].length));
+
+ // not intersecting
+ ASSERT_TRUE(entities[j].offset >= entities[i].offset + entities[i].length ||
+ entities[j].offset + entities[j].length <= entities[i].offset + entities[i].length);
+
+ if (entities[j].offset < entities[i].offset + entities[i].length) { // if nested
+ // types are different
+ ASSERT_TRUE(entities[j].type != entities[i].type);
+
+ // pre can't contain other entities
+ ASSERT_TRUE((type_mask & pre_mask) == 0);
+
+ if ((type_mask & splittable_mask) == 0 && entities[i].type != td::MessageEntity::Type::BlockQuote) {
+ // continuous entities can contain only splittable entities
+ ASSERT_TRUE(((1 << static_cast<td::int32>(entities[j].type)) & splittable_mask) != 0);
+ }
+ }
+ }
+ }
+ }
+
+ check_fix_formatted_text(
+ "\xe2\x80\x8f\xe2\x80\x8f \xe2\x80\x8e\xe2\x80\x8e\xe2\x80\x8e\xe2\x80\x8c \xe2\x80\x8f\xe2\x80\x8e "
+ "\xe2\x80\x8f",
+ {},
+ "\xe2\x80\x8c\xe2\x80\x8f \xe2\x80\x8c\xe2\x80\x8c\xe2\x80\x8e\xe2\x80\x8c \xe2\x80\x8c\xe2\x80\x8e "
+ "\xe2\x80\x8f",
+ {});
+}
+
+static void check_parse_html(td::string text, const td::string &result, const td::vector<td::MessageEntity> &entities) {
+ auto r_entities = td::parse_html(text);
+ ASSERT_TRUE(r_entities.is_ok());
+ ASSERT_EQ(entities, r_entities.ok());
+ ASSERT_STREQ(result, text);
+}
+
+static void check_parse_html(td::string text, td::Slice error_message) {
+ auto r_entities = td::parse_html(text);
+ ASSERT_TRUE(r_entities.is_error());
+ ASSERT_EQ(400, r_entities.error().code());
+ ASSERT_STREQ(error_message, r_entities.error().message());
+}
+
+TEST(MessageEntities, parse_html) {
+ td::string invalid_surrogate_pair_error_message =
+ "Text contains invalid Unicode characters after decoding HTML entities, check for unmatched surrogate code units";
+ check_parse_html("&#57311;", invalid_surrogate_pair_error_message);
+ check_parse_html("&#xDFDF;", invalid_surrogate_pair_error_message);
+ check_parse_html("&#xDFDF", invalid_surrogate_pair_error_message);
+ check_parse_html("🏟 🏟&lt;<abacaba", "Unclosed start tag at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<abac aba>", "Unsupported start tag \"abac\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<abac>", "Unsupported start tag \"abac\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i =aba>", "Empty attribute name in the tag \"i\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i aba>",
+ "Expected equal sign in declaration of an attribute of the tag \"i\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i aba = ", "Unclosed start tag \"i\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i aba = 190azAz-.,", "Unexpected end of name token at byte offset 27");
+ check_parse_html("🏟 🏟&lt;<i aba = \"&lt;&gt;&quot;>", "Unclosed start tag at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i aba = \'&lt;&gt;&quot;>", "Unclosed start tag at byte offset 13");
+ check_parse_html("🏟 🏟&lt;</", "Unexpected end tag at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<b></b></", "Unexpected end tag at byte offset 20");
+ check_parse_html("🏟 🏟&lt;<i>a</i ", "Unclosed end tag at byte offset 17");
+ check_parse_html("🏟 🏟&lt;<i>a</em >",
+ "Unmatched end tag at byte offset 17, expected \"</i>\", found \"</em>\"");
+
+ check_parse_html("", "", {});
+ check_parse_html("➡️ ➡️", "➡️ ➡️", {});
+ check_parse_html("&lt;&gt;&amp;&quot;&laquo;&raquo;&#12345678;", "<>&\"&laquo;&raquo;&#12345678;", {});
+ check_parse_html("➡️ ➡️<i>➡️ ➡️</i>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}});
+ check_parse_html("➡️ ➡️<em>➡️ ➡️</em>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}});
+ check_parse_html("➡️ ➡️<b>➡️ ➡️</b>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Bold, 5, 5}});
+ check_parse_html("➡️ ➡️<strong>➡️ ➡️</strong>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Bold, 5, 5}});
+ check_parse_html("➡️ ➡️<u>➡️ ➡️</u>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Underline, 5, 5}});
+ check_parse_html("➡️ ➡️<ins>➡️ ➡️</ins>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Underline, 5, 5}});
+ check_parse_html("➡️ ➡️<s>➡️ ➡️</s>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Strikethrough, 5, 5}});
+ check_parse_html("➡️ ➡️<strike>➡️ ➡️</strike>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Strikethrough, 5, 5}});
+ check_parse_html("➡️ ➡️<del>➡️ ➡️</del>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Strikethrough, 5, 5}});
+ check_parse_html("➡️ ➡️<i>➡️ ➡️</i><b>➡️ ➡️</b>", "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_html("🏟 🏟<i>🏟 &lt🏟</i>", "🏟 🏟🏟 <🏟", {{td::MessageEntity::Type::Italic, 5, 6}});
+ check_parse_html("🏟 🏟<i>🏟 &gt;<b aba = caba>&lt🏟</b></i>", "🏟 🏟🏟 ><🏟",
+ {{td::MessageEntity::Type::Italic, 5, 7}, {td::MessageEntity::Type::Bold, 9, 3}});
+ check_parse_html("🏟 🏟&lt;<i aba = 190azAz-. >a</i>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i aba = 190azAz-.>a</i>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i aba = \"&lt;&gt;&quot;\">a</i>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i aba = '&lt;&gt;&quot;'>a</i>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i aba = '&lt;&gt;&quot;'>a</>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i>🏟 🏟&lt;</>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Italic, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<i>a</ >", "🏟 🏟<a", {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i>a</i >", "🏟 🏟<a", {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<b></b>", "🏟 🏟<", {});
+ check_parse_html("<i>\t</i>", "\t", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_html("<i>\r</i>", "\r", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_html("<i>\n</i>", "\n", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_html("➡️ ➡️<span class = \"tg-spoiler\">➡️ ➡️</span><b>➡️ ➡️</b>",
+ "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Spoiler, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_html("🏟 🏟<span class=\"tg-spoiler\">🏟 &lt🏟</span>", "🏟 🏟🏟 <🏟",
+ {{td::MessageEntity::Type::Spoiler, 5, 6}});
+ check_parse_html("🏟 🏟<span class=\"tg-spoiler\">🏟 &gt;<b aba = caba>&lt🏟</b></span>",
+ "🏟 🏟🏟 ><🏟",
+ {{td::MessageEntity::Type::Spoiler, 5, 7}, {td::MessageEntity::Type::Bold, 9, 3}});
+ check_parse_html("➡️ ➡️<tg-spoiler>➡️ ➡️</tg-spoiler><b>➡️ ➡️</b>",
+ "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Spoiler, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_html("🏟 🏟<tg-spoiler>🏟 &lt🏟</tg-spoiler>", "🏟 🏟🏟 <🏟",
+ {{td::MessageEntity::Type::Spoiler, 5, 6}});
+ check_parse_html("🏟 🏟<tg-spoiler>🏟 &gt;<b aba = caba>&lt🏟</b></tg-spoiler>", "🏟 🏟🏟 ><🏟",
+ {{td::MessageEntity::Type::Spoiler, 5, 7}, {td::MessageEntity::Type::Bold, 9, 3}});
+ check_parse_html("<a href=telegram.org>\t</a>", "\t",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href=telegram.org>\r</a>", "\r",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href=telegram.org>\n</a>", "\n",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<code><i><b> </b></i></code><i><b><code> </code></b></i>", " ",
+ {{td::MessageEntity::Type::Code, 0, 1},
+ {td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Code, 1, 1},
+ {td::MessageEntity::Type::Bold, 1, 1},
+ {td::MessageEntity::Type::Italic, 1, 1}});
+ check_parse_html("<i><b> </b> <code> </code></i>", " ",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Code, 2, 1}});
+ check_parse_html("<a href=telegram.org> </a>", " ",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href =\"telegram.org\" > </a>", " ",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href= 'telegram.org' > </a>", " ",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href= 'telegram.org?&lt;' > </a>", " ",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/?<"}});
+ check_parse_html("<a> </a>", " ", {});
+ check_parse_html("<a>telegram.org </a>", "telegram.org ", {});
+ check_parse_html("<a>telegram.org</a>", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "http://telegram.org/"}});
+ check_parse_html("<a>https://telegram.org/asdsa?asdasdwe#12e3we</a>", "https://telegram.org/asdsa?asdasdwe#12e3we",
+ {{td::MessageEntity::Type::TextUrl, 0, 42, "https://telegram.org/asdsa?asdasdwe#12e3we"}});
+ check_parse_html("🏟 🏟&lt;<pre >🏟 🏟&lt;</>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Pre, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<code >🏟 🏟&lt;</>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Code, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<pre><code>🏟 🏟&lt;</code></>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Pre, 6, 6}, {td::MessageEntity::Type::Code, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<pre><code class=\"language-\">🏟 🏟&lt;</code></>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Pre, 6, 6}, {td::MessageEntity::Type::Code, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<pre><code class=\"language-fift\">🏟 🏟&lt;</></>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::PreCode, 6, 6, "fift"}});
+ check_parse_html("🏟 🏟&lt;<code class=\"language-fift\"><pre>🏟 🏟&lt;</></>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::PreCode, 6, 6, "fift"}});
+ check_parse_html("🏟 🏟&lt;<pre><code class=\"language-fift\">🏟 🏟&lt;</> </>", "🏟 🏟<🏟 🏟< ",
+ {{td::MessageEntity::Type::Pre, 6, 7}, {td::MessageEntity::Type::Code, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<pre> <code class=\"language-fift\">🏟 🏟&lt;</></>", "🏟 🏟< 🏟 🏟<",
+ {{td::MessageEntity::Type::Pre, 6, 7}, {td::MessageEntity::Type::Code, 7, 6}});
+ check_parse_html("➡️ ➡️<tg-emoji emoji-id = \"12345\">➡️ ➡️</tg-emoji><b>➡️ ➡️</b>",
+ "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::CustomEmoji, 5, 5, td::CustomEmojiId(static_cast<td::int64>(12345))},
+ {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_html("🏟 🏟<tg-emoji emoji-id=\"54321\">🏟 &lt🏟</tg-emoji>", "🏟 🏟🏟 <🏟",
+ {{td::MessageEntity::Type::CustomEmoji, 5, 6, td::CustomEmojiId(static_cast<td::int64>(54321))}});
+ check_parse_html("🏟 🏟<b aba = caba><tg-emoji emoji-id=\"1\">🏟</tg-emoji>1</b>", "🏟 🏟🏟1",
+ {{td::MessageEntity::Type::Bold, 5, 3},
+ {td::MessageEntity::Type::CustomEmoji, 5, 2, td::CustomEmojiId(static_cast<td::int64>(1))}});
+}
+
+static void check_parse_markdown(td::string text, const td::string &result,
+ const td::vector<td::MessageEntity> &entities) {
+ auto r_entities = td::parse_markdown_v2(text);
+ ASSERT_TRUE(r_entities.is_ok());
+ ASSERT_EQ(entities, r_entities.ok());
+ ASSERT_STREQ(result, text);
+}
+
+static void check_parse_markdown(td::string text, td::Slice error_message) {
+ auto r_entities = td::parse_markdown_v2(text);
+ ASSERT_TRUE(r_entities.is_error());
+ ASSERT_EQ(400, r_entities.error().code());
+ ASSERT_STREQ(error_message, r_entities.error().message());
+}
+
+TEST(MessageEntities, parse_markdown) {
+ td::Slice reserved_characters("]()>#+-=|{}.!");
+ td::Slice begin_characters("_*[~`");
+ for (char c = 1; c < 126; c++) {
+ if (begin_characters.find(c) != td::Slice::npos) {
+ continue;
+ }
+
+ td::string text(1, c);
+ if (reserved_characters.find(c) == td::Slice::npos) {
+ check_parse_markdown(text, text, {});
+ } else {
+ check_parse_markdown(
+ text, PSLICE() << "Character '" << c << "' is reserved and must be escaped with the preceding '\\'");
+
+ td::string escaped_text = "\\" + text;
+ check_parse_markdown(escaped_text, text, {});
+ }
+ }
+
+ check_parse_markdown("🏟 🏟_abacaba", "Can't find end of Italic entity at byte offset 9");
+ check_parse_markdown("🏟 🏟_abac * asd ", "Can't find end of Bold entity at byte offset 15");
+ check_parse_markdown("🏟 🏟_abac * asd _", "Can't find end of Italic entity at byte offset 21");
+ check_parse_markdown("🏟 🏟`", "Can't find end of Code entity at byte offset 9");
+ check_parse_markdown("🏟 🏟```", "Can't find end of Pre entity at byte offset 9");
+ check_parse_markdown("🏟 🏟```a", "Can't find end of Pre entity at byte offset 9");
+ check_parse_markdown("🏟 🏟```a ", "Can't find end of PreCode entity at byte offset 9");
+ check_parse_markdown("🏟 🏟__🏟 🏟_", "Can't find end of Italic entity at byte offset 20");
+ check_parse_markdown("🏟 🏟_🏟 🏟__", "Can't find end of Underline entity at byte offset 19");
+ check_parse_markdown("🏟 🏟```🏟 🏟`", "Can't find end of Code entity at byte offset 21");
+ check_parse_markdown("🏟 🏟```🏟 🏟_", "Can't find end of PreCode entity at byte offset 9");
+ check_parse_markdown("🏟 🏟```🏟 🏟\\`", "Can't find end of PreCode entity at byte offset 9");
+ check_parse_markdown("[telegram\\.org](asd\\)", "Can't find end of a URL at byte offset 16");
+ check_parse_markdown("[telegram\\.org](", "Can't find end of a URL at byte offset 16");
+ check_parse_markdown("[telegram\\.org](asd", "Can't find end of a URL at byte offset 16");
+ check_parse_markdown("🏟 🏟__🏟 _🏟___", "Can't find end of Italic entity at byte offset 23");
+ check_parse_markdown("🏟 🏟__", "Can't find end of Underline entity at byte offset 9");
+ check_parse_markdown("🏟 🏟||test\\|", "Can't find end of Spoiler entity at byte offset 9");
+ check_parse_markdown("🏟 🏟!", "Character '!' is reserved and must be escaped with the preceding '\\'");
+ check_parse_markdown("🏟 🏟![", "Can't find end of CustomEmoji entity at byte offset 9");
+ check_parse_markdown("🏟 🏟![👍", "Can't find end of CustomEmoji entity at byte offset 9");
+ check_parse_markdown("🏟 🏟![👍]", "Custom emoji entity must contain a tg://emoji URL");
+ check_parse_markdown("🏟 🏟![👍](tg://emoji?id=1234", "Can't find end of a custom emoji URL at byte offset 17");
+ check_parse_markdown("🏟 🏟![👍](t://emoji?id=1234)", "Custom emoji URL must have scheme tg");
+ check_parse_markdown("🏟 🏟![👍](tg:emojis?id=1234)", "Custom emoji URL must have host \"emoji\"");
+ check_parse_markdown("🏟 🏟![👍](tg://emoji#test)", "Custom emoji URL must have an emoji identifier");
+ check_parse_markdown("🏟 🏟![👍](tg://emoji?test=1#&id=25)", "Custom emoji URL must have an emoji identifier");
+ check_parse_markdown("🏟 🏟![👍](tg://emoji?test=1231&id=025)", "Invalid custom emoji identifier specified");
+
+ check_parse_markdown("", "", {});
+ check_parse_markdown("\\\\", "\\", {});
+ check_parse_markdown("\\\\\\", "\\\\", {});
+ check_parse_markdown("\\\\\\\\\\_\\*\\`", "\\\\_*`", {});
+ check_parse_markdown("➡️ ➡️", "➡️ ➡️", {});
+ check_parse_markdown("🏟 🏟``", "🏟 🏟", {});
+ check_parse_markdown("🏟 🏟_abac \\* asd _", "🏟 🏟abac * asd ", {{td::MessageEntity::Type::Italic, 5, 11}});
+ check_parse_markdown("🏟 \\.🏟_🏟\\. 🏟_", "🏟 .🏟🏟. 🏟", {{td::MessageEntity::Type::Italic, 6, 6}});
+ check_parse_markdown("\\\\\\a\\b\\c\\d\\e\\f\\1\\2\\3\\4\\➡️\\", "\\abcdef1234\\➡️\\", {});
+ check_parse_markdown("➡️ ➡️_➡️ ➡️_", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}});
+ check_parse_markdown("➡️ ➡️_➡️ ➡️_*➡️ ➡️*", "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_markdown("🏟 🏟_🏟 \\.🏟_", "🏟 🏟🏟 .🏟", {{td::MessageEntity::Type::Italic, 5, 6}});
+ check_parse_markdown("🏟 🏟_🏟 *🏟*_", "🏟 🏟🏟 🏟",
+ {{td::MessageEntity::Type::Italic, 5, 5}, {td::MessageEntity::Type::Bold, 8, 2}});
+ check_parse_markdown("🏟 🏟_🏟 __🏟___", "🏟 🏟🏟 🏟",
+ {{td::MessageEntity::Type::Italic, 5, 5}, {td::MessageEntity::Type::Underline, 8, 2}});
+ check_parse_markdown("🏟 🏟__🏟 _🏟_ __", "🏟 🏟🏟 🏟 ",
+ {{td::MessageEntity::Type::Underline, 5, 6}, {td::MessageEntity::Type::Italic, 8, 2}});
+ check_parse_markdown("🏟 🏟__🏟 _🏟_\\___", "🏟 🏟🏟 🏟_",
+ {{td::MessageEntity::Type::Underline, 5, 6}, {td::MessageEntity::Type::Italic, 8, 2}});
+ check_parse_markdown("🏟 🏟`🏟 🏟```", "🏟 🏟🏟 🏟", {{td::MessageEntity::Type::Code, 5, 5}});
+ check_parse_markdown("🏟 🏟```🏟 🏟```", "🏟 🏟 🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 3, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\n🏟```", "🏟 🏟🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 2, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\r🏟```", "🏟 🏟🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 2, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\n\r🏟```", "🏟 🏟🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 2, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\r\n🏟```", "🏟 🏟🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 2, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\n\n🏟```", "🏟 🏟\n🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 3, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\r\r🏟```", "🏟 🏟\r🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 3, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟 \\\\\\`🏟```", "🏟 🏟 \\`🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 5, "🏟"}});
+ check_parse_markdown("🏟 🏟**", "🏟 🏟", {});
+ check_parse_markdown("||test||", "test", {{td::MessageEntity::Type::Spoiler, 0, 4}});
+ check_parse_markdown("🏟 🏟``", "🏟 🏟", {});
+ check_parse_markdown("🏟 🏟``````", "🏟 🏟", {});
+ check_parse_markdown("🏟 🏟____", "🏟 🏟", {});
+ check_parse_markdown("`_* *_`__*` `*__", "_* *_ ",
+ {{td::MessageEntity::Type::Code, 0, 5},
+ {td::MessageEntity::Type::Code, 5, 1},
+ {td::MessageEntity::Type::Bold, 5, 1},
+ {td::MessageEntity::Type::Underline, 5, 1}});
+ check_parse_markdown("_* * ` `_", " ",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Code, 2, 1}});
+ check_parse_markdown("[](telegram.org)", "", {});
+ check_parse_markdown("[ ](telegram.org)", " ", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_markdown("[ ](as)", " ", {});
+ check_parse_markdown("[telegram\\.org]", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "http://telegram.org/"}});
+ check_parse_markdown("[telegram\\.org]a", "telegram.orga",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "http://telegram.org/"}});
+ check_parse_markdown("[telegram\\.org](telegram.dog)", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "http://telegram.dog/"}});
+ check_parse_markdown("[telegram\\.org](https://telegram.dog?)", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "https://telegram.dog/?"}});
+ check_parse_markdown("[telegram\\.org](https://telegram.dog?\\\\\\()", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "https://telegram.dog/?\\("}});
+ check_parse_markdown("[telegram\\.org]()", "telegram.org", {});
+ check_parse_markdown("[telegram\\.org](asdasd)", "telegram.org", {});
+ check_parse_markdown("[telegram\\.org](tg:user?id=123456)", "telegram.org",
+ {{0, 12, td::UserId(static_cast<td::int64>(123456))}});
+ check_parse_markdown("🏟 🏟![👍](TG://EMoJI/?test=1231&id=25#id=32)a", "🏟 🏟👍a",
+ {{td::MessageEntity::Type::CustomEmoji, 5, 2, td::CustomEmojiId(static_cast<td::int64>(25))}});
+}
+
+static void check_parse_markdown_v3(td::string text, td::vector<td::MessageEntity> entities,
+ const td::string &result_text, const td::vector<td::MessageEntity> &result_entities,
+ bool fix = false) {
+ auto parsed_text = td::parse_markdown_v3({std::move(text), std::move(entities)});
+ if (fix) {
+ ASSERT_TRUE(td::fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).is_ok());
+ }
+ ASSERT_STREQ(result_text, parsed_text.text);
+ ASSERT_EQ(result_entities, parsed_text.entities);
+ if (fix) {
+ auto markdown_text = td::get_markdown_v3(parsed_text);
+ ASSERT_TRUE(parsed_text == markdown_text || parsed_text == td::parse_markdown_v3(markdown_text));
+ }
+}
+
+static void check_parse_markdown_v3(td::string text, const td::string &result_text,
+ const td::vector<td::MessageEntity> &result_entities, bool fix = false) {
+ check_parse_markdown_v3(std::move(text), td::vector<td::MessageEntity>(), result_text, result_entities, fix);
+}
+
+TEST(MessageEntities, parse_markdown_v3) {
+ check_parse_markdown_v3("🏟````🏟``🏟`aba🏟```c🏟`aba🏟 daba🏟```c🏟`aba🏟```🏟 `🏟``🏟```",
+ "🏟````🏟``🏟aba🏟```c🏟aba🏟 daba🏟c🏟`aba🏟🏟 `🏟``🏟```",
+ {{td::MessageEntity::Type::Code, 12, 11}, {td::MessageEntity::Type::Pre, 35, 9}});
+ check_parse_markdown_v3(
+ "🏟````🏟``🏟`aba🏟```c🏟`aba🏟 daba🏟```c🏟`aba🏟🏟```🏟 `🏟``🏟```",
+ {{td::MessageEntity::Type::Italic, 12, 1},
+ {td::MessageEntity::Type::Italic, 44, 1},
+ {td::MessageEntity::Type::Bold, 45, 1},
+ {td::MessageEntity::Type::Bold, 49, 2}},
+ "🏟````🏟``🏟`aba🏟c🏟`aba🏟 daba🏟c🏟`aba🏟🏟🏟 `🏟``🏟",
+ {{td::MessageEntity::Type::Italic, 12, 1},
+ {td::MessageEntity::Type::Pre, 18, 16},
+ {td::MessageEntity::Type::Italic, 38, 1},
+ {td::MessageEntity::Type::Bold, 39, 1},
+ {td::MessageEntity::Type::Bold, 43, 2},
+ {td::MessageEntity::Type::Pre, 45, 10}});
+ check_parse_markdown_v3("` `", " ", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_parse_markdown_v3("`\n`", "\n", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_parse_markdown_v3("` `a", " a", {{td::MessageEntity::Type::Code, 0, 1}}, true);
+ check_parse_markdown_v3("`\n`a", "\na", {{td::MessageEntity::Type::Code, 0, 1}}, true);
+ check_parse_markdown_v3("``", "``", {});
+ check_parse_markdown_v3("`a````b```", "`a````b```", {});
+ check_parse_markdown_v3("ab", {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}}, "ab",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}});
+
+ check_parse_markdown_v3("[a](b[c](t.me)", "[a](b[c](t.me)", {});
+ check_parse_markdown_v3("[](t.me)", "[](t.me)", {});
+ check_parse_markdown_v3("[ ](t.me)", " ", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}});
+ check_parse_markdown_v3("[ ](t.me)", "", {}, true);
+ check_parse_markdown_v3("[ ](t.me)a", " a", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}}, true);
+ check_parse_markdown_v3(
+ "[ ](t.me) [ ](t.me)",
+ {{td::MessageEntity::Type::TextUrl, 8, 1, "http://t.me/"}, {10, 1, td::UserId(static_cast<td::int64>(1))}},
+ "[ ](t.me) [ ](t.me)",
+ {{td::MessageEntity::Type::TextUrl, 8, 1, "http://t.me/"}, {10, 1, td::UserId(static_cast<td::int64>(1))}});
+ check_parse_markdown_v3("[\n](t.me)", "\n", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}});
+ check_parse_markdown_v3("[\n](t.me)a", "\na", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}}, true);
+ check_parse_markdown_v3("asd[abcd](google.com)", {{td::MessageEntity::Type::Italic, 0, 5}}, "asdabcd",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::TextUrl, 3, 4, "http://google.com/"},
+ {td::MessageEntity::Type::Italic, 3, 1}});
+ check_parse_markdown_v3("asd[abcd](google.com)efg[hi](https://t.me?t=1#h)e",
+ {{td::MessageEntity::Type::Italic, 0, 5}, {td::MessageEntity::Type::Italic, 18, 31}},
+ "asdabcdefghie",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::TextUrl, 3, 4, "http://google.com/"},
+ {td::MessageEntity::Type::Italic, 3, 1},
+ {td::MessageEntity::Type::Italic, 7, 3},
+ {td::MessageEntity::Type::TextUrl, 10, 2, "https://t.me/?t=1#h"},
+ {td::MessageEntity::Type::Italic, 10, 2},
+ {td::MessageEntity::Type::Italic, 12, 1}});
+ check_parse_markdown_v3(
+ "🏟🏟🏟[🏟🏟🏟🏟🏟](www.🤙.tk#1)🤙🤙🤙[🏟🏟🏟🏟](www.🤙.tk#2)🤙🤙🤙["
+ "🏟🏟🏟🏟](www.🤙.tk#3)🏟🏟🏟[🏟🏟🏟🏟](www.🤙.tk#4)🤙🤙",
+ "🏟🏟🏟🏟🏟🏟🏟🏟🤙🤙🤙🏟🏟🏟🏟🤙🤙🤙🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟"
+ "🏟🤙🤙",
+ {{td::MessageEntity::Type::TextUrl, 6, 10, "http://www.🤙.tk/#1"},
+ {td::MessageEntity::Type::TextUrl, 22, 8, "http://www.🤙.tk/#2"},
+ {td::MessageEntity::Type::TextUrl, 36, 8, "http://www.🤙.tk/#3"},
+ {td::MessageEntity::Type::TextUrl, 50, 8, "http://www.🤙.tk/#4"}});
+ check_parse_markdown_v3(
+ "[🏟🏟🏟🏟🏟](www.🤙.tk#1)[🏟🏟🏟🏟](www.🤙.tk#2)[🏟🏟🏟🏟](www.🤙.tk#3)["
+ "🏟🏟🏟🏟](www.🤙.tk#4)",
+ "🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟",
+ {{td::MessageEntity::Type::TextUrl, 0, 10, "http://www.🤙.tk/#1"},
+ {td::MessageEntity::Type::TextUrl, 10, 8, "http://www.🤙.tk/#2"},
+ {td::MessageEntity::Type::TextUrl, 18, 8, "http://www.🤙.tk/#3"},
+ {td::MessageEntity::Type::TextUrl, 26, 8, "http://www.🤙.tk/#4"}});
+ check_parse_markdown_v3(
+ "🏟🏟🏟[🏟🏟🏟🏟🏟](www.🤙.tk)🤙🤙🤙[🏟🏟🏟🏟](www.🤙.tk)🤙🤙🤙["
+ "🏟🏟🏟🏟](www.🤙.tk)🏟🏟🏟[🏟🏟🏟🏟](www.🤙.tk)🤙🤙",
+ {{td::MessageEntity::Type::Bold, 0, 2},
+ {td::MessageEntity::Type::Bold, 4, 2},
+ {td::MessageEntity::Type::Bold, 7, 2},
+ {td::MessageEntity::Type::Bold, 11, 2},
+ {td::MessageEntity::Type::Bold, 15, 2},
+ {td::MessageEntity::Type::Bold, 18, 2},
+ {td::MessageEntity::Type::Bold, 26, 2},
+ {31, 2, td::UserId(static_cast<td::int64>(1))},
+ {td::MessageEntity::Type::Bold, 35, 1},
+ {td::MessageEntity::Type::Bold, 44, 2},
+ {td::MessageEntity::Type::Bold, 50, 2},
+ {td::MessageEntity::Type::Bold, 54, 2},
+ {56, 2, td::UserId(static_cast<td::int64>(2))},
+ {td::MessageEntity::Type::Bold, 58, 7},
+ {60, 2, td::UserId(static_cast<td::int64>(3))},
+ {td::MessageEntity::Type::Bold, 67, 7},
+ {td::MessageEntity::Type::Bold, 80, 7},
+ {td::MessageEntity::Type::Bold, 89, 25}},
+ "🏟🏟🏟🏟🏟🏟🏟🏟🤙🤙🤙🏟🏟🏟🏟🤙🤙🤙🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟"
+ "🏟🤙🤙",
+ {{td::MessageEntity::Type::Bold, 0, 2},
+ {td::MessageEntity::Type::Bold, 4, 2},
+ {td::MessageEntity::Type::TextUrl, 6, 10, "http://www.🤙.tk/"},
+ {td::MessageEntity::Type::Bold, 6, 2},
+ {td::MessageEntity::Type::Bold, 10, 2},
+ {td::MessageEntity::Type::Bold, 14, 2},
+ {18, 2, td::UserId(static_cast<td::int64>(1))},
+ {td::MessageEntity::Type::TextUrl, 22, 8, "http://www.🤙.tk/"},
+ {30, 2, td::UserId(static_cast<td::int64>(2))},
+ {td::MessageEntity::Type::Bold, 32, 2},
+ {34, 2, td::UserId(static_cast<td::int64>(3))},
+ {td::MessageEntity::Type::Bold, 34, 2},
+ {td::MessageEntity::Type::TextUrl, 36, 8, "http://www.🤙.tk/"},
+ {td::MessageEntity::Type::Bold, 36, 2},
+ {td::MessageEntity::Type::Bold, 40, 4},
+ {td::MessageEntity::Type::Bold, 44, 4},
+ {td::MessageEntity::Type::TextUrl, 50, 8, "http://www.🤙.tk/"},
+ {td::MessageEntity::Type::Bold, 50, 8},
+ {td::MessageEntity::Type::Bold, 58, 4}});
+ check_parse_markdown_v3("[`a`](t.me) [b](t.me)", {{td::MessageEntity::Type::Code, 13, 1}}, "[a](t.me) [b](t.me)",
+ {{td::MessageEntity::Type::Code, 1, 1}, {td::MessageEntity::Type::Code, 11, 1}});
+ check_parse_markdown_v3(
+ "[text](example.com)",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1}, {td::MessageEntity::Type::Strikethrough, 5, 14}}, "text",
+ {{td::MessageEntity::Type::TextUrl, 0, 4, "http://example.com/"}});
+ check_parse_markdown_v3("[text](example.com)",
+ {{td::MessageEntity::Type::Spoiler, 0, 1}, {td::MessageEntity::Type::Spoiler, 5, 14}}, "text",
+ {{td::MessageEntity::Type::TextUrl, 0, 4, "http://example.com/"}});
+
+ check_parse_markdown_v3("🏟[🏟](t.me) `🏟` [🏟](t.me) `a`", "🏟🏟 🏟 🏟 a",
+ {{td::MessageEntity::Type::TextUrl, 2, 2, "http://t.me/"},
+ {td::MessageEntity::Type::Code, 5, 2},
+ {td::MessageEntity::Type::TextUrl, 8, 2, "http://t.me/"},
+ {td::MessageEntity::Type::Code, 11, 1}});
+
+ check_parse_markdown_v3("__ __", " ", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_markdown_v3("__\n__", "\n", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_markdown_v3("__ __a", " a", {}, true);
+ check_parse_markdown_v3("__\n__a", "\na", {}, true);
+ check_parse_markdown_v3("**** __a__ **b** ~~c~~ ||d||", "**** a b c d",
+ {{td::MessageEntity::Type::Italic, 5, 1},
+ {td::MessageEntity::Type::Bold, 7, 1},
+ {td::MessageEntity::Type::Strikethrough, 9, 1},
+ {td::MessageEntity::Type::Spoiler, 11, 1}});
+ check_parse_markdown_v3("тест __аааа__ **бббб** ~~вввв~~ ||гггг||", "тест аааа бббб вввв гггг",
+ {{td::MessageEntity::Type::Italic, 5, 4},
+ {td::MessageEntity::Type::Bold, 10, 4},
+ {td::MessageEntity::Type::Strikethrough, 15, 4},
+ {td::MessageEntity::Type::Spoiler, 20, 4}});
+ check_parse_markdown_v3("___a___ ***b** ~c~~", "___a___ ***b** ~c~~", {});
+ check_parse_markdown_v3(
+ "__asd[ab__cd](t.me)", "asdabcd",
+ {{td::MessageEntity::Type::Italic, 0, 5}, {td::MessageEntity::Type::TextUrl, 3, 4, "http://t.me/"}});
+ check_parse_markdown_v3("__asd[ab__cd](t.me)", "asdabcd",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::TextUrl, 3, 4, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 3, 2}},
+ true);
+ check_parse_markdown_v3("__a #test__test", "__a #test__test", {});
+ check_parse_markdown_v3("a #testtest", {{td::MessageEntity::Type::Italic, 0, 7}}, "a #testtest",
+ {{td::MessageEntity::Type::Italic, 0, 7}});
+
+ // TODO parse_markdown_v3 is not idempotent now, which is bad
+ check_parse_markdown_v3(
+ "~~**~~__**a__", {{td::MessageEntity::Type::Strikethrough, 2, 1}, {td::MessageEntity::Type::Bold, 6, 1}},
+ "**__**a__", {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}}, true);
+ check_parse_markdown_v3("**__**a__",
+ {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}},
+ "__a__", {{td::MessageEntity::Type::Bold, 0, 2}}, true);
+ check_parse_markdown_v3("__a__", {{td::MessageEntity::Type::Bold, 0, 2}}, "a",
+ {{td::MessageEntity::Type::Italic, 0, 1}}, true);
+ check_parse_markdown_v3("~~__~~#test__test", "__#test__test", {{td::MessageEntity::Type::Strikethrough, 0, 2}});
+ check_parse_markdown_v3("__#test__test", {{td::MessageEntity::Type::Strikethrough, 0, 2}}, "#testtest",
+ {{td::MessageEntity::Type::Italic, 0, 5}});
+
+ check_parse_markdown_v3(
+ "~~**~~||**a||", {{td::MessageEntity::Type::Strikethrough, 2, 1}, {td::MessageEntity::Type::Bold, 6, 1}},
+ "**||**a||", {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}}, true);
+ check_parse_markdown_v3("**||**a||",
+ {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}},
+ "||a||", {{td::MessageEntity::Type::Bold, 0, 2}}, true);
+ check_parse_markdown_v3("||a||", {{td::MessageEntity::Type::Bold, 0, 2}}, "a",
+ {{td::MessageEntity::Type::Spoiler, 0, 1}}, true);
+ check_parse_markdown_v3("~~||~~#test||test", "#testtest", {{td::MessageEntity::Type::Spoiler, 0, 5}});
+ check_parse_markdown_v3("||#test||test", {{td::MessageEntity::Type::Strikethrough, 0, 2}}, "#testtest",
+ {{td::MessageEntity::Type::Spoiler, 0, 5}});
+
+ check_parse_markdown_v3("__[ab_](t.me)_", "__ab__", {{td::MessageEntity::Type::TextUrl, 2, 3, "http://t.me/"}});
+ check_parse_markdown_v3(
+ "__[ab__](t.me)_", "ab_",
+ {{td::MessageEntity::Type::TextUrl, 0, 2, "http://t.me/"}, {td::MessageEntity::Type::Italic, 0, 2}});
+ check_parse_markdown_v3("__[__ab__](t.me)__", "____ab____",
+ {{td::MessageEntity::Type::TextUrl, 2, 6, "http://t.me/"}});
+ check_parse_markdown_v3(
+ "__[__ab__](t.me)a__", "____aba",
+ {{td::MessageEntity::Type::TextUrl, 2, 4, "http://t.me/"}, {td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_markdown_v3("`a` __ab__", {{td::MessageEntity::Type::Bold, 6, 3}}, "a __ab__",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Bold, 4, 3}});
+ check_parse_markdown_v3("`a` __ab__", {{td::MessageEntity::Type::Underline, 5, 1}}, "a __ab__",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Underline, 3, 1}});
+
+ check_parse_markdown_v3("||[ab|](t.me)|", "||ab||", {{td::MessageEntity::Type::TextUrl, 2, 3, "http://t.me/"}});
+ check_parse_markdown_v3(
+ "||[ab||](t.me)|", "ab|",
+ {{td::MessageEntity::Type::TextUrl, 0, 2, "http://t.me/"}, {td::MessageEntity::Type::Spoiler, 0, 2}});
+ check_parse_markdown_v3("||[||ab||](t.me)||", "||||ab||||",
+ {{td::MessageEntity::Type::TextUrl, 2, 6, "http://t.me/"}});
+ check_parse_markdown_v3(
+ "||[||ab||](t.me)a||", "||||aba",
+ {{td::MessageEntity::Type::TextUrl, 2, 4, "http://t.me/"}, {td::MessageEntity::Type::Spoiler, 6, 1}});
+ check_parse_markdown_v3("`a` ||ab||", {{td::MessageEntity::Type::Bold, 6, 3}}, "a ||ab||",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Bold, 4, 3}});
+ check_parse_markdown_v3("`a` ||ab||", {{td::MessageEntity::Type::Underline, 5, 1}}, "a ||ab||",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Underline, 3, 1}});
+
+ check_parse_markdown_v3("`a` @test__test__test", "a @test__test__test", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_parse_markdown_v3("`a` #test__test__test", "a #test__test__test", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_parse_markdown_v3("`a` __@test_test_test__", "a @test_test_test",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Italic, 2, 15}});
+ check_parse_markdown_v3("`a` __#test_test_test__", "a #test_test_test",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Italic, 2, 15}});
+ check_parse_markdown_v3("[a](t.me) __@test**test**test__", "a @testtesttest",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 2, 13},
+ {td::MessageEntity::Type::Bold, 7, 4}});
+ check_parse_markdown_v3("[a](t.me) __#test~~test~~test__", "a #testtesttest",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 2, 13},
+ {td::MessageEntity::Type::Strikethrough, 7, 4}});
+ check_parse_markdown_v3("[a](t.me) __@test__test__test__", "a @testtesttest",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 2, 5},
+ {td::MessageEntity::Type::Italic, 11, 4}});
+ check_parse_markdown_v3("__**~~__gh**~~", "gh",
+ {{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Strikethrough, 0, 2}});
+ check_parse_markdown_v3("__ab**cd~~ef__gh**ij~~", "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Bold, 2, 6},
+ {td::MessageEntity::Type::Strikethrough, 4, 6}});
+ check_parse_markdown_v3("__ab**cd~~ef||gh__ij**kl~~mn||", "abcdefghijklmn",
+ {{td::MessageEntity::Type::Italic, 0, 2},
+ {td::MessageEntity::Type::Bold, 2, 2},
+ {td::MessageEntity::Type::Italic, 2, 2},
+ {td::MessageEntity::Type::Bold, 4, 2},
+ {td::MessageEntity::Type::Italic, 4, 2},
+ {td::MessageEntity::Type::Strikethrough, 4, 2},
+ {td::MessageEntity::Type::Spoiler, 6, 8},
+ {td::MessageEntity::Type::Strikethrough, 6, 6},
+ {td::MessageEntity::Type::Bold, 6, 4},
+ {td::MessageEntity::Type::Italic, 6, 2}},
+ true);
+ check_parse_markdown_v3("__ab**[cd~~ef__](t.me)gh**ij~~", "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Bold, 2, 6},
+ {td::MessageEntity::Type::TextUrl, 2, 4, "http://t.me/"},
+ {td::MessageEntity::Type::Strikethrough, 4, 6}});
+ check_parse_markdown_v3("__ab**[cd~~e](t.me)f__gh**ij~~", "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Bold, 2, 6},
+ {td::MessageEntity::Type::TextUrl, 2, 3, "http://t.me/"},
+ {td::MessageEntity::Type::Strikethrough, 4, 6}});
+ check_parse_markdown_v3("__ab**[cd~~](t.me)ef__gh**ij~~", "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Bold, 2, 6},
+ {td::MessageEntity::Type::TextUrl, 2, 2, "http://t.me/"},
+ {td::MessageEntity::Type::Strikethrough, 4, 6}});
+ check_parse_markdown_v3("[__**bold italic link**__](example.com)", "bold italic link",
+ {{td::MessageEntity::Type::TextUrl, 0, 16, "http://example.com/"},
+ {td::MessageEntity::Type::Bold, 0, 16},
+ {td::MessageEntity::Type::Italic, 0, 16}});
+ check_parse_markdown_v3(
+ "__italic__ ~~strikethrough~~ **bold** `code` ```pre``` __[italic__ text_url](telegram.org) __italic**bold "
+ "italic__bold**__italic__ ~~strikethrough~~ **bold** `code` ```pre``` __[italic__ text_url](telegram.org) "
+ "__italic**bold italic__bold** ||spoiler||",
+ "italic strikethrough bold code pre italic text_url italicbold italicbolditalic strikethrough bold code pre "
+ "italic text_url italicbold italicbold spoiler",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Strikethrough, 7, 13},
+ {td::MessageEntity::Type::Bold, 21, 4},
+ {td::MessageEntity::Type::Code, 26, 4},
+ {td::MessageEntity::Type::Pre, 31, 3},
+ {td::MessageEntity::Type::TextUrl, 35, 15, "http://telegram.org/"},
+ {td::MessageEntity::Type::Italic, 35, 6},
+ {td::MessageEntity::Type::Italic, 51, 17},
+ {td::MessageEntity::Type::Bold, 57, 15},
+ {td::MessageEntity::Type::Italic, 72, 6},
+ {td::MessageEntity::Type::Strikethrough, 79, 13},
+ {td::MessageEntity::Type::Bold, 93, 4},
+ {td::MessageEntity::Type::Code, 98, 4},
+ {td::MessageEntity::Type::Pre, 103, 3},
+ {td::MessageEntity::Type::TextUrl, 107, 15, "http://telegram.org/"},
+ {td::MessageEntity::Type::Italic, 107, 6},
+ {td::MessageEntity::Type::Italic, 123, 17},
+ {td::MessageEntity::Type::Bold, 129, 15},
+ {td::MessageEntity::Type::Spoiler, 145, 7}});
+
+ td::vector<td::string> parts{"a", " #test__a", "__", "**", "~~", "||", "[", "](t.me)", "`"};
+ td::vector<td::MessageEntity::Type> types{
+ td::MessageEntity::Type::Bold, td::MessageEntity::Type::Italic, td::MessageEntity::Type::Underline,
+ td::MessageEntity::Type::Strikethrough, td::MessageEntity::Type::Spoiler, td::MessageEntity::Type::Code,
+ td::MessageEntity::Type::Pre, td::MessageEntity::Type::PreCode, td::MessageEntity::Type::TextUrl,
+ td::MessageEntity::Type::MentionName, td::MessageEntity::Type::Cashtag};
+ for (size_t test_n = 0; test_n < 1000; test_n++) {
+ td::string str;
+ int part_n = td::Random::fast(1, 200);
+ for (int i = 0; i < part_n; i++) {
+ str += parts[td::Random::fast(0, static_cast<int>(parts.size()) - 1)];
+ }
+ td::vector<td::MessageEntity> entities;
+ int entity_n = td::Random::fast(1, 20);
+ for (int i = 0; i < entity_n; i++) {
+ auto type = types[td::Random::fast(0, static_cast<int>(types.size()) - 1)];
+ td::int32 offset = td::Random::fast(0, static_cast<int>(str.size()) - 1);
+ auto max_length = static_cast<int>(str.size() - offset);
+ if ((test_n & 1) != 0 && max_length > 4) {
+ max_length = 4;
+ }
+ td::int32 length = td::Random::fast(0, max_length);
+ entities.emplace_back(type, offset, length);
+ }
+
+ td::FormattedText text{std::move(str), std::move(entities)};
+ while (true) {
+ ASSERT_TRUE(td::fix_formatted_text(text.text, text.entities, true, true, true, true, true).is_ok());
+ auto parsed_text = td::parse_markdown_v3(text);
+ ASSERT_TRUE(td::fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).is_ok());
+ if (parsed_text == text) {
+ break;
+ }
+ text = std::move(parsed_text);
+ }
+ ASSERT_EQ(text, td::parse_markdown_v3(text));
+ auto markdown_text = td::get_markdown_v3(text);
+ ASSERT_TRUE(text == markdown_text || text == td::parse_markdown_v3(markdown_text));
+ }
+}
+
+static void check_get_markdown_v3(const td::string &result_text, const td::vector<td::MessageEntity> &result_entities,
+ td::string text, td::vector<td::MessageEntity> entities) {
+ auto markdown_text = td::get_markdown_v3({std::move(text), std::move(entities)});
+ ASSERT_STREQ(result_text, markdown_text.text);
+ ASSERT_EQ(result_entities, markdown_text.entities);
+}
+
+TEST(MessageEntities, get_markdown_v3) {
+ check_get_markdown_v3("``` ```", {}, " ", {{td::MessageEntity::Type::Pre, 0, 1}});
+ check_get_markdown_v3("` `", {}, " ", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_get_markdown_v3("`\n`", {}, "\n", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_get_markdown_v3("ab", {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}}, "ab",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}});
+
+ check_get_markdown_v3("[ ](http://t.me/)", {}, " ", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}});
+ check_get_markdown_v3(
+ "[ ]t.me[)](http://t.me/) [ ](t.me)", {{25, 1, td::UserId(static_cast<td::int64>(1))}}, "[ ]t.me) [ ](t.me)",
+ {{td::MessageEntity::Type::TextUrl, 7, 1, "http://t.me/"}, {9, 1, td::UserId(static_cast<td::int64>(1))}});
+
+ check_get_markdown_v3("__ __", {}, " ", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_get_markdown_v3("** **", {}, " ", {{td::MessageEntity::Type::Bold, 0, 1}});
+ check_get_markdown_v3("~~ ~~", {}, " ", {{td::MessageEntity::Type::Strikethrough, 0, 1}});
+ check_get_markdown_v3("|| ||", {}, " ", {{td::MessageEntity::Type::Spoiler, 0, 1}});
+ check_get_markdown_v3("__a__ **b** ~~c~~ ||d|| e", {{td::MessageEntity::Type::PreCode, 24, 1, "C++"}}, "a b c d e",
+ {{td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Strikethrough, 4, 1},
+ {td::MessageEntity::Type::Spoiler, 6, 1},
+ {td::MessageEntity::Type::PreCode, 8, 1, "C++"}});
+ check_get_markdown_v3("`ab` ```cd``` ef", {{td::MessageEntity::Type::PreCode, 14, 2, "C++"}}, "ab cd ef",
+ {{td::MessageEntity::Type::Code, 0, 2},
+ {td::MessageEntity::Type::Pre, 3, 2},
+ {td::MessageEntity::Type::PreCode, 6, 2, "C++"}});
+ check_get_markdown_v3("__asd__[__ab__cd](http://t.me/)", {}, "asdabcd",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::TextUrl, 3, 4, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 3, 2}});
+
+ check_get_markdown_v3("__ab", {{td::MessageEntity::Type::Italic, 3, 1}}, "__ab",
+ {{td::MessageEntity::Type::Italic, 3, 1}});
+ check_get_markdown_v3("__ab__**__cd__**~~**__ef__gh**ij~~", {}, "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 2},
+ {td::MessageEntity::Type::Bold, 2, 2},
+ {td::MessageEntity::Type::Italic, 2, 2},
+ {td::MessageEntity::Type::Strikethrough, 4, 6},
+ {td::MessageEntity::Type::Bold, 4, 4},
+ {td::MessageEntity::Type::Italic, 4, 2}});
+ check_get_markdown_v3("[**__bold italic link__**](http://example.com/)", {}, "bold italic link",
+ {{td::MessageEntity::Type::TextUrl, 0, 16, "http://example.com/"},
+ {td::MessageEntity::Type::Bold, 0, 16},
+ {td::MessageEntity::Type::Italic, 0, 16}});
}
diff --git a/protocols/Telegram/tdlib/td/test/mtproto.cpp b/protocols/Telegram/tdlib/td/test/mtproto.cpp
index 7702a1e37b..89f5441ab5 100644
--- a/protocols/Telegram/tdlib/td/test/mtproto.cpp
+++ b/protocols/Telegram/tdlib/td/test/mtproto.cpp
@@ -1,143 +1,276 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/utils/tests.h"
-
-#include "td/actor/actor.h"
-#include "td/actor/PromiseFuture.h"
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/net/PublicRsaKeyShared.h"
+#include "td/telegram/net/Session.h"
+#include "td/telegram/NotificationManager.h"
-#include "td/mtproto/crypto.h"
+#include "td/mtproto/AuthData.h"
+#include "td/mtproto/DhCallback.h"
+#include "td/mtproto/DhHandshake.h"
#include "td/mtproto/Handshake.h"
#include "td/mtproto/HandshakeActor.h"
-#include "td/mtproto/HandshakeConnection.h"
+#include "td/mtproto/Ping.h"
#include "td/mtproto/PingConnection.h"
+#include "td/mtproto/ProxySecret.h"
#include "td/mtproto/RawConnection.h"
+#include "td/mtproto/RSA.h"
+#include "td/mtproto/TlsInit.h"
+#include "td/mtproto/TransportType.h"
+#include "td/net/GetHostByNameActor.h"
#include "td/net/Socks5.h"
+#include "td/net/TransparentProxy.h"
-#include "td/telegram/ConfigManager.h"
-#include "td/telegram/net/PublicRsaKeyShared.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+#include "td/utils/base64.h"
+#include "td/utils/BufferedFd.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
#include "td/utils/logging.h"
+#include "td/utils/port/Clocks.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/SocketFd.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
+#include "td/utils/tests.h"
+#include "td/utils/Time.h"
-REGISTER_TESTS(mtproto);
-
-using namespace td;
-using namespace mtproto;
-
-#if !TD_WINDOWS && !TD_EMSCRIPTEN // TODO
-TEST(Mtproto, config) {
- ConcurrentScheduler sched;
- int threads_n = 0;
- sched.init(threads_n);
+TEST(Mtproto, GetHostByNameActor) {
+ int threads_n = 1;
+ td::ConcurrentScheduler sched(threads_n, 0);
- int cnt = 3;
+ int cnt = 1;
+ td::vector<td::ActorOwn<td::GetHostByNameActor>> actors;
{
- auto guard = sched.get_current_guard();
- get_simple_config_azure(PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
- LOG(ERROR) << to_string(r_simple_config.ok());
- if (--cnt == 0) {
- Scheduler::instance()->finish();
- }
- }))
- .release();
-
- get_simple_config_google_app(PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
- LOG(ERROR) << to_string(r_simple_config.ok());
- if (--cnt == 0) {
- Scheduler::instance()->finish();
- }
- }))
- .release();
+ auto guard = sched.get_main_guard();
+
+ auto run = [&](td::ActorId<td::GetHostByNameActor> actor_id, td::string host, bool prefer_ipv6, bool allow_ok,
+ bool allow_error) {
+ auto promise = td::PromiseCreator::lambda([&cnt, &actors, num = cnt, host, allow_ok,
+ allow_error](td::Result<td::IPAddress> r_ip_address) {
+ if (r_ip_address.is_error() && !allow_error) {
+ LOG(ERROR) << num << " \"" << host << "\" " << r_ip_address.error();
+ }
+ if (r_ip_address.is_ok() && !allow_ok && (r_ip_address.ok().is_ipv6() || r_ip_address.ok().get_ipv4() != 0)) {
+ LOG(ERROR) << num << " \"" << host << "\" " << r_ip_address.ok();
+ }
+ if (--cnt == 0) {
+ actors.clear();
+ td::Scheduler::instance()->finish();
+ }
+ });
+ cnt++;
+ td::send_closure_later(actor_id, &td::GetHostByNameActor::run, host, 443, prefer_ipv6, std::move(promise));
+ };
- get_simple_config_google_dns(PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
- LOG(ERROR) << to_string(r_simple_config.ok());
- if (--cnt == 0) {
- Scheduler::instance()->finish();
+ td::vector<td::string> hosts = {"127.0.0.2",
+ "1.1.1.1",
+ "localhost",
+ "web.telegram.org",
+ "web.telegram.org.",
+ "москва.рф",
+ "",
+ "%",
+ " ",
+ "a",
+ "\x80",
+ "[]",
+ "127.0.0.1.",
+ "0x12.0x34.0x56.0x78",
+ "0x7f.001",
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+ "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
+ "[[2001:0db8:85a3:0000:0000:8a2e:0370:7334]]"};
+ for (const auto &types :
+ {td::vector<td::GetHostByNameActor::ResolverType>{td::GetHostByNameActor::ResolverType::Native},
+ td::vector<td::GetHostByNameActor::ResolverType>{td::GetHostByNameActor::ResolverType::Google},
+ td::vector<td::GetHostByNameActor::ResolverType>{td::GetHostByNameActor::ResolverType::Google,
+ td::GetHostByNameActor::ResolverType::Google,
+ td::GetHostByNameActor::ResolverType::Native}}) {
+ td::GetHostByNameActor::Options options;
+ options.resolver_types = types;
+ options.scheduler_id = threads_n;
+
+ auto actor = td::create_actor<td::GetHostByNameActor>("GetHostByNameActor", std::move(options));
+ auto actor_id = actor.get();
+ actors.push_back(std::move(actor));
+
+ for (auto host : hosts) {
+ for (auto prefer_ipv6 : {false, true}) {
+ bool allow_ok = host.size() > 2 && host[1] != '[';
+ bool allow_both = host == "127.0.0.1." || host == "localhost" || (host == "москва.рф" && prefer_ipv6);
+ bool allow_error = !allow_ok || allow_both;
+ run(actor_id, host, prefer_ipv6, allow_ok, allow_error);
+ }
}
- }))
- .release();
+ }
}
+ cnt--;
sched.start();
while (sched.run_main(10)) {
- // empty;
+ // empty
}
sched.finish();
}
-#endif
+
+TEST(Time, to_unix_time) {
+ ASSERT_EQ(0, td::HttpDate::to_unix_time(1970, 1, 1, 0, 0, 0).move_as_ok());
+ ASSERT_EQ(60 * 60 + 60 + 1, td::HttpDate::to_unix_time(1970, 1, 1, 1, 1, 1).move_as_ok());
+ ASSERT_EQ(24 * 60 * 60, td::HttpDate::to_unix_time(1970, 1, 2, 0, 0, 0).move_as_ok());
+ ASSERT_EQ(31 * 24 * 60 * 60, td::HttpDate::to_unix_time(1970, 2, 1, 0, 0, 0).move_as_ok());
+ ASSERT_EQ(365 * 24 * 60 * 60, td::HttpDate::to_unix_time(1971, 1, 1, 0, 0, 0).move_as_ok());
+ ASSERT_EQ(1562780559, td::HttpDate::to_unix_time(2019, 7, 10, 17, 42, 39).move_as_ok());
+}
+
+TEST(Time, parse_http_date) {
+ ASSERT_EQ(784887151, td::HttpDate::parse_http_date("Tue, 15 Nov 1994 08:12:31 GMT").move_as_ok());
+}
+
+TEST(Mtproto, config) {
+ int threads_n = 0;
+ td::ConcurrentScheduler sched(threads_n, 0);
+
+ int cnt = 1;
+ {
+ auto guard = sched.get_main_guard();
+
+ auto run = [&](auto &func, bool is_test) {
+ auto promise =
+ td::PromiseCreator::lambda([&, num = cnt](td::Result<td::SimpleConfigResult> r_simple_config_result) {
+ if (r_simple_config_result.is_ok()) {
+ auto simple_config_result = r_simple_config_result.move_as_ok();
+ auto date = simple_config_result.r_http_date.is_ok()
+ ? td::to_string(simple_config_result.r_http_date.ok())
+ : (PSTRING() << simple_config_result.r_http_date.error());
+ auto config = simple_config_result.r_config.is_ok()
+ ? to_string(simple_config_result.r_config.ok())
+ : (PSTRING() << simple_config_result.r_config.error());
+ LOG(ERROR) << num << " " << date << " " << config;
+ } else {
+ LOG(ERROR) << num << " " << r_simple_config_result.error();
+ }
+ if (--cnt == 0) {
+ td::Scheduler::instance()->finish();
+ }
+ });
+ cnt++;
+ func(std::move(promise), false, td::Slice(), is_test, -1).release();
+ };
+
+ run(td::get_simple_config_azure, false);
+ run(td::get_simple_config_google_dns, false);
+ run(td::get_simple_config_mozilla_dns, false);
+ run(td::get_simple_config_azure, true);
+ run(td::get_simple_config_google_dns, true);
+ run(td::get_simple_config_mozilla_dns, true);
+ run(td::get_simple_config_firebase_remote_config, false);
+ run(td::get_simple_config_firebase_realtime, false);
+ run(td::get_simple_config_firebase_firestore, false);
+ }
+ cnt--;
+ if (cnt != 0) {
+ sched.start();
+ while (sched.run_main(10)) {
+ // empty;
+ }
+ sched.finish();
+ }
+}
TEST(Mtproto, encrypted_config) {
- string data =
- " LQ2 \b\n\tru6xVXpHHckW4eQWK0X3uThupVOor5sXT8t298IjDksYeUseQTOIrnUqiQj7o"
- "+ZgPfhnfe+lfcQA+naax9akgllimjlJtL5riTf3O7iqZSnJ614qmCucxqqVTbXk/"
- "hY2KaJTtsMqk7cShJjM3aQ4DD40h2InTaG7uyVO2q7K0GMUTeY3AM0Rt1lUjKHLD"
- "g4RwjTzZaG8TwfgL/mZ7jsvgTTTATPWKUo7SmxQ9Hsj+07NMGqr6JKZS6aiU1Knz"
- "VGCZ3OJEyRYocktN4HjaLpkguilaHWlVM2UNFUd5a+ajfLIiiKlH0FRC3XZ12CND"
- "Y+NBjv0I57N2O4fBfswTlA== ";
- auto config = decode_config(data).move_as_ok();
+ td::string data =
+ " hO//tt \b\n\tiwPVovorKtIYtQ8y2ik7CqfJiJ4pJOCLRa4fBmNPixuRPXnBFF/3mTAAZoSyHq4SNylGHz0Cv1/"
+ "FnWWdEV+BPJeOTk+ARHcNkuJBt0CqnfcVCoDOpKqGyq0U31s2MOpQvHgAG+Tlpg02syuH0E4dCGRw5CbJPARiynteb9y5fT5x/"
+ "kmdp6BMR5tWQSQF0liH16zLh8BDSIdiMsikdcwnAvBwdNhRqQBqGx9MTh62MDmlebjtczE9Gz0z5cscUO2yhzGdphgIy6SP+"
+ "bwaqLWYF0XdPGjKLMUEJW+rou6fbL1t/EUXPtU0XmQAnO0Fh86h+AqDMOe30N4qKrPQ== ";
+ auto config = td::decode_config(data).move_as_ok();
}
-class TestPingActor : public Actor {
+class TestPingActor final : public td::Actor {
public:
- TestPingActor(IPAddress ip_address, Status *result) : ip_address_(ip_address), result_(result) {
+ TestPingActor(td::IPAddress ip_address, td::Status *result) : ip_address_(ip_address), result_(result) {
}
private:
- IPAddress ip_address_;
- std::unique_ptr<mtproto::PingConnection> ping_connection_;
- Status *result_;
+ td::IPAddress ip_address_;
+ td::unique_ptr<td::mtproto::PingConnection> ping_connection_;
+ td::Status *result_;
+ bool is_inited_ = false;
- void start_up() override {
- ping_connection_ = std::make_unique<mtproto::PingConnection>(std::make_unique<mtproto::RawConnection>(
- SocketFd::open(ip_address_).move_as_ok(), mtproto::TransportType::Tcp, nullptr));
+ void start_up() final {
+ auto r_socket = td::SocketFd::open(ip_address_);
+ if (r_socket.is_error()) {
+ LOG(ERROR) << "Failed to open socket: " << r_socket.error();
+ return stop();
+ }
- ping_connection_->get_pollable().set_observer(this);
- subscribe(ping_connection_->get_pollable());
+ ping_connection_ = td::mtproto::PingConnection::create_req_pq(
+ td::mtproto::RawConnection::create(
+ ip_address_, td::BufferedFd<td::SocketFd>(r_socket.move_as_ok()),
+ td::mtproto::TransportType{td::mtproto::TransportType::Tcp, 0, td::mtproto::ProxySecret()}, nullptr),
+ 3);
+
+ td::Scheduler::subscribe(ping_connection_->get_poll_info().extract_pollable_fd(this));
+ is_inited_ = true;
set_timeout_in(10);
yield();
}
- void tear_down() override {
- unsubscribe_before_close(ping_connection_->get_pollable());
- ping_connection_->close();
- Scheduler::instance()->finish();
+
+ void tear_down() final {
+ if (is_inited_) {
+ td::Scheduler::unsubscribe_before_close(ping_connection_->get_poll_info().get_pollable_fd_ref());
+ }
+ td::Scheduler::instance()->finish();
}
- void loop() override {
+ void loop() final {
auto status = ping_connection_->flush();
if (status.is_error()) {
*result_ = std::move(status);
return stop();
}
if (ping_connection_->was_pong()) {
- LOG(ERROR) << "GOT PONG";
+ LOG(INFO) << "GOT PONG";
return stop();
}
}
- void timeout_expired() override {
- *result_ = Status::Error("Timeout expired");
+ void timeout_expired() final {
+ *result_ = td::Status::Error("Timeout expired");
stop();
}
};
-static IPAddress get_default_ip_address() {
- IPAddress ip_address;
+static td::IPAddress get_default_ip_address() {
+ td::IPAddress ip_address;
+#if TD_EMSCRIPTEN
+ ip_address.init_host_port("venus.web.telegram.org/apiws", 443).ensure();
+#else
ip_address.init_ipv4_port("149.154.167.40", 80).ensure();
+#endif
return ip_address;
}
-class Mtproto_ping : public td::Test {
+static td::int32 get_default_dc_id() {
+ return 10002;
+}
+
+class Mtproto_ping final : public td::Test {
public:
using Test::Test;
bool step() final {
if (!is_inited_) {
- sched_.init(0);
sched_.create_actor_unsafe<TestPingActor>(0, "Pinger", get_default_ip_address(), &result_).release();
sched_.start();
is_inited_ = true;
@@ -151,57 +284,65 @@ class Mtproto_ping : public td::Test {
if (result_.is_error()) {
LOG(ERROR) << result_;
}
- ASSERT_TRUE(result_.is_ok());
return false;
}
private:
bool is_inited_ = false;
- ConcurrentScheduler sched_;
- Status result_;
+ td::ConcurrentScheduler sched_{0, 0};
+ td::Status result_;
};
-Mtproto_ping mtproto_ping("Mtproto_ping");
+td::RegisterTest<Mtproto_ping> mtproto_ping("Mtproto_ping");
-class Context : public AuthKeyHandshakeContext {
+class HandshakeContext final : public td::mtproto::AuthKeyHandshakeContext {
public:
- DhCallback *get_dh_callback() override {
+ td::mtproto::DhCallback *get_dh_callback() final {
return nullptr;
}
- PublicRsaKeyInterface *get_public_rsa_key_interface() override {
+ td::mtproto::PublicRsaKeyInterface *get_public_rsa_key_interface() final {
return &public_rsa_key;
}
private:
- PublicRsaKeyShared public_rsa_key{DcId::empty()};
+ td::PublicRsaKeyShared public_rsa_key{td::DcId::empty(), true};
};
-class HandshakeTestActor : public Actor {
+class HandshakeTestActor final : public td::Actor {
public:
- explicit HandshakeTestActor(Status *result) : result_(result) {
+ HandshakeTestActor(td::int32 dc_id, td::Status *result) : dc_id_(dc_id), result_(result) {
}
private:
- Status *result_;
+ td::int32 dc_id_ = 0;
+ td::Status *result_;
bool wait_for_raw_connection_ = false;
- std::unique_ptr<RawConnection> raw_connection_;
+ td::unique_ptr<td::mtproto::RawConnection> raw_connection_;
bool wait_for_handshake_ = false;
- std::unique_ptr<AuthKeyHandshake> handshake_;
- Status status_;
+ td::unique_ptr<td::mtproto::AuthKeyHandshake> handshake_;
+ td::Status status_;
bool wait_for_result_ = false;
- void tear_down() override {
+ void tear_down() final {
if (raw_connection_) {
raw_connection_->close();
}
- finish(Status::Error("Interrupted"));
+ finish(td::Status::Error("Interrupted"));
}
- void loop() override {
+ void loop() final {
if (!wait_for_raw_connection_ && !raw_connection_) {
- raw_connection_ = std::make_unique<mtproto::RawConnection>(SocketFd::open(get_default_ip_address()).move_as_ok(),
- mtproto::TransportType::Tcp, nullptr);
+ auto ip_address = get_default_ip_address();
+ auto r_socket = td::SocketFd::open(ip_address);
+ if (r_socket.is_error()) {
+ finish(td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error()));
+ return stop();
+ }
+
+ raw_connection_ = td::mtproto::RawConnection::create(
+ ip_address, td::BufferedFd<td::SocketFd>(r_socket.move_as_ok()),
+ td::mtproto::TransportType{td::mtproto::TransportType::Tcp, 0, td::mtproto::ProxySecret()}, nullptr);
}
if (!wait_for_handshake_ && !handshake_) {
- handshake_ = std::make_unique<AuthKeyHandshake>(0);
+ handshake_ = td::make_unique<td::mtproto::AuthKeyHandshake>(dc_id_, 3600);
}
if (raw_connection_ && handshake_) {
if (wait_for_result_) {
@@ -211,34 +352,37 @@ class HandshakeTestActor : public Actor {
return stop();
}
if (!handshake_->is_ready_for_finish()) {
- finish(Status::Error("Key is not ready.."));
+ finish(td::Status::Error("Key is not ready.."));
return stop();
}
- finish(Status::OK());
+ finish(td::Status::OK());
return stop();
}
wait_for_result_ = true;
- create_actor<HandshakeActor>(
- "HandshakeActor", std::move(handshake_), std::move(raw_connection_), std::make_unique<Context>(), 10.0,
- PromiseCreator::lambda([self = actor_id(this)](Result<std::unique_ptr<RawConnection>> raw_connection) {
- send_closure(self, &HandshakeTestActor::got_connection, std::move(raw_connection), 1);
- }),
- PromiseCreator::lambda([self = actor_id(this)](Result<std::unique_ptr<AuthKeyHandshake>> handshake) {
- send_closure(self, &HandshakeTestActor::got_handshake, std::move(handshake), 1);
- }))
+ td::create_actor<td::mtproto::HandshakeActor>(
+ "HandshakeActor", std::move(handshake_), std::move(raw_connection_), td::make_unique<HandshakeContext>(),
+ 10.0,
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::RawConnection>> raw_connection) {
+ td::send_closure(actor_id, &HandshakeTestActor::got_connection, std::move(raw_connection), 1);
+ }),
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> handshake) {
+ td::send_closure(actor_id, &HandshakeTestActor::got_handshake, std::move(handshake), 1);
+ }))
.release();
wait_for_raw_connection_ = true;
wait_for_handshake_ = true;
}
}
- void got_connection(Result<std::unique_ptr<RawConnection>> r_raw_connection, int32 dummy) {
+ void got_connection(td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_raw_connection, bool dummy) {
CHECK(wait_for_raw_connection_);
wait_for_raw_connection_ = false;
if (r_raw_connection.is_ok()) {
raw_connection_ = r_raw_connection.move_as_ok();
- status_ = Status::OK();
+ status_ = td::Status::OK();
} else {
status_ = r_raw_connection.move_as_error();
}
@@ -246,7 +390,7 @@ class HandshakeTestActor : public Actor {
loop();
}
- void got_handshake(Result<std::unique_ptr<AuthKeyHandshake>> r_handshake, int32 dummy) {
+ void got_handshake(td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> r_handshake, bool dummy) {
CHECK(wait_for_handshake_);
wait_for_handshake_ = false;
CHECK(r_handshake.is_ok());
@@ -254,23 +398,22 @@ class HandshakeTestActor : public Actor {
loop();
}
- void finish(Status status) {
+ void finish(td::Status status) {
if (!result_) {
return;
}
*result_ = std::move(status);
result_ = nullptr;
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
};
-class Mtproto_handshake : public td::Test {
+class Mtproto_handshake final : public td::Test {
public:
using Test::Test;
bool step() final {
if (!is_inited_) {
- sched_.init(0);
- sched_.create_actor_unsafe<HandshakeTestActor>(0, "HandshakeTestActor", &result_).release();
+ sched_.create_actor_unsafe<HandshakeTestActor>(0, "HandshakeTestActor", get_default_dc_id(), &result_).release();
sched_.start();
is_inited_ = true;
}
@@ -283,60 +426,62 @@ class Mtproto_handshake : public td::Test {
if (result_.is_error()) {
LOG(ERROR) << result_;
}
- ASSERT_TRUE(result_.is_ok());
return false;
}
private:
bool is_inited_ = false;
- ConcurrentScheduler sched_;
- Status result_;
+ td::ConcurrentScheduler sched_{0, 0};
+ td::Status result_;
};
-Mtproto_handshake mtproto_handshake("Mtproto_handshake");
+td::RegisterTest<Mtproto_handshake> mtproto_handshake("Mtproto_handshake");
-class Socks5TestActor : public Actor {
+class Socks5TestActor final : public td::Actor {
public:
- void start_up() override {
- auto promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result<SocketFd> res) {
- send_closure(actor_id, &Socks5TestActor::on_result, std::move(res), false);
- });
+ void start_up() final {
+ auto promise =
+ td::PromiseCreator::lambda([actor_id = actor_id(this)](td::Result<td::BufferedFd<td::SocketFd>> res) {
+ td::send_closure(actor_id, &Socks5TestActor::on_result, std::move(res), false);
+ });
- class Callback : public Socks5::Callback {
+ class Callback final : public td::TransparentProxy::Callback {
public:
- explicit Callback(Promise<SocketFd> promise) : promise_(std::move(promise)) {
+ explicit Callback(td::Promise<td::BufferedFd<td::SocketFd>> promise) : promise_(std::move(promise)) {
}
- void set_result(Result<SocketFd> result) override {
+ void set_result(td::Result<td::BufferedFd<td::SocketFd>> result) final {
promise_.set_result(std::move(result));
}
- void on_connected() override {
+ void on_connected() final {
}
private:
- Promise<SocketFd> promise_;
+ td::Promise<td::BufferedFd<td::SocketFd>> promise_;
};
- IPAddress socks5_ip;
+ td::IPAddress socks5_ip;
socks5_ip.init_ipv4_port("131.191.89.104", 43077).ensure();
- IPAddress mtproto_ip = get_default_ip_address();
+ td::IPAddress mtproto_ip_address = get_default_ip_address();
- auto r_socket = SocketFd::open(socks5_ip);
- create_actor<Socks5>("socks5", r_socket.move_as_ok(), mtproto_ip, "", "",
- std::make_unique<Callback>(std::move(promise)), actor_shared())
+ auto r_socket = td::SocketFd::open(socks5_ip);
+ if (r_socket.is_error()) {
+ return promise.set_error(td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error()));
+ }
+ td::create_actor<td::Socks5>("Socks5", r_socket.move_as_ok(), mtproto_ip_address, "", "",
+ td::make_unique<Callback>(std::move(promise)), actor_shared(this))
.release();
}
private:
- void on_result(Result<SocketFd> res, bool dummy) {
+ void on_result(td::Result<td::BufferedFd<td::SocketFd>> res, bool dummy) {
res.ensure();
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
};
TEST(Mtproto, socks5) {
return;
- ConcurrentScheduler sched;
int threads_n = 0;
- sched.init(threads_n);
+ td::ConcurrentScheduler sched(threads_n, 0);
sched.create_actor_unsafe<Socks5TestActor>(0, "Socks5TestActor").release();
sched.start();
@@ -345,3 +490,251 @@ TEST(Mtproto, socks5) {
}
sched.finish();
}
+
+TEST(Mtproto, notifications) {
+ td::vector<td::string> pushes = {
+ "eyJwIjoiSkRnQ3NMRWxEaWhyVWRRN1pYM3J1WVU4TlRBMFhMb0N6UWRNdzJ1cWlqMkdRbVR1WXVvYXhUeFJHaG1QQm8yVElYZFBzX2N3b2RIb3lY"
+ "b2drVjM1dVl0UzdWeElNX1FNMDRKMG1mV3ZZWm4zbEtaVlJ0aFVBNGhYUWlaN0pfWDMyZDBLQUlEOWgzRnZwRjNXUFRHQWRaVkdFYzg3bnFPZ3hD"
+ "NUNMRkM2SU9fZmVqcEpaV2RDRlhBWWpwc1k2aktrbVNRdFZ1MzE5ZW04UFVieXZudFpfdTNud2hjQ0czMk96TGp4S1kyS1lzU21JZm1GMzRmTmw1"
+ "QUxaa2JvY2s2cE5rZEdrak9qYmRLckJyU0ZtWU8tQ0FsRE10dEplZFFnY1U5bVJQdU80b1d2NG5sb1VXS19zSlNTaXdIWEZyb1pWTnZTeFJ0Z1dN"
+ "ZyJ9",
+ "eyJwIjoiSkRnQ3NMRWxEaWlZby1GRWJndk9WaTFkUFdPVmZndzBBWHYwTWNzWDFhWEtNZC03T1Q2WWNfT0taRURHZDJsZ0h0WkhMSllyVG50RE95"
+ "TkY1aXJRQlZ4UUFLQlRBekhPTGZIS3BhQXdoaWd5b3NQd0piWnJVV2xRWmh4eEozUFUzZjBNRTEwX0xNT0pFN0xsVUFaY2dabUNaX2V1QmNPZWNK"
+ "VERxRkpIRGZjN2pBOWNrcFkyNmJRT2dPUEhCeHlEMUVrNVdQcFpLTnlBODVuYzQ1eHFPdERqcU5aVmFLU3pKb2VIcXBQMnJqR29kN2M5YkxsdGd5"
+ "Q0NGd2NBU3dJeDc3QWNWVXY1UnVZIn0"};
+ td::string key =
+ "uBa5yu01a-nJJeqsR3yeqMs6fJLYXjecYzFcvS6jIwS3nefBIr95LWrTm-IbRBNDLrkISz1Sv0KYpDzhU8WFRk1D0V_"
+ "qyO7XsbDPyrYxRBpGxofJUINSjb1uCxoSdoh1_F0UXEA2fWWKKVxL0DKUQssZfbVj3AbRglsWpH-jDK1oc6eBydRiS3i4j-"
+ "H0yJkEMoKRgaF9NaYI4u26oIQ-Ez46kTVU-R7e3acdofOJKm7HIKan_5ZMg82Dvec2M6vc_"
+ "I54Vs28iBx8IbBO1y5z9WSScgW3JCvFFKP2MXIu7Jow5-cpUx6jXdzwRUb9RDApwAFKi45zpv8eb3uPCDAmIQ";
+ td::vector<td::string> decrypted_payloads = {
+ "eyJsb2Nfa2V5IjoiTUVTU0FHRV9URVhUIiwibG9jX2FyZ3MiOlsiQXJzZW55IFNtaXJub3YiLCJhYmNkZWZnIl0sImN1c3RvbSI6eyJtc2dfaWQi"
+ "OiI1OTAwNDciLCJmcm9tX2lkIjoiNjI4MTQifSwiYmFkZ2UiOiI0MDkifQ",
+ "eyJsb2Nfa2V5IjoiIiwibG9jX2FyZ3MiOltdLCJjdXN0b20iOnsiY2hhbm5lbF9pZCI6IjExNzY4OTU0OTciLCJtYXhfaWQiOiIxMzU5In0sImJh"
+ "ZGdlIjoiMCJ9"};
+ key = td::base64url_decode(key).move_as_ok();
+
+ for (size_t i = 0; i < pushes.size(); i++) {
+ auto push = td::base64url_decode(pushes[i]).move_as_ok();
+ auto decrypted_payload = td::base64url_decode(decrypted_payloads[i]).move_as_ok();
+
+ auto key_id = td::mtproto::DhHandshake::calc_key_id(key);
+ ASSERT_EQ(key_id, td::NotificationManager::get_push_receiver_id(push).ok());
+ ASSERT_EQ(decrypted_payload, td::NotificationManager::decrypt_push(key_id, key, push).ok());
+ }
+}
+
+class FastPingTestActor final : public td::Actor {
+ public:
+ explicit FastPingTestActor(td::Status *result) : result_(result) {
+ }
+
+ private:
+ td::Status *result_;
+ td::unique_ptr<td::mtproto::RawConnection> connection_;
+ td::unique_ptr<td::mtproto::AuthKeyHandshake> handshake_;
+ td::ActorOwn<> fast_ping_;
+ int iteration_{0};
+
+ void start_up() final {
+ // Run handshake to create key and salt
+ auto ip_address = get_default_ip_address();
+ auto r_socket = td::SocketFd::open(ip_address);
+ if (r_socket.is_error()) {
+ *result_ = td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error());
+ return stop();
+ }
+
+ auto raw_connection = td::mtproto::RawConnection::create(
+ ip_address, td::BufferedFd<td::SocketFd>(r_socket.move_as_ok()),
+ td::mtproto::TransportType{td::mtproto::TransportType::Tcp, 0, td::mtproto::ProxySecret()}, nullptr);
+ auto handshake = td::make_unique<td::mtproto::AuthKeyHandshake>(get_default_dc_id(), 60 * 100 /*temp*/);
+ td::create_actor<td::mtproto::HandshakeActor>(
+ "HandshakeActor", std::move(handshake), std::move(raw_connection), td::make_unique<HandshakeContext>(), 10.0,
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::RawConnection>> raw_connection) {
+ td::send_closure(actor_id, &FastPingTestActor::got_connection, std::move(raw_connection), 1);
+ }),
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> handshake) {
+ td::send_closure(actor_id, &FastPingTestActor::got_handshake, std::move(handshake), 1);
+ }))
+ .release();
+ }
+
+ void got_connection(td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_raw_connection, bool dummy) {
+ if (r_raw_connection.is_error()) {
+ *result_ = r_raw_connection.move_as_error();
+ LOG(INFO) << "Receive " << *result_ << " instead of a connection";
+ return stop();
+ }
+ connection_ = r_raw_connection.move_as_ok();
+ loop();
+ }
+
+ void got_handshake(td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> r_handshake, bool dummy) {
+ if (r_handshake.is_error()) {
+ *result_ = r_handshake.move_as_error();
+ LOG(INFO) << "Receive " << *result_ << " instead of a handshake";
+ return stop();
+ }
+ handshake_ = r_handshake.move_as_ok();
+ loop();
+ }
+
+ void got_raw_connection(td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_connection) {
+ if (r_connection.is_error()) {
+ *result_ = r_connection.move_as_error();
+ LOG(INFO) << "Receive " << *result_ << " instead of a handshake";
+ return stop();
+ }
+ connection_ = r_connection.move_as_ok();
+ LOG(INFO) << "RTT: " << connection_->extra().rtt;
+ connection_->extra().rtt = 0;
+ loop();
+ }
+
+ void loop() final {
+ if (handshake_ && connection_) {
+ LOG(INFO) << "Iteration " << iteration_;
+ if (iteration_ == 6) {
+ return stop();
+ }
+ td::unique_ptr<td::mtproto::AuthData> auth_data;
+ if (iteration_ % 2 == 0) {
+ auth_data = td::make_unique<td::mtproto::AuthData>();
+ auth_data->set_tmp_auth_key(handshake_->get_auth_key());
+ auth_data->set_server_time_difference(handshake_->get_server_time_diff());
+ auth_data->set_server_salt(handshake_->get_server_salt(), td::Time::now());
+ auth_data->set_future_salts({td::mtproto::ServerSalt{0u, 1e20, 1e30}}, td::Time::now());
+ auth_data->set_use_pfs(true);
+ td::uint64 session_id = 0;
+ do {
+ td::Random::secure_bytes(reinterpret_cast<td::uint8 *>(&session_id), sizeof(session_id));
+ } while (session_id == 0);
+ auth_data->set_session_id(session_id);
+ }
+ iteration_++;
+ fast_ping_ = create_ping_actor(
+ td::Slice(), std::move(connection_), std::move(auth_data),
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_raw_connection) {
+ td::send_closure(actor_id, &FastPingTestActor::got_raw_connection, std::move(r_raw_connection));
+ }),
+ td::ActorShared<>());
+ }
+ }
+
+ void tear_down() final {
+ td::Scheduler::instance()->finish();
+ }
+};
+
+class Mtproto_FastPing final : public td::Test {
+ public:
+ using Test::Test;
+ bool step() final {
+ if (!is_inited_) {
+ sched_.create_actor_unsafe<FastPingTestActor>(0, "FastPingTestActor", &result_).release();
+ sched_.start();
+ is_inited_ = true;
+ }
+
+ bool ret = sched_.run_main(10);
+ if (ret) {
+ return true;
+ }
+ sched_.finish();
+ if (result_.is_error()) {
+ LOG(ERROR) << result_;
+ }
+ return false;
+ }
+
+ private:
+ bool is_inited_ = false;
+ td::ConcurrentScheduler sched_{0, 0};
+ td::Status result_;
+};
+td::RegisterTest<Mtproto_FastPing> mtproto_fastping("Mtproto_FastPing");
+
+TEST(Mtproto, Grease) {
+ td::string s(10000, '0');
+ td::mtproto::Grease::init(s);
+ for (auto c : s) {
+ CHECK((c & 0xF) == 0xA);
+ }
+ for (size_t i = 1; i < s.size(); i += 2) {
+ CHECK(s[i] != s[i - 1]);
+ }
+}
+
+TEST(Mtproto, TlsTransport) {
+ int threads_n = 1;
+ td::ConcurrentScheduler sched(threads_n, 0);
+ {
+ auto guard = sched.get_main_guard();
+ class RunTest final : public td::Actor {
+ void start_up() final {
+ class Callback final : public td::TransparentProxy::Callback {
+ public:
+ void set_result(td::Result<td::BufferedFd<td::SocketFd>> result) final {
+ if (result.is_ok()) {
+ LOG(ERROR) << "Unexpectedly succeeded to connect to MTProto proxy";
+ } else if (result.error().message() != "Response hash mismatch") {
+ LOG(ERROR) << "Receive unexpected result " << result.error();
+ }
+ td::Scheduler::instance()->finish();
+ }
+ void on_connected() final {
+ }
+ };
+
+ const td::string domain = "www.google.com";
+ td::IPAddress ip_address;
+ auto resolve_status = ip_address.init_host_port(domain, 443);
+ if (resolve_status.is_error()) {
+ LOG(ERROR) << resolve_status;
+ td::Scheduler::instance()->finish();
+ return;
+ }
+ auto r_socket = td::SocketFd::open(ip_address);
+ if (r_socket.is_error()) {
+ LOG(ERROR) << "Failed to open socket: " << r_socket.error();
+ td::Scheduler::instance()->finish();
+ return;
+ }
+ td::create_actor<td::mtproto::TlsInit>("TlsInit", r_socket.move_as_ok(), domain, "0123456789secret",
+ td::make_unique<Callback>(), td::ActorShared<>(),
+ td::Clocks::system() - td::Time::now())
+ .release();
+ }
+ };
+ td::create_actor<RunTest>("RunTest").release();
+ }
+
+ sched.start();
+ while (sched.run_main(10)) {
+ // empty
+ }
+ sched.finish();
+}
+
+TEST(Mtproto, RSA) {
+ auto pem = td::Slice(
+ "-----BEGIN RSA PUBLIC KEY-----\n"
+ "MIIBCgKCAQEAr4v4wxMDXIaMOh8bayF/NyoYdpcysn5EbjTIOZC0RkgzsRj3SGlu\n"
+ "52QSz+ysO41dQAjpFLgxPVJoOlxXokaOq827IfW0bGCm0doT5hxtedu9UCQKbE8j\n"
+ "lDOk+kWMXHPZFJKWRgKgTu9hcB3y3Vk+JFfLpq3d5ZB48B4bcwrRQnzkx5GhWOFX\n"
+ "x73ZgjO93eoQ2b/lDyXxK4B4IS+hZhjzezPZTI5upTRbs5ljlApsddsHrKk6jJNj\n"
+ "8Ygs/ps8e6ct82jLXbnndC9s8HjEvDvBPH9IPjv5JUlmHMBFZ5vFQIfbpo0u0+1P\n"
+ "n6bkEi5o7/ifoyVv2pAZTRwppTz0EuXD8QIDAQAB\n"
+ "-----END RSA PUBLIC KEY-----");
+ auto rsa = td::mtproto::RSA::from_pem_public_key(pem).move_as_ok();
+ ASSERT_EQ(-7596991558377038078, rsa.get_fingerprint());
+ ASSERT_EQ(256u, rsa.size());
+
+ td::string to(256, '\0');
+ rsa.encrypt(pem.substr(0, 256), to);
+ ASSERT_EQ("U2nJEtB2AgpHrm3HB0yhpTQgb0wbesi9Pv/W1v/vULU=", td::base64_encode(td::sha256(to)));
+}
diff --git a/protocols/Telegram/tdlib/td/test/online.cpp b/protocols/Telegram/tdlib/td/test/online.cpp
new file mode 100644
index 0000000000..6bcdcec833
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/online.cpp
@@ -0,0 +1,632 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/ClientActor.h"
+#include "td/telegram/Log.h"
+#include "td/telegram/td_api_json.h"
+#include "td/telegram/TdCallback.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+#include "td/actor/MultiPromise.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/utils/crypto.h"
+#include "td/utils/FileLog.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/misc.h"
+#include "td/utils/OptionParser.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/signals.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Random.h"
+
+#include <iostream>
+#include <map>
+#include <memory>
+
+namespace td {
+
+template <class T>
+static void check_td_error(T &result) {
+ LOG_CHECK(result->get_id() != td::td_api::error::ID) << to_string(result);
+}
+
+class TestClient : public Actor {
+ public:
+ explicit TestClient(td::string name) : name_(std::move(name)) {
+ }
+ struct Update {
+ td::uint64 id;
+ td::tl_object_ptr<td::td_api::Object> object;
+ Update(td::uint64 id, td::tl_object_ptr<td::td_api::Object> object) : id(id), object(std::move(object)) {
+ }
+ };
+ class Listener {
+ public:
+ Listener() = default;
+ Listener(const Listener &) = delete;
+ Listener &operator=(const Listener &) = delete;
+ Listener(Listener &&) = delete;
+ Listener &operator=(Listener &&) = delete;
+ virtual ~Listener() = default;
+ virtual void start_listen(TestClient *client) {
+ }
+ virtual void stop_listen() {
+ }
+ virtual void on_update(std::shared_ptr<Update> update) = 0;
+ };
+ struct RemoveListener {
+ void operator()(Listener *listener) {
+ send_closure(self, &TestClient::remove_listener, listener);
+ }
+ ActorId<TestClient> self;
+ };
+ using ListenerToken = std::unique_ptr<Listener, RemoveListener>;
+ void close(td::Promise<> close_promise) {
+ close_promise_ = std::move(close_promise);
+ td_client_.reset();
+ }
+
+ td::unique_ptr<td::TdCallback> make_td_callback() {
+ class TdCallbackImpl : public td::TdCallback {
+ public:
+ explicit TdCallbackImpl(td::ActorId<TestClient> client) : client_(client) {
+ }
+ void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) override {
+ send_closure(client_, &TestClient::on_result, id, std::move(result));
+ }
+ void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) override {
+ send_closure(client_, &TestClient::on_error, id, std::move(error));
+ }
+ TdCallbackImpl(const TdCallbackImpl &) = delete;
+ TdCallbackImpl &operator=(const TdCallbackImpl &) = delete;
+ TdCallbackImpl(TdCallbackImpl &&) = delete;
+ TdCallbackImpl &operator=(TdCallbackImpl &&) = delete;
+ ~TdCallbackImpl() override {
+ send_closure(client_, &TestClient::on_closed);
+ }
+
+ private:
+ td::ActorId<TestClient> client_;
+ };
+ return td::make_unique<TdCallbackImpl>(actor_id(this));
+ }
+
+ void add_listener(td::unique_ptr<Listener> listener) {
+ auto *ptr = listener.get();
+ listeners_.push_back(std::move(listener));
+ ptr->start_listen(this);
+ }
+ void remove_listener(Listener *listener) {
+ pending_remove_.push_back(listener);
+ }
+ void do_pending_remove_listeners() {
+ for (auto listener : pending_remove_) {
+ do_remove_listener(listener);
+ }
+ pending_remove_.clear();
+ }
+ void do_remove_listener(Listener *listener) {
+ for (size_t i = 0; i < listeners_.size(); i++) {
+ if (listeners_[i].get() == listener) {
+ listener->stop_listen();
+ listeners_.erase(listeners_.begin() + i);
+ break;
+ }
+ }
+ }
+
+ void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) {
+ on_update(std::make_shared<Update>(id, std::move(result)));
+ }
+ void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) {
+ on_update(std::make_shared<Update>(id, std::move(error)));
+ }
+ void on_update(std::shared_ptr<Update> update) {
+ for (auto &listener : listeners_) {
+ listener->on_update(update);
+ }
+ do_pending_remove_listeners();
+ }
+
+ void on_closed() {
+ stop();
+ }
+
+ void start_up() override {
+ auto old_context = set_context(std::make_shared<td::ActorContext>());
+ set_tag(name_);
+ LOG(INFO) << "START UP!";
+
+ td_client_ = td::create_actor<td::ClientActor>("Td-proxy", make_td_callback());
+ }
+
+ td::ActorOwn<td::ClientActor> td_client_;
+
+ td::string name_;
+
+ private:
+ td::vector<td::unique_ptr<Listener>> listeners_;
+ td::vector<Listener *> pending_remove_;
+
+ td::Promise<> close_promise_;
+};
+
+class Task : public TestClient::Listener {
+ public:
+ void on_update(std::shared_ptr<TestClient::Update> update) override {
+ auto it = sent_queries_.find(update->id);
+ if (it != sent_queries_.end()) {
+ it->second.set_value(std::move(update->object));
+ sent_queries_.erase(it);
+ }
+ process_update(update);
+ }
+ void start_listen(TestClient *client) override {
+ client_ = client;
+ start_up();
+ }
+ virtual void process_update(std::shared_ptr<TestClient::Update> update) {
+ }
+
+ template <class FunctionT, class CallbackT>
+ void send_query(td::tl_object_ptr<FunctionT> function, CallbackT callback) {
+ auto id = current_query_id_++;
+
+ using ResultT = typename FunctionT::ReturnType;
+ sent_queries_[id] =
+ [callback = Promise<ResultT>(std::move(callback))](Result<tl_object_ptr<td_api::Object>> r_obj) mutable {
+ TRY_RESULT_PROMISE(callback, obj, std::move(r_obj));
+ if (obj->get_id() == td::td_api::error::ID) {
+ auto err = move_tl_object_as<td_api::error>(std::move(obj));
+ callback.set_error(Status::Error(err->code_, err->message_));
+ return;
+ }
+ callback.set_value(move_tl_object_as<typename ResultT::element_type>(std::move(obj)));
+ };
+ send_closure(client_->td_client_, &td::ClientActor::request, id, std::move(function));
+ }
+
+ protected:
+ std::map<td::uint64, Promise<td::tl_object_ptr<td::td_api::Object>>> sent_queries_;
+ TestClient *client_ = nullptr;
+ td::uint64 current_query_id_ = 1;
+
+ virtual void start_up() {
+ }
+ void stop() {
+ client_->remove_listener(this);
+ client_ = nullptr;
+ }
+ bool is_alive() const {
+ return client_ != nullptr;
+ }
+};
+
+class InitTask : public Task {
+ public:
+ struct Options {
+ string name;
+ int32 api_id;
+ string api_hash;
+ };
+ InitTask(Options options, td::Promise<> promise) : options_(std::move(options)), promise_(std::move(promise)) {
+ }
+
+ private:
+ Options options_;
+ td::Promise<> promise_;
+ bool start_flag_{false};
+
+ void start_up() override {
+ send_query(td::make_tl_object<td::td_api::getAuthorizationState>(),
+ [this](auto res) { this->process_authorization_state(res.move_as_ok()); });
+ }
+ void process_authorization_state(td::tl_object_ptr<td::td_api::Object> authorization_state) {
+ start_flag_ = true;
+ td::tl_object_ptr<td::td_api::Function> function;
+ switch (authorization_state->get_id()) {
+ case td::td_api::authorizationStateReady::ID:
+ promise_.set_value({});
+ stop();
+ break;
+ case td::td_api::authorizationStateWaitTdlibParameters::ID: {
+ auto request = td::td_api::make_object<td::td_api::setTdlibParameters>();
+ request->use_test_dc_ = true;
+ request->database_directory_ = options_.name + TD_DIR_SLASH;
+ request->use_message_database_ = true;
+ request->use_secret_chats_ = true;
+ request->api_id_ = options_.api_id;
+ request->api_hash_ = options_.api_hash;
+ request->system_language_code_ = "en";
+ request->device_model_ = "Desktop";
+ request->application_version_ = "tdclient-test";
+ request->enable_storage_optimizer_ = true;
+ send(std::move(request));
+ break;
+ }
+ default:
+ LOG(ERROR) << "???";
+ promise_.set_error(
+ Status::Error(PSLICE() << "Unexpected authorization state " << to_string(authorization_state)));
+ stop();
+ break;
+ }
+ }
+ template <class T>
+ void send(T &&query) {
+ send_query(std::move(query), [this](auto res) {
+ if (is_alive()) {
+ res.ensure();
+ }
+ });
+ }
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (!start_flag_) {
+ return;
+ }
+ if (!update->object) {
+ return;
+ }
+ if (update->object->get_id() == td::td_api::updateAuthorizationState::ID) {
+ auto update_authorization_state = td::move_tl_object_as<td::td_api::updateAuthorizationState>(update->object);
+ process_authorization_state(std::move(update_authorization_state->authorization_state_));
+ }
+ }
+};
+
+class GetMe : public Task {
+ public:
+ struct Result {
+ int64 user_id;
+ int64 chat_id;
+ };
+ explicit GetMe(Promise<Result> promise) : promise_(std::move(promise)) {
+ }
+ void start_up() override {
+ send_query(td::make_tl_object<td::td_api::getMe>(), [this](auto res) { with_user_id(res.move_as_ok()->id_); });
+ }
+
+ private:
+ Promise<Result> promise_;
+ Result result_;
+
+ void with_user_id(int64 user_id) {
+ result_.user_id = user_id;
+ send_query(td::make_tl_object<td::td_api::createPrivateChat>(user_id, false),
+ [this](auto res) { with_chat_id(res.move_as_ok()->id_); });
+ }
+
+ void with_chat_id(int64 chat_id) {
+ result_.chat_id = chat_id;
+ promise_.set_value(std::move(result_));
+ stop();
+ }
+};
+
+class UploadFile : public Task {
+ public:
+ struct Result {
+ std::string content;
+ std::string remote_id;
+ };
+ UploadFile(std::string dir, std::string content, int64 chat_id, Promise<Result> promise)
+ : dir_(std::move(dir)), content_(std::move(content)), chat_id_(std::move(chat_id)), promise_(std::move(promise)) {
+ }
+ void start_up() override {
+ auto hash = hex_encode(sha256(content_)).substr(0, 10);
+ content_path_ = dir_ + TD_DIR_SLASH + hash + ".data";
+ id_path_ = dir_ + TD_DIR_SLASH + hash + ".id";
+
+ auto r_id = read_file(id_path_);
+ if (r_id.is_ok() && r_id.ok().size() > 10) {
+ auto id = r_id.move_as_ok();
+ LOG(ERROR) << "Got file from cache";
+ Result res;
+ res.content = std::move(content_);
+ res.remote_id = id.as_slice().str();
+ promise_.set_value(std::move(res));
+ stop();
+ return;
+ }
+
+ write_file(content_path_, content_).ensure();
+
+ send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageDocument>(
+ td::make_tl_object<td::td_api::inputFileLocal>(content_path_), nullptr, true,
+ td::make_tl_object<td::td_api::formattedText>("tag", td::Auto()))),
+ [this](auto res) { with_message(res.move_as_ok()); });
+ }
+
+ private:
+ std::string dir_;
+ std::string content_path_;
+ std::string id_path_;
+ std::string content_;
+ int64 chat_id_;
+ Promise<Result> promise_;
+ int64 file_id_{0};
+
+ void with_message(td::tl_object_ptr<td_api::message> message) {
+ CHECK(message->content_->get_id() == td::td_api::messageDocument::ID);
+ auto messageDocument = td::move_tl_object_as<td::td_api::messageDocument>(message->content_);
+ on_file(*messageDocument->document_->document_, true);
+ }
+
+ void on_file(const td_api::file &file, bool force = false) {
+ if (force) {
+ file_id_ = file.id_;
+ }
+ if (file.id_ != file_id_) {
+ return;
+ }
+ if (file.remote_->is_uploading_completed_) {
+ Result res;
+ res.content = std::move(content_);
+ res.remote_id = file.remote_->id_;
+
+ unlink(content_path_).ignore();
+ atomic_write_file(id_path_, res.remote_id).ignore();
+
+ promise_.set_value(std::move(res));
+ stop();
+ }
+ }
+
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (!update->object) {
+ return;
+ }
+ if (update->object->get_id() == td::td_api::updateFile::ID) {
+ auto updateFile = td::move_tl_object_as<td::td_api::updateFile>(update->object);
+ on_file(*updateFile->file_);
+ }
+ }
+};
+
+class TestDownloadFile : public Task {
+ public:
+ TestDownloadFile(std::string remote_id, std::string content, Promise<Unit> promise)
+ : remote_id_(std::move(remote_id)), content_(std::move(content)), promise_(std::move(promise)) {
+ }
+ void start_up() override {
+ send_query(td::make_tl_object<td::td_api::getRemoteFile>(remote_id_, nullptr),
+ [this](auto res) { start_file(*res.ok()); });
+ }
+
+ private:
+ std::string remote_id_;
+ std::string content_;
+ Promise<Unit> promise_;
+ struct Range {
+ size_t begin;
+ size_t end;
+ };
+ int32 file_id_{0};
+ std::vector<Range> ranges_;
+
+ void start_file(const td_api::file &file) {
+ LOG(ERROR) << "Start";
+ file_id_ = file.id_;
+ // CHECK(!file.local_->is_downloading_active_);
+ // CHECK(!file.local_->is_downloading_completed_);
+ // CHECK(file.local_->download_offset_ == 0);
+ if (!file.local_->path_.empty()) {
+ unlink(file.local_->path_).ignore();
+ }
+
+ auto size = narrow_cast<size_t>(file.size_);
+ Random::Xorshift128plus rnd(123);
+
+ size_t begin = 0;
+
+ while (begin + 128u < size) {
+ auto chunk_size = rnd.fast(128, 3096);
+ auto end = begin + chunk_size;
+ if (end > size) {
+ end = size;
+ }
+
+ ranges_.push_back({begin, end});
+ begin = end;
+ }
+
+ random_shuffle(as_mutable_span(ranges_), rnd);
+ start_chunk();
+ }
+
+ void got_chunk(const td_api::file &file) {
+ LOG(ERROR) << "Got chunk";
+ auto range = ranges_.back();
+ std::string got_chunk(range.end - range.begin, '\0');
+ FileFd::open(file.local_->path_, FileFd::Flags::Read).move_as_ok().pread(got_chunk, range.begin).ensure();
+ CHECK(got_chunk == as_slice(content_).substr(range.begin, range.end - range.begin));
+ ranges_.pop_back();
+ if (ranges_.empty()) {
+ promise_.set_value(Unit{});
+ return stop();
+ }
+ start_chunk();
+ }
+
+ void start_chunk() {
+ send_query(td::make_tl_object<td::td_api::downloadFile>(
+ file_id_, 1, static_cast<int64>(ranges_.back().begin),
+ static_cast<int64>(ranges_.back().end - ranges_.back().begin), true),
+ [this](auto res) { got_chunk(*res.ok()); });
+ }
+};
+
+static std::string gen_readable_file(size_t block_size, size_t block_count) {
+ std::string content;
+ for (size_t block_id = 0; block_id < block_count; block_id++) {
+ std::string block;
+ for (size_t line = 0; block.size() < block_size; line++) {
+ block += PSTRING() << "\nblock=" << block_id << ", line=" << line;
+ }
+ block.resize(block_size);
+ content += block;
+ }
+ return content;
+}
+
+class TestTd : public Actor {
+ public:
+ struct Options {
+ std::string alice_dir = "alice";
+ std::string bob_dir = "bob";
+ int32 api_id{0};
+ string api_hash;
+ };
+
+ explicit TestTd(Options options) : options_(std::move(options)) {
+ }
+
+ private:
+ Options options_;
+ ActorOwn<TestClient> alice_;
+ GetMe::Result alice_id_;
+ std::string alice_cache_dir_;
+ ActorOwn<TestClient> bob_;
+
+ void start_up() override {
+ alice_ = create_actor<TestClient>("Alice", "Alice");
+ bob_ = create_actor<TestClient>("Bob", "Bob");
+
+ MultiPromiseActorSafe mp("init");
+ mp.add_promise(promise_send_closure(actor_id(this), &TestTd::check_init));
+
+ InitTask::Options options;
+ options.api_id = options_.api_id;
+ options.api_hash = options_.api_hash;
+
+ options.name = options_.alice_dir;
+ td::send_closure(alice_, &TestClient::add_listener, td::make_unique<InitTask>(options, mp.get_promise()));
+ options.name = options_.bob_dir;
+ td::send_closure(bob_, &TestClient::add_listener, td::make_unique<InitTask>(options, mp.get_promise()));
+ }
+
+ void check_init(Result<Unit> res) {
+ LOG_IF(FATAL, res.is_error()) << res.error();
+ alice_cache_dir_ = options_.alice_dir + TD_DIR_SLASH + "cache";
+ mkdir(alice_cache_dir_).ignore();
+
+ td::send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<GetMe>(promise_send_closure(actor_id(this), &TestTd::with_alice_id)));
+
+ //close();
+ }
+
+ void with_alice_id(Result<GetMe::Result> alice_id) {
+ alice_id_ = alice_id.move_as_ok();
+ LOG(ERROR) << "Alice user_id=" << alice_id_.user_id << ", chat_id=" << alice_id_.chat_id;
+ auto content = gen_readable_file(65536, 20);
+ send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<UploadFile>(alice_cache_dir_, std::move(content), alice_id_.chat_id,
+ promise_send_closure(actor_id(this), &TestTd::with_file)));
+ }
+ void with_file(Result<UploadFile::Result> r_result) {
+ auto result = r_result.move_as_ok();
+ send_closure(
+ alice_, &TestClient::add_listener,
+ td::make_unique<TestDownloadFile>(result.remote_id, std::move(result.content),
+ promise_send_closure(actor_id(this), &TestTd::after_test_download_file)));
+ }
+ void after_test_download_file(Result<Unit>) {
+ close();
+ }
+
+ void close() {
+ MultiPromiseActorSafe mp("close");
+ mp.add_promise(promise_send_closure(actor_id(this), &TestTd::check_close));
+ td::send_closure(alice_, &TestClient::close, mp.get_promise());
+ td::send_closure(bob_, &TestClient::close, mp.get_promise());
+ }
+
+ void check_close(Result<Unit> res) {
+ Scheduler::instance()->finish();
+ stop();
+ }
+};
+
+static void fail_signal(int sig) {
+ signal_safe_write_signal_number(sig);
+ while (true) {
+ // spin forever to allow debugger to attach
+ }
+}
+
+static void on_fatal_error(const char *error) {
+ std::cerr << "Fatal error: " << error << std::endl;
+}
+int main(int argc, char **argv) {
+ ignore_signal(SignalType::HangUp).ensure();
+ ignore_signal(SignalType::Pipe).ensure();
+ set_signal_handler(SignalType::Error, fail_signal).ensure();
+ set_signal_handler(SignalType::Abort, fail_signal).ensure();
+ Log::set_fatal_error_callback(on_fatal_error);
+ init_openssl_threads();
+
+ TestTd::Options test_options;
+
+ test_options.api_id = [](auto x) -> int32 {
+ if (x) {
+ return to_integer<int32>(Slice(x));
+ }
+ return 0;
+ }(std::getenv("TD_API_ID"));
+ test_options.api_hash = [](auto x) -> std::string {
+ if (x) {
+ return x;
+ }
+ return std::string();
+ }(std::getenv("TD_API_HASH"));
+
+ int new_verbosity_level = VERBOSITY_NAME(INFO);
+
+ OptionParser options;
+ options.set_description("TDLib experimental tester");
+ options.add_option('v', "verbosity", "Set verbosity level", [&](Slice level) {
+ int new_verbosity = 1;
+ while (begins_with(level, "v")) {
+ new_verbosity++;
+ level.remove_prefix(1);
+ }
+ if (!level.empty()) {
+ new_verbosity += to_integer<int>(level) - (new_verbosity == 1);
+ }
+ new_verbosity_level = VERBOSITY_NAME(FATAL) + new_verbosity;
+ });
+ options.add_check([&] {
+ if (test_options.api_id == 0 || test_options.api_hash.empty()) {
+ return Status::Error("You must provide valid api-id and api-hash obtained at https://my.telegram.org");
+ }
+ return Status::OK();
+ });
+ auto r_non_options = options.run(argc, argv, 0);
+ if (r_non_options.is_error()) {
+ LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();
+ LOG(PLAIN) << options;
+ return 1;
+ }
+ SET_VERBOSITY_LEVEL(new_verbosity_level);
+
+ td::ConcurrentScheduler sched(4, 0);
+ sched.create_actor_unsafe<TestTd>(0, "TestTd", std::move(test_options)).release();
+ sched.start();
+ while (sched.run_main(10)) {
+ }
+ sched.finish();
+ return 0;
+}
+} // namespace td
+
+int main(int argc, char **argv) {
+ return td::main(argc, argv);
+}
diff --git a/protocols/Telegram/tdlib/td/test/poll.cpp b/protocols/Telegram/tdlib/td/test/poll.cpp
new file mode 100644
index 0000000000..4c8012e441
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/poll.cpp
@@ -0,0 +1,58 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/PollManager.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/tests.h"
+
+static void check_vote_percentage(const std::vector<td::int32> &voter_counts, td::int32 total_count,
+ const std::vector<td::int32> &expected) {
+ auto result = td::PollManager::get_vote_percentage(voter_counts, total_count);
+ if (result != expected) {
+ LOG(FATAL) << "Have " << voter_counts << " and " << total_count << ", but received " << result << " instead of "
+ << expected;
+ }
+}
+
+TEST(Poll, get_vote_percentage) {
+ check_vote_percentage({1}, 1, {100});
+ check_vote_percentage({999}, 999, {100});
+ check_vote_percentage({0}, 0, {0});
+ check_vote_percentage({2, 1}, 3, {67, 33});
+ check_vote_percentage({4, 1, 1}, 6, {66, 17, 17});
+ check_vote_percentage({100, 100}, 200, {50, 50});
+ check_vote_percentage({101, 99}, 200, {50, 50});
+ check_vote_percentage({102, 98}, 200, {51, 49});
+ check_vote_percentage({198, 2}, 200, {99, 1});
+ check_vote_percentage({199, 1}, 200, {99, 1});
+ check_vote_percentage({200}, 200, {100});
+ check_vote_percentage({0, 999}, 999, {0, 100});
+ check_vote_percentage({999, 999}, 999, {100, 100});
+ check_vote_percentage({499, 599}, 999, {50, 60});
+ check_vote_percentage({1, 1}, 2, {50, 50});
+ check_vote_percentage({1, 1, 1}, 3, {33, 33, 33});
+ check_vote_percentage({1, 1, 1, 1}, 4, {25, 25, 25, 25});
+ check_vote_percentage({1, 1, 1, 1, 1}, 5, {20, 20, 20, 20, 20});
+ check_vote_percentage({1, 1, 1, 1, 1, 1}, 6, {16, 16, 16, 16, 16, 16});
+ check_vote_percentage({1, 1, 1, 1, 1, 1, 1}, 7, {14, 14, 14, 14, 14, 14, 14});
+ check_vote_percentage({1, 1, 1, 1, 1, 1, 1, 1}, 8, {12, 12, 12, 12, 12, 12, 12, 12});
+ check_vote_percentage({1, 1, 1, 1, 1, 1, 1, 1, 1}, 9, {11, 11, 11, 11, 11, 11, 11, 11, 11});
+ check_vote_percentage({1, 1, 1, 1, 1, 1, 2}, 8, {12, 12, 12, 12, 12, 12, 25});
+ check_vote_percentage({1, 1, 1, 2, 2, 2, 3}, 12, {8, 8, 8, 17, 17, 17, 25});
+ check_vote_percentage({0, 1, 1, 1, 2, 2, 2, 3}, 12, {0, 8, 8, 8, 17, 17, 17, 25});
+ check_vote_percentage({1, 1, 1, 0}, 3, {33, 33, 33, 0});
+ check_vote_percentage({0, 1, 1, 1}, 3, {0, 33, 33, 33});
+ check_vote_percentage({9949, 9950, 9999}, 10000, {99, 100, 100});
+ check_vote_percentage({1234, 2345, 3456, 2841}, 9876,
+ {12 /* 12.49 */, 24 /* 23.74 */, 35 /* 34.99 */, 29 /* 28.76 */});
+ check_vote_percentage({1234, 2301, 3500, 2841}, 9876,
+ {12 /* 12.49 */, 23 /* 23.29 */, 35 /* 35.43 */, 29 /* 28.76 */});
+ check_vote_percentage({200, 200, 200, 270, 270, 60}, 1200, {17, 17, 17, 22, 22, 5});
+ check_vote_percentage({200, 200, 200, 300, 240, 60}, 1200, {16, 16, 16, 25, 20, 5});
+ check_vote_percentage({200, 200, 200, 250, 250, 20}, 1120, {18, 18, 18, 22, 22, 2});
+ check_vote_percentage({200, 200, 200, 250, 250, 40}, 1140, {17, 17, 17, 22, 22, 4});
+}
diff --git a/protocols/Telegram/tdlib/td/test/secret.cpp b/protocols/Telegram/tdlib/td/test/secret.cpp
index 2abed9787b..2c79b7e2d8 100644
--- a/protocols/Telegram/tdlib/td/test/secret.cpp
+++ b/protocols/Telegram/tdlib/td/test/secret.cpp
@@ -1,36 +1,48 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
-#include "td/actor/PromiseFuture.h"
-
-#include "td/db/binlog/detail/BinlogEventsProcessor.h"
-
-#include "td/mtproto/crypto.h"
-#include "td/mtproto/utils.h"
-
+#include "td/telegram/EncryptedFile.h"
+#include "td/telegram/FolderId.h"
#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/MessageId.h"
+#include "td/telegram/secret_api.h"
#include "td/telegram/SecretChatActor.h"
#include "td/telegram/SecretChatId.h"
-
-#include "td/telegram/secret_api.h"
#include "td/telegram/telegram_api.h"
+#include "td/telegram/UserId.h"
+
+#include "td/db/binlog/BinlogInterface.h"
+#include "td/db/binlog/detail/BinlogEventsProcessor.h"
+#include "td/db/BinlogKeyValue.h"
+#include "td/db/DbKey.h"
+
+#include "td/mtproto/DhCallback.h"
+#include "td/mtproto/utils.h"
#include "td/tl/tl_object_parse.h"
#include "td/tl/tl_object_store.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/as.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/format.h"
#include "td/utils/Gzip.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
+#include "td/utils/Promise.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/tests.h"
#include "td/utils/tl_helpers.h"
@@ -43,17 +55,14 @@
#include <map>
#include <memory>
-REGISTER_TESTS(secret);
-
namespace my_api {
using namespace td;
-//messages_getDhConfig
class messages_getDhConfig {
public:
- int32 version_;
- int32 random_length_;
+ int32 version_{};
+ int32 random_length_{};
messages_getDhConfig() = default;
@@ -70,7 +79,6 @@ class messages_getDhConfig {
}
};
-//InputUser
class InputUser {
public:
static tl_object_ptr<InputUser> fetch(TlBufferParser &p);
@@ -78,8 +86,8 @@ class InputUser {
class inputUser final : public InputUser {
public:
- int32 user_id_;
- int64 access_hash_;
+ int64 user_id_{};
+ int64 access_hash_{};
static const int32 ID = -668391402;
inputUser() = default;
@@ -92,6 +100,7 @@ class inputUser final : public InputUser {
{
}
};
+
tl_object_ptr<InputUser> InputUser::fetch(TlBufferParser &p) {
#define FAIL(error) \
p.set_error(error); \
@@ -109,36 +118,28 @@ tl_object_ptr<InputUser> InputUser::fetch(TlBufferParser &p) {
class messages_requestEncryption final {
public:
tl_object_ptr<InputUser> user_id_;
- int32 random_id_;
+ int32 random_id_{};
BufferSlice g_a_;
static const int32 ID = -162681021;
messages_requestEncryption();
explicit messages_requestEncryption(TlBufferParser &p)
-#define FAIL(error) p.set_error(error)
: user_id_(TlFetchObject<InputUser>::parse(p))
, random_id_(TlFetchInt::parse(p))
- , g_a_(TlFetchBytes<BufferSlice>::parse(p))
-#undef FAIL
- {
+ , g_a_(TlFetchBytes<BufferSlice>::parse(p)) {
}
};
class inputEncryptedChat final {
public:
- int32 chat_id_;
- int64 access_hash_;
+ int32 chat_id_{};
+ int64 access_hash_{};
inputEncryptedChat() = default;
static const int32 ID = -247351839;
- explicit inputEncryptedChat(TlBufferParser &p)
-#define FAIL(error) p.set_error(error)
- : chat_id_(TlFetchInt::parse(p))
- , access_hash_(TlFetchLong::parse(p))
-#undef FAIL
- {
+ explicit inputEncryptedChat(TlBufferParser &p) : chat_id_(TlFetchInt::parse(p)), access_hash_(TlFetchLong::parse(p)) {
}
static tl_object_ptr<inputEncryptedChat> fetch(TlBufferParser &p) {
return make_tl_object<inputEncryptedChat>(p);
@@ -149,56 +150,49 @@ class messages_acceptEncryption final {
public:
tl_object_ptr<inputEncryptedChat> peer_;
BufferSlice g_b_;
- int64 key_fingerprint_;
+ int64 key_fingerprint_{};
messages_acceptEncryption() = default;
static const int32 ID = 1035731989;
explicit messages_acceptEncryption(TlBufferParser &p)
-#define FAIL(error) p.set_error(error)
: peer_(TlFetchBoxed<TlFetchObject<inputEncryptedChat>, -247351839>::parse(p))
, g_b_(TlFetchBytes<BufferSlice>::parse(p))
- , key_fingerprint_(TlFetchLong::parse(p))
-#undef FAIL
- {
+ , key_fingerprint_(TlFetchLong::parse(p)) {
}
};
class messages_sendEncryptedService final {
public:
tl_object_ptr<inputEncryptedChat> peer_;
- int64 random_id_;
+ int64 random_id_{};
BufferSlice data_;
messages_sendEncryptedService() = default;
static const int32 ID = 852769188;
explicit messages_sendEncryptedService(TlBufferParser &p)
-#define FAIL(error) p.set_error(error)
: peer_(TlFetchBoxed<TlFetchObject<inputEncryptedChat>, -247351839>::parse(p))
, random_id_(TlFetchLong::parse(p))
- , data_(TlFetchBytes<BufferSlice>::parse(p))
-#undef FAIL
- {
+ , data_(TlFetchBytes<BufferSlice>::parse(p)) {
}
};
class messages_sendEncrypted final {
public:
+ int32 flags_;
tl_object_ptr<inputEncryptedChat> peer_;
- int64 random_id_;
+ int64 random_id_{};
BufferSlice data_;
messages_sendEncrypted() = default;
- static const int32 ID = -1451792525;
+ static const int32 ID = 1157265941;
explicit messages_sendEncrypted(TlBufferParser &p)
-#define FAIL(error) p.set_error(error)
- : peer_(TlFetchBoxed<TlFetchObject<inputEncryptedChat>, -247351839>::parse(p))
+ : flags_(TlFetchInt::parse(p))
+ , peer_(TlFetchBoxed<TlFetchObject<inputEncryptedChat>, -247351839>::parse(p))
, random_id_(TlFetchLong::parse(p))
- , data_(TlFetchBytes<BufferSlice>::parse(p))
-#undef FAIL
- {
+ , data_(TlFetchBytes<BufferSlice>::parse(p)) {
}
};
@@ -217,16 +211,16 @@ static void downcast_call(TlBufferParser &p, F &&f) {
case messages_sendEncryptedService::ID:
return f(*make_tl_object<messages_sendEncryptedService>(p));
default:
- CHECK(0) << id;
+ LOG(ERROR) << "Unknown constructor " << id;
UNREACHABLE();
}
}
class messages_dhConfig final {
public:
- int32 g_;
+ int32 g_{};
BufferSlice p_;
- int32 version_;
+ int32 version_{};
BufferSlice random_;
messages_dhConfig() = default;
@@ -258,17 +252,17 @@ class messages_dhConfig final {
class encryptedChat final {
public:
- int32 id_;
- int64 access_hash_;
- int32 date_;
- int32 admin_id_;
- int32 participant_id_;
+ int32 id_{};
+ int64 access_hash_{};
+ int32 date_{};
+ int64 admin_id_{};
+ int64 participant_id_{};
BufferSlice g_a_or_b_;
- int64 key_fingerprint_;
+ int64 key_fingerprint_{};
encryptedChat() = default;
- encryptedChat(int32 id_, int64 access_hash_, int32 date_, int32 admin_id_, int32 participant_id_,
+ encryptedChat(int32 id_, int64 access_hash_, int32 date_, int64 admin_id_, int64 participant_id_,
BufferSlice &&g_a_or_b_, int64 key_fingerprint_)
: id_(id_)
, access_hash_(access_hash_)
@@ -309,7 +303,7 @@ class encryptedChat final {
class messages_sentEncryptedMessage final {
public:
- int32 date_;
+ int32 date_{};
messages_sentEncryptedMessage() = default;
@@ -342,32 +336,32 @@ static string prime_base64 =
"WC2xF40WnGvEZbDW_5yjko_vW5rk5Bj8Feg-vqD4f6n_Xu1wBQ3tKEn0e_lZ2VaFDOkphR8NgRX2NbEF7i5OFdBLJFS_b0-t8DSxBAMRnNjjuS_MW"
"w";
-class FakeDhCallback : public DhCallback {
+class FakeDhCallback final : public mtproto::DhCallback {
public:
- int is_good_prime(Slice prime_str) const override {
+ int is_good_prime(Slice prime_str) const final {
auto it = cache.find(prime_str.str());
if (it == cache.end()) {
return -1;
}
return it->second;
}
- void add_good_prime(Slice prime_str) const override {
+ void add_good_prime(Slice prime_str) const final {
cache[prime_str.str()] = 1;
}
- void add_bad_prime(Slice prime_str) const override {
+ void add_bad_prime(Slice prime_str) const final {
cache[prime_str.str()] = 0;
}
mutable std::map<string, int> cache;
};
-class FakeBinlog
+class FakeBinlog final
: public BinlogInterface
, public Actor {
public:
FakeBinlog() {
register_actor("FakeBinlog", this).release();
}
- void force_sync(Promise<> promise) override {
+ void force_sync(Promise<> promise) final {
if (pending_events_.empty()) {
pending_events_.emplace_back();
}
@@ -385,15 +379,15 @@ class FakeBinlog
}
}
}
- void force_flush() override {
+ void force_flush() final {
}
- uint64 next_id() override {
+ uint64 next_id() final {
auto res = last_id_;
last_id_++;
return res;
}
- uint64 next_id(int32 shift) override {
+ uint64 next_id(int32 shift) final {
auto res = last_id_;
last_id_ += shift;
return res;
@@ -418,16 +412,16 @@ class FakeBinlog
pending_events_.clear();
}
- void change_key(DbKey key, Promise<> promise) override {
+ void change_key(DbKey key, Promise<> promise) final {
}
protected:
- void close_impl(Promise<> promise) override {
+ void close_impl(Promise<> promise) final {
}
- void close_and_destroy_impl(Promise<> promise) override {
+ void close_and_destroy_impl(Promise<> promise) final {
}
- void add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise) override {
- auto event = BinlogEvent(std::move(raw_event));
+ void add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise, BinlogDebugInfo info) final {
+ auto event = BinlogEvent(std::move(raw_event), info);
LOG(INFO) << "ADD EVENT: " << event.id_ << " " << event;
pending_events_.emplace_back();
pending_events_.back().event = std::move(event);
@@ -441,20 +435,18 @@ class FakeBinlog
has_request_sync = false;
auto pos = static_cast<size_t>(Random::fast_uint64() % pending_events_.size());
// pos = pending_events_.size() - 1;
- std::vector<Promise<>> promises;
+ td::vector<Promise<Unit>> promises;
for (size_t i = 0; i <= pos; i++) {
auto &pending = pending_events_[i];
auto event = std::move(pending.event);
if (!event.empty()) {
LOG(INFO) << "SAVE EVENT: " << event.id_ << " " << event;
- events_processor_.add_event(std::move(event));
+ events_processor_.add_event(std::move(event)).ensure();
}
append(promises, std::move(pending.promises_));
}
pending_events_.erase(pending_events_.begin(), pending_events_.begin() + pos + 1);
- for (auto &promise : promises) {
- promise.set_value(Unit());
- }
+ set_promises(promises);
for (auto &event : pending_events_) {
if (event.sync_flag) {
@@ -463,10 +455,10 @@ class FakeBinlog
}
}
}
- void timeout_expired() override {
+ void timeout_expired() final {
do_force_sync();
}
- void wakeup() override {
+ void wakeup() final {
if (has_request_sync) {
do_force_sync();
}
@@ -478,42 +470,16 @@ class FakeBinlog
struct PendingEvent {
BinlogEvent event;
bool sync_flag = false;
- std::vector<Promise<>> promises_;
+ td::vector<Promise<Unit>> promises_;
};
std::vector<PendingEvent> pending_events_;
};
using FakeKeyValue = BinlogKeyValue<BinlogInterface>;
-class OldFakeKeyValue : public KeyValueSyncInterface {
- SeqNo set(string key, string value) override {
- kv_[key] = value;
- return 0;
- }
-
- SeqNo erase(const string &key) override {
- kv_.erase(key);
- return 0;
- }
-
- bool isset(const string &key) override {
- return kv_.count(key) > 0;
- }
-
- string get(const string &key) override {
- auto it = kv_.find(key);
- if (it != kv_.end()) {
- return it->second;
- }
- return string();
- }
-
- private:
- std::map<string, string> kv_;
-};
class Master;
-class FakeSecretChatContext : public SecretChatActor::Context {
+class FakeSecretChatContext final : public SecretChatActor::Context {
public:
FakeSecretChatContext(std::shared_ptr<BinlogInterface> binlog, std::shared_ptr<KeyValueSyncInterface> key_value,
std::shared_ptr<bool> close_flag, ActorShared<Master> master)
@@ -521,28 +487,28 @@ class FakeSecretChatContext : public SecretChatActor::Context {
, key_value_(std::move(key_value))
, close_flag_(std::move(close_flag))
, master_(std::move(master)) {
- secret_chat_db_ = std::make_unique<SecretChatDb>(key_value_, 1);
+ secret_chat_db_ = std::make_shared<SecretChatDb>(key_value_, 1);
net_query_creator_.stop_check(); // :(
}
- DhCallback *dh_callback() override {
+ mtproto::DhCallback *dh_callback() final {
return &fake_dh_callback_;
}
- NetQueryCreator &net_query_creator() override {
+ NetQueryCreator &net_query_creator() final {
return net_query_creator_;
}
- int32 unix_time() override {
+ int32 unix_time() final {
return static_cast<int32>(std::time(nullptr));
}
- bool close_flag() override {
+ bool close_flag() final {
return *close_flag_;
}
- BinlogInterface *binlog() override {
+ BinlogInterface *binlog() final {
return binlog_.get();
}
- SecretChatDb *secret_chat_db() override {
+ SecretChatDb *secret_chat_db() final {
return secret_chat_db_.get();
}
- std::shared_ptr<DhConfig> dh_config() override {
+ std::shared_ptr<DhConfig> dh_config() final {
static auto config = [] {
DhConfig dh_config;
dh_config.version = 12;
@@ -553,39 +519,37 @@ class FakeSecretChatContext : public SecretChatActor::Context {
return config;
}
- void set_dh_config(std::shared_ptr<DhConfig> dh_config) override {
+ void set_dh_config(std::shared_ptr<DhConfig> dh_config) final {
// empty
}
- bool get_config_option_boolean(const string &name) const override {
+ bool get_config_option_boolean(const string &name) const final {
return false;
}
// We don't want to expose the whole NetQueryDispatcher, MessagesManager and ContactsManager.
// So it is more clear which parts of MessagesManager is really used. And it is much easier to create tests.
- void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) override;
+ void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) final;
void on_update_secret_chat(int64 access_hash, UserId user_id, SecretChatState state, bool is_outbound, int32 ttl,
- int32 date, string key_hash, int32 layer) override {
+ int32 date, string key_hash, int32 layer, FolderId initial_folder_id) final {
}
- void on_inbound_message(UserId user_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::encryptedFile> file,
- tl_object_ptr<secret_api::decryptedMessage> message, Promise<>) override;
+ void on_inbound_message(UserId user_id, MessageId message_id, int32 date, unique_ptr<EncryptedFile> file,
+ tl_object_ptr<secret_api::decryptedMessage> message, Promise<>) final;
- void on_send_message_error(int64 random_id, Status error, Promise<>) override;
- void on_send_message_ack(int64 random_id) override;
- void on_send_message_ok(int64 random_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::EncryptedFile> file, Promise<>) override;
- void on_delete_messages(std::vector<int64> random_id, Promise<>) override;
- void on_flush_history(MessageId, Promise<>) override;
- void on_read_message(int64, Promise<>) override;
+ void on_send_message_error(int64 random_id, Status error, Promise<>) final;
+ void on_send_message_ack(int64 random_id) final;
+ void on_send_message_ok(int64 random_id, MessageId message_id, int32 date, unique_ptr<EncryptedFile> file,
+ Promise<>) final;
+ void on_delete_messages(std::vector<int64> random_id, Promise<>) final;
+ void on_flush_history(bool, MessageId, Promise<>) final;
+ void on_read_message(int64, Promise<>) final;
- void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id,
- Promise<> promise) override {
+ void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id, Promise<> promise) final {
}
void on_set_ttl(UserId user_id, MessageId message_id, int32 date, int32 ttl, int64 random_id,
- Promise<> promise) override {
+ Promise<> promise) final {
}
private:
@@ -598,13 +562,13 @@ class FakeSecretChatContext : public SecretChatActor::Context {
std::shared_ptr<SecretChatDb> secret_chat_db_;
};
-NetQueryCreator FakeSecretChatContext::net_query_creator_;
+NetQueryCreator FakeSecretChatContext::net_query_creator_{nullptr};
-class Master : public Actor {
+class Master final : public Actor {
public:
explicit Master(Status *status) : status_(status) {
}
- class SecretChatProxy : public Actor {
+ class SecretChatProxy final : public Actor {
public:
SecretChatProxy(string name, ActorShared<Master> parent) : name_(std::move(name)) {
binlog_ = std::make_shared<FakeBinlog>();
@@ -615,8 +579,8 @@ class Master : public Actor {
parent_ = parent.get();
parent_token_ = parent.token();
actor_ = create_actor<SecretChatActor>(
- "SecretChat " + name_, 123,
- std::make_unique<FakeSecretChatContext>(binlog_, key_value_, close_flag_, std::move(parent)), true);
+ PSLICE() << "SecretChat " << name_, 123,
+ td::make_unique<FakeSecretChatContext>(binlog_, key_value_, close_flag_, std::move(parent)), true);
on_binlog_replay_finish();
}
@@ -624,12 +588,11 @@ class Master : public Actor {
void add_inbound_message(int32 chat_id, BufferSlice data, uint64 crc) {
CHECK(crc64(data.as_slice()) == crc);
- auto event = std::make_unique<logevent::InboundSecretMessage>();
- event->qts = 0;
+ auto event = make_unique<log_event::InboundSecretMessage>();
event->chat_id = chat_id;
event->date = 0;
event->encrypted_message = std::move(data);
- event->qts_ack = PromiseCreator::lambda(
+ event->promise = PromiseCreator::lambda(
[actor_id = actor_id(this), chat_id, data = event->encrypted_message.copy(), crc](Result<> result) mutable {
if (result.is_ok()) {
LOG(INFO) << "FINISH add_inbound_message " << tag("crc", crc);
@@ -642,7 +605,7 @@ class Master : public Actor {
add_event(Event::delayed_closure(&SecretChatActor::add_inbound_message, std::move(event)));
}
- void send_message(tl_object_ptr<secret_api::decryptedMessage> message) {
+ void send_message(tl_object_ptr<secret_api::DecryptedMessage> message) {
BufferSlice serialized_message(serialize(*message));
auto resend_promise = PromiseCreator::lambda(
[actor_id = actor_id(this), serialized_message = std::move(serialized_message)](Result<> result) mutable {
@@ -671,7 +634,7 @@ class Master : public Actor {
int32 binlog_generation_ = 0;
void sync_binlog(int32 binlog_generation, Promise<> promise) {
if (binlog_generation != binlog_generation_) {
- return promise.set_error(Status::Error("binlog generation mismatch"));
+ return promise.set_error(Status::Error("Binlog generation mismatch"));
}
binlog_->force_sync(std::move(promise));
}
@@ -697,28 +660,28 @@ class Master : public Actor {
key_value_->external_init_finish(binlog_);
actor_ = create_actor<SecretChatActor>(
- "SecretChat " + name_, 123,
- std::make_unique<FakeSecretChatContext>(binlog_, key_value_, close_flag_,
- ActorShared<Master>(parent_, parent_token_)),
+ PSLICE() << "SecretChat " << name_, 123,
+ td::make_unique<FakeSecretChatContext>(binlog_, key_value_, close_flag_,
+ ActorShared<Master>(parent_, parent_token_)),
true);
for (auto &event : events) {
CHECK(event.type_ == LogEvent::HandlerType::SecretChats);
- auto r_message = logevent::SecretChatEvent::from_buffer_slice(event.data_as_buffer_slice());
+ auto r_message = log_event::SecretChatEvent::from_buffer_slice(event.data_as_buffer_slice());
LOG_IF(FATAL, r_message.is_error()) << "Failed to deserialize event: " << r_message.error();
auto message = r_message.move_as_ok();
- message->set_logevent_id(event.id_);
+ message->set_log_event_id(event.id_);
LOG(INFO) << "Process binlog event " << *message;
switch (message->get_type()) {
- case logevent::SecretChatEvent::Type::InboundSecretMessage:
+ case log_event::SecretChatEvent::Type::InboundSecretMessage:
send_closure_later(actor_, &SecretChatActor::replay_inbound_message,
- std::unique_ptr<logevent::InboundSecretMessage>(
- static_cast<logevent::InboundSecretMessage *>(message.release())));
+ unique_ptr<log_event::InboundSecretMessage>(
+ static_cast<log_event::InboundSecretMessage *>(message.release())));
break;
- case logevent::SecretChatEvent::Type::OutboundSecretMessage:
+ case log_event::SecretChatEvent::Type::OutboundSecretMessage:
send_closure_later(actor_, &SecretChatActor::replay_outbound_message,
- std::unique_ptr<logevent::OutboundSecretMessage>(
- static_cast<logevent::OutboundSecretMessage *>(message.release())));
+ unique_ptr<log_event::OutboundSecretMessage>(
+ static_cast<log_event::OutboundSecretMessage *>(message.release())));
break;
default:
UNREACHABLE();
@@ -729,7 +692,7 @@ class Master : public Actor {
}
void on_binlog_replay_finish() {
ready_ = true;
- LOG(INFO) << "on_binlog_replay_finish!";
+ LOG(INFO) << "Finish replay binlog";
send_closure(actor_, &SecretChatActor::binlog_replay_finish);
for (auto &event : pending_events_) {
send_event(actor_, std::move(event));
@@ -769,7 +732,7 @@ class Master : public Actor {
}
int32 bad_cnt_ = 0;
- void timeout_expired() override {
+ void timeout_expired() final {
LOG(INFO) << "TIMEOUT EXPIRED";
if (events_cnt_ < 4) {
bad_cnt_++;
@@ -795,21 +758,23 @@ class Master : public Actor {
auto &to() {
return get_by_id(3 - get_link_token());
}
- void start_up() override {
- set_context(std::make_shared<Global>());
+ void start_up() final {
+ auto old_context = set_context(std::make_shared<Global>());
alice_ = create_actor<SecretChatProxy>("SecretChatProxy alice", "alice", actor_shared(this, 1));
bob_ = create_actor<SecretChatProxy>("SecretChatProxy bob", "bob", actor_shared(this, 2));
- send_closure(alice_->get_actor_unsafe()->actor_, &SecretChatActor::create_chat, 2, 0, 123,
- PromiseCreator::lambda([actor_id = actor_id(this)](Result<SecretChatId> res) {
- send_closure(actor_id, &Master::got_secret_chat_id, std::move(res), 0);
+ send_closure(alice_.get_actor_unsafe()->actor_, &SecretChatActor::create_chat, UserId(static_cast<int64>(2)), 0,
+ 123, PromiseCreator::lambda([actor_id = actor_id(this)](Result<SecretChatId> res) {
+ send_closure(actor_id, &Master::got_secret_chat_id, std::move(res), false);
}));
}
- void got_secret_chat_id(Result<SecretChatId> res, int) { // second parameter is needed to workaround clang bug
+
+ void got_secret_chat_id(Result<SecretChatId> res, bool dummy) {
CHECK(res.is_ok());
auto id = res.move_as_ok();
LOG(INFO) << "SecretChatId = " << id;
}
- bool can_fail(NetQueryPtr &query) {
+
+ static bool can_fail(NetQueryPtr &query) {
static int cnt = 20;
if (cnt > 0) {
cnt--;
@@ -821,17 +786,18 @@ class Master : public Actor {
}
return false;
}
+
void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) {
- if (can_fail(query) && Random::fast(0, 1) == 0) {
+ if (can_fail(query) && Random::fast_bool()) {
LOG(INFO) << "Fail query " << query;
auto resend_promise =
- PromiseCreator::lambda([id = actor_shared(this, get_link_token()), callback_actor = callback.get(),
+ PromiseCreator::lambda([self = actor_shared(this, get_link_token()), callback_actor = callback.get(),
callback_token = callback.token()](Result<NetQueryPtr> r_net_query) mutable {
if (r_net_query.is_error()) {
- id.release();
+ self.release();
return;
}
- send_closure(std::move(id), &Master::send_net_query, r_net_query.move_as_ok(),
+ send_closure(std::move(self), &Master::send_net_query, r_net_query.move_as_ok(),
ActorShared<NetQueryCallback>(callback_actor, callback_token), true);
});
query->set_error(Status::Error(429, "Test error"));
@@ -866,31 +832,33 @@ class Master : public Actor {
config.version_ = 12;
auto storer = TLObjectStorer<my_api::messages_dhConfig>(config);
BufferSlice answer(storer.size());
- storer.store(answer.as_slice().ubegin());
+ auto real_size = storer.store(answer.as_slice().ubegin());
+ CHECK(real_size == answer.size());
net_query->set_ok(std::move(answer));
send_closure(std::move(callback), &NetQueryCallback::on_result, std::move(net_query));
}
void process_net_query(my_api::messages_requestEncryption &&request_encryption, NetQueryPtr net_query,
ActorShared<NetQueryCallback> callback) {
CHECK(get_link_token() == 1);
- send_closure(alice_->get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
+ send_closure(alice_.get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
make_tl_object<telegram_api::encryptedChatWaiting>(123, 321, 0, 1, 2));
- send_closure(
- bob_->get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
- make_tl_object<telegram_api::encryptedChatRequested>(123, 321, 0, 1, 2, request_encryption.g_a_.clone()));
+ send_closure(bob_.get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
+ make_tl_object<telegram_api::encryptedChatRequested>(0, false, 123, 321, 0, 1, 2,
+ request_encryption.g_a_.clone()));
net_query->clear();
}
void process_net_query(my_api::messages_acceptEncryption &&request_encryption, NetQueryPtr net_query,
ActorShared<NetQueryCallback> callback) {
CHECK(get_link_token() == 2);
- send_closure(alice_->get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
+ send_closure(alice_.get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
make_tl_object<telegram_api::encryptedChat>(123, 321, 0, 1, 2, request_encryption.g_b_.clone(),
request_encryption.key_fingerprint_));
my_api::encryptedChat encrypted_chat(123, 321, 0, 1, 2, BufferSlice(), request_encryption.key_fingerprint_);
auto storer = TLObjectStorer<my_api::encryptedChat>(encrypted_chat);
BufferSlice answer(storer.size());
- storer.store(answer.as_slice().ubegin());
+ auto real_size = storer.store(answer.as_slice().ubegin());
+ CHECK(real_size == answer.size());
net_query->set_ok(std::move(answer));
send_closure(std::move(callback), &NetQueryCallback::on_result, std::move(net_query));
send_closure(alice_, &SecretChatProxy::start_test);
@@ -898,49 +866,47 @@ class Master : public Actor {
send_ping(1, 5000);
set_timeout_in(1);
}
- void timeout_expired() override {
+ void timeout_expired() final {
send_message(1, "oppa");
send_message(2, "appo");
set_timeout_in(1);
}
- void send_ping(int id, int cnt) {
+ void send_ping(int32 id, int cnt) {
if (cnt % 200 == 0) {
- LOG(ERROR) << "send ping " << tag("id", id) << tag("cnt", cnt);
+ LOG(ERROR) << "Send ping " << tag("id", id) << tag("cnt", cnt);
} else {
- LOG(INFO) << "send ping " << tag("id", id) << tag("cnt", cnt);
+ LOG(INFO) << "Send ping " << tag("id", id) << tag("cnt", cnt);
}
string text = PSTRING() << "PING: " << cnt;
send_message(id, std::move(text));
}
- void send_message(int id, string text) {
+ void send_message(int32 id, string text) {
auto random_id = Random::secure_int64();
- LOG(INFO) << "send message: " << tag("id", id) << tag("text", text) << tag("random_id", random_id);
+ LOG(INFO) << "Send message: " << tag("id", id) << tag("text", text) << tag("random_id", random_id);
sent_messages_[random_id] = Message{id, text};
send_closure(get_by_id(id), &SecretChatProxy::send_message,
- secret_api::make_object<secret_api::decryptedMessage>(0, random_id, 0, text, Auto(), Auto(), Auto(),
- Auto(), 0));
+ secret_api::make_object<secret_api::decryptedMessage>(0, false /*ignored*/, random_id, 0, text, Auto(),
+ Auto(), Auto(), Auto(), 0));
}
void process_net_query(my_api::messages_sendEncryptedService &&message, NetQueryPtr net_query,
ActorShared<NetQueryCallback> callback) {
- process_net_query_send_enrypted(std::move(message.data_), std::move(net_query), std::move(callback));
+ process_net_query_send_encrypted(std::move(message.data_), std::move(net_query), std::move(callback));
}
void process_net_query(my_api::messages_sendEncrypted &&message, NetQueryPtr net_query,
ActorShared<NetQueryCallback> callback) {
- process_net_query_send_enrypted(std::move(message.data_), std::move(net_query), std::move(callback));
+ process_net_query_send_encrypted(std::move(message.data_), std::move(net_query), std::move(callback));
}
- void process_net_query_send_enrypted(BufferSlice data, NetQueryPtr net_query,
- ActorShared<NetQueryCallback> callback) {
- my_api::messages_sentEncryptedMessage sent_message;
- sent_message.date_ = 0;
- auto storer = TLObjectStorer<my_api::messages_sentEncryptedMessage>(sent_message);
- BufferSlice answer(storer.size());
- storer.store(answer.as_slice().ubegin());
+ void process_net_query_send_encrypted(BufferSlice data, NetQueryPtr net_query,
+ ActorShared<NetQueryCallback> callback) {
+ BufferSlice answer(8);
+ answer.as_slice().fill(0);
+ as<int32>(answer.as_slice().begin()) = static_cast<int32>(my_api::messages_sentEncryptedMessage::ID);
net_query->set_ok(std::move(answer));
send_closure(std::move(callback), &NetQueryCallback::on_result, std::move(net_query));
// We can't loose updates yet :(
auto crc = crc64(data.as_slice());
- LOG(INFO) << "send SecretChatProxy::add_inbound_message" << tag("crc", crc);
+ LOG(INFO) << "Send SecretChatProxy::add_inbound_message" << tag("crc", crc);
send_closure(to(), &SecretChatProxy::add_inbound_message, narrow_cast<int32>(3 - get_link_token()), std::move(data),
crc);
}
@@ -967,10 +933,10 @@ class Master : public Actor {
}
void on_send_message_error(int64 random_id, Status error, Promise<> promise) {
promise.set_value(Unit());
- LOG(INFO) << "on_send_message_error: " << tag("random_id", random_id) << error;
+ LOG(INFO) << "Receive send message error: " << tag("random_id", random_id) << error;
auto it = sent_messages_.find(random_id);
if (it == sent_messages_.end()) {
- LOG(INFO) << "TODO: try fix errors about message after it is sent";
+ LOG(INFO) << "TODO: try to fix errors about message after it is sent";
return;
}
CHECK(it != sent_messages_.end());
@@ -980,10 +946,10 @@ class Master : public Actor {
}
void on_send_message_ok(int64 random_id, Promise<> promise) {
promise.set_value(Unit());
- LOG(INFO) << "on_send_message_ok: " << tag("random_id", random_id);
+ LOG(INFO) << "Receive send message ok: " << tag("random_id", random_id);
auto it = sent_messages_.find(random_id);
if (it == sent_messages_.end()) {
- LOG(INFO) << "TODO: try fix errors about message after it is sent";
+ LOG(INFO) << "TODO: try to fix errors about message after it is sent";
return;
}
CHECK(it != sent_messages_.end());
@@ -1000,7 +966,7 @@ class Master : public Actor {
};
std::map<int64, Message> sent_messages_;
- void hangup_shared() override {
+ void hangup_shared() final {
LOG(INFO) << "GOT HANGUP: " << get_link_token();
send_closure(from(), &SecretChatProxy::on_closed);
}
@@ -1010,7 +976,7 @@ void FakeSecretChatContext::send_net_query(NetQueryPtr query, ActorShared<NetQue
send_closure(master_, &Master::send_net_query, std::move(query), std::move(callback), ordered);
}
void FakeSecretChatContext::on_inbound_message(UserId user_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::encryptedFile> file,
+ unique_ptr<EncryptedFile> file,
tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise) {
send_closure(master_, &Master::on_inbound_message, message->message_, std::move(promise));
}
@@ -1020,24 +986,22 @@ void FakeSecretChatContext::on_send_message_error(int64 random_id, Status error,
void FakeSecretChatContext::on_send_message_ack(int64 random_id) {
}
void FakeSecretChatContext::on_send_message_ok(int64 random_id, MessageId message_id, int32 date,
- tl_object_ptr<telegram_api::EncryptedFile> file, Promise<> promise) {
+ unique_ptr<EncryptedFile> file, Promise<> promise) {
send_closure(master_, &Master::on_send_message_ok, random_id, std::move(promise));
}
void FakeSecretChatContext::on_delete_messages(std::vector<int64> random_id, Promise<> promise) {
promise.set_value(Unit());
}
-void FakeSecretChatContext::on_flush_history(MessageId, Promise<> promise) {
- promise.set_error(Status::Error("unsupported"));
+void FakeSecretChatContext::on_flush_history(bool, MessageId, Promise<> promise) {
+ promise.set_error(Status::Error("Unsupported"));
}
void FakeSecretChatContext::on_read_message(int64, Promise<> promise) {
- promise.set_error(Status::Error("unsupported"));
+ promise.set_error(Status::Error("Unsupported"));
}
TEST(Secret, go) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
- ConcurrentScheduler sched;
- int threads_n = 0;
- sched.init(threads_n);
+ return;
+ ConcurrentScheduler sched(0, 0);
Status result;
sched.create_actor_unsafe<Master>(0, "HandshakeTestActor", &result).release();
diff --git a/protocols/Telegram/tdlib/td/test/secure_storage.cpp b/protocols/Telegram/tdlib/td/test/secure_storage.cpp
new file mode 100644
index 0000000000..774dead5ac
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/secure_storage.cpp
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SecureStorage.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/port/path.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tests.h"
+
+TEST(SecureStorage, secret) {
+ auto secret = td::secure_storage::Secret::create_new();
+ td::string key = "cucumber";
+ auto encrypted_secret = secret.encrypt(key, "", td::secure_storage::EnryptionAlgorithm::Sha512);
+ ASSERT_TRUE(encrypted_secret.as_slice() != secret.as_slice());
+ auto decrypted_secret = encrypted_secret.decrypt(key, "", td::secure_storage::EnryptionAlgorithm::Sha512).ok();
+ ASSERT_TRUE(secret.as_slice() == decrypted_secret.as_slice());
+ ASSERT_TRUE(encrypted_secret.decrypt("notcucumber", "", td::secure_storage::EnryptionAlgorithm::Sha512).is_error());
+}
+
+TEST(SecureStorage, simple) {
+ td::BufferSlice value("Small tale about cucumbers");
+ auto value_secret = td::secure_storage::Secret::create_new();
+
+ {
+ td::secure_storage::BufferSliceDataView value_view(value.copy());
+ td::BufferSlice prefix = td::secure_storage::gen_random_prefix(value_view.size());
+ td::secure_storage::BufferSliceDataView prefix_view(std::move(prefix));
+ td::secure_storage::ConcatDataView full_value_view(prefix_view, value_view);
+ auto hash = td::secure_storage::calc_value_hash(full_value_view).move_as_ok();
+
+ td::secure_storage::Encryptor encryptor(
+ td::secure_storage::calc_aes_cbc_state_sha512(PSLICE() << value_secret.as_slice() << hash.as_slice()),
+ full_value_view);
+ auto encrypted_value = encryptor.pread(0, encryptor.size()).move_as_ok();
+
+ td::secure_storage::Decryptor decryptor(
+ td::secure_storage::calc_aes_cbc_state_sha512(PSLICE() << value_secret.as_slice() << hash.as_slice()));
+ auto res = decryptor.append(encrypted_value.copy()).move_as_ok();
+ auto decrypted_hash = decryptor.finish().ok();
+ ASSERT_TRUE(decrypted_hash.as_slice() == hash.as_slice());
+ ASSERT_TRUE(res.as_slice() == value.as_slice());
+ }
+
+ {
+ auto encrypted_value = td::secure_storage::encrypt_value(value_secret, value.as_slice()).move_as_ok();
+ auto decrypted_value =
+ td::secure_storage::decrypt_value(value_secret, encrypted_value.hash, encrypted_value.data.as_slice())
+ .move_as_ok();
+ ASSERT_TRUE(decrypted_value.as_slice() == value.as_slice());
+ }
+
+ {
+ td::string value_path = "value.txt";
+ td::string encrypted_path = "encrypted.txt";
+ td::string decrypted_path = "decrypted.txt";
+ td::unlink(value_path).ignore();
+ td::unlink(encrypted_path).ignore();
+ td::unlink(decrypted_path).ignore();
+ td::string file_value(100000, 'a');
+ td::write_file(value_path, file_value).ensure();
+ auto hash = td::secure_storage::encrypt_file(value_secret, value_path, encrypted_path).move_as_ok();
+ td::secure_storage::decrypt_file(value_secret, hash, encrypted_path, decrypted_path).ensure();
+ ASSERT_TRUE(td::read_file(decrypted_path).move_as_ok().as_slice() == file_value);
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/test/set_with_position.cpp b/protocols/Telegram/tdlib/td/test/set_with_position.cpp
new file mode 100644
index 0000000000..c65b8bc362
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/set_with_position.cpp
@@ -0,0 +1,263 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/SetWithPosition.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/tests.h"
+
+#include <algorithm>
+#include <functional>
+#include <set>
+#include <utility>
+
+template <class T>
+class OldSetWithPosition {
+ public:
+ void add(T value) {
+ if (td::contains(values_, value)) {
+ return;
+ }
+ values_.push_back(value);
+ }
+ void remove(T value) {
+ auto it = std::find(values_.begin(), values_.end(), value);
+ if (it == values_.end()) {
+ return;
+ }
+ std::size_t i = it - values_.begin();
+ values_.erase(it);
+ if (pos_ > i) {
+ pos_--;
+ }
+ }
+ void reset_position() {
+ pos_ = 0;
+ }
+ T next() {
+ CHECK(has_next());
+ return values_[pos_++];
+ }
+ bool has_next() const {
+ return pos_ < values_.size();
+ }
+ void merge(OldSetWithPosition &&other) {
+ OldSetWithPosition res;
+ for (std::size_t i = 0; i < pos_; i++) {
+ res.add(values_[i]);
+ }
+ for (std::size_t i = 0; i < other.pos_; i++) {
+ res.add(other.values_[i]);
+ }
+ res.pos_ = res.values_.size();
+ for (std::size_t i = pos_; i < values_.size(); i++) {
+ res.add(values_[i]);
+ }
+ for (std::size_t i = other.pos_; i < other.values_.size(); i++) {
+ res.add(other.values_[i]);
+ }
+ *this = std::move(res);
+ }
+
+ private:
+ td::vector<T> values_;
+ std::size_t pos_{0};
+};
+
+template <class T, template <class> class SetWithPosition>
+class CheckedSetWithPosition {
+ public:
+ void add(int x) {
+ s_.add(x);
+ if (checked_.count(x) != 0) {
+ return;
+ }
+ not_checked_.insert(x);
+ }
+ void remove(int x) {
+ s_.remove(x);
+ checked_.erase(x);
+ not_checked_.erase(x);
+ }
+ bool has_next() const {
+ auto res = !not_checked_.empty();
+ //LOG(ERROR) << res;
+ ASSERT_EQ(res, s_.has_next());
+ return res;
+ }
+ void reset_position() {
+ s_.reset_position();
+ not_checked_.insert(checked_.begin(), checked_.end());
+ checked_ = {};
+ }
+
+ T next() {
+ CHECK(has_next());
+ auto next = s_.next();
+ //LOG(ERROR) << next;
+ ASSERT_TRUE(not_checked_.count(next) != 0);
+ not_checked_.erase(next);
+ checked_.insert(next);
+ return next;
+ }
+
+ void merge(CheckedSetWithPosition &&other) {
+ if (size() < other.size()) {
+ std::swap(*this, other);
+ std::swap(this->s_, other.s_);
+ }
+ for (auto x : other.checked_) {
+ not_checked_.erase(x);
+ checked_.insert(x);
+ }
+ for (auto x : other.not_checked_) {
+ if (checked_.count(x) != 0) {
+ continue;
+ }
+ not_checked_.insert(x);
+ }
+ s_.merge(std::move(other.s_));
+ }
+ std::size_t size() const {
+ return checked_.size() + not_checked_.size();
+ }
+
+ private:
+ std::set<T> checked_;
+ std::set<T> not_checked_;
+ td::SetWithPosition<T> s_;
+};
+
+template <template <class> class RawSet>
+static void test_hands() {
+ CheckedSetWithPosition<int, RawSet> a;
+ a.add(1);
+ a.add(2);
+ a.next();
+
+ CheckedSetWithPosition<int, RawSet> b;
+ b.add(1);
+ b.add(3);
+
+ a.merge(std::move(b));
+ while (a.has_next()) {
+ a.next();
+ }
+}
+
+#if !TD_CLANG
+template <template <class> class RawSet>
+static void test_stress() {
+ td::Random::Xorshift128plus rnd(123);
+ using Set = CheckedSetWithPosition<int, RawSet>;
+ for (int t = 0; t < 10; t++) {
+ td::vector<td::unique_ptr<Set>> sets(100);
+ for (auto &s : sets) {
+ s = td::make_unique<Set>();
+ }
+ int n;
+ auto merge = [&] {
+ int a = rnd.fast(0, n - 2);
+ int b = rnd.fast(a + 1, n - 1);
+ std::swap(sets[b], sets[n - 1]);
+ std::swap(sets[a], sets[n - 2]);
+ a = n - 2;
+ b = n - 1;
+ if (rnd.fast(0, 1) == 0) {
+ std::swap(sets[a], sets[b]);
+ }
+ sets[a]->merge(std::move(*sets[b]));
+ sets.pop_back();
+ };
+ auto next = [&] {
+ int i = rnd.fast(0, n - 1);
+ if (sets[i]->has_next()) {
+ sets[i]->next();
+ }
+ };
+ auto add = [&] {
+ int i = rnd.fast(0, n - 1);
+ int x = rnd.fast(0, 10);
+ sets[i]->add(x);
+ };
+ auto remove = [&] {
+ int i = rnd.fast(0, n - 1);
+ int x = rnd.fast(0, 10);
+ sets[i]->remove(x);
+ };
+ auto reset_position = [&] {
+ int i = rnd.fast(0, n - 1);
+ sets[i]->reset_position();
+ };
+ struct Step {
+ std::function<void()> func;
+ td::uint32 weight;
+ };
+ td::vector<Step> steps{{merge, 1}, {next, 10}, {add, 10}, {remove, 10}, {reset_position, 5}};
+ td::uint32 steps_sum = 0;
+ for (auto &step : steps) {
+ steps_sum += step.weight;
+ }
+
+ while (true) {
+ n = static_cast<int>(sets.size());
+ if (n == 1) {
+ break;
+ }
+ auto w = rnd() % steps_sum;
+ for (auto &step : steps) {
+ if (w < step.weight) {
+ step.func();
+ break;
+ }
+ w -= step.weight;
+ }
+ }
+ }
+}
+#endif
+
+template <template <class> class RawSet>
+static void test_speed() {
+ td::Random::Xorshift128plus rnd(123);
+ using Set = CheckedSetWithPosition<int, RawSet>;
+ const size_t total_size = 1 << 13;
+ td::vector<td::unique_ptr<Set>> sets(total_size);
+ for (size_t i = 0; i < sets.size(); i++) {
+ sets[i] = td::make_unique<Set>();
+ sets[i]->add(td::narrow_cast<int>(i));
+ }
+ for (size_t d = 1; d < sets.size(); d *= 2) {
+ for (size_t i = 0; i < sets.size(); i += 2 * d) {
+ size_t j = i + d;
+ CHECK(j < sets.size());
+ sets[i]->merge(std::move(*sets[j]));
+ }
+ }
+ ASSERT_EQ(total_size, sets[0]->size());
+}
+
+TEST(SetWithPosition, hands) {
+ test_hands<td::FastSetWithPosition>();
+ test_hands<OldSetWithPosition>();
+ test_hands<td::SetWithPosition>();
+}
+
+#if !TD_CLANG
+TEST(SetWithPosition, stress) {
+ test_stress<td::FastSetWithPosition>();
+ test_stress<OldSetWithPosition>();
+ test_stress<td::SetWithPosition>();
+}
+#endif
+
+TEST(SetWithPosition, speed) {
+ test_speed<td::FastSetWithPosition>();
+ test_speed<td::SetWithPosition>();
+}
diff --git a/protocols/Telegram/tdlib/td/test/string_cleaning.cpp b/protocols/Telegram/tdlib/td/test/string_cleaning.cpp
index 6fbd8150a4..442f82d913 100644
--- a/protocols/Telegram/tdlib/td/test/string_cleaning.cpp
+++ b/protocols/Telegram/tdlib/td/test/string_cleaning.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -9,37 +9,34 @@
#include "td/utils/Slice.h"
#include "td/utils/tests.h"
-REGISTER_TESTS(string_cleaning);
-
-using namespace td;
-
TEST(StringCleaning, clean_name) {
- ASSERT_EQ("@mention", clean_name("@mention", 1000000));
- ASSERT_EQ("@mention", clean_name(" @mention ", 1000000));
- ASSERT_EQ("@MENTION", clean_name("@MENTION", 1000000));
- ASSERT_EQ("ЛШТШФУМ", clean_name("ЛШТШФУМ", 1000000));
- ASSERT_EQ("....", clean_name("....", 1000000));
- ASSERT_EQ(". ASD ..", clean_name(". ASD ..", 1000000));
- ASSERT_EQ(". ASD", clean_name(". ASD ..", 10));
- ASSERT_EQ(". ASD", clean_name(".\n\n\nASD\n\n\n..", 10));
- ASSERT_EQ("", clean_name("\n\n\n\n\n\n", 1000000));
- ASSERT_EQ("", clean_name("\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\n\n\n\n\n\n \n\xC2\xA0 \xC2\xA0 \n", 100000));
- ASSERT_EQ("abc", clean_name("\xC2\xA0\xC2\xA0"
- "abc\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0",
- 1000000));
-};
+ ASSERT_EQ("@mention", td::clean_name("@mention", 1000000));
+ ASSERT_EQ("@mention", td::clean_name(" @mention ", 1000000));
+ ASSERT_EQ("@MENTION", td::clean_name("@MENTION", 1000000));
+ ASSERT_EQ("ЛШТШФУМ", td::clean_name("ЛШТШФУМ", 1000000));
+ ASSERT_EQ("....", td::clean_name("....", 1000000));
+ ASSERT_EQ(". ASD ..", td::clean_name(". ASD ..", 1000000));
+ ASSERT_EQ(". ASD", td::clean_name(". ASD ..", 10));
+ ASSERT_EQ(". ASD", td::clean_name(".\n\n\nASD\n\n\n..", 10));
+ ASSERT_EQ("", td::clean_name("\n\n\n\n\n\n", 1000000));
+ ASSERT_EQ("",
+ td::clean_name("\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\n\n\n\n\n\n \n\xC2\xA0 \xC2\xA0 \n", 100000));
+ ASSERT_EQ("abc", td::clean_name("\xC2\xA0\xC2\xA0"
+ "abc\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0",
+ 1000000));
+}
TEST(StringCleaning, clean_username) {
- ASSERT_EQ("@mention", clean_username("@mention"));
- ASSERT_EQ("@mention", clean_username(" @mention "));
- ASSERT_EQ("@mention", clean_username("@MENTION"));
- ASSERT_EQ("ЛШТШФУМ", clean_username("ЛШТШФУМ"));
- ASSERT_EQ("", clean_username("...."));
- ASSERT_EQ("asd", clean_username(". ASD .."));
-};
+ ASSERT_EQ("@mention", td::clean_username("@mention"));
+ ASSERT_EQ("@mention", td::clean_username(" @mention "));
+ ASSERT_EQ("@mention", td::clean_username("@MENTION"));
+ ASSERT_EQ("ЛШТШФУМ", td::clean_username("ЛШТШФУМ"));
+ ASSERT_EQ("", td::clean_username("...."));
+ ASSERT_EQ("asd", td::clean_username(". ASD .."));
+}
-static void check_clean_input_string(string str, string expected, bool expected_result) {
- auto result = clean_input_string(str);
+static void check_clean_input_string(td::string str, const td::string &expected, bool expected_result) {
+ auto result = td::clean_input_string(str);
ASSERT_EQ(expected_result, result);
if (result) {
ASSERT_EQ(expected, str);
@@ -48,7 +45,7 @@ static void check_clean_input_string(string str, string expected, bool expected_
TEST(StringCleaning, clean_input_string) {
check_clean_input_string("/abc", "/abc", true);
- check_clean_input_string(string(50000, 'a'), string(34996, 'a'), true);
+ check_clean_input_string(td::string(50000, 'a'), td::string(34996, 'a'), true);
check_clean_input_string("\xff", "", false);
check_clean_input_string("\xc0\x80", "", false);
check_clean_input_string("\xd0", "", false);
@@ -59,18 +56,25 @@ TEST(StringCleaning, clean_input_string) {
check_clean_input_string("\xf4\x8f\xbf\xc0", "", false);
check_clean_input_string("\r\r\r\r\r\r\r", "", true);
check_clean_input_string("\r\n\r\n\r\n\r\n\r\n\r\n\r", "\n\n\n\n\n\n", true);
- check_clean_input_string(Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
- "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
+ check_clean_input_string(td::Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13"
+ "\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
.str(),
" \x0a \x21", true);
check_clean_input_string(
"\xe2\x80\xa7\xe2\x80\xa8\xe2\x80\xa9\xe2\x80\xaa\xe2\x80\xab\xe2\x80\xac\xe2\x80\xad\xe2\x80\xae\xe2\x80\xaf",
"\xe2\x80\xa7\xe2\x80\xaf", true);
+ check_clean_input_string(
+ "\xe2\x80\x8f\xe2\x80\x8f \xe2\x80\x8e\xe2\x80\x8e\xe2\x80\x8e\xe2\x80\x8c \xe2\x80\x8f\xe2\x80\x8e "
+ "\xe2\x80\x8f",
+ "\xe2\x80\x8c\xe2\x80\x8f \xe2\x80\x8c\xe2\x80\x8c\xe2\x80\x8e\xe2\x80\x8c \xe2\x80\x8c\xe2\x80\x8e "
+ "\xe2\x80\x8f",
+ true);
check_clean_input_string("\xcc\xb3\xcc\xbf\xcc\x8a", "", true);
}
-static void check_strip_empty_characters(string str, size_t max_length, string expected) {
- ASSERT_EQ(expected, strip_empty_characters(str, max_length));
+static void check_strip_empty_characters(td::string str, std::size_t max_length, const td::string &expected,
+ bool strip_rtlo = false) {
+ ASSERT_EQ(expected, td::strip_empty_characters(std::move(str), max_length, strip_rtlo));
}
TEST(StringCleaning, strip_empty_characters) {
@@ -78,28 +82,31 @@ TEST(StringCleaning, strip_empty_characters) {
check_strip_empty_characters("/abc", 3, "/ab");
check_strip_empty_characters("/abc", 0, "");
check_strip_empty_characters("/abc", 10000000, "/abc");
- string spaces =
- u8"\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u202F\u205F\u3000\uFEFF"
- u8"\uFFFC\uFFFC";
- string spaces_replace = " ";
- string empty = "\xE2\x80\x8C\xE2\x80\x8D\xE2\x80\xAE\xC2\xA0\xC2\xA0";
+ td::string spaces =
+ u8"\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2800\u3000\uFFFC"
+ u8"\uFFFC";
+ td::string spaces_replace = " ";
+ td::string rtlo = u8"\u202E";
+ td::string empty = "\xE2\x80\x8B\xE2\x80\x8C\xE2\x80\x8D\xE2\x80\x8E\xE2\x80\x8F\xE2\x80\xAE\xC2\xA0\xC2\xA0";
check_strip_empty_characters(spaces, 1000000, "");
+ check_strip_empty_characters(spaces + rtlo, 1000000, "");
+ check_strip_empty_characters(spaces + rtlo, 1000000, "", true);
+ check_strip_empty_characters(spaces + rtlo + "a", 1000000, rtlo + "a");
+ check_strip_empty_characters(spaces + rtlo + "a", 1000000, "a", true);
check_strip_empty_characters(empty, 1000000, "");
check_strip_empty_characters(empty + "a", 1000000, empty + "a");
check_strip_empty_characters(spaces + empty + spaces + "abc" + spaces, 1000000, empty + spaces_replace + "abc");
- check_strip_empty_characters(spaces + spaces + empty + spaces + spaces + empty + empty, 1000000,
- empty + spaces_replace + spaces_replace + empty + empty);
+ check_strip_empty_characters(spaces + spaces + empty + spaces + spaces + empty + empty, 1000000, "");
check_strip_empty_characters("\r\r\r\r\r\r\r", 1000000, "");
check_strip_empty_characters("\r\n\r\n\r\n\r\n\r\n\r\n\r", 1000000, "");
- check_strip_empty_characters(Slice(" \t\r\n\0\va\v\0\n\r\t ").str(), 1000000, "a");
- check_strip_empty_characters(
- Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
- "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
- .str(),
- 1000000,
- "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
- "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21");
+ check_strip_empty_characters(td::Slice(" \t\r\n\0\va\v\0\n\r\t ").str(), 1000000, "a");
+ check_strip_empty_characters(td::Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12"
+ "\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
+ .str(),
+ 1000000,
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21");
check_strip_empty_characters("\xcc\xb3\xcc\xbf\xcc\x8a", 2, "\xcc\xb3\xcc\xbf");
check_strip_empty_characters(
"\xe2\x80\xa7\xe2\x80\xa8\xe2\x80\xa9\xe2\x80\xaa\xe2\x80\xab\xe2\x80\xac\xe2\x80\xad\xe2\x80\xae", 3,
diff --git a/protocols/Telegram/tdlib/td/test/tdclient.cpp b/protocols/Telegram/tdlib/td/test/tdclient.cpp
index 7cef0fcbc0..6ced39da30 100644
--- a/protocols/Telegram/tdlib/td/test/tdclient.cpp
+++ b/protocols/Telegram/tdlib/td/test/tdclient.cpp
@@ -1,63 +1,60 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "data.h"
-#include "td/actor/PromiseFuture.h"
-
+#include "td/telegram/Client.h"
#include "td/telegram/ClientActor.h"
-
+#include "td/telegram/files/PartsManager.h"
#include "td/telegram/td_api.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+#include "td/actor/PromiseFuture.h"
+
#include "td/utils/base64.h"
#include "td/utils/BufferedFd.h"
+#include "td/utils/common.h"
#include "td/utils/filesystem.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h"
+#include "td/utils/port/sleep.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Promise.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/tests.h"
+#include <atomic>
#include <cstdio>
#include <functional>
#include <map>
#include <memory>
+#include <mutex>
+#include <set>
#include <utility>
-REGISTER_TESTS(tdclient);
-
-namespace td {
-
template <class T>
static void check_td_error(T &result) {
- CHECK(result->get_id() != td_api::error::ID) << to_string(result);
+ LOG_CHECK(result->get_id() != td::td_api::error::ID) << to_string(result);
}
-static void rmrf(CSlice path) {
- td::walk_path(path, [](CSlice path, bool is_dir) {
- if (is_dir) {
- td::rmdir(path).ignore();
- } else {
- td::unlink(path).ignore();
- }
- });
-}
-
-class TestClient : public Actor {
+class TestClient final : public td::Actor {
public:
- explicit TestClient(string name) : name_(std::move(name)) {
+ explicit TestClient(td::string name) : name_(std::move(name)) {
}
struct Update {
- uint64 id;
- tl_object_ptr<td_api::Object> object;
- Update(uint64 id, tl_object_ptr<td_api::Object> object) : id(id), object(std::move(object)) {
+ td::uint64 id;
+ td::tl_object_ptr<td::td_api::Object> object;
+ Update(td::uint64 id, td::tl_object_ptr<td::td_api::Object> object) : id(id), object(std::move(object)) {
}
};
class Listener {
@@ -74,33 +71,37 @@ class TestClient : public Actor {
}
virtual void on_update(std::shared_ptr<Update> update) = 0;
};
- void close(Promise<> close_promise) {
+ void close(td::Promise<> close_promise) {
close_promise_ = std::move(close_promise);
- td_.reset();
+ td_client_.reset();
}
- unique_ptr<TdCallback> make_td_callback() {
- class TdCallbackImpl : public TdCallback {
+ td::unique_ptr<td::TdCallback> make_td_callback() {
+ class TdCallbackImpl final : public td::TdCallback {
public:
- explicit TdCallbackImpl(ActorId<TestClient> client) : client_(client) {
+ explicit TdCallbackImpl(td::ActorId<TestClient> client) : client_(client) {
}
- void on_result(uint64 id, tl_object_ptr<td_api::Object> result) override {
+ void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) final {
send_closure(client_, &TestClient::on_result, id, std::move(result));
}
- void on_error(uint64 id, tl_object_ptr<td_api::error> error) override {
+ void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) final {
send_closure(client_, &TestClient::on_error, id, std::move(error));
}
- void on_closed() override {
+ TdCallbackImpl(const TdCallbackImpl &) = delete;
+ TdCallbackImpl &operator=(const TdCallbackImpl &) = delete;
+ TdCallbackImpl(TdCallbackImpl &&) = delete;
+ TdCallbackImpl &operator=(TdCallbackImpl &&) = delete;
+ ~TdCallbackImpl() final {
send_closure(client_, &TestClient::on_closed);
}
private:
- ActorId<TestClient> client_;
+ td::ActorId<TestClient> client_;
};
- return make_unique<TdCallbackImpl>(actor_id(this));
+ return td::make_unique<TdCallbackImpl>(actor_id(this));
}
- void add_listener(std::unique_ptr<Listener> listener) {
+ void add_listener(td::unique_ptr<Listener> listener) {
auto *ptr = listener.get();
listeners_.push_back(std::move(listener));
ptr->start_listen(this);
@@ -124,10 +125,10 @@ class TestClient : public Actor {
}
}
- void on_result(uint64 id, tl_object_ptr<td_api::Object> result) {
+ void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) {
on_update(std::make_shared<Update>(id, std::move(result)));
}
- void on_error(uint64 id, tl_object_ptr<td_api::error> error) {
+ void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) {
on_update(std::make_shared<Update>(id, std::move(error)));
}
void on_update(std::shared_ptr<Update> update) {
@@ -141,29 +142,29 @@ class TestClient : public Actor {
stop();
}
- void start_up() override {
- rmrf(name_);
- set_context(std::make_shared<td::ActorContext>());
+ void start_up() final {
+ td::rmrf(name_).ignore();
+ auto old_context = set_context(std::make_shared<td::ActorContext>());
set_tag(name_);
LOG(INFO) << "START UP!";
- td_ = create_actor<ClientActor>("Td-proxy", make_td_callback());
+ td_client_ = td::create_actor<td::ClientActor>("Td-proxy", make_td_callback());
}
- ActorOwn<ClientActor> td_;
+ td::ActorOwn<td::ClientActor> td_client_;
- string name_;
+ td::string name_;
private:
- std::vector<std::unique_ptr<Listener>> listeners_;
- std::vector<Listener *> pending_remove_;
+ td::vector<td::unique_ptr<Listener>> listeners_;
+ td::vector<Listener *> pending_remove_;
- Promise<> close_promise_;
+ td::Promise<> close_promise_;
};
-class Task : public TestClient::Listener {
+class TestClinetTask : public TestClient::Listener {
public:
- void on_update(std::shared_ptr<TestClient::Update> update) override {
+ void on_update(std::shared_ptr<TestClient::Update> update) final {
auto it = sent_queries_.find(update->id);
if (it != sent_queries_.end()) {
it->second(std::move(update->object));
@@ -171,7 +172,7 @@ class Task : public TestClient::Listener {
}
process_update(update);
}
- void start_listen(TestClient *client) override {
+ void start_listen(TestClient *client) final {
client_ = client;
start_up();
}
@@ -179,16 +180,16 @@ class Task : public TestClient::Listener {
}
template <class F>
- void send_query(tl_object_ptr<td_api::Function> function, F callback) {
+ void send_query(td::tl_object_ptr<td::td_api::Function> function, F callback) {
auto id = current_query_id_++;
sent_queries_[id] = std::forward<F>(callback);
- send_closure(client_->td_, &ClientActor::request, id, std::move(function));
+ send_closure(client_->td_client_, &td::ClientActor::request, id, std::move(function));
}
protected:
- std::map<uint64, std::function<void(tl_object_ptr<td_api::Object>)>> sent_queries_;
- TestClient *client_;
- uint64 current_query_id_ = 1;
+ std::map<td::uint64, std::function<void(td::tl_object_ptr<td::td_api::Object>)>> sent_queries_;
+ TestClient *client_ = nullptr;
+ td::uint64 current_query_id_ = 1;
virtual void start_up() {
}
@@ -197,52 +198,58 @@ class Task : public TestClient::Listener {
}
};
-class DoAuthentication : public Task {
+class DoAuthentication final : public TestClinetTask {
public:
- DoAuthentication(string name, string phone, string code, Promise<> promise)
+ DoAuthentication(td::string name, td::string phone, td::string code, td::Promise<> promise)
: name_(std::move(name)), phone_(std::move(phone)), code_(std::move(code)), promise_(std::move(promise)) {
}
- void start_up() override {
- send_query(make_tl_object<td_api::getAuthorizationState>(),
+ void start_up() final {
+ send_query(td::make_tl_object<td::td_api::getAuthorizationState>(),
[this](auto res) { this->process_authorization_state(std::move(res)); });
}
- void process_authorization_state(tl_object_ptr<td_api::Object> authorization_state) {
+ void process_authorization_state(td::tl_object_ptr<td::td_api::Object> authorization_state) {
start_flag_ = true;
- tl_object_ptr<td_api::Function> function;
+ td::tl_object_ptr<td::td_api::Function> function;
switch (authorization_state->get_id()) {
- case td_api::authorizationStateWaitEncryptionKey::ID:
- function = make_tl_object<td_api::checkDatabaseEncryptionKey>();
+ case td::td_api::authorizationStateWaitPhoneNumber::ID:
+ function = td::make_tl_object<td::td_api::setAuthenticationPhoneNumber>(phone_, nullptr);
+ break;
+ case td::td_api::authorizationStateWaitEmailAddress::ID:
+ function = td::make_tl_object<td::td_api::setAuthenticationEmailAddress>("alice_test@gmail.com");
break;
- case td_api::authorizationStateWaitPhoneNumber::ID:
- function = make_tl_object<td_api::setAuthenticationPhoneNumber>(phone_, false, true);
+ case td::td_api::authorizationStateWaitEmailCode::ID:
+ function = td::make_tl_object<td::td_api::checkAuthenticationEmailCode>(
+ td::make_tl_object<td::td_api::emailAddressAuthenticationCode>(code_));
break;
- case td_api::authorizationStateWaitCode::ID:
- function = make_tl_object<td_api::checkAuthenticationCode>(code_, name_, "");
+ case td::td_api::authorizationStateWaitCode::ID:
+ function = td::make_tl_object<td::td_api::checkAuthenticationCode>(code_);
break;
- case td_api::authorizationStateWaitTdlibParameters::ID: {
- auto parameters = td_api::make_object<td_api::tdlibParameters>();
- parameters->use_test_dc_ = true;
- parameters->database_directory_ = name_ + TD_DIR_SLASH;
- parameters->use_message_database_ = true;
- parameters->use_secret_chats_ = true;
- parameters->api_id_ = 94575;
- parameters->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2";
- parameters->system_language_code_ = "en";
- parameters->device_model_ = "Desktop";
- parameters->system_version_ = "Unknown";
- parameters->application_version_ = "tdclient-test";
- parameters->ignore_file_names_ = false;
- parameters->enable_storage_optimizer_ = true;
- function = td_api::make_object<td_api::setTdlibParameters>(std::move(parameters));
+ case td::td_api::authorizationStateWaitRegistration::ID:
+ function = td::make_tl_object<td::td_api::registerUser>(name_, "");
+ break;
+ case td::td_api::authorizationStateWaitTdlibParameters::ID: {
+ auto request = td::td_api::make_object<td::td_api::setTdlibParameters>();
+ request->use_test_dc_ = true;
+ request->database_directory_ = name_ + TD_DIR_SLASH;
+ request->use_message_database_ = true;
+ request->use_secret_chats_ = true;
+ request->api_id_ = 94575;
+ request->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2";
+ request->system_language_code_ = "en";
+ request->device_model_ = "Desktop";
+ request->application_version_ = "tdclient-test";
+ request->enable_storage_optimizer_ = true;
+ function = std::move(request);
break;
}
- case td_api::authorizationStateReady::ID:
+ case td::td_api::authorizationStateReady::ID:
on_authorization_ready();
return;
default:
- CHECK(false) << "Unexpected authorization state " << to_string(authorization_state);
+ LOG(ERROR) << "Unexpected authorization state " << to_string(authorization_state);
+ UNREACHABLE();
}
- send_query(std::move(function), [this](auto res) { CHECK(res->get_id() == td_api::ok::ID) << to_string(res); });
+ send_query(std::move(function), [](auto res) { LOG_CHECK(res->get_id() == td::td_api::ok::ID) << to_string(res); });
}
void on_authorization_ready() {
LOG(INFO) << "GOT AUTHORIZED";
@@ -250,78 +257,82 @@ class DoAuthentication : public Task {
}
private:
- string name_;
- string phone_;
- string code_;
- Promise<> promise_;
+ td::string name_;
+ td::string phone_;
+ td::string code_;
+ td::Promise<> promise_;
bool start_flag_{false};
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
if (!start_flag_) {
return;
}
if (!update->object) {
return;
}
- if (update->object->get_id() == td_api::updateAuthorizationState::ID) {
- auto o = std::move(update->object);
- process_authorization_state(std::move(static_cast<td_api::updateAuthorizationState &>(*o).authorization_state_));
+ if (update->object->get_id() == td::td_api::updateAuthorizationState::ID) {
+ auto update_authorization_state = td::move_tl_object_as<td::td_api::updateAuthorizationState>(update->object);
+ process_authorization_state(std::move(update_authorization_state->authorization_state_));
}
}
};
-class SetUsername : public Task {
+class SetUsername final : public TestClinetTask {
public:
- SetUsername(string username, Promise<> promise) : username_(std::move(username)), promise_(std::move(promise)) {
+ SetUsername(td::string username, td::Promise<> promise)
+ : username_(std::move(username)), promise_(std::move(promise)) {
}
private:
- string username_;
- Promise<> promise_;
- int32 self_id_;
- string tag_;
+ td::string username_;
+ td::Promise<> promise_;
+ td::int64 self_id_ = 0;
+ td::string tag_;
- void start_up() override {
- send_query(make_tl_object<td_api::getMe>(), [this](auto res) { this->process_me_user(std::move(res)); });
+ void start_up() final {
+ send_query(td::make_tl_object<td::td_api::getMe>(), [this](auto res) { this->process_me_user(std::move(res)); });
}
- void process_me_user(tl_object_ptr<td_api::Object> res) {
- CHECK(res->get_id() == td_api::user::ID);
- auto user = move_tl_object_as<td_api::user>(res);
+ void process_me_user(td::tl_object_ptr<td::td_api::Object> res) {
+ CHECK(res->get_id() == td::td_api::user::ID);
+ auto user = td::move_tl_object_as<td::td_api::user>(res);
self_id_ = user->id_;
- if (user->username_ != username_) {
+ auto current_username = user->usernames_ != nullptr ? user->usernames_->editable_username_ : td::string();
+ if (current_username != username_) {
LOG(INFO) << "SET USERNAME: " << username_;
- send_query(make_tl_object<td_api::setUsername>(username_), [this](auto res) {
- CHECK(res->get_id() == td_api::ok::ID);
+ send_query(td::make_tl_object<td::td_api::setUsername>(username_), [this](auto res) {
+ CHECK(res->get_id() == td::td_api::ok::ID);
this->send_self_message();
});
} else {
send_self_message();
}
}
+
void send_self_message() {
- tag_ = PSTRING() << format::as_hex(Random::secure_int64());
-
- send_query(make_tl_object<td_api::createPrivateChat>(self_id_, false), [this](auto res) {
- CHECK(res->get_id() == td_api::chat::ID);
- auto chat = move_tl_object_as<td_api::chat>(res);
- this->send_query(
- make_tl_object<td_api::sendMessage>(
- chat->id_, 0, false, false, nullptr,
- make_tl_object<td_api::inputMessageText>(
- make_tl_object<td_api::formattedText>(PSTRING() << tag_ << " INIT", Auto()), false, false)),
- [](auto res) {});
+ tag_ = PSTRING() << td::format::as_hex(td::Random::secure_int64());
+
+ send_query(td::make_tl_object<td::td_api::createPrivateChat>(self_id_, false), [this](auto res) {
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::td_api::chat>(res);
+ this->send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat->id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageText>(
+ td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " INIT", td::Auto()),
+ false, false)),
+ [](auto res) {});
});
}
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
if (!update->object) {
return;
}
- if (update->object->get_id() == td_api::updateMessageSendSucceeded::ID) {
- auto updateNewMessage = move_tl_object_as<td_api::updateMessageSendSucceeded>(update->object);
+ if (update->object->get_id() == td::td_api::updateMessageSendSucceeded::ID) {
+ auto updateNewMessage = td::move_tl_object_as<td::td_api::updateMessageSendSucceeded>(update->object);
auto &message = updateNewMessage->message_;
- if (message->content_->get_id() == td_api::messageText::ID) {
- auto messageText = move_tl_object_as<td_api::messageText>(message->content_);
+ if (message->content_->get_id() == td::td_api::messageText::ID) {
+ auto messageText = td::move_tl_object_as<td::td_api::messageText>(message->content_);
auto text = messageText->text_->text_;
if (text.substr(0, tag_.size()) == tag_) {
LOG(INFO) << "GOT SELF MESSAGE";
@@ -332,28 +343,29 @@ class SetUsername : public Task {
}
};
-class CheckTestA : public Task {
+class CheckTestA final : public TestClinetTask {
public:
- CheckTestA(string tag, Promise<> promise) : tag_(std::move(tag)), promise_(std::move(promise)) {
+ CheckTestA(td::string tag, td::Promise<> promise) : tag_(std::move(tag)), promise_(std::move(promise)) {
}
private:
- string tag_;
- Promise<> promise_;
- string previous_text_;
+ td::string tag_;
+ td::Promise<> promise_;
+ td::string previous_text_;
int cnt_ = 20;
- void process_update(std::shared_ptr<TestClient::Update> update) override {
- if (update->object->get_id() == td_api::updateNewMessage::ID) {
- auto updateNewMessage = move_tl_object_as<td_api::updateNewMessage>(update->object);
+
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
+ if (update->object->get_id() == td::td_api::updateNewMessage::ID) {
+ auto updateNewMessage = td::move_tl_object_as<td::td_api::updateNewMessage>(update->object);
auto &message = updateNewMessage->message_;
- if (message->content_->get_id() == td_api::messageText::ID) {
- auto messageText = move_tl_object_as<td_api::messageText>(message->content_);
+ if (message->content_->get_id() == td::td_api::messageText::ID) {
+ auto messageText = td::move_tl_object_as<td::td_api::messageText>(message->content_);
auto text = messageText->text_->text_;
if (text.substr(0, tag_.size()) == tag_) {
- CHECK(text > previous_text_) << tag("now", text) << tag("previous", previous_text_);
+ LOG_CHECK(text > previous_text_) << td::tag("now", text) << td::tag("previous", previous_text_);
previous_text_ = text;
cnt_--;
- LOG(INFO) << "GOT " << tag("text", text) << tag("left", cnt_);
+ LOG(INFO) << "GOT " << td::tag("text", text) << td::tag("left", cnt_);
if (cnt_ == 0) {
return stop();
}
@@ -363,98 +375,101 @@ class CheckTestA : public Task {
}
};
-class TestA : public Task {
+class TestA final : public TestClinetTask {
public:
- TestA(string tag, string username) : tag_(std::move(tag)), username_(std::move(username)) {
+ TestA(td::string tag, td::string username) : tag_(std::move(tag)), username_(std::move(username)) {
}
- void start_up() override {
- send_query(make_tl_object<td_api::searchPublicChat>(username_), [this](auto res) {
- CHECK(res->get_id() == td_api::chat::ID);
- auto chat = move_tl_object_as<td_api::chat>(res);
+
+ void start_up() final {
+ send_query(td::make_tl_object<td::td_api::searchPublicChat>(username_), [this](auto res) {
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::td_api::chat>(res);
for (int i = 0; i < 20; i++) {
- this->send_query(make_tl_object<td_api::sendMessage>(
- chat->id_, 0, false, false, nullptr,
- make_tl_object<td_api::inputMessageText>(
- make_tl_object<td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), Auto()),
- false, false)),
- [&](auto res) { this->stop(); });
+ this->send_query(
+ td::make_tl_object<td::td_api::sendMessage>(
+ chat->id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageText>(
+ td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), td::Auto()),
+ false, false)),
+ [&](auto res) { this->stop(); });
}
});
}
private:
- string tag_;
- string username_;
+ td::string tag_;
+ td::string username_;
};
-class TestSecretChat : public Task {
+class TestSecretChat final : public TestClinetTask {
public:
- TestSecretChat(string tag, string username) : tag_(std::move(tag)), username_(std::move(username)) {
+ TestSecretChat(td::string tag, td::string username) : tag_(std::move(tag)), username_(std::move(username)) {
}
- void start_up() override {
+ void start_up() final {
auto f = [this](auto res) {
- CHECK(res->get_id() == td_api::chat::ID);
- auto chat = move_tl_object_as<td_api::chat>(res);
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::td_api::chat>(res);
this->chat_id_ = chat->id_;
- this->secret_chat_id_ = move_tl_object_as<td_api::chatTypeSecret>(chat->type_)->secret_chat_id_;
+ this->secret_chat_id_ = td::move_tl_object_as<td::td_api::chatTypeSecret>(chat->type_)->secret_chat_id_;
};
- send_query(make_tl_object<td_api::searchPublicChat>(username_), [this, f = std::move(f)](auto res) mutable {
- CHECK(res->get_id() == td_api::chat::ID);
- auto chat = move_tl_object_as<td_api::chat>(res);
- CHECK(chat->type_->get_id() == td_api::chatTypePrivate::ID);
- auto info = move_tl_object_as<td_api::chatTypePrivate>(chat->type_);
- this->send_query(make_tl_object<td_api::createNewSecretChat>(info->user_id_), std::move(f));
+ send_query(td::make_tl_object<td::td_api::searchPublicChat>(username_), [this, f = std::move(f)](auto res) mutable {
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::td_api::chat>(res);
+ CHECK(chat->type_->get_id() == td::td_api::chatTypePrivate::ID);
+ auto info = td::move_tl_object_as<td::td_api::chatTypePrivate>(chat->type_);
+ this->send_query(td::make_tl_object<td::td_api::createNewSecretChat>(info->user_id_), std::move(f));
});
}
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
if (!update->object) {
return;
}
- if (update->object->get_id() == td_api::updateSecretChat::ID) {
- auto update_secret_chat = move_tl_object_as<td_api::updateSecretChat>(update->object);
+ if (update->object->get_id() == td::td_api::updateSecretChat::ID) {
+ auto update_secret_chat = td::move_tl_object_as<td::td_api::updateSecretChat>(update->object);
if (update_secret_chat->secret_chat_->id_ != secret_chat_id_ ||
- update_secret_chat->secret_chat_->state_->get_id() != td_api::secretChatStateReady::ID) {
+ update_secret_chat->secret_chat_->state_->get_id() != td::td_api::secretChatStateReady::ID) {
return;
}
LOG(INFO) << "SEND ENCRYPTED MESSAGES";
for (int i = 0; i < 20; i++) {
- send_query(make_tl_object<td_api::sendMessage>(
- chat_id_, 0, false, false, nullptr,
- make_tl_object<td_api::inputMessageText>(
- make_tl_object<td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), Auto()), false,
- false)),
- [](auto res) {});
+ send_query(
+ td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageText>(
+ td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), td::Auto()),
+ false, false)),
+ [](auto res) {});
}
}
}
private:
- string tag_;
- string username_;
- int64 secret_chat_id_ = 0;
- int64 chat_id_ = 0;
+ td::string tag_;
+ td::string username_;
+ td::int64 secret_chat_id_ = 0;
+ td::int64 chat_id_ = 0;
};
-class TestFileGenerated : public Task {
+class TestFileGenerated final : public TestClinetTask {
public:
- TestFileGenerated(string tag, string username) : tag_(std::move(tag)), username_(std::move(username)) {
+ TestFileGenerated(td::string tag, td::string username) : tag_(std::move(tag)), username_(std::move(username)) {
}
- void start_up() override {
+ void start_up() final {
}
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
if (!update->object) {
return;
}
- if (update->object->get_id() == td_api::updateNewMessage::ID) {
- auto updateNewMessage = move_tl_object_as<td_api::updateNewMessage>(update->object);
+ if (update->object->get_id() == td::td_api::updateNewMessage::ID) {
+ auto updateNewMessage = td::move_tl_object_as<td::td_api::updateNewMessage>(update->object);
auto &message = updateNewMessage->message_;
chat_id_ = message->chat_id_;
- if (message->content_->get_id() == td_api::messageText::ID) {
- auto messageText = move_tl_object_as<td_api::messageText>(message->content_);
+ if (message->content_->get_id() == td::td_api::messageText::ID) {
+ auto messageText = td::move_tl_object_as<td::td_api::messageText>(message->content_);
auto text = messageText->text_->text_;
if (text.substr(0, tag_.size()) == tag_) {
if (text.substr(tag_.size() + 1) == "ONE_FILE") {
@@ -462,47 +477,49 @@ class TestFileGenerated : public Task {
}
}
}
- } else if (update->object->get_id() == td_api::updateFileGenerationStart::ID) {
- auto info = move_tl_object_as<td_api::updateFileGenerationStart>(update->object);
+ } else if (update->object->get_id() == td::td_api::updateFileGenerationStart::ID) {
+ auto info = td::move_tl_object_as<td::td_api::updateFileGenerationStart>(update->object);
generate_file(info->generation_id_, info->original_path_, info->destination_path_, info->conversion_);
- } else if (update->object->get_id() == td_api::updateFile::ID) {
- auto file = move_tl_object_as<td_api::updateFile>(update->object);
+ } else if (update->object->get_id() == td::td_api::updateFile::ID) {
+ auto file = td::move_tl_object_as<td::td_api::updateFile>(update->object);
LOG(INFO) << to_string(file);
}
}
+
void one_file() {
LOG(ERROR) << "Start ONE_FILE test";
- string file_path = string("test_documents") + TD_DIR_SLASH + "a.txt";
- mkpath(file_path).ensure();
+ auto file_path = PSTRING() << "test_documents" << TD_DIR_SLASH << "a.txt";
+ td::mkpath(file_path).ensure();
auto raw_file =
- FileFd::open(file_path, FileFd::Flags::Create | FileFd::Flags::Truncate | FileFd::Flags::Write).move_as_ok();
- auto file = BufferedFd<FileFd>(std::move(raw_file));
+ td::FileFd::open(file_path, td::FileFd::Flags::Create | td::FileFd::Flags::Truncate | td::FileFd::Flags::Write)
+ .move_as_ok();
+ auto file = td::BufferedFd<td::FileFd>(std::move(raw_file));
for (int i = 1; i < 100000; i++) {
file.write(PSLICE() << i << "\n").ensure();
}
file.flush_write().ensure(); // important
file.close();
- this->send_query(make_tl_object<td_api::sendMessage>(
- chat_id_, 0, false, false, nullptr,
- make_tl_object<td_api::inputMessageDocument>(
- make_tl_object<td_api::inputFileGenerated>(file_path, "square", 0),
- make_tl_object<td_api::inputThumbnail>(
- make_tl_object<td_api::inputFileGenerated>(file_path, "thumbnail", 0), 0, 0),
- make_tl_object<td_api::formattedText>(tag_, Auto()))),
- [](auto res) { check_td_error(res); });
-
- this->send_query(
- make_tl_object<td_api::sendMessage>(chat_id_, 0, false, false, nullptr,
- make_tl_object<td_api::inputMessageDocument>(
- make_tl_object<td_api::inputFileGenerated>(file_path, "square", 0),
- nullptr, make_tl_object<td_api::formattedText>(tag_, Auto()))),
- [](auto res) { check_td_error(res); });
+ send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageDocument>(
+ td::make_tl_object<td::td_api::inputFileGenerated>(file_path, "square", 0),
+ td::make_tl_object<td::td_api::inputThumbnail>(
+ td::make_tl_object<td::td_api::inputFileGenerated>(file_path, "thumbnail", 0), 0, 0),
+ true, td::make_tl_object<td::td_api::formattedText>(tag_, td::Auto()))),
+ [](auto res) { check_td_error(res); });
+
+ send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageDocument>(
+ td::make_tl_object<td::td_api::inputFileGenerated>(file_path, "square", 0), nullptr, true,
+ td::make_tl_object<td::td_api::formattedText>(tag_, td::Auto()))),
+ [](auto res) { check_td_error(res); });
}
- friend class GenerateFile;
- class GenerateFile : public Actor {
+ class GenerateFile final : public td::Actor {
public:
- GenerateFile(Task *parent, int64 id, string original_path, string destination_path, string conversion)
+ GenerateFile(TestClinetTask *parent, td::int64 id, td::string original_path, td::string destination_path,
+ td::string conversion)
: parent_(parent)
, id_(id)
, original_path_(std::move(original_path))
@@ -511,26 +528,27 @@ class TestFileGenerated : public Task {
}
private:
- Task *parent_;
- int64 id_;
- string original_path_;
- string destination_path_;
- string conversion_;
+ TestClinetTask *parent_;
+ td::int64 id_;
+ td::string original_path_;
+ td::string destination_path_;
+ td::string conversion_;
FILE *from = nullptr;
FILE *to = nullptr;
- void start_up() override {
+ void start_up() final {
from = std::fopen(original_path_.c_str(), "rb");
CHECK(from);
to = std::fopen(destination_path_.c_str(), "wb");
CHECK(to);
yield();
}
- void loop() override {
+
+ void loop() final {
int cnt = 0;
while (true) {
- uint32 x;
+ td::uint32 x;
auto r = std::fscanf(from, "%u", &x);
if (r != 1) {
return stop();
@@ -542,98 +560,100 @@ class TestFileGenerated : public Task {
}
auto ready = std::ftell(to);
LOG(ERROR) << "READY: " << ready;
- parent_->send_query(make_tl_object<td_api::setFileGenerationProgress>(
- id_, 1039823 /*yeah, exact size of this file*/, narrow_cast<int32>(ready)),
+ parent_->send_query(td::make_tl_object<td::td_api::setFileGenerationProgress>(
+ id_, 1039823 /*yeah, exact size of this file*/, td::narrow_cast<td::int32>(ready)),
[](auto result) { check_td_error(result); });
set_timeout_in(0.02);
}
- void tear_down() override {
+ void tear_down() final {
std::fclose(from);
std::fclose(to);
- parent_->send_query(make_tl_object<td_api::finishFileGeneration>(id_, nullptr),
+ parent_->send_query(td::make_tl_object<td::td_api::finishFileGeneration>(id_, nullptr),
[](auto result) { check_td_error(result); });
}
};
- void generate_file(int64 id, string original_path, string destination_path, string conversion) {
- LOG(ERROR) << "Generate file " << tag("id", id) << tag("original_path", original_path)
- << tag("destination_path", destination_path) << tag("conversion", conversion);
+
+ void generate_file(td::int64 id, const td::string &original_path, const td::string &destination_path,
+ const td::string &conversion) {
+ LOG(ERROR) << "Generate file " << td::tag("id", id) << td::tag("original_path", original_path)
+ << td::tag("destination_path", destination_path) << td::tag("conversion", conversion);
if (conversion == "square") {
- create_actor<GenerateFile>("GenerateFile", this, id, original_path, destination_path, conversion).release();
+ td::create_actor<GenerateFile>("GenerateFile", this, id, original_path, destination_path, conversion).release();
} else if (conversion == "thumbnail") {
- write_file(destination_path, base64url_decode(Slice(thumbnail, thumbnail_size)).ok()).ensure();
- this->send_query(make_tl_object<td_api::finishFileGeneration>(id, nullptr),
- [](auto result) { check_td_error(result); });
+ td::write_file(destination_path, td::base64url_decode(td::Slice(thumbnail, thumbnail_size)).ok()).ensure();
+ send_query(td::make_tl_object<td::td_api::finishFileGeneration>(id, nullptr),
+ [](auto result) { check_td_error(result); });
} else {
- LOG(FATAL) << "unknown " << tag("conversion", conversion);
+ LOG(FATAL) << "Unknown " << td::tag("conversion", conversion);
}
}
private:
- string tag_;
- string username_;
- int64 chat_id_ = 0;
+ td::string tag_;
+ td::string username_;
+ td::int64 chat_id_ = 0;
};
-class CheckTestC : public Task {
+class CheckTestC final : public TestClinetTask {
public:
- CheckTestC(string username, string tag, Promise<> promise)
+ CheckTestC(td::string username, td::string tag, td::Promise<> promise)
: username_(std::move(username)), tag_(std::move(tag)), promise_(std::move(promise)) {
}
- void start_up() override {
- send_query(make_tl_object<td_api::searchPublicChat>(username_), [this](auto res) {
- CHECK(res->get_id() == td_api::chat::ID);
- auto chat = move_tl_object_as<td_api::chat>(res);
+ void start_up() final {
+ send_query(td::make_tl_object<td::td_api::searchPublicChat>(username_), [this](auto res) {
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::td_api::chat>(res);
chat_id_ = chat->id_;
this->one_file();
});
}
private:
- string username_;
- string tag_;
- Promise<> promise_;
- int64 chat_id_;
+ td::string username_;
+ td::string tag_;
+ td::Promise<> promise_;
+ td::int64 chat_id_ = 0;
void one_file() {
- this->send_query(
- make_tl_object<td_api::sendMessage>(
- chat_id_, 0, false, false, nullptr,
- make_tl_object<td_api::inputMessageText>(
- make_tl_object<td_api::formattedText>(PSTRING() << tag_ << " ONE_FILE", Auto()), false, false)),
- [](auto res) { check_td_error(res); });
+ send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageText>(
+ td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " ONE_FILE", td::Auto()),
+ false, false)),
+ [](auto res) { check_td_error(res); });
}
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
if (!update->object) {
return;
}
- if (update->object->get_id() == td_api::updateNewMessage::ID) {
- auto updateNewMessage = move_tl_object_as<td_api::updateNewMessage>(update->object);
+ if (update->object->get_id() == td::td_api::updateNewMessage::ID) {
+ auto updateNewMessage = td::move_tl_object_as<td::td_api::updateNewMessage>(update->object);
auto &message = updateNewMessage->message_;
- if (message->content_->get_id() == td_api::messageDocument::ID) {
- auto messageDocument = move_tl_object_as<td_api::messageDocument>(message->content_);
+ if (message->content_->get_id() == td::td_api::messageDocument::ID) {
+ auto messageDocument = td::move_tl_object_as<td::td_api::messageDocument>(message->content_);
auto text = messageDocument->caption_->text_;
if (text.substr(0, tag_.size()) == tag_) {
file_id_to_check_ = messageDocument->document_->document_->id_;
LOG(ERROR) << "GOT FILE " << to_string(messageDocument->document_->document_);
- this->send_query(make_tl_object<td_api::downloadFile>(file_id_to_check_, 1),
- [](auto res) { check_td_error(res); });
+ send_query(td::make_tl_object<td::td_api::downloadFile>(file_id_to_check_, 1, 0, 0, false),
+ [](auto res) { check_td_error(res); });
}
}
- } else if (update->object->get_id() == td_api::updateFile::ID) {
- auto updateFile = move_tl_object_as<td_api::updateFile>(update->object);
+ } else if (update->object->get_id() == td::td_api::updateFile::ID) {
+ auto updateFile = td::move_tl_object_as<td::td_api::updateFile>(update->object);
if (updateFile->file_->id_ == file_id_to_check_ && (updateFile->file_->local_->is_downloading_completed_)) {
check_file(updateFile->file_->local_->path_);
}
}
}
- void check_file(CSlice path) {
+ void check_file(td::CSlice path) {
FILE *from = std::fopen(path.c_str(), "rb");
CHECK(from);
- uint32 x;
- uint32 y = 1;
+ td::uint32 x;
+ td::uint32 y = 1;
while (std::fscanf(from, "%u", &x) == 1) {
CHECK(x == y * y);
y++;
@@ -641,47 +661,47 @@ class CheckTestC : public Task {
std::fclose(from);
stop();
}
- int32 file_id_to_check_ = 0;
+ td::int32 file_id_to_check_ = 0;
};
-class LoginTestActor : public Actor {
+class LoginTestActor final : public td::Actor {
public:
- explicit LoginTestActor(Status *status) : status_(status) {
- *status_ = Status::OK();
+ explicit LoginTestActor(td::Status *status) : status_(status) {
+ *status_ = td::Status::OK();
}
private:
- Status *status_;
- ActorOwn<TestClient> alice_;
- ActorOwn<TestClient> bob_;
+ td::Status *status_;
+ td::ActorOwn<TestClient> alice_;
+ td::ActorOwn<TestClient> bob_;
- string alice_phone_ = "9996636437";
- string bob_phone_ = "9996636438";
- string alice_username_ = "alice_" + alice_phone_;
- string bob_username_ = "bob_" + bob_phone_;
+ td::string alice_phone_ = "9996636437";
+ td::string bob_phone_ = "9996636438";
+ td::string alice_username_ = "alice_" + alice_phone_;
+ td::string bob_username_ = "bob_" + bob_phone_;
- string stage_name_;
+ td::string stage_name_;
- void begin_stage(string stage_name, double timeout) {
+ void begin_stage(td::string stage_name, double timeout) {
LOG(WARNING) << "Begin stage '" << stage_name << "'";
stage_name_ = std::move(stage_name);
set_timeout_in(timeout);
}
- void start_up() override {
+ void start_up() final {
begin_stage("Logging in", 160);
- alice_ = create_actor<TestClient>("AliceClient", "alice");
- bob_ = create_actor<TestClient>("BobClient", "bob");
-
- send_closure(alice_, &TestClient::add_listener,
- std::make_unique<DoAuthentication>(
- "alice", alice_phone_, "33333",
- PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec))));
-
- send_closure(bob_, &TestClient::add_listener,
- std::make_unique<DoAuthentication>(
- "bob", bob_phone_, "33333",
- PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec))));
+ alice_ = td::create_actor<TestClient>("AliceClient", "alice");
+ bob_ = td::create_actor<TestClient>("BobClient", "bob");
+
+ td::send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<DoAuthentication>(
+ "alice", alice_phone_, "33333",
+ td::create_event_promise(self_closure(this, &LoginTestActor::start_up_fence_dec))));
+
+ td::send_closure(bob_, &TestClient::add_listener,
+ td::make_unique<DoAuthentication>(
+ "bob", bob_phone_, "33333",
+ td::create_event_promise(self_closure(this, &LoginTestActor::start_up_fence_dec))));
}
int start_up_fence_ = 3;
@@ -691,34 +711,34 @@ class LoginTestActor : public Actor {
init();
} else if (start_up_fence_ == 1) {
return init();
- class WaitActor : public Actor {
+ class WaitActor final : public td::Actor {
public:
- WaitActor(double timeout, Promise<> promise) : timeout_(timeout), promise_(std::move(promise)) {
+ WaitActor(double timeout, td::Promise<> promise) : timeout_(timeout), promise_(std::move(promise)) {
}
- void start_up() override {
+ void start_up() final {
set_timeout_in(timeout_);
}
- void timeout_expired() override {
+ void timeout_expired() final {
stop();
}
private:
double timeout_;
- Promise<> promise_;
+ td::Promise<> promise_;
};
- create_actor<WaitActor>("WaitActor", 2,
- PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec)))
+ td::create_actor<WaitActor>("WaitActor", 2,
+ td::create_event_promise(self_closure(this, &LoginTestActor::start_up_fence_dec)))
.release();
}
}
void init() {
- send_closure(alice_, &TestClient::add_listener,
- std::make_unique<SetUsername>(
- alice_username_, PromiseCreator::event(self_closure(this, &LoginTestActor::init_fence_dec))));
- send_closure(bob_, &TestClient::add_listener,
- std::make_unique<SetUsername>(
- bob_username_, PromiseCreator::event(self_closure(this, &LoginTestActor::init_fence_dec))));
+ td::send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<SetUsername>(alice_username_, td::create_event_promise(self_closure(
+ this, &LoginTestActor::init_fence_dec))));
+ td::send_closure(bob_, &TestClient::add_listener,
+ td::make_unique<SetUsername>(
+ bob_username_, td::create_event_promise(self_closure(this, &LoginTestActor::init_fence_dec))));
}
int init_fence_ = 2;
@@ -728,7 +748,7 @@ class LoginTestActor : public Actor {
}
}
- int32 test_a_fence_ = 2;
+ int test_a_fence_ = 2;
void test_a_fence() {
if (--test_a_fence_ == 0) {
test_b();
@@ -737,33 +757,33 @@ class LoginTestActor : public Actor {
void test_a() {
begin_stage("Ready to create chats", 80);
- string alice_tag = PSTRING() << format::as_hex(Random::secure_int64());
- string bob_tag = PSTRING() << format::as_hex(Random::secure_int64());
-
- send_closure(bob_, &TestClient::add_listener,
- std::make_unique<CheckTestA>(
- alice_tag, PromiseCreator::event(self_closure(this, &LoginTestActor::test_a_fence))));
- send_closure(alice_, &TestClient::add_listener,
- std::make_unique<CheckTestA>(
- bob_tag, PromiseCreator::event(self_closure(this, &LoginTestActor::test_a_fence))));
-
- send_closure(alice_, &TestClient::add_listener, std::make_unique<TestA>(alice_tag, bob_username_));
- send_closure(bob_, &TestClient::add_listener, std::make_unique<TestA>(bob_tag, alice_username_));
- // send_closure(alice_, &TestClient::add_listener, std::make_unique<TestChat>(bob_username_));
+ td::string alice_tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
+ td::string bob_tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
+
+ td::send_closure(bob_, &TestClient::add_listener,
+ td::make_unique<CheckTestA>(
+ alice_tag, td::create_event_promise(self_closure(this, &LoginTestActor::test_a_fence))));
+ td::send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<CheckTestA>(
+ bob_tag, td::create_event_promise(self_closure(this, &LoginTestActor::test_a_fence))));
+
+ td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestA>(alice_tag, bob_username_));
+ td::send_closure(bob_, &TestClient::add_listener, td::make_unique<TestA>(bob_tag, alice_username_));
+ // td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestChat>(bob_username_));
}
- void timeout_expired() override {
+ void timeout_expired() final {
LOG(FATAL) << "Timeout expired in stage '" << stage_name_ << "'";
}
- int32 test_b_fence_ = 1;
+ int test_b_fence_ = 1;
void test_b_fence() {
if (--test_b_fence_ == 0) {
test_c();
}
}
- int32 test_c_fence_ = 1;
+ int test_c_fence_ = 1;
void test_c_fence() {
if (--test_c_fence_ == 0) {
finish();
@@ -772,45 +792,47 @@ class LoginTestActor : public Actor {
void test_b() {
begin_stage("Create secret chat", 40);
- string tag = PSTRING() << format::as_hex(Random::secure_int64());
+ td::string tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
- send_closure(
+ td::send_closure(
bob_, &TestClient::add_listener,
- std::make_unique<CheckTestA>(tag, PromiseCreator::event(self_closure(this, &LoginTestActor::test_b_fence))));
- send_closure(alice_, &TestClient::add_listener, std::make_unique<TestSecretChat>(tag, bob_username_));
+ td::make_unique<CheckTestA>(tag, td::create_event_promise(self_closure(this, &LoginTestActor::test_b_fence))));
+ td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestSecretChat>(tag, bob_username_));
}
void test_c() {
begin_stage("Send generated file", 240);
- string tag = PSTRING() << format::as_hex(Random::secure_int64());
+ td::string tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
- send_closure(bob_, &TestClient::add_listener,
- std::make_unique<CheckTestC>(
- alice_username_, tag, PromiseCreator::event(self_closure(this, &LoginTestActor::test_c_fence))));
- send_closure(alice_, &TestClient::add_listener, std::make_unique<TestFileGenerated>(tag, bob_username_));
+ td::send_closure(
+ bob_, &TestClient::add_listener,
+ td::make_unique<CheckTestC>(alice_username_, tag,
+ td::create_event_promise(self_closure(this, &LoginTestActor::test_c_fence))));
+ td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestFileGenerated>(tag, bob_username_));
}
- int32 finish_fence_ = 2;
+ int finish_fence_ = 2;
void finish_fence() {
finish_fence_--;
if (finish_fence_ == 0) {
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
stop();
}
}
+
void finish() {
- send_closure(alice_, &TestClient::close, PromiseCreator::event(self_closure(this, &LoginTestActor::finish_fence)));
- send_closure(bob_, &TestClient::close, PromiseCreator::event(self_closure(this, &LoginTestActor::finish_fence)));
+ td::send_closure(alice_, &TestClient::close,
+ td::create_event_promise(self_closure(this, &LoginTestActor::finish_fence)));
+ td::send_closure(bob_, &TestClient::close,
+ td::create_event_promise(self_closure(this, &LoginTestActor::finish_fence)));
}
};
-class Tdclient_login : public td::Test {
+class Tdclient_login final : public td::Test {
public:
using Test::Test;
bool step() final {
if (!is_inited_) {
- SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG) + 2);
- sched_.init(4);
sched_.create_actor_unsafe<LoginTestActor>(0, "LoginTestActor", &result_).release();
sched_.start();
is_inited_ = true;
@@ -830,8 +852,347 @@ class Tdclient_login : public td::Test {
private:
bool is_inited_ = false;
- ConcurrentScheduler sched_;
- Status result_;
+ td::ConcurrentScheduler sched_{4, 0};
+ td::Status result_;
};
-Tdclient_login Tdclient_login("Tdclient_login");
-}; // namespace td
+//RegisterTest<Tdclient_login> Tdclient_login("Tdclient_login");
+
+TEST(Client, Simple) {
+ td::Client client;
+ // client.execute({1, td::td_api::make_object<td::td_api::setLogTagVerbosityLevel>("actor", 1)});
+ client.send({3, td::make_tl_object<td::td_api::testSquareInt>(3)});
+ while (true) {
+ auto result = client.receive(10);
+ if (result.id == 3) {
+ auto test_int = td::td_api::move_object_as<td::td_api::testInt>(result.object);
+ ASSERT_EQ(test_int->value_, 9);
+ break;
+ }
+ }
+}
+
+TEST(Client, SimpleMulti) {
+ std::vector<td::Client> clients(7);
+ //for (auto &client : clients) {
+ //client.execute({1, td::td_api::make_object<td::td_api::setLogTagVerbosityLevel>("td_requests", 1)});
+ //}
+
+ for (size_t i = 0; i < clients.size(); i++) {
+ clients[i].send({i + 2, td::make_tl_object<td::td_api::testSquareInt>(3)});
+ if (td::Random::fast_bool()) {
+ clients[i].send({1, td::make_tl_object<td::td_api::close>()});
+ }
+ }
+
+ for (size_t i = 0; i < clients.size(); i++) {
+ while (true) {
+ auto result = clients[i].receive(10);
+ if (result.id == i + 2) {
+ CHECK(result.object->get_id() == td::td_api::testInt::ID);
+ auto test_int = td::td_api::move_object_as<td::td_api::testInt>(result.object);
+ ASSERT_EQ(test_int->value_, 9);
+ break;
+ }
+ }
+ }
+}
+
+#if !TD_THREAD_UNSUPPORTED
+TEST(Client, Multi) {
+ td::vector<td::thread> threads;
+ std::atomic<int> ok_count{0};
+ for (int i = 0; i < 4; i++) {
+ threads.emplace_back([i, &ok_count] {
+ for (int j = 0; j < 1000; j++) {
+ td::Client client;
+ auto request_id = static_cast<td::uint64>(j + 2 + 1000 * i);
+ client.send({request_id, td::make_tl_object<td::td_api::testSquareInt>(3)});
+ if (j & 1) {
+ client.send({1, td::make_tl_object<td::td_api::close>()});
+ }
+ while (true) {
+ auto result = client.receive(10);
+ if (result.id == request_id) {
+ ok_count++;
+ if ((j & 1) == 0) {
+ client.send({1, td::make_tl_object<td::td_api::close>()});
+ }
+ }
+ if (result.id == 0 && result.object != nullptr &&
+ result.object->get_id() == td::td_api::updateAuthorizationState::ID &&
+ static_cast<const td::td_api::updateAuthorizationState *>(result.object.get())
+ ->authorization_state_->get_id() == td::td_api::authorizationStateClosed::ID) {
+ ok_count++;
+ break;
+ }
+ }
+ }
+ });
+ }
+
+ for (auto &thread : threads) {
+ thread.join();
+ }
+ ASSERT_EQ(8 * 1000, ok_count.load());
+}
+
+TEST(Client, Manager) {
+ td::vector<td::thread> threads;
+ td::ClientManager client;
+#if !TD_EVENTFD_UNSUPPORTED // Client must be used from a single thread if there is no EventFd
+ int threads_n = 4;
+#else
+ int threads_n = 1;
+#endif
+ int clients_n = 1000;
+ client.send(0, 3, td::make_tl_object<td::td_api::testSquareInt>(3));
+ client.send(-1, 3, td::make_tl_object<td::td_api::testSquareInt>(3));
+ for (int i = 0; i < threads_n; i++) {
+ threads.emplace_back([&] {
+ for (int i = 0; i <= clients_n; i++) {
+ auto id = client.create_client_id();
+ if (i != 0) {
+ client.send(id, 3, td::make_tl_object<td::td_api::testSquareInt>(3));
+ }
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+
+ std::set<td::int32> ids;
+ while (ids.size() != static_cast<size_t>(threads_n) * clients_n) {
+ auto event = client.receive(10);
+ if (event.client_id == 0 || event.client_id == -1) {
+ ASSERT_EQ(td::td_api::error::ID, event.object->get_id());
+ ASSERT_EQ(400, static_cast<td::td_api::error &>(*event.object).code_);
+ continue;
+ }
+ if (event.request_id == 3) {
+ ASSERT_EQ(td::td_api::testInt::ID, event.object->get_id());
+ ASSERT_TRUE(ids.insert(event.client_id).second);
+ }
+ }
+}
+
+#if !TD_EVENTFD_UNSUPPORTED // Client must be used from a single thread if there is no EventFd
+TEST(Client, Close) {
+ std::atomic<bool> stop_send{false};
+ std::atomic<bool> can_stop_receive{false};
+ std::atomic<td::int64> send_count{1};
+ std::atomic<td::int64> receive_count{0};
+ td::Client client;
+
+ std::mutex request_ids_mutex;
+ std::set<td::uint64> request_ids;
+ request_ids.insert(1);
+ td::thread send_thread([&] {
+ td::uint64 request_id = 2;
+ while (!stop_send.load()) {
+ {
+ std::unique_lock<std::mutex> guard(request_ids_mutex);
+ request_ids.insert(request_id);
+ }
+ client.send({request_id++, td::make_tl_object<td::td_api::testSquareInt>(3)});
+ send_count++;
+ }
+ can_stop_receive = true;
+ });
+
+ td::thread receive_thread([&] {
+ auto max_continue_send = td::Random::fast_bool() ? 0 : 1000;
+ while (true) {
+ auto response = client.receive(10.0);
+ if (response.object == nullptr) {
+ if (!stop_send) {
+ stop_send = true;
+ } else {
+ return;
+ }
+ }
+ if (response.id > 0) {
+ if (!stop_send && response.object->get_id() == td::td_api::error::ID &&
+ static_cast<td::td_api::error &>(*response.object).code_ == 500 &&
+ td::Random::fast(0, max_continue_send) == 0) {
+ stop_send = true;
+ }
+ receive_count++;
+ {
+ std::unique_lock<std::mutex> guard(request_ids_mutex);
+ size_t erased_count = request_ids.erase(response.id);
+ CHECK(erased_count > 0);
+ }
+ }
+ if (can_stop_receive && receive_count == send_count) {
+ break;
+ }
+ }
+ });
+
+ td::usleep_for((td::Random::fast_bool() ? 0 : 1000) * (td::Random::fast_bool() ? 1 : 50));
+ client.send({1, td::make_tl_object<td::td_api::close>()});
+
+ send_thread.join();
+ receive_thread.join();
+ ASSERT_EQ(send_count.load(), receive_count.load());
+ ASSERT_TRUE(request_ids.empty());
+}
+
+TEST(Client, ManagerClose) {
+ std::atomic<bool> stop_send{false};
+ std::atomic<bool> can_stop_receive{false};
+ std::atomic<td::int64> send_count{1};
+ std::atomic<td::int64> receive_count{0};
+ td::ClientManager client_manager;
+ auto client_id = client_manager.create_client_id();
+
+ std::mutex request_ids_mutex;
+ std::set<td::uint64> request_ids;
+ request_ids.insert(1);
+ td::thread send_thread([&] {
+ td::uint64 request_id = 2;
+ while (!stop_send.load()) {
+ {
+ std::unique_lock<std::mutex> guard(request_ids_mutex);
+ request_ids.insert(request_id);
+ }
+ client_manager.send(client_id, request_id++, td::make_tl_object<td::td_api::testSquareInt>(3));
+ send_count++;
+ }
+ can_stop_receive = true;
+ });
+
+ td::thread receive_thread([&] {
+ auto max_continue_send = td::Random::fast_bool() ? 0 : 1000;
+ bool can_stop_send = false;
+ while (true) {
+ auto response = client_manager.receive(10.0);
+ if (response.object == nullptr) {
+ if (!stop_send) {
+ can_stop_send = true;
+ } else {
+ return;
+ }
+ }
+ if (can_stop_send && max_continue_send-- <= 0) {
+ stop_send = true;
+ }
+ if (response.request_id > 0) {
+ receive_count++;
+ {
+ std::unique_lock<std::mutex> guard(request_ids_mutex);
+ size_t erased_count = request_ids.erase(response.request_id);
+ CHECK(erased_count > 0);
+ }
+ }
+ if (can_stop_receive && receive_count == send_count) {
+ break;
+ }
+ }
+ });
+
+ td::usleep_for((td::Random::fast_bool() ? 0 : 1000) * (td::Random::fast_bool() ? 1 : 50));
+ client_manager.send(client_id, 1, td::make_tl_object<td::td_api::close>());
+
+ send_thread.join();
+ receive_thread.join();
+ ASSERT_EQ(send_count.load(), receive_count.load());
+ ASSERT_TRUE(request_ids.empty());
+}
+#endif
+#endif
+
+TEST(Client, ManagerCloseOneThread) {
+ td::ClientManager client_manager;
+
+ td::uint64 request_id = 2;
+ std::map<td::uint64, td::int32> sent_requests;
+ td::uint64 sent_count = 0;
+ td::uint64 receive_count = 0;
+
+ auto send_request = [&](td::int32 client_id, td::int32 expected_error_code) {
+ sent_count++;
+ sent_requests.emplace(request_id, expected_error_code);
+ client_manager.send(client_id, request_id++, td::make_tl_object<td::td_api::testSquareInt>(3));
+ };
+
+ auto receive = [&] {
+ while (receive_count != sent_count) {
+ auto response = client_manager.receive(1.0);
+ if (response.object == nullptr) {
+ continue;
+ }
+ if (response.request_id > 0) {
+ receive_count++;
+ auto it = sent_requests.find(response.request_id);
+ CHECK(it != sent_requests.end());
+ auto expected_error_code = it->second;
+ sent_requests.erase(it);
+
+ if (expected_error_code == 0) {
+ if (response.request_id == 1) {
+ ASSERT_EQ(td::td_api::ok::ID, response.object->get_id());
+ } else {
+ ASSERT_EQ(td::td_api::testInt::ID, response.object->get_id());
+ }
+ } else {
+ ASSERT_EQ(td::td_api::error::ID, response.object->get_id());
+ ASSERT_EQ(expected_error_code, static_cast<td::td_api::error &>(*response.object).code_);
+ }
+ }
+ }
+ };
+
+ for (int t = 0; t < 3; t++) {
+ for (td::int32 i = -5; i <= 0; i++) {
+ send_request(i, 400);
+ }
+
+ receive();
+
+ auto client_id = client_manager.create_client_id();
+
+ for (td::int32 i = -5; i < 5; i++) {
+ send_request(i, i == client_id ? 0 : (i > 0 && i < client_id ? 500 : 400));
+ }
+
+ receive();
+
+ for (int i = 0; i < 10; i++) {
+ send_request(client_id, 0);
+ }
+
+ receive();
+
+ sent_count++;
+ sent_requests.emplace(1, 0);
+ client_manager.send(client_id, 1, td::make_tl_object<td::td_api::close>());
+
+ for (int i = 0; i < 10; i++) {
+ send_request(client_id, 500);
+ }
+
+ receive();
+
+ for (int i = 0; i < 10; i++) {
+ send_request(client_id, 500);
+ }
+
+ receive();
+ }
+
+ ASSERT_TRUE(sent_requests.empty());
+}
+
+TEST(PartsManager, hands) {
+ {
+ td::PartsManager pm;
+ pm.init(0, 100000, false, 10, {0, 1, 2}, false, true).ensure_error();
+ //pm.set_known_prefix(0, false).ensure();
+ }
+ {
+ td::PartsManager pm;
+ pm.init(1, 100000, true, 10, {0, 1, 2}, false, true).ensure_error();
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/test/tests_runner.cpp b/protocols/Telegram/tdlib/td/test/tests_runner.cpp
deleted file mode 100644
index 0edb186f0a..0000000000
--- a/protocols/Telegram/tdlib/td/test/tests_runner.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
-//
-// Distributed under the Boost Software License, Version 1.0. (See accompanying
-// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
-//
-#include "test/tests_runner.h"
-
-#include "test/TestsRunner.h"
-
-extern "C" {
-void tests_runner_init(const char *dir) {
- td::TestsRunner::init(dir);
-}
-void run_all_tests() {
- td::TestsRunner::run_all_tests();
-}
-}
diff --git a/protocols/Telegram/tdlib/td/test/tqueue.cpp b/protocols/Telegram/tdlib/td/test/tqueue.cpp
new file mode 100644
index 0000000000..b1bf94a09e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/tqueue.cpp
@@ -0,0 +1,250 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/db/binlog/Binlog.h"
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/TQueue.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/int_types.h"
+#include "td/utils/logging.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Span.h"
+#include "td/utils/tests.h"
+#include "td/utils/Time.h"
+
+#include <memory>
+#include <utility>
+
+TEST(TQueue, hands) {
+ td::TQueue::Event events[100];
+ auto events_span = td::MutableSpan<td::TQueue::Event>(events, 100);
+
+ auto tqueue = td::TQueue::create();
+ auto qid = 12;
+ ASSERT_EQ(true, tqueue->get_head(qid).empty());
+ ASSERT_EQ(true, tqueue->get_tail(qid).empty());
+ tqueue->push(qid, "hello", 1, 0, td::TQueue::EventId());
+ auto head = tqueue->get_head(qid);
+ auto tail = tqueue->get_tail(qid);
+ ASSERT_EQ(head.next().ok(), tail);
+ ASSERT_EQ(1u, tqueue->get(qid, head, true, 0, events_span).move_as_ok());
+ ASSERT_EQ(1u, tqueue->get(qid, head, true, 0, events_span).move_as_ok());
+ ASSERT_EQ(1u, tqueue->get(qid, tail, false, 0, events_span).move_as_ok());
+ ASSERT_EQ(1u, tqueue->get(qid, head, true, 0, events_span).move_as_ok());
+ ASSERT_EQ(0u, tqueue->get(qid, tail, true, 0, events_span).move_as_ok());
+ ASSERT_EQ(0u, tqueue->get(qid, head, true, 0, events_span).move_as_ok());
+}
+
+class TestTQueue {
+ public:
+ using EventId = td::TQueue::EventId;
+
+ static td::CSlice binlog_path() {
+ return td::CSlice("tqueue_binlog");
+ }
+
+ TestTQueue() {
+ baseline_ = td::TQueue::create();
+
+ memory_ = td::TQueue::create();
+ auto memory_storage = td::make_unique<td::TQueueMemoryStorage>();
+ memory_storage_ = memory_storage.get();
+ memory_->set_callback(std::move(memory_storage));
+
+ binlog_ = td::TQueue::create();
+ auto tqueue_binlog = td::make_unique<td::TQueueBinlog<td::Binlog>>();
+ td::Binlog::destroy(binlog_path()).ensure();
+ auto binlog = std::make_shared<td::Binlog>();
+ binlog->init(binlog_path().str(), [&](const td::BinlogEvent &event) { UNREACHABLE(); }).ensure();
+ tqueue_binlog->set_binlog(std::move(binlog));
+ binlog_->set_callback(std::move(tqueue_binlog));
+ }
+
+ void restart(td::Random::Xorshift128plus &rnd, td::int32 now) {
+ if (rnd.fast(0, 10) == 0) {
+ baseline_->run_gc(now);
+ }
+
+ memory_->extract_callback().release();
+ auto memory_storage = td::unique_ptr<td::TQueueMemoryStorage>(memory_storage_);
+ memory_ = td::TQueue::create();
+ memory_storage->replay(*memory_);
+ memory_->set_callback(std::move(memory_storage));
+ if (rnd.fast(0, 10) == 0) {
+ memory_->run_gc(now);
+ }
+
+ if (rnd.fast(0, 30) != 0) {
+ return;
+ }
+
+ LOG(INFO) << "Restart binlog";
+ binlog_ = td::TQueue::create();
+ auto tqueue_binlog = td::make_unique<td::TQueueBinlog<td::Binlog>>();
+ auto binlog = std::make_shared<td::Binlog>();
+ binlog
+ ->init(binlog_path().str(),
+ [&](const td::BinlogEvent &event) { tqueue_binlog->replay(event, *binlog_).ignore(); })
+ .ensure();
+ tqueue_binlog->set_binlog(std::move(binlog));
+ binlog_->set_callback(std::move(tqueue_binlog));
+ if (rnd.fast(0, 2) == 0) {
+ binlog_->run_gc(now);
+ }
+ }
+
+ EventId push(td::TQueue::QueueId queue_id, const td::string &data, td::int32 expires_at, EventId new_id = EventId()) {
+ auto a_id = baseline_->push(queue_id, data, expires_at, 0, new_id).move_as_ok();
+ auto b_id = memory_->push(queue_id, data, expires_at, 0, new_id).move_as_ok();
+ auto c_id = binlog_->push(queue_id, data, expires_at, 0, new_id).move_as_ok();
+ ASSERT_EQ(a_id, b_id);
+ ASSERT_EQ(a_id, c_id);
+ return a_id;
+ }
+
+ void check_head_tail(td::TQueue::QueueId qid) {
+ //ASSERT_EQ(baseline_->get_head(qid), memory_->get_head(qid));
+ //ASSERT_EQ(baseline_->get_head(qid), binlog_->get_head(qid));
+ ASSERT_EQ(baseline_->get_tail(qid), memory_->get_tail(qid));
+ ASSERT_EQ(baseline_->get_tail(qid), binlog_->get_tail(qid));
+ }
+
+ void check_get(td::TQueue::QueueId qid, td::Random::Xorshift128plus &rnd, td::int32 now) {
+ td::TQueue::Event a[10];
+ td::MutableSpan<td::TQueue::Event> a_span(a, 10);
+ td::TQueue::Event b[10];
+ td::MutableSpan<td::TQueue::Event> b_span(b, 10);
+ td::TQueue::Event c[10];
+ td::MutableSpan<td::TQueue::Event> c_span(c, 10);
+
+ auto a_from = baseline_->get_head(qid);
+ //auto b_from = memory_->get_head(qid);
+ //auto c_from = binlog_->get_head(qid);
+ //ASSERT_EQ(a_from, b_from);
+ //ASSERT_EQ(a_from, c_from);
+
+ auto tmp = a_from.advance(rnd.fast(-10, 10));
+ if (tmp.is_ok()) {
+ a_from = tmp.move_as_ok();
+ }
+ baseline_->get(qid, a_from, true, now, a_span).move_as_ok();
+ memory_->get(qid, a_from, true, now, b_span).move_as_ok();
+ binlog_->get(qid, a_from, true, now, c_span).move_as_ok();
+ ASSERT_EQ(a_span.size(), b_span.size());
+ ASSERT_EQ(a_span.size(), c_span.size());
+ for (size_t i = 0; i < a_span.size(); i++) {
+ ASSERT_EQ(a_span[i].id, b_span[i].id);
+ ASSERT_EQ(a_span[i].id, c_span[i].id);
+ ASSERT_EQ(a_span[i].data, b_span[i].data);
+ ASSERT_EQ(a_span[i].data, c_span[i].data);
+ }
+ }
+
+ private:
+ td::unique_ptr<td::TQueue> baseline_;
+ td::unique_ptr<td::TQueue> memory_;
+ td::unique_ptr<td::TQueue> binlog_;
+ td::TQueueMemoryStorage *memory_storage_{nullptr};
+};
+
+TEST(TQueue, random) {
+ using EventId = td::TQueue::EventId;
+ td::Random::Xorshift128plus rnd(123);
+ auto next_queue_id = [&rnd] {
+ return rnd.fast(1, 10);
+ };
+ auto next_first_id = [&rnd] {
+ if (rnd.fast(0, 3) == 0) {
+ return EventId::from_int32(EventId::MAX_ID - 20).move_as_ok();
+ }
+ return EventId::from_int32(rnd.fast(1000000000, 1500000000)).move_as_ok();
+ };
+
+ TestTQueue q;
+ td::int32 now = 1000;
+ auto push_event = [&] {
+ auto data = PSTRING() << rnd();
+ if (rnd.fast(0, 10000) == 0) {
+ data = td::string(1 << 19, '\0');
+ }
+ q.push(next_queue_id(), data, now + rnd.fast(-10, 10) * 10 + 5, next_first_id());
+ };
+ auto inc_now = [&] {
+ now += 10;
+ };
+ auto check_head_tail = [&] {
+ q.check_head_tail(next_queue_id());
+ };
+ auto restart = [&] {
+ q.restart(rnd, now);
+ };
+ auto get = [&] {
+ q.check_get(next_queue_id(), rnd, now);
+ };
+ td::RandomSteps steps({{push_event, 100}, {check_head_tail, 10}, {get, 40}, {inc_now, 5}, {restart, 1}});
+ for (int i = 0; i < 100000; i++) {
+ steps.step(rnd);
+ }
+}
+
+TEST(TQueue, memory_leak) {
+ return;
+ auto tqueue = td::TQueue::create();
+ auto tqueue_binlog = td::make_unique<td::TQueueBinlog<td::Binlog>>();
+ std::string binlog_path = "test_tqueue.binlog";
+ td::Binlog::destroy(binlog_path).ensure();
+ auto binlog = std::make_shared<td::Binlog>();
+ binlog->init(binlog_path, [&](const td::BinlogEvent &event) { UNREACHABLE(); }).ensure();
+ tqueue_binlog->set_binlog(std::move(binlog));
+ tqueue->set_callback(std::move(tqueue_binlog));
+
+ td::int32 now = 0;
+ std::vector<td::TQueue::EventId> ids;
+ td::Random::Xorshift128plus rnd(123);
+ int i = 0;
+ while (true) {
+ auto id = tqueue->push(1, "a", now + 600000, 0, {}).move_as_ok();
+ ids.push_back(id);
+ if (ids.size() > static_cast<std::size_t>(rnd()) % 100000) {
+ auto it = static_cast<std::size_t>(rnd()) % ids.size();
+ std::swap(ids.back(), ids[it]);
+ tqueue->forget(1, ids.back());
+ ids.pop_back();
+ }
+ now++;
+ if (i++ % 100000 == 0) {
+ LOG(ERROR) << td::BufferAllocator::get_buffer_mem() << " " << tqueue->get_size(1) << " "
+ << td::BufferAllocator::get_buffer_slice_size();
+ }
+ }
+}
+
+TEST(TQueue, clear) {
+ auto tqueue = td::TQueue::create();
+
+ auto start_time = td::Time::now();
+ td::int32 now = 0;
+ td::vector<td::TQueue::EventId> ids;
+ td::Random::Xorshift128plus rnd(123);
+ for (size_t i = 0; i < 1000000; i++) {
+ tqueue->push(1, td::string(td::Random::fast(100, 500), 'a'), now + 600000, 0, {}).ensure();
+ }
+ auto tail_id = tqueue->get_tail(1);
+ auto clear_start_time = td::Time::now();
+ size_t keep_count = td::Random::fast(0, 2);
+ tqueue->clear(1, keep_count);
+ auto finish_time = td::Time::now();
+ LOG(INFO) << "Added TQueue events in " << clear_start_time - start_time << " seconds and cleared them in "
+ << finish_time - clear_start_time << " seconds";
+ CHECK(tqueue->get_size(1) == keep_count);
+ CHECK(tqueue->get_head(1).advance(keep_count).ok() == tail_id);
+ CHECK(tqueue->get_tail(1) == tail_id);
+}
diff --git a/protocols/Telegram/tdlib/tdactor.vcxproj b/protocols/Telegram/tdlib/tdactor.vcxproj
new file mode 100644
index 0000000000..862a471cd2
--- /dev/null
+++ b/protocols/Telegram/tdlib/tdactor.vcxproj
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{85F63934-02FE-332A-8703-059040B65512}</ProjectGuid>
+ <ProjectName>tdactor</ProjectName>
+ <GenerateManifest>false</GenerateManifest>
+ <EmbedManifest>false</EmbedManifest>
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ </PropertyGroup>
+ <Import Project="..\..\..\build\vc.common\common.props" />
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>.\td\tdactor;.\td\tdutils;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+ <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="td\tdactor\td\actor\impl\Scheduler.cpp" />
+ <ClCompile Include="td\tdactor\td\actor\MultiPromise.cpp" />
+ <ClInclude Include="td\tdactor\td\actor\impl\Actor-decl.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\Actor.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\ActorId-decl.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\ActorId.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\ActorInfo-decl.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\ActorInfo.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\EventFull-decl.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\EventFull.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\Event.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\Scheduler-decl.h" />
+ <ClInclude Include="td\tdactor\td\actor\impl\Scheduler.h" />
+ <ClInclude Include="td\tdactor\td\actor\MultiPromise.h" />
+ <ClInclude Include="td\tdactor\td\actor\PromiseFuture.h" />
+ <ClInclude Include="td\tdactor\td\actor\SchedulerLocalStorage.h" />
+ <ClInclude Include="td\tdactor\td\actor\SignalSlot.h" />
+ <ClInclude Include="td\tdactor\td\actor\SleepActor.h" />
+ <ClInclude Include="td\tdactor\td\actor\Timeout.h" />
+ <ClInclude Include="td\tdactor\td\actor\actor.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="tdutils.vcxproj">
+ <Project>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</Project>
+ <Name>tdutils</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tdactor.vcxproj.filters b/protocols/Telegram/tdlib/tdactor.vcxproj.filters
index 830d9d15c0..830d9d15c0 100644
--- a/protocols/Telegram/tdlib/build/tdactor.vcxproj.filters
+++ b/protocols/Telegram/tdlib/tdactor.vcxproj.filters
diff --git a/protocols/Telegram/tdlib/tdcore.vcxproj b/protocols/Telegram/tdlib/tdcore.vcxproj
new file mode 100644
index 0000000000..032e259186
--- /dev/null
+++ b/protocols/Telegram/tdlib/tdcore.vcxproj
@@ -0,0 +1,295 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{FC88FB5A-AAED-3F3E-9959-236444D8F644}</ProjectGuid>
+ <ProjectName>tdcore</ProjectName>
+ <GenerateManifest>false</GenerateManifest>
+ <EmbedManifest>false</EmbedManifest>
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ </PropertyGroup>
+ <Import Project="..\..\..\build\vc.common\common.props" />
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>.\td;.\td\td\generate\auto;..\..\..\include;.\td\tdactor;.\td\tdutils;.\td\tdnet;.\td\tddb;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+ <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="td\td\mtproto\AuthData.cpp" />
+ <ClCompile Include="td\td\mtproto\Handshake.cpp" />
+ <ClCompile Include="td\td\mtproto\HandshakeActor.cpp" />
+ <ClCompile Include="td\td\mtproto\HttpTransport.cpp" />
+ <ClCompile Include="td\td\mtproto\IStreamTransport.cpp" />
+ <ClCompile Include="td\td\mtproto\RawConnection.cpp" />
+ <ClCompile Include="td\td\mtproto\SessionConnection.cpp" />
+ <ClCompile Include="td\td\mtproto\TcpTransport.cpp" />
+ <ClCompile Include="td\td\mtproto\Transport.cpp" />
+ <ClCompile Include="td\td\mtproto\utils.cpp" />
+ <ClCompile Include="td\td\telegram\AnimationsManager.cpp" />
+ <ClCompile Include="td\td\telegram\AudiosManager.cpp" />
+ <ClCompile Include="td\td\telegram\AuthManager.cpp" />
+ <ClCompile Include="td\td\telegram\CallActor.cpp" />
+ <ClCompile Include="td\td\telegram\CallDiscardReason.cpp" />
+ <ClCompile Include="td\td\telegram\CallManager.cpp" />
+ <ClCompile Include="td\td\telegram\CallbackQueriesManager.cpp" />
+ <ClCompile Include="td\td\telegram\ClientActor.cpp" />
+ <ClCompile Include="td\td\telegram\ConfigManager.cpp" />
+ <ClCompile Include="td\td\telegram\Contact.cpp" />
+ <ClCompile Include="td\td\telegram\ContactsManager.cpp" />
+ <ClCompile Include="td\td\telegram\DelayDispatcher.cpp" />
+ <ClCompile Include="td\td\telegram\DeviceTokenManager.cpp" />
+ <ClCompile Include="td\td\telegram\DhCache.cpp" />
+ <ClCompile Include="td\td\telegram\DialogDb.cpp" />
+ <ClCompile Include="td\td\telegram\DialogId.cpp" />
+ <ClCompile Include="td\td\telegram\DialogParticipant.cpp" />
+ <ClCompile Include="td\td\telegram\DocumentsManager.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileDb.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileDownloader.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileFromBytes.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileGcParameters.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileGcWorker.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileGenerateManager.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileHashUploader.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileLoader.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileLoaderUtils.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileLoadManager.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileManager.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileStats.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileStatsWorker.cpp" />
+ <ClCompile Include="td\td\telegram\files\FileUploader.cpp" />
+ <ClCompile Include="td\td\telegram\files\PartsManager.cpp" />
+ <ClCompile Include="td\td\telegram\files\ResourceManager.cpp" />
+ <ClCompile Include="td\td\telegram\Game.cpp" />
+ <ClCompile Include="td\td\telegram\Global.cpp" />
+ <ClCompile Include="td\td\telegram\HashtagHints.cpp" />
+ <ClCompile Include="td\td\telegram\InlineQueriesManager.cpp" />
+ <ClCompile Include="td\td\telegram\Location.cpp" />
+ <ClCompile Include="td\td\telegram\MessageEntity.cpp" />
+ <ClCompile Include="td\td\telegram\MessagesManager.cpp" />
+ <ClCompile Include="td\td\telegram\misc.cpp" />
+ <ClCompile Include="td\td\telegram\net\AuthDataShared.cpp" />
+ <ClCompile Include="td\td\telegram\net\ConnectionCreator.cpp" />
+ <ClCompile Include="td\td\telegram\net\DcAuthManager.cpp" />
+ <ClCompile Include="td\td\telegram\net\DcOptionsSet.cpp" />
+ <ClCompile Include="td\td\telegram\net\MtprotoHeader.cpp" />
+ <ClCompile Include="td\td\telegram\net\NetActor.cpp" />
+ <ClCompile Include="td\td\telegram\net\NetQuery.cpp" />
+ <ClCompile Include="td\td\telegram\net\NetQueryCreator.cpp" />
+ <ClCompile Include="td\td\telegram\net\NetQueryDelayer.cpp" />
+ <ClCompile Include="td\td\telegram\net\NetQueryDispatcher.cpp" />
+ <ClCompile Include="td\td\telegram\net\NetStatsManager.cpp" />
+ <ClCompile Include="td\td\telegram\net\PublicRsaKeyShared.cpp" />
+ <ClCompile Include="td\td\telegram\net\PublicRsaKeyWatchdog.cpp" />
+ <ClCompile Include="td\td\telegram\net\Session.cpp" />
+ <ClCompile Include="td\td\telegram\net\SessionProxy.cpp" />
+ <ClCompile Include="td\td\telegram\net\SessionMultiProxy.cpp" />
+ <ClCompile Include="td\td\telegram\Payments.cpp" />
+ <ClCompile Include="td\td\telegram\PasswordManager.cpp" />
+ <ClCompile Include="td\td\telegram\PrivacyManager.cpp" />
+ <ClCompile Include="td\td\telegram\Photo.cpp" />
+ <ClCompile Include="td\td\telegram\ReplyMarkup.cpp" />
+ <ClCompile Include="td\td\telegram\SecretChatActor.cpp" />
+ <ClCompile Include="td\td\telegram\SecretChatDb.cpp" />
+ <ClCompile Include="td\td\telegram\SecretChatsManager.cpp" />
+ <ClCompile Include="td\td\telegram\SequenceDispatcher.cpp" />
+ <ClCompile Include="td\td\telegram\StateManager.cpp" />
+ <ClCompile Include="td\td\telegram\StickersManager.cpp" />
+ <ClCompile Include="td\td\telegram\StorageManager.cpp" />
+ <ClCompile Include="td\td\telegram\Td.cpp" />
+ <ClCompile Include="td\td\telegram\TdDb.cpp" />
+ <ClCompile Include="td\td\telegram\TopDialogManager.cpp" />
+ <ClCompile Include="td\td\telegram\UpdatesManager.cpp" />
+ <ClCompile Include="td\td\telegram\VideoNotesManager.cpp" />
+ <ClCompile Include="td\td\telegram\VideosManager.cpp" />
+ <ClCompile Include="td\td\telegram\VoiceNotesManager.cpp" />
+ <ClCompile Include="td\td\telegram\WebPagesManager.cpp" />
+ <ClInclude Include="td\td\mtproto\AuthData.h" />
+ <ClInclude Include="td\td\mtproto\AuthKey.h" />
+ <ClInclude Include="td\td\mtproto\CryptoStorer.h" />
+ <ClInclude Include="td\td\mtproto\Handshake.h" />
+ <ClInclude Include="td\td\mtproto\HandshakeActor.h" />
+ <ClInclude Include="td\td\mtproto\HandshakeConnection.h" />
+ <ClInclude Include="td\td\mtproto\HttpTransport.h" />
+ <ClInclude Include="td\td\mtproto\IStreamTransport.h" />
+ <ClInclude Include="td\td\mtproto\NoCryptoStorer.h" />
+ <ClInclude Include="td\td\mtproto\PacketStorer.h" />
+ <ClInclude Include="td\td\mtproto\PingConnection.h" />
+ <ClInclude Include="td\td\mtproto\RawConnection.h" />
+ <ClInclude Include="td\td\mtproto\SessionConnection.h" />
+ <ClInclude Include="td\td\mtproto\TcpTransport.h" />
+ <ClInclude Include="td\td\mtproto\Transport.h" />
+ <ClInclude Include="td\td\mtproto\utils.h" />
+ <ClInclude Include="td\td\telegram\AccessRights.h" />
+ <ClInclude Include="td\td\telegram\AnimationsManager.h" />
+ <ClInclude Include="td\td\telegram\AudiosManager.h" />
+ <ClInclude Include="td\td\telegram\AuthManager.h" />
+ <ClInclude Include="td\td\telegram\CallActor.h" />
+ <ClInclude Include="td\td\telegram\CallDiscardReason.h" />
+ <ClInclude Include="td\td\telegram\CallId.h" />
+ <ClInclude Include="td\td\telegram\CallManager.h" />
+ <ClInclude Include="td\td\telegram\CallbackQueriesManager.h" />
+ <ClInclude Include="td\td\telegram\ChannelId.h" />
+ <ClInclude Include="td\td\telegram\ChatId.h" />
+ <ClInclude Include="td\td\telegram\ClientActor.h" />
+ <ClInclude Include="td\td\telegram\ConfigManager.h" />
+ <ClInclude Include="td\td\telegram\Contact.h" />
+ <ClInclude Include="td\td\telegram\ContactsManager.h" />
+ <ClInclude Include="td\td\telegram\DelayDispatcher.h" />
+ <ClInclude Include="td\td\telegram\DeviceTokenManager.h" />
+ <ClInclude Include="td\td\telegram\DhCache.h" />
+ <ClInclude Include="td\td\telegram\DhConfig.h" />
+ <ClInclude Include="td\td\telegram\DialogDb.h" />
+ <ClInclude Include="td\td\telegram\DialogId.h" />
+ <ClInclude Include="td\td\telegram\DialogParticipant.h" />
+ <ClInclude Include="td\td\telegram\DocumentsManager.h" />
+ <ClInclude Include="td\td\telegram\files\FileDb.h" />
+ <ClInclude Include="td\td\telegram\files\FileDownloader.h" />
+ <ClInclude Include="td\td\telegram\files\FileFromBytes.h" />
+ <ClInclude Include="td\td\telegram\files\FileGcParameters.h" />
+ <ClInclude Include="td\td\telegram\files\FileGcWorker.h" />
+ <ClInclude Include="td\td\telegram\files\FileGenerateManager.h" />
+ <ClInclude Include="td\td\telegram\files\FileHashUploader.h" />
+ <ClInclude Include="td\td\telegram\files\FileId.h" />
+ <ClInclude Include="td\td\telegram\files\FileLoaderActor.h" />
+ <ClInclude Include="td\td\telegram\files\FileLoader.h" />
+ <ClInclude Include="td\td\telegram\files\FileLoaderUtils.h" />
+ <ClInclude Include="td\td\telegram\files\FileLoadManager.h" />
+ <ClInclude Include="td\td\telegram\files\FileLocation.h" />
+ <ClInclude Include="td\td\telegram\files\FileManager.h" />
+ <ClInclude Include="td\td\telegram\files\FileStats.h" />
+ <ClInclude Include="td\td\telegram\files\FileStatsWorker.h" />
+ <ClInclude Include="td\td\telegram\files\FileUploader.h" />
+ <ClInclude Include="td\td\telegram\files\PartsManager.h" />
+ <ClInclude Include="td\td\telegram\files\ResourceManager.h" />
+ <ClInclude Include="td\td\telegram\files\ResourceState.h" />
+ <ClInclude Include="td\td\telegram\Game.h" />
+ <ClInclude Include="td\td\telegram\Global.h" />
+ <ClInclude Include="td\td\telegram\HashtagHints.h" />
+ <ClInclude Include="td\td\telegram\InlineQueriesManager.h" />
+ <ClInclude Include="td\td\telegram\Location.h" />
+ <ClInclude Include="td\td\telegram\logevent\LogEvent.h" />
+ <ClInclude Include="td\td\telegram\logevent\SecretChatEvent.h" />
+ <ClInclude Include="td\td\telegram\MessageEntity.h" />
+ <ClInclude Include="td\td\telegram\MessageId.h" />
+ <ClInclude Include="td\td\telegram\MessagesManager.h" />
+ <ClInclude Include="td\td\telegram\misc.h" />
+ <ClInclude Include="td\td\telegram\net\AuthDataShared.h" />
+ <ClInclude Include="td\td\telegram\net\ConnectionCreator.h" />
+ <ClInclude Include="td\td\telegram\net\DcAuthManager.h" />
+ <ClInclude Include="td\td\telegram\net\DcId.h" />
+ <ClInclude Include="td\td\telegram\net\DcOptions.h" />
+ <ClInclude Include="td\td\telegram\net\DcOptionsSet.h" />
+ <ClInclude Include="td\td\telegram\net\MtprotoHeader.h" />
+ <ClInclude Include="td\td\telegram\net\NetActor.h" />
+ <ClInclude Include="td\td\telegram\net\NetQuery.h" />
+ <ClInclude Include="td\td\telegram\net\NetQueryCounter.h" />
+ <ClInclude Include="td\td\telegram\net\NetQueryCreator.h" />
+ <ClInclude Include="td\td\telegram\net\NetQueryDelayer.h" />
+ <ClInclude Include="td\td\telegram\net\NetQueryDispatcher.h" />
+ <ClInclude Include="td\td\telegram\net\NetStatsManager.h" />
+ <ClInclude Include="td\td\telegram\net\NetType.h" />
+ <ClInclude Include="td\td\telegram\net\PublicRsaKeyShared.h" />
+ <ClInclude Include="td\td\telegram\net\PublicRsaKeyWatchdog.h" />
+ <ClInclude Include="td\td\telegram\net\Session.h" />
+ <ClInclude Include="td\td\telegram\net\SessionProxy.h" />
+ <ClInclude Include="td\td\telegram\net\SessionMultiProxy.h" />
+ <ClInclude Include="td\td\telegram\net\TempAuthKeyWatchdog.h" />
+ <ClInclude Include="td\td\telegram\PasswordManager.h" />
+ <ClInclude Include="td\td\telegram\Payments.h" />
+ <ClInclude Include="td\td\telegram\Photo.h" />
+ <ClInclude Include="td\td\telegram\PrivacyManager.h" />
+ <ClInclude Include="td\td\telegram\PtsManager.h" />
+ <ClInclude Include="td\td\telegram\ReplyMarkup.h" />
+ <ClInclude Include="td\td\telegram\SecretChatActor.h" />
+ <ClInclude Include="td\td\telegram\SecretChatId.h" />
+ <ClInclude Include="td\td\telegram\SecretChatDb.h" />
+ <ClInclude Include="td\td\telegram\SecretChatsManager.h" />
+ <ClInclude Include="td\td\telegram\SecretInputMedia.h" />
+ <ClInclude Include="td\td\telegram\SequenceDispatcher.h" />
+ <ClInclude Include="td\td\telegram\StateManager.h" />
+ <ClInclude Include="td\td\telegram\StickersManager.h" />
+ <ClInclude Include="td\td\telegram\StorageManager.h" />
+ <ClInclude Include="td\td\telegram\Td.h" />
+ <ClInclude Include="td\td\telegram\TdCallback.h" />
+ <ClInclude Include="td\td\telegram\TdDb.h" />
+ <ClInclude Include="td\td\telegram\TdParameters.h" />
+ <ClInclude Include="td\td\telegram\TopDialogManager.h" />
+ <ClInclude Include="td\td\telegram\UniqueId.h" />
+ <ClInclude Include="td\td\telegram\UpdatesManager.h" />
+ <ClInclude Include="td\td\telegram\UserId.h" />
+ <ClInclude Include="td\td\telegram\Version.h" />
+ <ClInclude Include="td\td\telegram\VideoNotesManager.h" />
+ <ClInclude Include="td\td\telegram\VideosManager.h" />
+ <ClInclude Include="td\td\telegram\VoiceNotesManager.h" />
+ <ClInclude Include="td\td\telegram\WebPageId.h" />
+ <ClInclude Include="td\td\telegram\WebPagesManager.h" />
+ <ClInclude Include="td\td\telegram\AnimationsManager.hpp" />
+ <ClInclude Include="td\td\telegram\AudiosManager.hpp" />
+ <ClInclude Include="td\td\telegram\AuthManager.hpp" />
+ <ClInclude Include="td\td\telegram\DocumentsManager.hpp" />
+ <ClInclude Include="td\td\telegram\files\FileId.hpp" />
+ <ClInclude Include="td\td\telegram\files\FileManager.hpp" />
+ <ClInclude Include="td\td\telegram\Game.hpp" />
+ <ClInclude Include="td\td\telegram\Photo.hpp" />
+ <ClInclude Include="td\td\telegram\ReplyMarkup.hpp" />
+ <ClInclude Include="td\td\telegram\StickersManager.hpp" />
+ <ClInclude Include="td\td\telegram\VideoNotesManager.hpp" />
+ <ClInclude Include="td\td\telegram\VideosManager.hpp" />
+ <ClInclude Include="td\td\telegram\VoiceNotesManager.hpp" />
+ <ClCompile Include="td\td\generate\auto\td\mtproto\mtproto_api.cpp" />
+ <ClInclude Include="td\td\generate\auto\td\mtproto\mtproto_api.h" />
+ <ClInclude Include="td\td\generate\auto\td\mtproto\mtproto_api.hpp" />
+ <ClCompile Include="td\td\generate\auto\td\telegram\td_api.cpp" />
+ <ClInclude Include="td\td\generate\auto\td\telegram\td_api.h" />
+ <ClInclude Include="td\td\generate\auto\td\telegram\td_api.hpp" />
+ <ClCompile Include="td\td\generate\auto\td\telegram\telegram_api.cpp" />
+ <ClInclude Include="td\td\generate\auto\td\telegram\telegram_api.h" />
+ <ClInclude Include="td\td\generate\auto\td\telegram\telegram_api.hpp" />
+ <ClCompile Include="td\td\generate\auto\td\telegram\secret_api.cpp" />
+ <ClInclude Include="td\td\generate\auto\td\telegram\secret_api.h" />
+ <ClInclude Include="td\td\generate\auto\td\telegram\secret_api.hpp" />
+ <ClInclude Include="td\td\tl\TlObject.h" />
+ <ClInclude Include="td\td\tl\tl_object_parse.h" />
+ <ClInclude Include="td\td\tl\tl_object_store.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="tdactor.vcxproj">
+ <Project>{85F63934-02FE-332A-8703-059040B65512}</Project>
+ <Name>tdactor</Name>
+ </ProjectReference>
+ <ProjectReference Include="tddb.vcxproj">
+ <Project>{F525EE11-8820-3D8A-87A5-465D50A98A64}</Project>
+ <Name>tddb</Name>
+ </ProjectReference>
+ <ProjectReference Include="tdnet.vcxproj">
+ <Project>{2246C3CF-7888-3102-984A-80214ADF418C}</Project>
+ <Name>tdnet</Name>
+ </ProjectReference>
+ <ProjectReference Include="tdutils.vcxproj">
+ <Project>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</Project>
+ <Name>tdutils</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tdcore.vcxproj.filters b/protocols/Telegram/tdlib/tdcore.vcxproj.filters
index 1e4dddda0e..1e4dddda0e 100644
--- a/protocols/Telegram/tdlib/build/tdcore.vcxproj.filters
+++ b/protocols/Telegram/tdlib/tdcore.vcxproj.filters
diff --git a/protocols/Telegram/tdlib/tddb.vcxproj b/protocols/Telegram/tdlib/tddb.vcxproj
new file mode 100644
index 0000000000..df5b42a7a5
--- /dev/null
+++ b/protocols/Telegram/tdlib/tddb.vcxproj
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{F525EE11-8820-3D8A-87A5-465D50A98A64}</ProjectGuid>
+ <ProjectName>tddb</ProjectName>
+ <GenerateManifest>false</GenerateManifest>
+ <EmbedManifest>false</EmbedManifest>
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ </PropertyGroup>
+ <Import Project="..\..\..\build\vc.common\common.props" />
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>.\td\tddb;.\td\tdactor;.\td\tdutils;.\td\build\tdutils;.\td\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+ <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="td\tddb\td\db\binlog\Binlog.cpp" />
+ <ClCompile Include="td\tddb\td\db\binlog\BinlogEvent.cpp" />
+ <ClCompile Include="td\tddb\td\db\binlog\ConcurrentBinlog.cpp" />
+ <ClCompile Include="td\tddb\td\db\binlog\detail\BinlogEventsBuffer.cpp" />
+ <ClCompile Include="td\tddb\td\db\binlog\detail\BinlogEventsProcessor.cpp" />
+ <ClCompile Include="td\tddb\td\db\SqliteDb.cpp" />
+ <ClCompile Include="td\tddb\td\db\SqliteStatement.cpp" />
+ <ClCompile Include="td\tddb\td\db\SqliteKeyValueAsync.cpp" />
+ <ClCompile Include="td\tddb\td\db\detail\RawSqliteDb.cpp" />
+ <ClInclude Include="td\tddb\td\db\binlog\Binlog.h" />
+ <ClInclude Include="td\tddb\td\db\binlog\BinlogInterface.h" />
+ <ClInclude Include="td\tddb\td\db\binlog\BinlogEvent.h" />
+ <ClInclude Include="td\tddb\td\db\binlog\BinlogHelper.h" />
+ <ClInclude Include="td\tddb\td\db\binlog\ConcurrentBinlog.h" />
+ <ClInclude Include="td\tddb\td\db\binlog\detail\BinlogEventsBuffer.h" />
+ <ClInclude Include="td\tddb\td\db\binlog\detail\BinlogEventsProcessor.h" />
+ <ClInclude Include="td\tddb\td\db\BinlogKeyValue.h" />
+ <ClInclude Include="td\tddb\td\db\DbKey.h" />
+ <ClInclude Include="td\tddb\td\db\KeyValueSyncInterface.h" />
+ <ClInclude Include="td\tddb\td\db\SeqKeyValue.h" />
+ <ClInclude Include="td\tddb\td\db\SqliteConnectionSafe.h" />
+ <ClInclude Include="td\tddb\td\db\SqliteDb.h" />
+ <ClInclude Include="td\tddb\td\db\SqliteKeyValue.h" />
+ <ClInclude Include="td\tddb\td\db\SqliteKeyValueAsync.h" />
+ <ClInclude Include="td\tddb\td\db\SqliteKeyValueSafe.h" />
+ <ClInclude Include="td\tddb\td\db\SqliteStatement.h" />
+ <ClInclude Include="td\tddb\td\db\TsSeqKeyValue.h" />
+ <ClInclude Include="td\tddb\td\db\detail\RawSqliteDb.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="tdactor.vcxproj">
+ <Project>{85F63934-02FE-332A-8703-059040B65512}</Project>
+ <Name>tdactor</Name>
+ </ProjectReference>
+ <ProjectReference Include="tdutils.vcxproj">
+ <Project>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</Project>
+ <Name>tdutils</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tddb.vcxproj.filters b/protocols/Telegram/tdlib/tddb.vcxproj.filters
index c1f569c828..c1f569c828 100644
--- a/protocols/Telegram/tdlib/build/tddb.vcxproj.filters
+++ b/protocols/Telegram/tdlib/tddb.vcxproj.filters
diff --git a/protocols/Telegram/tdlib/tdlib.vcxproj b/protocols/Telegram/tdlib/tdlib.vcxproj
index a3bd11fe9e..ff8deb1790 100644
--- a/protocols/Telegram/tdlib/tdlib.vcxproj
+++ b/protocols/Telegram/tdlib/tdlib.vcxproj
@@ -24,20 +24,18 @@
<GenerateManifest>false</GenerateManifest>
<EmbedManifest>false</EmbedManifest>
<ConfigurationType>StaticLibrary</ConfigurationType>
- <OutDir Condition="'$(Platform)'=='Win32'">$(SolutionDir)$(Configuration)\Obj\$(ProjectName)\</OutDir>
- <OutDir Condition="'$(Platform)'=='x64'">$(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\</OutDir>
</PropertyGroup>
<Import Project="..\..\..\build\vc.common\common.props" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>td;td\td\generate\auto;td\tdactor;td\tdutils;td\tdnet;td\tddb;..\..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+ <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="td\td\mtproto\AuthData.cpp" />
- <ClCompile Include="td\td\mtproto\crypto.cpp" />
<ClCompile Include="td\td\mtproto\Handshake.cpp" />
<ClCompile Include="td\td\mtproto\HandshakeActor.cpp" />
<ClCompile Include="td\td\mtproto\HttpTransport.cpp" />
@@ -56,7 +54,6 @@
<ClCompile Include="td\td\telegram\CallbackQueriesManager.cpp" />
<ClCompile Include="td\td\telegram\ClientActor.cpp" />
<ClCompile Include="td\td\telegram\ConfigManager.cpp" />
- <ClCompile Include="td\td\telegram\ConfigShared.cpp" />
<ClCompile Include="td\td\telegram\Contact.cpp" />
<ClCompile Include="td\td\telegram\ContactsManager.cpp" />
<ClCompile Include="td\td\telegram\DelayDispatcher.cpp" />
@@ -88,7 +85,6 @@
<ClCompile Include="td\td\telegram\InlineQueriesManager.cpp" />
<ClCompile Include="td\td\telegram\Location.cpp" />
<ClCompile Include="td\td\telegram\MessageEntity.cpp" />
- <ClCompile Include="td\td\telegram\MessagesDb.cpp" />
<ClCompile Include="td\td\telegram\MessagesManager.cpp" />
<ClCompile Include="td\td\telegram\misc.cpp" />
<ClCompile Include="td\td\telegram\net\AuthDataShared.cpp" />
@@ -98,7 +94,6 @@
<ClCompile Include="td\td\telegram\net\MtprotoHeader.cpp" />
<ClCompile Include="td\td\telegram\net\NetActor.cpp" />
<ClCompile Include="td\td\telegram\net\NetQuery.cpp" />
- <ClCompile Include="td\td\telegram\net\NetQueryCounter.cpp" />
<ClCompile Include="td\td\telegram\net\NetQueryCreator.cpp" />
<ClCompile Include="td\td\telegram\net\NetQueryDelayer.cpp" />
<ClCompile Include="td\td\telegram\net\NetQueryDispatcher.cpp" />
@@ -130,7 +125,6 @@
<ClCompile Include="td\td\telegram\WebPagesManager.cpp" />
<ClInclude Include="td\td\mtproto\AuthData.h" />
<ClInclude Include="td\td\mtproto\AuthKey.h" />
- <ClInclude Include="td\td\mtproto\crypto.h" />
<ClInclude Include="td\td\mtproto\CryptoStorer.h" />
<ClInclude Include="td\td\mtproto\Handshake.h" />
<ClInclude Include="td\td\mtproto\HandshakeActor.h" />
@@ -158,7 +152,6 @@
<ClInclude Include="td\td\telegram\ChatId.h" />
<ClInclude Include="td\td\telegram\ClientActor.h" />
<ClInclude Include="td\td\telegram\ConfigManager.h" />
- <ClInclude Include="td\td\telegram\ConfigShared.h" />
<ClInclude Include="td\td\telegram\Contact.h" />
<ClInclude Include="td\td\telegram\ContactsManager.h" />
<ClInclude Include="td\td\telegram\DelayDispatcher.h" />
@@ -198,7 +191,6 @@
<ClInclude Include="td\td\telegram\logevent\SecretChatEvent.h" />
<ClInclude Include="td\td\telegram\MessageEntity.h" />
<ClInclude Include="td\td\telegram\MessageId.h" />
- <ClInclude Include="td\td\telegram\MessagesDb.h" />
<ClInclude Include="td\td\telegram\MessagesManager.h" />
<ClInclude Include="td\td\telegram\misc.h" />
<ClInclude Include="td\td\telegram\net\AuthDataShared.h" />
@@ -258,7 +250,6 @@
<ClInclude Include="td\td\telegram\files\FileId.hpp" />
<ClInclude Include="td\td\telegram\files\FileManager.hpp" />
<ClInclude Include="td\td\telegram\Game.hpp" />
- <ClInclude Include="td\td\telegram\Payments.hpp" />
<ClInclude Include="td\td\telegram\Photo.hpp" />
<ClInclude Include="td\td\telegram\ReplyMarkup.hpp" />
<ClInclude Include="td\td\telegram\StickersManager.hpp" />
@@ -294,21 +285,11 @@
<Project>{2246C3CF-7888-3102-984A-80214ADF418C}</Project>
<Name>tdnet</Name>
</ProjectReference>
- <ProjectReference Include="tdsqlite.vcxproj">
- <Project>{4FA94C32-60A9-33CC-B822-9BB1BDDD34FD}</Project>
- <Name>tdsqlite</Name>
- </ProjectReference>
<ProjectReference Include="tdutils.vcxproj">
<Project>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</Project>
<Name>tdutils</Name>
</ProjectReference>
</ItemGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
- <ItemGroup>
- <ClCompile Include="src\stdafx.cxx">
- <PrecompiledHeader>Create</PrecompiledHeader>
- </ClCompile>
- </ItemGroup>
</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/tdlib.vcxproj.filters b/protocols/Telegram/tdlib/tdlib.vcxproj.filters
index 932cb27fd2..393fec68fa 100644
--- a/protocols/Telegram/tdlib/tdlib.vcxproj.filters
+++ b/protocols/Telegram/tdlib/tdlib.vcxproj.filters
@@ -4,9 +4,6 @@
<ClCompile Include="td\td\mtproto\AuthData.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="td\td\mtproto\crypto.cpp">
- <Filter>Source Files</Filter>
- </ClCompile>
<ClCompile Include="td\td\mtproto\Handshake.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -61,9 +58,6 @@
<ClCompile Include="td\td\telegram\ConfigManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="td\td\telegram\ConfigShared.cpp">
- <Filter>Source Files</Filter>
- </ClCompile>
<ClCompile Include="td\td\telegram\Contact.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -157,9 +151,6 @@
<ClCompile Include="td\td\telegram\MessageEntity.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="td\td\telegram\MessagesDb.cpp">
- <Filter>Source Files</Filter>
- </ClCompile>
<ClCompile Include="td\td\telegram\MessagesManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -187,9 +178,6 @@
<ClCompile Include="td\td\telegram\net\NetQuery.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="td\td\telegram\net\NetQueryCounter.cpp">
- <Filter>Source Files</Filter>
- </ClCompile>
<ClCompile Include="td\td\telegram\net\NetQueryCreator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -297,9 +285,6 @@
<ClInclude Include="td\td\mtproto\AuthKey.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="td\td\mtproto\crypto.h">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="td\td\mtproto\CryptoStorer.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -381,9 +366,6 @@
<ClInclude Include="td\td\telegram\ConfigManager.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="td\td\telegram\ConfigShared.h">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="td\td\telegram\Contact.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -501,9 +483,6 @@
<ClInclude Include="td\td\telegram\MessageId.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="td\td\telegram\MessagesDb.h">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="td\td\telegram\MessagesManager.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -681,9 +660,6 @@
<ClInclude Include="td\td\telegram\Game.hpp">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="td\td\telegram\Payments.hpp">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="td\td\telegram\Photo.hpp">
<Filter>Header Files</Filter>
</ClInclude>
@@ -744,4 +720,4 @@
<UniqueIdentifier>{CC4593AA-1CC3-37C8-BDF9-C5986B1808BD}</UniqueIdentifier>
</Filter>
</ItemGroup>
-</Project>
+</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/tdnet.vcxproj b/protocols/Telegram/tdlib/tdnet.vcxproj
new file mode 100644
index 0000000000..c858bf5a60
--- /dev/null
+++ b/protocols/Telegram/tdlib/tdnet.vcxproj
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{2246C3CF-7888-3102-984A-80214ADF418C}</ProjectGuid>
+ <ProjectName>tdnet</ProjectName>
+ <GenerateManifest>false</GenerateManifest>
+ <EmbedManifest>false</EmbedManifest>
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ </PropertyGroup>
+ <Import Project="..\..\..\build\vc.common\common.props" />
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>.\td\tdnet;..\..\..\include;.\td\tdutils;.\td\tdactor;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+ <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="td\tdnet\td\net\GetHostByNameActor.cpp" />
+ <ClCompile Include="td\tdnet\td\net\HttpChunkedByteFlow.cpp" />
+ <ClCompile Include="td\tdnet\td\net\HttpConnectionBase.cpp" />
+ <ClCompile Include="td\tdnet\td\net\HttpContentLengthByteFlow.cpp" />
+ <ClCompile Include="td\tdnet\td\net\HttpFile.cpp" />
+ <ClCompile Include="td\tdnet\td\net\HttpInboundConnection.cpp" />
+ <ClCompile Include="td\tdnet\td\net\HttpOutboundConnection.cpp" />
+ <ClCompile Include="td\tdnet\td\net\HttpQuery.cpp" />
+ <ClCompile Include="td\tdnet\td\net\HttpReader.cpp" />
+ <ClCompile Include="td\tdnet\td\net\Socks5.cpp" />
+ <ClCompile Include="td\tdnet\td\net\TcpListener.cpp" />
+ <ClCompile Include="td\tdnet\td\net\Wget.cpp" />
+ <ClInclude Include="td\tdnet\td\net\GetHostByNameActor.h" />
+ <ClInclude Include="td\tdnet\td\net\HttpChunkedByteFlow.h" />
+ <ClInclude Include="td\tdnet\td\net\HttpConnectionBase.h" />
+ <ClInclude Include="td\tdnet\td\net\HttpContentLengthByteFlow.h" />
+ <ClInclude Include="td\tdnet\td\net\HttpFile.h" />
+ <ClInclude Include="td\tdnet\td\net\HttpHeaderCreator.h" />
+ <ClInclude Include="td\tdnet\td\net\HttpInboundConnection.h" />
+ <ClInclude Include="td\tdnet\td\net\HttpOutboundConnection.h" />
+ <ClInclude Include="td\tdnet\td\net\HttpQuery.h" />
+ <ClInclude Include="td\tdnet\td\net\HttpReader.h" />
+ <ClInclude Include="td\tdnet\td\net\NetStats.h" />
+ <ClInclude Include="td\tdnet\td\net\Socks5.h" />
+ <ClInclude Include="td\tdnet\td\net\TcpListener.h" />
+ <ClInclude Include="td\tdnet\td\net\Wget.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="tdactor.vcxproj">
+ <Project>{85F63934-02FE-332A-8703-059040B65512}</Project>
+ <Name>tdactor</Name>
+ </ProjectReference>
+ <ProjectReference Include="tdutils.vcxproj">
+ <Project>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</Project>
+ <Name>tdutils</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tdnet.vcxproj.filters b/protocols/Telegram/tdlib/tdnet.vcxproj.filters
index 8e762041c9..8e762041c9 100644
--- a/protocols/Telegram/tdlib/build/tdnet.vcxproj.filters
+++ b/protocols/Telegram/tdlib/tdnet.vcxproj.filters
diff --git a/protocols/Telegram/tdlib/tdutils.vcxproj b/protocols/Telegram/tdlib/tdutils.vcxproj
new file mode 100644
index 0000000000..0eae44e208
--- /dev/null
+++ b/protocols/Telegram/tdlib/tdutils.vcxproj
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{D21C6A0F-BED1-3377-9659-7FC7D82EFC4F}</ProjectGuid>
+ <ProjectName>tdutils</ProjectName>
+ <GenerateManifest>false</GenerateManifest>
+ <EmbedManifest>false</EmbedManifest>
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ </PropertyGroup>
+ <Import Project="..\..\..\build\vc.common\common.props" />
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>.\td\tdutils;..\..\..\include;..\..\..\libs\zlib\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
+ <DisableSpecificWarnings>4100;4127;4324;4505;4702</DisableSpecificWarnings>
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="td\tdutils\td\utils\port\Clocks.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\FileFd.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\IPAddress.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\path.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\ServerSocketFd.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\signals.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\sleep.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\SocketFd.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\Stat.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\thread_local.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\wstring_convert.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\detail\Epoll.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\detail\EventFdBsd.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\detail\EventFdLinux.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\detail\EventFdWindows.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\detail\KQueue.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\detail\Poll.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\detail\Select.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\detail\ThreadIdGuard.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\port\detail\WineventPoll.cpp" />
+ <ClCompile Include="td\tdutils\generate\auto\mime_type_to_extension.cpp" />
+ <ClCompile Include="td\tdutils\generate\auto\extension_to_mime_type.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\base64.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\BigNum.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\buffer.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\crypto.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\FileLog.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\filesystem.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\find_boundary.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\Gzip.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\GzipByteFlow.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\Hints.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\HttpUrl.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\JsonBuilder.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\logging.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\misc.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\MimeType.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\Random.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\StackAllocator.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\Status.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\StringBuilder.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\Time.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\Timer.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\tl_parsers.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\unicode.cpp" />
+ <ClCompile Include="td\tdutils\td\utils\utf8.cpp" />
+ <ClInclude Include="td\tdutils\td\utils\port\Clocks.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\config.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\CxCli.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\EventFd.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\EventFdBase.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\FileFd.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\IPAddress.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\path.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\platform.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\Poll.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\PollBase.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\RwMutex.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\ServerSocketFd.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\signals.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\sleep.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\SocketFd.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\Stat.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\thread.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\thread_local.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\wstring_convert.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\Epoll.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\EventFdBsd.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\EventFdLinux.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\EventFdWindows.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\KQueue.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\Poll.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\Select.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\ThreadIdGuard.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\ThreadPthread.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\ThreadStl.h" />
+ <ClInclude Include="td\tdutils\td\utils\port\detail\WineventPoll.h" />
+ <ClInclude Include="td\tdutils\td\utils\AesCtrByteFlow.h" />
+ <ClInclude Include="td\tdutils\td\utils\base64.h" />
+ <ClInclude Include="td\tdutils\td\utils\benchmark.h" />
+ <ClInclude Include="td\tdutils\td\utils\BigNum.h" />
+ <ClInclude Include="td\tdutils\td\utils\buffer.h" />
+ <ClInclude Include="td\tdutils\td\utils\BufferedFd.h" />
+ <ClInclude Include="td\tdutils\td\utils\BufferedReader.h" />
+ <ClInclude Include="td\tdutils\td\utils\ByteFlow.h" />
+ <ClInclude Include="td\tdutils\td\utils\ChangesProcessor.h" />
+ <ClInclude Include="td\tdutils\td\utils\Closure.h" />
+ <ClInclude Include="td\tdutils\td\utils\common.h" />
+ <ClInclude Include="td\tdutils\td\utils\Container.h" />
+ <ClInclude Include="td\tdutils\td\utils\crypto.h" />
+ <ClInclude Include="td\tdutils\td\utils\Enumerator.h" />
+ <ClInclude Include="td\tdutils\td\utils\FileLog.h" />
+ <ClInclude Include="td\tdutils\td\utils\filesystem.h" />
+ <ClInclude Include="td\tdutils\td\utils\find_boundary.h" />
+ <ClInclude Include="td\tdutils\td\utils\FloodControlFast.h" />
+ <ClInclude Include="td\tdutils\td\utils\FloodControlStrict.h" />
+ <ClInclude Include="td\tdutils\td\utils\format.h" />
+ <ClInclude Include="td\tdutils\td\utils\Gzip.h" />
+ <ClInclude Include="td\tdutils\td\utils\GzipByteFlow.h" />
+ <ClInclude Include="td\tdutils\td\utils\HazardPointers.h" />
+ <ClInclude Include="td\tdutils\td\utils\Heap.h" />
+ <ClInclude Include="td\tdutils\td\utils\Hints.h" />
+ <ClInclude Include="td\tdutils\td\utils\HttpUrl.h" />
+ <ClInclude Include="td\tdutils\td\utils\int_types.h" />
+ <ClInclude Include="td\tdutils\td\utils\invoke.h" />
+ <ClInclude Include="td\tdutils\td\utils\JsonBuilder.h" />
+ <ClInclude Include="td\tdutils\td\utils\List.h" />
+ <ClInclude Include="td\tdutils\td\utils\logging.h" />
+ <ClInclude Include="td\tdutils\td\utils\MemoryLog.h" />
+ <ClInclude Include="td\tdutils\td\utils\MimeType.h" />
+ <ClInclude Include="td\tdutils\td\utils\misc.h" />
+ <ClInclude Include="td\tdutils\td\utils\MovableValue.h" />
+ <ClInclude Include="td\tdutils\td\utils\MpmcQueue.h" />
+ <ClInclude Include="td\tdutils\td\utils\MpmcWaiter.h" />
+ <ClInclude Include="td\tdutils\td\utils\MpscPollableQueue.h" />
+ <ClInclude Include="td\tdutils\td\utils\MpscLinkQueue.h" />
+ <ClInclude Include="td\tdutils\td\utils\Named.h" />
+ <ClInclude Include="td\tdutils\td\utils\ObjectPool.h" />
+ <ClInclude Include="td\tdutils\td\utils\Observer.h" />
+ <ClInclude Include="td\tdutils\td\utils\optional.h" />
+ <ClInclude Include="td\tdutils\td\utils\OptionsParser.h" />
+ <ClInclude Include="td\tdutils\td\utils\OrderedEventsProcessor.h" />
+ <ClInclude Include="td\tdutils\td\utils\overloaded.h" />
+ <ClInclude Include="td\tdutils\td\utils\Parser.h" />
+ <ClInclude Include="td\tdutils\td\utils\PathView.h" />
+ <ClInclude Include="td\tdutils\td\utils\queue.h" />
+ <ClInclude Include="td\tdutils\td\utils\Random.h" />
+ <ClInclude Include="td\tdutils\td\utils\ScopeGuard.h" />
+ <ClInclude Include="td\tdutils\td\utils\SharedObjectPool.h" />
+ <ClInclude Include="td\tdutils\td\utils\Slice-decl.h" />
+ <ClInclude Include="td\tdutils\td\utils\Slice.h" />
+ <ClInclude Include="td\tdutils\td\utils\SpinLock.h" />
+ <ClInclude Include="td\tdutils\td\utils\StackAllocator.h" />
+ <ClInclude Include="td\tdutils\td\utils\Status.h" />
+ <ClInclude Include="td\tdutils\td\utils\Storer.h" />
+ <ClInclude Include="td\tdutils\td\utils\StorerBase.h" />
+ <ClInclude Include="td\tdutils\td\utils\StringBuilder.h" />
+ <ClInclude Include="td\tdutils\td\utils\tests.h" />
+ <ClInclude Include="td\tdutils\td\utils\Time.h" />
+ <ClInclude Include="td\tdutils\td\utils\TimedStat.h" />
+ <ClInclude Include="td\tdutils\td\utils\Timer.h" />
+ <ClInclude Include="td\tdutils\td\utils\tl_helpers.h" />
+ <ClInclude Include="td\tdutils\td\utils\tl_parsers.h" />
+ <ClInclude Include="td\tdutils\td\utils\tl_storers.h" />
+ <ClInclude Include="td\tdutils\td\utils\type_traits.h" />
+ <ClInclude Include="td\tdutils\td\utils\unicode.h" />
+ <ClInclude Include="td\tdutils\td\utils\utf8.h" />
+ <ClInclude Include="td\tdutils\td\utils\Variant.h" />
+ </ItemGroup>
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/protocols/Telegram/tdlib/build/tdutils.vcxproj.filters b/protocols/Telegram/tdlib/tdutils.vcxproj.filters
index e6f9fe816d..e6f9fe816d 100644
--- a/protocols/Telegram/tdlib/build/tdutils.vcxproj.filters
+++ b/protocols/Telegram/tdlib/tdutils.vcxproj.filters